@scotthuang/agent-knock-knock 0.2.28 → 0.2.30
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
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.30 - 2026-07-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added terminal bridge monitoring for tmux-controlled Codex conversations so AKK can send only the user-facing task text, observe Codex rollout/terminal state, and deliver the OpenClaw callback itself.
|
|
8
|
+
|
|
9
|
+
## 0.2.29 - 2026-07-08
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Clarified AKK skill and `agent_knock_knock_send` tool routing so messages to listed AKK/Codex sessions, terminal-controlled rows, or tmux targets such as `my-work:0.1` use `send` instead of starting a new delegation.
|
|
14
|
+
|
|
3
15
|
## 0.2.28 - 2026-07-07
|
|
4
16
|
|
|
5
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -173,6 +173,7 @@ AKK list
|
|
|
173
173
|
- `native`: local native sessions that AKK can discover but cannot directly control.
|
|
174
174
|
- `terminal_controlled`: local sessions running in a controllable terminal provider. The current provider is tmux. These entries include terminal metadata, command capabilities, and concise approval state when a visible approval prompt is detected.
|
|
175
175
|
- `terminal_controlled` entries can be addressed directly by their `id` from `AKK list` for `AKK send <id>`, `AKK status <id>`, `AKK cancel <id>`, and `AKK approve <id>`. They do not need an AKK state file before terminal control.
|
|
176
|
+
- Background sends to terminal-controlled Codex sessions use terminal bridge mode: AKK types only the user-facing task text into the tmux pane, monitors Codex rollout/terminal state, and delivers the OpenClaw callback itself when it observes completion.
|
|
176
177
|
- `AKK status <terminal-controlled-id>` is the unified way to inspect current terminal output. AKK captures the terminal pane internally and returns `terminal_screen`; there is no separate public screen-capture command.
|
|
177
178
|
- `AKK describe <id>` summarizes what a listed session is about. AKK-managed sessions use saved conversation history; native and terminal-controlled Codex sessions use exact Codex rollout history when a session id is available, fall back to cwd-matched rollout history when needed, and otherwise report only visible terminal/process context with lower confidence.
|
|
178
179
|
|
|
@@ -283,7 +284,7 @@ Runtime logs are diagnostic-only and are safe to use for local troubleshooting.
|
|
|
283
284
|
- Default agent: configured with `defaultAgent`; fallback is `codex`
|
|
284
285
|
- OpenClaw session: inherited from the current OpenClaw session; fallback is `agent:main:main`
|
|
285
286
|
- Delegated ACPX session: generated per new task, unless `session`, `codexSession`, `claudeSession`, or `cursorSession` is configured
|
|
286
|
-
- Agent callback timeout: `60` minutes before a waiting task is marked `stalled`
|
|
287
|
+
- Agent callback timeout: `60` minutes before a waiting task or terminal bridge monitor is marked `stalled`
|
|
287
288
|
- Idle timeout: `10080` minutes before an idle task is lazily closed
|
|
288
289
|
- Soft response limit: `50` rounds
|
|
289
290
|
- Hard response limit: `100` rounds
|
package/dist/src/cli.js
CHANGED
|
@@ -124,7 +124,7 @@ async function runCommand(commandName, options) {
|
|
|
124
124
|
runCallback(options);
|
|
125
125
|
}
|
|
126
126
|
else if (commandName === "monitor") {
|
|
127
|
-
runMonitor(options);
|
|
127
|
+
await runMonitor(options);
|
|
128
128
|
}
|
|
129
129
|
else if (commandName === "agent") {
|
|
130
130
|
await runAgent(options);
|
|
@@ -875,7 +875,8 @@ function createNativeSessionConversation({ agent, strategy, session, modelInfo,
|
|
|
875
875
|
model_source: modelInfo?.source,
|
|
876
876
|
takeover_match_kind: takeoverMatchKind,
|
|
877
877
|
terminal_control: terminalControl,
|
|
878
|
-
needs_bootstrap: needsBootstrap
|
|
878
|
+
needs_bootstrap: needsBootstrap,
|
|
879
|
+
terminal_bridge: strategy === "terminal_control"
|
|
879
880
|
}
|
|
880
881
|
}, paths);
|
|
881
882
|
saveState(paths.statePath, attachedConversation);
|
|
@@ -1264,6 +1265,53 @@ function startExecutorMonitor({ statePath, logPath, pid, outputPath, agentTimeou
|
|
|
1264
1265
|
child.unref();
|
|
1265
1266
|
return child;
|
|
1266
1267
|
}
|
|
1268
|
+
function startTerminalBridgeMonitor({ statePath, logPath, agentTimeoutMinutes, pollIntervalMs, codexHome }) {
|
|
1269
|
+
const args = [
|
|
1270
|
+
new URL(import.meta.url).pathname,
|
|
1271
|
+
"monitor",
|
|
1272
|
+
"--terminal-bridge",
|
|
1273
|
+
"--state",
|
|
1274
|
+
statePath,
|
|
1275
|
+
"--log",
|
|
1276
|
+
logPath,
|
|
1277
|
+
"--agent-timeout-minutes",
|
|
1278
|
+
String(agentTimeoutMinutes),
|
|
1279
|
+
"--poll-interval-ms",
|
|
1280
|
+
String(pollIntervalMs)
|
|
1281
|
+
];
|
|
1282
|
+
if (codexHome) {
|
|
1283
|
+
args.push("--codex-home", codexHome);
|
|
1284
|
+
}
|
|
1285
|
+
const child = spawn(process.execPath, args, {
|
|
1286
|
+
detached: true,
|
|
1287
|
+
stdio: "ignore",
|
|
1288
|
+
cwd: process.cwd(),
|
|
1289
|
+
env: process.env
|
|
1290
|
+
});
|
|
1291
|
+
child.unref();
|
|
1292
|
+
return child;
|
|
1293
|
+
}
|
|
1294
|
+
function terminalBridgeEnabled(conversation) {
|
|
1295
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
1296
|
+
? conversation.native_session_takeover
|
|
1297
|
+
: undefined;
|
|
1298
|
+
return nativeTakeover?.["terminal_bridge"] === true;
|
|
1299
|
+
}
|
|
1300
|
+
function withTerminalBridgeState({ conversation, message, startedAt }) {
|
|
1301
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
1302
|
+
? conversation.native_session_takeover
|
|
1303
|
+
: {};
|
|
1304
|
+
return {
|
|
1305
|
+
...conversation,
|
|
1306
|
+
native_session_takeover: {
|
|
1307
|
+
...nativeTakeover,
|
|
1308
|
+
terminal_bridge: true,
|
|
1309
|
+
terminal_bridge_started_at: startedAt,
|
|
1310
|
+
terminal_bridge_message_id: message.id
|
|
1311
|
+
},
|
|
1312
|
+
updated_at: startedAt
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1267
1315
|
function uniqueDelegateSessionName(kind) {
|
|
1268
1316
|
const { sessionPrefix } = executorDefinitionForKind(kind || "claude");
|
|
1269
1317
|
const timestamp = new Date().toISOString().replace(/\D/g, "").slice(0, 14);
|
|
@@ -2310,7 +2358,9 @@ async function runTerminalConversationSend({ options, conversationId, messageBod
|
|
|
2310
2358
|
}
|
|
2311
2359
|
async function runTerminalControlSend({ options, conversation, nextConversation, statePath, logPath, executor, message, terminalControl, needsNativeTakeoverBootstrap }) {
|
|
2312
2360
|
const provider = createTerminalControlProvider(options);
|
|
2313
|
-
const
|
|
2361
|
+
const bridge = terminalBridgeEnabled(conversation);
|
|
2362
|
+
const bridgeStartedAt = new Date().toISOString();
|
|
2363
|
+
const terminalPayload = needsNativeTakeoverBootstrap && !bridge
|
|
2314
2364
|
? terminalSubmissionPayload(buildAgentSendPayload({
|
|
2315
2365
|
conversation,
|
|
2316
2366
|
executor,
|
|
@@ -2342,14 +2392,45 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
2342
2392
|
message: textSummary(message.body),
|
|
2343
2393
|
payload: textSummary(terminalPayload)
|
|
2344
2394
|
});
|
|
2395
|
+
const bridgeConversation = bridge
|
|
2396
|
+
? withTerminalBridgeState({
|
|
2397
|
+
conversation: nextConversation,
|
|
2398
|
+
message,
|
|
2399
|
+
startedAt: bridgeStartedAt
|
|
2400
|
+
})
|
|
2401
|
+
: nextConversation;
|
|
2345
2402
|
const deliveredConversation = markTakeoverBootstrapped({
|
|
2346
|
-
conversation:
|
|
2403
|
+
conversation: bridgeConversation,
|
|
2347
2404
|
statePath,
|
|
2348
2405
|
logPath,
|
|
2349
2406
|
executor,
|
|
2350
|
-
native: needsNativeTakeoverBootstrap,
|
|
2407
|
+
native: needsNativeTakeoverBootstrap && !bridge,
|
|
2351
2408
|
fork: false
|
|
2352
2409
|
});
|
|
2410
|
+
const bridgeMonitor = bridge && deliveredConversation.gateway_method && options.disableTerminalBridgeMonitor !== true
|
|
2411
|
+
? startTerminalBridgeMonitor({
|
|
2412
|
+
statePath,
|
|
2413
|
+
logPath,
|
|
2414
|
+
agentTimeoutMinutes: Number(options.agentTimeoutMinutes ?? DEFAULT_AGENT_TIMEOUT_MINUTES),
|
|
2415
|
+
pollIntervalMs: Number(options.monitorPollIntervalMs ?? DEFAULT_MONITOR_POLL_INTERVAL_MS),
|
|
2416
|
+
codexHome: options.codexHome
|
|
2417
|
+
})
|
|
2418
|
+
: undefined;
|
|
2419
|
+
if (bridgeMonitor) {
|
|
2420
|
+
appendEvent(logPath, {
|
|
2421
|
+
ts: new Date().toISOString(),
|
|
2422
|
+
conversation_id: deliveredConversation.conversation_id,
|
|
2423
|
+
event: "terminal_bridge_monitor_launch",
|
|
2424
|
+
pid: bridgeMonitor.pid ?? null,
|
|
2425
|
+
terminal_control: terminalControl,
|
|
2426
|
+
agent_timeout_minutes: Number(options.agentTimeoutMinutes ?? DEFAULT_AGENT_TIMEOUT_MINUTES)
|
|
2427
|
+
});
|
|
2428
|
+
runtimeLog("info", "terminal_bridge_monitor_launch", {
|
|
2429
|
+
conversation_id: deliveredConversation.conversation_id,
|
|
2430
|
+
monitor_pid: bridgeMonitor.pid ?? null,
|
|
2431
|
+
terminal_target: terminalControl.target
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2353
2434
|
printJson({
|
|
2354
2435
|
conversation: deliveredConversation,
|
|
2355
2436
|
message,
|
|
@@ -2358,6 +2439,7 @@ async function runTerminalControlSend({ options, conversation, nextConversation,
|
|
|
2358
2439
|
background: true,
|
|
2359
2440
|
callback_expected: Boolean(deliveredConversation.callback_command || deliveredConversation.gateway_method),
|
|
2360
2441
|
terminal_control: terminalControl,
|
|
2442
|
+
monitor_pid: bridgeMonitor?.pid ?? null,
|
|
2361
2443
|
executor,
|
|
2362
2444
|
budget: budgetAction(deliveredConversation),
|
|
2363
2445
|
openclaw_next_action: openClawYieldNextAction({
|
|
@@ -2423,7 +2505,8 @@ function createManagedTerminalConversationFromRawId({ options, conversationId, m
|
|
|
2423
2505
|
attached_at: now.toISOString(),
|
|
2424
2506
|
takeover_match_kind: "raw_terminal_send",
|
|
2425
2507
|
terminal_control: terminalControl,
|
|
2426
|
-
needs_bootstrap:
|
|
2508
|
+
needs_bootstrap: false,
|
|
2509
|
+
terminal_bridge: true
|
|
2427
2510
|
}
|
|
2428
2511
|
}, paths);
|
|
2429
2512
|
saveState(paths.statePath, attachedConversation);
|
|
@@ -3192,7 +3275,10 @@ function runClose(options) {
|
|
|
3192
3275
|
closed: true
|
|
3193
3276
|
});
|
|
3194
3277
|
}
|
|
3195
|
-
function runMonitor(options) {
|
|
3278
|
+
async function runMonitor(options) {
|
|
3279
|
+
if (options.terminalBridge) {
|
|
3280
|
+
return await runTerminalBridgeMonitor(options);
|
|
3281
|
+
}
|
|
3196
3282
|
const statePath = expandHome(required(options.state, "--state is required"));
|
|
3197
3283
|
const logPath = expandHome(options.log ?? logPathForStatePath(statePath));
|
|
3198
3284
|
const pid = options.pid ? Number(options.pid) : undefined;
|
|
@@ -3321,6 +3407,227 @@ function runMonitor(options) {
|
|
|
3321
3407
|
sleepSync(pollIntervalMs);
|
|
3322
3408
|
}
|
|
3323
3409
|
}
|
|
3410
|
+
async function runTerminalBridgeMonitor(options) {
|
|
3411
|
+
const statePath = expandHome(required(options.state, "--state is required"));
|
|
3412
|
+
const logPath = expandHome(options.log ?? logPathForStatePath(statePath));
|
|
3413
|
+
const timeoutMinutes = Number(options.agentTimeoutMinutes ?? DEFAULT_AGENT_TIMEOUT_MINUTES);
|
|
3414
|
+
const pollIntervalMs = Math.max(50, Number(options.pollIntervalMs ?? DEFAULT_MONITOR_POLL_INTERVAL_MS));
|
|
3415
|
+
let conversation = loadState(statePath);
|
|
3416
|
+
const executor = executorForConversation(conversation);
|
|
3417
|
+
appendEvent(logPath, {
|
|
3418
|
+
ts: new Date().toISOString(),
|
|
3419
|
+
conversation_id: conversation.conversation_id,
|
|
3420
|
+
event: "terminal_bridge_monitor_started",
|
|
3421
|
+
executor,
|
|
3422
|
+
agent_timeout_minutes: timeoutMinutes,
|
|
3423
|
+
poll_interval_ms: pollIntervalMs
|
|
3424
|
+
});
|
|
3425
|
+
runtimeLog("info", "terminal_bridge_monitor_started", {
|
|
3426
|
+
conversation_id: conversation.conversation_id,
|
|
3427
|
+
agent: executor.kind,
|
|
3428
|
+
executor_session: executor.session,
|
|
3429
|
+
agent_timeout_minutes: timeoutMinutes
|
|
3430
|
+
});
|
|
3431
|
+
while (true) {
|
|
3432
|
+
conversation = loadState(statePath);
|
|
3433
|
+
if (!isWaitingForAgent(conversation.status)) {
|
|
3434
|
+
runtimeLog("info", "terminal_bridge_monitor_finished", {
|
|
3435
|
+
conversation_id: conversation.conversation_id,
|
|
3436
|
+
status: conversation.status,
|
|
3437
|
+
reason: "conversation_no_longer_waiting"
|
|
3438
|
+
});
|
|
3439
|
+
printJson({
|
|
3440
|
+
conversation,
|
|
3441
|
+
monitored: true,
|
|
3442
|
+
terminal_bridge: true,
|
|
3443
|
+
completed: false,
|
|
3444
|
+
reason: "conversation_no_longer_waiting"
|
|
3445
|
+
});
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
const nativeTakeover = isRecord(conversation.native_session_takeover)
|
|
3449
|
+
? conversation.native_session_takeover
|
|
3450
|
+
: undefined;
|
|
3451
|
+
const terminalControl = terminalControlFromTakeover(nativeTakeover);
|
|
3452
|
+
if (!terminalControl || nativeTakeover?.["terminal_bridge"] !== true) {
|
|
3453
|
+
const stalledConversation = markConversationStalled({
|
|
3454
|
+
statePath,
|
|
3455
|
+
logPath,
|
|
3456
|
+
reason: "terminal bridge monitor could not find terminal bridge metadata",
|
|
3457
|
+
detail: {
|
|
3458
|
+
terminal_bridge: true
|
|
3459
|
+
}
|
|
3460
|
+
});
|
|
3461
|
+
printJson({
|
|
3462
|
+
conversation: stalledConversation,
|
|
3463
|
+
monitored: true,
|
|
3464
|
+
terminal_bridge: true,
|
|
3465
|
+
stalled: true,
|
|
3466
|
+
reason: stalledConversation?.stalled_reason
|
|
3467
|
+
});
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
const terminalStatus = await terminalStatusForControl(terminalControl, options);
|
|
3471
|
+
const approval = terminalStatus.approval_state;
|
|
3472
|
+
if (isRecord(approval) && approval.blocked === true) {
|
|
3473
|
+
appendEvent(logPath, {
|
|
3474
|
+
ts: new Date().toISOString(),
|
|
3475
|
+
conversation_id: conversation.conversation_id,
|
|
3476
|
+
event: "terminal_bridge_approval_detected",
|
|
3477
|
+
terminal_control: terminalControl,
|
|
3478
|
+
activity_state: terminalStatus.activity_state,
|
|
3479
|
+
activity_reason: terminalStatus.activity_reason
|
|
3480
|
+
});
|
|
3481
|
+
printJson({
|
|
3482
|
+
conversation,
|
|
3483
|
+
monitored: true,
|
|
3484
|
+
terminal_bridge: true,
|
|
3485
|
+
awaiting_approval: true,
|
|
3486
|
+
terminal_control: terminalControl,
|
|
3487
|
+
terminal_status: terminalStatus
|
|
3488
|
+
});
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
const contextMatch = await terminalBridgeCodexContext({
|
|
3492
|
+
conversation,
|
|
3493
|
+
nativeTakeover,
|
|
3494
|
+
options
|
|
3495
|
+
});
|
|
3496
|
+
const startedAt = stringValue(nativeTakeover?.["terminal_bridge_started_at"]);
|
|
3497
|
+
const assistantMessage = contextMatch?.context
|
|
3498
|
+
? latestAssistantAfter(contextMatch.context, startedAt)
|
|
3499
|
+
: undefined;
|
|
3500
|
+
const terminalStillWorking = terminalStatus.activity_state === "working";
|
|
3501
|
+
if (assistantMessage && !terminalStillWorking) {
|
|
3502
|
+
appendEvent(logPath, {
|
|
3503
|
+
ts: new Date().toISOString(),
|
|
3504
|
+
conversation_id: conversation.conversation_id,
|
|
3505
|
+
event: "terminal_bridge_completion_detected",
|
|
3506
|
+
terminal_control: terminalControl,
|
|
3507
|
+
match: contextMatch?.match,
|
|
3508
|
+
codex_session: contextMatch?.context.source,
|
|
3509
|
+
assistant_timestamp: assistantMessage.timestamp
|
|
3510
|
+
});
|
|
3511
|
+
const callbackMessage = createMessage({
|
|
3512
|
+
conversation,
|
|
3513
|
+
from: executor.actor,
|
|
3514
|
+
to: "openclaw",
|
|
3515
|
+
type: "done",
|
|
3516
|
+
requiresResponse: false,
|
|
3517
|
+
body: assistantMessage.text,
|
|
3518
|
+
metadata: {
|
|
3519
|
+
source: "terminal_bridge",
|
|
3520
|
+
terminal_control: terminalControl,
|
|
3521
|
+
codex_session: contextMatch?.context.source,
|
|
3522
|
+
confidence: contextMatch?.confidence,
|
|
3523
|
+
assistant_timestamp: assistantMessage.timestamp
|
|
3524
|
+
}
|
|
3525
|
+
});
|
|
3526
|
+
runLockedCallback({
|
|
3527
|
+
...options,
|
|
3528
|
+
statePath,
|
|
3529
|
+
log: logPath,
|
|
3530
|
+
messageJson: JSON.stringify(callbackMessage),
|
|
3531
|
+
gatewayMethod: conversation.gateway_method,
|
|
3532
|
+
gatewaySession: conversation.gateway_session,
|
|
3533
|
+
openclawSession: conversation.openclaw_session,
|
|
3534
|
+
openclawBin: conversation.openclaw_bin,
|
|
3535
|
+
gatewayUrl: conversation.gateway_url,
|
|
3536
|
+
token: conversation.gateway_token
|
|
3537
|
+
});
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
if (Number.isFinite(timeoutMinutes) && timeoutMinutes > 0) {
|
|
3541
|
+
const updatedAtMs = Date.parse(String(conversation.updated_at ?? conversation.created_at));
|
|
3542
|
+
if (Number.isFinite(updatedAtMs) && Date.now() - updatedAtMs >= timeoutMinutes * 60 * 1000) {
|
|
3543
|
+
const stalledConversation = markConversationStalled({
|
|
3544
|
+
statePath,
|
|
3545
|
+
logPath,
|
|
3546
|
+
reason: `terminal bridge did not observe completion after ${timeoutMinutes} minutes`,
|
|
3547
|
+
detail: {
|
|
3548
|
+
terminal_bridge: true,
|
|
3549
|
+
terminal_control: terminalControl,
|
|
3550
|
+
match: contextMatch?.match,
|
|
3551
|
+
terminal_activity_state: terminalStatus.activity_state
|
|
3552
|
+
}
|
|
3553
|
+
});
|
|
3554
|
+
printJson({
|
|
3555
|
+
conversation: stalledConversation,
|
|
3556
|
+
monitored: true,
|
|
3557
|
+
terminal_bridge: true,
|
|
3558
|
+
stalled: true,
|
|
3559
|
+
reason: stalledConversation?.stalled_reason
|
|
3560
|
+
});
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
sleepSync(pollIntervalMs);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
async function terminalBridgeCodexContext({ conversation, nativeTakeover, options }) {
|
|
3568
|
+
const provider = createAgentSessionProvider("codex", options);
|
|
3569
|
+
const nativeSessionId = stringValue(nativeTakeover?.["native_session_id"]);
|
|
3570
|
+
const terminalConversation = parseTerminalConversationId(nativeSessionId);
|
|
3571
|
+
const activeProcess = await activeCodexProcessForPid(options, terminalConversation?.pid);
|
|
3572
|
+
const directSessionId = activeProcess?.sessionId ?? (terminalConversation ? undefined : nativeSessionId);
|
|
3573
|
+
if (directSessionId) {
|
|
3574
|
+
const context = await provider.getForkContext({
|
|
3575
|
+
sessionId: directSessionId,
|
|
3576
|
+
maxMessages: Number(options.maxMessages ?? 16),
|
|
3577
|
+
maxCommands: Number(options.maxCommands ?? 10),
|
|
3578
|
+
maxTextLength: Number(options.maxTextLength ?? 4000)
|
|
3579
|
+
});
|
|
3580
|
+
if (context) {
|
|
3581
|
+
return {
|
|
3582
|
+
context,
|
|
3583
|
+
process: activeProcess,
|
|
3584
|
+
match: activeProcess?.sessionId ? "process_session_id" : "native_session_id",
|
|
3585
|
+
confidence: "high"
|
|
3586
|
+
};
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
const cwd = activeProcess?.cwd ?? stringValue(nativeTakeover?.["source_cwd"]);
|
|
3590
|
+
if (!cwd) {
|
|
3591
|
+
return undefined;
|
|
3592
|
+
}
|
|
3593
|
+
const sessions = (await provider.listHistoricalSessions())
|
|
3594
|
+
.filter((session) => session.cwd === cwd)
|
|
3595
|
+
.sort((left, right) => Number(right.updatedAtMs ?? 0) - Number(left.updatedAtMs ?? 0));
|
|
3596
|
+
const selected = sessions[0];
|
|
3597
|
+
if (!selected) {
|
|
3598
|
+
return undefined;
|
|
3599
|
+
}
|
|
3600
|
+
const context = await provider.getForkContext({
|
|
3601
|
+
sessionId: selected.id,
|
|
3602
|
+
maxMessages: Number(options.maxMessages ?? 16),
|
|
3603
|
+
maxCommands: Number(options.maxCommands ?? 10),
|
|
3604
|
+
maxTextLength: Number(options.maxTextLength ?? 4000)
|
|
3605
|
+
});
|
|
3606
|
+
if (!context) {
|
|
3607
|
+
return undefined;
|
|
3608
|
+
}
|
|
3609
|
+
return {
|
|
3610
|
+
context,
|
|
3611
|
+
process: activeProcess,
|
|
3612
|
+
match: sessions.length === 1 ? "cwd" : "cwd_latest",
|
|
3613
|
+
confidence: sessions.length === 1 ? "medium" : "low"
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
function latestAssistantAfter(context, startedAt) {
|
|
3617
|
+
const threshold = startedAt ? Date.parse(startedAt) : undefined;
|
|
3618
|
+
return [...visibleRolloutMessages(context)]
|
|
3619
|
+
.reverse()
|
|
3620
|
+
.find((message) => {
|
|
3621
|
+
if (message.role !== "assistant") {
|
|
3622
|
+
return false;
|
|
3623
|
+
}
|
|
3624
|
+
if (!Number.isFinite(threshold)) {
|
|
3625
|
+
return true;
|
|
3626
|
+
}
|
|
3627
|
+
const messageTime = message.timestamp ? Date.parse(message.timestamp) : NaN;
|
|
3628
|
+
return Number.isFinite(messageTime) && messageTime >= Number(threshold);
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3324
3631
|
function resolveExecutable(command) {
|
|
3325
3632
|
if (command.includes(path.sep)) {
|
|
3326
3633
|
return command;
|