@fitchmultz/pi-subagents 0.44.4 → 0.44.5
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/CHANGELOG.md +9 -0
- package/README.md +4 -0
- package/dist/extension/build-info.js +1 -1
- package/dist/extension/compact-tools.js +1 -1
- package/dist/extension/schemas.js +9 -9
- package/dist/history/index.js +14 -9
- package/dist/history/ingest.js +26 -5
- package/dist/history/worker.js +13 -8
- package/dist/pi-intercom/bridge.js +750 -0
- package/dist/runs/foreground/question-control.js +23 -12
- package/dist/runs/foreground/wait-run.js +5 -3
- package/dist/runs/shared/run-history.js +8 -1
- package/dist/runs/shared/supervisor-questions.js +32 -2
- package/dist/shared/status-format.js +1 -1
- package/docs/intercom-bridge.md +212 -0
- package/docs/intercom.md +3 -1
- package/package.json +2 -1
- package/scripts/intercom-bridge-credentials.mjs +143 -0
- package/scripts/intercom-remote.mjs +103 -0
- package/scripts/package-smoke.mjs +4 -0
- package/src/extension/compact-tools.ts +1 -1
- package/src/extension/schemas.ts +9 -9
- package/src/history/index.ts +13 -9
- package/src/history/ingest.ts +22 -5
- package/src/history/worker.ts +11 -7
- package/src/pi-intercom/bridge.ts +668 -0
- package/src/runs/foreground/question-control.ts +23 -11
- package/src/runs/foreground/wait-run.ts +5 -2
- package/src/runs/shared/run-history.ts +8 -1
- package/src/runs/shared/supervisor-questions.ts +45 -2
- package/src/shared/status-format.ts +2 -2
- package/src/shared/types.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.44.5] - 2026-10-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Optional loopback-only mTLS Intercom HTTPS bridge over SSH reverse forwarding, with certificate-bound remote peer identity, bounded messaging/inbox API, metadata audit logs, remote CLI, and credential issue/rotation/revocation tooling. The same-machine broker and extension startup remain unchanged.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- Keep supervisor-question tool results bounded: page compact question/answer previews without repeating frozen launch metadata or native attempt baselines. Full question, interview and immutable answer files remain available; answering, cancellation and revival retain their original durable contracts.
|
|
10
|
+
- Keep the history worker's cost per child append proportional to the new bytes. It used to re-hash every indexed byte of a linked session on each change, so a live child writing to a source of several hundred megabytes held the worker at full CPU for hours. Each check now re-hashes the last indexed line. Replacement, truncation and in-place rewrites of that line still start a new generation, and detail reads still verify their record digests. Indexes from earlier releases are verified in full once and then switch to the bounded check without re-indexing.
|
|
11
|
+
|
|
3
12
|
## [0.44.4] - 2026-10-04
|
|
4
13
|
|
|
5
14
|
### Added
|
package/README.md
CHANGED
|
@@ -450,6 +450,8 @@ The files in `prompts/` document common workflows without registering additional
|
|
|
450
450
|
|
|
451
451
|
## Bundled intercom
|
|
452
452
|
|
|
453
|
+
For a remote agent on Linux, the optional [secure remote bridge](docs/intercom-bridge.md) provides a loopback-only mTLS HTTPS API over an SSH reverse forward. It registers a certificate-bound peer such as `grok-bot` and permits only messaging operations; the private Unix broker socket remains local. The guide includes setup, remote commands, credential rotation and revocation.
|
|
454
|
+
|
|
453
455
|
The same `pi-subagents` install registers the intercom extension and skill. `load_intercom({})` exposes the full peer tool explicitly; actionable peer inbound and recovered reply needs also expose it before use. Presence, passive breadcrumbs, routine subagent notices, and human messages answered in chat do not. Broker presence and delivery continue even with the schema hidden. Managed children get a private coordination channel back to the parent Pi session unless an explicit agent extension allowlist excludes it. See [the intercom guide](docs/intercom.md) for direct peer messaging, keyboard UI, tool actions, configuration, and broker details.
|
|
454
456
|
|
|
455
457
|
If you previously installed the standalone package, remove that old settings entry once to avoid loading two intercom extensions:
|
|
@@ -489,6 +491,8 @@ agent_runs({ action: "stop", id: "<run-id>" })
|
|
|
489
491
|
|
|
490
492
|
Resume the **same saved supervisor session**, even from another cwd, to recover its questions. A different session does not silently adopt them. Ordinary `intercom` replies also save the answer while the live waiter is connected. After supervisor/broker restart, prefer `agent_runs` questions/answer; a nudge is guidance, not an answer to a blocking question.
|
|
491
493
|
|
|
494
|
+
`questions` returns 20 compact summaries by default (use `limit: 1–100` and `details.questionList.nextOffset` as `offset`), with unresolved questions first. Filter by run `id`, child `index`, or exact `questionId`. Question and answer previews are capped at 2,048 characters; truncation is marked. Read `questionPath` for the complete question, structured interview and saved launch contract, or `answerPath` for the exact immutable answer. These files remain unchanged by browsing; repeated tool results do not copy launch metadata or native attempt baselines. If questions change while paging, restart at offset 0 or look up the exact question ID.
|
|
495
|
+
|
|
492
496
|
Answers are saved once. Repeating the same answer does not start duplicate work; conflicting answers are rejected without replacing the original. A live child reads the saved answer; an exited child resumes from its saved session in a new run, retaining the original acceptance contract. An answer receipt is not execution completion. `stop` cancels outstanding questions and aborts a live waiter using its saved run identity, including after a parent restart.
|
|
493
497
|
|
|
494
498
|
If an answer launch was interrupted before a continuation existed, the reply gives an explicit `continue` recovery call. It refuses to restart when launch evidence is uncertain; inspect the advertised continuation first. Questions, answers, launch contracts, and results live under `${PI_CODING_AGENT_DIR:-~/.pi/agent}/sessions/subagent-runs/<run-id>/`, separate from temporary logs. Ownership and review use native parent `subagent-run` entries; pending intercom delivery is journaled in that same saved Pi session. Temporary cleanup does not erase these records, but deleting saved sessions or metadata removes their recovery data. Pending questions are not age-cleaned.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const EXTENSION_BUILD = Object.freeze({"version":"0.44.
|
|
1
|
+
export const EXTENSION_BUILD = Object.freeze({"version":"0.44.5","sha256":"53f1d8cd3c71b05fdfc903bfcecd06eb5d80bb367a16a09d235199d3f93266f2"});
|
|
@@ -60,7 +60,7 @@ export function registerCompactSubagentTools(pi, options) {
|
|
|
60
60
|
name: "agent_runs",
|
|
61
61
|
defaultActive: false,
|
|
62
62
|
label: "Agent Runs",
|
|
63
|
-
description: `List ${childSafe ? "only this child's directly owned" : "your delegated"} runs across working directories (questions/failures, then live work, then unreviewed results; 20 per page). Filter globally by agent/state/text, sort, and page with the returned cursor. history reads 100 bounded native-entry previews; search finds saved visible text using words or quoted phrases, not operators or prefixes. Browse freshness is not completion or delivery proof. Inspect concise results, paths and continuations; full:true includes the full task/configuration. Answer durable questions, nudge, stop, continue, or save parent-only review. Review notes are not sent to children; put actionable instructions in continue/nudge. Inspect/review/nudge never restart finished work. Continue/answer can launch a saved child; async:false waits for its actual result. Saved continuations keep settings unless agent selects a current profile; model overrides win. Live guidance never mutates model or acceptance. profiles lists roles, sources, context and model/thinking/fallback defaults. History survives reload.`,
|
|
63
|
+
description: `List ${childSafe ? "only this child's directly owned" : "your delegated"} runs across working directories (questions/failures, then live work, then unreviewed results; 20 per page). Filter globally by agent/state/text, sort, and page with the returned cursor. history reads 100 bounded native-entry previews; search finds saved visible text using words or quoted phrases, not operators or prefixes. questions pages 20 compact previews (offset/limit); questionId selects one, and questionPath/answerPath retain full records. Browse freshness is not completion or delivery proof. Inspect concise results, paths and continuations; full:true includes the full task/configuration. Answer durable questions, nudge, stop, continue, or save parent-only review. Review notes are not sent to children; put actionable instructions in continue/nudge. Inspect/review/nudge never restart finished work. Continue/answer can launch a saved child; async:false waits for its actual result. Saved continuations keep settings unless agent selects a current profile; model overrides win. Live guidance never mutates model or acceptance. profiles lists roles, sources, context and model/thinking/fallback defaults. History survives reload.`,
|
|
64
64
|
parameters: AgentRunsParams,
|
|
65
65
|
async execute(id, params, signal, onUpdate, ctx) {
|
|
66
66
|
const normalized = normalizeEverydayParams(params, true);
|
|
@@ -255,13 +255,13 @@ const HistoryQueryFields = {
|
|
|
255
255
|
export const AgentRunsValidationParams = Type.Object({
|
|
256
256
|
action: Type.Enum(["list", "inspect", "history", "search", "nudge", "stop", "continue", "profiles", "questions", "answer", "review"], { type: "string" }),
|
|
257
257
|
id: Type.Optional(Type.String({ minLength: 1, description: "Run ID or unambiguous prefix." })),
|
|
258
|
-
questionId: Type.Optional(Type.String({ minLength: 1, description: "Durable supervisor question ID for answer." })),
|
|
258
|
+
questionId: Type.Optional(Type.String({ minLength: 1, description: "Durable supervisor question ID for exact questions lookup or answer." })),
|
|
259
259
|
async: Type.Optional(Type.Boolean({ description: "Continue/answer: false waits for the actual continuation result." })),
|
|
260
260
|
index: Type.Optional(Type.Integer({ minimum: 0, description: "Child index for a multi-child run." })),
|
|
261
261
|
message: Type.Optional(Type.String({ minLength: 1, description: "Guidance, follow-up, answer, or optional parent-only review note. Review notes are not sent to the child; put actionable instructions in continue/nudge. Only continue/answer can start a saved child." })),
|
|
262
262
|
decision: Type.Optional(Type.Enum(["accepted", "needs_changes"], { type: "string", description: "Parent-only review outcome; not sent to the child. Separate from execution and runtime acceptance checks." })),
|
|
263
|
-
offset: Type.Optional(Type.Integer({ minimum: 0, description: "List offset; history is retained regardless of page size." })),
|
|
264
|
-
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, description: "Results per list/search/history page (default 20; history 100)." })),
|
|
263
|
+
offset: Type.Optional(Type.Integer({ minimum: 0, description: "List/questions offset; history is retained regardless of page size." })),
|
|
264
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, description: "Results per list/questions/search/history page (default 20; history 100)." })),
|
|
265
265
|
...HistoryQueryFields,
|
|
266
266
|
full: Type.Optional(Type.Boolean({ description: "Inspect only: include the full task and saved launch configuration. Default is a concise report; stored data is unchanged." })),
|
|
267
267
|
agent: Type.Optional(Type.String({ minLength: 1, description: "List/search: filter by child agent. Continue/answer: adopt this current profile, including model, thinking and fallbacks; a separate model override wins. Otherwise keep saved settings. Required for old runs without a saved profile." })),
|
|
@@ -279,8 +279,8 @@ export const AgentRunsValidationParams = Type.Object({
|
|
|
279
279
|
{ if: { properties: { action: { enum: ["review"] } } }, then: requiredObject("decision") },
|
|
280
280
|
{ if: { anyOf: [requiredObject("async"), requiredObject("model"), requiredObject("cwd"), requiredObject("output"), requiredObject("acceptance")] }, then: { properties: { action: { enum: ["continue", "answer"] } } } },
|
|
281
281
|
{ if: requiredObject("agent"), then: { properties: { action: { enum: ["list", "search", "continue", "answer"] } } } },
|
|
282
|
-
{ if: requiredObject("offset"), then: { properties: { action: { enum: ["list"] } } } },
|
|
283
|
-
{ if: requiredObject("limit"), then: { properties: { action: { enum: ["list", "history", "search"] } } } },
|
|
282
|
+
{ if: requiredObject("offset"), then: { properties: { action: { enum: ["list", "questions"] } } } },
|
|
283
|
+
{ if: requiredObject("limit"), then: { properties: { action: { enum: ["list", "questions", "history", "search"] } } } },
|
|
284
284
|
{ if: requiredObject("cursor"), then: { properties: { action: { enum: ["list", "history", "search"] } } } },
|
|
285
285
|
{ if: requiredObject("sort"), then: { properties: { action: { enum: ["list", "search"] } } } },
|
|
286
286
|
{ not: { anyOf: [requiredObject("cursor", "offset"), requiredObject("cursor", "before")] } },
|
|
@@ -313,9 +313,9 @@ export const SubagentParams = Type.Object({
|
|
|
313
313
|
runId: Type.Optional(Type.String({
|
|
314
314
|
description: "Target run ID; prefer id. Defaults to the most recently active controllable run for interrupt/extend/nudge."
|
|
315
315
|
})),
|
|
316
|
-
questionId:
|
|
316
|
+
questionId: AgentRunsValidationParams.properties.questionId,
|
|
317
317
|
decision: Type.Optional(Type.Enum(["accepted", "needs_changes"], { type: "string", description: "Parent-only review decision; not sent to the child. Put actionable instructions in resume/nudge. Does not launch work or change runtime acceptance." })),
|
|
318
|
-
offset: Type.Optional(Type.Integer({ minimum: 0, description: "Status list offset." })),
|
|
318
|
+
offset: Type.Optional(Type.Integer({ minimum: 0, description: "Status list/questions offset." })),
|
|
319
319
|
limit: AgentRunsValidationParams.properties.limit,
|
|
320
320
|
...HistoryQueryFields,
|
|
321
321
|
full: Type.Optional(Type.Boolean({ description: "Exact status only: include the full task and saved launch configuration; default is concise." })),
|
|
@@ -390,8 +390,8 @@ export const SubagentParams = Type.Object({
|
|
|
390
390
|
{ if: requiredObject("decision"), then: { ...requiredObject("action"), properties: { action: { enum: ["review"] } } } },
|
|
391
391
|
{ if: { ...requiredObject("action"), properties: { action: { enum: ["history"] } } }, then: { anyOf: [requiredObject("id"), requiredObject("runId")] } },
|
|
392
392
|
{ if: { ...requiredObject("action"), properties: { action: { enum: ["search"] } } }, then: requiredObject("query") },
|
|
393
|
-
{ if: requiredObject("offset"), then: { ...requiredObject("action"), properties: { action: { enum: ["status"] } } } },
|
|
394
|
-
{ if: requiredObject("limit"), then: { ...requiredObject("action"), properties: { action: { enum: ["status", "history", "search"] } } } },
|
|
393
|
+
{ if: requiredObject("offset"), then: { ...requiredObject("action"), properties: { action: { enum: ["status", "questions"] } } } },
|
|
394
|
+
{ if: requiredObject("limit"), then: { ...requiredObject("action"), properties: { action: { enum: ["status", "questions", "history", "search"] } } } },
|
|
395
395
|
{ if: requiredObject("cursor"), then: { ...requiredObject("action"), properties: { action: { enum: ["status", "history", "search"] } } } },
|
|
396
396
|
{ if: requiredObject("sort"), then: { ...requiredObject("action"), properties: { action: { enum: ["status", "search"] } } } },
|
|
397
397
|
{ if: { allOf: [{ ...requiredObject("action"), properties: { action: { enum: ["search"] } } }, requiredObject("index")] }, then: { anyOf: [requiredObject("id"), requiredObject("runId")] } },
|
package/dist/history/index.js
CHANGED
|
@@ -114,6 +114,16 @@ export class SubagentHistoryIndex {
|
|
|
114
114
|
}
|
|
115
115
|
await this.ready;
|
|
116
116
|
}
|
|
117
|
+
/** Arms an ordinary deadline only in the continuation that saw the current admission and worker settle, never behind a replacement admission. */
|
|
118
|
+
async admitted(method, input, signal, timeout) {
|
|
119
|
+
await this.ensure();
|
|
120
|
+
const ready = this.ready, child = this.process;
|
|
121
|
+
await ready;
|
|
122
|
+
// setOwner() or a restart may have replaced readiness during either await; wait for the replacement.
|
|
123
|
+
if (!child || ready !== this.ready || child !== this.process)
|
|
124
|
+
return this.admitted(method, input, signal, timeout);
|
|
125
|
+
return this.send(method, input, signal, timeout);
|
|
126
|
+
}
|
|
117
127
|
async setOwner(input) {
|
|
118
128
|
if (this.closed)
|
|
119
129
|
throw new HistoryIndexError("CLOSED", "History index is closed.");
|
|
@@ -138,21 +148,17 @@ export class SubagentHistoryIndex {
|
|
|
138
148
|
if (run.ownerSessionId !== this.owner.ownerSessionId || foreground && foreground.runId !== run.runId)
|
|
139
149
|
throw new HistoryIndexError("OWNERSHIP", "Run does not belong to the current owner.");
|
|
140
150
|
const copy = structuredClone(run), fg = foreground && structuredClone(foreground);
|
|
141
|
-
await this.
|
|
151
|
+
await this.admitted("updateRun", { run: copy, foreground: fg });
|
|
142
152
|
this.owner.runs = [...this.owner.runs.filter((entry) => entry.runId !== run.runId), copy];
|
|
143
153
|
if (fg)
|
|
144
154
|
this.owner.foregroundRuns = [...(this.owner.foregroundRuns ?? []).filter((entry) => entry.runId !== run.runId), fg];
|
|
145
155
|
}
|
|
146
156
|
async query(method, input) {
|
|
147
|
-
await this.ensure();
|
|
148
157
|
const { signal, ...wire } = input;
|
|
149
|
-
return this.
|
|
158
|
+
return this.admitted(method, wire, signal);
|
|
150
159
|
}
|
|
151
160
|
/** Tool discovery waits for run metadata only, not transcript backfill. */
|
|
152
|
-
|
|
153
|
-
await this.ensure();
|
|
154
|
-
return this.send("needsControls", undefined, undefined, 15_000);
|
|
155
|
-
}
|
|
161
|
+
needsControls() { return this.admitted("needsControls", undefined, undefined, 15_000); }
|
|
156
162
|
listRuns(options = {}) { return this.query("listRuns", options); }
|
|
157
163
|
historyPage(input) { return this.query("historyPage", input); }
|
|
158
164
|
search(input) { return this.query("search", input); }
|
|
@@ -166,8 +172,7 @@ export class SubagentHistoryIndex {
|
|
|
166
172
|
async refresh(runId, options = {}) {
|
|
167
173
|
if (!this.closed)
|
|
168
174
|
this.unavailable = undefined;
|
|
169
|
-
|
|
170
|
-
return this.send("refresh", { runId }, options.signal, 120_000);
|
|
175
|
+
return this.admitted("refresh", { runId }, options.signal, 120_000);
|
|
171
176
|
}
|
|
172
177
|
onChanged(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); }
|
|
173
178
|
/** Hard cancellation affects this instance's outstanding work, not native sessions or other indexers. */
|
package/dist/history/ingest.js
CHANGED
|
@@ -232,8 +232,13 @@ export class SourceIngest {
|
|
|
232
232
|
fd;
|
|
233
233
|
initial;
|
|
234
234
|
source;
|
|
235
|
-
prefix = createHash("sha256");
|
|
236
235
|
recordDigest = createHash("sha256");
|
|
236
|
+
/** prefix_digest is "start:sha256" of the last indexed line; earlier releases hashed the whole prefix. */
|
|
237
|
+
legacy = false;
|
|
238
|
+
expected = null;
|
|
239
|
+
verified = createHash("sha256");
|
|
240
|
+
line = createHash("sha256");
|
|
241
|
+
lineStart = 0;
|
|
237
242
|
verifyAt = 0;
|
|
238
243
|
position;
|
|
239
244
|
frames;
|
|
@@ -274,6 +279,11 @@ export class SourceIngest {
|
|
|
274
279
|
this.headerSeen = source.cursor > 0;
|
|
275
280
|
store.clearUnpublished(source.id);
|
|
276
281
|
store.run("DELETE FROM pending_text");
|
|
282
|
+
// Appends re-hash only the last indexed line, so live sources cost their new bytes, not their size.
|
|
283
|
+
const anchor = /^(\d+):([0-9a-f]{64})$/.exec(source.prefix_digest ?? "");
|
|
284
|
+
this.legacy = !anchor;
|
|
285
|
+
this.expected = anchor ? anchor[2] : source.prefix_digest;
|
|
286
|
+
this.verifyAt = this.lineStart = anchor ? Number(anchor[1]) : 0;
|
|
277
287
|
this.phase = !force && source.stamp === stamp(this.initial) ? "done" : source.cursor ? "verify" : "read";
|
|
278
288
|
if (this.phase !== "done")
|
|
279
289
|
store.run("UPDATE sources SET state='indexing',error=NULL WHERE id=?", source.id);
|
|
@@ -294,7 +304,7 @@ export class SourceIngest {
|
|
|
294
304
|
if (!current || current.generation !== this.source.generation || current.cursor !== this.source.cursor)
|
|
295
305
|
throw new HistoryIndexError("SOURCE_CHANGED", "Another writer advanced this source; reconcile before replaying.");
|
|
296
306
|
work?.();
|
|
297
|
-
this.store.run("UPDATE sources SET cursor=?,prefix_digest=?,identity=? WHERE id=?", end, this.
|
|
307
|
+
this.store.run("UPDATE sources SET cursor=?,prefix_digest=?,identity=? WHERE id=?", end, `${this.source.cursor}:${this.recordDigest.copy().digest("hex")}`, identity(this.initial), this.id);
|
|
298
308
|
this.store.bump();
|
|
299
309
|
});
|
|
300
310
|
this.source.cursor = end;
|
|
@@ -392,13 +402,25 @@ export class SourceIngest {
|
|
|
392
402
|
if (count !== bytes.length)
|
|
393
403
|
throw new Error("Journal truncated during prefix verification.");
|
|
394
404
|
this.store.operations.sourceBytesRead += count;
|
|
395
|
-
this.
|
|
405
|
+
this.verified.update(bytes);
|
|
406
|
+
if (this.legacy) {
|
|
407
|
+
// A whole-prefix check also finds the last indexed line, which anchors every later check.
|
|
408
|
+
const before = this.source.cursor - 2 - this.verifyAt, lf = before < 0 ? -1 : bytes.lastIndexOf(10, before);
|
|
409
|
+
if (lf < 0)
|
|
410
|
+
this.line.update(bytes);
|
|
411
|
+
else {
|
|
412
|
+
this.lineStart = this.verifyAt + lf + 1;
|
|
413
|
+
this.line = createHash("sha256").update(bytes.subarray(lf + 1));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
396
416
|
this.verifyAt += count;
|
|
397
417
|
if (this.verifyAt === this.source.cursor) {
|
|
398
|
-
if (this.
|
|
418
|
+
if (this.verified.digest("hex") !== this.expected) {
|
|
399
419
|
this.store.resetSource(this.id);
|
|
400
420
|
throw new HistoryIndexError("SOURCE_CHANGED", "Journal prefix changed; a new physical generation was scheduled.");
|
|
401
421
|
}
|
|
422
|
+
if (this.legacy)
|
|
423
|
+
this.store.run("UPDATE sources SET prefix_digest=? WHERE id=?", `${this.lineStart}:${this.line.digest("hex")}`, this.id);
|
|
402
424
|
this.phase = "read";
|
|
403
425
|
}
|
|
404
426
|
return false;
|
|
@@ -418,7 +440,6 @@ export class SourceIngest {
|
|
|
418
440
|
const newline = this.buffer.indexOf(10);
|
|
419
441
|
const bytes = newline < 0 ? this.buffer : this.buffer.subarray(0, newline + 1);
|
|
420
442
|
this.recordDigest.update(bytes);
|
|
421
|
-
this.prefix.update(bytes);
|
|
422
443
|
this.frames.write(bytes);
|
|
423
444
|
this.text.flush(this.store);
|
|
424
445
|
this.buffer = this.buffer.subarray(bytes.length);
|
package/dist/history/worker.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { ownedRunView } from "../runs/shared/run-records.js";
|
|
4
4
|
import { acceptanceHumanAction } from "../runs/shared/acceptance-evaluation.js";
|
|
5
|
-
import { getRunMetadataDir, listRunQuestions } from "../runs/shared/supervisor-questions.js";
|
|
5
|
+
import { getRunMetadataDir, listRunQuestions, summarizeSupervisorQuestion } from "../runs/shared/supervisor-questions.js";
|
|
6
6
|
import { HistoryIndexError } from "./types.js";
|
|
7
7
|
import { exactTextDigest, HistoryStore, safeText } from "./store.js";
|
|
8
8
|
import { readCanonicalOutput, readSavedOutput } from "./canonical-result.js";
|
|
@@ -25,8 +25,12 @@ const controlQueries = [];
|
|
|
25
25
|
function replyControls(id) {
|
|
26
26
|
send({ id, value: Boolean(store.get("SELECT id FROM runs WHERE attention<4 OR state='live' LIMIT 1")) });
|
|
27
27
|
}
|
|
28
|
-
function send(response) {
|
|
29
|
-
process.
|
|
28
|
+
function send(response) {
|
|
29
|
+
// The parent can disconnect after process.connected was read; then this and later replies are undeliverable, so stop as on disconnect.
|
|
30
|
+
if (process.connected)
|
|
31
|
+
process.send(response, undefined, undefined, (error) => { if (error)
|
|
32
|
+
shutdown(); });
|
|
33
|
+
}
|
|
30
34
|
function changed() { send({ changed: true }); }
|
|
31
35
|
function errorReply(id, error) {
|
|
32
36
|
const code = error instanceof HistoryIndexError ? error.code : "UNAVAILABLE";
|
|
@@ -41,10 +45,10 @@ function compactView(view) {
|
|
|
41
45
|
return {
|
|
42
46
|
...view, task: safeText(view.task, 2048), error: view.error && safeText(view.error), diagnosis: view.diagnosis && safeText(view.diagnosis), recoveryError: view.recoveryError && safeText(view.recoveryError),
|
|
43
47
|
review: view.review && { ...view.review, message: view.review.message && safeText(view.review.message) }, continuations: [],
|
|
44
|
-
questions: view.questions?.map((question) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
questions: view.questions?.map((question) => {
|
|
49
|
+
const summary = summarizeSupervisorQuestion(question);
|
|
50
|
+
return { ...summary, message: safeText(question.message, 2048), answer: summary.answer && { ...summary.answer, message: safeText(question.answer?.message, 2048) } };
|
|
51
|
+
}),
|
|
48
52
|
children: view.children.map((child) => ({
|
|
49
53
|
agent: child.agent, index: child.index, workflowNodeId: child.workflowNodeId, sessionFile: child.sessionFile, task: child.task && safeText(child.task), label: child.label && safeText(child.label, 256),
|
|
50
54
|
state: child.state, configuration: child.configuration, missingSession: child.missingSession, identityUnavailable: child.identityUnavailable, modelSelection: child.modelSelection,
|
|
@@ -458,4 +462,5 @@ async function handle(request) {
|
|
|
458
462
|
}
|
|
459
463
|
}
|
|
460
464
|
process.on("message", (request) => { void handle(request); });
|
|
461
|
-
|
|
465
|
+
function shutdown() { closed = true; clearInterval(timer); clear(); process.exit(0); }
|
|
466
|
+
process.on("disconnect", shutdown);
|