@alook/daemon 0.1.17 → 0.1.19
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 +1506 -300
- package/dist/index.js +1421 -290
- 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;
|
|
@@ -7679,12 +8025,19 @@ class AgentProcessManager {
|
|
|
7679
8025
|
snapshot() {
|
|
7680
8026
|
return this.state;
|
|
7681
8027
|
}
|
|
8028
|
+
runningAgentCount() {
|
|
8029
|
+
return this.sessions.size;
|
|
8030
|
+
}
|
|
7682
8031
|
auditContext(agentId) {
|
|
7683
8032
|
return {
|
|
7684
8033
|
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
7685
8034
|
launchId: this.launchIds.get(agentId) ?? null
|
|
7686
8035
|
};
|
|
7687
8036
|
}
|
|
8037
|
+
timelineTurnOwner(agentId) {
|
|
8038
|
+
const owner = this.traceOwnerFor(agentId);
|
|
8039
|
+
return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
|
|
8040
|
+
}
|
|
7688
8041
|
liveSessionReports() {
|
|
7689
8042
|
return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
|
|
7690
8043
|
agentId,
|
|
@@ -7754,6 +8107,10 @@ class AgentProcessManager {
|
|
|
7754
8107
|
if (!expectedSpan || owner.activeSpan !== expectedSpan)
|
|
7755
8108
|
return false;
|
|
7756
8109
|
owner.activeSpan = null;
|
|
8110
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
8111
|
+
owner.timelineTurnOwner = null;
|
|
8112
|
+
if (timelineTurnOwner)
|
|
8113
|
+
this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
|
|
7757
8114
|
const nowMs = this.now();
|
|
7758
8115
|
const base = {
|
|
7759
8116
|
recordKind: "turn_span",
|
|
@@ -7811,6 +8168,13 @@ class AgentProcessManager {
|
|
|
7811
8168
|
inbox: a.inbox.length,
|
|
7812
8169
|
lastDeliverAt: a.lastDeliverAt,
|
|
7813
8170
|
lastProgressAt: a.lastProgressAt,
|
|
8171
|
+
lastNativeActivityAt: a.lastNativeActivityAt,
|
|
8172
|
+
lastNativeActivityKind: a.lastNativeActivityKind,
|
|
8173
|
+
runtimePhase: a.runtimePhase,
|
|
8174
|
+
backendTurnId: a.backendTurnId,
|
|
8175
|
+
turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
|
|
8176
|
+
nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
|
|
8177
|
+
recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
|
|
7814
8178
|
idleSince: a.idleSince,
|
|
7815
8179
|
resetting: a.resetting,
|
|
7816
8180
|
resettingSince: a.resettingSince,
|
|
@@ -7821,6 +8185,7 @@ class AgentProcessManager {
|
|
|
7821
8185
|
timeIso: new Date(nowMs).toISOString(),
|
|
7822
8186
|
...activeSpan ? activeSpan : {},
|
|
7823
8187
|
sinceProgressMs: nowMs - a.lastProgressAt,
|
|
8188
|
+
sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
|
|
7824
8189
|
sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
|
|
7825
8190
|
sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
|
|
7826
8191
|
...event.type === "turn_completed" && event.endReason === "errored" ? {
|
|
@@ -7855,11 +8220,18 @@ class AgentProcessManager {
|
|
|
7855
8220
|
terminationCause: normalizeTerminationCause(event.terminationCause)
|
|
7856
8221
|
} : { event: "turn_end", outcome: "clean" });
|
|
7857
8222
|
}
|
|
7858
|
-
if (this.opts.onAgentActivity && event.type !== "
|
|
8223
|
+
if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
|
|
7859
8224
|
const after = this.deriveActivitySnapshot(state);
|
|
7860
8225
|
for (const [agentId, activity] of Object.entries(after)) {
|
|
7861
|
-
if (agentId in before
|
|
8226
|
+
if (!(agentId in before)) {
|
|
8227
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
8228
|
+
continue;
|
|
8229
|
+
}
|
|
8230
|
+
const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
|
|
8231
|
+
const publishable = event.type !== "spawned" || activity === "running";
|
|
8232
|
+
if (publishable && previouslyPublished !== activity) {
|
|
7862
8233
|
this.opts.onAgentActivity({ agentId, state: activity });
|
|
8234
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
7863
8235
|
}
|
|
7864
8236
|
}
|
|
7865
8237
|
}
|
|
@@ -8024,6 +8396,47 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8024
8396
|
case "terminate_stalled": {
|
|
8025
8397
|
const session = this.sessions.get(effect.agentId);
|
|
8026
8398
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
8399
|
+
const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
|
|
8400
|
+
if (effect.type === "terminate_stalled" && effect.recordSessionId) {
|
|
8401
|
+
const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
|
|
8402
|
+
if (persisted === false) {
|
|
8403
|
+
this.dispatch({
|
|
8404
|
+
type: "stall_control_failed",
|
|
8405
|
+
agentId: effect.agentId,
|
|
8406
|
+
sessionId: effect.recordSessionId,
|
|
8407
|
+
transition: "attempt"
|
|
8408
|
+
});
|
|
8409
|
+
this.log.error("stall recovery attempt was not persisted; termination deferred", {
|
|
8410
|
+
agentId: effect.agentId,
|
|
8411
|
+
sessionId: effect.recordSessionId
|
|
8412
|
+
});
|
|
8413
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
|
|
8414
|
+
break;
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8417
|
+
if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
|
|
8418
|
+
const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
|
|
8419
|
+
if (!persisted) {
|
|
8420
|
+
this.dispatch({
|
|
8421
|
+
type: "stall_control_failed",
|
|
8422
|
+
agentId: effect.agentId,
|
|
8423
|
+
sessionId: effect.forgetSessionId,
|
|
8424
|
+
transition: "fence"
|
|
8425
|
+
});
|
|
8426
|
+
this.log.error("repeated-session fence was not persisted; termination deferred", {
|
|
8427
|
+
agentId: effect.agentId,
|
|
8428
|
+
sessionId: effect.forgetSessionId
|
|
8429
|
+
});
|
|
8430
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
|
|
8431
|
+
break;
|
|
8432
|
+
}
|
|
8433
|
+
if (spawnState)
|
|
8434
|
+
spawnState.discardEvents = true;
|
|
8435
|
+
this.log.warn("repeatedly stalled backend session fenced", {
|
|
8436
|
+
agentId: effect.agentId,
|
|
8437
|
+
sessionId: effect.forgetSessionId
|
|
8438
|
+
});
|
|
8439
|
+
}
|
|
8027
8440
|
if (effect.type === "terminate_stalled" && spawnState) {
|
|
8028
8441
|
this.closeTurn(spawnState, spawnState.activeSpan, {
|
|
8029
8442
|
event: "turn_abort",
|
|
@@ -8042,10 +8455,27 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8042
8455
|
if (spawnState) {
|
|
8043
8456
|
spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
|
|
8044
8457
|
}
|
|
8045
|
-
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
|
|
8458
|
+
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
|
|
8046
8459
|
this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
|
|
8047
8460
|
break;
|
|
8048
8461
|
}
|
|
8462
|
+
case "clear_stall_recovery": {
|
|
8463
|
+
const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
|
|
8464
|
+
if (persisted === false) {
|
|
8465
|
+
this.dispatch({
|
|
8466
|
+
type: "stall_control_failed",
|
|
8467
|
+
agentId: effect.agentId,
|
|
8468
|
+
sessionId: effect.sessionId,
|
|
8469
|
+
transition: "clear"
|
|
8470
|
+
});
|
|
8471
|
+
this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
|
|
8472
|
+
agentId: effect.agentId,
|
|
8473
|
+
sessionId: effect.sessionId
|
|
8474
|
+
});
|
|
8475
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
|
|
8476
|
+
}
|
|
8477
|
+
break;
|
|
8478
|
+
}
|
|
8049
8479
|
case "expire_admission": {
|
|
8050
8480
|
const owner = this.activeSpawnState.get(effect.agentId);
|
|
8051
8481
|
if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
|
|
@@ -8068,6 +8498,26 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8068
8498
|
mode: effect.mode
|
|
8069
8499
|
});
|
|
8070
8500
|
break;
|
|
8501
|
+
case "reset_idle_session": {
|
|
8502
|
+
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
8503
|
+
const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
|
|
8504
|
+
if (!persisted) {
|
|
8505
|
+
this.log.error("idle session reset barrier was not persisted; reset deferred", {
|
|
8506
|
+
agentId: effect.agentId,
|
|
8507
|
+
sessionId: effect.sessionId
|
|
8508
|
+
});
|
|
8509
|
+
this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
|
|
8510
|
+
break;
|
|
8511
|
+
}
|
|
8512
|
+
if (spawnState)
|
|
8513
|
+
spawnState.discardEvents = true;
|
|
8514
|
+
this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
|
|
8515
|
+
this.log.info("idle agent session reset", {
|
|
8516
|
+
agentId: effect.agentId,
|
|
8517
|
+
sessionId: effect.sessionId
|
|
8518
|
+
});
|
|
8519
|
+
break;
|
|
8520
|
+
}
|
|
8071
8521
|
case "force_exit": {
|
|
8072
8522
|
const session = this.sessions.get(effect.agentId);
|
|
8073
8523
|
const state = this.activeSpawnState.get(effect.agentId);
|
|
@@ -8100,8 +8550,8 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8100
8550
|
}
|
|
8101
8551
|
}
|
|
8102
8552
|
}
|
|
8103
|
-
logSessionEnded(agentId, reason) {
|
|
8104
|
-
this.log.info("agent session ended", { agentId, sessionId
|
|
8553
|
+
logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
|
|
8554
|
+
this.log.info("agent session ended", { agentId, sessionId, reason });
|
|
8105
8555
|
}
|
|
8106
8556
|
doSpawn(agentId, messages, resumeSessionId) {
|
|
8107
8557
|
const [first, ...pending] = messages;
|
|
@@ -8126,7 +8576,13 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8126
8576
|
mode: { kind: "default" }
|
|
8127
8577
|
};
|
|
8128
8578
|
const provider = runtimeConfig.runtime;
|
|
8129
|
-
const
|
|
8579
|
+
const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
|
|
8580
|
+
const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
|
|
8581
|
+
const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
|
|
8582
|
+
const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
|
|
8583
|
+
const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
|
|
8584
|
+
const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
|
|
8585
|
+
const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
|
|
8130
8586
|
const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
|
|
8131
8587
|
const agentName = runtimeConfig.agentName ?? base.config?.agentName;
|
|
8132
8588
|
const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
|
|
@@ -8157,12 +8613,15 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8157
8613
|
handshakeTimer: null,
|
|
8158
8614
|
torndown: false,
|
|
8159
8615
|
superseded: false,
|
|
8616
|
+
discardEvents: false,
|
|
8617
|
+
stalledSessionIdAtLaunch,
|
|
8160
8618
|
spawnFailureReason: null,
|
|
8161
8619
|
terminationSemantics: null,
|
|
8162
8620
|
spawnOrdinal: this.nextSpawnOrdinal++,
|
|
8163
8621
|
launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
|
|
8164
8622
|
nextTurnOrdinal: 1,
|
|
8165
8623
|
activeSpan: null,
|
|
8624
|
+
timelineTurnOwner: null,
|
|
8166
8625
|
pendingDeliverySpans: new Map
|
|
8167
8626
|
};
|
|
8168
8627
|
const previousOwner = this.activeSpawnState.get(agentId);
|
|
@@ -8213,7 +8672,6 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8213
8672
|
this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
|
|
8214
8673
|
}
|
|
8215
8674
|
}
|
|
8216
|
-
this.flushThinkingAudit(agentId);
|
|
8217
8675
|
if (state.sessionInstanceId) {
|
|
8218
8676
|
this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
|
|
8219
8677
|
}
|
|
@@ -8238,10 +8696,23 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8238
8696
|
const onEvent = (event) => {
|
|
8239
8697
|
if (state.torndown)
|
|
8240
8698
|
return;
|
|
8699
|
+
if (state.discardEvents) {
|
|
8700
|
+
this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
|
|
8701
|
+
return;
|
|
8702
|
+
}
|
|
8241
8703
|
if (event.type === "session_failed" && !state.hasEstablished) {
|
|
8242
8704
|
reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
|
|
8243
8705
|
}
|
|
8244
8706
|
if (event.type === "session_started") {
|
|
8707
|
+
const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
|
|
8708
|
+
if (persisted === false) {
|
|
8709
|
+
state.discardEvents = true;
|
|
8710
|
+
reportSpawnFailure("resume_control_update_failed", {
|
|
8711
|
+
message: "Backend session rejected because resume control could not be persisted"
|
|
8712
|
+
});
|
|
8713
|
+
state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
|
|
8714
|
+
return;
|
|
8715
|
+
}
|
|
8245
8716
|
state.hasEstablished = true;
|
|
8246
8717
|
clearHandshakeTimer();
|
|
8247
8718
|
this.opts.onRuntimeSessionEstablished?.(driver.id);
|
|
@@ -8260,7 +8731,8 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8260
8731
|
type: "attach_session",
|
|
8261
8732
|
agentId,
|
|
8262
8733
|
sessionInstanceId: session.sessionInstanceId,
|
|
8263
|
-
nowMs: this.now()
|
|
8734
|
+
nowMs: this.now(),
|
|
8735
|
+
turnSilence: session.snapshot().diagnostics?.turnSilence
|
|
8264
8736
|
}, state);
|
|
8265
8737
|
previousOwner?.pendingDeliverySpans.clear();
|
|
8266
8738
|
(async () => {
|
|
@@ -8408,26 +8880,6 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8408
8880
|
this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
|
|
8409
8881
|
}
|
|
8410
8882
|
}
|
|
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
8883
|
onAgentEvent(agentId, event, runtimeId, owner) {
|
|
8432
8884
|
if (event.type === "session_closed")
|
|
8433
8885
|
return;
|
|
@@ -8460,32 +8912,43 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8460
8912
|
}
|
|
8461
8913
|
}
|
|
8462
8914
|
if (this.opts.onBotAuditEvent) {
|
|
8463
|
-
if (event.type === "
|
|
8464
|
-
|
|
8465
|
-
|
|
8915
|
+
if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
|
|
8916
|
+
const { text, truncated, chars } = truncateThinking(event.text);
|
|
8917
|
+
try {
|
|
8918
|
+
this.opts.onBotAuditEvent(agentId, {
|
|
8919
|
+
kind: "thinking",
|
|
8920
|
+
payload: { text, truncated: truncated || event.truncated, chars }
|
|
8921
|
+
}, {
|
|
8922
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8923
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
8924
|
+
});
|
|
8925
|
+
} catch (err) {
|
|
8926
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
8466
8927
|
}
|
|
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
|
-
}
|
|
8928
|
+
}
|
|
8929
|
+
if (event.type === "tool_started") {
|
|
8930
|
+
const audit = extractToolAudit(event.name, event.input);
|
|
8931
|
+
if (!audit.suppressed) {
|
|
8932
|
+
const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
|
|
8933
|
+
try {
|
|
8934
|
+
this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
|
|
8935
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8936
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
8937
|
+
});
|
|
8938
|
+
} catch (err) {
|
|
8939
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
8481
8940
|
}
|
|
8482
8941
|
}
|
|
8483
8942
|
}
|
|
8484
8943
|
}
|
|
8485
8944
|
if (event.type === "session_started") {
|
|
8486
|
-
this.dispatch({
|
|
8945
|
+
this.dispatch({
|
|
8946
|
+
type: "backend_session",
|
|
8947
|
+
agentId,
|
|
8948
|
+
sessionId: event.backendSessionId,
|
|
8949
|
+
stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
|
|
8950
|
+
}, owner);
|
|
8487
8951
|
this.liveSessions.set(agentId, event.backendSessionId);
|
|
8488
|
-
this.opts.timeline?.setSession(agentId, event.backendSessionId);
|
|
8489
8952
|
this.opts.onAgentSession?.({
|
|
8490
8953
|
agentId,
|
|
8491
8954
|
sessionId: event.backendSessionId,
|
|
@@ -8497,8 +8960,20 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8497
8960
|
runtime: runtimeId
|
|
8498
8961
|
});
|
|
8499
8962
|
}
|
|
8500
|
-
if (event.type === "
|
|
8501
|
-
|
|
8963
|
+
if (event.type === "turn_started") {
|
|
8964
|
+
const timelineTurnOwner = {
|
|
8965
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
8966
|
+
rootTurnId: event.turnId,
|
|
8967
|
+
barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
|
|
8968
|
+
};
|
|
8969
|
+
owner.timelineTurnOwner = timelineTurnOwner;
|
|
8970
|
+
this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
|
|
8971
|
+
}
|
|
8972
|
+
if (event.type === "assistant_message_completed" && event.text.length > 0) {
|
|
8973
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
8974
|
+
if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
|
|
8975
|
+
this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
|
|
8976
|
+
}
|
|
8502
8977
|
}
|
|
8503
8978
|
if (event.type === "command_queued")
|
|
8504
8979
|
this.acknowledgePendingDelivery(owner, event.commandId);
|
|
@@ -8516,9 +8991,10 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8516
8991
|
switch (event.type) {
|
|
8517
8992
|
case "turn_started":
|
|
8518
8993
|
return { type: "turn_started", turnId: event.turnId, commandIds: event.commandIds };
|
|
8519
|
-
case "
|
|
8520
|
-
case "
|
|
8521
|
-
|
|
8994
|
+
case "work_heartbeat":
|
|
8995
|
+
case "assistant_reasoning_completed":
|
|
8996
|
+
case "assistant_message_completed":
|
|
8997
|
+
return { type: "turn_work", turnId: event.turnId };
|
|
8522
8998
|
case "tool_started":
|
|
8523
8999
|
return { type: "turn_tool_started", turnId: event.turnId };
|
|
8524
9000
|
case "tool_finished":
|
|
@@ -8545,37 +9021,55 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8545
9021
|
if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
|
|
8546
9022
|
this.openTurn(owner);
|
|
8547
9023
|
}
|
|
8548
|
-
const
|
|
9024
|
+
const nativeSignal = (() => {
|
|
8549
9025
|
switch (event.type) {
|
|
8550
|
-
case "
|
|
8551
|
-
return "
|
|
8552
|
-
case "
|
|
8553
|
-
return
|
|
8554
|
-
|
|
8555
|
-
|
|
9026
|
+
case "turn_started":
|
|
9027
|
+
return { kind: "turn_started", phase: "inference", turnId: event.turnId };
|
|
9028
|
+
case "backend_turn_started":
|
|
9029
|
+
return {
|
|
9030
|
+
kind: "backend_turn_started",
|
|
9031
|
+
phase: "inference",
|
|
9032
|
+
turnId: event.turnId,
|
|
9033
|
+
backendTurnId: event.backendTurnId
|
|
9034
|
+
};
|
|
9035
|
+
case "assistant_reasoning_completed":
|
|
9036
|
+
return { kind: "thinking", phase: "inference", turnId: event.turnId };
|
|
9037
|
+
case "assistant_message_completed":
|
|
9038
|
+
return { kind: "text", phase: "inference", turnId: event.turnId };
|
|
9039
|
+
case "work_heartbeat":
|
|
9040
|
+
return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
|
|
8556
9041
|
case "tool_started":
|
|
8557
|
-
return "tool_call";
|
|
9042
|
+
return { kind: "tool_call", phase: "tool", turnId: event.turnId };
|
|
8558
9043
|
case "tool_finished":
|
|
8559
|
-
return "tool_output";
|
|
8560
|
-
case "
|
|
8561
|
-
|
|
8562
|
-
case "
|
|
8563
|
-
case "
|
|
8564
|
-
|
|
9044
|
+
return { kind: "tool_output", phase: "inference", turnId: event.turnId };
|
|
9045
|
+
case "compaction_started":
|
|
9046
|
+
case "compaction_finished":
|
|
9047
|
+
case "review_started":
|
|
9048
|
+
case "review_finished":
|
|
9049
|
+
case "internal_progress":
|
|
9050
|
+
return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
|
|
9051
|
+
case "recovery":
|
|
9052
|
+
return event.turnId ? {
|
|
9053
|
+
kind: "recovery",
|
|
9054
|
+
phase: event.stage === "retrying" ? "recovery" : "inference",
|
|
9055
|
+
recoveryStage: event.stage,
|
|
9056
|
+
turnId: event.turnId
|
|
9057
|
+
} : null;
|
|
8565
9058
|
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";
|
|
9059
|
+
return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
|
|
8574
9060
|
default:
|
|
8575
|
-
return
|
|
9061
|
+
return null;
|
|
8576
9062
|
}
|
|
8577
9063
|
})();
|
|
8578
|
-
|
|
9064
|
+
if (nativeSignal) {
|
|
9065
|
+
this.dispatch({
|
|
9066
|
+
type: "runtime_signal",
|
|
9067
|
+
agentId,
|
|
9068
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
9069
|
+
...nativeSignal,
|
|
9070
|
+
nowMs: this.now()
|
|
9071
|
+
}, owner);
|
|
9072
|
+
}
|
|
8579
9073
|
if (event.type === "turn_completed") {
|
|
8580
9074
|
this.logSessionEnded(agentId, "turn_end");
|
|
8581
9075
|
const marker = this.nonCleanEndMarker.get(agentId);
|
|
@@ -25323,6 +25817,7 @@ Then read @memory.md and your .context_timeline for durable context, and pull `
|
|
|
25323
25817
|
class AgentRouter {
|
|
25324
25818
|
opts;
|
|
25325
25819
|
running = new Set;
|
|
25820
|
+
nextWakeAdmissionOrdinal = 1;
|
|
25326
25821
|
runtimes = new Map;
|
|
25327
25822
|
pendingResend = false;
|
|
25328
25823
|
scheduleResend;
|
|
@@ -25483,7 +25978,7 @@ class AgentRouter {
|
|
|
25483
25978
|
this.opts.typingTracker?.add(cmd.agentId, channelScope);
|
|
25484
25979
|
const text2 = (this.opts.formatUnreadNoticeText ?? defaultFormatUnreadNoticeText)(cmd.unreadNotice);
|
|
25485
25980
|
const producedEffect = this.opts.manager.deliver(cmd.agentId, {
|
|
25486
|
-
id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}`,
|
|
25981
|
+
id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}:admission:${this.nextWakeAdmissionOrdinal++}`,
|
|
25487
25982
|
seq: cmd.unreadNotice.latestSeq,
|
|
25488
25983
|
text: text2
|
|
25489
25984
|
});
|
|
@@ -26911,7 +27406,7 @@ class WsControlChannel {
|
|
|
26911
27406
|
}
|
|
26912
27407
|
// src/timeline/timeline.ts
|
|
26913
27408
|
import * as fs7 from "node:fs";
|
|
26914
|
-
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
27409
|
+
import { createHash as createHash4, randomBytes as randomBytes4 } from "node:crypto";
|
|
26915
27410
|
import { basename, dirname as dirname3, join as join10 } from "node:path";
|
|
26916
27411
|
|
|
26917
27412
|
// src/timeline/filelock.ts
|
|
@@ -26978,17 +27473,25 @@ function reclaim(lockPath) {
|
|
|
26978
27473
|
var TIMELINE_MAX_BYTES = 1048576;
|
|
26979
27474
|
var TIMELINE_READ_CHUNK_BYTES = 65536;
|
|
26980
27475
|
var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
27476
|
+
var RESUME_CONTROL_FILENAME = ".resume-control.json";
|
|
27477
|
+
var RESUME_CONTROL_MAX_BYTES = 4096;
|
|
27478
|
+
var EMPTY_RESUME_CONTROL = {
|
|
27479
|
+
version: 1,
|
|
27480
|
+
attemptedSessionId: null,
|
|
27481
|
+
fencedSessionId: null,
|
|
27482
|
+
fullBarrier: null
|
|
27483
|
+
};
|
|
26981
27484
|
function isBarrier(entry) {
|
|
26982
|
-
return entry.system
|
|
27485
|
+
return entry.system !== undefined;
|
|
26983
27486
|
}
|
|
26984
27487
|
function canonicalTimelineEntry(value) {
|
|
26985
27488
|
if (!value || typeof value !== "object")
|
|
26986
27489
|
return null;
|
|
26987
27490
|
const entry = value;
|
|
26988
27491
|
if (entry.system) {
|
|
26989
|
-
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
|
|
27492
|
+
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
27493
|
return null;
|
|
26991
|
-
return createSystemEntry(entry.system.type, entry.system.time);
|
|
27494
|
+
return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
|
|
26992
27495
|
}
|
|
26993
27496
|
if (entry.session_id !== null && typeof entry.session_id !== "string")
|
|
26994
27497
|
return null;
|
|
@@ -27006,13 +27509,60 @@ function canonicalTimelineEntry(value) {
|
|
|
27006
27509
|
};
|
|
27007
27510
|
}
|
|
27008
27511
|
function timelineLine(entry) {
|
|
27009
|
-
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
27512
|
+
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
27513
|
const text2 = JSON.stringify(boundedEntry);
|
|
27011
27514
|
const bytes = Buffer.byteLength(text2, "utf8") + 1;
|
|
27012
27515
|
if (bytes > TIMELINE_MAX_BYTES)
|
|
27013
27516
|
return null;
|
|
27014
27517
|
return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
|
|
27015
27518
|
}
|
|
27519
|
+
function timelineRowHash(line) {
|
|
27520
|
+
return createHash4("sha256").update(line.text, "utf8").digest("hex");
|
|
27521
|
+
}
|
|
27522
|
+
function timelineFileGeneration(filePath, lines) {
|
|
27523
|
+
try {
|
|
27524
|
+
const stat = fs7.lstatSync(filePath, { bigint: true });
|
|
27525
|
+
if (!stat.isFile())
|
|
27526
|
+
return null;
|
|
27527
|
+
const digest = createHash4("sha256");
|
|
27528
|
+
digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
|
|
27529
|
+
`);
|
|
27530
|
+
for (const line of lines)
|
|
27531
|
+
digest.update(line.text, "utf8").update(`
|
|
27532
|
+
`);
|
|
27533
|
+
return digest.digest("hex");
|
|
27534
|
+
} catch {
|
|
27535
|
+
return null;
|
|
27536
|
+
}
|
|
27537
|
+
}
|
|
27538
|
+
function handleFor(filename, generation, lines, rowOrdinal) {
|
|
27539
|
+
return {
|
|
27540
|
+
filename,
|
|
27541
|
+
fileGeneration: generation,
|
|
27542
|
+
rowOrdinal,
|
|
27543
|
+
expectedHash: timelineRowHash(lines[rowOrdinal])
|
|
27544
|
+
};
|
|
27545
|
+
}
|
|
27546
|
+
function refreshTimelineEntryHandle(handle, rewrite) {
|
|
27547
|
+
if (handle.filename !== rewrite.filename)
|
|
27548
|
+
return handle;
|
|
27549
|
+
if (handle.fileGeneration !== rewrite.previousFileGeneration)
|
|
27550
|
+
return null;
|
|
27551
|
+
if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
|
|
27552
|
+
return null;
|
|
27553
|
+
const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
|
|
27554
|
+
if (rowOrdinal === null || rowOrdinal === undefined)
|
|
27555
|
+
return null;
|
|
27556
|
+
const expectedHash = rewrite.rowHashes[rowOrdinal];
|
|
27557
|
+
if (!expectedHash)
|
|
27558
|
+
return null;
|
|
27559
|
+
return {
|
|
27560
|
+
filename: handle.filename,
|
|
27561
|
+
fileGeneration: rewrite.fileGeneration,
|
|
27562
|
+
rowOrdinal,
|
|
27563
|
+
expectedHash
|
|
27564
|
+
};
|
|
27565
|
+
}
|
|
27016
27566
|
function compactLines(input) {
|
|
27017
27567
|
let head = 0;
|
|
27018
27568
|
let bytes = input.reduce((total, line) => total + line.bytes, 0);
|
|
@@ -27224,11 +27774,37 @@ function atomicReplaceTimeline(filePath, lines) {
|
|
|
27224
27774
|
} catch {}
|
|
27225
27775
|
}
|
|
27226
27776
|
}
|
|
27227
|
-
function
|
|
27777
|
+
function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
|
|
27778
|
+
const previousFileGeneration = timelineFileGeneration(filePath, existing);
|
|
27779
|
+
const previousRowHashes = existing.map(timelineRowHash);
|
|
27228
27780
|
const compacted = compactLines(input);
|
|
27229
|
-
|
|
27230
|
-
|
|
27231
|
-
|
|
27781
|
+
const targetOrdinal = compacted.indexOf(required2);
|
|
27782
|
+
if (targetOrdinal < 0)
|
|
27783
|
+
return { status: "rejected", reason: "evicted" };
|
|
27784
|
+
if (!atomicReplaceTimeline(filePath, compacted))
|
|
27785
|
+
return { status: "rejected", reason: "write" };
|
|
27786
|
+
const fileGeneration = timelineFileGeneration(filePath, compacted);
|
|
27787
|
+
if (!fileGeneration)
|
|
27788
|
+
return { status: "rejected", reason: "write" };
|
|
27789
|
+
const rowOrdinals = existing.map((line, oldOrdinal) => {
|
|
27790
|
+
if (replacement?.oldOrdinal === oldOrdinal)
|
|
27791
|
+
return compacted.indexOf(replacement.line);
|
|
27792
|
+
const nextOrdinal = compacted.indexOf(line);
|
|
27793
|
+
return nextOrdinal < 0 ? null : nextOrdinal;
|
|
27794
|
+
});
|
|
27795
|
+
const rewrite = {
|
|
27796
|
+
filename,
|
|
27797
|
+
previousFileGeneration,
|
|
27798
|
+
fileGeneration,
|
|
27799
|
+
previousRowHashes,
|
|
27800
|
+
rowOrdinals,
|
|
27801
|
+
rowHashes: compacted.map(timelineRowHash)
|
|
27802
|
+
};
|
|
27803
|
+
return {
|
|
27804
|
+
status: "written",
|
|
27805
|
+
rewrite,
|
|
27806
|
+
handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
|
|
27807
|
+
};
|
|
27232
27808
|
}
|
|
27233
27809
|
function filenameForDate(date5) {
|
|
27234
27810
|
const y = date5.getFullYear();
|
|
@@ -27260,114 +27836,181 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
27260
27836
|
}
|
|
27261
27837
|
return entries;
|
|
27262
27838
|
}
|
|
27263
|
-
function
|
|
27839
|
+
function readResumeControlState(timelineDir) {
|
|
27840
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27841
|
+
return { kind: "missing" };
|
|
27842
|
+
const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27843
|
+
let source;
|
|
27844
|
+
try {
|
|
27845
|
+
source = fs7.lstatSync(filePath);
|
|
27846
|
+
} catch (error51) {
|
|
27847
|
+
return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
|
|
27848
|
+
}
|
|
27849
|
+
if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
|
|
27850
|
+
return { kind: "invalid" };
|
|
27851
|
+
}
|
|
27852
|
+
let fd = null;
|
|
27853
|
+
try {
|
|
27854
|
+
fd = fs7.openSync(filePath, fs7.constants.O_RDONLY | (fs7.constants.O_NOFOLLOW ?? 0));
|
|
27855
|
+
const stat = fs7.fstatSync(fd);
|
|
27856
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
|
|
27857
|
+
return { kind: "invalid" };
|
|
27858
|
+
}
|
|
27859
|
+
const bounded = Buffer.allocUnsafe(stat.size + 1);
|
|
27860
|
+
let bytesRead = 0;
|
|
27861
|
+
while (bytesRead < bounded.length) {
|
|
27862
|
+
const count = fs7.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
|
|
27863
|
+
if (count <= 0)
|
|
27864
|
+
break;
|
|
27865
|
+
bytesRead += count;
|
|
27866
|
+
}
|
|
27867
|
+
if (bytesRead !== stat.size)
|
|
27868
|
+
return { kind: "invalid" };
|
|
27869
|
+
const raw = bounded.subarray(0, bytesRead).toString("utf8");
|
|
27870
|
+
const value = JSON.parse(raw);
|
|
27871
|
+
const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
|
|
27872
|
+
if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
|
|
27873
|
+
return { kind: "invalid" };
|
|
27874
|
+
return {
|
|
27875
|
+
kind: "state",
|
|
27876
|
+
state: {
|
|
27877
|
+
version: 1,
|
|
27878
|
+
attemptedSessionId: value.attemptedSessionId,
|
|
27879
|
+
fencedSessionId: value.fencedSessionId,
|
|
27880
|
+
fullBarrier: value.fullBarrier
|
|
27881
|
+
}
|
|
27882
|
+
};
|
|
27883
|
+
} catch {
|
|
27884
|
+
return { kind: "invalid" };
|
|
27885
|
+
} finally {
|
|
27886
|
+
if (fd !== null) {
|
|
27887
|
+
try {
|
|
27888
|
+
fs7.closeSync(fd);
|
|
27889
|
+
} catch {}
|
|
27890
|
+
}
|
|
27891
|
+
}
|
|
27892
|
+
}
|
|
27893
|
+
function updateResumeControlState(timelineDir, update) {
|
|
27264
27894
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27265
27895
|
return false;
|
|
27266
|
-
const
|
|
27267
|
-
const filePath = join10(timelineDir, filename);
|
|
27268
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
27896
|
+
const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27269
27897
|
if (!acquireLock(lockPath))
|
|
27270
27898
|
return false;
|
|
27271
27899
|
try {
|
|
27272
|
-
const
|
|
27273
|
-
const
|
|
27274
|
-
|
|
27900
|
+
const current = readResumeControlState(timelineDir);
|
|
27901
|
+
const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
|
|
27902
|
+
const next = update({ ...base });
|
|
27903
|
+
const canonical = {
|
|
27904
|
+
version: 1,
|
|
27905
|
+
attemptedSessionId: next.attemptedSessionId,
|
|
27906
|
+
fencedSessionId: next.fencedSessionId,
|
|
27907
|
+
fullBarrier: next.fullBarrier
|
|
27908
|
+
};
|
|
27909
|
+
const body = JSON.stringify(canonical) + `
|
|
27910
|
+
`;
|
|
27911
|
+
if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
|
|
27275
27912
|
return false;
|
|
27276
|
-
|
|
27913
|
+
const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27914
|
+
const tempPath = join10(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
|
|
27915
|
+
let fd = null;
|
|
27916
|
+
try {
|
|
27917
|
+
fd = fs7.openSync(tempPath, "wx", 384);
|
|
27918
|
+
fs7.writeFileSync(fd, body, "utf8");
|
|
27919
|
+
fs7.fsyncSync(fd);
|
|
27920
|
+
fs7.closeSync(fd);
|
|
27921
|
+
fd = null;
|
|
27922
|
+
fs7.renameSync(tempPath, filePath);
|
|
27923
|
+
return true;
|
|
27924
|
+
} finally {
|
|
27925
|
+
if (fd !== null) {
|
|
27926
|
+
try {
|
|
27927
|
+
fs7.closeSync(fd);
|
|
27928
|
+
} catch {}
|
|
27929
|
+
}
|
|
27930
|
+
try {
|
|
27931
|
+
fs7.unlinkSync(tempPath);
|
|
27932
|
+
} catch {}
|
|
27933
|
+
}
|
|
27277
27934
|
} catch {
|
|
27278
27935
|
return false;
|
|
27279
27936
|
} finally {
|
|
27280
27937
|
releaseLock(lockPath);
|
|
27281
27938
|
}
|
|
27282
27939
|
}
|
|
27283
|
-
function
|
|
27940
|
+
function appendTrackedEntry(timelineDir, entry, now = new Date) {
|
|
27284
27941
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27285
|
-
return
|
|
27942
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27286
27943
|
const filename = filenameForDate(now);
|
|
27287
27944
|
const filePath = join10(timelineDir, filename);
|
|
27288
27945
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
27289
|
-
|
|
27290
|
-
|
|
27946
|
+
try {
|
|
27947
|
+
if (!acquireLock(lockPath))
|
|
27948
|
+
return { status: "rejected", reason: "lock" };
|
|
27949
|
+
} catch {
|
|
27950
|
+
return { status: "rejected", reason: "write" };
|
|
27951
|
+
}
|
|
27291
27952
|
try {
|
|
27292
27953
|
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
27954
|
const required2 = timelineLine(entry);
|
|
27955
|
+
if (!existing)
|
|
27956
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27311
27957
|
if (!required2)
|
|
27312
|
-
return
|
|
27313
|
-
return
|
|
27958
|
+
return { status: "rejected", reason: "oversized" };
|
|
27959
|
+
return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
|
|
27314
27960
|
} catch {
|
|
27315
|
-
return
|
|
27961
|
+
return { status: "rejected", reason: "write" };
|
|
27316
27962
|
} finally {
|
|
27317
27963
|
releaseLock(lockPath);
|
|
27318
27964
|
}
|
|
27319
27965
|
}
|
|
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);
|
|
27966
|
+
function updateTrackedEntry(timelineDir, handle, update) {
|
|
27967
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27968
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27969
|
+
if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename(handle.filename) !== handle.filename) {
|
|
27970
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27971
|
+
}
|
|
27972
|
+
const filePath = join10(timelineDir, handle.filename);
|
|
27973
|
+
const lockPath = lockPathFor(timelineDir, handle.filename);
|
|
27974
|
+
try {
|
|
27339
27975
|
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
|
-
|
|
27976
|
+
return { status: "rejected", reason: "lock" };
|
|
27977
|
+
} catch {
|
|
27978
|
+
return { status: "rejected", reason: "write" };
|
|
27979
|
+
}
|
|
27980
|
+
try {
|
|
27981
|
+
const existing = scanTimelineFile(filePath);
|
|
27982
|
+
if (!existing)
|
|
27983
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27984
|
+
if (existing.length === 0)
|
|
27985
|
+
return { status: "rejected", reason: "missing" };
|
|
27986
|
+
const generation = timelineFileGeneration(filePath, existing);
|
|
27987
|
+
if (!generation || generation !== handle.fileGeneration) {
|
|
27988
|
+
return { status: "rejected", reason: "generation" };
|
|
27989
|
+
}
|
|
27990
|
+
const captured = existing[handle.rowOrdinal];
|
|
27991
|
+
if (!captured)
|
|
27992
|
+
return { status: "rejected", reason: "ordinal" };
|
|
27993
|
+
if (timelineRowHash(captured) !== handle.expectedHash) {
|
|
27994
|
+
return { status: "rejected", reason: "hash" };
|
|
27995
|
+
}
|
|
27996
|
+
if (captured.entry.system)
|
|
27997
|
+
return { status: "rejected", reason: "system" };
|
|
27998
|
+
const nextEntry = update({
|
|
27999
|
+
...captured.entry,
|
|
28000
|
+
messages: [...captured.entry.messages],
|
|
28001
|
+
agent_responses: [...captured.entry.agent_responses]
|
|
28002
|
+
});
|
|
28003
|
+
const replacement = timelineLine(nextEntry);
|
|
28004
|
+
if (!replacement)
|
|
28005
|
+
return { status: "rejected", reason: "oversized" };
|
|
28006
|
+
const input = [...existing];
|
|
28007
|
+
input[handle.rowOrdinal] = replacement;
|
|
28008
|
+
return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
|
|
28009
|
+
} catch {
|
|
28010
|
+
return { status: "rejected", reason: "write" };
|
|
28011
|
+
} finally {
|
|
28012
|
+
releaseLock(lockPath);
|
|
27369
28013
|
}
|
|
27370
|
-
return "missing";
|
|
27371
28014
|
}
|
|
27372
28015
|
function yieldToEventLoop() {
|
|
27373
28016
|
return new Promise((resolve3) => setImmediate(resolve3));
|
|
@@ -27431,84 +28074,562 @@ function createTimelineEntry(fields) {
|
|
|
27431
28074
|
provider: fields.provider ?? null
|
|
27432
28075
|
};
|
|
27433
28076
|
}
|
|
27434
|
-
function createSystemEntry(type, time3) {
|
|
28077
|
+
function createSystemEntry(type, time3, backendSessionId) {
|
|
27435
28078
|
return {
|
|
27436
28079
|
session_id: null,
|
|
27437
28080
|
messages: [],
|
|
27438
28081
|
agent_responses: [],
|
|
27439
28082
|
provider: null,
|
|
27440
|
-
system: {
|
|
28083
|
+
system: {
|
|
28084
|
+
type,
|
|
28085
|
+
time: time3,
|
|
28086
|
+
...backendSessionId ? { backend_session_id: backendSessionId } : {}
|
|
28087
|
+
}
|
|
27441
28088
|
};
|
|
27442
28089
|
}
|
|
27443
|
-
function
|
|
28090
|
+
function resolveResumableSession(rows, provider) {
|
|
28091
|
+
let candidateSessionId = null;
|
|
28092
|
+
let recoveryMarkerSeen = false;
|
|
28093
|
+
let stalledSessionId = null;
|
|
28094
|
+
let fencedSessionId = null;
|
|
27444
28095
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
27445
28096
|
const e = rows[i];
|
|
27446
|
-
if (e.system
|
|
27447
|
-
|
|
28097
|
+
if (e.system) {
|
|
28098
|
+
if (e.system.type === "stall_recovery_attempt") {
|
|
28099
|
+
if (!recoveryMarkerSeen) {
|
|
28100
|
+
recoveryMarkerSeen = true;
|
|
28101
|
+
stalledSessionId = e.system.backend_session_id ?? null;
|
|
28102
|
+
}
|
|
28103
|
+
continue;
|
|
28104
|
+
}
|
|
28105
|
+
if (e.system.type === "stall_recovery_clear") {
|
|
28106
|
+
if (!recoveryMarkerSeen)
|
|
28107
|
+
recoveryMarkerSeen = true;
|
|
28108
|
+
continue;
|
|
28109
|
+
}
|
|
28110
|
+
if (e.system.type === "stall_recovery" && fencedSessionId === null) {
|
|
28111
|
+
fencedSessionId = e.system.backend_session_id ?? null;
|
|
28112
|
+
}
|
|
28113
|
+
if (candidateSessionId !== null) {
|
|
28114
|
+
return {
|
|
28115
|
+
kind: "session",
|
|
28116
|
+
sessionId: candidateSessionId,
|
|
28117
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
28118
|
+
fencedSessionId
|
|
28119
|
+
};
|
|
28120
|
+
}
|
|
28121
|
+
return {
|
|
28122
|
+
kind: "barrier",
|
|
28123
|
+
type: e.system.type,
|
|
28124
|
+
forgottenSessionId: e.system.backend_session_id ?? null,
|
|
28125
|
+
fencedSessionId
|
|
28126
|
+
};
|
|
28127
|
+
}
|
|
27448
28128
|
if (!e.session_id)
|
|
27449
28129
|
continue;
|
|
27450
28130
|
if (provider && e.provider !== provider)
|
|
27451
28131
|
continue;
|
|
27452
|
-
|
|
28132
|
+
if (candidateSessionId === null) {
|
|
28133
|
+
candidateSessionId = e.session_id;
|
|
28134
|
+
continue;
|
|
28135
|
+
}
|
|
28136
|
+
if (candidateSessionId !== e.session_id)
|
|
28137
|
+
break;
|
|
27453
28138
|
}
|
|
27454
|
-
|
|
28139
|
+
if (candidateSessionId !== null) {
|
|
28140
|
+
return {
|
|
28141
|
+
kind: "session",
|
|
28142
|
+
sessionId: candidateSessionId,
|
|
28143
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
28144
|
+
fencedSessionId
|
|
28145
|
+
};
|
|
28146
|
+
}
|
|
28147
|
+
return { kind: "none", stalledSessionId, fencedSessionId };
|
|
27455
28148
|
}
|
|
27456
28149
|
// src/timeline/recorder.ts
|
|
27457
28150
|
var MAX_AGENT_RESPONSES = 5;
|
|
27458
|
-
|
|
27459
|
-
|
|
27460
|
-
|
|
27461
|
-
|
|
28151
|
+
var MAX_AGENT_RESPONSE_BYTES = 65536;
|
|
28152
|
+
var MAX_PENDING_COMMITS_PER_AGENT = 8;
|
|
28153
|
+
var PENDING_COMMIT_TTL_MS = 15 * 60000;
|
|
28154
|
+
var TRUNCATION_MARKER = `
|
|
28155
|
+
… [truncated]`;
|
|
28156
|
+
function turnKey(agentId, owner) {
|
|
28157
|
+
return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
|
|
28158
|
+
}
|
|
28159
|
+
function sameOwner(left, right) {
|
|
28160
|
+
return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
|
|
28161
|
+
}
|
|
28162
|
+
function utf8Prefix2(text2, maxBytes) {
|
|
28163
|
+
if (maxBytes <= 0)
|
|
28164
|
+
return "";
|
|
28165
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
28166
|
+
return text2;
|
|
28167
|
+
let low = 0;
|
|
28168
|
+
let high = text2.length;
|
|
28169
|
+
while (low < high) {
|
|
28170
|
+
const mid = Math.ceil((low + high) / 2);
|
|
28171
|
+
let end2 = mid;
|
|
28172
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
28173
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
28174
|
+
end2 -= 1;
|
|
28175
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
28176
|
+
low = mid;
|
|
28177
|
+
else
|
|
28178
|
+
high = mid - 1;
|
|
28179
|
+
}
|
|
28180
|
+
let end = low;
|
|
28181
|
+
const code = text2.charCodeAt(end - 1);
|
|
28182
|
+
if (code >= 55296 && code <= 56319)
|
|
28183
|
+
end -= 1;
|
|
28184
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
28185
|
+
end -= 1;
|
|
28186
|
+
return text2.slice(0, end);
|
|
28187
|
+
}
|
|
28188
|
+
function boundedResponse(text2, alreadyTruncated) {
|
|
28189
|
+
const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
|
|
28190
|
+
const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
|
|
28191
|
+
if (!needsTruncation)
|
|
28192
|
+
return text2;
|
|
28193
|
+
return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
|
|
28194
|
+
}
|
|
28195
|
+
function serializedTimelineBytes(entry) {
|
|
28196
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
28197
|
+
}
|
|
28198
|
+
function fitResponsesToRow(entry, responses) {
|
|
28199
|
+
let fitted = [...entry.agent_responses];
|
|
28200
|
+
for (const response of responses) {
|
|
28201
|
+
fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
|
|
28202
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
|
|
28203
|
+
continue;
|
|
28204
|
+
const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
|
|
28205
|
+
let low = 0;
|
|
28206
|
+
let high = truncationBase.length;
|
|
28207
|
+
let replacement = null;
|
|
28208
|
+
while (low <= high) {
|
|
28209
|
+
const mid = Math.floor((low + high) / 2);
|
|
28210
|
+
let end = mid;
|
|
28211
|
+
const code = truncationBase.charCodeAt(end - 1);
|
|
28212
|
+
if (code >= 55296 && code <= 56319)
|
|
28213
|
+
end -= 1;
|
|
28214
|
+
const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
|
|
28215
|
+
const candidate = [...fitted.slice(0, -1), candidateResponse];
|
|
28216
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
|
|
28217
|
+
replacement = candidateResponse;
|
|
28218
|
+
low = mid + 1;
|
|
28219
|
+
} else {
|
|
28220
|
+
high = mid - 1;
|
|
28221
|
+
}
|
|
28222
|
+
}
|
|
28223
|
+
if (replacement === null)
|
|
28224
|
+
return null;
|
|
28225
|
+
fitted[fitted.length - 1] = replacement;
|
|
27462
28226
|
}
|
|
28227
|
+
return fitted;
|
|
27463
28228
|
}
|
|
27464
28229
|
function createTimelineRecorder(opts) {
|
|
27465
28230
|
const now = opts.now ?? (() => new Date);
|
|
27466
28231
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
27467
28232
|
const sessionByAgent = new Map;
|
|
28233
|
+
const resolveForAgent = (agentId, provider) => {
|
|
28234
|
+
const dir = dirFor(agentId);
|
|
28235
|
+
const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
|
|
28236
|
+
const control = readResumeControlState(dir);
|
|
28237
|
+
if (control.kind === "missing")
|
|
28238
|
+
return recent;
|
|
28239
|
+
if (control.kind === "invalid") {
|
|
28240
|
+
return {
|
|
28241
|
+
kind: "barrier",
|
|
28242
|
+
type: "reset_session",
|
|
28243
|
+
forgottenSessionId: null,
|
|
28244
|
+
fencedSessionId: null
|
|
28245
|
+
};
|
|
28246
|
+
}
|
|
28247
|
+
const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
|
|
28248
|
+
if (fullBarrier !== null) {
|
|
28249
|
+
return {
|
|
28250
|
+
kind: "barrier",
|
|
28251
|
+
type: fullBarrier,
|
|
28252
|
+
forgottenSessionId: null,
|
|
28253
|
+
fencedSessionId
|
|
28254
|
+
};
|
|
28255
|
+
}
|
|
28256
|
+
if (recent.kind === "session") {
|
|
28257
|
+
return {
|
|
28258
|
+
kind: "session",
|
|
28259
|
+
sessionId: recent.sessionId,
|
|
28260
|
+
stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
|
|
28261
|
+
fencedSessionId
|
|
28262
|
+
};
|
|
28263
|
+
}
|
|
28264
|
+
if (recent.kind === "barrier") {
|
|
28265
|
+
if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
|
|
28266
|
+
return { ...recent, fencedSessionId };
|
|
28267
|
+
}
|
|
28268
|
+
}
|
|
28269
|
+
return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
|
|
28270
|
+
};
|
|
28271
|
+
const sessionByEpoch = new Map;
|
|
28272
|
+
const barrierByAgent = new Map;
|
|
28273
|
+
const turnsByAgent = new Map;
|
|
28274
|
+
const activeTurnByAgent = new Map;
|
|
28275
|
+
const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
|
|
28276
|
+
const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
|
|
28277
|
+
const statesFor = (agentId) => {
|
|
28278
|
+
let states = turnsByAgent.get(agentId);
|
|
28279
|
+
if (!states) {
|
|
28280
|
+
states = new Map;
|
|
28281
|
+
turnsByAgent.set(agentId, states);
|
|
28282
|
+
}
|
|
28283
|
+
return states;
|
|
28284
|
+
};
|
|
28285
|
+
const diagnostic = (agentId, code, reason) => {
|
|
28286
|
+
try {
|
|
28287
|
+
opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
|
|
28288
|
+
} catch {}
|
|
28289
|
+
};
|
|
28290
|
+
const applyRewrite = (agentId, rewrite) => {
|
|
28291
|
+
for (const state of statesFor(agentId).values()) {
|
|
28292
|
+
if (!state.handle)
|
|
28293
|
+
continue;
|
|
28294
|
+
const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
|
|
28295
|
+
if (refreshed) {
|
|
28296
|
+
state.handle = refreshed;
|
|
28297
|
+
} else if (state.handle.filename === rewrite.filename) {
|
|
28298
|
+
state.handle = undefined;
|
|
28299
|
+
state.rowFenced = true;
|
|
28300
|
+
diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
|
|
28301
|
+
}
|
|
28302
|
+
}
|
|
28303
|
+
};
|
|
28304
|
+
const handleTrackedResult = (agentId, state, result) => {
|
|
28305
|
+
if (result.status === "written") {
|
|
28306
|
+
applyRewrite(agentId, result.rewrite);
|
|
28307
|
+
if (state && result.handle) {
|
|
28308
|
+
state.handle = result.handle;
|
|
28309
|
+
state.rowFenced = false;
|
|
28310
|
+
}
|
|
28311
|
+
return "written";
|
|
28312
|
+
}
|
|
28313
|
+
if (result.reason === "lock" || result.reason === "write")
|
|
28314
|
+
return "retryable";
|
|
28315
|
+
if (state && result.reason !== "oversized")
|
|
28316
|
+
state.rowFenced = true;
|
|
28317
|
+
diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
|
|
28318
|
+
return "terminal";
|
|
28319
|
+
};
|
|
28320
|
+
const tryCommit = (agentId, state) => {
|
|
28321
|
+
if (state.responses.length === 0)
|
|
28322
|
+
return "written";
|
|
28323
|
+
const dir = dirFor(agentId);
|
|
28324
|
+
if (!prepareTimelineDirectory(dir)) {
|
|
28325
|
+
diagnostic(agentId, "timeline_directory_unavailable");
|
|
28326
|
+
return "terminal";
|
|
28327
|
+
}
|
|
28328
|
+
if (state.handle) {
|
|
28329
|
+
const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
|
|
28330
|
+
const fitted2 = fitResponsesToRow(entry2, state.responses);
|
|
28331
|
+
return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
|
|
28332
|
+
});
|
|
28333
|
+
return handleTrackedResult(agentId, state, result2);
|
|
28334
|
+
}
|
|
28335
|
+
if (state.rowFenced || state.pendingMode === "handle")
|
|
28336
|
+
return "terminal";
|
|
28337
|
+
if (state.owner.barrierGeneration !== currentBarrier(agentId))
|
|
28338
|
+
return "terminal";
|
|
28339
|
+
const entry = createTimelineEntry({
|
|
28340
|
+
messages: [],
|
|
28341
|
+
sessionId: state.backendSessionId,
|
|
28342
|
+
provider: state.provider
|
|
28343
|
+
});
|
|
28344
|
+
const fitted = fitResponsesToRow(entry, state.responses);
|
|
28345
|
+
if (!fitted) {
|
|
28346
|
+
diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
|
|
28347
|
+
return "terminal";
|
|
28348
|
+
}
|
|
28349
|
+
entry.agent_responses = fitted;
|
|
28350
|
+
const result = appendTrackedEntry(dir, entry, now());
|
|
28351
|
+
return handleTrackedResult(agentId, state, result);
|
|
28352
|
+
};
|
|
28353
|
+
const deleteState = (agentId, key) => {
|
|
28354
|
+
const states = statesFor(agentId);
|
|
28355
|
+
states.delete(key);
|
|
28356
|
+
if (activeTurnByAgent.get(agentId) === key)
|
|
28357
|
+
activeTurnByAgent.delete(agentId);
|
|
28358
|
+
if (states.size === 0)
|
|
28359
|
+
turnsByAgent.delete(agentId);
|
|
28360
|
+
};
|
|
28361
|
+
const retryPending = (agentId) => {
|
|
28362
|
+
const states = turnsByAgent.get(agentId);
|
|
28363
|
+
if (!states)
|
|
28364
|
+
return;
|
|
28365
|
+
const nowMs = now().getTime();
|
|
28366
|
+
for (const [key, state] of [...states]) {
|
|
28367
|
+
if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
|
|
28368
|
+
deleteState(agentId, key);
|
|
28369
|
+
continue;
|
|
28370
|
+
}
|
|
28371
|
+
if (!state.finalized || state.pendingSinceMs === undefined)
|
|
28372
|
+
continue;
|
|
28373
|
+
if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
|
|
28374
|
+
diagnostic(agentId, "timeline_pending_commit_expired");
|
|
28375
|
+
deleteState(agentId, key);
|
|
28376
|
+
continue;
|
|
28377
|
+
}
|
|
28378
|
+
const result = tryCommit(agentId, state);
|
|
28379
|
+
if (result === "written") {
|
|
28380
|
+
state.responses = [];
|
|
28381
|
+
state.pendingSinceMs = undefined;
|
|
28382
|
+
state.pendingMode = undefined;
|
|
28383
|
+
state.completedAtMs = nowMs;
|
|
28384
|
+
} else if (result === "terminal") {
|
|
28385
|
+
deleteState(agentId, key);
|
|
28386
|
+
}
|
|
28387
|
+
}
|
|
28388
|
+
const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
|
|
28389
|
+
while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
|
|
28390
|
+
const oldest = completed.shift();
|
|
28391
|
+
if (oldest)
|
|
28392
|
+
deleteState(agentId, oldest[0]);
|
|
28393
|
+
}
|
|
28394
|
+
};
|
|
28395
|
+
const retainPending = (agentId, key, state) => {
|
|
28396
|
+
const states = statesFor(agentId);
|
|
28397
|
+
const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
|
|
28398
|
+
if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
|
|
28399
|
+
diagnostic(agentId, "timeline_pending_commit_overflow");
|
|
28400
|
+
deleteState(agentId, key);
|
|
28401
|
+
return;
|
|
28402
|
+
}
|
|
28403
|
+
state.pendingSinceMs ??= now().getTime();
|
|
28404
|
+
state.pendingMode = state.handle ? "handle" : "fallback";
|
|
28405
|
+
};
|
|
28406
|
+
const appendOwnerless = (agentId, messages) => {
|
|
28407
|
+
if (messages.length === 0)
|
|
28408
|
+
return;
|
|
28409
|
+
const dir = dirFor(agentId);
|
|
28410
|
+
if (!prepareTimelineDirectory(dir))
|
|
28411
|
+
return;
|
|
28412
|
+
const result = appendTrackedEntry(dir, createTimelineEntry({
|
|
28413
|
+
messages,
|
|
28414
|
+
sessionId: sessionByAgent.get(agentId) ?? null,
|
|
28415
|
+
provider: opts.providerFor?.(agentId) ?? null
|
|
28416
|
+
}), now());
|
|
28417
|
+
handleTrackedResult(agentId, null, result);
|
|
28418
|
+
};
|
|
27468
28419
|
return {
|
|
27469
|
-
|
|
27470
|
-
|
|
28420
|
+
barrierGeneration(agentId) {
|
|
28421
|
+
return currentBarrier(agentId);
|
|
27471
28422
|
},
|
|
27472
|
-
|
|
28423
|
+
beginTurn(agentId, owner) {
|
|
28424
|
+
retryPending(agentId);
|
|
28425
|
+
const states = statesFor(agentId);
|
|
28426
|
+
if (owner.barrierGeneration !== currentBarrier(agentId)) {
|
|
28427
|
+
diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
|
|
28428
|
+
return;
|
|
28429
|
+
}
|
|
28430
|
+
const key = turnKey(agentId, owner);
|
|
28431
|
+
if (!states.has(key)) {
|
|
28432
|
+
states.set(key, {
|
|
28433
|
+
owner: { ...owner },
|
|
28434
|
+
provider: opts.providerFor?.(agentId) ?? null,
|
|
28435
|
+
backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
|
|
28436
|
+
responses: [],
|
|
28437
|
+
rowFenced: false,
|
|
28438
|
+
finalized: false
|
|
28439
|
+
});
|
|
28440
|
+
}
|
|
28441
|
+
activeTurnByAgent.set(agentId, key);
|
|
28442
|
+
},
|
|
28443
|
+
recordInboxPull(agentId, owner, messages) {
|
|
28444
|
+
retryPending(agentId);
|
|
27473
28445
|
if (messages.length === 0)
|
|
27474
28446
|
return;
|
|
28447
|
+
if (!owner) {
|
|
28448
|
+
appendOwnerless(agentId, messages);
|
|
28449
|
+
return;
|
|
28450
|
+
}
|
|
28451
|
+
const key = turnKey(agentId, owner);
|
|
28452
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28453
|
+
if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
|
|
28454
|
+
appendOwnerless(agentId, messages);
|
|
28455
|
+
return;
|
|
28456
|
+
}
|
|
27475
28457
|
const dir = dirFor(agentId);
|
|
27476
28458
|
if (!prepareTimelineDirectory(dir))
|
|
27477
28459
|
return;
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
|
|
28460
|
+
const stamp = now();
|
|
28461
|
+
let result;
|
|
28462
|
+
if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
|
|
28463
|
+
result = updateTrackedEntry(dir, state.handle, (entry) => ({
|
|
28464
|
+
...entry,
|
|
28465
|
+
messages: [...entry.messages, ...messages],
|
|
28466
|
+
session_id: state.backendSessionId,
|
|
28467
|
+
provider: state.provider
|
|
28468
|
+
}));
|
|
28469
|
+
} else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
|
|
28470
|
+
result = appendTrackedEntry(dir, createTimelineEntry({
|
|
28471
|
+
messages,
|
|
28472
|
+
sessionId: state.backendSessionId,
|
|
28473
|
+
provider: state.provider
|
|
28474
|
+
}), stamp);
|
|
28475
|
+
} else {
|
|
28476
|
+
appendOwnerless(agentId, messages);
|
|
28477
|
+
return;
|
|
28478
|
+
}
|
|
28479
|
+
handleTrackedResult(agentId, state, result);
|
|
27483
28480
|
},
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
|
|
28481
|
+
recordAssistantMessage(agentId, owner, text2, truncated = false) {
|
|
28482
|
+
retryPending(agentId);
|
|
28483
|
+
const key = turnKey(agentId, owner);
|
|
28484
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28485
|
+
if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
|
|
28486
|
+
diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
|
|
27487
28487
|
return;
|
|
27488
|
-
|
|
27489
|
-
|
|
28488
|
+
}
|
|
28489
|
+
state.responses.push(boundedResponse(text2, truncated));
|
|
28490
|
+
if (state.responses.length > MAX_AGENT_RESPONSES) {
|
|
28491
|
+
state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
|
|
28492
|
+
}
|
|
28493
|
+
},
|
|
28494
|
+
finalizeTurn(agentId, owner) {
|
|
28495
|
+
retryPending(agentId);
|
|
28496
|
+
const key = turnKey(agentId, owner);
|
|
28497
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28498
|
+
if (!state || state.finalized || !sameOwner(state.owner, owner))
|
|
27490
28499
|
return;
|
|
27491
|
-
const
|
|
27492
|
-
|
|
27493
|
-
|
|
27494
|
-
|
|
27495
|
-
|
|
27496
|
-
|
|
27497
|
-
|
|
28500
|
+
const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
|
|
28501
|
+
state.finalized = true;
|
|
28502
|
+
activeTurnByAgent.delete(agentId);
|
|
28503
|
+
if (state.responses.length === 0) {
|
|
28504
|
+
state.completedAtMs = now().getTime();
|
|
28505
|
+
return;
|
|
28506
|
+
}
|
|
28507
|
+
if (!state.handle && !fallbackAuthorized) {
|
|
28508
|
+
diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
|
|
28509
|
+
deleteState(agentId, key);
|
|
28510
|
+
return;
|
|
28511
|
+
}
|
|
28512
|
+
const result = tryCommit(agentId, state);
|
|
28513
|
+
if (result === "written") {
|
|
28514
|
+
state.responses = [];
|
|
28515
|
+
state.completedAtMs = now().getTime();
|
|
28516
|
+
} else if (result === "terminal") {
|
|
28517
|
+
deleteState(agentId, key);
|
|
28518
|
+
} else {
|
|
28519
|
+
retainPending(agentId, key, state);
|
|
28520
|
+
}
|
|
28521
|
+
},
|
|
28522
|
+
fenceSession(agentId) {
|
|
28523
|
+
retryPending(agentId);
|
|
28524
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
28525
|
+
activeTurnByAgent.delete(agentId);
|
|
28526
|
+
const states = turnsByAgent.get(agentId);
|
|
28527
|
+
if (!states)
|
|
28528
|
+
return;
|
|
28529
|
+
for (const [key, state] of [...states]) {
|
|
28530
|
+
if (!state.handle) {
|
|
28531
|
+
diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
|
|
28532
|
+
deleteState(agentId, key);
|
|
28533
|
+
}
|
|
28534
|
+
}
|
|
28535
|
+
},
|
|
28536
|
+
setSession(agentId, sessionId, sessionInstanceId) {
|
|
28537
|
+
retryPending(agentId);
|
|
28538
|
+
const dir = dirFor(agentId);
|
|
28539
|
+
if (!prepareTimelineDirectory(dir))
|
|
28540
|
+
return false;
|
|
28541
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
28542
|
+
...state,
|
|
28543
|
+
fullBarrier: null,
|
|
28544
|
+
attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
|
|
28545
|
+
}));
|
|
28546
|
+
if (!persisted)
|
|
28547
|
+
return false;
|
|
28548
|
+
sessionByAgent.set(agentId, sessionId);
|
|
28549
|
+
if (sessionInstanceId) {
|
|
28550
|
+
sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
|
|
28551
|
+
for (const state of statesFor(agentId).values()) {
|
|
28552
|
+
if (state.owner.sessionInstanceId === sessionInstanceId)
|
|
28553
|
+
state.backendSessionId = sessionId;
|
|
28554
|
+
}
|
|
28555
|
+
}
|
|
28556
|
+
return true;
|
|
27498
28557
|
},
|
|
27499
28558
|
resumeSessionId(agentId, provider) {
|
|
27500
|
-
const
|
|
27501
|
-
return
|
|
28559
|
+
const resolution = resolveForAgent(agentId, provider);
|
|
28560
|
+
return resolution.kind === "session" ? resolution.sessionId : null;
|
|
28561
|
+
},
|
|
28562
|
+
resolveResumeSession(agentId, provider) {
|
|
28563
|
+
return resolveForAgent(agentId, provider);
|
|
27502
28564
|
},
|
|
27503
|
-
|
|
28565
|
+
recordSessionStall(agentId, sessionId) {
|
|
28566
|
+
return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
|
|
28567
|
+
},
|
|
28568
|
+
clearSessionStall(agentId, sessionId) {
|
|
28569
|
+
return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
|
|
28570
|
+
},
|
|
28571
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
28572
|
+
retryPending(agentId);
|
|
27504
28573
|
const dir = dirFor(agentId);
|
|
27505
|
-
sessionByAgent.delete(agentId);
|
|
27506
28574
|
if (!prepareTimelineDirectory(dir))
|
|
27507
|
-
return;
|
|
28575
|
+
return false;
|
|
27508
28576
|
const stamp = now();
|
|
27509
|
-
|
|
28577
|
+
let persisted = false;
|
|
28578
|
+
if (barrierType === "reset_session" || barrierType === "nap") {
|
|
28579
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
28580
|
+
...state,
|
|
28581
|
+
attemptedSessionId: null,
|
|
28582
|
+
fencedSessionId: null,
|
|
28583
|
+
fullBarrier: barrierType
|
|
28584
|
+
}));
|
|
28585
|
+
} else if (barrierType === "stall_recovery") {
|
|
28586
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
28587
|
+
...state,
|
|
28588
|
+
attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
|
|
28589
|
+
fencedSessionId: forgottenSessionId ?? null,
|
|
28590
|
+
fullBarrier: null
|
|
28591
|
+
}));
|
|
28592
|
+
}
|
|
28593
|
+
if (!persisted)
|
|
28594
|
+
return false;
|
|
28595
|
+
sessionByAgent.delete(agentId);
|
|
28596
|
+
for (const key of [...sessionByEpoch.keys()]) {
|
|
28597
|
+
if (key.startsWith(`${agentId}\x00`))
|
|
28598
|
+
sessionByEpoch.delete(key);
|
|
28599
|
+
}
|
|
28600
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
28601
|
+
activeTurnByAgent.delete(agentId);
|
|
28602
|
+
const states = turnsByAgent.get(agentId);
|
|
28603
|
+
if (states) {
|
|
28604
|
+
for (const [key, state] of [...states]) {
|
|
28605
|
+
if (!state.handle) {
|
|
28606
|
+
diagnostic(agentId, "timeline_fallback_rejected", "barrier");
|
|
28607
|
+
deleteState(agentId, key);
|
|
28608
|
+
}
|
|
28609
|
+
}
|
|
28610
|
+
}
|
|
28611
|
+
const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
|
|
28612
|
+
handleTrackedResult(agentId, null, result);
|
|
28613
|
+
return true;
|
|
27510
28614
|
}
|
|
27511
28615
|
};
|
|
28616
|
+
function appendStallMarker(agentId, type, sessionId) {
|
|
28617
|
+
retryPending(agentId);
|
|
28618
|
+
const dir = dirFor(agentId);
|
|
28619
|
+
if (!prepareTimelineDirectory(dir))
|
|
28620
|
+
return false;
|
|
28621
|
+
const stamp = now();
|
|
28622
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
28623
|
+
...state,
|
|
28624
|
+
attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
|
|
28625
|
+
fullBarrier: null
|
|
28626
|
+
}));
|
|
28627
|
+
if (!persisted)
|
|
28628
|
+
return false;
|
|
28629
|
+
const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
|
|
28630
|
+
handleTrackedResult(agentId, null, result);
|
|
28631
|
+
return true;
|
|
28632
|
+
}
|
|
27512
28633
|
}
|
|
27513
28634
|
// src/discovery.ts
|
|
27514
28635
|
import * as path9 from "path";
|
|
@@ -28123,11 +29244,15 @@ async function createDaemon(opts) {
|
|
|
28123
29244
|
const typingTracker = createTypingScopeTracker();
|
|
28124
29245
|
const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
|
|
28125
29246
|
const proxy = await startCredentialProxy(broker, {
|
|
28126
|
-
onInboxPullStart: (agentId) =>
|
|
29247
|
+
onInboxPullStart: (agentId) => ({
|
|
29248
|
+
modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
|
|
29249
|
+
owner: managerRef?.timelineTurnOwner(agentId) ?? null
|
|
29250
|
+
}),
|
|
28127
29251
|
onInboxPullResponse: (agentId, messages, observationToken) => {
|
|
28128
|
-
|
|
28129
|
-
|
|
28130
|
-
|
|
29252
|
+
const token = observationToken && typeof observationToken === "object" ? observationToken : null;
|
|
29253
|
+
timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
|
|
29254
|
+
if (typeof token?.modelSeenGeneration === "number") {
|
|
29255
|
+
channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
|
|
28131
29256
|
}
|
|
28132
29257
|
},
|
|
28133
29258
|
onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
|
|
@@ -28188,6 +29313,7 @@ async function createDaemon(opts) {
|
|
|
28188
29313
|
typingTracker.clear(agentId);
|
|
28189
29314
|
}
|
|
28190
29315
|
const botsById = new Map;
|
|
29316
|
+
let botCacheReady = false;
|
|
28191
29317
|
async function listMyBotsHttp() {
|
|
28192
29318
|
const res = await fetch(`${opts.serverUrl}/api/community/daemon/bots`, {
|
|
28193
29319
|
method: "GET",
|
|
@@ -28198,22 +29324,26 @@ async function createDaemon(opts) {
|
|
|
28198
29324
|
const json2 = await res.json();
|
|
28199
29325
|
return json2.bots ?? [];
|
|
28200
29326
|
}
|
|
29327
|
+
function replaceBotCache(bots) {
|
|
29328
|
+
botsById.clear();
|
|
29329
|
+
for (const b of bots) {
|
|
29330
|
+
botsById.set(b.id, {
|
|
29331
|
+
name: b.name,
|
|
29332
|
+
discriminator: b.discriminator,
|
|
29333
|
+
description: b.description,
|
|
29334
|
+
ownerName: b.ownerName,
|
|
29335
|
+
ownerDiscriminator: b.ownerDiscriminator
|
|
29336
|
+
});
|
|
29337
|
+
}
|
|
29338
|
+
botCacheReady = true;
|
|
29339
|
+
}
|
|
28201
29340
|
async function coldStartWarmup() {
|
|
28202
29341
|
const start = Date.now();
|
|
28203
29342
|
let attempt = 0;
|
|
28204
29343
|
while (Date.now() - start < WARMUP_CEILING_MS) {
|
|
28205
29344
|
try {
|
|
28206
29345
|
const bots = await listMyBotsHttp();
|
|
28207
|
-
|
|
28208
|
-
for (const b of bots) {
|
|
28209
|
-
botsById.set(b.id, {
|
|
28210
|
-
name: b.name,
|
|
28211
|
-
discriminator: b.discriminator,
|
|
28212
|
-
description: b.description,
|
|
28213
|
-
ownerName: b.ownerName,
|
|
28214
|
-
ownerDiscriminator: b.ownerDiscriminator
|
|
28215
|
-
});
|
|
28216
|
-
}
|
|
29346
|
+
replaceBotCache(bots);
|
|
28217
29347
|
log.info("cold-start bot-cache warmup succeeded", { bots: bots.length, attempt });
|
|
28218
29348
|
return;
|
|
28219
29349
|
} catch {
|
|
@@ -28434,7 +29564,14 @@ async function createDaemon(opts) {
|
|
|
28434
29564
|
const statusPath = opts.statusFilePath;
|
|
28435
29565
|
const writeStatus = () => {
|
|
28436
29566
|
const nowMs = Date.now();
|
|
28437
|
-
writeStatusFile(statusPath, {
|
|
29567
|
+
writeStatusFile(statusPath, {
|
|
29568
|
+
writtenAt: nowMs,
|
|
29569
|
+
agentSummary: {
|
|
29570
|
+
total: botCacheReady ? botsById.size : null,
|
|
29571
|
+
running: manager.runningAgentCount()
|
|
29572
|
+
},
|
|
29573
|
+
agents: manager.statusProjection(nowMs)
|
|
29574
|
+
});
|
|
28438
29575
|
};
|
|
28439
29576
|
writeStatus();
|
|
28440
29577
|
statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
|
|
@@ -28459,15 +29596,7 @@ async function createDaemon(opts) {
|
|
|
28459
29596
|
if (!botsById.has(agentId)) {
|
|
28460
29597
|
try {
|
|
28461
29598
|
const bots = await listMyBotsHttp();
|
|
28462
|
-
|
|
28463
|
-
botsById.set(b.id, {
|
|
28464
|
-
name: b.name,
|
|
28465
|
-
discriminator: b.discriminator,
|
|
28466
|
-
description: b.description,
|
|
28467
|
-
ownerName: b.ownerName,
|
|
28468
|
-
ownerDiscriminator: b.ownerDiscriminator
|
|
28469
|
-
});
|
|
28470
|
-
}
|
|
29599
|
+
replaceBotCache(bots);
|
|
28471
29600
|
} catch {}
|
|
28472
29601
|
}
|
|
28473
29602
|
if (!botsById.has(agentId)) {
|
|
@@ -28578,10 +29707,12 @@ export {
|
|
|
28578
29707
|
RUNTIME_RAW_TRACE_AGENT_IDS_ENV,
|
|
28579
29708
|
RUNTIME_AUTH_ACTION_REQUIRED_PATTERNS,
|
|
28580
29709
|
LOCAL_MESSAGE_REMINDER_PATH,
|
|
29710
|
+
DEFAULT_TURN_SILENCE_POLICY,
|
|
28581
29711
|
DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
28582
29712
|
DEFAULT_STALE_THRESHOLD_MS,
|
|
28583
29713
|
DEFAULT_RESET_STUCK_THRESHOLD_MS,
|
|
28584
29714
|
DEFAULT_IDLE_TIMEOUT_MS,
|
|
29715
|
+
DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
28585
29716
|
DEFAULT_HELD_CONTEXT_LIMIT,
|
|
28586
29717
|
DEFAULT_CAPABILITY_RESOLVER,
|
|
28587
29718
|
CredentialBroker,
|