@oxbit/cli 0.7.1 → 0.8.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.
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync, constants as constants$1, watch } from "node:fs";
2
+ import { realpathSync, constants as constants$1, watch, createWriteStream } from "node:fs";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import * as fs from "node:fs/promises";
5
5
  import path from "node:path";
6
- import { spawn, execFileSync } from "node:child_process";
7
- import { parseArgs } from "node:util";
6
+ import { spawn, execFile, execFileSync } from "node:child_process";
7
+ import { promisify, parseArgs } from "node:util";
8
8
  import http, { createServer } from "node:http";
9
9
  import os, { userInfo } from "node:os";
10
10
  import { createHash, randomUUID, randomBytes, timingSafeEqual } from "node:crypto";
@@ -24,6 +24,7 @@ import ts from "typescript";
24
24
  import ignore from "ignore";
25
25
  import * as Y from "yjs";
26
26
  import { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate, removeAwarenessStates } from "y-protocols/awareness";
27
+ import { pipeline } from "node:stream/promises";
27
28
  import { createSocket } from "node:dgram";
28
29
  const REQUIRED_NODE_MAJOR = 24;
29
30
  function nodeVersionError(version = process.versions.node) {
@@ -35,8 +36,8 @@ if (failure) {
35
36
  process.stderr.write(failure + "\n");
36
37
  process.exit(1);
37
38
  }
38
- function setting(name, env = process.env) {
39
- return env[`OXBIT_${name}`];
39
+ function setting(name, env2 = process.env) {
40
+ return env2[`OXBIT_${name}`];
40
41
  }
41
42
  function workspaceDataDir(root, home = os.homedir()) {
42
43
  const id = createHash("sha256").update(root).digest("hex").slice(0, 24);
@@ -54,8 +55,8 @@ const MAX_BUFFER_BYTES = 1024 * 1024;
54
55
  const READ_CHUNK_BYTES = 512 * 1024;
55
56
  const RUNTIME_SERVICE_TYPE = "_oxbit._tcp";
56
57
  class RpcError extends Error {
57
- constructor(code, message, data) {
58
- super(message);
58
+ constructor(code, message2, data) {
59
+ super(message2);
59
60
  this.code = code;
60
61
  this.data = data;
61
62
  this.name = "RpcError";
@@ -275,14 +276,14 @@ class WorkspaceFiles {
275
276
  const full = await this.resolve(relative);
276
277
  const entries = await fs.readdir(full, { withFileTypes: true });
277
278
  const result = [];
278
- for (const entry of entries) {
279
- if (entry.name === ".git" || entry.name.startsWith(".oxbit-tmp-"))
279
+ for (const entry2 of entries) {
280
+ if (entry2.name === ".git" || entry2.name.startsWith(".oxbit-tmp-"))
280
281
  continue;
281
- const child = path.posix.join(relative, entry.name);
282
+ const child = path.posix.join(relative, entry2.name);
282
283
  try {
283
284
  const target = await this.resolve(child);
284
285
  const stat = await fs.stat(target);
285
- if (entry.isSymbolicLink() && stat.isDirectory()) {
286
+ if (entry2.isSymbolicLink() && stat.isDirectory()) {
286
287
  let ancestor = path.resolve(this.root, relative || "."), cycle = false;
287
288
  while (this.inside(ancestor)) {
288
289
  if (await fs.realpath(ancestor) === target) {
@@ -297,7 +298,7 @@ class WorkspaceFiles {
297
298
  if (stat.isFile() || stat.isDirectory())
298
299
  result.push({
299
300
  path: child,
300
- name: entry.name,
301
+ name: entry2.name,
301
302
  kind: stat.isDirectory() ? "directory" : "file",
302
303
  size: stat.size,
303
304
  readonly: await this.readonly(target)
@@ -586,8 +587,8 @@ function trackProcess(pid, onExit) {
586
587
  function trackChild(child) {
587
588
  trackProcess(child.pid, (done) => child.once("close", done));
588
589
  }
589
- function resolveTerminalShell(env = process.env, platform = process.platform) {
590
- let shell = env.SHELL;
590
+ function resolveTerminalShell(env2 = process.env, platform = process.platform) {
591
+ let shell = env2.SHELL;
591
592
  if (!shell && platform !== "win32") {
592
593
  try {
593
594
  shell = userInfo().shell || void 0;
@@ -932,11 +933,11 @@ const $id$1 = "https://oxbit.dev/schemas/settings.v1.schema.json";
932
933
  const title$2 = "Oxbit Settings";
933
934
  const description$2 = "User, private workspace and repository settings. Literal dotted IDs; objects merge recursively, arrays and scalar values replace. Unrecognized extension settings are preserved. Null is a value, not a reset, and must be valid for its setting.";
934
935
  const type$2 = "object";
935
- const properties$2 = /* @__PURE__ */ JSON.parse(`{"$schema":{"type":"string","description":"JSON Schema URI. Ignored when merging effective preferences."},"agentACP.provider":{"title":"Default agent","type":"string","default":"codex","enum":["codex","cursor","amp"]},"agentACP.codex.command":{"title":"Codex ACP executable","type":"string","default":"npx"},"agentACP.codex.args":{"title":"Codex ACP arguments (JSON array)","type":"string","default":"[\\"-y\\",\\"@agentclientprotocol/codex-acp@2.2.2\\"]"},"agentACP.cursor.command":{"title":"Cursor ACP executable","type":"string","default":"agent"},"agentACP.cursor.args":{"title":"Cursor ACP arguments (JSON array)","type":"string","default":"[\\"acp\\"]"},"agentACP.amp.command":{"title":"Amp Agent ACP executable","type":"string","default":"npx"},"agentACP.amp.args":{"title":"Amp Agent ACP arguments (JSON array)","type":"string","default":"[\\"-y\\",\\"amp-acp@0.10.0\\"]"},"workbench.keymap":{"title":"Keymap","description":"Keyboard layout applied on top of the Oxbit defaults. User keybindings always win.","type":"string","default":"default","enum":["default","vscode","jetbrains","macos","sublime","atom","visual-studio","emacs"]},"runtime.keepAlive":{"title":"Keep Runtime Alive","description":"How long a runtime Oxbit started keeps running after the last client disconnects or its connection heartbeats stop. Terminals and tasks continue while it runs.","type":"string","default":"75s","enum":["75s","15m","1h","8h","untilStopped"]},"runtime.autoReconnect":{"title":"Reconnect Automatically","description":"Reconnect to the runtime after the connection drops, including when the iOS app returns to the foreground.","type":"boolean","default":true},"workbench.tooltipDelay":{"title":"Tooltip Delay","description":"Delay in milliseconds before showing a tooltip on hover. Keyboard focus shows tooltips immediately.","type":"number","default":400,"minimum":0,"maximum":5000},"workbench.colorTheme":{"title":"Color Theme","description":"Specifies the color theme used in the workbench.","type":"string","default":"Graphite (dark)"},"workbench.density":{"title":"Layout Density","description":"Controls row heights, padding and label sizes across the workbench.","type":"string","default":"compact","enum":["compact","comfortable"]},"workbench.sidebarLocation":{"title":"Sidebar Location","description":"Where the primary sidebar is docked.","type":"string","default":"left","enum":["left","right"]},"editor.fontFamily":{"title":"Font Family","description":"Controls the font family of the editor.","type":"string","default":"'JetBrains Mono', ui-monospace, monospace"},"editor.fontSize":{"title":"Font Size","description":"Font size in pixels.","type":"number","default":13,"minimum":8,"maximum":32},"editor.lineHeight":{"title":"Line Height","description":"Line height in pixels. 0 uses 1.55 × font size.","type":"number","default":20,"minimum":0,"maximum":60},"editor.fontLigatures":{"title":"Font Ligatures","description":"Enables font ligatures when the font supports them.","type":"boolean","default":false},"editor.tabSize":{"title":"Tab Size","description":"The number of spaces a tab is equal to.","type":"number","default":2,"minimum":1,"maximum":8},"editor.insertSpaces":{"title":"Insert Spaces","description":"Insert spaces when pressing Tab.","type":"boolean","default":true},"editor.detectIndentation":{"title":"Detect Indentation","description":"Detect Tab Size and Insert Spaces from file contents on open.","type":"boolean","default":true},"editor.renderIndentGuides":{"title":"Indentation Guides","description":"Render vertical guides for each indent level.","type":"boolean","default":true},"editor.wordWrap":{"title":"Word Wrap","description":"Controls how lines should wrap.","type":"string","default":"off","enum":["off","on","bounded"]},"editor.renderWhitespace":{"title":"Render Whitespace","description":"Controls how the editor renders whitespace characters.","type":"string","default":"selection","enum":["none","boundary","selection","all"]},"editor.cursorBlinking":{"title":"Cursor Blinking","description":"Cursor animation style.","type":"string","default":"blink","enum":["blink","smooth","phase","solid"]},"editor.minimap":{"title":"Minimap","description":"Show a minimap of the document.","type":"boolean","default":false},"files.autoSave":{"title":"Auto Save","description":"Controls auto save of editors that have unsaved changes.","type":"string","default":"off","enum":["off","afterDelay","onFocusChange","onWindowChange"]},"files.autoSaveDelay":{"title":"Auto Save Delay","description":"Delay in milliseconds after which an editor is saved automatically. Applies when Auto Save is afterDelay.","type":"number","default":1000,"minimum":100,"maximum":60000},"files.trimTrailingWhitespace":{"title":"Trim Trailing Whitespace","description":"Remove trailing whitespace when saving.","type":"boolean","default":true},"editor.formatOnSave":{"title":"Format On Save","description":"Format a file on save. A formatter must be available.","type":"boolean","default":false},"editor.defaultFormatter":{"title":"Default Formatter","description":"Formatter used when several are available.","type":"string","default":"oxbit.prettier","anyOf":[{"enum":["oxbit.prettier","oxbit.builtin-ts"]},{"type":"string"}]},"terminal.fontSize":{"title":"Terminal Font Size","description":"Font size in pixels for the terminal.","type":"number","default":12,"minimum":8,"maximum":32},"terminal.scrollback":{"title":"Scrollback","description":"Maximum number of lines kept in the terminal buffer.","type":"number","default":5000,"minimum":100,"maximum":100000},"terminal.confirmOnKill":{"title":"Confirm On Kill","description":"Ask before killing a terminal with a running process.","type":"boolean","default":true},"scm.autoFetch":{"title":"Auto Fetch","description":"Periodically fetch from the default remote.","type":"boolean","default":true},"scm.diffLayout":{"title":"Diff Layout","description":"Default layout for diff editors.","type":"string","default":"side-by-side","enum":["side-by-side","inline"]},"files.associations":{"title":"File Associations","description":"Map filename or workspace-relative glob patterns to language IDs. Explicit associations take precedence over built-in detection.","type":"object","default":{},"propertyNames":{"minLength":1,"maxLength":1024},"additionalProperties":{"type":"string","pattern":"^[\\\\w+-]+$"}},"languageServers":{"title":"Language Servers","description":"Configure enabled state, selectors, rootMarkers, executable, args, env, initializationOptions, settings, and priority by server ID. Servers install on first use in a trusted runtime. License keys belong in the runtime-local credential file.","type":"object","default":{},"propertyNames":{"pattern":"^[a-zA-Z0-9._-]+$"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable this language server."},"selectors":{"type":"array","items":{"type":"object","properties":{"language":{"type":"string"},"pattern":{"type":"string"},"scheme":{"const":"file"}},"additionalProperties":false,"minProperties":1}},"rootMarkers":{"type":"array","items":{"type":"string","pattern":"^(?!/)(?!.*\\\\\\\\)(?!.*(?:^|/)\\\\.\\\\.(?:/|$))[^\\\\u0000]+$","minLength":1}},"executable":{"type":"string","pattern":"^[^\\\\u0000]*$","minLength":1},"args":{"type":"array","items":{"type":"string","pattern":"^[^\\\\u0000]*$"}},"env":{"type":"object","propertyNames":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"additionalProperties":{"type":"string","pattern":"^[^\\\\u0000]*$"}},"initializationOptions":{"type":"object","additionalProperties":true,"description":"Server-specific initialization options. Store license credentials in the runtime credential file."},"settings":{"type":"object","additionalProperties":true,"description":"Server-specific configuration; nested objects merge across files."},"priority":{"type":"number"}},"additionalProperties":false}},"ui.fontFamily":{"title":"UI Font Family","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":""},"ui.fontSize":{"title":"UI Font Size","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":12,"minimum":8,"maximum":72},"ui.fontWeight":{"title":"UI Font Weight","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":400,"minimum":100,"maximum":900},"ui.fontStyle":{"title":"UI Font Style","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":"normal","enum":["normal","italic","oblique"]},"ui.lineHeight":{"title":"UI Line Height","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":1.4,"minimum":1,"maximum":3},"ui.letterSpacing":{"title":"UI Letter Spacing","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":0,"minimum":-3,"maximum":10},"ui.fontLigatures":{"title":"UI Font Ligatures","description":"Overrides theme typography. Reset restores the theme value.","type":"boolean","default":false},"terminal.fontFamily":{"title":"Terminal Font Family","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":""},"terminal.fontWeight":{"title":"Terminal Font Weight","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":400,"minimum":100,"maximum":900},"terminal.fontStyle":{"title":"Terminal Font Style","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":"normal","enum":["normal","italic","oblique"]},"terminal.lineHeight":{"title":"Terminal Line Height","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":1,"minimum":1,"maximum":3},"terminal.letterSpacing":{"title":"Terminal Letter Spacing","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":0,"minimum":-3,"maximum":10},"terminal.fontLigatures":{"title":"Terminal Font Ligatures","description":"Overrides theme typography. Reset restores the theme value.","type":"boolean","default":false},"workbench.iconTheme":{"title":"File Icon Theme","description":"Select a locally installed icon theme. Unavailable selections use Oxbit defaults until restored.","type":"string","default":"oxbit.default"},"workbench.productIconTheme":{"title":"Product Icon Theme","description":"Select a locally installed icon theme. Unavailable selections use Oxbit defaults until restored.","type":"string","default":"oxbit.default"},"editor.semanticHighlighting":{"title":"Semantic Highlighting","description":"Color symbols using language server information.","type":"boolean","default":true},"editor.inlayHints.types":{"title":"Type Inlay Hints","description":"Show supported type hints in the visible editor.","type":"boolean","default":true},"editor.inlayHints.parameters":{"title":"Parameter Inlay Hints","description":"Show supported parameter hints in the visible editor.","type":"boolean","default":true},"editor.largeFileIntelligence":{"title":"Large File Intelligence","description":"Allow full semantic highlighting and inlay hints for files larger than 1 MiB.","type":"boolean","default":false},"workbench.locale":{"title":"Display Language","type":"string","default":"en","enum":["en","de","es","ja","zh"]},"workbench.reducedMotion":{"title":"Reduced Motion","type":"boolean","default":false},"desktop.projects.openBehavior":{"title":"Open projects","type":"string","default":"currentWindow","enum":["currentWindow","newWindow"]},"desktop.tools.gitPath":{"title":"git executable path","type":"string","default":""},"desktop.tools.ghPath":{"title":"gh executable path","type":"string","default":""},"project.intelligence":{"type":"object","properties":{"enabled":{"type":"boolean","default":true,"description":"Index project dependencies, imports and related files."},"exclude":{"type":"array","items":{"type":"string","maxLength":1024},"default":[]},"maxFiles":{"type":"integer","minimum":1,"maximum":100000,"default":20000},"maxFileBytes":{"type":"integer","minimum":1,"maximum":5242880,"default":1048576}},"additionalProperties":false,"title":"Project Intelligence","description":"Partial overrides of project.json intelligence. Objects merge across files."},"project.schemas":{"type":"object","properties":{"catalog":{"type":"boolean","default":true,"description":"Discover file schemas through SchemaStore. Bundled Oxbit settings support remains available."},"download":{"type":"boolean","default":true,"description":"Allow remote schema downloads; cached and bundled schemas work offline."},"associations":{"type":"array","maxItems":1000,"default":[],"items":{"type":"object","properties":{"url":{"type":"string","minLength":1,"description":"Schema URL or workspace file URI."},"fileMatch":{"type":"array","items":{"type":"string","maxLength":1024}},"schema":{"type":["object","boolean"],"description":"An inline JSON Schema."}},"additionalProperties":false,"anyOf":[{"required":["url"]},{"required":["schema"]}]}}},"additionalProperties":false,"title":"JSON Schemas","description":"Partial overrides of project.json schemas. Association arrays replace lower arrays."}}`);
936
+ const properties$2 = /* @__PURE__ */ JSON.parse(`{"$schema":{"type":"string","description":"JSON Schema URI. Ignored when merging effective preferences."},"agentACP.provider":{"title":"Default agent","description":"A built-in agent ID, an ACP Registry agent ID, or custom.","type":"string","default":"codex"},"agentACP.useModifierToSend":{"title":"Send with Ctrl/Cmd+Enter only","description":"Enter inserts a new line and Ctrl/Cmd+Enter sends the message.","type":"boolean","default":false},"agentACP.notifications":{"title":"Agent desktop notifications","description":"Show a system notification when the agent needs input or finishes while Oxbit is in the background.","type":"string","default":"whenHidden","enum":["whenHidden","never"]},"agentACP.codex.command":{"title":"Codex ACP executable","type":"string","default":"npx"},"agentACP.codex.args":{"title":"Codex ACP arguments (JSON array)","type":"string","default":"[\\"-y\\",\\"@agentclientprotocol/codex-acp@2.2.2\\"]"},"agentACP.claude.command":{"title":"Claude Agent executable","type":"string","default":"npx"},"agentACP.claude.args":{"title":"Claude Agent arguments (JSON array)","type":"string","default":"[\\"-y\\",\\"@agentclientprotocol/claude-agent-acp@0.89.1\\"]"},"agentACP.gemini.command":{"title":"Gemini CLI executable","type":"string","default":"npx"},"agentACP.gemini.args":{"title":"Gemini CLI arguments (JSON array)","type":"string","default":"[\\"-y\\",\\"@google/gemini-cli@0.63.0\\",\\"--acp\\"]"},"agentACP.copilot.command":{"title":"GitHub Copilot executable","type":"string","default":"npx"},"agentACP.copilot.args":{"title":"GitHub Copilot arguments (JSON array)","type":"string","default":"[\\"-y\\",\\"@github/copilot@1.0.95\\",\\"--acp\\"]"},"agentACP.cursor.command":{"title":"Cursor ACP executable","type":"string","default":"agent"},"agentACP.cursor.args":{"title":"Cursor ACP arguments (JSON array)","type":"string","default":"[\\"acp\\"]"},"agentACP.amp.command":{"title":"Amp Agent ACP executable","type":"string","default":"npx"},"agentACP.amp.args":{"title":"Amp Agent ACP arguments (JSON array)","type":"string","default":"[\\"-y\\",\\"amp-acp@0.10.0\\"]"},"agentACP.custom.name":{"title":"Custom agent name","type":"string","default":""},"agentACP.custom.command":{"title":"Custom agent executable","type":"string","default":""},"agentACP.custom.args":{"title":"Custom agent arguments (JSON array)","type":"string","default":"[]"},"workbench.keymap":{"title":"Keymap","description":"Keyboard layout applied on top of the Oxbit defaults. User keybindings always win.","type":"string","default":"default","enum":["default","vscode","jetbrains","macos","sublime","atom","visual-studio","emacs"]},"runtime.keepAlive":{"title":"Keep Runtime Alive","description":"How long a runtime Oxbit started keeps running after the last client disconnects or its connection heartbeats stop. Terminals and tasks continue while it runs.","type":"string","default":"75s","enum":["75s","15m","1h","8h","untilStopped"]},"runtime.autoReconnect":{"title":"Reconnect Automatically","description":"Reconnect to the runtime after the connection drops, including when the iOS app returns to the foreground.","type":"boolean","default":true},"workbench.tooltipDelay":{"title":"Tooltip Delay","description":"Delay in milliseconds before showing a tooltip on hover. Keyboard focus shows tooltips immediately.","type":"number","default":400,"minimum":0,"maximum":5000},"workbench.colorTheme":{"title":"Color Theme","description":"Specifies the color theme used in the workbench.","type":"string","default":"Graphite (dark)"},"workbench.density":{"title":"Layout Density","description":"Controls row heights, padding and label sizes across the workbench.","type":"string","default":"compact","enum":["compact","comfortable"]},"workbench.sidebarLocation":{"title":"Sidebar Location","description":"Where the primary sidebar is docked.","type":"string","default":"left","enum":["left","right"]},"editor.fontFamily":{"title":"Font Family","description":"Controls the font family of the editor.","type":"string","default":"'JetBrains Mono', ui-monospace, monospace"},"editor.fontSize":{"title":"Font Size","description":"Font size in pixels.","type":"number","default":13,"minimum":8,"maximum":32},"editor.lineHeight":{"title":"Line Height","description":"Line height in pixels. 0 uses 1.55 × font size.","type":"number","default":20,"minimum":0,"maximum":60},"editor.fontLigatures":{"title":"Font Ligatures","description":"Enables font ligatures when the font supports them.","type":"boolean","default":false},"editor.tabSize":{"title":"Tab Size","description":"The number of spaces a tab is equal to.","type":"number","default":2,"minimum":1,"maximum":8},"editor.insertSpaces":{"title":"Insert Spaces","description":"Insert spaces when pressing Tab.","type":"boolean","default":true},"editor.detectIndentation":{"title":"Detect Indentation","description":"Detect Tab Size and Insert Spaces from file contents on open.","type":"boolean","default":true},"editor.renderIndentGuides":{"title":"Indentation Guides","description":"Render vertical guides for each indent level.","type":"boolean","default":true},"editor.wordWrap":{"title":"Word Wrap","description":"Controls how lines should wrap.","type":"string","default":"off","enum":["off","on","bounded"]},"editor.renderWhitespace":{"title":"Render Whitespace","description":"Controls how the editor renders whitespace characters.","type":"string","default":"selection","enum":["none","boundary","selection","all"]},"editor.cursorBlinking":{"title":"Cursor Blinking","description":"Cursor animation style.","type":"string","default":"blink","enum":["blink","smooth","phase","solid"]},"editor.minimap":{"title":"Minimap","description":"Show a minimap of the document.","type":"boolean","default":false},"files.autoSave":{"title":"Auto Save","description":"Controls auto save of editors that have unsaved changes.","type":"string","default":"off","enum":["off","afterDelay","onFocusChange","onWindowChange"]},"files.autoSaveDelay":{"title":"Auto Save Delay","description":"Delay in milliseconds after which an editor is saved automatically. Applies when Auto Save is afterDelay.","type":"number","default":1000,"minimum":100,"maximum":60000},"files.trimTrailingWhitespace":{"title":"Trim Trailing Whitespace","description":"Remove trailing whitespace when saving.","type":"boolean","default":true},"editor.formatOnSave":{"title":"Format On Save","description":"Format a file on save. A formatter must be available.","type":"boolean","default":false},"editor.defaultFormatter":{"title":"Default Formatter","description":"Formatter used when several are available.","type":"string","default":"oxbit.prettier","anyOf":[{"enum":["oxbit.prettier","oxbit.builtin-ts"]},{"type":"string"}]},"terminal.fontSize":{"title":"Terminal Font Size","description":"Font size in pixels for the terminal.","type":"number","default":12,"minimum":8,"maximum":32},"terminal.scrollback":{"title":"Scrollback","description":"Maximum number of lines kept in the terminal buffer.","type":"number","default":5000,"minimum":100,"maximum":100000},"terminal.confirmOnKill":{"title":"Confirm On Kill","description":"Ask before killing a terminal with a running process.","type":"boolean","default":true},"scm.autoFetch":{"title":"Auto Fetch","description":"Periodically fetch from the default remote.","type":"boolean","default":true},"scm.diffLayout":{"title":"Diff Layout","description":"Default layout for diff editors.","type":"string","default":"side-by-side","enum":["side-by-side","inline"]},"files.associations":{"title":"File Associations","description":"Map filename or workspace-relative glob patterns to language IDs. Explicit associations take precedence over built-in detection.","type":"object","default":{},"propertyNames":{"minLength":1,"maxLength":1024},"additionalProperties":{"type":"string","pattern":"^[\\\\w+-]+$"}},"languageServers":{"title":"Language Servers","description":"Configure enabled state, selectors, rootMarkers, executable, args, env, initializationOptions, settings, and priority by server ID. Servers install on first use in a trusted runtime. License keys belong in the runtime-local credential file.","type":"object","default":{},"propertyNames":{"pattern":"^[a-zA-Z0-9._-]+$"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable this language server."},"selectors":{"type":"array","items":{"type":"object","properties":{"language":{"type":"string"},"pattern":{"type":"string"},"scheme":{"const":"file"}},"additionalProperties":false,"minProperties":1}},"rootMarkers":{"type":"array","items":{"type":"string","pattern":"^(?!/)(?!.*\\\\\\\\)(?!.*(?:^|/)\\\\.\\\\.(?:/|$))[^\\\\u0000]+$","minLength":1}},"executable":{"type":"string","pattern":"^[^\\\\u0000]*$","minLength":1},"args":{"type":"array","items":{"type":"string","pattern":"^[^\\\\u0000]*$"}},"env":{"type":"object","propertyNames":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"additionalProperties":{"type":"string","pattern":"^[^\\\\u0000]*$"}},"initializationOptions":{"type":"object","additionalProperties":true,"description":"Server-specific initialization options. Store license credentials in the runtime credential file."},"settings":{"type":"object","additionalProperties":true,"description":"Server-specific configuration; nested objects merge across files."},"priority":{"type":"number"}},"additionalProperties":false}},"ui.fontFamily":{"title":"UI Font Family","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":""},"ui.fontSize":{"title":"UI Font Size","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":12,"minimum":8,"maximum":72},"ui.fontWeight":{"title":"UI Font Weight","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":400,"minimum":100,"maximum":900},"ui.fontStyle":{"title":"UI Font Style","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":"normal","enum":["normal","italic","oblique"]},"ui.lineHeight":{"title":"UI Line Height","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":1.4,"minimum":1,"maximum":3},"ui.letterSpacing":{"title":"UI Letter Spacing","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":0,"minimum":-3,"maximum":10},"ui.fontLigatures":{"title":"UI Font Ligatures","description":"Overrides theme typography. Reset restores the theme value.","type":"boolean","default":false},"terminal.fontFamily":{"title":"Terminal Font Family","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":""},"terminal.fontWeight":{"title":"Terminal Font Weight","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":400,"minimum":100,"maximum":900},"terminal.fontStyle":{"title":"Terminal Font Style","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":"normal","enum":["normal","italic","oblique"]},"terminal.lineHeight":{"title":"Terminal Line Height","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":1,"minimum":1,"maximum":3},"terminal.letterSpacing":{"title":"Terminal Letter Spacing","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":0,"minimum":-3,"maximum":10},"terminal.fontLigatures":{"title":"Terminal Font Ligatures","description":"Overrides theme typography. Reset restores the theme value.","type":"boolean","default":false},"workbench.iconTheme":{"title":"File Icon Theme","description":"Select a locally installed icon theme. Unavailable selections use Oxbit defaults until restored.","type":"string","default":"oxbit.default"},"workbench.productIconTheme":{"title":"Product Icon Theme","description":"Select a locally installed icon theme. Unavailable selections use Oxbit defaults until restored.","type":"string","default":"oxbit.default"},"editor.semanticHighlighting":{"title":"Semantic Highlighting","description":"Color symbols using language server information.","type":"boolean","default":true},"editor.inlayHints.types":{"title":"Type Inlay Hints","description":"Show supported type hints in the visible editor.","type":"boolean","default":true},"editor.inlayHints.parameters":{"title":"Parameter Inlay Hints","description":"Show supported parameter hints in the visible editor.","type":"boolean","default":true},"editor.largeFileIntelligence":{"title":"Large File Intelligence","description":"Allow full semantic highlighting and inlay hints for files larger than 1 MiB.","type":"boolean","default":false},"workbench.locale":{"title":"Display Language","type":"string","default":"en","enum":["en","de","es","ja","zh"]},"workbench.reducedMotion":{"title":"Reduced Motion","type":"boolean","default":false},"desktop.projects.openBehavior":{"title":"Open projects","type":"string","default":"currentWindow","enum":["currentWindow","newWindow"]},"desktop.tools.gitPath":{"title":"git executable path","type":"string","default":""},"desktop.tools.ghPath":{"title":"gh executable path","type":"string","default":""},"project.intelligence":{"type":"object","properties":{"enabled":{"type":"boolean","default":true,"description":"Index project dependencies, imports and related files."},"exclude":{"type":"array","items":{"type":"string","maxLength":1024},"default":[]},"maxFiles":{"type":"integer","minimum":1,"maximum":100000,"default":20000},"maxFileBytes":{"type":"integer","minimum":1,"maximum":5242880,"default":1048576}},"additionalProperties":false,"title":"Project Intelligence","description":"Partial overrides of project.json intelligence. Objects merge across files."},"project.schemas":{"type":"object","properties":{"catalog":{"type":"boolean","default":true,"description":"Discover file schemas through SchemaStore. Bundled Oxbit settings support remains available."},"download":{"type":"boolean","default":true,"description":"Allow remote schema downloads; cached and bundled schemas work offline."},"associations":{"type":"array","maxItems":1000,"default":[],"items":{"type":"object","properties":{"url":{"type":"string","minLength":1,"description":"Schema URL or workspace file URI."},"fileMatch":{"type":"array","items":{"type":"string","maxLength":1024}},"schema":{"type":["object","boolean"],"description":"An inline JSON Schema."}},"additionalProperties":false,"anyOf":[{"required":["url"]},{"required":["schema"]}]}}},"additionalProperties":false,"title":"JSON Schemas","description":"Partial overrides of project.json schemas. Association arrays replace lower arrays."}}`);
936
937
  const propertyNames = { "anyOf": [{ "not": { "pattern": "^\\[.*\\]$" } }, { "pattern": "^\\[[^\\[\\]]+\\]$" }] };
937
938
  const patternProperties = { "^\\[[^\\[\\]]+\\]$": { "$ref": "#/definitions/languageOverrides" } };
938
939
  const additionalProperties$1 = true;
939
- const definitions$1 = { "languageOverrides": { "type": "object", "properties": { "agentACP.provider": { "$ref": "#/properties/agentACP.provider" }, "agentACP.codex.command": { "$ref": "#/properties/agentACP.codex.command" }, "agentACP.codex.args": { "$ref": "#/properties/agentACP.codex.args" }, "agentACP.cursor.command": { "$ref": "#/properties/agentACP.cursor.command" }, "agentACP.cursor.args": { "$ref": "#/properties/agentACP.cursor.args" }, "agentACP.amp.command": { "$ref": "#/properties/agentACP.amp.command" }, "agentACP.amp.args": { "$ref": "#/properties/agentACP.amp.args" }, "workbench.keymap": { "$ref": "#/properties/workbench.keymap" }, "runtime.keepAlive": { "$ref": "#/properties/runtime.keepAlive" }, "runtime.autoReconnect": { "$ref": "#/properties/runtime.autoReconnect" }, "workbench.tooltipDelay": { "$ref": "#/properties/workbench.tooltipDelay" }, "workbench.colorTheme": { "$ref": "#/properties/workbench.colorTheme" }, "workbench.density": { "$ref": "#/properties/workbench.density" }, "workbench.sidebarLocation": { "$ref": "#/properties/workbench.sidebarLocation" }, "editor.fontFamily": { "$ref": "#/properties/editor.fontFamily" }, "editor.fontSize": { "$ref": "#/properties/editor.fontSize" }, "editor.lineHeight": { "$ref": "#/properties/editor.lineHeight" }, "editor.fontLigatures": { "$ref": "#/properties/editor.fontLigatures" }, "editor.tabSize": { "$ref": "#/properties/editor.tabSize" }, "editor.insertSpaces": { "$ref": "#/properties/editor.insertSpaces" }, "editor.detectIndentation": { "$ref": "#/properties/editor.detectIndentation" }, "editor.renderIndentGuides": { "$ref": "#/properties/editor.renderIndentGuides" }, "editor.wordWrap": { "$ref": "#/properties/editor.wordWrap" }, "editor.renderWhitespace": { "$ref": "#/properties/editor.renderWhitespace" }, "editor.cursorBlinking": { "$ref": "#/properties/editor.cursorBlinking" }, "editor.minimap": { "$ref": "#/properties/editor.minimap" }, "files.autoSave": { "$ref": "#/properties/files.autoSave" }, "files.autoSaveDelay": { "$ref": "#/properties/files.autoSaveDelay" }, "files.trimTrailingWhitespace": { "$ref": "#/properties/files.trimTrailingWhitespace" }, "editor.formatOnSave": { "$ref": "#/properties/editor.formatOnSave" }, "editor.defaultFormatter": { "$ref": "#/properties/editor.defaultFormatter" }, "terminal.fontSize": { "$ref": "#/properties/terminal.fontSize" }, "terminal.scrollback": { "$ref": "#/properties/terminal.scrollback" }, "terminal.confirmOnKill": { "$ref": "#/properties/terminal.confirmOnKill" }, "scm.autoFetch": { "$ref": "#/properties/scm.autoFetch" }, "scm.diffLayout": { "$ref": "#/properties/scm.diffLayout" }, "files.associations": { "$ref": "#/properties/files.associations" }, "languageServers": { "$ref": "#/properties/languageServers" }, "ui.fontFamily": { "$ref": "#/properties/ui.fontFamily" }, "ui.fontSize": { "$ref": "#/properties/ui.fontSize" }, "ui.fontWeight": { "$ref": "#/properties/ui.fontWeight" }, "ui.fontStyle": { "$ref": "#/properties/ui.fontStyle" }, "ui.lineHeight": { "$ref": "#/properties/ui.lineHeight" }, "ui.letterSpacing": { "$ref": "#/properties/ui.letterSpacing" }, "ui.fontLigatures": { "$ref": "#/properties/ui.fontLigatures" }, "terminal.fontFamily": { "$ref": "#/properties/terminal.fontFamily" }, "terminal.fontWeight": { "$ref": "#/properties/terminal.fontWeight" }, "terminal.fontStyle": { "$ref": "#/properties/terminal.fontStyle" }, "terminal.lineHeight": { "$ref": "#/properties/terminal.lineHeight" }, "terminal.letterSpacing": { "$ref": "#/properties/terminal.letterSpacing" }, "terminal.fontLigatures": { "$ref": "#/properties/terminal.fontLigatures" }, "workbench.iconTheme": { "$ref": "#/properties/workbench.iconTheme" }, "workbench.productIconTheme": { "$ref": "#/properties/workbench.productIconTheme" }, "editor.semanticHighlighting": { "$ref": "#/properties/editor.semanticHighlighting" }, "editor.inlayHints.types": { "$ref": "#/properties/editor.inlayHints.types" }, "editor.inlayHints.parameters": { "$ref": "#/properties/editor.inlayHints.parameters" }, "editor.largeFileIntelligence": { "$ref": "#/properties/editor.largeFileIntelligence" }, "workbench.locale": { "$ref": "#/properties/workbench.locale" }, "workbench.reducedMotion": { "$ref": "#/properties/workbench.reducedMotion" }, "desktop.projects.openBehavior": { "$ref": "#/properties/desktop.projects.openBehavior" }, "desktop.tools.gitPath": { "$ref": "#/properties/desktop.tools.gitPath" }, "desktop.tools.ghPath": { "$ref": "#/properties/desktop.tools.ghPath" }, "project.intelligence": false, "project.schemas": false }, "additionalProperties": true, "description": "Settings for a language ID, such as [mdx] or [php]. Runtime project options belong at the top level.", "propertyNames": { "not": { "pattern": "^\\[.*\\]$" } } } };
940
+ const definitions$1 = { "languageOverrides": { "type": "object", "properties": { "agentACP.provider": { "$ref": "#/properties/agentACP.provider" }, "agentACP.useModifierToSend": { "$ref": "#/properties/agentACP.useModifierToSend" }, "agentACP.notifications": { "$ref": "#/properties/agentACP.notifications" }, "agentACP.codex.command": { "$ref": "#/properties/agentACP.codex.command" }, "agentACP.codex.args": { "$ref": "#/properties/agentACP.codex.args" }, "agentACP.claude.command": { "$ref": "#/properties/agentACP.claude.command" }, "agentACP.claude.args": { "$ref": "#/properties/agentACP.claude.args" }, "agentACP.gemini.command": { "$ref": "#/properties/agentACP.gemini.command" }, "agentACP.gemini.args": { "$ref": "#/properties/agentACP.gemini.args" }, "agentACP.copilot.command": { "$ref": "#/properties/agentACP.copilot.command" }, "agentACP.copilot.args": { "$ref": "#/properties/agentACP.copilot.args" }, "agentACP.cursor.command": { "$ref": "#/properties/agentACP.cursor.command" }, "agentACP.cursor.args": { "$ref": "#/properties/agentACP.cursor.args" }, "agentACP.amp.command": { "$ref": "#/properties/agentACP.amp.command" }, "agentACP.amp.args": { "$ref": "#/properties/agentACP.amp.args" }, "agentACP.custom.name": { "$ref": "#/properties/agentACP.custom.name" }, "agentACP.custom.command": { "$ref": "#/properties/agentACP.custom.command" }, "agentACP.custom.args": { "$ref": "#/properties/agentACP.custom.args" }, "workbench.keymap": { "$ref": "#/properties/workbench.keymap" }, "runtime.keepAlive": { "$ref": "#/properties/runtime.keepAlive" }, "runtime.autoReconnect": { "$ref": "#/properties/runtime.autoReconnect" }, "workbench.tooltipDelay": { "$ref": "#/properties/workbench.tooltipDelay" }, "workbench.colorTheme": { "$ref": "#/properties/workbench.colorTheme" }, "workbench.density": { "$ref": "#/properties/workbench.density" }, "workbench.sidebarLocation": { "$ref": "#/properties/workbench.sidebarLocation" }, "editor.fontFamily": { "$ref": "#/properties/editor.fontFamily" }, "editor.fontSize": { "$ref": "#/properties/editor.fontSize" }, "editor.lineHeight": { "$ref": "#/properties/editor.lineHeight" }, "editor.fontLigatures": { "$ref": "#/properties/editor.fontLigatures" }, "editor.tabSize": { "$ref": "#/properties/editor.tabSize" }, "editor.insertSpaces": { "$ref": "#/properties/editor.insertSpaces" }, "editor.detectIndentation": { "$ref": "#/properties/editor.detectIndentation" }, "editor.renderIndentGuides": { "$ref": "#/properties/editor.renderIndentGuides" }, "editor.wordWrap": { "$ref": "#/properties/editor.wordWrap" }, "editor.renderWhitespace": { "$ref": "#/properties/editor.renderWhitespace" }, "editor.cursorBlinking": { "$ref": "#/properties/editor.cursorBlinking" }, "editor.minimap": { "$ref": "#/properties/editor.minimap" }, "files.autoSave": { "$ref": "#/properties/files.autoSave" }, "files.autoSaveDelay": { "$ref": "#/properties/files.autoSaveDelay" }, "files.trimTrailingWhitespace": { "$ref": "#/properties/files.trimTrailingWhitespace" }, "editor.formatOnSave": { "$ref": "#/properties/editor.formatOnSave" }, "editor.defaultFormatter": { "$ref": "#/properties/editor.defaultFormatter" }, "terminal.fontSize": { "$ref": "#/properties/terminal.fontSize" }, "terminal.scrollback": { "$ref": "#/properties/terminal.scrollback" }, "terminal.confirmOnKill": { "$ref": "#/properties/terminal.confirmOnKill" }, "scm.autoFetch": { "$ref": "#/properties/scm.autoFetch" }, "scm.diffLayout": { "$ref": "#/properties/scm.diffLayout" }, "files.associations": { "$ref": "#/properties/files.associations" }, "languageServers": { "$ref": "#/properties/languageServers" }, "ui.fontFamily": { "$ref": "#/properties/ui.fontFamily" }, "ui.fontSize": { "$ref": "#/properties/ui.fontSize" }, "ui.fontWeight": { "$ref": "#/properties/ui.fontWeight" }, "ui.fontStyle": { "$ref": "#/properties/ui.fontStyle" }, "ui.lineHeight": { "$ref": "#/properties/ui.lineHeight" }, "ui.letterSpacing": { "$ref": "#/properties/ui.letterSpacing" }, "ui.fontLigatures": { "$ref": "#/properties/ui.fontLigatures" }, "terminal.fontFamily": { "$ref": "#/properties/terminal.fontFamily" }, "terminal.fontWeight": { "$ref": "#/properties/terminal.fontWeight" }, "terminal.fontStyle": { "$ref": "#/properties/terminal.fontStyle" }, "terminal.lineHeight": { "$ref": "#/properties/terminal.lineHeight" }, "terminal.letterSpacing": { "$ref": "#/properties/terminal.letterSpacing" }, "terminal.fontLigatures": { "$ref": "#/properties/terminal.fontLigatures" }, "workbench.iconTheme": { "$ref": "#/properties/workbench.iconTheme" }, "workbench.productIconTheme": { "$ref": "#/properties/workbench.productIconTheme" }, "editor.semanticHighlighting": { "$ref": "#/properties/editor.semanticHighlighting" }, "editor.inlayHints.types": { "$ref": "#/properties/editor.inlayHints.types" }, "editor.inlayHints.parameters": { "$ref": "#/properties/editor.inlayHints.parameters" }, "editor.largeFileIntelligence": { "$ref": "#/properties/editor.largeFileIntelligence" }, "workbench.locale": { "$ref": "#/properties/workbench.locale" }, "workbench.reducedMotion": { "$ref": "#/properties/workbench.reducedMotion" }, "desktop.projects.openBehavior": { "$ref": "#/properties/desktop.projects.openBehavior" }, "desktop.tools.gitPath": { "$ref": "#/properties/desktop.tools.gitPath" }, "desktop.tools.ghPath": { "$ref": "#/properties/desktop.tools.ghPath" }, "project.intelligence": false, "project.schemas": false }, "additionalProperties": true, "description": "Settings for a language ID, such as [mdx] or [php]. Runtime project options belong at the top level.", "propertyNames": { "not": { "pattern": "^\\[.*\\]$" } } } };
940
941
  const schema = {
941
942
  $schema: $schema$2,
942
943
  $id: $id$1,
@@ -1041,9 +1042,9 @@ function lspWatchPattern(glob, rootUri) {
1041
1042
  pattern = glob.pattern;
1042
1043
  const uri = typeof glob.baseUri === "string" ? glob.baseUri : glob.baseUri?.uri;
1043
1044
  if (!uri) return;
1044
- const url = new URL(uri);
1045
- if (url.protocol !== "file:" || url.host || url.search || url.hash) return;
1046
- base = decodeURIComponent(url.pathname).replace(/\/$/, "");
1045
+ const url2 = new URL(uri);
1046
+ if (url2.protocol !== "file:" || url2.host || url2.search || url2.hash) return;
1047
+ base = decodeURIComponent(url2.pathname).replace(/\/$/, "");
1047
1048
  if (pattern.startsWith("/")) return;
1048
1049
  } else return;
1049
1050
  if (base !== root && !base.startsWith(root + "/")) return;
@@ -1112,7 +1113,35 @@ const ACP_PROVIDERS = [
1112
1113
  command: "npx",
1113
1114
  args: ["-y", "@agentclientprotocol/codex-acp@2.2.2"],
1114
1115
  setup: "Uses the Codex ACP adapter. Sign in through an advertised authentication method, or use your existing Codex credentials.",
1115
- url: "https://github.com/agentclientprotocol/codex-acp"
1116
+ url: "https://github.com/agentclientprotocol/codex-acp",
1117
+ registryId: "codex-acp"
1118
+ },
1119
+ {
1120
+ id: "claude",
1121
+ name: "Claude Agent",
1122
+ command: "npx",
1123
+ args: ["-y", "@agentclientprotocol/claude-agent-acp@0.89.1"],
1124
+ setup: "Uses the Claude Agent ACP adapter. Sign in through an advertised authentication method, or use your existing Claude Code credentials.",
1125
+ url: "https://github.com/agentclientprotocol/claude-agent-acp",
1126
+ registryId: "claude-acp"
1127
+ },
1128
+ {
1129
+ id: "gemini",
1130
+ name: "Gemini CLI",
1131
+ command: "npx",
1132
+ args: ["-y", "@google/gemini-cli@0.63.0", "--acp"],
1133
+ setup: "Runs Gemini CLI in ACP mode. Sign in through an advertised authentication method, or set GEMINI_API_KEY in the runtime environment.",
1134
+ url: "https://github.com/google-gemini/gemini-cli",
1135
+ registryId: "gemini"
1136
+ },
1137
+ {
1138
+ id: "copilot",
1139
+ name: "GitHub Copilot",
1140
+ command: "npx",
1141
+ args: ["-y", "@github/copilot@1.0.95", "--acp"],
1142
+ setup: "Runs GitHub Copilot CLI in ACP mode. Sign in through an advertised authentication method, or run copilot and use /login first.",
1143
+ url: "https://github.com/github/copilot-cli",
1144
+ registryId: "github-copilot-cli"
1116
1145
  },
1117
1146
  {
1118
1147
  id: "cursor",
@@ -1120,7 +1149,8 @@ const ACP_PROVIDERS = [
1120
1149
  command: "agent",
1121
1150
  args: ["acp"],
1122
1151
  setup: "Install Cursor CLI and run agent login first. If your executable is cursor-agent, change the command below.",
1123
- url: "https://cursor.com/docs/cli/acp"
1152
+ url: "https://cursor.com/docs/cli/acp",
1153
+ registryId: "cursor"
1124
1154
  },
1125
1155
  {
1126
1156
  id: "amp",
@@ -1128,9 +1158,14 @@ const ACP_PROVIDERS = [
1128
1158
  command: "npx",
1129
1159
  args: ["-y", "amp-acp@0.10.0"],
1130
1160
  setup: "Uses the community Amp ACP adapter. Install Amp CLI and run amp login first. Set AMP_CLI_PATH in the runtime environment if needed.",
1131
- url: "https://github.com/tao12345666333/amp-acp"
1161
+ url: "https://github.com/tao12345666333/amp-acp",
1162
+ registryId: "amp-acp"
1132
1163
  }
1133
1164
  ];
1165
+ const ACP_CUSTOM_PROVIDER = "custom";
1166
+ const ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
1167
+ const acpPreset = (id) => ACP_PROVIDERS.find((provider) => provider.id === id);
1168
+ const acpBuiltinForRegistry = (registryId) => ACP_PROVIDERS.find((provider) => provider.registryId === registryId);
1134
1169
  const taskPhases = [
1135
1170
  "preinit",
1136
1171
  "init",
@@ -1167,29 +1202,29 @@ function settingsFile(layers, scope) {
1167
1202
  return { ...structuredClone(layers[scope]), ...Object.fromEntries(Object.entries(layers[scope === "user" ? "userLanguages" : "workspaceLanguages"]).filter(([, value]) => Object.keys(value).length).map(([language, value]) => [`[${language}]`, structuredClone(value)])) };
1168
1203
  }
1169
1204
  const SDK_VERSION = "1.0.0";
1170
- function invalid(message) {
1171
- throw new RpcError("INVALID_TASK_CONFIG", message);
1205
+ function invalid$1(message2) {
1206
+ throw new RpcError("INVALID_TASK_CONFIG", message2);
1172
1207
  }
1173
1208
  function object$2(value, label) {
1174
1209
  if (!value || typeof value !== "object" || Array.isArray(value))
1175
- invalid(`${label} must be an object`);
1210
+ invalid$1(`${label} must be an object`);
1176
1211
  return value;
1177
1212
  }
1178
1213
  function taskName(value) {
1179
1214
  if (typeof value !== "string" || !value.trim() || value.length > 120 || /[\x00-\x1f\x7f]/.test(value) || ["__proto__", "constructor", "prototype"].includes(value))
1180
- invalid("Task name must contain 1–120 printable characters");
1215
+ invalid$1("Task name must contain 1–120 printable characters");
1181
1216
  return value;
1182
1217
  }
1183
1218
  function stringMap(value, label) {
1184
1219
  const record = object$2(value, label);
1185
1220
  for (const [key, item] of Object.entries(record))
1186
1221
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || ["__proto__", "constructor", "prototype"].includes(key) || typeof item !== "string" || item.length > 32768 || item.includes("\0"))
1187
- invalid(`${label}: invalid environment entry ${key}`);
1222
+ invalid$1(`${label}: invalid environment entry ${key}`);
1188
1223
  return record;
1189
1224
  }
1190
1225
  function number(value, min, max, label) {
1191
1226
  if (!Number.isSafeInteger(value) || Number(value) < min || Number(value) > max)
1192
- invalid(`${label} must be an integer between ${min} and ${max}`);
1227
+ invalid$1(`${label} must be an integer between ${min} and ${max}`);
1193
1228
  }
1194
1229
  function port(value) {
1195
1230
  if (value === "auto") return value;
@@ -1206,14 +1241,14 @@ function hooks(value) {
1206
1241
  const data = object$2(value ?? {}, "worktree");
1207
1242
  for (const key of Object.keys(data))
1208
1243
  if (!taskPhases.includes(key))
1209
- invalid(`Unknown worktree phase: ${key}`);
1244
+ invalid$1(`Unknown worktree phase: ${key}`);
1210
1245
  for (const phase of taskPhases)
1211
1246
  if (data[phase] !== void 0) {
1212
1247
  const lines = Array.isArray(data[phase]) ? data[phase] : [data[phase]];
1213
1248
  if (lines.length > 128 || lines.some(
1214
1249
  (line) => typeof line !== "string" || line.length > 32768 || line.includes("\0")
1215
1250
  ))
1216
- invalid(`worktree.${phase} must be a script or array of scripts`);
1251
+ invalid$1(`worktree.${phase} must be a script or array of scripts`);
1217
1252
  }
1218
1253
  return Object.fromEntries(
1219
1254
  taskPhases.filter((phase) => data[phase] !== void 0).map((phase) => [phase, data[phase]])
@@ -1240,9 +1275,9 @@ function definition(value) {
1240
1275
  "stop"
1241
1276
  ]);
1242
1277
  for (const key of Object.keys(data))
1243
- if (!known.has(key)) invalid(`Unknown task field: ${key}`);
1278
+ if (!known.has(key)) invalid$1(`Unknown task field: ${key}`);
1244
1279
  if (typeof data.command !== "string" || data.command.length > 32768 || data.command.includes("\0") || !data.command.trim() && !data.dependsOn?.length)
1245
- invalid("A task needs a command or dependencies");
1280
+ invalid$1("A task needs a command or dependencies");
1246
1281
  for (const [key, choices] of Object.entries({
1247
1282
  type: ["command", "service"],
1248
1283
  execution: ["shell", "process"],
@@ -1250,39 +1285,39 @@ function definition(value) {
1250
1285
  group: ["build", "test"]
1251
1286
  }))
1252
1287
  if (data[key] !== void 0 && !choices.includes(data[key]))
1253
- invalid(`Invalid ${key}`);
1288
+ invalid$1(`Invalid ${key}`);
1254
1289
  for (const key of ["args", "dependsOn"])
1255
1290
  if (data[key] !== void 0 && (!Array.isArray(data[key]) || data[key].length > 128 || data[key].some(
1256
1291
  (v) => typeof v !== "string" || v.length > 32768 || v.includes("\0")
1257
1292
  )))
1258
- invalid(`${key} must be an array of strings`);
1293
+ invalid$1(`${key} must be an array of strings`);
1259
1294
  for (const key of ["cwd", "host", "hostname"])
1260
1295
  if (data[key] !== void 0 && (typeof data[key] !== "string" || data[key].length > 4096 || /[\x00-\x1f]/.test(data[key])))
1261
- invalid(`Invalid ${key}`);
1296
+ invalid$1(`Invalid ${key}`);
1262
1297
  if (data.env !== void 0) stringMap(data.env, "task.env");
1263
1298
  if (data.port !== void 0) port(data.port);
1264
1299
  if (data.ports !== void 0) {
1265
1300
  const entries = Object.entries(object$2(data.ports, "ports"));
1266
- if (entries.length > 16) invalid("Maximum 16 named ports per task");
1301
+ if (entries.length > 16) invalid$1("Maximum 16 named ports per task");
1267
1302
  for (const [key, value2] of entries) {
1268
1303
  if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key) || key.toLowerCase() === "port")
1269
- invalid("Port names must be identifiers other than 'port'");
1304
+ invalid$1("Port names must be identifiers other than 'port'");
1270
1305
  port(value2);
1271
1306
  }
1272
1307
  if (new Set(entries.map(([key]) => key.toUpperCase())).size !== entries.length)
1273
- invalid("Port names must be unique ignoring case");
1308
+ invalid$1("Port names must be unique ignoring case");
1274
1309
  }
1275
1310
  if (data.ready !== void 0) {
1276
1311
  const ready = object$2(data.ready, "ready");
1277
1312
  for (const key of Object.keys(ready))
1278
1313
  if (!["pattern", "url", "timeoutMs", "intervalMs"].includes(key))
1279
- invalid(`Unknown readiness field: ${key}`);
1314
+ invalid$1(`Unknown readiness field: ${key}`);
1280
1315
  if (ready.pattern !== void 0) {
1281
1316
  if (typeof ready.pattern !== "string" || !ready.pattern.length || ready.pattern.length > 256)
1282
- invalid("ready.pattern must be 1–256 characters");
1317
+ invalid$1("ready.pattern must be 1–256 characters");
1283
1318
  }
1284
1319
  if (ready.url !== void 0 && (typeof ready.url !== "string" || ready.url.length > 4096))
1285
- invalid("Invalid readiness URL");
1320
+ invalid$1("Invalid readiness URL");
1286
1321
  if (ready.timeoutMs !== void 0)
1287
1322
  number(ready.timeoutMs, 100, 36e5, "ready.timeoutMs");
1288
1323
  if (ready.intervalMs !== void 0)
@@ -1292,9 +1327,9 @@ function definition(value) {
1292
1327
  const restart = object$2(data.restart, "restart");
1293
1328
  for (const key of Object.keys(restart))
1294
1329
  if (!["policy", "maxAttempts", "delayMs"].includes(key))
1295
- invalid(`Unknown restart field: ${key}`);
1330
+ invalid$1(`Unknown restart field: ${key}`);
1296
1331
  if (!["never", "on-failure"].includes(restart.policy))
1297
- invalid("Invalid restart policy");
1332
+ invalid$1("Invalid restart policy");
1298
1333
  if (restart.maxAttempts !== void 0)
1299
1334
  number(restart.maxAttempts, 0, 10, "restart.maxAttempts");
1300
1335
  if (restart.delayMs !== void 0)
@@ -1304,9 +1339,9 @@ function definition(value) {
1304
1339
  const stop2 = object$2(data.stop, "stop");
1305
1340
  for (const key of Object.keys(stop2))
1306
1341
  if (!["signal", "timeoutMs"].includes(key))
1307
- invalid(`Unknown stop field: ${key}`);
1342
+ invalid$1(`Unknown stop field: ${key}`);
1308
1343
  if (stop2.signal !== void 0 && !["SIGTERM", "SIGINT"].includes(stop2.signal))
1309
- invalid("Invalid stop signal");
1344
+ invalid$1("Invalid stop signal");
1310
1345
  if (stop2.timeoutMs !== void 0)
1311
1346
  number(stop2.timeoutMs, 100, 6e4, "stop.timeoutMs");
1312
1347
  }
@@ -1317,7 +1352,7 @@ const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
1317
1352
  function json(text2) {
1318
1353
  const errors = [];
1319
1354
  const result = parse$1(text2, errors, { allowTrailingComma: true });
1320
- if (errors.length) invalid(`Invalid JSON near offset ${errors[0].offset}`);
1355
+ if (errors.length) invalid$1(`Invalid JSON near offset ${errors[0].offset}`);
1321
1356
  return object$2(result, "configuration");
1322
1357
  }
1323
1358
  function editJson(text2, location, value) {
@@ -1369,11 +1404,11 @@ function quotePath(value) {
1369
1404
  }
1370
1405
  function xml(text2) {
1371
1406
  if (/<!DOCTYPE|<!ENTITY/i.test(text2))
1372
- invalid("XML document types and entities are not supported");
1407
+ invalid$1("XML document types and entities are not supported");
1373
1408
  return new DOMParser({
1374
- onError: (level, message) => {
1409
+ onError: (level, message2) => {
1375
1410
  if (level !== "warning")
1376
- invalid(`Invalid run configuration XML: ${message}`);
1411
+ invalid$1(`Invalid run configuration XML: ${message2}`);
1377
1412
  }
1378
1413
  }).parseFromString(text2, "application/xml");
1379
1414
  }
@@ -1406,7 +1441,7 @@ function jetbrains(element) {
1406
1441
  const setting2 = (name2, nativeName = name2) => values[name2] ?? element.getAttribute(nativeName) ?? children(element, nativeName)[0]?.getAttribute("value") ?? "";
1407
1442
  let task = { command: "" }, disabledReason;
1408
1443
  const envs = Array.from(element.getElementsByTagName("env"));
1409
- const env = Object.fromEntries(
1444
+ const env2 = Object.fromEntries(
1410
1445
  envs.map((node) => [
1411
1446
  node.getAttribute("name"),
1412
1447
  node.getAttribute("value") ?? ""
@@ -1449,7 +1484,7 @@ function jetbrains(element) {
1449
1484
  disabledReason = "Gradle custom VM/script parameters need an explicit Oxbit command";
1450
1485
  } else
1451
1486
  disabledReason = `${type2 || "This IDE configuration"} requires an IDE/debug adapter; add an explicit Oxbit command to enable it`;
1452
- task.env = env;
1487
+ task.env = env2;
1453
1488
  const before = Array.from(element.getElementsByTagName("method")).flatMap((node) => children(node, "option")).filter((node) => node.getAttribute("enabled") === "true");
1454
1489
  if (before.length)
1455
1490
  disabledReason = "Before-launch IDE steps require an explicit command/dependency mapping";
@@ -1528,7 +1563,7 @@ function readSource(kind, text2) {
1528
1563
  result.worktree = hooks(kind === "oxbit" ? data.worktree : meta.worktree);
1529
1564
  if (kind === "oxbit") {
1530
1565
  if (data.version !== 1)
1531
- invalid("Unsupported Oxbit tasks version (expected 1)");
1566
+ invalid$1("Unsupported Oxbit tasks version (expected 1)");
1532
1567
  for (const key of Object.keys(data))
1533
1568
  if (![
1534
1569
  "$schema",
@@ -1538,7 +1573,7 @@ function readSource(kind, text2) {
1538
1573
  "env",
1539
1574
  "worktree"
1540
1575
  ].includes(key))
1541
- invalid(`Unknown Oxbit configuration field: ${key}`);
1576
+ invalid$1(`Unknown Oxbit configuration field: ${key}`);
1542
1577
  for (const [name, task] of Object.entries(
1543
1578
  object$2(data.tasks ?? {}, "tasks")
1544
1579
  ))
@@ -1571,10 +1606,10 @@ function readSource(kind, text2) {
1571
1606
  );
1572
1607
  } else if (kind === "vscode-launch") {
1573
1608
  if (!Array.isArray(data.configurations))
1574
- invalid("VS Code launch configurations must be an array");
1575
- for (const entry of data.configurations) {
1609
+ invalid$1("VS Code launch configurations must be an array");
1610
+ for (const entry2 of data.configurations) {
1576
1611
  const platform = process.platform === "darwin" ? "osx" : process.platform === "win32" ? "windows" : "linux";
1577
- const item = { ...entry, ...entry[platform] };
1612
+ const item = { ...entry2, ...entry2[platform] };
1578
1613
  const python = ["python", "debugpy"].includes(item.type);
1579
1614
  let disabledReason;
1580
1615
  if (item.request !== "launch" || !["node", "pwa-node", "python", "debugpy"].includes(item.type))
@@ -1603,11 +1638,11 @@ function readSource(kind, text2) {
1603
1638
  );
1604
1639
  } else if (kind === "vscode") {
1605
1640
  if (data.version !== "2.0.0")
1606
- invalid("Only VS Code tasks version 2.0.0 is supported");
1607
- if (!Array.isArray(data.tasks)) invalid("VS Code tasks must be an array");
1641
+ invalid$1("Only VS Code tasks version 2.0.0 is supported");
1642
+ if (!Array.isArray(data.tasks)) invalid$1("VS Code tasks must be an array");
1608
1643
  for (const raw of data.tasks) {
1609
- const entry = object$2(raw, "VS Code task"), platform = process.platform === "darwin" ? "osx" : process.platform === "win32" ? "windows" : "linux";
1610
- const item = { ...entry, ...entry[platform] };
1644
+ const entry2 = object$2(raw, "VS Code task"), platform = process.platform === "darwin" ? "osx" : process.platform === "win32" ? "windows" : "linux";
1645
+ const item = { ...entry2, ...entry2[platform] };
1611
1646
  const args = (item.args ?? []).map(
1612
1647
  (arg) => typeof arg === "string" ? arg : object$2(arg, "argument").value
1613
1648
  );
@@ -1664,10 +1699,10 @@ function readSource(kind, text2) {
1664
1699
  }
1665
1700
  }
1666
1701
  }
1667
- if (result.tasks.length > 256) invalid("Maximum 256 tasks per configuration");
1702
+ if (result.tasks.length > 256) invalid$1("Maximum 256 tasks per configuration");
1668
1703
  const names = /* @__PURE__ */ new Set();
1669
1704
  for (const task of result.tasks) {
1670
- if (names.has(task.name)) invalid(`Duplicate task name: ${task.name}`);
1705
+ if (names.has(task.name)) invalid$1(`Duplicate task name: ${task.name}`);
1671
1706
  names.add(task.name);
1672
1707
  if (!task.disabledReason) task.definition = definition(task.definition);
1673
1708
  }
@@ -1675,7 +1710,7 @@ function readSource(kind, text2) {
1675
1710
  }
1676
1711
  function writeTask(kind, text2, name, task, sourceCommand) {
1677
1712
  if (kind === "make" || kind === "just")
1678
- invalid("Recipe imports are read-only; copy to Oxbit to customize");
1713
+ invalid$1("Recipe imports are read-only; copy to Oxbit to customize");
1679
1714
  if (kind === "jetbrains") {
1680
1715
  const doc = xml(text2);
1681
1716
  let target = Array.from(doc.getElementsByTagName("configuration")).find(
@@ -1713,11 +1748,11 @@ function writeTask(kind, text2, name, task, sourceCommand) {
1713
1748
  }
1714
1749
  if (kind === "procfile") {
1715
1750
  if (task && (task.command.includes("\n") || Object.keys(task).some((key) => !["command", "type"].includes(key))))
1716
- invalid(
1751
+ invalid$1(
1717
1752
  "Procfile supports a single-line service command. Copy to Oxbit for advanced settings."
1718
1753
  );
1719
1754
  if (!/^[\w-]+$/.test(name))
1720
- invalid(
1755
+ invalid$1(
1721
1756
  "Procfile names must contain letters, numbers, hyphens or underscores"
1722
1757
  );
1723
1758
  const lines = text2.split(/\r?\n/), index = lines.findIndex((line) => line.startsWith(name + ":"));
@@ -1810,10 +1845,10 @@ function writeTask(kind, text2, name, task, sourceCommand) {
1810
1845
  try {
1811
1846
  script = JSON.parse(script);
1812
1847
  } catch {
1813
- invalid("Composer script array must be valid JSON");
1848
+ invalid$1("Composer script array must be valid JSON");
1814
1849
  }
1815
1850
  if (!Array.isArray(script) || script.some((v) => typeof v !== "string"))
1816
- invalid("Composer script array must contain strings");
1851
+ invalid$1("Composer script array must contain strings");
1817
1852
  }
1818
1853
  next = editJson(next, ["scripts", name], script);
1819
1854
  }
@@ -1823,9 +1858,9 @@ function writeTask(kind, text2, name, task, sourceCommand) {
1823
1858
  delete metadata.args;
1824
1859
  return editJson(next, ["oxbit", "tasks", name], metadata);
1825
1860
  }
1826
- function writeSettings(kind, text2, worktree, env) {
1861
+ function writeSettings(kind, text2, worktree, env2) {
1827
1862
  if (["make", "just", "procfile"].includes(kind))
1828
- invalid(
1863
+ invalid$1(
1829
1864
  "This source cannot store lifecycle settings; save an Oxbit configuration first"
1830
1865
  );
1831
1866
  if (kind === "jetbrains") {
@@ -1839,7 +1874,7 @@ function writeSettings(kind, text2, worktree, env) {
1839
1874
  doc.documentElement.appendChild(component);
1840
1875
  }
1841
1876
  option(doc, component, "worktree", JSON.stringify(worktree));
1842
- option(doc, component, "env", JSON.stringify(env));
1877
+ option(doc, component, "env", JSON.stringify(env2));
1843
1878
  return new XMLSerializer().serializeToString(doc);
1844
1879
  }
1845
1880
  let next = editJson(
@@ -1851,7 +1886,7 @@ function writeSettings(kind, text2, worktree, env) {
1851
1886
  next = editJson(next, ["worktree", "setup"], worktree.init);
1852
1887
  next = editJson(next, ["worktree", "teardown"], worktree.preteardown);
1853
1888
  }
1854
- return editJson(next, kind === "oxbit" ? ["env"] : ["oxbit", "env"], env);
1889
+ return editJson(next, kind === "oxbit" ? ["env"] : ["oxbit", "env"], env2);
1855
1890
  }
1856
1891
  const digest$2 = (text2) => createHash("sha256").update(text2).digest("hex");
1857
1892
  const missing$1 = (error) => error.code === "ENOENT";
@@ -2008,10 +2043,10 @@ class TaskConfigStore {
2008
2043
  const entries = await fs.readdir(path.join(this.root, directory), {
2009
2044
  withFileTypes: true
2010
2045
  });
2011
- for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name)).slice(0, 128))
2012
- if (entry.isFile() && entry.name.endsWith(".xml"))
2046
+ for (const entry2 of entries.sort((a, b) => a.name.localeCompare(b.name)).slice(0, 128))
2047
+ if (entry2.isFile() && entry2.name.endsWith(".xml"))
2013
2048
  candidates.push({
2014
- file: path.join(directory, entry.name),
2049
+ file: path.join(directory, entry2.name),
2015
2050
  kind: "jetbrains"
2016
2051
  });
2017
2052
  } catch (error) {
@@ -2028,7 +2063,7 @@ class TaskConfigStore {
2028
2063
  { file: this.privatePath, kind: "oxbit", private: true }
2029
2064
  );
2030
2065
  const sources = [], tasks = [];
2031
- let worktree = {}, env = {}, settingsFound = false, noImports = false;
2066
+ let worktree = {}, env2 = {}, settingsFound = false, noImports = false;
2032
2067
  for (const candidate of candidates) {
2033
2068
  if (noImports) continue;
2034
2069
  const file = path.resolve(this.root, candidate.file), source2 = {
@@ -2062,7 +2097,7 @@ class TaskConfigStore {
2062
2097
  noImports = json(text2).autoDetect === false;
2063
2098
  if (!settingsFound && source2.writable && !["procfile"].includes(source2.kind)) {
2064
2099
  worktree = parsed.worktree;
2065
- env = parsed.env;
2100
+ env2 = parsed.env;
2066
2101
  settingsFound = true;
2067
2102
  }
2068
2103
  for (const task of parsed.tasks)
@@ -2099,7 +2134,7 @@ class TaskConfigStore {
2099
2134
  sources,
2100
2135
  tasks,
2101
2136
  worktree,
2102
- env
2137
+ env: env2
2103
2138
  };
2104
2139
  }
2105
2140
  async source(id, expected) {
@@ -2424,22 +2459,22 @@ function tcpReady(host, port2, timeoutMs = 1e3) {
2424
2459
  socket.once("timeout", () => finish(false));
2425
2460
  });
2426
2461
  }
2427
- function expand(value, env, strict = true) {
2462
+ function expand(value, env2, strict = true) {
2428
2463
  return value.replace(
2429
2464
  /\$\$|\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
2430
2465
  (match, braced, bare) => {
2431
2466
  if (match === "$$") return "$";
2432
2467
  const key = braced ?? bare;
2433
- if (env[key] === void 0 && strict)
2468
+ if (env2[key] === void 0 && strict)
2434
2469
  throw new RpcError(
2435
2470
  "UNRESOLVED_VARIABLE",
2436
2471
  `Variable ${key} is not defined`
2437
2472
  );
2438
- return env[key] ?? "";
2473
+ return env2[key] ?? "";
2439
2474
  }
2440
2475
  );
2441
2476
  }
2442
- function taskEnvironment(store, task, ports, catalog, context = {}) {
2477
+ function taskEnvironment(store, task, ports, catalog, context = {}, base = process.env) {
2443
2478
  const root = context.projectDir ?? context.root ?? store.root, plan = ports.get(task.id);
2444
2479
  const variables = {
2445
2480
  OXBIT_PROJECT_ID: store.projectId,
@@ -2467,26 +2502,26 @@ function taskEnvironment(store, task, ports, catalog, context = {}) {
2467
2502
  for (const [name, port2] of Object.entries(plan.ports))
2468
2503
  variables[`OXBIT_${name.toUpperCase()}_PORT`] = String(port2);
2469
2504
  }
2470
- const env = { ...process.env, ...variables };
2471
- env.PASEO_SOURCE_CHECKOUT_PATH = variables.OXBIT_SOURCE_DIR;
2472
- env.PASEO_WORKTREE_PATH = root;
2473
- env.PASEO_ROOT_PATH = variables.OXBIT_SOURCE_DIR;
2474
- env.PASEO_BRANCH_NAME = variables.OXBIT_BRANCH;
2475
- env.PASEO_WORKSPACE_ID = variables.OXBIT_WORKSPACE_ID;
2476
- env.PASEO_SCRIPTNAME = task.name;
2505
+ const env2 = { ...base, ...variables };
2506
+ env2.PASEO_SOURCE_CHECKOUT_PATH = variables.OXBIT_SOURCE_DIR;
2507
+ env2.PASEO_WORKTREE_PATH = root;
2508
+ env2.PASEO_ROOT_PATH = variables.OXBIT_SOURCE_DIR;
2509
+ env2.PASEO_BRANCH_NAME = variables.OXBIT_BRANCH;
2510
+ env2.PASEO_WORKSPACE_ID = variables.OXBIT_WORKSPACE_ID;
2511
+ env2.PASEO_SCRIPTNAME = task.name;
2477
2512
  if (plan) {
2478
- env.PASEO_PORT = variables.OXBIT_PORT;
2479
- env.PASEO_URL = variables.OXBIT_URL;
2480
- env.PASEO_WORKTREE_PORT = variables.OXBIT_PORT;
2481
- env.PORT = variables.OXBIT_PORT;
2482
- env.HOST = variables.OXBIT_HOST;
2513
+ env2.PASEO_PORT = variables.OXBIT_PORT;
2514
+ env2.PASEO_URL = variables.OXBIT_URL;
2515
+ env2.PASEO_WORKTREE_PORT = variables.OXBIT_PORT;
2516
+ env2.PORT = variables.OXBIT_PORT;
2517
+ env2.HOST = variables.OXBIT_HOST;
2483
2518
  }
2484
2519
  for (const [key, value] of Object.entries(variables))
2485
2520
  if (key.startsWith("OXBIT_SERVICE_"))
2486
- env[key.replace("OXBIT_", "PASEO_")] = value;
2521
+ env2[key.replace("OXBIT_", "PASEO_")] = value;
2487
2522
  const custom = { ...catalog.env, ...task.env }, resolved = /* @__PURE__ */ new Set(), resolving = /* @__PURE__ */ new Set();
2488
2523
  function resolve(key) {
2489
- if (resolved.has(key)) return env[key];
2524
+ if (resolved.has(key)) return env2[key];
2490
2525
  if (resolving.has(key))
2491
2526
  throw new RpcError(
2492
2527
  "UNRESOLVED_VARIABLE",
@@ -2505,13 +2540,13 @@ function taskEnvironment(store, task, ports, catalog, context = {}) {
2505
2540
  const other = match[1] ?? match[2];
2506
2541
  if (other !== key && Object.hasOwn(custom, other)) resolve(other);
2507
2542
  }
2508
- env[key] = expand(value, env);
2543
+ env2[key] = expand(value, env2);
2509
2544
  resolving.delete(key);
2510
2545
  resolved.add(key);
2511
- return env[key];
2546
+ return env2[key];
2512
2547
  }
2513
2548
  for (const key of Object.keys(custom)) resolve(key);
2514
- return { env, variables };
2549
+ return { env: env2, variables };
2515
2550
  }
2516
2551
  function detectLinks(text2) {
2517
2552
  const clean = text2.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
@@ -2519,11 +2554,11 @@ function detectLinks(text2) {
2519
2554
  for (const match of clean.matchAll(/https?:\/\/[^\s<>"'\x00-\x1f]+/g)) {
2520
2555
  const raw = match[0].replace(/[),.;\]}]+$/, "");
2521
2556
  try {
2522
- const url = new URL(raw);
2523
- if (!url.username && !url.password) {
2524
- if (["0.0.0.0", "[::]"].includes(url.hostname))
2525
- url.hostname = "localhost";
2526
- found.add(url.href);
2557
+ const url2 = new URL(raw);
2558
+ if (!url2.username && !url2.password) {
2559
+ if (["0.0.0.0", "[::]"].includes(url2.hostname))
2560
+ url2.hostname = "localhost";
2561
+ found.add(url2.href);
2527
2562
  }
2528
2563
  } catch {
2529
2564
  }
@@ -2531,35 +2566,38 @@ function detectLinks(text2) {
2531
2566
  return [...found].slice(0, 32);
2532
2567
  }
2533
2568
  class TaskRunner {
2534
- constructor(store, emit, forwardPort) {
2569
+ constructor(store, emit, forwardPort, shellEnvironment = async () => process.env) {
2535
2570
  this.store = store;
2536
2571
  this.emit = emit;
2537
2572
  this.forwardPort = forwardPort;
2573
+ this.shellEnvironment = shellEnvironment;
2538
2574
  this.ports = new TaskPorts(store.root);
2539
2575
  }
2540
2576
  store;
2541
2577
  emit;
2542
2578
  forwardPort;
2579
+ shellEnvironment;
2543
2580
  runs = /* @__PURE__ */ new Map();
2544
2581
  ports;
2545
2582
  publicPorts = /* @__PURE__ */ new Map();
2546
2583
  closed = false;
2584
+ environment;
2547
2585
  publicLinks(run2, links) {
2548
2586
  if (!this.forwardPort) return links;
2549
2587
  const plan = this.ports.get(run2.task.id);
2550
2588
  return links.map((link) => {
2551
2589
  try {
2552
- const url = new URL(link), port2 = this.publicPorts.get(run2.task.id)?.get(Number(url.port || (url.protocol === "https:" ? 443 : 80)));
2590
+ const url2 = new URL(link), port2 = this.publicPorts.get(run2.task.id)?.get(Number(url2.port || (url2.protocol === "https:" ? 443 : 80)));
2553
2591
  if (port2 && [
2554
2592
  "localhost",
2555
2593
  "127.0.0.1",
2556
2594
  "[::1]",
2557
2595
  plan?.hostname,
2558
2596
  plan?.host
2559
- ].includes(url.hostname)) {
2560
- url.hostname = "127.0.0.1";
2561
- url.port = String(port2);
2562
- return url.href;
2597
+ ].includes(url2.hostname)) {
2598
+ url2.hostname = "127.0.0.1";
2599
+ url2.port = String(port2);
2600
+ return url2.href;
2563
2601
  }
2564
2602
  } catch {
2565
2603
  }
@@ -2584,10 +2622,10 @@ class TaskRunner {
2584
2622
  for (const update of run2.updates) update();
2585
2623
  run2.updates.clear();
2586
2624
  }
2587
- state(run2, state2, message) {
2588
- if (run2.value.state === state2 && run2.value.message === message) return;
2625
+ state(run2, state2, message2) {
2626
+ if (run2.value.state === state2 && run2.value.message === message2) return;
2589
2627
  run2.value.state = state2;
2590
- run2.value.message = message;
2628
+ run2.value.message = message2;
2591
2629
  this.publish(run2);
2592
2630
  }
2593
2631
  owned(id, owner) {
@@ -2685,10 +2723,10 @@ class TaskRunner {
2685
2723
  )
2686
2724
  );
2687
2725
  }
2688
- async runHook(owner, command, name, context, env, signal) {
2726
+ async runHook(owner, command, name, context, env2, signal) {
2689
2727
  if (signal?.aborted)
2690
2728
  throw new RpcError("CANCELLED", "Worktree operation was cancelled");
2691
- const catalog = { ...await this.store.catalog(), tasks: [], env };
2729
+ const catalog = { ...await this.store.catalog(), tasks: [], env: env2 };
2692
2730
  signal?.throwIfAborted();
2693
2731
  const run2 = this.begin(
2694
2732
  owner,
@@ -2822,12 +2860,14 @@ class TaskRunner {
2822
2860
  }
2823
2861
  async spawnAttempt(run2) {
2824
2862
  const generation = ++run2.generation;
2825
- const { env, variables } = taskEnvironment(
2863
+ const base = await (this.environment ??= this.shellEnvironment(this.store.root));
2864
+ const { env: env2, variables } = taskEnvironment(
2826
2865
  this.store,
2827
2866
  run2.task,
2828
2867
  this.ports,
2829
2868
  run2.catalog,
2830
- run2.context
2869
+ run2.context,
2870
+ base
2831
2871
  );
2832
2872
  await fs.mkdir(variables.OXBIT_PREINIT_DIR, {
2833
2873
  recursive: true,
@@ -2835,7 +2875,7 @@ class TaskRunner {
2835
2875
  });
2836
2876
  const cwd = path.resolve(
2837
2877
  run2.context.root ?? this.store.root,
2838
- expand(run2.task.cwd ?? ".", env)
2878
+ expand(run2.task.cwd ?? ".", env2)
2839
2879
  );
2840
2880
  const real = await fs.realpath(cwd);
2841
2881
  const bound = await fs.realpath(run2.context.root ?? this.store.root), relative = path.relative(bound, real);
@@ -2844,7 +2884,7 @@ class TaskRunner {
2844
2884
  "INVALID_PATH",
2845
2885
  "Task working directory must be within this workspace"
2846
2886
  );
2847
- const args = (run2.task.args ?? []).map((arg) => expand(arg, env));
2887
+ const args = (run2.task.args ?? []).map((arg) => expand(arg, env2));
2848
2888
  const plan = this.ports.get(run2.task.id);
2849
2889
  if (plan && this.forwardPort) {
2850
2890
  const forwarded = /* @__PURE__ */ new Map();
@@ -2866,15 +2906,15 @@ class TaskRunner {
2866
2906
  run2.readySeen = false;
2867
2907
  run2.timedOut = false;
2868
2908
  run2.tail = "";
2869
- const command = run2.task.execution === "process" ? expand(run2.task.command, env) : run2.task.command + (args.length ? " " + args.map(shellQuote).join(" ") : "");
2909
+ const command = run2.task.execution === "process" ? expand(run2.task.command, env2) : run2.task.command + (args.length ? " " + args.map(shellQuote).join(" ") : "");
2870
2910
  const child = run2.task.execution === "process" ? spawn(command, args, {
2871
2911
  cwd: real,
2872
- env,
2912
+ env: env2,
2873
2913
  detached: process.platform !== "win32",
2874
2914
  stdio: ["ignore", "pipe", "pipe"]
2875
2915
  }) : spawn(command, {
2876
2916
  cwd: real,
2877
- env,
2917
+ env: env2,
2878
2918
  shell: true,
2879
2919
  detached: process.platform !== "win32",
2880
2920
  stdio: ["ignore", "pipe", "pipe"]
@@ -2958,19 +2998,19 @@ class TaskRunner {
2958
2998
  let healthy = !run2.task.ready?.pattern || run2.readySeen;
2959
2999
  try {
2960
3000
  if (run2.task.ready?.url) {
2961
- const url = new URL(
3001
+ const url2 = new URL(
2962
3002
  expand(run2.task.ready.url, {
2963
3003
  ...process.env,
2964
3004
  ...run2.value.variables
2965
3005
  })
2966
3006
  );
2967
- if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
3007
+ if (!["http:", "https:"].includes(url2.protocol) || url2.username || url2.password)
2968
3008
  throw new Error(
2969
3009
  "Readiness URL must use HTTP(S) without credentials"
2970
3010
  );
2971
- if (plan && url.hostname === plan.hostname)
2972
- url.hostname = plan.host === "0.0.0.0" ? "127.0.0.1" : plan.host;
2973
- const response = await fetch(url, {
3011
+ if (plan && url2.hostname === plan.hostname)
3012
+ url2.hostname = plan.host === "0.0.0.0" ? "127.0.0.1" : plan.host;
3013
+ const response = await fetch(url2, {
2974
3014
  signal: AbortSignal.timeout(1500),
2975
3015
  redirect: "manual"
2976
3016
  });
@@ -3055,7 +3095,7 @@ class TaskRunner {
3055
3095
  await this.ports.reserveAgain(run2.task.id).catch(() => {
3056
3096
  });
3057
3097
  }
3058
- finish(run2, code, signal, message) {
3098
+ finish(run2, code, signal, message2) {
3059
3099
  if (run2.ended) return;
3060
3100
  run2.ended = true;
3061
3101
  clearTimeout(run2.monitor);
@@ -3068,7 +3108,7 @@ class TaskRunner {
3068
3108
  this.state(
3069
3109
  run2,
3070
3110
  run2.stopRequested ? "stopped" : code === 0 && run2.task.type !== "service" ? "completed" : "failed",
3071
- message
3111
+ message2
3072
3112
  );
3073
3113
  run2.doneResolve();
3074
3114
  this.emit(
@@ -3346,13 +3386,13 @@ ${command}`,
3346
3386
  "INVALID_STATE",
3347
3387
  "Switch to the source checkout before removing this worktree"
3348
3388
  );
3349
- const exists = await fs.lstat(record.path).catch((error) => {
3389
+ const exists2 = await fs.lstat(record.path).catch((error) => {
3350
3390
  if (error.code === "ENOENT") return null;
3351
3391
  throw error;
3352
3392
  });
3353
- if (exists && (exists.isSymbolicLink() || await fs.realpath(record.path) !== record.path))
3393
+ if (exists2 && (exists2.isSymbolicLink() || await fs.realpath(record.path) !== record.path))
3354
3394
  throw new RpcError("INVALID_STATE", "Managed worktree path changed");
3355
- if (exists) {
3395
+ if (exists2) {
3356
3396
  const known = await this.git(
3357
3397
  ["worktree", "list", "--porcelain", "-z"],
3358
3398
  void 0,
@@ -3379,9 +3419,9 @@ ${command}`,
3379
3419
  record.operationPid = process.pid;
3380
3420
  record.message = void 0;
3381
3421
  await this.persist(records2);
3382
- let removed = !exists;
3422
+ let removed = !exists2;
3383
3423
  try {
3384
- if (exists) {
3424
+ if (exists2) {
3385
3425
  await this.phase(record, "preteardown", owner, signal);
3386
3426
  await this.runner.stopInDirectory(record.path);
3387
3427
  if (signal?.aborted)
@@ -3642,14 +3682,14 @@ class Git {
3642
3682
  if (error.code !== "ENOENT") throw error;
3643
3683
  }
3644
3684
  }
3645
- const binary = diff.includes("Binary files ") || before.stdout.includes("\0") || after.includes("\0");
3685
+ const binary2 = diff.includes("Binary files ") || before.stdout.includes("\0") || after.includes("\0");
3646
3686
  const pieces = diff.split(/(?=^@@ )/m);
3647
- const canHunk = !binary && !change?.conflict && !change?.originalPath && pieces.length > 1 && !/^(new file|deleted file|old mode|new mode|diff --cc)/m.test(diff) && (diff.match(/^diff --git /gm)?.length ?? 0) === 1;
3687
+ const canHunk = !binary2 && !change?.conflict && !change?.originalPath && pieces.length > 1 && !/^(new file|deleted file|old mode|new mode|diff --cc)/m.test(diff) && (diff.match(/^diff --git /gm)?.length ?? 0) === 1;
3648
3688
  return bounded({
3649
- before: binary ? "" : before.exitCode ? "" : before.stdout,
3650
- after: binary ? "" : after,
3689
+ before: binary2 ? "" : before.exitCode ? "" : before.stdout,
3690
+ after: binary2 ? "" : after,
3651
3691
  diff,
3652
- binary,
3692
+ binary: binary2,
3653
3693
  fingerprint: digest$1(diff),
3654
3694
  hunks: canHunk ? pieces.slice(1).map((patch, index) => ({
3655
3695
  index,
@@ -3775,12 +3815,12 @@ class Git {
3775
3815
  ],
3776
3816
  signal
3777
3817
  );
3778
- const binary = diff.stdout.includes("Binary files ") || before?.stdout.includes("\0") || after.stdout.includes("\0");
3818
+ const binary2 = diff.stdout.includes("Binary files ") || before?.stdout.includes("\0") || after.stdout.includes("\0");
3779
3819
  return bounded({
3780
- before: binary || before?.exitCode ? "" : before?.stdout ?? "",
3781
- after: binary || after.exitCode ? "" : after.stdout,
3820
+ before: binary2 || before?.exitCode ? "" : before?.stdout ?? "",
3821
+ after: binary2 || after.exitCode ? "" : after.stdout,
3782
3822
  diff: diff.stdout,
3783
- binary: !!binary
3823
+ binary: !!binary2
3784
3824
  });
3785
3825
  }
3786
3826
  async stashes(signal) {
@@ -4319,9 +4359,9 @@ class ExternalSources {
4319
4359
  }
4320
4360
  }
4321
4361
  async authorize(uri) {
4322
- const url = new URL(uri);
4323
- if (url.protocol !== "file:" || url.host || url.search || url.hash) throw new RpcError("PATH_DENIED", "External sources require local file URIs");
4324
- const file = await fs.realpath(fileURLToPath(url));
4362
+ const url2 = new URL(uri);
4363
+ if (url2.protocol !== "file:" || url2.host || url2.search || url2.hash) throw new RpcError("PATH_DENIED", "External sources require local file URIs");
4364
+ const file = await fs.realpath(fileURLToPath(url2));
4325
4365
  if (!this.roots.some((root) => contains(root, file))) throw new RpcError("PATH_DENIED", "Source is outside registered dependency roots");
4326
4366
  const stat = await fs.stat(file);
4327
4367
  if (!stat.isFile() || stat.size > 20 * 1024 * 1024) throw new RpcError("PATH_DENIED", "Source is not a supported text file");
@@ -4331,16 +4371,16 @@ class ExternalSources {
4331
4371
  return { handle, uri, name: path.basename(file), readonly: true };
4332
4372
  }
4333
4373
  async read(handle) {
4334
- const entry = this.handles.get(handle);
4335
- if (!entry || !this.roots.some((root) => contains(root, entry.file)) || await fs.realpath(entry.file) !== entry.file) throw new RpcError("PATH_DENIED", "External source handle expired");
4336
- const descriptor = await fs.open(entry.file, constants$1.O_RDONLY | constants$1.O_NOFOLLOW);
4374
+ const entry2 = this.handles.get(handle);
4375
+ if (!entry2 || !this.roots.some((root) => contains(root, entry2.file)) || await fs.realpath(entry2.file) !== entry2.file) throw new RpcError("PATH_DENIED", "External source handle expired");
4376
+ const descriptor = await fs.open(entry2.file, constants$1.O_RDONLY | constants$1.O_NOFOLLOW);
4337
4377
  try {
4338
4378
  const stat = await descriptor.stat();
4339
4379
  if (!stat.isFile() || stat.size > 20 * 1024 * 1024) throw new RpcError("PATH_DENIED", "External source is not a supported text file");
4340
4380
  const bytes = await descriptor.readFile();
4341
- const current = await fs.stat(entry.file);
4342
- if (stat.dev !== current.dev || stat.ino !== current.ino || bytes.length > 20 * 1024 * 1024 || await fs.realpath(entry.file) !== entry.file || bytes.includes(0)) throw new RpcError("PATH_DENIED", "External source changed or is binary");
4343
- return { handle, uri: entry.uri, name: path.basename(entry.file), text: new TextDecoder("utf-8", { fatal: true }).decode(bytes), revision: createHash("sha256").update(bytes).digest("hex"), readonly: true };
4381
+ const current = await fs.stat(entry2.file);
4382
+ if (stat.dev !== current.dev || stat.ino !== current.ino || bytes.length > 20 * 1024 * 1024 || await fs.realpath(entry2.file) !== entry2.file || bytes.includes(0)) throw new RpcError("PATH_DENIED", "External source changed or is binary");
4383
+ return { handle, uri: entry2.uri, name: path.basename(entry2.file), text: new TextDecoder("utf-8", { fatal: true }).decode(bytes), revision: createHash("sha256").update(bytes).digest("hex"), readonly: true };
4344
4384
  } finally {
4345
4385
  await descriptor.close();
4346
4386
  }
@@ -4531,17 +4571,17 @@ class LanguageServer {
4531
4571
  }
4532
4572
  }
4533
4573
  }
4534
- send(message) {
4535
- if (message.id !== void 0 && !message.method) {
4536
- const pending = this.incoming.get(message.id);
4574
+ send(message2) {
4575
+ if (message2.id !== void 0 && !message2.method) {
4576
+ const pending = this.incoming.get(message2.id);
4537
4577
  if (!pending) return;
4538
- this.incoming.delete(message.id);
4539
- if (message.error) pending.reject(new ResponseError(message.error.code, message.error.message, message.error.data));
4540
- else pending.resolve(message.result);
4578
+ this.incoming.delete(message2.id);
4579
+ if (message2.error) pending.reject(new ResponseError(message2.error.code, message2.error.message, message2.error.data));
4580
+ else pending.resolve(message2.result);
4541
4581
  return;
4542
4582
  }
4543
4583
  if (!this.rpc) throw new RpcError("LSP_UNAVAILABLE", "Language server is stopped");
4544
- void this.rpc.sendNotification(message.method, ...Array.isArray(message.params) ? message.params : [message.params]).catch((error) => {
4584
+ void this.rpc.sendNotification(message2.method, ...Array.isArray(message2.params) ? message2.params : [message2.params]).catch((error) => {
4545
4585
  if (this.rpc) this.emit("window/logMessage", { type: 1, message: String(error) });
4546
4586
  });
4547
4587
  }
@@ -4696,12 +4736,12 @@ class LanguageServer {
4696
4736
  });
4697
4737
  connection.listen();
4698
4738
  this.child.stderr.on("data", (data) => {
4699
- let message = data.toString().slice(0, 16e3);
4739
+ let message2 = data.toString().slice(0, 16e3);
4700
4740
  const key = this.launchSpec?.initializationOptions?.licenceKey;
4701
- if (typeof key === "string" && key) message = message.replaceAll(key, "[redacted]");
4702
- this.output.push(message);
4741
+ if (typeof key === "string" && key) message2 = message2.replaceAll(key, "[redacted]");
4742
+ this.output.push(message2);
4703
4743
  while (this.output.join("").length > 16e3) this.output.shift();
4704
- this.emit("window/logMessage", { type: 4, message });
4744
+ this.emit("window/logMessage", { type: 4, message: message2 });
4705
4745
  });
4706
4746
  const launched = this.child;
4707
4747
  this.child.on("error", (error) => {
@@ -4762,61 +4802,61 @@ class LanguageServer {
4762
4802
  this.emit("oxbit/serverState", { state: "running", name: this.serverName });
4763
4803
  return { capabilities: this.capabilities, registrations: [...this.registrations.values()], rootUri: this.uri(""), projectRootUri: pathToFileURL(this.options.root ?? this.files.root).href, version: this.launchSpec?.version };
4764
4804
  }
4765
- receive(message) {
4766
- if (setting("LSP_TRACE")) this.emit("window/logMessage", { type: 4, message: `${this.serverName}: ${message.method ?? "response"}` });
4767
- if (message.id !== void 0 && message.method) {
4768
- if (message.method === "vscode/content" && this.launchSpec?.schemaContent) {
4805
+ receive(message2) {
4806
+ if (setting("LSP_TRACE")) this.emit("window/logMessage", { type: 4, message: `${this.serverName}: ${message2.method ?? "response"}` });
4807
+ if (message2.id !== void 0 && message2.method) {
4808
+ if (message2.method === "vscode/content" && this.launchSpec?.schemaContent) {
4769
4809
  const connection = this.rpc;
4770
- const uri = Array.isArray(message.params) && message.params.length === 1 ? message.params[0] : message.params;
4810
+ const uri = Array.isArray(message2.params) && message2.params.length === 1 ? message2.params[0] : message2.params;
4771
4811
  void this.launchSpec.schemaContent(uri).then(
4772
4812
  (result) => {
4773
- if (this.rpc === connection) this.send({ id: message.id, result });
4813
+ if (this.rpc === connection) this.send({ id: message2.id, result });
4774
4814
  },
4775
4815
  (error) => {
4776
- if (this.rpc === connection) this.send({ id: message.id, error: { code: -32603, message: String(error) } });
4816
+ if (this.rpc === connection) this.send({ id: message2.id, error: { code: -32603, message: String(error) } });
4777
4817
  }
4778
4818
  );
4779
4819
  return;
4780
4820
  }
4781
- if (["workspace/semanticTokens/refresh", "workspace/inlayHint/refresh"].includes(message.method)) {
4782
- this.emit("oxbit/refresh", { method: message.method });
4783
- this.send({ id: message.id, result: null });
4821
+ if (["workspace/semanticTokens/refresh", "workspace/inlayHint/refresh"].includes(message2.method)) {
4822
+ this.emit("oxbit/refresh", { method: message2.method });
4823
+ this.send({ id: message2.id, result: null });
4784
4824
  return;
4785
4825
  }
4786
- if (message.method === "window/showMessageRequest") {
4826
+ if (message2.method === "window/showMessageRequest") {
4787
4827
  const connection = this.rpc;
4788
4828
  const timer = setTimeout(() => {
4789
- if (this.rpc === connection) this.send({ id: message.id, result: null });
4829
+ if (this.rpc === connection) this.send({ id: message2.id, result: null });
4790
4830
  }, 3e4);
4791
4831
  timer.unref();
4792
- this.emit("oxbit/serverRequest", { id: message.id, method: message.method, params: message.params });
4832
+ this.emit("oxbit/serverRequest", { id: message2.id, method: message2.method, params: message2.params });
4793
4833
  return;
4794
4834
  }
4795
- if (message.method === "workspace/applyEdit") {
4835
+ if (message2.method === "workspace/applyEdit") {
4796
4836
  const child = this.child;
4797
4837
  void (async () => {
4798
- await this.validate(message.params?.edit);
4799
- return this.applyEdit ? await this.applyEdit(message.params.edit, message.params.label) : { applied: false, failureReason: "No initiating document client is available" };
4838
+ await this.validate(message2.params?.edit);
4839
+ return this.applyEdit ? await this.applyEdit(message2.params.edit, message2.params.label) : { applied: false, failureReason: "No initiating document client is available" };
4800
4840
  })().catch((error) => ({ applied: false, failureReason: String(error) })).then((result) => {
4801
- if (this.child === child) this.send({ jsonrpc: "2.0", id: message.id, result });
4841
+ if (this.child === child) this.send({ jsonrpc: "2.0", id: message2.id, result });
4802
4842
  });
4803
4843
  return;
4804
4844
  }
4805
- if (message.method === "client/registerCapability" || message.method === "client/unregisterCapability") {
4806
- const adding = message.method === "client/registerCapability";
4807
- const registrations = adding ? message.params?.registrations : message.params?.unregisterations;
4845
+ if (message2.method === "client/registerCapability" || message2.method === "client/unregisterCapability") {
4846
+ const adding = message2.method === "client/registerCapability";
4847
+ const registrations = adding ? message2.params?.registrations : message2.params?.unregisterations;
4808
4848
  const syncMethods = ["textDocument/didOpen", "textDocument/didClose", "textDocument/didChange", "textDocument/didSave", "textDocument/willSave", "textDocument/willSaveWaitUntil"];
4809
4849
  const watcherShape = (watcher) => (typeof watcher?.globPattern === "string" || typeof watcher?.globPattern?.pattern === "string") && (watcher.kind === void 0 || Number.isInteger(watcher.kind) && watcher.kind >= 1 && watcher.kind <= 7);
4810
4850
  const supported = (item) => item && typeof item.id === "string" && (lspCapabilityKeys[item.method] || syncMethods.includes(item.method) || item.method === "workspace/didChangeConfiguration" || item.method === "workspace/didChangeWorkspaceFolders" || item.method === "workspace/didChangeWatchedFiles" && Array.isArray(item.registerOptions?.watchers) && item.registerOptions.watchers.every(watcherShape));
4811
4851
  if (!Array.isArray(registrations) || new Set(registrations.map((item) => item?.id)).size !== registrations.length || registrations.some((item) => adding ? !supported(item) || this.registrations.has(item.id) : this.registrations.get(item.id)?.method !== item.method)) {
4812
- this.send({ id: message.id, error: { code: -32602, message: "Unsupported or unknown capability registration" } });
4852
+ this.send({ id: message2.id, error: { code: -32602, message: "Unsupported or unknown capability registration" } });
4813
4853
  } else {
4814
4854
  const previousSync = new Map([...this.documents.keys()].map((uri) => [uri, this.syncOptions(uri).openClose]));
4815
4855
  for (const item of registrations) {
4816
4856
  if (adding) this.registrations.set(item.id, item);
4817
4857
  else this.registrations.delete(item.id);
4818
4858
  }
4819
- this.send({ id: message.id, result: null });
4859
+ this.send({ id: message2.id, result: null });
4820
4860
  for (const [uri, wasOpen] of previousSync) {
4821
4861
  const isOpen = this.syncOptions(uri).openClose;
4822
4862
  if (!wasOpen && isOpen) this.opened(uri);
@@ -4826,15 +4866,15 @@ class LanguageServer {
4826
4866
  }
4827
4867
  return;
4828
4868
  }
4829
- if (message.method === "workspace/workspaceFolders") {
4830
- this.send({ jsonrpc: "2.0", id: message.id, result: [{ uri: pathToFileURL(this.options.root ?? this.files.root).href, name: path.basename(this.options.root ?? this.files.root) }] });
4869
+ if (message2.method === "workspace/workspaceFolders") {
4870
+ this.send({ jsonrpc: "2.0", id: message2.id, result: [{ uri: pathToFileURL(this.options.root ?? this.files.root).href, name: path.basename(this.options.root ?? this.files.root) }] });
4831
4871
  return;
4832
4872
  }
4833
- if (message.method === "workspace/configuration")
4873
+ if (message2.method === "workspace/configuration")
4834
4874
  this.send({
4835
4875
  jsonrpc: "2.0",
4836
- id: message.id,
4837
- result: (message.params?.items ?? []).map((item) => {
4876
+ id: message2.id,
4877
+ result: (message2.params?.items ?? []).map((item) => {
4838
4878
  if (item.scopeUri) {
4839
4879
  try {
4840
4880
  const scope = fileURLToPath(item.scopeUri), root = this.options.root ?? this.files.root;
@@ -4849,28 +4889,28 @@ class LanguageServer {
4849
4889
  return item.section.split(".").reduce((value, key) => value?.[key], settings) ?? null;
4850
4890
  })
4851
4891
  });
4852
- else if (message.method === "window/workDoneProgress/create")
4853
- this.send({ jsonrpc: "2.0", id: message.id, result: null });
4854
- else this.send({ jsonrpc: "2.0", id: message.id, error: { code: -32601, message: `Unsupported server request ${message.method}` } });
4892
+ else if (message2.method === "window/workDoneProgress/create")
4893
+ this.send({ jsonrpc: "2.0", id: message2.id, result: null });
4894
+ else this.send({ jsonrpc: "2.0", id: message2.id, error: { code: -32601, message: `Unsupported server request ${message2.method}` } });
4855
4895
  return;
4856
4896
  }
4857
- if (message.method === "tsserver/request" && this.companion) {
4897
+ if (message2.method === "tsserver/request" && this.companion) {
4858
4898
  const child = this.child;
4859
- for (const [id, command, payload] of Array.isArray(message.params?.[0]) ? message.params : [message.params]) void this.companion.request("workspace/executeCommand", {
4899
+ for (const [id, command, payload] of Array.isArray(message2.params?.[0]) ? message2.params : [message2.params]) void this.companion.request("workspace/executeCommand", {
4860
4900
  command: "typescript.tsserverRequest",
4861
4901
  arguments: [command, payload]
4862
4902
  }).catch(() => null).then((result) => {
4863
- if (this.child === child) this.send({ jsonrpc: "2.0", method: "tsserver/response", params: Array.isArray(message.params?.[0]) ? [[id, result?.body ?? null]] : [id, result?.body ?? null] });
4903
+ if (this.child === child) this.send({ jsonrpc: "2.0", method: "tsserver/response", params: Array.isArray(message2.params?.[0]) ? [[id, result?.body ?? null]] : [id, result?.body ?? null] });
4864
4904
  });
4865
4905
  return;
4866
4906
  }
4867
- if (message.method === "textDocument/publishDiagnostics" && this.launchSpec?.diagnostics) message.params = { ...message.params, diagnostics: this.launchSpec.diagnostics(message.params.diagnostics ?? []) };
4868
- if (message.method === "textDocument/publishDiagnostics" && this.companion) {
4869
- this.primaryDiagnostics.set(message.params.uri, message.params.diagnostics ?? []);
4870
- this.emit(message.method, { ...message.params, diagnostics: [...message.params.diagnostics ?? [], ...this.companionDiagnostics.get(message.params.uri) ?? []] });
4907
+ if (message2.method === "textDocument/publishDiagnostics" && this.launchSpec?.diagnostics) message2.params = { ...message2.params, diagnostics: this.launchSpec.diagnostics(message2.params.diagnostics ?? []) };
4908
+ if (message2.method === "textDocument/publishDiagnostics" && this.companion) {
4909
+ this.primaryDiagnostics.set(message2.params.uri, message2.params.diagnostics ?? []);
4910
+ this.emit(message2.method, { ...message2.params, diagnostics: [...message2.params.diagnostics ?? [], ...this.companionDiagnostics.get(message2.params.uri) ?? []] });
4871
4911
  return;
4872
4912
  }
4873
- if (message.method && !this.options.onServerMessage?.(message.method, message.params)) this.emit(message.method, message.params);
4913
+ if (message2.method && !this.options.onServerMessage?.(message2.method, message2.params)) this.emit(message2.method, message2.params);
4874
4914
  }
4875
4915
  respond(id, result) {
4876
4916
  this.send({ id, result });
@@ -5101,8 +5141,8 @@ async function dependencyRoots(root, signal) {
5101
5141
  for (const base of resolve.resolve.paths(name) ?? []) {
5102
5142
  try {
5103
5143
  const installed = await fs.realpath(path.join(base, name));
5104
- const pkg = JSON.parse(await fs.readFile(path.join(installed, "package.json"), "utf8"));
5105
- if (pkg.name !== name || installed === path.parse(installed).root) break;
5144
+ const pkg2 = JSON.parse(await fs.readFile(path.join(installed, "package.json"), "utf8"));
5145
+ if (pkg2.name !== name || installed === path.parse(installed).root) break;
5106
5146
  if (!roots.has(installed)) {
5107
5147
  roots.add(installed);
5108
5148
  queue.push({ directory: installed, development: false });
@@ -5222,11 +5262,11 @@ const npmPresets = {
5222
5262
  };
5223
5263
  async function resolveLaunch(id, root, installer, signal) {
5224
5264
  if (id === "laravel" || id === "ruff") {
5225
- const binary = await installer.native(id, signal);
5226
- return { executable: binary.executable, args: id === "ruff" ? ["server"] : [], version: binary.version };
5265
+ const binary2 = await installer.native(id, signal);
5266
+ return { executable: binary2.executable, args: id === "ruff" ? ["server"] : [], version: binary2.version };
5227
5267
  }
5228
5268
  if (id === "marksman" || id === "taplo") {
5229
- const binary = await installer.native(id, signal);
5269
+ const binary2 = await installer.native(id, signal);
5230
5270
  const schemaDirectory = path.join(installer.cache, "schemas");
5231
5271
  if (id === "taplo") {
5232
5272
  await fs.mkdir(schemaDirectory, { recursive: true });
@@ -5234,9 +5274,9 @@ async function resolveLaunch(id, root, installer, signal) {
5234
5274
  await fs.writeFile(path.join(schemaDirectory, "cargo-config.json"), JSON.stringify(cargoConfigSchema));
5235
5275
  }
5236
5276
  return {
5237
- executable: binary.executable,
5277
+ executable: binary2.executable,
5238
5278
  args: id === "marksman" ? ["server"] : ["lsp", "stdio"],
5239
- version: binary.version,
5279
+ version: binary2.version,
5240
5280
  ...id === "marksman" ? { env: { DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1" } } : {},
5241
5281
  ...id === "taplo" ? { dependencyRoots: [schemaDirectory], settingsSection: "evenBetterToml", initializationOptions: { cachePath: path.join(installer.cache, "taplo-schemas"), configurationSection: "evenBetterToml" }, settings: { evenBetterToml: { schema: { enabled: true, catalogs: [], associations: {
5242
5282
  "(^|/)Cargo\\.toml$": pathToFileURL(path.join(schemaDirectory, "cargo.json")).href,
@@ -5253,9 +5293,9 @@ async function resolveLaunch(id, root, installer, signal) {
5253
5293
  settings: { xml: { telemetry: { enabled: false }, downloadExternalResources: { enabled: false } } }
5254
5294
  };
5255
5295
  }
5256
- const entry = npmPresets[id];
5257
- if (!entry) throw new Error(`Unknown language server preset: ${id}`);
5258
- const [name, bin, dependencies = []] = entry;
5296
+ const entry2 = npmPresets[id];
5297
+ if (!entry2) throw new Error(`Unknown language server preset: ${id}`);
5298
+ const [name, bin, dependencies = []] = entry2;
5259
5299
  const installation = await installer.npm(id, [name, ...dependencies], signal);
5260
5300
  const modules = path.join(installation.directory, "node_modules");
5261
5301
  const bootstrap = await consoleBootstrap(installer.cache);
@@ -5322,10 +5362,10 @@ function lockedPackages(roots) {
5322
5362
  const selected = /* @__PURE__ */ new Map();
5323
5363
  function visit(location) {
5324
5364
  if (selected.has(location)) return;
5325
- const entry = packages[location];
5326
- if (!entry?.resolved || !entry.integrity || !entry.version) throw new Error(`Missing locked package: ${location}`);
5327
- selected.set(location, entry);
5328
- for (const name of Object.keys({ ...entry.dependencies, ...entry.optionalDependencies })) {
5365
+ const entry2 = packages[location];
5366
+ if (!entry2?.resolved || !entry2.integrity || !entry2.version) throw new Error(`Missing locked package: ${location}`);
5367
+ selected.set(location, entry2);
5368
+ for (const name of Object.keys({ ...entry2.dependencies, ...entry2.optionalDependencies })) {
5329
5369
  let base = location, found;
5330
5370
  while (true) {
5331
5371
  const candidate = `${base ? base + "/" : ""}node_modules/${name}`;
@@ -5337,7 +5377,7 @@ function lockedPackages(roots) {
5337
5377
  base = base.includes("/node_modules/") ? base.slice(0, base.lastIndexOf("/node_modules/")) : "";
5338
5378
  }
5339
5379
  if (found) visit(found);
5340
- else if (!entry.optionalDependencies?.[name]) throw new Error(`Unresolved locked dependency: ${name}`);
5380
+ else if (!entry2.optionalDependencies?.[name]) throw new Error(`Unresolved locked dependency: ${name}`);
5341
5381
  }
5342
5382
  }
5343
5383
  roots.forEach((name) => visit(`node_modules/${name}`));
@@ -5356,13 +5396,13 @@ class ManagedInstaller {
5356
5396
  cache;
5357
5397
  platform;
5358
5398
  download;
5359
- async bytes(url, integrity, signal) {
5360
- if (!url.startsWith("https://")) throw new Error("Managed artifacts require HTTPS");
5399
+ async bytes(url2, integrity, signal) {
5400
+ if (!url2.startsWith("https://")) throw new Error("Managed artifacts require HTTPS");
5361
5401
  let failure2;
5362
5402
  for (let attempt = 0; attempt < 3; attempt++) {
5363
5403
  signal?.throwIfAborted();
5364
5404
  try {
5365
- const response = await this.download(url, { signal: AbortSignal.any([AbortSignal.timeout(12e4), ...signal ? [signal] : []]) });
5405
+ const response = await this.download(url2, { signal: AbortSignal.any([AbortSignal.timeout(12e4), ...signal ? [signal] : []]) });
5366
5406
  if (!response.ok || !response.body) throw new Error(`Artifact download failed (${response.status})`);
5367
5407
  const chunks = [];
5368
5408
  let size = 0;
@@ -5395,13 +5435,13 @@ class ManagedInstaller {
5395
5435
  strict: true,
5396
5436
  preservePaths: false,
5397
5437
  filter(name, candidate) {
5398
- const entry = candidate;
5438
+ const entry2 = candidate;
5399
5439
  const normalized = name.split("/").slice(strip).join("/");
5400
5440
  if (name.startsWith("/") || name.includes("\\") || name.split("/").includes("..")) rejection ??= new Error("Unsafe archive path");
5401
- else if (!["File", "OldFile", "Directory", "SymbolicLink"].includes(entry.type)) rejection ??= new Error("Unsupported archive entry");
5402
- else if (entry.type === "SymbolicLink") {
5403
- const target = path.resolve(directory, path.dirname(normalized), entry.linkpath ?? "");
5404
- if (path.isAbsolute(entry.linkpath ?? "") || !target.startsWith(directory + path.sep)) rejection ??= new Error("Archive link leaves installation");
5441
+ else if (!["File", "OldFile", "Directory", "SymbolicLink"].includes(entry2.type)) rejection ??= new Error("Unsupported archive entry");
5442
+ else if (entry2.type === "SymbolicLink") {
5443
+ const target = path.resolve(directory, path.dirname(normalized), entry2.linkpath ?? "");
5444
+ if (path.isAbsolute(entry2.linkpath ?? "") || !target.startsWith(directory + path.sep)) rejection ??= new Error("Archive link leaves installation");
5405
5445
  }
5406
5446
  return !rejection;
5407
5447
  }
@@ -5485,7 +5525,7 @@ class ManagedInstaller {
5485
5525
  const queue = [...entries].sort(([a], [b]) => a.split("/").length - b.split("/").length);
5486
5526
  for (let start = 0; start < queue.length; start += 6) {
5487
5527
  const batch = queue.slice(start, start + 6);
5488
- const bytes = await Promise.all(batch.map(([, entry]) => this.bytes(entry.resolved, entry.integrity, signal)));
5528
+ const bytes = await Promise.all(batch.map(([, entry2]) => this.bytes(entry2.resolved, entry2.integrity, signal)));
5489
5529
  for (const [index, [location]] of batch.entries()) {
5490
5530
  signal?.throwIfAborted();
5491
5531
  await this.extract(bytes[index], path.join(stage, location), 1);
@@ -5540,17 +5580,17 @@ class JsonSchemas {
5540
5580
  uri.hash = "";
5541
5581
  return uri.href;
5542
5582
  }
5543
- async cached(url) {
5583
+ async cached(url2) {
5544
5584
  try {
5545
- const file = path.join(this.cache, digest(url) + ".json");
5585
+ const file = path.join(this.cache, digest(url2) + ".json");
5546
5586
  if ((await fs.stat(file)).size > maxBytes * 3) return;
5547
5587
  const value = JSON.parse(await fs.readFile(file, "utf8"));
5548
- if (value.url === url && typeof value.text === "string" && typeof value.fetchedAt === "number" && value.integrity === digest(value.text)) return value;
5588
+ if (value.url === url2 && typeof value.text === "string" && typeof value.fetchedAt === "number" && value.integrity === digest(value.text)) return value;
5549
5589
  } catch {
5550
5590
  }
5551
5591
  }
5552
- async fetchJson(url) {
5553
- let current = url;
5592
+ async fetchJson(url2) {
5593
+ let current = url2;
5554
5594
  const signal = AbortSignal.any([this.abort.signal, AbortSignal.timeout(8e3)]);
5555
5595
  for (let redirects = 0; redirects < 6; redirects++) {
5556
5596
  const response = await this.download(current, { signal, redirect: "manual", headers: { Accept: "application/schema+json, application/json" } });
@@ -5584,47 +5624,47 @@ class JsonSchemas {
5584
5624
  throw new Error("Too many JSON schema redirects");
5585
5625
  }
5586
5626
  async remote(value, enabled) {
5587
- const url = this.remoteUri(value), cached2 = await this.cached(url);
5627
+ const url2 = this.remoteUri(value), cached2 = await this.cached(url2);
5588
5628
  if (cached2 && (!enabled || Date.now() - cached2.fetchedAt < maxAge)) return cached2.text;
5589
5629
  if (!enabled) throw new Error("Schema downloads are disabled and this schema is not cached");
5590
- if ((this.failures.get(url) ?? 0) > Date.now()) {
5630
+ if ((this.failures.get(url2) ?? 0) > Date.now()) {
5591
5631
  if (cached2) return cached2.text;
5592
5632
  throw new Error("Schema download is temporarily unavailable");
5593
5633
  }
5594
- let pending = this.pending.get(url);
5634
+ let pending = this.pending.get(url2);
5595
5635
  if (!pending) {
5596
5636
  pending = (async () => {
5597
5637
  try {
5598
- const text2 = await this.fetchJson(url);
5638
+ const text2 = await this.fetchJson(url2);
5599
5639
  await fs.mkdir(this.cache, { recursive: true, mode: 448 });
5600
- const target = path.join(this.cache, digest(url) + ".json"), temporary = target + "." + randomUUID();
5640
+ const target = path.join(this.cache, digest(url2) + ".json"), temporary = target + "." + randomUUID();
5601
5641
  try {
5602
- await fs.writeFile(temporary, JSON.stringify({ url, fetchedAt: Date.now(), integrity: digest(text2), text: text2 }), { mode: 384 });
5642
+ await fs.writeFile(temporary, JSON.stringify({ url: url2, fetchedAt: Date.now(), integrity: digest(text2), text: text2 }), { mode: 384 });
5603
5643
  await fs.rename(temporary, target);
5604
5644
  } finally {
5605
5645
  await fs.rm(temporary, { force: true });
5606
5646
  }
5607
5647
  return text2;
5608
5648
  } catch (error) {
5609
- this.failures.set(url, Date.now() + 6e4);
5649
+ this.failures.set(url2, Date.now() + 6e4);
5610
5650
  if (cached2) return cached2.text;
5611
5651
  throw error;
5612
5652
  } finally {
5613
- this.pending.delete(url);
5653
+ this.pending.delete(url2);
5614
5654
  }
5615
5655
  })();
5616
- this.pending.set(url, pending);
5656
+ this.pending.set(url2, pending);
5617
5657
  }
5618
5658
  return pending;
5619
5659
  }
5620
5660
  async content(uri, enabled = true) {
5621
5661
  if (typeof uri !== "string" || uri.length > 8192) throw new Error("Invalid schema URI");
5622
- const url = new URL(uri);
5623
- url.hash = "";
5624
- if (url.href === SETTINGS_SCHEMA_URI || this.settingsPaths.schemaFile && url.href === pathToFileURL(this.settingsPaths.schemaFile).href) return JSON.stringify(settingsSchema);
5625
- if (url.protocol === "file:") {
5626
- url.hash = "";
5627
- const file = await this.files.resolve(path.relative(this.files.root, fileURLToPath(url)).split(path.sep).join("/"));
5662
+ const url2 = new URL(uri);
5663
+ url2.hash = "";
5664
+ if (url2.href === SETTINGS_SCHEMA_URI || this.settingsPaths.schemaFile && url2.href === pathToFileURL(this.settingsPaths.schemaFile).href) return JSON.stringify(settingsSchema);
5665
+ if (url2.protocol === "file:") {
5666
+ url2.hash = "";
5667
+ const file = await this.files.resolve(path.relative(this.files.root, fileURLToPath(url2)).split(path.sep).join("/"));
5628
5668
  if ((await fs.stat(file)).size > maxBytes) throw new Error("JSON schema exceeds 5 MiB");
5629
5669
  return fs.readFile(file, "utf8");
5630
5670
  }
@@ -6092,24 +6132,24 @@ class ProjectStore {
6092
6132
  } catch {
6093
6133
  }
6094
6134
  const entries = await fs.readdir(directoryPath, { withFileTypes: true }).catch(() => []);
6095
- for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
6096
- const relative = path.posix.join(directory, entry.name);
6097
- if (entry.isSymbolicLink() || entry.name.startsWith(".oxbit-tmp-") || config.intelligence.exclude.some((pattern) => matchesFilePattern(pattern, relative) || matchesFilePattern(pattern, relative + "/"))) continue;
6135
+ for (const entry2 of entries.sort((a, b) => a.name.localeCompare(b.name))) {
6136
+ const relative = path.posix.join(directory, entry2.name);
6137
+ if (entry2.isSymbolicLink() || entry2.name.startsWith(".oxbit-tmp-") || config.intelligence.exclude.some((pattern) => matchesFilePattern(pattern, relative) || matchesFilePattern(pattern, relative + "/"))) continue;
6098
6138
  let ignored = false;
6099
6139
  for (const [base, rules] of ignores) if (!base || relative.startsWith(base + "/")) {
6100
- const match = rules.test(relative.slice(base ? base.length + 1 : 0) + (entry.isDirectory() ? "/" : ""));
6140
+ const match = rules.test(relative.slice(base ? base.length + 1 : 0) + (entry2.isDirectory() ? "/" : ""));
6101
6141
  if (match.ignored) ignored = true;
6102
6142
  else if (match.unignored) ignored = false;
6103
6143
  }
6104
6144
  if (ignored) continue;
6105
- if (entry.isDirectory()) {
6106
- if (!excludedDirectories.has(entry.name)) {
6145
+ if (entry2.isDirectory()) {
6146
+ if (!excludedDirectories.has(entry2.name)) {
6107
6147
  if (queue.length < config.intelligence.maxFiles) queue.push(relative);
6108
6148
  else result.truncated = true;
6109
6149
  }
6110
6150
  continue;
6111
6151
  }
6112
- if (!entry.isFile()) continue;
6152
+ if (!entry2.isFile()) continue;
6113
6153
  if (result.files.length >= config.intelligence.maxFiles) {
6114
6154
  result.truncated = true;
6115
6155
  break;
@@ -6134,12 +6174,12 @@ class ProjectStore {
6134
6174
  const text2 = await fs.readFile(full, "utf8").catch(() => "");
6135
6175
  bytes += stat.size;
6136
6176
  texts.set(full, text2);
6137
- if (entry.name === "package.json" || entry.name === "composer.json") {
6177
+ if (entry2.name === "package.json" || entry2.name === "composer.json") {
6138
6178
  try {
6139
- const data = JSON.parse(text2), ecosystem = entry.name === "package.json" ? "npm" : "composer";
6140
- const pkg = { path: relative, name: typeof data.name === "string" ? data.name : directory || config.name, ecosystem, dependencies: [] };
6141
- for (const kind of ecosystem === "npm" ? ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"] : ["require", "require-dev"]) for (const [name, version] of Object.entries(data[kind] ?? {})) if (typeof version === "string") pkg.dependencies.push({ name, version, kind });
6142
- result.packages.push(pkg);
6179
+ const data = JSON.parse(text2), ecosystem = entry2.name === "package.json" ? "npm" : "composer";
6180
+ const pkg2 = { path: relative, name: typeof data.name === "string" ? data.name : directory || config.name, ecosystem, dependencies: [] };
6181
+ for (const kind of ecosystem === "npm" ? ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"] : ["require", "require-dev"]) for (const [name, version] of Object.entries(data[kind] ?? {})) if (typeof version === "string") pkg2.dependencies.push({ name, version, kind });
6182
+ result.packages.push(pkg2);
6143
6183
  } catch {
6144
6184
  result.warnings.push(`Invalid manifest: ${relative}`);
6145
6185
  }
@@ -6175,7 +6215,7 @@ class ProjectStore {
6175
6215
  const values = [base, ...[".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".mdx", ".vue", ".astro", ".json"].flatMap((ext) => [base + ext, path.join(base, "index" + ext)])];
6176
6216
  return values.find((file) => fullPaths.has(file));
6177
6217
  };
6178
- const workspacePackages = new Map(result.packages.filter((pkg) => pkg.ecosystem === "npm").map((pkg) => [pkg.name, pkg.path]));
6218
+ const workspacePackages = new Map(result.packages.filter((pkg2) => pkg2.ecosystem === "npm").map((pkg2) => [pkg2.name, pkg2.path]));
6179
6219
  const exportPath = (value) => typeof value === "string" ? value : Array.isArray(value) ? value.map(exportPath).find(Boolean) : value && typeof value === "object" ? exportPath(value.types ?? value.import ?? value.default ?? value.require) : void 0;
6180
6220
  let analyzed = 0;
6181
6221
  for (const record of result.files) {
@@ -6206,8 +6246,8 @@ class ProjectStore {
6206
6246
  if (!target && workspaceManifest) {
6207
6247
  const file = path.join(this.files.root, workspaceManifest), manifest = JSON.parse(texts.get(file));
6208
6248
  const subpath = specifier.slice(packageName.length), key = subpath ? "." + subpath : ".";
6209
- const entry = exportPath(manifest.exports?.[key] ?? (!subpath ? manifest.exports : void 0)) ?? (!subpath ? manifest.types ?? manifest.typings ?? manifest.module ?? manifest.main ?? "index" : subpath.slice(1));
6210
- if (typeof entry === "string") target = candidate(path.resolve(path.dirname(file), entry));
6249
+ const entry2 = exportPath(manifest.exports?.[key] ?? (!subpath ? manifest.exports : void 0)) ?? (!subpath ? manifest.types ?? manifest.typings ?? manifest.module ?? manifest.main ?? "index" : subpath.slice(1));
6250
+ if (typeof entry2 === "string") target = candidate(path.resolve(path.dirname(file), entry2));
6211
6251
  }
6212
6252
  return { specifier, ...target && fullPaths.has(target) ? { target: fullPaths.get(target) } : !specifier.startsWith(".") && !specifier.startsWith("/") ? { package: specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0] } : {} };
6213
6253
  });
@@ -6217,7 +6257,7 @@ class ProjectStore {
6217
6257
  for (const name of names) if (name && ts.isIdentifier(name) && record.symbols.length < 100) record.symbols.push({ name: name.text, kind: ts.SyntaxKind[statement.kind], line: ast.getLineAndCharacterOfPosition(name.getStart(ast)).line + 1 });
6218
6258
  }
6219
6259
  }
6220
- const dependencies = new Set(result.packages.flatMap((pkg) => pkg.dependencies.map((item) => item.name)));
6260
+ const dependencies = new Set(result.packages.flatMap((pkg2) => pkg2.dependencies.map((item) => item.name)));
6221
6261
  result.frameworks = ["laravel/framework", "next", "nuxt", "astro", "vue", "react", "@mdx-js/mdx"].filter((name) => dependencies.has(name));
6222
6262
  if (result.truncated) result.warnings.push(`File/directory inventory reached the ${config.intelligence.maxFiles} entry budget`);
6223
6263
  result.warnings = result.warnings.slice(0, 100);
@@ -6769,9 +6809,9 @@ class Collaboration {
6769
6809
  }
6770
6810
  }
6771
6811
  async changed(relative) {
6772
- const entry = this.rooms.get(relative);
6773
- if (!entry) return;
6774
- const room = await entry;
6812
+ const entry2 = this.rooms.get(relative);
6813
+ if (!entry2) return;
6814
+ const room = await entry2;
6775
6815
  if (room.saving) return;
6776
6816
  let disk;
6777
6817
  try {
@@ -6820,9 +6860,9 @@ class Collaboration {
6820
6860
  );
6821
6861
  }
6822
6862
  async leave(connection, relative) {
6823
- for (const [key, entry] of this.rooms) {
6863
+ for (const [key, entry2] of this.rooms) {
6824
6864
  if (relative && relative !== key) continue;
6825
- const room = await entry;
6865
+ const room = await entry2;
6826
6866
  room.members.delete(connection);
6827
6867
  const clients = room.clients.get(connection);
6828
6868
  if (clients)
@@ -6830,7 +6870,7 @@ class Collaboration {
6830
6870
  room.clients.delete(connection);
6831
6871
  if (!room.members.size) {
6832
6872
  await room.persist;
6833
- if (!room.members.size && this.rooms.get(key) === entry) {
6873
+ if (!room.members.size && this.rooms.get(key) === entry2) {
6834
6874
  this.rooms.delete(key);
6835
6875
  this.lsp.closeCanonical(key);
6836
6876
  room.awareness.destroy();
@@ -6840,8 +6880,8 @@ class Collaboration {
6840
6880
  }
6841
6881
  }
6842
6882
  async close() {
6843
- for (const entry of this.rooms.values()) {
6844
- const room = await entry;
6883
+ for (const entry2 of this.rooms.values()) {
6884
+ const room = await entry2;
6845
6885
  await room.persist;
6846
6886
  room.awareness.destroy();
6847
6887
  room.doc.destroy();
@@ -6851,7 +6891,7 @@ class Collaboration {
6851
6891
  }
6852
6892
  const terminal = (state2) => !["pending", "running", "unknown"].includes(state2);
6853
6893
  const object$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
6854
- const str = (value, max = 4096) => typeof value === "string" ? value.slice(0, max) : void 0;
6894
+ const str$1 = (value, max = 4096) => typeof value === "string" ? value.slice(0, max) : void 0;
6855
6895
  const identity = (value) => typeof value === "string" && value.length > 0 && value.length <= 512 && !value.includes("\0");
6856
6896
  const preview = (value, max = 8192) => value === void 0 ? void 0 : (typeof value === "string" ? value : JSON.stringify(value)).slice(0, max);
6857
6897
  const statusNames = {
@@ -6890,7 +6930,7 @@ function normalizeDelegation(provider, update) {
6890
6930
  {
6891
6931
  providerId: sub.threadId,
6892
6932
  toolId,
6893
- name: str(str(sub.path)?.split("/").filter(Boolean).at(-1), 200),
6933
+ name: str$1(str$1(sub.path)?.split("/").filter(Boolean).at(-1), 200),
6894
6934
  state: state(sub.activity === "started" ? "running" : sub.activity),
6895
6935
  evidence: "provider",
6896
6936
  spawn: sub.activity === "started"
@@ -6906,7 +6946,7 @@ function normalizeDelegation(provider, update) {
6906
6946
  return [
6907
6947
  {
6908
6948
  toolId,
6909
- task: str(raw.prompt),
6949
+ task: str$1(raw.prompt),
6910
6950
  state: update.status === "failed" ? "failed" : "unknown",
6911
6951
  evidence: "tool",
6912
6952
  spawn: true
@@ -6915,11 +6955,11 @@ function normalizeDelegation(provider, update) {
6915
6955
  return ids.map((providerId) => ({
6916
6956
  providerId,
6917
6957
  toolId,
6918
- parent: str(collab.senderThreadId, 512),
6919
- task: str(raw.prompt),
6920
- model: str(raw.model, 200),
6958
+ parent: str$1(collab.senderThreadId, 512),
6959
+ task: str$1(raw.prompt),
6960
+ model: str$1(raw.model, 200),
6921
6961
  state: state(states[providerId]?.status) ?? (collab.tool === "spawnAgent" && update.status === "failed" ? "failed" : void 0),
6922
- result: str(states[providerId]?.message, 16384),
6962
+ result: str$1(states[providerId]?.message, 16384),
6923
6963
  evidence: "provider",
6924
6964
  spawn: collab.tool === "spawnAgent",
6925
6965
  reopen: ["resumeAgent", "sendInput", "followupTask"].includes(collab.tool) && ["running", "pendingInit"].includes(states[providerId]?.status)
@@ -6933,9 +6973,9 @@ function normalizeDelegation(provider, update) {
6933
6973
  {
6934
6974
  toolId,
6935
6975
  providerId: identity(task.agentId ?? outcome.agentId) ? task.agentId ?? outcome.agentId : void 0,
6936
- name: str(task.description, 200),
6937
- task: str(task.prompt),
6938
- model: str(task.model, 200),
6976
+ name: str$1(task.description, 200),
6977
+ task: str$1(task.prompt),
6978
+ model: str$1(task.model, 200),
6939
6979
  durationMs: Number.isFinite(task.durationMs ?? outcome.durationMs) && (task.durationMs ?? outcome.durationMs) >= 0 ? task.durationMs ?? outcome.durationMs : void 0,
6940
6980
  state: state(outcomeName),
6941
6981
  result: preview(task.result ?? outcome.result ?? output.result),
@@ -6948,9 +6988,9 @@ function normalizeDelegation(provider, update) {
6948
6988
  return [
6949
6989
  {
6950
6990
  toolId,
6951
- name: str(raw.description ?? raw.subagent_type, 200),
6952
- task: str(raw.prompt),
6953
- model: str(raw.model, 200),
6991
+ name: str$1(raw.description ?? raw.subagent_type, 200),
6992
+ task: str$1(raw.prompt),
6993
+ model: str$1(raw.model, 200),
6954
6994
  state: update.status === "failed" ? "failed" : "unknown",
6955
6995
  evidence: "tool",
6956
6996
  spawn: true
@@ -7109,8 +7149,8 @@ class SubagentRegistry {
7109
7149
  parentId: this.idForSession(sessionId),
7110
7150
  sessionId: child,
7111
7151
  providerId: child,
7112
- name: str(update.name, 200) || record.name,
7113
- task: str(update.task) ?? record.task,
7152
+ name: str$1(update.name, 200) || record.name,
7153
+ task: str$1(update.task) ?? record.task,
7114
7154
  evidence: "native",
7115
7155
  visibility: "full"
7116
7156
  });
@@ -7232,20 +7272,20 @@ class SubagentRegistry {
7232
7272
  }
7233
7273
  toolActivity(record, update) {
7234
7274
  const id = `tool:${update.toolCallId}`;
7235
- let entry = record.activity.find((a) => a.id === id);
7236
- if (!entry) {
7237
- entry = { id, kind: "tool" };
7238
- record.activity.push(entry);
7239
- }
7240
- entry.title = str(update.title, 200) ?? entry.title ?? "Delegated task";
7241
- if (!["completed", "failed"].includes(entry.status ?? ""))
7242
- entry.status = str(update.status, 40) ?? entry.status;
7243
- entry.input = preview(update.rawInput) ?? entry.input;
7244
- entry.output = preview(toolOutput(update)) ?? entry.output;
7245
- entry.locations = Array.isArray(update.locations) ? update.locations.filter((l) => typeof l.path === "string").slice(0, 16).map((l) => ({
7275
+ let entry2 = record.activity.find((a) => a.id === id);
7276
+ if (!entry2) {
7277
+ entry2 = { id, kind: "tool" };
7278
+ record.activity.push(entry2);
7279
+ }
7280
+ entry2.title = str$1(update.title, 200) ?? entry2.title ?? "Delegated task";
7281
+ if (!["completed", "failed"].includes(entry2.status ?? ""))
7282
+ entry2.status = str$1(update.status, 40) ?? entry2.status;
7283
+ entry2.input = preview(update.rawInput) ?? entry2.input;
7284
+ entry2.output = preview(toolOutput(update)) ?? entry2.output;
7285
+ entry2.locations = Array.isArray(update.locations) ? update.locations.filter((l) => typeof l.path === "string").slice(0, 16).map((l) => ({
7246
7286
  path: l.path.slice(0, 4096),
7247
7287
  line: Number.isSafeInteger(l.line) ? l.line : void 0
7248
- })) : entry.locations;
7288
+ })) : entry2.locations;
7249
7289
  }
7250
7290
  activity(sessionId, update) {
7251
7291
  const id = this.idForSession(sessionId);
@@ -7264,24 +7304,24 @@ class SubagentRegistry {
7264
7304
  "user_message_chunk"
7265
7305
  ].includes(update.sessionUpdate)) {
7266
7306
  const role = update.sessionUpdate === "agent_thought_chunk" ? "thought" : update.sessionUpdate === "user_message_chunk" ? "user" : "agent";
7267
- const content = str(update.content?.text ?? update.content?.resource?.text, 16384) ?? "";
7307
+ const content = str$1(update.content?.text ?? update.content?.resource?.text, 16384) ?? "";
7268
7308
  const previous = record.activity.at(-1);
7269
- let entry = identity(update.messageId) ? record.activity.find(
7309
+ let entry2 = identity(update.messageId) ? record.activity.find(
7270
7310
  (a) => a.kind === "message" && a.id === `message:provider:${update.messageId}`
7271
7311
  ) : previous?.kind === "message" && previous.role === role ? previous : void 0;
7272
- if (!entry) {
7273
- entry = {
7312
+ if (!entry2) {
7313
+ entry2 = {
7274
7314
  id: identity(update.messageId) ? `message:provider:${update.messageId}` : `message:local:${++this.serial}`,
7275
7315
  kind: "message",
7276
7316
  role,
7277
7317
  text: ""
7278
7318
  };
7279
- record.activity.push(entry);
7319
+ record.activity.push(entry2);
7280
7320
  }
7281
- if (entry.text.length + content.length > 16384) record.truncated = true;
7282
- entry.text = (entry.text + content).slice(-16384);
7321
+ if (entry2.text.length + content.length > 16384) record.truncated = true;
7322
+ entry2.text = (entry2.text + content).slice(-16384);
7283
7323
  if (role === "agent") {
7284
- record.result = entry.text;
7324
+ record.result = entry2.text;
7285
7325
  phase = "responding";
7286
7326
  } else if (role === "thought") phase = "thinking";
7287
7327
  } else if (["tool_call", "tool_call_update"].includes(update.sessionUpdate) && identity(update.toolCallId)) {
@@ -7319,12 +7359,12 @@ class SubagentRegistry {
7319
7359
  const id = this.idForSession(sessionId), record = id && this.records.get(id);
7320
7360
  if (!record) return;
7321
7361
  const entryId = `terminal:${terminalId}`;
7322
- const entry = record.activity.find((a) => a.id === entryId) ?? {
7362
+ const entry2 = record.activity.find((a) => a.id === entryId) ?? {
7323
7363
  id: entryId,
7324
7364
  kind: "terminal"
7325
7365
  };
7326
- if (!record.activity.includes(entry)) record.activity.push(entry);
7327
- Object.assign(entry, {
7366
+ if (!record.activity.includes(entry2)) record.activity.push(entry2);
7367
+ Object.assign(entry2, {
7328
7368
  title: title2.slice(0, 200),
7329
7369
  text: output.slice(-8192),
7330
7370
  status: ended ? "completed" : "in_progress"
@@ -7350,7 +7390,7 @@ class SubagentRegistry {
7350
7390
  let cached;
7351
7391
  function runtimeVersion() {
7352
7392
  if (cached) return cached;
7353
- return cached = "0.7.1";
7393
+ return cached = "0.8.0";
7354
7394
  }
7355
7395
  const MAX_REQUEST_BYTES = 1024 * 1024;
7356
7396
  const MAX_CONCURRENT_REQUESTS = 16;
@@ -7475,7 +7515,7 @@ function validateArguments(name, args) {
7475
7515
  if (args.line !== void 0 && (!Number.isSafeInteger(args.line) || args.line < 1)) return "line must be a positive integer";
7476
7516
  if (args.limit !== void 0 && (!Number.isSafeInteger(args.limit) || args.limit < 1)) return "limit must be a positive integer";
7477
7517
  if (name === "oxbit_propose_edit" && typeof args.content !== "string") return "content must be a string";
7478
- if (name === "oxbit_update_plan" && (!Array.isArray(args.entries) || args.entries.length > 50 || args.entries.some((entry) => !object(entry) || typeof entry.content !== "string" || !["pending", "in_progress", "completed"].includes(String(entry.status)) || entry.priority !== void 0 && typeof entry.priority !== "string"))) return "entries must contain plan content and status";
7518
+ if (name === "oxbit_update_plan" && (!Array.isArray(args.entries) || args.entries.length > 50 || args.entries.some((entry2) => !object(entry2) || typeof entry2.content !== "string" || !["pending", "in_progress", "completed"].includes(String(entry2.status)) || entry2.priority !== void 0 && typeof entry2.priority !== "string"))) return "entries must contain plan content and status";
7479
7519
  return void 0;
7480
7520
  }
7481
7521
  function reply(response, status2, body) {
@@ -7487,8 +7527,8 @@ function reply(response, status2, body) {
7487
7527
  response.setHeader("content-type", "application/json; charset=utf-8");
7488
7528
  response.end(JSON.stringify(body));
7489
7529
  }
7490
- function rpcError(id, code, message) {
7491
- return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
7530
+ function rpcError(id, code, message2) {
7531
+ return { jsonrpc: "2.0", id: id ?? null, error: { code, message: message2 } };
7492
7532
  }
7493
7533
  class AgentMCP {
7494
7534
  constructor(handler) {
@@ -7573,25 +7613,25 @@ class AgentMCP {
7573
7613
  if (length > MAX_REQUEST_BYTES) return reply(response, 413);
7574
7614
  chunks.push(chunk);
7575
7615
  }
7576
- let message;
7616
+ let message2;
7577
7617
  try {
7578
- message = JSON.parse(Buffer.concat(chunks).toString("utf8"));
7618
+ message2 = JSON.parse(Buffer.concat(chunks).toString("utf8"));
7579
7619
  } catch {
7580
7620
  return reply(response, 400, rpcError(null, -32700, "Invalid JSON"));
7581
7621
  }
7582
- if (!object(message) || message.jsonrpc !== "2.0" || typeof message.method !== "string" || message.id !== void 0 && typeof message.id !== "string" && typeof message.id !== "number") return reply(response, 400, rpcError(object(message) ? message.id : null, -32600, "Invalid request"));
7622
+ if (!object(message2) || message2.jsonrpc !== "2.0" || typeof message2.method !== "string" || message2.id !== void 0 && typeof message2.id !== "string" && typeof message2.id !== "number") return reply(response, 400, rpcError(object(message2) ? message2.id : null, -32600, "Invalid request"));
7583
7623
  const version = request.headers["mcp-protocol-version"];
7584
- if (version && !PROTOCOL_VERSIONS.includes(version)) return reply(response, 400, rpcError(message.id, -32600, "Unsupported MCP protocol version"));
7585
- if (message.id === void 0) return reply(response, 202);
7586
- const id = message.id;
7587
- if (message.method === "initialize") {
7588
- const requested = object(message.params) && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0;
7624
+ if (version && !PROTOCOL_VERSIONS.includes(version)) return reply(response, 400, rpcError(message2.id, -32600, "Unsupported MCP protocol version"));
7625
+ if (message2.id === void 0) return reply(response, 202);
7626
+ const id = message2.id;
7627
+ if (message2.method === "initialize") {
7628
+ const requested = object(message2.params) && typeof message2.params.protocolVersion === "string" ? message2.params.protocolVersion : void 0;
7589
7629
  return reply(response, 200, { jsonrpc: "2.0", id, result: { protocolVersion: requested && PROTOCOL_VERSIONS.includes(requested) ? requested : PROTOCOL_VERSIONS[0], capabilities: { tools: { listChanged: false } }, serverInfo: { name: "oxbit-workspace", version: runtimeVersion() } } });
7590
7630
  }
7591
- if (message.method === "ping") return reply(response, 200, { jsonrpc: "2.0", id, result: {} });
7592
- if (message.method === "tools/list") return reply(response, 200, { jsonrpc: "2.0", id, result: { tools: NATIVE_AGENT_TOOLS } });
7593
- if (message.method !== "tools/call") return reply(response, 200, rpcError(id, -32601, "Method not found"));
7594
- const params = message.params;
7631
+ if (message2.method === "ping") return reply(response, 200, { jsonrpc: "2.0", id, result: {} });
7632
+ if (message2.method === "tools/list") return reply(response, 200, { jsonrpc: "2.0", id, result: { tools: NATIVE_AGENT_TOOLS } });
7633
+ if (message2.method !== "tools/call") return reply(response, 200, rpcError(id, -32601, "Method not found"));
7634
+ const params = message2.params;
7595
7635
  if (!object(params) || typeof params.name !== "string" || !object(params.arguments ?? {})) return reply(response, 200, rpcError(id, -32602, "Invalid tool call"));
7596
7636
  const args = params.arguments ?? {};
7597
7637
  const invalid2 = validateArguments(params.name, args);
@@ -7612,7 +7652,332 @@ class AgentMCP {
7612
7652
  }
7613
7653
  }
7614
7654
  }
7655
+ const run$1 = promisify(execFile);
7656
+ const TTL = 3e5, MiB = 1024 * 1024, MAX_LISTING = 2 * MiB, MAX_ARCHIVE = 512 * MiB;
7657
+ const REGISTRY_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
7658
+ const VERSION = /^[A-Za-z0-9][A-Za-z0-9.+_-]{0,63}$/;
7659
+ const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
7660
+ const TARGETS = ["darwin", "linux", "windows"].flatMap((os2) => [`${os2}-aarch64`, `${os2}-x86_64`]);
7661
+ const ARCHIVES = [".tar.gz", ".tgz", ".tar.bz2", ".tar.xz", ".zip"];
7662
+ const invalid = () => {
7663
+ throw new Error("invalid");
7664
+ };
7665
+ const plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7666
+ const str = (value, max) => typeof value === "string" && value && value.length <= max && !value.includes("\0") ? value : invalid();
7667
+ const optional = (value, max) => value === void 0 ? void 0 : str(value, max);
7668
+ const list = (value) => value === void 0 ? [] : Array.isArray(value) && value.length <= 128 && value.every((v) => typeof v === "string" && v.length <= 16384 && !v.includes("\0")) ? value : invalid();
7669
+ function env(value) {
7670
+ if (value === void 0) return {};
7671
+ if (!plain(value)) return invalid();
7672
+ const entries = Object.entries(value);
7673
+ if (entries.length > 64 || entries.some(([k, v]) => !ENV_KEY.test(k) || typeof v !== "string" || v.length > 16384 || v.includes("\0"))) return invalid();
7674
+ return Object.fromEntries(entries);
7675
+ }
7676
+ function url(value) {
7677
+ const parsed = new URL(str(value, 4096));
7678
+ return ["https:", "http:"].includes(parsed.protocol) ? parsed.href : invalid();
7679
+ }
7680
+ function pkg(value) {
7681
+ if (value === void 0) return;
7682
+ if (!plain(value)) return invalid();
7683
+ const name = str(value.package, 512);
7684
+ if (name.startsWith("-")) return invalid();
7685
+ return { package: name, args: list(value.args), env: env(value.env) };
7686
+ }
7687
+ function binary(value) {
7688
+ if (value === void 0) return;
7689
+ if (!plain(value)) return invalid();
7690
+ const targets = {};
7691
+ for (const [key, t] of Object.entries(value)) {
7692
+ if (!plain(t)) return invalid();
7693
+ const sha256 = t.sha256 === void 0 ? void 0 : typeof t.sha256 === "string" && /^[a-f0-9]{64}$/i.test(t.sha256) ? t.sha256.toLowerCase() : invalid();
7694
+ const target = { archive: url(t.archive), cmd: str(t.cmd, 1024), args: list(t.args), env: env(t.env), sha256 };
7695
+ if (TARGETS.includes(key)) targets[key] = target;
7696
+ }
7697
+ return targets;
7698
+ }
7699
+ function entry(value) {
7700
+ if (!plain(value) || !plain(value.distribution)) return invalid();
7701
+ const id = str(value.id, 64), version = str(value.version, 64);
7702
+ if (!REGISTRY_ID.test(id) || !VERSION.test(version)) return invalid();
7703
+ if (typeof value.description !== "string" || value.description.length > 4e3) return invalid();
7704
+ const d = value.distribution;
7705
+ const distribution = { npx: pkg(d.npx), uvx: pkg(d.uvx), binary: binary(d.binary) };
7706
+ if (!distribution.npx && !distribution.uvx && !distribution.binary) return invalid();
7707
+ return {
7708
+ id,
7709
+ version,
7710
+ name: str(value.name, 200),
7711
+ description: value.description,
7712
+ repository: optional(value.repository, 2048),
7713
+ website: optional(value.website, 2048),
7714
+ license: optional(value.license, 200),
7715
+ distribution
7716
+ };
7717
+ }
7718
+ function parseRegistry(text2) {
7719
+ const value = JSON.parse(text2);
7720
+ if (!plain(value) || !Array.isArray(value.agents)) throw new Error("The ACP Registry response has no agents list");
7721
+ const agents = /* @__PURE__ */ new Map();
7722
+ for (const item of value.agents.slice(0, 2e3)) {
7723
+ try {
7724
+ const agent = entry(item);
7725
+ if (!agents.has(agent.id)) agents.set(agent.id, agent);
7726
+ } catch {
7727
+ }
7728
+ }
7729
+ return [...agents.values()];
7730
+ }
7731
+ function platformTarget(platform = process.platform, arch = process.arch) {
7732
+ const os2 = { darwin: "darwin", linux: "linux", win32: "windows" }[platform];
7733
+ const cpu = { arm64: "aarch64", x64: "x86_64" }[arch];
7734
+ return os2 && cpu ? `${os2}-${cpu}` : void 0;
7735
+ }
7736
+ function pickDistribution(agent, target = platformTarget()) {
7737
+ const { npx, uvx, binary: binary2 } = agent.distribution;
7738
+ if (npx) return { kind: "npx", spec: npx };
7739
+ if (uvx) return { kind: "uvx", spec: uvx };
7740
+ const spec = target ? binary2?.[target] : void 0;
7741
+ if (spec) return { kind: "binary", spec };
7742
+ }
7743
+ async function* capped(body, max, what) {
7744
+ let length = 0;
7745
+ for await (const chunk of body) {
7746
+ length += chunk.length;
7747
+ if (length > max) throw new Error(`${what} exceeds ${max / MiB} MiB`);
7748
+ yield chunk;
7749
+ }
7750
+ }
7751
+ const exists = (file) => fs.stat(file).then(() => true, () => false);
7752
+ const message = (error) => error instanceof Error ? error.message : String(error);
7753
+ async function installedCommand(dir, cmd) {
7754
+ const root = await fs.realpath(dir);
7755
+ const inside = (file) => file.startsWith(root + path.sep);
7756
+ const escape = new RpcError("INVALID_PARAMS", `The agent command ${cmd} resolves outside its install directory`);
7757
+ const target = path.resolve(root, cmd);
7758
+ if (!inside(target)) throw escape;
7759
+ const real = await fs.realpath(target).catch(() => {
7760
+ throw new RpcError("INVALID_PARAMS", `The agent archive has no ${cmd}`);
7761
+ });
7762
+ if (!inside(real)) throw escape;
7763
+ const stat = await fs.stat(real);
7764
+ if (!stat.isFile()) throw new RpcError("INVALID_PARAMS", `The agent command ${cmd} is not a file`);
7765
+ if (process.platform !== "win32") await fs.chmod(real, stat.mode | 73);
7766
+ return real;
7767
+ }
7768
+ function untilAborted(promise, signal) {
7769
+ if (!signal) return promise;
7770
+ if (signal.aborted) return Promise.reject(new Error("Agent connection cancelled"));
7771
+ return new Promise((resolve, reject) => {
7772
+ const stop2 = () => reject(new Error("Agent connection cancelled"));
7773
+ signal.addEventListener("abort", stop2, { once: true });
7774
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", stop2));
7775
+ });
7776
+ }
7777
+ async function extract(archive, dest, format, signal) {
7778
+ const tar = ["tar", ["-xf", archive, "-C", dest]];
7779
+ const attempts = format !== ".zip" ? [tar] : [...process.platform === "darwin" ? [tar] : [], ["unzip", ["-q", archive, "-d", dest]]];
7780
+ const missing2 = [];
7781
+ let failure2;
7782
+ for (const [tool, args] of attempts) {
7783
+ await fs.rm(dest, { recursive: true, force: true });
7784
+ await fs.mkdir(dest);
7785
+ try {
7786
+ await run$1(tool, args, { signal, maxBuffer: MiB });
7787
+ return;
7788
+ } catch (error) {
7789
+ if (signal?.aborted) throw new Error("Agent connection cancelled");
7790
+ if (error.code === "ENOENT") missing2.push(tool);
7791
+ else failure2 = new Error(`${tool} could not extract the agent archive: ${String(error.stderr || message(error)).trim().slice(0, 500)}`);
7792
+ }
7793
+ }
7794
+ throw failure2 ?? new RpcError("INVALID_PARAMS", `Install ${missing2.join(" or ")} to extract this agent archive`);
7795
+ }
7796
+ class ACPRegistry {
7797
+ constructor(dataDir, url2 = ACP_REGISTRY_URL, download = fetch, target = platformTarget()) {
7798
+ this.dataDir = dataDir;
7799
+ this.url = url2;
7800
+ this.download = download;
7801
+ this.target = target;
7802
+ this.installRoot = path.join(dataDir, "acp-agents");
7803
+ }
7804
+ dataDir;
7805
+ url;
7806
+ download;
7807
+ target;
7808
+ fetching;
7809
+ installs = /* @__PURE__ */ new Map();
7810
+ installRoot;
7811
+ get cacheFile() {
7812
+ return path.join(this.dataDir, "acp-registry.json");
7813
+ }
7814
+ checkUrl(value) {
7815
+ const parsed = new URL(value), registry = new URL(this.url);
7816
+ const loopback = (u) => u.protocol === "http:" && u.hostname === "127.0.0.1";
7817
+ if (parsed.protocol === "https:" || loopback(registry) && loopback(parsed)) return parsed.href;
7818
+ throw new Error(`ACP agent downloads require HTTPS (${parsed.origin})`);
7819
+ }
7820
+ async open(source2, signal, max, what) {
7821
+ let current = this.checkUrl(source2);
7822
+ for (let redirects = 0; ; redirects++) {
7823
+ const response = await this.download(current, { signal, redirect: "manual" });
7824
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
7825
+ await response.body?.cancel();
7826
+ const location = response.headers.get("location");
7827
+ if (!location) throw new Error(`${what} redirect has no location`);
7828
+ if (redirects >= 5) throw new Error(`${what} has too many redirects`);
7829
+ current = this.checkUrl(new URL(location, current).href);
7830
+ continue;
7831
+ }
7832
+ if (!response.ok || !response.body) {
7833
+ await response.body?.cancel();
7834
+ throw new Error(`${what} download failed (HTTP ${response.status})`);
7835
+ }
7836
+ if (Number(response.headers.get("content-length")) > max) {
7837
+ await response.body.cancel();
7838
+ throw new Error(`${what} exceeds ${max / MiB} MiB`);
7839
+ }
7840
+ return capped(response.body, max, what);
7841
+ }
7842
+ }
7843
+ async cached() {
7844
+ try {
7845
+ const value = JSON.parse(await fs.readFile(this.cacheFile, "utf8"));
7846
+ if (value?.url === this.url && typeof value.text === "string" && !Number.isNaN(Date.parse(value.fetchedAt)))
7847
+ return { agents: parseRegistry(value.text), fetchedAt: value.fetchedAt };
7848
+ } catch {
7849
+ }
7850
+ }
7851
+ async fetchRegistry() {
7852
+ const chunks = [];
7853
+ for await (const chunk of await this.open(this.url, AbortSignal.timeout(15e3), MAX_LISTING, "The ACP Registry")) chunks.push(chunk);
7854
+ const text2 = Buffer.concat(chunks).toString("utf8");
7855
+ const agents = parseRegistry(text2), fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
7856
+ await fs.mkdir(this.dataDir, { recursive: true, mode: 448 });
7857
+ const temp = `${this.cacheFile}.${process.pid}.tmp`;
7858
+ await fs.writeFile(temp, JSON.stringify({ url: this.url, fetchedAt, text: text2 }), { mode: 384 });
7859
+ await fs.rename(temp, this.cacheFile);
7860
+ return { agents, fetchedAt };
7861
+ }
7862
+ async load(refresh = false) {
7863
+ const cached2 = await this.cached();
7864
+ const age = cached2 ? Date.now() - Date.parse(cached2.fetchedAt) : Infinity;
7865
+ if (cached2 && !refresh && age >= 0 && age < TTL) return cached2;
7866
+ try {
7867
+ this.fetching ??= this.fetchRegistry().finally(() => this.fetching = void 0);
7868
+ return await this.fetching;
7869
+ } catch (error) {
7870
+ const reason = `Could not reach the ACP Registry: ${message(error)}`;
7871
+ if (cached2) return { ...cached2, error: reason };
7872
+ throw new RpcError("UNAVAILABLE", reason);
7873
+ }
7874
+ }
7875
+ dir(agent) {
7876
+ return path.join(this.installRoot, agent.id, agent.version);
7877
+ }
7878
+ unavailable(picked) {
7879
+ if (!picked) return "No build for this runtime's platform";
7880
+ if (picked.kind !== "binary") return void 0;
7881
+ if (!picked.spec.sha256) return "The registry publishes no checksum for this build";
7882
+ const file = new URL(picked.spec.archive).pathname.toLowerCase();
7883
+ if (!ARCHIVES.some((ext) => file.endsWith(ext))) return "Oxbit cannot extract this build's archive format";
7884
+ }
7885
+ async listing(refresh = false) {
7886
+ const { agents, fetchedAt, error } = await this.load(refresh);
7887
+ const listed = await Promise.all(agents.map(async (agent) => {
7888
+ const picked = pickDistribution(agent, this.target), builtin = acpBuiltinForRegistry(agent.id)?.id;
7889
+ const { npx, uvx } = agent.distribution;
7890
+ const distribution = picked?.kind ?? (npx ? "npx" : uvx ? "uvx" : "binary");
7891
+ const reason = this.unavailable(picked);
7892
+ return {
7893
+ id: agent.id,
7894
+ name: agent.name,
7895
+ version: agent.version,
7896
+ description: agent.description,
7897
+ ...agent.repository ? { repository: agent.repository } : {},
7898
+ ...agent.website ? { website: agent.website } : {},
7899
+ ...agent.license ? { license: agent.license } : {},
7900
+ distribution,
7901
+ available: !reason,
7902
+ ...reason ? { reason } : {},
7903
+ ...picked?.kind === "binary" ? { installed: await exists(this.dir(agent)) } : {},
7904
+ ...builtin ? { builtin } : {}
7905
+ };
7906
+ }));
7907
+ return { agents: listed, fetchedAt, ...error ? { error } : {} };
7908
+ }
7909
+ async resolve(id, version, signal) {
7910
+ const agent = (await this.load()).agents.find((a) => a.id === id);
7911
+ if (!agent) throw new RpcError("INVALID_PARAMS", "Unknown agent");
7912
+ if (version !== void 0 && version !== agent.version)
7913
+ throw new RpcError("INVALID_PARAMS", `${agent.name} ${version} is no longer in the ACP Registry; refresh the agent list`);
7914
+ const picked = pickDistribution(agent, this.target);
7915
+ if (!picked) throw new RpcError("INVALID_PARAMS", `${agent.name} has no ACP Registry build for ${this.target ?? `${process.platform}-${process.arch}`}`);
7916
+ const base = { name: agent.name, env: picked.spec.env };
7917
+ if (picked.kind !== "binary") {
7918
+ const { package: name, args } = picked.spec;
7919
+ return { ...base, command: picked.kind, args: picked.kind === "npx" ? ["-y", name, ...args] : [name, ...args] };
7920
+ }
7921
+ return { ...base, command: await this.install(agent, picked.spec, signal), args: picked.spec.args };
7922
+ }
7923
+ /** Concurrent callers share one download, which stops only after every caller has cancelled. */
7924
+ async install(agent, spec, signal) {
7925
+ const key = `${agent.id}@${agent.version}`;
7926
+ let shared = this.installs.get(key);
7927
+ if (!shared) {
7928
+ const abort = new AbortController();
7929
+ const promise = this.installOnce(agent, spec, abort.signal).finally(() => this.installs.delete(key));
7930
+ promise.catch(() => {
7931
+ });
7932
+ shared = { promise, abort, waiters: 0 };
7933
+ this.installs.set(key, shared);
7934
+ }
7935
+ const install = shared;
7936
+ install.waiters++;
7937
+ try {
7938
+ return await untilAborted(install.promise, signal);
7939
+ } finally {
7940
+ if (--install.waiters === 0 && signal?.aborted) install.abort.abort();
7941
+ }
7942
+ }
7943
+ async installOnce(agent, spec, signal) {
7944
+ const dir = this.dir(agent);
7945
+ if (await exists(dir)) return installedCommand(dir, spec.cmd);
7946
+ if (!spec.sha256) throw new RpcError("INVALID_PARAMS", `${agent.name} has no sha256 checksum in the ACP Registry, so Oxbit does not install it`);
7947
+ const file = new URL(spec.archive).pathname.toLowerCase();
7948
+ const format = ARCHIVES.find((ext) => file.endsWith(ext));
7949
+ if (!format) throw new RpcError("INVALID_PARAMS", `Oxbit cannot extract ${path.posix.basename(file)}; supported archives are ${ARCHIVES.join(", ")}`);
7950
+ await fs.mkdir(path.dirname(dir), { recursive: true, mode: 448 });
7951
+ const temp = await fs.mkdtemp(path.join(path.dirname(dir), `.${agent.version}-`));
7952
+ try {
7953
+ const archive = path.join(temp, `archive${format}`), files = path.join(temp, "files");
7954
+ const hash2 = createHash("sha256");
7955
+ const body = await this.open(spec.archive, signal, MAX_ARCHIVE, `The ${agent.name} archive`);
7956
+ await pipeline(async function* () {
7957
+ for await (const chunk of body) {
7958
+ hash2.update(chunk);
7959
+ yield chunk;
7960
+ }
7961
+ }, createWriteStream(archive, { mode: 384 }), { signal });
7962
+ if (hash2.digest("hex") !== spec.sha256)
7963
+ throw new RpcError("INVALID_PARAMS", `The ${agent.name} archive does not match its sha256 checksum`);
7964
+ await extract(archive, files, format, signal);
7965
+ await installedCommand(files, spec.cmd);
7966
+ await fs.rename(files, dir).catch(async (error) => {
7967
+ if (!await exists(dir)) throw error;
7968
+ });
7969
+ return await installedCommand(dir, spec.cmd);
7970
+ } finally {
7971
+ await fs.rm(temp, { recursive: true, force: true });
7972
+ }
7973
+ }
7974
+ }
7615
7975
  const MAX_BYTES = 1024 * 1024;
7976
+ const IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
7977
+ const MAX_IMAGES = 4;
7978
+ const MAX_IMAGE_DATA = Math.ceil(5 * 1024 * 1024 / 3) * 4;
7979
+ const MAX_QUEUED_IMAGE_DATA = 16 * 1024 * 1024;
7980
+ const withoutImageData = (key, value) => key === "data" ? void 0 : value;
7616
7981
  const EDITOR_UNSUPPORTED = "The connected editor does not support this tool; update Oxbit";
7617
7982
  function strings(value) {
7618
7983
  if (!Array.isArray(value) || value.length > 128 || value.some(
@@ -7626,14 +7991,16 @@ function strings(value) {
7626
7991
  }
7627
7992
  class AgentACP {
7628
7993
  constructor(files, emit, stateChanged = () => {
7629
- }) {
7994
+ }, acpRegistry) {
7630
7995
  this.files = files;
7631
7996
  this.emit = emit;
7632
7997
  this.stateChanged = stateChanged;
7998
+ this.acpRegistry = acpRegistry;
7633
7999
  }
7634
8000
  files;
7635
8001
  emit;
7636
8002
  stateChanged;
8003
+ acpRegistry;
7637
8004
  agents = /* @__PURE__ */ new Map();
7638
8005
  mcp = new AgentMCP((id, name, args) => this.tool(id, name, args));
7639
8006
  get(owner, id) {
@@ -7642,8 +8009,8 @@ class AgentACP {
7642
8009
  throw new RpcError("NOT_FOUND", "Agent connection has ended");
7643
8010
  return agent;
7644
8011
  }
7645
- send(agent, message) {
7646
- const data = JSON.stringify({ jsonrpc: "2.0", ...message }) + "\n";
8012
+ send(agent, message2) {
8013
+ const data = JSON.stringify({ jsonrpc: "2.0", ...message2 }) + "\n";
7647
8014
  if (agent.stopped || !agent.child.stdin.writable)
7648
8015
  throw new Error("Agent process has ended");
7649
8016
  if (Buffer.byteLength(data) > 2 * MAX_BYTES || agent.child.stdin.writableLength > MAX_BYTES)
@@ -7672,9 +8039,9 @@ class AgentACP {
7672
8039
  publish(agent, event, params) {
7673
8040
  const payload = { id: agent.connection.id, ...params, seq: ++agent.eventSequence };
7674
8041
  if (["acp.update", "acp.subagent", "acp.terminal", "acp.turnStarted", "acp.turnEnded"].includes(event)) {
7675
- const entry = JSON.parse(JSON.stringify({ name: event, params: payload }));
7676
- agent.events.push(entry);
7677
- agent.eventBytes += Buffer.byteLength(JSON.stringify(entry));
8042
+ const entry2 = JSON.parse(JSON.stringify({ name: event, params: payload }));
8043
+ agent.events.push(entry2);
8044
+ agent.eventBytes += Buffer.byteLength(JSON.stringify(entry2));
7678
8045
  while (agent.events.length > 2e3 || agent.eventBytes > 2 * MAX_BYTES) {
7679
8046
  agent.eventBytes -= Buffer.byteLength(JSON.stringify(agent.events.shift()));
7680
8047
  agent.truncated = true;
@@ -7742,27 +8109,15 @@ class AgentACP {
7742
8109
  return [...this.agents.values()].some((a) => this.working(a));
7743
8110
  }
7744
8111
  async start(owner, launch, signal, client = owner) {
7745
- const preset2 = ACP_PROVIDERS.find((p) => p.id === launch.provider);
7746
- if (!preset2)
7747
- throw new RpcError("INVALID_PARAMS", "Choose Codex, Cursor, or Amp");
7748
8112
  if (signal?.aborted) throw new Error("Agent connection cancelled");
7749
- const owned = [...this.agents.values()].filter((a) => a.owner === owner);
7750
- if (owned.length >= 3) {
7751
- const idle = owned.find((a) => !a.client && !this.working(a));
7752
- if (!idle)
7753
- throw new RpcError("BUSY", "3 agents are running for this device; stop one in Runtime sessions");
7754
- this.stopAgent(idle, "Stopped to start another agent");
7755
- }
7756
- const command = requireString(
7757
- { command: launch.command ?? preset2.command },
7758
- "command"
7759
- );
7760
- if (!command.trim() || command.includes("\0"))
7761
- throw new RpcError("INVALID_PARAMS", "Invalid agent executable");
7762
- const args = strings(launch.args ?? [...preset2.args]);
8113
+ this.slot(owner, false);
8114
+ const resolved = await this.resolveLaunch(launch, signal);
8115
+ if (signal?.aborted) throw new Error("Agent connection cancelled");
8116
+ this.slot(owner, true);
8117
+ const { command, args } = resolved;
7763
8118
  const child = spawn(command, args, {
7764
8119
  cwd: this.files.root,
7765
- env: process.env,
8120
+ env: resolved.env && Object.keys(resolved.env).length ? { ...process.env, ...resolved.env } : process.env,
7766
8121
  stdio: "pipe",
7767
8122
  detached: process.platform !== "win32",
7768
8123
  windowsHide: true
@@ -7775,7 +8130,8 @@ class AgentACP {
7775
8130
  child,
7776
8131
  connection: {
7777
8132
  id: randomUUID(),
7778
- provider: preset2.id,
8133
+ provider: resolved.provider,
8134
+ name: resolved.name,
7779
8135
  root: this.files.root,
7780
8136
  authMethods: []
7781
8137
  },
@@ -7829,10 +8185,10 @@ class AgentACP {
7829
8185
  return;
7830
8186
  }
7831
8187
  try {
7832
- const message = JSON.parse(line);
7833
- if (message?.jsonrpc !== "2.0")
8188
+ const message2 = JSON.parse(line);
8189
+ if (message2?.jsonrpc !== "2.0")
7834
8190
  throw new Error("Invalid ACP envelope");
7835
- void this.message(agent, message).catch(
8191
+ void this.message(agent, message2).catch(
7836
8192
  (error) => this.stopAgent(agent, String(error))
7837
8193
  );
7838
8194
  } catch {
@@ -7852,7 +8208,7 @@ class AgentACP {
7852
8208
  clientCapabilities: {
7853
8209
  fs: { readTextFile: true, writeTextFile: true },
7854
8210
  terminal: true,
7855
- ...preset2.id === "codex" ? { subagents: {} } : {}
8211
+ ...resolved.provider === "codex" ? { subagents: {} } : {}
7856
8212
  },
7857
8213
  clientInfo: { name: "oxbit", title: "Oxbit", version: runtimeVersion() }
7858
8214
  });
@@ -7869,6 +8225,43 @@ class AgentACP {
7869
8225
  signal?.removeEventListener("abort", abort);
7870
8226
  }
7871
8227
  }
8228
+ slot(owner, evict) {
8229
+ const owned = [...this.agents.values()].filter((a) => a.owner === owner);
8230
+ if (owned.length < 3) return;
8231
+ const idle = owned.find((a) => !a.client && !this.working(a));
8232
+ if (!idle)
8233
+ throw new RpcError("BUSY", "3 agents are running for this device; stop one in Runtime sessions");
8234
+ if (evict) this.stopAgent(idle, "Stopped to start another agent");
8235
+ }
8236
+ async resolveLaunch(launch, signal) {
8237
+ const provider = launch?.provider;
8238
+ if (typeof provider !== "string" || !provider || provider.length > 64)
8239
+ throw new RpcError("INVALID_PARAMS", "Unknown agent");
8240
+ if (launch.registry !== void 0) {
8241
+ const { id, version } = launch.registry ?? {};
8242
+ if (typeof id !== "string" || !REGISTRY_ID.test(id) || version !== void 0 && typeof version !== "string")
8243
+ throw new RpcError("INVALID_PARAMS", "Invalid ACP Registry agent");
8244
+ const builtin = acpBuiltinForRegistry(id);
8245
+ if (builtin)
8246
+ return { provider: builtin.id, name: builtin.name, command: builtin.command, args: [...builtin.args] };
8247
+ if (!this.acpRegistry)
8248
+ throw new RpcError("INVALID_PARAMS", "This runtime cannot launch ACP Registry agents");
8249
+ return { provider: id, ...await this.acpRegistry.resolve(id, version, signal) };
8250
+ }
8251
+ const preset2 = acpPreset(provider);
8252
+ if (!preset2 && (typeof launch.command !== "string" || !launch.command.trim()))
8253
+ throw new RpcError("INVALID_PARAMS", provider === ACP_CUSTOM_PROVIDER ? "Enter a command for the custom agent" : "Unknown agent");
8254
+ const command = requireString({ command: launch.command ?? preset2.command }, "command");
8255
+ if (!command.trim() || command.includes("\0"))
8256
+ throw new RpcError("INVALID_PARAMS", "Invalid agent executable");
8257
+ const custom = typeof launch.name === "string" ? launch.name.trim().slice(0, 80) : "";
8258
+ return {
8259
+ provider,
8260
+ name: preset2?.name ?? (custom || "Custom agent"),
8261
+ command,
8262
+ args: strings(launch.args ?? (preset2 ? [...preset2.args] : []))
8263
+ };
8264
+ }
7872
8265
  async call(owner, id, method, params = {}, signal) {
7873
8266
  const agent = this.get(owner, id);
7874
8267
  if (agent.busy)
@@ -8017,33 +8410,8 @@ class AgentACP {
8017
8410
  const text2 = requireString(params, "text", MAX_BYTES);
8018
8411
  if (!text2.trim()) throw new Error("Enter a message");
8019
8412
  const prompt = [{ type: "text", text: text2 }];
8020
- if (params.context !== void 0) {
8021
- if (!Array.isArray(params.context) || params.context.length > 8)
8022
- throw new Error("Attach up to eight context items");
8023
- for (const item of params.context) {
8024
- const content = requireString(item, "text", 2e5);
8025
- const relative = requireString(item, "path");
8026
- const absolute = await this.files.resolve(relative);
8027
- const label = typeof item.label === "string" ? item.label.slice(0, 1024) : relative;
8028
- if (agent.connection.capabilities?.promptCapabilities?.embeddedContext)
8029
- prompt.push({
8030
- type: "resource",
8031
- resource: {
8032
- uri: pathToFileURL(absolute).href + (Number.isSafeInteger(item.line) ? `#L${item.line}` : ""),
8033
- mimeType: "text/plain",
8034
- text: `Editor snapshot: ${label}
8035
- ${content}`
8036
- }
8037
- });
8038
- else
8039
- prompt.push({
8040
- type: "text",
8041
- text: `Attached editor context: ${label}
8042
- ${content}`
8043
- });
8044
- }
8045
- }
8046
- if (Buffer.byteLength(JSON.stringify(prompt)) > MAX_BYTES)
8413
+ if (params.context !== void 0) prompt.push(...await this.contextBlocks(agent, params.context));
8414
+ if (Buffer.byteLength(JSON.stringify(prompt.filter((block) => block.type !== "image"))) > MAX_BYTES)
8047
8415
  throw new Error("The message and attachments are too large");
8048
8416
  if (agent.busy || agent.stopped || agent.connection.sessionId !== sessionId)
8049
8417
  throw new Error("Agent connection is busy or ended");
@@ -8051,7 +8419,11 @@ ${content}`
8051
8419
  agent.busy = true;
8052
8420
  agent.turn++;
8053
8421
  if (agent.title === "New conversation") agent.title = text2.slice(0, 100);
8054
- this.publish(agent, "acp.turnStarted", { messageId, text: text2, context: params.context ?? [] });
8422
+ this.publish(agent, "acp.turnStarted", {
8423
+ messageId,
8424
+ text: text2,
8425
+ context: JSON.parse(JSON.stringify(params.context ?? [], withoutImageData))
8426
+ });
8055
8427
  const abort = () => this.stopAgent(agent, "Agent request interrupted");
8056
8428
  signal?.addEventListener("abort", abort, { once: true });
8057
8429
  let stopReason;
@@ -8081,6 +8453,48 @@ ${content}`
8081
8453
  }
8082
8454
  }
8083
8455
  }
8456
+ /** Validates attached context and converts it into ACP prompt content blocks. */
8457
+ async contextBlocks(agent, context) {
8458
+ if (context === void 0) return [];
8459
+ if (!Array.isArray(context) || context.length > 8)
8460
+ throw new Error("Attach up to eight context items");
8461
+ if (context.filter((item) => item?.kind === "image").length > MAX_IMAGES)
8462
+ throw new Error(`Attach up to ${MAX_IMAGES} images per message`);
8463
+ const blocks = [];
8464
+ for (const item of context) {
8465
+ if (item?.kind === "image") {
8466
+ if (!agent.connection.capabilities?.promptCapabilities?.image)
8467
+ throw new RpcError("INVALID_PARAMS", "This agent does not accept images");
8468
+ const mimeType = requireString(item, "mimeType", 64);
8469
+ const data = requireString(item, "data", MAX_IMAGE_DATA);
8470
+ if (!IMAGE_TYPES.includes(mimeType) || !data || !/^[A-Za-z0-9+/]+={0,2}$/.test(data))
8471
+ throw new RpcError("INVALID_PARAMS", "Invalid image attachment");
8472
+ blocks.push({ type: "image", mimeType, data });
8473
+ continue;
8474
+ }
8475
+ const content = requireString(item, "text", 2e5);
8476
+ const relative = requireString(item, "path");
8477
+ const absolute = await this.files.resolve(relative);
8478
+ const label = typeof item.label === "string" ? item.label.slice(0, 1024) : relative;
8479
+ if (agent.connection.capabilities?.promptCapabilities?.embeddedContext)
8480
+ blocks.push({
8481
+ type: "resource",
8482
+ resource: {
8483
+ uri: pathToFileURL(absolute).href + (Number.isSafeInteger(item.line) ? `#L${item.line}` : ""),
8484
+ mimeType: "text/plain",
8485
+ text: `Editor snapshot: ${label}
8486
+ ${content}`
8487
+ }
8488
+ });
8489
+ else
8490
+ blocks.push({
8491
+ type: "text",
8492
+ text: `Attached editor context: ${label}
8493
+ ${content}`
8494
+ });
8495
+ }
8496
+ return blocks;
8497
+ }
8084
8498
  publishQueue(agent) {
8085
8499
  this.publish(agent, "acp.queue", { queue: agent.queue, paused: agent.queuePaused });
8086
8500
  return { queue: [...agent.queue], paused: agent.queuePaused };
@@ -8091,18 +8505,13 @@ ${content}`
8091
8505
  const text2 = requireString(params, "text", MAX_BYTES).trim();
8092
8506
  if (!text2) throw new Error("Enter a message");
8093
8507
  const messageId = params.messageId === void 0 ? randomUUID() : requireString(params, "messageId", 256);
8094
- if (params.context !== void 0) {
8095
- if (!Array.isArray(params.context) || params.context.length > 8)
8096
- throw new Error("Attach up to eight context items");
8097
- for (const item of params.context) {
8098
- requireString(item, "text", 2e5);
8099
- await this.files.resolve(requireString(item, "path"));
8100
- }
8101
- }
8508
+ if (params.context !== void 0) await this.contextBlocks(agent, params.context);
8102
8509
  if (agent.stopped) throw new Error("Agent connection has ended");
8103
8510
  if (agent.queue.some((p) => p.id === messageId)) return this.publishQueue(agent);
8104
8511
  const prompt = { id: messageId, text: text2, ...params.context ? { context: structuredClone(params.context) } : {} };
8105
- if (agent.queue.length >= 16 || Buffer.byteLength(JSON.stringify([...agent.queue, prompt])) > MAX_BYTES)
8512
+ const queued = [...agent.queue, prompt];
8513
+ const imageData = queued.flatMap((p) => p.context ?? []).reduce((n, item) => n + (item.data?.length ?? 0), 0);
8514
+ if (agent.queue.length >= 16 || imageData > MAX_QUEUED_IMAGE_DATA || Buffer.byteLength(JSON.stringify(queued, withoutImageData)) > MAX_BYTES)
8106
8515
  throw new Error("Queued messages exceed the limit; remove a message first");
8107
8516
  if (interrupt) {
8108
8517
  agent.queue.unshift(prompt);
@@ -8351,10 +8760,10 @@ ${content}`
8351
8760
  break;
8352
8761
  case "oxbit_update_plan": {
8353
8762
  if (!Array.isArray(args.entries) || args.entries.length > 50) throw new Error("Provide up to 50 plan entries");
8354
- const entries = args.entries.map((entry) => {
8355
- const content = requireString(entry, "content", 2e3);
8356
- if (!["pending", "in_progress", "completed"].includes(entry.status)) throw new Error("Invalid plan status");
8357
- return { content, status: entry.status, ...typeof entry.priority === "string" ? { priority: entry.priority.slice(0, 40) } : {} };
8763
+ const entries = args.entries.map((entry2) => {
8764
+ const content = requireString(entry2, "content", 2e3);
8765
+ if (!["pending", "in_progress", "completed"].includes(entry2.status)) throw new Error("Invalid plan status");
8766
+ return { content, status: entry2.status, ...typeof entry2.priority === "string" ? { priority: entry2.priority.slice(0, 40) } : {} };
8358
8767
  });
8359
8768
  publish({ sessionUpdate: "plan", entries });
8360
8769
  result = { entries };
@@ -8370,52 +8779,52 @@ ${content}`
8370
8779
  throw error;
8371
8780
  }
8372
8781
  }
8373
- async message(agent, message) {
8782
+ async message(agent, message2) {
8374
8783
  if (agent.stopped) return;
8375
- if (!message.method) {
8376
- const pending = agent.pending.get(message.id);
8784
+ if (!message2.method) {
8785
+ const pending = agent.pending.get(message2.id);
8377
8786
  if (!pending) return;
8378
8787
  clearTimeout(pending.timer);
8379
- agent.pending.delete(message.id);
8380
- if (message.error)
8788
+ agent.pending.delete(message2.id);
8789
+ if (message2.error)
8381
8790
  pending.reject(
8382
8791
  new RpcError(
8383
- String(message.error.code),
8384
- message.error.message ?? "ACP request failed",
8385
- message.error.data
8792
+ String(message2.error.code),
8793
+ message2.error.message ?? "ACP request failed",
8794
+ message2.error.data
8386
8795
  )
8387
8796
  );
8388
- else pending.resolve(message.result);
8797
+ else pending.resolve(message2.result);
8389
8798
  return;
8390
8799
  }
8391
- const params = message.params ?? {};
8392
- if (message.id === void 0) {
8393
- if (message.method === "session/update" && agent.newSessionUpdates) {
8800
+ const params = message2.params ?? {};
8801
+ if (message2.id === void 0) {
8802
+ if (message2.method === "session/update" && agent.newSessionUpdates) {
8394
8803
  if (agent.newSessionUpdates.length >= 100)
8395
8804
  throw new Error("Too many updates while creating a session");
8396
8805
  agent.newSessionUpdates.push(params);
8397
8806
  return;
8398
8807
  }
8399
- if (message.method === "session/update")
8808
+ if (message2.method === "session/update")
8400
8809
  this.routeUpdate(agent, params.sessionId, params.update);
8401
- else if (message.method.startsWith("cursor/") && agent.connection.provider === "cursor")
8810
+ else if (message2.method.startsWith("cursor/") && agent.connection.provider === "cursor")
8402
8811
  this.routeUpdate(
8403
8812
  agent,
8404
8813
  params.sessionId ?? agent.connection.sessionId,
8405
- { ...params, sessionUpdate: message.method }
8814
+ { ...params, sessionUpdate: message2.method }
8406
8815
  );
8407
8816
  return;
8408
8817
  }
8409
8818
  try {
8410
8819
  const registry = agent.subagents;
8411
- const cursorRequest = message.method.startsWith("cursor/") && agent.connection.provider === "cursor";
8820
+ const cursorRequest = message2.method.startsWith("cursor/") && agent.connection.provider === "cursor";
8412
8821
  const sessionId = params.sessionId ?? (cursorRequest ? agent.connection.sessionId : void 0);
8413
8822
  if (!registry?.acceptsRequest(sessionId))
8414
8823
  throw new Error("Unknown ACP session");
8415
8824
  params.sessionId = sessionId;
8416
- if (message.method.startsWith("terminal/")) {
8417
- const result2 = await this.terminal(agent, message.method, params);
8418
- this.send(agent, { id: message.id, result: result2 });
8825
+ if (message2.method.startsWith("terminal/")) {
8826
+ const result2 = await this.terminal(agent, message2.method, params);
8827
+ this.send(agent, { id: message2.id, result: result2 });
8419
8828
  return;
8420
8829
  }
8421
8830
  if (![
@@ -8424,19 +8833,19 @@ ${content}`
8424
8833
  "session/request_permission",
8425
8834
  "cursor/ask_question",
8426
8835
  "cursor/create_plan"
8427
- ].includes(message.method)) {
8836
+ ].includes(message2.method)) {
8428
8837
  this.send(agent, {
8429
- id: message.id,
8838
+ id: message2.id,
8430
8839
  error: { code: -32601, message: "Client method is not supported" }
8431
8840
  });
8432
8841
  return;
8433
8842
  }
8434
- const result = await this.clientRequest(agent, message.method, params);
8435
- if (!agent.stopped) this.send(agent, { id: message.id, result });
8843
+ const result = await this.clientRequest(agent, message2.method, params);
8844
+ if (!agent.stopped) this.send(agent, { id: message2.id, result });
8436
8845
  } catch (error) {
8437
8846
  if (!agent.stopped)
8438
8847
  this.send(agent, {
8439
- id: message.id,
8848
+ id: message2.id,
8440
8849
  error: { code: -32603, message: String(error) }
8441
8850
  });
8442
8851
  }
@@ -8472,15 +8881,15 @@ ${content}`
8472
8881
  const command = requireString(params, "command");
8473
8882
  if (!command || command.includes("\0"))
8474
8883
  throw new Error("Invalid terminal command");
8475
- const env = { ...process.env };
8884
+ const env2 = { ...process.env };
8476
8885
  if (params.env !== void 0) {
8477
8886
  if (!Array.isArray(params.env) || params.env.length > 128)
8478
8887
  throw new Error("Invalid terminal environment");
8479
- for (const entry of params.env) {
8480
- const name = requireString(entry, "name"), value = requireString(entry, "value", 16384);
8888
+ for (const entry2 of params.env) {
8889
+ const name = requireString(entry2, "name"), value = requireString(entry2, "value", 16384);
8481
8890
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) || value.includes("\0"))
8482
8891
  throw new Error("Invalid environment variable");
8483
- env[name] = value;
8892
+ env2[name] = value;
8484
8893
  }
8485
8894
  }
8486
8895
  const limit2 = params.outputByteLimit ?? MAX_BYTES;
@@ -8492,7 +8901,7 @@ ${content}`
8492
8901
  throw new Error("Agent connection has ended");
8493
8902
  const child = spawn(command, strings(params.args ?? []), {
8494
8903
  cwd,
8495
- env,
8904
+ env: env2,
8496
8905
  stdio: "pipe",
8497
8906
  detached: process.platform !== "win32",
8498
8907
  windowsHide: true
@@ -8612,6 +9021,124 @@ ${content}`
8612
9021
  return this.mcp.close();
8613
9022
  }
8614
9023
  }
9024
+ const CHECKPOINT_TIMEOUT = 5e3;
9025
+ const TREE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
9026
+ const GITLINK = "160000";
9027
+ const BATCH = 500;
9028
+ const unavailable = (reason) => ({ unavailable: reason });
9029
+ class ACPCheckpoints {
9030
+ constructor(root, executable = "git") {
9031
+ this.root = root;
9032
+ this.executable = executable;
9033
+ }
9034
+ root;
9035
+ executable;
9036
+ queue = Promise.resolve();
9037
+ async run(args, signal, index, acceptFailure = false) {
9038
+ const result = await runCommand(this.executable, ["--literal-pathspecs", "-c", "color.ui=false", ...args], {
9039
+ cwd: this.root,
9040
+ signal,
9041
+ maxBytes: MAX_MESSAGE_BYTES,
9042
+ env: {
9043
+ ...process.env,
9044
+ GIT_TERMINAL_PROMPT: "0",
9045
+ GIT_OPTIONAL_LOCKS: "0",
9046
+ LC_ALL: "C",
9047
+ ...index ? { GIT_INDEX_FILE: index } : {}
9048
+ }
9049
+ });
9050
+ if (result.exitCode && !acceptFailure)
9051
+ throw new RpcError("GIT_FAILED", result.stderr.trim() || `Git exited ${result.exitCode}`, { exitCode: result.exitCode });
9052
+ return result;
9053
+ }
9054
+ serial(task) {
9055
+ const next = this.queue.then(task, task);
9056
+ this.queue = next.catch(() => {
9057
+ });
9058
+ return next;
9059
+ }
9060
+ async withIndex(task) {
9061
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), "oxbit-checkpoint-"));
9062
+ try {
9063
+ return await task(path.join(directory, "index"));
9064
+ } finally {
9065
+ await fs.rm(directory, { recursive: true, force: true });
9066
+ }
9067
+ }
9068
+ /** Copies the real index so `add -A` reuses its stat data, then writes the tree. */
9069
+ async snapshot(signal) {
9070
+ const repository = await this.run(["rev-parse", "--git-dir", "--show-toplevel"], signal, void 0, true);
9071
+ if (repository.exitCode) return unavailable("not-repository");
9072
+ const top = repository.stdout.trim().split("\n").at(-1) ?? "";
9073
+ if (await fs.realpath(top).catch(() => top) !== this.root) return unavailable("subdirectory");
9074
+ const real = path.resolve(this.root, (await this.run(["rev-parse", "--git-path", "index"], signal)).stdout.trim());
9075
+ return this.withIndex(async (index) => {
9076
+ await fs.copyFile(real, index).catch((error) => {
9077
+ if (error.code !== "ENOENT") throw error;
9078
+ });
9079
+ await this.run(["add", "-A"], signal, index);
9080
+ return (await this.run(["write-tree"], signal, index)).stdout.trim();
9081
+ });
9082
+ }
9083
+ create(signal) {
9084
+ const timeout = AbortSignal.timeout(CHECKPOINT_TIMEOUT);
9085
+ return this.serial(async () => {
9086
+ try {
9087
+ const tree = await this.snapshot(signal ? AbortSignal.any([signal, timeout]) : timeout);
9088
+ return typeof tree === "string" ? { tree } : tree;
9089
+ } catch (error) {
9090
+ if (signal?.aborted) throw error;
9091
+ return unavailable(timeout.aborted ? "timeout" : "failed");
9092
+ }
9093
+ });
9094
+ }
9095
+ async changes(tree, signal) {
9096
+ if (typeof tree !== "string" || !TREE.test(tree)) throw new RpcError("INVALID_PARAMS", "Choose a valid checkpoint");
9097
+ const current = await this.snapshot(signal);
9098
+ if (typeof current !== "string") return current;
9099
+ if ((await this.run(["cat-file", "-e", `${tree}^{tree}`], signal, void 0, true)).exitCode) return unavailable("missing");
9100
+ const output = (await this.run(["diff-tree", "-r", "-z", "--raw", "--no-renames", current, tree], signal)).stdout.split("\0");
9101
+ const changes = [];
9102
+ for (let i = 0; i + 1 < output.length; i += 2) {
9103
+ const [from, to, , , status2] = output[i].replace(/^:/, "").split(" ");
9104
+ if (from === GITLINK || to === GITLINK) continue;
9105
+ changes.push({ status: status2, path: output[i + 1] });
9106
+ }
9107
+ return { tree: current, changes };
9108
+ }
9109
+ diff(tree, signal) {
9110
+ return this.serial(() => this.changes(tree, signal));
9111
+ }
9112
+ /** Writes only paths that differ from the checkpoint. `paths` is the set the client checked for unsaved edits. */
9113
+ restore(tree, paths, signal) {
9114
+ if (paths !== void 0 && (!Array.isArray(paths) || paths.some((item) => typeof item !== "string")))
9115
+ throw new RpcError("INVALID_PARAMS", "paths must be a string array");
9116
+ return this.serial(async () => {
9117
+ const diff = await this.changes(tree, signal);
9118
+ if ("unavailable" in diff) throw new RpcError("CHECKPOINT_UNAVAILABLE", "This checkpoint is no longer available");
9119
+ const checked = paths && new Set(paths);
9120
+ if (checked && diff.changes.some((change) => !checked.has(change.path)))
9121
+ throw new RpcError("CONFLICT", "Files changed while preparing the restore. Try again.");
9122
+ const deleted = diff.changes.filter((change) => change.status === "D").map((change) => change.path);
9123
+ const restored = diff.changes.filter((change) => change.status !== "D").map((change) => change.path);
9124
+ for (const file of deleted) await this.remove(file);
9125
+ if (restored.length)
9126
+ await this.withIndex(async (index) => {
9127
+ await this.run(["read-tree", tree], signal, index);
9128
+ for (let i = 0; i < restored.length; i += BATCH)
9129
+ await this.run(["checkout-index", "-f", "--", ...restored.slice(i, i + BATCH)], signal, index);
9130
+ });
9131
+ return { restored, deleted };
9132
+ });
9133
+ }
9134
+ async remove(relative) {
9135
+ const full = path.resolve(this.root, relative);
9136
+ if (!full.startsWith(this.root + path.sep)) throw new RpcError("PATH_DENIED", `Path is outside the workspace: ${relative}`);
9137
+ await fs.rm(full, { force: true });
9138
+ for (let directory = path.dirname(full); directory !== this.root; directory = path.dirname(directory))
9139
+ if (!await fs.rmdir(directory).then(() => true, () => false)) break;
9140
+ }
9141
+ }
8615
9142
  var re = { exports: {} };
8616
9143
  var constants;
8617
9144
  var hasRequiredConstants;
@@ -9289,7 +9816,7 @@ function requireSort() {
9289
9816
  if (hasRequiredSort) return sort_1;
9290
9817
  hasRequiredSort = 1;
9291
9818
  const compareBuild = requireCompareBuild();
9292
- const sort = (list, loose) => list.sort((a, b) => compareBuild(a, b, loose));
9819
+ const sort = (list2, loose) => list2.sort((a, b) => compareBuild(a, b, loose));
9293
9820
  sort_1 = sort;
9294
9821
  return sort_1;
9295
9822
  }
@@ -9299,7 +9826,7 @@ function requireRsort() {
9299
9826
  if (hasRequiredRsort) return rsort_1;
9300
9827
  hasRequiredRsort = 1;
9301
9828
  const compareBuild = requireCompareBuild();
9302
- const rsort = (list, loose) => list.sort((a, b) => compareBuild(b, a, loose));
9829
+ const rsort = (list2, loose) => list2.sort((a, b) => compareBuild(b, a, loose));
9303
9830
  rsort_1 = rsort;
9304
9831
  return rsort_1;
9305
9832
  }
@@ -11460,8 +11987,8 @@ function createKernel({
11460
11987
  }
11461
11988
  extensionChanged(id);
11462
11989
  },
11463
- async load(url, options2) {
11464
- const parsed = new URL(url, globalThis.location?.href ?? "file:///");
11990
+ async load(url2, options2) {
11991
+ const parsed = new URL(url2, globalThis.location?.href ?? "file:///");
11465
11992
  if (!["http:", "https:", "file:"].includes(parsed.protocol) && (!extensionOrigins.has(`${parsed.protocol}//${parsed.host}`) || parsed.username || parsed.password))
11466
11993
  throw new Error(
11467
11994
  "Extensions require a trusted HTTP(S), file, or application ESM artifact"
@@ -11612,9 +12139,9 @@ function bonjourName() {
11612
12139
  return macLocalName || void 0;
11613
12140
  }
11614
12141
  function lanAddresses$1(interfaces = os.networkInterfaces()) {
11615
- const addresses = Object.values(interfaces).flatMap((entries) => entries ?? []).filter((entry) => !entry.internal);
12142
+ const addresses = Object.values(interfaces).flatMap((entries) => entries ?? []).filter((entry2) => !entry2.internal);
11616
12143
  const of = (family) => [
11617
- ...new Set(addresses.filter((entry) => entry.family === family).map((entry) => entry.address.split("%")[0]))
12144
+ ...new Set(addresses.filter((entry2) => entry2.family === family).map((entry2) => entry2.address.split("%")[0]))
11618
12145
  ];
11619
12146
  return { v4: of("IPv4"), v6: of("IPv6") };
11620
12147
  }
@@ -11630,6 +12157,58 @@ function lanUrl(port2, interfaces = os.networkInterfaces()) {
11630
12157
  const address = lanAddresses$1(interfaces).v4[0];
11631
12158
  return address ? `http://${address}:${port2}` : void 0;
11632
12159
  }
12160
+ const MAX_OUTPUT_BYTES = 1024 * 1024;
12161
+ const shellOwned = /* @__PURE__ */ new Set(["PWD", "OLDPWD", "SHLVL", "_", "OXBIT_RESOLVING_ENVIRONMENT"]);
12162
+ const launchOwned = (key) => /^(SSH_|GH_|GITHUB_)/.test(key) || key.toLowerCase().endsWith("_proxy");
12163
+ function parseShellEnvironment(output, mark) {
12164
+ const start = output.indexOf(mark), end = output.lastIndexOf(mark);
12165
+ if (start < 0 || end <= start) return void 0;
12166
+ const env2 = {};
12167
+ for (const entry2 of output.slice(start + mark.length, end).split("\0")) {
12168
+ const separator = entry2.indexOf("="), key = entry2.slice(0, separator);
12169
+ if (separator > 0 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && !shellOwned.has(key))
12170
+ env2[key] = entry2.slice(separator + 1);
12171
+ }
12172
+ return env2;
12173
+ }
12174
+ function loginShellEnvironment(cwd, inherited = process.env, platform = process.platform, timeout = 1e4) {
12175
+ if (platform === "win32") return Promise.resolve(inherited);
12176
+ const { shell, args } = resolveTerminalShell(inherited, platform);
12177
+ const mark = randomBytes(12).toString("hex");
12178
+ return new Promise((resolve) => {
12179
+ const chunks = [];
12180
+ let bytes = 0, settled = false;
12181
+ const child = spawn(shell, [...args, "-c", `printf ${mark}; /usr/bin/env -0; printf ${mark}`], {
12182
+ cwd,
12183
+ env: { ...inherited, OXBIT_RESOLVING_ENVIRONMENT: "1" },
12184
+ stdio: ["ignore", "pipe", "ignore"],
12185
+ detached: true
12186
+ });
12187
+ trackChild(child);
12188
+ const finish = (env2) => {
12189
+ if (settled) return;
12190
+ settled = true;
12191
+ clearTimeout(timer);
12192
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
12193
+ if (!env2) {
12194
+ resolve(inherited);
12195
+ return;
12196
+ }
12197
+ const merged = { ...inherited, ...env2 };
12198
+ for (const [key, value] of Object.entries(inherited))
12199
+ if (launchOwned(key)) merged[key] = value;
12200
+ resolve(merged);
12201
+ };
12202
+ const timer = setTimeout(() => finish(), timeout);
12203
+ child.stdout.on("data", (chunk) => {
12204
+ bytes += chunk.length;
12205
+ if (bytes > MAX_OUTPUT_BYTES) finish();
12206
+ else chunks.push(chunk);
12207
+ });
12208
+ child.on("error", () => finish());
12209
+ child.on("close", (code) => finish(code === 0 ? parseShellEnvironment(Buffer.concat(chunks).toString(), mark) : void 0));
12210
+ });
12211
+ }
11633
12212
  const MAX_SOCKET_BACKLOG_BYTES = MAX_FILE_WRITE_MESSAGE_BYTES;
11634
12213
  const allCapabilities = [
11635
12214
  "filesystem.read",
@@ -11734,13 +12313,13 @@ async function createRuntime(options2) {
11734
12313
  const origin = request.headers.origin;
11735
12314
  return origin === void 0 ? !required : allowedOrigins().has(origin);
11736
12315
  };
11737
- const send = (connection, message) => {
12316
+ const send = (connection, message2) => {
11738
12317
  if (connection.ws.readyState !== WebSocket.OPEN) return;
11739
12318
  if (connection.ws.bufferedAmount > MAX_SOCKET_BACKLOG_BYTES) {
11740
12319
  connection.ws.close(1013, "Slow consumer; reconnect to resynchronize");
11741
12320
  return;
11742
12321
  }
11743
- connection.ws.send(JSON.stringify(message));
12322
+ connection.ws.send(JSON.stringify(message2));
11744
12323
  };
11745
12324
  const event = (connection, name, params, stream, seq) => send(connection, {
11746
12325
  v: 1,
@@ -11771,10 +12350,11 @@ async function createRuntime(options2) {
11771
12350
  throw new RpcError("FORBIDDEN", "Owner access is required");
11772
12351
  return session;
11773
12352
  };
12353
+ const acpRegistry = new ACPRegistry(dataDir, options2.acpRegistryUrl);
11774
12354
  const agents = new AgentACP(files, (connectionId, name, params) => {
11775
12355
  const connection = connections.get(connectionId);
11776
12356
  if (connection?.session && !connection.session.revoked && trusted) event(connection, name, params);
11777
- }, () => updateIdle());
12357
+ }, () => updateIdle(), acpRegistry);
11778
12358
  const processEvent = ({ event: name, params, stream, seq }, ownerId) => {
11779
12359
  const cap = name.startsWith("terminal.") ? "terminal" : "tasks";
11780
12360
  for (const c of connections.values()) {
@@ -11784,7 +12364,7 @@ async function createRuntime(options2) {
11784
12364
  };
11785
12365
  const processes = new Processes(root, processEvent);
11786
12366
  const taskStore = await TaskConfigStore.create(root, { home: options2.tasksHome ?? setting("TASKS_HOME"), gitPath: options2.desktop?.gitPath });
11787
- const tasks = new TaskRunner(taskStore, processEvent, options2.forwardTaskPort);
12367
+ const tasks = new TaskRunner(taskStore, processEvent, options2.forwardTaskPort, loginShellEnvironment);
11788
12368
  const worktrees = new TaskWorktrees(taskStore, tasks);
11789
12369
  let projectChanged = () => {
11790
12370
  };
@@ -11834,6 +12414,7 @@ async function createRuntime(options2) {
11834
12414
  }
11835
12415
  );
11836
12416
  const git = new Git(files, options2.desktop?.gitPath);
12417
+ const checkpoints = new ACPCheckpoints(files.root, options2.desktop?.gitPath);
11837
12418
  const extensions = new RuntimeExtensions(files, {
11838
12419
  "runtime.git": {
11839
12420
  status: (signal) => git.status(signal),
@@ -11939,29 +12520,29 @@ async function createRuntime(options2) {
11939
12520
  httpJson(response, 403, { error: "Origin is not allowed" });
11940
12521
  return;
11941
12522
  }
11942
- const url = new URL(request.url ?? "/", `http://${host}:${port2}`);
11943
- if (url.pathname === "/api/health") {
12523
+ const url2 = new URL(request.url ?? "/", `http://${host}:${port2}`);
12524
+ if (url2.pathname === "/api/health") {
11944
12525
  httpJson(response, 200, { ok: true, protocol: 1, ...identity2 });
11945
12526
  return;
11946
12527
  }
11947
- if (url.pathname === "/api/pair" && request.method === "POST") {
12528
+ if (url2.pathname === "/api/pair" && request.method === "POST") {
11948
12529
  if (options2.desktop) {
11949
12530
  httpJson(response, 403, { error: "Desktop pairing requires the parent channel" });
11950
12531
  return;
11951
12532
  }
11952
- const address2 = request.socket.remoteAddress ?? "unknown", entry = attempts.get(address2) ?? {
12533
+ const address2 = request.socket.remoteAddress ?? "unknown", entry2 = attempts.get(address2) ?? {
11953
12534
  count: 0,
11954
12535
  reset: Date.now() + 6e4
11955
12536
  };
11956
- if (Date.now() > entry.reset) {
11957
- entry.count = 0;
11958
- entry.reset = Date.now() + 6e4;
12537
+ if (Date.now() > entry2.reset) {
12538
+ entry2.count = 0;
12539
+ entry2.reset = Date.now() + 6e4;
11959
12540
  }
11960
- if (++entry.count > pairingAttemptsPerMinute) {
12541
+ if (++entry2.count > pairingAttemptsPerMinute) {
11961
12542
  httpJson(response, 429, { error: "Pairing rate limit reached" });
11962
12543
  return;
11963
12544
  }
11964
- attempts.set(address2, entry);
12545
+ attempts.set(address2, entry2);
11965
12546
  const input = await body(request);
11966
12547
  const code = typeof input.code === "string" ? input.code : "";
11967
12548
  const a = Buffer.from(code), b = Buffer.from(pairingCode);
@@ -11977,7 +12558,7 @@ async function createRuntime(options2) {
11977
12558
  httpJson(response, 200, { ...result, runtime: identity2 });
11978
12559
  return;
11979
12560
  }
11980
- if (url.pathname === "/api/grants") {
12561
+ if (url2.pathname === "/api/grants") {
11981
12562
  const session = httpSession(request);
11982
12563
  if (!session?.owner) {
11983
12564
  httpJson(response, 403, { error: "Owner access is required" });
@@ -12006,7 +12587,7 @@ async function createRuntime(options2) {
12006
12587
  return;
12007
12588
  }
12008
12589
  }
12009
- if (url.pathname.startsWith("/api/grants/") && request.method === "DELETE") {
12590
+ if (url2.pathname.startsWith("/api/grants/") && request.method === "DELETE") {
12010
12591
  if (!httpSession(request)?.owner) {
12011
12592
  httpJson(response, 403, { error: "Owner access is required" });
12012
12593
  return;
@@ -12015,7 +12596,7 @@ async function createRuntime(options2) {
12015
12596
  response,
12016
12597
  200,
12017
12598
  await revoke(
12018
- decodeURIComponent(url.pathname.slice("/api/grants/".length))
12599
+ decodeURIComponent(url2.pathname.slice("/api/grants/".length))
12019
12600
  )
12020
12601
  );
12021
12602
  return;
@@ -12026,7 +12607,7 @@ async function createRuntime(options2) {
12026
12607
  }
12027
12608
  let relative;
12028
12609
  try {
12029
- relative = decodeURIComponent(url.pathname);
12610
+ relative = decodeURIComponent(url2.pathname);
12030
12611
  } catch {
12031
12612
  httpJson(response, 400, { error: "Invalid path" });
12032
12613
  return;
@@ -12150,7 +12731,7 @@ async function createRuntime(options2) {
12150
12731
  const required = permission(method), session = authorized(connection, required.cap, required.trust);
12151
12732
  if (method.startsWith("acp.")) {
12152
12733
  owner(connection);
12153
- if (!["acp.start", "acp.list", "acp.attach", "acp.disconnect"].includes(method))
12734
+ if (!["acp.start", "acp.list", "acp.attach", "acp.detach", "acp.stop", "acp.disconnect", "acp.registry"].includes(method) && !method.startsWith("acp.checkpoint."))
12154
12735
  agents.control(session.id, requireString(params, "id"), connection.id);
12155
12736
  }
12156
12737
  if (method.startsWith("project.")) owner(connection);
@@ -12178,6 +12759,8 @@ async function createRuntime(options2) {
12178
12759
  return agents.start(session.id, params, signal, connection.id);
12179
12760
  case "acp.list":
12180
12761
  return agents.list(session.id);
12762
+ case "acp.registry":
12763
+ return acpRegistry.listing(params.refresh === true);
12181
12764
  case "acp.attach":
12182
12765
  return agents.attach(session.id, requireString(params, "id"), connection.id, params.clientCapabilities?.editorTools === true);
12183
12766
  case "acp.enqueue":
@@ -12195,9 +12778,20 @@ async function createRuntime(options2) {
12195
12778
  return agents.cancel(session.id, requireString(params, "id"));
12196
12779
  case "acp.stop":
12197
12780
  return agents.stop(session.id, requireString(params, "id"));
12781
+ case "acp.detach":
12782
+ agents.detach(connection.id);
12783
+ return {};
12198
12784
  case "acp.disconnect":
12199
12785
  agents.disconnect(session.id, connection.id);
12200
12786
  return {};
12787
+ case "acp.checkpoint.create":
12788
+ return checkpoints.create(signal);
12789
+ case "acp.checkpoint.diff":
12790
+ return checkpoints.diff(params.tree, signal);
12791
+ case "acp.checkpoint.restore":
12792
+ if (agents.list(session.id).sessions.some((agent) => agent.busy || agent.pendingRequests))
12793
+ throw new RpcError("BUSY", "Wait for the agent to finish before restoring a checkpoint");
12794
+ return checkpoints.restore(params.tree, params.paths, signal);
12201
12795
  case "workspace.info":
12202
12796
  return sessionInfo(session);
12203
12797
  case "workspace.trust":
@@ -12669,32 +13263,32 @@ async function createRuntime(options2) {
12669
13263
  authDeadline.unref();
12670
13264
  ws.on("message", (raw) => {
12671
13265
  void (async () => {
12672
- let message;
13266
+ let message2;
12673
13267
  try {
12674
- message = parseClientMessage(raw.toString());
13268
+ message2 = parseClientMessage(raw.toString());
12675
13269
  } catch (error) {
12676
13270
  event(connection, "protocol.error", errorShape(error));
12677
13271
  ws.close(1008, "Invalid protocol message");
12678
13272
  return;
12679
13273
  }
12680
- if (message.type === "cancel") {
13274
+ if (message2.type === "cancel") {
12681
13275
  authorized(connection);
12682
- connection.pending.get(message.id)?.abort();
13276
+ connection.pending.get(message2.id)?.abort();
12683
13277
  return;
12684
13278
  }
12685
- if (message.type === "ack") {
13279
+ if (message2.type === "ack") {
12686
13280
  const session = authorized(connection, "terminal");
12687
- if (!message.stream.startsWith("terminal:"))
13281
+ if (!message2.stream.startsWith("terminal:"))
12688
13282
  throw new RpcError("INVALID_PARAMS", "Unknown stream");
12689
13283
  processes.ack(
12690
- message.stream.slice(9),
13284
+ message2.stream.slice(9),
12691
13285
  session.id,
12692
13286
  connection.id,
12693
- message.seq
13287
+ message2.seq
12694
13288
  );
12695
13289
  return;
12696
13290
  }
12697
- const { id, method, params } = message;
13291
+ const { id, method, params } = message2;
12698
13292
  if (connection.pending.size >= 64) {
12699
13293
  send(connection, {
12700
13294
  v: 1,
@@ -13240,8 +13834,8 @@ function advertise(ad, log = stderrLog) {
13240
13834
  })
13241
13835
  };
13242
13836
  }
13243
- const dataDirFor = (root, env = process.env) => {
13244
- const directory = setting("DATA_DIR", env);
13837
+ const dataDirFor = (root, env2 = process.env) => {
13838
+ const directory = setting("DATA_DIR", env2);
13245
13839
  return directory ? path.resolve(directory) : workspaceDataDir(root);
13246
13840
  };
13247
13841
  const recordFile = (dataDir) => path.join(dataDir, "daemon.json");
@@ -13252,14 +13846,14 @@ function launchUrl(daemon, file) {
13252
13846
  if (file) params.set("open", file);
13253
13847
  return `http://${localHost(daemon.host)}:${daemon.port}/#${params}`;
13254
13848
  }
13255
- function browserCommand(url, platform) {
13256
- if (platform === "darwin") return { command: "open", args: [url] };
13849
+ function browserCommand(url2, platform) {
13850
+ if (platform === "darwin") return { command: "open", args: [url2] };
13257
13851
  if (platform === "win32")
13258
- return { command: "cmd", args: ["/c", "start", "", url] };
13259
- return { command: "xdg-open", args: [url] };
13852
+ return { command: "cmd", args: ["/c", "start", "", url2] };
13853
+ return { command: "xdg-open", args: [url2] };
13260
13854
  }
13261
- function launchBrowser(url) {
13262
- const { command, args } = browserCommand(url, process.platform);
13855
+ function launchBrowser(url2) {
13856
+ const { command, args } = browserCommand(url2, process.platform);
13263
13857
  const child = spawn(command, args, { detached: true, stdio: "ignore" });
13264
13858
  child.on("error", () => {
13265
13859
  });
@@ -13357,7 +13951,7 @@ function parseDuration(value) {
13357
13951
  const duration = match ? Number(match[1]) * units[match[2] ?? ""] : NaN;
13358
13952
  return Number.isSafeInteger(duration) ? duration : void 0;
13359
13953
  }
13360
- function parse(argv, env = process.env) {
13954
+ function parse(argv, env2 = process.env) {
13361
13955
  const parsed = tokenize(argv);
13362
13956
  if ("error" in parsed) return parsed;
13363
13957
  const { values, positionals } = parsed;
@@ -13369,23 +13963,23 @@ function parse(argv, env = process.env) {
13369
13963
  return { error: "--desktop cannot be combined with browser runtime options" };
13370
13964
  if (values.lan && values.host)
13371
13965
  return { error: "--lan binds every interface and cannot be combined with --host" };
13372
- const requested = text(values.port) ?? env.PORT;
13966
+ const requested = text(values.port) ?? env2.PORT;
13373
13967
  const port2 = requested === void 0 ? 0 : Number(requested);
13374
13968
  if (!Number.isInteger(port2) || port2 < 0 || port2 > 65535)
13375
13969
  return {
13376
13970
  error: `Port must be a whole number between 0 and 65535, received "${requested}"`
13377
13971
  };
13378
- const lifetime = text(values["keep-alive"]) ?? setting("KEEP_ALIVE", env);
13972
+ const lifetime = text(values["keep-alive"]) ?? setting("KEEP_ALIVE", env2);
13379
13973
  const keepAlive = lifetime === void 0 ? 0 : parseDuration(lifetime);
13380
13974
  if (keepAlive === void 0)
13381
13975
  return {
13382
13976
  error: `Keep-alive must be a duration such as 75s, 15m, 1h, forever, or milliseconds, received "${lifetime}"`
13383
13977
  };
13384
13978
  return {
13385
- target: positionals[0] ?? setting("WORKSPACE", env) ?? ".",
13979
+ target: positionals[0] ?? setting("WORKSPACE", env2) ?? ".",
13386
13980
  port: port2,
13387
13981
  keepAlive,
13388
- host: values.lan ? "0.0.0.0" : text(values.host) ?? env.HOST ?? DEFAULT_HOST,
13982
+ host: values.lan ? "0.0.0.0" : text(values.host) ?? env2.HOST ?? DEFAULT_HOST,
13389
13983
  lan: values.lan === true,
13390
13984
  open: values["no-open"] !== true,
13391
13985
  foreground: values.foreground === true,
@@ -13417,15 +14011,15 @@ function serveFailure(error, host, port2) {
13417
14011
  return `Cannot serve on ${address}: the address is not available on this machine.`;
13418
14012
  return `The runtime could not start: ${failure2.message ?? String(error)}`;
13419
14013
  }
13420
- async function serve(invocation, target, dataDir, env) {
14014
+ async function serve(invocation, target, dataDir, env2) {
13421
14015
  try {
13422
- return await serveRuntime(invocation, target, dataDir, env);
14016
+ return await serveRuntime(invocation, target, dataDir, env2);
13423
14017
  } catch (error) {
13424
14018
  process.stderr.write(serveFailure(error, invocation.host, invocation.port) + "\n");
13425
14019
  return 1;
13426
14020
  }
13427
14021
  }
13428
- async function serveRuntime(invocation, target, dataDir, env) {
14022
+ async function serveRuntime(invocation, target, dataDir, env2) {
13429
14023
  const host = invocation.host;
13430
14024
  const port2 = invocation.port;
13431
14025
  let stop2 = () => {
@@ -13435,10 +14029,10 @@ async function serveRuntime(invocation, target, dataDir, env) {
13435
14029
  port: port2,
13436
14030
  host,
13437
14031
  dataDir,
13438
- pairingCode: setting("PAIRING_CODE", env),
13439
- pairingAttemptsPerMinute: setting("PAIRING_ATTEMPTS_PER_MINUTE", env) === void 0 ? void 0 : Number(setting("PAIRING_ATTEMPTS_PER_MINUTE", env)),
13440
- origins: setting("ORIGINS", env)?.split(",").filter(Boolean),
13441
- webRoot: setting("WEB_ROOT", env),
14032
+ pairingCode: setting("PAIRING_CODE", env2),
14033
+ pairingAttemptsPerMinute: setting("PAIRING_ATTEMPTS_PER_MINUTE", env2) === void 0 ? void 0 : Number(setting("PAIRING_ATTEMPTS_PER_MINUTE", env2)),
14034
+ origins: setting("ORIGINS", env2)?.split(",").filter(Boolean),
14035
+ webRoot: setting("WEB_ROOT", env2),
13442
14036
  ...invocation.keepAlive ? { idleShutdown: { afterMs: invocation.keepAlive, onIdle: () => stop2() } } : {}
13443
14037
  });
13444
14038
  let advertiser;
@@ -13456,16 +14050,16 @@ async function serveRuntime(invocation, target, dataDir, env) {
13456
14050
  startedAt: Date.now()
13457
14051
  };
13458
14052
  await writeRecord(dataDir, daemon);
13459
- const url = launchUrl(daemon, target.file);
14053
+ const url2 = launchUrl(daemon, target.file);
13460
14054
  process.stdout.write(
13461
14055
  (invocation.lan ? networkSummary(daemon) : `Oxbit runtime: http://${host}:${runtime.port}
13462
14056
  Workspace: ${runtime.root}
13463
14057
  Owner pairing code: ${runtime.pairingCode}
13464
14058
  Runtime id: ${runtime.identity.id}
13465
- `) + `Open: ${url}
14059
+ `) + `Open: ${url2}
13466
14060
  `
13467
14061
  );
13468
- if (invocation.open) launchBrowser(url);
14062
+ if (invocation.open) launchBrowser(url2);
13469
14063
  await new Promise((resolve) => {
13470
14064
  const shutdown = () => {
13471
14065
  void Promise.resolve(advertiser?.close()).then(() => runtime.close()).then(() => removeRecord(dataDir)).then(resolve, resolve);
@@ -13487,22 +14081,22 @@ Owner pairing code: ${daemon.pairingCode}
13487
14081
  Runtime id: ${daemon.id}
13488
14082
  `;
13489
14083
  }
13490
- async function detach(invocation, target, dataDir, entry) {
14084
+ async function detach(invocation, target, dataDir, entry2) {
13491
14085
  await fs.mkdir(dataDir, { recursive: true, mode: 448 });
13492
14086
  const log = await fs.open(logFile(dataDir), "a", 384);
13493
- const args = [entry, "--foreground", "--no-open", ...invocation.lan ? ["--lan"] : ["--host", invocation.host]];
14087
+ const args = [entry2, "--foreground", "--no-open", ...invocation.lan ? ["--lan"] : ["--host", invocation.host]];
13494
14088
  if (invocation.port) args.push("--port", String(invocation.port));
13495
14089
  args.push("--keep-alive", invocation.keepAlive ? String(invocation.keepAlive) : "forever");
13496
14090
  args.push(target.root);
13497
- const env = { ...process.env, OXBIT_DATA_DIR: dataDir };
13498
- delete env.PORT;
13499
- delete env.HOST;
13500
- delete env.OXBIT_WORKSPACE;
13501
- delete env.OXBIT_KEEP_ALIVE;
14091
+ const env2 = { ...process.env, OXBIT_DATA_DIR: dataDir };
14092
+ delete env2.PORT;
14093
+ delete env2.HOST;
14094
+ delete env2.OXBIT_WORKSPACE;
14095
+ delete env2.OXBIT_KEEP_ALIVE;
13502
14096
  const child = spawn(process.execPath, args, {
13503
14097
  detached: true,
13504
14098
  stdio: ["ignore", log.fd, log.fd],
13505
- env
14099
+ env: env2
13506
14100
  });
13507
14101
  let exit;
13508
14102
  child.on("exit", (code) => {
@@ -13565,11 +14159,11 @@ Open: ${launchUrl(daemon)}
13565
14159
  }
13566
14160
  async function run(argv, context = {}) {
13567
14161
  const {
13568
- env = process.env,
14162
+ env: env2 = process.env,
13569
14163
  cwd = process.cwd(),
13570
- entry = fileURLToPath(import.meta.url)
14164
+ entry: entry2 = fileURLToPath(import.meta.url)
13571
14165
  } = context;
13572
- const invocation = parse(argv, env);
14166
+ const invocation = parse(argv, env2);
13573
14167
  if ("error" in invocation) {
13574
14168
  process.stderr.write(`${invocation.error}
13575
14169
 
@@ -13594,10 +14188,10 @@ ${USAGE}`);
13594
14188
  return 1;
13595
14189
  }
13596
14190
  if (invocation.action === "desktop") return launchDesktop(target.file ? path.join(target.root, target.file) : target.root);
13597
- const dataDir = dataDirFor(target.root, env);
14191
+ const dataDir = dataDirFor(target.root, env2);
13598
14192
  if (invocation.action === "stop") return stop(dataDir, target.root);
13599
14193
  if (invocation.action === "status") return status(dataDir, target.root);
13600
- if (invocation.foreground) return serve(invocation, target, dataDir, env);
14194
+ if (invocation.foreground) return serve(invocation, target, dataDir, env2);
13601
14195
  const existing = await liveDaemon(dataDir);
13602
14196
  if (existing && invocation.lan && !wildcardHost(existing.host)) {
13603
14197
  process.stderr.write(`A runtime already serves ${existing.root} on ${existing.host}. Run oxbit --stop, then oxbit --lan.
@@ -13608,18 +14202,18 @@ ${USAGE}`);
13608
14202
  if (existing) daemon = existing;
13609
14203
  else
13610
14204
  try {
13611
- daemon = await detach(invocation, target, dataDir, entry);
14205
+ daemon = await detach(invocation, target, dataDir, entry2);
13612
14206
  } catch (error) {
13613
14207
  process.stderr.write(`${error.message}
13614
14208
  `);
13615
14209
  return 1;
13616
14210
  }
13617
- const url = launchUrl(daemon, target.file);
13618
- process.stdout.write(invocation.lan ? `${networkSummary(daemon)}Open: ${url}
14211
+ const url2 = launchUrl(daemon, target.file);
14212
+ process.stdout.write(invocation.lan ? `${networkSummary(daemon)}Open: ${url2}
13619
14213
  ` : `${daemon.root}
13620
- ${url}
14214
+ ${url2}
13621
14215
  `);
13622
- if (invocation.open) launchBrowser(url);
14216
+ if (invocation.open) launchBrowser(url2);
13623
14217
  return 0;
13624
14218
  }
13625
14219
  function desktopCommand(target, platform = process.platform) {