@scotthuang/agent-knock-knock 0.12.11 → 0.12.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +43 -30
- package/dist/src/approval-policy.d.ts +11 -2
- package/dist/src/approval-policy.js +33 -7
- package/dist/src/approval-policy.js.map +1 -1
- package/dist/src/callback-cli-adapter.d.ts +47 -0
- package/dist/src/callback-cli-adapter.js +156 -0
- package/dist/src/callback-cli-adapter.js.map +1 -0
- package/dist/src/callback-outbox-policy.d.ts +95 -0
- package/dist/src/callback-outbox-policy.js +149 -0
- package/dist/src/callback-outbox-policy.js.map +1 -0
- package/dist/src/callback-outbox-service.d.ts +247 -0
- package/dist/src/callback-outbox-service.js +1053 -0
- package/dist/src/callback-outbox-service.js.map +1 -0
- package/dist/src/callback-outbox-settlement.d.ts +54 -0
- package/dist/src/callback-outbox-settlement.js +241 -0
- package/dist/src/callback-outbox-settlement.js.map +1 -0
- package/dist/src/canonical-json.d.ts +1 -0
- package/dist/src/canonical-json.js +14 -0
- package/dist/src/canonical-json.js.map +1 -0
- package/dist/src/claude-lifecycle-compatibility.js +15 -0
- package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
- package/dist/src/claude-local-transcript-provider.d.ts +82 -1
- package/dist/src/claude-local-transcript-provider.js +873 -23
- package/dist/src/claude-local-transcript-provider.js.map +1 -1
- package/dist/src/cli-command-runtime.d.ts +14 -0
- package/dist/src/cli-command-runtime.js +164 -0
- package/dist/src/cli-command-runtime.js.map +1 -0
- package/dist/src/cli-core.d.ts +4 -42
- package/dist/src/cli-core.js +958 -28684
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/cli-runtime-context.d.ts +57 -0
- package/dist/src/cli-runtime-context.js +118 -0
- package/dist/src/cli-runtime-context.js.map +1 -0
- package/dist/src/codex-lifecycle-compatibility.js +2 -1
- package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
- package/dist/src/codex-local-session-provider.d.ts +18 -0
- package/dist/src/codex-local-session-provider.js +67 -0
- package/dist/src/codex-local-session-provider.js.map +1 -1
- package/dist/src/codex-session-provider.js +1 -3
- package/dist/src/codex-session-provider.js.map +1 -1
- package/dist/src/conversation-trace.d.ts +30 -0
- package/dist/src/conversation-trace.js +185 -0
- package/dist/src/conversation-trace.js.map +1 -0
- package/dist/src/deferred-foreground-application-service.d.ts +84 -0
- package/dist/src/deferred-foreground-application-service.js +742 -0
- package/dist/src/deferred-foreground-application-service.js.map +1 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +78 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.js +795 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -0
- package/dist/src/deferred-foreground-boundary.d.ts +61 -0
- package/dist/src/deferred-foreground-boundary.js +2 -0
- package/dist/src/deferred-foreground-boundary.js.map +1 -0
- package/dist/src/deferred-foreground-capability.d.ts +4 -0
- package/dist/src/deferred-foreground-capability.js +184 -0
- package/dist/src/deferred-foreground-capability.js.map +1 -0
- package/dist/src/deferred-foreground-preparation-cli-adapter.d.ts +53 -0
- package/dist/src/deferred-foreground-preparation-cli-adapter.js +210 -0
- package/dist/src/deferred-foreground-preparation-cli-adapter.js.map +1 -0
- package/dist/src/deferred-foreground-preparation-service.d.ts +72 -0
- package/dist/src/deferred-foreground-preparation-service.js +179 -0
- package/dist/src/deferred-foreground-preparation-service.js.map +1 -0
- package/dist/src/deferred-foreground-recovery-cli-adapter.d.ts +139 -0
- package/dist/src/deferred-foreground-recovery-cli-adapter.js +1125 -0
- package/dist/src/deferred-foreground-recovery-cli-adapter.js.map +1 -0
- package/dist/src/deferred-foreground-recovery-service.d.ts +48 -0
- package/dist/src/deferred-foreground-recovery-service.js +91 -0
- package/dist/src/deferred-foreground-recovery-service.js.map +1 -0
- package/dist/src/deferred-foreground-transfer-policy.d.ts +2 -0
- package/dist/src/deferred-foreground-transfer-policy.js +10 -0
- package/dist/src/deferred-foreground-transfer-policy.js.map +1 -0
- package/dist/src/deferred-foreground-transfer.js +46 -105
- package/dist/src/deferred-foreground-transfer.js.map +1 -1
- package/dist/src/doctor-capabilities.d.ts +9 -0
- package/dist/src/doctor-capabilities.js +22 -0
- package/dist/src/doctor-capabilities.js.map +1 -1
- package/dist/src/durable-json-file.d.ts +22 -0
- package/dist/src/durable-json-file.js +127 -0
- package/dist/src/durable-json-file.js.map +1 -0
- package/dist/src/file-lock-cli-adapter.d.ts +43 -0
- package/dist/src/file-lock-cli-adapter.js +150 -0
- package/dist/src/file-lock-cli-adapter.js.map +1 -0
- package/dist/src/herdr-terminal-control-provider.js +1 -3
- package/dist/src/herdr-terminal-control-provider.js.map +1 -1
- package/dist/src/install-doctor-command-adapter.d.ts +17 -0
- package/dist/src/install-doctor-command-adapter.js +225 -0
- package/dist/src/install-doctor-command-adapter.js.map +1 -0
- package/dist/src/live-lifecycle-evidence.js +76 -46
- package/dist/src/live-lifecycle-evidence.js.map +1 -1
- package/dist/src/live-lifecycle-smoke.js +74 -50
- package/dist/src/live-lifecycle-smoke.js.map +1 -1
- package/dist/src/managed-session.d.ts +2 -0
- package/dist/src/managed-session.js +24 -0
- package/dist/src/managed-session.js.map +1 -1
- package/dist/src/managed-turn-recovery-service.d.ts +86 -0
- package/dist/src/managed-turn-recovery-service.js +145 -0
- package/dist/src/managed-turn-recovery-service.js.map +1 -0
- package/dist/src/mutation-transaction.d.ts +48 -0
- package/dist/src/mutation-transaction.js +73 -0
- package/dist/src/mutation-transaction.js.map +1 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +154 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js +786 -0
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -0
- package/dist/src/native-thread-lifecycle-ledger-cli-adapter.d.ts +25 -0
- package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js +50 -0
- package/dist/src/native-thread-lifecycle-ledger-cli-adapter.js.map +1 -0
- package/dist/src/native-thread-lifecycle-query-service.d.ts +75 -0
- package/dist/src/native-thread-lifecycle-query-service.js +297 -0
- package/dist/src/native-thread-lifecycle-query-service.js.map +1 -0
- package/dist/src/native-thread-lifecycle-recovery-adapter.d.ts +45 -0
- package/dist/src/native-thread-lifecycle-recovery-adapter.js +154 -0
- package/dist/src/native-thread-lifecycle-recovery-adapter.js.map +1 -0
- package/dist/src/native-thread-lifecycle-recovery-service.d.ts +211 -0
- package/dist/src/native-thread-lifecycle-recovery-service.js +1093 -0
- package/dist/src/native-thread-lifecycle-recovery-service.js.map +1 -0
- package/dist/src/native-thread-resume-snapshot-policy.d.ts +73 -0
- package/dist/src/native-thread-resume-snapshot-policy.js +218 -0
- package/dist/src/native-thread-resume-snapshot-policy.js.map +1 -0
- package/dist/src/native-thread-resume-snapshot.d.ts +3 -61
- package/dist/src/native-thread-resume-snapshot.js +2 -143
- package/dist/src/native-thread-resume-snapshot.js.map +1 -1
- package/dist/src/native-thread-transition-application.d.ts +147 -0
- package/dist/src/native-thread-transition-application.js +974 -0
- package/dist/src/native-thread-transition-application.js.map +1 -0
- package/dist/src/native-thread-transition-policy.d.ts +127 -0
- package/dist/src/native-thread-transition-policy.js +154 -0
- package/dist/src/native-thread-transition-policy.js.map +1 -0
- package/dist/src/native-thread-transition-resource-adapter.d.ts +12 -0
- package/dist/src/native-thread-transition-resource-adapter.js +44 -0
- package/dist/src/native-thread-transition-resource-adapter.js.map +1 -0
- package/dist/src/native-thread-transition-settlement-service.d.ts +94 -0
- package/dist/src/native-thread-transition-settlement-service.js +134 -0
- package/dist/src/native-thread-transition-settlement-service.js.map +1 -0
- package/dist/src/native-thread-transition-verification-adapter.d.ts +62 -0
- package/dist/src/native-thread-transition-verification-adapter.js +451 -0
- package/dist/src/native-thread-transition-verification-adapter.js.map +1 -0
- package/dist/src/openclaw-callback-transport.d.ts +69 -0
- package/dist/src/openclaw-callback-transport.js +583 -0
- package/dist/src/openclaw-callback-transport.js.map +1 -0
- package/dist/src/openclaw-plugin-callback-adapter.d.ts +1 -0
- package/dist/src/openclaw-plugin-callback-adapter.js +423 -0
- package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -0
- package/dist/src/openclaw-plugin-command-adapter.d.ts +16 -0
- package/dist/src/openclaw-plugin-command-adapter.js +1868 -0
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -0
- package/dist/src/openclaw-plugin-helpers.d.ts +27 -4
- package/dist/src/openclaw-plugin-helpers.js +356 -81
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +358 -0
- package/dist/src/openclaw-plugin-schemas.js +366 -0
- package/dist/src/openclaw-plugin-schemas.js.map +1 -0
- package/dist/src/openclaw-plugin-supervisor.d.ts +6 -0
- package/dist/src/openclaw-plugin-supervisor.js +123 -0
- package/dist/src/openclaw-plugin-supervisor.js.map +1 -0
- package/dist/src/openclaw-plugin.js +7 -2212
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/openclaw-private-authority-offers.d.ts +22 -0
- package/dist/src/openclaw-private-authority-offers.js +177 -0
- package/dist/src/openclaw-private-authority-offers.js.map +1 -0
- package/dist/src/protocol.d.ts +5 -0
- package/dist/src/protocol.js +59 -3
- package/dist/src/protocol.js.map +1 -1
- package/dist/src/session-store.d.ts +1 -0
- package/dist/src/session-store.js +84 -106
- package/dist/src/session-store.js.map +1 -1
- package/dist/src/store.d.ts +1 -0
- package/dist/src/store.js +5 -2
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-acceptance-application-service.d.ts +65 -0
- package/dist/src/terminal-acceptance-application-service.js +63 -0
- package/dist/src/terminal-acceptance-application-service.js.map +1 -0
- package/dist/src/terminal-acceptance-cli-adapter.d.ts +210 -0
- package/dist/src/terminal-acceptance-cli-adapter.js +957 -0
- package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -0
- package/dist/src/terminal-action-projection.d.ts +309 -0
- package/dist/src/terminal-action-projection.js +376 -0
- package/dist/src/terminal-action-projection.js.map +1 -0
- package/dist/src/terminal-agent-bridge.d.ts +3 -0
- package/dist/src/terminal-agent-bridge.js +29 -8
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-authority-policy.d.ts +141 -0
- package/dist/src/terminal-authority-policy.js +367 -0
- package/dist/src/terminal-authority-policy.js.map +1 -0
- package/dist/src/terminal-binding-authority.d.ts +118 -0
- package/dist/src/terminal-binding-authority.js +245 -0
- package/dist/src/terminal-binding-authority.js.map +1 -0
- package/dist/src/terminal-binding-reconciliation-service.d.ts +75 -0
- package/dist/src/terminal-binding-reconciliation-service.js +73 -0
- package/dist/src/terminal-binding-reconciliation-service.js.map +1 -0
- package/dist/src/terminal-command-cli-adapter.d.ts +380 -0
- package/dist/src/terminal-command-cli-adapter.js +3288 -0
- package/dist/src/terminal-command-cli-adapter.js.map +1 -0
- package/dist/src/terminal-control-ref.d.ts +4 -0
- package/dist/src/terminal-control-ref.js +11 -8
- package/dist/src/terminal-control-ref.js.map +1 -1
- package/dist/src/terminal-delegate-cli-adapter.d.ts +41 -0
- package/dist/src/terminal-delegate-cli-adapter.js +242 -0
- package/dist/src/terminal-delegate-cli-adapter.js.map +1 -0
- package/dist/src/terminal-dispatch-abort.d.ts +22 -0
- package/dist/src/terminal-dispatch-abort.js +30 -0
- package/dist/src/terminal-dispatch-abort.js.map +1 -0
- package/dist/src/terminal-dispatch-application.d.ts +115 -0
- package/dist/src/terminal-dispatch-application.js +478 -0
- package/dist/src/terminal-dispatch-application.js.map +1 -0
- package/dist/src/terminal-dispatch-capability.d.ts +63 -0
- package/dist/src/terminal-dispatch-capability.js +133 -0
- package/dist/src/terminal-dispatch-capability.js.map +1 -0
- package/dist/src/terminal-dispatch-completion-cli-adapter.d.ts +22 -0
- package/dist/src/terminal-dispatch-completion-cli-adapter.js +96 -0
- package/dist/src/terminal-dispatch-completion-cli-adapter.js.map +1 -0
- package/dist/src/terminal-dispatch-composition.d.ts +79 -0
- package/dist/src/terminal-dispatch-composition.js +2 -0
- package/dist/src/terminal-dispatch-composition.js.map +1 -0
- package/dist/src/terminal-dispatch-execution.d.ts +290 -0
- package/dist/src/terminal-dispatch-execution.js +916 -0
- package/dist/src/terminal-dispatch-execution.js.map +1 -0
- package/dist/src/terminal-dispatch-ledger-codec.d.ts +110 -0
- package/dist/src/terminal-dispatch-ledger-codec.js +399 -0
- package/dist/src/terminal-dispatch-ledger-codec.js.map +1 -0
- package/dist/src/terminal-dispatch-policy.d.ts +93 -0
- package/dist/src/terminal-dispatch-policy.js +122 -0
- package/dist/src/terminal-dispatch-policy.js.map +1 -0
- package/dist/src/terminal-dispatch-presenter.d.ts +65 -0
- package/dist/src/terminal-dispatch-presenter.js +258 -0
- package/dist/src/terminal-dispatch-presenter.js.map +1 -0
- package/dist/src/terminal-dispatch-receipt.d.ts +79 -0
- package/dist/src/terminal-dispatch-receipt.js +314 -0
- package/dist/src/terminal-dispatch-receipt.js.map +1 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.d.ts +132 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js +1330 -0
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -0
- package/dist/src/terminal-dispatch-recovery-service.d.ts +271 -0
- package/dist/src/terminal-dispatch-recovery-service.js +374 -0
- package/dist/src/terminal-dispatch-recovery-service.js.map +1 -0
- package/dist/src/terminal-dispatch-repository-cli-adapter.d.ts +35 -0
- package/dist/src/terminal-dispatch-repository-cli-adapter.js +483 -0
- package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -0
- package/dist/src/terminal-handoff-application-service.d.ts +7 -0
- package/dist/src/terminal-handoff-application-service.js +18 -0
- package/dist/src/terminal-handoff-application-service.js.map +1 -0
- package/dist/src/terminal-handoff-cli-adapter.d.ts +229 -0
- package/dist/src/terminal-handoff-cli-adapter.js +1389 -0
- package/dist/src/terminal-handoff-cli-adapter.js.map +1 -0
- package/dist/src/terminal-handoff-facts.d.ts +12 -0
- package/dist/src/terminal-handoff-facts.js +16 -0
- package/dist/src/terminal-handoff-facts.js.map +1 -0
- package/dist/src/terminal-identity-authority-cli-adapter.d.ts +318 -0
- package/dist/src/terminal-identity-authority-cli-adapter.js +1072 -0
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -0
- package/dist/src/terminal-identity-authority-service.d.ts +48 -0
- package/dist/src/terminal-identity-authority-service.js +45 -0
- package/dist/src/terminal-identity-authority-service.js.map +1 -0
- package/dist/src/terminal-list-cli-adapter.d.ts +274 -0
- package/dist/src/terminal-list-cli-adapter.js +2444 -0
- package/dist/src/terminal-list-cli-adapter.js.map +1 -0
- package/dist/src/terminal-list-renderer.d.ts +34 -0
- package/dist/src/terminal-list-renderer.js +643 -0
- package/dist/src/terminal-list-renderer.js.map +1 -0
- package/dist/src/terminal-maintenance-cli-adapter.d.ts +102 -0
- package/dist/src/terminal-maintenance-cli-adapter.js +1059 -0
- package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -0
- package/dist/src/terminal-monitor-application-service.d.ts +275 -0
- package/dist/src/terminal-monitor-application-service.js +982 -0
- package/dist/src/terminal-monitor-application-service.js.map +1 -0
- package/dist/src/terminal-monitor-cli-adapter.d.ts +130 -0
- package/dist/src/terminal-monitor-cli-adapter.js +442 -0
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -0
- package/dist/src/terminal-monitor-decision-policy.d.ts +98 -0
- package/dist/src/terminal-monitor-decision-policy.js +246 -0
- package/dist/src/terminal-monitor-decision-policy.js.map +1 -0
- package/dist/src/terminal-monitor-launch-plan.d.ts +45 -0
- package/dist/src/terminal-monitor-launch-plan.js +94 -0
- package/dist/src/terminal-monitor-launch-plan.js.map +1 -0
- package/dist/src/terminal-monitor-ownership-policy.d.ts +31 -0
- package/dist/src/terminal-monitor-ownership-policy.js +64 -0
- package/dist/src/terminal-monitor-ownership-policy.js.map +1 -0
- package/dist/src/terminal-monitor-poll-policy.d.ts +48 -0
- package/dist/src/terminal-monitor-poll-policy.js +67 -0
- package/dist/src/terminal-monitor-poll-policy.js.map +1 -0
- package/dist/src/terminal-monitor-reconciliation-eligibility.d.ts +53 -0
- package/dist/src/terminal-monitor-reconciliation-eligibility.js +114 -0
- package/dist/src/terminal-monitor-reconciliation-eligibility.js.map +1 -0
- package/dist/src/terminal-monitor-state-cli-adapter.d.ts +106 -0
- package/dist/src/terminal-monitor-state-cli-adapter.js +1492 -0
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -0
- package/dist/src/terminal-monitor-state-reconciliation-service.d.ts +87 -0
- package/dist/src/terminal-monitor-state-reconciliation-service.js +106 -0
- package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -0
- package/dist/src/terminal-monitor-supervision-cli-adapter.d.ts +122 -0
- package/dist/src/terminal-monitor-supervision-cli-adapter.js +685 -0
- package/dist/src/terminal-monitor-supervision-cli-adapter.js.map +1 -0
- package/dist/src/terminal-process-facts.d.ts +2 -0
- package/dist/src/terminal-process-facts.js +19 -0
- package/dist/src/terminal-process-facts.js.map +1 -0
- package/dist/src/terminal-process-source.d.ts +6 -4
- package/dist/src/terminal-process-source.js +25 -20
- package/dist/src/terminal-process-source.js.map +1 -1
- package/dist/src/terminal-runtime-cli-adapter.d.ts +80 -0
- package/dist/src/terminal-runtime-cli-adapter.js +488 -0
- package/dist/src/terminal-runtime-cli-adapter.js.map +1 -0
- package/dist/src/terminal-scoped-approval-authority.d.ts +62 -0
- package/dist/src/terminal-scoped-approval-authority.js +281 -0
- package/dist/src/terminal-scoped-approval-authority.js.map +1 -0
- package/dist/src/terminal-selector-projection-service.d.ts +26 -0
- package/dist/src/terminal-selector-projection-service.js +81 -0
- package/dist/src/terminal-selector-projection-service.js.map +1 -0
- package/dist/src/terminal-status-cli-adapter.d.ts +126 -0
- package/dist/src/terminal-status-cli-adapter.js +577 -0
- package/dist/src/terminal-status-cli-adapter.js.map +1 -0
- package/dist/src/terminal-status-facts.d.ts +56 -0
- package/dist/src/terminal-status-facts.js +199 -0
- package/dist/src/terminal-status-facts.js.map +1 -0
- package/dist/src/terminal-submission-acceptance.d.ts +62 -145
- package/dist/src/terminal-submission-acceptance.js +555 -222
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-submission-facts.d.ts +123 -0
- package/dist/src/terminal-submission-facts.js +205 -0
- package/dist/src/terminal-submission-facts.js.map +1 -0
- package/dist/src/terminal-turn-binding-authority-cli-adapter.d.ts +14 -0
- package/dist/src/terminal-turn-binding-authority-cli-adapter.js +90 -0
- package/dist/src/terminal-turn-binding-authority-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-callback-cli-adapter.d.ts +20 -0
- package/dist/src/terminal-watch-callback-cli-adapter.js +55 -0
- package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-cli-adapter.d.ts +64 -0
- package/dist/src/terminal-watch-cli-adapter.js +508 -0
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -0
- package/dist/src/terminal-watch-service.d.ts +97 -0
- package/dist/src/terminal-watch-service.js +714 -0
- package/dist/src/terminal-watch-service.js.map +1 -0
- package/dist/src/terminal-watch-store.d.ts +149 -0
- package/dist/src/terminal-watch-store.js +861 -0
- package/dist/src/terminal-watch-store.js.map +1 -0
- package/dist/src/value-guards.d.ts +4 -0
- package/dist/src/value-guards.js +12 -0
- package/dist/src/value-guards.js.map +1 -0
- package/dist/src/verified-dead-agent-policy.d.ts +192 -0
- package/dist/src/verified-dead-agent-policy.js +262 -0
- package/dist/src/verified-dead-agent-policy.js.map +1 -0
- package/docs/quickstart-herdr.md +8 -0
- package/docs/quickstart-tmux.md +33 -6
- package/openclaw.plugin.json +11 -3
- package/package.json +3 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +58 -40
|
@@ -1,407 +1,7 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
1
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
const CALLBACK_METHOD = AKK_CALLBACK_METHOD;
|
|
9
|
-
const MONITOR_SUPERVISOR_INTERVAL_MS = 5_000;
|
|
10
|
-
const MAX_DISPLAYED_RESUME_SNAPSHOTS = 512;
|
|
11
|
-
const defaultBinPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
12
|
-
const relayPathByApi = new WeakMap();
|
|
13
|
-
const sendParameters = {
|
|
14
|
-
type: "object",
|
|
15
|
-
additionalProperties: false,
|
|
16
|
-
required: ["request"],
|
|
17
|
-
not: {
|
|
18
|
-
anyOf: [
|
|
19
|
-
{ required: ["session_id", "selector"] },
|
|
20
|
-
{ required: ["session_id", "expected_terminal_token"] }
|
|
21
|
-
]
|
|
22
|
-
},
|
|
23
|
-
properties: {
|
|
24
|
-
session_id: {
|
|
25
|
-
type: "string",
|
|
26
|
-
minLength: 1,
|
|
27
|
-
description: "Strict session-scoped AKK id only when the current list action prefills it. This preserves that exact native context and never follows the pane after a human switches threads. A rollout-backed Codex Session is a continuing context label but is not a direct ordinary-send target; use that terminal row's selector/token action instead. Discovery selectors, terminal ids, and turn ids are never session_id destinations."
|
|
28
|
-
},
|
|
29
|
-
selector: {
|
|
30
|
-
type: "string",
|
|
31
|
-
minLength: 1,
|
|
32
|
-
description: "Terminal-scoped selector: codex, claude, only, latest, an @short-ref, or a live terminal id. Use one only when explicitly named by the user or prefilled by list; never infer it. A follow-current handoff action uses the exact full terminal id together with expected_terminal_token. Legacy discovery selectors remain supported. Omit both target fields only when AKK should attach the unique eligible idle pane."
|
|
33
|
-
},
|
|
34
|
-
expected_terminal_token: {
|
|
35
|
-
type: "string",
|
|
36
|
-
minLength: 1,
|
|
37
|
-
description: "Fresh terminal snapshot token prefilled by the same terminal row's follow-current send action. Preserve it exactly with that action's full terminal selector; never infer, copy, reuse, or combine it with session_id."
|
|
38
|
-
},
|
|
39
|
-
request: {
|
|
40
|
-
type: "string",
|
|
41
|
-
minLength: 1,
|
|
42
|
-
description: "Message for the coding agent. Each accepted ordinary send creates a new turn inside the selected session without clearing native agent context."
|
|
43
|
-
},
|
|
44
|
-
type: {
|
|
45
|
-
type: "string",
|
|
46
|
-
enum: ["task"],
|
|
47
|
-
description: "Ordinary sends always create a task turn. Use agent_knock_knock_respond for an answer to an in-flight turn."
|
|
48
|
-
},
|
|
49
|
-
idleTimeoutMinutes: {
|
|
50
|
-
type: "number",
|
|
51
|
-
description: "Minutes an idle or completed AKK Turn record is retained before controlled reconciliation closes it."
|
|
52
|
-
},
|
|
53
|
-
agentTimeoutMinutes: {
|
|
54
|
-
type: "number",
|
|
55
|
-
description: "Callback timeout in minutes; terminal bridge tasks treat it as an inactivity timeout."
|
|
56
|
-
},
|
|
57
|
-
agentHardTimeoutMinutes: {
|
|
58
|
-
type: "number",
|
|
59
|
-
exclusiveMinimum: 0,
|
|
60
|
-
description: "Maximum terminal monitor lifetime in minutes."
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
const respondParameters = {
|
|
65
|
-
type: "object",
|
|
66
|
-
additionalProperties: false,
|
|
67
|
-
required: ["turn_id", "request"],
|
|
68
|
-
properties: {
|
|
69
|
-
turn_id: {
|
|
70
|
-
type: "string",
|
|
71
|
-
description: "Authoritative AKK turn id from a question or blocked callback, never a discovery selector or terminal id. A response continues this exact in-flight turn and does not create a new turn."
|
|
72
|
-
},
|
|
73
|
-
request: {
|
|
74
|
-
type: "string",
|
|
75
|
-
description: "Answer or decision for the coding agent's exact in-flight turn."
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
const listParameters = {
|
|
80
|
-
type: "object",
|
|
81
|
-
additionalProperties: false,
|
|
82
|
-
properties: {
|
|
83
|
-
agent: {
|
|
84
|
-
type: "string",
|
|
85
|
-
enum: EXECUTOR_KINDS
|
|
86
|
-
},
|
|
87
|
-
status: {
|
|
88
|
-
type: "string"
|
|
89
|
-
},
|
|
90
|
-
all: {
|
|
91
|
-
type: "boolean"
|
|
92
|
-
},
|
|
93
|
-
noApprovalScan: {
|
|
94
|
-
type: "boolean",
|
|
95
|
-
description: "When true, list live terminals without scanning their panes for approval prompts."
|
|
96
|
-
},
|
|
97
|
-
terminalDebug: {
|
|
98
|
-
type: "boolean",
|
|
99
|
-
description: "When true, include terminal-provider discovery diagnostics for debugging Gateway environment issues."
|
|
100
|
-
},
|
|
101
|
-
idleTimeoutMinutes: {
|
|
102
|
-
type: "number"
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
const listResumableThreadsParameters = {
|
|
107
|
-
type: "object",
|
|
108
|
-
additionalProperties: false,
|
|
109
|
-
required: ["terminal_id"],
|
|
110
|
-
properties: {
|
|
111
|
-
terminal_id: {
|
|
112
|
-
type: "string",
|
|
113
|
-
minLength: 1,
|
|
114
|
-
pattern: "^terminal:v[0-9]+:\\S+$",
|
|
115
|
-
description: "Exact full terminal_id from the selected terminal row's current available_actions. Do not use a short ref, session id, turn id, or constructed selector."
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
const nativeInspectParameters = {
|
|
120
|
-
type: "object",
|
|
121
|
-
additionalProperties: false,
|
|
122
|
-
required: ["terminal_id", "inspection", "expected_binding_token"],
|
|
123
|
-
properties: {
|
|
124
|
-
terminal_id: {
|
|
125
|
-
type: "string",
|
|
126
|
-
minLength: 1,
|
|
127
|
-
pattern: "^terminal:v[0-9]+:\\S+$",
|
|
128
|
-
description: "Exact full terminal_id from the same current native_inspect action as expected_binding_token. Never use a short ref, Session id, Turn id, or constructed selector."
|
|
129
|
-
},
|
|
130
|
-
inspection: {
|
|
131
|
-
type: "string",
|
|
132
|
-
enum: ["status"],
|
|
133
|
-
description: "Closed adapter-owned inspection kind. Exact Codex 0.146.0/0.146.1/0.147.0 and Claude Code 2.1.218/2.1.226 /status profiles are supported; this is never an arbitrary native command string."
|
|
134
|
-
},
|
|
135
|
-
expected_binding_token: {
|
|
136
|
-
type: "string",
|
|
137
|
-
minLength: 1,
|
|
138
|
-
description: "Fresh snapshot-bound terminal and binding token prefilled by this terminal's current native_inspect action. Never guess, construct, or reuse it after another terminal action."
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
const newThreadParameters = {
|
|
143
|
-
type: "object",
|
|
144
|
-
additionalProperties: false,
|
|
145
|
-
required: ["terminal_id", "expected_binding_token"],
|
|
146
|
-
properties: {
|
|
147
|
-
terminal_id: {
|
|
148
|
-
type: "string",
|
|
149
|
-
minLength: 1,
|
|
150
|
-
pattern: "^terminal:v[0-9]+:\\S+$",
|
|
151
|
-
description: "Exact full terminal_id from the same current lifecycle snapshot as expected_binding_token."
|
|
152
|
-
},
|
|
153
|
-
expected_binding_token: {
|
|
154
|
-
type: "string",
|
|
155
|
-
minLength: 1,
|
|
156
|
-
description: "Fresh compare-and-swap token prefilled by this terminal's available new_thread action or returned by agent_knock_knock_list_resumable_threads. Never guess, construct, or reuse it after another terminal action."
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
const reconcileBindingParameters = {
|
|
161
|
-
type: "object",
|
|
162
|
-
additionalProperties: false,
|
|
163
|
-
required: [
|
|
164
|
-
"terminal_id",
|
|
165
|
-
"conflicting_session_id",
|
|
166
|
-
"expected_session_revision",
|
|
167
|
-
"expected_binding_token",
|
|
168
|
-
"expected_terminal_token"
|
|
169
|
-
],
|
|
170
|
-
properties: {
|
|
171
|
-
terminal_id: {
|
|
172
|
-
type: "string",
|
|
173
|
-
minLength: 1,
|
|
174
|
-
pattern: "^terminal:v[0-9]+:\\S+$",
|
|
175
|
-
description: "Exact full terminal_id from the same current reconcile_binding action as every expected token."
|
|
176
|
-
},
|
|
177
|
-
conflicting_session_id: {
|
|
178
|
-
type: "string",
|
|
179
|
-
minLength: 1,
|
|
180
|
-
description: "Exact conflicting managed Session id prefilled by AKK list. Never choose or construct one independently."
|
|
181
|
-
},
|
|
182
|
-
expected_session_revision: {
|
|
183
|
-
type: "integer",
|
|
184
|
-
minimum: 1,
|
|
185
|
-
description: "Exact managed Session revision from the advertised reconcile_binding action."
|
|
186
|
-
},
|
|
187
|
-
expected_binding_token: {
|
|
188
|
-
type: "string",
|
|
189
|
-
minLength: 1,
|
|
190
|
-
description: "Fresh fingerprint of the conflicting binding from the advertised action."
|
|
191
|
-
},
|
|
192
|
-
expected_terminal_token: {
|
|
193
|
-
type: "string",
|
|
194
|
-
minLength: 1,
|
|
195
|
-
description: "Fresh fingerprint of the live terminal process incarnation from the same advertised action."
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
const resumeThreadParameters = {
|
|
200
|
-
type: "object",
|
|
201
|
-
additionalProperties: false,
|
|
202
|
-
required: [
|
|
203
|
-
"terminal_id",
|
|
204
|
-
"native_thread_id",
|
|
205
|
-
"expected_binding_token",
|
|
206
|
-
"candidate_token"
|
|
207
|
-
],
|
|
208
|
-
properties: {
|
|
209
|
-
terminal_id: {
|
|
210
|
-
type: "string",
|
|
211
|
-
minLength: 1,
|
|
212
|
-
pattern: "^terminal:v[0-9]+:\\S+$",
|
|
213
|
-
description: "Exact full terminal_id passed to agent_knock_knock_list_resumable_threads."
|
|
214
|
-
},
|
|
215
|
-
native_thread_id: {
|
|
216
|
-
type: "string",
|
|
217
|
-
pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
|
|
218
|
-
description: "Complete native thread UUID from a resumable=true row returned for this exact terminal. Never truncate, guess, or select an unavailable row."
|
|
219
|
-
},
|
|
220
|
-
expected_binding_token: {
|
|
221
|
-
type: "string",
|
|
222
|
-
minLength: 1,
|
|
223
|
-
description: "Fresh compare-and-swap token from the same agent_knock_knock_list_resumable_threads result as native_thread_id."
|
|
224
|
-
},
|
|
225
|
-
candidate_token: {
|
|
226
|
-
type: "string",
|
|
227
|
-
minLength: 1,
|
|
228
|
-
description: "Opaque fingerprint from the selected resumable thread row in the same current list result. It binds resume to that exact historical file/evidence snapshot; never construct or reuse it."
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
const renewParameters = {
|
|
233
|
-
type: "object",
|
|
234
|
-
additionalProperties: false,
|
|
235
|
-
not: { required: ["turn_id", "conversation_id"] },
|
|
236
|
-
anyOf: [
|
|
237
|
-
{ required: ["turn_id"] },
|
|
238
|
-
{ required: ["conversation_id"] }
|
|
239
|
-
],
|
|
240
|
-
properties: {
|
|
241
|
-
turn_id: {
|
|
242
|
-
type: "string",
|
|
243
|
-
description: "Authoritative AKK turn id whose monitoring should be renewed."
|
|
244
|
-
},
|
|
245
|
-
conversation_id: {
|
|
246
|
-
type: "string",
|
|
247
|
-
deprecated: true,
|
|
248
|
-
description: "Deprecated compatibility alias for turn_id."
|
|
249
|
-
},
|
|
250
|
-
minutes: {
|
|
251
|
-
type: "number",
|
|
252
|
-
exclusiveMinimum: 0,
|
|
253
|
-
description: "New terminal inactivity timeout in minutes."
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
const retryCallbackParameters = {
|
|
258
|
-
type: "object",
|
|
259
|
-
additionalProperties: false,
|
|
260
|
-
not: { required: ["turn_id", "conversation_id"] },
|
|
261
|
-
anyOf: [
|
|
262
|
-
{ required: ["turn_id"] },
|
|
263
|
-
{ required: ["conversation_id"] }
|
|
264
|
-
],
|
|
265
|
-
properties: {
|
|
266
|
-
turn_id: {
|
|
267
|
-
type: "string",
|
|
268
|
-
description: "Authoritative AKK turn id whose persisted callback should be retried."
|
|
269
|
-
},
|
|
270
|
-
conversation_id: {
|
|
271
|
-
type: "string",
|
|
272
|
-
deprecated: true,
|
|
273
|
-
description: "Deprecated compatibility alias for turn_id."
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
const statusParameters = {
|
|
278
|
-
type: "object",
|
|
279
|
-
additionalProperties: false,
|
|
280
|
-
not: { required: ["turn_id", "conversation_id"] },
|
|
281
|
-
anyOf: [
|
|
282
|
-
{ required: ["turn_id"] },
|
|
283
|
-
{ required: ["conversation_id"] }
|
|
284
|
-
],
|
|
285
|
-
properties: {
|
|
286
|
-
turn_id: {
|
|
287
|
-
type: "string",
|
|
288
|
-
description: "Authoritative AKK turn id to inspect."
|
|
289
|
-
},
|
|
290
|
-
conversation_id: {
|
|
291
|
-
type: "string",
|
|
292
|
-
deprecated: true,
|
|
293
|
-
description: "Deprecated legacy Turn alias, or the exact raw-terminal selector prefilled by that terminal row's available status action. Managed Turn status must use turn_id; never construct or guess a raw-terminal selector."
|
|
294
|
-
},
|
|
295
|
-
idleTimeoutMinutes: {
|
|
296
|
-
type: "number"
|
|
297
|
-
},
|
|
298
|
-
trace: {
|
|
299
|
-
type: "boolean",
|
|
300
|
-
description: "Include a safe executor trace summary with tool calls, permission requests, monitor events, and redacted thinking markers."
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
const cancelParameters = {
|
|
305
|
-
type: "object",
|
|
306
|
-
additionalProperties: false,
|
|
307
|
-
not: { required: ["turn_id", "conversation_id"] },
|
|
308
|
-
anyOf: [
|
|
309
|
-
{ required: ["turn_id"] },
|
|
310
|
-
{ required: ["conversation_id"] }
|
|
311
|
-
],
|
|
312
|
-
properties: {
|
|
313
|
-
turn_id: {
|
|
314
|
-
type: "string",
|
|
315
|
-
description: "Authoritative AKK turn id to interrupt."
|
|
316
|
-
},
|
|
317
|
-
conversation_id: {
|
|
318
|
-
type: "string",
|
|
319
|
-
deprecated: true,
|
|
320
|
-
description: "Deprecated legacy Turn alias, or the exact raw-terminal selector prefilled by that terminal row's available cancel action. Managed Turn cancellation must use turn_id; never construct or guess a raw-terminal selector."
|
|
321
|
-
},
|
|
322
|
-
idleTimeoutMinutes: {
|
|
323
|
-
type: "number"
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
const closeParameters = {
|
|
328
|
-
type: "object",
|
|
329
|
-
additionalProperties: false,
|
|
330
|
-
not: {
|
|
331
|
-
anyOf: [
|
|
332
|
-
{ required: ["turn_id", "conversation_id"] },
|
|
333
|
-
{ required: ["expected_message_id", "expected_transition_id"] },
|
|
334
|
-
{ required: ["expected_handoff_token", "conversation_id"] },
|
|
335
|
-
{ required: ["expected_handoff_token", "expected_message_id"] },
|
|
336
|
-
{ required: ["expected_handoff_token", "expected_transition_id"] }
|
|
337
|
-
]
|
|
338
|
-
},
|
|
339
|
-
anyOf: [
|
|
340
|
-
{ required: ["turn_id"] },
|
|
341
|
-
{ required: ["conversation_id"] }
|
|
342
|
-
],
|
|
343
|
-
properties: {
|
|
344
|
-
turn_id: {
|
|
345
|
-
type: "string",
|
|
346
|
-
description: "Authoritative AKK turn id whose managed record should be closed."
|
|
347
|
-
},
|
|
348
|
-
conversation_id: {
|
|
349
|
-
type: "string",
|
|
350
|
-
deprecated: true,
|
|
351
|
-
description: "Deprecated legacy Turn alias, or an exact list-prefilled raw-terminal/orphan recovery selector. Managed Turn close must use turn_id; never construct or guess a raw-terminal selector."
|
|
352
|
-
},
|
|
353
|
-
reason: {
|
|
354
|
-
type: "string"
|
|
355
|
-
},
|
|
356
|
-
expected_message_id: {
|
|
357
|
-
type: "string",
|
|
358
|
-
description: "Required only to clear an orphaned terminal dispatch shown by AKK list. Must exactly match that entry's current message_id and must not be combined with expected_transition_id."
|
|
359
|
-
},
|
|
360
|
-
expected_transition_id: {
|
|
361
|
-
type: "string",
|
|
362
|
-
description: "Required only to recover an unresolved native-thread lifecycle transition shown by AKK list. Must exactly match that entry's current transition_id and must not be combined with expected_message_id."
|
|
363
|
-
},
|
|
364
|
-
expected_handoff_token: {
|
|
365
|
-
type: "string",
|
|
366
|
-
description: "Snapshot fence for the exact managed Turn close advertised only by terminals[].handoff_decision.choices.take_over_current.action. Copy that complete action only after explicit user confirmation; it requires turn_id and reason=superseded_by_human_context_switch, and cannot be combined with a raw-terminal target or another close fence."
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
const approveParameters = {
|
|
371
|
-
type: "object",
|
|
372
|
-
additionalProperties: false,
|
|
373
|
-
required: ["expected_approval_fingerprint"],
|
|
374
|
-
not: { required: ["turn_id", "conversation_id"] },
|
|
375
|
-
anyOf: [
|
|
376
|
-
{ required: ["turn_id"] },
|
|
377
|
-
{ required: ["conversation_id"] }
|
|
378
|
-
],
|
|
379
|
-
allOf: [
|
|
380
|
-
{
|
|
381
|
-
if: { required: ["expected_terminal_token"] },
|
|
382
|
-
then: { required: ["conversation_id"] }
|
|
383
|
-
}
|
|
384
|
-
],
|
|
385
|
-
properties: {
|
|
386
|
-
turn_id: {
|
|
387
|
-
type: "string",
|
|
388
|
-
description: "Authoritative AKK turn id containing the approval prompt."
|
|
389
|
-
},
|
|
390
|
-
conversation_id: {
|
|
391
|
-
type: "string",
|
|
392
|
-
deprecated: true,
|
|
393
|
-
description: "Deprecated legacy Turn alias, or the exact raw-terminal selector prefilled by that terminal row's available approval action. Managed Turn approval must use turn_id; never construct or guess a raw-terminal selector."
|
|
394
|
-
},
|
|
395
|
-
expected_approval_fingerprint: {
|
|
396
|
-
type: "string",
|
|
397
|
-
description: "Exact approval fingerprint returned by the latest status or approval-required callback. The prompt is captured again and must still match before keys are sent."
|
|
398
|
-
},
|
|
399
|
-
expected_terminal_token: {
|
|
400
|
-
type: "string",
|
|
401
|
-
description: "Only for a terminal-scoped manual Codex approval prefilled by the latest AKK list action. Preserve it exactly with that action's full conversation_id; never construct, reuse, or use it for automatic approval."
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
};
|
|
2
|
+
import { registerOpenClawCallbackGateway } from "./openclaw-plugin-callback-adapter.js";
|
|
3
|
+
import { bindOpenClawRelayPath, defaultOpenClawRelayPath, registerOpenClawCommands } from "./openclaw-plugin-command-adapter.js";
|
|
4
|
+
import { createMonitorReconciliationService, MONITOR_SUPERVISOR_INTERVAL_MS } from "./openclaw-plugin-supervisor.js";
|
|
405
5
|
function createPlugin(relayPath, { monitorSupervisorIntervalMs = MONITOR_SUPERVISOR_INTERVAL_MS } = {}) {
|
|
406
6
|
const displayedResumeSnapshots = new Map();
|
|
407
7
|
return definePluginEntry({
|
|
@@ -409,21 +9,8 @@ function createPlugin(relayPath, { monitorSupervisorIntervalMs = MONITOR_SUPERVI
|
|
|
409
9
|
name: "Agent Knock Knock",
|
|
410
10
|
description: "Agent Knock Knock (AKK/akk) lets OpenClaw operate local Codex and Claude Code through shared tmux or Herdr terminals, with visible monitoring, approvals, callbacks, cancellation, and seamless human takeover.",
|
|
411
11
|
register(api) {
|
|
412
|
-
|
|
413
|
-
api
|
|
414
|
-
try {
|
|
415
|
-
const result = await handleCallback(api, params);
|
|
416
|
-
respond(true, result);
|
|
417
|
-
}
|
|
418
|
-
catch (error) {
|
|
419
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
420
|
-
api.logger.warn?.(`agent-knock-knock callback failed: ${message}`);
|
|
421
|
-
respond(false, undefined, {
|
|
422
|
-
code: "AGENT_KNOCK_KNOCK_CALLBACK_FAILED",
|
|
423
|
-
message
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
}, { scope: "operator.write" });
|
|
12
|
+
bindOpenClawRelayPath(api, relayPath);
|
|
13
|
+
registerOpenClawCallbackGateway(api);
|
|
427
14
|
try {
|
|
428
15
|
api.registerService?.(createMonitorReconciliationService(api, monitorSupervisorIntervalMs));
|
|
429
16
|
}
|
|
@@ -431,1805 +18,13 @@ function createPlugin(relayPath, { monitorSupervisorIntervalMs = MONITOR_SUPERVI
|
|
|
431
18
|
const message = error instanceof Error ? error.message : String(error);
|
|
432
19
|
api.logger.warn?.(`agent-knock-knock monitor reconciliation service was not registered: ${message}`);
|
|
433
20
|
}
|
|
434
|
-
api
|
|
435
|
-
name: "akk",
|
|
436
|
-
description: "Send coding work through existing Codex or Claude Code shared terminals, inspect managed turns, and explicitly start, clear, list, or resume native threads.",
|
|
437
|
-
acceptsArgs: true,
|
|
438
|
-
requireAuth: true,
|
|
439
|
-
nativeProgressMessages: {
|
|
440
|
-
default: "AKK is handling the request..."
|
|
441
|
-
},
|
|
442
|
-
agentPromptGuidance: [
|
|
443
|
-
"Use /akk <task> when exactly one eligible idle coding-agent terminal pane should receive new work. Use /akk codex: <task>, /akk claude: <task>, or another selector returned by /akk list to target an existing pane. Use session_id only when the current list action prefills it. A rollout-backed Codex pane instead uses terminal_follow_current: an exact list-prefilled terminal selector plus expected_terminal_token, or an explicitly named/uniquely selected pane whose equivalent fresh candidate authority AKK derives under its terminal lock. AKK sends the ordinary task once within the complete exact rollout inventory and binds only the uniquely accepting native thread. Never infer or reuse a listed token. Use /akk threads, /akk new-thread or clear-thread, and /akk resume-thread only with an exact full terminal_id returned by /akk list; these switch native context without creating a Turn. Resume numbers and short IDs are bound to the last displayed snapshot, while previous is available only from the latest verified committed transition. For native Codex or Claude status, use only an advertised agent_knock_knock_native_inspect action; agent_knock_knock_status inspects AKK Turn state and does not execute /status. AKK never starts a coding-agent process."
|
|
444
|
-
],
|
|
445
|
-
handler: async (ctx) => handleAkkCommand(api, ctx, displayedResumeSnapshots)
|
|
446
|
-
});
|
|
447
|
-
registerCliTool(api, {
|
|
448
|
-
name: "agent_knock_knock_list",
|
|
449
|
-
description: "List existing Codex and Claude Code tmux or Herdr panes as the primary terminals[] resources. Each terminal may include managed.current_turn or managed.recent_turn; all=true also includes older managed.history and retained unavailable history. By default, unavailable_managed_turns contains attention-needed records whose pane is unavailable. A live human thread switch remains management_state=conflict and exposes handoff_state as adoptable or blocked without mutating the Store. Use only each row's available_actions and authoritative prefilled arguments, except for two explicit-confirmation nested recovery paths: handoff_decision.choices.take_over_current.action and blocking_turns[].recovery_action. The latter is only for collateral blockers; an active handoff source Turn remains exclusively snapshot-fenced by handoff_decision. Refresh list immediately after either nested action. A session_exact session-scoped send targets session_id only when that action is listed and never redirects to the pane's replacement context. A rollout-backed Codex row instead advertises terminal_follow_current as a terminal-scoped follow-current send with its exact selector and expected_terminal_token; AKK pins the complete exact rollout inventory, sends the task once, and binds only the unique exact acceptor. Explicit discovery selectors remain compatible and derive equivalent fresh candidate authority under the terminal lock. respond targets the exact in-flight turn; native_inspect runs only a closed, exact-version Codex or Claude status profile with the current terminal/binding token; read-only thread listing targets the exact terminal, while new/resume mutations also require the current binding token and create no Turn; reconcile_binding remains a low-level conflict recovery action; managed controls target the exact turn; a raw terminal row may prefill its own compatibility selector for status or recovery controls. Never construct identifiers or tokens. AKK revalidates every terminal side effect and never starts a coding-agent process.",
|
|
450
|
-
parameters: listParameters,
|
|
451
|
-
buildArgs: (params) => {
|
|
452
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
453
|
-
const args = ["list", "--reconcile"];
|
|
454
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
455
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
456
|
-
pushOptional(args, "--agent", stringValue(params.agent));
|
|
457
|
-
pushOptional(args, "--status", stringValue(params.status));
|
|
458
|
-
if (params.all === true) {
|
|
459
|
-
args.push("--all");
|
|
460
|
-
}
|
|
461
|
-
if (params.noApprovalScan === true) {
|
|
462
|
-
args.push("--no-approval-scan");
|
|
463
|
-
}
|
|
464
|
-
if (params.terminalDebug === true) {
|
|
465
|
-
args.push("--terminal-debug");
|
|
466
|
-
}
|
|
467
|
-
return args;
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
registerCliTool(api, {
|
|
471
|
-
name: "agent_knock_knock_list_resumable_threads",
|
|
472
|
-
description: "List verified native Codex or Claude Code threads for one exact terminal. Call this only from that terminal row's available list_resumable_threads action. The result includes the current native identity, a fresh expected_binding_token, exact candidate UUIDs with opaque candidate_token fingerprints, and an exact previous action only when the latest committed transition has one uniquely resumable source. Resume only a row with resumable=true or that previous action, preserving all full prefilled arguments from the same result. Number/short/handle fields are human display aids, never exact tool arguments. This is read-only for Session/Turn state and creates no AKK Turn.",
|
|
473
|
-
parameters: listResumableThreadsParameters,
|
|
474
|
-
normalizeTurnIdentity: false,
|
|
475
|
-
buildArgs: (params) => {
|
|
476
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
477
|
-
const args = [
|
|
478
|
-
"list-resumable-threads",
|
|
479
|
-
"--terminal",
|
|
480
|
-
requiredString(params.terminal_id, "terminal_id")
|
|
481
|
-
];
|
|
482
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
483
|
-
pushOptional(args, "--codex-home", stringValue(config.codexHome));
|
|
484
|
-
return args;
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
registerCliTool(api, {
|
|
488
|
-
name: "agent_knock_knock_native_inspect",
|
|
489
|
-
description: "Execute one closed, exact-version native status inspection in an exact Codex or Claude Code terminal without ordinary Turn delivery. Call only from that terminal row's current native_inspect action and preserve its exact terminal_id, inspection=status, and expected_binding_token. Supported profiles are Codex 0.146.0/0.146.1/0.147.0 and Claude Code 2.1.218/2.1.226; Claude's exact Status panel is parsed and safely dismissed before success. Arbitrary slash commands, /usage, /cost, /stats, /usage-credits, /model, and /compact remain unavailable. Bare Codex /usage opens an interactive menu whose later Enter can select an account-side usage-limit reset. This creates no AKK Session, Turn, receipt, monitor, or callback. agent_knock_knock_status is different: it inspects AKK Turn state and the bounded current screen without executing native /status.",
|
|
490
|
-
parameters: nativeInspectParameters,
|
|
491
|
-
normalizeTurnIdentity: false,
|
|
492
|
-
buildArgs: (params) => {
|
|
493
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
494
|
-
const inspection = requiredString(params.inspection, "inspection");
|
|
495
|
-
if (inspection !== "status") {
|
|
496
|
-
throw new Error("inspection must be status");
|
|
497
|
-
}
|
|
498
|
-
const args = [
|
|
499
|
-
"native-inspect",
|
|
500
|
-
"--terminal",
|
|
501
|
-
requiredString(params.terminal_id, "terminal_id"),
|
|
502
|
-
"--inspection",
|
|
503
|
-
inspection,
|
|
504
|
-
"--expected-binding-token",
|
|
505
|
-
requiredString(params.expected_binding_token, "expected_binding_token")
|
|
506
|
-
];
|
|
507
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
508
|
-
pushOptional(args, "--codex-home", stringValue(config.codexHome));
|
|
509
|
-
return args;
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
registerCliTool(api, {
|
|
513
|
-
name: "agent_knock_knock_new_thread",
|
|
514
|
-
description: "Start and verify a clean native coding-agent thread in the same exact terminal. Call only from a current available new_thread action, using its exact terminal_id and expected_binding_token, or immediately after agent_knock_knock_list_resumable_threads using that result's token. Never send /clear as ordinary task text. This lifecycle transition creates a new AKK Session but no Turn; use ordinary send afterward.",
|
|
515
|
-
parameters: newThreadParameters,
|
|
516
|
-
normalizeTurnIdentity: false,
|
|
517
|
-
isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
|
|
518
|
-
buildArgs: (params) => {
|
|
519
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
520
|
-
const args = [
|
|
521
|
-
"new-thread",
|
|
522
|
-
"--terminal",
|
|
523
|
-
requiredString(params.terminal_id, "terminal_id"),
|
|
524
|
-
"--expected-binding-token",
|
|
525
|
-
requiredString(params.expected_binding_token, "expected_binding_token")
|
|
526
|
-
];
|
|
527
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
528
|
-
pushOptional(args, "--codex-home", stringValue(config.codexHome));
|
|
529
|
-
return args;
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
registerCliTool(api, {
|
|
533
|
-
name: "agent_knock_knock_reconcile_binding",
|
|
534
|
-
description: "Detach one exact conflicting managed Session binding without adopting the live replacement thread. Call only after the user explicitly requests recovery and only from a current terminal row's advertised reconcile_binding action. Preserve its terminal_id, conflicting_session_id, Session revision, binding token, and terminal token exactly. This action sends no coding-agent input and creates no Turn; refresh AKK list afterward.",
|
|
535
|
-
parameters: reconcileBindingParameters,
|
|
536
|
-
normalizeTurnIdentity: false,
|
|
537
|
-
buildArgs: (params) => {
|
|
538
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
539
|
-
const revision = Number(params.expected_session_revision);
|
|
540
|
-
if (!Number.isSafeInteger(revision) || revision < 1) {
|
|
541
|
-
throw new Error("expected_session_revision must be a positive safe integer");
|
|
542
|
-
}
|
|
543
|
-
const args = [
|
|
544
|
-
"reconcile-binding",
|
|
545
|
-
"--terminal",
|
|
546
|
-
requiredString(params.terminal_id, "terminal_id"),
|
|
547
|
-
"--conflicting-session",
|
|
548
|
-
requiredString(params.conflicting_session_id, "conflicting_session_id"),
|
|
549
|
-
"--expected-session-revision",
|
|
550
|
-
String(revision),
|
|
551
|
-
"--expected-binding-token",
|
|
552
|
-
requiredString(params.expected_binding_token, "expected_binding_token"),
|
|
553
|
-
"--expected-terminal-token",
|
|
554
|
-
requiredString(params.expected_terminal_token, "expected_terminal_token")
|
|
555
|
-
];
|
|
556
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
557
|
-
pushOptional(args, "--codex-home", stringValue(config.codexHome));
|
|
558
|
-
return args;
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
registerCliTool(api, {
|
|
562
|
-
name: "agent_knock_knock_resume_thread",
|
|
563
|
-
description: "Resume one exact verified historical native thread in the same terminal. First call agent_knock_knock_list_resumable_threads, then pass its exact terminal_id and expected_binding_token plus one complete native_thread_id whose row says resumable=true and that same row's candidate_token. Never guess, truncate, or reuse IDs or tokens, and never send a resume slash command as ordinary task text. This creates or reactivates an AKK Session but creates no Turn.",
|
|
564
|
-
parameters: resumeThreadParameters,
|
|
565
|
-
normalizeTurnIdentity: false,
|
|
566
|
-
isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
|
|
567
|
-
buildArgs: (params) => {
|
|
568
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
569
|
-
const args = [
|
|
570
|
-
"resume-thread",
|
|
571
|
-
"--terminal",
|
|
572
|
-
requiredString(params.terminal_id, "terminal_id"),
|
|
573
|
-
"--native-thread",
|
|
574
|
-
requiredString(params.native_thread_id, "native_thread_id"),
|
|
575
|
-
"--expected-binding-token",
|
|
576
|
-
requiredString(params.expected_binding_token, "expected_binding_token"),
|
|
577
|
-
"--candidate-token",
|
|
578
|
-
requiredString(params.candidate_token, "candidate_token")
|
|
579
|
-
];
|
|
580
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
581
|
-
pushOptional(args, "--codex-home", stringValue(config.codexHome));
|
|
582
|
-
return args;
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
api.registerTool((_toolContext) => ({
|
|
586
|
-
label: "AKK Status",
|
|
587
|
-
name: "agent_knock_knock_status",
|
|
588
|
-
description: "Inspect one exact AKK-managed turn by its authoritative turn_id, or use only a raw terminal row's own prefilled compatibility selector. The deprecated conversation_id remains a legacy Turn alias and the list-prefilled raw-terminal input; never construct it. Returns live state, a bounded terminal screen, and available purpose/context with confidence and limitations. AKK never starts a coding agent.",
|
|
589
|
-
parameters: statusParameters,
|
|
590
|
-
async execute(_toolCallId, params) {
|
|
591
|
-
const result = runCli(api, buildStatusCliArgs(api, isRecord(params) ? params : {}));
|
|
592
|
-
return toolResult(result);
|
|
593
|
-
}
|
|
594
|
-
}), { name: "agent_knock_knock_status", optional: true });
|
|
595
|
-
api.registerTool((toolContext) => ({
|
|
596
|
-
label: "AKK Send",
|
|
597
|
-
name: "agent_knock_knock_send",
|
|
598
|
-
description: "Start a new AKK Turn. A session_exact session-scoped send uses session_id only when the current list action prefills it; it preserves that Session's native context and never redirects to a thread the human selected later in the pane. Rollout-backed Codex uses terminal_follow_current terminal-scoped follow-current instead: preserve the exact list-prefilled full terminal selector and fresh expected_terminal_token, or preserve a selector explicitly named by the user. AKK pins the complete exact rollout inventory under the terminal lock, sends the request once, and binds only the unique exact rollout that accepts it. Never infer, copy, or reuse a listed token. Both target fields may be omitted only when AKK should require one unique eligible idle pane and derive the same fresh candidate authority. Never pass a turn id or selector as session_id. To answer an in-flight question, use agent_knock_knock_respond instead. For ordinary use omit monitoring timeouts unless the user explicitly asks to change them. timeoutSeconds is unsupported. AKK never starts a coding agent. This is asynchronous: after acceptance, yield and wait for the callback or a later explicit status request.",
|
|
599
|
-
parameters: sendParameters,
|
|
600
|
-
async execute(toolCallId, params) {
|
|
601
|
-
const result = await runSendRequest(api, isRecord(params) ? params : {}, toolContext, terminalMessageIdForToolCall({
|
|
602
|
-
toolCallId,
|
|
603
|
-
sessionKey: toolContext?.sessionKey,
|
|
604
|
-
sessionId: toolContext?.sessionId,
|
|
605
|
-
toolName: "agent_knock_knock_send"
|
|
606
|
-
}));
|
|
607
|
-
return toolResult(result, { submissionErrors: true });
|
|
608
|
-
}
|
|
609
|
-
}), { name: "agent_knock_knock_send", optional: true });
|
|
610
|
-
registerCliTool(api, {
|
|
611
|
-
name: "agent_knock_knock_respond",
|
|
612
|
-
description: "Respond to a question or blocked callback in one exact in-flight AKK turn. This continues that turn and does not create a new turn; for later ordinary work refresh agent_knock_knock_list and use that terminal row's currently advertised send action.",
|
|
613
|
-
parameters: respondParameters,
|
|
614
|
-
buildArgs: (params, toolContext, toolCallId) => {
|
|
615
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
616
|
-
const openclawSession = stringValue(toolContext?.sessionKey) ?? "agent:main:main";
|
|
617
|
-
const args = [
|
|
618
|
-
"respond",
|
|
619
|
-
"--turn",
|
|
620
|
-
authoritativeManagedId(params.turn_id, "turn_id"),
|
|
621
|
-
"--message",
|
|
622
|
-
requiredString(params.request, "request")
|
|
623
|
-
];
|
|
624
|
-
pushOptional(args, "--message-id", terminalMessageIdForToolCall({
|
|
625
|
-
toolCallId,
|
|
626
|
-
sessionKey: openclawSession,
|
|
627
|
-
sessionId: toolContext?.sessionId,
|
|
628
|
-
toolName: "agent_knock_knock_respond"
|
|
629
|
-
}));
|
|
630
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
631
|
-
pushOptional(args, "--openclaw-session", openclawSession);
|
|
632
|
-
return args;
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
registerCliTool(api, {
|
|
636
|
-
name: "agent_knock_knock_approve",
|
|
637
|
-
description: "Manually approve a permission request only after the user reviews and explicitly confirms it. Managed approval uses exact turn_id. An unmanaged raw terminal, or a managed Codex pane whose current prompt has no usable AKK Turn owner, may be approved only through its own latest list-prefilled action; preserve its full conversation_id and expected_terminal_token exactly. Terminal-scoped approval never enters automatic approval, never changes managed Turn or Session binding, and has no durable dispatch receipt: if its result is interrupted, refresh status and inspect the live prompt instead of retrying blindly. Claude Code uses no Hooks: this manual path accepts only an exact one-time Bash permission screen, then recaptures its short-lived evidence and process identity before sending Enter. Separately, trusted default-disabled plugin configuration can auto-approve an exact Claude command/workspace match without exposing policy control to the model. Hook-free durable completion is independently verified from the local Claude transcript.",
|
|
638
|
-
parameters: approveParameters,
|
|
639
|
-
buildArgs: (params) => {
|
|
640
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
641
|
-
const args = ["approve"];
|
|
642
|
-
pushTurnTarget(args, params);
|
|
643
|
-
pushOptional(args, "--expected-approval-fingerprint", requiredString(params.expected_approval_fingerprint, "expected_approval_fingerprint"));
|
|
644
|
-
const expectedTerminalToken = Object.hasOwn(params, "expected_terminal_token")
|
|
645
|
-
? requiredString(params.expected_terminal_token, "expected_terminal_token")
|
|
646
|
-
: undefined;
|
|
647
|
-
if (expectedTerminalToken &&
|
|
648
|
-
!Object.hasOwn(params, "conversation_id")) {
|
|
649
|
-
throw new Error("expected_terminal_token requires the exact list-prefilled conversation_id");
|
|
650
|
-
}
|
|
651
|
-
pushOptional(args, "--expected-terminal-token", expectedTerminalToken);
|
|
652
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
653
|
-
return args;
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
registerCliTool(api, {
|
|
657
|
-
name: "agent_knock_knock_renew",
|
|
658
|
-
description: "Renew monitoring for one exact stalled turn_id without sending text or keys to the coding agent. Use this when the user wants a still-live long-running terminal task to keep monitoring after an inactivity stall.",
|
|
659
|
-
parameters: renewParameters,
|
|
660
|
-
buildArgs: (params) => {
|
|
661
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
662
|
-
const args = ["renew"];
|
|
663
|
-
pushTurnTarget(args, params);
|
|
664
|
-
pushOptional(args, "--minutes", numberString(params.minutes) ?? numberString(config.agentTimeoutMinutes));
|
|
665
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
666
|
-
return args;
|
|
667
|
-
}
|
|
668
|
-
});
|
|
669
|
-
registerCliTool(api, {
|
|
670
|
-
name: "agent_knock_knock_retry_callback",
|
|
671
|
-
description: "Retry a persisted AKK callback for an exact turn that failed before reaching OpenClaw. The original callback message id and turn identity are reused for idempotent delivery.",
|
|
672
|
-
parameters: retryCallbackParameters,
|
|
673
|
-
buildArgs: (params) => {
|
|
674
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
675
|
-
const args = ["retry-callback"];
|
|
676
|
-
pushTurnTarget(args, params);
|
|
677
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
678
|
-
return args;
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
registerCliTool(api, {
|
|
682
|
-
name: "agent_knock_knock_cancel",
|
|
683
|
-
description: "Interrupt one exact AKK turn_id, or use only an unmanaged raw terminal row's own prefilled cancel action. Claude sends Escape; Codex uses its declared interrupt key. The shared terminal pane remains open for human takeover.",
|
|
684
|
-
parameters: cancelParameters,
|
|
685
|
-
buildArgs: (params) => {
|
|
686
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
687
|
-
const args = ["cancel"];
|
|
688
|
-
pushTurnTarget(args, params);
|
|
689
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
690
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
691
|
-
return args;
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
registerCliTool(api, {
|
|
695
|
-
name: "agent_knock_knock_close",
|
|
696
|
-
description: "Close an AKK-managed turn record without terminating the shared terminal pane. For a list-prefilled raw-terminal recovery, the user must explicitly request it and provide the exact expected_message_id or expected_transition_id reported by that AKK list entry. When a verified human native-thread switch conflicts with one active Turn, only the complete nested handoff_decision take_over_current action is authoritative: it requires explicit user confirmation and its exact turn_id, supersede reason, and expected_handoff_token. When list exposes blocking_turns[], its complete nested recovery_action is likewise authoritative only after explicit user confirmation; copy it unchanged and refresh list after the Store-only close.",
|
|
697
|
-
parameters: closeParameters,
|
|
698
|
-
isErrorResult: isBlockedTerminalDispatchResult,
|
|
699
|
-
buildArgs: (params) => {
|
|
700
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
701
|
-
const args = ["close"];
|
|
702
|
-
assertExclusiveRecoveryFence(params);
|
|
703
|
-
pushTurnTarget(args, params);
|
|
704
|
-
pushOptional(args, "--reason", stringValue(params.reason));
|
|
705
|
-
pushOptional(args, "--expected-message-id", stringValue(params.expected_message_id));
|
|
706
|
-
pushOptional(args, "--expected-transition-id", stringValue(params.expected_transition_id));
|
|
707
|
-
pushOptional(args, "--expected-handoff-token", stringValue(params.expected_handoff_token));
|
|
708
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
709
|
-
return args;
|
|
710
|
-
}
|
|
711
|
-
});
|
|
21
|
+
registerOpenClawCommands(api, displayedResumeSnapshots);
|
|
712
22
|
}
|
|
713
23
|
});
|
|
714
24
|
}
|
|
715
|
-
|
|
716
|
-
const intervalMs = Number.isFinite(configuredIntervalMs) &&
|
|
717
|
-
configuredIntervalMs > 0
|
|
718
|
-
? Math.max(50, Math.ceil(configuredIntervalMs))
|
|
719
|
-
: MONITOR_SUPERVISOR_INTERVAL_MS;
|
|
720
|
-
let stopped = true;
|
|
721
|
-
let timer;
|
|
722
|
-
let inFlight;
|
|
723
|
-
const reconciliationArgs = (reason) => {
|
|
724
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
725
|
-
const args = ["reconcile-monitors", "--reason", reason];
|
|
726
|
-
if (reason === "monitor_supervision") {
|
|
727
|
-
args.push("--terminal-monitors-only");
|
|
728
|
-
}
|
|
729
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
730
|
-
return args;
|
|
731
|
-
};
|
|
732
|
-
const report = (result, reason) => {
|
|
733
|
-
if (reason === "startup_reconciliation" ||
|
|
734
|
-
Number(result.launched ?? 0) > 0 ||
|
|
735
|
-
Number(result.errors ?? 0) > 0) {
|
|
736
|
-
const label = reason === "startup_reconciliation"
|
|
737
|
-
? "monitor reconciliation"
|
|
738
|
-
: `monitor ${reason}`;
|
|
739
|
-
api.logger.info?.(`agent-knock-knock ${label}: ` +
|
|
740
|
-
`checked=${result.checked ?? 0} launched=${result.launched ?? 0} ` +
|
|
741
|
-
`already_running=${result.already_running ?? 0} skipped=${result.skipped ?? 0} ` +
|
|
742
|
-
`errors=${result.errors ?? 0}`);
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
const schedule = () => {
|
|
746
|
-
if (stopped) {
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
timer = setTimeout(() => {
|
|
750
|
-
timer = undefined;
|
|
751
|
-
inFlight = supervise().finally(() => {
|
|
752
|
-
inFlight = undefined;
|
|
753
|
-
schedule();
|
|
754
|
-
});
|
|
755
|
-
}, intervalMs);
|
|
756
|
-
timer.unref?.();
|
|
757
|
-
};
|
|
758
|
-
const supervise = async () => {
|
|
759
|
-
try {
|
|
760
|
-
const result = await runCliAsync(api, reconciliationArgs("monitor_supervision"));
|
|
761
|
-
report(result, "monitor_supervision");
|
|
762
|
-
}
|
|
763
|
-
catch (error) {
|
|
764
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
765
|
-
api.logger.warn?.(`agent-knock-knock monitor supervision deferred after error: ${message}`);
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
return {
|
|
769
|
-
id: "agent-knock-knock-monitor-reconciliation",
|
|
770
|
-
start() {
|
|
771
|
-
if (!stopped) {
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
stopped = false;
|
|
775
|
-
try {
|
|
776
|
-
const result = runCli(api, reconciliationArgs("startup_reconciliation"));
|
|
777
|
-
report(result, "startup_reconciliation");
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
781
|
-
api.logger.warn?.(`agent-knock-knock monitor reconciliation skipped after startup error: ${message}`);
|
|
782
|
-
}
|
|
783
|
-
schedule();
|
|
784
|
-
},
|
|
785
|
-
async stop() {
|
|
786
|
-
stopped = true;
|
|
787
|
-
if (timer) {
|
|
788
|
-
clearTimeout(timer);
|
|
789
|
-
timer = undefined;
|
|
790
|
-
}
|
|
791
|
-
await inFlight;
|
|
792
|
-
}
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
const plugin = createPlugin(defaultBinPath);
|
|
25
|
+
const plugin = createPlugin(defaultOpenClawRelayPath);
|
|
796
26
|
export function createOpenClawPluginForTest(relayPath, options = {}) {
|
|
797
27
|
return createPlugin(relayPath, options);
|
|
798
28
|
}
|
|
799
29
|
export default plugin;
|
|
800
|
-
function resumeSelectionScope(sessionKey, sessionId) {
|
|
801
|
-
const key = requiredString(sessionKey, "OpenClaw session identity is required for snapshot-bound Resume");
|
|
802
|
-
const incarnation = stringValue(sessionId) ?? null;
|
|
803
|
-
return `openclaw:${createHash("sha256")
|
|
804
|
-
.update(JSON.stringify([key, incarnation]))
|
|
805
|
-
.digest("hex")}`;
|
|
806
|
-
}
|
|
807
|
-
function resumeSnapshotCacheKey(sessionKey, sessionId, terminalId) {
|
|
808
|
-
const key = requiredString(sessionKey, "OpenClaw session identity is required for snapshot-bound Resume");
|
|
809
|
-
return JSON.stringify([key, stringValue(sessionId) ?? null, terminalId]);
|
|
810
|
-
}
|
|
811
|
-
function rememberDisplayedResumeSnapshot(snapshots, key, snapshotId, expiresAtMs) {
|
|
812
|
-
const now = Date.now();
|
|
813
|
-
for (const [candidateKey, value] of snapshots) {
|
|
814
|
-
if (value.expiresAtMs <= now) {
|
|
815
|
-
snapshots.delete(candidateKey);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
snapshots.delete(key);
|
|
819
|
-
while (snapshots.size >= MAX_DISPLAYED_RESUME_SNAPSHOTS) {
|
|
820
|
-
const oldestKey = snapshots.keys().next().value;
|
|
821
|
-
if (typeof oldestKey !== "string") {
|
|
822
|
-
break;
|
|
823
|
-
}
|
|
824
|
-
snapshots.delete(oldestKey);
|
|
825
|
-
}
|
|
826
|
-
snapshots.set(key, { snapshotId, expiresAtMs });
|
|
827
|
-
}
|
|
828
|
-
function currentDisplayedResumeSnapshotId(snapshots, key) {
|
|
829
|
-
const value = snapshots.get(key);
|
|
830
|
-
if (!value) {
|
|
831
|
-
return undefined;
|
|
832
|
-
}
|
|
833
|
-
if (value.expiresAtMs <= Date.now()) {
|
|
834
|
-
snapshots.delete(key);
|
|
835
|
-
return undefined;
|
|
836
|
-
}
|
|
837
|
-
return value.snapshotId;
|
|
838
|
-
}
|
|
839
|
-
async function handleAkkCommand(api, ctx, displayedResumeSnapshots) {
|
|
840
|
-
try {
|
|
841
|
-
const parsed = parseAkkCommand(ctx.args);
|
|
842
|
-
if (parsed.action === "help") {
|
|
843
|
-
return { text: akkUsageText() };
|
|
844
|
-
}
|
|
845
|
-
if (parsed.action === "delegate") {
|
|
846
|
-
const result = await runDelegate(api, {
|
|
847
|
-
request: parsed.request
|
|
848
|
-
}, {
|
|
849
|
-
sessionKey: ctx.sessionKey
|
|
850
|
-
});
|
|
851
|
-
return {
|
|
852
|
-
text: formatDelegateCommandResult(result),
|
|
853
|
-
isError: result.status !== "async_pending"
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
857
|
-
if (parsed.action === "list-resumable-threads" ||
|
|
858
|
-
parsed.action === "new-thread" ||
|
|
859
|
-
parsed.action === "resume-thread") {
|
|
860
|
-
const selectionScope = resumeSelectionScope(ctx.sessionKey, ctx.sessionId);
|
|
861
|
-
const snapshotCacheKey = resumeSnapshotCacheKey(ctx.sessionKey, ctx.sessionId, parsed.terminalId);
|
|
862
|
-
if (parsed.action === "resume-thread" &&
|
|
863
|
-
parsed.selection &&
|
|
864
|
-
["number", "short-id", "snapshot-handle"].includes(parsed.selection.kind)) {
|
|
865
|
-
const mutationArgs = buildAkkCommandCliArgs(parsed, config, {
|
|
866
|
-
sessionKey: ctx.sessionKey,
|
|
867
|
-
selectionScope,
|
|
868
|
-
selectionSnapshotId: parsed.selection.kind === "snapshot-handle"
|
|
869
|
-
? undefined
|
|
870
|
-
: currentDisplayedResumeSnapshotId(displayedResumeSnapshots, snapshotCacheKey)
|
|
871
|
-
});
|
|
872
|
-
if (!mutationArgs) {
|
|
873
|
-
throw new Error("could not build snapshot-bound resume command");
|
|
874
|
-
}
|
|
875
|
-
const transitionResult = runCli(api, mutationArgs);
|
|
876
|
-
if (isAkkThreadTransitionSuccess(transitionResult)) {
|
|
877
|
-
displayedResumeSnapshots.delete(snapshotCacheKey);
|
|
878
|
-
}
|
|
879
|
-
return {
|
|
880
|
-
text: formatAkkThreadTransitionCommandResult(transitionResult),
|
|
881
|
-
isError: !isAkkThreadTransitionSuccess(transitionResult)
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
const discoveryArgs = buildAkkCommandCliArgs({
|
|
885
|
-
action: "list-resumable-threads",
|
|
886
|
-
terminalId: parsed.terminalId
|
|
887
|
-
}, config, { selectionScope });
|
|
888
|
-
if (!discoveryArgs) {
|
|
889
|
-
throw new Error("could not build native-thread discovery command");
|
|
890
|
-
}
|
|
891
|
-
const discovery = runCli(api, discoveryArgs);
|
|
892
|
-
if (parsed.action === "list-resumable-threads" ||
|
|
893
|
-
(parsed.action === "resume-thread" &&
|
|
894
|
-
!parsed.selection)) {
|
|
895
|
-
const snapshotId = isRecord(discovery.selection_snapshot)
|
|
896
|
-
? stringValue(discovery.selection_snapshot.snapshot_id)
|
|
897
|
-
: undefined;
|
|
898
|
-
const expiresAt = isRecord(discovery.selection_snapshot)
|
|
899
|
-
? Date.parse(String(discovery.selection_snapshot.expires_at ?? ""))
|
|
900
|
-
: Number.NaN;
|
|
901
|
-
if (snapshotId && Number.isFinite(expiresAt) && expiresAt > Date.now()) {
|
|
902
|
-
rememberDisplayedResumeSnapshot(displayedResumeSnapshots, snapshotCacheKey, snapshotId, expiresAt);
|
|
903
|
-
}
|
|
904
|
-
else {
|
|
905
|
-
displayedResumeSnapshots.delete(snapshotCacheKey);
|
|
906
|
-
}
|
|
907
|
-
return { text: formatAkkThreadsCommandResult(discovery) };
|
|
908
|
-
}
|
|
909
|
-
let expectedBindingToken = requiredString(discovery.expected_binding_token, "expected_binding_token from lifecycle discovery");
|
|
910
|
-
let candidateToken;
|
|
911
|
-
let mutationCommand = parsed;
|
|
912
|
-
if (parsed.action === "resume-thread") {
|
|
913
|
-
let nativeThreadId;
|
|
914
|
-
if (parsed.selection?.kind === "previous") {
|
|
915
|
-
const previous = isRecord(discovery.previous)
|
|
916
|
-
? discovery.previous
|
|
917
|
-
: undefined;
|
|
918
|
-
const action = isRecord(previous?.available_actions) &&
|
|
919
|
-
isRecord(previous.available_actions.resume_thread)
|
|
920
|
-
? previous.available_actions.resume_thread
|
|
921
|
-
: undefined;
|
|
922
|
-
const actionArguments = isRecord(action?.arguments)
|
|
923
|
-
? action.arguments
|
|
924
|
-
: undefined;
|
|
925
|
-
nativeThreadId = requiredString(actionArguments?.native_thread_id, "no verified previous native thread is available; run /akk threads");
|
|
926
|
-
if (stringValue(actionArguments?.terminal_id) !== parsed.terminalId) {
|
|
927
|
-
throw new Error("previous resume action belongs to another terminal");
|
|
928
|
-
}
|
|
929
|
-
expectedBindingToken = requiredString(actionArguments?.expected_binding_token, "expected_binding_token from the previous resume action");
|
|
930
|
-
candidateToken = requiredString(actionArguments?.candidate_token, "candidate_token from the previous resume action");
|
|
931
|
-
}
|
|
932
|
-
else {
|
|
933
|
-
if (parsed.selection?.kind !== "exact") {
|
|
934
|
-
throw new Error("resume selection could not be resolved");
|
|
935
|
-
}
|
|
936
|
-
nativeThreadId = parsed.selection.nativeThreadId;
|
|
937
|
-
const candidate = Array.isArray(discovery.threads)
|
|
938
|
-
? discovery.threads.find((thread) => isRecord(thread) &&
|
|
939
|
-
stringValue(thread.native_thread_id) === nativeThreadId)
|
|
940
|
-
: undefined;
|
|
941
|
-
if (!isRecord(candidate) || candidate.resumable !== true) {
|
|
942
|
-
throw new Error(`native thread ${nativeThreadId} is not resumable in the current lifecycle snapshot`);
|
|
943
|
-
}
|
|
944
|
-
candidateToken = requiredString(candidate.candidate_token, "candidate_token from the selected resumable thread");
|
|
945
|
-
}
|
|
946
|
-
mutationCommand = {
|
|
947
|
-
action: "resume-thread",
|
|
948
|
-
terminalId: parsed.terminalId,
|
|
949
|
-
selection: { kind: "exact", nativeThreadId }
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
const mutationArgs = buildAkkCommandCliArgs(mutationCommand, config, {
|
|
953
|
-
sessionKey: ctx.sessionKey,
|
|
954
|
-
expectedBindingToken,
|
|
955
|
-
candidateToken,
|
|
956
|
-
selectionScope
|
|
957
|
-
});
|
|
958
|
-
if (!mutationArgs) {
|
|
959
|
-
throw new Error("could not build native-thread lifecycle command");
|
|
960
|
-
}
|
|
961
|
-
const transitionResult = runCli(api, mutationArgs);
|
|
962
|
-
if (isAkkThreadTransitionSuccess(transitionResult)) {
|
|
963
|
-
displayedResumeSnapshots.delete(snapshotCacheKey);
|
|
964
|
-
}
|
|
965
|
-
return {
|
|
966
|
-
text: formatAkkThreadTransitionCommandResult(transitionResult),
|
|
967
|
-
isError: !isAkkThreadTransitionSuccess(transitionResult)
|
|
968
|
-
};
|
|
969
|
-
}
|
|
970
|
-
const args = buildAkkCommandCliArgs(parsed, config, {
|
|
971
|
-
sessionKey: ctx.sessionKey
|
|
972
|
-
});
|
|
973
|
-
if (!args) {
|
|
974
|
-
return { text: akkUsageText(), isError: true };
|
|
975
|
-
}
|
|
976
|
-
// Doctor calls back into the running Gateway for its independent health
|
|
977
|
-
// check. Keep the Gateway event loop free while that child CLI runs.
|
|
978
|
-
const result = parsed.action === "doctor"
|
|
979
|
-
? await runCliAsync(api, args, { allowNonzeroJson: true })
|
|
980
|
-
: runCli(api, args);
|
|
981
|
-
switch (parsed.action) {
|
|
982
|
-
case "doctor":
|
|
983
|
-
return {
|
|
984
|
-
text: formatDoctorCommandResult(result),
|
|
985
|
-
isError: result.ok !== true
|
|
986
|
-
};
|
|
987
|
-
case "list":
|
|
988
|
-
return { text: formatAkkListCommandResult(result) };
|
|
989
|
-
case "status":
|
|
990
|
-
return { text: formatStatusCommandResult(result) };
|
|
991
|
-
case "send":
|
|
992
|
-
return {
|
|
993
|
-
text: formatSendCommandResult(result),
|
|
994
|
-
isError: terminalSubmissionReported(result)
|
|
995
|
-
? !isAkkNativeSubmissionAccepted(result)
|
|
996
|
-
: result.status === "delivered_unfenced"
|
|
997
|
-
};
|
|
998
|
-
case "respond":
|
|
999
|
-
return formatAkkRespondCommandResult(result);
|
|
1000
|
-
case "approve":
|
|
1001
|
-
return { text: formatApproveCommandResult(result) };
|
|
1002
|
-
case "renew":
|
|
1003
|
-
return { text: formatRenewCommandResult(result) };
|
|
1004
|
-
case "retry-callback":
|
|
1005
|
-
return { text: formatRetryCallbackCommandResult(result) };
|
|
1006
|
-
case "cancel":
|
|
1007
|
-
return { text: formatCancelCommandResult(result) };
|
|
1008
|
-
case "close":
|
|
1009
|
-
return {
|
|
1010
|
-
text: formatCloseCommandResult(result),
|
|
1011
|
-
isError: isBlockedTerminalDispatchResult(result)
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
catch (error) {
|
|
1016
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1017
|
-
return {
|
|
1018
|
-
text: `AKK command failed: ${message}`,
|
|
1019
|
-
isError: true
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
function formatDelegateCommandResult(result) {
|
|
1024
|
-
const agent = executorDisplayName(result.agent);
|
|
1025
|
-
const { sessionId, turnId } = publicTurnIdentity(result);
|
|
1026
|
-
if (result.status === "submission_unfenced") {
|
|
1027
|
-
return [
|
|
1028
|
-
`AKK sent the terminal input to ${agent}, but could not bind later side effects to an exact native session.`,
|
|
1029
|
-
`session: ${sessionId}`,
|
|
1030
|
-
`turn: ${turnId}`,
|
|
1031
|
-
"next: do not retry or continue automatically; inspect the named pane and close this Turn before sending more work."
|
|
1032
|
-
].join("\n");
|
|
1033
|
-
}
|
|
1034
|
-
if (result.status === "submission_uncertain") {
|
|
1035
|
-
return [
|
|
1036
|
-
`AKK could not prove whether ${agent} received the terminal task.`,
|
|
1037
|
-
`session: ${sessionId}`,
|
|
1038
|
-
`turn: ${turnId}`,
|
|
1039
|
-
"next: do not retry automatically; inspect AKK status and the named terminal pane."
|
|
1040
|
-
].join("\n");
|
|
1041
|
-
}
|
|
1042
|
-
if (result.status === "submission_aborted") {
|
|
1043
|
-
const safeToRetry = result.safe_to_retry === true && result.do_not_retry !== true;
|
|
1044
|
-
return [
|
|
1045
|
-
`AKK stopped before sending the terminal task to ${agent}.`,
|
|
1046
|
-
`session: ${sessionId}`,
|
|
1047
|
-
`turn: ${turnId}`,
|
|
1048
|
-
safeToRetry
|
|
1049
|
-
? "next: no terminal input was sent and the aborted receipt is durable, so this request may be retried."
|
|
1050
|
-
: "next: do not retry automatically; AKK could not prove a durable safe abort, so inspect this Turn and its terminal dispatch ledger."
|
|
1051
|
-
].join("\n");
|
|
1052
|
-
}
|
|
1053
|
-
if (result.status === "submission_pending_acceptance") {
|
|
1054
|
-
return [
|
|
1055
|
-
`AKK dispatched terminal input to ${agent}, but native acceptance is still pending.`,
|
|
1056
|
-
`session: ${sessionId}`,
|
|
1057
|
-
`turn: ${turnId}`,
|
|
1058
|
-
"next: do not retry or report success; wait for acceptance or inspect the shared terminal pane."
|
|
1059
|
-
].join("\n");
|
|
1060
|
-
}
|
|
1061
|
-
if (result.status === "submission_not_accepted") {
|
|
1062
|
-
return [
|
|
1063
|
-
`AKK proved that ${agent} did not accept the terminal draft.`,
|
|
1064
|
-
`session: ${sessionId}`,
|
|
1065
|
-
`turn: ${turnId}`,
|
|
1066
|
-
"next: do not retry automatically; inspect the exact draft in the shared terminal pane."
|
|
1067
|
-
].join("\n");
|
|
1068
|
-
}
|
|
1069
|
-
return [
|
|
1070
|
-
`AKK sent the task to ${agent} in the shared terminal.`,
|
|
1071
|
-
`session: ${sessionId}`,
|
|
1072
|
-
`turn: ${turnId}`,
|
|
1073
|
-
`status: ${result.conversation_status ?? result.status ?? "unknown"}`,
|
|
1074
|
-
"The result will return to this OpenClaw session through the callback."
|
|
1075
|
-
].join("\n");
|
|
1076
|
-
}
|
|
1077
|
-
function executorDisplayName(kind) {
|
|
1078
|
-
try {
|
|
1079
|
-
return executorDefinitionForKind(String(kind ?? "codex")).displayName;
|
|
1080
|
-
}
|
|
1081
|
-
catch {
|
|
1082
|
-
return String(kind ?? "agent");
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
function formatStatusCommandResult(result) {
|
|
1086
|
-
const summary = result.summary ?? result.conversation ?? result ?? {};
|
|
1087
|
-
const terminalStatus = isRecord(result.terminal_status) ? result.terminal_status : {};
|
|
1088
|
-
const rawCallbackDelivery = isRecord(result.conversation?.callback_delivery)
|
|
1089
|
-
? result.conversation.callback_delivery
|
|
1090
|
-
: undefined;
|
|
1091
|
-
const summarizedCallbackDelivery = isRecord(summary.callback_delivery)
|
|
1092
|
-
? summary.callback_delivery
|
|
1093
|
-
: undefined;
|
|
1094
|
-
const callbackDelivery = rawCallbackDelivery || summarizedCallbackDelivery
|
|
1095
|
-
? {
|
|
1096
|
-
...(rawCallbackDelivery ?? {}),
|
|
1097
|
-
...(summarizedCallbackDelivery ?? {})
|
|
1098
|
-
}
|
|
1099
|
-
: undefined;
|
|
1100
|
-
const { sessionId, turnId } = publicTurnIdentity(result);
|
|
1101
|
-
const lines = [
|
|
1102
|
-
"AKK status:",
|
|
1103
|
-
`session: ${sessionId}`,
|
|
1104
|
-
`turn: ${turnId}`,
|
|
1105
|
-
`agent: ${summary.agent ?? summary.executor?.kind ?? terminalStatus.agent ?? "unknown"}`,
|
|
1106
|
-
`status: ${summary.status ?? terminalStatus.activity_state ?? "unknown"}`
|
|
1107
|
-
];
|
|
1108
|
-
if (callbackDelivery) {
|
|
1109
|
-
const callbackParts = [
|
|
1110
|
-
String(callbackDelivery.status ?? "unknown"),
|
|
1111
|
-
Number.isSafeInteger(Number(callbackDelivery.attempts))
|
|
1112
|
-
? `attempt ${Number(callbackDelivery.attempts)}`
|
|
1113
|
-
: undefined,
|
|
1114
|
-
callbackDelivery.attempt_state === "in_flight"
|
|
1115
|
-
? "in flight"
|
|
1116
|
-
: undefined,
|
|
1117
|
-
stringValue(callbackDelivery.next_attempt_at)
|
|
1118
|
-
? `next retry ${stringValue(callbackDelivery.next_attempt_at)}`
|
|
1119
|
-
: undefined
|
|
1120
|
-
].filter(Boolean);
|
|
1121
|
-
lines.push(`callback: ${callbackParts.join(", ")}`);
|
|
1122
|
-
}
|
|
1123
|
-
if (summary.request) {
|
|
1124
|
-
lines.push(`request: ${truncateText(summary.request, 180)}`);
|
|
1125
|
-
}
|
|
1126
|
-
if (result.about) {
|
|
1127
|
-
lines.push(`about: ${truncateText(result.about, 500)}`);
|
|
1128
|
-
}
|
|
1129
|
-
if (result.confidence) {
|
|
1130
|
-
lines.push(`confidence: ${result.confidence}`);
|
|
1131
|
-
}
|
|
1132
|
-
const limitations = Array.isArray(result.limitations)
|
|
1133
|
-
? result.limitations.filter(Boolean)
|
|
1134
|
-
: [];
|
|
1135
|
-
if (limitations.length > 0) {
|
|
1136
|
-
lines.push(`limitations: ${limitations.slice(0, 3).join("; ")}`);
|
|
1137
|
-
}
|
|
1138
|
-
const screen = terminalScreenExcerpt(result);
|
|
1139
|
-
if (screen) {
|
|
1140
|
-
lines.push(`terminal screen:\n${screen}`);
|
|
1141
|
-
}
|
|
1142
|
-
return lines.join("\n");
|
|
1143
|
-
}
|
|
1144
|
-
function formatDoctorCommandResult(result) {
|
|
1145
|
-
const capabilities = isRecord(result.capabilities) ? result.capabilities : {};
|
|
1146
|
-
const tmux = isRecord(capabilities.tmux) ? capabilities.tmux : {};
|
|
1147
|
-
const herdr = isRecord(capabilities.herdr) ? capabilities.herdr : {};
|
|
1148
|
-
const openclaw = isRecord(result.openclaw) ? result.openclaw : {};
|
|
1149
|
-
const checks = Array.isArray(openclaw.checks) ? openclaw.checks : [];
|
|
1150
|
-
const failures = checks
|
|
1151
|
-
.filter((check) => isRecord(check) && check.ok !== true)
|
|
1152
|
-
.map((check) => String(check.name ?? "unknown"));
|
|
1153
|
-
const remediation = [...new Set(checks.flatMap((check) => isRecord(check) && Array.isArray(check.remediation)
|
|
1154
|
-
? check.remediation.filter((command) => typeof command === "string" && command.trim().length > 0)
|
|
1155
|
-
: []))].slice(0, 3);
|
|
1156
|
-
return [
|
|
1157
|
-
`AKK doctor: ${result.ok === true ? "ready" : "needs attention"}`,
|
|
1158
|
-
`tmux: ${tmux.status ?? "unknown"}`,
|
|
1159
|
-
`Herdr: ${herdr.status ?? "unknown"}`,
|
|
1160
|
-
`OpenClaw package: ${openclaw.package_ready === true ? "ready" : "not ready"}`,
|
|
1161
|
-
`Gateway: ${openclaw.gateway_ready === true ? "healthy" : "unavailable"}`,
|
|
1162
|
-
...(failures.length > 0 ? [`check: ${failures.join(", ")}`] : []),
|
|
1163
|
-
...remediation.map((command) => `next: ${command}`)
|
|
1164
|
-
].join("\n");
|
|
1165
|
-
}
|
|
1166
|
-
function formatRenewCommandResult(result) {
|
|
1167
|
-
const { sessionId, turnId } = publicTurnIdentity(result);
|
|
1168
|
-
return [
|
|
1169
|
-
"AKK monitoring renewed.",
|
|
1170
|
-
`session: ${sessionId}`,
|
|
1171
|
-
`turn: ${turnId}`,
|
|
1172
|
-
`inactivity timeout: ${result.agent_timeout_minutes ?? "unknown"} minutes`,
|
|
1173
|
-
`hard lifetime: ${result.agent_hard_timeout_minutes ?? "unknown"} minutes`,
|
|
1174
|
-
"No message or key was sent to the coding agent."
|
|
1175
|
-
].join("\n");
|
|
1176
|
-
}
|
|
1177
|
-
function formatRetryCallbackCommandResult(result) {
|
|
1178
|
-
const { sessionId, turnId } = publicTurnIdentity(result);
|
|
1179
|
-
return [
|
|
1180
|
-
"AKK callback delivered.",
|
|
1181
|
-
`session: ${sessionId}`,
|
|
1182
|
-
`turn: ${turnId}`,
|
|
1183
|
-
`status: ${result.conversation?.status ?? "unknown"}`,
|
|
1184
|
-
`attempts: ${result.conversation?.callback_delivery?.attempts ?? "unknown"}`
|
|
1185
|
-
].join("\n");
|
|
1186
|
-
}
|
|
1187
|
-
function formatSendCommandResult(result) {
|
|
1188
|
-
const conversation = result.conversation ?? {};
|
|
1189
|
-
const conversationId = conversation.conversation_id ?? result.conversation_id ?? "unknown";
|
|
1190
|
-
const sessionId = conversation.session_id ?? result.session_id ?? conversationId;
|
|
1191
|
-
const turnId = conversation.turn_id ?? result.turn_id ?? conversationId;
|
|
1192
|
-
const status = conversation.status ?? result.status ?? "unknown";
|
|
1193
|
-
const nextAction = isRecord(result.openclaw_next_action) ? result.openclaw_next_action : undefined;
|
|
1194
|
-
if (result.status === "delivered_unfenced") {
|
|
1195
|
-
return [
|
|
1196
|
-
"AKK sent the terminal input but could not bind an exact native session.",
|
|
1197
|
-
`session: ${sessionId}`,
|
|
1198
|
-
`turn: ${turnId}`,
|
|
1199
|
-
`status: ${status}`,
|
|
1200
|
-
"next: do not retry or continue automatically; inspect the shared terminal pane and close this Turn before sending more work."
|
|
1201
|
-
].join("\n");
|
|
1202
|
-
}
|
|
1203
|
-
if (result.submission_outcome === "uncertain") {
|
|
1204
|
-
return [
|
|
1205
|
-
"AKK terminal submission outcome is uncertain.",
|
|
1206
|
-
`session: ${sessionId}`,
|
|
1207
|
-
`turn: ${turnId}`,
|
|
1208
|
-
`status: ${result.status ?? status}`,
|
|
1209
|
-
"next: do not retry automatically; inspect AKK status and the named terminal pane."
|
|
1210
|
-
].join("\n");
|
|
1211
|
-
}
|
|
1212
|
-
if (result.submission_outcome === "aborted") {
|
|
1213
|
-
const safeToRetry = result.safe_to_retry === true && result.do_not_retry !== true;
|
|
1214
|
-
return [
|
|
1215
|
-
"AKK terminal submission was aborted before terminal input.",
|
|
1216
|
-
`session: ${sessionId}`,
|
|
1217
|
-
`turn: ${turnId}`,
|
|
1218
|
-
`status: ${result.status ?? status}`,
|
|
1219
|
-
safeToRetry
|
|
1220
|
-
? "next: this request was not sent, the aborted receipt is durable, and it may be retried."
|
|
1221
|
-
: "next: do not retry automatically; inspect the Turn because a durable safe abort was not proven."
|
|
1222
|
-
].join("\n");
|
|
1223
|
-
}
|
|
1224
|
-
if (result.submission_outcome === "pending_acceptance") {
|
|
1225
|
-
return [
|
|
1226
|
-
"AKK dispatched the terminal input but native acceptance is still pending.",
|
|
1227
|
-
`session: ${sessionId}`,
|
|
1228
|
-
`turn: ${turnId}`,
|
|
1229
|
-
`status: ${result.status ?? status}`,
|
|
1230
|
-
"next: do not retry or report success; wait for acceptance or inspect the shared terminal pane."
|
|
1231
|
-
].join("\n");
|
|
1232
|
-
}
|
|
1233
|
-
if (result.submission_outcome === "not_accepted") {
|
|
1234
|
-
return [
|
|
1235
|
-
"AKK proved that the agent did not accept the terminal draft.",
|
|
1236
|
-
`session: ${sessionId}`,
|
|
1237
|
-
`turn: ${turnId}`,
|
|
1238
|
-
`status: ${result.status ?? status}`,
|
|
1239
|
-
"next: do not retry automatically; inspect the exact draft in the shared terminal pane."
|
|
1240
|
-
].join("\n");
|
|
1241
|
-
}
|
|
1242
|
-
if (terminalSubmissionReported(result) &&
|
|
1243
|
-
!isAkkNativeSubmissionAccepted(result)) {
|
|
1244
|
-
return [
|
|
1245
|
-
"AKK could not verify native agent acceptance for this terminal input.",
|
|
1246
|
-
`session: ${sessionId}`,
|
|
1247
|
-
`turn: ${turnId}`,
|
|
1248
|
-
`status: ${result.status ?? status}`,
|
|
1249
|
-
"next: do not retry or report success; inspect the exact Turn receipt and shared terminal pane."
|
|
1250
|
-
].join("\n");
|
|
1251
|
-
}
|
|
1252
|
-
const lines = [
|
|
1253
|
-
"AKK turn sent.",
|
|
1254
|
-
`session: ${sessionId}`,
|
|
1255
|
-
`turn: ${turnId}`,
|
|
1256
|
-
`status: ${status}`
|
|
1257
|
-
];
|
|
1258
|
-
if (result.source) {
|
|
1259
|
-
lines.push(`source: ${result.source}`);
|
|
1260
|
-
}
|
|
1261
|
-
return [
|
|
1262
|
-
...lines,
|
|
1263
|
-
nextAction?.action === "yield"
|
|
1264
|
-
? "next: yield now and wait for the AKK callback or an explicit status request."
|
|
1265
|
-
: `launched: ${result.launched === true ? "yes" : "no"}`
|
|
1266
|
-
].join("\n");
|
|
1267
|
-
}
|
|
1268
|
-
function formatCancelCommandResult(result) {
|
|
1269
|
-
const conversation = result.conversation ?? {};
|
|
1270
|
-
const { sessionId, turnId } = publicTurnIdentity(result);
|
|
1271
|
-
return [
|
|
1272
|
-
"AKK cancel requested.",
|
|
1273
|
-
`session: ${sessionId}`,
|
|
1274
|
-
`turn: ${turnId}`,
|
|
1275
|
-
`agent: ${result.executor?.kind ?? conversation.executor?.kind ?? "unknown"}`,
|
|
1276
|
-
`status: ${conversation.status ?? (result.cancel_requested === true ? "cancel requested" : "not cancelled")}`
|
|
1277
|
-
].join("\n");
|
|
1278
|
-
}
|
|
1279
|
-
function formatApproveCommandResult(result) {
|
|
1280
|
-
const conversation = result.conversation ?? {};
|
|
1281
|
-
const { sessionId, turnId } = publicTurnIdentity(result);
|
|
1282
|
-
return [
|
|
1283
|
-
result.approved === true
|
|
1284
|
-
? "AKK approved the current terminal request."
|
|
1285
|
-
: "AKK did not approve the terminal request.",
|
|
1286
|
-
`session: ${sessionId}`,
|
|
1287
|
-
`turn: ${turnId}`,
|
|
1288
|
-
`status: ${conversation.status ?? "unknown"}`,
|
|
1289
|
-
...(result.reason ? [`reason: ${result.reason}`] : [])
|
|
1290
|
-
].join("\n");
|
|
1291
|
-
}
|
|
1292
|
-
function formatCloseCommandResult(result) {
|
|
1293
|
-
if (result.source === "terminal_control") {
|
|
1294
|
-
const terminalControl = isRecord(result.terminal_control)
|
|
1295
|
-
? result.terminal_control
|
|
1296
|
-
: {};
|
|
1297
|
-
if (result.terminal_dispatch_resolved !== true) {
|
|
1298
|
-
return [
|
|
1299
|
-
"AKK did not clear the unresolved terminal dispatch fence.",
|
|
1300
|
-
`terminal: ${terminalControl.target ?? "unknown"}`,
|
|
1301
|
-
...(stringValue(result.transition_id)
|
|
1302
|
-
? [`transition: ${stringValue(result.transition_id)}`]
|
|
1303
|
-
: []),
|
|
1304
|
-
`reason: ${stringValue(result.reason) ?? "the recorded lifecycle outcome could not be verified"}`,
|
|
1305
|
-
"This terminal remains blocked. Do not retry or continue automatically; inspect the pane and use the fresh recovery action from /akk list."
|
|
1306
|
-
].join("\n");
|
|
1307
|
-
}
|
|
1308
|
-
return [
|
|
1309
|
-
"AKK cleared the unresolved terminal dispatch fence.",
|
|
1310
|
-
`terminal: ${terminalControl.target ?? "unknown"}`,
|
|
1311
|
-
...(stringValue(result.transition_id)
|
|
1312
|
-
? [`transition: ${stringValue(result.transition_id)}`]
|
|
1313
|
-
: [
|
|
1314
|
-
`previous turn: ${result.owner_turn_id ?? result.owner_conversation_id ?? "unknown"}`
|
|
1315
|
-
]),
|
|
1316
|
-
"The coding agent and terminal pane remain open."
|
|
1317
|
-
].join("\n");
|
|
1318
|
-
}
|
|
1319
|
-
const conversation = result.conversation ?? {};
|
|
1320
|
-
const { sessionId, turnId } = publicTurnIdentity(result);
|
|
1321
|
-
return [
|
|
1322
|
-
"AKK Turn record closed.",
|
|
1323
|
-
`session: ${sessionId}`,
|
|
1324
|
-
`turn: ${turnId}`,
|
|
1325
|
-
`status: ${conversation.status ?? "unknown"}`
|
|
1326
|
-
].join("\n");
|
|
1327
|
-
}
|
|
1328
|
-
function publicTurnIdentity(result) {
|
|
1329
|
-
const conversation = isRecord(result.conversation) ? result.conversation : {};
|
|
1330
|
-
const summary = isRecord(result.summary) ? result.summary : {};
|
|
1331
|
-
const compatibilityId = stringValue(conversation.conversation_id) ??
|
|
1332
|
-
stringValue(summary.conversation_id) ??
|
|
1333
|
-
stringValue(result.conversation_id);
|
|
1334
|
-
return {
|
|
1335
|
-
sessionId: stringValue(conversation.session_id) ??
|
|
1336
|
-
stringValue(summary.session_id) ??
|
|
1337
|
-
stringValue(result.session_id) ??
|
|
1338
|
-
compatibilityId ??
|
|
1339
|
-
"unknown",
|
|
1340
|
-
turnId: stringValue(conversation.turn_id) ??
|
|
1341
|
-
stringValue(summary.turn_id) ??
|
|
1342
|
-
stringValue(result.turn_id) ??
|
|
1343
|
-
compatibilityId ??
|
|
1344
|
-
"unknown"
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
function buildStatusCliArgs(api, params) {
|
|
1348
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1349
|
-
const args = [
|
|
1350
|
-
"status",
|
|
1351
|
-
"--reconcile"
|
|
1352
|
-
];
|
|
1353
|
-
pushTurnTarget(args, params);
|
|
1354
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1355
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
|
|
1356
|
-
numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
1357
|
-
if (params.trace === true) {
|
|
1358
|
-
args.push("--trace");
|
|
1359
|
-
}
|
|
1360
|
-
return args;
|
|
1361
|
-
}
|
|
1362
|
-
function terminalScreenExcerpt(result) {
|
|
1363
|
-
const screen = result.terminal_screen;
|
|
1364
|
-
const text = typeof screen === "string"
|
|
1365
|
-
? screen
|
|
1366
|
-
: isRecord(screen)
|
|
1367
|
-
? stringValue(screen.excerpt) ??
|
|
1368
|
-
stringValue(screen.text) ??
|
|
1369
|
-
stringValue(screen.content)
|
|
1370
|
-
: undefined;
|
|
1371
|
-
if (!text) {
|
|
1372
|
-
return undefined;
|
|
1373
|
-
}
|
|
1374
|
-
return text.length <= 1600
|
|
1375
|
-
? text
|
|
1376
|
-
: `…${text.slice(-1599)}`;
|
|
1377
|
-
}
|
|
1378
|
-
async function runSendRequest(api, params, toolContext, messageId) {
|
|
1379
|
-
const requestedType = Object.hasOwn(params, "type")
|
|
1380
|
-
? stringValue(params.type)
|
|
1381
|
-
: "task";
|
|
1382
|
-
if (requestedType !== "task") {
|
|
1383
|
-
throw new Error("ordinary send type must be task; use agent_knock_knock_respond for an in-flight response");
|
|
1384
|
-
}
|
|
1385
|
-
if (Object.hasOwn(params, "session_id") && Object.hasOwn(params, "selector")) {
|
|
1386
|
-
throw new Error("ordinary send accepts only one of session_id or selector");
|
|
1387
|
-
}
|
|
1388
|
-
const sessionId = Object.hasOwn(params, "session_id")
|
|
1389
|
-
? authoritativeManagedId(params.session_id, "session_id")
|
|
1390
|
-
: undefined;
|
|
1391
|
-
const selector = Object.hasOwn(params, "selector")
|
|
1392
|
-
? requiredString(params.selector, "selector")
|
|
1393
|
-
: undefined;
|
|
1394
|
-
const expectedTerminalToken = Object.hasOwn(params, "expected_terminal_token")
|
|
1395
|
-
? requiredString(params.expected_terminal_token, "expected_terminal_token")
|
|
1396
|
-
: undefined;
|
|
1397
|
-
if (expectedTerminalToken && !selector) {
|
|
1398
|
-
throw new Error("expected_terminal_token requires a terminal-scoped selector and cannot be used with session_id");
|
|
1399
|
-
}
|
|
1400
|
-
if (expectedTerminalToken &&
|
|
1401
|
-
!/^terminal:v[0-9]+:/u.test(selector ?? "")) {
|
|
1402
|
-
throw new Error("expected_terminal_token requires the exact full terminal selector prefilled by AKK list");
|
|
1403
|
-
}
|
|
1404
|
-
if (!sessionId && !selector) {
|
|
1405
|
-
return runDelegate(api, { ...params, messageId }, toolContext);
|
|
1406
|
-
}
|
|
1407
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1408
|
-
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
1409
|
-
"agent:main:main";
|
|
1410
|
-
const args = [
|
|
1411
|
-
"send"
|
|
1412
|
-
];
|
|
1413
|
-
if (sessionId) {
|
|
1414
|
-
args.push("--session", sessionId);
|
|
1415
|
-
}
|
|
1416
|
-
else {
|
|
1417
|
-
args.push("--conversation", requiredString(selector, "selector"));
|
|
1418
|
-
}
|
|
1419
|
-
pushOptional(args, "--expected-terminal-token", expectedTerminalToken);
|
|
1420
|
-
args.push("--message", requiredString(params.request, "request"), "--background");
|
|
1421
|
-
pushOptional(args, "--type", stringValue(params.type));
|
|
1422
|
-
pushOptional(args, "--message-id", messageId);
|
|
1423
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1424
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
|
|
1425
|
-
numberString(config.idleTimeoutMinutes));
|
|
1426
|
-
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ??
|
|
1427
|
-
numberString(config.agentTimeoutMinutes));
|
|
1428
|
-
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ??
|
|
1429
|
-
numberString(config.agentHardTimeoutMinutes));
|
|
1430
|
-
pushOptional(args, "--openclaw-session", openclawSession);
|
|
1431
|
-
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
1432
|
-
pushOptional(args, "--gateway-session", openclawSession);
|
|
1433
|
-
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
1434
|
-
return runCli(api, args);
|
|
1435
|
-
}
|
|
1436
|
-
function toolResult(result, { submissionErrors = false, normalizeTurnIdentity = true, forceError = false } = {}) {
|
|
1437
|
-
const normalized = normalizeTurnIdentity ? withTurnIdentity(result) : result;
|
|
1438
|
-
const submissionError = submissionErrors && isSubmissionError(normalized);
|
|
1439
|
-
return {
|
|
1440
|
-
content: [
|
|
1441
|
-
{
|
|
1442
|
-
type: "text",
|
|
1443
|
-
text: JSON.stringify(normalized, null, 2)
|
|
1444
|
-
}
|
|
1445
|
-
],
|
|
1446
|
-
details: normalized,
|
|
1447
|
-
...(submissionError || forceError ? { isError: true } : {})
|
|
1448
|
-
};
|
|
1449
|
-
}
|
|
1450
|
-
function isBlockedTerminalDispatchResult(result) {
|
|
1451
|
-
return Boolean(isRecord(result) &&
|
|
1452
|
-
result.source === "terminal_control" &&
|
|
1453
|
-
result.terminal_dispatch_resolved !== true);
|
|
1454
|
-
}
|
|
1455
|
-
function isSubmissionError(result) {
|
|
1456
|
-
if (!isRecord(result)) {
|
|
1457
|
-
return false;
|
|
1458
|
-
}
|
|
1459
|
-
if (terminalSubmissionReported(result)) {
|
|
1460
|
-
return !isAkkNativeSubmissionAccepted(result);
|
|
1461
|
-
}
|
|
1462
|
-
return [
|
|
1463
|
-
"submission_unfenced",
|
|
1464
|
-
"submission_uncertain",
|
|
1465
|
-
"submission_aborted",
|
|
1466
|
-
"submission_pending_acceptance",
|
|
1467
|
-
"submission_not_accepted"
|
|
1468
|
-
].includes(String(result.status ?? "")) ||
|
|
1469
|
-
["uncertain", "aborted", "pending_acceptance", "not_accepted"].includes(String(result.submission_outcome ?? "")) ||
|
|
1470
|
-
result.status === "delivered_unfenced";
|
|
1471
|
-
}
|
|
1472
|
-
function terminalSubmissionReported(result) {
|
|
1473
|
-
return result.submission_outcome !== undefined ||
|
|
1474
|
-
result.delivery_receipt !== undefined ||
|
|
1475
|
-
result.delivered !== undefined;
|
|
1476
|
-
}
|
|
1477
|
-
function withTurnIdentity(result) {
|
|
1478
|
-
if (!isRecord(result)) {
|
|
1479
|
-
return result;
|
|
1480
|
-
}
|
|
1481
|
-
const sources = [
|
|
1482
|
-
{ label: "result", value: result },
|
|
1483
|
-
{ label: "result.conversation", value: result.conversation },
|
|
1484
|
-
{ label: "result.summary", value: result.summary },
|
|
1485
|
-
{ label: "result.message", value: result.message }
|
|
1486
|
-
];
|
|
1487
|
-
const compatibilityId = consistentResultIdentity("conversation_id", sources);
|
|
1488
|
-
const explicitSessionId = consistentResultIdentity("session_id", sources);
|
|
1489
|
-
const explicitTurnId = consistentResultIdentity("turn_id", sources);
|
|
1490
|
-
const hasModernIdentity = Boolean(explicitSessionId || explicitTurnId);
|
|
1491
|
-
if (hasModernIdentity && (!explicitSessionId || !explicitTurnId)) {
|
|
1492
|
-
throw new Error("agent-knock-knock CLI returned a partial session_id/turn_id identity");
|
|
1493
|
-
}
|
|
1494
|
-
if (hasModernIdentity && !compatibilityId) {
|
|
1495
|
-
throw new Error("agent-knock-knock CLI returned modern identity without conversation_id");
|
|
1496
|
-
}
|
|
1497
|
-
if (hasModernIdentity && compatibilityId !== explicitTurnId) {
|
|
1498
|
-
throw new Error("agent-knock-knock CLI returned conversation_id that differs from turn_id");
|
|
1499
|
-
}
|
|
1500
|
-
if (!hasModernIdentity && !compatibilityId) {
|
|
1501
|
-
return result;
|
|
1502
|
-
}
|
|
1503
|
-
if (!hasModernIdentity &&
|
|
1504
|
-
(compatibilityId?.startsWith("terminal:") ||
|
|
1505
|
-
stringValue(result.source) === "terminal" ||
|
|
1506
|
-
(isRecord(result.summary) &&
|
|
1507
|
-
stringValue(result.summary.source) === "terminal"))) {
|
|
1508
|
-
return result;
|
|
1509
|
-
}
|
|
1510
|
-
const sessionId = explicitSessionId ?? compatibilityId;
|
|
1511
|
-
const turnId = explicitTurnId ?? compatibilityId;
|
|
1512
|
-
return {
|
|
1513
|
-
...result,
|
|
1514
|
-
session_id: sessionId,
|
|
1515
|
-
turn_id: turnId
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1518
|
-
function consistentResultIdentity(field, sources) {
|
|
1519
|
-
const values = sources.flatMap(({ label, value }) => {
|
|
1520
|
-
if (!isRecord(value) || !Object.hasOwn(value, field)) {
|
|
1521
|
-
return [];
|
|
1522
|
-
}
|
|
1523
|
-
const identity = stringValue(value[field]);
|
|
1524
|
-
if (!identity) {
|
|
1525
|
-
throw new Error(`agent-knock-knock CLI returned invalid ${label}.${field}`);
|
|
1526
|
-
}
|
|
1527
|
-
return [{ label, identity }];
|
|
1528
|
-
});
|
|
1529
|
-
const expected = values[0];
|
|
1530
|
-
for (const candidate of values.slice(1)) {
|
|
1531
|
-
if (candidate.identity !== expected.identity) {
|
|
1532
|
-
throw new Error(`agent-knock-knock CLI returned conflicting ${field} between ${expected.label} and ${candidate.label}`);
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
return expected?.identity;
|
|
1536
|
-
}
|
|
1537
|
-
async function runDelegate(api, params, toolContext) {
|
|
1538
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1539
|
-
const request = requiredString(params.request, "request");
|
|
1540
|
-
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
1541
|
-
"agent:main:main";
|
|
1542
|
-
const args = [
|
|
1543
|
-
"delegate",
|
|
1544
|
-
"--request",
|
|
1545
|
-
request,
|
|
1546
|
-
"--background"
|
|
1547
|
-
];
|
|
1548
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1549
|
-
pushOptional(args, "--message-id", stringValue(params.messageId));
|
|
1550
|
-
pushOptional(args, "--openclaw-session", openclawSession);
|
|
1551
|
-
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
1552
|
-
pushOptional(args, "--gateway-session", openclawSession);
|
|
1553
|
-
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
1554
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
1555
|
-
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
|
|
1556
|
-
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
|
|
1557
|
-
const parsed = withTurnIdentity(await runCliAsync(api, args));
|
|
1558
|
-
if (!isRecord(parsed)) {
|
|
1559
|
-
throw new Error("agent-knock-knock delegate returned a non-object result");
|
|
1560
|
-
}
|
|
1561
|
-
const conversationId = stringValue(parsed.conversation_id) ??
|
|
1562
|
-
(isRecord(parsed.conversation)
|
|
1563
|
-
? stringValue(parsed.conversation.conversation_id)
|
|
1564
|
-
: undefined);
|
|
1565
|
-
const sessionId = stringValue(parsed.session_id);
|
|
1566
|
-
const turnId = stringValue(parsed.turn_id);
|
|
1567
|
-
if (!conversationId || !sessionId || !turnId) {
|
|
1568
|
-
throw new Error("agent-knock-knock delegate returned incomplete Turn identity");
|
|
1569
|
-
}
|
|
1570
|
-
const parsedConversation = isRecord(parsed.conversation)
|
|
1571
|
-
? parsed.conversation
|
|
1572
|
-
: undefined;
|
|
1573
|
-
const parsedPaths = isRecord(parsed.paths) ? parsed.paths : undefined;
|
|
1574
|
-
const parsedTerminalControl = isRecord(parsed.terminal_control)
|
|
1575
|
-
? parsed.terminal_control
|
|
1576
|
-
: undefined;
|
|
1577
|
-
const statePath = parsedConversation?.state_path ??
|
|
1578
|
-
parsedPaths?.statePath;
|
|
1579
|
-
const logPath = parsedConversation?.event_log_path ??
|
|
1580
|
-
parsedPaths?.logPath;
|
|
1581
|
-
const submissionUncertain = parsed.submission_outcome === "uncertain";
|
|
1582
|
-
const submissionAborted = parsed.submission_outcome === "aborted";
|
|
1583
|
-
const submissionAccepted = parsed.submission_outcome === "agent_accepted" &&
|
|
1584
|
-
parsed.delivery_receipt === "agent_accepted" &&
|
|
1585
|
-
parsed.delivered === true;
|
|
1586
|
-
const submissionNotAccepted = parsed.submission_outcome === "not_accepted";
|
|
1587
|
-
const submissionPending = !submissionAccepted &&
|
|
1588
|
-
!submissionUncertain &&
|
|
1589
|
-
!submissionAborted &&
|
|
1590
|
-
!submissionNotAccepted;
|
|
1591
|
-
const submissionUnfenced = parsed.status === "delivered_unfenced";
|
|
1592
|
-
const agent = (isRecord(parsedConversation?.executor)
|
|
1593
|
-
? stringValue(parsedConversation.executor.kind)
|
|
1594
|
-
: undefined) ??
|
|
1595
|
-
stringValue(parsed.agent);
|
|
1596
|
-
return {
|
|
1597
|
-
status: submissionUnfenced
|
|
1598
|
-
? "submission_unfenced"
|
|
1599
|
-
: submissionUncertain
|
|
1600
|
-
? "submission_uncertain"
|
|
1601
|
-
: submissionAborted
|
|
1602
|
-
? "submission_aborted"
|
|
1603
|
-
: submissionNotAccepted
|
|
1604
|
-
? "submission_not_accepted"
|
|
1605
|
-
: submissionPending
|
|
1606
|
-
? "submission_pending_acceptance"
|
|
1607
|
-
: "async_pending",
|
|
1608
|
-
submission_status: submissionUnfenced
|
|
1609
|
-
? "submitted_unfenced"
|
|
1610
|
-
: submissionUncertain
|
|
1611
|
-
? "uncertain"
|
|
1612
|
-
: submissionAborted
|
|
1613
|
-
? "aborted"
|
|
1614
|
-
: submissionNotAccepted
|
|
1615
|
-
? "not_accepted"
|
|
1616
|
-
: submissionPending
|
|
1617
|
-
? "pending_acceptance"
|
|
1618
|
-
: "accepted",
|
|
1619
|
-
conversation_id: conversationId,
|
|
1620
|
-
session_id: sessionId,
|
|
1621
|
-
turn_id: turnId,
|
|
1622
|
-
conversation_status: parsedConversation?.status,
|
|
1623
|
-
state_path: statePath,
|
|
1624
|
-
event_log_path: logPath,
|
|
1625
|
-
agent,
|
|
1626
|
-
executor: parsedConversation?.executor,
|
|
1627
|
-
session: (isRecord(parsedConversation?.executor)
|
|
1628
|
-
? parsedConversation.executor.session
|
|
1629
|
-
: undefined) ??
|
|
1630
|
-
parsedTerminalControl?.target,
|
|
1631
|
-
openclaw_session: openclawSession,
|
|
1632
|
-
launched: parsed.launched === true,
|
|
1633
|
-
replayed: parsed.replayed === true,
|
|
1634
|
-
background: parsed.background === true,
|
|
1635
|
-
pid: parsed.pid ?? parsedTerminalControl?.panePid ?? null,
|
|
1636
|
-
callback_method: CALLBACK_METHOD,
|
|
1637
|
-
...(submissionUnfenced
|
|
1638
|
-
? {
|
|
1639
|
-
submission_outcome: "submitted",
|
|
1640
|
-
do_not_retry: true,
|
|
1641
|
-
reason: parsed.reason,
|
|
1642
|
-
openclaw_next_action: parsed.openclaw_next_action,
|
|
1643
|
-
note: "AKK sent the terminal input but could not fence later side effects to an exact native session. Do not retry or continue automatically; inspect the pane and close this Turn."
|
|
1644
|
-
}
|
|
1645
|
-
: submissionUncertain
|
|
1646
|
-
? {
|
|
1647
|
-
submission_outcome: "uncertain",
|
|
1648
|
-
do_not_retry: true,
|
|
1649
|
-
reason: parsed.reason,
|
|
1650
|
-
openclaw_next_action: parsed.openclaw_next_action,
|
|
1651
|
-
note: "AKK could not prove whether the terminal accepted Enter. Do not retry automatically; inspect the exact AKK Turn record and shared terminal."
|
|
1652
|
-
}
|
|
1653
|
-
: submissionAborted
|
|
1654
|
-
? parsed.safe_to_retry === true && parsed.do_not_retry !== true
|
|
1655
|
-
? {
|
|
1656
|
-
submission_outcome: "aborted",
|
|
1657
|
-
safe_to_retry: true,
|
|
1658
|
-
do_not_retry: false,
|
|
1659
|
-
reason: parsed.reason,
|
|
1660
|
-
openclaw_next_action: parsed.openclaw_next_action,
|
|
1661
|
-
note: "AKK stopped before sending terminal input and durably proved a safe abort. The request may be retried."
|
|
1662
|
-
}
|
|
1663
|
-
: {
|
|
1664
|
-
submission_outcome: "aborted",
|
|
1665
|
-
safe_to_retry: false,
|
|
1666
|
-
do_not_retry: true,
|
|
1667
|
-
reason: parsed.reason,
|
|
1668
|
-
openclaw_next_action: parsed.openclaw_next_action,
|
|
1669
|
-
note: "AKK could not prove a durable safe abort. Do not retry automatically; inspect the exact Turn and terminal dispatch ledger."
|
|
1670
|
-
}
|
|
1671
|
-
: submissionNotAccepted
|
|
1672
|
-
? {
|
|
1673
|
-
submission_outcome: "not_accepted",
|
|
1674
|
-
do_not_retry: true,
|
|
1675
|
-
reason: parsed.reason,
|
|
1676
|
-
openclaw_next_action: parsed.openclaw_next_action,
|
|
1677
|
-
note: "AKK proved terminal transport but the exact draft is still present in the agent composer. Do not retry automatically; inspect the shared pane."
|
|
1678
|
-
}
|
|
1679
|
-
: submissionPending
|
|
1680
|
-
? {
|
|
1681
|
-
submission_outcome: "pending_acceptance",
|
|
1682
|
-
do_not_retry: true,
|
|
1683
|
-
reason: parsed.reason,
|
|
1684
|
-
openclaw_next_action: parsed.openclaw_next_action,
|
|
1685
|
-
note: "AKK proved only terminal transport and is still waiting for native agent acceptance. Do not retry or report the task as accepted."
|
|
1686
|
-
}
|
|
1687
|
-
: {
|
|
1688
|
-
openclaw_next_action: {
|
|
1689
|
-
action: "yield",
|
|
1690
|
-
reason: "The coding agent is working in the shared terminal. End this OpenClaw turn now and wait for an Agent Knock Knock callback.",
|
|
1691
|
-
do_not: "Do not poll terminal internals while waiting. Further communication must use Agent Knock Knock tools so the same shared terminal remains authoritative.",
|
|
1692
|
-
expected_callback: "The callback will be injected and scheduled into this OpenClaw session by the agent-knock-knock.callback Gateway method."
|
|
1693
|
-
},
|
|
1694
|
-
note: "The task was sent to the shared terminal. OpenClaw should yield now and wait for the scheduled callback turn."
|
|
1695
|
-
})
|
|
1696
|
-
};
|
|
1697
|
-
}
|
|
1698
|
-
function terminalMessageIdForToolCall({ toolCallId: toolCallIdValue, sessionKey: sessionKeyValue, sessionId: sessionIdValue, toolName }) {
|
|
1699
|
-
const toolCallId = stringValue(toolCallIdValue);
|
|
1700
|
-
if (!toolCallId) {
|
|
1701
|
-
return undefined;
|
|
1702
|
-
}
|
|
1703
|
-
const sessionKey = stringValue(sessionKeyValue) ?? "agent:main:main";
|
|
1704
|
-
// sessionId is the OpenClaw conversation incarnation. It changes across
|
|
1705
|
-
// /new and /reset even when sessionKey remains stable. Keep a literal null
|
|
1706
|
-
// fallback so retries with the same legacy context remain deterministic.
|
|
1707
|
-
const sessionId = stringValue(sessionIdValue) ?? null;
|
|
1708
|
-
const digest = createHash("sha256")
|
|
1709
|
-
.update(JSON.stringify([sessionKey, sessionId, toolName, toolCallId]))
|
|
1710
|
-
.digest("hex");
|
|
1711
|
-
return `msg-openclaw-${digest}`;
|
|
1712
|
-
}
|
|
1713
|
-
function registerCliTool(api, { name, description, parameters, buildArgs, normalizeTurnIdentity = true, isErrorResult = (_result) => false }) {
|
|
1714
|
-
api.registerTool((toolContext) => ({
|
|
1715
|
-
label: toolLabel(name),
|
|
1716
|
-
name,
|
|
1717
|
-
description,
|
|
1718
|
-
parameters,
|
|
1719
|
-
async execute(toolCallId, params) {
|
|
1720
|
-
const result = runCli(api, buildArgs(isRecord(params) ? params : {}, toolContext, toolCallId));
|
|
1721
|
-
return toolResult(result, {
|
|
1722
|
-
submissionErrors: name === "agent_knock_knock_respond",
|
|
1723
|
-
normalizeTurnIdentity,
|
|
1724
|
-
forceError: typeof isErrorResult === "function" && isErrorResult(result) === true
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
|
-
}), { name, optional: true });
|
|
1728
|
-
}
|
|
1729
|
-
function toolLabel(name) {
|
|
1730
|
-
const action = String(name)
|
|
1731
|
-
.replace(/^agent_knock_knock_/, "")
|
|
1732
|
-
.split("_")
|
|
1733
|
-
.filter(Boolean)
|
|
1734
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
1735
|
-
.join(" ");
|
|
1736
|
-
return `AKK ${action || "Tool"}`;
|
|
1737
|
-
}
|
|
1738
|
-
function runCli(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false } = {}) {
|
|
1739
|
-
const binPath = relayPathForApi(api);
|
|
1740
|
-
const spawned = spawnSync(process.execPath, [binPath, ...cliArgs], {
|
|
1741
|
-
encoding: "utf8",
|
|
1742
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
1743
|
-
cwd
|
|
1744
|
-
});
|
|
1745
|
-
if (spawned.error) {
|
|
1746
|
-
throw new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${spawned.error.message}`);
|
|
1747
|
-
}
|
|
1748
|
-
if (spawned.status !== 0) {
|
|
1749
|
-
if (allowNonzeroJson && spawned.stdout.trim()) {
|
|
1750
|
-
return parseJson(spawned.stdout);
|
|
1751
|
-
}
|
|
1752
|
-
throw new Error(cleanError(spawned.stderr || spawned.stdout || `agent-knock-knock ${cliArgs[0]} exited with status ${spawned.status}`));
|
|
1753
|
-
}
|
|
1754
|
-
return parseJson(spawned.stdout);
|
|
1755
|
-
}
|
|
1756
|
-
function runCliAsync(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false, timeoutMs = 90_000 } = {}) {
|
|
1757
|
-
const binPath = relayPathForApi(api);
|
|
1758
|
-
const maxBuffer = 10 * 1024 * 1024;
|
|
1759
|
-
return new Promise((resolve, reject) => {
|
|
1760
|
-
const child = spawn(process.execPath, [binPath, ...cliArgs], {
|
|
1761
|
-
cwd,
|
|
1762
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1763
|
-
windowsHide: true
|
|
1764
|
-
});
|
|
1765
|
-
child.stdout.setEncoding("utf8");
|
|
1766
|
-
child.stderr.setEncoding("utf8");
|
|
1767
|
-
let stdout = "";
|
|
1768
|
-
let stderr = "";
|
|
1769
|
-
let overflow = false;
|
|
1770
|
-
let timedOut = false;
|
|
1771
|
-
const append = (current, chunk) => {
|
|
1772
|
-
const next = current + chunk;
|
|
1773
|
-
if (Buffer.byteLength(next, "utf8") > maxBuffer) {
|
|
1774
|
-
overflow = true;
|
|
1775
|
-
child.kill("SIGKILL");
|
|
1776
|
-
}
|
|
1777
|
-
return next;
|
|
1778
|
-
};
|
|
1779
|
-
child.stdout.on("data", (chunk) => {
|
|
1780
|
-
stdout = append(stdout, chunk);
|
|
1781
|
-
});
|
|
1782
|
-
child.stderr.on("data", (chunk) => {
|
|
1783
|
-
stderr = append(stderr, chunk);
|
|
1784
|
-
});
|
|
1785
|
-
const timeout = setTimeout(() => {
|
|
1786
|
-
timedOut = true;
|
|
1787
|
-
child.kill("SIGKILL");
|
|
1788
|
-
}, timeoutMs);
|
|
1789
|
-
timeout.unref();
|
|
1790
|
-
child.once("error", (error) => {
|
|
1791
|
-
clearTimeout(timeout);
|
|
1792
|
-
reject(new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${error.message}`));
|
|
1793
|
-
});
|
|
1794
|
-
child.once("close", (status) => {
|
|
1795
|
-
clearTimeout(timeout);
|
|
1796
|
-
if (timedOut) {
|
|
1797
|
-
reject(new Error(`agent-knock-knock ${cliArgs[0]} timed out`));
|
|
1798
|
-
return;
|
|
1799
|
-
}
|
|
1800
|
-
if (overflow) {
|
|
1801
|
-
reject(new Error(`agent-knock-knock ${cliArgs[0]} output exceeded 10 MiB`));
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
if (status !== 0) {
|
|
1805
|
-
if (allowNonzeroJson && stdout.trim()) {
|
|
1806
|
-
try {
|
|
1807
|
-
resolve(parseJson(stdout));
|
|
1808
|
-
}
|
|
1809
|
-
catch (error) {
|
|
1810
|
-
reject(error);
|
|
1811
|
-
}
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
reject(new Error(cleanError(stderr ||
|
|
1815
|
-
stdout ||
|
|
1816
|
-
`agent-knock-knock ${cliArgs[0]} exited with status ${status}`)));
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
try {
|
|
1820
|
-
resolve(parseJson(stdout));
|
|
1821
|
-
}
|
|
1822
|
-
catch (error) {
|
|
1823
|
-
reject(error);
|
|
1824
|
-
}
|
|
1825
|
-
});
|
|
1826
|
-
});
|
|
1827
|
-
}
|
|
1828
|
-
function relayPathForApi(api) {
|
|
1829
|
-
return relayPathByApi.get(api) ?? defaultBinPath;
|
|
1830
|
-
}
|
|
1831
|
-
function callbackIdentity({ params, conversation, message, messageMetadata }) {
|
|
1832
|
-
const messageHasSessionId = isRecord(message) &&
|
|
1833
|
-
Object.hasOwn(message, "session_id");
|
|
1834
|
-
const messageHasTurnId = isRecord(message) &&
|
|
1835
|
-
Object.hasOwn(message, "turn_id");
|
|
1836
|
-
if (messageHasSessionId !== messageHasTurnId) {
|
|
1837
|
-
throw new Error("modern callback messages require both session_id and turn_id");
|
|
1838
|
-
}
|
|
1839
|
-
const identityMode = messageHasSessionId ? "modern" : "legacy";
|
|
1840
|
-
const sources = [
|
|
1841
|
-
{ label: "message", value: message },
|
|
1842
|
-
{ label: "message.metadata", value: messageMetadata },
|
|
1843
|
-
{ label: "conversation", value: conversation },
|
|
1844
|
-
{ label: "params", value: params }
|
|
1845
|
-
];
|
|
1846
|
-
const explicitConversationId = consistentCallbackIdentity("conversation_id", sources);
|
|
1847
|
-
const explicitTurnId = consistentCallbackIdentity("turn_id", sources);
|
|
1848
|
-
const explicitSessionId = consistentCallbackIdentity("session_id", sources);
|
|
1849
|
-
const hasModernIdentity = Boolean(explicitSessionId || explicitTurnId);
|
|
1850
|
-
if (hasModernIdentity && (!explicitSessionId || !explicitTurnId)) {
|
|
1851
|
-
throw new Error("modern callbacks require both session_id and turn_id");
|
|
1852
|
-
}
|
|
1853
|
-
if (hasModernIdentity && !explicitConversationId) {
|
|
1854
|
-
throw new Error("modern callbacks require conversation_id as the Turn Store alias");
|
|
1855
|
-
}
|
|
1856
|
-
if (hasModernIdentity && explicitConversationId !== explicitTurnId) {
|
|
1857
|
-
throw new Error("callback conversation_id must equal turn_id for modern callback identities");
|
|
1858
|
-
}
|
|
1859
|
-
if (!explicitConversationId) {
|
|
1860
|
-
throw new Error("callback identity requires session_id and turn_id, or a legacy conversation_id");
|
|
1861
|
-
}
|
|
1862
|
-
const turnId = explicitTurnId ?? explicitConversationId;
|
|
1863
|
-
const conversationId = explicitConversationId;
|
|
1864
|
-
const sessionId = explicitSessionId ?? explicitConversationId;
|
|
1865
|
-
return {
|
|
1866
|
-
conversationId,
|
|
1867
|
-
sessionId,
|
|
1868
|
-
turnId,
|
|
1869
|
-
identityMode
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
function consistentCallbackIdentity(field, sources) {
|
|
1873
|
-
const values = sources.flatMap(({ label, value }) => {
|
|
1874
|
-
if (!isRecord(value) || !Object.hasOwn(value, field)) {
|
|
1875
|
-
return [];
|
|
1876
|
-
}
|
|
1877
|
-
const identity = stringValue(value[field]);
|
|
1878
|
-
if (!identity) {
|
|
1879
|
-
throw new Error(`callback ${label}.${field} must be a non-empty string`);
|
|
1880
|
-
}
|
|
1881
|
-
return [{ label, identity }];
|
|
1882
|
-
});
|
|
1883
|
-
const expected = values[0];
|
|
1884
|
-
for (const candidate of values.slice(1)) {
|
|
1885
|
-
if (candidate.identity !== expected.identity) {
|
|
1886
|
-
throw new Error(`callback ${field} mismatch between ${expected.label} and ${candidate.label}`);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
return expected?.identity;
|
|
1890
|
-
}
|
|
1891
|
-
function callbackSessionKey({ params, conversation, message, messageMetadata }) {
|
|
1892
|
-
const sessionKey = callbackStringField("params.sessionKey", params, "sessionKey");
|
|
1893
|
-
if (!sessionKey) {
|
|
1894
|
-
throw new Error("callback params.sessionKey is required");
|
|
1895
|
-
}
|
|
1896
|
-
const gatewaySession = consistentCallbackTarget("Gateway session", [
|
|
1897
|
-
{
|
|
1898
|
-
label: "conversation.gateway_session",
|
|
1899
|
-
owner: conversation,
|
|
1900
|
-
field: "gateway_session"
|
|
1901
|
-
},
|
|
1902
|
-
{
|
|
1903
|
-
label: "message.gateway_session",
|
|
1904
|
-
owner: message,
|
|
1905
|
-
field: "gateway_session"
|
|
1906
|
-
},
|
|
1907
|
-
{
|
|
1908
|
-
label: "message.metadata.gateway_session",
|
|
1909
|
-
owner: messageMetadata,
|
|
1910
|
-
field: "gateway_session"
|
|
1911
|
-
}
|
|
1912
|
-
]);
|
|
1913
|
-
const openclawSession = consistentCallbackTarget("OpenClaw session", [
|
|
1914
|
-
{
|
|
1915
|
-
label: "params.openclaw_session",
|
|
1916
|
-
owner: params,
|
|
1917
|
-
field: "openclaw_session"
|
|
1918
|
-
},
|
|
1919
|
-
{
|
|
1920
|
-
label: "conversation.openclaw_session",
|
|
1921
|
-
owner: conversation,
|
|
1922
|
-
field: "openclaw_session"
|
|
1923
|
-
},
|
|
1924
|
-
{
|
|
1925
|
-
label: "message.openclaw_session",
|
|
1926
|
-
owner: message,
|
|
1927
|
-
field: "openclaw_session"
|
|
1928
|
-
},
|
|
1929
|
-
{
|
|
1930
|
-
label: "message.metadata.openclaw_session",
|
|
1931
|
-
owner: messageMetadata,
|
|
1932
|
-
field: "openclaw_session"
|
|
1933
|
-
}
|
|
1934
|
-
]);
|
|
1935
|
-
const expectedGatewayTarget = gatewaySession ?? openclawSession;
|
|
1936
|
-
if (expectedGatewayTarget && expectedGatewayTarget !== sessionKey) {
|
|
1937
|
-
throw new Error("callback Gateway session mismatch with params.sessionKey");
|
|
1938
|
-
}
|
|
1939
|
-
return sessionKey;
|
|
1940
|
-
}
|
|
1941
|
-
function consistentCallbackTarget(label, sources) {
|
|
1942
|
-
const values = sources.flatMap((source) => {
|
|
1943
|
-
const value = callbackStringField(source.label, source.owner, source.field);
|
|
1944
|
-
return value ? [{ label: source.label, value }] : [];
|
|
1945
|
-
});
|
|
1946
|
-
const expected = values[0];
|
|
1947
|
-
for (const candidate of values.slice(1)) {
|
|
1948
|
-
if (candidate.value !== expected.value) {
|
|
1949
|
-
throw new Error(`callback ${label} mismatch between ${expected.label} and ${candidate.label}`);
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
return expected?.value;
|
|
1953
|
-
}
|
|
1954
|
-
function callbackStringField(label, owner, field) {
|
|
1955
|
-
if (!isRecord(owner) || !Object.hasOwn(owner, field)) {
|
|
1956
|
-
return undefined;
|
|
1957
|
-
}
|
|
1958
|
-
const value = stringValue(owner[field]);
|
|
1959
|
-
if (!value) {
|
|
1960
|
-
throw new Error(`callback ${label} must be a non-empty string`);
|
|
1961
|
-
}
|
|
1962
|
-
return value;
|
|
1963
|
-
}
|
|
1964
|
-
async function handleCallback(api, params) {
|
|
1965
|
-
if (!isRecord(params)) {
|
|
1966
|
-
throw new Error("callback params must be an object");
|
|
1967
|
-
}
|
|
1968
|
-
const message = isRecord(params.message) ? params.message : undefined;
|
|
1969
|
-
const conversation = isRecord(params.conversation) ? params.conversation : undefined;
|
|
1970
|
-
const messageMetadata = isRecord(message?.metadata) ? message.metadata : undefined;
|
|
1971
|
-
if (!message) {
|
|
1972
|
-
throw new Error("callback params.message is required");
|
|
1973
|
-
}
|
|
1974
|
-
const sessionKey = callbackSessionKey({
|
|
1975
|
-
params,
|
|
1976
|
-
conversation,
|
|
1977
|
-
message,
|
|
1978
|
-
messageMetadata
|
|
1979
|
-
});
|
|
1980
|
-
const { conversationId, sessionId, turnId, identityMode } = callbackIdentity({ params, conversation, message, messageMetadata });
|
|
1981
|
-
const messageId = stringValue(message.id);
|
|
1982
|
-
if (!messageId) {
|
|
1983
|
-
throw new Error("callback message.id is required");
|
|
1984
|
-
}
|
|
1985
|
-
const autoApproval = tryAutoApproveCallback({
|
|
1986
|
-
api,
|
|
1987
|
-
message,
|
|
1988
|
-
conversationId,
|
|
1989
|
-
statePath: stringValue(params.statePath)
|
|
1990
|
-
});
|
|
1991
|
-
if (autoApproval?.handled === true) {
|
|
1992
|
-
return {
|
|
1993
|
-
ok: true,
|
|
1994
|
-
enqueued: false,
|
|
1995
|
-
delivery_required: false,
|
|
1996
|
-
delivery_mode: "none",
|
|
1997
|
-
session_key: sessionKey,
|
|
1998
|
-
conversation_id: conversationId,
|
|
1999
|
-
session_id: sessionId,
|
|
2000
|
-
turn_id: turnId,
|
|
2001
|
-
message_id: messageId,
|
|
2002
|
-
message_type: stringValue(message.type) ?? "unknown",
|
|
2003
|
-
auto_approved: autoApproval.approved === true,
|
|
2004
|
-
approval_already_handled: autoApproval.action === "already_approved",
|
|
2005
|
-
approval: autoApproval
|
|
2006
|
-
};
|
|
2007
|
-
}
|
|
2008
|
-
const formatted = formatCallbackInjection({
|
|
2009
|
-
message,
|
|
2010
|
-
sessionId,
|
|
2011
|
-
turnId,
|
|
2012
|
-
statePath: stringValue(params.statePath)
|
|
2013
|
-
});
|
|
2014
|
-
const dedupeIdentity = identityMode === "legacy"
|
|
2015
|
-
? conversationId
|
|
2016
|
-
: `${sessionId}:${turnId}`;
|
|
2017
|
-
const injection = await api.session.workflow.enqueueNextTurnInjection({
|
|
2018
|
-
sessionKey,
|
|
2019
|
-
text: formatted,
|
|
2020
|
-
idempotencyKey: `agent-knock-knock:${dedupeIdentity}:${messageId}`,
|
|
2021
|
-
placement: "append_context",
|
|
2022
|
-
ttlMs: 24 * 60 * 60 * 1000,
|
|
2023
|
-
metadata: {
|
|
2024
|
-
kind: "agent-knock-knock-callback",
|
|
2025
|
-
conversation_id: conversationId,
|
|
2026
|
-
session_id: sessionId,
|
|
2027
|
-
turn_id: turnId,
|
|
2028
|
-
message_id: messageId,
|
|
2029
|
-
message_type: stringValue(message.type) ?? "unknown",
|
|
2030
|
-
state_path: stringValue(params.statePath),
|
|
2031
|
-
log_path: stringValue(params.logPath)
|
|
2032
|
-
}
|
|
2033
|
-
});
|
|
2034
|
-
const delivery = buildCallbackDeliveryPlan({
|
|
2035
|
-
sessionKey,
|
|
2036
|
-
conversationId,
|
|
2037
|
-
sessionId,
|
|
2038
|
-
turnId,
|
|
2039
|
-
identityMode,
|
|
2040
|
-
messageId,
|
|
2041
|
-
message,
|
|
2042
|
-
formatted
|
|
2043
|
-
});
|
|
2044
|
-
return {
|
|
2045
|
-
ok: true,
|
|
2046
|
-
enqueued: injection?.enqueued ?? true,
|
|
2047
|
-
delivery_required: delivery.required,
|
|
2048
|
-
delivery_mode: delivery?.mode,
|
|
2049
|
-
chat_send: delivery.chat_send,
|
|
2050
|
-
session_send: "session_send" in delivery ? delivery.session_send : undefined,
|
|
2051
|
-
injection_id: injection?.id,
|
|
2052
|
-
session_key: injection?.sessionKey ?? sessionKey,
|
|
2053
|
-
conversation_id: conversationId,
|
|
2054
|
-
session_id: sessionId,
|
|
2055
|
-
turn_id: turnId,
|
|
2056
|
-
message_id: messageId,
|
|
2057
|
-
message_type: stringValue(message.type) ?? "unknown"
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
|
-
function tryAutoApproveCallback({ api, message, conversationId, statePath }) {
|
|
2061
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
2062
|
-
const result = attemptAutoApproval({
|
|
2063
|
-
message,
|
|
2064
|
-
policy: config.autoApprove,
|
|
2065
|
-
statePath,
|
|
2066
|
-
execute: (args) => {
|
|
2067
|
-
return runCli(api, args);
|
|
2068
|
-
}
|
|
2069
|
-
});
|
|
2070
|
-
if (result) {
|
|
2071
|
-
api.logger.info?.(`agent-knock-knock approval policy for ${conversationId ?? "unknown"}: ${result.action} (${result.reason})`);
|
|
2072
|
-
}
|
|
2073
|
-
return result;
|
|
2074
|
-
}
|
|
2075
|
-
function buildCallbackDeliveryPlan({ sessionKey, conversationId, sessionId, turnId, identityMode, messageId, message, formatted }) {
|
|
2076
|
-
const type = stringValue(message.type) ?? "unknown";
|
|
2077
|
-
const shouldWake = message.requires_response === true ||
|
|
2078
|
-
type === "question" ||
|
|
2079
|
-
type === "blocked" ||
|
|
2080
|
-
type === "done" ||
|
|
2081
|
-
type === "error";
|
|
2082
|
-
if (!shouldWake) {
|
|
2083
|
-
return {
|
|
2084
|
-
required: false,
|
|
2085
|
-
mode: "none"
|
|
2086
|
-
};
|
|
2087
|
-
}
|
|
2088
|
-
const dedupeIdentity = identityMode === "legacy"
|
|
2089
|
-
? conversationId
|
|
2090
|
-
: `${sessionId}:${turnId}`;
|
|
2091
|
-
return {
|
|
2092
|
-
required: true,
|
|
2093
|
-
mode: "chat.send",
|
|
2094
|
-
chat_send: {
|
|
2095
|
-
sessionKey,
|
|
2096
|
-
message: [
|
|
2097
|
-
"Continue this OpenClaw product-manager conversation from the Agent Knock Knock callback below.",
|
|
2098
|
-
"Treat the callback as a structured message from the coding agent's managed terminal turn, not as a terminal log, status announcement, or instruction to inspect local state.",
|
|
2099
|
-
"Respond in this conversation as OpenClaw product manager. If the callback is question or blocked, make the product decision and use agent_knock_knock_respond with its exact turn_id. If it is done, summarize the result to the user.",
|
|
2100
|
-
"Do not poll files, processes, sessions, stdout, or stderr. Use only the structured callback payload below.",
|
|
2101
|
-
"",
|
|
2102
|
-
formatted
|
|
2103
|
-
].join("\n"),
|
|
2104
|
-
idempotencyKey: `agent-knock-knock-callback:${dedupeIdentity}:${messageId}`,
|
|
2105
|
-
deliver: true
|
|
2106
|
-
}
|
|
2107
|
-
};
|
|
2108
|
-
}
|
|
2109
|
-
function formatCallbackInjection({ message, sessionId, turnId, statePath }) {
|
|
2110
|
-
const type = stringValue(message.type) ?? "unknown";
|
|
2111
|
-
const body = stringValue(message.body) ?? JSON.stringify(message.body ?? "");
|
|
2112
|
-
const requiresResponse = message.requires_response === true ? "yes" : "no";
|
|
2113
|
-
const round = typeof message.round === "number" ? String(message.round) : "unknown";
|
|
2114
|
-
const stateLine = statePath ? `State: ${statePath}\n` : "";
|
|
2115
|
-
const shortcuts = type === "done"
|
|
2116
|
-
? formatDoneShortcuts(sessionId, turnId)
|
|
2117
|
-
: message.requires_response === true || type === "question" || type === "blocked"
|
|
2118
|
-
? formatRespondShortcut(turnId)
|
|
2119
|
-
: "";
|
|
2120
|
-
return [
|
|
2121
|
-
"[Agent Knock Knock callback]",
|
|
2122
|
-
`Session: ${sessionId}`,
|
|
2123
|
-
`Turn: ${turnId}`,
|
|
2124
|
-
`Message type: ${type}`,
|
|
2125
|
-
`Requires OpenClaw response: ${requiresResponse}`,
|
|
2126
|
-
`Round: ${round}`,
|
|
2127
|
-
stateLine.trimEnd(),
|
|
2128
|
-
"",
|
|
2129
|
-
body,
|
|
2130
|
-
shortcuts
|
|
2131
|
-
].filter((line) => line !== "").join("\n");
|
|
2132
|
-
}
|
|
2133
|
-
function formatDoneShortcuts(sessionId, turnId) {
|
|
2134
|
-
return [
|
|
2135
|
-
"",
|
|
2136
|
-
"[AKK convenience commands]",
|
|
2137
|
-
"When summarizing this result to the user, include these short next-step commands:",
|
|
2138
|
-
"- `AKK list` lists live shared terminals with their current or recent managed turns.",
|
|
2139
|
-
`- Session ${JSON.stringify(sessionId)} remains the context label; refresh \`AKK list\`, then use that terminal row's exact \`available_actions.send\` with \`agent_knock_knock_send\` for later work. Do not assume the returned Session is directly sendable.`,
|
|
2140
|
-
`- Use \`agent_knock_knock_status\` with \`turn_id: ${JSON.stringify(turnId)}\` to inspect this exact turn.`,
|
|
2141
|
-
"- AKK never starts or closes the coding agent or terminal pane."
|
|
2142
|
-
].join("\n");
|
|
2143
|
-
}
|
|
2144
|
-
function formatRespondShortcut(turnId) {
|
|
2145
|
-
return [
|
|
2146
|
-
"",
|
|
2147
|
-
"[AKK response command]",
|
|
2148
|
-
`- Use \`agent_knock_knock_respond\` with \`turn_id: ${JSON.stringify(turnId)}\` and your decision in \`request\`. Do not use ordinary send for this response.`
|
|
2149
|
-
].join("\n");
|
|
2150
|
-
}
|
|
2151
|
-
function pushTurnTarget(args, params) {
|
|
2152
|
-
if (Object.hasOwn(params, "turn_id") && Object.hasOwn(params, "conversation_id")) {
|
|
2153
|
-
throw new Error("turn-target tools accept only one of turn_id or conversation_id");
|
|
2154
|
-
}
|
|
2155
|
-
const turnId = stringValue(params.turn_id);
|
|
2156
|
-
if (turnId) {
|
|
2157
|
-
args.push("--turn", authoritativeManagedId(turnId, "turn_id"));
|
|
2158
|
-
return;
|
|
2159
|
-
}
|
|
2160
|
-
args.push("--conversation", requiredString(params.conversation_id, "turn_id"));
|
|
2161
|
-
}
|
|
2162
|
-
function authoritativeManagedId(value, name) {
|
|
2163
|
-
const id = requiredString(value, name).trim();
|
|
2164
|
-
if (/^(?:only|latest|codex|claude|(?:codex|claude):latest)$/iu.test(id) ||
|
|
2165
|
-
/^@[0-9a-f]+$/iu.test(id) ||
|
|
2166
|
-
/^terminal:/iu.test(id)) {
|
|
2167
|
-
throw new Error(`${name} must be an authoritative managed id, not a discovery selector or terminal id`);
|
|
2168
|
-
}
|
|
2169
|
-
return id;
|
|
2170
|
-
}
|
|
2171
|
-
function assertExclusiveRecoveryFence(params) {
|
|
2172
|
-
const expectedMessageId = stringValue(params.expected_message_id);
|
|
2173
|
-
const expectedTransitionId = stringValue(params.expected_transition_id);
|
|
2174
|
-
const expectedHandoffToken = stringValue(params.expected_handoff_token);
|
|
2175
|
-
const closeFenceCount = [
|
|
2176
|
-
expectedMessageId,
|
|
2177
|
-
expectedTransitionId,
|
|
2178
|
-
expectedHandoffToken
|
|
2179
|
-
].filter(Boolean).length;
|
|
2180
|
-
if (closeFenceCount > 1) {
|
|
2181
|
-
throw new Error("close accepts only one of expected_message_id, expected_transition_id, or expected_handoff_token");
|
|
2182
|
-
}
|
|
2183
|
-
const handoffReason = stringValue(params.reason) ===
|
|
2184
|
-
"superseded_by_human_context_switch";
|
|
2185
|
-
if (expectedHandoffToken) {
|
|
2186
|
-
if (!stringValue(params.turn_id) || stringValue(params.conversation_id)) {
|
|
2187
|
-
throw new Error("expected_handoff_token requires the exact managed turn_id from the advertised handoff_decision action");
|
|
2188
|
-
}
|
|
2189
|
-
if (!handoffReason) {
|
|
2190
|
-
throw new Error("expected_handoff_token requires reason=superseded_by_human_context_switch");
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
else if (handoffReason) {
|
|
2194
|
-
throw new Error("reason=superseded_by_human_context_switch requires expected_handoff_token from the advertised handoff_decision action");
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
function pushOptional(args, flag, value) {
|
|
2198
|
-
if (value !== undefined && value !== "") {
|
|
2199
|
-
args.push(flag, value);
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
function isRecord(value) {
|
|
2203
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2204
|
-
}
|
|
2205
|
-
function stringValue(value) {
|
|
2206
|
-
return typeof value === "string" && value.trim() ? value : undefined;
|
|
2207
|
-
}
|
|
2208
|
-
function numberString(value) {
|
|
2209
|
-
return typeof value === "number" && Number.isFinite(value) ? String(value) : undefined;
|
|
2210
|
-
}
|
|
2211
|
-
function truncateText(value, maxLength) {
|
|
2212
|
-
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
2213
|
-
if (text.length <= maxLength) {
|
|
2214
|
-
return text;
|
|
2215
|
-
}
|
|
2216
|
-
return `${text.slice(0, Math.max(0, maxLength - 1))}...`;
|
|
2217
|
-
}
|
|
2218
|
-
function requiredString(value, name) {
|
|
2219
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
2220
|
-
throw new Error(`${name} is required`);
|
|
2221
|
-
}
|
|
2222
|
-
return value;
|
|
2223
|
-
}
|
|
2224
|
-
function parseJson(text) {
|
|
2225
|
-
try {
|
|
2226
|
-
return JSON.parse(text);
|
|
2227
|
-
}
|
|
2228
|
-
catch (error) {
|
|
2229
|
-
throw new Error(`agent-knock-knock CLI returned invalid JSON: ${error.message}`);
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
function cleanError(text) {
|
|
2233
|
-
return String(text).trim().slice(0, 2000);
|
|
2234
|
-
}
|
|
2235
30
|
//# sourceMappingURL=openclaw-plugin.js.map
|