pi-crew 0.6.4 → 0.7.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/CHANGELOG.md +62 -0
- package/README.md +68 -0
- package/package.json +2 -1
- package/src/errors.ts +20 -2
- package/src/extension/knowledge-injection.ts +71 -0
- package/src/extension/pi-api.ts +47 -0
- package/src/extension/register.ts +19 -6
- package/src/extension/registration/commands.ts +40 -1
- package/src/extension/registration/compaction-guard.ts +154 -14
- package/src/extension/team-tool/handle-settings.ts +57 -0
- package/src/extension/team-tool/inspect.ts +4 -1
- package/src/extension/team-tool/plan.ts +8 -1
- package/src/runtime/intercom-bridge.ts +5 -1
- package/src/runtime/replace.ts +555 -0
- package/src/runtime/resilient-edit.ts +166 -0
- package/src/runtime/single-agent-compose.ts +87 -0
- package/src/runtime/task-runner/prompt-builder.ts +6 -0
- package/src/runtime/team-runner.ts +24 -7
- package/src/schema/team-tool-schema.ts +6 -0
- package/src/state/usage.ts +73 -0
- package/src/ui/card-colors.ts +126 -0
- package/src/ui/deploy-bundled-themes.ts +71 -0
- package/src/ui/render-diff.ts +37 -3
- package/src/ui/settings-overlay.ts +70 -7
- package/src/ui/syntax-highlight.ts +42 -23
- package/src/ui/theme-discovery.ts +188 -0
- package/src/ui/tool-renderers/index.ts +27 -14
- package/themes/crew-catppuccin-latte.json +96 -0
- package/themes/crew-catppuccin-mocha.json +93 -0
- package/themes/crew-dark.json +90 -0
- package/themes/crew-dracula.json +83 -0
- package/themes/crew-gruvbox-dark.json +83 -0
- package/themes/crew-gruvbox-light.json +90 -0
- package/themes/crew-nord.json +85 -0
- package/themes/crew-one-dark.json +89 -0
- package/themes/crew-solarized-dark.json +90 -0
- package/themes/crew-solarized-light.json +92 -0
- package/themes/crew-tokyo-night.json +85 -0
- package/src/extension/registration/brief-tool-overrides.ts +0 -400
- package/src/runtime/budget-tracker.ts +0 -354
- package/src/state/memory-store.ts +0 -244
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.2] — Fix: Knowledge Injection into Workers + HITL for All Workflows (2026-06-15)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **Knowledge injection into crew workers (O4)** — crew workers are spawned with `--no-extensions` and only load `prompt-runtime.ts`; they do **not** load the pi-crew extension. So the `before_agent_start` hook in `knowledge-injection.ts` never fired for workers — `.crew/knowledge.md` was invisible to every crew worker. **Fix**: inject `buildKnowledgeFragment(task.cwd)` directly into `renderTaskPrompt()` in `prompt-builder.ts` (where all worker context is assembled). The main session still gets knowledge via the hook; workers now get it via the prompt path.
|
|
8
|
+
|
|
9
|
+
**Live-verified**: all 3 workers (explorer, executor, verifier) in a fast-fix run confirmed seeing the knowledge section verbatim (`"Use TABS for indentation"`, `"Tests run via npm test"`).
|
|
10
|
+
|
|
11
|
+
- **Plan-level HITL for all workflows (T1.2)** — the post-batch approval re-check in the team-runner main loop only checked `hasPendingMutatingAdaptiveTask(tasks)` (adaptive/implementation workflows only). The boundary detector `hasPendingMutatingTaskAtBoundary(tasks)` was added to the initial build but **not** to the loop's re-check. So for non-adaptive workflows (`default`, `fast-fix`), the approval gate never fired after read-only tasks completed. **Fix**: add `hasPendingMutatingTaskAtBoundary(tasks)` to the loop's post-batch re-check (line 835), matching the initial-build logic.
|
|
12
|
+
|
|
13
|
+
**Verified** via 6 new unit tests + full flow trace. When `runtime.requirePlanApproval: true`, the run now blocks at the plan→execute boundary (`status: blocked`, "Plan approval required") until approved via `team api op=approve-plan`.
|
|
14
|
+
|
|
15
|
+
## [0.7.1] — Fix: Auto-Continue After Compaction (2026-06-15)
|
|
16
|
+
|
|
17
|
+
### Bug Fix
|
|
18
|
+
|
|
19
|
+
- **`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").
|
|
20
|
+
|
|
21
|
+
**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**.
|
|
22
|
+
|
|
23
|
+
- `buildContinuationPrompt()`: action-oriented prompt ("Context was compacted while crew tasks were in-flight. Continue the work — do not wait for me.")
|
|
24
|
+
- `triggerContinuation(pi, ctx, runs)`: fire-and-forget `sendUserMessage`, best-effort error handling
|
|
25
|
+
- Wired in both the reactive path (`session_compact` handler) and proactive path (`startCompact.onComplete`)
|
|
26
|
+
- Only fires when in-flight crew runs exist; non-crew compaction unaffected
|
|
27
|
+
- 6 new unit tests
|
|
28
|
+
|
|
29
|
+
## [0.7.0] — Long-Term Roadmap: Compaction Resilience, Cost Visibility, Trust Trinity (2026-06-15)
|
|
30
|
+
|
|
31
|
+
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.**
|
|
32
|
+
|
|
33
|
+
### Highlights
|
|
34
|
+
|
|
35
|
+
- **🛡️ 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.
|
|
36
|
+
- **💰 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.
|
|
37
|
+
- **✋ 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.
|
|
38
|
+
- **🧠 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.
|
|
39
|
+
- **🎯 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.
|
|
40
|
+
- **🧹 2,335 LOC of dead code removed** — grep-verified unused BudgetTracker, MemoryStore, `.bak` files, disabled brief-mode.
|
|
41
|
+
- **🔌 Pi-api seam** — centralizes the 8-symbol Pi coupling surface in one file, hedging against Pi API churn.
|
|
42
|
+
|
|
43
|
+
### Features (Phase 0 — Stabilize & Clean)
|
|
44
|
+
|
|
45
|
+
- **`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.
|
|
46
|
+
- **`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.
|
|
47
|
+
- **`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.
|
|
48
|
+
- **`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.
|
|
49
|
+
|
|
50
|
+
### Features (Phase 1 — Trust Trinity)
|
|
51
|
+
|
|
52
|
+
- **`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.
|
|
53
|
+
- **`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.
|
|
54
|
+
- **`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.
|
|
55
|
+
|
|
56
|
+
### Features (Phase 2 — Lean Power)
|
|
57
|
+
|
|
58
|
+
- **`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.
|
|
59
|
+
|
|
60
|
+
### Upgrade Notes
|
|
61
|
+
- New config: `runtime.requirePlanApproval = true` enables plan-level approval gates on any workflow.
|
|
62
|
+
- New file: `.crew/knowledge.md` (optional) — write durable project knowledge; it's injected into every run.
|
|
63
|
+
- Cost report appears automatically in `team summary`. Budget *enforcement* (auto-stop) is intentionally deferred until cost-data accuracy is validated.
|
|
64
|
+
|
|
3
65
|
## [0.6.4] — Visually Rich Tool Rendering: Merged Frames, Live Progress Bars (2026-06-14)
|
|
4
66
|
|
|
5
67
|
### 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.
|
|
3
|
+
"version": "0.7.2",
|
|
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/",
|
package/src/errors.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
|
+
// pi-crew structured error module — taxonomy mapping E001–E006.
|
|
1
2
|
/**
|
|
2
|
-
* Error
|
|
3
|
-
*
|
|
3
|
+
* @fileoverview Error types and structured error handling for pi-crew.
|
|
4
|
+
*
|
|
5
|
+
* This module defines pi-crew's error taxonomy, mapping to semantic categories
|
|
6
|
+
* matching fallow's E001-E004 pattern. It exports three main constructs:
|
|
7
|
+
*
|
|
8
|
+
* - {@link ErrorCode} — a `const` object and string-literal union type alias
|
|
9
|
+
* enumerating machine-readable error codes (E001–E006). Implemented as a
|
|
10
|
+
* `const` object rather than a TypeScript `enum` so that Node's
|
|
11
|
+
* `--experimental-strip-types` can load this module (enum syntax is not
|
|
12
|
+
* supported in strip-only mode).
|
|
13
|
+
*
|
|
14
|
+
* - {@link CrewError} — a structured `Error` subclass that carries an
|
|
15
|
+
* {@link ErrorCode}, an optional human-readable `help` hint, and an optional
|
|
16
|
+
* `context` string. Its `toString()` renders the display format:
|
|
17
|
+
* `error[E001]: Failed to read manifest.json: not found`.
|
|
18
|
+
*
|
|
19
|
+
* - {@link errors} — a factory object exposing convenience constructors
|
|
20
|
+
* (e.g. `errors.fileRead`, `errors.taskNotFound`) that build `CrewError`
|
|
21
|
+
* instances with sensible defaults and pre-attached context.
|
|
4
22
|
*/
|
|
5
23
|
// Implemented as const object + type alias (not `enum`) so that Node's
|
|
6
24
|
// `--experimental-strip-types` can load this module. TypeScript `enum`
|
|
@@ -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
|
-
|
|
1990
|
-
//
|
|
1991
|
-
//
|
|
1992
|
-
//
|
|
1993
|
-
//
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
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
|
-
//
|
|
107
|
-
//
|
|
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;
|