@oxbit/cli 0.8.0 → 0.10.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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync, constants as constants$1, watch, createWriteStream } from "node:fs";
2
+ import { realpathSync, constants as constants$1, watch, createWriteStream, existsSync } 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";
@@ -933,11 +933,11 @@ const $id$1 = "https://oxbit.dev/schemas/settings.v1.schema.json";
933
933
  const title$2 = "Oxbit Settings";
934
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.";
935
935
  const type$2 = "object";
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
+ 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.autoApplyEdits":{"title":"Auto-apply agent edits","description":"New conversations save agent file changes without review. Undo stays available, and permission prompts still ask.","type":"boolean","default":false},"agentACP.generateTitles":{"title":"Generate conversation titles","description":"When an agent does not name a conversation, ask the Oxbit Agent model for a short title after the first reply.","type":"boolean","default":true},"agentACP.oxbit-cli.command":{"title":"Oxbit Agent CLI executable","type":"string","default":"oxbit-agent"},"agentACP.oxbit-cli.args":{"title":"Oxbit Agent CLI arguments (JSON array)","type":"string","default":"[\\"acp\\"]"},"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":"[]"},"agentACP.oxbit.api":{"title":"Oxbit Agent API","description":"openai uses the Responses API, anthropic the Messages API, and openai-compatible Chat Completions at the base URL.","type":"string","default":"openai","enum":["openai","anthropic","openai-compatible"]},"agentACP.oxbit.baseUrl":{"title":"Oxbit Agent base URL","description":"Origin and path prefix for openai-compatible, for example https://openrouter.ai/api.","type":"string","default":""},"agentACP.oxbit.model":{"title":"Oxbit Agent model","description":"Model ID for new conversations.","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."}}`);
937
937
  const propertyNames = { "anyOf": [{ "not": { "pattern": "^\\[.*\\]$" } }, { "pattern": "^\\[[^\\[\\]]+\\]$" }] };
938
938
  const patternProperties = { "^\\[[^\\[\\]]+\\]$": { "$ref": "#/definitions/languageOverrides" } };
939
939
  const additionalProperties$1 = true;
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
+ 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.autoApplyEdits": { "$ref": "#/properties/agentACP.autoApplyEdits" }, "agentACP.generateTitles": { "$ref": "#/properties/agentACP.generateTitles" }, "agentACP.oxbit-cli.command": { "$ref": "#/properties/agentACP.oxbit-cli.command" }, "agentACP.oxbit-cli.args": { "$ref": "#/properties/agentACP.oxbit-cli.args" }, "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" }, "agentACP.oxbit.api": { "$ref": "#/properties/agentACP.oxbit.api" }, "agentACP.oxbit.baseUrl": { "$ref": "#/properties/agentACP.oxbit.baseUrl" }, "agentACP.oxbit.model": { "$ref": "#/properties/agentACP.oxbit.model" }, "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": "^\\[.*\\]$" } } } };
941
941
  const schema = {
942
942
  $schema: $schema$2,
943
943
  $id: $id$1,
@@ -1106,7 +1106,17 @@ function synchronization(capabilities) {
1106
1106
  const sync = capabilities.textDocumentSync;
1107
1107
  return typeof sync === "number" ? { openClose: true, change: sync } : sync ?? { openClose: false, change: 0 };
1108
1108
  }
1109
+ const OXBIT_CLI_PROVIDER = "oxbit-cli";
1109
1110
  const ACP_PROVIDERS = [
1111
+ {
1112
+ id: OXBIT_CLI_PROVIDER,
1113
+ name: "Oxbit Agent CLI",
1114
+ command: "oxbit-agent",
1115
+ args: ["acp"],
1116
+ setup: "Runs the oxbit-agent CLI that ships with Oxbit. It reads providers, keys, MCP servers, skills and hooks from ~/.oxbit/agent. Elsewhere, install @oxbit/agent from npm.",
1117
+ url: "https://github.com/yannelli/oxbit/tree/main/apps/agent",
1118
+ registryId: "oxbit-agent"
1119
+ },
1110
1120
  {
1111
1121
  id: "codex",
1112
1122
  name: "Codex ACP",
@@ -1166,6 +1176,7 @@ const ACP_CUSTOM_PROVIDER = "custom";
1166
1176
  const ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
1167
1177
  const acpPreset = (id) => ACP_PROVIDERS.find((provider) => provider.id === id);
1168
1178
  const acpBuiltinForRegistry = (registryId) => ACP_PROVIDERS.find((provider) => provider.registryId === registryId);
1179
+ const PUSH_RELAY_URL = "https://oxbit-push.ryanyannelli.workers.dev";
1169
1180
  const taskPhases = [
1170
1181
  "preinit",
1171
1182
  "init",
@@ -3456,6 +3467,8 @@ function bounded(value) {
3456
3467
  );
3457
3468
  return value;
3458
3469
  }
3470
+ const BRANCH_DIFF_FILES = 50;
3471
+ const BRANCH_DIFF_TEXT = 2e5;
3459
3472
  const conflicted = (index, working) => ["DD", "AU", "UD", "UA", "DU", "AA", "UU"].includes(index + working);
3460
3473
  const digest$1 = (value) => createHash("sha256").update(value).digest("hex");
3461
3474
  class Git {
@@ -3786,18 +3799,18 @@ class Git {
3786
3799
  if (typeof params.path !== "string")
3787
3800
  throw new RpcError("INVALID_PARAMS", "A file path is required");
3788
3801
  await this.files.resolve(params.path, true);
3789
- const detail = await this.show(params.ref, signal), file = detail.files.find((file2) => file2.path === params.path);
3802
+ const detail2 = await this.show(params.ref, signal), file = detail2.files.find((file2) => file2.path === params.path);
3790
3803
  if (!file)
3791
3804
  throw new RpcError("NOT_FOUND", "File is not part of this commit");
3792
- const before = detail.commit.parents[0] ? await this.optional(
3805
+ const before = detail2.commit.parents[0] ? await this.optional(
3793
3806
  [
3794
3807
  "show",
3795
- `${detail.commit.parents[0]}:${file.originalPath ?? file.path}`
3808
+ `${detail2.commit.parents[0]}:${file.originalPath ?? file.path}`
3796
3809
  ],
3797
3810
  signal
3798
3811
  ) : void 0;
3799
3812
  const after = await this.optional(
3800
- ["show", `${detail.commit.id}:${file.path}`],
3813
+ ["show", `${detail2.commit.id}:${file.path}`],
3801
3814
  signal
3802
3815
  );
3803
3816
  const diff = await this.run(
@@ -3808,7 +3821,7 @@ class Git {
3808
3821
  "--no-ext-diff",
3809
3822
  "--no-textconv",
3810
3823
  "--no-color",
3811
- detail.commit.id,
3824
+ detail2.commit.id,
3812
3825
  "--",
3813
3826
  file.path,
3814
3827
  ...file.originalPath ? [file.originalPath] : []
@@ -3866,6 +3879,88 @@ class Git {
3866
3879
  )).stdout
3867
3880
  });
3868
3881
  }
3882
+ /** The upstream remote's default branch, then `origin/HEAD`, then local `main` or `master`. */
3883
+ async baseBranch(branch, signal) {
3884
+ const remote = branch ? (await this.optional(["config", "--get", `branch.${branch}.remote`], signal)).stdout.trim() : "";
3885
+ const candidates = [
3886
+ ...remote && remote !== "." ? [`refs/remotes/${remote}/HEAD`] : [],
3887
+ "refs/remotes/origin/HEAD",
3888
+ "refs/heads/main",
3889
+ "refs/heads/master"
3890
+ ];
3891
+ for (const ref of candidates) {
3892
+ const commit = await this.optional(
3893
+ ["rev-parse", "--verify", "--quiet", "--end-of-options", `${ref}^{commit}`],
3894
+ signal
3895
+ );
3896
+ if (commit.exitCode) continue;
3897
+ const name = await this.optional(["rev-parse", "--abbrev-ref", ref], signal);
3898
+ return { name: name.stdout.trim() || ref, commit: commit.stdout.trim() };
3899
+ }
3900
+ return void 0;
3901
+ }
3902
+ async untrackedPatch(relative, signal) {
3903
+ const result = await this.run(
3904
+ ["diff", "--no-index", "--no-ext-diff", "--no-textconv", "--no-color", "--src-prefix=a/", "--dst-prefix=b/", "--", "/dev/null", relative],
3905
+ signal,
3906
+ this.files.root,
3907
+ void 0,
3908
+ true
3909
+ );
3910
+ if (result.exitCode > 1)
3911
+ throw new RpcError("GIT_FAILED", result.stderr.trim() || `Git exited ${result.exitCode}`);
3912
+ return result.stdout;
3913
+ }
3914
+ /** Committed, staged, unstaged and untracked changes since the merge base with the base branch. */
3915
+ async branchDiff(params, signal) {
3916
+ const limit2 = Number(params.limit ?? BRANCH_DIFF_FILES);
3917
+ if (!Number.isInteger(limit2) || limit2 < 0 || limit2 > BRANCH_DIFF_FILES)
3918
+ throw new RpcError("INVALID_PARAMS", "Invalid file limit");
3919
+ const inside = await this.optional(["rev-parse", "--is-inside-work-tree"], signal);
3920
+ if (inside.exitCode || inside.stdout.trim() !== "true") {
3921
+ if (inside.exitCode && !inside.stderr.includes("not a git repository"))
3922
+ throw new RpcError("GIT_FAILED", inside.stderr.trim());
3923
+ return { repository: false };
3924
+ }
3925
+ const head = await this.head(signal);
3926
+ if (!head) throw new RpcError("NOT_FOUND", "This branch has no commits yet");
3927
+ const branch = (await this.optional(["symbolic-ref", "-q", "--short", "HEAD"], signal)).stdout.trim();
3928
+ const base = await this.baseBranch(branch, signal);
3929
+ if (!base) throw new RpcError("NOT_FOUND", "No base branch: origin/HEAD, main and master are missing");
3930
+ const mergeBase = await this.optional(["merge-base", base.commit, head], signal);
3931
+ if (mergeBase.exitCode)
3932
+ throw new RpcError("NOT_FOUND", `${branch || "HEAD"} has no common history with ${base.name}`);
3933
+ const from = mergeBase.stdout.trim();
3934
+ const names = async (args) => (await this.run(args, signal)).stdout.split("\0").filter(Boolean);
3935
+ const tracked = await names(["diff", "--name-only", "--relative", "-z", from, "--"]);
3936
+ const untracked = await names(["ls-files", "--others", "--exclude-standard", "-z", "--"]);
3937
+ const files = [];
3938
+ let size = 0, truncated = false;
3939
+ for (const [index, relative] of [...tracked, ...untracked].entries()) {
3940
+ if (files.length >= limit2 || size >= BRANCH_DIFF_TEXT) {
3941
+ truncated = true;
3942
+ break;
3943
+ }
3944
+ let diff = "";
3945
+ try {
3946
+ diff = index < tracked.length ? (await this.run(
3947
+ ["diff", "--no-ext-diff", "--no-textconv", "--no-color", "--relative", "--src-prefix=a/", "--dst-prefix=b/", from, "--", relative],
3948
+ signal
3949
+ )).stdout : await this.untrackedPatch(relative, signal);
3950
+ } catch (error) {
3951
+ if (error.code !== "OUTPUT_LIMIT") throw error;
3952
+ truncated = true;
3953
+ }
3954
+ const binary2 = /^Binary files /m.test(diff);
3955
+ if (size + diff.length > BRANCH_DIFF_TEXT) {
3956
+ diff = diff.slice(0, BRANCH_DIFF_TEXT - size);
3957
+ truncated = true;
3958
+ }
3959
+ size += diff.length;
3960
+ files.push({ path: relative, diff, binary: binary2 });
3961
+ }
3962
+ return bounded({ base: base.name, head: branch || head.slice(0, 7), files, truncated });
3963
+ }
3869
3964
  async action(method, params, signal, progress) {
3870
3965
  const pending = this.queue.catch(() => {
3871
3966
  }).then(() => this.execute(method, params, signal, progress));
@@ -4617,7 +4712,10 @@ class LanguageServer {
4617
4712
  if (resume) this.paused = false;
4618
4713
  if (!resume && this.phase === "failed") throw new RpcError("LSP_FAILED", this.error || "Language server failed. Retry from Language Servers.");
4619
4714
  if (this.paused) throw new RpcError("LSP_STOPPED", "Language server is stopped. Start it from Language Servers.");
4620
- if (this.starting) return this.starting;
4715
+ if (this.starting) {
4716
+ this.emit("oxbit/serverState", { state: this.phase });
4717
+ return this.starting;
4718
+ }
4621
4719
  if (this.child && this.capabilities)
4622
4720
  return { capabilities: this.capabilities, registrations: [...this.registrations.values()], rootUri: this.uri(""), projectRootUri: pathToFileURL(this.options.root ?? this.files.root).href, version: this.launchSpec?.version };
4623
4721
  this.error = "";
@@ -5247,6 +5345,47 @@ function deferToRuff(spec) {
5247
5345
  diagnostics: (diagnostics) => diagnostics.filter((item) => item.code !== void 0)
5248
5346
  };
5249
5347
  }
5348
+ const phpProbes = [
5349
+ { command: ["herd", "which-php"], php: (output) => output.includes("No usable PHP version found") ? void 0 : [output.trim()] },
5350
+ { command: ["valet", "which-php"], php: (output) => [output.trim()] },
5351
+ { command: ["./vendor/bin/sail", "ps"], php: () => ["./vendor/bin/sail", "php"] },
5352
+ { command: ["lando", "php", "-r", "echo PHP_BINARY;"], php: () => ["lando", "php"] },
5353
+ { command: ["ddev", "php", "-r", "echo PHP_BINARY;"], php: () => ["ddev", "php"] },
5354
+ { command: ["php", "-r", "echo PHP_BINARY;"], php: (output) => [output.trim()] }
5355
+ ];
5356
+ function probe(command, cwd, env2, timeout, signal) {
5357
+ return new Promise((resolve) => {
5358
+ const child = spawn(command[0], command.slice(1), { cwd, env: env2, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "ignore"] });
5359
+ trackChild(child);
5360
+ let output = "";
5361
+ const finish = (value) => {
5362
+ clearTimeout(timer);
5363
+ signal?.removeEventListener("abort", stop2);
5364
+ resolve(value);
5365
+ };
5366
+ const stop2 = () => {
5367
+ killProcess(child);
5368
+ finish();
5369
+ };
5370
+ const timer = setTimeout(stop2, timeout);
5371
+ signal?.addEventListener("abort", stop2, { once: true });
5372
+ child.stdout.on("data", (data) => {
5373
+ output = (output + data.toString()).slice(-4096);
5374
+ });
5375
+ child.on("error", () => finish());
5376
+ child.on("close", (code) => finish(code === 0 ? output : void 0));
5377
+ });
5378
+ }
5379
+ async function laravelPhpCommand(root, env2, signal, timeout = 5e3) {
5380
+ for (const { command, php } of phpProbes) {
5381
+ signal?.throwIfAborted();
5382
+ const output = await probe(command, root, env2, timeout, signal);
5383
+ const found = output === void 0 ? void 0 : php(output);
5384
+ if (found?.[0]) return found;
5385
+ }
5386
+ signal?.throwIfAborted();
5387
+ throw new Error("Laravel needs PHP on the runtime host. No PHP was found through Herd, Valet, Sail, Lando, DDEV or php on PATH. Install PHP or set languageServers.laravel.initializationOptions.phpCommand.");
5388
+ }
5250
5389
  const npmPresets = {
5251
5390
  typescript: ["typescript-language-server", "lib/cli.mjs", ["typescript"]],
5252
5391
  html: ["vscode-langservers-extracted", "bin/vscode-html-language-server"],
@@ -5487,19 +5626,22 @@ class ManagedInstaller {
5487
5626
  }
5488
5627
  }
5489
5628
  } catch {
5490
- try {
5491
- if (Date.now() - (await fs.stat(lock)).mtimeMs > 6e4) {
5492
- await fs.rm(lock, { recursive: true, force: true });
5493
- continue;
5494
- }
5495
- } catch {
5629
+ }
5630
+ try {
5631
+ if (Date.now() - (await fs.stat(lock)).mtimeMs > 6e4) {
5632
+ await fs.rm(lock, { recursive: true, force: true });
5633
+ continue;
5496
5634
  }
5635
+ } catch {
5497
5636
  }
5498
5637
  if (Date.now() > deadline) throw new Error("Timed out waiting for another language server installation");
5499
5638
  await setTimeout$1(100, void 0, { signal });
5500
5639
  }
5501
5640
  }
5502
5641
  const stage = path.join(base, `.staging-${randomUUID()}`);
5642
+ const heartbeat = setInterval(() => void fs.utimes(lock, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date()).catch(() => {
5643
+ }), 1e4);
5644
+ heartbeat.unref();
5503
5645
  try {
5504
5646
  if (await complete()) return target;
5505
5647
  for (const name of await fs.readdir(base)) if (name.startsWith(".staging-")) await fs.rm(path.join(base, name), { recursive: true, force: true });
@@ -5515,6 +5657,7 @@ class ManagedInstaller {
5515
5657
  await fs.rename(stage, target);
5516
5658
  return target;
5517
5659
  } finally {
5660
+ clearInterval(heartbeat);
5518
5661
  await fs.rm(stage, { recursive: true, force: true });
5519
5662
  await fs.rm(lock, { recursive: true, force: true });
5520
5663
  }
@@ -5813,6 +5956,8 @@ class LanguageServerManager {
5813
5956
  else spec = await resolveLaunch(definition2.id, root, this.installer, signal);
5814
5957
  spec = { ...spec, args: definition2.args ?? spec.args, env: { ...spec.env, ...definition2.env }, initializationOptions: merge(spec.initializationOptions, definition2.initializationOptions), settings: merge(spec.settings, definition2.settings) };
5815
5958
  if (ruff) spec = deferToRuff(spec);
5959
+ if (definition2.id === "laravel" && !spec.initializationOptions?.phpCommand && (spec.initializationOptions?.phpEnvironment ?? "auto") === "auto")
5960
+ spec.initializationOptions = { ...spec.initializationOptions, phpCommand: await laravelPhpCommand(root, { ...process.env, ...spec.env }, signal) };
5816
5961
  if (definition2.id === "json") {
5817
5962
  const options2 = this.project ? (await this.project.reload()).schemas : { catalog: true, download: true, associations: [] };
5818
5963
  spec.settings = await this.schemas.settings(spec.settings ?? {}, options2);
@@ -5853,7 +5998,7 @@ class LanguageServerManager {
5853
5998
  if (!instance.attachments.size && !instance.idle) {
5854
5999
  instance.idle = setTimeout(() => {
5855
6000
  instance.idle = void 0;
5856
- void this.stop(false, instance.id);
6001
+ if (this.instances.get(instance.id) === instance) void this.stop(false, instance.id);
5857
6002
  }, this.idleMs);
5858
6003
  instance.idle.unref();
5859
6004
  }
@@ -7390,7 +7535,7 @@ class SubagentRegistry {
7390
7535
  let cached;
7391
7536
  function runtimeVersion() {
7392
7537
  if (cached) return cached;
7393
- return cached = "0.8.0";
7538
+ return cached = "0.10.0";
7394
7539
  }
7395
7540
  const MAX_REQUEST_BYTES = 1024 * 1024;
7396
7541
  const MAX_CONCURRENT_REQUESTS = 16;
@@ -7979,6 +8124,9 @@ const MAX_IMAGE_DATA = Math.ceil(5 * 1024 * 1024 / 3) * 4;
7979
8124
  const MAX_QUEUED_IMAGE_DATA = 16 * 1024 * 1024;
7980
8125
  const withoutImageData = (key, value) => key === "data" ? void 0 : value;
7981
8126
  const EDITOR_UNSUPPORTED = "The connected editor does not support this tool; update Oxbit";
8127
+ function bundledAgent(base = import.meta.url) {
8128
+ return ["./agent/index.js", "../../agent/dist/index.js"].map((relative) => fileURLToPath(new URL(relative, base))).find((file) => existsSync(file));
8129
+ }
7982
8130
  function strings(value) {
7983
8131
  if (!Array.isArray(value) || value.length > 128 || value.some(
7984
8132
  (v) => typeof v !== "string" || v.length > 16384 || v.includes("\0")
@@ -7991,16 +8139,19 @@ function strings(value) {
7991
8139
  }
7992
8140
  class AgentACP {
7993
8141
  constructor(files, emit, stateChanged = () => {
7994
- }, acpRegistry) {
8142
+ }, acpRegistry, statusChanged = () => {
8143
+ }) {
7995
8144
  this.files = files;
7996
8145
  this.emit = emit;
7997
8146
  this.stateChanged = stateChanged;
7998
8147
  this.acpRegistry = acpRegistry;
8148
+ this.statusChanged = statusChanged;
7999
8149
  }
8000
8150
  files;
8001
8151
  emit;
8002
8152
  stateChanged;
8003
8153
  acpRegistry;
8154
+ statusChanged;
8004
8155
  agents = /* @__PURE__ */ new Map();
8005
8156
  mcp = new AgentMCP((id, name, args) => this.tool(id, name, args));
8006
8157
  get(owner, id) {
@@ -8050,6 +8201,20 @@ class AgentACP {
8050
8201
  if (agent.client) this.emit(agent.client, event, payload);
8051
8202
  this.stateChanged();
8052
8203
  }
8204
+ report(agent, status2) {
8205
+ try {
8206
+ this.statusChanged({
8207
+ owner: agent.owner,
8208
+ agent: agent.connection.name ?? agent.connection.provider,
8209
+ status: status2,
8210
+ pending: agent.approvals.size,
8211
+ startedAt: agent.startedAt
8212
+ });
8213
+ } catch (error) {
8214
+ process.stderr.write(`Oxbit agent status listener failed: ${String(error)}
8215
+ `);
8216
+ }
8217
+ }
8053
8218
  list(owner) {
8054
8219
  return { sessions: [...this.agents.values()].filter((a) => a.owner === owner && !a.stopped).map((a) => ({
8055
8220
  connection: a.connection,
@@ -8255,12 +8420,11 @@ class AgentACP {
8255
8420
  if (!command.trim() || command.includes("\0"))
8256
8421
  throw new RpcError("INVALID_PARAMS", "Invalid agent executable");
8257
8422
  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
- };
8423
+ const name = preset2?.name ?? (custom || "Custom agent");
8424
+ const args = strings(launch.args ?? (preset2 ? [...preset2.args] : []));
8425
+ const bundled = provider === OXBIT_CLI_PROVIDER && command === preset2?.command ? bundledAgent() : void 0;
8426
+ if (bundled) return { provider, name, command: process.execPath, args: [bundled, ...args] };
8427
+ return { provider, name, command, args };
8264
8428
  }
8265
8429
  async call(owner, id, method, params = {}, signal) {
8266
8430
  const agent = this.get(owner, id);
@@ -8305,11 +8469,16 @@ class AgentACP {
8305
8469
  agent.busy = false;
8306
8470
  }
8307
8471
  }
8308
- if (["session/new", "session/load", "session/resume"].includes(method)) {
8472
+ if (["session/new", "session/load", "session/resume", "session/fork"].includes(method)) {
8309
8473
  if (method === "session/load" && !agent.connection.capabilities?.loadSession)
8310
8474
  throw new Error("This agent does not support loading conversations");
8311
8475
  if (method === "session/resume" && !agent.connection.capabilities?.sessionCapabilities?.resume)
8312
8476
  throw new Error("This agent does not support resuming conversations");
8477
+ if (method === "session/fork" && !agent.connection.capabilities?.sessionCapabilities?.fork)
8478
+ throw new Error("This agent does not support forking conversations");
8479
+ const source2 = agent.connection.sessionId;
8480
+ if (method === "session/fork" && !source2)
8481
+ throw new Error("Start a conversation first");
8313
8482
  if (agent.subagents?.activeCount)
8314
8483
  throw new Error(
8315
8484
  "Wait for active subagents or disconnect before changing conversations"
@@ -8321,7 +8490,7 @@ class AgentACP {
8321
8490
  const previousBytes = agent.eventBytes;
8322
8491
  const previousTitle = agent.title;
8323
8492
  const previousTruncated = agent.truncated;
8324
- const target = method === "session/new" ? void 0 : requireString(params, "sessionId");
8493
+ const target = method === "session/new" || method === "session/fork" ? void 0 : requireString(params, "sessionId");
8325
8494
  if (agent.approvals.size)
8326
8495
  throw new Error(
8327
8496
  "Resolve pending requests before changing conversations"
@@ -8342,7 +8511,7 @@ class AgentACP {
8342
8511
  const result = await this.request(agent, method, {
8343
8512
  cwd: this.files.root,
8344
8513
  mcpServers: [await this.mcp.server(agent.connection.id, !!agent.connection.capabilities?.mcpCapabilities?.http)],
8345
- ...target ? { sessionId: target } : {}
8514
+ ...target ? { sessionId: target } : method === "session/fork" ? { sessionId: source2 } : {}
8346
8515
  });
8347
8516
  const sessionId2 = target ?? result?.sessionId;
8348
8517
  if (typeof sessionId2 !== "string" || !sessionId2)
@@ -8418,12 +8587,14 @@ class AgentACP {
8418
8587
  const messageId = params.messageId === void 0 ? randomUUID() : requireString(params, "messageId", 256);
8419
8588
  agent.busy = true;
8420
8589
  agent.turn++;
8590
+ agent.startedAt = Date.now();
8421
8591
  if (agent.title === "New conversation") agent.title = text2.slice(0, 100);
8422
8592
  this.publish(agent, "acp.turnStarted", {
8423
8593
  messageId,
8424
8594
  text: text2,
8425
8595
  context: JSON.parse(JSON.stringify(params.context ?? [], withoutImageData))
8426
8596
  });
8597
+ this.report(agent, "working");
8427
8598
  const abort = () => this.stopAgent(agent, "Agent request interrupted");
8428
8599
  signal?.addEventListener("abort", abort, { once: true });
8429
8600
  let stopReason;
@@ -8448,6 +8619,8 @@ class AgentACP {
8448
8619
  signal?.removeEventListener("abort", abort);
8449
8620
  if (!agent.stopped) {
8450
8621
  this.publish(agent, "acp.turnEnded", { stopReason, error: failure2 });
8622
+ if (failure2 !== void 0) this.report(agent, "failed");
8623
+ else if (stopReason !== "cancelled") this.report(agent, "finished");
8451
8624
  this.publishQueue(agent);
8452
8625
  queueMicrotask(() => this.runQueue(agent));
8453
8626
  }
@@ -8530,6 +8703,14 @@ ${content}`
8530
8703
  this.publishQueue(agent);
8531
8704
  return { prompt };
8532
8705
  }
8706
+ moveQueued(owner, id, promptId, index) {
8707
+ const agent = this.get(owner, id);
8708
+ const from = agent.queue.findIndex((p) => p.id === promptId);
8709
+ if (from < 0) throw new Error("The queued message has already started or was removed");
8710
+ const [prompt] = agent.queue.splice(from, 1);
8711
+ agent.queue.splice(Math.max(0, Math.min(index, agent.queue.length)), 0, prompt);
8712
+ return this.publishQueue(agent);
8713
+ }
8533
8714
  resumeQueue(owner, id) {
8534
8715
  const agent = this.get(owner, id);
8535
8716
  agent.queuePaused = false;
@@ -8691,6 +8872,7 @@ ${content}`
8691
8872
  if (method !== "fs/read_text_file" && !method.startsWith("oxbit/"))
8692
8873
  registry.waiting(sessionId, requestId, true, params.toolCallId ?? params.toolCall?.toolCallId);
8693
8874
  this.publish(agent, "acp.request", this.requestEvent(agent, requestId, request));
8875
+ if (method === "session/request_permission") this.report(agent, "waiting");
8694
8876
  });
8695
8877
  }
8696
8878
  async tool(id, name, args) {
@@ -12209,6 +12391,215 @@ function loginShellEnvironment(cwd, inherited = process.env, platform = process.
12209
12391
  child.on("close", (code) => finish(code === 0 ? parseShellEnvironment(Buffer.concat(chunks).toString(), mark) : void 0));
12210
12392
  });
12211
12393
  }
12394
+ const PUSH_DEVICES_FILE = "push-devices.json";
12395
+ const MAX_DEVICES = 16;
12396
+ const RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
12397
+ const WINDOW_SECONDS = 15 * 60;
12398
+ const RELAY_TIMEOUT_MS = 1e4;
12399
+ const HANDLE = /^[A-Za-z0-9_-]{1,2048}$/;
12400
+ const STATUSES = ["working", "waiting", "finished", "failed"];
12401
+ const TITLES = { waiting: "Agent needs your input", finished: "Agent finished", failed: "Agent turn failed" };
12402
+ const log = (message2) => process.stderr.write(`Oxbit push: ${message2}
12403
+ `);
12404
+ function deviceIdOf(params) {
12405
+ const value = requireString(params, "deviceId", 128);
12406
+ if (!value) throw new RpcError("INVALID_PARAMS", "Invalid deviceId");
12407
+ return value;
12408
+ }
12409
+ function handleOf(params, key) {
12410
+ const value = params[key];
12411
+ if (value === void 0 || value === null) return void 0;
12412
+ if (typeof value !== "string" || !HANDLE.test(value)) throw new RpcError("INVALID_PARAMS", `Invalid ${key}`);
12413
+ return value;
12414
+ }
12415
+ const text$1 = (value) => typeof value === "string" && value.length <= 1024;
12416
+ function activityOf(value) {
12417
+ if (value === null) return null;
12418
+ const v = value ?? {};
12419
+ const startedAt = v.startedAt;
12420
+ if (typeof value !== "object" || !text$1(v.agent) || !text$1(v.title) || !text$1(v.detail) || !STATUSES.includes(v.status) || !Number.isSafeInteger(v.pending) || v.pending < 0 || startedAt !== void 0 && (typeof startedAt !== "number" || !Number.isFinite(startedAt) || startedAt < 0))
12421
+ throw new RpcError("INVALID_PARAMS", "Invalid activity");
12422
+ return {
12423
+ agent: v.agent,
12424
+ title: v.title,
12425
+ status: v.status,
12426
+ detail: v.detail,
12427
+ pending: v.pending,
12428
+ ...startedAt === void 0 ? {} : { startedAt }
12429
+ };
12430
+ }
12431
+ function restore(entry2) {
12432
+ try {
12433
+ const activity = entry2.activity === void 0 ? null : activityOf(entry2.activity);
12434
+ const device = {
12435
+ deviceId: deviceIdOf(entry2),
12436
+ session: requireString(entry2, "session", 128),
12437
+ alert: handleOf(entry2, "alert"),
12438
+ liveActivity: handleOf(entry2, "liveActivity"),
12439
+ active: false,
12440
+ ...activity ? { activity } : {},
12441
+ updatedAt: entry2.updatedAt
12442
+ };
12443
+ return Number.isFinite(device.updatedAt) ? device : void 0;
12444
+ } catch {
12445
+ return void 0;
12446
+ }
12447
+ }
12448
+ function detail(status2, pending) {
12449
+ if (status2 === "working") return "Working";
12450
+ if (status2 === "waiting") return pending === 1 ? "1 request needs an answer" : `${pending} requests need an answer`;
12451
+ return TITLES[status2];
12452
+ }
12453
+ function activityState(snapshot, event) {
12454
+ return {
12455
+ status: event.status,
12456
+ title: snapshot.title,
12457
+ detail: detail(event.status, event.pending),
12458
+ pending: event.pending,
12459
+ ...event.status === "working" && event.startedAt !== void 0 ? { startedAt: Math.floor(event.startedAt / 1e3) } : {}
12460
+ };
12461
+ }
12462
+ class PushDevices {
12463
+ constructor(file) {
12464
+ this.file = file;
12465
+ }
12466
+ file;
12467
+ devices = /* @__PURE__ */ new Map();
12468
+ /** Last Live Activity state sent per device. */
12469
+ sent = /* @__PURE__ */ new Map();
12470
+ persistence = Promise.resolve();
12471
+ static async open(dataDir) {
12472
+ const store = new PushDevices(path.join(dataDir, PUSH_DEVICES_FILE));
12473
+ try {
12474
+ const state2 = JSON.parse(await fs.readFile(store.file, "utf8"));
12475
+ if (state2?.version === 1 && Array.isArray(state2.devices))
12476
+ for (const entry2 of state2.devices) {
12477
+ const device = entry2 && typeof entry2 === "object" ? restore(entry2) : void 0;
12478
+ if (device) store.devices.set(device.deviceId, device);
12479
+ }
12480
+ } catch (error) {
12481
+ if (error.code !== "ENOENT") log(`ignoring unreadable ${PUSH_DEVICES_FILE}: ${String(error)}`);
12482
+ }
12483
+ store.prune();
12484
+ return store;
12485
+ }
12486
+ async register(session, connection, params) {
12487
+ const deviceId = deviceIdOf(params), alert = handleOf(params, "alert"), liveActivity = handleOf(params, "liveActivity");
12488
+ const previous = this.devices.get(deviceId);
12489
+ this.sent.delete(deviceId);
12490
+ this.devices.set(deviceId, {
12491
+ ...previous,
12492
+ deviceId,
12493
+ session,
12494
+ alert,
12495
+ liveActivity,
12496
+ active: true,
12497
+ connection,
12498
+ updatedAt: Date.now()
12499
+ });
12500
+ await this.persist();
12501
+ return {};
12502
+ }
12503
+ async state(session, connection, params) {
12504
+ const deviceId = deviceIdOf(params);
12505
+ if (typeof params.active !== "boolean") throw new RpcError("INVALID_PARAMS", "active must be boolean");
12506
+ const activity = params.activity === void 0 ? void 0 : activityOf(params.activity);
12507
+ const device = this.devices.get(deviceId);
12508
+ if (!device || device.session !== session) throw new RpcError("NOT_FOUND", "Register the device first");
12509
+ device.active = params.active;
12510
+ if (params.active) device.connection = connection;
12511
+ if (activity) device.activity = activity;
12512
+ else if (activity === null) delete device.activity;
12513
+ device.updatedAt = Date.now();
12514
+ this.sent.delete(deviceId);
12515
+ await this.persist();
12516
+ return {};
12517
+ }
12518
+ async unregister(session, params) {
12519
+ const deviceId = deviceIdOf(params);
12520
+ if (this.devices.get(deviceId)?.session === session) {
12521
+ this.devices.delete(deviceId);
12522
+ this.sent.delete(deviceId);
12523
+ await this.persist();
12524
+ }
12525
+ return {};
12526
+ }
12527
+ /** Marks devices inactive when the connection that set them active closes. */
12528
+ disconnect(connection) {
12529
+ let changed = false;
12530
+ for (const device of this.devices.values())
12531
+ if (device.active && device.connection === connection) {
12532
+ device.active = false;
12533
+ changed = true;
12534
+ }
12535
+ if (changed) this.persist().catch((error) => log(`cannot save devices: ${String(error)}`));
12536
+ }
12537
+ /** Sends alert and Live Activity pushes to the owner's inactive devices; resolves after every send settles. */
12538
+ async notify(event) {
12539
+ const sends = [];
12540
+ for (const device of this.devices.values()) {
12541
+ if (device.active || device.session !== event.owner) continue;
12542
+ if (event.status !== "working" && device.alert)
12543
+ sends.push(this.send(device.deviceId, "alert", {
12544
+ handle: device.alert,
12545
+ alert: { title: TITLES[event.status], body: device.activity?.title || event.agent }
12546
+ }));
12547
+ if (!device.liveActivity || !device.activity) continue;
12548
+ const state2 = activityState(device.activity, event), end = event.status === "finished" || event.status === "failed";
12549
+ const key = JSON.stringify([end, state2]);
12550
+ if (this.sent.get(device.deviceId) === key) continue;
12551
+ this.sent.set(device.deviceId, key);
12552
+ const window = Math.floor(Date.now() / 1e3) + WINDOW_SECONDS;
12553
+ sends.push(this.send(device.deviceId, "liveActivity", {
12554
+ handle: device.liveActivity,
12555
+ activity: { event: end ? "end" : "update", state: state2, ...end ? { dismissalDate: window } : { staleDate: window } }
12556
+ }));
12557
+ }
12558
+ await Promise.all(sends);
12559
+ }
12560
+ flush() {
12561
+ return this.persistence.catch(() => {
12562
+ });
12563
+ }
12564
+ async send(deviceId, kind, request) {
12565
+ const relay = (process.env.OXBIT_PUSH_RELAY ?? PUSH_RELAY_URL).replace(/\/+$/, "");
12566
+ try {
12567
+ const response = await fetch(`${relay}/v1/push`, {
12568
+ method: "POST",
12569
+ headers: { "Content-Type": "application/json" },
12570
+ body: JSON.stringify(request),
12571
+ signal: AbortSignal.timeout(RELAY_TIMEOUT_MS)
12572
+ });
12573
+ const result = await response.json().catch(() => ({}));
12574
+ const device = this.devices.get(deviceId);
12575
+ if (result.gone && device?.[kind] === request.handle) {
12576
+ delete device[kind];
12577
+ if (kind === "liveActivity") this.sent.delete(deviceId);
12578
+ await this.persist();
12579
+ } else if (!response.ok || !result.sent)
12580
+ log(`relay did not send to ${deviceId}: ${result.reason ?? `HTTP ${response.status}`}`);
12581
+ } catch (error) {
12582
+ log(`push to ${deviceId} failed: ${error instanceof Error ? error.message : String(error)}`);
12583
+ }
12584
+ }
12585
+ prune(now = Date.now()) {
12586
+ for (const [id, device] of this.devices)
12587
+ if (now - device.updatedAt > RETENTION_MS) this.devices.delete(id);
12588
+ const oldest = [...this.devices.values()].sort((a, b) => a.updatedAt - b.updatedAt);
12589
+ for (const device of oldest.slice(0, Math.max(0, oldest.length - MAX_DEVICES))) this.devices.delete(device.deviceId);
12590
+ for (const id of this.sent.keys()) if (!this.devices.has(id)) this.sent.delete(id);
12591
+ }
12592
+ persist() {
12593
+ this.prune();
12594
+ const payload = JSON.stringify({ version: 1, devices: [...this.devices.values()] });
12595
+ this.persistence = this.persistence.catch(() => {
12596
+ }).then(async () => {
12597
+ await fs.writeFile(this.file + ".tmp", payload, { mode: 384 });
12598
+ await fs.rename(this.file + ".tmp", this.file);
12599
+ });
12600
+ return this.persistence;
12601
+ }
12602
+ }
12212
12603
  const MAX_SOCKET_BACKLOG_BYTES = MAX_FILE_WRITE_MESSAGE_BYTES;
12213
12604
  const allCapabilities = [
12214
12605
  "filesystem.read",
@@ -12351,10 +12742,11 @@ async function createRuntime(options2) {
12351
12742
  return session;
12352
12743
  };
12353
12744
  const acpRegistry = new ACPRegistry(dataDir, options2.acpRegistryUrl);
12745
+ const pushDevices = await PushDevices.open(dataDir);
12354
12746
  const agents = new AgentACP(files, (connectionId, name, params) => {
12355
12747
  const connection = connections.get(connectionId);
12356
12748
  if (connection?.session && !connection.session.revoked && trusted) event(connection, name, params);
12357
- }, () => updateIdle(), acpRegistry);
12749
+ }, () => updateIdle(), acpRegistry, (status2) => void pushDevices.notify(status2));
12358
12750
  const processEvent = ({ event: name, params, stream, seq }, ownerId) => {
12359
12751
  const cap = name.startsWith("terminal.") ? "terminal" : "tasks";
12360
12752
  for (const c of connections.values()) {
@@ -12701,7 +13093,7 @@ async function createRuntime(options2) {
12701
13093
  if (method.startsWith("terminal.")) return { cap: "terminal", trust: true };
12702
13094
  if (method.startsWith("tasks.")) return { cap: "tasks", trust: !["tasks.catalog", "tasks.list", "tasks.attach", "tasks.worktrees"].includes(method) };
12703
13095
  if (method.startsWith("git.")) return { cap: "git", trust: true };
12704
- if (method.startsWith("acp.")) return { cap: "extensions", trust: true };
13096
+ if (method.startsWith("acp.") || method.startsWith("push.")) return { cap: "extensions", trust: true };
12705
13097
  if (method.startsWith("lsp.")) return { cap: "lsp", trust: true };
12706
13098
  if (method.startsWith("collab.")) return { cap: "collaboration" };
12707
13099
  if (method.startsWith("extensions."))
@@ -12734,6 +13126,7 @@ async function createRuntime(options2) {
12734
13126
  if (!["acp.start", "acp.list", "acp.attach", "acp.detach", "acp.stop", "acp.disconnect", "acp.registry"].includes(method) && !method.startsWith("acp.checkpoint."))
12735
13127
  agents.control(session.id, requireString(params, "id"), connection.id);
12736
13128
  }
13129
+ if (method.startsWith("push.")) owner(connection);
12737
13130
  if (method.startsWith("project.")) owner(connection);
12738
13131
  if (method.startsWith("settings.")) owner(connection);
12739
13132
  switch (method) {
@@ -12768,6 +13161,9 @@ async function createRuntime(options2) {
12768
13161
  return agents.enqueue(session.id, requireString(params, "id"), params, method === "acp.interrupt");
12769
13162
  case "acp.dequeue":
12770
13163
  return agents.dequeue(session.id, requireString(params, "id"), requireString(params, "promptId"));
13164
+ case "acp.moveQueued":
13165
+ if (!Number.isInteger(params.index)) throw new RpcError("INVALID_PARAMS", "index must be an integer");
13166
+ return agents.moveQueued(session.id, requireString(params, "id"), requireString(params, "promptId"), params.index);
12771
13167
  case "acp.resumeQueue":
12772
13168
  return agents.resumeQueue(session.id, requireString(params, "id"));
12773
13169
  case "acp.call":
@@ -12784,6 +13180,12 @@ async function createRuntime(options2) {
12784
13180
  case "acp.disconnect":
12785
13181
  agents.disconnect(session.id, connection.id);
12786
13182
  return {};
13183
+ case "push.register":
13184
+ return pushDevices.register(session.id, connection.id, params);
13185
+ case "push.state":
13186
+ return pushDevices.state(session.id, connection.id, params);
13187
+ case "push.unregister":
13188
+ return pushDevices.unregister(session.id, params);
12787
13189
  case "acp.checkpoint.create":
12788
13190
  return checkpoints.create(signal);
12789
13191
  case "acp.checkpoint.diff":
@@ -12965,6 +13367,8 @@ async function createRuntime(options2) {
12965
13367
  return git.show(params.ref, signal);
12966
13368
  case "git.commitDiff":
12967
13369
  return git.commitDiff(params, signal);
13370
+ case "git.branchDiff":
13371
+ return git.branchDiff(params, signal);
12968
13372
  case "git.stashes":
12969
13373
  return git.stashes(signal);
12970
13374
  case "git.stashDiff":
@@ -13129,6 +13533,25 @@ async function createRuntime(options2) {
13129
13533
  params.encoding,
13130
13534
  params.eol
13131
13535
  );
13536
+ case "search.files": {
13537
+ const term = requireString(params, "query", 1024).trim().toLowerCase();
13538
+ if (!term) return [];
13539
+ const result = await runCommand(options2.desktop?.rgPath ?? "rg", ["--files", "--hidden", "--glob", "!.git", "--glob", "!node_modules", "--glob", "!.oxbit-tmp-*"], {
13540
+ cwd: root,
13541
+ signal,
13542
+ maxBytes: 64 * MAX_BUFFER_BYTES
13543
+ });
13544
+ if (result.exitCode > 1) throw new RpcError("SEARCH_FAILED", result.stderr.trim());
13545
+ const matches = (path2) => {
13546
+ let at = 0;
13547
+ for (const char of term) {
13548
+ at = path2.indexOf(char, at) + 1;
13549
+ if (!at) return false;
13550
+ }
13551
+ return true;
13552
+ };
13553
+ return result.stdout.split("\n").filter((path2) => path2 && matches(path2.toLowerCase())).sort().slice(0, 1e3);
13554
+ }
13132
13555
  case "search.query": {
13133
13556
  const query = requireString(params, "query", 8192);
13134
13557
  if (!query) return [];
@@ -13394,6 +13817,7 @@ async function createRuntime(options2) {
13394
13817
  clearTimeout(authDeadline);
13395
13818
  agents.detach(connection.id);
13396
13819
  lsp.detach(connection.id);
13820
+ pushDevices.disconnect(connection.id);
13397
13821
  connections.delete(connection.id);
13398
13822
  updateIdle();
13399
13823
  for (const pending of connection.edits.values()) {
@@ -13544,6 +13968,7 @@ async function createRuntime(options2) {
13544
13968
  await Promise.allSettled([...inflight.values()]);
13545
13969
  await collaboration.close();
13546
13970
  await persistence;
13971
+ await pushDevices.flush();
13547
13972
  await new Promise((resolve) => websocket.close(() => resolve()));
13548
13973
  await new Promise((resolve) => server.close(() => resolve()));
13549
13974
  }
@@ -13741,7 +14166,7 @@ function lanAddresses() {
13741
14166
  return { v4: of("IPv4"), v6: of("IPv6") };
13742
14167
  }
13743
14168
  const stderrLog = (line) => process.stderr.write(line + "\n");
13744
- function advertise(ad, log = stderrLog) {
14169
+ function advertise(ad, log2 = stderrLog) {
13745
14170
  const instance = instanceLabel(ad.name, ad.id);
13746
14171
  const current = (ttl) => records({ ...ad, ...(ad.addresses ?? lanAddresses)() }, ttl);
13747
14172
  const timers = [];
@@ -13760,11 +14185,11 @@ function advertise(ad, log = stderrLog) {
13760
14185
  };
13761
14186
  const fail = (error) => {
13762
14187
  if (closed) return;
13763
- log(`mDNS advertising disabled: ${error.message}`);
14188
+ log2(`mDNS advertising disabled: ${error.message}`);
13764
14189
  stop2();
13765
14190
  };
13766
14191
  const sendFailure = (error) => {
13767
- if (!sendFailed) log(`mDNS send failed: ${error.message}`);
14192
+ if (!sendFailed) log2(`mDNS send failed: ${error.message}`);
13768
14193
  sendFailed = true;
13769
14194
  };
13770
14195
  const send = (packet, port2 = PORT, address = GROUP, done) => {
@@ -14083,7 +14508,7 @@ Runtime id: ${daemon.id}
14083
14508
  }
14084
14509
  async function detach(invocation, target, dataDir, entry2) {
14085
14510
  await fs.mkdir(dataDir, { recursive: true, mode: 448 });
14086
- const log = await fs.open(logFile(dataDir), "a", 384);
14511
+ const log2 = await fs.open(logFile(dataDir), "a", 384);
14087
14512
  const args = [entry2, "--foreground", "--no-open", ...invocation.lan ? ["--lan"] : ["--host", invocation.host]];
14088
14513
  if (invocation.port) args.push("--port", String(invocation.port));
14089
14514
  args.push("--keep-alive", invocation.keepAlive ? String(invocation.keepAlive) : "forever");
@@ -14095,7 +14520,7 @@ async function detach(invocation, target, dataDir, entry2) {
14095
14520
  delete env2.OXBIT_KEEP_ALIVE;
14096
14521
  const child = spawn(process.execPath, args, {
14097
14522
  detached: true,
14098
- stdio: ["ignore", log.fd, log.fd],
14523
+ stdio: ["ignore", log2.fd, log2.fd],
14099
14524
  env: env2
14100
14525
  });
14101
14526
  let exit;
@@ -14103,7 +14528,7 @@ async function detach(invocation, target, dataDir, entry2) {
14103
14528
  exit = code ?? 1;
14104
14529
  });
14105
14530
  child.unref();
14106
- await log.close();
14531
+ await log2.close();
14107
14532
  const deadline = Date.now() + READY_TIMEOUT;
14108
14533
  while (Date.now() < deadline) {
14109
14534
  const daemon = await liveDaemon(dataDir);