pi-crew 0.9.68 → 0.10.2

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 (253) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/NOTICE.md +21 -0
  3. package/README.md +44 -2
  4. package/agents/analyst.md +1 -1
  5. package/agents/cold-verifier.md +3 -1
  6. package/agents/critic.md +1 -1
  7. package/agents/executor.md +1 -1
  8. package/agents/explorer.md +1 -1
  9. package/agents/planner.md +1 -1
  10. package/agents/reviewer.md +1 -1
  11. package/agents/security-reviewer.md +1 -1
  12. package/agents/test-engineer.md +1 -1
  13. package/agents/verifier.md +1 -1
  14. package/agents/writer.md +1 -1
  15. package/dist/index.mjs +68113 -60774
  16. package/docs/README.md +2 -0
  17. package/docs/actions-reference.md +31 -0
  18. package/docs/commands-reference.md +17 -6
  19. package/docs/resource-formats.md +13 -0
  20. package/package.json +4 -2
  21. package/schema.json +503 -91
  22. package/scripts/resource-sampler.mjs +36 -2
  23. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  24. package/skills/widget-rendering/SKILL.md +7 -7
  25. package/src/agents/agent-config.ts +2 -1
  26. package/src/agents/discover-agents.ts +23 -14
  27. package/src/config/config-merge.ts +183 -0
  28. package/src/config/config-validation.ts +687 -0
  29. package/src/config/config.ts +22 -864
  30. package/src/config/defaults.ts +43 -2
  31. package/src/config/drift-detector.ts +1 -1
  32. package/src/config/env-vars.ts +691 -0
  33. package/src/config/role-tools.ts +11 -9
  34. package/src/config/sanitize-project-config.ts +172 -0
  35. package/src/config/types.ts +49 -1
  36. package/src/extension/async-notifier.ts +25 -2
  37. package/src/extension/crew-cleanup.ts +13 -0
  38. package/src/extension/crew-vibes/config.ts +2 -1
  39. package/src/extension/crew-vibes/footer.ts +19 -0
  40. package/src/extension/crew-vibes/index.ts +11 -1
  41. package/src/extension/plan-orchestrate.ts +132 -0
  42. package/src/extension/register.ts +8 -0
  43. package/src/extension/registration/command-registration.ts +1 -0
  44. package/src/extension/registration/commands/dashboard.ts +158 -0
  45. package/src/extension/registration/commands/index.ts +35 -0
  46. package/src/extension/registration/commands/manage.ts +303 -0
  47. package/src/extension/registration/commands/run.ts +228 -0
  48. package/src/extension/registration/commands/shared.ts +639 -0
  49. package/src/extension/registration/commands/status.ts +60 -0
  50. package/src/extension/registration/commands.ts +13 -1224
  51. package/src/extension/registration/foreground-run-controller.ts +10 -2
  52. package/src/extension/registration/lifecycle-handlers.ts +178 -17
  53. package/src/extension/registration/runtime-cleanup.ts +23 -5
  54. package/src/extension/registration/subagent-tools.ts +218 -9
  55. package/src/extension/registration/team-tool.ts +5 -1
  56. package/src/extension/registration/ui.ts +5 -4
  57. package/src/extension/rpc-hmac.ts +5 -3
  58. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  59. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  60. package/src/extension/team-tool/api/task-claims.ts +109 -40
  61. package/src/extension/team-tool/cancel.ts +84 -50
  62. package/src/extension/team-tool/dispatch/index.ts +1 -0
  63. package/src/extension/team-tool/dispatch/run.ts +4 -1
  64. package/src/extension/team-tool/doctor.ts +103 -1
  65. package/src/extension/team-tool/orchestrate.ts +66 -1
  66. package/src/extension/team-tool/plans.ts +192 -0
  67. package/src/extension/team-tool/respond.ts +197 -65
  68. package/src/extension/team-tool/run-deadline.ts +35 -3
  69. package/src/extension/team-tool/run-intent.ts +63 -0
  70. package/src/extension/team-tool/run.ts +74 -20
  71. package/src/extension/team-tool/status.ts +84 -26
  72. package/src/extension/team-tool.ts +11 -2
  73. package/src/hooks/registry.ts +1 -6
  74. package/src/i18n.ts +9 -0
  75. package/src/prompt/prompt-runtime.ts +521 -2
  76. package/src/prompt/worker-events-channel.ts +173 -0
  77. package/src/runtime/README.md +8 -8
  78. package/src/runtime/async-runner.ts +7 -3
  79. package/src/runtime/background-runner.ts +42 -14
  80. package/src/runtime/broker/broker-issuer.ts +9 -2
  81. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  82. package/src/runtime/broker/crew-broker.ts +838 -10
  83. package/src/runtime/broker/wait-status-cache.ts +157 -0
  84. package/src/runtime/budget-enforcement.ts +281 -0
  85. package/src/runtime/child-pi/child-pi-constants.ts +8 -0
  86. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  87. package/src/runtime/child-pi/child-pi-streams.ts +21 -1
  88. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  89. package/src/runtime/child-pi/child-pi.ts +97 -201
  90. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  91. package/src/runtime/crew-agent-records.ts +259 -14
  92. package/src/runtime/delegate-spawn.ts +148 -0
  93. package/src/runtime/detached-run-results.ts +90 -0
  94. package/src/runtime/deterministic-ast.ts +2 -1
  95. package/src/runtime/dispatch-batch.ts +945 -0
  96. package/src/runtime/finalize-run.ts +557 -0
  97. package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
  98. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
  99. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  100. package/src/runtime/group-join.ts +11 -125
  101. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  102. package/src/runtime/merge-gate.ts +32 -10
  103. package/src/runtime/merge-loop.ts +130 -0
  104. package/src/runtime/model/model-budget-summary.ts +53 -0
  105. package/src/runtime/model/model-fallback.ts +36 -2
  106. package/src/runtime/model/pi-args.ts +10 -0
  107. package/src/runtime/model/provider-extensions.ts +10 -0
  108. package/src/runtime/orphan-worker-registry.ts +1 -1
  109. package/src/runtime/output/output-validator.ts +45 -0
  110. package/src/runtime/parent-guard.ts +3 -1
  111. package/src/runtime/peer-dep.ts +2 -1
  112. package/src/runtime/per-write-validator.ts +0 -5
  113. package/src/runtime/pi-spawn.ts +61 -15
  114. package/src/runtime/plan-approval.ts +125 -0
  115. package/src/runtime/plan-replan.ts +151 -0
  116. package/src/runtime/process-status.ts +16 -1
  117. package/src/runtime/recovery/checkpoint.ts +0 -18
  118. package/src/runtime/recovery/crash-recovery.ts +111 -46
  119. package/src/runtime/run-tracker.ts +77 -10
  120. package/src/runtime/scheduler-context.ts +98 -0
  121. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  122. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  123. package/src/runtime/scheduling/nested-slots.ts +70 -0
  124. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  125. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  126. package/src/runtime/settings-store.ts +219 -0
  127. package/src/runtime/spawn-policy.ts +217 -0
  128. package/src/runtime/stale-reconciler.ts +87 -6
  129. package/src/runtime/subagent-manager.ts +25 -1
  130. package/src/runtime/task-output-context.ts +230 -9
  131. package/src/runtime/task-packet.ts +23 -1
  132. package/src/runtime/task-runner/child-executor.ts +106 -7
  133. package/src/runtime/task-runner/post-execution.ts +125 -1
  134. package/src/runtime/task-runner/pre-execution.ts +39 -1
  135. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  136. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  137. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  138. package/src/runtime/task-runner/state-helpers.ts +26 -24
  139. package/src/runtime/task-runner.ts +11 -0
  140. package/src/runtime/team-runner.ts +132 -1673
  141. package/src/runtime/verification/spec-sandbox.ts +255 -0
  142. package/src/runtime/verification/verification-gates.ts +3 -2
  143. package/src/runtime/verification/verification-worktree.ts +2 -1
  144. package/src/runtime/workflow-phase-advance.ts +100 -0
  145. package/src/runtime/workspace-tree.ts +9 -0
  146. package/src/schema/config-schema.ts +66 -26
  147. package/src/schema/sensitive-config-paths.ts +64 -0
  148. package/src/schema/team-tool-schema.ts +13 -3
  149. package/src/state/README.md +4 -10
  150. package/src/state/atomic-write.ts +20 -3
  151. package/src/state/contracts.ts +38 -0
  152. package/src/state/coordination/mailbox.ts +12 -2
  153. package/src/state/event-log/cursor.ts +223 -0
  154. package/src/state/event-log/event-log-rotation.ts +12 -4
  155. package/src/state/event-log/event-log.ts +152 -369
  156. package/src/state/event-log/sequence-cache.ts +373 -0
  157. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  158. package/src/state/stores/active-run-registry.ts +3 -2
  159. package/src/state/stores/manifest-io.ts +237 -0
  160. package/src/state/stores/ownership-map.ts +162 -0
  161. package/src/state/stores/plan-store.ts +241 -0
  162. package/src/state/stores/run-cache.ts +0 -90
  163. package/src/state/stores/spec-store.ts +189 -0
  164. package/src/state/stores/state-store.ts +139 -232
  165. package/src/state/types.ts +199 -0
  166. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  167. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  168. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  169. package/src/ui/dock-footer.ts +49 -0
  170. package/src/ui/heartbeat-aggregator.ts +9 -1
  171. package/src/ui/inline-panel/agent-pane.ts +375 -0
  172. package/src/ui/inline-panel/agent-transcript.ts +338 -0
  173. package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
  174. package/src/ui/inline-panel/crew-editor.ts +192 -0
  175. package/src/ui/inline-panel/index.ts +290 -0
  176. package/src/ui/inline-panel/panel-rows.ts +37 -0
  177. package/src/ui/inline-panel/panel-selection.ts +157 -0
  178. package/src/ui/inline-panel/panel-store.ts +111 -0
  179. package/src/ui/inline-panel/view-session-store.ts +36 -0
  180. package/src/ui/keybinding-map.ts +54 -13
  181. package/src/ui/pi-ui-compat.ts +9 -0
  182. package/src/ui/powerbar-publisher.ts +52 -1
  183. package/src/ui/run-dashboard.ts +31 -5
  184. package/src/ui/run-snapshot-cache.ts +57 -30
  185. package/src/ui/snapshot-types.ts +6 -1
  186. package/src/ui/widget/index.ts +176 -22
  187. package/src/ui/widget/task-list.ts +198 -0
  188. package/src/ui/widget/widget-formatters.ts +240 -4
  189. package/src/ui/widget/widget-renderer.ts +243 -38
  190. package/src/ui/widget/widget-types.ts +11 -0
  191. package/src/utils/child-process-shield.ts +106 -0
  192. package/src/utils/file-coalescer.ts +0 -4
  193. package/src/utils/fs-errno.ts +66 -0
  194. package/src/utils/fs-watch.ts +1 -1
  195. package/src/utils/internal-error.ts +3 -1
  196. package/src/utils/paths.ts +11 -3
  197. package/src/utils/redaction.ts +7 -0
  198. package/src/utils/safe-abort.ts +45 -0
  199. package/src/utils/task-name-generator.ts +1 -8
  200. package/src/workflows/discover-workflows.ts +20 -2
  201. package/src/workflows/validate-workflow.ts +7 -1
  202. package/src/workflows/workflow-config.ts +17 -0
  203. package/src/workflows/workflow-serializer.ts +3 -0
  204. package/src/worktree/worktree-manager.ts +22 -0
  205. package/workflows/default.workflow.md +36 -26
  206. package/workflows/strict-fast-fix.workflow.md +26 -0
  207. package/src/agents/agent-search.ts +0 -98
  208. package/src/benchmark/benchmark-runner.ts +0 -313
  209. package/src/benchmark/feedback-loop.ts +0 -73
  210. package/src/config/resilient-parser.ts +0 -117
  211. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  212. package/src/extension/result-watcher.ts +0 -139
  213. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  214. package/src/observability/metric-retention.ts +0 -64
  215. package/src/runtime/compaction/compaction-summary.ts +0 -278
  216. package/src/runtime/errors/crew-errors.ts +0 -162
  217. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  218. package/src/runtime/loop-gates.ts +0 -128
  219. package/src/runtime/metric-parser.ts +0 -36
  220. package/src/runtime/output/stream-preview.ts +0 -184
  221. package/src/runtime/output/tool-progress.ts +0 -278
  222. package/src/runtime/phase-tracker.ts +0 -385
  223. package/src/runtime/pipeline-runner.ts +0 -523
  224. package/src/runtime/process/process-lifecycle.ts +0 -491
  225. package/src/runtime/recovery/retry-runner.ts +0 -330
  226. package/src/runtime/run-drift.ts +0 -219
  227. package/src/runtime/task-quality.ts +0 -199
  228. package/src/runtime/task-runner/run-projection.ts +0 -128
  229. package/src/runtime/verification/post-checks.ts +0 -142
  230. package/src/state/coordination/schedule.ts +0 -166
  231. package/src/state/event-log/jsonl-writer.ts +0 -115
  232. package/src/state/hook-instinct-bridge.ts +0 -94
  233. package/src/state/hook-integrations.ts +0 -51
  234. package/src/state/session-state-map.ts +0 -51
  235. package/src/state/stores/blob-store.ts +0 -308
  236. package/src/state/stores/instinct-store.ts +0 -275
  237. package/src/state/stores/observation-store.ts +0 -176
  238. package/src/state/tiered-eval.ts +0 -480
  239. package/src/state/types-eval.ts +0 -58
  240. package/src/tools/safe-bash-extension.ts +0 -54
  241. package/src/tools/safe-bash.ts +0 -505
  242. package/src/ui/agent-management-overlay.ts +0 -160
  243. package/src/ui/crew-footer.ts +0 -102
  244. package/src/ui/crew-select-list.ts +0 -114
  245. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  246. package/src/ui/transcript-entries.ts +0 -256
  247. package/src/utils/conflict-detect.ts +0 -721
  248. package/src/utils/fingerprint.ts +0 -180
  249. package/src/utils/gh-protocol.ts +0 -556
  250. package/src/utils/project-detector.ts +0 -160
  251. package/src/utils/sse-parser.ts +0 -131
  252. package/src/workflows/cost-estimator.ts +0 -34
  253. package/src/workflows/intermediate-store.ts +0 -166
@@ -20,6 +20,7 @@ import { setWorkingIndicator } from "../../ui/pi-ui-compat.ts";
20
20
  import { requestPowerbarUpdate } from "../../ui/powerbar-publisher.ts";
21
21
  import { updateCrewWidget } from "../../ui/widget/index.ts";
22
22
  import { logInternalError } from "../../utils/internal-error.ts";
23
+ import { safeAbort } from "../../utils/safe-abort.ts";
23
24
  import type { RegistrationContext } from "./registration-types.ts";
24
25
 
25
26
  /**
@@ -36,7 +37,10 @@ export function installForegroundRunController(pi: ExtensionAPI, ctx: Registrati
36
37
  ctx.abortForegroundRun = (runId) => {
37
38
  const controller = ctx.foregroundTeamRunControllers.get(runId);
38
39
  if (!controller) return false;
39
- controller.abort();
40
+ // safe-abort: cancel can race a completed run whose spawned children
41
+ // already exited — Node's child_process signal listener would throw
42
+ // AbortError out of abort() and kill the host (uncaughtException).
43
+ safeAbort(controller, "fg-run.abort-by-run-id");
40
44
  return true;
41
45
  };
42
46
  }
@@ -142,7 +146,11 @@ function startForegroundRunImpl(
142
146
  .catch((error) => logInternalError("register.foreground-watchdog", error, `runId=${runId}`));
143
147
  }
144
148
  const ownerCurrent = ctx.isContextCurrent(extensionCtx, ownerGeneration);
145
- if (extensionCtx.hasUI) {
149
+ // The owner ctx can go stale while the run is in flight (e.g. the
150
+ // user opened an agent session view via /crew-view and the run
151
+ // finishes there): the `hasUI` getter THROWS on a stale ctx, so
152
+ // it must only be touched for the still-current owner.
153
+ if (ownerCurrent && extensionCtx.hasUI) {
146
154
  try {
147
155
  setWorkingIndicator(extensionCtx);
148
156
  extensionCtx.ui.setWorkingMessage();
@@ -19,13 +19,16 @@ import * as path from "node:path";
19
19
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
20
20
  import { loadConfig } from "../../config/config.ts";
21
21
  import { DEFAULT_UI } from "../../config/defaults.ts";
22
+ import { getCrewEnv } from "../../config/env-vars.ts";
22
23
  import { pruneFinishedRuns, pruneUserLevelRuns } from "../../extension/run-maintenance.ts";
23
24
  import { type BrokerSpawnCredentials, setActiveBrokerIssuer } from "../../runtime/broker/broker-issuer.ts";
24
25
  import { CrewBroker } from "../../runtime/broker/crew-broker.ts";
25
26
  import { terminateActiveChildPiProcesses } from "../../runtime/child-pi/child-pi.ts";
27
+ import { forgetDetachedRun, hasDetachedRuns, peekFinishedDetachedRunResults } from "../../runtime/detached-run-results.ts";
26
28
  import { listLiveAgents } from "../../runtime/live-session/live-agent-manager.ts";
27
29
  import type { createManifestCache } from "../../runtime/manifest-cache.ts";
28
- import { cleanupLegacyOrphanTempDirs, cleanupOrphanTempDirs, currentCrewDepth } from "../../runtime/model/pi-args.ts";
30
+ import { configuredModelInfosFromPiConfig } from "../../runtime/model/model-fallback.ts";
31
+ import { cleanupLegacyOrphanTempDirs, cleanupOrphanTempDirs, currentCrewDepth, resolveCrewMaxDepth } from "../../runtime/model/pi-args.ts";
29
32
  import { clearProviderQuotaCache, noteProviderResponse } from "../../runtime/model/provider-quota.ts";
30
33
  import { noteSessionModel, noteSessionThinking, resolveProviderForResponse } from "../../runtime/model/session-model.ts";
31
34
  import { cleanupOrphanWorkers } from "../../runtime/orphan-worker-registry.ts";
@@ -33,11 +36,13 @@ import { reconcileAllStaleRuns } from "../../runtime/recovery/crash-recovery.ts"
33
36
  import { CrewScheduler, type ScheduledJob } from "../../runtime/scheduling/scheduler.ts";
34
37
  import { tryRegisterSessionCleanup } from "../../runtime/session-resources.ts";
35
38
  import { createSessionSnapshot } from "../../runtime/session-snapshot.ts";
36
- import { applyCrewSettingsToConfig, loadCrewSettings } from "../../runtime/settings-store.ts";
39
+ import { applyCrewSettingsTiersToConfig, loadCrewSettingsTiers } from "../../runtime/settings-store.ts";
37
40
  import { loadRunManifestById } from "../../state/stores/state-store.ts";
38
41
  import type { TeamRunManifest } from "../../state/types.ts";
39
42
  import { summarizeHeartbeats } from "../../ui/heartbeat-aggregator.ts";
40
- import { requestRender, setExtensionWidget } from "../../ui/pi-ui-compat.ts";
43
+ import { installInlinePanel } from "../../ui/inline-panel/index.ts";
44
+ import { clearSessionSwitchInFlight, markSessionSwitchInFlight } from "../../ui/inline-panel/view-session-store.ts";
45
+ import { requestRender, setExtensionWidget, toPiWidgetPlacement } from "../../ui/pi-ui-compat.ts";
41
46
  import {
42
47
  registerPiCrewPowerbarSegments,
43
48
  requestPowerbarUpdate,
@@ -73,6 +78,36 @@ export function installSessionLifecycleHandlers(pi: ExtensionAPI, ctx: Registrat
73
78
  installModelTrackingHandlers(pi);
74
79
  }
75
80
 
81
+ /**
82
+ * Hand over the outcome of runs detached by an agent-view switch.
83
+ *
84
+ * Delivered as a DISPLAYED session entry (not a follow-up queue item): an idle
85
+ * session never flushes a follow-up, so the queued variant left the result
86
+ * invisible. Each result is dropped from the registry only after its send
87
+ * succeeded, and only while the owning session is current — a worker's view
88
+ * session must never receive the parent run's report.
89
+ */
90
+ function deliverDetachedRunResults(pi: ExtensionAPI, extensionCtx: ExtensionContext): void {
91
+ if (!hasDetachedRuns()) return;
92
+ try {
93
+ // Agent views are in-document panes now, never sessions: the current
94
+ // session is always the main one, so detached results always deliver.
95
+ const inViewSession = false;
96
+ for (const { runId, text } of peekFinishedDetachedRunResults({ inViewSession })) {
97
+ pi.sendMessage({ customType: "pi-crew-run-result", content: text, display: true });
98
+ forgetDetachedRun(runId);
99
+ try {
100
+ extensionCtx.ui.notify(text.split("\n")[0] ?? `pi-crew run finished: ${runId}`, "info");
101
+ } catch {
102
+ /* toast is secondary to the session entry */
103
+ }
104
+ }
105
+ } catch (error) {
106
+ // Keep the entry: the next tick retries rather than losing the outcome.
107
+ logInternalError("register.detachedRunResults", error);
108
+ }
109
+ }
110
+
76
111
  /**
77
112
  * model_select / thinking_level_select:
78
113
  * Track what the MAIN session is *actually* running so subagents that
@@ -123,6 +158,12 @@ function installSessionShutdownHandler(pi: ExtensionAPI, ctx: RegistrationContex
123
158
  */
124
159
  function installSessionBeforeSwitchHandler(pi: ExtensionAPI, ctx: RegistrationContext): void {
125
160
  pi.on("session_before_switch", () => {
161
+ // The switch tears the current turn down right after this handler
162
+ // (teardownCurrent → session.abort()). That turn-abort must NOT cancel
163
+ // a foreground team run that is still forming (see run-deadline.ts):
164
+ // foreground runs survive session switches (P0). Cleared on the next
165
+ // session_start.
166
+ markSessionSwitchInFlight();
126
167
  ctx.sessionGeneration++;
127
168
  const pendingCount = ctx.lifecycleState.deliveryCoordinator?.getPendingCount() ?? 0;
128
169
  try {
@@ -165,6 +206,9 @@ function installSessionBeforeSwitchHandler(pi: ExtensionAPI, ctx: RegistrationCo
165
206
  */
166
207
  function installSessionStartHandler(pi: ExtensionAPI, ctx: RegistrationContext): void {
167
208
  pi.on("session_start", (_event, extensionCtx) => {
209
+ // Any session start means a pending switch landed — the turn-abort
210
+ // suppression window for that switch is over.
211
+ clearSessionSwitchInFlight();
168
212
  runArtifactCleanup(extensionCtx.cwd);
169
213
 
170
214
  // Restore brief mode state from session entries (best-effort).
@@ -215,8 +259,18 @@ function installSessionStartHandler(pi: ExtensionAPI, ctx: RegistrationContext):
215
259
  }, 0);
216
260
 
217
261
  const loadedConfig = loadConfig(extensionCtx.cwd);
218
- const crewSettings = loadCrewSettings(extensionCtx.cwd);
219
- applyCrewSettingsToConfig(loadedConfig.config, crewSettings);
262
+ // Wave 2B (P1 security): crew settings load as TIERS — the project-tier
263
+ // <cwd>/.pi/crew-settings.json is untrusted (a cloned repo can ship it)
264
+ // and now goes through sanitizeProjectConfig + tighten-only guard
265
+ // tiering instead of being applied raw over the sanitized config (the
266
+ // pre-2B `loadCrewSettings` + `applyCrewSettingsToConfig` bypass).
267
+ // See src/runtime/settings-store.ts for the tier pipeline + the
268
+ // scheduledJobs/schedulingEnabled boundary decision.
269
+ const crewSettingsTiers = loadCrewSettingsTiers(extensionCtx.cwd);
270
+ const settingsWarnings = applyCrewSettingsTiersToConfig(loadedConfig.config, crewSettingsTiers);
271
+ if (settingsWarnings.length > 0) {
272
+ (loadedConfig.warnings ??= []).push(...settingsWarnings);
273
+ }
220
274
 
221
275
  // Start scheduler with event-based executor
222
276
  const sessionId =
@@ -229,14 +283,20 @@ function installSessionStartHandler(pi: ExtensionAPI, ctx: RegistrationContext):
229
283
  // Wire scheduler into handle-schedule.ts so handlers can add/list jobs.
230
284
  // EXT-9: module-scoped setter (was globalThis[Symbol.for(...)]).
231
285
  registerCrewScheduler(ctx.crewScheduler);
232
- // Load scheduled jobs from settings if present
233
- if (Array.isArray(crewSettings.scheduledJobs)) {
234
- for (const job of crewSettings.scheduledJobs) {
235
- try {
236
- ctx.crewScheduler.add(job as ScheduledJob);
237
- } catch {
238
- /* skip invalid */
239
- }
286
+ // Load scheduled jobs from settings if present.
287
+ // BOUNDARY (Wave B2): project-tier scheduledJobs are OPT-IN GATED — the
288
+ // registration loop reads the gated `effectiveScheduledJobs` view (user
289
+ // jobs always; project jobs ONLY when the user-tier global file has BOTH
290
+ // schedulingEnabled:true AND allowProjectScheduledJobs:true).
291
+ // <cwd>/.pi/crew-settings.json remains the persistence store for the
292
+ // user's own `crew schedule add/update/remove` commands (handle-schedule.ts),
293
+ // so crew-schedule users must set both flags in ~/.pi/crew-settings.json.
294
+ // `schedulingEnabled` is user-tier-only (project values always dropped).
295
+ for (const job of crewSettingsTiers.effectiveScheduledJobs) {
296
+ try {
297
+ ctx.crewScheduler.add(job as ScheduledJob);
298
+ } catch {
299
+ /* skip invalid */
240
300
  }
241
301
  }
242
302
  ctx.autoRecoveryLast.clear();
@@ -255,6 +315,13 @@ function installSessionStartHandler(pi: ExtensionAPI, ctx: RegistrationContext):
255
315
  });
256
316
  const cache = ctx.getManifestCache(extensionCtx.cwd);
257
317
  updateCrewWidget(extensionCtx, ctx.widgetState, loadedConfig.config.ui, cache, ctx.getRunSnapshotCache(extensionCtx.cwd));
318
+ // Inline agent panel: keyboard-navigable rows under the prompt + per-agent
319
+ // transcript pane. Installed after the widget so the row projection sees
320
+ // the same caches the widget paint uses.
321
+ installInlinePanel(pi, extensionCtx, loadedConfig.config.ui);
322
+ // Returning from an agent view lands here: report any detached run that
323
+ // finished while the view was open, without waiting for a render tick.
324
+ deliverDetachedRunResults(pi, extensionCtx);
258
325
  updatePiCrewPowerbar(
259
326
  pi.events,
260
327
  extensionCtx.cwd,
@@ -540,6 +607,44 @@ export function filterManifestsForHealthNotifications(
540
607
  return manifests.filter((run) => !run.ownerSessionId || run.ownerSessionId === currentSessionId);
541
608
  }
542
609
 
610
+ /**
611
+ * bug-026 sub-issue C: runEventBus event types that mark a run TERMINAL,
612
+ * across BOTH type namespaces seen on the bus — dotted `run.*` strings (kept
613
+ * for parity with classifyEventChannel's WORKER_LIFECYCLE_TYPES, see
614
+ * src/ui/run-event-bus.ts:31-44) and underscore `run_*` (the native
615
+ * RunEventType namespace actually emitted by team-runner, e.g.
616
+ * `run_completed`). Exported for unit testing.
617
+ */
618
+ export const TERMINAL_RUN_EVENT_TYPES: ReadonlySet<string> = new Set([
619
+ "run.completed",
620
+ "run.failed",
621
+ "run.cancelled",
622
+ "run_completed",
623
+ "run_failed",
624
+ "run_cancelled",
625
+ ]);
626
+
627
+ /** True when a runEventBus event type marks the run terminal (either namespace). */
628
+ export function isTerminalRunEventType(type: string): boolean {
629
+ return TERMINAL_RUN_EVENT_TYPES.has(type);
630
+ }
631
+
632
+ /** Pure filter: drop `runId` from a preloaded-manifest frame (bug-026 sub-issue C eviction). */
633
+ export function evictRunFromManifests(manifests: TeamRunManifest[], runId: string): TeamRunManifest[] {
634
+ return manifests.filter((m) => m.runId !== runId);
635
+ }
636
+
637
+ /**
638
+ * Apply a runEventBus payload to a preloaded-manifest frame: evict the run on
639
+ * terminal events, pass through unchanged otherwise. This is the exact logic
640
+ * wired into the setupRenderLoop `runEventBus.onAny` subscription (bug-026
641
+ * sub-issue C); exported so tests can exercise it end-to-end against the
642
+ * real bus without spinning up the full render loop.
643
+ */
644
+ export function applyTerminalRunEventToManifests(manifests: TeamRunManifest[], event: { type: string; runId: string }): TeamRunManifest[] {
645
+ return isTerminalRunEventType(event.type) ? evictRunFromManifests(manifests, event.runId) : manifests;
646
+ }
647
+
543
648
  function setupRenderLoop(
544
649
  pi: ExtensionAPI,
545
650
  ctx: RegistrationContext,
@@ -625,13 +730,14 @@ function setupRenderLoop(
625
730
 
626
731
  const renderTick = (): void => {
627
732
  if (!ctx.currentCtx) return;
733
+ deliverDetachedRunResults(pi, ctx.currentCtx);
628
734
  const config = lastPreloadedConfig?.config.ui;
629
735
  const activeCache = lastFrameManifestCache ?? ctx.getManifestCache(ctx.currentCtx.cwd);
630
736
  const snapshotCache = lastFrameSnapshotCache ?? ctx.getRunSnapshotCache(ctx.currentCtx.cwd);
631
737
  const manifests = lastPreloadedManifests;
632
738
  if (!lastPreloadedConfig) backgroundPreload();
633
739
  if (ctx.uiState.liveSidebarRunId || ctx.uiState.dashboardOpen) {
634
- const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
740
+ const placement = toPiWidgetPlacement(config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement);
635
741
  if (ctx.widgetState.lastVisibility !== "hidden" || ctx.widgetState.lastPlacement !== placement) {
636
742
  setExtensionWidget(ctx.currentCtx, "pi-crew", undefined, { placement });
637
743
  setExtensionWidget(ctx.currentCtx, "pi-crew-active", undefined, { placement });
@@ -803,6 +909,12 @@ function setupRenderLoop(
803
909
  // re-renders within debounceMs of any agent lifecycle event.
804
910
  const sched = ctx.renderScheduler;
805
911
  const unsubscribeRunEvents = runEventBus.onAny((event) => {
912
+ // bug-026 sub-issue C: evict terminal runs from the preloaded manifest
913
+ // frame so a stale "running" entry cannot persist for the session
914
+ // lifetime. Additive to the renderTick GATE 1 / FIX #1 / GATE 2 snapshot
915
+ // purges (which invalidate the snapshot cache) — those gates do not
916
+ // touch lastPreloadedManifests itself.
917
+ lastPreloadedManifests = applyTerminalRunEventToManifests(lastPreloadedManifests, event);
806
918
  sched.schedule({
807
919
  runId: event.runId,
808
920
  source: "runEventBus",
@@ -905,7 +1017,7 @@ export function installCrewBrokerLifecycleController(_pi: ExtensionAPI, _ctx: Re
905
1017
 
906
1018
  function effectiveEnabled(): boolean {
907
1019
  // Env wins over config. PI_CREW_BROKER=1 forces on, =0 forces off.
908
- const envOverride = process.env.PI_CREW_BROKER;
1020
+ const envOverride = getCrewEnv("PI_CREW_BROKER");
909
1021
  if (envOverride === "0") return false;
910
1022
  // Config block: read fresh so a runtime config update takes effect.
911
1023
  try {
@@ -934,7 +1046,23 @@ export function installCrewBrokerLifecycleController(_pi: ExtensionAPI, _ctx: Re
934
1046
  starting = (async () => {
935
1047
  const cfg = (() => {
936
1048
  try {
937
- return loadConfig().config.broker;
1049
+ // B1 battery 2026-08-18 (third config-layer bug): loadConfig()
1050
+ // WITHOUT cwd reads only the user config — the workspace
1051
+ // .crew/config.json (where broker.waitMethodsEnabled:true would
1052
+ // live) was never merged, so the flag stayed default-false even
1053
+ // after the parser + merge fixes. Pass the SAME cwd the broker
1054
+ // itself uses below.
1055
+ return loadConfig(process.cwd()).config.broker;
1056
+ } catch {
1057
+ return undefined;
1058
+ }
1059
+ })();
1060
+ // T3/R5 (ADR-5 §10): governed-nesting + limits config for the delegate
1061
+ // surface — same cwd discipline as the broker block above.
1062
+ const nestingCfg = (() => {
1063
+ try {
1064
+ const c = loadConfig(process.cwd()).config;
1065
+ return { nesting: c.nesting, limits: c.limits };
938
1066
  } catch {
939
1067
  return undefined;
940
1068
  }
@@ -944,6 +1072,32 @@ export function installCrewBrokerLifecycleController(_pi: ExtensionAPI, _ctx: Re
944
1072
  socketPath: getBrokerSocketPath(sessionId),
945
1073
  maxFrameBytes: cfg?.maxFrameBytes ?? 262144,
946
1074
  outboundQueueCap: cfg?.outboundQueueCap ?? 256,
1075
+ // WP-2 review round 1 (P1): thread the capability gate into the
1076
+ // PRODUCTION broker — the constructor default (false) made
1077
+ // config.broker.waitMethodsEnabled a dead knob and the ADR-0
1078
+ // "then true" flip a silent no-op. Fail-closed when unset.
1079
+ waitMethodsEnabled: cfg?.waitMethodsEnabled ?? false,
1080
+ // T3/R5 (ADR-5 §10): governed-nesting capability gate — fail-closed
1081
+ // default; production threads config.nesting (sensitive: user config
1082
+ // only). Nested-slot sizing + admission-time model catalog (ADR-5 §7 —
1083
+ // the production wiring MUST supply it) + workspace gate mirror.
1084
+ nestingEnabled: nestingCfg?.nesting?.enabled ?? false,
1085
+ ...(nestingCfg?.nesting?.maxSlots !== undefined ? { nestingMaxSlots: nestingCfg.nesting.maxSlots } : {}),
1086
+ ...(nestingCfg?.nesting?.maxDepth !== undefined ? { nestingMaxDepth: nestingCfg.nesting.maxDepth } : {}),
1087
+ // ADR-5 §12: enabling the sensitive USER-config-only flag IS the manual
1088
+ // trust decision for the escalation surface.
1089
+ nestingTrustedEscalation: nestingCfg?.nesting?.enabled === true,
1090
+ ...(nestingCfg?.limits?.maxConcurrentWorkers !== undefined
1091
+ ? { globalWorkerSemaphore: nestingCfg.limits.maxConcurrentWorkers }
1092
+ : {}),
1093
+ serializeOnPathOverlap: nestingCfg?.limits?.serializeOnPathOverlap ?? false,
1094
+ modelCatalog: () => {
1095
+ try {
1096
+ return configuredModelInfosFromPiConfig(process.cwd()).map((info) => info.fullId);
1097
+ } catch {
1098
+ return undefined;
1099
+ }
1100
+ },
947
1101
  enabled: true,
948
1102
  cwd: process.cwd(),
949
1103
  });
@@ -960,10 +1114,17 @@ export function installCrewBrokerLifecycleController(_pi: ExtensionAPI, _ctx: Re
960
1114
  return starting!;
961
1115
  }
962
1116
 
963
- const issueForChild = async (runId: string, taskId?: string): Promise<BrokerSpawnCredentials | undefined> => {
1117
+ const issueForChild = async (runId: string, taskId?: string, childDepth?: number): Promise<BrokerSpawnCredentials | undefined> => {
964
1118
  if (!runId || typeof runId !== "string") return undefined;
965
1119
  if (!isRootSession(process.env)) return undefined;
966
1120
  if (!effectiveEnabled()) return undefined;
1121
+ // ADR-5 §4 (governed nesting): tokens are minted ONLY for children that
1122
+ // may themselves delegate — childDepth < resolved maxDepth. At the default
1123
+ // maxDepth=2 a delegate-spawned depth-2 grandchild gets NO credentials
1124
+ // (env containment: no PI_CREW_BROKER_SOCKET/TOKEN at depth 2; identity
1125
+ // routing via PI_CREW_BROKER_RUN_ID/TASK_ID is threaded unconditionally
1126
+ // elsewhere). Undefined childDepth = legacy worker spawn (depth 1).
1127
+ if (childDepth !== undefined && childDepth >= resolveCrewMaxDepth(undefined)) return undefined;
967
1128
  const sessionId = cachedSessionId;
968
1129
  if (!sessionId) return undefined;
969
1130
  try {
@@ -20,12 +20,12 @@
20
20
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
21
21
  import { loadConfig } from "../../config/config.ts";
22
22
  import { clearHooksScoped } from "../../hooks/registry.ts";
23
- import { terminateActiveChildPiProcesses } from "../../runtime/child-pi/child-pi.ts";
24
23
  import { stopAllWatchdogs } from "../../runtime/foreground-watchdog.ts";
25
24
  import { clearPiCrewPowerbar, disposePowerbarCoalescer } from "../../ui/powerbar-publisher.ts";
26
25
  import { stopCrewWidget } from "../../ui/widget/index.ts";
27
26
  import { logInternalError } from "../../utils/internal-error.ts";
28
27
  import { clearProjectRootCache } from "../../utils/paths.ts";
28
+ import { safeAbortAll } from "../../utils/safe-abort.ts";
29
29
  import { extractSessionId } from "../../utils/session-utils.ts";
30
30
  import { stopAsyncRunNotifier } from "../async-notifier.ts";
31
31
  import { uninstallCrewGlobalRegistry } from "../team-tool.ts";
@@ -135,7 +135,9 @@ function buildCleanupRuntime(ctx: RegistrationContext): () => void {
135
135
  ctx.stopSessionBoundSubagents();
136
136
  // P0 fix: also abort foreground team runs on session shutdown (not on session switch).
137
137
  // This is the only place where foreground team run controllers should be aborted.
138
- for (const controller of ctx.foregroundTeamRunControllers.values()) controller.abort();
138
+ // safe-abort: shutdown races completed runs whose spawned children already
139
+ // exited — abort() would throw AbortError from Node's child_process listener.
140
+ safeAbortAll(ctx.foregroundTeamRunControllers.values(), "cleanup-runtime.foreground-team");
139
141
  ctx.foregroundTeamRunControllers.clear();
140
142
  // RC-01: stop the foreground-run watchdog timers on full shutdown — they were
141
143
  // never cleared, so an active (non-terminal) foreground run left its watchdog
@@ -219,10 +221,26 @@ function buildStopSessionBoundSubagents(ctx: RegistrationContext): () => void {
219
221
  // Only abort subagent controllers — NOT foreground team runs.
220
222
  // Foreground team runs are bound to the session lifecycle; they will be aborted
221
223
  // by cleanupRuntime during session_shutdown.
222
- for (const controller of ctx.foregroundControllers.values()) controller.abort();
224
+ // safe-abort: these signals may be linked to spawned children (pipelines pass
225
+ // them into spawn options.signal); aborting after a child exited makes Node's
226
+ // child_process listener throw AbortError synchronously — inside a session
227
+ // event handler that would surface as uncaughtException and kill pi.
228
+ safeAbortAll(ctx.foregroundControllers.values(), "stop-session-bound.foreground");
223
229
  ctx.foregroundControllers.clear();
224
- ctx.subagentManager.abortAll("Session switching — foreground subagents cancelled.");
225
- terminateActiveChildPiProcesses();
230
+ try {
231
+ ctx.subagentManager.abortAll("Session switching — foreground subagents cancelled.");
232
+ } catch (error) {
233
+ logInternalError("stop-session-bound.abortAll", error);
234
+ }
235
+ // Deliberately NOT terminateActiveChildPiProcesses() here: the active
236
+ // child registry holds EVERY child pi spawned in this process —
237
+ // including foreground team-run workers. Killing them on a session
238
+ // switch (resume/new/fork) is exactly the regression "jumping into any
239
+ // session stops the run" (exit 143). Session-bound subagents are
240
+ // cancelled above via their own controllers; foreground runs must
241
+ // survive switches (P0) and are aborted only by cleanupRuntime on real
242
+ // shutdown; the process-exit hook (tryRegisterSessionCleanup) drains
243
+ // the registry when pi actually exits.
226
244
  ctx.disposeRenderSchedulerSubscriptions();
227
245
  ctx.renderScheduler?.dispose();
228
246
  ctx.renderScheduler = undefined;