pi-ptc-subagents 0.1.2 → 1.0.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/CHANGELOG.md +56 -0
- package/README.md +34 -4
- package/dist/index.d.ts +556 -2
- package/dist/index.js +41 -3128
- package/dist/protocol-CfOgxz3u.js +2 -0
- package/dist/worker.js +3 -758
- package/package.json +3 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/protocol-DUnAP1Ro.js +0 -117
- package/dist/protocol-DUnAP1Ro.js.map +0 -1
- package/dist/worker.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,466 @@
|
|
|
1
|
-
import { ExtensionAPI, Skill } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { ExtensionAPI, Skill, truncateTail } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import "@earendil-works/pi-ai";
|
|
2
3
|
import { MessagePort, Worker } from "node:worker_threads";
|
|
4
|
+
//#region src/runtime/ulid.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Lexically-sortable identifier (26-char Crockford base32). Brand-only: the runtime value is a
|
|
7
|
+
* plain `string`, the brand is a compile-time distinction that keeps a taskId from being
|
|
8
|
+
* passed into a cursor slot.
|
|
9
|
+
*/
|
|
10
|
+
type ULID = string & {
|
|
11
|
+
readonly __brand: "ULID";
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/runtime/child-process-lifecycle.d.ts
|
|
15
|
+
/**
|
|
16
|
+
* One line of the child's stdout, parsed as JSON. The child emits the events described
|
|
17
|
+
* in examples/extensions/subagent/index.ts (message_end, tool_result_end, error).
|
|
18
|
+
* Anything unparseable is dropped: the line is one event the host cannot interpret,
|
|
19
|
+
* and the run continues.
|
|
20
|
+
*/
|
|
21
|
+
interface ParsedAgentEvent {
|
|
22
|
+
type: string;
|
|
23
|
+
message?: {
|
|
24
|
+
role?: string;
|
|
25
|
+
content?: ReadonlyArray<{
|
|
26
|
+
type?: string;
|
|
27
|
+
text?: string;
|
|
28
|
+
}>;
|
|
29
|
+
usage?: {
|
|
30
|
+
input?: number;
|
|
31
|
+
output?: number;
|
|
32
|
+
cacheRead?: number;
|
|
33
|
+
cacheWrite?: number;
|
|
34
|
+
cost?: {
|
|
35
|
+
total?: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
model?: string;
|
|
39
|
+
stopReason?: string;
|
|
40
|
+
errorMessage?: string;
|
|
41
|
+
};
|
|
42
|
+
message_text?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Opaque per-handle state owned by the adapter that produced it. The dispatch code
|
|
46
|
+
* never reads `opaque` directly; it only hands the handle back to adapter methods
|
|
47
|
+
* (`kill`, `events`, `exit`, `stderr`).
|
|
48
|
+
*/
|
|
49
|
+
interface ChildHandle {
|
|
50
|
+
readonly id: ULID;
|
|
51
|
+
readonly opaque: unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Spawn options shared by both adapters. `promptFile` / `sessionDir` / `sessionId` /
|
|
55
|
+
* `sessionName` are the R1 fields the background dispatch consumes (ADR-0022 §1/R1). The
|
|
56
|
+
* foreground `pi.dispatch` path passes only `cwd` / `env` / `signal` / `promptFile`.
|
|
57
|
+
*/
|
|
58
|
+
interface ChildSpawnOptions {
|
|
59
|
+
cwd: string;
|
|
60
|
+
env?: NodeJS.ProcessEnv;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
promptFile: string;
|
|
63
|
+
sessionDir?: string;
|
|
64
|
+
sessionId?: string;
|
|
65
|
+
sessionName?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Process-exit shape the `close` event on `node:child_process.ChildProcess` produces. */
|
|
68
|
+
interface ChildExitValue {
|
|
69
|
+
code: number | null;
|
|
70
|
+
signal: NodeJS.Signals | null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The lifecycle contract. Implementations:
|
|
74
|
+
* - `RealChildProcessLifecycle` - wraps `node:child_process.spawn` (production).
|
|
75
|
+
* - `MockChildProcessLifecycle` - in-memory queue + exit signal (tests).
|
|
76
|
+
*/
|
|
77
|
+
interface ChildProcessLifecycle {
|
|
78
|
+
/**
|
|
79
|
+
* Launch one child process. The first element of `argv` is the executable; the rest
|
|
80
|
+
* are forwarded as command-line arguments. Returns an opaque handle the caller uses
|
|
81
|
+
* to talk to the same child via the other methods.
|
|
82
|
+
*
|
|
83
|
+
* `opts.env` is the full environment for the child; the caller is responsible for
|
|
84
|
+
* merging anything the child needs (e.g. `PI_PTC_DEPTH`) on top of `process.env`.
|
|
85
|
+
*/
|
|
86
|
+
spawn(argv: readonly string[], opts: ChildSpawnOptions): ChildHandle;
|
|
87
|
+
/**
|
|
88
|
+
* Send `signal` to the child iff it is still attached. Absorbs "process already gone"
|
|
89
|
+
* throws so callers can fire-and-forget on cancel. Mirrors `safeKill` below.
|
|
90
|
+
*/
|
|
91
|
+
kill(handle: ChildHandle, signal: "SIGTERM" | "SIGKILL"): void;
|
|
92
|
+
/**
|
|
93
|
+
* Async iterator over the child's stdout events. Yields one `ParsedAgentEvent` per
|
|
94
|
+
* JSONL line. The iterator ends after the child closes; callers should `await` it
|
|
95
|
+
* fully before reading `exit()`.
|
|
96
|
+
*/
|
|
97
|
+
events(handle: ChildHandle): AsyncIterable<ParsedAgentEvent>;
|
|
98
|
+
/**
|
|
99
|
+
* Resolve when the child closes, with `(code, signal)` matching `node:child_process`'s
|
|
100
|
+
* `close` event. A clean exit is `(0, null)`; a signal kill is `(null, <signal>)`;
|
|
101
|
+
* a spawn failure surfaces as `(null, null)` once the error event has propagated.
|
|
102
|
+
*/
|
|
103
|
+
exit(handle: ChildHandle): Promise<ChildExitValue>;
|
|
104
|
+
/**
|
|
105
|
+
* Resolve with the accumulated stderr text once the child has closed. Returns `""`
|
|
106
|
+
* when the child wrote nothing. Spawn failures append a `[spawn-error] ...` marker
|
|
107
|
+
* (same convention as the pre-BG-03 dispatch code) so callers can distinguish a
|
|
108
|
+
* failed spawn from a clean non-zero exit.
|
|
109
|
+
*/
|
|
110
|
+
stderr(handle: ChildHandle): Promise<string>;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/runtime/task-storage.d.ts
|
|
114
|
+
/**
|
|
115
|
+
* The 6-state TaskRecord status (ADR-0022 §2). `queued` is deliberately absent in v1
|
|
116
|
+
* (spawn-or-reject, no in-task queue; cap=8 rejects above the limit immediately).
|
|
117
|
+
*/
|
|
118
|
+
type TaskStatus = "running" | "stopping" | "succeeded" | "failed" | "canceled" | "lost";
|
|
119
|
+
/** Origin of the spawn — `ptc-program` from a PTC run, `ptc-batch` from a future batch entry. */
|
|
120
|
+
interface TaskSpawnSource {
|
|
121
|
+
kind: "ptc-program" | "ptc-batch";
|
|
122
|
+
/** Caller id (PTC run id for `ptc-program`; batch id for `ptc-batch`). */
|
|
123
|
+
callerId: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Session-level row tracking the lifecycle of one background child. 21 fields, exact shape from
|
|
127
|
+
* ADR-0022 §3. Optional fields are absent on records still `running` (e.g. `finishedAt` /
|
|
128
|
+
* `exitCode` / `outputRef` are written at transition time, not at spawn).
|
|
129
|
+
*/
|
|
130
|
+
interface TaskRecord {
|
|
131
|
+
id: ULID;
|
|
132
|
+
label: string;
|
|
133
|
+
agentName: string;
|
|
134
|
+
/** 0 = parent's direct, 1 = grandchild, etc. (ADR-0016 recursive section). */
|
|
135
|
+
depth: number;
|
|
136
|
+
status: TaskStatus;
|
|
137
|
+
/** ms epoch. */
|
|
138
|
+
createdAt: number;
|
|
139
|
+
startedAt: number;
|
|
140
|
+
finishedAt?: number;
|
|
141
|
+
durationMs?: number;
|
|
142
|
+
/** Last state transition ms epoch; used for cursor ordering of events. */
|
|
143
|
+
transitionAt: number;
|
|
144
|
+
/** `<sessionDir>/tasks/<id>/output.log` once the child has flushed any output. */
|
|
145
|
+
outputRef?: string;
|
|
146
|
+
outputBytes?: number;
|
|
147
|
+
/** ≤ 2 KB inline preview (Map+preview, ADR-0022 §3). */
|
|
148
|
+
outputPreview?: string;
|
|
149
|
+
stopReason?: string;
|
|
150
|
+
errorMessage?: string;
|
|
151
|
+
exitCode?: number;
|
|
152
|
+
spawnSource: TaskSpawnSource;
|
|
153
|
+
parentTaskId?: ULID;
|
|
154
|
+
/** `<sessionDir>/tasks/<id>.pi-*` session file (R1). */
|
|
155
|
+
sessionFile?: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Per-subscriber cursor for one TaskRecord (ADR-0022 §5). Cursor is per-subscriber (not per-task)
|
|
159
|
+
* because fork semantics differ from single-session semantics: a forked branch observes events
|
|
160
|
+
* from `max(parent, child)` cursor onwards, not from task creation.
|
|
161
|
+
*/
|
|
162
|
+
interface Subscription {
|
|
163
|
+
subscriberId: ULID;
|
|
164
|
+
taskId: ULID;
|
|
165
|
+
/** Monotonic ULID; advances on every event delivered. */
|
|
166
|
+
cursor: ULID;
|
|
167
|
+
status: "active" | "closed";
|
|
168
|
+
/** ms epoch. */
|
|
169
|
+
createdAt: number;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Append-only event in a subscription buffer. The `eventId` IS the cursor: monotonic ULID, the
|
|
173
|
+
* `loadEvents(since)` filter slices `[strictly-after since, ...]`. Schema fields beyond the
|
|
174
|
+
* cursor carry enough state for the renderer to emit `<bg-task-notification>` (ADR-0022 §7)
|
|
175
|
+
* without re-reading the TaskRecord.
|
|
176
|
+
*/
|
|
177
|
+
interface TaskEvent {
|
|
178
|
+
/** Cursor / event id; the loadEvents(since) filter. */
|
|
179
|
+
eventId: ULID;
|
|
180
|
+
subscriptionId: ULID;
|
|
181
|
+
taskId: ULID;
|
|
182
|
+
/**
|
|
183
|
+
* Emit key per ADR-0022 §2 + §8, e.g. `task:<id>:running`, `task:<id>:->canceled`,
|
|
184
|
+
* `task:<id>:->lost`. Storage layer treats this as an opaque string.
|
|
185
|
+
*/
|
|
186
|
+
type: string;
|
|
187
|
+
status: TaskStatus;
|
|
188
|
+
/** ms epoch; matches `TaskRecord.transitionAt` at the moment of emission. */
|
|
189
|
+
transitionAtMs: number;
|
|
190
|
+
outputBytes?: number;
|
|
191
|
+
/** ≤ 2 KB inline preview (only present when `outputBytes <= 2048`). */
|
|
192
|
+
outputPreview?: string;
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/runtime/task-registry.d.ts
|
|
196
|
+
/** Minimal structured logger seam (ADR-0022 implementation spec §12). */
|
|
197
|
+
interface RegistryLogger {
|
|
198
|
+
info(msg: string): void;
|
|
199
|
+
warn(msg: string): void;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Thin, frozen, spawn-time projection of a TaskRecord (ADR-0022 §4). The program carries
|
|
203
|
+
* this; the registry stores the TaskRecord. The handle is *not* updated on transition.
|
|
204
|
+
*/
|
|
205
|
+
interface DispatchHandle {
|
|
206
|
+
taskId: ULID;
|
|
207
|
+
label: string;
|
|
208
|
+
status: "running";
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Per-call dependencies for one registry command. `callerId` is also the subscriber id
|
|
212
|
+
* (ADR-0022 §5, "Subscriber == owner"); `clock` must be the injected time source.
|
|
213
|
+
*/
|
|
214
|
+
interface TransitionContext {
|
|
215
|
+
clock: () => number;
|
|
216
|
+
logger?: RegistryLogger;
|
|
217
|
+
callerId: string;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The three `lost` reasons ADR-0022 §8 keeps distinct in the TaskRecord's
|
|
221
|
+
* `errorMessage` field for auditability.
|
|
222
|
+
*/
|
|
223
|
+
type LostReason = "session_ended_while_running" | "user_killed_via_esc" | "lost_on_session_restart";
|
|
224
|
+
/** All state mutations the TaskRegistry accepts (single-terminal-writer invariant, this file). */
|
|
225
|
+
type TaskCommand = {
|
|
226
|
+
kind: "spawn";
|
|
227
|
+
parentTaskId?: ULID;
|
|
228
|
+
handle: DispatchHandle;
|
|
229
|
+
record: Omit<TaskRecord, "id" | "status" | "createdAt" | "transitionAt">;
|
|
230
|
+
} | {
|
|
231
|
+
kind: "transition";
|
|
232
|
+
taskId: ULID;
|
|
233
|
+
to: TaskStatus;
|
|
234
|
+
reason?: string;
|
|
235
|
+
stopReason?: string;
|
|
236
|
+
errorMessage?: string;
|
|
237
|
+
exitCode?: number;
|
|
238
|
+
/**
|
|
239
|
+
* ADR-0022 §3: the child's captured-output projection written on the terminal
|
|
240
|
+
* transition. The BG-04 pump drains stdout, persists it through `OutputStorage`, and
|
|
241
|
+
* passes these three so `ptc_task_output` can find it.
|
|
242
|
+
*/
|
|
243
|
+
outputRef?: string;
|
|
244
|
+
outputBytes?: number;
|
|
245
|
+
outputPreview?: string;
|
|
246
|
+
} | {
|
|
247
|
+
kind: "stop";
|
|
248
|
+
taskId: ULID;
|
|
249
|
+
reason: string;
|
|
250
|
+
} | {
|
|
251
|
+
kind: "reconcile-lost";
|
|
252
|
+
taskId: ULID;
|
|
253
|
+
reason: LostReason;
|
|
254
|
+
} | {
|
|
255
|
+
kind: "resolve-exit";
|
|
256
|
+
taskId: ULID;
|
|
257
|
+
exitCode: number;
|
|
258
|
+
outputRef?: string;
|
|
259
|
+
outputBytes?: number;
|
|
260
|
+
outputPreview?: string;
|
|
261
|
+
};
|
|
262
|
+
/** Outcome of one successful command: the persisted record, emitted events, and cursor. */
|
|
263
|
+
interface TransitionResult {
|
|
264
|
+
record: TaskRecord;
|
|
265
|
+
events: TaskEvent[];
|
|
266
|
+
/** The event id of the last emitted event; equal to the subscription's new cursor. */
|
|
267
|
+
cursor: ULID;
|
|
268
|
+
/**
|
|
269
|
+
* The status the record held *before* this command applied. Absent on `spawn` (there is no
|
|
270
|
+
* prior state); present on every transition/stop/resolve so a caller can report the source
|
|
271
|
+
* state without a second, racing read (ADR-0022 §8 late-arrival stop).
|
|
272
|
+
*/
|
|
273
|
+
fromStatus?: TaskStatus;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* In-process transition observer (issue #68 §1). Fired synchronously by
|
|
277
|
+
* {@link TaskRegistry.transition} **after** the new record and its event have been persisted,
|
|
278
|
+
* with the post-transition record and the emitted event. Observers are best-effort: a throwing
|
|
279
|
+
* observer is warned about and never corrupts the transition. Registration returns an
|
|
280
|
+
* idempotent unsubscribe.
|
|
281
|
+
*/
|
|
282
|
+
type TaskTransitionObserver = (record: TaskRecord, event: TaskEvent) => void;
|
|
283
|
+
/** Read-side filter (ADR-0022 §3). `limit` defaults to 100; `orderBy` defaults to desc. */
|
|
284
|
+
interface TaskQuery {
|
|
285
|
+
status?: TaskStatus[];
|
|
286
|
+
label?: string;
|
|
287
|
+
limit?: number;
|
|
288
|
+
orderBy?: "createdAt-asc" | "createdAt-desc";
|
|
289
|
+
}
|
|
290
|
+
/** The five-method lifecycle surface from the BG-02 brief. */
|
|
291
|
+
interface TaskRegistry {
|
|
292
|
+
transition(command: TaskCommand, ctx: TransitionContext): Promise<TransitionResult>;
|
|
293
|
+
query(view: TaskQuery): Promise<TaskRecord[]>;
|
|
294
|
+
/**
|
|
295
|
+
* Load one TaskRecord by id; `null` when the id is unknown. The O(1) complement of `query`,
|
|
296
|
+
* used by the background pump to see whether a stop was requested before it writes the
|
|
297
|
+
* terminal transition (ADR-0022 §8).
|
|
298
|
+
*/
|
|
299
|
+
get(taskId: ULID): Promise<TaskRecord | null>;
|
|
300
|
+
/**
|
|
301
|
+
* Subscribe to every successfully persisted state write (spawn included). Returns an
|
|
302
|
+
* idempotent unsubscribe. Observers run synchronously, in-process; see
|
|
303
|
+
* {@link TaskTransitionObserver} for the best-effort contract and single-writer ordering.
|
|
304
|
+
*/
|
|
305
|
+
onTransition(observer: TaskTransitionObserver): () => void;
|
|
306
|
+
reconcileLostTasks(): Promise<TaskRecord[]>;
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/runtime/output-storage.d.ts
|
|
310
|
+
/**
|
|
311
|
+
* The persistence seam for one task's captured output.
|
|
312
|
+
*
|
|
313
|
+
* `readOutput` returning `null` and `writeOutput` accepting any string are the whole contract;
|
|
314
|
+
* ordering, framing and truncation are the caller's job.
|
|
315
|
+
*/
|
|
316
|
+
interface OutputStorage {
|
|
317
|
+
/** Load the task's raw output; `null` when nothing has been written for that id. */
|
|
318
|
+
readOutput(taskId: ULID): Promise<string | null>;
|
|
319
|
+
/** Persist (insert-or-replace) the task's raw output. */
|
|
320
|
+
writeOutput(taskId: ULID, content: string): Promise<void>;
|
|
321
|
+
/**
|
|
322
|
+
* The stable path/ref the dispatcher records on `TaskRecord.outputRef`. It exists even before
|
|
323
|
+
* the first `writeOutput` (the ref is the record's address, not the file's existence).
|
|
324
|
+
*/
|
|
325
|
+
outputRef(taskId: ULID): string;
|
|
326
|
+
}
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/runtime/dispatch.d.ts
|
|
329
|
+
/**
|
|
330
|
+
* Per-run in-flight `pi.dispatch` counter (ADR-0016 §2 + ADR-0022 §9). The dispatcher's
|
|
331
|
+
* foreground gate owns the canonical counter today; this class is the small exported seam
|
|
332
|
+
* ADR-0022 §9 asks for so the background branch can hold a slot for a child's whole
|
|
333
|
+
* lifetime and release it only on the terminal transition — a background task keeps
|
|
334
|
+
* counting against `dispatchConcurrency` after `dispatch()` has already returned. The
|
|
335
|
+
* dispatcher can adopt this type without a behaviour change.
|
|
336
|
+
*/
|
|
337
|
+
declare class DispatchSlotCounter {
|
|
338
|
+
#private;
|
|
339
|
+
readonly limit: number;
|
|
340
|
+
constructor(limit: number);
|
|
341
|
+
/** Number of slots currently held. */
|
|
342
|
+
get active(): number;
|
|
343
|
+
/** Reserve one in-flight slot (optionally keyed by a per-task holder token); `false` means the cap is reached (hard reject, no queue). */
|
|
344
|
+
tryAcquire(holder?: string): boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Release one slot; per-token idempotent (a late release for the same task is a no-op) and
|
|
347
|
+
* never negative. An anonymous release only frees an anonymous reservation.
|
|
348
|
+
*/
|
|
349
|
+
release(holder?: string): void;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Injected dependencies for the background branch. The foreground path ignores this object
|
|
353
|
+
* entirely (it uses the module-level {@link DISPATCH_LIFECYCLE}); every field is optional so
|
|
354
|
+
* existing zero-argument call sites keep working. Tests pass an in-memory registry and a
|
|
355
|
+
* mock lifecycle so no real `pi` process is ever spawned.
|
|
356
|
+
*/
|
|
357
|
+
interface DispatchDeps {
|
|
358
|
+
/** Session-level TaskRegistry (ADR-0022 §3). Defaults to a lazy in-memory registry. */
|
|
359
|
+
taskRegistry?: TaskRegistry;
|
|
360
|
+
/** Child lifecycle for the background spawn. Defaults to the shared real adapter. */
|
|
361
|
+
lifecycle?: ChildProcessLifecycle;
|
|
362
|
+
/** Per-run in-flight counter shared with the dispatcher's foreground gate (ADR-0022 §9). */
|
|
363
|
+
slots?: DispatchSlotCounter;
|
|
364
|
+
/** Time source for TaskRecord stamps. Defaults to `Date.now`. */
|
|
365
|
+
clock?: () => number;
|
|
366
|
+
/** Logger for the background pump's failure path. Defaults to a `console.warn` logger. */
|
|
367
|
+
logger?: RegistryLogger;
|
|
368
|
+
/**
|
|
369
|
+
* ADR-0022 §3: where the pump persists a task's drained stdout. When present, the terminal
|
|
370
|
+
* transition records `outputRef` / `outputBytes` / `outputPreview` so `ptc_task_output`
|
|
371
|
+
* can dereference the bytes (BG-07).
|
|
372
|
+
*/
|
|
373
|
+
outputStorage?: OutputStorage;
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/runtime/notification-pipeline.d.ts
|
|
377
|
+
/** Idle-wake callback: one subscriber's pending events, delivered in cursor order. */
|
|
378
|
+
type IdleWakeHandler = (subscriberId: ULID, events: TaskEvent[]) => void;
|
|
379
|
+
/**
|
|
380
|
+
* The five-method pipeline surface (Ticket BG-05). The concrete class adds the
|
|
381
|
+
* dispatcher-facing `notifyIdle` wake trigger on top of this interface.
|
|
382
|
+
*/
|
|
383
|
+
interface NotificationPipeline {
|
|
384
|
+
/** Create (or return the existing) subscription for one (subscriberId, taskId) pair. */
|
|
385
|
+
subscribe(subscriberId: ULID, taskId: ULID, since?: ULID): Promise<Subscription>;
|
|
386
|
+
/** Read pending events strictly newer than the cursor, WITHOUT advancing the cursor. */
|
|
387
|
+
drainPending(subscriberId: ULID, taskId: ULID): Promise<TaskEvent[]>;
|
|
388
|
+
/** Advance the cursor to `untilCursor`; never moves backwards (idempotent). */
|
|
389
|
+
acknowledgeEvents(subscriberId: ULID, taskId: ULID, untilCursor: ULID): Promise<void>;
|
|
390
|
+
/** Register an idle-wake handler; handlers fire in registration order. */
|
|
391
|
+
onIdleWake(handler: IdleWakeHandler): void;
|
|
392
|
+
/** Count events strictly newer than the cursor. */
|
|
393
|
+
pendingCount(subscriberId: ULID, taskId: ULID): Promise<number>;
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/runtime/task-notification.d.ts
|
|
397
|
+
/**
|
|
398
|
+
* One drained event joined to the TaskRecord it describes. The join is required: the event
|
|
399
|
+
* lacks `label` / `agentName` / `depth` / `durationMs` / `outputRef` (see the module
|
|
400
|
+
* note above), all of which the ADR-0022 §7 child element must carry.
|
|
401
|
+
*/
|
|
402
|
+
interface TaskNotificationItem {
|
|
403
|
+
event: TaskEvent;
|
|
404
|
+
record: TaskRecord;
|
|
405
|
+
}
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/runtime/background-runtime.d.ts
|
|
408
|
+
/** Best-effort reporter for a bind failure; the index notifies the user from it. */
|
|
409
|
+
type BackgroundBindReporter = (message: string) => void;
|
|
410
|
+
/** One cursor to advance after a drained batch has actually been delivered. */
|
|
411
|
+
interface NotificationAck {
|
|
412
|
+
subscriberId: ULID;
|
|
413
|
+
taskId: ULID;
|
|
414
|
+
cursor: ULID;
|
|
415
|
+
/**
|
|
416
|
+
* The concrete session pipeline this drain READ from, carried so the ack lands on the same
|
|
417
|
+
* session even if a rebind happens between the drain and the send (P1). Without it the ack goes
|
|
418
|
+
* through the stable proxy's *current* delegate and targets a session that never knew the
|
|
419
|
+
* subscription, leaving the original cursor unadvanced and re-delivering the batch.
|
|
420
|
+
*/
|
|
421
|
+
sink: NotificationPipeline;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* One drain: the events to render plus the cursors the caller acknowledges after a successful
|
|
425
|
+
* send. The split is what lets a failed send leave the cursor unadvanced (ADR-0022 §5/§6).
|
|
426
|
+
*/
|
|
427
|
+
interface NotificationDrain {
|
|
428
|
+
items: TaskNotificationItem[];
|
|
429
|
+
acks: readonly NotificationAck[];
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* The holder. `registry` / `outputStorage` / `pipeline` are stable objects safe to capture
|
|
433
|
+
* before any session exists; `bindSession` swaps the session delegate underneath them.
|
|
434
|
+
*/
|
|
435
|
+
interface BackgroundTaskRuntime {
|
|
436
|
+
readonly registry: TaskRegistry;
|
|
437
|
+
readonly outputStorage: OutputStorage;
|
|
438
|
+
readonly pipeline: NotificationPipeline;
|
|
439
|
+
readonly slots: DispatchSlotCounter;
|
|
440
|
+
readonly lifecycle: ChildProcessLifecycle;
|
|
441
|
+
/** Stable deps the two PTC tools hand to `runPtcProgram` (ADR-0022 §9). */
|
|
442
|
+
readonly dispatchDeps: DispatchDeps;
|
|
443
|
+
/** Session clock (`Date.now` unless injected); passed to `ptc_task_stop`. */
|
|
444
|
+
readonly clock: () => number;
|
|
445
|
+
/**
|
|
446
|
+
* (Re)bind to a session: build the session storage/registry/pipeline, swap the delegates, then
|
|
447
|
+
* reconcile. Returns the records marked `lost`. A bind failure (storage construction or the
|
|
448
|
+
* reconcile sweep) is reported via `reporter` and the logger, and leaves the previous delegate
|
|
449
|
+
* usable; it never rejects and never partially swaps.
|
|
450
|
+
*/
|
|
451
|
+
bindSession(sessionDir: string | undefined, reporter?: BackgroundBindReporter): Promise<TaskRecord[]>;
|
|
452
|
+
/** Drain undelivered events for a subscriber as `{event, record}` pairs ready for the renderer. */
|
|
453
|
+
drainNotifications(subscriberId: ULID): Promise<NotificationDrain>;
|
|
454
|
+
/**
|
|
455
|
+
* Advance the subscription cursor for a drained batch. The caller invokes this ONLY after the
|
|
456
|
+
* batch's message was actually delivered (ADR-0022 §5/§6); a failed send leaves the cursor
|
|
457
|
+
* where it was so the next drain re-delivers the event.
|
|
458
|
+
*/
|
|
459
|
+
acknowledgeNotifications(acks: readonly NotificationAck[]): Promise<void>;
|
|
460
|
+
/** Mark every non-terminal task lost (given reason) and release resources. */
|
|
461
|
+
shutdown(reason: LostReason): Promise<TaskRecord[]>;
|
|
462
|
+
}
|
|
463
|
+
//#endregion
|
|
3
464
|
//#region src/mode/ptc-mode.d.ts
|
|
4
465
|
/** The two surfaces this package exposes. `/ptc on` needs at least one of them active. */
|
|
5
466
|
export declare const PTC_MODE_TOOL_NAMES: readonly string[];
|
|
@@ -344,6 +805,27 @@ interface PtcErrorShape {
|
|
|
344
805
|
message: string;
|
|
345
806
|
stack?: string;
|
|
346
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* One binding call the program made, as the dispatcher recorded it.
|
|
810
|
+
*
|
|
811
|
+
* Host-side only: this shape never crosses the wire — the existing `call` /
|
|
812
|
+
* `call-result` frames already carry every fact, and the host reconstructs the
|
|
813
|
+
* record at the seam where the facts are known (`SubCallTracker`,
|
|
814
|
+
* `src/runtime/sub-call-tracker.ts`). A snapshot reaches the renderer
|
|
815
|
+
* via `PtcToolDetails.subCalls`.
|
|
816
|
+
*/
|
|
817
|
+
type SubCallStatus = "running" | "ok" | "error" | "cancelled" | "rejected";
|
|
818
|
+
interface SubCallRecord {
|
|
819
|
+
callId: number;
|
|
820
|
+
name: string;
|
|
821
|
+
args: unknown;
|
|
822
|
+
status: SubCallStatus;
|
|
823
|
+
startMs: number;
|
|
824
|
+
endMs?: number;
|
|
825
|
+
durationMs?: number;
|
|
826
|
+
resultSummary?: string;
|
|
827
|
+
errorMessage?: string;
|
|
828
|
+
}
|
|
347
829
|
//#endregion
|
|
348
830
|
//#region src/tools/text.d.ts
|
|
349
831
|
/** Hard cap for one rendered line; longer lines are truncated with `…`. */
|
|
@@ -389,6 +871,29 @@ interface BindingContext {
|
|
|
389
871
|
depth: number;
|
|
390
872
|
/** Maximum allowed depth; passed through to `pi.dispatch` for the depth check. */
|
|
391
873
|
maxDispatchDepth: number;
|
|
874
|
+
/**
|
|
875
|
+
* ADR-0022 §5: the TaskRecord owner / subscription subscriber. The dispatcher threads the
|
|
876
|
+
* run id here so a background task's events are addressed to the spawning run.
|
|
877
|
+
*/
|
|
878
|
+
callerId?: string;
|
|
879
|
+
/**
|
|
880
|
+
* ADR-0022 R1: the session dir a background child persists into. Threaded from the
|
|
881
|
+
* dispatcher (see `RunPtcProgramOptions.sessionDir`); when no dir is available the spawn
|
|
882
|
+
* keeps the foreground no-session shape.
|
|
883
|
+
*/
|
|
884
|
+
sessionDir?: string;
|
|
885
|
+
/**
|
|
886
|
+
* ADR-0022 §3/reopen R-m12: this process's own background task id, when this run is inside a
|
|
887
|
+
* background child. Threaded from the dispatcher (see `RunPtcProgramOptions.parentTaskId`)
|
|
888
|
+
* and forwarded to `pi.dispatch` so a nested spawn records `TaskRecord.parentTaskId`.
|
|
889
|
+
*/
|
|
890
|
+
parentTaskId?: ULID;
|
|
891
|
+
/**
|
|
892
|
+
* ADR-0022 §9: per-run dispatch dependencies. The dispatcher supplies the run's shared
|
|
893
|
+
* `DispatchSlotCounter` here; a host may also pass session-level deps (registry / lifecycle /
|
|
894
|
+
* output storage) so the binding's `dispatch()` call shares them.
|
|
895
|
+
*/
|
|
896
|
+
dispatchDeps?: DispatchDeps;
|
|
392
897
|
}
|
|
393
898
|
interface Binding {
|
|
394
899
|
readonly name: string;
|
|
@@ -519,8 +1024,28 @@ interface RunPtcProgramOptions {
|
|
|
519
1024
|
* to 0.
|
|
520
1025
|
*/
|
|
521
1026
|
depth?: number;
|
|
1027
|
+
/**
|
|
1028
|
+
* ADR-0022 §3/reopen R-m12: this process's own background task id, when the run is inside a
|
|
1029
|
+
* child spawned by `pi.dispatch({ background: true })`. The entrypoint reads it from
|
|
1030
|
+
* `PI_PTC_TASK_ID` and threads it to the binding context so a nested spawn records
|
|
1031
|
+
* `TaskRecord.parentTaskId`. Absent for a top-level session.
|
|
1032
|
+
*/
|
|
1033
|
+
parentTaskId?: ULID;
|
|
522
1034
|
/** Identifier carried to the worker; generated when omitted. */
|
|
523
1035
|
runId?: string;
|
|
1036
|
+
/**
|
|
1037
|
+
* ADR-0022 R1: the session dir background children persist into, when the host has one.
|
|
1038
|
+
* Threaded to the binding context so `pi.dispatch({ background: true })` can stamp the R1
|
|
1039
|
+
* session flags; absent means the background spawn keeps the foreground no-session shape.
|
|
1040
|
+
*/
|
|
1041
|
+
sessionDir?: string;
|
|
1042
|
+
/**
|
|
1043
|
+
* ADR-0022 §3/§9: session-level dispatch dependencies (TaskRegistry / OutputStorage /
|
|
1044
|
+
* lifecycle / clock / logger). The dispatcher merges its per-run `DispatchSlotCounter`
|
|
1045
|
+
* into this bag before handing it to the `pi.dispatch` binding, so background tasks count
|
|
1046
|
+
* against this run's `dispatchConcurrency` for their whole lifetime.
|
|
1047
|
+
*/
|
|
1048
|
+
dispatchDeps?: DispatchDeps;
|
|
524
1049
|
/**
|
|
525
1050
|
* Optional worker pool (ADR-0017). Absent = spawn a fresh worker and terminate it
|
|
526
1051
|
* at run end (the original cold-start path). Present = the dispatcher acquires a
|
|
@@ -529,6 +1054,18 @@ interface RunPtcProgramOptions {
|
|
|
529
1054
|
* `kind: workerExit`.
|
|
530
1055
|
*/
|
|
531
1056
|
pool?: WorkerPool;
|
|
1057
|
+
/**
|
|
1058
|
+
* Called every time a sub-call starts or ends, so the caller can push a live partial result
|
|
1059
|
+
* and have the tree visible while the run is in flight (ADR-0021 §4). Never called after the
|
|
1060
|
+
* run settles — `finish` owns the terminal snapshot. Absent means "no live updates wanted"
|
|
1061
|
+
* (direct library use, tests that only assert the terminal outcome).
|
|
1062
|
+
*
|
|
1063
|
+
* The argument is a **thunk**, not the snapshot itself: a wide `Promise.all` produces an
|
|
1064
|
+
* event per call, and `snapshot()` copies every record. Building it eagerly would make N
|
|
1065
|
+
* sequential calls cost O(N²) copies for pushes that the caller's throttle mostly drops.
|
|
1066
|
+
* Call it only when a push is actually due.
|
|
1067
|
+
*/
|
|
1068
|
+
onSubCallChange?: (snapshot: () => readonly SubCallRecord[]) => void;
|
|
532
1069
|
}
|
|
533
1070
|
/**
|
|
534
1071
|
* One image hoisted out of a successful binding result (DSH parity — see ADR-0014,
|
|
@@ -568,6 +1105,15 @@ interface PtcRunOutcome {
|
|
|
568
1105
|
images?: PtcImage[];
|
|
569
1106
|
/** Failure details; absent on success. */
|
|
570
1107
|
error?: PtcErrorShape;
|
|
1108
|
+
/**
|
|
1109
|
+
* One record per binding call the program made (ADR-0021).
|
|
1110
|
+
*
|
|
1111
|
+
* Present only when the dispatcher tracked sub-calls for the surface in question (always,
|
|
1112
|
+
* post-ADR-0021 — the dispatcher wires `SubCallTracker` for every run). Order is host-side
|
|
1113
|
+
* dispatch order; the renderer reads it as a point-in-time snapshot. Absent for runs that settled
|
|
1114
|
+
* before the tracker was constructed (the pre-`Promise` abort and pool-acquire-failed paths).
|
|
1115
|
+
*/
|
|
1116
|
+
subCalls?: readonly SubCallRecord[];
|
|
571
1117
|
}
|
|
572
1118
|
/**
|
|
573
1119
|
* Run one PTC program in a fresh worker and resolve with its outcome.
|
|
@@ -600,7 +1146,15 @@ export declare class TurnPools {
|
|
|
600
1146
|
}
|
|
601
1147
|
//#endregion
|
|
602
1148
|
//#region src/index.d.ts
|
|
603
|
-
|
|
1149
|
+
/**
|
|
1150
|
+
* Optional seams for the extension factory. Production calls `ptcSubagents(pi)`; tests may inject
|
|
1151
|
+
* a pre-built background runtime (with a mock child lifecycle) so no real `pi` process is spawned.
|
|
1152
|
+
*/
|
|
1153
|
+
export interface PtcSubagentsOptions {
|
|
1154
|
+
/** Use this session-scoped background runtime instead of constructing one. */
|
|
1155
|
+
backgroundRuntime?: BackgroundTaskRuntime;
|
|
1156
|
+
}
|
|
1157
|
+
export default function ptcSubagents(pi: ExtensionAPI, options?: PtcSubagentsOptions): void;
|
|
604
1158
|
//#endregion
|
|
605
1159
|
export type { Binding, BindingContext, BindingTable, CreateBuiltinBindingsOptions, DefaultModeConfig, ModeBlockReason, ModeEntryDecision, ModeEntryInput, ModeHideStrategy, PersistedModeState, PtcConfig, PtcErrorKind, PtcErrorShape, PtcImage, PtcJsonValue, PtcModeState, PtcRunOutcome, PtcSurface, RunPtcProgramOptions, TurnPoolsOptions, WorkerPoolOptions, WorkerPoolStats, WorkerPoolWorkerOptions };
|
|
606
1160
|
//# sourceMappingURL=index.d.ts.map
|