pi-crew 0.9.68 → 0.10.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 +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import * as fs from "node:fs";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Information about a detected project.
|
|
8
|
-
*/
|
|
9
|
-
export interface ProjectInfo {
|
|
10
|
-
projectId: string;
|
|
11
|
-
projectName: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Create a SHA256 hash of a string, returning the first 16 characters.
|
|
16
|
-
*/
|
|
17
|
-
function hashPath(input: string): string {
|
|
18
|
-
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Extract the repository name from a git remote URL.
|
|
23
|
-
* Handles formats like:
|
|
24
|
-
* - https://github.com/user/repo.git
|
|
25
|
-
* - https://github.com/user/repo
|
|
26
|
-
* - git@github.com:user/repo.git
|
|
27
|
-
* - ssh://git@github.com/user/repo.git
|
|
28
|
-
*/
|
|
29
|
-
function extractRepoName(remoteUrl: string): string | null {
|
|
30
|
-
// Remove .git suffix
|
|
31
|
-
const withoutGit = remoteUrl.replace(/\.git$/, "");
|
|
32
|
-
|
|
33
|
-
// Handle SSH format: git@github.com:user/repo
|
|
34
|
-
const sshMatch = withoutGit.match(/^git@[^:]+:(.+)$/);
|
|
35
|
-
if (sshMatch) {
|
|
36
|
-
const parts = sshMatch[1].split("/");
|
|
37
|
-
return parts[parts.length - 1] ?? null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Handle HTTPS/HTTP/SSH URL format
|
|
41
|
-
try {
|
|
42
|
-
const url = new URL(withoutGit);
|
|
43
|
-
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
44
|
-
return pathParts[pathParts.length - 1] ?? null;
|
|
45
|
-
} catch {
|
|
46
|
-
// Not a valid URL, try to extract from path-like string
|
|
47
|
-
const parts = withoutGit.split("/").filter(Boolean);
|
|
48
|
-
return parts[parts.length - 1] ?? null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Try to get the repository name from git remote origin.
|
|
54
|
-
*/
|
|
55
|
-
function tryGitRemote(cwd: string): string | null {
|
|
56
|
-
try {
|
|
57
|
-
const remoteUrl = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
58
|
-
cwd,
|
|
59
|
-
encoding: "utf-8",
|
|
60
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
61
|
-
}).trim();
|
|
62
|
-
|
|
63
|
-
if (!remoteUrl) return null;
|
|
64
|
-
|
|
65
|
-
const repoName = extractRepoName(remoteUrl);
|
|
66
|
-
if (!repoName) return null;
|
|
67
|
-
|
|
68
|
-
// Verify it's a valid directory name (no path traversal attempts)
|
|
69
|
-
if (repoName.includes("..") || repoName.includes("/")) return null;
|
|
70
|
-
|
|
71
|
-
return repoName;
|
|
72
|
-
} catch {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Try to get the directory name from git toplevel.
|
|
79
|
-
*/
|
|
80
|
-
function tryGitToplevel(cwd: string): string | null {
|
|
81
|
-
try {
|
|
82
|
-
const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
83
|
-
cwd,
|
|
84
|
-
encoding: "utf-8",
|
|
85
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
86
|
-
}).trim();
|
|
87
|
-
|
|
88
|
-
if (!toplevel || !fs.existsSync(toplevel)) return null;
|
|
89
|
-
|
|
90
|
-
const dirName = path.basename(toplevel);
|
|
91
|
-
// Verify it's a valid directory name
|
|
92
|
-
if (!dirName || dirName.includes("..") || dirName.includes("/")) return null;
|
|
93
|
-
|
|
94
|
-
return dirName;
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Detect project information from a working directory.
|
|
102
|
-
* Uses a hierarchy of detection methods, falling through on failure.
|
|
103
|
-
*
|
|
104
|
-
* Detection hierarchy (first successful wins):
|
|
105
|
-
* 1. CLAUDE_PROJECT_DIR env var → hash the path, use dir name as name
|
|
106
|
-
* 2. git remote get-url origin → extract repo name from URL
|
|
107
|
-
* 3. git rev-parse --show-toplevel → use dir name
|
|
108
|
-
* 4. Fallback: hash(cwd) for projectId, path.basename(cwd) for name
|
|
109
|
-
*/
|
|
110
|
-
export function detectProjectId(cwd: string): ProjectInfo {
|
|
111
|
-
// Normalize the cwd path
|
|
112
|
-
const normalizedCwd = path.resolve(cwd);
|
|
113
|
-
|
|
114
|
-
// Method 1: Check CLAUDE_PROJECT_DIR env var
|
|
115
|
-
const claudeProjectDir = process.env.CLAUDE_PROJECT_DIR;
|
|
116
|
-
if (claudeProjectDir?.trim()) {
|
|
117
|
-
const resolvedPath = path.resolve(claudeProjectDir.trim());
|
|
118
|
-
const projectName = path.basename(resolvedPath);
|
|
119
|
-
const projectId = hashPath(resolvedPath);
|
|
120
|
-
return { projectId, projectName };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Method 2: Try git remote origin
|
|
124
|
-
const repoName = tryGitRemote(normalizedCwd);
|
|
125
|
-
if (repoName) {
|
|
126
|
-
const projectId = hashPath(normalizedCwd);
|
|
127
|
-
return { projectId, projectName: repoName };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Method 3: Try git toplevel
|
|
131
|
-
const toplevelName = tryGitToplevel(normalizedCwd);
|
|
132
|
-
if (toplevelName) {
|
|
133
|
-
const projectId = hashPath(normalizedCwd);
|
|
134
|
-
return { projectId, projectName: toplevelName };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Method 4: Fallback - hash the cwd and use basename
|
|
138
|
-
const projectId = hashPath(normalizedCwd);
|
|
139
|
-
const projectName = path.basename(normalizedCwd) || "unknown";
|
|
140
|
-
return { projectId, projectName };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Get the storage directory for a specific project's instincts.
|
|
145
|
-
* @param projectId - The project identifier (from detectProjectId)
|
|
146
|
-
* @param crewRoot - The crew root directory (e.g., from projectCrewRoot)
|
|
147
|
-
* @returns The path to the project's storage directory
|
|
148
|
-
*/
|
|
149
|
-
export function getProjectStorageDir(projectId: string, crewRoot: string): string {
|
|
150
|
-
return path.join(crewRoot, "instincts", "projects", projectId);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Get the global storage directory for instincts.
|
|
155
|
-
* @param crewRoot - The crew root directory
|
|
156
|
-
* @returns The path to the global storage directory
|
|
157
|
-
*/
|
|
158
|
-
export function getGlobalStorageDir(crewRoot: string): string {
|
|
159
|
-
return path.join(crewRoot, "instincts", "global");
|
|
160
|
-
}
|
package/src/utils/sse-parser.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/** A single Server-Sent Event */
|
|
2
|
-
export interface ServerSentEvent {
|
|
3
|
-
event: string | null;
|
|
4
|
-
data: string;
|
|
5
|
-
raw: string[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** L1: Maximum number of raw lines before discarding an oversized event. */
|
|
9
|
-
const MAX_EVENT_LINES = 1000;
|
|
10
|
-
|
|
11
|
-
/** L2: Maximum data size per line to prevent unbounded memory usage. */
|
|
12
|
-
const MAX_DATA_SIZE = 100000; // 100KB per line
|
|
13
|
-
|
|
14
|
-
/** Read newline-delimited lines from a text ReadableStream, buffering partial chunks. */
|
|
15
|
-
async function* readLines(stream: ReadableStream<string>, signal?: AbortSignal): AsyncGenerator<string> {
|
|
16
|
-
const reader = stream.getReader();
|
|
17
|
-
let buffer = "";
|
|
18
|
-
try {
|
|
19
|
-
while (true) {
|
|
20
|
-
if (signal?.aborted) return;
|
|
21
|
-
const { done, value } = await reader.read();
|
|
22
|
-
if (done) {
|
|
23
|
-
if (buffer.length > 0) yield buffer;
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
buffer += value;
|
|
27
|
-
let idx: number;
|
|
28
|
-
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
29
|
-
const line = buffer.endsWith("\r\n", idx + 1) ? buffer.slice(0, idx - 1) : buffer.slice(0, idx);
|
|
30
|
-
yield line;
|
|
31
|
-
buffer = buffer.slice(idx + 1);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
} finally {
|
|
35
|
-
reader.releaseLock();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Parse SSE events from a byte stream */
|
|
40
|
-
export async function* readSseEvents(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<ServerSentEvent> {
|
|
41
|
-
const textStream = stream.pipeThrough(new TextDecoderStream() as unknown as TransformStream<Uint8Array, string>);
|
|
42
|
-
let currentEvent: string | null = null;
|
|
43
|
-
let currentData: string[] = [];
|
|
44
|
-
let currentRaw: string[] = [];
|
|
45
|
-
|
|
46
|
-
function flush(): ServerSentEvent | null {
|
|
47
|
-
if (currentData.length === 0 && currentEvent === null) return null;
|
|
48
|
-
const evt: ServerSentEvent = {
|
|
49
|
-
event: currentEvent,
|
|
50
|
-
data: currentData.join("\n"),
|
|
51
|
-
raw: currentRaw,
|
|
52
|
-
};
|
|
53
|
-
currentEvent = null;
|
|
54
|
-
currentData = [];
|
|
55
|
-
currentRaw = [];
|
|
56
|
-
return evt;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for await (const line of readLines(textStream, signal)) {
|
|
60
|
-
if (signal?.aborted) return;
|
|
61
|
-
|
|
62
|
-
// Empty line → dispatch
|
|
63
|
-
if (line === "") {
|
|
64
|
-
const evt = flush();
|
|
65
|
-
if (evt) yield evt;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Comment
|
|
70
|
-
if (line.startsWith(":")) continue;
|
|
71
|
-
|
|
72
|
-
// [DONE] sentinel
|
|
73
|
-
if (line === "[DONE]") return;
|
|
74
|
-
|
|
75
|
-
const colonIdx = line.indexOf(":");
|
|
76
|
-
if (colonIdx === -1) {
|
|
77
|
-
// Field with no value
|
|
78
|
-
const field = line;
|
|
79
|
-
if (field === "event") {
|
|
80
|
-
currentEvent = "";
|
|
81
|
-
} else if (field === "data") {
|
|
82
|
-
currentData.push("");
|
|
83
|
-
}
|
|
84
|
-
currentRaw.push(line);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const field = line.slice(0, colonIdx);
|
|
89
|
-
let value = line.slice(colonIdx + 1);
|
|
90
|
-
// Remove leading space after colon per SSE spec
|
|
91
|
-
if (value.startsWith(" ")) value = value.slice(1);
|
|
92
|
-
|
|
93
|
-
currentRaw.push(line);
|
|
94
|
-
|
|
95
|
-
// L1: Guard against unbounded memory growth (line count)
|
|
96
|
-
if (currentRaw.length > MAX_EVENT_LINES) {
|
|
97
|
-
const evt = flush();
|
|
98
|
-
if (evt) yield evt;
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// L2: Guard against unbounded memory growth (data size per line)
|
|
103
|
-
if (value.length > MAX_DATA_SIZE) {
|
|
104
|
-
// Truncate oversized data to prevent memory issues
|
|
105
|
-
value = value.slice(0, MAX_DATA_SIZE);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (field === "event") {
|
|
109
|
-
currentEvent = value;
|
|
110
|
-
} else if (field === "data") {
|
|
111
|
-
currentData.push(value);
|
|
112
|
-
}
|
|
113
|
-
// id / retry fields ignored
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Flush any remaining event at end of stream
|
|
117
|
-
const evt = flush();
|
|
118
|
-
if (evt) yield evt;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Parse SSE events and yield parsed JSON data objects */
|
|
122
|
-
export async function* readSseJson<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<T> {
|
|
123
|
-
for await (const evt of readSseEvents(stream, signal)) {
|
|
124
|
-
try {
|
|
125
|
-
const parsed: T = JSON.parse(evt.data) as T;
|
|
126
|
-
yield parsed;
|
|
127
|
-
} catch {
|
|
128
|
-
// intentional parse fallback
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { WorkflowConfig } from "./workflow-config.ts";
|
|
2
|
-
|
|
3
|
-
// Rough token estimates per role (input + output combined)
|
|
4
|
-
const ROLE_TOKEN_ESTIMATES: Record<string, number> = {
|
|
5
|
-
explorer: 15000,
|
|
6
|
-
planner: 20000,
|
|
7
|
-
executor: 25000,
|
|
8
|
-
reviewer: 12000,
|
|
9
|
-
"security-reviewer": 12000,
|
|
10
|
-
"test-engineer": 15000,
|
|
11
|
-
analyst: 18000,
|
|
12
|
-
writer: 15000,
|
|
13
|
-
verifier: 10000,
|
|
14
|
-
critic: 12000,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Rough cost per 1K tokens (USD) — using a mid-range model price
|
|
18
|
-
const COST_PER_1K_TOKENS = 0.003;
|
|
19
|
-
|
|
20
|
-
export interface CostEstimate {
|
|
21
|
-
totalTokens: number;
|
|
22
|
-
estimatedCostUSD: number;
|
|
23
|
-
perStep: Array<{ stepId: string; role: string; tokens: number }>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function estimateWorkflowCost(workflow: WorkflowConfig): CostEstimate {
|
|
27
|
-
const perStep = workflow.steps.map((step) => {
|
|
28
|
-
const tokens = ROLE_TOKEN_ESTIMATES[step.role] ?? 15000; // default 15K
|
|
29
|
-
return { stepId: step.id, role: step.role, tokens };
|
|
30
|
-
});
|
|
31
|
-
const totalTokens = perStep.reduce((sum, s) => sum + s.tokens, 0);
|
|
32
|
-
const estimatedCostUSD = (totalTokens / 1000) * COST_PER_1K_TOKENS;
|
|
33
|
-
return { totalTokens, estimatedCostUSD, perStep };
|
|
34
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase-gated intermediate store — persist workflow step outputs to disk.
|
|
3
|
-
*
|
|
4
|
-
* Pattern origin: Understand-Anything 7-phase pipeline where each phase
|
|
5
|
-
* writes structured JSON to intermediate/ directory. Phase N reads Phase N-1
|
|
6
|
-
* output from disk (not context). Enables:
|
|
7
|
-
* - Context isolation between steps
|
|
8
|
-
* - Incremental re-runs (skip completed phases)
|
|
9
|
-
* - Debugging (inspect intermediate outputs)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
13
|
-
import path from "node:path";
|
|
14
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
15
|
-
import { isSafePathId } from "../utils/safe-paths.ts";
|
|
16
|
-
|
|
17
|
-
// ── Types ────────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
export interface IntermediateOutput {
|
|
20
|
-
phase: string;
|
|
21
|
-
stepId: string;
|
|
22
|
-
timestamp: string;
|
|
23
|
-
data: unknown;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface IntermediateStoreConfig {
|
|
27
|
-
/** Root directory for intermediates (e.g., ".crew/intermediate/") */
|
|
28
|
-
intermediateDir: string;
|
|
29
|
-
/** File patterns to preserve across runs (e.g., ["scan-result.json"]) */
|
|
30
|
-
preservePatterns: string[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ── Store Operations ─────────────────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
const DEFAULT_CONFIG: IntermediateStoreConfig = {
|
|
36
|
-
intermediateDir: ".crew/intermediate",
|
|
37
|
-
preservePatterns: [],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Ensure the intermediate directory exists.
|
|
42
|
-
*/
|
|
43
|
-
export function ensureIntermediateDir(config: Partial<IntermediateStoreConfig> = {}): string {
|
|
44
|
-
const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
|
|
45
|
-
mkdirSync(dir, { recursive: true });
|
|
46
|
-
return dir;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Write an intermediate output for a phase.
|
|
51
|
-
*
|
|
52
|
-
* @param config - Store configuration
|
|
53
|
-
* @param phase - Phase name (e.g., "explore", "analyze")
|
|
54
|
-
* @param stepId - Step ID for correlation
|
|
55
|
-
* @param data - Phase output data
|
|
56
|
-
* @returns Path to the written file
|
|
57
|
-
*/
|
|
58
|
-
export function writeIntermediate(config: Partial<IntermediateStoreConfig>, phase: string, stepId: string, data: unknown): string {
|
|
59
|
-
// Security: validate phase/stepId before building the filename to prevent
|
|
60
|
-
// path traversal via poisoned phase or stepId.
|
|
61
|
-
if (!isSafePathId(phase) || !isSafePathId(stepId)) {
|
|
62
|
-
throw new Error("Invalid phase or stepId for intermediate store");
|
|
63
|
-
}
|
|
64
|
-
const dir = ensureIntermediateDir(config);
|
|
65
|
-
const filename = `${phase}-${stepId}.json`;
|
|
66
|
-
const filePath = path.join(dir, filename);
|
|
67
|
-
|
|
68
|
-
const output: IntermediateOutput = {
|
|
69
|
-
phase,
|
|
70
|
-
stepId,
|
|
71
|
-
timestamp: new Date().toISOString(),
|
|
72
|
-
data,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
writeFileSync(filePath, JSON.stringify(output, null, 2), "utf-8");
|
|
76
|
-
return filePath;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Read an intermediate output for a phase.
|
|
81
|
-
*
|
|
82
|
-
* @param config - Store configuration
|
|
83
|
-
* @param phase - Phase name
|
|
84
|
-
* @param stepId - Step ID
|
|
85
|
-
* @returns Parsed intermediate output, or undefined if not found
|
|
86
|
-
*/
|
|
87
|
-
export function readIntermediate(config: Partial<IntermediateStoreConfig>, phase: string, stepId: string): IntermediateOutput | undefined {
|
|
88
|
-
// M-2 fix (code-review 2026-06-23): validate phase/stepId before building the
|
|
89
|
-
// filename to prevent path traversal via a poisoned stepId.
|
|
90
|
-
if (!isSafePathId(phase) || !isSafePathId(stepId)) return undefined;
|
|
91
|
-
const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
|
|
92
|
-
const filename = `${phase}-${stepId}.json`;
|
|
93
|
-
const filePath = path.join(dir, filename);
|
|
94
|
-
|
|
95
|
-
if (!existsSync(filePath)) return undefined;
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const content = readFileSync(filePath, "utf-8");
|
|
99
|
-
return JSON.parse(content) as IntermediateOutput;
|
|
100
|
-
} catch (error) {
|
|
101
|
-
logInternalError("intermediate-store.read", error, `filePath=${filePath}`);
|
|
102
|
-
return undefined;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Read the most recent intermediate output for any phase.
|
|
108
|
-
*
|
|
109
|
-
* Useful when you don't know the exact stepId but want the latest
|
|
110
|
-
* output from a phase (e.g., for incremental re-runs).
|
|
111
|
-
*/
|
|
112
|
-
export function readLatestIntermediate(config: Partial<IntermediateStoreConfig>, phase: string): IntermediateOutput | undefined {
|
|
113
|
-
const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
|
|
114
|
-
if (!existsSync(dir)) return undefined;
|
|
115
|
-
|
|
116
|
-
const files = readdirSync(dir)
|
|
117
|
-
.filter((f) => f.startsWith(`${phase}-`) && f.endsWith(".json"))
|
|
118
|
-
.sort()
|
|
119
|
-
.reverse(); // most recent first
|
|
120
|
-
|
|
121
|
-
if (files.length === 0) return undefined;
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const content = readFileSync(path.join(dir, files[0]!), "utf-8");
|
|
125
|
-
return JSON.parse(content) as IntermediateOutput;
|
|
126
|
-
} catch {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Clean up intermediate files, preserving specified patterns.
|
|
133
|
-
*
|
|
134
|
-
* @param config - Store configuration
|
|
135
|
-
* @returns Number of files removed
|
|
136
|
-
*/
|
|
137
|
-
export function cleanupIntermediates(config: Partial<IntermediateStoreConfig> = {}): number {
|
|
138
|
-
const dir = config.intermediateDir ?? DEFAULT_CONFIG.intermediateDir;
|
|
139
|
-
const preserve = config.preservePatterns ?? DEFAULT_CONFIG.preservePatterns;
|
|
140
|
-
|
|
141
|
-
if (!existsSync(dir)) return 0;
|
|
142
|
-
|
|
143
|
-
const files = readdirSync(dir);
|
|
144
|
-
let removed = 0;
|
|
145
|
-
|
|
146
|
-
for (const file of files) {
|
|
147
|
-
const shouldPreserve = preserve.some((pattern) => file.includes(pattern));
|
|
148
|
-
if (!shouldPreserve) {
|
|
149
|
-
try {
|
|
150
|
-
unlinkSync(path.join(dir, file));
|
|
151
|
-
removed++;
|
|
152
|
-
} catch (error) {
|
|
153
|
-
logInternalError("intermediate-store.cleanup", error, `file=${file}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return removed;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Check if a phase has completed (intermediate exists).
|
|
163
|
-
*/
|
|
164
|
-
export function hasPhaseCompleted(config: Partial<IntermediateStoreConfig>, phase: string, stepId: string): boolean {
|
|
165
|
-
return readIntermediate(config, phase, stepId) !== undefined;
|
|
166
|
-
}
|