@fitchmultz/pi-subagents 0.50.0 → 0.52.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 +63 -2
- package/README.md +36 -5
- package/dist/agent/child-runtime.js +80 -0
- package/dist/agent/contracts.js +15 -24
- package/dist/agent/host.js +3 -16
- package/dist/agent/index.js +212 -192
- package/dist/agent/journal-tail.js +115 -156
- package/dist/agent/launch.js +20 -8
- package/dist/agent/legacy.js +65 -23
- package/dist/agent/notifications.js +138 -47
- package/dist/agent/observation-state.js +246 -0
- package/dist/agent/observation-view.js +258 -0
- package/dist/agent/observation.js +350 -0
- package/dist/agent/observe-card.js +243 -0
- package/dist/agent/observe-chrome.js +85 -0
- package/dist/agent/observe-client.js +285 -0
- package/dist/agent/observe-layout.js +65 -0
- package/dist/agent/observe-reading.js +84 -0
- package/dist/agent/observe-screen.js +400 -96
- package/dist/agent/observe-transcript.js +382 -0
- package/dist/agent/observe.js +250 -43
- package/dist/agent/parking.js +253 -0
- package/dist/agent/process.js +228 -28
- package/dist/agent/receipts.js +92 -20
- package/dist/agent/runner.js +229 -126
- package/dist/agent/shell.js +2 -1
- package/dist/agent/storage.js +47 -12
- package/dist/agent/tool-schemas.js +20 -0
- package/dist/agent/typebox.js +10 -5
- package/dist/agent/usage.js +0 -115
- package/dist/agent/view.js +46 -16
- package/dist/agent/wake.js +99 -0
- package/dist/build-info.js +1 -1
- package/dist/intercom-client.d.ts +26 -0
- package/dist/intercom-client.js +45 -0
- package/dist/pi-intercom/agent-dir.d.ts +1 -0
- package/dist/pi-intercom/bridge-session.js +2 -1
- package/dist/pi-intercom/broker/broker.js +62 -43
- package/dist/pi-intercom/broker/client-last.d.ts +24 -0
- package/dist/pi-intercom/broker/client-last.js +42 -5
- package/dist/pi-intercom/broker/client-park.d.ts +9 -0
- package/dist/pi-intercom/broker/client-park.js +21 -0
- package/dist/pi-intercom/broker/client.d.ts +59 -0
- package/dist/pi-intercom/broker/client.js +55 -29
- package/dist/pi-intercom/broker/connection.d.ts +29 -0
- package/dist/pi-intercom/broker/framing.d.ts +17 -0
- package/dist/pi-intercom/broker/framing.js +10 -7
- package/dist/pi-intercom/broker/guard.js +23 -0
- package/dist/pi-intercom/broker/last.d.ts +30 -0
- package/dist/pi-intercom/broker/last.js +87 -40
- package/dist/pi-intercom/broker/parked.js +107 -0
- package/dist/pi-intercom/broker/paths.d.ts +13 -0
- package/dist/pi-intercom/broker/paths.js +35 -12
- package/dist/pi-intercom/broker/pid.js +5 -1
- package/dist/pi-intercom/broker/server-connection.js +20 -1
- package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
- package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
- package/dist/pi-intercom/broker/targets.d.ts +9 -0
- package/dist/pi-intercom/connection.js +71 -20
- package/dist/pi-intercom/identity.js +6 -5
- package/dist/pi-intercom/inbound-delivery.js +16 -60
- package/dist/pi-intercom/inbound-journal.js +83 -6
- package/dist/pi-intercom/inbound-record.js +18 -1
- package/dist/pi-intercom/inbound.js +22 -37
- package/dist/pi-intercom/index.js +41 -0
- package/dist/pi-intercom/last-message.js +4 -16
- package/dist/pi-intercom/lifecycle.js +88 -3
- package/dist/pi-intercom/outbound.js +5 -15
- package/dist/pi-intercom/reply-tracker.js +7 -6
- package/dist/pi-intercom/reply-wait.js +2 -2
- package/dist/pi-intercom/session-targets.d.ts +24 -0
- package/dist/pi-intercom/tool-registration.js +4 -1
- package/dist/pi-intercom/tool-results.js +5 -7
- package/dist/pi-intercom/tool-schemas.js +12 -26
- package/dist/pi-intercom/types.d.ts +212 -0
- package/dist/pi-intercom/types.js +54 -3
- package/dist/pi-intercom/validation.d.ts +12 -0
- package/dist/pi-intercom/validation.js +3 -0
- package/dist/shared/bounded-json.js +151 -0
- package/dist/shared/compact-cards.js +66 -0
- package/dist/shared/journal-frames.js +3 -6
- package/dist/shared/json-projection.js +267 -129
- package/dist/shared/native-journal.js +123 -56
- package/dist/shared/parking.js +12 -0
- package/dist/shared/post-exit-stdio-guard.js +120 -7
- package/dist/shared/tui-action.js +29 -0
- package/dist/shared/unknown.d.ts +9 -0
- package/docs/code-quality.md +5 -3
- package/docs/development.md +42 -3
- package/docs/intercom-bridge.md +9 -4
- package/docs/intercom.md +228 -7
- package/docs/performance.md +118 -14
- package/docs/reference.md +168 -20
- package/docs/retired-history-maintenance.md +252 -0
- package/package.json +28 -15
- package/scripts/archive-retired-history.mjs +464 -0
- package/scripts/build.mjs +48 -28
- package/scripts/measure-size.mjs +1 -1
- package/scripts/native-package-smoke.mjs +153 -12
- package/scripts/package-smoke.mjs +80 -11
- package/scripts/prepare.mjs +2 -2
- package/scripts/real-pi-live.mjs +377 -0
- package/scripts/real-pi-processes.mjs +6 -1
- package/scripts/real-pi-smoke.mjs +6 -282
- package/scripts/run-tests.mjs +4 -2
- package/scripts/typescript.mjs +35 -0
- package/skills/pi-intercom/SKILL.md +2 -1
- package/skills/pi-subagents/SKILL.md +23 -5
- package/src/agent/child-runtime.ts +105 -0
- package/src/agent/contracts.ts +18 -34
- package/src/agent/host.ts +39 -20
- package/src/agent/index.ts +234 -207
- package/src/agent/journal-tail.ts +141 -153
- package/src/agent/launch.ts +33 -9
- package/src/agent/legacy.ts +85 -31
- package/src/agent/notifications.ts +192 -67
- package/src/agent/observation-state.ts +277 -0
- package/src/agent/observation-view.ts +267 -0
- package/src/agent/observation.ts +409 -0
- package/src/agent/observe-card.ts +304 -0
- package/src/agent/observe-chrome.ts +170 -0
- package/src/agent/observe-client.ts +318 -0
- package/src/agent/observe-layout.ts +79 -0
- package/src/agent/observe-reading.ts +97 -0
- package/src/agent/observe-screen.ts +443 -129
- package/src/agent/observe-transcript.ts +434 -0
- package/src/agent/observe.ts +304 -71
- package/src/agent/parking.ts +341 -0
- package/src/agent/process.ts +280 -37
- package/src/agent/receipts.ts +114 -24
- package/src/agent/runner.ts +227 -149
- package/src/agent/shell.ts +2 -1
- package/src/agent/storage.ts +44 -11
- package/src/agent/tool-schemas.ts +33 -0
- package/src/agent/typebox.ts +12 -7
- package/src/agent/usage.ts +0 -130
- package/src/agent/view.ts +70 -20
- package/src/agent/wake.ts +107 -0
- package/src/intercom-client.ts +108 -0
- package/src/pi-intercom/bridge-session.ts +2 -2
- package/src/pi-intercom/broker/broker.ts +64 -53
- package/src/pi-intercom/broker/client-last.ts +65 -12
- package/src/pi-intercom/broker/client-park.ts +23 -0
- package/src/pi-intercom/broker/client.ts +63 -45
- package/src/pi-intercom/broker/framing.ts +11 -8
- package/src/pi-intercom/broker/guard.ts +33 -0
- package/src/pi-intercom/broker/last.ts +131 -43
- package/src/pi-intercom/broker/parked.ts +130 -0
- package/src/pi-intercom/broker/paths.ts +39 -12
- package/src/pi-intercom/broker/pid.ts +6 -1
- package/src/pi-intercom/broker/server-connection.ts +27 -2
- package/src/pi-intercom/connection.ts +103 -39
- package/src/pi-intercom/identity.ts +8 -5
- package/src/pi-intercom/inbound-delivery.ts +20 -66
- package/src/pi-intercom/inbound-journal.ts +103 -7
- package/src/pi-intercom/inbound-record.ts +21 -4
- package/src/pi-intercom/inbound.ts +32 -49
- package/src/pi-intercom/index.ts +44 -0
- package/src/pi-intercom/last-message.ts +11 -20
- package/src/pi-intercom/lifecycle.ts +117 -14
- package/src/pi-intercom/outbound.ts +7 -16
- package/src/pi-intercom/reply-tracker.ts +8 -6
- package/src/pi-intercom/reply-wait.ts +3 -2
- package/src/pi-intercom/runtime-types.ts +0 -1
- package/src/pi-intercom/tool-registration.ts +4 -1
- package/src/pi-intercom/tool-results.ts +10 -9
- package/src/pi-intercom/tool-schemas.ts +12 -44
- package/src/pi-intercom/types.ts +115 -4
- package/src/pi-intercom/validation.ts +4 -0
- package/src/shared/bounded-json.ts +162 -0
- package/src/shared/compact-cards.ts +118 -0
- package/src/shared/journal-frames.ts +3 -19
- package/src/shared/json-projection.ts +267 -147
- package/src/shared/native-journal.ts +157 -75
- package/src/shared/parking.ts +22 -0
- package/src/shared/post-exit-stdio-guard.ts +165 -7
- package/src/shared/tui-action.ts +39 -0
- package/tsconfig.client.json +8 -0
- package/tsconfig.json +1 -0
- package/src/shared/stream-json.d.ts +0 -10
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
existsSync,
|
|
5
|
-
mkdirSync,
|
|
6
|
-
mkdtempSync,
|
|
7
|
-
readFileSync,
|
|
8
|
-
rmSync,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
} from "node:fs";
|
|
11
|
-
import { dirname, join, resolve, relative, isAbsolute } from "node:path";
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
12
4
|
import process from "node:process";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { SmokeProcesses, filesIn, processRef } from "./real-pi-processes.mjs";
|
|
5
|
+
import { runLiveSmoke } from "./real-pi-live.mjs";
|
|
6
|
+
import { SmokeProcesses } from "./real-pi-processes.mjs";
|
|
16
7
|
|
|
17
8
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
18
|
-
const authAgentDir =
|
|
19
|
-
process.env.PI_REAL_SMOKE_AUTH_AGENT_DIR ??
|
|
20
|
-
(process.env.HOME ? join(process.env.HOME, ".pi", "agent") : undefined);
|
|
21
9
|
|
|
22
10
|
function usage() {
|
|
23
11
|
console.log(
|
|
24
|
-
`Usage: node scripts/real-pi-smoke.mjs [--llm] [--llm-full] [--keep-temp] [--timeout-ms <ms>]\n\nRuns an opt-in real Pi package smoke for this checkout. It uses an isolated\ntemporary Pi home, installs the single pi-subagents package with bundled intercom, verifies pi list,\nand loads the single compiled entry. It does not install pi-fitch-kit, publish anything,\nor create GitHub Actions.\n\nOptions:\n --llm Also run live model-backed
|
|
12
|
+
`Usage: node scripts/real-pi-smoke.mjs [--llm] [--llm-full] [--keep-temp] [--timeout-ms <ms>]\n\nRuns an opt-in real Pi package smoke for this checkout. It uses an isolated\ntemporary Pi home, installs the single pi-subagents package with bundled intercom, verifies pi list,\nand loads the single compiled entry. It does not install pi-fitch-kit, publish anything,\nor create GitHub Actions.\n\nOptions:\n --llm Also run live model-backed intercom, list, and delegate prompts in a persistent RPC main\n that stays open until each launch receipt's completion notification resumes it\n --llm-full Also verify forked completion context and saved-conversation continuation\n --keep-temp Keep the isolated temporary home for debugging\n --timeout-ms <ms> Per-command timeout in milliseconds (default: ${DEFAULT_TIMEOUT_MS})\n -h, --help Show this help\n\nEnvironment:\n PI_REAL_SMOKE_AUTH_AGENT_DIR Source Pi agent dir for auth.json/models.json during --llm (default: ~/.pi/agent)\n PI_REAL_SMOKE_MODEL Model passed to live --llm smoke prompts, e.g. openai/gpt-6-astra\n PI_REAL_SMOKE_PROVIDER Provider passed to live --llm smoke prompts\n\nExit codes:\n 0 real Pi smoke passed\n 1 install/list/live smoke failed\n 2 invalid arguments`,
|
|
25
13
|
);
|
|
26
14
|
}
|
|
27
15
|
|
|
@@ -75,23 +63,6 @@ function parseArgs(argv) {
|
|
|
75
63
|
return options;
|
|
76
64
|
}
|
|
77
65
|
|
|
78
|
-
function copyLiveAuth(agentDir) {
|
|
79
|
-
if (!authAgentDir || !existsSync(authAgentDir)) {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
mkdirSync(agentDir, { recursive: true });
|
|
83
|
-
const copied = [];
|
|
84
|
-
for (const filename of ["auth.json", "models.json"]) {
|
|
85
|
-
const source = join(authAgentDir, filename);
|
|
86
|
-
if (!existsSync(source)) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
copyFileSync(source, join(agentDir, filename));
|
|
90
|
-
copied.push(filename);
|
|
91
|
-
}
|
|
92
|
-
return copied;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
66
|
function isolatedEnv(root, agentDir) {
|
|
96
67
|
const home = join(root, "home");
|
|
97
68
|
const env = { ...process.env };
|
|
@@ -176,253 +147,6 @@ function registeredCommands(response) {
|
|
|
176
147
|
: [];
|
|
177
148
|
}
|
|
178
149
|
|
|
179
|
-
async function runLivePrompt(label, prompt, options, expectedTool) {
|
|
180
|
-
const args = [
|
|
181
|
-
"--print",
|
|
182
|
-
"--mode",
|
|
183
|
-
"json",
|
|
184
|
-
"--session-dir",
|
|
185
|
-
join(options.root, "sessions"),
|
|
186
|
-
"--approve",
|
|
187
|
-
];
|
|
188
|
-
if (process.env.PI_REAL_SMOKE_PROVIDER) {
|
|
189
|
-
args.push("--provider", process.env.PI_REAL_SMOKE_PROVIDER);
|
|
190
|
-
}
|
|
191
|
-
if (process.env.PI_REAL_SMOKE_MODEL) {
|
|
192
|
-
args.push(
|
|
193
|
-
"--model",
|
|
194
|
-
process.env.PI_REAL_SMOKE_MODEL,
|
|
195
|
-
"--models",
|
|
196
|
-
process.env.PI_REAL_SMOKE_MODEL,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
args.push(prompt);
|
|
200
|
-
const output = await runPi(label, args, options);
|
|
201
|
-
writeFileSync(join(options.root, `${label.replace(/ /g, "-")}.jsonl`), output);
|
|
202
|
-
const events = output.split("\n").flatMap((line) => {
|
|
203
|
-
try {
|
|
204
|
-
return [JSON.parse(line)];
|
|
205
|
-
} catch {
|
|
206
|
-
return [];
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
if (
|
|
210
|
-
!events.some(
|
|
211
|
-
(event) => event.type === "tool_execution_start" && event.toolName === expectedTool,
|
|
212
|
-
)
|
|
213
|
-
) {
|
|
214
|
-
throw new Error(
|
|
215
|
-
`${label} never invoked ${expectedTool}; a matching final sentence is not execution evidence.`,
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
const errors = events.filter((event) => event.type === "tool_execution_end" && event.isError);
|
|
219
|
-
if (errors.length) {
|
|
220
|
-
throw new Error(
|
|
221
|
-
`${label} had ${errors.length} failed tool call(s); inspect its saved wire log.`,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
if (!events.some((event) => event.type === "agent_settled")) {
|
|
225
|
-
throw new Error(`${label} did not reach agent_settled.`);
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
events,
|
|
229
|
-
text: events
|
|
230
|
-
.filter((event) => event.type === "message_end" && event.message?.role === "assistant")
|
|
231
|
-
.flatMap((event) =>
|
|
232
|
-
event.message.content.filter((part) => part.type === "text").map((part) => part.text),
|
|
233
|
-
)
|
|
234
|
-
.join("\n"),
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function* savedAssistantModels(file) {
|
|
239
|
-
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
240
|
-
if (!line.trim()) {
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
const entry = JSON.parse(line);
|
|
244
|
-
if (entry.type === "message" && entry.message?.role === "assistant") {
|
|
245
|
-
yield `${entry.message.provider}/${entry.message.model}`;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function auditSavedModels(directories, expectedModel) {
|
|
251
|
-
const models = new Set();
|
|
252
|
-
let responses = 0;
|
|
253
|
-
for (const file of directories.flatMap((dir) => [...filesIn(dir)])) {
|
|
254
|
-
if (!file.endsWith(".jsonl")) {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
for (const model of savedAssistantModels(file)) {
|
|
258
|
-
responses++;
|
|
259
|
-
models.add(model);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (!responses) {
|
|
263
|
-
throw new Error("No saved assistant messages found for model audit.");
|
|
264
|
-
}
|
|
265
|
-
if (expectedModel?.includes("/") && [...models].some((model) => model !== expectedModel)) {
|
|
266
|
-
throw new Error(`Unexpected model in saved parent/child sessions: ${[...models].join(", ")}`);
|
|
267
|
-
}
|
|
268
|
-
console.log(
|
|
269
|
-
`[real-pi-smoke] audited ${responses} saved parent/child responses: ${[...models].join(", ")}`,
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function requireOutput(label, output, pattern) {
|
|
274
|
-
if (!pattern.test(output)) {
|
|
275
|
-
throw new Error(`${label} did not include expected evidence ${pattern}.\nOutput:\n${output}`);
|
|
276
|
-
}
|
|
277
|
-
const compact = output.trim().split(/\r?\n/).slice(-8).join("\n");
|
|
278
|
-
console.log(`[real-pi-smoke] ${label} output evidence:\n${compact}`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function asyncCompletion(dir, runId) {
|
|
282
|
-
const resultPath = join(dir, "result.json");
|
|
283
|
-
if (!existsSync(resultPath)) {
|
|
284
|
-
return { status: "missing" };
|
|
285
|
-
}
|
|
286
|
-
const result = JSON.parse(readFileSync(resultPath, "utf8"));
|
|
287
|
-
if (result.id !== runId) {
|
|
288
|
-
throw new Error("Async completion belongs to another saved run");
|
|
289
|
-
}
|
|
290
|
-
if (result.status !== "completed") {
|
|
291
|
-
throw new Error(`async run ${runId} ended ${result.status}: ${result.error ?? ""}`);
|
|
292
|
-
}
|
|
293
|
-
const processPath = join(dir, "actor.lock", "process.json");
|
|
294
|
-
if (!existsSync(processPath)) {
|
|
295
|
-
return { status: "completed; actor exit unconfirmed" };
|
|
296
|
-
}
|
|
297
|
-
const actor = JSON.parse(readFileSync(processPath, "utf8"));
|
|
298
|
-
return {
|
|
299
|
-
status: result.status,
|
|
300
|
-
output: isChildTreeAlive(processRef(actor.pid))
|
|
301
|
-
? undefined
|
|
302
|
-
: readFileSync(join(dir, "output.txt"), "utf8"),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async function waitForAsyncCompletion(run, pattern, { env, timeoutMs, signal }) {
|
|
307
|
-
const dir = dirname(run.sessionFile);
|
|
308
|
-
const within = relative(join(env.PI_CODING_AGENT_DIR, "sessions", "agent-runs"), dir);
|
|
309
|
-
if (isAbsolute(within) || within.startsWith("..") || !/^[a-zA-Z0-9_-]{1,128}$/.test(run.id)) {
|
|
310
|
-
throw new Error("Async receipt did not identify this smoke's private saved run");
|
|
311
|
-
}
|
|
312
|
-
const deadline = Date.now() + timeoutMs;
|
|
313
|
-
let lastState = "missing";
|
|
314
|
-
while (Date.now() < deadline) {
|
|
315
|
-
signal.throwIfAborted();
|
|
316
|
-
const completion = asyncCompletion(dir, run.id);
|
|
317
|
-
lastState = completion.status;
|
|
318
|
-
if (completion.output !== undefined) {
|
|
319
|
-
requireOutput(`async run ${run.id} completed and actor exited`, completion.output, pattern);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
// Result publication is not process exit; retain resources until final actor writes finish.
|
|
323
|
-
// oxlint-disable-next-line no-await-in-loop
|
|
324
|
-
await delay(100, undefined, { signal });
|
|
325
|
-
}
|
|
326
|
-
throw new Error(
|
|
327
|
-
`async run ${run.id} did not complete and exit after ${timeoutMs}ms (last state: ${lastState}).`,
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function runLiveSmoke(options, runOptions, agentDir) {
|
|
332
|
-
const copiedAuthFiles = copyLiveAuth(agentDir);
|
|
333
|
-
if (copiedAuthFiles.length > 0) {
|
|
334
|
-
console.log(
|
|
335
|
-
`[real-pi-smoke] copied ${copiedAuthFiles.join(" and ")} into isolated Pi agent dir for live provider auth`,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
const childModelInstruction = process.env.PI_REAL_SMOKE_MODEL
|
|
339
|
-
? ` Pass model '${process.env.PI_REAL_SMOKE_MODEL}' to every delegate call.`
|
|
340
|
-
: "";
|
|
341
|
-
await runBasicLiveSmoke(runOptions, childModelInstruction);
|
|
342
|
-
if (options.llmFull) {
|
|
343
|
-
await runFullLiveSmoke(runOptions, childModelInstruction);
|
|
344
|
-
}
|
|
345
|
-
auditSavedModels(
|
|
346
|
-
[join(runOptions.root, "sessions"), join(agentDir, "sessions", "agent-runs")],
|
|
347
|
-
process.env.PI_REAL_SMOKE_MODEL,
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function toolDetails(events, name) {
|
|
352
|
-
return events
|
|
353
|
-
.filter((event) => event.type === "tool_execution_end" && event.toolName === name)
|
|
354
|
-
.map((event) => event.result?.details);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function requireCompleted(run, expected) {
|
|
358
|
-
if (run?.status !== "completed" || run.output?.text !== expected) {
|
|
359
|
-
throw new Error(`Native child did not complete with expected output: ${expected}`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async function runBasicLiveSmoke(runOptions, childModelInstruction) {
|
|
364
|
-
const prompts = [
|
|
365
|
-
[
|
|
366
|
-
"intercom",
|
|
367
|
-
"Call the intercom tool with action status. Reply exactly 'real-pi-smoke intercom ok' only if it reports 'Connected: Yes'.",
|
|
368
|
-
"intercom",
|
|
369
|
-
],
|
|
370
|
-
[
|
|
371
|
-
"list",
|
|
372
|
-
"Call agent_runs with action list. Reply exactly 'real-pi-smoke list ok' only if it successfully returns a saved-run list. There are no profiles to discover.",
|
|
373
|
-
"agent_runs",
|
|
374
|
-
],
|
|
375
|
-
[
|
|
376
|
-
"foreground",
|
|
377
|
-
`Call delegate with prompt 'Reply exactly: real-pi-smoke foreground ok', name 'smoke-foreground', and async false.${childModelInstruction} Reply exactly 'real-pi-smoke foreground ok' only if the actual child result contains it.`,
|
|
378
|
-
"delegate",
|
|
379
|
-
],
|
|
380
|
-
];
|
|
381
|
-
for (const [kind, prompt, tool] of prompts) {
|
|
382
|
-
// Keep separate native parent sessions so each smoke owns its saved runs.
|
|
383
|
-
// oxlint-disable-next-line no-await-in-loop
|
|
384
|
-
const result = await runLivePrompt(`real Pi ${kind} prompt`, prompt, runOptions, tool);
|
|
385
|
-
if (kind === "foreground") {
|
|
386
|
-
requireCompleted(toolDetails(result.events, "delegate")[0], "real-pi-smoke foreground ok");
|
|
387
|
-
}
|
|
388
|
-
requireOutput(`real Pi ${kind} prompt`, result.text, new RegExp(`real-pi-smoke ${kind} ok`));
|
|
389
|
-
}
|
|
390
|
-
const result = await runLivePrompt(
|
|
391
|
-
"real Pi async prompt",
|
|
392
|
-
`Call delegate with prompt 'Reply exactly: real-pi-smoke async ok', name 'smoke-async', and async true.${childModelInstruction} Do not wait or call agent_runs. Reply exactly 'real-pi-smoke async launched ok' after receiving a launch receipt, not claiming completion.`,
|
|
393
|
-
runOptions,
|
|
394
|
-
"delegate",
|
|
395
|
-
);
|
|
396
|
-
requireOutput("real Pi async prompt", result.text, /real-pi-smoke async launched ok/);
|
|
397
|
-
const receipt = toolDetails(result.events, "delegate")[0];
|
|
398
|
-
if (receipt?.receipt !== "launch" || !receipt.id || !receipt.sessionFile) {
|
|
399
|
-
throw new Error("Native delegate did not return a saved asynchronous launch receipt");
|
|
400
|
-
}
|
|
401
|
-
await waitForAsyncCompletion(receipt, /^real-pi-smoke async ok$/m, runOptions);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
async function runFullLiveSmoke(runOptions, childModelInstruction) {
|
|
405
|
-
const prompt = `First call delegate with prompt 'Reply exactly CALIBRATION_FACT=packed-otter-7319', name 'fact', context fresh and async false.${childModelInstruction} Then call delegate with context fork, async false, and prompt 'Read the preceding delegate tool result in inherited conversation. Without tools, filesystem reads, or guessing, return the CALIBRATION_FACT line exactly; return FACT_NOT_VISIBLE if absent.' Finally call agent_runs with action continue, the first saved run id, async false, and prompt 'Without tools, repeat the CALIBRATION_FACT line from your own saved conversation exactly.' Reply exactly 'real-pi-smoke fork ok' only if both the forked child and continued child actually return CALIBRATION_FACT=packed-otter-7319. There are no profiles, task prefixes, output settings, or canned role instructions.`;
|
|
406
|
-
const result = await runLivePrompt("real Pi fork prompt", prompt, runOptions, "delegate");
|
|
407
|
-
const delegates = toolDetails(result.events, "delegate");
|
|
408
|
-
const continuation = toolDetails(result.events, "agent_runs")[0];
|
|
409
|
-
for (const run of [...delegates, continuation]) {
|
|
410
|
-
requireCompleted(run, "CALIBRATION_FACT=packed-otter-7319");
|
|
411
|
-
}
|
|
412
|
-
if (
|
|
413
|
-
delegates.length !== 2 ||
|
|
414
|
-
delegates[0].context !== "fresh" ||
|
|
415
|
-
delegates[1].context !== "fork" ||
|
|
416
|
-
continuation.predecessor !== delegates[0].id ||
|
|
417
|
-
continuation.sessionFile !== delegates[0].sessionFile
|
|
418
|
-
) {
|
|
419
|
-
throw new Error(
|
|
420
|
-
"Fork/continuation smoke did not execute the two contexts and reuse the first saved conversation",
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
requireOutput("real Pi fork prompt", result.text, /real-pi-smoke fork ok/);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
150
|
function createProject(root) {
|
|
427
151
|
const projectRoot = join(root, "project");
|
|
428
152
|
mkdirSync(projectRoot, { recursive: true });
|
|
@@ -479,7 +203,7 @@ async function main() {
|
|
|
479
203
|
);
|
|
480
204
|
if (!options.llm) {
|
|
481
205
|
console.log(
|
|
482
|
-
"[real-pi-smoke] live model subagent prompts skipped; pass --llm to exercise
|
|
206
|
+
"[real-pi-smoke] live model subagent prompts skipped; pass --llm to exercise launch receipts and completion notifications.",
|
|
483
207
|
);
|
|
484
208
|
}
|
|
485
209
|
} finally {
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
4
|
-
import { availableParallelism } from "node:os";
|
|
4
|
+
import { availableParallelism, tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
|
|
7
7
|
// ponytail: integration is sized for the 3-vCPU macOS hosted runner, where the suite takes
|
|
@@ -91,7 +91,9 @@ function sanitizedEnv() {
|
|
|
91
91
|
function runNodeTest(label, files, timeoutMs, concurrency) {
|
|
92
92
|
const args = [...(concurrency ? [`--test-concurrency=${concurrency}`] : []), "--test", ...files];
|
|
93
93
|
const startedAt = Date.now();
|
|
94
|
-
const tempRoot = mkdtempSync(
|
|
94
|
+
const tempRoot = mkdtempSync(
|
|
95
|
+
join(process.platform === "android" ? tmpdir() : "/tmp", "pi-subagents-"),
|
|
96
|
+
);
|
|
95
97
|
const env = sanitizedEnv();
|
|
96
98
|
env.PI_SUBAGENT_TEMP_ROOT = tempRoot;
|
|
97
99
|
env.PI_INTERCOM_RUNTIME_DIR = join(tempRoot, "ic");
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Android's npm compiler package is not published; use its installed native compiler.
|
|
8
|
+
* @param {string} [script]
|
|
9
|
+
* @returns {{ command: string, args: string[] }}
|
|
10
|
+
*/
|
|
11
|
+
export function compilerInvocation(script) {
|
|
12
|
+
if (script === undefined && process.platform === "android") {
|
|
13
|
+
return { command: "tsgo", args: [] };
|
|
14
|
+
}
|
|
15
|
+
const compiler = script ?? join(process.cwd(), "node_modules", "typescript", "bin", "tsc");
|
|
16
|
+
if (!existsSync(compiler)) {
|
|
17
|
+
throw new Error(`typescript is not installed at ${compiler}; run npm install first.`);
|
|
18
|
+
}
|
|
19
|
+
return { command: process.execPath, args: [compiler] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (import.meta.main) {
|
|
23
|
+
const compiler = compilerInvocation();
|
|
24
|
+
const result = spawnSync(compiler.command, [...compiler.args, ...process.argv.slice(2)], {
|
|
25
|
+
stdio: "inherit",
|
|
26
|
+
});
|
|
27
|
+
if (result.error) {
|
|
28
|
+
console.error(
|
|
29
|
+
process.platform === "android"
|
|
30
|
+
? `Cannot run native TypeScript: install an Android build of tsgo on PATH. ${result.error.message}`
|
|
31
|
+
: result.error.message,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
process.exitCode = result.status ?? 1;
|
|
35
|
+
}
|
|
@@ -56,4 +56,5 @@ persistent supervisor question/approval workflow.
|
|
|
56
56
|
Old agent-directory brokers may coexist during migration; updated sessions use
|
|
57
57
|
the same-user broker. Do not kill another broker or change runtime configuration
|
|
58
58
|
to force discovery. Only intercom-connected eligible sessions appear, not all
|
|
59
|
-
open Pi processes. See
|
|
59
|
+
open Pi processes. See [local messaging](../../docs/intercom.md) for broker and
|
|
60
|
+
persistence contracts.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pi-subagents
|
|
3
|
-
description: "Delegate one complete assignment to a native Pi child; inspect, continue,
|
|
3
|
+
description: "Delegate one complete assignment to a native Pi child; inspect, continue, or stop saved child runs. Use for Pi delegation, not profile administration or non-Pi agents."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Pi delegation
|
|
@@ -25,19 +25,26 @@ Fresh context is default; `context:"fork"` preserves the native conversation,
|
|
|
25
25
|
including prior tool results. A forked child still cannot delegate. Optional
|
|
26
26
|
`cwd` and `model` are explicit launch overrides; do not change global auth/model
|
|
27
27
|
settings to implement an override.
|
|
28
|
+
`model:"provider/model:thinking"` uses Pi's native suffix resolver when the host
|
|
29
|
+
exports it; a literal model ID wins before suffix parsing. Without that host API,
|
|
30
|
+
use a literal ID with inherited thinking or upgrade Pi. Never retry another model
|
|
31
|
+
silently.
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
Every launch, continuation and guidance call returns a receipt; no tool waits for
|
|
34
|
+
child completion. Continue useful independent work; when child evidence gates the
|
|
35
|
+
next step, end the turn and let the completion notice resume you rather than
|
|
36
|
+
polling `inspect`. A launch receipt is not completion. Review actual outputs and
|
|
32
37
|
validation evidence before accepting the result.
|
|
33
38
|
|
|
39
|
+
Keep the main session running for completion delivery. A one-shot print
|
|
40
|
+
invocation exits after its final turn and stops any remaining children.
|
|
41
|
+
|
|
34
42
|
## Controls
|
|
35
43
|
|
|
36
44
|
- `agent_runs({action:"list"})` discovers this native main's saved runs.
|
|
37
45
|
- `inspect` reads a run by ID; follow returned offsets for complete output.
|
|
38
46
|
- `continue` takes a complete follow-up `prompt` and keeps the saved child chat.
|
|
39
47
|
Active work receives guidance; completed work gets an immutable successor.
|
|
40
|
-
- `wait` observes existing work; cancelling that wait does not stop the producer.
|
|
41
48
|
- `stop` requests termination. Inspect actual process evidence; request acceptance
|
|
42
49
|
does not prove actor or command exit.
|
|
43
50
|
|
|
@@ -45,6 +52,15 @@ Use IDs returned by the tools. Runs belong to their native main session, not a
|
|
|
45
52
|
cwd, project, name, or forked copy of its transcript. Resume the same main for
|
|
46
53
|
ownership. Do not delete ambiguous launch state to force another attempt.
|
|
47
54
|
|
|
55
|
+
Escape aborts the parent's reply, not already-launched children; `stop` ends one
|
|
56
|
+
run. Children terminate when their parent exits. Resume the same parent and explicitly
|
|
57
|
+
continue a saved child conversation; the child inspects the interrupted work
|
|
58
|
+
rather than automatically replaying tool calls. Ordinary `/reload` is not exit.
|
|
59
|
+
Under a fork launcher that provides `ctx.managedHost`, `pi restart` or `/restart`
|
|
60
|
+
is not exit either: children keep running and report to the ready replacement.
|
|
61
|
+
That needs one full CLI relaunch after upgrading; a worker under an older loaded
|
|
62
|
+
launcher still stops children when it exits.
|
|
63
|
+
|
|
48
64
|
## Coordination and delivery
|
|
49
65
|
|
|
50
66
|
All pairs inside a main/children group can use `intercom` list/send/last. Across
|
|
@@ -60,3 +76,5 @@ proof; a child-reported pass is evidence to inspect, not automatic sign-off.
|
|
|
60
76
|
Normal child completion returns through the run, not a redundant intercom
|
|
61
77
|
message. Use direct `send` for guidance, corrections, or blockers that affect live
|
|
62
78
|
work. Ordinary `ask` is bounded request/reply, not durable supervisor revival.
|
|
79
|
+
The [pi-intercom skill](../pi-intercom/SKILL.md) covers messaging operations.
|
|
80
|
+
See [tools and lifecycle](../../docs/reference.md) for the owning contracts.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import type {
|
|
3
|
+
CreateAgentSessionRuntimeFactory,
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionContext,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { Launch } from "./contracts.ts";
|
|
8
|
+
import type { loadSDK, WorkingState } from "./host.ts";
|
|
9
|
+
import { shellCommand } from "./process.ts";
|
|
10
|
+
import { localCwdOwner } from "./cwd.ts";
|
|
11
|
+
import type { ToolEvent } from "./observation-state.ts";
|
|
12
|
+
|
|
13
|
+
function guard(pi: ExtensionAPI, directory: string, token: string): void {
|
|
14
|
+
pi.on("tool_call", (event) => {
|
|
15
|
+
if (event.toolName === "delegate" || event.toolName === "agent_runs") {
|
|
16
|
+
return { block: true, reason: "Only a main session may delegate or control children." };
|
|
17
|
+
}
|
|
18
|
+
const owner = pi.getAllTools().find((tool) => tool.name === "bash")?.sourceInfo;
|
|
19
|
+
if (
|
|
20
|
+
event.toolName === "bash" &&
|
|
21
|
+
typeof event.input.command === "string" &&
|
|
22
|
+
owner &&
|
|
23
|
+
(owner.source === "builtin" || localCwdOwner(owner))
|
|
24
|
+
) {
|
|
25
|
+
// Preserve the selected local owner's cwd/env and leave remote/custom tools alone.
|
|
26
|
+
const input = event.input;
|
|
27
|
+
input.command = shellCommand(event.input.command, directory, token);
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
interface ChildHooks {
|
|
33
|
+
readonly directory: string;
|
|
34
|
+
readonly token: string;
|
|
35
|
+
/** Exact parked working state; absent means the saved journal alone. */
|
|
36
|
+
readonly saved: WorkingState | undefined;
|
|
37
|
+
readonly started: (pi: ExtensionAPI, ctx: ExtensionContext) => void;
|
|
38
|
+
readonly tool: (event: ToolEvent) => void;
|
|
39
|
+
}
|
|
40
|
+
/** One native child session with this package's child policy and live-view hooks. */
|
|
41
|
+
export function childRuntime(
|
|
42
|
+
sdk: Awaited<ReturnType<typeof loadSDK>>,
|
|
43
|
+
launch: Launch,
|
|
44
|
+
hooks: ChildHooks,
|
|
45
|
+
): CreateAgentSessionRuntimeFactory {
|
|
46
|
+
return async ({ sessionManager }) => {
|
|
47
|
+
// Exact restore keeps settings, tool policy and accepted queues; it never replays input.
|
|
48
|
+
const restore = hooks.saved ? { workingSession: hooks.saved } : {};
|
|
49
|
+
if (!fs.existsSync(launch.sessionFile)) {
|
|
50
|
+
throw new Error("Missing saved native session; refusing to create another.");
|
|
51
|
+
}
|
|
52
|
+
const settingsManager = sdk.SettingsManager.create(launch.cwd, launch.agentDir, {
|
|
53
|
+
projectTrusted: launch.trusted,
|
|
54
|
+
});
|
|
55
|
+
const services = await sdk.createAgentSessionServices({
|
|
56
|
+
...restore,
|
|
57
|
+
cwd: launch.cwd,
|
|
58
|
+
agentDir: launch.agentDir,
|
|
59
|
+
settingsManager,
|
|
60
|
+
resourceLoaderOptions: {
|
|
61
|
+
additionalExtensionPaths: [launch.entry],
|
|
62
|
+
extensionFactories: [
|
|
63
|
+
{
|
|
64
|
+
name: "codemode",
|
|
65
|
+
builtin: true,
|
|
66
|
+
factory: sdk.createCodemodeExtension({ mode: "on" }),
|
|
67
|
+
},
|
|
68
|
+
{ name: "tool_search", builtin: true, factory: sdk.createToolSearchExtension() },
|
|
69
|
+
{ name: "mcp", builtin: true, factory: sdk.createMcpExtension() },
|
|
70
|
+
(pi) => {
|
|
71
|
+
guard(pi, hooks.directory, hooks.token);
|
|
72
|
+
pi.on("session_start", (_event, ctx) => {
|
|
73
|
+
hooks.started(pi, ctx);
|
|
74
|
+
});
|
|
75
|
+
// Sparse tool lifecycle keeps the live view's tools even while nobody watches.
|
|
76
|
+
pi.on("tool_execution_start", hooks.tool);
|
|
77
|
+
pi.on("tool_execution_update", hooks.tool);
|
|
78
|
+
pi.on("tool_execution_end", hooks.tool);
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const diagnostics = services.resourceLoader.getExtensions().errors;
|
|
84
|
+
if (diagnostics.length > 0) {
|
|
85
|
+
throw new Error(`Child extension load failed: ${JSON.stringify(diagnostics)}`);
|
|
86
|
+
}
|
|
87
|
+
const model = services.modelRuntime.getModel(launch.provider, launch.model);
|
|
88
|
+
if (!model) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Saved model ${launch.provider}/${launch.model} is unavailable; no fallback was used.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
...(await sdk.createAgentSessionFromServices({
|
|
95
|
+
...restore,
|
|
96
|
+
services,
|
|
97
|
+
sessionManager,
|
|
98
|
+
model,
|
|
99
|
+
thinkingLevel: launch.thinking,
|
|
100
|
+
})),
|
|
101
|
+
services,
|
|
102
|
+
diagnostics: services.diagnostics,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
}
|
package/src/agent/contracts.ts
CHANGED
|
@@ -36,11 +36,17 @@ export const processSchema = Type.Object({
|
|
|
36
36
|
pid: Type.Integer({ minimum: 1 }),
|
|
37
37
|
identity: Type.String(),
|
|
38
38
|
token: Type.String(),
|
|
39
|
+
clock: Type.Optional(
|
|
40
|
+
Type.Object({
|
|
41
|
+
boot: Type.String(),
|
|
42
|
+
namespace: Type.String(),
|
|
43
|
+
at: Type.String({ pattern: "^[1-9][0-9]*$", maxLength: 20 }),
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
39
46
|
});
|
|
40
47
|
export type ProcessIdentity = Static<typeof processSchema>;
|
|
41
48
|
export const attachmentSchema = Type.Object({
|
|
42
|
-
|
|
43
|
-
identity: Type.String(),
|
|
49
|
+
...Type.Pick(processSchema, ["pid", "identity", "clock"]).properties,
|
|
44
50
|
generation: Type.String(),
|
|
45
51
|
attached: Type.Boolean(),
|
|
46
52
|
});
|
|
@@ -97,6 +103,16 @@ export const commandSchema = Type.Object({
|
|
|
97
103
|
});
|
|
98
104
|
export type Command = Static<typeof commandSchema>;
|
|
99
105
|
export const successorSchema = Type.Object({ id: Type.String() });
|
|
106
|
+
/** Authority to restore exact working state, valid only after the named actor has exited. */
|
|
107
|
+
/** Exact parked authority: one exited generation's state bytes for this session and leaf. */
|
|
108
|
+
export const parkedSchema = Type.Object({
|
|
109
|
+
token: Type.String(),
|
|
110
|
+
state: Type.String(),
|
|
111
|
+
digest: Type.String(),
|
|
112
|
+
sessionId: Type.String(),
|
|
113
|
+
leafId: Type.Union([Type.String(), Type.Null()]),
|
|
114
|
+
});
|
|
115
|
+
export type Parked = Static<typeof parkedSchema>;
|
|
100
116
|
export const exitSchema = Type.Object({
|
|
101
117
|
pid: Type.Integer(),
|
|
102
118
|
code: Type.Union([Type.Integer(), Type.Null()]),
|
|
@@ -110,35 +126,3 @@ export const admissionSchema = Type.Object({
|
|
|
110
126
|
state: Type.Optional(Type.Union([Type.Literal("accepted"), Type.Literal("stale")])),
|
|
111
127
|
at: Type.Number(),
|
|
112
128
|
});
|
|
113
|
-
export const delegateSchema = Type.Object(
|
|
114
|
-
{
|
|
115
|
-
prompt: Type.String({ minLength: 1 }),
|
|
116
|
-
name: Type.Optional(Type.String({ minLength: 1 })),
|
|
117
|
-
cwd: Type.Optional(Type.String({ minLength: 1 })),
|
|
118
|
-
model: Type.Optional(
|
|
119
|
-
Type.String({ minLength: 1, description: "Exact provider/model ID; no fallback." }),
|
|
120
|
-
),
|
|
121
|
-
context: Type.Optional(Type.Union([Type.Literal("fresh"), Type.Literal("fork")])),
|
|
122
|
-
async: Type.Optional(Type.Boolean({ default: true })),
|
|
123
|
-
},
|
|
124
|
-
{ additionalProperties: false },
|
|
125
|
-
);
|
|
126
|
-
export type DelegateInput = Static<typeof delegateSchema>;
|
|
127
|
-
export const runsSchema = Type.Object(
|
|
128
|
-
{
|
|
129
|
-
action: Type.Union([
|
|
130
|
-
Type.Literal("list"),
|
|
131
|
-
Type.Literal("inspect"),
|
|
132
|
-
Type.Literal("continue"),
|
|
133
|
-
Type.Literal("stop"),
|
|
134
|
-
Type.Literal("wait"),
|
|
135
|
-
]),
|
|
136
|
-
id: Type.Optional(Type.String()),
|
|
137
|
-
prompt: Type.Optional(Type.String({ minLength: 1 })),
|
|
138
|
-
async: Type.Optional(Type.Boolean({ default: true })),
|
|
139
|
-
offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
140
|
-
length: Type.Optional(Type.Integer({ minimum: 4, maximum: 262144 })),
|
|
141
|
-
},
|
|
142
|
-
{ additionalProperties: false },
|
|
143
|
-
);
|
|
144
|
-
export type RunsInput = Static<typeof runsSchema>;
|