@wrongstack/runtime 0.277.2 → 0.280.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.
package/dist/index.js CHANGED
@@ -1,124 +1,185 @@
1
+ import { buildChildEnv, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, buildRecoveryStrategies, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultPromptLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, createStrategyCompactor, EventBus, resolveModelMatrix, resolveModelTargetFromEntry, ToolRegistry, Context, createDefaultPipelines, applyModelRuntime, mergeModelRuntime, WIDE_SUBAGENT_CAPABILITIES, AutoApprovePermissionPolicy, ToolExecutor, Agent, createFallbackModelExtension, assertNotPrivateHost } from '@wrongstack/core';
2
+ export { DefaultSystemPromptBuilder } from '@wrongstack/core';
1
3
  export * from '@wrongstack/core/defaults';
2
4
  export { DefaultPathResolver, DefaultTokenCounter } from '@wrongstack/core/infrastructure';
3
- import { Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, buildRecoveryStrategies, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultPromptLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, createStrategyCompactor, assertNotPrivateHost, buildChildEnv, EventBus, resolveModelMatrix, resolveModelTargetFromEntry, ToolRegistry, Context, WIDE_SUBAGENT_CAPABILITIES, AutoApprovePermissionPolicy, ToolExecutor, Agent, createDefaultPipelines, createFallbackModelExtension } from '@wrongstack/core';
4
- export { DefaultSystemPromptBuilder } from '@wrongstack/core';
5
- import * as fs2 from 'fs/promises';
6
- import * as os2 from 'os';
7
- import * as path from 'path';
8
5
  import { spawn } from 'child_process';
9
6
  import { randomUUID } from 'crypto';
7
+ import * as fs from 'fs/promises';
8
+ import * as os from 'os';
9
+ import * as path from 'path';
10
10
 
11
11
  // src/index.ts
12
-
13
- // src/host.ts
14
- function createRuntimeHostFromParts(parts) {
15
- return {
16
- agent: parts.agent,
17
- context: parts.context,
18
- events: parts.events,
19
- tools: parts.tools,
20
- providers: parts.providers,
21
- slashCommands: parts.slashCommands,
22
- session: parts.session,
23
- extensions: parts.extensions,
24
- async shutdown() {
25
- await parts.shutdown?.();
26
- }
27
- };
12
+ var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
13
+ async function readClipboardImage() {
14
+ const platform = process.platform;
15
+ if (platform === "win32") return readWindows();
16
+ if (platform === "darwin") return readDarwin();
17
+ if (platform === "linux") return readLinux();
18
+ return null;
28
19
  }
29
- async function applyWrongStackPack(host, pack, opts = {}) {
30
- const owner = opts.owner ?? pack.name;
31
- const unregisterExtensions = [];
32
- const registeredToolNames = [];
33
- const registeredCommandNames = [];
34
- const registeredProviderTypes = [];
35
- if (pack.tools) {
36
- const tools = [...pack.tools];
37
- host.tools.registerAllOrThrow(tools, owner);
38
- for (const t of tools) registeredToolNames.push(t.name);
39
- }
40
- if (pack.providers) {
41
- const providers = [...pack.providers];
42
- host.providers.registerAll(providers);
43
- for (const p of providers) registeredProviderTypes.push(p.type);
20
+ async function readClipboardText() {
21
+ const platform = process.platform;
22
+ if (platform === "win32") {
23
+ const ps = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw";
24
+ const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
25
+ if (out == null) return null;
26
+ const text = out.replace(/\r?\n$/, "");
27
+ return text.length > 0 ? text : null;
44
28
  }
45
- if (pack.slashCommands) {
46
- const cmds = [...pack.slashCommands];
47
- host.slashCommands.registerAll(cmds, owner);
48
- for (const c of cmds) registeredCommandNames.push(c.name);
29
+ if (platform === "darwin") {
30
+ const out = await runCmd("pbpaste", []);
31
+ return out && out.length > 0 ? out : null;
49
32
  }
50
- if (pack.extensions && host.extensions) {
51
- for (const ext of pack.extensions) {
52
- unregisterExtensions.push(host.extensions.register(ext));
33
+ if (platform === "linux") {
34
+ const tries = [
35
+ ["wl-paste", ["--no-newline"]],
36
+ ["xclip", ["-selection", "clipboard", "-o"]]
37
+ ];
38
+ for (const [cmd, args] of tries) {
39
+ const out = await runCmd(cmd, args);
40
+ if (out && out.length > 0) return out;
53
41
  }
42
+ return null;
54
43
  }
55
- if (pack.setup) {
56
- if (!opts.api) {
57
- throw new Error(`Pack "${pack.name}" defines setup() but no PluginAPI was provided`);
58
- }
59
- try {
60
- await pack.setup(opts.api);
61
- } catch (setupErr) {
62
- for (const unregister of unregisterExtensions.reverse()) {
63
- unregister();
64
- }
65
- for (const name of registeredCommandNames.reverse()) {
66
- host.slashCommands.unregister(name);
67
- }
68
- for (const name of registeredToolNames.reverse()) {
69
- host.tools.unregister(name);
70
- }
71
- for (const type of registeredProviderTypes.reverse()) {
72
- host.providers.unregister(type);
73
- }
74
- throw setupErr;
75
- }
44
+ return null;
45
+ }
46
+ async function readWindows() {
47
+ const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);
48
+ const ps = [
49
+ "Add-Type -AssemblyName System.Windows.Forms",
50
+ "Add-Type -AssemblyName System.Drawing",
51
+ "$img = [System.Windows.Forms.Clipboard]::GetImage()",
52
+ 'if ($img -eq $null) { Write-Output "NO_IMAGE"; exit 0 }',
53
+ `$img.Save('${tmp.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)`,
54
+ 'Write-Output "OK"'
55
+ ].join("; ");
56
+ const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
57
+ if (!out || out.trim() === "NO_IMAGE") return null;
58
+ if (!out.includes("OK")) return null;
59
+ return readPngFile(tmp);
60
+ }
61
+ async function readDarwin() {
62
+ const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);
63
+ const script = [
64
+ "try",
65
+ ` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
66
+ " write (the clipboard as \xABclass PNGf\xBB) to the_file",
67
+ " close access the_file",
68
+ "on error",
69
+ " try",
70
+ ' close access POSIX file "' + tmp + '"',
71
+ " end try",
72
+ ' return "NO_IMAGE"',
73
+ "end try",
74
+ 'return "OK"'
75
+ ].join("\n");
76
+ const out = await runCmd("osascript", ["-e", script]);
77
+ if (out?.trim() !== "OK") return null;
78
+ return readPngFile(tmp);
79
+ }
80
+ async function readLinux() {
81
+ const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);
82
+ const tries = [
83
+ ["wl-paste", ["--type", "image/png"]],
84
+ ["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
85
+ ];
86
+ for (const [cmd, args] of tries) {
87
+ const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
88
+ if (ok) return readPngFile(tmp);
76
89
  }
77
- return {
78
- pack,
79
- owner,
80
- async teardown() {
81
- for (const unregister of unregisterExtensions.reverse()) {
82
- unregister();
83
- }
84
- for (const name of registeredCommandNames.reverse()) {
85
- host.slashCommands.unregister(name);
86
- }
87
- for (const name of registeredToolNames.reverse()) {
88
- host.tools.unregister(name);
89
- }
90
- for (const type of registeredProviderTypes.reverse()) {
91
- host.providers.unregister(type);
92
- }
93
- if (pack.teardown) {
94
- if (!opts.api) {
95
- throw new Error(`Pack "${pack.name}" defines teardown() but no PluginAPI was provided`);
96
- }
97
- await pack.teardown(opts.api);
98
- }
99
- }
100
- };
90
+ return null;
101
91
  }
102
- async function applyWrongStackPacks(host, packs, opts = {}) {
103
- const applied = [];
92
+ async function readPngFile(p) {
104
93
  try {
105
- for (const pack of packs) {
106
- applied.push(await applyWrongStackPack(host, pack, opts));
94
+ const buf = await fs.readFile(p);
95
+ if (buf.length === 0) {
96
+ await fs.unlink(p).catch(() => void 0);
97
+ return null;
107
98
  }
108
- return applied;
109
- } catch (err) {
110
- for (const mounted of applied.reverse()) {
111
- await mounted.teardown().catch((teardownErr) => {
112
- const detail = teardownErr instanceof Error ? teardownErr.message : String(teardownErr);
113
- process.emitWarning(
114
- `Pack teardown during error rollback failed: ${detail}`,
115
- "PackRollbackWarning"
116
- );
117
- });
99
+ if (buf.length > MAX_IMAGE_BYTES) {
100
+ await fs.unlink(p).catch(() => void 0);
101
+ throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
102
+ }
103
+ if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
104
+ await fs.unlink(p).catch(() => void 0);
105
+ return null;
118
106
  }
107
+ await fs.unlink(p).catch(() => void 0);
108
+ return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
109
+ } catch (err) {
110
+ if (err.code === "ENOENT") return null;
119
111
  throw err;
120
112
  }
121
113
  }
114
+ var CLIPBOARD_CMD_TIMEOUT_MS = 5e3;
115
+ function runCmd(cmd, args) {
116
+ return new Promise((resolve) => {
117
+ const child = spawn(cmd, args, {
118
+ env: buildChildEnv(),
119
+ stdio: ["ignore", "pipe", "pipe"],
120
+ windowsHide: true
121
+ });
122
+ let out = "";
123
+ let settled = false;
124
+ let killedByTimeout = false;
125
+ const finish = (value) => {
126
+ if (settled) return;
127
+ settled = true;
128
+ clearTimeout(timer);
129
+ clearTimeout(killCap);
130
+ resolve(value);
131
+ };
132
+ const timer = setTimeout(() => {
133
+ killedByTimeout = true;
134
+ child.kill("SIGTERM");
135
+ }, CLIPBOARD_CMD_TIMEOUT_MS);
136
+ const killCap = setTimeout(() => finish(null), CLIPBOARD_CMD_TIMEOUT_MS + 2e3);
137
+ child.stdout.on("data", (c) => {
138
+ out += String(c);
139
+ });
140
+ child.on("error", () => finish(null));
141
+ child.on("exit", (code) => {
142
+ if (killedByTimeout) return finish(null);
143
+ finish(code === 0 ? out : null);
144
+ });
145
+ });
146
+ }
147
+ function runCmdToFile(cmd, args, outPath) {
148
+ return new Promise((resolve) => {
149
+ const child = spawn(cmd, args, {
150
+ env: buildChildEnv(),
151
+ stdio: ["ignore", "pipe", "pipe"],
152
+ windowsHide: true
153
+ });
154
+ const chunks = [];
155
+ let settled = false;
156
+ let killedByTimeout = false;
157
+ const finish = (value) => {
158
+ if (settled) return;
159
+ settled = true;
160
+ clearTimeout(timer);
161
+ clearTimeout(killCap);
162
+ resolve(value);
163
+ };
164
+ const timer = setTimeout(() => {
165
+ killedByTimeout = true;
166
+ child.kill("SIGTERM");
167
+ }, CLIPBOARD_CMD_TIMEOUT_MS);
168
+ const killCap = setTimeout(() => finish(false), CLIPBOARD_CMD_TIMEOUT_MS + 2e3);
169
+ child.stdout.on("data", (c) => chunks.push(c));
170
+ child.on("error", () => finish(false));
171
+ child.on("exit", async (code) => {
172
+ if (killedByTimeout) return finish(false);
173
+ if (code !== 0 || chunks.length === 0) return finish(false);
174
+ try {
175
+ await fs.writeFile(outPath, Buffer.concat(chunks));
176
+ finish(true);
177
+ } catch {
178
+ finish(false);
179
+ }
180
+ });
181
+ });
182
+ }
122
183
  function createDefaultContainer(opts) {
123
184
  const { config, wpaths, logger, modelsRegistry } = opts;
124
185
  const container = new Container();
@@ -140,7 +201,11 @@ function createDefaultContainer(opts) {
140
201
  container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
141
202
  container.bind(
142
203
  TOKENS.TokenCounter,
143
- () => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider, events: opts.events })
204
+ () => new DefaultTokenCounter({
205
+ registry: modelsRegistry,
206
+ providerId: config.provider,
207
+ events: opts.events
208
+ })
144
209
  );
145
210
  const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
146
211
  container.bind(TOKENS.ModeStore, () => modeStore);
@@ -155,9 +220,18 @@ function createDefaultContainer(opts) {
155
220
  );
156
221
  const memoryStore = new DefaultMemoryStore({ paths: wpaths, events: opts.events });
157
222
  container.bind(TOKENS.MemoryStore, () => memoryStore);
158
- const skillLoader = new DefaultSkillLoader({ paths: wpaths, bundledDir: opts.bundledSkillsDir });
223
+ const skillLoader = new DefaultSkillLoader({
224
+ paths: wpaths,
225
+ bundledDir: opts.bundledSkillsDir,
226
+ readClaudeSkills: config.skills?.readClaudeSkills,
227
+ foreignSources: config.skills?.foreignSources,
228
+ extraDirs: config.skills?.extraDirs
229
+ });
159
230
  container.bind(TOKENS.SkillLoader, () => skillLoader);
160
- const promptLoader = new DefaultPromptLoader({ paths: wpaths, bundledDir: opts.bundledPromptsDir });
231
+ const promptLoader = new DefaultPromptLoader({
232
+ paths: wpaths,
233
+ bundledDir: opts.bundledPromptsDir
234
+ });
161
235
  container.bind(TOKENS.PromptLoader, () => promptLoader);
162
236
  if (opts.systemPrompt) {
163
237
  container.bind(
@@ -167,25 +241,24 @@ function createDefaultContainer(opts) {
167
241
  globalDir: wpaths.globalInstructions,
168
242
  projectDir: wpaths.inProjectInstructions
169
243
  },
244
+ skillMode: config.skills?.mode,
245
+ skillEagerMaxChars: config.skills?.eagerMaxChars,
170
246
  ...opts.systemPrompt
171
247
  })
172
248
  );
173
249
  }
174
- container.bind(
175
- TOKENS.PermissionPolicy,
176
- () => {
177
- const policyOptions = {
178
- trustFile: wpaths.projectTrust,
179
- yolo: opts.permission?.yolo ?? false,
180
- yoloDestructive: opts.permission?.yoloDestructive ?? opts.permission?.forceAllYolo ?? false,
181
- confirmDestructive: opts.permission?.confirmDestructive ?? false
182
- };
183
- if (opts.permission?.promptDelegate !== void 0) {
184
- policyOptions.promptDelegate = opts.permission.promptDelegate;
185
- }
186
- return new DefaultPermissionPolicy(policyOptions);
250
+ container.bind(TOKENS.PermissionPolicy, () => {
251
+ const policyOptions = {
252
+ trustFile: wpaths.projectTrust,
253
+ yolo: opts.permission?.yolo ?? false,
254
+ yoloDestructive: opts.permission?.yoloDestructive ?? opts.permission?.forceAllYolo ?? false,
255
+ confirmDestructive: opts.permission?.confirmDestructive ?? false
256
+ };
257
+ if (opts.permission?.promptDelegate !== void 0) {
258
+ policyOptions.promptDelegate = opts.permission.promptDelegate;
187
259
  }
188
- );
260
+ return new DefaultPermissionPolicy(policyOptions);
261
+ });
189
262
  container.bind(
190
263
  TOKENS.Compactor,
191
264
  () => (
@@ -209,196 +282,594 @@ function createDefaultContainer(opts) {
209
282
  );
210
283
  return container;
211
284
  }
212
- var ImageInputUnsupportedError = class extends Error {
213
- constructor(opts) {
214
- const target = [opts.providerId, opts.model].filter(Boolean).join("/") || "current model";
215
- super(
216
- `${target} does not support image input, and no image-understanding adapter is available for ${opts.imageCount} image${opts.imageCount === 1 ? "" : "s"}. Switch to a vision model or enable an MCP/tool adapter that can describe images.`
217
- );
218
- this.name = "ImageInputUnsupportedError";
219
- }
220
- };
221
- var VisionUrlBlockedError = class extends Error {
222
- url;
223
- constructor(opts) {
224
- super(`vision: blocked unsafe image URL "${opts.url}" (${opts.reason})`);
225
- this.name = "VisionUrlBlockedError";
226
- this.url = opts.url;
227
- }
228
- };
229
- async function routeImagesForModel(blocks, opts) {
230
- const images = blocks.filter((b) => b.type === "image");
231
- if (images.length === 0) {
232
- return { blocks, route: "none", convertedImages: 0 };
233
- }
234
- if (opts.supportsVision) {
235
- return { blocks, route: "native", convertedImages: 0 };
236
- }
237
- const adapters = await resolveAdapters(opts.adapters);
238
- if (adapters.length === 0) {
239
- throw new ImageInputUnsupportedError({
240
- providerId: opts.providerId,
241
- model: opts.model,
242
- imageCount: images.length
243
- });
244
- }
245
- const out = [];
246
- let convertedImages = 0;
247
- let lastErr;
248
- let adapterName;
249
- for (const block of blocks) {
250
- if (block.type !== "image") {
251
- out.push(block);
252
- continue;
285
+ var SUBAGENT_BASELINE = "You are a subagent executing one delegated task end-to-end. Work autonomously with your tools; do not ask for confirmation on routine in-project actions. Keep output concise.";
286
+ var _SUBAGENT_ABORT = "wrongstack:subagent-abort-controller";
287
+ function makeLightSubagentFactory(deps) {
288
+ const configStore = deps.container.resolve(TOKENS.ConfigStore);
289
+ const tokenCounter = deps.container.resolve(TOKENS.TokenCounter);
290
+ const secretScrubber = deps.container.resolve(TOKENS.SecretScrubber);
291
+ const systemPromptBuilder = deps.container.resolve(TOKENS.SystemPromptBuilder);
292
+ return async (subCfg) => {
293
+ const events = new EventBus();
294
+ const config = configStore.get();
295
+ const modelsRegistry = deps.modelsRegistry ?? deps.container.safeResolve(TOKENS.ModelsRegistry);
296
+ const matrixEntry = subCfg.model ? void 0 : resolveModelMatrix(config.modelMatrix, subCfg.role);
297
+ const matrixTarget = resolveModelTargetFromEntry(config, matrixEntry);
298
+ const effProvider = subCfg.provider ?? matrixTarget?.provider ?? config.provider;
299
+ const effModel = subCfg.model ?? matrixTarget?.model ?? config.model;
300
+ const provider = buildProvider(deps.providerRegistry, config, effProvider, effModel);
301
+ let subReasoningConfig = await resolveReasoningConfig(modelsRegistry, effProvider, effModel);
302
+ const runtimeOverride = subCfg.modelRuntime ?? matrixTarget?.modelRuntime;
303
+ const subCwd = subCfg.cwd ?? deps.cwd ?? deps.projectRoot;
304
+ const allowed = filterToolList(deps.toolRegistry, subCfg.tools);
305
+ const subRegistry = new ToolRegistry();
306
+ for (const t of allowed) subRegistry.register(t);
307
+ const baseSystem = await systemPromptBuilder.build({
308
+ cwd: subCwd,
309
+ projectRoot: deps.projectRoot,
310
+ tools: allowed,
311
+ model: effModel,
312
+ provider: effProvider,
313
+ subagent: true
314
+ });
315
+ baseSystem.unshift({ type: "text", text: SUBAGENT_BASELINE });
316
+ if (subCfg.systemPromptOverride) {
317
+ baseSystem.push({ type: "text", text: subCfg.systemPromptOverride });
253
318
  }
254
- let description;
255
- for (const adapter of adapters) {
256
- try {
257
- description = await adapter.describe({
258
- image: block,
259
- prompt: opts.prompt,
260
- ctx: opts.ctx,
261
- signal: opts.signal
319
+ const agentName = subCfg.name ?? subCfg.id ?? `sub_${cryptoId()}`;
320
+ const session = makeSubagentSessionShim(deps.session);
321
+ const ac = new AbortController();
322
+ const ctx = new Context({
323
+ systemPrompt: baseSystem,
324
+ provider,
325
+ session,
326
+ signal: ac.signal,
327
+ tokenCounter,
328
+ cwd: subCwd,
329
+ projectRoot: deps.projectRoot,
330
+ allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
331
+ model: effModel,
332
+ tools: allowed,
333
+ agentId: agentName,
334
+ agentName,
335
+ traceId: deps.session.traceId
336
+ });
337
+ if (subCfg.role) ctx.meta["agentRole"] = subCfg.role;
338
+ ctx.meta[_SUBAGENT_ABORT] = ac;
339
+ const pipelines = createDefaultPipelines();
340
+ pipelines.request.use({
341
+ name: "ModelRuntimeSettings",
342
+ async handler(req) {
343
+ return applyModelRuntime(req, {
344
+ getSettings: () => mergeModelRuntime(configStore.get().modelRuntime, runtimeOverride),
345
+ getReasoningConfig: () => subReasoningConfig,
346
+ getCapabilities: () => ctx.provider.capabilities
262
347
  });
263
- adapterName = adapter.name;
264
- break;
265
- } catch (err) {
266
- lastErr = err;
267
348
  }
268
- }
269
- if (!description?.trim()) {
270
- throw new Error(
271
- `No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ""}`
272
- );
273
- }
274
- convertedImages++;
275
- out.push({
276
- type: "text",
277
- text: `[Image ${convertedImages} analyzed via ${adapterName ?? "vision adapter"}]
278
- ${description.trim()}`
279
349
  });
350
+ const caps = subCfg.allowedCapabilities ?? WIDE_SUBAGENT_CAPABILITIES;
351
+ const permissionPolicy = new AutoApprovePermissionPolicy(caps);
352
+ const toolExecutor = new ToolExecutor(subRegistry, {
353
+ permissionPolicy,
354
+ secretScrubber,
355
+ events,
356
+ confirmAwaiter: void 0,
357
+ iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
358
+ perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
359
+ tracer: void 0
360
+ });
361
+ const agent = new Agent({
362
+ container: deps.container,
363
+ tools: subRegistry,
364
+ providers: deps.providerRegistry,
365
+ events,
366
+ pipelines,
367
+ context: ctx,
368
+ permissionPolicy,
369
+ toolExecutor
370
+ });
371
+ const subFallbacks = subCfg.fallbackModels;
372
+ const matrixFallbacks = matrixTarget?.fallbackModels;
373
+ agent.extensions.register(
374
+ createFallbackModelExtension({
375
+ getConfig: () => {
376
+ const live = configStore.get();
377
+ if (subFallbacks?.length) return { ...live, fallbackModels: subFallbacks };
378
+ if (matrixFallbacks?.length) return { ...live, fallbackModels: matrixFallbacks };
379
+ return live;
380
+ },
381
+ buildProvider: (id, model) => buildProvider(deps.providerRegistry, configStore.get(), id, model ?? effModel),
382
+ onModelSwitch: async (id, model) => {
383
+ subReasoningConfig = await resolveReasoningConfig(modelsRegistry, id, model);
384
+ },
385
+ events
386
+ })
387
+ );
388
+ return { agent, events };
389
+ };
390
+ }
391
+ function filterToolList(registry, allow) {
392
+ const all = registry.list();
393
+ if (!allow || allow.length === 0) return all;
394
+ const allowSet = new Set(allow);
395
+ return all.filter((t) => allowSet.has(t.name));
396
+ }
397
+ function buildProvider(registry, config, providerId, model) {
398
+ const providerConfig = config.providers?.[providerId] ?? {
399
+ type: providerId,
400
+ apiKey: config.apiKey,
401
+ baseUrl: config.baseUrl
402
+ };
403
+ if (!registry.has(providerId)) {
404
+ throw new Error(
405
+ `No provider factory registered for "${providerId}" \u2014 cannot build a subagent provider for the SDD run.`
406
+ );
280
407
  }
281
- return { blocks: out, route: "adapter", convertedImages, adapterName };
408
+ return registry.create({ ...providerConfig, type: providerId, model });
282
409
  }
283
- async function resolveAdapters(adapters) {
284
- if (!adapters) return [];
285
- return typeof adapters === "function" ? await adapters() : adapters;
410
+ async function resolveReasoningConfig(registry, providerId, modelId) {
411
+ if (!registry) return void 0;
412
+ try {
413
+ return (await registry.getModel(providerId, modelId))?.capabilities.reasoningConfig;
414
+ } catch {
415
+ return void 0;
416
+ }
286
417
  }
287
- function createToolVisionAdapters(registry, opts = {}) {
288
- return registry.list().filter(isLikelyVisionTool).map((tool) => ({
289
- name: tool.name,
290
- async describe(input) {
291
- const currentTool = registry.get(tool.name);
292
- if (!currentTool) {
293
- throw new Error(`Tool "${tool.name}" is no longer registered`);
294
- }
295
- const built = await buildToolPayload(currentTool, input.image, input.prompt ?? opts.prompt);
296
- if (!built) {
297
- throw new Error(`Tool "${currentTool.name}" does not expose a supported image input schema`);
298
- }
299
- try {
300
- const result = await currentTool.execute(built.payload, input.ctx, {
301
- signal: input.signal
302
- });
303
- return stringifyToolResult(result);
304
- } finally {
305
- await built.cleanup?.();
306
- }
418
+ function makeSubagentSessionShim(parent) {
419
+ return {
420
+ id: parent.id,
421
+ transcriptPath: parent.transcriptPath,
422
+ get traceId() {
423
+ return parent.traceId;
424
+ },
425
+ set traceId(value) {
426
+ parent.traceId = value;
427
+ },
428
+ get pendingToolUses() {
429
+ return [];
430
+ },
431
+ append: (ev) => parent.append({ ...ev }),
432
+ appendBatch: (evs) => parent.appendBatch(evs.map((e) => ({ ...e }))),
433
+ flush: () => parent.flush(),
434
+ close: async () => {
435
+ },
436
+ recordFileChange: () => {
437
+ },
438
+ recordSideEffect: () => {
439
+ },
440
+ writeCheckpoint: async () => {
441
+ },
442
+ writeFileSnapshot: async () => {
443
+ },
444
+ truncateToCheckpoint: async () => 0,
445
+ clearSession: async () => {
446
+ },
447
+ writeInFlightMarker: async () => {
448
+ },
449
+ clearInFlightMarker: async () => {
307
450
  }
308
- }));
451
+ };
309
452
  }
310
- function isLikelyVisionTool(tool) {
311
- if (tool.permission !== "auto" || tool.mutating) return false;
312
- const haystack = `${tool.name} ${tool.description ?? ""} ${tool.usageHint ?? ""}`.toLowerCase();
313
- if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;
314
- if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;
315
- const props = schemaProperties(tool);
316
- return [
317
- "image",
318
- "base64",
319
- "data",
320
- "url",
321
- "image_url",
322
- "imageUrl",
323
- "path",
324
- "image_path",
325
- "imagePath",
326
- "image_url",
327
- "imageUrl",
328
- "file_path",
329
- "filePath",
330
- "filename",
331
- "file",
332
- "mediaType",
333
- "mimeType"
334
- ].some((key) => key in props);
453
+ function cryptoId() {
454
+ return randomUUID().slice(0, 8);
335
455
  }
336
- async function buildToolPayload(tool, image, prompt = "Describe this image for a coding agent. Include visible text, UI state, errors, layout, and any details needed to answer the user.") {
337
- const props = schemaProperties(tool);
338
- const payload = {};
339
- const mediaType = image.source.media_type ?? "image/png";
340
- const data = image.source.data;
341
- const url = image.source.url;
342
- let cleanup;
343
- if (image.source.type === "url" && url) {
344
- try {
345
- await assertNotPrivateHost(new URL(url).hostname);
346
- } catch (err) {
347
- const reason = err instanceof Error && err.message.startsWith("fetch:") ? err.message.slice("fetch:".length).trim() : "unresolvable host";
348
- throw new VisionUrlBlockedError({ url, reason });
456
+
457
+ // src/host.ts
458
+ function createRuntimeHostFromParts(parts) {
459
+ return {
460
+ agent: parts.agent,
461
+ context: parts.context,
462
+ events: parts.events,
463
+ tools: parts.tools,
464
+ providers: parts.providers,
465
+ slashCommands: parts.slashCommands,
466
+ session: parts.session,
467
+ extensions: parts.extensions,
468
+ async shutdown() {
469
+ await parts.shutdown?.();
349
470
  }
471
+ };
472
+ }
473
+ async function applyWrongStackPack(host, pack, opts = {}) {
474
+ const owner = opts.owner ?? pack.name;
475
+ const unregisterExtensions = [];
476
+ const registeredToolNames = [];
477
+ const registeredCommandNames = [];
478
+ const registeredProviderTypes = [];
479
+ if (pack.tools) {
480
+ const tools = [...pack.tools];
481
+ host.tools.registerAllOrThrow(tools, owner);
482
+ for (const t of tools) registeredToolNames.push(t.name);
350
483
  }
351
- const pathKey = firstPresent(props, [
352
- "path",
353
- "image_path",
354
- "imagePath",
355
- "image_url",
356
- "imageUrl",
357
- "file_path",
358
- "filePath",
359
- "filename",
360
- "file"
361
- ]);
362
- if (pathKey && image.source.type === "base64" && data) {
363
- const p = await writeTempImage(data, mediaType);
364
- payload[pathKey] = p;
365
- cleanup = async () => {
366
- await fs2.unlink(p).catch(() => void 0);
367
- await fs2.rmdir(path.dirname(p)).catch(() => void 0);
368
- };
369
- } else if ("image" in props) {
370
- payload.image = image.source.type === "base64" ? { type: "base64", mediaType, media_type: mediaType, data } : { type: "url", url };
371
- } else if (image.source.type === "base64" && "base64" in props) {
372
- payload.base64 = data;
373
- } else if (image.source.type === "base64" && "data" in props) {
374
- payload.data = data;
375
- } else if (image.source.type === "url" && "url" in props) {
376
- payload.url = url;
377
- } else if (image.source.type === "url" && "image_url" in props) {
378
- payload.image_url = url;
379
- } else if (image.source.type === "url" && "imageUrl" in props) {
380
- payload.imageUrl = url;
381
- } else {
382
- return null;
484
+ if (pack.providers) {
485
+ const providers = [...pack.providers];
486
+ host.providers.registerAll(providers);
487
+ for (const p of providers) registeredProviderTypes.push(p.type);
383
488
  }
384
- if ("mediaType" in props) payload.mediaType = mediaType;
385
- if ("mimeType" in props) payload.mimeType = mediaType;
386
- if ("media_type" in props) payload.media_type = mediaType;
387
- if ("prompt" in props) payload.prompt = prompt;
388
- if ("query" in props) payload.query = prompt;
389
- if ("instruction" in props) payload.instruction = prompt;
390
- const built = { payload };
391
- if (cleanup !== void 0) built.cleanup = cleanup;
392
- return built;
489
+ if (pack.slashCommands) {
490
+ const cmds = [...pack.slashCommands];
491
+ host.slashCommands.registerAll(cmds, owner);
492
+ for (const c of cmds)
493
+ registeredCommandNames.push(owner === "core" ? c.name : `${owner}:${c.name}`);
494
+ }
495
+ if (pack.extensions && host.extensions) {
496
+ for (const ext of pack.extensions) {
497
+ unregisterExtensions.push(host.extensions.register(ext));
498
+ }
499
+ }
500
+ if (pack.setup) {
501
+ if (!opts.api) {
502
+ throw new Error(`Pack "${pack.name}" defines setup() but no PluginAPI was provided`);
503
+ }
504
+ try {
505
+ await pack.setup(opts.api);
506
+ } catch (setupErr) {
507
+ for (let i = unregisterExtensions.length - 1; i >= 0; i--) {
508
+ unregisterExtensions[i]();
509
+ }
510
+ for (let i = registeredCommandNames.length - 1; i >= 0; i--) {
511
+ host.slashCommands.unregister(registeredCommandNames[i]);
512
+ }
513
+ for (let i = registeredToolNames.length - 1; i >= 0; i--) {
514
+ host.tools.unregister(registeredToolNames[i]);
515
+ }
516
+ for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {
517
+ host.providers.unregister(registeredProviderTypes[i]);
518
+ }
519
+ throw setupErr;
520
+ }
521
+ }
522
+ return {
523
+ pack,
524
+ owner,
525
+ async teardown() {
526
+ for (let i = unregisterExtensions.length - 1; i >= 0; i--) {
527
+ unregisterExtensions[i]();
528
+ }
529
+ for (let i = registeredCommandNames.length - 1; i >= 0; i--) {
530
+ host.slashCommands.unregister(registeredCommandNames[i]);
531
+ }
532
+ for (let i = registeredToolNames.length - 1; i >= 0; i--) {
533
+ host.tools.unregister(registeredToolNames[i]);
534
+ }
535
+ for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {
536
+ host.providers.unregister(registeredProviderTypes[i]);
537
+ }
538
+ if (pack.teardown) {
539
+ if (!opts.api) {
540
+ throw new Error(`Pack "${pack.name}" defines teardown() but no PluginAPI was provided`);
541
+ }
542
+ await pack.teardown(opts.api);
543
+ }
544
+ }
545
+ };
546
+ }
547
+ async function applyWrongStackPacks(host, packs, opts = {}) {
548
+ const applied = [];
549
+ try {
550
+ for (const pack of packs) {
551
+ applied.push(await applyWrongStackPack(host, pack, opts));
552
+ }
553
+ return applied;
554
+ } catch (err) {
555
+ for (let i = applied.length - 1; i >= 0; i--) {
556
+ const mounted = applied[i];
557
+ await mounted.teardown().catch((teardownErr) => {
558
+ const detail = teardownErr instanceof Error ? teardownErr.message : String(teardownErr);
559
+ process.emitWarning(
560
+ `Pack teardown during error rollback failed: ${detail}`,
561
+ "PackRollbackWarning"
562
+ );
563
+ });
564
+ }
565
+ throw err;
566
+ }
567
+ }
568
+
569
+ // src/local-llm-probe.ts
570
+ var PROBE_TIMEOUT_MS = 3e3;
571
+ async function probeLocalLlm(opts) {
572
+ const { baseUrl, apiKey, noAuth, scrubber, fetchImpl, timeoutMs } = opts;
573
+ const fetchFn = fetchImpl ?? fetch;
574
+ const timeout = timeoutMs ?? PROBE_TIMEOUT_MS;
575
+ const base = baseUrl.replace(/\/+$/, "");
576
+ const url = /\/models$/.test(base) ? base : `${base}/models`;
577
+ const headers = { accept: "application/json" };
578
+ if (!noAuth && apiKey) {
579
+ headers["authorization"] = `Bearer ${apiKey}`;
580
+ }
581
+ const started = Date.now();
582
+ let res;
583
+ try {
584
+ res = await fetchFn(url, {
585
+ method: "GET",
586
+ headers,
587
+ signal: AbortSignal.timeout(timeout)
588
+ });
589
+ } catch (err) {
590
+ const elapsed2 = Date.now() - started;
591
+ const name = err?.name;
592
+ if (name === "TimeoutError" || name === "AbortError") {
593
+ return { ok: false, status: "timeout", elapsedMs: elapsed2, detail: `> ${timeout}ms` };
594
+ }
595
+ const detail = scrubber.scrub(err?.message ?? String(err));
596
+ return { ok: false, status: "unreachable", elapsedMs: elapsed2, detail };
597
+ }
598
+ const elapsed = Date.now() - started;
599
+ if (!res.ok) {
600
+ let bodySlice = "";
601
+ try {
602
+ const txt = await res.text();
603
+ bodySlice = txt.slice(0, 200);
604
+ } catch {
605
+ }
606
+ return {
607
+ ok: false,
608
+ status: "http_error",
609
+ httpStatus: res.status,
610
+ elapsedMs: elapsed,
611
+ detail: scrubber.scrub(bodySlice) || void 0
612
+ };
613
+ }
614
+ const modelIds = [];
615
+ try {
616
+ const parsed = await res.json();
617
+ if (!parsed || typeof parsed !== "object") {
618
+ return {
619
+ ok: false,
620
+ status: "invalid_response",
621
+ httpStatus: res.status,
622
+ elapsedMs: elapsed,
623
+ detail: "response is not a JSON object"
624
+ };
625
+ }
626
+ const obj = parsed;
627
+ const dataList = obj["data"];
628
+ const modelsList = obj["models"];
629
+ const rawList = Array.isArray(dataList) ? dataList : Array.isArray(modelsList) ? modelsList : null;
630
+ if (rawList === null) {
631
+ return {
632
+ ok: false,
633
+ status: "invalid_response",
634
+ httpStatus: res.status,
635
+ elapsedMs: elapsed,
636
+ detail: "no `data` or `models` array in response"
637
+ };
638
+ }
639
+ const seen = /* @__PURE__ */ new Set();
640
+ for (const entry of rawList) {
641
+ if (!entry || typeof entry !== "object") continue;
642
+ const e = entry;
643
+ const raw = typeof e["id"] === "string" ? e["id"] : typeof e["name"] === "string" ? e["name"] : null;
644
+ if (raw === null) continue;
645
+ const id = scrubber.scrub(raw).trim();
646
+ if (id.length === 0) continue;
647
+ if (seen.has(id)) continue;
648
+ seen.add(id);
649
+ modelIds.push(id);
650
+ }
651
+ } catch (err) {
652
+ return {
653
+ ok: false,
654
+ status: "invalid_response",
655
+ httpStatus: res.status,
656
+ elapsedMs: elapsed,
657
+ detail: scrubber.scrub(err?.message ?? "parse failed")
658
+ };
659
+ }
660
+ return {
661
+ ok: true,
662
+ status: "ok",
663
+ httpStatus: res.status,
664
+ elapsedMs: elapsed,
665
+ modelCount: modelIds.length,
666
+ modelIds
667
+ };
668
+ }
669
+ var ImageInputUnsupportedError = class extends Error {
670
+ constructor(opts) {
671
+ const target = [opts.providerId, opts.model].filter(Boolean).join("/") || "current model";
672
+ super(
673
+ `${target} does not support image input, and no image-understanding adapter is available for ${opts.imageCount} image${opts.imageCount === 1 ? "" : "s"}. Switch to a vision model or enable an MCP/tool adapter that can describe images.`
674
+ );
675
+ this.name = "ImageInputUnsupportedError";
676
+ }
677
+ };
678
+ var VisionUrlBlockedError = class extends Error {
679
+ url;
680
+ constructor(opts) {
681
+ super(`vision: blocked unsafe image URL "${opts.url}" (${opts.reason})`);
682
+ this.name = "VisionUrlBlockedError";
683
+ this.url = opts.url;
684
+ }
685
+ };
686
+ async function routeImagesForModel(blocks, opts) {
687
+ const images = blocks.filter((b) => b.type === "image");
688
+ if (images.length === 0) {
689
+ return { blocks, route: "none", convertedImages: 0 };
690
+ }
691
+ if (opts.supportsVision) {
692
+ for (const img of images) {
693
+ if (img.source.type === "url" && img.source.url) {
694
+ try {
695
+ await assertNotPrivateHost(new URL(img.source.url).hostname);
696
+ } catch (err) {
697
+ const reason = err instanceof Error && err.message.startsWith("fetch:") ? err.message.slice("fetch:".length).trim() : "unresolvable host";
698
+ throw new VisionUrlBlockedError({ url: img.source.url, reason });
699
+ }
700
+ }
701
+ }
702
+ return { blocks, route: "native", convertedImages: 0 };
703
+ }
704
+ const adapters = await resolveAdapters(opts.adapters);
705
+ if (adapters.length === 0) {
706
+ throw new ImageInputUnsupportedError({
707
+ providerId: opts.providerId,
708
+ model: opts.model,
709
+ imageCount: images.length
710
+ });
711
+ }
712
+ const out = [];
713
+ let convertedImages = 0;
714
+ let lastErr;
715
+ let adapterName;
716
+ for (const block of blocks) {
717
+ if (block.type !== "image") {
718
+ out.push(block);
719
+ continue;
720
+ }
721
+ let description;
722
+ for (const adapter of adapters) {
723
+ try {
724
+ description = await adapter.describe({
725
+ image: block,
726
+ prompt: opts.prompt,
727
+ ctx: opts.ctx,
728
+ signal: opts.signal
729
+ });
730
+ adapterName = adapter.name;
731
+ break;
732
+ } catch (err) {
733
+ lastErr = err;
734
+ }
735
+ }
736
+ if (!description?.trim()) {
737
+ throw new Error(
738
+ `No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ""}`
739
+ );
740
+ }
741
+ convertedImages++;
742
+ out.push({
743
+ type: "text",
744
+ text: `[Image ${convertedImages} analyzed via ${adapterName ?? "vision adapter"}]
745
+ ${description.trim()}`
746
+ });
747
+ }
748
+ return { blocks: out, route: "adapter", convertedImages, adapterName };
749
+ }
750
+ async function resolveAdapters(adapters) {
751
+ if (!adapters) return [];
752
+ return typeof adapters === "function" ? await adapters() : adapters;
753
+ }
754
+ function createToolVisionAdapters(registry, opts = {}) {
755
+ return registry.list().filter(isLikelyVisionTool).map((tool) => ({
756
+ name: tool.name,
757
+ async describe(input) {
758
+ const currentTool = registry.get(tool.name);
759
+ if (!currentTool) {
760
+ throw new Error(`Tool "${tool.name}" is no longer registered`);
761
+ }
762
+ const built = await buildToolPayload(currentTool, input.image, input.prompt ?? opts.prompt);
763
+ if (!built) {
764
+ throw new Error(
765
+ `Tool "${currentTool.name}" does not expose a supported image input schema`
766
+ );
767
+ }
768
+ try {
769
+ const result = await currentTool.execute(built.payload, input.ctx, {
770
+ signal: input.signal
771
+ });
772
+ return stringifyToolResult(result);
773
+ } finally {
774
+ await built.cleanup?.();
775
+ }
776
+ }
777
+ }));
778
+ }
779
+ var VISION_IMAGE_KEYS = [
780
+ "image",
781
+ "base64",
782
+ "data",
783
+ "url",
784
+ "image_url",
785
+ "imageUrl",
786
+ "path",
787
+ "image_path",
788
+ "imagePath",
789
+ "file_path",
790
+ "filePath",
791
+ "filename",
792
+ "file",
793
+ "mediaType",
794
+ "mimeType"
795
+ ];
796
+ var VISION_PATH_KEYS = [
797
+ "path",
798
+ "image_path",
799
+ "imagePath",
800
+ "image_url",
801
+ "imageUrl",
802
+ "file_path",
803
+ "filePath",
804
+ "filename",
805
+ "file"
806
+ ];
807
+ var VISION_MEDIA_TYPE_KEYS = ["mediaType", "mimeType", "media_type"];
808
+ var VISION_PROMPT_KEYS = ["prompt", "query", "instruction"];
809
+ function isLikelyVisionTool(tool) {
810
+ if (tool.permission !== "auto" || tool.mutating) return false;
811
+ const haystack = `${tool.name} ${tool.description ?? ""} ${tool.usageHint ?? ""}`.toLowerCase();
812
+ if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;
813
+ if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;
814
+ const props = schemaProperties(tool);
815
+ return VISION_IMAGE_KEYS.some((key) => key in props);
816
+ }
817
+ async function buildToolPayload(tool, image, prompt = "Describe this image for a coding agent. Include visible text, UI state, errors, layout, and any details needed to answer the user.") {
818
+ const props = schemaProperties(tool);
819
+ const payload = {};
820
+ const mediaType = image.source.media_type ?? "image/png";
821
+ const data = image.source.data;
822
+ const url = image.source.url;
823
+ let cleanup;
824
+ if (image.source.type === "url" && url) {
825
+ try {
826
+ await assertNotPrivateHost(new URL(url).hostname);
827
+ } catch (err) {
828
+ const reason = err instanceof Error && err.message.startsWith("fetch:") ? err.message.slice("fetch:".length).trim() : "unresolvable host";
829
+ throw new VisionUrlBlockedError({ url, reason });
830
+ }
831
+ }
832
+ const pathKey = firstPresent(props, [...VISION_PATH_KEYS]);
833
+ if (pathKey && image.source.type === "base64" && data) {
834
+ const p = await writeTempImage(data, mediaType);
835
+ payload[pathKey] = p;
836
+ cleanup = async () => {
837
+ await fs.unlink(p).catch(() => void 0);
838
+ await fs.rmdir(path.dirname(p)).catch(() => void 0);
839
+ };
840
+ } else if ("image" in props) {
841
+ payload.image = image.source.type === "base64" ? { type: "base64", mediaType, media_type: mediaType, data } : { type: "url", url };
842
+ } else if (image.source.type === "base64" && "base64" in props) {
843
+ payload.base64 = data;
844
+ } else if (image.source.type === "base64" && "data" in props) {
845
+ payload.data = data;
846
+ } else if (image.source.type === "url" && "url" in props) {
847
+ payload.url = url;
848
+ } else if (image.source.type === "url" && "image_url" in props) {
849
+ payload.image_url = url;
850
+ } else if (image.source.type === "url" && "imageUrl" in props) {
851
+ payload.imageUrl = url;
852
+ } else {
853
+ return null;
854
+ }
855
+ for (const key of VISION_MEDIA_TYPE_KEYS) {
856
+ if (key in props) payload[key] = mediaType;
857
+ }
858
+ for (const key of VISION_PROMPT_KEYS) {
859
+ if (key in props) payload[key] = prompt;
860
+ }
861
+ const built = { payload };
862
+ if (cleanup !== void 0) built.cleanup = cleanup;
863
+ return built;
393
864
  }
394
865
  function firstPresent(props, keys) {
395
866
  return keys.find((key) => key in props);
396
867
  }
397
868
  async function writeTempImage(data, mediaType) {
398
869
  const ext = mediaType.includes("jpeg") || mediaType.includes("jpg") ? "jpg" : "png";
399
- const dir = await fs2.mkdtemp(path.join(os2.tmpdir(), "wstack-vision-"));
870
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "wstack-vision-"));
400
871
  const file = path.join(dir, `image.${ext}`);
401
- await fs2.writeFile(file, data, "base64");
872
+ await fs.writeFile(file, data, "base64");
402
873
  return file;
403
874
  }
404
875
  function schemaProperties(tool) {
@@ -422,299 +893,7 @@ function stringifyToolResult(value) {
422
893
  }
423
894
  return JSON.stringify(value);
424
895
  }
425
- var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
426
- async function readClipboardImage() {
427
- const platform = process.platform;
428
- if (platform === "win32") return readWindows();
429
- if (platform === "darwin") return readDarwin();
430
- if (platform === "linux") return readLinux();
431
- return null;
432
- }
433
- async function readClipboardText() {
434
- const platform = process.platform;
435
- if (platform === "win32") {
436
- const ps = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw";
437
- const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
438
- if (out == null) return null;
439
- const text = out.replace(/\r?\n$/, "");
440
- return text.length > 0 ? text : null;
441
- }
442
- if (platform === "darwin") {
443
- const out = await runCmd("pbpaste", []);
444
- return out && out.length > 0 ? out : null;
445
- }
446
- if (platform === "linux") {
447
- const tries = [
448
- ["wl-paste", ["--no-newline"]],
449
- ["xclip", ["-selection", "clipboard", "-o"]]
450
- ];
451
- for (const [cmd, args] of tries) {
452
- const out = await runCmd(cmd, args);
453
- if (out && out.length > 0) return out;
454
- }
455
- return null;
456
- }
457
- return null;
458
- }
459
- async function readWindows() {
460
- const tmp = path.join(os2.tmpdir(), `wstack-clip-${randomUUID()}.png`);
461
- const ps = [
462
- "Add-Type -AssemblyName System.Windows.Forms",
463
- "Add-Type -AssemblyName System.Drawing",
464
- "$img = [System.Windows.Forms.Clipboard]::GetImage()",
465
- 'if ($img -eq $null) { Write-Output "NO_IMAGE"; exit 0 }',
466
- `$img.Save('${tmp.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)`,
467
- 'Write-Output "OK"'
468
- ].join("; ");
469
- const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
470
- if (!out || out.trim() === "NO_IMAGE") return null;
471
- if (!out.includes("OK")) return null;
472
- return readPngFile(tmp);
473
- }
474
- async function readDarwin() {
475
- const tmp = path.join(os2.tmpdir(), `wstack-clip-${randomUUID()}.png`);
476
- const script = [
477
- "try",
478
- ` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
479
- " write (the clipboard as \xABclass PNGf\xBB) to the_file",
480
- " close access the_file",
481
- "on error",
482
- " try",
483
- ' close access POSIX file "' + tmp + '"',
484
- " end try",
485
- ' return "NO_IMAGE"',
486
- "end try",
487
- 'return "OK"'
488
- ].join("\n");
489
- const out = await runCmd("osascript", ["-e", script]);
490
- if (out?.trim() !== "OK") return null;
491
- return readPngFile(tmp);
492
- }
493
- async function readLinux() {
494
- const tmp = path.join(os2.tmpdir(), `wstack-clip-${randomUUID()}.png`);
495
- const tries = [
496
- ["wl-paste", ["--type", "image/png"]],
497
- ["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
498
- ];
499
- for (const [cmd, args] of tries) {
500
- const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
501
- if (ok) return readPngFile(tmp);
502
- }
503
- return null;
504
- }
505
- async function readPngFile(p) {
506
- try {
507
- const buf = await fs2.readFile(p);
508
- if (buf.length === 0) {
509
- await fs2.unlink(p).catch(() => void 0);
510
- return null;
511
- }
512
- if (buf.length > MAX_IMAGE_BYTES) {
513
- await fs2.unlink(p).catch(() => void 0);
514
- throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
515
- }
516
- if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
517
- await fs2.unlink(p).catch(() => void 0);
518
- return null;
519
- }
520
- await fs2.unlink(p).catch(() => void 0);
521
- return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
522
- } catch (err) {
523
- if (err.code === "ENOENT") return null;
524
- throw err;
525
- }
526
- }
527
- var CLIPBOARD_CMD_TIMEOUT_MS = 5e3;
528
- function runCmd(cmd, args) {
529
- return new Promise((resolve) => {
530
- const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
531
- let out = "";
532
- let settled = false;
533
- const finish = (value) => {
534
- if (settled) return;
535
- settled = true;
536
- clearTimeout(timer);
537
- resolve(value);
538
- };
539
- const timer = setTimeout(() => {
540
- child.kill("SIGTERM");
541
- finish(null);
542
- }, CLIPBOARD_CMD_TIMEOUT_MS);
543
- child.stdout.on("data", (c) => {
544
- out += String(c);
545
- });
546
- child.on("error", () => finish(null));
547
- child.on("exit", (code) => finish(code === 0 ? out : null));
548
- });
549
- }
550
- function runCmdToFile(cmd, args, outPath) {
551
- return new Promise((resolve) => {
552
- const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
553
- const chunks = [];
554
- let settled = false;
555
- const finish = (value) => {
556
- if (settled) return;
557
- settled = true;
558
- clearTimeout(timer);
559
- resolve(value);
560
- };
561
- const timer = setTimeout(() => {
562
- child.kill("SIGTERM");
563
- finish(false);
564
- }, CLIPBOARD_CMD_TIMEOUT_MS);
565
- child.stdout.on("data", (c) => chunks.push(c));
566
- child.on("error", () => finish(false));
567
- child.on("exit", async (code) => {
568
- if (code !== 0 || chunks.length === 0) return finish(false);
569
- try {
570
- await fs2.writeFile(outPath, Buffer.concat(chunks));
571
- finish(true);
572
- } catch {
573
- finish(false);
574
- }
575
- });
576
- });
577
- }
578
- var SUBAGENT_BASELINE = "You are a subagent executing one delegated task end-to-end. Work autonomously with your tools; do not ask for confirmation on routine in-project actions. Keep output concise.";
579
- var _SUBAGENT_ABORT = "wrongstack:subagent-abort-controller";
580
- function makeLightSubagentFactory(deps) {
581
- const configStore = deps.container.resolve(TOKENS.ConfigStore);
582
- const tokenCounter = deps.container.resolve(TOKENS.TokenCounter);
583
- const secretScrubber = deps.container.resolve(TOKENS.SecretScrubber);
584
- const systemPromptBuilder = deps.container.resolve(TOKENS.SystemPromptBuilder);
585
- return async (subCfg) => {
586
- const events = new EventBus();
587
- const config = configStore.get();
588
- const matrixEntry = subCfg.model ? void 0 : resolveModelMatrix(config.modelMatrix, subCfg.role);
589
- const matrixTarget = resolveModelTargetFromEntry(config, matrixEntry);
590
- const effProvider = subCfg.provider ?? matrixTarget?.provider ?? config.provider;
591
- const effModel = subCfg.model ?? matrixTarget?.model ?? config.model;
592
- const provider = buildProvider(deps.providerRegistry, config, effProvider, effModel);
593
- const subCwd = subCfg.cwd ?? deps.cwd ?? deps.projectRoot;
594
- const allowed = filterToolList(deps.toolRegistry, subCfg.tools);
595
- const subRegistry = new ToolRegistry();
596
- for (const t of allowed) subRegistry.register(t);
597
- const baseSystem = await systemPromptBuilder.build({
598
- cwd: subCwd,
599
- projectRoot: deps.projectRoot,
600
- tools: allowed,
601
- model: effModel,
602
- provider: effProvider,
603
- subagent: true
604
- });
605
- baseSystem.unshift({ type: "text", text: SUBAGENT_BASELINE });
606
- if (subCfg.systemPromptOverride) {
607
- baseSystem.push({ type: "text", text: subCfg.systemPromptOverride });
608
- }
609
- const agentName = subCfg.name ?? subCfg.id ?? `sub_${cryptoId()}`;
610
- const session = makeSubagentSessionShim(deps.session);
611
- const ac = new AbortController();
612
- const ctx = new Context({
613
- systemPrompt: baseSystem,
614
- provider,
615
- session,
616
- signal: ac.signal,
617
- tokenCounter,
618
- cwd: subCwd,
619
- projectRoot: deps.projectRoot,
620
- allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
621
- model: effModel,
622
- tools: allowed,
623
- agentId: agentName,
624
- agentName
625
- });
626
- if (subCfg.role) ctx.meta["agentRole"] = subCfg.role;
627
- ctx.meta[_SUBAGENT_ABORT] = ac;
628
- const caps = subCfg.allowedCapabilities ?? WIDE_SUBAGENT_CAPABILITIES;
629
- const permissionPolicy = new AutoApprovePermissionPolicy(caps);
630
- const toolExecutor = new ToolExecutor(subRegistry, {
631
- permissionPolicy,
632
- secretScrubber,
633
- events,
634
- confirmAwaiter: void 0,
635
- iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
636
- perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
637
- tracer: void 0
638
- });
639
- const agent = new Agent({
640
- container: deps.container,
641
- tools: subRegistry,
642
- providers: deps.providerRegistry,
643
- events,
644
- pipelines: createDefaultPipelines(),
645
- context: ctx,
646
- permissionPolicy,
647
- toolExecutor
648
- });
649
- const subFallbacks = subCfg.fallbackModels;
650
- const matrixFallbacks = matrixTarget?.fallbackModels;
651
- agent.extensions.register(
652
- createFallbackModelExtension({
653
- getConfig: () => {
654
- const live = configStore.get();
655
- if (subFallbacks && subFallbacks.length) return { ...live, fallbackModels: subFallbacks };
656
- if (matrixFallbacks && matrixFallbacks.length) return { ...live, fallbackModels: matrixFallbacks };
657
- return live;
658
- },
659
- buildProvider: (id) => buildProvider(deps.providerRegistry, configStore.get(), id, effModel),
660
- events
661
- })
662
- );
663
- return { agent, events };
664
- };
665
- }
666
- function filterToolList(registry, allow) {
667
- const all = registry.list();
668
- if (!allow || allow.length === 0) return all;
669
- const allowSet = new Set(allow);
670
- return all.filter((t) => allowSet.has(t.name));
671
- }
672
- function buildProvider(registry, config, providerId, model) {
673
- const providerConfig = config.providers?.[providerId] ?? {
674
- type: providerId,
675
- apiKey: config.apiKey,
676
- baseUrl: config.baseUrl
677
- };
678
- if (!registry.has(providerId)) {
679
- throw new Error(
680
- `No provider factory registered for "${providerId}" \u2014 cannot build a subagent provider for the SDD run.`
681
- );
682
- }
683
- return registry.create({ ...providerConfig, type: providerId, model });
684
- }
685
- function makeSubagentSessionShim(parent) {
686
- return {
687
- id: parent.id,
688
- transcriptPath: parent.transcriptPath,
689
- get pendingToolUses() {
690
- return [];
691
- },
692
- append: (ev) => parent.append({ ...ev }),
693
- appendBatch: (evs) => parent.appendBatch(evs.map((e) => ({ ...e }))),
694
- flush: () => parent.flush(),
695
- close: async () => {
696
- },
697
- recordFileChange: () => {
698
- },
699
- recordSideEffect: () => {
700
- },
701
- writeCheckpoint: async () => {
702
- },
703
- writeFileSnapshot: async () => {
704
- },
705
- truncateToCheckpoint: async () => 0,
706
- clearSession: async () => {
707
- },
708
- writeInFlightMarker: async () => {
709
- },
710
- clearInFlightMarker: async () => {
711
- }
712
- };
713
- }
714
- function cryptoId() {
715
- return crypto.randomUUID().slice(0, 8);
716
- }
717
896
 
718
- export { ImageInputUnsupportedError, VisionUrlBlockedError, applyWrongStackPack, applyWrongStackPacks, createDefaultContainer, createRuntimeHostFromParts, createToolVisionAdapters, makeLightSubagentFactory, readClipboardImage, readClipboardText, routeImagesForModel };
897
+ export { ImageInputUnsupportedError, VISION_IMAGE_KEYS, VISION_MEDIA_TYPE_KEYS, VISION_PATH_KEYS, VISION_PROMPT_KEYS, VisionUrlBlockedError, applyWrongStackPack, applyWrongStackPacks, createDefaultContainer, createRuntimeHostFromParts, createToolVisionAdapters, makeLightSubagentFactory, probeLocalLlm, readClipboardImage, readClipboardText, routeImagesForModel };
719
898
  //# sourceMappingURL=index.js.map
720
899
  //# sourceMappingURL=index.js.map