@evandu/super-pi 0.1.70 → 0.1.72

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.
@@ -0,0 +1,109 @@
1
+ import type { ChildProcessWithoutNullStreams } from "node:child_process";
2
+ /** Open-ended Pi RPC record; consumers validate fields before use. */
3
+ export type RpcRecord = {
4
+ type?: string;
5
+ id?: string;
6
+ success?: boolean;
7
+ error?: string;
8
+ /** Every `response` record's payload. Kept opaque: only the catalog reader knows these shapes. */
9
+ data?: unknown;
10
+ /**
11
+ * `tool_execution_end` carries the tool's OUTPUT. Declared so it can be REDUCED by
12
+ * extractToolResultText and then dropped: the raw object must never travel past this record, and that
13
+ * reduction is the whole reason the result is safe to persist. Kept opaque for the same reason `data` is.
14
+ */
15
+ result?: unknown;
16
+ /** The tool_execution_update record's running output. Reduced by the same function as `result`, never carried raw. */
17
+ partialResult?: unknown;
18
+ /**
19
+ * The inner event of a `message_update`. `message` rides on `done` - the authoritative final
20
+ * assistant message, with its text content, its stop reason and its model (pi-ai AssistantMessageEvent).
21
+ */
22
+ assistantMessageEvent?: {
23
+ type?: string;
24
+ delta?: string;
25
+ contentIndex?: number;
26
+ partial?: unknown;
27
+ reason?: string;
28
+ message?: {
29
+ role?: string;
30
+ content?: unknown;
31
+ stopReason?: string;
32
+ model?: string;
33
+ };
34
+ };
35
+ /**
36
+ * Cumulative usage of the CURRENT assistant message, repeated on every one of that message's updates
37
+ * (modes/json-event.ts:56-59). Reading it anywhere but on `done` counts the same tokens per token.
38
+ */
39
+ usage?: {
40
+ input?: number;
41
+ output?: number;
42
+ cacheRead?: number;
43
+ cacheWrite?: number;
44
+ cost?: {
45
+ total?: number;
46
+ };
47
+ };
48
+ toolCallId?: string;
49
+ toolName?: string;
50
+ args?: unknown;
51
+ /**
52
+ * tool_execution_end payload. `result` is NOT declared: it is deliberately never read, because for a
53
+ * `read` it is the file's contents and this type is the one place a reader would reach for it. The two
54
+ * numbers below are the whole of what a human-readable tool row can honestly report.
55
+ */
56
+ isError?: boolean;
57
+ durationMs?: number;
58
+ method?: string;
59
+ title?: string;
60
+ options?: unknown;
61
+ message?: string;
62
+ placeholder?: string;
63
+ prefill?: string;
64
+ timeout?: number;
65
+ notifyType?: string;
66
+ statusKey?: string;
67
+ statusText?: string;
68
+ widgetKey?: string;
69
+ widgetLines?: unknown;
70
+ widgetPlacement?: string;
71
+ text?: string;
72
+ extensionPath?: string;
73
+ event?: string;
74
+ steering?: unknown;
75
+ followUp?: unknown;
76
+ };
77
+ /** One Pi child, its stdout reader, and the single writer to its stdin. */
78
+ export declare class RpcChannel {
79
+ private readonly child;
80
+ private readonly records;
81
+ /** See `stash`: a drain loop must not discard another command's response. */
82
+ private readonly stashed;
83
+ private readonly waiters;
84
+ private buffer;
85
+ private exited;
86
+ private exitReason;
87
+ /** stderr is kept only to explain an early exit; a well-behaved Pi writes nothing here. */
88
+ stderr: string;
89
+ constructor(child: ChildProcessWithoutNullStreams);
90
+ get exitCode(): number | null;
91
+ get closed(): boolean;
92
+ private wake;
93
+ /** Waits for stdout or process exit without adding a fixed polling delay to every record. */
94
+ private changed;
95
+ /** Writes one JSONL command. Throws rather than silently dropping: a command Pi never sees is a lie the caller would act on. */
96
+ write(command: object): void;
97
+ /** Returns a response to its waiter after the drain loop encountered it. */
98
+ stash(record: RpcRecord): void;
99
+ /** Removes and returns the next record, or undefined after `timeoutMs` with nothing to read. */
100
+ next(timeoutMs?: number): Promise<RpcRecord | undefined>;
101
+ /** Sends a command and waits for its response. For one command; use write()+waitFor() to overlap several. */
102
+ request(command: {
103
+ id: string;
104
+ type: string;
105
+ } & Record<string, unknown>, timeoutMs: number): Promise<RpcRecord>;
106
+ private take;
107
+ /** Waits for one correlated response while leaving every unrelated record in order. */
108
+ waitFor(id: string, timeoutMs: number, what?: string): Promise<RpcRecord>;
109
+ }
@@ -0,0 +1,117 @@
1
+ /** One Pi child, its stdout reader, and the single writer to its stdin. */
2
+ export class RpcChannel {
3
+ child;
4
+ records = [];
5
+ /** See `stash`: a drain loop must not discard another command's response. */
6
+ stashed = new Map();
7
+ waiters = new Set();
8
+ buffer = "";
9
+ exited = false;
10
+ exitReason = "";
11
+ /** stderr is kept only to explain an early exit; a well-behaved Pi writes nothing here. */
12
+ stderr = "";
13
+ constructor(child) {
14
+ this.child = child;
15
+ child.stdout.setEncoding("utf8");
16
+ child.stderr.setEncoding("utf8");
17
+ child.stderr.on("data", (chunk) => { this.stderr += chunk.toString(); });
18
+ child.stdout.on("data", (chunk) => {
19
+ this.buffer += chunk.toString("utf8");
20
+ let changed = false;
21
+ let newline;
22
+ while ((newline = this.buffer.indexOf("\n")) >= 0) {
23
+ const line = this.buffer.slice(0, newline).replace(/\r$/, "");
24
+ this.buffer = this.buffer.slice(newline + 1);
25
+ if (!line)
26
+ continue;
27
+ try {
28
+ this.records.push(JSON.parse(line));
29
+ changed = true;
30
+ }
31
+ catch { /* Pi stdout must be JSONL */ }
32
+ }
33
+ if (changed)
34
+ this.wake();
35
+ });
36
+ child.once("exit", (code, signal) => {
37
+ this.exited = true;
38
+ this.exitReason = signal ? `signal ${signal}` : `code ${code}`;
39
+ this.wake();
40
+ });
41
+ child.once("error", (error) => {
42
+ this.exited = true;
43
+ this.exitReason = error.message;
44
+ this.wake();
45
+ });
46
+ }
47
+ get exitCode() { return this.child.exitCode; }
48
+ get closed() { return this.exited || this.child.exitCode !== null; }
49
+ wake() {
50
+ for (const resolve of this.waiters)
51
+ resolve();
52
+ this.waiters.clear();
53
+ }
54
+ /** Waits for stdout or process exit without adding a fixed polling delay to every record. */
55
+ changed(timeoutMs) {
56
+ if (timeoutMs <= 0)
57
+ return Promise.resolve();
58
+ return new Promise((resolve) => {
59
+ const finish = () => { clearTimeout(timer); this.waiters.delete(finish); resolve(); };
60
+ const timer = setTimeout(finish, timeoutMs);
61
+ this.waiters.add(finish);
62
+ });
63
+ }
64
+ /** Writes one JSONL command. Throws rather than silently dropping: a command Pi never sees is a lie the caller would act on. */
65
+ write(command) {
66
+ if (this.child.killed || this.child.stdin.destroyed)
67
+ throw new Error(`Pi RPC stdin is closed, so ${JSON.stringify(command)} was not sent`);
68
+ this.child.stdin.write(`${JSON.stringify(command)}\n`);
69
+ }
70
+ /** Returns a response to its waiter after the drain loop encountered it. */
71
+ stash(record) {
72
+ if (record.type === "response" && typeof record.id === "string") {
73
+ this.stashed.set(record.id, record);
74
+ this.wake();
75
+ }
76
+ }
77
+ /** Removes and returns the next record, or undefined after `timeoutMs` with nothing to read. */
78
+ async next(timeoutMs = 100) {
79
+ const deadline = performance.now() + timeoutMs;
80
+ for (;;) {
81
+ if (this.records.length)
82
+ return this.records.shift();
83
+ if (this.exited || this.child.exitCode !== null || performance.now() >= deadline)
84
+ return undefined;
85
+ await this.changed(deadline - performance.now());
86
+ }
87
+ }
88
+ /** Sends a command and waits for its response. For one command; use write()+waitFor() to overlap several. */
89
+ async request(command, timeoutMs) {
90
+ this.write(command);
91
+ return this.waitFor(command.id, timeoutMs, command.type);
92
+ }
93
+ take(id) {
94
+ const index = this.records.findIndex((record) => record.type === "response" && record.id === id);
95
+ if (index >= 0)
96
+ return this.records.splice(index, 1)[0];
97
+ const stashed = this.stashed.get(id);
98
+ if (!stashed)
99
+ return undefined;
100
+ this.stashed.delete(id);
101
+ return stashed;
102
+ }
103
+ /** Waits for one correlated response while leaving every unrelated record in order. */
104
+ async waitFor(id, timeoutMs, what = id) {
105
+ const deadline = performance.now() + timeoutMs;
106
+ for (;;) {
107
+ const record = this.take(id);
108
+ if (record)
109
+ return record;
110
+ if (this.exited || this.child.exitCode !== null)
111
+ throw new Error(`Pi RPC exited (${this.exitReason || `code ${this.child.exitCode}`}) while waiting for ${what}. ${this.stderr.trim()}`);
112
+ if (performance.now() >= deadline)
113
+ throw new Error(`Pi did not answer ${what} within ${timeoutMs}ms`);
114
+ await this.changed(deadline - performance.now());
115
+ }
116
+ }
117
+ }
@@ -1,5 +1,7 @@
1
- import { type ChildProcessWithoutNullStreams } from "node:child_process";
1
+ import { type RpcRecord } from "./rpc-channel.js";
2
+ export { RpcChannel, type RpcRecord } from "./rpc-channel.js";
2
3
  import type { ActiveMemory } from "./active-memory.js";
4
+ export { forkSessionFile, truncateSessionFile } from "./session-repository.js";
3
5
  import { type AgentDefinition, type Conversation, type ExtensionUiAnswer, type ExtensionUiRequest, type MessageImage, type ModelCatalog, type ModelOption, type ModelSelection, type PiQueueSnapshot, type ToolCallNotice, type ToolCallPreparing } from "./types.js";
4
6
  /**
5
7
  * Who answers Pi's Extension UI questions. The runtime never invents an answer: a blocking method
@@ -56,6 +58,8 @@ export interface AnswerInput {
56
58
  * revert boundary, which the revert handler reports in words.
57
59
  */
58
60
  onUserEntry?: (entryId: string) => void | Promise<void>;
61
+ /** Session file Pi resolved for the live worker. A host may cache it as a locator for a later revert. */
62
+ onSessionFile?: (path: string) => void | Promise<void>;
59
63
  /**
60
64
  * Called once per `tool_execution_start`, with the tool's name and the bounded label naming what it was
61
65
  * aimed at.
@@ -263,80 +267,6 @@ export declare class WelcomeRuntime implements AgentRuntime {
263
267
  * through the same channel instead of writing a second JSONL reader. Every field below is optional, so
264
268
  * a reader must check before using one.
265
269
  */
266
- export type RpcRecord = {
267
- type?: string;
268
- id?: string;
269
- success?: boolean;
270
- error?: string;
271
- /** Every `response` record's payload. Kept opaque: only the catalog reader knows these shapes. */
272
- data?: unknown;
273
- /**
274
- * `tool_execution_end` carries the tool's OUTPUT. Declared so it can be REDUCED by
275
- * extractToolResultText and then dropped: the raw object must never travel past this record, and that
276
- * reduction is the whole reason the result is safe to persist. Kept opaque for the same reason `data` is.
277
- */
278
- result?: unknown;
279
- /** The tool_execution_update record's running output. Reduced by the same function as `result`, never carried raw. */
280
- partialResult?: unknown;
281
- /**
282
- * The inner event of a `message_update`. `message` rides on `done` - the authoritative final
283
- * assistant message, with its text content, its stop reason and its model (pi-ai AssistantMessageEvent).
284
- */
285
- assistantMessageEvent?: {
286
- type?: string;
287
- delta?: string;
288
- contentIndex?: number;
289
- partial?: unknown;
290
- reason?: string;
291
- message?: {
292
- role?: string;
293
- content?: unknown;
294
- stopReason?: string;
295
- model?: string;
296
- };
297
- };
298
- /**
299
- * Cumulative usage of the CURRENT assistant message, repeated on every one of that message's updates
300
- * (modes/json-event.ts:56-59). Reading it anywhere but on `done` counts the same tokens per token.
301
- */
302
- usage?: {
303
- input?: number;
304
- output?: number;
305
- cacheRead?: number;
306
- cacheWrite?: number;
307
- cost?: {
308
- total?: number;
309
- };
310
- };
311
- toolCallId?: string;
312
- toolName?: string;
313
- args?: unknown;
314
- /**
315
- * tool_execution_end payload. `result` is NOT declared: it is deliberately never read, because for a
316
- * `read` it is the file's contents and this type is the one place a reader would reach for it. The two
317
- * numbers below are the whole of what a human-readable tool row can honestly report.
318
- */
319
- isError?: boolean;
320
- durationMs?: number;
321
- method?: string;
322
- title?: string;
323
- options?: unknown;
324
- message?: string;
325
- placeholder?: string;
326
- prefill?: string;
327
- timeout?: number;
328
- notifyType?: string;
329
- statusKey?: string;
330
- statusText?: string;
331
- widgetKey?: string;
332
- widgetLines?: unknown;
333
- widgetPlacement?: string;
334
- text?: string;
335
- extensionPath?: string;
336
- event?: string;
337
- steering?: unknown;
338
- followUp?: unknown;
339
- };
340
270
  /**
341
271
  * Reads Pi's `queue_update` payload, or returns undefined when the record is not one.
342
272
  *
@@ -366,58 +296,6 @@ export declare function describeExtensionUiRequest(record: RpcRecord): Extension
366
296
  * cannot produce a response Pi would read as a missing field.
367
297
  */
368
298
  export declare function buildExtensionUiResponse(request: ExtensionUiRequest, answer: ExtensionUiAnswer): ExtensionUiResponse;
369
- /** One Pi child, its stdout reader, and the single writer to its stdin. */
370
- export declare class RpcChannel {
371
- private readonly child;
372
- private readonly records;
373
- /**
374
- * Responses a drain loop pulled off the queue before the waiter that wanted them looked.
375
- *
376
- * THE RACE THIS EXISTS TO SETTLE, and it is not a rare one - it cost this file an intermittent test
377
- * failure. One queue serves two kinds of reader: `waitFor`, which is after a specific command's answer by
378
- * id, and `next`, which is a turn's drain loop reading whatever arrives first. The loop does not know
379
- * which ids someone is waiting for, so it used to take the first record of any kind - including the answer
380
- * to a `get_entries` written moments earlier - and the waiter then waited out its whole timeout for a
381
- * response that had already been consumed and discarded.
382
- *
383
- * So the drain loop no longer decides an unmatched response is garbage: it HANDS IT BACK here, and the
384
- * waiter finds it whether it got there first or second. Nothing is dropped, and nothing is waited for
385
- * twice.
386
- */
387
- private readonly stashed;
388
- private buffer;
389
- /** stderr is kept only to explain an early exit; a well-behaved Pi writes nothing here. */
390
- stderr: string;
391
- constructor(child: ChildProcessWithoutNullStreams);
392
- get exitCode(): number | null;
393
- /** Writes one JSONL command. Throws rather than silently dropping: a command Pi never sees is a lie the caller would act on. */
394
- write(command: object): void;
395
- /**
396
- * Returns one record to whoever is waiting for it, after a drain loop read it.
397
- *
398
- * Called for every response the loop does not consume itself. See `RpcChannel`'s note on `stashed` for why
399
- * this is not the same as discarding it - discarding it silently cost a turn its revert boundary.
400
- */
401
- stash(record: RpcRecord): void;
402
- /** Removes and returns the next record, or undefined after `timeoutMs` with nothing to read. */
403
- next(timeoutMs?: number): Promise<RpcRecord | undefined>;
404
- /** Sends a command and waits for its response. For one command; use write()+waitFor() to overlap several. */
405
- request(command: {
406
- id: string;
407
- type: string;
408
- } & Record<string, unknown>, timeoutMs: number): Promise<RpcRecord>;
409
- /**
410
- * Returns one response by id, from the queue if it is still there and from the stash if a drain loop got
411
- * there first. Removing it from the stash is what keeps a single answer from being handed out twice.
412
- */
413
- private take;
414
- /**
415
- * Waits for the response to one command id. Every record that is not that response stays QUEUED in
416
- * order, so a caller can have three requests in flight and still read all three answers: whoever
417
- * asks first takes its own record and leaves the rest for the drain loop.
418
- */
419
- waitFor(id: string, timeoutMs: number, what?: string): Promise<RpcRecord>;
420
- }
421
299
  /**
422
300
  * The commands a freshly spawned Pi must be told before it runs a conversation's turn.
423
301
  *
@@ -446,105 +324,6 @@ export declare function sessionCommands(conversation: Pick<Conversation, "model"
446
324
  * only the shim, which is the exact failure this function exists to prevent.
447
325
  */
448
326
  export declare function killTree(pid: number): Promise<void>;
449
- /**
450
- * Cuts a Pi session file down to the header plus everything up to and including `boundaryEntryId`.
451
- *
452
- * ============================ WHAT THIS DESTROYS ============================
453
- * **Every entry written after the boundary is gone from disk, permanently, and there is no way to
454
- * reconstruct it from this file.** That includes not only the messages a person can see but also Pi's
455
- * `tool_call` records, `compaction` summaries, `model_change` and `thinking_level_change` markers, usage
456
- * accounting, and anything a future Pi version adds. This is the step the whole revert design is built
457
- * around (research §4.2) and the step that cannot be undone by the application - Super Pi has no undo,
458
- * and adding one was explicitly left out of scope.
459
- *
460
- * It does NOT touch the workspace. No file the agent read or wrote is read, written or reverted here.
461
- * =============================================================================
462
- *
463
- * WHY TRUNCATE RATHER THAN ASK PI. Pi's `SessionManager` is append-only by design
464
- * (`session-manager.ts:1516-1519`: "Entries cannot be modified or deleted"), and its in-session rewind -
465
- * `branch()`, `navigateTree()` - is IN-MEMORY ONLY (`:1573-1584`): `_buildIndex()` re-reads the whole
466
- * file on every `open()` and sets the leaf to the last line unconditionally (`:1103-1122`). Super Pi
467
- * spawns one Pi per turn and kills it afterwards, so a leaf pointer set in one turn is gone before the
468
- * next one starts - a "call `branch()` once" solution would look correct for a turn and then silently
469
- * forget (research §5 pitfall 2). Truncating the file is the only version of this that is a durable state
470
- * change rather than per-turn bookkeeping that can be skipped exactly once.
471
- *
472
- * WHY THE FORMAT IS ASSUMED RATHER THAN GUESSED. The shape this relies on - line 0 is the one and only
473
- * `type: "session"` header, every other line is one self-contained JSON entry, every entry's `parentId`
474
- * already appears earlier in the file, no blank lines, and a trailing newline - was measured against
475
- * real Pi by scripts/probe-revert-session-format.mjs, which also proves the `parentId` property that
476
- * makes a prefix a VALID session rather than a fragment. That probe is the reason this function may
477
- * index by line at all; every one of those claims is re-checked here before anything is written, so a
478
- * Pi that changed the format fails loudly instead of producing a file Pi cannot open.
479
- *
480
- * WHY THE WRITE IS TEMP-THEN-RENAME. Pi appends with a bare `appendFileSync`
481
- * (`session-manager.ts:1187`) and makes no atomicity guarantee of its own. Writing the truncated copy
482
- * under a different name and renaming it over the original means a reader never observes a half-written
483
- * file, and a failure before the rename leaves the original untouched.
484
- *
485
- * WHY A BACKUP IS KEPT. `.super-pi-revert-backup` is written next to the session file, deliberately not
486
- * named `*.jsonl` so Pi's own discovery (`readdirSync` filtered on that suffix,
487
- * `session-manager.ts:1881`) never lists it, and deliberately NOT surfaced anywhere as a feature:
488
- * Super Pi has no undo and does not pretend to. It exists so that an irreversible, unrecoverable-by-code
489
- * operation on a person's conversation history is at least recoverable by hand.
490
- *
491
- * EXPORTED so it can be tested against a fabricated session file in a temp directory, without Pi and
492
- * without touching anybody's real transcript.
493
- */
494
- export declare function truncateSessionFile(path: string, boundaryEntryId: string, expectedSessionId: string, beforeBoundary?: boolean): Promise<{
495
- linesKept: number;
496
- entriesDropped: number;
497
- }>;
498
- /**
499
- * Writes a second Pi session file that begins as an EXACT copy of one, under a different session id.
500
- *
501
- * ============================ WHAT THIS DOES TO THE ORIGINAL ============================
502
- * **Nothing.** The source file is read and never written, never truncated, never renamed. This is the
503
- * whole difference from `truncateSessionFile`, and it is the reason a fork is the safe half of this pair
504
- * of operations: a revert destroys entries permanently and keeps a hand-recovery backup, while a fork
505
- * leaves both files whole and keeps no backup because there is nothing to recover.
506
- * ========================================================================================
507
- *
508
- * WHY A COPY AND NOT PI'S `fork()`. Covered at length on `ClientCommand`'s `conversation.fork`: Pi's fork
509
- * always tears down the runtime and switches to the new session (`agent-session-runtime.ts:299-307`), and
510
- * Super Pi has no runtime to switch - it spawns one Pi per turn and kills it. The file is the only durable
511
- * thing a fork has to produce here, so the file is what gets produced.
512
- *
513
- * ============================ WHAT THIS DOES TO PI's ENTRY IDS ============================
514
- * **Nothing either, and this is a deliberate divergence from Pi's own `createBranchedSession`, which
515
- * RENUMBERS `parentId` (`:1655-1666`).** Keeping every id verbatim means every cross-reference already
516
- * inside the file stays valid, and research pitfall 6 is exactly why that matters: a Pi entry may point
517
- * at other entries by id in ways this code has never enumerated - a `compaction` record carries a
518
- * `firstKeptEntryId`, `branch_summary` carries the branch point - and renumbering ids means rewriting every
519
- * one of those references correctly, with no way to find them all. Copying the bytes cannot break a
520
- * reference because it changes nothing a reference points at. The cost is that the same entry id now
521
- * exists in two files; that is safe because `_buildIndex` builds its `byId` map per file (`:1103-1122`),
522
- * and Super Pi only ever opens one file per process.
523
- *
524
- * WHAT THE HEADER CHANGES, and it is two fields. `id` becomes the new conversation's id, because that is
525
- * what Pi's lookup matches (`findLocalSessionByExactId` reads the header, probe B) and therefore what makes
526
- * `--session-id <forked>` open THIS file rather than silently inventing an empty session with that id
527
- * (`main.ts:436-448`, measured: `Warning: No project session found with id '...'`). And `parentSession`
528
- * records the file this one came from, which is the same one-line lineage pointer Pi itself writes
529
- * (`:1675-1682`) and is not a link - the files share nothing. `cwd`, `version` and `timestamp` are copied
530
- * verbatim: `cwd` MUST be identical, because Pi's lookup requires the recorded cwd to equal the one it is
531
- * asked from, and the fork always lives in the same workspace as its source.
532
- *
533
- * THE SAME FORMAT CHECKS AS TRUNCATE, AND FOR THE SAME REASON. Probe A measured this file's shape against
534
- * real Pi (`scripts/probe-revert-session-format.mjs`); a Pi upgrade can invalidate any of those claims, so
535
- * every one is re-checked before a byte is written and a failure names the shape actually found. Here the
536
- * `parentId` property is checked across the WHOLE file rather than across a kept prefix, because the whole
537
- * file is what is being copied.
538
- *
539
- * TEMP-THEN-RENAME, because Pi appends with a bare `appendFileSync` (`:1187`) and the target must never be
540
- * observed half-written - a half-written file under the forked id is precisely the "Pi opens an empty
541
- * session and the history has vanished with no error anywhere" outcome probe B was about.
542
- *
543
- * EXPORTED so it can be tested against fabricated session files in a temp directory, without Pi.
544
- */
545
- export declare function forkSessionFile(sourcePath: string, targetPath: string, expectedSessionId: string, targetSessionId: string): Promise<{
546
- entriesCopied: number;
547
- }>;
548
327
  /**
549
328
  * Reads Pi's `steer` disposition out of its response payload.
550
329
  *
@@ -712,7 +491,7 @@ export declare class PiRpcRuntime implements AgentRuntime {
712
491
  * own address, so it is the same string on every turn and is read once from the constructor's options.
713
492
  */
714
493
  private taskExtensionEnv;
715
- answer({ conversation, prompt, signal, cwd, host, onPiQueue, onUserEntry, onToolCall, onToolUpdate, onToolPreparing, onToolEnd, onThinking, images, memoryReview, memorySourceConversationId }: AnswerInput): AsyncIterable<string>;
494
+ answer({ conversation, prompt, signal, cwd, host, onPiQueue, onUserEntry, onSessionFile, onToolCall, onToolUpdate, onToolPreparing, onToolEnd, onThinking, images, memoryReview, memorySourceConversationId }: AnswerInput): AsyncIterable<string>;
716
495
  /**
717
496
  * Reads back the id of the user message this turn just created, and hands it to the caller.
718
497
  *