pi-crew 0.5.2 → 0.5.5

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/docs/bugs/cross-session-notification-leakage.md +82 -0
  3. package/docs/coding-agent-optimization.md +268 -0
  4. package/docs/deep-review-report.md +384 -0
  5. package/docs/distillation/cybersecurity-patterns.md +294 -0
  6. package/docs/migration-v0.4-v0.5.md +191 -0
  7. package/docs/optimization-plan.md +642 -0
  8. package/docs/pi-mono-opportunities.md +969 -0
  9. package/docs/pi-mono-review.md +291 -0
  10. package/docs/skills/REFERENCE.md +144 -0
  11. package/package.json +7 -6
  12. package/skills/artifact-analysis-loop/SKILL.md +302 -0
  13. package/skills/async-worker-recovery/SKILL.md +19 -1
  14. package/skills/child-pi-spawning/SKILL.md +19 -6
  15. package/skills/context-artifact-hygiene/SKILL.md +19 -2
  16. package/skills/delegation-patterns/SKILL.md +68 -3
  17. package/skills/detection-pipeline-design/SKILL.md +285 -0
  18. package/skills/event-log-tracing/SKILL.md +20 -6
  19. package/skills/git-master/SKILL.md +20 -6
  20. package/skills/hunting-investigation-loop/SKILL.md +401 -0
  21. package/skills/incident-playbook-construction/SKILL.md +383 -0
  22. package/skills/live-agent-lifecycle/SKILL.md +20 -6
  23. package/skills/mailbox-interactive/SKILL.md +19 -6
  24. package/skills/model-routing-context/SKILL.md +19 -1
  25. package/skills/multi-perspective-review/SKILL.md +19 -4
  26. package/skills/observability-reliability/SKILL.md +19 -2
  27. package/skills/orchestration/SKILL.md +20 -2
  28. package/skills/ownership-session-security/SKILL.md +20 -2
  29. package/skills/pi-extension-lifecycle/SKILL.md +20 -2
  30. package/skills/post-mortem/SKILL.md +7 -2
  31. package/skills/read-only-explorer/SKILL.md +20 -6
  32. package/skills/requirements-to-task-packet/SKILL.md +23 -3
  33. package/skills/resource-discovery-config/SKILL.md +20 -2
  34. package/skills/runtime-state-reader/SKILL.md +20 -2
  35. package/skills/safe-bash/SKILL.md +21 -6
  36. package/skills/scrutinize/SKILL.md +20 -2
  37. package/skills/secure-agent-orchestration-review/SKILL.md +29 -2
  38. package/skills/security-review/SKILL.md +560 -0
  39. package/skills/state-mutation-locking/SKILL.md +22 -2
  40. package/skills/systematic-debugging/SKILL.md +8 -6
  41. package/skills/threat-hypothesis-framework/SKILL.md +175 -0
  42. package/skills/ui-render-performance/SKILL.md +20 -2
  43. package/skills/verification-before-done/SKILL.md +17 -2
  44. package/skills/widget-rendering/SKILL.md +21 -6
  45. package/skills/workspace-isolation/SKILL.md +20 -6
  46. package/skills/worktree-isolation/SKILL.md +20 -6
  47. package/src/agents/agent-config.ts +40 -1
  48. package/src/config/config.ts +22 -5
  49. package/src/config/role-tools.ts +82 -0
  50. package/src/config/types.ts +4 -0
  51. package/src/extension/crew-cleanup.ts +114 -0
  52. package/src/extension/register.ts +15 -3
  53. package/src/extension/team-tool/run.ts +7 -7
  54. package/src/observability/event-bus.ts +60 -0
  55. package/src/runtime/background-runner.ts +8 -2
  56. package/src/runtime/child-pi.ts +122 -34
  57. package/src/runtime/crew-agent-runtime.ts +1 -0
  58. package/src/runtime/foreground-control.ts +87 -17
  59. package/src/runtime/pi-args.ts +11 -1
  60. package/src/runtime/pi-json-output.ts +31 -0
  61. package/src/runtime/progress-tracker.ts +124 -0
  62. package/src/runtime/skill-effectiveness.ts +473 -0
  63. package/src/runtime/skill-instructions.ts +37 -3
  64. package/src/runtime/task-runner.ts +91 -17
  65. package/src/runtime/team-runner.ts +11 -11
  66. package/src/runtime/tool-progress.ts +10 -3
  67. package/src/runtime/verification-gates.ts +367 -0
  68. package/src/schema/team-tool-schema.ts +7 -0
  69. package/src/state/decision-ledger.ts +92 -43
  70. package/src/state/event-log.ts +136 -10
  71. package/src/state/hook-instinct-bridge.ts +5 -5
  72. package/src/state/state-store.ts +3 -1
  73. package/src/state/types.ts +4 -0
  74. package/src/types/new-api-types.ts +34 -0
  75. package/src/ui/agent-management-overlay.ts +5 -1
  76. package/src/ui/crew-widget.ts +29 -15
  77. package/src/ui/powerbar-publisher.ts +100 -7
  78. package/src/ui/tool-render.ts +15 -15
  79. package/src/utils/session-utils.ts +52 -0
  80. package/src/worktree/worktree-manager.ts +32 -13
@@ -7,10 +7,11 @@ import type {
7
7
  TeamRunManifest,
8
8
  TeamTaskState,
9
9
  UsageState,
10
+ VerificationEvidence,
10
11
  } from "../state/types.ts";
11
12
  import { logInternalError } from "../utils/internal-error.ts";
12
13
  import { writeArtifact } from "../state/artifact-store.ts";
13
- import { appendEvent, appendEventFireAndForget } from "../state/event-log.ts";
14
+ import { appendEvent, appendEventAsync, appendEventFireAndForget } from "../state/event-log.ts";
14
15
  import { saveRunManifest } from "../state/state-store.ts";
15
16
  import { createTaskClaim } from "../state/task-claims.ts";
16
17
  import {
@@ -38,6 +39,7 @@ import { runChildPi, type ChildPiLifecycleEvent } from "./child-pi.ts";
38
39
  import { buildTaskPacket } from "./task-packet.ts";
39
40
  import { executeHook, appendHookEvent } from "../hooks/registry.ts";
40
41
  import { createVerificationEvidence } from "./green-contract.ts";
42
+ import { executeVerificationCommands, computeGreenLevelFromResults } from "./verification-gates.ts";
41
43
  import { createStartupEvidence } from "./worker-startup.ts";
42
44
  import { permissionForRole } from "./role-permission.ts";
43
45
  import { crewHooks } from "./crew-hooks.ts";
@@ -212,7 +214,7 @@ export async function runTeamTask(
212
214
  "started",
213
215
  ));
214
216
  upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
215
- appendEvent(manifest.eventsPath, {
217
+ await appendEventAsync(manifest.eventsPath, {
216
218
  type: "task.started",
217
219
  runId: manifest.runId,
218
220
  taskId: task.id,
@@ -244,6 +246,7 @@ export async function runTeamTask(
244
246
  teamRole: { skills: input.teamRoleSkills },
245
247
  step: input.step,
246
248
  override: input.skillOverride,
249
+ runId: manifest.runId, // ECC INSTINCT: Enable skill confidence tracking
247
250
  })
248
251
  : undefined;
249
252
  const skillBlock = input.skillBlock ?? renderedSkills?.block;
@@ -420,6 +423,11 @@ export async function runTeamTask(
420
423
  graceTurns: input.runtimeConfig?.graceTurns,
421
424
  inheritContext: input.runtimeConfig?.inheritContext,
422
425
  parentContext: input.parentContext,
426
+ excludeContextBash: input.runtimeConfig?.excludeContextBash,
427
+ sessionId: manifest.sessionId,
428
+ role: task.role,
429
+ runId: manifest.runId,
430
+ agentId: task.id,
423
431
  onSpawn: (pid) => {
424
432
  try {
425
433
  ({ task, tasks } = checkpointTask(
@@ -444,7 +452,7 @@ export async function runTeamTask(
444
452
  }
445
453
  },
446
454
  onLifecycleEvent: (event: ChildPiLifecycleEvent) => {
447
- appendEvent(manifest.eventsPath, {
455
+ void appendEventAsync(manifest.eventsPath, {
448
456
  type: `worker.${event.type}` as const,
449
457
  runId: manifest.runId,
450
458
  taskId: task.id,
@@ -590,7 +598,7 @@ export async function runTeamTask(
590
598
  attemptStartedAt.toISOString(),
591
599
  ),
592
600
  );
593
- appendEvent(manifest.eventsPath, {
601
+ await appendEventAsync(manifest.eventsPath, {
594
602
  type: "worker.cancelled",
595
603
  runId: manifest.runId,
596
604
  taskId: task.id,
@@ -863,7 +871,7 @@ export async function runTeamTask(
863
871
  }
864
872
  } else if (!error) {
865
873
  noYield = true;
866
- appendEvent(manifest.eventsPath, {
874
+ await appendEventAsync(manifest.eventsPath, {
867
875
  type: "task.needs_attention",
868
876
  runId: manifest.runId,
869
877
  taskId: task.id,
@@ -896,6 +904,16 @@ export async function runTeamTask(
896
904
  })
897
905
  : undefined;
898
906
 
907
+ // Capture unified patches from edit tool results
908
+ const patchArtifact = parsedOutput?.patches?.length
909
+ ? writeArtifact(manifest.artifactsRoot, {
910
+ kind: "patch",
911
+ relativePath: `patches/${task.id}.patch`,
912
+ content: parsedOutput.patches.join("\n---\n"),
913
+ producer: task.id,
914
+ })
915
+ : undefined;
916
+
899
917
  const mutationGuardMode =
900
918
  input.runtimeConfig?.completionMutationGuard ?? "warn";
901
919
  const mutationGuard =
@@ -959,7 +977,7 @@ export async function runTeamTask(
959
977
  if (outputText) {
960
978
  outputValidation = validateWorkerOutput(task.role, outputText);
961
979
  if (!outputValidation.valid) {
962
- appendEvent(manifest.eventsPath, {
980
+ await appendEventAsync(manifest.eventsPath, {
963
981
  type: "task.output_validation",
964
982
  runId: manifest.runId,
965
983
  taskId: task.id,
@@ -983,6 +1001,70 @@ export async function runTeamTask(
983
1001
  }
984
1002
  }
985
1003
 
1004
+ // --- ECC VERIFICATION_LOOP: Compute verification evidence before building task object ---
1005
+ // Compute verification evidence (may be async if verification commands need to run)
1006
+ const baseEvidence = createVerificationEvidence(
1007
+ taskPacket.verification,
1008
+ !error,
1009
+ error
1010
+ ? `Task failed: ${error}`
1011
+ : runtimeKind === "scaffold"
1012
+ ? "Safe scaffold mode; verification commands were not executed."
1013
+ : `${runtimeKind} worker finished without reporting a verification failure.`,
1014
+ );
1015
+
1016
+ // Only execute verification commands when:
1017
+ // 1. Task completed successfully (no error)
1018
+ // 2. Verification contract has commands
1019
+ // 3. Not in scaffold mode (scaffold mode intentionally skips execution)
1020
+ let verificationEvidence: VerificationEvidence = baseEvidence;
1021
+ if (!error && runtimeKind !== "scaffold" && taskPacket.verification?.commands?.length) {
1022
+ try {
1023
+ const commandResults = await executeVerificationCommands(
1024
+ taskPacket.verification,
1025
+ task.cwd,
1026
+ manifest.runId,
1027
+ task.id,
1028
+ manifest.artifactsRoot,
1029
+ input.signal,
1030
+ );
1031
+
1032
+ // Compute observed green level from results
1033
+ const observedGreenLevel = computeGreenLevelFromResults(
1034
+ commandResults,
1035
+ taskPacket.verification.requiredGreenLevel,
1036
+ );
1037
+
1038
+ // Determine satisfaction based on green level
1039
+ const requiredLevel = taskPacket.verification.requiredGreenLevel;
1040
+ const satisfied =
1041
+ observedGreenLevel === "none" ? false :
1042
+ observedGreenLevel === "targeted" ? requiredLevel === "targeted" :
1043
+ observedGreenLevel === "package" ? ["targeted", "package"].includes(requiredLevel) :
1044
+ observedGreenLevel === "workspace" ? ["targeted", "package", "workspace"].includes(requiredLevel) :
1045
+ observedGreenLevel === "merge_ready";
1046
+
1047
+ const allPassed = commandResults.every(r => r.status === "passed");
1048
+ const failedCount = commandResults.filter(r => r.status === "failed").length;
1049
+
1050
+ verificationEvidence = {
1051
+ requiredGreenLevel: taskPacket.verification.requiredGreenLevel,
1052
+ observedGreenLevel,
1053
+ satisfied: satisfied && allPassed,
1054
+ commands: commandResults,
1055
+ notes: allPassed
1056
+ ? `${commandResults.length} verification commands passed`
1057
+ : `${failedCount}/${commandResults.length} verification commands failed`,
1058
+ };
1059
+ } catch (execError) {
1060
+ // On execution error, return base evidence with error note
1061
+ verificationEvidence = {
1062
+ ...baseEvidence,
1063
+ notes: `Verification execution failed: ${execError instanceof Error ? execError.message : String(execError)}`,
1064
+ };
1065
+ }
1066
+ }
1067
+
986
1068
  task = {
987
1069
  ...task,
988
1070
  status: error ? "failed" : noYield ? "needs_attention" : "completed",
@@ -999,16 +1081,7 @@ export async function runTeamTask(
999
1081
  }
1000
1082
  : task.agentProgress,
1001
1083
  error,
1002
- verification: createVerificationEvidence(
1003
- taskPacket.verification,
1004
- !error,
1005
- error
1006
- ? `Task failed: ${error}`
1007
- : runtimeKind === "scaffold"
1008
- ? "Safe scaffold mode; verification commands were not executed."
1009
- : `${runtimeKind} worker finished without reporting a verification failure.`,
1010
- ),
1011
- promptArtifact,
1084
+ verification: verificationEvidence,
1012
1085
  resultArtifact,
1013
1086
  claim: undefined,
1014
1087
  heartbeat: touchWorkerHeartbeat(
@@ -1105,6 +1178,7 @@ export async function runTeamTask(
1105
1178
  ...(transcriptArtifact ? [transcriptArtifact] : []),
1106
1179
  ...(diffArtifact ? [diffArtifact] : []),
1107
1180
  ...(diffStatArtifact ? [diffStatArtifact] : []),
1181
+ ...(patchArtifact ? [patchArtifact] : []),
1108
1182
  ],
1109
1183
  };
1110
1184
  saveRunManifest(manifest);
@@ -1117,7 +1191,7 @@ export async function runTeamTask(
1117
1191
  cwd: manifest.cwd,
1118
1192
  });
1119
1193
  appendHookEvent(manifest, hookReport);
1120
- appendEvent(manifest.eventsPath, {
1194
+ await appendEventAsync(manifest.eventsPath, {
1121
1195
  type: error ? "task.failed" : noYield ? "task.needs_attention" : "task.completed",
1122
1196
  runId: manifest.runId,
1123
1197
  taskId: task.id,
@@ -6,7 +6,7 @@ import type { CrewRuntimeKind } from "./crew-agent-runtime.ts";
6
6
  import { resolveTaskRuntimeKind } from "./runtime-policy.ts";
7
7
  import { writeArtifact } from "../state/artifact-store.ts";
8
8
  import { executeHook, appendHookEvent } from "../hooks/registry.ts";
9
- import { appendEvent, appendEventFireAndForget } from "../state/event-log.ts";
9
+ import { appendEvent, appendEventAsync, appendEventFireAndForget } from "../state/event-log.ts";
10
10
  import type { TeamConfig } from "../teams/team-config.ts";
11
11
  import type { ArtifactDescriptor, PolicyDecision, TeamRunManifest, TaskAttemptState, TeamTaskState } from "../state/types.ts";
12
12
  import { loadRunManifestById, saveRunManifest, saveRunManifestAsync, saveRunTasksAsync, updateRunStatus } from "../state/state-store.ts";
@@ -395,7 +395,7 @@ async function executeTeamRunCore(
395
395
  return base;
396
396
  });
397
397
  await saveRunTasksAsync(manifest, tasks);
398
- for (const taskId of cancelledTaskIds) appendEvent(manifest.eventsPath, { type: "task.cancelled", runId: manifest.runId, taskId, message, data: { reason: cancelReason.code } });
398
+ for (const taskId of cancelledTaskIds) await appendEventAsync(manifest.eventsPath, { type: "task.cancelled", runId: manifest.runId, taskId, message, data: { reason: cancelReason.code } });
399
399
  manifest = updateRunStatus(manifest, "cancelled", message, { data: { reason: cancelReason.code, cancelledTaskIds } });
400
400
  return { manifest, tasks };
401
401
  }
@@ -429,7 +429,7 @@ async function executeTeamRunCore(
429
429
  };
430
430
  const preconditions = validatePhasePreconditions(wfMachine, wfContext);
431
431
  if (!preconditions.ready) {
432
- appendEvent(manifest.eventsPath, { type: "workflow.preconditions", runId: manifest.runId, message: `Workflow phase '${wfMachine.phases[wfMachine.currentPhaseIndex]?.name}' is missing inputs: ${preconditions.blocking.join(", ")}`, data: { phaseIndex: wfMachine.currentPhaseIndex, phaseName: wfMachine.phases[wfMachine.currentPhaseIndex]?.name, blocking: preconditions.blocking } });
432
+ await appendEventAsync(manifest.eventsPath, { type: "workflow.preconditions", runId: manifest.runId, message: `Workflow phase '${wfMachine.phases[wfMachine.currentPhaseIndex]?.name}' is missing inputs: ${preconditions.blocking.join(", ")}`, data: { phaseIndex: wfMachine.currentPhaseIndex, phaseName: wfMachine.phases[wfMachine.currentPhaseIndex]?.name, blocking: preconditions.blocking } });
433
433
  } else {
434
434
  // Advance the machine past completed phases.
435
435
  while (wfMachine.currentPhaseIndex < wfMachine.phases.length && wfMachine.phases[wfMachine.currentPhaseIndex]?.status === "completed") {
@@ -441,7 +441,7 @@ async function executeTeamRunCore(
441
441
  const readyRoles = effectiveReady.map((taskId) => tasks.find((task) => task.id === taskId)?.role).filter((role): role is string => Boolean(role));
442
442
  const concurrency = resolveBatchConcurrency({ workflowName: workflow.name, workflowMaxConcurrency: workflow.maxConcurrency, teamMaxConcurrency: input.team.maxConcurrency, limitMaxConcurrentWorkers: input.limits?.maxConcurrentWorkers, allowUnboundedConcurrency: input.limits?.allowUnboundedConcurrency, readyCount: effectiveReady.length, workspaceMode: manifest.workspaceMode, readyRoles });
443
443
  if (concurrency.reason.includes(";unbounded:")) {
444
- appendEvent(manifest.eventsPath, { type: "limits.unbounded", runId: manifest.runId, message: "Unbounded worker concurrency was explicitly enabled for this run.", data: { concurrencyReason: concurrency.reason, maxConcurrent: concurrency.maxConcurrent } });
444
+ await appendEventAsync(manifest.eventsPath, { type: "limits.unbounded", runId: manifest.runId, message: "Unbounded worker concurrency was explicitly enabled for this run.", data: { concurrencyReason: concurrency.reason, maxConcurrent: concurrency.maxConcurrent } });
445
445
  }
446
446
  const approvalPending = isPlanApprovalPending(manifest);
447
447
  const readyIds = approvalPending ? effectiveReady : effectiveReady.slice(0, concurrency.selectedCount);
@@ -474,7 +474,7 @@ async function executeTeamRunCore(
474
474
  }
475
475
  const batchTasks = readyBatch.filter((task) => tasks.find((t) => t.id === task.id && t.status !== "skipped"));
476
476
  if (batchTasks.length > 1) {
477
- appendEvent(manifest.eventsPath, { type: "task.parallel_start", runId: manifest.runId, message: `Launching ${batchTasks.length} tasks in PARALLEL (concurrency=${concurrency.selectedCount}): ${batchTasks.map((t) => `${t.role}(${t.id})`).join(", ")}`, data: { taskIds: batchTasks.map((t) => t.id), roles: batchTasks.map((t) => t.role), concurrency: concurrency.selectedCount } });
477
+ await appendEventAsync(manifest.eventsPath, { type: "task.parallel_start", runId: manifest.runId, message: `Launching ${batchTasks.length} tasks in PARALLEL (concurrency=${concurrency.selectedCount}): ${batchTasks.map((t) => `${t.role}(${t.id})`).join(", ")}`, data: { taskIds: batchTasks.map((t) => t.id), roles: batchTasks.map((t) => t.role), concurrency: concurrency.selectedCount } });
478
478
  }
479
479
  const results = await mapConcurrent(
480
480
  batchTasks,
@@ -519,7 +519,7 @@ async function executeTeamRunCore(
519
519
  attemptId: (attempt) => `${manifest.runId}:${task.id}:attempt-${attempt}`,
520
520
  onAttemptFailed: (attempt, error, delayMs, info) => {
521
521
  lastAttemptId = info.attemptId;
522
- appendEvent(manifest.eventsPath, { type: "crew.task.retry_attempt", runId: manifest.runId, taskId: task.id, message: error.message, data: { attempt, attemptId: info.attemptId, delayMs }, metadata: { attemptId: info.attemptId } });
522
+ appendEventAsync(manifest.eventsPath, { type: "crew.task.retry_attempt", runId: manifest.runId, taskId: task.id, message: error.message, data: { attempt, attemptId: info.attemptId, delayMs }, metadata: { attemptId: info.attemptId } }).catch(() => {});
523
523
  input.metricRegistry?.histogram("crew.task.retry_delay_ms", "Retry backoff delay, milliseconds").observe({ runId: manifest.runId, taskId: task.id }, delayMs);
524
524
  },
525
525
  onRetryGivenUp: (attempts, error, info) => {
@@ -536,7 +536,7 @@ async function executeTeamRunCore(
536
536
  const freshManifest = fresh?.manifest ?? manifest;
537
537
  const freshTasks = fresh?.tasks ?? tasks;
538
538
  const cancelledTasks = freshTasks.map((item) => item.id === task.id && (item.status === "queued" || item.status === "running") ? { ...item, status: "cancelled" as const, finishedAt: new Date().toISOString(), error: `${reason.message} (${reason.code})` } : item);
539
- appendEvent(freshManifest.eventsPath, { type: "task.cancelled", runId: freshManifest.runId, taskId: task.id, message: reason.message, data: { reason, phase: "retry" }, metadata: lastAttemptId ? { attemptId: lastAttemptId } : undefined });
539
+ appendEventAsync(freshManifest.eventsPath, { type: "task.cancelled", runId: freshManifest.runId, taskId: task.id, message: reason.message, data: { reason, phase: "retry" }, metadata: lastAttemptId ? { attemptId: lastAttemptId } : undefined }).catch(() => {});
540
540
  return { manifest: updateRunStatus(freshManifest, "cancelled", reason.message), tasks: cancelledTasks };
541
541
  }
542
542
  if (lastFailed) return lastFailed;
@@ -586,10 +586,10 @@ async function executeTeamRunCore(
586
586
  const transition = transitionPhase(wfMachine, pi, phaseStatus, wfContext);
587
587
  wfMachine = transition.machine;
588
588
  if (transition.guardResult && !transition.guardResult.allowed) {
589
- appendEvent(manifest.eventsPath, { type: "workflow.phase_guard_blocked", runId: manifest.runId, message: `Workflow phase '${phase.name}' guard blocked: ${transition.guardResult.reason ?? "unknown"}`, data: { phaseIndex: pi, phaseName: phase.name, reason: transition.guardResult.reason } });
589
+ await appendEventAsync(manifest.eventsPath, { type: "workflow.phase_guard_blocked", runId: manifest.runId, message: `Workflow phase '${phase.name}' guard blocked: ${transition.guardResult.reason ?? "unknown"}`, data: { phaseIndex: pi, phaseName: phase.name, reason: transition.guardResult.reason } });
590
590
  break;
591
591
  }
592
- appendEvent(manifest.eventsPath, { type: phaseStatus === "failed" ? "workflow.phase_failed" : "workflow.phase_completed", runId: manifest.runId, message: `Workflow phase '${phase.name}' ${phaseStatus}.`, data: { phaseIndex: pi, phaseStatus } });
592
+ await appendEventAsync(manifest.eventsPath, { type: phaseStatus === "failed" ? "workflow.phase_failed" : "workflow.phase_completed", runId: manifest.runId, message: `Workflow phase '${phase.name}' ${phaseStatus}.`, data: { phaseIndex: pi, phaseStatus } });
593
593
  }
594
594
  wfMachine = { ...wfMachine, currentPhaseIndex: pi + 1 };
595
595
  }
@@ -603,7 +603,7 @@ async function executeTeamRunCore(
603
603
  await saveRunTasksAsync(manifest, tasks);
604
604
  saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
605
605
  await saveRunManifestAsync(manifest);
606
- appendEvent(manifest.eventsPath, { type: "run.cancelled", runId: manifest.runId, message, data: { reason, phase: "task-batch", cancelledResultRunId: cancelledResult?.manifest.runId } });
606
+ await appendEventAsync(manifest.eventsPath, { type: "run.cancelled", runId: manifest.runId, message, data: { reason, phase: "task-batch", cancelledResultRunId: cancelledResult?.manifest.runId } });
607
607
  return { manifest, tasks };
608
608
  }
609
609
  queueIndex = buildTaskGraphIndex(tasks);
@@ -651,7 +651,7 @@ async function executeTeamRunCore(
651
651
  const effectivenessDecision = effectivenessPolicyDecision(effectiveness);
652
652
  if (effectivenessDecision) {
653
653
  manifest = { ...manifest, policyDecisions: [...(manifest.policyDecisions ?? []), effectivenessDecision], updatedAt: new Date().toISOString() };
654
- appendEvent(manifest.eventsPath, { type: "run.effectiveness", runId: manifest.runId, message: effectivenessDecision.message, data: { effectiveness, policyDecision: effectivenessDecision } });
654
+ await appendEventAsync(manifest.eventsPath, { type: "run.effectiveness", runId: manifest.runId, message: effectivenessDecision.message, data: { effectiveness, policyDecision: effectivenessDecision } });
655
655
  }
656
656
  const blockingDecision = manifest.policyDecisions?.find((item) => item.action === "block" || item.action === "escalate");
657
657
  if (failed) {
@@ -150,12 +150,18 @@ export interface ToolProgressDisplay {
150
150
  * Format tool progress for display
151
151
  */
152
152
  export function formatToolProgress(progress: CrewAgentProgress, maxContextTokens = 128000): ToolProgressDisplay {
153
- const recentTools = progress.recentTools.map((t) => ({
153
+ const recentTools: Array<{
154
+ tool: string;
155
+ args?: string;
156
+ startedAt?: string;
157
+ endedAt?: string;
158
+ status: "running" | "done" | "error";
159
+ }> = progress.recentTools.map((t) => ({
154
160
  tool: t.tool,
155
161
  args: t.args,
156
162
  startedAt: t.startedAt,
157
163
  endedAt: t.endedAt,
158
- status: t.endedAt ? "done" : "running" as const,
164
+ status: t.endedAt ? ("done" as const) : ("running" as const),
159
165
  }));
160
166
 
161
167
  // If there's a currentTool but no endedAt, it's still running
@@ -167,7 +173,8 @@ export function formatToolProgress(progress: CrewAgentProgress, maxContextTokens
167
173
  tool: progress.currentTool,
168
174
  args: progress.currentToolArgs,
169
175
  startedAt: progress.currentToolStartedAt,
170
- status: "running",
176
+ endedAt: undefined as string | undefined,
177
+ status: "running" as const,
171
178
  });
172
179
  }
173
180