pi-crew 0.5.2 → 0.5.6

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 (137) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +17 -1
  3. package/docs/architecture.md +2 -0
  4. package/docs/bugs/cross-session-notification-leakage.md +82 -0
  5. package/docs/coding-agent-optimization.md +268 -0
  6. package/docs/deep-review-report.md +384 -0
  7. package/docs/distillation/cybersecurity-patterns.md +294 -0
  8. package/docs/migration-v0.4-v0.5.md +208 -0
  9. package/docs/optimization-plan.md +642 -0
  10. package/docs/pi-crew-v0.5.5-audit-fix-plan.md +133 -0
  11. package/docs/pi-mono-opportunities.md +969 -0
  12. package/docs/pi-mono-review.md +291 -0
  13. package/docs/skills/REFERENCE.md +144 -0
  14. package/package.json +12 -9
  15. package/skills/artifact-analysis-loop/SKILL.md +302 -0
  16. package/skills/async-worker-recovery/SKILL.md +19 -1
  17. package/skills/child-pi-spawning/SKILL.md +19 -6
  18. package/skills/context-artifact-hygiene/SKILL.md +19 -2
  19. package/skills/delegation-patterns/SKILL.md +68 -3
  20. package/skills/detection-pipeline-design/SKILL.md +285 -0
  21. package/skills/event-log-tracing/SKILL.md +20 -6
  22. package/skills/git-master/SKILL.md +20 -6
  23. package/skills/hunting-investigation-loop/SKILL.md +401 -0
  24. package/skills/incident-playbook-construction/SKILL.md +383 -0
  25. package/skills/live-agent-lifecycle/SKILL.md +20 -6
  26. package/skills/mailbox-interactive/SKILL.md +19 -6
  27. package/skills/model-routing-context/SKILL.md +19 -1
  28. package/skills/multi-perspective-review/SKILL.md +19 -4
  29. package/skills/observability-reliability/SKILL.md +19 -2
  30. package/skills/orchestration/SKILL.md +20 -2
  31. package/skills/ownership-session-security/SKILL.md +20 -2
  32. package/skills/pi-extension-lifecycle/SKILL.md +20 -2
  33. package/skills/post-mortem/SKILL.md +7 -2
  34. package/skills/read-only-explorer/SKILL.md +20 -6
  35. package/skills/requirements-to-task-packet/SKILL.md +23 -3
  36. package/skills/resource-discovery-config/SKILL.md +20 -2
  37. package/skills/runtime-state-reader/SKILL.md +20 -2
  38. package/skills/safe-bash/SKILL.md +21 -6
  39. package/skills/scrutinize/SKILL.md +20 -2
  40. package/skills/secure-agent-orchestration-review/SKILL.md +29 -2
  41. package/skills/security-review/SKILL.md +560 -0
  42. package/skills/state-mutation-locking/SKILL.md +22 -2
  43. package/skills/systematic-debugging/SKILL.md +8 -6
  44. package/skills/threat-hypothesis-framework/SKILL.md +175 -0
  45. package/skills/ui-render-performance/SKILL.md +20 -2
  46. package/skills/verification-before-done/SKILL.md +17 -2
  47. package/skills/widget-rendering/SKILL.md +21 -6
  48. package/skills/workspace-isolation/SKILL.md +20 -6
  49. package/skills/worktree-isolation/SKILL.md +20 -6
  50. package/src/agents/agent-config.ts +40 -1
  51. package/src/benchmark/benchmark-runner.ts +45 -0
  52. package/src/benchmark/feedback-loop.ts +5 -0
  53. package/src/config/config.ts +32 -5
  54. package/src/config/role-tools.ts +82 -0
  55. package/src/config/suggestions.ts +8 -0
  56. package/src/config/types.ts +4 -0
  57. package/src/extension/async-notifier.ts +10 -1
  58. package/src/extension/crew-cleanup.ts +114 -0
  59. package/src/extension/cross-extension-rpc.ts +1 -1
  60. package/src/extension/notification-router.ts +18 -0
  61. package/src/extension/register.ts +27 -19
  62. package/src/extension/registration/subagent-tools.ts +1 -1
  63. package/src/extension/team-tool/anchor.ts +201 -0
  64. package/src/extension/team-tool/api.ts +2 -1
  65. package/src/extension/team-tool/auto-summarize.ts +154 -0
  66. package/src/extension/team-tool/run.ts +42 -7
  67. package/src/extension/team-tool.ts +44 -2
  68. package/src/hooks/registry.ts +1 -3
  69. package/src/observability/event-bus.ts +69 -0
  70. package/src/observability/event-to-metric.ts +0 -2
  71. package/src/runtime/anchor-manager.ts +473 -0
  72. package/src/runtime/async-runner.ts +8 -4
  73. package/src/runtime/auto-summarize.ts +350 -0
  74. package/src/runtime/background-runner.ts +10 -3
  75. package/src/runtime/budget-tracker.ts +354 -0
  76. package/src/runtime/chain-runner.ts +507 -0
  77. package/src/runtime/child-pi.ts +123 -35
  78. package/src/runtime/crash-recovery.ts +5 -4
  79. package/src/runtime/crew-agent-runtime.ts +1 -0
  80. package/src/runtime/custom-tools/irc-tool.ts +13 -0
  81. package/src/runtime/custom-tools/submit-result-tool.ts +3 -2
  82. package/src/runtime/delivery-coordinator.ts +10 -3
  83. package/src/runtime/dynamic-script-runner.ts +482 -0
  84. package/src/runtime/foreground-control.ts +87 -17
  85. package/src/runtime/handoff-manager.ts +589 -0
  86. package/src/runtime/hidden-handoff.ts +424 -0
  87. package/src/runtime/live-agent-manager.ts +20 -4
  88. package/src/runtime/live-session-runtime.ts +39 -4
  89. package/src/runtime/manifest-cache.ts +2 -1
  90. package/src/runtime/model-resolver.ts +16 -4
  91. package/src/runtime/phase-tracker.ts +373 -0
  92. package/src/runtime/pi-args.ts +11 -1
  93. package/src/runtime/pi-json-output.ts +31 -0
  94. package/src/runtime/pipeline-runner.ts +514 -0
  95. package/src/runtime/progress-tracker.ts +124 -0
  96. package/src/runtime/retry-runner.ts +354 -0
  97. package/src/runtime/sandbox.ts +252 -0
  98. package/src/runtime/scheduler.ts +7 -2
  99. package/src/runtime/skill-effectiveness.ts +473 -0
  100. package/src/runtime/skill-instructions.ts +37 -3
  101. package/src/runtime/subagent-manager.ts +1 -1
  102. package/src/runtime/task-graph.ts +11 -1
  103. package/src/runtime/task-runner.ts +92 -18
  104. package/src/runtime/team-runner.ts +13 -12
  105. package/src/runtime/tool-progress.ts +10 -3
  106. package/src/runtime/verification-gates.ts +367 -0
  107. package/src/schema/team-tool-schema.ts +37 -0
  108. package/src/skills/discover-skills.ts +5 -0
  109. package/src/state/active-run-registry.ts +9 -2
  110. package/src/state/contracts.ts +9 -0
  111. package/src/state/crew-init.ts +3 -3
  112. package/src/state/decision-ledger.ts +98 -55
  113. package/src/state/event-log-rotation.ts +2 -2
  114. package/src/state/event-log.ts +144 -10
  115. package/src/state/hook-instinct-bridge.ts +5 -5
  116. package/src/state/mailbox.ts +10 -0
  117. package/src/state/run-cache.ts +18 -8
  118. package/src/state/state-store.ts +3 -1
  119. package/src/state/types.ts +4 -0
  120. package/src/tools/safe-bash-extension.ts +1 -0
  121. package/src/tools/safe-bash.ts +152 -20
  122. package/src/types/new-api-types.ts +34 -0
  123. package/src/ui/agent-management-overlay.ts +5 -1
  124. package/src/ui/crew-widget.ts +29 -15
  125. package/src/ui/overlays/mailbox-detail-overlay.ts +13 -2
  126. package/src/ui/powerbar-publisher.ts +101 -7
  127. package/src/ui/tool-render.ts +15 -15
  128. package/src/ui/transcript-cache.ts +13 -0
  129. package/src/utils/bm25-search.ts +16 -8
  130. package/src/utils/env-filter.ts +8 -5
  131. package/src/utils/redaction.ts +169 -15
  132. package/src/utils/session-utils.ts +52 -0
  133. package/src/utils/sse-parser.ts +10 -1
  134. package/src/worktree/cleanup.ts +6 -1
  135. package/src/worktree/worktree-manager.ts +32 -13
  136. package/workflows/chain.workflow.md +252 -0
  137. package/workflows/pipeline.workflow.md +27 -0
@@ -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,13 +452,13 @@ 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,
451
459
  message: `Worker lifecycle: ${event.type}${event.error ? ` error=${event.error}` : ""}${event.exitCode != null ? ` exit=${event.exitCode}` : ""}`,
452
460
  data: { ...event },
453
- });
461
+ }).catch((error) => logInternalError("task-runner.lifecycle-event", error, `taskId=${task.id}, type=${event.type}`));
454
462
  },
455
463
  onStdoutLine: (line) => {
456
464
  appendCrewAgentOutput(manifest, task.id, line);
@@ -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";
@@ -36,6 +36,7 @@ import { registerRunPromise, resolveRunPromise, rejectRunPromise } from "./run-t
36
36
  import { clearTrackedTaskUsage } from "./usage-tracker.ts";
37
37
  import { CrewCancellationError, buildSyntheticTerminalEvidence, cancellationReasonFromSignal } from "./cancellation.ts";
38
38
  import { effectivenessPolicyDecision, evaluateRunEffectiveness, formatRunEffectivenessLines } from "./effectiveness.ts";
39
+ import { logInternalError } from "../utils/internal-error.ts";
39
40
 
40
41
  export interface ExecuteTeamRunInput {
41
42
  manifest: TeamRunManifest;
@@ -279,7 +280,7 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
279
280
  resolveRunPromise(manifest.runId, result);
280
281
  cleanupUsage();
281
282
  // Terminate live agents for this run — agents are done when the run ends.
282
- void terminateLiveAgentsForRun(manifest.runId, "completed", appendEvent, manifest.eventsPath).catch(() => {});
283
+ void terminateLiveAgentsForRun(manifest.runId, "completed", appendEvent, manifest.eventsPath).catch((error) => logInternalError("team-runner.completed.terminate", error, `runId=${manifest.runId}`));
283
284
 
284
285
  // Emit run completion hook (100% reliable, fire-and-forget)
285
286
  crewHooks.emit({ type: "run_completed", timestamp: new Date().toISOString(), runId: manifest.runId, data: { status: result.manifest.status, taskCount: result.tasks.length } });
@@ -395,7 +396,7 @@ async function executeTeamRunCore(
395
396
  return base;
396
397
  });
397
398
  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 } });
399
+ for (const taskId of cancelledTaskIds) await appendEventAsync(manifest.eventsPath, { type: "task.cancelled", runId: manifest.runId, taskId, message, data: { reason: cancelReason.code } });
399
400
  manifest = updateRunStatus(manifest, "cancelled", message, { data: { reason: cancelReason.code, cancelledTaskIds } });
400
401
  return { manifest, tasks };
401
402
  }
@@ -429,7 +430,7 @@ async function executeTeamRunCore(
429
430
  };
430
431
  const preconditions = validatePhasePreconditions(wfMachine, wfContext);
431
432
  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 } });
433
+ 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
434
  } else {
434
435
  // Advance the machine past completed phases.
435
436
  while (wfMachine.currentPhaseIndex < wfMachine.phases.length && wfMachine.phases[wfMachine.currentPhaseIndex]?.status === "completed") {
@@ -441,7 +442,7 @@ async function executeTeamRunCore(
441
442
  const readyRoles = effectiveReady.map((taskId) => tasks.find((task) => task.id === taskId)?.role).filter((role): role is string => Boolean(role));
442
443
  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
444
  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 } });
445
+ 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
446
  }
446
447
  const approvalPending = isPlanApprovalPending(manifest);
447
448
  const readyIds = approvalPending ? effectiveReady : effectiveReady.slice(0, concurrency.selectedCount);
@@ -474,7 +475,7 @@ async function executeTeamRunCore(
474
475
  }
475
476
  const batchTasks = readyBatch.filter((task) => tasks.find((t) => t.id === task.id && t.status !== "skipped"));
476
477
  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 } });
478
+ 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
479
  }
479
480
  const results = await mapConcurrent(
480
481
  batchTasks,
@@ -519,7 +520,7 @@ async function executeTeamRunCore(
519
520
  attemptId: (attempt) => `${manifest.runId}:${task.id}:attempt-${attempt}`,
520
521
  onAttemptFailed: (attempt, error, delayMs, info) => {
521
522
  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 } });
523
+ 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((error) => logInternalError("team-runner.retry-attempt", error, `taskId=${task.id}`));
523
524
  input.metricRegistry?.histogram("crew.task.retry_delay_ms", "Retry backoff delay, milliseconds").observe({ runId: manifest.runId, taskId: task.id }, delayMs);
524
525
  },
525
526
  onRetryGivenUp: (attempts, error, info) => {
@@ -536,7 +537,7 @@ async function executeTeamRunCore(
536
537
  const freshManifest = fresh?.manifest ?? manifest;
537
538
  const freshTasks = fresh?.tasks ?? tasks;
538
539
  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 });
540
+ 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((error) => logInternalError("team-runner.cancelled", error, `taskId=${task.id}`));
540
541
  return { manifest: updateRunStatus(freshManifest, "cancelled", reason.message), tasks: cancelledTasks };
541
542
  }
542
543
  if (lastFailed) return lastFailed;
@@ -586,10 +587,10 @@ async function executeTeamRunCore(
586
587
  const transition = transitionPhase(wfMachine, pi, phaseStatus, wfContext);
587
588
  wfMachine = transition.machine;
588
589
  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 } });
590
+ 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
591
  break;
591
592
  }
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 } });
593
+ 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
594
  }
594
595
  wfMachine = { ...wfMachine, currentPhaseIndex: pi + 1 };
595
596
  }
@@ -603,7 +604,7 @@ async function executeTeamRunCore(
603
604
  await saveRunTasksAsync(manifest, tasks);
604
605
  saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
605
606
  await saveRunManifestAsync(manifest);
606
- appendEvent(manifest.eventsPath, { type: "run.cancelled", runId: manifest.runId, message, data: { reason, phase: "task-batch", cancelledResultRunId: cancelledResult?.manifest.runId } });
607
+ await appendEventAsync(manifest.eventsPath, { type: "run.cancelled", runId: manifest.runId, message, data: { reason, phase: "task-batch", cancelledResultRunId: cancelledResult?.manifest.runId } });
607
608
  return { manifest, tasks };
608
609
  }
609
610
  queueIndex = buildTaskGraphIndex(tasks);
@@ -651,7 +652,7 @@ async function executeTeamRunCore(
651
652
  const effectivenessDecision = effectivenessPolicyDecision(effectiveness);
652
653
  if (effectivenessDecision) {
653
654
  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 } });
655
+ await appendEventAsync(manifest.eventsPath, { type: "run.effectiveness", runId: manifest.runId, message: effectivenessDecision.message, data: { effectiveness, policyDecision: effectivenessDecision } });
655
656
  }
656
657
  const blockingDecision = manifest.policyDecisions?.find((item) => item.action === "block" || item.action === "escalate");
657
658
  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