@wrongstack/runtime 0.277.2 → 0.280.0

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/clipboard.js CHANGED
@@ -111,44 +111,62 @@ async function readPngFile(p) {
111
111
  var CLIPBOARD_CMD_TIMEOUT_MS = 5e3;
112
112
  function runCmd(cmd, args) {
113
113
  return new Promise((resolve) => {
114
- const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
114
+ const child = spawn(cmd, args, {
115
+ env: buildChildEnv(),
116
+ stdio: ["ignore", "pipe", "pipe"],
117
+ windowsHide: true
118
+ });
115
119
  let out = "";
116
120
  let settled = false;
121
+ let killedByTimeout = false;
117
122
  const finish = (value) => {
118
123
  if (settled) return;
119
124
  settled = true;
120
125
  clearTimeout(timer);
126
+ clearTimeout(killCap);
121
127
  resolve(value);
122
128
  };
123
129
  const timer = setTimeout(() => {
130
+ killedByTimeout = true;
124
131
  child.kill("SIGTERM");
125
- finish(null);
126
132
  }, CLIPBOARD_CMD_TIMEOUT_MS);
133
+ const killCap = setTimeout(() => finish(null), CLIPBOARD_CMD_TIMEOUT_MS + 2e3);
127
134
  child.stdout.on("data", (c) => {
128
135
  out += String(c);
129
136
  });
130
137
  child.on("error", () => finish(null));
131
- child.on("exit", (code) => finish(code === 0 ? out : null));
138
+ child.on("exit", (code) => {
139
+ if (killedByTimeout) return finish(null);
140
+ finish(code === 0 ? out : null);
141
+ });
132
142
  });
133
143
  }
134
144
  function runCmdToFile(cmd, args, outPath) {
135
145
  return new Promise((resolve) => {
136
- const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
146
+ const child = spawn(cmd, args, {
147
+ env: buildChildEnv(),
148
+ stdio: ["ignore", "pipe", "pipe"],
149
+ windowsHide: true
150
+ });
137
151
  const chunks = [];
138
152
  let settled = false;
153
+ let killedByTimeout = false;
139
154
  const finish = (value) => {
140
155
  if (settled) return;
141
156
  settled = true;
142
157
  clearTimeout(timer);
158
+ clearTimeout(killCap);
143
159
  resolve(value);
144
160
  };
145
161
  const timer = setTimeout(() => {
162
+ killedByTimeout = true;
146
163
  child.kill("SIGTERM");
147
- finish(false);
148
164
  }, CLIPBOARD_CMD_TIMEOUT_MS);
165
+ const killCap = setTimeout(() => finish(false), CLIPBOARD_CMD_TIMEOUT_MS + 2e3);
149
166
  child.stdout.on("data", (c) => chunks.push(c));
150
167
  child.on("error", () => finish(false));
151
168
  child.on("exit", async (code) => {
169
+ if (killedByTimeout) return finish(false);
152
170
  if (code !== 0 || chunks.length === 0) return finish(false);
153
171
  try {
154
172
  await fs.writeFile(outPath, Buffer.concat(chunks));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/clipboard.ts"],"names":[],"mappings":"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","file":"clipboard.js","sourcesContent":["import { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { buildChildEnv } from '@wrongstack/core';\n\nexport interface ClipboardImage {\n base64: string;\n mediaType: 'image/png';\n bytes: number;\n}\n\nconst MAX_IMAGE_BYTES = 10 * 1024 * 1024;\n\nexport async function readClipboardImage(): Promise<ClipboardImage | null> {\n const platform = process.platform;\n if (platform === 'win32') return readWindows();\n if (platform === 'darwin') return readDarwin();\n if (platform === 'linux') return readLinux();\n return null;\n}\n\n/**\n * Read plain text from the system clipboard. Returns `null` when the clipboard\n * holds no text (or only an image), the read failed, or the platform is\n * unsupported. Used by the TUI's Ctrl+V handler: terminals in raw mode deliver\n * Ctrl+V to the app as a control byte rather than performing a native paste, so\n * we read the clipboard ourselves.\n */\nexport async function readClipboardText(): Promise<string | null> {\n const platform = process.platform;\n if (platform === 'win32') {\n // -Raw preserves embedded newlines; force UTF-8 so non-ASCII survives the\n // pipe. PowerShell appends one trailing newline to stdout — strip it.\n const ps =\n '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw';\n const out = await runCmd('powershell', ['-NoProfile', '-Command', ps]);\n if (out == null) return null;\n const text = out.replace(/\\r?\\n$/, '');\n return text.length > 0 ? text : null;\n }\n if (platform === 'darwin') {\n const out = await runCmd('pbpaste', []);\n return out && out.length > 0 ? out : null;\n }\n if (platform === 'linux') {\n const tries: Array<[string, string[]]> = [\n ['wl-paste', ['--no-newline']],\n ['xclip', ['-selection', 'clipboard', '-o']],\n ];\n for (const [cmd, args] of tries) {\n const out = await runCmd(cmd, args);\n if (out && out.length > 0) return out;\n }\n return null;\n }\n return null;\n}\n\nasync function readWindows(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);\n const ps = [\n 'Add-Type -AssemblyName System.Windows.Forms',\n 'Add-Type -AssemblyName System.Drawing',\n '$img = [System.Windows.Forms.Clipboard]::GetImage()',\n 'if ($img -eq $null) { Write-Output \"NO_IMAGE\"; exit 0 }',\n `$img.Save('${tmp.replace(/\\\\/g, '\\\\\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)`,\n 'Write-Output \"OK\"',\n ].join('; ');\n const out = await runCmd('powershell', ['-NoProfile', '-Command', ps]);\n if (!out || out.trim() === 'NO_IMAGE') return null;\n if (!out.includes('OK')) return null;\n return readPngFile(tmp);\n}\n\nasync function readDarwin(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);\n const script = [\n 'try',\n ` set the_file to (open for access POSIX file \"${tmp}\" with write permission)`,\n ' write (the clipboard as «class PNGf») to the_file',\n ' close access the_file',\n 'on error',\n ' try',\n ' close access POSIX file \"' + tmp + '\"',\n ' end try',\n ' return \"NO_IMAGE\"',\n 'end try',\n 'return \"OK\"',\n ].join('\\n');\n const out = await runCmd('osascript', ['-e', script]);\n if (out?.trim() !== 'OK') return null;\n return readPngFile(tmp);\n}\n\nasync function readLinux(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);\n const tries: Array<[string, string[]]> = [\n ['wl-paste', ['--type', 'image/png']],\n ['xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o']],\n ];\n for (const [cmd, args] of tries) {\n const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);\n if (ok) return readPngFile(tmp);\n }\n return null;\n}\n\nasync function readPngFile(p: string): Promise<ClipboardImage | null> {\n try {\n const buf = await fs.readFile(p);\n if (buf.length === 0) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n if (buf.length > MAX_IMAGE_BYTES) {\n await fs.unlink(p).catch(() => undefined);\n throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);\n }\n if (buf[0] !== 0x89 || buf[1] !== 0x50 || buf[2] !== 0x4e || buf[3] !== 0x47) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n await fs.unlink(p).catch(() => undefined);\n return { base64: buf.toString('base64'), mediaType: 'image/png', bytes: buf.length };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n\n/**\n * Hard ceiling for a clipboard subprocess. Reading the clipboard must never\n * hang the TUI: on a headless/loaded CI runner the PowerShell/xclip/wl-paste\n * read can stall indefinitely (no display, slow shell start). After this we\n * kill the child and resolve the safe default.\n */\nconst CLIPBOARD_CMD_TIMEOUT_MS = 5_000;\n\nfunction runCmd(cmd: string, args: string[]): Promise<string | null> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });\n let out = '';\n let settled = false;\n const finish = (value: string | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(value);\n };\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n finish(null);\n }, CLIPBOARD_CMD_TIMEOUT_MS);\n child.stdout.on('data', (c) => {\n out += String(c);\n });\n child.on('error', () => finish(null));\n child.on('exit', (code) => finish(code === 0 ? out : null));\n });\n}\n\nfunction runCmdToFile(cmd: string, args: string[], outPath: string): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });\n const chunks: Buffer[] = [];\n let settled = false;\n const finish = (value: boolean) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(value);\n };\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n finish(false);\n }, CLIPBOARD_CMD_TIMEOUT_MS);\n child.stdout.on('data', (c: Buffer) => chunks.push(c));\n child.on('error', () => finish(false));\n child.on('exit', async (code) => {\n if (code !== 0 || chunks.length === 0) return finish(false);\n try {\n await fs.writeFile(outPath, Buffer.concat(chunks));\n finish(true);\n } catch {\n finish(false);\n }\n });\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/clipboard.ts"],"names":[],"mappings":"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","file":"clipboard.js","sourcesContent":["import { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { buildChildEnv } from '@wrongstack/core';\n\nexport interface ClipboardImage {\n base64: string;\n mediaType: 'image/png';\n bytes: number;\n}\n\nconst MAX_IMAGE_BYTES = 10 * 1024 * 1024;\n\nexport async function readClipboardImage(): Promise<ClipboardImage | null> {\n const platform = process.platform;\n if (platform === 'win32') return readWindows();\n if (platform === 'darwin') return readDarwin();\n if (platform === 'linux') return readLinux();\n return null;\n}\n\n/**\n * Read plain text from the system clipboard. Returns `null` when the clipboard\n * holds no text (or only an image), the read failed, or the platform is\n * unsupported. Used by the TUI's Ctrl+V handler: terminals in raw mode deliver\n * Ctrl+V to the app as a control byte rather than performing a native paste, so\n * we read the clipboard ourselves.\n */\nexport async function readClipboardText(): Promise<string | null> {\n const platform = process.platform;\n if (platform === 'win32') {\n // -Raw preserves embedded newlines; force UTF-8 so non-ASCII survives the\n // pipe. PowerShell appends one trailing newline to stdout — strip it.\n const ps = '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw';\n const out = await runCmd('powershell', ['-NoProfile', '-Command', ps]);\n if (out == null) return null;\n const text = out.replace(/\\r?\\n$/, '');\n return text.length > 0 ? text : null;\n }\n if (platform === 'darwin') {\n const out = await runCmd('pbpaste', []);\n return out && out.length > 0 ? out : null;\n }\n if (platform === 'linux') {\n const tries: Array<[string, string[]]> = [\n ['wl-paste', ['--no-newline']],\n ['xclip', ['-selection', 'clipboard', '-o']],\n ];\n for (const [cmd, args] of tries) {\n const out = await runCmd(cmd, args);\n if (out && out.length > 0) return out;\n }\n return null;\n }\n return null;\n}\n\nasync function readWindows(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);\n const ps = [\n 'Add-Type -AssemblyName System.Windows.Forms',\n 'Add-Type -AssemblyName System.Drawing',\n '$img = [System.Windows.Forms.Clipboard]::GetImage()',\n 'if ($img -eq $null) { Write-Output \"NO_IMAGE\"; exit 0 }',\n `$img.Save('${tmp.replace(/\\\\/g, '\\\\\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)`,\n 'Write-Output \"OK\"',\n ].join('; ');\n const out = await runCmd('powershell', ['-NoProfile', '-Command', ps]);\n if (!out || out.trim() === 'NO_IMAGE') return null;\n if (!out.includes('OK')) return null;\n return readPngFile(tmp);\n}\n\nasync function readDarwin(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);\n const script = [\n 'try',\n ` set the_file to (open for access POSIX file \"${tmp}\" with write permission)`,\n ' write (the clipboard as «class PNGf») to the_file',\n ' close access the_file',\n 'on error',\n ' try',\n ' close access POSIX file \"' + tmp + '\"',\n ' end try',\n ' return \"NO_IMAGE\"',\n 'end try',\n 'return \"OK\"',\n ].join('\\n');\n const out = await runCmd('osascript', ['-e', script]);\n if (out?.trim() !== 'OK') return null;\n return readPngFile(tmp);\n}\n\nasync function readLinux(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);\n const tries: Array<[string, string[]]> = [\n ['wl-paste', ['--type', 'image/png']],\n ['xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o']],\n ];\n for (const [cmd, args] of tries) {\n const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);\n if (ok) return readPngFile(tmp);\n }\n return null;\n}\n\nasync function readPngFile(p: string): Promise<ClipboardImage | null> {\n try {\n const buf = await fs.readFile(p);\n if (buf.length === 0) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n if (buf.length > MAX_IMAGE_BYTES) {\n await fs.unlink(p).catch(() => undefined);\n throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);\n }\n if (buf[0] !== 0x89 || buf[1] !== 0x50 || buf[2] !== 0x4e || buf[3] !== 0x47) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n await fs.unlink(p).catch(() => undefined);\n return { base64: buf.toString('base64'), mediaType: 'image/png', bytes: buf.length };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n\n/**\n * Hard ceiling for a clipboard subprocess. Reading the clipboard must never\n * hang the TUI: on a headless/loaded CI runner the PowerShell/xclip/wl-paste\n * read can stall indefinitely (no display, slow shell start). After this we\n * kill the child and resolve the safe default.\n */\nconst CLIPBOARD_CMD_TIMEOUT_MS = 5_000;\n\nfunction runCmd(cmd: string, args: string[]): Promise<string | null> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, {\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n });\n let out = '';\n let settled = false;\n let killedByTimeout = false;\n const finish = (value: string | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearTimeout(killCap);\n resolve(value);\n };\n const timer = setTimeout(() => {\n killedByTimeout = true;\n child.kill('SIGTERM');\n }, CLIPBOARD_CMD_TIMEOUT_MS);\n // Safety cap: if the child ignores SIGTERM, do not hang forever.\n const killCap = setTimeout(() => finish(null), CLIPBOARD_CMD_TIMEOUT_MS + 2_000);\n child.stdout.on('data', (c) => {\n out += String(c);\n });\n child.on('error', () => finish(null));\n child.on('exit', (code) => {\n if (killedByTimeout) return finish(null);\n finish(code === 0 ? out : null);\n });\n });\n}\n\nfunction runCmdToFile(cmd: string, args: string[], outPath: string): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, {\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n });\n const chunks: Buffer[] = [];\n let settled = false;\n let killedByTimeout = false;\n const finish = (value: boolean) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearTimeout(killCap);\n resolve(value);\n };\n const timer = setTimeout(() => {\n killedByTimeout = true;\n child.kill('SIGTERM');\n }, CLIPBOARD_CMD_TIMEOUT_MS);\n // Safety cap: if the child ignores SIGTERM, do not hang forever.\n const killCap = setTimeout(() => finish(false), CLIPBOARD_CMD_TIMEOUT_MS + 2_000);\n child.stdout.on('data', (c: Buffer) => chunks.push(c));\n child.on('error', () => finish(false));\n child.on('exit', async (code) => {\n if (killedByTimeout) return finish(false);\n if (code !== 0 || chunks.length === 0) return finish(false);\n try {\n await fs.writeFile(outPath, Buffer.concat(chunks));\n finish(true);\n } catch {\n finish(false);\n }\n });\n });\n}\n"]}
package/dist/host.js CHANGED
@@ -33,7 +33,8 @@ async function applyWrongStackPack(host, pack, opts = {}) {
33
33
  if (pack.slashCommands) {
34
34
  const cmds = [...pack.slashCommands];
35
35
  host.slashCommands.registerAll(cmds, owner);
36
- for (const c of cmds) registeredCommandNames.push(c.name);
36
+ for (const c of cmds)
37
+ registeredCommandNames.push(owner === "core" ? c.name : `${owner}:${c.name}`);
37
38
  }
38
39
  if (pack.extensions && host.extensions) {
39
40
  for (const ext of pack.extensions) {
@@ -47,17 +48,17 @@ async function applyWrongStackPack(host, pack, opts = {}) {
47
48
  try {
48
49
  await pack.setup(opts.api);
49
50
  } catch (setupErr) {
50
- for (const unregister of unregisterExtensions.reverse()) {
51
- unregister();
51
+ for (let i = unregisterExtensions.length - 1; i >= 0; i--) {
52
+ unregisterExtensions[i]();
52
53
  }
53
- for (const name of registeredCommandNames.reverse()) {
54
- host.slashCommands.unregister(name);
54
+ for (let i = registeredCommandNames.length - 1; i >= 0; i--) {
55
+ host.slashCommands.unregister(registeredCommandNames[i]);
55
56
  }
56
- for (const name of registeredToolNames.reverse()) {
57
- host.tools.unregister(name);
57
+ for (let i = registeredToolNames.length - 1; i >= 0; i--) {
58
+ host.tools.unregister(registeredToolNames[i]);
58
59
  }
59
- for (const type of registeredProviderTypes.reverse()) {
60
- host.providers.unregister(type);
60
+ for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {
61
+ host.providers.unregister(registeredProviderTypes[i]);
61
62
  }
62
63
  throw setupErr;
63
64
  }
@@ -66,17 +67,17 @@ async function applyWrongStackPack(host, pack, opts = {}) {
66
67
  pack,
67
68
  owner,
68
69
  async teardown() {
69
- for (const unregister of unregisterExtensions.reverse()) {
70
- unregister();
70
+ for (let i = unregisterExtensions.length - 1; i >= 0; i--) {
71
+ unregisterExtensions[i]();
71
72
  }
72
- for (const name of registeredCommandNames.reverse()) {
73
- host.slashCommands.unregister(name);
73
+ for (let i = registeredCommandNames.length - 1; i >= 0; i--) {
74
+ host.slashCommands.unregister(registeredCommandNames[i]);
74
75
  }
75
- for (const name of registeredToolNames.reverse()) {
76
- host.tools.unregister(name);
76
+ for (let i = registeredToolNames.length - 1; i >= 0; i--) {
77
+ host.tools.unregister(registeredToolNames[i]);
77
78
  }
78
- for (const type of registeredProviderTypes.reverse()) {
79
- host.providers.unregister(type);
79
+ for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {
80
+ host.providers.unregister(registeredProviderTypes[i]);
80
81
  }
81
82
  if (pack.teardown) {
82
83
  if (!opts.api) {
@@ -95,7 +96,8 @@ async function applyWrongStackPacks(host, packs, opts = {}) {
95
96
  }
96
97
  return applied;
97
98
  } catch (err) {
98
- for (const mounted of applied.reverse()) {
99
+ for (let i = applied.length - 1; i >= 0; i--) {
100
+ const mounted = applied[i];
99
101
  await mounted.teardown().catch((teardownErr) => {
100
102
  const detail = teardownErr instanceof Error ? teardownErr.message : String(teardownErr);
101
103
  process.emitWarning(
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/host.ts"],"names":[],"mappings":"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","file":"host.js","sourcesContent":["import type {\n Agent,\n Context,\n EventBus,\n ExtensionRegistry,\n PluginAPI,\n ProviderRegistry,\n SessionWriter,\n SlashCommandRegistry,\n ToolRegistry,\n} from '@wrongstack/core';\nimport type { WrongStackPack } from './pack.js';\n\nexport interface RuntimeHost {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry | undefined;\n shutdown(): Promise<void>;\n}\n\nexport interface RuntimeHostParts {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry | undefined;\n shutdown?: (() => void | Promise<void>) | undefined;\n}\n\nexport function createRuntimeHostFromParts(parts: RuntimeHostParts): RuntimeHost {\n return {\n agent: parts.agent,\n context: parts.context,\n events: parts.events,\n tools: parts.tools,\n providers: parts.providers,\n slashCommands: parts.slashCommands,\n session: parts.session,\n extensions: parts.extensions,\n async shutdown() {\n await parts.shutdown?.();\n },\n };\n}\n\nexport interface ApplyPackOptions {\n owner?: string | undefined;\n api?: PluginAPI | undefined;\n}\n\nexport interface AppliedPack {\n pack: WrongStackPack;\n owner: string;\n teardown(): Promise<void>;\n}\n\nexport async function applyWrongStackPack(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry | undefined;\n },\n pack: WrongStackPack,\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack> {\n const owner = opts.owner ?? pack.name;\n const unregisterExtensions: Array<() => void> = [];\n\n // Track registered tool names, command names, and provider types so teardown\n // can reverse everything in registration order.\n const registeredToolNames: string[] = [];\n const registeredCommandNames: string[] = [];\n const registeredProviderTypes: string[] = [];\n\n if (pack.tools) {\n const tools = [...pack.tools];\n host.tools.registerAllOrThrow(tools, owner);\n for (const t of tools) registeredToolNames.push(t.name);\n }\n if (pack.providers) {\n const providers = [...pack.providers];\n host.providers.registerAll(providers);\n for (const p of providers) registeredProviderTypes.push(p.type);\n }\n if (pack.slashCommands) {\n const cmds = [...pack.slashCommands];\n host.slashCommands.registerAll(cmds, owner);\n for (const c of cmds) registeredCommandNames.push(c.name);\n }\n if (pack.extensions && host.extensions) {\n for (const ext of pack.extensions) {\n unregisterExtensions.push(host.extensions.register(ext));\n }\n }\n\n // If setup() throws after registration, roll back everything we registered above.\n // This makes applyWrongStackPack() transactional from the caller's perspective —\n // either the pack is fully applied or it is not.\n if (pack.setup) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines setup() but no PluginAPI was provided`);\n }\n try {\n await pack.setup(opts.api);\n } catch (setupErr) {\n // Roll back in reverse order: extensions first, then commands,\n // then tools, then providers. Extensions are unregistered before\n // tools/commands because extensions may depend on those capabilities;\n // tearing them down first avoids dangling refs.\n for (const unregister of unregisterExtensions.reverse()) {\n unregister();\n }\n for (const name of registeredCommandNames.reverse()) {\n host.slashCommands.unregister(name);\n }\n for (const name of registeredToolNames.reverse()) {\n host.tools.unregister(name);\n }\n for (const type of registeredProviderTypes.reverse()) {\n host.providers.unregister(type);\n }\n throw setupErr;\n }\n }\n\n return {\n pack,\n owner,\n async teardown() {\n for (const unregister of unregisterExtensions.reverse()) {\n unregister();\n }\n // Unregister commands, tools, and providers so the same pack can be\n // re-loaded cleanly without name/type conflicts.\n for (const name of registeredCommandNames.reverse()) {\n host.slashCommands.unregister(name);\n }\n for (const name of registeredToolNames.reverse()) {\n host.tools.unregister(name);\n }\n for (const type of registeredProviderTypes.reverse()) {\n host.providers.unregister(type);\n }\n if (pack.teardown) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines teardown() but no PluginAPI was provided`);\n }\n await pack.teardown(opts.api);\n }\n },\n };\n}\n\nexport async function applyWrongStackPacks(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry | undefined;\n },\n packs: readonly WrongStackPack[],\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack[]> {\n const applied: AppliedPack[] = [];\n try {\n for (const pack of packs) {\n applied.push(await applyWrongStackPack(host, pack, opts));\n }\n return applied;\n } catch (err) {\n // Roll back already-mounted packs. Surface teardown failures via\n // process.emitWarning so they don't mask the original error but\n // remain visible — a silent teardown failure can leave state\n // half-initialized in ways that make the next run fail mysteriously.\n for (const mounted of applied.reverse()) {\n await mounted.teardown().catch((teardownErr) => {\n const detail = teardownErr instanceof Error ? teardownErr.message : String(teardownErr);\n process.emitWarning(\n `Pack teardown during error rollback failed: ${detail}`,\n 'PackRollbackWarning',\n );\n });\n }\n throw err;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/host.ts"],"names":[],"mappings":"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","file":"host.js","sourcesContent":["import type {\n Agent,\n Context,\n EventBus,\n ExtensionRegistry,\n PluginAPI,\n ProviderRegistry,\n SessionWriter,\n SlashCommandRegistry,\n ToolRegistry,\n} from '@wrongstack/core';\nimport type { WrongStackPack } from './pack.js';\n\nexport interface RuntimeHost {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry | undefined;\n shutdown(): Promise<void>;\n}\n\nexport interface RuntimeHostParts {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry | undefined;\n shutdown?: (() => void | Promise<void>) | undefined;\n}\n\nexport function createRuntimeHostFromParts(parts: RuntimeHostParts): RuntimeHost {\n return {\n agent: parts.agent,\n context: parts.context,\n events: parts.events,\n tools: parts.tools,\n providers: parts.providers,\n slashCommands: parts.slashCommands,\n session: parts.session,\n extensions: parts.extensions,\n async shutdown() {\n await parts.shutdown?.();\n },\n };\n}\n\nexport interface ApplyPackOptions {\n owner?: string | undefined;\n api?: PluginAPI | undefined;\n}\n\nexport interface AppliedPack {\n pack: WrongStackPack;\n owner: string;\n teardown(): Promise<void>;\n}\n\nexport async function applyWrongStackPack(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry | undefined;\n },\n pack: WrongStackPack,\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack> {\n const owner = opts.owner ?? pack.name;\n const unregisterExtensions: Array<() => void> = [];\n\n // Track registered tool names, command names, and provider types so teardown\n // can reverse everything in registration order.\n const registeredToolNames: string[] = [];\n const registeredCommandNames: string[] = [];\n const registeredProviderTypes: string[] = [];\n\n if (pack.tools) {\n const tools = [...pack.tools];\n host.tools.registerAllOrThrow(tools, owner);\n for (const t of tools) registeredToolNames.push(t.name);\n }\n if (pack.providers) {\n const providers = [...pack.providers];\n host.providers.registerAll(providers);\n for (const p of providers) registeredProviderTypes.push(p.type);\n }\n if (pack.slashCommands) {\n const cmds = [...pack.slashCommands];\n host.slashCommands.registerAll(cmds, owner);\n // SlashCommandRegistry stores plugin-owned commands under `${owner}:${name}`;\n // track the real lookup key so teardown can unregister them.\n for (const c of cmds)\n registeredCommandNames.push(owner === 'core' ? c.name : `${owner}:${c.name}`);\n }\n if (pack.extensions && host.extensions) {\n for (const ext of pack.extensions) {\n unregisterExtensions.push(host.extensions.register(ext));\n }\n }\n\n // If setup() throws after registration, roll back everything we registered above.\n // This makes applyWrongStackPack() transactional from the caller's perspective —\n // either the pack is fully applied or it is not.\n if (pack.setup) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines setup() but no PluginAPI was provided`);\n }\n try {\n await pack.setup(opts.api);\n } catch (setupErr) {\n // Roll back in reverse order: extensions first, then commands,\n // then tools, then providers. Extensions are unregistered before\n // tools/commands because extensions may depend on those capabilities;\n // tearing them down first avoids dangling refs.\n for (let i = unregisterExtensions.length - 1; i >= 0; i--) {\n unregisterExtensions[i]!();\n }\n for (let i = registeredCommandNames.length - 1; i >= 0; i--) {\n host.slashCommands.unregister(registeredCommandNames[i]!);\n }\n for (let i = registeredToolNames.length - 1; i >= 0; i--) {\n host.tools.unregister(registeredToolNames[i]!);\n }\n for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {\n host.providers.unregister(registeredProviderTypes[i]!);\n }\n throw setupErr;\n }\n }\n\n return {\n pack,\n owner,\n async teardown() {\n for (let i = unregisterExtensions.length - 1; i >= 0; i--) {\n unregisterExtensions[i]!();\n }\n // Unregister commands, tools, and providers so the same pack can be\n // re-loaded cleanly without name/type conflicts.\n for (let i = registeredCommandNames.length - 1; i >= 0; i--) {\n host.slashCommands.unregister(registeredCommandNames[i]!);\n }\n for (let i = registeredToolNames.length - 1; i >= 0; i--) {\n host.tools.unregister(registeredToolNames[i]!);\n }\n for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {\n host.providers.unregister(registeredProviderTypes[i]!);\n }\n if (pack.teardown) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines teardown() but no PluginAPI was provided`);\n }\n await pack.teardown(opts.api);\n }\n },\n };\n}\n\nexport async function applyWrongStackPacks(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry | undefined;\n },\n packs: readonly WrongStackPack[],\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack[]> {\n const applied: AppliedPack[] = [];\n try {\n for (const pack of packs) {\n applied.push(await applyWrongStackPack(host, pack, opts));\n }\n return applied;\n } catch (err) {\n // Roll back already-mounted packs. Surface teardown failures via\n // process.emitWarning so they don't mask the original error but\n // remain visible — a silent teardown failure can leave state\n // half-initialized in ways that make the next run fail mysteriously.\n for (let i = applied.length - 1; i >= 0; i--) {\n const mounted = applied[i]!;\n await mounted.teardown().catch((teardownErr) => {\n const detail = teardownErr instanceof Error ? teardownErr.message : String(teardownErr);\n process.emitWarning(\n `Pack teardown during error rollback failed: ${detail}`,\n 'PackRollbackWarning',\n );\n });\n }\n throw err;\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
- export * from '@wrongstack/core/defaults';
2
- export { DefaultPathResolver, DefaultTokenCounter } from '@wrongstack/core/infrastructure';
3
1
  import { Config, WstackPaths, Logger, ModelsRegistry, EventBus, Tool, DefaultSystemPromptBuilderOptions, Container, ProviderRegistry, ToolRegistry, SessionWriter, AgentFactory } from '@wrongstack/core';
4
2
  export { DefaultSystemPromptBuilder, DefaultSystemPromptBuilderOptions } from '@wrongstack/core';
5
- export { WrongStackPack } from './pack.js';
6
- export { AppliedPack, ApplyPackOptions, RuntimeHost, RuntimeHostParts, applyWrongStackPack, applyWrongStackPacks, createRuntimeHostFromParts } from './host.js';
7
- export { ImageInputUnsupportedError, ToolVisionAdapterOptions, VisionAdapter, VisionAdapterInput, VisionAdapters, VisionRoutingOptions, VisionRoutingResult, VisionUrlBlockedError, createToolVisionAdapters, routeImagesForModel } from './vision.js';
3
+ export * from '@wrongstack/core/defaults';
4
+ export { DefaultPathResolver, DefaultTokenCounter } from '@wrongstack/core/infrastructure';
8
5
  export { ClipboardImage, readClipboardImage, readClipboardText } from './clipboard.js';
6
+ export { AppliedPack, ApplyPackOptions, RuntimeHost, RuntimeHostParts, applyWrongStackPack, applyWrongStackPacks, createRuntimeHostFromParts } from './host.js';
7
+ export { ProbeOptions, ProbeResult, probeLocalLlm } from './probe.js';
8
+ export { WrongStackPack } from './pack.js';
9
+ export { ImageInputUnsupportedError, ToolVisionAdapterOptions, VISION_IMAGE_KEYS, VISION_MEDIA_TYPE_KEYS, VISION_PATH_KEYS, VISION_PROMPT_KEYS, VisionAdapter, VisionAdapterInput, VisionAdapters, VisionRoutingOptions, VisionRoutingResult, VisionUrlBlockedError, createToolVisionAdapters, routeImagesForModel } from './vision.js';
9
10
 
10
11
  interface CreateContainerOptions {
11
12
  config: Config;
@@ -49,6 +50,8 @@ interface LightSubagentFactoryDeps {
49
50
  providerRegistry: ProviderRegistry;
50
51
  /** Full tool registry — each subagent gets an isolated clone of it. */
51
52
  toolRegistry: ToolRegistry;
53
+ /** Optional models catalog used to gate per-subagent reasoning settings. */
54
+ modelsRegistry?: ModelsRegistry | undefined;
52
55
  /** Parent session writer; subagent events are interleaved via a guarded shim. */
53
56
  session: SessionWriter;
54
57
  /** Project root anchor. */