@selesai/code 0.3.0 → 0.3.1

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 (230) hide show
  1. package/dist/core/diagnostics.d.ts +1 -0
  2. package/dist/core/diagnostics.d.ts.map +1 -1
  3. package/dist/core/diagnostics.js.map +1 -1
  4. package/dist/core/package-manager.d.ts +10 -0
  5. package/dist/core/package-manager.d.ts.map +1 -1
  6. package/dist/core/package-manager.js +93 -8
  7. package/dist/core/package-manager.js.map +1 -1
  8. package/dist/core/package-manager.test.d.ts +2 -0
  9. package/dist/core/package-manager.test.d.ts.map +1 -0
  10. package/dist/core/package-manager.test.js +20 -0
  11. package/dist/core/package-manager.test.js.map +1 -0
  12. package/dist/core/resource-loader.d.ts +3 -0
  13. package/dist/core/resource-loader.d.ts.map +1 -1
  14. package/dist/core/resource-loader.js +10 -0
  15. package/dist/core/resource-loader.js.map +1 -1
  16. package/dist/core/settings-manager.d.ts +1 -0
  17. package/dist/core/settings-manager.d.ts.map +1 -1
  18. package/dist/core/settings-manager.js.map +1 -1
  19. package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
  20. package/dist/extensions/caveman/index.js +116 -0
  21. package/dist/extensions/caveman/package.json +8 -0
  22. package/dist/extensions/caveman/test/extension.test.js +185 -0
  23. package/dist/extensions/caveman/test/helpers.test.js +67 -0
  24. package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
  25. package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
  26. package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
  27. package/dist/extensions/pi-intercom/config.ts +2 -2
  28. package/dist/extensions/pi-intercom/index.ts +2 -2
  29. package/dist/extensions/pi-intercom/package.json +2 -2
  30. package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
  31. package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
  32. package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
  33. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
  34. package/dist/extensions/pi-powerline-footer/index.ts +9 -16
  35. package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
  36. package/dist/extensions/pi-powerline-footer/working-vibes.ts +2 -9
  37. package/dist/extensions/pi-rewind-hook/index.ts +1 -1
  38. package/dist/extensions/pi-rewind-hook/package.json +1 -1
  39. package/dist/extensions/pi-subagents/README.md +224 -52
  40. package/dist/extensions/pi-subagents/install.mjs +93 -0
  41. package/dist/extensions/pi-subagents/package.json +4 -5
  42. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
  43. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
  44. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
  45. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
  46. package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
  47. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
  48. package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
  49. package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
  50. package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
  51. package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
  52. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
  53. package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
  54. package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
  55. package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
  56. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
  57. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
  58. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
  59. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
  61. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
  62. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
  63. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
  64. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
  65. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
  66. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
  67. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
  68. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
  69. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
  70. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
  71. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
  72. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
  73. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
  74. package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
  75. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
  76. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
  77. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
  78. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
  79. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
  80. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
  81. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
  84. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
  86. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
  87. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
  88. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
  89. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
  90. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
  92. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
  93. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
  94. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
  95. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
  96. package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
  97. package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
  98. package/dist/extensions/pi-subagents/src/shared/utils.ts +100 -15
  99. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
  100. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
  101. package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
  102. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
  103. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
  104. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
  105. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
  106. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
  107. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
  108. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
  109. package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
  110. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
  111. package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
  112. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
  113. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
  114. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
  115. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
  116. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
  117. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
  118. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
  119. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
  120. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
  121. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
  122. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
  123. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
  124. package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
  125. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
  126. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
  127. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
  128. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
  129. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
  130. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
  131. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
  132. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
  133. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
  134. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
  135. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
  136. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
  137. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
  138. package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
  139. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
  140. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
  141. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
  142. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
  143. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
  144. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
  145. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
  146. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
  147. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
  148. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
  149. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
  150. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
  151. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
  152. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
  153. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
  154. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
  155. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
  156. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
  157. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
  158. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
  159. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
  160. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
  161. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
  162. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
  163. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
  164. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
  165. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
  166. package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
  167. package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
  168. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
  169. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
  170. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
  171. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
  172. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
  173. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
  174. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
  175. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
  176. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
  177. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
  178. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
  179. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
  180. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
  181. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
  182. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
  183. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
  184. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
  185. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
  186. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
  187. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
  188. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
  189. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
  190. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
  191. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
  192. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
  193. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
  194. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
  195. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
  196. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
  197. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
  198. package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
  199. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
  200. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
  201. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
  202. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
  203. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
  204. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
  205. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
  206. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
  207. package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
  208. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
  209. package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
  210. package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
  211. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
  212. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
  213. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
  214. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
  215. package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
  216. package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
  217. package/dist/main.d.ts.map +1 -1
  218. package/dist/main.js +4 -0
  219. package/dist/main.js.map +1 -1
  220. package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
  221. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  222. package/dist/modes/interactive/components/first-time-setup.js +19 -5
  223. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  224. package/dist/skills/caveman/SKILL.md +49 -0
  225. package/dist/skills/handoff-text/SKILL.md +13 -0
  226. package/docs/docs.json +4 -0
  227. package/docs/extensions.md +2 -0
  228. package/docs/settings.md +15 -0
  229. package/docs/shared-host-extensions.md +109 -0
  230. package/package.json +1 -1
@@ -0,0 +1,335 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const queueDir = process.env.MOCK_PI_QUEUE_DIR;
5
+
6
+ function exitAfterFlush(code) {
7
+ // process.exit() can truncate buffered stdout/stderr on slow runners (e.g.
8
+ // GitHub Actions), dropping the final lines the parent executor needs to see
9
+ // the run as successful. Drain the writable streams first, then exit.
10
+ // A hard timeout (ref'd, not unref'd) guards against stream.end() callbacks
11
+ // that never fire on some platforms (notably Windows pipe-backed stdout).
12
+ const streams = [process.stdout, process.stderr].filter((s) => !s.destroyed && !s.writableEnded);
13
+ if (streams.length === 0) {
14
+ process.exit(code);
15
+ return;
16
+ }
17
+ let exited = false;
18
+ const forceExit = () => {
19
+ if (exited) return;
20
+ exited = true;
21
+ process.exit(code);
22
+ };
23
+ let pending = streams.length;
24
+ for (const stream of streams) {
25
+ stream.end(() => {
26
+ if (--pending === 0) forceExit();
27
+ });
28
+ }
29
+ setTimeout(forceExit, 500);
30
+ }
31
+
32
+ function fail(message, exitCode = 1) {
33
+ process.stderr.write(`${message}\n`);
34
+ exitAfterFlush(exitCode);
35
+ }
36
+
37
+ function listPendingFiles(dir) {
38
+ return fs.readdirSync(dir)
39
+ .filter((name) => name.startsWith("pending-") && name.endsWith(".json"))
40
+ .sort();
41
+ }
42
+
43
+ function readPendingResponse(filePath) {
44
+ try {
45
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
46
+ } catch (error) {
47
+ if (error && typeof error === "object" && "code" in error) {
48
+ const code = error.code;
49
+ if (code === "ENOENT") return undefined;
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ function hasArgMatcher(response) {
56
+ return Object.prototype.hasOwnProperty.call(response ?? {}, "matchArgIncludes");
57
+ }
58
+
59
+ function responseMatchesArgs(response, args) {
60
+ const matcher = response?.matchArgIncludes;
61
+ if (matcher === undefined) return true;
62
+ const needles = Array.isArray(matcher) ? matcher : [matcher];
63
+ if (needles.length === 0) return true;
64
+ const haystack = args.join("\n");
65
+ return needles.every((needle) => typeof needle === "string" && haystack.includes(needle));
66
+ }
67
+
68
+ function claimResponseFile(dir, fileName) {
69
+ const sourcePath = path.join(dir, fileName);
70
+ const targetPath = path.join(dir, fileName.replace(/^pending-/, "consumed-"));
71
+ try {
72
+ fs.renameSync(sourcePath, targetPath);
73
+ return JSON.parse(fs.readFileSync(targetPath, "utf-8"));
74
+ } catch (error) {
75
+ if (error && typeof error === "object" && "code" in error) {
76
+ const code = error.code;
77
+ if (code === "ENOENT" || code === "EEXIST" || code === "EPERM") return undefined;
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ function claimNextResponse(dir, args) {
84
+ for (const fileName of listPendingFiles(dir)) {
85
+ const sourcePath = path.join(dir, fileName);
86
+ const response = readPendingResponse(sourcePath);
87
+ if (!response || !hasArgMatcher(response) || !responseMatchesArgs(response, args)) continue;
88
+ const claimed = claimResponseFile(dir, fileName);
89
+ if (claimed) return claimed;
90
+ }
91
+
92
+ for (const fileName of listPendingFiles(dir)) {
93
+ const sourcePath = path.join(dir, fileName);
94
+ const response = readPendingResponse(sourcePath);
95
+ if (!response || hasArgMatcher(response)) continue;
96
+ const claimed = claimResponseFile(dir, fileName);
97
+ if (claimed) return claimed;
98
+ }
99
+
100
+ const defaultPath = path.join(dir, "default-response.json");
101
+ if (!fs.existsSync(defaultPath)) return undefined;
102
+ const fallback = JSON.parse(fs.readFileSync(defaultPath, "utf-8"));
103
+ return responseMatchesArgs(fallback, args) ? fallback : undefined;
104
+ }
105
+
106
+ function defaultAssistantMessage(output) {
107
+ return {
108
+ type: "message_end",
109
+ message: {
110
+ role: "assistant",
111
+ content: [{ type: "text", text: output }],
112
+ model: "mock/test-model",
113
+ stopReason: "stop",
114
+ usage: {
115
+ input: 100,
116
+ output: 50,
117
+ cacheRead: 0,
118
+ cacheWrite: 0,
119
+ cost: { total: 0.001 },
120
+ },
121
+ },
122
+ };
123
+ }
124
+
125
+ function taskRequestsAcceptance(args) {
126
+ for (const arg of args) {
127
+ if (typeof arg !== "string") continue;
128
+ if (arg.includes("## Acceptance Contract")) return true;
129
+ if (!arg.startsWith("@")) continue;
130
+ try {
131
+ if (fs.readFileSync(arg.slice(1), "utf-8").includes("## Acceptance Contract")) return true;
132
+ } catch {
133
+ // Ignore unreadable temp prompt references in the mock harness.
134
+ }
135
+ }
136
+ return false;
137
+ }
138
+
139
+ function defaultAcceptanceReport() {
140
+ return [
141
+ "```acceptance-report",
142
+ JSON.stringify({
143
+ criteriaSatisfied: [
144
+ { id: "criterion-1", status: "satisfied", evidence: "mock acceptance evidence" },
145
+ { id: "criterion-2", status: "satisfied", evidence: "mock acceptance evidence" },
146
+ ],
147
+ changedFiles: ["mock-file.ts"],
148
+ testsAddedOrUpdated: ["mock-file.test.ts"],
149
+ commandsRun: [{ command: "mock validation", result: "passed", summary: "passed" }],
150
+ validationOutput: ["mock validation passed"],
151
+ residualRisks: [],
152
+ noStagedFiles: true,
153
+ reviewFindings: [],
154
+ manualNotes: "mock run completed",
155
+ notes: "mock run completed",
156
+ }),
157
+ "```",
158
+ ].join("\n");
159
+ }
160
+
161
+ function withAcceptanceReport(output, args) {
162
+ if (!taskRequestsAcceptance(args) || output.includes("```acceptance-report")) return output;
163
+ return `${output}\n${defaultAcceptanceReport()}`;
164
+ }
165
+
166
+ function defaultResponse() {
167
+ return { output: "ok", exitCode: 0 };
168
+ }
169
+
170
+ function isJsonMode(args) {
171
+ for (let i = 0; i < args.length; i++) {
172
+ if (args[i] === "--mode") {
173
+ return args[i + 1] === "json";
174
+ }
175
+ }
176
+ return false;
177
+ }
178
+
179
+ function writeSessionFile(args) {
180
+ for (let i = 0; i < args.length; i++) {
181
+ if (args[i] !== "--session") continue;
182
+ const sessionFile = args[i + 1];
183
+ if (!sessionFile) return;
184
+ fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
185
+ fs.writeFileSync(sessionFile, "", { flag: "a" });
186
+ return;
187
+ }
188
+ }
189
+
190
+ function readSystemPromptRecords(args) {
191
+ const records = [];
192
+ for (let i = 0; i < args.length; i++) {
193
+ if (args[i] !== "--system-prompt" && args[i] !== "--append-system-prompt") continue;
194
+ const promptPath = args[i + 1];
195
+ if (!promptPath) continue;
196
+ try {
197
+ records.push({
198
+ mode: args[i],
199
+ path: promptPath,
200
+ text: fs.readFileSync(promptPath, "utf-8"),
201
+ });
202
+ } catch (error) {
203
+ records.push({
204
+ mode: args[i],
205
+ path: promptPath,
206
+ error: error instanceof Error ? error.message : String(error),
207
+ });
208
+ }
209
+ }
210
+ return records;
211
+ }
212
+
213
+ async function writeStdout(text) {
214
+ if (process.stdout.write(text)) return;
215
+ await new Promise((resolve) => process.stdout.once("drain", resolve));
216
+ }
217
+
218
+ async function writeJsonlLine(entry) {
219
+ const line = typeof entry === "string" ? entry : JSON.stringify(entry);
220
+ await writeStdout(`${line}\n`);
221
+ }
222
+
223
+ function extractPlainText(entry) {
224
+ if (!entry || typeof entry !== "object") return "";
225
+ if (entry.type === "message_end") {
226
+ const text = entry.message?.content?.find?.((part) => part?.type === "text")?.text;
227
+ return typeof text === "string" ? text : "";
228
+ }
229
+ if (entry.type === "tool_result_end") {
230
+ const text = entry.message?.content?.find?.((part) => part?.type === "text")?.text;
231
+ return typeof text === "string" ? text : "";
232
+ }
233
+ return "";
234
+ }
235
+
236
+ async function writeResponseEntries(entries, jsonMode, args) {
237
+ let sawProviderError = false;
238
+ for (const entry of entries) {
239
+ if (entry?.type === "message_end") {
240
+ const textPart = entry.message?.content?.find?.((part) => part?.type === "text");
241
+ const isProviderError = Boolean(entry.message?.errorMessage || entry.message?.stopReason === "error");
242
+ if (isProviderError) sawProviderError = true;
243
+ if (!isProviderError && textPart && typeof textPart.text === "string" && (!sawProviderError || textPart.text.trim())) {
244
+ textPart.text = withAcceptanceReport(textPart.text, args);
245
+ }
246
+ }
247
+ if (jsonMode) {
248
+ await writeJsonlLine(entry);
249
+ continue;
250
+ }
251
+ const text = extractPlainText(entry);
252
+ if (text) await writeStdout(`${text}\n`);
253
+ }
254
+ }
255
+
256
+ async function maybeWriteStructuredOutput(response, jsonMode) {
257
+ if (!Object.prototype.hasOwnProperty.call(response, "structuredOutput")) return;
258
+ const outputPath = process.env.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE;
259
+ if (!outputPath) return;
260
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
261
+ fs.writeFileSync(outputPath, JSON.stringify(response.structuredOutput), "utf-8");
262
+ if (!jsonMode) return;
263
+ await writeJsonlLine({ type: "tool_execution_start", toolName: "structured_output", args: { value: response.structuredOutput } });
264
+ await writeJsonlLine({
265
+ type: "tool_result_end",
266
+ message: {
267
+ role: "toolResult",
268
+ toolName: "structured_output",
269
+ content: [{ type: "text", text: "Structured output captured." }],
270
+ },
271
+ });
272
+ await writeJsonlLine({ type: "tool_execution_end", toolName: "structured_output" });
273
+ }
274
+
275
+ async function main() {
276
+ if (!queueDir) fail("MOCK_PI_QUEUE_DIR is required.");
277
+ if (!fs.existsSync(queueDir)) fail(`Mock queue dir does not exist: ${queueDir}`);
278
+
279
+ const args = process.argv.slice(2);
280
+ const jsonMode = isJsonMode(args);
281
+ const response = claimNextResponse(queueDir, args) ?? defaultResponse();
282
+ if (response.ignoreSigterm === true) {
283
+ process.on("SIGTERM", () => {});
284
+ }
285
+ writeSessionFile(args);
286
+ fs.writeFileSync(
287
+ path.join(queueDir, `call-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}.json`),
288
+ JSON.stringify({ args, systemPrompts: readSystemPromptRecords(args) }),
289
+ "utf-8",
290
+ );
291
+
292
+ if (typeof response.delay === "number" && response.delay > 0) {
293
+ await new Promise((resolve) => setTimeout(resolve, response.delay));
294
+ }
295
+
296
+ if (Array.isArray(response.steps) && response.steps.length > 0) {
297
+ for (const step of response.steps) {
298
+ if (typeof step?.delay === "number" && step.delay > 0) {
299
+ await new Promise((resolve) => setTimeout(resolve, step.delay));
300
+ }
301
+ if (Array.isArray(step?.jsonl) && step.jsonl.length > 0) {
302
+ await writeResponseEntries(step.jsonl, jsonMode, args);
303
+ }
304
+ if (typeof step?.stderr === "string" && step.stderr.length > 0) {
305
+ process.stderr.write(step.stderr);
306
+ }
307
+ }
308
+ } else if (Array.isArray(response.jsonl) && response.jsonl.length > 0) {
309
+ await writeResponseEntries(response.jsonl, jsonMode, args);
310
+ } else if (Array.isArray(response.echoEnv) && response.echoEnv.length > 0) {
311
+ const envSnapshot = Object.fromEntries(response.echoEnv.map((key) => [key, process.env[key] ?? null]));
312
+ const output = withAcceptanceReport(JSON.stringify(envSnapshot), args);
313
+ if (jsonMode) await writeJsonlLine(defaultAssistantMessage(output));
314
+ else await writeStdout(`${output}\n`);
315
+ } else if (typeof response.output === "string") {
316
+ const output = withAcceptanceReport(response.output, args);
317
+ if (jsonMode) await writeJsonlLine(defaultAssistantMessage(output));
318
+ else await writeStdout(`${output}\n`);
319
+ }
320
+ await maybeWriteStructuredOutput(response, jsonMode);
321
+
322
+ if (typeof response.stderr === "string" && response.stderr.length > 0) {
323
+ process.stderr.write(response.stderr);
324
+ }
325
+
326
+ if (typeof response.keepAliveAfterFinalMessageMs === "number" && response.keepAliveAfterFinalMessageMs > 0) {
327
+ await new Promise((resolve) => setTimeout(resolve, response.keepAliveAfterFinalMessageMs));
328
+ }
329
+
330
+ exitAfterFlush(typeof response.exitCode === "number" ? response.exitCode : 0);
331
+ }
332
+
333
+ main().catch((error) => {
334
+ fail(error instanceof Error ? error.message : String(error));
335
+ });
@@ -0,0 +1,129 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ interface MockPiResponse {
7
+ output?: string;
8
+ stderr?: string;
9
+ exitCode?: number;
10
+ delay?: number;
11
+ keepAliveAfterFinalMessageMs?: number;
12
+ ignoreSigterm?: boolean;
13
+ jsonl?: unknown[];
14
+ steps?: Array<{
15
+ delay?: number;
16
+ jsonl?: unknown[];
17
+ stderr?: string;
18
+ }>;
19
+ echoEnv?: string[];
20
+ matchArgIncludes?: string | string[];
21
+ }
22
+
23
+ export interface MockPi {
24
+ readonly dir: string;
25
+ install(): void;
26
+ uninstall(): void;
27
+ onCall(response: MockPiResponse): void;
28
+ reset(): void;
29
+ callCount(): number;
30
+ }
31
+
32
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
33
+ const SCRIPT_PATH = path.join(__dirname, "mock-pi-script.mjs");
34
+ const CALL_PREFIX = "call-";
35
+ const DEFAULT_RESPONSE_FILE = "default-response.json";
36
+ const QUEUED_PREFIX = "pending-";
37
+
38
+ function ensureDir(dir: string): void {
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ }
41
+
42
+ function writeExecutable(filePath: string, content: string): void {
43
+ fs.writeFileSync(filePath, content, "utf-8");
44
+ fs.chmodSync(filePath, 0o755);
45
+ }
46
+
47
+ function listQueueFiles(queueDir: string, prefix: string): string[] {
48
+ try {
49
+ return fs.readdirSync(queueDir)
50
+ .filter((name) => name.startsWith(prefix))
51
+ .sort();
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ export function createMockPi(): MockPi {
58
+ const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-mock-cli-"));
59
+ const queueDir = path.join(rootDir, "queue");
60
+ const binDir = path.join(rootDir, "bin");
61
+ ensureDir(queueDir);
62
+ ensureDir(binDir);
63
+
64
+ const shellScriptPath = path.join(binDir, "pi");
65
+ const cmdScriptPath = path.join(binDir, "pi.cmd");
66
+ writeExecutable(shellScriptPath, `#!/bin/sh\nexec "${process.execPath}" "${SCRIPT_PATH}" "$@"\n`);
67
+ writeExecutable(cmdScriptPath, `@echo off\r\n"${process.execPath}" "${SCRIPT_PATH}" %*\r\n`);
68
+
69
+ let installed = false;
70
+ let nextSequence = 0;
71
+ let originalPath: string | undefined;
72
+ let originalArgv1: string | undefined;
73
+ let originalQueueEnv: string | undefined;
74
+
75
+ return {
76
+ get dir() {
77
+ return queueDir;
78
+ },
79
+ install() {
80
+ if (installed) return;
81
+ installed = true;
82
+ originalPath = process.env.PATH;
83
+ originalQueueEnv = process.env.MOCK_PI_QUEUE_DIR;
84
+ process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`;
85
+ process.env.MOCK_PI_QUEUE_DIR = queueDir;
86
+ if (process.platform === "win32") {
87
+ originalArgv1 = process.argv[1];
88
+ process.argv[1] = SCRIPT_PATH;
89
+ }
90
+ },
91
+ uninstall() {
92
+ if (!installed) return;
93
+ installed = false;
94
+ if (originalPath === undefined) delete process.env.PATH;
95
+ else process.env.PATH = originalPath;
96
+ if (originalQueueEnv === undefined) delete process.env.MOCK_PI_QUEUE_DIR;
97
+ else process.env.MOCK_PI_QUEUE_DIR = originalQueueEnv;
98
+ if (process.platform === "win32") {
99
+ if (originalArgv1 === undefined) delete process.argv[1];
100
+ else process.argv[1] = originalArgv1;
101
+ }
102
+ try {
103
+ fs.rmSync(rootDir, { recursive: true, force: true });
104
+ } catch {}
105
+ },
106
+ onCall(response) {
107
+ ensureDir(queueDir);
108
+ nextSequence += 1;
109
+ const fileName = `${QUEUED_PREFIX}${String(nextSequence).padStart(6, "0")}.json`;
110
+ const tempPath = path.join(queueDir, `${fileName}.tmp-${process.pid}-${Date.now()}`);
111
+ const finalPath = path.join(queueDir, fileName);
112
+ fs.writeFileSync(tempPath, JSON.stringify(response), "utf-8");
113
+ fs.renameSync(tempPath, finalPath);
114
+ fs.writeFileSync(path.join(queueDir, DEFAULT_RESPONSE_FILE), JSON.stringify(response), "utf-8");
115
+ },
116
+ reset() {
117
+ nextSequence = 0;
118
+ ensureDir(queueDir);
119
+ for (const entry of fs.readdirSync(queueDir)) {
120
+ try {
121
+ fs.rmSync(path.join(queueDir, entry), { recursive: true, force: true });
122
+ } catch {}
123
+ }
124
+ },
125
+ callCount() {
126
+ return listQueueFiles(queueDir, CALL_PREFIX).length;
127
+ },
128
+ };
129
+ }
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import * as fs from "node:fs";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import {
7
+ fauxAssistantMessage,
8
+ fauxText,
9
+ registerFauxProvider,
10
+ } from "@earendil-works/pi-ai";
11
+ import {
12
+ createAgentSession,
13
+ DefaultResourceLoader,
14
+ SessionManager,
15
+ SettingsManager,
16
+ } from "@selesai/code";
17
+
18
+ function readText(filePath) {
19
+ return fs.readFileSync(filePath, "utf-8");
20
+ }
21
+
22
+ function parseArgs(argv) {
23
+ const parsed = {
24
+ mode: "text",
25
+ extensions: [],
26
+ appendSystemPrompts: [],
27
+ noSession: false,
28
+ noSkills: false,
29
+ };
30
+
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const arg = argv[i];
33
+ if (arg === "--mode") {
34
+ parsed.mode = argv[++i] ?? "text";
35
+ continue;
36
+ }
37
+ if (arg === "-p" || arg === "--print") continue;
38
+ if (arg === "--no-session") {
39
+ parsed.noSession = true;
40
+ continue;
41
+ }
42
+ if (arg === "--session") {
43
+ parsed.sessionFile = argv[++i];
44
+ continue;
45
+ }
46
+ if (arg === "--session-dir") {
47
+ parsed.sessionDir = argv[++i];
48
+ continue;
49
+ }
50
+ if (arg === "--model") {
51
+ parsed.model = argv[++i];
52
+ continue;
53
+ }
54
+ if (arg === "--tools") {
55
+ parsed.tools = (argv[++i] ?? "").split(",").map((tool) => tool.trim()).filter(Boolean);
56
+ continue;
57
+ }
58
+ if (arg === "--extension") {
59
+ const extensionPath = argv[++i];
60
+ if (extensionPath) parsed.extensions.push(extensionPath);
61
+ continue;
62
+ }
63
+ if (arg === "--no-extensions") continue;
64
+ if (arg === "--no-skills") {
65
+ parsed.noSkills = true;
66
+ continue;
67
+ }
68
+ if (arg === "--system-prompt") {
69
+ const promptPath = argv[++i];
70
+ if (promptPath) parsed.systemPrompt = readText(promptPath);
71
+ continue;
72
+ }
73
+ if (arg === "--append-system-prompt") {
74
+ const promptPath = argv[++i];
75
+ if (promptPath) parsed.appendSystemPrompts.push(readText(promptPath));
76
+ continue;
77
+ }
78
+ if (arg?.startsWith("--")) continue;
79
+ parsed.prompt = arg;
80
+ }
81
+
82
+ if (parsed.prompt?.startsWith("@")) {
83
+ parsed.prompt = readText(parsed.prompt.slice(1));
84
+ }
85
+ return parsed;
86
+ }
87
+
88
+ function createModelRegistry(model) {
89
+ return {
90
+ find: (provider, id) => provider === model.provider && id === model.id ? model : undefined,
91
+ getAll: () => [model],
92
+ getAvailable: () => [model],
93
+ hasConfiguredAuth: () => true,
94
+ isUsingOAuth: () => false,
95
+ getApiKeyAndHeaders: async () => ({ ok: true, apiKey: "faux", headers: {} }),
96
+ registerProvider: () => {},
97
+ unregisterProvider: () => {},
98
+ };
99
+ }
100
+
101
+ function createSessionManager(parsed, cwd) {
102
+ if (parsed.sessionFile) {
103
+ const sessionDir = parsed.sessionDir ?? path.dirname(parsed.sessionFile);
104
+ const manager = SessionManager.create(cwd, sessionDir);
105
+ manager.setSessionFile(parsed.sessionFile);
106
+ return manager;
107
+ }
108
+ if (parsed.noSession) return SessionManager.inMemory(cwd);
109
+ return SessionManager.create(cwd, parsed.sessionDir);
110
+ }
111
+
112
+ async function main() {
113
+ const parsed = parseArgs(process.argv.slice(2));
114
+ const responseText = process.env.SELESAI_SUBAGENTS_E2E_CHILD_TEXT ?? "CHILD_REAL_SESSION_OK";
115
+ const cwd = process.cwd();
116
+ const ownedAgentDir = process.env.SELESAI_CODING_AGENT_DIR
117
+ ? undefined
118
+ : mkdtempSync(path.join(os.tmpdir(), "pi-e2e-agent-dir-"));
119
+ const agentDir = process.env.SELESAI_CODING_AGENT_DIR ?? ownedAgentDir;
120
+
121
+ const faux = registerFauxProvider({
122
+ provider: "faux-e2e-child",
123
+ models: [{ id: "child", contextWindow: 200_000 }],
124
+ });
125
+ const model = faux.getModel();
126
+ faux.setResponses([
127
+ () => fauxAssistantMessage(fauxText(responseText), { stopReason: "stop" }),
128
+ ]);
129
+
130
+ const settingsManager = SettingsManager.inMemory({
131
+ compaction: { enabled: false },
132
+ retry: { enabled: false },
133
+ });
134
+ const loader = new DefaultResourceLoader({
135
+ cwd,
136
+ agentDir,
137
+ settingsManager,
138
+ additionalExtensionPaths: parsed.extensions,
139
+ noSkills: parsed.noSkills,
140
+ noPromptTemplates: true,
141
+ noThemes: true,
142
+ noContextFiles: true,
143
+ systemPrompt: parsed.systemPrompt,
144
+ appendSystemPrompt: parsed.appendSystemPrompts,
145
+ });
146
+
147
+ try {
148
+ await loader.reload();
149
+ const { session } = await createAgentSession({
150
+ cwd,
151
+ agentDir,
152
+ model,
153
+ modelRegistry: createModelRegistry(model),
154
+ resourceLoader: loader,
155
+ sessionManager: createSessionManager(parsed, cwd),
156
+ settingsManager,
157
+ tools: parsed.tools,
158
+ });
159
+
160
+ session.subscribe((event) => {
161
+ if (
162
+ event.type === "message_end"
163
+ || event.type === "tool_execution_start"
164
+ || event.type === "tool_execution_end"
165
+ || event.type === "tool_result_end"
166
+ ) {
167
+ process.stdout.write(`${JSON.stringify(event)}\n`);
168
+ }
169
+ });
170
+
171
+ await session.bindExtensions({});
172
+ await session.prompt(parsed.prompt ?? "", { expandPromptTemplates: false });
173
+ await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
174
+ session.dispose();
175
+ } finally {
176
+ faux.unregister();
177
+ if (ownedAgentDir) rmSync(ownedAgentDir, { recursive: true, force: true });
178
+ }
179
+ }
180
+
181
+ main().catch((error) => {
182
+ process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
183
+ process.exit(1);
184
+ });