@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
@@ -1,4 +1,4 @@
1
- import type { AsyncStatus, WorkflowLaneMetadata, WorkflowLaneMode } from "../../shared/types.ts";
1
+ import type { AsyncStatus, ManagedWorktreeProvider, WorktreeNaming, WorkflowLaneMetadata, WorkflowLaneMode } from "../../shared/types.ts";
2
2
 
3
3
  export const WORKFLOW_LANE_KEY_MAX_BYTES = 128;
4
4
  export const WORKFLOW_LANE_SOURCE_REF_MAX_BYTES = 128;
@@ -8,6 +8,7 @@ export const WORKFLOW_LANE_OUTPUT_PATH_MAX_BYTES = 256;
8
8
  export const WORKFLOW_LANE_OUTPUT_PATHS_MAX = 10;
9
9
  export const WORKTREE_STATUS_PATH_MAX_BYTES = 4096;
10
10
  export const WORKTREE_STATUS_BRANCH_MAX_BYTES = 256;
11
+ export const WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES = 256;
11
12
 
12
13
  const WORKFLOW_LANE_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
13
14
  const WORKFLOW_LANE_MODES = new Set<WorkflowLaneMode>(["mutation", "review", "scout", "gate"]);
@@ -75,16 +76,36 @@ export function assertWorkflowLaneKey(lane: WorkflowLaneMetadata | undefined, wo
75
76
  export interface WorktreeStatusReference {
76
77
  worktreePath: string;
77
78
  branch: string;
79
+ provider?: ManagedWorktreeProvider;
80
+ naming?: WorktreeNaming;
81
+ }
82
+
83
+ function normalizeWorktreeNaming(value: unknown, label: string): WorktreeNaming {
84
+ assertPlainObject(value, label);
85
+ assertKnownFields(value, ["requestedBranch", "branchPrefix", "label", "sanitizedPathComponent", "collision", "collisionSuffix"], label);
86
+ const collision = value.collision;
87
+ if (collision !== undefined && collision !== "branch" && collision !== "path" && collision !== "both") throw new Error(`${label}.collision is invalid.`);
88
+ return {
89
+ requestedBranch: boundedNonEmptyString(value.requestedBranch, `${label}.requestedBranch`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
90
+ branchPrefix: boundedNonEmptyString(value.branchPrefix, `${label}.branchPrefix`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
91
+ label: boundedNonEmptyString(value.label, `${label}.label`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES),
92
+ sanitizedPathComponent: boundedNonEmptyString(value.sanitizedPathComponent, `${label}.sanitizedPathComponent`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES),
93
+ ...(collision !== undefined ? { collision } : {}),
94
+ ...(value.collisionSuffix !== undefined ? { collisionSuffix: boundedNonEmptyString(value.collisionSuffix, `${label}.collisionSuffix`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES) } : {}),
95
+ };
78
96
  }
79
97
 
80
98
  /** Validate the display-only worktree fields copied into status.json. */
81
99
  export function normalizeWorktreeStatusReference(value: unknown, label = "worktree status reference"): WorktreeStatusReference | undefined {
82
100
  if (value === undefined) return undefined;
83
101
  assertPlainObject(value, label);
84
- assertKnownFields(value, ["worktreePath", "branch"], label);
102
+ assertKnownFields(value, ["worktreePath", "branch", "provider", "naming"], label);
103
+ if (value.provider !== undefined && value.provider !== "native" && value.provider !== "worktrunk") throw new Error(`${label}.provider is invalid.`);
85
104
  return {
86
105
  worktreePath: boundedNonEmptyString(value.worktreePath, `${label}.worktreePath`, WORKTREE_STATUS_PATH_MAX_BYTES),
87
106
  branch: boundedNonEmptyString(value.branch, `${label}.branch`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
107
+ ...(value.provider !== undefined ? { provider: value.provider as ManagedWorktreeProvider } : {}),
108
+ ...(value.naming !== undefined ? { naming: normalizeWorktreeNaming(value.naming, `${label}.naming`) } : {}),
88
109
  };
89
110
  }
90
111
 
@@ -100,6 +121,6 @@ export function validateAsyncStatusLaneMetadata(status: Pick<AsyncStatus, "runId
100
121
  const hasPath = step.worktreePath !== undefined;
101
122
  const hasBranch = step.branch !== undefined;
102
123
  if (hasPath !== hasBranch) throw new Error(`${label}.steps[${index}] must include both worktreePath and branch.`);
103
- if (hasPath) normalizeWorktreeStatusReference({ worktreePath: step.worktreePath, branch: step.branch }, `${label}.steps[${index}]`);
124
+ if (hasPath) normalizeWorktreeStatusReference({ worktreePath: step.worktreePath, branch: step.branch, ...(step.provider ? { provider: step.provider } : {}), ...(step.naming ? { naming: step.naming } : {}) }, `${label}.steps[${index}]`);
104
125
  }
105
126
  }
@@ -574,6 +574,8 @@ export function writeParallelHandoffGroup(input: {
574
574
  index: worktree.index,
575
575
  path: worktree.path,
576
576
  branch: worktree.branch,
577
+ ...(worktree.provider ? { provider: worktree.provider } : {}),
578
+ ...(worktree.naming ? { naming: worktree.naming } : {}),
577
579
  worktreeRemoved: false,
578
580
  branchRemoved: false,
579
581
  preserved: true,
@@ -653,6 +655,8 @@ export function discardPreservedWorktrees(
653
655
  path: task.path,
654
656
  agentCwd: task.path,
655
657
  branch: task.branch,
658
+ ...(task.provider ? { provider: task.provider } : {}),
659
+ ...(task.naming ? { naming: task.naming } : {}),
656
660
  index: task.index,
657
661
  nodeModulesLinked: false,
658
662
  syntheticPaths: [],
@@ -42,6 +42,7 @@ export interface RunnerSubagentStep {
42
42
  skipPrimaryModelVerification?: boolean;
43
43
  modelVerificationRegistry?: Array<{ provider: string; id: string; fullId: string; contextWindow?: number }>;
44
44
  tools?: string[];
45
+ excludeTools?: string[];
45
46
  allowNestedSubagents?: boolean;
46
47
  extensions?: string[];
47
48
  subagentOnlyExtensions?: string[];
@@ -68,12 +69,7 @@ export interface RunnerSubagentStep {
68
69
  toolTimeoutMs?: number;
69
70
  waitToolEnabled?: boolean;
70
71
  waitToolDefaultTimeoutMs?: number;
71
- structuredOutput?: {
72
- schema: import("../../shared/types.ts").JsonSchemaObject;
73
- schemaPath: string;
74
- outputPath: string;
75
- acceptanceReportPath?: string;
76
- };
72
+ structuredOutput?: import("./structured-output.ts").StructuredOutputRuntime;
77
73
  structuredOutputSchema?: import("../../shared/types.ts").JsonSchemaObject;
78
74
  agentContract?: import("../../shared/types.ts").AgentContract;
79
75
  definitionDigest?: string;
@@ -8,6 +8,7 @@ export interface PermissionConfig { rules?: PermissionRules }
8
8
  export const PERMISSION_POLICY_ENV = "SELESAI_SUBAGENT_PERMISSION_POLICY";
9
9
  export const PERMISSION_AUDIT_PATH_ENV = "SELESAI_SUBAGENT_PERMISSION_AUDIT_PATH";
10
10
  const INTERNAL_TOOLS = new Set(["contact_supervisor", "intercom", "bg_wait", "subagent_wait", "structured_output"]);
11
+
11
12
  const DECISIONS = new Set<PermissionDecision>(["allow", "ask", "deny"]);
12
13
  const MAX_POLICY_BYTES = 16 * 1024;
13
14
  const MAX_PREVIEW_BYTES = 2048;
@@ -20,8 +20,10 @@ import { encodeExtensionBindings, SELESAI_SUBAGENT_EXTENSION_BINDINGS_ENV, type
20
20
  import { RUNTIME_EXTENSION_ACK_PATH_ENV } from "./runtime-acknowledged-extensions.ts";
21
21
  import {
22
22
  STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV,
23
+ STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV,
23
24
  STRUCTURED_OUTPUT_CAPTURE_ENV,
24
25
  STRUCTURED_OUTPUT_SCHEMA_ENV,
26
+ type StructuredOutputRuntime,
25
27
  } from "./structured-output.ts";
26
28
  import {
27
29
  TEMP_ROOT_DIR,
@@ -45,7 +47,6 @@ import {
45
47
  } from "./tool-availability.ts";
46
48
  import {
47
49
  CHILD_WATCHDOG_CONFIG_ENV,
48
- encodeChildWatchdogConfig,
49
50
  type ChildWatchdogConfig,
50
51
  } from "../../watchdog/child-status.ts";
51
52
  import { WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV, WAIT_TOOL_ENABLED_ENV } from "../background/wait-config.ts";
@@ -91,11 +92,12 @@ export function resolveSubagentTaskDelivery(
91
92
  : "auto";
92
93
  }
93
94
 
94
- function shouldDeliverTaskViaFile(
95
+ export function shouldDeliverTaskViaFile(
95
96
  task: string,
96
97
  delivery: SubagentTaskDelivery,
98
+ platform: NodeJS.Platform = process.platform,
97
99
  ): boolean {
98
- return delivery === "file" || task.length > TASK_ARG_LIMIT;
100
+ return delivery === "file" || platform === "darwin" || task.length > TASK_ARG_LIMIT;
99
101
  }
100
102
  const MAX_LAUNCH_RESOLVED_EXTENSION_IDS = 32;
101
103
  const PROMPT_RUNTIME_EXTENSION_PATH = path.join(
@@ -171,6 +173,7 @@ export interface BuildPiArgsInput {
171
173
  inheritSkills: boolean;
172
174
  requireReadTool?: boolean;
173
175
  tools?: string[];
176
+ excludeTools?: string[];
174
177
  extensions?: string[];
175
178
  subagentOnlyExtensions?: string[];
176
179
  systemPrompt?: string | null;
@@ -201,12 +204,7 @@ export interface BuildPiArgsInput {
201
204
  steerInboxDir?: string;
202
205
  steerCapabilityPath?: string;
203
206
  steerAckDir?: string;
204
- structuredOutput?: {
205
- schema: JsonSchemaObject;
206
- schemaPath: string;
207
- outputPath: string;
208
- acceptanceReportPath?: string;
209
- };
207
+ structuredOutput?: StructuredOutputRuntime;
210
208
  fast?: boolean;
211
209
  modelCandidates?: readonly string[];
212
210
  toolBudget?: ResolvedToolBudget;
@@ -300,6 +298,7 @@ function resolveFastModeExtension(input: Pick<ResolvePiLaunchToolPlanInput, "fas
300
298
 
301
299
  export interface ResolvePiLaunchToolPlanInput {
302
300
  tools?: string[];
301
+ excludeTools?: string[];
303
302
  allowNestedSubagents?: boolean;
304
303
  extensions?: string[];
305
304
  subagentOnlyExtensions?: string[];
@@ -329,6 +328,7 @@ export interface PiLaunchToolPlan {
329
328
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
330
329
  requestedBuiltinTools: string[];
331
330
  declaredBuiltinTools: string[];
331
+ excludeTools: string[];
332
332
  toolExtensionPaths: string[];
333
333
  resolvedMcpSelections: ResolvedMcpDirectToolSelection[];
334
334
  effectiveMcpSelections: ResolvedMcpDirectToolSelection[];
@@ -499,8 +499,12 @@ export function resolvePiLaunchToolPlan(
499
499
  ? ["read", ...requestedBuiltinTools]
500
500
  : requestedBuiltinTools
501
501
  ).filter((tool) => !allowedToolSet || allowedToolSet.has(tool));
502
- const fanoutAuthorized = declaredBuiltinTools.includes("subagent") || (
502
+ const excludeTools = [...new Set((input.excludeTools ?? []).map((tool) => tool.trim()).filter(Boolean))];
503
+ const excludedToolSet = new Set(excludeTools);
504
+ const effectiveDeclaredBuiltinTools = declaredBuiltinTools.filter((tool) => !excludedToolSet.has(tool));
505
+ const fanoutAuthorized = effectiveDeclaredBuiltinTools.includes("subagent") || (
503
506
  input.allowNestedSubagents === true &&
507
+ !excludedToolSet.has("subagent") &&
504
508
  (!allowedToolSet || allowedToolSet.has("subagent"))
505
509
  );
506
510
  const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions
@@ -524,7 +528,7 @@ export function resolvePiLaunchToolPlan(
524
528
  !allowedToolSet ||
525
529
  allowedToolSet.has(selection.name) ||
526
530
  isLegacyUnderscoreMcpToolAllowed(selection, allowedToolSet, resolvedMcpNames, legacyMcpNameCounts),
527
- );
531
+ ).filter((selection) => !excludedToolSet.has(selection.name));
528
532
  const effectiveMcpTools = effectiveMcpSelections.map(
529
533
  (selection) => selection.name,
530
534
  );
@@ -540,10 +544,10 @@ export function resolvePiLaunchToolPlan(
540
544
  input.tools !== undefined ||
541
545
  (input.mcpDirectTools?.length ?? 0) > 0 ||
542
546
  allowedToolSet !== undefined;
543
- const internalTools = input.structuredOutput ? ["structured_output"] : [];
547
+ const internalTools = (input.structuredOutput ? ["structured_output"] : []).filter((tool) => !excludedToolSet.has(tool));
544
548
  const effectiveToolAllowlist = [
545
549
  ...new Set([
546
- ...declaredBuiltinTools,
550
+ ...effectiveDeclaredBuiltinTools,
547
551
  ...effectiveMcpTools,
548
552
  ...internalTools,
549
553
  ]),
@@ -554,11 +558,11 @@ export function resolvePiLaunchToolPlan(
554
558
  // appended intercom alongside contact_supervisor, so that exact pairing is
555
559
  // legacy plumbing, not a user demand for an external intercom provider;
556
560
  // a lone intercom entry stays strictly required (#1207).
557
- const legacySupervisorPairing = declaredBuiltinTools.includes("contact_supervisor");
561
+ const legacySupervisorPairing = effectiveDeclaredBuiltinTools.includes("contact_supervisor");
558
562
  const requiredChildTools = explicitToolAllowlist
559
563
  ? [
560
564
  ...new Set([
561
- ...(input.tools !== undefined ? declaredBuiltinTools : []),
565
+ ...(input.tools !== undefined ? effectiveDeclaredBuiltinTools : []),
562
566
  ...(input.mcpDirectTools?.length ? effectiveMcpTools : []),
563
567
  ...internalTools,
564
568
  ].filter((tool) => tool !== "contact_supervisor" && (!legacySupervisorPairing || tool !== "intercom"))),
@@ -620,6 +624,7 @@ export function resolvePiLaunchToolPlan(
620
624
  ceiling: capabilityCeiling,
621
625
  ...(requestedToolNames ? { requestedTools: requestedToolNames } : {}),
622
626
  effectiveTools: effectiveToolAllowlist,
627
+ ...(excludeTools.length > 0 ? { excludeTools } : {}),
623
628
  removedTools:
624
629
  requestedToolNames?.filter(
625
630
  (tool) => !effectiveToolAllowlist.includes(tool),
@@ -657,6 +662,7 @@ export function resolvePiLaunchToolPlan(
657
662
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
658
663
  requestedBuiltinTools,
659
664
  declaredBuiltinTools,
665
+ excludeTools,
660
666
  toolExtensionPaths,
661
667
  resolvedMcpSelections,
662
668
  effectiveMcpSelections,
@@ -740,6 +746,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
740
746
 
741
747
  const toolPlan = resolvePiLaunchToolPlan({
742
748
  tools: input.tools,
749
+ excludeTools: input.excludeTools,
743
750
  allowNestedSubagents: input.allowNestedSubagents,
744
751
  extensions: input.extensions,
745
752
  subagentOnlyExtensions: input.subagentOnlyExtensions,
@@ -766,6 +773,8 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
766
773
  );
767
774
  if (toolPlan.effectiveToolAllowlist.length > 0)
768
775
  args.push(toolPlan.effectiveToolAllowlist.join(","));
776
+ } else if (toolPlan.excludeTools.length > 0) {
777
+ args.push("--exclude-tools", toolPlan.excludeTools.join(","));
769
778
  }
770
779
  if (toolPlan.disableAmbientExtensions) {
771
780
  args.push("--no-extensions");
@@ -836,6 +845,11 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
836
845
  );
837
846
  env[RUNTIME_EXTENSION_ACK_PATH_ENV] = runtimeAcknowledgedExtensionsPath;
838
847
  let toolDiagnosticPath: string | undefined;
848
+ // Child launch environments are merged over process.env. Explicitly clear
849
+ // parent-scoped diagnostics so a nested zero-tool child cannot validate
850
+ // against, or overwrite, its parent's required-tool report.
851
+ env[REQUIRED_CHILD_TOOLS_ENV] = undefined;
852
+ env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = undefined;
839
853
  if (toolPlan.requiredChildTools.length > 0) {
840
854
  if (!tempDir)
841
855
  tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
@@ -999,10 +1013,13 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
999
1013
  env[SUBAGENT_CAPABILITY_CEILING_ENV] = encodedCapabilityCeiling;
1000
1014
  if (encodedPermissionRules)
1001
1015
  env[PERMISSION_POLICY_ENV] = encodedPermissionRules;
1016
+ env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = undefined;
1017
+ env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = undefined;
1002
1018
  if (input.structuredOutput) {
1003
1019
  env[STRUCTURED_OUTPUT_CAPTURE_ENV] = input.structuredOutput.outputPath;
1004
1020
  env[STRUCTURED_OUTPUT_SCHEMA_ENV] = input.structuredOutput.schemaPath;
1005
1021
  if (input.structuredOutput.acceptanceReportPath) env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = input.structuredOutput.acceptanceReportPath;
1022
+ if (input.structuredOutput.acceptanceReportRequired) env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = "1";
1006
1023
  }
1007
1024
  if (input.steerInboxDir) {
1008
1025
  env[SUBAGENT_STEER_INBOX_ENV] = input.steerInboxDir;
@@ -1013,9 +1030,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
1013
1030
  const encodedToolBudget = encodeToolBudgetEnv(input.toolBudget);
1014
1031
  if (encodedToolBudget) env[TOOL_BUDGET_ENV] = encodedToolBudget;
1015
1032
  env[TOOL_BUDGET_ZERO_AUTH_ENV] = input.allowZeroToolBudget ? "1" : undefined;
1016
- const encodedChildWatchdog = encodeChildWatchdogConfig(input.childWatchdog);
1017
- if (encodedChildWatchdog)
1018
- env[CHILD_WATCHDOG_CONFIG_ENV] = encodedChildWatchdog;
1033
+ if (input.childWatchdog) env[CHILD_WATCHDOG_CONFIG_ENV] = JSON.stringify(input.childWatchdog);
1019
1034
 
1020
1035
  env[SUBAGENT_PARENT_SESSION_ENV] =
1021
1036
  input.parentSessionId ?? process.env[SUBAGENT_PARENT_SESSION_ENV] ?? "";
@@ -1,8 +1,9 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../shared/utils.ts";
4
5
 
5
- export const PI_CODING_AGENT_PACKAGE = "@selesai/code";
6
+ export const SELESAI_CODING_AGENT_PACKAGE = "@selesai/code";
6
7
  export const SELESAI_SUBAGENT_SELESAI_BINARY_ENV = "SELESAI_SUBAGENT_SELESAI_BINARY";
7
8
 
8
9
  export function findPiPackageRootFromEntry(
@@ -15,7 +16,7 @@ export function findPiPackageRootFromEntry(
15
16
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as {
16
17
  name?: unknown;
17
18
  };
18
- if (pkg.name === PI_CODING_AGENT_PACKAGE) return dir;
19
+ if (pkg.name === SELESAI_CODING_AGENT_PACKAGE) return dir;
19
20
  }
20
21
  dir = path.dirname(dir);
21
22
  }
@@ -23,9 +24,13 @@ export function findPiPackageRootFromEntry(
23
24
  }
24
25
 
25
26
  export function resolveInstalledPiPackageRoot(): string | undefined {
26
- return findPiPackageRootFromEntry(
27
- fileURLToPath(import.meta.resolve(PI_CODING_AGENT_PACKAGE)),
28
- );
27
+ try {
28
+ return findPiPackageRootFromEntry(
29
+ fileURLToPath(import.meta.resolve(SELESAI_CODING_AGENT_PACKAGE)),
30
+ );
31
+ } catch {
32
+ return undefined;
33
+ }
29
34
  }
30
35
 
31
36
  export function resolvePiPackageRoot(): string | undefined {
@@ -58,12 +63,21 @@ interface PiSpawnCommand {
58
63
  args: string[];
59
64
  }
60
65
 
66
+ interface PiPackageJson {
67
+ name?: unknown;
68
+ bin?: string | Record<string, string>;
69
+ }
70
+
71
+ function isNodeScriptPath(filePath: string): boolean {
72
+ return /\.(?:mjs|cjs|js)$/i.test(filePath);
73
+ }
74
+
61
75
  function isRunnableNodeScript(
62
76
  filePath: string,
63
77
  existsSync: (filePath: string) => boolean,
64
78
  ): boolean {
65
79
  if (!existsSync(filePath)) return false;
66
- return /\.(?:mjs|cjs|js)$/i.test(filePath);
80
+ return isNodeScriptPath(filePath);
67
81
  }
68
82
 
69
83
  function normalizePath(filePath: string): string {
@@ -75,6 +89,23 @@ function isStandaloneSelesaiExecutable(execPath: string): boolean {
75
89
  return /^(?:selesai|selesai-code)(?:\.exe)?$/i.test(executableName ?? "");
76
90
  }
77
91
 
92
+ function resolvePiCliScriptFromPackageJson(
93
+ packageJsonPath: string,
94
+ readFileSync: (filePath: string, encoding: "utf-8") => string,
95
+ existsSync: (filePath: string) => boolean,
96
+ ): string | undefined {
97
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as PiPackageJson;
98
+ if (packageJson.name !== SELESAI_CODING_AGENT_PACKAGE) return undefined;
99
+ const binField = packageJson.bin;
100
+ const binPath =
101
+ typeof binField === "string"
102
+ ? binField
103
+ : (binField?.selesai ?? Object.values(binField ?? {})[0]);
104
+ if (!binPath) return undefined;
105
+ const candidate = path.resolve(path.dirname(packageJsonPath), binPath);
106
+ return isRunnableNodeScript(candidate, existsSync) ? candidate : undefined;
107
+ }
108
+
78
109
  export function resolvePiCliScript(
79
110
  deps: PiSpawnDeps = {},
80
111
  ): string | undefined {
@@ -84,6 +115,7 @@ export function resolvePiCliScript(
84
115
  deps.readFileSync ??
85
116
  ((filePath, encoding) => fs.readFileSync(filePath, encoding));
86
117
  const argv1 = deps.argv1 ?? process.argv[1];
118
+ const env = deps.env ?? process.env;
87
119
 
88
120
  if (argv1) {
89
121
  const argvPath = normalizePath(argv1);
@@ -99,38 +131,29 @@ export function resolvePiCliScript(
99
131
  }
100
132
  }
101
133
 
102
- try {
103
- const resolvePackageJson =
104
- deps.resolvePackageJson ??
105
- (() => {
106
- const root = deps.piPackageRoot ?? resolvePiPackageRoot();
107
- if (root) return path.join(root, "package.json");
108
- const packageRoot = deps.resolvePackageEntry
109
- ? findPiPackageRootFromEntry(deps.resolvePackageEntry())
110
- : resolveInstalledPiPackageRoot();
111
- if (!packageRoot)
112
- throw new Error(
113
- `Could not resolve ${PI_CODING_AGENT_PACKAGE} package root`,
114
- );
115
- return path.join(packageRoot, "package.json");
116
- });
117
- const packageJsonPath = resolvePackageJson();
118
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
119
- bin?: string | Record<string, string>;
120
- };
121
- const binField = packageJson.bin;
122
- const binPath =
123
- typeof binField === "string"
124
- ? binField
125
- : (binField?.selesai ?? Object.values(binField ?? {})[0]);
126
- if (!binPath) return undefined;
127
- const candidate = path.resolve(path.dirname(packageJsonPath), binPath);
128
- if (isRunnableNodeScript(candidate, existsSync)) {
129
- return candidate;
134
+ const packageJsonCandidates: Array<() => string | undefined> = [];
135
+ if (deps.resolvePackageJson) packageJsonCandidates.push(deps.resolvePackageJson);
136
+ for (const root of [deps.piPackageRoot, env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV], resolvePiPackageRoot()]) {
137
+ const trimmed = root?.trim();
138
+ if (trimmed) packageJsonCandidates.push(() => path.join(trimmed, "package.json"));
139
+ }
140
+ packageJsonCandidates.push(() => {
141
+ const packageRoot = deps.resolvePackageEntry
142
+ ? findPiPackageRootFromEntry(deps.resolvePackageEntry())
143
+ : resolveInstalledPiPackageRoot();
144
+ return packageRoot ? path.join(packageRoot, "package.json") : undefined;
145
+ });
146
+
147
+ for (const candidatePackageJson of packageJsonCandidates) {
148
+ try {
149
+ const packageJsonPath = candidatePackageJson();
150
+ if (!packageJsonPath) continue;
151
+ const candidate = resolvePiCliScriptFromPackageJson(packageJsonPath, readFileSync, existsSync);
152
+ if (candidate) return candidate;
153
+ } catch {
154
+ // Keep resolving; callers decide whether a PATH fallback is safe.
155
+
130
156
  }
131
- } catch {
132
- // Verified CLI resolution is optional; falling back to `pi` lets PATH handle execution.
133
- return undefined;
134
157
  }
135
158
 
136
159
  return undefined;
@@ -140,10 +163,18 @@ export function getPiSpawnCommand(
140
163
  args: string[],
141
164
  deps: PiSpawnDeps = {},
142
165
  ): PiSpawnCommand {
166
+ const platform = deps.platform ?? process.platform;
143
167
  const env = deps.env ?? process.env;
144
168
  const selesaiBinary = env[SELESAI_SUBAGENT_SELESAI_BINARY_ENV]?.trim();
145
169
  if (selesaiBinary) {
170
+ if (platform === "win32" && isNodeScriptPath(selesaiBinary)) {
171
+ return {
172
+ command: deps.execPath ?? process.execPath,
173
+ args: [selesaiBinary, ...args],
174
+ };
175
+ }
146
176
  return { command: selesaiBinary, args };
177
+
147
178
  }
148
179
 
149
180
  const execPath = deps.execPath ?? process.execPath;
@@ -158,6 +189,11 @@ export function getPiSpawnCommand(
158
189
  args: [piCliPath, ...args],
159
190
  };
160
191
  }
192
+ if (platform === "win32") {
193
+ throw new Error(
194
+ `Could not resolve the Pi CLI on Windows. Set ${SELESAI_SUBAGENT_SELESAI_BINARY_ENV} or ensure ${SELESAI_CODING_AGENT_PACKAGE} is installed.`,
195
+ );
196
+ }
161
197
 
162
198
  return { command: "selesai", args };
163
199
  }
@@ -5,17 +5,21 @@ import * as path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../shared/utils.ts";
7
7
  import type { JsonSchemaObject } from "../../shared/types.ts";
8
+ import type { ResolvedAcceptanceReportMode } from "./acceptance.ts";
8
9
 
9
10
  export const STRUCTURED_OUTPUT_SCHEMA_ENV = "SELESAI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA";
10
11
  export const STRUCTURED_OUTPUT_CAPTURE_ENV = "SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE";
11
12
  export const STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV = "SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE";
13
+ export const STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV = "SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED";
12
14
  export const MISSING_STRUCTURED_OUTPUT_CALL_ERROR = "Missing structured_output call; this step has outputSchema and must finish by calling structured_output.";
15
+ export const MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR = "Missing acceptanceReport in structured_output call; acceptance.report is \"on\".";
13
16
 
14
17
  export interface StructuredOutputRuntime {
15
18
  schema: JsonSchemaObject;
16
19
  schemaPath: string;
17
20
  outputPath: string;
18
21
  acceptanceReportPath?: string;
22
+ acceptanceReportRequired?: boolean;
19
23
  }
20
24
 
21
25
  const SCHEMA_MAP_KEYWORDS = ["properties", "patternProperties", "$defs", "definitions", "dependentSchemas"] as const;
@@ -61,14 +65,14 @@ function rewriteLocalJsonPointerRefs(schema: unknown, pointerPrefix: string, inh
61
65
  return rewritten;
62
66
  }
63
67
 
64
- export function createStructuredOutputToolParameters(schema: JsonSchemaObject, options: { acceptanceReport?: boolean } = {}): JsonSchemaObject {
68
+ export function createStructuredOutputToolParameters(schema: JsonSchemaObject, options: { acceptanceReport?: "optional" | "required" } = {}): JsonSchemaObject {
65
69
  return {
66
70
  type: "object",
67
71
  properties: {
68
72
  value: rewriteLocalJsonPointerRefs(schema, "#/properties/value"),
69
73
  ...(options.acceptanceReport ? { acceptanceReport: { type: "object" } } : {}),
70
74
  },
71
- required: ["value"],
75
+ required: ["value", ...(options.acceptanceReport === "required" ? ["acceptanceReport"] : [])],
72
76
  additionalProperties: false,
73
77
  };
74
78
  }
@@ -129,7 +133,7 @@ export function assertJsonSchemaObject(schema: unknown, label = "outputSchema"):
129
133
  }
130
134
  }
131
135
 
132
- export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?: string, options: { captureAcceptanceReport?: boolean } = {}): StructuredOutputRuntime {
136
+ export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?: string, options: { acceptanceReport?: ResolvedAcceptanceReportMode } = {}): StructuredOutputRuntime {
133
137
  assertJsonSchemaObject(schema);
134
138
  const rootDir = baseDir ?? os.tmpdir();
135
139
  fs.mkdirSync(rootDir, { recursive: true });
@@ -137,7 +141,14 @@ export function createStructuredOutputRuntime(schema: JsonSchemaObject, baseDir?
137
141
  const schemaPath = path.join(dir, "schema.json");
138
142
  const outputPath = path.join(dir, "output.json");
139
143
  fs.writeFileSync(schemaPath, JSON.stringify(schema), { mode: 0o600 });
140
- return { schema, schemaPath, outputPath, ...(options.captureAcceptanceReport ? { acceptanceReportPath: path.join(dir, "acceptance-report.json") } : {}) };
144
+ return {
145
+ schema,
146
+ schemaPath,
147
+ outputPath,
148
+ ...(options.acceptanceReport && options.acceptanceReport !== "off"
149
+ ? { acceptanceReportPath: path.join(dir, "acceptance-report.json"), acceptanceReportRequired: options.acceptanceReport === "required" }
150
+ : {}),
151
+ };
141
152
  }
142
153
 
143
154
  export async function validateStructuredOutputValue(schema: JsonSchemaObject, value: unknown): Promise<{ status: "valid" } | { status: "invalid"; message: string }> {
@@ -178,14 +189,30 @@ export async function readStructuredOutput(runtime: StructuredOutputRuntime): Pr
178
189
  }
179
190
 
180
191
  export function readStructuredOutputAcceptanceReport(runtime: StructuredOutputRuntime): { value?: unknown; error?: string } {
181
- if (!runtime.acceptanceReportPath || !fs.existsSync(runtime.acceptanceReportPath)) return {};
192
+ if (!runtime.acceptanceReportPath) return {};
193
+ if (!fs.existsSync(runtime.acceptanceReportPath)) {
194
+ return runtime.acceptanceReportRequired ? { error: MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR } : {};
195
+ }
182
196
  try {
183
- return { value: JSON.parse(fs.readFileSync(runtime.acceptanceReportPath, "utf-8")) };
197
+ return { value: JSON.parse(fs.readFileSync(runtime.acceptanceReportPath, "utf-8")) as unknown };
184
198
  } catch (error) {
185
199
  return { error: `Failed to read structured output acceptance report: ${error instanceof Error ? error.message : String(error)}` };
186
200
  }
187
201
  }
188
202
 
203
+ export function clearStructuredOutputCaptures(runtime: StructuredOutputRuntime): string | undefined {
204
+ let cleanupError: string | undefined;
205
+ for (const filePath of [runtime.outputPath, runtime.acceptanceReportPath]) {
206
+ if (!filePath) continue;
207
+ try {
208
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
209
+ } catch (error) {
210
+ cleanupError ??= `Failed to clear stale structured output capture ${filePath}: ${error instanceof Error ? error.message : String(error)}`;
211
+ }
212
+ }
213
+ return cleanupError;
214
+ }
215
+
189
216
  export function cleanupStructuredOutputRuntime(runtime: StructuredOutputRuntime | undefined): void {
190
217
  if (!runtime) return;
191
218
  try {
@@ -45,11 +45,12 @@ export function resolveControlConfig(
45
45
  const enabled = override?.enabled ?? globalConfig?.enabled ?? DEFAULT_CONTROL_CONFIG.enabled;
46
46
  const overrideNeedsAttentionAfterMs = parsePositiveInt(override?.needsAttentionAfterMs);
47
47
  const globalNeedsAttentionAfterMs = parsePositiveInt(globalConfig?.needsAttentionAfterMs);
48
+ const globalNeedsAttentionAfterMsIsExplicit = (globalConfig as Partial<ResolvedControlConfig> | undefined)?.needsAttentionAfterMsIsExplicit ?? globalNeedsAttentionAfterMs !== undefined;
48
49
  const needsAttentionAfterMs = overrideNeedsAttentionAfterMs
49
50
  ?? globalNeedsAttentionAfterMs
50
51
  ?? DEFAULT_CONTROL_CONFIG.needsAttentionAfterMs;
51
52
  const needsAttentionAfterMsIsExplicit = overrideNeedsAttentionAfterMs !== undefined
52
- || globalNeedsAttentionAfterMs !== undefined;
53
+ || globalNeedsAttentionAfterMsIsExplicit;
53
54
  const activeNoticeAfterMs = parsePositiveInt(override?.activeNoticeAfterMs)
54
55
  ?? parsePositiveInt(globalConfig?.activeNoticeAfterMs)
55
56
  ?? DEFAULT_CONTROL_CONFIG.activeNoticeAfterMs;
@@ -98,11 +99,12 @@ export function deriveActivityState(input: {
98
99
  config: ResolvedControlConfig;
99
100
  startedAt: number;
100
101
  lastActivityAt?: number;
102
+ turnCount?: number;
101
103
  currentTool?: string;
102
104
  thinking?: string | false;
103
105
  now?: number;
104
106
  }): ActivityState | undefined {
105
- if (!input.config.enabled || input.currentTool) return undefined;
107
+ if (!input.config.enabled || input.currentTool || (input.turnCount ?? 0) === 0) return undefined;
106
108
  const now = input.now ?? Date.now();
107
109
  const lastActivity = input.lastActivityAt ?? input.startedAt;
108
110
  const ageMs = Math.max(0, now - lastActivity);
@@ -8,7 +8,8 @@ import { decodePermissionRules, permissionDecision, PERMISSION_AUDIT_PATH_ENV, P
8
8
  import { consumeSteerRequestsFromDir, MAX_STEER_QUEUE_SIZE, steerAckPathFromDir, writeSteerAckAt, writeSteerCapabilityAt, writeSteerRequestToDir, type SteerDeliveryStatus, type SteerRequest } from "../background/control-channel.ts";
9
9
  import { SUBAGENT_CHILD_AGENT_ENV, SUBAGENT_CHILD_INDEX_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_FORK_CACHE_KEY_ENV, SUBAGENT_INHERIT_GLOBAL_CONTEXT_ENV, SUBAGENT_STEER_ACK_DIR_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_INBOX_ENV } from "./pi-args.ts";
10
10
  import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV, isRuntimeAcknowledgedExtensionId, writeRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
11
- import { createStructuredOutputToolParameters, STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
11
+ import { createStructuredOutputToolParameters, MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
12
+ import { validateAcceptanceReport } from "./acceptance.ts";
12
13
  import {
13
14
  CHILD_TOOL_DIAGNOSTIC_PATH_ENV,
14
15
  formatChildToolDiagnostic,
@@ -730,10 +731,14 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
730
731
  });
731
732
  const structuredOutputPath = process.env[STRUCTURED_OUTPUT_CAPTURE_ENV];
732
733
  const structuredAcceptanceReportPath = process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV];
734
+ const structuredAcceptanceReportRequired = process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] === "1";
733
735
  const structuredSchemaPath = process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
734
736
  if (structuredOutputPath && structuredSchemaPath) {
735
737
  const schema = JSON.parse(fs.readFileSync(structuredSchemaPath, "utf-8")) as JsonSchemaObject;
736
- const parameters = createStructuredOutputToolParameters(schema, { acceptanceReport: Boolean(structuredAcceptanceReportPath) });
738
+ const acceptanceReportMode = structuredAcceptanceReportPath
739
+ ? structuredAcceptanceReportRequired ? "required" as const : "optional" as const
740
+ : undefined;
741
+ const parameters = createStructuredOutputToolParameters(schema, { acceptanceReport: acceptanceReportMode });
737
742
  const registerTool = pi.registerTool as unknown as (tool: {
738
743
  name: string;
739
744
  label: string;
@@ -751,11 +756,23 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
751
756
  if (validation.status === "invalid") {
752
757
  throw new Error(`Structured output validation failed: ${validation.message}`);
753
758
  }
759
+ if (structuredAcceptanceReportRequired && params.acceptanceReport === undefined) {
760
+ throw new Error(MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR);
761
+ }
762
+ if (structuredAcceptanceReportRequired && params.acceptanceReport !== undefined) {
763
+ const acceptanceValidation = validateAcceptanceReport(params.acceptanceReport, "acceptanceReport");
764
+ if (!acceptanceValidation.report) {
765
+ throw new Error(`Invalid structured output acceptance report: ${acceptanceValidation.errors.join("; ")}`);
766
+ }
767
+ }
754
768
  fs.mkdirSync(path.dirname(structuredOutputPath), { recursive: true });
755
- fs.writeFileSync(structuredOutputPath, JSON.stringify(params.value), { mode: 0o600 });
756
769
  if (structuredAcceptanceReportPath && params.acceptanceReport !== undefined) {
770
+ fs.mkdirSync(path.dirname(structuredAcceptanceReportPath), { recursive: true });
757
771
  fs.writeFileSync(structuredAcceptanceReportPath, JSON.stringify(params.acceptanceReport), { mode: 0o600 });
772
+ } else if (structuredAcceptanceReportPath && fs.existsSync(structuredAcceptanceReportPath)) {
773
+ fs.unlinkSync(structuredAcceptanceReportPath);
758
774
  }
775
+ fs.writeFileSync(structuredOutputPath, JSON.stringify(params.value), { mode: 0o600 });
759
776
  return {
760
777
  content: [{ type: "text", text: "Structured output captured." }],
761
778
  details: { path: structuredOutputPath },