pi-crew 0.9.56 → 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 +29 -0
- package/README.md +53 -5
- package/dist/index.mjs +5350 -4350
- package/docs/README.md +54 -0
- package/package.json +12 -12
- 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 +108 -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 +24 -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 +11 -5
- 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 +1 -1
- 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} +11 -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} +28 -72
- package/src/runtime/{compact-stages → compaction/compact-stages}/bounded-tail.ts +1 -1
- 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/custom-tools/irc-tool.ts +2 -2
- package/src/runtime/delivery-coordinator.ts +0 -3
- package/src/runtime/diagnostic-export.ts +3 -3
- 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 +1 -1
- 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} +1 -1
- 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/{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 +1 -1
- package/src/runtime/task-output-context.ts +7 -5
- package/src/runtime/task-runner/child-executor.ts +62 -22
- 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 +380 -139
- 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 +133 -177
- 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} +161 -31
- package/src/state/{event-log.ts → event-log/event-log.ts} +38 -26
- 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} +236 -17
- 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/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 +88 -39
- package/workflows/chain.workflow.md +2 -2
- 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/smoke-team-perf.ts +0 -43
- 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/{semaphore.ts → scheduling/semaphore.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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import { logInternalError } from "
|
|
3
|
-
import { redactJsonLine } from "
|
|
2
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
3
|
+
import { redactJsonLine } from "../../utils/redaction.ts";
|
|
4
4
|
|
|
5
5
|
export interface DrainableSource {
|
|
6
6
|
pause(): void;
|
|
@@ -35,8 +35,12 @@ export interface JsonlWriter {
|
|
|
35
35
|
export function createJsonlWriter(filePath: string | undefined, source: DrainableSource, deps: JsonlWriterDeps = {}): JsonlWriter {
|
|
36
36
|
if (!filePath) {
|
|
37
37
|
return {
|
|
38
|
-
writeLine() {
|
|
39
|
-
|
|
38
|
+
writeLine() {
|
|
39
|
+
/* no-op: null writer */
|
|
40
|
+
},
|
|
41
|
+
async close() {
|
|
42
|
+
/* no-op: null writer */
|
|
43
|
+
},
|
|
40
44
|
};
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -46,8 +50,12 @@ export function createJsonlWriter(filePath: string | undefined, source: Drainabl
|
|
|
46
50
|
stream = createWriteStream(filePath);
|
|
47
51
|
} catch {
|
|
48
52
|
return {
|
|
49
|
-
writeLine() {
|
|
50
|
-
|
|
53
|
+
writeLine() {
|
|
54
|
+
/* no-op: stream creation failed */
|
|
55
|
+
},
|
|
56
|
+
async close() {
|
|
57
|
+
/* no-op: stream creation failed */
|
|
58
|
+
},
|
|
51
59
|
};
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
import { crewHooks } from "../runtime/crew-hooks.ts";
|
|
7
7
|
|
|
8
8
|
// Lazy-initialized store and paths
|
|
9
|
-
let storeInstance: import("./instinct-store.js").InstinctStore | null = null;
|
|
9
|
+
let storeInstance: import("./stores/instinct-store.js").InstinctStore | null = null;
|
|
10
10
|
let pathsInstance: typeof import("../utils/paths.js") | null = null;
|
|
11
11
|
|
|
12
12
|
async function getStore() {
|
|
13
13
|
if (!storeInstance) {
|
|
14
14
|
// LAZY: defer dynamic import of ./instinct-store.js to its call site.
|
|
15
|
-
const { InstinctStore } = await import("./instinct-store.js");
|
|
15
|
+
const { InstinctStore } = await import("./stores/instinct-store.js");
|
|
16
16
|
// LAZY: defer dynamic import of ../utils/paths.js to its call site.
|
|
17
17
|
const paths = await import("../utils/paths.js");
|
|
18
18
|
storeInstance = new InstinctStore(paths.projectCrewRoot(process.cwd()));
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { DEFAULT_CACHE, DEFAULT_PATHS } from "
|
|
5
|
-
import { userCrewRoot } from "
|
|
6
|
-
import { isSafePathId } from "
|
|
7
|
-
import { sharedScanCache } from "
|
|
8
|
-
import { sleepSync } from "
|
|
9
|
-
import { atomicWriteJson, isSymlinkSafePath, renameWithRetry } from "
|
|
10
|
-
import type { TeamRunManifest } from "
|
|
4
|
+
import { DEFAULT_CACHE, DEFAULT_PATHS } from "../../config/defaults.ts";
|
|
5
|
+
import { userCrewRoot } from "../../utils/paths.ts";
|
|
6
|
+
import { isSafePathId } from "../../utils/safe-paths.ts";
|
|
7
|
+
import { sharedScanCache } from "../../utils/scan-cache.ts";
|
|
8
|
+
import { sleepSync } from "../../utils/sleep.ts";
|
|
9
|
+
import { atomicWriteJson, isSymlinkSafePath, renameWithRetry } from "../atomic-write.ts";
|
|
10
|
+
import type { TeamRunManifest } from "../types.ts";
|
|
11
11
|
|
|
12
12
|
/** Magic bytes prefix for binary registry to prevent deserialization of hostile files. */
|
|
13
13
|
const BINARY_MAGIC = Buffer.from("PICREW2BIN", "utf-8");
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { redactSecretString, redactSecrets } from "
|
|
5
|
-
import { resolveRealContainedPath } from "
|
|
6
|
-
import { atomicWriteFile } from "
|
|
7
|
-
import type { ArtifactDescriptor } from "
|
|
4
|
+
import { redactSecretString, redactSecrets } from "../../utils/redaction.ts";
|
|
5
|
+
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
6
|
+
import { atomicWriteFile } from "../atomic-write.ts";
|
|
7
|
+
import type { ArtifactDescriptor } from "../types.ts";
|
|
8
8
|
|
|
9
9
|
function hashContent(content: string): string {
|
|
10
10
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -101,6 +101,64 @@ export function cleanupOldArtifacts(artifactsRoot: string, options: ArtifactClea
|
|
|
101
101
|
if (!didCleanup) writeCleanupMarker(artifactsRoot, markerFile);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* ST-10: Default TTL for "temporary" retention artifacts (1 hour).
|
|
106
|
+
* Temporary artifacts (progress, notepad) are short-lived and should not
|
|
107
|
+
* accumulate. One hour balances cleanup aggressiveness with the possibility
|
|
108
|
+
* that a long-running task writes periodic progress updates.
|
|
109
|
+
*/
|
|
110
|
+
export const TEMPORARY_RETENTION_TTL_MS = 60 * 60 * 1000;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* ST-10: Check whether an artifact descriptor indicates the file should be
|
|
114
|
+
* pruned (expired).
|
|
115
|
+
*
|
|
116
|
+
* Enforces:
|
|
117
|
+
* - `expiresAt`: if set and now ≥ expiresAt → expired.
|
|
118
|
+
* - `retention: "temporary"`: if createdAt age ≥ temporaryTtlMs → expired.
|
|
119
|
+
* ("run" and "project" retentions are handled by run-level prune and the
|
|
120
|
+
* age-based {@link cleanupOldArtifacts}, not by this per-descriptor check.)
|
|
121
|
+
*/
|
|
122
|
+
export function isArtifactExpired(desc: ArtifactDescriptor, nowMs: number, temporaryTtlMs: number = TEMPORARY_RETENTION_TTL_MS): boolean {
|
|
123
|
+
// Explicit expiresAt takes precedence.
|
|
124
|
+
if (desc.expiresAt) {
|
|
125
|
+
const expiryMs = Date.parse(desc.expiresAt);
|
|
126
|
+
if (Number.isFinite(expiryMs) && nowMs >= expiryMs) return true;
|
|
127
|
+
}
|
|
128
|
+
// "temporary" retention has a short TTL.
|
|
129
|
+
if (desc.retention === "temporary" && desc.createdAt) {
|
|
130
|
+
const createdMs = Date.parse(desc.createdAt);
|
|
131
|
+
if (Number.isFinite(createdMs) && nowMs - createdMs >= temporaryTtlMs) return true;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* ST-10: Prune expired artifacts by enforcing ArtifactDescriptor.retention/expiresAt.
|
|
138
|
+
*
|
|
139
|
+
* Deletes artifact files whose descriptor indicates expiry (expiresAt passed
|
|
140
|
+
* or temporary-retention TTL exceeded). Returns the count of files deleted.
|
|
141
|
+
*
|
|
142
|
+
* Unlike {@link cleanupOldArtifacts} (which is filesystem age-based on raw
|
|
143
|
+
* files), this operates on artifact *metadata* from the manifest's `artifacts[]`
|
|
144
|
+
* and deletes specific expired files via their descriptor `path`.
|
|
145
|
+
*/
|
|
146
|
+
export function pruneExpiredArtifacts(descriptors: ArtifactDescriptor[], options?: { now?: number; temporaryTtlMs?: number }): number {
|
|
147
|
+
const nowMs = options?.now ?? Date.now();
|
|
148
|
+
const temporaryTtlMs = options?.temporaryTtlMs ?? TEMPORARY_RETENTION_TTL_MS;
|
|
149
|
+
let deleted = 0;
|
|
150
|
+
for (const desc of descriptors) {
|
|
151
|
+
if (!isArtifactExpired(desc, nowMs, temporaryTtlMs)) continue;
|
|
152
|
+
try {
|
|
153
|
+
fs.unlinkSync(desc.path);
|
|
154
|
+
deleted++;
|
|
155
|
+
} catch {
|
|
156
|
+
// Best-effort: file may already be removed or path may be stale.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return deleted;
|
|
160
|
+
}
|
|
161
|
+
|
|
104
162
|
function resolveInside(baseDir: string, relativePath: string): string {
|
|
105
163
|
// Check if baseDir is a symlink on every call to prevent symlink attacks
|
|
106
164
|
try {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { resolveRealContainedPath } from "
|
|
5
|
-
import { atomicWriteFile, renameWithRetry } from "
|
|
6
|
-
import { withFileLockSync } from "
|
|
4
|
+
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
5
|
+
import { atomicWriteFile, renameWithRetry } from "../atomic-write.ts";
|
|
6
|
+
import { withFileLockSync } from "../coordination/locks.ts";
|
|
7
7
|
|
|
8
8
|
const SHA256_HEX = /^[a-f0-9]{64}$/i;
|
|
9
9
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ManifestSummary } from "
|
|
4
|
-
import { computeRunHealth } from "
|
|
5
|
-
import { atomicWriteFile } from "
|
|
3
|
+
import type { ManifestSummary } from "../../runtime/task-health.ts";
|
|
4
|
+
import { computeRunHealth } from "../../runtime/task-health.ts";
|
|
5
|
+
import { atomicWriteFile } from "../atomic-write.ts";
|
|
6
6
|
|
|
7
7
|
// Relative to the crew root (`<cwd>/.crew`). BUG A fix (pts/2 hang
|
|
8
8
|
// investigation 2026-06-16): this was `.crew/state/health`, which double-joined
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { assertSafePathId } from "
|
|
5
|
-
import { atomicWriteFile } from "
|
|
4
|
+
import { assertSafePathId } from "../../utils/safe-paths.ts";
|
|
5
|
+
import { atomicWriteFile } from "../atomic-write.ts";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Represents a learned instinct that guides agent behavior.
|
|
@@ -272,4 +272,4 @@ export class InstinctStore {
|
|
|
272
272
|
export {
|
|
273
273
|
getGlobalStorageDir,
|
|
274
274
|
getProjectStorageDir,
|
|
275
|
-
} from "
|
|
275
|
+
} from "../../utils/project-detector.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { logInternalError } from "
|
|
3
|
-
import { atomicWriteJson } from "
|
|
2
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
3
|
+
import { atomicWriteJson } from "../atomic-write.ts";
|
|
4
4
|
|
|
5
5
|
// ── Types ────────────────────────────────────────────────────────────────
|
|
6
6
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { projectCrewRoot } from "
|
|
5
|
-
import { atomicWriteJson } from "
|
|
6
|
-
import { withFileLockSync } from "
|
|
7
|
-
import type { TeamTaskState } from "
|
|
4
|
+
import { projectCrewRoot } from "../../utils/paths.ts";
|
|
5
|
+
import { atomicWriteJson } from "../atomic-write.ts";
|
|
6
|
+
import { withFileLockSync } from "../coordination/locks.ts";
|
|
7
|
+
import type { TeamTaskState } from "../types.ts";
|
|
8
8
|
|
|
9
9
|
const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
10
10
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { projectCrewRoot } from "
|
|
4
|
-
import { assertSafePathId } from "
|
|
5
|
-
import { atomicWriteFile } from "
|
|
6
|
-
import type { TeamRunManifest, TeamTaskState } from "
|
|
3
|
+
import { projectCrewRoot } from "../../utils/paths.ts";
|
|
4
|
+
import { assertSafePathId } from "../../utils/safe-paths.ts";
|
|
5
|
+
import { atomicWriteFile } from "../atomic-write.ts";
|
|
6
|
+
import type { TeamRunManifest, TeamTaskState } from "../types.ts";
|
|
7
7
|
|
|
8
8
|
export interface RunGraphNode {
|
|
9
9
|
id: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { projectCrewRoot } from "
|
|
4
|
-
import { assertSafePathId } from "
|
|
5
|
-
import { atomicWriteJson, readJsonFile } from "
|
|
3
|
+
import { projectCrewRoot } from "../../utils/paths.ts";
|
|
4
|
+
import { assertSafePathId } from "../../utils/safe-paths.ts";
|
|
5
|
+
import { atomicWriteJson, readJsonFile } from "../atomic-write.ts";
|
|
6
6
|
import { loadRunManifestById } from "./state-store.ts";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as fsp from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { DEFAULT_CACHE, DEFAULT_PATHS } from "
|
|
5
|
-
import { errors } from "
|
|
6
|
-
import type { TeamConfig } from "
|
|
7
|
-
import { createRunId, createTaskId } from "
|
|
8
|
-
import { logInternalError } from "
|
|
9
|
-
import { findRepoRoot, projectCrewRoot, userCrewRoot } from "
|
|
10
|
-
import { assertSafePathId, resolveContainedRelativePath, resolveRealContainedPath } from "
|
|
11
|
-
import { toPiSessionId } from "
|
|
12
|
-
import type { WorkflowConfig } from "
|
|
4
|
+
import { DEFAULT_CACHE, DEFAULT_PATHS } from "../../config/defaults.ts";
|
|
5
|
+
import { errors } from "../../errors.ts";
|
|
6
|
+
import type { TeamConfig } from "../../teams/team-config.ts";
|
|
7
|
+
import { createRunId, createTaskId } from "../../utils/ids.ts";
|
|
8
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
9
|
+
import { findRepoRoot, projectCrewRoot, userCrewRoot } from "../../utils/paths.ts";
|
|
10
|
+
import { assertSafePathId, resolveContainedRelativePath, resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
11
|
+
import { toPiSessionId } from "../../utils/session-utils.ts";
|
|
12
|
+
import type { WorkflowConfig } from "../../workflows/workflow-config.ts";
|
|
13
|
+
import {
|
|
14
|
+
atomicWriteJson,
|
|
15
|
+
atomicWriteJsonAsync,
|
|
16
|
+
atomicWriteJsonCoalesced,
|
|
17
|
+
flushPendingAtomicWrites,
|
|
18
|
+
readJsonFile,
|
|
19
|
+
} from "../atomic-write.ts";
|
|
20
|
+
import { canTransitionRunStatus, isTeamTaskStatus } from "../contracts.ts";
|
|
21
|
+
import { withRunLock, withRunLockSync } from "../coordination/locks.ts";
|
|
22
|
+
import { appendEvent } from "../event-log/event-log.ts";
|
|
23
|
+
import { reconstructTasksFromEvents } from "../event-log/event-reconstructor.ts";
|
|
24
|
+
import type { TeamRunManifest, TeamTaskState } from "../types.ts";
|
|
25
|
+
import { CURRENT_SCHEMA_VERSION, CURRENT_TASKS_SCHEMA_VERSION } from "../types.ts";
|
|
13
26
|
import { unregisterActiveRun } from "./active-run-registry.ts";
|
|
14
|
-
import { atomicWriteJson, atomicWriteJsonAsync, atomicWriteJsonCoalesced, flushPendingAtomicWrites, readJsonFile } from "./atomic-write.ts";
|
|
15
|
-
import { canTransitionRunStatus } from "./contracts.ts";
|
|
16
|
-
import { appendEvent } from "./event-log.ts";
|
|
17
|
-
import { withRunLock, withRunLockSync } from "./locks.ts";
|
|
18
|
-
import type { TeamRunManifest, TeamTaskState } from "./types.ts";
|
|
19
|
-
import { CURRENT_SCHEMA_VERSION } from "./types.ts";
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* stat() the manifest with a brief retry on Windows for the AV-scan window.
|
|
@@ -424,7 +431,32 @@ export async function saveRunManifestAsync(manifest: TeamRunManifest): Promise<v
|
|
|
424
431
|
});
|
|
425
432
|
}
|
|
426
433
|
|
|
434
|
+
/**
|
|
435
|
+
* ST-4: Defense-in-depth guard — refuse to persist an empty tasks array over
|
|
436
|
+
* a previously non-empty tasks file. Prevents the cascade where a corrupt
|
|
437
|
+
* tasks.json (→ [] on load via readJsonFile catching SyntaxError) is then
|
|
438
|
+
* persisted as [], permanently destroying the data. If the on-disk file has
|
|
439
|
+
* tasks and the incoming array is empty, we log a warning and refuse.
|
|
440
|
+
*
|
|
441
|
+
* @returns true if the write should proceed, false if it was refused.
|
|
442
|
+
*/
|
|
443
|
+
function shouldPersistTasks(manifest: TeamRunManifest, tasks: TeamTaskState[]): boolean {
|
|
444
|
+
if (tasks.length > 0) return true;
|
|
445
|
+
const existing = extractTaskArray(readJsonFile<unknown>(manifest.tasksPath));
|
|
446
|
+
if (existing.length > 0) {
|
|
447
|
+
console.warn(
|
|
448
|
+
`[state-store] refusing to persist empty tasks over ${existing.length} existing task(s) — possible corrupt-load cascade (runId=${manifest.runId})`,
|
|
449
|
+
);
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
|
|
427
455
|
export function saveRunTasks(manifest: TeamRunManifest, tasks: TeamTaskState[]): void {
|
|
456
|
+
// ST-4: refuse to persist [] over a previously-non-empty tasks file.
|
|
457
|
+
// Prevents the cascade where a corrupt tasks.json (→ [] on load) is then
|
|
458
|
+
// persisted as [], permanently destroying the data.
|
|
459
|
+
if (!shouldPersistTasks(manifest, tasks)) return;
|
|
428
460
|
// FIX: Invalidate cache BEFORE atomic write to prevent stale cache serving.
|
|
429
461
|
invalidateRunCache(manifest.stateRoot);
|
|
430
462
|
|
|
@@ -501,6 +533,8 @@ export function saveRunTasks(manifest: TeamRunManifest, tasks: TeamTaskState[]):
|
|
|
501
533
|
*/
|
|
502
534
|
/** @internal */
|
|
503
535
|
export function saveRunTasksCoalesced(manifest: TeamRunManifest, tasks: TeamTaskState[], skipCoalesce: boolean = false): void {
|
|
536
|
+
// ST-4: refuse to persist [] over a previously-non-empty tasks file.
|
|
537
|
+
if (!shouldPersistTasks(manifest, tasks)) return;
|
|
504
538
|
// FIX: Invalidate cache BEFORE atomic write to prevent stale cache serving.
|
|
505
539
|
invalidateRunCache(manifest.stateRoot);
|
|
506
540
|
try {
|
|
@@ -512,6 +546,8 @@ export function saveRunTasksCoalesced(manifest: TeamRunManifest, tasks: TeamTask
|
|
|
512
546
|
}
|
|
513
547
|
|
|
514
548
|
export async function saveRunTasksAsync(manifest: TeamRunManifest, tasks: TeamTaskState[]): Promise<void> {
|
|
549
|
+
// ST-4: refuse to persist [] over a previously-non-empty tasks file.
|
|
550
|
+
if (!shouldPersistTasks(manifest, tasks)) return;
|
|
515
551
|
// FIX: Invalidate cache BEFORE atomic write to prevent stale cache serving.
|
|
516
552
|
invalidateRunCache(manifest.stateRoot);
|
|
517
553
|
try {
|
|
@@ -733,6 +769,185 @@ async function readJsonFileAsync<T>(filePath: string): Promise<T | undefined> {
|
|
|
733
769
|
}
|
|
734
770
|
}
|
|
735
771
|
|
|
772
|
+
/**
|
|
773
|
+
* ST-4: Rename a corrupt file to a quarantine path (`.corrupt-<ts>`) so it is
|
|
774
|
+
* preserved for debugging but no longer read as the primary source of truth.
|
|
775
|
+
*/
|
|
776
|
+
function quarantineCorruptFile(filePath: string): void {
|
|
777
|
+
try {
|
|
778
|
+
fs.renameSync(filePath, `${filePath}.corrupt-${Date.now()}`);
|
|
779
|
+
} catch {
|
|
780
|
+
// Best-effort — if rename fails (file already gone, permission, etc.),
|
|
781
|
+
// we still proceed with reconstruction from the event log.
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* ST-4: Convert event-reconstructor output into TeamTaskState[].
|
|
787
|
+
* Reconstructed tasks carry lifecycle data (id, status, timing) from the
|
|
788
|
+
* event log; auxiliary fields (role, agent, title) are filled with defaults
|
|
789
|
+
* since they are not present in lifecycle events.
|
|
790
|
+
*/
|
|
791
|
+
function reconstructTasksFromEventLog(eventsPath: string, runId: string): TeamTaskState[] {
|
|
792
|
+
try {
|
|
793
|
+
const result = reconstructTasksFromEvents(eventsPath);
|
|
794
|
+
const tasks: TeamTaskState[] = [];
|
|
795
|
+
for (const [, rt] of result.tasks) {
|
|
796
|
+
tasks.push({
|
|
797
|
+
id: rt.id,
|
|
798
|
+
runId,
|
|
799
|
+
role: "unknown",
|
|
800
|
+
agent: "unknown",
|
|
801
|
+
title: "reconstructed from events",
|
|
802
|
+
status: isTeamTaskStatus(rt.status) ? rt.status : "queued",
|
|
803
|
+
dependsOn: [],
|
|
804
|
+
cwd: "",
|
|
805
|
+
startedAt: rt.startedAt,
|
|
806
|
+
finishedAt: rt.finishedAt,
|
|
807
|
+
error: rt.error,
|
|
808
|
+
segment: rt.segment,
|
|
809
|
+
diagnostics: rt.diagnostics,
|
|
810
|
+
metrics: rt.metrics,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
return tasks;
|
|
814
|
+
} catch {
|
|
815
|
+
return [];
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* ST-4: Load tasks.json with corruption recovery (sync path).
|
|
821
|
+
*
|
|
822
|
+
* Distinguishes:
|
|
823
|
+
* - ENOENT / ENOTDIR → legitimate empty → [] (NOT quarantined).
|
|
824
|
+
* - SyntaxError (parse failure) → corrupt → quarantine `.corrupt-<ts>` AND
|
|
825
|
+
* reconstruct from events.jsonl. If reconstruction yields tasks, persist
|
|
826
|
+
* them so subsequent loads see a valid file.
|
|
827
|
+
* - Non-array JSON (e.g. `{}`) → corrupt → same as SyntaxError.
|
|
828
|
+
* - Valid array → return as-is.
|
|
829
|
+
*/
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* ST-9: Extract the task array from a tasks.json payload.
|
|
833
|
+
*
|
|
834
|
+
* Accepts both the v0 legacy bare-array format and the v1+ envelope
|
|
835
|
+
* `{ schemaVersion, tasks }`. Returns [] for unrecognized shapes.
|
|
836
|
+
*/
|
|
837
|
+
function extractTaskArray(raw: unknown): TeamTaskState[] {
|
|
838
|
+
if (Array.isArray(raw)) return raw as TeamTaskState[];
|
|
839
|
+
if (raw !== null && typeof raw === "object" && "tasks" in raw) {
|
|
840
|
+
const envelope = raw as { tasks?: unknown };
|
|
841
|
+
if (Array.isArray(envelope.tasks)) return envelope.tasks as TeamTaskState[];
|
|
842
|
+
}
|
|
843
|
+
return [];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* ST-9: Whether `parsed` is a recognizable tasks.json shape (v0 bare array
|
|
848
|
+
* or v1+ envelope). Used to distinguish legitimate formats from corruption.
|
|
849
|
+
*/
|
|
850
|
+
function isRecognizableTasksPayload(parsed: unknown): boolean {
|
|
851
|
+
if (Array.isArray(parsed)) return true;
|
|
852
|
+
if (parsed !== null && typeof parsed === "object" && "tasks" in parsed) {
|
|
853
|
+
return Array.isArray((parsed as { tasks?: unknown }).tasks);
|
|
854
|
+
}
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* ST-9: Version-check + migration hook for tasks.json.
|
|
860
|
+
*
|
|
861
|
+
* tasks.json has two on-disk shapes:
|
|
862
|
+
* - v0 (current): bare JSON array `TeamTaskState[]` — what saveRunTasks*
|
|
863
|
+
* write today (backward-compatible; no schemaVersion envelope).
|
|
864
|
+
* - v1+ (future): envelope `{ schemaVersion: number, tasks: TeamTaskState[] }`
|
|
865
|
+
* — read-supported defensively for a future write-side switch.
|
|
866
|
+
*
|
|
867
|
+
* This detects the shape and returns the task array. v0 needs NO migration
|
|
868
|
+
* (it IS the current write format). For v1+ envelopes, a schemaVersion
|
|
869
|
+
* mismatch warns (mirroring the manifest check). Future breaking changes
|
|
870
|
+
* add real migration logic here.
|
|
871
|
+
*/
|
|
872
|
+
function migrateTasksFile(parsed: unknown, runId: string): TeamTaskState[] {
|
|
873
|
+
// v0 current: bare array (no schemaVersion envelope) — what writers produce.
|
|
874
|
+
if (Array.isArray(parsed)) {
|
|
875
|
+
// v0 bare array is the CURRENT write format (saveRunTasks* write the
|
|
876
|
+
// array directly, by design — backward compat). Nothing to migrate:
|
|
877
|
+
// return as-is. (v1+ envelope read-support below is defensive, for a
|
|
878
|
+
// future write-side switch.) Do NOT warn here — it would fire for 100%
|
|
879
|
+
// of runs on every load and flood the UI on startup.
|
|
880
|
+
return parsed as TeamTaskState[];
|
|
881
|
+
}
|
|
882
|
+
// v1+ envelope: { schemaVersion, tasks }.
|
|
883
|
+
if (parsed !== null && typeof parsed === "object" && "tasks" in parsed) {
|
|
884
|
+
const envelope = parsed as { schemaVersion?: unknown; tasks?: unknown };
|
|
885
|
+
const detected = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : 0;
|
|
886
|
+
if (detected !== CURRENT_TASKS_SCHEMA_VERSION) {
|
|
887
|
+
console.warn(
|
|
888
|
+
`[state-store] tasks.json schemaVersion mismatch: expected ${CURRENT_TASKS_SCHEMA_VERSION}, got ${detected}. Run ${runId} may be incompatible.`,
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return extractTaskArray(parsed);
|
|
893
|
+
}
|
|
894
|
+
function loadTasksWithRecovery(tasksPath: string, eventsPath: string, runId: string): TeamTaskState[] {
|
|
895
|
+
let content: string;
|
|
896
|
+
try {
|
|
897
|
+
content = fs.readFileSync(tasksPath, "utf-8");
|
|
898
|
+
} catch {
|
|
899
|
+
// ENOENT / ENOTDIR / other read errors → empty (retry loop handles
|
|
900
|
+
// transient instability; ENOENT is a legitimate empty run).
|
|
901
|
+
return [];
|
|
902
|
+
}
|
|
903
|
+
let parsed: unknown;
|
|
904
|
+
try {
|
|
905
|
+
parsed = JSON.parse(content);
|
|
906
|
+
} catch {
|
|
907
|
+
// SyntaxError — corrupt file.
|
|
908
|
+
quarantineCorruptFile(tasksPath);
|
|
909
|
+
const reconstructed = reconstructTasksFromEventLog(eventsPath, runId);
|
|
910
|
+
if (reconstructed.length > 0) atomicWriteJson(tasksPath, reconstructed);
|
|
911
|
+
return reconstructed;
|
|
912
|
+
}
|
|
913
|
+
if (!isRecognizableTasksPayload(parsed)) {
|
|
914
|
+
// Neither v0 bare array nor v1+ envelope (e.g. `{}`) — corrupt.
|
|
915
|
+
quarantineCorruptFile(tasksPath);
|
|
916
|
+
const reconstructed = reconstructTasksFromEventLog(eventsPath, runId);
|
|
917
|
+
if (reconstructed.length > 0) atomicWriteJson(tasksPath, reconstructed);
|
|
918
|
+
return reconstructed;
|
|
919
|
+
}
|
|
920
|
+
return migrateTasksFile(parsed, runId);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* ST-4: async twin of {@link loadTasksWithRecovery}.
|
|
925
|
+
*/
|
|
926
|
+
async function loadTasksWithRecoveryAsync(tasksPath: string, eventsPath: string, runId: string): Promise<TeamTaskState[]> {
|
|
927
|
+
let content: string;
|
|
928
|
+
try {
|
|
929
|
+
content = await fs.promises.readFile(tasksPath, "utf-8");
|
|
930
|
+
} catch {
|
|
931
|
+
return [];
|
|
932
|
+
}
|
|
933
|
+
let parsed: unknown;
|
|
934
|
+
try {
|
|
935
|
+
parsed = JSON.parse(content);
|
|
936
|
+
} catch {
|
|
937
|
+
quarantineCorruptFile(tasksPath);
|
|
938
|
+
const reconstructed = reconstructTasksFromEventLog(eventsPath, runId);
|
|
939
|
+
if (reconstructed.length > 0) await atomicWriteJsonAsync(tasksPath, reconstructed);
|
|
940
|
+
return reconstructed;
|
|
941
|
+
}
|
|
942
|
+
if (!isRecognizableTasksPayload(parsed)) {
|
|
943
|
+
quarantineCorruptFile(tasksPath);
|
|
944
|
+
const reconstructed = reconstructTasksFromEventLog(eventsPath, runId);
|
|
945
|
+
if (reconstructed.length > 0) await atomicWriteJsonAsync(tasksPath, reconstructed);
|
|
946
|
+
return reconstructed;
|
|
947
|
+
}
|
|
948
|
+
return migrateTasksFile(parsed, runId);
|
|
949
|
+
}
|
|
950
|
+
|
|
736
951
|
/**
|
|
737
952
|
* Load a run manifest and its tasks by runId.
|
|
738
953
|
* WARNING: This function provides best-effort consistency only. The sentinel-based
|
|
@@ -813,7 +1028,7 @@ export function loadRunManifestById(cwd: string, runId: string): { manifest: Tea
|
|
|
813
1028
|
const freshStat = fs.statSync(manifestPath);
|
|
814
1029
|
manifest = readJsonFile<TeamRunManifest>(manifestPath);
|
|
815
1030
|
const freshTasksStat = fs.existsSync(tasksPath) ? fs.statSync(tasksPath) : undefined;
|
|
816
|
-
tasks =
|
|
1031
|
+
tasks = loadTasksWithRecovery(tasksPath, manifest?.eventsPath ?? path.join(stateRoot, "events.jsonl"), manifest?.runId ?? runId);
|
|
817
1032
|
// If size/mtime didn't change between stat and read, we're consistent.
|
|
818
1033
|
if (
|
|
819
1034
|
freshStat.mtimeMs === manifestStat.mtimeMs &&
|
|
@@ -933,7 +1148,11 @@ export async function loadRunManifestByIdAsync(
|
|
|
933
1148
|
const freshStat = await fs.promises.stat(manifestPath);
|
|
934
1149
|
manifest = await readJsonFileAsync<TeamRunManifest>(manifestPath);
|
|
935
1150
|
const freshTasksStat = await fs.promises.stat(tasksPath).catch(() => undefined);
|
|
936
|
-
tasks =
|
|
1151
|
+
tasks = await loadTasksWithRecoveryAsync(
|
|
1152
|
+
tasksPath,
|
|
1153
|
+
manifest?.eventsPath ?? path.join(stateRoot, "events.jsonl"),
|
|
1154
|
+
manifest?.runId ?? runId,
|
|
1155
|
+
);
|
|
937
1156
|
// If size/mtime didn't change between stat and read, we're consistent.
|
|
938
1157
|
if (
|
|
939
1158
|
freshStat.mtimeMs === manifestStat.mtimeMs &&
|
package/src/state/types.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { CrashClass } from "../runtime/crash-classification.ts";
|
|
2
1
|
import type { CrewAgentProgress } from "../runtime/crew-agent-runtime.ts";
|
|
3
|
-
import type { WorkerHeartbeatState } from "../runtime/worker-heartbeat.ts";
|
|
2
|
+
import type { WorkerHeartbeatState } from "../runtime/heartbeat/worker-heartbeat.ts";
|
|
3
|
+
import type { CrashClass } from "../runtime/recovery/crash-classification.ts";
|
|
4
4
|
import type { TeamRunStatus, TeamTaskStatus } from "./contracts.ts";
|
|
5
|
+
import type { TaskClaimState } from "./coordination/task-claims.ts";
|
|
5
6
|
import type { CoherenceMark, RolloutEntry } from "./decision-ledger.ts";
|
|
6
|
-
import type { TaskClaimState } from "./task-claims.ts";
|
|
7
7
|
|
|
8
8
|
export type { TeamRunStatus, TeamTaskStatus } from "./contracts.ts";
|
|
9
9
|
export type { CoherenceMark, CrewAgentProgress, RolloutEntry };
|
|
@@ -183,6 +183,30 @@ export interface CrewAttentionEventData {
|
|
|
183
183
|
*/
|
|
184
184
|
export const CURRENT_SCHEMA_VERSION = 1 as const;
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* ST-9: schemaVersion sentinel for tasks.json (and future goal-state/mailbox
|
|
188
|
+
* files). Mirrors {@link CURRENT_SCHEMA_VERSION} for the manifest.
|
|
189
|
+
*
|
|
190
|
+
* tasks.json has two on-disk shapes:
|
|
191
|
+
* - v0 (legacy): bare JSON array `TeamTaskState[]` — no schemaVersion.
|
|
192
|
+
* - v1+ (current): envelope `{ schemaVersion, tasks }` — see {@link TasksFileData}.
|
|
193
|
+
*
|
|
194
|
+
* The reader detects the shape, warns on version mismatch, and applies
|
|
195
|
+
* migration logic in state-store.ts (the structured migration hook).
|
|
196
|
+
*/
|
|
197
|
+
export const CURRENT_TASKS_SCHEMA_VERSION = 1 as const;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* ST-9: Versioned envelope for tasks.json on disk.
|
|
201
|
+
* Writers produce this shape; readers accept it OR the legacy v0 bare array.
|
|
202
|
+
* `TeamTaskState` is defined later in this module but interface declarations
|
|
203
|
+
* are type-hoisted so the forward reference is valid.
|
|
204
|
+
*/
|
|
205
|
+
export interface TasksFileData {
|
|
206
|
+
schemaVersion: typeof CURRENT_TASKS_SCHEMA_VERSION;
|
|
207
|
+
tasks: TeamTaskState[];
|
|
208
|
+
}
|
|
209
|
+
|
|
186
210
|
export interface TeamRunManifest {
|
|
187
211
|
schemaVersion: typeof CURRENT_SCHEMA_VERSION;
|
|
188
212
|
runId: string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR-G3 (UI-3) — single shared Component contract for the UI layer.
|
|
3
|
+
*
|
|
4
|
+
* Five parallel "Component" shapes previously existed across src/ui/:
|
|
5
|
+
*
|
|
6
|
+
* - `RenderableComponent` (layout-primitives.ts)
|
|
7
|
+
* - `WidgetComponent` (widget/index.ts)
|
|
8
|
+
* - `DashboardComponent` (run-dashboard.ts)
|
|
9
|
+
* - `type Component` (transcript-viewer.ts)
|
|
10
|
+
* - `type Component = Container|Text` (tool-renderers/index.ts)
|
|
11
|
+
*
|
|
12
|
+
* They all share the same two core methods (render + invalidate) but could
|
|
13
|
+
* not be used interchangeably, which blocked polymorphic dispatch and shared
|
|
14
|
+
* testing. This module names that shared contract so any UI component can be
|
|
15
|
+
* treated uniformly.
|
|
16
|
+
*
|
|
17
|
+
* This is purely additive: every existing component already structurally
|
|
18
|
+
* satisfies these interfaces (render + invalidate are required everywhere;
|
|
19
|
+
* handleInput is present on the interactive ones). Adapting the existing
|
|
20
|
+
* declarations to `extends`/alias these types changes NO runtime behavior —
|
|
21
|
+
* it only makes the relationship explicit and compiler-enforced.
|
|
22
|
+
*
|
|
23
|
+
* Note: @earendil-works/pi-tui also declares a `Component` interface with the
|
|
24
|
+
* same render+invalidate core (plus an optional handleInput). `CrewComponent`
|
|
25
|
+
* is a structural supertype of it, so pi-tui components opt in automatically.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Minimum shared contract every UI component satisfies. */
|
|
29
|
+
export interface CrewComponent {
|
|
30
|
+
/**
|
|
31
|
+
* Drop cached render state so the next `render` recomputes from fresh data.
|
|
32
|
+
* Called on theme changes, resize, or any state mutation that invalidates
|
|
33
|
+
* previously produced lines.
|
|
34
|
+
*/
|
|
35
|
+
invalidate(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Produce rendered lines for the given terminal width.
|
|
38
|
+
* @param width - available terminal width in cells
|
|
39
|
+
* @returns one string per output row (already padded/ANSI-colored)
|
|
40
|
+
*/
|
|
41
|
+
render(width: number): string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A component that also accepts raw key/input data. Interactive viewers
|
|
46
|
+
* (RunDashboard, DurableTranscriptViewer) and overlays (ConfirmOverlay,
|
|
47
|
+
* MailboxDetailOverlay, AgentPickerOverlay) implement this; purely
|
|
48
|
+
* presentational components (Container, Text, Spacer, Box) do not.
|
|
49
|
+
*/
|
|
50
|
+
export interface InteractiveComponent extends CrewComponent {
|
|
51
|
+
/** Optional stable id (used by overlay stacks for lookup/debug). */
|
|
52
|
+
id?: string;
|
|
53
|
+
/** Consume a raw input chunk (a keypress escape sequence). */
|
|
54
|
+
handleInput(data: string): void;
|
|
55
|
+
}
|