@alook/daemon 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.js +1506 -300
- package/dist/index.js +1421 -290
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -17736,6 +17736,7 @@ __export(exports_community_schema, {
|
|
|
17736
17736
|
communityServerFolderItem: () => communityServerFolderItem,
|
|
17737
17737
|
communityServerFolder: () => communityServerFolder,
|
|
17738
17738
|
communityServer: () => communityServer,
|
|
17739
|
+
communityReadStateRevision: () => communityReadStateRevision,
|
|
17739
17740
|
communityReadState: () => communityReadState,
|
|
17740
17741
|
communityReaction: () => communityReaction,
|
|
17741
17742
|
communityPin: () => communityPin,
|
|
@@ -17900,6 +17901,10 @@ var communityReadState = sqliteTable("community_read_state", {
|
|
|
17900
17901
|
lastReadMessageId: text("last_read_message_id"),
|
|
17901
17902
|
lastReadSeq: integer2("last_read_seq").notNull().default(0)
|
|
17902
17903
|
}, (t) => [index("idx_read_state_user").on(t.userId)]);
|
|
17904
|
+
var communityReadStateRevision = sqliteTable("community_read_state_revision", {
|
|
17905
|
+
userId: text("user_id").primaryKey().references(() => user.id, { onDelete: "cascade" }),
|
|
17906
|
+
revision: integer2("revision").notNull().default(0)
|
|
17907
|
+
});
|
|
17903
17908
|
var communityReaction = sqliteTable("community_reaction", {
|
|
17904
17909
|
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
17905
17910
|
messageId: text("message_id").notNull().references(() => communityMessage.id, { onDelete: "cascade" }),
|
|
@@ -18493,6 +18498,24 @@ var communityUnreadBumpSchema = exports_external.strictObject({
|
|
|
18493
18498
|
railChannelId: string4.optional(),
|
|
18494
18499
|
isMention: exports_external.boolean().optional()
|
|
18495
18500
|
});
|
|
18501
|
+
var readStateEnvelopeFields = {
|
|
18502
|
+
revision: exports_external.number().int().positive(),
|
|
18503
|
+
inboxChanged: exports_external.literal(true)
|
|
18504
|
+
};
|
|
18505
|
+
var communityReadStateAdvancedSchema = exports_external.strictObject({
|
|
18506
|
+
type: exports_external.literal("community:read_state.advanced"),
|
|
18507
|
+
...readStateEnvelopeFields
|
|
18508
|
+
});
|
|
18509
|
+
var communityInboxChangedSchema = exports_external.strictObject({
|
|
18510
|
+
type: exports_external.literal("community:inbox.changed"),
|
|
18511
|
+
...readStateEnvelopeFields,
|
|
18512
|
+
reason: exports_external.enum([
|
|
18513
|
+
"read_all",
|
|
18514
|
+
"mention_read_all",
|
|
18515
|
+
"mention_dismiss",
|
|
18516
|
+
"notification_policy"
|
|
18517
|
+
])
|
|
18518
|
+
});
|
|
18496
18519
|
var communityPresenceUpdateSchema = exports_external.strictObject({
|
|
18497
18520
|
type: exports_external.literal("community:presence.update"),
|
|
18498
18521
|
userId: string4,
|
|
@@ -18588,6 +18611,8 @@ var CommunityWsEventDiscriminatedSchema = exports_external.discriminatedUnion("t
|
|
|
18588
18611
|
communityInviteCreateSchema,
|
|
18589
18612
|
communityMentionCreateSchema,
|
|
18590
18613
|
communityUnreadBumpSchema,
|
|
18614
|
+
communityReadStateAdvancedSchema,
|
|
18615
|
+
communityInboxChangedSchema,
|
|
18591
18616
|
communityPresenceUpdateSchema,
|
|
18592
18617
|
communityStatusUpdateSchema,
|
|
18593
18618
|
communityMachineCreatedSchema,
|
|
@@ -18632,6 +18657,8 @@ var WS_EVENTS = {
|
|
|
18632
18657
|
INVITE_CREATE: "community:invite.create",
|
|
18633
18658
|
MENTION_CREATE: "community:mention.create",
|
|
18634
18659
|
UNREAD_BUMP: "community:unread.bump",
|
|
18660
|
+
READ_STATE_ADVANCED: "community:read_state.advanced",
|
|
18661
|
+
INBOX_CHANGED: "community:inbox.changed",
|
|
18635
18662
|
PRESENCE_UPDATE: "community:presence.update",
|
|
18636
18663
|
STATUS_UPDATE: "community:status.update",
|
|
18637
18664
|
MACHINE_CREATED: "community:machine.created",
|
|
@@ -20288,19 +20315,23 @@ class ClaudeEventNormalizer {
|
|
|
20288
20315
|
const content = event?.message?.content;
|
|
20289
20316
|
if (!Array.isArray(content))
|
|
20290
20317
|
return;
|
|
20318
|
+
const completedText = [];
|
|
20291
20319
|
for (const block of content) {
|
|
20292
20320
|
if (block?.type === "thinking") {
|
|
20293
|
-
out.push({ kind: "
|
|
20321
|
+
out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
|
|
20294
20322
|
} else if (block?.type === "text") {
|
|
20295
20323
|
const text2 = block.text ?? "";
|
|
20296
20324
|
if (API_ERROR_RE.test(text2))
|
|
20297
20325
|
out.push({ kind: "error", message: text2 });
|
|
20298
20326
|
else
|
|
20299
|
-
|
|
20327
|
+
completedText.push(text2);
|
|
20300
20328
|
} else if (block?.type === "tool_use") {
|
|
20301
20329
|
out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
|
|
20302
20330
|
}
|
|
20303
20331
|
}
|
|
20332
|
+
if (completedText.length > 0) {
|
|
20333
|
+
out.push({ kind: "assistant_message_completed", text: completedText.join("") });
|
|
20334
|
+
}
|
|
20304
20335
|
}
|
|
20305
20336
|
handleUser(event, out) {
|
|
20306
20337
|
if (event.isReplay === true && typeof event.uuid === "string") {
|
|
@@ -20709,14 +20740,17 @@ class CodexEventNormalizer {
|
|
|
20709
20740
|
this.turnId = params.turn.id;
|
|
20710
20741
|
this.terminalTurn = null;
|
|
20711
20742
|
return [
|
|
20712
|
-
{
|
|
20713
|
-
|
|
20743
|
+
{
|
|
20744
|
+
kind: "turn_owner",
|
|
20745
|
+
receipt: this.turnReceipt(params.threadId, params.turn.id),
|
|
20746
|
+
nativeTurnId: params.turn.id
|
|
20747
|
+
}
|
|
20714
20748
|
];
|
|
20715
20749
|
case "item/reasoning/textDelta":
|
|
20716
20750
|
case "item/reasoning/summaryTextDelta":
|
|
20717
|
-
return [{ kind: "
|
|
20751
|
+
return [{ kind: "assistant_reasoning_delta", text: params?.delta ?? "" }];
|
|
20718
20752
|
case "item/agentMessage/delta":
|
|
20719
|
-
return [{ kind: "
|
|
20753
|
+
return [{ kind: "assistant_message_delta", text: params?.delta ?? "" }];
|
|
20720
20754
|
case "item/started":
|
|
20721
20755
|
return this.handleItemStarted(params);
|
|
20722
20756
|
case "item/completed":
|
|
@@ -20744,7 +20778,10 @@ class CodexEventNormalizer {
|
|
|
20744
20778
|
}
|
|
20745
20779
|
return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
20746
20780
|
case "error":
|
|
20747
|
-
|
|
20781
|
+
if (params?.willRetry === true) {
|
|
20782
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
|
|
20783
|
+
}
|
|
20784
|
+
return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
|
|
20748
20785
|
case "thread/tokenUsage/updated":
|
|
20749
20786
|
case "account/rateLimits/updated":
|
|
20750
20787
|
return mapCodexTelemetry(method, params);
|
|
@@ -20837,9 +20874,9 @@ class CodexEventNormalizer {
|
|
|
20837
20874
|
case "collabAgentToolCall":
|
|
20838
20875
|
return [{ kind: "tool_output", name: "collab_tool_call" }];
|
|
20839
20876
|
case "agentMessage":
|
|
20840
|
-
return [{ kind: "
|
|
20877
|
+
return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
|
|
20841
20878
|
case "reasoning":
|
|
20842
|
-
return [{ kind: "
|
|
20879
|
+
return [{ kind: "assistant_reasoning_completed", text: params?.item?.text ?? "" }];
|
|
20843
20880
|
default:
|
|
20844
20881
|
return [];
|
|
20845
20882
|
}
|
|
@@ -20872,7 +20909,13 @@ class CodexDriver {
|
|
|
20872
20909
|
lifetime: "session",
|
|
20873
20910
|
transport: { kind: "stdio_rpc", protocol: "codex.app-server.v1" },
|
|
20874
20911
|
wakeStart: "immediate",
|
|
20875
|
-
terminalOwnership: "transport_request"
|
|
20912
|
+
terminalOwnership: "transport_request",
|
|
20913
|
+
turnSilence: {
|
|
20914
|
+
nativeIdleTimeoutMs: 300000,
|
|
20915
|
+
daemonGraceMs: 60000,
|
|
20916
|
+
recoveryGraceMs: 60000,
|
|
20917
|
+
maxRecoveryExtensions: 1
|
|
20918
|
+
}
|
|
20876
20919
|
};
|
|
20877
20920
|
eventNormalizer = new CodexEventNormalizer;
|
|
20878
20921
|
requestId = 0;
|
|
@@ -21529,14 +21572,14 @@ class CursorAcpLane {
|
|
|
21529
21572
|
case "agent_message_chunk": {
|
|
21530
21573
|
const content = record2(update.content);
|
|
21531
21574
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
21532
|
-
this.events.emit("runtime_event", { kind: "
|
|
21575
|
+
this.events.emit("runtime_event", { kind: "assistant_message_delta", text: content.text });
|
|
21533
21576
|
}
|
|
21534
21577
|
return;
|
|
21535
21578
|
}
|
|
21536
21579
|
case "agent_thought_chunk": {
|
|
21537
21580
|
const content = record2(update.content);
|
|
21538
21581
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
21539
|
-
this.events.emit("runtime_event", { kind: "
|
|
21582
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_delta", text: content.text });
|
|
21540
21583
|
}
|
|
21541
21584
|
return;
|
|
21542
21585
|
}
|
|
@@ -22341,16 +22384,20 @@ class OpenCodeServiceLane {
|
|
|
22341
22384
|
}
|
|
22342
22385
|
switch (event.type) {
|
|
22343
22386
|
case "session.next.step.started":
|
|
22344
|
-
this.events.emit("runtime_event", {
|
|
22387
|
+
this.events.emit("runtime_event", {
|
|
22388
|
+
kind: "internal_progress",
|
|
22389
|
+
source: "opencode.service",
|
|
22390
|
+
itemType: "step_started"
|
|
22391
|
+
});
|
|
22345
22392
|
break;
|
|
22346
22393
|
case "session.next.text.ended":
|
|
22347
22394
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
22348
|
-
this.events.emit("runtime_event", { kind: "
|
|
22395
|
+
this.events.emit("runtime_event", { kind: "assistant_message_completed", text: data.text });
|
|
22349
22396
|
}
|
|
22350
22397
|
break;
|
|
22351
22398
|
case "session.next.reasoning.ended":
|
|
22352
22399
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
22353
|
-
this.events.emit("runtime_event", { kind: "
|
|
22400
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: data.text });
|
|
22354
22401
|
}
|
|
22355
22402
|
break;
|
|
22356
22403
|
case "session.next.tool.called":
|
|
@@ -23101,22 +23148,28 @@ function readPiSdkVersion() {
|
|
|
23101
23148
|
}
|
|
23102
23149
|
function mapPiSdkEvent(event, sessionId, state) {
|
|
23103
23150
|
if (event?.type === "message_update") {
|
|
23104
|
-
const d = event.
|
|
23151
|
+
const d = event.assistantMessageEvent ?? {};
|
|
23105
23152
|
switch (d.type) {
|
|
23106
23153
|
case "thinking_delta":
|
|
23107
|
-
return [{ kind: "
|
|
23154
|
+
return [{ kind: "assistant_reasoning_delta", text: d.delta ?? "" }];
|
|
23108
23155
|
case "text_delta":
|
|
23109
23156
|
state.sawTextDelta = true;
|
|
23110
|
-
return [{ kind: "
|
|
23111
|
-
case "text_end":
|
|
23112
|
-
|
|
23157
|
+
return [{ kind: "assistant_message_delta", text: d.delta ?? "" }];
|
|
23158
|
+
case "text_end": {
|
|
23159
|
+
state.sawTextDelta = false;
|
|
23160
|
+
return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
|
|
23161
|
+
}
|
|
23113
23162
|
case "error":
|
|
23114
|
-
return [{ kind: "error", message: d.
|
|
23163
|
+
return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
|
|
23115
23164
|
default:
|
|
23116
23165
|
return [];
|
|
23117
23166
|
}
|
|
23118
23167
|
}
|
|
23119
23168
|
switch (event?.type) {
|
|
23169
|
+
case "auto_retry_start":
|
|
23170
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
|
|
23171
|
+
case "auto_retry_end":
|
|
23172
|
+
return [{ kind: "runtime_recovery", stage: "recovered", source: "pi_auto_retry" }];
|
|
23120
23173
|
case "tool_execution_start":
|
|
23121
23174
|
return [{ kind: "tool_call", name: event.toolName ?? "unknown_tool", input: event.args ?? {} }];
|
|
23122
23175
|
case "tool_execution_end":
|
|
@@ -23281,6 +23334,13 @@ function assertAdapterCompatibility(registrationId, registeredCapabilities, adap
|
|
|
23281
23334
|
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
23335
|
throw new Error(`Adapter ${registrationId} has an invalid transport declaration`);
|
|
23283
23336
|
}
|
|
23337
|
+
if (execution.turnSilence !== undefined) {
|
|
23338
|
+
const silence = execution.turnSilence;
|
|
23339
|
+
const safeInteger = (value, minimum) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
|
|
23340
|
+
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)) {
|
|
23341
|
+
throw new Error(`Adapter ${registrationId} has an invalid turnSilence declaration`);
|
|
23342
|
+
}
|
|
23343
|
+
}
|
|
23284
23344
|
const capabilities = registeredCapabilities;
|
|
23285
23345
|
const declaredLifetime = capabilities?.sessionLifetime === "persistent" ? "session" : "turn";
|
|
23286
23346
|
if (execution.lifetime !== declaredLifetime) {
|
|
@@ -23370,6 +23430,12 @@ function capabilitiesFor(backend) {
|
|
|
23370
23430
|
return builtinRegistry.get(backend).capabilities;
|
|
23371
23431
|
}
|
|
23372
23432
|
|
|
23433
|
+
// agent-driver/dist/internal/adapter.js
|
|
23434
|
+
var DEFAULT_NATIVE_IDLE_TIMEOUT_MS = 300000;
|
|
23435
|
+
var DEFAULT_DAEMON_GRACE_MS = 60000;
|
|
23436
|
+
var DEFAULT_RECOVERY_GRACE_MS = 60000;
|
|
23437
|
+
var DEFAULT_MAX_RECOVERY_EXTENSIONS = 1;
|
|
23438
|
+
|
|
23373
23439
|
// agent-driver/dist/controller/event-queue.js
|
|
23374
23440
|
var MAX_BUFFERED_BYTES = 4194304;
|
|
23375
23441
|
|
|
@@ -23566,6 +23632,62 @@ function stableErrorCode(value, fallback) {
|
|
|
23566
23632
|
|
|
23567
23633
|
// agent-driver/dist/controller/logical-session.js
|
|
23568
23634
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
23635
|
+
var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
|
|
23636
|
+
var WORK_HEARTBEAT_MIN_INTERVAL_MS = 1000;
|
|
23637
|
+
function emptySemanticAssembler() {
|
|
23638
|
+
return { chunks: [], bytes: 0, truncated: false };
|
|
23639
|
+
}
|
|
23640
|
+
function utf8Prefix(text2, maxBytes) {
|
|
23641
|
+
if (maxBytes <= 0 || text2.length === 0)
|
|
23642
|
+
return "";
|
|
23643
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
23644
|
+
return text2;
|
|
23645
|
+
let low = 0;
|
|
23646
|
+
let high = text2.length;
|
|
23647
|
+
while (low < high) {
|
|
23648
|
+
const mid = Math.ceil((low + high) / 2);
|
|
23649
|
+
let end2 = mid;
|
|
23650
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
23651
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
23652
|
+
end2 -= 1;
|
|
23653
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
23654
|
+
low = mid;
|
|
23655
|
+
else
|
|
23656
|
+
high = mid - 1;
|
|
23657
|
+
}
|
|
23658
|
+
let end = low;
|
|
23659
|
+
const code = text2.charCodeAt(end - 1);
|
|
23660
|
+
if (code >= 55296 && code <= 56319)
|
|
23661
|
+
end -= 1;
|
|
23662
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
23663
|
+
end -= 1;
|
|
23664
|
+
return text2.slice(0, end);
|
|
23665
|
+
}
|
|
23666
|
+
function appendSemanticFragment(buffer, text2) {
|
|
23667
|
+
if (text2.length === 0 || buffer.truncated)
|
|
23668
|
+
return;
|
|
23669
|
+
const remaining = SEMANTIC_ASSEMBLER_MAX_BYTES - buffer.bytes;
|
|
23670
|
+
const bytes = Buffer.byteLength(text2, "utf8");
|
|
23671
|
+
if (bytes <= remaining) {
|
|
23672
|
+
buffer.chunks.push(text2);
|
|
23673
|
+
buffer.bytes += bytes;
|
|
23674
|
+
return;
|
|
23675
|
+
}
|
|
23676
|
+
const prefix = utf8Prefix(text2, remaining);
|
|
23677
|
+
if (prefix.length > 0) {
|
|
23678
|
+
buffer.chunks.push(prefix);
|
|
23679
|
+
buffer.bytes += Buffer.byteLength(prefix, "utf8");
|
|
23680
|
+
}
|
|
23681
|
+
buffer.truncated = true;
|
|
23682
|
+
}
|
|
23683
|
+
function finishSemanticAssembler(buffer) {
|
|
23684
|
+
return { text: buffer.chunks.join(""), truncated: buffer.truncated };
|
|
23685
|
+
}
|
|
23686
|
+
function boundedSemanticCompletion(text2) {
|
|
23687
|
+
const buffer = emptySemanticAssembler();
|
|
23688
|
+
appendSemanticFragment(buffer, text2);
|
|
23689
|
+
return finishSemanticAssembler(buffer);
|
|
23690
|
+
}
|
|
23569
23691
|
function driverError(category, code, message2, retryable = false) {
|
|
23570
23692
|
return { category, code, message: scrubDriverErrorMessage(message2), retryable };
|
|
23571
23693
|
}
|
|
@@ -23639,6 +23761,7 @@ class LogicalAgentSession {
|
|
|
23639
23761
|
resumeOutcome;
|
|
23640
23762
|
eventQueue;
|
|
23641
23763
|
behavior;
|
|
23764
|
+
turnSilence;
|
|
23642
23765
|
constructor(backend, config2, launch, adapter, capabilities2, host, prepared, hostReleaseTimeoutMs) {
|
|
23643
23766
|
this.backend = backend;
|
|
23644
23767
|
this.config = config2;
|
|
@@ -23649,6 +23772,18 @@ class LogicalAgentSession {
|
|
|
23649
23772
|
this.hostReleaseTimeoutMs = hostReleaseTimeoutMs;
|
|
23650
23773
|
this.capabilities = capabilities2;
|
|
23651
23774
|
this.behavior = this.capabilities;
|
|
23775
|
+
const declaredSilence = adapter.execution.turnSilence;
|
|
23776
|
+
const nativeIdleTimeoutMs = declaredSilence?.nativeIdleTimeoutMs ?? DEFAULT_NATIVE_IDLE_TIMEOUT_MS;
|
|
23777
|
+
const daemonGraceMs = declaredSilence?.daemonGraceMs ?? DEFAULT_DAEMON_GRACE_MS;
|
|
23778
|
+
const recoveryGraceMs = declaredSilence?.recoveryGraceMs ?? DEFAULT_RECOVERY_GRACE_MS;
|
|
23779
|
+
const maxRecoveryExtensions = declaredSilence?.maxRecoveryExtensions ?? DEFAULT_MAX_RECOVERY_EXTENSIONS;
|
|
23780
|
+
this.turnSilence = {
|
|
23781
|
+
nativeIdleTimeoutMs,
|
|
23782
|
+
daemonGraceMs,
|
|
23783
|
+
recoveryGraceMs,
|
|
23784
|
+
maxRecoveryExtensions,
|
|
23785
|
+
normalBudgetMs: nativeIdleTimeoutMs + daemonGraceMs
|
|
23786
|
+
};
|
|
23652
23787
|
this.resumeOutcome = launch.resumeSessionId ? "pending" : "not_requested";
|
|
23653
23788
|
this.sessionInstanceId = host.createId();
|
|
23654
23789
|
this.closed = new Promise((resolve3) => {
|
|
@@ -23753,6 +23888,7 @@ class LogicalAgentSession {
|
|
|
23753
23888
|
lastEventSequence: this.eventSequence,
|
|
23754
23889
|
diagnostics: {
|
|
23755
23890
|
deliveryPhase: this.deliveryPhase(),
|
|
23891
|
+
turnSilence: this.turnSilence,
|
|
23756
23892
|
metrics: {
|
|
23757
23893
|
physicalOpenCount: this.metricValue(this.physicalOpenCount),
|
|
23758
23894
|
turnCount: this.metricValue(this.turnCount),
|
|
@@ -23843,7 +23979,14 @@ class LogicalAgentSession {
|
|
|
23843
23979
|
const turnId = this.host.createId();
|
|
23844
23980
|
const commandIds = messages.map((message2) => message2.id);
|
|
23845
23981
|
const terminalOwner = this.adapter.beginTurn?.();
|
|
23846
|
-
this.activeTurn = {
|
|
23982
|
+
this.activeTurn = {
|
|
23983
|
+
turnId,
|
|
23984
|
+
commandIds: [...commandIds],
|
|
23985
|
+
...terminalOwner ? { terminalOwner } : {},
|
|
23986
|
+
pendingMessage: emptySemanticAssembler(),
|
|
23987
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
23988
|
+
lastWorkHeartbeatAt: null
|
|
23989
|
+
};
|
|
23847
23990
|
this.turnError = undefined;
|
|
23848
23991
|
this.interruptedTurnId = undefined;
|
|
23849
23992
|
this.processTurnEnded = false;
|
|
@@ -24041,14 +24184,36 @@ class LogicalAgentSession {
|
|
|
24041
24184
|
return;
|
|
24042
24185
|
}
|
|
24043
24186
|
this.activeTurn.terminalOwner = event.receipt;
|
|
24187
|
+
const nativeTurnId = event.nativeTurnId?.trim();
|
|
24188
|
+
if (nativeTurnId && nativeTurnId.length <= 512 && /^[A-Za-z0-9._:-]+$/.test(nativeTurnId)) {
|
|
24189
|
+
this.emit({
|
|
24190
|
+
type: "backend_turn_started",
|
|
24191
|
+
turnId: this.activeTurn.turnId,
|
|
24192
|
+
backendTurnId: nativeTurnId
|
|
24193
|
+
});
|
|
24194
|
+
}
|
|
24044
24195
|
return;
|
|
24045
|
-
case "
|
|
24046
|
-
|
|
24047
|
-
|
|
24196
|
+
case "assistant_reasoning_delta":
|
|
24197
|
+
case "assistant_message_delta":
|
|
24198
|
+
if (turnId && this.activeTurn?.turnId === turnId && event.text.length > 0) {
|
|
24199
|
+
appendSemanticFragment(event.kind === "assistant_message_delta" ? this.activeTurn.pendingMessage : this.activeTurn.pendingReasoning, event.text);
|
|
24200
|
+
const now = this.host.now();
|
|
24201
|
+
if (this.activeTurn.lastWorkHeartbeatAt === null || now - this.activeTurn.lastWorkHeartbeatAt >= WORK_HEARTBEAT_MIN_INTERVAL_MS) {
|
|
24202
|
+
this.activeTurn.lastWorkHeartbeatAt = now;
|
|
24203
|
+
this.emit({ type: "work_heartbeat", turnId });
|
|
24204
|
+
}
|
|
24205
|
+
}
|
|
24048
24206
|
return;
|
|
24049
|
-
case "
|
|
24050
|
-
|
|
24051
|
-
|
|
24207
|
+
case "assistant_reasoning_completed":
|
|
24208
|
+
case "assistant_message_completed":
|
|
24209
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
24210
|
+
const field = event.kind === "assistant_message_completed" ? "pendingMessage" : "pendingReasoning";
|
|
24211
|
+
this.activeTurn[field] = emptySemanticAssembler();
|
|
24212
|
+
if (event.text.length > 0) {
|
|
24213
|
+
const completed = boundedSemanticCompletion(event.text);
|
|
24214
|
+
this.emit({ type: event.kind, turnId, ...completed });
|
|
24215
|
+
}
|
|
24216
|
+
}
|
|
24052
24217
|
return;
|
|
24053
24218
|
case "tool_call":
|
|
24054
24219
|
this.outstandingToolUses += 1;
|
|
@@ -24091,9 +24256,19 @@ class LogicalAgentSession {
|
|
|
24091
24256
|
message: scrubDriverErrorMessage(event.message, "Runtime diagnostic")
|
|
24092
24257
|
});
|
|
24093
24258
|
return;
|
|
24259
|
+
case "runtime_recovery":
|
|
24260
|
+
this.emit({
|
|
24261
|
+
type: "recovery",
|
|
24262
|
+
turnId,
|
|
24263
|
+
stage: event.stage,
|
|
24264
|
+
source: event.source
|
|
24265
|
+
});
|
|
24266
|
+
return;
|
|
24094
24267
|
case "runtime_metric":
|
|
24095
|
-
if (event.name === "sse_reconnect" && event.increment === 1)
|
|
24268
|
+
if (event.name === "sse_reconnect" && event.increment === 1) {
|
|
24096
24269
|
this.sseReconnectCount += 1;
|
|
24270
|
+
this.emit({ type: "recovery", turnId, stage: "retrying", source: "transport_reconnect" });
|
|
24271
|
+
}
|
|
24097
24272
|
return;
|
|
24098
24273
|
case "telemetry": {
|
|
24099
24274
|
const details = jsonValue(event.attrs);
|
|
@@ -24116,6 +24291,18 @@ class LogicalAgentSession {
|
|
|
24116
24291
|
});
|
|
24117
24292
|
return;
|
|
24118
24293
|
case "turn_end":
|
|
24294
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
24295
|
+
const reasoning = finishSemanticAssembler(this.activeTurn.pendingReasoning);
|
|
24296
|
+
const message2 = finishSemanticAssembler(this.activeTurn.pendingMessage);
|
|
24297
|
+
this.activeTurn.pendingReasoning = emptySemanticAssembler();
|
|
24298
|
+
this.activeTurn.pendingMessage = emptySemanticAssembler();
|
|
24299
|
+
if (reasoning.text.length > 0) {
|
|
24300
|
+
this.emit({ type: "assistant_reasoning_completed", turnId, ...reasoning });
|
|
24301
|
+
}
|
|
24302
|
+
if (message2.text.length > 0) {
|
|
24303
|
+
this.emit({ type: "assistant_message_completed", turnId, ...message2 });
|
|
24304
|
+
}
|
|
24305
|
+
}
|
|
24119
24306
|
this.completeTurn(event.sessionId, physicalOwner, generation, event.turnOwner);
|
|
24120
24307
|
return;
|
|
24121
24308
|
}
|
|
@@ -24129,7 +24316,7 @@ class LogicalAgentSession {
|
|
|
24129
24316
|
reopenClosedLaneForWork(event, physicalOwner, generation) {
|
|
24130
24317
|
if (this.adapter.execution.lifetime === "turn")
|
|
24131
24318
|
return;
|
|
24132
|
-
const isRootWork = event.kind === "
|
|
24319
|
+
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
24320
|
if (!isRootWork)
|
|
24134
24321
|
return;
|
|
24135
24322
|
const tombstone = this.closedLaneTombstone;
|
|
@@ -24139,7 +24326,10 @@ class LogicalAgentSession {
|
|
|
24139
24326
|
this.activeTurn = {
|
|
24140
24327
|
turnId: tombstone.localTurnId,
|
|
24141
24328
|
commandIds: tombstone.commandIds,
|
|
24142
|
-
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {}
|
|
24329
|
+
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {},
|
|
24330
|
+
pendingMessage: emptySemanticAssembler(),
|
|
24331
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
24332
|
+
lastWorkHeartbeatAt: null
|
|
24143
24333
|
};
|
|
24144
24334
|
this.state = "working";
|
|
24145
24335
|
return tombstone.localTurnId;
|
|
@@ -26242,11 +26432,26 @@ function isActivelyWorking(agent2) {
|
|
|
26242
26432
|
return agent2.status === "running" && (leaseIsWorking(agent2.execution.lease) || agent2.pendingAdmissions.length > 0 || agent2.inbox.length > 0);
|
|
26243
26433
|
}
|
|
26244
26434
|
var DEFAULT_STALE_THRESHOLD_MS = 120000;
|
|
26245
|
-
var
|
|
26435
|
+
var DEFAULT_TURN_SILENCE_POLICY = {
|
|
26436
|
+
nativeIdleTimeoutMs: 300000,
|
|
26437
|
+
daemonGraceMs: 60000,
|
|
26438
|
+
recoveryGraceMs: 60000,
|
|
26439
|
+
maxRecoveryExtensions: 1,
|
|
26440
|
+
normalBudgetMs: 360000
|
|
26441
|
+
};
|
|
26442
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
26443
|
+
var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
|
|
26246
26444
|
var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
|
|
26247
26445
|
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 {
|
|
26446
|
+
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) {
|
|
26447
|
+
return {
|
|
26448
|
+
agents: {},
|
|
26449
|
+
staleThresholdMs,
|
|
26450
|
+
idleTimeoutMs,
|
|
26451
|
+
idleResetTimeoutMs,
|
|
26452
|
+
resetStuckThresholdMs,
|
|
26453
|
+
stoppingStuckThresholdMs
|
|
26454
|
+
};
|
|
26250
26455
|
}
|
|
26251
26456
|
function reduceManager(state, event) {
|
|
26252
26457
|
switch (event.type) {
|
|
@@ -26261,6 +26466,11 @@ function reduceManager(state, event) {
|
|
|
26261
26466
|
});
|
|
26262
26467
|
case "backend_session":
|
|
26263
26468
|
return mutate(state, event.agentId, (a) => {
|
|
26469
|
+
if (event.stalledBefore) {
|
|
26470
|
+
a.stalledSessionId = event.sessionId;
|
|
26471
|
+
} else if (a.stalledSessionId !== null && a.stalledSessionId !== event.sessionId) {
|
|
26472
|
+
a.stalledSessionId = null;
|
|
26473
|
+
}
|
|
26264
26474
|
a.sessionId = event.sessionId;
|
|
26265
26475
|
});
|
|
26266
26476
|
case "attach_session": {
|
|
@@ -26273,7 +26483,17 @@ function reduceManager(state, event) {
|
|
|
26273
26483
|
{
|
|
26274
26484
|
const a = agent2;
|
|
26275
26485
|
a.execution = { sessionInstanceId: event.sessionInstanceId, lease: { state: "none", lastTerminal: null } };
|
|
26486
|
+
a.turnSilence = event.turnSilence ?? {
|
|
26487
|
+
...DEFAULT_TURN_SILENCE_POLICY,
|
|
26488
|
+
nativeIdleTimeoutMs: state.staleThresholdMs,
|
|
26489
|
+
daemonGraceMs: 0,
|
|
26490
|
+
normalBudgetMs: state.staleThresholdMs
|
|
26491
|
+
};
|
|
26276
26492
|
a.lastProgressAt = event.nowMs;
|
|
26493
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26494
|
+
a.lastNativeActivityKind = null;
|
|
26495
|
+
a.runtimePhase = "admission";
|
|
26496
|
+
a.backendTurnId = null;
|
|
26277
26497
|
a.idleSince = null;
|
|
26278
26498
|
syncExecutionProjection(a);
|
|
26279
26499
|
}
|
|
@@ -26329,7 +26549,25 @@ function reduceManager(state, event) {
|
|
|
26329
26549
|
return { state, effects: [] };
|
|
26330
26550
|
return mutate(state, event.agentId, (a) => {
|
|
26331
26551
|
a.sessionId = null;
|
|
26552
|
+
a.stalledSessionId = null;
|
|
26553
|
+
a.idleSince = null;
|
|
26332
26554
|
});
|
|
26555
|
+
case "idle_reset_committed": {
|
|
26556
|
+
const existing = state.agents[event.agentId];
|
|
26557
|
+
if (!existing)
|
|
26558
|
+
return { state, effects: [] };
|
|
26559
|
+
const agent2 = clone2(existing);
|
|
26560
|
+
agent2.sessionId = null;
|
|
26561
|
+
agent2.stalledSessionId = null;
|
|
26562
|
+
agent2.idleSince = null;
|
|
26563
|
+
if (agent2.status !== "running")
|
|
26564
|
+
return commit(state, agent2, []);
|
|
26565
|
+
agent2.status = "stopping";
|
|
26566
|
+
agent2.stoppingSince = event.nowMs;
|
|
26567
|
+
return commit(state, agent2, [
|
|
26568
|
+
{ type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
|
|
26569
|
+
]);
|
|
26570
|
+
}
|
|
26333
26571
|
case "begin_reset":
|
|
26334
26572
|
if (!state.agents[event.agentId])
|
|
26335
26573
|
return { state, effects: [] };
|
|
@@ -26367,8 +26605,18 @@ function reduceManager(state, event) {
|
|
|
26367
26605
|
return;
|
|
26368
26606
|
const startedCommands = new Set(event.commandIds);
|
|
26369
26607
|
a.pendingAdmissions = a.pendingAdmissions.filter((entry) => !startedCommands.has(entry.commandId));
|
|
26370
|
-
a.execution.lease = {
|
|
26608
|
+
a.execution.lease = {
|
|
26609
|
+
state: "active",
|
|
26610
|
+
identity,
|
|
26611
|
+
lastWorkAt: event.nowMs,
|
|
26612
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26613
|
+
recoveryExtensionsUsed: 0
|
|
26614
|
+
};
|
|
26371
26615
|
a.lastProgressAt = event.nowMs;
|
|
26616
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26617
|
+
a.lastNativeActivityKind = "turn_started";
|
|
26618
|
+
a.runtimePhase = "inference";
|
|
26619
|
+
a.backendTurnId = null;
|
|
26372
26620
|
a.idleSince = null;
|
|
26373
26621
|
syncExecutionProjection(a);
|
|
26374
26622
|
});
|
|
@@ -26389,7 +26637,12 @@ function reduceManager(state, event) {
|
|
|
26389
26637
|
const lease = a.execution.lease;
|
|
26390
26638
|
const identity = identityOf(event);
|
|
26391
26639
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
26392
|
-
a.execution.lease = {
|
|
26640
|
+
a.execution.lease = {
|
|
26641
|
+
...lease,
|
|
26642
|
+
lastWorkAt: event.nowMs,
|
|
26643
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26644
|
+
recoveryExtensionsUsed: 0
|
|
26645
|
+
};
|
|
26393
26646
|
} else {
|
|
26394
26647
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
26395
26648
|
if (!terminal || !sameIdentity(terminal.identity, identity))
|
|
@@ -26398,6 +26651,8 @@ function reduceManager(state, event) {
|
|
|
26398
26651
|
state: "suspect_active",
|
|
26399
26652
|
identity,
|
|
26400
26653
|
lastWorkAt: event.nowMs,
|
|
26654
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26655
|
+
recoveryExtensionsUsed: 0,
|
|
26401
26656
|
reason: "work_after_terminal"
|
|
26402
26657
|
};
|
|
26403
26658
|
}
|
|
@@ -26411,7 +26666,7 @@ function reduceManager(state, event) {
|
|
|
26411
26666
|
case "turn_tool_finished":
|
|
26412
26667
|
return onTurnToolLifecycle(state, event, "finished");
|
|
26413
26668
|
case "turn_completed":
|
|
26414
|
-
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId);
|
|
26669
|
+
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId, event.endReason);
|
|
26415
26670
|
case "session_closed":
|
|
26416
26671
|
if (state.agents[event.agentId]?.execution.sessionInstanceId !== event.sessionInstanceId) {
|
|
26417
26672
|
return { state, effects: [] };
|
|
@@ -26421,7 +26676,6 @@ function reduceManager(state, event) {
|
|
|
26421
26676
|
const closing = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId === event.sessionInstanceId);
|
|
26422
26677
|
agent2.pendingAdmissions = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId !== event.sessionInstanceId);
|
|
26423
26678
|
agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
26424
|
-
agent2.idleSince = null;
|
|
26425
26679
|
syncExecutionProjection(agent2);
|
|
26426
26680
|
return commit(state, agent2, recoveryEffects(agent2, closing));
|
|
26427
26681
|
}
|
|
@@ -26429,8 +26683,57 @@ function reduceManager(state, event) {
|
|
|
26429
26683
|
return onExit(state, event.agentId);
|
|
26430
26684
|
case "tick":
|
|
26431
26685
|
return onTick(state, event.nowMs);
|
|
26432
|
-
case "runtime_signal":
|
|
26433
|
-
|
|
26686
|
+
case "runtime_signal": {
|
|
26687
|
+
const existing = state.agents[event.agentId];
|
|
26688
|
+
if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
|
|
26689
|
+
return { state, effects: [] };
|
|
26690
|
+
const lease = existing.execution.lease;
|
|
26691
|
+
const identity = { sessionInstanceId: event.sessionInstanceId, turnId: event.turnId };
|
|
26692
|
+
if (lease.state !== "active" && lease.state !== "suspect_active" || !sameIdentity(lease.identity, identity)) {
|
|
26693
|
+
return { state, effects: [] };
|
|
26694
|
+
}
|
|
26695
|
+
return mutate(state, event.agentId, (a) => {
|
|
26696
|
+
const active = a.execution.lease;
|
|
26697
|
+
if (active.state !== "active" && active.state !== "suspect_active" || !sameIdentity(active.identity, identity))
|
|
26698
|
+
return;
|
|
26699
|
+
if (event.kind === "recovery" && event.recoveryStage !== "recovered") {
|
|
26700
|
+
if (active.recoveryExtensionsUsed < a.turnSilence.maxRecoveryExtensions) {
|
|
26701
|
+
a.execution.lease = {
|
|
26702
|
+
...active,
|
|
26703
|
+
nativeDeadlineAt: Math.max(active.nativeDeadlineAt, event.nowMs + a.turnSilence.recoveryGraceMs),
|
|
26704
|
+
recoveryExtensionsUsed: active.recoveryExtensionsUsed + 1
|
|
26705
|
+
};
|
|
26706
|
+
}
|
|
26707
|
+
} else {
|
|
26708
|
+
a.execution.lease = {
|
|
26709
|
+
...active,
|
|
26710
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs
|
|
26711
|
+
};
|
|
26712
|
+
}
|
|
26713
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26714
|
+
a.lastNativeActivityKind = event.kind;
|
|
26715
|
+
a.runtimePhase = event.phase;
|
|
26716
|
+
if (event.backendTurnId)
|
|
26717
|
+
a.backendTurnId = event.backendTurnId;
|
|
26718
|
+
});
|
|
26719
|
+
}
|
|
26720
|
+
case "stall_control_failed":
|
|
26721
|
+
return mutate(state, event.agentId, (a) => {
|
|
26722
|
+
if (event.transition === "clear") {
|
|
26723
|
+
if (a.sessionId === event.sessionId)
|
|
26724
|
+
a.stalledSessionId = event.sessionId;
|
|
26725
|
+
return;
|
|
26726
|
+
}
|
|
26727
|
+
if (a.status !== "stopping")
|
|
26728
|
+
return;
|
|
26729
|
+
const lease = a.execution.lease;
|
|
26730
|
+
if (lease.state !== "active" && lease.state !== "suspect_active")
|
|
26731
|
+
return;
|
|
26732
|
+
a.status = "running";
|
|
26733
|
+
a.stoppingSince = null;
|
|
26734
|
+
a.sessionId = event.sessionId;
|
|
26735
|
+
a.stalledSessionId = event.transition === "fence" ? event.sessionId : null;
|
|
26736
|
+
});
|
|
26434
26737
|
case "delivery_rejected":
|
|
26435
26738
|
return mutate(state, event.agentId, (a) => {
|
|
26436
26739
|
if (!a.inbox.some((message2) => message2.id === event.message.id)) {
|
|
@@ -26468,7 +26771,7 @@ function onWake(state, agentId, message2) {
|
|
|
26468
26771
|
}
|
|
26469
26772
|
return commit(state, agent2, []);
|
|
26470
26773
|
}
|
|
26471
|
-
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
26774
|
+
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endReason) {
|
|
26472
26775
|
const existing = state.agents[agentId];
|
|
26473
26776
|
if (!existing)
|
|
26474
26777
|
return { state, effects: [] };
|
|
@@ -26484,13 +26787,23 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
|
26484
26787
|
const lastTerminal = { identity, at: nowMs };
|
|
26485
26788
|
agent2.execution.lease = { state: "none", lastTerminal };
|
|
26486
26789
|
agent2.lastProgressAt = nowMs;
|
|
26790
|
+
agent2.lastNativeActivityAt = nowMs;
|
|
26791
|
+
agent2.lastNativeActivityKind = "turn_end";
|
|
26792
|
+
agent2.runtimePhase = "terminal";
|
|
26793
|
+
const clearedStallSessionId = endReason === undefined ? agent2.stalledSessionId : null;
|
|
26794
|
+
if (clearedStallSessionId !== null)
|
|
26795
|
+
agent2.stalledSessionId = null;
|
|
26487
26796
|
syncExecutionProjection(agent2);
|
|
26797
|
+
const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
|
|
26488
26798
|
if (agent2.inbox.length > 0) {
|
|
26489
26799
|
const messages = drainInbox(agent2);
|
|
26490
|
-
return commit(state, agent2,
|
|
26800
|
+
return commit(state, agent2, [
|
|
26801
|
+
...clearEffects,
|
|
26802
|
+
...messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" }))
|
|
26803
|
+
]);
|
|
26491
26804
|
}
|
|
26492
26805
|
agent2.idleSince = nowMs;
|
|
26493
|
-
return commit(state, agent2,
|
|
26806
|
+
return commit(state, agent2, clearEffects);
|
|
26494
26807
|
}
|
|
26495
26808
|
function onTurnToolLifecycle(state, event, lifecycle) {
|
|
26496
26809
|
const existing = state.agents[event.agentId];
|
|
@@ -26512,11 +26825,22 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
26512
26825
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
26513
26826
|
const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
|
|
26514
26827
|
if (outstandingToolUses > 0) {
|
|
26515
|
-
agent2.execution.lease = {
|
|
26828
|
+
agent2.execution.lease = {
|
|
26829
|
+
...lease,
|
|
26830
|
+
lastWorkAt: event.nowMs,
|
|
26831
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26832
|
+
recoveryExtensionsUsed: 0,
|
|
26833
|
+
outstandingToolUses
|
|
26834
|
+
};
|
|
26516
26835
|
} else {
|
|
26517
26836
|
const unblockedLease = { ...lease };
|
|
26518
26837
|
delete unblockedLease.outstandingToolUses;
|
|
26519
|
-
agent2.execution.lease = {
|
|
26838
|
+
agent2.execution.lease = {
|
|
26839
|
+
...unblockedLease,
|
|
26840
|
+
lastWorkAt: event.nowMs,
|
|
26841
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26842
|
+
recoveryExtensionsUsed: 0
|
|
26843
|
+
};
|
|
26520
26844
|
}
|
|
26521
26845
|
} else {
|
|
26522
26846
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
@@ -26526,11 +26850,16 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
26526
26850
|
state: "suspect_active",
|
|
26527
26851
|
identity,
|
|
26528
26852
|
lastWorkAt: event.nowMs,
|
|
26853
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26854
|
+
recoveryExtensionsUsed: 0,
|
|
26529
26855
|
outstandingToolUses: 1,
|
|
26530
26856
|
reason: "work_after_terminal"
|
|
26531
26857
|
};
|
|
26532
26858
|
}
|
|
26533
26859
|
agent2.lastProgressAt = event.nowMs;
|
|
26860
|
+
agent2.lastNativeActivityAt = event.nowMs;
|
|
26861
|
+
agent2.lastNativeActivityKind = lifecycle === "started" ? "tool_call" : "tool_output";
|
|
26862
|
+
agent2.runtimePhase = lifecycle === "started" ? "tool" : "inference";
|
|
26534
26863
|
agent2.idleSince = null;
|
|
26535
26864
|
syncExecutionProjection(agent2);
|
|
26536
26865
|
});
|
|
@@ -26543,6 +26872,8 @@ function onExit(state, agentId) {
|
|
|
26543
26872
|
const effects = recoveryEffects(agent2, agent2.pendingAdmissions);
|
|
26544
26873
|
agent2.pendingAdmissions = [];
|
|
26545
26874
|
agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
26875
|
+
agent2.runtimePhase = "idle";
|
|
26876
|
+
agent2.backendTurnId = null;
|
|
26546
26877
|
agent2.stoppingSince = null;
|
|
26547
26878
|
syncExecutionProjection(agent2);
|
|
26548
26879
|
if (agent2.inbox.length > 0) {
|
|
@@ -26562,10 +26893,19 @@ function onTick(state, nowMs) {
|
|
|
26562
26893
|
for (const id of Object.keys(agents)) {
|
|
26563
26894
|
const a = agents[id];
|
|
26564
26895
|
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 >=
|
|
26896
|
+
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
26897
|
if (stalled) {
|
|
26567
|
-
|
|
26568
|
-
|
|
26898
|
+
const repeatedSessionStall = a.sessionId !== null && a.stalledSessionId === a.sessionId;
|
|
26899
|
+
const forgetSessionId = repeatedSessionStall ? a.sessionId : undefined;
|
|
26900
|
+
agents[id] = {
|
|
26901
|
+
...a,
|
|
26902
|
+
status: "stopping",
|
|
26903
|
+
sessionId: repeatedSessionStall ? null : a.sessionId,
|
|
26904
|
+
stalledSessionId: repeatedSessionStall ? null : a.sessionId,
|
|
26905
|
+
idleSince: null,
|
|
26906
|
+
stoppingSince: nowMs
|
|
26907
|
+
};
|
|
26908
|
+
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
26909
|
continue;
|
|
26570
26910
|
}
|
|
26571
26911
|
const expiredAdmission = a.status === "running" && a.pendingAdmissions.filter((entry) => !entry.driverAcknowledged && nowMs - entry.admittedAt >= state.staleThresholdMs);
|
|
@@ -26597,9 +26937,14 @@ function onTick(state, nowMs) {
|
|
|
26597
26937
|
effects.push({ type: "force_exit", agentId: id, reason: "stopping_stuck" });
|
|
26598
26938
|
continue;
|
|
26599
26939
|
}
|
|
26940
|
+
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);
|
|
26941
|
+
if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
|
|
26942
|
+
effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
|
|
26943
|
+
continue;
|
|
26944
|
+
}
|
|
26600
26945
|
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
26946
|
if (idleEligible && a.idleSince !== null && nowMs - a.idleSince >= state.idleTimeoutMs) {
|
|
26602
|
-
agents[id] = { ...a, status: "stopping",
|
|
26947
|
+
agents[id] = { ...a, status: "stopping", stoppingSince: nowMs };
|
|
26603
26948
|
effects.push({ type: "stop", agentId: id, reason: "idle_timeout" });
|
|
26604
26949
|
}
|
|
26605
26950
|
}
|
|
@@ -26611,11 +26956,17 @@ function freshAgent(agentId) {
|
|
|
26611
26956
|
status: "idle",
|
|
26612
26957
|
inbox: [],
|
|
26613
26958
|
sessionId: null,
|
|
26959
|
+
stalledSessionId: null,
|
|
26614
26960
|
execution: { sessionInstanceId: null, lease: { state: "detached" } },
|
|
26615
26961
|
pendingAdmissions: [],
|
|
26616
26962
|
turnId: null,
|
|
26617
26963
|
turnActive: false,
|
|
26618
26964
|
lastProgressAt: 0,
|
|
26965
|
+
lastNativeActivityAt: 0,
|
|
26966
|
+
lastNativeActivityKind: null,
|
|
26967
|
+
runtimePhase: "idle",
|
|
26968
|
+
backendTurnId: null,
|
|
26969
|
+
turnSilence: DEFAULT_TURN_SILENCE_POLICY,
|
|
26619
26970
|
lastDeliverAt: null,
|
|
26620
26971
|
idleSince: null,
|
|
26621
26972
|
stoppingSince: null,
|
|
@@ -26737,9 +27088,6 @@ function identitySection(config2) {
|
|
|
26737
27088
|
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
27089
|
}
|
|
26739
27090
|
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
27091
|
return parts.join(`
|
|
26744
27092
|
`);
|
|
26745
27093
|
}
|
|
@@ -26976,7 +27324,17 @@ function chaosAwarenessSection() {
|
|
|
26976
27324
|
].join(`
|
|
26977
27325
|
`);
|
|
26978
27326
|
}
|
|
26979
|
-
function workspaceMemorySection() {
|
|
27327
|
+
function workspaceMemorySection(config2) {
|
|
27328
|
+
const roleSection = config2.description ? [
|
|
27329
|
+
"",
|
|
27330
|
+
"### Your bio",
|
|
27331
|
+
"",
|
|
27332
|
+
config2.description,
|
|
27333
|
+
"",
|
|
27334
|
+
"Your bio is the public description of your role that other people and agents see on " + "your Alook profile.",
|
|
27335
|
+
"",
|
|
27336
|
+
`You can change your own role and bio description with \`${CLI} setting profile ` + `--set-bio <text>\`.`
|
|
27337
|
+
] : [];
|
|
26980
27338
|
return [
|
|
26981
27339
|
"## Self-awareness",
|
|
26982
27340
|
"",
|
|
@@ -26985,6 +27343,7 @@ function workspaceMemorySection() {
|
|
|
26985
27343
|
"**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
|
|
26986
27344
|
"",
|
|
26987
27345
|
"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.",
|
|
27346
|
+
...roleSection,
|
|
26988
27347
|
"",
|
|
26989
27348
|
"### Napping",
|
|
26990
27349
|
"",
|
|
@@ -27026,7 +27385,7 @@ function buildCliSystemPrompt(config2) {
|
|
|
27026
27385
|
criticalRulesSection(),
|
|
27027
27386
|
executionModelSection(),
|
|
27028
27387
|
chaosAwarenessSection(),
|
|
27029
|
-
workspaceMemorySection(),
|
|
27388
|
+
workspaceMemorySection(config2),
|
|
27030
27389
|
utilsSection()
|
|
27031
27390
|
];
|
|
27032
27391
|
return sections.filter((s) => s && s.length > 0).join(`
|
|
@@ -27294,8 +27653,8 @@ class AgentProcessManager {
|
|
|
27294
27653
|
resumeSessions = new Map;
|
|
27295
27654
|
launchIds = new Map;
|
|
27296
27655
|
liveSessions = new Map;
|
|
27297
|
-
thinkingBuffers = new Map;
|
|
27298
27656
|
activeSpawnState = new Map;
|
|
27657
|
+
publishedAgentActivity = new Map;
|
|
27299
27658
|
traceProcessNonce = randomUUID5();
|
|
27300
27659
|
nextSpawnOrdinal = 1;
|
|
27301
27660
|
nextDaemonTurnOrdinal = 1;
|
|
@@ -27309,7 +27668,8 @@ class AgentProcessManager {
|
|
|
27309
27668
|
this.opts = {
|
|
27310
27669
|
tickIntervalMs: 5000,
|
|
27311
27670
|
staleThresholdMs: 120000,
|
|
27312
|
-
idleTimeoutMs:
|
|
27671
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
27672
|
+
idleResetTimeoutMs: DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
27313
27673
|
resetStuckThresholdMs: 120000,
|
|
27314
27674
|
stoppingStuckThresholdMs: DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
27315
27675
|
handshakeTimeoutMs: 60000,
|
|
@@ -27318,7 +27678,7 @@ class AgentProcessManager {
|
|
|
27318
27678
|
};
|
|
27319
27679
|
this.now = opts.now ?? (() => Date.now());
|
|
27320
27680
|
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);
|
|
27681
|
+
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
|
|
27322
27682
|
}
|
|
27323
27683
|
register(agentId, launch) {
|
|
27324
27684
|
if (launch?.runtimeConfig)
|
|
@@ -27337,11 +27697,19 @@ class AgentProcessManager {
|
|
|
27337
27697
|
const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
|
|
27338
27698
|
return effects.length > 0;
|
|
27339
27699
|
}
|
|
27340
|
-
forgetSession(agentId, barrierType = "reset_session") {
|
|
27700
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
27701
|
+
if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId))
|
|
27702
|
+
return false;
|
|
27703
|
+
this.dispatch({ type: "reset_session", agentId });
|
|
27704
|
+
return true;
|
|
27705
|
+
}
|
|
27706
|
+
forgetSessionSources(agentId, barrierType, forgottenSessionId) {
|
|
27707
|
+
const persisted = this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
|
|
27708
|
+
if (persisted === false)
|
|
27709
|
+
return false;
|
|
27341
27710
|
this.resumeSessions.delete(agentId);
|
|
27342
27711
|
this.liveSessions.delete(agentId);
|
|
27343
|
-
|
|
27344
|
-
this.opts.timeline?.forgetSession(agentId, barrierType);
|
|
27712
|
+
return true;
|
|
27345
27713
|
}
|
|
27346
27714
|
enqueueRewake(agentId, message2) {
|
|
27347
27715
|
this.dispatch({ type: "rewake_after_reset", agentId, message: message2 });
|
|
@@ -27364,9 +27732,14 @@ class AgentProcessManager {
|
|
|
27364
27732
|
});
|
|
27365
27733
|
}
|
|
27366
27734
|
async restartAgent(agentId, opts) {
|
|
27735
|
+
if (opts.forgetSession && !this.forgetSession(agentId, opts.barrierType ?? "reset_session")) {
|
|
27736
|
+
this.log.error("resume control transition failed; reset aborted", { agentId, barrierType: opts.barrierType });
|
|
27737
|
+
this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
|
|
27738
|
+
throw new Error("Reset aborted because resume control could not be persisted");
|
|
27739
|
+
}
|
|
27367
27740
|
this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
|
|
27368
|
-
if (opts.forgetSession)
|
|
27369
|
-
this.
|
|
27741
|
+
if (!opts.forgetSession)
|
|
27742
|
+
this.opts.timeline?.fenceSession(agentId);
|
|
27370
27743
|
this.abortCurrentTurn(agentId, opts.abortCause);
|
|
27371
27744
|
this.markResetting(agentId);
|
|
27372
27745
|
const status = this.state.agents[agentId]?.status;
|
|
@@ -27435,12 +27808,19 @@ class AgentProcessManager {
|
|
|
27435
27808
|
snapshot() {
|
|
27436
27809
|
return this.state;
|
|
27437
27810
|
}
|
|
27811
|
+
runningAgentCount() {
|
|
27812
|
+
return this.sessions.size;
|
|
27813
|
+
}
|
|
27438
27814
|
auditContext(agentId) {
|
|
27439
27815
|
return {
|
|
27440
27816
|
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
27441
27817
|
launchId: this.launchIds.get(agentId) ?? null
|
|
27442
27818
|
};
|
|
27443
27819
|
}
|
|
27820
|
+
timelineTurnOwner(agentId) {
|
|
27821
|
+
const owner = this.traceOwnerFor(agentId);
|
|
27822
|
+
return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
|
|
27823
|
+
}
|
|
27444
27824
|
liveSessionReports() {
|
|
27445
27825
|
return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
|
|
27446
27826
|
agentId,
|
|
@@ -27510,6 +27890,10 @@ class AgentProcessManager {
|
|
|
27510
27890
|
if (!expectedSpan || owner.activeSpan !== expectedSpan)
|
|
27511
27891
|
return false;
|
|
27512
27892
|
owner.activeSpan = null;
|
|
27893
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
27894
|
+
owner.timelineTurnOwner = null;
|
|
27895
|
+
if (timelineTurnOwner)
|
|
27896
|
+
this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
|
|
27513
27897
|
const nowMs = this.now();
|
|
27514
27898
|
const base = {
|
|
27515
27899
|
recordKind: "turn_span",
|
|
@@ -27567,6 +27951,13 @@ class AgentProcessManager {
|
|
|
27567
27951
|
inbox: a.inbox.length,
|
|
27568
27952
|
lastDeliverAt: a.lastDeliverAt,
|
|
27569
27953
|
lastProgressAt: a.lastProgressAt,
|
|
27954
|
+
lastNativeActivityAt: a.lastNativeActivityAt,
|
|
27955
|
+
lastNativeActivityKind: a.lastNativeActivityKind,
|
|
27956
|
+
runtimePhase: a.runtimePhase,
|
|
27957
|
+
backendTurnId: a.backendTurnId,
|
|
27958
|
+
turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
|
|
27959
|
+
nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
|
|
27960
|
+
recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
|
|
27570
27961
|
idleSince: a.idleSince,
|
|
27571
27962
|
resetting: a.resetting,
|
|
27572
27963
|
resettingSince: a.resettingSince,
|
|
@@ -27577,6 +27968,7 @@ class AgentProcessManager {
|
|
|
27577
27968
|
timeIso: new Date(nowMs).toISOString(),
|
|
27578
27969
|
...activeSpan ? activeSpan : {},
|
|
27579
27970
|
sinceProgressMs: nowMs - a.lastProgressAt,
|
|
27971
|
+
sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
|
|
27580
27972
|
sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
|
|
27581
27973
|
sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
|
|
27582
27974
|
...event.type === "turn_completed" && event.endReason === "errored" ? {
|
|
@@ -27611,11 +28003,18 @@ class AgentProcessManager {
|
|
|
27611
28003
|
terminationCause: normalizeTerminationCause(event.terminationCause)
|
|
27612
28004
|
} : { event: "turn_end", outcome: "clean" });
|
|
27613
28005
|
}
|
|
27614
|
-
if (this.opts.onAgentActivity && event.type !== "
|
|
28006
|
+
if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
|
|
27615
28007
|
const after = this.deriveActivitySnapshot(state);
|
|
27616
28008
|
for (const [agentId, activity] of Object.entries(after)) {
|
|
27617
|
-
if (agentId in before
|
|
28009
|
+
if (!(agentId in before)) {
|
|
28010
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
28011
|
+
continue;
|
|
28012
|
+
}
|
|
28013
|
+
const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
|
|
28014
|
+
const publishable = event.type !== "spawned" || activity === "running";
|
|
28015
|
+
if (publishable && previouslyPublished !== activity) {
|
|
27618
28016
|
this.opts.onAgentActivity({ agentId, state: activity });
|
|
28017
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
27619
28018
|
}
|
|
27620
28019
|
}
|
|
27621
28020
|
}
|
|
@@ -27780,6 +28179,47 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27780
28179
|
case "terminate_stalled": {
|
|
27781
28180
|
const session2 = this.sessions.get(effect.agentId);
|
|
27782
28181
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
28182
|
+
const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
|
|
28183
|
+
if (effect.type === "terminate_stalled" && effect.recordSessionId) {
|
|
28184
|
+
const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
|
|
28185
|
+
if (persisted === false) {
|
|
28186
|
+
this.dispatch({
|
|
28187
|
+
type: "stall_control_failed",
|
|
28188
|
+
agentId: effect.agentId,
|
|
28189
|
+
sessionId: effect.recordSessionId,
|
|
28190
|
+
transition: "attempt"
|
|
28191
|
+
});
|
|
28192
|
+
this.log.error("stall recovery attempt was not persisted; termination deferred", {
|
|
28193
|
+
agentId: effect.agentId,
|
|
28194
|
+
sessionId: effect.recordSessionId
|
|
28195
|
+
});
|
|
28196
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
|
|
28197
|
+
break;
|
|
28198
|
+
}
|
|
28199
|
+
}
|
|
28200
|
+
if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
|
|
28201
|
+
const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
|
|
28202
|
+
if (!persisted) {
|
|
28203
|
+
this.dispatch({
|
|
28204
|
+
type: "stall_control_failed",
|
|
28205
|
+
agentId: effect.agentId,
|
|
28206
|
+
sessionId: effect.forgetSessionId,
|
|
28207
|
+
transition: "fence"
|
|
28208
|
+
});
|
|
28209
|
+
this.log.error("repeated-session fence was not persisted; termination deferred", {
|
|
28210
|
+
agentId: effect.agentId,
|
|
28211
|
+
sessionId: effect.forgetSessionId
|
|
28212
|
+
});
|
|
28213
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
|
|
28214
|
+
break;
|
|
28215
|
+
}
|
|
28216
|
+
if (spawnState)
|
|
28217
|
+
spawnState.discardEvents = true;
|
|
28218
|
+
this.log.warn("repeatedly stalled backend session fenced", {
|
|
28219
|
+
agentId: effect.agentId,
|
|
28220
|
+
sessionId: effect.forgetSessionId
|
|
28221
|
+
});
|
|
28222
|
+
}
|
|
27783
28223
|
if (effect.type === "terminate_stalled" && spawnState) {
|
|
27784
28224
|
this.closeTurn(spawnState, spawnState.activeSpan, {
|
|
27785
28225
|
event: "turn_abort",
|
|
@@ -27798,10 +28238,27 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27798
28238
|
if (spawnState) {
|
|
27799
28239
|
spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
|
|
27800
28240
|
}
|
|
27801
|
-
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
|
|
28241
|
+
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
|
|
27802
28242
|
this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
|
|
27803
28243
|
break;
|
|
27804
28244
|
}
|
|
28245
|
+
case "clear_stall_recovery": {
|
|
28246
|
+
const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
|
|
28247
|
+
if (persisted === false) {
|
|
28248
|
+
this.dispatch({
|
|
28249
|
+
type: "stall_control_failed",
|
|
28250
|
+
agentId: effect.agentId,
|
|
28251
|
+
sessionId: effect.sessionId,
|
|
28252
|
+
transition: "clear"
|
|
28253
|
+
});
|
|
28254
|
+
this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
|
|
28255
|
+
agentId: effect.agentId,
|
|
28256
|
+
sessionId: effect.sessionId
|
|
28257
|
+
});
|
|
28258
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
|
|
28259
|
+
}
|
|
28260
|
+
break;
|
|
28261
|
+
}
|
|
27805
28262
|
case "expire_admission": {
|
|
27806
28263
|
const owner = this.activeSpawnState.get(effect.agentId);
|
|
27807
28264
|
if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
|
|
@@ -27824,6 +28281,26 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27824
28281
|
mode: effect.mode
|
|
27825
28282
|
});
|
|
27826
28283
|
break;
|
|
28284
|
+
case "reset_idle_session": {
|
|
28285
|
+
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
28286
|
+
const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
|
|
28287
|
+
if (!persisted) {
|
|
28288
|
+
this.log.error("idle session reset barrier was not persisted; reset deferred", {
|
|
28289
|
+
agentId: effect.agentId,
|
|
28290
|
+
sessionId: effect.sessionId
|
|
28291
|
+
});
|
|
28292
|
+
this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
|
|
28293
|
+
break;
|
|
28294
|
+
}
|
|
28295
|
+
if (spawnState)
|
|
28296
|
+
spawnState.discardEvents = true;
|
|
28297
|
+
this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
|
|
28298
|
+
this.log.info("idle agent session reset", {
|
|
28299
|
+
agentId: effect.agentId,
|
|
28300
|
+
sessionId: effect.sessionId
|
|
28301
|
+
});
|
|
28302
|
+
break;
|
|
28303
|
+
}
|
|
27827
28304
|
case "force_exit": {
|
|
27828
28305
|
const session2 = this.sessions.get(effect.agentId);
|
|
27829
28306
|
const state = this.activeSpawnState.get(effect.agentId);
|
|
@@ -27856,8 +28333,8 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27856
28333
|
}
|
|
27857
28334
|
}
|
|
27858
28335
|
}
|
|
27859
|
-
logSessionEnded(agentId, reason) {
|
|
27860
|
-
this.log.info("agent session ended", { agentId, sessionId
|
|
28336
|
+
logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
|
|
28337
|
+
this.log.info("agent session ended", { agentId, sessionId, reason });
|
|
27861
28338
|
}
|
|
27862
28339
|
doSpawn(agentId, messages, resumeSessionId) {
|
|
27863
28340
|
const [first, ...pending] = messages;
|
|
@@ -27882,7 +28359,13 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27882
28359
|
mode: { kind: "default" }
|
|
27883
28360
|
};
|
|
27884
28361
|
const provider = runtimeConfig.runtime;
|
|
27885
|
-
const
|
|
28362
|
+
const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
|
|
28363
|
+
const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
|
|
28364
|
+
const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
|
|
28365
|
+
const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
|
|
28366
|
+
const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
|
|
28367
|
+
const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
|
|
28368
|
+
const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
|
|
27886
28369
|
const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
|
|
27887
28370
|
const agentName = runtimeConfig.agentName ?? base.config?.agentName;
|
|
27888
28371
|
const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
|
|
@@ -27913,12 +28396,15 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27913
28396
|
handshakeTimer: null,
|
|
27914
28397
|
torndown: false,
|
|
27915
28398
|
superseded: false,
|
|
28399
|
+
discardEvents: false,
|
|
28400
|
+
stalledSessionIdAtLaunch,
|
|
27916
28401
|
spawnFailureReason: null,
|
|
27917
28402
|
terminationSemantics: null,
|
|
27918
28403
|
spawnOrdinal: this.nextSpawnOrdinal++,
|
|
27919
28404
|
launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
|
|
27920
28405
|
nextTurnOrdinal: 1,
|
|
27921
28406
|
activeSpan: null,
|
|
28407
|
+
timelineTurnOwner: null,
|
|
27922
28408
|
pendingDeliverySpans: new Map
|
|
27923
28409
|
};
|
|
27924
28410
|
const previousOwner = this.activeSpawnState.get(agentId);
|
|
@@ -27969,7 +28455,6 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27969
28455
|
this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
|
|
27970
28456
|
}
|
|
27971
28457
|
}
|
|
27972
|
-
this.flushThinkingAudit(agentId);
|
|
27973
28458
|
if (state.sessionInstanceId) {
|
|
27974
28459
|
this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
|
|
27975
28460
|
}
|
|
@@ -27994,10 +28479,23 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27994
28479
|
const onEvent = (event) => {
|
|
27995
28480
|
if (state.torndown)
|
|
27996
28481
|
return;
|
|
28482
|
+
if (state.discardEvents) {
|
|
28483
|
+
this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
|
|
28484
|
+
return;
|
|
28485
|
+
}
|
|
27997
28486
|
if (event.type === "session_failed" && !state.hasEstablished) {
|
|
27998
28487
|
reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
|
|
27999
28488
|
}
|
|
28000
28489
|
if (event.type === "session_started") {
|
|
28490
|
+
const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
|
|
28491
|
+
if (persisted === false) {
|
|
28492
|
+
state.discardEvents = true;
|
|
28493
|
+
reportSpawnFailure("resume_control_update_failed", {
|
|
28494
|
+
message: "Backend session rejected because resume control could not be persisted"
|
|
28495
|
+
});
|
|
28496
|
+
state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
|
|
28497
|
+
return;
|
|
28498
|
+
}
|
|
28001
28499
|
state.hasEstablished = true;
|
|
28002
28500
|
clearHandshakeTimer();
|
|
28003
28501
|
this.opts.onRuntimeSessionEstablished?.(driver.id);
|
|
@@ -28016,7 +28514,8 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28016
28514
|
type: "attach_session",
|
|
28017
28515
|
agentId,
|
|
28018
28516
|
sessionInstanceId: session2.sessionInstanceId,
|
|
28019
|
-
nowMs: this.now()
|
|
28517
|
+
nowMs: this.now(),
|
|
28518
|
+
turnSilence: session2.snapshot().diagnostics?.turnSilence
|
|
28020
28519
|
}, state);
|
|
28021
28520
|
previousOwner?.pendingDeliverySpans.clear();
|
|
28022
28521
|
(async () => {
|
|
@@ -28164,26 +28663,6 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28164
28663
|
this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
|
|
28165
28664
|
}
|
|
28166
28665
|
}
|
|
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
28666
|
onAgentEvent(agentId, event, runtimeId, owner) {
|
|
28188
28667
|
if (event.type === "session_closed")
|
|
28189
28668
|
return;
|
|
@@ -28216,32 +28695,43 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28216
28695
|
}
|
|
28217
28696
|
}
|
|
28218
28697
|
if (this.opts.onBotAuditEvent) {
|
|
28219
|
-
if (event.type === "
|
|
28220
|
-
|
|
28221
|
-
|
|
28698
|
+
if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
|
|
28699
|
+
const { text: text2, truncated, chars } = truncateThinking(event.text);
|
|
28700
|
+
try {
|
|
28701
|
+
this.opts.onBotAuditEvent(agentId, {
|
|
28702
|
+
kind: "thinking",
|
|
28703
|
+
payload: { text: text2, truncated: truncated || event.truncated, chars }
|
|
28704
|
+
}, {
|
|
28705
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28706
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
28707
|
+
});
|
|
28708
|
+
} catch (err) {
|
|
28709
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
28222
28710
|
}
|
|
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
|
-
}
|
|
28711
|
+
}
|
|
28712
|
+
if (event.type === "tool_started") {
|
|
28713
|
+
const audit = extractToolAudit(event.name, event.input);
|
|
28714
|
+
if (!audit.suppressed) {
|
|
28715
|
+
const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
|
|
28716
|
+
try {
|
|
28717
|
+
this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
|
|
28718
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28719
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
28720
|
+
});
|
|
28721
|
+
} catch (err) {
|
|
28722
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
28237
28723
|
}
|
|
28238
28724
|
}
|
|
28239
28725
|
}
|
|
28240
28726
|
}
|
|
28241
28727
|
if (event.type === "session_started") {
|
|
28242
|
-
this.dispatch({
|
|
28728
|
+
this.dispatch({
|
|
28729
|
+
type: "backend_session",
|
|
28730
|
+
agentId,
|
|
28731
|
+
sessionId: event.backendSessionId,
|
|
28732
|
+
stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
|
|
28733
|
+
}, owner);
|
|
28243
28734
|
this.liveSessions.set(agentId, event.backendSessionId);
|
|
28244
|
-
this.opts.timeline?.setSession(agentId, event.backendSessionId);
|
|
28245
28735
|
this.opts.onAgentSession?.({
|
|
28246
28736
|
agentId,
|
|
28247
28737
|
sessionId: event.backendSessionId,
|
|
@@ -28253,8 +28743,20 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28253
28743
|
runtime: runtimeId
|
|
28254
28744
|
});
|
|
28255
28745
|
}
|
|
28256
|
-
if (event.type === "
|
|
28257
|
-
|
|
28746
|
+
if (event.type === "turn_started") {
|
|
28747
|
+
const timelineTurnOwner = {
|
|
28748
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
28749
|
+
rootTurnId: event.turnId,
|
|
28750
|
+
barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
|
|
28751
|
+
};
|
|
28752
|
+
owner.timelineTurnOwner = timelineTurnOwner;
|
|
28753
|
+
this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
|
|
28754
|
+
}
|
|
28755
|
+
if (event.type === "assistant_message_completed" && event.text.length > 0) {
|
|
28756
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
28757
|
+
if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
|
|
28758
|
+
this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
|
|
28759
|
+
}
|
|
28258
28760
|
}
|
|
28259
28761
|
if (event.type === "command_queued")
|
|
28260
28762
|
this.acknowledgePendingDelivery(owner, event.commandId);
|
|
@@ -28272,9 +28774,10 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28272
28774
|
switch (event.type) {
|
|
28273
28775
|
case "turn_started":
|
|
28274
28776
|
return { type: "turn_started", turnId: event.turnId, commandIds: event.commandIds };
|
|
28275
|
-
case "
|
|
28276
|
-
case "
|
|
28277
|
-
|
|
28777
|
+
case "work_heartbeat":
|
|
28778
|
+
case "assistant_reasoning_completed":
|
|
28779
|
+
case "assistant_message_completed":
|
|
28780
|
+
return { type: "turn_work", turnId: event.turnId };
|
|
28278
28781
|
case "tool_started":
|
|
28279
28782
|
return { type: "turn_tool_started", turnId: event.turnId };
|
|
28280
28783
|
case "tool_finished":
|
|
@@ -28301,37 +28804,55 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28301
28804
|
if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
|
|
28302
28805
|
this.openTurn(owner);
|
|
28303
28806
|
}
|
|
28304
|
-
const
|
|
28807
|
+
const nativeSignal = (() => {
|
|
28305
28808
|
switch (event.type) {
|
|
28306
|
-
case "
|
|
28307
|
-
return "
|
|
28308
|
-
case "
|
|
28309
|
-
return
|
|
28310
|
-
|
|
28311
|
-
|
|
28809
|
+
case "turn_started":
|
|
28810
|
+
return { kind: "turn_started", phase: "inference", turnId: event.turnId };
|
|
28811
|
+
case "backend_turn_started":
|
|
28812
|
+
return {
|
|
28813
|
+
kind: "backend_turn_started",
|
|
28814
|
+
phase: "inference",
|
|
28815
|
+
turnId: event.turnId,
|
|
28816
|
+
backendTurnId: event.backendTurnId
|
|
28817
|
+
};
|
|
28818
|
+
case "assistant_reasoning_completed":
|
|
28819
|
+
return { kind: "thinking", phase: "inference", turnId: event.turnId };
|
|
28820
|
+
case "assistant_message_completed":
|
|
28821
|
+
return { kind: "text", phase: "inference", turnId: event.turnId };
|
|
28822
|
+
case "work_heartbeat":
|
|
28823
|
+
return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
|
|
28312
28824
|
case "tool_started":
|
|
28313
|
-
return "tool_call";
|
|
28825
|
+
return { kind: "tool_call", phase: "tool", turnId: event.turnId };
|
|
28314
28826
|
case "tool_finished":
|
|
28315
|
-
return "tool_output";
|
|
28316
|
-
case "
|
|
28317
|
-
|
|
28318
|
-
case "
|
|
28319
|
-
case "
|
|
28320
|
-
|
|
28827
|
+
return { kind: "tool_output", phase: "inference", turnId: event.turnId };
|
|
28828
|
+
case "compaction_started":
|
|
28829
|
+
case "compaction_finished":
|
|
28830
|
+
case "review_started":
|
|
28831
|
+
case "review_finished":
|
|
28832
|
+
case "internal_progress":
|
|
28833
|
+
return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
|
|
28834
|
+
case "recovery":
|
|
28835
|
+
return event.turnId ? {
|
|
28836
|
+
kind: "recovery",
|
|
28837
|
+
phase: event.stage === "retrying" ? "recovery" : "inference",
|
|
28838
|
+
recoveryStage: event.stage,
|
|
28839
|
+
turnId: event.turnId
|
|
28840
|
+
} : null;
|
|
28321
28841
|
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";
|
|
28842
|
+
return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
|
|
28330
28843
|
default:
|
|
28331
|
-
return
|
|
28844
|
+
return null;
|
|
28332
28845
|
}
|
|
28333
28846
|
})();
|
|
28334
|
-
|
|
28847
|
+
if (nativeSignal) {
|
|
28848
|
+
this.dispatch({
|
|
28849
|
+
type: "runtime_signal",
|
|
28850
|
+
agentId,
|
|
28851
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
28852
|
+
...nativeSignal,
|
|
28853
|
+
nowMs: this.now()
|
|
28854
|
+
}, owner);
|
|
28855
|
+
}
|
|
28335
28856
|
if (event.type === "turn_completed") {
|
|
28336
28857
|
this.logSessionEnded(agentId, "turn_end");
|
|
28337
28858
|
const marker = this.nonCleanEndMarker.get(agentId);
|
|
@@ -28411,6 +28932,7 @@ Then read @memory.md and your .context_timeline for durable context, and pull `
|
|
|
28411
28932
|
class AgentRouter {
|
|
28412
28933
|
opts;
|
|
28413
28934
|
running = new Set;
|
|
28935
|
+
nextWakeAdmissionOrdinal = 1;
|
|
28414
28936
|
runtimes = new Map;
|
|
28415
28937
|
pendingResend = false;
|
|
28416
28938
|
scheduleResend;
|
|
@@ -28571,7 +29093,7 @@ class AgentRouter {
|
|
|
28571
29093
|
this.opts.typingTracker?.add(cmd.agentId, channelScope);
|
|
28572
29094
|
const text2 = (this.opts.formatUnreadNoticeText ?? defaultFormatUnreadNoticeText)(cmd.unreadNotice);
|
|
28573
29095
|
const producedEffect = this.opts.manager.deliver(cmd.agentId, {
|
|
28574
|
-
id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}`,
|
|
29096
|
+
id: `${cmd.agentId}:wake:${cmd.unreadNotice.channel}:${cmd.unreadNotice.latestSeq}:admission:${this.nextWakeAdmissionOrdinal++}`,
|
|
28575
29097
|
seq: cmd.unreadNotice.latestSeq,
|
|
28576
29098
|
text: text2
|
|
28577
29099
|
});
|
|
@@ -28726,7 +29248,7 @@ function createTypingScopeTracker() {
|
|
|
28726
29248
|
}
|
|
28727
29249
|
// src/timeline/timeline.ts
|
|
28728
29250
|
import * as fs9 from "node:fs";
|
|
28729
|
-
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
29251
|
+
import { createHash as createHash2, randomBytes as randomBytes4 } from "node:crypto";
|
|
28730
29252
|
import { basename as basename3, dirname as dirname5, join as join11 } from "node:path";
|
|
28731
29253
|
|
|
28732
29254
|
// src/timeline/filelock.ts
|
|
@@ -28793,17 +29315,25 @@ function reclaim(lockPath) {
|
|
|
28793
29315
|
var TIMELINE_MAX_BYTES = 1048576;
|
|
28794
29316
|
var TIMELINE_READ_CHUNK_BYTES = 65536;
|
|
28795
29317
|
var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
29318
|
+
var RESUME_CONTROL_FILENAME = ".resume-control.json";
|
|
29319
|
+
var RESUME_CONTROL_MAX_BYTES = 4096;
|
|
29320
|
+
var EMPTY_RESUME_CONTROL = {
|
|
29321
|
+
version: 1,
|
|
29322
|
+
attemptedSessionId: null,
|
|
29323
|
+
fencedSessionId: null,
|
|
29324
|
+
fullBarrier: null
|
|
29325
|
+
};
|
|
28796
29326
|
function isBarrier(entry) {
|
|
28797
|
-
return entry.system
|
|
29327
|
+
return entry.system !== undefined;
|
|
28798
29328
|
}
|
|
28799
29329
|
function canonicalTimelineEntry(value) {
|
|
28800
29330
|
if (!value || typeof value !== "object")
|
|
28801
29331
|
return null;
|
|
28802
29332
|
const entry = value;
|
|
28803
29333
|
if (entry.system) {
|
|
28804
|
-
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
|
|
29334
|
+
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
29335
|
return null;
|
|
28806
|
-
return createSystemEntry(entry.system.type, entry.system.time);
|
|
29336
|
+
return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
|
|
28807
29337
|
}
|
|
28808
29338
|
if (entry.session_id !== null && typeof entry.session_id !== "string")
|
|
28809
29339
|
return null;
|
|
@@ -28821,13 +29351,60 @@ function canonicalTimelineEntry(value) {
|
|
|
28821
29351
|
};
|
|
28822
29352
|
}
|
|
28823
29353
|
function timelineLine(entry) {
|
|
28824
|
-
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
29354
|
+
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
29355
|
const text2 = JSON.stringify(boundedEntry);
|
|
28826
29356
|
const bytes = Buffer.byteLength(text2, "utf8") + 1;
|
|
28827
29357
|
if (bytes > TIMELINE_MAX_BYTES)
|
|
28828
29358
|
return null;
|
|
28829
29359
|
return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
|
|
28830
29360
|
}
|
|
29361
|
+
function timelineRowHash(line) {
|
|
29362
|
+
return createHash2("sha256").update(line.text, "utf8").digest("hex");
|
|
29363
|
+
}
|
|
29364
|
+
function timelineFileGeneration(filePath, lines) {
|
|
29365
|
+
try {
|
|
29366
|
+
const stat = fs9.lstatSync(filePath, { bigint: true });
|
|
29367
|
+
if (!stat.isFile())
|
|
29368
|
+
return null;
|
|
29369
|
+
const digest = createHash2("sha256");
|
|
29370
|
+
digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
|
|
29371
|
+
`);
|
|
29372
|
+
for (const line of lines)
|
|
29373
|
+
digest.update(line.text, "utf8").update(`
|
|
29374
|
+
`);
|
|
29375
|
+
return digest.digest("hex");
|
|
29376
|
+
} catch {
|
|
29377
|
+
return null;
|
|
29378
|
+
}
|
|
29379
|
+
}
|
|
29380
|
+
function handleFor(filename, generation, lines, rowOrdinal) {
|
|
29381
|
+
return {
|
|
29382
|
+
filename,
|
|
29383
|
+
fileGeneration: generation,
|
|
29384
|
+
rowOrdinal,
|
|
29385
|
+
expectedHash: timelineRowHash(lines[rowOrdinal])
|
|
29386
|
+
};
|
|
29387
|
+
}
|
|
29388
|
+
function refreshTimelineEntryHandle(handle, rewrite) {
|
|
29389
|
+
if (handle.filename !== rewrite.filename)
|
|
29390
|
+
return handle;
|
|
29391
|
+
if (handle.fileGeneration !== rewrite.previousFileGeneration)
|
|
29392
|
+
return null;
|
|
29393
|
+
if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
|
|
29394
|
+
return null;
|
|
29395
|
+
const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
|
|
29396
|
+
if (rowOrdinal === null || rowOrdinal === undefined)
|
|
29397
|
+
return null;
|
|
29398
|
+
const expectedHash = rewrite.rowHashes[rowOrdinal];
|
|
29399
|
+
if (!expectedHash)
|
|
29400
|
+
return null;
|
|
29401
|
+
return {
|
|
29402
|
+
filename: handle.filename,
|
|
29403
|
+
fileGeneration: rewrite.fileGeneration,
|
|
29404
|
+
rowOrdinal,
|
|
29405
|
+
expectedHash
|
|
29406
|
+
};
|
|
29407
|
+
}
|
|
28831
29408
|
function compactLines(input) {
|
|
28832
29409
|
let head = 0;
|
|
28833
29410
|
let bytes = input.reduce((total, line) => total + line.bytes, 0);
|
|
@@ -29039,11 +29616,37 @@ function atomicReplaceTimeline(filePath, lines) {
|
|
|
29039
29616
|
} catch {}
|
|
29040
29617
|
}
|
|
29041
29618
|
}
|
|
29042
|
-
function
|
|
29619
|
+
function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
|
|
29620
|
+
const previousFileGeneration = timelineFileGeneration(filePath, existing);
|
|
29621
|
+
const previousRowHashes = existing.map(timelineRowHash);
|
|
29043
29622
|
const compacted = compactLines(input);
|
|
29044
|
-
|
|
29045
|
-
|
|
29046
|
-
|
|
29623
|
+
const targetOrdinal = compacted.indexOf(required2);
|
|
29624
|
+
if (targetOrdinal < 0)
|
|
29625
|
+
return { status: "rejected", reason: "evicted" };
|
|
29626
|
+
if (!atomicReplaceTimeline(filePath, compacted))
|
|
29627
|
+
return { status: "rejected", reason: "write" };
|
|
29628
|
+
const fileGeneration = timelineFileGeneration(filePath, compacted);
|
|
29629
|
+
if (!fileGeneration)
|
|
29630
|
+
return { status: "rejected", reason: "write" };
|
|
29631
|
+
const rowOrdinals = existing.map((line, oldOrdinal) => {
|
|
29632
|
+
if (replacement?.oldOrdinal === oldOrdinal)
|
|
29633
|
+
return compacted.indexOf(replacement.line);
|
|
29634
|
+
const nextOrdinal = compacted.indexOf(line);
|
|
29635
|
+
return nextOrdinal < 0 ? null : nextOrdinal;
|
|
29636
|
+
});
|
|
29637
|
+
const rewrite = {
|
|
29638
|
+
filename,
|
|
29639
|
+
previousFileGeneration,
|
|
29640
|
+
fileGeneration,
|
|
29641
|
+
previousRowHashes,
|
|
29642
|
+
rowOrdinals,
|
|
29643
|
+
rowHashes: compacted.map(timelineRowHash)
|
|
29644
|
+
};
|
|
29645
|
+
return {
|
|
29646
|
+
status: "written",
|
|
29647
|
+
rewrite,
|
|
29648
|
+
handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
|
|
29649
|
+
};
|
|
29047
29650
|
}
|
|
29048
29651
|
function filenameForDate(date5) {
|
|
29049
29652
|
const y = date5.getFullYear();
|
|
@@ -29075,114 +29678,181 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
29075
29678
|
}
|
|
29076
29679
|
return entries;
|
|
29077
29680
|
}
|
|
29078
|
-
function
|
|
29681
|
+
function readResumeControlState(timelineDir) {
|
|
29682
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29683
|
+
return { kind: "missing" };
|
|
29684
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29685
|
+
let source;
|
|
29686
|
+
try {
|
|
29687
|
+
source = fs9.lstatSync(filePath);
|
|
29688
|
+
} catch (error51) {
|
|
29689
|
+
return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
|
|
29690
|
+
}
|
|
29691
|
+
if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
|
|
29692
|
+
return { kind: "invalid" };
|
|
29693
|
+
}
|
|
29694
|
+
let fd = null;
|
|
29695
|
+
try {
|
|
29696
|
+
fd = fs9.openSync(filePath, fs9.constants.O_RDONLY | (fs9.constants.O_NOFOLLOW ?? 0));
|
|
29697
|
+
const stat = fs9.fstatSync(fd);
|
|
29698
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
|
|
29699
|
+
return { kind: "invalid" };
|
|
29700
|
+
}
|
|
29701
|
+
const bounded = Buffer.allocUnsafe(stat.size + 1);
|
|
29702
|
+
let bytesRead = 0;
|
|
29703
|
+
while (bytesRead < bounded.length) {
|
|
29704
|
+
const count = fs9.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
|
|
29705
|
+
if (count <= 0)
|
|
29706
|
+
break;
|
|
29707
|
+
bytesRead += count;
|
|
29708
|
+
}
|
|
29709
|
+
if (bytesRead !== stat.size)
|
|
29710
|
+
return { kind: "invalid" };
|
|
29711
|
+
const raw = bounded.subarray(0, bytesRead).toString("utf8");
|
|
29712
|
+
const value = JSON.parse(raw);
|
|
29713
|
+
const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
|
|
29714
|
+
if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
|
|
29715
|
+
return { kind: "invalid" };
|
|
29716
|
+
return {
|
|
29717
|
+
kind: "state",
|
|
29718
|
+
state: {
|
|
29719
|
+
version: 1,
|
|
29720
|
+
attemptedSessionId: value.attemptedSessionId,
|
|
29721
|
+
fencedSessionId: value.fencedSessionId,
|
|
29722
|
+
fullBarrier: value.fullBarrier
|
|
29723
|
+
}
|
|
29724
|
+
};
|
|
29725
|
+
} catch {
|
|
29726
|
+
return { kind: "invalid" };
|
|
29727
|
+
} finally {
|
|
29728
|
+
if (fd !== null) {
|
|
29729
|
+
try {
|
|
29730
|
+
fs9.closeSync(fd);
|
|
29731
|
+
} catch {}
|
|
29732
|
+
}
|
|
29733
|
+
}
|
|
29734
|
+
}
|
|
29735
|
+
function updateResumeControlState(timelineDir, update) {
|
|
29079
29736
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29080
29737
|
return false;
|
|
29081
|
-
const
|
|
29082
|
-
const filePath = join11(timelineDir, filename);
|
|
29083
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
29738
|
+
const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29084
29739
|
if (!acquireLock(lockPath))
|
|
29085
29740
|
return false;
|
|
29086
29741
|
try {
|
|
29087
|
-
const
|
|
29088
|
-
const
|
|
29089
|
-
|
|
29742
|
+
const current = readResumeControlState(timelineDir);
|
|
29743
|
+
const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
|
|
29744
|
+
const next = update({ ...base });
|
|
29745
|
+
const canonical = {
|
|
29746
|
+
version: 1,
|
|
29747
|
+
attemptedSessionId: next.attemptedSessionId,
|
|
29748
|
+
fencedSessionId: next.fencedSessionId,
|
|
29749
|
+
fullBarrier: next.fullBarrier
|
|
29750
|
+
};
|
|
29751
|
+
const body = JSON.stringify(canonical) + `
|
|
29752
|
+
`;
|
|
29753
|
+
if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
|
|
29090
29754
|
return false;
|
|
29091
|
-
|
|
29755
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29756
|
+
const tempPath = join11(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
|
|
29757
|
+
let fd = null;
|
|
29758
|
+
try {
|
|
29759
|
+
fd = fs9.openSync(tempPath, "wx", 384);
|
|
29760
|
+
fs9.writeFileSync(fd, body, "utf8");
|
|
29761
|
+
fs9.fsyncSync(fd);
|
|
29762
|
+
fs9.closeSync(fd);
|
|
29763
|
+
fd = null;
|
|
29764
|
+
fs9.renameSync(tempPath, filePath);
|
|
29765
|
+
return true;
|
|
29766
|
+
} finally {
|
|
29767
|
+
if (fd !== null) {
|
|
29768
|
+
try {
|
|
29769
|
+
fs9.closeSync(fd);
|
|
29770
|
+
} catch {}
|
|
29771
|
+
}
|
|
29772
|
+
try {
|
|
29773
|
+
fs9.unlinkSync(tempPath);
|
|
29774
|
+
} catch {}
|
|
29775
|
+
}
|
|
29092
29776
|
} catch {
|
|
29093
29777
|
return false;
|
|
29094
29778
|
} finally {
|
|
29095
29779
|
releaseLock(lockPath);
|
|
29096
29780
|
}
|
|
29097
29781
|
}
|
|
29098
|
-
function
|
|
29782
|
+
function appendTrackedEntry(timelineDir, entry, now = new Date) {
|
|
29099
29783
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29100
|
-
return
|
|
29784
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29101
29785
|
const filename = filenameForDate(now);
|
|
29102
29786
|
const filePath = join11(timelineDir, filename);
|
|
29103
29787
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
29104
|
-
|
|
29105
|
-
|
|
29788
|
+
try {
|
|
29789
|
+
if (!acquireLock(lockPath))
|
|
29790
|
+
return { status: "rejected", reason: "lock" };
|
|
29791
|
+
} catch {
|
|
29792
|
+
return { status: "rejected", reason: "write" };
|
|
29793
|
+
}
|
|
29106
29794
|
try {
|
|
29107
29795
|
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
29796
|
const required2 = timelineLine(entry);
|
|
29797
|
+
if (!existing)
|
|
29798
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29126
29799
|
if (!required2)
|
|
29127
|
-
return
|
|
29128
|
-
return
|
|
29800
|
+
return { status: "rejected", reason: "oversized" };
|
|
29801
|
+
return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
|
|
29129
29802
|
} catch {
|
|
29130
|
-
return
|
|
29803
|
+
return { status: "rejected", reason: "write" };
|
|
29131
29804
|
} finally {
|
|
29132
29805
|
releaseLock(lockPath);
|
|
29133
29806
|
}
|
|
29134
29807
|
}
|
|
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);
|
|
29808
|
+
function updateTrackedEntry(timelineDir, handle, update) {
|
|
29809
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29810
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29811
|
+
if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename3(handle.filename) !== handle.filename) {
|
|
29812
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29813
|
+
}
|
|
29814
|
+
const filePath = join11(timelineDir, handle.filename);
|
|
29815
|
+
const lockPath = lockPathFor(timelineDir, handle.filename);
|
|
29816
|
+
try {
|
|
29154
29817
|
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
|
-
|
|
29818
|
+
return { status: "rejected", reason: "lock" };
|
|
29819
|
+
} catch {
|
|
29820
|
+
return { status: "rejected", reason: "write" };
|
|
29821
|
+
}
|
|
29822
|
+
try {
|
|
29823
|
+
const existing = scanTimelineFile(filePath);
|
|
29824
|
+
if (!existing)
|
|
29825
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29826
|
+
if (existing.length === 0)
|
|
29827
|
+
return { status: "rejected", reason: "missing" };
|
|
29828
|
+
const generation = timelineFileGeneration(filePath, existing);
|
|
29829
|
+
if (!generation || generation !== handle.fileGeneration) {
|
|
29830
|
+
return { status: "rejected", reason: "generation" };
|
|
29831
|
+
}
|
|
29832
|
+
const captured = existing[handle.rowOrdinal];
|
|
29833
|
+
if (!captured)
|
|
29834
|
+
return { status: "rejected", reason: "ordinal" };
|
|
29835
|
+
if (timelineRowHash(captured) !== handle.expectedHash) {
|
|
29836
|
+
return { status: "rejected", reason: "hash" };
|
|
29837
|
+
}
|
|
29838
|
+
if (captured.entry.system)
|
|
29839
|
+
return { status: "rejected", reason: "system" };
|
|
29840
|
+
const nextEntry = update({
|
|
29841
|
+
...captured.entry,
|
|
29842
|
+
messages: [...captured.entry.messages],
|
|
29843
|
+
agent_responses: [...captured.entry.agent_responses]
|
|
29844
|
+
});
|
|
29845
|
+
const replacement = timelineLine(nextEntry);
|
|
29846
|
+
if (!replacement)
|
|
29847
|
+
return { status: "rejected", reason: "oversized" };
|
|
29848
|
+
const input = [...existing];
|
|
29849
|
+
input[handle.rowOrdinal] = replacement;
|
|
29850
|
+
return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
|
|
29851
|
+
} catch {
|
|
29852
|
+
return { status: "rejected", reason: "write" };
|
|
29853
|
+
} finally {
|
|
29854
|
+
releaseLock(lockPath);
|
|
29184
29855
|
}
|
|
29185
|
-
return "missing";
|
|
29186
29856
|
}
|
|
29187
29857
|
function yieldToEventLoop() {
|
|
29188
29858
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
@@ -29246,84 +29916,562 @@ function createTimelineEntry(fields) {
|
|
|
29246
29916
|
provider: fields.provider ?? null
|
|
29247
29917
|
};
|
|
29248
29918
|
}
|
|
29249
|
-
function createSystemEntry(type, time3) {
|
|
29919
|
+
function createSystemEntry(type, time3, backendSessionId) {
|
|
29250
29920
|
return {
|
|
29251
29921
|
session_id: null,
|
|
29252
29922
|
messages: [],
|
|
29253
29923
|
agent_responses: [],
|
|
29254
29924
|
provider: null,
|
|
29255
|
-
system: {
|
|
29925
|
+
system: {
|
|
29926
|
+
type,
|
|
29927
|
+
time: time3,
|
|
29928
|
+
...backendSessionId ? { backend_session_id: backendSessionId } : {}
|
|
29929
|
+
}
|
|
29256
29930
|
};
|
|
29257
29931
|
}
|
|
29258
|
-
function
|
|
29932
|
+
function resolveResumableSession(rows, provider) {
|
|
29933
|
+
let candidateSessionId = null;
|
|
29934
|
+
let recoveryMarkerSeen = false;
|
|
29935
|
+
let stalledSessionId = null;
|
|
29936
|
+
let fencedSessionId = null;
|
|
29259
29937
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
29260
29938
|
const e = rows[i];
|
|
29261
|
-
if (e.system
|
|
29262
|
-
|
|
29939
|
+
if (e.system) {
|
|
29940
|
+
if (e.system.type === "stall_recovery_attempt") {
|
|
29941
|
+
if (!recoveryMarkerSeen) {
|
|
29942
|
+
recoveryMarkerSeen = true;
|
|
29943
|
+
stalledSessionId = e.system.backend_session_id ?? null;
|
|
29944
|
+
}
|
|
29945
|
+
continue;
|
|
29946
|
+
}
|
|
29947
|
+
if (e.system.type === "stall_recovery_clear") {
|
|
29948
|
+
if (!recoveryMarkerSeen)
|
|
29949
|
+
recoveryMarkerSeen = true;
|
|
29950
|
+
continue;
|
|
29951
|
+
}
|
|
29952
|
+
if (e.system.type === "stall_recovery" && fencedSessionId === null) {
|
|
29953
|
+
fencedSessionId = e.system.backend_session_id ?? null;
|
|
29954
|
+
}
|
|
29955
|
+
if (candidateSessionId !== null) {
|
|
29956
|
+
return {
|
|
29957
|
+
kind: "session",
|
|
29958
|
+
sessionId: candidateSessionId,
|
|
29959
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
29960
|
+
fencedSessionId
|
|
29961
|
+
};
|
|
29962
|
+
}
|
|
29963
|
+
return {
|
|
29964
|
+
kind: "barrier",
|
|
29965
|
+
type: e.system.type,
|
|
29966
|
+
forgottenSessionId: e.system.backend_session_id ?? null,
|
|
29967
|
+
fencedSessionId
|
|
29968
|
+
};
|
|
29969
|
+
}
|
|
29263
29970
|
if (!e.session_id)
|
|
29264
29971
|
continue;
|
|
29265
29972
|
if (provider && e.provider !== provider)
|
|
29266
29973
|
continue;
|
|
29267
|
-
|
|
29974
|
+
if (candidateSessionId === null) {
|
|
29975
|
+
candidateSessionId = e.session_id;
|
|
29976
|
+
continue;
|
|
29977
|
+
}
|
|
29978
|
+
if (candidateSessionId !== e.session_id)
|
|
29979
|
+
break;
|
|
29268
29980
|
}
|
|
29269
|
-
|
|
29981
|
+
if (candidateSessionId !== null) {
|
|
29982
|
+
return {
|
|
29983
|
+
kind: "session",
|
|
29984
|
+
sessionId: candidateSessionId,
|
|
29985
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
29986
|
+
fencedSessionId
|
|
29987
|
+
};
|
|
29988
|
+
}
|
|
29989
|
+
return { kind: "none", stalledSessionId, fencedSessionId };
|
|
29270
29990
|
}
|
|
29271
29991
|
// src/timeline/recorder.ts
|
|
29272
29992
|
var MAX_AGENT_RESPONSES = 5;
|
|
29273
|
-
|
|
29274
|
-
|
|
29275
|
-
|
|
29276
|
-
|
|
29993
|
+
var MAX_AGENT_RESPONSE_BYTES = 65536;
|
|
29994
|
+
var MAX_PENDING_COMMITS_PER_AGENT = 8;
|
|
29995
|
+
var PENDING_COMMIT_TTL_MS = 15 * 60000;
|
|
29996
|
+
var TRUNCATION_MARKER = `
|
|
29997
|
+
… [truncated]`;
|
|
29998
|
+
function turnKey(agentId, owner) {
|
|
29999
|
+
return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
|
|
30000
|
+
}
|
|
30001
|
+
function sameOwner(left, right) {
|
|
30002
|
+
return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
|
|
30003
|
+
}
|
|
30004
|
+
function utf8Prefix2(text2, maxBytes) {
|
|
30005
|
+
if (maxBytes <= 0)
|
|
30006
|
+
return "";
|
|
30007
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
30008
|
+
return text2;
|
|
30009
|
+
let low = 0;
|
|
30010
|
+
let high = text2.length;
|
|
30011
|
+
while (low < high) {
|
|
30012
|
+
const mid = Math.ceil((low + high) / 2);
|
|
30013
|
+
let end2 = mid;
|
|
30014
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
30015
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
30016
|
+
end2 -= 1;
|
|
30017
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
30018
|
+
low = mid;
|
|
30019
|
+
else
|
|
30020
|
+
high = mid - 1;
|
|
30021
|
+
}
|
|
30022
|
+
let end = low;
|
|
30023
|
+
const code = text2.charCodeAt(end - 1);
|
|
30024
|
+
if (code >= 55296 && code <= 56319)
|
|
30025
|
+
end -= 1;
|
|
30026
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
30027
|
+
end -= 1;
|
|
30028
|
+
return text2.slice(0, end);
|
|
30029
|
+
}
|
|
30030
|
+
function boundedResponse(text2, alreadyTruncated) {
|
|
30031
|
+
const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
|
|
30032
|
+
const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
|
|
30033
|
+
if (!needsTruncation)
|
|
30034
|
+
return text2;
|
|
30035
|
+
return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
|
|
30036
|
+
}
|
|
30037
|
+
function serializedTimelineBytes(entry) {
|
|
30038
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
30039
|
+
}
|
|
30040
|
+
function fitResponsesToRow(entry, responses) {
|
|
30041
|
+
let fitted = [...entry.agent_responses];
|
|
30042
|
+
for (const response of responses) {
|
|
30043
|
+
fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
|
|
30044
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
|
|
30045
|
+
continue;
|
|
30046
|
+
const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
|
|
30047
|
+
let low = 0;
|
|
30048
|
+
let high = truncationBase.length;
|
|
30049
|
+
let replacement = null;
|
|
30050
|
+
while (low <= high) {
|
|
30051
|
+
const mid = Math.floor((low + high) / 2);
|
|
30052
|
+
let end = mid;
|
|
30053
|
+
const code = truncationBase.charCodeAt(end - 1);
|
|
30054
|
+
if (code >= 55296 && code <= 56319)
|
|
30055
|
+
end -= 1;
|
|
30056
|
+
const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
|
|
30057
|
+
const candidate = [...fitted.slice(0, -1), candidateResponse];
|
|
30058
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
|
|
30059
|
+
replacement = candidateResponse;
|
|
30060
|
+
low = mid + 1;
|
|
30061
|
+
} else {
|
|
30062
|
+
high = mid - 1;
|
|
30063
|
+
}
|
|
30064
|
+
}
|
|
30065
|
+
if (replacement === null)
|
|
30066
|
+
return null;
|
|
30067
|
+
fitted[fitted.length - 1] = replacement;
|
|
29277
30068
|
}
|
|
30069
|
+
return fitted;
|
|
29278
30070
|
}
|
|
29279
30071
|
function createTimelineRecorder(opts) {
|
|
29280
30072
|
const now = opts.now ?? (() => new Date);
|
|
29281
30073
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
29282
30074
|
const sessionByAgent = new Map;
|
|
30075
|
+
const resolveForAgent = (agentId, provider) => {
|
|
30076
|
+
const dir = dirFor(agentId);
|
|
30077
|
+
const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
|
|
30078
|
+
const control = readResumeControlState(dir);
|
|
30079
|
+
if (control.kind === "missing")
|
|
30080
|
+
return recent;
|
|
30081
|
+
if (control.kind === "invalid") {
|
|
30082
|
+
return {
|
|
30083
|
+
kind: "barrier",
|
|
30084
|
+
type: "reset_session",
|
|
30085
|
+
forgottenSessionId: null,
|
|
30086
|
+
fencedSessionId: null
|
|
30087
|
+
};
|
|
30088
|
+
}
|
|
30089
|
+
const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
|
|
30090
|
+
if (fullBarrier !== null) {
|
|
30091
|
+
return {
|
|
30092
|
+
kind: "barrier",
|
|
30093
|
+
type: fullBarrier,
|
|
30094
|
+
forgottenSessionId: null,
|
|
30095
|
+
fencedSessionId
|
|
30096
|
+
};
|
|
30097
|
+
}
|
|
30098
|
+
if (recent.kind === "session") {
|
|
30099
|
+
return {
|
|
30100
|
+
kind: "session",
|
|
30101
|
+
sessionId: recent.sessionId,
|
|
30102
|
+
stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
|
|
30103
|
+
fencedSessionId
|
|
30104
|
+
};
|
|
30105
|
+
}
|
|
30106
|
+
if (recent.kind === "barrier") {
|
|
30107
|
+
if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
|
|
30108
|
+
return { ...recent, fencedSessionId };
|
|
30109
|
+
}
|
|
30110
|
+
}
|
|
30111
|
+
return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
|
|
30112
|
+
};
|
|
30113
|
+
const sessionByEpoch = new Map;
|
|
30114
|
+
const barrierByAgent = new Map;
|
|
30115
|
+
const turnsByAgent = new Map;
|
|
30116
|
+
const activeTurnByAgent = new Map;
|
|
30117
|
+
const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
|
|
30118
|
+
const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
|
|
30119
|
+
const statesFor = (agentId) => {
|
|
30120
|
+
let states = turnsByAgent.get(agentId);
|
|
30121
|
+
if (!states) {
|
|
30122
|
+
states = new Map;
|
|
30123
|
+
turnsByAgent.set(agentId, states);
|
|
30124
|
+
}
|
|
30125
|
+
return states;
|
|
30126
|
+
};
|
|
30127
|
+
const diagnostic = (agentId, code, reason) => {
|
|
30128
|
+
try {
|
|
30129
|
+
opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
|
|
30130
|
+
} catch {}
|
|
30131
|
+
};
|
|
30132
|
+
const applyRewrite = (agentId, rewrite) => {
|
|
30133
|
+
for (const state of statesFor(agentId).values()) {
|
|
30134
|
+
if (!state.handle)
|
|
30135
|
+
continue;
|
|
30136
|
+
const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
|
|
30137
|
+
if (refreshed) {
|
|
30138
|
+
state.handle = refreshed;
|
|
30139
|
+
} else if (state.handle.filename === rewrite.filename) {
|
|
30140
|
+
state.handle = undefined;
|
|
30141
|
+
state.rowFenced = true;
|
|
30142
|
+
diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
|
|
30143
|
+
}
|
|
30144
|
+
}
|
|
30145
|
+
};
|
|
30146
|
+
const handleTrackedResult = (agentId, state, result) => {
|
|
30147
|
+
if (result.status === "written") {
|
|
30148
|
+
applyRewrite(agentId, result.rewrite);
|
|
30149
|
+
if (state && result.handle) {
|
|
30150
|
+
state.handle = result.handle;
|
|
30151
|
+
state.rowFenced = false;
|
|
30152
|
+
}
|
|
30153
|
+
return "written";
|
|
30154
|
+
}
|
|
30155
|
+
if (result.reason === "lock" || result.reason === "write")
|
|
30156
|
+
return "retryable";
|
|
30157
|
+
if (state && result.reason !== "oversized")
|
|
30158
|
+
state.rowFenced = true;
|
|
30159
|
+
diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
|
|
30160
|
+
return "terminal";
|
|
30161
|
+
};
|
|
30162
|
+
const tryCommit = (agentId, state) => {
|
|
30163
|
+
if (state.responses.length === 0)
|
|
30164
|
+
return "written";
|
|
30165
|
+
const dir = dirFor(agentId);
|
|
30166
|
+
if (!prepareTimelineDirectory(dir)) {
|
|
30167
|
+
diagnostic(agentId, "timeline_directory_unavailable");
|
|
30168
|
+
return "terminal";
|
|
30169
|
+
}
|
|
30170
|
+
if (state.handle) {
|
|
30171
|
+
const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
|
|
30172
|
+
const fitted2 = fitResponsesToRow(entry2, state.responses);
|
|
30173
|
+
return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
|
|
30174
|
+
});
|
|
30175
|
+
return handleTrackedResult(agentId, state, result2);
|
|
30176
|
+
}
|
|
30177
|
+
if (state.rowFenced || state.pendingMode === "handle")
|
|
30178
|
+
return "terminal";
|
|
30179
|
+
if (state.owner.barrierGeneration !== currentBarrier(agentId))
|
|
30180
|
+
return "terminal";
|
|
30181
|
+
const entry = createTimelineEntry({
|
|
30182
|
+
messages: [],
|
|
30183
|
+
sessionId: state.backendSessionId,
|
|
30184
|
+
provider: state.provider
|
|
30185
|
+
});
|
|
30186
|
+
const fitted = fitResponsesToRow(entry, state.responses);
|
|
30187
|
+
if (!fitted) {
|
|
30188
|
+
diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
|
|
30189
|
+
return "terminal";
|
|
30190
|
+
}
|
|
30191
|
+
entry.agent_responses = fitted;
|
|
30192
|
+
const result = appendTrackedEntry(dir, entry, now());
|
|
30193
|
+
return handleTrackedResult(agentId, state, result);
|
|
30194
|
+
};
|
|
30195
|
+
const deleteState = (agentId, key) => {
|
|
30196
|
+
const states = statesFor(agentId);
|
|
30197
|
+
states.delete(key);
|
|
30198
|
+
if (activeTurnByAgent.get(agentId) === key)
|
|
30199
|
+
activeTurnByAgent.delete(agentId);
|
|
30200
|
+
if (states.size === 0)
|
|
30201
|
+
turnsByAgent.delete(agentId);
|
|
30202
|
+
};
|
|
30203
|
+
const retryPending = (agentId) => {
|
|
30204
|
+
const states = turnsByAgent.get(agentId);
|
|
30205
|
+
if (!states)
|
|
30206
|
+
return;
|
|
30207
|
+
const nowMs = now().getTime();
|
|
30208
|
+
for (const [key, state] of [...states]) {
|
|
30209
|
+
if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
|
|
30210
|
+
deleteState(agentId, key);
|
|
30211
|
+
continue;
|
|
30212
|
+
}
|
|
30213
|
+
if (!state.finalized || state.pendingSinceMs === undefined)
|
|
30214
|
+
continue;
|
|
30215
|
+
if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
|
|
30216
|
+
diagnostic(agentId, "timeline_pending_commit_expired");
|
|
30217
|
+
deleteState(agentId, key);
|
|
30218
|
+
continue;
|
|
30219
|
+
}
|
|
30220
|
+
const result = tryCommit(agentId, state);
|
|
30221
|
+
if (result === "written") {
|
|
30222
|
+
state.responses = [];
|
|
30223
|
+
state.pendingSinceMs = undefined;
|
|
30224
|
+
state.pendingMode = undefined;
|
|
30225
|
+
state.completedAtMs = nowMs;
|
|
30226
|
+
} else if (result === "terminal") {
|
|
30227
|
+
deleteState(agentId, key);
|
|
30228
|
+
}
|
|
30229
|
+
}
|
|
30230
|
+
const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
|
|
30231
|
+
while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
|
|
30232
|
+
const oldest = completed.shift();
|
|
30233
|
+
if (oldest)
|
|
30234
|
+
deleteState(agentId, oldest[0]);
|
|
30235
|
+
}
|
|
30236
|
+
};
|
|
30237
|
+
const retainPending = (agentId, key, state) => {
|
|
30238
|
+
const states = statesFor(agentId);
|
|
30239
|
+
const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
|
|
30240
|
+
if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
|
|
30241
|
+
diagnostic(agentId, "timeline_pending_commit_overflow");
|
|
30242
|
+
deleteState(agentId, key);
|
|
30243
|
+
return;
|
|
30244
|
+
}
|
|
30245
|
+
state.pendingSinceMs ??= now().getTime();
|
|
30246
|
+
state.pendingMode = state.handle ? "handle" : "fallback";
|
|
30247
|
+
};
|
|
30248
|
+
const appendOwnerless = (agentId, messages) => {
|
|
30249
|
+
if (messages.length === 0)
|
|
30250
|
+
return;
|
|
30251
|
+
const dir = dirFor(agentId);
|
|
30252
|
+
if (!prepareTimelineDirectory(dir))
|
|
30253
|
+
return;
|
|
30254
|
+
const result = appendTrackedEntry(dir, createTimelineEntry({
|
|
30255
|
+
messages,
|
|
30256
|
+
sessionId: sessionByAgent.get(agentId) ?? null,
|
|
30257
|
+
provider: opts.providerFor?.(agentId) ?? null
|
|
30258
|
+
}), now());
|
|
30259
|
+
handleTrackedResult(agentId, null, result);
|
|
30260
|
+
};
|
|
29283
30261
|
return {
|
|
29284
|
-
|
|
29285
|
-
|
|
30262
|
+
barrierGeneration(agentId) {
|
|
30263
|
+
return currentBarrier(agentId);
|
|
30264
|
+
},
|
|
30265
|
+
beginTurn(agentId, owner) {
|
|
30266
|
+
retryPending(agentId);
|
|
30267
|
+
const states = statesFor(agentId);
|
|
30268
|
+
if (owner.barrierGeneration !== currentBarrier(agentId)) {
|
|
30269
|
+
diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
|
|
30270
|
+
return;
|
|
30271
|
+
}
|
|
30272
|
+
const key = turnKey(agentId, owner);
|
|
30273
|
+
if (!states.has(key)) {
|
|
30274
|
+
states.set(key, {
|
|
30275
|
+
owner: { ...owner },
|
|
30276
|
+
provider: opts.providerFor?.(agentId) ?? null,
|
|
30277
|
+
backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
|
|
30278
|
+
responses: [],
|
|
30279
|
+
rowFenced: false,
|
|
30280
|
+
finalized: false
|
|
30281
|
+
});
|
|
30282
|
+
}
|
|
30283
|
+
activeTurnByAgent.set(agentId, key);
|
|
29286
30284
|
},
|
|
29287
|
-
|
|
30285
|
+
recordInboxPull(agentId, owner, messages) {
|
|
30286
|
+
retryPending(agentId);
|
|
29288
30287
|
if (messages.length === 0)
|
|
29289
30288
|
return;
|
|
30289
|
+
if (!owner) {
|
|
30290
|
+
appendOwnerless(agentId, messages);
|
|
30291
|
+
return;
|
|
30292
|
+
}
|
|
30293
|
+
const key = turnKey(agentId, owner);
|
|
30294
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30295
|
+
if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
|
|
30296
|
+
appendOwnerless(agentId, messages);
|
|
30297
|
+
return;
|
|
30298
|
+
}
|
|
29290
30299
|
const dir = dirFor(agentId);
|
|
29291
30300
|
if (!prepareTimelineDirectory(dir))
|
|
29292
30301
|
return;
|
|
29293
|
-
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
|
|
29297
|
-
|
|
30302
|
+
const stamp = now();
|
|
30303
|
+
let result;
|
|
30304
|
+
if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
|
|
30305
|
+
result = updateTrackedEntry(dir, state.handle, (entry) => ({
|
|
30306
|
+
...entry,
|
|
30307
|
+
messages: [...entry.messages, ...messages],
|
|
30308
|
+
session_id: state.backendSessionId,
|
|
30309
|
+
provider: state.provider
|
|
30310
|
+
}));
|
|
30311
|
+
} else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
|
|
30312
|
+
result = appendTrackedEntry(dir, createTimelineEntry({
|
|
30313
|
+
messages,
|
|
30314
|
+
sessionId: state.backendSessionId,
|
|
30315
|
+
provider: state.provider
|
|
30316
|
+
}), stamp);
|
|
30317
|
+
} else {
|
|
30318
|
+
appendOwnerless(agentId, messages);
|
|
30319
|
+
return;
|
|
30320
|
+
}
|
|
30321
|
+
handleTrackedResult(agentId, state, result);
|
|
29298
30322
|
},
|
|
29299
|
-
|
|
29300
|
-
|
|
29301
|
-
|
|
30323
|
+
recordAssistantMessage(agentId, owner, text2, truncated = false) {
|
|
30324
|
+
retryPending(agentId);
|
|
30325
|
+
const key = turnKey(agentId, owner);
|
|
30326
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30327
|
+
if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
|
|
30328
|
+
diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
|
|
29302
30329
|
return;
|
|
29303
|
-
|
|
29304
|
-
|
|
30330
|
+
}
|
|
30331
|
+
state.responses.push(boundedResponse(text2, truncated));
|
|
30332
|
+
if (state.responses.length > MAX_AGENT_RESPONSES) {
|
|
30333
|
+
state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
|
|
30334
|
+
}
|
|
30335
|
+
},
|
|
30336
|
+
finalizeTurn(agentId, owner) {
|
|
30337
|
+
retryPending(agentId);
|
|
30338
|
+
const key = turnKey(agentId, owner);
|
|
30339
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30340
|
+
if (!state || state.finalized || !sameOwner(state.owner, owner))
|
|
29305
30341
|
return;
|
|
29306
|
-
const
|
|
29307
|
-
|
|
29308
|
-
|
|
29309
|
-
|
|
29310
|
-
|
|
29311
|
-
|
|
29312
|
-
|
|
30342
|
+
const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
|
|
30343
|
+
state.finalized = true;
|
|
30344
|
+
activeTurnByAgent.delete(agentId);
|
|
30345
|
+
if (state.responses.length === 0) {
|
|
30346
|
+
state.completedAtMs = now().getTime();
|
|
30347
|
+
return;
|
|
30348
|
+
}
|
|
30349
|
+
if (!state.handle && !fallbackAuthorized) {
|
|
30350
|
+
diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
|
|
30351
|
+
deleteState(agentId, key);
|
|
30352
|
+
return;
|
|
30353
|
+
}
|
|
30354
|
+
const result = tryCommit(agentId, state);
|
|
30355
|
+
if (result === "written") {
|
|
30356
|
+
state.responses = [];
|
|
30357
|
+
state.completedAtMs = now().getTime();
|
|
30358
|
+
} else if (result === "terminal") {
|
|
30359
|
+
deleteState(agentId, key);
|
|
30360
|
+
} else {
|
|
30361
|
+
retainPending(agentId, key, state);
|
|
30362
|
+
}
|
|
30363
|
+
},
|
|
30364
|
+
fenceSession(agentId) {
|
|
30365
|
+
retryPending(agentId);
|
|
30366
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
30367
|
+
activeTurnByAgent.delete(agentId);
|
|
30368
|
+
const states = turnsByAgent.get(agentId);
|
|
30369
|
+
if (!states)
|
|
30370
|
+
return;
|
|
30371
|
+
for (const [key, state] of [...states]) {
|
|
30372
|
+
if (!state.handle) {
|
|
30373
|
+
diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
|
|
30374
|
+
deleteState(agentId, key);
|
|
30375
|
+
}
|
|
30376
|
+
}
|
|
30377
|
+
},
|
|
30378
|
+
setSession(agentId, sessionId, sessionInstanceId) {
|
|
30379
|
+
retryPending(agentId);
|
|
30380
|
+
const dir = dirFor(agentId);
|
|
30381
|
+
if (!prepareTimelineDirectory(dir))
|
|
30382
|
+
return false;
|
|
30383
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
30384
|
+
...state,
|
|
30385
|
+
fullBarrier: null,
|
|
30386
|
+
attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
|
|
30387
|
+
}));
|
|
30388
|
+
if (!persisted)
|
|
30389
|
+
return false;
|
|
30390
|
+
sessionByAgent.set(agentId, sessionId);
|
|
30391
|
+
if (sessionInstanceId) {
|
|
30392
|
+
sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
|
|
30393
|
+
for (const state of statesFor(agentId).values()) {
|
|
30394
|
+
if (state.owner.sessionInstanceId === sessionInstanceId)
|
|
30395
|
+
state.backendSessionId = sessionId;
|
|
30396
|
+
}
|
|
30397
|
+
}
|
|
30398
|
+
return true;
|
|
29313
30399
|
},
|
|
29314
30400
|
resumeSessionId(agentId, provider) {
|
|
29315
|
-
const
|
|
29316
|
-
return
|
|
30401
|
+
const resolution = resolveForAgent(agentId, provider);
|
|
30402
|
+
return resolution.kind === "session" ? resolution.sessionId : null;
|
|
30403
|
+
},
|
|
30404
|
+
resolveResumeSession(agentId, provider) {
|
|
30405
|
+
return resolveForAgent(agentId, provider);
|
|
30406
|
+
},
|
|
30407
|
+
recordSessionStall(agentId, sessionId) {
|
|
30408
|
+
return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
|
|
30409
|
+
},
|
|
30410
|
+
clearSessionStall(agentId, sessionId) {
|
|
30411
|
+
return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
|
|
29317
30412
|
},
|
|
29318
|
-
forgetSession(agentId, barrierType = "reset_session") {
|
|
30413
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
30414
|
+
retryPending(agentId);
|
|
29319
30415
|
const dir = dirFor(agentId);
|
|
29320
|
-
sessionByAgent.delete(agentId);
|
|
29321
30416
|
if (!prepareTimelineDirectory(dir))
|
|
29322
|
-
return;
|
|
30417
|
+
return false;
|
|
29323
30418
|
const stamp = now();
|
|
29324
|
-
|
|
30419
|
+
let persisted = false;
|
|
30420
|
+
if (barrierType === "reset_session" || barrierType === "nap") {
|
|
30421
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
30422
|
+
...state,
|
|
30423
|
+
attemptedSessionId: null,
|
|
30424
|
+
fencedSessionId: null,
|
|
30425
|
+
fullBarrier: barrierType
|
|
30426
|
+
}));
|
|
30427
|
+
} else if (barrierType === "stall_recovery") {
|
|
30428
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
30429
|
+
...state,
|
|
30430
|
+
attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
|
|
30431
|
+
fencedSessionId: forgottenSessionId ?? null,
|
|
30432
|
+
fullBarrier: null
|
|
30433
|
+
}));
|
|
30434
|
+
}
|
|
30435
|
+
if (!persisted)
|
|
30436
|
+
return false;
|
|
30437
|
+
sessionByAgent.delete(agentId);
|
|
30438
|
+
for (const key of [...sessionByEpoch.keys()]) {
|
|
30439
|
+
if (key.startsWith(`${agentId}\x00`))
|
|
30440
|
+
sessionByEpoch.delete(key);
|
|
30441
|
+
}
|
|
30442
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
30443
|
+
activeTurnByAgent.delete(agentId);
|
|
30444
|
+
const states = turnsByAgent.get(agentId);
|
|
30445
|
+
if (states) {
|
|
30446
|
+
for (const [key, state] of [...states]) {
|
|
30447
|
+
if (!state.handle) {
|
|
30448
|
+
diagnostic(agentId, "timeline_fallback_rejected", "barrier");
|
|
30449
|
+
deleteState(agentId, key);
|
|
30450
|
+
}
|
|
30451
|
+
}
|
|
30452
|
+
}
|
|
30453
|
+
const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
|
|
30454
|
+
handleTrackedResult(agentId, null, result);
|
|
30455
|
+
return true;
|
|
29325
30456
|
}
|
|
29326
30457
|
};
|
|
30458
|
+
function appendStallMarker(agentId, type, sessionId) {
|
|
30459
|
+
retryPending(agentId);
|
|
30460
|
+
const dir = dirFor(agentId);
|
|
30461
|
+
if (!prepareTimelineDirectory(dir))
|
|
30462
|
+
return false;
|
|
30463
|
+
const stamp = now();
|
|
30464
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
30465
|
+
...state,
|
|
30466
|
+
attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
|
|
30467
|
+
fullBarrier: null
|
|
30468
|
+
}));
|
|
30469
|
+
if (!persisted)
|
|
30470
|
+
return false;
|
|
30471
|
+
const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
|
|
30472
|
+
handleTrackedResult(agentId, null, result);
|
|
30473
|
+
return true;
|
|
30474
|
+
}
|
|
29327
30475
|
}
|
|
29328
30476
|
// src/daemon/diagnosticsCommand.ts
|
|
29329
30477
|
function reportUnavailable(options, reportId) {
|
|
@@ -29868,11 +31016,15 @@ async function createDaemon(opts) {
|
|
|
29868
31016
|
const typingTracker = createTypingScopeTracker();
|
|
29869
31017
|
const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
|
|
29870
31018
|
const proxy = await startCredentialProxy(broker, {
|
|
29871
|
-
onInboxPullStart: (agentId) =>
|
|
31019
|
+
onInboxPullStart: (agentId) => ({
|
|
31020
|
+
modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
|
|
31021
|
+
owner: managerRef?.timelineTurnOwner(agentId) ?? null
|
|
31022
|
+
}),
|
|
29872
31023
|
onInboxPullResponse: (agentId, messages, observationToken) => {
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
|
|
31024
|
+
const token = observationToken && typeof observationToken === "object" ? observationToken : null;
|
|
31025
|
+
timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
|
|
31026
|
+
if (typeof token?.modelSeenGeneration === "number") {
|
|
31027
|
+
channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
|
|
29876
31028
|
}
|
|
29877
31029
|
},
|
|
29878
31030
|
onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
|
|
@@ -29933,6 +31085,7 @@ async function createDaemon(opts) {
|
|
|
29933
31085
|
typingTracker.clear(agentId);
|
|
29934
31086
|
}
|
|
29935
31087
|
const botsById = new Map;
|
|
31088
|
+
let botCacheReady = false;
|
|
29936
31089
|
async function listMyBotsHttp() {
|
|
29937
31090
|
const res = await fetch(`${opts.serverUrl}/api/community/daemon/bots`, {
|
|
29938
31091
|
method: "GET",
|
|
@@ -29943,22 +31096,26 @@ async function createDaemon(opts) {
|
|
|
29943
31096
|
const json2 = await res.json();
|
|
29944
31097
|
return json2.bots ?? [];
|
|
29945
31098
|
}
|
|
31099
|
+
function replaceBotCache(bots) {
|
|
31100
|
+
botsById.clear();
|
|
31101
|
+
for (const b of bots) {
|
|
31102
|
+
botsById.set(b.id, {
|
|
31103
|
+
name: b.name,
|
|
31104
|
+
discriminator: b.discriminator,
|
|
31105
|
+
description: b.description,
|
|
31106
|
+
ownerName: b.ownerName,
|
|
31107
|
+
ownerDiscriminator: b.ownerDiscriminator
|
|
31108
|
+
});
|
|
31109
|
+
}
|
|
31110
|
+
botCacheReady = true;
|
|
31111
|
+
}
|
|
29946
31112
|
async function coldStartWarmup() {
|
|
29947
31113
|
const start = Date.now();
|
|
29948
31114
|
let attempt = 0;
|
|
29949
31115
|
while (Date.now() - start < WARMUP_CEILING_MS) {
|
|
29950
31116
|
try {
|
|
29951
31117
|
const bots = await listMyBotsHttp();
|
|
29952
|
-
|
|
29953
|
-
for (const b of bots) {
|
|
29954
|
-
botsById.set(b.id, {
|
|
29955
|
-
name: b.name,
|
|
29956
|
-
discriminator: b.discriminator,
|
|
29957
|
-
description: b.description,
|
|
29958
|
-
ownerName: b.ownerName,
|
|
29959
|
-
ownerDiscriminator: b.ownerDiscriminator
|
|
29960
|
-
});
|
|
29961
|
-
}
|
|
31118
|
+
replaceBotCache(bots);
|
|
29962
31119
|
log2.info("cold-start bot-cache warmup succeeded", { bots: bots.length, attempt });
|
|
29963
31120
|
return;
|
|
29964
31121
|
} catch {
|
|
@@ -30179,7 +31336,14 @@ async function createDaemon(opts) {
|
|
|
30179
31336
|
const statusPath = opts.statusFilePath;
|
|
30180
31337
|
const writeStatus = () => {
|
|
30181
31338
|
const nowMs = Date.now();
|
|
30182
|
-
writeStatusFile(statusPath, {
|
|
31339
|
+
writeStatusFile(statusPath, {
|
|
31340
|
+
writtenAt: nowMs,
|
|
31341
|
+
agentSummary: {
|
|
31342
|
+
total: botCacheReady ? botsById.size : null,
|
|
31343
|
+
running: manager.runningAgentCount()
|
|
31344
|
+
},
|
|
31345
|
+
agents: manager.statusProjection(nowMs)
|
|
31346
|
+
});
|
|
30183
31347
|
};
|
|
30184
31348
|
writeStatus();
|
|
30185
31349
|
statusTimer = setInterval(writeStatus, STATUS_WRITE_INTERVAL_MS);
|
|
@@ -30204,15 +31368,7 @@ async function createDaemon(opts) {
|
|
|
30204
31368
|
if (!botsById.has(agentId)) {
|
|
30205
31369
|
try {
|
|
30206
31370
|
const bots = await listMyBotsHttp();
|
|
30207
|
-
|
|
30208
|
-
botsById.set(b.id, {
|
|
30209
|
-
name: b.name,
|
|
30210
|
-
discriminator: b.discriminator,
|
|
30211
|
-
description: b.description,
|
|
30212
|
-
ownerName: b.ownerName,
|
|
30213
|
-
ownerDiscriminator: b.ownerDiscriminator
|
|
30214
|
-
});
|
|
30215
|
-
}
|
|
31371
|
+
replaceBotCache(bots);
|
|
30216
31372
|
} catch {}
|
|
30217
31373
|
}
|
|
30218
31374
|
if (!botsById.has(agentId)) {
|
|
@@ -30634,8 +31790,8 @@ function projectDaemonLogRow(value, targetAgentId) {
|
|
|
30634
31790
|
fields: projectedFields
|
|
30635
31791
|
};
|
|
30636
31792
|
}
|
|
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"];
|
|
31793
|
+
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"];
|
|
31794
|
+
var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "clear_stall_recovery", "force_exit", "gated_hold"];
|
|
30639
31795
|
var AGENT_STATUSES = ["idle", "starting", "running", "stopping"];
|
|
30640
31796
|
var DELIVERY_PHASES = ["idle", "admission_wait", "steering", "next_turn_queued", "compacting", "reviewing", "tool_wait", "working"];
|
|
30641
31797
|
var RESUME_OUTCOMES = ["not_requested", "pending", "resumed", "reset_required", "failed"];
|
|
@@ -30644,6 +31800,8 @@ var TERMINATION_CAUSES = ["runtime_error", "killed_stalled", "other"];
|
|
|
30644
31800
|
var SPAWN_FAILURE_REASONS = ["ENOENT", "handshake_timeout", "pre_handshake_exit", "spawn_threw", "other"];
|
|
30645
31801
|
var TERMINATION_SEMANTICS = ["killed_stalled", "idle_stop", "force_exit", "other"];
|
|
30646
31802
|
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"];
|
|
31803
|
+
var NATIVE_ACTIVITY_KINDS = ["turn_started", "backend_turn_started", "thinking", "text", "tool_call", "tool_output", "internal_progress", "recovery", "turn_end"];
|
|
31804
|
+
var RUNTIME_PHASES = ["idle", "admission", "inference", "tool", "recovery", "terminal"];
|
|
30647
31805
|
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
31806
|
function enumValue(value, values, bucket = false, maxChars = 64) {
|
|
30649
31807
|
if (!boundedString(value, maxChars))
|
|
@@ -30681,6 +31839,13 @@ function copyString(row, output, key, maxChars, optional2 = false, nullable2 = f
|
|
|
30681
31839
|
output[key] = row[key];
|
|
30682
31840
|
return true;
|
|
30683
31841
|
}
|
|
31842
|
+
function copyScrubbedString(row, output, key, maxChars, optional2 = false, nullable2 = false) {
|
|
31843
|
+
if (!copyString(row, output, key, maxChars, optional2, nullable2))
|
|
31844
|
+
return false;
|
|
31845
|
+
if (typeof output[key] === "string")
|
|
31846
|
+
output[key] = scrubDiagnosticText(output[key]);
|
|
31847
|
+
return true;
|
|
31848
|
+
}
|
|
30684
31849
|
function projectTraceBase(row, targetAgentId) {
|
|
30685
31850
|
if (row.agentId !== targetAgentId || !boundedString(row.agentId, 128) || !canonicalTime(row.timeIso))
|
|
30686
31851
|
return null;
|
|
@@ -30707,11 +31872,29 @@ function projectFsm(row, targetAgentId) {
|
|
|
30707
31872
|
Object.assign(output, { event, status, turnActive: row.turnActive });
|
|
30708
31873
|
if (!copyInteger(row, output, "inbox") || !copyInteger(row, output, "lastDeliverAt", { nullable: true }) || !copyInteger(row, output, "lastProgressAt") || !copyInteger(row, output, "idleSince", { nullable: true }))
|
|
30709
31874
|
return null;
|
|
31875
|
+
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 }))
|
|
31876
|
+
return null;
|
|
31877
|
+
if ("lastNativeActivityKind" in row && row.lastNativeActivityKind !== undefined) {
|
|
31878
|
+
if (row.lastNativeActivityKind === null)
|
|
31879
|
+
output.lastNativeActivityKind = null;
|
|
31880
|
+
else {
|
|
31881
|
+
const kind = enumValue(row.lastNativeActivityKind, NATIVE_ACTIVITY_KINDS);
|
|
31882
|
+
if (!kind)
|
|
31883
|
+
return null;
|
|
31884
|
+
output.lastNativeActivityKind = kind;
|
|
31885
|
+
}
|
|
31886
|
+
}
|
|
31887
|
+
if ("runtimePhase" in row && row.runtimePhase !== undefined) {
|
|
31888
|
+
const phase = enumValue(row.runtimePhase, RUNTIME_PHASES);
|
|
31889
|
+
if (!phase)
|
|
31890
|
+
return null;
|
|
31891
|
+
output.runtimePhase = phase;
|
|
31892
|
+
}
|
|
30710
31893
|
output.resetting = row.resetting;
|
|
30711
31894
|
if (!copyInteger(row, output, "resettingSince", { nullable: true }) || !copyInteger(row, output, "stoppingSince", { nullable: true }))
|
|
30712
31895
|
return null;
|
|
30713
31896
|
output.deliveryPhase = deliveryPhase;
|
|
30714
|
-
if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
|
|
31897
|
+
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
31898
|
return null;
|
|
30716
31899
|
const metricKeys = [
|
|
30717
31900
|
"physicalOpenCount",
|
|
@@ -30842,7 +32025,7 @@ function projectStatusRow(value, targetAgentId) {
|
|
|
30842
32025
|
return output;
|
|
30843
32026
|
}
|
|
30844
32027
|
// src/diagnostics/bundle.ts
|
|
30845
|
-
import { createHash as
|
|
32028
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
30846
32029
|
import { createWriteStream, unlinkSync as unlinkSync5 } from "node:fs";
|
|
30847
32030
|
import { Readable, Transform } from "node:stream";
|
|
30848
32031
|
import { pipeline } from "node:stream/promises";
|
|
@@ -30992,7 +32175,7 @@ async function buildDiagnosticBundle(args) {
|
|
|
30992
32175
|
const { footer, total } = envelope();
|
|
30993
32176
|
if (total > maxUncompressed)
|
|
30994
32177
|
throw new BundleTooLargeError;
|
|
30995
|
-
const hash2 =
|
|
32178
|
+
const hash2 = createHash3("sha256");
|
|
30996
32179
|
let compressedBytes = 0;
|
|
30997
32180
|
const meter = new Transform({
|
|
30998
32181
|
transform(chunk2, _encoding, callback) {
|
|
@@ -31033,7 +32216,7 @@ async function buildDiagnosticBundle(args) {
|
|
|
31033
32216
|
};
|
|
31034
32217
|
}
|
|
31035
32218
|
// src/diagnostics/coordinator.ts
|
|
31036
|
-
import { createHash as
|
|
32219
|
+
import { createHash as createHash4, randomBytes as randomBytes5 } from "node:crypto";
|
|
31037
32220
|
import {
|
|
31038
32221
|
chmodSync as chmodSync2,
|
|
31039
32222
|
closeSync as closeSync4,
|
|
@@ -31189,7 +32372,7 @@ function createDiagnosticReportCoordinator(args) {
|
|
|
31189
32372
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
31190
32373
|
return null;
|
|
31191
32374
|
const bytes = readFileSync6(path11);
|
|
31192
|
-
return { sizeBytes: bytes.byteLength, sha256:
|
|
32375
|
+
return { sizeBytes: bytes.byteLength, sha256: createHash4("sha256").update(bytes).digest("hex") };
|
|
31193
32376
|
} catch {
|
|
31194
32377
|
return null;
|
|
31195
32378
|
}
|
|
@@ -32501,8 +33684,13 @@ function daemonList(opts) {
|
|
|
32501
33684
|
if (alive && statusPath) {
|
|
32502
33685
|
const s = daemonStatusFromFile(statusPath, now);
|
|
32503
33686
|
if (s.found) {
|
|
32504
|
-
|
|
32505
|
-
|
|
33687
|
+
if (s.agentSummary) {
|
|
33688
|
+
agents = s.agentSummary.total;
|
|
33689
|
+
running = s.agentSummary.running;
|
|
33690
|
+
} else {
|
|
33691
|
+
agents = s.agents.length;
|
|
33692
|
+
running = s.agents.filter((a) => a.derivedActivity === "running").length;
|
|
33693
|
+
}
|
|
32506
33694
|
lastActiveMs = daemonLastActiveMs(s, now);
|
|
32507
33695
|
}
|
|
32508
33696
|
}
|
|
@@ -32517,7 +33705,24 @@ function daemonList(opts) {
|
|
|
32517
33705
|
return results;
|
|
32518
33706
|
}
|
|
32519
33707
|
var STATUS_STALE_MS = 20000;
|
|
32520
|
-
var MISSING_STATUS = {
|
|
33708
|
+
var MISSING_STATUS = {
|
|
33709
|
+
found: false,
|
|
33710
|
+
ageMs: null,
|
|
33711
|
+
freshness: "missing",
|
|
33712
|
+
writtenAt: null,
|
|
33713
|
+
agentSummary: null,
|
|
33714
|
+
agents: []
|
|
33715
|
+
};
|
|
33716
|
+
function validAgentSummary(value) {
|
|
33717
|
+
if (!value || typeof value !== "object")
|
|
33718
|
+
return null;
|
|
33719
|
+
const summary = value;
|
|
33720
|
+
const validTotal = summary.total === null || Number.isInteger(summary.total) && summary.total >= 0;
|
|
33721
|
+
const validRunning = Number.isInteger(summary.running) && summary.running >= 0;
|
|
33722
|
+
if (!validTotal || !validRunning)
|
|
33723
|
+
return null;
|
|
33724
|
+
return { total: summary.total, running: summary.running };
|
|
33725
|
+
}
|
|
32521
33726
|
function daemonStatusFromFile(statusPath, nowMs) {
|
|
32522
33727
|
if (!fs12.existsSync(statusPath))
|
|
32523
33728
|
return MISSING_STATUS;
|
|
@@ -32529,6 +33734,7 @@ function daemonStatusFromFile(statusPath, nowMs) {
|
|
|
32529
33734
|
ageMs,
|
|
32530
33735
|
freshness: ageMs <= STATUS_STALE_MS ? "fresh" : "stale",
|
|
32531
33736
|
writtenAt: snap.writtenAt,
|
|
33737
|
+
agentSummary: validAgentSummary(snap.agentSummary),
|
|
32532
33738
|
agents: Array.isArray(snap.agents) ? snap.agents : []
|
|
32533
33739
|
};
|
|
32534
33740
|
} catch {
|