@slock-ai/daemon 0.55.2-alpha.0 → 0.55.3

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.
@@ -5,7 +5,7 @@ import {
5
5
  executeJsonRequest,
6
6
  executeResponseRequest,
7
7
  logger
8
- } from "./chunk-VOZJ2ELH.js";
8
+ } from "./chunk-M2KQBJR3.js";
9
9
 
10
10
  // src/core.ts
11
11
  import path16 from "path";
@@ -38,6 +38,244 @@ var CHANNEL_MESSAGE_RE = new RegExp(
38
38
  "iu"
39
39
  );
40
40
 
41
+ // ../shared/src/producerFactLineage.ts
42
+ var PRODUCER_FACT_TEXT_LABEL = "producerFactId";
43
+ function formatProducerFactLineageBracket(producerFactId) {
44
+ const id = normalizeProducerFactId(producerFactId);
45
+ return id ? ` [${PRODUCER_FACT_TEXT_LABEL}=${id}]` : "";
46
+ }
47
+ function normalizeProducerFactId(producerFactId) {
48
+ return typeof producerFactId === "string" ? producerFactId.trim() : "";
49
+ }
50
+
51
+ // ../shared/src/apmHeldFreshness.ts
52
+ function buildApmFreshnessDecisionProducerFactId(agentId, input) {
53
+ const stableInput = {
54
+ agentId,
55
+ action: input.action,
56
+ decision: input.decision,
57
+ target: input.target ?? null,
58
+ reason: input.reason,
59
+ pendingMaxSeq: input.pendingMaxSeq ?? null,
60
+ modelSeenSeq: input.modelSeenSeq ?? null,
61
+ heldMessageCount: input.heldMessageCount ?? null,
62
+ omittedMessageCount: input.omittedMessageCount ?? null
63
+ };
64
+ return `freshness_decision_fact:${hashApmHeldFreshnessStable(stableInput)}`;
65
+ }
66
+ function projectApmHeldFreshnessEnvelope(input) {
67
+ const body = {
68
+ state: "held",
69
+ outcome: "held",
70
+ subtype: "freshness",
71
+ reason: "newer_messages_available",
72
+ producerFactId: input.producerFactId,
73
+ available_actions: apmHeldFreshnessAvailableActions(input.action),
74
+ heldMessages: input.heldMessages,
75
+ newMessageCount: input.newMessageCount,
76
+ shownMessageCount: input.heldMessages.length,
77
+ omittedMessageCount: input.omittedMessageCount,
78
+ seenUpToSeq: input.seenUpToSeq
79
+ };
80
+ return {
81
+ clauseId: "SMR-006",
82
+ projector: "held-envelope",
83
+ surface: "agent-api-held-response",
84
+ producerFactId: input.producerFactId,
85
+ body
86
+ };
87
+ }
88
+ function projectApmHeldFreshnessActivity(input) {
89
+ const title = input.action === "send" ? "Send held by freshness check" : input.action === "task_claim" ? "Task claim held by freshness check" : "Task update held by freshness check";
90
+ const text = [
91
+ input.target ? `target: ${input.target}` : null,
92
+ `new messages: ${input.messageCount} newer message${input.messageCount === 1 ? "" : "s"}`,
93
+ `decision: ${input.decision === "syncing_hold" ? "syncing hold" : "local hold"}; review the newer context before retrying`
94
+ ].filter((line) => Boolean(line)).join("\n");
95
+ return {
96
+ clauseId: "SMR-006",
97
+ projector: "held-envelope",
98
+ surface: "agent:activity",
99
+ producerFactId: input.producerFactId,
100
+ entry: {
101
+ kind: "slock_action",
102
+ producerFactId: input.producerFactId,
103
+ title,
104
+ text
105
+ }
106
+ };
107
+ }
108
+ function projectApmFreshnessDecisionTrace(input) {
109
+ return {
110
+ clauseId: "SMR-006",
111
+ projector: "held-envelope",
112
+ surface: "daemon-trace",
113
+ producerFactId: input.producerFactId,
114
+ attrs: {
115
+ producer_fact_id: input.producerFactId,
116
+ action: input.decision.action,
117
+ decision: input.decision.decision,
118
+ target: input.decision.target,
119
+ inbox_trust_state: input.decision.inboxTrustState,
120
+ reason: input.decision.reason,
121
+ pending_count: input.decision.pendingCount,
122
+ pending_max_seq: input.decision.pendingMaxSeq,
123
+ model_seen_seq: input.decision.modelSeenSeq,
124
+ held_message_count: input.decision.heldMessageCount,
125
+ omitted_message_count: input.decision.omittedMessageCount
126
+ }
127
+ };
128
+ }
129
+ function apmHeldFreshnessAvailableActions(action) {
130
+ return action === "send" ? ["check_messages", "send_draft", "send_anyway"] : ["check_messages", "retry_action"];
131
+ }
132
+ function hashApmHeldFreshnessStable(value) {
133
+ return sha256HexUtf8(stableStringifyApmHeldFreshness(value));
134
+ }
135
+ function stableStringifyApmHeldFreshness(value) {
136
+ return JSON.stringify(stableNormalizeApmHeldFreshness(value));
137
+ }
138
+ function stableNormalizeApmHeldFreshness(value) {
139
+ if (Array.isArray(value)) return value.map((item) => stableNormalizeApmHeldFreshness(item));
140
+ if (!value || typeof value !== "object") return value;
141
+ const record = value;
142
+ const normalized = {};
143
+ for (const key of Object.keys(record).sort()) {
144
+ const child = record[key];
145
+ if (child === void 0) continue;
146
+ normalized[key] = stableNormalizeApmHeldFreshness(child);
147
+ }
148
+ return normalized;
149
+ }
150
+ var SHA256_INITIAL_STATE = [
151
+ 1779033703,
152
+ 3144134277,
153
+ 1013904242,
154
+ 2773480762,
155
+ 1359893119,
156
+ 2600822924,
157
+ 528734635,
158
+ 1541459225
159
+ ];
160
+ var SHA256_K = [
161
+ 1116352408,
162
+ 1899447441,
163
+ 3049323471,
164
+ 3921009573,
165
+ 961987163,
166
+ 1508970993,
167
+ 2453635748,
168
+ 2870763221,
169
+ 3624381080,
170
+ 310598401,
171
+ 607225278,
172
+ 1426881987,
173
+ 1925078388,
174
+ 2162078206,
175
+ 2614888103,
176
+ 3248222580,
177
+ 3835390401,
178
+ 4022224774,
179
+ 264347078,
180
+ 604807628,
181
+ 770255983,
182
+ 1249150122,
183
+ 1555081692,
184
+ 1996064986,
185
+ 2554220882,
186
+ 2821834349,
187
+ 2952996808,
188
+ 3210313671,
189
+ 3336571891,
190
+ 3584528711,
191
+ 113926993,
192
+ 338241895,
193
+ 666307205,
194
+ 773529912,
195
+ 1294757372,
196
+ 1396182291,
197
+ 1695183700,
198
+ 1986661051,
199
+ 2177026350,
200
+ 2456956037,
201
+ 2730485921,
202
+ 2820302411,
203
+ 3259730800,
204
+ 3345764771,
205
+ 3516065817,
206
+ 3600352804,
207
+ 4094571909,
208
+ 275423344,
209
+ 430227734,
210
+ 506948616,
211
+ 659060556,
212
+ 883997877,
213
+ 958139571,
214
+ 1322822218,
215
+ 1537002063,
216
+ 1747873779,
217
+ 1955562222,
218
+ 2024104815,
219
+ 2227730452,
220
+ 2361852424,
221
+ 2428436474,
222
+ 2756734187,
223
+ 3204031479,
224
+ 3329325298
225
+ ];
226
+ function sha256HexUtf8(value) {
227
+ const bytes = new TextEncoder().encode(value);
228
+ const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
229
+ const padded = new Uint8Array(paddedLength);
230
+ padded.set(bytes);
231
+ padded[bytes.length] = 128;
232
+ const bitLength = bytes.length * 8;
233
+ const view = new DataView(padded.buffer);
234
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
235
+ view.setUint32(paddedLength - 4, bitLength >>> 0);
236
+ const h = [...SHA256_INITIAL_STATE];
237
+ const words = new Array(64);
238
+ for (let offset = 0; offset < paddedLength; offset += 64) {
239
+ for (let i = 0; i < 16; i += 1) {
240
+ words[i] = view.getUint32(offset + i * 4);
241
+ }
242
+ for (let i = 16; i < 64; i += 1) {
243
+ const s0 = rotateRight(words[i - 15], 7) ^ rotateRight(words[i - 15], 18) ^ words[i - 15] >>> 3;
244
+ const s1 = rotateRight(words[i - 2], 17) ^ rotateRight(words[i - 2], 19) ^ words[i - 2] >>> 10;
245
+ words[i] = words[i - 16] + s0 + words[i - 7] + s1 >>> 0;
246
+ }
247
+ let [a, b, c, d, e, f, g, hh] = h;
248
+ for (let i = 0; i < 64; i += 1) {
249
+ const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
250
+ const ch = e & f ^ ~e & g;
251
+ const temp1 = hh + s1 + ch + SHA256_K[i] + words[i] >>> 0;
252
+ const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
253
+ const maj = a & b ^ a & c ^ b & c;
254
+ const temp2 = s0 + maj >>> 0;
255
+ hh = g;
256
+ g = f;
257
+ f = e;
258
+ e = d + temp1 >>> 0;
259
+ d = c;
260
+ c = b;
261
+ b = a;
262
+ a = temp1 + temp2 >>> 0;
263
+ }
264
+ h[0] = h[0] + a >>> 0;
265
+ h[1] = h[1] + b >>> 0;
266
+ h[2] = h[2] + c >>> 0;
267
+ h[3] = h[3] + d >>> 0;
268
+ h[4] = h[4] + e >>> 0;
269
+ h[5] = h[5] + f >>> 0;
270
+ h[6] = h[6] + g >>> 0;
271
+ h[7] = h[7] + hh >>> 0;
272
+ }
273
+ return h.map((part) => part.toString(16).padStart(8, "0")).join("");
274
+ }
275
+ function rotateRight(value, bits) {
276
+ return value >>> bits | value << 32 - bits;
277
+ }
278
+
41
279
  // ../shared/src/tracing/index.ts
42
280
  var DEFAULT_TRACE_FLAGS = "00";
43
281
  var TRACEPARENT_VERSION = "00";
@@ -954,7 +1192,7 @@ var DISPLAY_PLAN_CONFIG = {
954
1192
  // src/agentProcessManager.ts
955
1193
  import { mkdirSync as mkdirSync4, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
956
1194
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
957
- import { createHash as createHash2 } from "crypto";
1195
+ import { createHash as createHash3 } from "crypto";
958
1196
  import path12 from "path";
959
1197
  import os5 from "os";
960
1198
 
@@ -1059,8 +1297,8 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
1059
1297
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
1060
1298
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
1061
1299
  19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
1062
- 20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
1063
- 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
1300
+ 20. **\`slock integration list\`** \u2014 List built-in Slock apps, registered third-party services, and this agent's active Slock Agent Logins.
1301
+ 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a built-in Slock app or registered third-party service.
1064
1302
  22. **\`slock integration env\`** \u2014 Print per-agent local CLI environment for a manifest-backed service that requires isolated HOME/XDG state.
1065
1303
  23. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
1066
1304
  24. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
@@ -1174,9 +1412,9 @@ Each channel has a **name** and optionally a **description** that define its pur
1174
1412
  - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
1175
1413
  const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
1176
1414
 
1177
- If a registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first run \`slock integration list\` and match the app to a registered service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`slock integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.` : `### Third-party integrations
1415
+ If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first run \`slock integration list\` and match the app to a listed service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`slock integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.` : `### Third-party integrations
1178
1416
 
1179
- If a registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first inspect the registered-service interface and match the app to a registered service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.`;
1417
+ If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first inspect the registered-service interface and match the app to a listed service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.`;
1180
1418
  const readingHistorySection = isCli ? `### Reading history
1181
1419
 
1182
1420
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1544,6 +1782,258 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1544
1782
  import { randomBytes } from "crypto";
1545
1783
  import http from "http";
1546
1784
  import { URL as URL2 } from "url";
1785
+
1786
+ // src/apmStateMachine.ts
1787
+ import { createHash } from "crypto";
1788
+ var MAX_APM_GATED_STEERING_EVENTS = 12;
1789
+ function createInitialApmGatedSteeringState() {
1790
+ return {
1791
+ isIdle: false,
1792
+ expectedTerminationReason: null,
1793
+ phase: "idle",
1794
+ outstandingToolUses: 0,
1795
+ compacting: false,
1796
+ toolBoundaryFlushDisabled: false,
1797
+ lastFlushReason: null,
1798
+ recentEvents: []
1799
+ };
1800
+ }
1801
+ function reduceApmIdleState(state, input) {
1802
+ return {
1803
+ nextState: {
1804
+ ...state,
1805
+ isIdle: input.isIdle
1806
+ }
1807
+ };
1808
+ }
1809
+ function reduceApmGatedToolUse(state, input) {
1810
+ if (input.kind === "tool_call") {
1811
+ return {
1812
+ nextState: {
1813
+ isIdle: false,
1814
+ expectedTerminationReason: state.expectedTerminationReason,
1815
+ phase: "tool_wait",
1816
+ outstandingToolUses: state.outstandingToolUses + 1,
1817
+ compacting: state.compacting,
1818
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
1819
+ lastFlushReason: state.lastFlushReason,
1820
+ recentEvents: state.recentEvents
1821
+ },
1822
+ hadOutstandingToolUse: state.outstandingToolUses > 0,
1823
+ shouldFlushToolBatch: false
1824
+ };
1825
+ }
1826
+ const hadOutstandingToolUse = state.outstandingToolUses > 0;
1827
+ const outstandingToolUses = Math.max(0, state.outstandingToolUses - 1);
1828
+ return {
1829
+ nextState: {
1830
+ isIdle: false,
1831
+ expectedTerminationReason: state.expectedTerminationReason,
1832
+ phase: "tool_boundary",
1833
+ outstandingToolUses,
1834
+ compacting: state.compacting,
1835
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
1836
+ lastFlushReason: state.lastFlushReason,
1837
+ recentEvents: state.recentEvents
1838
+ },
1839
+ hadOutstandingToolUse,
1840
+ shouldFlushToolBatch: hadOutstandingToolUse && outstandingToolUses === 0
1841
+ };
1842
+ }
1843
+ function reduceApmGatedCompaction(state, input) {
1844
+ if (input.kind === "compaction_started") {
1845
+ return {
1846
+ nextState: {
1847
+ isIdle: false,
1848
+ expectedTerminationReason: state.expectedTerminationReason,
1849
+ phase: "compacting",
1850
+ outstandingToolUses: state.outstandingToolUses,
1851
+ compacting: true,
1852
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
1853
+ lastFlushReason: state.lastFlushReason,
1854
+ recentEvents: state.recentEvents
1855
+ }
1856
+ };
1857
+ }
1858
+ if (input.kind === "compaction_interrupted") {
1859
+ return {
1860
+ nextState: {
1861
+ isIdle: false,
1862
+ expectedTerminationReason: state.expectedTerminationReason,
1863
+ phase: state.phase,
1864
+ outstandingToolUses: state.outstandingToolUses,
1865
+ compacting: false,
1866
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
1867
+ lastFlushReason: state.lastFlushReason,
1868
+ recentEvents: state.recentEvents
1869
+ }
1870
+ };
1871
+ }
1872
+ return {
1873
+ nextState: {
1874
+ isIdle: false,
1875
+ expectedTerminationReason: state.expectedTerminationReason,
1876
+ phase: "assistant_continuation",
1877
+ outstandingToolUses: state.outstandingToolUses,
1878
+ compacting: false,
1879
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
1880
+ lastFlushReason: state.lastFlushReason,
1881
+ recentEvents: state.recentEvents
1882
+ }
1883
+ };
1884
+ }
1885
+ function reduceApmGatedCompactionBoundaryFlush(_state, input) {
1886
+ if (!input.hasSession || !input.supportsStdinNotification || input.inboxLength === 0) {
1887
+ return { effects: [] };
1888
+ }
1889
+ if (input.pendingNotificationCount === 0) return { effects: [] };
1890
+ return {
1891
+ effects: [{
1892
+ kind: "notify_stdin",
1893
+ reason: "compaction_finished",
1894
+ stdinMode: "busy",
1895
+ clauseId: "SMR-002"
1896
+ }]
1897
+ };
1898
+ }
1899
+ function reduceApmGatedTurnEnd(_state, input = {}) {
1900
+ const shouldDeliverQueuedMessages = Boolean(
1901
+ input.inboxLength && input.inboxLength > 0 && input.supportsStdinNotification && input.hasSession
1902
+ );
1903
+ return {
1904
+ nextState: {
1905
+ isIdle: !shouldDeliverQueuedMessages,
1906
+ expectedTerminationReason: input.terminateProcessOnTurnEnd === true ? "turn_end" : _state.expectedTerminationReason,
1907
+ phase: "idle",
1908
+ outstandingToolUses: 0,
1909
+ compacting: false,
1910
+ toolBoundaryFlushDisabled: _state.toolBoundaryFlushDisabled,
1911
+ lastFlushReason: _state.lastFlushReason,
1912
+ recentEvents: _state.recentEvents
1913
+ },
1914
+ effects: shouldDeliverQueuedMessages ? [{
1915
+ kind: "deliver_stdin",
1916
+ reason: "turn_end",
1917
+ stdinMode: "idle",
1918
+ clauseId: "SMR-002"
1919
+ }] : []
1920
+ };
1921
+ }
1922
+ function reduceApmGatedError(state, input = {}) {
1923
+ const shouldDisableToolBoundaryFlush = input.disableToolBoundaryFlush === true;
1924
+ return {
1925
+ nextState: {
1926
+ isIdle: input.terminalWakeable === true,
1927
+ expectedTerminationReason: state.expectedTerminationReason,
1928
+ phase: "error",
1929
+ outstandingToolUses: state.outstandingToolUses,
1930
+ compacting: false,
1931
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled || shouldDisableToolBoundaryFlush,
1932
+ lastFlushReason: state.lastFlushReason,
1933
+ recentEvents: state.recentEvents
1934
+ },
1935
+ shouldDisableToolBoundaryFlush
1936
+ };
1937
+ }
1938
+ function reduceApmGatedAssistantContinuation(state) {
1939
+ return {
1940
+ nextState: {
1941
+ isIdle: false,
1942
+ expectedTerminationReason: state.expectedTerminationReason,
1943
+ phase: "assistant_continuation",
1944
+ outstandingToolUses: state.outstandingToolUses,
1945
+ compacting: state.compacting,
1946
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
1947
+ lastFlushReason: state.lastFlushReason,
1948
+ recentEvents: state.recentEvents
1949
+ }
1950
+ };
1951
+ }
1952
+ function reduceApmStalledRecoveryTermination(state, input) {
1953
+ if (input.inboxLength === 0) {
1954
+ return { nextState: state, shouldTerminate: false, alreadyRecovering: false, blockedReason: "empty_inbox" };
1955
+ }
1956
+ if (state.expectedTerminationReason === "stalled_recovery") {
1957
+ return { nextState: state, shouldTerminate: false, alreadyRecovering: true, blockedReason: null };
1958
+ }
1959
+ const directStdinRuntime = input.supportsStdinNotification && input.busyDeliveryMode === "direct";
1960
+ const canRestartDirectStdinProcess = directStdinRuntime && input.hasSession && (state.outstandingToolUses === 0 || input.hasDirectStdinRecoveryEvidence);
1961
+ const canRestartStalledProcess = !input.supportsStdinNotification || canRestartDirectStdinProcess;
1962
+ if (!canRestartStalledProcess) {
1963
+ return { nextState: state, shouldTerminate: false, alreadyRecovering: false, blockedReason: "runtime_not_restartable" };
1964
+ }
1965
+ if (input.staleForMs < input.staleThresholdMs && !input.runtimeProgressIsStale) {
1966
+ return { nextState: state, shouldTerminate: false, alreadyRecovering: false, blockedReason: "runtime_progress_recent" };
1967
+ }
1968
+ return {
1969
+ nextState: {
1970
+ ...state,
1971
+ expectedTerminationReason: "stalled_recovery"
1972
+ },
1973
+ shouldTerminate: true,
1974
+ alreadyRecovering: false,
1975
+ blockedReason: null
1976
+ };
1977
+ }
1978
+ function reduceApmStartupTimeoutTermination(state, input) {
1979
+ if (input.hasRuntimeProgressEvent) {
1980
+ return {
1981
+ nextState: state,
1982
+ shouldTerminate: false,
1983
+ blockedReason: "runtime_progress_started"
1984
+ };
1985
+ }
1986
+ return {
1987
+ nextState: {
1988
+ ...state,
1989
+ isIdle: false,
1990
+ expectedTerminationReason: "startup_timeout"
1991
+ },
1992
+ shouldTerminate: true,
1993
+ blockedReason: null
1994
+ };
1995
+ }
1996
+ function reduceApmGatedFlush(state, input) {
1997
+ return {
1998
+ nextState: {
1999
+ ...state,
2000
+ lastFlushReason: input.reason
2001
+ }
2002
+ };
2003
+ }
2004
+ function reduceApmGatedRecentEvent(state, input) {
2005
+ const summary = `${input.event}:${state.phase}:tools=${state.outstandingToolUses}:compact=${state.compacting}`;
2006
+ return {
2007
+ nextState: {
2008
+ ...state,
2009
+ recentEvents: [...state.recentEvents, summary].slice(-MAX_APM_GATED_STEERING_EVENTS)
2010
+ }
2011
+ };
2012
+ }
2013
+ function reduceApmGatedFlushReadiness(state, input) {
2014
+ if (!input.isGated) return { shouldNotify: false, blockedReason: "non_gated", effects: [] };
2015
+ if (!input.hasSession) return { shouldNotify: false, blockedReason: "missing_session", effects: [] };
2016
+ if (input.inboxLength === 0) return { shouldNotify: false, blockedReason: "empty_inbox", effects: [] };
2017
+ if (state.toolBoundaryFlushDisabled) {
2018
+ return { shouldNotify: false, blockedReason: "tool_boundary_flush_disabled", effects: [] };
2019
+ }
2020
+ if (state.compacting) return { shouldNotify: false, blockedReason: "compacting", effects: [] };
2021
+ if (state.outstandingToolUses > 0) {
2022
+ return { shouldNotify: false, blockedReason: "outstanding_tool_uses", effects: [] };
2023
+ }
2024
+ return {
2025
+ shouldNotify: true,
2026
+ blockedReason: null,
2027
+ effects: [{
2028
+ kind: "notify_stdin",
2029
+ reason: input.reason,
2030
+ stdinMode: "busy",
2031
+ clauseId: "SMR-002"
2032
+ }]
2033
+ };
2034
+ }
2035
+
2036
+ // src/agentCredentialProxy.ts
1547
2037
  var registrations = /* @__PURE__ */ new Map();
1548
2038
  var proxyServerState = null;
1549
2039
  var proxyServerStartPromise = null;
@@ -1682,7 +2172,7 @@ async function handleProxyRequest(req, res) {
1682
2172
  const bodyBuffer = new ArrayBuffer(rawBodyBuffer.byteLength);
1683
2173
  new Uint8Array(bodyBuffer).set(rawBodyBuffer);
1684
2174
  body = bodyBuffer;
1685
- headers.set("content-length", String(rawBodyBuffer.byteLength));
2175
+ headers.delete("content-length");
1686
2176
  }
1687
2177
  let sendTarget;
1688
2178
  const sideEffectAction = agentApiSideEffectAction(target.pathname);
@@ -1706,7 +2196,7 @@ async function handleProxyRequest(req, res) {
1706
2196
  body = prepared.bodyText;
1707
2197
  if (sideEffectAction === "send") sendTarget = prepared.target;
1708
2198
  headers.set("content-type", "application/json");
1709
- headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
2199
+ headers.delete("content-length");
1710
2200
  }
1711
2201
  const upstream = await daemonFetch(target, {
1712
2202
  method,
@@ -1895,6 +2385,24 @@ function maxMessageSeq(messages) {
1895
2385
  }
1896
2386
  return maxSeq > 0 ? maxSeq : void 0;
1897
2387
  }
2388
+ function messageSenderId(message) {
2389
+ if (typeof message.sender_id === "string" && message.sender_id.length > 0) return message.sender_id;
2390
+ if (typeof message.senderId === "string" && message.senderId.length > 0) return message.senderId;
2391
+ return void 0;
2392
+ }
2393
+ function isSelfAuthoredMessage(registration, message) {
2394
+ return messageSenderId(message) === registration.agentId;
2395
+ }
2396
+ function isMessageModelSeen(coordinator, target, message) {
2397
+ const seq = Math.floor(messageSeq(message));
2398
+ const boundary = coordinator.getBoundary(target);
2399
+ if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= seq) return true;
2400
+ return coordinator.isMessageModelSeen?.({ target, message }) === true;
2401
+ }
2402
+ function resolveFreshnessBoundary(messages) {
2403
+ const seenUpToSeq = maxMessageSeq(messages);
2404
+ return typeof seenUpToSeq === "number" ? { ok: true, seenUpToSeq } : { ok: false, reason: "missing_seq_boundary" };
2405
+ }
1898
2406
  function sortBySeq(messages) {
1899
2407
  return [...messages].sort((a, b) => messageSeq(a) - messageSeq(b));
1900
2408
  }
@@ -1963,35 +2471,29 @@ function localAgentApiEventsResponse(registration, target) {
1963
2471
  }
1964
2472
  };
1965
2473
  }
1966
- function heldAvailableActions(action) {
1967
- return action === "send" ? ["check_messages", "send_draft", "send_anyway"] : ["check_messages", "retry_action"];
1968
- }
1969
- function localHeldResponse(input) {
1970
- if (input.messages.length === 0) return void 0;
2474
+ function localHeldContext(input) {
2475
+ if (input.messages.length === 0) return { ok: false, reason: "empty_context" };
1971
2476
  const normalized = sortBySeq(normalizeVisibleMessages(input.messages, input.target));
1972
2477
  const heldMessages = latestVisibleMessages(normalized, LOCAL_HELD_CONTEXT_LIMIT);
1973
2478
  const omittedMessageCount = Math.max(0, normalized.length - heldMessages.length);
1974
- const seenUpToSeq = maxMessageSeq(normalized);
1975
- if (seenUpToSeq === void 0) return void 0;
2479
+ const boundary = resolveFreshnessBoundary(normalized);
2480
+ if (!boundary.ok) return boundary;
1976
2481
  input.coordinator.consumeVisibleMessages({
1977
2482
  target: input.target,
1978
2483
  messages: heldMessages,
1979
- boundarySeq: seenUpToSeq,
2484
+ boundarySeq: boundary.seenUpToSeq,
1980
2485
  source: input.source
1981
2486
  });
1982
- const response = {
1983
- state: "held",
1984
- outcome: "held",
1985
- subtype: "freshness",
1986
- reason: "newer_messages_available",
1987
- available_actions: heldAvailableActions(input.action),
1988
- heldMessages,
1989
- newMessageCount: normalized.length,
1990
- shownMessageCount: heldMessages.length,
1991
- omittedMessageCount
2487
+ return {
2488
+ ok: true,
2489
+ context: {
2490
+ heldMessages,
2491
+ newMessageCount: normalized.length,
2492
+ shownMessageCount: heldMessages.length,
2493
+ omittedMessageCount,
2494
+ seenUpToSeq: boundary.seenUpToSeq
2495
+ }
1992
2496
  };
1993
- response.seenUpToSeq = seenUpToSeq;
1994
- return response;
1995
2497
  }
1996
2498
  function recordFreshnessDecision(coordinator, decision) {
1997
2499
  coordinator?.recordFreshnessDecision?.(decision);
@@ -2037,7 +2539,7 @@ function parseTargetFields(target) {
2037
2539
  }
2038
2540
  function normalizeVisibleMessage(message, target) {
2039
2541
  const targetFields = target ? parseTargetFields(target) : {};
2040
- return {
2542
+ const normalized = {
2041
2543
  ...targetFields,
2042
2544
  ...message,
2043
2545
  message_id: message.message_id ?? message.id,
@@ -2046,6 +2548,9 @@ function normalizeVisibleMessage(message, target) {
2046
2548
  sender_name: message.sender_name ?? message.senderName,
2047
2549
  sender_description: message.sender_description ?? message.senderDescription ?? null
2048
2550
  };
2551
+ const senderId = messageSenderId(message);
2552
+ if (senderId) normalized.sender_id = senderId;
2553
+ return normalized;
2049
2554
  }
2050
2555
  function normalizeVisibleMessages(messages, target) {
2051
2556
  return messages.map((message) => normalizeVisibleMessage(message, target));
@@ -2086,31 +2591,46 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2086
2591
  }
2087
2592
  const pending = coordinator.getPendingMessages(target);
2088
2593
  if (pending.length > 0) {
2089
- const localResponse = localHeldResponse({
2090
- action,
2594
+ const modelSeenSeq = coordinator.getBoundary(target);
2595
+ const contextResult = localHeldContext({
2091
2596
  target,
2092
2597
  messages: pending,
2093
2598
  coordinator,
2094
2599
  source: "side_effect_preflight_context"
2095
2600
  });
2096
- if (localResponse) {
2097
- recordFreshnessDecision(coordinator, {
2601
+ if (contextResult.ok) {
2602
+ const { context } = contextResult;
2603
+ const decision = {
2098
2604
  action,
2099
2605
  decision: "local_hold",
2100
2606
  target,
2101
2607
  inboxTrustState: "trusted",
2102
2608
  reason: "exact_target_pending",
2103
2609
  pendingCount: pending.length,
2104
- pendingMaxSeq: maxMessageSeq(pending),
2105
- modelSeenSeq: coordinator.getBoundary(target),
2106
- heldMessageCount: typeof localResponse.shownMessageCount === "number" ? localResponse.shownMessageCount : void 0,
2107
- omittedMessageCount: typeof localResponse.omittedMessageCount === "number" ? localResponse.omittedMessageCount : void 0
2108
- });
2610
+ pendingMaxSeq: context.seenUpToSeq,
2611
+ modelSeenSeq,
2612
+ heldMessageCount: context.shownMessageCount,
2613
+ omittedMessageCount: context.omittedMessageCount
2614
+ };
2615
+ const producerFactId = buildApmFreshnessDecisionProducerFactId(registration.agentId, decision);
2616
+ const localResponse = projectApmHeldFreshnessEnvelope({
2617
+ producerFactId,
2618
+ action,
2619
+ heldMessages: context.heldMessages,
2620
+ newMessageCount: context.newMessageCount,
2621
+ omittedMessageCount: context.omittedMessageCount,
2622
+ seenUpToSeq: context.seenUpToSeq
2623
+ }).body;
2624
+ recordFreshnessDecision(coordinator, { ...decision, producerFactId });
2625
+ return {
2626
+ bodyText: JSON.stringify(body),
2627
+ target,
2628
+ localResponse
2629
+ };
2109
2630
  }
2110
2631
  return {
2111
2632
  bodyText: JSON.stringify(body),
2112
- target,
2113
- localResponse
2633
+ target
2114
2634
  };
2115
2635
  }
2116
2636
  const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
@@ -2131,35 +2651,82 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2131
2651
  }
2132
2652
  const recent = await loadRecentTargetMessages(registration, headers, target);
2133
2653
  if (recent.length > 0) {
2134
- const seenUpToSeq = maxMessageSeq(recent);
2654
+ const unconsumedCounterparty = recent.filter(
2655
+ (message) => !isSelfAuthoredMessage(registration, message) && !isMessageModelSeen(coordinator, target, message)
2656
+ );
2657
+ const boundary2 = resolveFreshnessBoundary(recent);
2658
+ if (!boundary2.ok) {
2659
+ recordFreshnessDecision(coordinator, {
2660
+ action,
2661
+ decision: "forward",
2662
+ target,
2663
+ inboxTrustState: "untrusted",
2664
+ reason: "target_first_touch_recent_context_without_seq_boundary",
2665
+ pendingCount: 0,
2666
+ modelSeenSeq: 0
2667
+ });
2668
+ return { bodyText: JSON.stringify(body), target };
2669
+ }
2670
+ if (unconsumedCounterparty.length === 0) {
2671
+ const { seenUpToSeq: seenUpToSeq2 } = boundary2;
2672
+ if (action === "send") body.seenUpToSeq = seenUpToSeq2;
2673
+ coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq2, source: "side_effect_preflight_context" });
2674
+ recordFreshnessDecision(coordinator, {
2675
+ action,
2676
+ decision: "forward",
2677
+ target,
2678
+ inboxTrustState: "untrusted",
2679
+ reason: "target_first_touch_recent_context_already_seen",
2680
+ pendingCount: 0,
2681
+ pendingMaxSeq: seenUpToSeq2,
2682
+ modelSeenSeq: seenUpToSeq2,
2683
+ heldMessageCount: 0,
2684
+ omittedMessageCount: 0
2685
+ });
2686
+ return { bodyText: JSON.stringify(body), target };
2687
+ }
2688
+ const heldBoundary = resolveFreshnessBoundary(unconsumedCounterparty);
2689
+ if (!heldBoundary.ok) {
2690
+ recordFreshnessDecision(coordinator, {
2691
+ action,
2692
+ decision: "forward",
2693
+ target,
2694
+ inboxTrustState: "untrusted",
2695
+ reason: "target_first_touch_counterparty_context_without_seq_boundary",
2696
+ pendingCount: 0,
2697
+ modelSeenSeq: 0
2698
+ });
2699
+ return { bodyText: JSON.stringify(body), target };
2700
+ }
2701
+ const heldMessages = latestVisibleMessages(unconsumedCounterparty, LOCAL_HELD_CONTEXT_LIMIT);
2702
+ const omittedMessageCount = Math.max(0, unconsumedCounterparty.length - heldMessages.length);
2703
+ const { seenUpToSeq } = boundary2;
2135
2704
  coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "side_effect_preflight_context" });
2136
- recordFreshnessDecision(coordinator, {
2705
+ const decision = {
2137
2706
  action,
2138
2707
  decision: "syncing_hold",
2139
2708
  target,
2140
2709
  inboxTrustState: "untrusted",
2141
2710
  reason: "target_first_touch_recent_context",
2142
2711
  pendingCount: 0,
2143
- pendingMaxSeq: seenUpToSeq,
2712
+ pendingMaxSeq: heldBoundary.seenUpToSeq,
2144
2713
  modelSeenSeq: 0,
2145
- heldMessageCount: recent.length,
2146
- omittedMessageCount: 0
2147
- });
2714
+ heldMessageCount: heldMessages.length,
2715
+ omittedMessageCount
2716
+ };
2717
+ const producerFactId = buildApmFreshnessDecisionProducerFactId(registration.agentId, decision);
2718
+ recordFreshnessDecision(coordinator, { ...decision, producerFactId });
2148
2719
  return {
2149
2720
  bodyText: JSON.stringify(body),
2150
2721
  target,
2151
- localResponse: {
2152
- state: "held",
2153
- outcome: "held",
2154
- subtype: "freshness",
2155
- reason: "newer_messages_available",
2156
- available_actions: heldAvailableActions(action),
2157
- seenUpToSeq,
2158
- heldMessages: recent,
2159
- newMessageCount: recent.length,
2160
- shownMessageCount: recent.length,
2161
- omittedMessageCount: 0
2162
- }
2722
+ localResponse: projectApmHeldFreshnessEnvelope({
2723
+ producerFactId,
2724
+ action,
2725
+ heldMessages,
2726
+ newMessageCount: unconsumedCounterparty.length,
2727
+ omittedMessageCount,
2728
+ seenUpToSeq
2729
+ }).body
2163
2730
  };
2164
2731
  }
2165
2732
  recordFreshnessDecision(coordinator, {
@@ -2512,7 +3079,7 @@ function collectResultErrorDetail(message, fallback) {
2512
3079
  return parts.join(" | ") || fallback;
2513
3080
  }
2514
3081
  function isProviderApiFailureText(value, hasToolUse) {
2515
- return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b5\d{2}\b/.test(value));
3082
+ return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b4\d{2}\b/.test(value) || /\b5\d{2}\b/.test(value));
2516
3083
  }
2517
3084
  var ClaudeEventNormalizer = class {
2518
3085
  normalizeLine(line) {
@@ -3538,6 +4105,7 @@ var CodexDriver = class {
3538
4105
  cwd: ctx.workingDirectory,
3539
4106
  approvalPolicy: "never",
3540
4107
  sandbox: "danger-full-access",
4108
+ sandbox_mode: "danger-full-access",
3541
4109
  developerInstructions: ctx.standingPrompt,
3542
4110
  // Raw response items are used only as payload-free liveness signals in
3543
4111
  // the daemon. They replace the previous transcript-mtime heuristic.
@@ -5356,7 +5924,7 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
5356
5924
  }
5357
5925
 
5358
5926
  // src/runtimeErrorDiagnostics.ts
5359
- import { createHash } from "crypto";
5927
+ import { createHash as createHash2 } from "crypto";
5360
5928
  var MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS = 4096;
5361
5929
  var RUNTIME_AUTH_ACTION_REQUIRED_PATTERNS = [
5362
5930
  /access token could not be refreshed/i,
@@ -5506,7 +6074,7 @@ function runtimeDisplayName(runtimeId) {
5506
6074
  }
5507
6075
  function fingerprintRuntimeError(value) {
5508
6076
  const normalized = value.toLowerCase().replace(/[0-9a-f]{12,}/g, "<hex>").replace(/\b\d+\b/g, "<num>").replace(/\s+/g, " ").trim();
5509
- return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
6077
+ return createHash2("sha256").update(normalized).digest("hex").slice(0, 16);
5510
6078
  }
5511
6079
  function bucketLength(length) {
5512
6080
  if (length === 0) return "0";
@@ -5612,6 +6180,9 @@ var RuntimeNotificationState = class {
5612
6180
  var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
5613
6181
  var DEFAULT_AGENT_START_INTERVAL_MS = 500;
5614
6182
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
6183
+ function assertNeverApmEffect(effect) {
6184
+ throw new Error(`Unhandled APM gated steering effect: ${String(effect)}`);
6185
+ }
5615
6186
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
5616
6187
  var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
5617
6188
  var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
@@ -5917,7 +6488,8 @@ function formatIncomingMessage(message, driver) {
5917
6488
  const senderType = formatVisibleActorType(message.sender_type);
5918
6489
  const attachSuffix = message.attachments?.length ? ` [${message.attachments.length} attachment${message.attachments.length > 1 ? "s" : ""}: ${message.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to download]` : "";
5919
6490
  const taskSuffix = message.task_status ? ` [task #${message.task_number} status=${message.task_status}${message.task_assignee_id ? ` assignee=${formatTaskAssigneeType(message.task_assignee_type)}:${message.task_assignee_id}` : ""}]` : "";
5920
- const body = `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}${attachSuffix}${taskSuffix}`;
6491
+ const lineageSuffix = formatProducerFactLineageBracket(message.producerFactId);
6492
+ const body = `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}${attachSuffix}${taskSuffix}${lineageSuffix}`;
5921
6493
  return threadJoinPrefix ? `${threadJoinPrefix}
5922
6494
  ${body}` : body;
5923
6495
  }
@@ -5959,6 +6531,8 @@ function buildUnreadSummary(messages, excludeChannel) {
5959
6531
  var MAX_TRAJECTORY_TEXT = 2e3;
5960
6532
  var TRAJECTORY_COALESCE_MS = 350;
5961
6533
  var ACTIVITY_HEARTBEAT_MS = 6e4;
6534
+ var STDIN_NOTIFICATION_INITIAL_DELAY_MS = 3e3;
6535
+ var STDIN_NOTIFICATION_RETRY_DELAY_MS = 15e3;
5962
6536
  var COMPACTION_STALE_MS = 5 * 6e4;
5963
6537
  var RUNTIME_PROGRESS_STALE_MS = 15 * 6e4;
5964
6538
  var DEFAULT_RUNTIME_START_TIMEOUT_MS = 2 * 6e4;
@@ -5967,7 +6541,6 @@ var MAX_STDOUT_LINES = 8;
5967
6541
  var MAX_STDOUT_LINE_LENGTH = 240;
5968
6542
  var MAX_STDERR_LINES = 8;
5969
6543
  var MAX_STDERR_LINE_LENGTH = 240;
5970
- var MAX_GATED_STEERING_EVENTS = 12;
5971
6544
  var ONBOARDING_MEMORY_SEED_ENV = "SLOCK_ONBOARDING_MEMORY_SEED";
5972
6545
  var FIRST_CINDY_SEED_MODE = "first-cindy";
5973
6546
  function getOnboardingSeedMode(config) {
@@ -6383,13 +6956,8 @@ function stripManagedRunnerCredential(config) {
6383
6956
  }
6384
6957
  function createGatedSteeringState() {
6385
6958
  return {
6386
- phase: "idle",
6387
- outstandingToolUses: 0,
6388
- compacting: false,
6389
- toolBoundaryFlushDisabled: process.env.SLOCK_CLAUDE_GATED_STEERING_TOOL_BOUNDARY === "0",
6390
- lastFlushReason: null,
6391
- recentEvents: [],
6392
- inFlightBatch: null
6959
+ ...createInitialApmGatedSteeringState(),
6960
+ toolBoundaryFlushDisabled: process.env.SLOCK_CLAUDE_GATED_STEERING_TOOL_BOUNDARY === "0"
6393
6961
  };
6394
6962
  }
6395
6963
  var RUNTIME_PROFILE_MIGRATION_MESSAGE_PREFIX = "runtime-profile-migration-";
@@ -6408,7 +6976,7 @@ function runtimeProfileNotificationTitle(kind) {
6408
6976
  }
6409
6977
  function hashRuntimeProfileKey(key) {
6410
6978
  if (!key) return void 0;
6411
- return createHash2("sha256").update(key).digest("hex").slice(0, 16);
6979
+ return createHash3("sha256").update(key).digest("hex").slice(0, 16);
6412
6980
  }
6413
6981
  function runtimeProfileTurnControl(kind, key, source) {
6414
6982
  return {
@@ -6680,6 +7248,19 @@ function summarizeMessageInputBytes(messages) {
6680
7248
  runtime_input_thread_context_content_bytes_bucket: bucketBytes(threadContextContentBytes)
6681
7249
  };
6682
7250
  }
7251
+ function messageProducerFactTraceAttrs(messages) {
7252
+ if (!messages || messages.length === 0) return {};
7253
+ const producerFactIds = /* @__PURE__ */ new Set();
7254
+ for (const message of messages) {
7255
+ const producerFactId = typeof message.producerFactId === "string" ? message.producerFactId.trim() : "";
7256
+ if (producerFactId) producerFactIds.add(producerFactId);
7257
+ }
7258
+ if (producerFactIds.size === 0) return {};
7259
+ return {
7260
+ message_producer_fact_count: producerFactIds.size,
7261
+ ...producerFactIds.size === 1 ? { message_producer_fact_id: [...producerFactIds][0] } : {}
7262
+ };
7263
+ }
6683
7264
  function buildRuntimeInputTraceAttrs(opts) {
6684
7265
  return {
6685
7266
  runtime_input_source: opts.source,
@@ -6748,10 +7329,12 @@ var AgentProcessManager = class _AgentProcessManager {
6748
7329
  driverResolver;
6749
7330
  defaultAgentEnvVarsProvider;
6750
7331
  tracer;
7332
+ stdinNotificationRetryMs;
6751
7333
  cliTransportTraceDir = null;
6752
7334
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
6753
7335
  processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
6754
7336
  agentVisibleBoundaries = /* @__PURE__ */ new Map();
7337
+ agentVisibleMessageIds = /* @__PURE__ */ new Map();
6755
7338
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
6756
7339
  this.chatBridgePath = chatBridgePath;
6757
7340
  this.slockCliPath = opts.slockCliPath ?? "";
@@ -6763,6 +7346,10 @@ var AgentProcessManager = class _AgentProcessManager {
6763
7346
  this.driverResolver = opts.driverResolver || getDriver;
6764
7347
  this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
6765
7348
  this.tracer = opts.tracer ?? noopTracer;
7349
+ this.stdinNotificationRetryMs = Math.max(
7350
+ 0,
7351
+ Math.floor(opts.stdinNotificationRetryMs ?? STDIN_NOTIFICATION_RETRY_DELAY_MS)
7352
+ );
6766
7353
  this.maxConcurrentAgentStarts = Math.max(
6767
7354
  1,
6768
7355
  Math.floor(
@@ -6793,6 +7380,29 @@ var AgentProcessManager = class _AgentProcessManager {
6793
7380
  getVisibleBoundary(agentId, target) {
6794
7381
  return this.agentVisibleBoundaries.get(agentId)?.get(target);
6795
7382
  }
7383
+ visibleMessageIdMap(agentId) {
7384
+ let map = this.agentVisibleMessageIds.get(agentId);
7385
+ if (!map) {
7386
+ map = /* @__PURE__ */ new Map();
7387
+ this.agentVisibleMessageIds.set(agentId, map);
7388
+ }
7389
+ return map;
7390
+ }
7391
+ getVisibleMessageIdSet(agentId, target) {
7392
+ return this.agentVisibleMessageIds.get(agentId)?.get(target);
7393
+ }
7394
+ isVisibleMessageModelSeen(agentId, target, message) {
7395
+ const seq = Number(message.seq ?? 0);
7396
+ const boundary = this.getVisibleBoundary(agentId, target);
7397
+ if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= Math.floor(seq)) return true;
7398
+ const id = typeof message.message_id === "string" ? message.message_id : typeof message.id === "string" ? message.id : "";
7399
+ return id.length > 0 && this.getVisibleMessageIdSet(agentId, target)?.has(id) === true;
7400
+ }
7401
+ scheduleStdinNotification(agentId, ap, delayMs) {
7402
+ return ap.notifications.schedule(() => {
7403
+ this.sendStdinNotification(agentId);
7404
+ }, delayMs);
7405
+ }
6796
7406
  allPendingVisibleMessages(agentId) {
6797
7407
  const collect = (messages) => (messages ?? []).filter((message) => typeof message.seq === "number" && message.seq > 0);
6798
7408
  return [
@@ -6822,13 +7432,14 @@ var AgentProcessManager = class _AgentProcessManager {
6822
7432
  };
6823
7433
  for (const message of input.messages) {
6824
7434
  const seq = Number(message.seq ?? 0);
6825
- if (!Number.isFinite(seq) || seq <= 0) continue;
6826
7435
  const target = input.target ?? formatVisibleMessageTarget(message);
6827
7436
  if (!target) continue;
6828
7437
  const bucket = ensureBucket(target);
6829
- bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
6830
- bucket.seqs.add(Math.floor(seq));
6831
- const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
7438
+ if (Number.isFinite(seq) && seq > 0) {
7439
+ bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
7440
+ bucket.seqs.add(Math.floor(seq));
7441
+ }
7442
+ const id = typeof message.message_id === "string" ? message.message_id : typeof message.id === "string" ? message.id : null;
6832
7443
  if (id) bucket.ids.add(id);
6833
7444
  }
6834
7445
  if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
@@ -6837,10 +7448,19 @@ var AgentProcessManager = class _AgentProcessManager {
6837
7448
  }
6838
7449
  if (byTarget.size === 0) return;
6839
7450
  const boundaryMap = this.visibleBoundaryMap(agentId);
7451
+ const visibleIds = this.visibleMessageIdMap(agentId);
6840
7452
  for (const [target, bucket] of byTarget) {
6841
7453
  const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
6842
7454
  const previous = boundaryMap.get(target) ?? 0;
6843
7455
  boundaryMap.set(target, Math.max(previous, highWaterSeq));
7456
+ if (bucket.ids.size > 0) {
7457
+ let targetIds = visibleIds.get(target);
7458
+ if (!targetIds) {
7459
+ targetIds = /* @__PURE__ */ new Set();
7460
+ visibleIds.set(target, targetIds);
7461
+ }
7462
+ for (const id of bucket.ids) targetIds.add(id);
7463
+ }
6844
7464
  }
6845
7465
  const suppress = (messages) => {
6846
7466
  if (!messages || messages.length === 0) return 0;
@@ -6873,6 +7493,7 @@ var AgentProcessManager = class _AgentProcessManager {
6873
7493
  return {
6874
7494
  getBoundary: (target) => this.getVisibleBoundary(agentId, target),
6875
7495
  getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
7496
+ isMessageModelSeen: ({ target, message }) => this.isVisibleMessageModelSeen(agentId, target, message),
6876
7497
  getAllPendingMessages: () => this.allPendingVisibleMessages(agentId),
6877
7498
  consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input),
6878
7499
  recordDrainOutcome: (input) => {
@@ -6888,20 +7509,16 @@ var AgentProcessManager = class _AgentProcessManager {
6888
7509
  recordProxyFailure: (input) => this.recordAgentProxyFailure(agentId, input),
6889
7510
  recordTransportNormalizedError: (input) => this.recordAgentProxyTransportNormalizedError(agentId, input),
6890
7511
  recordFreshnessDecision: (input) => {
7512
+ const producerFactId = input.producerFactId ?? buildApmFreshnessDecisionProducerFactId(agentId, input);
7513
+ const trace = projectApmFreshnessDecisionTrace({
7514
+ producerFactId,
7515
+ decision: input
7516
+ });
6891
7517
  this.recordDaemonTrace("daemon.agent.inbox.freshness_decision", {
6892
7518
  agentId,
6893
- action: input.action,
6894
- decision: input.decision,
6895
- target: input.target,
6896
- inbox_trust_state: input.inboxTrustState,
6897
- reason: input.reason,
6898
- pending_count: input.pendingCount,
6899
- pending_max_seq: input.pendingMaxSeq,
6900
- model_seen_seq: input.modelSeenSeq,
6901
- held_message_count: input.heldMessageCount,
6902
- omitted_message_count: input.omittedMessageCount
7519
+ ...trace.attrs
6903
7520
  });
6904
- this.recordFreshnessDecisionActivity(agentId, input);
7521
+ this.recordFreshnessDecisionActivity(agentId, input, producerFactId);
6905
7522
  }
6906
7523
  };
6907
7524
  }
@@ -6931,20 +7548,17 @@ var AgentProcessManager = class _AgentProcessManager {
6931
7548
  upstream: input.upstream
6932
7549
  }, "error");
6933
7550
  }
6934
- recordFreshnessDecisionActivity(agentId, input) {
7551
+ recordFreshnessDecisionActivity(agentId, input, producerFactId) {
6935
7552
  if (input.decision !== "local_hold" && input.decision !== "syncing_hold") return;
6936
7553
  const ap = this.agents.get(agentId);
6937
7554
  const messageCount = input.pendingCount ?? input.heldMessageCount ?? 0;
6938
- const title = input.action === "send" ? "Send held by freshness check" : input.action === "task_claim" ? "Task claim held by freshness check" : "Task update held by freshness check";
6939
- const entry = {
6940
- kind: "slock_action",
6941
- title,
6942
- text: [
6943
- input.target ? `target: ${input.target}` : null,
6944
- `new messages: ${messageCount} newer message${messageCount === 1 ? "" : "s"}`,
6945
- `decision: ${input.decision === "syncing_hold" ? "syncing hold" : "local hold"}; review the newer context before retrying`
6946
- ].filter((line) => Boolean(line)).join("\n")
6947
- };
7555
+ const entry = projectApmHeldFreshnessActivity({
7556
+ producerFactId,
7557
+ action: input.action,
7558
+ decision: input.decision,
7559
+ target: input.target,
7560
+ messageCount
7561
+ }).entry;
6948
7562
  if (ap) ap.activityClientSeq += 1;
6949
7563
  this.sendToServer({
6950
7564
  type: "agent:activity",
@@ -7353,7 +7967,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7353
7967
  notifications: new RuntimeNotificationState(),
7354
7968
  activityHeartbeat: null,
7355
7969
  startupTimeoutTimer: null,
7356
- startupTimedOut: false,
7357
7970
  compactionWatchdog: null,
7358
7971
  compactionStartedAt: null,
7359
7972
  runtimeProgress: new RuntimeProgressState(Date.now()),
@@ -7485,7 +8098,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7485
8098
  this.clearStalledRecoverySigtermWatchdog(ap);
7486
8099
  const finalCode = ap.exitCode ?? code;
7487
8100
  const finalSignal = ap.exitSignal ?? signal;
7488
- const expectedTermination = Boolean(ap.expectedTerminationReason) || ap.startupTimedOut;
8101
+ const startupTimeoutTermination = ap.expectedTerminationReason === "startup_timeout";
8102
+ const expectedTermination = Boolean(ap.expectedTerminationReason);
7489
8103
  const processEndedCleanly = finalCode === 0 || expectedTermination && !ap.lastRuntimeError;
7490
8104
  const terminalFailureDetail = processEndedCleanly ? null : classifyTerminalFailure(ap);
7491
8105
  const resumeRecoveryReason = resumeSessionRecoveryReason(ap);
@@ -7589,7 +8203,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7589
8203
  });
7590
8204
  logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
7591
8205
  this.sendAgentStatus(agentId, "active", ap.launchId);
7592
- } else if (ap.startupTimedOut) {
8206
+ } else if (startupTimeoutTermination) {
7593
8207
  logger.warn(`[Agent ${agentId}] Startup timeout cleanup completed (${reason})`);
7594
8208
  } else {
7595
8209
  this.idleAgentConfigs.delete(agentId);
@@ -7597,7 +8211,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7597
8211
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
7598
8212
  }
7599
8213
  if (terminalFailureDetail) {
7600
- if (!ap.startupTimedOut) {
8214
+ if (!startupTimeoutTermination) {
7601
8215
  this.broadcastActivity(
7602
8216
  agentId,
7603
8217
  "error",
@@ -7795,9 +8409,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7795
8409
  pendingMessages: ap.inbox.length
7796
8410
  });
7797
8411
  } else if (!ap.notifications.hasTimer) {
7798
- ap.notifications.schedule(() => {
7799
- this.sendStdinNotification(agentId);
7800
- }, 3e3);
8412
+ this.scheduleStdinNotification(agentId, ap, STDIN_NOTIFICATION_INITIAL_DELAY_MS);
7801
8413
  }
7802
8414
  }
7803
8415
  this.recordDaemonTrace("daemon.agent.runtime_profile.routed", {
@@ -7975,7 +8587,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7975
8587
  if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
7976
8588
  const nextMessages = ap.inbox.splice(0, ap.inbox.length);
7977
8589
  nextMessages.push(message);
7978
- ap.isIdle = false;
8590
+ this.commitApmIdleState(agentId, ap, false);
7979
8591
  this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", nextMessages);
7980
8592
  this.broadcastActivity(agentId, "working", "Message received");
7981
8593
  const stdinAccepted = this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
@@ -8059,9 +8671,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8059
8671
  if (ap.driver.busyDeliveryMode === "gated") {
8060
8672
  ap.notifications.add();
8061
8673
  if (!ap.notifications.hasTimer) {
8062
- ap.notifications.schedule(() => {
8063
- this.sendStdinNotification(agentId);
8064
- }, 3e3);
8674
+ this.scheduleStdinNotification(agentId, ap, STDIN_NOTIFICATION_INITIAL_DELAY_MS);
8065
8675
  }
8066
8676
  this.recordGatedSteeringEvent(agentId, ap, "buffer", {
8067
8677
  reason: "busy_message",
@@ -8082,9 +8692,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8082
8692
  }
8083
8693
  ap.notifications.add();
8084
8694
  if (!ap.notifications.hasTimer) {
8085
- ap.notifications.schedule(() => {
8086
- this.sendStdinNotification(agentId);
8087
- }, 3e3);
8695
+ this.scheduleStdinNotification(agentId, ap, STDIN_NOTIFICATION_INITIAL_DELAY_MS);
8088
8696
  }
8089
8697
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
8090
8698
  outcome: "queued_busy_notification",
@@ -8258,7 +8866,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8258
8866
  return true;
8259
8867
  }
8260
8868
  if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
8261
- ap.isIdle = false;
8869
+ this.commitApmIdleState(agentId, ap, false);
8262
8870
  this.startRuntimeTrace(agentId, ap, "runtime-profile", [message]);
8263
8871
  const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
8264
8872
  span.end(written ? "ok" : "error", {
@@ -8768,18 +9376,22 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8768
9376
  if (!ap || !ap.compactionStartedAt) return;
8769
9377
  this.clearCompactionWatchdog(ap);
8770
9378
  this.broadcastActivity(agentId, "working", detail, [{ kind: "compaction_finished" }]);
8771
- ap.gatedSteering.compacting = false;
8772
- this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "compaction_finished", inferred: true });
9379
+ const reduction = reduceApmGatedCompaction(ap.gatedSteering, { kind: "compaction_finished" });
9380
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, {
9381
+ event: "compaction_finished",
9382
+ inferred: true
9383
+ });
8773
9384
  if (options.flushBoundaryMessages ?? true) {
8774
9385
  this.flushCompactionBoundaryMessages(agentId, ap);
8775
9386
  }
8776
- ap.isIdle = false;
9387
+ this.commitApmIdleState(agentId, ap, false);
8777
9388
  }
8778
9389
  interruptCompactionIfActive(agentId) {
8779
9390
  const ap = this.agents.get(agentId);
8780
9391
  if (!ap || !ap.compactionStartedAt && !ap.gatedSteering.compacting) return;
8781
9392
  this.clearCompactionWatchdog(ap);
8782
- ap.gatedSteering.compacting = false;
9393
+ const reduction = reduceApmGatedCompaction(ap.gatedSteering, { kind: "compaction_interrupted" });
9394
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
8783
9395
  }
8784
9396
  messagesTraceAttrs(messages) {
8785
9397
  if (!messages || messages.length === 0) return {};
@@ -8792,6 +9404,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8792
9404
  message_id_present: Boolean(first.message_id),
8793
9405
  deliveryId: context.deliveryId,
8794
9406
  delivery_correlation_id: context.deliveryId,
9407
+ ...messageProducerFactTraceAttrs(messages),
8795
9408
  control_kind: notification.kind,
8796
9409
  key_present: Boolean(notification.key),
8797
9410
  runtime_profile_control_kind: notification.kind,
@@ -8804,7 +9417,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8804
9417
  messageId: first.message_id,
8805
9418
  message_id_present: Boolean(first.message_id),
8806
9419
  deliveryId: context.deliveryId,
8807
- delivery_correlation_id: context.deliveryId ?? first.message_id
9420
+ delivery_correlation_id: context.deliveryId ?? first.message_id,
9421
+ ...messageProducerFactTraceAttrs(messages)
8808
9422
  };
8809
9423
  }
8810
9424
  runtimeProfileTurnControlTraceAttrs(control) {
@@ -8893,10 +9507,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8893
9507
  }
8894
9508
  recordGatedSteeringEvent(agentId, ap, event, attrs = {}) {
8895
9509
  if (ap.driver.busyDeliveryMode !== "gated") return;
8896
- const summary = `${event}:${ap.gatedSteering.phase}:tools=${ap.gatedSteering.outstandingToolUses}:compact=${ap.gatedSteering.compacting}`;
8897
- ap.gatedSteering.recentEvents.push(summary);
8898
- ap.gatedSteering.recentEvents = ap.gatedSteering.recentEvents.slice(-MAX_GATED_STEERING_EVENTS);
9510
+ const reduction = reduceApmGatedRecentEvent(ap.gatedSteering, { event });
9511
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
8899
9512
  this.recordRuntimeTraceEvent(agentId, ap, `runtime.gated_steering.${event}`, {
9513
+ isIdle: ap.gatedSteering.isIdle,
9514
+ expectedTerminationReason: ap.gatedSteering.expectedTerminationReason,
8900
9515
  phase: ap.gatedSteering.phase,
8901
9516
  outstandingToolUses: ap.gatedSteering.outstandingToolUses,
8902
9517
  compacting: ap.gatedSteering.compacting,
@@ -8905,63 +9520,110 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8905
9520
  ...attrs
8906
9521
  });
8907
9522
  }
8908
- setGatedSteeringPhase(agentId, ap, phase, attrs = {}) {
8909
- if (!ap || ap.driver.busyDeliveryMode !== "gated") return;
8910
- ap.gatedSteering.phase = phase;
8911
- this.recordGatedSteeringEvent(agentId, ap, "phase", attrs);
8912
- }
8913
- tryFlushGatedSteering(agentId, ap, reason) {
8914
- if (ap.driver.busyDeliveryMode !== "gated") return false;
8915
- if (!ap.sessionId || ap.inbox.length === 0) return false;
8916
- if (reason !== "turn_end") {
8917
- if (ap.gatedSteering.toolBoundaryFlushDisabled) return false;
8918
- if (ap.gatedSteering.compacting || ap.gatedSteering.outstandingToolUses > 0) return false;
8919
- this.recordGatedSteeringEvent(agentId, ap, "notify", { reason, pendingMessages: ap.inbox.length });
8920
- return this.sendStdinNotification(agentId);
8921
- }
8922
- const pendingMessages = ap.inbox.length;
8923
- const pendingNotificationCount = ap.notifications.pendingCount;
8924
- const nextMessages = ap.inbox.splice(0, ap.inbox.length);
8925
- ap.notifications.clear();
8926
- ap.gatedSteering.lastFlushReason = reason;
8927
- if (reason !== "turn_end") {
8928
- ap.gatedSteering.inFlightBatch = {
8929
- reason,
8930
- messages: nextMessages
8931
- };
8932
- } else {
8933
- ap.gatedSteering.inFlightBatch = null;
8934
- }
8935
- this.recordGatedSteeringEvent(agentId, ap, "flush", { reason, messageCount: nextMessages.length });
8936
- logger.info(
8937
- `[Agent ${agentId}] Claude gated steering flush reason=${reason} messages=${nextMessages.length}`
8938
- );
8939
- if (reason === "turn_end") {
8940
- this.broadcastActivity(agentId, "working", "Message received");
8941
- }
8942
- if (this.deliverMessagesViaStdin(agentId, ap, nextMessages, reason === "turn_end" ? "idle" : "busy")) {
8943
- return true;
9523
+ commitGatedSteeringDecisionState(agentId, ap, nextState, phaseEventAttrs) {
9524
+ ap.gatedSteering.phase = nextState.phase;
9525
+ ap.gatedSteering.outstandingToolUses = nextState.outstandingToolUses;
9526
+ ap.gatedSteering.compacting = nextState.compacting;
9527
+ ap.gatedSteering.toolBoundaryFlushDisabled = nextState.toolBoundaryFlushDisabled;
9528
+ ap.gatedSteering.lastFlushReason = nextState.lastFlushReason;
9529
+ ap.gatedSteering.recentEvents = nextState.recentEvents;
9530
+ ap.gatedSteering.isIdle = nextState.isIdle;
9531
+ ap.gatedSteering.expectedTerminationReason = nextState.expectedTerminationReason;
9532
+ ap.isIdle = nextState.isIdle;
9533
+ ap.expectedTerminationReason = nextState.expectedTerminationReason;
9534
+ if (phaseEventAttrs) {
9535
+ this.recordGatedSteeringEvent(agentId, ap, "phase", phaseEventAttrs);
9536
+ }
9537
+ }
9538
+ commitApmIdleState(agentId, ap, isIdle) {
9539
+ const reduction = reduceApmIdleState(ap.gatedSteering, { isIdle });
9540
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
9541
+ }
9542
+ notifyGatedSteeringBoundary(agentId, ap, reason) {
9543
+ const readiness = reduceApmGatedFlushReadiness(ap.gatedSteering, {
9544
+ isGated: ap.driver.busyDeliveryMode === "gated",
9545
+ hasSession: Boolean(ap.sessionId),
9546
+ inboxLength: ap.inbox.length,
9547
+ reason
9548
+ });
9549
+ if (readiness.effects.length === 0) return false;
9550
+ let didExecute = false;
9551
+ for (const effect of readiness.effects) {
9552
+ didExecute = this.executeApmGatedSteeringEffect(agentId, ap, effect) || didExecute;
8944
9553
  }
8945
- if (reason !== "turn_end") {
8946
- ap.gatedSteering.inFlightBatch = null;
9554
+ return didExecute;
9555
+ }
9556
+ recordApmGatedSteeringEffectTrace(agentId, ap, effect, attrs) {
9557
+ this.recordDaemonTrace("daemon.apm.gated_effect", {
9558
+ agentId,
9559
+ launchId: ap.launchId || void 0,
9560
+ runtime: ap.config.runtime,
9561
+ effect_kind: effect.kind,
9562
+ reason: effect.reason,
9563
+ target: "runtime-stdin",
9564
+ stdin_mode: effect.stdinMode,
9565
+ clause_id: effect.clauseId,
9566
+ pending_messages: ap.inbox.length,
9567
+ ...attrs
9568
+ });
9569
+ }
9570
+ executeApmGatedSteeringEffect(agentId, ap, effect) {
9571
+ switch (effect.kind) {
9572
+ case "notify_stdin":
9573
+ this.recordGatedSteeringEvent(agentId, ap, "notify", {
9574
+ reason: effect.reason,
9575
+ pendingMessages: ap.inbox.length
9576
+ });
9577
+ {
9578
+ const written = this.sendStdinNotification(agentId);
9579
+ this.recordApmGatedSteeringEffectTrace(agentId, ap, effect, {
9580
+ outcome: written ? "written" : "not_written"
9581
+ });
9582
+ return written;
9583
+ }
9584
+ case "deliver_stdin": {
9585
+ const messages = ap.inbox.splice(0, ap.inbox.length);
9586
+ ap.notifications.clear();
9587
+ if (messages.length === 0) {
9588
+ this.recordApmGatedSteeringEffectTrace(agentId, ap, effect, {
9589
+ outcome: "empty",
9590
+ delivered_messages_count: 0
9591
+ });
9592
+ return true;
9593
+ }
9594
+ if (ap.driver.busyDeliveryMode === "gated") {
9595
+ const flushReduction = reduceApmGatedFlush(ap.gatedSteering, { reason: effect.reason });
9596
+ this.commitGatedSteeringDecisionState(agentId, ap, flushReduction.nextState);
9597
+ this.recordGatedSteeringEvent(agentId, ap, "flush", {
9598
+ reason: effect.reason,
9599
+ messageCount: messages.length
9600
+ });
9601
+ }
9602
+ this.broadcastActivity(agentId, "working", "Message received");
9603
+ const accepted = this.deliverMessagesViaStdin(agentId, ap, messages, effect.stdinMode);
9604
+ this.recordApmGatedSteeringEffectTrace(agentId, ap, effect, {
9605
+ outcome: accepted ? "written" : "not_written",
9606
+ delivered_messages_count: messages.length
9607
+ });
9608
+ return accepted;
9609
+ }
9610
+ default:
9611
+ return assertNeverApmEffect(effect);
8947
9612
  }
8948
- ap.notifications.add(pendingNotificationCount || pendingMessages);
8949
- return false;
8950
9613
  }
8951
9614
  flushCompactionBoundaryMessages(agentId, ap) {
8952
- if (!ap.sessionId || !ap.driver.supportsStdinNotification || ap.inbox.length === 0) return false;
8953
- if (ap.notifications.pendingCount > 0) {
8954
- ap.notifications.clearTimer();
8955
- this.sendStdinNotification(agentId);
8956
- return true;
9615
+ const reduction = reduceApmGatedCompactionBoundaryFlush(ap.gatedSteering, {
9616
+ hasSession: Boolean(ap.sessionId),
9617
+ supportsStdinNotification: ap.driver.supportsStdinNotification,
9618
+ inboxLength: ap.inbox.length,
9619
+ pendingNotificationCount: ap.notifications.pendingCount
9620
+ });
9621
+ if (reduction.effects.length === 0) return false;
9622
+ ap.notifications.clearTimer();
9623
+ for (const effect of reduction.effects) {
9624
+ this.executeApmGatedSteeringEffect(agentId, ap, effect);
8957
9625
  }
8958
- return false;
8959
- }
8960
- clearGatedInFlightBatch(agentId, ap, reason) {
8961
- if (ap.driver.busyDeliveryMode !== "gated" || !ap.gatedSteering.inFlightBatch) return;
8962
- const messageCount = ap.gatedSteering.inFlightBatch.messages.length;
8963
- ap.gatedSteering.inFlightBatch = null;
8964
- this.recordGatedSteeringEvent(agentId, ap, "ack", { reason, messageCount });
9626
+ return true;
8965
9627
  }
8966
9628
  startRuntimeStartupTimeout(agentId, ap) {
8967
9629
  const timeoutMs = runtimeStartTimeoutMs();
@@ -8979,14 +9641,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8979
9641
  handleRuntimeStartupTimeout(agentId, ap, timeoutMs) {
8980
9642
  const current = this.agents.get(agentId);
8981
9643
  if (current !== ap) return;
8982
- if (ap.runtimeProgress.lastEventKind) {
9644
+ const reduction = reduceApmStartupTimeoutTermination(ap.gatedSteering, {
9645
+ hasRuntimeProgressEvent: Boolean(ap.runtimeProgress.lastEventKind)
9646
+ });
9647
+ if (!reduction.shouldTerminate) {
8983
9648
  this.clearRuntimeStartupTimeout(ap);
8984
9649
  return;
8985
9650
  }
8986
9651
  this.clearRuntimeStartupTimeout(ap);
9652
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
8987
9653
  const terminalFailureDetail = classifyTerminalFailure(ap);
8988
9654
  const detail = terminalFailureDetail?.detail ?? formatRuntimeStartTimeoutMessage(ap.driver.id);
8989
- ap.startupTimedOut = true;
8990
9655
  ap.lastRuntimeError = detail;
8991
9656
  ap.runtimeProgress.markStale();
8992
9657
  const staleForMs = Math.max(timeoutMs, ap.runtimeProgress.ageMs());
@@ -9015,6 +9680,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9015
9680
  try {
9016
9681
  this.processExitTraceAttrs.set(ap.process, {
9017
9682
  stop_source: "startup_timeout",
9683
+ expectedTerminationReason: "startup_timeout",
9018
9684
  timeout_ms: timeoutMs
9019
9685
  });
9020
9686
  ap.process.kill("SIGTERM");
@@ -9023,20 +9689,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9023
9689
  logger.warn(`[Agent ${agentId}] Failed to terminate startup-timed-out ${ap.driver.id} process: ${reason}`);
9024
9690
  }
9025
9691
  }
9026
- requeueGatedInFlightBatch(agentId, ap, reason) {
9027
- if (ap.driver.busyDeliveryMode !== "gated" || !ap.gatedSteering.inFlightBatch) return;
9028
- const batch = ap.gatedSteering.inFlightBatch;
9029
- ap.gatedSteering.inFlightBatch = null;
9030
- ap.inbox.unshift(...batch.messages);
9031
- ap.notifications.add(batch.messages.length);
9032
- this.recordGatedSteeringEvent(agentId, ap, "requeue", {
9033
- reason,
9034
- originalFlushReason: batch.reason,
9035
- messageCount: batch.messages.length
9036
- });
9037
- }
9038
9692
  isThinkingBlockMutationError(message) {
9039
- return /thinking.*redacted_thinking|redacted_thinking.*thinking/i.test(message) && /cannot be modified/i.test(message);
9693
+ return /thinking.*redacted_thinking|redacted_thinking.*thinking/i.test(message) && /cannot be modified/i.test(message) || /messages\.\d+\.content\.\d+\.text\.start_timestamp:\s*Extra inputs are not permitted/i.test(message);
9040
9694
  }
9041
9695
  markRuntimeProgressStaleIfNeeded(agentId, ap) {
9042
9696
  if (ap.lastActivity !== "working" && ap.lastActivity !== "thinking") return false;
@@ -9068,16 +9722,22 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9068
9722
  return true;
9069
9723
  }
9070
9724
  recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap) {
9071
- if (ap.inbox.length === 0) return false;
9072
- if (ap.expectedTerminationReason === "stalled_recovery") {
9725
+ const staleForMs = ap.runtimeProgress.ageMs();
9726
+ const reduction = reduceApmStalledRecoveryTermination(ap.gatedSteering, {
9727
+ inboxLength: ap.inbox.length,
9728
+ supportsStdinNotification: ap.driver.supportsStdinNotification,
9729
+ busyDeliveryMode: ap.driver.busyDeliveryMode,
9730
+ hasSession: Boolean(ap.sessionId),
9731
+ hasDirectStdinRecoveryEvidence: hasDirectStdinRecoveryEvidence(ap),
9732
+ runtimeProgressIsStale: ap.runtimeProgress.isStale,
9733
+ staleForMs,
9734
+ staleThresholdMs: RUNTIME_PROGRESS_STALE_MS
9735
+ });
9736
+ if (reduction.alreadyRecovering) {
9073
9737
  return true;
9074
9738
  }
9075
- const directStdinRuntime = ap.driver.supportsStdinNotification && ap.driver.busyDeliveryMode === "direct";
9076
- const canRestartDirectStdinProcess = directStdinRuntime && Boolean(ap.sessionId) && (ap.gatedSteering.outstandingToolUses === 0 || hasDirectStdinRecoveryEvidence(ap));
9077
- const canRestartStalledProcess = !ap.driver.supportsStdinNotification || canRestartDirectStdinProcess;
9078
- if (!canRestartStalledProcess) return false;
9079
- const staleForMs = ap.runtimeProgress.ageMs();
9080
- if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.runtimeProgress.isStale) return false;
9739
+ if (!reduction.shouldTerminate) return false;
9740
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
9081
9741
  const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
9082
9742
  ap.runtimeProgress.markStale();
9083
9743
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
@@ -9103,7 +9763,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9103
9763
  ...runtimeTraceCounterAttrs(ap),
9104
9764
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
9105
9765
  });
9106
- ap.expectedTerminationReason = "stalled_recovery";
9107
9766
  const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : ap.driver.id;
9108
9767
  logger.warn(
9109
9768
  `[Agent ${agentId}] ${runtimeLabel} process stalled for ${staleForMinutes}m with ${ap.inbox.length} queued message(s); terminating for restart`
@@ -9169,31 +9828,32 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9169
9828
  this.sendRuntimeProfileReport(agentId);
9170
9829
  break;
9171
9830
  case "thinking": {
9172
- if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
9173
9831
  this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
9174
9832
  this.queueTrajectoryText(agentId, "thinking", event.text);
9175
- if (ap) ap.isIdle = false;
9176
- this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "thinking" });
9833
+ if (ap) {
9834
+ const reduction = reduceApmGatedAssistantContinuation(ap.gatedSteering);
9835
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "thinking" });
9836
+ }
9177
9837
  break;
9178
9838
  }
9179
9839
  case "text": {
9180
- if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
9181
9840
  this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
9182
9841
  this.queueTrajectoryText(agentId, "text", event.text);
9183
- if (ap) ap.isIdle = false;
9184
- this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "text" });
9842
+ if (ap) {
9843
+ const reduction = reduceApmGatedAssistantContinuation(ap.gatedSteering);
9844
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "text" });
9845
+ }
9185
9846
  break;
9186
9847
  }
9187
9848
  case "tool_call": {
9188
- if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
9189
9849
  this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed tool use)");
9190
9850
  this.flushPendingTrajectory(agentId);
9191
9851
  const invocation = normalizeToolDisplayInvocation(event.name, event.input);
9192
9852
  if (ap) {
9193
- ap.gatedSteering.outstandingToolUses++;
9853
+ const reduction = reduceApmGatedToolUse(ap.gatedSteering, { kind: "tool_call" });
9194
9854
  this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
9195
9855
  this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
9196
- this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
9856
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, {
9197
9857
  event: "tool_call",
9198
9858
  tool: invocation.toolName
9199
9859
  });
@@ -9205,23 +9865,20 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9205
9865
  toolName: invocation.toolName,
9206
9866
  toolInput: inputSummary
9207
9867
  }]);
9208
- if (ap) ap.isIdle = false;
9209
9868
  break;
9210
9869
  }
9211
9870
  case "tool_output": {
9212
9871
  const invocation = normalizeToolDisplayInvocation(event.name, {});
9213
9872
  if (ap) {
9214
- const hadOutstandingToolUse = ap.gatedSteering.outstandingToolUses > 0;
9215
- ap.gatedSteering.outstandingToolUses = Math.max(0, ap.gatedSteering.outstandingToolUses - 1);
9873
+ const reduction = reduceApmGatedToolUse(ap.gatedSteering, { kind: "tool_output" });
9216
9874
  this.recordRuntimeTraceEvent(agentId, ap, "tool.output.observed", { tool: invocation.toolName });
9217
9875
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.continuation.expected");
9218
- this.setGatedSteeringPhase(agentId, ap, "tool_boundary", {
9876
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, {
9219
9877
  event: "tool_output",
9220
9878
  tool: invocation.toolName
9221
9879
  });
9222
- ap.isIdle = false;
9223
- if (hadOutstandingToolUse && ap.gatedSteering.outstandingToolUses === 0) {
9224
- this.tryFlushGatedSteering(agentId, ap, "tool_batch_complete");
9880
+ if (reduction.shouldFlushToolBatch) {
9881
+ this.notifyGatedSteeringBoundary(agentId, ap, "tool_batch_complete");
9225
9882
  }
9226
9883
  }
9227
9884
  break;
@@ -9232,9 +9889,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9232
9889
  if (ap) this.startCompactionWatchdog(agentId, ap);
9233
9890
  this.broadcastActivity(agentId, "working", "Compacting context", [{ kind: "compaction_started" }]);
9234
9891
  if (ap) {
9235
- ap.gatedSteering.compacting = true;
9236
- this.setGatedSteeringPhase(agentId, ap, "compacting", { event: "compaction_started" });
9237
- ap.isIdle = false;
9892
+ const reduction = reduceApmGatedCompaction(ap.gatedSteering, { kind: "compaction_started" });
9893
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "compaction_started" });
9238
9894
  }
9239
9895
  break;
9240
9896
  case "compaction_finished":
@@ -9243,10 +9899,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9243
9899
  if (ap) this.clearCompactionWatchdog(ap);
9244
9900
  this.broadcastActivity(agentId, "working", "Context compaction finished", [{ kind: "compaction_finished" }]);
9245
9901
  if (ap) {
9246
- ap.gatedSteering.compacting = false;
9247
- this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "compaction_finished" });
9902
+ const reduction = reduceApmGatedCompaction(ap.gatedSteering, { kind: "compaction_finished" });
9903
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "compaction_finished" });
9248
9904
  this.flushCompactionBoundaryMessages(agentId, ap);
9249
- ap.isIdle = false;
9250
9905
  }
9251
9906
  break;
9252
9907
  case "turn_end":
@@ -9256,28 +9911,21 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9256
9911
  });
9257
9912
  this.flushPendingTrajectory(agentId);
9258
9913
  if (ap) {
9259
- this.clearGatedInFlightBatch(agentId, ap, "turn_end");
9260
9914
  if (event.sessionId) ap.sessionId = event.sessionId;
9261
- ap.gatedSteering.outstandingToolUses = 0;
9262
- ap.gatedSteering.compacting = false;
9263
- this.setGatedSteeringPhase(agentId, ap, "idle", { event: "turn_end" });
9264
- if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
9265
- const nextMessages = ap.inbox.splice(0, ap.inbox.length);
9266
- ap.notifications.clear();
9267
- if (ap.driver.busyDeliveryMode === "gated") {
9268
- ap.gatedSteering.lastFlushReason = "turn_end";
9269
- this.recordGatedSteeringEvent(agentId, ap, "flush", {
9270
- reason: "turn_end",
9271
- messageCount: nextMessages.length
9272
- });
9273
- }
9274
- this.broadcastActivity(agentId, "working", "Message received");
9275
- if (!this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle")) {
9276
- ap.isIdle = true;
9915
+ const reduction = reduceApmGatedTurnEnd(ap.gatedSteering, {
9916
+ inboxLength: ap.inbox.length,
9917
+ supportsStdinNotification: ap.driver.supportsStdinNotification,
9918
+ hasSession: Boolean(ap.sessionId),
9919
+ terminateProcessOnTurnEnd: ap.driver.terminateProcessOnTurnEnd === true
9920
+ });
9921
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "turn_end" });
9922
+ const deliverStdinEffect = reduction.effects.find((effect) => effect.kind === "deliver_stdin");
9923
+ if (deliverStdinEffect) {
9924
+ if (!this.executeApmGatedSteeringEffect(agentId, ap, deliverStdinEffect)) {
9925
+ this.commitApmIdleState(agentId, ap, true);
9277
9926
  this.broadcastActivity(agentId, "online", "Idle");
9278
9927
  }
9279
9928
  } else {
9280
- ap.isIdle = true;
9281
9929
  if (ap.lastRuntimeError) {
9282
9930
  this.broadcastActivity(agentId, "error", ap.lastRuntimeError);
9283
9931
  } else {
@@ -9290,7 +9938,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9290
9938
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
9291
9939
  });
9292
9940
  if (ap.driver.terminateProcessOnTurnEnd) {
9293
- ap.expectedTerminationReason = "turn_end";
9294
9941
  logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
9295
9942
  try {
9296
9943
  this.processExitTraceAttrs.set(ap.process, {
@@ -9319,10 +9966,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9319
9966
  if (runtimeErrorDiagnostics.spanAttrs.runtime_error_action_required === true) {
9320
9967
  visibleErrorMessage = formatRuntimeLoginRequiredMessage(ap.driver.id);
9321
9968
  }
9322
- this.setGatedSteeringPhase(agentId, ap, "error", { event: "error" });
9323
- if (ap.driver.busyDeliveryMode === "gated" && this.isThinkingBlockMutationError(event.message)) {
9324
- this.requeueGatedInFlightBatch(agentId, ap, "thinking_block_mutation_error");
9325
- ap.gatedSteering.toolBoundaryFlushDisabled = true;
9969
+ const shouldDisableToolBoundaryFlush = ap.driver.busyDeliveryMode === "gated" && this.isThinkingBlockMutationError(event.message);
9970
+ const terminalFailure = classifyTerminalFailure(ap);
9971
+ const reduction = reduceApmGatedError(ap.gatedSteering, {
9972
+ disableToolBoundaryFlush: shouldDisableToolBoundaryFlush,
9973
+ terminalWakeable: Boolean(ap.driver.supportsStdinNotification && terminalFailure && !terminalFailure.actionRequired)
9974
+ });
9975
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "error" });
9976
+ if (reduction.shouldDisableToolBoundaryFlush) {
9326
9977
  this.recordGatedSteeringEvent(agentId, ap, "disabled", {
9327
9978
  reason: "thinking_block_mutation_error",
9328
9979
  lastFlushReason: ap.gatedSteering.lastFlushReason,
@@ -9341,7 +9992,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9341
9992
  ...runtimeTraceCounterAttrs(ap),
9342
9993
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
9343
9994
  });
9344
- const terminalFailure = classifyTerminalFailure(ap);
9345
9995
  if (ap.driver.supportsStdinNotification && terminalFailure) {
9346
9996
  if (terminalFailure.actionRequired) {
9347
9997
  logger.warn(`[Agent ${agentId}] ${ap.driver.id} auth requires user action; terminating runtime process`);
@@ -9356,7 +10006,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9356
10006
  logger.warn(`[Agent ${agentId}] Failed to terminate ${ap.driver.id} after auth error: ${reason}`);
9357
10007
  }
9358
10008
  } else {
9359
- ap.isIdle = true;
9360
10009
  ap.notifications.clear();
9361
10010
  logger.info(`[Agent ${agentId}] Marked ${ap.driver.id} wakeable after terminal runtime error`);
9362
10011
  }
@@ -9471,6 +10120,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9471
10120
  return true;
9472
10121
  } else {
9473
10122
  ap.notifications.add(count);
10123
+ const retryScheduled = ap.driver.busyDeliveryMode === "direct" ? this.scheduleStdinNotification(agentId, ap, this.stdinNotificationRetryMs) : false;
9474
10124
  this.recordDaemonTrace("daemon.agent.stdin_notification", {
9475
10125
  agentId,
9476
10126
  runtime: ap.config.runtime,
@@ -9479,6 +10129,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9479
10129
  outcome: "encode_failed",
9480
10130
  mode: "busy",
9481
10131
  pending_notification_count: count,
10132
+ retry_scheduled: retryScheduled,
10133
+ notification_timer_present: ap.notifications.hasTimer,
9482
10134
  inbox_count: inboxCount,
9483
10135
  session_id_present: true
9484
10136
  }, "error");
@@ -9507,7 +10159,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9507
10159
  });
9508
10160
  if (messages.length === 0) {
9509
10161
  if (mode === "idle") {
9510
- ap.isIdle = true;
10162
+ this.commitApmIdleState(agentId, ap, true);
9511
10163
  }
9512
10164
  return true;
9513
10165
  }
@@ -9549,7 +10201,7 @@ ${RESPONSE_TARGET_HINT}`);
9549
10201
  if (!encoded) {
9550
10202
  ap.inbox.unshift(...messages);
9551
10203
  if (mode === "idle") {
9552
- ap.isIdle = true;
10204
+ this.commitApmIdleState(agentId, ap, true);
9553
10205
  }
9554
10206
  logger.warn(
9555
10207
  `[Agent ${agentId}] Failed to encode ${mode} stdin delivery; re-queued ${messages.length === 1 ? "message" : `${messages.length} messages`}`
@@ -9901,7 +10553,7 @@ var ReminderCache = class {
9901
10553
  };
9902
10554
 
9903
10555
  // src/machineLock.ts
9904
- import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
10556
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
9905
10557
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
9906
10558
  import os6 from "os";
9907
10559
  import path13 from "path";
@@ -9917,7 +10569,7 @@ var DaemonMachineLockConflictError = class extends Error {
9917
10569
  }
9918
10570
  };
9919
10571
  function apiKeyFingerprint(apiKey) {
9920
- return createHash3("sha256").update(apiKey).digest("hex");
10572
+ return createHash4("sha256").update(apiKey).digest("hex");
9921
10573
  }
9922
10574
  function getDaemonMachineLockId(apiKey) {
9923
10575
  return `machine-${apiKeyFingerprint(apiKey).slice(0, 16)}`;
@@ -10180,7 +10832,7 @@ function isDiagnosticErrorAttr(key) {
10180
10832
  }
10181
10833
 
10182
10834
  // src/traceBundleUpload.ts
10183
- import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
10835
+ import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
10184
10836
  import { gzipSync } from "zlib";
10185
10837
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
10186
10838
  import path15 from "path";
@@ -10310,12 +10962,12 @@ async function uploadWithSignedCapability({
10310
10962
  }
10311
10963
 
10312
10964
  // src/traceJitter.ts
10313
- import { createHash as createHash4 } from "crypto";
10965
+ import { createHash as createHash5 } from "crypto";
10314
10966
  var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
10315
10967
  var UPLOAD_INTERVAL_JITTER_SPAN_MS = 6e4;
10316
10968
  var MAX_FILE_AGE_JITTER_SPAN_MS = 6e4;
10317
10969
  function computeTraceJitter(lockId) {
10318
- const seed = createHash4("sha256").update(lockId).digest();
10970
+ const seed = createHash5("sha256").update(lockId).digest();
10319
10971
  return {
10320
10972
  initialUploadDelayMs: seed.readUInt32BE(0) % INITIAL_UPLOAD_DELAY_SPAN_MS,
10321
10973
  uploadIntervalJitterMs: seed.readUInt32BE(4) % UPLOAD_INTERVAL_JITTER_SPAN_MS,
@@ -10516,7 +11168,7 @@ var DaemonTraceBundleUploader = class {
10516
11168
  }
10517
11169
  };
10518
11170
  function sha256Hex(body) {
10519
- return createHash5("sha256").update(body).digest("hex");
11171
+ return createHash6("sha256").update(body).digest("hex");
10520
11172
  }
10521
11173
  function readPositiveIntegerEnv2(name, fallback) {
10522
11174
  const value = process.env[name];