pi-crew 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +160 -0
- package/README.md +70 -33
- package/docs/issue-29-analysis.md +189 -0
- package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +1268 -0
- package/package.json +2 -2
- package/src/config/config.ts +106 -15
- package/src/errors.ts +107 -0
- package/src/extension/async-notifier.ts +6 -2
- package/src/extension/crew-cleanup.ts +8 -5
- package/src/extension/management.ts +464 -109
- package/src/extension/register.ts +194 -34
- package/src/extension/registration/commands.ts +2 -2
- package/src/extension/registration/subagent-helpers.ts +2 -2
- package/src/extension/registration/subagent-tools.ts +1 -1
- package/src/extension/registration/team-tool.ts +1 -1
- package/src/extension/registration/viewers.ts +2 -2
- package/src/extension/team-tool/api.ts +3 -3
- package/src/extension/team-tool/cancel.ts +3 -3
- package/src/extension/team-tool/explain.ts +1 -1
- package/src/extension/team-tool/handle-schedule.ts +40 -0
- package/src/extension/team-tool/inspect.ts +3 -3
- package/src/extension/team-tool/lifecycle-actions.ts +4 -4
- package/src/extension/team-tool/respond.ts +2 -2
- package/src/extension/team-tool/run.ts +60 -11
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool.ts +173 -46
- package/src/hooks/registry.ts +77 -13
- package/src/hooks/types.ts +9 -0
- package/src/plugins/plugin-define.ts +6 -0
- package/src/plugins/plugin-registry.ts +32 -0
- package/src/plugins/plugins/index.ts +3 -0
- package/src/plugins/plugins/nextjs.ts +19 -0
- package/src/plugins/plugins/vite.ts +14 -0
- package/src/plugins/plugins/vitest.ts +14 -0
- package/src/runtime/async-runner.ts +35 -7
- package/src/runtime/background-runner.ts +529 -144
- package/src/runtime/chain-parser.ts +5 -1
- package/src/runtime/chain-runner.ts +9 -3
- package/src/runtime/checkpoint.ts +262 -180
- package/src/runtime/child-pi.ts +164 -37
- package/src/runtime/crash-recovery.ts +25 -14
- package/src/runtime/crew-agent-records.ts +2 -0
- package/src/runtime/diagnostic-export.ts +1 -1
- package/src/runtime/dynamic-script-runner.ts +10 -7
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/heartbeat-watcher.ts +19 -2
- package/src/runtime/intercom-bridge.ts +7 -0
- package/src/runtime/iteration-hooks.ts +1 -1
- package/src/runtime/manifest-cache.ts +4 -0
- package/src/runtime/orphan-worker-registry.ts +444 -0
- package/src/runtime/parent-guard.ts +70 -16
- package/src/runtime/pi-args.ts +437 -14
- package/src/runtime/pi-spawn.ts +1 -0
- package/src/runtime/post-checks.ts +1 -1
- package/src/runtime/retry-runner.ts +9 -3
- package/src/runtime/run-tracker.ts +38 -10
- package/src/runtime/sandbox.ts +17 -2
- package/src/runtime/scheduler.ts +25 -2
- package/src/runtime/skill-effectiveness.ts +105 -62
- package/src/runtime/skill-instructions.ts +110 -32
- package/src/runtime/stale-reconciler.ts +310 -69
- package/src/runtime/subagent-manager.ts +251 -57
- package/src/runtime/task-health.ts +76 -0
- package/src/runtime/task-id.ts +20 -0
- package/src/runtime/task-runner/live-executor.ts +1 -1
- package/src/runtime/task-runner/progress.ts +1 -1
- package/src/runtime/task-runner/state-helpers.ts +110 -6
- package/src/runtime/task-runner.ts +92 -70
- package/src/runtime/team-runner.ts +186 -20
- package/src/schema/team-tool-schema.ts +27 -9
- package/src/skills/discover-skills.ts +9 -3
- package/src/state/active-run-registry.ts +170 -38
- package/src/state/artifact-store.ts +25 -13
- package/src/state/atomic-write-v2.ts +86 -0
- package/src/state/atomic-write.ts +346 -55
- package/src/state/blob-store.ts +178 -10
- package/src/state/crew-init.ts +161 -28
- package/src/state/decision-ledger.ts +172 -111
- package/src/state/event-log-rotation.ts +82 -52
- package/src/state/event-log.ts +254 -70
- package/src/state/health-store.ts +71 -0
- package/src/state/locks.ts +102 -20
- package/src/state/mailbox.ts +45 -7
- package/src/state/observation-store.ts +4 -1
- package/src/state/run-graph.ts +141 -130
- package/src/state/run-metrics.ts +24 -8
- package/src/state/state-store.ts +330 -43
- package/src/ui/live-run-sidebar.ts +1 -1
- package/src/ui/loaders.ts +4 -0
- package/src/ui/overlays/agent-picker-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +2 -2
- package/src/ui/run-snapshot-cache.ts +1 -1
- package/src/utils/env-filter.ts +66 -0
- package/src/utils/file-coalescer.ts +9 -1
- package/src/utils/paths.ts +80 -5
- package/src/utils/redaction.ts +6 -1
- package/src/utils/safe-paths.ts +281 -10
- package/src/worktree/cleanup.ts +112 -24
- package/src/worktree/worktree-manager.ts +128 -15
- package/test-bugs-all.mjs +10 -6
- package/test-integration-check.ts +4 -0
|
@@ -1,22 +1,126 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import type { TaskCheckpointState, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
3
|
import { loadRunManifestById, saveRunTasks } from "../../state/state-store.ts";
|
|
3
4
|
import { recordFromTask, upsertCrewAgent } from "../crew-agent-records.ts";
|
|
5
|
+
import { logInternalError } from "../../utils/internal-error.ts";
|
|
6
|
+
import { withRunLockSync } from "../../state/locks.ts";
|
|
4
7
|
|
|
5
8
|
export function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
|
|
6
9
|
return tasks.map((task) => task.id === updated.id ? updated : task);
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Persist a single task update using compare-and-swap under the run lock.
|
|
14
|
+
*
|
|
15
|
+
* Problem: The naive read-merge-write pattern is vulnerable to a read-modify-write
|
|
16
|
+
* race. When two parallel task completions race:
|
|
17
|
+
* 1. Task A loads tasks [A(running), B(running)], writes [A(completed), B(running)]
|
|
18
|
+
* 2. Task B loads [A(running), B(running)] (stale, before A's write), writes [A(running), B(completed)]
|
|
19
|
+
* Result: Task A's completed status is clobbered.
|
|
20
|
+
*
|
|
21
|
+
* Solution: Use mtime-based CAS under the run lock. Before writing, stat the tasks file
|
|
22
|
+
* to record its mtime. After merging, re-stat — if mtime changed, another writer
|
|
23
|
+
* committed first; retry with the fresh state. This is O(retry) under contention but
|
|
24
|
+
* converges in the normal single-writer case.
|
|
25
|
+
*
|
|
26
|
+
* @param checkpointPhase - Optional checkpoint phase to include in the task state alongside the update.
|
|
27
|
+
*/
|
|
28
|
+
export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks: TeamTaskState[], updated: TeamTaskState, checkpointPhase?: TaskCheckpointState["phase"]): TeamTaskState[] {
|
|
29
|
+
let baseMtime = 0;
|
|
30
|
+
try {
|
|
31
|
+
baseMtime = fs.statSync(manifest.tasksPath).mtimeMs;
|
|
32
|
+
} catch {
|
|
33
|
+
// File doesn't exist yet — baseMtime=0 means "anything is fine"
|
|
34
|
+
baseMtime = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let merged: TeamTaskState[] | undefined;
|
|
38
|
+
|
|
39
|
+
// Build the task with optional checkpoint phase
|
|
40
|
+
const taskWithCheckpoint = checkpointPhase
|
|
41
|
+
? { ...updated, checkpoint: { phase: checkpointPhase, updatedAt: new Date().toISOString() } }
|
|
42
|
+
: updated;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
return withRunLockSync(manifest, () => {
|
|
46
|
+
retryLoop: for (let attempt = 0; attempt < 100; attempt++) {
|
|
47
|
+
const latest = loadRunManifestById(manifest.cwd, manifest.runId)?.tasks ?? fallbackTasks;
|
|
48
|
+
merged = updateTask(latest, taskWithCheckpoint);
|
|
49
|
+
|
|
50
|
+
// Re-stat to detect concurrent writes
|
|
51
|
+
let currentMtime: number;
|
|
52
|
+
try {
|
|
53
|
+
currentMtime = fs.statSync(manifest.tasksPath).mtimeMs;
|
|
54
|
+
} catch {
|
|
55
|
+
currentMtime = 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (currentMtime !== baseMtime) {
|
|
59
|
+
// Another writer committed — their update is in latest, re-merge on top
|
|
60
|
+
baseMtime = currentMtime;
|
|
61
|
+
continue retryLoop;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// No concurrent writer — check that our merged result is based on the
|
|
65
|
+
// same base we observed (no intermediate writer between our load and check)
|
|
66
|
+
let recheckMtime: number;
|
|
67
|
+
try {
|
|
68
|
+
recheckMtime = fs.statSync(manifest.tasksPath).mtimeMs;
|
|
69
|
+
} catch {
|
|
70
|
+
// Run state deleted (prune/forget) — nothing to persist.
|
|
71
|
+
return fallbackTasks;
|
|
72
|
+
}
|
|
73
|
+
if (recheckMtime !== baseMtime) {
|
|
74
|
+
baseMtime = recheckMtime;
|
|
75
|
+
continue retryLoop;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Final pre-write mtime check to catch any concurrent writer that completed
|
|
79
|
+
// between the recheck and saveRunTasks
|
|
80
|
+
let preWriteMtime: number;
|
|
81
|
+
try {
|
|
82
|
+
preWriteMtime = fs.statSync(manifest.tasksPath).mtimeMs;
|
|
83
|
+
} catch {
|
|
84
|
+
preWriteMtime = 0;
|
|
85
|
+
}
|
|
86
|
+
if (preWriteMtime !== baseMtime) {
|
|
87
|
+
// Another writer committed — retry
|
|
88
|
+
baseMtime = preWriteMtime;
|
|
89
|
+
continue retryLoop;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
break retryLoop;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (merged === undefined) {
|
|
96
|
+
logInternalError("persistSingleTaskUpdate", new Error("failed to converge after 50 attempts"));
|
|
97
|
+
throw new Error("persistSingleTaskUpdate: failed to converge after 50 attempts");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
saveRunTasks(manifest, merged);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
logInternalError("persistSingleTaskUpdate", err);
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
return merged;
|
|
107
|
+
});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (merged === undefined) {
|
|
110
|
+
logInternalError("persistSingleTaskUpdate", err);
|
|
111
|
+
}
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
14
114
|
}
|
|
15
115
|
|
|
16
116
|
export function checkpointTask(manifest: TeamRunManifest, tasks: TeamTaskState[], task: TeamTaskState, phase: TaskCheckpointState["phase"], childPid?: number): { task: TeamTaskState; tasks: TeamTaskState[] } {
|
|
17
117
|
const checkpoint: TaskCheckpointState = { phase, updatedAt: new Date().toISOString(), ...(childPid ? { childPid } : task.checkpoint?.childPid ? { childPid: task.checkpoint.childPid } : {}) };
|
|
18
118
|
const nextTask = { ...task, checkpoint };
|
|
19
119
|
const nextTasks = persistSingleTaskUpdate(manifest, updateTask(tasks, nextTask), nextTask);
|
|
20
|
-
|
|
120
|
+
try {
|
|
121
|
+
upsertCrewAgent(manifest, recordFromTask(manifest, nextTask, "child-process"));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
logInternalError("checkpointTask", err);
|
|
124
|
+
}
|
|
21
125
|
return { task: nextTask, tasks: nextTasks };
|
|
22
126
|
}
|
|
@@ -220,7 +220,7 @@ export async function runTeamTask(
|
|
|
220
220
|
tasks: updateTask(tasks, cancelledTask),
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
|
-
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
223
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, task, "started");
|
|
224
224
|
if (runtimeKind === "child-process")
|
|
225
225
|
({ task, tasks } = checkpointTask(
|
|
226
226
|
manifest,
|
|
@@ -261,7 +261,7 @@ export async function runTeamTask(
|
|
|
261
261
|
teamRole: { skills: input.teamRoleSkills },
|
|
262
262
|
step: input.step,
|
|
263
263
|
override: input.skillOverride,
|
|
264
|
-
runId: manifest.runId,
|
|
264
|
+
runId: manifest.runId,
|
|
265
265
|
})
|
|
266
266
|
: undefined;
|
|
267
267
|
const skillBlock = input.skillBlock ?? renderedSkills?.block;
|
|
@@ -386,15 +386,29 @@ export async function runTeamTask(
|
|
|
386
386
|
let lastRunProgressSummary: ProgressEventSummary | undefined;
|
|
387
387
|
const persistHeartbeat = (force = false): void => {
|
|
388
388
|
const now = Date.now();
|
|
389
|
+
// Skip disk write if throttled (unless forced).
|
|
389
390
|
if (!force && now - lastHeartbeatPersistedAt < 1000) return;
|
|
390
|
-
|
|
391
|
+
try {
|
|
392
|
+
// Write to disk first, then update in-memory.
|
|
393
|
+
// Disk state is always <= in-memory state, so a crash never produces
|
|
394
|
+
// a fresher in-memory heartbeat than what's on disk. This prevents the
|
|
395
|
+
// stale reconciler from seeing a live heartbeat paired with stale task state
|
|
396
|
+
// (which could cause false zombie detection).
|
|
397
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
// Run state may have been deleted by prune/forget/cleanup.
|
|
400
|
+
// This is not fatal — the run is gone, no point persisting.
|
|
401
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
402
|
+
throw err;
|
|
403
|
+
}
|
|
404
|
+
// Now update in-memory heartbeat so it is always >= persisted state.
|
|
391
405
|
task = {
|
|
392
406
|
...task,
|
|
393
407
|
heartbeat: touchWorkerHeartbeat(
|
|
394
408
|
task.heartbeat ?? createWorkerHeartbeat(task.id),
|
|
395
409
|
),
|
|
396
410
|
};
|
|
397
|
-
|
|
411
|
+
lastHeartbeatPersistedAt = now;
|
|
398
412
|
};
|
|
399
413
|
const persistChildProgress = (
|
|
400
414
|
event: unknown,
|
|
@@ -438,6 +452,10 @@ export async function runTeamTask(
|
|
|
438
452
|
for (let i = 0; i < attemptModels.length; i++) {
|
|
439
453
|
// M1 fix: set transcript path per attempt to avoid mixing across fallback attempts.
|
|
440
454
|
transcriptPath = `${manifest.artifactsRoot}/transcripts/${task.id}.attempt-${i}.jsonl`;
|
|
455
|
+
// Ensure transcripts/ subdirectory exists before child-pi appends
|
|
456
|
+
// to it. appendTranscript uses O_APPEND (no mkdir) for security,
|
|
457
|
+
// so the caller must create the directory.
|
|
458
|
+
fs.mkdirSync(path.join(manifest.artifactsRoot, "transcripts"), { recursive: true });
|
|
441
459
|
const model = attemptModels[i];
|
|
442
460
|
const attemptStartedAt = new Date();
|
|
443
461
|
const pendingAttempt: ModelAttemptSummary = {
|
|
@@ -472,6 +490,7 @@ export async function runTeamTask(
|
|
|
472
490
|
role: task.role,
|
|
473
491
|
runId: manifest.runId,
|
|
474
492
|
agentId: task.id,
|
|
493
|
+
artifactsRoot: manifest.artifactsRoot,
|
|
475
494
|
onSpawn: (pid) => {
|
|
476
495
|
try {
|
|
477
496
|
({ task, tasks } = checkpointTask(
|
|
@@ -521,89 +540,90 @@ export async function runTeamTask(
|
|
|
521
540
|
// Errors are logged but processing continues so subsequent events still update state.
|
|
522
541
|
try {
|
|
523
542
|
appendCrewAgentEvent(manifest, task.id, event);
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
cacheWrite: (task.lifetimeUsage?.cacheWrite ?? 0) + (usage.cacheWrite ?? 0),
|
|
548
|
-
};
|
|
543
|
+
if (
|
|
544
|
+
event &&
|
|
545
|
+
typeof event === "object" &&
|
|
546
|
+
!Array.isArray(event)
|
|
547
|
+
)
|
|
548
|
+
collectedJsonEvents.push(
|
|
549
|
+
event as Record<string, unknown>,
|
|
550
|
+
);
|
|
551
|
+
if (collectedJsonEvents.length > 1000) {
|
|
552
|
+
collectedJsonEvents.splice(0, collectedJsonEvents.length - 1000);
|
|
553
|
+
}
|
|
554
|
+
// Accumulate lifetime usage via message_end events (survives compaction)
|
|
555
|
+
if (event && typeof event === "object" && (event as Record<string, unknown>).type === "message_end") {
|
|
556
|
+
const msg = (event as Record<string, unknown>).message as Record<string, unknown> | undefined;
|
|
557
|
+
if (msg?.role === "assistant") {
|
|
558
|
+
const usage = msg.usage as Record<string, number> | undefined;
|
|
559
|
+
if (usage) {
|
|
560
|
+
task.lifetimeUsage = {
|
|
561
|
+
input: (task.lifetimeUsage?.input ?? 0) + (usage.input ?? 0),
|
|
562
|
+
output: (task.lifetimeUsage?.output ?? 0) + (usage.output ?? 0),
|
|
563
|
+
cacheWrite: (task.lifetimeUsage?.cacheWrite ?? 0) + (usage.cacheWrite ?? 0),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
549
566
|
}
|
|
550
567
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (process.env.PI_CREW_BACKGROUND_MODE === "1" && event) {
|
|
556
|
-
try {
|
|
568
|
+
persistHeartbeat();
|
|
569
|
+
// Bug #3 fix: Write worker JSON events to background.log for debugging when running in background mode.
|
|
570
|
+
// This supplements the event log so developers can see what the child Pi worker produced.
|
|
571
|
+
if (process.env.PI_CREW_BACKGROUND_MODE === "1" && event) {
|
|
557
572
|
const bgLogPath = `${manifest.stateRoot}/background.log`;
|
|
558
573
|
const eventLine = typeof event === "object" && !Array.isArray(event) ? JSON.stringify(event) : String(event);
|
|
559
574
|
fs.appendFileSync(bgLogPath, `${eventLine}\n`);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
task
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
575
|
+
}
|
|
576
|
+
// Apply agentProgress update first, then persist, then update in-memory array.
|
|
577
|
+
// This ensures disk state is always >= in-memory state, preventing
|
|
578
|
+
// fresher in-memory state from being lost on crash.
|
|
579
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, {
|
|
580
|
+
...task,
|
|
581
|
+
agentProgress: applyAgentProgressEvent(
|
|
582
|
+
task.agentProgress ?? emptyCrewAgentProgress(),
|
|
583
|
+
event,
|
|
584
|
+
task.startedAt,
|
|
585
|
+
),
|
|
586
|
+
});
|
|
587
|
+
task = {
|
|
588
|
+
...task,
|
|
589
|
+
agentProgress: applyAgentProgressEvent(
|
|
590
|
+
task.agentProgress ?? emptyCrewAgentProgress(),
|
|
591
|
+
event,
|
|
592
|
+
task.startedAt,
|
|
593
|
+
),
|
|
594
|
+
};
|
|
595
|
+
tasks = updateTask(tasks, task);
|
|
596
|
+
// Bridge event to UI event bus for near-instant updates
|
|
573
597
|
const bridgeEvent = bridgeEventFromJsonEvent(
|
|
574
598
|
manifest.runId,
|
|
575
599
|
task.id,
|
|
576
600
|
event,
|
|
577
601
|
);
|
|
578
602
|
if (bridgeEvent) streamBridge?.handler(bridgeEvent);
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
// Feed overflow recovery tracker
|
|
583
|
-
if (input.onJsonEvent) {
|
|
584
|
-
try {
|
|
603
|
+
// Feed overflow recovery tracker
|
|
604
|
+
if (input.onJsonEvent) {
|
|
585
605
|
input.onJsonEvent(
|
|
586
606
|
task.id,
|
|
587
607
|
manifest.runId,
|
|
588
608
|
event,
|
|
589
609
|
);
|
|
590
|
-
} catch {
|
|
591
|
-
/* overflow tracking errors should not affect task */
|
|
592
610
|
}
|
|
611
|
+
if (
|
|
612
|
+
!finalCheckpointWritten &&
|
|
613
|
+
isFinalChildEvent(event)
|
|
614
|
+
) {
|
|
615
|
+
finalCheckpointWritten = true;
|
|
616
|
+
({ task, tasks } = checkpointTask(
|
|
617
|
+
manifest,
|
|
618
|
+
tasks,
|
|
619
|
+
task,
|
|
620
|
+
"child-stdout-final",
|
|
621
|
+
));
|
|
622
|
+
}
|
|
623
|
+
persistChildProgress(event);
|
|
624
|
+
} catch (err) {
|
|
625
|
+
logInternalError("task-runner.on-json-event", err as Error, `taskId=${task.id}`);
|
|
593
626
|
}
|
|
594
|
-
if (
|
|
595
|
-
!finalCheckpointWritten &&
|
|
596
|
-
isFinalChildEvent(event)
|
|
597
|
-
) {
|
|
598
|
-
finalCheckpointWritten = true;
|
|
599
|
-
({ task, tasks } = checkpointTask(
|
|
600
|
-
manifest,
|
|
601
|
-
tasks,
|
|
602
|
-
task,
|
|
603
|
-
"child-stdout-final",
|
|
604
|
-
));
|
|
605
|
-
}
|
|
606
|
-
persistChildProgress(event);
|
|
607
627
|
},
|
|
608
628
|
});
|
|
609
629
|
const evidenceStatus = childResult.exitStatus?.cancelled
|
|
@@ -881,6 +901,8 @@ export async function runTeamTask(
|
|
|
881
901
|
} else {
|
|
882
902
|
resultArtifact = live.resultArtifact;
|
|
883
903
|
}
|
|
904
|
+
// Sync task.resultArtifact with the re-written artifact (if liveText was truthy)
|
|
905
|
+
task = { ...task, resultArtifact };
|
|
884
906
|
logArtifact = live.logArtifact;
|
|
885
907
|
transcriptArtifact = live.transcriptArtifact;
|
|
886
908
|
} else {
|
|
@@ -1065,7 +1087,7 @@ export async function runTeamTask(
|
|
|
1065
1087
|
// 2. Verification contract has commands
|
|
1066
1088
|
// 3. Not in scaffold mode (scaffold mode intentionally skips execution)
|
|
1067
1089
|
let verificationEvidence: VerificationEvidence = baseEvidence;
|
|
1068
|
-
if (
|
|
1090
|
+
if (runtimeKind !== "scaffold" && taskPacket.verification?.commands?.length) {
|
|
1069
1091
|
try {
|
|
1070
1092
|
const commandResults = await executeVerificationCommands(
|
|
1071
1093
|
taskPacket.verification,
|