pi-crew 0.1.49 → 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 +74 -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 +14 -1
- package/src/config/defaults.ts +5 -5
- 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 -82
- package/src/extension/project-init.ts +36 -4
- package/src/extension/register.ts +67 -22
- 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-export.ts +26 -12
- 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/context.ts +1 -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 +14 -3
- 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/concurrency.ts +3 -1
- package/src/runtime/crash-recovery.ts +33 -0
- package/src/runtime/delta-conflict.ts +360 -0
- package/src/runtime/diagnostic-export.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/event-stream-bridge.ts +3 -1
- 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 -2
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +74 -6
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +122 -0
- package/src/runtime/process-status.ts +14 -1
- 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 +3 -3
- 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-builder.ts +1 -1
- 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 +126 -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 +12 -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 -11
- 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 +9 -4
- 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/powerbar-publisher.ts +6 -0
- 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/src/worktree/worktree-manager.ts +11 -3
- 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 -38
- 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
|
@@ -23,7 +23,9 @@ export function defaultWorkflowConcurrency(workflowName: string, workflowMaxConc
|
|
|
23
23
|
if (workflowMaxConcurrency !== undefined) return workflowMaxConcurrency;
|
|
24
24
|
if (workflowName === "parallel-research") return DEFAULT_CONCURRENCY.workflow.parallelResearch;
|
|
25
25
|
if (workflowName === "research") return DEFAULT_CONCURRENCY.workflow.research;
|
|
26
|
-
if (workflowName === "implementation"
|
|
26
|
+
if (workflowName === "implementation") return DEFAULT_CONCURRENCY.workflow.implementation;
|
|
27
|
+
if (workflowName === "review") return DEFAULT_CONCURRENCY.workflow.review;
|
|
28
|
+
if (workflowName === "default") return DEFAULT_CONCURRENCY.workflow.default;
|
|
27
29
|
return DEFAULT_CONCURRENCY.fallback;
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -11,6 +11,8 @@ import { checkProcessLiveness } from "./process-status.ts";
|
|
|
11
11
|
import { reconcileStaleRun, type ReconcileResult } from "./stale-reconciler.ts";
|
|
12
12
|
import { executeHook, appendHookEvent } from "../hooks/registry.ts";
|
|
13
13
|
import { activeRunEntries, unregisterActiveRun, readActiveRunRegistry } from "../state/active-run-registry.ts";
|
|
14
|
+
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
15
|
+
import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
14
16
|
|
|
15
17
|
export interface RecoveryPlan {
|
|
16
18
|
runId: string;
|
|
@@ -168,6 +170,32 @@ export function cancelOrphanedRuns(
|
|
|
168
170
|
* This is the **global** cleanup that cancelOrphanedRuns (project-scoped)
|
|
169
171
|
* cannot reach.
|
|
170
172
|
*/
|
|
173
|
+
/**
|
|
174
|
+
* Best-effort removal of stateRoot and artifactsRoot directories for a purged run.
|
|
175
|
+
* Uses resolveRealContainedPath to ensure we only delete paths that are safely
|
|
176
|
+
* contained within a known crew root (project or user level).
|
|
177
|
+
*/
|
|
178
|
+
function tryRemoveRunDirectories(entry: { stateRoot: string; cwd: string }): void {
|
|
179
|
+
const roots = [projectCrewRoot(entry.cwd), userCrewRoot()];
|
|
180
|
+
for (const root of roots) {
|
|
181
|
+
try {
|
|
182
|
+
resolveRealContainedPath(root, entry.stateRoot);
|
|
183
|
+
// If we get here, stateRoot is safely contained — remove it
|
|
184
|
+
fs.rmSync(entry.stateRoot, { recursive: true, force: true });
|
|
185
|
+
break;
|
|
186
|
+
} catch {
|
|
187
|
+
// Not contained in this root, try next
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// NOTE: artifactsRoot is shared across runs and cleaned up by pruneFinishedRuns/pruneUserLevelRuns — not deleted here.
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Purge the global active-run-index of entries whose manifest is no longer active.
|
|
195
|
+
*
|
|
196
|
+
* Note: This function only cleans user-level active run entries.
|
|
197
|
+
* Project-level stale runs are handled by session_start auto-prune triggered during run creation.
|
|
198
|
+
*/
|
|
171
199
|
export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.now()): { purged: string[]; kept: string[] } {
|
|
172
200
|
const purged: string[] = [];
|
|
173
201
|
const kept: string[] = [];
|
|
@@ -177,6 +205,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
|
|
|
177
205
|
// 1. Manifest file gone → definitely stale
|
|
178
206
|
if (!fs.existsSync(entry.manifestPath)) {
|
|
179
207
|
unregisterActiveRun(entry.runId);
|
|
208
|
+
tryRemoveRunDirectories(entry);
|
|
180
209
|
purged.push(entry.runId);
|
|
181
210
|
continue;
|
|
182
211
|
}
|
|
@@ -184,6 +213,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
|
|
|
184
213
|
// 2. CWD gone → temp dir cleaned up
|
|
185
214
|
if (!fs.existsSync(entry.cwd)) {
|
|
186
215
|
unregisterActiveRun(entry.runId);
|
|
216
|
+
tryRemoveRunDirectories(entry);
|
|
187
217
|
purged.push(entry.runId);
|
|
188
218
|
continue;
|
|
189
219
|
}
|
|
@@ -194,6 +224,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
|
|
|
194
224
|
manifest = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8"));
|
|
195
225
|
} catch {
|
|
196
226
|
unregisterActiveRun(entry.runId);
|
|
227
|
+
tryRemoveRunDirectories(entry);
|
|
197
228
|
purged.push(entry.runId);
|
|
198
229
|
continue;
|
|
199
230
|
}
|
|
@@ -202,6 +233,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
|
|
|
202
233
|
const terminalStatuses = new Set(["completed", "failed", "cancelled", "blocked"]);
|
|
203
234
|
if (manifest && terminalStatuses.has(manifest.status ?? "")) {
|
|
204
235
|
unregisterActiveRun(entry.runId);
|
|
236
|
+
tryRemoveRunDirectories(entry);
|
|
205
237
|
purged.push(entry.runId);
|
|
206
238
|
continue;
|
|
207
239
|
}
|
|
@@ -231,6 +263,7 @@ export function purgeStaleActiveRunIndex(staleThresholdMs = 300_000, now = Date.
|
|
|
231
263
|
// Best-effort manifest cleanup
|
|
232
264
|
}
|
|
233
265
|
unregisterActiveRun(entry.runId);
|
|
266
|
+
tryRemoveRunDirectories(entry);
|
|
234
267
|
purged.push(entry.runId);
|
|
235
268
|
continue;
|
|
236
269
|
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta conflict detection for pi-crew import and resume operations.
|
|
3
|
+
*
|
|
4
|
+
* Compares incoming bundles against existing state to surface conflicts
|
|
5
|
+
* (file overwrites, status mismatches, schema drift, deleted resources)
|
|
6
|
+
* without blocking the operation — only reporting for user awareness.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ── Types ──────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export type ConflictKind = "file_overwrite" | "state_mismatch" | "schema_drift" | "resource_deleted";
|
|
12
|
+
|
|
13
|
+
export interface Conflict {
|
|
14
|
+
kind: ConflictKind;
|
|
15
|
+
/** File or resource path that is in conflict. */
|
|
16
|
+
path: string;
|
|
17
|
+
/** Current value or summary (optional). */
|
|
18
|
+
existing?: string;
|
|
19
|
+
/** Incoming value or summary (optional). */
|
|
20
|
+
incoming?: string;
|
|
21
|
+
severity: "error" | "warning";
|
|
22
|
+
autoResolvable: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ConflictReport {
|
|
26
|
+
hasConflicts: boolean;
|
|
27
|
+
conflicts: Conflict[];
|
|
28
|
+
summary: { errors: number; warnings: number; autoResolvable: number };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ConflictStrategy = "skip" | "overwrite" | "merge";
|
|
32
|
+
|
|
33
|
+
export interface ConflictResolution {
|
|
34
|
+
resolved: boolean;
|
|
35
|
+
action: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
interface TaskLike {
|
|
41
|
+
id: string;
|
|
42
|
+
status: string;
|
|
43
|
+
role?: string;
|
|
44
|
+
agent?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildReport(conflicts: Conflict[]): ConflictReport {
|
|
48
|
+
const errors = conflicts.filter((c) => c.severity === "error").length;
|
|
49
|
+
const warnings = conflicts.filter((c) => c.severity === "warning").length;
|
|
50
|
+
const autoResolvable = conflicts.filter((c) => c.autoResolvable).length;
|
|
51
|
+
return {
|
|
52
|
+
hasConflicts: conflicts.length > 0,
|
|
53
|
+
conflicts,
|
|
54
|
+
summary: { errors, warnings, autoResolvable },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
59
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract task-like objects from an unknown array, filtering out non-records.
|
|
64
|
+
*/
|
|
65
|
+
function extractTaskLikes(tasks: unknown[]): TaskLike[] {
|
|
66
|
+
return tasks
|
|
67
|
+
.filter(isRecord)
|
|
68
|
+
.map((t) => ({
|
|
69
|
+
id: typeof t.id === "string" ? t.id : "",
|
|
70
|
+
status: typeof t.status === "string" ? t.status : "",
|
|
71
|
+
role: typeof t.role === "string" ? t.role : undefined,
|
|
72
|
+
agent: typeof t.agent === "string" ? t.agent : undefined,
|
|
73
|
+
}))
|
|
74
|
+
.filter((t) => t.id !== "");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Import Conflict Detection ──────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
export interface ImportBundle {
|
|
80
|
+
manifest: Record<string, unknown>;
|
|
81
|
+
tasks: unknown[];
|
|
82
|
+
events?: unknown[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ExistingState {
|
|
86
|
+
manifest?: Record<string, unknown>;
|
|
87
|
+
tasks?: unknown[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Detect conflicts between an import bundle and existing run state.
|
|
92
|
+
*
|
|
93
|
+
* Checks:
|
|
94
|
+
* - **schema_drift**: manifest `schemaVersion` differs between import and existing.
|
|
95
|
+
* - **file_overwrite**: artifact paths in the import bundle that already exist
|
|
96
|
+
* in the current manifest's artifact list.
|
|
97
|
+
* - **state_mismatch**: task statuses differ between import and existing tasks.
|
|
98
|
+
* - **resource_deleted**: referenced agent/team/workflow in import does not exist
|
|
99
|
+
* in the current state.
|
|
100
|
+
*/
|
|
101
|
+
export function detectImportConflicts(
|
|
102
|
+
importBundle: ImportBundle,
|
|
103
|
+
existingState: ExistingState,
|
|
104
|
+
): ConflictReport {
|
|
105
|
+
const conflicts: Conflict[] = [];
|
|
106
|
+
const { manifest: incoming, tasks: incomingTasks } = importBundle;
|
|
107
|
+
const { manifest: current, tasks: currentTasks } = existingState;
|
|
108
|
+
|
|
109
|
+
// ── Schema drift ─────────────────────────────────────────────────
|
|
110
|
+
if (current) {
|
|
111
|
+
const incomingVersion = incoming.schemaVersion;
|
|
112
|
+
const currentVersion = current.schemaVersion;
|
|
113
|
+
if (
|
|
114
|
+
typeof incomingVersion !== "undefined" &&
|
|
115
|
+
typeof currentVersion !== "undefined" &&
|
|
116
|
+
incomingVersion !== currentVersion
|
|
117
|
+
) {
|
|
118
|
+
conflicts.push({
|
|
119
|
+
kind: "schema_drift",
|
|
120
|
+
path: "manifest.schemaVersion",
|
|
121
|
+
existing: String(currentVersion),
|
|
122
|
+
incoming: String(incomingVersion),
|
|
123
|
+
severity: "warning",
|
|
124
|
+
autoResolvable: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── File overwrite (artifact collision) ──────────────────────────
|
|
130
|
+
const incomingArtifacts = Array.isArray(incoming.artifacts) ? incoming.artifacts : [];
|
|
131
|
+
const currentArtifacts = Array.isArray(current?.artifacts) ? current?.artifacts : [];
|
|
132
|
+
|
|
133
|
+
if (current && currentArtifacts.length > 0) {
|
|
134
|
+
const currentPaths = new Set(
|
|
135
|
+
currentArtifacts
|
|
136
|
+
.filter(isRecord)
|
|
137
|
+
.map((a) => (typeof a.path === "string" ? a.path : ""))
|
|
138
|
+
.filter((p) => p !== ""),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
for (const artifact of incomingArtifacts) {
|
|
142
|
+
if (!isRecord(artifact)) continue;
|
|
143
|
+
const artifactPath = typeof artifact.path === "string" ? artifact.path : "";
|
|
144
|
+
if (artifactPath !== "" && currentPaths.has(artifactPath)) {
|
|
145
|
+
conflicts.push({
|
|
146
|
+
kind: "file_overwrite",
|
|
147
|
+
path: artifactPath,
|
|
148
|
+
existing: "present in current run",
|
|
149
|
+
incoming: "present in import bundle",
|
|
150
|
+
severity: "warning",
|
|
151
|
+
autoResolvable: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── State mismatch (task status differences) ─────────────────────
|
|
158
|
+
if (currentTasks && currentTasks.length > 0) {
|
|
159
|
+
const currentTaskMap = new Map(
|
|
160
|
+
extractTaskLikes(currentTasks).map((t) => [t.id, t]),
|
|
161
|
+
);
|
|
162
|
+
const incomingTaskList = extractTaskLikes(incomingTasks);
|
|
163
|
+
|
|
164
|
+
for (const inTask of incomingTaskList) {
|
|
165
|
+
const curTask = currentTaskMap.get(inTask.id);
|
|
166
|
+
if (curTask && curTask.status !== inTask.status) {
|
|
167
|
+
conflicts.push({
|
|
168
|
+
kind: "state_mismatch",
|
|
169
|
+
path: `tasks/${inTask.id}`,
|
|
170
|
+
existing: curTask.status,
|
|
171
|
+
incoming: inTask.status,
|
|
172
|
+
severity: "error",
|
|
173
|
+
autoResolvable: false,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Resource deleted (agent/team/workflow no longer in current) ──
|
|
180
|
+
if (current) {
|
|
181
|
+
const currentTeam = typeof current.team === "string" ? current.team : undefined;
|
|
182
|
+
const currentWorkflow = typeof current.workflow === "string" ? current.workflow : undefined;
|
|
183
|
+
const incomingTeam = typeof incoming.team === "string" ? incoming.team : undefined;
|
|
184
|
+
const incomingWorkflow = typeof incoming.workflow === "string" ? incoming.workflow : undefined;
|
|
185
|
+
|
|
186
|
+
if (incomingTeam && currentTeam && incomingTeam !== currentTeam) {
|
|
187
|
+
conflicts.push({
|
|
188
|
+
kind: "resource_deleted",
|
|
189
|
+
path: `team/${incomingTeam}`,
|
|
190
|
+
existing: currentTeam,
|
|
191
|
+
incoming: incomingTeam,
|
|
192
|
+
severity: "warning",
|
|
193
|
+
autoResolvable: true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (incomingWorkflow && currentWorkflow && incomingWorkflow !== currentWorkflow) {
|
|
198
|
+
conflicts.push({
|
|
199
|
+
kind: "resource_deleted",
|
|
200
|
+
path: `workflow/${incomingWorkflow}`,
|
|
201
|
+
existing: currentWorkflow,
|
|
202
|
+
incoming: incomingWorkflow,
|
|
203
|
+
severity: "warning",
|
|
204
|
+
autoResolvable: true,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check agent references from tasks vs current tasks
|
|
209
|
+
if (currentTasks && currentTasks.length > 0) {
|
|
210
|
+
const currentAgents = new Set(
|
|
211
|
+
extractTaskLikes(currentTasks)
|
|
212
|
+
.map((t) => t.agent)
|
|
213
|
+
.filter((a): a is string => a !== undefined),
|
|
214
|
+
);
|
|
215
|
+
const incomingTaskList = extractTaskLikes(incomingTasks);
|
|
216
|
+
for (const inTask of incomingTaskList) {
|
|
217
|
+
if (
|
|
218
|
+
inTask.agent &&
|
|
219
|
+
inTask.id !== "" &&
|
|
220
|
+
currentAgents.size > 0 &&
|
|
221
|
+
!currentAgents.has(inTask.agent) &&
|
|
222
|
+
// Only flag if there's a matching task id (agent was reassigned)
|
|
223
|
+
extractTaskLikes(currentTasks).some((ct) => ct.id === inTask.id)
|
|
224
|
+
) {
|
|
225
|
+
conflicts.push({
|
|
226
|
+
kind: "resource_deleted",
|
|
227
|
+
path: `agent/${inTask.agent}`,
|
|
228
|
+
existing: "not in current run",
|
|
229
|
+
incoming: inTask.agent,
|
|
230
|
+
severity: "warning",
|
|
231
|
+
autoResolvable: true,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return buildReport(conflicts);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Resume Conflict Detection ──────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
export interface SuspendedState {
|
|
244
|
+
tasks: unknown[];
|
|
245
|
+
artifacts: string[];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface CurrentState {
|
|
249
|
+
changedFiles: string[];
|
|
250
|
+
taskStatuses: Record<string, string>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Detect conflicts when resuming a suspended run against current filesystem state.
|
|
255
|
+
*
|
|
256
|
+
* Checks:
|
|
257
|
+
* - **file_overwrite**: files changed since suspension.
|
|
258
|
+
* - **state_mismatch**: task statuses changed externally.
|
|
259
|
+
*/
|
|
260
|
+
export function detectResumeConflicts(
|
|
261
|
+
suspendedState: SuspendedState,
|
|
262
|
+
currentState: CurrentState,
|
|
263
|
+
): ConflictReport {
|
|
264
|
+
const conflicts: Conflict[] = [];
|
|
265
|
+
const { tasks: suspendedTasks, artifacts: suspendedArtifacts } = suspendedState;
|
|
266
|
+
const { changedFiles, taskStatuses } = currentState;
|
|
267
|
+
|
|
268
|
+
// ── File overwrite ───────────────────────────────────────────────
|
|
269
|
+
const changedSet = new Set(changedFiles);
|
|
270
|
+
for (const artifactPath of suspendedArtifacts) {
|
|
271
|
+
if (changedSet.has(artifactPath)) {
|
|
272
|
+
conflicts.push({
|
|
273
|
+
kind: "file_overwrite",
|
|
274
|
+
path: artifactPath,
|
|
275
|
+
existing: "modified since suspension",
|
|
276
|
+
incoming: "expected unchanged",
|
|
277
|
+
severity: "error",
|
|
278
|
+
autoResolvable: false,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── State mismatch ───────────────────────────────────────────────
|
|
284
|
+
const suspendedTaskList = extractTaskLikes(suspendedTasks);
|
|
285
|
+
for (const task of suspendedTaskList) {
|
|
286
|
+
const currentStatus = taskStatuses[task.id];
|
|
287
|
+
if (currentStatus !== undefined && currentStatus !== task.status) {
|
|
288
|
+
conflicts.push({
|
|
289
|
+
kind: "state_mismatch",
|
|
290
|
+
path: `tasks/${task.id}`,
|
|
291
|
+
existing: currentStatus,
|
|
292
|
+
incoming: task.status,
|
|
293
|
+
severity: "error",
|
|
294
|
+
autoResolvable: false,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return buildReport(conflicts);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Conflict Resolution ────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Apply a resolution strategy to a conflict.
|
|
306
|
+
*
|
|
307
|
+
* - **skip**: skip the conflicting item (always resolves).
|
|
308
|
+
* - **overwrite**: replace existing with incoming (resolves for `file_overwrite`
|
|
309
|
+
* and `schema_drift`; does not resolve `state_mismatch` or `resource_deleted`).
|
|
310
|
+
* - **merge**: attempt merge — resolves `state_mismatch` with merged status;
|
|
311
|
+
* resolves others conditionally.
|
|
312
|
+
*/
|
|
313
|
+
export function resolveConflict(
|
|
314
|
+
conflict: Conflict,
|
|
315
|
+
strategy: ConflictStrategy,
|
|
316
|
+
): ConflictResolution {
|
|
317
|
+
switch (strategy) {
|
|
318
|
+
case "skip":
|
|
319
|
+
return { resolved: true, action: `Skipped ${conflict.path}` };
|
|
320
|
+
|
|
321
|
+
case "overwrite":
|
|
322
|
+
if (conflict.kind === "file_overwrite" || conflict.kind === "schema_drift") {
|
|
323
|
+
return { resolved: true, action: `Overwritten ${conflict.path} with incoming value` };
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
resolved: false,
|
|
327
|
+
action: `Cannot overwrite ${conflict.kind} at ${conflict.path}; manual resolution required`,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
case "merge":
|
|
331
|
+
if (conflict.kind === "state_mismatch") {
|
|
332
|
+
return {
|
|
333
|
+
resolved: true,
|
|
334
|
+
action: `Merged ${conflict.path}: kept existing=${conflict.existing ?? "?"}, incoming=${conflict.incoming ?? "?"}`,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (conflict.kind === "resource_deleted") {
|
|
338
|
+
return {
|
|
339
|
+
resolved: true,
|
|
340
|
+
action: `Merged ${conflict.path}: using incoming resource reference`,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
if (conflict.kind === "file_overwrite") {
|
|
344
|
+
return {
|
|
345
|
+
resolved: true,
|
|
346
|
+
action: `Merged ${conflict.path}: kept both versions`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (conflict.kind === "schema_drift") {
|
|
350
|
+
return {
|
|
351
|
+
resolved: true,
|
|
352
|
+
action: `Merged ${conflict.path}: using incoming schema version`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
resolved: false,
|
|
357
|
+
action: `Cannot merge ${conflict.kind} at ${conflict.path}`,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -26,12 +26,14 @@ export interface DiagnosticReport {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const SECRET_KEY_PATTERN = /(token|key|password|secret|credential|auth)/i;
|
|
29
|
+
const ENV_DEBUG_ALLOWLIST = /^(PI_CREW_|PI_TEAMS_|PI_.*HOME|NODE_ENV|NODE_VERSION|OS|PROCESSOR|TERM|LANG|HOME|USERPROFILE|APPDATA|PLATFORM|ARCH|WIN32|DOCKER|CI|VERBOSE|DEBUG|NO_COLOR|FORCE_COLOR|NPM_CONFIG|npm_)/i;
|
|
29
30
|
|
|
30
31
|
function envRedacted(): Record<string, string> {
|
|
31
32
|
const output: Record<string, string> = {};
|
|
32
33
|
for (const [key, value] of Object.entries(process.env)) {
|
|
33
34
|
if (SECRET_KEY_PATTERN.test(key)) output[key] = "***";
|
|
34
|
-
else if (typeof value === "string") output[key] = value;
|
|
35
|
+
else if (typeof value === "string" && ENV_DEBUG_ALLOWLIST.test(key)) output[key] = value;
|
|
36
|
+
// All other env vars are omitted to prevent leaking sensitive paths or system topology.
|
|
35
37
|
}
|
|
36
38
|
return output;
|
|
37
39
|
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
2
|
-
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
3
|
-
import type { TeamConfig } from "../teams/team-config.ts";
|
|
4
|
-
import type { WorkflowConfig } from "../workflows/workflow-config.ts";
|
|
5
|
-
|
|
6
|
-
export function isDirectRun(manifest: Pick<TeamRunManifest, "team" | "workflow">): boolean {
|
|
7
|
-
return manifest.workflow === "direct-agent";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function directTeamAndWorkflowFromRun(manifest: TeamRunManifest, tasks: TeamTaskState[], agents: AgentConfig[]): { team: TeamConfig; workflow: WorkflowConfig } | undefined {
|
|
11
|
-
if (!isDirectRun(manifest)) return undefined;
|
|
12
|
-
const firstTask = tasks[0];
|
|
13
|
-
const agentName = firstTask?.agent ?? (manifest.team.replace(/^direct-/, "") || "executor");
|
|
14
|
-
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
15
|
-
const role = firstTask?.role ?? "agent";
|
|
16
|
-
const stepId = firstTask?.stepId ?? "01_agent";
|
|
17
|
-
return {
|
|
18
|
-
team: {
|
|
19
|
-
name: manifest.team,
|
|
20
|
-
description: `Direct subagent run for ${agentName}`,
|
|
21
|
-
source: "builtin",
|
|
22
|
-
filePath: "<generated>",
|
|
23
|
-
roles: [{ name: role, agent: agentName, description: agent?.description }],
|
|
24
|
-
defaultWorkflow: "direct-agent",
|
|
25
|
-
workspaceMode: manifest.workspaceMode,
|
|
26
|
-
},
|
|
27
|
-
workflow: {
|
|
28
|
-
name: manifest.workflow ?? "direct-agent",
|
|
29
|
-
description: `Direct task for ${agentName}`,
|
|
30
|
-
source: "builtin",
|
|
31
|
-
filePath: "<generated>",
|
|
32
|
-
steps: [{ id: stepId, role, task: "{goal}", model: firstTask?.model }],
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
}
|
|
1
|
+
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
2
|
+
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
3
|
+
import type { TeamConfig } from "../teams/team-config.ts";
|
|
4
|
+
import type { WorkflowConfig } from "../workflows/workflow-config.ts";
|
|
5
|
+
|
|
6
|
+
export function isDirectRun(manifest: Pick<TeamRunManifest, "team" | "workflow">): boolean {
|
|
7
|
+
return manifest.workflow === "direct-agent";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function directTeamAndWorkflowFromRun(manifest: TeamRunManifest, tasks: TeamTaskState[], agents: AgentConfig[]): { team: TeamConfig; workflow: WorkflowConfig } | undefined {
|
|
11
|
+
if (!isDirectRun(manifest)) return undefined;
|
|
12
|
+
const firstTask = tasks[0];
|
|
13
|
+
const agentName = firstTask?.agent ?? (manifest.team.replace(/^direct-/, "") || "executor");
|
|
14
|
+
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
15
|
+
const role = firstTask?.role ?? "agent";
|
|
16
|
+
const stepId = firstTask?.stepId ?? "01_agent";
|
|
17
|
+
return {
|
|
18
|
+
team: {
|
|
19
|
+
name: manifest.team,
|
|
20
|
+
description: `Direct subagent run for ${agentName}`,
|
|
21
|
+
source: "builtin",
|
|
22
|
+
filePath: "<generated>",
|
|
23
|
+
roles: [{ name: role, agent: agentName, description: agent?.description }],
|
|
24
|
+
defaultWorkflow: "direct-agent",
|
|
25
|
+
workspaceMode: manifest.workspaceMode,
|
|
26
|
+
},
|
|
27
|
+
workflow: {
|
|
28
|
+
name: manifest.workflow ?? "direct-agent",
|
|
29
|
+
description: `Direct task for ${agentName}`,
|
|
30
|
+
source: "builtin",
|
|
31
|
+
filePath: "<generated>",
|
|
32
|
+
steps: [{ id: stepId, role, task: "{goal}", model: firstTask?.model }],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -53,7 +53,9 @@ export function bridgeEventFromJsonEvent(runId: string, taskId: string, event: u
|
|
|
53
53
|
if (typeof record.toolName === "string") result.toolName = record.toolName;
|
|
54
54
|
if (record.args && typeof record.args === "object") {
|
|
55
55
|
try {
|
|
56
|
-
|
|
56
|
+
const json = JSON.stringify(record.args);
|
|
57
|
+
// Truncate at a JSON boundary to avoid breaking structure
|
|
58
|
+
result.toolArgs = json.length > 200 ? json.slice(0, 197) + "..." : json;
|
|
57
59
|
} catch {
|
|
58
60
|
/* skip */
|
|
59
61
|
}
|