@scotthuang/agent-knock-knock 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +22 -30
- package/dist/src/cli.js +171 -411
- package/dist/src/cli.js.map +1 -1
- package/dist/src/openclaw-doctor.d.ts +0 -1
- package/dist/src/openclaw-doctor.js +0 -7
- package/dist/src/openclaw-doctor.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +0 -5
- package/dist/src/openclaw-plugin-helpers.js +43 -51
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin.js +145 -244
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/docs/quickstart-tmux.md +10 -6
- package/openclaw.plugin.json +2 -19
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +45 -59
|
@@ -1,28 +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
|
|
4
|
+
import { EXECUTOR_KINDS, executorDefinitionForKind } from "./executors.js";
|
|
5
5
|
import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, formatAkkListCommandResult, parseAkkCommand, resolvePluginStoreDir } from "./openclaw-plugin-helpers.js";
|
|
6
6
|
import { attemptAutoApproval } from "./approval-policy.js";
|
|
7
7
|
const CALLBACK_METHOD = AKK_CALLBACK_METHOD;
|
|
8
8
|
const defaultBinPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
9
|
-
const
|
|
9
|
+
const sendParameters = {
|
|
10
10
|
type: "object",
|
|
11
11
|
additionalProperties: false,
|
|
12
12
|
required: ["request"],
|
|
13
13
|
properties: {
|
|
14
|
-
|
|
14
|
+
selector: {
|
|
15
15
|
type: "string",
|
|
16
|
-
|
|
17
|
-
description: "Coding agent to delegate to. Defaults to plugin config defaultAgent, falling back to codex when unset. Explicit user agent requests override the default."
|
|
16
|
+
description: "Optional existing tmux target selector from AKK list: codex, claude, only, latest, an @short-ref, or an authoritative full id. Omit it to start a new managed turn through the unique eligible idle pane."
|
|
18
17
|
},
|
|
19
18
|
request: {
|
|
20
19
|
type: "string",
|
|
21
|
-
description: "
|
|
20
|
+
description: "New task or follow-up message for the coding agent."
|
|
22
21
|
},
|
|
23
|
-
|
|
22
|
+
type: {
|
|
24
23
|
type: "string",
|
|
25
|
-
|
|
24
|
+
enum: ["answer", "task", "control", "error"]
|
|
26
25
|
},
|
|
27
26
|
idleTimeoutMinutes: {
|
|
28
27
|
type: "number",
|
|
@@ -55,7 +54,7 @@ const listParameters = {
|
|
|
55
54
|
},
|
|
56
55
|
managedOnly: {
|
|
57
56
|
type: "boolean",
|
|
58
|
-
description: "When true, only list AKK-managed
|
|
57
|
+
description: "When true, only list AKK-managed turns and skip live tmux terminal discovery."
|
|
59
58
|
},
|
|
60
59
|
noApprovalScan: {
|
|
61
60
|
type: "boolean",
|
|
@@ -115,59 +114,6 @@ const statusParameters = {
|
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
116
|
};
|
|
118
|
-
const describeParameters = {
|
|
119
|
-
type: "object",
|
|
120
|
-
additionalProperties: false,
|
|
121
|
-
required: ["conversation_id"],
|
|
122
|
-
properties: {
|
|
123
|
-
conversation_id: {
|
|
124
|
-
type: "string",
|
|
125
|
-
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."
|
|
126
|
-
},
|
|
127
|
-
idleTimeoutMinutes: {
|
|
128
|
-
type: "number"
|
|
129
|
-
},
|
|
130
|
-
maxMessages: {
|
|
131
|
-
type: "number"
|
|
132
|
-
},
|
|
133
|
-
maxCommands: {
|
|
134
|
-
type: "number"
|
|
135
|
-
},
|
|
136
|
-
maxTextLength: {
|
|
137
|
-
type: "number"
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
const sendParameters = {
|
|
142
|
-
type: "object",
|
|
143
|
-
additionalProperties: false,
|
|
144
|
-
required: ["conversation_id", "message"],
|
|
145
|
-
properties: {
|
|
146
|
-
conversation_id: {
|
|
147
|
-
type: "string",
|
|
148
|
-
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."
|
|
149
|
-
},
|
|
150
|
-
message: {
|
|
151
|
-
type: "string"
|
|
152
|
-
},
|
|
153
|
-
type: {
|
|
154
|
-
type: "string",
|
|
155
|
-
enum: ["answer", "task", "control", "error"]
|
|
156
|
-
},
|
|
157
|
-
idleTimeoutMinutes: {
|
|
158
|
-
type: "number"
|
|
159
|
-
},
|
|
160
|
-
agentTimeoutMinutes: {
|
|
161
|
-
type: "number",
|
|
162
|
-
description: "Terminal bridge inactivity timeout override in minutes."
|
|
163
|
-
},
|
|
164
|
-
agentHardTimeoutMinutes: {
|
|
165
|
-
type: "number",
|
|
166
|
-
exclusiveMinimum: 0,
|
|
167
|
-
description: "Terminal bridge fallback lifetime ceiling override in minutes."
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
117
|
const cancelParameters = {
|
|
172
118
|
type: "object",
|
|
173
119
|
additionalProperties: false,
|
|
@@ -199,47 +145,6 @@ const closeParameters = {
|
|
|
199
145
|
}
|
|
200
146
|
}
|
|
201
147
|
};
|
|
202
|
-
const agentTakeoverParameters = {
|
|
203
|
-
type: "object",
|
|
204
|
-
additionalProperties: false,
|
|
205
|
-
required: ["agent", "sessionId", "strategy"],
|
|
206
|
-
properties: {
|
|
207
|
-
agent: {
|
|
208
|
-
type: "string",
|
|
209
|
-
enum: ["codex"],
|
|
210
|
-
description: "Local coding agent session provider to plan takeover for. Currently supports Codex."
|
|
211
|
-
},
|
|
212
|
-
sessionId: {
|
|
213
|
-
type: "string",
|
|
214
|
-
description: "Native Codex session id to inspect or take over."
|
|
215
|
-
},
|
|
216
|
-
strategy: {
|
|
217
|
-
type: "string",
|
|
218
|
-
enum: ["terminal_control"],
|
|
219
|
-
description: "Attach to the existing Codex process through its shared tmux terminal."
|
|
220
|
-
},
|
|
221
|
-
createConversation: {
|
|
222
|
-
type: "boolean",
|
|
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."
|
|
224
|
-
},
|
|
225
|
-
confirmTerminal: {
|
|
226
|
-
type: "boolean",
|
|
227
|
-
description: "Only for strategy=terminal_control. When true, AKK attaches to the exact terminalTarget after the user confirms that terminal pane."
|
|
228
|
-
},
|
|
229
|
-
terminalTarget: {
|
|
230
|
-
type: "string",
|
|
231
|
-
description: "Required with confirmTerminal=true. The exact tmux target from the previous terminal_control plan, such as codex-work:0.0."
|
|
232
|
-
},
|
|
233
|
-
request: {
|
|
234
|
-
type: "string",
|
|
235
|
-
description: "Optional user-visible request label stored on the created AKK conversation."
|
|
236
|
-
},
|
|
237
|
-
codexHome: {
|
|
238
|
-
type: "string",
|
|
239
|
-
description: "Optional Codex home directory. Defaults to ~/.codex."
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
148
|
const approveParameters = {
|
|
244
149
|
type: "object",
|
|
245
150
|
additionalProperties: false,
|
|
@@ -281,6 +186,7 @@ const plugin = definePluginEntry({
|
|
|
281
186
|
try {
|
|
282
187
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
283
188
|
const args = ["reconcile-monitors"];
|
|
189
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
284
190
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
285
191
|
const result = runCli(api, args);
|
|
286
192
|
api.logger.info?.("agent-knock-knock monitor reconciliation: " +
|
|
@@ -301,42 +207,26 @@ const plugin = definePluginEntry({
|
|
|
301
207
|
}
|
|
302
208
|
api.registerCommand?.({
|
|
303
209
|
name: "akk",
|
|
304
|
-
description: "
|
|
210
|
+
description: "Send coding work through existing Codex or Claude Code tmux terminals, inspect tasks, approve exact prompts, and cancel running work.",
|
|
305
211
|
acceptsArgs: true,
|
|
306
212
|
requireAuth: true,
|
|
307
213
|
nativeProgressMessages: {
|
|
308
214
|
default: "AKK is handling the request..."
|
|
309
215
|
},
|
|
310
216
|
agentPromptGuidance: [
|
|
311
|
-
"Use /akk <task>
|
|
217
|
+
"Use /akk <task> when exactly one eligible idle coding-agent tmux pane should receive new work. Use /akk codex: <task>, /akk claude: <task>, or another selector returned by /akk list to target an existing pane. AKK never starts a coding agent."
|
|
312
218
|
],
|
|
313
219
|
handler: async (ctx) => handleAkkCommand(api, ctx)
|
|
314
220
|
});
|
|
315
|
-
api.registerTool((toolContext) => ({
|
|
316
|
-
label: "AKK Delegate",
|
|
317
|
-
name: "agent_knock_knock_delegate",
|
|
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.",
|
|
319
|
-
parameters: delegateParameters,
|
|
320
|
-
async execute(_toolCallId, params) {
|
|
321
|
-
const result = await runDelegate(api, params, toolContext);
|
|
322
|
-
return {
|
|
323
|
-
content: [
|
|
324
|
-
{
|
|
325
|
-
type: "text",
|
|
326
|
-
text: JSON.stringify(result, null, 2)
|
|
327
|
-
}
|
|
328
|
-
],
|
|
329
|
-
details: result
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
}), { name: "agent_knock_knock_delegate", optional: true });
|
|
333
221
|
registerCliTool(api, {
|
|
334
222
|
name: "agent_knock_knock_list",
|
|
335
|
-
description: "List
|
|
223
|
+
description: "List AKK-managed work and existing Codex or Claude Code tmux terminals. Use this to choose an exact selector before sending, inspecting, approving, or cancelling. AKK reports terminal approval state when available and never starts a coding agent.",
|
|
336
224
|
parameters: listParameters,
|
|
337
225
|
buildArgs: (params) => {
|
|
226
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
338
227
|
const args = ["list"];
|
|
339
|
-
pushOptional(args, "--
|
|
228
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
229
|
+
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
340
230
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
341
231
|
pushOptional(args, "--agent", stringValue(params.agent));
|
|
342
232
|
pushOptional(args, "--status", stringValue(params.status));
|
|
@@ -355,70 +245,36 @@ const plugin = definePluginEntry({
|
|
|
355
245
|
return args;
|
|
356
246
|
}
|
|
357
247
|
});
|
|
358
|
-
|
|
248
|
+
api.registerTool((_toolContext) => ({
|
|
249
|
+
label: "AKK Status",
|
|
359
250
|
name: "agent_knock_knock_status",
|
|
360
|
-
description: "
|
|
251
|
+
description: "Inspect one AKK-managed turn or existing coding-agent tmux terminal. Returns live state, a bounded terminal screen, and available purpose/context with confidence and limitations. AKK never starts a coding agent.",
|
|
361
252
|
parameters: statusParameters,
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
366
|
-
if (params.trace === true) {
|
|
367
|
-
args.push("--trace");
|
|
368
|
-
}
|
|
369
|
-
return args;
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
registerCliTool(api, {
|
|
373
|
-
name: "agent_knock_knock_describe",
|
|
374
|
-
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.",
|
|
375
|
-
parameters: describeParameters,
|
|
376
|
-
buildArgs: (params) => {
|
|
377
|
-
const args = ["describe", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
378
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
|
|
379
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(api.pluginConfig?.idleTimeoutMinutes));
|
|
380
|
-
pushOptional(args, "--max-messages", numberString(params.maxMessages));
|
|
381
|
-
pushOptional(args, "--max-commands", numberString(params.maxCommands));
|
|
382
|
-
pushOptional(args, "--max-text-length", numberString(params.maxTextLength));
|
|
383
|
-
return args;
|
|
253
|
+
async execute(_toolCallId, params) {
|
|
254
|
+
const result = runCli(api, buildStatusCliArgs(api, isRecord(params) ? params : {}));
|
|
255
|
+
return toolResult(result);
|
|
384
256
|
}
|
|
385
|
-
});
|
|
386
|
-
|
|
257
|
+
}), { name: "agent_knock_knock_status", optional: true });
|
|
258
|
+
api.registerTool((toolContext) => ({
|
|
259
|
+
label: "AKK Send",
|
|
387
260
|
name: "agent_knock_knock_send",
|
|
388
|
-
description: "Send a
|
|
261
|
+
description: "Send a new task or follow-up through an existing Codex or Claude Code tmux terminal. Omit selector only when AKK should require one unique eligible idle pane; otherwise pass codex, claude, only, latest, an @short-ref from AKK list, or an authoritative full id. AKK never starts a coding agent. This is asynchronous: after acceptance, yield and wait for the callback or a later explicit status request.",
|
|
389
262
|
parameters: sendParameters,
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
"agent:main:main";
|
|
394
|
-
const args = [
|
|
395
|
-
"send",
|
|
396
|
-
"--conversation",
|
|
397
|
-
requiredString(params.conversation_id, "conversation_id"),
|
|
398
|
-
"--message",
|
|
399
|
-
requiredString(params.message, "message"),
|
|
400
|
-
"--background"
|
|
401
|
-
];
|
|
402
|
-
pushOptional(args, "--type", stringValue(params.type));
|
|
403
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
404
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
405
|
-
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
|
|
406
|
-
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
|
|
407
|
-
pushOptional(args, "--openclaw-session", openclawSession);
|
|
408
|
-
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
409
|
-
pushOptional(args, "--gateway-session", openclawSession);
|
|
410
|
-
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
411
|
-
return args;
|
|
263
|
+
async execute(_toolCallId, params) {
|
|
264
|
+
const result = await runSendRequest(api, isRecord(params) ? params : {}, toolContext);
|
|
265
|
+
return toolResult(result);
|
|
412
266
|
}
|
|
413
|
-
});
|
|
267
|
+
}), { name: "agent_knock_knock_send", optional: true });
|
|
414
268
|
registerCliTool(api, {
|
|
415
269
|
name: "agent_knock_knock_approve",
|
|
416
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.",
|
|
417
271
|
parameters: approveParameters,
|
|
418
272
|
buildArgs: (params) => {
|
|
273
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
419
274
|
const args = ["approve", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
275
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
420
276
|
pushOptional(args, "--expected-approval-fingerprint", requiredString(params.expected_approval_fingerprint, "expected_approval_fingerprint"));
|
|
421
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(
|
|
277
|
+
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
422
278
|
return args;
|
|
423
279
|
}
|
|
424
280
|
});
|
|
@@ -430,6 +286,7 @@ const plugin = definePluginEntry({
|
|
|
430
286
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
431
287
|
const args = ["renew", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
432
288
|
pushOptional(args, "--minutes", numberString(params.minutes) ?? numberString(config.agentTimeoutMinutes));
|
|
289
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
433
290
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
434
291
|
return args;
|
|
435
292
|
}
|
|
@@ -441,6 +298,7 @@ const plugin = definePluginEntry({
|
|
|
441
298
|
buildArgs: (params) => {
|
|
442
299
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
443
300
|
const args = ["retry-callback", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
301
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
444
302
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
445
303
|
return args;
|
|
446
304
|
}
|
|
@@ -452,6 +310,7 @@ const plugin = definePluginEntry({
|
|
|
452
310
|
buildArgs: (params) => {
|
|
453
311
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
454
312
|
const args = ["cancel", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
313
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
455
314
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
456
315
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
457
316
|
return args;
|
|
@@ -462,46 +321,12 @@ const plugin = definePluginEntry({
|
|
|
462
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.",
|
|
463
322
|
parameters: closeParameters,
|
|
464
323
|
buildArgs: (params) => {
|
|
324
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
465
325
|
const args = ["close", "--conversation", requiredString(params.conversation_id, "conversation_id")];
|
|
466
326
|
pushOptional(args, "--reason", stringValue(params.reason));
|
|
327
|
+
pushOptional(args, "--workspace", pluginWorkspace(config));
|
|
467
328
|
pushOptional(args, "--expected-message-id", stringValue(params.expected_message_id));
|
|
468
|
-
pushOptional(args, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
|
|
469
|
-
return args;
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
registerCliTool(api, {
|
|
473
|
-
name: "agent_knock_knock_agent_takeover",
|
|
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.",
|
|
475
|
-
parameters: agentTakeoverParameters,
|
|
476
|
-
buildArgs: (params, toolContext) => {
|
|
477
|
-
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
478
|
-
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
479
|
-
"agent:main:main";
|
|
480
|
-
const args = [
|
|
481
|
-
"agent",
|
|
482
|
-
"takeover",
|
|
483
|
-
"--agent",
|
|
484
|
-
requiredString(params.agent, "agent"),
|
|
485
|
-
"--session-id",
|
|
486
|
-
requiredString(params.sessionId, "sessionId"),
|
|
487
|
-
"--strategy",
|
|
488
|
-
requiredString(params.strategy, "strategy")
|
|
489
|
-
];
|
|
490
|
-
if (params.createConversation === true) {
|
|
491
|
-
args.push("--create-conversation");
|
|
492
|
-
}
|
|
493
|
-
if (params.confirmTerminal === true) {
|
|
494
|
-
args.push("--confirm-terminal");
|
|
495
|
-
}
|
|
496
|
-
pushOptional(args, "--terminal-target", stringValue(params.terminalTarget));
|
|
497
|
-
pushOptional(args, "--request", stringValue(params.request));
|
|
498
329
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
499
|
-
pushOptional(args, "--openclaw-session", openclawSession);
|
|
500
|
-
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
501
|
-
pushOptional(args, "--gateway-session", openclawSession);
|
|
502
|
-
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
503
|
-
pushOptional(args, "--idle-timeout-minutes", numberString(config.idleTimeoutMinutes));
|
|
504
|
-
pushOptional(args, "--codex-home", stringValue(params.codexHome) ?? stringValue(api.pluginConfig?.codexHome));
|
|
505
330
|
return args;
|
|
506
331
|
}
|
|
507
332
|
});
|
|
@@ -516,7 +341,6 @@ async function handleAkkCommand(api, ctx) {
|
|
|
516
341
|
}
|
|
517
342
|
if (parsed.action === "delegate") {
|
|
518
343
|
const result = await runDelegate(api, {
|
|
519
|
-
agent: parsed.agent,
|
|
520
344
|
request: parsed.request
|
|
521
345
|
}, {
|
|
522
346
|
sessionKey: ctx.sessionKey
|
|
@@ -548,8 +372,6 @@ async function handleAkkCommand(api, ctx) {
|
|
|
548
372
|
return { text: formatAkkListCommandResult(result) };
|
|
549
373
|
case "status":
|
|
550
374
|
return { text: formatStatusCommandResult(result) };
|
|
551
|
-
case "describe":
|
|
552
|
-
return { text: formatDescribeCommandResult(result) };
|
|
553
375
|
case "send":
|
|
554
376
|
return {
|
|
555
377
|
text: formatSendCommandResult(result),
|
|
@@ -622,6 +444,22 @@ function formatStatusCommandResult(result) {
|
|
|
622
444
|
if (summary.request) {
|
|
623
445
|
lines.push(`request: ${truncateText(summary.request, 180)}`);
|
|
624
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
|
+
}
|
|
625
463
|
return lines.join("\n");
|
|
626
464
|
}
|
|
627
465
|
function formatDoctorCommandResult(result) {
|
|
@@ -659,21 +497,6 @@ function formatRetryCallbackCommandResult(result) {
|
|
|
659
497
|
`attempts: ${result.conversation?.callback_delivery?.attempts ?? "unknown"}`
|
|
660
498
|
].join("\n");
|
|
661
499
|
}
|
|
662
|
-
function formatDescribeCommandResult(result) {
|
|
663
|
-
const lines = [
|
|
664
|
-
`AKK description: ${result.conversation_id ?? "unknown"}`,
|
|
665
|
-
`source: ${result.source ?? "unknown"}`,
|
|
666
|
-
`confidence: ${result.confidence ?? "unknown"}`
|
|
667
|
-
];
|
|
668
|
-
if (result.about) {
|
|
669
|
-
lines.push(`about: ${truncateText(result.about, 500)}`);
|
|
670
|
-
}
|
|
671
|
-
const limitations = Array.isArray(result.limitations) ? result.limitations.filter(Boolean) : [];
|
|
672
|
-
if (limitations.length > 0) {
|
|
673
|
-
lines.push(`limitations: ${limitations.slice(0, 3).join("; ")}`);
|
|
674
|
-
}
|
|
675
|
-
return lines.join("\n");
|
|
676
|
-
}
|
|
677
500
|
function formatSendCommandResult(result) {
|
|
678
501
|
const conversation = result.conversation ?? {};
|
|
679
502
|
const conversationId = conversation.conversation_id ?? result.conversation_id ?? "unknown";
|
|
@@ -752,21 +575,88 @@ function formatCloseCommandResult(result) {
|
|
|
752
575
|
`status: ${conversation.status ?? "unknown"}`
|
|
753
576
|
].join("\n");
|
|
754
577
|
}
|
|
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
|
+
}
|
|
615
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
616
|
+
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
617
|
+
"agent:main:main";
|
|
618
|
+
const args = [
|
|
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
|
+
}
|
|
755
652
|
async function runDelegate(api, params, toolContext) {
|
|
756
653
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
757
|
-
const
|
|
758
|
-
const
|
|
759
|
-
const rawRequest = requiredString(params.request, "request");
|
|
760
|
-
const prefixedRequest = stringValue(params.agent) ? undefined : parseLeadingExecutorAlias(rawRequest);
|
|
761
|
-
const request = prefixedRequest?.request ?? rawRequest;
|
|
762
|
-
const agent = executorDefinitionForKind(stringValue(params.agent) ?? prefixedRequest?.kind ?? stringValue(config.defaultAgent) ?? "codex").kind;
|
|
654
|
+
const workspace = pluginWorkspace(config);
|
|
655
|
+
const request = requiredString(params.request, "request");
|
|
763
656
|
const openclawSession = stringValue(toolContext?.sessionKey) ??
|
|
764
657
|
"agent:main:main";
|
|
765
658
|
const args = [
|
|
766
|
-
binPath,
|
|
767
659
|
"delegate",
|
|
768
|
-
"--agent",
|
|
769
|
-
agent,
|
|
770
660
|
"--request",
|
|
771
661
|
request,
|
|
772
662
|
"--workspace",
|
|
@@ -781,7 +671,7 @@ async function runDelegate(api, params, toolContext) {
|
|
|
781
671
|
pushOptional(args, "--idle-timeout-minutes", numberString(params.idleTimeoutMinutes) ?? numberString(config.idleTimeoutMinutes));
|
|
782
672
|
pushOptional(args, "--agent-timeout-minutes", numberString(params.agentTimeoutMinutes) ?? numberString(config.agentTimeoutMinutes));
|
|
783
673
|
pushOptional(args, "--agent-hard-timeout-minutes", numberString(params.agentHardTimeoutMinutes) ?? numberString(config.agentHardTimeoutMinutes));
|
|
784
|
-
const parsed = await runCliAsync(api, args
|
|
674
|
+
const parsed = await runCliAsync(api, args, { cwd: workspace });
|
|
785
675
|
const conversationId = parsed.conversation?.conversation_id ??
|
|
786
676
|
parsed.conversation_id;
|
|
787
677
|
const statePath = parsed.conversation?.state_path ??
|
|
@@ -790,6 +680,8 @@ async function runDelegate(api, params, toolContext) {
|
|
|
790
680
|
parsed.paths?.logPath;
|
|
791
681
|
const submissionUncertain = parsed.submission_outcome === "uncertain";
|
|
792
682
|
const submissionAborted = parsed.submission_outcome === "aborted";
|
|
683
|
+
const agent = stringValue(parsed.conversation?.executor?.kind) ??
|
|
684
|
+
stringValue(parsed.agent);
|
|
793
685
|
return {
|
|
794
686
|
status: submissionUncertain
|
|
795
687
|
? "submission_uncertain"
|
|
@@ -1045,7 +937,13 @@ function tryAutoApproveCallback({ api, message, conversationId, statePath }) {
|
|
|
1045
937
|
message,
|
|
1046
938
|
policy: config.autoApprove,
|
|
1047
939
|
statePath,
|
|
1048
|
-
execute: (args) =>
|
|
940
|
+
execute: (args) => {
|
|
941
|
+
return runCli(api, [
|
|
942
|
+
...args,
|
|
943
|
+
"--workspace",
|
|
944
|
+
pluginWorkspace(config)
|
|
945
|
+
]);
|
|
946
|
+
}
|
|
1049
947
|
});
|
|
1050
948
|
if (result) {
|
|
1051
949
|
api.logger.info?.(`agent-knock-knock approval policy for ${conversationId ?? "unknown"}: ${result.action} (${result.reason})`);
|
|
@@ -1109,11 +1007,14 @@ function formatDoneShortcuts(conversationId) {
|
|
|
1109
1007
|
"[AKK convenience commands]",
|
|
1110
1008
|
"When summarizing this result to the user, include these short next-step commands:",
|
|
1111
1009
|
"- `AKK list` lists live shared terminals and open AKK tasks.",
|
|
1112
|
-
|
|
1010
|
+
"- Use the matching `@short-ref` from `AKK list`, then `AKK @short-ref: <message>` to continue in the same shared terminal.",
|
|
1113
1011
|
`- \`AKK status ${conversationId}\` shows this task record.`,
|
|
1114
|
-
|
|
1012
|
+
"- AKK never starts or closes the coding agent or tmux pane."
|
|
1115
1013
|
].join("\n");
|
|
1116
1014
|
}
|
|
1015
|
+
function pluginWorkspace(config) {
|
|
1016
|
+
return stringValue(config.workspace) ?? process.cwd();
|
|
1017
|
+
}
|
|
1117
1018
|
function pushOptional(args, flag, value) {
|
|
1118
1019
|
if (value !== undefined && value !== "") {
|
|
1119
1020
|
args.push(flag, value);
|
|
@@ -1146,7 +1047,7 @@ function parseJson(text) {
|
|
|
1146
1047
|
return JSON.parse(text);
|
|
1147
1048
|
}
|
|
1148
1049
|
catch (error) {
|
|
1149
|
-
throw new Error(`agent-knock-knock
|
|
1050
|
+
throw new Error(`agent-knock-knock CLI returned invalid JSON: ${error.message}`);
|
|
1150
1051
|
}
|
|
1151
1052
|
}
|
|
1152
1053
|
function cleanError(text) {
|