pi-crew 0.1.51 → 0.2.0
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 +56 -1
- package/README.md +176 -781
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +70 -11
- package/agents/writer.md +11 -11
- package/docs/actions-reference.md +595 -0
- package/docs/commands-reference.md +347 -0
- package/docs/runtime-flow.md +148 -148
- package/index.ts +6 -6
- package/package.json +99 -99
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -157
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/adapters/claude-adapter.ts +25 -0
- package/src/adapters/codex-adapter.ts +21 -0
- package/src/adapters/cursor-adapter.ts +17 -0
- package/src/adapters/export-util.ts +137 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +18 -0
- package/src/adapters/types.ts +23 -0
- package/src/agents/agent-config.ts +2 -0
- package/src/agents/agent-search.ts +98 -98
- package/src/agents/discover-agents.ts +2 -1
- package/src/config/config.ts +13 -1
- package/src/config/drift-detector.ts +211 -0
- package/src/config/markers.ts +327 -0
- package/src/config/resilient-parser.ts +108 -0
- package/src/config/suggestions.ts +74 -0
- package/src/extension/cross-extension-rpc.ts +103 -94
- package/src/extension/project-init.ts +21 -1
- package/src/extension/register.ts +45 -14
- package/src/extension/registration/commands.ts +77 -8
- package/src/extension/registration/subagent-tools.ts +10 -1
- package/src/extension/registration/team-tool.ts +10 -1
- package/src/extension/registration/viewers.ts +48 -34
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-import.ts +25 -1
- package/src/extension/run-index.ts +5 -1
- package/src/extension/run-maintenance.ts +142 -68
- package/src/extension/team-manager-command.ts +10 -1
- package/src/extension/team-tool/doctor.ts +28 -3
- package/src/extension/team-tool/handle-settings.ts +195 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -42
- package/src/extension/team-tool/lifecycle-actions.ts +27 -8
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/run.ts +12 -1
- package/src/extension/team-tool.ts +11 -1
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +92 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -0
- package/src/runtime/background-runner.ts +11 -1
- package/src/runtime/cancellation-token.ts +89 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi.ts +7 -2
- package/src/runtime/compaction-summary.ts +271 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +33 -0
- package/src/runtime/delta-conflict.ts +360 -0
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/iteration-hooks.ts +262 -0
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -150
- package/src/runtime/live-irc.ts +92 -92
- package/src/runtime/live-session-health.ts +100 -100
- package/src/runtime/loop-gates.ts +129 -0
- package/src/runtime/metric-parser.ts +40 -0
- package/src/runtime/notebook-helpers.ts +90 -90
- package/src/runtime/orphan-sentinel.ts +7 -7
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/phase-progress.ts +217 -0
- package/src/runtime/pi-args.ts +38 -11
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +57 -7
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +122 -0
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -164
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -121
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/sensitive-paths.ts +2 -2
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -177
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph.ts +207 -0
- package/src/runtime/task-quality.ts +207 -0
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +7 -1
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/team-runner.ts +117 -7
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -0
- package/src/runtime/workspace-tree.ts +298 -298
- package/src/schema/config-schema.ts +11 -0
- package/src/schema/validation-types.ts +148 -0
- package/src/skills/skill-templates.ts +374 -0
- package/src/state/active-run-registry.ts +35 -11
- package/src/state/atomic-write.ts +33 -26
- package/src/state/contracts.ts +1 -0
- package/src/state/event-reconstructor.ts +217 -0
- package/src/state/locks.ts +2 -13
- package/src/state/mailbox.ts +4 -3
- package/src/state/state-store.ts +32 -14
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +9 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +5 -2
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dashboard-panes/progress-pane.ts +11 -0
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/render-coalescer.ts +51 -51
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-action-dispatcher.ts +10 -1
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -258
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sleep.ts +40 -26
- package/src/utils/task-name-generator.ts +337 -337
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +30 -29
- package/workflows/fast-fix.workflow.md +23 -22
- package/workflows/implementation.workflow.md +43 -43
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/docs/refactor-tasks-phase3.md +0 -394
- package/docs/refactor-tasks-phase4.md +0 -564
- package/docs/refactor-tasks-phase5.md +0 -402
- package/docs/refactor-tasks-phase6.md +0 -662
- package/docs/refactor-tasks.md +0 -1484
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
- package/docs/research/AUDIT_OH_MY_PI.md +0 -261
- package/docs/research/AUDIT_PI_CREW.md +0 -457
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
- package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
- package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
- package/docs/research-awesome-agent-skills-distillation.md +0 -100
- package/docs/research-extension-examples.md +0 -297
- package/docs/research-extension-system.md +0 -324
- package/docs/research-oh-my-pi-distillation.md +0 -369
- package/docs/research-optimization-plan.md +0 -548
- package/docs/research-phase10-distillation.md +0 -199
- package/docs/research-phase11-distillation.md +0 -201
- package/docs/research-phase8-operator-experience-plan.md +0 -819
- package/docs/research-phase9-observability-reliability-plan.md +0 -1190
- package/docs/research-pi-coding-agent.md +0 -357
- package/docs/research-source-pi-crew-reference.md +0 -174
- package/docs/research-ui-optimization-plan.md +0 -480
- package/docs/source-runtime-refactor-map.md +0 -107
- package/src/utils/atomic-write.ts +0 -33
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured Result Extractor — attempts to extract structured data from worker output.
|
|
3
|
-
* Tries multiple extraction strategies before falling back to raw text.
|
|
4
|
-
*/
|
|
5
|
-
export interface ExtractedResult {
|
|
6
|
-
/** Whether structured data was successfully extracted */
|
|
7
|
-
structured: boolean;
|
|
8
|
-
/** Parsed structured data (if structured=true) */
|
|
9
|
-
data: unknown;
|
|
10
|
-
/** Raw text output (always available) */
|
|
11
|
-
rawText: string;
|
|
12
|
-
/** Error message if extraction was attempted but failed */
|
|
13
|
-
error?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Extract structured result from raw worker output text.
|
|
18
|
-
* Tries strategies in order: direct JSON, fenced JSON, key-value markers.
|
|
19
|
-
*/
|
|
20
|
-
export function extractStructuredResult(raw: string, _schema?: Record<string, unknown>): ExtractedResult {
|
|
21
|
-
const trimmed = raw.trim();
|
|
22
|
-
if (!trimmed) {
|
|
23
|
-
return { structured: false, data: null, rawText: raw };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Strategy 1: Direct JSON parse (entire output is JSON)
|
|
27
|
-
const directResult = tryDirectJson(trimmed);
|
|
28
|
-
if (directResult !== undefined) {
|
|
29
|
-
return { structured: true, data: directResult, rawText: raw };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Strategy 2: Extract from ```json ... ``` fence
|
|
33
|
-
const fencedResult = tryFencedJson(trimmed);
|
|
34
|
-
if (fencedResult !== undefined) {
|
|
35
|
-
return { structured: true, data: fencedResult, rawText: raw };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Strategy 3: Extract from markers like "RESULT:" or "OUTPUT:"
|
|
39
|
-
const markerResult = tryMarkerExtraction(trimmed);
|
|
40
|
-
if (markerResult !== undefined) {
|
|
41
|
-
return { structured: true, data: markerResult, rawText: raw };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return { structured: false, data: null, rawText: raw };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function tryDirectJson(text: string): unknown | undefined {
|
|
48
|
-
if (!text.startsWith("{") && !text.startsWith("[")) return undefined;
|
|
49
|
-
try {
|
|
50
|
-
return JSON.parse(text);
|
|
51
|
-
} catch {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function tryFencedJson(text: string): unknown | undefined {
|
|
57
|
-
const match = text.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
58
|
-
if (!match?.[1]) return undefined;
|
|
59
|
-
try {
|
|
60
|
-
return JSON.parse(match[1].trim());
|
|
61
|
-
} catch {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function tryMarkerExtraction(text: string): unknown | undefined {
|
|
67
|
-
// Try to find JSON after common markers
|
|
68
|
-
const markers = ["RESULT:", "OUTPUT:", "ANSWER:", "### Result\n", "## Output\n"];
|
|
69
|
-
for (const marker of markers) {
|
|
70
|
-
const idx = text.indexOf(marker);
|
|
71
|
-
if (idx === -1) continue;
|
|
72
|
-
const after = text.slice(idx + marker.length).trim();
|
|
73
|
-
// Try JSON parse on text after marker
|
|
74
|
-
if (after.startsWith("{") || after.startsWith("[")) {
|
|
75
|
-
try {
|
|
76
|
-
return JSON.parse(after);
|
|
77
|
-
} catch {
|
|
78
|
-
// Try to find just the JSON object/array
|
|
79
|
-
const jsonEnd = findMatchingBracket(after);
|
|
80
|
-
if (jsonEnd > 0) {
|
|
81
|
-
try {
|
|
82
|
-
return JSON.parse(after.slice(0, jsonEnd));
|
|
83
|
-
} catch {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function findMatchingBracket(text: string): number {
|
|
94
|
-
const openChar = text[0];
|
|
95
|
-
const closeChar = openChar === "{" ? "}" : "]";
|
|
96
|
-
let depth = 0;
|
|
97
|
-
let inString = false;
|
|
98
|
-
let escape = false;
|
|
99
|
-
for (let i = 0; i < text.length; i++) {
|
|
100
|
-
const ch = text[i];
|
|
101
|
-
if (escape) {
|
|
102
|
-
escape = false;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (ch === "\\") {
|
|
106
|
-
escape = true;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (ch === '"') {
|
|
110
|
-
inString = !inString;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (inString) continue;
|
|
114
|
-
if (ch === openChar) depth++;
|
|
115
|
-
if (ch === closeChar) {
|
|
116
|
-
depth--;
|
|
117
|
-
if (depth === 0) return i + 1;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return -1;
|
|
121
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Structured Result Extractor — attempts to extract structured data from worker output.
|
|
3
|
+
* Tries multiple extraction strategies before falling back to raw text.
|
|
4
|
+
*/
|
|
5
|
+
export interface ExtractedResult {
|
|
6
|
+
/** Whether structured data was successfully extracted */
|
|
7
|
+
structured: boolean;
|
|
8
|
+
/** Parsed structured data (if structured=true) */
|
|
9
|
+
data: unknown;
|
|
10
|
+
/** Raw text output (always available) */
|
|
11
|
+
rawText: string;
|
|
12
|
+
/** Error message if extraction was attempted but failed */
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract structured result from raw worker output text.
|
|
18
|
+
* Tries strategies in order: direct JSON, fenced JSON, key-value markers.
|
|
19
|
+
*/
|
|
20
|
+
export function extractStructuredResult(raw: string, _schema?: Record<string, unknown>): ExtractedResult {
|
|
21
|
+
const trimmed = raw.trim();
|
|
22
|
+
if (!trimmed) {
|
|
23
|
+
return { structured: false, data: null, rawText: raw };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Strategy 1: Direct JSON parse (entire output is JSON)
|
|
27
|
+
const directResult = tryDirectJson(trimmed);
|
|
28
|
+
if (directResult !== undefined) {
|
|
29
|
+
return { structured: true, data: directResult, rawText: raw };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Strategy 2: Extract from ```json ... ``` fence
|
|
33
|
+
const fencedResult = tryFencedJson(trimmed);
|
|
34
|
+
if (fencedResult !== undefined) {
|
|
35
|
+
return { structured: true, data: fencedResult, rawText: raw };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Strategy 3: Extract from markers like "RESULT:" or "OUTPUT:"
|
|
39
|
+
const markerResult = tryMarkerExtraction(trimmed);
|
|
40
|
+
if (markerResult !== undefined) {
|
|
41
|
+
return { structured: true, data: markerResult, rawText: raw };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { structured: false, data: null, rawText: raw };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function tryDirectJson(text: string): unknown | undefined {
|
|
48
|
+
if (!text.startsWith("{") && !text.startsWith("[")) return undefined;
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(text);
|
|
51
|
+
} catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function tryFencedJson(text: string): unknown | undefined {
|
|
57
|
+
const match = text.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
58
|
+
if (!match?.[1]) return undefined;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(match[1].trim());
|
|
61
|
+
} catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function tryMarkerExtraction(text: string): unknown | undefined {
|
|
67
|
+
// Try to find JSON after common markers
|
|
68
|
+
const markers = ["RESULT:", "OUTPUT:", "ANSWER:", "### Result\n", "## Output\n"];
|
|
69
|
+
for (const marker of markers) {
|
|
70
|
+
const idx = text.indexOf(marker);
|
|
71
|
+
if (idx === -1) continue;
|
|
72
|
+
const after = text.slice(idx + marker.length).trim();
|
|
73
|
+
// Try JSON parse on text after marker
|
|
74
|
+
if (after.startsWith("{") || after.startsWith("[")) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(after);
|
|
77
|
+
} catch {
|
|
78
|
+
// Try to find just the JSON object/array
|
|
79
|
+
const jsonEnd = findMatchingBracket(after);
|
|
80
|
+
if (jsonEnd > 0) {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(after.slice(0, jsonEnd));
|
|
83
|
+
} catch {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function findMatchingBracket(text: string): number {
|
|
94
|
+
const openChar = text[0];
|
|
95
|
+
const closeChar = openChar === "{" ? "}" : "]";
|
|
96
|
+
let depth = 0;
|
|
97
|
+
let inString = false;
|
|
98
|
+
let escape = false;
|
|
99
|
+
for (let i = 0; i < text.length; i++) {
|
|
100
|
+
const ch = text[i];
|
|
101
|
+
if (escape) {
|
|
102
|
+
escape = false;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (ch === "\\") {
|
|
106
|
+
escape = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (ch === '"') {
|
|
110
|
+
inString = !inString;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (inString) continue;
|
|
114
|
+
if (ch === openChar) depth++;
|
|
115
|
+
if (ch === closeChar) {
|
|
116
|
+
depth--;
|
|
117
|
+
if (depth === 0) return i + 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return -1;
|
|
121
|
+
}
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
export type RolePermissionMode = "read_only" | "workspace_write" | "danger_full_access" | "explicit_confirm";
|
|
2
|
-
|
|
3
|
-
const READ_ONLY_ROLES = new Set(["explorer", "reviewer", "security-reviewer", "verifier", "analyst", "critic", "planner", "writer"]);
|
|
4
|
-
const WRITE_ROLES = new Set(["executor", "test-engineer"]);
|
|
5
|
-
const READ_ONLY_COMMANDS = new Set(["cat", "head", "tail", "less", "more", "wc", "ls", "find", "grep", "rg", "awk", "sed", "echo", "printf", "which", "where", "whoami", "pwd", "env", "printenv", "date", "df", "du", "uname", "file", "stat", "diff", "sort", "uniq", "tr", "cut", "paste", "test", "true", "false", "type", "readlink", "realpath", "basename", "dirname", "sha256sum", "md5sum", "xxd", "hexdump", "od", "strings", "tree", "jq", "git", "gh"]);
|
|
6
|
-
|
|
7
|
-
export interface PermissionCheckResult {
|
|
8
|
-
allowed: boolean;
|
|
9
|
-
mode: RolePermissionMode;
|
|
10
|
-
reason?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function permissionForRole(role: string): RolePermissionMode {
|
|
14
|
-
if (READ_ONLY_ROLES.has(role)) return "read_only";
|
|
15
|
-
if (WRITE_ROLES.has(role)) return "workspace_write";
|
|
16
|
-
return "workspace_write";
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function isReadOnlyCommand(command: string): boolean {
|
|
20
|
-
const first = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop() ?? "";
|
|
21
|
-
return READ_ONLY_COMMANDS.has(first) && !/\s(-i|--in-place)\b|\s>{1,2}\s|\brm\b|\bmv\b|\bcp\b|\b(?:npm|pnpm|yarn|bun)\s+(install|add|ci|remove)\b|\bgit\s+(commit|push|merge|rebase|reset|checkout|clean)\b/.test(command);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function checkRolePermission(role: string, command: string): PermissionCheckResult {
|
|
25
|
-
const mode = permissionForRole(role);
|
|
26
|
-
if (mode === "read_only" && !isReadOnlyCommand(command)) return { allowed: false, mode, reason: `Role '${role}' is read-only and command may modify state.` };
|
|
27
|
-
return { allowed: true, mode };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function currentCrewRole(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
31
|
-
return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || undefined;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function checkSubagentSpawnPermission(role: string | undefined): PermissionCheckResult {
|
|
35
|
-
if (!role) return { allowed: true, mode: "workspace_write" };
|
|
36
|
-
const mode = permissionForRole(role);
|
|
37
|
-
if (mode === "read_only") return { allowed: false, mode, reason: `Role '${role}' is read-only and cannot spawn additional subagents.` };
|
|
38
|
-
return { allowed: true, mode };
|
|
39
|
-
}
|
|
1
|
+
export type RolePermissionMode = "read_only" | "workspace_write" | "danger_full_access" | "explicit_confirm";
|
|
2
|
+
|
|
3
|
+
const READ_ONLY_ROLES = new Set(["explorer", "reviewer", "security-reviewer", "verifier", "analyst", "critic", "planner", "writer"]);
|
|
4
|
+
const WRITE_ROLES = new Set(["executor", "test-engineer"]);
|
|
5
|
+
const READ_ONLY_COMMANDS = new Set(["cat", "head", "tail", "less", "more", "wc", "ls", "find", "grep", "rg", "awk", "sed", "echo", "printf", "which", "where", "whoami", "pwd", "env", "printenv", "date", "df", "du", "uname", "file", "stat", "diff", "sort", "uniq", "tr", "cut", "paste", "test", "true", "false", "type", "readlink", "realpath", "basename", "dirname", "sha256sum", "md5sum", "xxd", "hexdump", "od", "strings", "tree", "jq", "git", "gh"]);
|
|
6
|
+
|
|
7
|
+
export interface PermissionCheckResult {
|
|
8
|
+
allowed: boolean;
|
|
9
|
+
mode: RolePermissionMode;
|
|
10
|
+
reason?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function permissionForRole(role: string): RolePermissionMode {
|
|
14
|
+
if (READ_ONLY_ROLES.has(role)) return "read_only";
|
|
15
|
+
if (WRITE_ROLES.has(role)) return "workspace_write";
|
|
16
|
+
return "workspace_write";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isReadOnlyCommand(command: string): boolean {
|
|
20
|
+
const first = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop() ?? "";
|
|
21
|
+
return READ_ONLY_COMMANDS.has(first) && !/\s(-i|--in-place)\b|\s>{1,2}\s|\brm\b|\bmv\b|\bcp\b|\b(?:npm|pnpm|yarn|bun)\s+(install|add|ci|remove)\b|\bgit\s+(commit|push|merge|rebase|reset|checkout|clean)\b/.test(command);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function checkRolePermission(role: string, command: string): PermissionCheckResult {
|
|
25
|
+
const mode = permissionForRole(role);
|
|
26
|
+
if (mode === "read_only" && !isReadOnlyCommand(command)) return { allowed: false, mode, reason: `Role '${role}' is read-only and command may modify state.` };
|
|
27
|
+
return { allowed: true, mode };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function currentCrewRole(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
31
|
+
return env.PI_CREW_ROLE?.trim() || env.PI_TEAMS_ROLE?.trim() || undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function checkSubagentSpawnPermission(role: string | undefined): PermissionCheckResult {
|
|
35
|
+
if (!role) return { allowed: true, mode: "workspace_write" };
|
|
36
|
+
const mode = permissionForRole(role);
|
|
37
|
+
if (mode === "read_only") return { allowed: false, mode, reason: `Role '${role}' is read-only and cannot spawn additional subagents.` };
|
|
38
|
+
return { allowed: true, mode };
|
|
39
|
+
}
|
|
@@ -12,10 +12,10 @@ import * as path from "node:path";
|
|
|
12
12
|
|
|
13
13
|
/** Basenames that almost certainly hold secrets or PII */
|
|
14
14
|
const SENSITIVE_BASENAMES = /\.(?:env|pem|key|p12|pfx|crt|cer|jks|keystore|asc|gpg)(?:\..+)?$/i;
|
|
15
|
-
const SENSITIVE_EXACT = /^(?:\.env|\.netrc|\.npmrc|\.pypirc|credentials|secrets?|passwords?|id_(?:rsa|dsa|ecdsa|ed25519)(?:\.pub)?|authorized_keys|known_hosts)$/i;
|
|
15
|
+
const SENSITIVE_EXACT = /^(?:\.env|\.netrc|\.npmrc|\.pypirc|credentials|secrets?|passwords?|id_(?:rsa|dsa|ecdsa|ed25519)(?:\.pub)?|authorized_keys|known_hosts|jwt\.json|session\.cookie|\.token)$/i;
|
|
16
16
|
|
|
17
17
|
/** Path components that indicate sensitive directories */
|
|
18
|
-
const SENSITIVE_DIRS = new Set([".ssh", ".aws", ".gnupg", ".kube", ".docker", ".config/gcloud"]);
|
|
18
|
+
const SENSITIVE_DIRS = new Set([".ssh", ".aws", ".gnupg", ".kube", ".docker", ".config/gcloud", ".config/gh"]);
|
|
19
19
|
|
|
20
20
|
/** Name tokens that suggest sensitive content */
|
|
21
21
|
const SENSITIVE_TOKENS = ["secret", "credential", "password", "passwd", "apikey", "accesskey", "token", "privatekey"];
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
|
|
6
|
-
* Falls back to returning undefined if the API is not available.
|
|
7
|
-
*
|
|
8
|
-
* The returned function (if defined) can be called to unregister the cleanup.
|
|
9
|
-
*/
|
|
10
|
-
export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
|
|
11
|
-
const api = pi as unknown as Record<string, unknown>;
|
|
12
|
-
const registerFn = api["registerSessionResourceCleanup"];
|
|
13
|
-
if (typeof registerFn === "function") {
|
|
14
|
-
try {
|
|
15
|
-
const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
|
|
16
|
-
if (typeof unregister === "function") return unregister;
|
|
17
|
-
// API returned void — cleanup is registered but cannot be unregistered
|
|
18
|
-
return undefined;
|
|
19
|
-
} catch (error) {
|
|
20
|
-
logInternalError("session-resources.register", error);
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
|
|
6
|
+
* Falls back to returning undefined if the API is not available.
|
|
7
|
+
*
|
|
8
|
+
* The returned function (if defined) can be called to unregister the cleanup.
|
|
9
|
+
*/
|
|
10
|
+
export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
|
|
11
|
+
const api = pi as unknown as Record<string, unknown>;
|
|
12
|
+
const registerFn = api["registerSessionResourceCleanup"];
|
|
13
|
+
if (typeof registerFn === "function") {
|
|
14
|
+
try {
|
|
15
|
+
const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
|
|
16
|
+
if (typeof unregister === "function") return unregister;
|
|
17
|
+
// API returned void — cleanup is registered but cannot be unregistered
|
|
18
|
+
return undefined;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logInternalError("session-resources.register", error);
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a lightweight snapshot of task state for event emission.
|
|
5
|
-
* Prevents mutation-during-callback issues by copying relevant fields.
|
|
6
|
-
*/
|
|
7
|
-
export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
|
|
8
|
-
return {
|
|
9
|
-
...task,
|
|
10
|
-
dependsOn: [...task.dependsOn],
|
|
11
|
-
usage: task.usage ? { ...task.usage } : undefined,
|
|
12
|
-
agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
|
|
13
|
-
heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
|
|
14
|
-
modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
|
|
15
|
-
modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
|
|
16
|
-
claim: task.claim ? { ...task.claim } : undefined,
|
|
17
|
-
checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
|
|
18
|
-
attempts: task.attempts?.map((a) => ({ ...a })),
|
|
19
|
-
worktree: task.worktree ? { ...task.worktree } : undefined,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Session state snapshot for persistence before session switches.
|
|
25
|
-
* Captures the minimal set of data needed to resume operations.
|
|
26
|
-
*/
|
|
27
|
-
export interface SessionStateSnapshot {
|
|
28
|
-
/** ISO timestamp of the snapshot */
|
|
29
|
-
capturedAt: string;
|
|
30
|
-
/** Active run IDs at time of snapshot */
|
|
31
|
-
activeRunIds: string[];
|
|
32
|
-
/** Number of pending deliveries */
|
|
33
|
-
pendingDeliveryCount: number;
|
|
34
|
-
/** Session generation counter */
|
|
35
|
-
sessionGeneration: number;
|
|
36
|
-
/** Summary of active tasks by status */
|
|
37
|
-
taskSummary: Record<string, number>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Create a session state snapshot for pre-switch persistence.
|
|
42
|
-
*/
|
|
43
|
-
export function createSessionSnapshot(
|
|
44
|
-
activeRuns: TeamRunManifest[],
|
|
45
|
-
pendingDeliveryCount: number,
|
|
46
|
-
sessionGeneration: number,
|
|
47
|
-
): SessionStateSnapshot {
|
|
48
|
-
const taskSummary: Record<string, number> = {};
|
|
49
|
-
for (const run of activeRuns) {
|
|
50
|
-
taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
capturedAt: new Date().toISOString(),
|
|
54
|
-
activeRunIds: activeRuns.map((r) => r.runId),
|
|
55
|
-
pendingDeliveryCount,
|
|
56
|
-
sessionGeneration,
|
|
57
|
-
taskSummary,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
1
|
+
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a lightweight snapshot of task state for event emission.
|
|
5
|
+
* Prevents mutation-during-callback issues by copying relevant fields.
|
|
6
|
+
*/
|
|
7
|
+
export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
|
|
8
|
+
return {
|
|
9
|
+
...task,
|
|
10
|
+
dependsOn: [...task.dependsOn],
|
|
11
|
+
usage: task.usage ? { ...task.usage } : undefined,
|
|
12
|
+
agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
|
|
13
|
+
heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
|
|
14
|
+
modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
|
|
15
|
+
modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
|
|
16
|
+
claim: task.claim ? { ...task.claim } : undefined,
|
|
17
|
+
checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
|
|
18
|
+
attempts: task.attempts?.map((a) => ({ ...a })),
|
|
19
|
+
worktree: task.worktree ? { ...task.worktree } : undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Session state snapshot for persistence before session switches.
|
|
25
|
+
* Captures the minimal set of data needed to resume operations.
|
|
26
|
+
*/
|
|
27
|
+
export interface SessionStateSnapshot {
|
|
28
|
+
/** ISO timestamp of the snapshot */
|
|
29
|
+
capturedAt: string;
|
|
30
|
+
/** Active run IDs at time of snapshot */
|
|
31
|
+
activeRunIds: string[];
|
|
32
|
+
/** Number of pending deliveries */
|
|
33
|
+
pendingDeliveryCount: number;
|
|
34
|
+
/** Session generation counter */
|
|
35
|
+
sessionGeneration: number;
|
|
36
|
+
/** Summary of active tasks by status */
|
|
37
|
+
taskSummary: Record<string, number>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a session state snapshot for pre-switch persistence.
|
|
42
|
+
*/
|
|
43
|
+
export function createSessionSnapshot(
|
|
44
|
+
activeRuns: TeamRunManifest[],
|
|
45
|
+
pendingDeliveryCount: number,
|
|
46
|
+
sessionGeneration: number,
|
|
47
|
+
): SessionStateSnapshot {
|
|
48
|
+
const taskSummary: Record<string, number> = {};
|
|
49
|
+
for (const run of activeRuns) {
|
|
50
|
+
taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
capturedAt: new Date().toISOString(),
|
|
54
|
+
activeRunIds: activeRuns.map((r) => r.runId),
|
|
55
|
+
pendingDeliveryCount,
|
|
56
|
+
sessionGeneration,
|
|
57
|
+
taskSummary,
|
|
58
|
+
};
|
|
59
|
+
}
|