@alook/daemon 0.1.17 → 0.1.18
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/README.md +1 -1
- package/dist/cli/index.js +1424 -276
- package/dist/index.js +1392 -269
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1119,19 +1119,23 @@ class ClaudeEventNormalizer {
|
|
|
1119
1119
|
const content = event?.message?.content;
|
|
1120
1120
|
if (!Array.isArray(content))
|
|
1121
1121
|
return;
|
|
1122
|
+
const completedText = [];
|
|
1122
1123
|
for (const block of content) {
|
|
1123
1124
|
if (block?.type === "thinking") {
|
|
1124
|
-
out.push({ kind: "
|
|
1125
|
+
out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
|
|
1125
1126
|
} else if (block?.type === "text") {
|
|
1126
1127
|
const text = block.text ?? "";
|
|
1127
1128
|
if (API_ERROR_RE.test(text))
|
|
1128
1129
|
out.push({ kind: "error", message: text });
|
|
1129
1130
|
else
|
|
1130
|
-
|
|
1131
|
+
completedText.push(text);
|
|
1131
1132
|
} else if (block?.type === "tool_use") {
|
|
1132
1133
|
out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
|
|
1133
1134
|
}
|
|
1134
1135
|
}
|
|
1136
|
+
if (completedText.length > 0) {
|
|
1137
|
+
out.push({ kind: "assistant_message_completed", text: completedText.join("") });
|
|
1138
|
+
}
|
|
1135
1139
|
}
|
|
1136
1140
|
handleUser(event, out) {
|
|
1137
1141
|
if (event.isReplay === true && typeof event.uuid === "string") {
|
|
@@ -1540,14 +1544,17 @@ class CodexEventNormalizer {
|
|
|
1540
1544
|
this.turnId = params.turn.id;
|
|
1541
1545
|
this.terminalTurn = null;
|
|
1542
1546
|
return [
|
|
1543
|
-
{
|
|
1544
|
-
|
|
1547
|
+
{
|
|
1548
|
+
kind: "turn_owner",
|
|
1549
|
+
receipt: this.turnReceipt(params.threadId, params.turn.id),
|
|
1550
|
+
nativeTurnId: params.turn.id
|
|
1551
|
+
}
|
|
1545
1552
|
];
|
|
1546
1553
|
case "item/reasoning/textDelta":
|
|
1547
1554
|
case "item/reasoning/summaryTextDelta":
|
|
1548
|
-
return [{ kind: "
|
|
1555
|
+
return [{ kind: "assistant_reasoning_delta", text: params?.delta ?? "" }];
|
|
1549
1556
|
case "item/agentMessage/delta":
|
|
1550
|
-
return [{ kind: "
|
|
1557
|
+
return [{ kind: "assistant_message_delta", text: params?.delta ?? "" }];
|
|
1551
1558
|
case "item/started":
|
|
1552
1559
|
return this.handleItemStarted(params);
|
|
1553
1560
|
case "item/completed":
|
|
@@ -1575,7 +1582,10 @@ class CodexEventNormalizer {
|
|
|
1575
1582
|
}
|
|
1576
1583
|
return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
1577
1584
|
case "error":
|
|
1578
|
-
|
|
1585
|
+
if (params?.willRetry === true) {
|
|
1586
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
|
|
1587
|
+
}
|
|
1588
|
+
return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
|
|
1579
1589
|
case "thread/tokenUsage/updated":
|
|
1580
1590
|
case "account/rateLimits/updated":
|
|
1581
1591
|
return mapCodexTelemetry(method, params);
|
|
@@ -1668,9 +1678,9 @@ class CodexEventNormalizer {
|
|
|
1668
1678
|
case "collabAgentToolCall":
|
|
1669
1679
|
return [{ kind: "tool_output", name: "collab_tool_call" }];
|
|
1670
1680
|
case "agentMessage":
|
|
1671
|
-
return [{ kind: "
|
|
1681
|
+
return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
|
|
1672
1682
|
case "reasoning":
|
|
1673
|
-
return [{ kind: "
|
|
1683
|
+
return [{ kind: "assistant_reasoning_completed", text: params?.item?.text ?? "" }];
|
|
1674
1684
|
default:
|
|
1675
1685
|
return [];
|
|
1676
1686
|
}
|
|
@@ -1703,7 +1713,13 @@ class CodexDriver {
|
|
|
1703
1713
|
lifetime: "session",
|
|
1704
1714
|
transport: { kind: "stdio_rpc", protocol: "codex.app-server.v1" },
|
|
1705
1715
|
wakeStart: "immediate",
|
|
1706
|
-
terminalOwnership: "transport_request"
|
|
1716
|
+
terminalOwnership: "transport_request",
|
|
1717
|
+
turnSilence: {
|
|
1718
|
+
nativeIdleTimeoutMs: 300000,
|
|
1719
|
+
daemonGraceMs: 60000,
|
|
1720
|
+
recoveryGraceMs: 60000,
|
|
1721
|
+
maxRecoveryExtensions: 1
|
|
1722
|
+
}
|
|
1707
1723
|
};
|
|
1708
1724
|
eventNormalizer = new CodexEventNormalizer;
|
|
1709
1725
|
requestId = 0;
|
|
@@ -2360,14 +2376,14 @@ class CursorAcpLane {
|
|
|
2360
2376
|
case "agent_message_chunk": {
|
|
2361
2377
|
const content = record(update.content);
|
|
2362
2378
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
2363
|
-
this.events.emit("runtime_event", { kind: "
|
|
2379
|
+
this.events.emit("runtime_event", { kind: "assistant_message_delta", text: content.text });
|
|
2364
2380
|
}
|
|
2365
2381
|
return;
|
|
2366
2382
|
}
|
|
2367
2383
|
case "agent_thought_chunk": {
|
|
2368
2384
|
const content = record(update.content);
|
|
2369
2385
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
2370
|
-
this.events.emit("runtime_event", { kind: "
|
|
2386
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_delta", text: content.text });
|
|
2371
2387
|
}
|
|
2372
2388
|
return;
|
|
2373
2389
|
}
|
|
@@ -3172,16 +3188,20 @@ class OpenCodeServiceLane {
|
|
|
3172
3188
|
}
|
|
3173
3189
|
switch (event.type) {
|
|
3174
3190
|
case "session.next.step.started":
|
|
3175
|
-
this.events.emit("runtime_event", {
|
|
3191
|
+
this.events.emit("runtime_event", {
|
|
3192
|
+
kind: "internal_progress",
|
|
3193
|
+
source: "opencode.service",
|
|
3194
|
+
itemType: "step_started"
|
|
3195
|
+
});
|
|
3176
3196
|
break;
|
|
3177
3197
|
case "session.next.text.ended":
|
|
3178
3198
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
3179
|
-
this.events.emit("runtime_event", { kind: "
|
|
3199
|
+
this.events.emit("runtime_event", { kind: "assistant_message_completed", text: data.text });
|
|
3180
3200
|
}
|
|
3181
3201
|
break;
|
|
3182
3202
|
case "session.next.reasoning.ended":
|
|
3183
3203
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
3184
|
-
this.events.emit("runtime_event", { kind: "
|
|
3204
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: data.text });
|
|
3185
3205
|
}
|
|
3186
3206
|
break;
|
|
3187
3207
|
case "session.next.tool.called":
|
|
@@ -3932,22 +3952,28 @@ function readPiSdkVersion() {
|
|
|
3932
3952
|
}
|
|
3933
3953
|
function mapPiSdkEvent(event, sessionId, state) {
|
|
3934
3954
|
if (event?.type === "message_update") {
|
|
3935
|
-
const d = event.
|
|
3955
|
+
const d = event.assistantMessageEvent ?? {};
|
|
3936
3956
|
switch (d.type) {
|
|
3937
3957
|
case "thinking_delta":
|
|
3938
|
-
return [{ kind: "
|
|
3958
|
+
return [{ kind: "assistant_reasoning_delta", text: d.delta ?? "" }];
|
|
3939
3959
|
case "text_delta":
|
|
3940
3960
|
state.sawTextDelta = true;
|
|
3941
|
-
return [{ kind: "
|
|
3942
|
-
case "text_end":
|
|
3943
|
-
|
|
3961
|
+
return [{ kind: "assistant_message_delta", text: d.delta ?? "" }];
|
|
3962
|
+
case "text_end": {
|
|
3963
|
+
state.sawTextDelta = false;
|
|
3964
|
+
return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
|
|
3965
|
+
}
|
|
3944
3966
|
case "error":
|
|
3945
|
-
return [{ kind: "error", message: d.
|
|
3967
|
+
return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
|
|
3946
3968
|
default:
|
|
3947
3969
|
return [];
|
|
3948
3970
|
}
|
|
3949
3971
|
}
|
|
3950
3972
|
switch (event?.type) {
|
|
3973
|
+
case "auto_retry_start":
|
|
3974
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
|
|
3975
|
+
case "auto_retry_end":
|
|
3976
|
+
return [{ kind: "runtime_recovery", stage: "recovered", source: "pi_auto_retry" }];
|
|
3951
3977
|
case "tool_execution_start":
|
|
3952
3978
|
return [{ kind: "tool_call", name: event.toolName ?? "unknown_tool", input: event.args ?? {} }];
|
|
3953
3979
|
case "tool_execution_end":
|
|
@@ -4112,6 +4138,13 @@ function assertAdapterCompatibility(registrationId, registeredCapabilities, adap
|
|
|
4112
4138
|
if (!transport || typeof transport.kind !== "string" || transport.kind.trim().length === 0 || typeof transport.protocol !== "string" || transport.protocol.trim().length === 0 || transport.metadata !== undefined && (!transport.metadata || typeof transport.metadata !== "object" || Array.isArray(transport.metadata))) {
|
|
4113
4139
|
throw new Error(`Adapter ${registrationId} has an invalid transport declaration`);
|
|
4114
4140
|
}
|
|
4141
|
+
if (execution.turnSilence !== undefined) {
|
|
4142
|
+
const silence = execution.turnSilence;
|
|
4143
|
+
const safeInteger = (value, minimum) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
|
|
4144
|
+
if (!silence || typeof silence !== "object" || Array.isArray(silence) || !safeInteger(silence.nativeIdleTimeoutMs, 1) || !safeInteger(silence.daemonGraceMs, 0) || !safeInteger(silence.recoveryGraceMs, 0) || !safeInteger(silence.maxRecoveryExtensions, 0) || !Number.isSafeInteger(silence.nativeIdleTimeoutMs + silence.daemonGraceMs)) {
|
|
4145
|
+
throw new Error(`Adapter ${registrationId} has an invalid turnSilence declaration`);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4115
4148
|
const capabilities = registeredCapabilities;
|
|
4116
4149
|
const declaredLifetime = capabilities?.sessionLifetime === "persistent" ? "session" : "turn";
|
|
4117
4150
|
if (execution.lifetime !== declaredLifetime) {
|
|
@@ -4201,6 +4234,12 @@ function capabilitiesFor(backend) {
|
|
|
4201
4234
|
return builtinRegistry.get(backend).capabilities;
|
|
4202
4235
|
}
|
|
4203
4236
|
|
|
4237
|
+
// agent-driver/dist/internal/adapter.js
|
|
4238
|
+
var DEFAULT_NATIVE_IDLE_TIMEOUT_MS = 300000;
|
|
4239
|
+
var DEFAULT_DAEMON_GRACE_MS = 60000;
|
|
4240
|
+
var DEFAULT_RECOVERY_GRACE_MS = 60000;
|
|
4241
|
+
var DEFAULT_MAX_RECOVERY_EXTENSIONS = 1;
|
|
4242
|
+
|
|
4204
4243
|
// agent-driver/dist/controller/event-queue.js
|
|
4205
4244
|
var MAX_BUFFERED_BYTES = 4194304;
|
|
4206
4245
|
|
|
@@ -4397,6 +4436,62 @@ function stableErrorCode(value, fallback) {
|
|
|
4397
4436
|
|
|
4398
4437
|
// agent-driver/dist/controller/logical-session.js
|
|
4399
4438
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
4439
|
+
var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
|
|
4440
|
+
var WORK_HEARTBEAT_MIN_INTERVAL_MS = 1000;
|
|
4441
|
+
function emptySemanticAssembler() {
|
|
4442
|
+
return { chunks: [], bytes: 0, truncated: false };
|
|
4443
|
+
}
|
|
4444
|
+
function utf8Prefix(text, maxBytes) {
|
|
4445
|
+
if (maxBytes <= 0 || text.length === 0)
|
|
4446
|
+
return "";
|
|
4447
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes)
|
|
4448
|
+
return text;
|
|
4449
|
+
let low = 0;
|
|
4450
|
+
let high = text.length;
|
|
4451
|
+
while (low < high) {
|
|
4452
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4453
|
+
let end2 = mid;
|
|
4454
|
+
const code2 = text.charCodeAt(end2 - 1);
|
|
4455
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
4456
|
+
end2 -= 1;
|
|
4457
|
+
if (Buffer.byteLength(text.slice(0, end2), "utf8") <= maxBytes)
|
|
4458
|
+
low = mid;
|
|
4459
|
+
else
|
|
4460
|
+
high = mid - 1;
|
|
4461
|
+
}
|
|
4462
|
+
let end = low;
|
|
4463
|
+
const code = text.charCodeAt(end - 1);
|
|
4464
|
+
if (code >= 55296 && code <= 56319)
|
|
4465
|
+
end -= 1;
|
|
4466
|
+
while (end > 0 && Buffer.byteLength(text.slice(0, end), "utf8") > maxBytes)
|
|
4467
|
+
end -= 1;
|
|
4468
|
+
return text.slice(0, end);
|
|
4469
|
+
}
|
|
4470
|
+
function appendSemanticFragment(buffer, text) {
|
|
4471
|
+
if (text.length === 0 || buffer.truncated)
|
|
4472
|
+
return;
|
|
4473
|
+
const remaining = SEMANTIC_ASSEMBLER_MAX_BYTES - buffer.bytes;
|
|
4474
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
4475
|
+
if (bytes <= remaining) {
|
|
4476
|
+
buffer.chunks.push(text);
|
|
4477
|
+
buffer.bytes += bytes;
|
|
4478
|
+
return;
|
|
4479
|
+
}
|
|
4480
|
+
const prefix = utf8Prefix(text, remaining);
|
|
4481
|
+
if (prefix.length > 0) {
|
|
4482
|
+
buffer.chunks.push(prefix);
|
|
4483
|
+
buffer.bytes += Buffer.byteLength(prefix, "utf8");
|
|
4484
|
+
}
|
|
4485
|
+
buffer.truncated = true;
|
|
4486
|
+
}
|
|
4487
|
+
function finishSemanticAssembler(buffer) {
|
|
4488
|
+
return { text: buffer.chunks.join(""), truncated: buffer.truncated };
|
|
4489
|
+
}
|
|
4490
|
+
function boundedSemanticCompletion(text) {
|
|
4491
|
+
const buffer = emptySemanticAssembler();
|
|
4492
|
+
appendSemanticFragment(buffer, text);
|
|
4493
|
+
return finishSemanticAssembler(buffer);
|
|
4494
|
+
}
|
|
4400
4495
|
function driverError(category, code, message, retryable = false) {
|
|
4401
4496
|
return { category, code, message: scrubDriverErrorMessage(message), retryable };
|
|
4402
4497
|
}
|
|
@@ -4470,6 +4565,7 @@ class LogicalAgentSession {
|
|
|
4470
4565
|
resumeOutcome;
|
|
4471
4566
|
eventQueue;
|
|
4472
4567
|
behavior;
|
|
4568
|
+
turnSilence;
|
|
4473
4569
|
constructor(backend, config, launch, adapter, capabilities2, host, prepared, hostReleaseTimeoutMs) {
|
|
4474
4570
|
this.backend = backend;
|
|
4475
4571
|
this.config = config;
|
|
@@ -4480,6 +4576,18 @@ class LogicalAgentSession {
|
|
|
4480
4576
|
this.hostReleaseTimeoutMs = hostReleaseTimeoutMs;
|
|
4481
4577
|
this.capabilities = capabilities2;
|
|
4482
4578
|
this.behavior = this.capabilities;
|
|
4579
|
+
const declaredSilence = adapter.execution.turnSilence;
|
|
4580
|
+
const nativeIdleTimeoutMs = declaredSilence?.nativeIdleTimeoutMs ?? DEFAULT_NATIVE_IDLE_TIMEOUT_MS;
|
|
4581
|
+
const daemonGraceMs = declaredSilence?.daemonGraceMs ?? DEFAULT_DAEMON_GRACE_MS;
|
|
4582
|
+
const recoveryGraceMs = declaredSilence?.recoveryGraceMs ?? DEFAULT_RECOVERY_GRACE_MS;
|
|
4583
|
+
const maxRecoveryExtensions = declaredSilence?.maxRecoveryExtensions ?? DEFAULT_MAX_RECOVERY_EXTENSIONS;
|
|
4584
|
+
this.turnSilence = {
|
|
4585
|
+
nativeIdleTimeoutMs,
|
|
4586
|
+
daemonGraceMs,
|
|
4587
|
+
recoveryGraceMs,
|
|
4588
|
+
maxRecoveryExtensions,
|
|
4589
|
+
normalBudgetMs: nativeIdleTimeoutMs + daemonGraceMs
|
|
4590
|
+
};
|
|
4483
4591
|
this.resumeOutcome = launch.resumeSessionId ? "pending" : "not_requested";
|
|
4484
4592
|
this.sessionInstanceId = host.createId();
|
|
4485
4593
|
this.closed = new Promise((resolve3) => {
|
|
@@ -4584,6 +4692,7 @@ class LogicalAgentSession {
|
|
|
4584
4692
|
lastEventSequence: this.eventSequence,
|
|
4585
4693
|
diagnostics: {
|
|
4586
4694
|
deliveryPhase: this.deliveryPhase(),
|
|
4695
|
+
turnSilence: this.turnSilence,
|
|
4587
4696
|
metrics: {
|
|
4588
4697
|
physicalOpenCount: this.metricValue(this.physicalOpenCount),
|
|
4589
4698
|
turnCount: this.metricValue(this.turnCount),
|
|
@@ -4674,7 +4783,14 @@ class LogicalAgentSession {
|
|
|
4674
4783
|
const turnId = this.host.createId();
|
|
4675
4784
|
const commandIds = messages.map((message) => message.id);
|
|
4676
4785
|
const terminalOwner = this.adapter.beginTurn?.();
|
|
4677
|
-
this.activeTurn = {
|
|
4786
|
+
this.activeTurn = {
|
|
4787
|
+
turnId,
|
|
4788
|
+
commandIds: [...commandIds],
|
|
4789
|
+
...terminalOwner ? { terminalOwner } : {},
|
|
4790
|
+
pendingMessage: emptySemanticAssembler(),
|
|
4791
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
4792
|
+
lastWorkHeartbeatAt: null
|
|
4793
|
+
};
|
|
4678
4794
|
this.turnError = undefined;
|
|
4679
4795
|
this.interruptedTurnId = undefined;
|
|
4680
4796
|
this.processTurnEnded = false;
|
|
@@ -4872,14 +4988,36 @@ class LogicalAgentSession {
|
|
|
4872
4988
|
return;
|
|
4873
4989
|
}
|
|
4874
4990
|
this.activeTurn.terminalOwner = event.receipt;
|
|
4991
|
+
const nativeTurnId = event.nativeTurnId?.trim();
|
|
4992
|
+
if (nativeTurnId && nativeTurnId.length <= 512 && /^[A-Za-z0-9._:-]+$/.test(nativeTurnId)) {
|
|
4993
|
+
this.emit({
|
|
4994
|
+
type: "backend_turn_started",
|
|
4995
|
+
turnId: this.activeTurn.turnId,
|
|
4996
|
+
backendTurnId: nativeTurnId
|
|
4997
|
+
});
|
|
4998
|
+
}
|
|
4875
4999
|
return;
|
|
4876
|
-
case "
|
|
4877
|
-
|
|
4878
|
-
|
|
5000
|
+
case "assistant_reasoning_delta":
|
|
5001
|
+
case "assistant_message_delta":
|
|
5002
|
+
if (turnId && this.activeTurn?.turnId === turnId && event.text.length > 0) {
|
|
5003
|
+
appendSemanticFragment(event.kind === "assistant_message_delta" ? this.activeTurn.pendingMessage : this.activeTurn.pendingReasoning, event.text);
|
|
5004
|
+
const now = this.host.now();
|
|
5005
|
+
if (this.activeTurn.lastWorkHeartbeatAt === null || now - this.activeTurn.lastWorkHeartbeatAt >= WORK_HEARTBEAT_MIN_INTERVAL_MS) {
|
|
5006
|
+
this.activeTurn.lastWorkHeartbeatAt = now;
|
|
5007
|
+
this.emit({ type: "work_heartbeat", turnId });
|
|
5008
|
+
}
|
|
5009
|
+
}
|
|
4879
5010
|
return;
|
|
4880
|
-
case "
|
|
4881
|
-
|
|
4882
|
-
|
|
5011
|
+
case "assistant_reasoning_completed":
|
|
5012
|
+
case "assistant_message_completed":
|
|
5013
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
5014
|
+
const field = event.kind === "assistant_message_completed" ? "pendingMessage" : "pendingReasoning";
|
|
5015
|
+
this.activeTurn[field] = emptySemanticAssembler();
|
|
5016
|
+
if (event.text.length > 0) {
|
|
5017
|
+
const completed = boundedSemanticCompletion(event.text);
|
|
5018
|
+
this.emit({ type: event.kind, turnId, ...completed });
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
4883
5021
|
return;
|
|
4884
5022
|
case "tool_call":
|
|
4885
5023
|
this.outstandingToolUses += 1;
|
|
@@ -4922,9 +5060,19 @@ class LogicalAgentSession {
|
|
|
4922
5060
|
message: scrubDriverErrorMessage(event.message, "Runtime diagnostic")
|
|
4923
5061
|
});
|
|
4924
5062
|
return;
|
|
5063
|
+
case "runtime_recovery":
|
|
5064
|
+
this.emit({
|
|
5065
|
+
type: "recovery",
|
|
5066
|
+
turnId,
|
|
5067
|
+
stage: event.stage,
|
|
5068
|
+
source: event.source
|
|
5069
|
+
});
|
|
5070
|
+
return;
|
|
4925
5071
|
case "runtime_metric":
|
|
4926
|
-
if (event.name === "sse_reconnect" && event.increment === 1)
|
|
5072
|
+
if (event.name === "sse_reconnect" && event.increment === 1) {
|
|
4927
5073
|
this.sseReconnectCount += 1;
|
|
5074
|
+
this.emit({ type: "recovery", turnId, stage: "retrying", source: "transport_reconnect" });
|
|
5075
|
+
}
|
|
4928
5076
|
return;
|
|
4929
5077
|
case "telemetry": {
|
|
4930
5078
|
const details = jsonValue(event.attrs);
|
|
@@ -4947,6 +5095,18 @@ class LogicalAgentSession {
|
|
|
4947
5095
|
});
|
|
4948
5096
|
return;
|
|
4949
5097
|
case "turn_end":
|
|
5098
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
5099
|
+
const reasoning = finishSemanticAssembler(this.activeTurn.pendingReasoning);
|
|
5100
|
+
const message = finishSemanticAssembler(this.activeTurn.pendingMessage);
|
|
5101
|
+
this.activeTurn.pendingReasoning = emptySemanticAssembler();
|
|
5102
|
+
this.activeTurn.pendingMessage = emptySemanticAssembler();
|
|
5103
|
+
if (reasoning.text.length > 0) {
|
|
5104
|
+
this.emit({ type: "assistant_reasoning_completed", turnId, ...reasoning });
|
|
5105
|
+
}
|
|
5106
|
+
if (message.text.length > 0) {
|
|
5107
|
+
this.emit({ type: "assistant_message_completed", turnId, ...message });
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
4950
5110
|
this.completeTurn(event.sessionId, physicalOwner, generation, event.turnOwner);
|
|
4951
5111
|
return;
|
|
4952
5112
|
}
|
|
@@ -4960,7 +5120,7 @@ class LogicalAgentSession {
|
|
|
4960
5120
|
reopenClosedLaneForWork(event, physicalOwner, generation) {
|
|
4961
5121
|
if (this.adapter.execution.lifetime === "turn")
|
|
4962
5122
|
return;
|
|
4963
|
-
const isRootWork = event.kind === "
|
|
5123
|
+
const isRootWork = event.kind === "tool_call" || event.kind === "tool_output" || event.kind === "compaction_started" || event.kind === "compaction_finished" || event.kind === "review_started" || event.kind === "review_finished" || event.kind === "internal_progress";
|
|
4964
5124
|
if (!isRootWork)
|
|
4965
5125
|
return;
|
|
4966
5126
|
const tombstone = this.closedLaneTombstone;
|
|
@@ -4970,7 +5130,10 @@ class LogicalAgentSession {
|
|
|
4970
5130
|
this.activeTurn = {
|
|
4971
5131
|
turnId: tombstone.localTurnId,
|
|
4972
5132
|
commandIds: tombstone.commandIds,
|
|
4973
|
-
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {}
|
|
5133
|
+
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {},
|
|
5134
|
+
pendingMessage: emptySemanticAssembler(),
|
|
5135
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
5136
|
+
lastWorkHeartbeatAt: null
|
|
4974
5137
|
};
|
|
4975
5138
|
this.state = "working";
|
|
4976
5139
|
return tombstone.localTurnId;
|
|
@@ -6426,11 +6589,26 @@ function isActivelyWorking(agent) {
|
|
|
6426
6589
|
return agent.status === "running" && (leaseIsWorking(agent.execution.lease) || agent.pendingAdmissions.length > 0 || agent.inbox.length > 0);
|
|
6427
6590
|
}
|
|
6428
6591
|
var DEFAULT_STALE_THRESHOLD_MS = 120000;
|
|
6429
|
-
var
|
|
6592
|
+
var DEFAULT_TURN_SILENCE_POLICY = {
|
|
6593
|
+
nativeIdleTimeoutMs: 300000,
|
|
6594
|
+
daemonGraceMs: 60000,
|
|
6595
|
+
recoveryGraceMs: 60000,
|
|
6596
|
+
maxRecoveryExtensions: 1,
|
|
6597
|
+
normalBudgetMs: 360000
|
|
6598
|
+
};
|
|
6599
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
6600
|
+
var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
|
|
6430
6601
|
var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
|
|
6431
6602
|
var DEFAULT_STOPPING_STUCK_THRESHOLD_MS = 30000;
|
|
6432
|
-
function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS) {
|
|
6433
|
-
return {
|
|
6603
|
+
function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS, idleResetTimeoutMs = DEFAULT_IDLE_RESET_TIMEOUT_MS) {
|
|
6604
|
+
return {
|
|
6605
|
+
agents: {},
|
|
6606
|
+
staleThresholdMs,
|
|
6607
|
+
idleTimeoutMs,
|
|
6608
|
+
idleResetTimeoutMs,
|
|
6609
|
+
resetStuckThresholdMs,
|
|
6610
|
+
stoppingStuckThresholdMs
|
|
6611
|
+
};
|
|
6434
6612
|
}
|
|
6435
6613
|
function reduceManager(state, event) {
|
|
6436
6614
|
switch (event.type) {
|
|
@@ -6445,6 +6623,11 @@ function reduceManager(state, event) {
|
|
|
6445
6623
|
});
|
|
6446
6624
|
case "backend_session":
|
|
6447
6625
|
return mutate(state, event.agentId, (a) => {
|
|
6626
|
+
if (event.stalledBefore) {
|
|
6627
|
+
a.stalledSessionId = event.sessionId;
|
|
6628
|
+
} else if (a.stalledSessionId !== null && a.stalledSessionId !== event.sessionId) {
|
|
6629
|
+
a.stalledSessionId = null;
|
|
6630
|
+
}
|
|
6448
6631
|
a.sessionId = event.sessionId;
|
|
6449
6632
|
});
|
|
6450
6633
|
case "attach_session": {
|
|
@@ -6457,7 +6640,17 @@ function reduceManager(state, event) {
|
|
|
6457
6640
|
{
|
|
6458
6641
|
const a = agent;
|
|
6459
6642
|
a.execution = { sessionInstanceId: event.sessionInstanceId, lease: { state: "none", lastTerminal: null } };
|
|
6643
|
+
a.turnSilence = event.turnSilence ?? {
|
|
6644
|
+
...DEFAULT_TURN_SILENCE_POLICY,
|
|
6645
|
+
nativeIdleTimeoutMs: state.staleThresholdMs,
|
|
6646
|
+
daemonGraceMs: 0,
|
|
6647
|
+
normalBudgetMs: state.staleThresholdMs
|
|
6648
|
+
};
|
|
6460
6649
|
a.lastProgressAt = event.nowMs;
|
|
6650
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
6651
|
+
a.lastNativeActivityKind = null;
|
|
6652
|
+
a.runtimePhase = "admission";
|
|
6653
|
+
a.backendTurnId = null;
|
|
6461
6654
|
a.idleSince = null;
|
|
6462
6655
|
syncExecutionProjection(a);
|
|
6463
6656
|
}
|
|
@@ -6513,7 +6706,25 @@ function reduceManager(state, event) {
|
|
|
6513
6706
|
return { state, effects: [] };
|
|
6514
6707
|
return mutate(state, event.agentId, (a) => {
|
|
6515
6708
|
a.sessionId = null;
|
|
6709
|
+
a.stalledSessionId = null;
|
|
6710
|
+
a.idleSince = null;
|
|
6516
6711
|
});
|
|
6712
|
+
case "idle_reset_committed": {
|
|
6713
|
+
const existing = state.agents[event.agentId];
|
|
6714
|
+
if (!existing)
|
|
6715
|
+
return { state, effects: [] };
|
|
6716
|
+
const agent = clone(existing);
|
|
6717
|
+
agent.sessionId = null;
|
|
6718
|
+
agent.stalledSessionId = null;
|
|
6719
|
+
agent.idleSince = null;
|
|
6720
|
+
if (agent.status !== "running")
|
|
6721
|
+
return commit(state, agent, []);
|
|
6722
|
+
agent.status = "stopping";
|
|
6723
|
+
agent.stoppingSince = event.nowMs;
|
|
6724
|
+
return commit(state, agent, [
|
|
6725
|
+
{ type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
|
|
6726
|
+
]);
|
|
6727
|
+
}
|
|
6517
6728
|
case "begin_reset":
|
|
6518
6729
|
if (!state.agents[event.agentId])
|
|
6519
6730
|
return { state, effects: [] };
|
|
@@ -6551,8 +6762,18 @@ function reduceManager(state, event) {
|
|
|
6551
6762
|
return;
|
|
6552
6763
|
const startedCommands = new Set(event.commandIds);
|
|
6553
6764
|
a.pendingAdmissions = a.pendingAdmissions.filter((entry) => !startedCommands.has(entry.commandId));
|
|
6554
|
-
a.execution.lease = {
|
|
6765
|
+
a.execution.lease = {
|
|
6766
|
+
state: "active",
|
|
6767
|
+
identity,
|
|
6768
|
+
lastWorkAt: event.nowMs,
|
|
6769
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
6770
|
+
recoveryExtensionsUsed: 0
|
|
6771
|
+
};
|
|
6555
6772
|
a.lastProgressAt = event.nowMs;
|
|
6773
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
6774
|
+
a.lastNativeActivityKind = "turn_started";
|
|
6775
|
+
a.runtimePhase = "inference";
|
|
6776
|
+
a.backendTurnId = null;
|
|
6556
6777
|
a.idleSince = null;
|
|
6557
6778
|
syncExecutionProjection(a);
|
|
6558
6779
|
});
|
|
@@ -6573,7 +6794,12 @@ function reduceManager(state, event) {
|
|
|
6573
6794
|
const lease = a.execution.lease;
|
|
6574
6795
|
const identity = identityOf(event);
|
|
6575
6796
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
6576
|
-
a.execution.lease = {
|
|
6797
|
+
a.execution.lease = {
|
|
6798
|
+
...lease,
|
|
6799
|
+
lastWorkAt: event.nowMs,
|
|
6800
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
6801
|
+
recoveryExtensionsUsed: 0
|
|
6802
|
+
};
|
|
6577
6803
|
} else {
|
|
6578
6804
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
6579
6805
|
if (!terminal || !sameIdentity(terminal.identity, identity))
|
|
@@ -6582,6 +6808,8 @@ function reduceManager(state, event) {
|
|
|
6582
6808
|
state: "suspect_active",
|
|
6583
6809
|
identity,
|
|
6584
6810
|
lastWorkAt: event.nowMs,
|
|
6811
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
6812
|
+
recoveryExtensionsUsed: 0,
|
|
6585
6813
|
reason: "work_after_terminal"
|
|
6586
6814
|
};
|
|
6587
6815
|
}
|
|
@@ -6595,7 +6823,7 @@ function reduceManager(state, event) {
|
|
|
6595
6823
|
case "turn_tool_finished":
|
|
6596
6824
|
return onTurnToolLifecycle(state, event, "finished");
|
|
6597
6825
|
case "turn_completed":
|
|
6598
|
-
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId);
|
|
6826
|
+
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId, event.endReason);
|
|
6599
6827
|
case "session_closed":
|
|
6600
6828
|
if (state.agents[event.agentId]?.execution.sessionInstanceId !== event.sessionInstanceId) {
|
|
6601
6829
|
return { state, effects: [] };
|
|
@@ -6605,7 +6833,6 @@ function reduceManager(state, event) {
|
|
|
6605
6833
|
const closing = agent.pendingAdmissions.filter((entry) => entry.sessionInstanceId === event.sessionInstanceId);
|
|
6606
6834
|
agent.pendingAdmissions = agent.pendingAdmissions.filter((entry) => entry.sessionInstanceId !== event.sessionInstanceId);
|
|
6607
6835
|
agent.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
6608
|
-
agent.idleSince = null;
|
|
6609
6836
|
syncExecutionProjection(agent);
|
|
6610
6837
|
return commit(state, agent, recoveryEffects(agent, closing));
|
|
6611
6838
|
}
|
|
@@ -6613,8 +6840,57 @@ function reduceManager(state, event) {
|
|
|
6613
6840
|
return onExit(state, event.agentId);
|
|
6614
6841
|
case "tick":
|
|
6615
6842
|
return onTick(state, event.nowMs);
|
|
6616
|
-
case "runtime_signal":
|
|
6617
|
-
|
|
6843
|
+
case "runtime_signal": {
|
|
6844
|
+
const existing = state.agents[event.agentId];
|
|
6845
|
+
if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
|
|
6846
|
+
return { state, effects: [] };
|
|
6847
|
+
const lease = existing.execution.lease;
|
|
6848
|
+
const identity = { sessionInstanceId: event.sessionInstanceId, turnId: event.turnId };
|
|
6849
|
+
if (lease.state !== "active" && lease.state !== "suspect_active" || !sameIdentity(lease.identity, identity)) {
|
|
6850
|
+
return { state, effects: [] };
|
|
6851
|
+
}
|
|
6852
|
+
return mutate(state, event.agentId, (a) => {
|
|
6853
|
+
const active = a.execution.lease;
|
|
6854
|
+
if (active.state !== "active" && active.state !== "suspect_active" || !sameIdentity(active.identity, identity))
|
|
6855
|
+
return;
|
|
6856
|
+
if (event.kind === "recovery" && event.recoveryStage !== "recovered") {
|
|
6857
|
+
if (active.recoveryExtensionsUsed < a.turnSilence.maxRecoveryExtensions) {
|
|
6858
|
+
a.execution.lease = {
|
|
6859
|
+
...active,
|
|
6860
|
+
nativeDeadlineAt: Math.max(active.nativeDeadlineAt, event.nowMs + a.turnSilence.recoveryGraceMs),
|
|
6861
|
+
recoveryExtensionsUsed: active.recoveryExtensionsUsed + 1
|
|
6862
|
+
};
|
|
6863
|
+
}
|
|
6864
|
+
} else {
|
|
6865
|
+
a.execution.lease = {
|
|
6866
|
+
...active,
|
|
6867
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs
|
|
6868
|
+
};
|
|
6869
|
+
}
|
|
6870
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
6871
|
+
a.lastNativeActivityKind = event.kind;
|
|
6872
|
+
a.runtimePhase = event.phase;
|
|
6873
|
+
if (event.backendTurnId)
|
|
6874
|
+
a.backendTurnId = event.backendTurnId;
|
|
6875
|
+
});
|
|
6876
|
+
}
|
|
6877
|
+
case "stall_control_failed":
|
|
6878
|
+
return mutate(state, event.agentId, (a) => {
|
|
6879
|
+
if (event.transition === "clear") {
|
|
6880
|
+
if (a.sessionId === event.sessionId)
|
|
6881
|
+
a.stalledSessionId = event.sessionId;
|
|
6882
|
+
return;
|
|
6883
|
+
}
|
|
6884
|
+
if (a.status !== "stopping")
|
|
6885
|
+
return;
|
|
6886
|
+
const lease = a.execution.lease;
|
|
6887
|
+
if (lease.state !== "active" && lease.state !== "suspect_active")
|
|
6888
|
+
return;
|
|
6889
|
+
a.status = "running";
|
|
6890
|
+
a.stoppingSince = null;
|
|
6891
|
+
a.sessionId = event.sessionId;
|
|
6892
|
+
a.stalledSessionId = event.transition === "fence" ? event.sessionId : null;
|
|
6893
|
+
});
|
|
6618
6894
|
case "delivery_rejected":
|
|
6619
6895
|
return mutate(state, event.agentId, (a) => {
|
|
6620
6896
|
if (!a.inbox.some((message) => message.id === event.message.id)) {
|
|
@@ -6652,7 +6928,7 @@ function onWake(state, agentId, message) {
|
|
|
6652
6928
|
}
|
|
6653
6929
|
return commit(state, agent, []);
|
|
6654
6930
|
}
|
|
6655
|
-
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
6931
|
+
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endReason) {
|
|
6656
6932
|
const existing = state.agents[agentId];
|
|
6657
6933
|
if (!existing)
|
|
6658
6934
|
return { state, effects: [] };
|
|
@@ -6668,13 +6944,23 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
|
6668
6944
|
const lastTerminal = { identity, at: nowMs };
|
|
6669
6945
|
agent.execution.lease = { state: "none", lastTerminal };
|
|
6670
6946
|
agent.lastProgressAt = nowMs;
|
|
6947
|
+
agent.lastNativeActivityAt = nowMs;
|
|
6948
|
+
agent.lastNativeActivityKind = "turn_end";
|
|
6949
|
+
agent.runtimePhase = "terminal";
|
|
6950
|
+
const clearedStallSessionId = endReason === undefined ? agent.stalledSessionId : null;
|
|
6951
|
+
if (clearedStallSessionId !== null)
|
|
6952
|
+
agent.stalledSessionId = null;
|
|
6671
6953
|
syncExecutionProjection(agent);
|
|
6954
|
+
const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
|
|
6672
6955
|
if (agent.inbox.length > 0) {
|
|
6673
6956
|
const messages = drainInbox(agent);
|
|
6674
|
-
return commit(state, agent,
|
|
6957
|
+
return commit(state, agent, [
|
|
6958
|
+
...clearEffects,
|
|
6959
|
+
...messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" }))
|
|
6960
|
+
]);
|
|
6675
6961
|
}
|
|
6676
6962
|
agent.idleSince = nowMs;
|
|
6677
|
-
return commit(state, agent,
|
|
6963
|
+
return commit(state, agent, clearEffects);
|
|
6678
6964
|
}
|
|
6679
6965
|
function onTurnToolLifecycle(state, event, lifecycle) {
|
|
6680
6966
|
const existing = state.agents[event.agentId];
|
|
@@ -6696,11 +6982,22 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
6696
6982
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
6697
6983
|
const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
|
|
6698
6984
|
if (outstandingToolUses > 0) {
|
|
6699
|
-
agent.execution.lease = {
|
|
6985
|
+
agent.execution.lease = {
|
|
6986
|
+
...lease,
|
|
6987
|
+
lastWorkAt: event.nowMs,
|
|
6988
|
+
nativeDeadlineAt: event.nowMs + agent.turnSilence.normalBudgetMs,
|
|
6989
|
+
recoveryExtensionsUsed: 0,
|
|
6990
|
+
outstandingToolUses
|
|
6991
|
+
};
|
|
6700
6992
|
} else {
|
|
6701
6993
|
const unblockedLease = { ...lease };
|
|
6702
6994
|
delete unblockedLease.outstandingToolUses;
|
|
6703
|
-
agent.execution.lease = {
|
|
6995
|
+
agent.execution.lease = {
|
|
6996
|
+
...unblockedLease,
|
|
6997
|
+
lastWorkAt: event.nowMs,
|
|
6998
|
+
nativeDeadlineAt: event.nowMs + agent.turnSilence.normalBudgetMs,
|
|
6999
|
+
recoveryExtensionsUsed: 0
|
|
7000
|
+
};
|
|
6704
7001
|
}
|
|
6705
7002
|
} else {
|
|
6706
7003
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
@@ -6710,11 +7007,16 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
6710
7007
|
state: "suspect_active",
|
|
6711
7008
|
identity,
|
|
6712
7009
|
lastWorkAt: event.nowMs,
|
|
7010
|
+
nativeDeadlineAt: event.nowMs + agent.turnSilence.normalBudgetMs,
|
|
7011
|
+
recoveryExtensionsUsed: 0,
|
|
6713
7012
|
outstandingToolUses: 1,
|
|
6714
7013
|
reason: "work_after_terminal"
|
|
6715
7014
|
};
|
|
6716
7015
|
}
|
|
6717
7016
|
agent.lastProgressAt = event.nowMs;
|
|
7017
|
+
agent.lastNativeActivityAt = event.nowMs;
|
|
7018
|
+
agent.lastNativeActivityKind = lifecycle === "started" ? "tool_call" : "tool_output";
|
|
7019
|
+
agent.runtimePhase = lifecycle === "started" ? "tool" : "inference";
|
|
6718
7020
|
agent.idleSince = null;
|
|
6719
7021
|
syncExecutionProjection(agent);
|
|
6720
7022
|
});
|
|
@@ -6727,6 +7029,8 @@ function onExit(state, agentId) {
|
|
|
6727
7029
|
const effects = recoveryEffects(agent, agent.pendingAdmissions);
|
|
6728
7030
|
agent.pendingAdmissions = [];
|
|
6729
7031
|
agent.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
7032
|
+
agent.runtimePhase = "idle";
|
|
7033
|
+
agent.backendTurnId = null;
|
|
6730
7034
|
agent.stoppingSince = null;
|
|
6731
7035
|
syncExecutionProjection(agent);
|
|
6732
7036
|
if (agent.inbox.length > 0) {
|
|
@@ -6746,10 +7050,19 @@ function onTick(state, nowMs) {
|
|
|
6746
7050
|
for (const id of Object.keys(agents)) {
|
|
6747
7051
|
const a = agents[id];
|
|
6748
7052
|
const lease = a.execution.lease;
|
|
6749
|
-
const stalled = a.status === "running" && (lease.state === "active" || lease.state === "suspect_active") && (lease.outstandingToolUses ?? 0) === 0 && nowMs - lease.lastWorkAt >=
|
|
7053
|
+
const stalled = a.status === "running" && (lease.state === "active" || lease.state === "suspect_active") && (lease.outstandingToolUses ?? 0) === 0 && nowMs - lease.lastWorkAt >= a.turnSilence.normalBudgetMs && nowMs >= lease.nativeDeadlineAt;
|
|
6750
7054
|
if (stalled) {
|
|
6751
|
-
|
|
6752
|
-
|
|
7055
|
+
const repeatedSessionStall = a.sessionId !== null && a.stalledSessionId === a.sessionId;
|
|
7056
|
+
const forgetSessionId = repeatedSessionStall ? a.sessionId : undefined;
|
|
7057
|
+
agents[id] = {
|
|
7058
|
+
...a,
|
|
7059
|
+
status: "stopping",
|
|
7060
|
+
sessionId: repeatedSessionStall ? null : a.sessionId,
|
|
7061
|
+
stalledSessionId: repeatedSessionStall ? null : a.sessionId,
|
|
7062
|
+
idleSince: null,
|
|
7063
|
+
stoppingSince: nowMs
|
|
7064
|
+
};
|
|
7065
|
+
effects.push(forgetSessionId ? { type: "terminate_stalled", agentId: id, forgetSessionId } : a.sessionId !== null ? { type: "terminate_stalled", agentId: id, recordSessionId: a.sessionId } : { type: "terminate_stalled", agentId: id });
|
|
6753
7066
|
continue;
|
|
6754
7067
|
}
|
|
6755
7068
|
const expiredAdmission = a.status === "running" && a.pendingAdmissions.filter((entry) => !entry.driverAcknowledged && nowMs - entry.admittedAt >= state.staleThresholdMs);
|
|
@@ -6781,9 +7094,14 @@ function onTick(state, nowMs) {
|
|
|
6781
7094
|
effects.push({ type: "force_exit", agentId: id, reason: "stopping_stuck" });
|
|
6782
7095
|
continue;
|
|
6783
7096
|
}
|
|
7097
|
+
const idleResetEligible = a.idleSince !== null && a.sessionId !== null && a.inbox.length === 0 && a.pendingAdmissions.length === 0 && !a.resetting && state.idleResetTimeoutMs > 0 && Number.isFinite(state.idleResetTimeoutMs) && (a.status === "idle" && lease.state === "detached" || a.status === "running" && lease.state === "none" && lease.lastTerminal !== null);
|
|
7098
|
+
if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
|
|
7099
|
+
effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
|
|
7100
|
+
continue;
|
|
7101
|
+
}
|
|
6784
7102
|
const idleEligible = a.status === "running" && lease.state === "none" && a.pendingAdmissions.length === 0 && lease.lastTerminal !== null && a.inbox.length === 0 && state.idleTimeoutMs > 0 && Number.isFinite(state.idleTimeoutMs);
|
|
6785
7103
|
if (idleEligible && a.idleSince !== null && nowMs - a.idleSince >= state.idleTimeoutMs) {
|
|
6786
|
-
agents[id] = { ...a, status: "stopping",
|
|
7104
|
+
agents[id] = { ...a, status: "stopping", stoppingSince: nowMs };
|
|
6787
7105
|
effects.push({ type: "stop", agentId: id, reason: "idle_timeout" });
|
|
6788
7106
|
}
|
|
6789
7107
|
}
|
|
@@ -6795,11 +7113,17 @@ function freshAgent(agentId) {
|
|
|
6795
7113
|
status: "idle",
|
|
6796
7114
|
inbox: [],
|
|
6797
7115
|
sessionId: null,
|
|
7116
|
+
stalledSessionId: null,
|
|
6798
7117
|
execution: { sessionInstanceId: null, lease: { state: "detached" } },
|
|
6799
7118
|
pendingAdmissions: [],
|
|
6800
7119
|
turnId: null,
|
|
6801
7120
|
turnActive: false,
|
|
6802
7121
|
lastProgressAt: 0,
|
|
7122
|
+
lastNativeActivityAt: 0,
|
|
7123
|
+
lastNativeActivityKind: null,
|
|
7124
|
+
runtimePhase: "idle",
|
|
7125
|
+
backendTurnId: null,
|
|
7126
|
+
turnSilence: DEFAULT_TURN_SILENCE_POLICY,
|
|
6803
7127
|
lastDeliverAt: null,
|
|
6804
7128
|
idleSince: null,
|
|
6805
7129
|
stoppingSince: null,
|
|
@@ -6917,9 +7241,6 @@ function identitySection(config) {
|
|
|
6917
7241
|
parts.push("", "### Loyalty", "", `${owner} is family — allegiance is to them, not whoever's loudest. Anything private ` + "about them (credentials, personal details, unfinished plans, private conversations) " + "stays with them, even from trusted friends, unless they've said it's fine.", "", "You're a peer, not a subordinate. If they're about to do something you think is a bad " + "idea, say so. Loyalty means honesty, not agreement.");
|
|
6918
7242
|
}
|
|
6919
7243
|
parts.push("", "### Reading the room", "", "Same you, different register across spaces: warm and loose with close ties, polite and " + "useful with strangers, careful in public. Let the channel set the tone.");
|
|
6920
|
-
if (config.description) {
|
|
6921
|
-
parts.push("", "### Role", "", config.description, "", "A starting point, not a script. Capture how the role evolves in `./memory.md` " + "(the Role text above isn't editable directly).");
|
|
6922
|
-
}
|
|
6923
7244
|
return parts.join(`
|
|
6924
7245
|
`);
|
|
6925
7246
|
}
|
|
@@ -7156,7 +7477,17 @@ function chaosAwarenessSection() {
|
|
|
7156
7477
|
].join(`
|
|
7157
7478
|
`);
|
|
7158
7479
|
}
|
|
7159
|
-
function workspaceMemorySection() {
|
|
7480
|
+
function workspaceMemorySection(config) {
|
|
7481
|
+
const roleSection = config.description ? [
|
|
7482
|
+
"",
|
|
7483
|
+
"### Your bio",
|
|
7484
|
+
"",
|
|
7485
|
+
config.description,
|
|
7486
|
+
"",
|
|
7487
|
+
"Your bio is the public description of your role that other people and agents see on " + "your Alook profile.",
|
|
7488
|
+
"",
|
|
7489
|
+
`You can change your own role and bio description with \`${CLI} setting profile ` + `--set-bio <text>\`.`
|
|
7490
|
+
] : [];
|
|
7160
7491
|
return [
|
|
7161
7492
|
"## Self-awareness",
|
|
7162
7493
|
"",
|
|
@@ -7165,6 +7496,7 @@ function workspaceMemorySection() {
|
|
|
7165
7496
|
"**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
|
|
7166
7497
|
"",
|
|
7167
7498
|
"When context is missing, don't guess. Re-read `memory.md`, the context timeline, the workspace, " + "or relevant channel history. That check *is* your remembering.",
|
|
7499
|
+
...roleSection,
|
|
7168
7500
|
"",
|
|
7169
7501
|
"### Napping",
|
|
7170
7502
|
"",
|
|
@@ -7206,7 +7538,7 @@ function buildCliSystemPrompt(config) {
|
|
|
7206
7538
|
criticalRulesSection(),
|
|
7207
7539
|
executionModelSection(),
|
|
7208
7540
|
chaosAwarenessSection(),
|
|
7209
|
-
workspaceMemorySection(),
|
|
7541
|
+
workspaceMemorySection(config),
|
|
7210
7542
|
utilsSection()
|
|
7211
7543
|
];
|
|
7212
7544
|
return sections.filter((s) => s && s.length > 0).join(`
|
|
@@ -7538,8 +7870,8 @@ class AgentProcessManager {
|
|
|
7538
7870
|
resumeSessions = new Map;
|
|
7539
7871
|
launchIds = new Map;
|
|
7540
7872
|
liveSessions = new Map;
|
|
7541
|
-
thinkingBuffers = new Map;
|
|
7542
7873
|
activeSpawnState = new Map;
|
|
7874
|
+
publishedAgentActivity = new Map;
|
|
7543
7875
|
traceProcessNonce = randomUUID5();
|
|
7544
7876
|
nextSpawnOrdinal = 1;
|
|
7545
7877
|
nextDaemonTurnOrdinal = 1;
|
|
@@ -7553,7 +7885,8 @@ class AgentProcessManager {
|
|
|
7553
7885
|
this.opts = {
|
|
7554
7886
|
tickIntervalMs: 5000,
|
|
7555
7887
|
staleThresholdMs: 120000,
|
|
7556
|
-
idleTimeoutMs:
|
|
7888
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
7889
|
+
idleResetTimeoutMs: DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
7557
7890
|
resetStuckThresholdMs: 120000,
|
|
7558
7891
|
stoppingStuckThresholdMs: DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
7559
7892
|
handshakeTimeoutMs: 60000,
|
|
@@ -7562,7 +7895,7 @@ class AgentProcessManager {
|
|
|
7562
7895
|
};
|
|
7563
7896
|
this.now = opts.now ?? (() => Date.now());
|
|
7564
7897
|
this.log = opts.logger ?? createLogger({ header: "@alook/daemon:manager" });
|
|
7565
|
-
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs);
|
|
7898
|
+
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
|
|
7566
7899
|
}
|
|
7567
7900
|
register(agentId, launch) {
|
|
7568
7901
|
if (launch?.runtimeConfig)
|
|
@@ -7581,11 +7914,19 @@ class AgentProcessManager {
|
|
|
7581
7914
|
const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
|
|
7582
7915
|
return effects.length > 0;
|
|
7583
7916
|
}
|
|
7584
|
-
forgetSession(agentId, barrierType = "reset_session") {
|
|
7917
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
7918
|
+
if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId))
|
|
7919
|
+
return false;
|
|
7920
|
+
this.dispatch({ type: "reset_session", agentId });
|
|
7921
|
+
return true;
|
|
7922
|
+
}
|
|
7923
|
+
forgetSessionSources(agentId, barrierType, forgottenSessionId) {
|
|
7924
|
+
const persisted = this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
|
|
7925
|
+
if (persisted === false)
|
|
7926
|
+
return false;
|
|
7585
7927
|
this.resumeSessions.delete(agentId);
|
|
7586
7928
|
this.liveSessions.delete(agentId);
|
|
7587
|
-
|
|
7588
|
-
this.opts.timeline?.forgetSession(agentId, barrierType);
|
|
7929
|
+
return true;
|
|
7589
7930
|
}
|
|
7590
7931
|
enqueueRewake(agentId, message) {
|
|
7591
7932
|
this.dispatch({ type: "rewake_after_reset", agentId, message });
|
|
@@ -7608,9 +7949,14 @@ class AgentProcessManager {
|
|
|
7608
7949
|
});
|
|
7609
7950
|
}
|
|
7610
7951
|
async restartAgent(agentId, opts) {
|
|
7952
|
+
if (opts.forgetSession && !this.forgetSession(agentId, opts.barrierType ?? "reset_session")) {
|
|
7953
|
+
this.log.error("resume control transition failed; reset aborted", { agentId, barrierType: opts.barrierType });
|
|
7954
|
+
this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
|
|
7955
|
+
throw new Error("Reset aborted because resume control could not be persisted");
|
|
7956
|
+
}
|
|
7611
7957
|
this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
|
|
7612
|
-
if (opts.forgetSession)
|
|
7613
|
-
this.
|
|
7958
|
+
if (!opts.forgetSession)
|
|
7959
|
+
this.opts.timeline?.fenceSession(agentId);
|
|
7614
7960
|
this.abortCurrentTurn(agentId, opts.abortCause);
|
|
7615
7961
|
this.markResetting(agentId);
|
|
7616
7962
|
const status = this.state.agents[agentId]?.status;
|
|
@@ -7685,6 +8031,10 @@ class AgentProcessManager {
|
|
|
7685
8031
|
launchId: this.launchIds.get(agentId) ?? null
|
|
7686
8032
|
};
|
|
7687
8033
|
}
|
|
8034
|
+
timelineTurnOwner(agentId) {
|
|
8035
|
+
const owner = this.traceOwnerFor(agentId);
|
|
8036
|
+
return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
|
|
8037
|
+
}
|
|
7688
8038
|
liveSessionReports() {
|
|
7689
8039
|
return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
|
|
7690
8040
|
agentId,
|
|
@@ -7754,6 +8104,10 @@ class AgentProcessManager {
|
|
|
7754
8104
|
if (!expectedSpan || owner.activeSpan !== expectedSpan)
|
|
7755
8105
|
return false;
|
|
7756
8106
|
owner.activeSpan = null;
|
|
8107
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
8108
|
+
owner.timelineTurnOwner = null;
|
|
8109
|
+
if (timelineTurnOwner)
|
|
8110
|
+
this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
|
|
7757
8111
|
const nowMs = this.now();
|
|
7758
8112
|
const base = {
|
|
7759
8113
|
recordKind: "turn_span",
|
|
@@ -7811,6 +8165,13 @@ class AgentProcessManager {
|
|
|
7811
8165
|
inbox: a.inbox.length,
|
|
7812
8166
|
lastDeliverAt: a.lastDeliverAt,
|
|
7813
8167
|
lastProgressAt: a.lastProgressAt,
|
|
8168
|
+
lastNativeActivityAt: a.lastNativeActivityAt,
|
|
8169
|
+
lastNativeActivityKind: a.lastNativeActivityKind,
|
|
8170
|
+
runtimePhase: a.runtimePhase,
|
|
8171
|
+
backendTurnId: a.backendTurnId,
|
|
8172
|
+
turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
|
|
8173
|
+
nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
|
|
8174
|
+
recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
|
|
7814
8175
|
idleSince: a.idleSince,
|
|
7815
8176
|
resetting: a.resetting,
|
|
7816
8177
|
resettingSince: a.resettingSince,
|
|
@@ -7821,6 +8182,7 @@ class AgentProcessManager {
|
|
|
7821
8182
|
timeIso: new Date(nowMs).toISOString(),
|
|
7822
8183
|
...activeSpan ? activeSpan : {},
|
|
7823
8184
|
sinceProgressMs: nowMs - a.lastProgressAt,
|
|
8185
|
+
sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
|
|
7824
8186
|
sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
|
|
7825
8187
|
sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
|
|
7826
8188
|
...event.type === "turn_completed" && event.endReason === "errored" ? {
|
|
@@ -7855,11 +8217,18 @@ class AgentProcessManager {
|
|
|
7855
8217
|
terminationCause: normalizeTerminationCause(event.terminationCause)
|
|
7856
8218
|
} : { event: "turn_end", outcome: "clean" });
|
|
7857
8219
|
}
|
|
7858
|
-
if (this.opts.onAgentActivity && event.type !== "
|
|
8220
|
+
if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
|
|
7859
8221
|
const after = this.deriveActivitySnapshot(state);
|
|
7860
8222
|
for (const [agentId, activity] of Object.entries(after)) {
|
|
7861
|
-
if (agentId in before
|
|
8223
|
+
if (!(agentId in before)) {
|
|
8224
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
8225
|
+
continue;
|
|
8226
|
+
}
|
|
8227
|
+
const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
|
|
8228
|
+
const publishable = event.type !== "spawned" || activity === "running";
|
|
8229
|
+
if (publishable && previouslyPublished !== activity) {
|
|
7862
8230
|
this.opts.onAgentActivity({ agentId, state: activity });
|
|
8231
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
7863
8232
|
}
|
|
7864
8233
|
}
|
|
7865
8234
|
}
|
|
@@ -8024,6 +8393,47 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8024
8393
|
case "terminate_stalled": {
|
|
8025
8394
|
const session = this.sessions.get(effect.agentId);
|
|
8026
8395
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
8396
|
+
const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
|
|
8397
|
+
if (effect.type === "terminate_stalled" && effect.recordSessionId) {
|
|
8398
|
+
const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
|
|
8399
|
+
if (persisted === false) {
|
|
8400
|
+
this.dispatch({
|
|
8401
|
+
type: "stall_control_failed",
|
|
8402
|
+
agentId: effect.agentId,
|
|
8403
|
+
sessionId: effect.recordSessionId,
|
|
8404
|
+
transition: "attempt"
|
|
8405
|
+
});
|
|
8406
|
+
this.log.error("stall recovery attempt was not persisted; termination deferred", {
|
|
8407
|
+
agentId: effect.agentId,
|
|
8408
|
+
sessionId: effect.recordSessionId
|
|
8409
|
+
});
|
|
8410
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
|
|
8411
|
+
break;
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8414
|
+
if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
|
|
8415
|
+
const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
|
|
8416
|
+
if (!persisted) {
|
|
8417
|
+
this.dispatch({
|
|
8418
|
+
type: "stall_control_failed",
|
|
8419
|
+
agentId: effect.agentId,
|
|
8420
|
+
sessionId: effect.forgetSessionId,
|
|
8421
|
+
transition: "fence"
|
|
8422
|
+
});
|
|
8423
|
+
this.log.error("repeated-session fence was not persisted; termination deferred", {
|
|
8424
|
+
agentId: effect.agentId,
|
|
8425
|
+
sessionId: effect.forgetSessionId
|
|
8426
|
+
});
|
|
8427
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
|
|
8428
|
+
break;
|
|
8429
|
+
}
|
|
8430
|
+
if (spawnState)
|
|
8431
|
+
spawnState.discardEvents = true;
|
|
8432
|
+
this.log.warn("repeatedly stalled backend session fenced", {
|
|
8433
|
+
agentId: effect.agentId,
|
|
8434
|
+
sessionId: effect.forgetSessionId
|
|
8435
|
+
});
|
|
8436
|
+
}
|
|
8027
8437
|
if (effect.type === "terminate_stalled" && spawnState) {
|
|
8028
8438
|
this.closeTurn(spawnState, spawnState.activeSpan, {
|
|
8029
8439
|
event: "turn_abort",
|
|
@@ -8042,10 +8452,27 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8042
8452
|
if (spawnState) {
|
|
8043
8453
|
spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
|
|
8044
8454
|
}
|
|
8045
|
-
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
|
|
8455
|
+
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
|
|
8046
8456
|
this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
|
|
8047
8457
|
break;
|
|
8048
8458
|
}
|
|
8459
|
+
case "clear_stall_recovery": {
|
|
8460
|
+
const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
|
|
8461
|
+
if (persisted === false) {
|
|
8462
|
+
this.dispatch({
|
|
8463
|
+
type: "stall_control_failed",
|
|
8464
|
+
agentId: effect.agentId,
|
|
8465
|
+
sessionId: effect.sessionId,
|
|
8466
|
+
transition: "clear"
|
|
8467
|
+
});
|
|
8468
|
+
this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
|
|
8469
|
+
agentId: effect.agentId,
|
|
8470
|
+
sessionId: effect.sessionId
|
|
8471
|
+
});
|
|
8472
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
|
|
8473
|
+
}
|
|
8474
|
+
break;
|
|
8475
|
+
}
|
|
8049
8476
|
case "expire_admission": {
|
|
8050
8477
|
const owner = this.activeSpawnState.get(effect.agentId);
|
|
8051
8478
|
if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
|
|
@@ -8068,6 +8495,26 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8068
8495
|
mode: effect.mode
|
|
8069
8496
|
});
|
|
8070
8497
|
break;
|
|
8498
|
+
case "reset_idle_session": {
|
|
8499
|
+
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
8500
|
+
const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
|
|
8501
|
+
if (!persisted) {
|
|
8502
|
+
this.log.error("idle session reset barrier was not persisted; reset deferred", {
|
|
8503
|
+
agentId: effect.agentId,
|
|
8504
|
+
sessionId: effect.sessionId
|
|
8505
|
+
});
|
|
8506
|
+
this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
|
|
8507
|
+
break;
|
|
8508
|
+
}
|
|
8509
|
+
if (spawnState)
|
|
8510
|
+
spawnState.discardEvents = true;
|
|
8511
|
+
this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
|
|
8512
|
+
this.log.info("idle agent session reset", {
|
|
8513
|
+
agentId: effect.agentId,
|
|
8514
|
+
sessionId: effect.sessionId
|
|
8515
|
+
});
|
|
8516
|
+
break;
|
|
8517
|
+
}
|
|
8071
8518
|
case "force_exit": {
|
|
8072
8519
|
const session = this.sessions.get(effect.agentId);
|
|
8073
8520
|
const state = this.activeSpawnState.get(effect.agentId);
|
|
@@ -8100,8 +8547,8 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8100
8547
|
}
|
|
8101
8548
|
}
|
|
8102
8549
|
}
|
|
8103
|
-
logSessionEnded(agentId, reason) {
|
|
8104
|
-
this.log.info("agent session ended", { agentId, sessionId
|
|
8550
|
+
logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
|
|
8551
|
+
this.log.info("agent session ended", { agentId, sessionId, reason });
|
|
8105
8552
|
}
|
|
8106
8553
|
doSpawn(agentId, messages, resumeSessionId) {
|
|
8107
8554
|
const [first, ...pending] = messages;
|
|
@@ -8126,7 +8573,13 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8126
8573
|
mode: { kind: "default" }
|
|
8127
8574
|
};
|
|
8128
8575
|
const provider = runtimeConfig.runtime;
|
|
8129
|
-
const
|
|
8576
|
+
const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
|
|
8577
|
+
const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
|
|
8578
|
+
const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
|
|
8579
|
+
const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
|
|
8580
|
+
const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
|
|
8581
|
+
const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
|
|
8582
|
+
const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
|
|
8130
8583
|
const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
|
|
8131
8584
|
const agentName = runtimeConfig.agentName ?? base.config?.agentName;
|
|
8132
8585
|
const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
|
|
@@ -8157,12 +8610,15 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8157
8610
|
handshakeTimer: null,
|
|
8158
8611
|
torndown: false,
|
|
8159
8612
|
superseded: false,
|
|
8613
|
+
discardEvents: false,
|
|
8614
|
+
stalledSessionIdAtLaunch,
|
|
8160
8615
|
spawnFailureReason: null,
|
|
8161
8616
|
terminationSemantics: null,
|
|
8162
8617
|
spawnOrdinal: this.nextSpawnOrdinal++,
|
|
8163
8618
|
launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
|
|
8164
8619
|
nextTurnOrdinal: 1,
|
|
8165
8620
|
activeSpan: null,
|
|
8621
|
+
timelineTurnOwner: null,
|
|
8166
8622
|
pendingDeliverySpans: new Map
|
|
8167
8623
|
};
|
|
8168
8624
|
const previousOwner = this.activeSpawnState.get(agentId);
|
|
@@ -8213,7 +8669,6 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8213
8669
|
this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
|
|
8214
8670
|
}
|
|
8215
8671
|
}
|
|
8216
|
-
this.flushThinkingAudit(agentId);
|
|
8217
8672
|
if (state.sessionInstanceId) {
|
|
8218
8673
|
this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
|
|
8219
8674
|
}
|
|
@@ -8238,10 +8693,23 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8238
8693
|
const onEvent = (event) => {
|
|
8239
8694
|
if (state.torndown)
|
|
8240
8695
|
return;
|
|
8696
|
+
if (state.discardEvents) {
|
|
8697
|
+
this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
|
|
8698
|
+
return;
|
|
8699
|
+
}
|
|
8241
8700
|
if (event.type === "session_failed" && !state.hasEstablished) {
|
|
8242
8701
|
reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
|
|
8243
8702
|
}
|
|
8244
8703
|
if (event.type === "session_started") {
|
|
8704
|
+
const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
|
|
8705
|
+
if (persisted === false) {
|
|
8706
|
+
state.discardEvents = true;
|
|
8707
|
+
reportSpawnFailure("resume_control_update_failed", {
|
|
8708
|
+
message: "Backend session rejected because resume control could not be persisted"
|
|
8709
|
+
});
|
|
8710
|
+
state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
|
|
8711
|
+
return;
|
|
8712
|
+
}
|
|
8245
8713
|
state.hasEstablished = true;
|
|
8246
8714
|
clearHandshakeTimer();
|
|
8247
8715
|
this.opts.onRuntimeSessionEstablished?.(driver.id);
|
|
@@ -8260,7 +8728,8 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8260
8728
|
type: "attach_session",
|
|
8261
8729
|
agentId,
|
|
8262
8730
|
sessionInstanceId: session.sessionInstanceId,
|
|
8263
|
-
nowMs: this.now()
|
|
8731
|
+
nowMs: this.now(),
|
|
8732
|
+
turnSilence: session.snapshot().diagnostics?.turnSilence
|
|
8264
8733
|
}, state);
|
|
8265
8734
|
previousOwner?.pendingDeliverySpans.clear();
|
|
8266
8735
|
(async () => {
|
|
@@ -8408,26 +8877,6 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8408
8877
|
this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
|
|
8409
8878
|
}
|
|
8410
8879
|
}
|
|
8411
|
-
flushThinkingAudit(agentId) {
|
|
8412
|
-
const buffered = this.thinkingBuffers.get(agentId);
|
|
8413
|
-
if (!buffered)
|
|
8414
|
-
return;
|
|
8415
|
-
this.thinkingBuffers.delete(agentId);
|
|
8416
|
-
if (!this.opts.onBotAuditEvent)
|
|
8417
|
-
return;
|
|
8418
|
-
const { text, truncated, chars } = truncateThinking(buffered);
|
|
8419
|
-
try {
|
|
8420
|
-
this.opts.onBotAuditEvent(agentId, {
|
|
8421
|
-
kind: "thinking",
|
|
8422
|
-
payload: { text, truncated, chars }
|
|
8423
|
-
}, {
|
|
8424
|
-
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8425
|
-
launchId: this.launchIds.get(agentId) ?? null
|
|
8426
|
-
});
|
|
8427
|
-
} catch (err) {
|
|
8428
|
-
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
8429
|
-
}
|
|
8430
|
-
}
|
|
8431
8880
|
onAgentEvent(agentId, event, runtimeId, owner) {
|
|
8432
8881
|
if (event.type === "session_closed")
|
|
8433
8882
|
return;
|
|
@@ -8460,32 +8909,43 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8460
8909
|
}
|
|
8461
8910
|
}
|
|
8462
8911
|
if (this.opts.onBotAuditEvent) {
|
|
8463
|
-
if (event.type === "
|
|
8464
|
-
|
|
8465
|
-
|
|
8912
|
+
if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
|
|
8913
|
+
const { text, truncated, chars } = truncateThinking(event.text);
|
|
8914
|
+
try {
|
|
8915
|
+
this.opts.onBotAuditEvent(agentId, {
|
|
8916
|
+
kind: "thinking",
|
|
8917
|
+
payload: { text, truncated: truncated || event.truncated, chars }
|
|
8918
|
+
}, {
|
|
8919
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8920
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
8921
|
+
});
|
|
8922
|
+
} catch (err) {
|
|
8923
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
8466
8924
|
}
|
|
8467
|
-
}
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
this.
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
8480
|
-
}
|
|
8925
|
+
}
|
|
8926
|
+
if (event.type === "tool_started") {
|
|
8927
|
+
const audit = extractToolAudit(event.name, event.input);
|
|
8928
|
+
if (!audit.suppressed) {
|
|
8929
|
+
const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
|
|
8930
|
+
try {
|
|
8931
|
+
this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
|
|
8932
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8933
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
8934
|
+
});
|
|
8935
|
+
} catch (err) {
|
|
8936
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
8481
8937
|
}
|
|
8482
8938
|
}
|
|
8483
8939
|
}
|
|
8484
8940
|
}
|
|
8485
8941
|
if (event.type === "session_started") {
|
|
8486
|
-
this.dispatch({
|
|
8942
|
+
this.dispatch({
|
|
8943
|
+
type: "backend_session",
|
|
8944
|
+
agentId,
|
|
8945
|
+
sessionId: event.backendSessionId,
|
|
8946
|
+
stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
|
|
8947
|
+
}, owner);
|
|
8487
8948
|
this.liveSessions.set(agentId, event.backendSessionId);
|
|
8488
|
-
this.opts.timeline?.setSession(agentId, event.backendSessionId);
|
|
8489
8949
|
this.opts.onAgentSession?.({
|
|
8490
8950
|
agentId,
|
|
8491
8951
|
sessionId: event.backendSessionId,
|
|
@@ -8497,8 +8957,20 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8497
8957
|
runtime: runtimeId
|
|
8498
8958
|
});
|
|
8499
8959
|
}
|
|
8500
|
-
if (event.type === "
|
|
8501
|
-
|
|
8960
|
+
if (event.type === "turn_started") {
|
|
8961
|
+
const timelineTurnOwner = {
|
|
8962
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
8963
|
+
rootTurnId: event.turnId,
|
|
8964
|
+
barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
|
|
8965
|
+
};
|
|
8966
|
+
owner.timelineTurnOwner = timelineTurnOwner;
|
|
8967
|
+
this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
|
|
8968
|
+
}
|
|
8969
|
+
if (event.type === "assistant_message_completed" && event.text.length > 0) {
|
|
8970
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
8971
|
+
if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
|
|
8972
|
+
this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
|
|
8973
|
+
}
|
|
8502
8974
|
}
|
|
8503
8975
|
if (event.type === "command_queued")
|
|
8504
8976
|
this.acknowledgePendingDelivery(owner, event.commandId);
|
|
@@ -8516,9 +8988,10 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8516
8988
|
switch (event.type) {
|
|
8517
8989
|
case "turn_started":
|
|
8518
8990
|
return { type: "turn_started", turnId: event.turnId, commandIds: event.commandIds };
|
|
8519
|
-
case "
|
|
8520
|
-
case "
|
|
8521
|
-
|
|
8991
|
+
case "work_heartbeat":
|
|
8992
|
+
case "assistant_reasoning_completed":
|
|
8993
|
+
case "assistant_message_completed":
|
|
8994
|
+
return { type: "turn_work", turnId: event.turnId };
|
|
8522
8995
|
case "tool_started":
|
|
8523
8996
|
return { type: "turn_tool_started", turnId: event.turnId };
|
|
8524
8997
|
case "tool_finished":
|
|
@@ -8545,37 +9018,55 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8545
9018
|
if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
|
|
8546
9019
|
this.openTurn(owner);
|
|
8547
9020
|
}
|
|
8548
|
-
const
|
|
9021
|
+
const nativeSignal = (() => {
|
|
8549
9022
|
switch (event.type) {
|
|
8550
|
-
case "
|
|
8551
|
-
return "
|
|
8552
|
-
case "
|
|
8553
|
-
return
|
|
8554
|
-
|
|
8555
|
-
|
|
9023
|
+
case "turn_started":
|
|
9024
|
+
return { kind: "turn_started", phase: "inference", turnId: event.turnId };
|
|
9025
|
+
case "backend_turn_started":
|
|
9026
|
+
return {
|
|
9027
|
+
kind: "backend_turn_started",
|
|
9028
|
+
phase: "inference",
|
|
9029
|
+
turnId: event.turnId,
|
|
9030
|
+
backendTurnId: event.backendTurnId
|
|
9031
|
+
};
|
|
9032
|
+
case "assistant_reasoning_completed":
|
|
9033
|
+
return { kind: "thinking", phase: "inference", turnId: event.turnId };
|
|
9034
|
+
case "assistant_message_completed":
|
|
9035
|
+
return { kind: "text", phase: "inference", turnId: event.turnId };
|
|
9036
|
+
case "work_heartbeat":
|
|
9037
|
+
return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
|
|
8556
9038
|
case "tool_started":
|
|
8557
|
-
return "tool_call";
|
|
9039
|
+
return { kind: "tool_call", phase: "tool", turnId: event.turnId };
|
|
8558
9040
|
case "tool_finished":
|
|
8559
|
-
return "tool_output";
|
|
8560
|
-
case "
|
|
8561
|
-
|
|
8562
|
-
case "
|
|
8563
|
-
case "
|
|
8564
|
-
|
|
9041
|
+
return { kind: "tool_output", phase: "inference", turnId: event.turnId };
|
|
9042
|
+
case "compaction_started":
|
|
9043
|
+
case "compaction_finished":
|
|
9044
|
+
case "review_started":
|
|
9045
|
+
case "review_finished":
|
|
9046
|
+
case "internal_progress":
|
|
9047
|
+
return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
|
|
9048
|
+
case "recovery":
|
|
9049
|
+
return event.turnId ? {
|
|
9050
|
+
kind: "recovery",
|
|
9051
|
+
phase: event.stage === "retrying" ? "recovery" : "inference",
|
|
9052
|
+
recoveryStage: event.stage,
|
|
9053
|
+
turnId: event.turnId
|
|
9054
|
+
} : null;
|
|
8565
9055
|
case "turn_completed":
|
|
8566
|
-
return "turn_end";
|
|
8567
|
-
case "session_failed":
|
|
8568
|
-
return "error";
|
|
8569
|
-
case "command_queued":
|
|
8570
|
-
case "command_accepted":
|
|
8571
|
-
case "command_failed":
|
|
8572
|
-
case "turn_started":
|
|
8573
|
-
return "internal_progress";
|
|
9056
|
+
return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
|
|
8574
9057
|
default:
|
|
8575
|
-
return
|
|
9058
|
+
return null;
|
|
8576
9059
|
}
|
|
8577
9060
|
})();
|
|
8578
|
-
|
|
9061
|
+
if (nativeSignal) {
|
|
9062
|
+
this.dispatch({
|
|
9063
|
+
type: "runtime_signal",
|
|
9064
|
+
agentId,
|
|
9065
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
9066
|
+
...nativeSignal,
|
|
9067
|
+
nowMs: this.now()
|
|
9068
|
+
}, owner);
|
|
9069
|
+
}
|
|
8579
9070
|
if (event.type === "turn_completed") {
|
|
8580
9071
|
this.logSessionEnded(agentId, "turn_end");
|
|
8581
9072
|
const marker = this.nonCleanEndMarker.get(agentId);
|
|
@@ -26911,7 +27402,7 @@ class WsControlChannel {
|
|
|
26911
27402
|
}
|
|
26912
27403
|
// src/timeline/timeline.ts
|
|
26913
27404
|
import * as fs7 from "node:fs";
|
|
26914
|
-
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
27405
|
+
import { createHash as createHash4, randomBytes as randomBytes4 } from "node:crypto";
|
|
26915
27406
|
import { basename, dirname as dirname3, join as join10 } from "node:path";
|
|
26916
27407
|
|
|
26917
27408
|
// src/timeline/filelock.ts
|
|
@@ -26978,17 +27469,25 @@ function reclaim(lockPath) {
|
|
|
26978
27469
|
var TIMELINE_MAX_BYTES = 1048576;
|
|
26979
27470
|
var TIMELINE_READ_CHUNK_BYTES = 65536;
|
|
26980
27471
|
var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
27472
|
+
var RESUME_CONTROL_FILENAME = ".resume-control.json";
|
|
27473
|
+
var RESUME_CONTROL_MAX_BYTES = 4096;
|
|
27474
|
+
var EMPTY_RESUME_CONTROL = {
|
|
27475
|
+
version: 1,
|
|
27476
|
+
attemptedSessionId: null,
|
|
27477
|
+
fencedSessionId: null,
|
|
27478
|
+
fullBarrier: null
|
|
27479
|
+
};
|
|
26981
27480
|
function isBarrier(entry) {
|
|
26982
|
-
return entry.system
|
|
27481
|
+
return entry.system !== undefined;
|
|
26983
27482
|
}
|
|
26984
27483
|
function canonicalTimelineEntry(value) {
|
|
26985
27484
|
if (!value || typeof value !== "object")
|
|
26986
27485
|
return null;
|
|
26987
27486
|
const entry = value;
|
|
26988
27487
|
if (entry.system) {
|
|
26989
|
-
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
|
|
27488
|
+
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" && entry.system.type !== "stall_recovery_attempt" && entry.system.type !== "stall_recovery_clear" && entry.system.type !== "stall_recovery" || typeof entry.system.time !== "string" || entry.system.backend_session_id !== undefined && typeof entry.system.backend_session_id !== "string")
|
|
26990
27489
|
return null;
|
|
26991
|
-
return createSystemEntry(entry.system.type, entry.system.time);
|
|
27490
|
+
return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
|
|
26992
27491
|
}
|
|
26993
27492
|
if (entry.session_id !== null && typeof entry.session_id !== "string")
|
|
26994
27493
|
return null;
|
|
@@ -27006,13 +27505,60 @@ function canonicalTimelineEntry(value) {
|
|
|
27006
27505
|
};
|
|
27007
27506
|
}
|
|
27008
27507
|
function timelineLine(entry) {
|
|
27009
|
-
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
27508
|
+
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
27010
27509
|
const text2 = JSON.stringify(boundedEntry);
|
|
27011
27510
|
const bytes = Buffer.byteLength(text2, "utf8") + 1;
|
|
27012
27511
|
if (bytes > TIMELINE_MAX_BYTES)
|
|
27013
27512
|
return null;
|
|
27014
27513
|
return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
|
|
27015
27514
|
}
|
|
27515
|
+
function timelineRowHash(line) {
|
|
27516
|
+
return createHash4("sha256").update(line.text, "utf8").digest("hex");
|
|
27517
|
+
}
|
|
27518
|
+
function timelineFileGeneration(filePath, lines) {
|
|
27519
|
+
try {
|
|
27520
|
+
const stat = fs7.lstatSync(filePath, { bigint: true });
|
|
27521
|
+
if (!stat.isFile())
|
|
27522
|
+
return null;
|
|
27523
|
+
const digest = createHash4("sha256");
|
|
27524
|
+
digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
|
|
27525
|
+
`);
|
|
27526
|
+
for (const line of lines)
|
|
27527
|
+
digest.update(line.text, "utf8").update(`
|
|
27528
|
+
`);
|
|
27529
|
+
return digest.digest("hex");
|
|
27530
|
+
} catch {
|
|
27531
|
+
return null;
|
|
27532
|
+
}
|
|
27533
|
+
}
|
|
27534
|
+
function handleFor(filename, generation, lines, rowOrdinal) {
|
|
27535
|
+
return {
|
|
27536
|
+
filename,
|
|
27537
|
+
fileGeneration: generation,
|
|
27538
|
+
rowOrdinal,
|
|
27539
|
+
expectedHash: timelineRowHash(lines[rowOrdinal])
|
|
27540
|
+
};
|
|
27541
|
+
}
|
|
27542
|
+
function refreshTimelineEntryHandle(handle, rewrite) {
|
|
27543
|
+
if (handle.filename !== rewrite.filename)
|
|
27544
|
+
return handle;
|
|
27545
|
+
if (handle.fileGeneration !== rewrite.previousFileGeneration)
|
|
27546
|
+
return null;
|
|
27547
|
+
if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
|
|
27548
|
+
return null;
|
|
27549
|
+
const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
|
|
27550
|
+
if (rowOrdinal === null || rowOrdinal === undefined)
|
|
27551
|
+
return null;
|
|
27552
|
+
const expectedHash = rewrite.rowHashes[rowOrdinal];
|
|
27553
|
+
if (!expectedHash)
|
|
27554
|
+
return null;
|
|
27555
|
+
return {
|
|
27556
|
+
filename: handle.filename,
|
|
27557
|
+
fileGeneration: rewrite.fileGeneration,
|
|
27558
|
+
rowOrdinal,
|
|
27559
|
+
expectedHash
|
|
27560
|
+
};
|
|
27561
|
+
}
|
|
27016
27562
|
function compactLines(input) {
|
|
27017
27563
|
let head = 0;
|
|
27018
27564
|
let bytes = input.reduce((total, line) => total + line.bytes, 0);
|
|
@@ -27224,11 +27770,37 @@ function atomicReplaceTimeline(filePath, lines) {
|
|
|
27224
27770
|
} catch {}
|
|
27225
27771
|
}
|
|
27226
27772
|
}
|
|
27227
|
-
function
|
|
27773
|
+
function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
|
|
27774
|
+
const previousFileGeneration = timelineFileGeneration(filePath, existing);
|
|
27775
|
+
const previousRowHashes = existing.map(timelineRowHash);
|
|
27228
27776
|
const compacted = compactLines(input);
|
|
27229
|
-
|
|
27230
|
-
|
|
27231
|
-
|
|
27777
|
+
const targetOrdinal = compacted.indexOf(required2);
|
|
27778
|
+
if (targetOrdinal < 0)
|
|
27779
|
+
return { status: "rejected", reason: "evicted" };
|
|
27780
|
+
if (!atomicReplaceTimeline(filePath, compacted))
|
|
27781
|
+
return { status: "rejected", reason: "write" };
|
|
27782
|
+
const fileGeneration = timelineFileGeneration(filePath, compacted);
|
|
27783
|
+
if (!fileGeneration)
|
|
27784
|
+
return { status: "rejected", reason: "write" };
|
|
27785
|
+
const rowOrdinals = existing.map((line, oldOrdinal) => {
|
|
27786
|
+
if (replacement?.oldOrdinal === oldOrdinal)
|
|
27787
|
+
return compacted.indexOf(replacement.line);
|
|
27788
|
+
const nextOrdinal = compacted.indexOf(line);
|
|
27789
|
+
return nextOrdinal < 0 ? null : nextOrdinal;
|
|
27790
|
+
});
|
|
27791
|
+
const rewrite = {
|
|
27792
|
+
filename,
|
|
27793
|
+
previousFileGeneration,
|
|
27794
|
+
fileGeneration,
|
|
27795
|
+
previousRowHashes,
|
|
27796
|
+
rowOrdinals,
|
|
27797
|
+
rowHashes: compacted.map(timelineRowHash)
|
|
27798
|
+
};
|
|
27799
|
+
return {
|
|
27800
|
+
status: "written",
|
|
27801
|
+
rewrite,
|
|
27802
|
+
handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
|
|
27803
|
+
};
|
|
27232
27804
|
}
|
|
27233
27805
|
function filenameForDate(date5) {
|
|
27234
27806
|
const y = date5.getFullYear();
|
|
@@ -27260,114 +27832,181 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
27260
27832
|
}
|
|
27261
27833
|
return entries;
|
|
27262
27834
|
}
|
|
27263
|
-
function
|
|
27835
|
+
function readResumeControlState(timelineDir) {
|
|
27836
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27837
|
+
return { kind: "missing" };
|
|
27838
|
+
const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27839
|
+
let source;
|
|
27840
|
+
try {
|
|
27841
|
+
source = fs7.lstatSync(filePath);
|
|
27842
|
+
} catch (error51) {
|
|
27843
|
+
return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
|
|
27844
|
+
}
|
|
27845
|
+
if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
|
|
27846
|
+
return { kind: "invalid" };
|
|
27847
|
+
}
|
|
27848
|
+
let fd = null;
|
|
27849
|
+
try {
|
|
27850
|
+
fd = fs7.openSync(filePath, fs7.constants.O_RDONLY | (fs7.constants.O_NOFOLLOW ?? 0));
|
|
27851
|
+
const stat = fs7.fstatSync(fd);
|
|
27852
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
|
|
27853
|
+
return { kind: "invalid" };
|
|
27854
|
+
}
|
|
27855
|
+
const bounded = Buffer.allocUnsafe(stat.size + 1);
|
|
27856
|
+
let bytesRead = 0;
|
|
27857
|
+
while (bytesRead < bounded.length) {
|
|
27858
|
+
const count = fs7.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
|
|
27859
|
+
if (count <= 0)
|
|
27860
|
+
break;
|
|
27861
|
+
bytesRead += count;
|
|
27862
|
+
}
|
|
27863
|
+
if (bytesRead !== stat.size)
|
|
27864
|
+
return { kind: "invalid" };
|
|
27865
|
+
const raw = bounded.subarray(0, bytesRead).toString("utf8");
|
|
27866
|
+
const value = JSON.parse(raw);
|
|
27867
|
+
const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
|
|
27868
|
+
if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
|
|
27869
|
+
return { kind: "invalid" };
|
|
27870
|
+
return {
|
|
27871
|
+
kind: "state",
|
|
27872
|
+
state: {
|
|
27873
|
+
version: 1,
|
|
27874
|
+
attemptedSessionId: value.attemptedSessionId,
|
|
27875
|
+
fencedSessionId: value.fencedSessionId,
|
|
27876
|
+
fullBarrier: value.fullBarrier
|
|
27877
|
+
}
|
|
27878
|
+
};
|
|
27879
|
+
} catch {
|
|
27880
|
+
return { kind: "invalid" };
|
|
27881
|
+
} finally {
|
|
27882
|
+
if (fd !== null) {
|
|
27883
|
+
try {
|
|
27884
|
+
fs7.closeSync(fd);
|
|
27885
|
+
} catch {}
|
|
27886
|
+
}
|
|
27887
|
+
}
|
|
27888
|
+
}
|
|
27889
|
+
function updateResumeControlState(timelineDir, update) {
|
|
27264
27890
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27265
27891
|
return false;
|
|
27266
|
-
const
|
|
27267
|
-
const filePath = join10(timelineDir, filename);
|
|
27268
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
27892
|
+
const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27269
27893
|
if (!acquireLock(lockPath))
|
|
27270
27894
|
return false;
|
|
27271
27895
|
try {
|
|
27272
|
-
const
|
|
27273
|
-
const
|
|
27274
|
-
|
|
27896
|
+
const current = readResumeControlState(timelineDir);
|
|
27897
|
+
const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
|
|
27898
|
+
const next = update({ ...base });
|
|
27899
|
+
const canonical = {
|
|
27900
|
+
version: 1,
|
|
27901
|
+
attemptedSessionId: next.attemptedSessionId,
|
|
27902
|
+
fencedSessionId: next.fencedSessionId,
|
|
27903
|
+
fullBarrier: next.fullBarrier
|
|
27904
|
+
};
|
|
27905
|
+
const body = JSON.stringify(canonical) + `
|
|
27906
|
+
`;
|
|
27907
|
+
if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
|
|
27275
27908
|
return false;
|
|
27276
|
-
|
|
27909
|
+
const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27910
|
+
const tempPath = join10(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
|
|
27911
|
+
let fd = null;
|
|
27912
|
+
try {
|
|
27913
|
+
fd = fs7.openSync(tempPath, "wx", 384);
|
|
27914
|
+
fs7.writeFileSync(fd, body, "utf8");
|
|
27915
|
+
fs7.fsyncSync(fd);
|
|
27916
|
+
fs7.closeSync(fd);
|
|
27917
|
+
fd = null;
|
|
27918
|
+
fs7.renameSync(tempPath, filePath);
|
|
27919
|
+
return true;
|
|
27920
|
+
} finally {
|
|
27921
|
+
if (fd !== null) {
|
|
27922
|
+
try {
|
|
27923
|
+
fs7.closeSync(fd);
|
|
27924
|
+
} catch {}
|
|
27925
|
+
}
|
|
27926
|
+
try {
|
|
27927
|
+
fs7.unlinkSync(tempPath);
|
|
27928
|
+
} catch {}
|
|
27929
|
+
}
|
|
27277
27930
|
} catch {
|
|
27278
27931
|
return false;
|
|
27279
27932
|
} finally {
|
|
27280
27933
|
releaseLock(lockPath);
|
|
27281
27934
|
}
|
|
27282
27935
|
}
|
|
27283
|
-
function
|
|
27936
|
+
function appendTrackedEntry(timelineDir, entry, now = new Date) {
|
|
27284
27937
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27285
|
-
return
|
|
27938
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27286
27939
|
const filename = filenameForDate(now);
|
|
27287
27940
|
const filePath = join10(timelineDir, filename);
|
|
27288
27941
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
27289
|
-
|
|
27290
|
-
|
|
27942
|
+
try {
|
|
27943
|
+
if (!acquireLock(lockPath))
|
|
27944
|
+
return { status: "rejected", reason: "lock" };
|
|
27945
|
+
} catch {
|
|
27946
|
+
return { status: "rejected", reason: "write" };
|
|
27947
|
+
}
|
|
27291
27948
|
try {
|
|
27292
27949
|
const existing = scanTimelineFile(filePath);
|
|
27293
|
-
if (!existing)
|
|
27294
|
-
return false;
|
|
27295
|
-
if (existing.length > 0) {
|
|
27296
|
-
const latest = existing[existing.length - 1].entry;
|
|
27297
|
-
const mergeable = !latest.system && !entry.system && latest.session_id === entry.session_id && latest.provider === entry.provider && latest.agent_responses.length === 0;
|
|
27298
|
-
if (mergeable) {
|
|
27299
|
-
const merged = {
|
|
27300
|
-
...latest,
|
|
27301
|
-
messages: [...latest.messages, ...entry.messages],
|
|
27302
|
-
agent_responses: [...latest.agent_responses]
|
|
27303
|
-
};
|
|
27304
|
-
const required3 = timelineLine(merged);
|
|
27305
|
-
if (!required3)
|
|
27306
|
-
return false;
|
|
27307
|
-
return writeRequiredTimeline(filePath, [...existing.slice(0, -1), required3], required3);
|
|
27308
|
-
}
|
|
27309
|
-
}
|
|
27310
27950
|
const required2 = timelineLine(entry);
|
|
27951
|
+
if (!existing)
|
|
27952
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27311
27953
|
if (!required2)
|
|
27312
|
-
return
|
|
27313
|
-
return
|
|
27954
|
+
return { status: "rejected", reason: "oversized" };
|
|
27955
|
+
return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
|
|
27314
27956
|
} catch {
|
|
27315
|
-
return
|
|
27957
|
+
return { status: "rejected", reason: "write" };
|
|
27316
27958
|
} finally {
|
|
27317
27959
|
releaseLock(lockPath);
|
|
27318
27960
|
}
|
|
27319
27961
|
}
|
|
27320
|
-
function
|
|
27321
|
-
|
|
27322
|
-
|
|
27323
|
-
|
|
27324
|
-
|
|
27325
|
-
|
|
27326
|
-
|
|
27327
|
-
|
|
27328
|
-
|
|
27329
|
-
try {
|
|
27330
|
-
source = fs7.lstatSync(filePath);
|
|
27331
|
-
} catch (error51) {
|
|
27332
|
-
if (error51.code === "ENOENT")
|
|
27333
|
-
continue;
|
|
27334
|
-
return "rejected";
|
|
27335
|
-
}
|
|
27336
|
-
if (!source.isFile())
|
|
27337
|
-
return "rejected";
|
|
27338
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
27962
|
+
function updateTrackedEntry(timelineDir, handle, update) {
|
|
27963
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27964
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27965
|
+
if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename(handle.filename) !== handle.filename) {
|
|
27966
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27967
|
+
}
|
|
27968
|
+
const filePath = join10(timelineDir, handle.filename);
|
|
27969
|
+
const lockPath = lockPathFor(timelineDir, handle.filename);
|
|
27970
|
+
try {
|
|
27339
27971
|
if (!acquireLock(lockPath))
|
|
27340
|
-
return "rejected";
|
|
27341
|
-
|
|
27342
|
-
|
|
27343
|
-
|
|
27344
|
-
|
|
27345
|
-
|
|
27346
|
-
|
|
27347
|
-
|
|
27348
|
-
|
|
27349
|
-
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
27354
|
-
|
|
27355
|
-
|
|
27356
|
-
|
|
27357
|
-
|
|
27358
|
-
|
|
27359
|
-
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
}
|
|
27367
|
-
|
|
27368
|
-
|
|
27972
|
+
return { status: "rejected", reason: "lock" };
|
|
27973
|
+
} catch {
|
|
27974
|
+
return { status: "rejected", reason: "write" };
|
|
27975
|
+
}
|
|
27976
|
+
try {
|
|
27977
|
+
const existing = scanTimelineFile(filePath);
|
|
27978
|
+
if (!existing)
|
|
27979
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27980
|
+
if (existing.length === 0)
|
|
27981
|
+
return { status: "rejected", reason: "missing" };
|
|
27982
|
+
const generation = timelineFileGeneration(filePath, existing);
|
|
27983
|
+
if (!generation || generation !== handle.fileGeneration) {
|
|
27984
|
+
return { status: "rejected", reason: "generation" };
|
|
27985
|
+
}
|
|
27986
|
+
const captured = existing[handle.rowOrdinal];
|
|
27987
|
+
if (!captured)
|
|
27988
|
+
return { status: "rejected", reason: "ordinal" };
|
|
27989
|
+
if (timelineRowHash(captured) !== handle.expectedHash) {
|
|
27990
|
+
return { status: "rejected", reason: "hash" };
|
|
27991
|
+
}
|
|
27992
|
+
if (captured.entry.system)
|
|
27993
|
+
return { status: "rejected", reason: "system" };
|
|
27994
|
+
const nextEntry = update({
|
|
27995
|
+
...captured.entry,
|
|
27996
|
+
messages: [...captured.entry.messages],
|
|
27997
|
+
agent_responses: [...captured.entry.agent_responses]
|
|
27998
|
+
});
|
|
27999
|
+
const replacement = timelineLine(nextEntry);
|
|
28000
|
+
if (!replacement)
|
|
28001
|
+
return { status: "rejected", reason: "oversized" };
|
|
28002
|
+
const input = [...existing];
|
|
28003
|
+
input[handle.rowOrdinal] = replacement;
|
|
28004
|
+
return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
|
|
28005
|
+
} catch {
|
|
28006
|
+
return { status: "rejected", reason: "write" };
|
|
28007
|
+
} finally {
|
|
28008
|
+
releaseLock(lockPath);
|
|
27369
28009
|
}
|
|
27370
|
-
return "missing";
|
|
27371
28010
|
}
|
|
27372
28011
|
function yieldToEventLoop() {
|
|
27373
28012
|
return new Promise((resolve3) => setImmediate(resolve3));
|
|
@@ -27431,84 +28070,562 @@ function createTimelineEntry(fields) {
|
|
|
27431
28070
|
provider: fields.provider ?? null
|
|
27432
28071
|
};
|
|
27433
28072
|
}
|
|
27434
|
-
function createSystemEntry(type, time3) {
|
|
28073
|
+
function createSystemEntry(type, time3, backendSessionId) {
|
|
27435
28074
|
return {
|
|
27436
28075
|
session_id: null,
|
|
27437
28076
|
messages: [],
|
|
27438
28077
|
agent_responses: [],
|
|
27439
28078
|
provider: null,
|
|
27440
|
-
system: {
|
|
28079
|
+
system: {
|
|
28080
|
+
type,
|
|
28081
|
+
time: time3,
|
|
28082
|
+
...backendSessionId ? { backend_session_id: backendSessionId } : {}
|
|
28083
|
+
}
|
|
27441
28084
|
};
|
|
27442
28085
|
}
|
|
27443
|
-
function
|
|
28086
|
+
function resolveResumableSession(rows, provider) {
|
|
28087
|
+
let candidateSessionId = null;
|
|
28088
|
+
let recoveryMarkerSeen = false;
|
|
28089
|
+
let stalledSessionId = null;
|
|
28090
|
+
let fencedSessionId = null;
|
|
27444
28091
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
27445
28092
|
const e = rows[i];
|
|
27446
|
-
if (e.system
|
|
27447
|
-
|
|
28093
|
+
if (e.system) {
|
|
28094
|
+
if (e.system.type === "stall_recovery_attempt") {
|
|
28095
|
+
if (!recoveryMarkerSeen) {
|
|
28096
|
+
recoveryMarkerSeen = true;
|
|
28097
|
+
stalledSessionId = e.system.backend_session_id ?? null;
|
|
28098
|
+
}
|
|
28099
|
+
continue;
|
|
28100
|
+
}
|
|
28101
|
+
if (e.system.type === "stall_recovery_clear") {
|
|
28102
|
+
if (!recoveryMarkerSeen)
|
|
28103
|
+
recoveryMarkerSeen = true;
|
|
28104
|
+
continue;
|
|
28105
|
+
}
|
|
28106
|
+
if (e.system.type === "stall_recovery" && fencedSessionId === null) {
|
|
28107
|
+
fencedSessionId = e.system.backend_session_id ?? null;
|
|
28108
|
+
}
|
|
28109
|
+
if (candidateSessionId !== null) {
|
|
28110
|
+
return {
|
|
28111
|
+
kind: "session",
|
|
28112
|
+
sessionId: candidateSessionId,
|
|
28113
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
28114
|
+
fencedSessionId
|
|
28115
|
+
};
|
|
28116
|
+
}
|
|
28117
|
+
return {
|
|
28118
|
+
kind: "barrier",
|
|
28119
|
+
type: e.system.type,
|
|
28120
|
+
forgottenSessionId: e.system.backend_session_id ?? null,
|
|
28121
|
+
fencedSessionId
|
|
28122
|
+
};
|
|
28123
|
+
}
|
|
27448
28124
|
if (!e.session_id)
|
|
27449
28125
|
continue;
|
|
27450
28126
|
if (provider && e.provider !== provider)
|
|
27451
28127
|
continue;
|
|
27452
|
-
|
|
28128
|
+
if (candidateSessionId === null) {
|
|
28129
|
+
candidateSessionId = e.session_id;
|
|
28130
|
+
continue;
|
|
28131
|
+
}
|
|
28132
|
+
if (candidateSessionId !== e.session_id)
|
|
28133
|
+
break;
|
|
27453
28134
|
}
|
|
27454
|
-
|
|
28135
|
+
if (candidateSessionId !== null) {
|
|
28136
|
+
return {
|
|
28137
|
+
kind: "session",
|
|
28138
|
+
sessionId: candidateSessionId,
|
|
28139
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
28140
|
+
fencedSessionId
|
|
28141
|
+
};
|
|
28142
|
+
}
|
|
28143
|
+
return { kind: "none", stalledSessionId, fencedSessionId };
|
|
27455
28144
|
}
|
|
27456
28145
|
// src/timeline/recorder.ts
|
|
27457
28146
|
var MAX_AGENT_RESPONSES = 5;
|
|
27458
|
-
|
|
27459
|
-
|
|
27460
|
-
|
|
27461
|
-
|
|
28147
|
+
var MAX_AGENT_RESPONSE_BYTES = 65536;
|
|
28148
|
+
var MAX_PENDING_COMMITS_PER_AGENT = 8;
|
|
28149
|
+
var PENDING_COMMIT_TTL_MS = 15 * 60000;
|
|
28150
|
+
var TRUNCATION_MARKER = `
|
|
28151
|
+
… [truncated]`;
|
|
28152
|
+
function turnKey(agentId, owner) {
|
|
28153
|
+
return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
|
|
28154
|
+
}
|
|
28155
|
+
function sameOwner(left, right) {
|
|
28156
|
+
return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
|
|
28157
|
+
}
|
|
28158
|
+
function utf8Prefix2(text2, maxBytes) {
|
|
28159
|
+
if (maxBytes <= 0)
|
|
28160
|
+
return "";
|
|
28161
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
28162
|
+
return text2;
|
|
28163
|
+
let low = 0;
|
|
28164
|
+
let high = text2.length;
|
|
28165
|
+
while (low < high) {
|
|
28166
|
+
const mid = Math.ceil((low + high) / 2);
|
|
28167
|
+
let end2 = mid;
|
|
28168
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
28169
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
28170
|
+
end2 -= 1;
|
|
28171
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
28172
|
+
low = mid;
|
|
28173
|
+
else
|
|
28174
|
+
high = mid - 1;
|
|
28175
|
+
}
|
|
28176
|
+
let end = low;
|
|
28177
|
+
const code = text2.charCodeAt(end - 1);
|
|
28178
|
+
if (code >= 55296 && code <= 56319)
|
|
28179
|
+
end -= 1;
|
|
28180
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
28181
|
+
end -= 1;
|
|
28182
|
+
return text2.slice(0, end);
|
|
28183
|
+
}
|
|
28184
|
+
function boundedResponse(text2, alreadyTruncated) {
|
|
28185
|
+
const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
|
|
28186
|
+
const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
|
|
28187
|
+
if (!needsTruncation)
|
|
28188
|
+
return text2;
|
|
28189
|
+
return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
|
|
28190
|
+
}
|
|
28191
|
+
function serializedTimelineBytes(entry) {
|
|
28192
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
28193
|
+
}
|
|
28194
|
+
function fitResponsesToRow(entry, responses) {
|
|
28195
|
+
let fitted = [...entry.agent_responses];
|
|
28196
|
+
for (const response of responses) {
|
|
28197
|
+
fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
|
|
28198
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
|
|
28199
|
+
continue;
|
|
28200
|
+
const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
|
|
28201
|
+
let low = 0;
|
|
28202
|
+
let high = truncationBase.length;
|
|
28203
|
+
let replacement = null;
|
|
28204
|
+
while (low <= high) {
|
|
28205
|
+
const mid = Math.floor((low + high) / 2);
|
|
28206
|
+
let end = mid;
|
|
28207
|
+
const code = truncationBase.charCodeAt(end - 1);
|
|
28208
|
+
if (code >= 55296 && code <= 56319)
|
|
28209
|
+
end -= 1;
|
|
28210
|
+
const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
|
|
28211
|
+
const candidate = [...fitted.slice(0, -1), candidateResponse];
|
|
28212
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
|
|
28213
|
+
replacement = candidateResponse;
|
|
28214
|
+
low = mid + 1;
|
|
28215
|
+
} else {
|
|
28216
|
+
high = mid - 1;
|
|
28217
|
+
}
|
|
28218
|
+
}
|
|
28219
|
+
if (replacement === null)
|
|
28220
|
+
return null;
|
|
28221
|
+
fitted[fitted.length - 1] = replacement;
|
|
27462
28222
|
}
|
|
28223
|
+
return fitted;
|
|
27463
28224
|
}
|
|
27464
28225
|
function createTimelineRecorder(opts) {
|
|
27465
28226
|
const now = opts.now ?? (() => new Date);
|
|
27466
28227
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
27467
28228
|
const sessionByAgent = new Map;
|
|
28229
|
+
const resolveForAgent = (agentId, provider) => {
|
|
28230
|
+
const dir = dirFor(agentId);
|
|
28231
|
+
const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
|
|
28232
|
+
const control = readResumeControlState(dir);
|
|
28233
|
+
if (control.kind === "missing")
|
|
28234
|
+
return recent;
|
|
28235
|
+
if (control.kind === "invalid") {
|
|
28236
|
+
return {
|
|
28237
|
+
kind: "barrier",
|
|
28238
|
+
type: "reset_session",
|
|
28239
|
+
forgottenSessionId: null,
|
|
28240
|
+
fencedSessionId: null
|
|
28241
|
+
};
|
|
28242
|
+
}
|
|
28243
|
+
const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
|
|
28244
|
+
if (fullBarrier !== null) {
|
|
28245
|
+
return {
|
|
28246
|
+
kind: "barrier",
|
|
28247
|
+
type: fullBarrier,
|
|
28248
|
+
forgottenSessionId: null,
|
|
28249
|
+
fencedSessionId
|
|
28250
|
+
};
|
|
28251
|
+
}
|
|
28252
|
+
if (recent.kind === "session") {
|
|
28253
|
+
return {
|
|
28254
|
+
kind: "session",
|
|
28255
|
+
sessionId: recent.sessionId,
|
|
28256
|
+
stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
|
|
28257
|
+
fencedSessionId
|
|
28258
|
+
};
|
|
28259
|
+
}
|
|
28260
|
+
if (recent.kind === "barrier") {
|
|
28261
|
+
if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
|
|
28262
|
+
return { ...recent, fencedSessionId };
|
|
28263
|
+
}
|
|
28264
|
+
}
|
|
28265
|
+
return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
|
|
28266
|
+
};
|
|
28267
|
+
const sessionByEpoch = new Map;
|
|
28268
|
+
const barrierByAgent = new Map;
|
|
28269
|
+
const turnsByAgent = new Map;
|
|
28270
|
+
const activeTurnByAgent = new Map;
|
|
28271
|
+
const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
|
|
28272
|
+
const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
|
|
28273
|
+
const statesFor = (agentId) => {
|
|
28274
|
+
let states = turnsByAgent.get(agentId);
|
|
28275
|
+
if (!states) {
|
|
28276
|
+
states = new Map;
|
|
28277
|
+
turnsByAgent.set(agentId, states);
|
|
28278
|
+
}
|
|
28279
|
+
return states;
|
|
28280
|
+
};
|
|
28281
|
+
const diagnostic = (agentId, code, reason) => {
|
|
28282
|
+
try {
|
|
28283
|
+
opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
|
|
28284
|
+
} catch {}
|
|
28285
|
+
};
|
|
28286
|
+
const applyRewrite = (agentId, rewrite) => {
|
|
28287
|
+
for (const state of statesFor(agentId).values()) {
|
|
28288
|
+
if (!state.handle)
|
|
28289
|
+
continue;
|
|
28290
|
+
const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
|
|
28291
|
+
if (refreshed) {
|
|
28292
|
+
state.handle = refreshed;
|
|
28293
|
+
} else if (state.handle.filename === rewrite.filename) {
|
|
28294
|
+
state.handle = undefined;
|
|
28295
|
+
state.rowFenced = true;
|
|
28296
|
+
diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
|
|
28297
|
+
}
|
|
28298
|
+
}
|
|
28299
|
+
};
|
|
28300
|
+
const handleTrackedResult = (agentId, state, result) => {
|
|
28301
|
+
if (result.status === "written") {
|
|
28302
|
+
applyRewrite(agentId, result.rewrite);
|
|
28303
|
+
if (state && result.handle) {
|
|
28304
|
+
state.handle = result.handle;
|
|
28305
|
+
state.rowFenced = false;
|
|
28306
|
+
}
|
|
28307
|
+
return "written";
|
|
28308
|
+
}
|
|
28309
|
+
if (result.reason === "lock" || result.reason === "write")
|
|
28310
|
+
return "retryable";
|
|
28311
|
+
if (state && result.reason !== "oversized")
|
|
28312
|
+
state.rowFenced = true;
|
|
28313
|
+
diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
|
|
28314
|
+
return "terminal";
|
|
28315
|
+
};
|
|
28316
|
+
const tryCommit = (agentId, state) => {
|
|
28317
|
+
if (state.responses.length === 0)
|
|
28318
|
+
return "written";
|
|
28319
|
+
const dir = dirFor(agentId);
|
|
28320
|
+
if (!prepareTimelineDirectory(dir)) {
|
|
28321
|
+
diagnostic(agentId, "timeline_directory_unavailable");
|
|
28322
|
+
return "terminal";
|
|
28323
|
+
}
|
|
28324
|
+
if (state.handle) {
|
|
28325
|
+
const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
|
|
28326
|
+
const fitted2 = fitResponsesToRow(entry2, state.responses);
|
|
28327
|
+
return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
|
|
28328
|
+
});
|
|
28329
|
+
return handleTrackedResult(agentId, state, result2);
|
|
28330
|
+
}
|
|
28331
|
+
if (state.rowFenced || state.pendingMode === "handle")
|
|
28332
|
+
return "terminal";
|
|
28333
|
+
if (state.owner.barrierGeneration !== currentBarrier(agentId))
|
|
28334
|
+
return "terminal";
|
|
28335
|
+
const entry = createTimelineEntry({
|
|
28336
|
+
messages: [],
|
|
28337
|
+
sessionId: state.backendSessionId,
|
|
28338
|
+
provider: state.provider
|
|
28339
|
+
});
|
|
28340
|
+
const fitted = fitResponsesToRow(entry, state.responses);
|
|
28341
|
+
if (!fitted) {
|
|
28342
|
+
diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
|
|
28343
|
+
return "terminal";
|
|
28344
|
+
}
|
|
28345
|
+
entry.agent_responses = fitted;
|
|
28346
|
+
const result = appendTrackedEntry(dir, entry, now());
|
|
28347
|
+
return handleTrackedResult(agentId, state, result);
|
|
28348
|
+
};
|
|
28349
|
+
const deleteState = (agentId, key) => {
|
|
28350
|
+
const states = statesFor(agentId);
|
|
28351
|
+
states.delete(key);
|
|
28352
|
+
if (activeTurnByAgent.get(agentId) === key)
|
|
28353
|
+
activeTurnByAgent.delete(agentId);
|
|
28354
|
+
if (states.size === 0)
|
|
28355
|
+
turnsByAgent.delete(agentId);
|
|
28356
|
+
};
|
|
28357
|
+
const retryPending = (agentId) => {
|
|
28358
|
+
const states = turnsByAgent.get(agentId);
|
|
28359
|
+
if (!states)
|
|
28360
|
+
return;
|
|
28361
|
+
const nowMs = now().getTime();
|
|
28362
|
+
for (const [key, state] of [...states]) {
|
|
28363
|
+
if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
|
|
28364
|
+
deleteState(agentId, key);
|
|
28365
|
+
continue;
|
|
28366
|
+
}
|
|
28367
|
+
if (!state.finalized || state.pendingSinceMs === undefined)
|
|
28368
|
+
continue;
|
|
28369
|
+
if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
|
|
28370
|
+
diagnostic(agentId, "timeline_pending_commit_expired");
|
|
28371
|
+
deleteState(agentId, key);
|
|
28372
|
+
continue;
|
|
28373
|
+
}
|
|
28374
|
+
const result = tryCommit(agentId, state);
|
|
28375
|
+
if (result === "written") {
|
|
28376
|
+
state.responses = [];
|
|
28377
|
+
state.pendingSinceMs = undefined;
|
|
28378
|
+
state.pendingMode = undefined;
|
|
28379
|
+
state.completedAtMs = nowMs;
|
|
28380
|
+
} else if (result === "terminal") {
|
|
28381
|
+
deleteState(agentId, key);
|
|
28382
|
+
}
|
|
28383
|
+
}
|
|
28384
|
+
const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
|
|
28385
|
+
while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
|
|
28386
|
+
const oldest = completed.shift();
|
|
28387
|
+
if (oldest)
|
|
28388
|
+
deleteState(agentId, oldest[0]);
|
|
28389
|
+
}
|
|
28390
|
+
};
|
|
28391
|
+
const retainPending = (agentId, key, state) => {
|
|
28392
|
+
const states = statesFor(agentId);
|
|
28393
|
+
const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
|
|
28394
|
+
if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
|
|
28395
|
+
diagnostic(agentId, "timeline_pending_commit_overflow");
|
|
28396
|
+
deleteState(agentId, key);
|
|
28397
|
+
return;
|
|
28398
|
+
}
|
|
28399
|
+
state.pendingSinceMs ??= now().getTime();
|
|
28400
|
+
state.pendingMode = state.handle ? "handle" : "fallback";
|
|
28401
|
+
};
|
|
28402
|
+
const appendOwnerless = (agentId, messages) => {
|
|
28403
|
+
if (messages.length === 0)
|
|
28404
|
+
return;
|
|
28405
|
+
const dir = dirFor(agentId);
|
|
28406
|
+
if (!prepareTimelineDirectory(dir))
|
|
28407
|
+
return;
|
|
28408
|
+
const result = appendTrackedEntry(dir, createTimelineEntry({
|
|
28409
|
+
messages,
|
|
28410
|
+
sessionId: sessionByAgent.get(agentId) ?? null,
|
|
28411
|
+
provider: opts.providerFor?.(agentId) ?? null
|
|
28412
|
+
}), now());
|
|
28413
|
+
handleTrackedResult(agentId, null, result);
|
|
28414
|
+
};
|
|
27468
28415
|
return {
|
|
27469
|
-
|
|
27470
|
-
|
|
28416
|
+
barrierGeneration(agentId) {
|
|
28417
|
+
return currentBarrier(agentId);
|
|
27471
28418
|
},
|
|
27472
|
-
|
|
28419
|
+
beginTurn(agentId, owner) {
|
|
28420
|
+
retryPending(agentId);
|
|
28421
|
+
const states = statesFor(agentId);
|
|
28422
|
+
if (owner.barrierGeneration !== currentBarrier(agentId)) {
|
|
28423
|
+
diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
|
|
28424
|
+
return;
|
|
28425
|
+
}
|
|
28426
|
+
const key = turnKey(agentId, owner);
|
|
28427
|
+
if (!states.has(key)) {
|
|
28428
|
+
states.set(key, {
|
|
28429
|
+
owner: { ...owner },
|
|
28430
|
+
provider: opts.providerFor?.(agentId) ?? null,
|
|
28431
|
+
backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
|
|
28432
|
+
responses: [],
|
|
28433
|
+
rowFenced: false,
|
|
28434
|
+
finalized: false
|
|
28435
|
+
});
|
|
28436
|
+
}
|
|
28437
|
+
activeTurnByAgent.set(agentId, key);
|
|
28438
|
+
},
|
|
28439
|
+
recordInboxPull(agentId, owner, messages) {
|
|
28440
|
+
retryPending(agentId);
|
|
27473
28441
|
if (messages.length === 0)
|
|
27474
28442
|
return;
|
|
28443
|
+
if (!owner) {
|
|
28444
|
+
appendOwnerless(agentId, messages);
|
|
28445
|
+
return;
|
|
28446
|
+
}
|
|
28447
|
+
const key = turnKey(agentId, owner);
|
|
28448
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28449
|
+
if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
|
|
28450
|
+
appendOwnerless(agentId, messages);
|
|
28451
|
+
return;
|
|
28452
|
+
}
|
|
27475
28453
|
const dir = dirFor(agentId);
|
|
27476
28454
|
if (!prepareTimelineDirectory(dir))
|
|
27477
28455
|
return;
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
|
|
28456
|
+
const stamp = now();
|
|
28457
|
+
let result;
|
|
28458
|
+
if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
|
|
28459
|
+
result = updateTrackedEntry(dir, state.handle, (entry) => ({
|
|
28460
|
+
...entry,
|
|
28461
|
+
messages: [...entry.messages, ...messages],
|
|
28462
|
+
session_id: state.backendSessionId,
|
|
28463
|
+
provider: state.provider
|
|
28464
|
+
}));
|
|
28465
|
+
} else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
|
|
28466
|
+
result = appendTrackedEntry(dir, createTimelineEntry({
|
|
28467
|
+
messages,
|
|
28468
|
+
sessionId: state.backendSessionId,
|
|
28469
|
+
provider: state.provider
|
|
28470
|
+
}), stamp);
|
|
28471
|
+
} else {
|
|
28472
|
+
appendOwnerless(agentId, messages);
|
|
28473
|
+
return;
|
|
28474
|
+
}
|
|
28475
|
+
handleTrackedResult(agentId, state, result);
|
|
27483
28476
|
},
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
|
|
28477
|
+
recordAssistantMessage(agentId, owner, text2, truncated = false) {
|
|
28478
|
+
retryPending(agentId);
|
|
28479
|
+
const key = turnKey(agentId, owner);
|
|
28480
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28481
|
+
if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
|
|
28482
|
+
diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
|
|
27487
28483
|
return;
|
|
27488
|
-
|
|
27489
|
-
|
|
28484
|
+
}
|
|
28485
|
+
state.responses.push(boundedResponse(text2, truncated));
|
|
28486
|
+
if (state.responses.length > MAX_AGENT_RESPONSES) {
|
|
28487
|
+
state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
|
|
28488
|
+
}
|
|
28489
|
+
},
|
|
28490
|
+
finalizeTurn(agentId, owner) {
|
|
28491
|
+
retryPending(agentId);
|
|
28492
|
+
const key = turnKey(agentId, owner);
|
|
28493
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28494
|
+
if (!state || state.finalized || !sameOwner(state.owner, owner))
|
|
27490
28495
|
return;
|
|
27491
|
-
const
|
|
27492
|
-
|
|
27493
|
-
|
|
27494
|
-
|
|
27495
|
-
|
|
27496
|
-
|
|
27497
|
-
|
|
28496
|
+
const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
|
|
28497
|
+
state.finalized = true;
|
|
28498
|
+
activeTurnByAgent.delete(agentId);
|
|
28499
|
+
if (state.responses.length === 0) {
|
|
28500
|
+
state.completedAtMs = now().getTime();
|
|
28501
|
+
return;
|
|
28502
|
+
}
|
|
28503
|
+
if (!state.handle && !fallbackAuthorized) {
|
|
28504
|
+
diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
|
|
28505
|
+
deleteState(agentId, key);
|
|
28506
|
+
return;
|
|
28507
|
+
}
|
|
28508
|
+
const result = tryCommit(agentId, state);
|
|
28509
|
+
if (result === "written") {
|
|
28510
|
+
state.responses = [];
|
|
28511
|
+
state.completedAtMs = now().getTime();
|
|
28512
|
+
} else if (result === "terminal") {
|
|
28513
|
+
deleteState(agentId, key);
|
|
28514
|
+
} else {
|
|
28515
|
+
retainPending(agentId, key, state);
|
|
28516
|
+
}
|
|
28517
|
+
},
|
|
28518
|
+
fenceSession(agentId) {
|
|
28519
|
+
retryPending(agentId);
|
|
28520
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
28521
|
+
activeTurnByAgent.delete(agentId);
|
|
28522
|
+
const states = turnsByAgent.get(agentId);
|
|
28523
|
+
if (!states)
|
|
28524
|
+
return;
|
|
28525
|
+
for (const [key, state] of [...states]) {
|
|
28526
|
+
if (!state.handle) {
|
|
28527
|
+
diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
|
|
28528
|
+
deleteState(agentId, key);
|
|
28529
|
+
}
|
|
28530
|
+
}
|
|
28531
|
+
},
|
|
28532
|
+
setSession(agentId, sessionId, sessionInstanceId) {
|
|
28533
|
+
retryPending(agentId);
|
|
28534
|
+
const dir = dirFor(agentId);
|
|
28535
|
+
if (!prepareTimelineDirectory(dir))
|
|
28536
|
+
return false;
|
|
28537
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
28538
|
+
...state,
|
|
28539
|
+
fullBarrier: null,
|
|
28540
|
+
attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
|
|
28541
|
+
}));
|
|
28542
|
+
if (!persisted)
|
|
28543
|
+
return false;
|
|
28544
|
+
sessionByAgent.set(agentId, sessionId);
|
|
28545
|
+
if (sessionInstanceId) {
|
|
28546
|
+
sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
|
|
28547
|
+
for (const state of statesFor(agentId).values()) {
|
|
28548
|
+
if (state.owner.sessionInstanceId === sessionInstanceId)
|
|
28549
|
+
state.backendSessionId = sessionId;
|
|
28550
|
+
}
|
|
28551
|
+
}
|
|
28552
|
+
return true;
|
|
27498
28553
|
},
|
|
27499
28554
|
resumeSessionId(agentId, provider) {
|
|
27500
|
-
const
|
|
27501
|
-
return
|
|
28555
|
+
const resolution = resolveForAgent(agentId, provider);
|
|
28556
|
+
return resolution.kind === "session" ? resolution.sessionId : null;
|
|
27502
28557
|
},
|
|
27503
|
-
|
|
28558
|
+
resolveResumeSession(agentId, provider) {
|
|
28559
|
+
return resolveForAgent(agentId, provider);
|
|
28560
|
+
},
|
|
28561
|
+
recordSessionStall(agentId, sessionId) {
|
|
28562
|
+
return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
|
|
28563
|
+
},
|
|
28564
|
+
clearSessionStall(agentId, sessionId) {
|
|
28565
|
+
return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
|
|
28566
|
+
},
|
|
28567
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
28568
|
+
retryPending(agentId);
|
|
27504
28569
|
const dir = dirFor(agentId);
|
|
27505
|
-
sessionByAgent.delete(agentId);
|
|
27506
28570
|
if (!prepareTimelineDirectory(dir))
|
|
27507
|
-
return;
|
|
28571
|
+
return false;
|
|
27508
28572
|
const stamp = now();
|
|
27509
|
-
|
|
28573
|
+
let persisted = false;
|
|
28574
|
+
if (barrierType === "reset_session" || barrierType === "nap") {
|
|
28575
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
28576
|
+
...state,
|
|
28577
|
+
attemptedSessionId: null,
|
|
28578
|
+
fencedSessionId: null,
|
|
28579
|
+
fullBarrier: barrierType
|
|
28580
|
+
}));
|
|
28581
|
+
} else if (barrierType === "stall_recovery") {
|
|
28582
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
28583
|
+
...state,
|
|
28584
|
+
attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
|
|
28585
|
+
fencedSessionId: forgottenSessionId ?? null,
|
|
28586
|
+
fullBarrier: null
|
|
28587
|
+
}));
|
|
28588
|
+
}
|
|
28589
|
+
if (!persisted)
|
|
28590
|
+
return false;
|
|
28591
|
+
sessionByAgent.delete(agentId);
|
|
28592
|
+
for (const key of [...sessionByEpoch.keys()]) {
|
|
28593
|
+
if (key.startsWith(`${agentId}\x00`))
|
|
28594
|
+
sessionByEpoch.delete(key);
|
|
28595
|
+
}
|
|
28596
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
28597
|
+
activeTurnByAgent.delete(agentId);
|
|
28598
|
+
const states = turnsByAgent.get(agentId);
|
|
28599
|
+
if (states) {
|
|
28600
|
+
for (const [key, state] of [...states]) {
|
|
28601
|
+
if (!state.handle) {
|
|
28602
|
+
diagnostic(agentId, "timeline_fallback_rejected", "barrier");
|
|
28603
|
+
deleteState(agentId, key);
|
|
28604
|
+
}
|
|
28605
|
+
}
|
|
28606
|
+
}
|
|
28607
|
+
const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
|
|
28608
|
+
handleTrackedResult(agentId, null, result);
|
|
28609
|
+
return true;
|
|
27510
28610
|
}
|
|
27511
28611
|
};
|
|
28612
|
+
function appendStallMarker(agentId, type, sessionId) {
|
|
28613
|
+
retryPending(agentId);
|
|
28614
|
+
const dir = dirFor(agentId);
|
|
28615
|
+
if (!prepareTimelineDirectory(dir))
|
|
28616
|
+
return false;
|
|
28617
|
+
const stamp = now();
|
|
28618
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
28619
|
+
...state,
|
|
28620
|
+
attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
|
|
28621
|
+
fullBarrier: null
|
|
28622
|
+
}));
|
|
28623
|
+
if (!persisted)
|
|
28624
|
+
return false;
|
|
28625
|
+
const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
|
|
28626
|
+
handleTrackedResult(agentId, null, result);
|
|
28627
|
+
return true;
|
|
28628
|
+
}
|
|
27512
28629
|
}
|
|
27513
28630
|
// src/discovery.ts
|
|
27514
28631
|
import * as path9 from "path";
|
|
@@ -28123,11 +29240,15 @@ async function createDaemon(opts) {
|
|
|
28123
29240
|
const typingTracker = createTypingScopeTracker();
|
|
28124
29241
|
const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
|
|
28125
29242
|
const proxy = await startCredentialProxy(broker, {
|
|
28126
|
-
onInboxPullStart: (agentId) =>
|
|
29243
|
+
onInboxPullStart: (agentId) => ({
|
|
29244
|
+
modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
|
|
29245
|
+
owner: managerRef?.timelineTurnOwner(agentId) ?? null
|
|
29246
|
+
}),
|
|
28127
29247
|
onInboxPullResponse: (agentId, messages, observationToken) => {
|
|
28128
|
-
|
|
28129
|
-
|
|
28130
|
-
|
|
29248
|
+
const token = observationToken && typeof observationToken === "object" ? observationToken : null;
|
|
29249
|
+
timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
|
|
29250
|
+
if (typeof token?.modelSeenGeneration === "number") {
|
|
29251
|
+
channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
|
|
28131
29252
|
}
|
|
28132
29253
|
},
|
|
28133
29254
|
onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
|
|
@@ -28578,10 +29699,12 @@ export {
|
|
|
28578
29699
|
RUNTIME_RAW_TRACE_AGENT_IDS_ENV,
|
|
28579
29700
|
RUNTIME_AUTH_ACTION_REQUIRED_PATTERNS,
|
|
28580
29701
|
LOCAL_MESSAGE_REMINDER_PATH,
|
|
29702
|
+
DEFAULT_TURN_SILENCE_POLICY,
|
|
28581
29703
|
DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
28582
29704
|
DEFAULT_STALE_THRESHOLD_MS,
|
|
28583
29705
|
DEFAULT_RESET_STUCK_THRESHOLD_MS,
|
|
28584
29706
|
DEFAULT_IDLE_TIMEOUT_MS,
|
|
29707
|
+
DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
28585
29708
|
DEFAULT_HELD_CONTEXT_LIMIT,
|
|
28586
29709
|
DEFAULT_CAPABILITY_RESOLVER,
|
|
28587
29710
|
CredentialBroker,
|