pi-crew 0.6.3 → 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.
- package/CHANGELOG.md +84 -0
- package/README.md +80 -1
- package/docs/ui-optimization-plan.md +447 -0
- package/package.json +2 -1
- package/src/extension/knowledge-injection.ts +71 -0
- package/src/extension/pi-api.ts +47 -0
- package/src/extension/register.ts +32 -1
- package/src/extension/registration/commands.ts +65 -1
- package/src/extension/registration/compaction-guard.ts +154 -14
- package/src/extension/registration/subagent-tools.ts +8 -3
- package/src/extension/registration/team-tool.ts +18 -11
- 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/extension/team-tool/run.ts +4 -3
- package/src/extension/team-tool-types.ts +2 -0
- 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/team-runner.ts +23 -6
- package/src/schema/team-tool-schema.ts +6 -0
- package/src/state/session-state-map.ts +51 -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/powerbar-publisher.ts +1 -1
- package/src/ui/render-diff.ts +37 -3
- package/src/ui/settings-overlay.ts +70 -7
- package/src/ui/status-colors.ts +5 -1
- package/src/ui/syntax-highlight.ts +42 -23
- package/src/ui/theme-adapter.ts +80 -1
- package/src/ui/theme-discovery.ts +188 -0
- package/src/ui/tool-progress-formatter.ts +9 -5
- package/src/ui/tool-render.ts +4 -0
- package/src/ui/tool-renderers/brief-mode.ts +207 -0
- package/src/ui/tool-renderers/index.ts +640 -0
- package/src/ui/widget/index.ts +224 -0
- package/src/ui/widget/widget-formatters.ts +148 -0
- package/src/ui/widget/widget-model.ts +90 -0
- package/src/ui/widget/widget-renderer.ts +130 -0
- package/src/ui/widget/widget-types.ts +37 -0
- package/src/utils/guards.ts +110 -0
- 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/runtime/budget-tracker.ts +0 -354
- package/src/state/memory-store.ts +0 -244
|
@@ -4,6 +4,11 @@ import { configPatchFromConfig } from "../team-tool/config-patch.ts";
|
|
|
4
4
|
import { result } from "../team-tool/context.ts";
|
|
5
5
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
6
6
|
import { suggestConfigKey } from "../../config/suggestions.ts";
|
|
7
|
+
import {
|
|
8
|
+
formatThemesListing,
|
|
9
|
+
discoverPiThemes,
|
|
10
|
+
setPiTheme,
|
|
11
|
+
} from "../../ui/theme-discovery.ts";
|
|
7
12
|
|
|
8
13
|
// ---------------------------------------------------------------------------
|
|
9
14
|
// Effective defaults — values used when config key is not set
|
|
@@ -244,6 +249,8 @@ const USAGE = [
|
|
|
244
249
|
" json Show full effective config as JSON",
|
|
245
250
|
" schema Show all known config keys (schema reference)",
|
|
246
251
|
" paths Show config file paths (user + project)",
|
|
252
|
+
" themes Browse theme gallery (Pi UI themes)",
|
|
253
|
+
" theme <name> Switch the Pi UI theme (applies live, no restart)",
|
|
247
254
|
" get <key> Get a specific config value",
|
|
248
255
|
" set <key> <value> Set a config value",
|
|
249
256
|
" unset <key> Remove a config value",
|
|
@@ -319,6 +326,56 @@ export function handleSettings(params: { config?: Record<string, unknown> }, ctx
|
|
|
319
326
|
return result(lines.join("\n"), { ...OK, path: loaded.path } as never);
|
|
320
327
|
}
|
|
321
328
|
|
|
329
|
+
// team-settings themes — browse the theme gallery
|
|
330
|
+
if (args === "themes" || args === "theme-gallery") {
|
|
331
|
+
return result(formatThemesListing(), { ...OK } as never);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// team-settings theme <name> — switch the Pi UI theme
|
|
335
|
+
if (args === "theme" || args.startsWith("theme ")) {
|
|
336
|
+
const name = args === "theme" ? "" : args.slice(6).trim();
|
|
337
|
+
if (!name) {
|
|
338
|
+
const available = discoverPiThemes().map((t) => t.name).join(", ");
|
|
339
|
+
return result(
|
|
340
|
+
`Usage: team-settings theme <name>\n\nAvailable Pi themes: ${available}\n\nBrowse all: team-settings themes`,
|
|
341
|
+
{ ...ERR },
|
|
342
|
+
true,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
const available = discoverPiThemes();
|
|
346
|
+
const exists = available.some((t) => t.name === name);
|
|
347
|
+
if (!exists) {
|
|
348
|
+
const hint = available.map((t) => t.name).join(", ");
|
|
349
|
+
return result(
|
|
350
|
+
`Unknown Pi theme: ${name}\n\nAvailable: ${hint}\n\nCustom themes live in ~/.pi/agent/themes/<name>.json`,
|
|
351
|
+
{ ...ERR },
|
|
352
|
+
true,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const savedTo = setPiTheme(name);
|
|
357
|
+
return result(
|
|
358
|
+
[
|
|
359
|
+
`✓ Pi theme set to '${name}'`,
|
|
360
|
+
` Written to: ${savedTo}`,
|
|
361
|
+
` Applied live — no restart needed.`,
|
|
362
|
+
].join("\n"),
|
|
363
|
+
{ ...OK, theme: name } as never,
|
|
364
|
+
);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
return result(error instanceof Error ? error.message : String(error), { ...ERR }, true);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// team-settings shiki <name> — removed (Shiki highlighting dropped)
|
|
371
|
+
if (args === "shiki" || args === "shiki-theme" || args.startsWith("shiki ") || args.startsWith("shiki-theme ")) {
|
|
372
|
+
return result(
|
|
373
|
+
`Shiki syntax highlighting has been removed from pi-crew.\nUse 'team-settings theme <name>' to switch the Pi UI theme, which drives code-block colors.`,
|
|
374
|
+
{ ...ERR },
|
|
375
|
+
true,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
322
379
|
// team-settings scope [user|project]
|
|
323
380
|
if (args === "scope" || args.startsWith("scope ")) {
|
|
324
381
|
const scopeArg = args === "scope" ? "" : args.slice(6).trim();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
2
2
|
import { readEvents } from "../../state/event-log.ts";
|
|
3
3
|
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
4
|
-
import { aggregateUsage, formatUsage } from "../../state/usage.ts";
|
|
4
|
+
import { aggregateUsage, formatUsage, formatCostReport } from "../../state/usage.ts";
|
|
5
5
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
6
6
|
import { locateRunCwd } from "../team-tool.ts";
|
|
7
7
|
import { result, type TeamContext } from "./context.ts";
|
|
@@ -41,6 +41,9 @@ export function handleSummary(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
41
41
|
`Workflow: ${loaded.manifest.workflow ?? "(none)"}`,
|
|
42
42
|
`Goal: ${loaded.manifest.goal}`,
|
|
43
43
|
`Usage: ${formatUsage(usage)}`,
|
|
44
|
+
"",
|
|
45
|
+
formatCostReport(loaded.tasks),
|
|
46
|
+
"",
|
|
44
47
|
"Tasks:",
|
|
45
48
|
...loaded.tasks.map((task) => `- ${task.id}: ${task.status} (${task.role} -> ${task.agent})${task.error ? ` - ${task.error}` : ""}`),
|
|
46
49
|
];
|
|
@@ -4,6 +4,7 @@ import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
|
|
|
4
4
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
5
5
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
6
6
|
import { result, type TeamContext } from "./context.ts";
|
|
7
|
+
import { composeSingleAgentPrompt } from "../../runtime/single-agent-compose.ts";
|
|
7
8
|
|
|
8
9
|
export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
9
10
|
const teamName = params.team ?? "default";
|
|
@@ -14,6 +15,12 @@ export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTea
|
|
|
14
15
|
if (!workflow) return result(`Workflow '${workflowName}' not found.`, { action: "plan", status: "error" }, true);
|
|
15
16
|
const errors = validateWorkflowForTeam(workflow, team);
|
|
16
17
|
if (errors.length > 0) return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...errors.map((error) => `- ${error}`)].join("\n"), { action: "plan", status: "error" }, true);
|
|
17
|
-
const
|
|
18
|
+
const goal = params.goal ?? params.task ?? "(not provided)";
|
|
19
|
+
// ROADMAP T2.2: single-agent composition mode (cliff hedge).
|
|
20
|
+
if (params.singleAgent) {
|
|
21
|
+
const composed = composeSingleAgentPrompt(workflow, goal);
|
|
22
|
+
return result([`Single-agent plan for ${team.name} / ${workflow.name} (${composed.stepCount} phases composed into one sequential prompt):`, "", composed.prompt, "", "This prompt can be handed to a single agent to execute the entire workflow sequentially — pi-crew's cliff-resilient mode (survives single-agent domination)."].join("\n"), { action: "plan", status: "ok" });
|
|
23
|
+
}
|
|
24
|
+
const lines = [`Team plan: ${team.name}`, `Workflow: ${workflow.name}`, `Goal: ${goal}`, "", "Steps:", ...workflow.steps.map((step, index) => `${index + 1}. ${step.id} [${step.role}]${step.dependsOn?.length ? ` after ${step.dependsOn.join(", ")}` : ""}`)];
|
|
18
25
|
return result(lines.join("\n"), { action: "plan", status: "ok" });
|
|
19
26
|
}
|
|
@@ -254,6 +254,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
254
254
|
const asyncManifest = { ...effectiveManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
|
|
255
255
|
atomicWriteJson(paths.manifestPath, asyncManifest);
|
|
256
256
|
void appendEventAsync(effectiveManifest.eventsPath, { type: "async.spawned", runId: effectiveManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
|
|
257
|
+
ctx.onRunStarted?.(effectiveManifest.runId);
|
|
257
258
|
scheduleBackgroundEarlyExitGuard(resolvedCtx.cwd, effectiveManifest.runId, spawned.pid, spawned.logPath);
|
|
258
259
|
// Wait for the async run to complete and return actual results.
|
|
259
260
|
try {
|
|
@@ -341,7 +342,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
341
342
|
}
|
|
342
343
|
|
|
343
344
|
const runFailed = completed.manifest.status === "failed" || completed.manifest.status === "blocked";
|
|
344
|
-
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot }, runFailed);
|
|
345
|
+
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot, metrics }, runFailed);
|
|
345
346
|
} catch (waitError: unknown) {
|
|
346
347
|
const errorMessage = waitError instanceof Error ? waitError.message : String(waitError);
|
|
347
348
|
return result(
|
|
@@ -474,7 +475,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
474
475
|
}
|
|
475
476
|
|
|
476
477
|
const runFailed = completed.manifest.status === "failed" || completed.manifest.status === "blocked";
|
|
477
|
-
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot }, runFailed);
|
|
478
|
+
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot, metrics }, runFailed);
|
|
478
479
|
} catch (waitError: unknown) {
|
|
479
480
|
const errorMessage = waitError instanceof Error ? waitError.message : String(waitError);
|
|
480
481
|
return result(
|
|
@@ -514,5 +515,5 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
514
515
|
? "Experimental live-session worker execution was enabled."
|
|
515
516
|
: "Safe scaffold mode: child Pi workers were not launched because runtime.mode=scaffold or executeWorkers=false was configured.",
|
|
516
517
|
].join("\n");
|
|
517
|
-
return result(text, { action: "run", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot }, executed.manifest.status === "failed");
|
|
518
|
+
return result(text, { action: "run", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot, metrics: collectRunMetrics(resolvedCtx.cwd, executed.manifest.runId) }, executed.manifest.status === "failed");
|
|
518
519
|
}
|
|
@@ -10,6 +10,8 @@ export interface TeamToolDetails {
|
|
|
10
10
|
resumedIds?: string[];
|
|
11
11
|
retriedTaskIds?: string[];
|
|
12
12
|
mailboxIds?: string[];
|
|
13
|
+
/** Run metrics for compact display in TUI tool result rendering. */
|
|
14
|
+
metrics?: { taskCount?: number; completedCount?: number; totalTokens?: number; totalCost?: number; durationMs?: number; consistencyScore?: number };
|
|
13
15
|
/** Structured data for programmatic consumption (e.g. TUI widgets). */
|
|
14
16
|
data?: Record<string, unknown>;
|
|
15
17
|
}
|
|
@@ -51,6 +51,10 @@ const MAX_QUEUE_SIZE = 100;
|
|
|
51
51
|
export class IntercomQueue {
|
|
52
52
|
private pending = new Map<string, PendingMessage>();
|
|
53
53
|
private queue: IntercomMessage[] = [];
|
|
54
|
+
/** Monotonic sequence counter — guarantees unique IDs even when many
|
|
55
|
+
* messages enqueue within the same millisecond (avoids Date.now()
|
|
56
|
+
* collision + weak Math.random birthday-paradox flakes under load). */
|
|
57
|
+
private seq = 0;
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
60
|
* Enqueue a message and return a promise that resolves when the
|
|
@@ -63,7 +67,7 @@ export class IntercomQueue {
|
|
|
63
67
|
if (firstKey) this.evict(firstKey, "queue_full");
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
const id = `icm-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,
|
|
70
|
+
const id = `icm-${Date.now().toString(36)}-${(this.seq++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
67
71
|
|
|
68
72
|
return new Promise<IntercomResponse>((resolve) => {
|
|
69
73
|
const entry: PendingMessage = { message, id, resolve };
|