@scotthuang/agent-knock-knock 0.3.2 → 0.4.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 +9 -571
- package/README.md +97 -123
- 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 +1489 -3406
- 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 +1 -1
- package/dist/src/openclaw-doctor.js +1 -1
- package/dist/src/openclaw-doctor.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +5 -8
- package/dist/src/openclaw-plugin-helpers.js +57 -40
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin.js +138 -248
- 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 +7 -7
- package/openclaw.plugin.json +4 -83
- package/package.json +7 -15
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +94 -147
- 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
|
@@ -2,7 +2,7 @@ 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
4
|
import { EXECUTOR_KINDS, executorDefinitionForKind, parseLeadingExecutorAlias } from "./executors.js";
|
|
5
|
-
import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, formatAkkListCommandResult, parseAkkCommand, resolvePluginStoreDir
|
|
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));
|
|
@@ -24,58 +24,6 @@ const delegateParameters = {
|
|
|
24
24
|
type: "string",
|
|
25
25
|
description: "Workspace path for the selected coding agent. Defaults to plugin config or the Gateway process directory."
|
|
26
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: {
|
|
68
|
-
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."
|
|
78
|
-
},
|
|
79
27
|
idleTimeoutMinutes: {
|
|
80
28
|
type: "number",
|
|
81
29
|
description: "Minutes an idle AKK session remains open before lazy cleanup closes it."
|
|
@@ -83,6 +31,11 @@ const delegateParameters = {
|
|
|
83
31
|
agentTimeoutMinutes: {
|
|
84
32
|
type: "number",
|
|
85
33
|
description: "Callback timeout in minutes; terminal bridge tasks treat it as an inactivity timeout."
|
|
34
|
+
},
|
|
35
|
+
agentHardTimeoutMinutes: {
|
|
36
|
+
type: "number",
|
|
37
|
+
exclusiveMinimum: 0,
|
|
38
|
+
description: "Maximum terminal monitor lifetime in minutes."
|
|
86
39
|
}
|
|
87
40
|
}
|
|
88
41
|
};
|
|
@@ -201,12 +154,6 @@ const sendParameters = {
|
|
|
201
154
|
type: "string",
|
|
202
155
|
enum: ["answer", "task", "control", "error"]
|
|
203
156
|
},
|
|
204
|
-
allProxy: {
|
|
205
|
-
type: "string"
|
|
206
|
-
},
|
|
207
|
-
model: {
|
|
208
|
-
type: "string"
|
|
209
|
-
},
|
|
210
157
|
idleTimeoutMinutes: {
|
|
211
158
|
type: "number"
|
|
212
159
|
},
|
|
@@ -230,31 +177,6 @@ const cancelParameters = {
|
|
|
230
177
|
type: "string",
|
|
231
178
|
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
179
|
},
|
|
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
180
|
idleTimeoutMinutes: {
|
|
259
181
|
type: "number"
|
|
260
182
|
}
|
|
@@ -270,6 +192,10 @@ const closeParameters = {
|
|
|
270
192
|
},
|
|
271
193
|
reason: {
|
|
272
194
|
type: "string"
|
|
195
|
+
},
|
|
196
|
+
expected_message_id: {
|
|
197
|
+
type: "string",
|
|
198
|
+
description: "Required only to clear an orphaned terminal dispatch shown by AKK list. Must exactly match that entry's current message_id."
|
|
273
199
|
}
|
|
274
200
|
}
|
|
275
201
|
};
|
|
@@ -289,25 +215,13 @@ const agentTakeoverParameters = {
|
|
|
289
215
|
},
|
|
290
216
|
strategy: {
|
|
291
217
|
type: "string",
|
|
292
|
-
enum: ["
|
|
293
|
-
description: "
|
|
218
|
+
enum: ["terminal_control"],
|
|
219
|
+
description: "Attach to the existing Codex process through its shared tmux terminal."
|
|
294
220
|
},
|
|
295
221
|
createConversation: {
|
|
296
222
|
type: "boolean",
|
|
297
223
|
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
224
|
},
|
|
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
225
|
confirmTerminal: {
|
|
312
226
|
type: "boolean",
|
|
313
227
|
description: "Only for strategy=terminal_control. When true, AKK attaches to the exact terminalTarget after the user confirms that terminal pane."
|
|
@@ -320,29 +234,9 @@ const agentTakeoverParameters = {
|
|
|
320
234
|
type: "string",
|
|
321
235
|
description: "Optional user-visible request label stored on the created AKK conversation."
|
|
322
236
|
},
|
|
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
|
-
},
|
|
331
237
|
codexHome: {
|
|
332
238
|
type: "string",
|
|
333
239
|
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."
|
|
346
240
|
}
|
|
347
241
|
}
|
|
348
242
|
};
|
|
@@ -364,7 +258,7 @@ const approveParameters = {
|
|
|
364
258
|
const plugin = definePluginEntry({
|
|
365
259
|
id: "agent-knock-knock",
|
|
366
260
|
name: "Agent Knock Knock",
|
|
367
|
-
description: "Agent Knock Knock (AKK/akk)
|
|
261
|
+
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
262
|
register(api) {
|
|
369
263
|
api.registerGatewayMethod(CALLBACK_METHOD, async ({ params, respond }) => {
|
|
370
264
|
try {
|
|
@@ -414,17 +308,17 @@ const plugin = definePluginEntry({
|
|
|
414
308
|
default: "AKK is handling the request..."
|
|
415
309
|
},
|
|
416
310
|
agentPromptGuidance: [
|
|
417
|
-
"Use /akk <task> to
|
|
311
|
+
"Use /akk <task> to send work through a uniquely matching idle tmux terminal. /akk uses the plugin-configured defaultAgent and falls back to Codex; use /akk claude <task> or /akk codex <task> when the user explicitly requests that agent."
|
|
418
312
|
],
|
|
419
313
|
handler: async (ctx) => handleAkkCommand(api, ctx)
|
|
420
314
|
});
|
|
421
315
|
api.registerTool((toolContext) => ({
|
|
422
316
|
label: "AKK Delegate",
|
|
423
317
|
name: "agent_knock_knock_delegate",
|
|
424
|
-
description: "
|
|
318
|
+
description: "Send an implementation task to a uniquely matching idle Codex or Claude Code tmux terminal. AKK never starts a hidden coding-agent runtime; no match or an ambiguous match fails closed with setup guidance. Omit agent to use the configured default, falling back to Codex.",
|
|
425
319
|
parameters: delegateParameters,
|
|
426
320
|
async execute(_toolCallId, params) {
|
|
427
|
-
const result = runDelegate(api, params, toolContext);
|
|
321
|
+
const result = await runDelegate(api, params, toolContext);
|
|
428
322
|
return {
|
|
429
323
|
content: [
|
|
430
324
|
{
|
|
@@ -496,7 +390,6 @@ const plugin = definePluginEntry({
|
|
|
496
390
|
buildArgs: (params, toolContext) => {
|
|
497
391
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
498
392
|
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
499
|
-
stringValue(config.openclawSession) ??
|
|
500
393
|
"agent:main:main";
|
|
501
394
|
const args = [
|
|
502
395
|
"send",
|
|
@@ -507,9 +400,6 @@ const plugin = definePluginEntry({
|
|
|
507
400
|
"--background"
|
|
508
401
|
];
|
|
509
402
|
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
403
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
514
404
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
515
405
|
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
|
|
@@ -518,9 +408,6 @@ const plugin = definePluginEntry({
|
|
|
518
408
|
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
519
409
|
pushOptional(args, "--gateway-session", openclawSession);
|
|
520
410
|
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
411
|
return args;
|
|
525
412
|
}
|
|
526
413
|
});
|
|
@@ -549,7 +436,7 @@ const plugin = definePluginEntry({
|
|
|
549
436
|
});
|
|
550
437
|
registerCliTool(api, {
|
|
551
438
|
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
|
|
439
|
+
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
440
|
parameters: retryCallbackParameters,
|
|
554
441
|
buildArgs: (params) => {
|
|
555
442
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
@@ -560,44 +447,35 @@ const plugin = definePluginEntry({
|
|
|
560
447
|
});
|
|
561
448
|
registerCliTool(api, {
|
|
562
449
|
name: "agent_knock_knock_cancel",
|
|
563
|
-
description: "
|
|
450
|
+
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
451
|
parameters: cancelParameters,
|
|
565
452
|
buildArgs: (params) => {
|
|
566
453
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
567
|
-
const overrides = resolveConversationOverrides(params, config);
|
|
568
454
|
const args = ["cancel", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
569
|
-
pushOptional(args, "--all-proxy", overrides.allProxy);
|
|
570
455
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
571
456
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
572
457
|
return args;
|
|
573
458
|
}
|
|
574
459
|
});
|
|
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
460
|
registerCliTool(api, {
|
|
582
461
|
name: "agent_knock_knock_close",
|
|
583
|
-
description: "Close an
|
|
462
|
+
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
463
|
parameters: closeParameters,
|
|
585
464
|
buildArgs: (params) => {
|
|
586
465
|
const args = ["close", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
587
466
|
pushOptional(args, "--reason", stringValue(params.reason));
|
|
467
|
+
pushOptional(args, "--expected-message-id", stringValue(params.expected_message_id));
|
|
588
468
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
|
|
589
469
|
return args;
|
|
590
470
|
}
|
|
591
471
|
});
|
|
592
472
|
registerCliTool(api, {
|
|
593
473
|
name: "agent_knock_knock_agent_takeover",
|
|
594
|
-
description: "Build or execute a
|
|
474
|
+
description: "Build or execute a terminal-control attachment plan for an existing Codex session running inside tmux. The first call is side-effect-free; attach only after the user confirms the exact tmux target.",
|
|
595
475
|
parameters: agentTakeoverParameters,
|
|
596
476
|
buildArgs: (params, toolContext) => {
|
|
597
477
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
598
478
|
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
599
|
-
stringValue(config.openclawSession) ??
|
|
600
|
-
stringValue(params.openclawSession) ??
|
|
601
479
|
"agent:main:main";
|
|
602
480
|
const args = [
|
|
603
481
|
"agent",
|
|
@@ -612,32 +490,18 @@ const plugin = definePluginEntry({
|
|
|
612
490
|
if (params.createConversation === true) {
|
|
613
491
|
args.push("--create-conversation");
|
|
614
492
|
}
|
|
615
|
-
if (params.confirmTerminate === true) {
|
|
616
|
-
args.push("--confirm-terminate");
|
|
617
|
-
}
|
|
618
|
-
if (params.allowCwdOnly === true) {
|
|
619
|
-
args.push("--allow-cwd-only");
|
|
620
|
-
}
|
|
621
493
|
if (params.confirmTerminal === true) {
|
|
622
494
|
args.push("--confirm-terminal");
|
|
623
495
|
}
|
|
624
|
-
pushOptional(args, "--expected-pid", numberString(params.expectedPid));
|
|
625
496
|
pushOptional(args, "--terminal-target", stringValue(params.terminalTarget));
|
|
626
497
|
pushOptional(args, "--request", stringValue(params.request));
|
|
627
|
-
pushOptional(args, "--fork-summary", stringValue(params.forkSummary));
|
|
628
498
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
629
499
|
pushOptional(args, "--openclaw-session", openclawSession);
|
|
630
500
|
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
631
501
|
pushOptional(args, "--gateway-session", openclawSession);
|
|
632
502
|
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
503
|
pushOptional(args, "--idle-timeout-minutes", numberString(config.idleTimeoutMinutes));
|
|
637
504
|
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
505
|
return args;
|
|
642
506
|
}
|
|
643
507
|
});
|
|
@@ -651,13 +515,16 @@ async function handleAkkCommand(api, ctx) {
|
|
|
651
515
|
return { text: akkUsageText() };
|
|
652
516
|
}
|
|
653
517
|
if (parsed.action === "delegate") {
|
|
654
|
-
const result = runDelegate(api, {
|
|
518
|
+
const result = await runDelegate(api, {
|
|
655
519
|
agent: parsed.agent,
|
|
656
520
|
request: parsed.request
|
|
657
521
|
}, {
|
|
658
522
|
sessionKey: ctx.sessionKey
|
|
659
523
|
});
|
|
660
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
text: formatDelegateCommandResult(result),
|
|
526
|
+
isError: result.status !== "async_pending"
|
|
527
|
+
};
|
|
661
528
|
}
|
|
662
529
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
663
530
|
const args = buildAkkCommandCliArgs(parsed, config, {
|
|
@@ -684,15 +551,18 @@ async function handleAkkCommand(api, ctx) {
|
|
|
684
551
|
case "describe":
|
|
685
552
|
return { text: formatDescribeCommandResult(result) };
|
|
686
553
|
case "send":
|
|
687
|
-
return {
|
|
554
|
+
return {
|
|
555
|
+
text: formatSendCommandResult(result),
|
|
556
|
+
isError: ["uncertain", "aborted"].includes(String(result.submission_outcome ?? ""))
|
|
557
|
+
};
|
|
558
|
+
case "approve":
|
|
559
|
+
return { text: formatApproveCommandResult(result) };
|
|
688
560
|
case "renew":
|
|
689
561
|
return { text: formatRenewCommandResult(result) };
|
|
690
562
|
case "retry-callback":
|
|
691
563
|
return { text: formatRetryCallbackCommandResult(result) };
|
|
692
564
|
case "cancel":
|
|
693
565
|
return { text: formatCancelCommandResult(result) };
|
|
694
|
-
case "recover":
|
|
695
|
-
return { text: formatRecoveryCommandResult(result) };
|
|
696
566
|
case "close":
|
|
697
567
|
return { text: formatCloseCommandResult(result) };
|
|
698
568
|
}
|
|
@@ -707,6 +577,22 @@ async function handleAkkCommand(api, ctx) {
|
|
|
707
577
|
}
|
|
708
578
|
function formatDelegateCommandResult(result) {
|
|
709
579
|
const agent = executorDisplayName(result.agent);
|
|
580
|
+
if (result.status === "submission_uncertain") {
|
|
581
|
+
return [
|
|
582
|
+
`AKK could not prove whether ${agent} received the terminal task.`,
|
|
583
|
+
`conversation: ${result.conversation_id ?? "unknown"}`,
|
|
584
|
+
`session: ${result.session ?? "unknown"}`,
|
|
585
|
+
"next: do not retry automatically; inspect AKK status and the named tmux pane."
|
|
586
|
+
].join("\n");
|
|
587
|
+
}
|
|
588
|
+
if (result.status === "submission_aborted") {
|
|
589
|
+
return [
|
|
590
|
+
`AKK stopped before sending the terminal task to ${agent}.`,
|
|
591
|
+
`conversation: ${result.conversation_id ?? "unknown"}`,
|
|
592
|
+
`session: ${result.session ?? "unknown"}`,
|
|
593
|
+
"next: no tmux input was sent, so this request may be retried."
|
|
594
|
+
].join("\n");
|
|
595
|
+
}
|
|
710
596
|
return [
|
|
711
597
|
`AKK delegated the task to ${agent}.`,
|
|
712
598
|
`conversation: ${result.conversation_id ?? "unknown"}`,
|
|
@@ -741,7 +627,6 @@ function formatStatusCommandResult(result) {
|
|
|
741
627
|
function formatDoctorCommandResult(result) {
|
|
742
628
|
const capabilities = isRecord(result.capabilities) ? result.capabilities : {};
|
|
743
629
|
const tmux = isRecord(capabilities.tmux) ? capabilities.tmux : {};
|
|
744
|
-
const acpx = isRecord(capabilities.acpx) ? capabilities.acpx : {};
|
|
745
630
|
const openclaw = isRecord(result.openclaw) ? result.openclaw : {};
|
|
746
631
|
const checks = Array.isArray(openclaw.checks) ? openclaw.checks : [];
|
|
747
632
|
const failures = checks
|
|
@@ -752,9 +637,7 @@ function formatDoctorCommandResult(result) {
|
|
|
752
637
|
: []))].slice(0, 3);
|
|
753
638
|
return [
|
|
754
639
|
`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"}`,
|
|
640
|
+
`tmux: ${tmux.status ?? "unknown"}`,
|
|
758
641
|
`OpenClaw package: ${openclaw.package_ready === true ? "ready" : "not ready"}`,
|
|
759
642
|
`Gateway: ${openclaw.gateway_ready === true ? "healthy" : "unavailable"}`,
|
|
760
643
|
...(failures.length > 0 ? [`check: ${failures.join(", ")}`] : []),
|
|
@@ -796,6 +679,22 @@ function formatSendCommandResult(result) {
|
|
|
796
679
|
const conversationId = conversation.conversation_id ?? result.conversation_id ?? "unknown";
|
|
797
680
|
const status = conversation.status ?? result.status ?? "unknown";
|
|
798
681
|
const nextAction = isRecord(result.openclaw_next_action) ? result.openclaw_next_action : undefined;
|
|
682
|
+
if (result.submission_outcome === "uncertain") {
|
|
683
|
+
return [
|
|
684
|
+
"AKK terminal submission outcome is uncertain.",
|
|
685
|
+
`conversation: ${conversationId}`,
|
|
686
|
+
`status: ${result.status ?? status}`,
|
|
687
|
+
"next: do not retry automatically; inspect AKK status and the named tmux pane."
|
|
688
|
+
].join("\n");
|
|
689
|
+
}
|
|
690
|
+
if (result.submission_outcome === "aborted") {
|
|
691
|
+
return [
|
|
692
|
+
"AKK terminal submission was aborted before tmux input.",
|
|
693
|
+
`conversation: ${conversationId}`,
|
|
694
|
+
`status: ${result.status ?? status}`,
|
|
695
|
+
"next: this request was not sent and may be retried."
|
|
696
|
+
].join("\n");
|
|
697
|
+
}
|
|
799
698
|
const lines = [
|
|
800
699
|
"AKK follow-up sent.",
|
|
801
700
|
`conversation: ${conversationId}`,
|
|
@@ -822,17 +721,30 @@ function formatCancelCommandResult(result) {
|
|
|
822
721
|
`status: ${conversation.status ?? (result.cancel_requested === true ? "cancel requested" : "not cancelled")}`
|
|
823
722
|
].join("\n");
|
|
824
723
|
}
|
|
825
|
-
function
|
|
724
|
+
function formatApproveCommandResult(result) {
|
|
826
725
|
const conversation = result.conversation ?? {};
|
|
827
726
|
return [
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
`
|
|
832
|
-
`status: ${conversation.status ?? "unknown"}
|
|
727
|
+
result.approved === true
|
|
728
|
+
? "AKK approved the current terminal request."
|
|
729
|
+
: "AKK did not approve the terminal request.",
|
|
730
|
+
`conversation: ${conversation.conversation_id ?? result.conversation_id ?? "unknown"}`,
|
|
731
|
+
`status: ${conversation.status ?? "unknown"}`,
|
|
732
|
+
...(result.reason ? [`reason: ${result.reason}`] : [])
|
|
833
733
|
].join("\n");
|
|
834
734
|
}
|
|
835
735
|
function formatCloseCommandResult(result) {
|
|
736
|
+
if (result.source === "terminal_control" &&
|
|
737
|
+
result.terminal_dispatch_resolved === true) {
|
|
738
|
+
const terminalControl = isRecord(result.terminal_control)
|
|
739
|
+
? result.terminal_control
|
|
740
|
+
: {};
|
|
741
|
+
return [
|
|
742
|
+
"AKK cleared the unresolved terminal dispatch fence.",
|
|
743
|
+
`terminal: ${terminalControl.target ?? "unknown"}`,
|
|
744
|
+
`previous owner: ${result.owner_conversation_id ?? "unknown"}`,
|
|
745
|
+
"The coding agent and tmux pane remain open."
|
|
746
|
+
].join("\n");
|
|
747
|
+
}
|
|
836
748
|
const conversation = result.conversation ?? {};
|
|
837
749
|
return [
|
|
838
750
|
"AKK session closed.",
|
|
@@ -840,7 +752,7 @@ function formatCloseCommandResult(result) {
|
|
|
840
752
|
`status: ${conversation.status ?? "unknown"}`
|
|
841
753
|
].join("\n");
|
|
842
754
|
}
|
|
843
|
-
function runDelegate(api, params, toolContext) {
|
|
755
|
+
async function runDelegate(api, params, toolContext) {
|
|
844
756
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
845
757
|
const binPath = stringValue(config.binPath) ?? defaultBinPath;
|
|
846
758
|
const workspace = stringValue(params.workspace) ?? stringValue(config.workspace) ?? process.cwd();
|
|
@@ -848,21 +760,7 @@ function runDelegate(api, params, toolContext) {
|
|
|
848
760
|
const prefixedRequest = stringValue(params.agent) ? undefined : parseLeadingExecutorAlias(rawRequest);
|
|
849
761
|
const request = prefixedRequest?.request ?? rawRequest;
|
|
850
762
|
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
763
|
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
864
|
-
stringValue(config.openclawSession) ??
|
|
865
|
-
stringValue(params.openclawSession) ??
|
|
866
764
|
"agent:main:main";
|
|
867
765
|
const args = [
|
|
868
766
|
binPath,
|
|
@@ -876,56 +774,72 @@ function runDelegate(api, params, toolContext) {
|
|
|
876
774
|
"--background"
|
|
877
775
|
];
|
|
878
776
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
879
|
-
pushOptional(args, "--session", agentSession);
|
|
880
|
-
pushOptional(args, "--all-proxy", allProxy);
|
|
881
|
-
pushOptional(args, "--model", model);
|
|
882
777
|
pushOptional(args, "--openclaw-session", openclawSession);
|
|
883
778
|
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
884
779
|
pushOptional(args, "--gateway-session", openclawSession);
|
|
885
780
|
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
781
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
890
782
|
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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;
|
|
783
|
+
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
|
|
784
|
+
const parsed = await runCliAsync(api, args.slice(1), { cwd: workspace });
|
|
785
|
+
const conversationId = parsed.conversation?.conversation_id ??
|
|
786
|
+
parsed.conversation_id;
|
|
787
|
+
const statePath = parsed.conversation?.state_path ??
|
|
788
|
+
parsed.paths?.statePath;
|
|
789
|
+
const logPath = parsed.conversation?.event_log_path ??
|
|
790
|
+
parsed.paths?.logPath;
|
|
791
|
+
const submissionUncertain = parsed.submission_outcome === "uncertain";
|
|
792
|
+
const submissionAborted = parsed.submission_outcome === "aborted";
|
|
906
793
|
return {
|
|
907
|
-
status:
|
|
908
|
-
|
|
794
|
+
status: submissionUncertain
|
|
795
|
+
? "submission_uncertain"
|
|
796
|
+
: submissionAborted
|
|
797
|
+
? "submission_aborted"
|
|
798
|
+
: "async_pending",
|
|
799
|
+
delegation_status: submissionUncertain
|
|
800
|
+
? "uncertain"
|
|
801
|
+
: submissionAborted
|
|
802
|
+
? "aborted"
|
|
803
|
+
: "delegated",
|
|
909
804
|
conversation_id: conversationId,
|
|
910
805
|
conversation_status: parsed.conversation?.status,
|
|
911
806
|
state_path: statePath,
|
|
912
807
|
event_log_path: logPath,
|
|
913
808
|
agent,
|
|
914
809
|
executor: parsed.conversation?.executor,
|
|
915
|
-
session: parsed.conversation?.executor?.session ??
|
|
916
|
-
|
|
810
|
+
session: parsed.conversation?.executor?.session ??
|
|
811
|
+
parsed.terminal_control?.target,
|
|
917
812
|
openclaw_session: openclawSession,
|
|
918
813
|
launched: parsed.launched === true,
|
|
814
|
+
replayed: parsed.replayed === true,
|
|
919
815
|
background: parsed.background === true,
|
|
920
|
-
pid: parsed.pid ?? null,
|
|
816
|
+
pid: parsed.pid ?? parsed.terminal_control?.panePid ?? null,
|
|
921
817
|
callback_method: CALLBACK_METHOD,
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
818
|
+
...(submissionUncertain
|
|
819
|
+
? {
|
|
820
|
+
submission_outcome: "uncertain",
|
|
821
|
+
do_not_retry: true,
|
|
822
|
+
reason: parsed.reason,
|
|
823
|
+
openclaw_next_action: parsed.openclaw_next_action,
|
|
824
|
+
note: "AKK could not prove whether tmux accepted Enter. Do not retry automatically; inspect the durable AKK conversation and shared terminal."
|
|
825
|
+
}
|
|
826
|
+
: submissionAborted
|
|
827
|
+
? {
|
|
828
|
+
submission_outcome: "aborted",
|
|
829
|
+
safe_to_retry: true,
|
|
830
|
+
reason: parsed.reason,
|
|
831
|
+
openclaw_next_action: parsed.openclaw_next_action,
|
|
832
|
+
note: "AKK stopped before sending tmux input. The request was not submitted and may be retried."
|
|
833
|
+
}
|
|
834
|
+
: {
|
|
835
|
+
openclaw_next_action: {
|
|
836
|
+
action: "yield",
|
|
837
|
+
reason: "The coding agent is working in the shared tmux terminal. End this OpenClaw turn now and wait for an Agent Knock Knock callback.",
|
|
838
|
+
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.",
|
|
839
|
+
expected_callback: "The callback will be injected and scheduled into this OpenClaw session by the agent-knock-knock.callback Gateway method."
|
|
840
|
+
},
|
|
841
|
+
note: "The task was sent to the shared tmux terminal. OpenClaw should yield now and wait for the scheduled callback turn."
|
|
842
|
+
})
|
|
929
843
|
};
|
|
930
844
|
}
|
|
931
845
|
function registerCliTool(api, { name, description, parameters, buildArgs }) {
|
|
@@ -957,17 +871,6 @@ function toolLabel(name) {
|
|
|
957
871
|
.join(" ");
|
|
958
872
|
return `AKK ${action || "Tool"}`;
|
|
959
873
|
}
|
|
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
874
|
function runCli(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false } = {}) {
|
|
972
875
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
973
876
|
const binPath = stringValue(config.binPath) ?? defaultBinPath;
|
|
@@ -1205,11 +1108,10 @@ function formatDoneShortcuts(conversationId) {
|
|
|
1205
1108
|
"",
|
|
1206
1109
|
"[AKK convenience commands]",
|
|
1207
1110
|
"When summarizing this result to the user, include these short next-step commands:",
|
|
1208
|
-
"- `AKK list` lists open AKK
|
|
1209
|
-
`- \`AKK send ${conversationId}: <message>\`
|
|
1210
|
-
`- \`AKK status ${conversationId}\` shows this
|
|
1211
|
-
`- \`AKK
|
|
1212
|
-
`- \`AKK close ${conversationId}\` closes this AKK session.`
|
|
1111
|
+
"- `AKK list` lists live shared terminals and open AKK tasks.",
|
|
1112
|
+
`- \`AKK send ${conversationId}: <message>\` continues in this same shared terminal.`,
|
|
1113
|
+
`- \`AKK status ${conversationId}\` shows this task record.`,
|
|
1114
|
+
`- \`AKK close ${conversationId}\` closes the AKK record without closing tmux.`
|
|
1213
1115
|
].join("\n");
|
|
1214
1116
|
}
|
|
1215
1117
|
function pushOptional(args, flag, value) {
|
|
@@ -1223,18 +1125,6 @@ function isRecord(value) {
|
|
|
1223
1125
|
function stringValue(value) {
|
|
1224
1126
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
1225
1127
|
}
|
|
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
1128
|
function numberString(value) {
|
|
1239
1129
|
return typeof value === "number" && Number.isFinite(value) ? String(value) : undefined;
|
|
1240
1130
|
}
|