diff --git a/openclaw-sensor-bridge/README.md b/openclaw-sensor-bridge/README.md index 89b97a4..6a9664b 100644 --- a/openclaw-sensor-bridge/README.md +++ b/openclaw-sensor-bridge/README.md @@ -37,7 +37,7 @@ Edit `~/.openclaw/openclaw.json` to include: "enabled": true, "token": "", "allowRequestSessionKey": true, - "allowedSessionKeyPrefixes": ["w2a:"] + "allowedSessionKeyPrefixes": ["hook:", "w2a:"] } ``` diff --git a/openclaw-sensor-bridge/package.json b/openclaw-sensor-bridge/package.json index 7c64677..eb0ba6f 100644 --- a/openclaw-sensor-bridge/package.json +++ b/openclaw-sensor-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@world2agent/openclaw-sensor-bridge", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "description": "World2Agent bridge for OpenClaw — runs sensors as supervised subprocesses and delivers their signals into OpenClaw via the gateway's /hooks/agent webhook", "license": "Apache-2.0", "author": "MachinePulse Pte. Ltd.", diff --git a/openclaw-sensor-bridge/skills/world2agent-manage/SKILL.md b/openclaw-sensor-bridge/skills/world2agent-manage/SKILL.md index bfb6e4a..24b3005 100644 --- a/openclaw-sensor-bridge/skills/world2agent-manage/SKILL.md +++ b/openclaw-sensor-bridge/skills/world2agent-manage/SKILL.md @@ -147,24 +147,37 @@ JSON ### Step 3: choose delivery target OpenClaw's `/hooks/agent` accepts a `deliver` flag that routes the agent's -reply to a real channel. Three options: - -| Mode | Effect | Pick when | -|---|---|---| -| auto-push to paired channel (**default**) | Agent runs, reply auto-delivered via OpenClaw's outbound layer. `install-sensor.sh` auto-detects the first `_HOME_CHANNEL=` entry in `~/.openclaw/.env` (priority: feishu, imessage, telegram, slack, discord, signal, whatsapp, wecom, dingtalk) and uses that as the target. | User has already paired a chat platform with OpenClaw — the env var is the user's signal that "this is my preferred inbox." | -| dashboard-only | Agent runs, reply persists to the W2A session lane only. User has to open the dashboard / `openclaw sessions` to see it. | No paired channel, or user explicitly wants the handler skill to gate notifications via `imsg`/`feishu`/etc. tool calls of its own. | -| explicit `--notify-channel --notify-to ` | Same as auto-push but the user picks the channel/handle. | User has multiple paired channels and wants this sensor on a non-default one. | - -**Default behavior:** if the user doesn't bring delivery up, -`install-sensor.sh` auto-fills `--notify-channel` / `--notify-to` from the -home-channel env vars and the agent reply is pushed to that chat. Only -when no `_HOME_CHANNEL` is set does it fall back to -dashboard-only. So **don't ask the user about delivery unless they raise -it** — a paired channel is a strong signal they've already chosen their -preferred inbox. - -Pass `--notify-channel`/`--notify-to` explicitly to override, or omit -both on a host with no paired channels for dashboard-only. +reply to a real chat. Without it, signals only land in the OpenClaw +dashboard's session lane and the user has to manually open the dashboard +to see them — which is almost never what they want. + +**You MUST ask the user about delivery before installing**, translating +into their conversation language (per "Conversation language" above). +Three short questions, in order: + +1. *"When a signal arrives, do you want the reply pushed to a chat + (so you'll actually be notified), or only logged to the OpenClaw + dashboard (you'd have to open it yourself to see anything)?"* + - Push → continue to Q2. + - Dashboard-only → skip Q2/Q3, go to Step 4. +2. *"Which platform should the reply go to?"* — accept one of: + `feishu`, `telegram`, `discord`, `slack`, `imessage`, `wecom`, + `dingtalk`, `signal`, `whatsapp`. +3. *"What's the target ID on ``? For feishu that's your + user `open_id` (`ou_xxx`) or a group `chat_id` (`oc_xxx`); if you + don't know yours, run `/lark-contact` to look up your own `open_id`. + For other platforms, the corresponding user or group identifier."* + +Then in Step 5 pass `--notify-channel --notify-to ` for push +mode, or pass neither for dashboard-only. + +**Do not** read or write `~/.openclaw/.env` from this SKILL. The script +keeps a `_HOME_CHANNEL` auto-fill as a power-user shortcut for +people who'd rather configure once than answer per-install — but treat +that as an invisible internal optimization. **Always ask the three +questions above**, even if you suspect `.env` might be set up. If the +script's auto-detect kicks in despite the user's answer, the explicit +CLI flags you pass take precedence. ### Step 4: compose the handler SKILL.md @@ -257,7 +270,12 @@ Successful output: "session_key": "w2a:hackernews", "agent_id": "main", "skill_path": "/.../SKILL.md", - "supervisor_reload": { "ok": true, "applied": {"started":[...]} } | null + "supervisor_reload": { "ok": true, "applied": {"started":[...]} } | null, + "delivery": { + "mode": "push" | "dashboard-only", + "channel": "feishu" | null, + "to": "ou_xxx" | null + } } ``` @@ -265,16 +283,36 @@ Successful output: reachable from this process — that's fine, the file watcher picks up the new `~/.world2agent/config.json` entry within ~500 ms anyway. +`delivery.mode` reflects what the script actually wired up — read it +verbatim and report it back in Step 6 (don't assume the user's Step 3 +answer succeeded; the `.env` auto-fill could have kicked in unexpectedly, +or — more importantly — the user could have answered "push" but you +forgot to pass the flags). + If the install script refuses with a frontmatter mismatch, fix the rendered handler's `name` and retry. ### Step 6: report to the user -One sentence: `Installed (sensor_id ); next matching -signal will trigger an agent run on session lane agent::.` - -If they configured a notify target, add: `replies will be delivered to -:`. +Branch on `result.delivery.mode`: + +- **`mode == "push"`**: + *"Installed `` (sensor_id ``). Replies will be + delivered to `:`. The next matching + signal will trigger an agent run on session lane + `agent::`."* + +- **`mode == "dashboard-only"`** (⚠️ user MUST be told explicitly): + *"Installed `` (sensor_id ``), but **no IM target + configured** — replies will only land in the OpenClaw dashboard + (session lane `agent::`); you won't get a + notification anywhere. To enable push later: re-run + `/world2agent:sensor-add` and answer "push" to the delivery question, + or run `world2agent-manage` directly with + `--notify-channel --notify-to `."* + +The dashboard-only case is the one that silently bites users — never +report it as a plain "installed!" without spelling out the consequence. --- diff --git a/openclaw-sensor-bridge/skills/world2agent-manage/scripts/_lib.sh b/openclaw-sensor-bridge/skills/world2agent-manage/scripts/_lib.sh index de173c0..68f0e8d 100644 --- a/openclaw-sensor-bridge/skills/world2agent-manage/scripts/_lib.sh +++ b/openclaw-sensor-bridge/skills/world2agent-manage/scripts/_lib.sh @@ -233,7 +233,13 @@ openclaw_hooks_ready() { # - set hooks.enabled = true # - generate hooks.token if empty (32 hex chars from /dev/urandom) # - set hooks.allowRequestSessionKey = true -# - ensure "w2a:" is present in hooks.allowedSessionKeyPrefixes +# - ensure both "hook:" and "w2a:" are present in +# hooks.allowedSessionKeyPrefixes +# +# `hook:` is OpenClaw's own default sessionKey namespace — without it in the +# allowlist, the gateway refuses to start (`hooks.allowedSessionKeyPrefixes +# must include 'hook:' when hooks.defaultSessionKey is unset`). `w2a:` is the +# bridge's per-sensor lane. # # A timestamped backup is written next to the file before any mutation. # Stdout: "noop" when nothing changed, or "wrote:" on mutation. @@ -251,15 +257,17 @@ ensure_openclaw_hooks() { return 1 fi - local enabled token allow has_w2a + local enabled token allow has_hook has_w2a enabled=$(jq -r '.hooks.enabled // false' "$cfg") token=$(jq -r '.hooks.token // ""' "$cfg") allow=$(jq -r '.hooks.allowRequestSessionKey // false' "$cfg") + has_hook=$(jq -r '((.hooks.allowedSessionKeyPrefixes // []) | any(. == "hook:"))' "$cfg") has_w2a=$(jq -r '((.hooks.allowedSessionKeyPrefixes // []) | any(. == "w2a:"))' "$cfg") local changed=false [ "$enabled" != "true" ] && changed=true [ "$allow" != "true" ] && changed=true + [ "$has_hook" != "true" ] && changed=true [ "$has_w2a" != "true" ] && changed=true [ -z "$token" ] && changed=true @@ -283,7 +291,8 @@ ensure_openclaw_hooks() { token: ((.hooks.token // "") | if . == "" then $token else . end), allowedSessionKeyPrefixes: ( ((.hooks.allowedSessionKeyPrefixes // []) | map(select(type == "string"))) - | if any(. == "w2a:") then . else . + ["w2a:"] end + | if any(. == "hook:") then . else . + ["hook:"] end + | if any(. == "w2a:") then . else . + ["w2a:"] end ) }) ' "$cfg" >"$tmp"; then diff --git a/openclaw-sensor-bridge/skills/world2agent-manage/scripts/bootstrap.sh b/openclaw-sensor-bridge/skills/world2agent-manage/scripts/bootstrap.sh index df6e2eb..3b37575 100644 --- a/openclaw-sensor-bridge/skills/world2agent-manage/scripts/bootstrap.sh +++ b/openclaw-sensor-bridge/skills/world2agent-manage/scripts/bootstrap.sh @@ -58,7 +58,7 @@ add_step state "$([ "$state_existed" = true ] && echo "present" || echo "created # we surface that via gateway_restart_needed in the output. hooks_action=$(ensure_openclaw_hooks 2>/tmp/.w2a-hooks-err) || { err=$(cat /tmp/.w2a-hooks-err 2>/dev/null); rm -f /tmp/.w2a-hooks-err - out_err "could not configure OpenClaw hooks: ${err:-unknown error}. Edit $(openclaw_config_path) manually to set hooks.enabled=true, hooks.token=\"\", hooks.allowRequestSessionKey=true, hooks.allowedSessionKeyPrefixes=[\"w2a:\"]; then restart the gateway." + out_err "could not configure OpenClaw hooks: ${err:-unknown error}. Edit $(openclaw_config_path) manually to set hooks.enabled=true, hooks.token=\"\", hooks.allowRequestSessionKey=true, hooks.allowedSessionKeyPrefixes=[\"hook:\",\"w2a:\"]; then restart the gateway." } rm -f /tmp/.w2a-hooks-err gateway_restart_needed=false diff --git a/openclaw-sensor-bridge/skills/world2agent-manage/scripts/install-sensor.sh b/openclaw-sensor-bridge/skills/world2agent-manage/scripts/install-sensor.sh index be4676b..5209223 100644 --- a/openclaw-sensor-bridge/skills/world2agent-manage/scripts/install-sensor.sh +++ b/openclaw-sensor-bridge/skills/world2agent-manage/scripts/install-sensor.sh @@ -245,6 +245,20 @@ if [ -z "$reload_result" ] || ! jq -e . <<<"$reload_result" >/dev/null 2>&1; the reload_result='null' fi +# Surface the resolved delivery target so the SKILL/agent can give the user +# accurate post-install feedback. Without this, a sensor that silently fell +# back to "dashboard-only" looks identical to a fully-wired push sensor in +# the script output — agent reports "installed!" and the user never realises +# signals aren't being pushed anywhere. +if [ -n "$notify_channel" ]; then + delivery_json=$(jq -nc \ + --arg ch "$notify_channel" \ + --arg to "$notify_to" \ + '{mode:"push", channel:$ch, to:$to}') +else + delivery_json='{"mode":"dashboard-only","channel":null,"to":null}' +fi + out_ok "$(jq -nc \ --arg pkg "$pkg" \ --arg sensor_id "$sensor_id" \ @@ -253,4 +267,5 @@ out_ok "$(jq -nc \ --arg agent_id "$agent_id" \ --arg skill_path "$skill_dir/SKILL.md" \ --argjson reload "$reload_result" \ - '{package:$pkg,sensor_id:$sensor_id,skill_id:$skill_id,session_key:$session_key,agent_id:$agent_id,skill_path:$skill_path,supervisor_reload:$reload}')" + --argjson delivery "$delivery_json" \ + '{package:$pkg,sensor_id:$sensor_id,skill_id:$skill_id,session_key:$session_key,agent_id:$agent_id,skill_path:$skill_path,supervisor_reload:$reload,delivery:$delivery}')"