@scotthuang/agent-knock-knock 0.3.2 → 0.5.0
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 +17 -565
- package/README.md +100 -134
- package/dist/src/agent-session-provider.d.ts +1 -2
- package/dist/src/approval-policy.d.ts +1 -1
- package/dist/src/approval-policy.js +1 -1
- package/dist/src/approval-policy.js.map +1 -1
- package/dist/src/claude-terminal-agent-adapter.d.ts +2 -17
- package/dist/src/claude-terminal-agent-adapter.js +4 -489
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli.js +1736 -3893
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-local-session-provider.d.ts +1 -2
- package/dist/src/codex-local-session-provider.js +1 -9
- package/dist/src/codex-local-session-provider.js.map +1 -1
- package/dist/src/codex-session-provider.d.ts +1 -8
- package/dist/src/codex-session-provider.js +0 -56
- package/dist/src/codex-session-provider.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +1 -1
- package/dist/src/doctor-capabilities.d.ts +5 -13
- package/dist/src/doctor-capabilities.js +15 -60
- package/dist/src/doctor-capabilities.js.map +1 -1
- package/dist/src/executors.d.ts +5 -51
- package/dist/src/executors.js +4 -68
- package/dist/src/executors.js.map +1 -1
- package/dist/src/openclaw-doctor.d.ts +0 -1
- package/dist/src/openclaw-doctor.js +0 -7
- package/dist/src/openclaw-doctor.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +5 -13
- package/dist/src/openclaw-plugin-helpers.js +91 -82
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin.js +272 -481
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/protocol.d.ts +1 -5
- package/dist/src/protocol.js +1 -1
- package/dist/src/protocol.js.map +1 -1
- package/dist/src/session-selector.js +1 -1
- package/dist/src/session-selector.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +3 -17
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +3 -3
- package/dist/src/terminal-agent-bridge.js +0 -75
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/transcript.js +2 -3
- package/dist/src/transcript.js.map +1 -1
- package/docs/quickstart-tmux.md +15 -11
- package/openclaw.plugin.json +4 -100
- package/package.json +7 -15
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +82 -149
- package/dist/src/acpx-output.d.ts +0 -18
- package/dist/src/acpx-output.js +0 -223
- package/dist/src/acpx-output.js.map +0 -1
- package/dist/src/bootstrap.d.ts +0 -8
- package/dist/src/bootstrap.js +0 -45
- package/dist/src/bootstrap.js.map +0 -1
- package/dist/src/claude-hook-installer.d.ts +0 -65
- package/dist/src/claude-hook-installer.js +0 -410
- package/dist/src/claude-hook-installer.js.map +0 -1
- package/dist/src/claude-hook-protocol.d.ts +0 -89
- package/dist/src/claude-hook-protocol.js +0 -243
- package/dist/src/claude-hook-protocol.js.map +0 -1
- package/dist/src/claude-hook-store.d.ts +0 -247
- package/dist/src/claude-hook-store.js +0 -1075
- package/dist/src/claude-hook-store.js.map +0 -1
- package/dist/src/session-takeover-planner.d.ts +0 -37
- package/dist/src/session-takeover-planner.js +0 -57
- package/dist/src/session-takeover-planner.js.map +0 -1
- package/docs/quickstart-managed-acpx.md +0 -43
- package/scripts/smoke-acpx.js +0 -139
- package/scripts/smoke-tmux.js +0 -140
|
@@ -1,80 +1,27 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
4
|
-
import { EXECUTOR_KINDS, executorDefinitionForKind
|
|
5
|
-
import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, formatAkkListCommandResult, parseAkkCommand, resolvePluginStoreDir
|
|
4
|
+
import { EXECUTOR_KINDS, executorDefinitionForKind } from "./executors.js";
|
|
5
|
+
import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, formatAkkListCommandResult, parseAkkCommand, resolvePluginStoreDir } from "./openclaw-plugin-helpers.js";
|
|
6
6
|
import { attemptAutoApproval } from "./approval-policy.js";
|
|
7
7
|
const CALLBACK_METHOD = AKK_CALLBACK_METHOD;
|
|
8
8
|
const defaultBinPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
9
|
-
const
|
|
9
|
+
const sendParameters = {
|
|
10
10
|
type: "object",
|
|
11
11
|
additionalProperties: false,
|
|
12
12
|
required: ["request"],
|
|
13
13
|
properties: {
|
|
14
|
-
|
|
14
|
+
selector: {
|
|
15
15
|
type: "string",
|
|
16
|
-
|
|
17
|
-
description: "Coding agent to delegate to. Defaults to plugin config defaultAgent, falling back to codex when unset. Explicit user agent requests override the default."
|
|
16
|
+
description: "Optional existing tmux target selector from AKK list: codex, claude, only, latest, an @short-ref, or an authoritative full id. Omit it to start a new managed turn through the unique eligible idle pane."
|
|
18
17
|
},
|
|
19
18
|
request: {
|
|
20
19
|
type: "string",
|
|
21
|
-
description: "
|
|
20
|
+
description: "New task or follow-up message for the coding agent."
|
|
22
21
|
},
|
|
23
|
-
|
|
24
|
-
type: "string",
|
|
25
|
-
description: "Workspace path for the selected coding agent. Defaults to plugin config or the Gateway process directory."
|
|
26
|
-
},
|
|
27
|
-
claudeSession: {
|
|
28
|
-
type: "string",
|
|
29
|
-
description: "Claude Code session name."
|
|
30
|
-
},
|
|
31
|
-
codexSession: {
|
|
32
|
-
type: "string",
|
|
33
|
-
description: "Codex session name."
|
|
34
|
-
},
|
|
35
|
-
cursorSession: {
|
|
36
|
-
type: "string",
|
|
37
|
-
description: "Cursor session name."
|
|
38
|
-
},
|
|
39
|
-
codexAllProxy: {
|
|
40
|
-
type: "string",
|
|
41
|
-
description: "ALL_PROXY value used when launching Codex through ACPX."
|
|
42
|
-
},
|
|
43
|
-
cursorAllProxy: {
|
|
44
|
-
type: "string",
|
|
45
|
-
description: "ALL_PROXY value used when launching Cursor through ACPX."
|
|
46
|
-
},
|
|
47
|
-
codexModel: {
|
|
48
|
-
type: "string",
|
|
49
|
-
description: "ACPX model id used when launching Codex."
|
|
50
|
-
},
|
|
51
|
-
cursorModel: {
|
|
52
|
-
type: "string",
|
|
53
|
-
description: "ACPX model id used when launching Cursor."
|
|
54
|
-
},
|
|
55
|
-
model: {
|
|
56
|
-
type: "string",
|
|
57
|
-
description: "ACPX model id used when launching the selected coding agent."
|
|
58
|
-
},
|
|
59
|
-
allProxy: {
|
|
60
|
-
type: "string",
|
|
61
|
-
description: "ALL_PROXY value used when launching the selected coding agent through ACPX."
|
|
62
|
-
},
|
|
63
|
-
session: {
|
|
64
|
-
type: "string",
|
|
65
|
-
description: "Explicit coding agent session name."
|
|
66
|
-
},
|
|
67
|
-
openclawSession: {
|
|
22
|
+
type: {
|
|
68
23
|
type: "string",
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
softLimit: {
|
|
72
|
-
type: "number",
|
|
73
|
-
description: "Soft response-requiring round limit."
|
|
74
|
-
},
|
|
75
|
-
hardLimit: {
|
|
76
|
-
type: "number",
|
|
77
|
-
description: "Hard response-requiring round limit."
|
|
24
|
+
enum: ["answer", "task", "control", "error"]
|
|
78
25
|
},
|
|
79
26
|
idleTimeoutMinutes: {
|
|
80
27
|
type: "number",
|
|
@@ -83,6 +30,11 @@ const delegateParameters = {
|
|
|
83
30
|
agentTimeoutMinutes: {
|
|
84
31
|
type: "number",
|
|
85
32
|
description: "Callback timeout in minutes; terminal bridge tasks treat it as an inactivity timeout."
|
|
33
|
+
},
|
|
34
|
+
agentHardTimeoutMinutes: {
|
|
35
|
+
type: "number",
|
|
36
|
+
exclusiveMinimum: 0,
|
|
37
|
+
description: "Maximum terminal monitor lifetime in minutes."
|
|
86
38
|
}
|
|
87
39
|
}
|
|
88
40
|
};
|
|
@@ -102,7 +54,7 @@ const listParameters = {
|
|
|
102
54
|
},
|
|
103
55
|
managedOnly: {
|
|
104
56
|
type: "boolean",
|
|
105
|
-
description: "When true, only list AKK-managed
|
|
57
|
+
description: "When true, only list AKK-managed turns and skip live tmux terminal discovery."
|
|
106
58
|
},
|
|
107
59
|
noApprovalScan: {
|
|
108
60
|
type: "boolean",
|
|
@@ -162,65 +114,6 @@ const statusParameters = {
|
|
|
162
114
|
}
|
|
163
115
|
}
|
|
164
116
|
};
|
|
165
|
-
const describeParameters = {
|
|
166
|
-
type: "object",
|
|
167
|
-
additionalProperties: false,
|
|
168
|
-
required: ["conversation_id"],
|
|
169
|
-
properties: {
|
|
170
|
-
conversation_id: {
|
|
171
|
-
type: "string",
|
|
172
|
-
description: "AKK-managed conversation id, native Codex id, or terminal-controlled id from AKK list. Use this when the user asks what a listed AKK/Codex session is about."
|
|
173
|
-
},
|
|
174
|
-
idleTimeoutMinutes: {
|
|
175
|
-
type: "number"
|
|
176
|
-
},
|
|
177
|
-
maxMessages: {
|
|
178
|
-
type: "number"
|
|
179
|
-
},
|
|
180
|
-
maxCommands: {
|
|
181
|
-
type: "number"
|
|
182
|
-
},
|
|
183
|
-
maxTextLength: {
|
|
184
|
-
type: "number"
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
const sendParameters = {
|
|
189
|
-
type: "object",
|
|
190
|
-
additionalProperties: false,
|
|
191
|
-
required: ["conversation_id", "message"],
|
|
192
|
-
properties: {
|
|
193
|
-
conversation_id: {
|
|
194
|
-
type: "string",
|
|
195
|
-
description: "AKK-managed conversation id, or a terminal-controlled id from AKK list such as terminal:v2:tmux:codex:codex-work:0.1:33389. When the user refers to a listed tmux target like my-work:0.1, resolve it to the terminal-controlled id from AKK list before sending."
|
|
196
|
-
},
|
|
197
|
-
message: {
|
|
198
|
-
type: "string"
|
|
199
|
-
},
|
|
200
|
-
type: {
|
|
201
|
-
type: "string",
|
|
202
|
-
enum: ["answer", "task", "control", "error"]
|
|
203
|
-
},
|
|
204
|
-
allProxy: {
|
|
205
|
-
type: "string"
|
|
206
|
-
},
|
|
207
|
-
model: {
|
|
208
|
-
type: "string"
|
|
209
|
-
},
|
|
210
|
-
idleTimeoutMinutes: {
|
|
211
|
-
type: "number"
|
|
212
|
-
},
|
|
213
|
-
agentTimeoutMinutes: {
|
|
214
|
-
type: "number",
|
|
215
|
-
description: "Terminal bridge inactivity timeout override in minutes."
|
|
216
|
-
},
|
|
217
|
-
agentHardTimeoutMinutes: {
|
|
218
|
-
type: "number",
|
|
219
|
-
exclusiveMinimum: 0,
|
|
220
|
-
description: "Terminal bridge fallback lifetime ceiling override in minutes."
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
117
|
const cancelParameters = {
|
|
225
118
|
type: "object",
|
|
226
119
|
additionalProperties: false,
|
|
@@ -230,31 +123,6 @@ const cancelParameters = {
|
|
|
230
123
|
type: "string",
|
|
231
124
|
description: "AKK-managed conversation id, or a terminal-controlled id from AKK list such as terminal:v2:tmux:codex:codex-work:0.1:33389."
|
|
232
125
|
},
|
|
233
|
-
allProxy: {
|
|
234
|
-
type: "string"
|
|
235
|
-
},
|
|
236
|
-
idleTimeoutMinutes: {
|
|
237
|
-
type: "number"
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
const recoveryParameters = {
|
|
242
|
-
type: "object",
|
|
243
|
-
additionalProperties: false,
|
|
244
|
-
required: ["conversation_id"],
|
|
245
|
-
properties: {
|
|
246
|
-
conversation_id: {
|
|
247
|
-
type: "string"
|
|
248
|
-
},
|
|
249
|
-
session: {
|
|
250
|
-
type: "string"
|
|
251
|
-
},
|
|
252
|
-
allProxy: {
|
|
253
|
-
type: "string"
|
|
254
|
-
},
|
|
255
|
-
model: {
|
|
256
|
-
type: "string"
|
|
257
|
-
},
|
|
258
126
|
idleTimeoutMinutes: {
|
|
259
127
|
type: "number"
|
|
260
128
|
}
|
|
@@ -270,79 +138,10 @@ const closeParameters = {
|
|
|
270
138
|
},
|
|
271
139
|
reason: {
|
|
272
140
|
type: "string"
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
const agentTakeoverParameters = {
|
|
277
|
-
type: "object",
|
|
278
|
-
additionalProperties: false,
|
|
279
|
-
required: ["agent", "sessionId", "strategy"],
|
|
280
|
-
properties: {
|
|
281
|
-
agent: {
|
|
282
|
-
type: "string",
|
|
283
|
-
enum: ["codex"],
|
|
284
|
-
description: "Local coding agent session provider to plan takeover for. Currently supports Codex."
|
|
285
|
-
},
|
|
286
|
-
sessionId: {
|
|
287
|
-
type: "string",
|
|
288
|
-
description: "Native Codex session id to inspect or take over."
|
|
289
|
-
},
|
|
290
|
-
strategy: {
|
|
291
|
-
type: "string",
|
|
292
|
-
enum: ["terminate_then_resume", "fork", "terminal_control"],
|
|
293
|
-
description: "Takeover strategy. terminate_then_resume returns a confirmation plan to stop the exact active CLI before resume. terminal_control attaches to an existing controllable terminal session after confirmation. fork returns bounded context for OpenClaw summary confirmation."
|
|
294
|
-
},
|
|
295
|
-
createConversation: {
|
|
296
|
-
type: "boolean",
|
|
297
|
-
description: "When true and the selected strategy is ready, create an AKK-managed conversation bound to the native session so later AKK send/status/close can use it."
|
|
298
|
-
},
|
|
299
|
-
confirmTerminate: {
|
|
300
|
-
type: "boolean",
|
|
301
|
-
description: "Only for strategy=terminate_then_resume. When true, AKK will terminate the exact expected Codex CLI pid after rechecking that it still matches the requested session, then create the AKK conversation."
|
|
302
|
-
},
|
|
303
|
-
expectedPid: {
|
|
304
|
-
type: "number",
|
|
305
|
-
description: "Required with confirmTerminate=true. The exact Codex CLI pid from the previous takeover plan that the user confirmed may be terminated."
|
|
306
|
-
},
|
|
307
|
-
allowCwdOnly: {
|
|
308
|
-
type: "boolean",
|
|
309
|
-
description: "Only for strategy=terminate_then_resume with confirmTerminate=true. Allows terminating the expected pid when Codex does not expose a session id in argv, as long as the pid still runs in the target session cwd. Use only after explaining the higher risk to the user."
|
|
310
|
-
},
|
|
311
|
-
confirmTerminal: {
|
|
312
|
-
type: "boolean",
|
|
313
|
-
description: "Only for strategy=terminal_control. When true, AKK attaches to the exact terminalTarget after the user confirms that terminal pane."
|
|
314
|
-
},
|
|
315
|
-
terminalTarget: {
|
|
316
|
-
type: "string",
|
|
317
|
-
description: "Required with confirmTerminal=true. The exact tmux target from the previous terminal_control plan, such as codex-work:0.0."
|
|
318
|
-
},
|
|
319
|
-
request: {
|
|
320
|
-
type: "string",
|
|
321
|
-
description: "Optional user-visible request label stored on the created AKK conversation."
|
|
322
|
-
},
|
|
323
|
-
forkSummary: {
|
|
324
|
-
type: "string",
|
|
325
|
-
description: "Required when strategy=fork and createConversation=true. OpenClaw-approved summary of the bounded source context to inject into the new forked AKK session."
|
|
326
|
-
},
|
|
327
|
-
openclawSession: {
|
|
328
|
-
type: "string",
|
|
329
|
-
description: "OpenClaw session label recorded in the created AKK conversation."
|
|
330
141
|
},
|
|
331
|
-
|
|
142
|
+
expected_message_id: {
|
|
332
143
|
type: "string",
|
|
333
|
-
description: "
|
|
334
|
-
},
|
|
335
|
-
maxMessages: {
|
|
336
|
-
type: "number",
|
|
337
|
-
description: "Maximum rollout messages to include for fork context."
|
|
338
|
-
},
|
|
339
|
-
maxCommands: {
|
|
340
|
-
type: "number",
|
|
341
|
-
description: "Maximum rollout command records to include for fork context."
|
|
342
|
-
},
|
|
343
|
-
maxTextLength: {
|
|
344
|
-
type: "number",
|
|
345
|
-
description: "Maximum text length per fork-context message."
|
|
144
|
+
description: "Required only to clear an orphaned terminal dispatch shown by AKK list. Must exactly match that entry's current message_id."
|
|
346
145
|
}
|
|
347
146
|
}
|
|
348
147
|
};
|
|
@@ -364,7 +163,7 @@ const approveParameters = {
|
|
|
364
163
|
const plugin = definePluginEntry({
|
|
365
164
|
id: "agent-knock-knock",
|
|
366
165
|
name: "Agent Knock Knock",
|
|
367
|
-
description: "Agent Knock Knock (AKK/akk)
|
|
166
|
+
description: "Agent Knock Knock (AKK/akk) lets OpenClaw operate local Codex and Claude Code through shared tmux terminals, with visible monitoring, approvals, callbacks, cancellation, and seamless human takeover.",
|
|
368
167
|
register(api) {
|
|
369
168
|
api.registerGatewayMethod(CALLBACK_METHOD, async ({ params, respond }) => {
|
|
370
169
|
try {
|
|
@@ -387,6 +186,7 @@ const plugin = definePluginEntry({
|
|
|
387
186
|
try {
|
|
388
187
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
389
188
|
const args = ["reconcile-monitors"];
|
|
189
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
390
190
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
391
191
|
const result = runCli(api, args);
|
|
392
192
|
api.logger.info?.("agent-knock-knock monitor reconciliation: " +
|
|
@@ -407,42 +207,26 @@ const plugin = definePluginEntry({
|
|
|
407
207
|
}
|
|
408
208
|
api.registerCommand?.({
|
|
409
209
|
name: "akk",
|
|
410
|
-
description: "
|
|
210
|
+
description: "Send coding work through existing Codex or Claude Code tmux terminals, inspect tasks, approve exact prompts, and cancel running work.",
|
|
411
211
|
acceptsArgs: true,
|
|
412
212
|
requireAuth: true,
|
|
413
213
|
nativeProgressMessages: {
|
|
414
214
|
default: "AKK is handling the request..."
|
|
415
215
|
},
|
|
416
216
|
agentPromptGuidance: [
|
|
417
|
-
"Use /akk <task>
|
|
217
|
+
"Use /akk <task> when exactly one eligible idle coding-agent tmux pane should receive new work. Use /akk codex: <task>, /akk claude: <task>, or another selector returned by /akk list to target an existing pane. AKK never starts a coding agent."
|
|
418
218
|
],
|
|
419
219
|
handler: async (ctx) => handleAkkCommand(api, ctx)
|
|
420
220
|
});
|
|
421
|
-
api.registerTool((toolContext) => ({
|
|
422
|
-
label: "AKK Delegate",
|
|
423
|
-
name: "agent_knock_knock_delegate",
|
|
424
|
-
description: "Delegate an implementation task to a local coding agent. Use this when the user says AKK, akk, Agent Knock Knock, asks to hand work to Codex, Claude, or Cursor, or asks OpenClaw to start a background coding-agent task. If the user says AKK without an explicit agent, omit the agent parameter so the plugin-configured defaultAgent is used, falling back to Codex when unset. The tool starts the coding agent in the background and returns only protocol metadata, not raw terminal output.",
|
|
425
|
-
parameters: delegateParameters,
|
|
426
|
-
async execute(_toolCallId, params) {
|
|
427
|
-
const result = runDelegate(api, params, toolContext);
|
|
428
|
-
return {
|
|
429
|
-
content: [
|
|
430
|
-
{
|
|
431
|
-
type: "text",
|
|
432
|
-
text: JSON.stringify(result, null, 2)
|
|
433
|
-
}
|
|
434
|
-
],
|
|
435
|
-
details: result
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
}), { name: "agent_knock_knock_delegate", optional: true });
|
|
439
221
|
registerCliTool(api, {
|
|
440
222
|
name: "agent_knock_knock_list",
|
|
441
|
-
description: "List
|
|
223
|
+
description: "List AKK-managed work and existing Codex or Claude Code tmux terminals. Use this to choose an exact selector before sending, inspecting, approving, or cancelling. AKK reports terminal approval state when available and never starts a coding agent.",
|
|
442
224
|
parameters: listParameters,
|
|
443
225
|
buildArgs: (params) => {
|
|
226
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
444
227
|
const args = ["list"];
|
|
445
|
-
pushOptional(args, "--
|
|
228
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
229
|
+
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
446
230
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
447
231
|
pushOptional(args, "--agent", stringValue(params.agent));
|
|
448
232
|
pushOptional(args, "--status", stringValue(params.status));
|
|
@@ -461,77 +245,36 @@ const plugin = definePluginEntry({
|
|
|
461
245
|
return args;
|
|
462
246
|
}
|
|
463
247
|
});
|
|
464
|
-
|
|
248
|
+
api.registerTool((_toolContext) => ({
|
|
249
|
+
label: "AKK Status",
|
|
465
250
|
name: "agent_knock_knock_status",
|
|
466
|
-
description: "
|
|
251
|
+
description: "Inspect one AKK-managed turn or existing coding-agent tmux terminal. Returns live state, a bounded terminal screen, and available purpose/context with confidence and limitations. AKK never starts a coding agent.",
|
|
467
252
|
parameters: statusParameters,
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
472
|
-
if (params.trace === true) {
|
|
473
|
-
args.push("--trace");
|
|
474
|
-
}
|
|
475
|
-
return args;
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
registerCliTool(api, {
|
|
479
|
-
name: "agent_knock_knock_describe",
|
|
480
|
-
description: "Describe what a listed Agent Knock Knock or local coding-agent session is about. It summarizes AKK history when available, otherwise uses supported durable agent context or a conservative terminal-screen fallback with confidence and limitations.",
|
|
481
|
-
parameters: describeParameters,
|
|
482
|
-
buildArgs: (params) => {
|
|
483
|
-
const args = ["describe", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
484
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
|
|
485
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
486
|
-
pushOptional(args, "--max-messages", numberString(params.maxMessages));
|
|
487
|
-
pushOptional(args, "--max-commands", numberString(params.maxCommands));
|
|
488
|
-
pushOptional(args, "--max-text-length", numberString(params.maxTextLength));
|
|
489
|
-
return args;
|
|
253
|
+
async execute(_toolCallId, params) {
|
|
254
|
+
const result = runCli(api, buildStatusCliArgs(api, isRecord(params) ? params : {}));
|
|
255
|
+
return toolResult(result);
|
|
490
256
|
}
|
|
491
|
-
});
|
|
492
|
-
|
|
257
|
+
}), { name: "agent_knock_knock_status", optional: true });
|
|
258
|
+
api.registerTool((toolContext) => ({
|
|
259
|
+
label: "AKK Send",
|
|
493
260
|
name: "agent_knock_knock_send",
|
|
494
|
-
description: "Send a
|
|
261
|
+
description: "Send a new task or follow-up through an existing Codex or Claude Code tmux terminal. Omit selector only when AKK should require one unique eligible idle pane; otherwise pass codex, claude, only, latest, an @short-ref from AKK list, or an authoritative full id. AKK never starts a coding agent. This is asynchronous: after acceptance, yield and wait for the callback or a later explicit status request.",
|
|
495
262
|
parameters: sendParameters,
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
stringValue(config.openclawSession) ??
|
|
500
|
-
"agent:main:main";
|
|
501
|
-
const args = [
|
|
502
|
-
"send",
|
|
503
|
-
"--conversation",
|
|
504
|
-
requiredString(params.conversation_id, "conversation_id"),
|
|
505
|
-
"--message",
|
|
506
|
-
requiredString(params.message, "message"),
|
|
507
|
-
"--background"
|
|
508
|
-
];
|
|
509
|
-
pushOptional(args, "--type", stringValue(params.type));
|
|
510
|
-
const overrides = resolveConversationOverrides(params, config);
|
|
511
|
-
pushOptional(args, "--all-proxy", overrides.allProxy);
|
|
512
|
-
pushOptional(args, "--model", overrides.model);
|
|
513
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
514
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
515
|
-
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
|
|
516
|
-
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
|
|
517
|
-
pushOptional(args, "--openclaw-session", openclawSession);
|
|
518
|
-
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
519
|
-
pushOptional(args, "--gateway-session", openclawSession);
|
|
520
|
-
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
521
|
-
pushOptional(args, "--callback-command", stringValue(config.callbackCommand));
|
|
522
|
-
pushOptional(args, "--soft-limit", numberString(config.softLimit));
|
|
523
|
-
pushOptional(args, "--hard-limit", numberString(config.hardLimit));
|
|
524
|
-
return args;
|
|
263
|
+
async execute(_toolCallId, params) {
|
|
264
|
+
const result = await runSendRequest(api, isRecord(params) ? params : {}, toolContext);
|
|
265
|
+
return toolResult(result);
|
|
525
266
|
}
|
|
526
|
-
});
|
|
267
|
+
}), { name: "agent_knock_knock_send", optional: true });
|
|
527
268
|
registerCliTool(api, {
|
|
528
269
|
name: "agent_knock_knock_approve",
|
|
529
270
|
description: "Manually approve the current AKK terminal permission request only after the user reviews and explicitly confirms it. Claude Code uses no Hooks: this manual path accepts only an exact one-time Bash permission screen for the current managed turn, 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.",
|
|
530
271
|
parameters: approveParameters,
|
|
531
272
|
buildArgs: (params) => {
|
|
273
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
532
274
|
const args = ["approve", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
275
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
533
276
|
pushOptional(args, "--expected-approval-fingerprint", requiredString(params.expected_approval_fingerprint, "expected_approval_fingerprint"));
|
|
534
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(
|
|
277
|
+
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
535
278
|
return args;
|
|
536
279
|
}
|
|
537
280
|
});
|
|
@@ -543,101 +286,47 @@ const plugin = definePluginEntry({
|
|
|
543
286
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
544
287
|
const args = ["renew", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
545
288
|
pushOptional(args, "--minutes", numberString(params.minutes) ?? numberString(config.agentTimeoutMinutes));
|
|
289
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
546
290
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
547
291
|
return args;
|
|
548
292
|
}
|
|
549
293
|
});
|
|
550
294
|
registerCliTool(api, {
|
|
551
295
|
name: "agent_knock_knock_retry_callback",
|
|
552
|
-
description: "Retry a persisted AKK callback that failed before reaching OpenClaw. The original callback message id is reused for idempotent delivery
|
|
296
|
+
description: "Retry a persisted AKK callback that failed before reaching OpenClaw. The original callback message id is reused for idempotent delivery; a completed terminal task remains available for follow-up until idle cleanup.",
|
|
553
297
|
parameters: retryCallbackParameters,
|
|
554
298
|
buildArgs: (params) => {
|
|
555
299
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
556
300
|
const args = ["retry-callback", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
301
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
557
302
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
558
303
|
return args;
|
|
559
304
|
}
|
|
560
305
|
});
|
|
561
306
|
registerCliTool(api, {
|
|
562
307
|
name: "agent_knock_knock_cancel",
|
|
563
|
-
description: "
|
|
308
|
+
description: "Interrupt an existing Agent Knock Knock Codex or Claude Code tmux task. Claude sends Escape; Codex uses its declared interrupt key. The shared tmux pane remains open for human takeover.",
|
|
564
309
|
parameters: cancelParameters,
|
|
565
310
|
buildArgs: (params) => {
|
|
566
311
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
567
|
-
const overrides = resolveConversationOverrides(params, config);
|
|
568
312
|
const args = ["cancel", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
569
|
-
pushOptional(args, "--
|
|
313
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
570
314
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
571
315
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
572
316
|
return args;
|
|
573
317
|
}
|
|
574
318
|
});
|
|
575
|
-
registerCliTool(api, {
|
|
576
|
-
name: "agent_knock_knock_recover",
|
|
577
|
-
description: "Recover an Agent Knock Knock task whose coding-agent session is unavailable by starting a new session with AKK's saved protocol history summary plus the pending message. Use only after the user chooses recovery.",
|
|
578
|
-
parameters: recoveryParameters,
|
|
579
|
-
buildArgs: (params) => buildRecoveryArgs(api, "recover", params)
|
|
580
|
-
});
|
|
581
319
|
registerCliTool(api, {
|
|
582
320
|
name: "agent_knock_knock_close",
|
|
583
|
-
description: "Close an
|
|
321
|
+
description: "Close an AKK-managed task record without terminating the shared tmux pane. For an orphaned terminal dispatch only, the user must explicitly request recovery and provide the exact expected_message_id reported by AKK list.",
|
|
584
322
|
parameters: closeParameters,
|
|
585
323
|
buildArgs: (params) => {
|
|
324
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
586
325
|
const args = ["close", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
587
326
|
pushOptional(args, "--reason", stringValue(params.reason));
|
|
588
|
-
pushOptional(args, "--
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
registerCliTool(api, {
|
|
593
|
-
name: "agent_knock_knock_agent_takeover",
|
|
594
|
-
description: "Build or execute a takeover plan for an existing native local coding-agent session. Use this for AKK takeover requests. By default it is side-effect-free and returns the plan; with createConversation=true it can attach a ready native session or create a confirmed forked AKK-managed conversation. For terminate_then_resume, only call with confirmTerminate=true and expectedPid after the user explicitly confirms stopping that exact Codex CLI pid. If Codex does not expose a session id, allowCwdOnly=true is a higher-risk explicit-pid fallback.",
|
|
595
|
-
parameters: agentTakeoverParameters,
|
|
596
|
-
buildArgs: (params, toolContext) => {
|
|
597
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
598
|
-
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
599
|
-
stringValue(config.openclawSession) ??
|
|
600
|
-
stringValue(params.openclawSession) ??
|
|
601
|
-
"agent:main:main";
|
|
602
|
-
const args = [
|
|
603
|
-
"agent",
|
|
604
|
-
"takeover",
|
|
605
|
-
"--agent",
|
|
606
|
-
requiredString(params.agent, "agent"),
|
|
607
|
-
"--session-id",
|
|
608
|
-
requiredString(params.sessionId, "sessionId"),
|
|
609
|
-
"--strategy",
|
|
610
|
-
requiredString(params.strategy, "strategy")
|
|
611
|
-
];
|
|
612
|
-
if (params.createConversation === true) {
|
|
613
|
-
args.push("--create-conversation");
|
|
614
|
-
}
|
|
615
|
-
if (params.confirmTerminate === true) {
|
|
616
|
-
args.push("--confirm-terminate");
|
|
617
|
-
}
|
|
618
|
-
if (params.allowCwdOnly === true) {
|
|
619
|
-
args.push("--allow-cwd-only");
|
|
620
|
-
}
|
|
621
|
-
if (params.confirmTerminal === true) {
|
|
622
|
-
args.push("--confirm-terminal");
|
|
623
|
-
}
|
|
624
|
-
pushOptional(args, "--expected-pid", numberString(params.expectedPid));
|
|
625
|
-
pushOptional(args, "--terminal-target", stringValue(params.terminalTarget));
|
|
626
|
-
pushOptional(args, "--request", stringValue(params.request));
|
|
627
|
-
pushOptional(args, "--fork-summary", stringValue(params.forkSummary));
|
|
327
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
328
|
+
pushOptional(args, "--expected-message-id", stringValue(params.expected_message_id));
|
|
628
329
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
629
|
-
pushOptional(args, "--openclaw-session", openclawSession);
|
|
630
|
-
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
631
|
-
pushOptional(args, "--gateway-session", openclawSession);
|
|
632
|
-
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
633
|
-
pushOptional(args, "--callback-command", stringValue(config.callbackCommand));
|
|
634
|
-
pushOptional(args, "--soft-limit", numberString(config.softLimit));
|
|
635
|
-
pushOptional(args, "--hard-limit", numberString(config.hardLimit));
|
|
636
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(config.idleTimeoutMinutes));
|
|
637
|
-
pushOptional(args, "--codex-home", stringValue(params.codexHome) ?? stringValue(api.pluginConfig?.codexHome));
|
|
638
|
-
pushOptional(args, "--max-messages", numberString(params.maxMessages));
|
|
639
|
-
pushOptional(args, "--max-commands", numberString(params.maxCommands));
|
|
640
|
-
pushOptional(args, "--max-text-length", numberString(params.maxTextLength));
|
|
641
330
|
return args;
|
|
642
331
|
}
|
|
643
332
|
});
|
|
@@ -651,13 +340,15 @@ async function handleAkkCommand(api, ctx) {
|
|
|
651
340
|
return { text: akkUsageText() };
|
|
652
341
|
}
|
|
653
342
|
if (parsed.action === "delegate") {
|
|
654
|
-
const result = runDelegate(api, {
|
|
655
|
-
agent: parsed.agent,
|
|
343
|
+
const result = await runDelegate(api, {
|
|
656
344
|
request: parsed.request
|
|
657
345
|
}, {
|
|
658
346
|
sessionKey: ctx.sessionKey
|
|
659
347
|
});
|
|
660
|
-
return {
|
|
348
|
+
return {
|
|
349
|
+
text: formatDelegateCommandResult(result),
|
|
350
|
+
isError: result.status !== "async_pending"
|
|
351
|
+
};
|
|
661
352
|
}
|
|
662
353
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
663
354
|
const args = buildAkkCommandCliArgs(parsed, config, {
|
|
@@ -681,18 +372,19 @@ async function handleAkkCommand(api, ctx) {
|
|
|
681
372
|
return { text: formatAkkListCommandResult(result) };
|
|
682
373
|
case "status":
|
|
683
374
|
return { text: formatStatusCommandResult(result) };
|
|
684
|
-
case "describe":
|
|
685
|
-
return { text: formatDescribeCommandResult(result) };
|
|
686
375
|
case "send":
|
|
687
|
-
return {
|
|
376
|
+
return {
|
|
377
|
+
text: formatSendCommandResult(result),
|
|
378
|
+
isError: ["uncertain", "aborted"].includes(String(result.submission_outcome ?? ""))
|
|
379
|
+
};
|
|
380
|
+
case "approve":
|
|
381
|
+
return { text: formatApproveCommandResult(result) };
|
|
688
382
|
case "renew":
|
|
689
383
|
return { text: formatRenewCommandResult(result) };
|
|
690
384
|
case "retry-callback":
|
|
691
385
|
return { text: formatRetryCallbackCommandResult(result) };
|
|
692
386
|
case "cancel":
|
|
693
387
|
return { text: formatCancelCommandResult(result) };
|
|
694
|
-
case "recover":
|
|
695
|
-
return { text: formatRecoveryCommandResult(result) };
|
|
696
388
|
case "close":
|
|
697
389
|
return { text: formatCloseCommandResult(result) };
|
|
698
390
|
}
|
|
@@ -707,6 +399,22 @@ async function handleAkkCommand(api, ctx) {
|
|
|
707
399
|
}
|
|
708
400
|
function formatDelegateCommandResult(result) {
|
|
709
401
|
const agent = executorDisplayName(result.agent);
|
|
402
|
+
if (result.status === "submission_uncertain") {
|
|
403
|
+
return [
|
|
404
|
+
`AKK could not prove whether ${agent} received the terminal task.`,
|
|
405
|
+
`conversation: ${result.conversation_id ?? "unknown"}`,
|
|
406
|
+
`session: ${result.session ?? "unknown"}`,
|
|
407
|
+
"next: do not retry automatically; inspect AKK status and the named tmux pane."
|
|
408
|
+
].join("\n");
|
|
409
|
+
}
|
|
410
|
+
if (result.status === "submission_aborted") {
|
|
411
|
+
return [
|
|
412
|
+
`AKK stopped before sending the terminal task to ${agent}.`,
|
|
413
|
+
`conversation: ${result.conversation_id ?? "unknown"}`,
|
|
414
|
+
`session: ${result.session ?? "unknown"}`,
|
|
415
|
+
"next: no tmux input was sent, so this request may be retried."
|
|
416
|
+
].join("\n");
|
|
417
|
+
}
|
|
710
418
|
return [
|
|
711
419
|
`AKK delegated the task to ${agent}.`,
|
|
712
420
|
`conversation: ${result.conversation_id ?? "unknown"}`,
|
|
@@ -736,12 +444,27 @@ function formatStatusCommandResult(result) {
|
|
|
736
444
|
if (summary.request) {
|
|
737
445
|
lines.push(`request: ${truncateText(summary.request, 180)}`);
|
|
738
446
|
}
|
|
447
|
+
if (result.about) {
|
|
448
|
+
lines.push(`about: ${truncateText(result.about, 500)}`);
|
|
449
|
+
}
|
|
450
|
+
if (result.confidence) {
|
|
451
|
+
lines.push(`confidence: ${result.confidence}`);
|
|
452
|
+
}
|
|
453
|
+
const limitations = Array.isArray(result.limitations)
|
|
454
|
+
? result.limitations.filter(Boolean)
|
|
455
|
+
: [];
|
|
456
|
+
if (limitations.length > 0) {
|
|
457
|
+
lines.push(`limitations: ${limitations.slice(0, 3).join("; ")}`);
|
|
458
|
+
}
|
|
459
|
+
const screen = terminalScreenExcerpt(result);
|
|
460
|
+
if (screen) {
|
|
461
|
+
lines.push(`terminal screen:\n${screen}`);
|
|
462
|
+
}
|
|
739
463
|
return lines.join("\n");
|
|
740
464
|
}
|
|
741
465
|
function formatDoctorCommandResult(result) {
|
|
742
466
|
const capabilities = isRecord(result.capabilities) ? result.capabilities : {};
|
|
743
467
|
const tmux = isRecord(capabilities.tmux) ? capabilities.tmux : {};
|
|
744
|
-
const acpx = isRecord(capabilities.acpx) ? capabilities.acpx : {};
|
|
745
468
|
const openclaw = isRecord(result.openclaw) ? result.openclaw : {};
|
|
746
469
|
const checks = Array.isArray(openclaw.checks) ? openclaw.checks : [];
|
|
747
470
|
const failures = checks
|
|
@@ -752,9 +475,7 @@ function formatDoctorCommandResult(result) {
|
|
|
752
475
|
: []))].slice(0, 3);
|
|
753
476
|
return [
|
|
754
477
|
`AKK doctor: ${result.ok === true ? "ready" : "needs attention"}`,
|
|
755
|
-
`
|
|
756
|
-
`tmux: ${tmux.checked === false ? "not checked" : tmux.status ?? "unknown"}`,
|
|
757
|
-
`acpx: ${acpx.checked === false ? "not checked" : acpx.status ?? "unknown"}`,
|
|
478
|
+
`tmux: ${tmux.status ?? "unknown"}`,
|
|
758
479
|
`OpenClaw package: ${openclaw.package_ready === true ? "ready" : "not ready"}`,
|
|
759
480
|
`Gateway: ${openclaw.gateway_ready === true ? "healthy" : "unavailable"}`,
|
|
760
481
|
...(failures.length > 0 ? [`check: ${failures.join(", ")}`] : []),
|
|
@@ -776,26 +497,27 @@ function formatRetryCallbackCommandResult(result) {
|
|
|
776
497
|
`attempts: ${result.conversation?.callback_delivery?.attempts ?? "unknown"}`
|
|
777
498
|
].join("\n");
|
|
778
499
|
}
|
|
779
|
-
function formatDescribeCommandResult(result) {
|
|
780
|
-
const lines = [
|
|
781
|
-
`AKK description: ${result.conversation_id ?? "unknown"}`,
|
|
782
|
-
`source: ${result.source ?? "unknown"}`,
|
|
783
|
-
`confidence: ${result.confidence ?? "unknown"}`
|
|
784
|
-
];
|
|
785
|
-
if (result.about) {
|
|
786
|
-
lines.push(`about: ${truncateText(result.about, 500)}`);
|
|
787
|
-
}
|
|
788
|
-
const limitations = Array.isArray(result.limitations) ? result.limitations.filter(Boolean) : [];
|
|
789
|
-
if (limitations.length > 0) {
|
|
790
|
-
lines.push(`limitations: ${limitations.slice(0, 3).join("; ")}`);
|
|
791
|
-
}
|
|
792
|
-
return lines.join("\n");
|
|
793
|
-
}
|
|
794
500
|
function formatSendCommandResult(result) {
|
|
795
501
|
const conversation = result.conversation ?? {};
|
|
796
502
|
const conversationId = conversation.conversation_id ?? result.conversation_id ?? "unknown";
|
|
797
503
|
const status = conversation.status ?? result.status ?? "unknown";
|
|
798
504
|
const nextAction = isRecord(result.openclaw_next_action) ? result.openclaw_next_action : undefined;
|
|
505
|
+
if (result.submission_outcome === "uncertain") {
|
|
506
|
+
return [
|
|
507
|
+
"AKK terminal submission outcome is uncertain.",
|
|
508
|
+
`conversation: ${conversationId}`,
|
|
509
|
+
`status: ${result.status ?? status}`,
|
|
510
|
+
"next: do not retry automatically; inspect AKK status and the named tmux pane."
|
|
511
|
+
].join("\n");
|
|
512
|
+
}
|
|
513
|
+
if (result.submission_outcome === "aborted") {
|
|
514
|
+
return [
|
|
515
|
+
"AKK terminal submission was aborted before tmux input.",
|
|
516
|
+
`conversation: ${conversationId}`,
|
|
517
|
+
`status: ${result.status ?? status}`,
|
|
518
|
+
"next: this request was not sent and may be retried."
|
|
519
|
+
].join("\n");
|
|
520
|
+
}
|
|
799
521
|
const lines = [
|
|
800
522
|
"AKK follow-up sent.",
|
|
801
523
|
`conversation: ${conversationId}`,
|
|
@@ -822,17 +544,30 @@ function formatCancelCommandResult(result) {
|
|
|
822
544
|
`status: ${conversation.status ?? (result.cancel_requested === true ? "cancel requested" : "not cancelled")}`
|
|
823
545
|
].join("\n");
|
|
824
546
|
}
|
|
825
|
-
function
|
|
547
|
+
function formatApproveCommandResult(result) {
|
|
826
548
|
const conversation = result.conversation ?? {};
|
|
827
549
|
return [
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
`
|
|
832
|
-
`status: ${conversation.status ?? "unknown"}
|
|
550
|
+
result.approved === true
|
|
551
|
+
? "AKK approved the current terminal request."
|
|
552
|
+
: "AKK did not approve the terminal request.",
|
|
553
|
+
`conversation: ${conversation.conversation_id ?? result.conversation_id ?? "unknown"}`,
|
|
554
|
+
`status: ${conversation.status ?? "unknown"}`,
|
|
555
|
+
...(result.reason ? [`reason: ${result.reason}`] : [])
|
|
833
556
|
].join("\n");
|
|
834
557
|
}
|
|
835
558
|
function formatCloseCommandResult(result) {
|
|
559
|
+
if (result.source === "terminal_control" &&
|
|
560
|
+
result.terminal_dispatch_resolved === true) {
|
|
561
|
+
const terminalControl = isRecord(result.terminal_control)
|
|
562
|
+
? result.terminal_control
|
|
563
|
+
: {};
|
|
564
|
+
return [
|
|
565
|
+
"AKK cleared the unresolved terminal dispatch fence.",
|
|
566
|
+
`terminal: ${terminalControl.target ?? "unknown"}`,
|
|
567
|
+
`previous owner: ${result.owner_conversation_id ?? "unknown"}`,
|
|
568
|
+
"The coding agent and tmux pane remain open."
|
|
569
|
+
].join("\n");
|
|
570
|
+
}
|
|
836
571
|
const conversation = result.conversation ?? {};
|
|
837
572
|
return [
|
|
838
573
|
"AKK session closed.",
|
|
@@ -840,35 +575,88 @@ function formatCloseCommandResult(result) {
|
|
|
840
575
|
`status: ${conversation.status ?? "unknown"}`
|
|
841
576
|
].join("\n");
|
|
842
577
|
}
|
|
843
|
-
function
|
|
578
|
+
function buildStatusCliArgs(api, params) {
|
|
579
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
580
|
+
const args = [
|
|
581
|
+
"status",
|
|
582
|
+
"--conversation",
|
|
583
|
+
requiredString(params.conversation_id, "conversation_id")
|
|
584
|
+
];
|
|
585
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
586
|
+
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
587
|
+
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
|
|
588
|
+
numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
589
|
+
if (params.trace === true) {
|
|
590
|
+
args.push("--trace");
|
|
591
|
+
}
|
|
592
|
+
return args;
|
|
593
|
+
}
|
|
594
|
+
function terminalScreenExcerpt(result) {
|
|
595
|
+
const screen = result.terminal_screen;
|
|
596
|
+
const text = typeof screen === "string"
|
|
597
|
+
? screen
|
|
598
|
+
: isRecord(screen)
|
|
599
|
+
? stringValue(screen.excerpt) ??
|
|
600
|
+
stringValue(screen.text) ??
|
|
601
|
+
stringValue(screen.content)
|
|
602
|
+
: undefined;
|
|
603
|
+
if (!text) {
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
606
|
+
return text.length <= 1600
|
|
607
|
+
? text
|
|
608
|
+
: `…${text.slice(-1599)}`;
|
|
609
|
+
}
|
|
610
|
+
async function runSendRequest(api, params, toolContext) {
|
|
611
|
+
const selector = stringValue(params.selector);
|
|
612
|
+
if (!selector) {
|
|
613
|
+
return runDelegate(api, params, toolContext);
|
|
614
|
+
}
|
|
844
615
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
845
|
-
const binPath = stringValue(config.binPath) ?? defaultBinPath;
|
|
846
|
-
const workspace = stringValue(params.workspace) ?? stringValue(config.workspace) ?? process.cwd();
|
|
847
|
-
const rawRequest = requiredString(params.request, "request");
|
|
848
|
-
const prefixedRequest = stringValue(params.agent) ? undefined : parseLeadingExecutorAlias(rawRequest);
|
|
849
|
-
const request = prefixedRequest?.request ?? rawRequest;
|
|
850
|
-
const agent = executorDefinitionForKind(stringValue(params.agent) ?? prefixedRequest?.kind ?? stringValue(config.defaultAgent) ?? "codex").kind;
|
|
851
|
-
const executorDefinition = executorDefinitionForKind(agent);
|
|
852
|
-
const agentSession = stringValue(params.session) ??
|
|
853
|
-
firstStringForKeys(params, executorDefinition.sessionConfigKeys) ??
|
|
854
|
-
firstStringForKeys(config, executorDefinition.sessionConfigKeys);
|
|
855
|
-
const allProxy = stringValue(params.allProxy) ??
|
|
856
|
-
firstStringForKeys(params, executorDefinition.proxyConfigKeys) ??
|
|
857
|
-
firstStringForKeys(config, executorDefinition.proxyConfigKeys) ??
|
|
858
|
-
stringValue(config.allProxy);
|
|
859
|
-
const model = stringValue(params.model) ??
|
|
860
|
-
firstStringForKeys(params, executorDefinition.modelConfigKeys) ??
|
|
861
|
-
firstStringForKeys(config, executorDefinition.modelConfigKeys) ??
|
|
862
|
-
stringValue(config.model);
|
|
863
616
|
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
864
|
-
stringValue(config.openclawSession) ??
|
|
865
|
-
stringValue(params.openclawSession) ??
|
|
866
617
|
"agent:main:main";
|
|
867
618
|
const args = [
|
|
868
|
-
|
|
619
|
+
"send",
|
|
620
|
+
"--conversation",
|
|
621
|
+
selector,
|
|
622
|
+
"--message",
|
|
623
|
+
requiredString(params.request, "request"),
|
|
624
|
+
"--background"
|
|
625
|
+
];
|
|
626
|
+
pushOptional(args, "--type", stringValue(params.type));
|
|
627
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
628
|
+
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
629
|
+
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ??
|
|
630
|
+
numberString(config.idleTimeoutMinutes));
|
|
631
|
+
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ??
|
|
632
|
+
numberString(config.agentTimeoutMinutes));
|
|
633
|
+
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ??
|
|
634
|
+
numberString(config.agentHardTimeoutMinutes));
|
|
635
|
+
pushOptional(args, "--openclaw-session", openclawSession);
|
|
636
|
+
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
637
|
+
pushOptional(args, "--gateway-session", openclawSession);
|
|
638
|
+
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
639
|
+
return runCli(api, args);
|
|
640
|
+
}
|
|
641
|
+
function toolResult(result) {
|
|
642
|
+
return {
|
|
643
|
+
content: [
|
|
644
|
+
{
|
|
645
|
+
type: "text",
|
|
646
|
+
text: JSON.stringify(result, null, 2)
|
|
647
|
+
}
|
|
648
|
+
],
|
|
649
|
+
details: result
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
async function runDelegate(api, params, toolContext) {
|
|
653
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
654
|
+
const workspace = pluginWorkspace(config);
|
|
655
|
+
const request = requiredString(params.request, "request");
|
|
656
|
+
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
657
|
+
"agent:main:main";
|
|
658
|
+
const args = [
|
|
869
659
|
"delegate",
|
|
870
|
-
"--agent",
|
|
871
|
-
agent,
|
|
872
660
|
"--request",
|
|
873
661
|
request,
|
|
874
662
|
"--workspace",
|
|
@@ -876,56 +664,74 @@ function runDelegate(api, params, toolContext) {
|
|
|
876
664
|
"--background"
|
|
877
665
|
];
|
|
878
666
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
879
|
-
pushOptional(args, "--session", agentSession);
|
|
880
|
-
pushOptional(args, "--all-proxy", allProxy);
|
|
881
|
-
pushOptional(args, "--model", model);
|
|
882
667
|
pushOptional(args, "--openclaw-session", openclawSession);
|
|
883
668
|
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
884
669
|
pushOptional(args, "--gateway-session", openclawSession);
|
|
885
670
|
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
886
|
-
pushOptional(args, "--callback-command", stringValue(config.callbackCommand));
|
|
887
|
-
pushOptional(args, "--soft-limit", numberString(params.softLimit) ?? numberString(config.softLimit));
|
|
888
|
-
pushOptional(args, "--hard-limit", numberString(params.hardLimit) ?? numberString(config.hardLimit));
|
|
889
671
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
890
672
|
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const conversationId = parsed.conversation?.conversation_id;
|
|
904
|
-
const statePath = parsed.paths?.statePath;
|
|
905
|
-
const logPath = parsed.paths?.logPath;
|
|
673
|
+
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
|
|
674
|
+
const parsed = await runCliAsync(api, args, { cwd: workspace });
|
|
675
|
+
const conversationId = parsed.conversation?.conversation_id ??
|
|
676
|
+
parsed.conversation_id;
|
|
677
|
+
const statePath = parsed.conversation?.state_path ??
|
|
678
|
+
parsed.paths?.statePath;
|
|
679
|
+
const logPath = parsed.conversation?.event_log_path ??
|
|
680
|
+
parsed.paths?.logPath;
|
|
681
|
+
const submissionUncertain = parsed.submission_outcome === "uncertain";
|
|
682
|
+
const submissionAborted = parsed.submission_outcome === "aborted";
|
|
683
|
+
const agent = stringValue(parsed.conversation?.executor?.kind) ??
|
|
684
|
+
stringValue(parsed.agent);
|
|
906
685
|
return {
|
|
907
|
-
status:
|
|
908
|
-
|
|
686
|
+
status: submissionUncertain
|
|
687
|
+
? "submission_uncertain"
|
|
688
|
+
: submissionAborted
|
|
689
|
+
? "submission_aborted"
|
|
690
|
+
: "async_pending",
|
|
691
|
+
delegation_status: submissionUncertain
|
|
692
|
+
? "uncertain"
|
|
693
|
+
: submissionAborted
|
|
694
|
+
? "aborted"
|
|
695
|
+
: "delegated",
|
|
909
696
|
conversation_id: conversationId,
|
|
910
697
|
conversation_status: parsed.conversation?.status,
|
|
911
698
|
state_path: statePath,
|
|
912
699
|
event_log_path: logPath,
|
|
913
700
|
agent,
|
|
914
701
|
executor: parsed.conversation?.executor,
|
|
915
|
-
session: parsed.conversation?.executor?.session ??
|
|
916
|
-
|
|
702
|
+
session: parsed.conversation?.executor?.session ??
|
|
703
|
+
parsed.terminal_control?.target,
|
|
917
704
|
openclaw_session: openclawSession,
|
|
918
705
|
launched: parsed.launched === true,
|
|
706
|
+
replayed: parsed.replayed === true,
|
|
919
707
|
background: parsed.background === true,
|
|
920
|
-
pid: parsed.pid ?? null,
|
|
708
|
+
pid: parsed.pid ?? parsed.terminal_control?.panePid ?? null,
|
|
921
709
|
callback_method: CALLBACK_METHOD,
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
710
|
+
...(submissionUncertain
|
|
711
|
+
? {
|
|
712
|
+
submission_outcome: "uncertain",
|
|
713
|
+
do_not_retry: true,
|
|
714
|
+
reason: parsed.reason,
|
|
715
|
+
openclaw_next_action: parsed.openclaw_next_action,
|
|
716
|
+
note: "AKK could not prove whether tmux accepted Enter. Do not retry automatically; inspect the durable AKK conversation and shared terminal."
|
|
717
|
+
}
|
|
718
|
+
: submissionAborted
|
|
719
|
+
? {
|
|
720
|
+
submission_outcome: "aborted",
|
|
721
|
+
safe_to_retry: true,
|
|
722
|
+
reason: parsed.reason,
|
|
723
|
+
openclaw_next_action: parsed.openclaw_next_action,
|
|
724
|
+
note: "AKK stopped before sending tmux input. The request was not submitted and may be retried."
|
|
725
|
+
}
|
|
726
|
+
: {
|
|
727
|
+
openclaw_next_action: {
|
|
728
|
+
action: "yield",
|
|
729
|
+
reason: "The coding agent is working in the shared tmux terminal. End this OpenClaw turn now and wait for an Agent Knock Knock callback.",
|
|
730
|
+
do_not: "Do not poll terminal internals while waiting. Follow-up communication must use Agent Knock Knock tools so the same shared terminal remains authoritative.",
|
|
731
|
+
expected_callback: "The callback will be injected and scheduled into this OpenClaw session by the agent-knock-knock.callback Gateway method."
|
|
732
|
+
},
|
|
733
|
+
note: "The task was sent to the shared tmux terminal. OpenClaw should yield now and wait for the scheduled callback turn."
|
|
734
|
+
})
|
|
929
735
|
};
|
|
930
736
|
}
|
|
931
737
|
function registerCliTool(api, { name, description, parameters, buildArgs }) {
|
|
@@ -957,17 +763,6 @@ function toolLabel(name) {
|
|
|
957
763
|
.join(" ");
|
|
958
764
|
return `AKK ${action || "Tool"}`;
|
|
959
765
|
}
|
|
960
|
-
function buildRecoveryArgs(api, command, params) {
|
|
961
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
962
|
-
const overrides = resolveConversationOverrides(params, config);
|
|
963
|
-
const args = [command, "--conversation", requiredString(params.conversation_id, "conversation_id"), "--background"];
|
|
964
|
-
pushOptional(args, "--session", stringValue(params.session));
|
|
965
|
-
pushOptional(args, "--all-proxy", overrides.allProxy);
|
|
966
|
-
pushOptional(args, "--model", overrides.model);
|
|
967
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
968
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
969
|
-
return args;
|
|
970
|
-
}
|
|
971
766
|
function runCli(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false } = {}) {
|
|
972
767
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
973
768
|
const binPath = stringValue(config.binPath) ?? defaultBinPath;
|
|
@@ -1142,7 +937,13 @@ function tryAutoApproveCallback({ api, message, conversationId, statePath }) {
|
|
|
1142
937
|
message,
|
|
1143
938
|
policy: config.autoApprove,
|
|
1144
939
|
statePath,
|
|
1145
|
-
execute: (args) =>
|
|
940
|
+
execute: (args) => {
|
|
941
|
+
return runCli(api, [
|
|
942
|
+
...args,
|
|
943
|
+
"--workspace",
|
|
944
|
+
pluginWorkspace(config)
|
|
945
|
+
]);
|
|
946
|
+
}
|
|
1146
947
|
});
|
|
1147
948
|
if (result) {
|
|
1148
949
|
api.logger.info?.(`agent-knock-knock approval policy for ${conversationId ?? "unknown"}: ${result.action} (${result.reason})`);
|
|
@@ -1205,13 +1006,15 @@ function formatDoneShortcuts(conversationId) {
|
|
|
1205
1006
|
"",
|
|
1206
1007
|
"[AKK convenience commands]",
|
|
1207
1008
|
"When summarizing this result to the user, include these short next-step commands:",
|
|
1208
|
-
"- `AKK list` lists open AKK
|
|
1209
|
-
|
|
1210
|
-
`- \`AKK status ${conversationId}\` shows this
|
|
1211
|
-
|
|
1212
|
-
`- \`AKK close ${conversationId}\` closes this AKK session.`
|
|
1009
|
+
"- `AKK list` lists live shared terminals and open AKK tasks.",
|
|
1010
|
+
"- Use the matching `@short-ref` from `AKK list`, then `AKK @short-ref: <message>` to continue in the same shared terminal.",
|
|
1011
|
+
`- \`AKK status ${conversationId}\` shows this task record.`,
|
|
1012
|
+
"- AKK never starts or closes the coding agent or tmux pane."
|
|
1213
1013
|
].join("\n");
|
|
1214
1014
|
}
|
|
1015
|
+
function pluginWorkspace(config) {
|
|
1016
|
+
return stringValue(config.workspace) ?? process.cwd();
|
|
1017
|
+
}
|
|
1215
1018
|
function pushOptional(args, flag, value) {
|
|
1216
1019
|
if (value !== undefined && value !== "") {
|
|
1217
1020
|
args.push(flag, value);
|
|
@@ -1223,18 +1026,6 @@ function isRecord(value) {
|
|
|
1223
1026
|
function stringValue(value) {
|
|
1224
1027
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
1225
1028
|
}
|
|
1226
|
-
function firstStringForKeys(source, keys) {
|
|
1227
|
-
if (!isRecord(source)) {
|
|
1228
|
-
return undefined;
|
|
1229
|
-
}
|
|
1230
|
-
for (const key of keys) {
|
|
1231
|
-
const value = stringValue(source[key]);
|
|
1232
|
-
if (value) {
|
|
1233
|
-
return value;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
return undefined;
|
|
1237
|
-
}
|
|
1238
1029
|
function numberString(value) {
|
|
1239
1030
|
return typeof value === "number" && Number.isFinite(value) ? String(value) : undefined;
|
|
1240
1031
|
}
|
|
@@ -1256,7 +1047,7 @@ function parseJson(text) {
|
|
|
1256
1047
|
return JSON.parse(text);
|
|
1257
1048
|
}
|
|
1258
1049
|
catch (error) {
|
|
1259
|
-
throw new Error(`agent-knock-knock
|
|
1050
|
+
throw new Error(`agent-knock-knock CLI returned invalid JSON: ${error.message}`);
|
|
1260
1051
|
}
|
|
1261
1052
|
}
|
|
1262
1053
|
function cleanError(text) {
|