@oxbit/cli 0.6.1 → 0.7.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 +824 -169
- package/runtime/package.json +1 -1
- package/web/dist/assets/index-CCGtng18.css +1 -0
- package/web/dist/assets/{index-DLliH0c7.js → index-a2bql7tE.js} +125 -124
- package/web/dist/assets/{typescript-BCqEHgyO.js → typescript-DnCbM4Ma.js} +2 -2
- package/web/dist/index.html +2 -2
- package/web/dist/schemas/settings.v1.schema.json +2 -2
- package/web/dist/sdk/index.js +4 -4
- package/web/dist/assets/index-EACTV3yF.css +0 -1
package/runtime/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as fs from "node:fs/promises";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawn, execFileSync } from "node:child_process";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
|
-
import http from "node:http";
|
|
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";
|
|
11
11
|
import { WebSocketServer, WebSocket } from "ws";
|
|
@@ -43,6 +43,13 @@ function workspaceDataDir(root, home = os.homedir()) {
|
|
|
43
43
|
return path.join(home, ".oxbit", "workspaces", id);
|
|
44
44
|
}
|
|
45
45
|
const MAX_MESSAGE_BYTES = 2 * 1024 * 1024;
|
|
46
|
+
const MAX_FILE_BYTES = 20 * 1024 * 1024;
|
|
47
|
+
const MAX_FILE_WRITE_MESSAGE_BYTES = MAX_FILE_BYTES * 6 + MAX_MESSAGE_BYTES;
|
|
48
|
+
const MAX_COLLABORATION_UPDATE_BYTES = Math.ceil(MAX_FILE_BYTES / 3) * 4 + MAX_MESSAGE_BYTES;
|
|
49
|
+
const requestMessageLimit = (method) => method === "fs.write" || method === "collab.save" ? MAX_FILE_WRITE_MESSAGE_BYTES : method === "collab.update" ? MAX_COLLABORATION_UPDATE_BYTES + MAX_MESSAGE_BYTES : MAX_MESSAGE_BYTES;
|
|
50
|
+
function operationEpoch(id) {
|
|
51
|
+
return /^op:([0-9a-f-]{36}):[0-9a-f-]{36}$/.exec(id)?.[1];
|
|
52
|
+
}
|
|
46
53
|
const MAX_BUFFER_BYTES = 1024 * 1024;
|
|
47
54
|
const READ_CHUNK_BYTES = 512 * 1024;
|
|
48
55
|
const RUNTIME_SERVICE_TYPE = "_oxbit._tcp";
|
|
@@ -57,8 +64,9 @@ class RpcError extends Error {
|
|
|
57
64
|
data;
|
|
58
65
|
}
|
|
59
66
|
function parseClientMessage(raw) {
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const bytes = new TextEncoder().encode(raw).byteLength;
|
|
68
|
+
if (bytes > MAX_FILE_WRITE_MESSAGE_BYTES)
|
|
69
|
+
throw new RpcError("TOO_LARGE", "Message exceeds the file save limit");
|
|
62
70
|
let m;
|
|
63
71
|
try {
|
|
64
72
|
m = JSON.parse(raw);
|
|
@@ -67,6 +75,8 @@ function parseClientMessage(raw) {
|
|
|
67
75
|
}
|
|
68
76
|
if (!m || m.v !== 1 || !["request", "cancel", "ack"].includes(m.type))
|
|
69
77
|
throw new RpcError("INVALID_MESSAGE", "Invalid protocol message");
|
|
78
|
+
if (bytes > (m.type === "request" ? requestMessageLimit(m.method) : MAX_MESSAGE_BYTES))
|
|
79
|
+
throw new RpcError("TOO_LARGE", "Message exceeds 2 MiB");
|
|
70
80
|
if (m.type === "ack") {
|
|
71
81
|
if (typeof m.stream !== "string" || !m.stream || m.stream.length > 256 || !Number.isSafeInteger(m.seq) || m.seq < 0)
|
|
72
82
|
throw new RpcError("INVALID_MESSAGE", "Invalid acknowledgement");
|
|
@@ -224,6 +234,10 @@ class WorkspaceFiles {
|
|
|
224
234
|
protected(candidate) {
|
|
225
235
|
return [...this.protectedRoots].some((root) => candidate === root || candidate.startsWith(root + path.sep));
|
|
226
236
|
}
|
|
237
|
+
protectDescendants(candidate) {
|
|
238
|
+
if ([...this.protectedRoots].some((root) => root.startsWith(candidate + path.sep)))
|
|
239
|
+
throw new RpcError("PATH_DENIED", "Directory contains protected files");
|
|
240
|
+
}
|
|
227
241
|
inside(candidate) {
|
|
228
242
|
const rel = path.relative(this.root, candidate);
|
|
229
243
|
return rel === "" || !rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel);
|
|
@@ -311,7 +325,7 @@ class WorkspaceFiles {
|
|
|
311
325
|
const stat = await fs.stat(full);
|
|
312
326
|
if (!stat.isFile())
|
|
313
327
|
throw new RpcError("NOT_FILE", "Path is not a regular file");
|
|
314
|
-
if (stat.size >
|
|
328
|
+
if (stat.size > MAX_FILE_BYTES)
|
|
315
329
|
throw new RpcError(
|
|
316
330
|
"FILE_TOO_LARGE",
|
|
317
331
|
"Files above 20 MiB must be opened outside this editor"
|
|
@@ -329,7 +343,7 @@ class WorkspaceFiles {
|
|
|
329
343
|
const stat = await fs.stat(full);
|
|
330
344
|
if (!stat.isFile())
|
|
331
345
|
throw new RpcError("NOT_FILE", "Path is not a regular file");
|
|
332
|
-
if (stat.size >
|
|
346
|
+
if (stat.size > MAX_FILE_BYTES)
|
|
333
347
|
throw new RpcError(
|
|
334
348
|
"FILE_TOO_LARGE",
|
|
335
349
|
"Files above 20 MiB must be opened outside this editor"
|
|
@@ -393,7 +407,7 @@ class WorkspaceFiles {
|
|
|
393
407
|
actualRevision: actual
|
|
394
408
|
});
|
|
395
409
|
const bytes = encode(text2, options2.encoding, options2.eol);
|
|
396
|
-
if (bytes.length >
|
|
410
|
+
if (bytes.length > MAX_FILE_BYTES)
|
|
397
411
|
throw new RpcError(
|
|
398
412
|
"FILE_TOO_LARGE",
|
|
399
413
|
"Files above 20 MiB are unsupported"
|
|
@@ -427,6 +441,8 @@ class WorkspaceFiles {
|
|
|
427
441
|
const target = await this.resolve(to, true);
|
|
428
442
|
if (from === this.root || target === this.root)
|
|
429
443
|
throw new RpcError("PATH_DENIED", "Cannot rename workspace root");
|
|
444
|
+
this.protectDescendants(from);
|
|
445
|
+
this.protectDescendants(target);
|
|
430
446
|
try {
|
|
431
447
|
await fs.lstat(target);
|
|
432
448
|
throw new RpcError("EXISTS", "Destination already exists");
|
|
@@ -439,9 +455,103 @@ class WorkspaceFiles {
|
|
|
439
455
|
const full = await this.resolve(relative);
|
|
440
456
|
if (full === this.root)
|
|
441
457
|
throw new RpcError("PATH_DENIED", "Cannot delete workspace root");
|
|
458
|
+
this.protectDescendants(full);
|
|
442
459
|
await fs.rm(path.resolve(this.root, relative), { recursive: true });
|
|
443
460
|
}
|
|
444
461
|
}
|
|
462
|
+
const OPERATIONS_PER_EPOCH = 128;
|
|
463
|
+
const MAX_RETAINED_OPERATIONS = OPERATIONS_PER_EPOCH * 2;
|
|
464
|
+
const MAX_RUNNING_OPERATIONS = 64;
|
|
465
|
+
const MAX_OPERATION_RESULT_BYTES = 1024 * 1024;
|
|
466
|
+
class OperationHistory {
|
|
467
|
+
records = /* @__PURE__ */ new Map();
|
|
468
|
+
state;
|
|
469
|
+
constructor(state2, records2 = []) {
|
|
470
|
+
this.state = state2 ? { ...state2 } : { epoch: randomUUID(), admitted: 0 };
|
|
471
|
+
for (const record of records2) {
|
|
472
|
+
const operation = { ...record };
|
|
473
|
+
if (operation.status === "running") {
|
|
474
|
+
operation.status = "interrupted";
|
|
475
|
+
operation.error = { code: "INTERRUPTED", message: "Runtime stopped before operation completion was recorded; inspect state before retrying with a new request ID" };
|
|
476
|
+
}
|
|
477
|
+
this.records.set(this.key(operation.owner, operation.id), operation);
|
|
478
|
+
}
|
|
479
|
+
if (!state2 && this.records.size > OPERATIONS_PER_EPOCH || this.records.size > MAX_RETAINED_OPERATIONS + MAX_RUNNING_OPERATIONS) {
|
|
480
|
+
this.state = { epoch: randomUUID(), admitted: 0 };
|
|
481
|
+
while (this.records.size > OPERATIONS_PER_EPOCH) this.records.delete(this.records.keys().next().value);
|
|
482
|
+
} else if (!state2) this.state.admitted = this.records.size;
|
|
483
|
+
this.compactResults();
|
|
484
|
+
}
|
|
485
|
+
get epoch() {
|
|
486
|
+
return this.state.epoch;
|
|
487
|
+
}
|
|
488
|
+
checkpoint() {
|
|
489
|
+
return { ...this.state };
|
|
490
|
+
}
|
|
491
|
+
values() {
|
|
492
|
+
return this.records.values();
|
|
493
|
+
}
|
|
494
|
+
key(owner, id) {
|
|
495
|
+
return `${owner}:${id}`;
|
|
496
|
+
}
|
|
497
|
+
get(owner, id) {
|
|
498
|
+
return this.records.get(this.key(owner, id));
|
|
499
|
+
}
|
|
500
|
+
accepts(id) {
|
|
501
|
+
const epoch = operationEpoch(id);
|
|
502
|
+
return epoch === void 0 ? !id.startsWith("op:") : epoch === this.state.epoch || epoch === this.state.previousEpoch;
|
|
503
|
+
}
|
|
504
|
+
status(owner, id) {
|
|
505
|
+
return this.get(owner, id) ?? (this.accepts(id) ? { id, status: "unknown" } : { id, status: "expired", error: { code: "OPERATION_EXPIRED", message: "Operation history expired; inspect current state before submitting a new operation" } });
|
|
506
|
+
}
|
|
507
|
+
begin(owner, id, method) {
|
|
508
|
+
if (this.get(owner, id)) throw new RpcError("DUPLICATE_ID", "Operation already exists");
|
|
509
|
+
if (!this.accepts(id)) throw new RpcError("OPERATION_EXPIRED", "Operation ID expired; inspect current state before submitting a new operation");
|
|
510
|
+
if ([...this.records.values()].filter((operation2) => operation2.status === "running").length >= MAX_RUNNING_OPERATIONS)
|
|
511
|
+
throw new RpcError("BUSY", "Maximum 64 concurrent operations");
|
|
512
|
+
const operation = { id, owner, method, status: "running", startedAt: Date.now() };
|
|
513
|
+
this.records.set(this.key(owner, id), operation);
|
|
514
|
+
if (++this.state.admitted >= OPERATIONS_PER_EPOCH) {
|
|
515
|
+
this.state = { epoch: randomUUID(), previousEpoch: this.state.epoch, admitted: 0 };
|
|
516
|
+
let retained = 0;
|
|
517
|
+
for (const [key, record] of [...this.records].reverse())
|
|
518
|
+
if (record.status !== "running" && (!this.accepts(record.id) || ++retained > OPERATIONS_PER_EPOCH)) this.records.delete(key);
|
|
519
|
+
}
|
|
520
|
+
return operation;
|
|
521
|
+
}
|
|
522
|
+
complete(operation, result) {
|
|
523
|
+
operation.status = "completed";
|
|
524
|
+
operation.result = result;
|
|
525
|
+
this.compactResults();
|
|
526
|
+
}
|
|
527
|
+
fail(operation, error) {
|
|
528
|
+
operation.status = "failed";
|
|
529
|
+
operation.error = error;
|
|
530
|
+
delete operation.result;
|
|
531
|
+
this.compactResults();
|
|
532
|
+
}
|
|
533
|
+
compactResults() {
|
|
534
|
+
let remaining = MAX_OPERATION_RESULT_BYTES;
|
|
535
|
+
for (const operation of [...this.records.values()].reverse()) {
|
|
536
|
+
if (operation.error) {
|
|
537
|
+
operation.error.code = operation.error.code.slice(0, 128);
|
|
538
|
+
if (operation.error.message.length > 4096) {
|
|
539
|
+
operation.error.message = operation.error.message.slice(0, 4096);
|
|
540
|
+
operation.resultExpired = true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const result = operation.result;
|
|
544
|
+
const largeText = (operation.method === "fs.write" || operation.method === "collab.save") && typeof result?.text === "string" && result.text.length > remaining;
|
|
545
|
+
const body = largeText ? void 0 : JSON.stringify({ result: operation.result, data: operation.error?.data });
|
|
546
|
+
const bytes = body === void 0 ? remaining + 1 : body === "{}" ? 0 : Buffer.byteLength(body);
|
|
547
|
+
if (bytes > remaining) {
|
|
548
|
+
delete operation.result;
|
|
549
|
+
if (operation.error) delete operation.error.data;
|
|
550
|
+
operation.resultExpired = true;
|
|
551
|
+
} else remaining -= bytes;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
445
555
|
function killProcess(child) {
|
|
446
556
|
if (child.exitCode !== null) return;
|
|
447
557
|
try {
|
|
@@ -776,7 +886,7 @@ function validateJson(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
|
776
886
|
}
|
|
777
887
|
seen.delete(value);
|
|
778
888
|
}
|
|
779
|
-
function object$
|
|
889
|
+
function object$3(value) {
|
|
780
890
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a JSON object");
|
|
781
891
|
}
|
|
782
892
|
function strings$1(value) {
|
|
@@ -784,17 +894,17 @@ function strings$1(value) {
|
|
|
784
894
|
}
|
|
785
895
|
function validateFileAssociations(value) {
|
|
786
896
|
validateJson(value);
|
|
787
|
-
object$
|
|
897
|
+
object$3(value);
|
|
788
898
|
for (const [pattern, id] of Object.entries(value))
|
|
789
899
|
if (!pattern || pattern.length > 1024 || typeof id !== "string" || !/^[\w+-]+$/.test(id)) throw new Error("File associations map glob patterns to language IDs");
|
|
790
900
|
}
|
|
791
901
|
function validateLanguageServers(value) {
|
|
792
902
|
validateJson(value);
|
|
793
|
-
object$
|
|
903
|
+
object$3(value);
|
|
794
904
|
const keys = ["enabled", "selectors", "rootMarkers", "executable", "args", "env", "initializationOptions", "settings", "priority"];
|
|
795
905
|
for (const [id, config] of Object.entries(value)) {
|
|
796
906
|
if (!/^[a-zA-Z0-9._-]+$/.test(id)) throw new Error("Invalid language server ID");
|
|
797
|
-
object$
|
|
907
|
+
object$3(config);
|
|
798
908
|
for (const key of Object.keys(config)) if (!keys.includes(key)) throw new Error(`Unknown language server option: ${key}`);
|
|
799
909
|
if (config.enabled !== void 0 && typeof config.enabled !== "boolean") throw new Error("enabled must be boolean");
|
|
800
910
|
if (config.priority !== void 0 && (typeof config.priority !== "number" || !Number.isFinite(config.priority))) throw new Error("priority must be finite");
|
|
@@ -807,11 +917,11 @@ function validateLanguageServers(value) {
|
|
|
807
917
|
if (config.selectors !== void 0) {
|
|
808
918
|
if (!Array.isArray(config.selectors)) throw new Error("selectors must be an array");
|
|
809
919
|
for (const selector of config.selectors) {
|
|
810
|
-
object$
|
|
920
|
+
object$3(selector);
|
|
811
921
|
if (Object.keys(selector).some((key) => !["language", "pattern", "scheme"].includes(key)) || !Object.keys(selector).length || Object.values(selector).some((item) => typeof item !== "string") || selector.scheme !== void 0 && selector.scheme !== "file") throw new Error("Invalid document selector");
|
|
812
922
|
}
|
|
813
923
|
}
|
|
814
|
-
for (const key of ["env", "settings", "initializationOptions"]) if (config[key] !== void 0) object$
|
|
924
|
+
for (const key of ["env", "settings", "initializationOptions"]) if (config[key] !== void 0) object$3(config[key]);
|
|
815
925
|
if (config.env && Object.entries(config.env).some(([key, item]) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof item !== "string" || item.includes("\0"))) throw new Error("Invalid environment override");
|
|
816
926
|
const credentials = (item) => Boolean(item && typeof item === "object" && Object.entries(item).some(([key, child]) => /(?:^|[._-])licen[cs]e(?:[._-]?key)?$/i.test(key) || credentials(child)));
|
|
817
927
|
if (credentials(config.initializationOptions) || credentials(config.settings) || credentials(config.env)) throw new Error("Store license credentials in the runtime-local language server credential file");
|
|
@@ -822,7 +932,7 @@ const $id$1 = "https://oxbit.dev/schemas/settings.v1.schema.json";
|
|
|
822
932
|
const title$2 = "Oxbit Settings";
|
|
823
933
|
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.";
|
|
824
934
|
const type$2 = "object";
|
|
825
|
-
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@1.10.0\\"]"},"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.9.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."}}`);
|
|
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."}}`);
|
|
826
936
|
const propertyNames = { "anyOf": [{ "not": { "pattern": "^\\[.*\\]$" } }, { "pattern": "^\\[[^\\[\\]]+\\]$" }] };
|
|
827
937
|
const patternProperties = { "^\\[[^\\[\\]]+\\]$": { "$ref": "#/definitions/languageOverrides" } };
|
|
828
938
|
const additionalProperties$1 = true;
|
|
@@ -1000,7 +1110,7 @@ const ACP_PROVIDERS = [
|
|
|
1000
1110
|
id: "codex",
|
|
1001
1111
|
name: "Codex ACP",
|
|
1002
1112
|
command: "npx",
|
|
1003
|
-
args: ["-y", "@agentclientprotocol/codex-acp@
|
|
1113
|
+
args: ["-y", "@agentclientprotocol/codex-acp@2.2.2"],
|
|
1004
1114
|
setup: "Uses the Codex ACP adapter. Sign in through an advertised authentication method, or use your existing Codex credentials.",
|
|
1005
1115
|
url: "https://github.com/agentclientprotocol/codex-acp"
|
|
1006
1116
|
},
|
|
@@ -1016,7 +1126,7 @@ const ACP_PROVIDERS = [
|
|
|
1016
1126
|
id: "amp",
|
|
1017
1127
|
name: "Amp Agent ACP",
|
|
1018
1128
|
command: "npx",
|
|
1019
|
-
args: ["-y", "amp-acp@0.
|
|
1129
|
+
args: ["-y", "amp-acp@0.10.0"],
|
|
1020
1130
|
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.",
|
|
1021
1131
|
url: "https://github.com/tao12345666333/amp-acp"
|
|
1022
1132
|
}
|
|
@@ -1060,7 +1170,7 @@ const SDK_VERSION = "1.0.0";
|
|
|
1060
1170
|
function invalid(message) {
|
|
1061
1171
|
throw new RpcError("INVALID_TASK_CONFIG", message);
|
|
1062
1172
|
}
|
|
1063
|
-
function object$
|
|
1173
|
+
function object$2(value, label) {
|
|
1064
1174
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1065
1175
|
invalid(`${label} must be an object`);
|
|
1066
1176
|
return value;
|
|
@@ -1071,7 +1181,7 @@ function taskName(value) {
|
|
|
1071
1181
|
return value;
|
|
1072
1182
|
}
|
|
1073
1183
|
function stringMap(value, label) {
|
|
1074
|
-
const record = object$
|
|
1184
|
+
const record = object$2(value, label);
|
|
1075
1185
|
for (const [key, item] of Object.entries(record))
|
|
1076
1186
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || ["__proto__", "constructor", "prototype"].includes(key) || typeof item !== "string" || item.length > 32768 || item.includes("\0"))
|
|
1077
1187
|
invalid(`${label}: invalid environment entry ${key}`);
|
|
@@ -1087,13 +1197,13 @@ function port(value) {
|
|
|
1087
1197
|
number(value, 1, 65535, "port");
|
|
1088
1198
|
return value;
|
|
1089
1199
|
}
|
|
1090
|
-
const range2 = object$
|
|
1200
|
+
const range2 = object$2(value, "port range");
|
|
1091
1201
|
number(range2.min, 1, 65535, "port minimum");
|
|
1092
1202
|
number(range2.max, range2.min, 65535, "port maximum");
|
|
1093
1203
|
return { min: range2.min, max: range2.max };
|
|
1094
1204
|
}
|
|
1095
1205
|
function hooks(value) {
|
|
1096
|
-
const data = object$
|
|
1206
|
+
const data = object$2(value ?? {}, "worktree");
|
|
1097
1207
|
for (const key of Object.keys(data))
|
|
1098
1208
|
if (!taskPhases.includes(key))
|
|
1099
1209
|
invalid(`Unknown worktree phase: ${key}`);
|
|
@@ -1110,7 +1220,7 @@ function hooks(value) {
|
|
|
1110
1220
|
);
|
|
1111
1221
|
}
|
|
1112
1222
|
function definition(value) {
|
|
1113
|
-
const data = object$
|
|
1223
|
+
const data = object$2(value, "task");
|
|
1114
1224
|
const known = /* @__PURE__ */ new Set([
|
|
1115
1225
|
"command",
|
|
1116
1226
|
"type",
|
|
@@ -1152,7 +1262,7 @@ function definition(value) {
|
|
|
1152
1262
|
if (data.env !== void 0) stringMap(data.env, "task.env");
|
|
1153
1263
|
if (data.port !== void 0) port(data.port);
|
|
1154
1264
|
if (data.ports !== void 0) {
|
|
1155
|
-
const entries = Object.entries(object$
|
|
1265
|
+
const entries = Object.entries(object$2(data.ports, "ports"));
|
|
1156
1266
|
if (entries.length > 16) invalid("Maximum 16 named ports per task");
|
|
1157
1267
|
for (const [key, value2] of entries) {
|
|
1158
1268
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key) || key.toLowerCase() === "port")
|
|
@@ -1163,7 +1273,7 @@ function definition(value) {
|
|
|
1163
1273
|
invalid("Port names must be unique ignoring case");
|
|
1164
1274
|
}
|
|
1165
1275
|
if (data.ready !== void 0) {
|
|
1166
|
-
const ready = object$
|
|
1276
|
+
const ready = object$2(data.ready, "ready");
|
|
1167
1277
|
for (const key of Object.keys(ready))
|
|
1168
1278
|
if (!["pattern", "url", "timeoutMs", "intervalMs"].includes(key))
|
|
1169
1279
|
invalid(`Unknown readiness field: ${key}`);
|
|
@@ -1179,7 +1289,7 @@ function definition(value) {
|
|
|
1179
1289
|
number(ready.intervalMs, 100, 6e4, "ready.intervalMs");
|
|
1180
1290
|
}
|
|
1181
1291
|
if (data.restart !== void 0) {
|
|
1182
|
-
const restart = object$
|
|
1292
|
+
const restart = object$2(data.restart, "restart");
|
|
1183
1293
|
for (const key of Object.keys(restart))
|
|
1184
1294
|
if (!["policy", "maxAttempts", "delayMs"].includes(key))
|
|
1185
1295
|
invalid(`Unknown restart field: ${key}`);
|
|
@@ -1191,7 +1301,7 @@ function definition(value) {
|
|
|
1191
1301
|
number(restart.delayMs, 100, 6e4, "restart.delayMs");
|
|
1192
1302
|
}
|
|
1193
1303
|
if (data.stop !== void 0) {
|
|
1194
|
-
const stop2 = object$
|
|
1304
|
+
const stop2 = object$2(data.stop, "stop");
|
|
1195
1305
|
for (const key of Object.keys(stop2))
|
|
1196
1306
|
if (!["signal", "timeoutMs"].includes(key))
|
|
1197
1307
|
invalid(`Unknown stop field: ${key}`);
|
|
@@ -1208,7 +1318,7 @@ function json(text2) {
|
|
|
1208
1318
|
const errors = [];
|
|
1209
1319
|
const result = parse$1(text2, errors, { allowTrailingComma: true });
|
|
1210
1320
|
if (errors.length) invalid(`Invalid JSON near offset ${errors[0].offset}`);
|
|
1211
|
-
return object$
|
|
1321
|
+
return object$2(result, "configuration");
|
|
1212
1322
|
}
|
|
1213
1323
|
function editJson(text2, location, value) {
|
|
1214
1324
|
const tree = value === void 0 ? parseTree(text2) : void 0;
|
|
@@ -1410,7 +1520,7 @@ function readSource(kind, text2) {
|
|
|
1410
1520
|
);
|
|
1411
1521
|
} else {
|
|
1412
1522
|
const data = json(text2);
|
|
1413
|
-
const meta = object$
|
|
1523
|
+
const meta = object$2(data.oxbit ?? {}, "oxbit metadata");
|
|
1414
1524
|
result.env = stringMap(
|
|
1415
1525
|
(kind === "oxbit" ? data.env : meta.env) ?? {},
|
|
1416
1526
|
"env"
|
|
@@ -1430,7 +1540,7 @@ function readSource(kind, text2) {
|
|
|
1430
1540
|
].includes(key))
|
|
1431
1541
|
invalid(`Unknown Oxbit configuration field: ${key}`);
|
|
1432
1542
|
for (const [name, task] of Object.entries(
|
|
1433
|
-
object$
|
|
1543
|
+
object$2(data.tasks ?? {}, "tasks")
|
|
1434
1544
|
))
|
|
1435
1545
|
add(name, definition(task));
|
|
1436
1546
|
} else if (kind === "paseo") {
|
|
@@ -1442,9 +1552,9 @@ function readSource(kind, text2) {
|
|
|
1442
1552
|
...result.worktree
|
|
1443
1553
|
};
|
|
1444
1554
|
for (const [name, value] of Object.entries(
|
|
1445
|
-
object$
|
|
1555
|
+
object$2(data.scripts ?? {}, "scripts")
|
|
1446
1556
|
)) {
|
|
1447
|
-
const script = object$
|
|
1557
|
+
const script = object$2(value, name);
|
|
1448
1558
|
add(
|
|
1449
1559
|
name,
|
|
1450
1560
|
definition({
|
|
@@ -1496,10 +1606,10 @@ function readSource(kind, text2) {
|
|
|
1496
1606
|
invalid("Only VS Code tasks version 2.0.0 is supported");
|
|
1497
1607
|
if (!Array.isArray(data.tasks)) invalid("VS Code tasks must be an array");
|
|
1498
1608
|
for (const raw of data.tasks) {
|
|
1499
|
-
const entry = object$
|
|
1609
|
+
const entry = object$2(raw, "VS Code task"), platform = process.platform === "darwin" ? "osx" : process.platform === "win32" ? "windows" : "linux";
|
|
1500
1610
|
const item = { ...entry, ...entry[platform] };
|
|
1501
1611
|
const args = (item.args ?? []).map(
|
|
1502
|
-
(arg) => typeof arg === "string" ? arg : object$
|
|
1612
|
+
(arg) => typeof arg === "string" ? arg : object$2(arg, "argument").value
|
|
1503
1613
|
);
|
|
1504
1614
|
const opts = { ...data.options, ...item.options };
|
|
1505
1615
|
let command = item.command ?? data.command ?? "", execution = item.type === "process" ? "process" : "shell";
|
|
@@ -1538,7 +1648,7 @@ function readSource(kind, text2) {
|
|
|
1538
1648
|
}
|
|
1539
1649
|
} else {
|
|
1540
1650
|
for (const [name, script] of Object.entries(
|
|
1541
|
-
object$
|
|
1651
|
+
object$2(data.scripts ?? {}, "scripts")
|
|
1542
1652
|
)) {
|
|
1543
1653
|
if (typeof script !== "string" && !(kind === "composer" && Array.isArray(script)))
|
|
1544
1654
|
continue;
|
|
@@ -6527,8 +6637,8 @@ class Collaboration {
|
|
|
6527
6637
|
)
|
|
6528
6638
|
);
|
|
6529
6639
|
}
|
|
6530
|
-
bytes(encoded) {
|
|
6531
|
-
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encoded) || encoded.length >
|
|
6640
|
+
bytes(encoded, limit2 = MAX_MESSAGE_BYTES) {
|
|
6641
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encoded) || encoded.length > limit2)
|
|
6532
6642
|
throw new RpcError("INVALID_PARAMS", "Invalid collaboration update");
|
|
6533
6643
|
return Buffer.from(encoded, "base64");
|
|
6534
6644
|
}
|
|
@@ -6564,14 +6674,14 @@ class Collaboration {
|
|
|
6564
6674
|
}
|
|
6565
6675
|
async update(relative, connection, encoded) {
|
|
6566
6676
|
const room = await this.member(relative, connection);
|
|
6567
|
-
const bytes = this.bytes(encoded);
|
|
6677
|
+
const bytes = this.bytes(encoded, MAX_COLLABORATION_UPDATE_BYTES);
|
|
6568
6678
|
try {
|
|
6569
|
-
if (room.doc.getText("content").length + bytes.length >
|
|
6679
|
+
if (room.doc.getText("content").length + bytes.length > MAX_FILE_BYTES) {
|
|
6570
6680
|
const candidate = new Y.Doc();
|
|
6571
6681
|
try {
|
|
6572
6682
|
Y.applyUpdate(candidate, Y.encodeStateAsUpdate(room.doc));
|
|
6573
6683
|
Y.applyUpdate(candidate, bytes);
|
|
6574
|
-
if (candidate.getText("content").length >
|
|
6684
|
+
if (candidate.getText("content").length > MAX_FILE_BYTES) throw new RpcError("FILE_TOO_LARGE", "Shared documents are limited to 20 MiB");
|
|
6575
6685
|
} finally {
|
|
6576
6686
|
candidate.destroy();
|
|
6577
6687
|
}
|
|
@@ -6740,7 +6850,7 @@ class Collaboration {
|
|
|
6740
6850
|
}
|
|
6741
6851
|
}
|
|
6742
6852
|
const terminal = (state2) => !["pending", "running", "unknown"].includes(state2);
|
|
6743
|
-
const object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6853
|
+
const object$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6744
6854
|
const str = (value, max = 4096) => typeof value === "string" ? value.slice(0, max) : void 0;
|
|
6745
6855
|
const identity = (value) => typeof value === "string" && value.length > 0 && value.length <= 512 && !value.includes("\0");
|
|
6746
6856
|
const preview = (value, max = 8192) => value === void 0 ? void 0 : (typeof value === "string" ? value : JSON.stringify(value)).slice(0, max);
|
|
@@ -6770,11 +6880,11 @@ ${item.newText ?? ""}` : JSON.stringify(item))
|
|
|
6770
6880
|
).join("\n");
|
|
6771
6881
|
}
|
|
6772
6882
|
function normalizeDelegation(provider, update) {
|
|
6773
|
-
const raw = object(update.rawInput), meta = object(update._meta?.codex);
|
|
6883
|
+
const raw = object$1(update.rawInput), meta = object$1(update._meta?.codex);
|
|
6774
6884
|
const toolId = update.toolCallId;
|
|
6775
6885
|
if (!identity(toolId)) return [];
|
|
6776
6886
|
if (provider === "codex") {
|
|
6777
|
-
const collab = object(meta.collaboration), sub = object(meta.subagent);
|
|
6887
|
+
const collab = object$1(meta.collaboration), sub = object$1(meta.subagent);
|
|
6778
6888
|
if (identity(sub.threadId))
|
|
6779
6889
|
return [
|
|
6780
6890
|
{
|
|
@@ -6788,7 +6898,7 @@ function normalizeDelegation(provider, update) {
|
|
|
6788
6898
|
];
|
|
6789
6899
|
if (!identity(collab.tool)) return [];
|
|
6790
6900
|
const receivers = Array.isArray(collab.receiverThreadIds) ? collab.receiverThreadIds.filter(identity) : [];
|
|
6791
|
-
const states = object(raw.agentsStates);
|
|
6901
|
+
const states = object$1(raw.agentsStates);
|
|
6792
6902
|
const ids = [
|
|
6793
6903
|
.../* @__PURE__ */ new Set([...receivers, ...Object.keys(states).filter(identity)])
|
|
6794
6904
|
];
|
|
@@ -6816,8 +6926,8 @@ function normalizeDelegation(provider, update) {
|
|
|
6816
6926
|
}));
|
|
6817
6927
|
}
|
|
6818
6928
|
if (provider === "cursor" && update.taskMeta) {
|
|
6819
|
-
const task = object(update.taskMeta), output = object(update.rawOutput);
|
|
6820
|
-
const outcome = object(task.outcome ?? output.outcome);
|
|
6929
|
+
const task = object$1(update.taskMeta), output = object$1(update.rawOutput);
|
|
6930
|
+
const outcome = object$1(task.outcome ?? output.outcome);
|
|
6821
6931
|
const outcomeName = typeof (task.outcome ?? output.outcome) === "string" ? task.outcome ?? output.outcome : outcome.outcome;
|
|
6822
6932
|
return [
|
|
6823
6933
|
{
|
|
@@ -7237,7 +7347,273 @@ class SubagentRegistry {
|
|
|
7237
7347
|
}
|
|
7238
7348
|
}
|
|
7239
7349
|
}
|
|
7350
|
+
let cached;
|
|
7351
|
+
function runtimeVersion() {
|
|
7352
|
+
if (cached) return cached;
|
|
7353
|
+
return cached = "0.7.0";
|
|
7354
|
+
}
|
|
7355
|
+
const MAX_REQUEST_BYTES = 1024 * 1024;
|
|
7356
|
+
const MAX_CONCURRENT_REQUESTS = 16;
|
|
7357
|
+
const PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26"];
|
|
7358
|
+
const SERVER_NAME = "oxbit_workspace";
|
|
7359
|
+
const NATIVE_AGENT_TOOLS = [
|
|
7360
|
+
{
|
|
7361
|
+
name: "oxbit_get_workspace",
|
|
7362
|
+
description: "Get the workspace, active editor, and current selection when an editor is connected.",
|
|
7363
|
+
inputSchema: { type: "object", properties: {} }
|
|
7364
|
+
},
|
|
7365
|
+
{
|
|
7366
|
+
name: "oxbit_list_files",
|
|
7367
|
+
description: "List files in a workspace directory. Paths are workspace relative or absolute within the workspace.",
|
|
7368
|
+
inputSchema: { type: "object", properties: { path: { type: "string", description: "Directory path; defaults to the workspace root." } } }
|
|
7369
|
+
},
|
|
7370
|
+
{
|
|
7371
|
+
name: "oxbit_read_file",
|
|
7372
|
+
description: "Read a bounded range of a workspace file, including live unsaved editor content. Paths are workspace relative or absolute within the workspace.",
|
|
7373
|
+
inputSchema: { type: "object", properties: { path: { type: "string" }, line: { type: "integer", minimum: 1 }, limit: { type: "integer", minimum: 1 } }, required: ["path"] }
|
|
7374
|
+
},
|
|
7375
|
+
{
|
|
7376
|
+
name: "oxbit_get_diagnostics",
|
|
7377
|
+
description: "Get live editor diagnostics for a workspace file or the workspace. Requires a connected editor; headless runtimes have no diagnostics.",
|
|
7378
|
+
inputSchema: { type: "object", properties: { path: { type: "string", description: "Optional workspace file path." } } }
|
|
7379
|
+
},
|
|
7380
|
+
{
|
|
7381
|
+
name: "oxbit_open_file",
|
|
7382
|
+
description: "Open a workspace file in the editor at an optional line.",
|
|
7383
|
+
inputSchema: { type: "object", properties: { path: { type: "string" }, line: { type: "integer", minimum: 1 } }, required: ["path"] }
|
|
7384
|
+
},
|
|
7385
|
+
{
|
|
7386
|
+
name: "oxbit_propose_edit",
|
|
7387
|
+
description: "Propose replacement content for a workspace file. The tool waits for engineer review and returns the decision.",
|
|
7388
|
+
inputSchema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] }
|
|
7389
|
+
},
|
|
7390
|
+
{
|
|
7391
|
+
name: "oxbit_update_plan",
|
|
7392
|
+
description: "Publish the current task plan with at most 50 entries.",
|
|
7393
|
+
inputSchema: { type: "object", properties: { entries: { type: "array", maxItems: 50, items: { type: "object", properties: { content: { type: "string" }, status: { type: "string", enum: ["pending", "in_progress", "completed"] }, priority: { type: "string" } }, required: ["content", "status"] } } }, required: ["entries"] }
|
|
7394
|
+
}
|
|
7395
|
+
];
|
|
7396
|
+
const STDIO_PROXY = String.raw`
|
|
7397
|
+
const readline = require('node:readline');
|
|
7398
|
+
const input = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
7399
|
+
const pending = new Set();
|
|
7400
|
+
let closing = false;
|
|
7401
|
+
const maxBytes = 1024 * 1024;
|
|
7402
|
+
const send = (message) => {
|
|
7403
|
+
if (!closing) process.stdout.write(JSON.stringify(message) + '\n');
|
|
7404
|
+
};
|
|
7405
|
+
const fail = (id, message) => send({ jsonrpc: '2.0', id: id ?? null, error: { code: -32603, message } });
|
|
7406
|
+
input.on('line', (line) => {
|
|
7407
|
+
if (Buffer.byteLength(line) > maxBytes) return fail(null, 'MCP input line is too large');
|
|
7408
|
+
let message;
|
|
7409
|
+
try { message = JSON.parse(line); }
|
|
7410
|
+
catch { return send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Invalid JSON' } }); }
|
|
7411
|
+
const hasId = message !== null && typeof message === 'object' && Object.prototype.hasOwnProperty.call(message, 'id');
|
|
7412
|
+
if (pending.size >= 16) {
|
|
7413
|
+
if (hasId) fail(message.id, 'MCP proxy is busy');
|
|
7414
|
+
return;
|
|
7415
|
+
}
|
|
7416
|
+
const controller = new AbortController();
|
|
7417
|
+
pending.add(controller);
|
|
7418
|
+
void (async () => {
|
|
7419
|
+
try {
|
|
7420
|
+
const response = await fetch(process.env.OXBIT_MCP_URL, {
|
|
7421
|
+
method: 'POST',
|
|
7422
|
+
headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', authorization: 'Bearer ' + process.env.OXBIT_MCP_TOKEN },
|
|
7423
|
+
body: line,
|
|
7424
|
+
signal: controller.signal,
|
|
7425
|
+
});
|
|
7426
|
+
if (!hasId) { await response.body?.cancel(); return; }
|
|
7427
|
+
const length = Number(response.headers.get('content-length'));
|
|
7428
|
+
if (length > maxBytes) throw new Error('MCP response is too large');
|
|
7429
|
+
const reader = response.body?.getReader();
|
|
7430
|
+
if (!reader) throw new Error(response.ok ? 'MCP proxy returned an empty response' : 'MCP proxy HTTP ' + response.status);
|
|
7431
|
+
const chunks = [];
|
|
7432
|
+
let size = 0;
|
|
7433
|
+
while (true) {
|
|
7434
|
+
const { done, value } = await reader.read();
|
|
7435
|
+
if (done) break;
|
|
7436
|
+
size += value.byteLength;
|
|
7437
|
+
if (size > maxBytes) { await reader.cancel(); throw new Error('MCP response is too large'); }
|
|
7438
|
+
chunks.push(value);
|
|
7439
|
+
}
|
|
7440
|
+
let result;
|
|
7441
|
+
try { result = JSON.parse(Buffer.concat(chunks, size).toString('utf8')); }
|
|
7442
|
+
catch { throw new Error(response.ok ? 'MCP proxy returned invalid JSON' : 'MCP proxy HTTP ' + response.status); }
|
|
7443
|
+
if (result?.jsonrpc !== '2.0' || result.id !== message.id || (!result.result && !result.error)) throw new Error(response.ok ? 'MCP proxy returned an invalid response' : 'MCP proxy HTTP ' + response.status);
|
|
7444
|
+
if (!response.ok && !result.error) throw new Error('MCP proxy HTTP ' + response.status);
|
|
7445
|
+
send(result);
|
|
7446
|
+
} catch (error) {
|
|
7447
|
+
if (hasId && !closing) fail(message.id, error instanceof Error ? error.message : String(error));
|
|
7448
|
+
} finally {
|
|
7449
|
+
pending.delete(controller);
|
|
7450
|
+
}
|
|
7451
|
+
})();
|
|
7452
|
+
});
|
|
7453
|
+
input.on('close', () => {
|
|
7454
|
+
closing = true;
|
|
7455
|
+
for (const controller of pending) controller.abort();
|
|
7456
|
+
});`;
|
|
7457
|
+
function object(value) {
|
|
7458
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7459
|
+
}
|
|
7460
|
+
function validateArguments(name, args) {
|
|
7461
|
+
if (!NATIVE_AGENT_TOOLS.some((tool) => tool.name === name)) return "Unknown tool";
|
|
7462
|
+
const keys = Object.keys(args);
|
|
7463
|
+
const allowed = {
|
|
7464
|
+
oxbit_get_workspace: [],
|
|
7465
|
+
oxbit_list_files: ["path"],
|
|
7466
|
+
oxbit_read_file: ["path", "line", "limit"],
|
|
7467
|
+
oxbit_get_diagnostics: ["path"],
|
|
7468
|
+
oxbit_open_file: ["path", "line"],
|
|
7469
|
+
oxbit_propose_edit: ["path", "content"],
|
|
7470
|
+
oxbit_update_plan: ["entries"]
|
|
7471
|
+
};
|
|
7472
|
+
if (keys.some((key) => !allowed[name].includes(key))) return "Unknown tool argument";
|
|
7473
|
+
if (["oxbit_read_file", "oxbit_open_file", "oxbit_propose_edit"].includes(name) && (typeof args.path !== "string" || !args.path)) return "path must be a nonempty string";
|
|
7474
|
+
if (["oxbit_list_files", "oxbit_get_diagnostics"].includes(name) && args.path !== void 0 && typeof args.path !== "string") return "path must be a string";
|
|
7475
|
+
if (args.line !== void 0 && (!Number.isSafeInteger(args.line) || args.line < 1)) return "line must be a positive integer";
|
|
7476
|
+
if (args.limit !== void 0 && (!Number.isSafeInteger(args.limit) || args.limit < 1)) return "limit must be a positive integer";
|
|
7477
|
+
if (name === "oxbit_propose_edit" && typeof args.content !== "string") return "content must be a string";
|
|
7478
|
+
if (name === "oxbit_update_plan" && (!Array.isArray(args.entries) || args.entries.length > 50 || args.entries.some((entry) => !object(entry) || typeof entry.content !== "string" || !["pending", "in_progress", "completed"].includes(String(entry.status)) || entry.priority !== void 0 && typeof entry.priority !== "string"))) return "entries must contain plan content and status";
|
|
7479
|
+
return void 0;
|
|
7480
|
+
}
|
|
7481
|
+
function reply(response, status2, body) {
|
|
7482
|
+
response.statusCode = status2;
|
|
7483
|
+
if (body === void 0) {
|
|
7484
|
+
response.end();
|
|
7485
|
+
return;
|
|
7486
|
+
}
|
|
7487
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
7488
|
+
response.end(JSON.stringify(body));
|
|
7489
|
+
}
|
|
7490
|
+
function rpcError(id, code, message) {
|
|
7491
|
+
return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
|
|
7492
|
+
}
|
|
7493
|
+
class AgentMCP {
|
|
7494
|
+
constructor(handler) {
|
|
7495
|
+
this.handler = handler;
|
|
7496
|
+
}
|
|
7497
|
+
handler;
|
|
7498
|
+
listener;
|
|
7499
|
+
starting;
|
|
7500
|
+
url;
|
|
7501
|
+
tokens = /* @__PURE__ */ new Map();
|
|
7502
|
+
activeRequests = 0;
|
|
7503
|
+
async server(connectionId, httpSupported) {
|
|
7504
|
+
if (!this.listener && !this.starting) this.starting = this.startListener();
|
|
7505
|
+
if (this.starting) await this.starting;
|
|
7506
|
+
let token = this.tokens.get(connectionId);
|
|
7507
|
+
if (!token) {
|
|
7508
|
+
token = randomBytes(32).toString("hex");
|
|
7509
|
+
this.tokens.set(connectionId, token);
|
|
7510
|
+
}
|
|
7511
|
+
if (httpSupported) return { name: SERVER_NAME, type: "http", url: this.url, headers: [{ name: "Authorization", value: `Bearer ${token}` }] };
|
|
7512
|
+
return { name: SERVER_NAME, command: process.execPath, args: ["-e", STDIO_PROXY], env: [
|
|
7513
|
+
{ name: "OXBIT_MCP_URL", value: this.url },
|
|
7514
|
+
{ name: "OXBIT_MCP_TOKEN", value: token },
|
|
7515
|
+
...process.versions.electron ? [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }] : []
|
|
7516
|
+
] };
|
|
7517
|
+
}
|
|
7518
|
+
async startListener() {
|
|
7519
|
+
const listener = createServer((request, response) => {
|
|
7520
|
+
void this.handle(request, response);
|
|
7521
|
+
});
|
|
7522
|
+
try {
|
|
7523
|
+
await new Promise((resolve, reject) => {
|
|
7524
|
+
listener.once("error", reject);
|
|
7525
|
+
listener.listen(0, "127.0.0.1", () => {
|
|
7526
|
+
listener.off("error", reject);
|
|
7527
|
+
resolve();
|
|
7528
|
+
});
|
|
7529
|
+
});
|
|
7530
|
+
const address = listener.address();
|
|
7531
|
+
if (!address || typeof address === "string") throw new Error("MCP listener has no TCP address");
|
|
7532
|
+
this.listener = listener;
|
|
7533
|
+
this.url = "http://127.0.0.1:" + address.port + "/mcp";
|
|
7534
|
+
} catch (error) {
|
|
7535
|
+
listener.close();
|
|
7536
|
+
throw error;
|
|
7537
|
+
} finally {
|
|
7538
|
+
this.starting = void 0;
|
|
7539
|
+
}
|
|
7540
|
+
}
|
|
7541
|
+
revoke(connectionId) {
|
|
7542
|
+
this.tokens.delete(connectionId);
|
|
7543
|
+
}
|
|
7544
|
+
async close() {
|
|
7545
|
+
this.tokens.clear();
|
|
7546
|
+
if (this.starting) await this.starting.catch(() => {
|
|
7547
|
+
});
|
|
7548
|
+
const listener = this.listener;
|
|
7549
|
+
this.listener = void 0;
|
|
7550
|
+
this.url = void 0;
|
|
7551
|
+
if (listener) await new Promise((resolve) => listener.close(() => resolve()));
|
|
7552
|
+
}
|
|
7553
|
+
async handle(request, response) {
|
|
7554
|
+
const address = this.listener?.address();
|
|
7555
|
+
if (!address || typeof address === "string") return reply(response, 503);
|
|
7556
|
+
const origin = `http://127.0.0.1:${address.port}`;
|
|
7557
|
+
if (request.headers.host !== `127.0.0.1:${address.port}` || request.headers.origin && request.headers.origin !== origin) return reply(response, 403);
|
|
7558
|
+
if (request.url !== "/mcp") return reply(response, 404);
|
|
7559
|
+
if (request.method !== "POST") return reply(response, 405);
|
|
7560
|
+
const authorization = request.headers.authorization;
|
|
7561
|
+
const token = authorization?.startsWith("Bearer ") ? authorization.slice(7) : void 0;
|
|
7562
|
+
const connectionId = token && [...this.tokens].find(([, candidate]) => candidate === token)?.[0];
|
|
7563
|
+
if (!connectionId) return reply(response, 401);
|
|
7564
|
+
if (this.activeRequests >= MAX_CONCURRENT_REQUESTS) return reply(response, 429);
|
|
7565
|
+
const contentLength = Number(request.headers["content-length"]);
|
|
7566
|
+
if (contentLength > MAX_REQUEST_BYTES) return reply(response, 413);
|
|
7567
|
+
this.activeRequests++;
|
|
7568
|
+
try {
|
|
7569
|
+
let length = 0;
|
|
7570
|
+
const chunks = [];
|
|
7571
|
+
for await (const chunk of request) {
|
|
7572
|
+
length += chunk.length;
|
|
7573
|
+
if (length > MAX_REQUEST_BYTES) return reply(response, 413);
|
|
7574
|
+
chunks.push(chunk);
|
|
7575
|
+
}
|
|
7576
|
+
let message;
|
|
7577
|
+
try {
|
|
7578
|
+
message = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
7579
|
+
} catch {
|
|
7580
|
+
return reply(response, 400, rpcError(null, -32700, "Invalid JSON"));
|
|
7581
|
+
}
|
|
7582
|
+
if (!object(message) || message.jsonrpc !== "2.0" || typeof message.method !== "string" || message.id !== void 0 && typeof message.id !== "string" && typeof message.id !== "number") return reply(response, 400, rpcError(object(message) ? message.id : null, -32600, "Invalid request"));
|
|
7583
|
+
const version = request.headers["mcp-protocol-version"];
|
|
7584
|
+
if (version && !PROTOCOL_VERSIONS.includes(version)) return reply(response, 400, rpcError(message.id, -32600, "Unsupported MCP protocol version"));
|
|
7585
|
+
if (message.id === void 0) return reply(response, 202);
|
|
7586
|
+
const id = message.id;
|
|
7587
|
+
if (message.method === "initialize") {
|
|
7588
|
+
const requested = object(message.params) && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0;
|
|
7589
|
+
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
|
+
}
|
|
7591
|
+
if (message.method === "ping") return reply(response, 200, { jsonrpc: "2.0", id, result: {} });
|
|
7592
|
+
if (message.method === "tools/list") return reply(response, 200, { jsonrpc: "2.0", id, result: { tools: NATIVE_AGENT_TOOLS } });
|
|
7593
|
+
if (message.method !== "tools/call") return reply(response, 200, rpcError(id, -32601, "Method not found"));
|
|
7594
|
+
const params = message.params;
|
|
7595
|
+
if (!object(params) || typeof params.name !== "string" || !object(params.arguments ?? {})) return reply(response, 200, rpcError(id, -32602, "Invalid tool call"));
|
|
7596
|
+
const args = params.arguments ?? {};
|
|
7597
|
+
const invalid2 = validateArguments(params.name, args);
|
|
7598
|
+
if (invalid2) return reply(response, 200, { jsonrpc: "2.0", id, result: { isError: true, content: [{ type: "text", text: invalid2 }] } });
|
|
7599
|
+
try {
|
|
7600
|
+
const value = await this.handler(connectionId, params.name, args);
|
|
7601
|
+
if (this.tokens.get(connectionId) !== token) return reply(response, 401);
|
|
7602
|
+
const structuredContent = object(value) ? value : { value: value ?? null };
|
|
7603
|
+
return reply(response, 200, { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(value ?? null) }], structuredContent } });
|
|
7604
|
+
} catch (error) {
|
|
7605
|
+
if (this.tokens.get(connectionId) !== token) return reply(response, 401);
|
|
7606
|
+
return reply(response, 200, { jsonrpc: "2.0", id, result: { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] } });
|
|
7607
|
+
}
|
|
7608
|
+
} catch (error) {
|
|
7609
|
+
if (!response.writableEnded) reply(response, 400, rpcError(null, -32600, error instanceof Error ? error.message : String(error)));
|
|
7610
|
+
} finally {
|
|
7611
|
+
this.activeRequests--;
|
|
7612
|
+
}
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7240
7615
|
const MAX_BYTES = 1024 * 1024;
|
|
7616
|
+
const EDITOR_UNSUPPORTED = "The connected editor does not support this tool; update Oxbit";
|
|
7241
7617
|
function strings(value) {
|
|
7242
7618
|
if (!Array.isArray(value) || value.length > 128 || value.some(
|
|
7243
7619
|
(v) => typeof v !== "string" || v.length > 16384 || v.includes("\0")
|
|
@@ -7249,13 +7625,17 @@ function strings(value) {
|
|
|
7249
7625
|
return value;
|
|
7250
7626
|
}
|
|
7251
7627
|
class AgentACP {
|
|
7252
|
-
constructor(files, emit) {
|
|
7628
|
+
constructor(files, emit, stateChanged = () => {
|
|
7629
|
+
}) {
|
|
7253
7630
|
this.files = files;
|
|
7254
7631
|
this.emit = emit;
|
|
7632
|
+
this.stateChanged = stateChanged;
|
|
7255
7633
|
}
|
|
7256
7634
|
files;
|
|
7257
7635
|
emit;
|
|
7636
|
+
stateChanged;
|
|
7258
7637
|
agents = /* @__PURE__ */ new Map();
|
|
7638
|
+
mcp = new AgentMCP((id, name, args) => this.tool(id, name, args));
|
|
7259
7639
|
get(owner, id) {
|
|
7260
7640
|
const agent = this.agents.get(id);
|
|
7261
7641
|
if (!agent || agent.owner !== owner || agent.stopped)
|
|
@@ -7290,15 +7670,89 @@ class AgentACP {
|
|
|
7290
7670
|
});
|
|
7291
7671
|
}
|
|
7292
7672
|
publish(agent, event, params) {
|
|
7293
|
-
|
|
7673
|
+
const payload = { id: agent.connection.id, ...params, seq: ++agent.eventSequence };
|
|
7674
|
+
if (["acp.update", "acp.subagent", "acp.terminal", "acp.turnStarted", "acp.turnEnded"].includes(event)) {
|
|
7675
|
+
const entry = JSON.parse(JSON.stringify({ name: event, params: payload }));
|
|
7676
|
+
agent.events.push(entry);
|
|
7677
|
+
agent.eventBytes += Buffer.byteLength(JSON.stringify(entry));
|
|
7678
|
+
while (agent.events.length > 2e3 || agent.eventBytes > 2 * MAX_BYTES) {
|
|
7679
|
+
agent.eventBytes -= Buffer.byteLength(JSON.stringify(agent.events.shift()));
|
|
7680
|
+
agent.truncated = true;
|
|
7681
|
+
}
|
|
7682
|
+
}
|
|
7683
|
+
if (agent.client) this.emit(agent.client, event, payload);
|
|
7684
|
+
this.stateChanged();
|
|
7685
|
+
}
|
|
7686
|
+
list(owner) {
|
|
7687
|
+
return { sessions: [...this.agents.values()].filter((a) => a.owner === owner && !a.stopped).map((a) => ({
|
|
7688
|
+
connection: a.connection,
|
|
7689
|
+
busy: a.busy,
|
|
7690
|
+
title: a.title,
|
|
7691
|
+
pendingRequests: a.approvals.size,
|
|
7692
|
+
queued: a.queue.length
|
|
7693
|
+
})) };
|
|
7694
|
+
}
|
|
7695
|
+
control(owner, id, client) {
|
|
7696
|
+
if (this.get(owner, id).client !== client)
|
|
7697
|
+
throw new RpcError("BUSY", "Attach this agent session before controlling it");
|
|
7698
|
+
}
|
|
7699
|
+
attach(owner, id, client = owner, editorTools = false) {
|
|
7700
|
+
const agent = this.get(owner, id);
|
|
7701
|
+
if (agent.client && agent.client !== client)
|
|
7702
|
+
this.emit(agent.client, "acp.attachedElsewhere", { id, reason: "This session was attached in another Oxbit window" });
|
|
7703
|
+
this.detach(client, id);
|
|
7704
|
+
agent.client = client;
|
|
7705
|
+
agent.editorTools = editorTools;
|
|
7706
|
+
if (!editorTools) this.rejectEditorRequests(agent, EDITOR_UNSUPPORTED);
|
|
7707
|
+
return {
|
|
7708
|
+
connection: agent.connection,
|
|
7709
|
+
busy: agent.busy,
|
|
7710
|
+
events: [...agent.events],
|
|
7711
|
+
requests: [...agent.approvals].map(([requestId, request]) => this.requestEvent(agent, requestId, request)),
|
|
7712
|
+
queue: [...agent.queue],
|
|
7713
|
+
queuePaused: agent.queuePaused,
|
|
7714
|
+
sequence: agent.eventSequence,
|
|
7715
|
+
truncated: agent.truncated
|
|
7716
|
+
};
|
|
7294
7717
|
}
|
|
7295
|
-
|
|
7718
|
+
detach(client, exceptId) {
|
|
7719
|
+
for (const agent of this.agents.values()) {
|
|
7720
|
+
if (agent.client !== client || agent.connection.id === exceptId) continue;
|
|
7721
|
+
agent.client = void 0;
|
|
7722
|
+
for (const [id, request] of agent.approvals) {
|
|
7723
|
+
if (request.method === "fs/read_text_file") {
|
|
7724
|
+
agent.approvals.delete(id);
|
|
7725
|
+
void this.readFile(request.params).then(request.resolve, request.reject);
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
this.rejectEditorRequests(agent, "The editor disconnected; attach an Oxbit window to use this tool");
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
rejectEditorRequests(agent, reason) {
|
|
7732
|
+
for (const [id, request] of agent.approvals) {
|
|
7733
|
+
if (!request.method.startsWith("oxbit/")) continue;
|
|
7734
|
+
agent.approvals.delete(id);
|
|
7735
|
+
request.reject(new Error(reason));
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
working(agent) {
|
|
7739
|
+
return agent.busy || agent.queueRunning || !!agent.approvals.size || !!agent.subagents?.activeCount || !!agent.queue.length;
|
|
7740
|
+
}
|
|
7741
|
+
get hasWork() {
|
|
7742
|
+
return [...this.agents.values()].some((a) => this.working(a));
|
|
7743
|
+
}
|
|
7744
|
+
async start(owner, launch, signal, client = owner) {
|
|
7296
7745
|
const preset2 = ACP_PROVIDERS.find((p) => p.id === launch.provider);
|
|
7297
7746
|
if (!preset2)
|
|
7298
7747
|
throw new RpcError("INVALID_PARAMS", "Choose Codex, Cursor, or Amp");
|
|
7299
7748
|
if (signal?.aborted) throw new Error("Agent connection cancelled");
|
|
7300
|
-
|
|
7301
|
-
|
|
7749
|
+
const owned = [...this.agents.values()].filter((a) => a.owner === owner);
|
|
7750
|
+
if (owned.length >= 3) {
|
|
7751
|
+
const idle = owned.find((a) => !a.client && !this.working(a));
|
|
7752
|
+
if (!idle)
|
|
7753
|
+
throw new RpcError("BUSY", "3 agents are running for this device; stop one in Runtime sessions");
|
|
7754
|
+
this.stopAgent(idle, "Stopped to start another agent");
|
|
7755
|
+
}
|
|
7302
7756
|
const command = requireString(
|
|
7303
7757
|
{ command: launch.command ?? preset2.command },
|
|
7304
7758
|
"command"
|
|
@@ -7316,6 +7770,8 @@ class AgentACP {
|
|
|
7316
7770
|
trackChild(child);
|
|
7317
7771
|
const agent = {
|
|
7318
7772
|
owner,
|
|
7773
|
+
client,
|
|
7774
|
+
editorTools: launch.clientCapabilities?.editorTools === true,
|
|
7319
7775
|
child,
|
|
7320
7776
|
connection: {
|
|
7321
7777
|
id: randomUUID(),
|
|
@@ -7330,7 +7786,15 @@ class AgentACP {
|
|
|
7330
7786
|
busy: false,
|
|
7331
7787
|
stopped: false,
|
|
7332
7788
|
sequence: 0,
|
|
7333
|
-
turn: 0
|
|
7789
|
+
turn: 0,
|
|
7790
|
+
events: [],
|
|
7791
|
+
eventBytes: 0,
|
|
7792
|
+
eventSequence: 0,
|
|
7793
|
+
truncated: false,
|
|
7794
|
+
title: "New conversation",
|
|
7795
|
+
queue: [],
|
|
7796
|
+
queuePaused: false,
|
|
7797
|
+
queueRunning: false
|
|
7334
7798
|
};
|
|
7335
7799
|
this.agents.set(agent.connection.id, agent);
|
|
7336
7800
|
const abort = () => this.stopAgent(agent, "Agent connection cancelled");
|
|
@@ -7390,7 +7854,7 @@ class AgentACP {
|
|
|
7390
7854
|
terminal: true,
|
|
7391
7855
|
...preset2.id === "codex" ? { subagents: {} } : {}
|
|
7392
7856
|
},
|
|
7393
|
-
clientInfo: { name: "oxbit", title: "Oxbit", version:
|
|
7857
|
+
clientInfo: { name: "oxbit", title: "Oxbit", version: runtimeVersion() }
|
|
7394
7858
|
});
|
|
7395
7859
|
if (initialized.protocolVersion !== 1)
|
|
7396
7860
|
throw new Error("Agent does not support ACP version 1");
|
|
@@ -7460,12 +7924,23 @@ class AgentACP {
|
|
|
7460
7924
|
const previous = { ...agent.connection };
|
|
7461
7925
|
const previousRegistry = agent.subagents;
|
|
7462
7926
|
const previousTerminals = agent.terminals;
|
|
7927
|
+
const previousEvents = agent.events;
|
|
7928
|
+
const previousBytes = agent.eventBytes;
|
|
7929
|
+
const previousTitle = agent.title;
|
|
7930
|
+
const previousTruncated = agent.truncated;
|
|
7463
7931
|
const target = method === "session/new" ? void 0 : requireString(params, "sessionId");
|
|
7464
7932
|
if (agent.approvals.size)
|
|
7465
7933
|
throw new Error(
|
|
7466
7934
|
"Resolve pending requests before changing conversations"
|
|
7467
7935
|
);
|
|
7936
|
+
if (agent.queue.length)
|
|
7937
|
+
throw new Error("Remove queued messages before changing conversations");
|
|
7468
7938
|
agent.busy = true;
|
|
7939
|
+
agent.events = [];
|
|
7940
|
+
agent.eventBytes = 0;
|
|
7941
|
+
agent.truncated = false;
|
|
7942
|
+
agent.title = "New conversation";
|
|
7943
|
+
agent.queuePaused = false;
|
|
7469
7944
|
agent.connection.sessionId = target;
|
|
7470
7945
|
agent.subagents = target ? this.registry(agent, target, true) : void 0;
|
|
7471
7946
|
agent.terminals = /* @__PURE__ */ new Map();
|
|
@@ -7473,7 +7948,7 @@ class AgentACP {
|
|
|
7473
7948
|
try {
|
|
7474
7949
|
const result = await this.request(agent, method, {
|
|
7475
7950
|
cwd: this.files.root,
|
|
7476
|
-
mcpServers: [],
|
|
7951
|
+
mcpServers: [await this.mcp.server(agent.connection.id, !!agent.connection.capabilities?.mcpCapabilities?.http)],
|
|
7477
7952
|
...target ? { sessionId: target } : {}
|
|
7478
7953
|
});
|
|
7479
7954
|
const sessionId2 = target ?? result?.sessionId;
|
|
@@ -7498,6 +7973,10 @@ class AgentACP {
|
|
|
7498
7973
|
killProcess(terminal2.child);
|
|
7499
7974
|
agent.terminals = previousTerminals;
|
|
7500
7975
|
agent.subagents = previousRegistry;
|
|
7976
|
+
agent.events = previousEvents;
|
|
7977
|
+
agent.eventBytes = previousBytes;
|
|
7978
|
+
agent.title = previousTitle;
|
|
7979
|
+
agent.truncated = previousTruncated;
|
|
7501
7980
|
throw error;
|
|
7502
7981
|
} finally {
|
|
7503
7982
|
agent.newSessionUpdates = void 0;
|
|
@@ -7568,35 +8047,119 @@ ${content}`
|
|
|
7568
8047
|
throw new Error("The message and attachments are too large");
|
|
7569
8048
|
if (agent.busy || agent.stopped || agent.connection.sessionId !== sessionId)
|
|
7570
8049
|
throw new Error("Agent connection is busy or ended");
|
|
8050
|
+
const messageId = params.messageId === void 0 ? randomUUID() : requireString(params, "messageId", 256);
|
|
7571
8051
|
agent.busy = true;
|
|
7572
8052
|
agent.turn++;
|
|
8053
|
+
if (agent.title === "New conversation") agent.title = text2.slice(0, 100);
|
|
8054
|
+
this.publish(agent, "acp.turnStarted", { messageId, text: text2, context: params.context ?? [] });
|
|
7573
8055
|
const abort = () => this.stopAgent(agent, "Agent request interrupted");
|
|
7574
8056
|
signal?.addEventListener("abort", abort, { once: true });
|
|
8057
|
+
let stopReason;
|
|
8058
|
+
let failure2;
|
|
7575
8059
|
try {
|
|
7576
|
-
|
|
8060
|
+
const result = await this.request(
|
|
7577
8061
|
agent,
|
|
7578
8062
|
method,
|
|
7579
8063
|
{ sessionId, prompt },
|
|
7580
8064
|
30 * 60 * 1e3
|
|
7581
8065
|
);
|
|
8066
|
+
stopReason = result?.stopReason;
|
|
8067
|
+
if (stopReason !== "end_turn" && stopReason !== "cancelled") agent.queuePaused = true;
|
|
8068
|
+
return result;
|
|
8069
|
+
} catch (error) {
|
|
8070
|
+
failure2 = error instanceof Error ? error.message : String(error);
|
|
8071
|
+
agent.queuePaused = true;
|
|
8072
|
+
throw error;
|
|
7582
8073
|
} finally {
|
|
7583
8074
|
this.expireRequests(agent, sessionId);
|
|
7584
8075
|
agent.busy = false;
|
|
7585
8076
|
signal?.removeEventListener("abort", abort);
|
|
8077
|
+
if (!agent.stopped) {
|
|
8078
|
+
this.publish(agent, "acp.turnEnded", { stopReason, error: failure2 });
|
|
8079
|
+
this.publishQueue(agent);
|
|
8080
|
+
queueMicrotask(() => this.runQueue(agent));
|
|
8081
|
+
}
|
|
7586
8082
|
}
|
|
7587
8083
|
}
|
|
7588
|
-
|
|
8084
|
+
publishQueue(agent) {
|
|
8085
|
+
this.publish(agent, "acp.queue", { queue: agent.queue, paused: agent.queuePaused });
|
|
8086
|
+
return { queue: [...agent.queue], paused: agent.queuePaused };
|
|
8087
|
+
}
|
|
8088
|
+
async enqueue(owner, id, params, interrupt = false) {
|
|
8089
|
+
const agent = this.get(owner, id);
|
|
8090
|
+
if (!agent.connection.sessionId) throw new Error("Start a conversation first");
|
|
8091
|
+
const text2 = requireString(params, "text", MAX_BYTES).trim();
|
|
8092
|
+
if (!text2) throw new Error("Enter a message");
|
|
8093
|
+
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
|
+
}
|
|
8102
|
+
if (agent.stopped) throw new Error("Agent connection has ended");
|
|
8103
|
+
if (agent.queue.some((p) => p.id === messageId)) return this.publishQueue(agent);
|
|
8104
|
+
const prompt = { id: messageId, text: text2, ...params.context ? { context: structuredClone(params.context) } : {} };
|
|
8105
|
+
if (agent.queue.length >= 16 || Buffer.byteLength(JSON.stringify([...agent.queue, prompt])) > MAX_BYTES)
|
|
8106
|
+
throw new Error("Queued messages exceed the limit; remove a message first");
|
|
8107
|
+
if (interrupt) {
|
|
8108
|
+
agent.queue.unshift(prompt);
|
|
8109
|
+
agent.queuePaused = false;
|
|
8110
|
+
if (agent.busy) this.cancel(owner, id, true);
|
|
8111
|
+
} else agent.queue.push(prompt);
|
|
8112
|
+
const result = this.publishQueue(agent);
|
|
8113
|
+
queueMicrotask(() => this.runQueue(agent));
|
|
8114
|
+
return result;
|
|
8115
|
+
}
|
|
8116
|
+
dequeue(owner, id, promptId) {
|
|
8117
|
+
const agent = this.get(owner, id);
|
|
8118
|
+
const index = agent.queue.findIndex((p) => p.id === promptId);
|
|
8119
|
+
if (index < 0) throw new Error("The queued message has already started or was removed");
|
|
8120
|
+
const [prompt] = agent.queue.splice(index, 1);
|
|
8121
|
+
this.publishQueue(agent);
|
|
8122
|
+
return { prompt };
|
|
8123
|
+
}
|
|
8124
|
+
resumeQueue(owner, id) {
|
|
7589
8125
|
const agent = this.get(owner, id);
|
|
8126
|
+
agent.queuePaused = false;
|
|
8127
|
+
this.publishQueue(agent);
|
|
8128
|
+
queueMicrotask(() => this.runQueue(agent));
|
|
8129
|
+
return {};
|
|
8130
|
+
}
|
|
8131
|
+
runQueue(agent) {
|
|
8132
|
+
if (agent.busy || agent.queueRunning || agent.stopped || agent.queuePaused || !agent.queue.length) return;
|
|
8133
|
+
agent.queueRunning = true;
|
|
8134
|
+
const prompt = agent.queue.shift();
|
|
8135
|
+
this.publishQueue(agent);
|
|
8136
|
+
void this.call(agent.owner, agent.connection.id, "session/prompt", {
|
|
8137
|
+
text: prompt.text,
|
|
8138
|
+
context: prompt.context,
|
|
8139
|
+
messageId: prompt.id
|
|
8140
|
+
}).catch(() => {
|
|
8141
|
+
if (agent.stopped) return;
|
|
8142
|
+
agent.queuePaused = true;
|
|
8143
|
+
agent.queue.unshift(prompt);
|
|
8144
|
+
this.publishQueue(agent);
|
|
8145
|
+
}).finally(() => {
|
|
8146
|
+
agent.queueRunning = false;
|
|
8147
|
+
this.runQueue(agent);
|
|
8148
|
+
});
|
|
8149
|
+
}
|
|
8150
|
+
cancel(owner, id, continueQueue = false) {
|
|
8151
|
+
const agent = this.get(owner, id);
|
|
8152
|
+
agent.queuePaused = !continueQueue;
|
|
8153
|
+
this.publishQueue(agent);
|
|
7590
8154
|
this.send(agent, {
|
|
7591
8155
|
method: "session/cancel",
|
|
7592
8156
|
params: { sessionId: agent.connection.sessionId }
|
|
7593
8157
|
});
|
|
7594
8158
|
for (const [requestId, request] of agent.approvals) {
|
|
7595
8159
|
if (request.params.sessionId !== agent.connection.sessionId) continue;
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
});
|
|
8160
|
+
if (request.method === "session/request_permission")
|
|
8161
|
+
request.resolve({ outcome: { outcome: "cancelled" } });
|
|
8162
|
+
else request.reject(new Error("Cancelled"));
|
|
7600
8163
|
agent.approvals.delete(requestId);
|
|
7601
8164
|
agent.subagents?.waiting(
|
|
7602
8165
|
request.params.sessionId,
|
|
@@ -7634,6 +8197,8 @@ ${content}`
|
|
|
7634
8197
|
routeUpdate(agent, sessionId, update) {
|
|
7635
8198
|
if (!update || typeof update !== "object" || !agent.subagents?.observe(sessionId, update))
|
|
7636
8199
|
return;
|
|
8200
|
+
if (sessionId === agent.connection.sessionId && update.sessionUpdate === "session_info_update" && typeof update.title === "string")
|
|
8201
|
+
agent.title = update.title.slice(0, 200);
|
|
7637
8202
|
this.publish(agent, "acp.update", {
|
|
7638
8203
|
rootSessionId: agent.subagents.root,
|
|
7639
8204
|
sessionId,
|
|
@@ -7650,11 +8215,9 @@ ${content}`
|
|
|
7650
8215
|
const requestIds = [];
|
|
7651
8216
|
for (const [id, request] of agent.approvals) {
|
|
7652
8217
|
if (request.params.sessionId !== sessionId) continue;
|
|
7653
|
-
if (
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
...request.method === "session/request_permission" ? { result: { outcome: { outcome: "cancelled" } } } : { error: { code: -32800, message: "Session turn ended" } }
|
|
7657
|
-
});
|
|
8218
|
+
if (request.method === "session/request_permission")
|
|
8219
|
+
request.resolve({ outcome: { outcome: "cancelled" } });
|
|
8220
|
+
else request.reject(new Error("Session turn ended"));
|
|
7658
8221
|
agent.approvals.delete(id);
|
|
7659
8222
|
requestIds.push(id);
|
|
7660
8223
|
agent.subagents?.waiting(
|
|
@@ -7677,6 +8240,136 @@ ${content}`
|
|
|
7677
8240
|
await this.files.resolve(relative, missing2);
|
|
7678
8241
|
return relative;
|
|
7679
8242
|
}
|
|
8243
|
+
requestEvent(agent, requestId, request) {
|
|
8244
|
+
const sessionId = request.params.sessionId;
|
|
8245
|
+
return {
|
|
8246
|
+
id: agent.connection.id,
|
|
8247
|
+
requestId,
|
|
8248
|
+
sessionId,
|
|
8249
|
+
rootSessionId: agent.subagents.root,
|
|
8250
|
+
subagentId: agent.subagents.idForSession(sessionId) ?? agent.subagents.idForTool(sessionId, request.params.toolCallId ?? request.params.toolCall?.toolCallId),
|
|
8251
|
+
method: request.method,
|
|
8252
|
+
params: request.params
|
|
8253
|
+
};
|
|
8254
|
+
}
|
|
8255
|
+
async readFile(params) {
|
|
8256
|
+
const file = await this.files.read(params.path);
|
|
8257
|
+
const start = (params.line ?? 1) - 1;
|
|
8258
|
+
const content = params.line !== void 0 || params.limit !== void 0 ? file.text.split("\n").slice(start, params.limit ? start + params.limit : void 0).join("\n") : file.text;
|
|
8259
|
+
if (Buffer.byteLength(content) > MAX_BYTES)
|
|
8260
|
+
throw new Error("File content exceeds 1 MiB; request a smaller line range");
|
|
8261
|
+
return { content };
|
|
8262
|
+
}
|
|
8263
|
+
async clientRequest(agent, method, params) {
|
|
8264
|
+
const registry = agent.subagents;
|
|
8265
|
+
const sessionId = params.sessionId;
|
|
8266
|
+
if (!registry?.acceptsRequest(sessionId)) throw new Error("Unknown ACP session");
|
|
8267
|
+
if (agent.approvals.size >= 32) throw new Error("Too many pending agent requests");
|
|
8268
|
+
if (method.startsWith("fs/")) {
|
|
8269
|
+
params.path = await this.relative(params.path, method === "fs/write_text_file");
|
|
8270
|
+
for (const field of ["line", "limit"])
|
|
8271
|
+
if (params[field] !== void 0 && (!Number.isSafeInteger(params[field]) || params[field] < 1))
|
|
8272
|
+
throw new Error(`Invalid ${field}`);
|
|
8273
|
+
if (method === "fs/write_text_file") requireString(params, "content", MAX_BYTES);
|
|
8274
|
+
}
|
|
8275
|
+
if (agent.stopped || registry !== agent.subagents || !registry.acceptsRequest(sessionId))
|
|
8276
|
+
throw new Error("ACP session ended");
|
|
8277
|
+
if (!agent.client && method === "fs/read_text_file") return this.readFile(params);
|
|
8278
|
+
return new Promise((resolve, reject) => {
|
|
8279
|
+
const requestId = randomUUID();
|
|
8280
|
+
const request = { method, params, resolve, reject };
|
|
8281
|
+
agent.approvals.set(requestId, request);
|
|
8282
|
+
if (method !== "fs/read_text_file" && !method.startsWith("oxbit/"))
|
|
8283
|
+
registry.waiting(sessionId, requestId, true, params.toolCallId ?? params.toolCall?.toolCallId);
|
|
8284
|
+
this.publish(agent, "acp.request", this.requestEvent(agent, requestId, request));
|
|
8285
|
+
});
|
|
8286
|
+
}
|
|
8287
|
+
async tool(id, name, args) {
|
|
8288
|
+
const agent = this.agents.get(id);
|
|
8289
|
+
const sessionId = agent?.connection.sessionId;
|
|
8290
|
+
if (!agent || agent.stopped || !sessionId) throw new Error("Start an agent conversation first");
|
|
8291
|
+
const registry = agent.subagents;
|
|
8292
|
+
const toolCallId = `oxbit-${randomUUID()}`;
|
|
8293
|
+
const write = name === "oxbit_propose_edit";
|
|
8294
|
+
let relative;
|
|
8295
|
+
if (args.path !== void 0) {
|
|
8296
|
+
const input = requireString(args, "path");
|
|
8297
|
+
relative = path.isAbsolute(input) ? await this.relative(input, write) : input;
|
|
8298
|
+
await this.files.resolve(relative, write);
|
|
8299
|
+
}
|
|
8300
|
+
for (const key of ["line", "limit"])
|
|
8301
|
+
if (args[key] !== void 0 && (!Number.isSafeInteger(args[key]) || args[key] < 1))
|
|
8302
|
+
throw new Error(`Invalid ${key}`);
|
|
8303
|
+
const publish = (update) => {
|
|
8304
|
+
if (!agent.stopped && registry === agent.subagents)
|
|
8305
|
+
this.routeUpdate(agent, sessionId, update);
|
|
8306
|
+
};
|
|
8307
|
+
const editor = () => {
|
|
8308
|
+
if (agent.client && !agent.editorTools) throw new Error(EDITOR_UNSUPPORTED);
|
|
8309
|
+
return !!agent.client;
|
|
8310
|
+
};
|
|
8311
|
+
publish({
|
|
8312
|
+
sessionUpdate: "tool_call",
|
|
8313
|
+
toolCallId,
|
|
8314
|
+
title: name,
|
|
8315
|
+
kind: write ? "edit" : "read",
|
|
8316
|
+
status: "in_progress",
|
|
8317
|
+
rawInput: args,
|
|
8318
|
+
...relative ? { locations: [{ path: path.join(this.files.root, relative), line: args.line }] } : {}
|
|
8319
|
+
});
|
|
8320
|
+
try {
|
|
8321
|
+
let result;
|
|
8322
|
+
const params = { ...args, path: relative, sessionId, toolCallId };
|
|
8323
|
+
switch (name) {
|
|
8324
|
+
case "oxbit_get_workspace":
|
|
8325
|
+
result = {
|
|
8326
|
+
root: this.files.root,
|
|
8327
|
+
clientConnected: !!agent.client,
|
|
8328
|
+
...editor() ? await this.clientRequest(agent, "oxbit/workspace", params) : { activeFile: null, openFiles: [], selection: null, diagnostics: [] }
|
|
8329
|
+
};
|
|
8330
|
+
break;
|
|
8331
|
+
case "oxbit_list_files": {
|
|
8332
|
+
const entries = await this.files.list(relative ?? "");
|
|
8333
|
+
result = { entries: entries.slice(0, 1e3), truncated: entries.length > 1e3 };
|
|
8334
|
+
break;
|
|
8335
|
+
}
|
|
8336
|
+
case "oxbit_read_file":
|
|
8337
|
+
case "oxbit_propose_edit":
|
|
8338
|
+
if (relative === void 0) throw new Error("A workspace path is required");
|
|
8339
|
+
result = await this.clientRequest(agent, write ? "fs/write_text_file" : "fs/read_text_file", {
|
|
8340
|
+
...params,
|
|
8341
|
+
path: path.join(this.files.root, relative)
|
|
8342
|
+
});
|
|
8343
|
+
break;
|
|
8344
|
+
case "oxbit_get_diagnostics":
|
|
8345
|
+
result = editor() ? await this.clientRequest(agent, "oxbit/diagnostics", params) : { diagnostics: [], available: false, reason: "Attach an Oxbit editor for language diagnostics" };
|
|
8346
|
+
break;
|
|
8347
|
+
case "oxbit_open_file":
|
|
8348
|
+
if (relative === void 0) throw new Error("A workspace path is required");
|
|
8349
|
+
if (!editor()) throw new Error("Attach an Oxbit editor to open a file");
|
|
8350
|
+
result = await this.clientRequest(agent, "oxbit/open_file", params);
|
|
8351
|
+
break;
|
|
8352
|
+
case "oxbit_update_plan": {
|
|
8353
|
+
if (!Array.isArray(args.entries) || args.entries.length > 50) throw new Error("Provide up to 50 plan entries");
|
|
8354
|
+
const entries = args.entries.map((entry) => {
|
|
8355
|
+
const content = requireString(entry, "content", 2e3);
|
|
8356
|
+
if (!["pending", "in_progress", "completed"].includes(entry.status)) throw new Error("Invalid plan status");
|
|
8357
|
+
return { content, status: entry.status, ...typeof entry.priority === "string" ? { priority: entry.priority.slice(0, 40) } : {} };
|
|
8358
|
+
});
|
|
8359
|
+
publish({ sessionUpdate: "plan", entries });
|
|
8360
|
+
result = { entries };
|
|
8361
|
+
break;
|
|
8362
|
+
}
|
|
8363
|
+
default:
|
|
8364
|
+
throw new Error("Unknown Oxbit tool");
|
|
8365
|
+
}
|
|
8366
|
+
publish({ sessionUpdate: "tool_call_update", toolCallId, status: "completed", rawOutput: JSON.stringify(result ?? {}).slice(0, 8e3) });
|
|
8367
|
+
return result ?? {};
|
|
8368
|
+
} catch (error) {
|
|
8369
|
+
publish({ sessionUpdate: "tool_call_update", toolCallId, status: "failed", rawOutput: String(error) });
|
|
8370
|
+
throw error;
|
|
8371
|
+
}
|
|
8372
|
+
}
|
|
7680
8373
|
async message(agent, message) {
|
|
7681
8374
|
if (agent.stopped) return;
|
|
7682
8375
|
if (!message.method) {
|
|
@@ -7721,8 +8414,8 @@ ${content}`
|
|
|
7721
8414
|
throw new Error("Unknown ACP session");
|
|
7722
8415
|
params.sessionId = sessionId;
|
|
7723
8416
|
if (message.method.startsWith("terminal/")) {
|
|
7724
|
-
const
|
|
7725
|
-
this.send(agent, { id: message.id, result });
|
|
8417
|
+
const result2 = await this.terminal(agent, message.method, params);
|
|
8418
|
+
this.send(agent, { id: message.id, result: result2 });
|
|
7726
8419
|
return;
|
|
7727
8420
|
}
|
|
7728
8421
|
if (![
|
|
@@ -7738,46 +8431,8 @@ ${content}`
|
|
|
7738
8431
|
});
|
|
7739
8432
|
return;
|
|
7740
8433
|
}
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
if (message.method.startsWith("fs/")) {
|
|
7744
|
-
params.path = await this.relative(
|
|
7745
|
-
params.path,
|
|
7746
|
-
message.method === "fs/write_text_file"
|
|
7747
|
-
);
|
|
7748
|
-
for (const field of ["line", "limit"])
|
|
7749
|
-
if (params[field] !== void 0 && (!Number.isSafeInteger(params[field]) || params[field] < 1))
|
|
7750
|
-
throw new Error(`Invalid ${field}`);
|
|
7751
|
-
if (message.method === "fs/write_text_file")
|
|
7752
|
-
requireString(params, "content", MAX_BYTES);
|
|
7753
|
-
}
|
|
7754
|
-
if (agent.stopped) return;
|
|
7755
|
-
if (registry !== agent.subagents || !registry.acceptsRequest(sessionId))
|
|
7756
|
-
throw new Error("ACP session ended");
|
|
7757
|
-
const requestId = randomUUID();
|
|
7758
|
-
agent.approvals.set(requestId, {
|
|
7759
|
-
rpcId: message.id,
|
|
7760
|
-
method: message.method,
|
|
7761
|
-
params
|
|
7762
|
-
});
|
|
7763
|
-
if (message.method !== "fs/read_text_file")
|
|
7764
|
-
registry.waiting(
|
|
7765
|
-
sessionId,
|
|
7766
|
-
requestId,
|
|
7767
|
-
true,
|
|
7768
|
-
params.toolCallId ?? params.toolCall?.toolCallId
|
|
7769
|
-
);
|
|
7770
|
-
this.publish(agent, "acp.request", {
|
|
7771
|
-
sessionId,
|
|
7772
|
-
rootSessionId: registry.root,
|
|
7773
|
-
subagentId: registry.idForSession(sessionId) ?? registry.idForTool(
|
|
7774
|
-
sessionId,
|
|
7775
|
-
params.toolCallId ?? params.toolCall?.toolCallId
|
|
7776
|
-
),
|
|
7777
|
-
requestId,
|
|
7778
|
-
method: message.method,
|
|
7779
|
-
params
|
|
7780
|
-
});
|
|
8434
|
+
const result = await this.clientRequest(agent, message.method, params);
|
|
8435
|
+
if (!agent.stopped) this.send(agent, { id: message.id, result });
|
|
7781
8436
|
} catch (error) {
|
|
7782
8437
|
if (!agent.stopped)
|
|
7783
8438
|
this.send(agent, {
|
|
@@ -7796,10 +8451,8 @@ ${content}`
|
|
|
7796
8451
|
)))
|
|
7797
8452
|
throw new Error("Invalid permission response");
|
|
7798
8453
|
}
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
...error ? { error: { code: -32603, message: error.slice(0, 4096) } } : { result }
|
|
7802
|
-
});
|
|
8454
|
+
if (error) request.reject(new Error(error.slice(0, 4096)));
|
|
8455
|
+
else request.resolve(result);
|
|
7803
8456
|
agent.approvals.delete(requestId);
|
|
7804
8457
|
agent.subagents?.waiting(
|
|
7805
8458
|
request.params.sessionId,
|
|
@@ -7934,26 +8587,29 @@ ${content}`
|
|
|
7934
8587
|
pending.reject(new Error(reason));
|
|
7935
8588
|
}
|
|
7936
8589
|
agent.pending.clear();
|
|
8590
|
+
for (const approval of agent.approvals.values()) approval.reject(new Error(reason));
|
|
7937
8591
|
agent.approvals.clear();
|
|
7938
8592
|
for (const terminal2 of agent.terminals.values())
|
|
7939
8593
|
killProcess(terminal2.child);
|
|
7940
8594
|
agent.terminals.clear();
|
|
7941
8595
|
killProcess(agent.child);
|
|
7942
8596
|
this.agents.delete(agent.connection.id);
|
|
8597
|
+
this.mcp.revoke(agent.connection.id);
|
|
7943
8598
|
this.publish(agent, "acp.exit", { reason });
|
|
7944
8599
|
}
|
|
7945
8600
|
stop(owner, id) {
|
|
7946
8601
|
this.stopAgent(this.get(owner, id), "Agent disconnected");
|
|
7947
8602
|
return {};
|
|
7948
8603
|
}
|
|
7949
|
-
disconnect(owner) {
|
|
8604
|
+
disconnect(owner, client) {
|
|
7950
8605
|
for (const agent of this.agents.values())
|
|
7951
|
-
if (agent.owner === owner)
|
|
8606
|
+
if (agent.owner === owner && (client === void 0 || agent.client === client))
|
|
7952
8607
|
this.stopAgent(agent, "Agent client disconnected");
|
|
7953
8608
|
}
|
|
7954
8609
|
dispose() {
|
|
7955
8610
|
for (const agent of this.agents.values())
|
|
7956
8611
|
this.stopAgent(agent, "Agent tooling stopped");
|
|
8612
|
+
return this.mcp.close();
|
|
7957
8613
|
}
|
|
7958
8614
|
}
|
|
7959
8615
|
var re = { exports: {} };
|
|
@@ -10150,12 +10806,12 @@ function createKernel({
|
|
|
10150
10806
|
get(id, language) {
|
|
10151
10807
|
return configuration.inspect(id, language).value;
|
|
10152
10808
|
},
|
|
10153
|
-
inspect(id, language) {
|
|
10809
|
+
inspect(id, language, scope = "workspace") {
|
|
10154
10810
|
const scopes = language ? [.../* @__PURE__ */ new Set([language, canonicalLanguageId(language), ...languages.find((item) => item.id === canonicalLanguageId(language))?.settingsAliases ?? []])] : [];
|
|
10155
10811
|
const ordered = [
|
|
10156
|
-
...scopes.map((id2) => configurationData.workspaceLanguages[id2]),
|
|
10812
|
+
...scope === "workspace" ? scopes.map((id2) => configurationData.workspaceLanguages[id2]) : [],
|
|
10157
10813
|
...scopes.map((id2) => configurationData.userLanguages[id2]),
|
|
10158
|
-
configurationData.workspace,
|
|
10814
|
+
...scope === "workspace" ? [configurationData.workspace] : [],
|
|
10159
10815
|
configurationData.user
|
|
10160
10816
|
];
|
|
10161
10817
|
let value = structuredClone(settings.get(id)?.default);
|
|
@@ -10942,11 +11598,6 @@ class RuntimeExtensions {
|
|
|
10942
11598
|
this.watchers.clear();
|
|
10943
11599
|
}
|
|
10944
11600
|
}
|
|
10945
|
-
let cached;
|
|
10946
|
-
function runtimeVersion() {
|
|
10947
|
-
if (cached) return cached;
|
|
10948
|
-
return cached = "0.6.1";
|
|
10949
|
-
}
|
|
10950
11601
|
const wildcardHost = (host) => host === "0.0.0.0" || host === "::";
|
|
10951
11602
|
const localHost = (host) => wildcardHost(host) ? "127.0.0.1" : host;
|
|
10952
11603
|
const localName = (hostname = os.hostname()) => hostname.split(".")[0];
|
|
@@ -10979,7 +11630,7 @@ function lanUrl(port2, interfaces = os.networkInterfaces()) {
|
|
|
10979
11630
|
const address = lanAddresses$1(interfaces).v4[0];
|
|
10980
11631
|
return address ? `http://${address}:${port2}` : void 0;
|
|
10981
11632
|
}
|
|
10982
|
-
const MAX_SOCKET_BACKLOG_BYTES =
|
|
11633
|
+
const MAX_SOCKET_BACKLOG_BYTES = MAX_FILE_WRITE_MESSAGE_BYTES;
|
|
10983
11634
|
const allCapabilities = [
|
|
10984
11635
|
"filesystem.read",
|
|
10985
11636
|
"filesystem.write",
|
|
@@ -11024,7 +11675,8 @@ async function createRuntime(options2) {
|
|
|
11024
11675
|
const dataDir = path.resolve(options2.dataDir ?? workspaceDataDir(root));
|
|
11025
11676
|
await fs.mkdir(dataDir, { recursive: true, mode: 448 });
|
|
11026
11677
|
const stateFile = path.join(dataDir, "runtime.json");
|
|
11027
|
-
const files = new WorkspaceFiles(root, dataDir), sessions = /* @__PURE__ */ new Map(),
|
|
11678
|
+
const files = new WorkspaceFiles(root, dataDir), sessions = /* @__PURE__ */ new Map(), inflight = /* @__PURE__ */ new Map(), connections = /* @__PURE__ */ new Map();
|
|
11679
|
+
let operations = new OperationHistory();
|
|
11028
11680
|
let trusted = false, closed = false, runtimeId = randomUUID();
|
|
11029
11681
|
try {
|
|
11030
11682
|
const state2 = JSON.parse(await fs.readFile(stateFile, "utf8"));
|
|
@@ -11032,16 +11684,7 @@ async function createRuntime(options2) {
|
|
|
11032
11684
|
if (typeof state2.id === "string" && /^[A-Za-z0-9-]{8,64}$/.test(state2.id)) runtimeId = state2.id;
|
|
11033
11685
|
for (const session of state2.sessions ?? [])
|
|
11034
11686
|
sessions.set(session.hash, session);
|
|
11035
|
-
|
|
11036
|
-
if (operation.status === "running") {
|
|
11037
|
-
operation.status = "interrupted";
|
|
11038
|
-
operation.error = {
|
|
11039
|
-
code: "INTERRUPTED",
|
|
11040
|
-
message: "Runtime stopped before operation completion was recorded; inspect state before retrying with a new request ID"
|
|
11041
|
-
};
|
|
11042
|
-
}
|
|
11043
|
-
operations.set(`${operation.owner}:${operation.id}`, operation);
|
|
11044
|
-
}
|
|
11687
|
+
operations = new OperationHistory(state2.operationHistory, state2.operations ?? []);
|
|
11045
11688
|
} catch (error) {
|
|
11046
11689
|
if (error.code !== "ENOENT")
|
|
11047
11690
|
throw new Error(`Cannot recover runtime state: ${String(error)}`);
|
|
@@ -11064,6 +11707,7 @@ async function createRuntime(options2) {
|
|
|
11064
11707
|
id: runtimeId,
|
|
11065
11708
|
trusted,
|
|
11066
11709
|
sessions: [...sessions.values()],
|
|
11710
|
+
operationHistory: operations.checkpoint(),
|
|
11067
11711
|
operations: [...operations.values()]
|
|
11068
11712
|
});
|
|
11069
11713
|
persistence = persistence.catch(() => {
|
|
@@ -11130,7 +11774,7 @@ async function createRuntime(options2) {
|
|
|
11130
11774
|
const agents = new AgentACP(files, (connectionId, name, params) => {
|
|
11131
11775
|
const connection = connections.get(connectionId);
|
|
11132
11776
|
if (connection?.session && !connection.session.revoked && trusted) event(connection, name, params);
|
|
11133
|
-
});
|
|
11777
|
+
}, () => updateIdle());
|
|
11134
11778
|
const processEvent = ({ event: name, params, stream, seq }, ownerId) => {
|
|
11135
11779
|
const cap = name.startsWith("terminal.") ? "terminal" : "tasks";
|
|
11136
11780
|
for (const c of connections.values()) {
|
|
@@ -11220,7 +11864,8 @@ async function createRuntime(options2) {
|
|
|
11220
11864
|
capabilities: session.capabilities,
|
|
11221
11865
|
owner: session.owner,
|
|
11222
11866
|
trusted,
|
|
11223
|
-
sessionId: session.id
|
|
11867
|
+
sessionId: session.id,
|
|
11868
|
+
operationEpoch: operations.epoch
|
|
11224
11869
|
});
|
|
11225
11870
|
const createSession = async (isOwner, capabilities) => {
|
|
11226
11871
|
const token = randomBytes(32).toString("base64url"), session = {
|
|
@@ -11239,6 +11884,7 @@ async function createRuntime(options2) {
|
|
|
11239
11884
|
const session = [...sessions.values()].find((s) => s.id === id);
|
|
11240
11885
|
if (!session) throw new RpcError("NOT_FOUND", "Grant does not exist");
|
|
11241
11886
|
session.revoked = true;
|
|
11887
|
+
agents.disconnect(session.id);
|
|
11242
11888
|
processes.revoke(session.id);
|
|
11243
11889
|
tasks.revoke(session.id);
|
|
11244
11890
|
for (const c of connections.values())
|
|
@@ -11442,7 +12088,7 @@ async function createRuntime(options2) {
|
|
|
11442
12088
|
});
|
|
11443
12089
|
const websocket = new WebSocketServer({
|
|
11444
12090
|
noServer: true,
|
|
11445
|
-
maxPayload:
|
|
12091
|
+
maxPayload: MAX_FILE_WRITE_MESSAGE_BYTES,
|
|
11446
12092
|
perMessageDeflate: false
|
|
11447
12093
|
});
|
|
11448
12094
|
server.on("upgrade", (request, socket, head) => {
|
|
@@ -11502,7 +12148,11 @@ async function createRuntime(options2) {
|
|
|
11502
12148
|
if (workspace !== "default")
|
|
11503
12149
|
throw new RpcError("FORBIDDEN", "Workspace is not granted");
|
|
11504
12150
|
const required = permission(method), session = authorized(connection, required.cap, required.trust);
|
|
11505
|
-
if (method.startsWith("acp."))
|
|
12151
|
+
if (method.startsWith("acp.")) {
|
|
12152
|
+
owner(connection);
|
|
12153
|
+
if (!["acp.start", "acp.list", "acp.attach", "acp.disconnect"].includes(method))
|
|
12154
|
+
agents.control(session.id, requireString(params, "id"), connection.id);
|
|
12155
|
+
}
|
|
11506
12156
|
if (method.startsWith("project.")) owner(connection);
|
|
11507
12157
|
if (method.startsWith("settings.")) owner(connection);
|
|
11508
12158
|
switch (method) {
|
|
@@ -11525,17 +12175,28 @@ async function createRuntime(options2) {
|
|
|
11525
12175
|
case "project.relations":
|
|
11526
12176
|
return project.relations(requireString(params, "path"));
|
|
11527
12177
|
case "acp.start":
|
|
11528
|
-
return agents.start(
|
|
12178
|
+
return agents.start(session.id, params, signal, connection.id);
|
|
12179
|
+
case "acp.list":
|
|
12180
|
+
return agents.list(session.id);
|
|
12181
|
+
case "acp.attach":
|
|
12182
|
+
return agents.attach(session.id, requireString(params, "id"), connection.id, params.clientCapabilities?.editorTools === true);
|
|
12183
|
+
case "acp.enqueue":
|
|
12184
|
+
case "acp.interrupt":
|
|
12185
|
+
return agents.enqueue(session.id, requireString(params, "id"), params, method === "acp.interrupt");
|
|
12186
|
+
case "acp.dequeue":
|
|
12187
|
+
return agents.dequeue(session.id, requireString(params, "id"), requireString(params, "promptId"));
|
|
12188
|
+
case "acp.resumeQueue":
|
|
12189
|
+
return agents.resumeQueue(session.id, requireString(params, "id"));
|
|
11529
12190
|
case "acp.call":
|
|
11530
|
-
return agents.call(
|
|
12191
|
+
return agents.call(session.id, requireString(params, "id"), requireString(params, "method"), params.params ?? {});
|
|
11531
12192
|
case "acp.respond":
|
|
11532
|
-
return agents.respond(
|
|
12193
|
+
return agents.respond(session.id, requireString(params, "id"), requireString(params, "requestId"), params.result, params.error === void 0 ? void 0 : requireString(params, "error"));
|
|
11533
12194
|
case "acp.cancel":
|
|
11534
|
-
return agents.cancel(
|
|
12195
|
+
return agents.cancel(session.id, requireString(params, "id"));
|
|
11535
12196
|
case "acp.stop":
|
|
11536
|
-
return agents.stop(
|
|
12197
|
+
return agents.stop(session.id, requireString(params, "id"));
|
|
11537
12198
|
case "acp.disconnect":
|
|
11538
|
-
agents.disconnect(connection.id);
|
|
12199
|
+
agents.disconnect(session.id, connection.id);
|
|
11539
12200
|
return {};
|
|
11540
12201
|
case "workspace.info":
|
|
11541
12202
|
return sessionInfo(session);
|
|
@@ -11545,7 +12206,7 @@ async function createRuntime(options2) {
|
|
|
11545
12206
|
throw new RpcError("INVALID_PARAMS", "trusted must be boolean");
|
|
11546
12207
|
trusted = params.trusted;
|
|
11547
12208
|
if (!trusted) {
|
|
11548
|
-
agents.dispose();
|
|
12209
|
+
await agents.dispose();
|
|
11549
12210
|
await extensions.suspend();
|
|
11550
12211
|
for (const c of connections.values()) for (const pending of c.pending.values()) pending.abort();
|
|
11551
12212
|
for (const s of sessions.values()) {
|
|
@@ -11573,10 +12234,7 @@ async function createRuntime(options2) {
|
|
|
11573
12234
|
owner(connection);
|
|
11574
12235
|
return revoke(requireString(params, "id"));
|
|
11575
12236
|
case "operation.status": {
|
|
11576
|
-
|
|
11577
|
-
`${session.id}:${requireString(params, "id")}`
|
|
11578
|
-
);
|
|
11579
|
-
return value ?? { id: params.id, status: "unknown" };
|
|
12237
|
+
return operations.status(session.id, requireString(params, "id"));
|
|
11580
12238
|
}
|
|
11581
12239
|
case "fs.list":
|
|
11582
12240
|
return files.list(typeof params.path === "string" ? params.path : "");
|
|
@@ -11594,7 +12252,7 @@ async function createRuntime(options2) {
|
|
|
11594
12252
|
case "fs.write": {
|
|
11595
12253
|
const snapshot = await files.write(
|
|
11596
12254
|
requireString(params, "path"),
|
|
11597
|
-
requireString(params, "text",
|
|
12255
|
+
requireString(params, "text", MAX_FILE_BYTES),
|
|
11598
12256
|
{
|
|
11599
12257
|
expectedRevision: requireRevision(params),
|
|
11600
12258
|
encoding: params.encoding,
|
|
@@ -11857,7 +12515,7 @@ async function createRuntime(options2) {
|
|
|
11857
12515
|
return collaboration.update(
|
|
11858
12516
|
requireString(params, "path"),
|
|
11859
12517
|
connection.id,
|
|
11860
|
-
requireString(params, "update",
|
|
12518
|
+
requireString(params, "update", MAX_COLLABORATION_UPDATE_BYTES)
|
|
11861
12519
|
);
|
|
11862
12520
|
case "collab.awareness":
|
|
11863
12521
|
return collaboration.awareness(
|
|
@@ -11967,7 +12625,7 @@ async function createRuntime(options2) {
|
|
|
11967
12625
|
};
|
|
11968
12626
|
let idleSince, idleTimer;
|
|
11969
12627
|
const updateIdle = () => {
|
|
11970
|
-
if ([...connections.values()].some((c) => c.session && !c.session.revoked)) {
|
|
12628
|
+
if (agents.hasWork || [...connections.values()].some((c) => c.session && !c.session.revoked)) {
|
|
11971
12629
|
idleSince = void 0;
|
|
11972
12630
|
clearTimeout(idleTimer);
|
|
11973
12631
|
return;
|
|
@@ -12057,7 +12715,7 @@ async function createRuntime(options2) {
|
|
|
12057
12715
|
let result;
|
|
12058
12716
|
if (durableMethods.has(method)) {
|
|
12059
12717
|
const required = permission(method), session = authorized(connection, required.cap, required.trust);
|
|
12060
|
-
const key = `${session.id}:${id}`, existing = operations.get(
|
|
12718
|
+
const key = `${session.id}:${id}`, existing = operations.get(session.id, id);
|
|
12061
12719
|
if (existing) {
|
|
12062
12720
|
if (existing.method !== method)
|
|
12063
12721
|
throw new RpcError(
|
|
@@ -12065,6 +12723,8 @@ async function createRuntime(options2) {
|
|
|
12065
12723
|
"Request ID was used for another operation"
|
|
12066
12724
|
);
|
|
12067
12725
|
if (inflight.has(key)) result = await inflight.get(key);
|
|
12726
|
+
else if (existing.resultExpired)
|
|
12727
|
+
throw new RpcError("OPERATION_RESULT_EXPIRED", "Operation result expired; inspect current state before submitting a new operation", { status: existing.status });
|
|
12068
12728
|
else if (existing.status === "completed")
|
|
12069
12729
|
result = existing.result;
|
|
12070
12730
|
else
|
|
@@ -12074,14 +12734,11 @@ async function createRuntime(options2) {
|
|
|
12074
12734
|
existing.error?.data
|
|
12075
12735
|
);
|
|
12076
12736
|
} else {
|
|
12077
|
-
const
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
startedAt: Date.now()
|
|
12083
|
-
};
|
|
12084
|
-
operations.set(key, operation);
|
|
12737
|
+
const previousEpoch = operations.epoch;
|
|
12738
|
+
const operation = operations.begin(session.id, id, method);
|
|
12739
|
+
if (operations.epoch !== previousEpoch) {
|
|
12740
|
+
for (const c of connections.values()) if (c.session && !c.session.revoked) event(c, "operation.epoch", { epoch: operations.epoch });
|
|
12741
|
+
}
|
|
12085
12742
|
const run2 = (async () => {
|
|
12086
12743
|
await persist();
|
|
12087
12744
|
try {
|
|
@@ -12092,13 +12749,11 @@ async function createRuntime(options2) {
|
|
|
12092
12749
|
controller.signal,
|
|
12093
12750
|
id
|
|
12094
12751
|
);
|
|
12095
|
-
operation
|
|
12096
|
-
operation.result = value;
|
|
12752
|
+
operations.complete(operation, value);
|
|
12097
12753
|
await persist();
|
|
12098
12754
|
return value;
|
|
12099
12755
|
} catch (error) {
|
|
12100
|
-
operation
|
|
12101
|
-
operation.error = errorShape(error);
|
|
12756
|
+
operations.fail(operation, errorShape(error));
|
|
12102
12757
|
await persist();
|
|
12103
12758
|
throw error;
|
|
12104
12759
|
}
|
|
@@ -12143,7 +12798,7 @@ async function createRuntime(options2) {
|
|
|
12143
12798
|
});
|
|
12144
12799
|
ws.on("close", () => {
|
|
12145
12800
|
clearTimeout(authDeadline);
|
|
12146
|
-
agents.
|
|
12801
|
+
agents.detach(connection.id);
|
|
12147
12802
|
lsp.detach(connection.id);
|
|
12148
12803
|
connections.delete(connection.id);
|
|
12149
12804
|
updateIdle();
|
|
@@ -12155,7 +12810,7 @@ async function createRuntime(options2) {
|
|
|
12155
12810
|
processes.disconnect(connection.id);
|
|
12156
12811
|
void collaboration.leave(connection.id);
|
|
12157
12812
|
for (const [id, controller] of connection.pending) {
|
|
12158
|
-
const operation = connection.session && operations.get(
|
|
12813
|
+
const operation = connection.session && operations.get(connection.session.id, id);
|
|
12159
12814
|
if (!operation) controller.abort();
|
|
12160
12815
|
}
|
|
12161
12816
|
});
|
|
@@ -12285,7 +12940,7 @@ async function createRuntime(options2) {
|
|
|
12285
12940
|
for (const controller of c.pending.values()) controller.abort();
|
|
12286
12941
|
c.ws.terminate();
|
|
12287
12942
|
}
|
|
12288
|
-
agents.dispose();
|
|
12943
|
+
await agents.dispose();
|
|
12289
12944
|
extensions.dispose();
|
|
12290
12945
|
processes.close();
|
|
12291
12946
|
await tasks.close();
|
|
@@ -12535,17 +13190,17 @@ function advertise(ad, log = stderrLog) {
|
|
|
12535
13190
|
try {
|
|
12536
13191
|
const query = parseMessage(packet);
|
|
12537
13192
|
if (query.flags & 32768) return;
|
|
12538
|
-
const
|
|
12539
|
-
if (!
|
|
12540
|
-
if (from.port === PORT) return send(encodeMessage(
|
|
13193
|
+
const reply2 = answer(query.questions, current());
|
|
13194
|
+
if (!reply2) return;
|
|
13195
|
+
if (from.port === PORT) return send(encodeMessage(reply2));
|
|
12541
13196
|
const legacy = (r) => ({ ...r, ttl: Math.min(r.ttl, 10), flush: false });
|
|
12542
13197
|
send(
|
|
12543
13198
|
encodeMessage({
|
|
12544
13199
|
id: query.id,
|
|
12545
13200
|
flags: FLAGS_RESPONSE,
|
|
12546
13201
|
questions: query.questions,
|
|
12547
|
-
answers:
|
|
12548
|
-
additionals:
|
|
13202
|
+
answers: reply2.answers.map(legacy),
|
|
13203
|
+
additionals: reply2.additionals.map(legacy)
|
|
12549
13204
|
}),
|
|
12550
13205
|
from.port,
|
|
12551
13206
|
from.address
|