pi-minimal-dispatch 0.2.0 → 0.2.1
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 +11 -0
- package/README.md +9 -6
- package/docs/API.md +3 -1
- package/package.json +1 -1
- package/src/derive.ts +15 -0
- package/src/events.ts +2 -0
- package/src/format.ts +5 -1
- package/src/index.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
- Status lines (`lanes` status/wait, `/lanes`) tag each lane with its model:
|
|
6
|
+
`[short-id think:level]` after the name — provider prefix dropped, thinking
|
|
7
|
+
level only when set. The tag shows the actual model from the lane's last
|
|
8
|
+
assistant message, falling back to the pinned value until the first answer;
|
|
9
|
+
`LaneStatus` carries `model`/`thinkingLevel` (full ids) for structured
|
|
10
|
+
consumers.
|
|
11
|
+
- `dispatch`'s `model` param documents pi's `id:thinking` suffix
|
|
12
|
+
(e.g. `zai/glm-4.6:high`), which the tag renders split off the id.
|
|
13
|
+
|
|
3
14
|
## 0.2.0 — 2026-10-23
|
|
4
15
|
|
|
5
16
|
Requires pi ≥ 1.4.0 (the MCP reach rules the validator encodes are pi
|
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@ never guessed from empty files.
|
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
wave 'survey':
|
|
13
|
-
docs
|
|
14
|
-
review · settled · 4 turns · tok 12021in/310out, $0.0041 · stop:stop · last 12s ago
|
|
13
|
+
docs [glm-4.6 think:high] · running · 2 turns · tok 5703in/90out, $0.0009 · stop:toolUse · last 3.1s ago
|
|
14
|
+
review [glm-5.3-flash] · settled · 4 turns · tok 12021in/310out, $0.0041 · stop:stop · last 12s ago
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Why
|
|
@@ -75,8 +75,8 @@ pi decides when to call the tools. A real session looks like:
|
|
|
75
75
|
manifest: /abs/path/to/.pi/work/survey-manifest.json
|
|
76
76
|
|
|
77
77
|
> lanes wave="survey" action="wait"
|
|
78
|
-
docs · settled · 2 turns · tok 5703in/90out, $0.0009 · stop:stop · last 0.8s ago
|
|
79
|
-
review · settled · 4 turns · tok 12021in/310out, $0.0041 · stop:stop · last 2.1s ago
|
|
78
|
+
docs [glm-5.3-flash] · settled · 2 turns · tok 5703in/90out, $0.0009 · stop:stop · last 0.8s ago
|
|
79
|
+
review [glm-4.6] · settled · 4 turns · tok 12021in/310out, $0.0041 · stop:stop · last 2.1s ago
|
|
80
80
|
|
|
81
81
|
> lanes wave="survey" action="collect"
|
|
82
82
|
collect wave 'survey':
|
|
@@ -139,8 +139,11 @@ While any tracked lane runs, the footer shows one line — e.g. `lanes: 2 runnin
|
|
|
139
139
|
— updated at the `pollMs` cadence and cleared when nothing is in flight.
|
|
140
140
|
`/lanes` (optionally `/lanes <wave>`) prints the full status table for waves
|
|
141
141
|
seen this session — an untracked wave name is resolved from the default
|
|
142
|
-
workDir — and completes wave names.
|
|
143
|
-
|
|
142
|
+
workDir — and completes wave names. Each line tags the lane with its model —
|
|
143
|
+
short id (provider prefix dropped), thinking level appended as `think:<level>`
|
|
144
|
+
when set — the actual model from the event stream once answers exist, the pinned
|
|
145
|
+
value before that. Both are TUI conveniences over the same derived state the
|
|
146
|
+
`lanes` tool reports.
|
|
144
147
|
|
|
145
148
|
## State model
|
|
146
149
|
|
package/docs/API.md
CHANGED
|
@@ -20,7 +20,7 @@ the leash. Events stream to `<workDir>/<wave>-<name>.events.jsonl`.
|
|
|
20
20
|
| `lanes` | array | — | One entry per lane (min 1); lane slugs unique within the wave |
|
|
21
21
|
| `lanes[].name` | string | — | Lane slug: `kebab-case`, max 64 chars |
|
|
22
22
|
| `lanes[].briefPath` | string | — | Path to the lane's brief file (its `@file` prompt) |
|
|
23
|
-
| `lanes[].model` | string? | pi default | Pin a model id; omitted: pi's **configured default model** — not the dispatcher session's active model |
|
|
23
|
+
| `lanes[].model` | string? | pi default | Pin a model id; omitted: pi's **configured default model** — not the dispatcher session's active model. Accepts pi's `id:thinking` suffix (e.g. `zai/glm-4.6:high`) |
|
|
24
24
|
| `lanes[].deadlineSec` | number? | `deadlineSec` config (900) | Leash in seconds; holds even if the dispatcher is gone. Minimum 1 |
|
|
25
25
|
| `lanes[].tools` | string? | `read,grep,find,ls` | Tool allowlist for `--tools` — plain names or `*` patterns; no `+name`/`-name` modifiers, no shell quoting, not empty. Add `bash` when a lane must run commands (bash can still invoke git). With MCP enabled, reach requires `codemode` or `tool_search` here — see [MCP and web reach](#mcp-and-web-reach) |
|
|
26
26
|
| `lanes[].noExtensions` | boolean? | false | Pass `-ne` (drop extensions incl. MCP in the lane) |
|
|
@@ -98,6 +98,8 @@ Per-lane `structuredContent` entries. Note: `settled` only means the
|
|
|
98
98
|
| `overdue` | boolean? | alive past its deadline (leash misfire) — informational |
|
|
99
99
|
| `turns` | number | turn count so far |
|
|
100
100
|
| `usage` | object | run totals summed over assistant messages: `{input, output, cacheRead, cacheWrite, cost}` |
|
|
101
|
+
| `model` | string? | model answering the last assistant message; until the first assistant message, the pinned model with pi's `:thinking` suffix split off (last colon, valid level word only) |
|
|
102
|
+
| `thinkingLevel` | string? | thinking level of the last assistant message (`off`–`max`) — absent for models without thinking |
|
|
101
103
|
| `stopReason` | string? | last assistant stop reason (`stop` is clean) |
|
|
102
104
|
| `lastEventAgeSec` | number? | seconds since the events file last grew — the stuck-lane signal |
|
|
103
105
|
| `spawnError` | string? | why a `spawn`-failed lane never started |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-minimal-dispatch",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Minimal detached lane dispatch with visible state for the pi coding agent — dispatch/lanes tools over a manifest + JSON-event protocol",
|
|
5
5
|
"author": "Mike Cheung",
|
|
6
6
|
"keywords": [
|
package/src/derive.ts
CHANGED
|
@@ -45,6 +45,10 @@ export type LaneStatus = {
|
|
|
45
45
|
overdue?: boolean;
|
|
46
46
|
turns: number;
|
|
47
47
|
usage: Usage;
|
|
48
|
+
/** Model answering the last assistant message; before the first, the pinned model (pi's `id:thinking` suffix split off). */
|
|
49
|
+
model?: string;
|
|
50
|
+
/** Thinking level of the last assistant message; absent for models without thinking. */
|
|
51
|
+
thinkingLevel?: string;
|
|
48
52
|
stopReason?: string;
|
|
49
53
|
lastEventAgeSec: number | null;
|
|
50
54
|
spawnError?: string;
|
|
@@ -113,11 +117,16 @@ function errTailFor(lane: LaneRecord): string | undefined {
|
|
|
113
117
|
|
|
114
118
|
/** Derive a lane's status from its events file, PID liveness, and manifest facts. Never stored. */
|
|
115
119
|
export function deriveLane(lane: LaneRecord, now: number): LaneStatus {
|
|
120
|
+
// pi's --model splits a `:thinking` suffix only on the last colon and only for a valid
|
|
121
|
+
// level word (model-resolver's nSuffix) — ids like `qwen/qwen3-coder:exacto` keep their colon.
|
|
122
|
+
const pinned = lane.model?.match(/^(.+):(off|minimal|low|medium|high|xhigh|max)$/);
|
|
116
123
|
const base: LaneStatus = {
|
|
117
124
|
name: lane.name,
|
|
118
125
|
state: "failed",
|
|
119
126
|
turns: 0,
|
|
120
127
|
usage: emptyUsage(),
|
|
128
|
+
model: pinned?.[1] ?? lane.model,
|
|
129
|
+
thinkingLevel: pinned?.[2],
|
|
121
130
|
lastEventAgeSec: null,
|
|
122
131
|
};
|
|
123
132
|
|
|
@@ -135,6 +144,12 @@ export function deriveLane(lane: LaneRecord, now: number): LaneStatus {
|
|
|
135
144
|
base.usage.cacheWrite += u.cacheWrite ?? 0;
|
|
136
145
|
base.usage.cost += u.cost?.total ?? 0;
|
|
137
146
|
if (typeof message.stopReason === "string") base.stopReason = message.stopReason;
|
|
147
|
+
// The last assistant message is authoritative for identity — a message without
|
|
148
|
+
// thinkingLevel (model switch to a non-thinking model) clears a stale level.
|
|
149
|
+
if (typeof message.model === "string") {
|
|
150
|
+
base.model = message.model;
|
|
151
|
+
base.thinkingLevel = typeof message.thinkingLevel === "string" ? message.thinkingLevel : undefined;
|
|
152
|
+
}
|
|
138
153
|
}
|
|
139
154
|
}
|
|
140
155
|
if (fs.existsSync(lane.eventsPath)) {
|
package/src/events.ts
CHANGED
package/src/format.ts
CHANGED
|
@@ -7,7 +7,11 @@ import type { LaneStatus } from "./derive.ts";
|
|
|
7
7
|
|
|
8
8
|
/** One status-table line per lane (two-space indent, err tail four-space indented). */
|
|
9
9
|
export function formatLaneLine(s: LaneStatus): string {
|
|
10
|
-
|
|
10
|
+
// Short form: provider prefix dropped, thinking level labeled `think:` (a bare level word
|
|
11
|
+
// reads like a model variant) — the full ids live in the structured status payload.
|
|
12
|
+
const id = s.model ? s.model.split("/").pop() : undefined;
|
|
13
|
+
const tag = id === undefined ? "" : ` [${id}${s.thinkingLevel !== undefined ? ` think:${s.thinkingLevel}` : ""}]`;
|
|
14
|
+
const bits = [`${s.name}${tag}`, s.state];
|
|
11
15
|
if (s.cause) bits.push(`(${s.cause}${s.spawnError ? `: ${s.spawnError}` : ""})`);
|
|
12
16
|
if (s.overdue) bits.push("OVERDUE");
|
|
13
17
|
bits.push(s.turns === 1 ? "1 turn" : `${s.turns} turns`);
|
package/src/index.ts
CHANGED
|
@@ -108,7 +108,7 @@ const dispatchTool = defineTool({
|
|
|
108
108
|
Type.Object({
|
|
109
109
|
name: Type.String({ description: "Lane slug (kebab-case, max 64 chars, unique within the wave)" }),
|
|
110
110
|
briefPath: Type.String({ description: "Path to the lane's brief file (@file prompt)" }),
|
|
111
|
-
model: Type.Optional(Type.String({ description: "Pin a model id for this lane; omitted: pi's configured default model — not necessarily the dispatcher session's active model" })),
|
|
111
|
+
model: Type.Optional(Type.String({ description: "Pin a model id for this lane; omitted: pi's configured default model — not necessarily the dispatcher session's active model. Accepts pi's `id:thinking` suffix (e.g. 'zai/glm-4.6:high')" })),
|
|
112
112
|
deadlineSec: Type.Optional(Type.Number({ description: "Deadline leash in seconds (config default: 900)", minimum: 1 })),
|
|
113
113
|
tools: Type.Optional(Type.String({ description: "Tool allowlist for --tools; omitted: 'read,grep,find,ls' — add 'bash' when a lane must run commands (bash can still invoke git). Plain names or mcp__/tool patterns only: +name/-name modifiers, shell quoting, and empty lists are rejected. With mcp enabled, MCP/web tools are reachable only through 'codemode' or 'tool_search' named here ('mcp__server__*' entries declare tools directly only for direct-exposure servers)" })),
|
|
114
114
|
noExtensions: Type.Optional(Type.Boolean({ description: "Pass -ne (drop extensions incl. MCP)" })),
|
|
@@ -209,6 +209,7 @@ const lanesTool = defineTool({
|
|
|
209
209
|
description:
|
|
210
210
|
"Observe/control a dispatched wave. action=status (default): per-lane derived state " +
|
|
211
211
|
"(starting/running/settled/failed + cause spawn|killed|deadline|crashed), turns, run-total usage, " +
|
|
212
|
+
"model + thinking level (actual, from the last assistant message; pinned value before the first), " +
|
|
212
213
|
"stopReason, last-event age, .err tail for crashed lanes. action=wait: hold this call open, " +
|
|
213
214
|
"re-deriving at the pollMs cadence, until every lane is settled/failed or waitSec (default 300, " +
|
|
214
215
|
"max 3600) elapses — then return the status table; replaces blind sleep-then-status polling. " +
|