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.
- package/dist/{SUPPORTED_CLIS-C3tcRetU.js → SUPPORTED_CLIS-AFV5pjSx.js} +2 -2
- package/dist/{SUPPORTED_CLIS-CY3SaV0D.js → SUPPORTED_CLIS-DRVuSo48.js} +3 -3
- package/dist/{agentShare-g6N1A6A_.js → agentShare-qdFSp9Uq.js} +6 -5
- package/dist/{callback-L4k-v2GG.js → callback-Da7MRLh6.js} +6 -5
- package/dist/{callback-z-w0ND5z.js → callback-DfNFdZin.js} +5 -5
- package/dist/{callbackCore-Cv4EOGjE.js → callbackCore-DcptaZwH.js} +1 -1
- package/dist/{callbackCore-Cg3Ye0ZS.js → callbackCore-K99x3C75.js} +1 -1
- package/dist/{channels-BwpN_8mu.js → channels-DQaZbare.js} +2 -2
- package/dist/cli.js +11 -11
- package/dist/{cwdConflictWarn-DZTxTGXq.js → cwdConflictWarn-CtQhl-vF.js} +1 -1
- package/dist/{cwdPassthroughHint-DDVkhBiD.js → cwdPassthroughHint-apaX_O7p.js} +1 -1
- package/dist/{expose-BG17GV7D.js → expose-BXn6My6A.js} +1 -1
- package/dist/{forkNested-VroZS-nk.js → forkNested-CDfWPiHw.js} +12 -2
- package/dist/{hist-CoXBBjJt.js → hist-BpocAe1N.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/initMsg-BjRseolL.js +3 -0
- package/dist/initMsg-C-w4A_eF.js +85 -0
- package/dist/needsInput-103zhZ90.js +79 -0
- package/dist/{nodeRuntime-CT0w6zsB.js → nodeRuntime-CRoEMWkp.js} +1 -1
- package/dist/{notifyDaemon-BL1tmxUK.js → notifyDaemon-BFzqcdN1.js} +32 -5
- package/dist/notifyStore-CEDiVyis.js +4 -0
- package/dist/notifyStore-Cbw2ZJ0X.js +559 -0
- package/dist/{openBrowser-Cw8hg7ok.js → openBrowser-DhQ9pJRi.js} +1 -1
- package/dist/parentLink-B798ASCW.js +5 -0
- package/dist/parentLink-C0cIRjfL.js +53 -0
- package/dist/parentPingLoop-BIKxzbPH.js +469 -0
- package/dist/{remotes-DWVw-Q7V.js → remotes-Dg_JscKk.js} +2 -2
- package/dist/{remotes-iIvqiFWK.js → remotes-iMhdZytp.js} +3 -3
- package/dist/{rustBinary-DSVRhDzO.js → rustBinary-JGNROWfY.js} +2 -2
- package/dist/{schedule-i4o5SJ_h.js → schedule-DbLpjgl5.js} +6 -6
- package/dist/{serve-CpGJD2Kt.js → serve-ucVXOci1.js} +31 -30
- package/dist/{setup-B-iQKsQW.js → setup-De7uYRZb.js} +4 -4
- package/dist/{share-DOnynkf4.js → share-Db_zb6gH.js} +1 -1
- package/dist/{share-B-e_9WBx.js → share-NUhi-Wdd.js} +1 -1
- package/dist/{spawnGate-S6EoFdzX.js → spawnGate-BvaZeHE7.js} +1 -1
- package/dist/{spawnGate-BmsTv340.js → spawnGate-CEXVmTtv.js} +2 -2
- package/dist/subcommands-C04exkIM.js +11 -0
- package/dist/{subcommands-CQPnT5qu.js → subcommands-DtF31RoW.js} +41 -656
- package/dist/{systemPathLink-CjLmiPbB.js → systemPathLink-7TFPwJna.js} +1 -1
- package/dist/{terminal-CybRgMZu.js → terminal-Dm32SXU-.js} +2 -2
- package/dist/{todoCli-DZmOmcvd.js → todoCli-c_R7D7Y-.js} +1 -1
- package/dist/{tray-Cjm3a8JH.js → tray-DZnGjJBK.js} +1 -1
- package/dist/trayApp-CZbwLK9t.js +5 -0
- package/dist/{trayApp-UglHbk_m.js → trayApp-CfkPG0wy.js} +2 -2
- package/dist/{ts-nuUnrVcf.js → ts-CGtoPER5.js} +43 -10
- package/dist/{versionChecker-CMOLbEEi.js → versionChecker-CJgW9DHY.js} +2 -2
- package/dist/{webrtcLink-CS4D-3G1.js → webrtcLink-DyCj380i.js} +1 -1
- package/dist/{webrtcRemote-zQybN9IR.js → webrtcRemote-BDg0jjTs.js} +2 -2
- package/dist/{widget-y5Bpvwsa.js → widget-DXtaX3QR.js} +3 -3
- package/dist/{workspaceConfig-BDqp7fYl.js → workspaceConfig-HiWQzq6A.js} +1 -1
- package/dist/{ws-CH8GzcQn.js → ws-CqlS5JhN.js} +5 -4
- package/dist/{ws-BrLV-AlH.js → ws-aKRZpI9b.js} +9 -11
- package/package.json +1 -1
- package/ts/forkNested.spec.ts +22 -0
- package/ts/forkNested.ts +19 -0
- package/ts/index.ts +77 -12
- package/ts/initMsg.spec.ts +114 -0
- package/ts/initMsg.ts +107 -0
- package/ts/ownerHeartbeat.ts +40 -1
- package/ts/parentLink.spec.ts +64 -0
- package/ts/parentLink.ts +58 -0
- package/ts/parentPing.spec.ts +176 -0
- package/ts/parentPing.ts +195 -0
- package/ts/parentPingLoop.delivery.spec.ts +236 -0
- package/ts/parentPingLoop.spec.ts +79 -0
- package/ts/parentPingLoop.ts +196 -0
- package/ts/parentPingSend.spec.ts +195 -0
- package/ts/parentPingSend.ts +148 -0
- package/ts/parentWatching.spec.ts +61 -0
- package/ts/parentWatching.ts +89 -0
- package/ts/subcommands.ts +11 -2
- package/ts/trayApp.spec.ts +31 -4
- package/ts/ws.ts +17 -12
- package/dist/subcommands-Cgt2-kjg.js +0 -10
- 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-
|
|
3
|
-
import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-
|
|
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-
|
|
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-
|
|
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-
|
|
176
|
+
//# sourceMappingURL=remotes-iMhdZytp.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
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-
|
|
246
|
+
//# sourceMappingURL=rustBinary-JGNROWfY.js.map
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import "./logger-CDIsZ-Pp.js";
|
|
2
|
-
import "./versionChecker-
|
|
3
|
-
import "./ts-
|
|
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-
|
|
10
|
-
import { f as resolveSpawnCwd } from "./workspaceConfig-
|
|
11
|
-
import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-
|
|
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-
|
|
156
|
+
//# sourceMappingURL=schedule-DbLpjgl5.js.map
|