@tshu-w/pi-agents 0.2.0 → 0.2.2
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/bin/pd.mjs +32 -35
- package/docs/HOSTS.md +50 -118
- package/docs/PROGRAM.md +32 -104
- package/docs/SPEC.md +91 -180
- package/package.json +1 -1
- package/src/agents/agents.ts +27 -26
- package/src/agents/registry.ts +33 -8
- package/src/agents/tool.ts +34 -11
- package/src/host/client.mjs +6 -1
- package/src/host/rpc-client.mjs +37 -17
- package/src/index.ts +9 -3
- package/src/output.ts +5 -1
- package/src/programs/codemode.ts +14 -0
- package/src/programs/execute.ts +49 -2
- package/src/programs/loadout.ts +18 -3
- package/src/programs/programs.ts +52 -11
- package/src/programs/scope.ts +5 -4
- package/src/programs/tool.ts +1 -1
- package/src/render-call.ts +113 -15
- package/src/roots/paths.mjs +4 -4
- package/src/ui/message.ts +24 -0
- package/src/ui/queue.ts +7 -6
- package/src/ui/viewer.ts +67 -31
- package/src/workbench.mjs +45 -33
package/bin/pd.mjs
CHANGED
|
@@ -7,12 +7,13 @@ import { createRequire } from 'node:module';
|
|
|
7
7
|
import { dirname, join, resolve } from 'node:path';
|
|
8
8
|
import { pathToFileURL } from 'node:url';
|
|
9
9
|
import { styleText } from 'node:util';
|
|
10
|
-
import { attach, settledHosts, startHost } from '../src/host/client.mjs';
|
|
10
|
+
import { attach, hostSessionFile, settledHosts, startHost } from '../src/host/client.mjs';
|
|
11
11
|
import { detachKey, detachMatcher } from '../src/host/keys.mjs';
|
|
12
12
|
import { discoverRoots, findRoot } from '../src/roots/discovery.mjs';
|
|
13
|
-
import {
|
|
13
|
+
import { defaultSessionDir, resolvePath, sessionDir, statePaths } from '../src/roots/paths.mjs';
|
|
14
14
|
import { piPackage } from '../src/pi.mjs';
|
|
15
15
|
import { markedFiles } from '../src/daemon/sessions.mjs';
|
|
16
|
+
import { argGroups } from '../src/host/rpc-client.mjs';
|
|
16
17
|
|
|
17
18
|
const real = process.env.PATH.split(':').map(dir => join(dir, 'pi')).find(file => {
|
|
18
19
|
try { accessSync(file, constants.X_OK); return true; } catch { return false; }
|
|
@@ -23,9 +24,13 @@ if (!real) {
|
|
|
23
24
|
}
|
|
24
25
|
const args = process.argv.slice(2);
|
|
25
26
|
const paths = statePaths();
|
|
27
|
+
const dist = file => import(pathToFileURL(join(piPackage(real), 'dist', file)).href);
|
|
28
|
+
// Pi's own parsing, so that `pd` reads every argument as `pi` does.
|
|
29
|
+
const { parseArgs } = await dist('cli/args.js');
|
|
30
|
+
const { ENV_SESSION_DIR, getAgentDir } = await dist('config.js');
|
|
31
|
+
const parsed = parseArgs(args);
|
|
26
32
|
|
|
27
33
|
const COMMANDS = new Set(['install', 'remove', 'uninstall', 'update', 'list', 'config', 'auth', 'mcp']);
|
|
28
|
-
const DIRECT_FLAGS = new Set(['-h', '--help', '-v', '--version', '--export', '--list-models', '--no-session']);
|
|
29
34
|
|
|
30
35
|
function runDirectly() {
|
|
31
36
|
const child = spawn(real, args, { stdio: 'inherit' });
|
|
@@ -44,20 +49,12 @@ function error(text) {
|
|
|
44
49
|
return styleText('red', text, { stream: process.stderr });
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
function option(name, alias) {
|
|
48
|
-
for (let i = 0; i < args.length; i++) {
|
|
49
|
-
if (args[i] === '--') return;
|
|
50
|
-
if (args[i] === name || args[i] === alias) return args[i + 1] ?? '';
|
|
51
|
-
if (args[i].startsWith(`${name}=`)) return args[i].slice(name.length + 1);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
52
|
|
|
56
53
|
/** Attaches the terminal to the host at `socket`; `send`, if given, sends messages once attached, and its error is reported once detached. */
|
|
57
54
|
async function attachAndReport(socket, send) {
|
|
58
55
|
const tui = await import(pathToFileURL(createRequire(join(piPackage(real), 'package.json')).resolve('@earendil-works/pi-tui')).href);
|
|
59
56
|
let sent;
|
|
60
|
-
const result = await attach(socket, { isDetach: detachMatcher(tui, detachKey(
|
|
57
|
+
const result = await attach(socket, { isDetach: detachMatcher(tui, detachKey(getAgentDir())), onAttach: () => { sent = send?.(); } });
|
|
61
58
|
const failure = await sent;
|
|
62
59
|
if (failure) process.stderr.write(`${error(failure)}\n`);
|
|
63
60
|
const id = result.session?.id;
|
|
@@ -74,31 +71,30 @@ async function attachAndReport(socket, send) {
|
|
|
74
71
|
/** The running host already holding the Session that `--session`, `--session-id`, or `-c` would open. */
|
|
75
72
|
async function runningTarget() {
|
|
76
73
|
let running;
|
|
77
|
-
const session =
|
|
78
|
-
const sessionId = option('--session-id');
|
|
74
|
+
const { session, sessionId } = parsed;
|
|
79
75
|
if (session) {
|
|
80
76
|
const file = resolve(session);
|
|
81
|
-
running = host => host.session && (host.session.id.startsWith(session) || host
|
|
82
|
-
} else if (sessionId &&
|
|
77
|
+
running = host => host.session && (host.session.id.startsWith(session) || hostSessionFile(host) === file);
|
|
78
|
+
} else if (sessionId && parsed.fork === undefined) {
|
|
83
79
|
// With `--fork`, the ID names a new Session.
|
|
84
80
|
const file = await sessionById(sessionId);
|
|
85
81
|
// A Session without messages has no file yet; its host reports where it will be.
|
|
86
|
-
const directory = sessionDirOption() ?? defaultSessionDir(process.cwd());
|
|
82
|
+
const directory = await sessionDirOption() ?? defaultSessionDir(process.cwd(), getAgentDir());
|
|
87
83
|
running = host => host.session && (file
|
|
88
|
-
? host
|
|
89
|
-
: host.session.id === sessionId && dirname(host
|
|
90
|
-
} else if (
|
|
84
|
+
? hostSessionFile(host) === file
|
|
85
|
+
: host.session.id === sessionId && dirname(hostSessionFile(host)) === directory && host.session.cwd === process.cwd());
|
|
86
|
+
} else if (parsed.continue) {
|
|
91
87
|
const file = await recentSession();
|
|
92
|
-
if (file) running = host => host
|
|
88
|
+
if (file) running = host => hostSessionFile(host) === file;
|
|
93
89
|
}
|
|
94
90
|
return running && (await settledHosts(paths, running)).find(running);
|
|
95
91
|
}
|
|
96
92
|
|
|
97
93
|
/** The Session `-c` continues, chosen by Pi's own `continueRecent` rules. */
|
|
98
94
|
async function recentSession() {
|
|
99
|
-
const { findMostRecentSession } = await
|
|
100
|
-
const configured = sessionDirOption();
|
|
101
|
-
const fallback = defaultSessionDir(process.cwd());
|
|
95
|
+
const { findMostRecentSession } = await dist('core/session-manager.js');
|
|
96
|
+
const configured = await sessionDirOption();
|
|
97
|
+
const fallback = defaultSessionDir(process.cwd(), getAgentDir());
|
|
102
98
|
const directory = configured ?? fallback;
|
|
103
99
|
// A configured directory may hold other cwds' Sessions.
|
|
104
100
|
return findMostRecentSession(directory, directory === fallback ? undefined : process.cwd()) ?? undefined;
|
|
@@ -106,13 +102,14 @@ async function recentSession() {
|
|
|
106
102
|
|
|
107
103
|
/** The Session `--session-id` would open, found by Pi's own rules, if it exists. */
|
|
108
104
|
async function sessionById(id) {
|
|
109
|
-
const { SessionManager } = await
|
|
110
|
-
return SessionManager.findById(process.cwd(), id, sessionDirOption());
|
|
105
|
+
const { SessionManager } = await dist('core/session-manager.js');
|
|
106
|
+
return SessionManager.findById(process.cwd(), id, await sessionDirOption());
|
|
111
107
|
}
|
|
112
108
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
/** The session directory configured for Pi in this cwd, as Pi resolves it: `--session-dir`, its environment variable, then its settings. */
|
|
110
|
+
async function sessionDirOption() {
|
|
111
|
+
const configured = parsed.sessionDir || process.env[ENV_SESSION_DIR] || (await dist('core/settings-manager.js')).SettingsManager.create(process.cwd(), getAgentDir()).getSessionDir();
|
|
112
|
+
return configured ? resolvePath(configured) : undefined;
|
|
116
113
|
}
|
|
117
114
|
|
|
118
115
|
async function attachCommand(query) {
|
|
@@ -147,16 +144,14 @@ async function attachCommand(query) {
|
|
|
147
144
|
|
|
148
145
|
/** Warns about the options a running Session does not apply. */
|
|
149
146
|
function warnIgnored(options) {
|
|
150
|
-
const ignored = options.filter((
|
|
151
|
-
&& !arg.startsWith('--session=') && !arg.startsWith('--session-id=') && !['--session', '--session-id', '--session-dir'].includes(options[i - 1]));
|
|
147
|
+
const ignored = argGroups(parseArgs, options).filter(([option]) => !['--session', '--session-id', '--session-dir', '-c', '--continue'].includes(option)).flat();
|
|
152
148
|
if (ignored.length) process.stderr.write(`${styleText('yellow', `The Session is already running; using it without applying: ${ignored.join(' ')}`, { stream: process.stderr })}\n`);
|
|
153
149
|
}
|
|
154
150
|
|
|
155
151
|
/** Pi's mode for `args` when it is not interactive: 'text', 'json', or 'rpc'. */
|
|
156
152
|
function headlessMode() {
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
if (!interactive || args.includes('-p') || args.includes('--print')) return 'text';
|
|
153
|
+
if (parsed.mode === 'json' || parsed.mode === 'rpc') return parsed.mode;
|
|
154
|
+
if (!interactive || parsed.print) return 'text';
|
|
160
155
|
}
|
|
161
156
|
|
|
162
157
|
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
@@ -166,7 +161,9 @@ else if (args[0] === 'agents' && interactive) {
|
|
|
166
161
|
await workbench({ pi: real, paths, sessionDir: sessionDir() });
|
|
167
162
|
process.exit(0);
|
|
168
163
|
}
|
|
169
|
-
else if (COMMANDS.has(args[0]) ||
|
|
164
|
+
else if (COMMANDS.has(args[0]) || parsed.help || parsed.version || parsed.export !== undefined || parsed.listModels !== undefined || parsed.noSession) {
|
|
165
|
+
runDirectly();
|
|
166
|
+
}
|
|
170
167
|
else if (headlessMode()) {
|
|
171
168
|
const { runHeadless } = await import('../src/host/rpc-client.mjs');
|
|
172
169
|
await runHeadless({ pi: real, paths, args, mode: headlessMode(), findRunning: runningTarget, warnIgnored });
|
package/docs/HOSTS.md
CHANGED
|
@@ -2,169 +2,105 @@
|
|
|
2
2
|
|
|
3
3
|
## 1. Overview
|
|
4
4
|
|
|
5
|
-
`pd` runs each Pi Session in a host of its own, so the Session keeps running
|
|
6
|
-
after its terminal closes, and terminals, scripts, and other programs can use
|
|
7
|
-
it at once. The workbench, opened with `pd agents`, shows the user's root
|
|
8
|
-
Sessions in one terminal view: from it the user opens a Session, reads its
|
|
9
|
-
latest result, or sends it a message.
|
|
5
|
+
`pd` runs each Pi Session in a host of its own, so the Session keeps running after its terminal closes, and terminals, scripts, and other programs can use it at once. The workbench, opened with `pd agents`, shows the user's root Sessions in one terminal view: from it the user opens a Session, reads its latest result, or sends it a message.
|
|
10
6
|
|
|
11
7
|
Three kinds of process take part:
|
|
12
8
|
|
|
13
|
-
- **Host**: one per Session. It runs Pi's full TUI in a pseudo-terminal and
|
|
14
|
-
|
|
15
|
-
- **Daemon**: one per user. It tracks Sessions, delivers messages, and starts
|
|
16
|
-
hosts.
|
|
9
|
+
- **Host**: one per Session. It runs Pi's full TUI in a pseudo-terminal and relays it to whichever terminal is attached.
|
|
10
|
+
- **Daemon**: one per user. It tracks Sessions, delivers messages, and starts hosts.
|
|
17
11
|
- **Workbench**: the daemon's terminal view, opened with `pd agents`.
|
|
18
12
|
|
|
19
|
-
Every running root Session reports its state to the daemon. The daemon starts
|
|
20
|
-
when a Session or the workbench first needs it, and exits soon after its last
|
|
21
|
-
connection closes.
|
|
13
|
+
Every running root Session reports its state to the daemon. The daemon starts when a Session or the workbench first needs it, and exits soon after its last connection closes.
|
|
22
14
|
|
|
23
15
|
Files live in `${XDG_STATE_HOME:-~/.local/state}/pi/agents/`:
|
|
24
16
|
|
|
25
17
|
- `workbench.json`: seen marks and which Sessions are listed.
|
|
26
18
|
- `host.log`, `daemon.log`: logs.
|
|
27
19
|
|
|
28
|
-
Sockets and Session locks go in `$XDG_RUNTIME_DIR/pi-agents/`. macOS has no
|
|
29
|
-
`XDG_RUNTIME_DIR`, so there they go in `runtime/` under the state directory.
|
|
20
|
+
Sockets and Session locks go in `$XDG_RUNTIME_DIR/pi-agents/`. macOS has no `XDG_RUNTIME_DIR`, so there they go in `runtime/` under the state directory.
|
|
30
21
|
|
|
31
22
|
## 2. Setup
|
|
32
23
|
|
|
33
|
-
Install with npm to get the `pd` command, also named `pi-agents`, then have Pi
|
|
34
|
-
load the extension from the same copy, so the command and the extension are
|
|
35
|
-
always the same version:
|
|
24
|
+
Install with npm to get the `pd` command, also named `pi-agents`, then have Pi load the extension from the same copy, so the command and the extension are always the same version:
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
```sh
|
|
27
|
+
npm install -g @tshu-w/pi-agents
|
|
28
|
+
pi install "$(npm root -g)/@tshu-w/pi-agents"
|
|
29
|
+
```
|
|
39
30
|
|
|
40
|
-
`pd` runs whichever `pi` is on `PATH`. Sessions started with plain `pi` also
|
|
41
|
-
load the extension, but run outside hosts and end with their terminal.
|
|
31
|
+
`pd` runs whichever `pi` is on `PATH`. Sessions started with plain `pi` also load the extension, but run outside hosts and end with their terminal.
|
|
42
32
|
|
|
43
33
|
## 3. Commands
|
|
44
34
|
|
|
45
|
-
- `pd [pi options]` starts Pi interactively in a new host and attaches the
|
|
46
|
-
terminal. If the Session is already running in a host, `pd` attaches to it
|
|
47
|
-
instead, sending it the messages as follow-ups and ignoring other options.
|
|
35
|
+
- `pd [pi options]` starts Pi interactively in a new host and attaches the terminal. If the Session is already running in a host, `pd` attaches to it instead, sending it the messages as follow-ups and ignoring other options.
|
|
48
36
|
- `pd agents` opens the workbench.
|
|
49
|
-
- `pd attach <session>` attaches to a Session, starting it first if it is not
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
messages back to back. Their output covers their own turns, including
|
|
63
|
-
input other clients add to them.
|
|
64
|
-
- `SIGINT`, `SIGTERM`, or `SIGHUP` (as from Ctrl+C, `kill`, or a closed
|
|
65
|
-
terminal) makes `-p` and `--mode json` withdraw their prompts that have
|
|
66
|
-
not started and stop their running turn, including input other clients
|
|
67
|
-
added to it. Killed with `SIGKILL`, they leave their turn running.
|
|
68
|
-
- An RPC client that ends leaves its turn running. One whose Session stops
|
|
69
|
-
opens it again; requests awaiting a response then fail.
|
|
70
|
-
- `--no-session` starts and package commands (`pd install`, `pd update`, ...)
|
|
71
|
-
run Pi directly.
|
|
72
|
-
|
|
73
|
-
A host keeps the options and environment of the process that started it, as
|
|
74
|
-
Pi reads them only at startup.
|
|
75
|
-
|
|
76
|
-
A Session running outside a host still appears in the workbench, but can be
|
|
77
|
-
attached only after it exits.
|
|
37
|
+
- `pd attach <session>` attaches to a Session, starting it first if it is not running.
|
|
38
|
+
- Non-interactive starts (`-p`, `--mode json`, `--mode rpc`, piped stdin) work as in Pi, on the Session in a host, so several of them and an attached terminal can use one Session at once. Their commands act on that Session, as `abort` stops whichever turn is running. Unlike Pi:
|
|
39
|
+
- Dialogs are not sent to them. A dialog waits while a terminal is attached and is cancelled otherwise.
|
|
40
|
+
- Extensions see interactive mode, so what an extension writes to stdout does not reach them; `/program` prints as with `pi -p`.
|
|
41
|
+
- `new_session`, `switch_session`, `fork`, and `clone` move only the caller to the other Session.
|
|
42
|
+
- `-p` and `--mode json` wait until the Session is idle, then run their messages back to back. Their output covers their own turns, including input other clients add to them.
|
|
43
|
+
- `SIGINT`, `SIGTERM`, or `SIGHUP` (as from Ctrl+C, `kill`, or a closed terminal) makes `-p` and `--mode json` withdraw their prompts that have not started and stop their running turn, including input other clients added to it. Killed with `SIGKILL`, they leave their turn running.
|
|
44
|
+
- An RPC client that ends leaves its turn running. One whose Session stops opens it again; requests awaiting a response then fail.
|
|
45
|
+
- `--no-session` starts and package commands (`pd install`, `pd update`, ...) run Pi directly.
|
|
46
|
+
|
|
47
|
+
A host keeps the options and environment of the process that started it, as Pi reads them only at startup.
|
|
48
|
+
|
|
49
|
+
A Session running outside a host still appears in the workbench, but can be attached only after it exits.
|
|
78
50
|
|
|
79
51
|
## 4. Attach and detach
|
|
80
52
|
|
|
81
|
-
Only one terminal is attached at a time. A new terminal takes over, and the
|
|
82
|
-
previous one is told how to reattach.
|
|
53
|
+
Only one terminal is attached at a time. A new terminal takes over, and the previous one is told how to reattach.
|
|
83
54
|
|
|
84
|
-
On attach, the host restores Pi's screen and the terminal modes Pi has set.
|
|
85
|
-
Pi detects the colors and keyboard protocol of the first terminal that
|
|
86
|
-
attaches, as it would without a host.
|
|
55
|
+
On attach, the host restores Pi's screen and the terminal modes Pi has set. Pi detects the colors and keyboard protocol of the first terminal that attaches, as it would without a host.
|
|
87
56
|
|
|
88
|
-
Closing the window, pressing `Ctrl+Z` (
|
|
89
|
-
running `/detach` detaches. The Session keeps running, and the terminal prints:
|
|
57
|
+
Closing the window, pressing `Ctrl+Z` (`pi-agents.detach`), or running `/detach` detaches. The Session keeps running, and the terminal prints:
|
|
90
58
|
|
|
91
|
-
|
|
59
|
+
```text
|
|
60
|
+
Pi keeps running. To reattach: pd attach <session>
|
|
61
|
+
```
|
|
92
62
|
|
|
93
63
|
A host disables Pi's suspend.
|
|
94
64
|
|
|
95
|
-
A detached Session exits after 10 minutes with no live work in its tree and no
|
|
96
|
-
`pd --mode rpc` connected. A message to a Session that is not running starts it
|
|
97
|
-
in a host, detached.
|
|
65
|
+
A detached Session exits after 10 minutes with no live work in its tree and no `pd --mode rpc` connected. A message to a Session that is not running starts it in a host, detached.
|
|
98
66
|
|
|
99
67
|
## 5. Workbench
|
|
100
68
|
|
|
101
|
-
The list shows running Sessions, Sessions that ran without a terminal (started
|
|
102
|
-
here, woken by a message, or detached), Sessions opened here, and Sessions
|
|
103
|
-
with an unseen turn. Older Sessions are reached with `/resume`.
|
|
69
|
+
The list shows running Sessions, Sessions that ran without a terminal (started here, woken by a message, or detached), Sessions opened here, and Sessions with an unseen turn. Older Sessions are reached with `/resume`.
|
|
104
70
|
|
|
105
|
-
Each row shows a root Session's name and what it did last. The name is the
|
|
106
|
-
Session name, or else the first line of its first message. While the Session
|
|
107
|
-
works, the row shows the running tool; otherwise, the start of its last reply.
|
|
108
|
-
Running owned Agents and Programs are counted, as in `3 agents running`.
|
|
71
|
+
Each row shows a root Session's name and what it did last. The name is the Session name, or else the first line of its first message. While the Session works, the row shows the running tool; otherwise, the start of its last reply. Running owned Agents and Programs are counted, as in `3 agents running`.
|
|
109
72
|
|
|
110
73
|
Rows are grouped by state, newest first in each group:
|
|
111
74
|
|
|
112
|
-
- **Needs attention**: a dialog waiting for the user, or a finished turn the user
|
|
113
|
-
has not seen. A turn that failed after Pi's retries, or a host that exited
|
|
114
|
-
abnormally, is marked failed.
|
|
75
|
+
- **Needs attention**: a dialog waiting for the user, or a finished turn the user has not seen. A turn that failed after Pi's retries, or a host that exited abnormally, is marked failed.
|
|
115
76
|
- **Working**: the Session or its tree has live work.
|
|
116
77
|
- **Completed**: everything else.
|
|
117
78
|
|
|
118
|
-
While a Session works, its icon spins. Otherwise the shape tells whether Pi is
|
|
119
|
-
still running (`●`) or has exited (`○`), and the color marks a finished (green)
|
|
120
|
-
or failed (red) turn, or a dialog waiting for the user (yellow). The header
|
|
121
|
-
shows Pi's logo, the size of each group, and key hints.
|
|
79
|
+
While a Session works, its icon spins. Otherwise the shape tells whether Pi is still running (`●`) or has exited (`○`), and the color marks a finished (green) or failed (red) turn, or a dialog waiting for the user (yellow). The header shows Pi's logo, the size of each group, and key hints.
|
|
122
80
|
|
|
123
|
-
A turn counts as seen once it has been shown in an attached terminal or a
|
|
124
|
-
preview. Sessions
|
|
125
|
-
from before the workbench start out seen. A Session that holds its lock but is
|
|
126
|
-
not reporting to the daemon, as while its Pi exits, shows as unknown (`?`).
|
|
81
|
+
A turn counts as seen once it has been shown in an attached terminal or a preview. Sessions from before the workbench start out seen. A Session that holds its lock but is not reporting to the daemon, as while its Pi exits, shows as unknown (`?`).
|
|
127
82
|
|
|
128
83
|
Keys:
|
|
129
84
|
|
|
130
|
-
- `Enter` attaches; detaching returns here.
|
|
131
|
-
- `Space` previews the selected Session below the list: the conversation
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
The input keeps `@<session>` at its start, so a message goes to the
|
|
136
|
-
previewed Session. In the preview:
|
|
137
|
-
- `Enter` sends a typed message; with none typed, it attaches.
|
|
138
|
-
- `Ctrl+O` (`app.tools.expand`) attaches.
|
|
139
|
-
- It scrolls as fullscreen mode does; with no message typed, `↑`/`↓`
|
|
140
|
-
scroll too.
|
|
141
|
-
- `Esc` clears a typed message; with none typed, it closes the preview.
|
|
142
|
-
- `Ctrl+Z` closes the preview.
|
|
85
|
+
- `Enter` (`tui.select.confirm`) attaches; detaching returns here.
|
|
86
|
+
- `Space` previews the selected Session below the list: the conversation since the last compaction, with thinking left out, and each run of tool calls, branch summary, and custom message folded to one line, such as `... (6 tool calls: bash, read, edit)`. The preview follows the Session as it works. The input keeps `@<session>` at its start, so a message goes to the previewed Session. In the preview:
|
|
87
|
+
- With no message typed, `Enter` attaches, as `Ctrl+O` (`app.tools.expand`) always does.
|
|
88
|
+
- It scrolls as fullscreen mode does; with no message typed, `↑`/`↓` (`tui.select.up`/`tui.select.down`) scroll too.
|
|
89
|
+
- `Esc` (`tui.select.cancel`) clears a typed message; with none typed, it or `Space` closes the preview, as `Ctrl+Z` always does.
|
|
143
90
|
- `Ctrl+X` stops the current turn, as `app.interrupt` does.
|
|
144
|
-
- `Ctrl+D` removes the selected Session from the list. Its file stays, and
|
|
145
|
-
|
|
146
|
-
attached to quits first; any other running Session must quit itself.
|
|
147
|
-
- `Ctrl+Z` (`pi-agents.detach`) exits the workbench, as detaching leaves a
|
|
148
|
-
Session; every Session keeps running.
|
|
91
|
+
- `Ctrl+D` (`app.session.delete`) removes the selected Session from the list. Its file stays, and `/resume` brings it back. An idle Session in a host that no terminal is attached to quits first; any other running Session must quit itself.
|
|
92
|
+
- `Ctrl+Z` (`pi-agents.detach`) exits the workbench, as detaching leaves a Session; every Session keeps running.
|
|
149
93
|
|
|
150
94
|
The input at the bottom:
|
|
151
95
|
|
|
152
|
-
- `@` on an empty input opens Pi's session picker. The chosen Session is
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- `/resume` opens Pi's session picker over all directories, newest first, and
|
|
157
|
-
attaches the chosen Session. `Ctrl+D` in the picker deletes a Session file.
|
|
158
|
-
- `/quit` exits the workbench, as do `Esc` and `Ctrl+C` twice on an empty
|
|
159
|
-
input.
|
|
160
|
-
- Any other text starts a new Session in the current directory with Pi's
|
|
161
|
-
default settings, detached.
|
|
96
|
+
- `@` on an empty input opens Pi's session picker. The chosen Session is selected in the list, and the input starts with `@<session>`; the text after it goes to that Session as a user message.
|
|
97
|
+
- `/resume` opens Pi's session picker over all directories, newest first, and attaches the chosen Session.
|
|
98
|
+
- `/quit` exits the workbench, as do `Esc` and `Ctrl+C` (`app.clear`) twice on an empty input.
|
|
99
|
+
- Any other text starts a new Session in the current directory with Pi's default settings, detached.
|
|
162
100
|
|
|
163
101
|
## 6. Upgrades
|
|
164
102
|
|
|
165
|
-
A host keeps running the version it started with until its Session exits.
|
|
166
|
-
When the daemon sees a newer pi-agents version, it restarts once idle, and
|
|
167
|
-
Sessions reconnect to the new daemon.
|
|
103
|
+
A host keeps running the version it started with until its Session exits. When the daemon sees a newer pi-agents version, it restarts once idle, and Sessions reconnect to the new daemon.
|
|
168
104
|
|
|
169
105
|
## 7. Platform
|
|
170
106
|
|
|
@@ -172,8 +108,4 @@ macOS and Linux.
|
|
|
172
108
|
|
|
173
109
|
## Not in this version
|
|
174
110
|
|
|
175
|
-
Attaching several terminals to one Session, Sessions on other machines,
|
|
176
|
-
model-written summaries, and a confirmation before quitting while work is
|
|
177
|
-
running. Keeping Sessions alive across host crashes and reboots needs a
|
|
178
|
-
durable runtime, which [Pi Durable](https://earendil.com/posts/pi-durable/)
|
|
179
|
-
explores.
|
|
111
|
+
Attaching several terminals to one Session, Sessions on other machines, model-written summaries, and a confirmation before quitting while work is running. Keeping Sessions alive across host crashes and reboots needs a durable runtime, which [Pi Durable](https://earendil.com/posts/pi-durable/) explores.
|
package/docs/PROGRAM.md
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
1
|
# Pi Agents Program Specification
|
|
2
2
|
|
|
3
|
-
A Program is JavaScript that calls tools and Agents. Agents are defined in
|
|
4
|
-
SPEC.md.
|
|
3
|
+
A Program is JavaScript that calls tools and Agents. Agents are defined in SPEC.md.
|
|
5
4
|
|
|
6
|
-
The `program` tool replaces Pi's `codemode` tool and keeps its script API,
|
|
7
|
-
tool list, result format, and truncation. This document describes what
|
|
8
|
-
Programs add.
|
|
5
|
+
The `program` tool replaces Pi's `codemode` tool and keeps its script API, tool list, result format, and truncation. This document describes what Programs add.
|
|
9
6
|
|
|
10
7
|
## 1. Semantic model
|
|
11
8
|
|
|
12
|
-
An Agent runs a **Program** with the `program` tool. That Agent is the
|
|
13
|
-
Program's **caller**. The Program can call the caller's tools and create
|
|
14
|
-
Agents.
|
|
9
|
+
An Agent runs a **Program** with the `program` tool. That Agent is the Program's **caller**. The Program can call the caller's tools and create Agents.
|
|
15
10
|
|
|
16
|
-
A Program runs in the **foreground** or the **background**. In the
|
|
17
|
-
foreground, the tool call returns the Program's result. In the background,
|
|
18
|
-
the tool call returns the Program's ID, and the caller gets the result with
|
|
19
|
-
`wait`.
|
|
11
|
+
A Program runs in the **foreground** or the **background**. In the foreground, the tool call returns the Program's result. In the background, the tool call returns the Program's ID, and the caller gets the result with `wait`.
|
|
20
12
|
|
|
21
|
-
A Program is shown by its short ID: the shortest prefix of its ID, at least
|
|
22
|
-
8 characters, that none of the caller's other Programs' IDs starts with. A
|
|
23
|
-
target is an ID or a unique ID prefix.
|
|
13
|
+
A Program is shown by its short ID: the shortest prefix of its ID, at least 8 characters, that none of the caller's other Programs' IDs starts with. A target is an ID or a unique ID prefix.
|
|
24
14
|
|
|
25
|
-
Agents created with `agent()` belong to the Program: it gets their results
|
|
26
|
-
from `send()`, without `wait` or notifications, and only it can abort
|
|
27
|
-
them. Each of these Agents and the Agents under it see only one another,
|
|
28
|
-
and no other Agent sees them. The user sees them in the task panel and `/tasks`,
|
|
29
|
-
and can talk to them or interrupt them in the viewer; an interrupted input
|
|
30
|
-
makes its `send()` throw. When the Program ends, their unfinished inputs end
|
|
31
|
-
as `aborted`, and the Agents go offline for good, from the bottom up.
|
|
15
|
+
Agents created with `agent()` belong to the Program: it gets their results from `send()`, without `wait` or notifications, and only it can abort them. Each of these Agents and the Agents under it see only one another, and no other Agent sees them. The user sees them in the task panel and `/tasks`, and can talk to them or interrupt them in the viewer; an interrupted input makes its `send()` throw. When the Program ends, their unfinished inputs end as `aborted`, and the Agents go offline for good, from the bottom up.
|
|
32
16
|
|
|
33
17
|
A Program is `running` until it ends in one of three ways:
|
|
34
18
|
|
|
@@ -38,60 +22,38 @@ A Program is `running` until it ends in one of three ways:
|
|
|
38
22
|
|
|
39
23
|
## 2. Operations
|
|
40
24
|
|
|
41
|
-
- `run` runs a Program in the foreground or background, with an optional
|
|
42
|
-
timeout.
|
|
25
|
+
- `run` runs a Program in the foreground or background, with an optional timeout.
|
|
43
26
|
- `wait` waits for background Programs and returns their results.
|
|
44
27
|
- `list` lists the caller's background Programs.
|
|
45
28
|
- `stop` stops a running Program.
|
|
46
29
|
|
|
47
|
-
When a background Program ends and its caller is not waiting for it, the
|
|
48
|
-
caller gets a notification, as in SPEC §5. A steer input ends `wait` early,
|
|
49
|
-
as in SPEC §5.
|
|
30
|
+
When a background Program ends and its caller is not waiting for it, the caller gets a notification, as in SPEC §5. A steer input ends `wait` early, as in SPEC §5.
|
|
50
31
|
|
|
51
32
|
## 3. Program API
|
|
52
33
|
|
|
53
|
-
`tools` holds the caller's tools, except `program`; which tools the caller
|
|
54
|
-
can use is checked at each call. If a Program fails or is stopped, the
|
|
55
|
-
error or stop reason follows its output. Its `store()` writes are kept
|
|
56
|
-
only if it completes.
|
|
34
|
+
`tools` holds the caller's tools, except `program`; which tools the caller can use is checked at each call. If a Program fails or is stopped, the error or stop reason follows its output. Its `store()` writes are kept only if it completes.
|
|
57
35
|
|
|
58
|
-
A Program also creates idle Agents with `agent()`, typed in the Agent API
|
|
59
|
-
(§6). Options work like `agent` `spawn`; an omitted name is generated,
|
|
60
|
-
unique within the Program. `send()` sends a message and returns its result.
|
|
61
|
-
`abort()` works like `agent` `abort`. The Agent gets its tools as if the caller had spawned it.
|
|
36
|
+
A Program also creates idle Agents with `agent()`, typed in the Agent API (§6). Options work like `agent` `spawn`; an omitted name is generated, unique within the Program. `send()` sends a message and returns its result. `abort()` works like `agent` `abort`. The Agent gets its tools as if the caller had spawned it.
|
|
62
37
|
|
|
63
|
-
A result is the Agent's answer. If the input fails or is aborted, the
|
|
64
|
-
result throws an Error. A write returns `undefined` once accepted.
|
|
38
|
+
A result is the Agent's answer. If the input fails or is aborted, the result throws an Error. A write returns `undefined` once accepted.
|
|
65
39
|
|
|
66
|
-
With `schema`, the Agent must call `submit_result` with a value that
|
|
67
|
-
matches the schema, and the result is that value. If it does not, the
|
|
68
|
-
input fails.
|
|
40
|
+
With `schema`, the Agent must call `submit_result` with a value that matches the schema, and the result is that value. If it does not, the input fails.
|
|
69
41
|
|
|
70
|
-
With `timeout`, an input that has not ended within that many seconds of
|
|
71
|
-
`send()`, including time queued for a slot, is aborted; the Agent's queued
|
|
72
|
-
inputs are unaffected. The result throws a timeout Error.
|
|
42
|
+
With `timeout`, an input that has not ended within that many seconds of `send()`, including time queued for a slot, is aborted; the Agent's queued inputs are unaffected. The result throws a timeout Error.
|
|
73
43
|
|
|
74
44
|
`send()` throws if `schema` or `timeout` is used without `followUp`.
|
|
75
45
|
|
|
76
46
|
## 4. Lifecycle
|
|
77
47
|
|
|
78
|
-
When a Program ends, it cancels its unfinished tool calls and aborts its
|
|
79
|
-
Agents' unfinished inputs, and waits for its Agents to go offline. It
|
|
80
|
-
returns its result after cleanup. Work not stopped within the cleanup
|
|
81
|
-
timeout is abandoned and may keep running; the Program's result and `stop`
|
|
82
|
-
report it as an error, and the Program's state is unchanged.
|
|
48
|
+
When a Program ends, it cancels its unfinished tool calls and aborts its Agents' unfinished inputs, and waits for its Agents to go offline. It returns its result after cleanup. Work not stopped within the cleanup timeout is abandoned and may keep running; the Program's result and `stop` report it as an error, and the Program's state is unchanged.
|
|
83
49
|
|
|
84
|
-
When the caller goes offline, its running Programs stop. Their results
|
|
85
|
-
remain available when the caller is loaded again.
|
|
50
|
+
When the caller goes offline, its running Programs stop. Their results remain available when the caller is loaded again.
|
|
86
51
|
|
|
87
52
|
## 5. Limits
|
|
88
53
|
|
|
89
|
-
A Program's Agents and the Agents under them count toward the caller's
|
|
90
|
-
tree: they share its slots and `maxOutstanding` limit, as in SPEC §4. At
|
|
91
|
-
`maxOutstanding`, `send()` throws.
|
|
54
|
+
A Program's Agents and the Agents under them count toward the caller's tree: they share its slots and `maxOutstanding` limit, as in SPEC §4. At `maxOutstanding`, `send()` throws.
|
|
92
55
|
|
|
93
|
-
An owned caller gives up its slot while a foreground Program runs or while
|
|
94
|
-
it waits for background Programs, and takes one again before continuing.
|
|
56
|
+
An owned caller gives up its slot while a foreground Program runs or while it waits for background Programs, and takes one again before continuing.
|
|
95
57
|
|
|
96
58
|
## 6. Model-facing tool
|
|
97
59
|
|
|
@@ -99,21 +61,11 @@ it waits for background Programs, and takes one again before continuing.
|
|
|
99
61
|
|
|
100
62
|
description:
|
|
101
63
|
|
|
102
|
-
Run JavaScript that composes tool calls and Agents; only its output and
|
|
103
|
-
return value reach the caller. `run` runs a Program in the foreground, or
|
|
104
|
-
in the background with `background`; `wait` waits for background Programs
|
|
105
|
-
and returns their results; `list` lists background Programs; `stop` stops
|
|
106
|
-
a running Program. When a background Program ends while its caller is not
|
|
107
|
-
waiting for it, the caller receives a notification; `wait` returns the
|
|
108
|
-
result.
|
|
64
|
+
Run JavaScript that composes tool calls and Agents; only its output and return value reach the caller. `run` runs a Program in the foreground, or in the background with `background`; `wait` waits for background Programs and returns their results; `list` lists background Programs; `stop` stops a running Program. When a background Program ends while its caller is not waiting for it, the caller receives a notification; `wait` returns the result.
|
|
109
65
|
|
|
110
|
-
Program code is the body of an async function. It calls the caller's tools
|
|
111
|
-
through `tools.*`, which excludes `program`, and creates Agents through
|
|
112
|
-
`agent()`. These Agents belong to the Program, are isolated from other
|
|
113
|
-
Agents, and go offline when it ends.
|
|
66
|
+
Program code is the body of an async function. It calls the caller's tools through `tools.*`, which excludes `program`, and creates Agents through `agent()`. These Agents belong to the Program, are isolated from other Agents, and go offline when it ends.
|
|
114
67
|
|
|
115
|
-
`program` replaces the `codemode` tool; wherever `codemode` is mentioned,
|
|
116
|
-
use `program`.
|
|
68
|
+
`program` replaces the `codemode` tool; wherever `codemode` is mentioned, use `program`.
|
|
117
69
|
|
|
118
70
|
[the script API of `codemode`, without its Model API]
|
|
119
71
|
|
|
@@ -163,14 +115,9 @@ Run JavaScript that composes tool calls and Agents
|
|
|
163
115
|
|
|
164
116
|
promptGuidelines:
|
|
165
117
|
|
|
166
|
-
- Use `program(action='run', code=...)` to call tools or Agents several
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
condition holds.
|
|
170
|
-
- Use `Promise.allSettled` for independent calls. Return only what the
|
|
171
|
-
caller needs; use `text()` for progress worth reading.
|
|
172
|
-
- Use `background=true` for long Programs, and `program(action='wait', ...)`
|
|
173
|
-
when the next step depends on their results.
|
|
118
|
+
- Use `program(action='run', code=...)` to call tools or Agents several times without a model turn between the calls, for example to read many files, filter large tool output, fan out Agents, or loop until a condition holds.
|
|
119
|
+
- Use `Promise.allSettled` for independent calls. Return only what the caller needs; use `text()` for progress worth reading.
|
|
120
|
+
- Use `background=true` for long Programs, and `program(action='wait', ...)` when the next step depends on their results.
|
|
174
121
|
|
|
175
122
|
parameters:
|
|
176
123
|
|
|
@@ -186,10 +133,7 @@ results:
|
|
|
186
133
|
|
|
187
134
|
In these formats, `<id>` is the Program's short ID.
|
|
188
135
|
|
|
189
|
-
`run` in the foreground returns the Program result, as an error if the
|
|
190
|
-
Program failed or was stopped.
|
|
191
|
-
A Program result lists its `send()` calls with its tool calls, and names the
|
|
192
|
-
calls still running when the script returned, which were cancelled.
|
|
136
|
+
`run` in the foreground returns the Program result, as an error if the Program failed or was stopped. A Program result lists its `send()` calls with its tool calls, and names the calls still running when the script returned, which were cancelled.
|
|
193
137
|
|
|
194
138
|
`run` in the background:
|
|
195
139
|
|
|
@@ -197,8 +141,7 @@ calls still running when the script returned, which were cancelled.
|
|
|
197
141
|
Program <id> started.
|
|
198
142
|
```
|
|
199
143
|
|
|
200
|
-
`wait` returns one block per result, followed by running Programs on
|
|
201
|
-
timeout:
|
|
144
|
+
`wait` returns one block per result, followed by running Programs on timeout:
|
|
202
145
|
|
|
203
146
|
```text
|
|
204
147
|
<program-result id="<id>" status="<outcome>">
|
|
@@ -210,8 +153,7 @@ Still running: <id>, ...
|
|
|
210
153
|
|
|
211
154
|
When a steer input ended the wait, the line of SPEC §6 follows.
|
|
212
155
|
|
|
213
|
-
A single completed result, with nothing running, is returned alone. When
|
|
214
|
-
there are no results or running Programs:
|
|
156
|
+
A single completed result, with nothing running, is returned alone. When there are no results or running Programs:
|
|
215
157
|
|
|
216
158
|
```text
|
|
217
159
|
No results.
|
|
@@ -242,8 +184,7 @@ Program <id> stopped.
|
|
|
242
184
|
Program <id> has already ended.
|
|
243
185
|
```
|
|
244
186
|
|
|
245
|
-
When some of the Program's work does not stop within the timeout, `stop`
|
|
246
|
-
fails with the following; only restarting Pi stops it.
|
|
187
|
+
When some of the Program's work does not stop within the timeout, `stop` fails with the following; only restarting Pi stops it.
|
|
247
188
|
|
|
248
189
|
```text
|
|
249
190
|
Program <id> did not stop within the timeout and may still be running.
|
|
@@ -257,8 +198,7 @@ Cleanup error: some of the Program's work did not stop within the timeout and ma
|
|
|
257
198
|
|
|
258
199
|
errors:
|
|
259
200
|
|
|
260
|
-
Invalid calls fail with an error. Errors that need a follow-up say what to
|
|
261
|
-
do:
|
|
201
|
+
Invalid calls fail with an error. Errors that need a follow-up say what to do:
|
|
262
202
|
|
|
263
203
|
```text
|
|
264
204
|
No Program matches "<target>". Use list to find Programs.
|
|
@@ -270,10 +210,7 @@ No Program matches "<target>". Use list to find Programs.
|
|
|
270
210
|
|
|
271
211
|
truncation:
|
|
272
212
|
|
|
273
|
-
`wait` text beyond Pi's output limits is truncated as in SPEC §6. `wait`
|
|
274
|
-
adds results in order; the result that crosses the limit is truncated but keeps
|
|
275
|
-
its closing tag, and later results are omitted. A truncated result counts
|
|
276
|
-
as returned. Omitted results stay unreturned, and the text ends with:
|
|
213
|
+
`wait` text beyond Pi's output limits is truncated as in SPEC §6. `wait` adds results in order; the result that crosses the limit is truncated but keeps its closing tag, and later results are omitted. A truncated result counts as returned. Omitted results stay unreturned, and the text ends with:
|
|
277
214
|
|
|
278
215
|
```text
|
|
279
216
|
[Results omitted: <id>, ... Use wait with fewer targets.]
|
|
@@ -289,19 +226,10 @@ Program <id> <outcome>.
|
|
|
289
226
|
|
|
290
227
|
## 7. Human interface
|
|
291
228
|
|
|
292
|
-
The task panel (SPEC §7) lists running background Programs.
|
|
293
|
-
`/tasks` opens a background Program in the task viewer (SPEC §7).
|
|
229
|
+
The task panel (SPEC §7) lists running background Programs. `/tasks` opens a background Program in the task viewer (SPEC §7).
|
|
294
230
|
|
|
295
|
-
`/program <path> [args]` runs the script in a file as a foreground Program of
|
|
296
|
-
the current Agent, with the text after the path as the global `args`. Esc
|
|
297
|
-
stops it. Like a `!` command, it and its result join the context without
|
|
298
|
-
starting a turn. With `pi -p`, the result is printed, and Pi exits with
|
|
299
|
-
status 1 if the run fails.
|
|
231
|
+
`/program <path> [args]` runs the script in a file as a foreground Program of the current Agent, with the text after the path as the global `args`. Like a `!` command, it and its result join the context without starting a turn. With `pi -p`, the result is printed, and Pi exits with status 1 if the run fails.
|
|
300
232
|
|
|
301
233
|
## 8. Configuration
|
|
302
234
|
|
|
303
|
-
`program` is inactive by default, like `codemode`; enable it with `"defaultTools": ["+program"]` or `--tools`. `/program` does not need it.
|
|
304
|
-
MCP treats `program` as `codemode`: it activates `program` when its servers need a script tool and neither is active, unless `autoEnableCodemode` is false, and a Program waits for the MCP servers its code names.
|
|
305
|
-
When both `program` and `codemode` are active, Pi warns once per Session, since they do a similar job.
|
|
306
|
-
`codemode.mode` decides how the tool list is presented, as for `codemode`.
|
|
307
|
-
`--program-only` activates `program` and uses `only` for the process, including its owned Agents.
|
|
235
|
+
`program` is inactive by default, like `codemode`; enable it with `"defaultTools": ["+program"]` or `--tools`. `/program` does not need it. MCP treats `program` as `codemode`: it activates `program` when its servers need a script tool and neither is active, unless `autoEnableCodemode` is false, and a Program waits for the MCP servers its code names. When both `program` and `codemode` are active, Pi warns once per Session, since they do a similar job. `codemode.mode` decides how the tool list is presented, as for `codemode`. `--program-only` activates `program` and uses `only` for the process, including its owned Agents.
|