pi-messenger 0.14.0 → 0.15.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 +31 -1
- package/README.md +81 -6
- package/config-overlay.ts +11 -12
- package/config.ts +11 -5
- package/crew/agents/crew-worker.md +3 -3
- package/crew/agents.ts +39 -22
- package/crew/handlers/coordination.ts +72 -15
- package/crew/handlers/plan.ts +104 -38
- package/crew/handlers/review.ts +20 -11
- package/crew/handlers/revise.ts +15 -13
- package/crew/handlers/status.ts +10 -10
- package/crew/handlers/sync.ts +11 -10
- package/crew/handlers/task.ts +173 -26
- package/crew/handlers/team.ts +235 -0
- package/crew/handlers/work.ts +71 -29
- package/crew/index.ts +44 -29
- package/crew/live-progress.ts +1 -1
- package/crew/lobby.ts +18 -15
- package/crew/prompt.ts +115 -19
- package/crew/registry.ts +1 -1
- package/crew/spawn.ts +21 -16
- package/crew/state-autonomous.ts +1 -1
- package/crew/state-planning.ts +1 -1
- package/crew/state.ts +2 -2
- package/crew/store.ts +39 -10
- package/crew/task-actions.ts +13 -5
- package/crew/team/store.ts +479 -0
- package/crew/team/subagent-roles.ts +87 -0
- package/crew/team/types.ts +50 -0
- package/crew/types.ts +25 -5
- package/crew/utils/artifacts.ts +11 -1
- package/crew/utils/config.ts +1 -1
- package/crew/utils/discover.ts +1 -1
- package/crew/utils/progress.ts +73 -6
- package/crew/utils/risk-labels.ts +7 -0
- package/crew/utils/task-format.ts +14 -0
- package/crew/utils/team-roles.ts +28 -0
- package/feed.ts +52 -11
- package/handlers.ts +34 -30
- package/index.ts +183 -114
- package/install.mjs +30 -2
- package/overlay-actions.ts +12 -12
- package/overlay-coordinator.ts +1 -1
- package/overlay-render.ts +54 -30
- package/overlay.ts +76 -43
- package/package.json +14 -3
- package/skills/pi-messenger-crew/SKILL.md +38 -4
- package/store.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [0.15.0] - 2026-07-30
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added an optional Team layer with reusable profiles, project charter/memory, role-aware tasks, approval gates, worker context, and overlay signals.
|
|
7
|
+
- Added `team.setup` for first-run Team setup with profile activation, starter charter creation, and next-step guidance.
|
|
8
|
+
- Surfaced rejected approval-gated tasks separately with `task.revise` / `task.revise-tree` guidance.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Migrated extension tool schemas from `@sinclair/typebox` to `typebox` 1.x and updated the local test/tsconfig references to the new package entry.
|
|
12
|
+
- Guarded status heartbeat updates against stale pi session contexts after reloads or session replacement while preserving non-stale errors, fixing #26, #25, and #18. Thanks to Kire Howard (`MatrixJockey`), `MartinMayday`, and Hydro (`HydroToxin`) for the reports, and to Riwut Libinuko (`cakriwut`) for #19.
|
|
13
|
+
- Excluded the orchestrator-only `pi-messenger-crew` skill from worker prompts to avoid irrelevant skill loading during Crew task execution. Thanks to Varun Maliwal (`vmaliwal`) for #15.
|
|
14
|
+
- Fixed auto-opened Crew overlays after planning to use the live session cwd.
|
|
15
|
+
- Restored `.js` extension path handling in Crew worker and lobby tool configuration.
|
|
16
|
+
- Made enum schemas load under typebox compatibility shims that do not expose `Type.Unsafe`, fixing #23. Thanks to David Moshal (`davidmoshal`) for #24.
|
|
17
|
+
- Enforced `crew.concurrency.max` in overlay-driven worker spawning paths. Thanks to Martin Hátaš (`MartinHatas`) for #17.
|
|
18
|
+
- Prevented the legacy `npx pi-messenger` installer from creating a duplicate extension copy when the native `pi install npm:pi-messenger` package flow is already configured, fixing #22. Thanks to David Moshal (`davidmoshal`) for the report.
|
|
19
|
+
- Crew planner, worker, reviewer, analyst, lobby, and revision agents now fall back to the host session model when no task, request, role, or config model override is set, fixing #20. Thanks to Velinus (`velinussage`) for the report.
|
|
20
|
+
- Crew subprocess launches use `pi.cmd` on Windows and task reset paths now refuse active workers before making tasks startable again, addressing #11. Thanks to Logan Laughlin (`llaughlin`) for the report.
|
|
21
|
+
- Compacted streaming `message_update` artifacts and stopped retaining every parsed event in memory, fixing the transcript amplification in #27. Thanks to Hugo Ruíz (`hugotown`) and `aeturnal` for the measurements, and to `suse-coder` (#13) and Tom (`monotykamary`) (#4) for the earlier reports of the resulting stalls and jank.
|
|
22
|
+
- Crew subprocesses now fail fast on terminal provider usage/quota/auth 4xx JSON events instead of waiting through retry loops, fixing #21. Thanks to Velinus (`velinussage`) for the report.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- `readFeedEvents` now reads only a bounded tail of `feed.jsonl` in backward 64KB chunks instead of parsing the whole file, making overlay feed reads on large histories dramatically faster (~1,800x on a 100k-event feed). Non-positive limits now return no events.
|
|
26
|
+
- Overlay rendering, auto-spawn/refill checks, and stuck-agent heartbeats now reuse a single Crew task snapshot per frame instead of rescanning the task directory per section, roughly quadrupling render-helper throughput on large task lists.
|
|
27
|
+
- Aligned Team built-in roles and sample profiles with the packaged `pi-subagents` role vocabulary while keeping Crew as the execution engine.
|
|
28
|
+
- Moved pi core packages and `typebox` to peer dependencies for packaged installs.
|
|
29
|
+
|
|
30
|
+
## [0.14.1] - 2026-04-04
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Added a `promptSnippet` for `pi_messenger` so Pi 0.59+ includes it in the default tool prompt section and reliably surfaces Crew/coordination workflows.
|
|
4
34
|
|
|
5
35
|
## [0.14.0] - 2026-04-03
|
|
6
36
|
|
package/README.md
CHANGED
|
@@ -154,6 +154,59 @@ Error responses use `{ error: { code, message, details? } }` shape.
|
|
|
154
154
|
|
|
155
155
|
Any skills you already have in `~/.pi/agent/skills/` are automatically available to crew workers — no setup needed.
|
|
156
156
|
|
|
157
|
+
### Team Layer
|
|
158
|
+
|
|
159
|
+
Team is an optional layer around Crew. Crew still plans and executes tasks; Team adds project-local roles, a charter, durable memory, reusable JSON profiles, and high-risk approval gates. Active Team state lives in `.pi/messenger/team/`. Reusable profiles live in `~/.pi/agent/messenger/team-profiles/`.
|
|
160
|
+
|
|
161
|
+
Most users should talk to their agent in plain language:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
Use the review squad for this cleanup.
|
|
165
|
+
Use a migration team and pause before risky database changes.
|
|
166
|
+
Research this first, then plan the implementation.
|
|
167
|
+
Approve the auth API task.
|
|
168
|
+
Reject the migration task; it needs rollback tests.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The agent maps those requests to Team actions. If a task needs approval, the agent should ask in plain language and continue after you approve. The tool calls are mainly for agents and power users:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
pi_messenger({ action: "team.setup", name: "migration-squad" })
|
|
175
|
+
pi_messenger({ action: "team.memory.note", type: "decision", message: "Auth API changes require reviewer sign-off." })
|
|
176
|
+
pi_messenger({ action: "team.roles" })
|
|
177
|
+
pi_messenger({ action: "team.status" })
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`team.setup` activates the profile, saves an editable JSON copy if needed, creates a starter charter when the project does not have one, and returns the next planning/status commands.
|
|
181
|
+
|
|
182
|
+
When Team is active, planner task JSON may include `role` and `riskLabels`. Tasks persist those as `role`, `risk_labels`, and `approval`; existing tasks without those fields still work. Workers receive bounded Team role, charter, memory, and approval context. `work` skips tasks that require approval but are not approved and returns pending approvals under `needsApproval`; rejected tasks are surfaced separately with `task.revise` / `task.revise-tree` guidance.
|
|
183
|
+
|
|
184
|
+
Team's built-in role names follow the packaged `pi-subagents` vocabulary where possible: `context-builder`, `delegate`, `oracle`, `planner`, `researcher`, `reviewer`, `scout`, and `worker`. Roles resolve from those built-in defaults, the active profile, and optional filesystem metadata from `pi-subagents` markdown files when present. `pi-messenger` only reads those files; it does not require or call the subagent extension, and Crew still uses its own Crew agents for execution.
|
|
185
|
+
|
|
186
|
+
Built-in sample profiles are available immediately and are saved as editable JSON the first time you activate them:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
pi_messenger({ action: "team.setup", name: "migration-squad" }) // migrations with approval gates
|
|
190
|
+
pi_messenger({ action: "team.setup", name: "review-squad" }) // scout/reviewer/worker cleanup flow
|
|
191
|
+
pi_messenger({ action: "team.setup", name: "research-squad" }) // research-first planning flow
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
A saved profile looks like this:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"name": "migration-squad",
|
|
199
|
+
"description": "Scout, implement, and review high-risk migrations with lead approval gates",
|
|
200
|
+
"roles": {
|
|
201
|
+
"scout": { "description": "Map affected schemas, APIs, and rollback paths before implementation" },
|
|
202
|
+
"worker": { "description": "Implement the approved migration in small, reversible steps" },
|
|
203
|
+
"reviewer": { "description": "Review migration safety, compatibility, rollback, and tests" }
|
|
204
|
+
},
|
|
205
|
+
"approval": { "mode": "risk-labels", "labels": ["database", "migration", "destructive", "api-contract"] },
|
|
206
|
+
"memory": { "inject": ["decision", "interface", "risk", "handoff"], "maxCharsPerType": 4000 }
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
157
210
|
### Crew Configuration
|
|
158
211
|
|
|
159
212
|
Crew spawns multiple LLM sessions in parallel — it can burn tokens fast. Start with a cheap worker model and scale up once you've seen the workflow. Add this to `~/.pi/agent/pi-messenger.json`:
|
|
@@ -162,7 +215,7 @@ Crew spawns multiple LLM sessions in parallel — it can burn tokens fast. Start
|
|
|
162
215
|
{ "crew": { "models": { "worker": "claude-haiku-4-5" } } }
|
|
163
216
|
```
|
|
164
217
|
|
|
165
|
-
|
|
218
|
+
By default, Crew agents inherit the host session model unless a task, request, role, config, or agent frontmatter model says otherwise. Override per-role as needed:
|
|
166
219
|
|
|
167
220
|
```json
|
|
168
221
|
{
|
|
@@ -216,10 +269,10 @@ Full config reference (all fields optional — only set what you want to change)
|
|
|
216
269
|
| `dependencies` | Dependency scheduling mode: `advisory` or `strict` | `"advisory"` |
|
|
217
270
|
| `coordination` | Worker coordination level: `none`, `minimal`, `moderate`, `chatty` | `"chatty"` |
|
|
218
271
|
| `messageBudgets` | Max outgoing messages per worker per level (sends rejected after limit) | `{ none: 0, minimal: 2, moderate: 5, chatty: 10 }` |
|
|
219
|
-
| `models.planner` | Model for planner agent |
|
|
220
|
-
| `models.worker` | Model for workers (overridden by per-task or per-wave `model` param) |
|
|
221
|
-
| `models.reviewer` | Model for reviewer agent |
|
|
222
|
-
| `models.analyst` | Model for analyst (plan-sync) agent |
|
|
272
|
+
| `models.planner` | Model for planner agent | host session model, then agent frontmatter |
|
|
273
|
+
| `models.worker` | Model for workers (overridden by per-task or per-wave `model` param) | host session model, then agent frontmatter |
|
|
274
|
+
| `models.reviewer` | Model for reviewer agent | host session model, then agent frontmatter |
|
|
275
|
+
| `models.analyst` | Model for analyst (plan-sync) agent | host session model, then agent frontmatter |
|
|
223
276
|
| `thinking.planner` | Thinking level for planner agent | (from frontmatter) |
|
|
224
277
|
| `thinking.worker` | Thinking level for worker agents | (from frontmatter) |
|
|
225
278
|
| `thinking.reviewer` | Thinking level for reviewer agents | (from frontmatter) |
|
|
@@ -231,10 +284,12 @@ Full config reference (all fields optional — only set what you want to change)
|
|
|
231
284
|
| `work.maxWaves` | Max autonomous waves | `50` |
|
|
232
285
|
| `work.shutdownGracePeriodMs` | Grace period before SIGTERM on abort | `30000` |
|
|
233
286
|
| `work.env` | Environment variables passed to spawned workers | `{}` |
|
|
287
|
+
| `artifacts.enabled` | Write compact Crew debug artifacts | `true` |
|
|
288
|
+
| `artifacts.cleanupDays` | Retention setting for Crew artifacts | `7` |
|
|
234
289
|
|
|
235
290
|
### Default Agent Models
|
|
236
291
|
|
|
237
|
-
Each crew agent ships with a
|
|
292
|
+
Each crew agent ships with a fallback model in its frontmatter. Override any role via `crew.models.<role>` in config:
|
|
238
293
|
|
|
239
294
|
| Agent | Role | Default Model |
|
|
240
295
|
|-------|------|---------------|
|
|
@@ -275,6 +330,8 @@ Agent definitions live in `crew/agents/` within the extension. To customize one
|
|
|
275
330
|
| `task.list` | List all tasks |
|
|
276
331
|
| `task.show` | Show task details (`id` required) |
|
|
277
332
|
| `task.start` | Start a task (`id` required) |
|
|
333
|
+
| `task.approve` | Approve an approval-gated task (`id` required) |
|
|
334
|
+
| `task.reject` | Reject an approval-gated task (`id` required, `reason` optional) |
|
|
278
335
|
| `task.done` | Complete a task (`id` required, `summary` optional) |
|
|
279
336
|
| `task.block` | Block a task (`id` + `reason` required) |
|
|
280
337
|
| `task.unblock` | Unblock a task (`id` required) |
|
|
@@ -286,6 +343,24 @@ Agent definitions live in `crew/agents/` within the extension. To customize one
|
|
|
286
343
|
| `crew.install` | Show discovered crew agents and their sources |
|
|
287
344
|
| `crew.uninstall` | Remove stale shared-directory crew agent copies |
|
|
288
345
|
|
|
346
|
+
### Team
|
|
347
|
+
|
|
348
|
+
| Action | Description |
|
|
349
|
+
|--------|-------------|
|
|
350
|
+
| `team.setup` | Activate a profile, create a starter charter if missing, and show next steps (`name` optional, defaults to `migration-squad`) |
|
|
351
|
+
| `team.profile.list` | List built-in samples and saved reusable JSON team profiles |
|
|
352
|
+
| `team.profile.use` | Activate a profile (`name` required; saves a sample/default profile if missing) |
|
|
353
|
+
| `team.profile.save` | Save the active profile under `name` |
|
|
354
|
+
| `team.charter.show` | Show the project team charter |
|
|
355
|
+
| `team.charter.create` | Create or replace the charter (`name` + `message` required) |
|
|
356
|
+
| `team.charter.update` | Append a charter update (`message` required) |
|
|
357
|
+
| `team.memory.note` | Append team memory (`type`: `decision`, `interface`, `risk`, or `handoff`; `message` required) |
|
|
358
|
+
| `team.memory.list` | List team memory (`type` and `limit` optional) |
|
|
359
|
+
| `team.roles` | Resolve Team roles from packaged-vocabulary defaults, profile config, and optional subagent metadata |
|
|
360
|
+
| `team.status` | Summarize team/profile/charter, roles, memory counts, and needs-lead tasks |
|
|
361
|
+
|
|
362
|
+
Approval-gated tasks use the Crew task commands `task.approve` and `task.reject`. Rejected tasks stay blocked from work and are surfaced with `task.revise` / `task.revise-tree` next steps.
|
|
363
|
+
|
|
289
364
|
### Swarm (Spec-Based)
|
|
290
365
|
|
|
291
366
|
| Action | Description |
|
package/config-overlay.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Pi Messenger - Config Overlay Component
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { Component, Focusable, TUI } from "@
|
|
6
|
-
import { matchesKey, truncateToWidth, visibleWidth } from "@
|
|
7
|
-
import type { Theme } from "@
|
|
8
|
-
import { getAutoRegisterPaths, saveAutoRegisterPaths, matchesAutoRegisterPath } from "./config.
|
|
5
|
+
import type { Component, Focusable, TUI } from "@earendil-works/pi-tui";
|
|
6
|
+
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
7
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { getAutoRegisterPaths, saveAutoRegisterPaths, matchesAutoRegisterPath } from "./config.ts";
|
|
9
9
|
|
|
10
10
|
export class MessengerConfigOverlay implements Component, Focusable {
|
|
11
11
|
readonly width = 60;
|
|
@@ -19,7 +19,8 @@ export class MessengerConfigOverlay implements Component, Focusable {
|
|
|
19
19
|
constructor(
|
|
20
20
|
private tui: TUI,
|
|
21
21
|
private theme: Theme,
|
|
22
|
-
private done: () => void
|
|
22
|
+
private done: () => void,
|
|
23
|
+
private cwd: string,
|
|
23
24
|
) {
|
|
24
25
|
this.paths = getAutoRegisterPaths();
|
|
25
26
|
}
|
|
@@ -63,12 +64,11 @@ export class MessengerConfigOverlay implements Component, Focusable {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
private addCurrentPath(): void {
|
|
66
|
-
|
|
67
|
-
if (this.paths.includes(cwd)) {
|
|
67
|
+
if (this.paths.includes(this.cwd)) {
|
|
68
68
|
this.statusMessage = "Already in list";
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
this.paths.push(cwd);
|
|
71
|
+
this.paths.push(this.cwd);
|
|
72
72
|
this.selectedIndex = this.paths.length - 1;
|
|
73
73
|
this.dirty = true;
|
|
74
74
|
this.statusMessage = "Added current folder";
|
|
@@ -88,8 +88,7 @@ export class MessengerConfigOverlay implements Component, Focusable {
|
|
|
88
88
|
const w = this.width;
|
|
89
89
|
const innerW = w - 2;
|
|
90
90
|
const lines: string[] = [];
|
|
91
|
-
const
|
|
92
|
-
const isCurrentInList = matchesAutoRegisterPath(cwd, this.paths);
|
|
91
|
+
const isCurrentInList = matchesAutoRegisterPath(this.cwd, this.paths);
|
|
93
92
|
|
|
94
93
|
const border = (s: string) => this.theme.fg("dim", s);
|
|
95
94
|
const pad = (s: string, len: number) => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
|
|
@@ -106,7 +105,7 @@ export class MessengerConfigOverlay implements Component, Focusable {
|
|
|
106
105
|
lines.push(emptyRow());
|
|
107
106
|
|
|
108
107
|
// Current folder status
|
|
109
|
-
const cwdDisplay = truncateToWidth(cwd, Math.max(10, innerW - 20));
|
|
108
|
+
const cwdDisplay = truncateToWidth(this.cwd, Math.max(10, innerW - 20));
|
|
110
109
|
lines.push(row(`Current folder: ${cwdDisplay}`));
|
|
111
110
|
const statusColor = isCurrentInList ? "accent" : "dim";
|
|
112
111
|
lines.push(row(`Auto-register: ${this.theme.fg(statusColor, isCurrentInList ? "YES" : "NO")}`));
|
|
@@ -126,7 +125,7 @@ export class MessengerConfigOverlay implements Component, Focusable {
|
|
|
126
125
|
for (let i = 0; i < this.paths.length; i++) {
|
|
127
126
|
const path = this.paths[i];
|
|
128
127
|
const isSelected = i === this.selectedIndex;
|
|
129
|
-
const isCurrent = path === cwd;
|
|
128
|
+
const isCurrent = path === this.cwd;
|
|
130
129
|
|
|
131
130
|
const marker = isSelected ? this.theme.fg("accent", "▸") : " ";
|
|
132
131
|
const suffix = isCurrent ? this.theme.fg("dim", " (current)") : "";
|
package/config.ts
CHANGED
|
@@ -125,8 +125,7 @@ export function getAutoRegisterPaths(): string[] {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
const projectPath = join(cwd, ".pi", "pi-messenger.json");
|
|
128
|
+
function buildConfig(projectConfig?: Partial<MessengerConfig> | null): MessengerConfig {
|
|
130
129
|
const extensionGlobalPath = join(homedir(), ".pi", "agent", "pi-messenger.json");
|
|
131
130
|
const mainSettingsPath = join(homedir(), ".pi", "agent", "settings.json");
|
|
132
131
|
|
|
@@ -140,9 +139,6 @@ export function loadConfig(cwd: string): MessengerConfig {
|
|
|
140
139
|
// Load extension-specific global config
|
|
141
140
|
const extensionConfig = readJsonFile(extensionGlobalPath) as Partial<MessengerConfig> | null;
|
|
142
141
|
|
|
143
|
-
// Load project config (highest priority)
|
|
144
|
-
const projectConfig = readJsonFile(projectPath) as Partial<MessengerConfig> | null;
|
|
145
|
-
|
|
146
142
|
const merged = {
|
|
147
143
|
...DEFAULT_CONFIG,
|
|
148
144
|
...settingsConfig,
|
|
@@ -201,3 +197,13 @@ export function loadConfig(cwd: string): MessengerConfig {
|
|
|
201
197
|
...sharedFields,
|
|
202
198
|
};
|
|
203
199
|
}
|
|
200
|
+
|
|
201
|
+
export function loadGlobalConfig(): MessengerConfig {
|
|
202
|
+
return buildConfig();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function loadConfig(cwd: string): MessengerConfig {
|
|
206
|
+
const projectPath = join(cwd, ".pi", "pi-messenger.json");
|
|
207
|
+
const projectConfig = readJsonFile(projectPath) as Partial<MessengerConfig> | null;
|
|
208
|
+
return buildConfig(projectConfig);
|
|
209
|
+
}
|
|
@@ -37,15 +37,15 @@ read({ path: ".pi/messenger/crew/tasks/<TASK_ID>.md" })
|
|
|
37
37
|
|
|
38
38
|
## Phase 2.5: Load Relevant Skills
|
|
39
39
|
|
|
40
|
-
If your task prompt includes an **Available Skills** section, read
|
|
40
|
+
If your task prompt includes an **Available Skills** section, read skills that match what you're building (for example a framework, testing library, or domain-specific tool) before starting implementation.
|
|
41
41
|
|
|
42
|
-
If skills are marked **Recommended for this task**, read those first.
|
|
42
|
+
You already have every `pi_messenger` action you need in this prompt, so do not load Crew orchestration references. If skills are marked **Recommended for this task**, read those first.
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
45
|
read({ path: "<skill-path-from-the-list>" })
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Skip this phase if no Available Skills section is present.
|
|
48
|
+
Skip this phase if no Available Skills section is present or no skills match your implementation work.
|
|
49
49
|
|
|
50
50
|
## Phase 3: Start Task & Reserve Files
|
|
51
51
|
|
package/crew/agents.ts
CHANGED
|
@@ -10,28 +10,30 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as os from "node:os";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
-
import { discoverCrewAgents, type CrewAgentConfig } from "./utils/discover.
|
|
14
|
-
import { truncateOutput } from "./utils/truncate.
|
|
13
|
+
import { discoverCrewAgents, type CrewAgentConfig } from "./utils/discover.ts";
|
|
14
|
+
import { truncateOutput } from "./utils/truncate.ts";
|
|
15
15
|
import {
|
|
16
16
|
createProgress,
|
|
17
17
|
parseJsonlLine,
|
|
18
18
|
updateProgress,
|
|
19
|
-
|
|
19
|
+
getAssistantText,
|
|
20
|
+
compactEventForArtifact,
|
|
21
|
+
getTerminalProviderError,
|
|
20
22
|
type PiEvent,
|
|
21
|
-
} from "./utils/progress.
|
|
23
|
+
} from "./utils/progress.ts";
|
|
22
24
|
import {
|
|
23
25
|
getArtifactPaths,
|
|
24
26
|
ensureArtifactsDir,
|
|
25
27
|
writeArtifact,
|
|
26
28
|
writeMetadata,
|
|
27
29
|
appendJsonl
|
|
28
|
-
} from "./utils/artifacts.
|
|
29
|
-
import { loadCrewConfig, getTruncationForRole, type CrewConfig } from "./utils/config.
|
|
30
|
-
import { removeLiveWorker, updateLiveWorker } from "./live-progress.
|
|
31
|
-
import { autonomousState, waitForConcurrencyChange } from "./state.
|
|
32
|
-
import { registerWorker, unregisterWorker, killAll } from "./registry.
|
|
33
|
-
import type { AgentTask, AgentResult } from "./types.
|
|
34
|
-
import { generateMemorableName } from "../lib.
|
|
30
|
+
} from "./utils/artifacts.ts";
|
|
31
|
+
import { loadCrewConfig, getTruncationForRole, type CrewConfig } from "./utils/config.ts";
|
|
32
|
+
import { removeLiveWorker, updateLiveWorker } from "./live-progress.ts";
|
|
33
|
+
import { autonomousState, waitForConcurrencyChange } from "./state.ts";
|
|
34
|
+
import { registerWorker, unregisterWorker, killAll } from "./registry.ts";
|
|
35
|
+
import type { AgentTask, AgentResult } from "./types.ts";
|
|
36
|
+
import { generateMemorableName } from "../lib.ts";
|
|
35
37
|
|
|
36
38
|
const __filename = fileURLToPath(import.meta.url);
|
|
37
39
|
const __dirname = path.dirname(__filename);
|
|
@@ -52,10 +54,16 @@ export function shutdownAllWorkers(): void {
|
|
|
52
54
|
export function resolveModel(
|
|
53
55
|
taskModel?: string,
|
|
54
56
|
paramModel?: string,
|
|
57
|
+
roleModel?: string,
|
|
55
58
|
configModel?: string,
|
|
59
|
+
sessionModel?: string,
|
|
56
60
|
agentModel?: string,
|
|
57
61
|
): string | undefined {
|
|
58
|
-
return taskModel ?? paramModel ?? configModel ?? agentModel;
|
|
62
|
+
return taskModel ?? paramModel ?? roleModel ?? configModel ?? sessionModel ?? agentModel;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getPiCommand(): string {
|
|
66
|
+
return process.platform === "win32" ? "pi.cmd" : "pi";
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
export function pushModelArgs(args: string[], model: string): void {
|
|
@@ -138,7 +146,7 @@ export async function spawnAgents(
|
|
|
138
146
|
// Setup artifacts directory if enabled
|
|
139
147
|
const artifactsDir = path.join(crewDir, "artifacts");
|
|
140
148
|
if (config.artifacts.enabled) {
|
|
141
|
-
ensureArtifactsDir(artifactsDir);
|
|
149
|
+
ensureArtifactsDir(artifactsDir, config.artifacts.cleanupDays);
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
const results: AgentResult[] = [];
|
|
@@ -254,7 +262,7 @@ async function runAgent(
|
|
|
254
262
|
? { ...process.env, ...envOverrides, ...workerFlag }
|
|
255
263
|
: undefined;
|
|
256
264
|
|
|
257
|
-
const proc = spawn(
|
|
265
|
+
const proc = spawn(getPiCommand(), args, {
|
|
258
266
|
cwd,
|
|
259
267
|
stdio: ["ignore", "pipe", "pipe"],
|
|
260
268
|
...(env ? { env } : {}),
|
|
@@ -266,7 +274,8 @@ async function runAgent(
|
|
|
266
274
|
let discoveredWorkerName: string | null = null;
|
|
267
275
|
|
|
268
276
|
let jsonlBuffer = "";
|
|
269
|
-
|
|
277
|
+
let fullOutput = "";
|
|
278
|
+
let terminalProviderError: string | null = null;
|
|
270
279
|
|
|
271
280
|
proc.stdout?.on("data", (data) => {
|
|
272
281
|
try {
|
|
@@ -277,12 +286,19 @@ async function runAgent(
|
|
|
277
286
|
for (const line of lines) {
|
|
278
287
|
const event = parseJsonlLine(line);
|
|
279
288
|
if (event) {
|
|
280
|
-
events.push(event);
|
|
281
289
|
updateProgress(progress, event, startTime);
|
|
290
|
+
const assistantText = getAssistantText(event);
|
|
291
|
+
if (assistantText) fullOutput = assistantText;
|
|
282
292
|
if (artifactPaths) {
|
|
283
|
-
try { appendJsonl(artifactPaths.jsonlPath,
|
|
293
|
+
try { appendJsonl(artifactPaths.jsonlPath, JSON.stringify(compactEventForArtifact(event))); }
|
|
284
294
|
catch { artifactPaths = undefined; }
|
|
285
295
|
}
|
|
296
|
+
const providerError = getTerminalProviderError(event);
|
|
297
|
+
if (providerError && !terminalProviderError) {
|
|
298
|
+
terminalProviderError = providerError;
|
|
299
|
+
progress.error = providerError;
|
|
300
|
+
proc.kill("SIGTERM");
|
|
301
|
+
}
|
|
286
302
|
if (task.taskId) {
|
|
287
303
|
updateLiveWorker(cwd, task.taskId, {
|
|
288
304
|
taskId: task.taskId,
|
|
@@ -308,11 +324,12 @@ async function runAgent(
|
|
|
308
324
|
removeLiveWorker(cwd, task.taskId);
|
|
309
325
|
unregisterWorker(cwd, task.taskId);
|
|
310
326
|
}
|
|
311
|
-
|
|
327
|
+
const exitCode = terminalProviderError ? 1 : code ?? 1;
|
|
328
|
+
progress.status = exitCode === 0 ? "completed" : "failed";
|
|
312
329
|
progress.durationMs = Date.now() - startTime;
|
|
313
|
-
if (
|
|
330
|
+
if (terminalProviderError) progress.error = terminalProviderError;
|
|
331
|
+
else if (stderr && exitCode !== 0) progress.error = stderr;
|
|
314
332
|
|
|
315
|
-
const fullOutput = getFinalOutput(events);
|
|
316
333
|
const truncation = truncateOutput(fullOutput, maxOutput, artifactPaths?.outputPath);
|
|
317
334
|
|
|
318
335
|
if (artifactPaths) {
|
|
@@ -322,7 +339,7 @@ async function runAgent(
|
|
|
322
339
|
runId,
|
|
323
340
|
agent: task.agent,
|
|
324
341
|
index,
|
|
325
|
-
exitCode
|
|
342
|
+
exitCode,
|
|
326
343
|
durationMs: progress.durationMs,
|
|
327
344
|
tokens: progress.tokens,
|
|
328
345
|
truncated: truncation.truncated,
|
|
@@ -337,7 +354,7 @@ async function runAgent(
|
|
|
337
354
|
|
|
338
355
|
resolve({
|
|
339
356
|
agent: task.agent,
|
|
340
|
-
exitCode
|
|
357
|
+
exitCode,
|
|
341
358
|
output: truncation.text,
|
|
342
359
|
truncated: truncation.truncated,
|
|
343
360
|
progress,
|
|
@@ -7,16 +7,21 @@
|
|
|
7
7
|
* time — no runtime worker state.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { Task } from "../types.
|
|
11
|
-
import type { CrewConfig } from "../utils/config.
|
|
12
|
-
import { readFeedEvents, type FeedEvent } from "../../feed.
|
|
13
|
-
import * as store from "../store.
|
|
10
|
+
import type { Task } from "../types.ts";
|
|
11
|
+
import type { CrewConfig } from "../utils/config.ts";
|
|
12
|
+
import { readFeedEvents, type FeedEvent } from "../../feed.ts";
|
|
13
|
+
import * as store from "../store.ts";
|
|
14
|
+
import * as teamStore from "../team/store.ts";
|
|
15
|
+
|
|
16
|
+
interface CoordinationPromptOptions {
|
|
17
|
+
readOnly?: boolean;
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
// =============================================================================
|
|
16
21
|
// Dependency Section
|
|
17
22
|
// =============================================================================
|
|
18
23
|
|
|
19
|
-
export function buildDependencySection(cwd: string, task: Task, config: CrewConfig): string {
|
|
24
|
+
export function buildDependencySection(cwd: string, task: Task, config: CrewConfig, options: CoordinationPromptOptions = {}): string {
|
|
20
25
|
if (config.dependencies === "advisory") {
|
|
21
26
|
const lines: string[] = [
|
|
22
27
|
"## Dependency Status\n",
|
|
@@ -51,10 +56,12 @@ export function buildDependencySection(cwd: string, task: Task, config: CrewConf
|
|
|
51
56
|
if (config.coordination !== "none") {
|
|
52
57
|
lines.push("- DM in-progress workers for API details they're building.");
|
|
53
58
|
}
|
|
59
|
+
if (!options.readOnly) lines.push("- Reserve your files before editing to prevent conflicts.");
|
|
54
60
|
lines.push(
|
|
55
|
-
"- Reserve your files before editing to prevent conflicts.",
|
|
56
61
|
"- Do NOT block yourself because a dependency isn't done. Work around it.",
|
|
57
|
-
|
|
62
|
+
options.readOnly
|
|
63
|
+
? "- Log findings, assumptions, and handoff notes in your progress for later reconciliation."
|
|
64
|
+
: "- Log any local definitions in your progress for later reconciliation.",
|
|
58
65
|
);
|
|
59
66
|
|
|
60
67
|
return lines.join("\n") + "\n\n";
|
|
@@ -130,6 +137,7 @@ export function buildCoordinationContext(
|
|
|
130
137
|
task: Task,
|
|
131
138
|
config: CrewConfig,
|
|
132
139
|
concurrentTasks: Task[],
|
|
140
|
+
options: CoordinationPromptOptions = {},
|
|
133
141
|
): string {
|
|
134
142
|
if (config.coordination === "none") return "";
|
|
135
143
|
|
|
@@ -164,17 +172,36 @@ These tasks are being worked on by other workers in this wave. Discover their ag
|
|
|
164
172
|
const concurrentIds = new Set(concurrentTasks.map(t => t.id));
|
|
165
173
|
const ready = store.getReadyTasks(cwd, { advisory: config.dependencies === "advisory" })
|
|
166
174
|
.filter(t => t.id !== task.id && !concurrentIds.has(t.id));
|
|
167
|
-
|
|
175
|
+
const claimable = ready.filter(t => !teamStore.taskNeedsApproval(t));
|
|
176
|
+
const needsApproval = ready.filter(teamStore.taskPendingApproval);
|
|
177
|
+
const rejected = ready.filter(teamStore.taskNeedsRevision);
|
|
178
|
+
if (claimable.length > 0) {
|
|
168
179
|
out += `## Ready Tasks
|
|
169
180
|
|
|
170
|
-
After completing your current task, you can claim one of these:
|
|
181
|
+
${options.readOnly ? "After completing your current task, only claim one of these if it fits your read-only assignment:" : "After completing your current task, you can claim one of these:"}
|
|
171
182
|
`;
|
|
172
|
-
for (const t of
|
|
183
|
+
for (const t of claimable) {
|
|
173
184
|
const deps = t.depends_on.length > 0 ? ` (deps: ${t.depends_on.join(", ")})` : "";
|
|
174
185
|
out += `- ${t.id}: ${t.title}${deps}\n`;
|
|
175
186
|
}
|
|
176
187
|
out += "\n";
|
|
177
188
|
}
|
|
189
|
+
if (needsApproval.length > 0) {
|
|
190
|
+
out += `## Ready Tasks Needing Approval
|
|
191
|
+
|
|
192
|
+
These tasks are ready but require lead approval before anyone can claim them:
|
|
193
|
+
`;
|
|
194
|
+
for (const t of needsApproval) out += `- ${t.id}: ${t.title} — approve with \`pi_messenger({ action: "task.approve", id: "${t.id}" })\`\n`;
|
|
195
|
+
out += "\n";
|
|
196
|
+
}
|
|
197
|
+
if (rejected.length > 0) {
|
|
198
|
+
out += `## Rejected Tasks Needing Revision
|
|
199
|
+
|
|
200
|
+
These tasks need revision before they can be approved or claimed:
|
|
201
|
+
`;
|
|
202
|
+
for (const t of rejected) out += `- ${t.id}: ${t.title}${t.approval?.feedback ? ` — ${t.approval.feedback}` : ""} — revise with \`pi_messenger({ action: "task.revise", id: "${t.id}", prompt: "Address approval feedback" })\`\n`;
|
|
203
|
+
out += "\n";
|
|
204
|
+
}
|
|
178
205
|
}
|
|
179
206
|
|
|
180
207
|
return out;
|
|
@@ -184,13 +211,23 @@ After completing your current task, you can claim one of these:
|
|
|
184
211
|
// Coordination Instructions
|
|
185
212
|
// =============================================================================
|
|
186
213
|
|
|
187
|
-
export function buildCoordinationInstructions(config: CrewConfig): string {
|
|
214
|
+
export function buildCoordinationInstructions(config: CrewConfig, options: CoordinationPromptOptions = {}): string {
|
|
188
215
|
const level = config.coordination;
|
|
189
216
|
if (level === "none") return "";
|
|
190
217
|
|
|
191
218
|
const budget = config.messageBudgets?.[level] ?? (level === "chatty" ? 10 : 5);
|
|
192
219
|
|
|
193
220
|
if (level === "minimal") {
|
|
221
|
+
if (options.readOnly) {
|
|
222
|
+
return `## Coordination
|
|
223
|
+
|
|
224
|
+
**Message budget: ${budget} messages this session.** The system enforces this.
|
|
225
|
+
|
|
226
|
+
Use \`pi_messenger({ action: "list" })\` to see active workers if your investigation overlaps with their work. Message a worker only when you need concrete context or a handoff detail.
|
|
227
|
+
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
|
|
194
231
|
return `## Coordination
|
|
195
232
|
|
|
196
233
|
**Message budget: ${budget} messages this session.** The system enforces this.
|
|
@@ -220,7 +257,16 @@ Do NOT edit files reserved by another worker without coordinating first.
|
|
|
220
257
|
|
|
221
258
|
`;
|
|
222
259
|
|
|
223
|
-
out +=
|
|
260
|
+
out += options.readOnly
|
|
261
|
+
? `### Announce yourself
|
|
262
|
+
After joining the mesh and starting your task, announce what you are investigating:
|
|
263
|
+
|
|
264
|
+
\`\`\`typescript
|
|
265
|
+
pi_messenger({ action: "broadcast", message: "Starting <task-id> (<title>) — investigating <scope>" })
|
|
266
|
+
\`\`\`
|
|
267
|
+
|
|
268
|
+
`
|
|
269
|
+
: `### Announce yourself
|
|
224
270
|
After joining the mesh and starting your task, announce what you're working on:
|
|
225
271
|
|
|
226
272
|
\`\`\`typescript
|
|
@@ -243,7 +289,16 @@ If a peer asks you a direct question, reply briefly. Ignore messages that don't
|
|
|
243
289
|
`;
|
|
244
290
|
}
|
|
245
291
|
|
|
246
|
-
out +=
|
|
292
|
+
out += options.readOnly
|
|
293
|
+
? `### On completion
|
|
294
|
+
Announce what you found or recommend:
|
|
295
|
+
|
|
296
|
+
\`\`\`typescript
|
|
297
|
+
pi_messenger({ action: "broadcast", message: "Completed <task-id>: found <summary>" })
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
`
|
|
301
|
+
: `### On completion
|
|
247
302
|
Announce what you built:
|
|
248
303
|
|
|
249
304
|
\`\`\`typescript
|
|
@@ -253,7 +308,9 @@ pi_messenger({ action: "broadcast", message: "Completed <task-id>: <file> export
|
|
|
253
308
|
### Reservations
|
|
254
309
|
Before editing files, check if another worker has reserved them via \`pi_messenger({ action: "list" })\`. If a file you need is reserved, message the owner to coordinate. Do NOT edit reserved files without coordinating first.
|
|
255
310
|
|
|
256
|
-
|
|
311
|
+
`;
|
|
312
|
+
|
|
313
|
+
out += `### Questions about dependencies
|
|
257
314
|
If your task depends on a completed task and something about its implementation is unclear, read the code and the task's progress log at \`.pi/messenger/crew/tasks/<task-id>.progress.md\`. Dependency authors are from previous waves and are no longer in the mesh.
|
|
258
315
|
|
|
259
316
|
`;
|
|
@@ -266,7 +323,7 @@ After completing your assigned task, check if there are ready tasks you can pick
|
|
|
266
323
|
pi_messenger({ action: "task.ready" })
|
|
267
324
|
\`\`\`
|
|
268
325
|
|
|
269
|
-
If a task is ready, claim
|
|
326
|
+
If a task is ready, claim it only if it matches your assignment mode. If \`task.start\` fails (another worker claimed it first or the task needs approval), check for other ready tasks. Only claim if your current task completed cleanly and quickly.
|
|
270
327
|
|
|
271
328
|
`;
|
|
272
329
|
}
|