@xingular/harness 1.6.0-unstable → 2.0.1-unstable
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 +43 -0
- package/dist/cli.js +23375 -19883
- package/dist/sdk/cli.js +16 -1
- package/dist/sdk/harness.js +2 -1
- package/dist/sdk/prompt/reporting.d.ts +1 -1
- package/dist/sdk/prompt/reporting.js +8 -3
- package/dist/sdk/server/methods.js +12 -3
- package/dist/sdk/server/serve.d.ts +5 -2
- package/dist/sdk/server/serve.js +10 -3
- package/dist/sdk/server/socket.d.ts +28 -0
- package/dist/sdk/server/socket.js +122 -0
- package/dist/sdk/sessions/contracts.d.ts +14 -1
- package/dist/sdk/sessions/contracts.js +9 -0
- package/dist/sdk/sessions/extensionUi.js +4 -4
- package/dist/sdk/sessions/live.d.ts +101 -0
- package/dist/sdk/sessions/live.js +265 -0
- package/dist/sdk/sessions/registry.d.ts +12 -0
- package/dist/sdk/sessions/registry.js +3 -0
- package/dist/sdk/sessions/runner.d.ts +25 -3
- package/dist/sdk/sessions/runner.js +171 -31
- package/dist/sdk/sessions/sessions.d.ts +49 -0
- package/dist/sdk/sessions/sessions.js +165 -8
- package/dist/sdk/{terminal → sessions}/stallWatch.d.ts +4 -0
- package/dist/sdk/{terminal → sessions}/stallWatch.js +10 -4
- package/dist/sdk/sessions/subagentService.js +1 -1
- package/dist/sdk/terminal/controller.js +1 -3
- package/dist/sdk/terminal/daemon.d.ts +44 -0
- package/dist/sdk/terminal/daemon.js +264 -0
- package/dist/sdk/terminal/liveTerminal.d.ts +96 -0
- package/dist/sdk/terminal/liveTerminal.js +436 -0
- package/dist/sdk/terminal/standalone.d.ts +19 -1
- package/dist/sdk/terminal/standalone.js +75 -25
- package/dist/sdk/terminal/terminals.d.ts +43 -0
- package/dist/sdk/terminal/terminals.js +171 -14
- package/dist/sdk/terminal/tui/commands.d.ts +2 -0
- package/dist/sdk/terminal/tui/commands.js +24 -2
- package/dist/sdk/terminal/tui/harnessRuntime.d.ts +8 -0
- package/dist/sdk/terminal/tui/harnessRuntime.js +771 -0
- package/dist/sdk/terminal/tui/thinkingLevels.d.ts +11 -0
- package/dist/sdk/terminal/tui/thinkingLevels.js +31 -0
- package/dist/terminal-opentui.js +2013 -975
- package/dist/terminal-pi.js +9 -0
- package/docs/architecture/02-sessions-and-runs.md +61 -3
- package/docs/architecture/04-prompt.md +8 -6
- package/docs/architecture/10-server-protocol.md +23 -1
- package/docs/architecture/11-terminal.md +67 -3
- package/docs/architecture/12-storage.md +3 -1
- package/docs/architecture/14-surfaces-and-parity.md +4 -0
- package/docs/architecture/15-remote-control.md +3 -1
- package/docs/architecture/README.md +5 -0
- package/docs/decisions/ADR-0001-owned-terminal.md +9 -2
- package/docs/decisions/ADR-0083-one-live-session-runtime.md +256 -0
- package/docs/parity.md +4 -3
- package/docs/releases/2.0.1-unstable.md +63 -0
- package/docs/terminal-tui.md +10 -5
- package/package.json +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Live sessions (ADR-0083): a Pi session the harness keeps open between the runs of one
|
|
2
|
+
// conversation, so a later run reuses it instead of opening and loading a new one. A live session
|
|
3
|
+
// serves only a run whose pins equal the ones it was opened with (INV-RUN-1); anything else closes it
|
|
4
|
+
// at the turn boundary, and the next run opens a fresh session on the same native history.
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { realpathSync } from 'node:fs';
|
|
7
|
+
import { HarnessError } from "../core/errors.js";
|
|
8
|
+
/** How long an idle live session stays open, unless the caller sets another time. */
|
|
9
|
+
export const LIVE_IDLE_MS = 30 * 60_000;
|
|
10
|
+
/** Follow-up messages one live session may hold. */
|
|
11
|
+
export const MAX_FOLLOW_UPS = 20;
|
|
12
|
+
const MAX_FOLLOW_UP_CHARS = 100_000;
|
|
13
|
+
/** The run fields a live session must share with a run before it may serve it. */
|
|
14
|
+
export function livePin(fields) {
|
|
15
|
+
return createHash('sha256').update(JSON.stringify(fields)).digest('hex');
|
|
16
|
+
}
|
|
17
|
+
function samePath(a, b) {
|
|
18
|
+
if (a === b)
|
|
19
|
+
return true;
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(a) === realpathSync(b);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const now = () => new Date().toISOString();
|
|
28
|
+
export class LiveSessions {
|
|
29
|
+
options;
|
|
30
|
+
entries = new Map();
|
|
31
|
+
/** Watchers of each session's native Pi events; they outlive a session replaced at a pin change. */
|
|
32
|
+
watchers = new Map();
|
|
33
|
+
unsubscribers = new Map();
|
|
34
|
+
/** Follow-ups of a session closed at a pin change, handed to the session that replaces it. */
|
|
35
|
+
parked = new Map();
|
|
36
|
+
idleMs;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.options = options;
|
|
39
|
+
this.idleMs = options.idleMs ?? LIVE_IDLE_MS;
|
|
40
|
+
}
|
|
41
|
+
/** Whether a run of this session is using its live session now. */
|
|
42
|
+
busy(sessionId) {
|
|
43
|
+
return Boolean(this.entries.get(sessionId)?.activeRunId);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The live session for this run, now marked in use, or null when there is none to reuse. An idle
|
|
47
|
+
* session whose pins, history or native id differ is closed here, at the turn boundary.
|
|
48
|
+
*/
|
|
49
|
+
claim(sessionId, pin, runId, historyFile, nativeSessionId) {
|
|
50
|
+
const entry = this.entries.get(sessionId);
|
|
51
|
+
if (!entry)
|
|
52
|
+
return null;
|
|
53
|
+
if (entry.activeRunId)
|
|
54
|
+
throw new HarnessError('session_busy', 'This session is already replying. Steer it with runs.steer, or queue the message with sessions.followUp.');
|
|
55
|
+
const historyMatches = !historyFile || !entry.pi.sessionFile || samePath(historyFile, entry.pi.sessionFile);
|
|
56
|
+
const nativeMatches = !nativeSessionId || nativeSessionId === entry.pi.sessionId;
|
|
57
|
+
if (entry.pin !== pin || !historyMatches || !nativeMatches || !entry.pi.rebind) {
|
|
58
|
+
if (entry.followUps.length)
|
|
59
|
+
this.parked.set(sessionId, entry.followUps.splice(0));
|
|
60
|
+
this.dispose(entry);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
this.stopTimer(entry);
|
|
64
|
+
entry.activeRunId = runId;
|
|
65
|
+
entry.idleSince = null;
|
|
66
|
+
return entry.pi;
|
|
67
|
+
}
|
|
68
|
+
/** Records the session a run just opened, so the next run of the conversation can reuse it. */
|
|
69
|
+
hold(sessionId, pin, pi, runId, sessionDir) {
|
|
70
|
+
const previous = this.entries.get(sessionId);
|
|
71
|
+
if (previous && previous.pi !== pi)
|
|
72
|
+
this.dispose(previous);
|
|
73
|
+
this.entries.set(sessionId, {
|
|
74
|
+
sessionId, sessionDir, pin, pi, activeRunId: runId, lastRunId: runId, runs: 0, openedAt: now(), idleSince: null,
|
|
75
|
+
followUps: previous?.followUps ?? this.parked.get(sessionId) ?? [], closeWhenIdle: false, timer: null
|
|
76
|
+
});
|
|
77
|
+
this.parked.delete(sessionId);
|
|
78
|
+
const entry = this.entries.get(sessionId);
|
|
79
|
+
// Every native event of the session reaches its watchers, whichever run is current.
|
|
80
|
+
this.unsubscribers.set(entry, pi.subscribe((event) => {
|
|
81
|
+
for (const watcher of this.watchers.get(sessionId) ?? []) {
|
|
82
|
+
try {
|
|
83
|
+
watcher(event);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.options.log?.(`a live session watcher failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/** The run has started on the session: count it. */
|
|
92
|
+
started(sessionId, runId) {
|
|
93
|
+
const entry = this.entries.get(sessionId);
|
|
94
|
+
if (!entry || entry.activeRunId !== runId)
|
|
95
|
+
return;
|
|
96
|
+
entry.runs += 1;
|
|
97
|
+
entry.lastRunId = runId;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The run settled. The session stays open for the next run. A queued follow-up is sent after a
|
|
101
|
+
* reply that completed or that the person stopped; after a failure, or an interruption the person
|
|
102
|
+
* did not ask for, it stays parked (page 02, queued-message dispatch). `close` ends the session
|
|
103
|
+
* instead of keeping it, with its queue parked.
|
|
104
|
+
*/
|
|
105
|
+
release(sessionId, runId, close = false) {
|
|
106
|
+
const entry = this.entries.get(sessionId);
|
|
107
|
+
if (!entry || entry.activeRunId !== runId)
|
|
108
|
+
return;
|
|
109
|
+
entry.activeRunId = null;
|
|
110
|
+
entry.idleSince = now();
|
|
111
|
+
if (close) {
|
|
112
|
+
// A session the run could not trust (a stalled turn) is not reused; its queue waits for the next session.
|
|
113
|
+
if (entry.followUps.length)
|
|
114
|
+
this.parked.set(sessionId, entry.followUps.splice(0));
|
|
115
|
+
this.dispose(entry);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (entry.closeWhenIdle) {
|
|
119
|
+
this.dispose(entry);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.startTimer(entry);
|
|
123
|
+
if (!entry.followUps.length)
|
|
124
|
+
return;
|
|
125
|
+
const record = this.options.record(runId);
|
|
126
|
+
// A work-slice continuation is the next run already; the queue waits for it.
|
|
127
|
+
const dispatch = record && record.terminalReason !== 'slice_complete'
|
|
128
|
+
&& (record.status === 'completed' || (record.status === 'interrupted' && record.terminalCode === 'user_abort'));
|
|
129
|
+
if (!dispatch)
|
|
130
|
+
return;
|
|
131
|
+
const text = entry.followUps.shift();
|
|
132
|
+
void this.options.followUp(sessionId, runId, text).catch((error) => {
|
|
133
|
+
// The message is not lost: it goes back to the front of the queue.
|
|
134
|
+
entry.followUps.unshift(text);
|
|
135
|
+
this.options.log?.(`a follow-up did not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Something other than the live session is about to change this history: a separate command
|
|
140
|
+
* run or a saved-history edit. An idle live session closes, so its next run reads the file again;
|
|
141
|
+
* one that is replying refuses the change.
|
|
142
|
+
*/
|
|
143
|
+
yieldHistory(match) {
|
|
144
|
+
for (const entry of [...this.entries.values()]) {
|
|
145
|
+
const hit = (match.sessionId !== undefined && entry.sessionId === match.sessionId)
|
|
146
|
+
|| (match.sessionDir !== undefined && samePath(entry.sessionDir, match.sessionDir));
|
|
147
|
+
if (!hit)
|
|
148
|
+
continue;
|
|
149
|
+
if (entry.activeRunId)
|
|
150
|
+
throw new HarnessError('session_busy', 'This conversation is replying in a live session. Wait for it to settle, or interrupt it, before changing its history.');
|
|
151
|
+
this.dispose(entry);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** The session could not serve the run after all: forget it and close it. */
|
|
155
|
+
discard(sessionId, pi) {
|
|
156
|
+
const entry = this.entries.get(sessionId);
|
|
157
|
+
if (entry?.pi === pi)
|
|
158
|
+
this.dispose(entry);
|
|
159
|
+
else
|
|
160
|
+
pi.dispose();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Queues a message for after the active run, or reports that the session is idle so the caller
|
|
164
|
+
* starts it now.
|
|
165
|
+
*/
|
|
166
|
+
queue(sessionId, text) {
|
|
167
|
+
const entry = this.entries.get(sessionId);
|
|
168
|
+
if (!entry)
|
|
169
|
+
throw new HarnessError('not_found', 'This session has no live session. Start a run with live: true first.');
|
|
170
|
+
if (!text.trim() || text.length > MAX_FOLLOW_UP_CHARS)
|
|
171
|
+
throw new HarnessError('invalid_request', `A follow-up needs text of at most ${MAX_FOLLOW_UP_CHARS} characters.`);
|
|
172
|
+
if (!entry.activeRunId)
|
|
173
|
+
return { idle: true, lastRunId: entry.lastRunId };
|
|
174
|
+
if (entry.followUps.length >= MAX_FOLLOW_UPS)
|
|
175
|
+
throw new HarnessError('invalid_request', `A session holds at most ${MAX_FOLLOW_UPS} follow-up messages.`);
|
|
176
|
+
entry.followUps.push(text);
|
|
177
|
+
return { queued: entry.followUps.length };
|
|
178
|
+
}
|
|
179
|
+
/** Drops the queued follow-ups and returns them, oldest first. */
|
|
180
|
+
clearQueue(sessionId) {
|
|
181
|
+
const entry = this.entries.get(sessionId);
|
|
182
|
+
return entry ? entry.followUps.splice(0) : [];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Receives the native Pi events of this conversation's live session, and of the one that replaces
|
|
186
|
+
* it, until the returned function is called (ADR-0083: a client renders from them).
|
|
187
|
+
*/
|
|
188
|
+
watch(sessionId, watcher) {
|
|
189
|
+
const set = this.watchers.get(sessionId) ?? new Set();
|
|
190
|
+
set.add(watcher);
|
|
191
|
+
this.watchers.set(sessionId, set);
|
|
192
|
+
return () => {
|
|
193
|
+
set.delete(watcher);
|
|
194
|
+
if (!set.size && this.watchers.get(sessionId) === set)
|
|
195
|
+
this.watchers.delete(sessionId);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/** The open Pi session of a conversation, for a read of its current state. */
|
|
199
|
+
session(sessionId) {
|
|
200
|
+
return this.entries.get(sessionId)?.pi ?? null;
|
|
201
|
+
}
|
|
202
|
+
status(sessionId) {
|
|
203
|
+
const entry = this.entries.get(sessionId);
|
|
204
|
+
return entry ? this.statusOf(entry) : null;
|
|
205
|
+
}
|
|
206
|
+
list() {
|
|
207
|
+
return [...this.entries.values()].map((entry) => this.statusOf(entry));
|
|
208
|
+
}
|
|
209
|
+
/** Closes a live session now, or when its active run settles. */
|
|
210
|
+
close(sessionId) {
|
|
211
|
+
const entry = this.entries.get(sessionId);
|
|
212
|
+
if (!entry)
|
|
213
|
+
return { closed: false, afterRun: null };
|
|
214
|
+
if (entry.activeRunId) {
|
|
215
|
+
entry.closeWhenIdle = true;
|
|
216
|
+
return { closed: true, afterRun: entry.activeRunId };
|
|
217
|
+
}
|
|
218
|
+
this.dispose(entry);
|
|
219
|
+
return { closed: true, afterRun: null };
|
|
220
|
+
}
|
|
221
|
+
closeAll() {
|
|
222
|
+
this.parked.clear();
|
|
223
|
+
for (const entry of [...this.entries.values()])
|
|
224
|
+
this.dispose(entry);
|
|
225
|
+
}
|
|
226
|
+
statusOf(entry) {
|
|
227
|
+
return {
|
|
228
|
+
sessionId: entry.sessionId,
|
|
229
|
+
nativeSessionId: entry.pi.sessionId,
|
|
230
|
+
sessionFile: entry.pi.sessionFile ?? null,
|
|
231
|
+
activeRunId: entry.activeRunId,
|
|
232
|
+
lastRunId: entry.lastRunId,
|
|
233
|
+
runs: entry.runs,
|
|
234
|
+
openedAt: entry.openedAt,
|
|
235
|
+
idleSince: entry.idleSince,
|
|
236
|
+
followUps: [...entry.followUps]
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
startTimer(entry) {
|
|
240
|
+
this.stopTimer(entry);
|
|
241
|
+
entry.timer = setTimeout(() => {
|
|
242
|
+
if (!entry.activeRunId && this.entries.get(entry.sessionId) === entry)
|
|
243
|
+
this.dispose(entry);
|
|
244
|
+
}, this.idleMs);
|
|
245
|
+
entry.timer.unref?.();
|
|
246
|
+
}
|
|
247
|
+
stopTimer(entry) {
|
|
248
|
+
if (entry.timer)
|
|
249
|
+
clearTimeout(entry.timer);
|
|
250
|
+
entry.timer = null;
|
|
251
|
+
}
|
|
252
|
+
dispose(entry) {
|
|
253
|
+
this.stopTimer(entry);
|
|
254
|
+
this.unsubscribers.get(entry)?.();
|
|
255
|
+
this.unsubscribers.delete(entry);
|
|
256
|
+
if (this.entries.get(entry.sessionId) === entry)
|
|
257
|
+
this.entries.delete(entry.sessionId);
|
|
258
|
+
try {
|
|
259
|
+
entry.pi.dispose();
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
this.options.log?.(`a live session did not close cleanly: ${error instanceof Error ? error.message : String(error)}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -17,9 +17,19 @@ export interface ApprovalInput {
|
|
|
17
17
|
/** How long to wait for a decision before the request counts as denied. */
|
|
18
18
|
timeoutMs?: number;
|
|
19
19
|
}
|
|
20
|
+
/** The Pi extension dialog a question stands for, so a terminal can show the dialog itself (ADR-0083). */
|
|
21
|
+
export interface QuestionDialog {
|
|
22
|
+
kind: 'select' | 'confirm' | 'input' | 'editor';
|
|
23
|
+
title: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
prefill?: string;
|
|
27
|
+
options?: string[];
|
|
28
|
+
}
|
|
20
29
|
export interface QuestionInput {
|
|
21
30
|
requestId?: string;
|
|
22
31
|
userOnly?: boolean;
|
|
32
|
+
dialog?: QuestionDialog;
|
|
23
33
|
summary?: string;
|
|
24
34
|
question: string;
|
|
25
35
|
uiSketch?: true;
|
|
@@ -55,6 +65,8 @@ export interface ActiveRun {
|
|
|
55
65
|
message: string;
|
|
56
66
|
}, reason?: string, code?: RunStopCode): void;
|
|
57
67
|
readonly finished: boolean;
|
|
68
|
+
/** Whether the run is waiting for a person's approval or answer, which no stall guard may cut short. */
|
|
69
|
+
readonly waiting?: boolean;
|
|
58
70
|
}
|
|
59
71
|
/** Called with every persisted event, and the run's record as it is after that event. */
|
|
60
72
|
export type RunListener = (event: RunEvent, run: RunRecord | null) => void;
|
|
@@ -211,6 +211,9 @@ export class RunRegistry {
|
|
|
211
211
|
get finished() {
|
|
212
212
|
return finished;
|
|
213
213
|
},
|
|
214
|
+
get waiting() {
|
|
215
|
+
return waitingStatus() !== 'running';
|
|
216
|
+
},
|
|
214
217
|
interrupt: async (stop) => {
|
|
215
218
|
// Release approvals/questions before SDK abort waits for their blocked tools.
|
|
216
219
|
run.finish('interrupted', undefined, stop?.reason, stop?.code ?? 'user_abort');
|
|
@@ -18,6 +18,12 @@ export interface TurnParams {
|
|
|
18
18
|
/** Child runs may not gain tools registered by extensions outside their snapshot. */
|
|
19
19
|
restrictTools?: boolean;
|
|
20
20
|
settle?: () => Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* A live session (ADR-0083): called instead of disposing the Pi session when the run settles, so
|
|
23
|
+
* the next run of the conversation can use it again. `close` asks to end the session instead,
|
|
24
|
+
* because the turn stalled and the session cannot be trusted.
|
|
25
|
+
*/
|
|
26
|
+
release?: (close?: boolean) => void;
|
|
21
27
|
cwd: string;
|
|
22
28
|
/** Canonical project root pinned to this run. */
|
|
23
29
|
workspaceRoot: string;
|
|
@@ -26,8 +32,10 @@ export interface TurnParams {
|
|
|
26
32
|
/** A top-level attended plan run: one hidden repair turn if the reply lacks its plan block. */
|
|
27
33
|
repairPlan?: boolean;
|
|
28
34
|
sessionDir: string;
|
|
29
|
-
/** The native history to open, or `null` to
|
|
35
|
+
/** The native history to open, or `null` to continue the folder's newest one or start one. */
|
|
30
36
|
historyFile: string | null;
|
|
37
|
+
/** Start a new history even when the folder has one (a new conversation). */
|
|
38
|
+
newHistory?: boolean;
|
|
31
39
|
model: string | null;
|
|
32
40
|
effort?: Effort | null;
|
|
33
41
|
text: string;
|
|
@@ -78,6 +86,11 @@ export interface TurnParams {
|
|
|
78
86
|
system: string;
|
|
79
87
|
/** Changed state, saved as a hidden message in native history before the request. */
|
|
80
88
|
update: ContextUpdate | null;
|
|
89
|
+
/** Other hidden messages saved before the request (`SessionRunInput.hiddenNotes`). */
|
|
90
|
+
hiddenNotes?: Array<{
|
|
91
|
+
customType: string;
|
|
92
|
+
content: string;
|
|
93
|
+
}>;
|
|
81
94
|
/** Run a session command instead of a prompt: compact, navigate with a summary, or export. */
|
|
82
95
|
command?: SessionCommand;
|
|
83
96
|
/** The work slice this reply is bounded by (PROP-0041); the guard updates it as the reply runs. */
|
|
@@ -230,6 +243,14 @@ export interface PiSessionLike {
|
|
|
230
243
|
cancelled: boolean;
|
|
231
244
|
}>;
|
|
232
245
|
exportToHtml?(outputPath?: string): Promise<string>;
|
|
246
|
+
/** The native history file the session writes, once it has one. */
|
|
247
|
+
readonly sessionFile?: string;
|
|
248
|
+
/**
|
|
249
|
+
* Points an open session at the next run of its conversation (ADR-0083): that run's gate, tools,
|
|
250
|
+
* extension host and run-scoped extensions replace the previous run's. Only a session the factory
|
|
251
|
+
* built for reuse has it; the caller checks that the run's pins equal the session's first.
|
|
252
|
+
*/
|
|
253
|
+
rebind?(input: SessionFactoryInput): Promise<void>;
|
|
233
254
|
}
|
|
234
255
|
export interface ToolGate {
|
|
235
256
|
/** Resolves to a block reason, or null to let the call run. */
|
|
@@ -242,7 +263,7 @@ export interface ToolGate {
|
|
|
242
263
|
/** Makes these tools ask before each call. Used for tools an extension brought, which no one switched on by name. */
|
|
243
264
|
ask?(toolNames: readonly string[]): void;
|
|
244
265
|
}
|
|
245
|
-
export
|
|
266
|
+
export interface SessionFactoryInput {
|
|
246
267
|
params: TurnParams;
|
|
247
268
|
/** Pi's own tools and the custom tools switched on for this turn, in order. */
|
|
248
269
|
allow: string[];
|
|
@@ -252,7 +273,8 @@ export type SessionFactory = (input: {
|
|
|
252
273
|
runtime: () => Promise<ModelRuntime>;
|
|
253
274
|
/** Where a loaded extension's dialogs and notices go. Without it extensions have no interface. */
|
|
254
275
|
extensions?: ExtensionHost;
|
|
255
|
-
}
|
|
276
|
+
}
|
|
277
|
+
export type SessionFactory = (input: SessionFactoryInput) => Promise<PiSessionLike>;
|
|
256
278
|
/** A one-line description of a tool call, for an approval request or the activity list. */
|
|
257
279
|
export declare function summarize(input: unknown, toolName?: string): string;
|
|
258
280
|
/** How much text a tool handed back to the model: what its result adds to every later request. */
|