@oxbit/cli 0.6.0 → 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 +829 -173
- package/runtime/package.json +1 -1
- package/web/dist/assets/index-CCGtng18.css +1 -0
- package/web/dist/assets/{index-DmlViIXr.js → index-a2bql7tE.js} +123 -122
- package/web/dist/assets/{typescript-CKOC7een.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-Cj4NF8QT.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 {
|
|
@@ -544,6 +654,7 @@ async function runCommand(command, args, options2) {
|
|
|
544
654
|
});
|
|
545
655
|
});
|
|
546
656
|
}
|
|
657
|
+
const minimumColumns = 4;
|
|
547
658
|
class Processes {
|
|
548
659
|
constructor(root, emit, loadPty = loadNodePty) {
|
|
549
660
|
this.root = root;
|
|
@@ -575,7 +686,7 @@ class Processes {
|
|
|
575
686
|
const id = randomUUID(), { shell, args } = resolveTerminalShell();
|
|
576
687
|
const terminal2 = pty.spawn(shell, args, {
|
|
577
688
|
name: "xterm-256color",
|
|
578
|
-
cols: this.dimension(cols, 1e3),
|
|
689
|
+
cols: this.dimension(cols, 1e3, minimumColumns),
|
|
579
690
|
rows: this.dimension(rows, 500),
|
|
580
691
|
cwd: this.root,
|
|
581
692
|
env: { ...process.env, SHELL: shell, TERM: "xterm-256color" }
|
|
@@ -622,10 +733,10 @@ class Processes {
|
|
|
622
733
|
});
|
|
623
734
|
return { id };
|
|
624
735
|
}
|
|
625
|
-
dimension(value, max) {
|
|
736
|
+
dimension(value, max, min = 1) {
|
|
626
737
|
if (!Number.isSafeInteger(value) || value < 1 || value > max)
|
|
627
738
|
throw new RpcError("INVALID_PARAMS", "Invalid terminal dimensions");
|
|
628
|
-
return value;
|
|
739
|
+
return Math.max(value, min);
|
|
629
740
|
}
|
|
630
741
|
flow(session) {
|
|
631
742
|
const slow = session.subscribers.size === 0 || [...session.subscribers.values()].some(
|
|
@@ -647,7 +758,7 @@ class Processes {
|
|
|
647
758
|
}
|
|
648
759
|
resize(id, owner, cols, rows) {
|
|
649
760
|
this.terminal(id, owner).pty.resize(
|
|
650
|
-
this.dimension(cols, 1e3),
|
|
761
|
+
this.dimension(cols, 1e3, minimumColumns),
|
|
651
762
|
this.dimension(rows, 500)
|
|
652
763
|
);
|
|
653
764
|
}
|
|
@@ -775,7 +886,7 @@ function validateJson(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
|
775
886
|
}
|
|
776
887
|
seen.delete(value);
|
|
777
888
|
}
|
|
778
|
-
function object$
|
|
889
|
+
function object$3(value) {
|
|
779
890
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a JSON object");
|
|
780
891
|
}
|
|
781
892
|
function strings$1(value) {
|
|
@@ -783,17 +894,17 @@ function strings$1(value) {
|
|
|
783
894
|
}
|
|
784
895
|
function validateFileAssociations(value) {
|
|
785
896
|
validateJson(value);
|
|
786
|
-
object$
|
|
897
|
+
object$3(value);
|
|
787
898
|
for (const [pattern, id] of Object.entries(value))
|
|
788
899
|
if (!pattern || pattern.length > 1024 || typeof id !== "string" || !/^[\w+-]+$/.test(id)) throw new Error("File associations map glob patterns to language IDs");
|
|
789
900
|
}
|
|
790
901
|
function validateLanguageServers(value) {
|
|
791
902
|
validateJson(value);
|
|
792
|
-
object$
|
|
903
|
+
object$3(value);
|
|
793
904
|
const keys = ["enabled", "selectors", "rootMarkers", "executable", "args", "env", "initializationOptions", "settings", "priority"];
|
|
794
905
|
for (const [id, config] of Object.entries(value)) {
|
|
795
906
|
if (!/^[a-zA-Z0-9._-]+$/.test(id)) throw new Error("Invalid language server ID");
|
|
796
|
-
object$
|
|
907
|
+
object$3(config);
|
|
797
908
|
for (const key of Object.keys(config)) if (!keys.includes(key)) throw new Error(`Unknown language server option: ${key}`);
|
|
798
909
|
if (config.enabled !== void 0 && typeof config.enabled !== "boolean") throw new Error("enabled must be boolean");
|
|
799
910
|
if (config.priority !== void 0 && (typeof config.priority !== "number" || !Number.isFinite(config.priority))) throw new Error("priority must be finite");
|
|
@@ -806,11 +917,11 @@ function validateLanguageServers(value) {
|
|
|
806
917
|
if (config.selectors !== void 0) {
|
|
807
918
|
if (!Array.isArray(config.selectors)) throw new Error("selectors must be an array");
|
|
808
919
|
for (const selector of config.selectors) {
|
|
809
|
-
object$
|
|
920
|
+
object$3(selector);
|
|
810
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");
|
|
811
922
|
}
|
|
812
923
|
}
|
|
813
|
-
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]);
|
|
814
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");
|
|
815
926
|
const credentials = (item) => Boolean(item && typeof item === "object" && Object.entries(item).some(([key, child]) => /(?:^|[._-])licen[cs]e(?:[._-]?key)?$/i.test(key) || credentials(child)));
|
|
816
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");
|
|
@@ -821,7 +932,7 @@ const $id$1 = "https://oxbit.dev/schemas/settings.v1.schema.json";
|
|
|
821
932
|
const title$2 = "Oxbit Settings";
|
|
822
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.";
|
|
823
934
|
const type$2 = "object";
|
|
824
|
-
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."}}`);
|
|
825
936
|
const propertyNames = { "anyOf": [{ "not": { "pattern": "^\\[.*\\]$" } }, { "pattern": "^\\[[^\\[\\]]+\\]$" }] };
|
|
826
937
|
const patternProperties = { "^\\[[^\\[\\]]+\\]$": { "$ref": "#/definitions/languageOverrides" } };
|
|
827
938
|
const additionalProperties$1 = true;
|
|
@@ -999,7 +1110,7 @@ const ACP_PROVIDERS = [
|
|
|
999
1110
|
id: "codex",
|
|
1000
1111
|
name: "Codex ACP",
|
|
1001
1112
|
command: "npx",
|
|
1002
|
-
args: ["-y", "@agentclientprotocol/codex-acp@
|
|
1113
|
+
args: ["-y", "@agentclientprotocol/codex-acp@2.2.2"],
|
|
1003
1114
|
setup: "Uses the Codex ACP adapter. Sign in through an advertised authentication method, or use your existing Codex credentials.",
|
|
1004
1115
|
url: "https://github.com/agentclientprotocol/codex-acp"
|
|
1005
1116
|
},
|
|
@@ -1015,7 +1126,7 @@ const ACP_PROVIDERS = [
|
|
|
1015
1126
|
id: "amp",
|
|
1016
1127
|
name: "Amp Agent ACP",
|
|
1017
1128
|
command: "npx",
|
|
1018
|
-
args: ["-y", "amp-acp@0.
|
|
1129
|
+
args: ["-y", "amp-acp@0.10.0"],
|
|
1019
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.",
|
|
1020
1131
|
url: "https://github.com/tao12345666333/amp-acp"
|
|
1021
1132
|
}
|
|
@@ -1059,7 +1170,7 @@ const SDK_VERSION = "1.0.0";
|
|
|
1059
1170
|
function invalid(message) {
|
|
1060
1171
|
throw new RpcError("INVALID_TASK_CONFIG", message);
|
|
1061
1172
|
}
|
|
1062
|
-
function object$
|
|
1173
|
+
function object$2(value, label) {
|
|
1063
1174
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1064
1175
|
invalid(`${label} must be an object`);
|
|
1065
1176
|
return value;
|
|
@@ -1070,7 +1181,7 @@ function taskName(value) {
|
|
|
1070
1181
|
return value;
|
|
1071
1182
|
}
|
|
1072
1183
|
function stringMap(value, label) {
|
|
1073
|
-
const record = object$
|
|
1184
|
+
const record = object$2(value, label);
|
|
1074
1185
|
for (const [key, item] of Object.entries(record))
|
|
1075
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"))
|
|
1076
1187
|
invalid(`${label}: invalid environment entry ${key}`);
|
|
@@ -1086,13 +1197,13 @@ function port(value) {
|
|
|
1086
1197
|
number(value, 1, 65535, "port");
|
|
1087
1198
|
return value;
|
|
1088
1199
|
}
|
|
1089
|
-
const range2 = object$
|
|
1200
|
+
const range2 = object$2(value, "port range");
|
|
1090
1201
|
number(range2.min, 1, 65535, "port minimum");
|
|
1091
1202
|
number(range2.max, range2.min, 65535, "port maximum");
|
|
1092
1203
|
return { min: range2.min, max: range2.max };
|
|
1093
1204
|
}
|
|
1094
1205
|
function hooks(value) {
|
|
1095
|
-
const data = object$
|
|
1206
|
+
const data = object$2(value ?? {}, "worktree");
|
|
1096
1207
|
for (const key of Object.keys(data))
|
|
1097
1208
|
if (!taskPhases.includes(key))
|
|
1098
1209
|
invalid(`Unknown worktree phase: ${key}`);
|
|
@@ -1109,7 +1220,7 @@ function hooks(value) {
|
|
|
1109
1220
|
);
|
|
1110
1221
|
}
|
|
1111
1222
|
function definition(value) {
|
|
1112
|
-
const data = object$
|
|
1223
|
+
const data = object$2(value, "task");
|
|
1113
1224
|
const known = /* @__PURE__ */ new Set([
|
|
1114
1225
|
"command",
|
|
1115
1226
|
"type",
|
|
@@ -1151,7 +1262,7 @@ function definition(value) {
|
|
|
1151
1262
|
if (data.env !== void 0) stringMap(data.env, "task.env");
|
|
1152
1263
|
if (data.port !== void 0) port(data.port);
|
|
1153
1264
|
if (data.ports !== void 0) {
|
|
1154
|
-
const entries = Object.entries(object$
|
|
1265
|
+
const entries = Object.entries(object$2(data.ports, "ports"));
|
|
1155
1266
|
if (entries.length > 16) invalid("Maximum 16 named ports per task");
|
|
1156
1267
|
for (const [key, value2] of entries) {
|
|
1157
1268
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key) || key.toLowerCase() === "port")
|
|
@@ -1162,7 +1273,7 @@ function definition(value) {
|
|
|
1162
1273
|
invalid("Port names must be unique ignoring case");
|
|
1163
1274
|
}
|
|
1164
1275
|
if (data.ready !== void 0) {
|
|
1165
|
-
const ready = object$
|
|
1276
|
+
const ready = object$2(data.ready, "ready");
|
|
1166
1277
|
for (const key of Object.keys(ready))
|
|
1167
1278
|
if (!["pattern", "url", "timeoutMs", "intervalMs"].includes(key))
|
|
1168
1279
|
invalid(`Unknown readiness field: ${key}`);
|
|
@@ -1178,7 +1289,7 @@ function definition(value) {
|
|
|
1178
1289
|
number(ready.intervalMs, 100, 6e4, "ready.intervalMs");
|
|
1179
1290
|
}
|
|
1180
1291
|
if (data.restart !== void 0) {
|
|
1181
|
-
const restart = object$
|
|
1292
|
+
const restart = object$2(data.restart, "restart");
|
|
1182
1293
|
for (const key of Object.keys(restart))
|
|
1183
1294
|
if (!["policy", "maxAttempts", "delayMs"].includes(key))
|
|
1184
1295
|
invalid(`Unknown restart field: ${key}`);
|
|
@@ -1190,7 +1301,7 @@ function definition(value) {
|
|
|
1190
1301
|
number(restart.delayMs, 100, 6e4, "restart.delayMs");
|
|
1191
1302
|
}
|
|
1192
1303
|
if (data.stop !== void 0) {
|
|
1193
|
-
const stop2 = object$
|
|
1304
|
+
const stop2 = object$2(data.stop, "stop");
|
|
1194
1305
|
for (const key of Object.keys(stop2))
|
|
1195
1306
|
if (!["signal", "timeoutMs"].includes(key))
|
|
1196
1307
|
invalid(`Unknown stop field: ${key}`);
|
|
@@ -1207,7 +1318,7 @@ function json(text2) {
|
|
|
1207
1318
|
const errors = [];
|
|
1208
1319
|
const result = parse$1(text2, errors, { allowTrailingComma: true });
|
|
1209
1320
|
if (errors.length) invalid(`Invalid JSON near offset ${errors[0].offset}`);
|
|
1210
|
-
return object$
|
|
1321
|
+
return object$2(result, "configuration");
|
|
1211
1322
|
}
|
|
1212
1323
|
function editJson(text2, location, value) {
|
|
1213
1324
|
const tree = value === void 0 ? parseTree(text2) : void 0;
|
|
@@ -1409,7 +1520,7 @@ function readSource(kind, text2) {
|
|
|
1409
1520
|
);
|
|
1410
1521
|
} else {
|
|
1411
1522
|
const data = json(text2);
|
|
1412
|
-
const meta = object$
|
|
1523
|
+
const meta = object$2(data.oxbit ?? {}, "oxbit metadata");
|
|
1413
1524
|
result.env = stringMap(
|
|
1414
1525
|
(kind === "oxbit" ? data.env : meta.env) ?? {},
|
|
1415
1526
|
"env"
|
|
@@ -1429,7 +1540,7 @@ function readSource(kind, text2) {
|
|
|
1429
1540
|
].includes(key))
|
|
1430
1541
|
invalid(`Unknown Oxbit configuration field: ${key}`);
|
|
1431
1542
|
for (const [name, task] of Object.entries(
|
|
1432
|
-
object$
|
|
1543
|
+
object$2(data.tasks ?? {}, "tasks")
|
|
1433
1544
|
))
|
|
1434
1545
|
add(name, definition(task));
|
|
1435
1546
|
} else if (kind === "paseo") {
|
|
@@ -1441,9 +1552,9 @@ function readSource(kind, text2) {
|
|
|
1441
1552
|
...result.worktree
|
|
1442
1553
|
};
|
|
1443
1554
|
for (const [name, value] of Object.entries(
|
|
1444
|
-
object$
|
|
1555
|
+
object$2(data.scripts ?? {}, "scripts")
|
|
1445
1556
|
)) {
|
|
1446
|
-
const script = object$
|
|
1557
|
+
const script = object$2(value, name);
|
|
1447
1558
|
add(
|
|
1448
1559
|
name,
|
|
1449
1560
|
definition({
|
|
@@ -1495,10 +1606,10 @@ function readSource(kind, text2) {
|
|
|
1495
1606
|
invalid("Only VS Code tasks version 2.0.0 is supported");
|
|
1496
1607
|
if (!Array.isArray(data.tasks)) invalid("VS Code tasks must be an array");
|
|
1497
1608
|
for (const raw of data.tasks) {
|
|
1498
|
-
const entry = object$
|
|
1609
|
+
const entry = object$2(raw, "VS Code task"), platform = process.platform === "darwin" ? "osx" : process.platform === "win32" ? "windows" : "linux";
|
|
1499
1610
|
const item = { ...entry, ...entry[platform] };
|
|
1500
1611
|
const args = (item.args ?? []).map(
|
|
1501
|
-
(arg) => typeof arg === "string" ? arg : object$
|
|
1612
|
+
(arg) => typeof arg === "string" ? arg : object$2(arg, "argument").value
|
|
1502
1613
|
);
|
|
1503
1614
|
const opts = { ...data.options, ...item.options };
|
|
1504
1615
|
let command = item.command ?? data.command ?? "", execution = item.type === "process" ? "process" : "shell";
|
|
@@ -1537,7 +1648,7 @@ function readSource(kind, text2) {
|
|
|
1537
1648
|
}
|
|
1538
1649
|
} else {
|
|
1539
1650
|
for (const [name, script] of Object.entries(
|
|
1540
|
-
object$
|
|
1651
|
+
object$2(data.scripts ?? {}, "scripts")
|
|
1541
1652
|
)) {
|
|
1542
1653
|
if (typeof script !== "string" && !(kind === "composer" && Array.isArray(script)))
|
|
1543
1654
|
continue;
|
|
@@ -6526,8 +6637,8 @@ class Collaboration {
|
|
|
6526
6637
|
)
|
|
6527
6638
|
);
|
|
6528
6639
|
}
|
|
6529
|
-
bytes(encoded) {
|
|
6530
|
-
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)
|
|
6531
6642
|
throw new RpcError("INVALID_PARAMS", "Invalid collaboration update");
|
|
6532
6643
|
return Buffer.from(encoded, "base64");
|
|
6533
6644
|
}
|
|
@@ -6563,14 +6674,14 @@ class Collaboration {
|
|
|
6563
6674
|
}
|
|
6564
6675
|
async update(relative, connection, encoded) {
|
|
6565
6676
|
const room = await this.member(relative, connection);
|
|
6566
|
-
const bytes = this.bytes(encoded);
|
|
6677
|
+
const bytes = this.bytes(encoded, MAX_COLLABORATION_UPDATE_BYTES);
|
|
6567
6678
|
try {
|
|
6568
|
-
if (room.doc.getText("content").length + bytes.length >
|
|
6679
|
+
if (room.doc.getText("content").length + bytes.length > MAX_FILE_BYTES) {
|
|
6569
6680
|
const candidate = new Y.Doc();
|
|
6570
6681
|
try {
|
|
6571
6682
|
Y.applyUpdate(candidate, Y.encodeStateAsUpdate(room.doc));
|
|
6572
6683
|
Y.applyUpdate(candidate, bytes);
|
|
6573
|
-
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");
|
|
6574
6685
|
} finally {
|
|
6575
6686
|
candidate.destroy();
|
|
6576
6687
|
}
|
|
@@ -6739,7 +6850,7 @@ class Collaboration {
|
|
|
6739
6850
|
}
|
|
6740
6851
|
}
|
|
6741
6852
|
const terminal = (state2) => !["pending", "running", "unknown"].includes(state2);
|
|
6742
|
-
const object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6853
|
+
const object$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6743
6854
|
const str = (value, max = 4096) => typeof value === "string" ? value.slice(0, max) : void 0;
|
|
6744
6855
|
const identity = (value) => typeof value === "string" && value.length > 0 && value.length <= 512 && !value.includes("\0");
|
|
6745
6856
|
const preview = (value, max = 8192) => value === void 0 ? void 0 : (typeof value === "string" ? value : JSON.stringify(value)).slice(0, max);
|
|
@@ -6769,11 +6880,11 @@ ${item.newText ?? ""}` : JSON.stringify(item))
|
|
|
6769
6880
|
).join("\n");
|
|
6770
6881
|
}
|
|
6771
6882
|
function normalizeDelegation(provider, update) {
|
|
6772
|
-
const raw = object(update.rawInput), meta = object(update._meta?.codex);
|
|
6883
|
+
const raw = object$1(update.rawInput), meta = object$1(update._meta?.codex);
|
|
6773
6884
|
const toolId = update.toolCallId;
|
|
6774
6885
|
if (!identity(toolId)) return [];
|
|
6775
6886
|
if (provider === "codex") {
|
|
6776
|
-
const collab = object(meta.collaboration), sub = object(meta.subagent);
|
|
6887
|
+
const collab = object$1(meta.collaboration), sub = object$1(meta.subagent);
|
|
6777
6888
|
if (identity(sub.threadId))
|
|
6778
6889
|
return [
|
|
6779
6890
|
{
|
|
@@ -6787,7 +6898,7 @@ function normalizeDelegation(provider, update) {
|
|
|
6787
6898
|
];
|
|
6788
6899
|
if (!identity(collab.tool)) return [];
|
|
6789
6900
|
const receivers = Array.isArray(collab.receiverThreadIds) ? collab.receiverThreadIds.filter(identity) : [];
|
|
6790
|
-
const states = object(raw.agentsStates);
|
|
6901
|
+
const states = object$1(raw.agentsStates);
|
|
6791
6902
|
const ids = [
|
|
6792
6903
|
.../* @__PURE__ */ new Set([...receivers, ...Object.keys(states).filter(identity)])
|
|
6793
6904
|
];
|
|
@@ -6815,8 +6926,8 @@ function normalizeDelegation(provider, update) {
|
|
|
6815
6926
|
}));
|
|
6816
6927
|
}
|
|
6817
6928
|
if (provider === "cursor" && update.taskMeta) {
|
|
6818
|
-
const task = object(update.taskMeta), output = object(update.rawOutput);
|
|
6819
|
-
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);
|
|
6820
6931
|
const outcomeName = typeof (task.outcome ?? output.outcome) === "string" ? task.outcome ?? output.outcome : outcome.outcome;
|
|
6821
6932
|
return [
|
|
6822
6933
|
{
|
|
@@ -7236,7 +7347,273 @@ class SubagentRegistry {
|
|
|
7236
7347
|
}
|
|
7237
7348
|
}
|
|
7238
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
|
+
}
|
|
7239
7615
|
const MAX_BYTES = 1024 * 1024;
|
|
7616
|
+
const EDITOR_UNSUPPORTED = "The connected editor does not support this tool; update Oxbit";
|
|
7240
7617
|
function strings(value) {
|
|
7241
7618
|
if (!Array.isArray(value) || value.length > 128 || value.some(
|
|
7242
7619
|
(v) => typeof v !== "string" || v.length > 16384 || v.includes("\0")
|
|
@@ -7248,13 +7625,17 @@ function strings(value) {
|
|
|
7248
7625
|
return value;
|
|
7249
7626
|
}
|
|
7250
7627
|
class AgentACP {
|
|
7251
|
-
constructor(files, emit) {
|
|
7628
|
+
constructor(files, emit, stateChanged = () => {
|
|
7629
|
+
}) {
|
|
7252
7630
|
this.files = files;
|
|
7253
7631
|
this.emit = emit;
|
|
7632
|
+
this.stateChanged = stateChanged;
|
|
7254
7633
|
}
|
|
7255
7634
|
files;
|
|
7256
7635
|
emit;
|
|
7636
|
+
stateChanged;
|
|
7257
7637
|
agents = /* @__PURE__ */ new Map();
|
|
7638
|
+
mcp = new AgentMCP((id, name, args) => this.tool(id, name, args));
|
|
7258
7639
|
get(owner, id) {
|
|
7259
7640
|
const agent = this.agents.get(id);
|
|
7260
7641
|
if (!agent || agent.owner !== owner || agent.stopped)
|
|
@@ -7289,15 +7670,89 @@ class AgentACP {
|
|
|
7289
7670
|
});
|
|
7290
7671
|
}
|
|
7291
7672
|
publish(agent, event, params) {
|
|
7292
|
-
|
|
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
|
+
};
|
|
7293
7717
|
}
|
|
7294
|
-
|
|
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) {
|
|
7295
7745
|
const preset2 = ACP_PROVIDERS.find((p) => p.id === launch.provider);
|
|
7296
7746
|
if (!preset2)
|
|
7297
7747
|
throw new RpcError("INVALID_PARAMS", "Choose Codex, Cursor, or Amp");
|
|
7298
7748
|
if (signal?.aborted) throw new Error("Agent connection cancelled");
|
|
7299
|
-
|
|
7300
|
-
|
|
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
|
+
}
|
|
7301
7756
|
const command = requireString(
|
|
7302
7757
|
{ command: launch.command ?? preset2.command },
|
|
7303
7758
|
"command"
|
|
@@ -7315,6 +7770,8 @@ class AgentACP {
|
|
|
7315
7770
|
trackChild(child);
|
|
7316
7771
|
const agent = {
|
|
7317
7772
|
owner,
|
|
7773
|
+
client,
|
|
7774
|
+
editorTools: launch.clientCapabilities?.editorTools === true,
|
|
7318
7775
|
child,
|
|
7319
7776
|
connection: {
|
|
7320
7777
|
id: randomUUID(),
|
|
@@ -7329,7 +7786,15 @@ class AgentACP {
|
|
|
7329
7786
|
busy: false,
|
|
7330
7787
|
stopped: false,
|
|
7331
7788
|
sequence: 0,
|
|
7332
|
-
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
|
|
7333
7798
|
};
|
|
7334
7799
|
this.agents.set(agent.connection.id, agent);
|
|
7335
7800
|
const abort = () => this.stopAgent(agent, "Agent connection cancelled");
|
|
@@ -7389,7 +7854,7 @@ class AgentACP {
|
|
|
7389
7854
|
terminal: true,
|
|
7390
7855
|
...preset2.id === "codex" ? { subagents: {} } : {}
|
|
7391
7856
|
},
|
|
7392
|
-
clientInfo: { name: "oxbit", title: "Oxbit", version:
|
|
7857
|
+
clientInfo: { name: "oxbit", title: "Oxbit", version: runtimeVersion() }
|
|
7393
7858
|
});
|
|
7394
7859
|
if (initialized.protocolVersion !== 1)
|
|
7395
7860
|
throw new Error("Agent does not support ACP version 1");
|
|
@@ -7459,12 +7924,23 @@ class AgentACP {
|
|
|
7459
7924
|
const previous = { ...agent.connection };
|
|
7460
7925
|
const previousRegistry = agent.subagents;
|
|
7461
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;
|
|
7462
7931
|
const target = method === "session/new" ? void 0 : requireString(params, "sessionId");
|
|
7463
7932
|
if (agent.approvals.size)
|
|
7464
7933
|
throw new Error(
|
|
7465
7934
|
"Resolve pending requests before changing conversations"
|
|
7466
7935
|
);
|
|
7936
|
+
if (agent.queue.length)
|
|
7937
|
+
throw new Error("Remove queued messages before changing conversations");
|
|
7467
7938
|
agent.busy = true;
|
|
7939
|
+
agent.events = [];
|
|
7940
|
+
agent.eventBytes = 0;
|
|
7941
|
+
agent.truncated = false;
|
|
7942
|
+
agent.title = "New conversation";
|
|
7943
|
+
agent.queuePaused = false;
|
|
7468
7944
|
agent.connection.sessionId = target;
|
|
7469
7945
|
agent.subagents = target ? this.registry(agent, target, true) : void 0;
|
|
7470
7946
|
agent.terminals = /* @__PURE__ */ new Map();
|
|
@@ -7472,7 +7948,7 @@ class AgentACP {
|
|
|
7472
7948
|
try {
|
|
7473
7949
|
const result = await this.request(agent, method, {
|
|
7474
7950
|
cwd: this.files.root,
|
|
7475
|
-
mcpServers: [],
|
|
7951
|
+
mcpServers: [await this.mcp.server(agent.connection.id, !!agent.connection.capabilities?.mcpCapabilities?.http)],
|
|
7476
7952
|
...target ? { sessionId: target } : {}
|
|
7477
7953
|
});
|
|
7478
7954
|
const sessionId2 = target ?? result?.sessionId;
|
|
@@ -7497,6 +7973,10 @@ class AgentACP {
|
|
|
7497
7973
|
killProcess(terminal2.child);
|
|
7498
7974
|
agent.terminals = previousTerminals;
|
|
7499
7975
|
agent.subagents = previousRegistry;
|
|
7976
|
+
agent.events = previousEvents;
|
|
7977
|
+
agent.eventBytes = previousBytes;
|
|
7978
|
+
agent.title = previousTitle;
|
|
7979
|
+
agent.truncated = previousTruncated;
|
|
7500
7980
|
throw error;
|
|
7501
7981
|
} finally {
|
|
7502
7982
|
agent.newSessionUpdates = void 0;
|
|
@@ -7567,35 +8047,119 @@ ${content}`
|
|
|
7567
8047
|
throw new Error("The message and attachments are too large");
|
|
7568
8048
|
if (agent.busy || agent.stopped || agent.connection.sessionId !== sessionId)
|
|
7569
8049
|
throw new Error("Agent connection is busy or ended");
|
|
8050
|
+
const messageId = params.messageId === void 0 ? randomUUID() : requireString(params, "messageId", 256);
|
|
7570
8051
|
agent.busy = true;
|
|
7571
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 ?? [] });
|
|
7572
8055
|
const abort = () => this.stopAgent(agent, "Agent request interrupted");
|
|
7573
8056
|
signal?.addEventListener("abort", abort, { once: true });
|
|
8057
|
+
let stopReason;
|
|
8058
|
+
let failure2;
|
|
7574
8059
|
try {
|
|
7575
|
-
|
|
8060
|
+
const result = await this.request(
|
|
7576
8061
|
agent,
|
|
7577
8062
|
method,
|
|
7578
8063
|
{ sessionId, prompt },
|
|
7579
8064
|
30 * 60 * 1e3
|
|
7580
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;
|
|
7581
8073
|
} finally {
|
|
7582
8074
|
this.expireRequests(agent, sessionId);
|
|
7583
8075
|
agent.busy = false;
|
|
7584
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
|
+
}
|
|
7585
8082
|
}
|
|
7586
8083
|
}
|
|
7587
|
-
|
|
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) {
|
|
7588
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);
|
|
7589
8154
|
this.send(agent, {
|
|
7590
8155
|
method: "session/cancel",
|
|
7591
8156
|
params: { sessionId: agent.connection.sessionId }
|
|
7592
8157
|
});
|
|
7593
8158
|
for (const [requestId, request] of agent.approvals) {
|
|
7594
8159
|
if (request.params.sessionId !== agent.connection.sessionId) continue;
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
});
|
|
8160
|
+
if (request.method === "session/request_permission")
|
|
8161
|
+
request.resolve({ outcome: { outcome: "cancelled" } });
|
|
8162
|
+
else request.reject(new Error("Cancelled"));
|
|
7599
8163
|
agent.approvals.delete(requestId);
|
|
7600
8164
|
agent.subagents?.waiting(
|
|
7601
8165
|
request.params.sessionId,
|
|
@@ -7633,6 +8197,8 @@ ${content}`
|
|
|
7633
8197
|
routeUpdate(agent, sessionId, update) {
|
|
7634
8198
|
if (!update || typeof update !== "object" || !agent.subagents?.observe(sessionId, update))
|
|
7635
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);
|
|
7636
8202
|
this.publish(agent, "acp.update", {
|
|
7637
8203
|
rootSessionId: agent.subagents.root,
|
|
7638
8204
|
sessionId,
|
|
@@ -7649,11 +8215,9 @@ ${content}`
|
|
|
7649
8215
|
const requestIds = [];
|
|
7650
8216
|
for (const [id, request] of agent.approvals) {
|
|
7651
8217
|
if (request.params.sessionId !== sessionId) continue;
|
|
7652
|
-
if (
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
...request.method === "session/request_permission" ? { result: { outcome: { outcome: "cancelled" } } } : { error: { code: -32800, message: "Session turn ended" } }
|
|
7656
|
-
});
|
|
8218
|
+
if (request.method === "session/request_permission")
|
|
8219
|
+
request.resolve({ outcome: { outcome: "cancelled" } });
|
|
8220
|
+
else request.reject(new Error("Session turn ended"));
|
|
7657
8221
|
agent.approvals.delete(id);
|
|
7658
8222
|
requestIds.push(id);
|
|
7659
8223
|
agent.subagents?.waiting(
|
|
@@ -7676,6 +8240,136 @@ ${content}`
|
|
|
7676
8240
|
await this.files.resolve(relative, missing2);
|
|
7677
8241
|
return relative;
|
|
7678
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
|
+
}
|
|
7679
8373
|
async message(agent, message) {
|
|
7680
8374
|
if (agent.stopped) return;
|
|
7681
8375
|
if (!message.method) {
|
|
@@ -7720,8 +8414,8 @@ ${content}`
|
|
|
7720
8414
|
throw new Error("Unknown ACP session");
|
|
7721
8415
|
params.sessionId = sessionId;
|
|
7722
8416
|
if (message.method.startsWith("terminal/")) {
|
|
7723
|
-
const
|
|
7724
|
-
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 });
|
|
7725
8419
|
return;
|
|
7726
8420
|
}
|
|
7727
8421
|
if (![
|
|
@@ -7737,46 +8431,8 @@ ${content}`
|
|
|
7737
8431
|
});
|
|
7738
8432
|
return;
|
|
7739
8433
|
}
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
if (message.method.startsWith("fs/")) {
|
|
7743
|
-
params.path = await this.relative(
|
|
7744
|
-
params.path,
|
|
7745
|
-
message.method === "fs/write_text_file"
|
|
7746
|
-
);
|
|
7747
|
-
for (const field of ["line", "limit"])
|
|
7748
|
-
if (params[field] !== void 0 && (!Number.isSafeInteger(params[field]) || params[field] < 1))
|
|
7749
|
-
throw new Error(`Invalid ${field}`);
|
|
7750
|
-
if (message.method === "fs/write_text_file")
|
|
7751
|
-
requireString(params, "content", MAX_BYTES);
|
|
7752
|
-
}
|
|
7753
|
-
if (agent.stopped) return;
|
|
7754
|
-
if (registry !== agent.subagents || !registry.acceptsRequest(sessionId))
|
|
7755
|
-
throw new Error("ACP session ended");
|
|
7756
|
-
const requestId = randomUUID();
|
|
7757
|
-
agent.approvals.set(requestId, {
|
|
7758
|
-
rpcId: message.id,
|
|
7759
|
-
method: message.method,
|
|
7760
|
-
params
|
|
7761
|
-
});
|
|
7762
|
-
if (message.method !== "fs/read_text_file")
|
|
7763
|
-
registry.waiting(
|
|
7764
|
-
sessionId,
|
|
7765
|
-
requestId,
|
|
7766
|
-
true,
|
|
7767
|
-
params.toolCallId ?? params.toolCall?.toolCallId
|
|
7768
|
-
);
|
|
7769
|
-
this.publish(agent, "acp.request", {
|
|
7770
|
-
sessionId,
|
|
7771
|
-
rootSessionId: registry.root,
|
|
7772
|
-
subagentId: registry.idForSession(sessionId) ?? registry.idForTool(
|
|
7773
|
-
sessionId,
|
|
7774
|
-
params.toolCallId ?? params.toolCall?.toolCallId
|
|
7775
|
-
),
|
|
7776
|
-
requestId,
|
|
7777
|
-
method: message.method,
|
|
7778
|
-
params
|
|
7779
|
-
});
|
|
8434
|
+
const result = await this.clientRequest(agent, message.method, params);
|
|
8435
|
+
if (!agent.stopped) this.send(agent, { id: message.id, result });
|
|
7780
8436
|
} catch (error) {
|
|
7781
8437
|
if (!agent.stopped)
|
|
7782
8438
|
this.send(agent, {
|
|
@@ -7795,10 +8451,8 @@ ${content}`
|
|
|
7795
8451
|
)))
|
|
7796
8452
|
throw new Error("Invalid permission response");
|
|
7797
8453
|
}
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
...error ? { error: { code: -32603, message: error.slice(0, 4096) } } : { result }
|
|
7801
|
-
});
|
|
8454
|
+
if (error) request.reject(new Error(error.slice(0, 4096)));
|
|
8455
|
+
else request.resolve(result);
|
|
7802
8456
|
agent.approvals.delete(requestId);
|
|
7803
8457
|
agent.subagents?.waiting(
|
|
7804
8458
|
request.params.sessionId,
|
|
@@ -7933,26 +8587,29 @@ ${content}`
|
|
|
7933
8587
|
pending.reject(new Error(reason));
|
|
7934
8588
|
}
|
|
7935
8589
|
agent.pending.clear();
|
|
8590
|
+
for (const approval of agent.approvals.values()) approval.reject(new Error(reason));
|
|
7936
8591
|
agent.approvals.clear();
|
|
7937
8592
|
for (const terminal2 of agent.terminals.values())
|
|
7938
8593
|
killProcess(terminal2.child);
|
|
7939
8594
|
agent.terminals.clear();
|
|
7940
8595
|
killProcess(agent.child);
|
|
7941
8596
|
this.agents.delete(agent.connection.id);
|
|
8597
|
+
this.mcp.revoke(agent.connection.id);
|
|
7942
8598
|
this.publish(agent, "acp.exit", { reason });
|
|
7943
8599
|
}
|
|
7944
8600
|
stop(owner, id) {
|
|
7945
8601
|
this.stopAgent(this.get(owner, id), "Agent disconnected");
|
|
7946
8602
|
return {};
|
|
7947
8603
|
}
|
|
7948
|
-
disconnect(owner) {
|
|
8604
|
+
disconnect(owner, client) {
|
|
7949
8605
|
for (const agent of this.agents.values())
|
|
7950
|
-
if (agent.owner === owner)
|
|
8606
|
+
if (agent.owner === owner && (client === void 0 || agent.client === client))
|
|
7951
8607
|
this.stopAgent(agent, "Agent client disconnected");
|
|
7952
8608
|
}
|
|
7953
8609
|
dispose() {
|
|
7954
8610
|
for (const agent of this.agents.values())
|
|
7955
8611
|
this.stopAgent(agent, "Agent tooling stopped");
|
|
8612
|
+
return this.mcp.close();
|
|
7956
8613
|
}
|
|
7957
8614
|
}
|
|
7958
8615
|
var re = { exports: {} };
|
|
@@ -10149,12 +10806,12 @@ function createKernel({
|
|
|
10149
10806
|
get(id, language) {
|
|
10150
10807
|
return configuration.inspect(id, language).value;
|
|
10151
10808
|
},
|
|
10152
|
-
inspect(id, language) {
|
|
10809
|
+
inspect(id, language, scope = "workspace") {
|
|
10153
10810
|
const scopes = language ? [.../* @__PURE__ */ new Set([language, canonicalLanguageId(language), ...languages.find((item) => item.id === canonicalLanguageId(language))?.settingsAliases ?? []])] : [];
|
|
10154
10811
|
const ordered = [
|
|
10155
|
-
...scopes.map((id2) => configurationData.workspaceLanguages[id2]),
|
|
10812
|
+
...scope === "workspace" ? scopes.map((id2) => configurationData.workspaceLanguages[id2]) : [],
|
|
10156
10813
|
...scopes.map((id2) => configurationData.userLanguages[id2]),
|
|
10157
|
-
configurationData.workspace,
|
|
10814
|
+
...scope === "workspace" ? [configurationData.workspace] : [],
|
|
10158
10815
|
configurationData.user
|
|
10159
10816
|
];
|
|
10160
10817
|
let value = structuredClone(settings.get(id)?.default);
|
|
@@ -10941,11 +11598,6 @@ class RuntimeExtensions {
|
|
|
10941
11598
|
this.watchers.clear();
|
|
10942
11599
|
}
|
|
10943
11600
|
}
|
|
10944
|
-
let cached;
|
|
10945
|
-
function runtimeVersion() {
|
|
10946
|
-
if (cached) return cached;
|
|
10947
|
-
return cached = "0.6.0";
|
|
10948
|
-
}
|
|
10949
11601
|
const wildcardHost = (host) => host === "0.0.0.0" || host === "::";
|
|
10950
11602
|
const localHost = (host) => wildcardHost(host) ? "127.0.0.1" : host;
|
|
10951
11603
|
const localName = (hostname = os.hostname()) => hostname.split(".")[0];
|
|
@@ -10978,7 +11630,7 @@ function lanUrl(port2, interfaces = os.networkInterfaces()) {
|
|
|
10978
11630
|
const address = lanAddresses$1(interfaces).v4[0];
|
|
10979
11631
|
return address ? `http://${address}:${port2}` : void 0;
|
|
10980
11632
|
}
|
|
10981
|
-
const MAX_SOCKET_BACKLOG_BYTES =
|
|
11633
|
+
const MAX_SOCKET_BACKLOG_BYTES = MAX_FILE_WRITE_MESSAGE_BYTES;
|
|
10982
11634
|
const allCapabilities = [
|
|
10983
11635
|
"filesystem.read",
|
|
10984
11636
|
"filesystem.write",
|
|
@@ -11023,7 +11675,8 @@ async function createRuntime(options2) {
|
|
|
11023
11675
|
const dataDir = path.resolve(options2.dataDir ?? workspaceDataDir(root));
|
|
11024
11676
|
await fs.mkdir(dataDir, { recursive: true, mode: 448 });
|
|
11025
11677
|
const stateFile = path.join(dataDir, "runtime.json");
|
|
11026
|
-
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();
|
|
11027
11680
|
let trusted = false, closed = false, runtimeId = randomUUID();
|
|
11028
11681
|
try {
|
|
11029
11682
|
const state2 = JSON.parse(await fs.readFile(stateFile, "utf8"));
|
|
@@ -11031,16 +11684,7 @@ async function createRuntime(options2) {
|
|
|
11031
11684
|
if (typeof state2.id === "string" && /^[A-Za-z0-9-]{8,64}$/.test(state2.id)) runtimeId = state2.id;
|
|
11032
11685
|
for (const session of state2.sessions ?? [])
|
|
11033
11686
|
sessions.set(session.hash, session);
|
|
11034
|
-
|
|
11035
|
-
if (operation.status === "running") {
|
|
11036
|
-
operation.status = "interrupted";
|
|
11037
|
-
operation.error = {
|
|
11038
|
-
code: "INTERRUPTED",
|
|
11039
|
-
message: "Runtime stopped before operation completion was recorded; inspect state before retrying with a new request ID"
|
|
11040
|
-
};
|
|
11041
|
-
}
|
|
11042
|
-
operations.set(`${operation.owner}:${operation.id}`, operation);
|
|
11043
|
-
}
|
|
11687
|
+
operations = new OperationHistory(state2.operationHistory, state2.operations ?? []);
|
|
11044
11688
|
} catch (error) {
|
|
11045
11689
|
if (error.code !== "ENOENT")
|
|
11046
11690
|
throw new Error(`Cannot recover runtime state: ${String(error)}`);
|
|
@@ -11063,6 +11707,7 @@ async function createRuntime(options2) {
|
|
|
11063
11707
|
id: runtimeId,
|
|
11064
11708
|
trusted,
|
|
11065
11709
|
sessions: [...sessions.values()],
|
|
11710
|
+
operationHistory: operations.checkpoint(),
|
|
11066
11711
|
operations: [...operations.values()]
|
|
11067
11712
|
});
|
|
11068
11713
|
persistence = persistence.catch(() => {
|
|
@@ -11129,7 +11774,7 @@ async function createRuntime(options2) {
|
|
|
11129
11774
|
const agents = new AgentACP(files, (connectionId, name, params) => {
|
|
11130
11775
|
const connection = connections.get(connectionId);
|
|
11131
11776
|
if (connection?.session && !connection.session.revoked && trusted) event(connection, name, params);
|
|
11132
|
-
});
|
|
11777
|
+
}, () => updateIdle());
|
|
11133
11778
|
const processEvent = ({ event: name, params, stream, seq }, ownerId) => {
|
|
11134
11779
|
const cap = name.startsWith("terminal.") ? "terminal" : "tasks";
|
|
11135
11780
|
for (const c of connections.values()) {
|
|
@@ -11219,7 +11864,8 @@ async function createRuntime(options2) {
|
|
|
11219
11864
|
capabilities: session.capabilities,
|
|
11220
11865
|
owner: session.owner,
|
|
11221
11866
|
trusted,
|
|
11222
|
-
sessionId: session.id
|
|
11867
|
+
sessionId: session.id,
|
|
11868
|
+
operationEpoch: operations.epoch
|
|
11223
11869
|
});
|
|
11224
11870
|
const createSession = async (isOwner, capabilities) => {
|
|
11225
11871
|
const token = randomBytes(32).toString("base64url"), session = {
|
|
@@ -11238,6 +11884,7 @@ async function createRuntime(options2) {
|
|
|
11238
11884
|
const session = [...sessions.values()].find((s) => s.id === id);
|
|
11239
11885
|
if (!session) throw new RpcError("NOT_FOUND", "Grant does not exist");
|
|
11240
11886
|
session.revoked = true;
|
|
11887
|
+
agents.disconnect(session.id);
|
|
11241
11888
|
processes.revoke(session.id);
|
|
11242
11889
|
tasks.revoke(session.id);
|
|
11243
11890
|
for (const c of connections.values())
|
|
@@ -11441,7 +12088,7 @@ async function createRuntime(options2) {
|
|
|
11441
12088
|
});
|
|
11442
12089
|
const websocket = new WebSocketServer({
|
|
11443
12090
|
noServer: true,
|
|
11444
|
-
maxPayload:
|
|
12091
|
+
maxPayload: MAX_FILE_WRITE_MESSAGE_BYTES,
|
|
11445
12092
|
perMessageDeflate: false
|
|
11446
12093
|
});
|
|
11447
12094
|
server.on("upgrade", (request, socket, head) => {
|
|
@@ -11501,7 +12148,11 @@ async function createRuntime(options2) {
|
|
|
11501
12148
|
if (workspace !== "default")
|
|
11502
12149
|
throw new RpcError("FORBIDDEN", "Workspace is not granted");
|
|
11503
12150
|
const required = permission(method), session = authorized(connection, required.cap, required.trust);
|
|
11504
|
-
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
|
+
}
|
|
11505
12156
|
if (method.startsWith("project.")) owner(connection);
|
|
11506
12157
|
if (method.startsWith("settings.")) owner(connection);
|
|
11507
12158
|
switch (method) {
|
|
@@ -11524,17 +12175,28 @@ async function createRuntime(options2) {
|
|
|
11524
12175
|
case "project.relations":
|
|
11525
12176
|
return project.relations(requireString(params, "path"));
|
|
11526
12177
|
case "acp.start":
|
|
11527
|
-
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"));
|
|
11528
12190
|
case "acp.call":
|
|
11529
|
-
return agents.call(
|
|
12191
|
+
return agents.call(session.id, requireString(params, "id"), requireString(params, "method"), params.params ?? {});
|
|
11530
12192
|
case "acp.respond":
|
|
11531
|
-
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"));
|
|
11532
12194
|
case "acp.cancel":
|
|
11533
|
-
return agents.cancel(
|
|
12195
|
+
return agents.cancel(session.id, requireString(params, "id"));
|
|
11534
12196
|
case "acp.stop":
|
|
11535
|
-
return agents.stop(
|
|
12197
|
+
return agents.stop(session.id, requireString(params, "id"));
|
|
11536
12198
|
case "acp.disconnect":
|
|
11537
|
-
agents.disconnect(connection.id);
|
|
12199
|
+
agents.disconnect(session.id, connection.id);
|
|
11538
12200
|
return {};
|
|
11539
12201
|
case "workspace.info":
|
|
11540
12202
|
return sessionInfo(session);
|
|
@@ -11544,7 +12206,7 @@ async function createRuntime(options2) {
|
|
|
11544
12206
|
throw new RpcError("INVALID_PARAMS", "trusted must be boolean");
|
|
11545
12207
|
trusted = params.trusted;
|
|
11546
12208
|
if (!trusted) {
|
|
11547
|
-
agents.dispose();
|
|
12209
|
+
await agents.dispose();
|
|
11548
12210
|
await extensions.suspend();
|
|
11549
12211
|
for (const c of connections.values()) for (const pending of c.pending.values()) pending.abort();
|
|
11550
12212
|
for (const s of sessions.values()) {
|
|
@@ -11572,10 +12234,7 @@ async function createRuntime(options2) {
|
|
|
11572
12234
|
owner(connection);
|
|
11573
12235
|
return revoke(requireString(params, "id"));
|
|
11574
12236
|
case "operation.status": {
|
|
11575
|
-
|
|
11576
|
-
`${session.id}:${requireString(params, "id")}`
|
|
11577
|
-
);
|
|
11578
|
-
return value ?? { id: params.id, status: "unknown" };
|
|
12237
|
+
return operations.status(session.id, requireString(params, "id"));
|
|
11579
12238
|
}
|
|
11580
12239
|
case "fs.list":
|
|
11581
12240
|
return files.list(typeof params.path === "string" ? params.path : "");
|
|
@@ -11593,7 +12252,7 @@ async function createRuntime(options2) {
|
|
|
11593
12252
|
case "fs.write": {
|
|
11594
12253
|
const snapshot = await files.write(
|
|
11595
12254
|
requireString(params, "path"),
|
|
11596
|
-
requireString(params, "text",
|
|
12255
|
+
requireString(params, "text", MAX_FILE_BYTES),
|
|
11597
12256
|
{
|
|
11598
12257
|
expectedRevision: requireRevision(params),
|
|
11599
12258
|
encoding: params.encoding,
|
|
@@ -11856,7 +12515,7 @@ async function createRuntime(options2) {
|
|
|
11856
12515
|
return collaboration.update(
|
|
11857
12516
|
requireString(params, "path"),
|
|
11858
12517
|
connection.id,
|
|
11859
|
-
requireString(params, "update",
|
|
12518
|
+
requireString(params, "update", MAX_COLLABORATION_UPDATE_BYTES)
|
|
11860
12519
|
);
|
|
11861
12520
|
case "collab.awareness":
|
|
11862
12521
|
return collaboration.awareness(
|
|
@@ -11966,7 +12625,7 @@ async function createRuntime(options2) {
|
|
|
11966
12625
|
};
|
|
11967
12626
|
let idleSince, idleTimer;
|
|
11968
12627
|
const updateIdle = () => {
|
|
11969
|
-
if ([...connections.values()].some((c) => c.session && !c.session.revoked)) {
|
|
12628
|
+
if (agents.hasWork || [...connections.values()].some((c) => c.session && !c.session.revoked)) {
|
|
11970
12629
|
idleSince = void 0;
|
|
11971
12630
|
clearTimeout(idleTimer);
|
|
11972
12631
|
return;
|
|
@@ -12056,7 +12715,7 @@ async function createRuntime(options2) {
|
|
|
12056
12715
|
let result;
|
|
12057
12716
|
if (durableMethods.has(method)) {
|
|
12058
12717
|
const required = permission(method), session = authorized(connection, required.cap, required.trust);
|
|
12059
|
-
const key = `${session.id}:${id}`, existing = operations.get(
|
|
12718
|
+
const key = `${session.id}:${id}`, existing = operations.get(session.id, id);
|
|
12060
12719
|
if (existing) {
|
|
12061
12720
|
if (existing.method !== method)
|
|
12062
12721
|
throw new RpcError(
|
|
@@ -12064,6 +12723,8 @@ async function createRuntime(options2) {
|
|
|
12064
12723
|
"Request ID was used for another operation"
|
|
12065
12724
|
);
|
|
12066
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 });
|
|
12067
12728
|
else if (existing.status === "completed")
|
|
12068
12729
|
result = existing.result;
|
|
12069
12730
|
else
|
|
@@ -12073,14 +12734,11 @@ async function createRuntime(options2) {
|
|
|
12073
12734
|
existing.error?.data
|
|
12074
12735
|
);
|
|
12075
12736
|
} else {
|
|
12076
|
-
const
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
startedAt: Date.now()
|
|
12082
|
-
};
|
|
12083
|
-
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
|
+
}
|
|
12084
12742
|
const run2 = (async () => {
|
|
12085
12743
|
await persist();
|
|
12086
12744
|
try {
|
|
@@ -12091,13 +12749,11 @@ async function createRuntime(options2) {
|
|
|
12091
12749
|
controller.signal,
|
|
12092
12750
|
id
|
|
12093
12751
|
);
|
|
12094
|
-
operation
|
|
12095
|
-
operation.result = value;
|
|
12752
|
+
operations.complete(operation, value);
|
|
12096
12753
|
await persist();
|
|
12097
12754
|
return value;
|
|
12098
12755
|
} catch (error) {
|
|
12099
|
-
operation
|
|
12100
|
-
operation.error = errorShape(error);
|
|
12756
|
+
operations.fail(operation, errorShape(error));
|
|
12101
12757
|
await persist();
|
|
12102
12758
|
throw error;
|
|
12103
12759
|
}
|
|
@@ -12142,7 +12798,7 @@ async function createRuntime(options2) {
|
|
|
12142
12798
|
});
|
|
12143
12799
|
ws.on("close", () => {
|
|
12144
12800
|
clearTimeout(authDeadline);
|
|
12145
|
-
agents.
|
|
12801
|
+
agents.detach(connection.id);
|
|
12146
12802
|
lsp.detach(connection.id);
|
|
12147
12803
|
connections.delete(connection.id);
|
|
12148
12804
|
updateIdle();
|
|
@@ -12154,7 +12810,7 @@ async function createRuntime(options2) {
|
|
|
12154
12810
|
processes.disconnect(connection.id);
|
|
12155
12811
|
void collaboration.leave(connection.id);
|
|
12156
12812
|
for (const [id, controller] of connection.pending) {
|
|
12157
|
-
const operation = connection.session && operations.get(
|
|
12813
|
+
const operation = connection.session && operations.get(connection.session.id, id);
|
|
12158
12814
|
if (!operation) controller.abort();
|
|
12159
12815
|
}
|
|
12160
12816
|
});
|
|
@@ -12284,7 +12940,7 @@ async function createRuntime(options2) {
|
|
|
12284
12940
|
for (const controller of c.pending.values()) controller.abort();
|
|
12285
12941
|
c.ws.terminate();
|
|
12286
12942
|
}
|
|
12287
|
-
agents.dispose();
|
|
12943
|
+
await agents.dispose();
|
|
12288
12944
|
extensions.dispose();
|
|
12289
12945
|
processes.close();
|
|
12290
12946
|
await tasks.close();
|
|
@@ -12534,17 +13190,17 @@ function advertise(ad, log = stderrLog) {
|
|
|
12534
13190
|
try {
|
|
12535
13191
|
const query = parseMessage(packet);
|
|
12536
13192
|
if (query.flags & 32768) return;
|
|
12537
|
-
const
|
|
12538
|
-
if (!
|
|
12539
|
-
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));
|
|
12540
13196
|
const legacy = (r) => ({ ...r, ttl: Math.min(r.ttl, 10), flush: false });
|
|
12541
13197
|
send(
|
|
12542
13198
|
encodeMessage({
|
|
12543
13199
|
id: query.id,
|
|
12544
13200
|
flags: FLAGS_RESPONSE,
|
|
12545
13201
|
questions: query.questions,
|
|
12546
|
-
answers:
|
|
12547
|
-
additionals:
|
|
13202
|
+
answers: reply2.answers.map(legacy),
|
|
13203
|
+
additionals: reply2.additionals.map(legacy)
|
|
12548
13204
|
}),
|
|
12549
13205
|
from.port,
|
|
12550
13206
|
from.address
|