pi-crew 0.5.2 → 0.5.6
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 +183 -0
- package/README.md +17 -1
- package/docs/architecture.md +2 -0
- package/docs/bugs/cross-session-notification-leakage.md +82 -0
- package/docs/coding-agent-optimization.md +268 -0
- package/docs/deep-review-report.md +384 -0
- package/docs/distillation/cybersecurity-patterns.md +294 -0
- package/docs/migration-v0.4-v0.5.md +208 -0
- package/docs/optimization-plan.md +642 -0
- package/docs/pi-crew-v0.5.5-audit-fix-plan.md +133 -0
- package/docs/pi-mono-opportunities.md +969 -0
- package/docs/pi-mono-review.md +291 -0
- package/docs/skills/REFERENCE.md +144 -0
- package/package.json +12 -9
- package/skills/artifact-analysis-loop/SKILL.md +302 -0
- package/skills/async-worker-recovery/SKILL.md +19 -1
- package/skills/child-pi-spawning/SKILL.md +19 -6
- package/skills/context-artifact-hygiene/SKILL.md +19 -2
- package/skills/delegation-patterns/SKILL.md +68 -3
- package/skills/detection-pipeline-design/SKILL.md +285 -0
- package/skills/event-log-tracing/SKILL.md +20 -6
- package/skills/git-master/SKILL.md +20 -6
- package/skills/hunting-investigation-loop/SKILL.md +401 -0
- package/skills/incident-playbook-construction/SKILL.md +383 -0
- package/skills/live-agent-lifecycle/SKILL.md +20 -6
- package/skills/mailbox-interactive/SKILL.md +19 -6
- package/skills/model-routing-context/SKILL.md +19 -1
- package/skills/multi-perspective-review/SKILL.md +19 -4
- package/skills/observability-reliability/SKILL.md +19 -2
- package/skills/orchestration/SKILL.md +20 -2
- package/skills/ownership-session-security/SKILL.md +20 -2
- package/skills/pi-extension-lifecycle/SKILL.md +20 -2
- package/skills/post-mortem/SKILL.md +7 -2
- package/skills/read-only-explorer/SKILL.md +20 -6
- package/skills/requirements-to-task-packet/SKILL.md +23 -3
- package/skills/resource-discovery-config/SKILL.md +20 -2
- package/skills/runtime-state-reader/SKILL.md +20 -2
- package/skills/safe-bash/SKILL.md +21 -6
- package/skills/scrutinize/SKILL.md +20 -2
- package/skills/secure-agent-orchestration-review/SKILL.md +29 -2
- package/skills/security-review/SKILL.md +560 -0
- package/skills/state-mutation-locking/SKILL.md +22 -2
- package/skills/systematic-debugging/SKILL.md +8 -6
- package/skills/threat-hypothesis-framework/SKILL.md +175 -0
- package/skills/ui-render-performance/SKILL.md +20 -2
- package/skills/verification-before-done/SKILL.md +17 -2
- package/skills/widget-rendering/SKILL.md +21 -6
- package/skills/workspace-isolation/SKILL.md +20 -6
- package/skills/worktree-isolation/SKILL.md +20 -6
- package/src/agents/agent-config.ts +40 -1
- package/src/benchmark/benchmark-runner.ts +45 -0
- package/src/benchmark/feedback-loop.ts +5 -0
- package/src/config/config.ts +32 -5
- package/src/config/role-tools.ts +82 -0
- package/src/config/suggestions.ts +8 -0
- package/src/config/types.ts +4 -0
- package/src/extension/async-notifier.ts +10 -1
- package/src/extension/crew-cleanup.ts +114 -0
- package/src/extension/cross-extension-rpc.ts +1 -1
- package/src/extension/notification-router.ts +18 -0
- package/src/extension/register.ts +27 -19
- package/src/extension/registration/subagent-tools.ts +1 -1
- package/src/extension/team-tool/anchor.ts +201 -0
- package/src/extension/team-tool/api.ts +2 -1
- package/src/extension/team-tool/auto-summarize.ts +154 -0
- package/src/extension/team-tool/run.ts +42 -7
- package/src/extension/team-tool.ts +44 -2
- package/src/hooks/registry.ts +1 -3
- package/src/observability/event-bus.ts +69 -0
- package/src/observability/event-to-metric.ts +0 -2
- package/src/runtime/anchor-manager.ts +473 -0
- package/src/runtime/async-runner.ts +8 -4
- package/src/runtime/auto-summarize.ts +350 -0
- package/src/runtime/background-runner.ts +10 -3
- package/src/runtime/budget-tracker.ts +354 -0
- package/src/runtime/chain-runner.ts +507 -0
- package/src/runtime/child-pi.ts +123 -35
- package/src/runtime/crash-recovery.ts +5 -4
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +13 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +3 -2
- package/src/runtime/delivery-coordinator.ts +10 -3
- package/src/runtime/dynamic-script-runner.ts +482 -0
- package/src/runtime/foreground-control.ts +87 -17
- package/src/runtime/handoff-manager.ts +589 -0
- package/src/runtime/hidden-handoff.ts +424 -0
- package/src/runtime/live-agent-manager.ts +20 -4
- package/src/runtime/live-session-runtime.ts +39 -4
- package/src/runtime/manifest-cache.ts +2 -1
- package/src/runtime/model-resolver.ts +16 -4
- package/src/runtime/phase-tracker.ts +373 -0
- package/src/runtime/pi-args.ts +11 -1
- package/src/runtime/pi-json-output.ts +31 -0
- package/src/runtime/pipeline-runner.ts +514 -0
- package/src/runtime/progress-tracker.ts +124 -0
- package/src/runtime/retry-runner.ts +354 -0
- package/src/runtime/sandbox.ts +252 -0
- package/src/runtime/scheduler.ts +7 -2
- package/src/runtime/skill-effectiveness.ts +473 -0
- package/src/runtime/skill-instructions.ts +37 -3
- package/src/runtime/subagent-manager.ts +1 -1
- package/src/runtime/task-graph.ts +11 -1
- package/src/runtime/task-runner.ts +92 -18
- package/src/runtime/team-runner.ts +13 -12
- package/src/runtime/tool-progress.ts +10 -3
- package/src/runtime/verification-gates.ts +367 -0
- package/src/schema/team-tool-schema.ts +37 -0
- package/src/skills/discover-skills.ts +5 -0
- package/src/state/active-run-registry.ts +9 -2
- package/src/state/contracts.ts +9 -0
- package/src/state/crew-init.ts +3 -3
- package/src/state/decision-ledger.ts +98 -55
- package/src/state/event-log-rotation.ts +2 -2
- package/src/state/event-log.ts +144 -10
- package/src/state/hook-instinct-bridge.ts +5 -5
- package/src/state/mailbox.ts +10 -0
- package/src/state/run-cache.ts +18 -8
- package/src/state/state-store.ts +3 -1
- package/src/state/types.ts +4 -0
- package/src/tools/safe-bash-extension.ts +1 -0
- package/src/tools/safe-bash.ts +152 -20
- package/src/types/new-api-types.ts +34 -0
- package/src/ui/agent-management-overlay.ts +5 -1
- package/src/ui/crew-widget.ts +29 -15
- package/src/ui/overlays/mailbox-detail-overlay.ts +13 -2
- package/src/ui/powerbar-publisher.ts +101 -7
- package/src/ui/tool-render.ts +15 -15
- package/src/ui/transcript-cache.ts +13 -0
- package/src/utils/bm25-search.ts +16 -8
- package/src/utils/env-filter.ts +8 -5
- package/src/utils/redaction.ts +169 -15
- package/src/utils/session-utils.ts +52 -0
- package/src/utils/sse-parser.ts +10 -1
- package/src/worktree/cleanup.ts +6 -1
- package/src/worktree/worktree-manager.ts +32 -13
- package/workflows/chain.workflow.md +252 -0
- package/workflows/pipeline.workflow.md +27 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-summarize commands for team tool.
|
|
3
|
+
* Provides on/off/status commands for auto-summarization.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
7
|
+
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
8
|
+
import { result, type TeamContext } from "./context.ts";
|
|
9
|
+
import {
|
|
10
|
+
AutoSummarizeService,
|
|
11
|
+
createAutoSummarizeService,
|
|
12
|
+
DEFAULT_AUTO_SUMMARIZE_CONFIG,
|
|
13
|
+
} from "../../runtime/auto-summarize.ts";
|
|
14
|
+
|
|
15
|
+
// Global auto-summarize service instance for CLI usage
|
|
16
|
+
let globalAutoSummarize: AutoSummarizeService | null = null;
|
|
17
|
+
|
|
18
|
+
function getAutoSummarize(): AutoSummarizeService {
|
|
19
|
+
if (!globalAutoSummarize) {
|
|
20
|
+
globalAutoSummarize = createAutoSummarizeService();
|
|
21
|
+
}
|
|
22
|
+
return globalAutoSummarize;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function handleAutoSummarizeOn(
|
|
26
|
+
params: TeamToolParamsValue,
|
|
27
|
+
ctx: TeamContext,
|
|
28
|
+
): PiTeamsToolResult {
|
|
29
|
+
const service = getAutoSummarize();
|
|
30
|
+
const cfg = params.config ?? {};
|
|
31
|
+
|
|
32
|
+
// Apply config updates if provided
|
|
33
|
+
if (cfg.threshold !== undefined) {
|
|
34
|
+
const threshold = typeof cfg.threshold === "number" ? cfg.threshold : parseInt(String(cfg.threshold), 10);
|
|
35
|
+
if (!isNaN(threshold) && threshold >= 0) {
|
|
36
|
+
service.setThreshold(threshold);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (cfg.minTools !== undefined) {
|
|
41
|
+
const minTools = typeof cfg.minTools === "number" ? cfg.minTools : parseInt(String(cfg.minTools), 10);
|
|
42
|
+
if (!isNaN(minTools) && minTools >= 0) {
|
|
43
|
+
service.setMinToolsUsed(minTools);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const previousState = service.isEnabled();
|
|
48
|
+
service.enable();
|
|
49
|
+
const config = service.getConfig();
|
|
50
|
+
|
|
51
|
+
return result(
|
|
52
|
+
[
|
|
53
|
+
`Auto-summarize enabled.`,
|
|
54
|
+
``,
|
|
55
|
+
`Configuration:`,
|
|
56
|
+
` Token threshold: ${config.threshold}`,
|
|
57
|
+
` Min tools: ${config.minToolsUsed}`,
|
|
58
|
+
` Collapse context: ${config.collapseContext}`,
|
|
59
|
+
].join("\n"),
|
|
60
|
+
{ action: "auto-summarize", status: "ok" },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function handleAutoSummarizeOff(
|
|
65
|
+
params: TeamToolParamsValue,
|
|
66
|
+
ctx: TeamContext,
|
|
67
|
+
): PiTeamsToolResult {
|
|
68
|
+
const service = getAutoSummarize();
|
|
69
|
+
|
|
70
|
+
service.disable();
|
|
71
|
+
|
|
72
|
+
return result(
|
|
73
|
+
"Auto-summarize disabled.",
|
|
74
|
+
{ action: "auto-summarize", status: "ok" },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function handleAutoSummarizeStatus(
|
|
79
|
+
params: TeamToolParamsValue,
|
|
80
|
+
ctx: TeamContext,
|
|
81
|
+
): PiTeamsToolResult {
|
|
82
|
+
const service = getAutoSummarize();
|
|
83
|
+
const config = service.getConfig();
|
|
84
|
+
const isEnabled = service.isEnabled();
|
|
85
|
+
|
|
86
|
+
return result(
|
|
87
|
+
[
|
|
88
|
+
`Auto-summarize Status`,
|
|
89
|
+
`──────────────────`,
|
|
90
|
+
`Enabled: ${isEnabled ? "Yes" : "No"}`,
|
|
91
|
+
``,
|
|
92
|
+
`Configuration:`,
|
|
93
|
+
` Token threshold: ${config.threshold} (default: ${DEFAULT_AUTO_SUMMARIZE_CONFIG.threshold})`,
|
|
94
|
+
` Min tools used: ${config.minToolsUsed} (default: ${DEFAULT_AUTO_SUMMARIZE_CONFIG.minToolsUsed})`,
|
|
95
|
+
` Collapse context: ${config.collapseContext ? "Yes" : "No"} (default: ${DEFAULT_AUTO_SUMMARIZE_CONFIG.collapseContext ? "Yes" : "No"})`,
|
|
96
|
+
``,
|
|
97
|
+
`Triggers:`,
|
|
98
|
+
` - Token count >= ${config.threshold}`,
|
|
99
|
+
` - Tool count >= ${config.minToolsUsed}`,
|
|
100
|
+
` - High token-to-tool ratio (>1000 tokens/tool with 3+ tools)`,
|
|
101
|
+
].join("\n"),
|
|
102
|
+
{ action: "auto-summarize", status: "ok" },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function handleAutoSummarizeConfig(
|
|
107
|
+
params: TeamToolParamsValue,
|
|
108
|
+
ctx: TeamContext,
|
|
109
|
+
): PiTeamsToolResult {
|
|
110
|
+
const service = getAutoSummarize();
|
|
111
|
+
const cfg = params.config ?? {};
|
|
112
|
+
|
|
113
|
+
// Parse config options
|
|
114
|
+
const updates: { threshold?: number; minTools?: number; collapseContext?: boolean } = {};
|
|
115
|
+
|
|
116
|
+
if (cfg.threshold !== undefined) {
|
|
117
|
+
const threshold = typeof cfg.threshold === "number" ? cfg.threshold : parseInt(String(cfg.threshold), 10);
|
|
118
|
+
if (!isNaN(threshold) && threshold >= 0) {
|
|
119
|
+
updates.threshold = threshold;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (cfg.minTools !== undefined) {
|
|
124
|
+
const minTools = typeof cfg.minTools === "number" ? cfg.minTools : parseInt(String(cfg.minTools), 10);
|
|
125
|
+
if (!isNaN(minTools) && minTools >= 0) {
|
|
126
|
+
updates.minTools = minTools;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (cfg.collapseContext !== undefined) {
|
|
131
|
+
updates.collapseContext = Boolean(cfg.collapseContext);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (Object.keys(updates).length > 0) {
|
|
135
|
+
service.updateConfig(updates);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const config = service.getConfig();
|
|
139
|
+
|
|
140
|
+
return result(
|
|
141
|
+
[
|
|
142
|
+
`Auto-summarize configuration updated.`,
|
|
143
|
+
``,
|
|
144
|
+
`Current settings:`,
|
|
145
|
+
` Token threshold: ${config.threshold}`,
|
|
146
|
+
` Min tools used: ${config.minToolsUsed}`,
|
|
147
|
+
` Collapse context: ${config.collapseContext ? "Yes" : "No"}`,
|
|
148
|
+
` Enabled: ${config.enabled ? "Yes" : "No"}`,
|
|
149
|
+
].join("\n"),
|
|
150
|
+
{ action: "auto-summarize", status: "ok" },
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
// Re-export for team-tool.ts
|
|
154
|
+
export { createAutoSummarizeService } from "../../runtime/auto-summarize.ts";
|
|
@@ -8,10 +8,12 @@ import { registerActiveRun, unregisterActiveRun } from "../../state/active-run-r
|
|
|
8
8
|
import { createRunManifest, loadRunManifestById, updateRunStatus } from "../../state/state-store.ts";
|
|
9
9
|
import { atomicWriteJson } from "../../state/atomic-write.ts";
|
|
10
10
|
import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
|
|
11
|
+
import { PipelineRunner, type PipelineWorkflow, type PipelineStage } from "../../runtime/pipeline-runner.ts";
|
|
11
12
|
// Heavy runtime — lazy-loaded to avoid 1.4s import cost at extension registration.
|
|
12
13
|
import type { executeTeamRun as ExecuteTeamRunFn } from "../../runtime/team-runner.ts";
|
|
13
14
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- type-only import for TS inference
|
|
14
15
|
const _typeCheck: typeof ExecuteTeamRunFn = null as never as typeof ExecuteTeamRunFn;
|
|
16
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
15
17
|
let _cachedExecuteTeamRun: typeof ExecuteTeamRunFn | undefined;
|
|
16
18
|
async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Promise<Awaited<ReturnType<typeof ExecuteTeamRunFn>>> {
|
|
17
19
|
if (!_cachedExecuteTeamRun) {
|
|
@@ -22,7 +24,7 @@ async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Pro
|
|
|
22
24
|
return _cachedExecuteTeamRun(...args);
|
|
23
25
|
}
|
|
24
26
|
import { spawnBackgroundTeamRun } from "../../subagents/async-entry.ts";
|
|
25
|
-
import { appendEvent, readEvents } from "../../state/event-log.ts";
|
|
27
|
+
import { appendEvent, appendEventAsync, readEvents } from "../../state/event-log.ts";
|
|
26
28
|
import { resolveCrewRuntime, runtimeResolutionState } from "../../runtime/runtime-resolver.ts";
|
|
27
29
|
import { normalizeSkillOverride } from "../../runtime/skill-instructions.ts";
|
|
28
30
|
import { expandParallelResearchWorkflow } from "../../runtime/parallel-research.ts";
|
|
@@ -67,7 +69,7 @@ function scheduleBackgroundEarlyExitGuard(cwd: string, runId: string, pid: numbe
|
|
|
67
69
|
const tail = tailFile(logPath);
|
|
68
70
|
const message = `Background runner exited within 3s; see background.log${tail ? `\n${tail}` : ""}`;
|
|
69
71
|
const failed = updateRunStatus(loaded.manifest, "failed", "Background runner exited within 3s; see background.log");
|
|
70
|
-
|
|
72
|
+
void appendEventAsync(failed.eventsPath, { type: "async.failed", runId: failed.runId, message, data: { pid, detail: liveness.detail } });
|
|
71
73
|
}, 3000);
|
|
72
74
|
timer.unref();
|
|
73
75
|
}
|
|
@@ -110,6 +112,39 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
110
112
|
if (!baseWorkflow) return result(`Workflow '${workflowName}' not found.`, { action: "run", status: "error" }, true);
|
|
111
113
|
const workflow = directAgent ? baseWorkflow : expandParallelResearchWorkflow(baseWorkflow, ctx.cwd);
|
|
112
114
|
|
|
115
|
+
// Check if this is a pipeline workflow - special handling for multi-stage execution
|
|
116
|
+
const isPipelineWorkflow = workflowName === "pipeline" && !directAgent;
|
|
117
|
+
if (isPipelineWorkflow) {
|
|
118
|
+
// For pipeline workflows, use PipelineRunner for execution
|
|
119
|
+
const pipelineRunner = new PipelineRunner();
|
|
120
|
+
const pipelineWorkflow: PipelineWorkflow = {
|
|
121
|
+
name: workflow.name,
|
|
122
|
+
description: workflow.description,
|
|
123
|
+
goal,
|
|
124
|
+
stages: workflow.steps.map((step) => ({
|
|
125
|
+
name: step.id,
|
|
126
|
+
team: step.role,
|
|
127
|
+
inputs: step.task,
|
|
128
|
+
usePreviousResults: step.dependsOn && step.dependsOn.length > 0,
|
|
129
|
+
})),
|
|
130
|
+
stopOnError: true,
|
|
131
|
+
defaultMaxConcurrency: workflow.maxConcurrency ?? 5,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// For now, show pipeline workflow info - full integration would require
|
|
135
|
+
// connecting PipelineRunner to the actual team execution system
|
|
136
|
+
const stageInfo = pipelineWorkflow.stages.map((s) => `- ${s.name} (${s.team})`).join("\n");
|
|
137
|
+
return result([
|
|
138
|
+
`Pipeline workflow: ${workflow.name}`,
|
|
139
|
+
`Goal: ${goal}`,
|
|
140
|
+
`Stages (${pipelineWorkflow.stages.length}):`,
|
|
141
|
+
stageInfo,
|
|
142
|
+
"",
|
|
143
|
+
"Pipeline execution is available via the PipelineRunner API.",
|
|
144
|
+
"Full CLI integration requires connecting to the team execution system.",
|
|
145
|
+
].join("\n"), { action: "run", status: "ok" }, false);
|
|
146
|
+
}
|
|
147
|
+
|
|
113
148
|
const validationErrors = validateWorkflowForTeam(workflow, team);
|
|
114
149
|
if (validationErrors.length > 0) {
|
|
115
150
|
return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...validationErrors.map((error) => `- ${error}`)].join("\n"), { action: "run", status: "error" }, true);
|
|
@@ -140,7 +175,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
140
175
|
const runtimeResolution = runtimeResolutionState(runtime);
|
|
141
176
|
const executionManifest = { ...updatedManifest, runtimeResolution, runConfig: executedConfig, updatedAt: new Date().toISOString() };
|
|
142
177
|
atomicWriteJson(paths.manifestPath, executionManifest);
|
|
143
|
-
|
|
178
|
+
appendEventAsync(executionManifest.eventsPath, { type: "runtime.resolved", runId: executionManifest.runId, message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution } }).catch((error) => logInternalError("team-tool.run.resolved", error, `runId=${executionManifest.runId}`));
|
|
144
179
|
const runAsync = params.async ?? executedConfig.asyncByDefault ?? false;
|
|
145
180
|
let effectiveRuntime = runtime;
|
|
146
181
|
if (runAsync && runtime.kind === "live-session") {
|
|
@@ -150,13 +185,13 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
150
185
|
const effectiveManifest = effectiveRuntime !== runtime ? { ...executionManifest, runtimeResolution: effectiveRuntimeResolution, updatedAt: new Date().toISOString() } : executionManifest;
|
|
151
186
|
if (effectiveRuntime !== runtime) {
|
|
152
187
|
atomicWriteJson(paths.manifestPath, effectiveManifest);
|
|
153
|
-
|
|
188
|
+
appendEventAsync(effectiveManifest.eventsPath, { type: "runtime.resolved", runId: effectiveManifest.runId, message: `Runtime overridden: child-process (async fallback from live-session)`, data: { runtimeResolution: effectiveRuntimeResolution } }).catch((error) => logInternalError("team-tool.run.override", error, `runId=${effectiveManifest.runId}`));
|
|
154
189
|
}
|
|
155
190
|
if (runAsync) {
|
|
156
191
|
if (effectiveRuntime.safety === "blocked") {
|
|
157
192
|
const runningManifest = updateRunStatus(effectiveManifest, "running", "Checking worker runtime availability.");
|
|
158
193
|
const blocked = updateRunStatus(runningManifest, "blocked", effectiveRuntime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
159
|
-
|
|
194
|
+
void appendEventAsync(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime: effectiveRuntime, runtimeResolution: effectiveRuntimeResolution, async: true, diagnostics: { requestedMode: effectiveRuntime.requestedMode, workersDisabled: executedConfig.executeWorkers === false, envCrew: process.env.PI_CREW_EXECUTE_WORKERS, envTeams: process.env.PI_TEAMS_EXECUTE_WORKERS } } });
|
|
160
195
|
unregisterActiveRun(blocked.runId);
|
|
161
196
|
return result([
|
|
162
197
|
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
@@ -169,7 +204,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
169
204
|
const spawned = await spawnBackgroundTeamRun(effectiveManifest);
|
|
170
205
|
const asyncManifest = { ...effectiveManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
|
|
171
206
|
atomicWriteJson(paths.manifestPath, asyncManifest);
|
|
172
|
-
|
|
207
|
+
void appendEventAsync(effectiveManifest.eventsPath, { type: "async.spawned", runId: effectiveManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
|
|
173
208
|
scheduleBackgroundEarlyExitGuard(ctx.cwd, effectiveManifest.runId, spawned.pid, spawned.logPath);
|
|
174
209
|
// Wait for the async run to complete and return actual results.
|
|
175
210
|
try {
|
|
@@ -280,7 +315,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
280
315
|
if (runtime.safety === "blocked") {
|
|
281
316
|
const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
|
|
282
317
|
const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
283
|
-
|
|
318
|
+
void appendEventAsync(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution, diagnostics: { requestedMode: runtime.requestedMode, workersDisabled: executedConfig.executeWorkers === false, envCrew: process.env.PI_CREW_EXECUTE_WORKERS, envTeams: process.env.PI_TEAMS_EXECUTE_WORKERS } } });
|
|
284
319
|
unregisterActiveRun(blocked.runId);
|
|
285
320
|
return result([
|
|
286
321
|
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
@@ -170,6 +170,8 @@ import { handlePlan } from "./team-tool/plan.ts";
|
|
|
170
170
|
import { handleOrchestrate } from "./team-tool/orchestrate.ts";
|
|
171
171
|
import { handleRespond } from "./team-tool/respond.ts";
|
|
172
172
|
import { handleStatus } from "./team-tool/status.ts";
|
|
173
|
+
import { handleAnchorSet, handleAnchorClear, handleAnchorStatus, handleAnchorAccumulate } from "./team-tool/anchor.ts";
|
|
174
|
+
import { handleAutoSummarizeOn, handleAutoSummarizeOff, handleAutoSummarizeStatus, handleAutoSummarizeConfig, createAutoSummarizeService } from "./team-tool/auto-summarize.ts";
|
|
173
175
|
|
|
174
176
|
export { handleApi } from "./team-tool/api.ts";
|
|
175
177
|
export { handleRetry } from "./team-tool/cancel.ts";
|
|
@@ -715,7 +717,12 @@ export function handleSteer(
|
|
|
715
717
|
true,
|
|
716
718
|
);
|
|
717
719
|
if (!task.pendingSteers) task.pendingSteers = [];
|
|
718
|
-
|
|
720
|
+
// HIGH-04: Cap pendingSteers array to prevent unbounded memory growth
|
|
721
|
+
const MAX_PENDING_STEERS = 100;
|
|
722
|
+
if (task.pendingSteers.length >= MAX_PENDING_STEERS) {
|
|
723
|
+
task.pendingSteers = task.pendingSteers.slice(-(MAX_PENDING_STEERS - 1));
|
|
724
|
+
}
|
|
725
|
+
task.pendingSteers.push(message);
|
|
719
726
|
saveRunTasks(loaded.manifest, loaded.tasks);
|
|
720
727
|
appendEvent(loaded.manifest.eventsPath, {
|
|
721
728
|
type: "task.steer_queued",
|
|
@@ -871,7 +878,7 @@ export async function handleTeamTool(
|
|
|
871
878
|
ctx: TeamContext,
|
|
872
879
|
): Promise<PiTeamsToolResult> {
|
|
873
880
|
const action = params.action ?? "list";
|
|
874
|
-
switch (action) {
|
|
881
|
+
switch (action as string) {
|
|
875
882
|
case "list":
|
|
876
883
|
return handleList(params, ctx);
|
|
877
884
|
case "get":
|
|
@@ -1157,6 +1164,41 @@ export async function handleTeamTool(
|
|
|
1157
1164
|
return handleSchedule(params, ctx);
|
|
1158
1165
|
case "scheduled":
|
|
1159
1166
|
return handleListScheduled(params, ctx);
|
|
1167
|
+
case "anchor": {
|
|
1168
|
+
const subAction = typeof params.config?.subAction === "string" ? params.config.subAction : "status";
|
|
1169
|
+
switch (subAction) {
|
|
1170
|
+
case "set":
|
|
1171
|
+
return handleAnchorSet(params, ctx);
|
|
1172
|
+
case "clear":
|
|
1173
|
+
return handleAnchorClear(params, ctx);
|
|
1174
|
+
case "accumulate":
|
|
1175
|
+
return handleAnchorAccumulate(params, ctx);
|
|
1176
|
+
default:
|
|
1177
|
+
return handleAnchorStatus(params, ctx);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
case "auto-summarize":
|
|
1181
|
+
case "auto_boomerang": {
|
|
1182
|
+
const subAction = typeof params.config?.subAction === "string" ? params.config.subAction : ((params.action as string) === "auto_boomerang" ? "toggle" : "status");
|
|
1183
|
+
switch (subAction) {
|
|
1184
|
+
case "on":
|
|
1185
|
+
return handleAutoSummarizeOn(params, ctx);
|
|
1186
|
+
case "off":
|
|
1187
|
+
return handleAutoSummarizeOff(params, ctx);
|
|
1188
|
+
case "config":
|
|
1189
|
+
return handleAutoSummarizeConfig(params, ctx);
|
|
1190
|
+
case "toggle": {
|
|
1191
|
+
const service = createAutoSummarizeService();
|
|
1192
|
+
service.toggle();
|
|
1193
|
+
return result(
|
|
1194
|
+
`Auto-summarize ${service.isEnabled() ? "enabled" : "disabled"}.`,
|
|
1195
|
+
{ action: "auto-summarize", status: "ok" },
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
default:
|
|
1199
|
+
return handleAutoSummarizeStatus(params, ctx);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1160
1202
|
case "onboard": {
|
|
1161
1203
|
const team = params.team ?? "default";
|
|
1162
1204
|
const onboarding = buildTeamOnboarding(team, ctx.cwd);
|
package/src/hooks/registry.ts
CHANGED
|
@@ -30,9 +30,7 @@ export async function executeHook(name: HookName, ctx: HookContext): Promise<Hoo
|
|
|
30
30
|
// SECURITY: If ctx contains a workspaceId, filter hooks to only those scoped to
|
|
31
31
|
// this workspace. This prevents globally-registered hooks from operating on runs
|
|
32
32
|
// they weren't designed for.
|
|
33
|
-
const scopedHooks = ctx.workspaceId
|
|
34
|
-
? hooks.filter((h) => !h.workspaceId || h.workspaceId === ctx.workspaceId)
|
|
35
|
-
: hooks;
|
|
33
|
+
const scopedHooks = hooks.filter((h) => !h.workspaceId || h.workspaceId === ctx.workspaceId);
|
|
36
34
|
if (scopedHooks.length === 0) return { hookName: name, outcome: "allow", durationMs: 0 };
|
|
37
35
|
const start = Date.now();
|
|
38
36
|
const diagnostics: string[] = [];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { AgentProgress } from "../runtime/progress-tracker.ts";
|
|
2
|
+
|
|
3
|
+
export type CrewEventType =
|
|
4
|
+
| "agent:progress"
|
|
5
|
+
| "agent:complete"
|
|
6
|
+
| "agent:error"
|
|
7
|
+
| "run:start"
|
|
8
|
+
| "run:complete";
|
|
9
|
+
|
|
10
|
+
export interface CrewEvent {
|
|
11
|
+
type: CrewEventType;
|
|
12
|
+
runId: string;
|
|
13
|
+
agentId?: string;
|
|
14
|
+
payload?: AgentProgress | string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CrewEventListener = (event: CrewEvent) => void;
|
|
19
|
+
|
|
20
|
+
class EventBus {
|
|
21
|
+
private listeners = new Map<CrewEventType, Set<CrewEventListener>>();
|
|
22
|
+
private static _instance?: EventBus;
|
|
23
|
+
|
|
24
|
+
static getInstance(): EventBus {
|
|
25
|
+
if (!EventBus._instance) {
|
|
26
|
+
EventBus._instance = new EventBus();
|
|
27
|
+
}
|
|
28
|
+
return EventBus._instance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Dispose of the EventBus instance and clear all listeners.
|
|
33
|
+
* Resets the singleton so a new instance can be created.
|
|
34
|
+
*/
|
|
35
|
+
dispose(): void {
|
|
36
|
+
this.listeners.clear();
|
|
37
|
+
EventBus._instance = undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
emit(event: CrewEvent): void {
|
|
41
|
+
const listeners = this.listeners.get(event.type);
|
|
42
|
+
if (listeners) {
|
|
43
|
+
for (const listener of listeners) {
|
|
44
|
+
try {
|
|
45
|
+
listener(event);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error("[EventBus] Listener error:", e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
on(type: CrewEventType, listener: CrewEventListener): () => void {
|
|
54
|
+
if (!this.listeners.has(type)) {
|
|
55
|
+
this.listeners.set(type, new Set());
|
|
56
|
+
}
|
|
57
|
+
this.listeners.get(type)!.add(listener);
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
this.listeners.get(type)?.delete(listener);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
off(type: CrewEventType, listener: CrewEventListener): void {
|
|
65
|
+
this.listeners.get(type)?.delete(listener);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const crewEventBus = EventBus.getInstance();
|
|
@@ -32,7 +32,6 @@ export function wireEventToMetrics(events: ExtensionAPI["events"] | undefined, r
|
|
|
32
32
|
const retryAttemptCount = registry.counter("crew.task.retry_attempt_total", "Retry attempts by run and task");
|
|
33
33
|
const deadletterCount = registry.counter("crew.task.deadletter_total", "Deadletter triggers by reason");
|
|
34
34
|
const overflowCount = registry.counter("crew.task.overflow_phase_total", "Overflow recovery phase transitions");
|
|
35
|
-
const waitingCount = registry.counter("crew.task.waiting_total", "Tasks entering waiting state");
|
|
36
35
|
const supervisorContactCount = registry.counter("crew.task.supervisor_contact_total", "Supervisor contact requests by reason");
|
|
37
36
|
registry.gauge("crew.heartbeat.staleness_ms", "Heartbeat elapsed since last seen, milliseconds");
|
|
38
37
|
const runDuration = registry.histogram("crew.run.duration_ms", "Run end-to-end duration, milliseconds", [1000, 5000, 15000, 30000, 60000, 300000, 600000, 1800000]);
|
|
@@ -50,7 +49,6 @@ export function wireEventToMetrics(events: ExtensionAPI["events"] | undefined, r
|
|
|
50
49
|
["crew.task.retry_attempt", (data) => { const item = recordValue(data); taskCount.inc({ status: "retry" }); retryAttemptCount.inc({ runId: stringValue(item.runId, "unknown"), taskId: stringValue(item.taskId, "unknown") }); }],
|
|
51
50
|
["crew.task.deadletter", (data) => { const item = recordValue(data); deadletterCount.inc({ reason: stringValue(item.reason, "unknown") }); }],
|
|
52
51
|
["crew.task.overflow", (data) => { const item = recordValue(data); overflowCount.inc({ phase: stringValue(item.phase, "unknown"), previous_phase: stringValue(item.previousPhase, "none") }); }],
|
|
53
|
-
["task.waiting", (data) => { const item = recordValue(data); waitingCount.inc({ taskId: stringValue(item.taskId, "unknown"), runId: stringValue(item.runId, "unknown") }); }],
|
|
54
52
|
["supervisor.contact", (data) => { const item = recordValue(data); supervisorContactCount.inc({ reason: stringValue(item.reason, "unknown"), taskId: stringValue(item.taskId, "unknown") }); }],
|
|
55
53
|
["crew.subagent.completed", (data) => { const item = recordValue(data); subagentCount.inc({ status: stringValue(item.status, "completed") }); }],
|
|
56
54
|
["crew.subagent.failed", () => subagentCount.inc({ status: "failed" })],
|