@scotthuang/agent-knock-knock 0.12.12 → 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 +22 -0
- package/README.md +39 -26
- 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/claude-local-transcript-provider.d.ts +81 -0
- package/dist/src/claude-local-transcript-provider.js +854 -18
- package/dist/src/claude-local-transcript-provider.js.map +1 -1
- package/dist/src/cli-core.js +85 -0
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/deferred-foreground-authority-cli-adapter.js +90 -19
- package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
- package/dist/src/deferred-foreground-transfer.js +3 -4
- package/dist/src/deferred-foreground-transfer.js.map +1 -1
- package/dist/src/openclaw-callback-transport.d.ts +5 -0
- package/dist/src/openclaw-callback-transport.js +1 -1
- package/dist/src/openclaw-callback-transport.js.map +1 -1
- package/dist/src/openclaw-plugin-callback-adapter.js +96 -14
- package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +548 -107
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +27 -4
- package/dist/src/openclaw-plugin-helpers.js +309 -44
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +44 -68
- package/dist/src/openclaw-plugin-schemas.js +63 -91
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-plugin-supervisor.js +48 -10
- package/dist/src/openclaw-plugin-supervisor.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/store.d.ts +1 -0
- package/dist/src/store.js +3 -1
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +19 -1
- package/dist/src/terminal-action-projection.js +23 -0
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.d.ts +5 -0
- package/dist/src/terminal-command-cli-adapter.js +249 -89
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-repository-cli-adapter.js +305 -79
- package/dist/src/terminal-dispatch-repository-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.d.ts +31 -7
- package/dist/src/terminal-list-cli-adapter.js +285 -67
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.d.ts +2 -0
- package/dist/src/terminal-list-renderer.js +116 -41
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-monitor-cli-adapter.js +1 -1
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js +6 -15
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-status-cli-adapter.d.ts +10 -0
- package/dist/src/terminal-status-cli-adapter.js +89 -7
- package/dist/src/terminal-status-cli-adapter.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.d.ts +60 -1
- package/dist/src/terminal-submission-acceptance.js +560 -0
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- 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/docs/quickstart-herdr.md +8 -0
- package/docs/quickstart-tmux.md +33 -6
- package/openclaw.plugin.json +11 -3
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +57 -39
|
@@ -1,5 +1,161 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { recordValue } from "./value-guards.js";
|
|
3
|
+
const MODEL_FACING_PRIVATE_AUTHORITY_PARTS = [
|
|
4
|
+
["expected", "terminal", "token"],
|
|
5
|
+
["expected", "binding", "token"],
|
|
6
|
+
["expected", "handoff", "token"],
|
|
7
|
+
["candidate", "token"],
|
|
8
|
+
["expected", "approval", "fingerprint"],
|
|
9
|
+
["approval", "fingerprint"],
|
|
10
|
+
["expected", "session", "revision"],
|
|
11
|
+
["lifecycle", "binding", "token"],
|
|
12
|
+
["binding", "token"],
|
|
13
|
+
["binding", "id"],
|
|
14
|
+
["binding", "generation"],
|
|
15
|
+
["terminal", "binding", "id"],
|
|
16
|
+
["terminal", "binding", "generation"],
|
|
17
|
+
["selection", "handle"],
|
|
18
|
+
["selection", "snapshot"],
|
|
19
|
+
["selection", "scope"],
|
|
20
|
+
["expected", "callback", "conversation", "id"],
|
|
21
|
+
["expected", "callback", "session", "id"],
|
|
22
|
+
["expected", "callback", "turn", "id"],
|
|
23
|
+
["expected", "callback", "message", "id"],
|
|
24
|
+
["expected", "callback", "openclaw", "session"]
|
|
25
|
+
];
|
|
26
|
+
// The optional separator accepts the three spellings that can be emitted by
|
|
27
|
+
// old CLI/JSON diagnostics: snake_case, kebab-case, and camelCase. These are
|
|
28
|
+
// exact AKK authority names rather than generic words such as "token" or
|
|
29
|
+
// "revision", so ordinary user and agent text remains intact.
|
|
30
|
+
const MODEL_FACING_PRIVATE_AUTHORITY_NAME = `(?:${MODEL_FACING_PRIVATE_AUTHORITY_PARTS
|
|
31
|
+
.map((parts) => parts.join("[_-]?"))
|
|
32
|
+
.join("|")})`;
|
|
33
|
+
const MODEL_FACING_SCALAR = `(?:"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|` +
|
|
34
|
+
"`(?:\\\\.|[^`\\\\])*`|true|false|null|" +
|
|
35
|
+
`-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?|[^\\s,;}\\]]+)`;
|
|
36
|
+
const MODEL_FACING_PRIVATE_AUTHORITY_JSON = new RegExp(`"${MODEL_FACING_PRIVATE_AUTHORITY_NAME}"\\s*:\\s*${MODEL_FACING_SCALAR}`, "giu");
|
|
37
|
+
const MODEL_FACING_PRIVATE_AUTHORITY_CLI = new RegExp(`--${MODEL_FACING_PRIVATE_AUTHORITY_NAME}(?:=|\\s+)${MODEL_FACING_SCALAR}`, "giu");
|
|
38
|
+
const MODEL_FACING_PRIVATE_AUTHORITY_ASSIGNMENT = new RegExp(`(^|[^A-Za-z0-9])${MODEL_FACING_PRIVATE_AUTHORITY_NAME}` +
|
|
39
|
+
`\\s*(?:=|:)\\s*${MODEL_FACING_SCALAR}`, "gimu");
|
|
40
|
+
const MODEL_FACING_PRIVATE_AUTHORITY_LINE = new RegExp(`^\\s*(?:[-*]\\s*)?(?:` +
|
|
41
|
+
`(?:["']?${MODEL_FACING_PRIVATE_AUTHORITY_NAME}["']?\\s*(?:=|:))|` +
|
|
42
|
+
`(?:--${MODEL_FACING_PRIVATE_AUTHORITY_NAME}(?:=|\\s+)))`, "iu");
|
|
43
|
+
const LEGACY_APPROVAL_TAIL_MARKER = "If the user approves, call `agent_knock_knock_approve`";
|
|
44
|
+
const LEGACY_APPROVAL_TERMINAL_INSTRUCTION = "Do not use raw tmux, shell, or manual key presses for this approval. Do not approve without explicit user confirmation.";
|
|
45
|
+
const MODEL_OPAQUE_AUTHORITY_FIELDS = new Set([
|
|
46
|
+
"acceptanceevidence",
|
|
47
|
+
"candidaterollouts",
|
|
48
|
+
"claudetranscriptanchor",
|
|
49
|
+
"codexopenrootrolloutinventory",
|
|
50
|
+
"codexrolloutacceptanceanchor",
|
|
51
|
+
"challenge",
|
|
52
|
+
"etag",
|
|
53
|
+
"expectedapprovalfingerprint",
|
|
54
|
+
"expectedcallbackconversationid",
|
|
55
|
+
"expectedcallbackmessageid",
|
|
56
|
+
"expectedcallbackopenclawsession",
|
|
57
|
+
"expectedcallbacksessionid",
|
|
58
|
+
"expectedcallbackturnid",
|
|
59
|
+
"generationid",
|
|
60
|
+
"livenativethreadid",
|
|
61
|
+
"nativesessiontakeover",
|
|
62
|
+
"nonce",
|
|
63
|
+
"proof",
|
|
64
|
+
"selectionhandle",
|
|
65
|
+
"selectionsnapshot",
|
|
66
|
+
"selectionscope",
|
|
67
|
+
"snapshotid"
|
|
68
|
+
]);
|
|
69
|
+
export function normalizeAkkModelFacingFieldName(key) {
|
|
70
|
+
return key.toLowerCase().replace(/[^a-z0-9]/gu, "");
|
|
71
|
+
}
|
|
72
|
+
export function isAkkModelFacingPrivateAuthorityField(key) {
|
|
73
|
+
const compact = normalizeAkkModelFacingFieldName(key);
|
|
74
|
+
return compact.endsWith("token") ||
|
|
75
|
+
compact.endsWith("fingerprint") ||
|
|
76
|
+
compact.endsWith("hash") ||
|
|
77
|
+
compact.endsWith("sha256") ||
|
|
78
|
+
compact.endsWith("digest") ||
|
|
79
|
+
compact.endsWith("revision") ||
|
|
80
|
+
compact.endsWith("revisions") ||
|
|
81
|
+
compact.endsWith("bindingid") ||
|
|
82
|
+
compact.endsWith("bindingids") ||
|
|
83
|
+
compact.endsWith("bindinggeneration") ||
|
|
84
|
+
compact.endsWith("bindinggenerations") ||
|
|
85
|
+
compact === "generation" ||
|
|
86
|
+
MODEL_OPAQUE_AUTHORITY_FIELDS.has(compact);
|
|
87
|
+
}
|
|
88
|
+
export function isAkkModelFacingDiagnosticField(key) {
|
|
89
|
+
if (!key)
|
|
90
|
+
return false;
|
|
91
|
+
const separated = key
|
|
92
|
+
.replace(/([a-z0-9])([A-Z])/gu, "$1_$2")
|
|
93
|
+
.toLowerCase();
|
|
94
|
+
return /(?:^|[_-])(?:reasons?|errors?|warnings?|diagnostics?)$/u.test(separated) || /(?:^|[_-])(?:error|warning|diagnostic)_message$/u.test(separated);
|
|
95
|
+
}
|
|
96
|
+
function sanitizeAkkModelFacingEmbeddedAuthorityText(value) {
|
|
97
|
+
return value
|
|
98
|
+
.replace(MODEL_FACING_PRIVATE_AUTHORITY_JSON, '"internal_authority":"[retained by AKK]"')
|
|
99
|
+
.replace(MODEL_FACING_PRIVATE_AUTHORITY_CLI, "[AKK internal authority omitted]")
|
|
100
|
+
.replace(MODEL_FACING_PRIVATE_AUTHORITY_ASSIGNMENT, (_match, prefix) => `${prefix}[AKK internal authority omitted]`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Remove only AKK-generated pre-v18 authority instructions from callback/event
|
|
104
|
+
* text. Exact authority-looking text outside one of these fixed machine-owned
|
|
105
|
+
* forms is business content and must remain byte-for-byte visible.
|
|
106
|
+
*/
|
|
107
|
+
export function sanitizeAkkModelFacingLegacyAuthorityInstructionText(value) {
|
|
108
|
+
const reviewOnly = stripAkkLegacyApprovalInstructionTail(value);
|
|
109
|
+
let machineAuthorityBlock = false;
|
|
110
|
+
return reviewOnly.split("\n").map((line) => {
|
|
111
|
+
const text = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
112
|
+
const carriageReturn = line.endsWith("\r") ? "\r" : "";
|
|
113
|
+
if (/^\s*(?:\[?AKK\]?\s+)?(?:approval|internal) authority\s*:?\s*$/iu
|
|
114
|
+
.test(text)) {
|
|
115
|
+
machineAuthorityBlock = true;
|
|
116
|
+
return line;
|
|
117
|
+
}
|
|
118
|
+
if (text.trim().length === 0) {
|
|
119
|
+
machineAuthorityBlock = false;
|
|
120
|
+
return line;
|
|
121
|
+
}
|
|
122
|
+
const fixedCommand = /^\s*(?:equivalent (?:user )?command|AKK (?:approval )?command)\s*:/iu
|
|
123
|
+
.test(text) && containsEmbeddedPrivateAuthority(text);
|
|
124
|
+
const blockAuthority = machineAuthorityBlock &&
|
|
125
|
+
containsEmbeddedPrivateAuthority(text);
|
|
126
|
+
if (fixedCommand || blockAuthority) {
|
|
127
|
+
return `${text.match(/^\s*/u)?.[0] ?? ""}` +
|
|
128
|
+
`[AKK internal authority omitted]${carriageReturn}`;
|
|
129
|
+
}
|
|
130
|
+
if (machineAuthorityBlock)
|
|
131
|
+
machineAuthorityBlock = false;
|
|
132
|
+
return line;
|
|
133
|
+
}).join("\n");
|
|
134
|
+
}
|
|
135
|
+
/** Remove only the exact legacy approval instruction appended by AKK. */
|
|
136
|
+
export function stripAkkLegacyApprovalInstructionTail(value) {
|
|
137
|
+
const markerIndex = value.lastIndexOf(LEGACY_APPROVAL_TAIL_MARKER);
|
|
138
|
+
const markerSuffix = markerIndex === -1 ? "" : value.slice(markerIndex);
|
|
139
|
+
const fixedTail = markerIndex !== -1 && (markerSuffix.trimEnd().endsWith(LEGACY_APPROVAL_TERMINAL_INSTRUCTION) ||
|
|
140
|
+
markerSuffix.split(/\r?\n/u).some(isLegacyMachineAuthorityLine));
|
|
141
|
+
return fixedTail ? value.slice(0, markerIndex) : value;
|
|
142
|
+
}
|
|
143
|
+
function isLegacyMachineAuthorityLine(line) {
|
|
144
|
+
return containsEmbeddedPrivateAuthority(line) && (MODEL_FACING_PRIVATE_AUTHORITY_LINE.test(line) ||
|
|
145
|
+
/^\s*(?:equivalent (?:user )?command|AKK (?:approval )?command)\s*:/iu
|
|
146
|
+
.test(line));
|
|
147
|
+
}
|
|
148
|
+
function containsEmbeddedPrivateAuthority(value) {
|
|
149
|
+
return sanitizeAkkModelFacingEmbeddedAuthorityText(value) !== value;
|
|
150
|
+
}
|
|
151
|
+
export function sanitizeAkkModelFacingDiagnosticText(message) {
|
|
152
|
+
const sanitized = sanitizeAkkModelFacingEmbeddedAuthorityText(message);
|
|
153
|
+
if (sanitized !== message ||
|
|
154
|
+
/(?:\bexpected\s+(?:session\s+)?revision\b|\bactual\s+(?:session\s+)?revision\b|\b(?:terminal|binding|handoff|candidate)\s+token\b|\bapproval\s+fingerprint\b|\bbinding\s+(?:id|generation)\b|\bcompare-and-swap\b|\bCAS\b)/iu.test(message)) {
|
|
155
|
+
return "AKK's private authority changed or could not be verified. Refresh AKK list/status and retry only the currently advertised semantic action.";
|
|
156
|
+
}
|
|
157
|
+
return sanitized;
|
|
158
|
+
}
|
|
3
159
|
export const AKK_CALLBACK_METHOD = "agent-knock-knock.callback";
|
|
4
160
|
export function parseAkkCommand(args) {
|
|
5
161
|
const input = String(args ?? "").trim();
|
|
@@ -33,6 +189,23 @@ function parseAkkLifecycleCommand(action, rest) {
|
|
|
33
189
|
}
|
|
34
190
|
return { action: "doctor" };
|
|
35
191
|
}
|
|
192
|
+
if (action === "watch") {
|
|
193
|
+
const usage = "Usage: /akk watch <exact-terminal-id>";
|
|
194
|
+
const { token: terminalId, rest: extra } = takeRequiredToken(rest, usage);
|
|
195
|
+
assertExactTerminalId(terminalId, usage);
|
|
196
|
+
if (extra.trim()) {
|
|
197
|
+
throw new Error(usage);
|
|
198
|
+
}
|
|
199
|
+
return { action: "watch", terminalId };
|
|
200
|
+
}
|
|
201
|
+
if (action === "unwatch") {
|
|
202
|
+
const usage = "Usage: /akk unwatch <watch-id>";
|
|
203
|
+
const { token: watchId, rest: extra } = takeRequiredToken(rest, usage);
|
|
204
|
+
if (extra.trim() || !isTerminalWatchId(watchId)) {
|
|
205
|
+
throw new Error(usage);
|
|
206
|
+
}
|
|
207
|
+
return { action: "unwatch", watchId };
|
|
208
|
+
}
|
|
36
209
|
if (action === "threads" || action === "list-resumable-threads") {
|
|
37
210
|
const usage = "Usage: /akk threads <exact-terminal-id>";
|
|
38
211
|
const { token: terminalId, rest: extra } = takeRequiredToken(rest, usage);
|
|
@@ -53,7 +226,7 @@ function parseAkkLifecycleCommand(action, rest) {
|
|
|
53
226
|
}
|
|
54
227
|
if (action === "resume-thread") {
|
|
55
228
|
const usage = "Usage: /akk resume-thread <exact-terminal-id> " +
|
|
56
|
-
"[native-thread-uuid|previous|刚才那个|number|@short-id
|
|
229
|
+
"[native-thread-uuid|previous|刚才那个|number|@short-id]";
|
|
57
230
|
const { token: terminalId, rest: nativeInput } = takeRequiredToken(rest, usage);
|
|
58
231
|
assertExactTerminalId(terminalId, usage);
|
|
59
232
|
const { token: selectionInput, rest: extra } = takeToken(nativeInput);
|
|
@@ -77,9 +250,11 @@ function parseAkkTurnCommand(action, rest) {
|
|
|
77
250
|
if (action === "status" || action === "show") {
|
|
78
251
|
const { token: turnId, rest: extra } = takeToken(rest);
|
|
79
252
|
if (extra.trim()) {
|
|
80
|
-
throw new Error("Usage: /akk status [turn-selector]");
|
|
253
|
+
throw new Error("Usage: /akk status [turn-selector|terminal-watch-id]");
|
|
81
254
|
}
|
|
82
|
-
return
|
|
255
|
+
return turnId && isTerminalWatchId(turnId)
|
|
256
|
+
? { action: "status", watchId: turnId }
|
|
257
|
+
: { action: "status", turnId: turnId || undefined };
|
|
83
258
|
}
|
|
84
259
|
if (action === "describe" || action === "summary" || action === "about") {
|
|
85
260
|
throw new Error("AKK describe was removed; use /akk status [turn-selector]");
|
|
@@ -99,15 +274,13 @@ function parseAkkTurnCommand(action, rest) {
|
|
|
99
274
|
throw new Error("AKK reply syntax changed; use /akk respond <turn-selector>: <answer>");
|
|
100
275
|
}
|
|
101
276
|
if (action === "approve") {
|
|
102
|
-
const { token: turnId, rest: approvalInput } = takeRequiredToken(rest, "Usage: /akk approve <turn-selector>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
throw new Error("Usage: /akk approve <turn-selector> --expected-approval-fingerprint <fingerprint>");
|
|
277
|
+
const { token: turnId, rest: approvalInput } = takeRequiredToken(rest, "Usage: /akk approve <turn-selector>");
|
|
278
|
+
if (approvalInput.trim()) {
|
|
279
|
+
throw new Error("Usage: /akk approve <turn-selector>");
|
|
106
280
|
}
|
|
107
281
|
return {
|
|
108
282
|
action: "approve",
|
|
109
|
-
turnId
|
|
110
|
-
expectedApprovalFingerprint: approval[1]
|
|
283
|
+
turnId
|
|
111
284
|
};
|
|
112
285
|
}
|
|
113
286
|
if (action === "cancel" || action === "stop") {
|
|
@@ -173,21 +346,26 @@ export function akkUsageText() {
|
|
|
173
346
|
"/akk claude: <request>",
|
|
174
347
|
"/akk <session-selector>: <message>",
|
|
175
348
|
"/akk list",
|
|
349
|
+
"/akk watch <exact-terminal-id>",
|
|
350
|
+
"/akk unwatch <watch-id>",
|
|
176
351
|
"/akk threads <exact-terminal-id>",
|
|
177
352
|
"/akk new-thread <exact-terminal-id>",
|
|
178
353
|
"/akk clear-thread <exact-terminal-id>",
|
|
179
|
-
"/akk resume-thread <exact-terminal-id> [uuid|previous|number|@short-id
|
|
354
|
+
"/akk resume-thread <exact-terminal-id> [uuid|previous|number|@short-id]",
|
|
180
355
|
"/akk doctor",
|
|
181
|
-
"/akk status [turn-selector]",
|
|
356
|
+
"/akk status [turn-selector|terminal-watch-id]",
|
|
182
357
|
"/akk respond <turn-selector>: <answer>",
|
|
183
|
-
"/akk approve <turn-selector>
|
|
358
|
+
"/akk approve <turn-selector>",
|
|
184
359
|
"/akk cancel <turn-selector>"
|
|
185
360
|
].join("\n");
|
|
186
361
|
}
|
|
187
362
|
export function formatAkkListCommandResult(result) {
|
|
188
363
|
const terminals = arrayValue(result.terminals);
|
|
364
|
+
const terminalWatches = arrayValue(result.terminal_watches);
|
|
189
365
|
const unavailableManagedTurns = arrayValue(result.unavailable_managed_turns);
|
|
190
|
-
if (terminals.length === 0 &&
|
|
366
|
+
if (terminals.length === 0 &&
|
|
367
|
+
terminalWatches.length === 0 &&
|
|
368
|
+
unavailableManagedTurns.length === 0) {
|
|
191
369
|
return "AKK found no live terminals or unavailable managed turns.";
|
|
192
370
|
}
|
|
193
371
|
const terminalLines = terminals.slice(0, 20).flatMap((terminal) => {
|
|
@@ -207,11 +385,15 @@ export function formatAkkListCommandResult(result) {
|
|
|
207
385
|
"new_thread",
|
|
208
386
|
"resume_thread"
|
|
209
387
|
].some((name) => Object.hasOwn(availableActions, name));
|
|
388
|
+
const hasWatchAction = Object.hasOwn(availableActions, "watch");
|
|
210
389
|
return [
|
|
211
390
|
`- ${formatTerminalLine(terminal)}`,
|
|
212
391
|
...(hasLifecycleAction && terminalId
|
|
213
392
|
? [` lifecycle terminal_id: ${terminalId}`]
|
|
214
393
|
: []),
|
|
394
|
+
...(hasWatchAction && terminalId
|
|
395
|
+
? [` AKK Watch available: /akk watch ${terminalId} — monitor this human-started task and receive attention/completion callbacks without polling.`]
|
|
396
|
+
: []),
|
|
215
397
|
...(sessionId || sessionShortRef
|
|
216
398
|
? [` AKK session: ${sessionShortRef ?? sessionId}`]
|
|
217
399
|
: []),
|
|
@@ -246,11 +428,21 @@ export function formatAkkListCommandResult(result) {
|
|
|
246
428
|
...(recovery ? [` recovery: ${recovery}`] : [])
|
|
247
429
|
];
|
|
248
430
|
});
|
|
431
|
+
const watchLines = terminalWatches.slice(0, 20).flatMap((watch) => [
|
|
432
|
+
`- ${formatTerminalWatchLine(watch)}`,
|
|
433
|
+
...formatAvailableActions(" actions", watch)
|
|
434
|
+
]);
|
|
435
|
+
const heading = terminalWatches.length > 0
|
|
436
|
+
? `AKK terminals (${terminals.length} live, ${terminalWatches.length} terminal watches, ${unavailableManagedTurns.length} unavailable managed turns):`
|
|
437
|
+
: `AKK terminals (${terminals.length} live, ${unavailableManagedTurns.length} unavailable managed turns):`;
|
|
249
438
|
return [
|
|
250
|
-
|
|
439
|
+
heading,
|
|
251
440
|
...(terminalLines.length > 0
|
|
252
441
|
? ["live terminals:", ...terminalLines]
|
|
253
442
|
: []),
|
|
443
|
+
...(watchLines.length > 0
|
|
444
|
+
? ["terminal watches:", ...watchLines]
|
|
445
|
+
: []),
|
|
254
446
|
...(unavailableManagedTurns.length > 0
|
|
255
447
|
? [
|
|
256
448
|
"unavailable managed turns:",
|
|
@@ -262,6 +454,63 @@ export function formatAkkListCommandResult(result) {
|
|
|
262
454
|
: [])
|
|
263
455
|
].join("\n");
|
|
264
456
|
}
|
|
457
|
+
export function formatAkkWatchCommandResult(result) {
|
|
458
|
+
const watch = terminalWatchRecord(result);
|
|
459
|
+
return [
|
|
460
|
+
"AKK Terminal Watch started:",
|
|
461
|
+
`watch: ${nonEmptyString(watch.watch_id) ?? "unknown"}`,
|
|
462
|
+
`terminal: ${nonEmptyString(watch.terminal_id) ?? "unknown"}`,
|
|
463
|
+
`agent: ${nonEmptyString(watch.agent) ?? "unknown"}`,
|
|
464
|
+
`status: ${nonEmptyString(watch.status) ?? "watching"}`,
|
|
465
|
+
"AKK did not send or adopt this task; it is observing work the human started in the terminal."
|
|
466
|
+
].join("\n");
|
|
467
|
+
}
|
|
468
|
+
export function formatAkkUnwatchCommandResult(result) {
|
|
469
|
+
const watch = terminalWatchRecord(result);
|
|
470
|
+
return [
|
|
471
|
+
"AKK Terminal Watch stopped:",
|
|
472
|
+
`watch: ${nonEmptyString(watch.watch_id) ?? "unknown"}`,
|
|
473
|
+
`status: ${nonEmptyString(watch.status) ?? "cancelled"}`,
|
|
474
|
+
"The terminal and its task were not interrupted."
|
|
475
|
+
].join("\n");
|
|
476
|
+
}
|
|
477
|
+
export function formatAkkWatchStatusCommandResult(result) {
|
|
478
|
+
const watch = terminalWatchRecord(result);
|
|
479
|
+
const settlement = recordValue(watch.settlement) ?? {};
|
|
480
|
+
const reason = nonEmptyString(settlement.reason_code) ??
|
|
481
|
+
nonEmptyString(watch.reason);
|
|
482
|
+
const completionText = nonEmptyString(settlement.completion_text);
|
|
483
|
+
return [
|
|
484
|
+
"AKK Terminal Watch status:",
|
|
485
|
+
`watch: ${nonEmptyString(watch.watch_id) ?? "unknown"}`,
|
|
486
|
+
`terminal: ${nonEmptyString(watch.terminal_id) ?? "unknown"}`,
|
|
487
|
+
`agent: ${nonEmptyString(watch.agent) ?? "unknown"}`,
|
|
488
|
+
`status: ${nonEmptyString(watch.status) ?? "unknown"}`,
|
|
489
|
+
...(reason
|
|
490
|
+
? [`reason: ${reason}`]
|
|
491
|
+
: []),
|
|
492
|
+
...(completionText
|
|
493
|
+
? [`completion: ${truncateText(completionText, 500)}`]
|
|
494
|
+
: []),
|
|
495
|
+
"This is observed external work; AKK did not send or adopt the task."
|
|
496
|
+
].join("\n");
|
|
497
|
+
}
|
|
498
|
+
export function formatAkkTerminalWatchHint(result) {
|
|
499
|
+
const hint = recordValue(result.terminal_watch_hint);
|
|
500
|
+
if (hint?.kind !== "terminal_watch_discovery" ||
|
|
501
|
+
hint.available_action_required !== true) {
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
const command = nonEmptyString(hint.command);
|
|
505
|
+
const instruction = nonEmptyString(hint.instruction);
|
|
506
|
+
if (!command || !instruction) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
return [
|
|
510
|
+
`AKK Watch available: ${command}`,
|
|
511
|
+
`next: ${instruction}`
|
|
512
|
+
];
|
|
513
|
+
}
|
|
265
514
|
export function formatAkkRespondCommandResult(result) {
|
|
266
515
|
const conversation = recordValue(result.conversation) ?? {};
|
|
267
516
|
const compatibilityId = nonEmptyString(conversation.conversation_id) ??
|
|
@@ -357,7 +606,6 @@ export function formatAkkThreadsCommandResult(result) {
|
|
|
357
606
|
const nativeThreadId = nonEmptyString(thread.native_thread_id) ?? "unknown";
|
|
358
607
|
const selectionNumber = finiteNumber(thread.selection_number);
|
|
359
608
|
const shortId = nonEmptyString(thread.short_id);
|
|
360
|
-
const selectionHandle = nonEmptyString(thread.selection_handle);
|
|
361
609
|
const status = thread.resumable === true
|
|
362
610
|
? "resumable"
|
|
363
611
|
: nonEmptyString(thread.unavailable_reason) ?? "unavailable";
|
|
@@ -365,11 +613,10 @@ export function formatAkkThreadsCommandResult(result) {
|
|
|
365
613
|
nonEmptyString(thread.title),
|
|
366
614
|
nonEmptyString(thread.preview)
|
|
367
615
|
].filter((value) => Boolean(value)).join(" · ");
|
|
368
|
-
const selector = selectionNumber !== undefined || shortId
|
|
616
|
+
const selector = selectionNumber !== undefined || shortId
|
|
369
617
|
? [
|
|
370
618
|
selectionNumber !== undefined ? `${selectionNumber}.` : undefined,
|
|
371
|
-
shortId
|
|
372
|
-
selectionHandle ? `[${selectionHandle}]` : undefined
|
|
619
|
+
shortId
|
|
373
620
|
].filter(Boolean).join(" ")
|
|
374
621
|
: `- ${nativeThreadId}`;
|
|
375
622
|
const previousMarker = nativeThreadId === previousNativeThreadId
|
|
@@ -398,7 +645,7 @@ export function formatAkkThreadsCommandResult(result) {
|
|
|
398
645
|
...(resumableCount > 0
|
|
399
646
|
? [
|
|
400
647
|
`next: /akk resume-thread ${terminalId} <native-thread-uuid>`,
|
|
401
|
-
`shortcuts: /akk resume-thread ${terminalId} <number|@short-id
|
|
648
|
+
`shortcuts: /akk resume-thread ${terminalId} <number|@short-id>`,
|
|
402
649
|
...(previousNativeThreadId
|
|
403
650
|
? [`previous: /akk resume-thread ${terminalId} previous`]
|
|
404
651
|
: []),
|
|
@@ -422,7 +669,9 @@ export function formatAkkThreadTransitionCommandResult(result) {
|
|
|
422
669
|
? [`transition: ${nonEmptyString(result.transition_id)}`]
|
|
423
670
|
: []),
|
|
424
671
|
...(nonEmptyString(result.reason)
|
|
425
|
-
? [
|
|
672
|
+
? [
|
|
673
|
+
`reason: ${sanitizeAkkModelFacingDiagnosticText(nonEmptyString(result.reason) ?? "")}`
|
|
674
|
+
]
|
|
426
675
|
: []),
|
|
427
676
|
"No AKK Turn was created.",
|
|
428
677
|
"Next: do not retry automatically; refresh /akk list and use only its exact lifecycle recovery action."
|
|
@@ -448,9 +697,6 @@ export function formatAkkThreadTransitionCommandResult(result) {
|
|
|
448
697
|
]
|
|
449
698
|
: []),
|
|
450
699
|
`native thread: ${nonEmptyString(result.native_thread_id) ?? "unknown"}`,
|
|
451
|
-
...(finiteNumber(result.binding_generation) !== undefined
|
|
452
|
-
? [`binding generation: ${finiteNumber(result.binding_generation)}`]
|
|
453
|
-
: []),
|
|
454
700
|
"No AKK Turn was created. The next ordinary send creates the first Turn in this native context."
|
|
455
701
|
].join("\n");
|
|
456
702
|
}
|
|
@@ -472,6 +718,20 @@ export function buildAkkCommandCliArgs(command, config, context = {}) {
|
|
|
472
718
|
switch (command.action) {
|
|
473
719
|
case "list":
|
|
474
720
|
return withOptionalArgs(["list", "--reconcile"], ["--store-dir", storeDir], ["--idle-timeout-minutes", idleTimeoutMinutes]);
|
|
721
|
+
case "watch": {
|
|
722
|
+
const openclawSession = nonEmptyString(context.sessionKey) ??
|
|
723
|
+
"agent:main:main";
|
|
724
|
+
return withOptionalArgs([
|
|
725
|
+
"watch-terminal",
|
|
726
|
+
"--terminal",
|
|
727
|
+
command.terminalId
|
|
728
|
+
], ["--store-dir", storeDir], [
|
|
729
|
+
"--hard-timeout-minutes",
|
|
730
|
+
finiteNumberString(config.agentHardTimeoutMinutes)
|
|
731
|
+
], ["--openclaw-session", openclawSession], ["--openclaw-bin", nonEmptyString(config.openclawBin)]);
|
|
732
|
+
}
|
|
733
|
+
case "unwatch":
|
|
734
|
+
return withOptionalArgs(["unwatch-terminal", "--watch", command.watchId], ["--store-dir", storeDir]);
|
|
475
735
|
case "list-resumable-threads":
|
|
476
736
|
return withOptionalArgs([
|
|
477
737
|
"list-resumable-threads",
|
|
@@ -520,17 +780,6 @@ export function buildAkkCommandCliArgs(command, config, context = {}) {
|
|
|
520
780
|
requiredSelectionScope(context.selectionScope)
|
|
521
781
|
], ["--store-dir", storeDir], ["--codex-home", codexHome]);
|
|
522
782
|
}
|
|
523
|
-
if (command.selection.kind === "snapshot-handle") {
|
|
524
|
-
return withOptionalArgs([
|
|
525
|
-
"resume-thread",
|
|
526
|
-
"--terminal",
|
|
527
|
-
command.terminalId,
|
|
528
|
-
"--selection-handle",
|
|
529
|
-
command.selection.selectionHandle,
|
|
530
|
-
"--selection-scope",
|
|
531
|
-
requiredSelectionScope(context.selectionScope)
|
|
532
|
-
], ["--store-dir", storeDir], ["--codex-home", codexHome]);
|
|
533
|
-
}
|
|
534
783
|
return withOptionalArgs([
|
|
535
784
|
"resume-thread",
|
|
536
785
|
"--terminal",
|
|
@@ -543,6 +792,9 @@ export function buildAkkCommandCliArgs(command, config, context = {}) {
|
|
|
543
792
|
requiredCandidateToken(context.candidateToken)
|
|
544
793
|
], ["--store-dir", storeDir], ["--codex-home", codexHome]);
|
|
545
794
|
case "status":
|
|
795
|
+
if (command.watchId) {
|
|
796
|
+
return withOptionalArgs(["watch-status", "--watch", command.watchId], ["--store-dir", storeDir]);
|
|
797
|
+
}
|
|
546
798
|
return withOptionalArgs([
|
|
547
799
|
"status",
|
|
548
800
|
"--reconcile",
|
|
@@ -571,13 +823,9 @@ export function buildAkkCommandCliArgs(command, config, context = {}) {
|
|
|
571
823
|
command.message
|
|
572
824
|
], ["--store-dir", storeDir]);
|
|
573
825
|
case "approve":
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
command.turnId,
|
|
578
|
-
"--expected-approval-fingerprint",
|
|
579
|
-
command.expectedApprovalFingerprint
|
|
580
|
-
], ["--store-dir", storeDir]);
|
|
826
|
+
// The OpenClaw command adapter resolves the current private approval
|
|
827
|
+
// authority immediately before invoking the CLI.
|
|
828
|
+
return undefined;
|
|
581
829
|
case "renew":
|
|
582
830
|
return withOptionalArgs(["renew", "--turn", command.turnId], [
|
|
583
831
|
"--minutes",
|
|
@@ -598,6 +846,9 @@ export function buildAkkCommandCliArgs(command, config, context = {}) {
|
|
|
598
846
|
], ["--expected-message-id", command.expectedMessageId], ["--expected-transition-id", command.expectedTransitionId], ["--store-dir", storeDir]);
|
|
599
847
|
}
|
|
600
848
|
}
|
|
849
|
+
function isTerminalWatchId(value) {
|
|
850
|
+
return /^terminal-watch-[A-Za-z0-9._:-]+$/u.test(value);
|
|
851
|
+
}
|
|
601
852
|
function parseSelectorMessage(input) {
|
|
602
853
|
const match = /^((?:(?:codex|claude):latest|only|latest|codex|claude|@[0-9a-f]+))\s*:\s*([\s\S]*)$/iu.exec(input);
|
|
603
854
|
const message = match?.[2]?.trim();
|
|
@@ -646,9 +897,6 @@ function parseAkkResumeSelection(value, usage) {
|
|
|
646
897
|
if (/^@[a-f0-9]{8,32}$/u.test(normalized)) {
|
|
647
898
|
return { kind: "short-id", shortId: normalized };
|
|
648
899
|
}
|
|
649
|
-
if (/^rs_[A-Za-z0-9_-]{22}:[1-9][0-9]*$/u.test(value)) {
|
|
650
|
-
return { kind: "snapshot-handle", selectionHandle: value };
|
|
651
|
-
}
|
|
652
900
|
throw new Error(`${usage}; use a complete UUID or a selection exactly returned by /akk threads`);
|
|
653
901
|
}
|
|
654
902
|
function requiredExpectedBindingToken(value) {
|
|
@@ -730,6 +978,11 @@ function arrayValue(value) {
|
|
|
730
978
|
? value.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item))
|
|
731
979
|
: [];
|
|
732
980
|
}
|
|
981
|
+
function terminalWatchRecord(result) {
|
|
982
|
+
return recordValue(result.watch) ??
|
|
983
|
+
recordValue(result.terminal_watch) ??
|
|
984
|
+
result;
|
|
985
|
+
}
|
|
733
986
|
function finiteNumber(value) {
|
|
734
987
|
return typeof value === "number" && Number.isFinite(value)
|
|
735
988
|
? value
|
|
@@ -772,6 +1025,16 @@ function formatManagedTurnLine(turn) {
|
|
|
772
1025
|
truncateText(context, 90)
|
|
773
1026
|
].filter(Boolean).join(" | ");
|
|
774
1027
|
}
|
|
1028
|
+
function formatTerminalWatchLine(watch) {
|
|
1029
|
+
return [
|
|
1030
|
+
nonEmptyString(watch.short_ref) ??
|
|
1031
|
+
nonEmptyString(watch.watch_id) ??
|
|
1032
|
+
"unknown",
|
|
1033
|
+
nonEmptyString(watch.agent) ?? "agent",
|
|
1034
|
+
nonEmptyString(watch.status) ?? "unknown",
|
|
1035
|
+
nonEmptyString(watch.terminal_id)
|
|
1036
|
+
].filter(Boolean).join(" | ");
|
|
1037
|
+
}
|
|
775
1038
|
function formatAvailableActions(label, resource) {
|
|
776
1039
|
const actions = recordValue(resource.available_actions);
|
|
777
1040
|
if (!actions) {
|
|
@@ -779,6 +1042,8 @@ function formatAvailableActions(label, resource) {
|
|
|
779
1042
|
}
|
|
780
1043
|
const displayedActions = new Set([
|
|
781
1044
|
"send",
|
|
1045
|
+
"watch",
|
|
1046
|
+
"unwatch",
|
|
782
1047
|
"respond",
|
|
783
1048
|
"status",
|
|
784
1049
|
"approve",
|