@selesai/code 0.13.12 → 0.13.14

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 (203) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/extensions/pi-subagents/CHANGELOG.md +77 -1
  3. package/dist/extensions/pi-subagents/VISION.md +12 -1
  4. package/dist/extensions/pi-subagents/docs/agents.md +13 -6
  5. package/dist/extensions/pi-subagents/docs/configuration.md +35 -9
  6. package/dist/extensions/pi-subagents/docs/extension-api.md +1 -1
  7. package/dist/extensions/pi-subagents/docs/missions.md +1 -1
  8. package/dist/extensions/pi-subagents/docs/models.md +6 -6
  9. package/dist/extensions/pi-subagents/docs/observability.md +6 -3
  10. package/dist/extensions/pi-subagents/docs/tool-reference.md +3 -3
  11. package/dist/extensions/pi-subagents/docs/watchdog.md +92 -114
  12. package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
  13. package/dist/extensions/pi-subagents/install.mjs +1 -2
  14. package/dist/extensions/pi-subagents/package.json +1 -1
  15. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  16. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +6 -6
  17. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  18. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +4 -4
  20. package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +1 -1
  21. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -4
  22. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +3 -0
  23. package/dist/extensions/pi-subagents/src/agents/agents.ts +120 -124
  24. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +5 -1
  25. package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -0
  26. package/dist/extensions/pi-subagents/src/api/shared-types.ts +3 -0
  27. package/dist/extensions/pi-subagents/src/extension/config.ts +20 -0
  28. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
  29. package/dist/extensions/pi-subagents/src/extension/schemas.ts +6 -2
  30. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
  31. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +19 -13
  32. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +26 -8
  33. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -23
  34. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -2
  35. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  36. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +54 -3
  37. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +16 -0
  38. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +22 -2
  39. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +63 -6
  40. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -1
  41. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -12
  42. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +13 -0
  43. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +3 -1
  44. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -8
  45. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +59 -6
  46. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +95 -18
  47. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +11 -43
  48. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -0
  49. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  50. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +24 -3
  51. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +4 -0
  52. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -6
  53. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +1 -0
  54. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +33 -18
  55. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +73 -37
  56. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +33 -6
  57. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +4 -2
  58. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  59. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -7
  60. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -1
  61. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +467 -63
  62. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +3 -1
  63. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +0 -12
  64. package/dist/extensions/pi-subagents/src/shared/fork-session-cwd.ts +27 -0
  65. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +3 -0
  66. package/dist/extensions/pi-subagents/src/shared/types.ts +42 -4
  67. package/dist/extensions/pi-subagents/src/shared/utils.ts +18 -7
  68. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
  69. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +26 -12
  70. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +61 -2
  71. package/dist/extensions/pi-subagents/src/tui/fleet.ts +12 -7
  72. package/dist/extensions/pi-subagents/src/tui/render.ts +227 -14
  73. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +56 -34
  74. package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +77 -0
  75. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +5 -3
  76. package/dist/extensions/pi-subagents/src/watchdog/guidance.ts +20 -0
  77. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +16 -13
  78. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +10 -9
  79. package/dist/extensions/pi-subagents/src/watchdog/render.ts +4 -5
  80. package/dist/extensions/pi-subagents/src/watchdog/review.ts +15 -4
  81. package/dist/extensions/pi-subagents/src/watchdog/rules.ts +70 -0
  82. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +75 -92
  83. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +2 -1
  84. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +48 -104
  85. package/dist/extensions/pi-subagents/src/watchdog/types.ts +18 -32
  86. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +0 -1
  87. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -2
  88. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +439 -0
  89. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +28 -1
  90. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +335 -21
  91. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +4 -1
  92. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +1 -1
  93. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +22 -18
  94. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +0 -1
  95. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +21 -18
  96. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +307 -22
  97. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -0
  98. package/dist/extensions/pi-subagents/test/support/helpers.ts +25 -0
  99. package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +15 -4
  100. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -2
  101. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +96 -15
  102. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +53 -5
  103. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +20 -0
  104. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +221 -8
  105. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +45 -24
  106. package/dist/extensions/pi-subagents/test/unit/agent-scan-dirs.test.ts +133 -0
  107. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +28 -2
  108. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +1 -1
  109. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  110. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +4 -1
  111. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +18 -4
  112. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +16 -0
  113. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +38 -0
  114. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
  115. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +21 -0
  116. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +19 -6
  117. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +26 -0
  118. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +22 -1
  119. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +2 -6
  120. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +66 -1
  121. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +1 -1
  122. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -10
  123. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +136 -18
  124. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +20 -0
  125. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +22 -0
  126. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +1 -0
  127. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +8 -0
  128. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +127 -48
  129. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -0
  130. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +17 -0
  131. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +88 -0
  132. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -2
  133. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +9 -9
  134. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
  135. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +9 -3
  136. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +22 -9
  137. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +91 -8
  138. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +27 -0
  139. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +50 -0
  140. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +0 -1
  141. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +2 -2
  142. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +34 -0
  143. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -2
  144. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +73 -13
  145. package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +79 -0
  146. package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +2 -4
  147. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -8
  148. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +48 -3
  149. package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +37 -0
  150. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +36 -118
  151. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +1 -1
  152. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +37 -14
  153. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +45 -26
  154. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +223 -0
  155. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +50 -1
  156. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +22 -0
  157. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +136 -4
  158. package/dist/extensions/pi-zentui/README.md +19 -5
  159. package/dist/extensions/pi-zentui/docs/configuration.md +37 -9
  160. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +29 -0
  161. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +31 -2
  162. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +55 -11
  163. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +115 -3
  164. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +119 -2
  165. package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +1768 -0
  166. package/dist/extensions/pi-zentui/extensions/zentui/thinking-status.ts +69 -0
  167. package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +155 -0
  168. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +2 -1
  169. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +16 -2
  170. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
  171. package/dist/extensions/pi-zentui/test/config-load-lifecycle.test.ts +124 -0
  172. package/dist/extensions/pi-zentui/test/config.test.ts +82 -2
  173. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +19 -4
  174. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +58 -9
  175. package/dist/extensions/pi-zentui/test/package-contents.mjs +33 -0
  176. package/dist/extensions/pi-zentui/test/prototype-patch-registry.test.ts +131 -0
  177. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +4 -4
  178. package/dist/extensions/pi-zentui/test/settings-command.test.ts +289 -7
  179. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +106 -0
  180. package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +51 -0
  181. package/dist/extensions/pi-zentui/test/thinking-experimental-loader-diagnostics.mjs +54 -0
  182. package/dist/extensions/pi-zentui/test/thinking-experimental-missing-export.test.ts +44 -0
  183. package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +297 -0
  184. package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +2329 -0
  185. package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +1668 -0
  186. package/dist/extensions/pi-zentui/test/thinking-status.test.ts +85 -0
  187. package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +122 -0
  188. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +58 -0
  189. package/dist/skills/brandkit/SKILL.md +0 -2
  190. package/dist/skills/{industrial-brutalist-ui → brutalist}/SKILL.md +1 -3
  191. package/dist/skills/gpt-taste/SKILL.md +0 -2
  192. package/dist/skills/image-to-code/SKILL.md +0 -2
  193. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -2
  194. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -2
  195. package/dist/skills/{minimalist-ui → minimalist}/SKILL.md +1 -3
  196. package/dist/skills/{full-output-enforcement → output}/SKILL.md +1 -3
  197. package/dist/skills/{redesign-existing-projects → redesign}/SKILL.md +1 -3
  198. package/dist/skills/{high-end-visual-design → soft}/SKILL.md +1 -3
  199. package/dist/skills/stitch/DESIGN.md +121 -0
  200. package/dist/skills/{stitch-design-taste → stitch}/SKILL.md +1 -3
  201. package/dist/skills/{design-taste-frontend → taste}/SKILL.md +1 -3
  202. package/dist/skills/{design-taste-frontend-v1 → taste-v1}/SKILL.md +2 -4
  203. package/package.json +1 -1
@@ -9,11 +9,13 @@
9
9
 
10
10
  import { after, afterEach, before, beforeEach, describe, it } from "node:test";
11
11
  import assert from "node:assert/strict";
12
+ import fsDefault from "node:fs";
12
13
  import { spawnSync } from "node:child_process";
13
14
  import * as fs from "node:fs";
15
+ import { syncBuiltinESMExports } from "node:module";
14
16
  import * as os from "node:os";
15
17
  import * as path from "node:path";
16
- import { createEventBus, createMockPi, createTempDir, events, makeAgent, makeMinimalCtx, removeTempDir, tryImport } from "../support/helpers.ts";
18
+ import { createEventBus, createMockPi, createTempDir, events, makeAgent, makeMinimalCtx, removeTempDir, resolveMockPiCallArgs, tryImport } from "../support/helpers.ts";
17
19
  import type { MockPi } from "../support/helpers.ts";
18
20
  import { deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest } from "../../src/runs/background/control-channel.ts";
19
21
  import { waitForSubagents } from "../../src/runs/background/subagent-wait.ts";
@@ -25,8 +27,10 @@ import { registerSubagentCapabilityCeiling } from "../../src/api/capability-ceil
25
27
  import { resolveSubagentLaunchContract } from "../../src/api/preflight.ts";
26
28
  import { discoverAgents } from "../../src/agents/agents.ts";
27
29
  import { runSync } from "../../src/runs/foreground/execution.ts";
28
- import { ACTIVE_ASYNC_CAPACITY_DIR, acquireActiveAsyncCapacity, activeAsyncCapacitySessionKey } from "../../src/runs/background/active-async-capacity.ts";
30
+ import { ACTIVE_ASYNC_CAPACITY_DIR, acquireActiveAsyncCapacity, activeAsyncCapacitySessionKey, getActiveAsyncCapacitySnapshot } from "../../src/runs/background/active-async-capacity.ts";
29
31
  import { deriveForkPromptCacheKey, SUBAGENT_FORK_CACHE_KEY_ENV } from "../../src/runs/shared/pi-args.ts";
32
+ import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../../src/runs/shared/pi-spawn.ts";
33
+ import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../src/shared/utils.ts";
30
34
  import { clearExclusions, recordModelFailure } from "../../src/runs/shared/model-exclusions.ts";
31
35
 
32
36
  interface LaunchResolvedExtensions {
@@ -82,7 +86,7 @@ interface AsyncResultPayload {
82
86
  totalTokens?: { input: number; output: number; total: number };
83
87
  totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
84
88
  usageBudget?: UsageBudgetState;
85
- results: Array<{ agent?: string; sessionName?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; timeoutRecovery?: { changedFiles?: string[]; message?: string; warning?: string; recoveryNeeded?: boolean; reason?: string; reportStatus?: string }; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; turns: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean; message?: string }; settlementDiagnostic?: { finalTextPresent?: boolean; mutation?: { expected?: boolean; attempted?: boolean; observed?: boolean }; requiredOutput?: { kind?: string; path?: string; missing?: boolean }; afterCompactionSettlement?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string; transcriptPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
89
+ results: Array<{ agent?: string; sessionName?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; timeoutRecovery?: { changedFiles?: string[]; message?: string; warning?: string; recoveryNeeded?: boolean; reason?: string; reportStatus?: string }; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; thinking?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; turns: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean; message?: string }; settlementDiagnostic?: { finalTextPresent?: boolean; mutation?: { expected?: boolean; attempted?: boolean; observed?: boolean }; requiredOutput?: { kind?: string; path?: string; missing?: boolean }; afterCompactionSettlement?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string; transcriptPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
86
90
  outputs?: Record<string, { text?: string; structured?: unknown }>;
87
91
  workflowGraph?: { nodes?: Array<{ kind?: string; label?: string; phase?: string; status?: string; acceptanceStatus?: string; error?: string; outputName?: string; structured?: boolean; children?: Array<{ label?: string; outputName?: string; itemKey?: string; status?: string; acceptanceStatus?: string; error?: string }> }> };
88
92
  parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
@@ -154,6 +158,7 @@ interface AsyncStatusPayload {
154
158
 
155
159
  interface MockPiCallRecord {
156
160
  args?: string[];
161
+ effectiveArgs?: string[];
157
162
  systemPrompts?: Array<{ mode?: string; path?: string; text?: string; error?: string }>;
158
163
  requiredChildTools?: string[];
159
164
  }
@@ -194,7 +199,7 @@ async function withIsolatedWatchdogSettings<T>(projectDir: string, run: () => Pr
194
199
  }
195
200
  }
196
201
 
197
- function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "autofollow" | "settling" | "stale" | "failed", seq: number, followUpPending = false) {
202
+ function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "stale" | "failed", seq: number) {
198
203
  return {
199
204
  type: CHILD_WATCHDOG_STATUS_EVENT,
200
205
  runId,
@@ -204,7 +209,6 @@ function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "autof
204
209
  seq,
205
210
  phase,
206
211
  ts: Date.now() + seq,
207
- followUpPending,
208
212
  };
209
213
  }
210
214
 
@@ -379,7 +383,7 @@ async function waitForMockPiCall(mockPi: MockPi, index: number, timeoutMs = 30_0
379
383
  if (callFile) {
380
384
  const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
381
385
  assert.ok(Array.isArray(payload.args), "expected recorded args");
382
- return { args: payload.args, systemPrompts: payload.systemPrompts ?? [] };
386
+ return { args: resolveMockPiCallArgs(payload), systemPrompts: payload.systemPrompts ?? [] };
383
387
  }
384
388
  if (Date.now() > deadline) assert.fail(`Timed out waiting for recorded mock pi call ${index}`);
385
389
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -398,7 +402,7 @@ function readLastMockPiArgs(mockPi: MockPi): string[] {
398
402
  assert.ok(callFile, "expected a recorded mock pi call");
399
403
  const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
400
404
  assert.ok(Array.isArray(payload.args), "expected recorded args");
401
- return payload.args;
405
+ return resolveMockPiCallArgs(payload);
402
406
  }
403
407
 
404
408
  function readMockPiArgs(mockPi: MockPi, index: number): string[] {
@@ -409,7 +413,7 @@ function readMockPiArgs(mockPi: MockPi, index: number): string[] {
409
413
  assert.ok(callFile, `expected recorded call ${index}`);
410
414
  const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
411
415
  assert.ok(Array.isArray(payload.args), "expected recorded args");
412
- return payload.args;
416
+ return resolveMockPiCallArgs(payload);
413
417
  }
414
418
 
415
419
  function readMockPiRequiredTools(mockPi: MockPi, index: number): string[] {
@@ -428,9 +432,10 @@ function readMockPiArgsMatching(mockPi: MockPi, text: string): string[] {
428
432
  .filter((name) => name.startsWith("call-") && name.endsWith(".json"))
429
433
  .sort();
430
434
  for (const callFile of callFiles) {
431
- const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as { args?: string[] };
435
+ const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
432
436
  assert.ok(Array.isArray(payload.args), "expected recorded args");
433
- if (payload.args.join("\n").includes(text)) return payload.args;
437
+ const args = resolveMockPiCallArgs(payload);
438
+ if (args.join("\n").includes(text)) return args;
434
439
  }
435
440
  assert.fail(`expected recorded call containing ${text}`);
436
441
  }
@@ -567,6 +572,56 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
567
572
  assert.equal(status.endedAt !== undefined, true);
568
573
  });
569
574
 
575
+ it("preserves effective thinking in the completed async result artifact", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
576
+ mockPi.onCall({ output: "completed with thinking" });
577
+ const id = `async-result-thinking-${Date.now().toString(36)}`;
578
+ const launch = executeAsyncSingle(id, {
579
+ agent: "worker",
580
+ task: "Report the configured reasoning level.",
581
+ agentConfig: makeAgent("worker", { model: "mock/test-model", thinking: "high", completionGuard: false }),
582
+ availableModels: [{ provider: "mock", id: "test-model", fullId: "mock/test-model" }],
583
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-result-thinking" },
584
+ artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
585
+ shareEnabled: false,
586
+ maxSubagentDepth: 2,
587
+ acceptance: false,
588
+ });
589
+
590
+ assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
591
+ const payload = await readAsyncPayload(id);
592
+ const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
593
+ assert.equal(payload.success, true);
594
+ assert.equal(payload.results[0]?.thinking, "high");
595
+ assert.equal(status.steps?.[0]?.thinking, "high");
596
+ });
597
+
598
+ it("forwards the installed Pi package root through the async runner environment", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
599
+ // The child resolves piPackageRoot as resolvePiPackageRoot() ??
600
+ // resolveInstalledPiPackageRoot() at module load. In this fork the host
601
+ // repo root is itself named @selesai/code, so the argv1 walk finds it
602
+ // before the shim; mirror the child's expression for the expectation.
603
+ const packageRoot = resolvePiPackageRoot() ?? resolveInstalledPiPackageRoot();
604
+ assert.ok(packageRoot, "expected the test Pi package to be installed");
605
+ mockPi.onCall({ echoEnv: [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV] });
606
+ const id = `async-package-root-env-${Date.now().toString(36)}`;
607
+ executeAsyncSingle(id, {
608
+ agent: "worker",
609
+ task: "Report the forwarded package root.",
610
+ agentConfig: makeAgent("worker", { completionGuard: false }),
611
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-package-root-env" },
612
+ artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
613
+ shareEnabled: false,
614
+ maxSubagentDepth: 2,
615
+ acceptance: false,
616
+ });
617
+
618
+ const payload = await readAsyncPayload(id);
619
+ assert.equal(payload.success, true);
620
+ assert.deepEqual(JSON.parse(payload.results[0]?.output ?? "{}"), {
621
+ [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]: packageRoot,
622
+ });
623
+ });
624
+
570
625
  it("persists the bounded usage projection in async results and metadata", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
571
626
  mockPi.onCall({ output: "accounted output" });
572
627
  const id = `async-usage-artifact-${Date.now().toString(36)}`;
@@ -990,6 +1045,124 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
990
1045
  assert.equal(mockPi.callCount(), 0);
991
1046
  });
992
1047
 
1048
+ it("establishes a lifecycle sidecar before an external CLI can run", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
1049
+ const id = `async-external-lifecycle-${Date.now().toString(36)}`;
1050
+ const startedRunners: string[] = [];
1051
+ const launch = executeAsyncSingle(id, {
1052
+ agent: "external",
1053
+ task: "Run external",
1054
+ agentConfig: makeAgent("external", {
1055
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", "setTimeout(() => process.stdout.write('external lifecycle'), 500)"] },
1056
+ } as never),
1057
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1058
+ artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1059
+ shareEnabled: false,
1060
+ activeAsyncCapacity: {
1061
+ owner: {},
1062
+ markStarted: (runnerProcessInstanceId: string) => { startedRunners.push(runnerProcessInstanceId); },
1063
+ markWorkflowStarted() {},
1064
+ rollback: () => false,
1065
+ rollbackBeforeRunnerProceed: () => false,
1066
+ reconcile: () => ({ used: 1, limit: 1 }),
1067
+ } as never,
1068
+ });
1069
+
1070
+ assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
1071
+ const proof = JSON.parse(fs.readFileSync(path.join(launch.details.asyncDir ?? "", "process-terminal.json"), "utf-8")) as { state?: string; runId?: string; runnerProcessInstanceId?: string };
1072
+ assert.equal(proof.state, "pending");
1073
+ assert.equal(proof.runId, id);
1074
+ assert.deepEqual(startedRunners, [proof.runnerProcessInstanceId]);
1075
+ const payload = await readAsyncPayload(id);
1076
+ assert.equal(payload.success, true);
1077
+ assert.match(payload.results[0]?.output ?? "", /external lifecycle/);
1078
+ });
1079
+
1080
+ it("continues startup when proceed authorization is published but temp cleanup fails", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async (t) => {
1081
+ const id = `async-external-proceed-cleanup-${Date.now().toString(36)}`;
1082
+ const originalRmSync = fsDefault.rmSync;
1083
+ let proceedCleanupFailures = 0;
1084
+ let rollbackCount = 0;
1085
+ t.mock.method(fsDefault, "rmSync", ((target: fs.PathLike, options?: fs.RmOptions) => {
1086
+ const targetPath = String(target);
1087
+ if (targetPath.includes(".runner-startup-proceed.json.") && targetPath.endsWith(".tmp") && proceedCleanupFailures === 0) {
1088
+ proceedCleanupFailures += 1;
1089
+ throw new Error("simulated proceed cleanup failure");
1090
+ }
1091
+ return originalRmSync(target, options);
1092
+ }) as typeof fsDefault.rmSync);
1093
+ syncBuiltinESMExports();
1094
+ t.after(() => syncBuiltinESMExports());
1095
+
1096
+ const launch = executeAsyncSingle(id, {
1097
+ agent: "external",
1098
+ task: "Run external",
1099
+ agentConfig: makeAgent("external", {
1100
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", "process.stdout.write('external proceed cleanup')"] },
1101
+ } as never),
1102
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
1103
+ artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1104
+ shareEnabled: false,
1105
+ activeAsyncCapacity: {
1106
+ owner: {},
1107
+ markStarted() {},
1108
+ markWorkflowStarted() {},
1109
+ rollback() { rollbackCount += 1; return false; },
1110
+ rollbackBeforeRunnerProceed() { rollbackCount += 1; return false; },
1111
+ reconcile: () => ({ used: 1, limit: 1 }),
1112
+ } as never,
1113
+ });
1114
+
1115
+ assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
1116
+ assert.equal(proceedCleanupFailures, 1);
1117
+ assert.equal(rollbackCount, 0);
1118
+ const payload = await readAsyncPayload(id);
1119
+ assert.equal(payload.success, true);
1120
+ assert.match(payload.results[0]?.output ?? "", /external proceed cleanup/);
1121
+ });
1122
+
1123
+ it("fails pre-proceed capacity bind without rebinding the killed runner", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async (t) => {
1124
+ const parentSessionId = `session-capacity-bind-${Date.now().toString(36)}`;
1125
+ const id = `async-capacity-bind-failure-${Date.now().toString(36)}`;
1126
+ const asyncDir = path.join(ASYNC_DIR, id);
1127
+ let ownerWrites = 0;
1128
+ fs.rmSync(path.join(ACTIVE_ASYNC_CAPACITY_DIR, activeAsyncCapacitySessionKey(parentSessionId)), { recursive: true, force: true });
1129
+ const activeAsyncCapacity = acquireActiveAsyncCapacity({ sessionId: parentSessionId, limit: 1, runId: id, kind: "runner", asyncDir }, {
1130
+ writeOwner(filePath, owner) {
1131
+ ownerWrites += 1;
1132
+ if (ownerWrites === 1) throw new Error("simulated durable bind failure");
1133
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
1134
+ fs.writeFileSync(filePath, JSON.stringify(owner, null, 2));
1135
+ },
1136
+ });
1137
+ assert.ok(activeAsyncCapacity);
1138
+ const originalKill = process.kill;
1139
+ t.mock.method(process, "kill", ((pid: number, signal?: NodeJS.Signals | 0) => {
1140
+ if (signal === 0) return true;
1141
+ return originalKill(pid, signal);
1142
+ }) as typeof process.kill);
1143
+
1144
+ const launch = executeAsyncSingle(id, {
1145
+ agent: "external",
1146
+ task: "Run external",
1147
+ agentConfig: makeAgent("external", {
1148
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", "setTimeout(() => {}, 30000)"] },
1149
+ } as never),
1150
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: parentSessionId },
1151
+ artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
1152
+ shareEnabled: false,
1153
+ activeAsyncCapacity,
1154
+ });
1155
+
1156
+ assert.equal(launch.isError, true);
1157
+ assert.match(launch.content[0]?.text ?? "", /Failed to establish async runner capacity ownership/);
1158
+ assert.equal(ownerWrites, 1);
1159
+ const status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
1160
+ assert.equal(status.state, "failed");
1161
+ assert.match(status.error ?? "", /Failed to establish async runner capacity ownership/);
1162
+ assert.equal(status.processTerminal?.state, "not-started");
1163
+ assert.deepEqual(getActiveAsyncCapacitySnapshot(parentSessionId, 1), { used: 0, limit: 1 });
1164
+ });
1165
+
993
1166
  it("lets explicit fast false opt out async external chains from inherited fast mode", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
994
1167
  const agent = makeAgent("external", {
995
1168
  fast: true,
@@ -1234,8 +1407,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1234
1407
  await waitForMockPiCall(mockPi, 0);
1235
1408
  const callFile = fs.readdirSync(mockPi.dir).find((name) => name.endsWith(".json"));
1236
1409
  assert.ok(callFile);
1237
- const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as { args?: string[] };
1238
- assert.match(call.args?.join("\n") ?? "", new RegExp(sentinel));
1410
+ const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
1411
+ assert.match(resolveMockPiCallArgs(call).join("\n"), new RegExp(sentinel));
1239
1412
 
1240
1413
  const payload = await readAsyncPayload(id);
1241
1414
  const artifactPaths = payload.results[0]?.artifactPaths;
@@ -1642,7 +1815,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
1642
1815
  fs.writeFileSync(path.join(repo, "input.md"), "parallel partial child change\n", "utf-8");
1643
1816
  const resultPath = await waitForAsyncResultFile(id, 8_000);
1644
1817
  const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
1645
- const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
1818
+ const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed", 30_000);
1646
1819
  assert.equal(payload.state, "failed");
1647
1820
  assert.equal(payload.success, false);
1648
1821
  assert.equal(payload.exitCode, 1);
@@ -2152,7 +2325,6 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2152
2325
  assert.match(interactiveResult.content[0]?.text ?? "", /does not need a wait call/);
2153
2326
  assert.match(interactiveResult.content[0]?.text ?? "", /native completion notification/);
2154
2327
  assert.doesNotMatch(interactiveResult.content[0]?.text ?? "", /bg_wait\(\{ id:/);
2155
- assert.doesNotMatch(interactiveResult.content[0]?.text ?? "", /subagent_wait/);
2156
2328
  assert.doesNotMatch(interactiveResult.content[0]?.text ?? "", /auto-drain/);
2157
2329
  await waitForAsyncResultFile(interactiveId, 30_000);
2158
2330
 
@@ -2168,7 +2340,6 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2168
2340
  assert.match(parallelResult.content[0]?.text ?? "", /Async parallel:/);
2169
2341
  assert.match(parallelResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
2170
2342
  assert.match(parallelResult.content[0]?.text ?? "", /Use bg_wait only/i);
2171
- assert.doesNotMatch(parallelResult.content[0]?.text ?? "", /subagent_wait/);
2172
2343
  assert.equal(startedEvent(parallelId).goal, "[prompt redacted]");
2173
2344
  const parallelResultPath = await waitForAsyncResultFile(parallelId, 10_000);
2174
2345
  const parallelPayload = JSON.parse(fs.readFileSync(parallelResultPath, "utf-8")) as { agent?: string; mode?: string };
@@ -2260,7 +2431,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2260
2431
  const asyncId = result.details?.asyncId;
2261
2432
  assert.ok(asyncId, "expected asyncId");
2262
2433
  const payload = await readAsyncPayload(asyncId);
2263
- assert.equal(payload.results[0]?.acceptance?.effectiveAcceptance.level, "attested");
2434
+ assert.equal(payload.results[0]?.acceptance?.effectiveAcceptance.level, "none");
2264
2435
  });
2265
2436
 
2266
2437
 
@@ -2293,7 +2464,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2293
2464
  maxSubagentDepth: 2,
2294
2465
  });
2295
2466
  const reviewPayload = await readAsyncPayload(reviewId);
2296
- assert.equal(reviewPayload.results[0]?.acceptance?.effectiveAcceptance?.level, "attested");
2467
+ assert.equal(reviewPayload.results[0]?.acceptance?.effectiveAcceptance?.level, "none");
2297
2468
  });
2298
2469
 
2299
2470
 
@@ -2334,7 +2505,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2334
2505
  assert.equal(fs.readFileSync(artifactPaths[0], "utf-8"), "chain first report");
2335
2506
  assert.equal(fs.readFileSync(artifactPaths[1], "utf-8"), "chain second report");
2336
2507
  const calls = fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).sort();
2337
- const taskArgs = calls.map((name) => (JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord).args?.at(-1) ?? "");
2508
+ const taskArgs = calls.map((name) => {
2509
+ const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord;
2510
+ return resolveMockPiCallArgs(call).at(-1) ?? "";
2511
+ });
2338
2512
  assert.ok(taskArgs.find((task) => task.includes("Write first"))?.includes(path.join("parallel-0", "0-worker", "context.md")));
2339
2513
  assert.ok(taskArgs.find((task) => task.includes("Write second"))?.includes(path.join("parallel-0", "1-worker", "context.md")));
2340
2514
  });
@@ -2773,10 +2947,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
2773
2947
  assert.ok(!result.isError);
2774
2948
  const payload = await readAsyncPayload(id);
2775
2949
  const explorerResults = payload.results.filter((child) => child.agent === "explorer");
2776
- assert.deepEqual(explorerResults.map((child) => child.acceptance?.effectiveAcceptance?.level), ["attested", "attested"]);
2950
+ assert.deepEqual(explorerResults.map((child) => child.acceptance?.effectiveAcceptance?.level), ["none", "none"]);
2777
2951
  const dynamicNode = payload.workflowGraph?.nodes?.[1];
2778
- assert.equal(dynamicNode?.acceptanceStatus, "attested");
2779
- assert.deepEqual(dynamicNode?.children?.map((child) => child.acceptanceStatus), ["attested", "attested"]);
2952
+ assert.equal(dynamicNode?.acceptanceStatus, "not-required");
2953
+ assert.deepEqual(dynamicNode?.children?.map((child) => child.acceptanceStatus), ["not-required", "not-required"]);
2780
2954
  });
2781
2955
 
2782
2956
  it("infers async dynamic acceptance after materializing item templates", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
@@ -4010,6 +4184,40 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4010
4184
  assert.equal(fs.readFileSync(outputPath, "utf-8"), "async full output\nwith details");
4011
4185
  });
4012
4186
 
4187
+ it("removes Pi turn-timing telemetry from runtime-persisted background output", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
4188
+ const report = "## Review\n\nVERDICT: FINDINGS";
4189
+ const timingFooter = "\x1b[38;2;136;136;136m✻ Turn took 5m 54s (Total time 5m 54s · 2 turns)\x1b[0m";
4190
+ mockPi.onCall({ output: `${report}\n\n${timingFooter}` });
4191
+ const id = `async-timing-footer-${Date.now().toString(36)}`;
4192
+ const outputPath = path.join(tempDir, "async-review.md");
4193
+ executeAsyncSingle(id, {
4194
+ agent: "reviewer",
4195
+ task: "Review without modifying files",
4196
+ agentConfig: makeAgent("reviewer", { tools: ["read", "grep", "find", "ls"] }),
4197
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4198
+ artifactConfig: {
4199
+ enabled: false,
4200
+ includeInput: false,
4201
+ includeOutput: false,
4202
+ includeJsonl: false,
4203
+ includeMetadata: false,
4204
+ cleanupDays: 7,
4205
+ },
4206
+ shareEnabled: false,
4207
+ sessionRoot: path.join(tempDir, "sessions"),
4208
+ output: outputPath,
4209
+ acceptance: false,
4210
+ maxSubagentDepth: 2,
4211
+ });
4212
+
4213
+ const resultPath = await waitForAsyncResultFile(id);
4214
+ const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
4215
+ assert.equal(payload.success, true);
4216
+ assert.equal(fs.readFileSync(outputPath, "utf-8"), report);
4217
+ assert.doesNotMatch(payload.summary ?? "", /Turn took/);
4218
+ assert.doesNotMatch(payload.results[0]?.output ?? "", /Turn took/);
4219
+ });
4220
+
4013
4221
  it("background single runs route relative outputs to outputBaseDir", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
4014
4222
  mockPi.onCall({ output: "async configured report" });
4015
4223
  const id = `async-configured-output-base-${Date.now().toString(36)}`;
@@ -4384,6 +4592,29 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4384
4592
  assert.deepEqual(payload.results[0]?.structuredOutput, { ok: true, note: "async" });
4385
4593
  });
4386
4594
 
4595
+ it("background outputSchema runs fail closed when required acceptanceReport is missing", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
4596
+ mockPi.onCall({ output: "structured", structuredOutput: { ok: true } });
4597
+ const id = `async-schema-missing-acceptance-${Date.now().toString(36)}`;
4598
+
4599
+ executeAsyncSingle(id, {
4600
+ agent: "worker",
4601
+ task: "Return structured data",
4602
+ agentConfig: makeAgent("worker", { completionGuard: false }),
4603
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
4604
+ artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
4605
+ shareEnabled: false,
4606
+ sessionRoot: path.join(tempDir, "sessions"),
4607
+ maxSubagentDepth: 2,
4608
+ acceptance: { level: "checked", report: "on" },
4609
+ structuredOutputSchema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } },
4610
+ });
4611
+
4612
+ const payload = JSON.parse(fs.readFileSync(await waitForAsyncResultFile(id, 10_000), "utf-8")) as AsyncResultPayload;
4613
+ assert.equal(payload.success, false);
4614
+ assert.match(payload.results[0]?.error ?? "", /Missing acceptanceReport/);
4615
+ assert.equal(payload.results[0]?.acceptance?.status, "rejected");
4616
+ });
4617
+
4387
4618
  it("background bash-enabled non-implementation agents can opt out of the completion guard", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
4388
4619
  mockPi.onCall({ output: "cold start test after patch" });
4389
4620
 
@@ -4829,6 +5060,59 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
4829
5060
  assert.equal(args[args.indexOf("--model") + 1], "mock/fallback");
4830
5061
  });
4831
5062
 
5063
+ it("aligns initial and resumed background forked sessions with an explicit child cwd", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
5064
+ mockPi.onCall({ output: "Forked async work" });
5065
+ const parentCwd = fs.realpathSync(tempDir);
5066
+ const childCwd = path.join(tempDir, "child-cwd");
5067
+ fs.mkdirSync(childCwd);
5068
+ const parentSessionFile = path.join(tempDir, "parent-cross-cwd.jsonl");
5069
+ const forkedSessionFile = path.join(tempDir, "forked-cross-cwd.jsonl");
5070
+ const parentHeader = { type: "session", version: 1, id: "parent", cwd: parentCwd };
5071
+ const childHeader = { type: "session", version: 1, id: "child", cwd: parentCwd, parentSession: parentSessionFile };
5072
+ fs.writeFileSync(parentSessionFile, `${JSON.stringify(parentHeader)}\n`, "utf-8");
5073
+ fs.writeFileSync(forkedSessionFile, `${JSON.stringify(childHeader)}\n`, "utf-8");
5074
+ const ctx = {
5075
+ ...makeMinimalCtx(parentCwd),
5076
+ sessionManager: {
5077
+ getSessionId: () => "session-cross-cwd",
5078
+ getSessionFile: () => parentSessionFile,
5079
+ getLeafId: () => "leaf-current",
5080
+ openSession: () => ({ createBranchedSession: () => forkedSessionFile }),
5081
+ },
5082
+ };
5083
+
5084
+ const executor = makeAsyncExecutor([makeAgent("worker")]);
5085
+ const launch = await executor.execute(
5086
+ "forked-cross-cwd",
5087
+ { agent: "worker", task: "Do work", async: true, context: "fork", cwd: childCwd },
5088
+ new AbortController().signal,
5089
+ undefined,
5090
+ ctx,
5091
+ ) as AsyncExecutionResult;
5092
+ assert.ok(!launch.isError, launch.content[0]?.text);
5093
+ assert.ok(launch.details.asyncId);
5094
+ await readAsyncPayload(launch.details.asyncId);
5095
+
5096
+ const sessionHeader = JSON.parse(fs.readFileSync(forkedSessionFile, "utf-8").split("\n", 1)[0]!) as { cwd?: string };
5097
+ assert.equal(sessionHeader.cwd, fs.realpathSync.native(childCwd));
5098
+
5099
+ fs.writeFileSync(forkedSessionFile, `${JSON.stringify(childHeader)}\n`, "utf-8");
5100
+ mockPi.onCall({ output: "Resumed async work" });
5101
+ const resumed = await executor.execute(
5102
+ "resume-cross-cwd",
5103
+ { action: "resume", id: launch.details.asyncId, message: "Continue" },
5104
+ new AbortController().signal,
5105
+ undefined,
5106
+ ctx,
5107
+ ) as AsyncExecutionResult;
5108
+ assert.ok(!resumed.isError, resumed.content[0]?.text);
5109
+ assert.ok(resumed.details.asyncId);
5110
+ await readAsyncPayload(resumed.details.asyncId);
5111
+
5112
+ const resumedHeader = JSON.parse(fs.readFileSync(forkedSessionFile, "utf-8").split("\n", 1)[0]!) as { cwd?: string };
5113
+ assert.equal(resumedHeader.cwd, fs.realpathSync.native(childCwd));
5114
+ });
5115
+
4832
5116
  it("background forked runs inherit a parent model outside the registry", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
4833
5117
  mockPi.onCall({ output: "Forked async work" });
4834
5118
  const parentSessionFile = path.join(tempDir, "parent.jsonl");
@@ -5666,6 +5950,35 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
5666
5950
  });
5667
5951
  });
5668
5952
 
5953
+ it("background runs carry unaddressed child watchdog blockers into the result payload and acceptance", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
5954
+ await withIsolatedWatchdogSettings(tempDir, async () => {
5955
+ writeWatchdogSettings(tempDir);
5956
+ const id = `async-watchdog-blocker-${Date.now().toString(36)}`;
5957
+ mockPi.onCall({ jsonl: [events.acceptanceReport(), events.watchdogWarning("blocker", "Claims tests passed without running them")] });
5958
+
5959
+ executeAsyncSingle(id, {
5960
+ agent: "worker",
5961
+ task: "Do work",
5962
+ agentConfig: makeAgent("worker"),
5963
+ ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
5964
+ artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
5965
+ shareEnabled: false,
5966
+ sessionRoot: path.join(tempDir, "sessions"),
5967
+ maxSubagentDepth: 2,
5968
+ acceptance: { level: "checked", criteria: ["Ship it"] },
5969
+ });
5970
+
5971
+ const resultPath = await waitForAsyncResultFile(id, 10_000);
5972
+ const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
5973
+ assert.equal(payload.success, false);
5974
+ const child = payload.results[0] as AsyncResultPayload["results"][number] & { watchdog?: { warnings?: Array<{ severity: string; addressed: boolean; summary: string }> } };
5975
+ assert.deepEqual(child.watchdog?.warnings?.map((warning) => [warning.severity, warning.addressed]), [["blocker", false]]);
5976
+ const check = child.acceptance?.runtimeChecks?.find((entry) => entry.id === "watchdog-blocker");
5977
+ assert.equal(check?.status, "failed");
5978
+ assert.match(check?.message ?? "", /Unresolved watchdog blocker/);
5979
+ });
5980
+ });
5981
+
5669
5982
  it("background forced drain after final assistant output is cleanup success", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
5670
5983
  mockPi.onCall({
5671
5984
  jsonl: [events.assistantMessage("async-done-before-drain")],
@@ -5727,6 +6040,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
5727
6040
  agent: "scout",
5728
6041
  task: "Inspect something",
5729
6042
  agentConfig: makeAgent("scout"),
6043
+ acceptance: { level: "attested", criteria: ["Finish cleanly"] },
5730
6044
  ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
5731
6045
  artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
5732
6046
  shareEnabled: false,
@@ -343,7 +343,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
343
343
  parallelGroups: [{ start: 1, count: 2, stepIndex: 1 }],
344
344
  steps: [
345
345
  { agent: "scout", status: "complete" },
346
- { agent: "reviewer", status: "running", currentTool: "read", transcriptPath },
346
+ { agent: "reviewer", status: "running", currentTool: "read", transcriptPath, model: "mock/test-model", thinking: "high" },
347
347
  { agent: "worker", status: "running" },
348
348
  { agent: "writer", status: "pending" },
349
349
  ],
@@ -379,6 +379,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
379
379
  assert.equal(job.cwd, customCwd);
380
380
  assert.deepEqual(job.agents, ["reviewer", "worker"]);
381
381
  assert.deepEqual(job.steps?.map((step: { index?: number }) => step.index), [1, 2]);
382
+ assert.equal(job.steps?.[0]?.thinking, "high");
382
383
  assert.equal(job.stepsTotal, 2);
383
384
  assert.equal(job.runningSteps, 2);
384
385
  assert.equal(job.completedSteps, 0);
@@ -1100,8 +1101,10 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
1100
1101
  await waitForCondition(() => state.asyncJobs.get("run-unchanged")?.toolCount === 1, "changed status load");
1101
1102
  await waitForCondition(() => ui.widgets.length > widgetsAfterStatusLoaded, "changed status widget replacement");
1102
1103
 
1104
+ const requestsBeforeTerminal = ui.renderRequests;
1103
1105
  writeStatus(4000, 1, "complete");
1104
1106
  await waitForCondition(() => state.asyncJobs.get("run-unchanged")?.status === "complete", "terminal status load");
1107
+ await waitForCondition(() => ui.renderRequests > requestsBeforeTerminal, "terminal status widget redraw");
1105
1108
  const widgetsAfterTerminal = ui.widgets.length;
1106
1109
  const requestsAfterTerminal = ui.renderRequests;
1107
1110
  await new Promise((resolve) => setTimeout(resolve, 35));
@@ -73,7 +73,7 @@ describe("async status helpers", () => {
73
73
  outputFile,
74
74
  steps: [
75
75
  { agent: "scout", status: "complete", durationMs: 10, description: "Inspect auth only" },
76
- { agent: "worker", status: "running", durationMs: 20, description: "Patch billing only", contextLimit: 128_000, toolBudgetBlocked: true, watchdog: { phase: "stale", seq: 2, lastUpdate: 200, followUpPending: false } },
76
+ { agent: "worker", status: "running", durationMs: 20, description: "Patch billing only", contextLimit: 128_000, toolBudgetBlocked: true, watchdog: { phase: "stale", seq: 2, lastUpdate: 200 } },
77
77
  ],
78
78
  });
79
79
  const descriptor = createRunFanoutBudget("run-a", 64);