@aexol/spectral 0.7.1 → 0.7.5

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 (219) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/agent/agents.js +1 -1
  3. package/dist/agent/index.js +199 -184
  4. package/dist/commands/serve.js +0 -3
  5. package/dist/designer/data/systems/renault/DESIGN.md +1 -1
  6. package/dist/designer/philosophies.js +668 -0
  7. package/dist/mcp/sampling-handler.js +1 -1
  8. package/dist/memory/commands/status.js +1 -1
  9. package/dist/memory/compaction.js +2 -2
  10. package/dist/memory/config.js +1 -1
  11. package/dist/memory/debug-log.js +1 -1
  12. package/dist/memory/hooks/compaction-hook.js +29 -0
  13. package/dist/memory/index.js +2 -0
  14. package/dist/memory/observer.js +2 -2
  15. package/dist/memory/project-observations-store.js +14 -0
  16. package/dist/memory/tokens.js +1 -1
  17. package/dist/memory/tools/read-project-observations.js +82 -0
  18. package/dist/memory/tools/recall-observation.js +2 -2
  19. package/dist/pi/agent-core/agent-loop.js +501 -0
  20. package/dist/pi/agent-core/agent.js +401 -0
  21. package/dist/pi/agent-core/harness/agent-harness.js +899 -0
  22. package/dist/pi/agent-core/harness/compaction/branch-summarization.js +173 -0
  23. package/dist/pi/agent-core/harness/compaction/compaction.js +532 -0
  24. package/dist/pi/agent-core/harness/compaction/utils.js +130 -0
  25. package/dist/pi/agent-core/harness/env/nodejs.js +485 -0
  26. package/dist/pi/agent-core/harness/messages.js +101 -0
  27. package/dist/pi/agent-core/harness/prompt-templates.js +229 -0
  28. package/dist/pi/agent-core/harness/session/jsonl-repo.js +100 -0
  29. package/dist/pi/agent-core/harness/session/jsonl-storage.js +230 -0
  30. package/dist/pi/agent-core/harness/session/memory-repo.js +41 -0
  31. package/dist/pi/agent-core/harness/session/memory-storage.js +113 -0
  32. package/dist/pi/agent-core/harness/session/repo-utils.js +38 -0
  33. package/dist/pi/agent-core/harness/session/session.js +196 -0
  34. package/dist/pi/agent-core/harness/session/uuid.js +49 -0
  35. package/dist/pi/agent-core/harness/skills.js +310 -0
  36. package/dist/pi/agent-core/harness/system-prompt.js +29 -0
  37. package/dist/pi/agent-core/harness/types.js +93 -0
  38. package/dist/pi/agent-core/harness/utils/shell-output.js +125 -0
  39. package/dist/pi/agent-core/harness/utils/truncate.js +289 -0
  40. package/dist/pi/agent-core/index.js +24 -0
  41. package/dist/pi/agent-core/node.js +2 -0
  42. package/dist/pi/agent-core/proxy.js +277 -0
  43. package/dist/pi/agent-core/types.js +1 -0
  44. package/dist/pi/ai/api-registry.js +43 -0
  45. package/dist/pi/ai/cli.js +120 -0
  46. package/dist/pi/ai/env-api-keys.js +169 -0
  47. package/dist/pi/ai/image-models.generated.js +441 -0
  48. package/dist/pi/ai/image-models.js +22 -0
  49. package/dist/pi/ai/images-api-registry.js +21 -0
  50. package/dist/pi/ai/images.js +13 -0
  51. package/dist/pi/ai/index.js +18 -0
  52. package/dist/pi/ai/models.generated.js +16220 -0
  53. package/dist/pi/ai/models.js +70 -0
  54. package/dist/pi/ai/oauth.js +1 -0
  55. package/dist/pi/ai/providers/anthropic.js +945 -0
  56. package/dist/pi/ai/providers/faux.js +367 -0
  57. package/dist/pi/ai/providers/github-copilot-headers.js +28 -0
  58. package/dist/pi/ai/providers/openai-completions.js +945 -0
  59. package/dist/pi/ai/providers/openai-prompt-cache.js +9 -0
  60. package/dist/pi/ai/providers/register-builtins.js +97 -0
  61. package/dist/pi/ai/providers/simple-options.js +40 -0
  62. package/dist/pi/ai/providers/transform-messages.js +183 -0
  63. package/dist/pi/ai/session-resources.js +21 -0
  64. package/dist/pi/ai/stream.js +26 -0
  65. package/dist/pi/ai/types.js +1 -0
  66. package/dist/pi/ai/utils/diagnostics.js +24 -0
  67. package/dist/pi/ai/utils/event-stream.js +80 -0
  68. package/dist/pi/ai/utils/hash.js +13 -0
  69. package/dist/pi/ai/utils/headers.js +7 -0
  70. package/dist/pi/ai/utils/json-parse.js +112 -0
  71. package/dist/pi/ai/utils/node-http-proxy.js +96 -0
  72. package/dist/pi/ai/utils/oauth/anthropic.js +334 -0
  73. package/dist/pi/ai/utils/oauth/device-code.js +54 -0
  74. package/dist/pi/ai/utils/oauth/github-copilot.js +270 -0
  75. package/dist/pi/ai/utils/oauth/index.js +121 -0
  76. package/dist/pi/ai/utils/oauth/oauth-page.js +104 -0
  77. package/dist/pi/ai/utils/oauth/openai-codex.js +384 -0
  78. package/dist/pi/ai/utils/oauth/pkce.js +30 -0
  79. package/dist/pi/ai/utils/oauth/types.js +1 -0
  80. package/dist/pi/ai/utils/overflow.js +150 -0
  81. package/dist/pi/ai/utils/sanitize-unicode.js +25 -0
  82. package/dist/pi/ai/utils/typebox-helpers.js +20 -0
  83. package/dist/pi/ai/utils/validation.js +280 -0
  84. package/dist/pi/coding-agent/bun/cli.js +7 -0
  85. package/dist/pi/coding-agent/bun/restore-sandbox-env.js +31 -0
  86. package/dist/pi/coding-agent/cli/args.js +340 -0
  87. package/dist/pi/coding-agent/cli/file-processor.js +82 -0
  88. package/dist/pi/coding-agent/cli/initial-message.js +21 -0
  89. package/dist/pi/coding-agent/cli.js +17 -0
  90. package/dist/pi/coding-agent/config.js +414 -0
  91. package/dist/pi/coding-agent/core/agent-session-runtime.js +299 -0
  92. package/dist/pi/coding-agent/core/agent-session-services.js +117 -0
  93. package/dist/pi/coding-agent/core/agent-session.js +2498 -0
  94. package/dist/pi/coding-agent/core/auth-guidance.js +20 -0
  95. package/dist/pi/coding-agent/core/auth-storage.js +441 -0
  96. package/dist/pi/coding-agent/core/bash-executor.js +110 -0
  97. package/dist/pi/coding-agent/core/compaction/branch-summarization.js +242 -0
  98. package/dist/pi/coding-agent/core/compaction/compaction.js +624 -0
  99. package/dist/pi/coding-agent/core/compaction/index.js +6 -0
  100. package/dist/pi/coding-agent/core/compaction/utils.js +152 -0
  101. package/dist/pi/coding-agent/core/defaults.js +1 -0
  102. package/dist/pi/coding-agent/core/diagnostics.js +1 -0
  103. package/dist/pi/coding-agent/core/event-bus.js +24 -0
  104. package/dist/pi/coding-agent/core/exec.js +74 -0
  105. package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +248 -0
  106. package/dist/pi/coding-agent/core/export-html/index.js +225 -0
  107. package/dist/pi/coding-agent/core/export-html/tool-renderer.js +107 -0
  108. package/dist/pi/coding-agent/core/extensions/index.js +8 -0
  109. package/dist/pi/coding-agent/core/extensions/loader.js +485 -0
  110. package/dist/pi/coding-agent/core/extensions/runner.js +824 -0
  111. package/dist/pi/coding-agent/core/extensions/types.js +44 -0
  112. package/dist/pi/coding-agent/core/extensions/wrapper.js +21 -0
  113. package/dist/pi/coding-agent/core/footer-data-provider.js +309 -0
  114. package/dist/pi/coding-agent/core/http-dispatcher.js +47 -0
  115. package/dist/pi/coding-agent/core/index.js +11 -0
  116. package/dist/pi/coding-agent/core/keybindings.js +294 -0
  117. package/dist/pi/coding-agent/core/messages.js +122 -0
  118. package/dist/pi/coding-agent/core/model-registry.js +728 -0
  119. package/dist/pi/coding-agent/core/model-resolver.js +494 -0
  120. package/dist/pi/coding-agent/core/output-guard.js +58 -0
  121. package/dist/pi/coding-agent/core/package-manager.js +2020 -0
  122. package/dist/pi/coding-agent/core/prompt-templates.js +237 -0
  123. package/dist/pi/coding-agent/core/provider-display-names.js +32 -0
  124. package/dist/pi/coding-agent/core/resolve-config-value.js +125 -0
  125. package/dist/pi/coding-agent/core/resource-loader.js +733 -0
  126. package/dist/pi/coding-agent/core/sdk.js +282 -0
  127. package/dist/pi/coding-agent/core/session-cwd.js +37 -0
  128. package/dist/pi/coding-agent/core/session-manager.js +1146 -0
  129. package/dist/pi/coding-agent/core/settings-manager.js +794 -0
  130. package/dist/pi/coding-agent/core/skills.js +386 -0
  131. package/dist/pi/coding-agent/core/slash-commands.js +24 -0
  132. package/dist/pi/coding-agent/core/source-info.js +18 -0
  133. package/dist/pi/coding-agent/core/system-prompt.js +122 -0
  134. package/dist/pi/coding-agent/core/telemetry.js +8 -0
  135. package/dist/pi/coding-agent/core/timings.js +30 -0
  136. package/dist/pi/coding-agent/core/tools/bash.js +341 -0
  137. package/dist/pi/coding-agent/core/tools/edit-diff.js +344 -0
  138. package/dist/pi/coding-agent/core/tools/edit.js +324 -0
  139. package/dist/pi/coding-agent/core/tools/file-mutation-queue.js +36 -0
  140. package/dist/pi/coding-agent/core/tools/find.js +297 -0
  141. package/dist/pi/coding-agent/core/tools/grep.js +303 -0
  142. package/dist/pi/coding-agent/core/tools/index.js +111 -0
  143. package/dist/pi/coding-agent/core/tools/ls.js +168 -0
  144. package/dist/pi/coding-agent/core/tools/output-accumulator.js +183 -0
  145. package/dist/pi/coding-agent/core/tools/path-utils.js +61 -0
  146. package/dist/pi/coding-agent/core/tools/read.js +288 -0
  147. package/dist/pi/coding-agent/core/tools/render-utils.js +48 -0
  148. package/dist/pi/coding-agent/core/tools/tool-definition-wrapper.js +33 -0
  149. package/dist/pi/coding-agent/core/tools/truncate.js +214 -0
  150. package/dist/pi/coding-agent/core/tools/write.js +212 -0
  151. package/dist/pi/coding-agent/index.js +41 -0
  152. package/dist/pi/coding-agent/main.js +5 -0
  153. package/dist/pi/coding-agent/migrations.js +280 -0
  154. package/dist/pi/coding-agent/modes/index.js +7 -0
  155. package/dist/pi/coding-agent/modes/interactive/components/diff.js +132 -0
  156. package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +35 -0
  157. package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +32 -0
  158. package/dist/pi/coding-agent/modes/interactive/interactive-mode.js +3 -0
  159. package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1023 -0
  160. package/dist/pi/coding-agent/modes/print-mode.js +130 -0
  161. package/dist/pi/coding-agent/modes/rpc/jsonl.js +48 -0
  162. package/dist/pi/coding-agent/modes/rpc/rpc-client.js +409 -0
  163. package/dist/pi/coding-agent/modes/rpc/rpc-mode.js +600 -0
  164. package/dist/pi/coding-agent/modes/rpc/rpc-types.js +7 -0
  165. package/dist/pi/coding-agent/utils/ansi.js +51 -0
  166. package/dist/pi/coding-agent/utils/changelog.js +86 -0
  167. package/dist/pi/coding-agent/utils/child-process.js +87 -0
  168. package/dist/pi/coding-agent/utils/clipboard-image.js +244 -0
  169. package/dist/pi/coding-agent/utils/clipboard-native.js +13 -0
  170. package/dist/pi/coding-agent/utils/clipboard.js +116 -0
  171. package/dist/pi/coding-agent/utils/exif-orientation.js +157 -0
  172. package/dist/pi/coding-agent/utils/frontmatter.js +25 -0
  173. package/dist/pi/coding-agent/utils/fs-watch.js +24 -0
  174. package/dist/pi/coding-agent/utils/git.js +162 -0
  175. package/dist/pi/coding-agent/utils/html.js +39 -0
  176. package/dist/pi/coding-agent/utils/image-convert.js +38 -0
  177. package/dist/pi/coding-agent/utils/image-resize.js +136 -0
  178. package/dist/pi/coding-agent/utils/mime.js +68 -0
  179. package/dist/pi/coding-agent/utils/paths.js +91 -0
  180. package/dist/pi/coding-agent/utils/photon.js +120 -0
  181. package/dist/pi/coding-agent/utils/pi-user-agent.js +4 -0
  182. package/dist/pi/coding-agent/utils/shell.js +194 -0
  183. package/dist/pi/coding-agent/utils/sleep.js +16 -0
  184. package/dist/pi/coding-agent/utils/syntax-highlight.js +117 -0
  185. package/dist/pi/coding-agent/utils/tools-manager.js +327 -0
  186. package/dist/pi/coding-agent/utils/version-check.js +81 -0
  187. package/dist/pi/coding-agent/utils/windows-self-update.js +76 -0
  188. package/dist/pi/tui/autocomplete.js +631 -0
  189. package/dist/pi/tui/components/box.js +103 -0
  190. package/dist/pi/tui/components/cancellable-loader.js +34 -0
  191. package/dist/pi/tui/components/editor.js +1915 -0
  192. package/dist/pi/tui/components/image.js +88 -0
  193. package/dist/pi/tui/components/input.js +425 -0
  194. package/dist/pi/tui/components/loader.js +68 -0
  195. package/dist/pi/tui/components/markdown.js +633 -0
  196. package/dist/pi/tui/components/select-list.js +158 -0
  197. package/dist/pi/tui/components/settings-list.js +184 -0
  198. package/dist/pi/tui/components/spacer.js +22 -0
  199. package/dist/pi/tui/components/text.js +88 -0
  200. package/dist/pi/tui/components/truncated-text.js +50 -0
  201. package/dist/pi/tui/editor-component.js +1 -0
  202. package/dist/pi/tui/fuzzy.js +109 -0
  203. package/dist/pi/tui/index.js +31 -0
  204. package/dist/pi/tui/keybindings.js +173 -0
  205. package/dist/pi/tui/keys.js +1172 -0
  206. package/dist/pi/tui/kill-ring.js +43 -0
  207. package/dist/pi/tui/stdin-buffer.js +360 -0
  208. package/dist/pi/tui/terminal-image.js +335 -0
  209. package/dist/pi/tui/terminal.js +324 -0
  210. package/dist/pi/tui/tui.js +1076 -0
  211. package/dist/pi/tui/undo-stack.js +24 -0
  212. package/dist/pi/tui/utils.js +1016 -0
  213. package/dist/relay/dispatcher.js +30 -0
  214. package/dist/server/handlers/queue.js +52 -0
  215. package/dist/server/pi-bridge.js +9 -1
  216. package/dist/server/session-stream.js +76 -111
  217. package/dist/server/storage.js +154 -2
  218. package/dist/server/title-generator.js +14 -153
  219. package/package.json +24 -6
package/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.7.2] - 2026-05-25
9
+
10
+ ### Changed
11
+ - Version bump for monorepo release
12
+
8
13
  ### Added
9
14
  - Admin OpenRouter catalog picker in `/studio/admin/models` (Base Models tab):
10
15
  search across 368 models from `https://openrouter.ai/api/v1/models` and add
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import * as fs from "node:fs";
19
19
  import * as path from "node:path";
20
- import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
20
+ import { getAgentDir, parseFrontmatter } from "../pi/coding-agent/index.js";
21
21
  function loadAgentsFromDir(dir, source) {
22
22
  const agents = [];
23
23
  if (!fs.existsSync(dir)) {
@@ -2,10 +2,10 @@
2
2
  * Subagent Delegation Extension for Spectral
3
3
  *
4
4
  * Registers a `subagent` tool that allows the coding agent to delegate tasks
5
- * to specialized sub-agents running in isolated `pi` / `spectral` processes.
5
+ * to specialized sub-agents running in-process via `agentLoop()`.
6
6
  *
7
- * Each subagent invocation spawns a separate process with its own context
8
- * window, keeping the main conversation lean and focused.
7
+ * Each subagent invocation runs with its own fresh AgentContext (isolated
8
+ * context window), keeping the main conversation lean and focused.
9
9
  *
10
10
  * Three execution modes:
11
11
  * - Single: { agent: "name", task: "..." }
@@ -16,20 +16,25 @@
16
16
  * - ~/.pi/agent/agents/*.md (user-level, always loaded)
17
17
  * - .pi/agents/*.md (project-level, opt-in via agentScope)
18
18
  *
19
- * The subagent binary is auto-detected:
20
- * - If running via `spectral`, uses the same invocation path
21
- * - Falls back to `pi` otherwise
19
+ * Subagents use an in-process `agentLoop()` for:
20
+ * - Clean context isolation (fresh messages array)
21
+ * - Combined system prompt (main agent + subagent-specific instructions)
22
+ * - Tool filtering (only the tools specified in agent config)
23
+ * - Model selection (session model preferred, agent-configured as fallback)
24
+ * - Signal propagation (abort passthrough)
25
+ * - Access to observational memory via the recall tool
22
26
  */
23
- import { spawn } from "node:child_process";
24
- import * as fs from "node:fs";
25
- import * as os from "node:os";
26
- import * as path from "node:path";
27
- import { StringEnum } from "@mariozechner/pi-ai";
28
- import { withFileMutationQueue, } from "@mariozechner/pi-coding-agent";
29
- import { Text } from "@mariozechner/pi-tui";
27
+ import { agentLoop } from "../pi/agent-core/index.js";
28
+ import { StringEnum } from "../pi/ai/index.js";
29
+ import { Text } from "../pi/tui/index.js";
30
30
  import { Type } from "typebox";
31
31
  import { discoverAgents } from "./agents.js";
32
32
  // ---------------------------------------------------------------------------
33
+ // Built-in tool resolution (no spawn needed — tools are registered in-process)
34
+ // ---------------------------------------------------------------------------
35
+ import { createTool, allToolNames } from "../pi/coding-agent/core/tools/index.js";
36
+ import { wrapToolDefinition } from "../pi/coding-agent/core/tools/tool-definition-wrapper.js";
37
+ // ---------------------------------------------------------------------------
33
38
  // Constants
34
39
  // ---------------------------------------------------------------------------
35
40
  const MAX_PARALLEL_TASKS = 8;
@@ -83,7 +88,72 @@ function getFinalOutput(messages) {
83
88
  return "";
84
89
  }
85
90
  // ---------------------------------------------------------------------------
86
- // Helpers — concurrency, temp files, binary detection
91
+ // Helpers — model resolution
92
+ // ---------------------------------------------------------------------------
93
+ /**
94
+ * Resolve a model string (e.g. "claude-sonnet-4-5" or "anthropic/claude-sonnet-4-5")
95
+ * to a Model object from the registry.
96
+ */
97
+ function resolveModelString(modelStr, modelRegistry) {
98
+ if (!modelStr)
99
+ return undefined;
100
+ const allModels = modelRegistry.getAll();
101
+ // Try "provider/modelId" format
102
+ const slashIdx = modelStr.indexOf("/");
103
+ if (slashIdx !== -1) {
104
+ const provider = modelStr.substring(0, slashIdx);
105
+ const modelId = modelStr.substring(slashIdx + 1);
106
+ const match = allModels.find((m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase());
107
+ if (match)
108
+ return match;
109
+ }
110
+ // Try exact model ID match
111
+ const idMatch = allModels.find((m) => m.id.toLowerCase() === modelStr.toLowerCase());
112
+ if (idMatch)
113
+ return idMatch;
114
+ // Try fuzzy match (model ID contains the string)
115
+ const fuzzy = allModels.find((m) => m.id.toLowerCase().includes(modelStr.toLowerCase()));
116
+ return fuzzy;
117
+ }
118
+ // ---------------------------------------------------------------------------
119
+ // Helpers — tool resolution
120
+ // ---------------------------------------------------------------------------
121
+ /**
122
+ * Resolve tool names from an agent definition into AgentTool objects.
123
+ *
124
+ * Built-in tools (read, bash, edit, write, grep, find, ls) are created via
125
+ * `createTool()`. Extension-registered tools (e.g., recall) are resolved
126
+ * via the ExtensionAPI and wrapped.
127
+ */
128
+ function resolveSubagentTools(toolNames, cwd, pi, ctx) {
129
+ if (!toolNames || toolNames.length === 0)
130
+ return [];
131
+ const tools = [];
132
+ for (const name of toolNames) {
133
+ // Built-in tools
134
+ if (allToolNames.has(name)) {
135
+ try {
136
+ const tool = createTool(name, cwd);
137
+ tools.push(tool);
138
+ }
139
+ catch {
140
+ // Tool creation failed — skip
141
+ }
142
+ continue;
143
+ }
144
+ // Extension-registered tools (recall, designer_*, etc.)
145
+ // pi at runtime is the embedded ExtensionAPI which has getToolDefinition;
146
+ // the npm types don't expose it, so we cast.
147
+ const richPi = pi;
148
+ const toolDef = richPi.getToolDefinition(name);
149
+ if (toolDef) {
150
+ tools.push(wrapToolDefinition(toolDef, () => ctx));
151
+ }
152
+ }
153
+ return tools;
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // Helpers — concurrency
87
157
  // ---------------------------------------------------------------------------
88
158
  async function mapWithConcurrencyLimit(items, concurrency, fn) {
89
159
  if (items.length === 0)
@@ -102,65 +172,10 @@ async function mapWithConcurrencyLimit(items, concurrency, fn) {
102
172
  await Promise.all(workers);
103
173
  return results;
104
174
  }
105
- async function writePromptToTempFile(agentName, prompt) {
106
- const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "spectral-subagent-"));
107
- const safeName = agentName.replace(/[^\w.-]+/g, "_");
108
- const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
109
- await withFileMutationQueue(filePath, async () => {
110
- await fs.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
111
- });
112
- return { dir: tmpDir, filePath };
113
- }
114
- /**
115
- * Detect the correct binary for spawning subagent processes.
116
- *
117
- * Strategy:
118
- * 1. If `process.argv[1]` points to an existing script (unlikely in bundled
119
- * env but possible during dev with tsx), use that script with node.
120
- * 2. If the executor is named `spectral` or `aexol`, use it directly (the
121
- * wrapper ensures MCP + memory extensions are auto-loaded).
122
- * 3. Otherwise fall back to `pi`.
123
- */
124
- function getPiInvocation(subagentArgs) {
125
- const currentScript = process.argv[1];
126
- // Case 1: running via tsx with an existing script file
127
- if (currentScript && fs.existsSync(currentScript)) {
128
- return { command: process.execPath, args: [currentScript, ...subagentArgs] };
129
- }
130
- // Case 2: check if this is the spectral/aexol wrapper binary
131
- const execName = path.basename(process.execPath).toLowerCase();
132
- if (execName === "spectral" || execName === "aexol") {
133
- return { command: process.execPath, args: subagentArgs };
134
- }
135
- // Case 3: generic node — try spectral first, then pi
136
- function hasBin(name) {
137
- const PATH = process.env.PATH || "";
138
- const envPathSep = process.platform === "win32" ? ";" : ":";
139
- for (const dir of PATH.split(envPathSep)) {
140
- const candidate = path.join(dir, name);
141
- try {
142
- if (fs.existsSync(candidate))
143
- return true;
144
- }
145
- catch {
146
- /* skip */
147
- }
148
- }
149
- return false;
150
- }
151
- if (hasBin("spectral")) {
152
- return { command: "spectral", args: subagentArgs };
153
- }
154
- if (hasBin("pi")) {
155
- return { command: "pi", args: subagentArgs };
156
- }
157
- // Last resort: use node with pi from node_modules (matches pi's own fallback)
158
- return { command: "pi", args: subagentArgs };
159
- }
160
175
  // ---------------------------------------------------------------------------
161
- // Core — run a single subagent
176
+ // Core — run a single subagent in-process via agentLoop
162
177
  // ---------------------------------------------------------------------------
163
- async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails) {
178
+ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails, pi, ctx) {
164
179
  const agent = agents.find((a) => a.name === agentName);
165
180
  if (!agent) {
166
181
  const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
@@ -175,13 +190,47 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
175
190
  step,
176
191
  };
177
192
  }
178
- const args = ["--mode", "json", "-p", "--no-session"];
179
- if (agent.model)
180
- args.push("--model", agent.model);
181
- if (agent.tools && agent.tools.length > 0)
182
- args.push("--tools", agent.tools.join(","));
183
- let tmpPromptDir = null;
184
- let tmpPromptPath = null;
193
+ // Build combined system prompt: main agent's + subagent's
194
+ const mainSystemPrompt = ctx.getSystemPrompt();
195
+ const combinedPrompt = mainSystemPrompt
196
+ ? `${mainSystemPrompt}\n\n---\n\nSubagent: ${agent.name}\n${agent.systemPrompt}`
197
+ : agent.systemPrompt;
198
+ // Resolve model — session model preferred, agent-configured as fallback
199
+ const sessionModel = ctx.model && ctx.modelRegistry.hasConfiguredAuth(ctx.model) ? ctx.model : undefined;
200
+ const model = sessionModel ?? resolveModelString(agent.model, ctx.modelRegistry);
201
+ if (!model) {
202
+ return {
203
+ agent: agentName,
204
+ agentSource: agent.source,
205
+ task,
206
+ exitCode: 1,
207
+ messages: [],
208
+ stderr: `Cannot resolve model "${agent.model ?? "(none specified)"}". Make sure the model is configured.`,
209
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
210
+ model: agent.model,
211
+ step,
212
+ errorMessage: `Model not found: ${agent.model ?? "unspecified"}`,
213
+ };
214
+ }
215
+ // Resolve auth for the model
216
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
217
+ if (!auth.ok || !auth.apiKey) {
218
+ return {
219
+ agent: agentName,
220
+ agentSource: agent.source,
221
+ task,
222
+ exitCode: 1,
223
+ messages: [],
224
+ stderr: `No API key configured for model "${model.provider}/${model.id}".`,
225
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
226
+ model: agent.model,
227
+ step,
228
+ errorMessage: `No API key for ${model.provider}/${model.id}`,
229
+ };
230
+ }
231
+ // Resolve tools
232
+ const effectiveCwd = cwd ?? defaultCwd;
233
+ const tools = resolveSubagentTools(agent.tools, effectiveCwd, pi, ctx);
185
234
  const currentResult = {
186
235
  agent: agentName,
187
236
  agentSource: agent.source,
@@ -190,7 +239,7 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
190
239
  messages: [],
191
240
  stderr: "",
192
241
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
193
- model: agent.model,
242
+ model: model.id,
194
243
  step,
195
244
  };
196
245
  const emitUpdate = () => {
@@ -202,113 +251,79 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
202
251
  }
203
252
  };
204
253
  try {
205
- if (agent.systemPrompt.trim()) {
206
- const tmp = await writePromptToTempFile(agent.name, agent.systemPrompt);
207
- tmpPromptDir = tmp.dir;
208
- tmpPromptPath = tmp.filePath;
209
- args.push("--append-system-prompt", tmpPromptPath);
210
- }
211
- args.push(`Task: ${task}`);
212
- let wasAborted = false;
213
- const exitCode = await new Promise((resolve) => {
214
- const invocation = getPiInvocation(args);
215
- const proc = spawn(invocation.command, invocation.args, {
216
- cwd: cwd ?? defaultCwd,
217
- shell: false,
218
- stdio: ["ignore", "pipe", "pipe"],
219
- });
220
- let buffer = "";
221
- const processLine = (line) => {
222
- if (!line.trim())
223
- return;
224
- let event;
225
- try {
226
- event = JSON.parse(line);
227
- }
228
- catch {
229
- return;
230
- }
231
- if (event.type === "message_end" && event.message) {
232
- const msg = event.message;
233
- currentResult.messages.push(msg);
234
- if (msg.role === "assistant") {
235
- currentResult.usage.turns++;
236
- const usage = msg.usage;
237
- if (usage) {
238
- currentResult.usage.input += usage.input || 0;
239
- currentResult.usage.output += usage.output || 0;
240
- currentResult.usage.cacheRead += usage.cacheRead || 0;
241
- currentResult.usage.cacheWrite += usage.cacheWrite || 0;
242
- currentResult.usage.cost += usage.cost?.total || 0;
243
- currentResult.usage.contextTokens = usage.totalTokens || 0;
244
- }
245
- if (!currentResult.model && msg.model)
246
- currentResult.model = msg.model;
247
- if (msg.stopReason)
248
- currentResult.stopReason = msg.stopReason;
249
- if (msg.errorMessage)
250
- currentResult.errorMessage = msg.errorMessage;
254
+ const config = {
255
+ model,
256
+ apiKey: auth.apiKey,
257
+ headers: auth.headers,
258
+ convertToLlm: (msgs) => msgs.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult"),
259
+ toolExecution: "parallel",
260
+ };
261
+ const stream = agentLoop([{ role: "user", content: [{ type: "text", text: task }], timestamp: Date.now() }], {
262
+ systemPrompt: combinedPrompt,
263
+ messages: [],
264
+ tools,
265
+ }, config, signal);
266
+ // Collect messages from the stream
267
+ const collectedMessages = [];
268
+ let hadError = false;
269
+ for await (const event of stream) {
270
+ if (signal?.aborted) {
271
+ hadError = true;
272
+ break;
273
+ }
274
+ if (event.type === "message_end" && event.message) {
275
+ const msg = event.message;
276
+ collectedMessages.push(msg);
277
+ if (msg.role === "assistant") {
278
+ currentResult.usage.turns++;
279
+ const usage = msg.usage;
280
+ if (usage) {
281
+ currentResult.usage.input += usage.input || 0;
282
+ currentResult.usage.output += usage.output || 0;
283
+ currentResult.usage.cacheRead += usage.cacheRead || 0;
284
+ currentResult.usage.cacheWrite += usage.cacheWrite || 0;
285
+ currentResult.usage.cost += usage.cost?.total || 0;
286
+ currentResult.usage.contextTokens = usage.totalTokens || 0;
287
+ }
288
+ if (!currentResult.model && msg.model)
289
+ currentResult.model = msg.model;
290
+ if (msg.stopReason)
291
+ currentResult.stopReason = msg.stopReason;
292
+ if (msg.errorMessage)
293
+ currentResult.errorMessage = msg.errorMessage;
294
+ if (msg.stopReason === "error" || msg.stopReason === "aborted") {
295
+ hadError = true;
251
296
  }
252
- emitUpdate();
253
- }
254
- if (event.type === "tool_result_end" && event.message) {
255
- currentResult.messages.push(event.message);
256
- emitUpdate();
257
297
  }
258
- };
259
- proc.stdout.on("data", (data) => {
260
- buffer += data.toString();
261
- const lines = buffer.split("\n");
262
- buffer = lines.pop() || "";
263
- for (const line of lines)
264
- processLine(line);
265
- });
266
- proc.stderr.on("data", (data) => {
267
- currentResult.stderr += data.toString();
268
- });
269
- proc.on("close", (code) => {
270
- if (buffer.trim())
271
- processLine(buffer);
272
- resolve(code ?? 0);
273
- });
274
- proc.on("error", () => {
275
- resolve(1);
276
- });
277
- if (signal) {
278
- const killProc = () => {
279
- wasAborted = true;
280
- proc.kill("SIGTERM");
281
- setTimeout(() => {
282
- if (!proc.killed)
283
- proc.kill("SIGKILL");
284
- }, 5000);
285
- };
286
- if (signal.aborted)
287
- killProc();
288
- else
289
- signal.addEventListener("abort", killProc, { once: true });
298
+ currentResult.messages = [...collectedMessages];
299
+ emitUpdate();
300
+ }
301
+ if (event.type === "tool_execution_end" && event.isError) {
302
+ emitUpdate();
290
303
  }
291
- });
292
- currentResult.exitCode = exitCode;
293
- if (wasAborted)
294
- throw new Error("Subagent was aborted");
304
+ }
305
+ // Drain the stream result (already consumed by iteration, but ensure cleanup)
306
+ try {
307
+ await stream.result();
308
+ }
309
+ catch {
310
+ // Stream result error — already handled by message events
311
+ }
312
+ currentResult.exitCode = hadError ? 1 : 0;
313
+ if (signal?.aborted) {
314
+ currentResult.exitCode = 1;
315
+ currentResult.stopReason = "aborted";
316
+ currentResult.errorMessage = "Subagent was aborted";
317
+ }
295
318
  return currentResult;
296
319
  }
297
- finally {
298
- if (tmpPromptPath)
299
- try {
300
- fs.unlinkSync(tmpPromptPath);
301
- }
302
- catch {
303
- /* ignore */
304
- }
305
- if (tmpPromptDir)
306
- try {
307
- fs.rmdirSync(tmpPromptDir);
308
- }
309
- catch {
310
- /* ignore */
311
- }
320
+ catch (err) {
321
+ const errorMsg = err instanceof Error ? err.message : String(err);
322
+ currentResult.exitCode = 1;
323
+ currentResult.stopReason = "error";
324
+ currentResult.errorMessage = errorMsg;
325
+ currentResult.stderr = errorMsg;
326
+ return currentResult;
312
327
  }
313
328
  }
314
329
  // ---------------------------------------------------------------------------
@@ -443,7 +458,7 @@ export default function subagentExtension(pi) {
443
458
  }
444
459
  }
445
460
  : undefined;
446
- const result = await runSingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, signal, chainUpdate, makeDetails("chain"));
461
+ const result = await runSingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, signal, chainUpdate, makeDetails("chain"), pi, ctx);
447
462
  results.push(result);
448
463
  const isError = result.exitCode !== 0 ||
449
464
  result.stopReason === "error" ||
@@ -521,7 +536,7 @@ export default function subagentExtension(pi) {
521
536
  allResults[index] = partial.details.results[0];
522
537
  emitParallelUpdate();
523
538
  }
524
- }, makeDetails("parallel"));
539
+ }, makeDetails("parallel"), pi, ctx);
525
540
  allResults[index] = result;
526
541
  emitParallelUpdate();
527
542
  return result;
@@ -544,7 +559,7 @@ export default function subagentExtension(pi) {
544
559
  }
545
560
  // ── Single mode ─────────────────────────────────────────────
546
561
  if (params.agent && params.task) {
547
- const result = await runSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, signal, onUpdate, makeDetails("single"));
562
+ const result = await runSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, signal, onUpdate, makeDetails("single"), pi, ctx);
548
563
  const isError = result.exitCode !== 0 ||
549
564
  result.stopReason === "error" ||
550
565
  result.stopReason === "aborted";
@@ -181,9 +181,6 @@ export async function runServe(opts = {}) {
181
181
  backendUrl,
182
182
  machineJwt: registration.record.machineJwt,
183
183
  bridgeFactory: opts.bridgeFactory,
184
- titleLlmCall: opts.titleLlmCall,
185
- disableAutoTitle: opts.disableAutoTitle,
186
- publishMetaEvent: deferredPublishMetaEvent,
187
184
  });
188
185
  if (!silent) {
189
186
  const action = registration.reused ? "Reusing" : "Registered";
@@ -25,7 +25,7 @@ Typography is unified under NouvelR — a proprietary geometric sans-serif desig
25
25
  ## 2. Color Palette & Roles
26
26
 
27
27
  ### Primary
28
- - **Renault Yellow** (`#EFDF00`): The brand's signature Pantone — a vivid, saturated yellow used for super-primary CTAs and the highest-priority action buttons. Appears as `--CtaLink-background-color` on `.is-cta-super-primary` class. Carries the energy of the diamond logo
28
+ - **Renault Yellow** (`#EFDF00`): The brand's signature Pantone — a vivid, saturated yellow used for super-primary CTAs and the highest-priority action buttons. Appears as `--CtaButton-background-color` on `.is-cta-super-primary` class. Carries the energy of the diamond logo
29
29
  - **Absolute Black** (`#000000`): Primary button background, heading text on light surfaces, and the dominant dark section surface. The structural anchor of the entire interface
30
30
  - **Pure White** (`#FFFFFF`): Primary surface for editorial content, inverted button backgrounds, hero text color, and the dominant light-mode canvas (--rt-color-white)
31
31