pi-subagents 0.80.0 → 0.82.0

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.
Files changed (184) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bundle/agents.js +18 -0
  3. package/bundle/background-work.js +16 -0
  4. package/bundle/capability-ceiling.js +22 -0
  5. package/bundle/child-tool-plan.js +11 -0
  6. package/bundle/chunk-43SCHIMD.js +57 -0
  7. package/bundle/chunk-4QFZZV3O.js +57 -0
  8. package/bundle/chunk-4SDU2BZX.js +1190 -0
  9. package/bundle/chunk-52UBALJL.js +753 -0
  10. package/bundle/chunk-5LFLVSTM.js +260 -0
  11. package/bundle/chunk-5TYMSZX3.js +235 -0
  12. package/bundle/chunk-62OCY2S4.js +14 -0
  13. package/bundle/chunk-7F6RKT6W.js +132 -0
  14. package/bundle/chunk-7IVSUMQQ.js +1337 -0
  15. package/bundle/chunk-7J4ZXMY7.js +18 -0
  16. package/bundle/chunk-7PCKZHR5.js +1234 -0
  17. package/bundle/chunk-7YMXOYEU.js +1373 -0
  18. package/bundle/chunk-B5EKXDHE.js +16870 -0
  19. package/bundle/chunk-CIZQXJ5Q.js +8619 -0
  20. package/bundle/chunk-CTPAE36Y.js +94 -0
  21. package/bundle/chunk-D4EHT5SJ.js +14 -0
  22. package/bundle/chunk-GC4DSPMH.js +946 -0
  23. package/bundle/chunk-GJECHSAR.js +179 -0
  24. package/bundle/chunk-M2CTFYT3.js +4164 -0
  25. package/bundle/chunk-MHZ4G23A.js +1296 -0
  26. package/bundle/chunk-N2BN7BF5.js +159 -0
  27. package/bundle/chunk-NF7IEKBH.js +490 -0
  28. package/bundle/chunk-NGGAFDL6.js +742 -0
  29. package/bundle/chunk-NIXWDZZV.js +9 -0
  30. package/bundle/chunk-NRQ75MGH.js +8327 -0
  31. package/bundle/chunk-OYF6AFDY.js +458 -0
  32. package/bundle/chunk-PLCCBLPY.js +526 -0
  33. package/bundle/chunk-RMQFMMAT.js +416 -0
  34. package/bundle/chunk-SIJ4S3EN.js +276 -0
  35. package/bundle/chunk-UKG4V45O.js +228 -0
  36. package/bundle/chunk-VH5OAWVG.js +4096 -0
  37. package/bundle/chunk-W2SZ22EM.js +5462 -0
  38. package/bundle/chunk-WVD4UEL4.js +9367 -0
  39. package/bundle/chunk-XSWOTIA3.js +175 -0
  40. package/bundle/chunk-YFCK7H2S.js +129 -0
  41. package/bundle/chunk-YKEWIB4M.js +380 -0
  42. package/bundle/chunk-YYREO77V.js +137 -0
  43. package/bundle/chunk-Z7KVHQHL.js +173 -0
  44. package/bundle/chunk-ZLINIYTP.js +8613 -0
  45. package/bundle/chunk-ZQGSDHL6.js +128 -0
  46. package/bundle/control-channel.js +11 -0
  47. package/bundle/delegation.js +14 -0
  48. package/bundle/external-job-provider.js +20 -0
  49. package/bundle/external-runs.js +21 -0
  50. package/bundle/index.js +30 -0
  51. package/bundle/inspectors.js +8 -0
  52. package/bundle/intercom-bridge.js +11 -0
  53. package/bundle/preflight.js +425 -0
  54. package/bundle/project-panes.js +28 -0
  55. package/bundle/required-child-extensions.js +6 -0
  56. package/bundle/shared-types.js +8 -0
  57. package/bundle/workflow-resources.js +7 -0
  58. package/docs/agents.md +31 -0
  59. package/docs/configuration.md +9 -1
  60. package/docs/extension-api.md +27 -0
  61. package/docs/observability.md +6 -2
  62. package/docs/tool-reference.md +3 -1
  63. package/docs/workflows.md +16 -0
  64. package/package.json +19 -19
  65. package/skills/pi-subagents/references/execution-controls.md +5 -4
  66. package/src/agents/agent-management.js +3 -3
  67. package/src/agents/agent-memory.js +3 -1
  68. package/src/agents/agents.js +6 -6
  69. package/src/agents/frontmatter.d.ts +4 -0
  70. package/src/agents/frontmatter.js +24 -3
  71. package/src/agents/skills.js +2 -1
  72. package/src/api/delegation.d.ts +20 -0
  73. package/src/extension/config.js +3 -0
  74. package/src/extension/herdr-pi-bridge.js +17 -10
  75. package/src/extension/index.js +109 -9
  76. package/src/extension/schemas.js +3 -3
  77. package/src/intercom/native-supervisor-channel.js +20 -0
  78. package/src/missions/lifecycle.js +7 -1
  79. package/src/runs/background/active-async-capacity.d.ts +1 -0
  80. package/src/runs/background/active-async-capacity.js +9 -3
  81. package/src/runs/background/async-execution.js +20 -10
  82. package/src/runs/background/async-job-tracker.d.ts +6 -1
  83. package/src/runs/background/async-job-tracker.js +9 -4
  84. package/src/runs/background/async-status.d.ts +2 -0
  85. package/src/runs/background/async-status.js +21 -2
  86. package/src/runs/background/await-async-run.js +3 -7
  87. package/src/runs/background/boot-id.d.ts +12 -0
  88. package/src/runs/background/boot-id.js +47 -0
  89. package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
  90. package/src/runs/background/interrupted-elsewhere.js +68 -0
  91. package/src/runs/background/notify.d.ts +2 -0
  92. package/src/runs/background/notify.js +13 -0
  93. package/src/runs/background/result-delivery-ownership.d.ts +6 -0
  94. package/src/runs/background/result-delivery-ownership.js +19 -1
  95. package/src/runs/background/result-watcher.d.ts +3 -1
  96. package/src/runs/background/result-watcher.js +24 -8
  97. package/src/runs/background/run-child-session.d.ts +1 -6
  98. package/src/runs/background/run-child-session.js +19 -1
  99. package/src/runs/background/run-status.js +2 -1
  100. package/src/runs/background/stale-run-reconciler.d.ts +25 -0
  101. package/src/runs/background/stale-run-reconciler.js +54 -13
  102. package/src/runs/background/subagent-runner-bootstrap.js +3 -0
  103. package/src/runs/background/subagent-runner.d.ts +4 -0
  104. package/src/runs/background/subagent-runner.js +13 -5
  105. package/src/runs/background/subagent-wait.d.ts +1 -1
  106. package/src/runs/background/subagent-wait.js +6 -1
  107. package/src/runs/background/terminal-run-index.d.ts +1 -0
  108. package/src/runs/background/terminal-run-index.js +4 -2
  109. package/src/runs/foreground/async-stop-action.js +3 -1
  110. package/src/runs/foreground/execution.js +45 -5
  111. package/src/runs/foreground/foreground-history.d.ts +16 -2
  112. package/src/runs/foreground/foreground-history.js +133 -31
  113. package/src/runs/foreground/subagent-executor.d.ts +11 -0
  114. package/src/runs/foreground/subagent-executor.js +277 -74
  115. package/src/runs/shared/child-launch.d.ts +2 -2
  116. package/src/runs/shared/child-session.d.ts +3 -2
  117. package/src/runs/shared/child-session.js +51 -8
  118. package/src/runs/shared/child-tool-plan.d.ts +7 -0
  119. package/src/runs/shared/child-tool-plan.js +21 -6
  120. package/src/runs/shared/herdr-connection.d.ts +17 -8
  121. package/src/runs/shared/herdr-connection.js +19 -7
  122. package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
  123. package/src/runs/shared/herdr-external-adapters.js +1 -1
  124. package/src/runs/shared/herdr-machine.d.ts +12 -2
  125. package/src/runs/shared/herdr-machine.js +55 -9
  126. package/src/runs/shared/herdr-pane-host.d.ts +88 -0
  127. package/src/runs/shared/herdr-pane-host.js +155 -0
  128. package/src/runs/shared/herdr-placed-run.d.ts +27 -150
  129. package/src/runs/shared/herdr-placed-run.js +48 -395
  130. package/src/runs/shared/local-machine-access.d.ts +5 -0
  131. package/src/runs/shared/local-machine-access.js +28 -0
  132. package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
  133. package/src/runs/shared/orca-progress-tabs.js +1 -24
  134. package/src/runs/shared/parallel-utils.d.ts +1 -1
  135. package/src/runs/shared/placed-run-owner.d.ts +49 -0
  136. package/src/runs/shared/placed-run-owner.js +93 -0
  137. package/src/runs/shared/placement-host.d.ts +99 -0
  138. package/src/runs/shared/placement-host.js +157 -0
  139. package/src/runs/shared/rex-machine.d.ts +25 -0
  140. package/src/runs/shared/rex-machine.js +83 -0
  141. package/src/runs/shared/rex-pane-host.d.ts +62 -0
  142. package/src/runs/shared/rex-pane-host.js +327 -0
  143. package/src/runs/shared/structured-output.d.ts +1 -0
  144. package/src/runs/shared/structured-output.js +1 -0
  145. package/src/shared/accessible-dir.js +4 -1
  146. package/src/shared/completion-owner.d.ts +5 -0
  147. package/src/shared/completion-owner.js +5 -0
  148. package/src/shared/provider-diagnostics.d.ts +9 -0
  149. package/src/shared/provider-diagnostics.js +75 -0
  150. package/src/shared/session-identity.d.ts +2 -0
  151. package/src/shared/session-identity.js +26 -0
  152. package/src/shared/types.d.ts +48 -6
  153. package/src/slash/delegation-adapters.d.ts +3 -1
  154. package/src/slash/delegation-adapters.js +7 -0
  155. package/src/slash/prompt-template-bridge.js +3 -1
  156. package/src/slash/subagents-admin.d.ts +1 -0
  157. package/src/slash/subagents-admin.js +1 -1
  158. package/src/tui/fleet-layout.d.ts +20 -0
  159. package/src/tui/fleet-layout.js +17 -0
  160. package/src/tui/fleet-viewport.d.ts +12 -0
  161. package/src/tui/fleet-viewport.js +35 -0
  162. package/src/tui/fleet.d.ts +6 -6
  163. package/src/tui/fleet.js +93 -66
  164. package/src/tui/render.d.ts +6 -0
  165. package/src/tui/render.js +3 -3
  166. package/src/tui/subagent-messages.js +1 -0
  167. package/src/workflows/scripted-workflow.d.ts +1 -1
  168. package/src/workflows/structured-workflow-scripts.d.ts +1 -0
  169. package/src/workflows/structured-workflow-scripts.js +5 -4
  170. package/src/workflows/workflow-auto-resume.d.ts +32 -0
  171. package/src/workflows/workflow-auto-resume.js +102 -0
  172. package/src/workflows/workflow-launch-record.d.ts +27 -0
  173. package/src/workflows/workflow-launch-record.js +48 -0
  174. package/src/workflows/workflow-resources.js +2 -2
  175. package/src/workflows/workflow-resume-claim.d.ts +82 -0
  176. package/src/workflows/workflow-resume-claim.js +210 -0
  177. package/src/workflows/workflow-resume.d.ts +56 -0
  178. package/src/workflows/workflow-resume.js +106 -0
  179. package/src/workflows/workflow-reuse.d.ts +15 -9
  180. package/src/workflows/workflow-reuse.js +77 -14
  181. package/src/workflows/workflow-revival.d.ts +2 -0
  182. package/src/workflows/workflow-revival.js +2 -1
  183. package/src/workflows/workflow-stop-cause.d.ts +6 -0
  184. package/src/workflows/workflow-stop-cause.js +8 -0
@@ -0,0 +1,458 @@
1
+ import {
2
+ shouldUseNativeFsWatch
3
+ } from "./chunk-NIXWDZZV.js";
4
+ import {
5
+ resolveWatchPath,
6
+ writeAtomicJson
7
+ } from "./chunk-CIZQXJ5Q.js";
8
+ import {
9
+ POLL_INTERVAL_MS
10
+ } from "./chunk-5LFLVSTM.js";
11
+
12
+ // src/runs/background/control-channel.ts
13
+ import { randomUUID } from "node:crypto";
14
+ import * as fs from "node:fs";
15
+ import * as path from "node:path";
16
+ var CONTROL_SAFETY_POLL_INTERVAL_MS = 5e3;
17
+ var REPEATED_SCAN_ERROR_SUMMARY_MS = 6e4;
18
+ function validCommandRequest(raw) {
19
+ if (!raw || typeof raw !== "object") return false;
20
+ const value = raw;
21
+ return value.type === "command" && typeof value.id === "string" && /^[a-f0-9-]{36}$/.test(value.id) && typeof value.ownerId === "string" && /^[a-f0-9-]{36}$/.test(value.ownerId) && (value.operation === "status" || value.operation === "yield" || value.operation === "cancel") && Number.isSafeInteger(value.deadlineAt) && value.deadlineAt > 0 && (value.toolCallId === void 0 ? value.operation === "status" : typeof value.toolCallId === "string" && value.toolCallId.length > 0 && value.toolCallId.length <= 256);
22
+ }
23
+ function commandRequestsDir(dir) {
24
+ return path.join(controlInboxDir(dir), "command-requests");
25
+ }
26
+ function requestAsyncCommand(dir, request) {
27
+ if (!validCommandRequest(request)) throw new Error("Malformed command request.");
28
+ writeAtomicJson(path.join(commandRequestsDir(dir), `${request.id}.json`), request);
29
+ }
30
+ function consumeCommandRequests(dir, fsImpl) {
31
+ const requests = [];
32
+ for (const name of fsImpl.readdirSync(commandRequestsDir(dir)).filter((entry) => entry.endsWith(".json")).sort()) {
33
+ const file = path.join(commandRequestsDir(dir), name);
34
+ let value;
35
+ try {
36
+ value = JSON.parse(fsImpl.readFileSync(file, "utf8"));
37
+ } catch (error) {
38
+ if (error.code === "ENOENT") continue;
39
+ }
40
+ try {
41
+ fsImpl.rmSync(file);
42
+ } catch (error) {
43
+ if (error.code === "ENOENT") continue;
44
+ throw error;
45
+ }
46
+ if (validCommandRequest(value)) requests.push(value);
47
+ }
48
+ return requests;
49
+ }
50
+ var STEER_REQUESTS_DIR = "steer-requests";
51
+ var STOP_REQUESTS_DIR = "stop-requests";
52
+ var REVIVAL_BRIEFS_DIR = "revival-briefs";
53
+ var MAX_STEER_QUEUE_SIZE = 20;
54
+ var STEER_INBOX_CLOSED_FILE = "steer-inbox-closed.json";
55
+ var STOP_INBOX_CLOSED_FILE = "stop-inbox-closed.json";
56
+ var STOP_INBOX_CLOSED_MESSAGE = "Runner stop inbox is closed. Retry stop after runner shutdown is observed.";
57
+ var MAX_STEER_MESSAGE_BYTES = 128 * 1024;
58
+ var MAX_STEER_REQUEST_ID_LENGTH = 256;
59
+ function controlInboxDir(asyncDir) {
60
+ return path.join(asyncDir, "control");
61
+ }
62
+ function interruptRequestPath(asyncDir) {
63
+ return path.join(controlInboxDir(asyncDir), "interrupt.json");
64
+ }
65
+ function timeoutRequestPath(asyncDir) {
66
+ return path.join(controlInboxDir(asyncDir), "timeout.json");
67
+ }
68
+ function stopRequestPath(asyncDir) {
69
+ return path.join(controlInboxDir(asyncDir), "stop.json");
70
+ }
71
+ function stopRequestsDir(asyncDir) {
72
+ return path.join(controlInboxDir(asyncDir), STOP_REQUESTS_DIR);
73
+ }
74
+ function steerRequestsDir(asyncDir) {
75
+ return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);
76
+ }
77
+ function steerInboxClosedPath(asyncDir) {
78
+ return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);
79
+ }
80
+ function stopInboxClosedPath(asyncDir) {
81
+ return path.join(controlInboxDir(asyncDir), STOP_INBOX_CLOSED_FILE);
82
+ }
83
+ function closeSteerInbox(asyncDir, state, write = writeAtomicJson) {
84
+ write(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });
85
+ }
86
+ function assertChildIndex(index) {
87
+ if (!Number.isInteger(index) || index < 0 || index > 1e6) throw new Error("child index must be a non-negative integer.");
88
+ }
89
+ function validStopChildId(childId) {
90
+ return typeof childId === "string" && Boolean(childId.trim()) && childId.length <= 256 && !/[\r\n]/.test(childId);
91
+ }
92
+ function steerRequestFileName(request) {
93
+ return `${String(request.ts).padStart(13, "0")}-${Buffer.from(request.id).toString("base64url")}.json`;
94
+ }
95
+ function stopRequestFileName(request) {
96
+ return `${String(request.ts ?? 0).padStart(13, "0")}-${randomUUID()}.json`;
97
+ }
98
+ function validSteerRequest(request) {
99
+ return request.type === "steer" && typeof request.id === "string" && /^[^\s]+$/.test(request.id) && request.id.length <= MAX_STEER_REQUEST_ID_LENGTH && typeof request.ts === "number" && Number.isFinite(request.ts) && request.ts > 0 && typeof request.message === "string" && Boolean(request.message.trim()) && Buffer.byteLength(request.message, "utf8") <= MAX_STEER_MESSAGE_BYTES && (request.mode === void 0 || request.mode === "steer" || request.mode === "follow_up" || request.mode === "auto") && (request.targetIndex === void 0 || Number.isInteger(request.targetIndex) && request.targetIndex >= 0 && request.targetIndex <= 1e6) && (request.targetIndexes === void 0 || request.targetIndex === void 0 && Array.isArray(request.targetIndexes) && request.targetIndexes.length > 0 && request.targetIndexes.length <= 1e3 && request.targetIndexes.every((index) => Number.isInteger(index) && index >= 0 && index <= 1e6) && new Set(request.targetIndexes).size === request.targetIndexes.length) && (request.source === void 0 || typeof request.source === "string" && Boolean(request.source.trim()) && request.source.length <= 256);
100
+ }
101
+ function writeSteerRequestToDir(dir, request) {
102
+ if (!validSteerRequest(request)) throw new Error("steer request is malformed or exceeds transport limits.");
103
+ const requestPath = path.join(dir, steerRequestFileName(request));
104
+ writeAtomicJson(requestPath, request);
105
+ return requestPath;
106
+ }
107
+ function requestAsyncInterrupt(asyncDir, payload = {}, deps = {}) {
108
+ const requestPath = interruptRequestPath(asyncDir);
109
+ const request = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "interrupt" };
110
+ writeAtomicJson(requestPath, request);
111
+ return requestPath;
112
+ }
113
+ function requestAsyncStop(asyncDir, payload = {}, deps = {}) {
114
+ if (payload.targetIndex !== void 0) assertChildIndex(payload.targetIndex);
115
+ if (payload.childId !== void 0 && !validStopChildId(payload.childId)) {
116
+ throw new Error("stop childId must be a non-empty string without newlines and at most 256 characters.");
117
+ }
118
+ const closedPath = stopInboxClosedPath(asyncDir);
119
+ if (fs.existsSync(closedPath)) throw new Error(STOP_INBOX_CLOSED_MESSAGE);
120
+ const request = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "stop" };
121
+ const requestPath = path.join(stopRequestsDir(asyncDir), stopRequestFileName(request));
122
+ (deps.write ?? writeAtomicJson)(requestPath, request);
123
+ if (fs.existsSync(closedPath)) {
124
+ fs.rmSync(requestPath, { force: true });
125
+ throw new Error(STOP_INBOX_CLOSED_MESSAGE);
126
+ }
127
+ return requestPath;
128
+ }
129
+ function requestAsyncSteer(asyncDir, payload, deps = {}) {
130
+ const message = payload.message.trim();
131
+ if (!message) throw new Error("steer message must not be empty.");
132
+ if (Buffer.byteLength(message, "utf8") > MAX_STEER_MESSAGE_BYTES) throw new Error(`steer message exceeds ${MAX_STEER_MESSAGE_BYTES} UTF-8 bytes.`);
133
+ if (payload.targetIndex !== void 0 && (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0 || payload.targetIndex > 1e6)) {
134
+ throw new Error("steer targetIndex must be an integer between 0 and 1000000.");
135
+ }
136
+ if (payload.targetIndexes !== void 0 && (!Array.isArray(payload.targetIndexes) || payload.targetIndex !== void 0 || payload.targetIndexes.length === 0 || payload.targetIndexes.length > 1e3 || payload.targetIndexes.some((index) => !Number.isInteger(index) || index < 0 || index > 1e6) || new Set(payload.targetIndexes).size !== payload.targetIndexes.length)) {
137
+ throw new Error("steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.");
138
+ }
139
+ const closedPath = steerInboxClosedPath(asyncDir);
140
+ if (fs.existsSync(closedPath)) throw new Error("Async run no longer accepts steering requests.");
141
+ const request = {
142
+ type: "steer",
143
+ id: payload.id ?? deps.randomId?.() ?? randomUUID(),
144
+ ts: payload.ts ?? deps.now?.() ?? Date.now(),
145
+ message,
146
+ ...payload.mode && payload.mode !== "steer" ? { mode: payload.mode } : {},
147
+ ...payload.targetIndex !== void 0 ? { targetIndex: payload.targetIndex } : {},
148
+ ...payload.targetIndexes !== void 0 ? { targetIndexes: [...payload.targetIndexes] } : {},
149
+ ...payload.source ? { source: payload.source } : {}
150
+ };
151
+ const requestPath = writeSteerRequestToDir(steerRequestsDir(asyncDir), request);
152
+ if (fs.existsSync(closedPath)) {
153
+ fs.rmSync(requestPath, { force: true });
154
+ throw new Error("Async run stopped accepting steering before the request was committed.");
155
+ }
156
+ return requestPath;
157
+ }
158
+ function parseSteerRequest(raw) {
159
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
160
+ const input = raw;
161
+ if (!validSteerRequest(input)) return void 0;
162
+ return {
163
+ type: "steer",
164
+ id: input.id.trim(),
165
+ ts: input.ts,
166
+ message: input.message.trim(),
167
+ ...input.mode ? { mode: input.mode } : {},
168
+ ...input.targetIndex !== void 0 ? { targetIndex: input.targetIndex } : {},
169
+ ...input.targetIndexes !== void 0 ? { targetIndexes: [...input.targetIndexes] } : {},
170
+ ...typeof input.source === "string" && input.source.trim() ? { source: input.source } : {}
171
+ };
172
+ }
173
+ function consumeSteerRequestsFromDir(dir, fsImpl = fs, onError = () => {
174
+ }) {
175
+ let entries;
176
+ try {
177
+ entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
178
+ } catch (error) {
179
+ if (error.code !== "ENOENT") onError(error);
180
+ return [];
181
+ }
182
+ const requests = [];
183
+ for (const entry of entries) {
184
+ const requestPath = path.join(dir, entry);
185
+ let parsed;
186
+ let text;
187
+ try {
188
+ text = fsImpl.readFileSync(requestPath, "utf-8");
189
+ } catch (error) {
190
+ if (error.code !== "ENOENT") onError(error);
191
+ continue;
192
+ }
193
+ try {
194
+ parsed = parseSteerRequest(JSON.parse(text));
195
+ } catch {
196
+ parsed = void 0;
197
+ }
198
+ try {
199
+ fsImpl.rmSync(requestPath, { recursive: true });
200
+ } catch (error) {
201
+ if (error.code !== "ENOENT") onError(error);
202
+ continue;
203
+ }
204
+ if (parsed) requests.push(parsed);
205
+ }
206
+ return requests.sort((left, right) => left.ts - right.ts || left.id.localeCompare(right.id));
207
+ }
208
+ function consumeSteerRequests(asyncDir, fsImpl = fs, onError) {
209
+ return consumeSteerRequestsFromDir(steerRequestsDir(asyncDir), fsImpl, onError);
210
+ }
211
+ function queueRevivalBrief(asyncDir, request) {
212
+ const dir = path.join(controlInboxDir(asyncDir), REVIVAL_BRIEFS_DIR);
213
+ const queued = fs.existsSync(dir) ? fs.readdirSync(dir).filter((entry) => entry.endsWith(".json")).length : 0;
214
+ if (queued >= MAX_STEER_QUEUE_SIZE) throw new Error(`Follow-up queue is full (${MAX_STEER_QUEUE_SIZE} messages).`);
215
+ return writeSteerRequestToDir(dir, { ...request, mode: "follow_up" });
216
+ }
217
+ function readRevivalBriefs(asyncDir) {
218
+ const dir = path.join(controlInboxDir(asyncDir), REVIVAL_BRIEFS_DIR);
219
+ if (!fs.existsSync(dir)) return [];
220
+ return fs.readdirSync(dir).filter((entry) => entry.endsWith(".json")).sort().flatMap((entry) => {
221
+ const filePath = path.join(dir, entry);
222
+ try {
223
+ const request = parseSteerRequest(JSON.parse(fs.readFileSync(filePath, "utf-8")));
224
+ return request ? [{ request, path: filePath }] : [];
225
+ } catch {
226
+ return [];
227
+ }
228
+ });
229
+ }
230
+ function consumeInterruptRequest(asyncDir, fsImpl = fs) {
231
+ const requestPath = interruptRequestPath(asyncDir);
232
+ if (!fsImpl.existsSync(requestPath)) return false;
233
+ try {
234
+ fsImpl.rmSync(requestPath, { force: true, recursive: true });
235
+ } catch {
236
+ }
237
+ return true;
238
+ }
239
+ function consumeTimeoutRequest(asyncDir, fsImpl = fs) {
240
+ const requestPath = timeoutRequestPath(asyncDir);
241
+ if (!fsImpl.existsSync(requestPath)) return false;
242
+ try {
243
+ fsImpl.rmSync(requestPath, { force: true, recursive: true });
244
+ } catch {
245
+ }
246
+ return true;
247
+ }
248
+ function parseStopRequest(raw) {
249
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
250
+ const parsed = raw;
251
+ if (parsed.type !== "stop") return void 0;
252
+ if (Object.hasOwn(parsed, "targetIndex") && !(Number.isInteger(parsed.targetIndex) && parsed.targetIndex >= 0 && parsed.targetIndex <= 1e6)) return void 0;
253
+ if (Object.hasOwn(parsed, "childId") && !validStopChildId(parsed.childId)) return void 0;
254
+ return {
255
+ type: "stop",
256
+ ...typeof parsed.ts === "number" ? { ts: parsed.ts } : {},
257
+ ...typeof parsed.source === "string" ? { source: parsed.source } : {},
258
+ ...typeof parsed.reason === "string" ? { reason: parsed.reason } : {},
259
+ ...parsed.targetIndex !== void 0 ? { targetIndex: parsed.targetIndex } : {},
260
+ ...validStopChildId(parsed.childId) ? { childId: parsed.childId } : {}
261
+ };
262
+ }
263
+ function consumeStopRequestFile(requestPath, fsImpl, onError) {
264
+ let text;
265
+ try {
266
+ text = fsImpl.readFileSync(requestPath, "utf-8");
267
+ } catch (error) {
268
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) onError?.(error);
269
+ return void 0;
270
+ }
271
+ let request;
272
+ try {
273
+ request = parseStopRequest(JSON.parse(text));
274
+ } catch {
275
+ request = void 0;
276
+ }
277
+ try {
278
+ fsImpl.rmSync(requestPath, { force: true, recursive: true });
279
+ } catch (error) {
280
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) onError?.(error);
281
+ return void 0;
282
+ }
283
+ return request;
284
+ }
285
+ function consumeStopRequestPayloads(asyncDir, fsImpl = fs, onError) {
286
+ const dir = stopRequestsDir(asyncDir);
287
+ const requests = [];
288
+ if (fsImpl.existsSync(dir)) {
289
+ let entries;
290
+ try {
291
+ entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
292
+ } catch (error) {
293
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) onError?.(error);
294
+ entries = [];
295
+ }
296
+ for (const entry of entries) {
297
+ const request = consumeStopRequestFile(path.join(dir, entry), fsImpl, onError);
298
+ if (request) requests.push(request);
299
+ }
300
+ }
301
+ const legacyPath = stopRequestPath(asyncDir);
302
+ if (fsImpl.existsSync(legacyPath)) {
303
+ const request = consumeStopRequestFile(legacyPath, fsImpl, onError);
304
+ if (request) requests.push(request);
305
+ }
306
+ return requests.sort((left, right) => (left.ts ?? 0) - (right.ts ?? 0));
307
+ }
308
+ function deliverInterruptRequest(input) {
309
+ requestAsyncInterrupt(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
310
+ }
311
+ function deliverStopRequest(input) {
312
+ requestAsyncStop(input.asyncDir, { ...input.source ? { source: input.source } : {}, ...input.targetIndex !== void 0 ? { targetIndex: input.targetIndex } : {}, ...input.childId ? { childId: input.childId } : {} }, { now: input.now });
313
+ }
314
+ function watchAsyncControlInbox(asyncDir, opts) {
315
+ const fsImpl = opts.fs ?? fs;
316
+ const timers = opts.timers ?? { setInterval, clearInterval };
317
+ const dir = controlInboxDir(asyncDir);
318
+ const report = (error, phase, request) => {
319
+ try {
320
+ if (opts.onError) opts.onError(error, phase, request);
321
+ else console.error(`Control inbox ${phase} failed:`, error);
322
+ } catch (reportError) {
323
+ console.error("Control inbox error reporter failed:", reportError);
324
+ }
325
+ };
326
+ const repeatedScanErrors = /* @__PURE__ */ new Map();
327
+ let scanFailed = false;
328
+ const reportScan = (error) => {
329
+ scanFailed = true;
330
+ const text = error instanceof Error ? error.message : String(error);
331
+ const now = (opts.now ?? Date.now)();
332
+ const repeated = repeatedScanErrors.get(text);
333
+ if (!repeated) {
334
+ repeatedScanErrors.set(text, { suppressed: 0, since: now });
335
+ report(error, "scan");
336
+ return;
337
+ }
338
+ repeated.suppressed++;
339
+ if (now - repeated.since < REPEATED_SCAN_ERROR_SUMMARY_MS) return;
340
+ report(`${repeated.suppressed} more identical failures in the last ${Math.round((now - repeated.since) / 1e3)}s: ${text}`, "scan");
341
+ repeated.suppressed = 0;
342
+ repeated.since = now;
343
+ };
344
+ const dirs = [
345
+ ...opts.onInterrupt || opts.onTimeout || opts.onStop ? [dir] : [],
346
+ ...opts.onStop ? [stopRequestsDir(asyncDir)] : [],
347
+ ...opts.onSteer ? [steerRequestsDir(asyncDir)] : [],
348
+ ...opts.onCommand ? [commandRequestsDir(asyncDir)] : []
349
+ ];
350
+ if (dirs.length === 0) return () => {
351
+ };
352
+ try {
353
+ for (const target of dirs) fsImpl.mkdirSync(target, { recursive: true });
354
+ } catch (error) {
355
+ report(error, "install");
356
+ }
357
+ let disposed = false;
358
+ const check = () => {
359
+ if (disposed) return;
360
+ scanFailed = false;
361
+ try {
362
+ if (opts.onCommand) for (const request of consumeCommandRequests(asyncDir, fsImpl)) {
363
+ try {
364
+ opts.onCommand(request);
365
+ } catch (error) {
366
+ report(error, "callback");
367
+ }
368
+ }
369
+ if (opts.onStop) for (const request of consumeStopRequestPayloads(asyncDir, fsImpl, reportScan)) {
370
+ try {
371
+ opts.onStop(request);
372
+ } catch (error) {
373
+ report(error, "callback");
374
+ }
375
+ }
376
+ if (opts.onTimeout && consumeTimeoutRequest(asyncDir, fsImpl)) {
377
+ try {
378
+ opts.onTimeout();
379
+ } catch (error) {
380
+ report(error, "callback");
381
+ }
382
+ }
383
+ if (opts.onInterrupt && consumeInterruptRequest(asyncDir, fsImpl)) {
384
+ try {
385
+ opts.onInterrupt();
386
+ } catch (error) {
387
+ report(error, "callback");
388
+ }
389
+ }
390
+ if (opts.onSteer) for (const request of consumeSteerRequestsFromDir(steerRequestsDir(asyncDir), fsImpl, reportScan)) {
391
+ try {
392
+ opts.onSteer(request);
393
+ } catch (error) {
394
+ report(error, "callback", request);
395
+ }
396
+ }
397
+ } catch (error) {
398
+ reportScan(error);
399
+ }
400
+ if (!scanFailed) repeatedScanErrors.clear();
401
+ };
402
+ check();
403
+ const watchers = [];
404
+ let interval;
405
+ let safetyInterval;
406
+ const startPolling = () => {
407
+ if (interval || disposed) return;
408
+ if (safetyInterval) {
409
+ timers.clearInterval(safetyInterval);
410
+ safetyInterval = void 0;
411
+ }
412
+ interval = timers.setInterval(check, opts.pollIntervalMs ?? POLL_INTERVAL_MS);
413
+ interval.unref?.();
414
+ };
415
+ try {
416
+ if (shouldUseNativeFsWatch("runner-control-inbox", opts.platform)) {
417
+ for (const target of dirs) {
418
+ const watcher = fsImpl.watch(resolveWatchPath(target, fsImpl.realpathSync.native), check);
419
+ watcher.on?.("error", startPolling);
420
+ watchers.push(watcher);
421
+ }
422
+ if (!interval) {
423
+ safetyInterval = timers.setInterval(check, opts.safetyPollIntervalMs ?? CONTROL_SAFETY_POLL_INTERVAL_MS);
424
+ safetyInterval.unref?.();
425
+ }
426
+ } else {
427
+ startPolling();
428
+ }
429
+ } catch {
430
+ startPolling();
431
+ }
432
+ return () => {
433
+ if (disposed) return;
434
+ disposed = true;
435
+ for (const watcher of watchers) {
436
+ try {
437
+ watcher.close();
438
+ } catch {
439
+ }
440
+ }
441
+ if (interval) timers.clearInterval(interval);
442
+ if (safetyInterval) timers.clearInterval(safetyInterval);
443
+ };
444
+ }
445
+
446
+ export {
447
+ requestAsyncCommand,
448
+ stopInboxClosedPath,
449
+ closeSteerInbox,
450
+ requestAsyncStop,
451
+ requestAsyncSteer,
452
+ consumeSteerRequests,
453
+ queueRevivalBrief,
454
+ readRevivalBriefs,
455
+ deliverInterruptRequest,
456
+ deliverStopRequest,
457
+ watchAsyncControlInbox
458
+ };