@alook/daemon 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.js +1424 -276
- package/dist/index.js +1392 -269
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -20288,19 +20288,23 @@ class ClaudeEventNormalizer {
|
|
|
20288
20288
|
const content = event?.message?.content;
|
|
20289
20289
|
if (!Array.isArray(content))
|
|
20290
20290
|
return;
|
|
20291
|
+
const completedText = [];
|
|
20291
20292
|
for (const block of content) {
|
|
20292
20293
|
if (block?.type === "thinking") {
|
|
20293
|
-
out.push({ kind: "
|
|
20294
|
+
out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
|
|
20294
20295
|
} else if (block?.type === "text") {
|
|
20295
20296
|
const text2 = block.text ?? "";
|
|
20296
20297
|
if (API_ERROR_RE.test(text2))
|
|
20297
20298
|
out.push({ kind: "error", message: text2 });
|
|
20298
20299
|
else
|
|
20299
|
-
|
|
20300
|
+
completedText.push(text2);
|
|
20300
20301
|
} else if (block?.type === "tool_use") {
|
|
20301
20302
|
out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
|
|
20302
20303
|
}
|
|
20303
20304
|
}
|
|
20305
|
+
if (completedText.length > 0) {
|
|
20306
|
+
out.push({ kind: "assistant_message_completed", text: completedText.join("") });
|
|
20307
|
+
}
|
|
20304
20308
|
}
|
|
20305
20309
|
handleUser(event, out) {
|
|
20306
20310
|
if (event.isReplay === true && typeof event.uuid === "string") {
|
|
@@ -20709,14 +20713,17 @@ class CodexEventNormalizer {
|
|
|
20709
20713
|
this.turnId = params.turn.id;
|
|
20710
20714
|
this.terminalTurn = null;
|
|
20711
20715
|
return [
|
|
20712
|
-
{
|
|
20713
|
-
|
|
20716
|
+
{
|
|
20717
|
+
kind: "turn_owner",
|
|
20718
|
+
receipt: this.turnReceipt(params.threadId, params.turn.id),
|
|
20719
|
+
nativeTurnId: params.turn.id
|
|
20720
|
+
}
|
|
20714
20721
|
];
|
|
20715
20722
|
case "item/reasoning/textDelta":
|
|
20716
20723
|
case "item/reasoning/summaryTextDelta":
|
|
20717
|
-
return [{ kind: "
|
|
20724
|
+
return [{ kind: "assistant_reasoning_delta", text: params?.delta ?? "" }];
|
|
20718
20725
|
case "item/agentMessage/delta":
|
|
20719
|
-
return [{ kind: "
|
|
20726
|
+
return [{ kind: "assistant_message_delta", text: params?.delta ?? "" }];
|
|
20720
20727
|
case "item/started":
|
|
20721
20728
|
return this.handleItemStarted(params);
|
|
20722
20729
|
case "item/completed":
|
|
@@ -20744,7 +20751,10 @@ class CodexEventNormalizer {
|
|
|
20744
20751
|
}
|
|
20745
20752
|
return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
20746
20753
|
case "error":
|
|
20747
|
-
|
|
20754
|
+
if (params?.willRetry === true) {
|
|
20755
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
|
|
20756
|
+
}
|
|
20757
|
+
return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
|
|
20748
20758
|
case "thread/tokenUsage/updated":
|
|
20749
20759
|
case "account/rateLimits/updated":
|
|
20750
20760
|
return mapCodexTelemetry(method, params);
|
|
@@ -20837,9 +20847,9 @@ class CodexEventNormalizer {
|
|
|
20837
20847
|
case "collabAgentToolCall":
|
|
20838
20848
|
return [{ kind: "tool_output", name: "collab_tool_call" }];
|
|
20839
20849
|
case "agentMessage":
|
|
20840
|
-
return [{ kind: "
|
|
20850
|
+
return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
|
|
20841
20851
|
case "reasoning":
|
|
20842
|
-
return [{ kind: "
|
|
20852
|
+
return [{ kind: "assistant_reasoning_completed", text: params?.item?.text ?? "" }];
|
|
20843
20853
|
default:
|
|
20844
20854
|
return [];
|
|
20845
20855
|
}
|
|
@@ -20872,7 +20882,13 @@ class CodexDriver {
|
|
|
20872
20882
|
lifetime: "session",
|
|
20873
20883
|
transport: { kind: "stdio_rpc", protocol: "codex.app-server.v1" },
|
|
20874
20884
|
wakeStart: "immediate",
|
|
20875
|
-
terminalOwnership: "transport_request"
|
|
20885
|
+
terminalOwnership: "transport_request",
|
|
20886
|
+
turnSilence: {
|
|
20887
|
+
nativeIdleTimeoutMs: 300000,
|
|
20888
|
+
daemonGraceMs: 60000,
|
|
20889
|
+
recoveryGraceMs: 60000,
|
|
20890
|
+
maxRecoveryExtensions: 1
|
|
20891
|
+
}
|
|
20876
20892
|
};
|
|
20877
20893
|
eventNormalizer = new CodexEventNormalizer;
|
|
20878
20894
|
requestId = 0;
|
|
@@ -21529,14 +21545,14 @@ class CursorAcpLane {
|
|
|
21529
21545
|
case "agent_message_chunk": {
|
|
21530
21546
|
const content = record2(update.content);
|
|
21531
21547
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
21532
|
-
this.events.emit("runtime_event", { kind: "
|
|
21548
|
+
this.events.emit("runtime_event", { kind: "assistant_message_delta", text: content.text });
|
|
21533
21549
|
}
|
|
21534
21550
|
return;
|
|
21535
21551
|
}
|
|
21536
21552
|
case "agent_thought_chunk": {
|
|
21537
21553
|
const content = record2(update.content);
|
|
21538
21554
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
21539
|
-
this.events.emit("runtime_event", { kind: "
|
|
21555
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_delta", text: content.text });
|
|
21540
21556
|
}
|
|
21541
21557
|
return;
|
|
21542
21558
|
}
|
|
@@ -22341,16 +22357,20 @@ class OpenCodeServiceLane {
|
|
|
22341
22357
|
}
|
|
22342
22358
|
switch (event.type) {
|
|
22343
22359
|
case "session.next.step.started":
|
|
22344
|
-
this.events.emit("runtime_event", {
|
|
22360
|
+
this.events.emit("runtime_event", {
|
|
22361
|
+
kind: "internal_progress",
|
|
22362
|
+
source: "opencode.service",
|
|
22363
|
+
itemType: "step_started"
|
|
22364
|
+
});
|
|
22345
22365
|
break;
|
|
22346
22366
|
case "session.next.text.ended":
|
|
22347
22367
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
22348
|
-
this.events.emit("runtime_event", { kind: "
|
|
22368
|
+
this.events.emit("runtime_event", { kind: "assistant_message_completed", text: data.text });
|
|
22349
22369
|
}
|
|
22350
22370
|
break;
|
|
22351
22371
|
case "session.next.reasoning.ended":
|
|
22352
22372
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
22353
|
-
this.events.emit("runtime_event", { kind: "
|
|
22373
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: data.text });
|
|
22354
22374
|
}
|
|
22355
22375
|
break;
|
|
22356
22376
|
case "session.next.tool.called":
|
|
@@ -23101,22 +23121,28 @@ function readPiSdkVersion() {
|
|
|
23101
23121
|
}
|
|
23102
23122
|
function mapPiSdkEvent(event, sessionId, state) {
|
|
23103
23123
|
if (event?.type === "message_update") {
|
|
23104
|
-
const d = event.
|
|
23124
|
+
const d = event.assistantMessageEvent ?? {};
|
|
23105
23125
|
switch (d.type) {
|
|
23106
23126
|
case "thinking_delta":
|
|
23107
|
-
return [{ kind: "
|
|
23127
|
+
return [{ kind: "assistant_reasoning_delta", text: d.delta ?? "" }];
|
|
23108
23128
|
case "text_delta":
|
|
23109
23129
|
state.sawTextDelta = true;
|
|
23110
|
-
return [{ kind: "
|
|
23111
|
-
case "text_end":
|
|
23112
|
-
|
|
23130
|
+
return [{ kind: "assistant_message_delta", text: d.delta ?? "" }];
|
|
23131
|
+
case "text_end": {
|
|
23132
|
+
state.sawTextDelta = false;
|
|
23133
|
+
return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
|
|
23134
|
+
}
|
|
23113
23135
|
case "error":
|
|
23114
|
-
return [{ kind: "error", message: d.
|
|
23136
|
+
return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
|
|
23115
23137
|
default:
|
|
23116
23138
|
return [];
|
|
23117
23139
|
}
|
|
23118
23140
|
}
|
|
23119
23141
|
switch (event?.type) {
|
|
23142
|
+
case "auto_retry_start":
|
|
23143
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
|
|
23144
|
+
case "auto_retry_end":
|
|
23145
|
+
return [{ kind: "runtime_recovery", stage: "recovered", source: "pi_auto_retry" }];
|
|
23120
23146
|
case "tool_execution_start":
|
|
23121
23147
|
return [{ kind: "tool_call", name: event.toolName ?? "unknown_tool", input: event.args ?? {} }];
|
|
23122
23148
|
case "tool_execution_end":
|
|
@@ -23281,6 +23307,13 @@ function assertAdapterCompatibility(registrationId, registeredCapabilities, adap
|
|
|
23281
23307
|
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))) {
|
|
23282
23308
|
throw new Error(`Adapter ${registrationId} has an invalid transport declaration`);
|
|
23283
23309
|
}
|
|
23310
|
+
if (execution.turnSilence !== undefined) {
|
|
23311
|
+
const silence = execution.turnSilence;
|
|
23312
|
+
const safeInteger = (value, minimum) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
|
|
23313
|
+
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)) {
|
|
23314
|
+
throw new Error(`Adapter ${registrationId} has an invalid turnSilence declaration`);
|
|
23315
|
+
}
|
|
23316
|
+
}
|
|
23284
23317
|
const capabilities = registeredCapabilities;
|
|
23285
23318
|
const declaredLifetime = capabilities?.sessionLifetime === "persistent" ? "session" : "turn";
|
|
23286
23319
|
if (execution.lifetime !== declaredLifetime) {
|
|
@@ -23370,6 +23403,12 @@ function capabilitiesFor(backend) {
|
|
|
23370
23403
|
return builtinRegistry.get(backend).capabilities;
|
|
23371
23404
|
}
|
|
23372
23405
|
|
|
23406
|
+
// agent-driver/dist/internal/adapter.js
|
|
23407
|
+
var DEFAULT_NATIVE_IDLE_TIMEOUT_MS = 300000;
|
|
23408
|
+
var DEFAULT_DAEMON_GRACE_MS = 60000;
|
|
23409
|
+
var DEFAULT_RECOVERY_GRACE_MS = 60000;
|
|
23410
|
+
var DEFAULT_MAX_RECOVERY_EXTENSIONS = 1;
|
|
23411
|
+
|
|
23373
23412
|
// agent-driver/dist/controller/event-queue.js
|
|
23374
23413
|
var MAX_BUFFERED_BYTES = 4194304;
|
|
23375
23414
|
|
|
@@ -23566,6 +23605,62 @@ function stableErrorCode(value, fallback) {
|
|
|
23566
23605
|
|
|
23567
23606
|
// agent-driver/dist/controller/logical-session.js
|
|
23568
23607
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
23608
|
+
var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
|
|
23609
|
+
var WORK_HEARTBEAT_MIN_INTERVAL_MS = 1000;
|
|
23610
|
+
function emptySemanticAssembler() {
|
|
23611
|
+
return { chunks: [], bytes: 0, truncated: false };
|
|
23612
|
+
}
|
|
23613
|
+
function utf8Prefix(text2, maxBytes) {
|
|
23614
|
+
if (maxBytes <= 0 || text2.length === 0)
|
|
23615
|
+
return "";
|
|
23616
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
23617
|
+
return text2;
|
|
23618
|
+
let low = 0;
|
|
23619
|
+
let high = text2.length;
|
|
23620
|
+
while (low < high) {
|
|
23621
|
+
const mid = Math.ceil((low + high) / 2);
|
|
23622
|
+
let end2 = mid;
|
|
23623
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
23624
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
23625
|
+
end2 -= 1;
|
|
23626
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
23627
|
+
low = mid;
|
|
23628
|
+
else
|
|
23629
|
+
high = mid - 1;
|
|
23630
|
+
}
|
|
23631
|
+
let end = low;
|
|
23632
|
+
const code = text2.charCodeAt(end - 1);
|
|
23633
|
+
if (code >= 55296 && code <= 56319)
|
|
23634
|
+
end -= 1;
|
|
23635
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
23636
|
+
end -= 1;
|
|
23637
|
+
return text2.slice(0, end);
|
|
23638
|
+
}
|
|
23639
|
+
function appendSemanticFragment(buffer, text2) {
|
|
23640
|
+
if (text2.length === 0 || buffer.truncated)
|
|
23641
|
+
return;
|
|
23642
|
+
const remaining = SEMANTIC_ASSEMBLER_MAX_BYTES - buffer.bytes;
|
|
23643
|
+
const bytes = Buffer.byteLength(text2, "utf8");
|
|
23644
|
+
if (bytes <= remaining) {
|
|
23645
|
+
buffer.chunks.push(text2);
|
|
23646
|
+
buffer.bytes += bytes;
|
|
23647
|
+
return;
|
|
23648
|
+
}
|
|
23649
|
+
const prefix = utf8Prefix(text2, remaining);
|
|
23650
|
+
if (prefix.length > 0) {
|
|
23651
|
+
buffer.chunks.push(prefix);
|
|
23652
|
+
buffer.bytes += Buffer.byteLength(prefix, "utf8");
|
|
23653
|
+
}
|
|
23654
|
+
buffer.truncated = true;
|
|
23655
|
+
}
|
|
23656
|
+
function finishSemanticAssembler(buffer) {
|
|
23657
|
+
return { text: buffer.chunks.join(""), truncated: buffer.truncated };
|
|
23658
|
+
}
|
|
23659
|
+
function boundedSemanticCompletion(text2) {
|
|
23660
|
+
const buffer = emptySemanticAssembler();
|
|
23661
|
+
appendSemanticFragment(buffer, text2);
|
|
23662
|
+
return finishSemanticAssembler(buffer);
|
|
23663
|
+
}
|
|
23569
23664
|
function driverError(category, code, message2, retryable = false) {
|
|
23570
23665
|
return { category, code, message: scrubDriverErrorMessage(message2), retryable };
|
|
23571
23666
|
}
|
|
@@ -23639,6 +23734,7 @@ class LogicalAgentSession {
|
|
|
23639
23734
|
resumeOutcome;
|
|
23640
23735
|
eventQueue;
|
|
23641
23736
|
behavior;
|
|
23737
|
+
turnSilence;
|
|
23642
23738
|
constructor(backend, config2, launch, adapter, capabilities2, host, prepared, hostReleaseTimeoutMs) {
|
|
23643
23739
|
this.backend = backend;
|
|
23644
23740
|
this.config = config2;
|
|
@@ -23649,6 +23745,18 @@ class LogicalAgentSession {
|
|
|
23649
23745
|
this.hostReleaseTimeoutMs = hostReleaseTimeoutMs;
|
|
23650
23746
|
this.capabilities = capabilities2;
|
|
23651
23747
|
this.behavior = this.capabilities;
|
|
23748
|
+
const declaredSilence = adapter.execution.turnSilence;
|
|
23749
|
+
const nativeIdleTimeoutMs = declaredSilence?.nativeIdleTimeoutMs ?? DEFAULT_NATIVE_IDLE_TIMEOUT_MS;
|
|
23750
|
+
const daemonGraceMs = declaredSilence?.daemonGraceMs ?? DEFAULT_DAEMON_GRACE_MS;
|
|
23751
|
+
const recoveryGraceMs = declaredSilence?.recoveryGraceMs ?? DEFAULT_RECOVERY_GRACE_MS;
|
|
23752
|
+
const maxRecoveryExtensions = declaredSilence?.maxRecoveryExtensions ?? DEFAULT_MAX_RECOVERY_EXTENSIONS;
|
|
23753
|
+
this.turnSilence = {
|
|
23754
|
+
nativeIdleTimeoutMs,
|
|
23755
|
+
daemonGraceMs,
|
|
23756
|
+
recoveryGraceMs,
|
|
23757
|
+
maxRecoveryExtensions,
|
|
23758
|
+
normalBudgetMs: nativeIdleTimeoutMs + daemonGraceMs
|
|
23759
|
+
};
|
|
23652
23760
|
this.resumeOutcome = launch.resumeSessionId ? "pending" : "not_requested";
|
|
23653
23761
|
this.sessionInstanceId = host.createId();
|
|
23654
23762
|
this.closed = new Promise((resolve3) => {
|
|
@@ -23753,6 +23861,7 @@ class LogicalAgentSession {
|
|
|
23753
23861
|
lastEventSequence: this.eventSequence,
|
|
23754
23862
|
diagnostics: {
|
|
23755
23863
|
deliveryPhase: this.deliveryPhase(),
|
|
23864
|
+
turnSilence: this.turnSilence,
|
|
23756
23865
|
metrics: {
|
|
23757
23866
|
physicalOpenCount: this.metricValue(this.physicalOpenCount),
|
|
23758
23867
|
turnCount: this.metricValue(this.turnCount),
|
|
@@ -23843,7 +23952,14 @@ class LogicalAgentSession {
|
|
|
23843
23952
|
const turnId = this.host.createId();
|
|
23844
23953
|
const commandIds = messages.map((message2) => message2.id);
|
|
23845
23954
|
const terminalOwner = this.adapter.beginTurn?.();
|
|
23846
|
-
this.activeTurn = {
|
|
23955
|
+
this.activeTurn = {
|
|
23956
|
+
turnId,
|
|
23957
|
+
commandIds: [...commandIds],
|
|
23958
|
+
...terminalOwner ? { terminalOwner } : {},
|
|
23959
|
+
pendingMessage: emptySemanticAssembler(),
|
|
23960
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
23961
|
+
lastWorkHeartbeatAt: null
|
|
23962
|
+
};
|
|
23847
23963
|
this.turnError = undefined;
|
|
23848
23964
|
this.interruptedTurnId = undefined;
|
|
23849
23965
|
this.processTurnEnded = false;
|
|
@@ -24041,14 +24157,36 @@ class LogicalAgentSession {
|
|
|
24041
24157
|
return;
|
|
24042
24158
|
}
|
|
24043
24159
|
this.activeTurn.terminalOwner = event.receipt;
|
|
24160
|
+
const nativeTurnId = event.nativeTurnId?.trim();
|
|
24161
|
+
if (nativeTurnId && nativeTurnId.length <= 512 && /^[A-Za-z0-9._:-]+$/.test(nativeTurnId)) {
|
|
24162
|
+
this.emit({
|
|
24163
|
+
type: "backend_turn_started",
|
|
24164
|
+
turnId: this.activeTurn.turnId,
|
|
24165
|
+
backendTurnId: nativeTurnId
|
|
24166
|
+
});
|
|
24167
|
+
}
|
|
24044
24168
|
return;
|
|
24045
|
-
case "
|
|
24046
|
-
|
|
24047
|
-
|
|
24169
|
+
case "assistant_reasoning_delta":
|
|
24170
|
+
case "assistant_message_delta":
|
|
24171
|
+
if (turnId && this.activeTurn?.turnId === turnId && event.text.length > 0) {
|
|
24172
|
+
appendSemanticFragment(event.kind === "assistant_message_delta" ? this.activeTurn.pendingMessage : this.activeTurn.pendingReasoning, event.text);
|
|
24173
|
+
const now = this.host.now();
|
|
24174
|
+
if (this.activeTurn.lastWorkHeartbeatAt === null || now - this.activeTurn.lastWorkHeartbeatAt >= WORK_HEARTBEAT_MIN_INTERVAL_MS) {
|
|
24175
|
+
this.activeTurn.lastWorkHeartbeatAt = now;
|
|
24176
|
+
this.emit({ type: "work_heartbeat", turnId });
|
|
24177
|
+
}
|
|
24178
|
+
}
|
|
24048
24179
|
return;
|
|
24049
|
-
case "
|
|
24050
|
-
|
|
24051
|
-
|
|
24180
|
+
case "assistant_reasoning_completed":
|
|
24181
|
+
case "assistant_message_completed":
|
|
24182
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
24183
|
+
const field = event.kind === "assistant_message_completed" ? "pendingMessage" : "pendingReasoning";
|
|
24184
|
+
this.activeTurn[field] = emptySemanticAssembler();
|
|
24185
|
+
if (event.text.length > 0) {
|
|
24186
|
+
const completed = boundedSemanticCompletion(event.text);
|
|
24187
|
+
this.emit({ type: event.kind, turnId, ...completed });
|
|
24188
|
+
}
|
|
24189
|
+
}
|
|
24052
24190
|
return;
|
|
24053
24191
|
case "tool_call":
|
|
24054
24192
|
this.outstandingToolUses += 1;
|
|
@@ -24091,9 +24229,19 @@ class LogicalAgentSession {
|
|
|
24091
24229
|
message: scrubDriverErrorMessage(event.message, "Runtime diagnostic")
|
|
24092
24230
|
});
|
|
24093
24231
|
return;
|
|
24232
|
+
case "runtime_recovery":
|
|
24233
|
+
this.emit({
|
|
24234
|
+
type: "recovery",
|
|
24235
|
+
turnId,
|
|
24236
|
+
stage: event.stage,
|
|
24237
|
+
source: event.source
|
|
24238
|
+
});
|
|
24239
|
+
return;
|
|
24094
24240
|
case "runtime_metric":
|
|
24095
|
-
if (event.name === "sse_reconnect" && event.increment === 1)
|
|
24241
|
+
if (event.name === "sse_reconnect" && event.increment === 1) {
|
|
24096
24242
|
this.sseReconnectCount += 1;
|
|
24243
|
+
this.emit({ type: "recovery", turnId, stage: "retrying", source: "transport_reconnect" });
|
|
24244
|
+
}
|
|
24097
24245
|
return;
|
|
24098
24246
|
case "telemetry": {
|
|
24099
24247
|
const details = jsonValue(event.attrs);
|
|
@@ -24116,6 +24264,18 @@ class LogicalAgentSession {
|
|
|
24116
24264
|
});
|
|
24117
24265
|
return;
|
|
24118
24266
|
case "turn_end":
|
|
24267
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
24268
|
+
const reasoning = finishSemanticAssembler(this.activeTurn.pendingReasoning);
|
|
24269
|
+
const message2 = finishSemanticAssembler(this.activeTurn.pendingMessage);
|
|
24270
|
+
this.activeTurn.pendingReasoning = emptySemanticAssembler();
|
|
24271
|
+
this.activeTurn.pendingMessage = emptySemanticAssembler();
|
|
24272
|
+
if (reasoning.text.length > 0) {
|
|
24273
|
+
this.emit({ type: "assistant_reasoning_completed", turnId, ...reasoning });
|
|
24274
|
+
}
|
|
24275
|
+
if (message2.text.length > 0) {
|
|
24276
|
+
this.emit({ type: "assistant_message_completed", turnId, ...message2 });
|
|
24277
|
+
}
|
|
24278
|
+
}
|
|
24119
24279
|
this.completeTurn(event.sessionId, physicalOwner, generation, event.turnOwner);
|
|
24120
24280
|
return;
|
|
24121
24281
|
}
|
|
@@ -24129,7 +24289,7 @@ class LogicalAgentSession {
|
|
|
24129
24289
|
reopenClosedLaneForWork(event, physicalOwner, generation) {
|
|
24130
24290
|
if (this.adapter.execution.lifetime === "turn")
|
|
24131
24291
|
return;
|
|
24132
|
-
const isRootWork = event.kind === "
|
|
24292
|
+
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";
|
|
24133
24293
|
if (!isRootWork)
|
|
24134
24294
|
return;
|
|
24135
24295
|
const tombstone = this.closedLaneTombstone;
|
|
@@ -24139,7 +24299,10 @@ class LogicalAgentSession {
|
|
|
24139
24299
|
this.activeTurn = {
|
|
24140
24300
|
turnId: tombstone.localTurnId,
|
|
24141
24301
|
commandIds: tombstone.commandIds,
|
|
24142
|
-
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {}
|
|
24302
|
+
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {},
|
|
24303
|
+
pendingMessage: emptySemanticAssembler(),
|
|
24304
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
24305
|
+
lastWorkHeartbeatAt: null
|
|
24143
24306
|
};
|
|
24144
24307
|
this.state = "working";
|
|
24145
24308
|
return tombstone.localTurnId;
|
|
@@ -26242,11 +26405,26 @@ function isActivelyWorking(agent2) {
|
|
|
26242
26405
|
return agent2.status === "running" && (leaseIsWorking(agent2.execution.lease) || agent2.pendingAdmissions.length > 0 || agent2.inbox.length > 0);
|
|
26243
26406
|
}
|
|
26244
26407
|
var DEFAULT_STALE_THRESHOLD_MS = 120000;
|
|
26245
|
-
var
|
|
26408
|
+
var DEFAULT_TURN_SILENCE_POLICY = {
|
|
26409
|
+
nativeIdleTimeoutMs: 300000,
|
|
26410
|
+
daemonGraceMs: 60000,
|
|
26411
|
+
recoveryGraceMs: 60000,
|
|
26412
|
+
maxRecoveryExtensions: 1,
|
|
26413
|
+
normalBudgetMs: 360000
|
|
26414
|
+
};
|
|
26415
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
26416
|
+
var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
|
|
26246
26417
|
var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
|
|
26247
26418
|
var DEFAULT_STOPPING_STUCK_THRESHOLD_MS = 30000;
|
|
26248
|
-
function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS) {
|
|
26249
|
-
return {
|
|
26419
|
+
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) {
|
|
26420
|
+
return {
|
|
26421
|
+
agents: {},
|
|
26422
|
+
staleThresholdMs,
|
|
26423
|
+
idleTimeoutMs,
|
|
26424
|
+
idleResetTimeoutMs,
|
|
26425
|
+
resetStuckThresholdMs,
|
|
26426
|
+
stoppingStuckThresholdMs
|
|
26427
|
+
};
|
|
26250
26428
|
}
|
|
26251
26429
|
function reduceManager(state, event) {
|
|
26252
26430
|
switch (event.type) {
|
|
@@ -26261,6 +26439,11 @@ function reduceManager(state, event) {
|
|
|
26261
26439
|
});
|
|
26262
26440
|
case "backend_session":
|
|
26263
26441
|
return mutate(state, event.agentId, (a) => {
|
|
26442
|
+
if (event.stalledBefore) {
|
|
26443
|
+
a.stalledSessionId = event.sessionId;
|
|
26444
|
+
} else if (a.stalledSessionId !== null && a.stalledSessionId !== event.sessionId) {
|
|
26445
|
+
a.stalledSessionId = null;
|
|
26446
|
+
}
|
|
26264
26447
|
a.sessionId = event.sessionId;
|
|
26265
26448
|
});
|
|
26266
26449
|
case "attach_session": {
|
|
@@ -26273,7 +26456,17 @@ function reduceManager(state, event) {
|
|
|
26273
26456
|
{
|
|
26274
26457
|
const a = agent2;
|
|
26275
26458
|
a.execution = { sessionInstanceId: event.sessionInstanceId, lease: { state: "none", lastTerminal: null } };
|
|
26459
|
+
a.turnSilence = event.turnSilence ?? {
|
|
26460
|
+
...DEFAULT_TURN_SILENCE_POLICY,
|
|
26461
|
+
nativeIdleTimeoutMs: state.staleThresholdMs,
|
|
26462
|
+
daemonGraceMs: 0,
|
|
26463
|
+
normalBudgetMs: state.staleThresholdMs
|
|
26464
|
+
};
|
|
26276
26465
|
a.lastProgressAt = event.nowMs;
|
|
26466
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26467
|
+
a.lastNativeActivityKind = null;
|
|
26468
|
+
a.runtimePhase = "admission";
|
|
26469
|
+
a.backendTurnId = null;
|
|
26277
26470
|
a.idleSince = null;
|
|
26278
26471
|
syncExecutionProjection(a);
|
|
26279
26472
|
}
|
|
@@ -26329,7 +26522,25 @@ function reduceManager(state, event) {
|
|
|
26329
26522
|
return { state, effects: [] };
|
|
26330
26523
|
return mutate(state, event.agentId, (a) => {
|
|
26331
26524
|
a.sessionId = null;
|
|
26525
|
+
a.stalledSessionId = null;
|
|
26526
|
+
a.idleSince = null;
|
|
26332
26527
|
});
|
|
26528
|
+
case "idle_reset_committed": {
|
|
26529
|
+
const existing = state.agents[event.agentId];
|
|
26530
|
+
if (!existing)
|
|
26531
|
+
return { state, effects: [] };
|
|
26532
|
+
const agent2 = clone2(existing);
|
|
26533
|
+
agent2.sessionId = null;
|
|
26534
|
+
agent2.stalledSessionId = null;
|
|
26535
|
+
agent2.idleSince = null;
|
|
26536
|
+
if (agent2.status !== "running")
|
|
26537
|
+
return commit(state, agent2, []);
|
|
26538
|
+
agent2.status = "stopping";
|
|
26539
|
+
agent2.stoppingSince = event.nowMs;
|
|
26540
|
+
return commit(state, agent2, [
|
|
26541
|
+
{ type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
|
|
26542
|
+
]);
|
|
26543
|
+
}
|
|
26333
26544
|
case "begin_reset":
|
|
26334
26545
|
if (!state.agents[event.agentId])
|
|
26335
26546
|
return { state, effects: [] };
|
|
@@ -26367,8 +26578,18 @@ function reduceManager(state, event) {
|
|
|
26367
26578
|
return;
|
|
26368
26579
|
const startedCommands = new Set(event.commandIds);
|
|
26369
26580
|
a.pendingAdmissions = a.pendingAdmissions.filter((entry) => !startedCommands.has(entry.commandId));
|
|
26370
|
-
a.execution.lease = {
|
|
26581
|
+
a.execution.lease = {
|
|
26582
|
+
state: "active",
|
|
26583
|
+
identity,
|
|
26584
|
+
lastWorkAt: event.nowMs,
|
|
26585
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26586
|
+
recoveryExtensionsUsed: 0
|
|
26587
|
+
};
|
|
26371
26588
|
a.lastProgressAt = event.nowMs;
|
|
26589
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26590
|
+
a.lastNativeActivityKind = "turn_started";
|
|
26591
|
+
a.runtimePhase = "inference";
|
|
26592
|
+
a.backendTurnId = null;
|
|
26372
26593
|
a.idleSince = null;
|
|
26373
26594
|
syncExecutionProjection(a);
|
|
26374
26595
|
});
|
|
@@ -26389,7 +26610,12 @@ function reduceManager(state, event) {
|
|
|
26389
26610
|
const lease = a.execution.lease;
|
|
26390
26611
|
const identity = identityOf(event);
|
|
26391
26612
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
26392
|
-
a.execution.lease = {
|
|
26613
|
+
a.execution.lease = {
|
|
26614
|
+
...lease,
|
|
26615
|
+
lastWorkAt: event.nowMs,
|
|
26616
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26617
|
+
recoveryExtensionsUsed: 0
|
|
26618
|
+
};
|
|
26393
26619
|
} else {
|
|
26394
26620
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
26395
26621
|
if (!terminal || !sameIdentity(terminal.identity, identity))
|
|
@@ -26398,6 +26624,8 @@ function reduceManager(state, event) {
|
|
|
26398
26624
|
state: "suspect_active",
|
|
26399
26625
|
identity,
|
|
26400
26626
|
lastWorkAt: event.nowMs,
|
|
26627
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26628
|
+
recoveryExtensionsUsed: 0,
|
|
26401
26629
|
reason: "work_after_terminal"
|
|
26402
26630
|
};
|
|
26403
26631
|
}
|
|
@@ -26411,7 +26639,7 @@ function reduceManager(state, event) {
|
|
|
26411
26639
|
case "turn_tool_finished":
|
|
26412
26640
|
return onTurnToolLifecycle(state, event, "finished");
|
|
26413
26641
|
case "turn_completed":
|
|
26414
|
-
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId);
|
|
26642
|
+
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId, event.endReason);
|
|
26415
26643
|
case "session_closed":
|
|
26416
26644
|
if (state.agents[event.agentId]?.execution.sessionInstanceId !== event.sessionInstanceId) {
|
|
26417
26645
|
return { state, effects: [] };
|
|
@@ -26421,7 +26649,6 @@ function reduceManager(state, event) {
|
|
|
26421
26649
|
const closing = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId === event.sessionInstanceId);
|
|
26422
26650
|
agent2.pendingAdmissions = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId !== event.sessionInstanceId);
|
|
26423
26651
|
agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
26424
|
-
agent2.idleSince = null;
|
|
26425
26652
|
syncExecutionProjection(agent2);
|
|
26426
26653
|
return commit(state, agent2, recoveryEffects(agent2, closing));
|
|
26427
26654
|
}
|
|
@@ -26429,8 +26656,57 @@ function reduceManager(state, event) {
|
|
|
26429
26656
|
return onExit(state, event.agentId);
|
|
26430
26657
|
case "tick":
|
|
26431
26658
|
return onTick(state, event.nowMs);
|
|
26432
|
-
case "runtime_signal":
|
|
26433
|
-
|
|
26659
|
+
case "runtime_signal": {
|
|
26660
|
+
const existing = state.agents[event.agentId];
|
|
26661
|
+
if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
|
|
26662
|
+
return { state, effects: [] };
|
|
26663
|
+
const lease = existing.execution.lease;
|
|
26664
|
+
const identity = { sessionInstanceId: event.sessionInstanceId, turnId: event.turnId };
|
|
26665
|
+
if (lease.state !== "active" && lease.state !== "suspect_active" || !sameIdentity(lease.identity, identity)) {
|
|
26666
|
+
return { state, effects: [] };
|
|
26667
|
+
}
|
|
26668
|
+
return mutate(state, event.agentId, (a) => {
|
|
26669
|
+
const active = a.execution.lease;
|
|
26670
|
+
if (active.state !== "active" && active.state !== "suspect_active" || !sameIdentity(active.identity, identity))
|
|
26671
|
+
return;
|
|
26672
|
+
if (event.kind === "recovery" && event.recoveryStage !== "recovered") {
|
|
26673
|
+
if (active.recoveryExtensionsUsed < a.turnSilence.maxRecoveryExtensions) {
|
|
26674
|
+
a.execution.lease = {
|
|
26675
|
+
...active,
|
|
26676
|
+
nativeDeadlineAt: Math.max(active.nativeDeadlineAt, event.nowMs + a.turnSilence.recoveryGraceMs),
|
|
26677
|
+
recoveryExtensionsUsed: active.recoveryExtensionsUsed + 1
|
|
26678
|
+
};
|
|
26679
|
+
}
|
|
26680
|
+
} else {
|
|
26681
|
+
a.execution.lease = {
|
|
26682
|
+
...active,
|
|
26683
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs
|
|
26684
|
+
};
|
|
26685
|
+
}
|
|
26686
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26687
|
+
a.lastNativeActivityKind = event.kind;
|
|
26688
|
+
a.runtimePhase = event.phase;
|
|
26689
|
+
if (event.backendTurnId)
|
|
26690
|
+
a.backendTurnId = event.backendTurnId;
|
|
26691
|
+
});
|
|
26692
|
+
}
|
|
26693
|
+
case "stall_control_failed":
|
|
26694
|
+
return mutate(state, event.agentId, (a) => {
|
|
26695
|
+
if (event.transition === "clear") {
|
|
26696
|
+
if (a.sessionId === event.sessionId)
|
|
26697
|
+
a.stalledSessionId = event.sessionId;
|
|
26698
|
+
return;
|
|
26699
|
+
}
|
|
26700
|
+
if (a.status !== "stopping")
|
|
26701
|
+
return;
|
|
26702
|
+
const lease = a.execution.lease;
|
|
26703
|
+
if (lease.state !== "active" && lease.state !== "suspect_active")
|
|
26704
|
+
return;
|
|
26705
|
+
a.status = "running";
|
|
26706
|
+
a.stoppingSince = null;
|
|
26707
|
+
a.sessionId = event.sessionId;
|
|
26708
|
+
a.stalledSessionId = event.transition === "fence" ? event.sessionId : null;
|
|
26709
|
+
});
|
|
26434
26710
|
case "delivery_rejected":
|
|
26435
26711
|
return mutate(state, event.agentId, (a) => {
|
|
26436
26712
|
if (!a.inbox.some((message2) => message2.id === event.message.id)) {
|
|
@@ -26468,7 +26744,7 @@ function onWake(state, agentId, message2) {
|
|
|
26468
26744
|
}
|
|
26469
26745
|
return commit(state, agent2, []);
|
|
26470
26746
|
}
|
|
26471
|
-
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
26747
|
+
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endReason) {
|
|
26472
26748
|
const existing = state.agents[agentId];
|
|
26473
26749
|
if (!existing)
|
|
26474
26750
|
return { state, effects: [] };
|
|
@@ -26484,13 +26760,23 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
|
26484
26760
|
const lastTerminal = { identity, at: nowMs };
|
|
26485
26761
|
agent2.execution.lease = { state: "none", lastTerminal };
|
|
26486
26762
|
agent2.lastProgressAt = nowMs;
|
|
26763
|
+
agent2.lastNativeActivityAt = nowMs;
|
|
26764
|
+
agent2.lastNativeActivityKind = "turn_end";
|
|
26765
|
+
agent2.runtimePhase = "terminal";
|
|
26766
|
+
const clearedStallSessionId = endReason === undefined ? agent2.stalledSessionId : null;
|
|
26767
|
+
if (clearedStallSessionId !== null)
|
|
26768
|
+
agent2.stalledSessionId = null;
|
|
26487
26769
|
syncExecutionProjection(agent2);
|
|
26770
|
+
const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
|
|
26488
26771
|
if (agent2.inbox.length > 0) {
|
|
26489
26772
|
const messages = drainInbox(agent2);
|
|
26490
|
-
return commit(state, agent2,
|
|
26773
|
+
return commit(state, agent2, [
|
|
26774
|
+
...clearEffects,
|
|
26775
|
+
...messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" }))
|
|
26776
|
+
]);
|
|
26491
26777
|
}
|
|
26492
26778
|
agent2.idleSince = nowMs;
|
|
26493
|
-
return commit(state, agent2,
|
|
26779
|
+
return commit(state, agent2, clearEffects);
|
|
26494
26780
|
}
|
|
26495
26781
|
function onTurnToolLifecycle(state, event, lifecycle) {
|
|
26496
26782
|
const existing = state.agents[event.agentId];
|
|
@@ -26512,11 +26798,22 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
26512
26798
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
26513
26799
|
const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
|
|
26514
26800
|
if (outstandingToolUses > 0) {
|
|
26515
|
-
agent2.execution.lease = {
|
|
26801
|
+
agent2.execution.lease = {
|
|
26802
|
+
...lease,
|
|
26803
|
+
lastWorkAt: event.nowMs,
|
|
26804
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26805
|
+
recoveryExtensionsUsed: 0,
|
|
26806
|
+
outstandingToolUses
|
|
26807
|
+
};
|
|
26516
26808
|
} else {
|
|
26517
26809
|
const unblockedLease = { ...lease };
|
|
26518
26810
|
delete unblockedLease.outstandingToolUses;
|
|
26519
|
-
agent2.execution.lease = {
|
|
26811
|
+
agent2.execution.lease = {
|
|
26812
|
+
...unblockedLease,
|
|
26813
|
+
lastWorkAt: event.nowMs,
|
|
26814
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26815
|
+
recoveryExtensionsUsed: 0
|
|
26816
|
+
};
|
|
26520
26817
|
}
|
|
26521
26818
|
} else {
|
|
26522
26819
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
@@ -26526,11 +26823,16 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
26526
26823
|
state: "suspect_active",
|
|
26527
26824
|
identity,
|
|
26528
26825
|
lastWorkAt: event.nowMs,
|
|
26826
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26827
|
+
recoveryExtensionsUsed: 0,
|
|
26529
26828
|
outstandingToolUses: 1,
|
|
26530
26829
|
reason: "work_after_terminal"
|
|
26531
26830
|
};
|
|
26532
26831
|
}
|
|
26533
26832
|
agent2.lastProgressAt = event.nowMs;
|
|
26833
|
+
agent2.lastNativeActivityAt = event.nowMs;
|
|
26834
|
+
agent2.lastNativeActivityKind = lifecycle === "started" ? "tool_call" : "tool_output";
|
|
26835
|
+
agent2.runtimePhase = lifecycle === "started" ? "tool" : "inference";
|
|
26534
26836
|
agent2.idleSince = null;
|
|
26535
26837
|
syncExecutionProjection(agent2);
|
|
26536
26838
|
});
|
|
@@ -26543,6 +26845,8 @@ function onExit(state, agentId) {
|
|
|
26543
26845
|
const effects = recoveryEffects(agent2, agent2.pendingAdmissions);
|
|
26544
26846
|
agent2.pendingAdmissions = [];
|
|
26545
26847
|
agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
26848
|
+
agent2.runtimePhase = "idle";
|
|
26849
|
+
agent2.backendTurnId = null;
|
|
26546
26850
|
agent2.stoppingSince = null;
|
|
26547
26851
|
syncExecutionProjection(agent2);
|
|
26548
26852
|
if (agent2.inbox.length > 0) {
|
|
@@ -26562,10 +26866,19 @@ function onTick(state, nowMs) {
|
|
|
26562
26866
|
for (const id of Object.keys(agents)) {
|
|
26563
26867
|
const a = agents[id];
|
|
26564
26868
|
const lease = a.execution.lease;
|
|
26565
|
-
const stalled = a.status === "running" && (lease.state === "active" || lease.state === "suspect_active") && (lease.outstandingToolUses ?? 0) === 0 && nowMs - lease.lastWorkAt >=
|
|
26869
|
+
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;
|
|
26566
26870
|
if (stalled) {
|
|
26567
|
-
|
|
26568
|
-
|
|
26871
|
+
const repeatedSessionStall = a.sessionId !== null && a.stalledSessionId === a.sessionId;
|
|
26872
|
+
const forgetSessionId = repeatedSessionStall ? a.sessionId : undefined;
|
|
26873
|
+
agents[id] = {
|
|
26874
|
+
...a,
|
|
26875
|
+
status: "stopping",
|
|
26876
|
+
sessionId: repeatedSessionStall ? null : a.sessionId,
|
|
26877
|
+
stalledSessionId: repeatedSessionStall ? null : a.sessionId,
|
|
26878
|
+
idleSince: null,
|
|
26879
|
+
stoppingSince: nowMs
|
|
26880
|
+
};
|
|
26881
|
+
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 });
|
|
26569
26882
|
continue;
|
|
26570
26883
|
}
|
|
26571
26884
|
const expiredAdmission = a.status === "running" && a.pendingAdmissions.filter((entry) => !entry.driverAcknowledged && nowMs - entry.admittedAt >= state.staleThresholdMs);
|
|
@@ -26597,9 +26910,14 @@ function onTick(state, nowMs) {
|
|
|
26597
26910
|
effects.push({ type: "force_exit", agentId: id, reason: "stopping_stuck" });
|
|
26598
26911
|
continue;
|
|
26599
26912
|
}
|
|
26913
|
+
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);
|
|
26914
|
+
if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
|
|
26915
|
+
effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
|
|
26916
|
+
continue;
|
|
26917
|
+
}
|
|
26600
26918
|
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);
|
|
26601
26919
|
if (idleEligible && a.idleSince !== null && nowMs - a.idleSince >= state.idleTimeoutMs) {
|
|
26602
|
-
agents[id] = { ...a, status: "stopping",
|
|
26920
|
+
agents[id] = { ...a, status: "stopping", stoppingSince: nowMs };
|
|
26603
26921
|
effects.push({ type: "stop", agentId: id, reason: "idle_timeout" });
|
|
26604
26922
|
}
|
|
26605
26923
|
}
|
|
@@ -26611,11 +26929,17 @@ function freshAgent(agentId) {
|
|
|
26611
26929
|
status: "idle",
|
|
26612
26930
|
inbox: [],
|
|
26613
26931
|
sessionId: null,
|
|
26932
|
+
stalledSessionId: null,
|
|
26614
26933
|
execution: { sessionInstanceId: null, lease: { state: "detached" } },
|
|
26615
26934
|
pendingAdmissions: [],
|
|
26616
26935
|
turnId: null,
|
|
26617
26936
|
turnActive: false,
|
|
26618
26937
|
lastProgressAt: 0,
|
|
26938
|
+
lastNativeActivityAt: 0,
|
|
26939
|
+
lastNativeActivityKind: null,
|
|
26940
|
+
runtimePhase: "idle",
|
|
26941
|
+
backendTurnId: null,
|
|
26942
|
+
turnSilence: DEFAULT_TURN_SILENCE_POLICY,
|
|
26619
26943
|
lastDeliverAt: null,
|
|
26620
26944
|
idleSince: null,
|
|
26621
26945
|
stoppingSince: null,
|
|
@@ -26737,9 +27061,6 @@ function identitySection(config2) {
|
|
|
26737
27061
|
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.");
|
|
26738
27062
|
}
|
|
26739
27063
|
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.");
|
|
26740
|
-
if (config2.description) {
|
|
26741
|
-
parts.push("", "### Role", "", config2.description, "", "A starting point, not a script. Capture how the role evolves in `./memory.md` " + "(the Role text above isn't editable directly).");
|
|
26742
|
-
}
|
|
26743
27064
|
return parts.join(`
|
|
26744
27065
|
`);
|
|
26745
27066
|
}
|
|
@@ -26976,7 +27297,17 @@ function chaosAwarenessSection() {
|
|
|
26976
27297
|
].join(`
|
|
26977
27298
|
`);
|
|
26978
27299
|
}
|
|
26979
|
-
function workspaceMemorySection() {
|
|
27300
|
+
function workspaceMemorySection(config2) {
|
|
27301
|
+
const roleSection = config2.description ? [
|
|
27302
|
+
"",
|
|
27303
|
+
"### Your bio",
|
|
27304
|
+
"",
|
|
27305
|
+
config2.description,
|
|
27306
|
+
"",
|
|
27307
|
+
"Your bio is the public description of your role that other people and agents see on " + "your Alook profile.",
|
|
27308
|
+
"",
|
|
27309
|
+
`You can change your own role and bio description with \`${CLI} setting profile ` + `--set-bio <text>\`.`
|
|
27310
|
+
] : [];
|
|
26980
27311
|
return [
|
|
26981
27312
|
"## Self-awareness",
|
|
26982
27313
|
"",
|
|
@@ -26985,6 +27316,7 @@ function workspaceMemorySection() {
|
|
|
26985
27316
|
"**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
|
|
26986
27317
|
"",
|
|
26987
27318
|
"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.",
|
|
27319
|
+
...roleSection,
|
|
26988
27320
|
"",
|
|
26989
27321
|
"### Napping",
|
|
26990
27322
|
"",
|
|
@@ -27026,7 +27358,7 @@ function buildCliSystemPrompt(config2) {
|
|
|
27026
27358
|
criticalRulesSection(),
|
|
27027
27359
|
executionModelSection(),
|
|
27028
27360
|
chaosAwarenessSection(),
|
|
27029
|
-
workspaceMemorySection(),
|
|
27361
|
+
workspaceMemorySection(config2),
|
|
27030
27362
|
utilsSection()
|
|
27031
27363
|
];
|
|
27032
27364
|
return sections.filter((s) => s && s.length > 0).join(`
|
|
@@ -27294,8 +27626,8 @@ class AgentProcessManager {
|
|
|
27294
27626
|
resumeSessions = new Map;
|
|
27295
27627
|
launchIds = new Map;
|
|
27296
27628
|
liveSessions = new Map;
|
|
27297
|
-
thinkingBuffers = new Map;
|
|
27298
27629
|
activeSpawnState = new Map;
|
|
27630
|
+
publishedAgentActivity = new Map;
|
|
27299
27631
|
traceProcessNonce = randomUUID5();
|
|
27300
27632
|
nextSpawnOrdinal = 1;
|
|
27301
27633
|
nextDaemonTurnOrdinal = 1;
|
|
@@ -27309,7 +27641,8 @@ class AgentProcessManager {
|
|
|
27309
27641
|
this.opts = {
|
|
27310
27642
|
tickIntervalMs: 5000,
|
|
27311
27643
|
staleThresholdMs: 120000,
|
|
27312
|
-
idleTimeoutMs:
|
|
27644
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
27645
|
+
idleResetTimeoutMs: DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
27313
27646
|
resetStuckThresholdMs: 120000,
|
|
27314
27647
|
stoppingStuckThresholdMs: DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
27315
27648
|
handshakeTimeoutMs: 60000,
|
|
@@ -27318,7 +27651,7 @@ class AgentProcessManager {
|
|
|
27318
27651
|
};
|
|
27319
27652
|
this.now = opts.now ?? (() => Date.now());
|
|
27320
27653
|
this.log = opts.logger ?? createLogger2({ header: "@alook/daemon:manager" });
|
|
27321
|
-
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs);
|
|
27654
|
+
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
|
|
27322
27655
|
}
|
|
27323
27656
|
register(agentId, launch) {
|
|
27324
27657
|
if (launch?.runtimeConfig)
|
|
@@ -27337,11 +27670,19 @@ class AgentProcessManager {
|
|
|
27337
27670
|
const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
|
|
27338
27671
|
return effects.length > 0;
|
|
27339
27672
|
}
|
|
27340
|
-
forgetSession(agentId, barrierType = "reset_session") {
|
|
27673
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
27674
|
+
if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId))
|
|
27675
|
+
return false;
|
|
27676
|
+
this.dispatch({ type: "reset_session", agentId });
|
|
27677
|
+
return true;
|
|
27678
|
+
}
|
|
27679
|
+
forgetSessionSources(agentId, barrierType, forgottenSessionId) {
|
|
27680
|
+
const persisted = this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
|
|
27681
|
+
if (persisted === false)
|
|
27682
|
+
return false;
|
|
27341
27683
|
this.resumeSessions.delete(agentId);
|
|
27342
27684
|
this.liveSessions.delete(agentId);
|
|
27343
|
-
|
|
27344
|
-
this.opts.timeline?.forgetSession(agentId, barrierType);
|
|
27685
|
+
return true;
|
|
27345
27686
|
}
|
|
27346
27687
|
enqueueRewake(agentId, message2) {
|
|
27347
27688
|
this.dispatch({ type: "rewake_after_reset", agentId, message: message2 });
|
|
@@ -27364,9 +27705,14 @@ class AgentProcessManager {
|
|
|
27364
27705
|
});
|
|
27365
27706
|
}
|
|
27366
27707
|
async restartAgent(agentId, opts) {
|
|
27708
|
+
if (opts.forgetSession && !this.forgetSession(agentId, opts.barrierType ?? "reset_session")) {
|
|
27709
|
+
this.log.error("resume control transition failed; reset aborted", { agentId, barrierType: opts.barrierType });
|
|
27710
|
+
this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
|
|
27711
|
+
throw new Error("Reset aborted because resume control could not be persisted");
|
|
27712
|
+
}
|
|
27367
27713
|
this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
|
|
27368
|
-
if (opts.forgetSession)
|
|
27369
|
-
this.
|
|
27714
|
+
if (!opts.forgetSession)
|
|
27715
|
+
this.opts.timeline?.fenceSession(agentId);
|
|
27370
27716
|
this.abortCurrentTurn(agentId, opts.abortCause);
|
|
27371
27717
|
this.markResetting(agentId);
|
|
27372
27718
|
const status = this.state.agents[agentId]?.status;
|
|
@@ -27441,6 +27787,10 @@ class AgentProcessManager {
|
|
|
27441
27787
|
launchId: this.launchIds.get(agentId) ?? null
|
|
27442
27788
|
};
|
|
27443
27789
|
}
|
|
27790
|
+
timelineTurnOwner(agentId) {
|
|
27791
|
+
const owner = this.traceOwnerFor(agentId);
|
|
27792
|
+
return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
|
|
27793
|
+
}
|
|
27444
27794
|
liveSessionReports() {
|
|
27445
27795
|
return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
|
|
27446
27796
|
agentId,
|
|
@@ -27510,6 +27860,10 @@ class AgentProcessManager {
|
|
|
27510
27860
|
if (!expectedSpan || owner.activeSpan !== expectedSpan)
|
|
27511
27861
|
return false;
|
|
27512
27862
|
owner.activeSpan = null;
|
|
27863
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
27864
|
+
owner.timelineTurnOwner = null;
|
|
27865
|
+
if (timelineTurnOwner)
|
|
27866
|
+
this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
|
|
27513
27867
|
const nowMs = this.now();
|
|
27514
27868
|
const base = {
|
|
27515
27869
|
recordKind: "turn_span",
|
|
@@ -27567,6 +27921,13 @@ class AgentProcessManager {
|
|
|
27567
27921
|
inbox: a.inbox.length,
|
|
27568
27922
|
lastDeliverAt: a.lastDeliverAt,
|
|
27569
27923
|
lastProgressAt: a.lastProgressAt,
|
|
27924
|
+
lastNativeActivityAt: a.lastNativeActivityAt,
|
|
27925
|
+
lastNativeActivityKind: a.lastNativeActivityKind,
|
|
27926
|
+
runtimePhase: a.runtimePhase,
|
|
27927
|
+
backendTurnId: a.backendTurnId,
|
|
27928
|
+
turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
|
|
27929
|
+
nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
|
|
27930
|
+
recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
|
|
27570
27931
|
idleSince: a.idleSince,
|
|
27571
27932
|
resetting: a.resetting,
|
|
27572
27933
|
resettingSince: a.resettingSince,
|
|
@@ -27577,6 +27938,7 @@ class AgentProcessManager {
|
|
|
27577
27938
|
timeIso: new Date(nowMs).toISOString(),
|
|
27578
27939
|
...activeSpan ? activeSpan : {},
|
|
27579
27940
|
sinceProgressMs: nowMs - a.lastProgressAt,
|
|
27941
|
+
sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
|
|
27580
27942
|
sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
|
|
27581
27943
|
sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
|
|
27582
27944
|
...event.type === "turn_completed" && event.endReason === "errored" ? {
|
|
@@ -27611,11 +27973,18 @@ class AgentProcessManager {
|
|
|
27611
27973
|
terminationCause: normalizeTerminationCause(event.terminationCause)
|
|
27612
27974
|
} : { event: "turn_end", outcome: "clean" });
|
|
27613
27975
|
}
|
|
27614
|
-
if (this.opts.onAgentActivity && event.type !== "
|
|
27976
|
+
if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
|
|
27615
27977
|
const after = this.deriveActivitySnapshot(state);
|
|
27616
27978
|
for (const [agentId, activity] of Object.entries(after)) {
|
|
27617
|
-
if (agentId in before
|
|
27979
|
+
if (!(agentId in before)) {
|
|
27980
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
27981
|
+
continue;
|
|
27982
|
+
}
|
|
27983
|
+
const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
|
|
27984
|
+
const publishable = event.type !== "spawned" || activity === "running";
|
|
27985
|
+
if (publishable && previouslyPublished !== activity) {
|
|
27618
27986
|
this.opts.onAgentActivity({ agentId, state: activity });
|
|
27987
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
27619
27988
|
}
|
|
27620
27989
|
}
|
|
27621
27990
|
}
|
|
@@ -27780,6 +28149,47 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27780
28149
|
case "terminate_stalled": {
|
|
27781
28150
|
const session2 = this.sessions.get(effect.agentId);
|
|
27782
28151
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
28152
|
+
const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
|
|
28153
|
+
if (effect.type === "terminate_stalled" && effect.recordSessionId) {
|
|
28154
|
+
const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
|
|
28155
|
+
if (persisted === false) {
|
|
28156
|
+
this.dispatch({
|
|
28157
|
+
type: "stall_control_failed",
|
|
28158
|
+
agentId: effect.agentId,
|
|
28159
|
+
sessionId: effect.recordSessionId,
|
|
28160
|
+
transition: "attempt"
|
|
28161
|
+
});
|
|
28162
|
+
this.log.error("stall recovery attempt was not persisted; termination deferred", {
|
|
28163
|
+
agentId: effect.agentId,
|
|
28164
|
+
sessionId: effect.recordSessionId
|
|
28165
|
+
});
|
|
28166
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
|
|
28167
|
+
break;
|
|
28168
|
+
}
|
|
28169
|
+
}
|
|
28170
|
+
if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
|
|
28171
|
+
const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
|
|
28172
|
+
if (!persisted) {
|
|
28173
|
+
this.dispatch({
|
|
28174
|
+
type: "stall_control_failed",
|
|
28175
|
+
agentId: effect.agentId,
|
|
28176
|
+
sessionId: effect.forgetSessionId,
|
|
28177
|
+
transition: "fence"
|
|
28178
|
+
});
|
|
28179
|
+
this.log.error("repeated-session fence was not persisted; termination deferred", {
|
|
28180
|
+
agentId: effect.agentId,
|
|
28181
|
+
sessionId: effect.forgetSessionId
|
|
28182
|
+
});
|
|
28183
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
|
|
28184
|
+
break;
|
|
28185
|
+
}
|
|
28186
|
+
if (spawnState)
|
|
28187
|
+
spawnState.discardEvents = true;
|
|
28188
|
+
this.log.warn("repeatedly stalled backend session fenced", {
|
|
28189
|
+
agentId: effect.agentId,
|
|
28190
|
+
sessionId: effect.forgetSessionId
|
|
28191
|
+
});
|
|
28192
|
+
}
|
|
27783
28193
|
if (effect.type === "terminate_stalled" && spawnState) {
|
|
27784
28194
|
this.closeTurn(spawnState, spawnState.activeSpan, {
|
|
27785
28195
|
event: "turn_abort",
|
|
@@ -27798,10 +28208,27 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27798
28208
|
if (spawnState) {
|
|
27799
28209
|
spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
|
|
27800
28210
|
}
|
|
27801
|
-
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
|
|
28211
|
+
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
|
|
27802
28212
|
this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
|
|
27803
28213
|
break;
|
|
27804
28214
|
}
|
|
28215
|
+
case "clear_stall_recovery": {
|
|
28216
|
+
const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
|
|
28217
|
+
if (persisted === false) {
|
|
28218
|
+
this.dispatch({
|
|
28219
|
+
type: "stall_control_failed",
|
|
28220
|
+
agentId: effect.agentId,
|
|
28221
|
+
sessionId: effect.sessionId,
|
|
28222
|
+
transition: "clear"
|
|
28223
|
+
});
|
|
28224
|
+
this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
|
|
28225
|
+
agentId: effect.agentId,
|
|
28226
|
+
sessionId: effect.sessionId
|
|
28227
|
+
});
|
|
28228
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
|
|
28229
|
+
}
|
|
28230
|
+
break;
|
|
28231
|
+
}
|
|
27805
28232
|
case "expire_admission": {
|
|
27806
28233
|
const owner = this.activeSpawnState.get(effect.agentId);
|
|
27807
28234
|
if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
|
|
@@ -27824,6 +28251,26 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27824
28251
|
mode: effect.mode
|
|
27825
28252
|
});
|
|
27826
28253
|
break;
|
|
28254
|
+
case "reset_idle_session": {
|
|
28255
|
+
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
28256
|
+
const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
|
|
28257
|
+
if (!persisted) {
|
|
28258
|
+
this.log.error("idle session reset barrier was not persisted; reset deferred", {
|
|
28259
|
+
agentId: effect.agentId,
|
|
28260
|
+
sessionId: effect.sessionId
|
|
28261
|
+
});
|
|
28262
|
+
this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
|
|
28263
|
+
break;
|
|
28264
|
+
}
|
|
28265
|
+
if (spawnState)
|
|
28266
|
+
spawnState.discardEvents = true;
|
|
28267
|
+
this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
|
|
28268
|
+
this.log.info("idle agent session reset", {
|
|
28269
|
+
agentId: effect.agentId,
|
|
28270
|
+
sessionId: effect.sessionId
|
|
28271
|
+
});
|
|
28272
|
+
break;
|
|
28273
|
+
}
|
|
27827
28274
|
case "force_exit": {
|
|
27828
28275
|
const session2 = this.sessions.get(effect.agentId);
|
|
27829
28276
|
const state = this.activeSpawnState.get(effect.agentId);
|
|
@@ -27856,8 +28303,8 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27856
28303
|
}
|
|
27857
28304
|
}
|
|
27858
28305
|
}
|
|
27859
|
-
logSessionEnded(agentId, reason) {
|
|
27860
|
-
this.log.info("agent session ended", { agentId, sessionId
|
|
28306
|
+
logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
|
|
28307
|
+
this.log.info("agent session ended", { agentId, sessionId, reason });
|
|
27861
28308
|
}
|
|
27862
28309
|
doSpawn(agentId, messages, resumeSessionId) {
|
|
27863
28310
|
const [first, ...pending] = messages;
|
|
@@ -27882,7 +28329,13 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27882
28329
|
mode: { kind: "default" }
|
|
27883
28330
|
};
|
|
27884
28331
|
const provider = runtimeConfig.runtime;
|
|
27885
|
-
const
|
|
28332
|
+
const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
|
|
28333
|
+
const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
|
|
28334
|
+
const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
|
|
28335
|
+
const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
|
|
28336
|
+
const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
|
|
28337
|
+
const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
|
|
28338
|
+
const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
|
|
27886
28339
|
const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
|
|
27887
28340
|
const agentName = runtimeConfig.agentName ?? base.config?.agentName;
|
|
27888
28341
|
const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
|
|
@@ -27913,12 +28366,15 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27913
28366
|
handshakeTimer: null,
|
|
27914
28367
|
torndown: false,
|
|
27915
28368
|
superseded: false,
|
|
28369
|
+
discardEvents: false,
|
|
28370
|
+
stalledSessionIdAtLaunch,
|
|
27916
28371
|
spawnFailureReason: null,
|
|
27917
28372
|
terminationSemantics: null,
|
|
27918
28373
|
spawnOrdinal: this.nextSpawnOrdinal++,
|
|
27919
28374
|
launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
|
|
27920
28375
|
nextTurnOrdinal: 1,
|
|
27921
28376
|
activeSpan: null,
|
|
28377
|
+
timelineTurnOwner: null,
|
|
27922
28378
|
pendingDeliverySpans: new Map
|
|
27923
28379
|
};
|
|
27924
28380
|
const previousOwner = this.activeSpawnState.get(agentId);
|
|
@@ -27969,7 +28425,6 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27969
28425
|
this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
|
|
27970
28426
|
}
|
|
27971
28427
|
}
|
|
27972
|
-
this.flushThinkingAudit(agentId);
|
|
27973
28428
|
if (state.sessionInstanceId) {
|
|
27974
28429
|
this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
|
|
27975
28430
|
}
|
|
@@ -27994,10 +28449,23 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27994
28449
|
const onEvent = (event) => {
|
|
27995
28450
|
if (state.torndown)
|
|
27996
28451
|
return;
|
|
28452
|
+
if (state.discardEvents) {
|
|
28453
|
+
this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
|
|
28454
|
+
return;
|
|
28455
|
+
}
|
|
27997
28456
|
if (event.type === "session_failed" && !state.hasEstablished) {
|
|
27998
28457
|
reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
|
|
27999
28458
|
}
|
|
28000
28459
|
if (event.type === "session_started") {
|
|
28460
|
+
const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
|
|
28461
|
+
if (persisted === false) {
|
|
28462
|
+
state.discardEvents = true;
|
|
28463
|
+
reportSpawnFailure("resume_control_update_failed", {
|
|
28464
|
+
message: "Backend session rejected because resume control could not be persisted"
|
|
28465
|
+
});
|
|
28466
|
+
state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
|
|
28467
|
+
return;
|
|
28468
|
+
}
|
|
28001
28469
|
state.hasEstablished = true;
|
|
28002
28470
|
clearHandshakeTimer();
|
|
28003
28471
|
this.opts.onRuntimeSessionEstablished?.(driver.id);
|
|
@@ -28016,7 +28484,8 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28016
28484
|
type: "attach_session",
|
|
28017
28485
|
agentId,
|
|
28018
28486
|
sessionInstanceId: session2.sessionInstanceId,
|
|
28019
|
-
nowMs: this.now()
|
|
28487
|
+
nowMs: this.now(),
|
|
28488
|
+
turnSilence: session2.snapshot().diagnostics?.turnSilence
|
|
28020
28489
|
}, state);
|
|
28021
28490
|
previousOwner?.pendingDeliverySpans.clear();
|
|
28022
28491
|
(async () => {
|
|
@@ -28164,26 +28633,6 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28164
28633
|
this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
|
|
28165
28634
|
}
|
|
28166
28635
|
}
|
|
28167
|
-
flushThinkingAudit(agentId) {
|
|
28168
|
-
const buffered = this.thinkingBuffers.get(agentId);
|
|
28169
|
-
if (!buffered)
|
|
28170
|
-
return;
|
|
28171
|
-
this.thinkingBuffers.delete(agentId);
|
|
28172
|
-
if (!this.opts.onBotAuditEvent)
|
|
28173
|
-
return;
|
|
28174
|
-
const { text: text2, truncated, chars } = truncateThinking(buffered);
|
|
28175
|
-
try {
|
|
28176
|
-
this.opts.onBotAuditEvent(agentId, {
|
|
28177
|
-
kind: "thinking",
|
|
28178
|
-
payload: { text: text2, truncated, chars }
|
|
28179
|
-
}, {
|
|
28180
|
-
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28181
|
-
launchId: this.launchIds.get(agentId) ?? null
|
|
28182
|
-
});
|
|
28183
|
-
} catch (err) {
|
|
28184
|
-
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
28185
|
-
}
|
|
28186
|
-
}
|
|
28187
28636
|
onAgentEvent(agentId, event, runtimeId, owner) {
|
|
28188
28637
|
if (event.type === "session_closed")
|
|
28189
28638
|
return;
|
|
@@ -28216,32 +28665,43 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28216
28665
|
}
|
|
28217
28666
|
}
|
|
28218
28667
|
if (this.opts.onBotAuditEvent) {
|
|
28219
|
-
if (event.type === "
|
|
28220
|
-
|
|
28221
|
-
|
|
28668
|
+
if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
|
|
28669
|
+
const { text: text2, truncated, chars } = truncateThinking(event.text);
|
|
28670
|
+
try {
|
|
28671
|
+
this.opts.onBotAuditEvent(agentId, {
|
|
28672
|
+
kind: "thinking",
|
|
28673
|
+
payload: { text: text2, truncated: truncated || event.truncated, chars }
|
|
28674
|
+
}, {
|
|
28675
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28676
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
28677
|
+
});
|
|
28678
|
+
} catch (err) {
|
|
28679
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
28222
28680
|
}
|
|
28223
|
-
}
|
|
28224
|
-
|
|
28225
|
-
|
|
28226
|
-
|
|
28227
|
-
|
|
28228
|
-
|
|
28229
|
-
|
|
28230
|
-
this.
|
|
28231
|
-
|
|
28232
|
-
|
|
28233
|
-
|
|
28234
|
-
|
|
28235
|
-
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
28236
|
-
}
|
|
28681
|
+
}
|
|
28682
|
+
if (event.type === "tool_started") {
|
|
28683
|
+
const audit = extractToolAudit(event.name, event.input);
|
|
28684
|
+
if (!audit.suppressed) {
|
|
28685
|
+
const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
|
|
28686
|
+
try {
|
|
28687
|
+
this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
|
|
28688
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28689
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
28690
|
+
});
|
|
28691
|
+
} catch (err) {
|
|
28692
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
28237
28693
|
}
|
|
28238
28694
|
}
|
|
28239
28695
|
}
|
|
28240
28696
|
}
|
|
28241
28697
|
if (event.type === "session_started") {
|
|
28242
|
-
this.dispatch({
|
|
28698
|
+
this.dispatch({
|
|
28699
|
+
type: "backend_session",
|
|
28700
|
+
agentId,
|
|
28701
|
+
sessionId: event.backendSessionId,
|
|
28702
|
+
stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
|
|
28703
|
+
}, owner);
|
|
28243
28704
|
this.liveSessions.set(agentId, event.backendSessionId);
|
|
28244
|
-
this.opts.timeline?.setSession(agentId, event.backendSessionId);
|
|
28245
28705
|
this.opts.onAgentSession?.({
|
|
28246
28706
|
agentId,
|
|
28247
28707
|
sessionId: event.backendSessionId,
|
|
@@ -28253,8 +28713,20 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28253
28713
|
runtime: runtimeId
|
|
28254
28714
|
});
|
|
28255
28715
|
}
|
|
28256
|
-
if (event.type === "
|
|
28257
|
-
|
|
28716
|
+
if (event.type === "turn_started") {
|
|
28717
|
+
const timelineTurnOwner = {
|
|
28718
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
28719
|
+
rootTurnId: event.turnId,
|
|
28720
|
+
barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
|
|
28721
|
+
};
|
|
28722
|
+
owner.timelineTurnOwner = timelineTurnOwner;
|
|
28723
|
+
this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
|
|
28724
|
+
}
|
|
28725
|
+
if (event.type === "assistant_message_completed" && event.text.length > 0) {
|
|
28726
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
28727
|
+
if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
|
|
28728
|
+
this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
|
|
28729
|
+
}
|
|
28258
28730
|
}
|
|
28259
28731
|
if (event.type === "command_queued")
|
|
28260
28732
|
this.acknowledgePendingDelivery(owner, event.commandId);
|
|
@@ -28272,9 +28744,10 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28272
28744
|
switch (event.type) {
|
|
28273
28745
|
case "turn_started":
|
|
28274
28746
|
return { type: "turn_started", turnId: event.turnId, commandIds: event.commandIds };
|
|
28275
|
-
case "
|
|
28276
|
-
case "
|
|
28277
|
-
|
|
28747
|
+
case "work_heartbeat":
|
|
28748
|
+
case "assistant_reasoning_completed":
|
|
28749
|
+
case "assistant_message_completed":
|
|
28750
|
+
return { type: "turn_work", turnId: event.turnId };
|
|
28278
28751
|
case "tool_started":
|
|
28279
28752
|
return { type: "turn_tool_started", turnId: event.turnId };
|
|
28280
28753
|
case "tool_finished":
|
|
@@ -28301,37 +28774,55 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28301
28774
|
if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
|
|
28302
28775
|
this.openTurn(owner);
|
|
28303
28776
|
}
|
|
28304
|
-
const
|
|
28777
|
+
const nativeSignal = (() => {
|
|
28305
28778
|
switch (event.type) {
|
|
28306
|
-
case "
|
|
28307
|
-
return "
|
|
28308
|
-
case "
|
|
28309
|
-
return
|
|
28310
|
-
|
|
28311
|
-
|
|
28779
|
+
case "turn_started":
|
|
28780
|
+
return { kind: "turn_started", phase: "inference", turnId: event.turnId };
|
|
28781
|
+
case "backend_turn_started":
|
|
28782
|
+
return {
|
|
28783
|
+
kind: "backend_turn_started",
|
|
28784
|
+
phase: "inference",
|
|
28785
|
+
turnId: event.turnId,
|
|
28786
|
+
backendTurnId: event.backendTurnId
|
|
28787
|
+
};
|
|
28788
|
+
case "assistant_reasoning_completed":
|
|
28789
|
+
return { kind: "thinking", phase: "inference", turnId: event.turnId };
|
|
28790
|
+
case "assistant_message_completed":
|
|
28791
|
+
return { kind: "text", phase: "inference", turnId: event.turnId };
|
|
28792
|
+
case "work_heartbeat":
|
|
28793
|
+
return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
|
|
28312
28794
|
case "tool_started":
|
|
28313
|
-
return "tool_call";
|
|
28795
|
+
return { kind: "tool_call", phase: "tool", turnId: event.turnId };
|
|
28314
28796
|
case "tool_finished":
|
|
28315
|
-
return "tool_output";
|
|
28316
|
-
case "
|
|
28317
|
-
|
|
28318
|
-
case "
|
|
28319
|
-
case "
|
|
28320
|
-
|
|
28797
|
+
return { kind: "tool_output", phase: "inference", turnId: event.turnId };
|
|
28798
|
+
case "compaction_started":
|
|
28799
|
+
case "compaction_finished":
|
|
28800
|
+
case "review_started":
|
|
28801
|
+
case "review_finished":
|
|
28802
|
+
case "internal_progress":
|
|
28803
|
+
return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
|
|
28804
|
+
case "recovery":
|
|
28805
|
+
return event.turnId ? {
|
|
28806
|
+
kind: "recovery",
|
|
28807
|
+
phase: event.stage === "retrying" ? "recovery" : "inference",
|
|
28808
|
+
recoveryStage: event.stage,
|
|
28809
|
+
turnId: event.turnId
|
|
28810
|
+
} : null;
|
|
28321
28811
|
case "turn_completed":
|
|
28322
|
-
return "turn_end";
|
|
28323
|
-
case "session_failed":
|
|
28324
|
-
return "error";
|
|
28325
|
-
case "command_queued":
|
|
28326
|
-
case "command_accepted":
|
|
28327
|
-
case "command_failed":
|
|
28328
|
-
case "turn_started":
|
|
28329
|
-
return "internal_progress";
|
|
28812
|
+
return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
|
|
28330
28813
|
default:
|
|
28331
|
-
return
|
|
28814
|
+
return null;
|
|
28332
28815
|
}
|
|
28333
28816
|
})();
|
|
28334
|
-
|
|
28817
|
+
if (nativeSignal) {
|
|
28818
|
+
this.dispatch({
|
|
28819
|
+
type: "runtime_signal",
|
|
28820
|
+
agentId,
|
|
28821
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
28822
|
+
...nativeSignal,
|
|
28823
|
+
nowMs: this.now()
|
|
28824
|
+
}, owner);
|
|
28825
|
+
}
|
|
28335
28826
|
if (event.type === "turn_completed") {
|
|
28336
28827
|
this.logSessionEnded(agentId, "turn_end");
|
|
28337
28828
|
const marker = this.nonCleanEndMarker.get(agentId);
|
|
@@ -28726,7 +29217,7 @@ function createTypingScopeTracker() {
|
|
|
28726
29217
|
}
|
|
28727
29218
|
// src/timeline/timeline.ts
|
|
28728
29219
|
import * as fs9 from "node:fs";
|
|
28729
|
-
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
29220
|
+
import { createHash as createHash2, randomBytes as randomBytes4 } from "node:crypto";
|
|
28730
29221
|
import { basename as basename3, dirname as dirname5, join as join11 } from "node:path";
|
|
28731
29222
|
|
|
28732
29223
|
// src/timeline/filelock.ts
|
|
@@ -28793,17 +29284,25 @@ function reclaim(lockPath) {
|
|
|
28793
29284
|
var TIMELINE_MAX_BYTES = 1048576;
|
|
28794
29285
|
var TIMELINE_READ_CHUNK_BYTES = 65536;
|
|
28795
29286
|
var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
29287
|
+
var RESUME_CONTROL_FILENAME = ".resume-control.json";
|
|
29288
|
+
var RESUME_CONTROL_MAX_BYTES = 4096;
|
|
29289
|
+
var EMPTY_RESUME_CONTROL = {
|
|
29290
|
+
version: 1,
|
|
29291
|
+
attemptedSessionId: null,
|
|
29292
|
+
fencedSessionId: null,
|
|
29293
|
+
fullBarrier: null
|
|
29294
|
+
};
|
|
28796
29295
|
function isBarrier(entry) {
|
|
28797
|
-
return entry.system
|
|
29296
|
+
return entry.system !== undefined;
|
|
28798
29297
|
}
|
|
28799
29298
|
function canonicalTimelineEntry(value) {
|
|
28800
29299
|
if (!value || typeof value !== "object")
|
|
28801
29300
|
return null;
|
|
28802
29301
|
const entry = value;
|
|
28803
29302
|
if (entry.system) {
|
|
28804
|
-
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
|
|
29303
|
+
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")
|
|
28805
29304
|
return null;
|
|
28806
|
-
return createSystemEntry(entry.system.type, entry.system.time);
|
|
29305
|
+
return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
|
|
28807
29306
|
}
|
|
28808
29307
|
if (entry.session_id !== null && typeof entry.session_id !== "string")
|
|
28809
29308
|
return null;
|
|
@@ -28821,13 +29320,60 @@ function canonicalTimelineEntry(value) {
|
|
|
28821
29320
|
};
|
|
28822
29321
|
}
|
|
28823
29322
|
function timelineLine(entry) {
|
|
28824
|
-
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
29323
|
+
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
28825
29324
|
const text2 = JSON.stringify(boundedEntry);
|
|
28826
29325
|
const bytes = Buffer.byteLength(text2, "utf8") + 1;
|
|
28827
29326
|
if (bytes > TIMELINE_MAX_BYTES)
|
|
28828
29327
|
return null;
|
|
28829
29328
|
return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
|
|
28830
29329
|
}
|
|
29330
|
+
function timelineRowHash(line) {
|
|
29331
|
+
return createHash2("sha256").update(line.text, "utf8").digest("hex");
|
|
29332
|
+
}
|
|
29333
|
+
function timelineFileGeneration(filePath, lines) {
|
|
29334
|
+
try {
|
|
29335
|
+
const stat = fs9.lstatSync(filePath, { bigint: true });
|
|
29336
|
+
if (!stat.isFile())
|
|
29337
|
+
return null;
|
|
29338
|
+
const digest = createHash2("sha256");
|
|
29339
|
+
digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
|
|
29340
|
+
`);
|
|
29341
|
+
for (const line of lines)
|
|
29342
|
+
digest.update(line.text, "utf8").update(`
|
|
29343
|
+
`);
|
|
29344
|
+
return digest.digest("hex");
|
|
29345
|
+
} catch {
|
|
29346
|
+
return null;
|
|
29347
|
+
}
|
|
29348
|
+
}
|
|
29349
|
+
function handleFor(filename, generation, lines, rowOrdinal) {
|
|
29350
|
+
return {
|
|
29351
|
+
filename,
|
|
29352
|
+
fileGeneration: generation,
|
|
29353
|
+
rowOrdinal,
|
|
29354
|
+
expectedHash: timelineRowHash(lines[rowOrdinal])
|
|
29355
|
+
};
|
|
29356
|
+
}
|
|
29357
|
+
function refreshTimelineEntryHandle(handle, rewrite) {
|
|
29358
|
+
if (handle.filename !== rewrite.filename)
|
|
29359
|
+
return handle;
|
|
29360
|
+
if (handle.fileGeneration !== rewrite.previousFileGeneration)
|
|
29361
|
+
return null;
|
|
29362
|
+
if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
|
|
29363
|
+
return null;
|
|
29364
|
+
const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
|
|
29365
|
+
if (rowOrdinal === null || rowOrdinal === undefined)
|
|
29366
|
+
return null;
|
|
29367
|
+
const expectedHash = rewrite.rowHashes[rowOrdinal];
|
|
29368
|
+
if (!expectedHash)
|
|
29369
|
+
return null;
|
|
29370
|
+
return {
|
|
29371
|
+
filename: handle.filename,
|
|
29372
|
+
fileGeneration: rewrite.fileGeneration,
|
|
29373
|
+
rowOrdinal,
|
|
29374
|
+
expectedHash
|
|
29375
|
+
};
|
|
29376
|
+
}
|
|
28831
29377
|
function compactLines(input) {
|
|
28832
29378
|
let head = 0;
|
|
28833
29379
|
let bytes = input.reduce((total, line) => total + line.bytes, 0);
|
|
@@ -29039,11 +29585,37 @@ function atomicReplaceTimeline(filePath, lines) {
|
|
|
29039
29585
|
} catch {}
|
|
29040
29586
|
}
|
|
29041
29587
|
}
|
|
29042
|
-
function
|
|
29588
|
+
function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
|
|
29589
|
+
const previousFileGeneration = timelineFileGeneration(filePath, existing);
|
|
29590
|
+
const previousRowHashes = existing.map(timelineRowHash);
|
|
29043
29591
|
const compacted = compactLines(input);
|
|
29044
|
-
|
|
29045
|
-
|
|
29046
|
-
|
|
29592
|
+
const targetOrdinal = compacted.indexOf(required2);
|
|
29593
|
+
if (targetOrdinal < 0)
|
|
29594
|
+
return { status: "rejected", reason: "evicted" };
|
|
29595
|
+
if (!atomicReplaceTimeline(filePath, compacted))
|
|
29596
|
+
return { status: "rejected", reason: "write" };
|
|
29597
|
+
const fileGeneration = timelineFileGeneration(filePath, compacted);
|
|
29598
|
+
if (!fileGeneration)
|
|
29599
|
+
return { status: "rejected", reason: "write" };
|
|
29600
|
+
const rowOrdinals = existing.map((line, oldOrdinal) => {
|
|
29601
|
+
if (replacement?.oldOrdinal === oldOrdinal)
|
|
29602
|
+
return compacted.indexOf(replacement.line);
|
|
29603
|
+
const nextOrdinal = compacted.indexOf(line);
|
|
29604
|
+
return nextOrdinal < 0 ? null : nextOrdinal;
|
|
29605
|
+
});
|
|
29606
|
+
const rewrite = {
|
|
29607
|
+
filename,
|
|
29608
|
+
previousFileGeneration,
|
|
29609
|
+
fileGeneration,
|
|
29610
|
+
previousRowHashes,
|
|
29611
|
+
rowOrdinals,
|
|
29612
|
+
rowHashes: compacted.map(timelineRowHash)
|
|
29613
|
+
};
|
|
29614
|
+
return {
|
|
29615
|
+
status: "written",
|
|
29616
|
+
rewrite,
|
|
29617
|
+
handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
|
|
29618
|
+
};
|
|
29047
29619
|
}
|
|
29048
29620
|
function filenameForDate(date5) {
|
|
29049
29621
|
const y = date5.getFullYear();
|
|
@@ -29075,114 +29647,181 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
29075
29647
|
}
|
|
29076
29648
|
return entries;
|
|
29077
29649
|
}
|
|
29078
|
-
function
|
|
29650
|
+
function readResumeControlState(timelineDir) {
|
|
29651
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29652
|
+
return { kind: "missing" };
|
|
29653
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29654
|
+
let source;
|
|
29655
|
+
try {
|
|
29656
|
+
source = fs9.lstatSync(filePath);
|
|
29657
|
+
} catch (error51) {
|
|
29658
|
+
return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
|
|
29659
|
+
}
|
|
29660
|
+
if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
|
|
29661
|
+
return { kind: "invalid" };
|
|
29662
|
+
}
|
|
29663
|
+
let fd = null;
|
|
29664
|
+
try {
|
|
29665
|
+
fd = fs9.openSync(filePath, fs9.constants.O_RDONLY | (fs9.constants.O_NOFOLLOW ?? 0));
|
|
29666
|
+
const stat = fs9.fstatSync(fd);
|
|
29667
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
|
|
29668
|
+
return { kind: "invalid" };
|
|
29669
|
+
}
|
|
29670
|
+
const bounded = Buffer.allocUnsafe(stat.size + 1);
|
|
29671
|
+
let bytesRead = 0;
|
|
29672
|
+
while (bytesRead < bounded.length) {
|
|
29673
|
+
const count = fs9.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
|
|
29674
|
+
if (count <= 0)
|
|
29675
|
+
break;
|
|
29676
|
+
bytesRead += count;
|
|
29677
|
+
}
|
|
29678
|
+
if (bytesRead !== stat.size)
|
|
29679
|
+
return { kind: "invalid" };
|
|
29680
|
+
const raw = bounded.subarray(0, bytesRead).toString("utf8");
|
|
29681
|
+
const value = JSON.parse(raw);
|
|
29682
|
+
const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
|
|
29683
|
+
if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
|
|
29684
|
+
return { kind: "invalid" };
|
|
29685
|
+
return {
|
|
29686
|
+
kind: "state",
|
|
29687
|
+
state: {
|
|
29688
|
+
version: 1,
|
|
29689
|
+
attemptedSessionId: value.attemptedSessionId,
|
|
29690
|
+
fencedSessionId: value.fencedSessionId,
|
|
29691
|
+
fullBarrier: value.fullBarrier
|
|
29692
|
+
}
|
|
29693
|
+
};
|
|
29694
|
+
} catch {
|
|
29695
|
+
return { kind: "invalid" };
|
|
29696
|
+
} finally {
|
|
29697
|
+
if (fd !== null) {
|
|
29698
|
+
try {
|
|
29699
|
+
fs9.closeSync(fd);
|
|
29700
|
+
} catch {}
|
|
29701
|
+
}
|
|
29702
|
+
}
|
|
29703
|
+
}
|
|
29704
|
+
function updateResumeControlState(timelineDir, update) {
|
|
29079
29705
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29080
29706
|
return false;
|
|
29081
|
-
const
|
|
29082
|
-
const filePath = join11(timelineDir, filename);
|
|
29083
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
29707
|
+
const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29084
29708
|
if (!acquireLock(lockPath))
|
|
29085
29709
|
return false;
|
|
29086
29710
|
try {
|
|
29087
|
-
const
|
|
29088
|
-
const
|
|
29089
|
-
|
|
29711
|
+
const current = readResumeControlState(timelineDir);
|
|
29712
|
+
const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
|
|
29713
|
+
const next = update({ ...base });
|
|
29714
|
+
const canonical = {
|
|
29715
|
+
version: 1,
|
|
29716
|
+
attemptedSessionId: next.attemptedSessionId,
|
|
29717
|
+
fencedSessionId: next.fencedSessionId,
|
|
29718
|
+
fullBarrier: next.fullBarrier
|
|
29719
|
+
};
|
|
29720
|
+
const body = JSON.stringify(canonical) + `
|
|
29721
|
+
`;
|
|
29722
|
+
if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
|
|
29090
29723
|
return false;
|
|
29091
|
-
|
|
29724
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29725
|
+
const tempPath = join11(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
|
|
29726
|
+
let fd = null;
|
|
29727
|
+
try {
|
|
29728
|
+
fd = fs9.openSync(tempPath, "wx", 384);
|
|
29729
|
+
fs9.writeFileSync(fd, body, "utf8");
|
|
29730
|
+
fs9.fsyncSync(fd);
|
|
29731
|
+
fs9.closeSync(fd);
|
|
29732
|
+
fd = null;
|
|
29733
|
+
fs9.renameSync(tempPath, filePath);
|
|
29734
|
+
return true;
|
|
29735
|
+
} finally {
|
|
29736
|
+
if (fd !== null) {
|
|
29737
|
+
try {
|
|
29738
|
+
fs9.closeSync(fd);
|
|
29739
|
+
} catch {}
|
|
29740
|
+
}
|
|
29741
|
+
try {
|
|
29742
|
+
fs9.unlinkSync(tempPath);
|
|
29743
|
+
} catch {}
|
|
29744
|
+
}
|
|
29092
29745
|
} catch {
|
|
29093
29746
|
return false;
|
|
29094
29747
|
} finally {
|
|
29095
29748
|
releaseLock(lockPath);
|
|
29096
29749
|
}
|
|
29097
29750
|
}
|
|
29098
|
-
function
|
|
29751
|
+
function appendTrackedEntry(timelineDir, entry, now = new Date) {
|
|
29099
29752
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29100
|
-
return
|
|
29753
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29101
29754
|
const filename = filenameForDate(now);
|
|
29102
29755
|
const filePath = join11(timelineDir, filename);
|
|
29103
29756
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
29104
|
-
|
|
29105
|
-
|
|
29757
|
+
try {
|
|
29758
|
+
if (!acquireLock(lockPath))
|
|
29759
|
+
return { status: "rejected", reason: "lock" };
|
|
29760
|
+
} catch {
|
|
29761
|
+
return { status: "rejected", reason: "write" };
|
|
29762
|
+
}
|
|
29106
29763
|
try {
|
|
29107
29764
|
const existing = scanTimelineFile(filePath);
|
|
29108
|
-
if (!existing)
|
|
29109
|
-
return false;
|
|
29110
|
-
if (existing.length > 0) {
|
|
29111
|
-
const latest = existing[existing.length - 1].entry;
|
|
29112
|
-
const mergeable = !latest.system && !entry.system && latest.session_id === entry.session_id && latest.provider === entry.provider && latest.agent_responses.length === 0;
|
|
29113
|
-
if (mergeable) {
|
|
29114
|
-
const merged = {
|
|
29115
|
-
...latest,
|
|
29116
|
-
messages: [...latest.messages, ...entry.messages],
|
|
29117
|
-
agent_responses: [...latest.agent_responses]
|
|
29118
|
-
};
|
|
29119
|
-
const required3 = timelineLine(merged);
|
|
29120
|
-
if (!required3)
|
|
29121
|
-
return false;
|
|
29122
|
-
return writeRequiredTimeline(filePath, [...existing.slice(0, -1), required3], required3);
|
|
29123
|
-
}
|
|
29124
|
-
}
|
|
29125
29765
|
const required2 = timelineLine(entry);
|
|
29766
|
+
if (!existing)
|
|
29767
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29126
29768
|
if (!required2)
|
|
29127
|
-
return
|
|
29128
|
-
return
|
|
29769
|
+
return { status: "rejected", reason: "oversized" };
|
|
29770
|
+
return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
|
|
29129
29771
|
} catch {
|
|
29130
|
-
return
|
|
29772
|
+
return { status: "rejected", reason: "write" };
|
|
29131
29773
|
} finally {
|
|
29132
29774
|
releaseLock(lockPath);
|
|
29133
29775
|
}
|
|
29134
29776
|
}
|
|
29135
|
-
function
|
|
29136
|
-
|
|
29137
|
-
|
|
29138
|
-
|
|
29139
|
-
|
|
29140
|
-
|
|
29141
|
-
|
|
29142
|
-
|
|
29143
|
-
|
|
29144
|
-
try {
|
|
29145
|
-
source = fs9.lstatSync(filePath);
|
|
29146
|
-
} catch (error51) {
|
|
29147
|
-
if (error51.code === "ENOENT")
|
|
29148
|
-
continue;
|
|
29149
|
-
return "rejected";
|
|
29150
|
-
}
|
|
29151
|
-
if (!source.isFile())
|
|
29152
|
-
return "rejected";
|
|
29153
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
29777
|
+
function updateTrackedEntry(timelineDir, handle, update) {
|
|
29778
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29779
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29780
|
+
if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename3(handle.filename) !== handle.filename) {
|
|
29781
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29782
|
+
}
|
|
29783
|
+
const filePath = join11(timelineDir, handle.filename);
|
|
29784
|
+
const lockPath = lockPathFor(timelineDir, handle.filename);
|
|
29785
|
+
try {
|
|
29154
29786
|
if (!acquireLock(lockPath))
|
|
29155
|
-
return "rejected";
|
|
29156
|
-
|
|
29157
|
-
|
|
29158
|
-
|
|
29159
|
-
|
|
29160
|
-
|
|
29161
|
-
|
|
29162
|
-
|
|
29163
|
-
|
|
29164
|
-
|
|
29165
|
-
|
|
29166
|
-
|
|
29167
|
-
|
|
29168
|
-
|
|
29169
|
-
|
|
29170
|
-
|
|
29171
|
-
|
|
29172
|
-
|
|
29173
|
-
|
|
29174
|
-
|
|
29175
|
-
|
|
29176
|
-
|
|
29177
|
-
|
|
29178
|
-
|
|
29179
|
-
|
|
29180
|
-
|
|
29181
|
-
}
|
|
29182
|
-
|
|
29183
|
-
|
|
29787
|
+
return { status: "rejected", reason: "lock" };
|
|
29788
|
+
} catch {
|
|
29789
|
+
return { status: "rejected", reason: "write" };
|
|
29790
|
+
}
|
|
29791
|
+
try {
|
|
29792
|
+
const existing = scanTimelineFile(filePath);
|
|
29793
|
+
if (!existing)
|
|
29794
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29795
|
+
if (existing.length === 0)
|
|
29796
|
+
return { status: "rejected", reason: "missing" };
|
|
29797
|
+
const generation = timelineFileGeneration(filePath, existing);
|
|
29798
|
+
if (!generation || generation !== handle.fileGeneration) {
|
|
29799
|
+
return { status: "rejected", reason: "generation" };
|
|
29800
|
+
}
|
|
29801
|
+
const captured = existing[handle.rowOrdinal];
|
|
29802
|
+
if (!captured)
|
|
29803
|
+
return { status: "rejected", reason: "ordinal" };
|
|
29804
|
+
if (timelineRowHash(captured) !== handle.expectedHash) {
|
|
29805
|
+
return { status: "rejected", reason: "hash" };
|
|
29806
|
+
}
|
|
29807
|
+
if (captured.entry.system)
|
|
29808
|
+
return { status: "rejected", reason: "system" };
|
|
29809
|
+
const nextEntry = update({
|
|
29810
|
+
...captured.entry,
|
|
29811
|
+
messages: [...captured.entry.messages],
|
|
29812
|
+
agent_responses: [...captured.entry.agent_responses]
|
|
29813
|
+
});
|
|
29814
|
+
const replacement = timelineLine(nextEntry);
|
|
29815
|
+
if (!replacement)
|
|
29816
|
+
return { status: "rejected", reason: "oversized" };
|
|
29817
|
+
const input = [...existing];
|
|
29818
|
+
input[handle.rowOrdinal] = replacement;
|
|
29819
|
+
return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
|
|
29820
|
+
} catch {
|
|
29821
|
+
return { status: "rejected", reason: "write" };
|
|
29822
|
+
} finally {
|
|
29823
|
+
releaseLock(lockPath);
|
|
29184
29824
|
}
|
|
29185
|
-
return "missing";
|
|
29186
29825
|
}
|
|
29187
29826
|
function yieldToEventLoop() {
|
|
29188
29827
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
@@ -29246,84 +29885,562 @@ function createTimelineEntry(fields) {
|
|
|
29246
29885
|
provider: fields.provider ?? null
|
|
29247
29886
|
};
|
|
29248
29887
|
}
|
|
29249
|
-
function createSystemEntry(type, time3) {
|
|
29888
|
+
function createSystemEntry(type, time3, backendSessionId) {
|
|
29250
29889
|
return {
|
|
29251
29890
|
session_id: null,
|
|
29252
29891
|
messages: [],
|
|
29253
29892
|
agent_responses: [],
|
|
29254
29893
|
provider: null,
|
|
29255
|
-
system: {
|
|
29894
|
+
system: {
|
|
29895
|
+
type,
|
|
29896
|
+
time: time3,
|
|
29897
|
+
...backendSessionId ? { backend_session_id: backendSessionId } : {}
|
|
29898
|
+
}
|
|
29256
29899
|
};
|
|
29257
29900
|
}
|
|
29258
|
-
function
|
|
29901
|
+
function resolveResumableSession(rows, provider) {
|
|
29902
|
+
let candidateSessionId = null;
|
|
29903
|
+
let recoveryMarkerSeen = false;
|
|
29904
|
+
let stalledSessionId = null;
|
|
29905
|
+
let fencedSessionId = null;
|
|
29259
29906
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
29260
29907
|
const e = rows[i];
|
|
29261
|
-
if (e.system
|
|
29262
|
-
|
|
29908
|
+
if (e.system) {
|
|
29909
|
+
if (e.system.type === "stall_recovery_attempt") {
|
|
29910
|
+
if (!recoveryMarkerSeen) {
|
|
29911
|
+
recoveryMarkerSeen = true;
|
|
29912
|
+
stalledSessionId = e.system.backend_session_id ?? null;
|
|
29913
|
+
}
|
|
29914
|
+
continue;
|
|
29915
|
+
}
|
|
29916
|
+
if (e.system.type === "stall_recovery_clear") {
|
|
29917
|
+
if (!recoveryMarkerSeen)
|
|
29918
|
+
recoveryMarkerSeen = true;
|
|
29919
|
+
continue;
|
|
29920
|
+
}
|
|
29921
|
+
if (e.system.type === "stall_recovery" && fencedSessionId === null) {
|
|
29922
|
+
fencedSessionId = e.system.backend_session_id ?? null;
|
|
29923
|
+
}
|
|
29924
|
+
if (candidateSessionId !== null) {
|
|
29925
|
+
return {
|
|
29926
|
+
kind: "session",
|
|
29927
|
+
sessionId: candidateSessionId,
|
|
29928
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
29929
|
+
fencedSessionId
|
|
29930
|
+
};
|
|
29931
|
+
}
|
|
29932
|
+
return {
|
|
29933
|
+
kind: "barrier",
|
|
29934
|
+
type: e.system.type,
|
|
29935
|
+
forgottenSessionId: e.system.backend_session_id ?? null,
|
|
29936
|
+
fencedSessionId
|
|
29937
|
+
};
|
|
29938
|
+
}
|
|
29263
29939
|
if (!e.session_id)
|
|
29264
29940
|
continue;
|
|
29265
29941
|
if (provider && e.provider !== provider)
|
|
29266
29942
|
continue;
|
|
29267
|
-
|
|
29943
|
+
if (candidateSessionId === null) {
|
|
29944
|
+
candidateSessionId = e.session_id;
|
|
29945
|
+
continue;
|
|
29946
|
+
}
|
|
29947
|
+
if (candidateSessionId !== e.session_id)
|
|
29948
|
+
break;
|
|
29268
29949
|
}
|
|
29269
|
-
|
|
29950
|
+
if (candidateSessionId !== null) {
|
|
29951
|
+
return {
|
|
29952
|
+
kind: "session",
|
|
29953
|
+
sessionId: candidateSessionId,
|
|
29954
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
29955
|
+
fencedSessionId
|
|
29956
|
+
};
|
|
29957
|
+
}
|
|
29958
|
+
return { kind: "none", stalledSessionId, fencedSessionId };
|
|
29270
29959
|
}
|
|
29271
29960
|
// src/timeline/recorder.ts
|
|
29272
29961
|
var MAX_AGENT_RESPONSES = 5;
|
|
29273
|
-
|
|
29274
|
-
|
|
29275
|
-
|
|
29276
|
-
|
|
29962
|
+
var MAX_AGENT_RESPONSE_BYTES = 65536;
|
|
29963
|
+
var MAX_PENDING_COMMITS_PER_AGENT = 8;
|
|
29964
|
+
var PENDING_COMMIT_TTL_MS = 15 * 60000;
|
|
29965
|
+
var TRUNCATION_MARKER = `
|
|
29966
|
+
… [truncated]`;
|
|
29967
|
+
function turnKey(agentId, owner) {
|
|
29968
|
+
return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
|
|
29969
|
+
}
|
|
29970
|
+
function sameOwner(left, right) {
|
|
29971
|
+
return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
|
|
29972
|
+
}
|
|
29973
|
+
function utf8Prefix2(text2, maxBytes) {
|
|
29974
|
+
if (maxBytes <= 0)
|
|
29975
|
+
return "";
|
|
29976
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
29977
|
+
return text2;
|
|
29978
|
+
let low = 0;
|
|
29979
|
+
let high = text2.length;
|
|
29980
|
+
while (low < high) {
|
|
29981
|
+
const mid = Math.ceil((low + high) / 2);
|
|
29982
|
+
let end2 = mid;
|
|
29983
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
29984
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
29985
|
+
end2 -= 1;
|
|
29986
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
29987
|
+
low = mid;
|
|
29988
|
+
else
|
|
29989
|
+
high = mid - 1;
|
|
29277
29990
|
}
|
|
29991
|
+
let end = low;
|
|
29992
|
+
const code = text2.charCodeAt(end - 1);
|
|
29993
|
+
if (code >= 55296 && code <= 56319)
|
|
29994
|
+
end -= 1;
|
|
29995
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
29996
|
+
end -= 1;
|
|
29997
|
+
return text2.slice(0, end);
|
|
29998
|
+
}
|
|
29999
|
+
function boundedResponse(text2, alreadyTruncated) {
|
|
30000
|
+
const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
|
|
30001
|
+
const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
|
|
30002
|
+
if (!needsTruncation)
|
|
30003
|
+
return text2;
|
|
30004
|
+
return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
|
|
30005
|
+
}
|
|
30006
|
+
function serializedTimelineBytes(entry) {
|
|
30007
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
30008
|
+
}
|
|
30009
|
+
function fitResponsesToRow(entry, responses) {
|
|
30010
|
+
let fitted = [...entry.agent_responses];
|
|
30011
|
+
for (const response of responses) {
|
|
30012
|
+
fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
|
|
30013
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
|
|
30014
|
+
continue;
|
|
30015
|
+
const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
|
|
30016
|
+
let low = 0;
|
|
30017
|
+
let high = truncationBase.length;
|
|
30018
|
+
let replacement = null;
|
|
30019
|
+
while (low <= high) {
|
|
30020
|
+
const mid = Math.floor((low + high) / 2);
|
|
30021
|
+
let end = mid;
|
|
30022
|
+
const code = truncationBase.charCodeAt(end - 1);
|
|
30023
|
+
if (code >= 55296 && code <= 56319)
|
|
30024
|
+
end -= 1;
|
|
30025
|
+
const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
|
|
30026
|
+
const candidate = [...fitted.slice(0, -1), candidateResponse];
|
|
30027
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
|
|
30028
|
+
replacement = candidateResponse;
|
|
30029
|
+
low = mid + 1;
|
|
30030
|
+
} else {
|
|
30031
|
+
high = mid - 1;
|
|
30032
|
+
}
|
|
30033
|
+
}
|
|
30034
|
+
if (replacement === null)
|
|
30035
|
+
return null;
|
|
30036
|
+
fitted[fitted.length - 1] = replacement;
|
|
30037
|
+
}
|
|
30038
|
+
return fitted;
|
|
29278
30039
|
}
|
|
29279
30040
|
function createTimelineRecorder(opts) {
|
|
29280
30041
|
const now = opts.now ?? (() => new Date);
|
|
29281
30042
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
29282
30043
|
const sessionByAgent = new Map;
|
|
30044
|
+
const resolveForAgent = (agentId, provider) => {
|
|
30045
|
+
const dir = dirFor(agentId);
|
|
30046
|
+
const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
|
|
30047
|
+
const control = readResumeControlState(dir);
|
|
30048
|
+
if (control.kind === "missing")
|
|
30049
|
+
return recent;
|
|
30050
|
+
if (control.kind === "invalid") {
|
|
30051
|
+
return {
|
|
30052
|
+
kind: "barrier",
|
|
30053
|
+
type: "reset_session",
|
|
30054
|
+
forgottenSessionId: null,
|
|
30055
|
+
fencedSessionId: null
|
|
30056
|
+
};
|
|
30057
|
+
}
|
|
30058
|
+
const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
|
|
30059
|
+
if (fullBarrier !== null) {
|
|
30060
|
+
return {
|
|
30061
|
+
kind: "barrier",
|
|
30062
|
+
type: fullBarrier,
|
|
30063
|
+
forgottenSessionId: null,
|
|
30064
|
+
fencedSessionId
|
|
30065
|
+
};
|
|
30066
|
+
}
|
|
30067
|
+
if (recent.kind === "session") {
|
|
30068
|
+
return {
|
|
30069
|
+
kind: "session",
|
|
30070
|
+
sessionId: recent.sessionId,
|
|
30071
|
+
stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
|
|
30072
|
+
fencedSessionId
|
|
30073
|
+
};
|
|
30074
|
+
}
|
|
30075
|
+
if (recent.kind === "barrier") {
|
|
30076
|
+
if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
|
|
30077
|
+
return { ...recent, fencedSessionId };
|
|
30078
|
+
}
|
|
30079
|
+
}
|
|
30080
|
+
return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
|
|
30081
|
+
};
|
|
30082
|
+
const sessionByEpoch = new Map;
|
|
30083
|
+
const barrierByAgent = new Map;
|
|
30084
|
+
const turnsByAgent = new Map;
|
|
30085
|
+
const activeTurnByAgent = new Map;
|
|
30086
|
+
const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
|
|
30087
|
+
const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
|
|
30088
|
+
const statesFor = (agentId) => {
|
|
30089
|
+
let states = turnsByAgent.get(agentId);
|
|
30090
|
+
if (!states) {
|
|
30091
|
+
states = new Map;
|
|
30092
|
+
turnsByAgent.set(agentId, states);
|
|
30093
|
+
}
|
|
30094
|
+
return states;
|
|
30095
|
+
};
|
|
30096
|
+
const diagnostic = (agentId, code, reason) => {
|
|
30097
|
+
try {
|
|
30098
|
+
opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
|
|
30099
|
+
} catch {}
|
|
30100
|
+
};
|
|
30101
|
+
const applyRewrite = (agentId, rewrite) => {
|
|
30102
|
+
for (const state of statesFor(agentId).values()) {
|
|
30103
|
+
if (!state.handle)
|
|
30104
|
+
continue;
|
|
30105
|
+
const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
|
|
30106
|
+
if (refreshed) {
|
|
30107
|
+
state.handle = refreshed;
|
|
30108
|
+
} else if (state.handle.filename === rewrite.filename) {
|
|
30109
|
+
state.handle = undefined;
|
|
30110
|
+
state.rowFenced = true;
|
|
30111
|
+
diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
|
|
30112
|
+
}
|
|
30113
|
+
}
|
|
30114
|
+
};
|
|
30115
|
+
const handleTrackedResult = (agentId, state, result) => {
|
|
30116
|
+
if (result.status === "written") {
|
|
30117
|
+
applyRewrite(agentId, result.rewrite);
|
|
30118
|
+
if (state && result.handle) {
|
|
30119
|
+
state.handle = result.handle;
|
|
30120
|
+
state.rowFenced = false;
|
|
30121
|
+
}
|
|
30122
|
+
return "written";
|
|
30123
|
+
}
|
|
30124
|
+
if (result.reason === "lock" || result.reason === "write")
|
|
30125
|
+
return "retryable";
|
|
30126
|
+
if (state && result.reason !== "oversized")
|
|
30127
|
+
state.rowFenced = true;
|
|
30128
|
+
diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
|
|
30129
|
+
return "terminal";
|
|
30130
|
+
};
|
|
30131
|
+
const tryCommit = (agentId, state) => {
|
|
30132
|
+
if (state.responses.length === 0)
|
|
30133
|
+
return "written";
|
|
30134
|
+
const dir = dirFor(agentId);
|
|
30135
|
+
if (!prepareTimelineDirectory(dir)) {
|
|
30136
|
+
diagnostic(agentId, "timeline_directory_unavailable");
|
|
30137
|
+
return "terminal";
|
|
30138
|
+
}
|
|
30139
|
+
if (state.handle) {
|
|
30140
|
+
const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
|
|
30141
|
+
const fitted2 = fitResponsesToRow(entry2, state.responses);
|
|
30142
|
+
return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
|
|
30143
|
+
});
|
|
30144
|
+
return handleTrackedResult(agentId, state, result2);
|
|
30145
|
+
}
|
|
30146
|
+
if (state.rowFenced || state.pendingMode === "handle")
|
|
30147
|
+
return "terminal";
|
|
30148
|
+
if (state.owner.barrierGeneration !== currentBarrier(agentId))
|
|
30149
|
+
return "terminal";
|
|
30150
|
+
const entry = createTimelineEntry({
|
|
30151
|
+
messages: [],
|
|
30152
|
+
sessionId: state.backendSessionId,
|
|
30153
|
+
provider: state.provider
|
|
30154
|
+
});
|
|
30155
|
+
const fitted = fitResponsesToRow(entry, state.responses);
|
|
30156
|
+
if (!fitted) {
|
|
30157
|
+
diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
|
|
30158
|
+
return "terminal";
|
|
30159
|
+
}
|
|
30160
|
+
entry.agent_responses = fitted;
|
|
30161
|
+
const result = appendTrackedEntry(dir, entry, now());
|
|
30162
|
+
return handleTrackedResult(agentId, state, result);
|
|
30163
|
+
};
|
|
30164
|
+
const deleteState = (agentId, key) => {
|
|
30165
|
+
const states = statesFor(agentId);
|
|
30166
|
+
states.delete(key);
|
|
30167
|
+
if (activeTurnByAgent.get(agentId) === key)
|
|
30168
|
+
activeTurnByAgent.delete(agentId);
|
|
30169
|
+
if (states.size === 0)
|
|
30170
|
+
turnsByAgent.delete(agentId);
|
|
30171
|
+
};
|
|
30172
|
+
const retryPending = (agentId) => {
|
|
30173
|
+
const states = turnsByAgent.get(agentId);
|
|
30174
|
+
if (!states)
|
|
30175
|
+
return;
|
|
30176
|
+
const nowMs = now().getTime();
|
|
30177
|
+
for (const [key, state] of [...states]) {
|
|
30178
|
+
if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
|
|
30179
|
+
deleteState(agentId, key);
|
|
30180
|
+
continue;
|
|
30181
|
+
}
|
|
30182
|
+
if (!state.finalized || state.pendingSinceMs === undefined)
|
|
30183
|
+
continue;
|
|
30184
|
+
if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
|
|
30185
|
+
diagnostic(agentId, "timeline_pending_commit_expired");
|
|
30186
|
+
deleteState(agentId, key);
|
|
30187
|
+
continue;
|
|
30188
|
+
}
|
|
30189
|
+
const result = tryCommit(agentId, state);
|
|
30190
|
+
if (result === "written") {
|
|
30191
|
+
state.responses = [];
|
|
30192
|
+
state.pendingSinceMs = undefined;
|
|
30193
|
+
state.pendingMode = undefined;
|
|
30194
|
+
state.completedAtMs = nowMs;
|
|
30195
|
+
} else if (result === "terminal") {
|
|
30196
|
+
deleteState(agentId, key);
|
|
30197
|
+
}
|
|
30198
|
+
}
|
|
30199
|
+
const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
|
|
30200
|
+
while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
|
|
30201
|
+
const oldest = completed.shift();
|
|
30202
|
+
if (oldest)
|
|
30203
|
+
deleteState(agentId, oldest[0]);
|
|
30204
|
+
}
|
|
30205
|
+
};
|
|
30206
|
+
const retainPending = (agentId, key, state) => {
|
|
30207
|
+
const states = statesFor(agentId);
|
|
30208
|
+
const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
|
|
30209
|
+
if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
|
|
30210
|
+
diagnostic(agentId, "timeline_pending_commit_overflow");
|
|
30211
|
+
deleteState(agentId, key);
|
|
30212
|
+
return;
|
|
30213
|
+
}
|
|
30214
|
+
state.pendingSinceMs ??= now().getTime();
|
|
30215
|
+
state.pendingMode = state.handle ? "handle" : "fallback";
|
|
30216
|
+
};
|
|
30217
|
+
const appendOwnerless = (agentId, messages) => {
|
|
30218
|
+
if (messages.length === 0)
|
|
30219
|
+
return;
|
|
30220
|
+
const dir = dirFor(agentId);
|
|
30221
|
+
if (!prepareTimelineDirectory(dir))
|
|
30222
|
+
return;
|
|
30223
|
+
const result = appendTrackedEntry(dir, createTimelineEntry({
|
|
30224
|
+
messages,
|
|
30225
|
+
sessionId: sessionByAgent.get(agentId) ?? null,
|
|
30226
|
+
provider: opts.providerFor?.(agentId) ?? null
|
|
30227
|
+
}), now());
|
|
30228
|
+
handleTrackedResult(agentId, null, result);
|
|
30229
|
+
};
|
|
29283
30230
|
return {
|
|
29284
|
-
|
|
29285
|
-
|
|
30231
|
+
barrierGeneration(agentId) {
|
|
30232
|
+
return currentBarrier(agentId);
|
|
29286
30233
|
},
|
|
29287
|
-
|
|
30234
|
+
beginTurn(agentId, owner) {
|
|
30235
|
+
retryPending(agentId);
|
|
30236
|
+
const states = statesFor(agentId);
|
|
30237
|
+
if (owner.barrierGeneration !== currentBarrier(agentId)) {
|
|
30238
|
+
diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
|
|
30239
|
+
return;
|
|
30240
|
+
}
|
|
30241
|
+
const key = turnKey(agentId, owner);
|
|
30242
|
+
if (!states.has(key)) {
|
|
30243
|
+
states.set(key, {
|
|
30244
|
+
owner: { ...owner },
|
|
30245
|
+
provider: opts.providerFor?.(agentId) ?? null,
|
|
30246
|
+
backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
|
|
30247
|
+
responses: [],
|
|
30248
|
+
rowFenced: false,
|
|
30249
|
+
finalized: false
|
|
30250
|
+
});
|
|
30251
|
+
}
|
|
30252
|
+
activeTurnByAgent.set(agentId, key);
|
|
30253
|
+
},
|
|
30254
|
+
recordInboxPull(agentId, owner, messages) {
|
|
30255
|
+
retryPending(agentId);
|
|
29288
30256
|
if (messages.length === 0)
|
|
29289
30257
|
return;
|
|
30258
|
+
if (!owner) {
|
|
30259
|
+
appendOwnerless(agentId, messages);
|
|
30260
|
+
return;
|
|
30261
|
+
}
|
|
30262
|
+
const key = turnKey(agentId, owner);
|
|
30263
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30264
|
+
if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
|
|
30265
|
+
appendOwnerless(agentId, messages);
|
|
30266
|
+
return;
|
|
30267
|
+
}
|
|
29290
30268
|
const dir = dirFor(agentId);
|
|
29291
30269
|
if (!prepareTimelineDirectory(dir))
|
|
29292
30270
|
return;
|
|
29293
|
-
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
|
|
29297
|
-
|
|
30271
|
+
const stamp = now();
|
|
30272
|
+
let result;
|
|
30273
|
+
if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
|
|
30274
|
+
result = updateTrackedEntry(dir, state.handle, (entry) => ({
|
|
30275
|
+
...entry,
|
|
30276
|
+
messages: [...entry.messages, ...messages],
|
|
30277
|
+
session_id: state.backendSessionId,
|
|
30278
|
+
provider: state.provider
|
|
30279
|
+
}));
|
|
30280
|
+
} else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
|
|
30281
|
+
result = appendTrackedEntry(dir, createTimelineEntry({
|
|
30282
|
+
messages,
|
|
30283
|
+
sessionId: state.backendSessionId,
|
|
30284
|
+
provider: state.provider
|
|
30285
|
+
}), stamp);
|
|
30286
|
+
} else {
|
|
30287
|
+
appendOwnerless(agentId, messages);
|
|
30288
|
+
return;
|
|
30289
|
+
}
|
|
30290
|
+
handleTrackedResult(agentId, state, result);
|
|
30291
|
+
},
|
|
30292
|
+
recordAssistantMessage(agentId, owner, text2, truncated = false) {
|
|
30293
|
+
retryPending(agentId);
|
|
30294
|
+
const key = turnKey(agentId, owner);
|
|
30295
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30296
|
+
if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
|
|
30297
|
+
diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
|
|
30298
|
+
return;
|
|
30299
|
+
}
|
|
30300
|
+
state.responses.push(boundedResponse(text2, truncated));
|
|
30301
|
+
if (state.responses.length > MAX_AGENT_RESPONSES) {
|
|
30302
|
+
state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
|
|
30303
|
+
}
|
|
29298
30304
|
},
|
|
29299
|
-
|
|
29300
|
-
|
|
29301
|
-
|
|
30305
|
+
finalizeTurn(agentId, owner) {
|
|
30306
|
+
retryPending(agentId);
|
|
30307
|
+
const key = turnKey(agentId, owner);
|
|
30308
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30309
|
+
if (!state || state.finalized || !sameOwner(state.owner, owner))
|
|
29302
30310
|
return;
|
|
29303
|
-
const
|
|
29304
|
-
|
|
30311
|
+
const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
|
|
30312
|
+
state.finalized = true;
|
|
30313
|
+
activeTurnByAgent.delete(agentId);
|
|
30314
|
+
if (state.responses.length === 0) {
|
|
30315
|
+
state.completedAtMs = now().getTime();
|
|
29305
30316
|
return;
|
|
29306
|
-
|
|
29307
|
-
|
|
29308
|
-
|
|
29309
|
-
|
|
29310
|
-
|
|
29311
|
-
|
|
29312
|
-
|
|
30317
|
+
}
|
|
30318
|
+
if (!state.handle && !fallbackAuthorized) {
|
|
30319
|
+
diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
|
|
30320
|
+
deleteState(agentId, key);
|
|
30321
|
+
return;
|
|
30322
|
+
}
|
|
30323
|
+
const result = tryCommit(agentId, state);
|
|
30324
|
+
if (result === "written") {
|
|
30325
|
+
state.responses = [];
|
|
30326
|
+
state.completedAtMs = now().getTime();
|
|
30327
|
+
} else if (result === "terminal") {
|
|
30328
|
+
deleteState(agentId, key);
|
|
30329
|
+
} else {
|
|
30330
|
+
retainPending(agentId, key, state);
|
|
30331
|
+
}
|
|
30332
|
+
},
|
|
30333
|
+
fenceSession(agentId) {
|
|
30334
|
+
retryPending(agentId);
|
|
30335
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
30336
|
+
activeTurnByAgent.delete(agentId);
|
|
30337
|
+
const states = turnsByAgent.get(agentId);
|
|
30338
|
+
if (!states)
|
|
30339
|
+
return;
|
|
30340
|
+
for (const [key, state] of [...states]) {
|
|
30341
|
+
if (!state.handle) {
|
|
30342
|
+
diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
|
|
30343
|
+
deleteState(agentId, key);
|
|
30344
|
+
}
|
|
30345
|
+
}
|
|
30346
|
+
},
|
|
30347
|
+
setSession(agentId, sessionId, sessionInstanceId) {
|
|
30348
|
+
retryPending(agentId);
|
|
30349
|
+
const dir = dirFor(agentId);
|
|
30350
|
+
if (!prepareTimelineDirectory(dir))
|
|
30351
|
+
return false;
|
|
30352
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
30353
|
+
...state,
|
|
30354
|
+
fullBarrier: null,
|
|
30355
|
+
attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
|
|
30356
|
+
}));
|
|
30357
|
+
if (!persisted)
|
|
30358
|
+
return false;
|
|
30359
|
+
sessionByAgent.set(agentId, sessionId);
|
|
30360
|
+
if (sessionInstanceId) {
|
|
30361
|
+
sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
|
|
30362
|
+
for (const state of statesFor(agentId).values()) {
|
|
30363
|
+
if (state.owner.sessionInstanceId === sessionInstanceId)
|
|
30364
|
+
state.backendSessionId = sessionId;
|
|
30365
|
+
}
|
|
30366
|
+
}
|
|
30367
|
+
return true;
|
|
29313
30368
|
},
|
|
29314
30369
|
resumeSessionId(agentId, provider) {
|
|
29315
|
-
const
|
|
29316
|
-
return
|
|
30370
|
+
const resolution = resolveForAgent(agentId, provider);
|
|
30371
|
+
return resolution.kind === "session" ? resolution.sessionId : null;
|
|
30372
|
+
},
|
|
30373
|
+
resolveResumeSession(agentId, provider) {
|
|
30374
|
+
return resolveForAgent(agentId, provider);
|
|
30375
|
+
},
|
|
30376
|
+
recordSessionStall(agentId, sessionId) {
|
|
30377
|
+
return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
|
|
29317
30378
|
},
|
|
29318
|
-
|
|
30379
|
+
clearSessionStall(agentId, sessionId) {
|
|
30380
|
+
return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
|
|
30381
|
+
},
|
|
30382
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
30383
|
+
retryPending(agentId);
|
|
29319
30384
|
const dir = dirFor(agentId);
|
|
29320
|
-
sessionByAgent.delete(agentId);
|
|
29321
30385
|
if (!prepareTimelineDirectory(dir))
|
|
29322
|
-
return;
|
|
30386
|
+
return false;
|
|
29323
30387
|
const stamp = now();
|
|
29324
|
-
|
|
30388
|
+
let persisted = false;
|
|
30389
|
+
if (barrierType === "reset_session" || barrierType === "nap") {
|
|
30390
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
30391
|
+
...state,
|
|
30392
|
+
attemptedSessionId: null,
|
|
30393
|
+
fencedSessionId: null,
|
|
30394
|
+
fullBarrier: barrierType
|
|
30395
|
+
}));
|
|
30396
|
+
} else if (barrierType === "stall_recovery") {
|
|
30397
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
30398
|
+
...state,
|
|
30399
|
+
attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
|
|
30400
|
+
fencedSessionId: forgottenSessionId ?? null,
|
|
30401
|
+
fullBarrier: null
|
|
30402
|
+
}));
|
|
30403
|
+
}
|
|
30404
|
+
if (!persisted)
|
|
30405
|
+
return false;
|
|
30406
|
+
sessionByAgent.delete(agentId);
|
|
30407
|
+
for (const key of [...sessionByEpoch.keys()]) {
|
|
30408
|
+
if (key.startsWith(`${agentId}\x00`))
|
|
30409
|
+
sessionByEpoch.delete(key);
|
|
30410
|
+
}
|
|
30411
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
30412
|
+
activeTurnByAgent.delete(agentId);
|
|
30413
|
+
const states = turnsByAgent.get(agentId);
|
|
30414
|
+
if (states) {
|
|
30415
|
+
for (const [key, state] of [...states]) {
|
|
30416
|
+
if (!state.handle) {
|
|
30417
|
+
diagnostic(agentId, "timeline_fallback_rejected", "barrier");
|
|
30418
|
+
deleteState(agentId, key);
|
|
30419
|
+
}
|
|
30420
|
+
}
|
|
30421
|
+
}
|
|
30422
|
+
const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
|
|
30423
|
+
handleTrackedResult(agentId, null, result);
|
|
30424
|
+
return true;
|
|
29325
30425
|
}
|
|
29326
30426
|
};
|
|
30427
|
+
function appendStallMarker(agentId, type, sessionId) {
|
|
30428
|
+
retryPending(agentId);
|
|
30429
|
+
const dir = dirFor(agentId);
|
|
30430
|
+
if (!prepareTimelineDirectory(dir))
|
|
30431
|
+
return false;
|
|
30432
|
+
const stamp = now();
|
|
30433
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
30434
|
+
...state,
|
|
30435
|
+
attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
|
|
30436
|
+
fullBarrier: null
|
|
30437
|
+
}));
|
|
30438
|
+
if (!persisted)
|
|
30439
|
+
return false;
|
|
30440
|
+
const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
|
|
30441
|
+
handleTrackedResult(agentId, null, result);
|
|
30442
|
+
return true;
|
|
30443
|
+
}
|
|
29327
30444
|
}
|
|
29328
30445
|
// src/daemon/diagnosticsCommand.ts
|
|
29329
30446
|
function reportUnavailable(options, reportId) {
|
|
@@ -29868,11 +30985,15 @@ async function createDaemon(opts) {
|
|
|
29868
30985
|
const typingTracker = createTypingScopeTracker();
|
|
29869
30986
|
const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
|
|
29870
30987
|
const proxy = await startCredentialProxy(broker, {
|
|
29871
|
-
onInboxPullStart: (agentId) =>
|
|
30988
|
+
onInboxPullStart: (agentId) => ({
|
|
30989
|
+
modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
|
|
30990
|
+
owner: managerRef?.timelineTurnOwner(agentId) ?? null
|
|
30991
|
+
}),
|
|
29872
30992
|
onInboxPullResponse: (agentId, messages, observationToken) => {
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
|
|
30993
|
+
const token = observationToken && typeof observationToken === "object" ? observationToken : null;
|
|
30994
|
+
timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
|
|
30995
|
+
if (typeof token?.modelSeenGeneration === "number") {
|
|
30996
|
+
channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
|
|
29876
30997
|
}
|
|
29877
30998
|
},
|
|
29878
30999
|
onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
|
|
@@ -30634,8 +31755,8 @@ function projectDaemonLogRow(value, targetAgentId) {
|
|
|
30634
31755
|
fields: projectedFields
|
|
30635
31756
|
};
|
|
30636
31757
|
}
|
|
30637
|
-
var MANAGER_EVENTS = ["register", "wake", "spawned", "session", "root_work", "turn_end", "exit", "tick", "reset_session", "begin_reset", "rewake_after_reset", "runtime_signal", "admission_started", "admission_settled"];
|
|
30638
|
-
var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "force_exit", "gated_hold"];
|
|
31758
|
+
var MANAGER_EVENTS = ["register", "wake", "spawned", "session", "root_work", "turn_end", "exit", "tick", "reset_session", "begin_reset", "rewake_after_reset", "runtime_signal", "stall_control_failed", "admission_started", "admission_settled"];
|
|
31759
|
+
var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "clear_stall_recovery", "force_exit", "gated_hold"];
|
|
30639
31760
|
var AGENT_STATUSES = ["idle", "starting", "running", "stopping"];
|
|
30640
31761
|
var DELIVERY_PHASES = ["idle", "admission_wait", "steering", "next_turn_queued", "compacting", "reviewing", "tool_wait", "working"];
|
|
30641
31762
|
var RESUME_OUTCOMES = ["not_requested", "pending", "resumed", "reset_required", "failed"];
|
|
@@ -30644,6 +31765,8 @@ var TERMINATION_CAUSES = ["runtime_error", "killed_stalled", "other"];
|
|
|
30644
31765
|
var SPAWN_FAILURE_REASONS = ["ENOENT", "handshake_timeout", "pre_handshake_exit", "spawn_threw", "other"];
|
|
30645
31766
|
var TERMINATION_SEMANTICS = ["killed_stalled", "idle_stop", "force_exit", "other"];
|
|
30646
31767
|
var ABORT_CAUSES = ["start_threw", "start_rejected", "send_threw", "spawn_failure", "handshake_timeout", "reset", "nap", "model_switch", "requested_stop", "shutdown", "physical_exit", "terminate_stalled", "force_exit", "other"];
|
|
31768
|
+
var NATIVE_ACTIVITY_KINDS = ["turn_started", "backend_turn_started", "thinking", "text", "tool_call", "tool_output", "internal_progress", "recovery", "turn_end"];
|
|
31769
|
+
var RUNTIME_PHASES = ["idle", "admission", "inference", "tool", "recovery", "terminal"];
|
|
30647
31770
|
var EXIT_SIGNALS = new Set(["SIGABRT", "SIGALRM", "SIGBREAK", "SIGBUS", "SIGCHLD", "SIGCONT", "SIGFPE", "SIGHUP", "SIGILL", "SIGINFO", "SIGINT", "SIGIO", "SIGIOT", "SIGKILL", "SIGLOST", "SIGPIPE", "SIGPOLL", "SIGPROF", "SIGPWR", "SIGQUIT", "SIGSEGV", "SIGSTKFLT", "SIGSTOP", "SIGSYS", "SIGTERM", "SIGTRAP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGUSR1", "SIGUSR2", "SIGVTALRM", "SIGWINCH", "SIGXCPU", "SIGXFSZ", "other"]);
|
|
30648
31771
|
function enumValue(value, values, bucket = false, maxChars = 64) {
|
|
30649
31772
|
if (!boundedString(value, maxChars))
|
|
@@ -30681,6 +31804,13 @@ function copyString(row, output, key, maxChars, optional2 = false, nullable2 = f
|
|
|
30681
31804
|
output[key] = row[key];
|
|
30682
31805
|
return true;
|
|
30683
31806
|
}
|
|
31807
|
+
function copyScrubbedString(row, output, key, maxChars, optional2 = false, nullable2 = false) {
|
|
31808
|
+
if (!copyString(row, output, key, maxChars, optional2, nullable2))
|
|
31809
|
+
return false;
|
|
31810
|
+
if (typeof output[key] === "string")
|
|
31811
|
+
output[key] = scrubDiagnosticText(output[key]);
|
|
31812
|
+
return true;
|
|
31813
|
+
}
|
|
30684
31814
|
function projectTraceBase(row, targetAgentId) {
|
|
30685
31815
|
if (row.agentId !== targetAgentId || !boundedString(row.agentId, 128) || !canonicalTime(row.timeIso))
|
|
30686
31816
|
return null;
|
|
@@ -30707,11 +31837,29 @@ function projectFsm(row, targetAgentId) {
|
|
|
30707
31837
|
Object.assign(output, { event, status, turnActive: row.turnActive });
|
|
30708
31838
|
if (!copyInteger(row, output, "inbox") || !copyInteger(row, output, "lastDeliverAt", { nullable: true }) || !copyInteger(row, output, "lastProgressAt") || !copyInteger(row, output, "idleSince", { nullable: true }))
|
|
30709
31839
|
return null;
|
|
31840
|
+
if (!copyInteger(row, output, "lastNativeActivityAt", { optional: true }) || !copyScrubbedString(row, output, "backendTurnId", 512, true, true) || !copyInteger(row, output, "turnSilenceBudgetMs", { optional: true }) || !copyInteger(row, output, "nativeDeadlineAt", { optional: true, nullable: true }) || !copyInteger(row, output, "recoveryExtensionsUsed", { optional: true }))
|
|
31841
|
+
return null;
|
|
31842
|
+
if ("lastNativeActivityKind" in row && row.lastNativeActivityKind !== undefined) {
|
|
31843
|
+
if (row.lastNativeActivityKind === null)
|
|
31844
|
+
output.lastNativeActivityKind = null;
|
|
31845
|
+
else {
|
|
31846
|
+
const kind = enumValue(row.lastNativeActivityKind, NATIVE_ACTIVITY_KINDS);
|
|
31847
|
+
if (!kind)
|
|
31848
|
+
return null;
|
|
31849
|
+
output.lastNativeActivityKind = kind;
|
|
31850
|
+
}
|
|
31851
|
+
}
|
|
31852
|
+
if ("runtimePhase" in row && row.runtimePhase !== undefined) {
|
|
31853
|
+
const phase = enumValue(row.runtimePhase, RUNTIME_PHASES);
|
|
31854
|
+
if (!phase)
|
|
31855
|
+
return null;
|
|
31856
|
+
output.runtimePhase = phase;
|
|
31857
|
+
}
|
|
30710
31858
|
output.resetting = row.resetting;
|
|
30711
31859
|
if (!copyInteger(row, output, "resettingSince", { nullable: true }) || !copyInteger(row, output, "stoppingSince", { nullable: true }))
|
|
30712
31860
|
return null;
|
|
30713
31861
|
output.deliveryPhase = deliveryPhase;
|
|
30714
|
-
if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
|
|
31862
|
+
if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceNativeActivityMs", { optional: true }) || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
|
|
30715
31863
|
return null;
|
|
30716
31864
|
const metricKeys = [
|
|
30717
31865
|
"physicalOpenCount",
|
|
@@ -30842,7 +31990,7 @@ function projectStatusRow(value, targetAgentId) {
|
|
|
30842
31990
|
return output;
|
|
30843
31991
|
}
|
|
30844
31992
|
// src/diagnostics/bundle.ts
|
|
30845
|
-
import { createHash as
|
|
31993
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
30846
31994
|
import { createWriteStream, unlinkSync as unlinkSync5 } from "node:fs";
|
|
30847
31995
|
import { Readable, Transform } from "node:stream";
|
|
30848
31996
|
import { pipeline } from "node:stream/promises";
|
|
@@ -30992,7 +32140,7 @@ async function buildDiagnosticBundle(args) {
|
|
|
30992
32140
|
const { footer, total } = envelope();
|
|
30993
32141
|
if (total > maxUncompressed)
|
|
30994
32142
|
throw new BundleTooLargeError;
|
|
30995
|
-
const hash2 =
|
|
32143
|
+
const hash2 = createHash3("sha256");
|
|
30996
32144
|
let compressedBytes = 0;
|
|
30997
32145
|
const meter = new Transform({
|
|
30998
32146
|
transform(chunk2, _encoding, callback) {
|
|
@@ -31033,7 +32181,7 @@ async function buildDiagnosticBundle(args) {
|
|
|
31033
32181
|
};
|
|
31034
32182
|
}
|
|
31035
32183
|
// src/diagnostics/coordinator.ts
|
|
31036
|
-
import { createHash as
|
|
32184
|
+
import { createHash as createHash4, randomBytes as randomBytes5 } from "node:crypto";
|
|
31037
32185
|
import {
|
|
31038
32186
|
chmodSync as chmodSync2,
|
|
31039
32187
|
closeSync as closeSync4,
|
|
@@ -31189,7 +32337,7 @@ function createDiagnosticReportCoordinator(args) {
|
|
|
31189
32337
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
31190
32338
|
return null;
|
|
31191
32339
|
const bytes = readFileSync6(path11);
|
|
31192
|
-
return { sizeBytes: bytes.byteLength, sha256:
|
|
32340
|
+
return { sizeBytes: bytes.byteLength, sha256: createHash4("sha256").update(bytes).digest("hex") };
|
|
31193
32341
|
} catch {
|
|
31194
32342
|
return null;
|
|
31195
32343
|
}
|