pi-crew 0.11.0 → 0.11.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 +155 -9
- package/README.md +161 -1037
- package/agents/verifier.md +18 -7
- package/dist/index.mjs +744 -90644
- package/docs/README.md +57 -46
- package/docs/architecture.md +87 -33
- package/docs/commands-reference.md +9 -5
- package/docs/troubleshooting.md +3 -2
- package/package.json +1 -3
- package/schema.json +39 -0
- package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +2 -0
- package/skills/real-test-pi-crew/SKILL.md +371 -34
- package/src/agents/agent-config.ts +1 -1
- package/src/agents/discover-agents.ts +1 -1
- package/src/config/config-validation.ts +15 -2
- package/src/config/config.ts +47 -13
- package/src/config/defaults.ts +0 -1
- package/src/config/env-vars.ts +35 -0
- package/src/config/types.ts +19 -5
- package/src/errors.ts +2 -2
- package/src/extension/async-notifier.ts +23 -0
- package/src/extension/crew-vibes/config.ts +0 -21
- package/src/extension/crew-vibes/index.ts +0 -2
- package/src/extension/crew-vibes/render.ts +1 -50
- package/src/extension/help.ts +21 -12
- package/src/extension/knowledge-injection.ts +2 -1
- package/src/extension/management.ts +8 -3
- package/src/extension/notification-sink.ts +17 -0
- package/src/extension/register.ts +7 -2
- package/src/extension/registration/command-utils.ts +28 -2
- package/src/extension/registration/commands/dashboard.ts +11 -1
- package/src/extension/registration/commands/manage.ts +36 -19
- package/src/extension/registration/commands/run.ts +24 -2
- package/src/extension/registration/commands/shared.ts +23 -1
- package/src/extension/registration/commands/status.ts +25 -2
- package/src/extension/registration/context-builder.ts +8 -2
- package/src/extension/registration/health-notify-policy.ts +100 -0
- package/src/extension/registration/lazy-configurers.ts +35 -0
- package/src/extension/registration/lifecycle-handlers.ts +91 -30
- package/src/extension/registration/lifecycle.ts +75 -10
- package/src/extension/registration/observability.ts +98 -35
- package/src/extension/registration/registration-types.ts +7 -5
- package/src/extension/registration/runtime-cleanup.ts +9 -3
- package/src/extension/registration/subagent-helpers.ts +38 -0
- package/src/extension/registration/subagent-tools.ts +16 -6
- package/src/extension/registration/team-tool.ts +10 -3
- package/src/extension/registration/terminal-status-wiring.ts +172 -0
- package/src/extension/registration/viewers.ts +6 -0
- package/src/extension/registration/wire-cross-extension.ts +28 -0
- package/src/extension/run-compare.ts +220 -0
- package/src/extension/run-export.ts +37 -5
- package/src/extension/run-maintenance.ts +155 -5
- package/src/extension/team-tool/dispatch/index.ts +3 -2
- package/src/extension/team-tool/dispatch/manage.ts +5 -2
- package/src/extension/team-tool/goal.ts +4 -1
- package/src/extension/team-tool/handle-settings.ts +33 -4
- package/src/extension/team-tool/health-monitor.ts +21 -7
- package/src/extension/team-tool/lifecycle-actions.ts +49 -1
- package/src/extension/team-tool/plan.ts +10 -0
- package/src/extension/team-tool/routing-hint.ts +63 -0
- package/src/extension/team-tool/status.ts +4 -0
- package/src/extension/team-tool.ts +52 -6
- package/src/extension/webhook-notify.ts +382 -0
- package/src/observability/metric-sink.ts +12 -2
- package/src/prompt/prompt-runtime.ts +82 -31
- package/src/prompt/worker-events-channel.ts +12 -0
- package/src/runtime/README.md +1 -1
- package/src/runtime/async-runner.ts +87 -1
- package/src/runtime/background-runner.ts +313 -234
- package/src/runtime/broker/crew-broker.ts +17 -11
- package/src/runtime/broker/delegate/shadow-lifecycle.ts +92 -0
- package/src/runtime/broker/wait-status-cache.ts +1 -1
- package/src/runtime/child-pi/child-pi-timers.ts +1 -1
- package/src/runtime/child-pi/mock-fixtures.ts +48 -0
- package/src/runtime/crew-agent-records.ts +337 -45
- package/src/runtime/deadletter.ts +43 -1
- package/src/runtime/delegate-spawn.ts +5 -1
- package/src/runtime/dispatch-batch.ts +72 -5
- package/src/runtime/goal-workflow/goal-loop-runner.ts +73 -4
- package/src/runtime/heartbeat/heartbeat-watcher.ts +7 -0
- package/src/runtime/model/model-fallback.ts +21 -1
- package/src/runtime/model/pi-args.ts +8 -10
- package/src/runtime/recovery/crash-recovery.ts +25 -1
- package/src/runtime/run-worker.ts +12 -1
- package/src/runtime/scheduling/global-worker-cap.ts +13 -6
- package/src/runtime/scheduling/run-coalesced-task-group.ts +27 -1
- package/src/runtime/scheduling/scheduler.ts +49 -13
- package/src/runtime/scheduling/semaphore.ts +148 -20
- package/src/runtime/scratchpad/README.md +1 -1
- package/src/runtime/scratchpad/protocol.ts +1 -1
- package/src/runtime/settings-store.ts +1 -1
- package/src/runtime/skill-instructions.ts +22 -0
- package/src/runtime/stale-reconciler.ts +85 -13
- package/src/runtime/task-display.ts +1 -1
- package/src/runtime/task-runner/pre-execution.ts +26 -2
- package/src/runtime/task-runner/prompt-builder.ts +142 -45
- package/src/runtime/task-runner.ts +21 -1
- package/src/runtime/team-runner.ts +38 -1
- package/src/runtime/workspace-lock.ts +4 -1
- package/src/schema/config-schema.ts +18 -0
- package/src/schema/team-tool-schema.ts +17 -0
- package/src/state/atomic-write.ts +53 -0
- package/src/state/contracts.ts +109 -0
- package/src/state/coordination/locks.ts +191 -33
- package/src/state/coordination/mailbox.ts +140 -15
- package/src/state/crew-init.ts +87 -12
- package/src/state/event-log/cursor.ts +37 -1
- package/src/state/event-log/event-log-rotation.ts +72 -7
- package/src/state/stores/active-run-registry.ts +13 -1
- package/src/state/stores/state-store.ts +112 -22
- package/src/state/types.ts +4 -0
- package/src/ui/adaptive-card.ts +65 -0
- package/src/ui/agents-jobs-browser.ts +70 -64
- package/src/ui/card-colors.ts +36 -7
- package/src/ui/dashboard-panes/agents-pane.ts +55 -14
- package/src/ui/dashboard-panes/cancellation-pane.ts +0 -42
- package/src/ui/dashboard-panes/health-pane.ts +7 -5
- package/src/ui/dashboard-panes/mailbox-pane.ts +22 -6
- package/src/ui/dashboard-panes/metrics-pane.ts +15 -7
- package/src/ui/dashboard-panes/pane-theme.ts +21 -0
- package/src/ui/dashboard-panes/plan-pane.ts +63 -30
- package/src/ui/dashboard-panes/progress-pane.ts +3 -2
- package/src/ui/dashboard-panes/schedules-pane.ts +44 -21
- package/src/ui/dashboard-panes/transcript-pane.ts +11 -5
- package/src/ui/dwf-phase-display.ts +3 -20
- package/src/ui/format-helpers.ts +22 -0
- package/src/ui/heartbeat-aggregator.ts +34 -0
- package/src/ui/inline-panel/crew-editor.ts +13 -3
- package/src/ui/inline-panel/index.ts +60 -4
- package/src/ui/keybinding-map.ts +251 -35
- package/src/ui/live-conversation-overlay.ts +180 -47
- package/src/ui/live-run-sidebar.ts +134 -55
- package/src/ui/mascot.ts +32 -16
- package/src/ui/overlays/agent-picker-overlay.ts +81 -26
- package/src/ui/overlays/confirm-overlay.ts +55 -29
- package/src/ui/overlays/help-overlay.ts +108 -53
- package/src/ui/overlays/mailbox-compose-overlay.ts +89 -50
- package/src/ui/overlays/mailbox-detail-overlay.ts +137 -57
- package/src/ui/powerbar-publisher.ts +0 -1
- package/src/ui/rail.ts +333 -0
- package/src/ui/run-dashboard.ts +193 -79
- package/src/ui/run-snapshot-cache.ts +18 -1
- package/src/ui/settings-overlay.ts +81 -39
- package/src/ui/spinner.ts +26 -2
- package/src/ui/terminal-status.ts +7 -1
- package/src/ui/theme-adapter.ts +0 -45
- package/src/ui/theme-discovery.ts +12 -6
- package/src/ui/tool-progress-formatter.ts +128 -9
- package/src/ui/tool-renderers/brief-mode.ts +10 -67
- package/src/ui/tool-renderers/index.ts +374 -523
- package/src/ui/transcript-viewer.ts +30 -12
- package/src/ui/widget/index.ts +32 -52
- package/src/ui/widget/task-list.ts +64 -32
- package/src/ui/widget/widget-formatters.ts +3 -402
- package/src/ui/widget/widget-model.ts +28 -7
- package/src/ui/widget/widget-renderer.ts +201 -128
- package/src/ui/widget/widget-types.ts +0 -2
- package/src/utils/incremental-reader.ts +11 -3
- package/src/utils/paths.ts +94 -12
- package/src/utils/project-markers.ts +40 -0
- package/src/utils/visual.ts +0 -4
- package/src/worktree/worktree-manager.ts +206 -26
- package/workflows/distill.workflow.md +3 -3
- package/workflows/fast-fix.workflow.md +1 -1
- package/workflows/plan-execute.workflow.md +1 -1
- package/workflows/review.workflow.md +1 -1
- package/workflows/strict-fast-fix.workflow.md +1 -1
- package/docs/migration-v0.4-v0.5.md +0 -208
- package/docs/runtime-flow.md +0 -148
- package/src/extension/crew-vibes/figures.ts +0 -22
- package/src/extension/crew-vibes/font-detect.ts +0 -71
- package/src/ui/dynamic-border.ts +0 -35
- package/src/ui/loaders.ts +0 -6
- package/src/ui/overlay-stack.ts +0 -148
|
@@ -167,7 +167,7 @@ export function resolveToolPolicy(agent: AgentConfig, role?: string): ResolvedTo
|
|
|
167
167
|
agent.loadMode === "lean" && agent.defaultTools?.length ? uniqueToolMerge(explicitTools, agent.defaultTools) : explicitTools;
|
|
168
168
|
// denylist: additive merge of role excludeTools + agent disallowedTools.
|
|
169
169
|
let excludeTools = uniqueToolMerge(roleConfig.excludeTools, agent.disallowedTools);
|
|
170
|
-
// P2 (scratchpad adoption lever, rlm-deep-review-2026-08-12.md §5.1A):
|
|
170
|
+
// P2 (scratchpad adoption lever, docs/archive/rlm-deep-review-2026-08-12.md §5.1A):
|
|
171
171
|
// when scratchpad is armed for this role AND the operator opted in via
|
|
172
172
|
// PI_CREW_SCRATCHPAD_DEMOTE_BASH=1, remove `bash` from the tool surface so
|
|
173
173
|
// the model reaches for `sh()` inside scratchpad cells (structured value
|
|
@@ -410,7 +410,7 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
|
|
|
410
410
|
// below use logInternalError).
|
|
411
411
|
if (contextMode === "fork" && !warnedForkAgents.has(filePath)) {
|
|
412
412
|
console.warn(
|
|
413
|
-
"contextMode: 'fork' is only effective in live-session runtime; current default child-process will behave as 'fresh'. See docs/
|
|
413
|
+
"contextMode: 'fork' is only effective in live-session runtime; current default child-process will behave as 'fresh'. See docs/architecture.md.",
|
|
414
414
|
);
|
|
415
415
|
warnedForkAgents.add(filePath);
|
|
416
416
|
// R5-L5: FIFO eviction — drop the oldest entry past the cap.
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
CrewTelemetryConfig,
|
|
22
22
|
CrewToolsConfig,
|
|
23
23
|
CrewUiConfig,
|
|
24
|
+
CrewWebhookConfig,
|
|
24
25
|
CrewWorktreeConfig,
|
|
25
26
|
GoalWrapWorkflowConfig,
|
|
26
27
|
PersistenceConfig,
|
|
@@ -499,11 +500,9 @@ function parseUiConfig(value: unknown): CrewUiConfig | undefined {
|
|
|
499
500
|
obj.widgetPlacement,
|
|
500
501
|
);
|
|
501
502
|
const rawDashboardPlacement = parseWithSchema(Type.Union([Type.Literal("center"), Type.Literal("right")]), obj.dashboardPlacement);
|
|
502
|
-
const rawRowStyle = parseWithSchema(Type.Union([Type.Literal("compact"), Type.Literal("detailed")]), obj.widgetRowStyle);
|
|
503
503
|
const ui: CrewUiConfig = {
|
|
504
504
|
widgetPlacement: rawWidgetPlacement,
|
|
505
505
|
widgetMaxLines: parsePositiveInteger(obj.widgetMaxLines, 50),
|
|
506
|
-
widgetRowStyle: rawRowStyle,
|
|
507
506
|
inlinePanel: parseWithSchema(Type.Boolean(), obj.inlinePanel),
|
|
508
507
|
powerbar: parseWithSchema(Type.Boolean(), obj.powerbar),
|
|
509
508
|
dashboardPlacement: rawDashboardPlacement,
|
|
@@ -585,6 +584,19 @@ function parsePolicyConfig(value: unknown): CrewPolicyConfig | undefined {
|
|
|
585
584
|
function parseNotificationsConfig(value: unknown): CrewNotificationsConfig | undefined {
|
|
586
585
|
const obj = asRecord(value);
|
|
587
586
|
if (!obj) return undefined;
|
|
587
|
+
// US-030: webhook block — field-wise parse mirroring the schema (url must
|
|
588
|
+
// be a non-empty string; an invalid/missing url parses to "" which the
|
|
589
|
+
// notifier treats as disabled — zero network). Sensitive: schema marks the
|
|
590
|
+
// block user-config-only; this parser stays shape-neutral.
|
|
591
|
+
const webhookObj = asRecord(obj.webhook);
|
|
592
|
+
const webhook: CrewWebhookConfig | undefined = webhookObj
|
|
593
|
+
? {
|
|
594
|
+
url: parseWithSchema(Type.String({ minLength: 1 }), webhookObj.url) ?? "",
|
|
595
|
+
enabled: parseWithSchema(Type.Boolean(), webhookObj.enabled),
|
|
596
|
+
secret: parseWithSchema(Type.String({ minLength: 1 }), webhookObj.secret),
|
|
597
|
+
allowLocalhost: parseWithSchema(Type.Boolean(), webhookObj.allowLocalhost),
|
|
598
|
+
}
|
|
599
|
+
: undefined;
|
|
588
600
|
const notifications: CrewNotificationsConfig = {
|
|
589
601
|
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
590
602
|
severityFilter: parseWithSchema(
|
|
@@ -599,6 +611,7 @@ function parseNotificationsConfig(value: unknown): CrewNotificationsConfig | und
|
|
|
599
611
|
batchWindowMs: parseWithSchema(Type.Integer({ minimum: 0, maximum: 60_000 }), obj.batchWindowMs),
|
|
600
612
|
quietHours: parseWithSchema(Type.String({ pattern: "^\\d{2}:\\d{2}-\\d{2}:\\d{2}$" }), obj.quietHours),
|
|
601
613
|
sinkRetentionDays: parsePositiveInteger(obj.sinkRetentionDays, 90),
|
|
614
|
+
webhook,
|
|
602
615
|
};
|
|
603
616
|
return Object.values(notifications).some((entry) => entry !== undefined) ? notifications : undefined;
|
|
604
617
|
}
|
package/src/config/config.ts
CHANGED
|
@@ -264,12 +264,47 @@ function unsetPath(record: Record<string, unknown>, dottedPath: string): void {
|
|
|
264
264
|
delete target[parts[parts.length - 1]!];
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
// F08 (RR-016): the JSON depth guard below MAX_JSON_DEPTH containers of TRUE
|
|
268
|
+
// nesting — measured by an explicit-stack walk over the parsed value, NOT by a
|
|
269
|
+
// JSON.parse reviver counter. The previous reviver incremented once per VALUE,
|
|
270
|
+
// so a shallow-but-wide config (48 agent model overrides = 101 values) was
|
|
271
|
+
// rejected as "too deep" and the ENTIRE file was discarded — silently losing
|
|
272
|
+
// every resource limit in it, while a genuinely 96-level-deep document with 99
|
|
273
|
+
// values was accepted. Keeping this a real depth limit preserves the guard
|
|
274
|
+
// against pathological nesting without punishing wide configs.
|
|
275
|
+
const MAX_CONFIG_SIZE = 10 * 1024 * 1024;
|
|
276
|
+
const MAX_JSON_DEPTH = 100;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Deepest container (object/array) nesting level of a parsed JSON value;
|
|
280
|
+
* scalars do not add depth. Iterative (explicit stack) on purpose: a recursive
|
|
281
|
+
* walk would itself stack-overflow on the exact input this guard exists to
|
|
282
|
+
* reject. Cost is O(nodes) and it is only run once per config parse (which the
|
|
283
|
+
* 2s loadConfig cache already amortizes).
|
|
284
|
+
*/
|
|
285
|
+
function measureJsonDepth(root: unknown): number {
|
|
286
|
+
let maxDepth = 0;
|
|
287
|
+
const stack: Array<{ value: unknown; depth: number }> = [{ value: root, depth: 1 }];
|
|
288
|
+
while (stack.length > 0) {
|
|
289
|
+
const frame = stack.pop()!;
|
|
290
|
+
if (frame.depth > maxDepth) maxDepth = frame.depth;
|
|
291
|
+
const children: unknown[] = Array.isArray(frame.value)
|
|
292
|
+
? frame.value
|
|
293
|
+
: frame.value !== null && typeof frame.value === "object"
|
|
294
|
+
? Object.values(frame.value as Record<string, unknown>)
|
|
295
|
+
: [];
|
|
296
|
+
for (const child of children) {
|
|
297
|
+
if (child !== null && typeof child === "object") stack.push({ value: child, depth: frame.depth + 1 });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return maxDepth;
|
|
301
|
+
}
|
|
302
|
+
|
|
267
303
|
function readConfigRecord(filePath: string): Record<string, unknown> {
|
|
268
304
|
if (!fs.existsSync(filePath)) return {};
|
|
269
305
|
// Defense-in-depth: reject config files larger than 10 MB before parsing.
|
|
270
|
-
// This prevents memory exhaustion
|
|
271
|
-
//
|
|
272
|
-
const MAX_CONFIG_SIZE = 10 * 1024 * 1024;
|
|
306
|
+
// This prevents memory exhaustion from oversized files. (Behaviour kept
|
|
307
|
+
// unchanged by F08/RR-016: the byte-size limit stays a separate, hard cap.)
|
|
273
308
|
const stat = fs.statSync(filePath);
|
|
274
309
|
if (stat.size > MAX_CONFIG_SIZE) {
|
|
275
310
|
logInternalError(
|
|
@@ -279,17 +314,16 @@ function readConfigRecord(filePath: string): Record<string, unknown> {
|
|
|
279
314
|
);
|
|
280
315
|
return {};
|
|
281
316
|
}
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
const
|
|
285
|
-
let depth = 0;
|
|
286
|
-
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"), (_key, value) => {
|
|
287
|
-
if (++depth > MAX_JSON_DEPTH) {
|
|
288
|
-
throw new Error(`config JSON exceeds max depth ${MAX_JSON_DEPTH}`);
|
|
289
|
-
}
|
|
290
|
-
return value;
|
|
291
|
-
}) as unknown;
|
|
317
|
+
// Plain parse first (V8's JSON.parse is iterative — a 1e6-deep document
|
|
318
|
+
// parses without a stack overflow), then the iterative depth walk above.
|
|
319
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
|
|
292
320
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
321
|
+
const depth = measureJsonDepth(raw);
|
|
322
|
+
if (depth > MAX_JSON_DEPTH) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
`config JSON nesting depth ${depth} exceeds max depth ${MAX_JSON_DEPTH} (fix: reduce nesting in ${filePath}, or delete the file to fall back to defaults)`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
293
327
|
return raw as Record<string, unknown>;
|
|
294
328
|
}
|
|
295
329
|
|
package/src/config/defaults.ts
CHANGED
|
@@ -136,7 +136,6 @@ export const DEFAULT_UI = {
|
|
|
136
136
|
// quota/meter footer (falls back to belowEditor when no footer sink exists).
|
|
137
137
|
widgetPlacement: "bottom" as const,
|
|
138
138
|
widgetMaxLines: 8,
|
|
139
|
-
widgetRowStyle: "compact" as const,
|
|
140
139
|
inlinePanel: true,
|
|
141
140
|
powerbar: true,
|
|
142
141
|
dashboardPlacement: "center" as const,
|
package/src/config/env-vars.ts
CHANGED
|
@@ -237,6 +237,25 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
237
237
|
parser: "boolean",
|
|
238
238
|
doc: "'1'/'true' allows PI_TEAMS_MOCK_CHILD_PI mock mode (mock-fixtures.ts:40)",
|
|
239
239
|
},
|
|
240
|
+
PI_CREW_TEST_ASYNC_INLINE: {
|
|
241
|
+
name: "PI_CREW_TEST_ASYNC_INLINE",
|
|
242
|
+
parser: "boolean",
|
|
243
|
+
doc: "'1' runs async team runs IN-PROCESS instead of spawning a detached background-runner (test seam; requires PI_CREW_ALLOW_MOCK=1 — see async-runner.ts spawnBackgroundTeamRun)",
|
|
244
|
+
},
|
|
245
|
+
PI_CREW_BACKGROUND_RUNNER_ENTRY: {
|
|
246
|
+
name: "PI_CREW_BACKGROUND_RUNNER_ENTRY",
|
|
247
|
+
parser: "boolean",
|
|
248
|
+
doc: "'1' marks this process as a directly-invoked background-runner entry (set by async-runner spawn; module import without it skips main() — background-runner.ts entry guard)",
|
|
249
|
+
},
|
|
250
|
+
PI_CREW_PROMPT_BREAKDOWN: {
|
|
251
|
+
name: "PI_CREW_PROMPT_BREAKDOWN",
|
|
252
|
+
parser: "boolean",
|
|
253
|
+
doc: "'1' writes a per-section prompt token breakdown artifact metadata/<task>.prompt-breakdown.json (SR-02 phase 1, prompt-builder.ts)",
|
|
254
|
+
},
|
|
255
|
+
PI_CREW_PROMPT_SKILLS: {
|
|
256
|
+
name: "PI_CREW_PROMPT_SKILLS",
|
|
257
|
+
doc: "'full' inlines complete skill bodies in worker prompts (SR-02 escape hatch); default injects compact index entries (name+description+path, read-on-demand)",
|
|
258
|
+
},
|
|
240
259
|
PI_CREW_ASYNC_EARLY_EXIT_GUARD: {
|
|
241
260
|
name: "PI_CREW_ASYNC_EARLY_EXIT_GUARD",
|
|
242
261
|
doc: "'0' skips the async-run early-exit guard (team-tool/run.ts:103)",
|
|
@@ -257,6 +276,10 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
257
276
|
name: "PI_CREW_DEBUG_BUDGET",
|
|
258
277
|
doc: "'1' logs token budget (team-tool/run.ts:498)",
|
|
259
278
|
},
|
|
279
|
+
PI_CREW_DEBUG_STALE: {
|
|
280
|
+
name: "PI_CREW_DEBUG_STALE",
|
|
281
|
+
doc: "'1' writes a forensic sidecar log of every STALE verdict from the stale reconciler (stale-reconciler.ts:287) — the reconciler may run in ANY host process, hence a fixed env gate",
|
|
282
|
+
},
|
|
260
283
|
PI_CREW_DWF_SCRIPT_TIMEOUT_MS: {
|
|
261
284
|
name: "PI_CREW_DWF_SCRIPT_TIMEOUT_MS",
|
|
262
285
|
parser: "int",
|
|
@@ -415,6 +438,18 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
415
438
|
name: "PI_CREW_AUTO_EXIT",
|
|
416
439
|
doc: "'1' → the worker shuts its session down after the final settled turn — spec §5.2 D7 (written by prepareSurfaceSpawn, read by surface-worker.ts)",
|
|
417
440
|
},
|
|
441
|
+
PI_CREW_AUTO_PRUNE_KEEP: {
|
|
442
|
+
name: "PI_CREW_AUTO_PRUNE_KEEP",
|
|
443
|
+
parser: "int",
|
|
444
|
+
default: "10",
|
|
445
|
+
doc: "DP-01: number of most-recent finished runs the session-start auto-prune retains (was hard-coded 10). Invalid/negative → 10 with a warning (run-maintenance.ts:resolveAutoPruneKeep)",
|
|
446
|
+
},
|
|
447
|
+
PI_CREW_AUTO_PRUNE_AGE_FLOOR_HOURS: {
|
|
448
|
+
name: "PI_CREW_AUTO_PRUNE_AGE_FLOOR_HOURS",
|
|
449
|
+
parser: "int",
|
|
450
|
+
default: "24",
|
|
451
|
+
doc: "DP-01: auto-prune never deletes a finished run younger than this many hours, even beyond top-keep — evidence/incident runs survive a session restart (0 disables; run-maintenance.ts:resolveAutoPruneAgeFloorMs)",
|
|
452
|
+
},
|
|
418
453
|
PI_CREW_SURFACE: {
|
|
419
454
|
name: "PI_CREW_SURFACE",
|
|
420
455
|
doc: "surface provider kind for this worker ('tmux'|'herdr') — arms the worker-side recorder/parent-guard (written by prepareSurfaceSpawn.ts:214, read by surface-worker.ts)",
|
package/src/config/types.ts
CHANGED
|
@@ -167,11 +167,6 @@ export interface CrewUiConfig {
|
|
|
167
167
|
*/
|
|
168
168
|
widgetPlacement?: "aboveEditor" | "belowEditor" | "bottom";
|
|
169
169
|
widgetMaxLines?: number;
|
|
170
|
-
/**
|
|
171
|
-
* Per-agent row layout in the widget. `compact` is one width-budgeted line
|
|
172
|
-
* per agent; `detailed` keeps the two-line tree (name row + activity row).
|
|
173
|
-
*/
|
|
174
|
-
widgetRowStyle?: "compact" | "detailed";
|
|
175
170
|
/**
|
|
176
171
|
* Keyboard-navigable agent rows under the prompt (`↓` from an empty prompt).
|
|
177
172
|
* Requires owning pi's editor component, so it yields to any other extension
|
|
@@ -224,6 +219,23 @@ export interface CrewPolicyConfig {
|
|
|
224
219
|
|
|
225
220
|
export type CrewNotificationSeverity = "info" | "warning" | "error" | "critical";
|
|
226
221
|
|
|
222
|
+
/**
|
|
223
|
+
* US-030: opt-in outbound webhook on run terminal transitions.
|
|
224
|
+
* SENSITIVE (user config only — the schema marks this block `sensitive`):
|
|
225
|
+
* a project-level webhook URL would let an untrusted repo exfiltrate run
|
|
226
|
+
* metadata (incl. the goal first line) to an attacker-controlled endpoint.
|
|
227
|
+
*/
|
|
228
|
+
export interface CrewWebhookConfig {
|
|
229
|
+
/** Target URL. Must be http(s); localhost/loopback/link-local refused unless `allowLocalhost`. */
|
|
230
|
+
url: string;
|
|
231
|
+
/** Master switch. Absent + url set = enabled; `false` disables (zero network). */
|
|
232
|
+
enabled?: boolean;
|
|
233
|
+
/** Shared secret → `x-pi-crew-signature: sha256=<hmac-sha256(body, secret)>` header. */
|
|
234
|
+
secret?: string;
|
|
235
|
+
/** Explicit SSRF-guard bypass for localhost/127.0.0.0/8/[::1]/169.254.0.0/16 targets. */
|
|
236
|
+
allowLocalhost?: boolean;
|
|
237
|
+
}
|
|
238
|
+
|
|
227
239
|
export interface CrewNotificationsConfig {
|
|
228
240
|
enabled?: boolean;
|
|
229
241
|
severityFilter?: CrewNotificationSeverity[];
|
|
@@ -231,6 +243,8 @@ export interface CrewNotificationsConfig {
|
|
|
231
243
|
batchWindowMs?: number;
|
|
232
244
|
quietHours?: string;
|
|
233
245
|
sinkRetentionDays?: number;
|
|
246
|
+
/** US-030: outbound webhook on run terminal transitions. Opt-in only — no URL configured means disabled (zero network calls). */
|
|
247
|
+
webhook?: CrewWebhookConfig;
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
export interface CrewObservabilityConfig {
|
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// pi-crew structured error module — taxonomy mapping E001–
|
|
1
|
+
// pi-crew structured error module — taxonomy mapping E001–E013.
|
|
2
2
|
/**
|
|
3
3
|
* @fileoverview Error types and structured error handling for pi-crew.
|
|
4
4
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* matching fallow's E001-E004 pattern. It exports three main constructs:
|
|
7
7
|
*
|
|
8
8
|
* - {@link ErrorCode} — a `const` object and string-literal union type alias
|
|
9
|
-
* enumerating machine-readable error codes (E001–
|
|
9
|
+
* enumerating machine-readable error codes (E001–E013). Implemented as a
|
|
10
10
|
* `const` object rather than a TypeScript `enum` so that Node's
|
|
11
11
|
* `--experimental-strip-types` can load this module (enum syntax is not
|
|
12
12
|
* supported in strip-only mode).
|
|
@@ -8,6 +8,7 @@ import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
|
8
8
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
9
9
|
import { extractSessionId } from "../utils/session-utils.ts";
|
|
10
10
|
import { listRuns } from "./run-index.ts";
|
|
11
|
+
import type { WebhookNotifier } from "./webhook-notify.ts";
|
|
11
12
|
|
|
12
13
|
export interface AsyncNotifierState {
|
|
13
14
|
seenFinishedRunIds: Set<string>;
|
|
@@ -20,6 +21,13 @@ export interface AsyncNotifierState {
|
|
|
20
21
|
export interface AsyncNotifierOptions {
|
|
21
22
|
generation?: number;
|
|
22
23
|
isCurrent?: (generation: number) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* US-030 (docs/specs/US-030.md): outbound webhook sink, invoked ONCE per
|
|
26
|
+
* observed terminal transition — the same point (and dedupe memory) as the
|
|
27
|
+
* completion toast below. The sink handles quiet-hours, SSRF, HMAC and
|
|
28
|
+
* retry internally and NEVER throws. Optional — absent = disabled.
|
|
29
|
+
*/
|
|
30
|
+
webhookNotifier?: WebhookNotifier;
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
function isFinished(status: string): boolean {
|
|
@@ -177,6 +185,21 @@ export function startAsyncRunNotifier(
|
|
|
177
185
|
// alarming 'Error: pi-crew run failed' toast for an internal sub-run
|
|
178
186
|
// the user never started directly.
|
|
179
187
|
if (current.workflow === "goal-turn" && current.team.startsWith("goal-")) continue;
|
|
188
|
+
// US-030: outbound webhook on the terminal transition. Only the three
|
|
189
|
+
// spec statuses (completed/failed/cancelled) — "blocked" runs do not
|
|
190
|
+
// notify. Fire-and-forget: the sink is contractually non-throwing, the
|
|
191
|
+
// try/catch is belt-only so a webhook failure can NEVER suppress the
|
|
192
|
+
// local toast (or reach the run lifecycle path).
|
|
193
|
+
if (
|
|
194
|
+
options.webhookNotifier &&
|
|
195
|
+
(current.status === "completed" || current.status === "failed" || current.status === "cancelled")
|
|
196
|
+
) {
|
|
197
|
+
try {
|
|
198
|
+
options.webhookNotifier.notifyTerminalRun(current);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logInternalError("async-notifier.webhook", error, current.runId);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
180
203
|
const level = current.status === "completed" ? "info" : current.status === "cancelled" ? "warning" : "error";
|
|
181
204
|
ctx.ui.notify(`pi-crew run ${current.status}: ${current.runId} (${current.team}/${current.workflow ?? "none"})`, level);
|
|
182
205
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { getCrewEnv } from "../../config/env-vars.ts";
|
|
4
|
-
import { hasCrewFontFile, isWebTerminal } from "./font-detect.ts";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Self-contained config for the crew-vibes module (provider quota).
|
|
@@ -52,26 +51,6 @@ export const DEFAULT_CONFIG: CrewVibesConfig = {
|
|
|
52
51
|
},
|
|
53
52
|
};
|
|
54
53
|
|
|
55
|
-
// Fallback capacity icons using standard Unicode characters that render
|
|
56
|
-
// on any terminal without the crew-vibes PUA font.
|
|
57
|
-
const FALLBACK_CAPACITY_ICONS: [string, string, string, string, string, string] = [
|
|
58
|
-
"\u25CB ", // ○ empty circle (lean)
|
|
59
|
-
"\u25D4 ", // ◔ circle with dot (chonking)
|
|
60
|
-
"\u25D1 ", // ◑ circle half filled (chonky)
|
|
61
|
-
"\u25CF ", // ● filled circle (big chonk)
|
|
62
|
-
"\u2B24 ", // ⬤ large filled circle (mega chonk)
|
|
63
|
-
"\u2B22 ", // ⬢ filled hexagon (oh lawd)
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
/** Return capacity icons: standard Unicode glyphs that render on any terminal.
|
|
67
|
-
* PUA glyphs (U+E710..U+E715) require crew-vibes.ttf AND terminal PUA
|
|
68
|
-
* support — many terminals cannot render them even with the font installed. */
|
|
69
|
-
export function capacityIcons(): [string, string, string, string, string, string] {
|
|
70
|
-
// Web terminals cannot render PUA glyphs — use fallback.
|
|
71
|
-
if (isWebTerminal()) return FALLBACK_CAPACITY_ICONS;
|
|
72
|
-
return hasCrewFontFile() ? DEFAULT_CONFIG.capacity.icons : FALLBACK_CAPACITY_ICONS;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
54
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
76
55
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
|
|
77
56
|
}
|
|
@@ -19,8 +19,6 @@ import { type CrewVibesConfig, loadConfig, saveConfig } from "./config.ts";
|
|
|
19
19
|
import { clearProviderUsageCache, fetchProviderUsage, type ProviderUsage } from "./provider-usage.ts";
|
|
20
20
|
import { asCrewTheme, clearVibesStatus, renderProviderUsage, setProviderStatus } from "./render.ts";
|
|
21
21
|
|
|
22
|
-
export const CREW_VIBES_STATUS_KEY = "pi-crew-vibes";
|
|
23
|
-
|
|
24
22
|
/**
|
|
25
23
|
* crew-vibes — provider rate-limit quota publisher.
|
|
26
24
|
*
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { CrewTheme } from "../../ui/theme-adapter.ts";
|
|
3
|
-
import { type
|
|
4
|
-
import { capacityIndex, isDangerStage } from "./figures.ts";
|
|
5
|
-
|
|
6
|
-
export type CapacityUsage = {
|
|
7
|
-
tokens: number | null;
|
|
8
|
-
percent: number | null;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function formatCount(value: number): string {
|
|
12
|
-
if (value < 1000) return value.toString();
|
|
13
|
-
if (value < 10_000) return `${(value / 1000).toFixed(1)}k`;
|
|
14
|
-
if (value < 1_000_000) return `${Math.round(value / 1000)}k`;
|
|
15
|
-
if (value < 10_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
16
|
-
return `${Math.round(value / 1_000_000)}M`;
|
|
17
|
-
}
|
|
3
|
+
import { type CrewVibesConfig, PROVIDER_STATUS_ID } from "./config.ts";
|
|
18
4
|
|
|
19
5
|
function asCrewTheme(theme: unknown): CrewTheme | undefined {
|
|
20
6
|
if (theme && typeof theme === "object" && typeof (theme as CrewTheme).fg === "function") {
|
|
@@ -23,41 +9,6 @@ function asCrewTheme(theme: unknown): CrewTheme | undefined {
|
|
|
23
9
|
return undefined;
|
|
24
10
|
}
|
|
25
11
|
|
|
26
|
-
export function getCapacityUsage(ctx: ExtensionContext): CapacityUsage {
|
|
27
|
-
const fn = (ctx as { getContextUsage?: () => { tokens?: number; percent?: number } | null }).getContextUsage;
|
|
28
|
-
const usage = typeof fn === "function" ? fn.call(ctx) : null;
|
|
29
|
-
return {
|
|
30
|
-
tokens: typeof usage?.tokens === "number" && Number.isFinite(usage.tokens) ? usage.tokens : null,
|
|
31
|
-
percent: typeof usage?.percent === "number" && Number.isFinite(usage.percent) ? usage.percent : null,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function formatCapacityPrefix(config: CapacityConfig, usage: CapacityUsage): string {
|
|
36
|
-
const display: TokenDisplay = config.tokenDisplay;
|
|
37
|
-
if (display === "off") return "";
|
|
38
|
-
if (display === "percentage") {
|
|
39
|
-
return `${usage.percent === null ? "?" : Math.round(Math.max(0, Math.min(999, usage.percent)))}% `;
|
|
40
|
-
}
|
|
41
|
-
return `${usage.tokens === null ? "?" : formatCount(usage.tokens)} `;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function colorStage(theme: CrewTheme | undefined, index: number, levels: number, text: string): string {
|
|
45
|
-
if (!theme || text.length === 0) return text;
|
|
46
|
-
return theme.fg(isDangerStage(index, levels) ? "error" : "success", text);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function renderCapacity(theme: CrewTheme | undefined, config: CapacityConfig, usage: CapacityUsage): string {
|
|
50
|
-
const icons = capacityIcons();
|
|
51
|
-
const levels = icons.length;
|
|
52
|
-
const index = capacityIndex(usage.percent, levels);
|
|
53
|
-
const icon = icons[index] ?? icons[0];
|
|
54
|
-
const label = config.labels[index] ?? config.labels[0];
|
|
55
|
-
const prefix = theme ? theme.fg("muted", formatCapacityPrefix(config, usage)) : formatCapacityPrefix(config, usage);
|
|
56
|
-
const coloredIcon = colorStage(theme, index, levels, icon);
|
|
57
|
-
const afterIcon = config.showLabel ? ` ${colorStage(theme, index, levels, label)}` : " ";
|
|
58
|
-
return `${prefix}${coloredIcon}${afterIcon}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
12
|
export function clearVibesStatus(ctx: ExtensionContext): void {
|
|
62
13
|
if (!ctx?.hasUI) return;
|
|
63
14
|
ctx.ui.setStatus(PROVIDER_STATUS_ID, undefined);
|
package/src/extension/help.ts
CHANGED
|
@@ -4,7 +4,6 @@ export function piTeamsHelp(): string {
|
|
|
4
4
|
"",
|
|
5
5
|
"Core:",
|
|
6
6
|
"- Agent can use the `team` tool autonomously; slash commands are manual controls.",
|
|
7
|
-
"- Tool action `recommend` suggests the best team/workflow for a goal.",
|
|
8
7
|
"- /teams — list teams, workflows, agents, recent runs",
|
|
9
8
|
"- /team-run [--team=name] [--workflow=name] [--async] [--worktree] <goal>",
|
|
10
9
|
"- /team-status <runId>",
|
|
@@ -12,38 +11,48 @@ export function piTeamsHelp(): string {
|
|
|
12
11
|
"- /team-resume <runId>",
|
|
13
12
|
"- /team-cancel <runId>",
|
|
14
13
|
"- /team-retry <runId> [taskId]",
|
|
14
|
+
"- /team-respond <runId> <taskId|--all> <message>",
|
|
15
|
+
"- /team-follow-up <runId> <taskId> <prompt>",
|
|
16
|
+
"- /team-goal — autonomous goal loop (sub-actions: start/status/pause/resume/stop/step/clear)",
|
|
17
|
+
"- /workflows — list static + dynamic workflows",
|
|
15
18
|
"",
|
|
16
19
|
"Inspection:",
|
|
17
20
|
"- /team-events <runId>",
|
|
18
21
|
"- /team-artifacts <runId>",
|
|
22
|
+
"- /team-result <runId> [taskId]",
|
|
23
|
+
"- /team-transcript <runId> [taskId]",
|
|
19
24
|
"- /team-worktrees <runId>",
|
|
20
|
-
"- /team-api <runId> <operation> [
|
|
25
|
+
"- /team-api <runId> <operation> [key=value]",
|
|
26
|
+
"- /team-metrics [filter]",
|
|
21
27
|
"- /team-dashboard",
|
|
22
|
-
"- /schedules [log <jobId-or-name>] — list scheduled jobs / tail latest run output",
|
|
23
28
|
"- /team-mascot",
|
|
24
|
-
"- /team-transcript <runId> [taskId]",
|
|
25
|
-
"- /team-result <runId> [taskId]",
|
|
26
|
-
"- /team-manager — interactive menu (alias: /team-cleanup-menu)",
|
|
27
29
|
"",
|
|
28
30
|
"Maintenance:",
|
|
29
|
-
"- /team-
|
|
31
|
+
"- /team-manager — interactive menu (alias: /team-cleanup-menu)",
|
|
30
32
|
"- /team-forget <runId> --confirm [--force]",
|
|
31
33
|
"- /team-prune --keep=20 --confirm",
|
|
34
|
+
"- /team-invalidate <runId>",
|
|
35
|
+
"",
|
|
36
|
+
"Skills:",
|
|
37
|
+
"- /skill-list [--json] — list builtin skill templates",
|
|
38
|
+
"- /skill-create <template-id> [--var key=value...] [--project]",
|
|
32
39
|
"",
|
|
33
40
|
"Portability:",
|
|
34
41
|
"- /team-export <runId>",
|
|
35
|
-
"- /team-import <path-to-run-export.json>
|
|
42
|
+
"- /team-import <path-to-run-export.json>",
|
|
36
43
|
"- /team-imports",
|
|
37
44
|
"",
|
|
38
|
-
"Diagnostics:",
|
|
39
|
-
"- /team-health",
|
|
45
|
+
"Diagnostics & config:",
|
|
40
46
|
"- /team-doctor",
|
|
47
|
+
"- /team-validate",
|
|
41
48
|
"- /team-init [--copy-builtins] [--overwrite]",
|
|
42
49
|
"- /team-config [key=value] [--unset=key.path] [--project]",
|
|
43
|
-
"- /team-
|
|
44
|
-
"- /team-
|
|
50
|
+
"- /team-settings [list|get <key>|set <key> <value>|unset <key>|path|scope]",
|
|
51
|
+
"- /team-autonomy [status|on|off|manual|suggested|assisted|aggressive]",
|
|
45
52
|
"- /team-help",
|
|
46
53
|
"",
|
|
54
|
+
"Non-team commands: /schedules [log <jobId-or-name>], /crew-view <runId> <taskId>, /crew-back, /team-vibes [on|off], /crew-brief [on|off|status]",
|
|
55
|
+
"",
|
|
47
56
|
"Goal loops (P0/P1 — autonomous goal loop):",
|
|
48
57
|
"- team action='goal' config.subAction='start' config.objective='...' config.evaluatorModel='...' [config.maxTurns=20] [budgetTotal=N]",
|
|
49
58
|
"- team action='goal' config.subAction='status' [config.goalId=<id>]",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import * as fs from "node:fs";
|
|
24
24
|
import * as path from "node:path";
|
|
25
25
|
import { sanitizeAgentSystemPrompt } from "../agents/discover-agents.ts";
|
|
26
|
+
import { getCrewEnv } from "../config/env-vars.ts";
|
|
26
27
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
27
28
|
import { projectCrewRoot } from "../utils/paths.ts";
|
|
28
29
|
import type { BeforeAgentStartEvent, ExtensionAPI } from "./pi-api.ts";
|
|
@@ -463,7 +464,7 @@ export function registerKnowledgeInjection(pi: ExtensionAPI): void {
|
|
|
463
464
|
// ARCH-2: never fire in child worker processes — knowledge reaches
|
|
464
465
|
// workers via prompt-builder's stablePrefix fragment; firing here too
|
|
465
466
|
// would double-inject.
|
|
466
|
-
if (
|
|
467
|
+
if (getCrewEnv("PI_CREW_KIND") === "subagent") return;
|
|
467
468
|
const options =
|
|
468
469
|
(
|
|
469
470
|
event as BeforeAgentStartEvent & {
|
|
@@ -175,22 +175,27 @@ function findResource(ctx: ManagementContext, resource: "agent" | "team" | "work
|
|
|
175
175
|
const sourceMatches = (item: { name: string; source: ResourceSource }) =>
|
|
176
176
|
(scope === "user" || scope === "project" ? item.source === scope : item.source !== "builtin") && item.name === normalized;
|
|
177
177
|
// Search in the correct scope array directly to avoid allAgents shadowing issue.
|
|
178
|
+
// Tier 9e (2026-09-21): the DEFAULT pool must include PROJECT resources —
|
|
179
|
+
// the error message already promises "mutable user/project scopes", but the
|
|
180
|
+
// pool was `[...builtin, ...user]` and `sourceMatches` then drops every
|
|
181
|
+
// builtin entry, degenerating the default to USER ONLY. A project resource
|
|
182
|
+
// was invisible unless the caller guessed scope:'project'.
|
|
178
183
|
if (resource === "agent") {
|
|
179
184
|
const discovery = discoverAgents(ctx.cwd);
|
|
180
185
|
const pool =
|
|
181
|
-
scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.
|
|
186
|
+
scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.user, ...discovery.project];
|
|
182
187
|
return pool.filter(sourceMatches);
|
|
183
188
|
}
|
|
184
189
|
if (resource === "team") {
|
|
185
190
|
const discovery = discoverTeams(ctx.cwd);
|
|
186
191
|
const pool =
|
|
187
|
-
scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.
|
|
192
|
+
scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.user, ...discovery.project];
|
|
188
193
|
return pool.filter(sourceMatches);
|
|
189
194
|
}
|
|
190
195
|
{
|
|
191
196
|
const discovery = discoverWorkflows(ctx.cwd);
|
|
192
197
|
const pool =
|
|
193
|
-
scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.
|
|
198
|
+
scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.user, ...discovery.project];
|
|
194
199
|
return pool.filter(sourceMatches);
|
|
195
200
|
}
|
|
196
201
|
}
|
|
@@ -23,12 +23,29 @@ function rotateOldFiles(dir: string, retentionDays: number, now = Date.now()): v
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* JSONL notification sink.
|
|
28
|
+
*
|
|
29
|
+
* RR-020 Fix 2: writes are gated on the crew root ALREADY existing — this sink
|
|
30
|
+
* is attached at session start (lifecycle.ts) and the router pushes `info`
|
|
31
|
+
* notices through it before any severity filter, which used to create an empty
|
|
32
|
+
* `<crewRoot>/state/notifications/` tree (and with it the crew root) on every
|
|
33
|
+
* project. A project that never ran a team now stays untouched; once the crew
|
|
34
|
+
* root exists the sink behaves exactly as before.
|
|
35
|
+
*/
|
|
26
36
|
export function createJsonlSink(crewRoot: string, retentionDays = 7): NotificationSink {
|
|
27
37
|
const dir = path.join(crewRoot, "state", "notifications");
|
|
28
38
|
let lastRotateDate = "";
|
|
29
39
|
return {
|
|
30
40
|
write(notification: NotificationDescriptor): void {
|
|
31
41
|
try {
|
|
42
|
+
// RR-020 Fix 2: never materialise the project crew root. The router
|
|
43
|
+
// calls this sink BEFORE its severity filter (notification-router.ts),
|
|
44
|
+
// so an `info` notice on session start used to mkdir `<crewRoot>/`
|
|
45
|
+
// (→ `<crewRoot>/state/notifications/`) for a project that never ran
|
|
46
|
+
// a team. Persist only when the crew root already exists — a project
|
|
47
|
+
// that has real crew state keeps every notification exactly as before.
|
|
48
|
+
if (!fs.existsSync(crewRoot)) return;
|
|
32
49
|
const timestamp = notification.timestamp ?? Date.now();
|
|
33
50
|
const date = new Date(timestamp).toISOString().slice(0, 10);
|
|
34
51
|
if (date !== lastRotateDate) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* • command-registration — registerTeamCommands
|
|
16
16
|
* • crash-recovery-cache — lazy importCrashRecovery
|
|
17
17
|
* • wire-cross-extension — RPC handle + global registry install
|
|
18
|
+
* • terminal-status-wiring — tab title + Ghostty progress (runEventBus)
|
|
18
19
|
*/
|
|
19
20
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
20
21
|
import { loadConfig } from "../config/config.ts";
|
|
@@ -26,7 +27,6 @@ import { installChildProcessAbortShield } from "../utils/child-process-shield.ts
|
|
|
26
27
|
import { resetTimings, time } from "../utils/timings.ts";
|
|
27
28
|
import { registerAutonomousPolicy } from "./autonomous-policy.ts";
|
|
28
29
|
import { registerContextStatusInjection } from "./context-status-injection.ts";
|
|
29
|
-
import { registerCleanupHandler } from "./crew-cleanup.ts";
|
|
30
30
|
import { registerCrewInputRouter } from "./crew-input-router.ts";
|
|
31
31
|
import { registerCrewShortcuts } from "./crew-shortcuts.ts";
|
|
32
32
|
import { registerCrewVibes } from "./crew-vibes/index.ts";
|
|
@@ -43,6 +43,7 @@ import { installCrewBrokerLifecycleController, installSessionLifecycleHandlers }
|
|
|
43
43
|
import { installRuntimeCleanup } from "./registration/runtime-cleanup.ts";
|
|
44
44
|
import { __test__subagentSpawnParams } from "./registration/subagent-helpers.ts";
|
|
45
45
|
import { installSubagentManager } from "./registration/subagent-manager-setup.ts";
|
|
46
|
+
import { installTerminalStatus } from "./registration/terminal-status-wiring.ts";
|
|
46
47
|
import { registerPiTools } from "./registration/tool-registration.ts";
|
|
47
48
|
import { installCrossExtensionWiring } from "./registration/wire-cross-extension.ts";
|
|
48
49
|
|
|
@@ -101,7 +102,11 @@ export async function registerPiTeams(pi: ExtensionAPI): Promise<void> {
|
|
|
101
102
|
// subagents or when the flag is off, it returns a no-op controller.
|
|
102
103
|
ctx.brokerController = installCrewBrokerLifecycleController(pi, ctx);
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
// M3-1 (UI-AUDIT P0-2/P1-1): wire the tab-title + Ghostty progress
|
|
106
|
+
// controller to runEventBus. Before this the controller was never
|
|
107
|
+
// constructed (dead since v0.8.3). Registers the dispose hook that the
|
|
108
|
+
// SIGTERM/SIGHUP handler calls (crew-cleanup.ts).
|
|
109
|
+
installTerminalStatus(pi, ctx);
|
|
105
110
|
registerCompactionGuard(pi, {
|
|
106
111
|
foregroundControllers: ctx.foregroundControllers,
|
|
107
112
|
foregroundTeamRunControllers: ctx.foregroundTeamRunControllers,
|
|
@@ -23,8 +23,34 @@ export function commandText(result: { content?: Array<{ type: string; text?: str
|
|
|
23
23
|
return result.content?.map((item) => item.text ?? "").join("\n") ?? "";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/** Hard cap for command-result notifications (spec W5: cap value stays 800). */
|
|
27
|
+
export const NOTIFY_TEXT_CAP = 800;
|
|
28
|
+
|
|
29
|
+
/** Explicit marker appended whenever a command-result notification is clipped. */
|
|
30
|
+
export const TRUNCATION_MARKER = "\n… [truncated]";
|
|
31
|
+
|
|
32
|
+
export interface NotifyCommandResultOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Pointer appended AFTER the truncation marker when clipping occurs (e.g.
|
|
35
|
+
* the on-disk log path so the full output stays reachable). Included
|
|
36
|
+
* INSIDE the cap — the body shrinks to make room. Ignored when the text
|
|
37
|
+
* fits without clipping.
|
|
38
|
+
*/
|
|
39
|
+
truncatedFooter?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function notifyCommandResult(
|
|
43
|
+
ctx: ExtensionCommandContext,
|
|
44
|
+
text: string,
|
|
45
|
+
options: NotifyCommandResultOptions = {},
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
if (text.length <= NOTIFY_TEXT_CAP) {
|
|
48
|
+
ctx.ui.notify(text, "info");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const tail = `${TRUNCATION_MARKER}${options.truncatedFooter ?? ""}`;
|
|
52
|
+
const bodyLength = Math.max(0, NOTIFY_TEXT_CAP - tail.length);
|
|
53
|
+
ctx.ui.notify(`${text.slice(0, bodyLength)}${tail}`, "info");
|
|
28
54
|
}
|
|
29
55
|
|
|
30
56
|
export function parseScalar(raw: string): unknown {
|