@a-t-h-i/bot-lobby 0.6.53 → 0.6.55
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/README.md +1 -1
- package/package.json +1 -1
- package/prompts/expert.md +17 -0
- package/prompts/goal-check.md +44 -0
- package/prompts/master.md +70 -32
- package/prompts/worker.md +25 -11
- package/src/desk/board-client.ts +65 -0
- package/src/desk/board.ts +200 -0
- package/src/desk/client-extension.ts +31 -1
- package/src/desk/ipc.ts +16 -4
- package/src/desk/session.ts +129 -1
- package/src/execution/agent-runner.ts +27 -132
- package/src/execution/delegation.ts +112 -38
- package/src/execution/live-run.ts +3 -14
- package/src/execution/pi-runner.ts +30 -163
- package/src/execution/worker-trees.ts +396 -0
- package/src/index.ts +6 -0
- package/src/lobby/feed.ts +1 -1
- package/src/lobby/host.ts +2 -0
- package/src/lobby/planner.ts +2 -2
- package/src/lobby/pr-review.ts +3 -3
- package/src/lobby/quickfix.ts +4 -3
- package/src/lobby/recovery.ts +2 -10
- package/src/lobby/runtime.ts +1 -1
- package/src/lobby/service.ts +3 -1
- package/src/master/goal-check.ts +114 -0
- package/src/master/master.ts +60 -24
- package/src/master/research.ts +3 -6
- package/src/pi/commands.ts +5 -45
- package/src/pi/delegation.ts +23 -4
- package/src/pi/events.ts +6 -19
- package/src/pi/grants.ts +89 -0
- package/src/pi/model-settings.ts +2 -4
- package/src/pi/model-support.ts +12 -5
- package/src/pi/route.ts +1 -1
- package/src/pi/run-summary.ts +3 -7
- package/src/pi/start-flags.ts +3 -3
- package/src/pi/start-task.ts +2 -8
- package/src/pi/tools.ts +10 -12
- package/src/roles/worker.ts +0 -18
- package/src/schemas/configuration.ts +81 -39
- package/src/schemas/findings.ts +0 -12
- package/src/schemas/task.ts +12 -1
- package/src/state/metrics.ts +1 -1
- package/src/state/work-clock.ts +140 -0
- package/src/state/work-time.ts +4 -7
- package/src/webui/api/index.ts +2 -1
- package/src/webui/api/settings.ts +6 -18
- package/src/webui/dev/fake-service.ts +14 -0
- package/src/webui/protocol.ts +23 -0
- package/src/workflow/workflow.ts +204 -344
- package/webui/dist/assets/{TerminalView-CbRc77C8.js → TerminalView-CIfjkb5H.js} +1 -1
- package/webui/dist/assets/index-BkXhtB6A.css +2 -0
- package/webui/dist/assets/index-DIM2-FUQ.js +114 -0
- package/webui/dist/build.json +10 -6
- package/webui/dist/index.html +2 -2
- package/src/state/budget.ts +0 -301
- package/src/state/step-limits.ts +0 -41
- package/webui/dist/assets/index-Cca5uEVs.js +0 -115
- package/webui/dist/assets/index-DsVdXmh5.css +0 -2
|
@@ -8,6 +8,7 @@ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { basename, join } from "node:path";
|
|
10
10
|
import { activityWord } from "../pi/activity.ts";
|
|
11
|
+
import { skillFiles } from "../pi/grants.ts";
|
|
11
12
|
import { shortDuration } from "../text.ts";
|
|
12
13
|
import { readRelayRequest } from "../ask/relay.ts";
|
|
13
14
|
import type { AskQuestion, AskResult } from "../ask/types.ts";
|
|
@@ -29,7 +30,9 @@ export interface PiRunOptions {
|
|
|
29
30
|
effortProfile?: RunProfileLike;
|
|
30
31
|
agent?: { runId: string; taskId: string; domain: Domain; role: Role; depth?: number };
|
|
31
32
|
mcpTools?: string[];
|
|
32
|
-
delegationQuota?: { used: number };
|
|
33
|
+
delegationQuota?: { used: number; consults?: number };
|
|
34
|
+
/** The model and thinking ask_expert consults on, resolved by the oracle (its session model when settings name none). */
|
|
35
|
+
expert?: { model?: string; thinking: string };
|
|
33
36
|
onChildRun?: (run: AgentRun) => void;
|
|
34
37
|
cwd: string;
|
|
35
38
|
task: string;
|
|
@@ -37,7 +40,8 @@ export interface PiRunOptions {
|
|
|
37
40
|
tools?: readonly string[];
|
|
38
41
|
model?: string;
|
|
39
42
|
thinking?: string;
|
|
40
|
-
|
|
43
|
+
/** Names of the skills this agent may load; absent keeps pi's own discovery. */
|
|
44
|
+
skills?: string[];
|
|
41
45
|
signal?: AbortSignal;
|
|
42
46
|
/** Reports the current one-word tool activity while the agent works. */
|
|
43
47
|
onActivity?: (activity: string) => void;
|
|
@@ -51,52 +55,21 @@ export interface PiRunOptions {
|
|
|
51
55
|
stallTimeoutMs?: number;
|
|
52
56
|
/** Silence allowed while one tool call is in flight (tests, builds); 0 disables. */
|
|
53
57
|
toolStallTimeoutMs?: number;
|
|
54
|
-
/** Ask the agent to wrap up and report at this elapsed time; 0 disables. */
|
|
55
|
-
wrapUpAtMs?: number;
|
|
56
58
|
/** Receives the steering handle once the process is running. */
|
|
57
59
|
onStart?: (handle: RunHandle) => void;
|
|
58
|
-
/** Under a task time budget: when the agent is warned, when its time is up, and who decides on more (see ProcessRunOptions). */
|
|
59
|
-
time?: TimeLimit;
|
|
60
60
|
/** Answers the agent's relayed questions; without it they are cancelled like any other dialog. */
|
|
61
61
|
onAsk?: RelayAsk;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
/**
|
|
65
|
-
* A run's allotted time under a task budget. At `headsUpAtMs` the agent is
|
|
66
|
-
* told how much is left; at `upAtMs` it is steered to stop and report where it
|
|
67
|
-
* left off. Once that report is in (its turn ended), `onTimeUp` decides: more
|
|
68
|
-
* ms keeps the same process going with its context (a follow-up prompt), 0
|
|
69
|
-
* ends it. The hard deadline (`timeoutMs`) and the stall watchdog wait while
|
|
70
|
-
* the decision is pending, and move out with any time granted.
|
|
71
|
-
*/
|
|
72
|
-
export interface TimeLimit {
|
|
73
|
-
/** Reporting time is included in the allowance, including extensions. */
|
|
74
|
-
hardLimit?: boolean;
|
|
75
|
-
upAtMs: number;
|
|
76
|
-
headsUpAtMs?: number;
|
|
77
|
-
headsUpMessage?: string;
|
|
78
|
-
upMessage: string;
|
|
79
|
-
onTimeUp?: (report: string) => Promise<{ extraMs: number; message?: string }>;
|
|
80
|
-
/** Time the agent gets to write its report after `upAtMs` before it is stopped. */
|
|
81
|
-
graceMs: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
64
|
export interface ProcessOutcome {
|
|
85
65
|
exitCode: number;
|
|
86
66
|
stdout: string;
|
|
87
67
|
stderr: string;
|
|
88
68
|
killed: boolean;
|
|
89
|
-
timedOut: boolean;
|
|
90
69
|
/** Killed by the watchdog after going silent. */
|
|
91
70
|
stalled?: boolean;
|
|
92
|
-
/** The agent was asked to wrap up before its deadline. */
|
|
93
|
-
wrappedUp?: boolean;
|
|
94
71
|
/** The RPC host rejected the prompt itself. */
|
|
95
72
|
protocolError?: string;
|
|
96
|
-
/** Stopped at its allotted time and not given more: its report says where it left off. */
|
|
97
|
-
timeUp?: boolean;
|
|
98
|
-
/** Time granted past its allotment, in ms. */
|
|
99
|
-
extendedMs?: number;
|
|
100
73
|
}
|
|
101
74
|
|
|
102
75
|
/** Streaming agent events forwarded as they arrive on stdout. */
|
|
@@ -111,14 +84,9 @@ export type PiStreamEvent =
|
|
|
111
84
|
| { type: "compaction" }
|
|
112
85
|
| { type: "compaction_end"; error?: string }
|
|
113
86
|
| { type: "usage"; input: number; output: number; cost: number; model?: string }
|
|
114
|
-
| { type: "wrap_up" }
|
|
115
|
-
/** Its allotted time is up: it was asked to stop and report where it left off. */
|
|
116
|
-
| { type: "time_up" }
|
|
117
|
-
/** It was given `ms` more and carries on. */
|
|
118
|
-
| { type: "extended"; ms: number }
|
|
119
87
|
/** One finished thinking block, bounded to `MAX_THOUGHT_CHARS`. */
|
|
120
88
|
| { type: "thought"; text: string }
|
|
121
|
-
/** It asked the user (relayed);
|
|
89
|
+
/** It asked the user (relayed); the stall watchdog waits until the answers are back. */
|
|
122
90
|
| { type: "asking"; questions: number }
|
|
123
91
|
/** The answers went back after `waitedMs` of waiting on the user. */
|
|
124
92
|
| { type: "answered"; waitedMs: number }
|
|
@@ -130,7 +98,6 @@ export const MAX_THOUGHT_CHARS = 1500;
|
|
|
130
98
|
export interface ProcessRunOptions {
|
|
131
99
|
cwd: string;
|
|
132
100
|
signal?: AbortSignal;
|
|
133
|
-
timeoutMs: number;
|
|
134
101
|
/** The task message sent to the agent over RPC stdin. */
|
|
135
102
|
prompt?: string;
|
|
136
103
|
/** Optional sink for streaming agent events. */
|
|
@@ -138,12 +105,7 @@ export interface ProcessRunOptions {
|
|
|
138
105
|
env?: Record<string, string>;
|
|
139
106
|
stallTimeoutMs?: number;
|
|
140
107
|
toolStallTimeoutMs?: number;
|
|
141
|
-
wrapUpAtMs?: number;
|
|
142
|
-
wrapUpMessage?: string;
|
|
143
108
|
onStart?: (handle: RunHandle) => void;
|
|
144
|
-
/** Grace between the deadline abort and the hard kill. */
|
|
145
|
-
graceMs?: number;
|
|
146
|
-
time?: TimeLimit;
|
|
147
109
|
onAsk?: RelayAsk;
|
|
148
110
|
}
|
|
149
111
|
|
|
@@ -160,9 +122,6 @@ export interface PiRunResult {
|
|
|
160
122
|
usage: { input: number; output: number; cost: number; turns: number };
|
|
161
123
|
model?: string;
|
|
162
124
|
stalled?: boolean;
|
|
163
|
-
wrappedUp?: boolean;
|
|
164
|
-
timeUp?: boolean;
|
|
165
|
-
extendedMs?: number;
|
|
166
125
|
}
|
|
167
126
|
|
|
168
127
|
export interface ParsedStream {
|
|
@@ -177,17 +136,8 @@ const MAX_STREAM_CHARS = 5_000_000;
|
|
|
177
136
|
const HEARTBEAT_MS = 2000;
|
|
178
137
|
const SETTLE_FALLBACK_MS = 3000;
|
|
179
138
|
const EXIT_DRAIN_MS = 1500;
|
|
180
|
-
const DEADLINE_GRACE_MS = 5000;
|
|
181
139
|
const DIALOG_METHODS = new Set(["select", "confirm", "input", "editor"]);
|
|
182
140
|
|
|
183
|
-
/** The steering message sent at the wrap-up point. */
|
|
184
|
-
export const WRAP_UP_MESSAGE = [
|
|
185
|
-
"bot-lobby: you are close to your time limit.",
|
|
186
|
-
"Stop exploring now. Finish or revert any half-done edit so every file is consistent,",
|
|
187
|
-
"then write your final report in the required output format.",
|
|
188
|
-
"List anything unfinished under Blockers or Notes.",
|
|
189
|
-
].join(" ");
|
|
190
|
-
|
|
191
141
|
/** RPC protocol lines the process host reacts to (never forwarded to the caller). */
|
|
192
142
|
export interface ControlEvent {
|
|
193
143
|
type: string;
|
|
@@ -366,10 +316,15 @@ export function finishedThought(line: string): string | undefined {
|
|
|
366
316
|
export const MCP_RESOURCE_TOOLS: readonly string[] = ["list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"];
|
|
367
317
|
|
|
368
318
|
/** Build the `pi` argv for one isolated, headless RPC agent run; the task goes over stdin. */
|
|
369
|
-
export function buildPiArgs(options: Omit<PiRunOptions, "task"> & { systemPromptFile?: string }): string[] {
|
|
319
|
+
export function buildPiArgs(options: Omit<PiRunOptions, "task"> & { systemPromptFile?: string; skillFiles?: string[] }): string[] {
|
|
370
320
|
const args = ["--mode", "rpc", "--no-session", "--no-prompt-templates", "--no-themes", "--extension", fileURLToPath(new URL("../index.ts", import.meta.url))];
|
|
371
321
|
if (options.model && options.model !== "inherit") args.push("--model", options.model);
|
|
372
322
|
if (options.thinking) args.push("--thinking", options.thinking);
|
|
323
|
+
// A skill grant replaces pi's own discovery with exactly the granted skills.
|
|
324
|
+
if (options.skillFiles) {
|
|
325
|
+
args.push("--no-skills");
|
|
326
|
+
for (const file of options.skillFiles) args.push("--skill", file);
|
|
327
|
+
}
|
|
373
328
|
if (options.tools && options.tools.length > 0) {
|
|
374
329
|
// pi keeps every MCP tool under --tools unless an entry names one, so name the granted ones (and the resource
|
|
375
330
|
// tools their servers need), or run without MCP at all when nothing is granted.
|
|
@@ -502,19 +457,11 @@ function rpcInput(proc: ChildProcess): { send(command: object): void; close(): v
|
|
|
502
457
|
|
|
503
458
|
interface RunState {
|
|
504
459
|
killed: boolean;
|
|
505
|
-
timedOut: boolean;
|
|
506
460
|
stalled: boolean;
|
|
507
|
-
wrappedUp: boolean;
|
|
508
461
|
settled: boolean;
|
|
509
462
|
protocolError?: string;
|
|
510
|
-
/**
|
|
511
|
-
timeUp: boolean;
|
|
512
|
-
headsUp: boolean;
|
|
513
|
-
/** Waiting on the decision: the agent is idle, so the deadline and the stall watchdog wait too. */
|
|
514
|
-
deciding: boolean;
|
|
515
|
-
/** Relayed questions waiting on the user: every clock and the watchdog wait. */
|
|
463
|
+
/** Relayed questions waiting on the user: the watchdog waits. */
|
|
516
464
|
asking: number;
|
|
517
|
-
extendedMs: number;
|
|
518
465
|
}
|
|
519
466
|
|
|
520
467
|
/** Smallest positive limit, used to pick a watchdog cadence that can see it. */
|
|
@@ -526,11 +473,11 @@ function watchInterval(...limits: Array<number | undefined>): number {
|
|
|
526
473
|
|
|
527
474
|
/**
|
|
528
475
|
* Default runner: spawn pi in RPC mode, send the task over stdin, and watch it.
|
|
529
|
-
* The child leads its own process group so a stall
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
476
|
+
* The child leads its own process group so a stall or cancel takes its
|
|
477
|
+
* grandchildren (dev servers, watch-mode tests) with it, and the run ends on
|
|
478
|
+
* process exit rather than waiting for a pipe a grandchild may hold open.
|
|
479
|
+
* There is no time limit: a run ends when the agent finishes, is cancelled,
|
|
480
|
+
* or goes silent for so long it is killed as stalled.
|
|
534
481
|
*/
|
|
535
482
|
export function spawnPiProcess(args: string[], options: ProcessRunOptions): Promise<ProcessOutcome> {
|
|
536
483
|
return new Promise((resolve) => {
|
|
@@ -545,25 +492,16 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
545
492
|
windowsHide: true,
|
|
546
493
|
stdio: ["pipe", "pipe", "pipe"],
|
|
547
494
|
});
|
|
548
|
-
const state: RunState = { killed: false,
|
|
495
|
+
const state: RunState = { killed: false, stalled: false, settled: false, asking: 0 };
|
|
549
496
|
const input = rpcInput(proc);
|
|
550
|
-
|
|
551
|
-
const time = options.time;
|
|
552
|
-
// The allotted time counts from here; a grant restarts it.
|
|
553
|
-
let clockStart = started;
|
|
554
|
-
let upAtMs = time?.upAtMs ?? 0;
|
|
555
|
-
let headsUpAtMs = time?.headsUpAtMs ?? 0;
|
|
556
|
-
let lastOutput = started;
|
|
497
|
+
let lastOutput = Date.now();
|
|
557
498
|
let quietGraceUntil = 0;
|
|
558
499
|
let toolsInFlight = 0;
|
|
559
500
|
let stderr = "";
|
|
560
501
|
let exitCode: number | undefined;
|
|
561
502
|
let settleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
562
503
|
let done = false;
|
|
563
|
-
// Time spent waiting on the user's answers is not the agent's: its limits move out by it.
|
|
564
|
-
let deadlineAt = started + options.timeoutMs;
|
|
565
504
|
let askStarted = 0;
|
|
566
|
-
let pausedMs = 0;
|
|
567
505
|
const asks = new AbortController();
|
|
568
506
|
|
|
569
507
|
const onEvent = (event: PiStreamEvent) => {
|
|
@@ -572,51 +510,18 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
572
510
|
if (event.type === "retry") quietGraceUntil = Date.now() + event.delayMs;
|
|
573
511
|
options.onEvent?.(event);
|
|
574
512
|
};
|
|
575
|
-
//
|
|
513
|
+
// The run ends once its turn is over.
|
|
576
514
|
let turnDone = false;
|
|
577
515
|
const turnOver = () => {
|
|
578
516
|
if (turnDone) return;
|
|
579
517
|
turnDone = true;
|
|
580
518
|
clearTimeout(settleTimer);
|
|
581
519
|
settleTimer = undefined;
|
|
582
|
-
|
|
583
|
-
else input.close();
|
|
584
|
-
};
|
|
585
|
-
const decide = (onTimeUp: NonNullable<TimeLimit["onTimeUp"]>) => {
|
|
586
|
-
state.deciding = true;
|
|
587
|
-
clearTimeout(deadline);
|
|
588
|
-
const report = parsePiStream(collector.peek()).text;
|
|
589
|
-
onTimeUp(report).then(({ extraMs, message }) => {
|
|
590
|
-
if (done || state.killed) return;
|
|
591
|
-
state.deciding = false;
|
|
592
|
-
if (extraMs <= 0) return input.close();
|
|
593
|
-
// Granted: the same agent carries on where it stopped, its context intact.
|
|
594
|
-
state.timeUp = false;
|
|
595
|
-
state.headsUp = false;
|
|
596
|
-
state.settled = false;
|
|
597
|
-
state.extendedMs += extraMs;
|
|
598
|
-
turnDone = false;
|
|
599
|
-
clockStart = Date.now();
|
|
600
|
-
lastOutput = clockStart;
|
|
601
|
-
upAtMs = time?.hardLimit ? Math.max(1, extraMs - Math.min(time.graceMs, Math.floor(extraMs / 4))) : extraMs;
|
|
602
|
-
// The user just named the time it has: no heads-up quoting the old allotment.
|
|
603
|
-
headsUpAtMs = 0;
|
|
604
|
-
const nextLimit = extraMs + (time?.hardLimit ? 0 : time?.graceMs ?? 0);
|
|
605
|
-
deadlineAt = Date.now() + nextLimit;
|
|
606
|
-
deadline = setTimeout(onDeadline, nextLimit);
|
|
607
|
-
input.send({ type: "prompt", message: message ?? "You have more time: carry on from where you left off and finish." });
|
|
608
|
-
options.onEvent?.({ type: "extended", ms: extraMs });
|
|
609
|
-
}, () => {
|
|
610
|
-
state.deciding = false;
|
|
611
|
-
input.close();
|
|
612
|
-
});
|
|
520
|
+
input.close();
|
|
613
521
|
};
|
|
614
522
|
const relay = (id: string | undefined, questions: AskQuestion[], ask: RelayAsk) => {
|
|
615
523
|
state.asking += 1;
|
|
616
|
-
if (state.asking === 1)
|
|
617
|
-
askStarted = Date.now();
|
|
618
|
-
clearTimeout(deadline);
|
|
619
|
-
}
|
|
524
|
+
if (state.asking === 1) askStarted = Date.now();
|
|
620
525
|
options.onEvent?.({ type: "asking", questions: questions.length });
|
|
621
526
|
ask(questions, asks.signal)
|
|
622
527
|
.catch((): AskResult => ({ answers: [], cancelled: true }))
|
|
@@ -626,11 +531,7 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
626
531
|
if (state.asking === 0) {
|
|
627
532
|
const now = Date.now();
|
|
628
533
|
const waited = now - askStarted;
|
|
629
|
-
pausedMs += waited;
|
|
630
|
-
deadlineAt += waited;
|
|
631
|
-
clockStart += waited;
|
|
632
534
|
lastOutput = now;
|
|
633
|
-
if (!state.deciding) deadline = setTimeout(onDeadline, Math.max(0, deadlineAt - now));
|
|
634
535
|
options.onEvent?.({ type: "answered", waitedMs: waited });
|
|
635
536
|
}
|
|
636
537
|
input.send({ type: "extension_ui_response", id, value: JSON.stringify(result) });
|
|
@@ -673,7 +574,6 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
673
574
|
const finish = (code: number) => {
|
|
674
575
|
if (done) return;
|
|
675
576
|
done = true;
|
|
676
|
-
clearTimeout(deadline);
|
|
677
577
|
clearTimeout(settleTimer);
|
|
678
578
|
clearInterval(watch);
|
|
679
579
|
// Questions still open when the run ends are put away.
|
|
@@ -682,9 +582,8 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
682
582
|
input.close();
|
|
683
583
|
// Reap anything the agent left running in its group.
|
|
684
584
|
if (POSIX) signalGroup(proc, "SIGTERM");
|
|
685
|
-
const { killed,
|
|
686
|
-
|
|
687
|
-
resolve({ exitCode: code, stdout: collector.finish(), stderr, killed, timedOut, stalled, wrappedUp, protocolError, ...(timeUp ? { timeUp } : {}), ...(state.extendedMs > 0 ? { extendedMs: state.extendedMs } : {}) });
|
|
585
|
+
const { killed, stalled, protocolError } = state;
|
|
586
|
+
resolve({ exitCode: code, stdout: collector.finish(), stderr, killed, stalled, protocolError });
|
|
688
587
|
};
|
|
689
588
|
const onAbort = () => {
|
|
690
589
|
state.killed = true;
|
|
@@ -692,31 +591,9 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
692
591
|
input.close();
|
|
693
592
|
terminate(proc, 3000);
|
|
694
593
|
};
|
|
695
|
-
const onDeadline = () => {
|
|
696
|
-
state.killed = true;
|
|
697
|
-
state.timedOut = true;
|
|
698
|
-
input.send({ type: "abort" });
|
|
699
|
-
input.close();
|
|
700
|
-
setTimeout(() => terminate(proc), options.graceMs ?? DEADLINE_GRACE_MS).unref();
|
|
701
|
-
};
|
|
702
|
-
let deadline = setTimeout(onDeadline, options.timeoutMs);
|
|
703
594
|
const watch = setInterval(() => {
|
|
704
|
-
if (state.killed || done || state.
|
|
595
|
+
if (state.killed || done || state.asking > 0) return;
|
|
705
596
|
const now = Date.now();
|
|
706
|
-
if (!state.wrappedUp && !state.settled && options.wrapUpAtMs && options.wrapUpAtMs > 0 && now - started - pausedMs >= options.wrapUpAtMs) {
|
|
707
|
-
state.wrappedUp = true;
|
|
708
|
-
input.send({ type: "steer", message: options.wrapUpMessage ?? WRAP_UP_MESSAGE });
|
|
709
|
-
options.onEvent?.({ type: "wrap_up" });
|
|
710
|
-
}
|
|
711
|
-
if (time && !state.headsUp && !state.timeUp && !turnDone && headsUpAtMs > 0 && now - clockStart >= headsUpAtMs) {
|
|
712
|
-
state.headsUp = true;
|
|
713
|
-
if (time.headsUpMessage) input.send({ type: "steer", message: time.headsUpMessage });
|
|
714
|
-
}
|
|
715
|
-
if (time && !state.timeUp && !turnDone && upAtMs > 0 && now - clockStart >= upAtMs) {
|
|
716
|
-
state.timeUp = true;
|
|
717
|
-
input.send({ type: "steer", message: time.upMessage });
|
|
718
|
-
options.onEvent?.({ type: "time_up" });
|
|
719
|
-
}
|
|
720
597
|
const limit = toolsInFlight > 0 ? options.toolStallTimeoutMs : options.stallTimeoutMs;
|
|
721
598
|
if (limit && limit > 0 && now - Math.max(lastOutput, quietGraceUntil) >= limit) {
|
|
722
599
|
state.killed = true;
|
|
@@ -724,7 +601,7 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
724
601
|
input.close();
|
|
725
602
|
terminate(proc, 3000);
|
|
726
603
|
}
|
|
727
|
-
}, watchInterval(options.stallTimeoutMs, options.toolStallTimeoutMs
|
|
604
|
+
}, watchInterval(options.stallTimeoutMs, options.toolStallTimeoutMs));
|
|
728
605
|
watch.unref();
|
|
729
606
|
|
|
730
607
|
proc.on("exit", (code, signal) => {
|
|
@@ -745,15 +622,11 @@ export function spawnPiProcess(args: string[], options: ProcessRunOptions): Prom
|
|
|
745
622
|
}
|
|
746
623
|
|
|
747
624
|
function toResult(parsed: ParsedStream, outcome: ProcessOutcome, aborted: boolean, options: PiRunOptions): PiRunResult {
|
|
748
|
-
const base = { output: parsed.text, usage: parsed.usage, model: parsed.model
|
|
625
|
+
const base = { output: parsed.text, usage: parsed.usage, model: parsed.model };
|
|
749
626
|
if (outcome.stalled) {
|
|
750
627
|
const quiet = shortDuration(options.stallTimeoutMs ?? 0);
|
|
751
628
|
return { ...base, status: "timeout", stalled: true, error: `stalled: no output for ${quiet}` };
|
|
752
629
|
}
|
|
753
|
-
if (outcome.timedOut) {
|
|
754
|
-
const note = outcome.wrappedUp ? " after a wrap-up request" : "";
|
|
755
|
-
return { ...base, status: "timeout", error: `hit the ${shortDuration(options.timeoutMs)} time limit${note}` };
|
|
756
|
-
}
|
|
757
630
|
if (outcome.killed || aborted || parsed.stopReason === "aborted") {
|
|
758
631
|
return { ...base, status: "cancelled", error: "agent was cancelled" };
|
|
759
632
|
}
|
|
@@ -784,23 +657,17 @@ export async function runPiAgent(given: PiRunOptions, run: ProcessRunner = spawn
|
|
|
784
657
|
try {
|
|
785
658
|
prompt = writePromptFile([options.systemPrompt, host.note].filter(Boolean).join("\n\n"));
|
|
786
659
|
if (options.signal?.aborted) return { status: "cancelled", output: "", usage: { input: 0, output: 0, cost: 0, turns: 0 } };
|
|
787
|
-
const args = buildPiArgs({ ...options, tools: host.tools, mcpTools: host.mcpTools, systemPromptFile: prompt.file });
|
|
660
|
+
const args = buildPiArgs({ ...options, tools: host.tools, mcpTools: host.mcpTools, systemPromptFile: prompt.file, ...(options.skills ? { skillFiles: skillFiles(options.skills, options.cwd) } : {}) });
|
|
788
661
|
const outcome = await run(args, {
|
|
789
662
|
cwd: options.cwd,
|
|
790
663
|
signal: options.signal,
|
|
791
|
-
timeoutMs: options.timeoutMs,
|
|
792
664
|
prompt: `Task: ${options.task}`,
|
|
793
665
|
env: host.env,
|
|
794
666
|
stallTimeoutMs: options.stallTimeoutMs,
|
|
795
667
|
toolStallTimeoutMs: options.toolStallTimeoutMs,
|
|
796
|
-
wrapUpAtMs: options.wrapUpAtMs,
|
|
797
|
-
...(options.time ? { time: options.time } : {}),
|
|
798
668
|
...(options.onAsk ? { onAsk: options.onAsk } : {}),
|
|
799
669
|
onStart: host.onStart,
|
|
800
670
|
onEvent: (event) => {
|
|
801
|
-
if (event.type === "asking") host.pause?.();
|
|
802
|
-
if (event.type === "answered") host.extend?.(event.waitedMs);
|
|
803
|
-
if (event.type === "extended") host.extend?.(event.ms, true);
|
|
804
671
|
if (event.type === "tool_execution_start") options.onActivity?.(activityWord(event.toolName));
|
|
805
672
|
options.onEvent?.(event);
|
|
806
673
|
},
|