agent-yes 1.265.4 → 1.266.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 (75) hide show
  1. package/dist/{SUPPORTED_CLIS-C3tcRetU.js → SUPPORTED_CLIS-AFV5pjSx.js} +2 -2
  2. package/dist/{SUPPORTED_CLIS-CY3SaV0D.js → SUPPORTED_CLIS-DRVuSo48.js} +3 -3
  3. package/dist/{agentShare-g6N1A6A_.js → agentShare-qdFSp9Uq.js} +6 -5
  4. package/dist/{callback-L4k-v2GG.js → callback-Da7MRLh6.js} +6 -5
  5. package/dist/{callback-z-w0ND5z.js → callback-DfNFdZin.js} +5 -5
  6. package/dist/{callbackCore-Cv4EOGjE.js → callbackCore-DcptaZwH.js} +1 -1
  7. package/dist/{callbackCore-Cg3Ye0ZS.js → callbackCore-K99x3C75.js} +1 -1
  8. package/dist/{channels-BwpN_8mu.js → channels-DQaZbare.js} +2 -2
  9. package/dist/cli.js +11 -11
  10. package/dist/{cwdConflictWarn-DZTxTGXq.js → cwdConflictWarn-CtQhl-vF.js} +1 -1
  11. package/dist/{cwdPassthroughHint-DDVkhBiD.js → cwdPassthroughHint-apaX_O7p.js} +1 -1
  12. package/dist/{expose-BG17GV7D.js → expose-BXn6My6A.js} +1 -1
  13. package/dist/{forkNested-VroZS-nk.js → forkNested-CDfWPiHw.js} +12 -2
  14. package/dist/{hist-CoXBBjJt.js → hist-BpocAe1N.js} +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/initMsg-BjRseolL.js +3 -0
  17. package/dist/initMsg-C-w4A_eF.js +85 -0
  18. package/dist/needsInput-103zhZ90.js +79 -0
  19. package/dist/{nodeRuntime-CT0w6zsB.js → nodeRuntime-CRoEMWkp.js} +1 -1
  20. package/dist/{notifyDaemon-BL1tmxUK.js → notifyDaemon-BFzqcdN1.js} +32 -5
  21. package/dist/notifyStore-CEDiVyis.js +4 -0
  22. package/dist/notifyStore-Cbw2ZJ0X.js +559 -0
  23. package/dist/{openBrowser-Cw8hg7ok.js → openBrowser-DhQ9pJRi.js} +1 -1
  24. package/dist/parentLink-B798ASCW.js +5 -0
  25. package/dist/parentLink-C0cIRjfL.js +53 -0
  26. package/dist/parentPingLoop-BIKxzbPH.js +469 -0
  27. package/dist/{remotes-DWVw-Q7V.js → remotes-Dg_JscKk.js} +2 -2
  28. package/dist/{remotes-iIvqiFWK.js → remotes-iMhdZytp.js} +3 -3
  29. package/dist/{rustBinary-DSVRhDzO.js → rustBinary-JGNROWfY.js} +2 -2
  30. package/dist/{schedule-i4o5SJ_h.js → schedule-DbLpjgl5.js} +6 -6
  31. package/dist/{serve-CpGJD2Kt.js → serve-ucVXOci1.js} +31 -30
  32. package/dist/{setup-B-iQKsQW.js → setup-De7uYRZb.js} +4 -4
  33. package/dist/{share-DOnynkf4.js → share-Db_zb6gH.js} +1 -1
  34. package/dist/{share-B-e_9WBx.js → share-NUhi-Wdd.js} +1 -1
  35. package/dist/{spawnGate-S6EoFdzX.js → spawnGate-BvaZeHE7.js} +1 -1
  36. package/dist/{spawnGate-BmsTv340.js → spawnGate-CEXVmTtv.js} +2 -2
  37. package/dist/subcommands-C04exkIM.js +11 -0
  38. package/dist/{subcommands-CQPnT5qu.js → subcommands-DtF31RoW.js} +41 -656
  39. package/dist/{systemPathLink-CjLmiPbB.js → systemPathLink-7TFPwJna.js} +1 -1
  40. package/dist/{terminal-CybRgMZu.js → terminal-Dm32SXU-.js} +2 -2
  41. package/dist/{todoCli-DZmOmcvd.js → todoCli-c_R7D7Y-.js} +1 -1
  42. package/dist/{tray-Cjm3a8JH.js → tray-DZnGjJBK.js} +1 -1
  43. package/dist/trayApp-CZbwLK9t.js +5 -0
  44. package/dist/{trayApp-UglHbk_m.js → trayApp-CfkPG0wy.js} +2 -2
  45. package/dist/{ts-nuUnrVcf.js → ts-CGtoPER5.js} +43 -10
  46. package/dist/{versionChecker-CMOLbEEi.js → versionChecker-CJgW9DHY.js} +2 -2
  47. package/dist/{webrtcLink-CS4D-3G1.js → webrtcLink-DyCj380i.js} +1 -1
  48. package/dist/{webrtcRemote-zQybN9IR.js → webrtcRemote-BDg0jjTs.js} +2 -2
  49. package/dist/{widget-y5Bpvwsa.js → widget-DXtaX3QR.js} +3 -3
  50. package/dist/{workspaceConfig-BDqp7fYl.js → workspaceConfig-HiWQzq6A.js} +1 -1
  51. package/dist/{ws-CH8GzcQn.js → ws-CqlS5JhN.js} +5 -4
  52. package/dist/{ws-BrLV-AlH.js → ws-aKRZpI9b.js} +9 -11
  53. package/package.json +1 -1
  54. package/ts/forkNested.spec.ts +22 -0
  55. package/ts/forkNested.ts +19 -0
  56. package/ts/index.ts +77 -12
  57. package/ts/initMsg.spec.ts +114 -0
  58. package/ts/initMsg.ts +107 -0
  59. package/ts/ownerHeartbeat.ts +40 -1
  60. package/ts/parentLink.spec.ts +64 -0
  61. package/ts/parentLink.ts +58 -0
  62. package/ts/parentPing.spec.ts +176 -0
  63. package/ts/parentPing.ts +195 -0
  64. package/ts/parentPingLoop.delivery.spec.ts +236 -0
  65. package/ts/parentPingLoop.spec.ts +79 -0
  66. package/ts/parentPingLoop.ts +196 -0
  67. package/ts/parentPingSend.spec.ts +195 -0
  68. package/ts/parentPingSend.ts +148 -0
  69. package/ts/parentWatching.spec.ts +61 -0
  70. package/ts/parentWatching.ts +89 -0
  71. package/ts/subcommands.ts +11 -2
  72. package/ts/trayApp.spec.ts +31 -4
  73. package/ts/ws.ts +17 -12
  74. package/dist/subcommands-Cgt2-kjg.js +0 -10
  75. package/dist/trayApp-CmwwBg8v.js +0 -5
@@ -0,0 +1,469 @@
1
+ import { n as logger } from "./logger-CDIsZ-Pp.js";
2
+ import "./globalPidIndex-Dq4KY2IV.js";
3
+ import { t as classifyNeedsInput } from "./needsInput-103zhZ90.js";
4
+ import { n as replyTargetOf, r as shortenHome } from "./initMsg-C-w4A_eF.js";
5
+ import { t as resolveSpawner } from "./parentLink-C0cIRjfL.js";
6
+ import { spawn } from "node:child_process";
7
+
8
+ //#region ts/parentPing.ts
9
+ const DEFAULT_PING_CONFIG = {
10
+ idleConfirmMs: 3e4,
11
+ repeatBaseMs: 12e4,
12
+ repeatMaxMs: 9e5,
13
+ maxRepeats: 4
14
+ };
15
+ function initialPingState() {
16
+ return {
17
+ state: null,
18
+ idleSince: null,
19
+ episode: null,
20
+ episodeQuestion: null,
21
+ sent: 0,
22
+ nextAt: null
23
+ };
24
+ }
25
+ /** Exponential backoff for repeat N (1-based), capped. */
26
+ function repeatDelayMs(attempt, cfg) {
27
+ const raw = cfg.repeatBaseMs * 2 ** Math.max(0, attempt - 1);
28
+ return Math.min(raw, cfg.repeatMaxMs);
29
+ }
30
+ /**
31
+ * Advance by one tick. Returns the ping to send (or null) and the next state.
32
+ *
33
+ * Episode semantics:
34
+ * - `needs_input` opens a `stuck` episode immediately. A CHANGED question
35
+ * re-opens it (the agent is asking something new — that's fresh information,
36
+ * not a repeat), which also resets the backoff.
37
+ * - `idle` opens a `finished` episode only after `idleConfirmMs` of continuous
38
+ * idleness — hysteresis, so a breath between tool calls is not "done".
39
+ * - `exited` fires once, terminally, and cannot be superseded or repeated: the
40
+ * process is gone, so there is nothing left to re-observe.
41
+ * - `active` closes any open episode. The child is working again; whatever we
42
+ * reported is stale and the parent will hear about the next edge.
43
+ */
44
+ function stepParentPing(prev, obs, cfg = DEFAULT_PING_CONFIG) {
45
+ const state = { ...prev };
46
+ const question = obs.question ?? null;
47
+ const wasExited = prev.episode === "exited";
48
+ state.state = obs.state;
49
+ if (wasExited) {
50
+ state.idleSince = null;
51
+ state.nextAt = null;
52
+ return {
53
+ state,
54
+ ping: null
55
+ };
56
+ }
57
+ if (obs.state === "exited") {
58
+ state.idleSince = null;
59
+ state.episode = "exited";
60
+ state.episodeQuestion = null;
61
+ state.sent = 1;
62
+ state.nextAt = null;
63
+ return {
64
+ state,
65
+ ping: {
66
+ reason: "exited",
67
+ attempt: 1,
68
+ question: null
69
+ }
70
+ };
71
+ }
72
+ if (obs.state === "active") {
73
+ state.idleSince = null;
74
+ state.episode = null;
75
+ state.episodeQuestion = null;
76
+ state.sent = 0;
77
+ state.nextAt = null;
78
+ return {
79
+ state,
80
+ ping: null
81
+ };
82
+ }
83
+ if (obs.state === "needs_input") {
84
+ state.idleSince = null;
85
+ if (prev.episode !== "stuck" || prev.episodeQuestion !== question) {
86
+ state.episode = "stuck";
87
+ state.episodeQuestion = question;
88
+ state.sent = 1;
89
+ state.nextAt = obs.now + repeatDelayMs(1, cfg);
90
+ return {
91
+ state,
92
+ ping: {
93
+ reason: "stuck",
94
+ attempt: 1,
95
+ question
96
+ }
97
+ };
98
+ }
99
+ return repeatIfDue(state, obs.now, cfg, question);
100
+ }
101
+ state.idleSince = prev.idleSince ?? obs.now;
102
+ if (prev.episode === "finished") return repeatIfDue(state, obs.now, cfg, null);
103
+ if (prev.episode === "stuck") return repeatIfDue(state, obs.now, cfg, prev.episodeQuestion);
104
+ if (obs.now - state.idleSince < cfg.idleConfirmMs) return {
105
+ state,
106
+ ping: null
107
+ };
108
+ state.episode = "finished";
109
+ state.episodeQuestion = null;
110
+ state.sent = 1;
111
+ state.nextAt = obs.now + repeatDelayMs(1, cfg);
112
+ return {
113
+ state,
114
+ ping: {
115
+ reason: "finished",
116
+ attempt: 1,
117
+ question: null
118
+ }
119
+ };
120
+ }
121
+ /** Emit the next repeat of an already-open episode if its timer is due. */
122
+ function repeatIfDue(state, now, cfg, question) {
123
+ if (state.nextAt === null || now < state.nextAt) return {
124
+ state,
125
+ ping: null
126
+ };
127
+ if (state.sent > cfg.maxRepeats) {
128
+ state.nextAt = null;
129
+ return {
130
+ state,
131
+ ping: null
132
+ };
133
+ }
134
+ const attempt = state.sent + 1;
135
+ state.sent = attempt;
136
+ state.nextAt = attempt > cfg.maxRepeats ? null : now + repeatDelayMs(attempt, cfg);
137
+ return {
138
+ state,
139
+ ping: {
140
+ reason: state.episode,
141
+ attempt,
142
+ question
143
+ }
144
+ };
145
+ }
146
+
147
+ //#endregion
148
+ //#region ts/parentPingSend.ts
149
+ /**
150
+ * Delivery side of the sub-agent→parent ping: turn a `PingDecision` into a
151
+ * message and inject it into the parent's stdin.
152
+ *
153
+ * We deliberately deliver through the real `ay send` subprocess rather than
154
+ * writing the parent's FIFO directly. `ay send` is where the `<ay-msg nonce
155
+ * from …>` attribution wrapper, the paste/submit handling and the durable
156
+ * message log all live; re-implementing any of that here would drift. The cost
157
+ * is one short-lived process per ping, which happens at most a handful of times
158
+ * per agent lifetime.
159
+ *
160
+ * Two flags are load-bearing:
161
+ * --force the recency guard ("you must have tailed this target lately")
162
+ * exists to stop an agent from firing at a target it never looked
163
+ * at. A child reporting to its own parent is the one relationship
164
+ * where that guard is pure false-positive.
165
+ * --no-wait the wrapper must never block its own PTY loop waiting for the
166
+ * parent's screen to settle.
167
+ */
168
+ const HEADLINE = {
169
+ finished: "looks FINISHED (idle at its prompt, no longer working)",
170
+ stuck: "is STUCK (parked waiting for input / not making progress)",
171
+ exited: "has EXITED"
172
+ };
173
+ /** Trim to `max` chars on a whitespace boundary where possible. */
174
+ function clip(s, max) {
175
+ const t = s.trim();
176
+ if (t.length <= max) return t;
177
+ return t.slice(0, max).replace(/\s+\S*$/, "") + "…";
178
+ }
179
+ /**
180
+ * The report body. Pure so the wording is testable and stable.
181
+ *
182
+ * It is explicitly framed as coming from the WRAPPER, not the agent: the agent
183
+ * itself may have said nothing at all (that is usually why this fires), and a
184
+ * parent that mistakes an automatic ping for the child's considered answer would
185
+ * act on a summary the child never wrote. The tail is included because the whole
186
+ * point is that the parent is not tailing.
187
+ */
188
+ function buildPingBody(decision, self, tail) {
189
+ const attempt = decision.attempt > 1 ? ` (reminder #${decision.attempt - 1} — no reply yet)` : "";
190
+ const lines = [
191
+ `<ay-report from ${self.cli} #${self.pid} @ ${shortenHome(self.cwd)}>`,
192
+ `Your sub-agent ${HEADLINE[decision.reason]}${attempt}.`,
193
+ `This is an automatic report from its agent-yes wrapper, not from the agent itself —`,
194
+ `treat the tail below as evidence, not as its answer.`
195
+ ];
196
+ if (self.prompt) lines.push(`Task it was spawned with: ${clip(self.prompt, 200)}`);
197
+ if (decision.reason === "exited" && self.exitCode != null) lines.push(`Exit code: ${self.exitCode}`);
198
+ if (decision.question) lines.push(`It is asking: ${clip(decision.question, 300)}`);
199
+ if (tail?.trim()) lines.push(`Last output:`, clip(tail, 1200));
200
+ lines.push(decision.reason === "exited" ? `Next: ay result get ${self.pid} | ay cat ${self.pid}` : `Next: ay tail ${self.pid} | ay send ${self.pid} "..." | ay exit ${self.pid}`, `</ay-report>`);
201
+ return lines.join("\n");
202
+ }
203
+ /**
204
+ * Argv for the `ay send` that carries `body` to `target`. Re-invokes THIS
205
+ * entrypoint (argv[0] + argv[1]) rather than a bare `ay` on PATH, so a wrapper
206
+ * running from a checkout, a global link, or `npx` all ping through the exact
207
+ * same build they are part of.
208
+ */
209
+ function buildSendArgv(target, argv1) {
210
+ const send = [
211
+ "send",
212
+ target,
213
+ "-",
214
+ "--force",
215
+ "--no-wait"
216
+ ];
217
+ if (argv1) return {
218
+ cmd: process.execPath,
219
+ args: [argv1, ...send]
220
+ };
221
+ return {
222
+ cmd: "ay",
223
+ args: send
224
+ };
225
+ }
226
+ /**
227
+ * Fire one ping. Resolves when the send subprocess exits; never throws — a
228
+ * failed report must not take down the agent it is reporting about.
229
+ *
230
+ * `selfWrapperPid` is stamped as AGENT_YES_PID for the subprocess so `ay send`
231
+ * attributes the message to THIS child. Without it the subprocess would inherit
232
+ * the wrapper's own AGENT_YES_PID — which is the PARENT's wrapper pid — and the
233
+ * parent would receive a message apparently sent by itself.
234
+ */
235
+ async function deliverPing(opts) {
236
+ const { cmd, args } = buildSendArgv(opts.target, process.argv[1]);
237
+ return await new Promise((resolve) => {
238
+ let done = false;
239
+ const finish = (ok) => {
240
+ if (done) return;
241
+ done = true;
242
+ resolve(ok);
243
+ };
244
+ try {
245
+ const child = spawn(cmd, args, {
246
+ stdio: [
247
+ "pipe",
248
+ "ignore",
249
+ "ignore"
250
+ ],
251
+ env: {
252
+ ...process.env,
253
+ AGENT_YES_PID: String(opts.selfWrapperPid)
254
+ }
255
+ });
256
+ const timer = setTimeout(() => {
257
+ child.kill("SIGKILL");
258
+ finish(false);
259
+ }, opts.timeoutMs ?? 3e4);
260
+ timer.unref?.();
261
+ child.on("error", (err) => {
262
+ clearTimeout(timer);
263
+ logger.debug(`[parent-ping] send failed: ${err.message}`);
264
+ finish(false);
265
+ });
266
+ child.on("exit", (code) => {
267
+ clearTimeout(timer);
268
+ finish(code === 0);
269
+ });
270
+ child.stdin?.on("error", () => {});
271
+ child.stdin?.end(opts.body);
272
+ } catch (err) {
273
+ logger.debug(`[parent-ping] spawn failed: ${err.message}`);
274
+ finish(false);
275
+ }
276
+ });
277
+ }
278
+
279
+ //#endregion
280
+ //#region ts/parentWatching.ts
281
+ /**
282
+ * "Is my parent already monitoring me?" — the suppression signal for the
283
+ * automatic finished/stuck/exited push.
284
+ *
285
+ * A parent harness that HAS a monitor loop (Claude Code's Monitor tool, an
286
+ * `ay ls --watch` NDJSON stream, `ay notify watch`, or just a polling `ay tail`)
287
+ * already sees the child go idle / needs_input / stopped, with better fidelity
288
+ * and on its own schedule. Injecting an `<ay-report …>` into that parent's stdin
289
+ * on top of it is pure noise — worse than noise, since a message lands in the
290
+ * parent's composer and interrupts whatever it was doing.
291
+ *
292
+ * So the push is a FALLBACK, not a duplicate channel: it fires only when nobody
293
+ * is looking. Two independent signals count as "looking", and either is enough:
294
+ *
295
+ * 1. A live `ay notify watch` heartbeat for this parent (the explicit,
296
+ * purpose-built watcher — see notifyStore's watcher registry).
297
+ * 2. This parent tailed/read THIS child within the read window — the same
298
+ * recency ledger `ay send`'s misdelivery guard uses. This is what catches
299
+ * an ad-hoc monitor loop that just polls `ay tail`/`ay ls`, which is what a
300
+ * harness-driven parent actually does.
301
+ *
302
+ * Failing OPEN (assume not watching, so we do send) is deliberate: a duplicate
303
+ * report is a minor annoyance, a silently stranded parent is the bug this whole
304
+ * feature exists to fix.
305
+ */
306
+ /**
307
+ * Parse the mode from the env value. Anything unrecognised (including unset)
308
+ * is `auto` — the setting is an escape hatch, not something to fail a run over.
309
+ */
310
+ function parseReportMode(raw) {
311
+ const v = raw?.trim().toLowerCase();
312
+ return v === "always" || v === "never" ? v : "auto";
313
+ }
314
+ /**
315
+ * Decide whether to actually deliver a report. Pure, so the policy is testable
316
+ * without touching the fs.
317
+ *
318
+ * `always` and `never` are absolute. `auto` sends only when the parent shows no
319
+ * sign of monitoring — EXCEPT that `exited` always goes out: it is terminal, it
320
+ * is the one edge a poll can miss entirely (the child's record can be reaped
321
+ * between two polls), and it can never repeat, so it cannot become spam.
322
+ */
323
+ function shouldDeliverReport(opts) {
324
+ if (opts.mode === "never") return false;
325
+ if (opts.mode === "always") return true;
326
+ if (opts.reason === "exited") return true;
327
+ return !opts.parentIsWatching;
328
+ }
329
+ /**
330
+ * Whether `parentWrapperPid` currently appears to be monitoring the agent
331
+ * `selfAgentPid`. Best-effort and fail-open (returns false on any error).
332
+ */
333
+ async function isParentWatching(parentWrapperPid, parentAgentPid, selfAgentPid, now = Date.now()) {
334
+ try {
335
+ const { liveWatchers } = await import("./notifyStore-CEDiVyis.js");
336
+ if ((await liveWatchers(now)).has(parentWrapperPid)) return true;
337
+ } catch (error) {
338
+ logger.debug("[parent-ping] watcher lookup failed:", error);
339
+ }
340
+ try {
341
+ const { lastReadAt, READ_WINDOW_MS } = await import("./subcommands-C04exkIM.js");
342
+ const last = await lastReadAt(`agent:${parentAgentPid}`, selfAgentPid);
343
+ if (last !== null && now - last <= READ_WINDOW_MS) return true;
344
+ } catch (error) {
345
+ logger.debug("[parent-ping] read-recency lookup failed:", error);
346
+ }
347
+ return false;
348
+ }
349
+
350
+ //#endregion
351
+ //#region ts/parentPingLoop.ts
352
+ /**
353
+ * Wiring for the sub-agent→parent ping: a small poller the wrapper owns for its
354
+ * OWN agent, feeding `stepParentPing` and delivering whatever it decides.
355
+ *
356
+ * Kept out of ts/index.ts (already ~1400 lines) and out of the auto-response
357
+ * heartbeat: this must not share a tick budget with the thing that answers
358
+ * permission prompts, and its classification is a different question ("is my
359
+ * agent done/stuck?") from the heartbeat's ("does this line need a keystroke?").
360
+ *
361
+ * No-parent is the common case (a human-launched top-level agent), and it costs
362
+ * nothing: we resolve the spawner once and simply never start a timer.
363
+ */
364
+ const POLL_MS = 5e3;
365
+ /**
366
+ * Classify the wrapper's own agent from its rendered screen.
367
+ *
368
+ * Order matters and is the same precedence `deriveLiveState` uses: a `working`
369
+ * spinner beats everything (a long silent tool call is NOT done), then a pending
370
+ * menu, then an idle prompt. Anything unrecognised falls back to `active` —
371
+ * always the safe default, because a false `active` costs a delayed report while
372
+ * a false `idle` tells the parent "finished" about an agent mid-task.
373
+ */
374
+ function classifySelfState(lines, conf) {
375
+ const text = lines.join("\n");
376
+ if (conf.working?.some((re) => re.test(text))) return "active";
377
+ if (classifyNeedsInput(lines, conf)) return "needs_input";
378
+ if (conf.ready?.some((re) => re.test(text))) return "idle";
379
+ return "active";
380
+ }
381
+ /**
382
+ * Start watching our own agent and reporting to whoever spawned it.
383
+ *
384
+ * `screen()` returns the currently-rendered tail lines (the wrapper's xterm
385
+ * proxy); it is called on every poll, so it must be cheap and must not throw.
386
+ */
387
+ function startParentPingLoop(opts) {
388
+ const cfg = opts.config ?? DEFAULT_PING_CONFIG;
389
+ const now = opts.now ?? (() => Date.now());
390
+ const mode = opts.mode ?? parseReportMode(process.env.AGENT_YES_REPORT_PARENT);
391
+ let state = initialPingState();
392
+ let target = null;
393
+ let parentAgentPid = null;
394
+ let timer = null;
395
+ let stopped = false;
396
+ let inFlight = Promise.resolve();
397
+ const send = (decision, tail) => {
398
+ if (!decision || !target) return Promise.resolve();
399
+ const t = target;
400
+ inFlight = inFlight.then(async () => {
401
+ const watching = mode === "auto" && opts.parentPid !== void 0 && parentAgentPid !== null ? await isParentWatching(opts.parentPid, parentAgentPid, opts.self.pid, now()) : false;
402
+ if (!shouldDeliverReport({
403
+ mode,
404
+ parentIsWatching: watching,
405
+ reason: decision.reason
406
+ })) {
407
+ logger.debug(`[parent-ping] ${decision.reason} suppressed (mode=${mode}, parent watching=${watching})`);
408
+ return;
409
+ }
410
+ const ok = await deliverPing({
411
+ target: t,
412
+ body: buildPingBody(decision, opts.self, tail),
413
+ selfWrapperPid: opts.selfWrapperPid
414
+ });
415
+ logger.debug(`[parent-ping] ${decision.reason} attempt ${decision.attempt} → ${t}: ${ok ? "sent" : "FAILED"}`);
416
+ }).catch(() => {});
417
+ return inFlight;
418
+ };
419
+ const ready = resolveSpawner(opts.parentPid).then((spawner) => {
420
+ if (!spawner || stopped) return false;
421
+ target = replyTargetOf(spawner);
422
+ parentAgentPid = spawner.pid;
423
+ timer = setInterval(() => {
424
+ let lines = [];
425
+ try {
426
+ lines = opts.screen();
427
+ } catch {
428
+ return;
429
+ }
430
+ const stepped = stepParentPing(state, {
431
+ now: now(),
432
+ state: classifySelfState(lines, opts.patterns)
433
+ }, cfg);
434
+ state = stepped.state;
435
+ if (stepped.ping) send(stepped.ping, lines.join("\n"));
436
+ }, opts.pollMs ?? POLL_MS);
437
+ timer.unref?.();
438
+ return true;
439
+ }).catch(() => false);
440
+ return {
441
+ stop() {
442
+ stopped = true;
443
+ if (timer) clearInterval(timer);
444
+ timer = null;
445
+ },
446
+ ready,
447
+ async pingExit(exitCode) {
448
+ await ready;
449
+ this.stop();
450
+ if (!target) return;
451
+ let tail = null;
452
+ try {
453
+ tail = opts.screen().join("\n");
454
+ } catch {}
455
+ opts.self.exitCode = exitCode;
456
+ const stepped = stepParentPing(state, {
457
+ now: now(),
458
+ state: "exited"
459
+ }, cfg);
460
+ state = stepped.state;
461
+ if (!stepped.ping) return;
462
+ await send(stepped.ping, tail);
463
+ }
464
+ };
465
+ }
466
+
467
+ //#endregion
468
+ export { startParentPingLoop };
469
+ //# sourceMappingURL=parentPingLoop-BIKxzbPH.js.map
@@ -1,5 +1,5 @@
1
1
  import "./e2e-CUMZC53I.js";
2
- import "./webrtcLink-CS4D-3G1.js";
3
- import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-iIvqiFWK.js";
2
+ import "./webrtcLink-DyCj380i.js";
3
+ import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-iMhdZytp.js";
4
4
 
5
5
  export { cmdRemote };
@@ -1,4 +1,4 @@
1
- import { n as isWebrtcSpec } from "./webrtcLink-CS4D-3G1.js";
1
+ import { n as isWebrtcSpec } from "./webrtcLink-DyCj380i.js";
2
2
  import { mkdir, readFile, writeFile } from "fs/promises";
3
3
  import { homedir } from "os";
4
4
  import path from "path";
@@ -86,7 +86,7 @@ async function resolveRemoteSpec(spec) {
86
86
  * The bridge lives for the rest of the process (torn down on `process.exit`).
87
87
  */
88
88
  async function resolveWebrtc(link, keyword) {
89
- const { startWebrtcBridge } = await import("./webrtcRemote-zQybN9IR.js");
89
+ const { startWebrtcBridge } = await import("./webrtcRemote-BDg0jjTs.js");
90
90
  const bridge = await startWebrtcBridge(link);
91
91
  return {
92
92
  url: bridge.baseUrl,
@@ -173,4 +173,4 @@ async function cmdRemote(rest) {
173
173
 
174
174
  //#endregion
175
175
  export { resolveRemoteSpec as a, readRemotes as i, deleteRemoteAlias as n, writeRemoteAlias as o, parseDirectRemoteSpec as r, cmdRemote as t };
176
- //# sourceMappingURL=remotes-iIvqiFWK.js.map
176
+ //# sourceMappingURL=remotes-iMhdZytp.js.map
@@ -1,4 +1,4 @@
1
- import { r as getInstalledPackage } from "./versionChecker-CMOLbEEi.js";
1
+ import { r as getInstalledPackage } from "./versionChecker-CJgW9DHY.js";
2
2
  import { execFileSync } from "child_process";
3
3
  import { existsSync, mkdirSync, unlinkSync } from "fs";
4
4
  import { chmod, copyFile } from "fs/promises";
@@ -243,4 +243,4 @@ async function getRustBinary(options = {}) {
243
243
 
244
244
  //#endregion
245
245
  export { findTrayLauncher as n, getRustBinary as r, findSpawnHiddenLauncher as t };
246
- //# sourceMappingURL=rustBinary-DSVRhDzO.js.map
246
+ //# sourceMappingURL=rustBinary-JGNROWfY.js.map
@@ -1,14 +1,14 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-CMOLbEEi.js";
3
- import "./ts-nuUnrVcf.js";
2
+ import "./versionChecker-CJgW9DHY.js";
3
+ import "./ts-CGtoPER5.js";
4
4
  import "./todoAutomation-BQRW614S.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-Dq4KY2IV.js";
7
7
  import "./pidStore-DJqSZPJi.js";
8
8
  import "./messageLog-BYFwioFY.js";
9
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C3tcRetU.js";
10
- import { f as resolveSpawnCwd } from "./workspaceConfig-BDqp7fYl.js";
11
- import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-CT0w6zsB.js";
9
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-AFV5pjSx.js";
10
+ import { f as resolveSpawnCwd } from "./workspaceConfig-HiWQzq6A.js";
11
+ import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-CRoEMWkp.js";
12
12
  import { createHash } from "node:crypto";
13
13
 
14
14
  //#region ts/oxmgrService.ts
@@ -153,4 +153,4 @@ async function cmdSchedule(rest) {
153
153
 
154
154
  //#endregion
155
155
  export { cmdSchedule };
156
- //# sourceMappingURL=schedule-i4o5SJ_h.js.map
156
+ //# sourceMappingURL=schedule-DbLpjgl5.js.map