@alook/daemon 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.js +1589 -322
- package/dist/index.js +1523 -298
- 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,
|
|
@@ -6891,6 +7215,16 @@ function runtimeModelName(config) {
|
|
|
6891
7215
|
|
|
6892
7216
|
// src/drivers/systemPrompt.ts
|
|
6893
7217
|
var CLI = "$ALOOK_CLI";
|
|
7218
|
+
var MESSAGE_SEND_STDIN_POLICY = [
|
|
7219
|
+
"`--stdin` is required and limited to 1 KiB of UTF-8. Write it as social language a person " + "with ADHD can scan without effort:",
|
|
7220
|
+
"",
|
|
7221
|
+
"- Lead with the point, result, decision, or one concrete ask.",
|
|
7222
|
+
"- Keep one message to one topic; suppress tangents and repeated recap.",
|
|
7223
|
+
"- Use at most five short items when a list helps.",
|
|
7224
|
+
"- Drop preambles, play-by-play, and closing pleasantries.",
|
|
7225
|
+
"- Put exact plans, reviews, evidence, logs, and long technical detail in a Markdown attachment. " + "The message body carries only the short summary and next action."
|
|
7226
|
+
].join(`
|
|
7227
|
+
`);
|
|
6894
7228
|
function identitySection(config) {
|
|
6895
7229
|
const parts = ["## Identity", ""];
|
|
6896
7230
|
const name = config.agentName ?? "a member of the household";
|
|
@@ -6907,9 +7241,6 @@ function identitySection(config) {
|
|
|
6907
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.");
|
|
6908
7242
|
}
|
|
6909
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.");
|
|
6910
|
-
if (config.description) {
|
|
6911
|
-
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).");
|
|
6912
|
-
}
|
|
6913
7244
|
return parts.join(`
|
|
6914
7245
|
`);
|
|
6915
7246
|
}
|
|
@@ -6921,9 +7252,9 @@ function cliCommandsSection() {
|
|
|
6921
7252
|
"",
|
|
6922
7253
|
"### Messaging",
|
|
6923
7254
|
"",
|
|
6924
|
-
`1. \`${CLI} inbox pull\` — fetch unread messages
|
|
6925
|
-
`2. \`${CLI} message send\` — send to a channel, DM, or thread. ` + `
|
|
6926
|
-
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted
|
|
7255
|
+
`1. \`${CLI} inbox pull\` — fetch unread messages; \`--no-ack\` peeks without advancing.`,
|
|
7256
|
+
`2. \`${CLI} message send --target <ref> --remind-after <0|Nm|Nh> --stdin\` — send to a ` + `channel, DM, or thread. ` + `The body is required through \`--stdin\` and limited to 1 KiB of UTF-8. ` + `Attach uploaded files with \`--attachment <id>\` (repeatable, order matters). ` + `\`--remind-after\` accepts \`0\`, or a whole-number duration from \`1m\` to \`24h\`.`,
|
|
7257
|
+
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted.`,
|
|
6927
7258
|
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
|
|
6928
7259
|
`5. \`${CLI} message emoji --target <ref> --emoji <e>\` — react with a single emoji. ` + `Works on channel messages (\`/<server>/<channel>#N\`), DM messages ` + `(\`/.dm/<peer>#N\`), and thread-reply messages (\`/<server>/<channel>/#N#M\`).`,
|
|
6929
7260
|
`6. \`${CLI} message mark set --target <full-message-ref>\` — persist a message as outstanding work.`,
|
|
@@ -6953,7 +7284,7 @@ function cliCommandsSection() {
|
|
|
6953
7284
|
"",
|
|
6954
7285
|
"### Context Lifecycle",
|
|
6955
7286
|
"",
|
|
6956
|
-
`1. \`${CLI} nap --handoff <file>\` — reset
|
|
7287
|
+
`1. \`${CLI} nap --handoff <file>\` — reset the current session from a required handoff file.`,
|
|
6957
7288
|
"",
|
|
6958
7289
|
"### Output format",
|
|
6959
7290
|
"",
|
|
@@ -6969,14 +7300,29 @@ function messagingSection() {
|
|
|
6969
7300
|
"",
|
|
6970
7301
|
"### Sending & receiving",
|
|
6971
7302
|
"",
|
|
6972
|
-
"You can initiate conversations — send to any channel or DM someone directly. You're not " + "limited to replying
|
|
7303
|
+
"You can initiate conversations — send to any channel or DM someone directly. You're not " + "limited to replying; the same sending rules apply either way.",
|
|
6973
7304
|
"",
|
|
6974
|
-
"
|
|
6975
|
-
"Free-form message bodies never go in command arguments.",
|
|
7305
|
+
"#### Message body",
|
|
6976
7306
|
"",
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
7307
|
+
MESSAGE_SEND_STDIN_POLICY,
|
|
7308
|
+
"",
|
|
7309
|
+
"#### Follow up when a conversation goes quiet",
|
|
7310
|
+
"",
|
|
7311
|
+
"`--remind-after T` is required on every send and controls whether a local follow-up is " + "armed for the same channel, thread, or DM.",
|
|
7312
|
+
"",
|
|
7313
|
+
"- Use `1m` to `24h` when silence would leave work unfinished — for example, after a question, " + "approval request, handoff, or blocker. If no newer message arrives by T, you will be reminded " + "to return; a newer message or daemon restart cancels the timer.",
|
|
7314
|
+
"- Use `0` only when no later action depends on a reply. It disables the timer.",
|
|
7315
|
+
"",
|
|
7316
|
+
"Example: `--remind-after 5m` asks for a follow-up after five quiet minutes.",
|
|
7317
|
+
"",
|
|
7318
|
+
"#### Sending mechanics",
|
|
7319
|
+
"",
|
|
7320
|
+
`- Send body: use \`${CLI} message send --target <ref> --remind-after T --stdin\` with the ` + "quoted-heredoc form under *Message formatting*.",
|
|
7321
|
+
`- Long detail: upload it with \`${CLI} message attachment upload --target <ref> --file <path>.md\`; ` + "add the returned id as `--attachment <id>` on the short stdin send.",
|
|
7322
|
+
`- Cite a specific message: add \`--reply "#37"\` — \`--reply\` takes the \`#N\` seq ` + "(within `--target`) of the message you're answering.",
|
|
7323
|
+
"",
|
|
7324
|
+
"Reply where the message came from. Post results in the channel that owns the topic. " + "When uncertain, read history (below) or DM the relevant people.",
|
|
7325
|
+
"Write every message body in the stdin/heredoc block; never place it directly on the command line.",
|
|
6980
7326
|
"",
|
|
6981
7327
|
"### Context refs",
|
|
6982
7328
|
"",
|
|
@@ -7010,14 +7356,14 @@ function messagingSection() {
|
|
|
7010
7356
|
"",
|
|
7011
7357
|
"### Message formatting",
|
|
7012
7358
|
"",
|
|
7013
|
-
"Alook
|
|
7359
|
+
"Alook specially renders refs and mentions in message bodies. Write them as plain text, not " + "inside backticks.",
|
|
7014
7360
|
"",
|
|
7015
|
-
"- **Context refs** —
|
|
7361
|
+
"- **Context refs** — write the full refs from *Context refs* above so they stay " + "clickable. Never paste a private DM ref into a server channel.",
|
|
7016
7362
|
"- **Mentions** — `@name#NNNN` calls that person's attention specifically. In a private " + "channel, first verify they " + `are a member with \`${CLI} channel member --channel <ref>\` before mentioning them.`,
|
|
7017
7363
|
"",
|
|
7018
7364
|
"```bash",
|
|
7019
7365
|
"# Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
|
|
7020
|
-
`${CLI} message send --target "/demo#1234/general" --stdin <<'ALOOK_MESSAGE_7F3C'`,
|
|
7366
|
+
`${CLI} message send --target "/demo#1234/general" --remind-after 5m --stdin <<'ALOOK_MESSAGE_7F3C'`,
|
|
7021
7367
|
"@alice#0001 Please review /demo#1234/general#42",
|
|
7022
7368
|
"ALOOK_MESSAGE_7F3C",
|
|
7023
7369
|
"```",
|
|
@@ -7030,7 +7376,7 @@ function messagingSection() {
|
|
|
7030
7376
|
"```",
|
|
7031
7377
|
"",
|
|
7032
7378
|
"`channel` is the reply ref. `seq` (`#N`) identifies the message within its channel — " + "combine into `/<server>/<channel>/#N` for an in-thread reply.",
|
|
7033
|
-
"`content.replyTo` (`{seq, sender}`)
|
|
7379
|
+
"`content.replyTo` (`{seq, sender}`) identifies the message being replied to.",
|
|
7034
7380
|
"`hint` is present when the containing surface changes how you should act. Follow it."
|
|
7035
7381
|
].join(`
|
|
7036
7382
|
`);
|
|
@@ -7043,15 +7389,15 @@ function channelTypesSection() {
|
|
|
7043
7389
|
"",
|
|
7044
7390
|
"### Text channels",
|
|
7045
7391
|
"",
|
|
7046
|
-
"- A text channel is a linear conversation. Send
|
|
7392
|
+
"- A text channel is a linear conversation. Send with `message send --target " + "/<server>/<channel>`.",
|
|
7047
7393
|
"- A text-channel message may have a side thread at `/<server>/<channel>/#N`. Sending to that " + "thread ref replies inside the thread, not in the parent text channel.",
|
|
7048
7394
|
"",
|
|
7049
7395
|
"### Forum channels",
|
|
7050
7396
|
"",
|
|
7051
7397
|
"- A forum is a collection of posts, not one linear conversation.",
|
|
7052
7398
|
"- Each top-level message in a forum is a post title. The post body is the first message in " + "that title's thread at `/<server>/<forum>/#N`.",
|
|
7053
|
-
"- To participate in
|
|
7054
|
-
"- To publish
|
|
7399
|
+
"- To participate in a post, use `message send --target /<server>/<forum>/#N`.",
|
|
7400
|
+
"- To publish a post, use `message send --target /<server>/<forum>` for its title, then " + "`message send --target /<server>/<forum>/#N` for the body."
|
|
7055
7401
|
].join(`
|
|
7056
7402
|
`);
|
|
7057
7403
|
}
|
|
@@ -7079,13 +7425,7 @@ function utilsSection() {
|
|
|
7079
7425
|
"",
|
|
7080
7426
|
"### Join a new server",
|
|
7081
7427
|
"",
|
|
7082
|
-
`If a message contains a \`/c/invite/...\` link, just run \`${CLI} server join --invite <link>\`. ` + "The server enforces owner-only: it accepts only invites your owner created and rejects the " + "rest with a reason. Safe to attempt without reasoning about who sent it."
|
|
7083
|
-
"",
|
|
7084
|
-
"### Follow up when a conversation goes quiet",
|
|
7085
|
-
"",
|
|
7086
|
-
`Use \`message send --remind-after <duration>\` when you send something that may need a later ` + "follow-up — for example, a question, approval request, handoff, or blocker — and silence would " + "leave the work unfinished. If no newer message appears in that channel, thread, or DM during " + "the duration, you'll receive a reminder to return and decide what to do next. Don't add it to " + "ordinary messages that need no follow-up.",
|
|
7087
|
-
"",
|
|
7088
|
-
`Example: ${CLI} message send --target "/demo#1234/team" --remind-after 1m --file ./message.md`
|
|
7428
|
+
`If a message contains a \`/c/invite/...\` link, just run \`${CLI} server join --invite <link>\`. ` + "The server enforces owner-only: it accepts only invites your owner created and rejects the " + "rest with a reason. Safe to attempt without reasoning about who sent it."
|
|
7089
7429
|
].join(`
|
|
7090
7430
|
`);
|
|
7091
7431
|
}
|
|
@@ -7093,11 +7433,10 @@ function criticalRulesSection() {
|
|
|
7093
7433
|
return [
|
|
7094
7434
|
"## Critical rules",
|
|
7095
7435
|
"",
|
|
7096
|
-
`- **\`${CLI}\` is the only way to communicate.** Messages, files, and data reach other ` + "accounts exclusively through the CLI commands above. Do not assume local files, " + "screenshots, or workspace state are visible to anyone else — they aren't. If someone " + `needs to see something,
|
|
7436
|
+
`- **\`${CLI}\` is the only way to communicate.** Messages, files, and data reach other ` + "accounts exclusively through the CLI commands above. Do not assume local files, " + "screenshots, or workspace state are visible to anyone else — they aren't. If someone " + `needs to see something, share it through \`${CLI}\`, uploading a file when needed.`,
|
|
7097
7437
|
"- **Never expose tokens, keys, or secrets.** Redact credential-like strings from tool output " + "before sharing.",
|
|
7098
7438
|
"- **Match the sender's language.** Reply in the language they wrote in.",
|
|
7099
|
-
"- **Channel alignment**: you can't send to a channel with unread messages. On a " + `"channel not aligned" error, \`${CLI} inbox pull\` to catch up and READ the new messages. ` + "Judge if your message is still needed or overlaps with what just landed. Adjust or skip; " + "don't mechanically resend."
|
|
7100
|
-
"- **Finish in-flight work before stopping.** Don't leave anything half-handled. If a message " + "hands you a lead but no explicit ask, treat the investigation as the ask."
|
|
7439
|
+
"- **Channel alignment**: you can't send to a channel with unread messages. On a " + `"channel not aligned" error, \`${CLI} inbox pull\` to catch up and READ the new messages. ` + "Judge if your message is still needed or overlaps with what just landed. Adjust or skip; " + "don't mechanically resend."
|
|
7101
7440
|
].join(`
|
|
7102
7441
|
`);
|
|
7103
7442
|
}
|
|
@@ -7105,7 +7444,7 @@ function executionModelSection() {
|
|
|
7105
7444
|
return [
|
|
7106
7445
|
"## How you work — async, not turn-based",
|
|
7107
7446
|
"",
|
|
7108
|
-
"Sending a message is I/O, not a stopping point. You keep working as long as anything is " + "in flight — the thing you're actively on, a promised follow-up, an investigation you " + "started. Stop only when all of it is done.",
|
|
7447
|
+
"Sending a message is I/O, not a stopping point. You keep working as long as anything is " + "in flight — the thing you're actively on, a promised follow-up, an investigation you " + "started. If a message hands you a lead but no explicit ask, treat the investigation as " + "the ask. Stop only when all of it is done.",
|
|
7109
7448
|
"",
|
|
7110
7449
|
"On wake, restore durable context from `memory.md` and the context timeline, then pull your inbox. " + "Follow *Outstanding work marks* below before taking new work.",
|
|
7111
7450
|
"",
|
|
@@ -7138,7 +7477,17 @@ function chaosAwarenessSection() {
|
|
|
7138
7477
|
].join(`
|
|
7139
7478
|
`);
|
|
7140
7479
|
}
|
|
7141
|
-
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
|
+
] : [];
|
|
7142
7491
|
return [
|
|
7143
7492
|
"## Self-awareness",
|
|
7144
7493
|
"",
|
|
@@ -7147,6 +7496,7 @@ function workspaceMemorySection() {
|
|
|
7147
7496
|
"**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
|
|
7148
7497
|
"",
|
|
7149
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,
|
|
7150
7500
|
"",
|
|
7151
7501
|
"### Napping",
|
|
7152
7502
|
"",
|
|
@@ -7188,7 +7538,7 @@ function buildCliSystemPrompt(config) {
|
|
|
7188
7538
|
criticalRulesSection(),
|
|
7189
7539
|
executionModelSection(),
|
|
7190
7540
|
chaosAwarenessSection(),
|
|
7191
|
-
workspaceMemorySection(),
|
|
7541
|
+
workspaceMemorySection(config),
|
|
7192
7542
|
utilsSection()
|
|
7193
7543
|
];
|
|
7194
7544
|
return sections.filter((s) => s && s.length > 0).join(`
|
|
@@ -7520,8 +7870,8 @@ class AgentProcessManager {
|
|
|
7520
7870
|
resumeSessions = new Map;
|
|
7521
7871
|
launchIds = new Map;
|
|
7522
7872
|
liveSessions = new Map;
|
|
7523
|
-
thinkingBuffers = new Map;
|
|
7524
7873
|
activeSpawnState = new Map;
|
|
7874
|
+
publishedAgentActivity = new Map;
|
|
7525
7875
|
traceProcessNonce = randomUUID5();
|
|
7526
7876
|
nextSpawnOrdinal = 1;
|
|
7527
7877
|
nextDaemonTurnOrdinal = 1;
|
|
@@ -7535,7 +7885,8 @@ class AgentProcessManager {
|
|
|
7535
7885
|
this.opts = {
|
|
7536
7886
|
tickIntervalMs: 5000,
|
|
7537
7887
|
staleThresholdMs: 120000,
|
|
7538
|
-
idleTimeoutMs:
|
|
7888
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
7889
|
+
idleResetTimeoutMs: DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
7539
7890
|
resetStuckThresholdMs: 120000,
|
|
7540
7891
|
stoppingStuckThresholdMs: DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
7541
7892
|
handshakeTimeoutMs: 60000,
|
|
@@ -7544,7 +7895,7 @@ class AgentProcessManager {
|
|
|
7544
7895
|
};
|
|
7545
7896
|
this.now = opts.now ?? (() => Date.now());
|
|
7546
7897
|
this.log = opts.logger ?? createLogger({ header: "@alook/daemon:manager" });
|
|
7547
|
-
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);
|
|
7548
7899
|
}
|
|
7549
7900
|
register(agentId, launch) {
|
|
7550
7901
|
if (launch?.runtimeConfig)
|
|
@@ -7563,11 +7914,19 @@ class AgentProcessManager {
|
|
|
7563
7914
|
const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
|
|
7564
7915
|
return effects.length > 0;
|
|
7565
7916
|
}
|
|
7566
|
-
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;
|
|
7567
7927
|
this.resumeSessions.delete(agentId);
|
|
7568
7928
|
this.liveSessions.delete(agentId);
|
|
7569
|
-
|
|
7570
|
-
this.opts.timeline?.forgetSession(agentId, barrierType);
|
|
7929
|
+
return true;
|
|
7571
7930
|
}
|
|
7572
7931
|
enqueueRewake(agentId, message) {
|
|
7573
7932
|
this.dispatch({ type: "rewake_after_reset", agentId, message });
|
|
@@ -7590,9 +7949,14 @@ class AgentProcessManager {
|
|
|
7590
7949
|
});
|
|
7591
7950
|
}
|
|
7592
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
|
+
}
|
|
7593
7957
|
this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
|
|
7594
|
-
if (opts.forgetSession)
|
|
7595
|
-
this.
|
|
7958
|
+
if (!opts.forgetSession)
|
|
7959
|
+
this.opts.timeline?.fenceSession(agentId);
|
|
7596
7960
|
this.abortCurrentTurn(agentId, opts.abortCause);
|
|
7597
7961
|
this.markResetting(agentId);
|
|
7598
7962
|
const status = this.state.agents[agentId]?.status;
|
|
@@ -7667,6 +8031,10 @@ class AgentProcessManager {
|
|
|
7667
8031
|
launchId: this.launchIds.get(agentId) ?? null
|
|
7668
8032
|
};
|
|
7669
8033
|
}
|
|
8034
|
+
timelineTurnOwner(agentId) {
|
|
8035
|
+
const owner = this.traceOwnerFor(agentId);
|
|
8036
|
+
return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
|
|
8037
|
+
}
|
|
7670
8038
|
liveSessionReports() {
|
|
7671
8039
|
return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
|
|
7672
8040
|
agentId,
|
|
@@ -7736,6 +8104,10 @@ class AgentProcessManager {
|
|
|
7736
8104
|
if (!expectedSpan || owner.activeSpan !== expectedSpan)
|
|
7737
8105
|
return false;
|
|
7738
8106
|
owner.activeSpan = null;
|
|
8107
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
8108
|
+
owner.timelineTurnOwner = null;
|
|
8109
|
+
if (timelineTurnOwner)
|
|
8110
|
+
this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
|
|
7739
8111
|
const nowMs = this.now();
|
|
7740
8112
|
const base = {
|
|
7741
8113
|
recordKind: "turn_span",
|
|
@@ -7793,6 +8165,13 @@ class AgentProcessManager {
|
|
|
7793
8165
|
inbox: a.inbox.length,
|
|
7794
8166
|
lastDeliverAt: a.lastDeliverAt,
|
|
7795
8167
|
lastProgressAt: a.lastProgressAt,
|
|
8168
|
+
lastNativeActivityAt: a.lastNativeActivityAt,
|
|
8169
|
+
lastNativeActivityKind: a.lastNativeActivityKind,
|
|
8170
|
+
runtimePhase: a.runtimePhase,
|
|
8171
|
+
backendTurnId: a.backendTurnId,
|
|
8172
|
+
turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
|
|
8173
|
+
nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
|
|
8174
|
+
recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
|
|
7796
8175
|
idleSince: a.idleSince,
|
|
7797
8176
|
resetting: a.resetting,
|
|
7798
8177
|
resettingSince: a.resettingSince,
|
|
@@ -7803,6 +8182,7 @@ class AgentProcessManager {
|
|
|
7803
8182
|
timeIso: new Date(nowMs).toISOString(),
|
|
7804
8183
|
...activeSpan ? activeSpan : {},
|
|
7805
8184
|
sinceProgressMs: nowMs - a.lastProgressAt,
|
|
8185
|
+
sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
|
|
7806
8186
|
sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
|
|
7807
8187
|
sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
|
|
7808
8188
|
...event.type === "turn_completed" && event.endReason === "errored" ? {
|
|
@@ -7837,11 +8217,18 @@ class AgentProcessManager {
|
|
|
7837
8217
|
terminationCause: normalizeTerminationCause(event.terminationCause)
|
|
7838
8218
|
} : { event: "turn_end", outcome: "clean" });
|
|
7839
8219
|
}
|
|
7840
|
-
if (this.opts.onAgentActivity && event.type !== "
|
|
8220
|
+
if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
|
|
7841
8221
|
const after = this.deriveActivitySnapshot(state);
|
|
7842
8222
|
for (const [agentId, activity] of Object.entries(after)) {
|
|
7843
|
-
if (agentId in before
|
|
8223
|
+
if (!(agentId in before)) {
|
|
8224
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
8225
|
+
continue;
|
|
8226
|
+
}
|
|
8227
|
+
const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
|
|
8228
|
+
const publishable = event.type !== "spawned" || activity === "running";
|
|
8229
|
+
if (publishable && previouslyPublished !== activity) {
|
|
7844
8230
|
this.opts.onAgentActivity({ agentId, state: activity });
|
|
8231
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
7845
8232
|
}
|
|
7846
8233
|
}
|
|
7847
8234
|
}
|
|
@@ -8006,6 +8393,47 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8006
8393
|
case "terminate_stalled": {
|
|
8007
8394
|
const session = this.sessions.get(effect.agentId);
|
|
8008
8395
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
8396
|
+
const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
|
|
8397
|
+
if (effect.type === "terminate_stalled" && effect.recordSessionId) {
|
|
8398
|
+
const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
|
|
8399
|
+
if (persisted === false) {
|
|
8400
|
+
this.dispatch({
|
|
8401
|
+
type: "stall_control_failed",
|
|
8402
|
+
agentId: effect.agentId,
|
|
8403
|
+
sessionId: effect.recordSessionId,
|
|
8404
|
+
transition: "attempt"
|
|
8405
|
+
});
|
|
8406
|
+
this.log.error("stall recovery attempt was not persisted; termination deferred", {
|
|
8407
|
+
agentId: effect.agentId,
|
|
8408
|
+
sessionId: effect.recordSessionId
|
|
8409
|
+
});
|
|
8410
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
|
|
8411
|
+
break;
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8414
|
+
if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
|
|
8415
|
+
const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
|
|
8416
|
+
if (!persisted) {
|
|
8417
|
+
this.dispatch({
|
|
8418
|
+
type: "stall_control_failed",
|
|
8419
|
+
agentId: effect.agentId,
|
|
8420
|
+
sessionId: effect.forgetSessionId,
|
|
8421
|
+
transition: "fence"
|
|
8422
|
+
});
|
|
8423
|
+
this.log.error("repeated-session fence was not persisted; termination deferred", {
|
|
8424
|
+
agentId: effect.agentId,
|
|
8425
|
+
sessionId: effect.forgetSessionId
|
|
8426
|
+
});
|
|
8427
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
|
|
8428
|
+
break;
|
|
8429
|
+
}
|
|
8430
|
+
if (spawnState)
|
|
8431
|
+
spawnState.discardEvents = true;
|
|
8432
|
+
this.log.warn("repeatedly stalled backend session fenced", {
|
|
8433
|
+
agentId: effect.agentId,
|
|
8434
|
+
sessionId: effect.forgetSessionId
|
|
8435
|
+
});
|
|
8436
|
+
}
|
|
8009
8437
|
if (effect.type === "terminate_stalled" && spawnState) {
|
|
8010
8438
|
this.closeTurn(spawnState, spawnState.activeSpan, {
|
|
8011
8439
|
event: "turn_abort",
|
|
@@ -8024,10 +8452,27 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8024
8452
|
if (spawnState) {
|
|
8025
8453
|
spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
|
|
8026
8454
|
}
|
|
8027
|
-
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
|
|
8455
|
+
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
|
|
8028
8456
|
this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
|
|
8029
8457
|
break;
|
|
8030
8458
|
}
|
|
8459
|
+
case "clear_stall_recovery": {
|
|
8460
|
+
const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
|
|
8461
|
+
if (persisted === false) {
|
|
8462
|
+
this.dispatch({
|
|
8463
|
+
type: "stall_control_failed",
|
|
8464
|
+
agentId: effect.agentId,
|
|
8465
|
+
sessionId: effect.sessionId,
|
|
8466
|
+
transition: "clear"
|
|
8467
|
+
});
|
|
8468
|
+
this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
|
|
8469
|
+
agentId: effect.agentId,
|
|
8470
|
+
sessionId: effect.sessionId
|
|
8471
|
+
});
|
|
8472
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
|
|
8473
|
+
}
|
|
8474
|
+
break;
|
|
8475
|
+
}
|
|
8031
8476
|
case "expire_admission": {
|
|
8032
8477
|
const owner = this.activeSpawnState.get(effect.agentId);
|
|
8033
8478
|
if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
|
|
@@ -8050,6 +8495,26 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8050
8495
|
mode: effect.mode
|
|
8051
8496
|
});
|
|
8052
8497
|
break;
|
|
8498
|
+
case "reset_idle_session": {
|
|
8499
|
+
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
8500
|
+
const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
|
|
8501
|
+
if (!persisted) {
|
|
8502
|
+
this.log.error("idle session reset barrier was not persisted; reset deferred", {
|
|
8503
|
+
agentId: effect.agentId,
|
|
8504
|
+
sessionId: effect.sessionId
|
|
8505
|
+
});
|
|
8506
|
+
this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
|
|
8507
|
+
break;
|
|
8508
|
+
}
|
|
8509
|
+
if (spawnState)
|
|
8510
|
+
spawnState.discardEvents = true;
|
|
8511
|
+
this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
|
|
8512
|
+
this.log.info("idle agent session reset", {
|
|
8513
|
+
agentId: effect.agentId,
|
|
8514
|
+
sessionId: effect.sessionId
|
|
8515
|
+
});
|
|
8516
|
+
break;
|
|
8517
|
+
}
|
|
8053
8518
|
case "force_exit": {
|
|
8054
8519
|
const session = this.sessions.get(effect.agentId);
|
|
8055
8520
|
const state = this.activeSpawnState.get(effect.agentId);
|
|
@@ -8082,8 +8547,8 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8082
8547
|
}
|
|
8083
8548
|
}
|
|
8084
8549
|
}
|
|
8085
|
-
logSessionEnded(agentId, reason) {
|
|
8086
|
-
this.log.info("agent session ended", { agentId, sessionId
|
|
8550
|
+
logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
|
|
8551
|
+
this.log.info("agent session ended", { agentId, sessionId, reason });
|
|
8087
8552
|
}
|
|
8088
8553
|
doSpawn(agentId, messages, resumeSessionId) {
|
|
8089
8554
|
const [first, ...pending] = messages;
|
|
@@ -8108,7 +8573,13 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8108
8573
|
mode: { kind: "default" }
|
|
8109
8574
|
};
|
|
8110
8575
|
const provider = runtimeConfig.runtime;
|
|
8111
|
-
const
|
|
8576
|
+
const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
|
|
8577
|
+
const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
|
|
8578
|
+
const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
|
|
8579
|
+
const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
|
|
8580
|
+
const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
|
|
8581
|
+
const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
|
|
8582
|
+
const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
|
|
8112
8583
|
const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
|
|
8113
8584
|
const agentName = runtimeConfig.agentName ?? base.config?.agentName;
|
|
8114
8585
|
const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
|
|
@@ -8139,12 +8610,15 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8139
8610
|
handshakeTimer: null,
|
|
8140
8611
|
torndown: false,
|
|
8141
8612
|
superseded: false,
|
|
8613
|
+
discardEvents: false,
|
|
8614
|
+
stalledSessionIdAtLaunch,
|
|
8142
8615
|
spawnFailureReason: null,
|
|
8143
8616
|
terminationSemantics: null,
|
|
8144
8617
|
spawnOrdinal: this.nextSpawnOrdinal++,
|
|
8145
8618
|
launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
|
|
8146
8619
|
nextTurnOrdinal: 1,
|
|
8147
8620
|
activeSpan: null,
|
|
8621
|
+
timelineTurnOwner: null,
|
|
8148
8622
|
pendingDeliverySpans: new Map
|
|
8149
8623
|
};
|
|
8150
8624
|
const previousOwner = this.activeSpawnState.get(agentId);
|
|
@@ -8195,7 +8669,6 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8195
8669
|
this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
|
|
8196
8670
|
}
|
|
8197
8671
|
}
|
|
8198
|
-
this.flushThinkingAudit(agentId);
|
|
8199
8672
|
if (state.sessionInstanceId) {
|
|
8200
8673
|
this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
|
|
8201
8674
|
}
|
|
@@ -8220,10 +8693,23 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8220
8693
|
const onEvent = (event) => {
|
|
8221
8694
|
if (state.torndown)
|
|
8222
8695
|
return;
|
|
8696
|
+
if (state.discardEvents) {
|
|
8697
|
+
this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
|
|
8698
|
+
return;
|
|
8699
|
+
}
|
|
8223
8700
|
if (event.type === "session_failed" && !state.hasEstablished) {
|
|
8224
8701
|
reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
|
|
8225
8702
|
}
|
|
8226
8703
|
if (event.type === "session_started") {
|
|
8704
|
+
const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
|
|
8705
|
+
if (persisted === false) {
|
|
8706
|
+
state.discardEvents = true;
|
|
8707
|
+
reportSpawnFailure("resume_control_update_failed", {
|
|
8708
|
+
message: "Backend session rejected because resume control could not be persisted"
|
|
8709
|
+
});
|
|
8710
|
+
state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
|
|
8711
|
+
return;
|
|
8712
|
+
}
|
|
8227
8713
|
state.hasEstablished = true;
|
|
8228
8714
|
clearHandshakeTimer();
|
|
8229
8715
|
this.opts.onRuntimeSessionEstablished?.(driver.id);
|
|
@@ -8242,7 +8728,8 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8242
8728
|
type: "attach_session",
|
|
8243
8729
|
agentId,
|
|
8244
8730
|
sessionInstanceId: session.sessionInstanceId,
|
|
8245
|
-
nowMs: this.now()
|
|
8731
|
+
nowMs: this.now(),
|
|
8732
|
+
turnSilence: session.snapshot().diagnostics?.turnSilence
|
|
8246
8733
|
}, state);
|
|
8247
8734
|
previousOwner?.pendingDeliverySpans.clear();
|
|
8248
8735
|
(async () => {
|
|
@@ -8390,26 +8877,6 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8390
8877
|
this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
|
|
8391
8878
|
}
|
|
8392
8879
|
}
|
|
8393
|
-
flushThinkingAudit(agentId) {
|
|
8394
|
-
const buffered = this.thinkingBuffers.get(agentId);
|
|
8395
|
-
if (!buffered)
|
|
8396
|
-
return;
|
|
8397
|
-
this.thinkingBuffers.delete(agentId);
|
|
8398
|
-
if (!this.opts.onBotAuditEvent)
|
|
8399
|
-
return;
|
|
8400
|
-
const { text, truncated, chars } = truncateThinking(buffered);
|
|
8401
|
-
try {
|
|
8402
|
-
this.opts.onBotAuditEvent(agentId, {
|
|
8403
|
-
kind: "thinking",
|
|
8404
|
-
payload: { text, truncated, chars }
|
|
8405
|
-
}, {
|
|
8406
|
-
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8407
|
-
launchId: this.launchIds.get(agentId) ?? null
|
|
8408
|
-
});
|
|
8409
|
-
} catch (err) {
|
|
8410
|
-
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
8411
|
-
}
|
|
8412
|
-
}
|
|
8413
8880
|
onAgentEvent(agentId, event, runtimeId, owner) {
|
|
8414
8881
|
if (event.type === "session_closed")
|
|
8415
8882
|
return;
|
|
@@ -8442,32 +8909,43 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8442
8909
|
}
|
|
8443
8910
|
}
|
|
8444
8911
|
if (this.opts.onBotAuditEvent) {
|
|
8445
|
-
if (event.type === "
|
|
8446
|
-
|
|
8447
|
-
|
|
8912
|
+
if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
|
|
8913
|
+
const { text, truncated, chars } = truncateThinking(event.text);
|
|
8914
|
+
try {
|
|
8915
|
+
this.opts.onBotAuditEvent(agentId, {
|
|
8916
|
+
kind: "thinking",
|
|
8917
|
+
payload: { text, truncated: truncated || event.truncated, chars }
|
|
8918
|
+
}, {
|
|
8919
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8920
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
8921
|
+
});
|
|
8922
|
+
} catch (err) {
|
|
8923
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
8448
8924
|
}
|
|
8449
|
-
}
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
this.
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
8462
|
-
}
|
|
8925
|
+
}
|
|
8926
|
+
if (event.type === "tool_started") {
|
|
8927
|
+
const audit = extractToolAudit(event.name, event.input);
|
|
8928
|
+
if (!audit.suppressed) {
|
|
8929
|
+
const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
|
|
8930
|
+
try {
|
|
8931
|
+
this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
|
|
8932
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
8933
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
8934
|
+
});
|
|
8935
|
+
} catch (err) {
|
|
8936
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
8463
8937
|
}
|
|
8464
8938
|
}
|
|
8465
8939
|
}
|
|
8466
8940
|
}
|
|
8467
8941
|
if (event.type === "session_started") {
|
|
8468
|
-
this.dispatch({
|
|
8942
|
+
this.dispatch({
|
|
8943
|
+
type: "backend_session",
|
|
8944
|
+
agentId,
|
|
8945
|
+
sessionId: event.backendSessionId,
|
|
8946
|
+
stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
|
|
8947
|
+
}, owner);
|
|
8469
8948
|
this.liveSessions.set(agentId, event.backendSessionId);
|
|
8470
|
-
this.opts.timeline?.setSession(agentId, event.backendSessionId);
|
|
8471
8949
|
this.opts.onAgentSession?.({
|
|
8472
8950
|
agentId,
|
|
8473
8951
|
sessionId: event.backendSessionId,
|
|
@@ -8479,8 +8957,20 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8479
8957
|
runtime: runtimeId
|
|
8480
8958
|
});
|
|
8481
8959
|
}
|
|
8482
|
-
if (event.type === "
|
|
8483
|
-
|
|
8960
|
+
if (event.type === "turn_started") {
|
|
8961
|
+
const timelineTurnOwner = {
|
|
8962
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
8963
|
+
rootTurnId: event.turnId,
|
|
8964
|
+
barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
|
|
8965
|
+
};
|
|
8966
|
+
owner.timelineTurnOwner = timelineTurnOwner;
|
|
8967
|
+
this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
|
|
8968
|
+
}
|
|
8969
|
+
if (event.type === "assistant_message_completed" && event.text.length > 0) {
|
|
8970
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
8971
|
+
if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
|
|
8972
|
+
this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
|
|
8973
|
+
}
|
|
8484
8974
|
}
|
|
8485
8975
|
if (event.type === "command_queued")
|
|
8486
8976
|
this.acknowledgePendingDelivery(owner, event.commandId);
|
|
@@ -8498,9 +8988,10 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8498
8988
|
switch (event.type) {
|
|
8499
8989
|
case "turn_started":
|
|
8500
8990
|
return { type: "turn_started", turnId: event.turnId, commandIds: event.commandIds };
|
|
8501
|
-
case "
|
|
8502
|
-
case "
|
|
8503
|
-
|
|
8991
|
+
case "work_heartbeat":
|
|
8992
|
+
case "assistant_reasoning_completed":
|
|
8993
|
+
case "assistant_message_completed":
|
|
8994
|
+
return { type: "turn_work", turnId: event.turnId };
|
|
8504
8995
|
case "tool_started":
|
|
8505
8996
|
return { type: "turn_tool_started", turnId: event.turnId };
|
|
8506
8997
|
case "tool_finished":
|
|
@@ -8527,37 +9018,55 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8527
9018
|
if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
|
|
8528
9019
|
this.openTurn(owner);
|
|
8529
9020
|
}
|
|
8530
|
-
const
|
|
9021
|
+
const nativeSignal = (() => {
|
|
8531
9022
|
switch (event.type) {
|
|
8532
|
-
case "
|
|
8533
|
-
return "
|
|
8534
|
-
case "
|
|
8535
|
-
return
|
|
8536
|
-
|
|
8537
|
-
|
|
9023
|
+
case "turn_started":
|
|
9024
|
+
return { kind: "turn_started", phase: "inference", turnId: event.turnId };
|
|
9025
|
+
case "backend_turn_started":
|
|
9026
|
+
return {
|
|
9027
|
+
kind: "backend_turn_started",
|
|
9028
|
+
phase: "inference",
|
|
9029
|
+
turnId: event.turnId,
|
|
9030
|
+
backendTurnId: event.backendTurnId
|
|
9031
|
+
};
|
|
9032
|
+
case "assistant_reasoning_completed":
|
|
9033
|
+
return { kind: "thinking", phase: "inference", turnId: event.turnId };
|
|
9034
|
+
case "assistant_message_completed":
|
|
9035
|
+
return { kind: "text", phase: "inference", turnId: event.turnId };
|
|
9036
|
+
case "work_heartbeat":
|
|
9037
|
+
return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
|
|
8538
9038
|
case "tool_started":
|
|
8539
|
-
return "tool_call";
|
|
9039
|
+
return { kind: "tool_call", phase: "tool", turnId: event.turnId };
|
|
8540
9040
|
case "tool_finished":
|
|
8541
|
-
return "tool_output";
|
|
8542
|
-
case "
|
|
8543
|
-
|
|
8544
|
-
case "
|
|
8545
|
-
case "
|
|
8546
|
-
|
|
9041
|
+
return { kind: "tool_output", phase: "inference", turnId: event.turnId };
|
|
9042
|
+
case "compaction_started":
|
|
9043
|
+
case "compaction_finished":
|
|
9044
|
+
case "review_started":
|
|
9045
|
+
case "review_finished":
|
|
9046
|
+
case "internal_progress":
|
|
9047
|
+
return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
|
|
9048
|
+
case "recovery":
|
|
9049
|
+
return event.turnId ? {
|
|
9050
|
+
kind: "recovery",
|
|
9051
|
+
phase: event.stage === "retrying" ? "recovery" : "inference",
|
|
9052
|
+
recoveryStage: event.stage,
|
|
9053
|
+
turnId: event.turnId
|
|
9054
|
+
} : null;
|
|
8547
9055
|
case "turn_completed":
|
|
8548
|
-
return "turn_end";
|
|
8549
|
-
case "session_failed":
|
|
8550
|
-
return "error";
|
|
8551
|
-
case "command_queued":
|
|
8552
|
-
case "command_accepted":
|
|
8553
|
-
case "command_failed":
|
|
8554
|
-
case "turn_started":
|
|
8555
|
-
return "internal_progress";
|
|
9056
|
+
return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
|
|
8556
9057
|
default:
|
|
8557
|
-
return
|
|
9058
|
+
return null;
|
|
8558
9059
|
}
|
|
8559
9060
|
})();
|
|
8560
|
-
|
|
9061
|
+
if (nativeSignal) {
|
|
9062
|
+
this.dispatch({
|
|
9063
|
+
type: "runtime_signal",
|
|
9064
|
+
agentId,
|
|
9065
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
9066
|
+
...nativeSignal,
|
|
9067
|
+
nowMs: this.now()
|
|
9068
|
+
}, owner);
|
|
9069
|
+
}
|
|
8561
9070
|
if (event.type === "turn_completed") {
|
|
8562
9071
|
this.logSessionEnded(agentId, "turn_end");
|
|
8563
9072
|
const marker = this.nonCleanEndMarker.get(agentId);
|
|
@@ -25792,7 +26301,7 @@ function parseLocalMessageReminderBody(body, agentId) {
|
|
|
25792
26301
|
return null;
|
|
25793
26302
|
if (!Number.isSafeInteger(record4.sentSeq) || record4.sentSeq < 1)
|
|
25794
26303
|
return null;
|
|
25795
|
-
if (!Number.isSafeInteger(record4.remindAfterMs) || record4.remindAfterMs < LOCAL_MESSAGE_REMINDER_MIN_MS || record4.remindAfterMs > LOCAL_MESSAGE_REMINDER_MAX_MS)
|
|
26304
|
+
if (!Number.isSafeInteger(record4.remindAfterMs) || record4.remindAfterMs !== 0 && record4.remindAfterMs < LOCAL_MESSAGE_REMINDER_MIN_MS || record4.remindAfterMs > LOCAL_MESSAGE_REMINDER_MAX_MS)
|
|
25796
26305
|
return null;
|
|
25797
26306
|
return {
|
|
25798
26307
|
agentId,
|
|
@@ -26893,7 +27402,7 @@ class WsControlChannel {
|
|
|
26893
27402
|
}
|
|
26894
27403
|
// src/timeline/timeline.ts
|
|
26895
27404
|
import * as fs7 from "node:fs";
|
|
26896
|
-
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
27405
|
+
import { createHash as createHash4, randomBytes as randomBytes4 } from "node:crypto";
|
|
26897
27406
|
import { basename, dirname as dirname3, join as join10 } from "node:path";
|
|
26898
27407
|
|
|
26899
27408
|
// src/timeline/filelock.ts
|
|
@@ -26960,17 +27469,25 @@ function reclaim(lockPath) {
|
|
|
26960
27469
|
var TIMELINE_MAX_BYTES = 1048576;
|
|
26961
27470
|
var TIMELINE_READ_CHUNK_BYTES = 65536;
|
|
26962
27471
|
var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
27472
|
+
var RESUME_CONTROL_FILENAME = ".resume-control.json";
|
|
27473
|
+
var RESUME_CONTROL_MAX_BYTES = 4096;
|
|
27474
|
+
var EMPTY_RESUME_CONTROL = {
|
|
27475
|
+
version: 1,
|
|
27476
|
+
attemptedSessionId: null,
|
|
27477
|
+
fencedSessionId: null,
|
|
27478
|
+
fullBarrier: null
|
|
27479
|
+
};
|
|
26963
27480
|
function isBarrier(entry) {
|
|
26964
|
-
return entry.system
|
|
27481
|
+
return entry.system !== undefined;
|
|
26965
27482
|
}
|
|
26966
27483
|
function canonicalTimelineEntry(value) {
|
|
26967
27484
|
if (!value || typeof value !== "object")
|
|
26968
27485
|
return null;
|
|
26969
27486
|
const entry = value;
|
|
26970
27487
|
if (entry.system) {
|
|
26971
|
-
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
|
|
27488
|
+
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" && entry.system.type !== "stall_recovery_attempt" && entry.system.type !== "stall_recovery_clear" && entry.system.type !== "stall_recovery" || typeof entry.system.time !== "string" || entry.system.backend_session_id !== undefined && typeof entry.system.backend_session_id !== "string")
|
|
26972
27489
|
return null;
|
|
26973
|
-
return createSystemEntry(entry.system.type, entry.system.time);
|
|
27490
|
+
return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
|
|
26974
27491
|
}
|
|
26975
27492
|
if (entry.session_id !== null && typeof entry.session_id !== "string")
|
|
26976
27493
|
return null;
|
|
@@ -26988,13 +27505,60 @@ function canonicalTimelineEntry(value) {
|
|
|
26988
27505
|
};
|
|
26989
27506
|
}
|
|
26990
27507
|
function timelineLine(entry) {
|
|
26991
|
-
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
27508
|
+
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
26992
27509
|
const text2 = JSON.stringify(boundedEntry);
|
|
26993
27510
|
const bytes = Buffer.byteLength(text2, "utf8") + 1;
|
|
26994
27511
|
if (bytes > TIMELINE_MAX_BYTES)
|
|
26995
27512
|
return null;
|
|
26996
27513
|
return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
|
|
26997
27514
|
}
|
|
27515
|
+
function timelineRowHash(line) {
|
|
27516
|
+
return createHash4("sha256").update(line.text, "utf8").digest("hex");
|
|
27517
|
+
}
|
|
27518
|
+
function timelineFileGeneration(filePath, lines) {
|
|
27519
|
+
try {
|
|
27520
|
+
const stat = fs7.lstatSync(filePath, { bigint: true });
|
|
27521
|
+
if (!stat.isFile())
|
|
27522
|
+
return null;
|
|
27523
|
+
const digest = createHash4("sha256");
|
|
27524
|
+
digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
|
|
27525
|
+
`);
|
|
27526
|
+
for (const line of lines)
|
|
27527
|
+
digest.update(line.text, "utf8").update(`
|
|
27528
|
+
`);
|
|
27529
|
+
return digest.digest("hex");
|
|
27530
|
+
} catch {
|
|
27531
|
+
return null;
|
|
27532
|
+
}
|
|
27533
|
+
}
|
|
27534
|
+
function handleFor(filename, generation, lines, rowOrdinal) {
|
|
27535
|
+
return {
|
|
27536
|
+
filename,
|
|
27537
|
+
fileGeneration: generation,
|
|
27538
|
+
rowOrdinal,
|
|
27539
|
+
expectedHash: timelineRowHash(lines[rowOrdinal])
|
|
27540
|
+
};
|
|
27541
|
+
}
|
|
27542
|
+
function refreshTimelineEntryHandle(handle, rewrite) {
|
|
27543
|
+
if (handle.filename !== rewrite.filename)
|
|
27544
|
+
return handle;
|
|
27545
|
+
if (handle.fileGeneration !== rewrite.previousFileGeneration)
|
|
27546
|
+
return null;
|
|
27547
|
+
if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
|
|
27548
|
+
return null;
|
|
27549
|
+
const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
|
|
27550
|
+
if (rowOrdinal === null || rowOrdinal === undefined)
|
|
27551
|
+
return null;
|
|
27552
|
+
const expectedHash = rewrite.rowHashes[rowOrdinal];
|
|
27553
|
+
if (!expectedHash)
|
|
27554
|
+
return null;
|
|
27555
|
+
return {
|
|
27556
|
+
filename: handle.filename,
|
|
27557
|
+
fileGeneration: rewrite.fileGeneration,
|
|
27558
|
+
rowOrdinal,
|
|
27559
|
+
expectedHash
|
|
27560
|
+
};
|
|
27561
|
+
}
|
|
26998
27562
|
function compactLines(input) {
|
|
26999
27563
|
let head = 0;
|
|
27000
27564
|
let bytes = input.reduce((total, line) => total + line.bytes, 0);
|
|
@@ -27206,11 +27770,37 @@ function atomicReplaceTimeline(filePath, lines) {
|
|
|
27206
27770
|
} catch {}
|
|
27207
27771
|
}
|
|
27208
27772
|
}
|
|
27209
|
-
function
|
|
27773
|
+
function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
|
|
27774
|
+
const previousFileGeneration = timelineFileGeneration(filePath, existing);
|
|
27775
|
+
const previousRowHashes = existing.map(timelineRowHash);
|
|
27210
27776
|
const compacted = compactLines(input);
|
|
27211
|
-
|
|
27212
|
-
|
|
27213
|
-
|
|
27777
|
+
const targetOrdinal = compacted.indexOf(required2);
|
|
27778
|
+
if (targetOrdinal < 0)
|
|
27779
|
+
return { status: "rejected", reason: "evicted" };
|
|
27780
|
+
if (!atomicReplaceTimeline(filePath, compacted))
|
|
27781
|
+
return { status: "rejected", reason: "write" };
|
|
27782
|
+
const fileGeneration = timelineFileGeneration(filePath, compacted);
|
|
27783
|
+
if (!fileGeneration)
|
|
27784
|
+
return { status: "rejected", reason: "write" };
|
|
27785
|
+
const rowOrdinals = existing.map((line, oldOrdinal) => {
|
|
27786
|
+
if (replacement?.oldOrdinal === oldOrdinal)
|
|
27787
|
+
return compacted.indexOf(replacement.line);
|
|
27788
|
+
const nextOrdinal = compacted.indexOf(line);
|
|
27789
|
+
return nextOrdinal < 0 ? null : nextOrdinal;
|
|
27790
|
+
});
|
|
27791
|
+
const rewrite = {
|
|
27792
|
+
filename,
|
|
27793
|
+
previousFileGeneration,
|
|
27794
|
+
fileGeneration,
|
|
27795
|
+
previousRowHashes,
|
|
27796
|
+
rowOrdinals,
|
|
27797
|
+
rowHashes: compacted.map(timelineRowHash)
|
|
27798
|
+
};
|
|
27799
|
+
return {
|
|
27800
|
+
status: "written",
|
|
27801
|
+
rewrite,
|
|
27802
|
+
handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
|
|
27803
|
+
};
|
|
27214
27804
|
}
|
|
27215
27805
|
function filenameForDate(date5) {
|
|
27216
27806
|
const y = date5.getFullYear();
|
|
@@ -27242,114 +27832,181 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
27242
27832
|
}
|
|
27243
27833
|
return entries;
|
|
27244
27834
|
}
|
|
27245
|
-
function
|
|
27835
|
+
function readResumeControlState(timelineDir) {
|
|
27836
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27837
|
+
return { kind: "missing" };
|
|
27838
|
+
const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27839
|
+
let source;
|
|
27840
|
+
try {
|
|
27841
|
+
source = fs7.lstatSync(filePath);
|
|
27842
|
+
} catch (error51) {
|
|
27843
|
+
return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
|
|
27844
|
+
}
|
|
27845
|
+
if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
|
|
27846
|
+
return { kind: "invalid" };
|
|
27847
|
+
}
|
|
27848
|
+
let fd = null;
|
|
27849
|
+
try {
|
|
27850
|
+
fd = fs7.openSync(filePath, fs7.constants.O_RDONLY | (fs7.constants.O_NOFOLLOW ?? 0));
|
|
27851
|
+
const stat = fs7.fstatSync(fd);
|
|
27852
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
|
|
27853
|
+
return { kind: "invalid" };
|
|
27854
|
+
}
|
|
27855
|
+
const bounded = Buffer.allocUnsafe(stat.size + 1);
|
|
27856
|
+
let bytesRead = 0;
|
|
27857
|
+
while (bytesRead < bounded.length) {
|
|
27858
|
+
const count = fs7.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
|
|
27859
|
+
if (count <= 0)
|
|
27860
|
+
break;
|
|
27861
|
+
bytesRead += count;
|
|
27862
|
+
}
|
|
27863
|
+
if (bytesRead !== stat.size)
|
|
27864
|
+
return { kind: "invalid" };
|
|
27865
|
+
const raw = bounded.subarray(0, bytesRead).toString("utf8");
|
|
27866
|
+
const value = JSON.parse(raw);
|
|
27867
|
+
const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
|
|
27868
|
+
if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
|
|
27869
|
+
return { kind: "invalid" };
|
|
27870
|
+
return {
|
|
27871
|
+
kind: "state",
|
|
27872
|
+
state: {
|
|
27873
|
+
version: 1,
|
|
27874
|
+
attemptedSessionId: value.attemptedSessionId,
|
|
27875
|
+
fencedSessionId: value.fencedSessionId,
|
|
27876
|
+
fullBarrier: value.fullBarrier
|
|
27877
|
+
}
|
|
27878
|
+
};
|
|
27879
|
+
} catch {
|
|
27880
|
+
return { kind: "invalid" };
|
|
27881
|
+
} finally {
|
|
27882
|
+
if (fd !== null) {
|
|
27883
|
+
try {
|
|
27884
|
+
fs7.closeSync(fd);
|
|
27885
|
+
} catch {}
|
|
27886
|
+
}
|
|
27887
|
+
}
|
|
27888
|
+
}
|
|
27889
|
+
function updateResumeControlState(timelineDir, update) {
|
|
27246
27890
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27247
27891
|
return false;
|
|
27248
|
-
const
|
|
27249
|
-
const filePath = join10(timelineDir, filename);
|
|
27250
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
27892
|
+
const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27251
27893
|
if (!acquireLock(lockPath))
|
|
27252
27894
|
return false;
|
|
27253
27895
|
try {
|
|
27254
|
-
const
|
|
27255
|
-
const
|
|
27256
|
-
|
|
27896
|
+
const current = readResumeControlState(timelineDir);
|
|
27897
|
+
const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
|
|
27898
|
+
const next = update({ ...base });
|
|
27899
|
+
const canonical = {
|
|
27900
|
+
version: 1,
|
|
27901
|
+
attemptedSessionId: next.attemptedSessionId,
|
|
27902
|
+
fencedSessionId: next.fencedSessionId,
|
|
27903
|
+
fullBarrier: next.fullBarrier
|
|
27904
|
+
};
|
|
27905
|
+
const body = JSON.stringify(canonical) + `
|
|
27906
|
+
`;
|
|
27907
|
+
if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
|
|
27257
27908
|
return false;
|
|
27258
|
-
|
|
27909
|
+
const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
|
|
27910
|
+
const tempPath = join10(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
|
|
27911
|
+
let fd = null;
|
|
27912
|
+
try {
|
|
27913
|
+
fd = fs7.openSync(tempPath, "wx", 384);
|
|
27914
|
+
fs7.writeFileSync(fd, body, "utf8");
|
|
27915
|
+
fs7.fsyncSync(fd);
|
|
27916
|
+
fs7.closeSync(fd);
|
|
27917
|
+
fd = null;
|
|
27918
|
+
fs7.renameSync(tempPath, filePath);
|
|
27919
|
+
return true;
|
|
27920
|
+
} finally {
|
|
27921
|
+
if (fd !== null) {
|
|
27922
|
+
try {
|
|
27923
|
+
fs7.closeSync(fd);
|
|
27924
|
+
} catch {}
|
|
27925
|
+
}
|
|
27926
|
+
try {
|
|
27927
|
+
fs7.unlinkSync(tempPath);
|
|
27928
|
+
} catch {}
|
|
27929
|
+
}
|
|
27259
27930
|
} catch {
|
|
27260
27931
|
return false;
|
|
27261
27932
|
} finally {
|
|
27262
27933
|
releaseLock(lockPath);
|
|
27263
27934
|
}
|
|
27264
27935
|
}
|
|
27265
|
-
function
|
|
27936
|
+
function appendTrackedEntry(timelineDir, entry, now = new Date) {
|
|
27266
27937
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27267
|
-
return
|
|
27938
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27268
27939
|
const filename = filenameForDate(now);
|
|
27269
27940
|
const filePath = join10(timelineDir, filename);
|
|
27270
27941
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
27271
|
-
|
|
27272
|
-
|
|
27942
|
+
try {
|
|
27943
|
+
if (!acquireLock(lockPath))
|
|
27944
|
+
return { status: "rejected", reason: "lock" };
|
|
27945
|
+
} catch {
|
|
27946
|
+
return { status: "rejected", reason: "write" };
|
|
27947
|
+
}
|
|
27273
27948
|
try {
|
|
27274
27949
|
const existing = scanTimelineFile(filePath);
|
|
27275
|
-
if (!existing)
|
|
27276
|
-
return false;
|
|
27277
|
-
if (existing.length > 0) {
|
|
27278
|
-
const latest = existing[existing.length - 1].entry;
|
|
27279
|
-
const mergeable = !latest.system && !entry.system && latest.session_id === entry.session_id && latest.provider === entry.provider && latest.agent_responses.length === 0;
|
|
27280
|
-
if (mergeable) {
|
|
27281
|
-
const merged = {
|
|
27282
|
-
...latest,
|
|
27283
|
-
messages: [...latest.messages, ...entry.messages],
|
|
27284
|
-
agent_responses: [...latest.agent_responses]
|
|
27285
|
-
};
|
|
27286
|
-
const required3 = timelineLine(merged);
|
|
27287
|
-
if (!required3)
|
|
27288
|
-
return false;
|
|
27289
|
-
return writeRequiredTimeline(filePath, [...existing.slice(0, -1), required3], required3);
|
|
27290
|
-
}
|
|
27291
|
-
}
|
|
27292
27950
|
const required2 = timelineLine(entry);
|
|
27951
|
+
if (!existing)
|
|
27952
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27293
27953
|
if (!required2)
|
|
27294
|
-
return
|
|
27295
|
-
return
|
|
27954
|
+
return { status: "rejected", reason: "oversized" };
|
|
27955
|
+
return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
|
|
27296
27956
|
} catch {
|
|
27297
|
-
return
|
|
27957
|
+
return { status: "rejected", reason: "write" };
|
|
27298
27958
|
} finally {
|
|
27299
27959
|
releaseLock(lockPath);
|
|
27300
27960
|
}
|
|
27301
27961
|
}
|
|
27302
|
-
function
|
|
27303
|
-
|
|
27304
|
-
|
|
27305
|
-
|
|
27306
|
-
|
|
27307
|
-
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
|
|
27311
|
-
try {
|
|
27312
|
-
source = fs7.lstatSync(filePath);
|
|
27313
|
-
} catch (error51) {
|
|
27314
|
-
if (error51.code === "ENOENT")
|
|
27315
|
-
continue;
|
|
27316
|
-
return "rejected";
|
|
27317
|
-
}
|
|
27318
|
-
if (!source.isFile())
|
|
27319
|
-
return "rejected";
|
|
27320
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
27962
|
+
function updateTrackedEntry(timelineDir, handle, update) {
|
|
27963
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
27964
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27965
|
+
if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename(handle.filename) !== handle.filename) {
|
|
27966
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27967
|
+
}
|
|
27968
|
+
const filePath = join10(timelineDir, handle.filename);
|
|
27969
|
+
const lockPath = lockPathFor(timelineDir, handle.filename);
|
|
27970
|
+
try {
|
|
27321
27971
|
if (!acquireLock(lockPath))
|
|
27322
|
-
return "rejected";
|
|
27323
|
-
|
|
27324
|
-
|
|
27325
|
-
|
|
27326
|
-
|
|
27327
|
-
|
|
27328
|
-
|
|
27329
|
-
|
|
27330
|
-
|
|
27331
|
-
|
|
27332
|
-
|
|
27333
|
-
|
|
27334
|
-
|
|
27335
|
-
|
|
27336
|
-
|
|
27337
|
-
|
|
27338
|
-
|
|
27339
|
-
|
|
27340
|
-
|
|
27341
|
-
|
|
27342
|
-
|
|
27343
|
-
|
|
27344
|
-
|
|
27345
|
-
|
|
27346
|
-
|
|
27347
|
-
|
|
27348
|
-
}
|
|
27349
|
-
|
|
27350
|
-
|
|
27972
|
+
return { status: "rejected", reason: "lock" };
|
|
27973
|
+
} catch {
|
|
27974
|
+
return { status: "rejected", reason: "write" };
|
|
27975
|
+
}
|
|
27976
|
+
try {
|
|
27977
|
+
const existing = scanTimelineFile(filePath);
|
|
27978
|
+
if (!existing)
|
|
27979
|
+
return { status: "rejected", reason: "unsafe" };
|
|
27980
|
+
if (existing.length === 0)
|
|
27981
|
+
return { status: "rejected", reason: "missing" };
|
|
27982
|
+
const generation = timelineFileGeneration(filePath, existing);
|
|
27983
|
+
if (!generation || generation !== handle.fileGeneration) {
|
|
27984
|
+
return { status: "rejected", reason: "generation" };
|
|
27985
|
+
}
|
|
27986
|
+
const captured = existing[handle.rowOrdinal];
|
|
27987
|
+
if (!captured)
|
|
27988
|
+
return { status: "rejected", reason: "ordinal" };
|
|
27989
|
+
if (timelineRowHash(captured) !== handle.expectedHash) {
|
|
27990
|
+
return { status: "rejected", reason: "hash" };
|
|
27991
|
+
}
|
|
27992
|
+
if (captured.entry.system)
|
|
27993
|
+
return { status: "rejected", reason: "system" };
|
|
27994
|
+
const nextEntry = update({
|
|
27995
|
+
...captured.entry,
|
|
27996
|
+
messages: [...captured.entry.messages],
|
|
27997
|
+
agent_responses: [...captured.entry.agent_responses]
|
|
27998
|
+
});
|
|
27999
|
+
const replacement = timelineLine(nextEntry);
|
|
28000
|
+
if (!replacement)
|
|
28001
|
+
return { status: "rejected", reason: "oversized" };
|
|
28002
|
+
const input = [...existing];
|
|
28003
|
+
input[handle.rowOrdinal] = replacement;
|
|
28004
|
+
return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
|
|
28005
|
+
} catch {
|
|
28006
|
+
return { status: "rejected", reason: "write" };
|
|
28007
|
+
} finally {
|
|
28008
|
+
releaseLock(lockPath);
|
|
27351
28009
|
}
|
|
27352
|
-
return "missing";
|
|
27353
28010
|
}
|
|
27354
28011
|
function yieldToEventLoop() {
|
|
27355
28012
|
return new Promise((resolve3) => setImmediate(resolve3));
|
|
@@ -27413,84 +28070,562 @@ function createTimelineEntry(fields) {
|
|
|
27413
28070
|
provider: fields.provider ?? null
|
|
27414
28071
|
};
|
|
27415
28072
|
}
|
|
27416
|
-
function createSystemEntry(type, time3) {
|
|
28073
|
+
function createSystemEntry(type, time3, backendSessionId) {
|
|
27417
28074
|
return {
|
|
27418
28075
|
session_id: null,
|
|
27419
28076
|
messages: [],
|
|
27420
28077
|
agent_responses: [],
|
|
27421
28078
|
provider: null,
|
|
27422
|
-
system: {
|
|
28079
|
+
system: {
|
|
28080
|
+
type,
|
|
28081
|
+
time: time3,
|
|
28082
|
+
...backendSessionId ? { backend_session_id: backendSessionId } : {}
|
|
28083
|
+
}
|
|
27423
28084
|
};
|
|
27424
28085
|
}
|
|
27425
|
-
function
|
|
28086
|
+
function resolveResumableSession(rows, provider) {
|
|
28087
|
+
let candidateSessionId = null;
|
|
28088
|
+
let recoveryMarkerSeen = false;
|
|
28089
|
+
let stalledSessionId = null;
|
|
28090
|
+
let fencedSessionId = null;
|
|
27426
28091
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
27427
28092
|
const e = rows[i];
|
|
27428
|
-
if (e.system
|
|
27429
|
-
|
|
28093
|
+
if (e.system) {
|
|
28094
|
+
if (e.system.type === "stall_recovery_attempt") {
|
|
28095
|
+
if (!recoveryMarkerSeen) {
|
|
28096
|
+
recoveryMarkerSeen = true;
|
|
28097
|
+
stalledSessionId = e.system.backend_session_id ?? null;
|
|
28098
|
+
}
|
|
28099
|
+
continue;
|
|
28100
|
+
}
|
|
28101
|
+
if (e.system.type === "stall_recovery_clear") {
|
|
28102
|
+
if (!recoveryMarkerSeen)
|
|
28103
|
+
recoveryMarkerSeen = true;
|
|
28104
|
+
continue;
|
|
28105
|
+
}
|
|
28106
|
+
if (e.system.type === "stall_recovery" && fencedSessionId === null) {
|
|
28107
|
+
fencedSessionId = e.system.backend_session_id ?? null;
|
|
28108
|
+
}
|
|
28109
|
+
if (candidateSessionId !== null) {
|
|
28110
|
+
return {
|
|
28111
|
+
kind: "session",
|
|
28112
|
+
sessionId: candidateSessionId,
|
|
28113
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
28114
|
+
fencedSessionId
|
|
28115
|
+
};
|
|
28116
|
+
}
|
|
28117
|
+
return {
|
|
28118
|
+
kind: "barrier",
|
|
28119
|
+
type: e.system.type,
|
|
28120
|
+
forgottenSessionId: e.system.backend_session_id ?? null,
|
|
28121
|
+
fencedSessionId
|
|
28122
|
+
};
|
|
28123
|
+
}
|
|
27430
28124
|
if (!e.session_id)
|
|
27431
28125
|
continue;
|
|
27432
28126
|
if (provider && e.provider !== provider)
|
|
27433
28127
|
continue;
|
|
27434
|
-
|
|
28128
|
+
if (candidateSessionId === null) {
|
|
28129
|
+
candidateSessionId = e.session_id;
|
|
28130
|
+
continue;
|
|
28131
|
+
}
|
|
28132
|
+
if (candidateSessionId !== e.session_id)
|
|
28133
|
+
break;
|
|
27435
28134
|
}
|
|
27436
|
-
|
|
28135
|
+
if (candidateSessionId !== null) {
|
|
28136
|
+
return {
|
|
28137
|
+
kind: "session",
|
|
28138
|
+
sessionId: candidateSessionId,
|
|
28139
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
28140
|
+
fencedSessionId
|
|
28141
|
+
};
|
|
28142
|
+
}
|
|
28143
|
+
return { kind: "none", stalledSessionId, fencedSessionId };
|
|
27437
28144
|
}
|
|
27438
28145
|
// src/timeline/recorder.ts
|
|
27439
28146
|
var MAX_AGENT_RESPONSES = 5;
|
|
27440
|
-
|
|
27441
|
-
|
|
27442
|
-
|
|
27443
|
-
|
|
28147
|
+
var MAX_AGENT_RESPONSE_BYTES = 65536;
|
|
28148
|
+
var MAX_PENDING_COMMITS_PER_AGENT = 8;
|
|
28149
|
+
var PENDING_COMMIT_TTL_MS = 15 * 60000;
|
|
28150
|
+
var TRUNCATION_MARKER = `
|
|
28151
|
+
… [truncated]`;
|
|
28152
|
+
function turnKey(agentId, owner) {
|
|
28153
|
+
return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
|
|
28154
|
+
}
|
|
28155
|
+
function sameOwner(left, right) {
|
|
28156
|
+
return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
|
|
28157
|
+
}
|
|
28158
|
+
function utf8Prefix2(text2, maxBytes) {
|
|
28159
|
+
if (maxBytes <= 0)
|
|
28160
|
+
return "";
|
|
28161
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
28162
|
+
return text2;
|
|
28163
|
+
let low = 0;
|
|
28164
|
+
let high = text2.length;
|
|
28165
|
+
while (low < high) {
|
|
28166
|
+
const mid = Math.ceil((low + high) / 2);
|
|
28167
|
+
let end2 = mid;
|
|
28168
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
28169
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
28170
|
+
end2 -= 1;
|
|
28171
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
28172
|
+
low = mid;
|
|
28173
|
+
else
|
|
28174
|
+
high = mid - 1;
|
|
28175
|
+
}
|
|
28176
|
+
let end = low;
|
|
28177
|
+
const code = text2.charCodeAt(end - 1);
|
|
28178
|
+
if (code >= 55296 && code <= 56319)
|
|
28179
|
+
end -= 1;
|
|
28180
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
28181
|
+
end -= 1;
|
|
28182
|
+
return text2.slice(0, end);
|
|
28183
|
+
}
|
|
28184
|
+
function boundedResponse(text2, alreadyTruncated) {
|
|
28185
|
+
const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
|
|
28186
|
+
const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
|
|
28187
|
+
if (!needsTruncation)
|
|
28188
|
+
return text2;
|
|
28189
|
+
return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
|
|
28190
|
+
}
|
|
28191
|
+
function serializedTimelineBytes(entry) {
|
|
28192
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
28193
|
+
}
|
|
28194
|
+
function fitResponsesToRow(entry, responses) {
|
|
28195
|
+
let fitted = [...entry.agent_responses];
|
|
28196
|
+
for (const response of responses) {
|
|
28197
|
+
fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
|
|
28198
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
|
|
28199
|
+
continue;
|
|
28200
|
+
const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
|
|
28201
|
+
let low = 0;
|
|
28202
|
+
let high = truncationBase.length;
|
|
28203
|
+
let replacement = null;
|
|
28204
|
+
while (low <= high) {
|
|
28205
|
+
const mid = Math.floor((low + high) / 2);
|
|
28206
|
+
let end = mid;
|
|
28207
|
+
const code = truncationBase.charCodeAt(end - 1);
|
|
28208
|
+
if (code >= 55296 && code <= 56319)
|
|
28209
|
+
end -= 1;
|
|
28210
|
+
const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
|
|
28211
|
+
const candidate = [...fitted.slice(0, -1), candidateResponse];
|
|
28212
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
|
|
28213
|
+
replacement = candidateResponse;
|
|
28214
|
+
low = mid + 1;
|
|
28215
|
+
} else {
|
|
28216
|
+
high = mid - 1;
|
|
28217
|
+
}
|
|
28218
|
+
}
|
|
28219
|
+
if (replacement === null)
|
|
28220
|
+
return null;
|
|
28221
|
+
fitted[fitted.length - 1] = replacement;
|
|
27444
28222
|
}
|
|
28223
|
+
return fitted;
|
|
27445
28224
|
}
|
|
27446
28225
|
function createTimelineRecorder(opts) {
|
|
27447
28226
|
const now = opts.now ?? (() => new Date);
|
|
27448
28227
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
27449
28228
|
const sessionByAgent = new Map;
|
|
28229
|
+
const resolveForAgent = (agentId, provider) => {
|
|
28230
|
+
const dir = dirFor(agentId);
|
|
28231
|
+
const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
|
|
28232
|
+
const control = readResumeControlState(dir);
|
|
28233
|
+
if (control.kind === "missing")
|
|
28234
|
+
return recent;
|
|
28235
|
+
if (control.kind === "invalid") {
|
|
28236
|
+
return {
|
|
28237
|
+
kind: "barrier",
|
|
28238
|
+
type: "reset_session",
|
|
28239
|
+
forgottenSessionId: null,
|
|
28240
|
+
fencedSessionId: null
|
|
28241
|
+
};
|
|
28242
|
+
}
|
|
28243
|
+
const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
|
|
28244
|
+
if (fullBarrier !== null) {
|
|
28245
|
+
return {
|
|
28246
|
+
kind: "barrier",
|
|
28247
|
+
type: fullBarrier,
|
|
28248
|
+
forgottenSessionId: null,
|
|
28249
|
+
fencedSessionId
|
|
28250
|
+
};
|
|
28251
|
+
}
|
|
28252
|
+
if (recent.kind === "session") {
|
|
28253
|
+
return {
|
|
28254
|
+
kind: "session",
|
|
28255
|
+
sessionId: recent.sessionId,
|
|
28256
|
+
stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
|
|
28257
|
+
fencedSessionId
|
|
28258
|
+
};
|
|
28259
|
+
}
|
|
28260
|
+
if (recent.kind === "barrier") {
|
|
28261
|
+
if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
|
|
28262
|
+
return { ...recent, fencedSessionId };
|
|
28263
|
+
}
|
|
28264
|
+
}
|
|
28265
|
+
return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
|
|
28266
|
+
};
|
|
28267
|
+
const sessionByEpoch = new Map;
|
|
28268
|
+
const barrierByAgent = new Map;
|
|
28269
|
+
const turnsByAgent = new Map;
|
|
28270
|
+
const activeTurnByAgent = new Map;
|
|
28271
|
+
const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
|
|
28272
|
+
const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
|
|
28273
|
+
const statesFor = (agentId) => {
|
|
28274
|
+
let states = turnsByAgent.get(agentId);
|
|
28275
|
+
if (!states) {
|
|
28276
|
+
states = new Map;
|
|
28277
|
+
turnsByAgent.set(agentId, states);
|
|
28278
|
+
}
|
|
28279
|
+
return states;
|
|
28280
|
+
};
|
|
28281
|
+
const diagnostic = (agentId, code, reason) => {
|
|
28282
|
+
try {
|
|
28283
|
+
opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
|
|
28284
|
+
} catch {}
|
|
28285
|
+
};
|
|
28286
|
+
const applyRewrite = (agentId, rewrite) => {
|
|
28287
|
+
for (const state of statesFor(agentId).values()) {
|
|
28288
|
+
if (!state.handle)
|
|
28289
|
+
continue;
|
|
28290
|
+
const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
|
|
28291
|
+
if (refreshed) {
|
|
28292
|
+
state.handle = refreshed;
|
|
28293
|
+
} else if (state.handle.filename === rewrite.filename) {
|
|
28294
|
+
state.handle = undefined;
|
|
28295
|
+
state.rowFenced = true;
|
|
28296
|
+
diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
|
|
28297
|
+
}
|
|
28298
|
+
}
|
|
28299
|
+
};
|
|
28300
|
+
const handleTrackedResult = (agentId, state, result) => {
|
|
28301
|
+
if (result.status === "written") {
|
|
28302
|
+
applyRewrite(agentId, result.rewrite);
|
|
28303
|
+
if (state && result.handle) {
|
|
28304
|
+
state.handle = result.handle;
|
|
28305
|
+
state.rowFenced = false;
|
|
28306
|
+
}
|
|
28307
|
+
return "written";
|
|
28308
|
+
}
|
|
28309
|
+
if (result.reason === "lock" || result.reason === "write")
|
|
28310
|
+
return "retryable";
|
|
28311
|
+
if (state && result.reason !== "oversized")
|
|
28312
|
+
state.rowFenced = true;
|
|
28313
|
+
diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
|
|
28314
|
+
return "terminal";
|
|
28315
|
+
};
|
|
28316
|
+
const tryCommit = (agentId, state) => {
|
|
28317
|
+
if (state.responses.length === 0)
|
|
28318
|
+
return "written";
|
|
28319
|
+
const dir = dirFor(agentId);
|
|
28320
|
+
if (!prepareTimelineDirectory(dir)) {
|
|
28321
|
+
diagnostic(agentId, "timeline_directory_unavailable");
|
|
28322
|
+
return "terminal";
|
|
28323
|
+
}
|
|
28324
|
+
if (state.handle) {
|
|
28325
|
+
const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
|
|
28326
|
+
const fitted2 = fitResponsesToRow(entry2, state.responses);
|
|
28327
|
+
return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
|
|
28328
|
+
});
|
|
28329
|
+
return handleTrackedResult(agentId, state, result2);
|
|
28330
|
+
}
|
|
28331
|
+
if (state.rowFenced || state.pendingMode === "handle")
|
|
28332
|
+
return "terminal";
|
|
28333
|
+
if (state.owner.barrierGeneration !== currentBarrier(agentId))
|
|
28334
|
+
return "terminal";
|
|
28335
|
+
const entry = createTimelineEntry({
|
|
28336
|
+
messages: [],
|
|
28337
|
+
sessionId: state.backendSessionId,
|
|
28338
|
+
provider: state.provider
|
|
28339
|
+
});
|
|
28340
|
+
const fitted = fitResponsesToRow(entry, state.responses);
|
|
28341
|
+
if (!fitted) {
|
|
28342
|
+
diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
|
|
28343
|
+
return "terminal";
|
|
28344
|
+
}
|
|
28345
|
+
entry.agent_responses = fitted;
|
|
28346
|
+
const result = appendTrackedEntry(dir, entry, now());
|
|
28347
|
+
return handleTrackedResult(agentId, state, result);
|
|
28348
|
+
};
|
|
28349
|
+
const deleteState = (agentId, key) => {
|
|
28350
|
+
const states = statesFor(agentId);
|
|
28351
|
+
states.delete(key);
|
|
28352
|
+
if (activeTurnByAgent.get(agentId) === key)
|
|
28353
|
+
activeTurnByAgent.delete(agentId);
|
|
28354
|
+
if (states.size === 0)
|
|
28355
|
+
turnsByAgent.delete(agentId);
|
|
28356
|
+
};
|
|
28357
|
+
const retryPending = (agentId) => {
|
|
28358
|
+
const states = turnsByAgent.get(agentId);
|
|
28359
|
+
if (!states)
|
|
28360
|
+
return;
|
|
28361
|
+
const nowMs = now().getTime();
|
|
28362
|
+
for (const [key, state] of [...states]) {
|
|
28363
|
+
if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
|
|
28364
|
+
deleteState(agentId, key);
|
|
28365
|
+
continue;
|
|
28366
|
+
}
|
|
28367
|
+
if (!state.finalized || state.pendingSinceMs === undefined)
|
|
28368
|
+
continue;
|
|
28369
|
+
if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
|
|
28370
|
+
diagnostic(agentId, "timeline_pending_commit_expired");
|
|
28371
|
+
deleteState(agentId, key);
|
|
28372
|
+
continue;
|
|
28373
|
+
}
|
|
28374
|
+
const result = tryCommit(agentId, state);
|
|
28375
|
+
if (result === "written") {
|
|
28376
|
+
state.responses = [];
|
|
28377
|
+
state.pendingSinceMs = undefined;
|
|
28378
|
+
state.pendingMode = undefined;
|
|
28379
|
+
state.completedAtMs = nowMs;
|
|
28380
|
+
} else if (result === "terminal") {
|
|
28381
|
+
deleteState(agentId, key);
|
|
28382
|
+
}
|
|
28383
|
+
}
|
|
28384
|
+
const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
|
|
28385
|
+
while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
|
|
28386
|
+
const oldest = completed.shift();
|
|
28387
|
+
if (oldest)
|
|
28388
|
+
deleteState(agentId, oldest[0]);
|
|
28389
|
+
}
|
|
28390
|
+
};
|
|
28391
|
+
const retainPending = (agentId, key, state) => {
|
|
28392
|
+
const states = statesFor(agentId);
|
|
28393
|
+
const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
|
|
28394
|
+
if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
|
|
28395
|
+
diagnostic(agentId, "timeline_pending_commit_overflow");
|
|
28396
|
+
deleteState(agentId, key);
|
|
28397
|
+
return;
|
|
28398
|
+
}
|
|
28399
|
+
state.pendingSinceMs ??= now().getTime();
|
|
28400
|
+
state.pendingMode = state.handle ? "handle" : "fallback";
|
|
28401
|
+
};
|
|
28402
|
+
const appendOwnerless = (agentId, messages) => {
|
|
28403
|
+
if (messages.length === 0)
|
|
28404
|
+
return;
|
|
28405
|
+
const dir = dirFor(agentId);
|
|
28406
|
+
if (!prepareTimelineDirectory(dir))
|
|
28407
|
+
return;
|
|
28408
|
+
const result = appendTrackedEntry(dir, createTimelineEntry({
|
|
28409
|
+
messages,
|
|
28410
|
+
sessionId: sessionByAgent.get(agentId) ?? null,
|
|
28411
|
+
provider: opts.providerFor?.(agentId) ?? null
|
|
28412
|
+
}), now());
|
|
28413
|
+
handleTrackedResult(agentId, null, result);
|
|
28414
|
+
};
|
|
27450
28415
|
return {
|
|
27451
|
-
|
|
27452
|
-
|
|
28416
|
+
barrierGeneration(agentId) {
|
|
28417
|
+
return currentBarrier(agentId);
|
|
28418
|
+
},
|
|
28419
|
+
beginTurn(agentId, owner) {
|
|
28420
|
+
retryPending(agentId);
|
|
28421
|
+
const states = statesFor(agentId);
|
|
28422
|
+
if (owner.barrierGeneration !== currentBarrier(agentId)) {
|
|
28423
|
+
diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
|
|
28424
|
+
return;
|
|
28425
|
+
}
|
|
28426
|
+
const key = turnKey(agentId, owner);
|
|
28427
|
+
if (!states.has(key)) {
|
|
28428
|
+
states.set(key, {
|
|
28429
|
+
owner: { ...owner },
|
|
28430
|
+
provider: opts.providerFor?.(agentId) ?? null,
|
|
28431
|
+
backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
|
|
28432
|
+
responses: [],
|
|
28433
|
+
rowFenced: false,
|
|
28434
|
+
finalized: false
|
|
28435
|
+
});
|
|
28436
|
+
}
|
|
28437
|
+
activeTurnByAgent.set(agentId, key);
|
|
27453
28438
|
},
|
|
27454
|
-
|
|
28439
|
+
recordInboxPull(agentId, owner, messages) {
|
|
28440
|
+
retryPending(agentId);
|
|
27455
28441
|
if (messages.length === 0)
|
|
27456
28442
|
return;
|
|
28443
|
+
if (!owner) {
|
|
28444
|
+
appendOwnerless(agentId, messages);
|
|
28445
|
+
return;
|
|
28446
|
+
}
|
|
28447
|
+
const key = turnKey(agentId, owner);
|
|
28448
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28449
|
+
if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
|
|
28450
|
+
appendOwnerless(agentId, messages);
|
|
28451
|
+
return;
|
|
28452
|
+
}
|
|
27457
28453
|
const dir = dirFor(agentId);
|
|
27458
28454
|
if (!prepareTimelineDirectory(dir))
|
|
27459
28455
|
return;
|
|
27460
|
-
|
|
27461
|
-
|
|
27462
|
-
|
|
27463
|
-
|
|
27464
|
-
|
|
28456
|
+
const stamp = now();
|
|
28457
|
+
let result;
|
|
28458
|
+
if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
|
|
28459
|
+
result = updateTrackedEntry(dir, state.handle, (entry) => ({
|
|
28460
|
+
...entry,
|
|
28461
|
+
messages: [...entry.messages, ...messages],
|
|
28462
|
+
session_id: state.backendSessionId,
|
|
28463
|
+
provider: state.provider
|
|
28464
|
+
}));
|
|
28465
|
+
} else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
|
|
28466
|
+
result = appendTrackedEntry(dir, createTimelineEntry({
|
|
28467
|
+
messages,
|
|
28468
|
+
sessionId: state.backendSessionId,
|
|
28469
|
+
provider: state.provider
|
|
28470
|
+
}), stamp);
|
|
28471
|
+
} else {
|
|
28472
|
+
appendOwnerless(agentId, messages);
|
|
28473
|
+
return;
|
|
28474
|
+
}
|
|
28475
|
+
handleTrackedResult(agentId, state, result);
|
|
28476
|
+
},
|
|
28477
|
+
recordAssistantMessage(agentId, owner, text2, truncated = false) {
|
|
28478
|
+
retryPending(agentId);
|
|
28479
|
+
const key = turnKey(agentId, owner);
|
|
28480
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28481
|
+
if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
|
|
28482
|
+
diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
|
|
28483
|
+
return;
|
|
28484
|
+
}
|
|
28485
|
+
state.responses.push(boundedResponse(text2, truncated));
|
|
28486
|
+
if (state.responses.length > MAX_AGENT_RESPONSES) {
|
|
28487
|
+
state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
|
|
28488
|
+
}
|
|
27465
28489
|
},
|
|
27466
|
-
|
|
27467
|
-
|
|
27468
|
-
|
|
28490
|
+
finalizeTurn(agentId, owner) {
|
|
28491
|
+
retryPending(agentId);
|
|
28492
|
+
const key = turnKey(agentId, owner);
|
|
28493
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
28494
|
+
if (!state || state.finalized || !sameOwner(state.owner, owner))
|
|
27469
28495
|
return;
|
|
27470
|
-
const
|
|
27471
|
-
|
|
28496
|
+
const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
|
|
28497
|
+
state.finalized = true;
|
|
28498
|
+
activeTurnByAgent.delete(agentId);
|
|
28499
|
+
if (state.responses.length === 0) {
|
|
28500
|
+
state.completedAtMs = now().getTime();
|
|
27472
28501
|
return;
|
|
27473
|
-
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
|
|
27478
|
-
|
|
27479
|
-
|
|
28502
|
+
}
|
|
28503
|
+
if (!state.handle && !fallbackAuthorized) {
|
|
28504
|
+
diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
|
|
28505
|
+
deleteState(agentId, key);
|
|
28506
|
+
return;
|
|
28507
|
+
}
|
|
28508
|
+
const result = tryCommit(agentId, state);
|
|
28509
|
+
if (result === "written") {
|
|
28510
|
+
state.responses = [];
|
|
28511
|
+
state.completedAtMs = now().getTime();
|
|
28512
|
+
} else if (result === "terminal") {
|
|
28513
|
+
deleteState(agentId, key);
|
|
28514
|
+
} else {
|
|
28515
|
+
retainPending(agentId, key, state);
|
|
28516
|
+
}
|
|
28517
|
+
},
|
|
28518
|
+
fenceSession(agentId) {
|
|
28519
|
+
retryPending(agentId);
|
|
28520
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
28521
|
+
activeTurnByAgent.delete(agentId);
|
|
28522
|
+
const states = turnsByAgent.get(agentId);
|
|
28523
|
+
if (!states)
|
|
28524
|
+
return;
|
|
28525
|
+
for (const [key, state] of [...states]) {
|
|
28526
|
+
if (!state.handle) {
|
|
28527
|
+
diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
|
|
28528
|
+
deleteState(agentId, key);
|
|
28529
|
+
}
|
|
28530
|
+
}
|
|
28531
|
+
},
|
|
28532
|
+
setSession(agentId, sessionId, sessionInstanceId) {
|
|
28533
|
+
retryPending(agentId);
|
|
28534
|
+
const dir = dirFor(agentId);
|
|
28535
|
+
if (!prepareTimelineDirectory(dir))
|
|
28536
|
+
return false;
|
|
28537
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
28538
|
+
...state,
|
|
28539
|
+
fullBarrier: null,
|
|
28540
|
+
attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
|
|
28541
|
+
}));
|
|
28542
|
+
if (!persisted)
|
|
28543
|
+
return false;
|
|
28544
|
+
sessionByAgent.set(agentId, sessionId);
|
|
28545
|
+
if (sessionInstanceId) {
|
|
28546
|
+
sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
|
|
28547
|
+
for (const state of statesFor(agentId).values()) {
|
|
28548
|
+
if (state.owner.sessionInstanceId === sessionInstanceId)
|
|
28549
|
+
state.backendSessionId = sessionId;
|
|
28550
|
+
}
|
|
28551
|
+
}
|
|
28552
|
+
return true;
|
|
27480
28553
|
},
|
|
27481
28554
|
resumeSessionId(agentId, provider) {
|
|
27482
|
-
const
|
|
27483
|
-
return
|
|
28555
|
+
const resolution = resolveForAgent(agentId, provider);
|
|
28556
|
+
return resolution.kind === "session" ? resolution.sessionId : null;
|
|
28557
|
+
},
|
|
28558
|
+
resolveResumeSession(agentId, provider) {
|
|
28559
|
+
return resolveForAgent(agentId, provider);
|
|
27484
28560
|
},
|
|
27485
|
-
|
|
28561
|
+
recordSessionStall(agentId, sessionId) {
|
|
28562
|
+
return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
|
|
28563
|
+
},
|
|
28564
|
+
clearSessionStall(agentId, sessionId) {
|
|
28565
|
+
return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
|
|
28566
|
+
},
|
|
28567
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
28568
|
+
retryPending(agentId);
|
|
27486
28569
|
const dir = dirFor(agentId);
|
|
27487
|
-
sessionByAgent.delete(agentId);
|
|
27488
28570
|
if (!prepareTimelineDirectory(dir))
|
|
27489
|
-
return;
|
|
28571
|
+
return false;
|
|
27490
28572
|
const stamp = now();
|
|
27491
|
-
|
|
28573
|
+
let persisted = false;
|
|
28574
|
+
if (barrierType === "reset_session" || barrierType === "nap") {
|
|
28575
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
28576
|
+
...state,
|
|
28577
|
+
attemptedSessionId: null,
|
|
28578
|
+
fencedSessionId: null,
|
|
28579
|
+
fullBarrier: barrierType
|
|
28580
|
+
}));
|
|
28581
|
+
} else if (barrierType === "stall_recovery") {
|
|
28582
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
28583
|
+
...state,
|
|
28584
|
+
attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
|
|
28585
|
+
fencedSessionId: forgottenSessionId ?? null,
|
|
28586
|
+
fullBarrier: null
|
|
28587
|
+
}));
|
|
28588
|
+
}
|
|
28589
|
+
if (!persisted)
|
|
28590
|
+
return false;
|
|
28591
|
+
sessionByAgent.delete(agentId);
|
|
28592
|
+
for (const key of [...sessionByEpoch.keys()]) {
|
|
28593
|
+
if (key.startsWith(`${agentId}\x00`))
|
|
28594
|
+
sessionByEpoch.delete(key);
|
|
28595
|
+
}
|
|
28596
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
28597
|
+
activeTurnByAgent.delete(agentId);
|
|
28598
|
+
const states = turnsByAgent.get(agentId);
|
|
28599
|
+
if (states) {
|
|
28600
|
+
for (const [key, state] of [...states]) {
|
|
28601
|
+
if (!state.handle) {
|
|
28602
|
+
diagnostic(agentId, "timeline_fallback_rejected", "barrier");
|
|
28603
|
+
deleteState(agentId, key);
|
|
28604
|
+
}
|
|
28605
|
+
}
|
|
28606
|
+
}
|
|
28607
|
+
const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
|
|
28608
|
+
handleTrackedResult(agentId, null, result);
|
|
28609
|
+
return true;
|
|
27492
28610
|
}
|
|
27493
28611
|
};
|
|
28612
|
+
function appendStallMarker(agentId, type, sessionId) {
|
|
28613
|
+
retryPending(agentId);
|
|
28614
|
+
const dir = dirFor(agentId);
|
|
28615
|
+
if (!prepareTimelineDirectory(dir))
|
|
28616
|
+
return false;
|
|
28617
|
+
const stamp = now();
|
|
28618
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
28619
|
+
...state,
|
|
28620
|
+
attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
|
|
28621
|
+
fullBarrier: null
|
|
28622
|
+
}));
|
|
28623
|
+
if (!persisted)
|
|
28624
|
+
return false;
|
|
28625
|
+
const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
|
|
28626
|
+
handleTrackedResult(agentId, null, result);
|
|
28627
|
+
return true;
|
|
28628
|
+
}
|
|
27494
28629
|
}
|
|
27495
28630
|
// src/discovery.ts
|
|
27496
28631
|
import * as path9 from "path";
|
|
@@ -27630,6 +28765,10 @@ class MessageReminderScheduler {
|
|
|
27630
28765
|
}
|
|
27631
28766
|
arm(input) {
|
|
27632
28767
|
const key = reminderKey(input.agentId, input.channel);
|
|
28768
|
+
if (input.remindAfterMs === 0) {
|
|
28769
|
+
this.clearReminder(key);
|
|
28770
|
+
return { armed: false, reason: "disabled" };
|
|
28771
|
+
}
|
|
27633
28772
|
const latest = this.latestObservedSeq.get(key);
|
|
27634
28773
|
if (latest !== undefined && latest > input.sentSeq) {
|
|
27635
28774
|
return { armed: false, reason: "newer_message_observed" };
|
|
@@ -27857,6 +28996,78 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
|
|
|
27857
28996
|
};
|
|
27858
28997
|
}
|
|
27859
28998
|
|
|
28999
|
+
// src/daemon/daemonSelfSleep.ts
|
|
29000
|
+
var DAEMON_SELF_SLEEP_TIMEOUT_MS = 15 * 24 * 60 * 60 * 1000;
|
|
29001
|
+
var systemClock = {
|
|
29002
|
+
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
29003
|
+
clearTimer: (timer) => clearTimeout(timer)
|
|
29004
|
+
};
|
|
29005
|
+
|
|
29006
|
+
class DaemonSelfSleepScheduler {
|
|
29007
|
+
opts;
|
|
29008
|
+
clock;
|
|
29009
|
+
workingAgents = new Set;
|
|
29010
|
+
timer = null;
|
|
29011
|
+
generation = 0;
|
|
29012
|
+
started = false;
|
|
29013
|
+
stopped = false;
|
|
29014
|
+
constructor(opts) {
|
|
29015
|
+
this.opts = opts;
|
|
29016
|
+
this.clock = opts.clock ?? systemClock;
|
|
29017
|
+
}
|
|
29018
|
+
start() {
|
|
29019
|
+
if (this.started || this.stopped)
|
|
29020
|
+
return;
|
|
29021
|
+
this.started = true;
|
|
29022
|
+
this.arm();
|
|
29023
|
+
}
|
|
29024
|
+
observeMessage() {
|
|
29025
|
+
if (!this.started || this.stopped)
|
|
29026
|
+
return;
|
|
29027
|
+
this.arm();
|
|
29028
|
+
}
|
|
29029
|
+
observeAgentActivity(agentId, working) {
|
|
29030
|
+
if (this.stopped)
|
|
29031
|
+
return;
|
|
29032
|
+
if (working) {
|
|
29033
|
+
this.workingAgents.add(agentId);
|
|
29034
|
+
if (this.started)
|
|
29035
|
+
this.cancel();
|
|
29036
|
+
return;
|
|
29037
|
+
}
|
|
29038
|
+
if (this.workingAgents.delete(agentId) && this.started && this.workingAgents.size === 0)
|
|
29039
|
+
this.arm();
|
|
29040
|
+
}
|
|
29041
|
+
stop() {
|
|
29042
|
+
if (this.stopped)
|
|
29043
|
+
return;
|
|
29044
|
+
this.stopped = true;
|
|
29045
|
+
this.workingAgents.clear();
|
|
29046
|
+
this.cancel();
|
|
29047
|
+
}
|
|
29048
|
+
arm() {
|
|
29049
|
+
this.cancel();
|
|
29050
|
+
if (this.workingAgents.size > 0)
|
|
29051
|
+
return;
|
|
29052
|
+
const generation = this.generation;
|
|
29053
|
+
this.timer = this.clock.setTimer(() => {
|
|
29054
|
+
if (this.stopped || this.generation !== generation || this.workingAgents.size > 0)
|
|
29055
|
+
return;
|
|
29056
|
+
this.timer = null;
|
|
29057
|
+
this.generation += 1;
|
|
29058
|
+
this.opts.onSleep();
|
|
29059
|
+
}, DAEMON_SELF_SLEEP_TIMEOUT_MS);
|
|
29060
|
+
this.timer.unref?.();
|
|
29061
|
+
}
|
|
29062
|
+
cancel() {
|
|
29063
|
+
this.generation += 1;
|
|
29064
|
+
if (!this.timer)
|
|
29065
|
+
return;
|
|
29066
|
+
this.clock.clearTimer(this.timer);
|
|
29067
|
+
this.timer = null;
|
|
29068
|
+
}
|
|
29069
|
+
}
|
|
29070
|
+
|
|
27860
29071
|
// src/daemon/createDaemon.ts
|
|
27861
29072
|
var WARMUP_BACKOFF_MS = [250, 500, 1000, 2000, 4000];
|
|
27862
29073
|
var WARMUP_CEILING_MS = 30000;
|
|
@@ -28013,6 +29224,10 @@ async function createDaemon(opts) {
|
|
|
28013
29224
|
let channelRef = null;
|
|
28014
29225
|
let managerRef = null;
|
|
28015
29226
|
let reminderSchedulerRef = null;
|
|
29227
|
+
const selfSleepScheduler = opts.onSelfSleep ? new DaemonSelfSleepScheduler({
|
|
29228
|
+
onSleep: opts.onSelfSleep,
|
|
29229
|
+
...opts.selfSleepClock ? { clock: opts.selfSleepClock } : {}
|
|
29230
|
+
}) : null;
|
|
28016
29231
|
const emitBotAuditEvent = (agentId, event, context) => {
|
|
28017
29232
|
channelRef?.reportBotAuditEvent?.({
|
|
28018
29233
|
type: "bot_audit_event",
|
|
@@ -28025,11 +29240,15 @@ async function createDaemon(opts) {
|
|
|
28025
29240
|
const typingTracker = createTypingScopeTracker();
|
|
28026
29241
|
const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
|
|
28027
29242
|
const proxy = await startCredentialProxy(broker, {
|
|
28028
|
-
onInboxPullStart: (agentId) =>
|
|
29243
|
+
onInboxPullStart: (agentId) => ({
|
|
29244
|
+
modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
|
|
29245
|
+
owner: managerRef?.timelineTurnOwner(agentId) ?? null
|
|
29246
|
+
}),
|
|
28029
29247
|
onInboxPullResponse: (agentId, messages, observationToken) => {
|
|
28030
|
-
|
|
28031
|
-
|
|
28032
|
-
|
|
29248
|
+
const token = observationToken && typeof observationToken === "object" ? observationToken : null;
|
|
29249
|
+
timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
|
|
29250
|
+
if (typeof token?.modelSeenGeneration === "number") {
|
|
29251
|
+
channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
|
|
28033
29252
|
}
|
|
28034
29253
|
},
|
|
28035
29254
|
onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
|
|
@@ -28306,6 +29525,7 @@ async function createDaemon(opts) {
|
|
|
28306
29525
|
tickIntervalMs: opts.tickIntervalMs ?? 2000,
|
|
28307
29526
|
onAgentSession: (info) => void channel2.reportAgentSession(info),
|
|
28308
29527
|
onAgentActivity: (info) => {
|
|
29528
|
+
selfSleepScheduler?.observeAgentActivity(info.agentId, info.state === "running");
|
|
28309
29529
|
channel2.reportAgentActivity?.(info);
|
|
28310
29530
|
if (info.state === "starting" || info.state === "running") {
|
|
28311
29531
|
if (!typingHeartbeats.has(info.agentId)) {
|
|
@@ -28383,6 +29603,7 @@ async function createDaemon(opts) {
|
|
|
28383
29603
|
reportDiagnosticFailure: opts.reportDiagnosticFailure
|
|
28384
29604
|
}));
|
|
28385
29605
|
channel2.onWakeDesiredAdvance((cmd) => {
|
|
29606
|
+
selfSleepScheduler?.observeMessage();
|
|
28386
29607
|
reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
|
|
28387
29608
|
});
|
|
28388
29609
|
channel2.onCommand((cmd) => {
|
|
@@ -28407,6 +29628,7 @@ async function createDaemon(opts) {
|
|
|
28407
29628
|
});
|
|
28408
29629
|
channel2.connect();
|
|
28409
29630
|
await router.start();
|
|
29631
|
+
selfSleepScheduler?.start();
|
|
28410
29632
|
return {
|
|
28411
29633
|
isOpen: () => channel2.status === "open",
|
|
28412
29634
|
onOpen: (hook) => {
|
|
@@ -28416,6 +29638,7 @@ async function createDaemon(opts) {
|
|
|
28416
29638
|
},
|
|
28417
29639
|
proxyUrl: proxy.url,
|
|
28418
29640
|
stop: async () => {
|
|
29641
|
+
selfSleepScheduler?.stop();
|
|
28419
29642
|
reminderSchedulerRef?.clearAll();
|
|
28420
29643
|
for (const agentId of [...typingHeartbeats.keys()]) {
|
|
28421
29644
|
emitTypingStopsAndClear(agentId);
|
|
@@ -28476,10 +29699,12 @@ export {
|
|
|
28476
29699
|
RUNTIME_RAW_TRACE_AGENT_IDS_ENV,
|
|
28477
29700
|
RUNTIME_AUTH_ACTION_REQUIRED_PATTERNS,
|
|
28478
29701
|
LOCAL_MESSAGE_REMINDER_PATH,
|
|
29702
|
+
DEFAULT_TURN_SILENCE_POLICY,
|
|
28479
29703
|
DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
28480
29704
|
DEFAULT_STALE_THRESHOLD_MS,
|
|
28481
29705
|
DEFAULT_RESET_STUCK_THRESHOLD_MS,
|
|
28482
29706
|
DEFAULT_IDLE_TIMEOUT_MS,
|
|
29707
|
+
DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
28483
29708
|
DEFAULT_HELD_CONTEXT_LIMIT,
|
|
28484
29709
|
DEFAULT_CAPABILITY_RESOLVER,
|
|
28485
29710
|
CredentialBroker,
|