pi-crew 0.6.4 → 0.7.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +68 -0
  3. package/package.json +2 -1
  4. package/src/extension/knowledge-injection.ts +71 -0
  5. package/src/extension/pi-api.ts +47 -0
  6. package/src/extension/register.ts +19 -6
  7. package/src/extension/registration/commands.ts +40 -1
  8. package/src/extension/registration/compaction-guard.ts +154 -14
  9. package/src/extension/team-tool/handle-settings.ts +57 -0
  10. package/src/extension/team-tool/inspect.ts +4 -1
  11. package/src/extension/team-tool/plan.ts +8 -1
  12. package/src/runtime/intercom-bridge.ts +5 -1
  13. package/src/runtime/replace.ts +555 -0
  14. package/src/runtime/resilient-edit.ts +166 -0
  15. package/src/runtime/single-agent-compose.ts +87 -0
  16. package/src/runtime/team-runner.ts +23 -6
  17. package/src/schema/team-tool-schema.ts +6 -0
  18. package/src/state/usage.ts +73 -0
  19. package/src/ui/card-colors.ts +126 -0
  20. package/src/ui/deploy-bundled-themes.ts +71 -0
  21. package/src/ui/render-diff.ts +37 -3
  22. package/src/ui/settings-overlay.ts +70 -7
  23. package/src/ui/syntax-highlight.ts +42 -23
  24. package/src/ui/theme-discovery.ts +188 -0
  25. package/src/ui/tool-renderers/index.ts +27 -14
  26. package/themes/crew-catppuccin-latte.json +96 -0
  27. package/themes/crew-catppuccin-mocha.json +93 -0
  28. package/themes/crew-dark.json +90 -0
  29. package/themes/crew-dracula.json +83 -0
  30. package/themes/crew-gruvbox-dark.json +83 -0
  31. package/themes/crew-gruvbox-light.json +90 -0
  32. package/themes/crew-nord.json +85 -0
  33. package/themes/crew-one-dark.json +89 -0
  34. package/themes/crew-solarized-dark.json +90 -0
  35. package/themes/crew-solarized-light.json +92 -0
  36. package/themes/crew-tokyo-night.json +85 -0
  37. package/src/extension/registration/brief-tool-overrides.ts +0 -400
  38. package/src/runtime/budget-tracker.ts +0 -354
  39. package/src/state/memory-store.ts +0 -244
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1] — Fix: Auto-Continue After Compaction (2026-06-15)
4
+
5
+ ### Bug Fix
6
+
7
+ - **`a??`** — **Compaction resilience actually works now.** The v0.7.0 fix (O10) only *appended a resume-directive entry* after compaction, but appending an entry does **not trigger an agent turn** — the session still waited for user input, so the user still had to type "continue". The real root cause: Pi's threshold auto-compaction (`_runAutoCompaction` reason=`threshold` willRetry=`false`) returns `this.agent.hasQueuedMessages()`, which is `false` when nothing is queued → the agent loop ends → Pi waits for input (documented: "NO auto-retry, user continues manually").
8
+
9
+ **Fix**: after `session_compact` fires, call `pi.sendUserMessage(continuationPrompt)`. Per Pi's API, `sendUserMessage` *always triggers a turn*. The agent automatically runs a new turn, sees the resume directive, calls `team status`, and continues the in-flight crew task — **zero manual intervention**.
10
+
11
+ - `buildContinuationPrompt()`: action-oriented prompt ("Context was compacted while crew tasks were in-flight. Continue the work — do not wait for me.")
12
+ - `triggerContinuation(pi, ctx, runs)`: fire-and-forget `sendUserMessage`, best-effort error handling
13
+ - Wired in both the reactive path (`session_compact` handler) and proactive path (`startCompact.onComplete`)
14
+ - Only fires when in-flight crew runs exist; non-crew compaction unaffected
15
+ - 6 new unit tests
16
+
17
+ ## [0.7.0] — Long-Term Roadmap: Compaction Resilience, Cost Visibility, Trust Trinity (2026-06-15)
18
+
19
+ This release implements Phase 0 + Phase 1 of the pi-crew long-term roadmap (a 10-round research synthesis), plus the single-agent cliff hedge. The organizing principle: **build trust and cliff-resilience, stay lean, delete before adding.**
20
+
21
+ ### Highlights
22
+
23
+ - **🛡️ Compaction resilience (O10)** — the #1 user pain ("after auto-compact, the task stops midway") is fixed. pi-crew now detects in-flight runs, injects an explicit resume directive into the compaction summary, and re-attaches after compaction. Tasks survive context compaction.
24
+ - **💰 Cost visibility (O1)** — `team summary <runId>` now shows a full cost report with per-role attribution and token breakdown. Multi-agent's #1 barrier is cost; now it's visible.
25
+ - **✋ Plan-level HITL for any workflow (O5)** — plan approval was locked to the `implementation` workflow; now any workflow honors `config.runtime.requirePlanApproval`, gating at the read-only→mutating (plan→execute) boundary.
26
+ - **🧠 Cross-run memory (O4)** — `.crew/knowledge.md` is auto-read and injected into every agent's system prompt. pi-crew "gets better the longer you use it." Radically downsized (~80 LOC) replacement for the deleted MemoryStore.
27
+ - **🎯 Single-agent cliff hedge (T0.5/T2.2)** — any workflow can be composed into a single sequential prompt (`team plan singleAgent=true`). Proves pi-crew's mission survives even if multi-agent is obsoleted by 1M+ token models.
28
+ - **🧹 2,335 LOC of dead code removed** — grep-verified unused BudgetTracker, MemoryStore, `.bak` files, disabled brief-mode.
29
+ - **🔌 Pi-api seam** — centralizes the 8-symbol Pi coupling surface in one file, hedging against Pi API churn.
30
+
31
+ ### Features (Phase 0 — Stabilize & Clean)
32
+
33
+ - **`dbb4b6c`** — Deleted `budget-tracker.ts` (353 LOC), `memory-store.ts` (244 LOC), `brief-tool-overrides.ts` (400 LOC, disabled since 0.6.4), 3× `.bak` files (1,338 LOC), and their tests. Net −2,918 LOC including tests.
34
+ - **`42c1442`** — **Compaction resilience (O10)**: `compaction-guard.ts` gained `collectInFlightRuns()`, `formatResumeDirective()`, and a new `session_compact` handler that re-injects a `crew:resume-directive` entry into the fresh post-compaction context + notifies the user. Covers both the proactive path and Pi's reactive auto-compact path.
35
+ - **`40caf9e`** — `src/extension/pi-api.ts`: a type-level seam re-exporting the 8 public-API symbols pi-crew uses (ExtensionAPI, ExtensionContext, ExtensionCommandContext, ToolDefinition, defineTool, createBashTool, AgentSessionEvent, BeforeAgentStartEvent) + `BUILT_AGAINST_PI_VERSION` constant for version-drift diagnostics.
36
+ - **`8f40b07`** — **Single-agent cliff hedge v0**: `single-agent-compose.ts` (~95 LOC). `orderSteps()` topologically sorts by dependsOn; `composeSingleAgentPrompt()` turns a workflow into one sequential execution prompt.
37
+
38
+ ### Features (Phase 1 — Trust Trinity)
39
+
40
+ - **`3184303`** — **Cost visibility (O1)**: `state/usage.ts` gained `formatTokens()`, `formatCost()`, `aggregateUsageByRole()`, and `formatCostReport()`. The `summary` action now includes a multi-line report with token split + per-role % breakdown.
41
+ - **`198994e`** — **Plan-level HITL (O5)**: `team-runner.ts` `requiresPlanApproval()` dropped the `workflow.name === "implementation"` constraint; `hasPendingMutatingTaskAtBoundary()` (new) gates at the plan→execute boundary for any workflow; `ensurePlanApprovalRequested()` is robust to a missing `assess` step and gives clearer approval guidance.
42
+ - **`0272d77`** — **Cross-run memory (O4)**: `knowledge-injection.ts` (~80 LOC) registers a `before_agent_start` hook that appends `.crew/knowledge.md` (truncated to 16KB) to every agent's system prompt — main session + each crew worker.
43
+
44
+ ### Features (Phase 2 — Lean Power)
45
+
46
+ - **`eeefe0a`** — **Single-agent runtime mode (T2.2)**: `singleAgent` boolean param on the `team plan` action. A Pi agent calling `team plan singleAgent=true` receives the full composed sequential prompt for any workflow. MCP tool consumption (T2.1) already existed for live-session workers (`mcp-proxy.ts`); verified and left in place rather than duplicated.
47
+
48
+ ### Upgrade Notes
49
+ - New config: `runtime.requirePlanApproval = true` enables plan-level approval gates on any workflow.
50
+ - New file: `.crew/knowledge.md` (optional) — write durable project knowledge; it's injected into every run.
51
+ - Cost report appears automatically in `team summary`. Budget *enforcement* (auto-stop) is intentionally deferred until cost-data accuracy is validated.
52
+
3
53
  ## [0.6.4] — Visually Rich Tool Rendering: Merged Frames, Live Progress Bars (2026-06-14)
4
54
 
5
55
  ### Highlights
package/README.md CHANGED
@@ -11,6 +11,17 @@ repo: https://github.com/baphuongna/pi-crew
11
11
 
12
12
  **v0.6.4**: See [CHANGELOG.md](CHANGELOG.md).
13
13
 
14
+ ### Highlights (v0.6.4 → v0.7.0)
15
+
16
+ This release implements **Phase 0 + Phase 1** of the long-term roadmap (synthesized from a 10-round research process), plus the **single-agent cliff hedge**. Principle: *build trust and cliff-resilience, stay lean, delete before adding.*
17
+
18
+ - **🛡️ Compaction resilience (O10)** — the #1 user pain ("after auto-compact, the task stops midway") is fixed. In-flight crew runs are detected, a resume directive is injected into the compaction summary, and tasks re-attach after compaction.
19
+ - **💰 Cost visibility (O1)** — `team summary <runId>` now shows a full cost report with per-role attribution and token breakdown (`$0.77 — executor 79%, reviewer 14%...`).
20
+ - **✋ Plan-level HITL for any workflow (O5)** — set `runtime.requirePlanApproval = true` to gate any workflow at the plan→execute boundary; approve via `team api op=approve-plan`.
21
+ - **🧠 Cross-run memory (O4)** — `.crew/knowledge.md` is auto-injected into every run's system prompt. pi-crew remembers project context across runs.
22
+ - **🎯 Single-agent cliff hedge** — `team plan singleAgent=true` composes any workflow into one sequential prompt, so pi-crew's mission survives even if multi-agent is obsoleted by large-context models.
23
+ - **🧹 2,335 LOC of dead code removed** + **Pi-api seam** centralizing the coupling surface.
24
+
14
25
  ### Highlights (v0.6.3 → v0.6.4)
15
26
 
16
27
  - **Visually rich tool rendering** — `team` and `Agent` tool calls now render as framed cards in the Pi TUI with box-drawing borders, colored status badges, and structured layouts
@@ -260,6 +271,63 @@ If preconditions are not met, a friendly error message is returned instead of cr
260
271
 
261
272
  ---
262
273
 
274
+ ## Reliability & Trust
275
+
276
+ ### Compaction resilience
277
+
278
+ pi-crew survives Pi's context compaction. When the context is compacted (auto or manual), in-flight crew runs are detected and a **resume directive** is injected into the post-compaction context, so tasks continue instead of stalling. You'll see a notification like:
279
+
280
+ ```
281
+ Context compacted. 1 pi-crew run(s) still in-flight — use team status to continue.
282
+ ```
283
+
284
+ ### Plan-level human-in-the-loop (HITL)
285
+
286
+ Set `runtime.requirePlanApproval = true` to gate **any workflow** at the plan→execute boundary. After the read-only (planning) phases complete, the run pauses for explicit approval before mutating tasks run:
287
+
288
+ ```
289
+ team api op=approve-plan runId=<runId> # approve → execute
290
+ team api op=cancel-plan runId=<runId> # cancel
291
+ ```
292
+
293
+ This is plan-level (not per-step) — per-step gates would kill the parallelism that's pi-crew's point.
294
+
295
+ ### Cross-run memory (`.crew/knowledge.md`)
296
+
297
+ Create `.crew/knowledge.md` in your project root with durable learnings (code style, test commands, common pitfalls, past refactors). It's auto-read (up to 16KB) and injected into **every** agent's system prompt — the main session and each crew worker. pi-crew gets better the longer you use it.
298
+
299
+ ```markdown
300
+ # Project Knowledge
301
+ - Tests: run with `npm test` (not jest directly)
302
+ - Style: tabs, not spaces
303
+ - Auth refactor (2026-06): split auth.ts into session.ts + api.ts
304
+ ```
305
+
306
+ ### Cost visibility
307
+
308
+ Every `team summary <runId>` includes a per-role cost report:
309
+
310
+ ```
311
+ ═══ Cost Report ═══
312
+ Tokens: 134k (in 112k, out 5.7k, cache-write 16k)
313
+ Cost: $0.7700 across 18 turn(s)
314
+ By role:
315
+ executor (2 tasks): $0.6100 — 79%, 98k tok, 13 turns
316
+ reviewer (1 task): $0.1100 — 14%, 23k tok, 3 turns
317
+ ```
318
+
319
+ ### Single-agent mode (cliff hedge)
320
+
321
+ Any workflow can run single-agent instead of multi-agent — composing all phases into one sequential prompt:
322
+
323
+ ```
324
+ team plan team=default workflow=default goal="..." singleAgent=true
325
+ ```
326
+
327
+ This is pi-crew's cliff-resilient mode: the workflow definitions, phase structure, and artifact contracts survive even if a single large-context model outperforms multi-agent teams.
328
+
329
+ ---
330
+
263
331
  ## Tool Actions
264
332
 
265
333
  ```json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.6.4",
3
+ "version": "0.7.1",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -33,6 +33,7 @@
33
33
  "teams/",
34
34
  "workflows/",
35
35
  "skills/**/SKILL.md",
36
+ "themes/",
36
37
  "README.md",
37
38
  "AGENTS.md",
38
39
  "docs/",
@@ -0,0 +1,71 @@
1
+ /**
2
+ * knowledge-injection.ts — Project knowledge that accumulates across runs (O4).
3
+ *
4
+ * ROADMAP Phase 1 / T1.3 ("downsized memory"): a deliberately minimal
5
+ * replacement for the deleted 244-LOC MemoryStore. Crews and the main
6
+ * session read `.crew/knowledge.md` and have it injected into the system
7
+ * prompt, so pi-crew "remembers" project context across runs.
8
+ *
9
+ * Philosophy (Round 6 stress-test): radically downsized. Just a Markdown
10
+ * file the user can edit, surfaced into every run. No vector DB, no
11
+ * embeddings, no graph. Simple = trustworthy.
12
+ */
13
+ import * as fs from "node:fs";
14
+ import * as path from "node:path";
15
+ import type { BeforeAgentStartEvent, ExtensionAPI } from "./pi-api.ts";
16
+ import { projectCrewRoot } from "../utils/paths.ts";
17
+
18
+ /** The knowledge file, relative to the project crew root. */
19
+ export const KNOWLEDGE_FILENAME = "knowledge.md";
20
+ /** Cap injected knowledge to avoid unbounded system-prompt growth. */
21
+ const MAX_KNOWLEDGE_BYTES = 16_000;
22
+
23
+ /** Resolve the knowledge file path for a cwd (may not exist yet). */
24
+ export function knowledgePath(cwd: string): string {
25
+ return path.join(projectCrewRoot(cwd), KNOWLEDGE_FILENAME);
26
+ }
27
+
28
+ /** Read knowledge content, truncated to a safe size. "" if absent/empty. */
29
+ export function readKnowledge(cwd: string): string {
30
+ try {
31
+ const p = knowledgePath(cwd);
32
+ if (!fs.existsSync(p)) return "";
33
+ let content = fs.readFileSync(p, "utf8").trim();
34
+ if (content.length > MAX_KNOWLEDGE_BYTES) {
35
+ content = `${content.slice(0, MAX_KNOWLEDGE_BYTES)}\n\n<!-- knowledge.md truncated at ${MAX_KNOWLEDGE_BYTES} bytes -->`;
36
+ }
37
+ return content;
38
+ } catch {
39
+ return "";
40
+ }
41
+ }
42
+
43
+ /** Build the injected prompt fragment (empty if no knowledge). */
44
+ export function buildKnowledgeFragment(cwd: string): string {
45
+ const content = readKnowledge(cwd);
46
+ if (!content) return "";
47
+ return [
48
+ "",
49
+ "# Project knowledge (from .crew/knowledge.md)",
50
+ "The following project knowledge was captured by pi-crew from prior runs.",
51
+ "Use it to avoid repeating past mistakes and to respect project conventions.",
52
+ "You may update .crew/knowledge.md when you learn something durable.",
53
+ "",
54
+ content,
55
+ ].join("\n");
56
+ }
57
+
58
+ /**
59
+ * Register the knowledge-injection hook. Appends project knowledge to the
60
+ * system prompt on every agent start (main session + each crew worker,
61
+ * since workers are child Pi processes that also fire before_agent_start).
62
+ */
63
+ export function registerKnowledgeInjection(pi: ExtensionAPI): void {
64
+ pi.on("before_agent_start", (event: BeforeAgentStartEvent) => {
65
+ const options = (event as BeforeAgentStartEvent & { systemPromptOptions?: { cwd?: unknown } }).systemPromptOptions ?? {};
66
+ const cwd = typeof options.cwd === "string" ? options.cwd : process.cwd();
67
+ const fragment = buildKnowledgeFragment(cwd);
68
+ if (!fragment) return undefined;
69
+ return { systemPrompt: `${event.systemPrompt}${fragment}` };
70
+ });
71
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * pi-api.ts — pi-crew's stable seam against the Pi extension API.
3
+ *
4
+ * PURPOSE (roadmap Phase 0 / Pillar 4 "Pi-Native, Protocol-Aware"):
5
+ * Centralize every symbol pi-crew imports from
6
+ * `@earendil-works/pi-coding-agent` so that:
7
+ * 1. The coupling surface is auditable in ONE file (8 symbols today).
8
+ * 2. A Pi API rename/restructure requires updating only this seam.
9
+ * 3. New code imports from here ("./pi-api") rather than the raw package,
10
+ * establishing the indirection that hedges Pi-coupling risk.
11
+ *
12
+ * COUpling SURFACE (kept intentionally small — public extension API only):
13
+ * - ExtensionAPI the registration entry point (pi)
14
+ * - ExtensionContext per-event/session context (ctx)
15
+ * - ExtensionCommandContext slash-command context
16
+ * - ToolDefinition tool schema type
17
+ * - defineTool tool factory
18
+ * - createBashTool built-in bash tool (for custom tooling)
19
+ * - AgentSessionEvent session event type
20
+ * - BeforeAgentStartEvent pre-turn system-prompt hook event
21
+ *
22
+ * NEW code should `import { ExtensionAPI, ExtensionContext } from "./pi-api"`.
23
+ * Existing files may keep their direct imports; migrate opportunistically
24
+ * (no big-bang refactor — see roadmap Phase 0 T0.3/T0.4 notes).
25
+ *
26
+ * Note: these are TYPE-level re-exports (erased at runtime). Runtime coupling
27
+ * to Pi is via the `pi` and `ctx` objects passed by Pi's loader — that is
28
+ * unavoidable and correct (pi-crew IS a Pi extension). This seam documents
29
+ * and centralizes the type-level surface.
30
+ */
31
+ export type {
32
+ ExtensionAPI,
33
+ ExtensionContext,
34
+ ExtensionCommandContext,
35
+ ToolDefinition,
36
+ AgentSessionEvent,
37
+ BeforeAgentStartEvent,
38
+ } from "@earendil-works/pi-coding-agent";
39
+
40
+ export { defineTool, createBashTool } from "@earendil-works/pi-coding-agent";
41
+
42
+ /**
43
+ * The Pi package version pi-crew was built against. Used for diagnostics
44
+ * and to surface version-drift if Pi upgrades introduce breaking changes.
45
+ * Update this when bumping the pi-coding-agent dependency.
46
+ */
47
+ export const BUILT_AGAINST_PI_VERSION = "0.79.3";
@@ -17,6 +17,7 @@ import {
17
17
  stopAsyncRunNotifier,
18
18
  } from "./async-notifier.ts";
19
19
  import { registerAutonomousPolicy } from "./autonomous-policy.ts";
20
+ import { registerKnowledgeInjection } from "./knowledge-injection.ts";
20
21
  import { registerCleanupHandler } from "./crew-cleanup.ts";
21
22
  import type { ScheduledJob } from "../runtime/scheduler.ts";
22
23
  import { clearHooksScoped } from "../hooks/registry.ts";
@@ -63,6 +64,7 @@ import {
63
64
  updateCrewWidget,
64
65
  } from "../ui/widget/index.ts";
65
66
  import { summarizeHeartbeats } from "../ui/heartbeat-aggregator.ts";
67
+ import { deployBundledThemes } from "../ui/deploy-bundled-themes.ts";
66
68
  import {
67
69
  requestRender,
68
70
  setExtensionWidget,
@@ -100,7 +102,6 @@ import {
100
102
  import { createJsonlSink, type NotificationSink } from "./notification-sink.ts";
101
103
  import { runArtifactCleanup } from "./registration/artifact-cleanup.ts";
102
104
  import { registerTeamCommands } from "./registration/commands.ts";
103
- import { registerBriefToolOverrides } from "./registration/brief-tool-overrides.ts";
104
105
  import { registerCompactionGuard } from "./registration/compaction-guard.ts";
105
106
  import {
106
107
  __test__subagentSpawnParams,
@@ -188,6 +189,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
188
189
  const disposeI18n = initI18n(pi);
189
190
  resetTimings();
190
191
  time("register:start");
192
+ // Deploy bundled themes (crew-dark, crew-dracula, etc.) to ~/.pi/agent/themes/
193
+ // so Pi's theme loader discovers them. Best-effort, idempotent.
194
+ deployBundledThemes();
191
195
  const globalStore = globalThis as Record<string | symbol, unknown>;
192
196
  const runtimeCleanupStoreKey = Symbol("__piCrewRuntimeCleanup");
193
197
  const previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];
@@ -982,6 +986,8 @@ export function registerPiTeams(pi: ExtensionAPI): void {
982
986
  };
983
987
  time("register.policy");
984
988
  registerAutonomousPolicy(pi);
989
+ registerKnowledgeInjection(pi);
990
+ time("register.knowledge");
985
991
  time("register.rpc");
986
992
  function getPiEvents():
987
993
  | Parameters<typeof registerPiCrewRpc>[0]
@@ -1986,11 +1992,18 @@ export function registerPiTeams(pi: ExtensionAPI): void {
1986
1992
 
1987
1993
  registerCleanupHandler(pi);
1988
1994
 
1989
- // Brief tool overrides DISABLED: re-registering built-in tools replaced Pi's
1990
- // superior native renderers (syntax highlighting, diff views, full file
1991
- // content) with inferior custom ones, and caused renderCall/renderResult
1992
- // to duplicate path/command info. Pi's native rendering is better.
1993
- // To re-enable, uncomment: registerBriefToolOverrides(pi, process.cwd());
1995
+
1996
+ // Resilient edit (Phase 2): OPT-IN via CREW_RESILIENT_EDIT=1.
1997
+ // Wraps the native edit tool with the cascading replace() fallback so that
1998
+ // slightly-wrong oldText (indentation drift, whitespace) still matches.
1999
+ // Auto-disables if pi-diff is loaded (it has its own replace integration).
2000
+ if (process.env.CREW_RESILIENT_EDIT === "1") {
2001
+ import("../runtime/resilient-edit.ts")
2002
+ .then(({ wrapEditWithResilientReplace }) => {
2003
+ wrapEditWithResilientReplace(pi);
2004
+ })
2005
+ .catch(() => { /* non-critical */ });
2006
+ }
1994
2007
 
1995
2008
  registerTeamCommands(pi, {
1996
2009
  startForegroundRun,
@@ -364,13 +364,52 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
364
364
  } catch (error) {
365
365
  ctx.ui.notify(`Failed to save: ${error instanceof Error ? error.message : String(error)}`, "error");
366
366
  }
367
- }, () => done(undefined));
367
+ }, () => done(undefined), async (action: string, value: unknown) => {
368
+ // Action callbacks (Pi theme switch) write to a different store
369
+ // than pi-crew config (e.g. ~/.pi/agent/settings.json).
370
+ try {
371
+ if (action === "piTheme" && typeof value === "string") {
372
+ // Live theme switch: ctx.ui.setTheme() swaps the global theme,
373
+ // persists it to settings.json, and triggers a UI redraw — no
374
+ // restart needed. Falls back to file-write + restart hint if
375
+ // the live API is unavailable (e.g. non-TUI mode).
376
+ if (typeof ctx.ui.setTheme === "function") {
377
+ const res = ctx.ui.setTheme(value);
378
+ if (res.success) {
379
+ ctx.ui.notify(`Theme: ${value} (applied live)`, "info");
380
+ } else {
381
+ const { setPiTheme } = await import("../../ui/theme-discovery.ts");
382
+ setPiTheme(value);
383
+ ctx.ui.notify(`Theme saved as '${value}' but failed to apply: ${res.error ?? "unknown"}. Restart Pi.`, "warning");
384
+ }
385
+ } else {
386
+ const { setPiTheme } = await import("../../ui/theme-discovery.ts");
387
+ setPiTheme(value);
388
+ ctx.ui.notify(`Pi theme set to '${value}'. Restart Pi to apply.`, "info");
389
+ }
390
+ }
391
+ } catch (error) {
392
+ ctx.ui.notify(`Failed: ${error instanceof Error ? error.message : String(error)}`, "error");
393
+ }
394
+ });
368
395
  return overlay;
369
396
  }, { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
370
397
  return;
371
398
  }
372
399
  const result = await handleTeamTool({ action: "settings", config: { args: args.trim() } }, teamCommandContext(ctx));
373
400
  await notifyCommandResult(ctx, commandText(result));
401
+ // Live-switch hook: when the text subcommand 'theme <name>' succeeds,
402
+ // also apply the theme live via ctx.ui.setTheme() (no restart). The
403
+ // handler above only writes to settings.json.
404
+ const trimmed = args.trim();
405
+ if (trimmed.startsWith("theme ")) {
406
+ const themeName = trimmed.slice(6).trim();
407
+ if (themeName && typeof ctx.ui.setTheme === "function") {
408
+ const res = ctx.ui.setTheme(themeName);
409
+ if (res.success) ctx.ui.notify(`Theme: ${themeName} (applied live)`, "info");
410
+ else ctx.ui.notify(`Saved but live-switch failed: ${res.error ?? "unknown"}. Restart Pi.`, "warning");
411
+ }
412
+ }
374
413
  },
375
414
  })
376
415
 
@@ -12,6 +12,8 @@ const HARD_RATIO = 0.95;
12
12
  const DEFAULT_CONTEXT_WINDOW = 200_000;
13
13
  const MAX_ARTIFACT_INDEX_RUNS = 10;
14
14
  const MAX_ARTIFACT_INDEX_ITEMS = 80;
15
+ /** Run statuses that mean the run is still in-flight and may need resuming. */
16
+ const IN_FLIGHT_RUN_STATUSES = new Set(["queued", "planning", "running"]);
15
17
 
16
18
  function contextWindow(ctx: { model?: { contextWindow?: number } }): number {
17
19
  const value = ctx.model?.contextWindow;
@@ -66,6 +68,99 @@ function formatCrewArtifactIndex(entries: CrewArtifactIndexEntry[]): string {
66
68
  return lines.join("\n");
67
69
  }
68
70
 
71
+ /**
72
+ * Collect in-flight (non-terminal) crew runs that must be resumable after
73
+ * compaction. These are runs the agent was actively working on or awaiting.
74
+ */
75
+ export function collectInFlightRuns(cwd: string): TeamRunManifest[] {
76
+ return listRecentRuns(cwd, MAX_ARTIFACT_INDEX_RUNS).filter((run) =>
77
+ IN_FLIGHT_RUN_STATUSES.has(run.status),
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Build an explicit resume directive that survives compaction. This is the
83
+ * core of O10 (compaction resilience): after compaction, the agent MUST know
84
+ * what crew tasks were in-flight and how to continue them.
85
+ */
86
+ function formatResumeDirective(runs: TeamRunManifest[]): string {
87
+ if (!runs.length) return "";
88
+ const lines = [
89
+ "",
90
+ "# pi-crew in-flight task resume directive (CRITICAL — do not drop)",
91
+ "The following pi-crew runs were in progress when the context was compacted.",
92
+ "After compaction, you MUST continue these tasks — do NOT consider them finished.",
93
+ ];
94
+ for (const run of runs) {
95
+ const wf = run.workflow ? `, workflow=${run.workflow}` : "";
96
+ lines.push(
97
+ `- runId=${run.runId} (status=${run.status}, team=${run.team}${wf}): ${run.goal}`,
98
+ );
99
+ }
100
+ lines.push("");
101
+ lines.push("To resume: call the `team` tool with action='status' to check progress, then");
102
+ lines.push("action='wait' (to join a still-running task) or action='summary' / action='get'");
103
+ lines.push("to retrieve results. If a task was mid-execution and the worker is still alive,");
104
+ lines.push("it continues independently — just re-attach. Do not restart completed work.");
105
+ return lines.join("\n");
106
+ }
107
+
108
+ /**
109
+ * Build a short continuation prompt for sendUserMessage. This is what actually
110
+ * makes the agent resume after compaction — Pi's threshold compaction does NOT
111
+ * auto-retry (it stops and waits for user input). By injecting this as a user
112
+ * message that triggers a turn, the agent automatically continues the in-flight
113
+ * crew task instead of stalling until the user types "continue".
114
+ */
115
+ export function buildContinuationPrompt(runs: TeamRunManifest[]): string {
116
+ if (!runs.length) return "";
117
+ const lines = [
118
+ "[pi-crew] Context was compacted while crew tasks were still in-flight. Continue the work — do not wait for me.",
119
+ ];
120
+ for (const run of runs) {
121
+ const wf = run.workflow ? `, workflow=${run.workflow}` : "";
122
+ lines.push(`- runId=${run.runId} (status=${run.status}, team=${run.team}${wf}): ${run.goal}`);
123
+ }
124
+ lines.push("");
125
+ lines.push("Resume: call `team` with action='status' to check progress, then action='wait' (join a running task), action='summary', or action='get' as appropriate. If a worker is still alive it continues independently — just re-attach. Do NOT restart completed work.");
126
+ return lines.join("\n");
127
+ }
128
+
129
+ /**
130
+ * Trigger automatic agent continuation after compaction. Fire-and-forget the
131
+ * promise — never block the compaction flow. The sendUserMessage type is
132
+ * declared `void` but the runtime returns a Promise (it triggers an agent turn).
133
+ */
134
+ export function triggerContinuation(pi: ExtensionAPI, ctx: ExtensionContext, runs: TeamRunManifest[]): void {
135
+ if (!runs.length) return;
136
+ const prompt = buildContinuationPrompt(runs);
137
+ try {
138
+ const result = pi.sendUserMessage(prompt) as unknown;
139
+ Promise.resolve(result).catch(() => {
140
+ // best-effort: if continuation fails, at least notify
141
+ try {
142
+ ctx.ui.notify("pi-crew: auto-continuation after compaction failed — use team status to resume manually.", "warning");
143
+ } catch {
144
+ // swallow
145
+ }
146
+ });
147
+ } catch {
148
+ // best-effort
149
+ }
150
+ }
151
+
152
+ /** Combined customInstructions injected into proactive compaction summaries. */
153
+ function buildCompactionInstructions(cwd: string): string {
154
+ const artifactIndex = collectCrewArtifactIndex(cwd);
155
+ const inFlight = collectInFlightRuns(cwd);
156
+ const parts = [
157
+ "Prioritize keeping pi-crew run state, task results, artifact references, run IDs, and next actions. Keep completed-task detail concise.",
158
+ ];
159
+ if (artifactIndex.length > 0) parts.push(formatCrewArtifactIndex(artifactIndex));
160
+ if (inFlight.length > 0) parts.push(formatResumeDirective(inFlight));
161
+ return parts.join("\n");
162
+ }
163
+
69
164
  export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompactionGuardOptions): void {
70
165
  let pendingCompactReason: string | null = null;
71
166
  let compactionInProgress = false;
@@ -73,18 +168,32 @@ export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompa
73
168
  const startCompact = (ctx: ExtensionContext, reason: string): void => {
74
169
  if (compactionInProgress) return;
75
170
  compactionInProgress = true;
76
- const artifactIndex = collectCrewArtifactIndex(ctx.cwd);
77
- if (artifactIndex.length > 0) {
78
- pi.appendEntry("crew:artifact-index", {
171
+ const customInstructions = buildCompactionInstructions(ctx.cwd);
172
+ // Append a durable resume entry so it appears in the post-compaction
173
+ // context regardless of how summarization treats customInstructions.
174
+ const inFlight = collectInFlightRuns(ctx.cwd);
175
+ if (inFlight.length > 0) {
176
+ pi.appendEntry("crew:resume-directive", {
79
177
  reason,
80
178
  createdAt: new Date().toISOString(),
81
- artifacts: artifactIndex,
179
+ runs: inFlight.map((r) => ({
180
+ runId: r.runId,
181
+ status: r.status,
182
+ team: r.team,
183
+ workflow: r.workflow,
184
+ goal: r.goal,
185
+ })),
82
186
  });
83
187
  }
84
188
  ctx.compact({
85
- customInstructions: `Prioritize keeping pi-crew run state, task results, artifact references, run IDs, and next actions. Keep completed-task detail concise.${formatCrewArtifactIndex(artifactIndex)}`,
189
+ customInstructions,
86
190
  onComplete: () => {
87
191
  compactionInProgress = false;
192
+ // O10 FIX: Pi's threshold compaction does NOT auto-retry — it
193
+ // stops and waits for user input. Trigger automatic
194
+ // continuation so the agent resumes the in-flight crew task.
195
+ const runs = collectInFlightRuns(ctx.cwd);
196
+ triggerContinuation(pi, ctx, runs);
88
197
  ctx.ui.notify(reason === "deferred" ? "Deferred compaction completed" : "Auto-compacted context during team run", "info");
89
198
  },
90
199
  onError: (error) => {
@@ -94,17 +203,49 @@ export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompa
94
203
  });
95
204
  };
96
205
 
97
- // Phase 1.2: Always allow compaction to proceed.
98
- // Previously this deferred compaction during foreground runs, but that prevented
99
- // context-mode's PreCompact hook from running (which needs compaction to build resume snapshots).
100
- // pi-crew state is preserved via customInstructions and artifactIndex appended above.
101
- pi.on("session_before_compact", async (_event, ctx) => {
102
- // Always allow compaction - context-mode needs it for resume snapshots
206
+ // Allow compaction to proceed. pi-crew state is preserved via the
207
+ // customInstructions + resume-directive entry appended in startCompact,
208
+ // and re-injected post-compaction by the session_compact handler below.
209
+ pi.on("session_before_compact", async (_event, _ctx) => {
103
210
  return;
104
211
  });
105
212
 
106
- // Phase 2.1: Proactive compaction with dynamic threshold.
107
- // Only trigger compaction when context usage >=75%, or when deferred from a previous high-usage turn.
213
+ // O10: After ANY compaction (proactive OR reactive/Pi-triggered), detect
214
+ // in-flight crew runs and trigger automatic continuation. This is the
215
+ // critical fix: Pi's threshold compaction does NOT auto-retry — it stops
216
+ // and waits for user input. By injecting a continuation user message that
217
+ // triggers a turn, the agent automatically resumes the in-flight crew task.
218
+ // This covers the common case where Pi auto-compacts without going through
219
+ // our proactive startCompact path.
220
+ pi.on("session_compact", (_event, ctx) => {
221
+ try {
222
+ const inFlight = collectInFlightRuns(ctx.cwd);
223
+ if (inFlight.length === 0) return;
224
+ // Re-append the resume directive entry for durable record.
225
+ pi.appendEntry("crew:resume-directive", {
226
+ reason: "post-compaction-continuation",
227
+ createdAt: new Date().toISOString(),
228
+ runs: inFlight.map((r) => ({
229
+ runId: r.runId,
230
+ status: r.status,
231
+ team: r.team,
232
+ workflow: r.workflow,
233
+ goal: r.goal,
234
+ })),
235
+ });
236
+ ctx.ui.notify(
237
+ `Context compacted. ${inFlight.length} pi-crew run(s) still in-flight — auto-resuming.`,
238
+ "info",
239
+ );
240
+ // THE FIX: trigger automatic continuation. Without this, Pi stops
241
+ // after threshold compaction and the user must type "continue".
242
+ triggerContinuation(pi, ctx, inFlight);
243
+ } catch {
244
+ // best-effort: never block compaction completion
245
+ }
246
+ });
247
+
248
+ // Proactive compaction with dynamic threshold.
108
249
  pi.on("turn_end", (_event, ctx) => {
109
250
  if (compactionInProgress) return;
110
251
  const hasActiveForeground = options.foregroundControllers.size > 0 || options.foregroundTeamRunControllers.size > 0;
@@ -112,7 +253,6 @@ export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompa
112
253
  // If deferred compaction is pending and foreground just ended, check if still needed
113
254
  if (!hasActiveForeground && pendingCompactReason) {
114
255
  pendingCompactReason = null;
115
- // Only compact if ratio still >= TRIGGER_RATIO, otherwise skip
116
256
  if (ratio === undefined || ratio < TRIGGER_RATIO) return;
117
257
  startCompact(ctx, "deferred");
118
258
  return;