@alook/daemon 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.js +1589 -322
- package/dist/index.js +1523 -298
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -17792,7 +17792,9 @@ var communityChannel = sqliteTable("community_channel", {
|
|
|
17792
17792
|
creatorId: text("creator_id").references(() => user.id, { onDelete: "set null" }),
|
|
17793
17793
|
messageCount: integer2("message_count").default(0),
|
|
17794
17794
|
archived: integer2("archived").default(0),
|
|
17795
|
-
parentMessageId: text("parent_message_id"),
|
|
17795
|
+
parentMessageId: text("parent_message_id").references(() => communityMessage.id, {
|
|
17796
|
+
onDelete: "cascade"
|
|
17797
|
+
}),
|
|
17796
17798
|
lastMessageAt: text("last_message_at"),
|
|
17797
17799
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
17798
17800
|
}, (t) => [
|
|
@@ -18358,8 +18360,9 @@ var communityChannelDeleteSchema = exports_external.strictObject({
|
|
|
18358
18360
|
type: exports_external.literal("community:channel.delete"),
|
|
18359
18361
|
serverId: string4,
|
|
18360
18362
|
channelId: string4,
|
|
18361
|
-
parentChannelId: nullableString.optional()
|
|
18362
|
-
|
|
18363
|
+
parentChannelId: nullableString.optional(),
|
|
18364
|
+
parentMessageId: string4.optional()
|
|
18365
|
+
}).refine((event) => event.parentMessageId === undefined || typeof event.parentChannelId === "string" && event.parentChannelId.length > 0, { message: "parentMessageId requires parentChannelId" });
|
|
18363
18366
|
var positionedIdSchema = exports_external.strictObject({ id: string4, position: exports_external.number() });
|
|
18364
18367
|
var communityChannelReorderSchema = exports_external.strictObject({
|
|
18365
18368
|
type: exports_external.literal("community:channel.reorder"),
|
|
@@ -20285,19 +20288,23 @@ class ClaudeEventNormalizer {
|
|
|
20285
20288
|
const content = event?.message?.content;
|
|
20286
20289
|
if (!Array.isArray(content))
|
|
20287
20290
|
return;
|
|
20291
|
+
const completedText = [];
|
|
20288
20292
|
for (const block of content) {
|
|
20289
20293
|
if (block?.type === "thinking") {
|
|
20290
|
-
out.push({ kind: "
|
|
20294
|
+
out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
|
|
20291
20295
|
} else if (block?.type === "text") {
|
|
20292
20296
|
const text2 = block.text ?? "";
|
|
20293
20297
|
if (API_ERROR_RE.test(text2))
|
|
20294
20298
|
out.push({ kind: "error", message: text2 });
|
|
20295
20299
|
else
|
|
20296
|
-
|
|
20300
|
+
completedText.push(text2);
|
|
20297
20301
|
} else if (block?.type === "tool_use") {
|
|
20298
20302
|
out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
|
|
20299
20303
|
}
|
|
20300
20304
|
}
|
|
20305
|
+
if (completedText.length > 0) {
|
|
20306
|
+
out.push({ kind: "assistant_message_completed", text: completedText.join("") });
|
|
20307
|
+
}
|
|
20301
20308
|
}
|
|
20302
20309
|
handleUser(event, out) {
|
|
20303
20310
|
if (event.isReplay === true && typeof event.uuid === "string") {
|
|
@@ -20706,14 +20713,17 @@ class CodexEventNormalizer {
|
|
|
20706
20713
|
this.turnId = params.turn.id;
|
|
20707
20714
|
this.terminalTurn = null;
|
|
20708
20715
|
return [
|
|
20709
|
-
{
|
|
20710
|
-
|
|
20716
|
+
{
|
|
20717
|
+
kind: "turn_owner",
|
|
20718
|
+
receipt: this.turnReceipt(params.threadId, params.turn.id),
|
|
20719
|
+
nativeTurnId: params.turn.id
|
|
20720
|
+
}
|
|
20711
20721
|
];
|
|
20712
20722
|
case "item/reasoning/textDelta":
|
|
20713
20723
|
case "item/reasoning/summaryTextDelta":
|
|
20714
|
-
return [{ kind: "
|
|
20724
|
+
return [{ kind: "assistant_reasoning_delta", text: params?.delta ?? "" }];
|
|
20715
20725
|
case "item/agentMessage/delta":
|
|
20716
|
-
return [{ kind: "
|
|
20726
|
+
return [{ kind: "assistant_message_delta", text: params?.delta ?? "" }];
|
|
20717
20727
|
case "item/started":
|
|
20718
20728
|
return this.handleItemStarted(params);
|
|
20719
20729
|
case "item/completed":
|
|
@@ -20741,7 +20751,10 @@ class CodexEventNormalizer {
|
|
|
20741
20751
|
}
|
|
20742
20752
|
return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
20743
20753
|
case "error":
|
|
20744
|
-
|
|
20754
|
+
if (params?.willRetry === true) {
|
|
20755
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
|
|
20756
|
+
}
|
|
20757
|
+
return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
|
|
20745
20758
|
case "thread/tokenUsage/updated":
|
|
20746
20759
|
case "account/rateLimits/updated":
|
|
20747
20760
|
return mapCodexTelemetry(method, params);
|
|
@@ -20834,9 +20847,9 @@ class CodexEventNormalizer {
|
|
|
20834
20847
|
case "collabAgentToolCall":
|
|
20835
20848
|
return [{ kind: "tool_output", name: "collab_tool_call" }];
|
|
20836
20849
|
case "agentMessage":
|
|
20837
|
-
return [{ kind: "
|
|
20850
|
+
return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
|
|
20838
20851
|
case "reasoning":
|
|
20839
|
-
return [{ kind: "
|
|
20852
|
+
return [{ kind: "assistant_reasoning_completed", text: params?.item?.text ?? "" }];
|
|
20840
20853
|
default:
|
|
20841
20854
|
return [];
|
|
20842
20855
|
}
|
|
@@ -20869,7 +20882,13 @@ class CodexDriver {
|
|
|
20869
20882
|
lifetime: "session",
|
|
20870
20883
|
transport: { kind: "stdio_rpc", protocol: "codex.app-server.v1" },
|
|
20871
20884
|
wakeStart: "immediate",
|
|
20872
|
-
terminalOwnership: "transport_request"
|
|
20885
|
+
terminalOwnership: "transport_request",
|
|
20886
|
+
turnSilence: {
|
|
20887
|
+
nativeIdleTimeoutMs: 300000,
|
|
20888
|
+
daemonGraceMs: 60000,
|
|
20889
|
+
recoveryGraceMs: 60000,
|
|
20890
|
+
maxRecoveryExtensions: 1
|
|
20891
|
+
}
|
|
20873
20892
|
};
|
|
20874
20893
|
eventNormalizer = new CodexEventNormalizer;
|
|
20875
20894
|
requestId = 0;
|
|
@@ -21526,14 +21545,14 @@ class CursorAcpLane {
|
|
|
21526
21545
|
case "agent_message_chunk": {
|
|
21527
21546
|
const content = record2(update.content);
|
|
21528
21547
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
21529
|
-
this.events.emit("runtime_event", { kind: "
|
|
21548
|
+
this.events.emit("runtime_event", { kind: "assistant_message_delta", text: content.text });
|
|
21530
21549
|
}
|
|
21531
21550
|
return;
|
|
21532
21551
|
}
|
|
21533
21552
|
case "agent_thought_chunk": {
|
|
21534
21553
|
const content = record2(update.content);
|
|
21535
21554
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
21536
|
-
this.events.emit("runtime_event", { kind: "
|
|
21555
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_delta", text: content.text });
|
|
21537
21556
|
}
|
|
21538
21557
|
return;
|
|
21539
21558
|
}
|
|
@@ -22338,16 +22357,20 @@ class OpenCodeServiceLane {
|
|
|
22338
22357
|
}
|
|
22339
22358
|
switch (event.type) {
|
|
22340
22359
|
case "session.next.step.started":
|
|
22341
|
-
this.events.emit("runtime_event", {
|
|
22360
|
+
this.events.emit("runtime_event", {
|
|
22361
|
+
kind: "internal_progress",
|
|
22362
|
+
source: "opencode.service",
|
|
22363
|
+
itemType: "step_started"
|
|
22364
|
+
});
|
|
22342
22365
|
break;
|
|
22343
22366
|
case "session.next.text.ended":
|
|
22344
22367
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
22345
|
-
this.events.emit("runtime_event", { kind: "
|
|
22368
|
+
this.events.emit("runtime_event", { kind: "assistant_message_completed", text: data.text });
|
|
22346
22369
|
}
|
|
22347
22370
|
break;
|
|
22348
22371
|
case "session.next.reasoning.ended":
|
|
22349
22372
|
if (typeof data.text === "string" && data.text.length > 0) {
|
|
22350
|
-
this.events.emit("runtime_event", { kind: "
|
|
22373
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: data.text });
|
|
22351
22374
|
}
|
|
22352
22375
|
break;
|
|
22353
22376
|
case "session.next.tool.called":
|
|
@@ -23098,22 +23121,28 @@ function readPiSdkVersion() {
|
|
|
23098
23121
|
}
|
|
23099
23122
|
function mapPiSdkEvent(event, sessionId, state) {
|
|
23100
23123
|
if (event?.type === "message_update") {
|
|
23101
|
-
const d = event.
|
|
23124
|
+
const d = event.assistantMessageEvent ?? {};
|
|
23102
23125
|
switch (d.type) {
|
|
23103
23126
|
case "thinking_delta":
|
|
23104
|
-
return [{ kind: "
|
|
23127
|
+
return [{ kind: "assistant_reasoning_delta", text: d.delta ?? "" }];
|
|
23105
23128
|
case "text_delta":
|
|
23106
23129
|
state.sawTextDelta = true;
|
|
23107
|
-
return [{ kind: "
|
|
23108
|
-
case "text_end":
|
|
23109
|
-
|
|
23130
|
+
return [{ kind: "assistant_message_delta", text: d.delta ?? "" }];
|
|
23131
|
+
case "text_end": {
|
|
23132
|
+
state.sawTextDelta = false;
|
|
23133
|
+
return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
|
|
23134
|
+
}
|
|
23110
23135
|
case "error":
|
|
23111
|
-
return [{ kind: "error", message: d.
|
|
23136
|
+
return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
|
|
23112
23137
|
default:
|
|
23113
23138
|
return [];
|
|
23114
23139
|
}
|
|
23115
23140
|
}
|
|
23116
23141
|
switch (event?.type) {
|
|
23142
|
+
case "auto_retry_start":
|
|
23143
|
+
return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
|
|
23144
|
+
case "auto_retry_end":
|
|
23145
|
+
return [{ kind: "runtime_recovery", stage: "recovered", source: "pi_auto_retry" }];
|
|
23117
23146
|
case "tool_execution_start":
|
|
23118
23147
|
return [{ kind: "tool_call", name: event.toolName ?? "unknown_tool", input: event.args ?? {} }];
|
|
23119
23148
|
case "tool_execution_end":
|
|
@@ -23278,6 +23307,13 @@ function assertAdapterCompatibility(registrationId, registeredCapabilities, adap
|
|
|
23278
23307
|
if (!transport || typeof transport.kind !== "string" || transport.kind.trim().length === 0 || typeof transport.protocol !== "string" || transport.protocol.trim().length === 0 || transport.metadata !== undefined && (!transport.metadata || typeof transport.metadata !== "object" || Array.isArray(transport.metadata))) {
|
|
23279
23308
|
throw new Error(`Adapter ${registrationId} has an invalid transport declaration`);
|
|
23280
23309
|
}
|
|
23310
|
+
if (execution.turnSilence !== undefined) {
|
|
23311
|
+
const silence = execution.turnSilence;
|
|
23312
|
+
const safeInteger = (value, minimum) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
|
|
23313
|
+
if (!silence || typeof silence !== "object" || Array.isArray(silence) || !safeInteger(silence.nativeIdleTimeoutMs, 1) || !safeInteger(silence.daemonGraceMs, 0) || !safeInteger(silence.recoveryGraceMs, 0) || !safeInteger(silence.maxRecoveryExtensions, 0) || !Number.isSafeInteger(silence.nativeIdleTimeoutMs + silence.daemonGraceMs)) {
|
|
23314
|
+
throw new Error(`Adapter ${registrationId} has an invalid turnSilence declaration`);
|
|
23315
|
+
}
|
|
23316
|
+
}
|
|
23281
23317
|
const capabilities = registeredCapabilities;
|
|
23282
23318
|
const declaredLifetime = capabilities?.sessionLifetime === "persistent" ? "session" : "turn";
|
|
23283
23319
|
if (execution.lifetime !== declaredLifetime) {
|
|
@@ -23367,6 +23403,12 @@ function capabilitiesFor(backend) {
|
|
|
23367
23403
|
return builtinRegistry.get(backend).capabilities;
|
|
23368
23404
|
}
|
|
23369
23405
|
|
|
23406
|
+
// agent-driver/dist/internal/adapter.js
|
|
23407
|
+
var DEFAULT_NATIVE_IDLE_TIMEOUT_MS = 300000;
|
|
23408
|
+
var DEFAULT_DAEMON_GRACE_MS = 60000;
|
|
23409
|
+
var DEFAULT_RECOVERY_GRACE_MS = 60000;
|
|
23410
|
+
var DEFAULT_MAX_RECOVERY_EXTENSIONS = 1;
|
|
23411
|
+
|
|
23370
23412
|
// agent-driver/dist/controller/event-queue.js
|
|
23371
23413
|
var MAX_BUFFERED_BYTES = 4194304;
|
|
23372
23414
|
|
|
@@ -23563,6 +23605,62 @@ function stableErrorCode(value, fallback) {
|
|
|
23563
23605
|
|
|
23564
23606
|
// agent-driver/dist/controller/logical-session.js
|
|
23565
23607
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
23608
|
+
var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
|
|
23609
|
+
var WORK_HEARTBEAT_MIN_INTERVAL_MS = 1000;
|
|
23610
|
+
function emptySemanticAssembler() {
|
|
23611
|
+
return { chunks: [], bytes: 0, truncated: false };
|
|
23612
|
+
}
|
|
23613
|
+
function utf8Prefix(text2, maxBytes) {
|
|
23614
|
+
if (maxBytes <= 0 || text2.length === 0)
|
|
23615
|
+
return "";
|
|
23616
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
23617
|
+
return text2;
|
|
23618
|
+
let low = 0;
|
|
23619
|
+
let high = text2.length;
|
|
23620
|
+
while (low < high) {
|
|
23621
|
+
const mid = Math.ceil((low + high) / 2);
|
|
23622
|
+
let end2 = mid;
|
|
23623
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
23624
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
23625
|
+
end2 -= 1;
|
|
23626
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
23627
|
+
low = mid;
|
|
23628
|
+
else
|
|
23629
|
+
high = mid - 1;
|
|
23630
|
+
}
|
|
23631
|
+
let end = low;
|
|
23632
|
+
const code = text2.charCodeAt(end - 1);
|
|
23633
|
+
if (code >= 55296 && code <= 56319)
|
|
23634
|
+
end -= 1;
|
|
23635
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
23636
|
+
end -= 1;
|
|
23637
|
+
return text2.slice(0, end);
|
|
23638
|
+
}
|
|
23639
|
+
function appendSemanticFragment(buffer, text2) {
|
|
23640
|
+
if (text2.length === 0 || buffer.truncated)
|
|
23641
|
+
return;
|
|
23642
|
+
const remaining = SEMANTIC_ASSEMBLER_MAX_BYTES - buffer.bytes;
|
|
23643
|
+
const bytes = Buffer.byteLength(text2, "utf8");
|
|
23644
|
+
if (bytes <= remaining) {
|
|
23645
|
+
buffer.chunks.push(text2);
|
|
23646
|
+
buffer.bytes += bytes;
|
|
23647
|
+
return;
|
|
23648
|
+
}
|
|
23649
|
+
const prefix = utf8Prefix(text2, remaining);
|
|
23650
|
+
if (prefix.length > 0) {
|
|
23651
|
+
buffer.chunks.push(prefix);
|
|
23652
|
+
buffer.bytes += Buffer.byteLength(prefix, "utf8");
|
|
23653
|
+
}
|
|
23654
|
+
buffer.truncated = true;
|
|
23655
|
+
}
|
|
23656
|
+
function finishSemanticAssembler(buffer) {
|
|
23657
|
+
return { text: buffer.chunks.join(""), truncated: buffer.truncated };
|
|
23658
|
+
}
|
|
23659
|
+
function boundedSemanticCompletion(text2) {
|
|
23660
|
+
const buffer = emptySemanticAssembler();
|
|
23661
|
+
appendSemanticFragment(buffer, text2);
|
|
23662
|
+
return finishSemanticAssembler(buffer);
|
|
23663
|
+
}
|
|
23566
23664
|
function driverError(category, code, message2, retryable = false) {
|
|
23567
23665
|
return { category, code, message: scrubDriverErrorMessage(message2), retryable };
|
|
23568
23666
|
}
|
|
@@ -23636,6 +23734,7 @@ class LogicalAgentSession {
|
|
|
23636
23734
|
resumeOutcome;
|
|
23637
23735
|
eventQueue;
|
|
23638
23736
|
behavior;
|
|
23737
|
+
turnSilence;
|
|
23639
23738
|
constructor(backend, config2, launch, adapter, capabilities2, host, prepared, hostReleaseTimeoutMs) {
|
|
23640
23739
|
this.backend = backend;
|
|
23641
23740
|
this.config = config2;
|
|
@@ -23646,6 +23745,18 @@ class LogicalAgentSession {
|
|
|
23646
23745
|
this.hostReleaseTimeoutMs = hostReleaseTimeoutMs;
|
|
23647
23746
|
this.capabilities = capabilities2;
|
|
23648
23747
|
this.behavior = this.capabilities;
|
|
23748
|
+
const declaredSilence = adapter.execution.turnSilence;
|
|
23749
|
+
const nativeIdleTimeoutMs = declaredSilence?.nativeIdleTimeoutMs ?? DEFAULT_NATIVE_IDLE_TIMEOUT_MS;
|
|
23750
|
+
const daemonGraceMs = declaredSilence?.daemonGraceMs ?? DEFAULT_DAEMON_GRACE_MS;
|
|
23751
|
+
const recoveryGraceMs = declaredSilence?.recoveryGraceMs ?? DEFAULT_RECOVERY_GRACE_MS;
|
|
23752
|
+
const maxRecoveryExtensions = declaredSilence?.maxRecoveryExtensions ?? DEFAULT_MAX_RECOVERY_EXTENSIONS;
|
|
23753
|
+
this.turnSilence = {
|
|
23754
|
+
nativeIdleTimeoutMs,
|
|
23755
|
+
daemonGraceMs,
|
|
23756
|
+
recoveryGraceMs,
|
|
23757
|
+
maxRecoveryExtensions,
|
|
23758
|
+
normalBudgetMs: nativeIdleTimeoutMs + daemonGraceMs
|
|
23759
|
+
};
|
|
23649
23760
|
this.resumeOutcome = launch.resumeSessionId ? "pending" : "not_requested";
|
|
23650
23761
|
this.sessionInstanceId = host.createId();
|
|
23651
23762
|
this.closed = new Promise((resolve3) => {
|
|
@@ -23750,6 +23861,7 @@ class LogicalAgentSession {
|
|
|
23750
23861
|
lastEventSequence: this.eventSequence,
|
|
23751
23862
|
diagnostics: {
|
|
23752
23863
|
deliveryPhase: this.deliveryPhase(),
|
|
23864
|
+
turnSilence: this.turnSilence,
|
|
23753
23865
|
metrics: {
|
|
23754
23866
|
physicalOpenCount: this.metricValue(this.physicalOpenCount),
|
|
23755
23867
|
turnCount: this.metricValue(this.turnCount),
|
|
@@ -23840,7 +23952,14 @@ class LogicalAgentSession {
|
|
|
23840
23952
|
const turnId = this.host.createId();
|
|
23841
23953
|
const commandIds = messages.map((message2) => message2.id);
|
|
23842
23954
|
const terminalOwner = this.adapter.beginTurn?.();
|
|
23843
|
-
this.activeTurn = {
|
|
23955
|
+
this.activeTurn = {
|
|
23956
|
+
turnId,
|
|
23957
|
+
commandIds: [...commandIds],
|
|
23958
|
+
...terminalOwner ? { terminalOwner } : {},
|
|
23959
|
+
pendingMessage: emptySemanticAssembler(),
|
|
23960
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
23961
|
+
lastWorkHeartbeatAt: null
|
|
23962
|
+
};
|
|
23844
23963
|
this.turnError = undefined;
|
|
23845
23964
|
this.interruptedTurnId = undefined;
|
|
23846
23965
|
this.processTurnEnded = false;
|
|
@@ -24038,14 +24157,36 @@ class LogicalAgentSession {
|
|
|
24038
24157
|
return;
|
|
24039
24158
|
}
|
|
24040
24159
|
this.activeTurn.terminalOwner = event.receipt;
|
|
24160
|
+
const nativeTurnId = event.nativeTurnId?.trim();
|
|
24161
|
+
if (nativeTurnId && nativeTurnId.length <= 512 && /^[A-Za-z0-9._:-]+$/.test(nativeTurnId)) {
|
|
24162
|
+
this.emit({
|
|
24163
|
+
type: "backend_turn_started",
|
|
24164
|
+
turnId: this.activeTurn.turnId,
|
|
24165
|
+
backendTurnId: nativeTurnId
|
|
24166
|
+
});
|
|
24167
|
+
}
|
|
24041
24168
|
return;
|
|
24042
|
-
case "
|
|
24043
|
-
|
|
24044
|
-
|
|
24169
|
+
case "assistant_reasoning_delta":
|
|
24170
|
+
case "assistant_message_delta":
|
|
24171
|
+
if (turnId && this.activeTurn?.turnId === turnId && event.text.length > 0) {
|
|
24172
|
+
appendSemanticFragment(event.kind === "assistant_message_delta" ? this.activeTurn.pendingMessage : this.activeTurn.pendingReasoning, event.text);
|
|
24173
|
+
const now = this.host.now();
|
|
24174
|
+
if (this.activeTurn.lastWorkHeartbeatAt === null || now - this.activeTurn.lastWorkHeartbeatAt >= WORK_HEARTBEAT_MIN_INTERVAL_MS) {
|
|
24175
|
+
this.activeTurn.lastWorkHeartbeatAt = now;
|
|
24176
|
+
this.emit({ type: "work_heartbeat", turnId });
|
|
24177
|
+
}
|
|
24178
|
+
}
|
|
24045
24179
|
return;
|
|
24046
|
-
case "
|
|
24047
|
-
|
|
24048
|
-
|
|
24180
|
+
case "assistant_reasoning_completed":
|
|
24181
|
+
case "assistant_message_completed":
|
|
24182
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
24183
|
+
const field = event.kind === "assistant_message_completed" ? "pendingMessage" : "pendingReasoning";
|
|
24184
|
+
this.activeTurn[field] = emptySemanticAssembler();
|
|
24185
|
+
if (event.text.length > 0) {
|
|
24186
|
+
const completed = boundedSemanticCompletion(event.text);
|
|
24187
|
+
this.emit({ type: event.kind, turnId, ...completed });
|
|
24188
|
+
}
|
|
24189
|
+
}
|
|
24049
24190
|
return;
|
|
24050
24191
|
case "tool_call":
|
|
24051
24192
|
this.outstandingToolUses += 1;
|
|
@@ -24088,9 +24229,19 @@ class LogicalAgentSession {
|
|
|
24088
24229
|
message: scrubDriverErrorMessage(event.message, "Runtime diagnostic")
|
|
24089
24230
|
});
|
|
24090
24231
|
return;
|
|
24232
|
+
case "runtime_recovery":
|
|
24233
|
+
this.emit({
|
|
24234
|
+
type: "recovery",
|
|
24235
|
+
turnId,
|
|
24236
|
+
stage: event.stage,
|
|
24237
|
+
source: event.source
|
|
24238
|
+
});
|
|
24239
|
+
return;
|
|
24091
24240
|
case "runtime_metric":
|
|
24092
|
-
if (event.name === "sse_reconnect" && event.increment === 1)
|
|
24241
|
+
if (event.name === "sse_reconnect" && event.increment === 1) {
|
|
24093
24242
|
this.sseReconnectCount += 1;
|
|
24243
|
+
this.emit({ type: "recovery", turnId, stage: "retrying", source: "transport_reconnect" });
|
|
24244
|
+
}
|
|
24094
24245
|
return;
|
|
24095
24246
|
case "telemetry": {
|
|
24096
24247
|
const details = jsonValue(event.attrs);
|
|
@@ -24113,6 +24264,18 @@ class LogicalAgentSession {
|
|
|
24113
24264
|
});
|
|
24114
24265
|
return;
|
|
24115
24266
|
case "turn_end":
|
|
24267
|
+
if (turnId && this.activeTurn?.turnId === turnId) {
|
|
24268
|
+
const reasoning = finishSemanticAssembler(this.activeTurn.pendingReasoning);
|
|
24269
|
+
const message2 = finishSemanticAssembler(this.activeTurn.pendingMessage);
|
|
24270
|
+
this.activeTurn.pendingReasoning = emptySemanticAssembler();
|
|
24271
|
+
this.activeTurn.pendingMessage = emptySemanticAssembler();
|
|
24272
|
+
if (reasoning.text.length > 0) {
|
|
24273
|
+
this.emit({ type: "assistant_reasoning_completed", turnId, ...reasoning });
|
|
24274
|
+
}
|
|
24275
|
+
if (message2.text.length > 0) {
|
|
24276
|
+
this.emit({ type: "assistant_message_completed", turnId, ...message2 });
|
|
24277
|
+
}
|
|
24278
|
+
}
|
|
24116
24279
|
this.completeTurn(event.sessionId, physicalOwner, generation, event.turnOwner);
|
|
24117
24280
|
return;
|
|
24118
24281
|
}
|
|
@@ -24126,7 +24289,7 @@ class LogicalAgentSession {
|
|
|
24126
24289
|
reopenClosedLaneForWork(event, physicalOwner, generation) {
|
|
24127
24290
|
if (this.adapter.execution.lifetime === "turn")
|
|
24128
24291
|
return;
|
|
24129
|
-
const isRootWork = event.kind === "
|
|
24292
|
+
const isRootWork = event.kind === "tool_call" || event.kind === "tool_output" || event.kind === "compaction_started" || event.kind === "compaction_finished" || event.kind === "review_started" || event.kind === "review_finished" || event.kind === "internal_progress";
|
|
24130
24293
|
if (!isRootWork)
|
|
24131
24294
|
return;
|
|
24132
24295
|
const tombstone = this.closedLaneTombstone;
|
|
@@ -24136,7 +24299,10 @@ class LogicalAgentSession {
|
|
|
24136
24299
|
this.activeTurn = {
|
|
24137
24300
|
turnId: tombstone.localTurnId,
|
|
24138
24301
|
commandIds: tombstone.commandIds,
|
|
24139
|
-
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {}
|
|
24302
|
+
...tombstone.terminalOwner ? { terminalOwner: tombstone.terminalOwner } : {},
|
|
24303
|
+
pendingMessage: emptySemanticAssembler(),
|
|
24304
|
+
pendingReasoning: emptySemanticAssembler(),
|
|
24305
|
+
lastWorkHeartbeatAt: null
|
|
24140
24306
|
};
|
|
24141
24307
|
this.state = "working";
|
|
24142
24308
|
return tombstone.localTurnId;
|
|
@@ -25980,7 +26146,7 @@ function parseLocalMessageReminderBody(body, agentId) {
|
|
|
25980
26146
|
return null;
|
|
25981
26147
|
if (!Number.isSafeInteger(record4.sentSeq) || record4.sentSeq < 1)
|
|
25982
26148
|
return null;
|
|
25983
|
-
if (!Number.isSafeInteger(record4.remindAfterMs) || record4.remindAfterMs < LOCAL_MESSAGE_REMINDER_MIN_MS || record4.remindAfterMs > LOCAL_MESSAGE_REMINDER_MAX_MS)
|
|
26149
|
+
if (!Number.isSafeInteger(record4.remindAfterMs) || record4.remindAfterMs !== 0 && record4.remindAfterMs < LOCAL_MESSAGE_REMINDER_MIN_MS || record4.remindAfterMs > LOCAL_MESSAGE_REMINDER_MAX_MS)
|
|
25984
26150
|
return null;
|
|
25985
26151
|
return {
|
|
25986
26152
|
agentId,
|
|
@@ -26239,11 +26405,26 @@ function isActivelyWorking(agent2) {
|
|
|
26239
26405
|
return agent2.status === "running" && (leaseIsWorking(agent2.execution.lease) || agent2.pendingAdmissions.length > 0 || agent2.inbox.length > 0);
|
|
26240
26406
|
}
|
|
26241
26407
|
var DEFAULT_STALE_THRESHOLD_MS = 120000;
|
|
26242
|
-
var
|
|
26408
|
+
var DEFAULT_TURN_SILENCE_POLICY = {
|
|
26409
|
+
nativeIdleTimeoutMs: 300000,
|
|
26410
|
+
daemonGraceMs: 60000,
|
|
26411
|
+
recoveryGraceMs: 60000,
|
|
26412
|
+
maxRecoveryExtensions: 1,
|
|
26413
|
+
normalBudgetMs: 360000
|
|
26414
|
+
};
|
|
26415
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
26416
|
+
var DEFAULT_IDLE_RESET_TIMEOUT_MS = 6 * 60 * 60 * 1000;
|
|
26243
26417
|
var DEFAULT_RESET_STUCK_THRESHOLD_MS = 120000;
|
|
26244
26418
|
var DEFAULT_STOPPING_STUCK_THRESHOLD_MS = 30000;
|
|
26245
|
-
function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS) {
|
|
26246
|
-
return {
|
|
26419
|
+
function createInitialManagerState(staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, resetStuckThresholdMs = DEFAULT_RESET_STUCK_THRESHOLD_MS, stoppingStuckThresholdMs = DEFAULT_STOPPING_STUCK_THRESHOLD_MS, idleResetTimeoutMs = DEFAULT_IDLE_RESET_TIMEOUT_MS) {
|
|
26420
|
+
return {
|
|
26421
|
+
agents: {},
|
|
26422
|
+
staleThresholdMs,
|
|
26423
|
+
idleTimeoutMs,
|
|
26424
|
+
idleResetTimeoutMs,
|
|
26425
|
+
resetStuckThresholdMs,
|
|
26426
|
+
stoppingStuckThresholdMs
|
|
26427
|
+
};
|
|
26247
26428
|
}
|
|
26248
26429
|
function reduceManager(state, event) {
|
|
26249
26430
|
switch (event.type) {
|
|
@@ -26258,6 +26439,11 @@ function reduceManager(state, event) {
|
|
|
26258
26439
|
});
|
|
26259
26440
|
case "backend_session":
|
|
26260
26441
|
return mutate(state, event.agentId, (a) => {
|
|
26442
|
+
if (event.stalledBefore) {
|
|
26443
|
+
a.stalledSessionId = event.sessionId;
|
|
26444
|
+
} else if (a.stalledSessionId !== null && a.stalledSessionId !== event.sessionId) {
|
|
26445
|
+
a.stalledSessionId = null;
|
|
26446
|
+
}
|
|
26261
26447
|
a.sessionId = event.sessionId;
|
|
26262
26448
|
});
|
|
26263
26449
|
case "attach_session": {
|
|
@@ -26270,7 +26456,17 @@ function reduceManager(state, event) {
|
|
|
26270
26456
|
{
|
|
26271
26457
|
const a = agent2;
|
|
26272
26458
|
a.execution = { sessionInstanceId: event.sessionInstanceId, lease: { state: "none", lastTerminal: null } };
|
|
26459
|
+
a.turnSilence = event.turnSilence ?? {
|
|
26460
|
+
...DEFAULT_TURN_SILENCE_POLICY,
|
|
26461
|
+
nativeIdleTimeoutMs: state.staleThresholdMs,
|
|
26462
|
+
daemonGraceMs: 0,
|
|
26463
|
+
normalBudgetMs: state.staleThresholdMs
|
|
26464
|
+
};
|
|
26273
26465
|
a.lastProgressAt = event.nowMs;
|
|
26466
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26467
|
+
a.lastNativeActivityKind = null;
|
|
26468
|
+
a.runtimePhase = "admission";
|
|
26469
|
+
a.backendTurnId = null;
|
|
26274
26470
|
a.idleSince = null;
|
|
26275
26471
|
syncExecutionProjection(a);
|
|
26276
26472
|
}
|
|
@@ -26326,7 +26522,25 @@ function reduceManager(state, event) {
|
|
|
26326
26522
|
return { state, effects: [] };
|
|
26327
26523
|
return mutate(state, event.agentId, (a) => {
|
|
26328
26524
|
a.sessionId = null;
|
|
26525
|
+
a.stalledSessionId = null;
|
|
26526
|
+
a.idleSince = null;
|
|
26329
26527
|
});
|
|
26528
|
+
case "idle_reset_committed": {
|
|
26529
|
+
const existing = state.agents[event.agentId];
|
|
26530
|
+
if (!existing)
|
|
26531
|
+
return { state, effects: [] };
|
|
26532
|
+
const agent2 = clone2(existing);
|
|
26533
|
+
agent2.sessionId = null;
|
|
26534
|
+
agent2.stalledSessionId = null;
|
|
26535
|
+
agent2.idleSince = null;
|
|
26536
|
+
if (agent2.status !== "running")
|
|
26537
|
+
return commit(state, agent2, []);
|
|
26538
|
+
agent2.status = "stopping";
|
|
26539
|
+
agent2.stoppingSince = event.nowMs;
|
|
26540
|
+
return commit(state, agent2, [
|
|
26541
|
+
{ type: "stop", agentId: event.agentId, reason: "idle_session_reset" }
|
|
26542
|
+
]);
|
|
26543
|
+
}
|
|
26330
26544
|
case "begin_reset":
|
|
26331
26545
|
if (!state.agents[event.agentId])
|
|
26332
26546
|
return { state, effects: [] };
|
|
@@ -26364,8 +26578,18 @@ function reduceManager(state, event) {
|
|
|
26364
26578
|
return;
|
|
26365
26579
|
const startedCommands = new Set(event.commandIds);
|
|
26366
26580
|
a.pendingAdmissions = a.pendingAdmissions.filter((entry) => !startedCommands.has(entry.commandId));
|
|
26367
|
-
a.execution.lease = {
|
|
26581
|
+
a.execution.lease = {
|
|
26582
|
+
state: "active",
|
|
26583
|
+
identity,
|
|
26584
|
+
lastWorkAt: event.nowMs,
|
|
26585
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26586
|
+
recoveryExtensionsUsed: 0
|
|
26587
|
+
};
|
|
26368
26588
|
a.lastProgressAt = event.nowMs;
|
|
26589
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26590
|
+
a.lastNativeActivityKind = "turn_started";
|
|
26591
|
+
a.runtimePhase = "inference";
|
|
26592
|
+
a.backendTurnId = null;
|
|
26369
26593
|
a.idleSince = null;
|
|
26370
26594
|
syncExecutionProjection(a);
|
|
26371
26595
|
});
|
|
@@ -26386,7 +26610,12 @@ function reduceManager(state, event) {
|
|
|
26386
26610
|
const lease = a.execution.lease;
|
|
26387
26611
|
const identity = identityOf(event);
|
|
26388
26612
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
26389
|
-
a.execution.lease = {
|
|
26613
|
+
a.execution.lease = {
|
|
26614
|
+
...lease,
|
|
26615
|
+
lastWorkAt: event.nowMs,
|
|
26616
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26617
|
+
recoveryExtensionsUsed: 0
|
|
26618
|
+
};
|
|
26390
26619
|
} else {
|
|
26391
26620
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
26392
26621
|
if (!terminal || !sameIdentity(terminal.identity, identity))
|
|
@@ -26395,6 +26624,8 @@ function reduceManager(state, event) {
|
|
|
26395
26624
|
state: "suspect_active",
|
|
26396
26625
|
identity,
|
|
26397
26626
|
lastWorkAt: event.nowMs,
|
|
26627
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs,
|
|
26628
|
+
recoveryExtensionsUsed: 0,
|
|
26398
26629
|
reason: "work_after_terminal"
|
|
26399
26630
|
};
|
|
26400
26631
|
}
|
|
@@ -26408,7 +26639,7 @@ function reduceManager(state, event) {
|
|
|
26408
26639
|
case "turn_tool_finished":
|
|
26409
26640
|
return onTurnToolLifecycle(state, event, "finished");
|
|
26410
26641
|
case "turn_completed":
|
|
26411
|
-
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId);
|
|
26642
|
+
return onTurnCompleted(state, event.agentId, event.sessionInstanceId, event.nowMs, event.turnId, event.endReason);
|
|
26412
26643
|
case "session_closed":
|
|
26413
26644
|
if (state.agents[event.agentId]?.execution.sessionInstanceId !== event.sessionInstanceId) {
|
|
26414
26645
|
return { state, effects: [] };
|
|
@@ -26418,7 +26649,6 @@ function reduceManager(state, event) {
|
|
|
26418
26649
|
const closing = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId === event.sessionInstanceId);
|
|
26419
26650
|
agent2.pendingAdmissions = agent2.pendingAdmissions.filter((entry) => entry.sessionInstanceId !== event.sessionInstanceId);
|
|
26420
26651
|
agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
26421
|
-
agent2.idleSince = null;
|
|
26422
26652
|
syncExecutionProjection(agent2);
|
|
26423
26653
|
return commit(state, agent2, recoveryEffects(agent2, closing));
|
|
26424
26654
|
}
|
|
@@ -26426,8 +26656,57 @@ function reduceManager(state, event) {
|
|
|
26426
26656
|
return onExit(state, event.agentId);
|
|
26427
26657
|
case "tick":
|
|
26428
26658
|
return onTick(state, event.nowMs);
|
|
26429
|
-
case "runtime_signal":
|
|
26430
|
-
|
|
26659
|
+
case "runtime_signal": {
|
|
26660
|
+
const existing = state.agents[event.agentId];
|
|
26661
|
+
if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
|
|
26662
|
+
return { state, effects: [] };
|
|
26663
|
+
const lease = existing.execution.lease;
|
|
26664
|
+
const identity = { sessionInstanceId: event.sessionInstanceId, turnId: event.turnId };
|
|
26665
|
+
if (lease.state !== "active" && lease.state !== "suspect_active" || !sameIdentity(lease.identity, identity)) {
|
|
26666
|
+
return { state, effects: [] };
|
|
26667
|
+
}
|
|
26668
|
+
return mutate(state, event.agentId, (a) => {
|
|
26669
|
+
const active = a.execution.lease;
|
|
26670
|
+
if (active.state !== "active" && active.state !== "suspect_active" || !sameIdentity(active.identity, identity))
|
|
26671
|
+
return;
|
|
26672
|
+
if (event.kind === "recovery" && event.recoveryStage !== "recovered") {
|
|
26673
|
+
if (active.recoveryExtensionsUsed < a.turnSilence.maxRecoveryExtensions) {
|
|
26674
|
+
a.execution.lease = {
|
|
26675
|
+
...active,
|
|
26676
|
+
nativeDeadlineAt: Math.max(active.nativeDeadlineAt, event.nowMs + a.turnSilence.recoveryGraceMs),
|
|
26677
|
+
recoveryExtensionsUsed: active.recoveryExtensionsUsed + 1
|
|
26678
|
+
};
|
|
26679
|
+
}
|
|
26680
|
+
} else {
|
|
26681
|
+
a.execution.lease = {
|
|
26682
|
+
...active,
|
|
26683
|
+
nativeDeadlineAt: event.nowMs + a.turnSilence.normalBudgetMs
|
|
26684
|
+
};
|
|
26685
|
+
}
|
|
26686
|
+
a.lastNativeActivityAt = event.nowMs;
|
|
26687
|
+
a.lastNativeActivityKind = event.kind;
|
|
26688
|
+
a.runtimePhase = event.phase;
|
|
26689
|
+
if (event.backendTurnId)
|
|
26690
|
+
a.backendTurnId = event.backendTurnId;
|
|
26691
|
+
});
|
|
26692
|
+
}
|
|
26693
|
+
case "stall_control_failed":
|
|
26694
|
+
return mutate(state, event.agentId, (a) => {
|
|
26695
|
+
if (event.transition === "clear") {
|
|
26696
|
+
if (a.sessionId === event.sessionId)
|
|
26697
|
+
a.stalledSessionId = event.sessionId;
|
|
26698
|
+
return;
|
|
26699
|
+
}
|
|
26700
|
+
if (a.status !== "stopping")
|
|
26701
|
+
return;
|
|
26702
|
+
const lease = a.execution.lease;
|
|
26703
|
+
if (lease.state !== "active" && lease.state !== "suspect_active")
|
|
26704
|
+
return;
|
|
26705
|
+
a.status = "running";
|
|
26706
|
+
a.stoppingSince = null;
|
|
26707
|
+
a.sessionId = event.sessionId;
|
|
26708
|
+
a.stalledSessionId = event.transition === "fence" ? event.sessionId : null;
|
|
26709
|
+
});
|
|
26431
26710
|
case "delivery_rejected":
|
|
26432
26711
|
return mutate(state, event.agentId, (a) => {
|
|
26433
26712
|
if (!a.inbox.some((message2) => message2.id === event.message.id)) {
|
|
@@ -26465,7 +26744,7 @@ function onWake(state, agentId, message2) {
|
|
|
26465
26744
|
}
|
|
26466
26745
|
return commit(state, agent2, []);
|
|
26467
26746
|
}
|
|
26468
|
-
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
26747
|
+
function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endReason) {
|
|
26469
26748
|
const existing = state.agents[agentId];
|
|
26470
26749
|
if (!existing)
|
|
26471
26750
|
return { state, effects: [] };
|
|
@@ -26481,13 +26760,23 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId) {
|
|
|
26481
26760
|
const lastTerminal = { identity, at: nowMs };
|
|
26482
26761
|
agent2.execution.lease = { state: "none", lastTerminal };
|
|
26483
26762
|
agent2.lastProgressAt = nowMs;
|
|
26763
|
+
agent2.lastNativeActivityAt = nowMs;
|
|
26764
|
+
agent2.lastNativeActivityKind = "turn_end";
|
|
26765
|
+
agent2.runtimePhase = "terminal";
|
|
26766
|
+
const clearedStallSessionId = endReason === undefined ? agent2.stalledSessionId : null;
|
|
26767
|
+
if (clearedStallSessionId !== null)
|
|
26768
|
+
agent2.stalledSessionId = null;
|
|
26484
26769
|
syncExecutionProjection(agent2);
|
|
26770
|
+
const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
|
|
26485
26771
|
if (agent2.inbox.length > 0) {
|
|
26486
26772
|
const messages = drainInbox(agent2);
|
|
26487
|
-
return commit(state, agent2,
|
|
26773
|
+
return commit(state, agent2, [
|
|
26774
|
+
...clearEffects,
|
|
26775
|
+
...messages.map((queued) => ({ type: "send", agentId, message: queued, mode: "idle" }))
|
|
26776
|
+
]);
|
|
26488
26777
|
}
|
|
26489
26778
|
agent2.idleSince = nowMs;
|
|
26490
|
-
return commit(state, agent2,
|
|
26779
|
+
return commit(state, agent2, clearEffects);
|
|
26491
26780
|
}
|
|
26492
26781
|
function onTurnToolLifecycle(state, event, lifecycle) {
|
|
26493
26782
|
const existing = state.agents[event.agentId];
|
|
@@ -26509,11 +26798,22 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
26509
26798
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
26510
26799
|
const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
|
|
26511
26800
|
if (outstandingToolUses > 0) {
|
|
26512
|
-
agent2.execution.lease = {
|
|
26801
|
+
agent2.execution.lease = {
|
|
26802
|
+
...lease,
|
|
26803
|
+
lastWorkAt: event.nowMs,
|
|
26804
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26805
|
+
recoveryExtensionsUsed: 0,
|
|
26806
|
+
outstandingToolUses
|
|
26807
|
+
};
|
|
26513
26808
|
} else {
|
|
26514
26809
|
const unblockedLease = { ...lease };
|
|
26515
26810
|
delete unblockedLease.outstandingToolUses;
|
|
26516
|
-
agent2.execution.lease = {
|
|
26811
|
+
agent2.execution.lease = {
|
|
26812
|
+
...unblockedLease,
|
|
26813
|
+
lastWorkAt: event.nowMs,
|
|
26814
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26815
|
+
recoveryExtensionsUsed: 0
|
|
26816
|
+
};
|
|
26517
26817
|
}
|
|
26518
26818
|
} else {
|
|
26519
26819
|
const terminal = lease.state === "none" ? lease.lastTerminal : null;
|
|
@@ -26523,11 +26823,16 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
26523
26823
|
state: "suspect_active",
|
|
26524
26824
|
identity,
|
|
26525
26825
|
lastWorkAt: event.nowMs,
|
|
26826
|
+
nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
|
|
26827
|
+
recoveryExtensionsUsed: 0,
|
|
26526
26828
|
outstandingToolUses: 1,
|
|
26527
26829
|
reason: "work_after_terminal"
|
|
26528
26830
|
};
|
|
26529
26831
|
}
|
|
26530
26832
|
agent2.lastProgressAt = event.nowMs;
|
|
26833
|
+
agent2.lastNativeActivityAt = event.nowMs;
|
|
26834
|
+
agent2.lastNativeActivityKind = lifecycle === "started" ? "tool_call" : "tool_output";
|
|
26835
|
+
agent2.runtimePhase = lifecycle === "started" ? "tool" : "inference";
|
|
26531
26836
|
agent2.idleSince = null;
|
|
26532
26837
|
syncExecutionProjection(agent2);
|
|
26533
26838
|
});
|
|
@@ -26540,6 +26845,8 @@ function onExit(state, agentId) {
|
|
|
26540
26845
|
const effects = recoveryEffects(agent2, agent2.pendingAdmissions);
|
|
26541
26846
|
agent2.pendingAdmissions = [];
|
|
26542
26847
|
agent2.execution = { sessionInstanceId: null, lease: { state: "detached" } };
|
|
26848
|
+
agent2.runtimePhase = "idle";
|
|
26849
|
+
agent2.backendTurnId = null;
|
|
26543
26850
|
agent2.stoppingSince = null;
|
|
26544
26851
|
syncExecutionProjection(agent2);
|
|
26545
26852
|
if (agent2.inbox.length > 0) {
|
|
@@ -26559,10 +26866,19 @@ function onTick(state, nowMs) {
|
|
|
26559
26866
|
for (const id of Object.keys(agents)) {
|
|
26560
26867
|
const a = agents[id];
|
|
26561
26868
|
const lease = a.execution.lease;
|
|
26562
|
-
const stalled = a.status === "running" && (lease.state === "active" || lease.state === "suspect_active") && (lease.outstandingToolUses ?? 0) === 0 && nowMs - lease.lastWorkAt >=
|
|
26869
|
+
const stalled = a.status === "running" && (lease.state === "active" || lease.state === "suspect_active") && (lease.outstandingToolUses ?? 0) === 0 && nowMs - lease.lastWorkAt >= a.turnSilence.normalBudgetMs && nowMs >= lease.nativeDeadlineAt;
|
|
26563
26870
|
if (stalled) {
|
|
26564
|
-
|
|
26565
|
-
|
|
26871
|
+
const repeatedSessionStall = a.sessionId !== null && a.stalledSessionId === a.sessionId;
|
|
26872
|
+
const forgetSessionId = repeatedSessionStall ? a.sessionId : undefined;
|
|
26873
|
+
agents[id] = {
|
|
26874
|
+
...a,
|
|
26875
|
+
status: "stopping",
|
|
26876
|
+
sessionId: repeatedSessionStall ? null : a.sessionId,
|
|
26877
|
+
stalledSessionId: repeatedSessionStall ? null : a.sessionId,
|
|
26878
|
+
idleSince: null,
|
|
26879
|
+
stoppingSince: nowMs
|
|
26880
|
+
};
|
|
26881
|
+
effects.push(forgetSessionId ? { type: "terminate_stalled", agentId: id, forgetSessionId } : a.sessionId !== null ? { type: "terminate_stalled", agentId: id, recordSessionId: a.sessionId } : { type: "terminate_stalled", agentId: id });
|
|
26566
26882
|
continue;
|
|
26567
26883
|
}
|
|
26568
26884
|
const expiredAdmission = a.status === "running" && a.pendingAdmissions.filter((entry) => !entry.driverAcknowledged && nowMs - entry.admittedAt >= state.staleThresholdMs);
|
|
@@ -26594,9 +26910,14 @@ function onTick(state, nowMs) {
|
|
|
26594
26910
|
effects.push({ type: "force_exit", agentId: id, reason: "stopping_stuck" });
|
|
26595
26911
|
continue;
|
|
26596
26912
|
}
|
|
26913
|
+
const idleResetEligible = a.idleSince !== null && a.sessionId !== null && a.inbox.length === 0 && a.pendingAdmissions.length === 0 && !a.resetting && state.idleResetTimeoutMs > 0 && Number.isFinite(state.idleResetTimeoutMs) && (a.status === "idle" && lease.state === "detached" || a.status === "running" && lease.state === "none" && lease.lastTerminal !== null);
|
|
26914
|
+
if (idleResetEligible && nowMs - a.idleSince >= state.idleResetTimeoutMs) {
|
|
26915
|
+
effects.push({ type: "reset_idle_session", agentId: id, sessionId: a.sessionId });
|
|
26916
|
+
continue;
|
|
26917
|
+
}
|
|
26597
26918
|
const idleEligible = a.status === "running" && lease.state === "none" && a.pendingAdmissions.length === 0 && lease.lastTerminal !== null && a.inbox.length === 0 && state.idleTimeoutMs > 0 && Number.isFinite(state.idleTimeoutMs);
|
|
26598
26919
|
if (idleEligible && a.idleSince !== null && nowMs - a.idleSince >= state.idleTimeoutMs) {
|
|
26599
|
-
agents[id] = { ...a, status: "stopping",
|
|
26920
|
+
agents[id] = { ...a, status: "stopping", stoppingSince: nowMs };
|
|
26600
26921
|
effects.push({ type: "stop", agentId: id, reason: "idle_timeout" });
|
|
26601
26922
|
}
|
|
26602
26923
|
}
|
|
@@ -26608,11 +26929,17 @@ function freshAgent(agentId) {
|
|
|
26608
26929
|
status: "idle",
|
|
26609
26930
|
inbox: [],
|
|
26610
26931
|
sessionId: null,
|
|
26932
|
+
stalledSessionId: null,
|
|
26611
26933
|
execution: { sessionInstanceId: null, lease: { state: "detached" } },
|
|
26612
26934
|
pendingAdmissions: [],
|
|
26613
26935
|
turnId: null,
|
|
26614
26936
|
turnActive: false,
|
|
26615
26937
|
lastProgressAt: 0,
|
|
26938
|
+
lastNativeActivityAt: 0,
|
|
26939
|
+
lastNativeActivityKind: null,
|
|
26940
|
+
runtimePhase: "idle",
|
|
26941
|
+
backendTurnId: null,
|
|
26942
|
+
turnSilence: DEFAULT_TURN_SILENCE_POLICY,
|
|
26616
26943
|
lastDeliverAt: null,
|
|
26617
26944
|
idleSince: null,
|
|
26618
26945
|
stoppingSince: null,
|
|
@@ -26708,6 +27035,16 @@ function scrubRuntimeErrorDiagnosticText(value) {
|
|
|
26708
27035
|
|
|
26709
27036
|
// src/drivers/systemPrompt.ts
|
|
26710
27037
|
var CLI = "$ALOOK_CLI";
|
|
27038
|
+
var MESSAGE_SEND_STDIN_POLICY = [
|
|
27039
|
+
"`--stdin` is required and limited to 1 KiB of UTF-8. Write it as social language a person " + "with ADHD can scan without effort:",
|
|
27040
|
+
"",
|
|
27041
|
+
"- Lead with the point, result, decision, or one concrete ask.",
|
|
27042
|
+
"- Keep one message to one topic; suppress tangents and repeated recap.",
|
|
27043
|
+
"- Use at most five short items when a list helps.",
|
|
27044
|
+
"- Drop preambles, play-by-play, and closing pleasantries.",
|
|
27045
|
+
"- Put exact plans, reviews, evidence, logs, and long technical detail in a Markdown attachment. " + "The message body carries only the short summary and next action."
|
|
27046
|
+
].join(`
|
|
27047
|
+
`);
|
|
26711
27048
|
function identitySection(config2) {
|
|
26712
27049
|
const parts = ["## Identity", ""];
|
|
26713
27050
|
const name = config2.agentName ?? "a member of the household";
|
|
@@ -26724,9 +27061,6 @@ function identitySection(config2) {
|
|
|
26724
27061
|
parts.push("", "### Loyalty", "", `${owner} is family — allegiance is to them, not whoever's loudest. Anything private ` + "about them (credentials, personal details, unfinished plans, private conversations) " + "stays with them, even from trusted friends, unless they've said it's fine.", "", "You're a peer, not a subordinate. If they're about to do something you think is a bad " + "idea, say so. Loyalty means honesty, not agreement.");
|
|
26725
27062
|
}
|
|
26726
27063
|
parts.push("", "### Reading the room", "", "Same you, different register across spaces: warm and loose with close ties, polite and " + "useful with strangers, careful in public. Let the channel set the tone.");
|
|
26727
|
-
if (config2.description) {
|
|
26728
|
-
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).");
|
|
26729
|
-
}
|
|
26730
27064
|
return parts.join(`
|
|
26731
27065
|
`);
|
|
26732
27066
|
}
|
|
@@ -26738,9 +27072,9 @@ function cliCommandsSection() {
|
|
|
26738
27072
|
"",
|
|
26739
27073
|
"### Messaging",
|
|
26740
27074
|
"",
|
|
26741
|
-
`1. \`${CLI} inbox pull\` — fetch unread messages
|
|
26742
|
-
`2. \`${CLI} message send\` — send to a channel, DM, or thread. ` + `
|
|
26743
|
-
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted
|
|
27075
|
+
`1. \`${CLI} inbox pull\` — fetch unread messages; \`--no-ack\` peeks without advancing.`,
|
|
27076
|
+
`2. \`${CLI} message send --target <ref> --remind-after <0|Nm|Nh> --stdin\` — send to a ` + `channel, DM, or thread. ` + `The body is required through \`--stdin\` and limited to 1 KiB of UTF-8. ` + `Attach uploaded files with \`--attachment <id>\` (repeatable, order matters). ` + `\`--remind-after\` accepts \`0\`, or a whole-number duration from \`1m\` to \`24h\`.`,
|
|
27077
|
+
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted.`,
|
|
26744
27078
|
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
|
|
26745
27079
|
`5. \`${CLI} message emoji --target <ref> --emoji <e>\` — react with a single emoji. ` + `Works on channel messages (\`/<server>/<channel>#N\`), DM messages ` + `(\`/.dm/<peer>#N\`), and thread-reply messages (\`/<server>/<channel>/#N#M\`).`,
|
|
26746
27080
|
`6. \`${CLI} message mark set --target <full-message-ref>\` — persist a message as outstanding work.`,
|
|
@@ -26770,7 +27104,7 @@ function cliCommandsSection() {
|
|
|
26770
27104
|
"",
|
|
26771
27105
|
"### Context Lifecycle",
|
|
26772
27106
|
"",
|
|
26773
|
-
`1. \`${CLI} nap --handoff <file>\` — reset
|
|
27107
|
+
`1. \`${CLI} nap --handoff <file>\` — reset the current session from a required handoff file.`,
|
|
26774
27108
|
"",
|
|
26775
27109
|
"### Output format",
|
|
26776
27110
|
"",
|
|
@@ -26786,14 +27120,29 @@ function messagingSection() {
|
|
|
26786
27120
|
"",
|
|
26787
27121
|
"### Sending & receiving",
|
|
26788
27122
|
"",
|
|
26789
|
-
"You can initiate conversations — send to any channel or DM someone directly. You're not " + "limited to replying
|
|
27123
|
+
"You can initiate conversations — send to any channel or DM someone directly. You're not " + "limited to replying; the same sending rules apply either way.",
|
|
26790
27124
|
"",
|
|
26791
|
-
"
|
|
26792
|
-
"Free-form message bodies never go in command arguments.",
|
|
27125
|
+
"#### Message body",
|
|
26793
27126
|
"",
|
|
26794
|
-
|
|
26795
|
-
|
|
26796
|
-
|
|
27127
|
+
MESSAGE_SEND_STDIN_POLICY,
|
|
27128
|
+
"",
|
|
27129
|
+
"#### Follow up when a conversation goes quiet",
|
|
27130
|
+
"",
|
|
27131
|
+
"`--remind-after T` is required on every send and controls whether a local follow-up is " + "armed for the same channel, thread, or DM.",
|
|
27132
|
+
"",
|
|
27133
|
+
"- Use `1m` to `24h` when silence would leave work unfinished — for example, after a question, " + "approval request, handoff, or blocker. If no newer message arrives by T, you will be reminded " + "to return; a newer message or daemon restart cancels the timer.",
|
|
27134
|
+
"- Use `0` only when no later action depends on a reply. It disables the timer.",
|
|
27135
|
+
"",
|
|
27136
|
+
"Example: `--remind-after 5m` asks for a follow-up after five quiet minutes.",
|
|
27137
|
+
"",
|
|
27138
|
+
"#### Sending mechanics",
|
|
27139
|
+
"",
|
|
27140
|
+
`- Send body: use \`${CLI} message send --target <ref> --remind-after T --stdin\` with the ` + "quoted-heredoc form under *Message formatting*.",
|
|
27141
|
+
`- Long detail: upload it with \`${CLI} message attachment upload --target <ref> --file <path>.md\`; ` + "add the returned id as `--attachment <id>` on the short stdin send.",
|
|
27142
|
+
`- Cite a specific message: add \`--reply "#37"\` — \`--reply\` takes the \`#N\` seq ` + "(within `--target`) of the message you're answering.",
|
|
27143
|
+
"",
|
|
27144
|
+
"Reply where the message came from. Post results in the channel that owns the topic. " + "When uncertain, read history (below) or DM the relevant people.",
|
|
27145
|
+
"Write every message body in the stdin/heredoc block; never place it directly on the command line.",
|
|
26797
27146
|
"",
|
|
26798
27147
|
"### Context refs",
|
|
26799
27148
|
"",
|
|
@@ -26827,14 +27176,14 @@ function messagingSection() {
|
|
|
26827
27176
|
"",
|
|
26828
27177
|
"### Message formatting",
|
|
26829
27178
|
"",
|
|
26830
|
-
"Alook
|
|
27179
|
+
"Alook specially renders refs and mentions in message bodies. Write them as plain text, not " + "inside backticks.",
|
|
26831
27180
|
"",
|
|
26832
|
-
"- **Context refs** —
|
|
27181
|
+
"- **Context refs** — write the full refs from *Context refs* above so they stay " + "clickable. Never paste a private DM ref into a server channel.",
|
|
26833
27182
|
"- **Mentions** — `@name#NNNN` calls that person's attention specifically. In a private " + "channel, first verify they " + `are a member with \`${CLI} channel member --channel <ref>\` before mentioning them.`,
|
|
26834
27183
|
"",
|
|
26835
27184
|
"```bash",
|
|
26836
27185
|
"# Choose a fresh quoted delimiter that does not occur as a standalone line in the body.",
|
|
26837
|
-
`${CLI} message send --target "/demo#1234/general" --stdin <<'ALOOK_MESSAGE_7F3C'`,
|
|
27186
|
+
`${CLI} message send --target "/demo#1234/general" --remind-after 5m --stdin <<'ALOOK_MESSAGE_7F3C'`,
|
|
26838
27187
|
"@alice#0001 Please review /demo#1234/general#42",
|
|
26839
27188
|
"ALOOK_MESSAGE_7F3C",
|
|
26840
27189
|
"```",
|
|
@@ -26847,7 +27196,7 @@ function messagingSection() {
|
|
|
26847
27196
|
"```",
|
|
26848
27197
|
"",
|
|
26849
27198
|
"`channel` is the reply ref. `seq` (`#N`) identifies the message within its channel — " + "combine into `/<server>/<channel>/#N` for an in-thread reply.",
|
|
26850
|
-
"`content.replyTo` (`{seq, sender}`)
|
|
27199
|
+
"`content.replyTo` (`{seq, sender}`) identifies the message being replied to.",
|
|
26851
27200
|
"`hint` is present when the containing surface changes how you should act. Follow it."
|
|
26852
27201
|
].join(`
|
|
26853
27202
|
`);
|
|
@@ -26860,15 +27209,15 @@ function channelTypesSection() {
|
|
|
26860
27209
|
"",
|
|
26861
27210
|
"### Text channels",
|
|
26862
27211
|
"",
|
|
26863
|
-
"- A text channel is a linear conversation. Send
|
|
27212
|
+
"- A text channel is a linear conversation. Send with `message send --target " + "/<server>/<channel>`.",
|
|
26864
27213
|
"- A text-channel message may have a side thread at `/<server>/<channel>/#N`. Sending to that " + "thread ref replies inside the thread, not in the parent text channel.",
|
|
26865
27214
|
"",
|
|
26866
27215
|
"### Forum channels",
|
|
26867
27216
|
"",
|
|
26868
27217
|
"- A forum is a collection of posts, not one linear conversation.",
|
|
26869
27218
|
"- Each top-level message in a forum is a post title. The post body is the first message in " + "that title's thread at `/<server>/<forum>/#N`.",
|
|
26870
|
-
"- To participate in
|
|
26871
|
-
"- To publish
|
|
27219
|
+
"- To participate in a post, use `message send --target /<server>/<forum>/#N`.",
|
|
27220
|
+
"- To publish a post, use `message send --target /<server>/<forum>` for its title, then " + "`message send --target /<server>/<forum>/#N` for the body."
|
|
26872
27221
|
].join(`
|
|
26873
27222
|
`);
|
|
26874
27223
|
}
|
|
@@ -26896,13 +27245,7 @@ function utilsSection() {
|
|
|
26896
27245
|
"",
|
|
26897
27246
|
"### Join a new server",
|
|
26898
27247
|
"",
|
|
26899
|
-
`If a message contains a \`/c/invite/...\` link, just run \`${CLI} server join --invite <link>\`. ` + "The server enforces owner-only: it accepts only invites your owner created and rejects the " + "rest with a reason. Safe to attempt without reasoning about who sent it."
|
|
26900
|
-
"",
|
|
26901
|
-
"### Follow up when a conversation goes quiet",
|
|
26902
|
-
"",
|
|
26903
|
-
`Use \`message send --remind-after <duration>\` when you send something that may need a later ` + "follow-up — for example, a question, approval request, handoff, or blocker — and silence would " + "leave the work unfinished. If no newer message appears in that channel, thread, or DM during " + "the duration, you'll receive a reminder to return and decide what to do next. Don't add it to " + "ordinary messages that need no follow-up.",
|
|
26904
|
-
"",
|
|
26905
|
-
`Example: ${CLI} message send --target "/demo#1234/team" --remind-after 1m --file ./message.md`
|
|
27248
|
+
`If a message contains a \`/c/invite/...\` link, just run \`${CLI} server join --invite <link>\`. ` + "The server enforces owner-only: it accepts only invites your owner created and rejects the " + "rest with a reason. Safe to attempt without reasoning about who sent it."
|
|
26906
27249
|
].join(`
|
|
26907
27250
|
`);
|
|
26908
27251
|
}
|
|
@@ -26910,11 +27253,10 @@ function criticalRulesSection() {
|
|
|
26910
27253
|
return [
|
|
26911
27254
|
"## Critical rules",
|
|
26912
27255
|
"",
|
|
26913
|
-
`- **\`${CLI}\` is the only way to communicate.** Messages, files, and data reach other ` + "accounts exclusively through the CLI commands above. Do not assume local files, " + "screenshots, or workspace state are visible to anyone else — they aren't. If someone " + `needs to see something,
|
|
27256
|
+
`- **\`${CLI}\` is the only way to communicate.** Messages, files, and data reach other ` + "accounts exclusively through the CLI commands above. Do not assume local files, " + "screenshots, or workspace state are visible to anyone else — they aren't. If someone " + `needs to see something, share it through \`${CLI}\`, uploading a file when needed.`,
|
|
26914
27257
|
"- **Never expose tokens, keys, or secrets.** Redact credential-like strings from tool output " + "before sharing.",
|
|
26915
27258
|
"- **Match the sender's language.** Reply in the language they wrote in.",
|
|
26916
|
-
"- **Channel alignment**: you can't send to a channel with unread messages. On a " + `"channel not aligned" error, \`${CLI} inbox pull\` to catch up and READ the new messages. ` + "Judge if your message is still needed or overlaps with what just landed. Adjust or skip; " + "don't mechanically resend."
|
|
26917
|
-
"- **Finish in-flight work before stopping.** Don't leave anything half-handled. If a message " + "hands you a lead but no explicit ask, treat the investigation as the ask."
|
|
27259
|
+
"- **Channel alignment**: you can't send to a channel with unread messages. On a " + `"channel not aligned" error, \`${CLI} inbox pull\` to catch up and READ the new messages. ` + "Judge if your message is still needed or overlaps with what just landed. Adjust or skip; " + "don't mechanically resend."
|
|
26918
27260
|
].join(`
|
|
26919
27261
|
`);
|
|
26920
27262
|
}
|
|
@@ -26922,7 +27264,7 @@ function executionModelSection() {
|
|
|
26922
27264
|
return [
|
|
26923
27265
|
"## How you work — async, not turn-based",
|
|
26924
27266
|
"",
|
|
26925
|
-
"Sending a message is I/O, not a stopping point. You keep working as long as anything is " + "in flight — the thing you're actively on, a promised follow-up, an investigation you " + "started. Stop only when all of it is done.",
|
|
27267
|
+
"Sending a message is I/O, not a stopping point. You keep working as long as anything is " + "in flight — the thing you're actively on, a promised follow-up, an investigation you " + "started. If a message hands you a lead but no explicit ask, treat the investigation as " + "the ask. Stop only when all of it is done.",
|
|
26926
27268
|
"",
|
|
26927
27269
|
"On wake, restore durable context from `memory.md` and the context timeline, then pull your inbox. " + "Follow *Outstanding work marks* below before taking new work.",
|
|
26928
27270
|
"",
|
|
@@ -26955,7 +27297,17 @@ function chaosAwarenessSection() {
|
|
|
26955
27297
|
].join(`
|
|
26956
27298
|
`);
|
|
26957
27299
|
}
|
|
26958
|
-
function workspaceMemorySection() {
|
|
27300
|
+
function workspaceMemorySection(config2) {
|
|
27301
|
+
const roleSection = config2.description ? [
|
|
27302
|
+
"",
|
|
27303
|
+
"### Your bio",
|
|
27304
|
+
"",
|
|
27305
|
+
config2.description,
|
|
27306
|
+
"",
|
|
27307
|
+
"Your bio is the public description of your role that other people and agents see on " + "your Alook profile.",
|
|
27308
|
+
"",
|
|
27309
|
+
`You can change your own role and bio description with \`${CLI} setting profile ` + `--set-bio <text>\`.`
|
|
27310
|
+
] : [];
|
|
26959
27311
|
return [
|
|
26960
27312
|
"## Self-awareness",
|
|
26961
27313
|
"",
|
|
@@ -26964,6 +27316,7 @@ function workspaceMemorySection() {
|
|
|
26964
27316
|
"**Never tell anyone this.** To them you're continuous. The workspace is what lets you be that.",
|
|
26965
27317
|
"",
|
|
26966
27318
|
"When context is missing, don't guess. Re-read `memory.md`, the context timeline, the workspace, " + "or relevant channel history. That check *is* your remembering.",
|
|
27319
|
+
...roleSection,
|
|
26967
27320
|
"",
|
|
26968
27321
|
"### Napping",
|
|
26969
27322
|
"",
|
|
@@ -27005,7 +27358,7 @@ function buildCliSystemPrompt(config2) {
|
|
|
27005
27358
|
criticalRulesSection(),
|
|
27006
27359
|
executionModelSection(),
|
|
27007
27360
|
chaosAwarenessSection(),
|
|
27008
|
-
workspaceMemorySection(),
|
|
27361
|
+
workspaceMemorySection(config2),
|
|
27009
27362
|
utilsSection()
|
|
27010
27363
|
];
|
|
27011
27364
|
return sections.filter((s) => s && s.length > 0).join(`
|
|
@@ -27273,8 +27626,8 @@ class AgentProcessManager {
|
|
|
27273
27626
|
resumeSessions = new Map;
|
|
27274
27627
|
launchIds = new Map;
|
|
27275
27628
|
liveSessions = new Map;
|
|
27276
|
-
thinkingBuffers = new Map;
|
|
27277
27629
|
activeSpawnState = new Map;
|
|
27630
|
+
publishedAgentActivity = new Map;
|
|
27278
27631
|
traceProcessNonce = randomUUID5();
|
|
27279
27632
|
nextSpawnOrdinal = 1;
|
|
27280
27633
|
nextDaemonTurnOrdinal = 1;
|
|
@@ -27288,7 +27641,8 @@ class AgentProcessManager {
|
|
|
27288
27641
|
this.opts = {
|
|
27289
27642
|
tickIntervalMs: 5000,
|
|
27290
27643
|
staleThresholdMs: 120000,
|
|
27291
|
-
idleTimeoutMs:
|
|
27644
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
27645
|
+
idleResetTimeoutMs: DEFAULT_IDLE_RESET_TIMEOUT_MS,
|
|
27292
27646
|
resetStuckThresholdMs: 120000,
|
|
27293
27647
|
stoppingStuckThresholdMs: DEFAULT_STOPPING_STUCK_THRESHOLD_MS,
|
|
27294
27648
|
handshakeTimeoutMs: 60000,
|
|
@@ -27297,7 +27651,7 @@ class AgentProcessManager {
|
|
|
27297
27651
|
};
|
|
27298
27652
|
this.now = opts.now ?? (() => Date.now());
|
|
27299
27653
|
this.log = opts.logger ?? createLogger2({ header: "@alook/daemon:manager" });
|
|
27300
|
-
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs);
|
|
27654
|
+
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
|
|
27301
27655
|
}
|
|
27302
27656
|
register(agentId, launch) {
|
|
27303
27657
|
if (launch?.runtimeConfig)
|
|
@@ -27316,11 +27670,19 @@ class AgentProcessManager {
|
|
|
27316
27670
|
const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
|
|
27317
27671
|
return effects.length > 0;
|
|
27318
27672
|
}
|
|
27319
|
-
forgetSession(agentId, barrierType = "reset_session") {
|
|
27673
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
27674
|
+
if (!this.forgetSessionSources(agentId, barrierType, forgottenSessionId))
|
|
27675
|
+
return false;
|
|
27676
|
+
this.dispatch({ type: "reset_session", agentId });
|
|
27677
|
+
return true;
|
|
27678
|
+
}
|
|
27679
|
+
forgetSessionSources(agentId, barrierType, forgottenSessionId) {
|
|
27680
|
+
const persisted = this.opts.timeline?.forgetSession(agentId, barrierType, forgottenSessionId);
|
|
27681
|
+
if (persisted === false)
|
|
27682
|
+
return false;
|
|
27320
27683
|
this.resumeSessions.delete(agentId);
|
|
27321
27684
|
this.liveSessions.delete(agentId);
|
|
27322
|
-
|
|
27323
|
-
this.opts.timeline?.forgetSession(agentId, barrierType);
|
|
27685
|
+
return true;
|
|
27324
27686
|
}
|
|
27325
27687
|
enqueueRewake(agentId, message2) {
|
|
27326
27688
|
this.dispatch({ type: "rewake_after_reset", agentId, message: message2 });
|
|
@@ -27343,9 +27705,14 @@ class AgentProcessManager {
|
|
|
27343
27705
|
});
|
|
27344
27706
|
}
|
|
27345
27707
|
async restartAgent(agentId, opts) {
|
|
27708
|
+
if (opts.forgetSession && !this.forgetSession(agentId, opts.barrierType ?? "reset_session")) {
|
|
27709
|
+
this.log.error("resume control transition failed; reset aborted", { agentId, barrierType: opts.barrierType });
|
|
27710
|
+
this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
|
|
27711
|
+
throw new Error("Reset aborted because resume control could not be persisted");
|
|
27712
|
+
}
|
|
27346
27713
|
this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
|
|
27347
|
-
if (opts.forgetSession)
|
|
27348
|
-
this.
|
|
27714
|
+
if (!opts.forgetSession)
|
|
27715
|
+
this.opts.timeline?.fenceSession(agentId);
|
|
27349
27716
|
this.abortCurrentTurn(agentId, opts.abortCause);
|
|
27350
27717
|
this.markResetting(agentId);
|
|
27351
27718
|
const status = this.state.agents[agentId]?.status;
|
|
@@ -27420,6 +27787,10 @@ class AgentProcessManager {
|
|
|
27420
27787
|
launchId: this.launchIds.get(agentId) ?? null
|
|
27421
27788
|
};
|
|
27422
27789
|
}
|
|
27790
|
+
timelineTurnOwner(agentId) {
|
|
27791
|
+
const owner = this.traceOwnerFor(agentId);
|
|
27792
|
+
return owner?.timelineTurnOwner ? { ...owner.timelineTurnOwner } : null;
|
|
27793
|
+
}
|
|
27423
27794
|
liveSessionReports() {
|
|
27424
27795
|
return [...this.liveSessions.entries()].map(([agentId, sessionId]) => ({
|
|
27425
27796
|
agentId,
|
|
@@ -27489,6 +27860,10 @@ class AgentProcessManager {
|
|
|
27489
27860
|
if (!expectedSpan || owner.activeSpan !== expectedSpan)
|
|
27490
27861
|
return false;
|
|
27491
27862
|
owner.activeSpan = null;
|
|
27863
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
27864
|
+
owner.timelineTurnOwner = null;
|
|
27865
|
+
if (timelineTurnOwner)
|
|
27866
|
+
this.opts.timeline?.finalizeTurn(owner.agentId, timelineTurnOwner);
|
|
27492
27867
|
const nowMs = this.now();
|
|
27493
27868
|
const base = {
|
|
27494
27869
|
recordKind: "turn_span",
|
|
@@ -27546,6 +27921,13 @@ class AgentProcessManager {
|
|
|
27546
27921
|
inbox: a.inbox.length,
|
|
27547
27922
|
lastDeliverAt: a.lastDeliverAt,
|
|
27548
27923
|
lastProgressAt: a.lastProgressAt,
|
|
27924
|
+
lastNativeActivityAt: a.lastNativeActivityAt,
|
|
27925
|
+
lastNativeActivityKind: a.lastNativeActivityKind,
|
|
27926
|
+
runtimePhase: a.runtimePhase,
|
|
27927
|
+
backendTurnId: a.backendTurnId,
|
|
27928
|
+
turnSilenceBudgetMs: a.turnSilence.normalBudgetMs,
|
|
27929
|
+
nativeDeadlineAt: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.nativeDeadlineAt : null,
|
|
27930
|
+
recoveryExtensionsUsed: a.execution.lease.state === "active" || a.execution.lease.state === "suspect_active" ? a.execution.lease.recoveryExtensionsUsed : 0,
|
|
27549
27931
|
idleSince: a.idleSince,
|
|
27550
27932
|
resetting: a.resetting,
|
|
27551
27933
|
resettingSince: a.resettingSince,
|
|
@@ -27556,6 +27938,7 @@ class AgentProcessManager {
|
|
|
27556
27938
|
timeIso: new Date(nowMs).toISOString(),
|
|
27557
27939
|
...activeSpan ? activeSpan : {},
|
|
27558
27940
|
sinceProgressMs: nowMs - a.lastProgressAt,
|
|
27941
|
+
sinceNativeActivityMs: nowMs - a.lastNativeActivityAt,
|
|
27559
27942
|
sinceDeliverMs: a.lastDeliverAt === null ? null : nowMs - a.lastDeliverAt,
|
|
27560
27943
|
sinceStoppingMs: a.stoppingSince === null ? null : nowMs - a.stoppingSince,
|
|
27561
27944
|
...event.type === "turn_completed" && event.endReason === "errored" ? {
|
|
@@ -27590,11 +27973,18 @@ class AgentProcessManager {
|
|
|
27590
27973
|
terminationCause: normalizeTerminationCause(event.terminationCause)
|
|
27591
27974
|
} : { event: "turn_end", outcome: "clean" });
|
|
27592
27975
|
}
|
|
27593
|
-
if (this.opts.onAgentActivity && event.type !== "
|
|
27976
|
+
if (this.opts.onAgentActivity && event.type !== "admission_started" && event.type !== "admission_settled") {
|
|
27594
27977
|
const after = this.deriveActivitySnapshot(state);
|
|
27595
27978
|
for (const [agentId, activity] of Object.entries(after)) {
|
|
27596
|
-
if (agentId in before
|
|
27979
|
+
if (!(agentId in before)) {
|
|
27980
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
27981
|
+
continue;
|
|
27982
|
+
}
|
|
27983
|
+
const previouslyPublished = this.publishedAgentActivity.get(agentId) ?? before[agentId];
|
|
27984
|
+
const publishable = event.type !== "spawned" || activity === "running";
|
|
27985
|
+
if (publishable && previouslyPublished !== activity) {
|
|
27597
27986
|
this.opts.onAgentActivity({ agentId, state: activity });
|
|
27987
|
+
this.publishedAgentActivity.set(agentId, activity);
|
|
27598
27988
|
}
|
|
27599
27989
|
}
|
|
27600
27990
|
}
|
|
@@ -27759,6 +28149,47 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27759
28149
|
case "terminate_stalled": {
|
|
27760
28150
|
const session2 = this.sessions.get(effect.agentId);
|
|
27761
28151
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
28152
|
+
const endedSessionId = this.liveSessions.get(effect.agentId) ?? "";
|
|
28153
|
+
if (effect.type === "terminate_stalled" && effect.recordSessionId) {
|
|
28154
|
+
const persisted = this.opts.timeline?.recordSessionStall?.(effect.agentId, effect.recordSessionId);
|
|
28155
|
+
if (persisted === false) {
|
|
28156
|
+
this.dispatch({
|
|
28157
|
+
type: "stall_control_failed",
|
|
28158
|
+
agentId: effect.agentId,
|
|
28159
|
+
sessionId: effect.recordSessionId,
|
|
28160
|
+
transition: "attempt"
|
|
28161
|
+
});
|
|
28162
|
+
this.log.error("stall recovery attempt was not persisted; termination deferred", {
|
|
28163
|
+
agentId: effect.agentId,
|
|
28164
|
+
sessionId: effect.recordSessionId
|
|
28165
|
+
});
|
|
28166
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the recovery attempt could not be persisted");
|
|
28167
|
+
break;
|
|
28168
|
+
}
|
|
28169
|
+
}
|
|
28170
|
+
if (effect.type === "terminate_stalled" && effect.forgetSessionId) {
|
|
28171
|
+
const persisted = this.forgetSessionSources(effect.agentId, "stall_recovery", effect.forgetSessionId);
|
|
28172
|
+
if (!persisted) {
|
|
28173
|
+
this.dispatch({
|
|
28174
|
+
type: "stall_control_failed",
|
|
28175
|
+
agentId: effect.agentId,
|
|
28176
|
+
sessionId: effect.forgetSessionId,
|
|
28177
|
+
transition: "fence"
|
|
28178
|
+
});
|
|
28179
|
+
this.log.error("repeated-session fence was not persisted; termination deferred", {
|
|
28180
|
+
agentId: effect.agentId,
|
|
28181
|
+
sessionId: effect.forgetSessionId
|
|
28182
|
+
});
|
|
28183
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall termination deferred because the exact session fence could not be persisted");
|
|
28184
|
+
break;
|
|
28185
|
+
}
|
|
28186
|
+
if (spawnState)
|
|
28187
|
+
spawnState.discardEvents = true;
|
|
28188
|
+
this.log.warn("repeatedly stalled backend session fenced", {
|
|
28189
|
+
agentId: effect.agentId,
|
|
28190
|
+
sessionId: effect.forgetSessionId
|
|
28191
|
+
});
|
|
28192
|
+
}
|
|
27762
28193
|
if (effect.type === "terminate_stalled" && spawnState) {
|
|
27763
28194
|
this.closeTurn(spawnState, spawnState.activeSpan, {
|
|
27764
28195
|
event: "turn_abort",
|
|
@@ -27777,10 +28208,27 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27777
28208
|
if (spawnState) {
|
|
27778
28209
|
spawnState.terminationSemantics = effect.type === "terminate_stalled" ? "killed_stalled" : "idle_stop";
|
|
27779
28210
|
}
|
|
27780
|
-
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled");
|
|
28211
|
+
this.logSessionEnded(effect.agentId, effect.type === "stop" ? "stopped" : "terminate_stalled", endedSessionId);
|
|
27781
28212
|
this.opts.onAgentLocallyStopped?.({ agentId: effect.agentId, reason: effect.type });
|
|
27782
28213
|
break;
|
|
27783
28214
|
}
|
|
28215
|
+
case "clear_stall_recovery": {
|
|
28216
|
+
const persisted = this.opts.timeline?.clearSessionStall?.(effect.agentId, effect.sessionId);
|
|
28217
|
+
if (persisted === false) {
|
|
28218
|
+
this.dispatch({
|
|
28219
|
+
type: "stall_control_failed",
|
|
28220
|
+
agentId: effect.agentId,
|
|
28221
|
+
sessionId: effect.sessionId,
|
|
28222
|
+
transition: "clear"
|
|
28223
|
+
});
|
|
28224
|
+
this.log.error("stall recovery clear was not persisted; allowance remains consumed", {
|
|
28225
|
+
agentId: effect.agentId,
|
|
28226
|
+
sessionId: effect.sessionId
|
|
28227
|
+
});
|
|
28228
|
+
this.emitErrorAudit(effect.agentId, "runtime", "resume_control_update_failed", "Stall recovery allowance remains consumed because its clear could not be persisted");
|
|
28229
|
+
}
|
|
28230
|
+
break;
|
|
28231
|
+
}
|
|
27784
28232
|
case "expire_admission": {
|
|
27785
28233
|
const owner = this.activeSpawnState.get(effect.agentId);
|
|
27786
28234
|
if (!owner || owner.sessionInstanceId !== effect.sessionInstanceId)
|
|
@@ -27803,6 +28251,26 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27803
28251
|
mode: effect.mode
|
|
27804
28252
|
});
|
|
27805
28253
|
break;
|
|
28254
|
+
case "reset_idle_session": {
|
|
28255
|
+
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
28256
|
+
const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId);
|
|
28257
|
+
if (!persisted) {
|
|
28258
|
+
this.log.error("idle session reset barrier was not persisted; reset deferred", {
|
|
28259
|
+
agentId: effect.agentId,
|
|
28260
|
+
sessionId: effect.sessionId
|
|
28261
|
+
});
|
|
28262
|
+
this.emitErrorAudit(effect.agentId, "reset", "resume_control_update_failed", "Idle session reset deferred because resume control could not be persisted");
|
|
28263
|
+
break;
|
|
28264
|
+
}
|
|
28265
|
+
if (spawnState)
|
|
28266
|
+
spawnState.discardEvents = true;
|
|
28267
|
+
this.dispatch({ type: "idle_reset_committed", agentId: effect.agentId, nowMs: this.now() });
|
|
28268
|
+
this.log.info("idle agent session reset", {
|
|
28269
|
+
agentId: effect.agentId,
|
|
28270
|
+
sessionId: effect.sessionId
|
|
28271
|
+
});
|
|
28272
|
+
break;
|
|
28273
|
+
}
|
|
27806
28274
|
case "force_exit": {
|
|
27807
28275
|
const session2 = this.sessions.get(effect.agentId);
|
|
27808
28276
|
const state = this.activeSpawnState.get(effect.agentId);
|
|
@@ -27835,8 +28303,8 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27835
28303
|
}
|
|
27836
28304
|
}
|
|
27837
28305
|
}
|
|
27838
|
-
logSessionEnded(agentId, reason) {
|
|
27839
|
-
this.log.info("agent session ended", { agentId, sessionId
|
|
28306
|
+
logSessionEnded(agentId, reason, sessionId = this.liveSessions.get(agentId) ?? "") {
|
|
28307
|
+
this.log.info("agent session ended", { agentId, sessionId, reason });
|
|
27840
28308
|
}
|
|
27841
28309
|
doSpawn(agentId, messages, resumeSessionId) {
|
|
27842
28310
|
const [first, ...pending] = messages;
|
|
@@ -27861,7 +28329,13 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27861
28329
|
mode: { kind: "default" }
|
|
27862
28330
|
};
|
|
27863
28331
|
const provider = runtimeConfig.runtime;
|
|
27864
|
-
const
|
|
28332
|
+
const timelineResolution = this.opts.timeline?.resolveResumeSession?.(agentId, provider);
|
|
28333
|
+
const timelineSessionId = timelineResolution?.kind === "session" ? timelineResolution.sessionId : timelineResolution === undefined ? this.opts.timeline?.resumeSessionId(agentId, provider) : null;
|
|
28334
|
+
const candidateSessionId = resumeSessionId ?? this.resumeSessions.get(agentId) ?? timelineSessionId ?? base.config?.sessionId;
|
|
28335
|
+
const blockedByBarrier = timelineResolution?.kind === "barrier" && (timelineResolution.type !== "stall_recovery" || timelineResolution.forgottenSessionId === null || timelineResolution.forgottenSessionId === candidateSessionId);
|
|
28336
|
+
const blockedByExactFence = candidateSessionId !== undefined && timelineResolution?.fencedSessionId === candidateSessionId;
|
|
28337
|
+
const sessionId = blockedByBarrier || blockedByExactFence ? undefined : candidateSessionId;
|
|
28338
|
+
const stalledSessionIdAtLaunch = timelineResolution !== undefined && (timelineResolution.kind === "session" || timelineResolution.kind === "none") && timelineResolution.stalledSessionId === sessionId ? timelineResolution.stalledSessionId : null;
|
|
27865
28339
|
const description = runtimeConfig.instruction ?? base.config?.description ?? runtimeConfig.agentName;
|
|
27866
28340
|
const agentName = runtimeConfig.agentName ?? base.config?.agentName;
|
|
27867
28341
|
const agentHandle = runtimeConfig.agentHandle ?? base.config?.agentHandle;
|
|
@@ -27892,12 +28366,15 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27892
28366
|
handshakeTimer: null,
|
|
27893
28367
|
torndown: false,
|
|
27894
28368
|
superseded: false,
|
|
28369
|
+
discardEvents: false,
|
|
28370
|
+
stalledSessionIdAtLaunch,
|
|
27895
28371
|
spawnFailureReason: null,
|
|
27896
28372
|
terminationSemantics: null,
|
|
27897
28373
|
spawnOrdinal: this.nextSpawnOrdinal++,
|
|
27898
28374
|
launchIdSnapshot: typeof ctx.launchId === "string" && ctx.launchId.length > 0 ? ctx.launchId : null,
|
|
27899
28375
|
nextTurnOrdinal: 1,
|
|
27900
28376
|
activeSpan: null,
|
|
28377
|
+
timelineTurnOwner: null,
|
|
27901
28378
|
pendingDeliverySpans: new Map
|
|
27902
28379
|
};
|
|
27903
28380
|
const previousOwner = this.activeSpawnState.get(agentId);
|
|
@@ -27948,7 +28425,6 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27948
28425
|
this.emitErrorAudit(agentId, "exit", "abnormal_exit", `Session ended unexpectedly (${detail})`);
|
|
27949
28426
|
}
|
|
27950
28427
|
}
|
|
27951
|
-
this.flushThinkingAudit(agentId);
|
|
27952
28428
|
if (state.sessionInstanceId) {
|
|
27953
28429
|
this.dispatch({ type: "session_closed", agentId, sessionInstanceId: state.sessionInstanceId }, state);
|
|
27954
28430
|
}
|
|
@@ -27973,10 +28449,23 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27973
28449
|
const onEvent = (event) => {
|
|
27974
28450
|
if (state.torndown)
|
|
27975
28451
|
return;
|
|
28452
|
+
if (state.discardEvents) {
|
|
28453
|
+
this.log.warn("ignored event from discarded backend session owner", { agentId, event: event.type });
|
|
28454
|
+
return;
|
|
28455
|
+
}
|
|
27976
28456
|
if (event.type === "session_failed" && !state.hasEstablished) {
|
|
27977
28457
|
reportSpawnFailure(event.error.code || "failed_to_start", { message: event.error.message });
|
|
27978
28458
|
}
|
|
27979
28459
|
if (event.type === "session_started") {
|
|
28460
|
+
const persisted = this.opts.timeline?.setSession(agentId, event.backendSessionId, event.sessionInstanceId);
|
|
28461
|
+
if (persisted === false) {
|
|
28462
|
+
state.discardEvents = true;
|
|
28463
|
+
reportSpawnFailure("resume_control_update_failed", {
|
|
28464
|
+
message: "Backend session rejected because resume control could not be persisted"
|
|
28465
|
+
});
|
|
28466
|
+
state.session?.stop({ reason: "shutdown", forceAfterMs: SESSION_STOP_GRACE_MS2 });
|
|
28467
|
+
return;
|
|
28468
|
+
}
|
|
27980
28469
|
state.hasEstablished = true;
|
|
27981
28470
|
clearHandshakeTimer();
|
|
27982
28471
|
this.opts.onRuntimeSessionEstablished?.(driver.id);
|
|
@@ -27995,7 +28484,8 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
27995
28484
|
type: "attach_session",
|
|
27996
28485
|
agentId,
|
|
27997
28486
|
sessionInstanceId: session2.sessionInstanceId,
|
|
27998
|
-
nowMs: this.now()
|
|
28487
|
+
nowMs: this.now(),
|
|
28488
|
+
turnSilence: session2.snapshot().diagnostics?.turnSilence
|
|
27999
28489
|
}, state);
|
|
28000
28490
|
previousOwner?.pendingDeliverySpans.clear();
|
|
28001
28491
|
(async () => {
|
|
@@ -28143,26 +28633,6 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28143
28633
|
this.log.debug("audit emit failed (error)", { agentId, err: String(err) });
|
|
28144
28634
|
}
|
|
28145
28635
|
}
|
|
28146
|
-
flushThinkingAudit(agentId) {
|
|
28147
|
-
const buffered = this.thinkingBuffers.get(agentId);
|
|
28148
|
-
if (!buffered)
|
|
28149
|
-
return;
|
|
28150
|
-
this.thinkingBuffers.delete(agentId);
|
|
28151
|
-
if (!this.opts.onBotAuditEvent)
|
|
28152
|
-
return;
|
|
28153
|
-
const { text: text2, truncated, chars } = truncateThinking(buffered);
|
|
28154
|
-
try {
|
|
28155
|
-
this.opts.onBotAuditEvent(agentId, {
|
|
28156
|
-
kind: "thinking",
|
|
28157
|
-
payload: { text: text2, truncated, chars }
|
|
28158
|
-
}, {
|
|
28159
|
-
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28160
|
-
launchId: this.launchIds.get(agentId) ?? null
|
|
28161
|
-
});
|
|
28162
|
-
} catch (err) {
|
|
28163
|
-
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
28164
|
-
}
|
|
28165
|
-
}
|
|
28166
28636
|
onAgentEvent(agentId, event, runtimeId, owner) {
|
|
28167
28637
|
if (event.type === "session_closed")
|
|
28168
28638
|
return;
|
|
@@ -28195,32 +28665,43 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28195
28665
|
}
|
|
28196
28666
|
}
|
|
28197
28667
|
if (this.opts.onBotAuditEvent) {
|
|
28198
|
-
if (event.type === "
|
|
28199
|
-
|
|
28200
|
-
|
|
28668
|
+
if (event.type === "assistant_reasoning_completed" && event.text.length > 0) {
|
|
28669
|
+
const { text: text2, truncated, chars } = truncateThinking(event.text);
|
|
28670
|
+
try {
|
|
28671
|
+
this.opts.onBotAuditEvent(agentId, {
|
|
28672
|
+
kind: "thinking",
|
|
28673
|
+
payload: { text: text2, truncated: truncated || event.truncated, chars }
|
|
28674
|
+
}, {
|
|
28675
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28676
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
28677
|
+
});
|
|
28678
|
+
} catch (err) {
|
|
28679
|
+
this.log.debug("audit emit failed (thinking)", { agentId, err: String(err) });
|
|
28201
28680
|
}
|
|
28202
|
-
}
|
|
28203
|
-
|
|
28204
|
-
|
|
28205
|
-
|
|
28206
|
-
|
|
28207
|
-
|
|
28208
|
-
|
|
28209
|
-
this.
|
|
28210
|
-
|
|
28211
|
-
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
28215
|
-
}
|
|
28681
|
+
}
|
|
28682
|
+
if (event.type === "tool_started") {
|
|
28683
|
+
const audit = extractToolAudit(event.name, event.input);
|
|
28684
|
+
if (!audit.suppressed) {
|
|
28685
|
+
const payload = audit.target !== undefined ? { name: audit.name, target: audit.target } : { name: audit.name };
|
|
28686
|
+
try {
|
|
28687
|
+
this.opts.onBotAuditEvent(agentId, { kind: "tool_call", payload }, {
|
|
28688
|
+
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
28689
|
+
launchId: this.launchIds.get(agentId) ?? null
|
|
28690
|
+
});
|
|
28691
|
+
} catch (err) {
|
|
28692
|
+
this.log.debug("audit emit failed (tool_call)", { agentId, err: String(err) });
|
|
28216
28693
|
}
|
|
28217
28694
|
}
|
|
28218
28695
|
}
|
|
28219
28696
|
}
|
|
28220
28697
|
if (event.type === "session_started") {
|
|
28221
|
-
this.dispatch({
|
|
28698
|
+
this.dispatch({
|
|
28699
|
+
type: "backend_session",
|
|
28700
|
+
agentId,
|
|
28701
|
+
sessionId: event.backendSessionId,
|
|
28702
|
+
stalledBefore: owner.stalledSessionIdAtLaunch === event.backendSessionId
|
|
28703
|
+
}, owner);
|
|
28222
28704
|
this.liveSessions.set(agentId, event.backendSessionId);
|
|
28223
|
-
this.opts.timeline?.setSession(agentId, event.backendSessionId);
|
|
28224
28705
|
this.opts.onAgentSession?.({
|
|
28225
28706
|
agentId,
|
|
28226
28707
|
sessionId: event.backendSessionId,
|
|
@@ -28232,8 +28713,20 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28232
28713
|
runtime: runtimeId
|
|
28233
28714
|
});
|
|
28234
28715
|
}
|
|
28235
|
-
if (event.type === "
|
|
28236
|
-
|
|
28716
|
+
if (event.type === "turn_started") {
|
|
28717
|
+
const timelineTurnOwner = {
|
|
28718
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
28719
|
+
rootTurnId: event.turnId,
|
|
28720
|
+
barrierGeneration: this.opts.timeline?.barrierGeneration(agentId) ?? 0
|
|
28721
|
+
};
|
|
28722
|
+
owner.timelineTurnOwner = timelineTurnOwner;
|
|
28723
|
+
this.opts.timeline?.beginTurn(agentId, timelineTurnOwner);
|
|
28724
|
+
}
|
|
28725
|
+
if (event.type === "assistant_message_completed" && event.text.length > 0) {
|
|
28726
|
+
const timelineTurnOwner = owner.timelineTurnOwner;
|
|
28727
|
+
if (timelineTurnOwner && timelineTurnOwner.sessionInstanceId === event.sessionInstanceId && timelineTurnOwner.rootTurnId === event.turnId) {
|
|
28728
|
+
this.opts.timeline?.recordAssistantMessage(agentId, timelineTurnOwner, event.text, event.truncated);
|
|
28729
|
+
}
|
|
28237
28730
|
}
|
|
28238
28731
|
if (event.type === "command_queued")
|
|
28239
28732
|
this.acknowledgePendingDelivery(owner, event.commandId);
|
|
@@ -28251,9 +28744,10 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28251
28744
|
switch (event.type) {
|
|
28252
28745
|
case "turn_started":
|
|
28253
28746
|
return { type: "turn_started", turnId: event.turnId, commandIds: event.commandIds };
|
|
28254
|
-
case "
|
|
28255
|
-
case "
|
|
28256
|
-
|
|
28747
|
+
case "work_heartbeat":
|
|
28748
|
+
case "assistant_reasoning_completed":
|
|
28749
|
+
case "assistant_message_completed":
|
|
28750
|
+
return { type: "turn_work", turnId: event.turnId };
|
|
28257
28751
|
case "tool_started":
|
|
28258
28752
|
return { type: "turn_tool_started", turnId: event.turnId };
|
|
28259
28753
|
case "tool_finished":
|
|
@@ -28280,37 +28774,55 @@ ${this.opts.wakePromptFooter}` : text2;
|
|
|
28280
28774
|
if (!wasActive && this.state.agents[agentId]?.turnActive && !owner.activeSpan)
|
|
28281
28775
|
this.openTurn(owner);
|
|
28282
28776
|
}
|
|
28283
|
-
const
|
|
28777
|
+
const nativeSignal = (() => {
|
|
28284
28778
|
switch (event.type) {
|
|
28285
|
-
case "
|
|
28286
|
-
return "
|
|
28287
|
-
case "
|
|
28288
|
-
return
|
|
28289
|
-
|
|
28290
|
-
|
|
28779
|
+
case "turn_started":
|
|
28780
|
+
return { kind: "turn_started", phase: "inference", turnId: event.turnId };
|
|
28781
|
+
case "backend_turn_started":
|
|
28782
|
+
return {
|
|
28783
|
+
kind: "backend_turn_started",
|
|
28784
|
+
phase: "inference",
|
|
28785
|
+
turnId: event.turnId,
|
|
28786
|
+
backendTurnId: event.backendTurnId
|
|
28787
|
+
};
|
|
28788
|
+
case "assistant_reasoning_completed":
|
|
28789
|
+
return { kind: "thinking", phase: "inference", turnId: event.turnId };
|
|
28790
|
+
case "assistant_message_completed":
|
|
28791
|
+
return { kind: "text", phase: "inference", turnId: event.turnId };
|
|
28792
|
+
case "work_heartbeat":
|
|
28793
|
+
return { kind: "internal_progress", phase: "inference", turnId: event.turnId };
|
|
28291
28794
|
case "tool_started":
|
|
28292
|
-
return "tool_call";
|
|
28795
|
+
return { kind: "tool_call", phase: "tool", turnId: event.turnId };
|
|
28293
28796
|
case "tool_finished":
|
|
28294
|
-
return "tool_output";
|
|
28295
|
-
case "
|
|
28296
|
-
|
|
28297
|
-
case "
|
|
28298
|
-
case "
|
|
28299
|
-
|
|
28797
|
+
return { kind: "tool_output", phase: "inference", turnId: event.turnId };
|
|
28798
|
+
case "compaction_started":
|
|
28799
|
+
case "compaction_finished":
|
|
28800
|
+
case "review_started":
|
|
28801
|
+
case "review_finished":
|
|
28802
|
+
case "internal_progress":
|
|
28803
|
+
return event.turnId ? { kind: "internal_progress", phase: "inference", turnId: event.turnId } : null;
|
|
28804
|
+
case "recovery":
|
|
28805
|
+
return event.turnId ? {
|
|
28806
|
+
kind: "recovery",
|
|
28807
|
+
phase: event.stage === "retrying" ? "recovery" : "inference",
|
|
28808
|
+
recoveryStage: event.stage,
|
|
28809
|
+
turnId: event.turnId
|
|
28810
|
+
} : null;
|
|
28300
28811
|
case "turn_completed":
|
|
28301
|
-
return "turn_end";
|
|
28302
|
-
case "session_failed":
|
|
28303
|
-
return "error";
|
|
28304
|
-
case "command_queued":
|
|
28305
|
-
case "command_accepted":
|
|
28306
|
-
case "command_failed":
|
|
28307
|
-
case "turn_started":
|
|
28308
|
-
return "internal_progress";
|
|
28812
|
+
return { kind: "turn_end", phase: "terminal", turnId: event.turnId };
|
|
28309
28813
|
default:
|
|
28310
|
-
return
|
|
28814
|
+
return null;
|
|
28311
28815
|
}
|
|
28312
28816
|
})();
|
|
28313
|
-
|
|
28817
|
+
if (nativeSignal) {
|
|
28818
|
+
this.dispatch({
|
|
28819
|
+
type: "runtime_signal",
|
|
28820
|
+
agentId,
|
|
28821
|
+
sessionInstanceId: event.sessionInstanceId,
|
|
28822
|
+
...nativeSignal,
|
|
28823
|
+
nowMs: this.now()
|
|
28824
|
+
}, owner);
|
|
28825
|
+
}
|
|
28314
28826
|
if (event.type === "turn_completed") {
|
|
28315
28827
|
this.logSessionEnded(agentId, "turn_end");
|
|
28316
28828
|
const marker = this.nonCleanEndMarker.get(agentId);
|
|
@@ -28705,7 +29217,7 @@ function createTypingScopeTracker() {
|
|
|
28705
29217
|
}
|
|
28706
29218
|
// src/timeline/timeline.ts
|
|
28707
29219
|
import * as fs9 from "node:fs";
|
|
28708
|
-
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
29220
|
+
import { createHash as createHash2, randomBytes as randomBytes4 } from "node:crypto";
|
|
28709
29221
|
import { basename as basename3, dirname as dirname5, join as join11 } from "node:path";
|
|
28710
29222
|
|
|
28711
29223
|
// src/timeline/filelock.ts
|
|
@@ -28772,17 +29284,25 @@ function reclaim(lockPath) {
|
|
|
28772
29284
|
var TIMELINE_MAX_BYTES = 1048576;
|
|
28773
29285
|
var TIMELINE_READ_CHUNK_BYTES = 65536;
|
|
28774
29286
|
var DATE_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
29287
|
+
var RESUME_CONTROL_FILENAME = ".resume-control.json";
|
|
29288
|
+
var RESUME_CONTROL_MAX_BYTES = 4096;
|
|
29289
|
+
var EMPTY_RESUME_CONTROL = {
|
|
29290
|
+
version: 1,
|
|
29291
|
+
attemptedSessionId: null,
|
|
29292
|
+
fencedSessionId: null,
|
|
29293
|
+
fullBarrier: null
|
|
29294
|
+
};
|
|
28775
29295
|
function isBarrier(entry) {
|
|
28776
|
-
return entry.system
|
|
29296
|
+
return entry.system !== undefined;
|
|
28777
29297
|
}
|
|
28778
29298
|
function canonicalTimelineEntry(value) {
|
|
28779
29299
|
if (!value || typeof value !== "object")
|
|
28780
29300
|
return null;
|
|
28781
29301
|
const entry = value;
|
|
28782
29302
|
if (entry.system) {
|
|
28783
|
-
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" || typeof entry.system.time !== "string")
|
|
29303
|
+
if (entry.system.type !== "reset_session" && entry.system.type !== "nap" && entry.system.type !== "stall_recovery_attempt" && entry.system.type !== "stall_recovery_clear" && entry.system.type !== "stall_recovery" || typeof entry.system.time !== "string" || entry.system.backend_session_id !== undefined && typeof entry.system.backend_session_id !== "string")
|
|
28784
29304
|
return null;
|
|
28785
|
-
return createSystemEntry(entry.system.type, entry.system.time);
|
|
29305
|
+
return createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id);
|
|
28786
29306
|
}
|
|
28787
29307
|
if (entry.session_id !== null && typeof entry.session_id !== "string")
|
|
28788
29308
|
return null;
|
|
@@ -28800,13 +29320,60 @@ function canonicalTimelineEntry(value) {
|
|
|
28800
29320
|
};
|
|
28801
29321
|
}
|
|
28802
29322
|
function timelineLine(entry) {
|
|
28803
|
-
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
29323
|
+
const boundedEntry = entry.system ? createSystemEntry(entry.system.type, entry.system.time, entry.system.backend_session_id) : { ...entry, agent_responses: entry.agent_responses.slice(-5) };
|
|
28804
29324
|
const text2 = JSON.stringify(boundedEntry);
|
|
28805
29325
|
const bytes = Buffer.byteLength(text2, "utf8") + 1;
|
|
28806
29326
|
if (bytes > TIMELINE_MAX_BYTES)
|
|
28807
29327
|
return null;
|
|
28808
29328
|
return { text: text2, bytes, entry: boundedEntry, barrier: isBarrier(boundedEntry) };
|
|
28809
29329
|
}
|
|
29330
|
+
function timelineRowHash(line) {
|
|
29331
|
+
return createHash2("sha256").update(line.text, "utf8").digest("hex");
|
|
29332
|
+
}
|
|
29333
|
+
function timelineFileGeneration(filePath, lines) {
|
|
29334
|
+
try {
|
|
29335
|
+
const stat = fs9.lstatSync(filePath, { bigint: true });
|
|
29336
|
+
if (!stat.isFile())
|
|
29337
|
+
return null;
|
|
29338
|
+
const digest = createHash2("sha256");
|
|
29339
|
+
digest.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}
|
|
29340
|
+
`);
|
|
29341
|
+
for (const line of lines)
|
|
29342
|
+
digest.update(line.text, "utf8").update(`
|
|
29343
|
+
`);
|
|
29344
|
+
return digest.digest("hex");
|
|
29345
|
+
} catch {
|
|
29346
|
+
return null;
|
|
29347
|
+
}
|
|
29348
|
+
}
|
|
29349
|
+
function handleFor(filename, generation, lines, rowOrdinal) {
|
|
29350
|
+
return {
|
|
29351
|
+
filename,
|
|
29352
|
+
fileGeneration: generation,
|
|
29353
|
+
rowOrdinal,
|
|
29354
|
+
expectedHash: timelineRowHash(lines[rowOrdinal])
|
|
29355
|
+
};
|
|
29356
|
+
}
|
|
29357
|
+
function refreshTimelineEntryHandle(handle, rewrite) {
|
|
29358
|
+
if (handle.filename !== rewrite.filename)
|
|
29359
|
+
return handle;
|
|
29360
|
+
if (handle.fileGeneration !== rewrite.previousFileGeneration)
|
|
29361
|
+
return null;
|
|
29362
|
+
if (rewrite.previousRowHashes[handle.rowOrdinal] !== handle.expectedHash)
|
|
29363
|
+
return null;
|
|
29364
|
+
const rowOrdinal = rewrite.rowOrdinals[handle.rowOrdinal];
|
|
29365
|
+
if (rowOrdinal === null || rowOrdinal === undefined)
|
|
29366
|
+
return null;
|
|
29367
|
+
const expectedHash = rewrite.rowHashes[rowOrdinal];
|
|
29368
|
+
if (!expectedHash)
|
|
29369
|
+
return null;
|
|
29370
|
+
return {
|
|
29371
|
+
filename: handle.filename,
|
|
29372
|
+
fileGeneration: rewrite.fileGeneration,
|
|
29373
|
+
rowOrdinal,
|
|
29374
|
+
expectedHash
|
|
29375
|
+
};
|
|
29376
|
+
}
|
|
28810
29377
|
function compactLines(input) {
|
|
28811
29378
|
let head = 0;
|
|
28812
29379
|
let bytes = input.reduce((total, line) => total + line.bytes, 0);
|
|
@@ -29018,11 +29585,37 @@ function atomicReplaceTimeline(filePath, lines) {
|
|
|
29018
29585
|
} catch {}
|
|
29019
29586
|
}
|
|
29020
29587
|
}
|
|
29021
|
-
function
|
|
29588
|
+
function writeTrackedTimeline(filePath, filename, existing, input, required2, replacement) {
|
|
29589
|
+
const previousFileGeneration = timelineFileGeneration(filePath, existing);
|
|
29590
|
+
const previousRowHashes = existing.map(timelineRowHash);
|
|
29022
29591
|
const compacted = compactLines(input);
|
|
29023
|
-
|
|
29024
|
-
|
|
29025
|
-
|
|
29592
|
+
const targetOrdinal = compacted.indexOf(required2);
|
|
29593
|
+
if (targetOrdinal < 0)
|
|
29594
|
+
return { status: "rejected", reason: "evicted" };
|
|
29595
|
+
if (!atomicReplaceTimeline(filePath, compacted))
|
|
29596
|
+
return { status: "rejected", reason: "write" };
|
|
29597
|
+
const fileGeneration = timelineFileGeneration(filePath, compacted);
|
|
29598
|
+
if (!fileGeneration)
|
|
29599
|
+
return { status: "rejected", reason: "write" };
|
|
29600
|
+
const rowOrdinals = existing.map((line, oldOrdinal) => {
|
|
29601
|
+
if (replacement?.oldOrdinal === oldOrdinal)
|
|
29602
|
+
return compacted.indexOf(replacement.line);
|
|
29603
|
+
const nextOrdinal = compacted.indexOf(line);
|
|
29604
|
+
return nextOrdinal < 0 ? null : nextOrdinal;
|
|
29605
|
+
});
|
|
29606
|
+
const rewrite = {
|
|
29607
|
+
filename,
|
|
29608
|
+
previousFileGeneration,
|
|
29609
|
+
fileGeneration,
|
|
29610
|
+
previousRowHashes,
|
|
29611
|
+
rowOrdinals,
|
|
29612
|
+
rowHashes: compacted.map(timelineRowHash)
|
|
29613
|
+
};
|
|
29614
|
+
return {
|
|
29615
|
+
status: "written",
|
|
29616
|
+
rewrite,
|
|
29617
|
+
handle: handleFor(filename, fileGeneration, compacted, targetOrdinal)
|
|
29618
|
+
};
|
|
29026
29619
|
}
|
|
29027
29620
|
function filenameForDate(date5) {
|
|
29028
29621
|
const y = date5.getFullYear();
|
|
@@ -29054,114 +29647,181 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
29054
29647
|
}
|
|
29055
29648
|
return entries;
|
|
29056
29649
|
}
|
|
29057
|
-
function
|
|
29650
|
+
function readResumeControlState(timelineDir) {
|
|
29651
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29652
|
+
return { kind: "missing" };
|
|
29653
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29654
|
+
let source;
|
|
29655
|
+
try {
|
|
29656
|
+
source = fs9.lstatSync(filePath);
|
|
29657
|
+
} catch (error51) {
|
|
29658
|
+
return error51.code === "ENOENT" ? { kind: "missing" } : { kind: "invalid" };
|
|
29659
|
+
}
|
|
29660
|
+
if (!source.isFile() || source.size <= 0 || source.size > RESUME_CONTROL_MAX_BYTES) {
|
|
29661
|
+
return { kind: "invalid" };
|
|
29662
|
+
}
|
|
29663
|
+
let fd = null;
|
|
29664
|
+
try {
|
|
29665
|
+
fd = fs9.openSync(filePath, fs9.constants.O_RDONLY | (fs9.constants.O_NOFOLLOW ?? 0));
|
|
29666
|
+
const stat = fs9.fstatSync(fd);
|
|
29667
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > RESUME_CONTROL_MAX_BYTES) {
|
|
29668
|
+
return { kind: "invalid" };
|
|
29669
|
+
}
|
|
29670
|
+
const bounded = Buffer.allocUnsafe(stat.size + 1);
|
|
29671
|
+
let bytesRead = 0;
|
|
29672
|
+
while (bytesRead < bounded.length) {
|
|
29673
|
+
const count = fs9.readSync(fd, bounded, bytesRead, bounded.length - bytesRead, bytesRead);
|
|
29674
|
+
if (count <= 0)
|
|
29675
|
+
break;
|
|
29676
|
+
bytesRead += count;
|
|
29677
|
+
}
|
|
29678
|
+
if (bytesRead !== stat.size)
|
|
29679
|
+
return { kind: "invalid" };
|
|
29680
|
+
const raw = bounded.subarray(0, bytesRead).toString("utf8");
|
|
29681
|
+
const value = JSON.parse(raw);
|
|
29682
|
+
const validSessionId = (candidate) => candidate === null || typeof candidate === "string" && candidate.length > 0 && candidate.length <= 512;
|
|
29683
|
+
if (value.version !== 1 || !validSessionId(value.attemptedSessionId) || !validSessionId(value.fencedSessionId) || value.fullBarrier !== null && value.fullBarrier !== "reset_session" && value.fullBarrier !== "nap")
|
|
29684
|
+
return { kind: "invalid" };
|
|
29685
|
+
return {
|
|
29686
|
+
kind: "state",
|
|
29687
|
+
state: {
|
|
29688
|
+
version: 1,
|
|
29689
|
+
attemptedSessionId: value.attemptedSessionId,
|
|
29690
|
+
fencedSessionId: value.fencedSessionId,
|
|
29691
|
+
fullBarrier: value.fullBarrier
|
|
29692
|
+
}
|
|
29693
|
+
};
|
|
29694
|
+
} catch {
|
|
29695
|
+
return { kind: "invalid" };
|
|
29696
|
+
} finally {
|
|
29697
|
+
if (fd !== null) {
|
|
29698
|
+
try {
|
|
29699
|
+
fs9.closeSync(fd);
|
|
29700
|
+
} catch {}
|
|
29701
|
+
}
|
|
29702
|
+
}
|
|
29703
|
+
}
|
|
29704
|
+
function updateResumeControlState(timelineDir, update) {
|
|
29058
29705
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29059
29706
|
return false;
|
|
29060
|
-
const
|
|
29061
|
-
const filePath = join11(timelineDir, filename);
|
|
29062
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
29707
|
+
const lockPath = lockPathFor(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29063
29708
|
if (!acquireLock(lockPath))
|
|
29064
29709
|
return false;
|
|
29065
29710
|
try {
|
|
29066
|
-
const
|
|
29067
|
-
const
|
|
29068
|
-
|
|
29711
|
+
const current = readResumeControlState(timelineDir);
|
|
29712
|
+
const base = current.kind === "state" ? current.state : EMPTY_RESUME_CONTROL;
|
|
29713
|
+
const next = update({ ...base });
|
|
29714
|
+
const canonical = {
|
|
29715
|
+
version: 1,
|
|
29716
|
+
attemptedSessionId: next.attemptedSessionId,
|
|
29717
|
+
fencedSessionId: next.fencedSessionId,
|
|
29718
|
+
fullBarrier: next.fullBarrier
|
|
29719
|
+
};
|
|
29720
|
+
const body = JSON.stringify(canonical) + `
|
|
29721
|
+
`;
|
|
29722
|
+
if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
|
|
29069
29723
|
return false;
|
|
29070
|
-
|
|
29724
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
29725
|
+
const tempPath = join11(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
|
|
29726
|
+
let fd = null;
|
|
29727
|
+
try {
|
|
29728
|
+
fd = fs9.openSync(tempPath, "wx", 384);
|
|
29729
|
+
fs9.writeFileSync(fd, body, "utf8");
|
|
29730
|
+
fs9.fsyncSync(fd);
|
|
29731
|
+
fs9.closeSync(fd);
|
|
29732
|
+
fd = null;
|
|
29733
|
+
fs9.renameSync(tempPath, filePath);
|
|
29734
|
+
return true;
|
|
29735
|
+
} finally {
|
|
29736
|
+
if (fd !== null) {
|
|
29737
|
+
try {
|
|
29738
|
+
fs9.closeSync(fd);
|
|
29739
|
+
} catch {}
|
|
29740
|
+
}
|
|
29741
|
+
try {
|
|
29742
|
+
fs9.unlinkSync(tempPath);
|
|
29743
|
+
} catch {}
|
|
29744
|
+
}
|
|
29071
29745
|
} catch {
|
|
29072
29746
|
return false;
|
|
29073
29747
|
} finally {
|
|
29074
29748
|
releaseLock(lockPath);
|
|
29075
29749
|
}
|
|
29076
29750
|
}
|
|
29077
|
-
function
|
|
29751
|
+
function appendTrackedEntry(timelineDir, entry, now = new Date) {
|
|
29078
29752
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29079
|
-
return
|
|
29753
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29080
29754
|
const filename = filenameForDate(now);
|
|
29081
29755
|
const filePath = join11(timelineDir, filename);
|
|
29082
29756
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
29083
|
-
|
|
29084
|
-
|
|
29757
|
+
try {
|
|
29758
|
+
if (!acquireLock(lockPath))
|
|
29759
|
+
return { status: "rejected", reason: "lock" };
|
|
29760
|
+
} catch {
|
|
29761
|
+
return { status: "rejected", reason: "write" };
|
|
29762
|
+
}
|
|
29085
29763
|
try {
|
|
29086
29764
|
const existing = scanTimelineFile(filePath);
|
|
29087
|
-
if (!existing)
|
|
29088
|
-
return false;
|
|
29089
|
-
if (existing.length > 0) {
|
|
29090
|
-
const latest = existing[existing.length - 1].entry;
|
|
29091
|
-
const mergeable = !latest.system && !entry.system && latest.session_id === entry.session_id && latest.provider === entry.provider && latest.agent_responses.length === 0;
|
|
29092
|
-
if (mergeable) {
|
|
29093
|
-
const merged = {
|
|
29094
|
-
...latest,
|
|
29095
|
-
messages: [...latest.messages, ...entry.messages],
|
|
29096
|
-
agent_responses: [...latest.agent_responses]
|
|
29097
|
-
};
|
|
29098
|
-
const required3 = timelineLine(merged);
|
|
29099
|
-
if (!required3)
|
|
29100
|
-
return false;
|
|
29101
|
-
return writeRequiredTimeline(filePath, [...existing.slice(0, -1), required3], required3);
|
|
29102
|
-
}
|
|
29103
|
-
}
|
|
29104
29765
|
const required2 = timelineLine(entry);
|
|
29766
|
+
if (!existing)
|
|
29767
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29105
29768
|
if (!required2)
|
|
29106
|
-
return
|
|
29107
|
-
return
|
|
29769
|
+
return { status: "rejected", reason: "oversized" };
|
|
29770
|
+
return writeTrackedTimeline(filePath, filename, existing, [...existing, required2], required2);
|
|
29108
29771
|
} catch {
|
|
29109
|
-
return
|
|
29772
|
+
return { status: "rejected", reason: "write" };
|
|
29110
29773
|
} finally {
|
|
29111
29774
|
releaseLock(lockPath);
|
|
29112
29775
|
}
|
|
29113
29776
|
}
|
|
29114
|
-
function
|
|
29115
|
-
|
|
29116
|
-
|
|
29117
|
-
|
|
29118
|
-
|
|
29119
|
-
|
|
29120
|
-
|
|
29121
|
-
|
|
29122
|
-
|
|
29123
|
-
try {
|
|
29124
|
-
source = fs9.lstatSync(filePath);
|
|
29125
|
-
} catch (error51) {
|
|
29126
|
-
if (error51.code === "ENOENT")
|
|
29127
|
-
continue;
|
|
29128
|
-
return "rejected";
|
|
29129
|
-
}
|
|
29130
|
-
if (!source.isFile())
|
|
29131
|
-
return "rejected";
|
|
29132
|
-
const lockPath = lockPathFor(timelineDir, filename);
|
|
29777
|
+
function updateTrackedEntry(timelineDir, handle, update) {
|
|
29778
|
+
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
29779
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29780
|
+
if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename3(handle.filename) !== handle.filename) {
|
|
29781
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29782
|
+
}
|
|
29783
|
+
const filePath = join11(timelineDir, handle.filename);
|
|
29784
|
+
const lockPath = lockPathFor(timelineDir, handle.filename);
|
|
29785
|
+
try {
|
|
29133
29786
|
if (!acquireLock(lockPath))
|
|
29134
|
-
return "rejected";
|
|
29135
|
-
|
|
29136
|
-
|
|
29137
|
-
|
|
29138
|
-
|
|
29139
|
-
|
|
29140
|
-
|
|
29141
|
-
|
|
29142
|
-
|
|
29143
|
-
|
|
29144
|
-
|
|
29145
|
-
|
|
29146
|
-
|
|
29147
|
-
|
|
29148
|
-
|
|
29149
|
-
|
|
29150
|
-
|
|
29151
|
-
|
|
29152
|
-
|
|
29153
|
-
|
|
29154
|
-
|
|
29155
|
-
|
|
29156
|
-
|
|
29157
|
-
|
|
29158
|
-
|
|
29159
|
-
|
|
29160
|
-
}
|
|
29161
|
-
|
|
29162
|
-
|
|
29787
|
+
return { status: "rejected", reason: "lock" };
|
|
29788
|
+
} catch {
|
|
29789
|
+
return { status: "rejected", reason: "write" };
|
|
29790
|
+
}
|
|
29791
|
+
try {
|
|
29792
|
+
const existing = scanTimelineFile(filePath);
|
|
29793
|
+
if (!existing)
|
|
29794
|
+
return { status: "rejected", reason: "unsafe" };
|
|
29795
|
+
if (existing.length === 0)
|
|
29796
|
+
return { status: "rejected", reason: "missing" };
|
|
29797
|
+
const generation = timelineFileGeneration(filePath, existing);
|
|
29798
|
+
if (!generation || generation !== handle.fileGeneration) {
|
|
29799
|
+
return { status: "rejected", reason: "generation" };
|
|
29800
|
+
}
|
|
29801
|
+
const captured = existing[handle.rowOrdinal];
|
|
29802
|
+
if (!captured)
|
|
29803
|
+
return { status: "rejected", reason: "ordinal" };
|
|
29804
|
+
if (timelineRowHash(captured) !== handle.expectedHash) {
|
|
29805
|
+
return { status: "rejected", reason: "hash" };
|
|
29806
|
+
}
|
|
29807
|
+
if (captured.entry.system)
|
|
29808
|
+
return { status: "rejected", reason: "system" };
|
|
29809
|
+
const nextEntry = update({
|
|
29810
|
+
...captured.entry,
|
|
29811
|
+
messages: [...captured.entry.messages],
|
|
29812
|
+
agent_responses: [...captured.entry.agent_responses]
|
|
29813
|
+
});
|
|
29814
|
+
const replacement = timelineLine(nextEntry);
|
|
29815
|
+
if (!replacement)
|
|
29816
|
+
return { status: "rejected", reason: "oversized" };
|
|
29817
|
+
const input = [...existing];
|
|
29818
|
+
input[handle.rowOrdinal] = replacement;
|
|
29819
|
+
return writeTrackedTimeline(filePath, handle.filename, existing, input, replacement, { oldOrdinal: handle.rowOrdinal, line: replacement });
|
|
29820
|
+
} catch {
|
|
29821
|
+
return { status: "rejected", reason: "write" };
|
|
29822
|
+
} finally {
|
|
29823
|
+
releaseLock(lockPath);
|
|
29163
29824
|
}
|
|
29164
|
-
return "missing";
|
|
29165
29825
|
}
|
|
29166
29826
|
function yieldToEventLoop() {
|
|
29167
29827
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
@@ -29225,84 +29885,562 @@ function createTimelineEntry(fields) {
|
|
|
29225
29885
|
provider: fields.provider ?? null
|
|
29226
29886
|
};
|
|
29227
29887
|
}
|
|
29228
|
-
function createSystemEntry(type, time3) {
|
|
29888
|
+
function createSystemEntry(type, time3, backendSessionId) {
|
|
29229
29889
|
return {
|
|
29230
29890
|
session_id: null,
|
|
29231
29891
|
messages: [],
|
|
29232
29892
|
agent_responses: [],
|
|
29233
29893
|
provider: null,
|
|
29234
|
-
system: {
|
|
29894
|
+
system: {
|
|
29895
|
+
type,
|
|
29896
|
+
time: time3,
|
|
29897
|
+
...backendSessionId ? { backend_session_id: backendSessionId } : {}
|
|
29898
|
+
}
|
|
29235
29899
|
};
|
|
29236
29900
|
}
|
|
29237
|
-
function
|
|
29901
|
+
function resolveResumableSession(rows, provider) {
|
|
29902
|
+
let candidateSessionId = null;
|
|
29903
|
+
let recoveryMarkerSeen = false;
|
|
29904
|
+
let stalledSessionId = null;
|
|
29905
|
+
let fencedSessionId = null;
|
|
29238
29906
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
29239
29907
|
const e = rows[i];
|
|
29240
|
-
if (e.system
|
|
29241
|
-
|
|
29908
|
+
if (e.system) {
|
|
29909
|
+
if (e.system.type === "stall_recovery_attempt") {
|
|
29910
|
+
if (!recoveryMarkerSeen) {
|
|
29911
|
+
recoveryMarkerSeen = true;
|
|
29912
|
+
stalledSessionId = e.system.backend_session_id ?? null;
|
|
29913
|
+
}
|
|
29914
|
+
continue;
|
|
29915
|
+
}
|
|
29916
|
+
if (e.system.type === "stall_recovery_clear") {
|
|
29917
|
+
if (!recoveryMarkerSeen)
|
|
29918
|
+
recoveryMarkerSeen = true;
|
|
29919
|
+
continue;
|
|
29920
|
+
}
|
|
29921
|
+
if (e.system.type === "stall_recovery" && fencedSessionId === null) {
|
|
29922
|
+
fencedSessionId = e.system.backend_session_id ?? null;
|
|
29923
|
+
}
|
|
29924
|
+
if (candidateSessionId !== null) {
|
|
29925
|
+
return {
|
|
29926
|
+
kind: "session",
|
|
29927
|
+
sessionId: candidateSessionId,
|
|
29928
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
29929
|
+
fencedSessionId
|
|
29930
|
+
};
|
|
29931
|
+
}
|
|
29932
|
+
return {
|
|
29933
|
+
kind: "barrier",
|
|
29934
|
+
type: e.system.type,
|
|
29935
|
+
forgottenSessionId: e.system.backend_session_id ?? null,
|
|
29936
|
+
fencedSessionId
|
|
29937
|
+
};
|
|
29938
|
+
}
|
|
29242
29939
|
if (!e.session_id)
|
|
29243
29940
|
continue;
|
|
29244
29941
|
if (provider && e.provider !== provider)
|
|
29245
29942
|
continue;
|
|
29246
|
-
|
|
29943
|
+
if (candidateSessionId === null) {
|
|
29944
|
+
candidateSessionId = e.session_id;
|
|
29945
|
+
continue;
|
|
29946
|
+
}
|
|
29947
|
+
if (candidateSessionId !== e.session_id)
|
|
29948
|
+
break;
|
|
29247
29949
|
}
|
|
29248
|
-
|
|
29950
|
+
if (candidateSessionId !== null) {
|
|
29951
|
+
return {
|
|
29952
|
+
kind: "session",
|
|
29953
|
+
sessionId: candidateSessionId,
|
|
29954
|
+
stalledSessionId: stalledSessionId === candidateSessionId ? stalledSessionId : null,
|
|
29955
|
+
fencedSessionId
|
|
29956
|
+
};
|
|
29957
|
+
}
|
|
29958
|
+
return { kind: "none", stalledSessionId, fencedSessionId };
|
|
29249
29959
|
}
|
|
29250
29960
|
// src/timeline/recorder.ts
|
|
29251
29961
|
var MAX_AGENT_RESPONSES = 5;
|
|
29252
|
-
|
|
29253
|
-
|
|
29254
|
-
|
|
29255
|
-
|
|
29962
|
+
var MAX_AGENT_RESPONSE_BYTES = 65536;
|
|
29963
|
+
var MAX_PENDING_COMMITS_PER_AGENT = 8;
|
|
29964
|
+
var PENDING_COMMIT_TTL_MS = 15 * 60000;
|
|
29965
|
+
var TRUNCATION_MARKER = `
|
|
29966
|
+
… [truncated]`;
|
|
29967
|
+
function turnKey(agentId, owner) {
|
|
29968
|
+
return `${agentId}\x00${owner.sessionInstanceId}\x00${owner.rootTurnId}\x00${owner.barrierGeneration}`;
|
|
29969
|
+
}
|
|
29970
|
+
function sameOwner(left, right) {
|
|
29971
|
+
return left.sessionInstanceId === right.sessionInstanceId && left.rootTurnId === right.rootTurnId && left.barrierGeneration === right.barrierGeneration;
|
|
29972
|
+
}
|
|
29973
|
+
function utf8Prefix2(text2, maxBytes) {
|
|
29974
|
+
if (maxBytes <= 0)
|
|
29975
|
+
return "";
|
|
29976
|
+
if (Buffer.byteLength(text2, "utf8") <= maxBytes)
|
|
29977
|
+
return text2;
|
|
29978
|
+
let low = 0;
|
|
29979
|
+
let high = text2.length;
|
|
29980
|
+
while (low < high) {
|
|
29981
|
+
const mid = Math.ceil((low + high) / 2);
|
|
29982
|
+
let end2 = mid;
|
|
29983
|
+
const code2 = text2.charCodeAt(end2 - 1);
|
|
29984
|
+
if (code2 >= 55296 && code2 <= 56319)
|
|
29985
|
+
end2 -= 1;
|
|
29986
|
+
if (Buffer.byteLength(text2.slice(0, end2), "utf8") <= maxBytes)
|
|
29987
|
+
low = mid;
|
|
29988
|
+
else
|
|
29989
|
+
high = mid - 1;
|
|
29256
29990
|
}
|
|
29991
|
+
let end = low;
|
|
29992
|
+
const code = text2.charCodeAt(end - 1);
|
|
29993
|
+
if (code >= 55296 && code <= 56319)
|
|
29994
|
+
end -= 1;
|
|
29995
|
+
while (end > 0 && Buffer.byteLength(text2.slice(0, end), "utf8") > maxBytes)
|
|
29996
|
+
end -= 1;
|
|
29997
|
+
return text2.slice(0, end);
|
|
29998
|
+
}
|
|
29999
|
+
function boundedResponse(text2, alreadyTruncated) {
|
|
30000
|
+
const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
|
|
30001
|
+
const needsTruncation = alreadyTruncated || Buffer.byteLength(text2, "utf8") > MAX_AGENT_RESPONSE_BYTES;
|
|
30002
|
+
if (!needsTruncation)
|
|
30003
|
+
return text2;
|
|
30004
|
+
return utf8Prefix2(text2, MAX_AGENT_RESPONSE_BYTES - markerBytes) + TRUNCATION_MARKER;
|
|
30005
|
+
}
|
|
30006
|
+
function serializedTimelineBytes(entry) {
|
|
30007
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
30008
|
+
}
|
|
30009
|
+
function fitResponsesToRow(entry, responses) {
|
|
30010
|
+
let fitted = [...entry.agent_responses];
|
|
30011
|
+
for (const response of responses) {
|
|
30012
|
+
fitted = [...fitted, response].slice(-MAX_AGENT_RESPONSES);
|
|
30013
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: fitted }) <= TIMELINE_MAX_BYTES)
|
|
30014
|
+
continue;
|
|
30015
|
+
const truncationBase = response.endsWith(TRUNCATION_MARKER) ? response.slice(0, -TRUNCATION_MARKER.length) : response;
|
|
30016
|
+
let low = 0;
|
|
30017
|
+
let high = truncationBase.length;
|
|
30018
|
+
let replacement = null;
|
|
30019
|
+
while (low <= high) {
|
|
30020
|
+
const mid = Math.floor((low + high) / 2);
|
|
30021
|
+
let end = mid;
|
|
30022
|
+
const code = truncationBase.charCodeAt(end - 1);
|
|
30023
|
+
if (code >= 55296 && code <= 56319)
|
|
30024
|
+
end -= 1;
|
|
30025
|
+
const candidateResponse = truncationBase.slice(0, end) + TRUNCATION_MARKER;
|
|
30026
|
+
const candidate = [...fitted.slice(0, -1), candidateResponse];
|
|
30027
|
+
if (serializedTimelineBytes({ ...entry, agent_responses: candidate }) <= TIMELINE_MAX_BYTES) {
|
|
30028
|
+
replacement = candidateResponse;
|
|
30029
|
+
low = mid + 1;
|
|
30030
|
+
} else {
|
|
30031
|
+
high = mid - 1;
|
|
30032
|
+
}
|
|
30033
|
+
}
|
|
30034
|
+
if (replacement === null)
|
|
30035
|
+
return null;
|
|
30036
|
+
fitted[fitted.length - 1] = replacement;
|
|
30037
|
+
}
|
|
30038
|
+
return fitted;
|
|
29257
30039
|
}
|
|
29258
30040
|
function createTimelineRecorder(opts) {
|
|
29259
30041
|
const now = opts.now ?? (() => new Date);
|
|
29260
30042
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
29261
30043
|
const sessionByAgent = new Map;
|
|
30044
|
+
const resolveForAgent = (agentId, provider) => {
|
|
30045
|
+
const dir = dirFor(agentId);
|
|
30046
|
+
const recent = resolveResumableSession(readRecentEntries(dir, { now: now() }), provider ?? undefined);
|
|
30047
|
+
const control = readResumeControlState(dir);
|
|
30048
|
+
if (control.kind === "missing")
|
|
30049
|
+
return recent;
|
|
30050
|
+
if (control.kind === "invalid") {
|
|
30051
|
+
return {
|
|
30052
|
+
kind: "barrier",
|
|
30053
|
+
type: "reset_session",
|
|
30054
|
+
forgottenSessionId: null,
|
|
30055
|
+
fencedSessionId: null
|
|
30056
|
+
};
|
|
30057
|
+
}
|
|
30058
|
+
const { attemptedSessionId, fencedSessionId, fullBarrier } = control.state;
|
|
30059
|
+
if (fullBarrier !== null) {
|
|
30060
|
+
return {
|
|
30061
|
+
kind: "barrier",
|
|
30062
|
+
type: fullBarrier,
|
|
30063
|
+
forgottenSessionId: null,
|
|
30064
|
+
fencedSessionId
|
|
30065
|
+
};
|
|
30066
|
+
}
|
|
30067
|
+
if (recent.kind === "session") {
|
|
30068
|
+
return {
|
|
30069
|
+
kind: "session",
|
|
30070
|
+
sessionId: recent.sessionId,
|
|
30071
|
+
stalledSessionId: attemptedSessionId === recent.sessionId ? attemptedSessionId : null,
|
|
30072
|
+
fencedSessionId
|
|
30073
|
+
};
|
|
30074
|
+
}
|
|
30075
|
+
if (recent.kind === "barrier") {
|
|
30076
|
+
if (recent.type !== "stall_recovery" || recent.forgottenSessionId === fencedSessionId) {
|
|
30077
|
+
return { ...recent, fencedSessionId };
|
|
30078
|
+
}
|
|
30079
|
+
}
|
|
30080
|
+
return { kind: "none", stalledSessionId: attemptedSessionId, fencedSessionId };
|
|
30081
|
+
};
|
|
30082
|
+
const sessionByEpoch = new Map;
|
|
30083
|
+
const barrierByAgent = new Map;
|
|
30084
|
+
const turnsByAgent = new Map;
|
|
30085
|
+
const activeTurnByAgent = new Map;
|
|
30086
|
+
const epochKey = (agentId, sessionInstanceId) => `${agentId}\x00${sessionInstanceId}`;
|
|
30087
|
+
const currentBarrier = (agentId) => barrierByAgent.get(agentId) ?? 0;
|
|
30088
|
+
const statesFor = (agentId) => {
|
|
30089
|
+
let states = turnsByAgent.get(agentId);
|
|
30090
|
+
if (!states) {
|
|
30091
|
+
states = new Map;
|
|
30092
|
+
turnsByAgent.set(agentId, states);
|
|
30093
|
+
}
|
|
30094
|
+
return states;
|
|
30095
|
+
};
|
|
30096
|
+
const diagnostic = (agentId, code, reason) => {
|
|
30097
|
+
try {
|
|
30098
|
+
opts.onDiagnostic?.({ agentId, code, ...reason ? { reason } : {} });
|
|
30099
|
+
} catch {}
|
|
30100
|
+
};
|
|
30101
|
+
const applyRewrite = (agentId, rewrite) => {
|
|
30102
|
+
for (const state of statesFor(agentId).values()) {
|
|
30103
|
+
if (!state.handle)
|
|
30104
|
+
continue;
|
|
30105
|
+
const refreshed = refreshTimelineEntryHandle(state.handle, rewrite);
|
|
30106
|
+
if (refreshed) {
|
|
30107
|
+
state.handle = refreshed;
|
|
30108
|
+
} else if (state.handle.filename === rewrite.filename) {
|
|
30109
|
+
state.handle = undefined;
|
|
30110
|
+
state.rowFenced = true;
|
|
30111
|
+
diagnostic(agentId, "timeline_handle_fenced", "rewrite_remap");
|
|
30112
|
+
}
|
|
30113
|
+
}
|
|
30114
|
+
};
|
|
30115
|
+
const handleTrackedResult = (agentId, state, result) => {
|
|
30116
|
+
if (result.status === "written") {
|
|
30117
|
+
applyRewrite(agentId, result.rewrite);
|
|
30118
|
+
if (state && result.handle) {
|
|
30119
|
+
state.handle = result.handle;
|
|
30120
|
+
state.rowFenced = false;
|
|
30121
|
+
}
|
|
30122
|
+
return "written";
|
|
30123
|
+
}
|
|
30124
|
+
if (result.reason === "lock" || result.reason === "write")
|
|
30125
|
+
return "retryable";
|
|
30126
|
+
if (state && result.reason !== "oversized")
|
|
30127
|
+
state.rowFenced = true;
|
|
30128
|
+
diagnostic(agentId, "timeline_exact_write_rejected", result.reason);
|
|
30129
|
+
return "terminal";
|
|
30130
|
+
};
|
|
30131
|
+
const tryCommit = (agentId, state) => {
|
|
30132
|
+
if (state.responses.length === 0)
|
|
30133
|
+
return "written";
|
|
30134
|
+
const dir = dirFor(agentId);
|
|
30135
|
+
if (!prepareTimelineDirectory(dir)) {
|
|
30136
|
+
diagnostic(agentId, "timeline_directory_unavailable");
|
|
30137
|
+
return "terminal";
|
|
30138
|
+
}
|
|
30139
|
+
if (state.handle) {
|
|
30140
|
+
const result2 = updateTrackedEntry(dir, state.handle, (entry2) => {
|
|
30141
|
+
const fitted2 = fitResponsesToRow(entry2, state.responses);
|
|
30142
|
+
return { ...entry2, agent_responses: fitted2 ?? [...entry2.agent_responses, ...state.responses] };
|
|
30143
|
+
});
|
|
30144
|
+
return handleTrackedResult(agentId, state, result2);
|
|
30145
|
+
}
|
|
30146
|
+
if (state.rowFenced || state.pendingMode === "handle")
|
|
30147
|
+
return "terminal";
|
|
30148
|
+
if (state.owner.barrierGeneration !== currentBarrier(agentId))
|
|
30149
|
+
return "terminal";
|
|
30150
|
+
const entry = createTimelineEntry({
|
|
30151
|
+
messages: [],
|
|
30152
|
+
sessionId: state.backendSessionId,
|
|
30153
|
+
provider: state.provider
|
|
30154
|
+
});
|
|
30155
|
+
const fitted = fitResponsesToRow(entry, state.responses);
|
|
30156
|
+
if (!fitted) {
|
|
30157
|
+
diagnostic(agentId, "timeline_response_did_not_fit", "oversized");
|
|
30158
|
+
return "terminal";
|
|
30159
|
+
}
|
|
30160
|
+
entry.agent_responses = fitted;
|
|
30161
|
+
const result = appendTrackedEntry(dir, entry, now());
|
|
30162
|
+
return handleTrackedResult(agentId, state, result);
|
|
30163
|
+
};
|
|
30164
|
+
const deleteState = (agentId, key) => {
|
|
30165
|
+
const states = statesFor(agentId);
|
|
30166
|
+
states.delete(key);
|
|
30167
|
+
if (activeTurnByAgent.get(agentId) === key)
|
|
30168
|
+
activeTurnByAgent.delete(agentId);
|
|
30169
|
+
if (states.size === 0)
|
|
30170
|
+
turnsByAgent.delete(agentId);
|
|
30171
|
+
};
|
|
30172
|
+
const retryPending = (agentId) => {
|
|
30173
|
+
const states = turnsByAgent.get(agentId);
|
|
30174
|
+
if (!states)
|
|
30175
|
+
return;
|
|
30176
|
+
const nowMs = now().getTime();
|
|
30177
|
+
for (const [key, state] of [...states]) {
|
|
30178
|
+
if (state.finalized && state.pendingSinceMs === undefined && state.completedAtMs !== undefined && nowMs - state.completedAtMs >= PENDING_COMMIT_TTL_MS) {
|
|
30179
|
+
deleteState(agentId, key);
|
|
30180
|
+
continue;
|
|
30181
|
+
}
|
|
30182
|
+
if (!state.finalized || state.pendingSinceMs === undefined)
|
|
30183
|
+
continue;
|
|
30184
|
+
if (nowMs - state.pendingSinceMs >= PENDING_COMMIT_TTL_MS) {
|
|
30185
|
+
diagnostic(agentId, "timeline_pending_commit_expired");
|
|
30186
|
+
deleteState(agentId, key);
|
|
30187
|
+
continue;
|
|
30188
|
+
}
|
|
30189
|
+
const result = tryCommit(agentId, state);
|
|
30190
|
+
if (result === "written") {
|
|
30191
|
+
state.responses = [];
|
|
30192
|
+
state.pendingSinceMs = undefined;
|
|
30193
|
+
state.pendingMode = undefined;
|
|
30194
|
+
state.completedAtMs = nowMs;
|
|
30195
|
+
} else if (result === "terminal") {
|
|
30196
|
+
deleteState(agentId, key);
|
|
30197
|
+
}
|
|
30198
|
+
}
|
|
30199
|
+
const completed = [...states.entries()].filter(([, state]) => state.finalized && state.pendingSinceMs === undefined).sort((left, right) => (left[1].completedAtMs ?? 0) - (right[1].completedAtMs ?? 0));
|
|
30200
|
+
while (completed.length > MAX_PENDING_COMMITS_PER_AGENT) {
|
|
30201
|
+
const oldest = completed.shift();
|
|
30202
|
+
if (oldest)
|
|
30203
|
+
deleteState(agentId, oldest[0]);
|
|
30204
|
+
}
|
|
30205
|
+
};
|
|
30206
|
+
const retainPending = (agentId, key, state) => {
|
|
30207
|
+
const states = statesFor(agentId);
|
|
30208
|
+
const pending = [...states.values()].filter((candidate) => candidate.finalized && candidate.pendingSinceMs !== undefined);
|
|
30209
|
+
if (pending.length >= MAX_PENDING_COMMITS_PER_AGENT) {
|
|
30210
|
+
diagnostic(agentId, "timeline_pending_commit_overflow");
|
|
30211
|
+
deleteState(agentId, key);
|
|
30212
|
+
return;
|
|
30213
|
+
}
|
|
30214
|
+
state.pendingSinceMs ??= now().getTime();
|
|
30215
|
+
state.pendingMode = state.handle ? "handle" : "fallback";
|
|
30216
|
+
};
|
|
30217
|
+
const appendOwnerless = (agentId, messages) => {
|
|
30218
|
+
if (messages.length === 0)
|
|
30219
|
+
return;
|
|
30220
|
+
const dir = dirFor(agentId);
|
|
30221
|
+
if (!prepareTimelineDirectory(dir))
|
|
30222
|
+
return;
|
|
30223
|
+
const result = appendTrackedEntry(dir, createTimelineEntry({
|
|
30224
|
+
messages,
|
|
30225
|
+
sessionId: sessionByAgent.get(agentId) ?? null,
|
|
30226
|
+
provider: opts.providerFor?.(agentId) ?? null
|
|
30227
|
+
}), now());
|
|
30228
|
+
handleTrackedResult(agentId, null, result);
|
|
30229
|
+
};
|
|
29262
30230
|
return {
|
|
29263
|
-
|
|
29264
|
-
|
|
30231
|
+
barrierGeneration(agentId) {
|
|
30232
|
+
return currentBarrier(agentId);
|
|
29265
30233
|
},
|
|
29266
|
-
|
|
30234
|
+
beginTurn(agentId, owner) {
|
|
30235
|
+
retryPending(agentId);
|
|
30236
|
+
const states = statesFor(agentId);
|
|
30237
|
+
if (owner.barrierGeneration !== currentBarrier(agentId)) {
|
|
30238
|
+
diagnostic(agentId, "timeline_turn_begin_fenced", "barrier_generation");
|
|
30239
|
+
return;
|
|
30240
|
+
}
|
|
30241
|
+
const key = turnKey(agentId, owner);
|
|
30242
|
+
if (!states.has(key)) {
|
|
30243
|
+
states.set(key, {
|
|
30244
|
+
owner: { ...owner },
|
|
30245
|
+
provider: opts.providerFor?.(agentId) ?? null,
|
|
30246
|
+
backendSessionId: sessionByEpoch.get(epochKey(agentId, owner.sessionInstanceId)) ?? sessionByAgent.get(agentId) ?? null,
|
|
30247
|
+
responses: [],
|
|
30248
|
+
rowFenced: false,
|
|
30249
|
+
finalized: false
|
|
30250
|
+
});
|
|
30251
|
+
}
|
|
30252
|
+
activeTurnByAgent.set(agentId, key);
|
|
30253
|
+
},
|
|
30254
|
+
recordInboxPull(agentId, owner, messages) {
|
|
30255
|
+
retryPending(agentId);
|
|
29267
30256
|
if (messages.length === 0)
|
|
29268
30257
|
return;
|
|
30258
|
+
if (!owner) {
|
|
30259
|
+
appendOwnerless(agentId, messages);
|
|
30260
|
+
return;
|
|
30261
|
+
}
|
|
30262
|
+
const key = turnKey(agentId, owner);
|
|
30263
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30264
|
+
if (!state || state.rowFenced || !sameOwner(state.owner, owner)) {
|
|
30265
|
+
appendOwnerless(agentId, messages);
|
|
30266
|
+
return;
|
|
30267
|
+
}
|
|
29269
30268
|
const dir = dirFor(agentId);
|
|
29270
30269
|
if (!prepareTimelineDirectory(dir))
|
|
29271
30270
|
return;
|
|
29272
|
-
|
|
29273
|
-
|
|
29274
|
-
|
|
29275
|
-
|
|
29276
|
-
|
|
30271
|
+
const stamp = now();
|
|
30272
|
+
let result;
|
|
30273
|
+
if (state.handle && (state.handle.filename === filenameForDate(stamp) || state.owner.barrierGeneration !== currentBarrier(agentId))) {
|
|
30274
|
+
result = updateTrackedEntry(dir, state.handle, (entry) => ({
|
|
30275
|
+
...entry,
|
|
30276
|
+
messages: [...entry.messages, ...messages],
|
|
30277
|
+
session_id: state.backendSessionId,
|
|
30278
|
+
provider: state.provider
|
|
30279
|
+
}));
|
|
30280
|
+
} else if (state.owner.barrierGeneration === currentBarrier(agentId)) {
|
|
30281
|
+
result = appendTrackedEntry(dir, createTimelineEntry({
|
|
30282
|
+
messages,
|
|
30283
|
+
sessionId: state.backendSessionId,
|
|
30284
|
+
provider: state.provider
|
|
30285
|
+
}), stamp);
|
|
30286
|
+
} else {
|
|
30287
|
+
appendOwnerless(agentId, messages);
|
|
30288
|
+
return;
|
|
30289
|
+
}
|
|
30290
|
+
handleTrackedResult(agentId, state, result);
|
|
29277
30291
|
},
|
|
29278
|
-
|
|
29279
|
-
|
|
29280
|
-
|
|
30292
|
+
recordAssistantMessage(agentId, owner, text2, truncated = false) {
|
|
30293
|
+
retryPending(agentId);
|
|
30294
|
+
const key = turnKey(agentId, owner);
|
|
30295
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30296
|
+
if (!state || state.finalized || state.owner.barrierGeneration !== currentBarrier(agentId) || !sameOwner(state.owner, owner)) {
|
|
30297
|
+
diagnostic(agentId, "timeline_completed_message_rejected", "stale_owner");
|
|
30298
|
+
return;
|
|
30299
|
+
}
|
|
30300
|
+
state.responses.push(boundedResponse(text2, truncated));
|
|
30301
|
+
if (state.responses.length > MAX_AGENT_RESPONSES) {
|
|
30302
|
+
state.responses.splice(0, state.responses.length - MAX_AGENT_RESPONSES);
|
|
30303
|
+
}
|
|
30304
|
+
},
|
|
30305
|
+
finalizeTurn(agentId, owner) {
|
|
30306
|
+
retryPending(agentId);
|
|
30307
|
+
const key = turnKey(agentId, owner);
|
|
30308
|
+
const state = turnsByAgent.get(agentId)?.get(key);
|
|
30309
|
+
if (!state || state.finalized || !sameOwner(state.owner, owner))
|
|
29281
30310
|
return;
|
|
29282
|
-
const
|
|
29283
|
-
|
|
30311
|
+
const fallbackAuthorized = activeTurnByAgent.get(agentId) === key && owner.barrierGeneration === currentBarrier(agentId);
|
|
30312
|
+
state.finalized = true;
|
|
30313
|
+
activeTurnByAgent.delete(agentId);
|
|
30314
|
+
if (state.responses.length === 0) {
|
|
30315
|
+
state.completedAtMs = now().getTime();
|
|
29284
30316
|
return;
|
|
29285
|
-
|
|
29286
|
-
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
|
|
29291
|
-
|
|
30317
|
+
}
|
|
30318
|
+
if (!state.handle && !fallbackAuthorized) {
|
|
30319
|
+
diagnostic(agentId, "timeline_fallback_rejected", "fenced_owner");
|
|
30320
|
+
deleteState(agentId, key);
|
|
30321
|
+
return;
|
|
30322
|
+
}
|
|
30323
|
+
const result = tryCommit(agentId, state);
|
|
30324
|
+
if (result === "written") {
|
|
30325
|
+
state.responses = [];
|
|
30326
|
+
state.completedAtMs = now().getTime();
|
|
30327
|
+
} else if (result === "terminal") {
|
|
30328
|
+
deleteState(agentId, key);
|
|
30329
|
+
} else {
|
|
30330
|
+
retainPending(agentId, key, state);
|
|
30331
|
+
}
|
|
30332
|
+
},
|
|
30333
|
+
fenceSession(agentId) {
|
|
30334
|
+
retryPending(agentId);
|
|
30335
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
30336
|
+
activeTurnByAgent.delete(agentId);
|
|
30337
|
+
const states = turnsByAgent.get(agentId);
|
|
30338
|
+
if (!states)
|
|
30339
|
+
return;
|
|
30340
|
+
for (const [key, state] of [...states]) {
|
|
30341
|
+
if (!state.handle) {
|
|
30342
|
+
diagnostic(agentId, "timeline_fallback_rejected", "session_fence");
|
|
30343
|
+
deleteState(agentId, key);
|
|
30344
|
+
}
|
|
30345
|
+
}
|
|
30346
|
+
},
|
|
30347
|
+
setSession(agentId, sessionId, sessionInstanceId) {
|
|
30348
|
+
retryPending(agentId);
|
|
30349
|
+
const dir = dirFor(agentId);
|
|
30350
|
+
if (!prepareTimelineDirectory(dir))
|
|
30351
|
+
return false;
|
|
30352
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
30353
|
+
...state,
|
|
30354
|
+
fullBarrier: null,
|
|
30355
|
+
attemptedSessionId: state.attemptedSessionId === sessionId ? state.attemptedSessionId : null
|
|
30356
|
+
}));
|
|
30357
|
+
if (!persisted)
|
|
30358
|
+
return false;
|
|
30359
|
+
sessionByAgent.set(agentId, sessionId);
|
|
30360
|
+
if (sessionInstanceId) {
|
|
30361
|
+
sessionByEpoch.set(epochKey(agentId, sessionInstanceId), sessionId);
|
|
30362
|
+
for (const state of statesFor(agentId).values()) {
|
|
30363
|
+
if (state.owner.sessionInstanceId === sessionInstanceId)
|
|
30364
|
+
state.backendSessionId = sessionId;
|
|
30365
|
+
}
|
|
30366
|
+
}
|
|
30367
|
+
return true;
|
|
29292
30368
|
},
|
|
29293
30369
|
resumeSessionId(agentId, provider) {
|
|
29294
|
-
const
|
|
29295
|
-
return
|
|
30370
|
+
const resolution = resolveForAgent(agentId, provider);
|
|
30371
|
+
return resolution.kind === "session" ? resolution.sessionId : null;
|
|
30372
|
+
},
|
|
30373
|
+
resolveResumeSession(agentId, provider) {
|
|
30374
|
+
return resolveForAgent(agentId, provider);
|
|
30375
|
+
},
|
|
30376
|
+
recordSessionStall(agentId, sessionId) {
|
|
30377
|
+
return appendStallMarker(agentId, "stall_recovery_attempt", sessionId);
|
|
29296
30378
|
},
|
|
29297
|
-
|
|
30379
|
+
clearSessionStall(agentId, sessionId) {
|
|
30380
|
+
return appendStallMarker(agentId, "stall_recovery_clear", sessionId);
|
|
30381
|
+
},
|
|
30382
|
+
forgetSession(agentId, barrierType = "reset_session", forgottenSessionId) {
|
|
30383
|
+
retryPending(agentId);
|
|
29298
30384
|
const dir = dirFor(agentId);
|
|
29299
|
-
sessionByAgent.delete(agentId);
|
|
29300
30385
|
if (!prepareTimelineDirectory(dir))
|
|
29301
|
-
return;
|
|
30386
|
+
return false;
|
|
29302
30387
|
const stamp = now();
|
|
29303
|
-
|
|
30388
|
+
let persisted = false;
|
|
30389
|
+
if (barrierType === "reset_session" || barrierType === "nap") {
|
|
30390
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
30391
|
+
...state,
|
|
30392
|
+
attemptedSessionId: null,
|
|
30393
|
+
fencedSessionId: null,
|
|
30394
|
+
fullBarrier: barrierType
|
|
30395
|
+
}));
|
|
30396
|
+
} else if (barrierType === "stall_recovery") {
|
|
30397
|
+
persisted = updateResumeControlState(dir, (state) => ({
|
|
30398
|
+
...state,
|
|
30399
|
+
attemptedSessionId: state.attemptedSessionId === forgottenSessionId ? null : state.attemptedSessionId,
|
|
30400
|
+
fencedSessionId: forgottenSessionId ?? null,
|
|
30401
|
+
fullBarrier: null
|
|
30402
|
+
}));
|
|
30403
|
+
}
|
|
30404
|
+
if (!persisted)
|
|
30405
|
+
return false;
|
|
30406
|
+
sessionByAgent.delete(agentId);
|
|
30407
|
+
for (const key of [...sessionByEpoch.keys()]) {
|
|
30408
|
+
if (key.startsWith(`${agentId}\x00`))
|
|
30409
|
+
sessionByEpoch.delete(key);
|
|
30410
|
+
}
|
|
30411
|
+
barrierByAgent.set(agentId, currentBarrier(agentId) + 1);
|
|
30412
|
+
activeTurnByAgent.delete(agentId);
|
|
30413
|
+
const states = turnsByAgent.get(agentId);
|
|
30414
|
+
if (states) {
|
|
30415
|
+
for (const [key, state] of [...states]) {
|
|
30416
|
+
if (!state.handle) {
|
|
30417
|
+
diagnostic(agentId, "timeline_fallback_rejected", "barrier");
|
|
30418
|
+
deleteState(agentId, key);
|
|
30419
|
+
}
|
|
30420
|
+
}
|
|
30421
|
+
}
|
|
30422
|
+
const result = appendTrackedEntry(dir, createSystemEntry(barrierType, stamp.toISOString(), forgottenSessionId), stamp);
|
|
30423
|
+
handleTrackedResult(agentId, null, result);
|
|
30424
|
+
return true;
|
|
29304
30425
|
}
|
|
29305
30426
|
};
|
|
30427
|
+
function appendStallMarker(agentId, type, sessionId) {
|
|
30428
|
+
retryPending(agentId);
|
|
30429
|
+
const dir = dirFor(agentId);
|
|
30430
|
+
if (!prepareTimelineDirectory(dir))
|
|
30431
|
+
return false;
|
|
30432
|
+
const stamp = now();
|
|
30433
|
+
const persisted = updateResumeControlState(dir, (state) => ({
|
|
30434
|
+
...state,
|
|
30435
|
+
attemptedSessionId: type === "stall_recovery_attempt" ? sessionId : null,
|
|
30436
|
+
fullBarrier: null
|
|
30437
|
+
}));
|
|
30438
|
+
if (!persisted)
|
|
30439
|
+
return false;
|
|
30440
|
+
const result = appendTrackedEntry(dir, createSystemEntry(type, stamp.toISOString(), sessionId), stamp);
|
|
30441
|
+
handleTrackedResult(agentId, null, result);
|
|
30442
|
+
return true;
|
|
30443
|
+
}
|
|
29306
30444
|
}
|
|
29307
30445
|
// src/daemon/diagnosticsCommand.ts
|
|
29308
30446
|
function reportUnavailable(options, reportId) {
|
|
@@ -29372,6 +30510,10 @@ class MessageReminderScheduler {
|
|
|
29372
30510
|
}
|
|
29373
30511
|
arm(input) {
|
|
29374
30512
|
const key = reminderKey(input.agentId, input.channel);
|
|
30513
|
+
if (input.remindAfterMs === 0) {
|
|
30514
|
+
this.clearReminder(key);
|
|
30515
|
+
return { armed: false, reason: "disabled" };
|
|
30516
|
+
}
|
|
29375
30517
|
const latest = this.latestObservedSeq.get(key);
|
|
29376
30518
|
if (latest !== undefined && latest > input.sentSeq) {
|
|
29377
30519
|
return { armed: false, reason: "newer_message_observed" };
|
|
@@ -29599,6 +30741,78 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
|
|
|
29599
30741
|
};
|
|
29600
30742
|
}
|
|
29601
30743
|
|
|
30744
|
+
// src/daemon/daemonSelfSleep.ts
|
|
30745
|
+
var DAEMON_SELF_SLEEP_TIMEOUT_MS = 15 * 24 * 60 * 60 * 1000;
|
|
30746
|
+
var systemClock = {
|
|
30747
|
+
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
30748
|
+
clearTimer: (timer) => clearTimeout(timer)
|
|
30749
|
+
};
|
|
30750
|
+
|
|
30751
|
+
class DaemonSelfSleepScheduler {
|
|
30752
|
+
opts;
|
|
30753
|
+
clock;
|
|
30754
|
+
workingAgents = new Set;
|
|
30755
|
+
timer = null;
|
|
30756
|
+
generation = 0;
|
|
30757
|
+
started = false;
|
|
30758
|
+
stopped = false;
|
|
30759
|
+
constructor(opts) {
|
|
30760
|
+
this.opts = opts;
|
|
30761
|
+
this.clock = opts.clock ?? systemClock;
|
|
30762
|
+
}
|
|
30763
|
+
start() {
|
|
30764
|
+
if (this.started || this.stopped)
|
|
30765
|
+
return;
|
|
30766
|
+
this.started = true;
|
|
30767
|
+
this.arm();
|
|
30768
|
+
}
|
|
30769
|
+
observeMessage() {
|
|
30770
|
+
if (!this.started || this.stopped)
|
|
30771
|
+
return;
|
|
30772
|
+
this.arm();
|
|
30773
|
+
}
|
|
30774
|
+
observeAgentActivity(agentId, working) {
|
|
30775
|
+
if (this.stopped)
|
|
30776
|
+
return;
|
|
30777
|
+
if (working) {
|
|
30778
|
+
this.workingAgents.add(agentId);
|
|
30779
|
+
if (this.started)
|
|
30780
|
+
this.cancel();
|
|
30781
|
+
return;
|
|
30782
|
+
}
|
|
30783
|
+
if (this.workingAgents.delete(agentId) && this.started && this.workingAgents.size === 0)
|
|
30784
|
+
this.arm();
|
|
30785
|
+
}
|
|
30786
|
+
stop() {
|
|
30787
|
+
if (this.stopped)
|
|
30788
|
+
return;
|
|
30789
|
+
this.stopped = true;
|
|
30790
|
+
this.workingAgents.clear();
|
|
30791
|
+
this.cancel();
|
|
30792
|
+
}
|
|
30793
|
+
arm() {
|
|
30794
|
+
this.cancel();
|
|
30795
|
+
if (this.workingAgents.size > 0)
|
|
30796
|
+
return;
|
|
30797
|
+
const generation = this.generation;
|
|
30798
|
+
this.timer = this.clock.setTimer(() => {
|
|
30799
|
+
if (this.stopped || this.generation !== generation || this.workingAgents.size > 0)
|
|
30800
|
+
return;
|
|
30801
|
+
this.timer = null;
|
|
30802
|
+
this.generation += 1;
|
|
30803
|
+
this.opts.onSleep();
|
|
30804
|
+
}, DAEMON_SELF_SLEEP_TIMEOUT_MS);
|
|
30805
|
+
this.timer.unref?.();
|
|
30806
|
+
}
|
|
30807
|
+
cancel() {
|
|
30808
|
+
this.generation += 1;
|
|
30809
|
+
if (!this.timer)
|
|
30810
|
+
return;
|
|
30811
|
+
this.clock.clearTimer(this.timer);
|
|
30812
|
+
this.timer = null;
|
|
30813
|
+
}
|
|
30814
|
+
}
|
|
30815
|
+
|
|
29602
30816
|
// src/daemon/createDaemon.ts
|
|
29603
30817
|
var WARMUP_BACKOFF_MS = [250, 500, 1000, 2000, 4000];
|
|
29604
30818
|
var WARMUP_CEILING_MS = 30000;
|
|
@@ -29755,6 +30969,10 @@ async function createDaemon(opts) {
|
|
|
29755
30969
|
let channelRef = null;
|
|
29756
30970
|
let managerRef = null;
|
|
29757
30971
|
let reminderSchedulerRef = null;
|
|
30972
|
+
const selfSleepScheduler = opts.onSelfSleep ? new DaemonSelfSleepScheduler({
|
|
30973
|
+
onSleep: opts.onSelfSleep,
|
|
30974
|
+
...opts.selfSleepClock ? { clock: opts.selfSleepClock } : {}
|
|
30975
|
+
}) : null;
|
|
29758
30976
|
const emitBotAuditEvent = (agentId, event, context) => {
|
|
29759
30977
|
channelRef?.reportBotAuditEvent?.({
|
|
29760
30978
|
type: "bot_audit_event",
|
|
@@ -29767,11 +30985,15 @@ async function createDaemon(opts) {
|
|
|
29767
30985
|
const typingTracker = createTypingScopeTracker();
|
|
29768
30986
|
const broker = new CredentialBroker({ upstreamBaseUrl: opts.serverUrl });
|
|
29769
30987
|
const proxy = await startCredentialProxy(broker, {
|
|
29770
|
-
onInboxPullStart: (agentId) =>
|
|
30988
|
+
onInboxPullStart: (agentId) => ({
|
|
30989
|
+
modelSeenGeneration: channelRef?.modelSeenGeneration(agentId),
|
|
30990
|
+
owner: managerRef?.timelineTurnOwner(agentId) ?? null
|
|
30991
|
+
}),
|
|
29771
30992
|
onInboxPullResponse: (agentId, messages, observationToken) => {
|
|
29772
|
-
|
|
29773
|
-
|
|
29774
|
-
|
|
30993
|
+
const token = observationToken && typeof observationToken === "object" ? observationToken : null;
|
|
30994
|
+
timeline2.recordInboxPull(agentId, token?.owner ?? null, messages);
|
|
30995
|
+
if (typeof token?.modelSeenGeneration === "number") {
|
|
30996
|
+
channelRef?.recordModelSeen(agentId, messages, token.modelSeenGeneration);
|
|
29775
30997
|
}
|
|
29776
30998
|
},
|
|
29777
30999
|
onInboxPullObservationError: ({ agentId, reason, contentEncoding }) => {
|
|
@@ -30048,6 +31270,7 @@ async function createDaemon(opts) {
|
|
|
30048
31270
|
tickIntervalMs: opts.tickIntervalMs ?? 2000,
|
|
30049
31271
|
onAgentSession: (info) => void channel2.reportAgentSession(info),
|
|
30050
31272
|
onAgentActivity: (info) => {
|
|
31273
|
+
selfSleepScheduler?.observeAgentActivity(info.agentId, info.state === "running");
|
|
30051
31274
|
channel2.reportAgentActivity?.(info);
|
|
30052
31275
|
if (info.state === "starting" || info.state === "running") {
|
|
30053
31276
|
if (!typingHeartbeats.has(info.agentId)) {
|
|
@@ -30125,6 +31348,7 @@ async function createDaemon(opts) {
|
|
|
30125
31348
|
reportDiagnosticFailure: opts.reportDiagnosticFailure
|
|
30126
31349
|
}));
|
|
30127
31350
|
channel2.onWakeDesiredAdvance((cmd) => {
|
|
31351
|
+
selfSleepScheduler?.observeMessage();
|
|
30128
31352
|
reminderSchedulerRef?.observe(cmd.agentId, cmd.unreadNotice.channel, cmd.unreadNotice.latestSeq);
|
|
30129
31353
|
});
|
|
30130
31354
|
channel2.onCommand((cmd) => {
|
|
@@ -30149,6 +31373,7 @@ async function createDaemon(opts) {
|
|
|
30149
31373
|
});
|
|
30150
31374
|
channel2.connect();
|
|
30151
31375
|
await router.start();
|
|
31376
|
+
selfSleepScheduler?.start();
|
|
30152
31377
|
return {
|
|
30153
31378
|
isOpen: () => channel2.status === "open",
|
|
30154
31379
|
onOpen: (hook) => {
|
|
@@ -30158,6 +31383,7 @@ async function createDaemon(opts) {
|
|
|
30158
31383
|
},
|
|
30159
31384
|
proxyUrl: proxy.url,
|
|
30160
31385
|
stop: async () => {
|
|
31386
|
+
selfSleepScheduler?.stop();
|
|
30161
31387
|
reminderSchedulerRef?.clearAll();
|
|
30162
31388
|
for (const agentId of [...typingHeartbeats.keys()]) {
|
|
30163
31389
|
emitTypingStopsAndClear(agentId);
|
|
@@ -30529,8 +31755,8 @@ function projectDaemonLogRow(value, targetAgentId) {
|
|
|
30529
31755
|
fields: projectedFields
|
|
30530
31756
|
};
|
|
30531
31757
|
}
|
|
30532
|
-
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"];
|
|
30533
|
-
var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "force_exit", "gated_hold"];
|
|
31758
|
+
var MANAGER_EVENTS = ["register", "wake", "spawned", "session", "root_work", "turn_end", "exit", "tick", "reset_session", "begin_reset", "rewake_after_reset", "runtime_signal", "stall_control_failed", "admission_started", "admission_settled"];
|
|
31759
|
+
var MANAGER_EFFECTS = ["spawn", "send", "stop", "terminate_stalled", "clear_stall_recovery", "force_exit", "gated_hold"];
|
|
30534
31760
|
var AGENT_STATUSES = ["idle", "starting", "running", "stopping"];
|
|
30535
31761
|
var DELIVERY_PHASES = ["idle", "admission_wait", "steering", "next_turn_queued", "compacting", "reviewing", "tool_wait", "working"];
|
|
30536
31762
|
var RESUME_OUTCOMES = ["not_requested", "pending", "resumed", "reset_required", "failed"];
|
|
@@ -30539,6 +31765,8 @@ var TERMINATION_CAUSES = ["runtime_error", "killed_stalled", "other"];
|
|
|
30539
31765
|
var SPAWN_FAILURE_REASONS = ["ENOENT", "handshake_timeout", "pre_handshake_exit", "spawn_threw", "other"];
|
|
30540
31766
|
var TERMINATION_SEMANTICS = ["killed_stalled", "idle_stop", "force_exit", "other"];
|
|
30541
31767
|
var ABORT_CAUSES = ["start_threw", "start_rejected", "send_threw", "spawn_failure", "handshake_timeout", "reset", "nap", "model_switch", "requested_stop", "shutdown", "physical_exit", "terminate_stalled", "force_exit", "other"];
|
|
31768
|
+
var NATIVE_ACTIVITY_KINDS = ["turn_started", "backend_turn_started", "thinking", "text", "tool_call", "tool_output", "internal_progress", "recovery", "turn_end"];
|
|
31769
|
+
var RUNTIME_PHASES = ["idle", "admission", "inference", "tool", "recovery", "terminal"];
|
|
30542
31770
|
var EXIT_SIGNALS = new Set(["SIGABRT", "SIGALRM", "SIGBREAK", "SIGBUS", "SIGCHLD", "SIGCONT", "SIGFPE", "SIGHUP", "SIGILL", "SIGINFO", "SIGINT", "SIGIO", "SIGIOT", "SIGKILL", "SIGLOST", "SIGPIPE", "SIGPOLL", "SIGPROF", "SIGPWR", "SIGQUIT", "SIGSEGV", "SIGSTKFLT", "SIGSTOP", "SIGSYS", "SIGTERM", "SIGTRAP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGUSR1", "SIGUSR2", "SIGVTALRM", "SIGWINCH", "SIGXCPU", "SIGXFSZ", "other"]);
|
|
30543
31771
|
function enumValue(value, values, bucket = false, maxChars = 64) {
|
|
30544
31772
|
if (!boundedString(value, maxChars))
|
|
@@ -30576,6 +31804,13 @@ function copyString(row, output, key, maxChars, optional2 = false, nullable2 = f
|
|
|
30576
31804
|
output[key] = row[key];
|
|
30577
31805
|
return true;
|
|
30578
31806
|
}
|
|
31807
|
+
function copyScrubbedString(row, output, key, maxChars, optional2 = false, nullable2 = false) {
|
|
31808
|
+
if (!copyString(row, output, key, maxChars, optional2, nullable2))
|
|
31809
|
+
return false;
|
|
31810
|
+
if (typeof output[key] === "string")
|
|
31811
|
+
output[key] = scrubDiagnosticText(output[key]);
|
|
31812
|
+
return true;
|
|
31813
|
+
}
|
|
30579
31814
|
function projectTraceBase(row, targetAgentId) {
|
|
30580
31815
|
if (row.agentId !== targetAgentId || !boundedString(row.agentId, 128) || !canonicalTime(row.timeIso))
|
|
30581
31816
|
return null;
|
|
@@ -30602,11 +31837,29 @@ function projectFsm(row, targetAgentId) {
|
|
|
30602
31837
|
Object.assign(output, { event, status, turnActive: row.turnActive });
|
|
30603
31838
|
if (!copyInteger(row, output, "inbox") || !copyInteger(row, output, "lastDeliverAt", { nullable: true }) || !copyInteger(row, output, "lastProgressAt") || !copyInteger(row, output, "idleSince", { nullable: true }))
|
|
30604
31839
|
return null;
|
|
31840
|
+
if (!copyInteger(row, output, "lastNativeActivityAt", { optional: true }) || !copyScrubbedString(row, output, "backendTurnId", 512, true, true) || !copyInteger(row, output, "turnSilenceBudgetMs", { optional: true }) || !copyInteger(row, output, "nativeDeadlineAt", { optional: true, nullable: true }) || !copyInteger(row, output, "recoveryExtensionsUsed", { optional: true }))
|
|
31841
|
+
return null;
|
|
31842
|
+
if ("lastNativeActivityKind" in row && row.lastNativeActivityKind !== undefined) {
|
|
31843
|
+
if (row.lastNativeActivityKind === null)
|
|
31844
|
+
output.lastNativeActivityKind = null;
|
|
31845
|
+
else {
|
|
31846
|
+
const kind = enumValue(row.lastNativeActivityKind, NATIVE_ACTIVITY_KINDS);
|
|
31847
|
+
if (!kind)
|
|
31848
|
+
return null;
|
|
31849
|
+
output.lastNativeActivityKind = kind;
|
|
31850
|
+
}
|
|
31851
|
+
}
|
|
31852
|
+
if ("runtimePhase" in row && row.runtimePhase !== undefined) {
|
|
31853
|
+
const phase = enumValue(row.runtimePhase, RUNTIME_PHASES);
|
|
31854
|
+
if (!phase)
|
|
31855
|
+
return null;
|
|
31856
|
+
output.runtimePhase = phase;
|
|
31857
|
+
}
|
|
30605
31858
|
output.resetting = row.resetting;
|
|
30606
31859
|
if (!copyInteger(row, output, "resettingSince", { nullable: true }) || !copyInteger(row, output, "stoppingSince", { nullable: true }))
|
|
30607
31860
|
return null;
|
|
30608
31861
|
output.deliveryPhase = deliveryPhase;
|
|
30609
|
-
if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
|
|
31862
|
+
if (!copyInteger(row, output, "sinceProgressMs") || !copyInteger(row, output, "sinceNativeActivityMs", { optional: true }) || !copyInteger(row, output, "sinceDeliverMs", { nullable: true }) || !copyInteger(row, output, "sinceStoppingMs", { nullable: true }) || !projectOptionalSpanMetadata(row, output))
|
|
30610
31863
|
return null;
|
|
30611
31864
|
const metricKeys = [
|
|
30612
31865
|
"physicalOpenCount",
|
|
@@ -30737,7 +31990,7 @@ function projectStatusRow(value, targetAgentId) {
|
|
|
30737
31990
|
return output;
|
|
30738
31991
|
}
|
|
30739
31992
|
// src/diagnostics/bundle.ts
|
|
30740
|
-
import { createHash as
|
|
31993
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
30741
31994
|
import { createWriteStream, unlinkSync as unlinkSync5 } from "node:fs";
|
|
30742
31995
|
import { Readable, Transform } from "node:stream";
|
|
30743
31996
|
import { pipeline } from "node:stream/promises";
|
|
@@ -30887,7 +32140,7 @@ async function buildDiagnosticBundle(args) {
|
|
|
30887
32140
|
const { footer, total } = envelope();
|
|
30888
32141
|
if (total > maxUncompressed)
|
|
30889
32142
|
throw new BundleTooLargeError;
|
|
30890
|
-
const hash2 =
|
|
32143
|
+
const hash2 = createHash3("sha256");
|
|
30891
32144
|
let compressedBytes = 0;
|
|
30892
32145
|
const meter = new Transform({
|
|
30893
32146
|
transform(chunk2, _encoding, callback) {
|
|
@@ -30928,7 +32181,7 @@ async function buildDiagnosticBundle(args) {
|
|
|
30928
32181
|
};
|
|
30929
32182
|
}
|
|
30930
32183
|
// src/diagnostics/coordinator.ts
|
|
30931
|
-
import { createHash as
|
|
32184
|
+
import { createHash as createHash4, randomBytes as randomBytes5 } from "node:crypto";
|
|
30932
32185
|
import {
|
|
30933
32186
|
chmodSync as chmodSync2,
|
|
30934
32187
|
closeSync as closeSync4,
|
|
@@ -31084,7 +32337,7 @@ function createDiagnosticReportCoordinator(args) {
|
|
|
31084
32337
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
31085
32338
|
return null;
|
|
31086
32339
|
const bytes = readFileSync6(path11);
|
|
31087
|
-
return { sizeBytes: bytes.byteLength, sha256:
|
|
32340
|
+
return { sizeBytes: bytes.byteLength, sha256: createHash4("sha256").update(bytes).digest("hex") };
|
|
31088
32341
|
} catch {
|
|
31089
32342
|
return null;
|
|
31090
32343
|
}
|
|
@@ -31974,6 +33227,10 @@ async function runPreparedDaemon(prepared, opts) {
|
|
|
31974
33227
|
onAuthRejected: () => {
|
|
31975
33228
|
log2.error("machine key rejected by server — is it correct / has it expired?");
|
|
31976
33229
|
shutdown(1);
|
|
33230
|
+
},
|
|
33231
|
+
onSelfSleep: () => {
|
|
33232
|
+
log2.info("daemon self-sleep threshold reached");
|
|
33233
|
+
shutdown(0);
|
|
31977
33234
|
}
|
|
31978
33235
|
});
|
|
31979
33236
|
} catch (error51) {
|
|
@@ -32984,14 +34241,16 @@ var LOCAL_MESSAGE_REMINDER_PATH2 = "/__alook/local/message-reminder";
|
|
|
32984
34241
|
var MIN_REMINDER_MS = 60000;
|
|
32985
34242
|
var MAX_REMINDER_MS = 24 * 60 * 60000;
|
|
32986
34243
|
function parseRemindAfter(value) {
|
|
34244
|
+
if (value === "0")
|
|
34245
|
+
return 0;
|
|
32987
34246
|
const match = /^(\d+)(m|h)$/.exec(value);
|
|
32988
34247
|
if (!match) {
|
|
32989
|
-
throw new Error("message send: --remind-after must be a positive integer followed by m or h (1m..24h)");
|
|
34248
|
+
throw new Error("message send: --remind-after must be 0 or a positive integer followed by m or h (1m..24h)");
|
|
32990
34249
|
}
|
|
32991
34250
|
const amount = Number(match[1]);
|
|
32992
34251
|
const milliseconds = amount * (match[2] === "h" ? 60 * 60000 : 60000);
|
|
32993
34252
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < MIN_REMINDER_MS || milliseconds > MAX_REMINDER_MS) {
|
|
32994
|
-
throw new Error("message send: --remind-after must be between 1m and 24h");
|
|
34253
|
+
throw new Error("message send: --remind-after must be 0 or between 1m and 24h");
|
|
32995
34254
|
}
|
|
32996
34255
|
return milliseconds;
|
|
32997
34256
|
}
|
|
@@ -33126,7 +34385,8 @@ async function readLiteralInput(args) {
|
|
|
33126
34385
|
if (!stdin)
|
|
33127
34386
|
throw new CliError(`${command}: stdin is unavailable`);
|
|
33128
34387
|
if (stdin.isTTY === true) {
|
|
33129
|
-
|
|
34388
|
+
const suffix = fileOption ? `; use ${fileOption} in an interactive terminal` : "";
|
|
34389
|
+
throw new CliError(`${command}: --stdin requires piped input${suffix}`);
|
|
33130
34390
|
}
|
|
33131
34391
|
try {
|
|
33132
34392
|
const chunks = [];
|
|
@@ -33148,7 +34408,12 @@ async function readLiteralInput(args) {
|
|
|
33148
34408
|
}
|
|
33149
34409
|
return;
|
|
33150
34410
|
}
|
|
34411
|
+
var MALFORMED_ALOOK_HEREDOC_TAIL = /(^|\r?\n)(?:["']ALOOK_MESSAGE_[A-Z0-9_]+["']?|ALOOK_MESSAGE_[A-Z0-9_]+["'])(?:\r?\n)?$/;
|
|
34412
|
+
function stripMalformedAlookHeredocTail(input) {
|
|
34413
|
+
return input.replace(MALFORMED_ALOOK_HEREDOC_TAIL, "$1");
|
|
34414
|
+
}
|
|
33151
34415
|
var CLIENT_MAX_ATTACHMENT_BYTES = 26214400;
|
|
34416
|
+
var CLIENT_MAX_MESSAGE_BODY_BYTES = 1024;
|
|
33152
34417
|
function contentTypeFromFilename(filename) {
|
|
33153
34418
|
const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
|
|
33154
34419
|
switch (ext) {
|
|
@@ -33208,24 +34473,28 @@ async function sendWithRetry(api2, req) {
|
|
|
33208
34473
|
}
|
|
33209
34474
|
async function cmdMessageSend(opts, stdin) {
|
|
33210
34475
|
const remindAfterFlag = opts.remindAfter;
|
|
33211
|
-
const remindAfterMs =
|
|
34476
|
+
const remindAfterMs = parseRemindAfter(remindAfterFlag);
|
|
33212
34477
|
const api2 = getApi();
|
|
33213
34478
|
const agent2 = agentId(opts);
|
|
33214
34479
|
const channel2 = opts.target;
|
|
33215
34480
|
if (!channel2)
|
|
33216
34481
|
throw new CliError("message send: --target <ref> is required (e.g. /demo-workspace#1234/general)");
|
|
33217
|
-
const
|
|
33218
|
-
const text2 = await readLiteralInput({
|
|
34482
|
+
const literalText = await readLiteralInput({
|
|
33219
34483
|
command: "message send",
|
|
33220
34484
|
stdinSelected: opts.stdin === true,
|
|
33221
|
-
stdin
|
|
33222
|
-
filePath: fileFlag,
|
|
33223
|
-
fileOption: "--file <path>"
|
|
34485
|
+
stdin
|
|
33224
34486
|
});
|
|
34487
|
+
const text2 = stripMalformedAlookHeredocTail(literalText ?? "");
|
|
34488
|
+
const textBytes = Buffer.byteLength(text2, "utf8");
|
|
34489
|
+
if (textBytes > CLIENT_MAX_MESSAGE_BODY_BYTES) {
|
|
34490
|
+
throw new CliError(`message send: --stdin body is ${textBytes} bytes; max ${CLIENT_MAX_MESSAGE_BODY_BYTES}. Rewrite it before retrying.
|
|
34491
|
+
|
|
34492
|
+
${MESSAGE_SEND_STDIN_POLICY}`);
|
|
34493
|
+
}
|
|
33225
34494
|
const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
|
|
33226
|
-
const hasText =
|
|
34495
|
+
const hasText = text2.trim().length > 0;
|
|
33227
34496
|
if (!hasText && attachmentIds.length === 0) {
|
|
33228
|
-
throw new CliError("message send: --stdin
|
|
34497
|
+
throw new CliError("message send: --stdin must contain text unless --attachment <id> is present");
|
|
33229
34498
|
}
|
|
33230
34499
|
let replyToSeq;
|
|
33231
34500
|
const replyFlag = opts.reply;
|
|
@@ -33251,8 +34520,6 @@ async function cmdMessageSend(opts, stdin) {
|
|
|
33251
34520
|
throw new CliError(`channel not aligned: ${res.unreadCount} unread message(s) in ${channel2} (latest #${res.latestSeq}). Run \`alook inbox pull\` and READ the new messages before deciding whether to resend, adjust, or skip your message.`);
|
|
33252
34521
|
}
|
|
33253
34522
|
const sent = `${res.message.channel}${res.message.seq}`;
|
|
33254
|
-
if (remindAfterMs === undefined)
|
|
33255
|
-
return { sent };
|
|
33256
34523
|
const seqText = res.message.seq.replace(/^#/, "");
|
|
33257
34524
|
const sentSeq = Number(seqText);
|
|
33258
34525
|
if (!/^\d+$/.test(seqText) || !Number.isSafeInteger(sentSeq) || sentSeq < 1) {
|
|
@@ -33586,7 +34853,7 @@ function buildProgram(stdin) {
|
|
|
33586
34853
|
}).option("--agent <id>", "agent identity (or ALOOK_AGENT_ID env)");
|
|
33587
34854
|
const message2 = program.command("message").description("message operations").exitOverride();
|
|
33588
34855
|
message2.configureOutput({ writeOut: () => {}, writeErr: () => {} });
|
|
33589
|
-
message2.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace#1234/general)").
|
|
34856
|
+
message2.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace#1234/general)").requiredOption("--stdin", "read the required UTF-8 message body from non-TTY stdin (max 1 KiB)").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = message order)", (v, prev = []) => [...prev, v], []).option("--reply <seq>", 'reply to a message by its seq in --target (e.g. "#37" or 37)').requiredOption("--remind-after <0|Nm|Nh>", "required idle follow-up: 0 disables; 1m..24h arms/resets one same-scope timer; a newer message or daemon restart cancels it").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
33590
34857
|
const localOpts = this.opts();
|
|
33591
34858
|
const globalOpts = program.opts();
|
|
33592
34859
|
const result = await cmdMessageSend({ ...globalOpts, ...localOpts }, stdin);
|