pi-crew 0.9.55 → 0.9.57
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/AGENTS.md +1 -1
- package/CHANGELOG.md +58 -0
- package/README.md +53 -5
- package/dist/index.mjs +5666 -4370
- package/docs/README.md +54 -0
- package/package.json +12 -13
- package/skills/real-test-pi-crew/SKILL.md +93 -3
- package/src/agents/discover-agents.ts +68 -10
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +2 -1
- package/src/config/types.ts +12 -0
- package/src/extension/action-suggestions.ts +16 -63
- package/src/extension/async-notifier.ts +3 -3
- package/src/extension/command-completions.ts +2 -2
- package/src/extension/crew-cleanup.ts +3 -3
- package/src/extension/crew-vibes/footer.ts +4 -2
- package/src/extension/cross-extension-rpc.ts +10 -5
- package/src/extension/knowledge-injection.ts +57 -10
- package/src/extension/register.ts +2 -2
- package/src/extension/registration/artifact-cleanup.ts +41 -3
- package/src/extension/registration/commands.ts +4 -4
- package/src/extension/registration/context-builder.ts +10 -10
- package/src/extension/registration/crash-recovery-cache.ts +3 -3
- package/src/extension/registration/foreground-run-controller.ts +1 -1
- package/src/extension/registration/lazy-configurers.ts +2 -2
- package/src/extension/registration/lifecycle-handlers.ts +12 -12
- package/src/extension/registration/lifecycle.ts +2 -2
- package/src/extension/registration/observability.ts +2 -2
- package/src/extension/registration/registration-types.ts +3 -3
- package/src/extension/registration/runtime-cleanup.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +2 -2
- package/src/extension/registration/subagent-manager-setup.ts +2 -2
- package/src/extension/registration/subagent-tools.ts +21 -16
- package/src/extension/registration/team-tool.ts +109 -17
- package/src/extension/registration/viewers.ts +3 -4
- package/src/extension/registration/wire-cross-extension.ts +7 -7
- package/src/extension/rpc-hmac.ts +3 -2
- package/src/extension/run-bundle-schema.ts +13 -4
- package/src/extension/run-export.ts +2 -2
- package/src/extension/run-index.ts +2 -3
- package/src/extension/run-maintenance.ts +57 -1
- package/src/extension/team-manager-command.ts +3 -1
- package/src/extension/team-tool/anchor.ts +8 -2
- package/src/extension/team-tool/api.ts +117 -39
- package/src/extension/team-tool/cancel.ts +23 -8
- package/src/extension/team-tool/chain-executor.ts +1 -1
- package/src/extension/team-tool/dispatch/automate.ts +17 -2
- package/src/extension/team-tool/dispatch/control.ts +17 -2
- package/src/extension/team-tool/dispatch/manage.ts +34 -2
- package/src/extension/team-tool/dispatch/run.ts +17 -2
- package/src/extension/team-tool/dispatch/status.ts +37 -5
- package/src/extension/team-tool/doctor.ts +2 -2
- package/src/extension/team-tool/explain.ts +1 -1
- package/src/extension/team-tool/goal-wrap.ts +5 -5
- package/src/extension/team-tool/goal.ts +30 -9
- package/src/extension/team-tool/handle-schedule.ts +37 -19
- package/src/extension/team-tool/inspect.ts +21 -5
- package/src/extension/team-tool/lifecycle-actions.ts +53 -10
- package/src/extension/team-tool/parallel-dispatch.ts +14 -5
- package/src/extension/team-tool/param-error.ts +138 -0
- package/src/extension/team-tool/respond.ts +20 -6
- package/src/extension/team-tool/run.ts +27 -17
- package/src/extension/team-tool/status.ts +17 -6
- package/src/extension/team-tool.ts +26 -19
- package/src/hooks/registry.ts +1 -1
- package/src/i18n.ts +22 -0
- package/src/observability/event-bus.ts +1 -1
- package/src/prompt/prompt-runtime.ts +2 -2
- package/src/runtime/README.md +35 -0
- package/src/runtime/agent-control.ts +1 -1
- package/src/runtime/anchor-manager.ts +0 -2
- package/src/runtime/async-runner.ts +1 -1
- package/src/runtime/attention-events.ts +3 -2
- package/src/runtime/auto-summarize.ts +0 -2
- package/src/runtime/background-runner.ts +103 -30
- package/src/runtime/{crew-broker-child.ts → broker/crew-broker-child.ts} +2 -2
- package/src/runtime/{crew-broker-client.ts → broker/crew-broker-client.ts} +4 -7
- package/src/runtime/{crew-broker.ts → broker/crew-broker.ts} +13 -21
- package/src/runtime/chain-runner.ts +50 -5
- package/src/runtime/{child-pi-constants.ts → child-pi/child-pi-constants.ts} +1 -1
- package/src/runtime/{child-pi-kill.ts → child-pi/child-pi-kill.ts} +3 -3
- package/src/runtime/{child-pi-spawn.ts → child-pi/child-pi-spawn.ts} +28 -12
- package/src/runtime/{child-pi-steering.ts → child-pi/child-pi-steering.ts} +1 -1
- package/src/runtime/{child-pi-streams.ts → child-pi/child-pi-streams.ts} +23 -4
- package/src/runtime/{child-pi-transcript.ts → child-pi/child-pi-transcript.ts} +6 -6
- package/src/runtime/{child-pi.ts → child-pi/child-pi.ts} +47 -78
- package/src/runtime/compaction/compact-stages/bounded-tail.ts +84 -0
- package/src/runtime/{compact-stages → compaction/compact-stages}/index.ts +2 -2
- package/src/runtime/{compaction-summary.ts → compaction/compaction-summary.ts} +3 -3
- package/src/runtime/{tool-output-pruner.ts → compaction/tool-output-pruner.ts} +1 -1
- package/src/runtime/crew-agent-records.ts +48 -2
- package/src/runtime/custom-tools/irc-tool.ts +2 -2
- package/src/runtime/delivery-coordinator.ts +0 -3
- package/src/runtime/diagnostic-export.ts +5 -4
- package/src/runtime/foreground-control.ts +1 -1
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/{adaptive-plan.ts → goal-workflow/adaptive-plan.ts} +8 -8
- package/src/runtime/{dynamic-workflow-context.ts → goal-workflow/dynamic-workflow-context.ts} +16 -16
- package/src/runtime/{dynamic-workflow-runner.ts → goal-workflow/dynamic-workflow-runner.ts} +10 -10
- package/src/runtime/{goal-achievement.ts → goal-workflow/goal-achievement.ts} +3 -3
- package/src/runtime/{goal-evaluator.ts → goal-workflow/goal-evaluator.ts} +8 -8
- package/src/runtime/{goal-loop-runner.ts → goal-workflow/goal-loop-runner.ts} +27 -25
- package/src/runtime/{goal-state-store.ts → goal-workflow/goal-state-store.ts} +8 -8
- package/src/runtime/{plan-templates.ts → goal-workflow/plan-templates.ts} +1 -1
- package/src/runtime/group-join.ts +3 -3
- package/src/runtime/handoff-manager.ts +0 -2
- package/src/runtime/{heartbeat-watcher.ts → heartbeat/heartbeat-watcher.ts} +7 -7
- package/src/runtime/iteration-hooks.ts +1 -1
- package/src/runtime/{live-agent-control.ts → live-session/live-agent-control.ts} +2 -2
- package/src/runtime/{live-agent-manager.ts → live-session/live-agent-manager.ts} +4 -4
- package/src/runtime/{live-session-runtime.ts → live-session/live-session-runtime.ts} +39 -41
- package/src/runtime/manifest-cache.ts +6 -2
- package/src/runtime/{model-fallback.ts → model/model-fallback.ts} +1 -1
- package/src/runtime/{pi-args.ts → model/pi-args.ts} +20 -5
- package/src/runtime/model/provider-extensions.ts +116 -0
- package/src/runtime/{runtime-policy.ts → model/runtime-policy.ts} +2 -2
- package/src/runtime/{runtime-resolver.ts → model/runtime-resolver.ts} +3 -3
- package/src/runtime/{runtime-warmup.ts → model/runtime-warmup.ts} +8 -8
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/{progress-tracker.ts → output/progress-tracker.ts} +1 -1
- package/src/runtime/{result-extractor.ts → output/result-extractor.ts} +4 -4
- package/src/runtime/{sidechain-output.ts → output/sidechain-output.ts} +2 -2
- package/src/runtime/{stream-preview.ts → output/stream-preview.ts} +1 -1
- package/src/runtime/{streaming-output.ts → output/streaming-output.ts} +2 -2
- package/src/runtime/{tool-progress.ts → output/tool-progress.ts} +1 -1
- package/src/runtime/parent-guard.ts +30 -10
- package/src/runtime/pipeline-runner.ts +2 -2
- package/src/runtime/policy-engine.ts +2 -2
- package/src/runtime/{cancellation.ts → process/cancellation.ts} +1 -1
- package/src/runtime/{post-exit-stdio-guard.ts → process/post-exit-stdio-guard.ts} +1 -1
- package/src/runtime/{process-lifecycle.ts → process/process-lifecycle.ts} +3 -3
- package/src/runtime/{checkpoint.ts → recovery/checkpoint.ts} +4 -4
- package/src/runtime/{crash-recovery.ts → recovery/crash-recovery.ts} +93 -21
- package/src/runtime/{overflow-recovery.ts → recovery/overflow-recovery.ts} +1 -1
- package/src/runtime/{recovery-recipes.ts → recovery/recovery-recipes.ts} +1 -1
- package/src/runtime/{retry-executor.ts → recovery/retry-executor.ts} +2 -2
- package/src/runtime/{retry-runner.ts → recovery/retry-runner.ts} +2 -8
- package/src/runtime/run-tracker.ts +1 -1
- package/src/runtime/run-worker.ts +3 -3
- package/src/runtime/{coalesce-tasks.ts → scheduling/coalesce-tasks.ts} +3 -3
- package/src/runtime/{concurrency.ts → scheduling/concurrency.ts} +9 -3
- package/src/runtime/{parallel-research.ts → scheduling/parallel-research.ts} +4 -2
- package/src/runtime/{run-coalesced-task-group.ts → scheduling/run-coalesced-task-group.ts} +95 -36
- package/src/runtime/{semaphore.ts → scheduling/semaphore.ts} +4 -1
- package/src/runtime/{task-graph-scheduler.ts → scheduling/task-graph-scheduler.ts} +1 -1
- package/src/runtime/session-resources.ts +1 -1
- package/src/runtime/settings-store.ts +1 -1
- package/src/runtime/single-agent-compose.ts +1 -0
- package/src/runtime/stale-reconciler.ts +1 -1
- package/src/runtime/subagent-manager.ts +1 -1
- package/src/runtime/supervisor-contact.ts +28 -13
- package/src/runtime/task-output-context.ts +7 -5
- package/src/runtime/task-runner/child-executor.ts +67 -31
- package/src/runtime/task-runner/live-executor.ts +8 -8
- package/src/runtime/task-runner/post-execution.ts +12 -12
- package/src/runtime/task-runner/pre-execution.ts +6 -6
- package/src/runtime/task-runner/progress.ts +1 -1
- package/src/runtime/task-runner/run-projection.ts +1 -1
- package/src/runtime/task-runner/scaffold-executor.ts +1 -1
- package/src/runtime/task-runner/state-helpers.ts +2 -2
- package/src/runtime/task-runner.ts +4 -4
- package/src/runtime/team-runner.ts +441 -175
- package/src/runtime/{completion-guard.ts → verification/completion-guard.ts} +1 -1
- package/src/runtime/{green-contract.ts → verification/green-contract.ts} +1 -1
- package/src/runtime/{post-checks.ts → verification/post-checks.ts} +4 -4
- package/src/runtime/{verification-gates.ts → verification/verification-gates.ts} +5 -5
- package/src/runtime/{verification-integrity.ts → verification/verification-integrity.ts} +1 -1
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +149 -185
- package/src/state/README.md +44 -0
- package/src/state/atomic-write.ts +24 -3
- package/src/state/{locks.ts → coordination/locks.ts} +118 -38
- package/src/state/{mailbox.ts → coordination/mailbox.ts} +14 -8
- package/src/state/{schedule.ts → coordination/schedule.ts} +3 -3
- package/src/state/{task-claims.ts → coordination/task-claims.ts} +1 -1
- package/src/state/decision-ledger.ts +1 -1
- package/src/state/{event-log-rotation.ts → event-log/event-log-rotation.ts} +189 -31
- package/src/state/{event-log.ts → event-log/event-log.ts} +77 -39
- package/src/state/{jsonl-writer.ts → event-log/jsonl-writer.ts} +14 -6
- package/src/state/hook-instinct-bridge.ts +2 -2
- package/src/state/{active-run-registry.ts → stores/active-run-registry.ts} +7 -7
- package/src/state/{artifact-store.ts → stores/artifact-store.ts} +62 -4
- package/src/state/{blob-store.ts → stores/blob-store.ts} +3 -3
- package/src/state/{health-store.ts → stores/health-store.ts} +3 -3
- package/src/state/{instinct-store.ts → stores/instinct-store.ts} +3 -3
- package/src/state/{observation-store.ts → stores/observation-store.ts} +2 -2
- package/src/state/{run-cache.ts → stores/run-cache.ts} +4 -4
- package/src/state/{run-graph.ts → stores/run-graph.ts} +4 -4
- package/src/state/{run-metrics.ts → stores/run-metrics.ts} +3 -3
- package/src/state/{state-store.ts → stores/state-store.ts} +254 -21
- package/src/state/types.ts +27 -3
- package/src/ui/component.ts +55 -0
- package/src/ui/crew-select-list.ts +3 -1
- package/src/ui/dashboard-panes/agents-pane.ts +1 -1
- package/src/ui/dwf-phase-display.ts +1 -1
- package/src/ui/heartbeat-aggregator.ts +1 -1
- package/src/ui/keybinding-map.ts +179 -6
- package/src/ui/layout-primitives.ts +9 -5
- package/src/ui/live-conversation-overlay.ts +1 -1
- package/src/ui/live-run-sidebar.ts +1 -1
- package/src/ui/overlay-stack.ts +148 -0
- package/src/ui/overlays/agent-picker-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-compose-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-detail-overlay.ts +2 -2
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/render-scheduler.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +3 -3
- package/src/ui/run-dashboard.ts +5 -33
- package/src/ui/run-event-bus.ts +3 -3
- package/src/ui/run-snapshot-cache.ts +37 -18
- package/src/ui/settings-overlay.ts +13 -21
- package/src/ui/snapshot-types.ts +1 -1
- package/src/ui/status-colors.ts +0 -2
- package/src/ui/terminal-status.ts +1 -1
- package/src/ui/theme-adapter.ts +33 -13
- package/src/ui/tool-renderers/index.ts +8 -0
- package/src/ui/transcript-viewer.ts +37 -13
- package/src/ui/widget/index.ts +32 -9
- package/src/ui/widget/widget-formatters.ts +62 -4
- package/src/ui/widget/widget-model.ts +2 -2
- package/src/ui/widget/widget-renderer.ts +1 -1
- package/src/utils/conflict-detect.ts +58 -5
- package/src/utils/ndjson.ts +2 -2
- package/src/utils/redaction.ts +39 -0
- package/src/utils/socket-path.ts +1 -1
- package/src/workflows/workflow-config.ts +1 -1
- package/src/worktree/cleanup.ts +1 -1
- package/src/worktree/worktree-manager.ts +111 -47
- package/workflows/chain.workflow.md +2 -2
- package/dist/build-meta.json +0 -16807
- package/index.bundle.ts +0 -25
- package/scripts/bench-check.mjs +0 -96
- package/scripts/bench-cold-start.mjs +0 -216
- package/scripts/build-bundle.mjs +0 -92
- package/scripts/check-all-skills.ts +0 -294
- package/scripts/check-bundle-size.mjs +0 -38
- package/scripts/check-bundle-staleness.mjs +0 -97
- package/scripts/check-conflict-markers.mjs +0 -91
- package/scripts/check-lazy-imports.mjs +0 -28
- package/scripts/dev-runner.mjs +0 -106
- package/scripts/profile-startup.mjs +0 -125
- package/scripts/release-smoke.mjs +0 -74
- package/scripts/run-bench.mjs +0 -47
- package/scripts/run-real-chain.ts +0 -41
- package/scripts/test-issue-29-crash.ts +0 -111
- package/scripts/test-issue-29-e2e.ts +0 -183
- package/scripts/test-issue-29-real-runtime.ts +0 -330
- package/scripts/test-issue-29-real-tasks.ts +0 -387
- package/scripts/test-issue-29-team-tool.ts +0 -105
- package/scripts/test-runner.mjs +0 -84
- package/scripts/verify-flicker-fix.ts +0 -109
- package/scripts/verify-skill.ts +0 -550
- package/scripts/watch-bundle.mjs +0 -259
- package/scripts/watchdog-harness.ts +0 -119
- package/src/adapters/claude-adapter.ts +0 -23
- package/src/adapters/codex-adapter.ts +0 -21
- package/src/adapters/cursor-adapter.ts +0 -17
- package/src/adapters/export-util.ts +0 -143
- package/src/adapters/index.ts +0 -15
- package/src/adapters/registry.ts +0 -18
- package/src/adapters/types.ts +0 -23
- package/src/runtime/code-summary.ts +0 -292
- package/src/runtime/cross-extension-rpc.ts +0 -143
- package/src/runtime/hidden-handoff.ts +0 -407
- package/src/runtime/prose-compressor.ts +0 -162
- package/src/subagents/async-entry.ts +0 -1
- package/src/subagents/index.ts +0 -3
- package/src/subagents/live/control.ts +0 -1
- package/src/subagents/live/manager.ts +0 -1
- package/src/subagents/live/realtime.ts +0 -1
- package/src/subagents/live/session-runtime.ts +0 -1
- package/src/subagents/manager.ts +0 -1
- package/src/subagents/spawn.ts +0 -1
- package/test-integration-check.ts +0 -118
- /package/src/runtime/{broker-issuer.ts → broker/broker-issuer.ts} +0 -0
- /package/src/runtime/{crew-broker-tokens.ts → broker/crew-broker-tokens.ts} +0 -0
- /package/src/runtime/{child-pi-pool.ts → child-pi/child-pi-pool.ts} +0 -0
- /package/src/runtime/{compact-pipeline.ts → compaction/compact-pipeline.ts} +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/ansi-strip-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/blank-collapse-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/deduplicate-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/head-snap-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/tail-capture-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/truncation-stage.ts +0 -0
- /package/src/runtime/{important-line-classifier.ts → compaction/important-line-classifier.ts} +0 -0
- /package/src/runtime/{heartbeat-gradient.ts → heartbeat/heartbeat-gradient.ts} +0 -0
- /package/src/runtime/{worker-heartbeat.ts → heartbeat/worker-heartbeat.ts} +0 -0
- /package/src/runtime/{worker-startup.ts → heartbeat/worker-startup.ts} +0 -0
- /package/src/runtime/{intercom-bridge.ts → live-session/intercom-bridge.ts} +0 -0
- /package/src/runtime/{live-control-realtime.ts → live-session/live-control-realtime.ts} +0 -0
- /package/src/runtime/{live-extension-bridge.ts → live-session/live-extension-bridge.ts} +0 -0
- /package/src/runtime/{live-irc.ts → live-session/live-irc.ts} +0 -0
- /package/src/runtime/{live-session-health.ts → live-session/live-session-health.ts} +0 -0
- /package/src/runtime/{model-resolver.ts → model/model-resolver.ts} +0 -0
- /package/src/runtime/{model-scope.ts → model/model-scope.ts} +0 -0
- /package/src/runtime/{output-validator.ts → output/output-validator.ts} +0 -0
- /package/src/runtime/{pi-json-output.ts → output/pi-json-output.ts} +0 -0
- /package/src/runtime/{progress-event-coalescer.ts → output/progress-event-coalescer.ts} +0 -0
- /package/src/runtime/{cancellation-token.ts → process/cancellation-token.ts} +0 -0
- /package/src/runtime/{zombie-scanner.ts → process/zombie-scanner.ts} +0 -0
- /package/src/runtime/{crash-classification.ts → recovery/crash-classification.ts} +0 -0
- /package/src/runtime/{batch-barrier.ts → scheduling/batch-barrier.ts} +0 -0
- /package/src/runtime/{global-worker-cap.ts → scheduling/global-worker-cap.ts} +0 -0
- /package/src/runtime/{parallel-utils.ts → scheduling/parallel-utils.ts} +0 -0
- /package/src/runtime/{scheduler.ts → scheduling/scheduler.ts} +0 -0
- /package/src/runtime/{task-graph.ts → scheduling/task-graph.ts} +0 -0
- /package/src/runtime/{verification-worktree.ts → verification/verification-worktree.ts} +0 -0
- /package/src/state/{event-reconstructor.ts → event-log/event-reconstructor.ts} +0 -0
- /package/src/state/{worker-atomic-writer.ts → event-log/worker-atomic-writer.ts} +0 -0
|
@@ -341,11 +341,20 @@ export function parseConflictUri(raw: string): ParsedConflictUri | null {
|
|
|
341
341
|
* Works by locating the recorded marker block by content (anchored to
|
|
342
342
|
* `entry.startLine` as the preferred match), so out-of-band edits earlier
|
|
343
343
|
* in the file that shift line numbers don't break resolution.
|
|
344
|
+
*
|
|
345
|
+
* EOL preservation: lines are split keeping each line's original ending
|
|
346
|
+
* bytes (CRLF / LF / none). Only the conflicting region is rewritten; every
|
|
347
|
+
* other line is round-tripped byte-for-byte, so a mixed-EOL file produces a
|
|
348
|
+
* minimal git diff (previously every line ending was normalised, rewriting
|
|
349
|
+
* the whole file even when only a few lines conflicted).
|
|
344
350
|
*/
|
|
345
351
|
export function spliceConflict(originalText: string, entry: ConflictEntry, replacement: string): string {
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
const
|
|
352
|
+
// Split preserving each line's original EOL so non-conflicting lines are
|
|
353
|
+
// round-tripped byte-for-byte (minimal git diff on mixed-EOL files).
|
|
354
|
+
const segments = splitPreservingEol(originalText);
|
|
355
|
+
// Content-only view (CR stripped) — identical to the old `split(/\r?\n/)`,
|
|
356
|
+
// so conflict detection / region matching behaviour is unchanged.
|
|
357
|
+
const lines = segments.map((s) => s.content);
|
|
349
358
|
const expected = buildRecordedRegion(entry);
|
|
350
359
|
const match = locateRegion(lines, expected, entry.startLine - 1);
|
|
351
360
|
if (!match) {
|
|
@@ -356,8 +365,52 @@ export function spliceConflict(originalText: string, entry: ConflictEntry, repla
|
|
|
356
365
|
|
|
357
366
|
const trimmed = normalizeTrailingNewline(replacement);
|
|
358
367
|
const replacementLines = trimmed.split("\n");
|
|
359
|
-
|
|
360
|
-
|
|
368
|
+
// EOL for the inserted lines: prefer the EOL that originally terminated the
|
|
369
|
+
// conflict region (local style); fall back to the file's dominant EOL.
|
|
370
|
+
const regionEol = segments[match.endIdx]?.eol ?? "";
|
|
371
|
+
const interEol = regionEol || (originalText.includes("\r\n") ? "\r\n" : "\n");
|
|
372
|
+
|
|
373
|
+
const out: string[] = [];
|
|
374
|
+
// Lines before the conflict — verbatim (original content + original EOL).
|
|
375
|
+
for (let i = 0; i < match.startIdx; i++) out.push(segments[i].content, segments[i].eol);
|
|
376
|
+
// Replacement lines — intermediate ones use the local EOL; the final one
|
|
377
|
+
// reuses the boundary EOL that originally separated the region from the
|
|
378
|
+
// following line, preserving that line's leading boundary exactly.
|
|
379
|
+
for (let i = 0; i < replacementLines.length; i++) {
|
|
380
|
+
out.push(replacementLines[i]);
|
|
381
|
+
out.push(i < replacementLines.length - 1 ? interEol : regionEol);
|
|
382
|
+
}
|
|
383
|
+
// Lines after the conflict — verbatim.
|
|
384
|
+
for (let i = match.endIdx + 1; i < segments.length; i++) out.push(segments[i].content, segments[i].eol);
|
|
385
|
+
return out.join("");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Split `text` into `{ content, eol }` segments keeping each line's exact
|
|
390
|
+
* ending bytes. The `content` view equals `text.split(/\r?\n/)`, but the
|
|
391
|
+
* original EOL (`"\r\n"`, `"\n"`, or `""` for the final line) is retained
|
|
392
|
+
* so callers can reconstruct the text byte-for-byte.
|
|
393
|
+
*/
|
|
394
|
+
function splitPreservingEol(text: string): { content: string; eol: string }[] {
|
|
395
|
+
const segments: { content: string; eol: string }[] = [];
|
|
396
|
+
const n = text.length;
|
|
397
|
+
let i = 0;
|
|
398
|
+
while (i <= n) {
|
|
399
|
+
const nl = text.indexOf("\n", i);
|
|
400
|
+
if (nl === -1) {
|
|
401
|
+
segments.push({ content: text.slice(i), eol: "" });
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
let contentEnd = nl;
|
|
405
|
+
let eol = "\n";
|
|
406
|
+
if (nl > 0 && text.charCodeAt(nl - 1) === 13 /* \r */) {
|
|
407
|
+
contentEnd = nl - 1;
|
|
408
|
+
eol = "\r\n";
|
|
409
|
+
}
|
|
410
|
+
segments.push({ content: text.slice(i, contentEnd), eol });
|
|
411
|
+
i = nl + 1;
|
|
412
|
+
}
|
|
413
|
+
return segments;
|
|
361
414
|
}
|
|
362
415
|
|
|
363
416
|
/** Reconstruct the recorded marker block as it should appear in the file. */
|
package/src/utils/ndjson.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ndjson.ts — Canonical broker NDJSON framing primitives (encoder + decoder
|
|
3
3
|
* + typed errors). Newline-delimited JSON, one frame per `\\n`.
|
|
4
4
|
*
|
|
5
|
-
* Moved out of the parallel-work stub `src/runtime/crew-broker-deps.ts`.
|
|
5
|
+
* Moved out of the parallel-work stub `src/runtime/broker/crew-broker-deps.ts`.
|
|
6
6
|
* The public surface (MAX_BROKER_FRAME_BYTES, BrokerError, BrokerErrorCode,
|
|
7
7
|
* encodeBrokerFrame, NdjsonDecoder) is preserved verbatim so importers
|
|
8
8
|
* can be updated with a single import-path change.
|
|
@@ -112,4 +112,4 @@ export class NdjsonDecoder {
|
|
|
112
112
|
// External re-exports
|
|
113
113
|
// ============================================================================
|
|
114
114
|
// Re-exported here for callers that import all broker primitives from ndjson.ts.
|
|
115
|
-
export { newBrokerToken } from "../runtime/crew-broker-tokens.ts";
|
|
115
|
+
export { newBrokerToken } from "../runtime/broker/crew-broker-tokens.ts";
|
package/src/utils/redaction.ts
CHANGED
|
@@ -222,7 +222,46 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
222
222
|
return true;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
// P1-6: cheap pre-filter. The common clean case (plain output, JSON event
|
|
226
|
+
// lines without secret-like key names) bails after one sweep instead of running
|
|
227
|
+
// all ~14 redaction passes. The marker set is the NECESSARY substring for every
|
|
228
|
+
// redactable pattern below — a string containing none of them cannot hold a
|
|
229
|
+
// redactable secret, so skipping is safe. Any marker present → full redaction.
|
|
230
|
+
// NOTE: keep this set in sync with the patterns in redactSecretString; the
|
|
231
|
+
// redaction-corpus test asserts every known secret type trips the pre-filter.
|
|
232
|
+
const SECRET_MARKERS_CASE_SENSITIVE = [
|
|
233
|
+
"-----BEGIN",
|
|
234
|
+
"eyJ",
|
|
235
|
+
"AKIA",
|
|
236
|
+
"AIza",
|
|
237
|
+
"sk_live_",
|
|
238
|
+
"xox",
|
|
239
|
+
// GitHub PAT prefixes (gh[pousr]_) — exact, not the over-broad "gh" which
|
|
240
|
+
// matched "though"/"highlight"/"might" and defeated the pre-filter skip.
|
|
241
|
+
"ghp_",
|
|
242
|
+
"gho_",
|
|
243
|
+
"ghu_",
|
|
244
|
+
"ghs_",
|
|
245
|
+
"ghr_",
|
|
246
|
+
];
|
|
247
|
+
const SECRET_MARKERS_LOWER = ["bearer", "authorization", "token", "api", "key", "password", "passwd", "secret", "credential", "private"];
|
|
248
|
+
|
|
249
|
+
function mayContainSecret(value: string): boolean {
|
|
250
|
+
for (const m of SECRET_MARKERS_CASE_SENSITIVE) {
|
|
251
|
+
if (value.includes(m)) return true;
|
|
252
|
+
}
|
|
253
|
+
// Case-insensitive markers (Bearer/auth headers + inline-secret key names).
|
|
254
|
+
// toLowerCase is a full pass, so only pay it after the case-sensitive misses.
|
|
255
|
+
const lower = value.toLowerCase();
|
|
256
|
+
for (const m of SECRET_MARKERS_LOWER) {
|
|
257
|
+
if (lower.includes(m)) return true;
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
225
262
|
export function redactSecretString(value: string): string {
|
|
263
|
+
// P1-6: skip the ~14 redaction passes when no secret marker is present.
|
|
264
|
+
if (!mayContainSecret(value)) return value;
|
|
226
265
|
let result = value;
|
|
227
266
|
|
|
228
267
|
// Replace PEM private keys. Skip the regex entirely on very large inputs —
|
package/src/utils/socket-path.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* socket-path.ts — Canonical broker Unix-socket / named-pipe path utilities.
|
|
3
3
|
*
|
|
4
|
-
* Moved out of the parallel-work stub `src/runtime/crew-broker-deps.ts`.
|
|
4
|
+
* Moved out of the parallel-work stub `src/runtime/broker/crew-broker-deps.ts`.
|
|
5
5
|
* The public surface (hashSessionId, getBrokerSocketPath,
|
|
6
6
|
* prepareBrokerSocketDir, removeStaleBrokerSocket) is preserved verbatim
|
|
7
7
|
* so importers can be updated with a single import-path change.
|
|
@@ -63,7 +63,7 @@ export interface WorkflowConfig {
|
|
|
63
63
|
* multiple ready tasks sharing role + cwd are grouped into a single
|
|
64
64
|
* multi-task worker prompt to reduce per-task cold-start cost. Disabled
|
|
65
65
|
* in v0.9.17 unless the workflow author opts in. See
|
|
66
|
-
* src/runtime/coalesce-tasks.ts. */
|
|
66
|
+
* src/runtime/scheduling/coalesce-tasks.ts. */
|
|
67
67
|
coalesceMicroTasks?: boolean;
|
|
68
68
|
}
|
|
69
69
|
|
package/src/worktree/cleanup.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
5
|
-
import { writeArtifact } from "../state/artifact-store.ts";
|
|
5
|
+
import { writeArtifact } from "../state/stores/artifact-store.ts";
|
|
6
6
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
7
7
|
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
8
8
|
import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
|
|
@@ -5,7 +5,7 @@ import * as path from "node:path";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { loadConfig } from "../config/config.ts";
|
|
7
7
|
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
8
|
-
import { writeArtifact } from "../state/artifact-store.ts";
|
|
8
|
+
import { writeArtifact } from "../state/stores/artifact-store.ts";
|
|
9
9
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
10
10
|
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
11
11
|
import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
|
|
@@ -463,15 +463,30 @@ async function branchExistsAsync(repoRoot: string, branch: string): Promise<{ lo
|
|
|
463
463
|
}
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
+
// P1-11: `git worktree prune` is a repo-level write that cleans ALL stale
|
|
467
|
+
// worktrees — running it per task is pure waste. Coalesce concurrent calls
|
|
468
|
+
// (N tasks in a batch → 1 prune) and memoize per repo per process (within a
|
|
469
|
+
// run no new stale worktrees appear, so one prune suffices).
|
|
470
|
+
const _prunedRepos = new Set<string>();
|
|
471
|
+
const _pruneInFlight = new Map<string, Promise<void>>();
|
|
466
472
|
async function pruneStaleWorktreesAsync(repoRoot: string): Promise<void> {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
473
|
+
if (_prunedRepos.has(repoRoot)) return;
|
|
474
|
+
const inFlight = _pruneInFlight.get(repoRoot);
|
|
475
|
+
if (inFlight) return inFlight;
|
|
476
|
+
const p = (async () => {
|
|
477
|
+
try {
|
|
478
|
+
await execFileAsync("git", ["worktree", "prune"], {
|
|
479
|
+
cwd: repoRoot,
|
|
480
|
+
windowsHide: true,
|
|
481
|
+
});
|
|
482
|
+
} catch {
|
|
483
|
+
/* best-effort */
|
|
484
|
+
}
|
|
485
|
+
_prunedRepos.add(repoRoot);
|
|
486
|
+
_pruneInFlight.delete(repoRoot);
|
|
487
|
+
})();
|
|
488
|
+
_pruneInFlight.set(repoRoot, p);
|
|
489
|
+
return p;
|
|
475
490
|
}
|
|
476
491
|
|
|
477
492
|
/**
|
|
@@ -568,7 +583,7 @@ export function overlaySeedPaths(repoRoot: string, worktreePath: string, seedPat
|
|
|
568
583
|
* recovered via `git apply` / manual restore. Best-effort: a snapshot failure
|
|
569
584
|
* only logs (it must not block the clean-slate reuse flow).
|
|
570
585
|
*/
|
|
571
|
-
function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskState, worktreePath: string, dirtyStatus: string): boolean {
|
|
586
|
+
export function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskState, worktreePath: string, dirtyStatus: string): boolean {
|
|
572
587
|
try {
|
|
573
588
|
const parts: string[] = [
|
|
574
589
|
`# Worktree recovery snapshot`,
|
|
@@ -576,15 +591,18 @@ function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskState, w
|
|
|
576
591
|
`capturedAt: ${new Date().toISOString()}`,
|
|
577
592
|
"",
|
|
578
593
|
];
|
|
594
|
+
// ST-1: use --binary so tracked binary modifications are recoverable via `git apply`.
|
|
579
595
|
let trackedDiff = "";
|
|
580
596
|
try {
|
|
581
|
-
trackedDiff = git(worktreePath, ["diff", "HEAD"]);
|
|
597
|
+
trackedDiff = git(worktreePath, ["diff", "HEAD", "--binary"]);
|
|
582
598
|
} catch {
|
|
583
599
|
trackedDiff = "";
|
|
584
600
|
}
|
|
585
601
|
if (trackedDiff.trim()) {
|
|
586
|
-
parts.push("## Tracked changes (`git diff HEAD`)", "```diff", trackedDiff, "```", "");
|
|
602
|
+
parts.push("## Tracked changes (`git diff HEAD --binary`)", "```diff", trackedDiff, "```", "");
|
|
587
603
|
}
|
|
604
|
+
// ST-1: per-file byte cap — skip/truncate larger files with a note to avoid OOM.
|
|
605
|
+
const MAX_FILE_BYTES = 256 * 1024;
|
|
588
606
|
for (const line of dirtyStatus.split("\n")) {
|
|
589
607
|
if (!line.startsWith("?? ")) continue;
|
|
590
608
|
// Strip the "?? " prefix and surrounding git quotes.
|
|
@@ -593,8 +611,27 @@ function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskState, w
|
|
|
593
611
|
try {
|
|
594
612
|
const abs = path.join(worktreePath, rel);
|
|
595
613
|
if (!fs.existsSync(abs) || fs.statSync(abs).isDirectory()) continue;
|
|
596
|
-
|
|
597
|
-
|
|
614
|
+
// ST-1: read raw bytes (not utf-8) so binary files are not corrupted.
|
|
615
|
+
const buf = fs.readFileSync(abs);
|
|
616
|
+
const originalSize = buf.byteLength;
|
|
617
|
+
let data: Buffer = buf;
|
|
618
|
+
let note = "";
|
|
619
|
+
if (originalSize > MAX_FILE_BYTES) {
|
|
620
|
+
data = buf.subarray(0, MAX_FILE_BYTES);
|
|
621
|
+
note = ` (truncated: ${originalSize} → ${MAX_FILE_BYTES} bytes)`;
|
|
622
|
+
}
|
|
623
|
+
// ST-1: detect non-UTF-8 via fatal TextDecoder; base64-encode binary files.
|
|
624
|
+
let isUtf8 = true;
|
|
625
|
+
try {
|
|
626
|
+
new TextDecoder("utf-8", { fatal: true }).decode(data);
|
|
627
|
+
} catch {
|
|
628
|
+
isUtf8 = false;
|
|
629
|
+
}
|
|
630
|
+
if (isUtf8) {
|
|
631
|
+
parts.push(`## Untracked file: ${rel}${note}`, "```", data.toString("utf-8"), "```", "");
|
|
632
|
+
} else {
|
|
633
|
+
parts.push(`## Untracked file: ${rel}${note} (base64-encoded binary)`, "```base64", data.toString("base64"), "```", "");
|
|
634
|
+
}
|
|
598
635
|
} catch {
|
|
599
636
|
/* skip unreadable/unstat-able entry */
|
|
600
637
|
}
|
|
@@ -729,8 +766,8 @@ export function prepareTaskWorkspace(manifest: TeamRunManifest, task: TeamTaskSt
|
|
|
729
766
|
if (currentBranch !== branch) {
|
|
730
767
|
throw new Error(`Existing worktree branch mismatch at ${worktreePath}: expected '${branch}', got '${currentBranch}'.`);
|
|
731
768
|
}
|
|
732
|
-
//
|
|
733
|
-
const dirtyStatus = git(worktreePath, ["status", "--porcelain"]);
|
|
769
|
+
// ST-1: use -uall so untracked directories are expanded to individual files.
|
|
770
|
+
const dirtyStatus = git(worktreePath, ["status", "--porcelain", "-uall"]);
|
|
734
771
|
if (dirtyStatus.trim()) {
|
|
735
772
|
// Snapshot uncommitted work to a recovery artifact BEFORE discarding, so the
|
|
736
773
|
// previous run's changes are never silently destroyed on reuse.
|
|
@@ -889,7 +926,8 @@ export async function prepareTaskWorkspaceAsync(
|
|
|
889
926
|
if (currentBranch !== branch) {
|
|
890
927
|
throw new Error(`Existing worktree branch mismatch at ${worktreePath}: expected '${branch}', got '${currentBranch}'.`);
|
|
891
928
|
}
|
|
892
|
-
|
|
929
|
+
// ST-1: use -uall so untracked directories are expanded to individual files.
|
|
930
|
+
const dirtyStatus = await gitAsync(worktreePath, ["status", "--porcelain", "-uall"]);
|
|
893
931
|
if (dirtyStatus.trim()) {
|
|
894
932
|
const snapshotOk = snapshotDirtyWorktree(manifest, task, worktreePath, dirtyStatus);
|
|
895
933
|
if (snapshotOk) {
|
|
@@ -1045,52 +1083,78 @@ export async function captureWorktreeDiffAsync(worktreePath: string): Promise<st
|
|
|
1045
1083
|
* assertCleanLeader, pruneStaleWorktrees, sanitizeBranchPart,
|
|
1046
1084
|
* linkNodeModulesIfPresent) but with a minimal, task-free signature.
|
|
1047
1085
|
*
|
|
1048
|
-
* Returns `undefined` when worktree
|
|
1049
|
-
*
|
|
1086
|
+
* Returns `undefined` when worktree isolation is genuinely unavailable (no git
|
|
1087
|
+
* repo, dirty leader) so the caller (`ctx.agent`) can fall back gracefully.
|
|
1088
|
+
* ST-15: when creation was attempted but failed (e.g. disk full), this THROWS
|
|
1089
|
+
* instead of returning undefined so the caller never silently runs in the
|
|
1090
|
+
* leader repo. The branch + path suffix is non-deterministic so a 2nd call for
|
|
1091
|
+
* the same agentId always produces a distinct worktree.
|
|
1050
1092
|
*/
|
|
1051
1093
|
export function prepareAgentWorktree(manifest: TeamRunManifest, agentId: string): PreparedTaskWorkspace | undefined {
|
|
1094
|
+
// ST-15: Split preconditions from worktree creation.
|
|
1095
|
+
// Precondition failures (no git repo, dirty leader) → return undefined so the
|
|
1096
|
+
// caller falls back gracefully (worktree isolation is genuinely unavailable).
|
|
1097
|
+
let repoRoot: string;
|
|
1098
|
+
let loadedConfig: ReturnType<typeof loadConfig>;
|
|
1052
1099
|
try {
|
|
1053
|
-
|
|
1054
|
-
|
|
1100
|
+
repoRoot = findGitRoot(manifest.cwd);
|
|
1101
|
+
loadedConfig = loadConfig(manifest.cwd);
|
|
1055
1102
|
if (loadedConfig.config.requireCleanWorktreeLeader !== false) assertCleanLeader(repoRoot);
|
|
1056
|
-
const sanitizedRunId = manifest.runId.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
1057
|
-
const worktreeRoot = path.join(projectCrewRoot(manifest.cwd), DEFAULT_PATHS.state.worktreesSubdir, sanitizedRunId);
|
|
1058
|
-
fs.mkdirSync(worktreeRoot, { recursive: true });
|
|
1059
|
-
const sanitizedAgentId = sanitizeBranchPart(agentId);
|
|
1060
|
-
const worktreePath = path.join(worktreeRoot, sanitizedAgentId);
|
|
1061
|
-
const branch = `pi-crew/${sanitizedRunId}/${sanitizedAgentId}`;
|
|
1062
|
-
pruneStaleWorktrees(repoRoot);
|
|
1063
|
-
git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
1064
|
-
const nodeModulesLinked =
|
|
1065
|
-
loadedConfig.config.worktree?.linkNodeModules === true ? linkNodeModulesIfPresent(repoRoot, worktreePath) : false;
|
|
1066
|
-
return { cwd: worktreePath, worktreePath, branch, nodeModulesLinked };
|
|
1067
1103
|
} catch {
|
|
1068
|
-
// Graceful fallback: no git repo
|
|
1104
|
+
// Graceful fallback: no git repo or dirty leader → run normally in cwd.
|
|
1069
1105
|
return undefined;
|
|
1070
1106
|
}
|
|
1107
|
+
|
|
1108
|
+
// ST-15: NON-DETERMINISTIC branch + path suffix. The OLD deterministic name
|
|
1109
|
+
// (`pi-crew/<runId>/<agentId>`) made a 2nd call for the same agent collide
|
|
1110
|
+
// ("already exists") → catch → returned undefined → the run silently fell
|
|
1111
|
+
// back to the leader repo (isolation lost, no error). The timestamp +
|
|
1112
|
+
// short-uuid suffix makes every call produce a distinct worktree.
|
|
1113
|
+
// Creation failures here THROW (not undefined) so callers never silently lose
|
|
1114
|
+
// isolation when worktree creation was requested but failed.
|
|
1115
|
+
const sanitizedRunId = manifest.runId.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
1116
|
+
const worktreeRoot = path.join(projectCrewRoot(manifest.cwd), DEFAULT_PATHS.state.worktreesSubdir, sanitizedRunId);
|
|
1117
|
+
fs.mkdirSync(worktreeRoot, { recursive: true });
|
|
1118
|
+
const sanitizedAgentId = sanitizeBranchPart(agentId);
|
|
1119
|
+
const stamp = `${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
1120
|
+
const worktreePath = path.join(worktreeRoot, `${sanitizedAgentId}-${stamp}`);
|
|
1121
|
+
const branch = `pi-crew/${sanitizedRunId}/${sanitizedAgentId}-${stamp}`;
|
|
1122
|
+
pruneStaleWorktrees(repoRoot);
|
|
1123
|
+
git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
1124
|
+
const nodeModulesLinked =
|
|
1125
|
+
loadedConfig.config.worktree?.linkNodeModules === true ? linkNodeModulesIfPresent(repoRoot, worktreePath) : false;
|
|
1126
|
+
return { cwd: worktreePath, worktreePath, branch, nodeModulesLinked };
|
|
1071
1127
|
}
|
|
1072
1128
|
|
|
1073
1129
|
/** Async version of prepareAgentWorktree — yields the event loop during git operations. */
|
|
1074
1130
|
export async function prepareAgentWorktreeAsync(manifest: TeamRunManifest, agentId: string): Promise<PreparedTaskWorkspace | undefined> {
|
|
1131
|
+
// ST-15: Split preconditions from worktree creation (see prepareAgentWorktree).
|
|
1132
|
+
let repoRoot: string;
|
|
1133
|
+
let loadedConfig: ReturnType<typeof loadConfig>;
|
|
1075
1134
|
try {
|
|
1076
|
-
|
|
1077
|
-
|
|
1135
|
+
repoRoot = await findGitRootAsync(manifest.cwd);
|
|
1136
|
+
loadedConfig = loadConfig(manifest.cwd);
|
|
1078
1137
|
if (loadedConfig.config.requireCleanWorktreeLeader !== false) await assertCleanLeaderAsync(repoRoot);
|
|
1079
|
-
const sanitizedRunId = manifest.runId.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
1080
|
-
const worktreeRoot = path.join(projectCrewRoot(manifest.cwd), DEFAULT_PATHS.state.worktreesSubdir, sanitizedRunId);
|
|
1081
|
-
fs.mkdirSync(worktreeRoot, { recursive: true });
|
|
1082
|
-
const sanitizedAgentId = sanitizeBranchPart(agentId);
|
|
1083
|
-
const worktreePath = path.join(worktreeRoot, sanitizedAgentId);
|
|
1084
|
-
const branch = `pi-crew/${sanitizedRunId}/${sanitizedAgentId}`;
|
|
1085
|
-
await pruneStaleWorktreesAsync(repoRoot);
|
|
1086
|
-
await gitAsync(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
1087
|
-
const nodeModulesLinked =
|
|
1088
|
-
loadedConfig.config.worktree?.linkNodeModules === true ? linkNodeModulesIfPresent(repoRoot, worktreePath) : false;
|
|
1089
|
-
return { cwd: worktreePath, worktreePath, branch, nodeModulesLinked };
|
|
1090
1138
|
} catch {
|
|
1091
|
-
// Graceful fallback: no git repo
|
|
1139
|
+
// Graceful fallback: no git repo or dirty leader → run normally in cwd.
|
|
1092
1140
|
return undefined;
|
|
1093
1141
|
}
|
|
1142
|
+
|
|
1143
|
+
// ST-15: NON-DETERMINISTIC branch + path suffix — a 2nd call for the same
|
|
1144
|
+
// agentId creates a DISTINCT worktree instead of colliding. Creation failures
|
|
1145
|
+
// here THROW so callers never silently fall back to the leader repo.
|
|
1146
|
+
const sanitizedRunId = manifest.runId.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
1147
|
+
const worktreeRoot = path.join(projectCrewRoot(manifest.cwd), DEFAULT_PATHS.state.worktreesSubdir, sanitizedRunId);
|
|
1148
|
+
fs.mkdirSync(worktreeRoot, { recursive: true });
|
|
1149
|
+
const sanitizedAgentId = sanitizeBranchPart(agentId);
|
|
1150
|
+
const stamp = `${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
1151
|
+
const worktreePath = path.join(worktreeRoot, `${sanitizedAgentId}-${stamp}`);
|
|
1152
|
+
const branch = `pi-crew/${sanitizedRunId}/${sanitizedAgentId}-${stamp}`;
|
|
1153
|
+
await pruneStaleWorktreesAsync(repoRoot);
|
|
1154
|
+
await gitAsync(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
1155
|
+
const nodeModulesLinked =
|
|
1156
|
+
loadedConfig.config.worktree?.linkNodeModules === true ? linkNodeModulesIfPresent(repoRoot, worktreePath) : false;
|
|
1157
|
+
return { cwd: worktreePath, worktreePath, branch, nodeModulesLinked };
|
|
1094
1158
|
}
|
|
1095
1159
|
|
|
1096
1160
|
/**
|
|
@@ -6,7 +6,7 @@ topology: dynamic
|
|
|
6
6
|
|
|
7
7
|
# Chain Workflow - Sequential execution with context passing
|
|
8
8
|
|
|
9
|
-
**Source:**
|
|
9
|
+
**Source:** pi-boomerang integration plan
|
|
10
10
|
**Syntax:** `step1 -> step2 -> step3`
|
|
11
11
|
**Version:** 1.0.0
|
|
12
12
|
|
|
@@ -255,4 +255,4 @@ pi team run --chain "@step1 -> @step2" --continue-on-error
|
|
|
255
255
|
|
|
256
256
|
---
|
|
257
257
|
|
|
258
|
-
*Generated from pi-boomerang integration plan
|
|
258
|
+
*Generated from pi-boomerang integration plan.*
|