@oxbit/cli 0.7.1 → 0.9.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 +1524 -517
- package/runtime/package.json +1 -1
- package/web/dist/assets/index-BG8qkqOX.css +1 -0
- package/web/dist/assets/index-MUHvcYc7.js +291 -0
- package/web/dist/assets/{typescript-DnCbM4Ma.js → typescript-Du4_Siji.js} +2 -2
- package/web/dist/index.html +2 -2
- package/web/dist/schemas/settings.v1.schema.json +126 -4
- package/web/dist/sdk/index.js +189 -7
- package/web/dist/assets/index-CCGtng18.css +0 -1
- package/web/dist/assets/index-a2bql7tE.js +0 -255
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":"[]"},"agentACP.oxbit.api":{"title":"Oxbit Agent API","description":"openai uses the Responses API, anthropic the Messages API, and openai-compatible Chat Completions at the base URL.","type":"string","default":"openai","enum":["openai","anthropic","openai-compatible"]},"agentACP.oxbit.baseUrl":{"title":"Oxbit Agent base URL","description":"Origin and path prefix for openai-compatible, for example https://openrouter.ai/api.","type":"string","default":""},"agentACP.oxbit.model":{"title":"Oxbit Agent model","description":"Model ID for new conversations.","type":"string","default":""},"workbench.keymap":{"title":"Keymap","description":"Keyboard layout applied on top of the Oxbit defaults. User keybindings always win.","type":"string","default":"default","enum":["default","vscode","jetbrains","macos","sublime","atom","visual-studio","emacs"]},"runtime.keepAlive":{"title":"Keep Runtime Alive","description":"How long a runtime Oxbit started keeps running after the last client disconnects or its connection heartbeats stop. Terminals and tasks continue while it runs.","type":"string","default":"75s","enum":["75s","15m","1h","8h","untilStopped"]},"runtime.autoReconnect":{"title":"Reconnect Automatically","description":"Reconnect to the runtime after the connection drops, including when the iOS app returns to the foreground.","type":"boolean","default":true},"workbench.tooltipDelay":{"title":"Tooltip Delay","description":"Delay in milliseconds before showing a tooltip on hover. Keyboard focus shows tooltips immediately.","type":"number","default":400,"minimum":0,"maximum":5000},"workbench.colorTheme":{"title":"Color Theme","description":"Specifies the color theme used in the workbench.","type":"string","default":"Graphite (dark)"},"workbench.density":{"title":"Layout Density","description":"Controls row heights, padding and label sizes across the workbench.","type":"string","default":"compact","enum":["compact","comfortable"]},"workbench.sidebarLocation":{"title":"Sidebar Location","description":"Where the primary sidebar is docked.","type":"string","default":"left","enum":["left","right"]},"editor.fontFamily":{"title":"Font Family","description":"Controls the font family of the editor.","type":"string","default":"'JetBrains Mono', ui-monospace, monospace"},"editor.fontSize":{"title":"Font Size","description":"Font size in pixels.","type":"number","default":13,"minimum":8,"maximum":32},"editor.lineHeight":{"title":"Line Height","description":"Line height in pixels. 0 uses 1.55 × font size.","type":"number","default":20,"minimum":0,"maximum":60},"editor.fontLigatures":{"title":"Font Ligatures","description":"Enables font ligatures when the font supports them.","type":"boolean","default":false},"editor.tabSize":{"title":"Tab Size","description":"The number of spaces a tab is equal to.","type":"number","default":2,"minimum":1,"maximum":8},"editor.insertSpaces":{"title":"Insert Spaces","description":"Insert spaces when pressing Tab.","type":"boolean","default":true},"editor.detectIndentation":{"title":"Detect Indentation","description":"Detect Tab Size and Insert Spaces from file contents on open.","type":"boolean","default":true},"editor.renderIndentGuides":{"title":"Indentation Guides","description":"Render vertical guides for each indent level.","type":"boolean","default":true},"editor.wordWrap":{"title":"Word Wrap","description":"Controls how lines should wrap.","type":"string","default":"off","enum":["off","on","bounded"]},"editor.renderWhitespace":{"title":"Render Whitespace","description":"Controls how the editor renders whitespace characters.","type":"string","default":"selection","enum":["none","boundary","selection","all"]},"editor.cursorBlinking":{"title":"Cursor Blinking","description":"Cursor animation style.","type":"string","default":"blink","enum":["blink","smooth","phase","solid"]},"editor.minimap":{"title":"Minimap","description":"Show a minimap of the document.","type":"boolean","default":false},"files.autoSave":{"title":"Auto Save","description":"Controls auto save of editors that have unsaved changes.","type":"string","default":"off","enum":["off","afterDelay","onFocusChange","onWindowChange"]},"files.autoSaveDelay":{"title":"Auto Save Delay","description":"Delay in milliseconds after which an editor is saved automatically. Applies when Auto Save is afterDelay.","type":"number","default":1000,"minimum":100,"maximum":60000},"files.trimTrailingWhitespace":{"title":"Trim Trailing Whitespace","description":"Remove trailing whitespace when saving.","type":"boolean","default":true},"editor.formatOnSave":{"title":"Format On Save","description":"Format a file on save. A formatter must be available.","type":"boolean","default":false},"editor.defaultFormatter":{"title":"Default Formatter","description":"Formatter used when several are available.","type":"string","default":"oxbit.prettier","anyOf":[{"enum":["oxbit.prettier","oxbit.builtin-ts"]},{"type":"string"}]},"terminal.fontSize":{"title":"Terminal Font Size","description":"Font size in pixels for the terminal.","type":"number","default":12,"minimum":8,"maximum":32},"terminal.scrollback":{"title":"Scrollback","description":"Maximum number of lines kept in the terminal buffer.","type":"number","default":5000,"minimum":100,"maximum":100000},"terminal.confirmOnKill":{"title":"Confirm On Kill","description":"Ask before killing a terminal with a running process.","type":"boolean","default":true},"scm.autoFetch":{"title":"Auto Fetch","description":"Periodically fetch from the default remote.","type":"boolean","default":true},"scm.diffLayout":{"title":"Diff Layout","description":"Default layout for diff editors.","type":"string","default":"side-by-side","enum":["side-by-side","inline"]},"files.associations":{"title":"File Associations","description":"Map filename or workspace-relative glob patterns to language IDs. Explicit associations take precedence over built-in detection.","type":"object","default":{},"propertyNames":{"minLength":1,"maxLength":1024},"additionalProperties":{"type":"string","pattern":"^[\\\\w+-]+$"}},"languageServers":{"title":"Language Servers","description":"Configure enabled state, selectors, rootMarkers, executable, args, env, initializationOptions, settings, and priority by server ID. Servers install on first use in a trusted runtime. License keys belong in the runtime-local credential file.","type":"object","default":{},"propertyNames":{"pattern":"^[a-zA-Z0-9._-]+$"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable this language server."},"selectors":{"type":"array","items":{"type":"object","properties":{"language":{"type":"string"},"pattern":{"type":"string"},"scheme":{"const":"file"}},"additionalProperties":false,"minProperties":1}},"rootMarkers":{"type":"array","items":{"type":"string","pattern":"^(?!/)(?!.*\\\\\\\\)(?!.*(?:^|/)\\\\.\\\\.(?:/|$))[^\\\\u0000]+$","minLength":1}},"executable":{"type":"string","pattern":"^[^\\\\u0000]*$","minLength":1},"args":{"type":"array","items":{"type":"string","pattern":"^[^\\\\u0000]*$"}},"env":{"type":"object","propertyNames":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"additionalProperties":{"type":"string","pattern":"^[^\\\\u0000]*$"}},"initializationOptions":{"type":"object","additionalProperties":true,"description":"Server-specific initialization options. Store license credentials in the runtime credential file."},"settings":{"type":"object","additionalProperties":true,"description":"Server-specific configuration; nested objects merge across files."},"priority":{"type":"number"}},"additionalProperties":false}},"ui.fontFamily":{"title":"UI Font Family","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":""},"ui.fontSize":{"title":"UI Font Size","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":12,"minimum":8,"maximum":72},"ui.fontWeight":{"title":"UI Font Weight","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":400,"minimum":100,"maximum":900},"ui.fontStyle":{"title":"UI Font Style","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":"normal","enum":["normal","italic","oblique"]},"ui.lineHeight":{"title":"UI Line Height","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":1.4,"minimum":1,"maximum":3},"ui.letterSpacing":{"title":"UI Letter Spacing","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":0,"minimum":-3,"maximum":10},"ui.fontLigatures":{"title":"UI Font Ligatures","description":"Overrides theme typography. Reset restores the theme value.","type":"boolean","default":false},"terminal.fontFamily":{"title":"Terminal Font Family","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":""},"terminal.fontWeight":{"title":"Terminal Font Weight","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":400,"minimum":100,"maximum":900},"terminal.fontStyle":{"title":"Terminal Font Style","description":"Overrides theme typography. Reset restores the theme value.","type":"string","default":"normal","enum":["normal","italic","oblique"]},"terminal.lineHeight":{"title":"Terminal Line Height","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":1,"minimum":1,"maximum":3},"terminal.letterSpacing":{"title":"Terminal Letter Spacing","description":"Overrides theme typography. Reset restores the theme value.","type":"number","default":0,"minimum":-3,"maximum":10},"terminal.fontLigatures":{"title":"Terminal Font Ligatures","description":"Overrides theme typography. Reset restores the theme value.","type":"boolean","default":false},"workbench.iconTheme":{"title":"File Icon Theme","description":"Select a locally installed icon theme. Unavailable selections use Oxbit defaults until restored.","type":"string","default":"oxbit.default"},"workbench.productIconTheme":{"title":"Product Icon Theme","description":"Select a locally installed icon theme. Unavailable selections use Oxbit defaults until restored.","type":"string","default":"oxbit.default"},"editor.semanticHighlighting":{"title":"Semantic Highlighting","description":"Color symbols using language server information.","type":"boolean","default":true},"editor.inlayHints.types":{"title":"Type Inlay Hints","description":"Show supported type hints in the visible editor.","type":"boolean","default":true},"editor.inlayHints.parameters":{"title":"Parameter Inlay Hints","description":"Show supported parameter hints in the visible editor.","type":"boolean","default":true},"editor.largeFileIntelligence":{"title":"Large File Intelligence","description":"Allow full semantic highlighting and inlay hints for files larger than 1 MiB.","type":"boolean","default":false},"workbench.locale":{"title":"Display Language","type":"string","default":"en","enum":["en","de","es","ja","zh"]},"workbench.reducedMotion":{"title":"Reduced Motion","type":"boolean","default":false},"desktop.projects.openBehavior":{"title":"Open projects","type":"string","default":"currentWindow","enum":["currentWindow","newWindow"]},"desktop.tools.gitPath":{"title":"git executable path","type":"string","default":""},"desktop.tools.ghPath":{"title":"gh executable path","type":"string","default":""},"project.intelligence":{"type":"object","properties":{"enabled":{"type":"boolean","default":true,"description":"Index project dependencies, imports and related files."},"exclude":{"type":"array","items":{"type":"string","maxLength":1024},"default":[]},"maxFiles":{"type":"integer","minimum":1,"maximum":100000,"default":20000},"maxFileBytes":{"type":"integer","minimum":1,"maximum":5242880,"default":1048576}},"additionalProperties":false,"title":"Project Intelligence","description":"Partial overrides of project.json intelligence. Objects merge across files."},"project.schemas":{"type":"object","properties":{"catalog":{"type":"boolean","default":true,"description":"Discover file schemas through SchemaStore. Bundled Oxbit settings support remains available."},"download":{"type":"boolean","default":true,"description":"Allow remote schema downloads; cached and bundled schemas work offline."},"associations":{"type":"array","maxItems":1000,"default":[],"items":{"type":"object","properties":{"url":{"type":"string","minLength":1,"description":"Schema URL or workspace file URI."},"fileMatch":{"type":"array","items":{"type":"string","maxLength":1024}},"schema":{"type":["object","boolean"],"description":"An inline JSON Schema."}},"additionalProperties":false,"anyOf":[{"required":["url"]},{"required":["schema"]}]}}},"additionalProperties":false,"title":"JSON Schemas","description":"Partial overrides of project.json schemas. Association arrays replace lower arrays."}}`);
|
|
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" }, "agentACP.oxbit.api": { "$ref": "#/properties/agentACP.oxbit.api" }, "agentACP.oxbit.baseUrl": { "$ref": "#/properties/agentACP.oxbit.baseUrl" }, "agentACP.oxbit.model": { "$ref": "#/properties/agentACP.oxbit.model" }, "workbench.keymap": { "$ref": "#/properties/workbench.keymap" }, "runtime.keepAlive": { "$ref": "#/properties/runtime.keepAlive" }, "runtime.autoReconnect": { "$ref": "#/properties/runtime.autoReconnect" }, "workbench.tooltipDelay": { "$ref": "#/properties/workbench.tooltipDelay" }, "workbench.colorTheme": { "$ref": "#/properties/workbench.colorTheme" }, "workbench.density": { "$ref": "#/properties/workbench.density" }, "workbench.sidebarLocation": { "$ref": "#/properties/workbench.sidebarLocation" }, "editor.fontFamily": { "$ref": "#/properties/editor.fontFamily" }, "editor.fontSize": { "$ref": "#/properties/editor.fontSize" }, "editor.lineHeight": { "$ref": "#/properties/editor.lineHeight" }, "editor.fontLigatures": { "$ref": "#/properties/editor.fontLigatures" }, "editor.tabSize": { "$ref": "#/properties/editor.tabSize" }, "editor.insertSpaces": { "$ref": "#/properties/editor.insertSpaces" }, "editor.detectIndentation": { "$ref": "#/properties/editor.detectIndentation" }, "editor.renderIndentGuides": { "$ref": "#/properties/editor.renderIndentGuides" }, "editor.wordWrap": { "$ref": "#/properties/editor.wordWrap" }, "editor.renderWhitespace": { "$ref": "#/properties/editor.renderWhitespace" }, "editor.cursorBlinking": { "$ref": "#/properties/editor.cursorBlinking" }, "editor.minimap": { "$ref": "#/properties/editor.minimap" }, "files.autoSave": { "$ref": "#/properties/files.autoSave" }, "files.autoSaveDelay": { "$ref": "#/properties/files.autoSaveDelay" }, "files.trimTrailingWhitespace": { "$ref": "#/properties/files.trimTrailingWhitespace" }, "editor.formatOnSave": { "$ref": "#/properties/editor.formatOnSave" }, "editor.defaultFormatter": { "$ref": "#/properties/editor.defaultFormatter" }, "terminal.fontSize": { "$ref": "#/properties/terminal.fontSize" }, "terminal.scrollback": { "$ref": "#/properties/terminal.scrollback" }, "terminal.confirmOnKill": { "$ref": "#/properties/terminal.confirmOnKill" }, "scm.autoFetch": { "$ref": "#/properties/scm.autoFetch" }, "scm.diffLayout": { "$ref": "#/properties/scm.diffLayout" }, "files.associations": { "$ref": "#/properties/files.associations" }, "languageServers": { "$ref": "#/properties/languageServers" }, "ui.fontFamily": { "$ref": "#/properties/ui.fontFamily" }, "ui.fontSize": { "$ref": "#/properties/ui.fontSize" }, "ui.fontWeight": { "$ref": "#/properties/ui.fontWeight" }, "ui.fontStyle": { "$ref": "#/properties/ui.fontStyle" }, "ui.lineHeight": { "$ref": "#/properties/ui.lineHeight" }, "ui.letterSpacing": { "$ref": "#/properties/ui.letterSpacing" }, "ui.fontLigatures": { "$ref": "#/properties/ui.fontLigatures" }, "terminal.fontFamily": { "$ref": "#/properties/terminal.fontFamily" }, "terminal.fontWeight": { "$ref": "#/properties/terminal.fontWeight" }, "terminal.fontStyle": { "$ref": "#/properties/terminal.fontStyle" }, "terminal.lineHeight": { "$ref": "#/properties/terminal.lineHeight" }, "terminal.letterSpacing": { "$ref": "#/properties/terminal.letterSpacing" }, "terminal.fontLigatures": { "$ref": "#/properties/terminal.fontLigatures" }, "workbench.iconTheme": { "$ref": "#/properties/workbench.iconTheme" }, "workbench.productIconTheme": { "$ref": "#/properties/workbench.productIconTheme" }, "editor.semanticHighlighting": { "$ref": "#/properties/editor.semanticHighlighting" }, "editor.inlayHints.types": { "$ref": "#/properties/editor.inlayHints.types" }, "editor.inlayHints.parameters": { "$ref": "#/properties/editor.inlayHints.parameters" }, "editor.largeFileIntelligence": { "$ref": "#/properties/editor.largeFileIntelligence" }, "workbench.locale": { "$ref": "#/properties/workbench.locale" }, "workbench.reducedMotion": { "$ref": "#/properties/workbench.reducedMotion" }, "desktop.projects.openBehavior": { "$ref": "#/properties/desktop.projects.openBehavior" }, "desktop.tools.gitPath": { "$ref": "#/properties/desktop.tools.gitPath" }, "desktop.tools.ghPath": { "$ref": "#/properties/desktop.tools.ghPath" }, "project.intelligence": false, "project.schemas": false }, "additionalProperties": true, "description": "Settings for a language ID, such as [mdx] or [php]. Runtime project options belong at the top level.", "propertyNames": { "not": { "pattern": "^\\[.*\\]$" } } } };
|
|
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,15 @@ 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);
|
|
1169
|
+
const PUSH_RELAY_URL = "https://oxbit-push.ryanyannelli.workers.dev";
|
|
1134
1170
|
const taskPhases = [
|
|
1135
1171
|
"preinit",
|
|
1136
1172
|
"init",
|
|
@@ -1167,29 +1203,29 @@ function settingsFile(layers, scope) {
|
|
|
1167
1203
|
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
1204
|
}
|
|
1169
1205
|
const SDK_VERSION = "1.0.0";
|
|
1170
|
-
function invalid(
|
|
1171
|
-
throw new RpcError("INVALID_TASK_CONFIG",
|
|
1206
|
+
function invalid$1(message2) {
|
|
1207
|
+
throw new RpcError("INVALID_TASK_CONFIG", message2);
|
|
1172
1208
|
}
|
|
1173
1209
|
function object$2(value, label) {
|
|
1174
1210
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1175
|
-
invalid(`${label} must be an object`);
|
|
1211
|
+
invalid$1(`${label} must be an object`);
|
|
1176
1212
|
return value;
|
|
1177
1213
|
}
|
|
1178
1214
|
function taskName(value) {
|
|
1179
1215
|
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");
|
|
1216
|
+
invalid$1("Task name must contain 1–120 printable characters");
|
|
1181
1217
|
return value;
|
|
1182
1218
|
}
|
|
1183
1219
|
function stringMap(value, label) {
|
|
1184
1220
|
const record = object$2(value, label);
|
|
1185
1221
|
for (const [key, item] of Object.entries(record))
|
|
1186
1222
|
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}`);
|
|
1223
|
+
invalid$1(`${label}: invalid environment entry ${key}`);
|
|
1188
1224
|
return record;
|
|
1189
1225
|
}
|
|
1190
1226
|
function number(value, min, max, label) {
|
|
1191
1227
|
if (!Number.isSafeInteger(value) || Number(value) < min || Number(value) > max)
|
|
1192
|
-
invalid(`${label} must be an integer between ${min} and ${max}`);
|
|
1228
|
+
invalid$1(`${label} must be an integer between ${min} and ${max}`);
|
|
1193
1229
|
}
|
|
1194
1230
|
function port(value) {
|
|
1195
1231
|
if (value === "auto") return value;
|
|
@@ -1206,14 +1242,14 @@ function hooks(value) {
|
|
|
1206
1242
|
const data = object$2(value ?? {}, "worktree");
|
|
1207
1243
|
for (const key of Object.keys(data))
|
|
1208
1244
|
if (!taskPhases.includes(key))
|
|
1209
|
-
invalid(`Unknown worktree phase: ${key}`);
|
|
1245
|
+
invalid$1(`Unknown worktree phase: ${key}`);
|
|
1210
1246
|
for (const phase of taskPhases)
|
|
1211
1247
|
if (data[phase] !== void 0) {
|
|
1212
1248
|
const lines = Array.isArray(data[phase]) ? data[phase] : [data[phase]];
|
|
1213
1249
|
if (lines.length > 128 || lines.some(
|
|
1214
1250
|
(line) => typeof line !== "string" || line.length > 32768 || line.includes("\0")
|
|
1215
1251
|
))
|
|
1216
|
-
invalid(`worktree.${phase} must be a script or array of scripts`);
|
|
1252
|
+
invalid$1(`worktree.${phase} must be a script or array of scripts`);
|
|
1217
1253
|
}
|
|
1218
1254
|
return Object.fromEntries(
|
|
1219
1255
|
taskPhases.filter((phase) => data[phase] !== void 0).map((phase) => [phase, data[phase]])
|
|
@@ -1240,9 +1276,9 @@ function definition(value) {
|
|
|
1240
1276
|
"stop"
|
|
1241
1277
|
]);
|
|
1242
1278
|
for (const key of Object.keys(data))
|
|
1243
|
-
if (!known.has(key)) invalid(`Unknown task field: ${key}`);
|
|
1279
|
+
if (!known.has(key)) invalid$1(`Unknown task field: ${key}`);
|
|
1244
1280
|
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");
|
|
1281
|
+
invalid$1("A task needs a command or dependencies");
|
|
1246
1282
|
for (const [key, choices] of Object.entries({
|
|
1247
1283
|
type: ["command", "service"],
|
|
1248
1284
|
execution: ["shell", "process"],
|
|
@@ -1250,39 +1286,39 @@ function definition(value) {
|
|
|
1250
1286
|
group: ["build", "test"]
|
|
1251
1287
|
}))
|
|
1252
1288
|
if (data[key] !== void 0 && !choices.includes(data[key]))
|
|
1253
|
-
invalid(`Invalid ${key}`);
|
|
1289
|
+
invalid$1(`Invalid ${key}`);
|
|
1254
1290
|
for (const key of ["args", "dependsOn"])
|
|
1255
1291
|
if (data[key] !== void 0 && (!Array.isArray(data[key]) || data[key].length > 128 || data[key].some(
|
|
1256
1292
|
(v) => typeof v !== "string" || v.length > 32768 || v.includes("\0")
|
|
1257
1293
|
)))
|
|
1258
|
-
invalid(`${key} must be an array of strings`);
|
|
1294
|
+
invalid$1(`${key} must be an array of strings`);
|
|
1259
1295
|
for (const key of ["cwd", "host", "hostname"])
|
|
1260
1296
|
if (data[key] !== void 0 && (typeof data[key] !== "string" || data[key].length > 4096 || /[\x00-\x1f]/.test(data[key])))
|
|
1261
|
-
invalid(`Invalid ${key}`);
|
|
1297
|
+
invalid$1(`Invalid ${key}`);
|
|
1262
1298
|
if (data.env !== void 0) stringMap(data.env, "task.env");
|
|
1263
1299
|
if (data.port !== void 0) port(data.port);
|
|
1264
1300
|
if (data.ports !== void 0) {
|
|
1265
1301
|
const entries = Object.entries(object$2(data.ports, "ports"));
|
|
1266
|
-
if (entries.length > 16) invalid("Maximum 16 named ports per task");
|
|
1302
|
+
if (entries.length > 16) invalid$1("Maximum 16 named ports per task");
|
|
1267
1303
|
for (const [key, value2] of entries) {
|
|
1268
1304
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key) || key.toLowerCase() === "port")
|
|
1269
|
-
invalid("Port names must be identifiers other than 'port'");
|
|
1305
|
+
invalid$1("Port names must be identifiers other than 'port'");
|
|
1270
1306
|
port(value2);
|
|
1271
1307
|
}
|
|
1272
1308
|
if (new Set(entries.map(([key]) => key.toUpperCase())).size !== entries.length)
|
|
1273
|
-
invalid("Port names must be unique ignoring case");
|
|
1309
|
+
invalid$1("Port names must be unique ignoring case");
|
|
1274
1310
|
}
|
|
1275
1311
|
if (data.ready !== void 0) {
|
|
1276
1312
|
const ready = object$2(data.ready, "ready");
|
|
1277
1313
|
for (const key of Object.keys(ready))
|
|
1278
1314
|
if (!["pattern", "url", "timeoutMs", "intervalMs"].includes(key))
|
|
1279
|
-
invalid(`Unknown readiness field: ${key}`);
|
|
1315
|
+
invalid$1(`Unknown readiness field: ${key}`);
|
|
1280
1316
|
if (ready.pattern !== void 0) {
|
|
1281
1317
|
if (typeof ready.pattern !== "string" || !ready.pattern.length || ready.pattern.length > 256)
|
|
1282
|
-
invalid("ready.pattern must be 1–256 characters");
|
|
1318
|
+
invalid$1("ready.pattern must be 1–256 characters");
|
|
1283
1319
|
}
|
|
1284
1320
|
if (ready.url !== void 0 && (typeof ready.url !== "string" || ready.url.length > 4096))
|
|
1285
|
-
invalid("Invalid readiness URL");
|
|
1321
|
+
invalid$1("Invalid readiness URL");
|
|
1286
1322
|
if (ready.timeoutMs !== void 0)
|
|
1287
1323
|
number(ready.timeoutMs, 100, 36e5, "ready.timeoutMs");
|
|
1288
1324
|
if (ready.intervalMs !== void 0)
|
|
@@ -1292,9 +1328,9 @@ function definition(value) {
|
|
|
1292
1328
|
const restart = object$2(data.restart, "restart");
|
|
1293
1329
|
for (const key of Object.keys(restart))
|
|
1294
1330
|
if (!["policy", "maxAttempts", "delayMs"].includes(key))
|
|
1295
|
-
invalid(`Unknown restart field: ${key}`);
|
|
1331
|
+
invalid$1(`Unknown restart field: ${key}`);
|
|
1296
1332
|
if (!["never", "on-failure"].includes(restart.policy))
|
|
1297
|
-
invalid("Invalid restart policy");
|
|
1333
|
+
invalid$1("Invalid restart policy");
|
|
1298
1334
|
if (restart.maxAttempts !== void 0)
|
|
1299
1335
|
number(restart.maxAttempts, 0, 10, "restart.maxAttempts");
|
|
1300
1336
|
if (restart.delayMs !== void 0)
|
|
@@ -1304,9 +1340,9 @@ function definition(value) {
|
|
|
1304
1340
|
const stop2 = object$2(data.stop, "stop");
|
|
1305
1341
|
for (const key of Object.keys(stop2))
|
|
1306
1342
|
if (!["signal", "timeoutMs"].includes(key))
|
|
1307
|
-
invalid(`Unknown stop field: ${key}`);
|
|
1343
|
+
invalid$1(`Unknown stop field: ${key}`);
|
|
1308
1344
|
if (stop2.signal !== void 0 && !["SIGTERM", "SIGINT"].includes(stop2.signal))
|
|
1309
|
-
invalid("Invalid stop signal");
|
|
1345
|
+
invalid$1("Invalid stop signal");
|
|
1310
1346
|
if (stop2.timeoutMs !== void 0)
|
|
1311
1347
|
number(stop2.timeoutMs, 100, 6e4, "stop.timeoutMs");
|
|
1312
1348
|
}
|
|
@@ -1317,7 +1353,7 @@ const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
|
1317
1353
|
function json(text2) {
|
|
1318
1354
|
const errors = [];
|
|
1319
1355
|
const result = parse$1(text2, errors, { allowTrailingComma: true });
|
|
1320
|
-
if (errors.length) invalid(`Invalid JSON near offset ${errors[0].offset}`);
|
|
1356
|
+
if (errors.length) invalid$1(`Invalid JSON near offset ${errors[0].offset}`);
|
|
1321
1357
|
return object$2(result, "configuration");
|
|
1322
1358
|
}
|
|
1323
1359
|
function editJson(text2, location, value) {
|
|
@@ -1369,11 +1405,11 @@ function quotePath(value) {
|
|
|
1369
1405
|
}
|
|
1370
1406
|
function xml(text2) {
|
|
1371
1407
|
if (/<!DOCTYPE|<!ENTITY/i.test(text2))
|
|
1372
|
-
invalid("XML document types and entities are not supported");
|
|
1408
|
+
invalid$1("XML document types and entities are not supported");
|
|
1373
1409
|
return new DOMParser({
|
|
1374
|
-
onError: (level,
|
|
1410
|
+
onError: (level, message2) => {
|
|
1375
1411
|
if (level !== "warning")
|
|
1376
|
-
invalid(`Invalid run configuration XML: ${
|
|
1412
|
+
invalid$1(`Invalid run configuration XML: ${message2}`);
|
|
1377
1413
|
}
|
|
1378
1414
|
}).parseFromString(text2, "application/xml");
|
|
1379
1415
|
}
|
|
@@ -1406,7 +1442,7 @@ function jetbrains(element) {
|
|
|
1406
1442
|
const setting2 = (name2, nativeName = name2) => values[name2] ?? element.getAttribute(nativeName) ?? children(element, nativeName)[0]?.getAttribute("value") ?? "";
|
|
1407
1443
|
let task = { command: "" }, disabledReason;
|
|
1408
1444
|
const envs = Array.from(element.getElementsByTagName("env"));
|
|
1409
|
-
const
|
|
1445
|
+
const env2 = Object.fromEntries(
|
|
1410
1446
|
envs.map((node) => [
|
|
1411
1447
|
node.getAttribute("name"),
|
|
1412
1448
|
node.getAttribute("value") ?? ""
|
|
@@ -1449,7 +1485,7 @@ function jetbrains(element) {
|
|
|
1449
1485
|
disabledReason = "Gradle custom VM/script parameters need an explicit Oxbit command";
|
|
1450
1486
|
} else
|
|
1451
1487
|
disabledReason = `${type2 || "This IDE configuration"} requires an IDE/debug adapter; add an explicit Oxbit command to enable it`;
|
|
1452
|
-
task.env =
|
|
1488
|
+
task.env = env2;
|
|
1453
1489
|
const before = Array.from(element.getElementsByTagName("method")).flatMap((node) => children(node, "option")).filter((node) => node.getAttribute("enabled") === "true");
|
|
1454
1490
|
if (before.length)
|
|
1455
1491
|
disabledReason = "Before-launch IDE steps require an explicit command/dependency mapping";
|
|
@@ -1528,7 +1564,7 @@ function readSource(kind, text2) {
|
|
|
1528
1564
|
result.worktree = hooks(kind === "oxbit" ? data.worktree : meta.worktree);
|
|
1529
1565
|
if (kind === "oxbit") {
|
|
1530
1566
|
if (data.version !== 1)
|
|
1531
|
-
invalid("Unsupported Oxbit tasks version (expected 1)");
|
|
1567
|
+
invalid$1("Unsupported Oxbit tasks version (expected 1)");
|
|
1532
1568
|
for (const key of Object.keys(data))
|
|
1533
1569
|
if (![
|
|
1534
1570
|
"$schema",
|
|
@@ -1538,7 +1574,7 @@ function readSource(kind, text2) {
|
|
|
1538
1574
|
"env",
|
|
1539
1575
|
"worktree"
|
|
1540
1576
|
].includes(key))
|
|
1541
|
-
invalid(`Unknown Oxbit configuration field: ${key}`);
|
|
1577
|
+
invalid$1(`Unknown Oxbit configuration field: ${key}`);
|
|
1542
1578
|
for (const [name, task] of Object.entries(
|
|
1543
1579
|
object$2(data.tasks ?? {}, "tasks")
|
|
1544
1580
|
))
|
|
@@ -1571,10 +1607,10 @@ function readSource(kind, text2) {
|
|
|
1571
1607
|
);
|
|
1572
1608
|
} else if (kind === "vscode-launch") {
|
|
1573
1609
|
if (!Array.isArray(data.configurations))
|
|
1574
|
-
invalid("VS Code launch configurations must be an array");
|
|
1575
|
-
for (const
|
|
1610
|
+
invalid$1("VS Code launch configurations must be an array");
|
|
1611
|
+
for (const entry2 of data.configurations) {
|
|
1576
1612
|
const platform = process.platform === "darwin" ? "osx" : process.platform === "win32" ? "windows" : "linux";
|
|
1577
|
-
const item = { ...
|
|
1613
|
+
const item = { ...entry2, ...entry2[platform] };
|
|
1578
1614
|
const python = ["python", "debugpy"].includes(item.type);
|
|
1579
1615
|
let disabledReason;
|
|
1580
1616
|
if (item.request !== "launch" || !["node", "pwa-node", "python", "debugpy"].includes(item.type))
|
|
@@ -1603,11 +1639,11 @@ function readSource(kind, text2) {
|
|
|
1603
1639
|
);
|
|
1604
1640
|
} else if (kind === "vscode") {
|
|
1605
1641
|
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");
|
|
1642
|
+
invalid$1("Only VS Code tasks version 2.0.0 is supported");
|
|
1643
|
+
if (!Array.isArray(data.tasks)) invalid$1("VS Code tasks must be an array");
|
|
1608
1644
|
for (const raw of data.tasks) {
|
|
1609
|
-
const
|
|
1610
|
-
const item = { ...
|
|
1645
|
+
const entry2 = object$2(raw, "VS Code task"), platform = process.platform === "darwin" ? "osx" : process.platform === "win32" ? "windows" : "linux";
|
|
1646
|
+
const item = { ...entry2, ...entry2[platform] };
|
|
1611
1647
|
const args = (item.args ?? []).map(
|
|
1612
1648
|
(arg) => typeof arg === "string" ? arg : object$2(arg, "argument").value
|
|
1613
1649
|
);
|
|
@@ -1664,10 +1700,10 @@ function readSource(kind, text2) {
|
|
|
1664
1700
|
}
|
|
1665
1701
|
}
|
|
1666
1702
|
}
|
|
1667
|
-
if (result.tasks.length > 256) invalid("Maximum 256 tasks per configuration");
|
|
1703
|
+
if (result.tasks.length > 256) invalid$1("Maximum 256 tasks per configuration");
|
|
1668
1704
|
const names = /* @__PURE__ */ new Set();
|
|
1669
1705
|
for (const task of result.tasks) {
|
|
1670
|
-
if (names.has(task.name)) invalid(`Duplicate task name: ${task.name}`);
|
|
1706
|
+
if (names.has(task.name)) invalid$1(`Duplicate task name: ${task.name}`);
|
|
1671
1707
|
names.add(task.name);
|
|
1672
1708
|
if (!task.disabledReason) task.definition = definition(task.definition);
|
|
1673
1709
|
}
|
|
@@ -1675,7 +1711,7 @@ function readSource(kind, text2) {
|
|
|
1675
1711
|
}
|
|
1676
1712
|
function writeTask(kind, text2, name, task, sourceCommand) {
|
|
1677
1713
|
if (kind === "make" || kind === "just")
|
|
1678
|
-
invalid("Recipe imports are read-only; copy to Oxbit to customize");
|
|
1714
|
+
invalid$1("Recipe imports are read-only; copy to Oxbit to customize");
|
|
1679
1715
|
if (kind === "jetbrains") {
|
|
1680
1716
|
const doc = xml(text2);
|
|
1681
1717
|
let target = Array.from(doc.getElementsByTagName("configuration")).find(
|
|
@@ -1713,11 +1749,11 @@ function writeTask(kind, text2, name, task, sourceCommand) {
|
|
|
1713
1749
|
}
|
|
1714
1750
|
if (kind === "procfile") {
|
|
1715
1751
|
if (task && (task.command.includes("\n") || Object.keys(task).some((key) => !["command", "type"].includes(key))))
|
|
1716
|
-
invalid(
|
|
1752
|
+
invalid$1(
|
|
1717
1753
|
"Procfile supports a single-line service command. Copy to Oxbit for advanced settings."
|
|
1718
1754
|
);
|
|
1719
1755
|
if (!/^[\w-]+$/.test(name))
|
|
1720
|
-
invalid(
|
|
1756
|
+
invalid$1(
|
|
1721
1757
|
"Procfile names must contain letters, numbers, hyphens or underscores"
|
|
1722
1758
|
);
|
|
1723
1759
|
const lines = text2.split(/\r?\n/), index = lines.findIndex((line) => line.startsWith(name + ":"));
|
|
@@ -1810,10 +1846,10 @@ function writeTask(kind, text2, name, task, sourceCommand) {
|
|
|
1810
1846
|
try {
|
|
1811
1847
|
script = JSON.parse(script);
|
|
1812
1848
|
} catch {
|
|
1813
|
-
invalid("Composer script array must be valid JSON");
|
|
1849
|
+
invalid$1("Composer script array must be valid JSON");
|
|
1814
1850
|
}
|
|
1815
1851
|
if (!Array.isArray(script) || script.some((v) => typeof v !== "string"))
|
|
1816
|
-
invalid("Composer script array must contain strings");
|
|
1852
|
+
invalid$1("Composer script array must contain strings");
|
|
1817
1853
|
}
|
|
1818
1854
|
next = editJson(next, ["scripts", name], script);
|
|
1819
1855
|
}
|
|
@@ -1823,9 +1859,9 @@ function writeTask(kind, text2, name, task, sourceCommand) {
|
|
|
1823
1859
|
delete metadata.args;
|
|
1824
1860
|
return editJson(next, ["oxbit", "tasks", name], metadata);
|
|
1825
1861
|
}
|
|
1826
|
-
function writeSettings(kind, text2, worktree,
|
|
1862
|
+
function writeSettings(kind, text2, worktree, env2) {
|
|
1827
1863
|
if (["make", "just", "procfile"].includes(kind))
|
|
1828
|
-
invalid(
|
|
1864
|
+
invalid$1(
|
|
1829
1865
|
"This source cannot store lifecycle settings; save an Oxbit configuration first"
|
|
1830
1866
|
);
|
|
1831
1867
|
if (kind === "jetbrains") {
|
|
@@ -1839,7 +1875,7 @@ function writeSettings(kind, text2, worktree, env) {
|
|
|
1839
1875
|
doc.documentElement.appendChild(component);
|
|
1840
1876
|
}
|
|
1841
1877
|
option(doc, component, "worktree", JSON.stringify(worktree));
|
|
1842
|
-
option(doc, component, "env", JSON.stringify(
|
|
1878
|
+
option(doc, component, "env", JSON.stringify(env2));
|
|
1843
1879
|
return new XMLSerializer().serializeToString(doc);
|
|
1844
1880
|
}
|
|
1845
1881
|
let next = editJson(
|
|
@@ -1851,7 +1887,7 @@ function writeSettings(kind, text2, worktree, env) {
|
|
|
1851
1887
|
next = editJson(next, ["worktree", "setup"], worktree.init);
|
|
1852
1888
|
next = editJson(next, ["worktree", "teardown"], worktree.preteardown);
|
|
1853
1889
|
}
|
|
1854
|
-
return editJson(next, kind === "oxbit" ? ["env"] : ["oxbit", "env"],
|
|
1890
|
+
return editJson(next, kind === "oxbit" ? ["env"] : ["oxbit", "env"], env2);
|
|
1855
1891
|
}
|
|
1856
1892
|
const digest$2 = (text2) => createHash("sha256").update(text2).digest("hex");
|
|
1857
1893
|
const missing$1 = (error) => error.code === "ENOENT";
|
|
@@ -2008,10 +2044,10 @@ class TaskConfigStore {
|
|
|
2008
2044
|
const entries = await fs.readdir(path.join(this.root, directory), {
|
|
2009
2045
|
withFileTypes: true
|
|
2010
2046
|
});
|
|
2011
|
-
for (const
|
|
2012
|
-
if (
|
|
2047
|
+
for (const entry2 of entries.sort((a, b) => a.name.localeCompare(b.name)).slice(0, 128))
|
|
2048
|
+
if (entry2.isFile() && entry2.name.endsWith(".xml"))
|
|
2013
2049
|
candidates.push({
|
|
2014
|
-
file: path.join(directory,
|
|
2050
|
+
file: path.join(directory, entry2.name),
|
|
2015
2051
|
kind: "jetbrains"
|
|
2016
2052
|
});
|
|
2017
2053
|
} catch (error) {
|
|
@@ -2028,7 +2064,7 @@ class TaskConfigStore {
|
|
|
2028
2064
|
{ file: this.privatePath, kind: "oxbit", private: true }
|
|
2029
2065
|
);
|
|
2030
2066
|
const sources = [], tasks = [];
|
|
2031
|
-
let worktree = {},
|
|
2067
|
+
let worktree = {}, env2 = {}, settingsFound = false, noImports = false;
|
|
2032
2068
|
for (const candidate of candidates) {
|
|
2033
2069
|
if (noImports) continue;
|
|
2034
2070
|
const file = path.resolve(this.root, candidate.file), source2 = {
|
|
@@ -2062,7 +2098,7 @@ class TaskConfigStore {
|
|
|
2062
2098
|
noImports = json(text2).autoDetect === false;
|
|
2063
2099
|
if (!settingsFound && source2.writable && !["procfile"].includes(source2.kind)) {
|
|
2064
2100
|
worktree = parsed.worktree;
|
|
2065
|
-
|
|
2101
|
+
env2 = parsed.env;
|
|
2066
2102
|
settingsFound = true;
|
|
2067
2103
|
}
|
|
2068
2104
|
for (const task of parsed.tasks)
|
|
@@ -2099,7 +2135,7 @@ class TaskConfigStore {
|
|
|
2099
2135
|
sources,
|
|
2100
2136
|
tasks,
|
|
2101
2137
|
worktree,
|
|
2102
|
-
env
|
|
2138
|
+
env: env2
|
|
2103
2139
|
};
|
|
2104
2140
|
}
|
|
2105
2141
|
async source(id, expected) {
|
|
@@ -2424,22 +2460,22 @@ function tcpReady(host, port2, timeoutMs = 1e3) {
|
|
|
2424
2460
|
socket.once("timeout", () => finish(false));
|
|
2425
2461
|
});
|
|
2426
2462
|
}
|
|
2427
|
-
function expand(value,
|
|
2463
|
+
function expand(value, env2, strict = true) {
|
|
2428
2464
|
return value.replace(
|
|
2429
2465
|
/\$\$|\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
2430
2466
|
(match, braced, bare) => {
|
|
2431
2467
|
if (match === "$$") return "$";
|
|
2432
2468
|
const key = braced ?? bare;
|
|
2433
|
-
if (
|
|
2469
|
+
if (env2[key] === void 0 && strict)
|
|
2434
2470
|
throw new RpcError(
|
|
2435
2471
|
"UNRESOLVED_VARIABLE",
|
|
2436
2472
|
`Variable ${key} is not defined`
|
|
2437
2473
|
);
|
|
2438
|
-
return
|
|
2474
|
+
return env2[key] ?? "";
|
|
2439
2475
|
}
|
|
2440
2476
|
);
|
|
2441
2477
|
}
|
|
2442
|
-
function taskEnvironment(store, task, ports, catalog, context = {}) {
|
|
2478
|
+
function taskEnvironment(store, task, ports, catalog, context = {}, base = process.env) {
|
|
2443
2479
|
const root = context.projectDir ?? context.root ?? store.root, plan = ports.get(task.id);
|
|
2444
2480
|
const variables = {
|
|
2445
2481
|
OXBIT_PROJECT_ID: store.projectId,
|
|
@@ -2467,26 +2503,26 @@ function taskEnvironment(store, task, ports, catalog, context = {}) {
|
|
|
2467
2503
|
for (const [name, port2] of Object.entries(plan.ports))
|
|
2468
2504
|
variables[`OXBIT_${name.toUpperCase()}_PORT`] = String(port2);
|
|
2469
2505
|
}
|
|
2470
|
-
const
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2506
|
+
const env2 = { ...base, ...variables };
|
|
2507
|
+
env2.PASEO_SOURCE_CHECKOUT_PATH = variables.OXBIT_SOURCE_DIR;
|
|
2508
|
+
env2.PASEO_WORKTREE_PATH = root;
|
|
2509
|
+
env2.PASEO_ROOT_PATH = variables.OXBIT_SOURCE_DIR;
|
|
2510
|
+
env2.PASEO_BRANCH_NAME = variables.OXBIT_BRANCH;
|
|
2511
|
+
env2.PASEO_WORKSPACE_ID = variables.OXBIT_WORKSPACE_ID;
|
|
2512
|
+
env2.PASEO_SCRIPTNAME = task.name;
|
|
2477
2513
|
if (plan) {
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2514
|
+
env2.PASEO_PORT = variables.OXBIT_PORT;
|
|
2515
|
+
env2.PASEO_URL = variables.OXBIT_URL;
|
|
2516
|
+
env2.PASEO_WORKTREE_PORT = variables.OXBIT_PORT;
|
|
2517
|
+
env2.PORT = variables.OXBIT_PORT;
|
|
2518
|
+
env2.HOST = variables.OXBIT_HOST;
|
|
2483
2519
|
}
|
|
2484
2520
|
for (const [key, value] of Object.entries(variables))
|
|
2485
2521
|
if (key.startsWith("OXBIT_SERVICE_"))
|
|
2486
|
-
|
|
2522
|
+
env2[key.replace("OXBIT_", "PASEO_")] = value;
|
|
2487
2523
|
const custom = { ...catalog.env, ...task.env }, resolved = /* @__PURE__ */ new Set(), resolving = /* @__PURE__ */ new Set();
|
|
2488
2524
|
function resolve(key) {
|
|
2489
|
-
if (resolved.has(key)) return
|
|
2525
|
+
if (resolved.has(key)) return env2[key];
|
|
2490
2526
|
if (resolving.has(key))
|
|
2491
2527
|
throw new RpcError(
|
|
2492
2528
|
"UNRESOLVED_VARIABLE",
|
|
@@ -2505,13 +2541,13 @@ function taskEnvironment(store, task, ports, catalog, context = {}) {
|
|
|
2505
2541
|
const other = match[1] ?? match[2];
|
|
2506
2542
|
if (other !== key && Object.hasOwn(custom, other)) resolve(other);
|
|
2507
2543
|
}
|
|
2508
|
-
|
|
2544
|
+
env2[key] = expand(value, env2);
|
|
2509
2545
|
resolving.delete(key);
|
|
2510
2546
|
resolved.add(key);
|
|
2511
|
-
return
|
|
2547
|
+
return env2[key];
|
|
2512
2548
|
}
|
|
2513
2549
|
for (const key of Object.keys(custom)) resolve(key);
|
|
2514
|
-
return { env, variables };
|
|
2550
|
+
return { env: env2, variables };
|
|
2515
2551
|
}
|
|
2516
2552
|
function detectLinks(text2) {
|
|
2517
2553
|
const clean = text2.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
@@ -2519,11 +2555,11 @@ function detectLinks(text2) {
|
|
|
2519
2555
|
for (const match of clean.matchAll(/https?:\/\/[^\s<>"'\x00-\x1f]+/g)) {
|
|
2520
2556
|
const raw = match[0].replace(/[),.;\]}]+$/, "");
|
|
2521
2557
|
try {
|
|
2522
|
-
const
|
|
2523
|
-
if (!
|
|
2524
|
-
if (["0.0.0.0", "[::]"].includes(
|
|
2525
|
-
|
|
2526
|
-
found.add(
|
|
2558
|
+
const url2 = new URL(raw);
|
|
2559
|
+
if (!url2.username && !url2.password) {
|
|
2560
|
+
if (["0.0.0.0", "[::]"].includes(url2.hostname))
|
|
2561
|
+
url2.hostname = "localhost";
|
|
2562
|
+
found.add(url2.href);
|
|
2527
2563
|
}
|
|
2528
2564
|
} catch {
|
|
2529
2565
|
}
|
|
@@ -2531,35 +2567,38 @@ function detectLinks(text2) {
|
|
|
2531
2567
|
return [...found].slice(0, 32);
|
|
2532
2568
|
}
|
|
2533
2569
|
class TaskRunner {
|
|
2534
|
-
constructor(store, emit, forwardPort) {
|
|
2570
|
+
constructor(store, emit, forwardPort, shellEnvironment = async () => process.env) {
|
|
2535
2571
|
this.store = store;
|
|
2536
2572
|
this.emit = emit;
|
|
2537
2573
|
this.forwardPort = forwardPort;
|
|
2574
|
+
this.shellEnvironment = shellEnvironment;
|
|
2538
2575
|
this.ports = new TaskPorts(store.root);
|
|
2539
2576
|
}
|
|
2540
2577
|
store;
|
|
2541
2578
|
emit;
|
|
2542
2579
|
forwardPort;
|
|
2580
|
+
shellEnvironment;
|
|
2543
2581
|
runs = /* @__PURE__ */ new Map();
|
|
2544
2582
|
ports;
|
|
2545
2583
|
publicPorts = /* @__PURE__ */ new Map();
|
|
2546
2584
|
closed = false;
|
|
2585
|
+
environment;
|
|
2547
2586
|
publicLinks(run2, links) {
|
|
2548
2587
|
if (!this.forwardPort) return links;
|
|
2549
2588
|
const plan = this.ports.get(run2.task.id);
|
|
2550
2589
|
return links.map((link) => {
|
|
2551
2590
|
try {
|
|
2552
|
-
const
|
|
2591
|
+
const url2 = new URL(link), port2 = this.publicPorts.get(run2.task.id)?.get(Number(url2.port || (url2.protocol === "https:" ? 443 : 80)));
|
|
2553
2592
|
if (port2 && [
|
|
2554
2593
|
"localhost",
|
|
2555
2594
|
"127.0.0.1",
|
|
2556
2595
|
"[::1]",
|
|
2557
2596
|
plan?.hostname,
|
|
2558
2597
|
plan?.host
|
|
2559
|
-
].includes(
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
return
|
|
2598
|
+
].includes(url2.hostname)) {
|
|
2599
|
+
url2.hostname = "127.0.0.1";
|
|
2600
|
+
url2.port = String(port2);
|
|
2601
|
+
return url2.href;
|
|
2563
2602
|
}
|
|
2564
2603
|
} catch {
|
|
2565
2604
|
}
|
|
@@ -2584,10 +2623,10 @@ class TaskRunner {
|
|
|
2584
2623
|
for (const update of run2.updates) update();
|
|
2585
2624
|
run2.updates.clear();
|
|
2586
2625
|
}
|
|
2587
|
-
state(run2, state2,
|
|
2588
|
-
if (run2.value.state === state2 && run2.value.message ===
|
|
2626
|
+
state(run2, state2, message2) {
|
|
2627
|
+
if (run2.value.state === state2 && run2.value.message === message2) return;
|
|
2589
2628
|
run2.value.state = state2;
|
|
2590
|
-
run2.value.message =
|
|
2629
|
+
run2.value.message = message2;
|
|
2591
2630
|
this.publish(run2);
|
|
2592
2631
|
}
|
|
2593
2632
|
owned(id, owner) {
|
|
@@ -2685,10 +2724,10 @@ class TaskRunner {
|
|
|
2685
2724
|
)
|
|
2686
2725
|
);
|
|
2687
2726
|
}
|
|
2688
|
-
async runHook(owner, command, name, context,
|
|
2727
|
+
async runHook(owner, command, name, context, env2, signal) {
|
|
2689
2728
|
if (signal?.aborted)
|
|
2690
2729
|
throw new RpcError("CANCELLED", "Worktree operation was cancelled");
|
|
2691
|
-
const catalog = { ...await this.store.catalog(), tasks: [], env };
|
|
2730
|
+
const catalog = { ...await this.store.catalog(), tasks: [], env: env2 };
|
|
2692
2731
|
signal?.throwIfAborted();
|
|
2693
2732
|
const run2 = this.begin(
|
|
2694
2733
|
owner,
|
|
@@ -2822,12 +2861,14 @@ class TaskRunner {
|
|
|
2822
2861
|
}
|
|
2823
2862
|
async spawnAttempt(run2) {
|
|
2824
2863
|
const generation = ++run2.generation;
|
|
2825
|
-
const
|
|
2864
|
+
const base = await (this.environment ??= this.shellEnvironment(this.store.root));
|
|
2865
|
+
const { env: env2, variables } = taskEnvironment(
|
|
2826
2866
|
this.store,
|
|
2827
2867
|
run2.task,
|
|
2828
2868
|
this.ports,
|
|
2829
2869
|
run2.catalog,
|
|
2830
|
-
run2.context
|
|
2870
|
+
run2.context,
|
|
2871
|
+
base
|
|
2831
2872
|
);
|
|
2832
2873
|
await fs.mkdir(variables.OXBIT_PREINIT_DIR, {
|
|
2833
2874
|
recursive: true,
|
|
@@ -2835,7 +2876,7 @@ class TaskRunner {
|
|
|
2835
2876
|
});
|
|
2836
2877
|
const cwd = path.resolve(
|
|
2837
2878
|
run2.context.root ?? this.store.root,
|
|
2838
|
-
expand(run2.task.cwd ?? ".",
|
|
2879
|
+
expand(run2.task.cwd ?? ".", env2)
|
|
2839
2880
|
);
|
|
2840
2881
|
const real = await fs.realpath(cwd);
|
|
2841
2882
|
const bound = await fs.realpath(run2.context.root ?? this.store.root), relative = path.relative(bound, real);
|
|
@@ -2844,7 +2885,7 @@ class TaskRunner {
|
|
|
2844
2885
|
"INVALID_PATH",
|
|
2845
2886
|
"Task working directory must be within this workspace"
|
|
2846
2887
|
);
|
|
2847
|
-
const args = (run2.task.args ?? []).map((arg) => expand(arg,
|
|
2888
|
+
const args = (run2.task.args ?? []).map((arg) => expand(arg, env2));
|
|
2848
2889
|
const plan = this.ports.get(run2.task.id);
|
|
2849
2890
|
if (plan && this.forwardPort) {
|
|
2850
2891
|
const forwarded = /* @__PURE__ */ new Map();
|
|
@@ -2866,15 +2907,15 @@ class TaskRunner {
|
|
|
2866
2907
|
run2.readySeen = false;
|
|
2867
2908
|
run2.timedOut = false;
|
|
2868
2909
|
run2.tail = "";
|
|
2869
|
-
const command = run2.task.execution === "process" ? expand(run2.task.command,
|
|
2910
|
+
const command = run2.task.execution === "process" ? expand(run2.task.command, env2) : run2.task.command + (args.length ? " " + args.map(shellQuote).join(" ") : "");
|
|
2870
2911
|
const child = run2.task.execution === "process" ? spawn(command, args, {
|
|
2871
2912
|
cwd: real,
|
|
2872
|
-
env,
|
|
2913
|
+
env: env2,
|
|
2873
2914
|
detached: process.platform !== "win32",
|
|
2874
2915
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2875
2916
|
}) : spawn(command, {
|
|
2876
2917
|
cwd: real,
|
|
2877
|
-
env,
|
|
2918
|
+
env: env2,
|
|
2878
2919
|
shell: true,
|
|
2879
2920
|
detached: process.platform !== "win32",
|
|
2880
2921
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2958,19 +2999,19 @@ class TaskRunner {
|
|
|
2958
2999
|
let healthy = !run2.task.ready?.pattern || run2.readySeen;
|
|
2959
3000
|
try {
|
|
2960
3001
|
if (run2.task.ready?.url) {
|
|
2961
|
-
const
|
|
3002
|
+
const url2 = new URL(
|
|
2962
3003
|
expand(run2.task.ready.url, {
|
|
2963
3004
|
...process.env,
|
|
2964
3005
|
...run2.value.variables
|
|
2965
3006
|
})
|
|
2966
3007
|
);
|
|
2967
|
-
if (!["http:", "https:"].includes(
|
|
3008
|
+
if (!["http:", "https:"].includes(url2.protocol) || url2.username || url2.password)
|
|
2968
3009
|
throw new Error(
|
|
2969
3010
|
"Readiness URL must use HTTP(S) without credentials"
|
|
2970
3011
|
);
|
|
2971
|
-
if (plan &&
|
|
2972
|
-
|
|
2973
|
-
const response = await fetch(
|
|
3012
|
+
if (plan && url2.hostname === plan.hostname)
|
|
3013
|
+
url2.hostname = plan.host === "0.0.0.0" ? "127.0.0.1" : plan.host;
|
|
3014
|
+
const response = await fetch(url2, {
|
|
2974
3015
|
signal: AbortSignal.timeout(1500),
|
|
2975
3016
|
redirect: "manual"
|
|
2976
3017
|
});
|
|
@@ -3055,7 +3096,7 @@ class TaskRunner {
|
|
|
3055
3096
|
await this.ports.reserveAgain(run2.task.id).catch(() => {
|
|
3056
3097
|
});
|
|
3057
3098
|
}
|
|
3058
|
-
finish(run2, code, signal,
|
|
3099
|
+
finish(run2, code, signal, message2) {
|
|
3059
3100
|
if (run2.ended) return;
|
|
3060
3101
|
run2.ended = true;
|
|
3061
3102
|
clearTimeout(run2.monitor);
|
|
@@ -3068,7 +3109,7 @@ class TaskRunner {
|
|
|
3068
3109
|
this.state(
|
|
3069
3110
|
run2,
|
|
3070
3111
|
run2.stopRequested ? "stopped" : code === 0 && run2.task.type !== "service" ? "completed" : "failed",
|
|
3071
|
-
|
|
3112
|
+
message2
|
|
3072
3113
|
);
|
|
3073
3114
|
run2.doneResolve();
|
|
3074
3115
|
this.emit(
|
|
@@ -3346,13 +3387,13 @@ ${command}`,
|
|
|
3346
3387
|
"INVALID_STATE",
|
|
3347
3388
|
"Switch to the source checkout before removing this worktree"
|
|
3348
3389
|
);
|
|
3349
|
-
const
|
|
3390
|
+
const exists2 = await fs.lstat(record.path).catch((error) => {
|
|
3350
3391
|
if (error.code === "ENOENT") return null;
|
|
3351
3392
|
throw error;
|
|
3352
3393
|
});
|
|
3353
|
-
if (
|
|
3394
|
+
if (exists2 && (exists2.isSymbolicLink() || await fs.realpath(record.path) !== record.path))
|
|
3354
3395
|
throw new RpcError("INVALID_STATE", "Managed worktree path changed");
|
|
3355
|
-
if (
|
|
3396
|
+
if (exists2) {
|
|
3356
3397
|
const known = await this.git(
|
|
3357
3398
|
["worktree", "list", "--porcelain", "-z"],
|
|
3358
3399
|
void 0,
|
|
@@ -3379,9 +3420,9 @@ ${command}`,
|
|
|
3379
3420
|
record.operationPid = process.pid;
|
|
3380
3421
|
record.message = void 0;
|
|
3381
3422
|
await this.persist(records2);
|
|
3382
|
-
let removed = !
|
|
3423
|
+
let removed = !exists2;
|
|
3383
3424
|
try {
|
|
3384
|
-
if (
|
|
3425
|
+
if (exists2) {
|
|
3385
3426
|
await this.phase(record, "preteardown", owner, signal);
|
|
3386
3427
|
await this.runner.stopInDirectory(record.path);
|
|
3387
3428
|
if (signal?.aborted)
|
|
@@ -3416,6 +3457,8 @@ function bounded(value) {
|
|
|
3416
3457
|
);
|
|
3417
3458
|
return value;
|
|
3418
3459
|
}
|
|
3460
|
+
const BRANCH_DIFF_FILES = 50;
|
|
3461
|
+
const BRANCH_DIFF_TEXT = 2e5;
|
|
3419
3462
|
const conflicted = (index, working) => ["DD", "AU", "UD", "UA", "DU", "AA", "UU"].includes(index + working);
|
|
3420
3463
|
const digest$1 = (value) => createHash("sha256").update(value).digest("hex");
|
|
3421
3464
|
class Git {
|
|
@@ -3642,14 +3685,14 @@ class Git {
|
|
|
3642
3685
|
if (error.code !== "ENOENT") throw error;
|
|
3643
3686
|
}
|
|
3644
3687
|
}
|
|
3645
|
-
const
|
|
3688
|
+
const binary2 = diff.includes("Binary files ") || before.stdout.includes("\0") || after.includes("\0");
|
|
3646
3689
|
const pieces = diff.split(/(?=^@@ )/m);
|
|
3647
|
-
const canHunk = !
|
|
3690
|
+
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
3691
|
return bounded({
|
|
3649
|
-
before:
|
|
3650
|
-
after:
|
|
3692
|
+
before: binary2 ? "" : before.exitCode ? "" : before.stdout,
|
|
3693
|
+
after: binary2 ? "" : after,
|
|
3651
3694
|
diff,
|
|
3652
|
-
binary,
|
|
3695
|
+
binary: binary2,
|
|
3653
3696
|
fingerprint: digest$1(diff),
|
|
3654
3697
|
hunks: canHunk ? pieces.slice(1).map((patch, index) => ({
|
|
3655
3698
|
index,
|
|
@@ -3746,18 +3789,18 @@ class Git {
|
|
|
3746
3789
|
if (typeof params.path !== "string")
|
|
3747
3790
|
throw new RpcError("INVALID_PARAMS", "A file path is required");
|
|
3748
3791
|
await this.files.resolve(params.path, true);
|
|
3749
|
-
const
|
|
3792
|
+
const detail2 = await this.show(params.ref, signal), file = detail2.files.find((file2) => file2.path === params.path);
|
|
3750
3793
|
if (!file)
|
|
3751
3794
|
throw new RpcError("NOT_FOUND", "File is not part of this commit");
|
|
3752
|
-
const before =
|
|
3795
|
+
const before = detail2.commit.parents[0] ? await this.optional(
|
|
3753
3796
|
[
|
|
3754
3797
|
"show",
|
|
3755
|
-
`${
|
|
3798
|
+
`${detail2.commit.parents[0]}:${file.originalPath ?? file.path}`
|
|
3756
3799
|
],
|
|
3757
3800
|
signal
|
|
3758
3801
|
) : void 0;
|
|
3759
3802
|
const after = await this.optional(
|
|
3760
|
-
["show", `${
|
|
3803
|
+
["show", `${detail2.commit.id}:${file.path}`],
|
|
3761
3804
|
signal
|
|
3762
3805
|
);
|
|
3763
3806
|
const diff = await this.run(
|
|
@@ -3768,19 +3811,19 @@ class Git {
|
|
|
3768
3811
|
"--no-ext-diff",
|
|
3769
3812
|
"--no-textconv",
|
|
3770
3813
|
"--no-color",
|
|
3771
|
-
|
|
3814
|
+
detail2.commit.id,
|
|
3772
3815
|
"--",
|
|
3773
3816
|
file.path,
|
|
3774
3817
|
...file.originalPath ? [file.originalPath] : []
|
|
3775
3818
|
],
|
|
3776
3819
|
signal
|
|
3777
3820
|
);
|
|
3778
|
-
const
|
|
3821
|
+
const binary2 = diff.stdout.includes("Binary files ") || before?.stdout.includes("\0") || after.stdout.includes("\0");
|
|
3779
3822
|
return bounded({
|
|
3780
|
-
before:
|
|
3781
|
-
after:
|
|
3823
|
+
before: binary2 || before?.exitCode ? "" : before?.stdout ?? "",
|
|
3824
|
+
after: binary2 || after.exitCode ? "" : after.stdout,
|
|
3782
3825
|
diff: diff.stdout,
|
|
3783
|
-
binary: !!
|
|
3826
|
+
binary: !!binary2
|
|
3784
3827
|
});
|
|
3785
3828
|
}
|
|
3786
3829
|
async stashes(signal) {
|
|
@@ -3826,6 +3869,88 @@ class Git {
|
|
|
3826
3869
|
)).stdout
|
|
3827
3870
|
});
|
|
3828
3871
|
}
|
|
3872
|
+
/** The upstream remote's default branch, then `origin/HEAD`, then local `main` or `master`. */
|
|
3873
|
+
async baseBranch(branch, signal) {
|
|
3874
|
+
const remote = branch ? (await this.optional(["config", "--get", `branch.${branch}.remote`], signal)).stdout.trim() : "";
|
|
3875
|
+
const candidates = [
|
|
3876
|
+
...remote && remote !== "." ? [`refs/remotes/${remote}/HEAD`] : [],
|
|
3877
|
+
"refs/remotes/origin/HEAD",
|
|
3878
|
+
"refs/heads/main",
|
|
3879
|
+
"refs/heads/master"
|
|
3880
|
+
];
|
|
3881
|
+
for (const ref of candidates) {
|
|
3882
|
+
const commit = await this.optional(
|
|
3883
|
+
["rev-parse", "--verify", "--quiet", "--end-of-options", `${ref}^{commit}`],
|
|
3884
|
+
signal
|
|
3885
|
+
);
|
|
3886
|
+
if (commit.exitCode) continue;
|
|
3887
|
+
const name = await this.optional(["rev-parse", "--abbrev-ref", ref], signal);
|
|
3888
|
+
return { name: name.stdout.trim() || ref, commit: commit.stdout.trim() };
|
|
3889
|
+
}
|
|
3890
|
+
return void 0;
|
|
3891
|
+
}
|
|
3892
|
+
async untrackedPatch(relative, signal) {
|
|
3893
|
+
const result = await this.run(
|
|
3894
|
+
["diff", "--no-index", "--no-ext-diff", "--no-textconv", "--no-color", "--src-prefix=a/", "--dst-prefix=b/", "--", "/dev/null", relative],
|
|
3895
|
+
signal,
|
|
3896
|
+
this.files.root,
|
|
3897
|
+
void 0,
|
|
3898
|
+
true
|
|
3899
|
+
);
|
|
3900
|
+
if (result.exitCode > 1)
|
|
3901
|
+
throw new RpcError("GIT_FAILED", result.stderr.trim() || `Git exited ${result.exitCode}`);
|
|
3902
|
+
return result.stdout;
|
|
3903
|
+
}
|
|
3904
|
+
/** Committed, staged, unstaged and untracked changes since the merge base with the base branch. */
|
|
3905
|
+
async branchDiff(params, signal) {
|
|
3906
|
+
const limit2 = Number(params.limit ?? BRANCH_DIFF_FILES);
|
|
3907
|
+
if (!Number.isInteger(limit2) || limit2 < 0 || limit2 > BRANCH_DIFF_FILES)
|
|
3908
|
+
throw new RpcError("INVALID_PARAMS", "Invalid file limit");
|
|
3909
|
+
const inside = await this.optional(["rev-parse", "--is-inside-work-tree"], signal);
|
|
3910
|
+
if (inside.exitCode || inside.stdout.trim() !== "true") {
|
|
3911
|
+
if (inside.exitCode && !inside.stderr.includes("not a git repository"))
|
|
3912
|
+
throw new RpcError("GIT_FAILED", inside.stderr.trim());
|
|
3913
|
+
return { repository: false };
|
|
3914
|
+
}
|
|
3915
|
+
const head = await this.head(signal);
|
|
3916
|
+
if (!head) throw new RpcError("NOT_FOUND", "This branch has no commits yet");
|
|
3917
|
+
const branch = (await this.optional(["symbolic-ref", "-q", "--short", "HEAD"], signal)).stdout.trim();
|
|
3918
|
+
const base = await this.baseBranch(branch, signal);
|
|
3919
|
+
if (!base) throw new RpcError("NOT_FOUND", "No base branch: origin/HEAD, main and master are missing");
|
|
3920
|
+
const mergeBase = await this.optional(["merge-base", base.commit, head], signal);
|
|
3921
|
+
if (mergeBase.exitCode)
|
|
3922
|
+
throw new RpcError("NOT_FOUND", `${branch || "HEAD"} has no common history with ${base.name}`);
|
|
3923
|
+
const from = mergeBase.stdout.trim();
|
|
3924
|
+
const names = async (args) => (await this.run(args, signal)).stdout.split("\0").filter(Boolean);
|
|
3925
|
+
const tracked = await names(["diff", "--name-only", "--relative", "-z", from, "--"]);
|
|
3926
|
+
const untracked = await names(["ls-files", "--others", "--exclude-standard", "-z", "--"]);
|
|
3927
|
+
const files = [];
|
|
3928
|
+
let size = 0, truncated = false;
|
|
3929
|
+
for (const [index, relative] of [...tracked, ...untracked].entries()) {
|
|
3930
|
+
if (files.length >= limit2 || size >= BRANCH_DIFF_TEXT) {
|
|
3931
|
+
truncated = true;
|
|
3932
|
+
break;
|
|
3933
|
+
}
|
|
3934
|
+
let diff = "";
|
|
3935
|
+
try {
|
|
3936
|
+
diff = index < tracked.length ? (await this.run(
|
|
3937
|
+
["diff", "--no-ext-diff", "--no-textconv", "--no-color", "--relative", "--src-prefix=a/", "--dst-prefix=b/", from, "--", relative],
|
|
3938
|
+
signal
|
|
3939
|
+
)).stdout : await this.untrackedPatch(relative, signal);
|
|
3940
|
+
} catch (error) {
|
|
3941
|
+
if (error.code !== "OUTPUT_LIMIT") throw error;
|
|
3942
|
+
truncated = true;
|
|
3943
|
+
}
|
|
3944
|
+
const binary2 = /^Binary files /m.test(diff);
|
|
3945
|
+
if (size + diff.length > BRANCH_DIFF_TEXT) {
|
|
3946
|
+
diff = diff.slice(0, BRANCH_DIFF_TEXT - size);
|
|
3947
|
+
truncated = true;
|
|
3948
|
+
}
|
|
3949
|
+
size += diff.length;
|
|
3950
|
+
files.push({ path: relative, diff, binary: binary2 });
|
|
3951
|
+
}
|
|
3952
|
+
return bounded({ base: base.name, head: branch || head.slice(0, 7), files, truncated });
|
|
3953
|
+
}
|
|
3829
3954
|
async action(method, params, signal, progress) {
|
|
3830
3955
|
const pending = this.queue.catch(() => {
|
|
3831
3956
|
}).then(() => this.execute(method, params, signal, progress));
|
|
@@ -4319,9 +4444,9 @@ class ExternalSources {
|
|
|
4319
4444
|
}
|
|
4320
4445
|
}
|
|
4321
4446
|
async authorize(uri) {
|
|
4322
|
-
const
|
|
4323
|
-
if (
|
|
4324
|
-
const file = await fs.realpath(fileURLToPath(
|
|
4447
|
+
const url2 = new URL(uri);
|
|
4448
|
+
if (url2.protocol !== "file:" || url2.host || url2.search || url2.hash) throw new RpcError("PATH_DENIED", "External sources require local file URIs");
|
|
4449
|
+
const file = await fs.realpath(fileURLToPath(url2));
|
|
4325
4450
|
if (!this.roots.some((root) => contains(root, file))) throw new RpcError("PATH_DENIED", "Source is outside registered dependency roots");
|
|
4326
4451
|
const stat = await fs.stat(file);
|
|
4327
4452
|
if (!stat.isFile() || stat.size > 20 * 1024 * 1024) throw new RpcError("PATH_DENIED", "Source is not a supported text file");
|
|
@@ -4331,16 +4456,16 @@ class ExternalSources {
|
|
|
4331
4456
|
return { handle, uri, name: path.basename(file), readonly: true };
|
|
4332
4457
|
}
|
|
4333
4458
|
async read(handle) {
|
|
4334
|
-
const
|
|
4335
|
-
if (!
|
|
4336
|
-
const descriptor = await fs.open(
|
|
4459
|
+
const entry2 = this.handles.get(handle);
|
|
4460
|
+
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");
|
|
4461
|
+
const descriptor = await fs.open(entry2.file, constants$1.O_RDONLY | constants$1.O_NOFOLLOW);
|
|
4337
4462
|
try {
|
|
4338
4463
|
const stat = await descriptor.stat();
|
|
4339
4464
|
if (!stat.isFile() || stat.size > 20 * 1024 * 1024) throw new RpcError("PATH_DENIED", "External source is not a supported text file");
|
|
4340
4465
|
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:
|
|
4466
|
+
const current = await fs.stat(entry2.file);
|
|
4467
|
+
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");
|
|
4468
|
+
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
4469
|
} finally {
|
|
4345
4470
|
await descriptor.close();
|
|
4346
4471
|
}
|
|
@@ -4531,17 +4656,17 @@ class LanguageServer {
|
|
|
4531
4656
|
}
|
|
4532
4657
|
}
|
|
4533
4658
|
}
|
|
4534
|
-
send(
|
|
4535
|
-
if (
|
|
4536
|
-
const pending = this.incoming.get(
|
|
4659
|
+
send(message2) {
|
|
4660
|
+
if (message2.id !== void 0 && !message2.method) {
|
|
4661
|
+
const pending = this.incoming.get(message2.id);
|
|
4537
4662
|
if (!pending) return;
|
|
4538
|
-
this.incoming.delete(
|
|
4539
|
-
if (
|
|
4540
|
-
else pending.resolve(
|
|
4663
|
+
this.incoming.delete(message2.id);
|
|
4664
|
+
if (message2.error) pending.reject(new ResponseError(message2.error.code, message2.error.message, message2.error.data));
|
|
4665
|
+
else pending.resolve(message2.result);
|
|
4541
4666
|
return;
|
|
4542
4667
|
}
|
|
4543
4668
|
if (!this.rpc) throw new RpcError("LSP_UNAVAILABLE", "Language server is stopped");
|
|
4544
|
-
void this.rpc.sendNotification(
|
|
4669
|
+
void this.rpc.sendNotification(message2.method, ...Array.isArray(message2.params) ? message2.params : [message2.params]).catch((error) => {
|
|
4545
4670
|
if (this.rpc) this.emit("window/logMessage", { type: 1, message: String(error) });
|
|
4546
4671
|
});
|
|
4547
4672
|
}
|
|
@@ -4577,7 +4702,10 @@ class LanguageServer {
|
|
|
4577
4702
|
if (resume) this.paused = false;
|
|
4578
4703
|
if (!resume && this.phase === "failed") throw new RpcError("LSP_FAILED", this.error || "Language server failed. Retry from Language Servers.");
|
|
4579
4704
|
if (this.paused) throw new RpcError("LSP_STOPPED", "Language server is stopped. Start it from Language Servers.");
|
|
4580
|
-
if (this.starting)
|
|
4705
|
+
if (this.starting) {
|
|
4706
|
+
this.emit("oxbit/serverState", { state: this.phase });
|
|
4707
|
+
return this.starting;
|
|
4708
|
+
}
|
|
4581
4709
|
if (this.child && this.capabilities)
|
|
4582
4710
|
return { capabilities: this.capabilities, registrations: [...this.registrations.values()], rootUri: this.uri(""), projectRootUri: pathToFileURL(this.options.root ?? this.files.root).href, version: this.launchSpec?.version };
|
|
4583
4711
|
this.error = "";
|
|
@@ -4696,12 +4824,12 @@ class LanguageServer {
|
|
|
4696
4824
|
});
|
|
4697
4825
|
connection.listen();
|
|
4698
4826
|
this.child.stderr.on("data", (data) => {
|
|
4699
|
-
let
|
|
4827
|
+
let message2 = data.toString().slice(0, 16e3);
|
|
4700
4828
|
const key = this.launchSpec?.initializationOptions?.licenceKey;
|
|
4701
|
-
if (typeof key === "string" && key)
|
|
4702
|
-
this.output.push(
|
|
4829
|
+
if (typeof key === "string" && key) message2 = message2.replaceAll(key, "[redacted]");
|
|
4830
|
+
this.output.push(message2);
|
|
4703
4831
|
while (this.output.join("").length > 16e3) this.output.shift();
|
|
4704
|
-
this.emit("window/logMessage", { type: 4, message });
|
|
4832
|
+
this.emit("window/logMessage", { type: 4, message: message2 });
|
|
4705
4833
|
});
|
|
4706
4834
|
const launched = this.child;
|
|
4707
4835
|
this.child.on("error", (error) => {
|
|
@@ -4762,61 +4890,61 @@ class LanguageServer {
|
|
|
4762
4890
|
this.emit("oxbit/serverState", { state: "running", name: this.serverName });
|
|
4763
4891
|
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
4892
|
}
|
|
4765
|
-
receive(
|
|
4766
|
-
if (setting("LSP_TRACE")) this.emit("window/logMessage", { type: 4, message: `${this.serverName}: ${
|
|
4767
|
-
if (
|
|
4768
|
-
if (
|
|
4893
|
+
receive(message2) {
|
|
4894
|
+
if (setting("LSP_TRACE")) this.emit("window/logMessage", { type: 4, message: `${this.serverName}: ${message2.method ?? "response"}` });
|
|
4895
|
+
if (message2.id !== void 0 && message2.method) {
|
|
4896
|
+
if (message2.method === "vscode/content" && this.launchSpec?.schemaContent) {
|
|
4769
4897
|
const connection = this.rpc;
|
|
4770
|
-
const uri = Array.isArray(
|
|
4898
|
+
const uri = Array.isArray(message2.params) && message2.params.length === 1 ? message2.params[0] : message2.params;
|
|
4771
4899
|
void this.launchSpec.schemaContent(uri).then(
|
|
4772
4900
|
(result) => {
|
|
4773
|
-
if (this.rpc === connection) this.send({ id:
|
|
4901
|
+
if (this.rpc === connection) this.send({ id: message2.id, result });
|
|
4774
4902
|
},
|
|
4775
4903
|
(error) => {
|
|
4776
|
-
if (this.rpc === connection) this.send({ id:
|
|
4904
|
+
if (this.rpc === connection) this.send({ id: message2.id, error: { code: -32603, message: String(error) } });
|
|
4777
4905
|
}
|
|
4778
4906
|
);
|
|
4779
4907
|
return;
|
|
4780
4908
|
}
|
|
4781
|
-
if (["workspace/semanticTokens/refresh", "workspace/inlayHint/refresh"].includes(
|
|
4782
|
-
this.emit("oxbit/refresh", { method:
|
|
4783
|
-
this.send({ id:
|
|
4909
|
+
if (["workspace/semanticTokens/refresh", "workspace/inlayHint/refresh"].includes(message2.method)) {
|
|
4910
|
+
this.emit("oxbit/refresh", { method: message2.method });
|
|
4911
|
+
this.send({ id: message2.id, result: null });
|
|
4784
4912
|
return;
|
|
4785
4913
|
}
|
|
4786
|
-
if (
|
|
4914
|
+
if (message2.method === "window/showMessageRequest") {
|
|
4787
4915
|
const connection = this.rpc;
|
|
4788
4916
|
const timer = setTimeout(() => {
|
|
4789
|
-
if (this.rpc === connection) this.send({ id:
|
|
4917
|
+
if (this.rpc === connection) this.send({ id: message2.id, result: null });
|
|
4790
4918
|
}, 3e4);
|
|
4791
4919
|
timer.unref();
|
|
4792
|
-
this.emit("oxbit/serverRequest", { id:
|
|
4920
|
+
this.emit("oxbit/serverRequest", { id: message2.id, method: message2.method, params: message2.params });
|
|
4793
4921
|
return;
|
|
4794
4922
|
}
|
|
4795
|
-
if (
|
|
4923
|
+
if (message2.method === "workspace/applyEdit") {
|
|
4796
4924
|
const child = this.child;
|
|
4797
4925
|
void (async () => {
|
|
4798
|
-
await this.validate(
|
|
4799
|
-
return this.applyEdit ? await this.applyEdit(
|
|
4926
|
+
await this.validate(message2.params?.edit);
|
|
4927
|
+
return this.applyEdit ? await this.applyEdit(message2.params.edit, message2.params.label) : { applied: false, failureReason: "No initiating document client is available" };
|
|
4800
4928
|
})().catch((error) => ({ applied: false, failureReason: String(error) })).then((result) => {
|
|
4801
|
-
if (this.child === child) this.send({ jsonrpc: "2.0", id:
|
|
4929
|
+
if (this.child === child) this.send({ jsonrpc: "2.0", id: message2.id, result });
|
|
4802
4930
|
});
|
|
4803
4931
|
return;
|
|
4804
4932
|
}
|
|
4805
|
-
if (
|
|
4806
|
-
const adding =
|
|
4807
|
-
const registrations = adding ?
|
|
4933
|
+
if (message2.method === "client/registerCapability" || message2.method === "client/unregisterCapability") {
|
|
4934
|
+
const adding = message2.method === "client/registerCapability";
|
|
4935
|
+
const registrations = adding ? message2.params?.registrations : message2.params?.unregisterations;
|
|
4808
4936
|
const syncMethods = ["textDocument/didOpen", "textDocument/didClose", "textDocument/didChange", "textDocument/didSave", "textDocument/willSave", "textDocument/willSaveWaitUntil"];
|
|
4809
4937
|
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
4938
|
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
4939
|
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:
|
|
4940
|
+
this.send({ id: message2.id, error: { code: -32602, message: "Unsupported or unknown capability registration" } });
|
|
4813
4941
|
} else {
|
|
4814
4942
|
const previousSync = new Map([...this.documents.keys()].map((uri) => [uri, this.syncOptions(uri).openClose]));
|
|
4815
4943
|
for (const item of registrations) {
|
|
4816
4944
|
if (adding) this.registrations.set(item.id, item);
|
|
4817
4945
|
else this.registrations.delete(item.id);
|
|
4818
4946
|
}
|
|
4819
|
-
this.send({ id:
|
|
4947
|
+
this.send({ id: message2.id, result: null });
|
|
4820
4948
|
for (const [uri, wasOpen] of previousSync) {
|
|
4821
4949
|
const isOpen = this.syncOptions(uri).openClose;
|
|
4822
4950
|
if (!wasOpen && isOpen) this.opened(uri);
|
|
@@ -4826,15 +4954,15 @@ class LanguageServer {
|
|
|
4826
4954
|
}
|
|
4827
4955
|
return;
|
|
4828
4956
|
}
|
|
4829
|
-
if (
|
|
4830
|
-
this.send({ jsonrpc: "2.0", id:
|
|
4957
|
+
if (message2.method === "workspace/workspaceFolders") {
|
|
4958
|
+
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
4959
|
return;
|
|
4832
4960
|
}
|
|
4833
|
-
if (
|
|
4961
|
+
if (message2.method === "workspace/configuration")
|
|
4834
4962
|
this.send({
|
|
4835
4963
|
jsonrpc: "2.0",
|
|
4836
|
-
id:
|
|
4837
|
-
result: (
|
|
4964
|
+
id: message2.id,
|
|
4965
|
+
result: (message2.params?.items ?? []).map((item) => {
|
|
4838
4966
|
if (item.scopeUri) {
|
|
4839
4967
|
try {
|
|
4840
4968
|
const scope = fileURLToPath(item.scopeUri), root = this.options.root ?? this.files.root;
|
|
@@ -4849,28 +4977,28 @@ class LanguageServer {
|
|
|
4849
4977
|
return item.section.split(".").reduce((value, key) => value?.[key], settings) ?? null;
|
|
4850
4978
|
})
|
|
4851
4979
|
});
|
|
4852
|
-
else if (
|
|
4853
|
-
this.send({ jsonrpc: "2.0", id:
|
|
4854
|
-
else this.send({ jsonrpc: "2.0", id:
|
|
4980
|
+
else if (message2.method === "window/workDoneProgress/create")
|
|
4981
|
+
this.send({ jsonrpc: "2.0", id: message2.id, result: null });
|
|
4982
|
+
else this.send({ jsonrpc: "2.0", id: message2.id, error: { code: -32601, message: `Unsupported server request ${message2.method}` } });
|
|
4855
4983
|
return;
|
|
4856
4984
|
}
|
|
4857
|
-
if (
|
|
4985
|
+
if (message2.method === "tsserver/request" && this.companion) {
|
|
4858
4986
|
const child = this.child;
|
|
4859
|
-
for (const [id, command, payload] of Array.isArray(
|
|
4987
|
+
for (const [id, command, payload] of Array.isArray(message2.params?.[0]) ? message2.params : [message2.params]) void this.companion.request("workspace/executeCommand", {
|
|
4860
4988
|
command: "typescript.tsserverRequest",
|
|
4861
4989
|
arguments: [command, payload]
|
|
4862
4990
|
}).catch(() => null).then((result) => {
|
|
4863
|
-
if (this.child === child) this.send({ jsonrpc: "2.0", method: "tsserver/response", params: Array.isArray(
|
|
4991
|
+
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
4992
|
});
|
|
4865
4993
|
return;
|
|
4866
4994
|
}
|
|
4867
|
-
if (
|
|
4868
|
-
if (
|
|
4869
|
-
this.primaryDiagnostics.set(
|
|
4870
|
-
this.emit(
|
|
4995
|
+
if (message2.method === "textDocument/publishDiagnostics" && this.launchSpec?.diagnostics) message2.params = { ...message2.params, diagnostics: this.launchSpec.diagnostics(message2.params.diagnostics ?? []) };
|
|
4996
|
+
if (message2.method === "textDocument/publishDiagnostics" && this.companion) {
|
|
4997
|
+
this.primaryDiagnostics.set(message2.params.uri, message2.params.diagnostics ?? []);
|
|
4998
|
+
this.emit(message2.method, { ...message2.params, diagnostics: [...message2.params.diagnostics ?? [], ...this.companionDiagnostics.get(message2.params.uri) ?? []] });
|
|
4871
4999
|
return;
|
|
4872
5000
|
}
|
|
4873
|
-
if (
|
|
5001
|
+
if (message2.method && !this.options.onServerMessage?.(message2.method, message2.params)) this.emit(message2.method, message2.params);
|
|
4874
5002
|
}
|
|
4875
5003
|
respond(id, result) {
|
|
4876
5004
|
this.send({ id, result });
|
|
@@ -5101,8 +5229,8 @@ async function dependencyRoots(root, signal) {
|
|
|
5101
5229
|
for (const base of resolve.resolve.paths(name) ?? []) {
|
|
5102
5230
|
try {
|
|
5103
5231
|
const installed = await fs.realpath(path.join(base, name));
|
|
5104
|
-
const
|
|
5105
|
-
if (
|
|
5232
|
+
const pkg2 = JSON.parse(await fs.readFile(path.join(installed, "package.json"), "utf8"));
|
|
5233
|
+
if (pkg2.name !== name || installed === path.parse(installed).root) break;
|
|
5106
5234
|
if (!roots.has(installed)) {
|
|
5107
5235
|
roots.add(installed);
|
|
5108
5236
|
queue.push({ directory: installed, development: false });
|
|
@@ -5207,6 +5335,47 @@ function deferToRuff(spec) {
|
|
|
5207
5335
|
diagnostics: (diagnostics) => diagnostics.filter((item) => item.code !== void 0)
|
|
5208
5336
|
};
|
|
5209
5337
|
}
|
|
5338
|
+
const phpProbes = [
|
|
5339
|
+
{ command: ["herd", "which-php"], php: (output) => output.includes("No usable PHP version found") ? void 0 : [output.trim()] },
|
|
5340
|
+
{ command: ["valet", "which-php"], php: (output) => [output.trim()] },
|
|
5341
|
+
{ command: ["./vendor/bin/sail", "ps"], php: () => ["./vendor/bin/sail", "php"] },
|
|
5342
|
+
{ command: ["lando", "php", "-r", "echo PHP_BINARY;"], php: () => ["lando", "php"] },
|
|
5343
|
+
{ command: ["ddev", "php", "-r", "echo PHP_BINARY;"], php: () => ["ddev", "php"] },
|
|
5344
|
+
{ command: ["php", "-r", "echo PHP_BINARY;"], php: (output) => [output.trim()] }
|
|
5345
|
+
];
|
|
5346
|
+
function probe(command, cwd, env2, timeout, signal) {
|
|
5347
|
+
return new Promise((resolve) => {
|
|
5348
|
+
const child = spawn(command[0], command.slice(1), { cwd, env: env2, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "ignore"] });
|
|
5349
|
+
trackChild(child);
|
|
5350
|
+
let output = "";
|
|
5351
|
+
const finish = (value) => {
|
|
5352
|
+
clearTimeout(timer);
|
|
5353
|
+
signal?.removeEventListener("abort", stop2);
|
|
5354
|
+
resolve(value);
|
|
5355
|
+
};
|
|
5356
|
+
const stop2 = () => {
|
|
5357
|
+
killProcess(child);
|
|
5358
|
+
finish();
|
|
5359
|
+
};
|
|
5360
|
+
const timer = setTimeout(stop2, timeout);
|
|
5361
|
+
signal?.addEventListener("abort", stop2, { once: true });
|
|
5362
|
+
child.stdout.on("data", (data) => {
|
|
5363
|
+
output = (output + data.toString()).slice(-4096);
|
|
5364
|
+
});
|
|
5365
|
+
child.on("error", () => finish());
|
|
5366
|
+
child.on("close", (code) => finish(code === 0 ? output : void 0));
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
async function laravelPhpCommand(root, env2, signal, timeout = 5e3) {
|
|
5370
|
+
for (const { command, php } of phpProbes) {
|
|
5371
|
+
signal?.throwIfAborted();
|
|
5372
|
+
const output = await probe(command, root, env2, timeout, signal);
|
|
5373
|
+
const found = output === void 0 ? void 0 : php(output);
|
|
5374
|
+
if (found?.[0]) return found;
|
|
5375
|
+
}
|
|
5376
|
+
signal?.throwIfAborted();
|
|
5377
|
+
throw new Error("Laravel needs PHP on the runtime host. No PHP was found through Herd, Valet, Sail, Lando, DDEV or php on PATH. Install PHP or set languageServers.laravel.initializationOptions.phpCommand.");
|
|
5378
|
+
}
|
|
5210
5379
|
const npmPresets = {
|
|
5211
5380
|
typescript: ["typescript-language-server", "lib/cli.mjs", ["typescript"]],
|
|
5212
5381
|
html: ["vscode-langservers-extracted", "bin/vscode-html-language-server"],
|
|
@@ -5222,11 +5391,11 @@ const npmPresets = {
|
|
|
5222
5391
|
};
|
|
5223
5392
|
async function resolveLaunch(id, root, installer, signal) {
|
|
5224
5393
|
if (id === "laravel" || id === "ruff") {
|
|
5225
|
-
const
|
|
5226
|
-
return { executable:
|
|
5394
|
+
const binary2 = await installer.native(id, signal);
|
|
5395
|
+
return { executable: binary2.executable, args: id === "ruff" ? ["server"] : [], version: binary2.version };
|
|
5227
5396
|
}
|
|
5228
5397
|
if (id === "marksman" || id === "taplo") {
|
|
5229
|
-
const
|
|
5398
|
+
const binary2 = await installer.native(id, signal);
|
|
5230
5399
|
const schemaDirectory = path.join(installer.cache, "schemas");
|
|
5231
5400
|
if (id === "taplo") {
|
|
5232
5401
|
await fs.mkdir(schemaDirectory, { recursive: true });
|
|
@@ -5234,9 +5403,9 @@ async function resolveLaunch(id, root, installer, signal) {
|
|
|
5234
5403
|
await fs.writeFile(path.join(schemaDirectory, "cargo-config.json"), JSON.stringify(cargoConfigSchema));
|
|
5235
5404
|
}
|
|
5236
5405
|
return {
|
|
5237
|
-
executable:
|
|
5406
|
+
executable: binary2.executable,
|
|
5238
5407
|
args: id === "marksman" ? ["server"] : ["lsp", "stdio"],
|
|
5239
|
-
version:
|
|
5408
|
+
version: binary2.version,
|
|
5240
5409
|
...id === "marksman" ? { env: { DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: "1", DOTNET_CLI_TELEMETRY_OPTOUT: "1" } } : {},
|
|
5241
5410
|
...id === "taplo" ? { dependencyRoots: [schemaDirectory], settingsSection: "evenBetterToml", initializationOptions: { cachePath: path.join(installer.cache, "taplo-schemas"), configurationSection: "evenBetterToml" }, settings: { evenBetterToml: { schema: { enabled: true, catalogs: [], associations: {
|
|
5242
5411
|
"(^|/)Cargo\\.toml$": pathToFileURL(path.join(schemaDirectory, "cargo.json")).href,
|
|
@@ -5253,9 +5422,9 @@ async function resolveLaunch(id, root, installer, signal) {
|
|
|
5253
5422
|
settings: { xml: { telemetry: { enabled: false }, downloadExternalResources: { enabled: false } } }
|
|
5254
5423
|
};
|
|
5255
5424
|
}
|
|
5256
|
-
const
|
|
5257
|
-
if (!
|
|
5258
|
-
const [name, bin, dependencies = []] =
|
|
5425
|
+
const entry2 = npmPresets[id];
|
|
5426
|
+
if (!entry2) throw new Error(`Unknown language server preset: ${id}`);
|
|
5427
|
+
const [name, bin, dependencies = []] = entry2;
|
|
5259
5428
|
const installation = await installer.npm(id, [name, ...dependencies], signal);
|
|
5260
5429
|
const modules = path.join(installation.directory, "node_modules");
|
|
5261
5430
|
const bootstrap = await consoleBootstrap(installer.cache);
|
|
@@ -5322,10 +5491,10 @@ function lockedPackages(roots) {
|
|
|
5322
5491
|
const selected = /* @__PURE__ */ new Map();
|
|
5323
5492
|
function visit(location) {
|
|
5324
5493
|
if (selected.has(location)) return;
|
|
5325
|
-
const
|
|
5326
|
-
if (!
|
|
5327
|
-
selected.set(location,
|
|
5328
|
-
for (const name of Object.keys({ ...
|
|
5494
|
+
const entry2 = packages[location];
|
|
5495
|
+
if (!entry2?.resolved || !entry2.integrity || !entry2.version) throw new Error(`Missing locked package: ${location}`);
|
|
5496
|
+
selected.set(location, entry2);
|
|
5497
|
+
for (const name of Object.keys({ ...entry2.dependencies, ...entry2.optionalDependencies })) {
|
|
5329
5498
|
let base = location, found;
|
|
5330
5499
|
while (true) {
|
|
5331
5500
|
const candidate = `${base ? base + "/" : ""}node_modules/${name}`;
|
|
@@ -5337,7 +5506,7 @@ function lockedPackages(roots) {
|
|
|
5337
5506
|
base = base.includes("/node_modules/") ? base.slice(0, base.lastIndexOf("/node_modules/")) : "";
|
|
5338
5507
|
}
|
|
5339
5508
|
if (found) visit(found);
|
|
5340
|
-
else if (!
|
|
5509
|
+
else if (!entry2.optionalDependencies?.[name]) throw new Error(`Unresolved locked dependency: ${name}`);
|
|
5341
5510
|
}
|
|
5342
5511
|
}
|
|
5343
5512
|
roots.forEach((name) => visit(`node_modules/${name}`));
|
|
@@ -5356,13 +5525,13 @@ class ManagedInstaller {
|
|
|
5356
5525
|
cache;
|
|
5357
5526
|
platform;
|
|
5358
5527
|
download;
|
|
5359
|
-
async bytes(
|
|
5360
|
-
if (!
|
|
5528
|
+
async bytes(url2, integrity, signal) {
|
|
5529
|
+
if (!url2.startsWith("https://")) throw new Error("Managed artifacts require HTTPS");
|
|
5361
5530
|
let failure2;
|
|
5362
5531
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
5363
5532
|
signal?.throwIfAborted();
|
|
5364
5533
|
try {
|
|
5365
|
-
const response = await this.download(
|
|
5534
|
+
const response = await this.download(url2, { signal: AbortSignal.any([AbortSignal.timeout(12e4), ...signal ? [signal] : []]) });
|
|
5366
5535
|
if (!response.ok || !response.body) throw new Error(`Artifact download failed (${response.status})`);
|
|
5367
5536
|
const chunks = [];
|
|
5368
5537
|
let size = 0;
|
|
@@ -5395,13 +5564,13 @@ class ManagedInstaller {
|
|
|
5395
5564
|
strict: true,
|
|
5396
5565
|
preservePaths: false,
|
|
5397
5566
|
filter(name, candidate) {
|
|
5398
|
-
const
|
|
5567
|
+
const entry2 = candidate;
|
|
5399
5568
|
const normalized = name.split("/").slice(strip).join("/");
|
|
5400
5569
|
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(
|
|
5570
|
+
else if (!["File", "OldFile", "Directory", "SymbolicLink"].includes(entry2.type)) rejection ??= new Error("Unsupported archive entry");
|
|
5571
|
+
else if (entry2.type === "SymbolicLink") {
|
|
5572
|
+
const target = path.resolve(directory, path.dirname(normalized), entry2.linkpath ?? "");
|
|
5573
|
+
if (path.isAbsolute(entry2.linkpath ?? "") || !target.startsWith(directory + path.sep)) rejection ??= new Error("Archive link leaves installation");
|
|
5405
5574
|
}
|
|
5406
5575
|
return !rejection;
|
|
5407
5576
|
}
|
|
@@ -5447,19 +5616,22 @@ class ManagedInstaller {
|
|
|
5447
5616
|
}
|
|
5448
5617
|
}
|
|
5449
5618
|
} catch {
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
} catch {
|
|
5619
|
+
}
|
|
5620
|
+
try {
|
|
5621
|
+
if (Date.now() - (await fs.stat(lock)).mtimeMs > 6e4) {
|
|
5622
|
+
await fs.rm(lock, { recursive: true, force: true });
|
|
5623
|
+
continue;
|
|
5456
5624
|
}
|
|
5625
|
+
} catch {
|
|
5457
5626
|
}
|
|
5458
5627
|
if (Date.now() > deadline) throw new Error("Timed out waiting for another language server installation");
|
|
5459
5628
|
await setTimeout$1(100, void 0, { signal });
|
|
5460
5629
|
}
|
|
5461
5630
|
}
|
|
5462
5631
|
const stage = path.join(base, `.staging-${randomUUID()}`);
|
|
5632
|
+
const heartbeat = setInterval(() => void fs.utimes(lock, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date()).catch(() => {
|
|
5633
|
+
}), 1e4);
|
|
5634
|
+
heartbeat.unref();
|
|
5463
5635
|
try {
|
|
5464
5636
|
if (await complete()) return target;
|
|
5465
5637
|
for (const name of await fs.readdir(base)) if (name.startsWith(".staging-")) await fs.rm(path.join(base, name), { recursive: true, force: true });
|
|
@@ -5475,6 +5647,7 @@ class ManagedInstaller {
|
|
|
5475
5647
|
await fs.rename(stage, target);
|
|
5476
5648
|
return target;
|
|
5477
5649
|
} finally {
|
|
5650
|
+
clearInterval(heartbeat);
|
|
5478
5651
|
await fs.rm(stage, { recursive: true, force: true });
|
|
5479
5652
|
await fs.rm(lock, { recursive: true, force: true });
|
|
5480
5653
|
}
|
|
@@ -5485,7 +5658,7 @@ class ManagedInstaller {
|
|
|
5485
5658
|
const queue = [...entries].sort(([a], [b]) => a.split("/").length - b.split("/").length);
|
|
5486
5659
|
for (let start = 0; start < queue.length; start += 6) {
|
|
5487
5660
|
const batch = queue.slice(start, start + 6);
|
|
5488
|
-
const bytes = await Promise.all(batch.map(([,
|
|
5661
|
+
const bytes = await Promise.all(batch.map(([, entry2]) => this.bytes(entry2.resolved, entry2.integrity, signal)));
|
|
5489
5662
|
for (const [index, [location]] of batch.entries()) {
|
|
5490
5663
|
signal?.throwIfAborted();
|
|
5491
5664
|
await this.extract(bytes[index], path.join(stage, location), 1);
|
|
@@ -5540,17 +5713,17 @@ class JsonSchemas {
|
|
|
5540
5713
|
uri.hash = "";
|
|
5541
5714
|
return uri.href;
|
|
5542
5715
|
}
|
|
5543
|
-
async cached(
|
|
5716
|
+
async cached(url2) {
|
|
5544
5717
|
try {
|
|
5545
|
-
const file = path.join(this.cache, digest(
|
|
5718
|
+
const file = path.join(this.cache, digest(url2) + ".json");
|
|
5546
5719
|
if ((await fs.stat(file)).size > maxBytes * 3) return;
|
|
5547
5720
|
const value = JSON.parse(await fs.readFile(file, "utf8"));
|
|
5548
|
-
if (value.url ===
|
|
5721
|
+
if (value.url === url2 && typeof value.text === "string" && typeof value.fetchedAt === "number" && value.integrity === digest(value.text)) return value;
|
|
5549
5722
|
} catch {
|
|
5550
5723
|
}
|
|
5551
5724
|
}
|
|
5552
|
-
async fetchJson(
|
|
5553
|
-
let current =
|
|
5725
|
+
async fetchJson(url2) {
|
|
5726
|
+
let current = url2;
|
|
5554
5727
|
const signal = AbortSignal.any([this.abort.signal, AbortSignal.timeout(8e3)]);
|
|
5555
5728
|
for (let redirects = 0; redirects < 6; redirects++) {
|
|
5556
5729
|
const response = await this.download(current, { signal, redirect: "manual", headers: { Accept: "application/schema+json, application/json" } });
|
|
@@ -5584,47 +5757,47 @@ class JsonSchemas {
|
|
|
5584
5757
|
throw new Error("Too many JSON schema redirects");
|
|
5585
5758
|
}
|
|
5586
5759
|
async remote(value, enabled) {
|
|
5587
|
-
const
|
|
5760
|
+
const url2 = this.remoteUri(value), cached2 = await this.cached(url2);
|
|
5588
5761
|
if (cached2 && (!enabled || Date.now() - cached2.fetchedAt < maxAge)) return cached2.text;
|
|
5589
5762
|
if (!enabled) throw new Error("Schema downloads are disabled and this schema is not cached");
|
|
5590
|
-
if ((this.failures.get(
|
|
5763
|
+
if ((this.failures.get(url2) ?? 0) > Date.now()) {
|
|
5591
5764
|
if (cached2) return cached2.text;
|
|
5592
5765
|
throw new Error("Schema download is temporarily unavailable");
|
|
5593
5766
|
}
|
|
5594
|
-
let pending = this.pending.get(
|
|
5767
|
+
let pending = this.pending.get(url2);
|
|
5595
5768
|
if (!pending) {
|
|
5596
5769
|
pending = (async () => {
|
|
5597
5770
|
try {
|
|
5598
|
-
const text2 = await this.fetchJson(
|
|
5771
|
+
const text2 = await this.fetchJson(url2);
|
|
5599
5772
|
await fs.mkdir(this.cache, { recursive: true, mode: 448 });
|
|
5600
|
-
const target = path.join(this.cache, digest(
|
|
5773
|
+
const target = path.join(this.cache, digest(url2) + ".json"), temporary = target + "." + randomUUID();
|
|
5601
5774
|
try {
|
|
5602
|
-
await fs.writeFile(temporary, JSON.stringify({ url, fetchedAt: Date.now(), integrity: digest(text2), text: text2 }), { mode: 384 });
|
|
5775
|
+
await fs.writeFile(temporary, JSON.stringify({ url: url2, fetchedAt: Date.now(), integrity: digest(text2), text: text2 }), { mode: 384 });
|
|
5603
5776
|
await fs.rename(temporary, target);
|
|
5604
5777
|
} finally {
|
|
5605
5778
|
await fs.rm(temporary, { force: true });
|
|
5606
5779
|
}
|
|
5607
5780
|
return text2;
|
|
5608
5781
|
} catch (error) {
|
|
5609
|
-
this.failures.set(
|
|
5782
|
+
this.failures.set(url2, Date.now() + 6e4);
|
|
5610
5783
|
if (cached2) return cached2.text;
|
|
5611
5784
|
throw error;
|
|
5612
5785
|
} finally {
|
|
5613
|
-
this.pending.delete(
|
|
5786
|
+
this.pending.delete(url2);
|
|
5614
5787
|
}
|
|
5615
5788
|
})();
|
|
5616
|
-
this.pending.set(
|
|
5789
|
+
this.pending.set(url2, pending);
|
|
5617
5790
|
}
|
|
5618
5791
|
return pending;
|
|
5619
5792
|
}
|
|
5620
5793
|
async content(uri, enabled = true) {
|
|
5621
5794
|
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(
|
|
5795
|
+
const url2 = new URL(uri);
|
|
5796
|
+
url2.hash = "";
|
|
5797
|
+
if (url2.href === SETTINGS_SCHEMA_URI || this.settingsPaths.schemaFile && url2.href === pathToFileURL(this.settingsPaths.schemaFile).href) return JSON.stringify(settingsSchema);
|
|
5798
|
+
if (url2.protocol === "file:") {
|
|
5799
|
+
url2.hash = "";
|
|
5800
|
+
const file = await this.files.resolve(path.relative(this.files.root, fileURLToPath(url2)).split(path.sep).join("/"));
|
|
5628
5801
|
if ((await fs.stat(file)).size > maxBytes) throw new Error("JSON schema exceeds 5 MiB");
|
|
5629
5802
|
return fs.readFile(file, "utf8");
|
|
5630
5803
|
}
|
|
@@ -5773,6 +5946,8 @@ class LanguageServerManager {
|
|
|
5773
5946
|
else spec = await resolveLaunch(definition2.id, root, this.installer, signal);
|
|
5774
5947
|
spec = { ...spec, args: definition2.args ?? spec.args, env: { ...spec.env, ...definition2.env }, initializationOptions: merge(spec.initializationOptions, definition2.initializationOptions), settings: merge(spec.settings, definition2.settings) };
|
|
5775
5948
|
if (ruff) spec = deferToRuff(spec);
|
|
5949
|
+
if (definition2.id === "laravel" && !spec.initializationOptions?.phpCommand && (spec.initializationOptions?.phpEnvironment ?? "auto") === "auto")
|
|
5950
|
+
spec.initializationOptions = { ...spec.initializationOptions, phpCommand: await laravelPhpCommand(root, { ...process.env, ...spec.env }, signal) };
|
|
5776
5951
|
if (definition2.id === "json") {
|
|
5777
5952
|
const options2 = this.project ? (await this.project.reload()).schemas : { catalog: true, download: true, associations: [] };
|
|
5778
5953
|
spec.settings = await this.schemas.settings(spec.settings ?? {}, options2);
|
|
@@ -5813,7 +5988,7 @@ class LanguageServerManager {
|
|
|
5813
5988
|
if (!instance.attachments.size && !instance.idle) {
|
|
5814
5989
|
instance.idle = setTimeout(() => {
|
|
5815
5990
|
instance.idle = void 0;
|
|
5816
|
-
void this.stop(false, instance.id);
|
|
5991
|
+
if (this.instances.get(instance.id) === instance) void this.stop(false, instance.id);
|
|
5817
5992
|
}, this.idleMs);
|
|
5818
5993
|
instance.idle.unref();
|
|
5819
5994
|
}
|
|
@@ -6092,24 +6267,24 @@ class ProjectStore {
|
|
|
6092
6267
|
} catch {
|
|
6093
6268
|
}
|
|
6094
6269
|
const entries = await fs.readdir(directoryPath, { withFileTypes: true }).catch(() => []);
|
|
6095
|
-
for (const
|
|
6096
|
-
const relative = path.posix.join(directory,
|
|
6097
|
-
if (
|
|
6270
|
+
for (const entry2 of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6271
|
+
const relative = path.posix.join(directory, entry2.name);
|
|
6272
|
+
if (entry2.isSymbolicLink() || entry2.name.startsWith(".oxbit-tmp-") || config.intelligence.exclude.some((pattern) => matchesFilePattern(pattern, relative) || matchesFilePattern(pattern, relative + "/"))) continue;
|
|
6098
6273
|
let ignored = false;
|
|
6099
6274
|
for (const [base, rules] of ignores) if (!base || relative.startsWith(base + "/")) {
|
|
6100
|
-
const match = rules.test(relative.slice(base ? base.length + 1 : 0) + (
|
|
6275
|
+
const match = rules.test(relative.slice(base ? base.length + 1 : 0) + (entry2.isDirectory() ? "/" : ""));
|
|
6101
6276
|
if (match.ignored) ignored = true;
|
|
6102
6277
|
else if (match.unignored) ignored = false;
|
|
6103
6278
|
}
|
|
6104
6279
|
if (ignored) continue;
|
|
6105
|
-
if (
|
|
6106
|
-
if (!excludedDirectories.has(
|
|
6280
|
+
if (entry2.isDirectory()) {
|
|
6281
|
+
if (!excludedDirectories.has(entry2.name)) {
|
|
6107
6282
|
if (queue.length < config.intelligence.maxFiles) queue.push(relative);
|
|
6108
6283
|
else result.truncated = true;
|
|
6109
6284
|
}
|
|
6110
6285
|
continue;
|
|
6111
6286
|
}
|
|
6112
|
-
if (!
|
|
6287
|
+
if (!entry2.isFile()) continue;
|
|
6113
6288
|
if (result.files.length >= config.intelligence.maxFiles) {
|
|
6114
6289
|
result.truncated = true;
|
|
6115
6290
|
break;
|
|
@@ -6134,12 +6309,12 @@ class ProjectStore {
|
|
|
6134
6309
|
const text2 = await fs.readFile(full, "utf8").catch(() => "");
|
|
6135
6310
|
bytes += stat.size;
|
|
6136
6311
|
texts.set(full, text2);
|
|
6137
|
-
if (
|
|
6312
|
+
if (entry2.name === "package.json" || entry2.name === "composer.json") {
|
|
6138
6313
|
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(
|
|
6314
|
+
const data = JSON.parse(text2), ecosystem = entry2.name === "package.json" ? "npm" : "composer";
|
|
6315
|
+
const pkg2 = { path: relative, name: typeof data.name === "string" ? data.name : directory || config.name, ecosystem, dependencies: [] };
|
|
6316
|
+
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 });
|
|
6317
|
+
result.packages.push(pkg2);
|
|
6143
6318
|
} catch {
|
|
6144
6319
|
result.warnings.push(`Invalid manifest: ${relative}`);
|
|
6145
6320
|
}
|
|
@@ -6175,7 +6350,7 @@ class ProjectStore {
|
|
|
6175
6350
|
const values = [base, ...[".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".mdx", ".vue", ".astro", ".json"].flatMap((ext) => [base + ext, path.join(base, "index" + ext)])];
|
|
6176
6351
|
return values.find((file) => fullPaths.has(file));
|
|
6177
6352
|
};
|
|
6178
|
-
const workspacePackages = new Map(result.packages.filter((
|
|
6353
|
+
const workspacePackages = new Map(result.packages.filter((pkg2) => pkg2.ecosystem === "npm").map((pkg2) => [pkg2.name, pkg2.path]));
|
|
6179
6354
|
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
6355
|
let analyzed = 0;
|
|
6181
6356
|
for (const record of result.files) {
|
|
@@ -6206,8 +6381,8 @@ class ProjectStore {
|
|
|
6206
6381
|
if (!target && workspaceManifest) {
|
|
6207
6382
|
const file = path.join(this.files.root, workspaceManifest), manifest = JSON.parse(texts.get(file));
|
|
6208
6383
|
const subpath = specifier.slice(packageName.length), key = subpath ? "." + subpath : ".";
|
|
6209
|
-
const
|
|
6210
|
-
if (typeof
|
|
6384
|
+
const entry2 = exportPath(manifest.exports?.[key] ?? (!subpath ? manifest.exports : void 0)) ?? (!subpath ? manifest.types ?? manifest.typings ?? manifest.module ?? manifest.main ?? "index" : subpath.slice(1));
|
|
6385
|
+
if (typeof entry2 === "string") target = candidate(path.resolve(path.dirname(file), entry2));
|
|
6211
6386
|
}
|
|
6212
6387
|
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
6388
|
});
|
|
@@ -6217,7 +6392,7 @@ class ProjectStore {
|
|
|
6217
6392
|
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
6393
|
}
|
|
6219
6394
|
}
|
|
6220
|
-
const dependencies = new Set(result.packages.flatMap((
|
|
6395
|
+
const dependencies = new Set(result.packages.flatMap((pkg2) => pkg2.dependencies.map((item) => item.name)));
|
|
6221
6396
|
result.frameworks = ["laravel/framework", "next", "nuxt", "astro", "vue", "react", "@mdx-js/mdx"].filter((name) => dependencies.has(name));
|
|
6222
6397
|
if (result.truncated) result.warnings.push(`File/directory inventory reached the ${config.intelligence.maxFiles} entry budget`);
|
|
6223
6398
|
result.warnings = result.warnings.slice(0, 100);
|
|
@@ -6769,9 +6944,9 @@ class Collaboration {
|
|
|
6769
6944
|
}
|
|
6770
6945
|
}
|
|
6771
6946
|
async changed(relative) {
|
|
6772
|
-
const
|
|
6773
|
-
if (!
|
|
6774
|
-
const room = await
|
|
6947
|
+
const entry2 = this.rooms.get(relative);
|
|
6948
|
+
if (!entry2) return;
|
|
6949
|
+
const room = await entry2;
|
|
6775
6950
|
if (room.saving) return;
|
|
6776
6951
|
let disk;
|
|
6777
6952
|
try {
|
|
@@ -6820,9 +6995,9 @@ class Collaboration {
|
|
|
6820
6995
|
);
|
|
6821
6996
|
}
|
|
6822
6997
|
async leave(connection, relative) {
|
|
6823
|
-
for (const [key,
|
|
6998
|
+
for (const [key, entry2] of this.rooms) {
|
|
6824
6999
|
if (relative && relative !== key) continue;
|
|
6825
|
-
const room = await
|
|
7000
|
+
const room = await entry2;
|
|
6826
7001
|
room.members.delete(connection);
|
|
6827
7002
|
const clients = room.clients.get(connection);
|
|
6828
7003
|
if (clients)
|
|
@@ -6830,7 +7005,7 @@ class Collaboration {
|
|
|
6830
7005
|
room.clients.delete(connection);
|
|
6831
7006
|
if (!room.members.size) {
|
|
6832
7007
|
await room.persist;
|
|
6833
|
-
if (!room.members.size && this.rooms.get(key) ===
|
|
7008
|
+
if (!room.members.size && this.rooms.get(key) === entry2) {
|
|
6834
7009
|
this.rooms.delete(key);
|
|
6835
7010
|
this.lsp.closeCanonical(key);
|
|
6836
7011
|
room.awareness.destroy();
|
|
@@ -6840,8 +7015,8 @@ class Collaboration {
|
|
|
6840
7015
|
}
|
|
6841
7016
|
}
|
|
6842
7017
|
async close() {
|
|
6843
|
-
for (const
|
|
6844
|
-
const room = await
|
|
7018
|
+
for (const entry2 of this.rooms.values()) {
|
|
7019
|
+
const room = await entry2;
|
|
6845
7020
|
await room.persist;
|
|
6846
7021
|
room.awareness.destroy();
|
|
6847
7022
|
room.doc.destroy();
|
|
@@ -6851,7 +7026,7 @@ class Collaboration {
|
|
|
6851
7026
|
}
|
|
6852
7027
|
const terminal = (state2) => !["pending", "running", "unknown"].includes(state2);
|
|
6853
7028
|
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;
|
|
7029
|
+
const str$1 = (value, max = 4096) => typeof value === "string" ? value.slice(0, max) : void 0;
|
|
6855
7030
|
const identity = (value) => typeof value === "string" && value.length > 0 && value.length <= 512 && !value.includes("\0");
|
|
6856
7031
|
const preview = (value, max = 8192) => value === void 0 ? void 0 : (typeof value === "string" ? value : JSON.stringify(value)).slice(0, max);
|
|
6857
7032
|
const statusNames = {
|
|
@@ -6890,7 +7065,7 @@ function normalizeDelegation(provider, update) {
|
|
|
6890
7065
|
{
|
|
6891
7066
|
providerId: sub.threadId,
|
|
6892
7067
|
toolId,
|
|
6893
|
-
name: str(str(sub.path)?.split("/").filter(Boolean).at(-1), 200),
|
|
7068
|
+
name: str$1(str$1(sub.path)?.split("/").filter(Boolean).at(-1), 200),
|
|
6894
7069
|
state: state(sub.activity === "started" ? "running" : sub.activity),
|
|
6895
7070
|
evidence: "provider",
|
|
6896
7071
|
spawn: sub.activity === "started"
|
|
@@ -6906,7 +7081,7 @@ function normalizeDelegation(provider, update) {
|
|
|
6906
7081
|
return [
|
|
6907
7082
|
{
|
|
6908
7083
|
toolId,
|
|
6909
|
-
task: str(raw.prompt),
|
|
7084
|
+
task: str$1(raw.prompt),
|
|
6910
7085
|
state: update.status === "failed" ? "failed" : "unknown",
|
|
6911
7086
|
evidence: "tool",
|
|
6912
7087
|
spawn: true
|
|
@@ -6915,11 +7090,11 @@ function normalizeDelegation(provider, update) {
|
|
|
6915
7090
|
return ids.map((providerId) => ({
|
|
6916
7091
|
providerId,
|
|
6917
7092
|
toolId,
|
|
6918
|
-
parent: str(collab.senderThreadId, 512),
|
|
6919
|
-
task: str(raw.prompt),
|
|
6920
|
-
model: str(raw.model, 200),
|
|
7093
|
+
parent: str$1(collab.senderThreadId, 512),
|
|
7094
|
+
task: str$1(raw.prompt),
|
|
7095
|
+
model: str$1(raw.model, 200),
|
|
6921
7096
|
state: state(states[providerId]?.status) ?? (collab.tool === "spawnAgent" && update.status === "failed" ? "failed" : void 0),
|
|
6922
|
-
result: str(states[providerId]?.message, 16384),
|
|
7097
|
+
result: str$1(states[providerId]?.message, 16384),
|
|
6923
7098
|
evidence: "provider",
|
|
6924
7099
|
spawn: collab.tool === "spawnAgent",
|
|
6925
7100
|
reopen: ["resumeAgent", "sendInput", "followupTask"].includes(collab.tool) && ["running", "pendingInit"].includes(states[providerId]?.status)
|
|
@@ -6933,9 +7108,9 @@ function normalizeDelegation(provider, update) {
|
|
|
6933
7108
|
{
|
|
6934
7109
|
toolId,
|
|
6935
7110
|
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),
|
|
7111
|
+
name: str$1(task.description, 200),
|
|
7112
|
+
task: str$1(task.prompt),
|
|
7113
|
+
model: str$1(task.model, 200),
|
|
6939
7114
|
durationMs: Number.isFinite(task.durationMs ?? outcome.durationMs) && (task.durationMs ?? outcome.durationMs) >= 0 ? task.durationMs ?? outcome.durationMs : void 0,
|
|
6940
7115
|
state: state(outcomeName),
|
|
6941
7116
|
result: preview(task.result ?? outcome.result ?? output.result),
|
|
@@ -6948,9 +7123,9 @@ function normalizeDelegation(provider, update) {
|
|
|
6948
7123
|
return [
|
|
6949
7124
|
{
|
|
6950
7125
|
toolId,
|
|
6951
|
-
name: str(raw.description ?? raw.subagent_type, 200),
|
|
6952
|
-
task: str(raw.prompt),
|
|
6953
|
-
model: str(raw.model, 200),
|
|
7126
|
+
name: str$1(raw.description ?? raw.subagent_type, 200),
|
|
7127
|
+
task: str$1(raw.prompt),
|
|
7128
|
+
model: str$1(raw.model, 200),
|
|
6954
7129
|
state: update.status === "failed" ? "failed" : "unknown",
|
|
6955
7130
|
evidence: "tool",
|
|
6956
7131
|
spawn: true
|
|
@@ -7109,8 +7284,8 @@ class SubagentRegistry {
|
|
|
7109
7284
|
parentId: this.idForSession(sessionId),
|
|
7110
7285
|
sessionId: child,
|
|
7111
7286
|
providerId: child,
|
|
7112
|
-
name: str(update.name, 200) || record.name,
|
|
7113
|
-
task: str(update.task) ?? record.task,
|
|
7287
|
+
name: str$1(update.name, 200) || record.name,
|
|
7288
|
+
task: str$1(update.task) ?? record.task,
|
|
7114
7289
|
evidence: "native",
|
|
7115
7290
|
visibility: "full"
|
|
7116
7291
|
});
|
|
@@ -7232,20 +7407,20 @@ class SubagentRegistry {
|
|
|
7232
7407
|
}
|
|
7233
7408
|
toolActivity(record, update) {
|
|
7234
7409
|
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
|
-
|
|
7410
|
+
let entry2 = record.activity.find((a) => a.id === id);
|
|
7411
|
+
if (!entry2) {
|
|
7412
|
+
entry2 = { id, kind: "tool" };
|
|
7413
|
+
record.activity.push(entry2);
|
|
7414
|
+
}
|
|
7415
|
+
entry2.title = str$1(update.title, 200) ?? entry2.title ?? "Delegated task";
|
|
7416
|
+
if (!["completed", "failed"].includes(entry2.status ?? ""))
|
|
7417
|
+
entry2.status = str$1(update.status, 40) ?? entry2.status;
|
|
7418
|
+
entry2.input = preview(update.rawInput) ?? entry2.input;
|
|
7419
|
+
entry2.output = preview(toolOutput(update)) ?? entry2.output;
|
|
7420
|
+
entry2.locations = Array.isArray(update.locations) ? update.locations.filter((l) => typeof l.path === "string").slice(0, 16).map((l) => ({
|
|
7246
7421
|
path: l.path.slice(0, 4096),
|
|
7247
7422
|
line: Number.isSafeInteger(l.line) ? l.line : void 0
|
|
7248
|
-
})) :
|
|
7423
|
+
})) : entry2.locations;
|
|
7249
7424
|
}
|
|
7250
7425
|
activity(sessionId, update) {
|
|
7251
7426
|
const id = this.idForSession(sessionId);
|
|
@@ -7264,24 +7439,24 @@ class SubagentRegistry {
|
|
|
7264
7439
|
"user_message_chunk"
|
|
7265
7440
|
].includes(update.sessionUpdate)) {
|
|
7266
7441
|
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) ?? "";
|
|
7442
|
+
const content = str$1(update.content?.text ?? update.content?.resource?.text, 16384) ?? "";
|
|
7268
7443
|
const previous = record.activity.at(-1);
|
|
7269
|
-
let
|
|
7444
|
+
let entry2 = identity(update.messageId) ? record.activity.find(
|
|
7270
7445
|
(a) => a.kind === "message" && a.id === `message:provider:${update.messageId}`
|
|
7271
7446
|
) : previous?.kind === "message" && previous.role === role ? previous : void 0;
|
|
7272
|
-
if (!
|
|
7273
|
-
|
|
7447
|
+
if (!entry2) {
|
|
7448
|
+
entry2 = {
|
|
7274
7449
|
id: identity(update.messageId) ? `message:provider:${update.messageId}` : `message:local:${++this.serial}`,
|
|
7275
7450
|
kind: "message",
|
|
7276
7451
|
role,
|
|
7277
7452
|
text: ""
|
|
7278
7453
|
};
|
|
7279
|
-
record.activity.push(
|
|
7454
|
+
record.activity.push(entry2);
|
|
7280
7455
|
}
|
|
7281
|
-
if (
|
|
7282
|
-
|
|
7456
|
+
if (entry2.text.length + content.length > 16384) record.truncated = true;
|
|
7457
|
+
entry2.text = (entry2.text + content).slice(-16384);
|
|
7283
7458
|
if (role === "agent") {
|
|
7284
|
-
record.result =
|
|
7459
|
+
record.result = entry2.text;
|
|
7285
7460
|
phase = "responding";
|
|
7286
7461
|
} else if (role === "thought") phase = "thinking";
|
|
7287
7462
|
} else if (["tool_call", "tool_call_update"].includes(update.sessionUpdate) && identity(update.toolCallId)) {
|
|
@@ -7319,12 +7494,12 @@ class SubagentRegistry {
|
|
|
7319
7494
|
const id = this.idForSession(sessionId), record = id && this.records.get(id);
|
|
7320
7495
|
if (!record) return;
|
|
7321
7496
|
const entryId = `terminal:${terminalId}`;
|
|
7322
|
-
const
|
|
7497
|
+
const entry2 = record.activity.find((a) => a.id === entryId) ?? {
|
|
7323
7498
|
id: entryId,
|
|
7324
7499
|
kind: "terminal"
|
|
7325
7500
|
};
|
|
7326
|
-
if (!record.activity.includes(
|
|
7327
|
-
Object.assign(
|
|
7501
|
+
if (!record.activity.includes(entry2)) record.activity.push(entry2);
|
|
7502
|
+
Object.assign(entry2, {
|
|
7328
7503
|
title: title2.slice(0, 200),
|
|
7329
7504
|
text: output.slice(-8192),
|
|
7330
7505
|
status: ended ? "completed" : "in_progress"
|
|
@@ -7350,7 +7525,7 @@ class SubagentRegistry {
|
|
|
7350
7525
|
let cached;
|
|
7351
7526
|
function runtimeVersion() {
|
|
7352
7527
|
if (cached) return cached;
|
|
7353
|
-
return cached = "0.
|
|
7528
|
+
return cached = "0.9.0";
|
|
7354
7529
|
}
|
|
7355
7530
|
const MAX_REQUEST_BYTES = 1024 * 1024;
|
|
7356
7531
|
const MAX_CONCURRENT_REQUESTS = 16;
|
|
@@ -7475,7 +7650,7 @@ function validateArguments(name, args) {
|
|
|
7475
7650
|
if (args.line !== void 0 && (!Number.isSafeInteger(args.line) || args.line < 1)) return "line must be a positive integer";
|
|
7476
7651
|
if (args.limit !== void 0 && (!Number.isSafeInteger(args.limit) || args.limit < 1)) return "limit must be a positive integer";
|
|
7477
7652
|
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((
|
|
7653
|
+
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
7654
|
return void 0;
|
|
7480
7655
|
}
|
|
7481
7656
|
function reply(response, status2, body) {
|
|
@@ -7487,8 +7662,8 @@ function reply(response, status2, body) {
|
|
|
7487
7662
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
7488
7663
|
response.end(JSON.stringify(body));
|
|
7489
7664
|
}
|
|
7490
|
-
function rpcError(id, code,
|
|
7491
|
-
return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
|
|
7665
|
+
function rpcError(id, code, message2) {
|
|
7666
|
+
return { jsonrpc: "2.0", id: id ?? null, error: { code, message: message2 } };
|
|
7492
7667
|
}
|
|
7493
7668
|
class AgentMCP {
|
|
7494
7669
|
constructor(handler) {
|
|
@@ -7573,25 +7748,25 @@ class AgentMCP {
|
|
|
7573
7748
|
if (length > MAX_REQUEST_BYTES) return reply(response, 413);
|
|
7574
7749
|
chunks.push(chunk);
|
|
7575
7750
|
}
|
|
7576
|
-
let
|
|
7751
|
+
let message2;
|
|
7577
7752
|
try {
|
|
7578
|
-
|
|
7753
|
+
message2 = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
7579
7754
|
} catch {
|
|
7580
7755
|
return reply(response, 400, rpcError(null, -32700, "Invalid JSON"));
|
|
7581
7756
|
}
|
|
7582
|
-
if (!object(
|
|
7757
|
+
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
7758
|
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(
|
|
7759
|
+
if (version && !PROTOCOL_VERSIONS.includes(version)) return reply(response, 400, rpcError(message2.id, -32600, "Unsupported MCP protocol version"));
|
|
7760
|
+
if (message2.id === void 0) return reply(response, 202);
|
|
7761
|
+
const id = message2.id;
|
|
7762
|
+
if (message2.method === "initialize") {
|
|
7763
|
+
const requested = object(message2.params) && typeof message2.params.protocolVersion === "string" ? message2.params.protocolVersion : void 0;
|
|
7589
7764
|
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
7765
|
}
|
|
7591
|
-
if (
|
|
7592
|
-
if (
|
|
7593
|
-
if (
|
|
7594
|
-
const params =
|
|
7766
|
+
if (message2.method === "ping") return reply(response, 200, { jsonrpc: "2.0", id, result: {} });
|
|
7767
|
+
if (message2.method === "tools/list") return reply(response, 200, { jsonrpc: "2.0", id, result: { tools: NATIVE_AGENT_TOOLS } });
|
|
7768
|
+
if (message2.method !== "tools/call") return reply(response, 200, rpcError(id, -32601, "Method not found"));
|
|
7769
|
+
const params = message2.params;
|
|
7595
7770
|
if (!object(params) || typeof params.name !== "string" || !object(params.arguments ?? {})) return reply(response, 200, rpcError(id, -32602, "Invalid tool call"));
|
|
7596
7771
|
const args = params.arguments ?? {};
|
|
7597
7772
|
const invalid2 = validateArguments(params.name, args);
|
|
@@ -7612,7 +7787,332 @@ class AgentMCP {
|
|
|
7612
7787
|
}
|
|
7613
7788
|
}
|
|
7614
7789
|
}
|
|
7790
|
+
const run$1 = promisify(execFile);
|
|
7791
|
+
const TTL = 3e5, MiB = 1024 * 1024, MAX_LISTING = 2 * MiB, MAX_ARCHIVE = 512 * MiB;
|
|
7792
|
+
const REGISTRY_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
7793
|
+
const VERSION = /^[A-Za-z0-9][A-Za-z0-9.+_-]{0,63}$/;
|
|
7794
|
+
const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
7795
|
+
const TARGETS = ["darwin", "linux", "windows"].flatMap((os2) => [`${os2}-aarch64`, `${os2}-x86_64`]);
|
|
7796
|
+
const ARCHIVES = [".tar.gz", ".tgz", ".tar.bz2", ".tar.xz", ".zip"];
|
|
7797
|
+
const invalid = () => {
|
|
7798
|
+
throw new Error("invalid");
|
|
7799
|
+
};
|
|
7800
|
+
const plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7801
|
+
const str = (value, max) => typeof value === "string" && value && value.length <= max && !value.includes("\0") ? value : invalid();
|
|
7802
|
+
const optional = (value, max) => value === void 0 ? void 0 : str(value, max);
|
|
7803
|
+
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();
|
|
7804
|
+
function env(value) {
|
|
7805
|
+
if (value === void 0) return {};
|
|
7806
|
+
if (!plain(value)) return invalid();
|
|
7807
|
+
const entries = Object.entries(value);
|
|
7808
|
+
if (entries.length > 64 || entries.some(([k, v]) => !ENV_KEY.test(k) || typeof v !== "string" || v.length > 16384 || v.includes("\0"))) return invalid();
|
|
7809
|
+
return Object.fromEntries(entries);
|
|
7810
|
+
}
|
|
7811
|
+
function url(value) {
|
|
7812
|
+
const parsed = new URL(str(value, 4096));
|
|
7813
|
+
return ["https:", "http:"].includes(parsed.protocol) ? parsed.href : invalid();
|
|
7814
|
+
}
|
|
7815
|
+
function pkg(value) {
|
|
7816
|
+
if (value === void 0) return;
|
|
7817
|
+
if (!plain(value)) return invalid();
|
|
7818
|
+
const name = str(value.package, 512);
|
|
7819
|
+
if (name.startsWith("-")) return invalid();
|
|
7820
|
+
return { package: name, args: list(value.args), env: env(value.env) };
|
|
7821
|
+
}
|
|
7822
|
+
function binary(value) {
|
|
7823
|
+
if (value === void 0) return;
|
|
7824
|
+
if (!plain(value)) return invalid();
|
|
7825
|
+
const targets = {};
|
|
7826
|
+
for (const [key, t] of Object.entries(value)) {
|
|
7827
|
+
if (!plain(t)) return invalid();
|
|
7828
|
+
const sha256 = t.sha256 === void 0 ? void 0 : typeof t.sha256 === "string" && /^[a-f0-9]{64}$/i.test(t.sha256) ? t.sha256.toLowerCase() : invalid();
|
|
7829
|
+
const target = { archive: url(t.archive), cmd: str(t.cmd, 1024), args: list(t.args), env: env(t.env), sha256 };
|
|
7830
|
+
if (TARGETS.includes(key)) targets[key] = target;
|
|
7831
|
+
}
|
|
7832
|
+
return targets;
|
|
7833
|
+
}
|
|
7834
|
+
function entry(value) {
|
|
7835
|
+
if (!plain(value) || !plain(value.distribution)) return invalid();
|
|
7836
|
+
const id = str(value.id, 64), version = str(value.version, 64);
|
|
7837
|
+
if (!REGISTRY_ID.test(id) || !VERSION.test(version)) return invalid();
|
|
7838
|
+
if (typeof value.description !== "string" || value.description.length > 4e3) return invalid();
|
|
7839
|
+
const d = value.distribution;
|
|
7840
|
+
const distribution = { npx: pkg(d.npx), uvx: pkg(d.uvx), binary: binary(d.binary) };
|
|
7841
|
+
if (!distribution.npx && !distribution.uvx && !distribution.binary) return invalid();
|
|
7842
|
+
return {
|
|
7843
|
+
id,
|
|
7844
|
+
version,
|
|
7845
|
+
name: str(value.name, 200),
|
|
7846
|
+
description: value.description,
|
|
7847
|
+
repository: optional(value.repository, 2048),
|
|
7848
|
+
website: optional(value.website, 2048),
|
|
7849
|
+
license: optional(value.license, 200),
|
|
7850
|
+
distribution
|
|
7851
|
+
};
|
|
7852
|
+
}
|
|
7853
|
+
function parseRegistry(text2) {
|
|
7854
|
+
const value = JSON.parse(text2);
|
|
7855
|
+
if (!plain(value) || !Array.isArray(value.agents)) throw new Error("The ACP Registry response has no agents list");
|
|
7856
|
+
const agents = /* @__PURE__ */ new Map();
|
|
7857
|
+
for (const item of value.agents.slice(0, 2e3)) {
|
|
7858
|
+
try {
|
|
7859
|
+
const agent = entry(item);
|
|
7860
|
+
if (!agents.has(agent.id)) agents.set(agent.id, agent);
|
|
7861
|
+
} catch {
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7864
|
+
return [...agents.values()];
|
|
7865
|
+
}
|
|
7866
|
+
function platformTarget(platform = process.platform, arch = process.arch) {
|
|
7867
|
+
const os2 = { darwin: "darwin", linux: "linux", win32: "windows" }[platform];
|
|
7868
|
+
const cpu = { arm64: "aarch64", x64: "x86_64" }[arch];
|
|
7869
|
+
return os2 && cpu ? `${os2}-${cpu}` : void 0;
|
|
7870
|
+
}
|
|
7871
|
+
function pickDistribution(agent, target = platformTarget()) {
|
|
7872
|
+
const { npx, uvx, binary: binary2 } = agent.distribution;
|
|
7873
|
+
if (npx) return { kind: "npx", spec: npx };
|
|
7874
|
+
if (uvx) return { kind: "uvx", spec: uvx };
|
|
7875
|
+
const spec = target ? binary2?.[target] : void 0;
|
|
7876
|
+
if (spec) return { kind: "binary", spec };
|
|
7877
|
+
}
|
|
7878
|
+
async function* capped(body, max, what) {
|
|
7879
|
+
let length = 0;
|
|
7880
|
+
for await (const chunk of body) {
|
|
7881
|
+
length += chunk.length;
|
|
7882
|
+
if (length > max) throw new Error(`${what} exceeds ${max / MiB} MiB`);
|
|
7883
|
+
yield chunk;
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
const exists = (file) => fs.stat(file).then(() => true, () => false);
|
|
7887
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
7888
|
+
async function installedCommand(dir, cmd) {
|
|
7889
|
+
const root = await fs.realpath(dir);
|
|
7890
|
+
const inside = (file) => file.startsWith(root + path.sep);
|
|
7891
|
+
const escape = new RpcError("INVALID_PARAMS", `The agent command ${cmd} resolves outside its install directory`);
|
|
7892
|
+
const target = path.resolve(root, cmd);
|
|
7893
|
+
if (!inside(target)) throw escape;
|
|
7894
|
+
const real = await fs.realpath(target).catch(() => {
|
|
7895
|
+
throw new RpcError("INVALID_PARAMS", `The agent archive has no ${cmd}`);
|
|
7896
|
+
});
|
|
7897
|
+
if (!inside(real)) throw escape;
|
|
7898
|
+
const stat = await fs.stat(real);
|
|
7899
|
+
if (!stat.isFile()) throw new RpcError("INVALID_PARAMS", `The agent command ${cmd} is not a file`);
|
|
7900
|
+
if (process.platform !== "win32") await fs.chmod(real, stat.mode | 73);
|
|
7901
|
+
return real;
|
|
7902
|
+
}
|
|
7903
|
+
function untilAborted(promise, signal) {
|
|
7904
|
+
if (!signal) return promise;
|
|
7905
|
+
if (signal.aborted) return Promise.reject(new Error("Agent connection cancelled"));
|
|
7906
|
+
return new Promise((resolve, reject) => {
|
|
7907
|
+
const stop2 = () => reject(new Error("Agent connection cancelled"));
|
|
7908
|
+
signal.addEventListener("abort", stop2, { once: true });
|
|
7909
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", stop2));
|
|
7910
|
+
});
|
|
7911
|
+
}
|
|
7912
|
+
async function extract(archive, dest, format, signal) {
|
|
7913
|
+
const tar = ["tar", ["-xf", archive, "-C", dest]];
|
|
7914
|
+
const attempts = format !== ".zip" ? [tar] : [...process.platform === "darwin" ? [tar] : [], ["unzip", ["-q", archive, "-d", dest]]];
|
|
7915
|
+
const missing2 = [];
|
|
7916
|
+
let failure2;
|
|
7917
|
+
for (const [tool, args] of attempts) {
|
|
7918
|
+
await fs.rm(dest, { recursive: true, force: true });
|
|
7919
|
+
await fs.mkdir(dest);
|
|
7920
|
+
try {
|
|
7921
|
+
await run$1(tool, args, { signal, maxBuffer: MiB });
|
|
7922
|
+
return;
|
|
7923
|
+
} catch (error) {
|
|
7924
|
+
if (signal?.aborted) throw new Error("Agent connection cancelled");
|
|
7925
|
+
if (error.code === "ENOENT") missing2.push(tool);
|
|
7926
|
+
else failure2 = new Error(`${tool} could not extract the agent archive: ${String(error.stderr || message(error)).trim().slice(0, 500)}`);
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
throw failure2 ?? new RpcError("INVALID_PARAMS", `Install ${missing2.join(" or ")} to extract this agent archive`);
|
|
7930
|
+
}
|
|
7931
|
+
class ACPRegistry {
|
|
7932
|
+
constructor(dataDir, url2 = ACP_REGISTRY_URL, download = fetch, target = platformTarget()) {
|
|
7933
|
+
this.dataDir = dataDir;
|
|
7934
|
+
this.url = url2;
|
|
7935
|
+
this.download = download;
|
|
7936
|
+
this.target = target;
|
|
7937
|
+
this.installRoot = path.join(dataDir, "acp-agents");
|
|
7938
|
+
}
|
|
7939
|
+
dataDir;
|
|
7940
|
+
url;
|
|
7941
|
+
download;
|
|
7942
|
+
target;
|
|
7943
|
+
fetching;
|
|
7944
|
+
installs = /* @__PURE__ */ new Map();
|
|
7945
|
+
installRoot;
|
|
7946
|
+
get cacheFile() {
|
|
7947
|
+
return path.join(this.dataDir, "acp-registry.json");
|
|
7948
|
+
}
|
|
7949
|
+
checkUrl(value) {
|
|
7950
|
+
const parsed = new URL(value), registry = new URL(this.url);
|
|
7951
|
+
const loopback = (u) => u.protocol === "http:" && u.hostname === "127.0.0.1";
|
|
7952
|
+
if (parsed.protocol === "https:" || loopback(registry) && loopback(parsed)) return parsed.href;
|
|
7953
|
+
throw new Error(`ACP agent downloads require HTTPS (${parsed.origin})`);
|
|
7954
|
+
}
|
|
7955
|
+
async open(source2, signal, max, what) {
|
|
7956
|
+
let current = this.checkUrl(source2);
|
|
7957
|
+
for (let redirects = 0; ; redirects++) {
|
|
7958
|
+
const response = await this.download(current, { signal, redirect: "manual" });
|
|
7959
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
7960
|
+
await response.body?.cancel();
|
|
7961
|
+
const location = response.headers.get("location");
|
|
7962
|
+
if (!location) throw new Error(`${what} redirect has no location`);
|
|
7963
|
+
if (redirects >= 5) throw new Error(`${what} has too many redirects`);
|
|
7964
|
+
current = this.checkUrl(new URL(location, current).href);
|
|
7965
|
+
continue;
|
|
7966
|
+
}
|
|
7967
|
+
if (!response.ok || !response.body) {
|
|
7968
|
+
await response.body?.cancel();
|
|
7969
|
+
throw new Error(`${what} download failed (HTTP ${response.status})`);
|
|
7970
|
+
}
|
|
7971
|
+
if (Number(response.headers.get("content-length")) > max) {
|
|
7972
|
+
await response.body.cancel();
|
|
7973
|
+
throw new Error(`${what} exceeds ${max / MiB} MiB`);
|
|
7974
|
+
}
|
|
7975
|
+
return capped(response.body, max, what);
|
|
7976
|
+
}
|
|
7977
|
+
}
|
|
7978
|
+
async cached() {
|
|
7979
|
+
try {
|
|
7980
|
+
const value = JSON.parse(await fs.readFile(this.cacheFile, "utf8"));
|
|
7981
|
+
if (value?.url === this.url && typeof value.text === "string" && !Number.isNaN(Date.parse(value.fetchedAt)))
|
|
7982
|
+
return { agents: parseRegistry(value.text), fetchedAt: value.fetchedAt };
|
|
7983
|
+
} catch {
|
|
7984
|
+
}
|
|
7985
|
+
}
|
|
7986
|
+
async fetchRegistry() {
|
|
7987
|
+
const chunks = [];
|
|
7988
|
+
for await (const chunk of await this.open(this.url, AbortSignal.timeout(15e3), MAX_LISTING, "The ACP Registry")) chunks.push(chunk);
|
|
7989
|
+
const text2 = Buffer.concat(chunks).toString("utf8");
|
|
7990
|
+
const agents = parseRegistry(text2), fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7991
|
+
await fs.mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
7992
|
+
const temp = `${this.cacheFile}.${process.pid}.tmp`;
|
|
7993
|
+
await fs.writeFile(temp, JSON.stringify({ url: this.url, fetchedAt, text: text2 }), { mode: 384 });
|
|
7994
|
+
await fs.rename(temp, this.cacheFile);
|
|
7995
|
+
return { agents, fetchedAt };
|
|
7996
|
+
}
|
|
7997
|
+
async load(refresh = false) {
|
|
7998
|
+
const cached2 = await this.cached();
|
|
7999
|
+
const age = cached2 ? Date.now() - Date.parse(cached2.fetchedAt) : Infinity;
|
|
8000
|
+
if (cached2 && !refresh && age >= 0 && age < TTL) return cached2;
|
|
8001
|
+
try {
|
|
8002
|
+
this.fetching ??= this.fetchRegistry().finally(() => this.fetching = void 0);
|
|
8003
|
+
return await this.fetching;
|
|
8004
|
+
} catch (error) {
|
|
8005
|
+
const reason = `Could not reach the ACP Registry: ${message(error)}`;
|
|
8006
|
+
if (cached2) return { ...cached2, error: reason };
|
|
8007
|
+
throw new RpcError("UNAVAILABLE", reason);
|
|
8008
|
+
}
|
|
8009
|
+
}
|
|
8010
|
+
dir(agent) {
|
|
8011
|
+
return path.join(this.installRoot, agent.id, agent.version);
|
|
8012
|
+
}
|
|
8013
|
+
unavailable(picked) {
|
|
8014
|
+
if (!picked) return "No build for this runtime's platform";
|
|
8015
|
+
if (picked.kind !== "binary") return void 0;
|
|
8016
|
+
if (!picked.spec.sha256) return "The registry publishes no checksum for this build";
|
|
8017
|
+
const file = new URL(picked.spec.archive).pathname.toLowerCase();
|
|
8018
|
+
if (!ARCHIVES.some((ext) => file.endsWith(ext))) return "Oxbit cannot extract this build's archive format";
|
|
8019
|
+
}
|
|
8020
|
+
async listing(refresh = false) {
|
|
8021
|
+
const { agents, fetchedAt, error } = await this.load(refresh);
|
|
8022
|
+
const listed = await Promise.all(agents.map(async (agent) => {
|
|
8023
|
+
const picked = pickDistribution(agent, this.target), builtin = acpBuiltinForRegistry(agent.id)?.id;
|
|
8024
|
+
const { npx, uvx } = agent.distribution;
|
|
8025
|
+
const distribution = picked?.kind ?? (npx ? "npx" : uvx ? "uvx" : "binary");
|
|
8026
|
+
const reason = this.unavailable(picked);
|
|
8027
|
+
return {
|
|
8028
|
+
id: agent.id,
|
|
8029
|
+
name: agent.name,
|
|
8030
|
+
version: agent.version,
|
|
8031
|
+
description: agent.description,
|
|
8032
|
+
...agent.repository ? { repository: agent.repository } : {},
|
|
8033
|
+
...agent.website ? { website: agent.website } : {},
|
|
8034
|
+
...agent.license ? { license: agent.license } : {},
|
|
8035
|
+
distribution,
|
|
8036
|
+
available: !reason,
|
|
8037
|
+
...reason ? { reason } : {},
|
|
8038
|
+
...picked?.kind === "binary" ? { installed: await exists(this.dir(agent)) } : {},
|
|
8039
|
+
...builtin ? { builtin } : {}
|
|
8040
|
+
};
|
|
8041
|
+
}));
|
|
8042
|
+
return { agents: listed, fetchedAt, ...error ? { error } : {} };
|
|
8043
|
+
}
|
|
8044
|
+
async resolve(id, version, signal) {
|
|
8045
|
+
const agent = (await this.load()).agents.find((a) => a.id === id);
|
|
8046
|
+
if (!agent) throw new RpcError("INVALID_PARAMS", "Unknown agent");
|
|
8047
|
+
if (version !== void 0 && version !== agent.version)
|
|
8048
|
+
throw new RpcError("INVALID_PARAMS", `${agent.name} ${version} is no longer in the ACP Registry; refresh the agent list`);
|
|
8049
|
+
const picked = pickDistribution(agent, this.target);
|
|
8050
|
+
if (!picked) throw new RpcError("INVALID_PARAMS", `${agent.name} has no ACP Registry build for ${this.target ?? `${process.platform}-${process.arch}`}`);
|
|
8051
|
+
const base = { name: agent.name, env: picked.spec.env };
|
|
8052
|
+
if (picked.kind !== "binary") {
|
|
8053
|
+
const { package: name, args } = picked.spec;
|
|
8054
|
+
return { ...base, command: picked.kind, args: picked.kind === "npx" ? ["-y", name, ...args] : [name, ...args] };
|
|
8055
|
+
}
|
|
8056
|
+
return { ...base, command: await this.install(agent, picked.spec, signal), args: picked.spec.args };
|
|
8057
|
+
}
|
|
8058
|
+
/** Concurrent callers share one download, which stops only after every caller has cancelled. */
|
|
8059
|
+
async install(agent, spec, signal) {
|
|
8060
|
+
const key = `${agent.id}@${agent.version}`;
|
|
8061
|
+
let shared = this.installs.get(key);
|
|
8062
|
+
if (!shared) {
|
|
8063
|
+
const abort = new AbortController();
|
|
8064
|
+
const promise = this.installOnce(agent, spec, abort.signal).finally(() => this.installs.delete(key));
|
|
8065
|
+
promise.catch(() => {
|
|
8066
|
+
});
|
|
8067
|
+
shared = { promise, abort, waiters: 0 };
|
|
8068
|
+
this.installs.set(key, shared);
|
|
8069
|
+
}
|
|
8070
|
+
const install = shared;
|
|
8071
|
+
install.waiters++;
|
|
8072
|
+
try {
|
|
8073
|
+
return await untilAborted(install.promise, signal);
|
|
8074
|
+
} finally {
|
|
8075
|
+
if (--install.waiters === 0 && signal?.aborted) install.abort.abort();
|
|
8076
|
+
}
|
|
8077
|
+
}
|
|
8078
|
+
async installOnce(agent, spec, signal) {
|
|
8079
|
+
const dir = this.dir(agent);
|
|
8080
|
+
if (await exists(dir)) return installedCommand(dir, spec.cmd);
|
|
8081
|
+
if (!spec.sha256) throw new RpcError("INVALID_PARAMS", `${agent.name} has no sha256 checksum in the ACP Registry, so Oxbit does not install it`);
|
|
8082
|
+
const file = new URL(spec.archive).pathname.toLowerCase();
|
|
8083
|
+
const format = ARCHIVES.find((ext) => file.endsWith(ext));
|
|
8084
|
+
if (!format) throw new RpcError("INVALID_PARAMS", `Oxbit cannot extract ${path.posix.basename(file)}; supported archives are ${ARCHIVES.join(", ")}`);
|
|
8085
|
+
await fs.mkdir(path.dirname(dir), { recursive: true, mode: 448 });
|
|
8086
|
+
const temp = await fs.mkdtemp(path.join(path.dirname(dir), `.${agent.version}-`));
|
|
8087
|
+
try {
|
|
8088
|
+
const archive = path.join(temp, `archive${format}`), files = path.join(temp, "files");
|
|
8089
|
+
const hash2 = createHash("sha256");
|
|
8090
|
+
const body = await this.open(spec.archive, signal, MAX_ARCHIVE, `The ${agent.name} archive`);
|
|
8091
|
+
await pipeline(async function* () {
|
|
8092
|
+
for await (const chunk of body) {
|
|
8093
|
+
hash2.update(chunk);
|
|
8094
|
+
yield chunk;
|
|
8095
|
+
}
|
|
8096
|
+
}, createWriteStream(archive, { mode: 384 }), { signal });
|
|
8097
|
+
if (hash2.digest("hex") !== spec.sha256)
|
|
8098
|
+
throw new RpcError("INVALID_PARAMS", `The ${agent.name} archive does not match its sha256 checksum`);
|
|
8099
|
+
await extract(archive, files, format, signal);
|
|
8100
|
+
await installedCommand(files, spec.cmd);
|
|
8101
|
+
await fs.rename(files, dir).catch(async (error) => {
|
|
8102
|
+
if (!await exists(dir)) throw error;
|
|
8103
|
+
});
|
|
8104
|
+
return await installedCommand(dir, spec.cmd);
|
|
8105
|
+
} finally {
|
|
8106
|
+
await fs.rm(temp, { recursive: true, force: true });
|
|
8107
|
+
}
|
|
8108
|
+
}
|
|
8109
|
+
}
|
|
7615
8110
|
const MAX_BYTES = 1024 * 1024;
|
|
8111
|
+
const IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
8112
|
+
const MAX_IMAGES = 4;
|
|
8113
|
+
const MAX_IMAGE_DATA = Math.ceil(5 * 1024 * 1024 / 3) * 4;
|
|
8114
|
+
const MAX_QUEUED_IMAGE_DATA = 16 * 1024 * 1024;
|
|
8115
|
+
const withoutImageData = (key, value) => key === "data" ? void 0 : value;
|
|
7616
8116
|
const EDITOR_UNSUPPORTED = "The connected editor does not support this tool; update Oxbit";
|
|
7617
8117
|
function strings(value) {
|
|
7618
8118
|
if (!Array.isArray(value) || value.length > 128 || value.some(
|
|
@@ -7626,14 +8126,19 @@ function strings(value) {
|
|
|
7626
8126
|
}
|
|
7627
8127
|
class AgentACP {
|
|
7628
8128
|
constructor(files, emit, stateChanged = () => {
|
|
8129
|
+
}, acpRegistry, statusChanged = () => {
|
|
7629
8130
|
}) {
|
|
7630
8131
|
this.files = files;
|
|
7631
8132
|
this.emit = emit;
|
|
7632
8133
|
this.stateChanged = stateChanged;
|
|
8134
|
+
this.acpRegistry = acpRegistry;
|
|
8135
|
+
this.statusChanged = statusChanged;
|
|
7633
8136
|
}
|
|
7634
8137
|
files;
|
|
7635
8138
|
emit;
|
|
7636
8139
|
stateChanged;
|
|
8140
|
+
acpRegistry;
|
|
8141
|
+
statusChanged;
|
|
7637
8142
|
agents = /* @__PURE__ */ new Map();
|
|
7638
8143
|
mcp = new AgentMCP((id, name, args) => this.tool(id, name, args));
|
|
7639
8144
|
get(owner, id) {
|
|
@@ -7642,8 +8147,8 @@ class AgentACP {
|
|
|
7642
8147
|
throw new RpcError("NOT_FOUND", "Agent connection has ended");
|
|
7643
8148
|
return agent;
|
|
7644
8149
|
}
|
|
7645
|
-
send(agent,
|
|
7646
|
-
const data = JSON.stringify({ jsonrpc: "2.0", ...
|
|
8150
|
+
send(agent, message2) {
|
|
8151
|
+
const data = JSON.stringify({ jsonrpc: "2.0", ...message2 }) + "\n";
|
|
7647
8152
|
if (agent.stopped || !agent.child.stdin.writable)
|
|
7648
8153
|
throw new Error("Agent process has ended");
|
|
7649
8154
|
if (Buffer.byteLength(data) > 2 * MAX_BYTES || agent.child.stdin.writableLength > MAX_BYTES)
|
|
@@ -7672,9 +8177,9 @@ class AgentACP {
|
|
|
7672
8177
|
publish(agent, event, params) {
|
|
7673
8178
|
const payload = { id: agent.connection.id, ...params, seq: ++agent.eventSequence };
|
|
7674
8179
|
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(
|
|
8180
|
+
const entry2 = JSON.parse(JSON.stringify({ name: event, params: payload }));
|
|
8181
|
+
agent.events.push(entry2);
|
|
8182
|
+
agent.eventBytes += Buffer.byteLength(JSON.stringify(entry2));
|
|
7678
8183
|
while (agent.events.length > 2e3 || agent.eventBytes > 2 * MAX_BYTES) {
|
|
7679
8184
|
agent.eventBytes -= Buffer.byteLength(JSON.stringify(agent.events.shift()));
|
|
7680
8185
|
agent.truncated = true;
|
|
@@ -7683,6 +8188,20 @@ class AgentACP {
|
|
|
7683
8188
|
if (agent.client) this.emit(agent.client, event, payload);
|
|
7684
8189
|
this.stateChanged();
|
|
7685
8190
|
}
|
|
8191
|
+
report(agent, status2) {
|
|
8192
|
+
try {
|
|
8193
|
+
this.statusChanged({
|
|
8194
|
+
owner: agent.owner,
|
|
8195
|
+
agent: agent.connection.name ?? agent.connection.provider,
|
|
8196
|
+
status: status2,
|
|
8197
|
+
pending: agent.approvals.size,
|
|
8198
|
+
startedAt: agent.startedAt
|
|
8199
|
+
});
|
|
8200
|
+
} catch (error) {
|
|
8201
|
+
process.stderr.write(`Oxbit agent status listener failed: ${String(error)}
|
|
8202
|
+
`);
|
|
8203
|
+
}
|
|
8204
|
+
}
|
|
7686
8205
|
list(owner) {
|
|
7687
8206
|
return { sessions: [...this.agents.values()].filter((a) => a.owner === owner && !a.stopped).map((a) => ({
|
|
7688
8207
|
connection: a.connection,
|
|
@@ -7742,27 +8261,15 @@ class AgentACP {
|
|
|
7742
8261
|
return [...this.agents.values()].some((a) => this.working(a));
|
|
7743
8262
|
}
|
|
7744
8263
|
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
8264
|
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]);
|
|
8265
|
+
this.slot(owner, false);
|
|
8266
|
+
const resolved = await this.resolveLaunch(launch, signal);
|
|
8267
|
+
if (signal?.aborted) throw new Error("Agent connection cancelled");
|
|
8268
|
+
this.slot(owner, true);
|
|
8269
|
+
const { command, args } = resolved;
|
|
7763
8270
|
const child = spawn(command, args, {
|
|
7764
8271
|
cwd: this.files.root,
|
|
7765
|
-
env: process.env,
|
|
8272
|
+
env: resolved.env && Object.keys(resolved.env).length ? { ...process.env, ...resolved.env } : process.env,
|
|
7766
8273
|
stdio: "pipe",
|
|
7767
8274
|
detached: process.platform !== "win32",
|
|
7768
8275
|
windowsHide: true
|
|
@@ -7775,7 +8282,8 @@ class AgentACP {
|
|
|
7775
8282
|
child,
|
|
7776
8283
|
connection: {
|
|
7777
8284
|
id: randomUUID(),
|
|
7778
|
-
provider:
|
|
8285
|
+
provider: resolved.provider,
|
|
8286
|
+
name: resolved.name,
|
|
7779
8287
|
root: this.files.root,
|
|
7780
8288
|
authMethods: []
|
|
7781
8289
|
},
|
|
@@ -7829,10 +8337,10 @@ class AgentACP {
|
|
|
7829
8337
|
return;
|
|
7830
8338
|
}
|
|
7831
8339
|
try {
|
|
7832
|
-
const
|
|
7833
|
-
if (
|
|
8340
|
+
const message2 = JSON.parse(line);
|
|
8341
|
+
if (message2?.jsonrpc !== "2.0")
|
|
7834
8342
|
throw new Error("Invalid ACP envelope");
|
|
7835
|
-
void this.message(agent,
|
|
8343
|
+
void this.message(agent, message2).catch(
|
|
7836
8344
|
(error) => this.stopAgent(agent, String(error))
|
|
7837
8345
|
);
|
|
7838
8346
|
} catch {
|
|
@@ -7852,7 +8360,7 @@ class AgentACP {
|
|
|
7852
8360
|
clientCapabilities: {
|
|
7853
8361
|
fs: { readTextFile: true, writeTextFile: true },
|
|
7854
8362
|
terminal: true,
|
|
7855
|
-
...
|
|
8363
|
+
...resolved.provider === "codex" ? { subagents: {} } : {}
|
|
7856
8364
|
},
|
|
7857
8365
|
clientInfo: { name: "oxbit", title: "Oxbit", version: runtimeVersion() }
|
|
7858
8366
|
});
|
|
@@ -7869,6 +8377,43 @@ class AgentACP {
|
|
|
7869
8377
|
signal?.removeEventListener("abort", abort);
|
|
7870
8378
|
}
|
|
7871
8379
|
}
|
|
8380
|
+
slot(owner, evict) {
|
|
8381
|
+
const owned = [...this.agents.values()].filter((a) => a.owner === owner);
|
|
8382
|
+
if (owned.length < 3) return;
|
|
8383
|
+
const idle = owned.find((a) => !a.client && !this.working(a));
|
|
8384
|
+
if (!idle)
|
|
8385
|
+
throw new RpcError("BUSY", "3 agents are running for this device; stop one in Runtime sessions");
|
|
8386
|
+
if (evict) this.stopAgent(idle, "Stopped to start another agent");
|
|
8387
|
+
}
|
|
8388
|
+
async resolveLaunch(launch, signal) {
|
|
8389
|
+
const provider = launch?.provider;
|
|
8390
|
+
if (typeof provider !== "string" || !provider || provider.length > 64)
|
|
8391
|
+
throw new RpcError("INVALID_PARAMS", "Unknown agent");
|
|
8392
|
+
if (launch.registry !== void 0) {
|
|
8393
|
+
const { id, version } = launch.registry ?? {};
|
|
8394
|
+
if (typeof id !== "string" || !REGISTRY_ID.test(id) || version !== void 0 && typeof version !== "string")
|
|
8395
|
+
throw new RpcError("INVALID_PARAMS", "Invalid ACP Registry agent");
|
|
8396
|
+
const builtin = acpBuiltinForRegistry(id);
|
|
8397
|
+
if (builtin)
|
|
8398
|
+
return { provider: builtin.id, name: builtin.name, command: builtin.command, args: [...builtin.args] };
|
|
8399
|
+
if (!this.acpRegistry)
|
|
8400
|
+
throw new RpcError("INVALID_PARAMS", "This runtime cannot launch ACP Registry agents");
|
|
8401
|
+
return { provider: id, ...await this.acpRegistry.resolve(id, version, signal) };
|
|
8402
|
+
}
|
|
8403
|
+
const preset2 = acpPreset(provider);
|
|
8404
|
+
if (!preset2 && (typeof launch.command !== "string" || !launch.command.trim()))
|
|
8405
|
+
throw new RpcError("INVALID_PARAMS", provider === ACP_CUSTOM_PROVIDER ? "Enter a command for the custom agent" : "Unknown agent");
|
|
8406
|
+
const command = requireString({ command: launch.command ?? preset2.command }, "command");
|
|
8407
|
+
if (!command.trim() || command.includes("\0"))
|
|
8408
|
+
throw new RpcError("INVALID_PARAMS", "Invalid agent executable");
|
|
8409
|
+
const custom = typeof launch.name === "string" ? launch.name.trim().slice(0, 80) : "";
|
|
8410
|
+
return {
|
|
8411
|
+
provider,
|
|
8412
|
+
name: preset2?.name ?? (custom || "Custom agent"),
|
|
8413
|
+
command,
|
|
8414
|
+
args: strings(launch.args ?? (preset2 ? [...preset2.args] : []))
|
|
8415
|
+
};
|
|
8416
|
+
}
|
|
7872
8417
|
async call(owner, id, method, params = {}, signal) {
|
|
7873
8418
|
const agent = this.get(owner, id);
|
|
7874
8419
|
if (agent.busy)
|
|
@@ -7912,11 +8457,16 @@ class AgentACP {
|
|
|
7912
8457
|
agent.busy = false;
|
|
7913
8458
|
}
|
|
7914
8459
|
}
|
|
7915
|
-
if (["session/new", "session/load", "session/resume"].includes(method)) {
|
|
8460
|
+
if (["session/new", "session/load", "session/resume", "session/fork"].includes(method)) {
|
|
7916
8461
|
if (method === "session/load" && !agent.connection.capabilities?.loadSession)
|
|
7917
8462
|
throw new Error("This agent does not support loading conversations");
|
|
7918
8463
|
if (method === "session/resume" && !agent.connection.capabilities?.sessionCapabilities?.resume)
|
|
7919
8464
|
throw new Error("This agent does not support resuming conversations");
|
|
8465
|
+
if (method === "session/fork" && !agent.connection.capabilities?.sessionCapabilities?.fork)
|
|
8466
|
+
throw new Error("This agent does not support forking conversations");
|
|
8467
|
+
const source2 = agent.connection.sessionId;
|
|
8468
|
+
if (method === "session/fork" && !source2)
|
|
8469
|
+
throw new Error("Start a conversation first");
|
|
7920
8470
|
if (agent.subagents?.activeCount)
|
|
7921
8471
|
throw new Error(
|
|
7922
8472
|
"Wait for active subagents or disconnect before changing conversations"
|
|
@@ -7928,7 +8478,7 @@ class AgentACP {
|
|
|
7928
8478
|
const previousBytes = agent.eventBytes;
|
|
7929
8479
|
const previousTitle = agent.title;
|
|
7930
8480
|
const previousTruncated = agent.truncated;
|
|
7931
|
-
const target = method === "session/new" ? void 0 : requireString(params, "sessionId");
|
|
8481
|
+
const target = method === "session/new" || method === "session/fork" ? void 0 : requireString(params, "sessionId");
|
|
7932
8482
|
if (agent.approvals.size)
|
|
7933
8483
|
throw new Error(
|
|
7934
8484
|
"Resolve pending requests before changing conversations"
|
|
@@ -7949,7 +8499,7 @@ class AgentACP {
|
|
|
7949
8499
|
const result = await this.request(agent, method, {
|
|
7950
8500
|
cwd: this.files.root,
|
|
7951
8501
|
mcpServers: [await this.mcp.server(agent.connection.id, !!agent.connection.capabilities?.mcpCapabilities?.http)],
|
|
7952
|
-
...target ? { sessionId: target } : {}
|
|
8502
|
+
...target ? { sessionId: target } : method === "session/fork" ? { sessionId: source2 } : {}
|
|
7953
8503
|
});
|
|
7954
8504
|
const sessionId2 = target ?? result?.sessionId;
|
|
7955
8505
|
if (typeof sessionId2 !== "string" || !sessionId2)
|
|
@@ -8017,41 +8567,22 @@ class AgentACP {
|
|
|
8017
8567
|
const text2 = requireString(params, "text", MAX_BYTES);
|
|
8018
8568
|
if (!text2.trim()) throw new Error("Enter a message");
|
|
8019
8569
|
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)
|
|
8570
|
+
if (params.context !== void 0) prompt.push(...await this.contextBlocks(agent, params.context));
|
|
8571
|
+
if (Buffer.byteLength(JSON.stringify(prompt.filter((block) => block.type !== "image"))) > MAX_BYTES)
|
|
8047
8572
|
throw new Error("The message and attachments are too large");
|
|
8048
8573
|
if (agent.busy || agent.stopped || agent.connection.sessionId !== sessionId)
|
|
8049
8574
|
throw new Error("Agent connection is busy or ended");
|
|
8050
8575
|
const messageId = params.messageId === void 0 ? randomUUID() : requireString(params, "messageId", 256);
|
|
8051
8576
|
agent.busy = true;
|
|
8052
8577
|
agent.turn++;
|
|
8578
|
+
agent.startedAt = Date.now();
|
|
8053
8579
|
if (agent.title === "New conversation") agent.title = text2.slice(0, 100);
|
|
8054
|
-
this.publish(agent, "acp.turnStarted", {
|
|
8580
|
+
this.publish(agent, "acp.turnStarted", {
|
|
8581
|
+
messageId,
|
|
8582
|
+
text: text2,
|
|
8583
|
+
context: JSON.parse(JSON.stringify(params.context ?? [], withoutImageData))
|
|
8584
|
+
});
|
|
8585
|
+
this.report(agent, "working");
|
|
8055
8586
|
const abort = () => this.stopAgent(agent, "Agent request interrupted");
|
|
8056
8587
|
signal?.addEventListener("abort", abort, { once: true });
|
|
8057
8588
|
let stopReason;
|
|
@@ -8076,11 +8607,55 @@ ${content}`
|
|
|
8076
8607
|
signal?.removeEventListener("abort", abort);
|
|
8077
8608
|
if (!agent.stopped) {
|
|
8078
8609
|
this.publish(agent, "acp.turnEnded", { stopReason, error: failure2 });
|
|
8610
|
+
if (failure2 !== void 0) this.report(agent, "failed");
|
|
8611
|
+
else if (stopReason !== "cancelled") this.report(agent, "finished");
|
|
8079
8612
|
this.publishQueue(agent);
|
|
8080
8613
|
queueMicrotask(() => this.runQueue(agent));
|
|
8081
8614
|
}
|
|
8082
8615
|
}
|
|
8083
8616
|
}
|
|
8617
|
+
/** Validates attached context and converts it into ACP prompt content blocks. */
|
|
8618
|
+
async contextBlocks(agent, context) {
|
|
8619
|
+
if (context === void 0) return [];
|
|
8620
|
+
if (!Array.isArray(context) || context.length > 8)
|
|
8621
|
+
throw new Error("Attach up to eight context items");
|
|
8622
|
+
if (context.filter((item) => item?.kind === "image").length > MAX_IMAGES)
|
|
8623
|
+
throw new Error(`Attach up to ${MAX_IMAGES} images per message`);
|
|
8624
|
+
const blocks = [];
|
|
8625
|
+
for (const item of context) {
|
|
8626
|
+
if (item?.kind === "image") {
|
|
8627
|
+
if (!agent.connection.capabilities?.promptCapabilities?.image)
|
|
8628
|
+
throw new RpcError("INVALID_PARAMS", "This agent does not accept images");
|
|
8629
|
+
const mimeType = requireString(item, "mimeType", 64);
|
|
8630
|
+
const data = requireString(item, "data", MAX_IMAGE_DATA);
|
|
8631
|
+
if (!IMAGE_TYPES.includes(mimeType) || !data || !/^[A-Za-z0-9+/]+={0,2}$/.test(data))
|
|
8632
|
+
throw new RpcError("INVALID_PARAMS", "Invalid image attachment");
|
|
8633
|
+
blocks.push({ type: "image", mimeType, data });
|
|
8634
|
+
continue;
|
|
8635
|
+
}
|
|
8636
|
+
const content = requireString(item, "text", 2e5);
|
|
8637
|
+
const relative = requireString(item, "path");
|
|
8638
|
+
const absolute = await this.files.resolve(relative);
|
|
8639
|
+
const label = typeof item.label === "string" ? item.label.slice(0, 1024) : relative;
|
|
8640
|
+
if (agent.connection.capabilities?.promptCapabilities?.embeddedContext)
|
|
8641
|
+
blocks.push({
|
|
8642
|
+
type: "resource",
|
|
8643
|
+
resource: {
|
|
8644
|
+
uri: pathToFileURL(absolute).href + (Number.isSafeInteger(item.line) ? `#L${item.line}` : ""),
|
|
8645
|
+
mimeType: "text/plain",
|
|
8646
|
+
text: `Editor snapshot: ${label}
|
|
8647
|
+
${content}`
|
|
8648
|
+
}
|
|
8649
|
+
});
|
|
8650
|
+
else
|
|
8651
|
+
blocks.push({
|
|
8652
|
+
type: "text",
|
|
8653
|
+
text: `Attached editor context: ${label}
|
|
8654
|
+
${content}`
|
|
8655
|
+
});
|
|
8656
|
+
}
|
|
8657
|
+
return blocks;
|
|
8658
|
+
}
|
|
8084
8659
|
publishQueue(agent) {
|
|
8085
8660
|
this.publish(agent, "acp.queue", { queue: agent.queue, paused: agent.queuePaused });
|
|
8086
8661
|
return { queue: [...agent.queue], paused: agent.queuePaused };
|
|
@@ -8091,18 +8666,13 @@ ${content}`
|
|
|
8091
8666
|
const text2 = requireString(params, "text", MAX_BYTES).trim();
|
|
8092
8667
|
if (!text2) throw new Error("Enter a message");
|
|
8093
8668
|
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
|
-
}
|
|
8669
|
+
if (params.context !== void 0) await this.contextBlocks(agent, params.context);
|
|
8102
8670
|
if (agent.stopped) throw new Error("Agent connection has ended");
|
|
8103
8671
|
if (agent.queue.some((p) => p.id === messageId)) return this.publishQueue(agent);
|
|
8104
8672
|
const prompt = { id: messageId, text: text2, ...params.context ? { context: structuredClone(params.context) } : {} };
|
|
8105
|
-
|
|
8673
|
+
const queued = [...agent.queue, prompt];
|
|
8674
|
+
const imageData = queued.flatMap((p) => p.context ?? []).reduce((n, item) => n + (item.data?.length ?? 0), 0);
|
|
8675
|
+
if (agent.queue.length >= 16 || imageData > MAX_QUEUED_IMAGE_DATA || Buffer.byteLength(JSON.stringify(queued, withoutImageData)) > MAX_BYTES)
|
|
8106
8676
|
throw new Error("Queued messages exceed the limit; remove a message first");
|
|
8107
8677
|
if (interrupt) {
|
|
8108
8678
|
agent.queue.unshift(prompt);
|
|
@@ -8121,6 +8691,14 @@ ${content}`
|
|
|
8121
8691
|
this.publishQueue(agent);
|
|
8122
8692
|
return { prompt };
|
|
8123
8693
|
}
|
|
8694
|
+
moveQueued(owner, id, promptId, index) {
|
|
8695
|
+
const agent = this.get(owner, id);
|
|
8696
|
+
const from = agent.queue.findIndex((p) => p.id === promptId);
|
|
8697
|
+
if (from < 0) throw new Error("The queued message has already started or was removed");
|
|
8698
|
+
const [prompt] = agent.queue.splice(from, 1);
|
|
8699
|
+
agent.queue.splice(Math.max(0, Math.min(index, agent.queue.length)), 0, prompt);
|
|
8700
|
+
return this.publishQueue(agent);
|
|
8701
|
+
}
|
|
8124
8702
|
resumeQueue(owner, id) {
|
|
8125
8703
|
const agent = this.get(owner, id);
|
|
8126
8704
|
agent.queuePaused = false;
|
|
@@ -8282,6 +8860,7 @@ ${content}`
|
|
|
8282
8860
|
if (method !== "fs/read_text_file" && !method.startsWith("oxbit/"))
|
|
8283
8861
|
registry.waiting(sessionId, requestId, true, params.toolCallId ?? params.toolCall?.toolCallId);
|
|
8284
8862
|
this.publish(agent, "acp.request", this.requestEvent(agent, requestId, request));
|
|
8863
|
+
if (method === "session/request_permission") this.report(agent, "waiting");
|
|
8285
8864
|
});
|
|
8286
8865
|
}
|
|
8287
8866
|
async tool(id, name, args) {
|
|
@@ -8351,10 +8930,10 @@ ${content}`
|
|
|
8351
8930
|
break;
|
|
8352
8931
|
case "oxbit_update_plan": {
|
|
8353
8932
|
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:
|
|
8933
|
+
const entries = args.entries.map((entry2) => {
|
|
8934
|
+
const content = requireString(entry2, "content", 2e3);
|
|
8935
|
+
if (!["pending", "in_progress", "completed"].includes(entry2.status)) throw new Error("Invalid plan status");
|
|
8936
|
+
return { content, status: entry2.status, ...typeof entry2.priority === "string" ? { priority: entry2.priority.slice(0, 40) } : {} };
|
|
8358
8937
|
});
|
|
8359
8938
|
publish({ sessionUpdate: "plan", entries });
|
|
8360
8939
|
result = { entries };
|
|
@@ -8370,52 +8949,52 @@ ${content}`
|
|
|
8370
8949
|
throw error;
|
|
8371
8950
|
}
|
|
8372
8951
|
}
|
|
8373
|
-
async message(agent,
|
|
8952
|
+
async message(agent, message2) {
|
|
8374
8953
|
if (agent.stopped) return;
|
|
8375
|
-
if (!
|
|
8376
|
-
const pending = agent.pending.get(
|
|
8954
|
+
if (!message2.method) {
|
|
8955
|
+
const pending = agent.pending.get(message2.id);
|
|
8377
8956
|
if (!pending) return;
|
|
8378
8957
|
clearTimeout(pending.timer);
|
|
8379
|
-
agent.pending.delete(
|
|
8380
|
-
if (
|
|
8958
|
+
agent.pending.delete(message2.id);
|
|
8959
|
+
if (message2.error)
|
|
8381
8960
|
pending.reject(
|
|
8382
8961
|
new RpcError(
|
|
8383
|
-
String(
|
|
8384
|
-
|
|
8385
|
-
|
|
8962
|
+
String(message2.error.code),
|
|
8963
|
+
message2.error.message ?? "ACP request failed",
|
|
8964
|
+
message2.error.data
|
|
8386
8965
|
)
|
|
8387
8966
|
);
|
|
8388
|
-
else pending.resolve(
|
|
8967
|
+
else pending.resolve(message2.result);
|
|
8389
8968
|
return;
|
|
8390
8969
|
}
|
|
8391
|
-
const params =
|
|
8392
|
-
if (
|
|
8393
|
-
if (
|
|
8970
|
+
const params = message2.params ?? {};
|
|
8971
|
+
if (message2.id === void 0) {
|
|
8972
|
+
if (message2.method === "session/update" && agent.newSessionUpdates) {
|
|
8394
8973
|
if (agent.newSessionUpdates.length >= 100)
|
|
8395
8974
|
throw new Error("Too many updates while creating a session");
|
|
8396
8975
|
agent.newSessionUpdates.push(params);
|
|
8397
8976
|
return;
|
|
8398
8977
|
}
|
|
8399
|
-
if (
|
|
8978
|
+
if (message2.method === "session/update")
|
|
8400
8979
|
this.routeUpdate(agent, params.sessionId, params.update);
|
|
8401
|
-
else if (
|
|
8980
|
+
else if (message2.method.startsWith("cursor/") && agent.connection.provider === "cursor")
|
|
8402
8981
|
this.routeUpdate(
|
|
8403
8982
|
agent,
|
|
8404
8983
|
params.sessionId ?? agent.connection.sessionId,
|
|
8405
|
-
{ ...params, sessionUpdate:
|
|
8984
|
+
{ ...params, sessionUpdate: message2.method }
|
|
8406
8985
|
);
|
|
8407
8986
|
return;
|
|
8408
8987
|
}
|
|
8409
8988
|
try {
|
|
8410
8989
|
const registry = agent.subagents;
|
|
8411
|
-
const cursorRequest =
|
|
8990
|
+
const cursorRequest = message2.method.startsWith("cursor/") && agent.connection.provider === "cursor";
|
|
8412
8991
|
const sessionId = params.sessionId ?? (cursorRequest ? agent.connection.sessionId : void 0);
|
|
8413
8992
|
if (!registry?.acceptsRequest(sessionId))
|
|
8414
8993
|
throw new Error("Unknown ACP session");
|
|
8415
8994
|
params.sessionId = sessionId;
|
|
8416
|
-
if (
|
|
8417
|
-
const result2 = await this.terminal(agent,
|
|
8418
|
-
this.send(agent, { id:
|
|
8995
|
+
if (message2.method.startsWith("terminal/")) {
|
|
8996
|
+
const result2 = await this.terminal(agent, message2.method, params);
|
|
8997
|
+
this.send(agent, { id: message2.id, result: result2 });
|
|
8419
8998
|
return;
|
|
8420
8999
|
}
|
|
8421
9000
|
if (![
|
|
@@ -8424,19 +9003,19 @@ ${content}`
|
|
|
8424
9003
|
"session/request_permission",
|
|
8425
9004
|
"cursor/ask_question",
|
|
8426
9005
|
"cursor/create_plan"
|
|
8427
|
-
].includes(
|
|
9006
|
+
].includes(message2.method)) {
|
|
8428
9007
|
this.send(agent, {
|
|
8429
|
-
id:
|
|
9008
|
+
id: message2.id,
|
|
8430
9009
|
error: { code: -32601, message: "Client method is not supported" }
|
|
8431
9010
|
});
|
|
8432
9011
|
return;
|
|
8433
9012
|
}
|
|
8434
|
-
const result = await this.clientRequest(agent,
|
|
8435
|
-
if (!agent.stopped) this.send(agent, { id:
|
|
9013
|
+
const result = await this.clientRequest(agent, message2.method, params);
|
|
9014
|
+
if (!agent.stopped) this.send(agent, { id: message2.id, result });
|
|
8436
9015
|
} catch (error) {
|
|
8437
9016
|
if (!agent.stopped)
|
|
8438
9017
|
this.send(agent, {
|
|
8439
|
-
id:
|
|
9018
|
+
id: message2.id,
|
|
8440
9019
|
error: { code: -32603, message: String(error) }
|
|
8441
9020
|
});
|
|
8442
9021
|
}
|
|
@@ -8472,15 +9051,15 @@ ${content}`
|
|
|
8472
9051
|
const command = requireString(params, "command");
|
|
8473
9052
|
if (!command || command.includes("\0"))
|
|
8474
9053
|
throw new Error("Invalid terminal command");
|
|
8475
|
-
const
|
|
9054
|
+
const env2 = { ...process.env };
|
|
8476
9055
|
if (params.env !== void 0) {
|
|
8477
9056
|
if (!Array.isArray(params.env) || params.env.length > 128)
|
|
8478
9057
|
throw new Error("Invalid terminal environment");
|
|
8479
|
-
for (const
|
|
8480
|
-
const name = requireString(
|
|
9058
|
+
for (const entry2 of params.env) {
|
|
9059
|
+
const name = requireString(entry2, "name"), value = requireString(entry2, "value", 16384);
|
|
8481
9060
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) || value.includes("\0"))
|
|
8482
9061
|
throw new Error("Invalid environment variable");
|
|
8483
|
-
|
|
9062
|
+
env2[name] = value;
|
|
8484
9063
|
}
|
|
8485
9064
|
}
|
|
8486
9065
|
const limit2 = params.outputByteLimit ?? MAX_BYTES;
|
|
@@ -8492,7 +9071,7 @@ ${content}`
|
|
|
8492
9071
|
throw new Error("Agent connection has ended");
|
|
8493
9072
|
const child = spawn(command, strings(params.args ?? []), {
|
|
8494
9073
|
cwd,
|
|
8495
|
-
env,
|
|
9074
|
+
env: env2,
|
|
8496
9075
|
stdio: "pipe",
|
|
8497
9076
|
detached: process.platform !== "win32",
|
|
8498
9077
|
windowsHide: true
|
|
@@ -8612,6 +9191,124 @@ ${content}`
|
|
|
8612
9191
|
return this.mcp.close();
|
|
8613
9192
|
}
|
|
8614
9193
|
}
|
|
9194
|
+
const CHECKPOINT_TIMEOUT = 5e3;
|
|
9195
|
+
const TREE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
9196
|
+
const GITLINK = "160000";
|
|
9197
|
+
const BATCH = 500;
|
|
9198
|
+
const unavailable = (reason) => ({ unavailable: reason });
|
|
9199
|
+
class ACPCheckpoints {
|
|
9200
|
+
constructor(root, executable = "git") {
|
|
9201
|
+
this.root = root;
|
|
9202
|
+
this.executable = executable;
|
|
9203
|
+
}
|
|
9204
|
+
root;
|
|
9205
|
+
executable;
|
|
9206
|
+
queue = Promise.resolve();
|
|
9207
|
+
async run(args, signal, index, acceptFailure = false) {
|
|
9208
|
+
const result = await runCommand(this.executable, ["--literal-pathspecs", "-c", "color.ui=false", ...args], {
|
|
9209
|
+
cwd: this.root,
|
|
9210
|
+
signal,
|
|
9211
|
+
maxBytes: MAX_MESSAGE_BYTES,
|
|
9212
|
+
env: {
|
|
9213
|
+
...process.env,
|
|
9214
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
9215
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
9216
|
+
LC_ALL: "C",
|
|
9217
|
+
...index ? { GIT_INDEX_FILE: index } : {}
|
|
9218
|
+
}
|
|
9219
|
+
});
|
|
9220
|
+
if (result.exitCode && !acceptFailure)
|
|
9221
|
+
throw new RpcError("GIT_FAILED", result.stderr.trim() || `Git exited ${result.exitCode}`, { exitCode: result.exitCode });
|
|
9222
|
+
return result;
|
|
9223
|
+
}
|
|
9224
|
+
serial(task) {
|
|
9225
|
+
const next = this.queue.then(task, task);
|
|
9226
|
+
this.queue = next.catch(() => {
|
|
9227
|
+
});
|
|
9228
|
+
return next;
|
|
9229
|
+
}
|
|
9230
|
+
async withIndex(task) {
|
|
9231
|
+
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "oxbit-checkpoint-"));
|
|
9232
|
+
try {
|
|
9233
|
+
return await task(path.join(directory, "index"));
|
|
9234
|
+
} finally {
|
|
9235
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
/** Copies the real index so `add -A` reuses its stat data, then writes the tree. */
|
|
9239
|
+
async snapshot(signal) {
|
|
9240
|
+
const repository = await this.run(["rev-parse", "--git-dir", "--show-toplevel"], signal, void 0, true);
|
|
9241
|
+
if (repository.exitCode) return unavailable("not-repository");
|
|
9242
|
+
const top = repository.stdout.trim().split("\n").at(-1) ?? "";
|
|
9243
|
+
if (await fs.realpath(top).catch(() => top) !== this.root) return unavailable("subdirectory");
|
|
9244
|
+
const real = path.resolve(this.root, (await this.run(["rev-parse", "--git-path", "index"], signal)).stdout.trim());
|
|
9245
|
+
return this.withIndex(async (index) => {
|
|
9246
|
+
await fs.copyFile(real, index).catch((error) => {
|
|
9247
|
+
if (error.code !== "ENOENT") throw error;
|
|
9248
|
+
});
|
|
9249
|
+
await this.run(["add", "-A"], signal, index);
|
|
9250
|
+
return (await this.run(["write-tree"], signal, index)).stdout.trim();
|
|
9251
|
+
});
|
|
9252
|
+
}
|
|
9253
|
+
create(signal) {
|
|
9254
|
+
const timeout = AbortSignal.timeout(CHECKPOINT_TIMEOUT);
|
|
9255
|
+
return this.serial(async () => {
|
|
9256
|
+
try {
|
|
9257
|
+
const tree = await this.snapshot(signal ? AbortSignal.any([signal, timeout]) : timeout);
|
|
9258
|
+
return typeof tree === "string" ? { tree } : tree;
|
|
9259
|
+
} catch (error) {
|
|
9260
|
+
if (signal?.aborted) throw error;
|
|
9261
|
+
return unavailable(timeout.aborted ? "timeout" : "failed");
|
|
9262
|
+
}
|
|
9263
|
+
});
|
|
9264
|
+
}
|
|
9265
|
+
async changes(tree, signal) {
|
|
9266
|
+
if (typeof tree !== "string" || !TREE.test(tree)) throw new RpcError("INVALID_PARAMS", "Choose a valid checkpoint");
|
|
9267
|
+
const current = await this.snapshot(signal);
|
|
9268
|
+
if (typeof current !== "string") return current;
|
|
9269
|
+
if ((await this.run(["cat-file", "-e", `${tree}^{tree}`], signal, void 0, true)).exitCode) return unavailable("missing");
|
|
9270
|
+
const output = (await this.run(["diff-tree", "-r", "-z", "--raw", "--no-renames", current, tree], signal)).stdout.split("\0");
|
|
9271
|
+
const changes = [];
|
|
9272
|
+
for (let i = 0; i + 1 < output.length; i += 2) {
|
|
9273
|
+
const [from, to, , , status2] = output[i].replace(/^:/, "").split(" ");
|
|
9274
|
+
if (from === GITLINK || to === GITLINK) continue;
|
|
9275
|
+
changes.push({ status: status2, path: output[i + 1] });
|
|
9276
|
+
}
|
|
9277
|
+
return { tree: current, changes };
|
|
9278
|
+
}
|
|
9279
|
+
diff(tree, signal) {
|
|
9280
|
+
return this.serial(() => this.changes(tree, signal));
|
|
9281
|
+
}
|
|
9282
|
+
/** Writes only paths that differ from the checkpoint. `paths` is the set the client checked for unsaved edits. */
|
|
9283
|
+
restore(tree, paths, signal) {
|
|
9284
|
+
if (paths !== void 0 && (!Array.isArray(paths) || paths.some((item) => typeof item !== "string")))
|
|
9285
|
+
throw new RpcError("INVALID_PARAMS", "paths must be a string array");
|
|
9286
|
+
return this.serial(async () => {
|
|
9287
|
+
const diff = await this.changes(tree, signal);
|
|
9288
|
+
if ("unavailable" in diff) throw new RpcError("CHECKPOINT_UNAVAILABLE", "This checkpoint is no longer available");
|
|
9289
|
+
const checked = paths && new Set(paths);
|
|
9290
|
+
if (checked && diff.changes.some((change) => !checked.has(change.path)))
|
|
9291
|
+
throw new RpcError("CONFLICT", "Files changed while preparing the restore. Try again.");
|
|
9292
|
+
const deleted = diff.changes.filter((change) => change.status === "D").map((change) => change.path);
|
|
9293
|
+
const restored = diff.changes.filter((change) => change.status !== "D").map((change) => change.path);
|
|
9294
|
+
for (const file of deleted) await this.remove(file);
|
|
9295
|
+
if (restored.length)
|
|
9296
|
+
await this.withIndex(async (index) => {
|
|
9297
|
+
await this.run(["read-tree", tree], signal, index);
|
|
9298
|
+
for (let i = 0; i < restored.length; i += BATCH)
|
|
9299
|
+
await this.run(["checkout-index", "-f", "--", ...restored.slice(i, i + BATCH)], signal, index);
|
|
9300
|
+
});
|
|
9301
|
+
return { restored, deleted };
|
|
9302
|
+
});
|
|
9303
|
+
}
|
|
9304
|
+
async remove(relative) {
|
|
9305
|
+
const full = path.resolve(this.root, relative);
|
|
9306
|
+
if (!full.startsWith(this.root + path.sep)) throw new RpcError("PATH_DENIED", `Path is outside the workspace: ${relative}`);
|
|
9307
|
+
await fs.rm(full, { force: true });
|
|
9308
|
+
for (let directory = path.dirname(full); directory !== this.root; directory = path.dirname(directory))
|
|
9309
|
+
if (!await fs.rmdir(directory).then(() => true, () => false)) break;
|
|
9310
|
+
}
|
|
9311
|
+
}
|
|
8615
9312
|
var re = { exports: {} };
|
|
8616
9313
|
var constants;
|
|
8617
9314
|
var hasRequiredConstants;
|
|
@@ -9289,7 +9986,7 @@ function requireSort() {
|
|
|
9289
9986
|
if (hasRequiredSort) return sort_1;
|
|
9290
9987
|
hasRequiredSort = 1;
|
|
9291
9988
|
const compareBuild = requireCompareBuild();
|
|
9292
|
-
const sort = (
|
|
9989
|
+
const sort = (list2, loose) => list2.sort((a, b) => compareBuild(a, b, loose));
|
|
9293
9990
|
sort_1 = sort;
|
|
9294
9991
|
return sort_1;
|
|
9295
9992
|
}
|
|
@@ -9299,7 +9996,7 @@ function requireRsort() {
|
|
|
9299
9996
|
if (hasRequiredRsort) return rsort_1;
|
|
9300
9997
|
hasRequiredRsort = 1;
|
|
9301
9998
|
const compareBuild = requireCompareBuild();
|
|
9302
|
-
const rsort = (
|
|
9999
|
+
const rsort = (list2, loose) => list2.sort((a, b) => compareBuild(b, a, loose));
|
|
9303
10000
|
rsort_1 = rsort;
|
|
9304
10001
|
return rsort_1;
|
|
9305
10002
|
}
|
|
@@ -11460,8 +12157,8 @@ function createKernel({
|
|
|
11460
12157
|
}
|
|
11461
12158
|
extensionChanged(id);
|
|
11462
12159
|
},
|
|
11463
|
-
async load(
|
|
11464
|
-
const parsed = new URL(
|
|
12160
|
+
async load(url2, options2) {
|
|
12161
|
+
const parsed = new URL(url2, globalThis.location?.href ?? "file:///");
|
|
11465
12162
|
if (!["http:", "https:", "file:"].includes(parsed.protocol) && (!extensionOrigins.has(`${parsed.protocol}//${parsed.host}`) || parsed.username || parsed.password))
|
|
11466
12163
|
throw new Error(
|
|
11467
12164
|
"Extensions require a trusted HTTP(S), file, or application ESM artifact"
|
|
@@ -11612,9 +12309,9 @@ function bonjourName() {
|
|
|
11612
12309
|
return macLocalName || void 0;
|
|
11613
12310
|
}
|
|
11614
12311
|
function lanAddresses$1(interfaces = os.networkInterfaces()) {
|
|
11615
|
-
const addresses = Object.values(interfaces).flatMap((entries) => entries ?? []).filter((
|
|
12312
|
+
const addresses = Object.values(interfaces).flatMap((entries) => entries ?? []).filter((entry2) => !entry2.internal);
|
|
11616
12313
|
const of = (family) => [
|
|
11617
|
-
...new Set(addresses.filter((
|
|
12314
|
+
...new Set(addresses.filter((entry2) => entry2.family === family).map((entry2) => entry2.address.split("%")[0]))
|
|
11618
12315
|
];
|
|
11619
12316
|
return { v4: of("IPv4"), v6: of("IPv6") };
|
|
11620
12317
|
}
|
|
@@ -11630,6 +12327,267 @@ function lanUrl(port2, interfaces = os.networkInterfaces()) {
|
|
|
11630
12327
|
const address = lanAddresses$1(interfaces).v4[0];
|
|
11631
12328
|
return address ? `http://${address}:${port2}` : void 0;
|
|
11632
12329
|
}
|
|
12330
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
12331
|
+
const shellOwned = /* @__PURE__ */ new Set(["PWD", "OLDPWD", "SHLVL", "_", "OXBIT_RESOLVING_ENVIRONMENT"]);
|
|
12332
|
+
const launchOwned = (key) => /^(SSH_|GH_|GITHUB_)/.test(key) || key.toLowerCase().endsWith("_proxy");
|
|
12333
|
+
function parseShellEnvironment(output, mark) {
|
|
12334
|
+
const start = output.indexOf(mark), end = output.lastIndexOf(mark);
|
|
12335
|
+
if (start < 0 || end <= start) return void 0;
|
|
12336
|
+
const env2 = {};
|
|
12337
|
+
for (const entry2 of output.slice(start + mark.length, end).split("\0")) {
|
|
12338
|
+
const separator = entry2.indexOf("="), key = entry2.slice(0, separator);
|
|
12339
|
+
if (separator > 0 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && !shellOwned.has(key))
|
|
12340
|
+
env2[key] = entry2.slice(separator + 1);
|
|
12341
|
+
}
|
|
12342
|
+
return env2;
|
|
12343
|
+
}
|
|
12344
|
+
function loginShellEnvironment(cwd, inherited = process.env, platform = process.platform, timeout = 1e4) {
|
|
12345
|
+
if (platform === "win32") return Promise.resolve(inherited);
|
|
12346
|
+
const { shell, args } = resolveTerminalShell(inherited, platform);
|
|
12347
|
+
const mark = randomBytes(12).toString("hex");
|
|
12348
|
+
return new Promise((resolve) => {
|
|
12349
|
+
const chunks = [];
|
|
12350
|
+
let bytes = 0, settled = false;
|
|
12351
|
+
const child = spawn(shell, [...args, "-c", `printf ${mark}; /usr/bin/env -0; printf ${mark}`], {
|
|
12352
|
+
cwd,
|
|
12353
|
+
env: { ...inherited, OXBIT_RESOLVING_ENVIRONMENT: "1" },
|
|
12354
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
12355
|
+
detached: true
|
|
12356
|
+
});
|
|
12357
|
+
trackChild(child);
|
|
12358
|
+
const finish = (env2) => {
|
|
12359
|
+
if (settled) return;
|
|
12360
|
+
settled = true;
|
|
12361
|
+
clearTimeout(timer);
|
|
12362
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
12363
|
+
if (!env2) {
|
|
12364
|
+
resolve(inherited);
|
|
12365
|
+
return;
|
|
12366
|
+
}
|
|
12367
|
+
const merged = { ...inherited, ...env2 };
|
|
12368
|
+
for (const [key, value] of Object.entries(inherited))
|
|
12369
|
+
if (launchOwned(key)) merged[key] = value;
|
|
12370
|
+
resolve(merged);
|
|
12371
|
+
};
|
|
12372
|
+
const timer = setTimeout(() => finish(), timeout);
|
|
12373
|
+
child.stdout.on("data", (chunk) => {
|
|
12374
|
+
bytes += chunk.length;
|
|
12375
|
+
if (bytes > MAX_OUTPUT_BYTES) finish();
|
|
12376
|
+
else chunks.push(chunk);
|
|
12377
|
+
});
|
|
12378
|
+
child.on("error", () => finish());
|
|
12379
|
+
child.on("close", (code) => finish(code === 0 ? parseShellEnvironment(Buffer.concat(chunks).toString(), mark) : void 0));
|
|
12380
|
+
});
|
|
12381
|
+
}
|
|
12382
|
+
const PUSH_DEVICES_FILE = "push-devices.json";
|
|
12383
|
+
const MAX_DEVICES = 16;
|
|
12384
|
+
const RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
12385
|
+
const WINDOW_SECONDS = 15 * 60;
|
|
12386
|
+
const RELAY_TIMEOUT_MS = 1e4;
|
|
12387
|
+
const HANDLE = /^[A-Za-z0-9_-]{1,2048}$/;
|
|
12388
|
+
const STATUSES = ["working", "waiting", "finished", "failed"];
|
|
12389
|
+
const TITLES = { waiting: "Agent needs your input", finished: "Agent finished", failed: "Agent turn failed" };
|
|
12390
|
+
const log = (message2) => process.stderr.write(`Oxbit push: ${message2}
|
|
12391
|
+
`);
|
|
12392
|
+
function deviceIdOf(params) {
|
|
12393
|
+
const value = requireString(params, "deviceId", 128);
|
|
12394
|
+
if (!value) throw new RpcError("INVALID_PARAMS", "Invalid deviceId");
|
|
12395
|
+
return value;
|
|
12396
|
+
}
|
|
12397
|
+
function handleOf(params, key) {
|
|
12398
|
+
const value = params[key];
|
|
12399
|
+
if (value === void 0 || value === null) return void 0;
|
|
12400
|
+
if (typeof value !== "string" || !HANDLE.test(value)) throw new RpcError("INVALID_PARAMS", `Invalid ${key}`);
|
|
12401
|
+
return value;
|
|
12402
|
+
}
|
|
12403
|
+
const text$1 = (value) => typeof value === "string" && value.length <= 1024;
|
|
12404
|
+
function activityOf(value) {
|
|
12405
|
+
if (value === null) return null;
|
|
12406
|
+
const v = value ?? {};
|
|
12407
|
+
const startedAt = v.startedAt;
|
|
12408
|
+
if (typeof value !== "object" || !text$1(v.agent) || !text$1(v.title) || !text$1(v.detail) || !STATUSES.includes(v.status) || !Number.isSafeInteger(v.pending) || v.pending < 0 || startedAt !== void 0 && (typeof startedAt !== "number" || !Number.isFinite(startedAt) || startedAt < 0))
|
|
12409
|
+
throw new RpcError("INVALID_PARAMS", "Invalid activity");
|
|
12410
|
+
return {
|
|
12411
|
+
agent: v.agent,
|
|
12412
|
+
title: v.title,
|
|
12413
|
+
status: v.status,
|
|
12414
|
+
detail: v.detail,
|
|
12415
|
+
pending: v.pending,
|
|
12416
|
+
...startedAt === void 0 ? {} : { startedAt }
|
|
12417
|
+
};
|
|
12418
|
+
}
|
|
12419
|
+
function restore(entry2) {
|
|
12420
|
+
try {
|
|
12421
|
+
const activity = entry2.activity === void 0 ? null : activityOf(entry2.activity);
|
|
12422
|
+
const device = {
|
|
12423
|
+
deviceId: deviceIdOf(entry2),
|
|
12424
|
+
session: requireString(entry2, "session", 128),
|
|
12425
|
+
alert: handleOf(entry2, "alert"),
|
|
12426
|
+
liveActivity: handleOf(entry2, "liveActivity"),
|
|
12427
|
+
active: false,
|
|
12428
|
+
...activity ? { activity } : {},
|
|
12429
|
+
updatedAt: entry2.updatedAt
|
|
12430
|
+
};
|
|
12431
|
+
return Number.isFinite(device.updatedAt) ? device : void 0;
|
|
12432
|
+
} catch {
|
|
12433
|
+
return void 0;
|
|
12434
|
+
}
|
|
12435
|
+
}
|
|
12436
|
+
function detail(status2, pending) {
|
|
12437
|
+
if (status2 === "working") return "Working";
|
|
12438
|
+
if (status2 === "waiting") return pending === 1 ? "1 request needs an answer" : `${pending} requests need an answer`;
|
|
12439
|
+
return TITLES[status2];
|
|
12440
|
+
}
|
|
12441
|
+
function activityState(snapshot, event) {
|
|
12442
|
+
return {
|
|
12443
|
+
status: event.status,
|
|
12444
|
+
title: snapshot.title,
|
|
12445
|
+
detail: detail(event.status, event.pending),
|
|
12446
|
+
pending: event.pending,
|
|
12447
|
+
...event.status === "working" && event.startedAt !== void 0 ? { startedAt: Math.floor(event.startedAt / 1e3) } : {}
|
|
12448
|
+
};
|
|
12449
|
+
}
|
|
12450
|
+
class PushDevices {
|
|
12451
|
+
constructor(file) {
|
|
12452
|
+
this.file = file;
|
|
12453
|
+
}
|
|
12454
|
+
file;
|
|
12455
|
+
devices = /* @__PURE__ */ new Map();
|
|
12456
|
+
/** Last Live Activity state sent per device. */
|
|
12457
|
+
sent = /* @__PURE__ */ new Map();
|
|
12458
|
+
persistence = Promise.resolve();
|
|
12459
|
+
static async open(dataDir) {
|
|
12460
|
+
const store = new PushDevices(path.join(dataDir, PUSH_DEVICES_FILE));
|
|
12461
|
+
try {
|
|
12462
|
+
const state2 = JSON.parse(await fs.readFile(store.file, "utf8"));
|
|
12463
|
+
if (state2?.version === 1 && Array.isArray(state2.devices))
|
|
12464
|
+
for (const entry2 of state2.devices) {
|
|
12465
|
+
const device = entry2 && typeof entry2 === "object" ? restore(entry2) : void 0;
|
|
12466
|
+
if (device) store.devices.set(device.deviceId, device);
|
|
12467
|
+
}
|
|
12468
|
+
} catch (error) {
|
|
12469
|
+
if (error.code !== "ENOENT") log(`ignoring unreadable ${PUSH_DEVICES_FILE}: ${String(error)}`);
|
|
12470
|
+
}
|
|
12471
|
+
store.prune();
|
|
12472
|
+
return store;
|
|
12473
|
+
}
|
|
12474
|
+
async register(session, connection, params) {
|
|
12475
|
+
const deviceId = deviceIdOf(params), alert = handleOf(params, "alert"), liveActivity = handleOf(params, "liveActivity");
|
|
12476
|
+
const previous = this.devices.get(deviceId);
|
|
12477
|
+
this.sent.delete(deviceId);
|
|
12478
|
+
this.devices.set(deviceId, {
|
|
12479
|
+
...previous,
|
|
12480
|
+
deviceId,
|
|
12481
|
+
session,
|
|
12482
|
+
alert,
|
|
12483
|
+
liveActivity,
|
|
12484
|
+
active: true,
|
|
12485
|
+
connection,
|
|
12486
|
+
updatedAt: Date.now()
|
|
12487
|
+
});
|
|
12488
|
+
await this.persist();
|
|
12489
|
+
return {};
|
|
12490
|
+
}
|
|
12491
|
+
async state(session, connection, params) {
|
|
12492
|
+
const deviceId = deviceIdOf(params);
|
|
12493
|
+
if (typeof params.active !== "boolean") throw new RpcError("INVALID_PARAMS", "active must be boolean");
|
|
12494
|
+
const activity = params.activity === void 0 ? void 0 : activityOf(params.activity);
|
|
12495
|
+
const device = this.devices.get(deviceId);
|
|
12496
|
+
if (!device || device.session !== session) throw new RpcError("NOT_FOUND", "Register the device first");
|
|
12497
|
+
device.active = params.active;
|
|
12498
|
+
if (params.active) device.connection = connection;
|
|
12499
|
+
if (activity) device.activity = activity;
|
|
12500
|
+
else if (activity === null) delete device.activity;
|
|
12501
|
+
device.updatedAt = Date.now();
|
|
12502
|
+
this.sent.delete(deviceId);
|
|
12503
|
+
await this.persist();
|
|
12504
|
+
return {};
|
|
12505
|
+
}
|
|
12506
|
+
async unregister(session, params) {
|
|
12507
|
+
const deviceId = deviceIdOf(params);
|
|
12508
|
+
if (this.devices.get(deviceId)?.session === session) {
|
|
12509
|
+
this.devices.delete(deviceId);
|
|
12510
|
+
this.sent.delete(deviceId);
|
|
12511
|
+
await this.persist();
|
|
12512
|
+
}
|
|
12513
|
+
return {};
|
|
12514
|
+
}
|
|
12515
|
+
/** Marks devices inactive when the connection that set them active closes. */
|
|
12516
|
+
disconnect(connection) {
|
|
12517
|
+
let changed = false;
|
|
12518
|
+
for (const device of this.devices.values())
|
|
12519
|
+
if (device.active && device.connection === connection) {
|
|
12520
|
+
device.active = false;
|
|
12521
|
+
changed = true;
|
|
12522
|
+
}
|
|
12523
|
+
if (changed) this.persist().catch((error) => log(`cannot save devices: ${String(error)}`));
|
|
12524
|
+
}
|
|
12525
|
+
/** Sends alert and Live Activity pushes to the owner's inactive devices; resolves after every send settles. */
|
|
12526
|
+
async notify(event) {
|
|
12527
|
+
const sends = [];
|
|
12528
|
+
for (const device of this.devices.values()) {
|
|
12529
|
+
if (device.active || device.session !== event.owner) continue;
|
|
12530
|
+
if (event.status !== "working" && device.alert)
|
|
12531
|
+
sends.push(this.send(device.deviceId, "alert", {
|
|
12532
|
+
handle: device.alert,
|
|
12533
|
+
alert: { title: TITLES[event.status], body: device.activity?.title || event.agent }
|
|
12534
|
+
}));
|
|
12535
|
+
if (!device.liveActivity || !device.activity) continue;
|
|
12536
|
+
const state2 = activityState(device.activity, event), end = event.status === "finished" || event.status === "failed";
|
|
12537
|
+
const key = JSON.stringify([end, state2]);
|
|
12538
|
+
if (this.sent.get(device.deviceId) === key) continue;
|
|
12539
|
+
this.sent.set(device.deviceId, key);
|
|
12540
|
+
const window = Math.floor(Date.now() / 1e3) + WINDOW_SECONDS;
|
|
12541
|
+
sends.push(this.send(device.deviceId, "liveActivity", {
|
|
12542
|
+
handle: device.liveActivity,
|
|
12543
|
+
activity: { event: end ? "end" : "update", state: state2, ...end ? { dismissalDate: window } : { staleDate: window } }
|
|
12544
|
+
}));
|
|
12545
|
+
}
|
|
12546
|
+
await Promise.all(sends);
|
|
12547
|
+
}
|
|
12548
|
+
flush() {
|
|
12549
|
+
return this.persistence.catch(() => {
|
|
12550
|
+
});
|
|
12551
|
+
}
|
|
12552
|
+
async send(deviceId, kind, request) {
|
|
12553
|
+
const relay = (process.env.OXBIT_PUSH_RELAY ?? PUSH_RELAY_URL).replace(/\/+$/, "");
|
|
12554
|
+
try {
|
|
12555
|
+
const response = await fetch(`${relay}/v1/push`, {
|
|
12556
|
+
method: "POST",
|
|
12557
|
+
headers: { "Content-Type": "application/json" },
|
|
12558
|
+
body: JSON.stringify(request),
|
|
12559
|
+
signal: AbortSignal.timeout(RELAY_TIMEOUT_MS)
|
|
12560
|
+
});
|
|
12561
|
+
const result = await response.json().catch(() => ({}));
|
|
12562
|
+
const device = this.devices.get(deviceId);
|
|
12563
|
+
if (result.gone && device?.[kind] === request.handle) {
|
|
12564
|
+
delete device[kind];
|
|
12565
|
+
if (kind === "liveActivity") this.sent.delete(deviceId);
|
|
12566
|
+
await this.persist();
|
|
12567
|
+
} else if (!response.ok || !result.sent)
|
|
12568
|
+
log(`relay did not send to ${deviceId}: ${result.reason ?? `HTTP ${response.status}`}`);
|
|
12569
|
+
} catch (error) {
|
|
12570
|
+
log(`push to ${deviceId} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
12571
|
+
}
|
|
12572
|
+
}
|
|
12573
|
+
prune(now = Date.now()) {
|
|
12574
|
+
for (const [id, device] of this.devices)
|
|
12575
|
+
if (now - device.updatedAt > RETENTION_MS) this.devices.delete(id);
|
|
12576
|
+
const oldest = [...this.devices.values()].sort((a, b) => a.updatedAt - b.updatedAt);
|
|
12577
|
+
for (const device of oldest.slice(0, Math.max(0, oldest.length - MAX_DEVICES))) this.devices.delete(device.deviceId);
|
|
12578
|
+
for (const id of this.sent.keys()) if (!this.devices.has(id)) this.sent.delete(id);
|
|
12579
|
+
}
|
|
12580
|
+
persist() {
|
|
12581
|
+
this.prune();
|
|
12582
|
+
const payload = JSON.stringify({ version: 1, devices: [...this.devices.values()] });
|
|
12583
|
+
this.persistence = this.persistence.catch(() => {
|
|
12584
|
+
}).then(async () => {
|
|
12585
|
+
await fs.writeFile(this.file + ".tmp", payload, { mode: 384 });
|
|
12586
|
+
await fs.rename(this.file + ".tmp", this.file);
|
|
12587
|
+
});
|
|
12588
|
+
return this.persistence;
|
|
12589
|
+
}
|
|
12590
|
+
}
|
|
11633
12591
|
const MAX_SOCKET_BACKLOG_BYTES = MAX_FILE_WRITE_MESSAGE_BYTES;
|
|
11634
12592
|
const allCapabilities = [
|
|
11635
12593
|
"filesystem.read",
|
|
@@ -11734,13 +12692,13 @@ async function createRuntime(options2) {
|
|
|
11734
12692
|
const origin = request.headers.origin;
|
|
11735
12693
|
return origin === void 0 ? !required : allowedOrigins().has(origin);
|
|
11736
12694
|
};
|
|
11737
|
-
const send = (connection,
|
|
12695
|
+
const send = (connection, message2) => {
|
|
11738
12696
|
if (connection.ws.readyState !== WebSocket.OPEN) return;
|
|
11739
12697
|
if (connection.ws.bufferedAmount > MAX_SOCKET_BACKLOG_BYTES) {
|
|
11740
12698
|
connection.ws.close(1013, "Slow consumer; reconnect to resynchronize");
|
|
11741
12699
|
return;
|
|
11742
12700
|
}
|
|
11743
|
-
connection.ws.send(JSON.stringify(
|
|
12701
|
+
connection.ws.send(JSON.stringify(message2));
|
|
11744
12702
|
};
|
|
11745
12703
|
const event = (connection, name, params, stream, seq) => send(connection, {
|
|
11746
12704
|
v: 1,
|
|
@@ -11771,10 +12729,12 @@ async function createRuntime(options2) {
|
|
|
11771
12729
|
throw new RpcError("FORBIDDEN", "Owner access is required");
|
|
11772
12730
|
return session;
|
|
11773
12731
|
};
|
|
12732
|
+
const acpRegistry = new ACPRegistry(dataDir, options2.acpRegistryUrl);
|
|
12733
|
+
const pushDevices = await PushDevices.open(dataDir);
|
|
11774
12734
|
const agents = new AgentACP(files, (connectionId, name, params) => {
|
|
11775
12735
|
const connection = connections.get(connectionId);
|
|
11776
12736
|
if (connection?.session && !connection.session.revoked && trusted) event(connection, name, params);
|
|
11777
|
-
}, () => updateIdle());
|
|
12737
|
+
}, () => updateIdle(), acpRegistry, (status2) => void pushDevices.notify(status2));
|
|
11778
12738
|
const processEvent = ({ event: name, params, stream, seq }, ownerId) => {
|
|
11779
12739
|
const cap = name.startsWith("terminal.") ? "terminal" : "tasks";
|
|
11780
12740
|
for (const c of connections.values()) {
|
|
@@ -11784,7 +12744,7 @@ async function createRuntime(options2) {
|
|
|
11784
12744
|
};
|
|
11785
12745
|
const processes = new Processes(root, processEvent);
|
|
11786
12746
|
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);
|
|
12747
|
+
const tasks = new TaskRunner(taskStore, processEvent, options2.forwardTaskPort, loginShellEnvironment);
|
|
11788
12748
|
const worktrees = new TaskWorktrees(taskStore, tasks);
|
|
11789
12749
|
let projectChanged = () => {
|
|
11790
12750
|
};
|
|
@@ -11834,6 +12794,7 @@ async function createRuntime(options2) {
|
|
|
11834
12794
|
}
|
|
11835
12795
|
);
|
|
11836
12796
|
const git = new Git(files, options2.desktop?.gitPath);
|
|
12797
|
+
const checkpoints = new ACPCheckpoints(files.root, options2.desktop?.gitPath);
|
|
11837
12798
|
const extensions = new RuntimeExtensions(files, {
|
|
11838
12799
|
"runtime.git": {
|
|
11839
12800
|
status: (signal) => git.status(signal),
|
|
@@ -11939,29 +12900,29 @@ async function createRuntime(options2) {
|
|
|
11939
12900
|
httpJson(response, 403, { error: "Origin is not allowed" });
|
|
11940
12901
|
return;
|
|
11941
12902
|
}
|
|
11942
|
-
const
|
|
11943
|
-
if (
|
|
12903
|
+
const url2 = new URL(request.url ?? "/", `http://${host}:${port2}`);
|
|
12904
|
+
if (url2.pathname === "/api/health") {
|
|
11944
12905
|
httpJson(response, 200, { ok: true, protocol: 1, ...identity2 });
|
|
11945
12906
|
return;
|
|
11946
12907
|
}
|
|
11947
|
-
if (
|
|
12908
|
+
if (url2.pathname === "/api/pair" && request.method === "POST") {
|
|
11948
12909
|
if (options2.desktop) {
|
|
11949
12910
|
httpJson(response, 403, { error: "Desktop pairing requires the parent channel" });
|
|
11950
12911
|
return;
|
|
11951
12912
|
}
|
|
11952
|
-
const address2 = request.socket.remoteAddress ?? "unknown",
|
|
12913
|
+
const address2 = request.socket.remoteAddress ?? "unknown", entry2 = attempts.get(address2) ?? {
|
|
11953
12914
|
count: 0,
|
|
11954
12915
|
reset: Date.now() + 6e4
|
|
11955
12916
|
};
|
|
11956
|
-
if (Date.now() >
|
|
11957
|
-
|
|
11958
|
-
|
|
12917
|
+
if (Date.now() > entry2.reset) {
|
|
12918
|
+
entry2.count = 0;
|
|
12919
|
+
entry2.reset = Date.now() + 6e4;
|
|
11959
12920
|
}
|
|
11960
|
-
if (++
|
|
12921
|
+
if (++entry2.count > pairingAttemptsPerMinute) {
|
|
11961
12922
|
httpJson(response, 429, { error: "Pairing rate limit reached" });
|
|
11962
12923
|
return;
|
|
11963
12924
|
}
|
|
11964
|
-
attempts.set(address2,
|
|
12925
|
+
attempts.set(address2, entry2);
|
|
11965
12926
|
const input = await body(request);
|
|
11966
12927
|
const code = typeof input.code === "string" ? input.code : "";
|
|
11967
12928
|
const a = Buffer.from(code), b = Buffer.from(pairingCode);
|
|
@@ -11977,7 +12938,7 @@ async function createRuntime(options2) {
|
|
|
11977
12938
|
httpJson(response, 200, { ...result, runtime: identity2 });
|
|
11978
12939
|
return;
|
|
11979
12940
|
}
|
|
11980
|
-
if (
|
|
12941
|
+
if (url2.pathname === "/api/grants") {
|
|
11981
12942
|
const session = httpSession(request);
|
|
11982
12943
|
if (!session?.owner) {
|
|
11983
12944
|
httpJson(response, 403, { error: "Owner access is required" });
|
|
@@ -12006,7 +12967,7 @@ async function createRuntime(options2) {
|
|
|
12006
12967
|
return;
|
|
12007
12968
|
}
|
|
12008
12969
|
}
|
|
12009
|
-
if (
|
|
12970
|
+
if (url2.pathname.startsWith("/api/grants/") && request.method === "DELETE") {
|
|
12010
12971
|
if (!httpSession(request)?.owner) {
|
|
12011
12972
|
httpJson(response, 403, { error: "Owner access is required" });
|
|
12012
12973
|
return;
|
|
@@ -12015,7 +12976,7 @@ async function createRuntime(options2) {
|
|
|
12015
12976
|
response,
|
|
12016
12977
|
200,
|
|
12017
12978
|
await revoke(
|
|
12018
|
-
decodeURIComponent(
|
|
12979
|
+
decodeURIComponent(url2.pathname.slice("/api/grants/".length))
|
|
12019
12980
|
)
|
|
12020
12981
|
);
|
|
12021
12982
|
return;
|
|
@@ -12026,7 +12987,7 @@ async function createRuntime(options2) {
|
|
|
12026
12987
|
}
|
|
12027
12988
|
let relative;
|
|
12028
12989
|
try {
|
|
12029
|
-
relative = decodeURIComponent(
|
|
12990
|
+
relative = decodeURIComponent(url2.pathname);
|
|
12030
12991
|
} catch {
|
|
12031
12992
|
httpJson(response, 400, { error: "Invalid path" });
|
|
12032
12993
|
return;
|
|
@@ -12120,7 +13081,7 @@ async function createRuntime(options2) {
|
|
|
12120
13081
|
if (method.startsWith("terminal.")) return { cap: "terminal", trust: true };
|
|
12121
13082
|
if (method.startsWith("tasks.")) return { cap: "tasks", trust: !["tasks.catalog", "tasks.list", "tasks.attach", "tasks.worktrees"].includes(method) };
|
|
12122
13083
|
if (method.startsWith("git.")) return { cap: "git", trust: true };
|
|
12123
|
-
if (method.startsWith("acp.")) return { cap: "extensions", trust: true };
|
|
13084
|
+
if (method.startsWith("acp.") || method.startsWith("push.")) return { cap: "extensions", trust: true };
|
|
12124
13085
|
if (method.startsWith("lsp.")) return { cap: "lsp", trust: true };
|
|
12125
13086
|
if (method.startsWith("collab.")) return { cap: "collaboration" };
|
|
12126
13087
|
if (method.startsWith("extensions."))
|
|
@@ -12150,9 +13111,10 @@ async function createRuntime(options2) {
|
|
|
12150
13111
|
const required = permission(method), session = authorized(connection, required.cap, required.trust);
|
|
12151
13112
|
if (method.startsWith("acp.")) {
|
|
12152
13113
|
owner(connection);
|
|
12153
|
-
if (!["acp.start", "acp.list", "acp.attach", "acp.disconnect"].includes(method))
|
|
13114
|
+
if (!["acp.start", "acp.list", "acp.attach", "acp.detach", "acp.stop", "acp.disconnect", "acp.registry"].includes(method) && !method.startsWith("acp.checkpoint."))
|
|
12154
13115
|
agents.control(session.id, requireString(params, "id"), connection.id);
|
|
12155
13116
|
}
|
|
13117
|
+
if (method.startsWith("push.")) owner(connection);
|
|
12156
13118
|
if (method.startsWith("project.")) owner(connection);
|
|
12157
13119
|
if (method.startsWith("settings.")) owner(connection);
|
|
12158
13120
|
switch (method) {
|
|
@@ -12178,6 +13140,8 @@ async function createRuntime(options2) {
|
|
|
12178
13140
|
return agents.start(session.id, params, signal, connection.id);
|
|
12179
13141
|
case "acp.list":
|
|
12180
13142
|
return agents.list(session.id);
|
|
13143
|
+
case "acp.registry":
|
|
13144
|
+
return acpRegistry.listing(params.refresh === true);
|
|
12181
13145
|
case "acp.attach":
|
|
12182
13146
|
return agents.attach(session.id, requireString(params, "id"), connection.id, params.clientCapabilities?.editorTools === true);
|
|
12183
13147
|
case "acp.enqueue":
|
|
@@ -12185,6 +13149,9 @@ async function createRuntime(options2) {
|
|
|
12185
13149
|
return agents.enqueue(session.id, requireString(params, "id"), params, method === "acp.interrupt");
|
|
12186
13150
|
case "acp.dequeue":
|
|
12187
13151
|
return agents.dequeue(session.id, requireString(params, "id"), requireString(params, "promptId"));
|
|
13152
|
+
case "acp.moveQueued":
|
|
13153
|
+
if (!Number.isInteger(params.index)) throw new RpcError("INVALID_PARAMS", "index must be an integer");
|
|
13154
|
+
return agents.moveQueued(session.id, requireString(params, "id"), requireString(params, "promptId"), params.index);
|
|
12188
13155
|
case "acp.resumeQueue":
|
|
12189
13156
|
return agents.resumeQueue(session.id, requireString(params, "id"));
|
|
12190
13157
|
case "acp.call":
|
|
@@ -12195,9 +13162,26 @@ async function createRuntime(options2) {
|
|
|
12195
13162
|
return agents.cancel(session.id, requireString(params, "id"));
|
|
12196
13163
|
case "acp.stop":
|
|
12197
13164
|
return agents.stop(session.id, requireString(params, "id"));
|
|
13165
|
+
case "acp.detach":
|
|
13166
|
+
agents.detach(connection.id);
|
|
13167
|
+
return {};
|
|
12198
13168
|
case "acp.disconnect":
|
|
12199
13169
|
agents.disconnect(session.id, connection.id);
|
|
12200
13170
|
return {};
|
|
13171
|
+
case "push.register":
|
|
13172
|
+
return pushDevices.register(session.id, connection.id, params);
|
|
13173
|
+
case "push.state":
|
|
13174
|
+
return pushDevices.state(session.id, connection.id, params);
|
|
13175
|
+
case "push.unregister":
|
|
13176
|
+
return pushDevices.unregister(session.id, params);
|
|
13177
|
+
case "acp.checkpoint.create":
|
|
13178
|
+
return checkpoints.create(signal);
|
|
13179
|
+
case "acp.checkpoint.diff":
|
|
13180
|
+
return checkpoints.diff(params.tree, signal);
|
|
13181
|
+
case "acp.checkpoint.restore":
|
|
13182
|
+
if (agents.list(session.id).sessions.some((agent) => agent.busy || agent.pendingRequests))
|
|
13183
|
+
throw new RpcError("BUSY", "Wait for the agent to finish before restoring a checkpoint");
|
|
13184
|
+
return checkpoints.restore(params.tree, params.paths, signal);
|
|
12201
13185
|
case "workspace.info":
|
|
12202
13186
|
return sessionInfo(session);
|
|
12203
13187
|
case "workspace.trust":
|
|
@@ -12371,6 +13355,8 @@ async function createRuntime(options2) {
|
|
|
12371
13355
|
return git.show(params.ref, signal);
|
|
12372
13356
|
case "git.commitDiff":
|
|
12373
13357
|
return git.commitDiff(params, signal);
|
|
13358
|
+
case "git.branchDiff":
|
|
13359
|
+
return git.branchDiff(params, signal);
|
|
12374
13360
|
case "git.stashes":
|
|
12375
13361
|
return git.stashes(signal);
|
|
12376
13362
|
case "git.stashDiff":
|
|
@@ -12535,6 +13521,25 @@ async function createRuntime(options2) {
|
|
|
12535
13521
|
params.encoding,
|
|
12536
13522
|
params.eol
|
|
12537
13523
|
);
|
|
13524
|
+
case "search.files": {
|
|
13525
|
+
const term = requireString(params, "query", 1024).trim().toLowerCase();
|
|
13526
|
+
if (!term) return [];
|
|
13527
|
+
const result = await runCommand(options2.desktop?.rgPath ?? "rg", ["--files", "--hidden", "--glob", "!.git", "--glob", "!node_modules", "--glob", "!.oxbit-tmp-*"], {
|
|
13528
|
+
cwd: root,
|
|
13529
|
+
signal,
|
|
13530
|
+
maxBytes: 64 * MAX_BUFFER_BYTES
|
|
13531
|
+
});
|
|
13532
|
+
if (result.exitCode > 1) throw new RpcError("SEARCH_FAILED", result.stderr.trim());
|
|
13533
|
+
const matches = (path2) => {
|
|
13534
|
+
let at = 0;
|
|
13535
|
+
for (const char of term) {
|
|
13536
|
+
at = path2.indexOf(char, at) + 1;
|
|
13537
|
+
if (!at) return false;
|
|
13538
|
+
}
|
|
13539
|
+
return true;
|
|
13540
|
+
};
|
|
13541
|
+
return result.stdout.split("\n").filter((path2) => path2 && matches(path2.toLowerCase())).sort().slice(0, 1e3);
|
|
13542
|
+
}
|
|
12538
13543
|
case "search.query": {
|
|
12539
13544
|
const query = requireString(params, "query", 8192);
|
|
12540
13545
|
if (!query) return [];
|
|
@@ -12669,32 +13674,32 @@ async function createRuntime(options2) {
|
|
|
12669
13674
|
authDeadline.unref();
|
|
12670
13675
|
ws.on("message", (raw) => {
|
|
12671
13676
|
void (async () => {
|
|
12672
|
-
let
|
|
13677
|
+
let message2;
|
|
12673
13678
|
try {
|
|
12674
|
-
|
|
13679
|
+
message2 = parseClientMessage(raw.toString());
|
|
12675
13680
|
} catch (error) {
|
|
12676
13681
|
event(connection, "protocol.error", errorShape(error));
|
|
12677
13682
|
ws.close(1008, "Invalid protocol message");
|
|
12678
13683
|
return;
|
|
12679
13684
|
}
|
|
12680
|
-
if (
|
|
13685
|
+
if (message2.type === "cancel") {
|
|
12681
13686
|
authorized(connection);
|
|
12682
|
-
connection.pending.get(
|
|
13687
|
+
connection.pending.get(message2.id)?.abort();
|
|
12683
13688
|
return;
|
|
12684
13689
|
}
|
|
12685
|
-
if (
|
|
13690
|
+
if (message2.type === "ack") {
|
|
12686
13691
|
const session = authorized(connection, "terminal");
|
|
12687
|
-
if (!
|
|
13692
|
+
if (!message2.stream.startsWith("terminal:"))
|
|
12688
13693
|
throw new RpcError("INVALID_PARAMS", "Unknown stream");
|
|
12689
13694
|
processes.ack(
|
|
12690
|
-
|
|
13695
|
+
message2.stream.slice(9),
|
|
12691
13696
|
session.id,
|
|
12692
13697
|
connection.id,
|
|
12693
|
-
|
|
13698
|
+
message2.seq
|
|
12694
13699
|
);
|
|
12695
13700
|
return;
|
|
12696
13701
|
}
|
|
12697
|
-
const { id, method, params } =
|
|
13702
|
+
const { id, method, params } = message2;
|
|
12698
13703
|
if (connection.pending.size >= 64) {
|
|
12699
13704
|
send(connection, {
|
|
12700
13705
|
v: 1,
|
|
@@ -12800,6 +13805,7 @@ async function createRuntime(options2) {
|
|
|
12800
13805
|
clearTimeout(authDeadline);
|
|
12801
13806
|
agents.detach(connection.id);
|
|
12802
13807
|
lsp.detach(connection.id);
|
|
13808
|
+
pushDevices.disconnect(connection.id);
|
|
12803
13809
|
connections.delete(connection.id);
|
|
12804
13810
|
updateIdle();
|
|
12805
13811
|
for (const pending of connection.edits.values()) {
|
|
@@ -12950,6 +13956,7 @@ async function createRuntime(options2) {
|
|
|
12950
13956
|
await Promise.allSettled([...inflight.values()]);
|
|
12951
13957
|
await collaboration.close();
|
|
12952
13958
|
await persistence;
|
|
13959
|
+
await pushDevices.flush();
|
|
12953
13960
|
await new Promise((resolve) => websocket.close(() => resolve()));
|
|
12954
13961
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
12955
13962
|
}
|
|
@@ -13147,7 +14154,7 @@ function lanAddresses() {
|
|
|
13147
14154
|
return { v4: of("IPv4"), v6: of("IPv6") };
|
|
13148
14155
|
}
|
|
13149
14156
|
const stderrLog = (line) => process.stderr.write(line + "\n");
|
|
13150
|
-
function advertise(ad,
|
|
14157
|
+
function advertise(ad, log2 = stderrLog) {
|
|
13151
14158
|
const instance = instanceLabel(ad.name, ad.id);
|
|
13152
14159
|
const current = (ttl) => records({ ...ad, ...(ad.addresses ?? lanAddresses)() }, ttl);
|
|
13153
14160
|
const timers = [];
|
|
@@ -13166,11 +14173,11 @@ function advertise(ad, log = stderrLog) {
|
|
|
13166
14173
|
};
|
|
13167
14174
|
const fail = (error) => {
|
|
13168
14175
|
if (closed) return;
|
|
13169
|
-
|
|
14176
|
+
log2(`mDNS advertising disabled: ${error.message}`);
|
|
13170
14177
|
stop2();
|
|
13171
14178
|
};
|
|
13172
14179
|
const sendFailure = (error) => {
|
|
13173
|
-
if (!sendFailed)
|
|
14180
|
+
if (!sendFailed) log2(`mDNS send failed: ${error.message}`);
|
|
13174
14181
|
sendFailed = true;
|
|
13175
14182
|
};
|
|
13176
14183
|
const send = (packet, port2 = PORT, address = GROUP, done) => {
|
|
@@ -13240,8 +14247,8 @@ function advertise(ad, log = stderrLog) {
|
|
|
13240
14247
|
})
|
|
13241
14248
|
};
|
|
13242
14249
|
}
|
|
13243
|
-
const dataDirFor = (root,
|
|
13244
|
-
const directory = setting("DATA_DIR",
|
|
14250
|
+
const dataDirFor = (root, env2 = process.env) => {
|
|
14251
|
+
const directory = setting("DATA_DIR", env2);
|
|
13245
14252
|
return directory ? path.resolve(directory) : workspaceDataDir(root);
|
|
13246
14253
|
};
|
|
13247
14254
|
const recordFile = (dataDir) => path.join(dataDir, "daemon.json");
|
|
@@ -13252,14 +14259,14 @@ function launchUrl(daemon, file) {
|
|
|
13252
14259
|
if (file) params.set("open", file);
|
|
13253
14260
|
return `http://${localHost(daemon.host)}:${daemon.port}/#${params}`;
|
|
13254
14261
|
}
|
|
13255
|
-
function browserCommand(
|
|
13256
|
-
if (platform === "darwin") return { command: "open", args: [
|
|
14262
|
+
function browserCommand(url2, platform) {
|
|
14263
|
+
if (platform === "darwin") return { command: "open", args: [url2] };
|
|
13257
14264
|
if (platform === "win32")
|
|
13258
|
-
return { command: "cmd", args: ["/c", "start", "",
|
|
13259
|
-
return { command: "xdg-open", args: [
|
|
14265
|
+
return { command: "cmd", args: ["/c", "start", "", url2] };
|
|
14266
|
+
return { command: "xdg-open", args: [url2] };
|
|
13260
14267
|
}
|
|
13261
|
-
function launchBrowser(
|
|
13262
|
-
const { command, args } = browserCommand(
|
|
14268
|
+
function launchBrowser(url2) {
|
|
14269
|
+
const { command, args } = browserCommand(url2, process.platform);
|
|
13263
14270
|
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
13264
14271
|
child.on("error", () => {
|
|
13265
14272
|
});
|
|
@@ -13357,7 +14364,7 @@ function parseDuration(value) {
|
|
|
13357
14364
|
const duration = match ? Number(match[1]) * units[match[2] ?? ""] : NaN;
|
|
13358
14365
|
return Number.isSafeInteger(duration) ? duration : void 0;
|
|
13359
14366
|
}
|
|
13360
|
-
function parse(argv,
|
|
14367
|
+
function parse(argv, env2 = process.env) {
|
|
13361
14368
|
const parsed = tokenize(argv);
|
|
13362
14369
|
if ("error" in parsed) return parsed;
|
|
13363
14370
|
const { values, positionals } = parsed;
|
|
@@ -13369,23 +14376,23 @@ function parse(argv, env = process.env) {
|
|
|
13369
14376
|
return { error: "--desktop cannot be combined with browser runtime options" };
|
|
13370
14377
|
if (values.lan && values.host)
|
|
13371
14378
|
return { error: "--lan binds every interface and cannot be combined with --host" };
|
|
13372
|
-
const requested = text(values.port) ??
|
|
14379
|
+
const requested = text(values.port) ?? env2.PORT;
|
|
13373
14380
|
const port2 = requested === void 0 ? 0 : Number(requested);
|
|
13374
14381
|
if (!Number.isInteger(port2) || port2 < 0 || port2 > 65535)
|
|
13375
14382
|
return {
|
|
13376
14383
|
error: `Port must be a whole number between 0 and 65535, received "${requested}"`
|
|
13377
14384
|
};
|
|
13378
|
-
const lifetime = text(values["keep-alive"]) ?? setting("KEEP_ALIVE",
|
|
14385
|
+
const lifetime = text(values["keep-alive"]) ?? setting("KEEP_ALIVE", env2);
|
|
13379
14386
|
const keepAlive = lifetime === void 0 ? 0 : parseDuration(lifetime);
|
|
13380
14387
|
if (keepAlive === void 0)
|
|
13381
14388
|
return {
|
|
13382
14389
|
error: `Keep-alive must be a duration such as 75s, 15m, 1h, forever, or milliseconds, received "${lifetime}"`
|
|
13383
14390
|
};
|
|
13384
14391
|
return {
|
|
13385
|
-
target: positionals[0] ?? setting("WORKSPACE",
|
|
14392
|
+
target: positionals[0] ?? setting("WORKSPACE", env2) ?? ".",
|
|
13386
14393
|
port: port2,
|
|
13387
14394
|
keepAlive,
|
|
13388
|
-
host: values.lan ? "0.0.0.0" : text(values.host) ??
|
|
14395
|
+
host: values.lan ? "0.0.0.0" : text(values.host) ?? env2.HOST ?? DEFAULT_HOST,
|
|
13389
14396
|
lan: values.lan === true,
|
|
13390
14397
|
open: values["no-open"] !== true,
|
|
13391
14398
|
foreground: values.foreground === true,
|
|
@@ -13417,15 +14424,15 @@ function serveFailure(error, host, port2) {
|
|
|
13417
14424
|
return `Cannot serve on ${address}: the address is not available on this machine.`;
|
|
13418
14425
|
return `The runtime could not start: ${failure2.message ?? String(error)}`;
|
|
13419
14426
|
}
|
|
13420
|
-
async function serve(invocation, target, dataDir,
|
|
14427
|
+
async function serve(invocation, target, dataDir, env2) {
|
|
13421
14428
|
try {
|
|
13422
|
-
return await serveRuntime(invocation, target, dataDir,
|
|
14429
|
+
return await serveRuntime(invocation, target, dataDir, env2);
|
|
13423
14430
|
} catch (error) {
|
|
13424
14431
|
process.stderr.write(serveFailure(error, invocation.host, invocation.port) + "\n");
|
|
13425
14432
|
return 1;
|
|
13426
14433
|
}
|
|
13427
14434
|
}
|
|
13428
|
-
async function serveRuntime(invocation, target, dataDir,
|
|
14435
|
+
async function serveRuntime(invocation, target, dataDir, env2) {
|
|
13429
14436
|
const host = invocation.host;
|
|
13430
14437
|
const port2 = invocation.port;
|
|
13431
14438
|
let stop2 = () => {
|
|
@@ -13435,10 +14442,10 @@ async function serveRuntime(invocation, target, dataDir, env) {
|
|
|
13435
14442
|
port: port2,
|
|
13436
14443
|
host,
|
|
13437
14444
|
dataDir,
|
|
13438
|
-
pairingCode: setting("PAIRING_CODE",
|
|
13439
|
-
pairingAttemptsPerMinute: setting("PAIRING_ATTEMPTS_PER_MINUTE",
|
|
13440
|
-
origins: setting("ORIGINS",
|
|
13441
|
-
webRoot: setting("WEB_ROOT",
|
|
14445
|
+
pairingCode: setting("PAIRING_CODE", env2),
|
|
14446
|
+
pairingAttemptsPerMinute: setting("PAIRING_ATTEMPTS_PER_MINUTE", env2) === void 0 ? void 0 : Number(setting("PAIRING_ATTEMPTS_PER_MINUTE", env2)),
|
|
14447
|
+
origins: setting("ORIGINS", env2)?.split(",").filter(Boolean),
|
|
14448
|
+
webRoot: setting("WEB_ROOT", env2),
|
|
13442
14449
|
...invocation.keepAlive ? { idleShutdown: { afterMs: invocation.keepAlive, onIdle: () => stop2() } } : {}
|
|
13443
14450
|
});
|
|
13444
14451
|
let advertiser;
|
|
@@ -13456,16 +14463,16 @@ async function serveRuntime(invocation, target, dataDir, env) {
|
|
|
13456
14463
|
startedAt: Date.now()
|
|
13457
14464
|
};
|
|
13458
14465
|
await writeRecord(dataDir, daemon);
|
|
13459
|
-
const
|
|
14466
|
+
const url2 = launchUrl(daemon, target.file);
|
|
13460
14467
|
process.stdout.write(
|
|
13461
14468
|
(invocation.lan ? networkSummary(daemon) : `Oxbit runtime: http://${host}:${runtime.port}
|
|
13462
14469
|
Workspace: ${runtime.root}
|
|
13463
14470
|
Owner pairing code: ${runtime.pairingCode}
|
|
13464
14471
|
Runtime id: ${runtime.identity.id}
|
|
13465
|
-
`) + `Open: ${
|
|
14472
|
+
`) + `Open: ${url2}
|
|
13466
14473
|
`
|
|
13467
14474
|
);
|
|
13468
|
-
if (invocation.open) launchBrowser(
|
|
14475
|
+
if (invocation.open) launchBrowser(url2);
|
|
13469
14476
|
await new Promise((resolve) => {
|
|
13470
14477
|
const shutdown = () => {
|
|
13471
14478
|
void Promise.resolve(advertiser?.close()).then(() => runtime.close()).then(() => removeRecord(dataDir)).then(resolve, resolve);
|
|
@@ -13487,29 +14494,29 @@ Owner pairing code: ${daemon.pairingCode}
|
|
|
13487
14494
|
Runtime id: ${daemon.id}
|
|
13488
14495
|
`;
|
|
13489
14496
|
}
|
|
13490
|
-
async function detach(invocation, target, dataDir,
|
|
14497
|
+
async function detach(invocation, target, dataDir, entry2) {
|
|
13491
14498
|
await fs.mkdir(dataDir, { recursive: true, mode: 448 });
|
|
13492
|
-
const
|
|
13493
|
-
const args = [
|
|
14499
|
+
const log2 = await fs.open(logFile(dataDir), "a", 384);
|
|
14500
|
+
const args = [entry2, "--foreground", "--no-open", ...invocation.lan ? ["--lan"] : ["--host", invocation.host]];
|
|
13494
14501
|
if (invocation.port) args.push("--port", String(invocation.port));
|
|
13495
14502
|
args.push("--keep-alive", invocation.keepAlive ? String(invocation.keepAlive) : "forever");
|
|
13496
14503
|
args.push(target.root);
|
|
13497
|
-
const
|
|
13498
|
-
delete
|
|
13499
|
-
delete
|
|
13500
|
-
delete
|
|
13501
|
-
delete
|
|
14504
|
+
const env2 = { ...process.env, OXBIT_DATA_DIR: dataDir };
|
|
14505
|
+
delete env2.PORT;
|
|
14506
|
+
delete env2.HOST;
|
|
14507
|
+
delete env2.OXBIT_WORKSPACE;
|
|
14508
|
+
delete env2.OXBIT_KEEP_ALIVE;
|
|
13502
14509
|
const child = spawn(process.execPath, args, {
|
|
13503
14510
|
detached: true,
|
|
13504
|
-
stdio: ["ignore",
|
|
13505
|
-
env
|
|
14511
|
+
stdio: ["ignore", log2.fd, log2.fd],
|
|
14512
|
+
env: env2
|
|
13506
14513
|
});
|
|
13507
14514
|
let exit;
|
|
13508
14515
|
child.on("exit", (code) => {
|
|
13509
14516
|
exit = code ?? 1;
|
|
13510
14517
|
});
|
|
13511
14518
|
child.unref();
|
|
13512
|
-
await
|
|
14519
|
+
await log2.close();
|
|
13513
14520
|
const deadline = Date.now() + READY_TIMEOUT;
|
|
13514
14521
|
while (Date.now() < deadline) {
|
|
13515
14522
|
const daemon = await liveDaemon(dataDir);
|
|
@@ -13565,11 +14572,11 @@ Open: ${launchUrl(daemon)}
|
|
|
13565
14572
|
}
|
|
13566
14573
|
async function run(argv, context = {}) {
|
|
13567
14574
|
const {
|
|
13568
|
-
env = process.env,
|
|
14575
|
+
env: env2 = process.env,
|
|
13569
14576
|
cwd = process.cwd(),
|
|
13570
|
-
entry = fileURLToPath(import.meta.url)
|
|
14577
|
+
entry: entry2 = fileURLToPath(import.meta.url)
|
|
13571
14578
|
} = context;
|
|
13572
|
-
const invocation = parse(argv,
|
|
14579
|
+
const invocation = parse(argv, env2);
|
|
13573
14580
|
if ("error" in invocation) {
|
|
13574
14581
|
process.stderr.write(`${invocation.error}
|
|
13575
14582
|
|
|
@@ -13594,10 +14601,10 @@ ${USAGE}`);
|
|
|
13594
14601
|
return 1;
|
|
13595
14602
|
}
|
|
13596
14603
|
if (invocation.action === "desktop") return launchDesktop(target.file ? path.join(target.root, target.file) : target.root);
|
|
13597
|
-
const dataDir = dataDirFor(target.root,
|
|
14604
|
+
const dataDir = dataDirFor(target.root, env2);
|
|
13598
14605
|
if (invocation.action === "stop") return stop(dataDir, target.root);
|
|
13599
14606
|
if (invocation.action === "status") return status(dataDir, target.root);
|
|
13600
|
-
if (invocation.foreground) return serve(invocation, target, dataDir,
|
|
14607
|
+
if (invocation.foreground) return serve(invocation, target, dataDir, env2);
|
|
13601
14608
|
const existing = await liveDaemon(dataDir);
|
|
13602
14609
|
if (existing && invocation.lan && !wildcardHost(existing.host)) {
|
|
13603
14610
|
process.stderr.write(`A runtime already serves ${existing.root} on ${existing.host}. Run oxbit --stop, then oxbit --lan.
|
|
@@ -13608,18 +14615,18 @@ ${USAGE}`);
|
|
|
13608
14615
|
if (existing) daemon = existing;
|
|
13609
14616
|
else
|
|
13610
14617
|
try {
|
|
13611
|
-
daemon = await detach(invocation, target, dataDir,
|
|
14618
|
+
daemon = await detach(invocation, target, dataDir, entry2);
|
|
13612
14619
|
} catch (error) {
|
|
13613
14620
|
process.stderr.write(`${error.message}
|
|
13614
14621
|
`);
|
|
13615
14622
|
return 1;
|
|
13616
14623
|
}
|
|
13617
|
-
const
|
|
13618
|
-
process.stdout.write(invocation.lan ? `${networkSummary(daemon)}Open: ${
|
|
14624
|
+
const url2 = launchUrl(daemon, target.file);
|
|
14625
|
+
process.stdout.write(invocation.lan ? `${networkSummary(daemon)}Open: ${url2}
|
|
13619
14626
|
` : `${daemon.root}
|
|
13620
|
-
${
|
|
14627
|
+
${url2}
|
|
13621
14628
|
`);
|
|
13622
|
-
if (invocation.open) launchBrowser(
|
|
14629
|
+
if (invocation.open) launchBrowser(url2);
|
|
13623
14630
|
return 0;
|
|
13624
14631
|
}
|
|
13625
14632
|
function desktopCommand(target, platform = process.platform) {
|