@scotthuang/agent-knock-knock 0.2.1 → 0.2.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 +6 -0
- package/README.md +45 -159
- package/dist/src/cli.js +309 -11
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-session-provider.d.ts +12 -0
- package/dist/src/codex-session-provider.js.map +1 -1
- package/dist/src/openclaw-plugin.js +37 -2
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/terminal-control-provider.d.ts +56 -0
- package/dist/src/terminal-control-provider.js +154 -0
- package/dist/src/terminal-control-provider.js.map +1 -0
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +5 -0
package/dist/src/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { writeRuntimeLog } from "./runtime-log.js";
|
|
|
14
14
|
import { formatTranscript, readNdjsonLog } from "./transcript.js";
|
|
15
15
|
import { appendEvent, defaultStoreDir, listConversations, logPathForStatePath, loadConversationById, loadState, messageEvent, pathsForConversation, pathsForConversationDir, saveState, statePathForConversationId } from "./store.js";
|
|
16
16
|
import { planFork, planSafeResume, planTakeover } from "./session-takeover-planner.js";
|
|
17
|
+
import { StaticTerminalControlProvider, TmuxTerminalControlProvider, enrichActiveProcessesWithTerminalControl } from "./terminal-control-provider.js";
|
|
17
18
|
const DEFAULT_IDLE_TIMEOUT_MINUTES = 10080;
|
|
18
19
|
const DEFAULT_AGENT_TIMEOUT_MINUTES = 60;
|
|
19
20
|
const DEFAULT_MONITOR_POLL_INTERVAL_MS = 5000;
|
|
@@ -89,10 +90,13 @@ async function runCommand(commandName, options) {
|
|
|
89
90
|
runList(options);
|
|
90
91
|
}
|
|
91
92
|
else if (commandName === "status") {
|
|
92
|
-
runStatus(options);
|
|
93
|
+
await runStatus(options);
|
|
93
94
|
}
|
|
94
95
|
else if (commandName === "send") {
|
|
95
|
-
runSend(options);
|
|
96
|
+
await runSend(options);
|
|
97
|
+
}
|
|
98
|
+
else if (commandName === "approve") {
|
|
99
|
+
await runApprove(options);
|
|
96
100
|
}
|
|
97
101
|
else if (commandName === "cancel") {
|
|
98
102
|
runCancel(options);
|
|
@@ -242,7 +246,7 @@ async function runAgentDiscover(options) {
|
|
|
242
246
|
agent,
|
|
243
247
|
scope,
|
|
244
248
|
capabilities,
|
|
245
|
-
active: await provider
|
|
249
|
+
active: await listActiveSessionsWithTerminalControl(provider, options)
|
|
246
250
|
};
|
|
247
251
|
}
|
|
248
252
|
throw new Error(`unsupported discover scope: ${scope}`);
|
|
@@ -267,7 +271,7 @@ async function runAgentTakeover(options) {
|
|
|
267
271
|
};
|
|
268
272
|
}
|
|
269
273
|
if (strategy === "safe_resume") {
|
|
270
|
-
const plan = planSafeResume(session, await provider
|
|
274
|
+
const plan = planSafeResume(session, await listActiveSessionsWithTerminalControl(provider, options));
|
|
271
275
|
if (plan.allowed && options.createConversation) {
|
|
272
276
|
const modelInfo = await provider.getSessionModel(session.id);
|
|
273
277
|
const attached = createNativeSessionConversation({
|
|
@@ -297,7 +301,7 @@ async function runAgentTakeover(options) {
|
|
|
297
301
|
};
|
|
298
302
|
}
|
|
299
303
|
if (strategy === "terminate_then_resume") {
|
|
300
|
-
const activeSessions = await provider
|
|
304
|
+
const activeSessions = await listActiveSessionsWithTerminalControl(provider, options);
|
|
301
305
|
const plan = planTakeover(session, activeSessions);
|
|
302
306
|
if (options.confirmTerminate === true) {
|
|
303
307
|
const expectedPid = Number(required(options.expectedPid, "--expected-pid is required with --confirm-terminate"));
|
|
@@ -332,7 +336,7 @@ async function runAgentTakeover(options) {
|
|
|
332
336
|
const termination = terminateProcessTarget(target, {
|
|
333
337
|
timeoutMs: Number(options.terminateTimeoutMs ?? 3000)
|
|
334
338
|
});
|
|
335
|
-
const activeAfterTermination = await provider
|
|
339
|
+
const activeAfterTermination = await listActiveSessionsWithTerminalControl(provider, options);
|
|
336
340
|
const afterTerminationPlan = planTakeover(session, activeAfterTermination);
|
|
337
341
|
if (afterTerminationPlan.targets.some((candidate) => candidate.sessionId === session.id || candidate.pid === expectedPid)) {
|
|
338
342
|
return {
|
|
@@ -379,6 +383,60 @@ async function runAgentTakeover(options) {
|
|
|
379
383
|
plan
|
|
380
384
|
};
|
|
381
385
|
}
|
|
386
|
+
if (strategy === "terminal_control") {
|
|
387
|
+
const activeSessions = await listActiveSessionsWithTerminalControl(provider, options);
|
|
388
|
+
const plan = planTerminalControlTakeover(session, activeSessions);
|
|
389
|
+
if (options.confirmTerminal === true) {
|
|
390
|
+
const terminalTarget = String(required(options.terminalTarget, "--terminal-target is required with --confirm-terminal"));
|
|
391
|
+
if (!options.createConversation) {
|
|
392
|
+
throw new Error("--create-conversation is required with --confirm-terminal");
|
|
393
|
+
}
|
|
394
|
+
const target = plan.targets.find((candidate) => candidate.terminalControl?.target === terminalTarget);
|
|
395
|
+
if (!plan.allowed || !target?.terminalControl) {
|
|
396
|
+
return {
|
|
397
|
+
agent,
|
|
398
|
+
sessionId,
|
|
399
|
+
strategy,
|
|
400
|
+
status: "blocked",
|
|
401
|
+
sideEffectsExecuted: false,
|
|
402
|
+
plan,
|
|
403
|
+
error: {
|
|
404
|
+
code: "terminal_target_unavailable",
|
|
405
|
+
message: `No matching tmux-controlled Codex process was found for ${terminalTarget}`
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const modelInfo = await provider.getSessionModel(session.id);
|
|
410
|
+
const attached = createNativeSessionConversation({
|
|
411
|
+
agent,
|
|
412
|
+
strategy,
|
|
413
|
+
session,
|
|
414
|
+
modelInfo,
|
|
415
|
+
options,
|
|
416
|
+
takeoverMatchKind: "terminal_control",
|
|
417
|
+
terminalControl: target.terminalControl,
|
|
418
|
+
needsBootstrap: false
|
|
419
|
+
});
|
|
420
|
+
return {
|
|
421
|
+
agent,
|
|
422
|
+
sessionId,
|
|
423
|
+
strategy,
|
|
424
|
+
status: "attached",
|
|
425
|
+
sideEffectsExecuted: true,
|
|
426
|
+
plan,
|
|
427
|
+
terminalControl: target.terminalControl,
|
|
428
|
+
...attached
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
agent,
|
|
433
|
+
sessionId,
|
|
434
|
+
strategy,
|
|
435
|
+
status: plan.allowed ? "requires_confirmation" : "blocked",
|
|
436
|
+
sideEffectsExecuted: false,
|
|
437
|
+
plan
|
|
438
|
+
};
|
|
439
|
+
}
|
|
382
440
|
if (strategy === "fork") {
|
|
383
441
|
const contextPackage = await provider.getForkContext({
|
|
384
442
|
sessionId,
|
|
@@ -516,6 +574,120 @@ function selectTerminateTarget({ plan, session, activeSessions, expectedPid, all
|
|
|
516
574
|
matchKind: "cwd_only_confirmed"
|
|
517
575
|
};
|
|
518
576
|
}
|
|
577
|
+
async function listActiveSessionsWithTerminalControl(provider, options) {
|
|
578
|
+
const activeSessions = await provider.listActiveSessions();
|
|
579
|
+
return enrichActiveProcessesWithTerminalControl(activeSessions, createTerminalControlProvider(options));
|
|
580
|
+
}
|
|
581
|
+
function createTerminalControlProvider(options) {
|
|
582
|
+
if (options.terminalsJson || options.terminalScreensJson) {
|
|
583
|
+
return new StaticTerminalControlProvider({
|
|
584
|
+
panes: parseJsonOption(options.terminalsJson, "--terminals-json"),
|
|
585
|
+
screens: parseJsonOption(options.terminalScreensJson, "--terminal-screens-json")
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return new TmuxTerminalControlProvider();
|
|
589
|
+
}
|
|
590
|
+
function planTerminalControlTakeover(session, activeSessions) {
|
|
591
|
+
const matched = activeSessions
|
|
592
|
+
.filter((process) => process.kind === "codex_cli" &&
|
|
593
|
+
process.terminalControl &&
|
|
594
|
+
(process.sessionId === session.id ||
|
|
595
|
+
(!process.sessionId && process.cwd === session.cwd)));
|
|
596
|
+
const matchedPidSet = new Set(matched.map((process) => process.pid));
|
|
597
|
+
const targets = matched
|
|
598
|
+
.filter((process) => !process.ppid || !matchedPidSet.has(process.ppid))
|
|
599
|
+
.map((process) => ({
|
|
600
|
+
pid: process.pid,
|
|
601
|
+
childPids: matched
|
|
602
|
+
.filter((child) => child.ppid === process.pid)
|
|
603
|
+
.map((child) => child.pid),
|
|
604
|
+
cwd: process.cwd,
|
|
605
|
+
command: process.command,
|
|
606
|
+
sessionId: process.sessionId,
|
|
607
|
+
terminalControl: process.terminalControl
|
|
608
|
+
}));
|
|
609
|
+
const exactTargets = targets.filter((target) => target.sessionId === session.id);
|
|
610
|
+
const selectableTargets = exactTargets.length > 0 ? exactTargets : targets;
|
|
611
|
+
return {
|
|
612
|
+
mode: "terminal_control",
|
|
613
|
+
allowed: selectableTargets.length === 1,
|
|
614
|
+
requiresConfirmation: selectableTargets.length === 1,
|
|
615
|
+
reason: selectableTargets.length === 0
|
|
616
|
+
? "no_terminal_control_target"
|
|
617
|
+
: selectableTargets.length === 1
|
|
618
|
+
? "terminal_control_available"
|
|
619
|
+
: "ambiguous_terminal_control_target",
|
|
620
|
+
targets: selectableTargets
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function terminalControlFromTakeover(nativeTakeover) {
|
|
624
|
+
if (!isRecord(nativeTakeover)) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
const terminalControl = nativeTakeover["terminal_control"];
|
|
628
|
+
if (!isRecord(terminalControl) || terminalControl.kind !== "tmux") {
|
|
629
|
+
return undefined;
|
|
630
|
+
}
|
|
631
|
+
const target = stringValue(terminalControl.target);
|
|
632
|
+
const session = stringValue(terminalControl.session);
|
|
633
|
+
const window = Number(terminalControl.window);
|
|
634
|
+
const pane = Number(terminalControl.pane);
|
|
635
|
+
const panePid = Number(terminalControl.panePid);
|
|
636
|
+
if (!target || !session || !Number.isInteger(window) || !Number.isInteger(pane) || !Number.isInteger(panePid)) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
kind: "tmux",
|
|
641
|
+
target,
|
|
642
|
+
session,
|
|
643
|
+
window,
|
|
644
|
+
pane,
|
|
645
|
+
panePid,
|
|
646
|
+
currentCommand: stringValue(terminalControl.currentCommand),
|
|
647
|
+
currentPath: stringValue(terminalControl.currentPath),
|
|
648
|
+
capabilities: ["capture_screen", "send_keys", "terminal_approval"]
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function detectCodexApprovalPrompt(screen) {
|
|
652
|
+
const approvalMarkers = [
|
|
653
|
+
"Would you like to run the following command?",
|
|
654
|
+
"Would you like to make the following edits?",
|
|
655
|
+
"Would you like to grant these permissions?",
|
|
656
|
+
"needs your approval."
|
|
657
|
+
];
|
|
658
|
+
if (!approvalMarkers.some((marker) => screen.includes(marker))) {
|
|
659
|
+
return {
|
|
660
|
+
approvable: false,
|
|
661
|
+
reason: "no Codex approval prompt was detected in the terminal screen"
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
for (const line of screen.split(/\r?\n/)) {
|
|
665
|
+
const match = /^[\s›]*1\.\s+(Yes,[^(]+)\(([^)]+)\)/u.exec(line.trim());
|
|
666
|
+
if (!match) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const key = match[2].trim();
|
|
670
|
+
if (key !== "y") {
|
|
671
|
+
return {
|
|
672
|
+
approvable: false,
|
|
673
|
+
reason: `primary approval shortcut is ${key}, not y`
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
approvable: true,
|
|
678
|
+
key,
|
|
679
|
+
label: match[1].trim()
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
approvable: false,
|
|
684
|
+
reason: "no primary approve option with a shortcut was detected"
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function screenExcerpt(screen, maxLength = 4000) {
|
|
688
|
+
const lines = screen.trimEnd().split(/\r?\n/);
|
|
689
|
+
return lines.slice(Math.max(0, lines.length - 80)).join("\n").slice(-maxLength);
|
|
690
|
+
}
|
|
519
691
|
function createForkConversation({ agent, strategy, session, contextPackage, forkSummary, modelInfo, options }) {
|
|
520
692
|
const workspace = session.cwd;
|
|
521
693
|
const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(workspace));
|
|
@@ -609,7 +781,7 @@ function createForkConversation({ agent, strategy, session, contextPackage, fork
|
|
|
609
781
|
next: `Use AKK send ${forkedConversation.conversation_id}: <message> to start the forked ${agent} session with the approved summary.`
|
|
610
782
|
};
|
|
611
783
|
}
|
|
612
|
-
function createNativeSessionConversation({ agent, strategy, session, modelInfo, options, takeoverMatchKind = strategy }) {
|
|
784
|
+
function createNativeSessionConversation({ agent, strategy, session, modelInfo, options, takeoverMatchKind = strategy, terminalControl = undefined, needsBootstrap = true }) {
|
|
613
785
|
const workspace = session.cwd;
|
|
614
786
|
const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(workspace));
|
|
615
787
|
cleanupIdleConversations(storeDir, options);
|
|
@@ -666,7 +838,8 @@ function createNativeSessionConversation({ agent, strategy, session, modelInfo,
|
|
|
666
838
|
acpx_model: modelInfo?.acpxModel,
|
|
667
839
|
model_source: modelInfo?.source,
|
|
668
840
|
takeover_match_kind: takeoverMatchKind,
|
|
669
|
-
|
|
841
|
+
terminal_control: terminalControl,
|
|
842
|
+
needs_bootstrap: needsBootstrap
|
|
670
843
|
}
|
|
671
844
|
}, paths);
|
|
672
845
|
saveState(paths.statePath, attachedConversation);
|
|
@@ -1118,7 +1291,7 @@ function runList(options) {
|
|
|
1118
1291
|
cleanup
|
|
1119
1292
|
});
|
|
1120
1293
|
}
|
|
1121
|
-
function runStatus(options) {
|
|
1294
|
+
async function runStatus(options) {
|
|
1122
1295
|
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1123
1296
|
const { conversation, statePath, logPath } = loadConversationFromOptions(options);
|
|
1124
1297
|
const events = readExistingEvents(logPath);
|
|
@@ -1133,6 +1306,24 @@ function runStatus(options) {
|
|
|
1133
1306
|
if (options.trace) {
|
|
1134
1307
|
result.trace = buildConversationTrace({ conversation, events, logPath });
|
|
1135
1308
|
}
|
|
1309
|
+
const terminalControl = terminalControlFromTakeover(isRecord(conversation.native_session_takeover) ? conversation.native_session_takeover : undefined);
|
|
1310
|
+
if (terminalControl) {
|
|
1311
|
+
result.terminal_control = terminalControl;
|
|
1312
|
+
try {
|
|
1313
|
+
const screen = await createTerminalControlProvider(options).capture(terminalControl.target, {
|
|
1314
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120)
|
|
1315
|
+
});
|
|
1316
|
+
result.terminal_screen = {
|
|
1317
|
+
excerpt: screenExcerpt(screen),
|
|
1318
|
+
approval: detectCodexApprovalPrompt(screen)
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
catch (error) {
|
|
1322
|
+
result.terminal_screen = {
|
|
1323
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1136
1327
|
printJson(result);
|
|
1137
1328
|
runtimeLog("info", "task_status_read", {
|
|
1138
1329
|
conversation_id: conversation.conversation_id,
|
|
@@ -1143,7 +1334,7 @@ function runStatus(options) {
|
|
|
1143
1334
|
trace: Boolean(options.trace)
|
|
1144
1335
|
});
|
|
1145
1336
|
}
|
|
1146
|
-
function runSend(options) {
|
|
1337
|
+
async function runSend(options) {
|
|
1147
1338
|
const messageBody = required(options.message ?? options.request, "--message is required");
|
|
1148
1339
|
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1149
1340
|
const { conversation, statePath, logPath } = loadConversationFromOptions(options);
|
|
@@ -1215,6 +1406,21 @@ function runSend(options) {
|
|
|
1215
1406
|
includeForkTakeoverBootstrap: needsForkTakeoverBootstrap,
|
|
1216
1407
|
forkTakeover: forkTakeoverForSend
|
|
1217
1408
|
});
|
|
1409
|
+
const terminalControlForSend = terminalControlFromTakeover(nativeTakeoverForSend);
|
|
1410
|
+
if (terminalControlForSend) {
|
|
1411
|
+
await runTerminalControlSend({
|
|
1412
|
+
options,
|
|
1413
|
+
conversation,
|
|
1414
|
+
nextConversation,
|
|
1415
|
+
statePath,
|
|
1416
|
+
logPath,
|
|
1417
|
+
executor,
|
|
1418
|
+
message,
|
|
1419
|
+
terminalControl: terminalControlForSend,
|
|
1420
|
+
needsNativeTakeoverBootstrap
|
|
1421
|
+
});
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1218
1424
|
if (nativeTakeoverForSend?.["native_session_id"] && executor.kind === "codex") {
|
|
1219
1425
|
runNativeCodexResumeSend({
|
|
1220
1426
|
options,
|
|
@@ -1476,6 +1682,94 @@ function runSend(options) {
|
|
|
1476
1682
|
budget: budgetAction(deliveredConversation)
|
|
1477
1683
|
});
|
|
1478
1684
|
}
|
|
1685
|
+
async function runApprove(options) {
|
|
1686
|
+
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1687
|
+
const { conversation, statePath, logPath } = loadConversationFromOptions(options);
|
|
1688
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
1689
|
+
? conversation.native_session_takeover
|
|
1690
|
+
: undefined;
|
|
1691
|
+
const terminalControl = terminalControlFromTakeover(nativeTakeover);
|
|
1692
|
+
if (!terminalControl) {
|
|
1693
|
+
throw new Error(`conversation ${conversation.conversation_id} is not controlled through a terminal`);
|
|
1694
|
+
}
|
|
1695
|
+
const provider = createTerminalControlProvider(options);
|
|
1696
|
+
const screen = await provider.capture(terminalControl.target, {
|
|
1697
|
+
scrollbackLines: Number(options.scrollbackLines ?? 120)
|
|
1698
|
+
});
|
|
1699
|
+
const approval = detectCodexApprovalPrompt(screen);
|
|
1700
|
+
if (!approval.approvable) {
|
|
1701
|
+
printJson({
|
|
1702
|
+
conversation,
|
|
1703
|
+
approved: false,
|
|
1704
|
+
blocked: true,
|
|
1705
|
+
reason: approval.reason,
|
|
1706
|
+
terminal_control: terminalControl,
|
|
1707
|
+
screen_excerpt: screenExcerpt(screen)
|
|
1708
|
+
});
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
await provider.sendKeys(terminalControl.target, [approval.key]);
|
|
1712
|
+
appendEvent(logPath, {
|
|
1713
|
+
ts: new Date().toISOString(),
|
|
1714
|
+
conversation_id: conversation.conversation_id,
|
|
1715
|
+
event: "terminal_approval_send",
|
|
1716
|
+
terminal_control: terminalControl,
|
|
1717
|
+
key: approval.key,
|
|
1718
|
+
label: approval.label
|
|
1719
|
+
});
|
|
1720
|
+
runtimeLog("info", "terminal_approval_send", {
|
|
1721
|
+
conversation_id: conversation.conversation_id,
|
|
1722
|
+
terminal_target: terminalControl.target,
|
|
1723
|
+
key: approval.key,
|
|
1724
|
+
label: approval.label
|
|
1725
|
+
});
|
|
1726
|
+
saveState(statePath, {
|
|
1727
|
+
...conversation,
|
|
1728
|
+
updated_at: new Date().toISOString()
|
|
1729
|
+
});
|
|
1730
|
+
printJson({
|
|
1731
|
+
conversation,
|
|
1732
|
+
approved: true,
|
|
1733
|
+
terminal_control: terminalControl,
|
|
1734
|
+
key: approval.key,
|
|
1735
|
+
label: approval.label
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
async function runTerminalControlSend({ options, conversation, nextConversation, statePath, logPath, executor, message, terminalControl, needsNativeTakeoverBootstrap }) {
|
|
1739
|
+
const provider = createTerminalControlProvider(options);
|
|
1740
|
+
await provider.sendText(terminalControl.target, String(message.body ?? ""));
|
|
1741
|
+
await provider.sendKeys(terminalControl.target, ["Enter"]);
|
|
1742
|
+
appendEvent(logPath, {
|
|
1743
|
+
ts: new Date().toISOString(),
|
|
1744
|
+
conversation_id: conversation.conversation_id,
|
|
1745
|
+
event: "terminal_message_send",
|
|
1746
|
+
executor,
|
|
1747
|
+
terminal_control: terminalControl,
|
|
1748
|
+
message: textSummary(message.body)
|
|
1749
|
+
});
|
|
1750
|
+
runtimeLog("info", "terminal_message_send", {
|
|
1751
|
+
conversation_id: conversation.conversation_id,
|
|
1752
|
+
agent: executor.kind,
|
|
1753
|
+
terminal_target: terminalControl.target,
|
|
1754
|
+
message: textSummary(message.body)
|
|
1755
|
+
});
|
|
1756
|
+
const deliveredConversation = markTakeoverBootstrapped({
|
|
1757
|
+
conversation: nextConversation,
|
|
1758
|
+
statePath,
|
|
1759
|
+
logPath,
|
|
1760
|
+
executor,
|
|
1761
|
+
native: needsNativeTakeoverBootstrap,
|
|
1762
|
+
fork: false
|
|
1763
|
+
});
|
|
1764
|
+
printJson({
|
|
1765
|
+
conversation: deliveredConversation,
|
|
1766
|
+
message,
|
|
1767
|
+
delivered: true,
|
|
1768
|
+
terminal_control: terminalControl,
|
|
1769
|
+
executor,
|
|
1770
|
+
budget: budgetAction(deliveredConversation)
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1479
1773
|
function runNativeCodexResumeSend({ options, conversation, nextConversation, statePath, logPath, executor, executorEnv, message, payload, nativeTakeover, needsNativeTakeoverBootstrap }) {
|
|
1480
1774
|
const codexPath = resolveExecutable("codex");
|
|
1481
1775
|
const nativeSessionId = String(nativeTakeover["native_session_id"]);
|
|
@@ -3349,6 +3643,9 @@ function required(value, message) {
|
|
|
3349
3643
|
function isRecord(value) {
|
|
3350
3644
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3351
3645
|
}
|
|
3646
|
+
function stringValue(value) {
|
|
3647
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
3648
|
+
}
|
|
3352
3649
|
function parseOptionalJson(text) {
|
|
3353
3650
|
try {
|
|
3354
3651
|
return JSON.parse(String(text));
|
|
@@ -3517,13 +3814,14 @@ function usage() {
|
|
|
3517
3814
|
agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all]
|
|
3518
3815
|
agent-knock-knock status --conversation <id> [--store-dir <dir>] [--trace]
|
|
3519
3816
|
agent-knock-knock send --conversation <id> --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>]
|
|
3817
|
+
agent-knock-knock approve --conversation <id>
|
|
3520
3818
|
agent-knock-knock cancel --conversation <id> [--all-proxy <url>]
|
|
3521
3819
|
agent-knock-knock recover --conversation <id> [--session <name>] [--all-proxy <url>]
|
|
3522
3820
|
agent-knock-knock close --conversation <id> [--reason <text>]
|
|
3523
3821
|
agent-knock-knock install-openclaw [--openclaw-bin <path>] [--skill-path <path>] [--no-restart]
|
|
3524
3822
|
agent-knock-knock doctor
|
|
3525
3823
|
agent-knock-knock agent discover --agent codex --scope capabilities|sessions|active
|
|
3526
|
-
agent-knock-knock agent takeover --agent codex --session-id <id> --strategy safe_resume|terminate_then_resume|fork [--create-conversation]
|
|
3824
|
+
agent-knock-knock agent takeover --agent codex --session-id <id> --strategy safe_resume|terminate_then_resume|terminal_control|fork [--create-conversation]
|
|
3527
3825
|
agent-knock-knock callback --state <file> --message-json <json> [--record-only]
|
|
3528
3826
|
agent-knock-knock transcript --log <file> [--include-raw]
|
|
3529
3827
|
agent-knock-knock transcript --conversation <dir> [--include-raw]
|