@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +17 -565
  2. package/README.md +100 -134
  3. package/dist/src/agent-session-provider.d.ts +1 -2
  4. package/dist/src/approval-policy.d.ts +1 -1
  5. package/dist/src/approval-policy.js +1 -1
  6. package/dist/src/approval-policy.js.map +1 -1
  7. package/dist/src/claude-terminal-agent-adapter.d.ts +2 -17
  8. package/dist/src/claude-terminal-agent-adapter.js +4 -489
  9. package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
  10. package/dist/src/cli.js +1736 -3893
  11. package/dist/src/cli.js.map +1 -1
  12. package/dist/src/codex-local-session-provider.d.ts +1 -2
  13. package/dist/src/codex-local-session-provider.js +1 -9
  14. package/dist/src/codex-local-session-provider.js.map +1 -1
  15. package/dist/src/codex-session-provider.d.ts +1 -8
  16. package/dist/src/codex-session-provider.js +0 -56
  17. package/dist/src/codex-session-provider.js.map +1 -1
  18. package/dist/src/codex-terminal-agent-adapter.d.ts +1 -1
  19. package/dist/src/doctor-capabilities.d.ts +5 -13
  20. package/dist/src/doctor-capabilities.js +15 -60
  21. package/dist/src/doctor-capabilities.js.map +1 -1
  22. package/dist/src/executors.d.ts +5 -51
  23. package/dist/src/executors.js +4 -68
  24. package/dist/src/executors.js.map +1 -1
  25. package/dist/src/openclaw-doctor.d.ts +0 -1
  26. package/dist/src/openclaw-doctor.js +0 -7
  27. package/dist/src/openclaw-doctor.js.map +1 -1
  28. package/dist/src/openclaw-plugin-helpers.d.ts +5 -13
  29. package/dist/src/openclaw-plugin-helpers.js +91 -82
  30. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  31. package/dist/src/openclaw-plugin.js +272 -481
  32. package/dist/src/openclaw-plugin.js.map +1 -1
  33. package/dist/src/protocol.d.ts +1 -5
  34. package/dist/src/protocol.js +1 -1
  35. package/dist/src/protocol.js.map +1 -1
  36. package/dist/src/session-selector.js +1 -1
  37. package/dist/src/session-selector.js.map +1 -1
  38. package/dist/src/terminal-agent-adapter.d.ts +3 -17
  39. package/dist/src/terminal-agent-adapter.js.map +1 -1
  40. package/dist/src/terminal-agent-bridge.d.ts +3 -3
  41. package/dist/src/terminal-agent-bridge.js +0 -75
  42. package/dist/src/terminal-agent-bridge.js.map +1 -1
  43. package/dist/src/transcript.js +2 -3
  44. package/dist/src/transcript.js.map +1 -1
  45. package/docs/quickstart-tmux.md +15 -11
  46. package/openclaw.plugin.json +4 -100
  47. package/package.json +7 -15
  48. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +82 -149
  49. package/dist/src/acpx-output.d.ts +0 -18
  50. package/dist/src/acpx-output.js +0 -223
  51. package/dist/src/acpx-output.js.map +0 -1
  52. package/dist/src/bootstrap.d.ts +0 -8
  53. package/dist/src/bootstrap.js +0 -45
  54. package/dist/src/bootstrap.js.map +0 -1
  55. package/dist/src/claude-hook-installer.d.ts +0 -65
  56. package/dist/src/claude-hook-installer.js +0 -410
  57. package/dist/src/claude-hook-installer.js.map +0 -1
  58. package/dist/src/claude-hook-protocol.d.ts +0 -89
  59. package/dist/src/claude-hook-protocol.js +0 -243
  60. package/dist/src/claude-hook-protocol.js.map +0 -1
  61. package/dist/src/claude-hook-store.d.ts +0 -247
  62. package/dist/src/claude-hook-store.js +0 -1075
  63. package/dist/src/claude-hook-store.js.map +0 -1
  64. package/dist/src/session-takeover-planner.d.ts +0 -37
  65. package/dist/src/session-takeover-planner.js +0 -57
  66. package/dist/src/session-takeover-planner.js.map +0 -1
  67. package/docs/quickstart-managed-acpx.md +0 -43
  68. package/scripts/smoke-acpx.js +0 -139
  69. 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, parseLeadingExecutorAlias } from "./executors.js";
5
- import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, formatAkkListCommandResult, parseAkkCommand, resolvePluginStoreDir, resolveConversationOverrides } from "./openclaw-plugin-helpers.js";
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 delegateParameters = {
9
+ const sendParameters = {
10
10
  type: "object",
11
11
  additionalProperties: false,
12
12
  required: ["request"],
13
13
  properties: {
14
- agent: {
14
+ selector: {
15
15
  type: "string",
16
- enum: EXECUTOR_KINDS,
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: "Implementation task for the coding agent."
20
+ description: "New task or follow-up message for the coding agent."
22
21
  },
23
- workspace: {
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
- description: "OpenClaw session label recorded in the protocol state."
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 delegated tasks and skip native/terminal active discovery."
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
- codexHome: {
142
+ expected_message_id: {
332
143
  type: "string",
333
- description: "Optional Codex home directory. Defaults to ~/.codex."
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) delegates OpenClaw coding work to local Codex, Claude, or Cursor agents. Use this for AKK, akk, Agent Knock Knock, Codex delegation, Claude delegation, Cursor delegation, task listing, follow-up messages, status, stalled terminal-monitor renewal, recovery, cancel requests, and close requests. Default delegation target comes from plugin config defaultAgent and falls back to Codex when unset; explicit user agent requests override it.",
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: "Delegate coding work to Agent Knock Knock, list tasks, send follow-ups, cancel running work, and close tasks.",
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> to delegate coding work to Agent Knock Knock. /akk uses the plugin-configured defaultAgent and falls back to Codex when unset; use /akk claude <task>, /akk cursor <task>, or /akk codex <task> when the user explicitly requests that agent."
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 Agent Knock Knock work and local active coding-agent sessions. Use this for AKK list, current AKK tasks, native Codex work, terminal-controlled Codex or Claude Code work, or asking which coding-agent sessions are open. The result separates delegated tasks, native processes, and terminal-controlled sessions; terminal entries include approval state when available.",
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, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
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
- registerCliTool(api, {
248
+ api.registerTool((_toolContext) => ({
249
+ label: "AKK Status",
465
250
  name: "agent_knock_knock_status",
466
- description: "Get detailed status for one Agent Knock Knock coding-agent task or terminal-controlled session. For terminal-controlled ids from AKK list, this is the only read-result/screen-inspection command: AKK captures the terminal pane internally and returns terminal_screen.",
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
- buildArgs: (params) => {
469
- const args = ["status", "--conversation", requiredString(params.conversation_id, "conversation_id")];
470
- pushOptional(args, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
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
- registerCliTool(api, {
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 message, follow-up, or new task to an existing open Agent Knock Knock or terminal-controlled coding-agent session from AKK list, including Codex or Claude Code in tmux. Do not start a new delegate for those requests. This is asynchronous: after acceptance, end the OpenClaw turn and wait for the AKK callback or a later explicit status request.",
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
- buildArgs: (params, toolContext) => {
497
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
498
- const openclawSession = stringValue(toolContext?.sessionKey) ??
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(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
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, and the task closes only after delivery succeeds.",
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: "Cancel an existing Agent Knock Knock Codex, Claude, or Cursor task. Delegated sessions use cooperative ACPX cancellation. Terminal-controlled Claude sends Escape to dismiss or interrupt the current screen flow; other adapters use their declared interrupt keys. The underlying tmux pane remains open.",
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, "--all-proxy", overrides.allProxy);
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 Agent Knock Knock coding-agent task without terminating the underlying ACPX session. Use this for AKK close requests.",
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, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
589
- return args;
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 { text: formatDelegateCommandResult(result) };
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 { text: formatSendCommandResult(result) };
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
- `mode: ${result.selected_mode ?? "all"}`,
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 formatRecoveryCommandResult(result) {
547
+ function formatApproveCommandResult(result) {
826
548
  const conversation = result.conversation ?? {};
827
549
  return [
828
- "AKK recovery started.",
829
- `conversation: ${conversation.conversation_id ?? "unknown"}`,
830
- `agent: ${result.executor?.kind ?? conversation.executor?.kind ?? "unknown"}`,
831
- `session: ${result.executor?.session ?? conversation.executor?.session ?? "unknown"}`,
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 runDelegate(api, params, toolContext) {
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
- binPath,
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
- const spawned = spawnSync(process.execPath, args, {
892
- encoding: "utf8",
893
- maxBuffer: 1024 * 1024 * 10,
894
- cwd: workspace
895
- });
896
- if (spawned.error) {
897
- throw new Error(`agent-knock-knock delegate failed to start: ${spawned.error.message}`);
898
- }
899
- if (spawned.status !== 0) {
900
- throw new Error(cleanError(spawned.stderr || spawned.stdout || `agent-knock-knock delegate exited with status ${spawned.status}`));
901
- }
902
- const parsed = parseJson(spawned.stdout);
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: "async_pending",
908
- delegation_status: "delegated",
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 ?? parsed.conversation?.claude_session,
916
- claude_session: parsed.conversation?.claude_session,
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
- openclaw_next_action: {
923
- action: "yield",
924
- reason: "The delegated coding agent is running asynchronously. End this OpenClaw turn now and wait for an Agent Knock Knock callback.",
925
- do_not: "Do not inspect event logs, process lists, session internals, files, stdout, or stderr while waiting. Follow-up communication must only use structured callbacks from agent-knock-knock.",
926
- expected_callback: "The callback will be injected and scheduled into this OpenClaw session by the agent-knock-knock.callback Gateway method."
927
- },
928
- note: "The coding agent was launched in the background. This is an async delegation; OpenClaw should yield now and wait for the scheduled callback turn."
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) => runCli(api, 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 sessions.",
1209
- `- \`AKK send ${conversationId}: <message>\` sends a follow-up to this same AKK session.`,
1210
- `- \`AKK status ${conversationId}\` shows this session status.`,
1211
- `- \`AKK cancel ${conversationId}\` requests cancellation of current running work without closing this AKK session.`,
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 delegate returned invalid JSON: ${error.message}`);
1050
+ throw new Error(`agent-knock-knock CLI returned invalid JSON: ${error.message}`);
1260
1051
  }
1261
1052
  }
1262
1053
  function cleanError(text) {