pi-crew 0.9.8 → 0.9.10
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 +311 -0
- package/README.md +2 -2
- package/docs/fixes/v0.9.10/locks-fix-verify.md +3 -0
- package/docs/fixes/v0.9.10/smoke-test.md +12 -0
- package/package.json +1 -1
- package/src/extension/register.ts +94 -21
- package/src/extension/registration/subagent-helpers.ts +1 -0
- package/src/extension/registration/subagent-tools.ts +9 -0
- package/src/extension/team-tool/doctor.ts +41 -18
- package/src/runtime/batch-barrier.ts +145 -0
- package/src/runtime/child-pi.ts +135 -22
- package/src/runtime/compact-pipeline.ts +56 -0
- package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
- package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
- package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
- package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
- package/src/runtime/compact-stages/index.ts +13 -0
- package/src/runtime/compact-stages/tail-capture-stage.ts +72 -0
- package/src/runtime/compact-stages/truncation-stage.ts +71 -0
- package/src/runtime/crash-classification.ts +208 -0
- package/src/runtime/custom-tools/irc-tool.ts +47 -7
- package/src/runtime/handoff-manager.ts +10 -0
- package/src/runtime/important-line-classifier.ts +130 -0
- package/src/runtime/iteration-hooks.ts +7 -19
- package/src/runtime/live-agent-manager.ts +185 -0
- package/src/runtime/live-session-runtime.ts +50 -1
- package/src/runtime/model-fallback.ts +29 -1
- package/src/runtime/process-lifecycle.ts +481 -0
- package/src/runtime/role-permission.ts +2 -2
- package/src/runtime/stream-preview.ts +9 -2
- package/src/runtime/subagent-manager.ts +6 -0
- package/src/runtime/task-output-context.ts +209 -24
- package/src/runtime/task-runner.ts +76 -15
- package/src/runtime/tool-output-pruner.ts +334 -0
- package/src/state/locks.ts +16 -0
- package/src/state/state-store.ts +8 -2
- package/src/state/types.ts +5 -0
- package/src/ui/live-run-sidebar.ts +6 -1
- package/src/ui/loaders.ts +24 -4
- package/src/ui/run-dashboard.ts +6 -1
- package/src/ui/run-event-bus.ts +1 -1
- package/src/ui/run-snapshot-cache.ts +50 -16
- package/src/ui/widget/index.ts +27 -5
- package/src/ui/widget/widget-renderer.ts +43 -13
- package/src/utils/redaction.ts +17 -1
- package/src/utils/visual.ts +6 -0
- package/src/ui/crew-widget.ts +0 -544
|
@@ -11,12 +11,21 @@ import { Box, Text } from "../layout-primitives.ts";
|
|
|
11
11
|
import { listLiveAgents } from "../../runtime/live-agent-manager.ts";
|
|
12
12
|
import { computePhaseProgress, formatPhaseProgressLine } from "../../runtime/phase-progress.ts";
|
|
13
13
|
import { spinnerFrame } from "../spinner.ts";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { computeLiveDurationMs } from "../live-duration.ts";
|
|
15
|
+
import { getTaskUsage } from "../../runtime/usage-tracker.ts";
|
|
16
|
+
import { agentActivity, agentStats, elapsed, formatTokensCompact, notificationBadge } from "./widget-formatters.ts";
|
|
17
|
+
import { activeWidgetRuns, shortRunLabel } from "./widget-model.ts";
|
|
16
18
|
import type { WidgetRun } from "./widget-types.ts";
|
|
17
19
|
|
|
18
20
|
const MAX_AGENTS_DISPLAY = 3;
|
|
19
21
|
const FINISHED_LINGER_MAX_AGE = 1;
|
|
22
|
+
/** Default terminal width when caller doesn't pass one explicitly. Keep <= 116
|
|
23
|
+
* (the same default used elsewhere in pi-crew tool renderers) so we never paint
|
|
24
|
+
* a line wider than the smallest expected TUI. Callers SHOULD pass the real
|
|
25
|
+
* width when known (via ctx.width || process.stdout.columns). */
|
|
26
|
+
export const DEFAULT_WIDGET_WIDTH = 100;
|
|
27
|
+
/** Cap per-component text so a single field cannot blow past width on its own. */
|
|
28
|
+
export const TASK_DESC_MAX = 60;
|
|
20
29
|
const ERROR_LINGER_MAX_AGE = 2;
|
|
21
30
|
const ERROR_STATUSES = new Set(["failed", "cancelled", "stopped", "needs_attention"]);
|
|
22
31
|
|
|
@@ -37,8 +46,12 @@ export function widgetHeader(runs: WidgetRun[], runningGlyph: string, maxLines =
|
|
|
37
46
|
|
|
38
47
|
// ── Line builder ──────────────────────────────────────────────────────
|
|
39
48
|
|
|
40
|
-
export function buildWidgetLines(cwd: string, frame = 0, maxLines = 8, providedRuns?: WidgetRun[], notificationCount = 0): string[] {
|
|
41
|
-
|
|
49
|
+
export function buildWidgetLines(cwd: string, frame = 0, maxLines = 8, providedRuns?: WidgetRun[], notificationCount = 0, width = DEFAULT_WIDGET_WIDTH): string[] {
|
|
50
|
+
// Match the legacy `buildCrewWidgetLines` API: when no runs are supplied,
|
|
51
|
+
// auto-fetch via activeWidgetRuns(cwd). Otherwise widgets calling with
|
|
52
|
+
// only `(cwd, frame)` would render an empty line set (regression vs. the
|
|
53
|
+
// pre-refactor implementation that called activeWidgetRuns here).
|
|
54
|
+
const runs = providedRuns ?? activeWidgetRuns(cwd);
|
|
42
55
|
if (!runs.length) return [];
|
|
43
56
|
|
|
44
57
|
const runningGlyph = spinnerFrame("widget-header");
|
|
@@ -56,9 +69,23 @@ export function buildWidgetLines(cwd: string, frame = 0, maxLines = 8, providedR
|
|
|
56
69
|
});
|
|
57
70
|
const completed = agents.filter((a) => a.status === "completed").length;
|
|
58
71
|
const runGlyph = iconForStatus(run.status, { runningGlyph });
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
// Run progress line. v1–v3 flickered on snapshot.tasks state, v4 was
|
|
73
|
+
// too minimal (`0/1 agents` only), v5 duplicated the worker activity
|
|
74
|
+
// line (tools/tokens/duration already shown one row below). v6 (this)
|
|
75
|
+
// shows only data that is RUN-level (not already in the per-agent
|
|
76
|
+
// activity line) and is GUARANTEED stable across ticks:
|
|
77
|
+
// - agents count — from `agents` array, always populated, never empty.
|
|
78
|
+
// - run elapsed — from `run.createdAt`, always set on manifest.
|
|
79
|
+
// Both come from sources with no race window — `agents` is read from
|
|
80
|
+
// snapshot.agents OR agentsFor(run) (both always return same length
|
|
81
|
+
// for a healthy run), and `run.createdAt` is immutable. The format
|
|
82
|
+
// shape `"X/Y agents · Ns"` is therefore truly invariant: same number
|
|
83
|
+
// of `·`-separated fields, same field meanings, every render tick.
|
|
84
|
+
const agentCountText = `${completed}/${agents.length} agents`;
|
|
85
|
+
const runElapsedMs = Math.max(0, Date.now() - new Date(run.createdAt).getTime());
|
|
86
|
+
const runElapsedText = `${Math.floor(runElapsedMs / 1000)}s`;
|
|
87
|
+
const progressPart = `${agentCountText} · ${runElapsedText}`;
|
|
88
|
+
lines.push(truncate(`├─ ${runGlyph} ${shortRunLabel(run)} · ${progressPart} · ${run.runId.slice(-8)}`, width));
|
|
62
89
|
|
|
63
90
|
const liveForRun = listLiveAgents().filter((a) => a.runId === run.runId);
|
|
64
91
|
|
|
@@ -67,8 +94,9 @@ export function buildWidgetLines(cwd: string, frame = 0, maxLines = 8, providedR
|
|
|
67
94
|
const name = liveHandle?.agent ?? agent.agent;
|
|
68
95
|
const icon = agent.status === "completed" ? "✓" : agent.status === "failed" ? "✗" : agent.status === "needs_attention" ? "⚠" : "▪";
|
|
69
96
|
const stats = agentStats(agent, liveHandle);
|
|
70
|
-
const desc = liveHandle?.description ?? agent.role;
|
|
71
|
-
|
|
97
|
+
const desc = truncate(liveHandle?.description ?? agent.role ?? "", TASK_DESC_MAX);
|
|
98
|
+
const _finished = truncate(`│ ├─ ${icon} ${name} · ${desc}${stats ? ` · ${stats}` : ""}`, width);
|
|
99
|
+
lines.push(_finished);
|
|
72
100
|
}
|
|
73
101
|
|
|
74
102
|
const visibleAgents = activeAgents.slice(0, MAX_AGENTS_DISPLAY);
|
|
@@ -79,13 +107,15 @@ export function buildWidgetLines(cwd: string, frame = 0, maxLines = 8, providedR
|
|
|
79
107
|
const liveHandle = liveForRun.find((h) => h.taskId === agent.taskId);
|
|
80
108
|
const stats = agentStats(agent, liveHandle);
|
|
81
109
|
const name = liveHandle?.agent ?? agent.agent;
|
|
82
|
-
const desc = liveHandle?.description ?? agent.role;
|
|
83
|
-
|
|
84
|
-
lines.push(
|
|
110
|
+
const desc = truncate(liveHandle?.description ?? agent.role ?? "", TASK_DESC_MAX);
|
|
111
|
+
const _activeMain = truncate(`│ ${branch} ${agentGlyph} ${name}${desc ? ` · ${desc}` : ` · ${agent.role}`}`, width);
|
|
112
|
+
lines.push(_activeMain);
|
|
113
|
+
const _activity = truncate(`│ ⊶ ${agentActivity(agent, liveHandle)}${stats ? ` · ${stats}` : ""}`, width);
|
|
114
|
+
lines.push(_activity);
|
|
85
115
|
}
|
|
86
116
|
|
|
87
117
|
if (activeAgents.length > MAX_AGENTS_DISPLAY) {
|
|
88
|
-
lines.push(`│ └─ … +${activeAgents.length - MAX_AGENTS_DISPLAY} more agents
|
|
118
|
+
lines.push(truncate(`│ └─ … +${activeAgents.length - MAX_AGENTS_DISPLAY} more agents`, width));
|
|
89
119
|
}
|
|
90
120
|
|
|
91
121
|
if (lines.length >= maxLines) break;
|
package/src/utils/redaction.ts
CHANGED
|
@@ -208,6 +208,22 @@ export function redactSecretString(value: string): string {
|
|
|
208
208
|
// i++ (advance 1 char) and re-scanned from i+1, so a long run was rescanned O(n)
|
|
209
209
|
// times = O(n^2). The P1f ReDoS test (300KB no-dot input) surfaced this pre-existing
|
|
210
210
|
// bug. Now advances past the whole run when it isn't a redactable secret -> O(n).
|
|
211
|
+
// FIX (BG2 follow-up): the inner-loop regex test /[a-zA-Z0-9_-]/.test(value[j])
|
|
212
|
+
// is O(1) per call but with measurable regex-engine overhead — for a 100KB
|
|
213
|
+
// underscore run that's 100K regex calls, well over the 200ms budget the
|
|
214
|
+
// P1f regression test allows. Replaced with a charCodeAt check (~5x faster
|
|
215
|
+
// in practice, O(1) per call with no regex allocation/eval cost).
|
|
216
|
+
function isKeyChar(c: string): boolean {
|
|
217
|
+
const code = c.charCodeAt(0);
|
|
218
|
+
return (
|
|
219
|
+
(code >= 48 && code <= 57) || // 0-9
|
|
220
|
+
(code >= 65 && code <= 90) || // A-Z
|
|
221
|
+
(code >= 97 && code <= 122) || // a-z
|
|
222
|
+
code === 95 || // _
|
|
223
|
+
code === 45 // -
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
211
227
|
function redactInlineSecrets(value: string): string {
|
|
212
228
|
const result: string[] = [];
|
|
213
229
|
let i = 0;
|
|
@@ -215,7 +231,7 @@ function redactInlineSecrets(value: string): string {
|
|
|
215
231
|
while (i < value.length) {
|
|
216
232
|
// Collect a run of key characters (alphanumeric, underscore, hyphen).
|
|
217
233
|
let j = i;
|
|
218
|
-
while (j < value.length &&
|
|
234
|
+
while (j < value.length && isKeyChar(value[j])) {
|
|
219
235
|
j++;
|
|
220
236
|
}
|
|
221
237
|
const keyLen = j - i;
|
package/src/utils/visual.ts
CHANGED
|
@@ -29,6 +29,12 @@ const WIDE_RANGES: Array<[number, number]> = [
|
|
|
29
29
|
[0x274C, 0x274C], [0x274E, 0x274E], [0x2753, 0x2755], [0x2757, 0x2757],
|
|
30
30
|
[0x2763, 0x2764], [0x2795, 0x2797], [0x27A1, 0x27A1], [0x27B0, 0x27B0],
|
|
31
31
|
[0x27BF, 0x27BF],
|
|
32
|
+
// Geometric Shapes / Misc Symbols-Arrows emoji that pi-tui upstream counts as
|
|
33
|
+
// width=2 (RGI emoji). Mismatch here caused the "Rendered line N exceeds
|
|
34
|
+
// terminal width (160 > 159)" TUI crash: pi-crew truncated to width 159 by
|
|
35
|
+
// its own (mismatched) measure, then Box padded to 159 chars, but pi-tui
|
|
36
|
+
// re-measured the padded line at 160 because ⬜ counts as 2 upstream.
|
|
37
|
+
[0x2B1B, 0x2B1C], // ⬛ BLACK LARGE SQUARE, ⬜ WHITE LARGE SQUARE
|
|
32
38
|
[0x1F300, 0x1F9FF], // Misc Symbols, Emoticons, Transport, Map, Supplement
|
|
33
39
|
[0x1FA00, 0x1FAFF], // Symbols Extended-A
|
|
34
40
|
[0x1F000, 0x1F02F], // Mahjong, Dominos
|