@scotthuang/agent-knock-knock 0.2.51 → 0.3.0-beta.2
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 +31 -0
- package/README.md +120 -79
- package/dist/src/cli.js +382 -49
- package/dist/src/cli.js.map +1 -1
- package/dist/src/doctor-capabilities.d.ts +64 -13
- package/dist/src/doctor-capabilities.js +241 -12
- package/dist/src/doctor-capabilities.js.map +1 -1
- package/dist/src/openclaw-doctor.d.ts +28 -0
- package/dist/src/openclaw-doctor.js +359 -0
- package/dist/src/openclaw-doctor.js.map +1 -0
- package/dist/src/openclaw-plugin-helpers.d.ts +5 -2
- package/dist/src/openclaw-plugin-helpers.js +72 -24
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin.d.ts +3 -7
- package/dist/src/openclaw-plugin.js +131 -13
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/session-selector.d.ts +81 -0
- package/dist/src/session-selector.js +330 -0
- package/dist/src/session-selector.js.map +1 -0
- package/dist/src/terminal-process-source.d.ts +4 -0
- package/dist/src/terminal-process-source.js +20 -0
- package/dist/src/terminal-process-source.js.map +1 -1
- package/docs/assets/agent-knock-knock-icon.png +0 -0
- package/docs/quickstart-managed-acpx.md +43 -0
- package/docs/quickstart-tmux.md +43 -0
- package/openclaw.plugin.json +22 -9
- package/package.json +40 -4
- package/scripts/smoke-acpx.js +139 -0
- package/scripts/smoke-tmux.js +140 -0
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +23 -20
package/dist/src/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { defaultClaudeSettingsPath, loadTrustedClaudeTokenjuiceLaunchers } from
|
|
|
14
14
|
import { CodexLocalSessionProvider } from "./codex-local-session-provider.js";
|
|
15
15
|
import { CodexStoreAdapter } from "./codex-store-adapter.js";
|
|
16
16
|
import { applyMessageToConversation, budgetAction, createConversation, createMessage, executorForConversation, extractStructuredMessage, parseMessageJson, resolveExecutor } from "./protocol.js";
|
|
17
|
-
import { EXECUTOR_KINDS, acpxCommandForExecutor, executorDefinitionForKind, modelEnvForExecutor, normalizeModelForExecutor, proxyEnvForExecutor } from "./executors.js";
|
|
17
|
+
import { EXECUTOR_KINDS, acpxCommandForExecutor, executorDefinitionForKind, isExecutorKind, modelEnvForExecutor, normalizeModelForExecutor, proxyEnvForExecutor } from "./executors.js";
|
|
18
18
|
import { executorBootstrapPrompt } from "./bootstrap.js";
|
|
19
19
|
import { redactString, writeRuntimeLog } from "./runtime-log.js";
|
|
20
20
|
import { formatTranscript, readNdjsonLog } from "./transcript.js";
|
|
@@ -23,10 +23,12 @@ import { planFork, planTakeover } from "./session-takeover-planner.js";
|
|
|
23
23
|
import { StaticTerminalControlProvider, TmuxTerminalControlProvider, terminalPaneContainsProcess } from "./terminal-control-provider.js";
|
|
24
24
|
import { parseTerminalConversationId } from "./terminal-agent-adapter.js";
|
|
25
25
|
import { createProductionTerminalAgentRegistry } from "./terminal-agent-registry.js";
|
|
26
|
-
import { StaticTerminalProcessSource, SystemTerminalProcessSource } from "./terminal-process-source.js";
|
|
26
|
+
import { parseProcessElapsedSeconds, StaticTerminalProcessSource, SystemTerminalProcessSource } from "./terminal-process-source.js";
|
|
27
27
|
import { TerminalAgentBridge } from "./terminal-agent-bridge.js";
|
|
28
28
|
import { evaluateApprovalPolicy } from "./approval-policy.js";
|
|
29
|
-
import { evaluateDoctorCapabilities } from "./doctor-capabilities.js";
|
|
29
|
+
import { evaluateDoctorCapabilities, runDoctorCapabilityProbes } from "./doctor-capabilities.js";
|
|
30
|
+
import { runOpenClawChainDiagnostics } from "./openclaw-doctor.js";
|
|
31
|
+
import { resolveSessionSelector, sessionShortRef } from "./session-selector.js";
|
|
30
32
|
const DEFAULT_IDLE_TIMEOUT_MINUTES = 10080;
|
|
31
33
|
const DEFAULT_AGENT_TIMEOUT_MINUTES = 60;
|
|
32
34
|
const DEFAULT_AGENT_HARD_TIMEOUT_MINUTES = 720;
|
|
@@ -57,6 +59,18 @@ const CONVERSATION_STATUSES = new Set([
|
|
|
57
59
|
"cancelled",
|
|
58
60
|
"cancelling"
|
|
59
61
|
]);
|
|
62
|
+
const SESSION_SELECTOR_COMMANDS = new Set([
|
|
63
|
+
"status",
|
|
64
|
+
"describe",
|
|
65
|
+
"summary",
|
|
66
|
+
"send",
|
|
67
|
+
"approve",
|
|
68
|
+
"cancel",
|
|
69
|
+
"renew",
|
|
70
|
+
"retry-callback",
|
|
71
|
+
"recover",
|
|
72
|
+
"close"
|
|
73
|
+
]);
|
|
60
74
|
class InlineCodexSessionAdapter {
|
|
61
75
|
threads;
|
|
62
76
|
processes;
|
|
@@ -113,6 +127,7 @@ catch (error) {
|
|
|
113
127
|
process.exit(1);
|
|
114
128
|
}
|
|
115
129
|
async function runCommand(commandName, options) {
|
|
130
|
+
await resolveConversationSelectorOption(commandName, options);
|
|
116
131
|
if (commandName === "help" || commandName === "--help" || commandName === "-h") {
|
|
117
132
|
usage();
|
|
118
133
|
}
|
|
@@ -196,7 +211,24 @@ async function runCommand(commandName, options) {
|
|
|
196
211
|
function runInstallOpenClaw(options) {
|
|
197
212
|
const root = packageRootDir();
|
|
198
213
|
const skillOnly = options.skillOnly === true;
|
|
199
|
-
const
|
|
214
|
+
const workspace = options.workspace === undefined
|
|
215
|
+
? undefined
|
|
216
|
+
: canonicalWorkspace(options.workspace);
|
|
217
|
+
const defaultAgent = optionalExecutorKind(options.defaultAgent);
|
|
218
|
+
const selectedMode = options.mode === undefined
|
|
219
|
+
? undefined
|
|
220
|
+
: parseDoctorMode(options.mode);
|
|
221
|
+
if (selectedMode === "tmux" && defaultAgent === "cursor") {
|
|
222
|
+
throw new Error("--default-agent cursor requires --mode acpx or --mode all");
|
|
223
|
+
}
|
|
224
|
+
if (skillOnly &&
|
|
225
|
+
(workspace !== undefined ||
|
|
226
|
+
defaultAgent !== undefined ||
|
|
227
|
+
selectedMode !== undefined ||
|
|
228
|
+
options.verify === true)) {
|
|
229
|
+
throw new Error("--skill-only cannot be combined with --workspace, --default-agent, --mode, or --verify");
|
|
230
|
+
}
|
|
231
|
+
const needsOpenClaw = !skillOnly || options.noRestart !== true || options.verify === true;
|
|
200
232
|
const openclawBin = needsOpenClaw
|
|
201
233
|
? options.openclawBin ?? resolveExecutable("openclaw")
|
|
202
234
|
: options.openclawBin;
|
|
@@ -210,12 +242,35 @@ function runInstallOpenClaw(options) {
|
|
|
210
242
|
path: root,
|
|
211
243
|
mode: pluginInstall.mode
|
|
212
244
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
245
|
+
const configOperations = [
|
|
246
|
+
{
|
|
247
|
+
path: "plugins.entries.agent-knock-knock.enabled",
|
|
248
|
+
value: true
|
|
249
|
+
},
|
|
250
|
+
...(workspace === undefined
|
|
251
|
+
? []
|
|
252
|
+
: [{
|
|
253
|
+
path: "plugins.entries.agent-knock-knock.config.workspace",
|
|
254
|
+
value: workspace
|
|
255
|
+
}]),
|
|
256
|
+
...(defaultAgent === undefined
|
|
257
|
+
? []
|
|
258
|
+
: [{
|
|
259
|
+
path: "plugins.entries.agent-knock-knock.config.defaultAgent",
|
|
260
|
+
value: defaultAgent
|
|
261
|
+
}]),
|
|
262
|
+
...(selectedMode === undefined
|
|
263
|
+
? []
|
|
264
|
+
: [{
|
|
265
|
+
path: "plugins.entries.agent-knock-knock.config.mode",
|
|
266
|
+
value: selectedMode
|
|
267
|
+
}])
|
|
268
|
+
];
|
|
269
|
+
runCheckedCommand(openclawBin, ["config", "set", "--batch-json", JSON.stringify(configOperations)], { label: "openclaw config set" });
|
|
216
270
|
steps.push({
|
|
217
|
-
name: "
|
|
218
|
-
plugin: "agent-knock-knock"
|
|
271
|
+
name: "plugin_configured",
|
|
272
|
+
plugin: "agent-knock-knock",
|
|
273
|
+
updated: configOperations.map((operation) => operation.path)
|
|
219
274
|
});
|
|
220
275
|
}
|
|
221
276
|
fs.mkdirSync(path.dirname(skillDest), { recursive: true });
|
|
@@ -232,17 +287,105 @@ function runInstallOpenClaw(options) {
|
|
|
232
287
|
name: "gateway_restarted"
|
|
233
288
|
});
|
|
234
289
|
}
|
|
290
|
+
const pendingRestart = !skillOnly && options.noRestart === true;
|
|
291
|
+
const verification = options.verify === true
|
|
292
|
+
? buildDoctorReport({
|
|
293
|
+
...options,
|
|
294
|
+
openclawBin,
|
|
295
|
+
...(workspace ? { workspace } : {}),
|
|
296
|
+
mode: selectedMode ?? "all"
|
|
297
|
+
})
|
|
298
|
+
: undefined;
|
|
299
|
+
const ready = verification
|
|
300
|
+
? verification.ok === true && !pendingRestart
|
|
301
|
+
: false;
|
|
302
|
+
const nextActions = installNextActions({
|
|
303
|
+
pendingRestart,
|
|
304
|
+
verification,
|
|
305
|
+
mode: selectedMode ?? "all",
|
|
306
|
+
agent: defaultAgent ?? "codex"
|
|
307
|
+
});
|
|
235
308
|
printJson({
|
|
236
309
|
installed: true,
|
|
310
|
+
ready,
|
|
311
|
+
pending_restart: pendingRestart,
|
|
237
312
|
mode: skillOnly ? "skill_only" : "full",
|
|
313
|
+
execution_mode: selectedMode ?? null,
|
|
314
|
+
default_agent: defaultAgent ?? null,
|
|
315
|
+
workspace: workspace ?? null,
|
|
238
316
|
package_root: root,
|
|
239
317
|
openclaw_bin: openclawBin ?? null,
|
|
240
318
|
steps,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
: "Agent Knock Knock is installed. Try: AKK list"
|
|
319
|
+
...(verification ? { verification } : {}),
|
|
320
|
+
next_actions: nextActions
|
|
244
321
|
});
|
|
245
322
|
}
|
|
323
|
+
function canonicalWorkspace(value) {
|
|
324
|
+
const requested = path.resolve(String(required(value, "--workspace is required")));
|
|
325
|
+
let canonical;
|
|
326
|
+
let stat;
|
|
327
|
+
try {
|
|
328
|
+
canonical = fs.realpathSync(requested);
|
|
329
|
+
stat = fs.statSync(canonical);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
throw new Error(`--workspace does not exist: ${requested}`);
|
|
333
|
+
}
|
|
334
|
+
if (!stat.isDirectory()) {
|
|
335
|
+
throw new Error(`--workspace must be a directory: ${requested}`);
|
|
336
|
+
}
|
|
337
|
+
return canonical;
|
|
338
|
+
}
|
|
339
|
+
function optionalExecutorKind(value) {
|
|
340
|
+
if (value === undefined) {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
const normalized = String(value).trim().toLowerCase();
|
|
344
|
+
if (!isExecutorKind(normalized)) {
|
|
345
|
+
throw new Error(`--default-agent must be one of: ${EXECUTOR_KINDS.join(", ")}`);
|
|
346
|
+
}
|
|
347
|
+
return normalized;
|
|
348
|
+
}
|
|
349
|
+
function installNextActions({ pendingRestart, verification, mode, agent }) {
|
|
350
|
+
if (pendingRestart) {
|
|
351
|
+
return [
|
|
352
|
+
{
|
|
353
|
+
action: "apply_plugin_changes",
|
|
354
|
+
command: "openclaw gateway restart"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
action: "verify",
|
|
358
|
+
command: `agent-knock-knock doctor --mode ${mode}`
|
|
359
|
+
}
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
if (verification && verification.ok !== true) {
|
|
363
|
+
const chain = isRecord(verification.openclaw) ? verification.openclaw : {};
|
|
364
|
+
const checks = Array.isArray(chain.checks) ? chain.checks : [];
|
|
365
|
+
const remediation = checks.flatMap((check) => isRecord(check) && Array.isArray(check.remediation)
|
|
366
|
+
? check.remediation.filter((command) => typeof command === "string")
|
|
367
|
+
: []);
|
|
368
|
+
return [...new Set(remediation)].map((command) => ({
|
|
369
|
+
action: "repair",
|
|
370
|
+
command
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
if (!verification) {
|
|
374
|
+
return [{
|
|
375
|
+
action: "verify",
|
|
376
|
+
command: `agent-knock-knock doctor --mode ${mode}`
|
|
377
|
+
}];
|
|
378
|
+
}
|
|
379
|
+
return mode === "tmux"
|
|
380
|
+
? [{
|
|
381
|
+
action: "start_agent",
|
|
382
|
+
command: `tmux new -s akk-${agent} ${agent}`
|
|
383
|
+
}]
|
|
384
|
+
: [{
|
|
385
|
+
action: "delegate",
|
|
386
|
+
command: `/akk ${agent} <task>`
|
|
387
|
+
}];
|
|
388
|
+
}
|
|
246
389
|
async function runClaudeHook(options) {
|
|
247
390
|
const rawInput = fs.readFileSync(0, "utf8");
|
|
248
391
|
let input;
|
|
@@ -321,18 +464,41 @@ function installOpenClawPlugin(openclawBin, root) {
|
|
|
321
464
|
return { mode: "replaced" };
|
|
322
465
|
}
|
|
323
466
|
function runDoctor(options) {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
467
|
+
const report = buildDoctorReport(options);
|
|
468
|
+
printJson(report);
|
|
469
|
+
if (!report.ok) {
|
|
470
|
+
process.exitCode = 1;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function buildDoctorReport(options) {
|
|
474
|
+
const mode = parseDoctorMode(options.mode ?? "all");
|
|
475
|
+
const timeoutMs = options.timeoutMs === undefined
|
|
476
|
+
? undefined
|
|
477
|
+
: positiveMilliseconds(options.timeoutMs, "--timeout-ms");
|
|
478
|
+
const openclawBin = String(options.openclawBin ?? resolveOptionalExecutable("openclaw"));
|
|
479
|
+
const executables = {
|
|
480
|
+
openclaw: openclawBin,
|
|
481
|
+
...(options.tmuxBin ? { tmux: String(options.tmuxBin) } : {}),
|
|
482
|
+
...(options.acpxBin ? { acpx: String(options.acpxBin) } : {}),
|
|
483
|
+
...(options.codexBin ? { codex: String(options.codexBin) } : {}),
|
|
484
|
+
...(options.claudeBin ? { claude: String(options.claudeBin) } : {}),
|
|
485
|
+
...(options.cursorBin ? { cursor: String(options.cursorBin) } : {})
|
|
486
|
+
};
|
|
487
|
+
const checks = [
|
|
488
|
+
{
|
|
489
|
+
command: "node",
|
|
490
|
+
status: "ok",
|
|
491
|
+
available: true,
|
|
492
|
+
executable: process.execPath,
|
|
493
|
+
version: process.versions.node,
|
|
494
|
+
version_supported: versionAtLeast(process.versions.node, MINIMUM_NODE_VERSION),
|
|
495
|
+
minimum_version: MINIMUM_NODE_VERSION
|
|
496
|
+
},
|
|
497
|
+
...runDoctorCapabilityProbes({
|
|
498
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
499
|
+
executables
|
|
500
|
+
}, mode)
|
|
501
|
+
];
|
|
336
502
|
const root = packageRootDir();
|
|
337
503
|
const packageFiles = [
|
|
338
504
|
"dist/src/cli.js",
|
|
@@ -346,29 +512,78 @@ function runDoctor(options) {
|
|
|
346
512
|
exists: fs.existsSync(filePath)
|
|
347
513
|
};
|
|
348
514
|
});
|
|
349
|
-
const capabilities = evaluateDoctorCapabilities(checks);
|
|
515
|
+
const capabilities = evaluateDoctorCapabilities(checks, mode);
|
|
350
516
|
const filesOk = packageFiles.every((check) => check.exists);
|
|
351
|
-
const
|
|
352
|
-
|
|
517
|
+
const openclaw = runOpenClawChainDiagnostics({
|
|
518
|
+
openclawBin,
|
|
519
|
+
...(options.workspace ? { workspace: String(options.workspace) } : {}),
|
|
520
|
+
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
521
|
+
});
|
|
522
|
+
const selectedAgent = optionalExecutorKind(options.defaultAgent) ??
|
|
523
|
+
openclaw.default_agent ??
|
|
524
|
+
"codex";
|
|
525
|
+
const selectedAgentReady = mode === "tmux"
|
|
526
|
+
? capabilities.tmux.status === "ready" &&
|
|
527
|
+
capabilities.tmux.agents.includes(selectedAgent)
|
|
528
|
+
: mode === "acpx"
|
|
529
|
+
? capabilities.acpx.status === "ready" &&
|
|
530
|
+
capabilities.acpx.agents.includes(selectedAgent)
|
|
531
|
+
: (capabilities.tmux.status === "ready" &&
|
|
532
|
+
capabilities.tmux.agents.includes(selectedAgent)) || (capabilities.acpx.status === "ready" &&
|
|
533
|
+
capabilities.acpx.agents.includes(selectedAgent));
|
|
534
|
+
const ok = capabilities.readiness === "ready" &&
|
|
535
|
+
selectedAgentReady &&
|
|
536
|
+
filesOk &&
|
|
537
|
+
openclaw.ready;
|
|
538
|
+
return {
|
|
353
539
|
ok,
|
|
540
|
+
readiness: ok
|
|
541
|
+
? "ready"
|
|
542
|
+
: capabilities.readiness === "not_ready"
|
|
543
|
+
? "not_ready"
|
|
544
|
+
: "partially_ready",
|
|
545
|
+
selected_mode: mode,
|
|
546
|
+
selected_agent: selectedAgent
|
|
547
|
+
? {
|
|
548
|
+
agent: selectedAgent,
|
|
549
|
+
ready: selectedAgentReady
|
|
550
|
+
}
|
|
551
|
+
: null,
|
|
354
552
|
package_root: root,
|
|
355
553
|
checks,
|
|
356
554
|
package_files: packageFiles,
|
|
357
555
|
capabilities: {
|
|
358
|
-
tmux:
|
|
359
|
-
|
|
556
|
+
tmux: {
|
|
557
|
+
...capabilities.tmux,
|
|
558
|
+
checked: mode !== "acpx"
|
|
559
|
+
},
|
|
560
|
+
acpx: {
|
|
561
|
+
...capabilities.acpx,
|
|
562
|
+
checked: mode !== "tmux"
|
|
563
|
+
}
|
|
360
564
|
},
|
|
565
|
+
openclaw,
|
|
361
566
|
notes: [
|
|
362
567
|
`Node.js ${MINIMUM_NODE_VERSION}+ and OpenClaw are required.`,
|
|
363
568
|
"Choose tmux (recommended), ACPX/ACP, or install both.",
|
|
364
569
|
"tmux supports Codex and Claude Code; ACPX supports Codex, Claude Code, and Cursor.",
|
|
365
570
|
"Claude tmux completion is hook-free and fails closed unless the local transcript schema is verified."
|
|
366
|
-
]
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
571
|
+
]
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function parseDoctorMode(value) {
|
|
575
|
+
const normalized = String(value).trim().toLowerCase();
|
|
576
|
+
if (normalized === "tmux" || normalized === "acpx" || normalized === "all") {
|
|
577
|
+
return normalized;
|
|
371
578
|
}
|
|
579
|
+
throw new Error("--mode must be one of: tmux, acpx, all");
|
|
580
|
+
}
|
|
581
|
+
function positiveMilliseconds(value, optionName) {
|
|
582
|
+
const parsed = Number(value);
|
|
583
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
584
|
+
throw new Error(`${optionName} must be a positive number`);
|
|
585
|
+
}
|
|
586
|
+
return Math.ceil(parsed);
|
|
372
587
|
}
|
|
373
588
|
function versionAtLeast(version, minimum) {
|
|
374
589
|
const parsed = version.split(".").slice(0, 3).map((part) => Number.parseInt(part, 10));
|
|
@@ -2160,12 +2375,12 @@ async function runList(options) {
|
|
|
2160
2375
|
const includeAll = Boolean(options.all);
|
|
2161
2376
|
const agentFilter = options.agent ? resolveExecutor({ kind: options.agent }).kind : undefined;
|
|
2162
2377
|
const statusFilter = options.status;
|
|
2163
|
-
const
|
|
2164
|
-
.map((conversation) => summarizeConversation(conversation))
|
|
2378
|
+
const storedConversations = listConversations(storeDir)
|
|
2165
2379
|
.filter((conversation) => includeAll || isActiveStatus(conversation.status))
|
|
2166
|
-
.filter((conversation) => !agentFilter || conversation.
|
|
2380
|
+
.filter((conversation) => !agentFilter || executorForConversation(conversation).kind === agentFilter)
|
|
2167
2381
|
.filter((conversation) => !statusFilter || conversation.status === statusFilter);
|
|
2168
|
-
const
|
|
2382
|
+
const conversations = storedConversations.map((conversation) => summarizeConversation(conversation));
|
|
2383
|
+
const delegated = storedConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), { terminalBridge: terminalBridgeEnabled(conversation) }));
|
|
2169
2384
|
const nativeScan = await buildNativeListGroups({ options, agentFilter, statusFilter });
|
|
2170
2385
|
printJson({
|
|
2171
2386
|
store_dir: storeDir,
|
|
@@ -2274,23 +2489,26 @@ async function terminalControlDiagnostics(provider) {
|
|
|
2274
2489
|
paneCount: (await provider.listPanes()).length
|
|
2275
2490
|
};
|
|
2276
2491
|
}
|
|
2277
|
-
function delegatedListEntry(task) {
|
|
2492
|
+
function delegatedListEntry(task, { terminalBridge = false } = {}) {
|
|
2278
2493
|
return {
|
|
2279
2494
|
...task,
|
|
2280
2495
|
id: task.conversation_id,
|
|
2496
|
+
short_ref: sessionShortRef(task.conversation_id),
|
|
2281
2497
|
source: "akk_delegate",
|
|
2282
2498
|
commands: {
|
|
2283
2499
|
send: canSendDelegated(task.status),
|
|
2284
2500
|
cancel: isWaitingForAgent(task.status),
|
|
2285
2501
|
close: task.status !== "closed",
|
|
2286
2502
|
status: true,
|
|
2287
|
-
approve:
|
|
2503
|
+
approve: terminalBridge && isActiveStatus(task.status)
|
|
2288
2504
|
}
|
|
2289
2505
|
};
|
|
2290
2506
|
}
|
|
2291
2507
|
function nativeListEntry(session, activeSessions) {
|
|
2508
|
+
const id = `native:${session.agent}:${session.pid}`;
|
|
2292
2509
|
return {
|
|
2293
|
-
id
|
|
2510
|
+
id,
|
|
2511
|
+
short_ref: sessionShortRef(id),
|
|
2294
2512
|
source: "native_active",
|
|
2295
2513
|
agent: session.agent,
|
|
2296
2514
|
status: "active",
|
|
@@ -2328,6 +2546,7 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
2328
2546
|
});
|
|
2329
2547
|
return {
|
|
2330
2548
|
id: bridge.terminalConversationId(session),
|
|
2549
|
+
short_ref: sessionShortRef(bridge.terminalConversationId(session)),
|
|
2331
2550
|
source: "terminal_control",
|
|
2332
2551
|
agent: session.agent,
|
|
2333
2552
|
status: "active",
|
|
@@ -2421,6 +2640,120 @@ function childPidsForRoot(root, processes) {
|
|
|
2421
2640
|
function canSendDelegated(status) {
|
|
2422
2641
|
return !["failed", "closed", "cancelled"].includes(status);
|
|
2423
2642
|
}
|
|
2643
|
+
async function resolveConversationSelectorOption(commandName, options) {
|
|
2644
|
+
if (!SESSION_SELECTOR_COMMANDS.has(String(commandName ?? "")) ||
|
|
2645
|
+
options.state) {
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
const supplied = stringValue(options.conversation ?? options.conversationId)?.trim();
|
|
2649
|
+
if (supplied && !isSessionSelectorSyntax(supplied)) {
|
|
2650
|
+
// Full authoritative IDs keep their existing command-specific validation
|
|
2651
|
+
// path. This avoids a discovery scan before option validation and preserves
|
|
2652
|
+
// precise downstream errors for closed or currently non-actionable state.
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
const candidates = await sessionSelectorCandidates(commandName, options);
|
|
2656
|
+
const resolution = resolveSessionSelector(supplied, candidates, {
|
|
2657
|
+
operation: commandName
|
|
2658
|
+
});
|
|
2659
|
+
options.conversation = resolution.id;
|
|
2660
|
+
delete options.conversationId;
|
|
2661
|
+
}
|
|
2662
|
+
function isSessionSelectorSyntax(value) {
|
|
2663
|
+
return (/^(?:only|latest|codex|claude|cursor|(?:codex|claude|cursor):latest)$/iu.test(value) ||
|
|
2664
|
+
/^@[0-9a-f]+$/iu.test(value));
|
|
2665
|
+
}
|
|
2666
|
+
async function sessionSelectorCandidates(commandName, options) {
|
|
2667
|
+
const storeDir = storeDirFromOptions(options);
|
|
2668
|
+
cleanupIdleConversations(storeDir, options);
|
|
2669
|
+
const storedConversations = listConversations(storeDir);
|
|
2670
|
+
const managed = storedConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), { terminalBridge: terminalBridgeEnabled(conversation) }));
|
|
2671
|
+
const managedTerminalKeys = new Set(storedConversations
|
|
2672
|
+
.filter((conversation) => isActiveStatus(conversation.status))
|
|
2673
|
+
.map((conversation) => terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
2674
|
+
? conversation.native_session_takeover
|
|
2675
|
+
: undefined)))
|
|
2676
|
+
.filter((key) => key !== undefined));
|
|
2677
|
+
const nativeScan = await buildNativeListGroups({
|
|
2678
|
+
options: {
|
|
2679
|
+
...options,
|
|
2680
|
+
noApprovalScan: commandName === "approve"
|
|
2681
|
+
? options.noApprovalScan
|
|
2682
|
+
: true
|
|
2683
|
+
},
|
|
2684
|
+
agentFilter: undefined,
|
|
2685
|
+
statusFilter: undefined
|
|
2686
|
+
});
|
|
2687
|
+
const observedAtMs = Date.now();
|
|
2688
|
+
return [
|
|
2689
|
+
...managed,
|
|
2690
|
+
...nativeScan.terminalControlled.filter((entry) => {
|
|
2691
|
+
const key = terminalControlSelectorKey(entry.terminal_control);
|
|
2692
|
+
return key === undefined || !managedTerminalKeys.has(key);
|
|
2693
|
+
}),
|
|
2694
|
+
...nativeScan.native
|
|
2695
|
+
].map((entry) => ({
|
|
2696
|
+
id: String(entry.id),
|
|
2697
|
+
agent: resolveExecutor({ kind: entry.agent }).kind,
|
|
2698
|
+
actionable: sessionEntrySupportsCommand(entry, commandName),
|
|
2699
|
+
...sessionEntryRecency(entry, observedAtMs),
|
|
2700
|
+
source: stringValue(entry.source),
|
|
2701
|
+
status: stringValue(entry.status),
|
|
2702
|
+
workspace: stringValue(entry.workspace ?? entry.cwd),
|
|
2703
|
+
label: stringValue(entry.request ?? entry.command)
|
|
2704
|
+
}));
|
|
2705
|
+
}
|
|
2706
|
+
function terminalControlSelectorKey(value) {
|
|
2707
|
+
if (!isRecord(value)) {
|
|
2708
|
+
return undefined;
|
|
2709
|
+
}
|
|
2710
|
+
const target = stringValue(value.target);
|
|
2711
|
+
const panePid = Number(value.panePid);
|
|
2712
|
+
if (!target || !Number.isSafeInteger(panePid) || panePid <= 1) {
|
|
2713
|
+
return undefined;
|
|
2714
|
+
}
|
|
2715
|
+
return JSON.stringify({
|
|
2716
|
+
target,
|
|
2717
|
+
pane_pid: panePid,
|
|
2718
|
+
socket_path: stringValue(value.socketPath) ?? null
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
function sessionEntrySupportsCommand(entry, commandName) {
|
|
2722
|
+
if (commandName === "summary") {
|
|
2723
|
+
commandName = "describe";
|
|
2724
|
+
}
|
|
2725
|
+
const commands = isRecord(entry.commands) ? entry.commands : {};
|
|
2726
|
+
if (typeof commands[commandName] === "boolean") {
|
|
2727
|
+
return commands[commandName] === true;
|
|
2728
|
+
}
|
|
2729
|
+
if (commandName === "describe") {
|
|
2730
|
+
return commands.status === true || entry.source === "native_active";
|
|
2731
|
+
}
|
|
2732
|
+
if (entry.source !== "akk_delegate") {
|
|
2733
|
+
return false;
|
|
2734
|
+
}
|
|
2735
|
+
if (commandName === "renew") {
|
|
2736
|
+
return entry.status === "stalled";
|
|
2737
|
+
}
|
|
2738
|
+
if (commandName === "retry-callback") {
|
|
2739
|
+
return ["callback_pending", "callback_failed"].includes(entry.status);
|
|
2740
|
+
}
|
|
2741
|
+
if (commandName === "recover") {
|
|
2742
|
+
return entry.status === "needs_recovery";
|
|
2743
|
+
}
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
function sessionEntryRecency(entry, observedAtMs) {
|
|
2747
|
+
const timestamp = Date.parse(String(entry.updated_at ?? entry.created_at ?? ""));
|
|
2748
|
+
if (Number.isFinite(timestamp)) {
|
|
2749
|
+
return { updatedAtMs: timestamp };
|
|
2750
|
+
}
|
|
2751
|
+
const elapsedSeconds = parseProcessElapsedSeconds(entry.elapsed);
|
|
2752
|
+
if (elapsedSeconds !== undefined) {
|
|
2753
|
+
return { updatedAtMs: observedAtMs - elapsedSeconds * 1000 };
|
|
2754
|
+
}
|
|
2755
|
+
return {};
|
|
2756
|
+
}
|
|
2424
2757
|
async function resolveTerminalConversationFromOptions(options) {
|
|
2425
2758
|
return createTerminalAgentBridge(options).resolveConversationId(stringValue(options.conversation ?? options.conversationId));
|
|
2426
2759
|
}
|
|
@@ -9654,18 +9987,18 @@ function usage() {
|
|
|
9654
9987
|
agent-knock-knock bootstrap-prompt --callback-command <command> [--agent ${agentList}]
|
|
9655
9988
|
agent-knock-knock delegate --request <text> [--agent ${agentList}] [--store-dir <dir>] [--all-proxy <url>] [--agent-timeout-minutes <minutes>] [--token <gateway-token>] [--send|--background]
|
|
9656
9989
|
agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all] [--managed-only] [--no-approval-scan] [--terminal-debug]
|
|
9657
|
-
agent-knock-knock status --conversation <id> [--store-dir <dir>] [--trace]
|
|
9658
|
-
agent-knock-knock describe --conversation <id> [--store-dir <dir>]
|
|
9659
|
-
agent-knock-knock send --conversation <id> --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>] [--agent-hard-timeout-minutes <minutes>]
|
|
9660
|
-
agent-knock-knock approve --conversation <id>
|
|
9661
|
-
agent-knock-knock cancel --conversation <id> [--all-proxy <url>]
|
|
9662
|
-
agent-knock-knock renew --conversation <id> [--minutes <inactivity-minutes>]
|
|
9990
|
+
agent-knock-knock status [--conversation <id|selector>] [--store-dir <dir>] [--trace]
|
|
9991
|
+
agent-knock-knock describe [--conversation <id|selector>] [--store-dir <dir>]
|
|
9992
|
+
agent-knock-knock send [--conversation <id|selector>] --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>] [--agent-hard-timeout-minutes <minutes>]
|
|
9993
|
+
agent-knock-knock approve [--conversation <id|selector>]
|
|
9994
|
+
agent-knock-knock cancel [--conversation <id|selector>] [--all-proxy <url>]
|
|
9995
|
+
agent-knock-knock renew [--conversation <id|selector>] [--minutes <inactivity-minutes>]
|
|
9663
9996
|
agent-knock-knock reconcile-monitors [--store-dir <dir>]
|
|
9664
9997
|
agent-knock-knock retry-callback --conversation <id> [--store-dir <dir>]
|
|
9665
9998
|
agent-knock-knock recover --conversation <id> [--session <name>] [--all-proxy <url>]
|
|
9666
9999
|
agent-knock-knock close --conversation <id> [--reason <text>]
|
|
9667
|
-
agent-knock-knock install-openclaw [--openclaw-bin <path>] [--skill-path <path>] [--skill-only] [--no-restart]
|
|
9668
|
-
agent-knock-knock doctor
|
|
10000
|
+
agent-knock-knock install-openclaw [--workspace <path>] [--default-agent ${agentList}] [--mode tmux|acpx|all] [--verify] [--openclaw-bin <path>] [--skill-path <path>] [--skill-only] [--no-restart]
|
|
10001
|
+
agent-knock-knock doctor [--mode tmux|acpx|all] [--workspace <path>] [--openclaw-bin <path>]
|
|
9669
10002
|
agent-knock-knock agent takeover --agent codex --session-id <id> --strategy terminate_then_resume|terminal_control|fork [--create-conversation]
|
|
9670
10003
|
agent-knock-knock callback --state <file> --message-json <json> [--record-only]
|
|
9671
10004
|
agent-knock-knock transcript --log <file> [--include-raw]
|