@selesai/code 0.8.6 → 0.8.7

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 (192) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/core/agent-session.js +13 -1
  3. package/dist/core/extensions/types.d.ts +2 -0
  4. package/dist/core/provider-composer.js +17 -4
  5. package/dist/core/settings-manager.d.ts +2 -0
  6. package/dist/core/settings-manager.js +3 -0
  7. package/dist/core/system-prompt.js +2 -8
  8. package/dist/core/system-prompt.test.js +6 -0
  9. package/dist/core/tools/index.d.ts +1 -0
  10. package/dist/core/tools/index.js +1 -0
  11. package/dist/core/tools/schema-prune.d.ts +16 -0
  12. package/dist/core/tools/schema-prune.js +39 -0
  13. package/dist/core/tools/schema-prune.test.d.ts +1 -0
  14. package/dist/core/tools/schema-prune.test.js +111 -0
  15. package/dist/defaults/settings.json +1 -0
  16. package/dist/extensions/model-prompt-injector/config.json +10 -0
  17. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  18. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  19. package/dist/extensions/package.json +1 -0
  20. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  21. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  22. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  23. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  24. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  25. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  26. package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
  27. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  28. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  29. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  30. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  31. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  32. package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
  33. package/dist/extensions/pi-subagents/install.mjs +2 -2
  34. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  35. package/dist/extensions/pi-subagents/package.json +1 -1
  36. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  37. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  38. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
  39. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  40. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  41. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  42. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  43. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  44. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  45. package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
  46. package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
  47. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  48. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  49. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  50. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  51. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  52. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  53. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  54. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  55. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  56. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  57. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  58. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  59. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  60. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  61. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  62. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
  63. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  64. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  65. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
  66. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  67. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  68. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  69. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  70. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +205 -41
  71. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  72. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  73. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
  74. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
  75. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  76. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  77. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  78. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  79. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  80. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  81. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  82. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  83. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  84. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
  85. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  86. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  88. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  89. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  90. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  92. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  95. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  96. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  97. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  98. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  99. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  100. package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
  101. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  102. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  103. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  104. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  105. package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
  106. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  107. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  108. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  109. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  110. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  111. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  112. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  113. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  114. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  115. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  116. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  117. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  118. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  119. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  120. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  121. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
  122. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  123. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  124. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  125. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  126. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  127. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  128. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  129. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  130. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  131. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  132. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  133. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  134. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  135. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  136. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  137. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  138. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  139. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  140. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
  141. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  142. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  143. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  144. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  145. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  146. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  147. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  148. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  149. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  150. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  151. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  152. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  153. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  154. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  155. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  156. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  157. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  158. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  159. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  160. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  161. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  162. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  163. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  164. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
  165. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
  166. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
  167. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
  168. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
  169. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
  170. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
  171. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
  172. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
  173. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  174. package/dist/extensions/question/index.ts +21 -6
  175. package/dist/extensions/question/question-list.ts +7 -1
  176. package/dist/extensions/question/row-layout.ts +3 -0
  177. package/dist/extensions/question/tests/exp.test.ts +70 -0
  178. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  179. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  180. package/dist/extensions/question/tests/helpers.test.ts +7 -0
  181. package/dist/extensions/question/tests/question-list.test.ts +140 -0
  182. package/dist/extensions/question/tests/row-layout.test.ts +13 -0
  183. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  184. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  185. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  186. package/dist/skills/pi-subagents/SKILL.md +1 -1
  187. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  188. package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
  189. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  190. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  191. package/docs/settings.md +6 -0
  192. package/package.json +3 -3
@@ -22,6 +22,23 @@ const READ_ONLY_BUILTIN_TOOLS = new Set([
22
22
  const CURSOR_FILE_MUTATION_THINKING =
23
23
  /(?:^|\n)\s*Cursor (?:edit|write)\s*:/i;
24
24
 
25
+ const REVIVED_TASK_PREFIX = /^You are reviving a previous subagent conversation\.\n\nOriginal run: .+\nOriginal agent: .+(?:\nOriginal session file: .+)?\n\nUse the stored session context as background\. Answer the orchestrator's follow-up below\. Do not assume the original child process is still alive\.\n\nFollow-up:\n/;
26
+ const IMPLEMENTATION_CHALLENGE_FOLLOW_UP_PATTERN = /^(?:Run )?implementation challenge pass (?:one|two|\d+)(?:\s+and implement any better current[- ]scope change\.?|\s+for the accepted candidate\.\s+Reconsider it and implement any better current[- ]scope change\.?)?$/i;
27
+ const NO_BETTER_CHANGE_NEEDED_PATTERN = /^\s*no (?:better|further|additional) (?:current[- ]scope )?(?:code |source |file )?(?:change|changes|edit|edits|patch|patches) (?:is|are) needed\b/i;
28
+ const KEPT_CURRENT_IMPLEMENTATION_PATTERN = /\b(?:kept (?:the )?current (?:implementation|candidate|shape)|(?:the )?current (?:implementation|candidate|shape) was kept)\b/i;
29
+ const NO_CHANGE_MADE_PATTERN = /\bno (?:new )?(?:(?:code|source|file|test)(?:\s*(?:,\s*or|,|\/|or)\s*(?:code|source|file|test))* changes?|changes?) (?:were|was) made\b/i;
30
+ const NO_BETTER_CHANGE_QUALIFIER_PATTERN = /\b(?:do\s+not|don't|dont|not|never|cannot|can't|cant|unable|uncertain|unsure|unclear|disagree|wrong|false|reject|rejected|rejecting|maybe|might|may|\w+n['’]t)\b/i;
31
+ const REPORT_SENTENCE_PATTERN = /[^.!?]+(?:[.!?]+(?=(?:["”'’]?\s)|["”'’]?$)|$)/g;
32
+ const QUOTED_CLAIM_PATTERN = /["“]([^"”]+)["”]([.!?]?\s*[^.!?]*)/g;
33
+ const QUOTED_CLAIM_REFERENCE_PATTERN = /\b(?:that|this|it|claim|report|message|disagree)\b/i;
34
+ const CLAIM_CONTRADICTION_PATTERN = /\bbut\b[^.!?]*\b(?:uncertain|unsure|unclear|disagree|wrong|false|reject|rejected|rejecting)\b/i;
35
+ const IMPLEMENTATION_RETRACTION_PATTERN = /\b(?:found|identified)\s+(?:(?:a|an|the)\s+)?(?:required|necessary|additional|better)?\s*(?:(?:code|source|file|test)\s+)?(?:changes?|edits?|patches?)\b|\b(?:(?:implementation|code|source|file|test)\s+)?work\s+(?:remains?|is\s+(?:needed|required))\b|\b(?:(?:a|an|the)\s+)?(?:(?:code|source|file|test)\s+)?(?:changes?|edits?|patches?)\s+(?:are|is)\s+(?:needed|required)\b|\b(?:required|necessary|additional|better)\s+(?:(?:code|source|file|test)\s+)?(?:changes?|edits?|patches?)\b|\b(?:I|we)?\s*need\s+(?:(?:code|source|file|test)\s+)?(?:changes?|edits?|patches?)\b|\b(?:need|needs|require|requires)\s+(?:to\s+)?(?:implement|make|apply)\b/i;
36
+ const IMPLEMENTATION_RETRACTION_GLOBAL_PATTERN = new RegExp(IMPLEMENTATION_RETRACTION_PATTERN.source, "gi");
37
+ const DIRECT_CLAIM_RETRACTION_PATTERN = /^\s*(?:I\s+)?(?:disagree|reject|retract)\b|\b(?:disagree|retract|retracted|retracting|reject|rejected|rejecting)\b[^.!?]*\b(?:that|this|it|claim|report|message)\b|\b(?:that|this|it|claim|report|message)\b[^.!?]*\b(?:is|was)\s+(?:rejected|retracted)\b/i;
38
+ const NEGATED_RETRACTION_PREFIX_PATTERN = /\b(?:no|not|\w+n['’]t)\b(?:\s+\w+){0,2}\s*$/i;
39
+ const REPORTED_CLAIM_PREFIX_PATTERN = /["“]|\b(?:prior|previous)\s+(?:message|report)\b|\b(?:said|stated|reported)\b/i;
40
+ const POST_CLAIM_SEPARATOR_PATTERN = /^[\s,;:.—–'"“‘’-]+/;
41
+
25
42
  interface CompletionMutationGuardInput {
26
43
  agent: string;
27
44
  task: string;
@@ -36,6 +53,11 @@ interface CompletionMutationGuardResult {
36
53
  triggered: boolean;
37
54
  }
38
55
 
56
+ interface ReportSentence {
57
+ text: string;
58
+ offset: number;
59
+ }
60
+
39
61
  export function hasMutationToolCapability(tools: string[] | undefined, mcpDirectTools: string[] | undefined): boolean {
40
62
  if ((mcpDirectTools?.length ?? 0) > 0) return true;
41
63
  if (tools === undefined) return true;
@@ -82,14 +104,98 @@ export function hasMutationToolCall(messages: Message[]): boolean {
82
104
  return false;
83
105
  }
84
106
 
107
+ function hasImplementationRetraction(text: string): boolean {
108
+ return Array.from(text.matchAll(IMPLEMENTATION_RETRACTION_GLOBAL_PATTERN)).some((retraction) =>
109
+ !NEGATED_RETRACTION_PREFIX_PATTERN.test(text.slice(0, retraction.index!)));
110
+ }
111
+
112
+ function hasDirectClaimRetraction(text: string): boolean {
113
+ return DIRECT_CLAIM_RETRACTION_PATTERN.test(text.replace(POST_CLAIM_SEPARATOR_PATTERN, ""));
114
+ }
115
+
116
+ function isInsideQuotedText(report: string, index: number): boolean {
117
+ let quote: "double" | "single" | undefined;
118
+ for (const char of report.slice(0, index)) {
119
+ if (quote === "double") {
120
+ if (char === "\"" || char === "”") quote = undefined;
121
+ } else if (quote === "single") {
122
+ if (char === "’") quote = undefined;
123
+ } else if (char === "\"" || char === "“") {
124
+ quote = "double";
125
+ } else if (char === "‘") {
126
+ quote = "single";
127
+ }
128
+ }
129
+ return quote !== undefined;
130
+ }
131
+
132
+ function unqualifiedClaimIndex(report: string, sentences: ReportSentence[], pattern: RegExp): number {
133
+ return sentences.findIndex((sentence) => {
134
+ const claim = sentence.text.match(pattern);
135
+ if (claim === null) return false;
136
+ const before = sentence.text.slice(0, claim.index!);
137
+ const after = sentence.text.slice(claim.index! + claim[0].length);
138
+ const previous = before.trimEnd().at(-1);
139
+ return previous !== "'"
140
+ && previous !== "‘"
141
+ && !isInsideQuotedText(report, sentence.offset + claim.index!)
142
+ && !REPORTED_CLAIM_PREFIX_PATTERN.test(before)
143
+ && !NO_BETTER_CHANGE_QUALIFIER_PATTERN.test(before)
144
+ && !CLAIM_CONTRADICTION_PATTERN.test(after)
145
+ && !hasImplementationRetraction(after)
146
+ && !hasDirectClaimRetraction(after);
147
+ });
148
+ }
149
+
150
+ function hasLaterClaimRetraction(sentences: ReportSentence[], claimIndex: number): boolean {
151
+ return sentences.slice(claimIndex + 1).some((sentence) => hasImplementationRetraction(sentence.text)
152
+ || hasDirectClaimRetraction(sentence.text));
153
+ }
154
+
155
+ function explicitlyRejectsQuotedClaim(report: string): boolean {
156
+ for (const [, claim, response] of report.matchAll(QUOTED_CLAIM_PATTERN)) {
157
+ if ((KEPT_CURRENT_IMPLEMENTATION_PATTERN.test(claim!) || NO_CHANGE_MADE_PATTERN.test(claim!))
158
+ && NO_BETTER_CHANGE_QUALIFIER_PATTERN.test(response!)
159
+ && QUOTED_CLAIM_REFERENCE_PATTERN.test(response!)) return true;
160
+ }
161
+ return false;
162
+ }
163
+
164
+ function isImplementationChallengeTask(task: string): boolean {
165
+ const followUp = task.replace(REVIVED_TASK_PREFIX, "");
166
+ return followUp !== task && IMPLEMENTATION_CHALLENGE_FOLLOW_UP_PATTERN.test(followUp.trim());
167
+ }
168
+
169
+ function reportsNoBetterChallengeChange(messages: Message[]): boolean {
170
+ const report = messages
171
+ .filter((message) => message.role === "assistant")
172
+ .flatMap((message) => message.content)
173
+ .flatMap((part) => part.type === "text" ? [part.text] : [])
174
+ .join("\n");
175
+ if (explicitlyRejectsQuotedClaim(report)) return false;
176
+ const sentences = Array.from(report.matchAll(REPORT_SENTENCE_PATTERN), (match) => ({
177
+ text: match[0],
178
+ offset: match.index!,
179
+ }));
180
+ const noBetterIndex = unqualifiedClaimIndex(report, sentences, NO_BETTER_CHANGE_NEEDED_PATTERN);
181
+ if (noBetterIndex >= 0) return !hasLaterClaimRetraction(sentences, noBetterIndex);
182
+ const keptIndex = unqualifiedClaimIndex(report, sentences, KEPT_CURRENT_IMPLEMENTATION_PATTERN);
183
+ const noChangeIndex = unqualifiedClaimIndex(report, sentences, NO_CHANGE_MADE_PATTERN);
184
+ return keptIndex >= 0
185
+ && noChangeIndex >= 0
186
+ && !hasLaterClaimRetraction(sentences, Math.min(keptIndex, noChangeIndex));
187
+ }
188
+
85
189
  export function evaluateCompletionMutationGuard(input: CompletionMutationGuardInput): CompletionMutationGuardResult {
86
190
  const expectedMutation = hasMutationToolCapability(input.tools, input.mcpDirectTools)
87
191
  ? expectsImplementationMutation(input.agent, input.task)
88
192
  : false;
89
193
  const attemptedMutation = hasMutationToolCall(input.messages);
194
+ const noEditChallengeComplete = isImplementationChallengeTask(input.task)
195
+ && reportsNoBetterChallengeChange(input.messages);
90
196
  return {
91
197
  expectedMutation,
92
198
  attemptedMutation,
93
- triggered: expectedMutation && !attemptedMutation,
199
+ triggered: expectedMutation && !attemptedMutation && !noEditChallengeComplete,
94
200
  };
95
201
  }
@@ -35,6 +35,8 @@ export function runExternalCli(input: {
35
35
  timeoutMessage?: string;
36
36
  stopMessage?: string;
37
37
  onProcess?: (process: ExternalProcessStatus) => void;
38
+ onStdout?: (chunk: Buffer) => void;
39
+ onStderr?: (chunk: Buffer) => void;
38
40
  }): Promise<ExternalCliRunResult> {
39
41
  return new Promise((resolve, reject) => {
40
42
  const startedAt = Date.now();
@@ -65,11 +67,13 @@ export function runExternalCli(input: {
65
67
  input.onProcess?.(initialProcess);
66
68
  child.stdout.on("data", (chunk: Buffer) => {
67
69
  stdoutStream.write(chunk);
70
+ input.onStdout?.(chunk);
68
71
  stdoutTail = Buffer.concat([stdoutTail, chunk]);
69
72
  if (stdoutTail.length > MAX_OUTPUT_TAIL_BYTES) stdoutTail = stdoutTail.subarray(stdoutTail.length - MAX_OUTPUT_TAIL_BYTES);
70
73
  });
71
74
  child.stderr.on("data", (chunk: Buffer) => {
72
75
  stderrStream.write(chunk);
76
+ input.onStderr?.(chunk);
73
77
  stderrTail = Buffer.concat([stderrTail, chunk]);
74
78
  if (stderrTail.length > MAX_ERROR_TAIL_BYTES) stderrTail = stderrTail.subarray(stderrTail.length - MAX_ERROR_TAIL_BYTES);
75
79
  });
@@ -2,7 +2,9 @@ import { createHash } from "node:crypto";
2
2
  import { Agent, type AgentTool, type StreamFn } from "@earendil-works/pi-agent-core";
3
3
  import { convertToLlm, type ExtensionContext } from "@selesai/code";
4
4
  import { streamSimple } from "@earendil-works/pi-ai/compat";
5
+ import type { ProviderHeaders } from "@earendil-works/pi-ai";
5
6
  import { Type, type Static } from "typebox";
7
+ import { agentStreamOptions } from "../../shared/agent-stream-options.ts";
6
8
 
7
9
  /**
8
10
  * LLM intent arbiter for the completion mutation guard.
@@ -28,6 +30,10 @@ export type TaskMutationArbiter = (task: string) => Promise<TaskMutationVerdict>
28
30
  const DecisionParams = Type.Object(
29
31
  {
30
32
  classification: Type.String({ enum: ["read_only", "implementation"] }),
33
+ confidence: Type.String({
34
+ enum: ["low", "medium", "high"],
35
+ description: "Confidence in the classification. Only read_only with high confidence rescues a failed run.",
36
+ }),
31
37
  reason: Type.String({ description: "One concise reason for this classification." }),
32
38
  },
33
39
  { additionalProperties: false },
@@ -35,6 +41,15 @@ const DecisionParams = Type.Object(
35
41
 
36
42
  type DecisionParams = Static<typeof DecisionParams>;
37
43
 
44
+ /** Map a model decision to a verdict. Only a high-confidence read_only rescues. */
45
+ export function mapArbiterDecision(
46
+ decision: { classification?: string; confidence?: string } | undefined,
47
+ ): TaskMutationVerdict {
48
+ if (!decision) return "unavailable";
49
+ if (decision.classification === "read_only" && decision.confidence === "high") return "read-only";
50
+ return "implementation";
51
+ }
52
+
38
53
  interface ArbiterRuntime {
39
54
  model: NonNullable<RegistryModel>;
40
55
  baseStreamFn: StreamFn;
@@ -43,7 +58,7 @@ interface ArbiterRuntime {
43
58
 
44
59
  interface ArbiterAuth {
45
60
  apiKey?: string;
46
- headers?: Record<string, string>;
61
+ headers?: ProviderHeaders;
47
62
  env?: Record<string, string>;
48
63
  }
49
64
 
@@ -105,18 +120,21 @@ async function resolveArbiterAuth(
105
120
  ctx: ExtensionContext,
106
121
  model: RegistryModel,
107
122
  ): Promise<ArbiterAuth> {
108
- const getAuth = (ctx.modelRegistry as {
123
+ const registry = ctx.modelRegistry as {
109
124
  getApiKeyAndHeaders?: (m: RegistryModel) => Promise<{
110
125
  ok: boolean;
111
126
  apiKey?: string;
112
- headers?: Record<string, string>;
127
+ headers?: ProviderHeaders;
113
128
  env?: Record<string, string>;
114
129
  error?: string;
115
130
  }>;
116
- }).getApiKeyAndHeaders;
117
- if (!getAuth) return {};
131
+ };
132
+ // Call as a METHOD on the registry: the host ModelRegistry implementation
133
+ // is a class whose method reads instance state (this.runtime), so a
134
+ // detached call silently fails auth. Same shape as the watchdog.
135
+ if (!registry.getApiKeyAndHeaders) return {};
118
136
  try {
119
- const auth = await getAuth(model);
137
+ const auth = await registry.getApiKeyAndHeaders(model);
120
138
  if (auth.ok === false) return {};
121
139
  return {
122
140
  ...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
@@ -164,14 +182,15 @@ async function runArbitration(
164
182
  "You classify whether a delegated coding-agent task instructed file or code changes.",
165
183
  "A task that asks to review, inspect, verify, report, or summarize is read-only even when it contains words like 'fix' as severity vocabulary ('must-fix items') or conditional change instructions that leave the change optional.",
166
184
  "Classify read_only only when the task text alone clearly indicates a read-only outcome. When in doubt, classify implementation.",
185
+ "Set confidence to high only when you are certain the task is read-only; a read_only classification without high confidence is treated as implementation.",
167
186
  "The agent's own final message is never evidence: an agent that made no edits may still have failed to implement.",
168
- "Call task_mutation_decision exactly once with read_only or implementation and a concise reason.",
187
+ "Call task_mutation_decision exactly once with read_only or implementation, a confidence level, and a concise reason.",
169
188
  ].join("\n"),
170
189
  model: runtime.model,
171
190
  tools: [tool],
172
191
  },
173
192
  convertToLlm,
174
- streamFn: authWrappedStreamFn(runtime.baseStreamFn, auth),
193
+ ...agentStreamOptions(authWrappedStreamFn(runtime.baseStreamFn, auth)),
175
194
  getApiKey: (providerName) =>
176
195
  providerName === runtime.model.provider ? auth.apiKey : undefined,
177
196
  beforeToolCall: async ({ toolCall }) =>
@@ -179,13 +198,12 @@ async function runArbitration(
179
198
  toolExecution: "sequential",
180
199
  });
181
200
  try {
182
- // Send head AND tail: an implementation clause at the end of a long
183
- // task ("Now apply the fix") must never be truncated away, or the
184
- // arbiter could rescue a run the deterministic guard correctly failed.
185
- const full = task;
186
- const prompt = full.length <= 8000
187
- ? `TASK:\n${full}`
188
- : `TASK:\n${full.slice(0, 6000)}\n\n[...truncated middle...]\n\n${full.slice(-2000)}`;
201
+ // The rescue gate refuses tasks over 8000 chars; if the arbiter is
202
+ // still invoked with one, fail closed rather than decide from
203
+ // partial evidence (an implementation clause could sit in the
204
+ // omitted middle).
205
+ if (task.length > 8000) return "unavailable";
206
+ const prompt = `TASK:\n${task}`;
189
207
  await Promise.race([
190
208
  agent.prompt(prompt),
191
209
  new Promise<never>((_, reject) => {
@@ -197,7 +215,7 @@ async function runArbitration(
197
215
  }),
198
216
  ]);
199
217
  if (!decision) return "unavailable";
200
- return decision.classification === "read_only" ? "read-only" : "implementation";
218
+ return mapArbiterDecision(decision);
201
219
  } catch {
202
220
  return "unavailable";
203
221
  }
@@ -228,13 +246,6 @@ export function isCompletionGuardFailure(result: { error?: string }): boolean {
228
246
  return result.error?.startsWith(COMPLETION_GUARD_ERROR_PREFIX) === true;
229
247
  }
230
248
 
231
- /**
232
- * Decision helper for the runSync completion-guard arbitration: given the raw
233
- * guard verdict, decide whether the arbiter rescues the run. Pure and
234
- * unit-testable; runSync calls this BEFORE any failure side effect is
235
- * published. Only a confident read-only verdict rescues; every error,
236
- * timeout, or other verdict keeps the guard's original behavior.
237
- */
238
249
  export async function arbitrateCompletionGuardRescue(input: {
239
250
  guardTriggered: boolean;
240
251
  task: string;
@@ -243,6 +254,11 @@ export async function arbitrateCompletionGuardRescue(input: {
243
254
  if (!input.guardTriggered || !input.arbiter) {
244
255
  return { triggered: input.guardTriggered, rescued: false };
245
256
  }
257
+ // Never decide from partial evidence: refuse tasks over 8000 chars before
258
+ // even consulting the model.
259
+ if (input.task.length > 8000) {
260
+ return { triggered: true, rescued: false };
261
+ }
246
262
  try {
247
263
  const verdict = await input.arbiter(input.task);
248
264
  if (verdict === "read-only") return { triggered: false, rescued: true };
@@ -163,6 +163,20 @@ export function resolveModelCandidate(
163
163
  return model;
164
164
  }
165
165
 
166
+ function resolveRequiredSubagentModelCandidate(
167
+ model: string,
168
+ availableModels: AvailableModelInfo[] | undefined,
169
+ preferredProvider?: string,
170
+ ): string {
171
+ if (!availableModels || availableModels.length === 0) return model;
172
+ const resolvedWhole = resolveBaseModelCandidate(model, availableModels, preferredProvider);
173
+ if (resolvedWhole) return resolvedWhole;
174
+ const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
175
+ const resolvedBase = thinkingSuffix ? resolveBaseModelCandidate(baseModel, availableModels, preferredProvider) : undefined;
176
+ if (resolvedBase) return `${resolvedBase}${thinkingSuffix}`;
177
+ throw new Error(`Unknown subagent model '${model}' in the active Pi model registry.`);
178
+ }
179
+
166
180
  export interface ResolveSubagentModelOverrideOptions {
167
181
  /** When set with `enforce: true`, out-of-scope models are rejected. */
168
182
  scope?: ModelScopeConfig;
@@ -207,7 +221,7 @@ export function resolveSubagentModelOverride(
207
221
  if (explicit === undefined) {
208
222
  resolved = parentModel ? `${parentModel.provider}/${parentModel.id}` : undefined;
209
223
  } else {
210
- resolved = resolveModelCandidate(explicit, availableModels, preferredProvider);
224
+ resolved = resolveRequiredSubagentModelCandidate(explicit, availableModels, preferredProvider);
211
225
  }
212
226
  if (resolved && options?.scope?.enforce) {
213
227
  const source: ModelSource = explicit === undefined ? "inherited" : (options.source ?? "inherited");
@@ -264,7 +278,7 @@ export function buildModelCandidates(
264
278
  for (let index = 0; index < rawCandidates.length; index++) {
265
279
  const raw = rawCandidates[index];
266
280
  if (!raw) continue;
267
- const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
281
+ const normalized = resolveRequiredSubagentModelCandidate(raw.trim(), availableModels, preferredProvider);
268
282
  if (!normalized || seen.has(normalized)) continue;
269
283
  if ((index > 0 || options?.scope?.strict === true) && options?.scope?.enforce) {
270
284
  const violation = checkModelScope(normalized, options.scope, "inherited");
@@ -34,6 +34,7 @@ import { THINKING_LEVELS } from "../../shared/model-info.ts";
34
34
  export const NESTED_EVENTS_DIR = path.join(TEMP_ROOT_DIR, "nested-subagent-events");
35
35
  const ROUTE_FILE = "route.json";
36
36
  const REGISTRY_FILE = "registry.json";
37
+ const ROUTE_INDEX_DIR = ".route-index";
37
38
  const MAX_EVENT_BYTES = 64 * 1024;
38
39
  const MAX_STEPS = 12;
39
40
  const MAX_CHILDREN = 16;
@@ -105,6 +106,18 @@ function commonRouteRoot(route: Pick<NestedRoute, "eventSink" | "controlInbox">)
105
106
  return path.dirname(path.resolve(route.eventSink));
106
107
  }
107
108
 
109
+ function routeIndexRoot(): string {
110
+ return path.join(NESTED_EVENTS_DIR, ROUTE_INDEX_DIR, "roots");
111
+ }
112
+
113
+ function routeIndexDir(rootRunId: string): string {
114
+ return path.join(routeIndexRoot(), encodeURIComponent(rootRunId));
115
+ }
116
+
117
+ function routeIndexPath(rootRunId: string, capabilityToken: string): string {
118
+ return path.join(routeIndexDir(rootRunId), `${encodeURIComponent(capabilityToken)}.json`);
119
+ }
120
+
108
121
  function validateRouteShape(route: NestedRoute): void {
109
122
  assertSafeId("rootRunId", route.rootRunId);
110
123
  assertSafeId("capabilityToken", route.capabilityToken);
@@ -121,7 +134,10 @@ export function createNestedRoute(rootRunId: string): NestedRoute {
121
134
  const controlInbox = path.join(routeRoot, "controls");
122
135
  fs.mkdirSync(eventSink, { recursive: true, mode: 0o700 });
123
136
  fs.mkdirSync(controlInbox, { recursive: true, mode: 0o700 });
124
- fs.writeFileSync(path.join(routeRoot, ROUTE_FILE), `${JSON.stringify({ rootRunId, capabilityToken, createdAt: Date.now() })}\n`, { mode: 0o600 });
137
+ const createdAt = Date.now();
138
+ fs.mkdirSync(routeIndexDir(rootRunId), { recursive: true, mode: 0o700 });
139
+ writeAtomicJson(routeIndexPath(rootRunId, capabilityToken), { rootRunId, capabilityToken, routeRoot: path.basename(routeRoot), createdAt });
140
+ writeAtomicJson(path.join(routeRoot, ROUTE_FILE), { rootRunId, capabilityToken, createdAt });
125
141
  return { rootRunId, eventSink, controlInbox, capabilityToken };
126
142
  }
127
143
 
@@ -515,68 +531,57 @@ function registryPath(route: NestedRoute): string {
515
531
  return path.join(commonRouteRoot(route), REGISTRY_FILE);
516
532
  }
517
533
 
534
+ function routeFromIndexEntry(rootRunId: string, filePath: string): NestedRoute | undefined {
535
+ try {
536
+ const metadata = JSON.parse(fs.readFileSync(filePath, "utf-8")) as { rootRunId?: unknown; capabilityToken?: unknown; routeRoot?: unknown; createdAt?: unknown };
537
+ if (metadata.rootRunId !== rootRunId || typeof metadata.capabilityToken !== "string" || typeof metadata.routeRoot !== "string") return undefined;
538
+ const routeRoot = path.join(NESTED_EVENTS_DIR, path.basename(metadata.routeRoot));
539
+ const route = {
540
+ rootRunId,
541
+ eventSink: path.join(routeRoot, "events"),
542
+ controlInbox: path.join(routeRoot, "controls"),
543
+ capabilityToken: metadata.capabilityToken,
544
+ };
545
+ validateRouteShape(route);
546
+ if (!fs.statSync(route.eventSink).isDirectory() || !fs.statSync(route.controlInbox).isDirectory()) return undefined;
547
+ const routeFilePath = path.join(routeRoot, ROUTE_FILE);
548
+ try {
549
+ const routeFile = JSON.parse(fs.readFileSync(routeFilePath, "utf-8")) as { rootRunId?: unknown; capabilityToken?: unknown };
550
+ if (routeFile.rootRunId !== rootRunId || routeFile.capabilityToken !== metadata.capabilityToken) return undefined;
551
+ } catch (error) {
552
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") return undefined;
553
+ try {
554
+ writeAtomicJson(routeFilePath, { rootRunId, capabilityToken: metadata.capabilityToken, createdAt: typeof metadata.createdAt === "number" ? metadata.createdAt : Date.now() });
555
+ } catch {
556
+ // The route index already carries enough data for reload lookup.
557
+ }
558
+ }
559
+ return route;
560
+ } catch {
561
+ return undefined;
562
+ }
563
+ }
564
+
518
565
  export function findNestedRouteForRootId(rootRunId: string): NestedRoute | undefined {
519
566
  assertSafeId("rootRunId", rootRunId);
520
567
  let entries: string[];
521
568
  try {
522
- entries = fs.readdirSync(NESTED_EVENTS_DIR);
569
+ entries = fs.readdirSync(routeIndexDir(rootRunId));
523
570
  } catch (error) {
524
571
  if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
525
572
  throw error;
526
573
  }
527
574
  for (const entry of entries) {
528
- if (!entry.startsWith(`${rootRunId}-`)) continue;
529
- const routeRoot = path.join(NESTED_EVENTS_DIR, entry);
530
- try {
531
- const metadata = JSON.parse(fs.readFileSync(path.join(routeRoot, ROUTE_FILE), "utf-8")) as { rootRunId?: unknown; capabilityToken?: unknown };
532
- if (metadata.rootRunId !== rootRunId || typeof metadata.capabilityToken !== "string") continue;
533
- const route = {
534
- rootRunId,
535
- eventSink: path.join(routeRoot, "events"),
536
- controlInbox: path.join(routeRoot, "controls"),
537
- capabilityToken: metadata.capabilityToken,
538
- };
539
- validateRouteShape(route);
540
- return route;
541
- } catch {
542
- continue;
543
- }
575
+ const route = routeFromIndexEntry(rootRunId, path.join(routeIndexDir(rootRunId), entry));
576
+ if (route) return route;
544
577
  }
545
578
  return undefined;
546
579
  }
547
580
 
548
- /**
549
- * Scan the nested-events directory once and index every route by its root run
550
- * id. Use this when resolving routes for many runs (e.g. listAsyncRuns) so the
551
- * cost is O(routes) total instead of O(runs * routes) from calling
552
- * findNestedRouteForRootId per run.
553
- */
554
581
  export function buildNestedRouteIndex(): Map<string, NestedRoute> {
555
- let entries: string[];
556
- try {
557
- entries = fs.readdirSync(NESTED_EVENTS_DIR);
558
- } catch (error) {
559
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return new Map();
560
- throw error;
561
- }
562
582
  const index = new Map<string, NestedRoute>();
563
- for (const entry of entries) {
564
- const routeRoot = path.join(NESTED_EVENTS_DIR, entry);
565
- try {
566
- const metadata = JSON.parse(fs.readFileSync(path.join(routeRoot, ROUTE_FILE), "utf-8")) as { rootRunId?: unknown; capabilityToken?: unknown };
567
- if (typeof metadata.rootRunId !== "string" || typeof metadata.capabilityToken !== "string") continue;
568
- if (index.has(metadata.rootRunId)) continue;
569
- const route: NestedRoute = {
570
- rootRunId: metadata.rootRunId,
571
- eventSink: path.join(routeRoot, "events"),
572
- controlInbox: path.join(routeRoot, "controls"),
573
- capabilityToken: metadata.capabilityToken,
574
- };
575
- validateRouteShape(route);
576
- index.set(metadata.rootRunId, route);
577
- } catch {
578
- continue;
579
- }
583
+ for (const route of listNestedRoutes()) {
584
+ if (!index.has(route.rootRunId)) index.set(route.rootRunId, route);
580
585
  }
581
586
  return index;
582
587
  }
@@ -630,27 +635,26 @@ function collectScopedNestedRuns(children: NestedRunSummary[] | undefined, scope
630
635
  }
631
636
 
632
637
  function listNestedRoutes(): NestedRoute[] {
633
- let entries: string[];
638
+ let rootEntries: string[];
634
639
  try {
635
- entries = fs.readdirSync(NESTED_EVENTS_DIR);
640
+ rootEntries = fs.readdirSync(routeIndexRoot());
636
641
  } catch (error) {
637
642
  if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
638
643
  throw error;
639
644
  }
640
645
  const routes: NestedRoute[] = [];
641
- for (const entry of entries) {
642
- const routeRoot = path.join(NESTED_EVENTS_DIR, entry);
646
+ for (const rootEntry of rootEntries) {
647
+ let rootRunId: string;
643
648
  try {
644
- const metadata = JSON.parse(fs.readFileSync(path.join(routeRoot, ROUTE_FILE), "utf-8")) as { rootRunId?: unknown; capabilityToken?: unknown };
645
- if (typeof metadata.rootRunId !== "string" || typeof metadata.capabilityToken !== "string") continue;
646
- const route = {
647
- rootRunId: metadata.rootRunId,
648
- eventSink: path.join(routeRoot, "events"),
649
- controlInbox: path.join(routeRoot, "controls"),
650
- capabilityToken: metadata.capabilityToken,
651
- };
652
- validateRouteShape(route);
653
- routes.push(route);
649
+ rootRunId = decodeURIComponent(rootEntry);
650
+ } catch {
651
+ continue;
652
+ }
653
+ try {
654
+ for (const entry of fs.readdirSync(path.join(routeIndexRoot(), rootEntry))) {
655
+ const route = routeFromIndexEntry(rootRunId, path.join(routeIndexRoot(), rootEntry, entry));
656
+ if (route) routes.push(route);
657
+ }
654
658
  } catch {
655
659
  continue;
656
660
  }