@scotthuang/agent-knock-knock 0.4.0 → 0.5.1
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 +26 -0
- package/README.md +82 -59
- 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 +3 -8
- package/dist/src/openclaw-doctor.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +2 -5
- package/dist/src/openclaw-plugin-helpers.js +67 -54
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin.js +143 -245
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/docs/quickstart-tmux.md +76 -17
- package/openclaw.plugin.json +4 -20
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +45 -59
package/dist/src/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { captureClaudeTranscriptAnchor, defaultClaudeHome, detectClaudeTranscrip
|
|
|
11
11
|
import { CodexLocalSessionProvider } from "./codex-local-session-provider.js";
|
|
12
12
|
import { CodexStoreAdapter } from "./codex-store-adapter.js";
|
|
13
13
|
import { applyMessageToConversation, budgetAction, createConversation, createMessage, executorForConversation, extractStructuredMessage, parseMessageJson, resolveExecutor } from "./protocol.js";
|
|
14
|
-
import { EXECUTOR_KINDS, executorDefinitionForKind
|
|
14
|
+
import { EXECUTOR_KINDS, executorDefinitionForKind } from "./executors.js";
|
|
15
15
|
import { redactString, writeRuntimeLog } from "./runtime-log.js";
|
|
16
16
|
import { formatTranscript, readNdjsonLog } from "./transcript.js";
|
|
17
17
|
import { appendEvent, defaultStoreDir, ensureDir, listConversations, logPathForStatePath, loadConversationById, loadState, messageEvent, pathsForConversation, pathsForConversationDir, saveState, statePathForConversationId } from "./store.js";
|
|
@@ -70,8 +70,6 @@ const CONVERSATION_STATUSES = new Set([
|
|
|
70
70
|
]);
|
|
71
71
|
const SESSION_SELECTOR_COMMANDS = new Set([
|
|
72
72
|
"status",
|
|
73
|
-
"describe",
|
|
74
|
-
"summary",
|
|
75
73
|
"send",
|
|
76
74
|
"approve",
|
|
77
75
|
"cancel",
|
|
@@ -110,9 +108,7 @@ class InlineCodexSessionAdapter {
|
|
|
110
108
|
}
|
|
111
109
|
const command = process.argv[2];
|
|
112
110
|
const rawArgs = process.argv.slice(3);
|
|
113
|
-
const args =
|
|
114
|
-
? { agentCommand: rawArgs[0], ...parseArgs(rawArgs.slice(1)) }
|
|
115
|
-
: parseArgs(rawArgs);
|
|
111
|
+
const args = parseArgs(rawArgs);
|
|
116
112
|
runtimeLog("info", "cli_start", {
|
|
117
113
|
command: command ?? "help",
|
|
118
114
|
cwd: process.cwd(),
|
|
@@ -151,9 +147,6 @@ async function runCommand(commandName, options) {
|
|
|
151
147
|
else if (commandName === "status") {
|
|
152
148
|
await runStatus(options);
|
|
153
149
|
}
|
|
154
|
-
else if (commandName === "describe" || commandName === "summary") {
|
|
155
|
-
await runDescribe(options);
|
|
156
|
-
}
|
|
157
150
|
else if (commandName === "send") {
|
|
158
151
|
await runSend(options);
|
|
159
152
|
}
|
|
@@ -190,9 +183,6 @@ async function runCommand(commandName, options) {
|
|
|
190
183
|
else if (commandName === "monitor") {
|
|
191
184
|
await runMonitor(options);
|
|
192
185
|
}
|
|
193
|
-
else if (commandName === "agent") {
|
|
194
|
-
await runAgent(options);
|
|
195
|
-
}
|
|
196
186
|
else {
|
|
197
187
|
usage();
|
|
198
188
|
process.exitCode = commandName ? 1 : 0;
|
|
@@ -204,15 +194,16 @@ function runInstallOpenClaw(options) {
|
|
|
204
194
|
const workspace = options.workspace === undefined
|
|
205
195
|
? undefined
|
|
206
196
|
: canonicalWorkspace(options.workspace);
|
|
207
|
-
|
|
197
|
+
if (options.defaultAgent !== undefined) {
|
|
198
|
+
throw new Error("--default-agent was removed; AKK now selects the only eligible idle tmux pane");
|
|
199
|
+
}
|
|
208
200
|
if (options.mode !== undefined) {
|
|
209
201
|
throw new Error("--mode was removed; Agent Knock Knock now uses tmux only");
|
|
210
202
|
}
|
|
211
203
|
if (skillOnly &&
|
|
212
204
|
(workspace !== undefined ||
|
|
213
|
-
defaultAgent !== undefined ||
|
|
214
205
|
options.verify === true)) {
|
|
215
|
-
throw new Error("--skill-only cannot be combined with --workspace, --
|
|
206
|
+
throw new Error("--skill-only cannot be combined with --workspace, --mode, or --verify");
|
|
216
207
|
}
|
|
217
208
|
const needsOpenClaw = !skillOnly || options.noRestart !== true || options.verify === true;
|
|
218
209
|
const openclawBin = needsOpenClaw
|
|
@@ -238,13 +229,7 @@ function runInstallOpenClaw(options) {
|
|
|
238
229
|
: [{
|
|
239
230
|
path: "plugins.entries.agent-knock-knock.config.workspace",
|
|
240
231
|
value: workspace
|
|
241
|
-
}])
|
|
242
|
-
...(defaultAgent === undefined
|
|
243
|
-
? []
|
|
244
|
-
: [{
|
|
245
|
-
path: "plugins.entries.agent-knock-knock.config.defaultAgent",
|
|
246
|
-
value: defaultAgent
|
|
247
|
-
}]),
|
|
232
|
+
}])
|
|
248
233
|
];
|
|
249
234
|
runCheckedCommand(openclawBin, ["config", "set", "--batch-json", JSON.stringify(configOperations)], { label: "openclaw config set" });
|
|
250
235
|
steps.push({
|
|
@@ -280,8 +265,7 @@ function runInstallOpenClaw(options) {
|
|
|
280
265
|
: false;
|
|
281
266
|
const nextActions = installNextActions({
|
|
282
267
|
pendingRestart,
|
|
283
|
-
verification
|
|
284
|
-
agent: defaultAgent ?? "codex"
|
|
268
|
+
verification
|
|
285
269
|
});
|
|
286
270
|
printJson({
|
|
287
271
|
installed: true,
|
|
@@ -289,7 +273,6 @@ function runInstallOpenClaw(options) {
|
|
|
289
273
|
pending_restart: pendingRestart,
|
|
290
274
|
mode: skillOnly ? "skill_only" : "full",
|
|
291
275
|
execution_mode: "tmux",
|
|
292
|
-
default_agent: defaultAgent ?? null,
|
|
293
276
|
workspace: workspace ?? null,
|
|
294
277
|
package_root: root,
|
|
295
278
|
openclaw_bin: openclawBin ?? null,
|
|
@@ -314,17 +297,38 @@ function canonicalWorkspace(value) {
|
|
|
314
297
|
}
|
|
315
298
|
return canonical;
|
|
316
299
|
}
|
|
317
|
-
function
|
|
318
|
-
if (
|
|
319
|
-
return
|
|
300
|
+
function matchesConfiguredWorkspace(configuredWorkspace, candidateWorkspace) {
|
|
301
|
+
if (configuredWorkspace === undefined) {
|
|
302
|
+
return true;
|
|
320
303
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
304
|
+
if (candidateWorkspace === undefined || candidateWorkspace === null) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
return canonicalWorkspace(configuredWorkspace) ===
|
|
309
|
+
canonicalWorkspace(candidateWorkspace);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return false;
|
|
324
313
|
}
|
|
325
|
-
return normalized;
|
|
326
314
|
}
|
|
327
|
-
function
|
|
315
|
+
function assertConfiguredWorkspace(configuredWorkspace, candidateWorkspace, subject) {
|
|
316
|
+
if (configuredWorkspace === undefined) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const configured = canonicalWorkspace(configuredWorkspace);
|
|
320
|
+
let candidate;
|
|
321
|
+
try {
|
|
322
|
+
candidate = canonicalWorkspace(candidateWorkspace);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
throw new Error(`refusing ${subject}; its workspace cannot be verified against configured workspace ${configured}`);
|
|
326
|
+
}
|
|
327
|
+
if (candidate !== configured) {
|
|
328
|
+
throw new Error(`refusing ${subject}; workspace ${candidate} does not match configured workspace ${configured}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function installNextActions({ pendingRestart, verification }) {
|
|
328
332
|
if (pendingRestart) {
|
|
329
333
|
return [
|
|
330
334
|
{
|
|
@@ -355,8 +359,8 @@ function installNextActions({ pendingRestart, verification, agent }) {
|
|
|
355
359
|
}];
|
|
356
360
|
}
|
|
357
361
|
return [{
|
|
358
|
-
action: "
|
|
359
|
-
command:
|
|
362
|
+
action: "start_shared_terminal",
|
|
363
|
+
command: "tmux new -s akk-work -c \"$PWD\" codex # use claude instead when preferred"
|
|
360
364
|
}];
|
|
361
365
|
}
|
|
362
366
|
function installOpenClawPlugin(openclawBin, root) {
|
|
@@ -438,13 +442,7 @@ function buildDoctorReport(options) {
|
|
|
438
442
|
...(options.workspace ? { workspace: String(options.workspace) } : {}),
|
|
439
443
|
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
440
444
|
});
|
|
441
|
-
const selectedAgent = optionalExecutorKind(options.defaultAgent) ??
|
|
442
|
-
openclaw.default_agent ??
|
|
443
|
-
"codex";
|
|
444
|
-
const selectedAgentReady = capabilities.tmux.status === "ready" &&
|
|
445
|
-
capabilities.tmux.agents.includes(selectedAgent);
|
|
446
445
|
const ok = capabilities.readiness === "ready" &&
|
|
447
|
-
selectedAgentReady &&
|
|
448
446
|
filesOk &&
|
|
449
447
|
openclaw.ready;
|
|
450
448
|
return {
|
|
@@ -455,12 +453,12 @@ function buildDoctorReport(options) {
|
|
|
455
453
|
? "not_ready"
|
|
456
454
|
: "partially_ready",
|
|
457
455
|
selected_mode: "tmux",
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
456
|
+
live_terminal: {
|
|
457
|
+
checked: false,
|
|
458
|
+
required_for_install_readiness: false,
|
|
459
|
+
detail: "Installation readiness checks tmux and at least one supported CLI. " +
|
|
460
|
+
"AKK verifies a live eligible pane when delegation begins."
|
|
461
|
+
},
|
|
464
462
|
package_root: root,
|
|
465
463
|
checks,
|
|
466
464
|
package_files: packageFiles,
|
|
@@ -471,6 +469,7 @@ function buildDoctorReport(options) {
|
|
|
471
469
|
notes: [
|
|
472
470
|
`Node.js ${MINIMUM_NODE_VERSION}+ and OpenClaw are required.`,
|
|
473
471
|
"AKK supports Codex and Claude Code through shared tmux terminals.",
|
|
472
|
+
"Doctor does not require a live coding-agent pane; delegation discovers and verifies one at send time.",
|
|
474
473
|
"Claude tmux completion is hook-free and fails closed unless the local transcript schema is verified."
|
|
475
474
|
]
|
|
476
475
|
};
|
|
@@ -497,88 +496,6 @@ function versionAtLeast(version, minimum) {
|
|
|
497
496
|
}
|
|
498
497
|
return true;
|
|
499
498
|
}
|
|
500
|
-
async function runAgent(options) {
|
|
501
|
-
const agentCommand = required(options.agentCommand, "agent subcommand is required: takeover");
|
|
502
|
-
if (agentCommand === "takeover") {
|
|
503
|
-
printJson(await runAgentTakeover(options));
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
throw new Error(`unsupported agent subcommand: ${agentCommand}`);
|
|
507
|
-
}
|
|
508
|
-
async function runAgentTakeover(options) {
|
|
509
|
-
const agent = required(options.agent, "--agent is required");
|
|
510
|
-
const sessionId = required(options.sessionId, "--session-id is required");
|
|
511
|
-
const strategy = options.strategy ?? "terminal_control";
|
|
512
|
-
if (strategy !== "terminal_control") {
|
|
513
|
-
throw new Error("Agent Knock Knock only supports terminal_control takeover through tmux");
|
|
514
|
-
}
|
|
515
|
-
const provider = createAgentSessionProvider(agent, options);
|
|
516
|
-
const session = await provider.getSession(sessionId);
|
|
517
|
-
if (!session) {
|
|
518
|
-
return {
|
|
519
|
-
agent,
|
|
520
|
-
sessionId,
|
|
521
|
-
strategy,
|
|
522
|
-
status: "blocked",
|
|
523
|
-
sideEffectsExecuted: false,
|
|
524
|
-
error: {
|
|
525
|
-
code: "session_not_found",
|
|
526
|
-
message: `No ${agent} session found for ${sessionId}`
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
const activeSessions = await listActiveSessionsWithTerminalControl(provider, options);
|
|
531
|
-
const plan = planTerminalControlTakeover(session, activeSessions);
|
|
532
|
-
if (options.confirmTerminal === true) {
|
|
533
|
-
const terminalTarget = String(required(options.terminalTarget, "--terminal-target is required with --confirm-terminal"));
|
|
534
|
-
if (!options.createConversation) {
|
|
535
|
-
throw new Error("--create-conversation is required with --confirm-terminal");
|
|
536
|
-
}
|
|
537
|
-
const target = plan.targets.find((candidate) => candidate.terminalControl?.target === terminalTarget);
|
|
538
|
-
if (!plan.allowed || !target?.terminalControl) {
|
|
539
|
-
return {
|
|
540
|
-
agent,
|
|
541
|
-
sessionId,
|
|
542
|
-
strategy,
|
|
543
|
-
status: "blocked",
|
|
544
|
-
sideEffectsExecuted: false,
|
|
545
|
-
plan,
|
|
546
|
-
error: {
|
|
547
|
-
code: "terminal_target_unavailable",
|
|
548
|
-
message: `No matching terminal-controlled ${agent} process was found for ${terminalTarget}`
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
const attached = createNativeSessionConversation({
|
|
553
|
-
agent,
|
|
554
|
-
strategy,
|
|
555
|
-
session,
|
|
556
|
-
options,
|
|
557
|
-
takeoverMatchKind: "terminal_control",
|
|
558
|
-
terminalControl: target.terminalControl,
|
|
559
|
-
terminalAgentPid: target.pid,
|
|
560
|
-
needsBootstrap: false
|
|
561
|
-
});
|
|
562
|
-
return {
|
|
563
|
-
agent,
|
|
564
|
-
sessionId,
|
|
565
|
-
strategy,
|
|
566
|
-
status: "attached",
|
|
567
|
-
sideEffectsExecuted: true,
|
|
568
|
-
plan,
|
|
569
|
-
terminalControl: target.terminalControl,
|
|
570
|
-
...attached
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
agent,
|
|
575
|
-
sessionId,
|
|
576
|
-
strategy,
|
|
577
|
-
status: plan.allowed ? "requires_confirmation" : "blocked",
|
|
578
|
-
sideEffectsExecuted: false,
|
|
579
|
-
plan
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
499
|
async function listActiveSessionsWithTerminalControl(provider, options, terminalProvider = createTerminalControlProvider(options)) {
|
|
583
500
|
const activeSessions = await provider.listActiveSessions();
|
|
584
501
|
const activePids = new Set(activeSessions.map((session) => session.pid));
|
|
@@ -736,7 +653,9 @@ function createTerminalAgentBridge(options, terminalProvider = createTerminalCon
|
|
|
736
653
|
terminalProvider,
|
|
737
654
|
async verifyIdentity({ agent, pid, terminalControl }) {
|
|
738
655
|
const adapter = registry.require(agent);
|
|
739
|
-
const snapshots = await processSource.listProcessSnapshots(undefined, {
|
|
656
|
+
const snapshots = await processSource.listProcessSnapshots(undefined, {
|
|
657
|
+
includeCwd: options.workspace !== undefined
|
|
658
|
+
});
|
|
740
659
|
const snapshot = snapshots.find((candidate) => candidate.pid === pid);
|
|
741
660
|
if (!snapshot || !adapter.classifyProcess(snapshot)) {
|
|
742
661
|
throw new Error(`terminal conversation agent ${agent} with pid ${pid} is no longer active`);
|
|
@@ -748,6 +667,8 @@ function createTerminalAgentBridge(options, terminalProvider = createTerminalCon
|
|
|
748
667
|
if (!pane || !terminalPaneContainsProcess(snapshot, pane, snapshots)) {
|
|
749
668
|
throw new Error(`terminal conversation agent ${agent} with pid ${pid} no longer belongs to pane ${terminalControl.target}`);
|
|
750
669
|
}
|
|
670
|
+
assertConfiguredWorkspace(options.workspace, snapshot.cwd, `terminal access to ${terminalControl.target} by agent process ${pid}`);
|
|
671
|
+
assertConfiguredWorkspace(options.workspace, pane.currentPath, `terminal access to ${terminalControl.target} by tmux pane`);
|
|
751
672
|
return {
|
|
752
673
|
terminalControl: {
|
|
753
674
|
...terminalControl,
|
|
@@ -760,39 +681,6 @@ function createTerminalAgentBridge(options, terminalProvider = createTerminalCon
|
|
|
760
681
|
}
|
|
761
682
|
});
|
|
762
683
|
}
|
|
763
|
-
function planTerminalControlTakeover(session, activeSessions) {
|
|
764
|
-
const matched = activeSessions
|
|
765
|
-
.filter((process) => process.kind === "codex_cli" &&
|
|
766
|
-
process.terminalControl &&
|
|
767
|
-
(process.sessionId === session.id ||
|
|
768
|
-
(!process.sessionId && process.cwd === session.cwd)));
|
|
769
|
-
const matchedPidSet = new Set(matched.map((process) => process.pid));
|
|
770
|
-
const targets = matched
|
|
771
|
-
.filter((process) => !process.ppid || !matchedPidSet.has(process.ppid))
|
|
772
|
-
.map((process) => ({
|
|
773
|
-
pid: process.pid,
|
|
774
|
-
childPids: matched
|
|
775
|
-
.filter((child) => child.ppid === process.pid)
|
|
776
|
-
.map((child) => child.pid),
|
|
777
|
-
cwd: process.cwd,
|
|
778
|
-
command: process.command,
|
|
779
|
-
sessionId: process.sessionId,
|
|
780
|
-
terminalControl: process.terminalControl
|
|
781
|
-
}));
|
|
782
|
-
const exactTargets = targets.filter((target) => target.sessionId === session.id);
|
|
783
|
-
const selectableTargets = exactTargets.length > 0 ? exactTargets : targets;
|
|
784
|
-
return {
|
|
785
|
-
mode: "terminal_control",
|
|
786
|
-
allowed: selectableTargets.length === 1,
|
|
787
|
-
requiresConfirmation: selectableTargets.length === 1,
|
|
788
|
-
reason: selectableTargets.length === 0
|
|
789
|
-
? "no_terminal_control_target"
|
|
790
|
-
: selectableTargets.length === 1
|
|
791
|
-
? "terminal_control_available"
|
|
792
|
-
: "ambiguous_terminal_control_target",
|
|
793
|
-
targets: selectableTargets
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
684
|
function terminalControlFromTakeover(nativeTakeover) {
|
|
797
685
|
if (!isRecord(nativeTakeover)) {
|
|
798
686
|
return undefined;
|
|
@@ -992,88 +880,19 @@ function isTerminalControlCapability(value) {
|
|
|
992
880
|
"terminal_cancel"
|
|
993
881
|
].includes(value);
|
|
994
882
|
}
|
|
995
|
-
function createNativeSessionConversation({ agent, strategy, session, options, takeoverMatchKind = strategy, terminalControl = undefined, terminalAgentPid = undefined, needsBootstrap = false }) {
|
|
996
|
-
const workspace = session.cwd;
|
|
997
|
-
const storeDir = expandHome(options.storeDir ?? options.logDir ?? defaultStoreDir(workspace));
|
|
998
|
-
cleanupIdleConversations(storeDir, options);
|
|
999
|
-
const executor = resolveExecutor({
|
|
1000
|
-
kind: agent,
|
|
1001
|
-
session: session.id
|
|
1002
|
-
});
|
|
1003
|
-
const now = new Date();
|
|
1004
|
-
const conversation = createConversation({
|
|
1005
|
-
userRequest: options.request ?? `Attach native ${agent} session ${session.id}`,
|
|
1006
|
-
workspace,
|
|
1007
|
-
openclawSession: options.openclawSession ?? "agent:main:main",
|
|
1008
|
-
executorKind: executor.kind,
|
|
1009
|
-
executorSession: executor.session,
|
|
1010
|
-
softLimit: Number(options.softLimit ?? 50),
|
|
1011
|
-
hardLimit: Number(options.hardLimit ?? 100),
|
|
1012
|
-
now
|
|
1013
|
-
});
|
|
1014
|
-
const paths = pathsForConversation(conversation.conversation_id, storeDir);
|
|
1015
|
-
const attachedConversation = withStoragePaths({
|
|
1016
|
-
...conversation,
|
|
1017
|
-
executor,
|
|
1018
|
-
status: "idle",
|
|
1019
|
-
idle_since: now.toISOString(),
|
|
1020
|
-
updated_at: now.toISOString(),
|
|
1021
|
-
gateway_url: options.gatewayUrl ?? "ws://127.0.0.1:18789",
|
|
1022
|
-
gateway_method: options.gatewayMethod,
|
|
1023
|
-
gateway_session: options.gatewaySession ?? options.openclawSession ?? "agent:main:main",
|
|
1024
|
-
openclaw_bin: options.openclawBin ?? resolveOptionalExecutable("openclaw"),
|
|
1025
|
-
native_session_takeover: {
|
|
1026
|
-
agent,
|
|
1027
|
-
native_session_id: session.id,
|
|
1028
|
-
terminal_agent_pid: terminalAgentPid,
|
|
1029
|
-
source_cwd: session.cwd,
|
|
1030
|
-
source_title: session.title,
|
|
1031
|
-
strategy,
|
|
1032
|
-
attached_at: now.toISOString(),
|
|
1033
|
-
takeover_match_kind: takeoverMatchKind,
|
|
1034
|
-
terminal_control: terminalControl,
|
|
1035
|
-
needs_bootstrap: needsBootstrap,
|
|
1036
|
-
terminal_bridge: true
|
|
1037
|
-
}
|
|
1038
|
-
}, paths);
|
|
1039
|
-
saveState(paths.statePath, attachedConversation);
|
|
1040
|
-
appendEvent(paths.logPath, {
|
|
1041
|
-
ts: now.toISOString(),
|
|
1042
|
-
conversation_id: attachedConversation.conversation_id,
|
|
1043
|
-
event: "native_session_attached",
|
|
1044
|
-
agent,
|
|
1045
|
-
strategy,
|
|
1046
|
-
native_session_id: session.id,
|
|
1047
|
-
source_cwd: session.cwd,
|
|
1048
|
-
executor
|
|
1049
|
-
});
|
|
1050
|
-
runtimeLog("info", "native_session_attached", {
|
|
1051
|
-
conversation_id: attachedConversation.conversation_id,
|
|
1052
|
-
agent,
|
|
1053
|
-
strategy,
|
|
1054
|
-
native_session_id: session.id,
|
|
1055
|
-
state_path: paths.statePath,
|
|
1056
|
-
event_log_path: paths.logPath
|
|
1057
|
-
});
|
|
1058
|
-
return {
|
|
1059
|
-
conversation: attachedConversation,
|
|
1060
|
-
paths,
|
|
1061
|
-
next: `Use AKK send ${attachedConversation.conversation_id}: <message> to continue this native ${agent} session through AKK.`
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
883
|
async function runDelegate(options) {
|
|
1065
884
|
const request = required(options.request, "--request is required");
|
|
1066
885
|
const workspace = canonicalWorkspace(options.workspace ?? process.cwd());
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const scan = await
|
|
886
|
+
const requestedAgent = options.agent === undefined
|
|
887
|
+
? undefined
|
|
888
|
+
: resolveExecutor({ kind: options.agent }).kind;
|
|
889
|
+
const scan = await buildTerminalListGroup({
|
|
1071
890
|
options: {
|
|
1072
891
|
...options,
|
|
1073
892
|
workspace,
|
|
1074
893
|
noApprovalScan: false
|
|
1075
894
|
},
|
|
1076
|
-
agentFilter:
|
|
895
|
+
agentFilter: requestedAgent,
|
|
1077
896
|
statusFilter: undefined
|
|
1078
897
|
});
|
|
1079
898
|
if (scan.summary.error) {
|
|
@@ -1092,15 +911,21 @@ async function runDelegate(options) {
|
|
|
1092
911
|
const observed = sameWorkspace.length > 0
|
|
1093
912
|
? ` Found ${sameWorkspace.length} matching pane(s), but none is idle.`
|
|
1094
913
|
: "";
|
|
1095
|
-
|
|
1096
|
-
|
|
914
|
+
const requestedExecutor = requestedAgent
|
|
915
|
+
? executorDefinitionForKind(requestedAgent)
|
|
916
|
+
: undefined;
|
|
917
|
+
throw new Error(`No idle ${requestedExecutor?.displayName ?? "Codex or Claude Code"} pane is available in ${workspace}.${observed} ` +
|
|
918
|
+
`Start ${requestedAgent ?? "codex or claude"} inside tmux in that workspace, wait until it is idle, then retry.`);
|
|
1097
919
|
}
|
|
1098
920
|
if (eligible.length > 1) {
|
|
1099
921
|
const candidates = eligible
|
|
1100
|
-
.map((candidate) => `${candidate.short_ref} (${candidate.terminal_control?.target ?? candidate.id})`)
|
|
922
|
+
.map((candidate) => `${candidate.short_ref} (${candidate.agent}, ${candidate.terminal_control?.target ?? candidate.id})`)
|
|
1101
923
|
.join(", ");
|
|
1102
|
-
|
|
1103
|
-
|
|
924
|
+
const scope = requestedAgent
|
|
925
|
+
? executorDefinitionForKind(requestedAgent).displayName
|
|
926
|
+
: "coding-agent";
|
|
927
|
+
throw new Error(`Multiple idle ${scope} panes match ${workspace}: ${candidates}. ` +
|
|
928
|
+
"Use /akk codex: <task>, /akk claude: <task>, or /akk @short-ref: <message> to choose one explicitly.");
|
|
1104
929
|
}
|
|
1105
930
|
await runSend({
|
|
1106
931
|
...options,
|
|
@@ -1495,35 +1320,49 @@ async function runList(options) {
|
|
|
1495
1320
|
const statusFilter = options.status;
|
|
1496
1321
|
const storedConversations = listConversations(storeDir)
|
|
1497
1322
|
.filter((conversation) => includeAll || isActiveStatus(conversation.status))
|
|
1323
|
+
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace))
|
|
1498
1324
|
.filter((conversation) => !agentFilter || executorForConversation(conversation).kind === agentFilter)
|
|
1499
1325
|
.filter((conversation) => !statusFilter || conversation.status === statusFilter);
|
|
1500
1326
|
const conversations = storedConversations.map((conversation) => summarizeConversation(conversation));
|
|
1501
1327
|
const delegated = storedConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), { terminalBridge: terminalBridgeEnabled(conversation) }));
|
|
1502
|
-
const
|
|
1328
|
+
const terminalScan = await buildTerminalListGroup({ options, agentFilter, statusFilter });
|
|
1329
|
+
const managedTerminalKeys = new Set(storedConversations
|
|
1330
|
+
.filter((conversation) => isActiveStatus(conversation.status))
|
|
1331
|
+
.map((conversation) => terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
1332
|
+
? conversation.native_session_takeover
|
|
1333
|
+
: undefined)))
|
|
1334
|
+
.filter((key) => key !== undefined));
|
|
1335
|
+
const terminalControlled = terminalScan.terminalControlled.filter((entry) => {
|
|
1336
|
+
if (!matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd)) {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
const key = terminalControlSelectorKey(entry.terminal_control);
|
|
1340
|
+
return key === undefined || !managedTerminalKeys.has(key);
|
|
1341
|
+
});
|
|
1503
1342
|
printJson({
|
|
1504
1343
|
store_dir: storeDir,
|
|
1505
1344
|
cleanup,
|
|
1506
1345
|
delegated,
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1346
|
+
terminal_controlled: terminalControlled,
|
|
1347
|
+
terminal_scan: {
|
|
1348
|
+
...terminalScan.summary,
|
|
1349
|
+
terminal_controlled_count: terminalControlled.length
|
|
1350
|
+
},
|
|
1510
1351
|
tasks: conversations
|
|
1511
1352
|
});
|
|
1512
1353
|
runtimeLog("info", "tasks_listed", {
|
|
1513
1354
|
store_dir: storeDir,
|
|
1514
1355
|
returned_count: conversations.length,
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
native_scan_error: nativeScan.summary.error,
|
|
1356
|
+
terminal_controlled_count: terminalControlled.length,
|
|
1357
|
+
terminal_scan_error: terminalScan.summary.error,
|
|
1518
1358
|
include_all: includeAll,
|
|
1519
1359
|
agent_filter: agentFilter,
|
|
1520
1360
|
status_filter: statusFilter,
|
|
1521
1361
|
cleanup
|
|
1522
1362
|
});
|
|
1523
1363
|
}
|
|
1524
|
-
async function
|
|
1364
|
+
async function buildTerminalListGroup({ options, agentFilter, statusFilter }) {
|
|
1525
1365
|
const empty = {
|
|
1526
|
-
native: [],
|
|
1527
1366
|
terminalControlled: [],
|
|
1528
1367
|
summary: {
|
|
1529
1368
|
enabled: false,
|
|
@@ -1540,7 +1379,7 @@ async function buildNativeListGroups({ options, agentFilter, statusFilter }) {
|
|
|
1540
1379
|
summary: {
|
|
1541
1380
|
enabled: false,
|
|
1542
1381
|
agents: [],
|
|
1543
|
-
skipped: `
|
|
1382
|
+
skipped: `terminal discovery skipped for status filter ${statusFilter}`
|
|
1544
1383
|
}
|
|
1545
1384
|
};
|
|
1546
1385
|
}
|
|
@@ -1560,40 +1399,35 @@ async function buildNativeListGroups({ options, agentFilter, statusFilter }) {
|
|
|
1560
1399
|
}
|
|
1561
1400
|
const terminalProvider = createTerminalControlProvider(options);
|
|
1562
1401
|
const bridge = createTerminalAgentBridge(options, terminalProvider, registry);
|
|
1563
|
-
const
|
|
1402
|
+
const terminalDiagnostics = options.terminalDebug
|
|
1403
|
+
? await terminalControlDiagnostics(terminalProvider)
|
|
1404
|
+
: undefined;
|
|
1564
1405
|
const terminalControlled = [];
|
|
1565
|
-
const native = [];
|
|
1566
1406
|
let activeCount = 0;
|
|
1567
1407
|
const errors = [];
|
|
1568
1408
|
try {
|
|
1569
1409
|
const processSource = createTerminalProcessSource(options);
|
|
1570
1410
|
const snapshots = await processSource.listProcessSnapshots((snapshot) => adapters.some((adapter) => adapter.capabilities.processDiscovery && adapter.classifyProcess(snapshot) !== undefined), { includeAncestors: true });
|
|
1571
1411
|
const activeSessions = await bridge.listProcesses(snapshots, adapters.map((adapter) => adapter.agent));
|
|
1572
|
-
activeCount = activeSessions.length;
|
|
1573
1412
|
const rootSessions = rootActiveProcesses(activeSessions);
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
else {
|
|
1579
|
-
native.push(nativeListEntry(session, activeSessions));
|
|
1580
|
-
}
|
|
1413
|
+
const controlledSessions = rootSessions.filter((session) => session.terminalControl !== undefined);
|
|
1414
|
+
activeCount = controlledSessions.length;
|
|
1415
|
+
for (const session of controlledSessions) {
|
|
1416
|
+
terminalControlled.push(await terminalControlledListEntry(session, activeSessions, options, bridge));
|
|
1581
1417
|
}
|
|
1582
1418
|
}
|
|
1583
1419
|
catch (error) {
|
|
1584
1420
|
errors.push(error instanceof Error ? error.message : String(error));
|
|
1585
1421
|
}
|
|
1586
1422
|
return {
|
|
1587
|
-
native,
|
|
1588
1423
|
terminalControlled,
|
|
1589
1424
|
summary: {
|
|
1590
1425
|
enabled: true,
|
|
1591
1426
|
agents: adapters.map((adapter) => adapter.agent),
|
|
1592
1427
|
active_count: activeCount,
|
|
1593
|
-
native_count: native.length,
|
|
1594
1428
|
terminal_controlled_count: terminalControlled.length,
|
|
1595
1429
|
approval_scan: options.noApprovalScan ? "disabled" : "enabled",
|
|
1596
|
-
|
|
1430
|
+
diagnostics: terminalDiagnostics,
|
|
1597
1431
|
error: errors.length > 0 ? errors.join("; ") : undefined
|
|
1598
1432
|
}
|
|
1599
1433
|
};
|
|
@@ -1622,33 +1456,6 @@ function delegatedListEntry(task, { terminalBridge = false } = {}) {
|
|
|
1622
1456
|
}
|
|
1623
1457
|
};
|
|
1624
1458
|
}
|
|
1625
|
-
function nativeListEntry(session, activeSessions) {
|
|
1626
|
-
const id = `native:${session.agent}:${session.pid}`;
|
|
1627
|
-
return {
|
|
1628
|
-
id,
|
|
1629
|
-
short_ref: sessionShortRef(id),
|
|
1630
|
-
source: "native_active",
|
|
1631
|
-
agent: session.agent,
|
|
1632
|
-
status: "active",
|
|
1633
|
-
pid: session.pid,
|
|
1634
|
-
child_pids: childPidsForRoot(session, activeSessions),
|
|
1635
|
-
command: session.command,
|
|
1636
|
-
cwd: session.cwd,
|
|
1637
|
-
workspace: session.cwd,
|
|
1638
|
-
elapsed: session.elapsed,
|
|
1639
|
-
session_id: session.sessionId,
|
|
1640
|
-
confidence: session.confidence,
|
|
1641
|
-
reason: session.reason,
|
|
1642
|
-
commands: {
|
|
1643
|
-
terminal_control_attach: false,
|
|
1644
|
-
send: false,
|
|
1645
|
-
cancel: false,
|
|
1646
|
-
approve: false,
|
|
1647
|
-
close: false,
|
|
1648
|
-
status: false
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
}
|
|
1652
1459
|
async function terminalControlledListEntry(session, activeSessions, options, bridge = createTerminalAgentBridge(options)) {
|
|
1653
1460
|
const terminalControl = session.terminalControl;
|
|
1654
1461
|
if (!terminalControl) {
|
|
@@ -1795,14 +1602,16 @@ async function sessionSelectorCandidates(commandName, options) {
|
|
|
1795
1602
|
const storeDir = storeDirFromOptions(options);
|
|
1796
1603
|
cleanupIdleConversations(storeDir, options);
|
|
1797
1604
|
const storedConversations = listConversations(storeDir);
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1605
|
+
const workspaceConversations = storedConversations
|
|
1606
|
+
.filter((conversation) => matchesConfiguredWorkspace(options.workspace, conversation.workspace));
|
|
1607
|
+
const managed = workspaceConversations.map((conversation) => delegatedListEntry(summarizeConversation(conversation), { terminalBridge: terminalBridgeEnabled(conversation) }));
|
|
1608
|
+
const managedTerminalKeys = new Set(workspaceConversations
|
|
1800
1609
|
.filter((conversation) => isActiveStatus(conversation.status))
|
|
1801
1610
|
.map((conversation) => terminalControlSelectorKey(terminalControlFromTakeover(isRecord(conversation.native_session_takeover)
|
|
1802
1611
|
? conversation.native_session_takeover
|
|
1803
1612
|
: undefined)))
|
|
1804
1613
|
.filter((key) => key !== undefined));
|
|
1805
|
-
const
|
|
1614
|
+
const terminalScan = await buildTerminalListGroup({
|
|
1806
1615
|
options: {
|
|
1807
1616
|
...options,
|
|
1808
1617
|
noApprovalScan: commandName === "approve"
|
|
@@ -1815,11 +1624,13 @@ async function sessionSelectorCandidates(commandName, options) {
|
|
|
1815
1624
|
const observedAtMs = Date.now();
|
|
1816
1625
|
return [
|
|
1817
1626
|
...managed,
|
|
1818
|
-
...
|
|
1627
|
+
...terminalScan.terminalControlled.filter((entry) => {
|
|
1628
|
+
if (!matchesConfiguredWorkspace(options.workspace, entry.workspace ?? entry.cwd)) {
|
|
1629
|
+
return false;
|
|
1630
|
+
}
|
|
1819
1631
|
const key = terminalControlSelectorKey(entry.terminal_control);
|
|
1820
1632
|
return key === undefined || !managedTerminalKeys.has(key);
|
|
1821
|
-
})
|
|
1822
|
-
...nativeScan.native
|
|
1633
|
+
})
|
|
1823
1634
|
].map((entry) => ({
|
|
1824
1635
|
id: String(entry.id),
|
|
1825
1636
|
agent: resolveExecutor({ kind: entry.agent }).kind,
|
|
@@ -1847,16 +1658,10 @@ function terminalControlSelectorKey(value) {
|
|
|
1847
1658
|
});
|
|
1848
1659
|
}
|
|
1849
1660
|
function sessionEntrySupportsCommand(entry, commandName) {
|
|
1850
|
-
if (commandName === "summary") {
|
|
1851
|
-
commandName = "describe";
|
|
1852
|
-
}
|
|
1853
1661
|
const commands = isRecord(entry.commands) ? entry.commands : {};
|
|
1854
1662
|
if (typeof commands[commandName] === "boolean") {
|
|
1855
1663
|
return commands[commandName] === true;
|
|
1856
1664
|
}
|
|
1857
|
-
if (commandName === "describe") {
|
|
1858
|
-
return commands.status === true || entry.source === "native_active";
|
|
1859
|
-
}
|
|
1860
1665
|
if (entry.source !== "akk_delegate") {
|
|
1861
1666
|
return false;
|
|
1862
1667
|
}
|
|
@@ -1882,21 +1687,6 @@ function sessionEntryRecency(entry, observedAtMs) {
|
|
|
1882
1687
|
async function resolveTerminalConversationFromOptions(options) {
|
|
1883
1688
|
return createTerminalAgentBridge(options).resolveConversationId(stringValue(options.conversation ?? options.conversationId));
|
|
1884
1689
|
}
|
|
1885
|
-
function parseNativeConversationId(conversationId) {
|
|
1886
|
-
const prefix = "native:codex:";
|
|
1887
|
-
if (!conversationId?.startsWith(prefix)) {
|
|
1888
|
-
return undefined;
|
|
1889
|
-
}
|
|
1890
|
-
const pid = Number(conversationId.slice(prefix.length));
|
|
1891
|
-
if (!Number.isInteger(pid)) {
|
|
1892
|
-
throw new Error(`invalid native conversation id: ${conversationId}`);
|
|
1893
|
-
}
|
|
1894
|
-
return {
|
|
1895
|
-
conversationId,
|
|
1896
|
-
agent: "codex",
|
|
1897
|
-
pid
|
|
1898
|
-
};
|
|
1899
|
-
}
|
|
1900
1690
|
async function runStatus(options) {
|
|
1901
1691
|
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1902
1692
|
const terminalConversation = await resolveTerminalConversationFromOptions(options);
|
|
@@ -1907,9 +1697,12 @@ async function runStatus(options) {
|
|
|
1907
1697
|
conversationId: terminalConversation.conversationId,
|
|
1908
1698
|
terminalTarget: terminalConversation.terminalControl.target
|
|
1909
1699
|
});
|
|
1700
|
+
const context = await terminalStatusContext(terminalConversation, terminalStatus, options);
|
|
1910
1701
|
printJson({
|
|
1911
1702
|
conversation_id: terminalConversation.conversationId,
|
|
1912
1703
|
source: "terminal_control",
|
|
1704
|
+
agent: terminalConversation.agent,
|
|
1705
|
+
...context,
|
|
1913
1706
|
terminal_control: terminalConversation.terminalControl,
|
|
1914
1707
|
terminal_status: terminalStatus,
|
|
1915
1708
|
terminal_screen: terminalStatus.screen
|
|
@@ -1931,6 +1724,9 @@ async function runStatus(options) {
|
|
|
1931
1724
|
const result = {
|
|
1932
1725
|
conversation,
|
|
1933
1726
|
summary: summarizeConversation(conversation),
|
|
1727
|
+
confidence: "high",
|
|
1728
|
+
about: managedConversationAbout(conversation, events),
|
|
1729
|
+
limitations: [],
|
|
1934
1730
|
state_path: statePath,
|
|
1935
1731
|
event_log_path: logPath,
|
|
1936
1732
|
budget: budgetAction(conversation),
|
|
@@ -1945,6 +1741,13 @@ async function runStatus(options) {
|
|
|
1945
1741
|
result.terminal_control = terminalControl;
|
|
1946
1742
|
result.terminal_status = await terminalStatusForControl(executor.kind, terminalControl, options, terminalRuntimeIdentityForConversation(conversation, terminalControl));
|
|
1947
1743
|
result.terminal_screen = result.terminal_status.screen;
|
|
1744
|
+
result.about = managedConversationAbout(conversation, events, result.terminal_status);
|
|
1745
|
+
result.limitations = result.terminal_status.reachable === false
|
|
1746
|
+
? ["terminal status unavailable"]
|
|
1747
|
+
: [];
|
|
1748
|
+
}
|
|
1749
|
+
else {
|
|
1750
|
+
result.limitations = ["terminal control metadata is unavailable"];
|
|
1948
1751
|
}
|
|
1949
1752
|
printJson(result);
|
|
1950
1753
|
runtimeLog("info", "task_status_read", {
|
|
@@ -1956,85 +1759,45 @@ async function runStatus(options) {
|
|
|
1956
1759
|
trace: Boolean(options.trace)
|
|
1957
1760
|
});
|
|
1958
1761
|
}
|
|
1959
|
-
async function
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
});
|
|
1970
|
-
if (terminalConversation.agent !== "codex") {
|
|
1971
|
-
const adapter = createRuntimeTerminalAgentRegistry(options).require(terminalConversation.agent);
|
|
1972
|
-
printJson({
|
|
1973
|
-
conversation_id: conversationId,
|
|
1974
|
-
source: "terminal_control",
|
|
1975
|
-
agent: terminalConversation.agent,
|
|
1976
|
-
confidence: terminalStatus.reachable ? "medium" : "low",
|
|
1977
|
-
about: terminalStatus.reachable
|
|
1978
|
-
? `${adapter.displayName} is attached through ${terminalConversation.terminalControl.kind}:${terminalConversation.terminalControl.target}.`
|
|
1979
|
-
: `${adapter.displayName} terminal status is unavailable.`,
|
|
1980
|
-
evidence: {
|
|
1981
|
-
terminal_status: terminalStatus,
|
|
1982
|
-
terminal_screen: terminalStatus.screen
|
|
1983
|
-
},
|
|
1984
|
-
limitations: ["Historical session context is not available for this terminal adapter."],
|
|
1985
|
-
terminal_control: terminalConversation.terminalControl
|
|
1762
|
+
async function terminalStatusContext(terminalConversation, terminalStatus, options) {
|
|
1763
|
+
if (terminalConversation.agent === "codex") {
|
|
1764
|
+
try {
|
|
1765
|
+
const process = await activeCodexProcessForPid(options, terminalConversation.pid);
|
|
1766
|
+
const description = await codexTerminalStatusContext({
|
|
1767
|
+
id: terminalConversation.conversationId,
|
|
1768
|
+
process,
|
|
1769
|
+
options,
|
|
1770
|
+
terminalControl: terminalConversation.terminalControl,
|
|
1771
|
+
terminalStatus
|
|
1986
1772
|
});
|
|
1987
|
-
return
|
|
1773
|
+
return {
|
|
1774
|
+
confidence: description.confidence,
|
|
1775
|
+
about: description.about,
|
|
1776
|
+
limitations: description.limitations
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
catch {
|
|
1780
|
+
return {
|
|
1781
|
+
confidence: "low",
|
|
1782
|
+
about: terminalStatus.reachable
|
|
1783
|
+
? `Codex is attached through ${terminalConversation.terminalControl.kind}:${terminalConversation.terminalControl.target}.`
|
|
1784
|
+
: "Codex terminal status is unavailable.",
|
|
1785
|
+
limitations: [
|
|
1786
|
+
"Codex historical session context is unavailable; live terminal status remains authoritative."
|
|
1787
|
+
]
|
|
1788
|
+
};
|
|
1988
1789
|
}
|
|
1989
|
-
const process = await activeCodexProcessForPid(options, terminalConversation.pid);
|
|
1990
|
-
printJson(await describeNativeCodexSession({
|
|
1991
|
-
id: conversationId,
|
|
1992
|
-
source: "terminal_control",
|
|
1993
|
-
process,
|
|
1994
|
-
options,
|
|
1995
|
-
terminalControl: terminalConversation.terminalControl,
|
|
1996
|
-
terminalStatus
|
|
1997
|
-
}));
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
|
-
const nativeConversation = parseNativeConversationId(conversationId);
|
|
2001
|
-
if (nativeConversation) {
|
|
2002
|
-
const process = await activeCodexProcessForPid(options, nativeConversation.pid);
|
|
2003
|
-
printJson(await describeNativeCodexSession({
|
|
2004
|
-
id: conversationId,
|
|
2005
|
-
source: "native_active",
|
|
2006
|
-
process,
|
|
2007
|
-
options
|
|
2008
|
-
}));
|
|
2009
|
-
return;
|
|
2010
1790
|
}
|
|
2011
|
-
const
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
: undefined;
|
|
2022
|
-
printJson({
|
|
2023
|
-
conversation_id: conversation.conversation_id,
|
|
2024
|
-
source: "akk_managed",
|
|
2025
|
-
confidence: "high",
|
|
2026
|
-
about: managedConversationAbout(conversation, events, terminalStatus),
|
|
2027
|
-
summary: summarizeConversation(conversation),
|
|
2028
|
-
evidence: {
|
|
2029
|
-
initial_request: conversation.user_request,
|
|
2030
|
-
recent_messages: recentMessageEvidence(events),
|
|
2031
|
-
trace: buildConversationTrace({ conversation, events, logPath }),
|
|
2032
|
-
terminal_screen: terminalStatus?.screen
|
|
2033
|
-
},
|
|
2034
|
-
limitations: terminalStatus?.reachable === false ? ["terminal status unavailable"] : [],
|
|
2035
|
-
state_path: statePath,
|
|
2036
|
-
event_log_path: logPath
|
|
2037
|
-
});
|
|
1791
|
+
const adapter = createRuntimeTerminalAgentRegistry(options).require(terminalConversation.agent);
|
|
1792
|
+
return {
|
|
1793
|
+
confidence: terminalStatus.reachable ? "medium" : "low",
|
|
1794
|
+
about: terminalStatus.reachable
|
|
1795
|
+
? `${adapter.displayName} is attached through ${terminalConversation.terminalControl.kind}:${terminalConversation.terminalControl.target}.`
|
|
1796
|
+
: `${adapter.displayName} terminal status is unavailable.`,
|
|
1797
|
+
limitations: [
|
|
1798
|
+
"Historical session context is not available for this terminal adapter."
|
|
1799
|
+
]
|
|
1800
|
+
};
|
|
2038
1801
|
}
|
|
2039
1802
|
async function terminalStatusForControl(agent, terminalControl, options, runtime) {
|
|
2040
1803
|
return createTerminalAgentBridge(options).status(agent, terminalControl, {
|
|
@@ -3947,6 +3710,10 @@ async function runReconcileMonitors(options) {
|
|
|
3947
3710
|
let skipped = 0;
|
|
3948
3711
|
let errors = 0;
|
|
3949
3712
|
for (const listedConversation of conversations) {
|
|
3713
|
+
if (!matchesConfiguredWorkspace(options.workspace, listedConversation.workspace)) {
|
|
3714
|
+
ignored += 1;
|
|
3715
|
+
continue;
|
|
3716
|
+
}
|
|
3950
3717
|
const statePath = expandHome(stringValue(listedConversation.state_path) ??
|
|
3951
3718
|
statePathForConversationId(listedConversation.conversation_id, storeDir));
|
|
3952
3719
|
const logPath = expandHome(stringValue(listedConversation.event_log_path) ??
|
|
@@ -7558,6 +7325,7 @@ function loadConversationFromOptions(options) {
|
|
|
7558
7325
|
const conversation = options.state
|
|
7559
7326
|
? loadState(statePath)
|
|
7560
7327
|
: loadConversationById(conversationId, storeDir);
|
|
7328
|
+
assertConfiguredWorkspace(options.workspace, conversation.workspace, `access to AKK conversation ${conversation.conversation_id}`);
|
|
7561
7329
|
return {
|
|
7562
7330
|
conversation,
|
|
7563
7331
|
statePath,
|
|
@@ -7609,7 +7377,7 @@ async function activeCodexProcessForPid(options, pid) {
|
|
|
7609
7377
|
const activeSessions = await listActiveSessionsWithTerminalControl(provider, options);
|
|
7610
7378
|
return activeSessions.find((process) => process.pid === pid);
|
|
7611
7379
|
}
|
|
7612
|
-
async function
|
|
7380
|
+
async function codexTerminalStatusContext({ id, process, options, terminalControl, terminalStatus }) {
|
|
7613
7381
|
const provider = createAgentSessionProvider("codex", options);
|
|
7614
7382
|
const directSessionId = process?.sessionId;
|
|
7615
7383
|
if (directSessionId) {
|
|
@@ -7620,9 +7388,8 @@ async function describeNativeCodexSession({ id, source, process, options, termin
|
|
|
7620
7388
|
maxTextLength: Number(options.maxTextLength ?? 1200)
|
|
7621
7389
|
});
|
|
7622
7390
|
if (context) {
|
|
7623
|
-
return
|
|
7391
|
+
return codexTerminalContextFromHistory({
|
|
7624
7392
|
id,
|
|
7625
|
-
source,
|
|
7626
7393
|
confidence: "high",
|
|
7627
7394
|
match: "session_id",
|
|
7628
7395
|
process,
|
|
@@ -7646,9 +7413,8 @@ async function describeNativeCodexSession({ id, source, process, options, termin
|
|
|
7646
7413
|
maxTextLength: Number(options.maxTextLength ?? 1200)
|
|
7647
7414
|
});
|
|
7648
7415
|
if (context) {
|
|
7649
|
-
return
|
|
7416
|
+
return codexTerminalContextFromHistory({
|
|
7650
7417
|
id,
|
|
7651
|
-
source,
|
|
7652
7418
|
confidence: sessions.length === 1 ? "medium" : "low",
|
|
7653
7419
|
match: sessions.length === 1 ? "cwd" : "cwd_latest",
|
|
7654
7420
|
process,
|
|
@@ -7670,7 +7436,7 @@ async function describeNativeCodexSession({ id, source, process, options, termin
|
|
|
7670
7436
|
}
|
|
7671
7437
|
return {
|
|
7672
7438
|
conversation_id: id,
|
|
7673
|
-
source,
|
|
7439
|
+
source: "terminal_control",
|
|
7674
7440
|
confidence: "screen_only",
|
|
7675
7441
|
match: "terminal_screen",
|
|
7676
7442
|
about: screenOnlyAbout({ process, terminalStatus }),
|
|
@@ -7686,10 +7452,10 @@ async function describeNativeCodexSession({ id, source, process, options, termin
|
|
|
7686
7452
|
]
|
|
7687
7453
|
};
|
|
7688
7454
|
}
|
|
7689
|
-
function
|
|
7455
|
+
function codexTerminalContextFromHistory({ id, confidence, match, process, context, terminalControl, terminalStatus, limitations, candidates }) {
|
|
7690
7456
|
return {
|
|
7691
7457
|
conversation_id: id,
|
|
7692
|
-
source,
|
|
7458
|
+
source: "terminal_control",
|
|
7693
7459
|
confidence,
|
|
7694
7460
|
match,
|
|
7695
7461
|
about: rolloutAbout(context, terminalStatus),
|
|
@@ -8694,17 +8460,11 @@ function usage() {
|
|
|
8694
8460
|
agent-knock-knock delegate --request <text> [--agent ${agentList}] [--workspace <path>] [--store-dir <dir>]
|
|
8695
8461
|
agent-knock-knock list [--store-dir <dir>] [--agent ${agentList}] [--status <status>] [--all] [--managed-only] [--no-approval-scan] [--terminal-debug]
|
|
8696
8462
|
agent-knock-knock status [--conversation <id|selector>] [--store-dir <dir>] [--trace]
|
|
8697
|
-
agent-knock-knock describe [--conversation <id|selector>] [--store-dir <dir>]
|
|
8698
8463
|
agent-knock-knock send [--conversation <id|selector>] --message <text> [--type answer|task|control] [--agent-timeout-minutes <minutes>] [--agent-hard-timeout-minutes <minutes>]
|
|
8699
|
-
agent-knock-knock approve [--conversation <id|selector>]
|
|
8464
|
+
agent-knock-knock approve [--conversation <id|selector>] --expected-approval-fingerprint <fingerprint>
|
|
8700
8465
|
agent-knock-knock cancel [--conversation <id|selector>]
|
|
8701
|
-
agent-knock-knock
|
|
8702
|
-
agent-knock-knock reconcile-monitors [--store-dir <dir>]
|
|
8703
|
-
agent-knock-knock retry-callback --conversation <id> [--store-dir <dir>]
|
|
8704
|
-
agent-knock-knock close --conversation <id> [--expected-message-id <id>] [--reason <text>]
|
|
8705
|
-
agent-knock-knock install-openclaw [--workspace <path>] [--default-agent ${agentList}] [--verify] [--openclaw-bin <path>] [--skill-path <path>] [--skill-only] [--no-restart]
|
|
8466
|
+
agent-knock-knock install-openclaw [--workspace <path>] [--verify] [--openclaw-bin <path>] [--skill-path <path>] [--skill-only] [--no-restart]
|
|
8706
8467
|
agent-knock-knock doctor [--workspace <path>] [--openclaw-bin <path>]
|
|
8707
|
-
agent-knock-knock agent takeover --agent codex --session-id <id> --strategy terminal_control [--create-conversation]
|
|
8708
8468
|
agent-knock-knock callback --state <file> --message-json <json> [--record-only]
|
|
8709
8469
|
agent-knock-knock transcript --log <file> [--include-raw]
|
|
8710
8470
|
agent-knock-knock transcript --conversation <dir> [--include-raw]
|