@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.
- package/package.json +1 -1
- package/runtime/dist/index.js +1089 -495
- package/runtime/package.json +1 -1
- package/web/dist/assets/index-DoZNi2PS.css +1 -0
- package/web/dist/assets/{index-a2bql7tE.js → index-O78N-E-Z.js} +138 -134
- package/web/dist/assets/{typescript-DnCbM4Ma.js → typescript-BDEBNDpD.js} +2 -2
- package/web/dist/index.html +2 -2
- package/web/dist/schemas/settings.v1.schema.json +94 -4
- package/web/dist/sdk/index.js +137 -7
- package/web/dist/assets/index-CCGtng18.css +0 -1
package/runtime/dist/index.js
CHANGED
|
@@ -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,
|
|
39
|
-
return
|
|
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,
|
|
58
|
-
super(
|
|
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
|
|
279
|
-
if (
|
|
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,
|
|
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 (
|
|
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:
|
|
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(
|
|
590
|
-
let 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
|
|
1045
|
-
if (
|
|
1046
|
-
base = decodeURIComponent(
|
|
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(
|
|
1171
|
-
throw new RpcError("INVALID_TASK_CONFIG",
|
|
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,
|
|
1409
|
+
onError: (level, message2) => {
|
|
1375
1410
|
if (level !== "warning")
|
|
1376
|
-
invalid(`Invalid run configuration XML: ${
|
|
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
|
|
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 =
|
|
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
|
|
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 = { ...
|
|
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
|
|
1610
|
-
const item = { ...
|
|
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,
|
|
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(
|
|
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"],
|
|
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
|
|
2012
|
-
if (
|
|
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,
|
|
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 = {},
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
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
|
|
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
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
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
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2543
|
+
env2[key] = expand(value, env2);
|
|
2509
2544
|
resolving.delete(key);
|
|
2510
2545
|
resolved.add(key);
|
|
2511
|
-
return
|
|
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
|
|
2523
|
-
if (!
|
|
2524
|
-
if (["0.0.0.0", "[::]"].includes(
|
|
2525
|
-
|
|
2526
|
-
found.add(
|
|
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
|
|
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(
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
return
|
|
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,
|
|
2588
|
-
if (run2.value.state === state2 && run2.value.message ===
|
|
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 =
|
|
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,
|
|
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
|
|
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 ?? ".",
|
|
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,
|
|
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,
|
|
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
|
|
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(
|
|
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 &&
|
|
2972
|
-
|
|
2973
|
-
const response = await fetch(
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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 = !
|
|
3422
|
+
let removed = !exists2;
|
|
3383
3423
|
try {
|
|
3384
|
-
if (
|
|
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
|
|
3685
|
+
const binary2 = diff.includes("Binary files ") || before.stdout.includes("\0") || after.includes("\0");
|
|
3646
3686
|
const pieces = diff.split(/(?=^@@ )/m);
|
|
3647
|
-
const canHunk = !
|
|
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:
|
|
3650
|
-
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
|
|
3818
|
+
const binary2 = diff.stdout.includes("Binary files ") || before?.stdout.includes("\0") || after.stdout.includes("\0");
|
|
3779
3819
|
return bounded({
|
|
3780
|
-
before:
|
|
3781
|
-
after:
|
|
3820
|
+
before: binary2 || before?.exitCode ? "" : before?.stdout ?? "",
|
|
3821
|
+
after: binary2 || after.exitCode ? "" : after.stdout,
|
|
3782
3822
|
diff: diff.stdout,
|
|
3783
|
-
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
|
|
4323
|
-
if (
|
|
4324
|
-
const file = await fs.realpath(fileURLToPath(
|
|
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
|
|
4335
|
-
if (!
|
|
4336
|
-
const descriptor = await fs.open(
|
|
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(
|
|
4342
|
-
if (stat.dev !== current.dev || stat.ino !== current.ino || bytes.length > 20 * 1024 * 1024 || await fs.realpath(
|
|
4343
|
-
return { handle, uri:
|
|
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(
|
|
4535
|
-
if (
|
|
4536
|
-
const pending = this.incoming.get(
|
|
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(
|
|
4539
|
-
if (
|
|
4540
|
-
else pending.resolve(
|
|
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(
|
|
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
|
|
4739
|
+
let message2 = data.toString().slice(0, 16e3);
|
|
4700
4740
|
const key = this.launchSpec?.initializationOptions?.licenceKey;
|
|
4701
|
-
if (typeof key === "string" && key)
|
|
4702
|
-
this.output.push(
|
|
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(
|
|
4766
|
-
if (setting("LSP_TRACE")) this.emit("window/logMessage", { type: 4, message: `${this.serverName}: ${
|
|
4767
|
-
if (
|
|
4768
|
-
if (
|
|
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(
|
|
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:
|
|
4813
|
+
if (this.rpc === connection) this.send({ id: message2.id, result });
|
|
4774
4814
|
},
|
|
4775
4815
|
(error) => {
|
|
4776
|
-
if (this.rpc === connection) this.send({ id:
|
|
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(
|
|
4782
|
-
this.emit("oxbit/refresh", { method:
|
|
4783
|
-
this.send({ id:
|
|
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 (
|
|
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:
|
|
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:
|
|
4832
|
+
this.emit("oxbit/serverRequest", { id: message2.id, method: message2.method, params: message2.params });
|
|
4793
4833
|
return;
|
|
4794
4834
|
}
|
|
4795
|
-
if (
|
|
4835
|
+
if (message2.method === "workspace/applyEdit") {
|
|
4796
4836
|
const child = this.child;
|
|
4797
4837
|
void (async () => {
|
|
4798
|
-
await this.validate(
|
|
4799
|
-
return this.applyEdit ? await this.applyEdit(
|
|
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:
|
|
4841
|
+
if (this.child === child) this.send({ jsonrpc: "2.0", id: message2.id, result });
|
|
4802
4842
|
});
|
|
4803
4843
|
return;
|
|
4804
4844
|
}
|
|
4805
|
-
if (
|
|
4806
|
-
const adding =
|
|
4807
|
-
const registrations = adding ?
|
|
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:
|
|
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:
|
|
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 (
|
|
4830
|
-
this.send({ jsonrpc: "2.0", id:
|
|
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 (
|
|
4873
|
+
if (message2.method === "workspace/configuration")
|
|
4834
4874
|
this.send({
|
|
4835
4875
|
jsonrpc: "2.0",
|
|
4836
|
-
id:
|
|
4837
|
-
result: (
|
|
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 (
|
|
4853
|
-
this.send({ jsonrpc: "2.0", id:
|
|
4854
|
-
else this.send({ jsonrpc: "2.0", id:
|
|
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 (
|
|
4897
|
+
if (message2.method === "tsserver/request" && this.companion) {
|
|
4858
4898
|
const child = this.child;
|
|
4859
|
-
for (const [id, command, payload] of Array.isArray(
|
|
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(
|
|
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 (
|
|
4868
|
-
if (
|
|
4869
|
-
this.primaryDiagnostics.set(
|
|
4870
|
-
this.emit(
|
|
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 (
|
|
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
|
|
5105
|
-
if (
|
|
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
|
|
5226
|
-
return { executable:
|
|
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
|
|
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:
|
|
5277
|
+
executable: binary2.executable,
|
|
5238
5278
|
args: id === "marksman" ? ["server"] : ["lsp", "stdio"],
|
|
5239
|
-
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
|
|
5257
|
-
if (!
|
|
5258
|
-
const [name, bin, dependencies = []] =
|
|
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
|
|
5326
|
-
if (!
|
|
5327
|
-
selected.set(location,
|
|
5328
|
-
for (const name of Object.keys({ ...
|
|
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 (!
|
|
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(
|
|
5360
|
-
if (!
|
|
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(
|
|
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
|
|
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(
|
|
5402
|
-
else if (
|
|
5403
|
-
const target = path.resolve(directory, path.dirname(normalized),
|
|
5404
|
-
if (path.isAbsolute(
|
|
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(([,
|
|
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(
|
|
5583
|
+
async cached(url2) {
|
|
5544
5584
|
try {
|
|
5545
|
-
const file = path.join(this.cache, digest(
|
|
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 ===
|
|
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(
|
|
5553
|
-
let current =
|
|
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
|
|
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(
|
|
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(
|
|
5634
|
+
let pending = this.pending.get(url2);
|
|
5595
5635
|
if (!pending) {
|
|
5596
5636
|
pending = (async () => {
|
|
5597
5637
|
try {
|
|
5598
|
-
const text2 = await this.fetchJson(
|
|
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(
|
|
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(
|
|
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(
|
|
5653
|
+
this.pending.delete(url2);
|
|
5614
5654
|
}
|
|
5615
5655
|
})();
|
|
5616
|
-
this.pending.set(
|
|
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
|
|
5623
|
-
|
|
5624
|
-
if (
|
|
5625
|
-
if (
|
|
5626
|
-
|
|
5627
|
-
const file = await this.files.resolve(path.relative(this.files.root, fileURLToPath(
|
|
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
|
|
6096
|
-
const relative = path.posix.join(directory,
|
|
6097
|
-
if (
|
|
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) + (
|
|
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 (
|
|
6106
|
-
if (!excludedDirectories.has(
|
|
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 (!
|
|
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 (
|
|
6177
|
+
if (entry2.name === "package.json" || entry2.name === "composer.json") {
|
|
6138
6178
|
try {
|
|
6139
|
-
const data = JSON.parse(text2), ecosystem =
|
|
6140
|
-
const
|
|
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")
|
|
6142
|
-
result.packages.push(
|
|
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((
|
|
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
|
|
6210
|
-
if (typeof
|
|
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((
|
|
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
|
|
6773
|
-
if (!
|
|
6774
|
-
const room = await
|
|
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,
|
|
6863
|
+
for (const [key, entry2] of this.rooms) {
|
|
6824
6864
|
if (relative && relative !== key) continue;
|
|
6825
|
-
const room = await
|
|
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) ===
|
|
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
|
|
6844
|
-
const room = await
|
|
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
|
|
7236
|
-
if (!
|
|
7237
|
-
|
|
7238
|
-
record.activity.push(
|
|
7239
|
-
}
|
|
7240
|
-
|
|
7241
|
-
if (!["completed", "failed"].includes(
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
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
|
-
})) :
|
|
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
|
|
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 (!
|
|
7273
|
-
|
|
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(
|
|
7319
|
+
record.activity.push(entry2);
|
|
7280
7320
|
}
|
|
7281
|
-
if (
|
|
7282
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
7327
|
-
Object.assign(
|
|
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.
|
|
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((
|
|
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,
|
|
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
|
|
7616
|
+
let message2;
|
|
7577
7617
|
try {
|
|
7578
|
-
|
|
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(
|
|
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(
|
|
7585
|
-
if (
|
|
7586
|
-
const id =
|
|
7587
|
-
if (
|
|
7588
|
-
const requested = object(
|
|
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 (
|
|
7592
|
-
if (
|
|
7593
|
-
if (
|
|
7594
|
-
const 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,
|
|
7646
|
-
const data = JSON.stringify({ jsonrpc: "2.0", ...
|
|
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
|
|
7676
|
-
agent.events.push(
|
|
7677
|
-
agent.eventBytes += Buffer.byteLength(JSON.stringify(
|
|
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
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
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:
|
|
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
|
|
7833
|
-
if (
|
|
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,
|
|
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
|
-
...
|
|
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
|
-
|
|
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", {
|
|
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
|
-
|
|
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((
|
|
8355
|
-
const content = requireString(
|
|
8356
|
-
if (!["pending", "in_progress", "completed"].includes(
|
|
8357
|
-
return { content, status:
|
|
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,
|
|
8782
|
+
async message(agent, message2) {
|
|
8374
8783
|
if (agent.stopped) return;
|
|
8375
|
-
if (!
|
|
8376
|
-
const pending = agent.pending.get(
|
|
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(
|
|
8380
|
-
if (
|
|
8788
|
+
agent.pending.delete(message2.id);
|
|
8789
|
+
if (message2.error)
|
|
8381
8790
|
pending.reject(
|
|
8382
8791
|
new RpcError(
|
|
8383
|
-
String(
|
|
8384
|
-
|
|
8385
|
-
|
|
8792
|
+
String(message2.error.code),
|
|
8793
|
+
message2.error.message ?? "ACP request failed",
|
|
8794
|
+
message2.error.data
|
|
8386
8795
|
)
|
|
8387
8796
|
);
|
|
8388
|
-
else pending.resolve(
|
|
8797
|
+
else pending.resolve(message2.result);
|
|
8389
8798
|
return;
|
|
8390
8799
|
}
|
|
8391
|
-
const params =
|
|
8392
|
-
if (
|
|
8393
|
-
if (
|
|
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 (
|
|
8808
|
+
if (message2.method === "session/update")
|
|
8400
8809
|
this.routeUpdate(agent, params.sessionId, params.update);
|
|
8401
|
-
else if (
|
|
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:
|
|
8814
|
+
{ ...params, sessionUpdate: message2.method }
|
|
8406
8815
|
);
|
|
8407
8816
|
return;
|
|
8408
8817
|
}
|
|
8409
8818
|
try {
|
|
8410
8819
|
const registry = agent.subagents;
|
|
8411
|
-
const cursorRequest =
|
|
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 (
|
|
8417
|
-
const result2 = await this.terminal(agent,
|
|
8418
|
-
this.send(agent, { id:
|
|
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(
|
|
8836
|
+
].includes(message2.method)) {
|
|
8428
8837
|
this.send(agent, {
|
|
8429
|
-
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,
|
|
8435
|
-
if (!agent.stopped) this.send(agent, { id:
|
|
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:
|
|
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
|
|
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
|
|
8480
|
-
const name = requireString(
|
|
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
|
-
|
|
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 = (
|
|
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 = (
|
|
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(
|
|
11464
|
-
const parsed = new URL(
|
|
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((
|
|
12142
|
+
const addresses = Object.values(interfaces).flatMap((entries) => entries ?? []).filter((entry2) => !entry2.internal);
|
|
11616
12143
|
const of = (family) => [
|
|
11617
|
-
...new Set(addresses.filter((
|
|
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,
|
|
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(
|
|
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
|
|
11943
|
-
if (
|
|
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 (
|
|
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",
|
|
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() >
|
|
11957
|
-
|
|
11958
|
-
|
|
12537
|
+
if (Date.now() > entry2.reset) {
|
|
12538
|
+
entry2.count = 0;
|
|
12539
|
+
entry2.reset = Date.now() + 6e4;
|
|
11959
12540
|
}
|
|
11960
|
-
if (++
|
|
12541
|
+
if (++entry2.count > pairingAttemptsPerMinute) {
|
|
11961
12542
|
httpJson(response, 429, { error: "Pairing rate limit reached" });
|
|
11962
12543
|
return;
|
|
11963
12544
|
}
|
|
11964
|
-
attempts.set(address2,
|
|
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 (
|
|
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 (
|
|
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(
|
|
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(
|
|
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
|
|
13266
|
+
let message2;
|
|
12673
13267
|
try {
|
|
12674
|
-
|
|
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 (
|
|
13274
|
+
if (message2.type === "cancel") {
|
|
12681
13275
|
authorized(connection);
|
|
12682
|
-
connection.pending.get(
|
|
13276
|
+
connection.pending.get(message2.id)?.abort();
|
|
12683
13277
|
return;
|
|
12684
13278
|
}
|
|
12685
|
-
if (
|
|
13279
|
+
if (message2.type === "ack") {
|
|
12686
13280
|
const session = authorized(connection, "terminal");
|
|
12687
|
-
if (!
|
|
13281
|
+
if (!message2.stream.startsWith("terminal:"))
|
|
12688
13282
|
throw new RpcError("INVALID_PARAMS", "Unknown stream");
|
|
12689
13283
|
processes.ack(
|
|
12690
|
-
|
|
13284
|
+
message2.stream.slice(9),
|
|
12691
13285
|
session.id,
|
|
12692
13286
|
connection.id,
|
|
12693
|
-
|
|
13287
|
+
message2.seq
|
|
12694
13288
|
);
|
|
12695
13289
|
return;
|
|
12696
13290
|
}
|
|
12697
|
-
const { id, method, params } =
|
|
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,
|
|
13244
|
-
const directory = setting("DATA_DIR",
|
|
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(
|
|
13256
|
-
if (platform === "darwin") return { command: "open", args: [
|
|
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", "",
|
|
13259
|
-
return { command: "xdg-open", args: [
|
|
13852
|
+
return { command: "cmd", args: ["/c", "start", "", url2] };
|
|
13853
|
+
return { command: "xdg-open", args: [url2] };
|
|
13260
13854
|
}
|
|
13261
|
-
function launchBrowser(
|
|
13262
|
-
const { command, args } = browserCommand(
|
|
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,
|
|
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) ??
|
|
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",
|
|
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",
|
|
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) ??
|
|
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,
|
|
14014
|
+
async function serve(invocation, target, dataDir, env2) {
|
|
13421
14015
|
try {
|
|
13422
|
-
return await serveRuntime(invocation, target, dataDir,
|
|
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,
|
|
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",
|
|
13439
|
-
pairingAttemptsPerMinute: setting("PAIRING_ATTEMPTS_PER_MINUTE",
|
|
13440
|
-
origins: setting("ORIGINS",
|
|
13441
|
-
webRoot: setting("WEB_ROOT",
|
|
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
|
|
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: ${
|
|
14059
|
+
`) + `Open: ${url2}
|
|
13466
14060
|
`
|
|
13467
14061
|
);
|
|
13468
|
-
if (invocation.open) launchBrowser(
|
|
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,
|
|
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 = [
|
|
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
|
|
13498
|
-
delete
|
|
13499
|
-
delete
|
|
13500
|
-
delete
|
|
13501
|
-
delete
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
13618
|
-
process.stdout.write(invocation.lan ? `${networkSummary(daemon)}Open: ${
|
|
14211
|
+
const url2 = launchUrl(daemon, target.file);
|
|
14212
|
+
process.stdout.write(invocation.lan ? `${networkSummary(daemon)}Open: ${url2}
|
|
13619
14213
|
` : `${daemon.root}
|
|
13620
|
-
${
|
|
14214
|
+
${url2}
|
|
13621
14215
|
`);
|
|
13622
|
-
if (invocation.open) launchBrowser(
|
|
14216
|
+
if (invocation.open) launchBrowser(url2);
|
|
13623
14217
|
return 0;
|
|
13624
14218
|
}
|
|
13625
14219
|
function desktopCommand(target, platform = process.platform) {
|