@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.
@@ -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, parseLeadingExecutorAlias } from "./executors.js";
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 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: {
22
+ type: {
24
23
  type: "string",
25
- description: "Workspace path for the selected coding agent. Defaults to plugin config or the Gateway process directory."
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 delegated tasks and skip native/terminal active discovery."
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: "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.",
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> 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."
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 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.",
336
224
  parameters: listParameters,
337
225
  buildArgs: (params) => {
226
+ const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
338
227
  const args = ["list"];
339
- pushOptional(args, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
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
- registerCliTool(api, {
248
+ api.registerTool((_toolContext) => ({
249
+ label: "AKK Status",
359
250
  name: "agent_knock_knock_status",
360
- 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.",
361
252
  parameters: statusParameters,
362
- buildArgs: (params) => {
363
- const args = ["status", "--conversation", requiredString(params.conversation_id, "conversation_id")];
364
- pushOptional(args, "--store-dir", resolvePluginStoreDir(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
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
- registerCliTool(api, {
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 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.",
389
262
  parameters: sendParameters,
390
- buildArgs: (params, toolContext) => {
391
- const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
392
- const openclawSession = stringValue(toolContext?.sessionKey) ??
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(isRecord(api.pluginConfig) ? api.pluginConfig : {}));
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 binPath = stringValue(config.binPath) ?? defaultBinPath;
758
- const workspace = stringValue(params.workspace) ?? stringValue(config.workspace) ?? process.cwd();
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.slice(1), { cwd: workspace });
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) => runCli(api, 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
- `- \`AKK send ${conversationId}: <message>\` continues in this same shared terminal.`,
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
- `- \`AKK close ${conversationId}\` closes the AKK record without closing tmux.`
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 delegate returned invalid JSON: ${error.message}`);
1050
+ throw new Error(`agent-knock-knock CLI returned invalid JSON: ${error.message}`);
1150
1051
  }
1151
1052
  }
1152
1053
  function cleanError(text) {