@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,70 @@
1
+ import assert from "node:assert/strict";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import test from "node:test";
6
+
7
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
8
+
9
+ const sourceImportPattern = /from\s+["'](@earendil-works\/[^"']+)["']|import\s+["'](@earendil-works\/[^"']+)["']/g;
10
+ const oldPiScopePattern = /@mariozechner\/pi-/;
11
+ const piPackageJsonSubpathPattern = /@earendil-works\/pi-[^"']+\/package\.json/;
12
+ const cjsPiPackageResolutionPattern = /require(?:\.resolve)?\(\s*["']@earendil-works\/pi-/;
13
+ const exactVersionPattern = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
14
+
15
+ function collectTsFiles(dir: string): string[] {
16
+ const files: string[] = [];
17
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
18
+ const entryPath = path.join(dir, entry.name);
19
+ if (entry.isDirectory()) {
20
+ collectTsFiles(entryPath).forEach((file) => files.push(file));
21
+ } else if (entry.name.endsWith(".ts")) {
22
+ files.push(entryPath);
23
+ }
24
+ }
25
+ return files;
26
+ }
27
+
28
+ test("direct @earendil-works runtime imports are declared for CI installs", () => {
29
+ const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
30
+ const declared = new Set([
31
+ ...Object.keys(packageJson.dependencies ?? {}),
32
+ ...Object.keys(packageJson.devDependencies ?? {}),
33
+ ]);
34
+ const imported = new Set<string>();
35
+
36
+ for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) {
37
+ const source = fs.readFileSync(file, "utf-8");
38
+ for (const match of source.matchAll(sourceImportPattern)) {
39
+ imported.add(match[1] ?? match[2]!);
40
+ }
41
+ }
42
+
43
+ const missing = [...imported].filter((specifier) => !declared.has(specifier)).sort();
44
+ assert.deepEqual(missing, []);
45
+ });
46
+
47
+ test("direct dependency declarations are exact version pins", () => {
48
+ const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
49
+
50
+ for (const section of ["dependencies", "devDependencies"] as const) {
51
+ for (const [name, version] of Object.entries<string>(packageJson[section] ?? {})) {
52
+ assert.match(version, exactVersionPattern, `${section}.${name} should use an exact version`);
53
+ }
54
+ }
55
+ });
56
+
57
+ test("old pi package scope is not used by source or tests", () => {
58
+ for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) {
59
+ const source = fs.readFileSync(file, "utf-8");
60
+ assert.equal(oldPiScopePattern.test(source), false, file);
61
+ }
62
+ });
63
+
64
+ test("Pi package resolution stays export-map safe", () => {
65
+ for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) {
66
+ const source = fs.readFileSync(file, "utf-8");
67
+ assert.equal(piPackageJsonSubpathPattern.test(source), false, `${file} should not resolve unexported package.json subpaths`);
68
+ assert.equal(cjsPiPackageResolutionPattern.test(source), false, `${file} should not use CommonJS resolution for ESM-only Pi packages`);
69
+ }
70
+ });
@@ -0,0 +1,247 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ isParallelGroup,
5
+ flattenSteps,
6
+ mapConcurrent,
7
+ aggregateParallelOutputs,
8
+ MAX_PARALLEL_CONCURRENCY,
9
+ DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
10
+ Semaphore,
11
+ type RunnerSubagentStep,
12
+ type ParallelStepGroup,
13
+ type RunnerStep,
14
+ } from "../../src/runs/shared/parallel-utils.ts";
15
+
16
+ describe("isParallelGroup", () => {
17
+ it("returns true for a parallel step group", () => {
18
+ const step: ParallelStepGroup = {
19
+ parallel: [
20
+ { agent: "a", task: "do stuff" },
21
+ { agent: "b", task: "do other stuff" },
22
+ ],
23
+ };
24
+ assert.equal(isParallelGroup(step), true);
25
+ });
26
+
27
+ it("returns false for a sequential step", () => {
28
+ const step: RunnerSubagentStep = { agent: "a", task: "do stuff" };
29
+ assert.equal(isParallelGroup(step), false);
30
+ });
31
+
32
+ it("returns false when parallel is not an array", () => {
33
+ const step = { parallel: "not-an-array", agent: "a", task: "x" } as unknown as RunnerStep;
34
+ assert.equal(isParallelGroup(step), false);
35
+ });
36
+ });
37
+
38
+ describe("flattenSteps", () => {
39
+ it("returns sequential steps unchanged", () => {
40
+ const steps: RunnerStep[] = [
41
+ { agent: "a", task: "t1" },
42
+ { agent: "b", task: "t2" },
43
+ ];
44
+ const flat = flattenSteps(steps);
45
+ assert.equal(flat.length, 2);
46
+ assert.equal(flat[0].agent, "a");
47
+ assert.equal(flat[1].agent, "b");
48
+ });
49
+
50
+ it("expands parallel groups into individual steps", () => {
51
+ const steps: RunnerStep[] = [
52
+ { agent: "scout", task: "find info" },
53
+ {
54
+ parallel: [
55
+ { agent: "reviewer-a", task: "review part 1" },
56
+ { agent: "reviewer-b", task: "review part 2" },
57
+ ],
58
+ },
59
+ { agent: "summarizer", task: "combine" },
60
+ ];
61
+ const flat = flattenSteps(steps);
62
+ assert.equal(flat.length, 4);
63
+ assert.deepEqual(
64
+ flat.map((s) => s.agent),
65
+ ["scout", "reviewer-a", "reviewer-b", "summarizer"],
66
+ );
67
+ });
68
+
69
+ it("handles empty steps array", () => {
70
+ assert.deepEqual(flattenSteps([]), []);
71
+ });
72
+
73
+ it("handles empty parallel group", () => {
74
+ const steps: RunnerStep[] = [
75
+ { agent: "before", task: "x" },
76
+ { parallel: [] },
77
+ { agent: "after", task: "y" },
78
+ ];
79
+ const flat = flattenSteps(steps);
80
+ assert.equal(flat.length, 2);
81
+ assert.deepEqual(flat.map((s) => s.agent), ["before", "after"]);
82
+ });
83
+ });
84
+
85
+ describe("mapConcurrent", () => {
86
+ it("processes all items and preserves order", async () => {
87
+ const items = [10, 20, 30, 40];
88
+ const results = await mapConcurrent(items, 2, async (item) => item * 2);
89
+ assert.deepEqual(results, [20, 40, 60, 80]);
90
+ });
91
+
92
+ it("respects concurrency limit", async () => {
93
+ let running = 0;
94
+ let maxRunning = 0;
95
+ const items = [1, 2, 3, 4, 5, 6];
96
+
97
+ await mapConcurrent(items, 2, async () => {
98
+ running++;
99
+ maxRunning = Math.max(maxRunning, running);
100
+ await new Promise((r) => setTimeout(r, 10));
101
+ running--;
102
+ });
103
+
104
+ assert.ok(maxRunning <= 2, `max concurrent was ${maxRunning}, expected <= 2`);
105
+ });
106
+
107
+ it("handles empty input", async () => {
108
+ const results = await mapConcurrent([], 4, async (item: number) => item);
109
+ assert.deepEqual(results, []);
110
+ });
111
+
112
+ it("clamps limit=0 to 1 (sequential execution)", async () => {
113
+ let running = 0;
114
+ let maxRunning = 0;
115
+ const items = [1, 2, 3];
116
+ await mapConcurrent(items, 0, async (item) => {
117
+ running++;
118
+ maxRunning = Math.max(maxRunning, running);
119
+ await new Promise((r) => setTimeout(r, 10));
120
+ running--;
121
+ return item * 10;
122
+ });
123
+ assert.equal(maxRunning, 1, "should run sequentially with limit=0");
124
+ });
125
+
126
+ it("clamps limit=-1 to 1 (sequential execution)", async () => {
127
+ let running = 0;
128
+ let maxRunning = 0;
129
+ const items = [1, 2, 3];
130
+ await mapConcurrent(items, -1, async (item) => {
131
+ running++;
132
+ maxRunning = Math.max(maxRunning, running);
133
+ await new Promise((r) => setTimeout(r, 10));
134
+ running--;
135
+ return item * 10;
136
+ });
137
+ assert.equal(maxRunning, 1, "should run sequentially with limit=-1");
138
+ });
139
+
140
+ it("does not stagger by default", async () => {
141
+ const startTimes: number[] = [];
142
+ const items = [1, 2, 3];
143
+
144
+ await mapConcurrent(items, 3, async (_item, i) => {
145
+ startTimes[i] = Date.now();
146
+ await new Promise((r) => setTimeout(r, 10));
147
+ });
148
+
149
+ // All workers should start nearly simultaneously
150
+ const d1 = startTimes[1]! - startTimes[0]!;
151
+ const d2 = startTimes[2]! - startTimes[0]!;
152
+ assert.ok(d1 < 20, `worker 1 should start immediately, got ${d1}ms delay`);
153
+ assert.ok(d2 < 20, `worker 2 should start immediately, got ${d2}ms delay`);
154
+ });
155
+
156
+ it("respects a shared global semaphore across simultaneous calls", async () => {
157
+ const globalSemaphore = new Semaphore(2);
158
+ let running = 0;
159
+ let maxRunning = 0;
160
+ const run = (items: number[]) => mapConcurrent(items, items.length, async (item) => {
161
+ running++;
162
+ maxRunning = Math.max(maxRunning, running);
163
+ await new Promise((r) => setTimeout(r, 10));
164
+ running--;
165
+ return item;
166
+ }, globalSemaphore);
167
+
168
+ const results = await Promise.all([
169
+ run([1, 2, 3]),
170
+ run([4, 5, 6]),
171
+ ]);
172
+
173
+ assert.deepEqual(results, [[1, 2, 3], [4, 5, 6]]);
174
+ assert.ok(maxRunning <= 2, `max concurrent was ${maxRunning}, expected <= 2`);
175
+ });
176
+
177
+ it("clamps invalid global semaphore limits to 1", async () => {
178
+ const globalSemaphore = new Semaphore(0);
179
+ let running = 0;
180
+ let maxRunning = 0;
181
+
182
+ await mapConcurrent([1, 2, 3], 3, async (item) => {
183
+ running++;
184
+ maxRunning = Math.max(maxRunning, running);
185
+ await new Promise((r) => setTimeout(r, 10));
186
+ running--;
187
+ return item;
188
+ }, globalSemaphore);
189
+
190
+ assert.equal(maxRunning, 1);
191
+ });
192
+ });
193
+
194
+ describe("DEFAULT_GLOBAL_CONCURRENCY_LIMIT", () => {
195
+ it("is 20", () => {
196
+ assert.equal(DEFAULT_GLOBAL_CONCURRENCY_LIMIT, 20);
197
+ });
198
+ });
199
+
200
+ describe("aggregateParallelOutputs", () => {
201
+ it("aggregates successful outputs with headers", () => {
202
+ const result = aggregateParallelOutputs([
203
+ { agent: "reviewer-a", output: "Looks good", exitCode: 0 },
204
+ { agent: "reviewer-b", output: "Needs fixes", exitCode: 0 },
205
+ ]);
206
+ assert.ok(result.includes("=== Parallel Task 1 (reviewer-a) ==="));
207
+ assert.ok(result.includes("Looks good"));
208
+ assert.ok(result.includes("=== Parallel Task 2 (reviewer-b) ==="));
209
+ assert.ok(result.includes("Needs fixes"));
210
+ });
211
+
212
+ it("marks failed tasks", () => {
213
+ const result = aggregateParallelOutputs([
214
+ { agent: "agent-a", output: "partial output", exitCode: 1 },
215
+ ]);
216
+ assert.ok(result.includes("FAILED (exit code 1)"));
217
+ });
218
+
219
+ it("marks empty output", () => {
220
+ const result = aggregateParallelOutputs([
221
+ { agent: "agent-a", output: "", exitCode: 0 },
222
+ ]);
223
+ assert.ok(result.includes("EMPTY OUTPUT"));
224
+ });
225
+
226
+ it("treats whitespace-only output as empty", () => {
227
+ const result = aggregateParallelOutputs([
228
+ { agent: "agent-a", output: " \n ", exitCode: 0 },
229
+ ]);
230
+ assert.ok(result.includes("EMPTY OUTPUT"));
231
+ });
232
+
233
+ it("marks skipped tasks (exitCode=-1) distinctly from failed", () => {
234
+ const result = aggregateParallelOutputs([
235
+ { agent: "agent-a", output: "done", exitCode: 0 },
236
+ { agent: "agent-b", output: "(skipped — fail-fast)", exitCode: -1 },
237
+ ]);
238
+ assert.ok(result.includes("SKIPPED"), "skipped task should show SKIPPED");
239
+ assert.ok(!result.includes("FAILED"), "skipped task should not show FAILED");
240
+ });
241
+ });
242
+
243
+ describe("MAX_PARALLEL_CONCURRENCY", () => {
244
+ it("is 4", () => {
245
+ assert.equal(MAX_PARALLEL_CONCURRENCY, 4);
246
+ });
247
+ });
@@ -0,0 +1,113 @@
1
+ import assert from "node:assert/strict";
2
+ import * as path from "node:path";
3
+ import { describe, it } from "node:test";
4
+
5
+ /**
6
+ * Tests for cross-platform path handling patterns used throughout the codebase.
7
+ * These tests document the correct patterns after fixes were applied.
8
+ *
9
+ * Fixed locations:
10
+ * - chain-execution.ts — uses path.isAbsolute() for absolute path detection
11
+ * - settings.ts — uses path.join() for path construction
12
+ */
13
+
14
+ describe("path.isAbsolute vs startsWith('/')", () => {
15
+ // chain-execution.ts:496 uses startsWith("/") to detect absolute paths.
16
+ // On Windows, absolute paths look like "C:\\..." or "C:/..." — neither starts with "/".
17
+
18
+ it("startsWith('/') misses Windows absolute paths", () => {
19
+ const windowsAbsolute = "C:\\dev\\pi-subagents\\output.md";
20
+ const windowsAbsoluteForward = "C:/dev/pi-subagents/output.md";
21
+
22
+ // This is what the current code does (chain-execution.ts:496):
23
+ assert.equal(windowsAbsolute.startsWith("/"), false,
24
+ "Windows backslash absolute path not detected by startsWith('/')");
25
+ assert.equal(windowsAbsoluteForward.startsWith("/"), false,
26
+ "Windows forward-slash absolute path not detected by startsWith('/')");
27
+
28
+ // This is what the code SHOULD do:
29
+ assert.equal(path.isAbsolute(windowsAbsolute), process.platform === "win32",
30
+ "path.isAbsolute correctly identifies Windows paths on Windows");
31
+ assert.equal(path.isAbsolute(windowsAbsoluteForward), process.platform === "win32",
32
+ "path.isAbsolute correctly identifies forward-slash Windows paths on Windows");
33
+
34
+ // POSIX paths work with both approaches
35
+ assert.equal("/home/user/output.md".startsWith("/"), true);
36
+ assert.equal(path.isAbsolute("/home/user/output.md"), true);
37
+ });
38
+
39
+ it("path.isAbsolute is the correct cross-platform check", () => {
40
+ // Relative paths — both approaches agree
41
+ assert.equal(path.isAbsolute("output.md"), false);
42
+ assert.equal(path.isAbsolute("subdir/output.md"), false);
43
+ assert.equal("output.md".startsWith("/"), false);
44
+
45
+ // The only platform-safe check for absolute paths is path.isAbsolute()
46
+ if (process.platform === "win32") {
47
+ // On Windows, these are absolute:
48
+ assert.equal(path.isAbsolute("C:\\output.md"), true);
49
+ assert.equal(path.isAbsolute("C:/output.md"), true);
50
+ assert.equal(path.isAbsolute("\\\\server\\share"), true); // UNC
51
+ // But startsWith("/") catches none of them
52
+ assert.equal("C:\\output.md".startsWith("/"), false);
53
+ assert.equal("C:/output.md".startsWith("/"), false);
54
+ }
55
+ });
56
+ });
57
+
58
+ describe("path.join vs template string concatenation", () => {
59
+ // settings.ts uses `${chainDir}/${file}` in several places.
60
+ // This works but produces inconsistent separators on Windows.
61
+
62
+ it("template concatenation produces forward slashes regardless of platform", () => {
63
+ // chain-execution.ts:496 uses startsWith("/") to detect absolute paths.
64
+ const chainDir = "C:\\Users\\marc\\temp\\chain-abc";
65
+ const file = "progress.md";
66
+
67
+ // Template string: always forward slash
68
+ const templateResult = `${chainDir}/${file}`;
69
+ assert.equal(templateResult, "C:\\Users\\marc\\temp\\chain-abc/progress.md",
70
+ "template string produces mixed separators");
71
+
72
+ // path.join: uses platform separator
73
+ const joinResult = path.join(chainDir, file);
74
+ if (process.platform === "win32") {
75
+ assert.equal(joinResult, "C:\\Users\\marc\\temp\\chain-abc\\progress.md",
76
+ "path.join uses consistent backslashes on Windows");
77
+ }
78
+ });
79
+
80
+ it("resolveChainPath pattern should use path.join for relative paths", () => {
81
+ // settings.ts:216: `${chainDir}/${filePath}` for relative paths
82
+ const chainDir = "C:\\temp\\chain-runs\\abc123";
83
+ const relative = "synthesis.md";
84
+
85
+ // Current: string concat
86
+ const current = `${chainDir}/${relative}`;
87
+ // Fixed: path.join
88
+ const fixed = path.join(chainDir, relative);
89
+
90
+ // On Windows these differ:
91
+ if (process.platform === "win32") {
92
+ assert.notEqual(current, fixed, "concat and path.join produce different results on Windows");
93
+ assert.ok(fixed.includes(path.sep), "path.join uses native separator");
94
+ }
95
+ });
96
+
97
+ it("parallel subdir naming should use path.join", () => {
98
+ // settings.ts:302,306 pattern: `${subdir}/${task.output}`
99
+ const subdir = "parallel-0/0-_code-reviewer";
100
+ const output = "review.md";
101
+
102
+ const templateResult = `${subdir}/${output}`;
103
+ const joinResult = path.join(subdir, output);
104
+
105
+ // Both produce forward slashes here (subdir itself uses /).
106
+ // But if subdir comes from path.join on Windows, it would have backslashes.
107
+ const windowsSubdir = path.join("parallel-0", "0-_code-reviewer");
108
+ const windowsJoin = path.join(windowsSubdir, output);
109
+ // Consistent: all native separators
110
+ assert.equal(windowsJoin, path.join("parallel-0", "0-_code-reviewer", output));
111
+ });
112
+ });
113
+
@@ -0,0 +1,94 @@
1
+ import { describe, test, before, after } from "node:test";
2
+ import * as assert from "node:assert";
3
+ import * as fs from "node:fs";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import { discoverAgents } from "../../src/agents/agents.ts";
7
+ import { resolveSkillPath, clearSkillCache } from "../../src/agents/skills.ts";
8
+
9
+ const tmpDir = path.join(os.tmpdir(), "pi-path-resolution-test");
10
+ const cwdDir = path.join(tmpDir, "cwd");
11
+
12
+ const realHomeDir = os.homedir();
13
+ const realUserAgentsDir = path.join(realHomeDir, ".agents");
14
+ const userAgentsDirBackup = path.join(tmpDir, ".agents_backup");
15
+
16
+ before(() => {
17
+ fs.mkdirSync(cwdDir, { recursive: true });
18
+
19
+ if (fs.existsSync(realUserAgentsDir)) {
20
+ fs.cpSync(realUserAgentsDir, userAgentsDirBackup, { recursive: true });
21
+ }
22
+ });
23
+
24
+ after(() => {
25
+ if (fs.existsSync(userAgentsDirBackup)) {
26
+ fs.rmSync(realUserAgentsDir, { recursive: true, force: true });
27
+ fs.cpSync(userAgentsDirBackup, realUserAgentsDir, { recursive: true });
28
+ } else {
29
+ fs.rmSync(realUserAgentsDir, { recursive: true, force: true });
30
+ }
31
+ fs.rmSync(tmpDir, { recursive: true, force: true });
32
+ });
33
+
34
+ describe("Path resolution for .agents and ~/.agents", () => {
35
+ test("should resolve skills in .agents/skills", () => {
36
+ const skillsDir = path.join(cwdDir, ".agents", "skills");
37
+ fs.mkdirSync(skillsDir, { recursive: true });
38
+ fs.writeFileSync(path.join(skillsDir, "test-skill-1.md"), "---\nname: test-skill-1\ndescription: test desc\n---\nSkill content");
39
+
40
+ clearSkillCache();
41
+ const resolved = resolveSkillPath("test-skill-1", cwdDir);
42
+ assert.ok(resolved);
43
+ assert.strictEqual(resolved?.path, path.join(skillsDir, "test-skill-1.md"));
44
+ });
45
+
46
+ test("should resolve skills in ~/.agents/skills", () => {
47
+ const userSkillsDir = path.join(realHomeDir, ".agents", "skills");
48
+ fs.mkdirSync(userSkillsDir, { recursive: true });
49
+ fs.writeFileSync(path.join(userSkillsDir, "test-skill-2.md"), "---\nname: test-skill-2\ndescription: test desc\n---\nSkill content");
50
+
51
+ clearSkillCache();
52
+ const resolved = resolveSkillPath("test-skill-2", cwdDir);
53
+ assert.ok(resolved);
54
+ assert.strictEqual(resolved?.path, path.join(userSkillsDir, "test-skill-2.md"));
55
+ });
56
+
57
+ test("should resolve project agents from both .agents and .pi/agents", () => {
58
+ const legacyDir = path.join(cwdDir, ".agents");
59
+ const agentsDir = path.join(cwdDir, ".selesai", "agents");
60
+ fs.mkdirSync(path.join(cwdDir, ".agents", "skills"), { recursive: true });
61
+ fs.mkdirSync(legacyDir, { recursive: true });
62
+ fs.mkdirSync(agentsDir, { recursive: true });
63
+ fs.writeFileSync(
64
+ path.join(legacyDir, "test-agent-legacy.md"),
65
+ "---\nname: test-agent-legacy\ndescription: Legacy agent\n---\nLegacy content"
66
+ );
67
+ fs.writeFileSync(
68
+ path.join(agentsDir, "test-agent-1.md"),
69
+ "---\nname: test-agent-1\ndescription: Test agent\n---\nAgent content"
70
+ );
71
+
72
+ const result = discoverAgents(cwdDir, "project");
73
+ const legacyAgent = result.agents.find((a) => a.name === "test-agent-legacy");
74
+ const agent = result.agents.find((a) => a.name === "test-agent-1");
75
+ assert.ok(legacyAgent);
76
+ assert.strictEqual(legacyAgent?.filePath, path.join(legacyDir, "test-agent-legacy.md"));
77
+ assert.ok(agent);
78
+ assert.strictEqual(agent?.filePath, path.join(agentsDir, "test-agent-1.md"));
79
+ });
80
+
81
+ test("should resolve agents in ~/.agents", () => {
82
+ const userAgentsDir = path.join(realHomeDir, ".agents");
83
+ fs.mkdirSync(userAgentsDir, { recursive: true });
84
+ fs.writeFileSync(
85
+ path.join(userAgentsDir, "test-agent-2.md"),
86
+ "---\nname: test-agent-2\ndescription: Test agent\n---\nAgent content"
87
+ );
88
+
89
+ const result = discoverAgents(cwdDir, "user");
90
+ const agent = result.agents.find((a) => a.name === "test-agent-2");
91
+ assert.ok(agent);
92
+ assert.strictEqual(agent?.filePath, path.join(userAgentsDir, "test-agent-2.md"));
93
+ });
94
+ });