@rivet-dev/agentos-core 0.0.0-main.29b0797 → 0.0.0-main.5ac5a14
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/README.md +10 -199
- package/dist/agent-os.d.ts +332 -54
- package/dist/agent-os.js +1533 -91
- package/dist/agentos-package.d.ts +2 -2
- package/dist/agentos-package.js +2 -2
- package/dist/base-filesystem.js +2 -2
- package/dist/code-execution.d.ts +70 -0
- package/dist/code-execution.js +115 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/language-execution.d.ts +170 -0
- package/dist/language-execution.js +1 -0
- package/dist/options-schema.d.ts +19 -3
- package/dist/options-schema.js +17 -2
- package/dist/packages.d.ts +1 -1
- package/dist/runtime-compat.js +19 -7
- package/dist/sandbox.js +33 -20
- package/dist/sidecar/agentos-protocol.d.ts +14 -3
- package/dist/sidecar/agentos-protocol.js +10 -0
- package/dist/sidecar/rpc-client.d.ts +1 -0
- package/dist/sidecar/rpc-client.js +25 -16
- package/dist/types.d.ts +5 -3
- package/package.json +26 -20
package/dist/agent-os.js
CHANGED
|
@@ -3,14 +3,18 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
4
4
|
import { join, posix as posixPath, resolve as resolveHostPath, } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import * as executionProtocol from "@rivet-dev/agentos-runtime-core/protocol";
|
|
7
|
+
import { SidecarRejectedError } from "@rivet-dev/agentos-runtime-core/sidecar-errors";
|
|
6
8
|
import { validateBindings } from "./bindings.js";
|
|
7
9
|
import { zodToJsonSchema } from "./bindings-zod.js";
|
|
8
10
|
import { parseAgentOsOptions } from "./options-schema.js";
|
|
11
|
+
import { getSandboxDisposeHooks, resolveSandboxOptions, } from "./sandbox.js";
|
|
9
12
|
import { resolvePublishedSidecarBinary } from "./sidecar/binary.js";
|
|
10
13
|
import { findCargoBinary, resolveCargoBinary } from "./sidecar/cargo.js";
|
|
11
14
|
const ACP_PROTOCOL_VERSION = 1;
|
|
12
15
|
const ACP_EXTENSION_NAMESPACE = "dev.rivet.agent-os.acp";
|
|
13
16
|
const SHELL_DISPOSE_TIMEOUT_MS = 5_000;
|
|
17
|
+
const PROCESS_OUTPUT_EVENT_LIMIT = 1_024;
|
|
14
18
|
function safeWireU64(value) {
|
|
15
19
|
const result = Number(value);
|
|
16
20
|
if (!Number.isSafeInteger(result) || result < 0) {
|
|
@@ -1427,12 +1431,212 @@ async function registerBindingsOnSidecar(client, session, vm, bindings) {
|
|
|
1427
1431
|
}
|
|
1428
1432
|
return buildBindingReference(bindings);
|
|
1429
1433
|
}
|
|
1434
|
+
function executionIdentity(options) {
|
|
1435
|
+
return {
|
|
1436
|
+
contextId: options.contextId ?? null,
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function executionOutput(options, background = false) {
|
|
1440
|
+
if (options.output?.retainEvents && !options.contextId && !background) {
|
|
1441
|
+
throw new Error("retainEvents is available only for spawned processes; use output.capture for attached runs");
|
|
1442
|
+
}
|
|
1443
|
+
const capture = options.output?.capture;
|
|
1444
|
+
return {
|
|
1445
|
+
capture: capture === "all"
|
|
1446
|
+
? executionProtocol.ExecutionOutputCapture.All
|
|
1447
|
+
: capture === "stderr"
|
|
1448
|
+
? executionProtocol.ExecutionOutputCapture.Stderr
|
|
1449
|
+
: capture === "none"
|
|
1450
|
+
? executionProtocol.ExecutionOutputCapture.None
|
|
1451
|
+
: null,
|
|
1452
|
+
retainEvents: options.output?.retainEvents ?? null,
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function executionBytes(data) {
|
|
1456
|
+
if (data === undefined)
|
|
1457
|
+
return null;
|
|
1458
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1459
|
+
return bytes.slice().buffer;
|
|
1460
|
+
}
|
|
1461
|
+
function processExecutionOptions(options = {}, internal) {
|
|
1462
|
+
const background = internal?.background ?? false;
|
|
1463
|
+
return {
|
|
1464
|
+
identity: executionIdentity(options),
|
|
1465
|
+
output: executionOutput(options, background),
|
|
1466
|
+
operationId: internal?.executionId ?? null,
|
|
1467
|
+
background,
|
|
1468
|
+
cwd: options.cwd ?? null,
|
|
1469
|
+
env: options.env ? new Map(Object.entries(options.env)) : null,
|
|
1470
|
+
args: options.args ?? [],
|
|
1471
|
+
stdin: executionBytes(options.stdin),
|
|
1472
|
+
timeoutMs: protocolTimeout(options.timeoutMs),
|
|
1473
|
+
pty: options.pty
|
|
1474
|
+
? {
|
|
1475
|
+
cols: options.pty.cols === undefined
|
|
1476
|
+
? null
|
|
1477
|
+
: protocolUnsigned(options.pty.cols, 0xffff, "pty.cols"),
|
|
1478
|
+
rows: options.pty.rows === undefined
|
|
1479
|
+
? null
|
|
1480
|
+
: protocolUnsigned(options.pty.rows, 0xffff, "pty.rows"),
|
|
1481
|
+
}
|
|
1482
|
+
: null,
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
function protocolUnsigned(value, maximum, name) {
|
|
1486
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > maximum) {
|
|
1487
|
+
throw new RangeError(`${name} must be an integer between 0 and ${maximum}`);
|
|
1488
|
+
}
|
|
1489
|
+
return value;
|
|
1490
|
+
}
|
|
1491
|
+
function protocolTimeout(value) {
|
|
1492
|
+
if (value === undefined)
|
|
1493
|
+
return null;
|
|
1494
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1495
|
+
throw new RangeError("timeoutMs must be a non-negative safe integer");
|
|
1496
|
+
}
|
|
1497
|
+
return BigInt(value);
|
|
1498
|
+
}
|
|
1499
|
+
function safeProtocolNumber(value, name) {
|
|
1500
|
+
const result = Number(value);
|
|
1501
|
+
if (!Number.isSafeInteger(result)) {
|
|
1502
|
+
throw new RangeError(`${name} exceeds JavaScript's safe integer range`);
|
|
1503
|
+
}
|
|
1504
|
+
return result;
|
|
1505
|
+
}
|
|
1506
|
+
function mapExecutionState(state) {
|
|
1507
|
+
const mapped = state.toLowerCase();
|
|
1508
|
+
return (mapped === "creating" ? "idle" : mapped);
|
|
1509
|
+
}
|
|
1510
|
+
function mapExecutionOutcome(outcome) {
|
|
1511
|
+
return outcome === executionProtocol.ExecutionOutcome.TimedOut
|
|
1512
|
+
? "timed_out"
|
|
1513
|
+
: outcome.toLowerCase();
|
|
1514
|
+
}
|
|
1515
|
+
function mapExecutionDescriptor(descriptor) {
|
|
1516
|
+
return {
|
|
1517
|
+
contextId: descriptor.executionId,
|
|
1518
|
+
state: mapExecutionState(descriptor.state),
|
|
1519
|
+
...(descriptor.retainedLanguage
|
|
1520
|
+
? {
|
|
1521
|
+
language: descriptor.retainedLanguage ===
|
|
1522
|
+
executionProtocol.RetainedExecutionLanguage.JavaScript
|
|
1523
|
+
? "javascript"
|
|
1524
|
+
: "python",
|
|
1525
|
+
}
|
|
1526
|
+
: {}),
|
|
1527
|
+
createdAtMs: safeProtocolNumber(descriptor.createdAtMs, "createdAtMs"),
|
|
1528
|
+
...(descriptor.lastStartedAtMs !== null
|
|
1529
|
+
? {
|
|
1530
|
+
lastStartedAtMs: safeProtocolNumber(descriptor.lastStartedAtMs, "lastStartedAtMs"),
|
|
1531
|
+
}
|
|
1532
|
+
: {}),
|
|
1533
|
+
...(descriptor.lastCompletedAtMs !== null
|
|
1534
|
+
? {
|
|
1535
|
+
lastCompletedAtMs: safeProtocolNumber(descriptor.lastCompletedAtMs, "lastCompletedAtMs"),
|
|
1536
|
+
}
|
|
1537
|
+
: {}),
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
function mapExecutionError(error) {
|
|
1541
|
+
if (!error)
|
|
1542
|
+
return undefined;
|
|
1543
|
+
return {
|
|
1544
|
+
code: error.code,
|
|
1545
|
+
name: error.name,
|
|
1546
|
+
message: error.message,
|
|
1547
|
+
...(error.stack ? { stack: error.stack } : {}),
|
|
1548
|
+
...(error.details
|
|
1549
|
+
? { details: JSON.parse(error.details) }
|
|
1550
|
+
: {}),
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
function mapExecutionResult(result) {
|
|
1554
|
+
const base = {
|
|
1555
|
+
...(result.exitCode !== null ? { exitCode: result.exitCode } : {}),
|
|
1556
|
+
...(result.stdout !== null
|
|
1557
|
+
? { stdout: new TextDecoder().decode(result.stdout) }
|
|
1558
|
+
: {}),
|
|
1559
|
+
...(result.stderr !== null
|
|
1560
|
+
? { stderr: new TextDecoder().decode(result.stderr) }
|
|
1561
|
+
: {}),
|
|
1562
|
+
...(result.stdoutTruncated !== null
|
|
1563
|
+
? { stdoutTruncated: result.stdoutTruncated }
|
|
1564
|
+
: {}),
|
|
1565
|
+
...(result.stderrTruncated !== null
|
|
1566
|
+
? { stderrTruncated: result.stderrTruncated }
|
|
1567
|
+
: {}),
|
|
1568
|
+
...(result.evaluationValue
|
|
1569
|
+
? { evaluationValue: JSON.parse(result.evaluationValue) }
|
|
1570
|
+
: {}),
|
|
1571
|
+
...(result.typeScriptCheckResult
|
|
1572
|
+
? {
|
|
1573
|
+
typeScriptCheckResult: JSON.parse(result.typeScriptCheckResult),
|
|
1574
|
+
}
|
|
1575
|
+
: {}),
|
|
1576
|
+
};
|
|
1577
|
+
const outcome = mapExecutionOutcome(result.outcome);
|
|
1578
|
+
if (outcome === "succeeded") {
|
|
1579
|
+
return { ...base, outcome };
|
|
1580
|
+
}
|
|
1581
|
+
return {
|
|
1582
|
+
...base,
|
|
1583
|
+
outcome,
|
|
1584
|
+
error: mapExecutionError(result.error) ?? {
|
|
1585
|
+
code: "execution_failed",
|
|
1586
|
+
name: "ExecutionError",
|
|
1587
|
+
message: `execution completed with ${outcome}`,
|
|
1588
|
+
},
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function mapTypeScriptCheckResult(result) {
|
|
1592
|
+
if (result.outcome !== "succeeded") {
|
|
1593
|
+
return { ...result, diagnostics: [] };
|
|
1594
|
+
}
|
|
1595
|
+
const data = result.typeScriptCheckResult;
|
|
1596
|
+
if (!data || Array.isArray(data) || typeof data !== "object") {
|
|
1597
|
+
throw new Error("TypeScript checker returned no diagnostic result");
|
|
1598
|
+
}
|
|
1599
|
+
const hasErrors = data.hasErrors;
|
|
1600
|
+
const diagnostics = data.diagnostics;
|
|
1601
|
+
if (typeof hasErrors !== "boolean" || !Array.isArray(diagnostics)) {
|
|
1602
|
+
throw new Error("TypeScript checker returned an invalid diagnostic result");
|
|
1603
|
+
}
|
|
1604
|
+
return {
|
|
1605
|
+
...result,
|
|
1606
|
+
hasErrors,
|
|
1607
|
+
diagnostics: diagnostics,
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function mapExecutionOutputEvent(event) {
|
|
1611
|
+
return {
|
|
1612
|
+
executionId: event.executionId,
|
|
1613
|
+
generation: safeProtocolNumber(event.generation, "execution generation"),
|
|
1614
|
+
...(event.processId ? { processId: event.processId } : {}),
|
|
1615
|
+
sequence: safeProtocolNumber(event.sequence, "execution output sequence"),
|
|
1616
|
+
channel: event.channel.toLowerCase(),
|
|
1617
|
+
chunk: new Uint8Array(event.chunk),
|
|
1618
|
+
timestampMs: safeProtocolNumber(event.timestampMs, "execution timestamp"),
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
function mapExecutionCompletedEvent(event) {
|
|
1622
|
+
return {
|
|
1623
|
+
executionId: event.executionId,
|
|
1624
|
+
generation: safeProtocolNumber(event.generation, "execution generation"),
|
|
1625
|
+
outcome: mapExecutionOutcome(event.outcome),
|
|
1626
|
+
...(event.exitCode !== null ? { exitCode: event.exitCode } : {}),
|
|
1627
|
+
...(event.error ? { error: mapExecutionError(event.error) } : {}),
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1430
1630
|
export class AgentOs {
|
|
1431
1631
|
#kernel;
|
|
1432
1632
|
sidecar;
|
|
1433
1633
|
_durableSessionEventHandlers = new Map();
|
|
1434
1634
|
_agentExitHandlers = new Map();
|
|
1435
1635
|
_processes = new Map();
|
|
1636
|
+
_languageProcesses = new Map();
|
|
1637
|
+
_languageProcessIds = new Map();
|
|
1638
|
+
_executionOutputHandlers = new Map();
|
|
1639
|
+
_executionCompletedHandlers = new Map();
|
|
1436
1640
|
_shells = new Map();
|
|
1437
1641
|
// Value is the recorded exit code (undefined until/unless the exit
|
|
1438
1642
|
// resolves) so waitShell can still report it after the entry is dropped.
|
|
@@ -1457,6 +1661,120 @@ export class AgentOs {
|
|
|
1457
1661
|
_agentStderrHandler;
|
|
1458
1662
|
_agentExitHandler;
|
|
1459
1663
|
_limitWarningHandler;
|
|
1664
|
+
_disposeHooks = [];
|
|
1665
|
+
/**
|
|
1666
|
+
* Execute commands and manage child processes in the VM.
|
|
1667
|
+
*
|
|
1668
|
+
* `exec` evaluates a command with the configured shell, while `execFile`
|
|
1669
|
+
* invokes an executable directly with an argv array.
|
|
1670
|
+
*/
|
|
1671
|
+
process = {
|
|
1672
|
+
exec: this._exec.bind(this),
|
|
1673
|
+
execFile: this._execFile.bind(this),
|
|
1674
|
+
spawn: this._spawnProcess.bind(this),
|
|
1675
|
+
get: this._getProcess.bind(this),
|
|
1676
|
+
list: this._listProcesses.bind(this),
|
|
1677
|
+
tree: this._processTree.bind(this),
|
|
1678
|
+
wait: this._waitProcess.bind(this),
|
|
1679
|
+
signal: this._signalProcess.bind(this),
|
|
1680
|
+
kill: this._killProcess.bind(this),
|
|
1681
|
+
writeStdin: this._writeProcessStdin.bind(this),
|
|
1682
|
+
closeStdin: this._closeProcessStdin.bind(this),
|
|
1683
|
+
resizePty: this._resizeProcessPty.bind(this),
|
|
1684
|
+
readOutput: this._readProcessOutput.bind(this),
|
|
1685
|
+
};
|
|
1686
|
+
javascript = {
|
|
1687
|
+
execute: this._executeJavaScript.bind(this),
|
|
1688
|
+
evaluate: this._evaluateJavaScript.bind(this),
|
|
1689
|
+
executeFile: this._executeJavaScriptFile.bind(this),
|
|
1690
|
+
spawn: this._spawnJavaScript.bind(this),
|
|
1691
|
+
spawnFile: this._spawnJavaScriptFile.bind(this),
|
|
1692
|
+
npm: {
|
|
1693
|
+
install: this._installNpmPackages.bind(this),
|
|
1694
|
+
runScript: this._executeNpmScript.bind(this),
|
|
1695
|
+
runPackage: this._executeNpmPackage.bind(this),
|
|
1696
|
+
},
|
|
1697
|
+
};
|
|
1698
|
+
typescript = {
|
|
1699
|
+
execute: this._executeTypeScript.bind(this),
|
|
1700
|
+
evaluate: this._evaluateTypeScript.bind(this),
|
|
1701
|
+
executeFile: this._executeTypeScriptFile.bind(this),
|
|
1702
|
+
spawn: this._spawnTypeScript.bind(this),
|
|
1703
|
+
spawnFile: this._spawnTypeScriptFile.bind(this),
|
|
1704
|
+
check: this._checkTypeScript.bind(this),
|
|
1705
|
+
checkProject: this._checkTypeScriptProject.bind(this),
|
|
1706
|
+
};
|
|
1707
|
+
python = {
|
|
1708
|
+
execute: this._executePython.bind(this),
|
|
1709
|
+
evaluate: this._evaluatePython.bind(this),
|
|
1710
|
+
executeFile: this._executePythonFile.bind(this),
|
|
1711
|
+
executeModule: this._executePythonModule.bind(this),
|
|
1712
|
+
spawn: this._spawnPython.bind(this),
|
|
1713
|
+
spawnFile: this._spawnPythonFile.bind(this),
|
|
1714
|
+
spawnModule: this._spawnPythonModule.bind(this),
|
|
1715
|
+
install: this._installPythonPackages.bind(this),
|
|
1716
|
+
};
|
|
1717
|
+
contexts = {
|
|
1718
|
+
get: this._getContext.bind(this),
|
|
1719
|
+
list: this._listContexts.bind(this),
|
|
1720
|
+
reset: this._resetContext.bind(this),
|
|
1721
|
+
delete: this._deleteContext.bind(this),
|
|
1722
|
+
};
|
|
1723
|
+
terminal = {
|
|
1724
|
+
open: this._openTerminal.bind(this),
|
|
1725
|
+
write: this._writeTerminal.bind(this),
|
|
1726
|
+
resize: this._resizeTerminal.bind(this),
|
|
1727
|
+
wait: this._waitTerminal.bind(this),
|
|
1728
|
+
close: this._closeTerminal.bind(this),
|
|
1729
|
+
};
|
|
1730
|
+
filesystem = {
|
|
1731
|
+
readFile: this._readFile.bind(this),
|
|
1732
|
+
writeFile: this._writeFile.bind(this),
|
|
1733
|
+
readFiles: this._readFiles.bind(this),
|
|
1734
|
+
writeFiles: this._writeFiles.bind(this),
|
|
1735
|
+
stat: this._stat.bind(this),
|
|
1736
|
+
mkdir: this._mkdir.bind(this),
|
|
1737
|
+
readdir: this._readdir.bind(this),
|
|
1738
|
+
readdirEntries: this._readdirEntries.bind(this),
|
|
1739
|
+
readdirRecursive: this._readdirRecursive.bind(this),
|
|
1740
|
+
exists: this._exists.bind(this),
|
|
1741
|
+
move: this._move.bind(this),
|
|
1742
|
+
remove: this._remove.bind(this),
|
|
1743
|
+
export: this._exportRootFilesystem.bind(this),
|
|
1744
|
+
mount: this._mountFs.bind(this),
|
|
1745
|
+
unmount: this._unmountFs.bind(this),
|
|
1746
|
+
listMounts: this._listMounts.bind(this),
|
|
1747
|
+
};
|
|
1748
|
+
network = {
|
|
1749
|
+
httpRequest: this._httpRequest.bind(this),
|
|
1750
|
+
};
|
|
1751
|
+
software = {
|
|
1752
|
+
list: this._listSoftware.bind(this),
|
|
1753
|
+
link: this._linkSoftware.bind(this),
|
|
1754
|
+
};
|
|
1755
|
+
agents = {
|
|
1756
|
+
list: this._listAgents.bind(this),
|
|
1757
|
+
};
|
|
1758
|
+
sessions = {
|
|
1759
|
+
open: this._openSession.bind(this),
|
|
1760
|
+
get: this._getSession.bind(this),
|
|
1761
|
+
list: this._listSessions.bind(this),
|
|
1762
|
+
delete: this._deleteSession.bind(this),
|
|
1763
|
+
unload: this._unloadSession.bind(this),
|
|
1764
|
+
prompt: this._prompt.bind(this),
|
|
1765
|
+
cancelPrompt: this._cancelPrompt.bind(this),
|
|
1766
|
+
respondPermission: this._respondPermission.bind(this),
|
|
1767
|
+
readHistory: this._readHistory.bind(this),
|
|
1768
|
+
getConfig: this._getSessionConfig.bind(this),
|
|
1769
|
+
setConfigOption: this._setSessionConfigOption.bind(this),
|
|
1770
|
+
getCapabilities: this._getSessionCapabilities.bind(this),
|
|
1771
|
+
getAgentInfo: this._getSessionAgentInfo.bind(this),
|
|
1772
|
+
};
|
|
1773
|
+
cron = {
|
|
1774
|
+
schedule: this._scheduleCron.bind(this),
|
|
1775
|
+
list: this._listCronJobs.bind(this),
|
|
1776
|
+
cancel: this._cancelCronJob.bind(this),
|
|
1777
|
+
};
|
|
1460
1778
|
constructor(kernel, sidecar, softwareRoots, hostMounts, env, rootFilesystem, sidecarClient, sidecarSession, sidecarVm, agentStderrHandler, agentExitHandler, limitWarningHandler) {
|
|
1461
1779
|
this.#kernel = kernel;
|
|
1462
1780
|
this.sidecar = sidecar;
|
|
@@ -1522,13 +1840,15 @@ export class AgentOs {
|
|
|
1522
1840
|
// sidecar owns agent resolution, agent enumeration, and agent snapshot
|
|
1523
1841
|
// bundle loading from the projected package dirs.
|
|
1524
1842
|
const localMounts = await resolveCompatLocalMounts(options?.mounts);
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
validateBindings(bindings);
|
|
1843
|
+
if (options?.bindings && options.bindings.length > 0) {
|
|
1844
|
+
validateBindings(options.bindings);
|
|
1528
1845
|
}
|
|
1529
|
-
// Resolve the sidecar handle
|
|
1530
|
-
//
|
|
1846
|
+
// Resolve the sidecar handle before starting an external sandbox so option
|
|
1847
|
+
// validation failures cannot leak provider resources.
|
|
1531
1848
|
const sidecar = resolveAgentOsSidecar(options?.sidecar);
|
|
1849
|
+
options = await resolveSandboxOptions(options);
|
|
1850
|
+
const sandboxDisposeHooks = getSandboxDisposeHooks(options);
|
|
1851
|
+
const bindings = options.bindings;
|
|
1532
1852
|
const createVmAdmin = async () => {
|
|
1533
1853
|
// The `/opt/agentos` projection is built by the sidecar from the
|
|
1534
1854
|
// forwarded `packages` (it owns the staging dir + read-only mount, and
|
|
@@ -1736,27 +2056,688 @@ export class AgentOs {
|
|
|
1736
2056
|
vm._bindings = vmAdmin.bindings;
|
|
1737
2057
|
vm._bindingReference = vmAdmin.bindingReference;
|
|
1738
2058
|
vm._permissions = vmAdmin.permissions;
|
|
2059
|
+
vm._disposeHooks.push(...sandboxDisposeHooks);
|
|
1739
2060
|
vm._installSidecarRequestHandler();
|
|
1740
2061
|
vm._cronManager = new CronManager(vm, options?.scheduleDriver ?? new TimerScheduleDriver());
|
|
1741
2062
|
return vm;
|
|
1742
2063
|
}
|
|
1743
2064
|
catch (error) {
|
|
1744
|
-
|
|
2065
|
+
const cleanupErrors = [];
|
|
2066
|
+
try {
|
|
2067
|
+
await sidecarLease?.dispose();
|
|
2068
|
+
}
|
|
2069
|
+
catch (disposeError) {
|
|
2070
|
+
cleanupErrors.push(disposeError);
|
|
2071
|
+
}
|
|
2072
|
+
const sandboxCleanupResults = await Promise.allSettled(sandboxDisposeHooks.map((hook) => hook()));
|
|
2073
|
+
cleanupErrors.push(...sandboxCleanupResults.flatMap((result) => result.status === "rejected" ? [result.reason] : []));
|
|
2074
|
+
if (cleanupErrors.length > 0) {
|
|
2075
|
+
throw new AggregateError([error, ...cleanupErrors], "AgentOS VM creation and cleanup failed");
|
|
2076
|
+
}
|
|
1745
2077
|
throw error;
|
|
1746
2078
|
}
|
|
1747
2079
|
}
|
|
2080
|
+
async createContext(contextId) {
|
|
2081
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "create_context", request: { contextId } });
|
|
2082
|
+
if (response.type !== "execution_descriptor") {
|
|
2083
|
+
throw new Error(`unexpected createContext response: ${response.type}`);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
async _executionOperation(payload, options, background = false) {
|
|
2087
|
+
if (options.signal?.aborted) {
|
|
2088
|
+
throw options.signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
2089
|
+
}
|
|
2090
|
+
let executionId;
|
|
2091
|
+
const earlyOutput = [];
|
|
2092
|
+
const completedBeforeAdmission = new Set();
|
|
2093
|
+
let resolveCompletion;
|
|
2094
|
+
const completion = new Promise((resolve) => {
|
|
2095
|
+
resolveCompletion = resolve;
|
|
2096
|
+
});
|
|
2097
|
+
let warnedAboutEarlyEvents = false;
|
|
2098
|
+
const warnAboutEarlyEvents = () => {
|
|
2099
|
+
if (warnedAboutEarlyEvents)
|
|
2100
|
+
return;
|
|
2101
|
+
warnedAboutEarlyEvents = true;
|
|
2102
|
+
console.warn("[agentos] execution admission received more than 1024 early events; use process.readOutput() to replay any omitted output");
|
|
2103
|
+
};
|
|
2104
|
+
const deliverOutput = (event) => {
|
|
2105
|
+
if (event.channel === "stderr")
|
|
2106
|
+
options.onStderr?.(event.chunk);
|
|
2107
|
+
else
|
|
2108
|
+
options.onStdout?.(event.chunk);
|
|
2109
|
+
};
|
|
2110
|
+
const unsubscribeOutput = this._onExecutionOutput("*", (event) => {
|
|
2111
|
+
if (executionId === undefined) {
|
|
2112
|
+
if (earlyOutput.length < 1_024)
|
|
2113
|
+
earlyOutput.push(event);
|
|
2114
|
+
else
|
|
2115
|
+
warnAboutEarlyEvents();
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
if (event.executionId === executionId)
|
|
2119
|
+
deliverOutput(event);
|
|
2120
|
+
});
|
|
2121
|
+
let completed = false;
|
|
2122
|
+
const unsubscribeCompleted = this._onExecutionCompleted("*", (event) => {
|
|
2123
|
+
if (executionId === undefined) {
|
|
2124
|
+
if (completedBeforeAdmission.size < 1_024) {
|
|
2125
|
+
completedBeforeAdmission.add(event.executionId);
|
|
2126
|
+
}
|
|
2127
|
+
else
|
|
2128
|
+
warnAboutEarlyEvents();
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
if (event.executionId === executionId) {
|
|
2132
|
+
completed = true;
|
|
2133
|
+
resolveCompletion?.();
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
let response;
|
|
2137
|
+
try {
|
|
2138
|
+
response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, payload);
|
|
2139
|
+
}
|
|
2140
|
+
catch (error) {
|
|
2141
|
+
unsubscribeOutput();
|
|
2142
|
+
unsubscribeCompleted();
|
|
2143
|
+
throw error;
|
|
2144
|
+
}
|
|
2145
|
+
if (response.type !== "execution_accepted") {
|
|
2146
|
+
unsubscribeOutput();
|
|
2147
|
+
unsubscribeCompleted();
|
|
2148
|
+
throw new Error(`unexpected execution response: ${response.type}`);
|
|
2149
|
+
}
|
|
2150
|
+
const descriptor = response.response.execution;
|
|
2151
|
+
executionId = response.response.operationId;
|
|
2152
|
+
for (const event of earlyOutput) {
|
|
2153
|
+
if (event.executionId === executionId)
|
|
2154
|
+
deliverOutput(event);
|
|
2155
|
+
}
|
|
2156
|
+
completed = completedBeforeAdmission.has(executionId);
|
|
2157
|
+
if (completed)
|
|
2158
|
+
resolveCompletion?.();
|
|
2159
|
+
const abort = () => {
|
|
2160
|
+
// Admitted executions report cancellation through their structured result.
|
|
2161
|
+
void this._cancelExecution(response.response.operationId).catch((error) => {
|
|
2162
|
+
console.error("[agentos] failed to cancel aborted execution", error);
|
|
2163
|
+
});
|
|
2164
|
+
};
|
|
2165
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
2166
|
+
if (options.signal?.aborted)
|
|
2167
|
+
abort();
|
|
2168
|
+
const cleanup = () => {
|
|
2169
|
+
unsubscribeOutput();
|
|
2170
|
+
unsubscribeCompleted();
|
|
2171
|
+
options.signal?.removeEventListener("abort", abort);
|
|
2172
|
+
};
|
|
2173
|
+
if (background) {
|
|
2174
|
+
if (!descriptor || descriptor.pid === null) {
|
|
2175
|
+
cleanup();
|
|
2176
|
+
throw new Error("spawned execution admission returned no process id");
|
|
2177
|
+
}
|
|
2178
|
+
if (completed)
|
|
2179
|
+
cleanup();
|
|
2180
|
+
else {
|
|
2181
|
+
const unsubscribeBackgroundCompletion = this._onExecutionCompleted(descriptor.executionId, () => {
|
|
2182
|
+
unsubscribeBackgroundCompletion();
|
|
2183
|
+
cleanup();
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
return {
|
|
2187
|
+
executionId: descriptor.executionId,
|
|
2188
|
+
pid: descriptor.pid,
|
|
2189
|
+
completed,
|
|
2190
|
+
completion,
|
|
2191
|
+
createdAtMs: safeProtocolNumber(descriptor.createdAtMs, "process startedAtMs"),
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
try {
|
|
2195
|
+
await completion;
|
|
2196
|
+
return await this._waitExecutionResult(response.response.operationId);
|
|
2197
|
+
}
|
|
2198
|
+
finally {
|
|
2199
|
+
cleanup();
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
async _spawnLanguageOperation(buildPayload, options, language) {
|
|
2203
|
+
if ("contextId" in options) {
|
|
2204
|
+
throw new Error("contextId is not supported for spawned processes; contexts are for attached operations");
|
|
2205
|
+
}
|
|
2206
|
+
const executionId = `process-${randomUUID()}`;
|
|
2207
|
+
const internalOptions = {
|
|
2208
|
+
...options,
|
|
2209
|
+
output: options.output,
|
|
2210
|
+
};
|
|
2211
|
+
const admitted = (await this._executionOperation(buildPayload(internalOptions, executionId), internalOptions, true));
|
|
2212
|
+
const descriptor = {
|
|
2213
|
+
pid: admitted.pid,
|
|
2214
|
+
state: "running",
|
|
2215
|
+
language,
|
|
2216
|
+
startedAtMs: admitted.createdAtMs,
|
|
2217
|
+
};
|
|
2218
|
+
this._languageProcesses.set(admitted.pid, {
|
|
2219
|
+
executionId: admitted.executionId,
|
|
2220
|
+
descriptor,
|
|
2221
|
+
outputHandlers: new Set(),
|
|
2222
|
+
exitHandlers: new Set(),
|
|
2223
|
+
});
|
|
2224
|
+
this._languageProcessIds.set(admitted.executionId, admitted.pid);
|
|
2225
|
+
const reconcileCompletion = async () => {
|
|
2226
|
+
if (!this._languageProcesses.get(admitted.pid)?.exit) {
|
|
2227
|
+
await this._waitProcess(admitted.pid);
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
if (admitted.completed)
|
|
2231
|
+
await reconcileCompletion();
|
|
2232
|
+
else {
|
|
2233
|
+
void admitted.completion
|
|
2234
|
+
.then(reconcileCompletion)
|
|
2235
|
+
.catch((error) => console.error("[agentos] failed to reconcile spawned process completion", error));
|
|
2236
|
+
}
|
|
2237
|
+
if (options.signal) {
|
|
2238
|
+
const abort = () => {
|
|
2239
|
+
void this._killProcess(admitted.pid);
|
|
2240
|
+
};
|
|
2241
|
+
options.signal.addEventListener("abort", abort, { once: true });
|
|
2242
|
+
}
|
|
2243
|
+
return this._languageProcesses.get(admitted.pid)?.descriptor ?? descriptor;
|
|
2244
|
+
}
|
|
2245
|
+
async _exec(command, options = {}) {
|
|
2246
|
+
return (await this._executionOperation({
|
|
2247
|
+
type: "shell_execution",
|
|
2248
|
+
request: { process: processExecutionOptions(options), command },
|
|
2249
|
+
}, options));
|
|
2250
|
+
}
|
|
2251
|
+
async _execFile(command, args = [], options = {}) {
|
|
2252
|
+
const operationOptions = { ...options, args: [...args] };
|
|
2253
|
+
return (await this._executionOperation({
|
|
2254
|
+
type: "argv_execution",
|
|
2255
|
+
request: {
|
|
2256
|
+
process: processExecutionOptions(operationOptions),
|
|
2257
|
+
command,
|
|
2258
|
+
},
|
|
2259
|
+
}, operationOptions));
|
|
2260
|
+
}
|
|
2261
|
+
/** @deprecated Use `process.exec()` for lifecycle-aware execution. */
|
|
1748
2262
|
async exec(command, options) {
|
|
1749
2263
|
return this.#kernel.exec(command, options);
|
|
1750
2264
|
}
|
|
2265
|
+
/** @deprecated Use `process.execFile()` for lifecycle-aware execution. */
|
|
1751
2266
|
async execArgv(command, args = [], options) {
|
|
1752
2267
|
const kernel = this.#kernel;
|
|
1753
2268
|
return kernel.execArgv(command, args, options);
|
|
1754
2269
|
}
|
|
1755
|
-
|
|
2270
|
+
async _executeJavaScript(source, options = {}) {
|
|
2271
|
+
return (await this._executionOperation({
|
|
2272
|
+
type: "javascript_execution",
|
|
2273
|
+
request: {
|
|
2274
|
+
process: processExecutionOptions(options),
|
|
2275
|
+
source,
|
|
2276
|
+
format: options.format === "commonjs"
|
|
2277
|
+
? executionProtocol.JavaScriptModuleFormat.CommonJs
|
|
2278
|
+
: executionProtocol.JavaScriptModuleFormat.Module,
|
|
2279
|
+
filePath: options.filePath ?? null,
|
|
2280
|
+
inputs: options.inputs ? JSON.stringify(options.inputs) : null,
|
|
2281
|
+
},
|
|
2282
|
+
}, options));
|
|
2283
|
+
}
|
|
2284
|
+
async _evaluationOperation(payload, options) {
|
|
2285
|
+
const result = (await this._executionOperation(payload, options));
|
|
2286
|
+
if (result.outcome !== "succeeded")
|
|
2287
|
+
return result;
|
|
2288
|
+
const value = result.evaluationValue;
|
|
2289
|
+
if (value === undefined) {
|
|
2290
|
+
throw new Error("successful evaluation did not return a JSON-serializable value");
|
|
2291
|
+
}
|
|
2292
|
+
return { ...result, value: value };
|
|
2293
|
+
}
|
|
2294
|
+
async _evaluateJavaScript(expression, options = {}) {
|
|
2295
|
+
return this._evaluationOperation({
|
|
2296
|
+
type: "javascript_evaluation",
|
|
2297
|
+
request: {
|
|
2298
|
+
process: processExecutionOptions(options),
|
|
2299
|
+
expression,
|
|
2300
|
+
format: options.format === "commonjs"
|
|
2301
|
+
? executionProtocol.JavaScriptModuleFormat.CommonJs
|
|
2302
|
+
: executionProtocol.JavaScriptModuleFormat.Module,
|
|
2303
|
+
filePath: options.filePath ?? null,
|
|
2304
|
+
inputs: options.inputs ? JSON.stringify(options.inputs) : null,
|
|
2305
|
+
},
|
|
2306
|
+
}, options);
|
|
2307
|
+
}
|
|
2308
|
+
async _executeJavaScriptFile(path, options = {}) {
|
|
2309
|
+
return (await this._executionOperation({
|
|
2310
|
+
type: "javascript_file_execution",
|
|
2311
|
+
request: { process: processExecutionOptions(options), path },
|
|
2312
|
+
}, options));
|
|
2313
|
+
}
|
|
2314
|
+
_spawnJavaScript(source, options = {}) {
|
|
2315
|
+
return this._spawnLanguageOperation((operationOptions, executionId) => ({
|
|
2316
|
+
type: "javascript_execution",
|
|
2317
|
+
request: {
|
|
2318
|
+
process: processExecutionOptions(operationOptions, {
|
|
2319
|
+
background: true,
|
|
2320
|
+
executionId,
|
|
2321
|
+
}),
|
|
2322
|
+
source,
|
|
2323
|
+
format: executionProtocol.JavaScriptModuleFormat.Module,
|
|
2324
|
+
filePath: null,
|
|
2325
|
+
inputs: null,
|
|
2326
|
+
},
|
|
2327
|
+
}), options, "javascript");
|
|
2328
|
+
}
|
|
2329
|
+
_spawnJavaScriptFile(path, options = {}) {
|
|
2330
|
+
return this._spawnLanguageOperation((operationOptions, executionId) => ({
|
|
2331
|
+
type: "javascript_file_execution",
|
|
2332
|
+
request: {
|
|
2333
|
+
process: processExecutionOptions(operationOptions, {
|
|
2334
|
+
background: true,
|
|
2335
|
+
executionId,
|
|
2336
|
+
}),
|
|
2337
|
+
path,
|
|
2338
|
+
},
|
|
2339
|
+
}), options, "javascript");
|
|
2340
|
+
}
|
|
2341
|
+
async _executeTypeScript(source, options = {}) {
|
|
2342
|
+
return (await this._executionOperation({
|
|
2343
|
+
type: "typescript_execution",
|
|
2344
|
+
request: {
|
|
2345
|
+
process: processExecutionOptions(options),
|
|
2346
|
+
source,
|
|
2347
|
+
filePath: options.filePath ?? null,
|
|
2348
|
+
tsconfigPath: options.tsconfigPath ?? null,
|
|
2349
|
+
compilerOptions: options.compilerOptions
|
|
2350
|
+
? JSON.stringify(options.compilerOptions)
|
|
2351
|
+
: null,
|
|
2352
|
+
inputs: options.inputs ? JSON.stringify(options.inputs) : null,
|
|
2353
|
+
},
|
|
2354
|
+
}, options));
|
|
2355
|
+
}
|
|
2356
|
+
async _evaluateTypeScript(expression, options = {}) {
|
|
2357
|
+
return this._evaluationOperation({
|
|
2358
|
+
type: "typescript_evaluation",
|
|
2359
|
+
request: {
|
|
2360
|
+
process: processExecutionOptions(options),
|
|
2361
|
+
expression,
|
|
2362
|
+
filePath: options.filePath ?? null,
|
|
2363
|
+
tsconfigPath: options.tsconfigPath ?? null,
|
|
2364
|
+
compilerOptions: options.compilerOptions
|
|
2365
|
+
? JSON.stringify(options.compilerOptions)
|
|
2366
|
+
: null,
|
|
2367
|
+
inputs: options.inputs ? JSON.stringify(options.inputs) : null,
|
|
2368
|
+
},
|
|
2369
|
+
}, options);
|
|
2370
|
+
}
|
|
2371
|
+
async _executeTypeScriptFile(path, options = {}) {
|
|
2372
|
+
return (await this._executionOperation({
|
|
2373
|
+
type: "typescript_file_execution",
|
|
2374
|
+
request: {
|
|
2375
|
+
process: processExecutionOptions(options),
|
|
2376
|
+
path,
|
|
2377
|
+
tsconfigPath: options.tsconfigPath ?? null,
|
|
2378
|
+
compilerOptions: options.compilerOptions
|
|
2379
|
+
? JSON.stringify(options.compilerOptions)
|
|
2380
|
+
: null,
|
|
2381
|
+
},
|
|
2382
|
+
}, options));
|
|
2383
|
+
}
|
|
2384
|
+
_spawnTypeScript(source, options = {}) {
|
|
2385
|
+
return this._spawnLanguageOperation((operationOptions, executionId) => ({
|
|
2386
|
+
type: "typescript_execution",
|
|
2387
|
+
request: {
|
|
2388
|
+
process: processExecutionOptions(operationOptions, {
|
|
2389
|
+
background: true,
|
|
2390
|
+
executionId,
|
|
2391
|
+
}),
|
|
2392
|
+
source,
|
|
2393
|
+
filePath: null,
|
|
2394
|
+
tsconfigPath: null,
|
|
2395
|
+
compilerOptions: null,
|
|
2396
|
+
inputs: null,
|
|
2397
|
+
},
|
|
2398
|
+
}), options, "javascript");
|
|
2399
|
+
}
|
|
2400
|
+
_spawnTypeScriptFile(path, options = {}) {
|
|
2401
|
+
return this._spawnLanguageOperation((operationOptions, executionId) => ({
|
|
2402
|
+
type: "typescript_file_execution",
|
|
2403
|
+
request: {
|
|
2404
|
+
process: processExecutionOptions(operationOptions, {
|
|
2405
|
+
background: true,
|
|
2406
|
+
executionId,
|
|
2407
|
+
}),
|
|
2408
|
+
path,
|
|
2409
|
+
tsconfigPath: null,
|
|
2410
|
+
compilerOptions: null,
|
|
2411
|
+
},
|
|
2412
|
+
}), options, "javascript");
|
|
2413
|
+
}
|
|
2414
|
+
async _checkTypeScript(source, options = {}) {
|
|
2415
|
+
const result = (await this._executionOperation({
|
|
2416
|
+
type: "typescript_check",
|
|
2417
|
+
request: {
|
|
2418
|
+
identity: executionIdentity(options),
|
|
2419
|
+
output: executionOutput(options),
|
|
2420
|
+
source,
|
|
2421
|
+
cwd: options.cwd ?? null,
|
|
2422
|
+
filePath: options.filePath ?? null,
|
|
2423
|
+
tsconfigPath: options.tsconfigPath ?? null,
|
|
2424
|
+
compilerOptions: options.compilerOptions
|
|
2425
|
+
? JSON.stringify(options.compilerOptions)
|
|
2426
|
+
: null,
|
|
2427
|
+
timeoutMs: protocolTimeout(options.timeoutMs),
|
|
2428
|
+
},
|
|
2429
|
+
}, options));
|
|
2430
|
+
return mapTypeScriptCheckResult(result);
|
|
2431
|
+
}
|
|
2432
|
+
async _checkTypeScriptProject(options = {}) {
|
|
2433
|
+
const result = (await this._executionOperation({
|
|
2434
|
+
type: "typescript_project_check",
|
|
2435
|
+
request: {
|
|
2436
|
+
identity: executionIdentity(options),
|
|
2437
|
+
output: executionOutput(options),
|
|
2438
|
+
cwd: options.cwd ?? null,
|
|
2439
|
+
tsconfigPath: options.tsconfigPath ?? null,
|
|
2440
|
+
timeoutMs: protocolTimeout(options.timeoutMs),
|
|
2441
|
+
},
|
|
2442
|
+
}, options));
|
|
2443
|
+
return mapTypeScriptCheckResult(result);
|
|
2444
|
+
}
|
|
2445
|
+
async _installNpmPackages(packagesOrOptions = {}, maybeOptions = {}) {
|
|
2446
|
+
if (typeof packagesOrOptions === "string" ||
|
|
2447
|
+
Array.isArray(packagesOrOptions)) {
|
|
2448
|
+
const packages = Array.isArray(packagesOrOptions)
|
|
2449
|
+
? packagesOrOptions
|
|
2450
|
+
: [packagesOrOptions];
|
|
2451
|
+
return (await this._executionOperation({
|
|
2452
|
+
type: "npm_package_install",
|
|
2453
|
+
request: {
|
|
2454
|
+
identity: executionIdentity(maybeOptions),
|
|
2455
|
+
output: executionOutput(maybeOptions),
|
|
2456
|
+
cwd: maybeOptions.cwd ?? null,
|
|
2457
|
+
env: maybeOptions.env
|
|
2458
|
+
? new Map(Object.entries(maybeOptions.env))
|
|
2459
|
+
: null,
|
|
2460
|
+
timeoutMs: protocolTimeout(maybeOptions.timeoutMs),
|
|
2461
|
+
packages,
|
|
2462
|
+
dev: maybeOptions.dev ?? null,
|
|
2463
|
+
global: maybeOptions.global ?? null,
|
|
2464
|
+
},
|
|
2465
|
+
}, maybeOptions));
|
|
2466
|
+
}
|
|
2467
|
+
const options = packagesOrOptions;
|
|
2468
|
+
return (await this._executionOperation({
|
|
2469
|
+
type: "npm_project_install",
|
|
2470
|
+
request: {
|
|
2471
|
+
identity: executionIdentity(options),
|
|
2472
|
+
output: executionOutput(options),
|
|
2473
|
+
cwd: options.cwd ?? null,
|
|
2474
|
+
env: options.env ? new Map(Object.entries(options.env)) : null,
|
|
2475
|
+
timeoutMs: protocolTimeout(options.timeoutMs),
|
|
2476
|
+
frozen: options.frozen ?? null,
|
|
2477
|
+
},
|
|
2478
|
+
}, options));
|
|
2479
|
+
}
|
|
2480
|
+
async _executeNpmScript(script, options = {}) {
|
|
2481
|
+
return (await this._executionOperation({
|
|
2482
|
+
type: "npm_script_execution",
|
|
2483
|
+
request: { process: processExecutionOptions(options), script },
|
|
2484
|
+
}, options));
|
|
2485
|
+
}
|
|
2486
|
+
async _executeNpmPackage(packageSpec, options = {}) {
|
|
2487
|
+
return (await this._executionOperation({
|
|
2488
|
+
type: "npm_package_execution",
|
|
2489
|
+
request: {
|
|
2490
|
+
process: processExecutionOptions(options),
|
|
2491
|
+
packageSpec,
|
|
2492
|
+
binary: options.binary ?? null,
|
|
2493
|
+
},
|
|
2494
|
+
}, options));
|
|
2495
|
+
}
|
|
2496
|
+
async _executePython(source, options = {}) {
|
|
2497
|
+
return (await this._executionOperation({
|
|
2498
|
+
type: "python_execution",
|
|
2499
|
+
request: {
|
|
2500
|
+
process: processExecutionOptions(options),
|
|
2501
|
+
source,
|
|
2502
|
+
inputs: options.inputs ? JSON.stringify(options.inputs) : null,
|
|
2503
|
+
},
|
|
2504
|
+
}, options));
|
|
2505
|
+
}
|
|
2506
|
+
async _evaluatePython(expression, options = {}) {
|
|
2507
|
+
return this._evaluationOperation({
|
|
2508
|
+
type: "python_evaluation",
|
|
2509
|
+
request: {
|
|
2510
|
+
process: processExecutionOptions(options),
|
|
2511
|
+
expression,
|
|
2512
|
+
inputs: options.inputs ? JSON.stringify(options.inputs) : null,
|
|
2513
|
+
},
|
|
2514
|
+
}, options);
|
|
2515
|
+
}
|
|
2516
|
+
async _executePythonFile(path, options = {}) {
|
|
2517
|
+
return (await this._executionOperation({
|
|
2518
|
+
type: "python_file_execution",
|
|
2519
|
+
request: { process: processExecutionOptions(options), path },
|
|
2520
|
+
}, options));
|
|
2521
|
+
}
|
|
2522
|
+
async _executePythonModule(module, options = {}) {
|
|
2523
|
+
return (await this._executionOperation({
|
|
2524
|
+
type: "python_module_execution",
|
|
2525
|
+
request: { process: processExecutionOptions(options), module },
|
|
2526
|
+
}, options));
|
|
2527
|
+
}
|
|
2528
|
+
_spawnPython(source, options = {}) {
|
|
2529
|
+
return this._spawnLanguageOperation((operationOptions, executionId) => ({
|
|
2530
|
+
type: "python_execution",
|
|
2531
|
+
request: {
|
|
2532
|
+
process: processExecutionOptions(operationOptions, {
|
|
2533
|
+
background: true,
|
|
2534
|
+
executionId,
|
|
2535
|
+
}),
|
|
2536
|
+
source,
|
|
2537
|
+
inputs: null,
|
|
2538
|
+
},
|
|
2539
|
+
}), options, "python");
|
|
2540
|
+
}
|
|
2541
|
+
_spawnPythonFile(path, options = {}) {
|
|
2542
|
+
return this._spawnLanguageOperation((operationOptions, executionId) => ({
|
|
2543
|
+
type: "python_file_execution",
|
|
2544
|
+
request: {
|
|
2545
|
+
process: processExecutionOptions(operationOptions, {
|
|
2546
|
+
background: true,
|
|
2547
|
+
executionId,
|
|
2548
|
+
}),
|
|
2549
|
+
path,
|
|
2550
|
+
},
|
|
2551
|
+
}), options, "python");
|
|
2552
|
+
}
|
|
2553
|
+
_spawnPythonModule(module, options = {}) {
|
|
2554
|
+
return this._spawnLanguageOperation((operationOptions, executionId) => ({
|
|
2555
|
+
type: "python_module_execution",
|
|
2556
|
+
request: {
|
|
2557
|
+
process: processExecutionOptions(operationOptions, {
|
|
2558
|
+
background: true,
|
|
2559
|
+
executionId,
|
|
2560
|
+
}),
|
|
2561
|
+
module,
|
|
2562
|
+
},
|
|
2563
|
+
}), options, "python");
|
|
2564
|
+
}
|
|
2565
|
+
async _installPythonPackages(packagesOrOptions = {}, maybeOptions = {}) {
|
|
2566
|
+
const packages = typeof packagesOrOptions === "string"
|
|
2567
|
+
? [packagesOrOptions]
|
|
2568
|
+
: Array.isArray(packagesOrOptions)
|
|
2569
|
+
? packagesOrOptions
|
|
2570
|
+
: [];
|
|
2571
|
+
const options = typeof packagesOrOptions === "string" || Array.isArray(packagesOrOptions)
|
|
2572
|
+
? maybeOptions
|
|
2573
|
+
: packagesOrOptions;
|
|
2574
|
+
return (await this._executionOperation({
|
|
2575
|
+
type: "python_install",
|
|
2576
|
+
request: {
|
|
2577
|
+
identity: executionIdentity(options),
|
|
2578
|
+
output: executionOutput(options),
|
|
2579
|
+
cwd: options.cwd ?? null,
|
|
2580
|
+
env: options.env ? new Map(Object.entries(options.env)) : null,
|
|
2581
|
+
timeoutMs: protocolTimeout(options.timeoutMs),
|
|
2582
|
+
packages,
|
|
2583
|
+
upgrade: options.upgrade ?? null,
|
|
2584
|
+
requirementsFile: options.requirementsFile ?? null,
|
|
2585
|
+
indexUrl: options.indexUrl ?? null,
|
|
2586
|
+
extraIndexUrls: options.extraIndexUrls ?? [],
|
|
2587
|
+
},
|
|
2588
|
+
}, options));
|
|
2589
|
+
}
|
|
2590
|
+
async _getContext(contextId) {
|
|
2591
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "get_execution", request: { executionId: contextId } });
|
|
2592
|
+
if (response.type !== "execution_descriptor") {
|
|
2593
|
+
throw new Error(`unexpected getContext response: ${response.type}`);
|
|
2594
|
+
}
|
|
2595
|
+
return mapExecutionDescriptor(response.response.execution);
|
|
2596
|
+
}
|
|
2597
|
+
async _listContexts() {
|
|
2598
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "list_executions" });
|
|
2599
|
+
if (response.type !== "execution_list") {
|
|
2600
|
+
throw new Error(`unexpected listContexts response: ${response.type}`);
|
|
2601
|
+
}
|
|
2602
|
+
return response.response.executions.map(mapExecutionDescriptor);
|
|
2603
|
+
}
|
|
2604
|
+
async _waitExecutionResult(executionId) {
|
|
2605
|
+
let resolveCompletion;
|
|
2606
|
+
const completion = new Promise((resolve) => {
|
|
2607
|
+
resolveCompletion = resolve;
|
|
2608
|
+
});
|
|
2609
|
+
const unsubscribe = this._onExecutionCompleted(executionId, () => {
|
|
2610
|
+
resolveCompletion?.();
|
|
2611
|
+
});
|
|
2612
|
+
let response;
|
|
2613
|
+
try {
|
|
2614
|
+
try {
|
|
2615
|
+
response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "wait_execution", request: { executionId } });
|
|
2616
|
+
}
|
|
2617
|
+
catch (error) {
|
|
2618
|
+
if (!(error instanceof SidecarRejectedError) ||
|
|
2619
|
+
error.detail.code !== "execution_busy") {
|
|
2620
|
+
throw error;
|
|
2621
|
+
}
|
|
2622
|
+
await completion;
|
|
2623
|
+
response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "wait_execution", request: { executionId } });
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
finally {
|
|
2627
|
+
unsubscribe();
|
|
2628
|
+
}
|
|
2629
|
+
if (response.type !== "execution_completed") {
|
|
2630
|
+
throw new Error(`unexpected waitExecution response: ${response.type}`);
|
|
2631
|
+
}
|
|
2632
|
+
return mapExecutionResult(response.response);
|
|
2633
|
+
}
|
|
2634
|
+
async _cancelExecution(executionId) {
|
|
2635
|
+
await this._descriptorLifecycleRequest("cancel_execution", executionId);
|
|
2636
|
+
}
|
|
2637
|
+
async _signalExecution(executionId, signal) {
|
|
2638
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "signal_execution", request: { executionId, signal } });
|
|
2639
|
+
if (response.type !== "execution_descriptor") {
|
|
2640
|
+
throw new Error(`unexpected signalExecution response: ${response.type}`);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
async _resetContext(contextId) {
|
|
2644
|
+
await this._descriptorLifecycleRequest("reset_execution", contextId);
|
|
2645
|
+
}
|
|
2646
|
+
async _descriptorLifecycleRequest(type, executionId) {
|
|
2647
|
+
const payload = type === "cancel_execution"
|
|
2648
|
+
? { type, request: { executionId } }
|
|
2649
|
+
: { type, request: { executionId } };
|
|
2650
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, payload);
|
|
2651
|
+
if (response.type !== "execution_descriptor") {
|
|
2652
|
+
throw new Error(`unexpected ${type} response: ${response.type}`);
|
|
2653
|
+
}
|
|
2654
|
+
return mapExecutionDescriptor(response.response.execution);
|
|
2655
|
+
}
|
|
2656
|
+
async _deleteContext(contextId) {
|
|
2657
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "delete_execution", request: { executionId: contextId } });
|
|
2658
|
+
if (response.type !== "execution_deleted") {
|
|
2659
|
+
throw new Error(`unexpected deleteContext response: ${response.type}`);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
async _writeExecutionStdin(executionId, data) {
|
|
2663
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, {
|
|
2664
|
+
type: "write_execution_stdin",
|
|
2665
|
+
request: { executionId, chunk: executionBytes(data) },
|
|
2666
|
+
});
|
|
2667
|
+
if (response.type !== "execution_io") {
|
|
2668
|
+
throw new Error(`unexpected writeExecutionStdin response: ${response.type}`);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
async _closeExecutionStdin(executionId) {
|
|
2672
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "close_execution_stdin", request: { executionId } });
|
|
2673
|
+
if (response.type !== "execution_io") {
|
|
2674
|
+
throw new Error(`unexpected closeExecutionStdin response: ${response.type}`);
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
async _resizeExecutionPty(executionId, size) {
|
|
2678
|
+
const cols = protocolUnsigned(size.cols, 0xffff, "size.cols");
|
|
2679
|
+
const rows = protocolUnsigned(size.rows, 0xffff, "size.rows");
|
|
2680
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, {
|
|
2681
|
+
type: "resize_execution_pty",
|
|
2682
|
+
request: { executionId, cols, rows },
|
|
2683
|
+
});
|
|
2684
|
+
if (response.type !== "execution_io") {
|
|
2685
|
+
throw new Error(`unexpected resizeExecutionPty response: ${response.type}`);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
async _readExecutionOutput(executionId, options = {}) {
|
|
2689
|
+
const limit = options.limit === undefined
|
|
2690
|
+
? null
|
|
2691
|
+
: protocolUnsigned(options.limit, 0xffff_ffff, "limit");
|
|
2692
|
+
const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, {
|
|
2693
|
+
type: "read_execution_output",
|
|
2694
|
+
request: {
|
|
2695
|
+
executionId,
|
|
2696
|
+
cursor: options.cursor ?? null,
|
|
2697
|
+
limit,
|
|
2698
|
+
},
|
|
2699
|
+
});
|
|
2700
|
+
if (response.type !== "execution_output_page") {
|
|
2701
|
+
throw new Error(`unexpected readExecutionOutput response: ${response.type}`);
|
|
2702
|
+
}
|
|
2703
|
+
return {
|
|
2704
|
+
events: response.response.events.map(mapExecutionOutputEvent),
|
|
2705
|
+
nextCursor: response.response.nextCursor,
|
|
2706
|
+
hasMore: response.response.hasMore,
|
|
2707
|
+
truncated: response.response.truncated,
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
_onExecutionOutput(executionId, handler) {
|
|
2711
|
+
const handlers = this._executionOutputHandlers.get(executionId) ?? new Set();
|
|
2712
|
+
handlers.add(handler);
|
|
2713
|
+
this._executionOutputHandlers.set(executionId, handlers);
|
|
2714
|
+
return () => {
|
|
2715
|
+
handlers.delete(handler);
|
|
2716
|
+
if (handlers.size === 0)
|
|
2717
|
+
this._executionOutputHandlers.delete(executionId);
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
_onExecutionCompleted(executionId, handler) {
|
|
2721
|
+
const handlers = this._executionCompletedHandlers.get(executionId) ?? new Set();
|
|
2722
|
+
handlers.add(handler);
|
|
2723
|
+
this._executionCompletedHandlers.set(executionId, handlers);
|
|
2724
|
+
return () => {
|
|
2725
|
+
handlers.delete(handler);
|
|
2726
|
+
if (handlers.size === 0)
|
|
2727
|
+
this._executionCompletedHandlers.delete(executionId);
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
_trackProcess(proc, command, args, retainEvents, outputHandlers, exitHandlers) {
|
|
1756
2731
|
const entry = {
|
|
1757
2732
|
proc,
|
|
1758
2733
|
command,
|
|
1759
2734
|
args,
|
|
2735
|
+
startedAtMs: Date.now(),
|
|
2736
|
+
retainEvents,
|
|
2737
|
+
events: [],
|
|
2738
|
+
nextSequence: 0,
|
|
2739
|
+
signal: undefined,
|
|
2740
|
+
exit: undefined,
|
|
1760
2741
|
outputHandlers,
|
|
1761
2742
|
exitHandlers,
|
|
1762
2743
|
};
|
|
@@ -1767,38 +2748,84 @@ export class AgentOs {
|
|
|
1767
2748
|
// `_processes` is a process table for this VM's lifetime; it is freed wholesale
|
|
1768
2749
|
// in dispose(). (H5: the leak was that dispose() never cleared it.)
|
|
1769
2750
|
void proc.wait().then((code) => {
|
|
2751
|
+
const exit = {
|
|
2752
|
+
pid: proc.pid,
|
|
2753
|
+
outcome: entry.signal ? "signalled" : "exited",
|
|
2754
|
+
exitCode: code,
|
|
2755
|
+
...(entry.signal ? { signal: entry.signal } : {}),
|
|
2756
|
+
};
|
|
2757
|
+
entry.exit = exit;
|
|
1770
2758
|
for (const h of exitHandlers)
|
|
1771
|
-
h(
|
|
2759
|
+
h(exit);
|
|
1772
2760
|
});
|
|
1773
|
-
return {
|
|
2761
|
+
return {
|
|
2762
|
+
pid: proc.pid,
|
|
2763
|
+
state: "running",
|
|
2764
|
+
command,
|
|
2765
|
+
startedAtMs: entry.startedAtMs,
|
|
2766
|
+
};
|
|
1774
2767
|
}
|
|
1775
|
-
|
|
2768
|
+
async _spawnProcess(command, args = [], options = {}) {
|
|
1776
2769
|
const outputHandlers = new Set();
|
|
1777
2770
|
const exitHandlers = new Set();
|
|
2771
|
+
const recordOutput = (channel, data) => {
|
|
2772
|
+
const entry = this._processes.get(proc.pid);
|
|
2773
|
+
if (!entry?.retainEvents)
|
|
2774
|
+
return;
|
|
2775
|
+
if (entry.events.length >= PROCESS_OUTPUT_EVENT_LIMIT) {
|
|
2776
|
+
entry.events.shift();
|
|
2777
|
+
}
|
|
2778
|
+
entry.events.push({
|
|
2779
|
+
pid: proc.pid,
|
|
2780
|
+
sequence: entry.nextSequence++,
|
|
2781
|
+
channel,
|
|
2782
|
+
chunk: data,
|
|
2783
|
+
timestampMs: Date.now(),
|
|
2784
|
+
});
|
|
2785
|
+
};
|
|
1778
2786
|
const proc = this.#kernel.spawn(command, args, {
|
|
1779
|
-
|
|
2787
|
+
cwd: options.cwd,
|
|
2788
|
+
env: options.env,
|
|
2789
|
+
stdin: options.stdin,
|
|
2790
|
+
timeout: options.timeoutMs,
|
|
2791
|
+
streamStdin: true,
|
|
1780
2792
|
onStdout: (data) => {
|
|
2793
|
+
recordOutput("stdout", data);
|
|
2794
|
+
options?.onStdout?.(data);
|
|
1781
2795
|
for (const h of outputHandlers) {
|
|
1782
2796
|
h({ pid: proc.pid, stream: "stdout", data });
|
|
1783
2797
|
}
|
|
1784
2798
|
},
|
|
1785
2799
|
onStderr: (data) => {
|
|
2800
|
+
recordOutput("stderr", data);
|
|
2801
|
+
options?.onStderr?.(data);
|
|
1786
2802
|
for (const h of outputHandlers) {
|
|
1787
2803
|
h({ pid: proc.pid, stream: "stderr", data });
|
|
1788
2804
|
}
|
|
1789
2805
|
},
|
|
1790
2806
|
});
|
|
1791
|
-
return this._trackProcess(proc, command, args, outputHandlers, exitHandlers);
|
|
2807
|
+
return this._trackProcess(proc, command, args, options.output?.retainEvents ?? false, outputHandlers, exitHandlers);
|
|
2808
|
+
}
|
|
2809
|
+
spawn(command, args = [], options) {
|
|
2810
|
+
return this.process.spawn(command, args, options);
|
|
1792
2811
|
}
|
|
1793
2812
|
/** Write data to a process's stdin. */
|
|
1794
|
-
|
|
2813
|
+
_writeProcessStdin(pid, data) {
|
|
2814
|
+
const language = this._languageProcesses.get(pid);
|
|
2815
|
+
if (language) {
|
|
2816
|
+
return this._writeExecutionStdin(language.executionId, data);
|
|
2817
|
+
}
|
|
1795
2818
|
const entry = this._processes.get(pid);
|
|
1796
2819
|
if (!entry)
|
|
1797
2820
|
throw new Error(`Process not found: ${pid}`);
|
|
1798
2821
|
return entry.proc.writeStdin(data);
|
|
1799
2822
|
}
|
|
1800
2823
|
/** Close a process's stdin stream. */
|
|
1801
|
-
|
|
2824
|
+
_closeProcessStdin(pid) {
|
|
2825
|
+
const language = this._languageProcesses.get(pid);
|
|
2826
|
+
if (language) {
|
|
2827
|
+
return this._closeExecutionStdin(language.executionId);
|
|
2828
|
+
}
|
|
1802
2829
|
const entry = this._processes.get(pid);
|
|
1803
2830
|
if (!entry)
|
|
1804
2831
|
throw new Error(`Process not found: ${pid}`);
|
|
@@ -1806,7 +2833,7 @@ export class AgentOs {
|
|
|
1806
2833
|
}
|
|
1807
2834
|
/** Subscribe to stdout and stderr from a process. */
|
|
1808
2835
|
onProcessOutput(pid, handler) {
|
|
1809
|
-
const entry = this._processes.get(pid);
|
|
2836
|
+
const entry = this._processes.get(pid) ?? this._languageProcesses.get(pid);
|
|
1810
2837
|
if (!entry)
|
|
1811
2838
|
throw new Error(`Process not found: ${pid}`);
|
|
1812
2839
|
entry.outputHandlers.add(handler);
|
|
@@ -1816,12 +2843,12 @@ export class AgentOs {
|
|
|
1816
2843
|
}
|
|
1817
2844
|
/** Subscribe to process exit. Returns an unsubscribe function. */
|
|
1818
2845
|
onProcessExit(pid, handler) {
|
|
1819
|
-
const entry = this._processes.get(pid);
|
|
2846
|
+
const entry = this._processes.get(pid) ?? this._languageProcesses.get(pid);
|
|
1820
2847
|
if (!entry)
|
|
1821
2848
|
throw new Error(`Process not found: ${pid}`);
|
|
1822
2849
|
// If already exited, call immediately.
|
|
1823
|
-
if (entry
|
|
1824
|
-
handler(
|
|
2850
|
+
if ("exit" in entry && entry.exit) {
|
|
2851
|
+
handler(entry.exit);
|
|
1825
2852
|
return () => { };
|
|
1826
2853
|
}
|
|
1827
2854
|
entry.exitHandlers.add(handler);
|
|
@@ -1830,11 +2857,39 @@ export class AgentOs {
|
|
|
1830
2857
|
};
|
|
1831
2858
|
}
|
|
1832
2859
|
/** Wait for a process to exit. Returns the exit code. */
|
|
1833
|
-
|
|
2860
|
+
async _waitProcess(pid) {
|
|
2861
|
+
const language = this._languageProcesses.get(pid);
|
|
2862
|
+
if (language) {
|
|
2863
|
+
if (language.exit)
|
|
2864
|
+
return language.exit;
|
|
2865
|
+
const result = await this._waitExecutionResult(language.executionId);
|
|
2866
|
+
const exit = {
|
|
2867
|
+
pid,
|
|
2868
|
+
outcome: result.outcome === "timed_out"
|
|
2869
|
+
? "timed_out"
|
|
2870
|
+
: result.outcome === "cancelled"
|
|
2871
|
+
? "signalled"
|
|
2872
|
+
: "exited",
|
|
2873
|
+
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
2874
|
+
...(language.signal ? { signal: language.signal } : {}),
|
|
2875
|
+
};
|
|
2876
|
+
language.exit = exit;
|
|
2877
|
+
language.descriptor = { ...language.descriptor, state: "exited" };
|
|
2878
|
+
for (const handler of language.exitHandlers)
|
|
2879
|
+
handler(exit);
|
|
2880
|
+
return exit;
|
|
2881
|
+
}
|
|
1834
2882
|
const entry = this._processes.get(pid);
|
|
1835
2883
|
if (!entry)
|
|
1836
2884
|
throw new Error(`Process not found: ${pid}`);
|
|
1837
|
-
|
|
2885
|
+
if (entry.exit)
|
|
2886
|
+
return entry.exit;
|
|
2887
|
+
const exitCode = await entry.proc.wait();
|
|
2888
|
+
return (entry.exit ?? {
|
|
2889
|
+
pid,
|
|
2890
|
+
outcome: "exited",
|
|
2891
|
+
exitCode,
|
|
2892
|
+
});
|
|
1838
2893
|
}
|
|
1839
2894
|
_assertSafeAbsolutePath(path) {
|
|
1840
2895
|
if (!path.startsWith("/")) {
|
|
@@ -1853,15 +2908,15 @@ export class AgentOs {
|
|
|
1853
2908
|
_vfs() {
|
|
1854
2909
|
return this.#kernel.vfs;
|
|
1855
2910
|
}
|
|
1856
|
-
async
|
|
2911
|
+
async _readFile(path) {
|
|
1857
2912
|
this._assertSafeAbsolutePath(path);
|
|
1858
2913
|
return this.#kernel.readFile(path);
|
|
1859
2914
|
}
|
|
1860
|
-
async
|
|
2915
|
+
async _writeFile(path, content) {
|
|
1861
2916
|
this._assertWritableAbsolutePath(path);
|
|
1862
2917
|
return this.#kernel.writeFile(path, content);
|
|
1863
2918
|
}
|
|
1864
|
-
async
|
|
2919
|
+
async _writeFiles(entries) {
|
|
1865
2920
|
const results = [];
|
|
1866
2921
|
for (const entry of entries) {
|
|
1867
2922
|
try {
|
|
@@ -1884,7 +2939,7 @@ export class AgentOs {
|
|
|
1884
2939
|
}
|
|
1885
2940
|
return results;
|
|
1886
2941
|
}
|
|
1887
|
-
async
|
|
2942
|
+
async _readFiles(paths) {
|
|
1888
2943
|
const results = [];
|
|
1889
2944
|
for (const path of paths) {
|
|
1890
2945
|
try {
|
|
@@ -1913,18 +2968,18 @@ export class AgentOs {
|
|
|
1913
2968
|
// exists() loop makes `mkdir -p` cost O(components * tree).
|
|
1914
2969
|
await this.#kernel.mkdir(path);
|
|
1915
2970
|
}
|
|
1916
|
-
async
|
|
2971
|
+
async _mkdir(path, options) {
|
|
1917
2972
|
if (options?.recursive) {
|
|
1918
2973
|
return this._mkdirp(path);
|
|
1919
2974
|
}
|
|
1920
2975
|
this._assertSafeAbsolutePath(path);
|
|
1921
2976
|
return this.#kernel.mkdir(path);
|
|
1922
2977
|
}
|
|
1923
|
-
async
|
|
2978
|
+
async _readdir(path) {
|
|
1924
2979
|
this._assertSafeAbsolutePath(path);
|
|
1925
2980
|
return this.#kernel.readdir(path);
|
|
1926
2981
|
}
|
|
1927
|
-
async
|
|
2982
|
+
async _readdirEntries(path) {
|
|
1928
2983
|
this._assertSafeAbsolutePath(path);
|
|
1929
2984
|
const entries = await this.#kernel.readdirRecursive(path, { maxDepth: 0 });
|
|
1930
2985
|
return entries.map((entry) => ({
|
|
@@ -1933,7 +2988,7 @@ export class AgentOs {
|
|
|
1933
2988
|
isSymbolicLink: entry.isSymbolicLink,
|
|
1934
2989
|
}));
|
|
1935
2990
|
}
|
|
1936
|
-
async
|
|
2991
|
+
async _readdirRecursive(path, options) {
|
|
1937
2992
|
this._assertSafeAbsolutePath(path);
|
|
1938
2993
|
const exclude = options?.exclude ? new Set(options.exclude) : undefined;
|
|
1939
2994
|
const entries = await this.#kernel.readdirRecursive(path, {
|
|
@@ -1963,15 +3018,15 @@ export class AgentOs {
|
|
|
1963
3018
|
}
|
|
1964
3019
|
return results;
|
|
1965
3020
|
}
|
|
1966
|
-
async
|
|
3021
|
+
async _stat(path) {
|
|
1967
3022
|
this._assertSafeAbsolutePath(path);
|
|
1968
3023
|
return this.#kernel.stat(path);
|
|
1969
3024
|
}
|
|
1970
|
-
async
|
|
3025
|
+
async _exists(path) {
|
|
1971
3026
|
this._assertSafeAbsolutePath(path);
|
|
1972
3027
|
return this.#kernel.exists(path);
|
|
1973
3028
|
}
|
|
1974
|
-
async
|
|
3029
|
+
async _exportRootFilesystem(options) {
|
|
1975
3030
|
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes <= 0) {
|
|
1976
3031
|
throw new RangeError("maxBytes must be a positive safe integer");
|
|
1977
3032
|
}
|
|
@@ -1991,7 +3046,7 @@ export class AgentOs {
|
|
|
1991
3046
|
* after the returned promise settles; a delivery failure rejects instead of
|
|
1992
3047
|
* leaving the mount silently host-only.
|
|
1993
3048
|
*/
|
|
1994
|
-
async
|
|
3049
|
+
async _mountFs(descriptor) {
|
|
1995
3050
|
this._assertSafeAbsolutePath(descriptor.path);
|
|
1996
3051
|
if (!(this.#kernel instanceof NativeSidecarKernelProxy)) {
|
|
1997
3052
|
throw new Error("portable dynamic mounts require the native sidecar");
|
|
@@ -2005,30 +3060,94 @@ export class AgentOs {
|
|
|
2005
3060
|
},
|
|
2006
3061
|
});
|
|
2007
3062
|
}
|
|
2008
|
-
async
|
|
3063
|
+
async _unmountFs(path) {
|
|
2009
3064
|
this._assertSafeAbsolutePath(path);
|
|
2010
3065
|
if (!(this.#kernel instanceof NativeSidecarKernelProxy)) {
|
|
2011
3066
|
throw new Error("portable dynamic mounts require the native sidecar");
|
|
2012
3067
|
}
|
|
2013
3068
|
await this.#kernel.unmountDescriptor(path);
|
|
2014
3069
|
}
|
|
2015
|
-
async
|
|
3070
|
+
async _listMounts() {
|
|
2016
3071
|
if (!(this.#kernel instanceof NativeSidecarKernelProxy))
|
|
2017
3072
|
return [];
|
|
2018
3073
|
return this.#kernel.listMounts();
|
|
2019
3074
|
}
|
|
2020
|
-
async
|
|
3075
|
+
async _move(from, to) {
|
|
2021
3076
|
this._assertWritableAbsolutePath(from);
|
|
2022
3077
|
this._assertWritableAbsolutePath(to);
|
|
2023
3078
|
await this.#kernel.movePath(from, to);
|
|
2024
3079
|
}
|
|
2025
|
-
async
|
|
3080
|
+
async _remove(path, options) {
|
|
2026
3081
|
this._assertWritableAbsolutePath(path);
|
|
2027
3082
|
await this.#kernel.removePath(path, {
|
|
2028
3083
|
recursive: options?.recursive ?? false,
|
|
2029
3084
|
});
|
|
2030
3085
|
}
|
|
2031
|
-
|
|
3086
|
+
/** @deprecated Use `filesystem.readFile()`. */
|
|
3087
|
+
readFile(path) {
|
|
3088
|
+
return this.filesystem.readFile(path);
|
|
3089
|
+
}
|
|
3090
|
+
/** @deprecated Use `filesystem.writeFile()`. */
|
|
3091
|
+
writeFile(path, content) {
|
|
3092
|
+
return this.filesystem.writeFile(path, content);
|
|
3093
|
+
}
|
|
3094
|
+
/** @deprecated Use `filesystem.writeFiles()`. */
|
|
3095
|
+
writeFiles(entries) {
|
|
3096
|
+
return this.filesystem.writeFiles(entries);
|
|
3097
|
+
}
|
|
3098
|
+
/** @deprecated Use `filesystem.readFiles()`. */
|
|
3099
|
+
readFiles(paths) {
|
|
3100
|
+
return this.filesystem.readFiles(paths);
|
|
3101
|
+
}
|
|
3102
|
+
/** @deprecated Use `filesystem.mkdir()`. */
|
|
3103
|
+
mkdir(path, options) {
|
|
3104
|
+
return this.filesystem.mkdir(path, options);
|
|
3105
|
+
}
|
|
3106
|
+
/** @deprecated Use `filesystem.readdir()`. */
|
|
3107
|
+
readdir(path) {
|
|
3108
|
+
return this.filesystem.readdir(path);
|
|
3109
|
+
}
|
|
3110
|
+
/** @deprecated Use `filesystem.readdirEntries()`. */
|
|
3111
|
+
readdirEntries(path) {
|
|
3112
|
+
return this.filesystem.readdirEntries(path);
|
|
3113
|
+
}
|
|
3114
|
+
/** @deprecated Use `filesystem.readdirRecursive()`. */
|
|
3115
|
+
readdirRecursive(path, options) {
|
|
3116
|
+
return this.filesystem.readdirRecursive(path, options);
|
|
3117
|
+
}
|
|
3118
|
+
/** @deprecated Use `filesystem.stat()`. */
|
|
3119
|
+
stat(path) {
|
|
3120
|
+
return this.filesystem.stat(path);
|
|
3121
|
+
}
|
|
3122
|
+
/** @deprecated Use `filesystem.exists()`. */
|
|
3123
|
+
exists(path) {
|
|
3124
|
+
return this.filesystem.exists(path);
|
|
3125
|
+
}
|
|
3126
|
+
/** @deprecated Use `filesystem.export()`. */
|
|
3127
|
+
exportRootFilesystem(options) {
|
|
3128
|
+
return this.filesystem.export(options);
|
|
3129
|
+
}
|
|
3130
|
+
/** @deprecated Use `filesystem.mount()`. */
|
|
3131
|
+
mountFs(descriptor) {
|
|
3132
|
+
return this.filesystem.mount(descriptor);
|
|
3133
|
+
}
|
|
3134
|
+
/** @deprecated Use `filesystem.unmount()`. */
|
|
3135
|
+
unmountFs(path) {
|
|
3136
|
+
return this.filesystem.unmount(path);
|
|
3137
|
+
}
|
|
3138
|
+
/** @deprecated Use `filesystem.listMounts()`. */
|
|
3139
|
+
listMounts() {
|
|
3140
|
+
return this.filesystem.listMounts();
|
|
3141
|
+
}
|
|
3142
|
+
/** @deprecated Use `filesystem.move()`. */
|
|
3143
|
+
move(from, to) {
|
|
3144
|
+
return this.filesystem.move(from, to);
|
|
3145
|
+
}
|
|
3146
|
+
/** @deprecated Use `filesystem.remove()`. */
|
|
3147
|
+
remove(path, options) {
|
|
3148
|
+
return this.filesystem.remove(path, options);
|
|
3149
|
+
}
|
|
3150
|
+
async _httpRequest(request) {
|
|
2032
3151
|
this._assertSafeAbsolutePath(request.path.split("?")[0] || "/");
|
|
2033
3152
|
const method = request.method?.toUpperCase() ?? "GET";
|
|
2034
3153
|
const body = typeof request.body === "string"
|
|
@@ -2056,7 +3175,79 @@ export class AgentOs {
|
|
|
2056
3175
|
body: new Uint8Array(Buffer.from(responsePayload.body ?? "", "base64")),
|
|
2057
3176
|
};
|
|
2058
3177
|
}
|
|
2059
|
-
|
|
3178
|
+
/** @deprecated Use `network.httpRequest()`. */
|
|
3179
|
+
httpRequest(request) {
|
|
3180
|
+
return this.network.httpRequest(request);
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Fetch an HTTP endpoint inside the VM while preserving duplicate response
|
|
3184
|
+
* headers and arbitrary binary bodies. agentOS Apps uses this compatibility
|
|
3185
|
+
* surface for its direct actor API.
|
|
3186
|
+
*/
|
|
3187
|
+
async fetch(port, request) {
|
|
3188
|
+
const url = new URL(request.url);
|
|
3189
|
+
const responsePayload = JSON.parse(await this._sidecarClient.vmFetch(this._sidecarSession, this._sidecarVm, {
|
|
3190
|
+
port,
|
|
3191
|
+
method: request.method,
|
|
3192
|
+
path: `${url.pathname}${url.search}`,
|
|
3193
|
+
headersJson: JSON.stringify(Object.fromEntries(request.headers.entries())),
|
|
3194
|
+
...(request.method !== "GET" && request.method !== "HEAD"
|
|
3195
|
+
? {
|
|
3196
|
+
bodyBase64: Buffer.from(await request.arrayBuffer()).toString("base64"),
|
|
3197
|
+
}
|
|
3198
|
+
: {}),
|
|
3199
|
+
}));
|
|
3200
|
+
const headers = new Headers();
|
|
3201
|
+
for (const [key, value] of responsePayload.headers ?? []) {
|
|
3202
|
+
headers.append(key, value);
|
|
3203
|
+
}
|
|
3204
|
+
return new Response(Buffer.from(responsePayload.body ?? "", "base64"), {
|
|
3205
|
+
status: responsePayload.status,
|
|
3206
|
+
statusText: responsePayload.statusText ?? "",
|
|
3207
|
+
headers,
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
async fetchStreamStart(port, request) {
|
|
3211
|
+
const url = new URL(request.url);
|
|
3212
|
+
return JSON.parse(await this._sidecarClient.vmFetch(this._sidecarSession, this._sidecarVm, {
|
|
3213
|
+
port,
|
|
3214
|
+
method: request.method,
|
|
3215
|
+
path: `${url.pathname}${url.search}`,
|
|
3216
|
+
headersJson: JSON.stringify(Object.fromEntries(request.headers.entries())),
|
|
3217
|
+
...(request.method !== "GET" && request.method !== "HEAD"
|
|
3218
|
+
? {
|
|
3219
|
+
bodyBase64: Buffer.from(await request.arrayBuffer()).toString("base64"),
|
|
3220
|
+
}
|
|
3221
|
+
: {}),
|
|
3222
|
+
streamOperation: "start",
|
|
3223
|
+
}));
|
|
3224
|
+
}
|
|
3225
|
+
async fetchStreamRead(streamId, maxBytes = 64 * 1024) {
|
|
3226
|
+
const response = JSON.parse(await this._sidecarClient.vmFetch(this._sidecarSession, this._sidecarVm, {
|
|
3227
|
+
port: 0,
|
|
3228
|
+
method: "GET",
|
|
3229
|
+
path: "/",
|
|
3230
|
+
headersJson: "{}",
|
|
3231
|
+
streamOperation: "read",
|
|
3232
|
+
streamId,
|
|
3233
|
+
maxBytes,
|
|
3234
|
+
}));
|
|
3235
|
+
return {
|
|
3236
|
+
body: new Uint8Array(Buffer.from(response.body, "base64")),
|
|
3237
|
+
done: response.done,
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
async fetchStreamCancel(streamId) {
|
|
3241
|
+
await this._sidecarClient.vmFetch(this._sidecarSession, this._sidecarVm, {
|
|
3242
|
+
port: 0,
|
|
3243
|
+
method: "GET",
|
|
3244
|
+
path: "/",
|
|
3245
|
+
headersJson: "{}",
|
|
3246
|
+
streamOperation: "cancel",
|
|
3247
|
+
streamId,
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
_openTerminal(options) {
|
|
2060
3251
|
const shellId = `shell-${++this._shellCounter}`;
|
|
2061
3252
|
this._closedShellIds.delete(shellId);
|
|
2062
3253
|
const dataHandlers = new Set();
|
|
@@ -2107,7 +3298,7 @@ export class AgentOs {
|
|
|
2107
3298
|
return this.#kernel.connectTerminal(options);
|
|
2108
3299
|
}
|
|
2109
3300
|
/** Write data to a shell's PTY input. */
|
|
2110
|
-
|
|
3301
|
+
_writeTerminal(shellId, data) {
|
|
2111
3302
|
const entry = this._shells.get(shellId);
|
|
2112
3303
|
if (!entry)
|
|
2113
3304
|
throw new Error(`Shell not found: ${shellId}`);
|
|
@@ -2150,7 +3341,7 @@ export class AgentOs {
|
|
|
2150
3341
|
return () => entry.exitHandlers.delete(handler);
|
|
2151
3342
|
}
|
|
2152
3343
|
/** Notify a shell of terminal resize. */
|
|
2153
|
-
|
|
3344
|
+
_resizeTerminal(shellId, cols, rows) {
|
|
2154
3345
|
const entry = this._shells.get(shellId);
|
|
2155
3346
|
if (!entry)
|
|
2156
3347
|
throw new Error(`Shell not found: ${shellId}`);
|
|
@@ -2161,7 +3352,7 @@ export class AgentOs {
|
|
|
2161
3352
|
* immediately for a shell that has already exited (within the closed-shell
|
|
2162
3353
|
* retention window).
|
|
2163
3354
|
*/
|
|
2164
|
-
|
|
3355
|
+
_waitTerminal(shellId) {
|
|
2165
3356
|
const entry = this._shells.get(shellId);
|
|
2166
3357
|
if (!entry) {
|
|
2167
3358
|
const exitCode = this._closedShellIds.get(shellId);
|
|
@@ -2172,7 +3363,7 @@ export class AgentOs {
|
|
|
2172
3363
|
return entry.exitPromise;
|
|
2173
3364
|
}
|
|
2174
3365
|
/** Kill a shell process and remove it from tracking. */
|
|
2175
|
-
|
|
3366
|
+
_closeTerminal(shellId) {
|
|
2176
3367
|
const entry = this._shells.get(shellId);
|
|
2177
3368
|
if (!entry) {
|
|
2178
3369
|
if (this._closedShellIds.has(shellId)) {
|
|
@@ -2184,6 +3375,26 @@ export class AgentOs {
|
|
|
2184
3375
|
this._shells.delete(shellId);
|
|
2185
3376
|
this._closedShellIds.add(shellId);
|
|
2186
3377
|
}
|
|
3378
|
+
/** @deprecated Use `terminal.open()`. */
|
|
3379
|
+
openShell(options) {
|
|
3380
|
+
return this.terminal.open(options);
|
|
3381
|
+
}
|
|
3382
|
+
/** @deprecated Use `terminal.write()`. */
|
|
3383
|
+
writeShell(shellId, data) {
|
|
3384
|
+
return this.terminal.write(shellId, data);
|
|
3385
|
+
}
|
|
3386
|
+
/** @deprecated Use `terminal.resize()`. */
|
|
3387
|
+
resizeShell(shellId, cols, rows) {
|
|
3388
|
+
this.terminal.resize(shellId, cols, rows);
|
|
3389
|
+
}
|
|
3390
|
+
/** @deprecated Use `terminal.wait()`. */
|
|
3391
|
+
waitShell(shellId) {
|
|
3392
|
+
return this.terminal.wait(shellId);
|
|
3393
|
+
}
|
|
3394
|
+
/** @deprecated Use `terminal.close()`. */
|
|
3395
|
+
closeShell(shellId) {
|
|
3396
|
+
this.terminal.close(shellId);
|
|
3397
|
+
}
|
|
2187
3398
|
_resolveVmPathToHostPath(vmPath) {
|
|
2188
3399
|
const normalizedVmPath = posixPath.normalize(vmPath);
|
|
2189
3400
|
for (const mount of this._hostMounts) {
|
|
@@ -2199,34 +3410,51 @@ export class AgentOs {
|
|
|
2199
3410
|
return null;
|
|
2200
3411
|
}
|
|
2201
3412
|
/** Returns info about all processes spawned via spawn(). */
|
|
2202
|
-
|
|
2203
|
-
return [
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
3413
|
+
async _listProcesses() {
|
|
3414
|
+
return [
|
|
3415
|
+
...[...this._processes.values()].map(({ proc, command, startedAtMs }) => ({
|
|
3416
|
+
pid: proc.pid,
|
|
3417
|
+
command,
|
|
3418
|
+
state: proc.exitCode === null ? "running" : "exited",
|
|
3419
|
+
startedAtMs,
|
|
3420
|
+
})),
|
|
3421
|
+
...[...this._languageProcesses.values()].map((entry) => entry.descriptor),
|
|
3422
|
+
];
|
|
2210
3423
|
}
|
|
2211
3424
|
/** Returns all kernel processes across all active runtimes (WASM and Node). */
|
|
2212
|
-
|
|
3425
|
+
_listAllProcesses() {
|
|
2213
3426
|
if (this.#kernel instanceof NativeSidecarKernelProxy) {
|
|
2214
3427
|
return this.#kernel.snapshotProcesses();
|
|
2215
3428
|
}
|
|
2216
3429
|
return [...this.#kernel.processes.values()];
|
|
2217
3430
|
}
|
|
2218
3431
|
/** Returns processes organized as a tree using ppid relationships. */
|
|
2219
|
-
|
|
2220
|
-
const all = this.
|
|
3432
|
+
async _processTree() {
|
|
3433
|
+
const all = this._listAllProcesses();
|
|
2221
3434
|
const nodeMap = new Map();
|
|
2222
3435
|
// Index: create a tree node for each process
|
|
2223
3436
|
for (const proc of all) {
|
|
2224
|
-
nodeMap.set(proc.pid, {
|
|
3437
|
+
nodeMap.set(proc.pid, {
|
|
3438
|
+
pid: proc.pid,
|
|
3439
|
+
ppid: proc.ppid,
|
|
3440
|
+
command: proc.command,
|
|
3441
|
+
state: proc.status,
|
|
3442
|
+
startedAtMs: proc.startTime,
|
|
3443
|
+
children: [],
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
for (const process of this._languageProcesses.values()) {
|
|
3447
|
+
if (!nodeMap.has(process.descriptor.pid)) {
|
|
3448
|
+
nodeMap.set(process.descriptor.pid, {
|
|
3449
|
+
...process.descriptor,
|
|
3450
|
+
children: [],
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
2225
3453
|
}
|
|
2226
3454
|
// Wire: attach each node to its parent
|
|
2227
3455
|
const roots = [];
|
|
2228
3456
|
for (const node of nodeMap.values()) {
|
|
2229
|
-
const parent = nodeMap.get(node.ppid);
|
|
3457
|
+
const parent = node.ppid === undefined ? undefined : nodeMap.get(node.ppid);
|
|
2230
3458
|
if (parent) {
|
|
2231
3459
|
parent.children.push(node);
|
|
2232
3460
|
}
|
|
@@ -2237,7 +3465,10 @@ export class AgentOs {
|
|
|
2237
3465
|
return roots;
|
|
2238
3466
|
}
|
|
2239
3467
|
/** Returns info about a specific process by PID. Throws if not found. */
|
|
2240
|
-
|
|
3468
|
+
async _getProcess(pid) {
|
|
3469
|
+
const language = this._languageProcesses.get(pid);
|
|
3470
|
+
if (language)
|
|
3471
|
+
return language.descriptor;
|
|
2241
3472
|
const entry = this._processes.get(pid);
|
|
2242
3473
|
if (!entry) {
|
|
2243
3474
|
throw new Error(`Process not found: ${pid}`);
|
|
@@ -2245,32 +3476,76 @@ export class AgentOs {
|
|
|
2245
3476
|
return {
|
|
2246
3477
|
pid: entry.proc.pid,
|
|
2247
3478
|
command: entry.command,
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
exitCode: entry.proc.exitCode,
|
|
3479
|
+
state: entry.proc.exitCode === null ? "running" : "exited",
|
|
3480
|
+
startedAtMs: entry.startedAtMs,
|
|
2251
3481
|
};
|
|
2252
3482
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
3483
|
+
async _signalProcess(pid, signal) {
|
|
3484
|
+
const language = this._languageProcesses.get(pid);
|
|
3485
|
+
if (language) {
|
|
3486
|
+
language.signal = signal;
|
|
3487
|
+
await this._signalExecution(language.executionId, signal);
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
2255
3490
|
const entry = this._processes.get(pid);
|
|
2256
3491
|
if (!entry) {
|
|
2257
3492
|
throw new Error(`Process not found: ${pid}`);
|
|
2258
3493
|
}
|
|
2259
3494
|
if (entry.proc.exitCode !== null)
|
|
2260
3495
|
return;
|
|
2261
|
-
entry.
|
|
3496
|
+
entry.signal = signal;
|
|
3497
|
+
const number = signal === "SIGKILL" ? 9 : signal === "SIGINT" ? 2 : 15;
|
|
3498
|
+
entry.proc.kill(number);
|
|
2262
3499
|
}
|
|
2263
3500
|
/** Send SIGKILL to force-kill a process. No-op if already exited. */
|
|
2264
|
-
|
|
3501
|
+
async _killProcess(pid) {
|
|
3502
|
+
await this._signalProcess(pid, "SIGKILL");
|
|
3503
|
+
}
|
|
3504
|
+
async _resizeProcessPty(pid, size) {
|
|
3505
|
+
const language = this._languageProcesses.get(pid);
|
|
3506
|
+
if (!language) {
|
|
3507
|
+
throw new Error(`Process ${pid} does not have a managed PTY`);
|
|
3508
|
+
}
|
|
3509
|
+
await this._resizeExecutionPty(language.executionId, size);
|
|
3510
|
+
}
|
|
3511
|
+
async _readProcessOutput(pid, options = {}) {
|
|
3512
|
+
const language = this._languageProcesses.get(pid);
|
|
3513
|
+
if (language) {
|
|
3514
|
+
const page = await this._readExecutionOutput(language.executionId, {
|
|
3515
|
+
cursor: options.after === undefined ? undefined : `1:${options.after + 1}`,
|
|
3516
|
+
});
|
|
3517
|
+
return {
|
|
3518
|
+
pid,
|
|
3519
|
+
events: page.events.map((event) => ({
|
|
3520
|
+
pid,
|
|
3521
|
+
sequence: event.sequence,
|
|
3522
|
+
channel: event.channel,
|
|
3523
|
+
chunk: event.chunk,
|
|
3524
|
+
timestampMs: event.timestampMs,
|
|
3525
|
+
})),
|
|
3526
|
+
nextCursor: page.nextCursor,
|
|
3527
|
+
hasMore: page.hasMore,
|
|
3528
|
+
truncated: page.truncated,
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
2265
3531
|
const entry = this._processes.get(pid);
|
|
2266
|
-
if (!entry)
|
|
3532
|
+
if (!entry)
|
|
2267
3533
|
throw new Error(`Process not found: ${pid}`);
|
|
3534
|
+
if (!entry.retainEvents) {
|
|
3535
|
+
throw new Error(`Process ${pid} was not spawned with output.retainEvents enabled`);
|
|
2268
3536
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
3537
|
+
const after = options.after ?? -1;
|
|
3538
|
+
const events = entry.events.filter((event) => event.sequence > after);
|
|
3539
|
+
return {
|
|
3540
|
+
pid,
|
|
3541
|
+
events,
|
|
3542
|
+
nextCursor: String(events.at(-1)?.sequence ?? after),
|
|
3543
|
+
hasMore: false,
|
|
3544
|
+
truncated: entry.events.length === PROCESS_OUTPUT_EVENT_LIMIT &&
|
|
3545
|
+
after < (entry.events[0]?.sequence ?? 0) - 1,
|
|
3546
|
+
};
|
|
2272
3547
|
}
|
|
2273
|
-
async
|
|
3548
|
+
async _openSession(input) {
|
|
2274
3549
|
const response = await this._sendAcpRequest({
|
|
2275
3550
|
tag: "AcpOpenSessionRequest",
|
|
2276
3551
|
val: {
|
|
@@ -2293,7 +3568,7 @@ export class AgentOs {
|
|
|
2293
3568
|
throw new Error(`unexpected openSession response: ${response.tag}`);
|
|
2294
3569
|
}
|
|
2295
3570
|
}
|
|
2296
|
-
async
|
|
3571
|
+
async _getSession(input) {
|
|
2297
3572
|
const response = await this._sendAcpRequest({
|
|
2298
3573
|
tag: "AcpGetDurableSessionRequest",
|
|
2299
3574
|
val: { sessionId: input?.sessionId ?? null },
|
|
@@ -2303,7 +3578,7 @@ export class AgentOs {
|
|
|
2303
3578
|
}
|
|
2304
3579
|
return decodeDurableSessionInfo(response.val.session);
|
|
2305
3580
|
}
|
|
2306
|
-
async
|
|
3581
|
+
async _listSessions(input) {
|
|
2307
3582
|
const response = await this._sendAcpRequest({
|
|
2308
3583
|
tag: "AcpListDurableSessionsRequest",
|
|
2309
3584
|
val: { cursor: input?.cursor ?? null, limit: input?.limit ?? null },
|
|
@@ -2316,7 +3591,7 @@ export class AgentOs {
|
|
|
2316
3591
|
nextCursor: response.val.nextCursor,
|
|
2317
3592
|
};
|
|
2318
3593
|
}
|
|
2319
|
-
async
|
|
3594
|
+
async _deleteSession(input = {}) {
|
|
2320
3595
|
const response = await this._sendAcpRequest({
|
|
2321
3596
|
tag: "AcpDeleteSessionRequest",
|
|
2322
3597
|
val: { sessionId: input.sessionId ?? null },
|
|
@@ -2325,7 +3600,7 @@ export class AgentOs {
|
|
|
2325
3600
|
throw new Error(`unexpected deleteSession response: ${response.tag}`);
|
|
2326
3601
|
}
|
|
2327
3602
|
}
|
|
2328
|
-
async
|
|
3603
|
+
async _unloadSession(input) {
|
|
2329
3604
|
const response = await this._sendAcpRequest({
|
|
2330
3605
|
tag: "AcpUnloadSessionRequest",
|
|
2331
3606
|
val: { sessionId: input?.sessionId ?? null },
|
|
@@ -2334,7 +3609,7 @@ export class AgentOs {
|
|
|
2334
3609
|
throw new Error(`unexpected unloadSession response: ${response.tag}`);
|
|
2335
3610
|
}
|
|
2336
3611
|
}
|
|
2337
|
-
async
|
|
3612
|
+
async _prompt(input) {
|
|
2338
3613
|
const response = await this._sendAcpRequest({
|
|
2339
3614
|
tag: "AcpPromptRequest",
|
|
2340
3615
|
val: {
|
|
@@ -2352,7 +3627,7 @@ export class AgentOs {
|
|
|
2352
3627
|
stopReason: response.val.stopReason,
|
|
2353
3628
|
};
|
|
2354
3629
|
}
|
|
2355
|
-
async
|
|
3630
|
+
async _cancelPrompt(input) {
|
|
2356
3631
|
const response = await this._sendAcpRequest({
|
|
2357
3632
|
tag: "AcpCancelPromptRequest",
|
|
2358
3633
|
val: { sessionId: input?.sessionId ?? null },
|
|
@@ -2366,7 +3641,7 @@ export class AgentOs {
|
|
|
2366
3641
|
}
|
|
2367
3642
|
return { status: response.val.status };
|
|
2368
3643
|
}
|
|
2369
|
-
async
|
|
3644
|
+
async _respondPermission(input) {
|
|
2370
3645
|
const response = await this._sendAcpRequest({
|
|
2371
3646
|
tag: "AcpRespondPermissionRequest",
|
|
2372
3647
|
val: {
|
|
@@ -2393,7 +3668,7 @@ export class AgentOs {
|
|
|
2393
3668
|
reason: permissionTerminalReason(response.val.reason),
|
|
2394
3669
|
};
|
|
2395
3670
|
}
|
|
2396
|
-
async
|
|
3671
|
+
async _readHistory(input) {
|
|
2397
3672
|
const response = await this._sendAcpRequest({
|
|
2398
3673
|
tag: "AcpReadHistoryRequest",
|
|
2399
3674
|
val: {
|
|
@@ -2412,7 +3687,7 @@ export class AgentOs {
|
|
|
2412
3687
|
hasMoreAfter: response.val.hasMoreAfter,
|
|
2413
3688
|
};
|
|
2414
3689
|
}
|
|
2415
|
-
async
|
|
3690
|
+
async _getSessionConfig(input) {
|
|
2416
3691
|
const response = await this._sendAcpRequest({
|
|
2417
3692
|
tag: "AcpGetSessionConfigRequest",
|
|
2418
3693
|
val: { sessionId: input?.sessionId ?? null },
|
|
@@ -2425,7 +3700,7 @@ export class AgentOs {
|
|
|
2425
3700
|
options: JSON.parse(response.val.options),
|
|
2426
3701
|
};
|
|
2427
3702
|
}
|
|
2428
|
-
async
|
|
3703
|
+
async _setSessionConfigOption(input) {
|
|
2429
3704
|
const response = await this._sendAcpRequest({
|
|
2430
3705
|
tag: "AcpSetSessionConfigOptionRequest",
|
|
2431
3706
|
val: {
|
|
@@ -2442,7 +3717,7 @@ export class AgentOs {
|
|
|
2442
3717
|
options: JSON.parse(response.val.options),
|
|
2443
3718
|
};
|
|
2444
3719
|
}
|
|
2445
|
-
async
|
|
3720
|
+
async _getSessionCapabilities(input) {
|
|
2446
3721
|
const response = await this._sendAcpRequest({
|
|
2447
3722
|
tag: "AcpGetSessionCapabilitiesRequest",
|
|
2448
3723
|
val: { sessionId: input?.sessionId ?? null },
|
|
@@ -2454,7 +3729,7 @@ export class AgentOs {
|
|
|
2454
3729
|
? null
|
|
2455
3730
|
: normalizeSessionCapabilities(JSON.parse(response.val.capabilities));
|
|
2456
3731
|
}
|
|
2457
|
-
async
|
|
3732
|
+
async _getSessionAgentInfo(input) {
|
|
2458
3733
|
const response = await this._sendAcpRequest({
|
|
2459
3734
|
tag: "AcpGetSessionAgentInfoRequest",
|
|
2460
3735
|
val: { sessionId: input?.sessionId ?? null },
|
|
@@ -2466,6 +3741,58 @@ export class AgentOs {
|
|
|
2466
3741
|
? null
|
|
2467
3742
|
: JSON.parse(response.val.agentInfo);
|
|
2468
3743
|
}
|
|
3744
|
+
/** @deprecated Use `sessions.open()`. */
|
|
3745
|
+
openSession(input) {
|
|
3746
|
+
return this.sessions.open(input);
|
|
3747
|
+
}
|
|
3748
|
+
/** @deprecated Use `sessions.get()`. */
|
|
3749
|
+
getSession(input) {
|
|
3750
|
+
return this.sessions.get(input);
|
|
3751
|
+
}
|
|
3752
|
+
/** @deprecated Use `sessions.list()`. */
|
|
3753
|
+
listSessions(input) {
|
|
3754
|
+
return this.sessions.list(input);
|
|
3755
|
+
}
|
|
3756
|
+
/** @deprecated Use `sessions.delete()`. */
|
|
3757
|
+
deleteSession(input = {}) {
|
|
3758
|
+
return this.sessions.delete(input);
|
|
3759
|
+
}
|
|
3760
|
+
/** @deprecated Use `sessions.unload()`. */
|
|
3761
|
+
unloadSession(input) {
|
|
3762
|
+
return this.sessions.unload(input);
|
|
3763
|
+
}
|
|
3764
|
+
/** @deprecated Use `sessions.prompt()`. */
|
|
3765
|
+
prompt(input) {
|
|
3766
|
+
return this.sessions.prompt(input);
|
|
3767
|
+
}
|
|
3768
|
+
/** @deprecated Use `sessions.cancelPrompt()`. */
|
|
3769
|
+
cancelPrompt(input) {
|
|
3770
|
+
return this.sessions.cancelPrompt(input);
|
|
3771
|
+
}
|
|
3772
|
+
/** @deprecated Use `sessions.respondPermission()`. */
|
|
3773
|
+
respondPermission(input) {
|
|
3774
|
+
return this.sessions.respondPermission(input);
|
|
3775
|
+
}
|
|
3776
|
+
/** @deprecated Use `sessions.readHistory()`. */
|
|
3777
|
+
readHistory(input) {
|
|
3778
|
+
return this.sessions.readHistory(input);
|
|
3779
|
+
}
|
|
3780
|
+
/** @deprecated Use `sessions.getConfig()`. */
|
|
3781
|
+
getSessionConfig(input) {
|
|
3782
|
+
return this.sessions.getConfig(input);
|
|
3783
|
+
}
|
|
3784
|
+
/** @deprecated Use `sessions.setConfigOption()`. */
|
|
3785
|
+
setSessionConfigOption(input) {
|
|
3786
|
+
return this.sessions.setConfigOption(input);
|
|
3787
|
+
}
|
|
3788
|
+
/** @deprecated Use `sessions.getCapabilities()`. */
|
|
3789
|
+
getSessionCapabilities(input) {
|
|
3790
|
+
return this.sessions.getCapabilities(input);
|
|
3791
|
+
}
|
|
3792
|
+
/** @deprecated Use `sessions.getAgentInfo()`. */
|
|
3793
|
+
getSessionAgentInfo(input) {
|
|
3794
|
+
return this.sessions.getAgentInfo(input);
|
|
3795
|
+
}
|
|
2469
3796
|
/**
|
|
2470
3797
|
* Dynamically link a software package into the RUNNING VM. The package's
|
|
2471
3798
|
* `bin/` commands appear under `/opt/agentos/bin` (on `$PATH`) and its `share/man`
|
|
@@ -2474,7 +3801,7 @@ export class AgentOs {
|
|
|
2474
3801
|
* block registers the package for `openSession({ agent: name })`. Persists for the VM's
|
|
2475
3802
|
* lifetime (and across a snapshot iff the volume persists).
|
|
2476
3803
|
*/
|
|
2477
|
-
async
|
|
3804
|
+
async _linkSoftware(descriptor) {
|
|
2478
3805
|
// Forward to the sidecar, which owns the `/opt/agentos` projection and
|
|
2479
3806
|
// appends the package to its live host-backed staging dir; the commands
|
|
2480
3807
|
// appear under `/opt/agentos/bin` immediately. The sidecar rejects a
|
|
@@ -2494,7 +3821,7 @@ export class AgentOs {
|
|
|
2494
3821
|
// picked up by the sidecar (it owns the projected `/opt/agentos` and answers
|
|
2495
3822
|
// openSession/listAgents from it). Nothing to record client-side.
|
|
2496
3823
|
}
|
|
2497
|
-
async
|
|
3824
|
+
async _listSoftware() {
|
|
2498
3825
|
return this._sidecarClient.providedCommands(this._sidecarSession, this._sidecarVm);
|
|
2499
3826
|
}
|
|
2500
3827
|
/**
|
|
@@ -2503,7 +3830,7 @@ export class AgentOs {
|
|
|
2503
3830
|
* projected `/opt/agentos` packages (the client parses no manifests). Every such
|
|
2504
3831
|
* agent is a package materialized into the VM, so `installed` is always `true`.
|
|
2505
3832
|
*/
|
|
2506
|
-
async
|
|
3833
|
+
async _listAgents() {
|
|
2507
3834
|
const response = await this._sendAcpRequest({
|
|
2508
3835
|
tag: "AcpListAgentsRequest",
|
|
2509
3836
|
val: { reserved: false },
|
|
@@ -2516,6 +3843,18 @@ export class AgentOs {
|
|
|
2516
3843
|
installed: agent.installed,
|
|
2517
3844
|
}));
|
|
2518
3845
|
}
|
|
3846
|
+
/** @deprecated Use `software.link()`. */
|
|
3847
|
+
linkSoftware(descriptor) {
|
|
3848
|
+
return this.software.link(descriptor);
|
|
3849
|
+
}
|
|
3850
|
+
/** @deprecated Use `software.list()`. */
|
|
3851
|
+
listSoftware() {
|
|
3852
|
+
return this.software.list();
|
|
3853
|
+
}
|
|
3854
|
+
/** @deprecated Use `agents.list()`. */
|
|
3855
|
+
listAgents() {
|
|
3856
|
+
return this.agents.list();
|
|
3857
|
+
}
|
|
2519
3858
|
_recordAgentStderr(event) {
|
|
2520
3859
|
if (!event.sessionId) {
|
|
2521
3860
|
return;
|
|
@@ -2569,6 +3908,81 @@ export class AgentOs {
|
|
|
2569
3908
|
}
|
|
2570
3909
|
}
|
|
2571
3910
|
_handleSidecarEvent(event) {
|
|
3911
|
+
if (event.payload.type === "execution_output") {
|
|
3912
|
+
const output = mapExecutionOutputEvent(event.payload.event);
|
|
3913
|
+
const pid = this._languageProcessIds.get(output.executionId);
|
|
3914
|
+
if (pid !== undefined) {
|
|
3915
|
+
const process = this._languageProcesses.get(pid);
|
|
3916
|
+
if (process) {
|
|
3917
|
+
const publicOutput = {
|
|
3918
|
+
pid,
|
|
3919
|
+
stream: output.channel === "stderr" ? "stderr" : "stdout",
|
|
3920
|
+
data: output.chunk,
|
|
3921
|
+
};
|
|
3922
|
+
for (const handler of process.outputHandlers) {
|
|
3923
|
+
try {
|
|
3924
|
+
handler(publicOutput);
|
|
3925
|
+
}
|
|
3926
|
+
catch (error) {
|
|
3927
|
+
console.error("AgentOS process output handler failed", error);
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
for (const key of ["*", output.executionId]) {
|
|
3933
|
+
for (const handler of this._executionOutputHandlers.get(key) ?? []) {
|
|
3934
|
+
try {
|
|
3935
|
+
handler(output);
|
|
3936
|
+
}
|
|
3937
|
+
catch (error) {
|
|
3938
|
+
console.error("AgentOS execution output handler failed", error);
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
if (event.payload.type === "execution_completed") {
|
|
3945
|
+
const completed = mapExecutionCompletedEvent(event.payload.event);
|
|
3946
|
+
const pid = this._languageProcessIds.get(completed.executionId);
|
|
3947
|
+
if (pid !== undefined) {
|
|
3948
|
+
const process = this._languageProcesses.get(pid);
|
|
3949
|
+
if (process && !process.exit) {
|
|
3950
|
+
const exit = {
|
|
3951
|
+
pid,
|
|
3952
|
+
outcome: completed.outcome === "timed_out"
|
|
3953
|
+
? "timed_out"
|
|
3954
|
+
: completed.outcome === "cancelled"
|
|
3955
|
+
? "signalled"
|
|
3956
|
+
: "exited",
|
|
3957
|
+
...(completed.exitCode !== undefined
|
|
3958
|
+
? { exitCode: completed.exitCode }
|
|
3959
|
+
: {}),
|
|
3960
|
+
...(process.signal ? { signal: process.signal } : {}),
|
|
3961
|
+
};
|
|
3962
|
+
process.exit = exit;
|
|
3963
|
+
process.descriptor = { ...process.descriptor, state: "exited" };
|
|
3964
|
+
for (const handler of process.exitHandlers) {
|
|
3965
|
+
try {
|
|
3966
|
+
handler(exit);
|
|
3967
|
+
}
|
|
3968
|
+
catch (error) {
|
|
3969
|
+
console.error("AgentOS process exit handler failed", error);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
for (const key of ["*", completed.executionId]) {
|
|
3975
|
+
for (const handler of this._executionCompletedHandlers.get(key) ?? []) {
|
|
3976
|
+
try {
|
|
3977
|
+
handler(completed);
|
|
3978
|
+
}
|
|
3979
|
+
catch (error) {
|
|
3980
|
+
console.error("AgentOS execution completion handler failed", error);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
2572
3986
|
if (event.payload.type === "ext") {
|
|
2573
3987
|
this._handleAcpExtEvent(event.payload.envelope);
|
|
2574
3988
|
return;
|
|
@@ -2970,12 +4384,12 @@ export class AgentOs {
|
|
|
2970
4384
|
this._acpTerminals.set(terminalId, terminal);
|
|
2971
4385
|
return { terminalId };
|
|
2972
4386
|
}
|
|
2973
|
-
_handleAcpWriteTerminal(params) {
|
|
4387
|
+
async _handleAcpWriteTerminal(params) {
|
|
2974
4388
|
const method = "terminal/write";
|
|
2975
4389
|
const terminal = this._requireAcpTerminal(params, method);
|
|
2976
4390
|
const data = this._requireAcpStringParam(params, "data", method);
|
|
2977
4391
|
const encoding = this._optionalAcpStringParam(params, "encoding", method);
|
|
2978
|
-
terminal.handle.write(encoding === "base64" ? Buffer.from(data, "base64") : data);
|
|
4392
|
+
await terminal.handle.write(encoding === "base64" ? Buffer.from(data, "base64") : data);
|
|
2979
4393
|
return null;
|
|
2980
4394
|
}
|
|
2981
4395
|
_handleAcpReadTerminal(params) {
|
|
@@ -3065,17 +4479,29 @@ export class AgentOs {
|
|
|
3065
4479
|
}
|
|
3066
4480
|
// ── Cron ────────────────────────────────────────────────────
|
|
3067
4481
|
/** Schedule a cron job. Returns a handle with the job ID and a cancel method. */
|
|
3068
|
-
|
|
4482
|
+
_scheduleCron(options) {
|
|
3069
4483
|
return this._cronManager.schedule(options);
|
|
3070
4484
|
}
|
|
3071
4485
|
/** List all registered cron jobs. */
|
|
3072
|
-
|
|
4486
|
+
_listCronJobs() {
|
|
3073
4487
|
return this._cronManager.list();
|
|
3074
4488
|
}
|
|
3075
4489
|
/** Cancel a cron job by ID. */
|
|
3076
|
-
|
|
4490
|
+
_cancelCronJob(id) {
|
|
3077
4491
|
this._cronManager.cancel(id);
|
|
3078
4492
|
}
|
|
4493
|
+
/** @deprecated Use `cron.schedule()`. */
|
|
4494
|
+
scheduleCron(options) {
|
|
4495
|
+
return this.cron.schedule(options);
|
|
4496
|
+
}
|
|
4497
|
+
/** @deprecated Use `cron.list()`. */
|
|
4498
|
+
listCronJobs() {
|
|
4499
|
+
return this.cron.list();
|
|
4500
|
+
}
|
|
4501
|
+
/** @deprecated Use `cron.cancel()`. */
|
|
4502
|
+
cancelCronJob(id) {
|
|
4503
|
+
this.cron.cancel(id);
|
|
4504
|
+
}
|
|
3079
4505
|
/** Subscribe to cron lifecycle events (fire, complete, error). */
|
|
3080
4506
|
onCronEvent(handler) {
|
|
3081
4507
|
this._cronManager.onEvent(handler);
|
|
@@ -3094,14 +4520,30 @@ export class AgentOs {
|
|
|
3094
4520
|
}
|
|
3095
4521
|
this._acpTerminals.clear();
|
|
3096
4522
|
this._processes.clear();
|
|
4523
|
+
this._executionOutputHandlers.clear();
|
|
4524
|
+
this._executionCompletedHandlers.clear();
|
|
3097
4525
|
await waitForTrackedExitPromises([...shellExitPromises, ...terminalExitPromises], SHELL_DISPOSE_TIMEOUT_MS);
|
|
3098
4526
|
this._disposeSidecarEventListener();
|
|
3099
4527
|
const sidecarLease = this._sidecarLease;
|
|
3100
4528
|
this._sidecarLease = null;
|
|
3101
|
-
|
|
3102
|
-
|
|
4529
|
+
const disposeVm = sidecarLease
|
|
4530
|
+
? sidecarLease.dispose()
|
|
4531
|
+
: this.#kernel.dispose();
|
|
4532
|
+
const hooks = this._disposeHooks.splice(0);
|
|
4533
|
+
const errors = [];
|
|
4534
|
+
try {
|
|
4535
|
+
await disposeVm;
|
|
4536
|
+
}
|
|
4537
|
+
catch (error) {
|
|
4538
|
+
errors.push(error);
|
|
4539
|
+
}
|
|
4540
|
+
const hookResults = await Promise.allSettled(hooks.map((hook) => hook()));
|
|
4541
|
+
errors.push(...hookResults.flatMap((result) => result.status === "rejected" ? [result.reason] : []));
|
|
4542
|
+
if (errors.length === 1)
|
|
4543
|
+
throw errors[0];
|
|
4544
|
+
if (errors.length > 1) {
|
|
4545
|
+
throw new AggregateError(errors, "AgentOS VM disposal failed");
|
|
3103
4546
|
}
|
|
3104
|
-
return this.#kernel.dispose();
|
|
3105
4547
|
}
|
|
3106
4548
|
}
|
|
3107
4549
|
const agentOsRuntimeAdmins = new WeakMap();
|