@rivet-dev/agentos-core 0.2.14 → 0.2.16-rc.1

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/dist/agent-os.js CHANGED
@@ -3,6 +3,8 @@ 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";
@@ -12,6 +14,7 @@ import { findCargoBinary, resolveCargoBinary } from "./sidecar/cargo.js";
12
14
  const ACP_PROTOCOL_VERSION = 1;
13
15
  const ACP_EXTENSION_NAMESPACE = "dev.rivet.agent-os.acp";
14
16
  const SHELL_DISPOSE_TIMEOUT_MS = 5_000;
17
+ const PROCESS_OUTPUT_EVENT_LIMIT = 1_024;
15
18
  function safeWireU64(value) {
16
19
  const result = Number(value);
17
20
  if (!Number.isSafeInteger(result) || result < 0) {
@@ -1428,12 +1431,212 @@ async function registerBindingsOnSidecar(client, session, vm, bindings) {
1428
1431
  }
1429
1432
  return buildBindingReference(bindings);
1430
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
+ }
1431
1630
  export class AgentOs {
1432
1631
  #kernel;
1433
1632
  sidecar;
1434
1633
  _durableSessionEventHandlers = new Map();
1435
1634
  _agentExitHandlers = new Map();
1436
1635
  _processes = new Map();
1636
+ _languageProcesses = new Map();
1637
+ _languageProcessIds = new Map();
1638
+ _executionOutputHandlers = new Map();
1639
+ _executionCompletedHandlers = new Map();
1437
1640
  _shells = new Map();
1438
1641
  // Value is the recorded exit code (undefined until/unless the exit
1439
1642
  // resolves) so waitShell can still report it after the entry is dropped.
@@ -1459,6 +1662,119 @@ export class AgentOs {
1459
1662
  _agentExitHandler;
1460
1663
  _limitWarningHandler;
1461
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
+ };
1462
1778
  constructor(kernel, sidecar, softwareRoots, hostMounts, env, rootFilesystem, sidecarClient, sidecarSession, sidecarVm, agentStderrHandler, agentExitHandler, limitWarningHandler) {
1463
1779
  this.#kernel = kernel;
1464
1780
  this.sidecar = sidecar;
@@ -1761,18 +2077,673 @@ export class AgentOs {
1761
2077
  throw error;
1762
2078
  }
1763
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
+ let rejectAbort;
2160
+ const aborted = !background && options.signal
2161
+ ? new Promise((_resolve, reject) => {
2162
+ rejectAbort = reject;
2163
+ })
2164
+ : undefined;
2165
+ const abort = () => {
2166
+ void this._cancelExecution(response.response.operationId).catch((error) => {
2167
+ console.error("[agentos] failed to cancel aborted execution", error);
2168
+ });
2169
+ rejectAbort?.(options.signal?.reason ?? new DOMException("Aborted", "AbortError"));
2170
+ };
2171
+ options.signal?.addEventListener("abort", abort, { once: true });
2172
+ if (options.signal?.aborted)
2173
+ abort();
2174
+ const cleanup = () => {
2175
+ unsubscribeOutput();
2176
+ unsubscribeCompleted();
2177
+ options.signal?.removeEventListener("abort", abort);
2178
+ };
2179
+ if (background) {
2180
+ if (!descriptor || descriptor.pid === null) {
2181
+ cleanup();
2182
+ throw new Error("spawned execution admission returned no process id");
2183
+ }
2184
+ if (completed)
2185
+ cleanup();
2186
+ else {
2187
+ const unsubscribeBackgroundCompletion = this._onExecutionCompleted(descriptor.executionId, () => {
2188
+ unsubscribeBackgroundCompletion();
2189
+ cleanup();
2190
+ });
2191
+ }
2192
+ return {
2193
+ executionId: descriptor.executionId,
2194
+ pid: descriptor.pid,
2195
+ completed,
2196
+ completion,
2197
+ createdAtMs: safeProtocolNumber(descriptor.createdAtMs, "process startedAtMs"),
2198
+ };
2199
+ }
2200
+ try {
2201
+ await (aborted ? Promise.race([completion, aborted]) : completion);
2202
+ return await this._waitExecutionResult(response.response.operationId);
2203
+ }
2204
+ finally {
2205
+ cleanup();
2206
+ }
2207
+ }
2208
+ async _spawnLanguageOperation(buildPayload, options, language) {
2209
+ if ("contextId" in options) {
2210
+ throw new Error("contextId is not supported for spawned processes; contexts are for attached operations");
2211
+ }
2212
+ const executionId = `process-${randomUUID()}`;
2213
+ const internalOptions = {
2214
+ ...options,
2215
+ output: options.output,
2216
+ };
2217
+ const admitted = (await this._executionOperation(buildPayload(internalOptions, executionId), internalOptions, true));
2218
+ const descriptor = {
2219
+ pid: admitted.pid,
2220
+ state: "running",
2221
+ language,
2222
+ startedAtMs: admitted.createdAtMs,
2223
+ };
2224
+ this._languageProcesses.set(admitted.pid, {
2225
+ executionId: admitted.executionId,
2226
+ descriptor,
2227
+ outputHandlers: new Set(),
2228
+ exitHandlers: new Set(),
2229
+ });
2230
+ this._languageProcessIds.set(admitted.executionId, admitted.pid);
2231
+ const reconcileCompletion = async () => {
2232
+ if (!this._languageProcesses.get(admitted.pid)?.exit) {
2233
+ await this._waitProcess(admitted.pid);
2234
+ }
2235
+ };
2236
+ if (admitted.completed)
2237
+ await reconcileCompletion();
2238
+ else {
2239
+ void admitted.completion
2240
+ .then(reconcileCompletion)
2241
+ .catch((error) => console.error("[agentos] failed to reconcile spawned process completion", error));
2242
+ }
2243
+ if (options.signal) {
2244
+ const abort = () => {
2245
+ void this._killProcess(admitted.pid);
2246
+ };
2247
+ options.signal.addEventListener("abort", abort, { once: true });
2248
+ }
2249
+ return this._languageProcesses.get(admitted.pid)?.descriptor ?? descriptor;
2250
+ }
2251
+ async _exec(command, options = {}) {
2252
+ return (await this._executionOperation({
2253
+ type: "shell_execution",
2254
+ request: { process: processExecutionOptions(options), command },
2255
+ }, options));
2256
+ }
2257
+ async _execFile(command, args = [], options = {}) {
2258
+ const operationOptions = { ...options, args: [...args] };
2259
+ return (await this._executionOperation({
2260
+ type: "argv_execution",
2261
+ request: {
2262
+ process: processExecutionOptions(operationOptions),
2263
+ command,
2264
+ },
2265
+ }, operationOptions));
2266
+ }
2267
+ /** @deprecated Use `process.exec()` for lifecycle-aware execution. */
1764
2268
  async exec(command, options) {
1765
2269
  return this.#kernel.exec(command, options);
1766
2270
  }
2271
+ /** @deprecated Use `process.execFile()` for lifecycle-aware execution. */
1767
2272
  async execArgv(command, args = [], options) {
1768
2273
  const kernel = this.#kernel;
1769
2274
  return kernel.execArgv(command, args, options);
1770
2275
  }
1771
- _trackProcess(proc, command, args, outputHandlers, exitHandlers) {
2276
+ async _executeJavaScript(source, options = {}) {
2277
+ return (await this._executionOperation({
2278
+ type: "javascript_execution",
2279
+ request: {
2280
+ process: processExecutionOptions(options),
2281
+ source,
2282
+ format: options.format === "commonjs"
2283
+ ? executionProtocol.JavaScriptModuleFormat.CommonJs
2284
+ : executionProtocol.JavaScriptModuleFormat.Module,
2285
+ filePath: options.filePath ?? null,
2286
+ inputs: options.inputs ? JSON.stringify(options.inputs) : null,
2287
+ },
2288
+ }, options));
2289
+ }
2290
+ async _evaluationOperation(payload, options) {
2291
+ const result = (await this._executionOperation(payload, options));
2292
+ if (result.outcome !== "succeeded")
2293
+ return result;
2294
+ const value = result.evaluationValue;
2295
+ if (value === undefined) {
2296
+ throw new Error("successful evaluation did not return a JSON-serializable value");
2297
+ }
2298
+ return { ...result, value: value };
2299
+ }
2300
+ async _evaluateJavaScript(expression, options = {}) {
2301
+ return this._evaluationOperation({
2302
+ type: "javascript_evaluation",
2303
+ request: {
2304
+ process: processExecutionOptions(options),
2305
+ expression,
2306
+ format: options.format === "commonjs"
2307
+ ? executionProtocol.JavaScriptModuleFormat.CommonJs
2308
+ : executionProtocol.JavaScriptModuleFormat.Module,
2309
+ filePath: options.filePath ?? null,
2310
+ inputs: options.inputs ? JSON.stringify(options.inputs) : null,
2311
+ },
2312
+ }, options);
2313
+ }
2314
+ async _executeJavaScriptFile(path, options = {}) {
2315
+ return (await this._executionOperation({
2316
+ type: "javascript_file_execution",
2317
+ request: { process: processExecutionOptions(options), path },
2318
+ }, options));
2319
+ }
2320
+ _spawnJavaScript(source, options = {}) {
2321
+ return this._spawnLanguageOperation((operationOptions, executionId) => ({
2322
+ type: "javascript_execution",
2323
+ request: {
2324
+ process: processExecutionOptions(operationOptions, {
2325
+ background: true,
2326
+ executionId,
2327
+ }),
2328
+ source,
2329
+ format: executionProtocol.JavaScriptModuleFormat.Module,
2330
+ filePath: null,
2331
+ inputs: null,
2332
+ },
2333
+ }), options, "javascript");
2334
+ }
2335
+ _spawnJavaScriptFile(path, options = {}) {
2336
+ return this._spawnLanguageOperation((operationOptions, executionId) => ({
2337
+ type: "javascript_file_execution",
2338
+ request: {
2339
+ process: processExecutionOptions(operationOptions, {
2340
+ background: true,
2341
+ executionId,
2342
+ }),
2343
+ path,
2344
+ },
2345
+ }), options, "javascript");
2346
+ }
2347
+ async _executeTypeScript(source, options = {}) {
2348
+ return (await this._executionOperation({
2349
+ type: "typescript_execution",
2350
+ request: {
2351
+ process: processExecutionOptions(options),
2352
+ source,
2353
+ filePath: options.filePath ?? null,
2354
+ tsconfigPath: options.tsconfigPath ?? null,
2355
+ compilerOptions: options.compilerOptions
2356
+ ? JSON.stringify(options.compilerOptions)
2357
+ : null,
2358
+ inputs: options.inputs ? JSON.stringify(options.inputs) : null,
2359
+ },
2360
+ }, options));
2361
+ }
2362
+ async _evaluateTypeScript(expression, options = {}) {
2363
+ return this._evaluationOperation({
2364
+ type: "typescript_evaluation",
2365
+ request: {
2366
+ process: processExecutionOptions(options),
2367
+ expression,
2368
+ filePath: options.filePath ?? null,
2369
+ tsconfigPath: options.tsconfigPath ?? null,
2370
+ compilerOptions: options.compilerOptions
2371
+ ? JSON.stringify(options.compilerOptions)
2372
+ : null,
2373
+ inputs: options.inputs ? JSON.stringify(options.inputs) : null,
2374
+ },
2375
+ }, options);
2376
+ }
2377
+ async _executeTypeScriptFile(path, options = {}) {
2378
+ return (await this._executionOperation({
2379
+ type: "typescript_file_execution",
2380
+ request: {
2381
+ process: processExecutionOptions(options),
2382
+ path,
2383
+ tsconfigPath: options.tsconfigPath ?? null,
2384
+ compilerOptions: options.compilerOptions
2385
+ ? JSON.stringify(options.compilerOptions)
2386
+ : null,
2387
+ },
2388
+ }, options));
2389
+ }
2390
+ _spawnTypeScript(source, options = {}) {
2391
+ return this._spawnLanguageOperation((operationOptions, executionId) => ({
2392
+ type: "typescript_execution",
2393
+ request: {
2394
+ process: processExecutionOptions(operationOptions, {
2395
+ background: true,
2396
+ executionId,
2397
+ }),
2398
+ source,
2399
+ filePath: null,
2400
+ tsconfigPath: null,
2401
+ compilerOptions: null,
2402
+ inputs: null,
2403
+ },
2404
+ }), options, "javascript");
2405
+ }
2406
+ _spawnTypeScriptFile(path, options = {}) {
2407
+ return this._spawnLanguageOperation((operationOptions, executionId) => ({
2408
+ type: "typescript_file_execution",
2409
+ request: {
2410
+ process: processExecutionOptions(operationOptions, {
2411
+ background: true,
2412
+ executionId,
2413
+ }),
2414
+ path,
2415
+ tsconfigPath: null,
2416
+ compilerOptions: null,
2417
+ },
2418
+ }), options, "javascript");
2419
+ }
2420
+ async _checkTypeScript(source, options = {}) {
2421
+ const result = (await this._executionOperation({
2422
+ type: "typescript_check",
2423
+ request: {
2424
+ identity: executionIdentity(options),
2425
+ output: executionOutput(options),
2426
+ source,
2427
+ cwd: options.cwd ?? null,
2428
+ filePath: options.filePath ?? null,
2429
+ tsconfigPath: options.tsconfigPath ?? null,
2430
+ compilerOptions: options.compilerOptions
2431
+ ? JSON.stringify(options.compilerOptions)
2432
+ : null,
2433
+ timeoutMs: protocolTimeout(options.timeoutMs),
2434
+ },
2435
+ }, options));
2436
+ return mapTypeScriptCheckResult(result);
2437
+ }
2438
+ async _checkTypeScriptProject(options = {}) {
2439
+ const result = (await this._executionOperation({
2440
+ type: "typescript_project_check",
2441
+ request: {
2442
+ identity: executionIdentity(options),
2443
+ output: executionOutput(options),
2444
+ cwd: options.cwd ?? null,
2445
+ tsconfigPath: options.tsconfigPath ?? null,
2446
+ timeoutMs: protocolTimeout(options.timeoutMs),
2447
+ },
2448
+ }, options));
2449
+ return mapTypeScriptCheckResult(result);
2450
+ }
2451
+ async _installNpmPackages(packagesOrOptions = {}, maybeOptions = {}) {
2452
+ if (typeof packagesOrOptions === "string" ||
2453
+ Array.isArray(packagesOrOptions)) {
2454
+ const packages = Array.isArray(packagesOrOptions)
2455
+ ? packagesOrOptions
2456
+ : [packagesOrOptions];
2457
+ return (await this._executionOperation({
2458
+ type: "npm_package_install",
2459
+ request: {
2460
+ identity: executionIdentity(maybeOptions),
2461
+ output: executionOutput(maybeOptions),
2462
+ cwd: maybeOptions.cwd ?? null,
2463
+ env: maybeOptions.env
2464
+ ? new Map(Object.entries(maybeOptions.env))
2465
+ : null,
2466
+ timeoutMs: protocolTimeout(maybeOptions.timeoutMs),
2467
+ packages,
2468
+ dev: maybeOptions.dev ?? null,
2469
+ global: maybeOptions.global ?? null,
2470
+ },
2471
+ }, maybeOptions));
2472
+ }
2473
+ const options = packagesOrOptions;
2474
+ return (await this._executionOperation({
2475
+ type: "npm_project_install",
2476
+ request: {
2477
+ identity: executionIdentity(options),
2478
+ output: executionOutput(options),
2479
+ cwd: options.cwd ?? null,
2480
+ env: options.env ? new Map(Object.entries(options.env)) : null,
2481
+ timeoutMs: protocolTimeout(options.timeoutMs),
2482
+ frozen: options.frozen ?? null,
2483
+ },
2484
+ }, options));
2485
+ }
2486
+ async _executeNpmScript(script, options = {}) {
2487
+ return (await this._executionOperation({
2488
+ type: "npm_script_execution",
2489
+ request: { process: processExecutionOptions(options), script },
2490
+ }, options));
2491
+ }
2492
+ async _executeNpmPackage(packageSpec, options = {}) {
2493
+ return (await this._executionOperation({
2494
+ type: "npm_package_execution",
2495
+ request: {
2496
+ process: processExecutionOptions(options),
2497
+ packageSpec,
2498
+ binary: options.binary ?? null,
2499
+ },
2500
+ }, options));
2501
+ }
2502
+ async _executePython(source, options = {}) {
2503
+ return (await this._executionOperation({
2504
+ type: "python_execution",
2505
+ request: {
2506
+ process: processExecutionOptions(options),
2507
+ source,
2508
+ inputs: options.inputs ? JSON.stringify(options.inputs) : null,
2509
+ },
2510
+ }, options));
2511
+ }
2512
+ async _evaluatePython(expression, options = {}) {
2513
+ return this._evaluationOperation({
2514
+ type: "python_evaluation",
2515
+ request: {
2516
+ process: processExecutionOptions(options),
2517
+ expression,
2518
+ inputs: options.inputs ? JSON.stringify(options.inputs) : null,
2519
+ },
2520
+ }, options);
2521
+ }
2522
+ async _executePythonFile(path, options = {}) {
2523
+ return (await this._executionOperation({
2524
+ type: "python_file_execution",
2525
+ request: { process: processExecutionOptions(options), path },
2526
+ }, options));
2527
+ }
2528
+ async _executePythonModule(module, options = {}) {
2529
+ return (await this._executionOperation({
2530
+ type: "python_module_execution",
2531
+ request: { process: processExecutionOptions(options), module },
2532
+ }, options));
2533
+ }
2534
+ _spawnPython(source, options = {}) {
2535
+ return this._spawnLanguageOperation((operationOptions, executionId) => ({
2536
+ type: "python_execution",
2537
+ request: {
2538
+ process: processExecutionOptions(operationOptions, {
2539
+ background: true,
2540
+ executionId,
2541
+ }),
2542
+ source,
2543
+ inputs: null,
2544
+ },
2545
+ }), options, "python");
2546
+ }
2547
+ _spawnPythonFile(path, options = {}) {
2548
+ return this._spawnLanguageOperation((operationOptions, executionId) => ({
2549
+ type: "python_file_execution",
2550
+ request: {
2551
+ process: processExecutionOptions(operationOptions, {
2552
+ background: true,
2553
+ executionId,
2554
+ }),
2555
+ path,
2556
+ },
2557
+ }), options, "python");
2558
+ }
2559
+ _spawnPythonModule(module, options = {}) {
2560
+ return this._spawnLanguageOperation((operationOptions, executionId) => ({
2561
+ type: "python_module_execution",
2562
+ request: {
2563
+ process: processExecutionOptions(operationOptions, {
2564
+ background: true,
2565
+ executionId,
2566
+ }),
2567
+ module,
2568
+ },
2569
+ }), options, "python");
2570
+ }
2571
+ async _installPythonPackages(packagesOrOptions = {}, maybeOptions = {}) {
2572
+ const packages = typeof packagesOrOptions === "string"
2573
+ ? [packagesOrOptions]
2574
+ : Array.isArray(packagesOrOptions)
2575
+ ? packagesOrOptions
2576
+ : [];
2577
+ const options = typeof packagesOrOptions === "string" || Array.isArray(packagesOrOptions)
2578
+ ? maybeOptions
2579
+ : packagesOrOptions;
2580
+ return (await this._executionOperation({
2581
+ type: "python_install",
2582
+ request: {
2583
+ identity: executionIdentity(options),
2584
+ output: executionOutput(options),
2585
+ cwd: options.cwd ?? null,
2586
+ env: options.env ? new Map(Object.entries(options.env)) : null,
2587
+ timeoutMs: protocolTimeout(options.timeoutMs),
2588
+ packages,
2589
+ upgrade: options.upgrade ?? null,
2590
+ requirementsFile: options.requirementsFile ?? null,
2591
+ indexUrl: options.indexUrl ?? null,
2592
+ extraIndexUrls: options.extraIndexUrls ?? [],
2593
+ },
2594
+ }, options));
2595
+ }
2596
+ async _getContext(contextId) {
2597
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "get_execution", request: { executionId: contextId } });
2598
+ if (response.type !== "execution_descriptor") {
2599
+ throw new Error(`unexpected getContext response: ${response.type}`);
2600
+ }
2601
+ return mapExecutionDescriptor(response.response.execution);
2602
+ }
2603
+ async _listContexts() {
2604
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "list_executions" });
2605
+ if (response.type !== "execution_list") {
2606
+ throw new Error(`unexpected listContexts response: ${response.type}`);
2607
+ }
2608
+ return response.response.executions.map(mapExecutionDescriptor);
2609
+ }
2610
+ async _waitExecutionResult(executionId) {
2611
+ let resolveCompletion;
2612
+ const completion = new Promise((resolve) => {
2613
+ resolveCompletion = resolve;
2614
+ });
2615
+ const unsubscribe = this._onExecutionCompleted(executionId, () => {
2616
+ resolveCompletion?.();
2617
+ });
2618
+ let response;
2619
+ try {
2620
+ try {
2621
+ response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "wait_execution", request: { executionId } });
2622
+ }
2623
+ catch (error) {
2624
+ if (!(error instanceof SidecarRejectedError) ||
2625
+ error.detail.code !== "execution_busy") {
2626
+ throw error;
2627
+ }
2628
+ await completion;
2629
+ response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "wait_execution", request: { executionId } });
2630
+ }
2631
+ }
2632
+ finally {
2633
+ unsubscribe();
2634
+ }
2635
+ if (response.type !== "execution_completed") {
2636
+ throw new Error(`unexpected waitExecution response: ${response.type}`);
2637
+ }
2638
+ return mapExecutionResult(response.response);
2639
+ }
2640
+ async _cancelExecution(executionId) {
2641
+ await this._descriptorLifecycleRequest("cancel_execution", executionId);
2642
+ }
2643
+ async _signalExecution(executionId, signal) {
2644
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "signal_execution", request: { executionId, signal } });
2645
+ if (response.type !== "execution_descriptor") {
2646
+ throw new Error(`unexpected signalExecution response: ${response.type}`);
2647
+ }
2648
+ }
2649
+ async _resetContext(contextId) {
2650
+ await this._descriptorLifecycleRequest("reset_execution", contextId);
2651
+ }
2652
+ async _descriptorLifecycleRequest(type, executionId) {
2653
+ const payload = type === "cancel_execution"
2654
+ ? { type, request: { executionId } }
2655
+ : { type, request: { executionId } };
2656
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, payload);
2657
+ if (response.type !== "execution_descriptor") {
2658
+ throw new Error(`unexpected ${type} response: ${response.type}`);
2659
+ }
2660
+ return mapExecutionDescriptor(response.response.execution);
2661
+ }
2662
+ async _deleteContext(contextId) {
2663
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "delete_execution", request: { executionId: contextId } });
2664
+ if (response.type !== "execution_deleted") {
2665
+ throw new Error(`unexpected deleteContext response: ${response.type}`);
2666
+ }
2667
+ }
2668
+ async _writeExecutionStdin(executionId, data) {
2669
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, {
2670
+ type: "write_execution_stdin",
2671
+ request: { executionId, chunk: executionBytes(data) },
2672
+ });
2673
+ if (response.type !== "execution_io") {
2674
+ throw new Error(`unexpected writeExecutionStdin response: ${response.type}`);
2675
+ }
2676
+ }
2677
+ async _closeExecutionStdin(executionId) {
2678
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, { type: "close_execution_stdin", request: { executionId } });
2679
+ if (response.type !== "execution_io") {
2680
+ throw new Error(`unexpected closeExecutionStdin response: ${response.type}`);
2681
+ }
2682
+ }
2683
+ async _resizeExecutionPty(executionId, size) {
2684
+ const cols = protocolUnsigned(size.cols, 0xffff, "size.cols");
2685
+ const rows = protocolUnsigned(size.rows, 0xffff, "size.rows");
2686
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, {
2687
+ type: "resize_execution_pty",
2688
+ request: { executionId, cols, rows },
2689
+ });
2690
+ if (response.type !== "execution_io") {
2691
+ throw new Error(`unexpected resizeExecutionPty response: ${response.type}`);
2692
+ }
2693
+ }
2694
+ async _readExecutionOutput(executionId, options = {}) {
2695
+ const limit = options.limit === undefined
2696
+ ? null
2697
+ : protocolUnsigned(options.limit, 0xffff_ffff, "limit");
2698
+ const response = await this._sidecarClient.sendVmRequest(this._sidecarSession, this._sidecarVm, {
2699
+ type: "read_execution_output",
2700
+ request: {
2701
+ executionId,
2702
+ cursor: options.cursor ?? null,
2703
+ limit,
2704
+ },
2705
+ });
2706
+ if (response.type !== "execution_output_page") {
2707
+ throw new Error(`unexpected readExecutionOutput response: ${response.type}`);
2708
+ }
2709
+ return {
2710
+ events: response.response.events.map(mapExecutionOutputEvent),
2711
+ nextCursor: response.response.nextCursor,
2712
+ hasMore: response.response.hasMore,
2713
+ truncated: response.response.truncated,
2714
+ };
2715
+ }
2716
+ _onExecutionOutput(executionId, handler) {
2717
+ const handlers = this._executionOutputHandlers.get(executionId) ?? new Set();
2718
+ handlers.add(handler);
2719
+ this._executionOutputHandlers.set(executionId, handlers);
2720
+ return () => {
2721
+ handlers.delete(handler);
2722
+ if (handlers.size === 0)
2723
+ this._executionOutputHandlers.delete(executionId);
2724
+ };
2725
+ }
2726
+ _onExecutionCompleted(executionId, handler) {
2727
+ const handlers = this._executionCompletedHandlers.get(executionId) ?? new Set();
2728
+ handlers.add(handler);
2729
+ this._executionCompletedHandlers.set(executionId, handlers);
2730
+ return () => {
2731
+ handlers.delete(handler);
2732
+ if (handlers.size === 0)
2733
+ this._executionCompletedHandlers.delete(executionId);
2734
+ };
2735
+ }
2736
+ _trackProcess(proc, command, args, retainEvents, outputHandlers, exitHandlers) {
1772
2737
  const entry = {
1773
2738
  proc,
1774
2739
  command,
1775
2740
  args,
2741
+ startedAtMs: Date.now(),
2742
+ retainEvents,
2743
+ events: [],
2744
+ nextSequence: 0,
2745
+ signal: undefined,
2746
+ exit: undefined,
1776
2747
  outputHandlers,
1777
2748
  exitHandlers,
1778
2749
  };
@@ -1783,40 +2754,83 @@ export class AgentOs {
1783
2754
  // `_processes` is a process table for this VM's lifetime; it is freed wholesale
1784
2755
  // in dispose(). (H5: the leak was that dispose() never cleared it.)
1785
2756
  void proc.wait().then((code) => {
2757
+ const exit = {
2758
+ pid: proc.pid,
2759
+ outcome: entry.signal ? "signalled" : "exited",
2760
+ exitCode: code,
2761
+ ...(entry.signal ? { signal: entry.signal } : {}),
2762
+ };
2763
+ entry.exit = exit;
1786
2764
  for (const h of exitHandlers)
1787
- h({ pid: proc.pid, exitCode: code });
2765
+ h(exit);
1788
2766
  });
1789
- return { pid: proc.pid };
2767
+ return {
2768
+ pid: proc.pid,
2769
+ state: "running",
2770
+ command,
2771
+ startedAtMs: entry.startedAtMs,
2772
+ };
1790
2773
  }
1791
- spawn(command, args, options) {
2774
+ async _spawnProcess(command, args = [], options = {}) {
1792
2775
  const outputHandlers = new Set();
1793
2776
  const exitHandlers = new Set();
2777
+ const recordOutput = (channel, data) => {
2778
+ const entry = this._processes.get(proc.pid);
2779
+ if (!entry?.retainEvents)
2780
+ return;
2781
+ if (entry.events.length >= PROCESS_OUTPUT_EVENT_LIMIT) {
2782
+ entry.events.shift();
2783
+ }
2784
+ entry.events.push({
2785
+ pid: proc.pid,
2786
+ sequence: entry.nextSequence++,
2787
+ channel,
2788
+ chunk: data,
2789
+ timestampMs: Date.now(),
2790
+ });
2791
+ };
1794
2792
  const proc = this.#kernel.spawn(command, args, {
1795
- ...options,
2793
+ cwd: options.cwd,
2794
+ env: options.env,
2795
+ stdin: options.stdin,
2796
+ timeout: options.timeoutMs,
1796
2797
  onStdout: (data) => {
2798
+ recordOutput("stdout", data);
1797
2799
  options?.onStdout?.(data);
1798
2800
  for (const h of outputHandlers) {
1799
2801
  h({ pid: proc.pid, stream: "stdout", data });
1800
2802
  }
1801
2803
  },
1802
2804
  onStderr: (data) => {
2805
+ recordOutput("stderr", data);
1803
2806
  options?.onStderr?.(data);
1804
2807
  for (const h of outputHandlers) {
1805
2808
  h({ pid: proc.pid, stream: "stderr", data });
1806
2809
  }
1807
2810
  },
1808
2811
  });
1809
- return this._trackProcess(proc, command, args, outputHandlers, exitHandlers);
2812
+ return this._trackProcess(proc, command, args, options.output?.retainEvents ?? false, outputHandlers, exitHandlers);
2813
+ }
2814
+ spawn(command, args = [], options) {
2815
+ return this.process.spawn(command, args, options);
1810
2816
  }
1811
2817
  /** Write data to a process's stdin. */
1812
- writeProcessStdin(pid, data) {
2818
+ _writeProcessStdin(pid, data) {
2819
+ const language = this._languageProcesses.get(pid);
2820
+ if (language) {
2821
+ return this._writeExecutionStdin(language.executionId, data);
2822
+ }
1813
2823
  const entry = this._processes.get(pid);
1814
2824
  if (!entry)
1815
2825
  throw new Error(`Process not found: ${pid}`);
1816
2826
  return entry.proc.writeStdin(data);
1817
2827
  }
1818
2828
  /** Close a process's stdin stream. */
1819
- closeProcessStdin(pid) {
2829
+ _closeProcessStdin(pid) {
2830
+ const language = this._languageProcesses.get(pid);
2831
+ if (language) {
2832
+ return this._closeExecutionStdin(language.executionId);
2833
+ }
1820
2834
  const entry = this._processes.get(pid);
1821
2835
  if (!entry)
1822
2836
  throw new Error(`Process not found: ${pid}`);
@@ -1824,7 +2838,7 @@ export class AgentOs {
1824
2838
  }
1825
2839
  /** Subscribe to stdout and stderr from a process. */
1826
2840
  onProcessOutput(pid, handler) {
1827
- const entry = this._processes.get(pid);
2841
+ const entry = this._processes.get(pid) ?? this._languageProcesses.get(pid);
1828
2842
  if (!entry)
1829
2843
  throw new Error(`Process not found: ${pid}`);
1830
2844
  entry.outputHandlers.add(handler);
@@ -1834,12 +2848,12 @@ export class AgentOs {
1834
2848
  }
1835
2849
  /** Subscribe to process exit. Returns an unsubscribe function. */
1836
2850
  onProcessExit(pid, handler) {
1837
- const entry = this._processes.get(pid);
2851
+ const entry = this._processes.get(pid) ?? this._languageProcesses.get(pid);
1838
2852
  if (!entry)
1839
2853
  throw new Error(`Process not found: ${pid}`);
1840
2854
  // If already exited, call immediately.
1841
- if (entry.proc.exitCode !== null) {
1842
- handler({ pid, exitCode: entry.proc.exitCode });
2855
+ if ("exit" in entry && entry.exit) {
2856
+ handler(entry.exit);
1843
2857
  return () => { };
1844
2858
  }
1845
2859
  entry.exitHandlers.add(handler);
@@ -1848,11 +2862,39 @@ export class AgentOs {
1848
2862
  };
1849
2863
  }
1850
2864
  /** Wait for a process to exit. Returns the exit code. */
1851
- waitProcess(pid) {
2865
+ async _waitProcess(pid) {
2866
+ const language = this._languageProcesses.get(pid);
2867
+ if (language) {
2868
+ if (language.exit)
2869
+ return language.exit;
2870
+ const result = await this._waitExecutionResult(language.executionId);
2871
+ const exit = {
2872
+ pid,
2873
+ outcome: result.outcome === "timed_out"
2874
+ ? "timed_out"
2875
+ : result.outcome === "cancelled"
2876
+ ? "signalled"
2877
+ : "exited",
2878
+ ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
2879
+ ...(language.signal ? { signal: language.signal } : {}),
2880
+ };
2881
+ language.exit = exit;
2882
+ language.descriptor = { ...language.descriptor, state: "exited" };
2883
+ for (const handler of language.exitHandlers)
2884
+ handler(exit);
2885
+ return exit;
2886
+ }
1852
2887
  const entry = this._processes.get(pid);
1853
2888
  if (!entry)
1854
2889
  throw new Error(`Process not found: ${pid}`);
1855
- return entry.proc.wait();
2890
+ if (entry.exit)
2891
+ return entry.exit;
2892
+ const exitCode = await entry.proc.wait();
2893
+ return (entry.exit ?? {
2894
+ pid,
2895
+ outcome: "exited",
2896
+ exitCode,
2897
+ });
1856
2898
  }
1857
2899
  _assertSafeAbsolutePath(path) {
1858
2900
  if (!path.startsWith("/")) {
@@ -1871,15 +2913,15 @@ export class AgentOs {
1871
2913
  _vfs() {
1872
2914
  return this.#kernel.vfs;
1873
2915
  }
1874
- async readFile(path) {
2916
+ async _readFile(path) {
1875
2917
  this._assertSafeAbsolutePath(path);
1876
2918
  return this.#kernel.readFile(path);
1877
2919
  }
1878
- async writeFile(path, content) {
2920
+ async _writeFile(path, content) {
1879
2921
  this._assertWritableAbsolutePath(path);
1880
2922
  return this.#kernel.writeFile(path, content);
1881
2923
  }
1882
- async writeFiles(entries) {
2924
+ async _writeFiles(entries) {
1883
2925
  const results = [];
1884
2926
  for (const entry of entries) {
1885
2927
  try {
@@ -1902,7 +2944,7 @@ export class AgentOs {
1902
2944
  }
1903
2945
  return results;
1904
2946
  }
1905
- async readFiles(paths) {
2947
+ async _readFiles(paths) {
1906
2948
  const results = [];
1907
2949
  for (const path of paths) {
1908
2950
  try {
@@ -1931,18 +2973,18 @@ export class AgentOs {
1931
2973
  // exists() loop makes `mkdir -p` cost O(components * tree).
1932
2974
  await this.#kernel.mkdir(path);
1933
2975
  }
1934
- async mkdir(path, options) {
2976
+ async _mkdir(path, options) {
1935
2977
  if (options?.recursive) {
1936
2978
  return this._mkdirp(path);
1937
2979
  }
1938
2980
  this._assertSafeAbsolutePath(path);
1939
2981
  return this.#kernel.mkdir(path);
1940
2982
  }
1941
- async readdir(path) {
2983
+ async _readdir(path) {
1942
2984
  this._assertSafeAbsolutePath(path);
1943
2985
  return this.#kernel.readdir(path);
1944
2986
  }
1945
- async readdirEntries(path) {
2987
+ async _readdirEntries(path) {
1946
2988
  this._assertSafeAbsolutePath(path);
1947
2989
  const entries = await this.#kernel.readdirRecursive(path, { maxDepth: 0 });
1948
2990
  return entries.map((entry) => ({
@@ -1951,7 +2993,7 @@ export class AgentOs {
1951
2993
  isSymbolicLink: entry.isSymbolicLink,
1952
2994
  }));
1953
2995
  }
1954
- async readdirRecursive(path, options) {
2996
+ async _readdirRecursive(path, options) {
1955
2997
  this._assertSafeAbsolutePath(path);
1956
2998
  const exclude = options?.exclude ? new Set(options.exclude) : undefined;
1957
2999
  const entries = await this.#kernel.readdirRecursive(path, {
@@ -1981,15 +3023,15 @@ export class AgentOs {
1981
3023
  }
1982
3024
  return results;
1983
3025
  }
1984
- async stat(path) {
3026
+ async _stat(path) {
1985
3027
  this._assertSafeAbsolutePath(path);
1986
3028
  return this.#kernel.stat(path);
1987
3029
  }
1988
- async exists(path) {
3030
+ async _exists(path) {
1989
3031
  this._assertSafeAbsolutePath(path);
1990
3032
  return this.#kernel.exists(path);
1991
3033
  }
1992
- async exportRootFilesystem(options) {
3034
+ async _exportRootFilesystem(options) {
1993
3035
  if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes <= 0) {
1994
3036
  throw new RangeError("maxBytes must be a positive safe integer");
1995
3037
  }
@@ -2009,7 +3051,7 @@ export class AgentOs {
2009
3051
  * after the returned promise settles; a delivery failure rejects instead of
2010
3052
  * leaving the mount silently host-only.
2011
3053
  */
2012
- async mountFs(descriptor) {
3054
+ async _mountFs(descriptor) {
2013
3055
  this._assertSafeAbsolutePath(descriptor.path);
2014
3056
  if (!(this.#kernel instanceof NativeSidecarKernelProxy)) {
2015
3057
  throw new Error("portable dynamic mounts require the native sidecar");
@@ -2023,30 +3065,94 @@ export class AgentOs {
2023
3065
  },
2024
3066
  });
2025
3067
  }
2026
- async unmountFs(path) {
3068
+ async _unmountFs(path) {
2027
3069
  this._assertSafeAbsolutePath(path);
2028
3070
  if (!(this.#kernel instanceof NativeSidecarKernelProxy)) {
2029
3071
  throw new Error("portable dynamic mounts require the native sidecar");
2030
3072
  }
2031
3073
  await this.#kernel.unmountDescriptor(path);
2032
3074
  }
2033
- async listMounts() {
3075
+ async _listMounts() {
2034
3076
  if (!(this.#kernel instanceof NativeSidecarKernelProxy))
2035
3077
  return [];
2036
3078
  return this.#kernel.listMounts();
2037
3079
  }
2038
- async move(from, to) {
3080
+ async _move(from, to) {
2039
3081
  this._assertWritableAbsolutePath(from);
2040
3082
  this._assertWritableAbsolutePath(to);
2041
3083
  await this.#kernel.movePath(from, to);
2042
3084
  }
2043
- async remove(path, options) {
3085
+ async _remove(path, options) {
2044
3086
  this._assertWritableAbsolutePath(path);
2045
3087
  await this.#kernel.removePath(path, {
2046
3088
  recursive: options?.recursive ?? false,
2047
3089
  });
2048
3090
  }
2049
- async httpRequest(request) {
3091
+ /** @deprecated Use `filesystem.readFile()`. */
3092
+ readFile(path) {
3093
+ return this.filesystem.readFile(path);
3094
+ }
3095
+ /** @deprecated Use `filesystem.writeFile()`. */
3096
+ writeFile(path, content) {
3097
+ return this.filesystem.writeFile(path, content);
3098
+ }
3099
+ /** @deprecated Use `filesystem.writeFiles()`. */
3100
+ writeFiles(entries) {
3101
+ return this.filesystem.writeFiles(entries);
3102
+ }
3103
+ /** @deprecated Use `filesystem.readFiles()`. */
3104
+ readFiles(paths) {
3105
+ return this.filesystem.readFiles(paths);
3106
+ }
3107
+ /** @deprecated Use `filesystem.mkdir()`. */
3108
+ mkdir(path, options) {
3109
+ return this.filesystem.mkdir(path, options);
3110
+ }
3111
+ /** @deprecated Use `filesystem.readdir()`. */
3112
+ readdir(path) {
3113
+ return this.filesystem.readdir(path);
3114
+ }
3115
+ /** @deprecated Use `filesystem.readdirEntries()`. */
3116
+ readdirEntries(path) {
3117
+ return this.filesystem.readdirEntries(path);
3118
+ }
3119
+ /** @deprecated Use `filesystem.readdirRecursive()`. */
3120
+ readdirRecursive(path, options) {
3121
+ return this.filesystem.readdirRecursive(path, options);
3122
+ }
3123
+ /** @deprecated Use `filesystem.stat()`. */
3124
+ stat(path) {
3125
+ return this.filesystem.stat(path);
3126
+ }
3127
+ /** @deprecated Use `filesystem.exists()`. */
3128
+ exists(path) {
3129
+ return this.filesystem.exists(path);
3130
+ }
3131
+ /** @deprecated Use `filesystem.export()`. */
3132
+ exportRootFilesystem(options) {
3133
+ return this.filesystem.export(options);
3134
+ }
3135
+ /** @deprecated Use `filesystem.mount()`. */
3136
+ mountFs(descriptor) {
3137
+ return this.filesystem.mount(descriptor);
3138
+ }
3139
+ /** @deprecated Use `filesystem.unmount()`. */
3140
+ unmountFs(path) {
3141
+ return this.filesystem.unmount(path);
3142
+ }
3143
+ /** @deprecated Use `filesystem.listMounts()`. */
3144
+ listMounts() {
3145
+ return this.filesystem.listMounts();
3146
+ }
3147
+ /** @deprecated Use `filesystem.move()`. */
3148
+ move(from, to) {
3149
+ return this.filesystem.move(from, to);
3150
+ }
3151
+ /** @deprecated Use `filesystem.remove()`. */
3152
+ remove(path, options) {
3153
+ return this.filesystem.remove(path, options);
3154
+ }
3155
+ async _httpRequest(request) {
2050
3156
  this._assertSafeAbsolutePath(request.path.split("?")[0] || "/");
2051
3157
  const method = request.method?.toUpperCase() ?? "GET";
2052
3158
  const body = typeof request.body === "string"
@@ -2074,9 +3180,13 @@ export class AgentOs {
2074
3180
  body: new Uint8Array(Buffer.from(responsePayload.body ?? "", "base64")),
2075
3181
  };
2076
3182
  }
3183
+ /** @deprecated Use `network.httpRequest()`. */
3184
+ httpRequest(request) {
3185
+ return this.network.httpRequest(request);
3186
+ }
2077
3187
  /**
2078
3188
  * Fetch an HTTP endpoint inside the VM while preserving duplicate response
2079
- * headers and arbitrary binary bodies. AgentOS Apps uses this compatibility
3189
+ * headers and arbitrary binary bodies. agentOS Apps uses this compatibility
2080
3190
  * surface for its direct actor API.
2081
3191
  */
2082
3192
  async fetch(port, request) {
@@ -2142,7 +3252,7 @@ export class AgentOs {
2142
3252
  streamId,
2143
3253
  });
2144
3254
  }
2145
- openShell(options) {
3255
+ _openTerminal(options) {
2146
3256
  const shellId = `shell-${++this._shellCounter}`;
2147
3257
  this._closedShellIds.delete(shellId);
2148
3258
  const dataHandlers = new Set();
@@ -2193,7 +3303,7 @@ export class AgentOs {
2193
3303
  return this.#kernel.connectTerminal(options);
2194
3304
  }
2195
3305
  /** Write data to a shell's PTY input. */
2196
- writeShell(shellId, data) {
3306
+ _writeTerminal(shellId, data) {
2197
3307
  const entry = this._shells.get(shellId);
2198
3308
  if (!entry)
2199
3309
  throw new Error(`Shell not found: ${shellId}`);
@@ -2236,7 +3346,7 @@ export class AgentOs {
2236
3346
  return () => entry.exitHandlers.delete(handler);
2237
3347
  }
2238
3348
  /** Notify a shell of terminal resize. */
2239
- resizeShell(shellId, cols, rows) {
3349
+ _resizeTerminal(shellId, cols, rows) {
2240
3350
  const entry = this._shells.get(shellId);
2241
3351
  if (!entry)
2242
3352
  throw new Error(`Shell not found: ${shellId}`);
@@ -2247,7 +3357,7 @@ export class AgentOs {
2247
3357
  * immediately for a shell that has already exited (within the closed-shell
2248
3358
  * retention window).
2249
3359
  */
2250
- waitShell(shellId) {
3360
+ _waitTerminal(shellId) {
2251
3361
  const entry = this._shells.get(shellId);
2252
3362
  if (!entry) {
2253
3363
  const exitCode = this._closedShellIds.get(shellId);
@@ -2258,7 +3368,7 @@ export class AgentOs {
2258
3368
  return entry.exitPromise;
2259
3369
  }
2260
3370
  /** Kill a shell process and remove it from tracking. */
2261
- closeShell(shellId) {
3371
+ _closeTerminal(shellId) {
2262
3372
  const entry = this._shells.get(shellId);
2263
3373
  if (!entry) {
2264
3374
  if (this._closedShellIds.has(shellId)) {
@@ -2270,6 +3380,26 @@ export class AgentOs {
2270
3380
  this._shells.delete(shellId);
2271
3381
  this._closedShellIds.add(shellId);
2272
3382
  }
3383
+ /** @deprecated Use `terminal.open()`. */
3384
+ openShell(options) {
3385
+ return this.terminal.open(options);
3386
+ }
3387
+ /** @deprecated Use `terminal.write()`. */
3388
+ writeShell(shellId, data) {
3389
+ return this.terminal.write(shellId, data);
3390
+ }
3391
+ /** @deprecated Use `terminal.resize()`. */
3392
+ resizeShell(shellId, cols, rows) {
3393
+ this.terminal.resize(shellId, cols, rows);
3394
+ }
3395
+ /** @deprecated Use `terminal.wait()`. */
3396
+ waitShell(shellId) {
3397
+ return this.terminal.wait(shellId);
3398
+ }
3399
+ /** @deprecated Use `terminal.close()`. */
3400
+ closeShell(shellId) {
3401
+ this.terminal.close(shellId);
3402
+ }
2273
3403
  _resolveVmPathToHostPath(vmPath) {
2274
3404
  const normalizedVmPath = posixPath.normalize(vmPath);
2275
3405
  for (const mount of this._hostMounts) {
@@ -2285,34 +3415,51 @@ export class AgentOs {
2285
3415
  return null;
2286
3416
  }
2287
3417
  /** Returns info about all processes spawned via spawn(). */
2288
- listProcesses() {
2289
- return [...this._processes.values()].map(({ proc, command, args }) => ({
2290
- pid: proc.pid,
2291
- command,
2292
- args,
2293
- running: proc.exitCode === null,
2294
- exitCode: proc.exitCode,
2295
- }));
3418
+ async _listProcesses() {
3419
+ return [
3420
+ ...[...this._processes.values()].map(({ proc, command, startedAtMs }) => ({
3421
+ pid: proc.pid,
3422
+ command,
3423
+ state: proc.exitCode === null ? "running" : "exited",
3424
+ startedAtMs,
3425
+ })),
3426
+ ...[...this._languageProcesses.values()].map((entry) => entry.descriptor),
3427
+ ];
2296
3428
  }
2297
3429
  /** Returns all kernel processes across all active runtimes (WASM and Node). */
2298
- allProcesses() {
3430
+ _listAllProcesses() {
2299
3431
  if (this.#kernel instanceof NativeSidecarKernelProxy) {
2300
3432
  return this.#kernel.snapshotProcesses();
2301
3433
  }
2302
3434
  return [...this.#kernel.processes.values()];
2303
3435
  }
2304
3436
  /** Returns processes organized as a tree using ppid relationships. */
2305
- processTree() {
2306
- const all = this.allProcesses();
3437
+ async _processTree() {
3438
+ const all = this._listAllProcesses();
2307
3439
  const nodeMap = new Map();
2308
3440
  // Index: create a tree node for each process
2309
3441
  for (const proc of all) {
2310
- nodeMap.set(proc.pid, { ...proc, children: [] });
3442
+ nodeMap.set(proc.pid, {
3443
+ pid: proc.pid,
3444
+ ppid: proc.ppid,
3445
+ command: proc.command,
3446
+ state: proc.status,
3447
+ startedAtMs: proc.startTime,
3448
+ children: [],
3449
+ });
3450
+ }
3451
+ for (const process of this._languageProcesses.values()) {
3452
+ if (!nodeMap.has(process.descriptor.pid)) {
3453
+ nodeMap.set(process.descriptor.pid, {
3454
+ ...process.descriptor,
3455
+ children: [],
3456
+ });
3457
+ }
2311
3458
  }
2312
3459
  // Wire: attach each node to its parent
2313
3460
  const roots = [];
2314
3461
  for (const node of nodeMap.values()) {
2315
- const parent = nodeMap.get(node.ppid);
3462
+ const parent = node.ppid === undefined ? undefined : nodeMap.get(node.ppid);
2316
3463
  if (parent) {
2317
3464
  parent.children.push(node);
2318
3465
  }
@@ -2323,7 +3470,10 @@ export class AgentOs {
2323
3470
  return roots;
2324
3471
  }
2325
3472
  /** Returns info about a specific process by PID. Throws if not found. */
2326
- getProcess(pid) {
3473
+ async _getProcess(pid) {
3474
+ const language = this._languageProcesses.get(pid);
3475
+ if (language)
3476
+ return language.descriptor;
2327
3477
  const entry = this._processes.get(pid);
2328
3478
  if (!entry) {
2329
3479
  throw new Error(`Process not found: ${pid}`);
@@ -2331,32 +3481,76 @@ export class AgentOs {
2331
3481
  return {
2332
3482
  pid: entry.proc.pid,
2333
3483
  command: entry.command,
2334
- args: entry.args,
2335
- running: entry.proc.exitCode === null,
2336
- exitCode: entry.proc.exitCode,
3484
+ state: entry.proc.exitCode === null ? "running" : "exited",
3485
+ startedAtMs: entry.startedAtMs,
2337
3486
  };
2338
3487
  }
2339
- /** Send SIGTERM to gracefully stop a process. No-op if already exited. */
2340
- stopProcess(pid) {
3488
+ async _signalProcess(pid, signal) {
3489
+ const language = this._languageProcesses.get(pid);
3490
+ if (language) {
3491
+ language.signal = signal;
3492
+ await this._signalExecution(language.executionId, signal);
3493
+ return;
3494
+ }
2341
3495
  const entry = this._processes.get(pid);
2342
3496
  if (!entry) {
2343
3497
  throw new Error(`Process not found: ${pid}`);
2344
3498
  }
2345
3499
  if (entry.proc.exitCode !== null)
2346
3500
  return;
2347
- entry.proc.kill();
3501
+ entry.signal = signal;
3502
+ const number = signal === "SIGKILL" ? 9 : signal === "SIGINT" ? 2 : 15;
3503
+ entry.proc.kill(number);
2348
3504
  }
2349
3505
  /** Send SIGKILL to force-kill a process. No-op if already exited. */
2350
- killProcess(pid) {
3506
+ async _killProcess(pid) {
3507
+ await this._signalProcess(pid, "SIGKILL");
3508
+ }
3509
+ async _resizeProcessPty(pid, size) {
3510
+ const language = this._languageProcesses.get(pid);
3511
+ if (!language) {
3512
+ throw new Error(`Process ${pid} does not have a managed PTY`);
3513
+ }
3514
+ await this._resizeExecutionPty(language.executionId, size);
3515
+ }
3516
+ async _readProcessOutput(pid, options = {}) {
3517
+ const language = this._languageProcesses.get(pid);
3518
+ if (language) {
3519
+ const page = await this._readExecutionOutput(language.executionId, {
3520
+ cursor: options.after === undefined ? undefined : `1:${options.after + 1}`,
3521
+ });
3522
+ return {
3523
+ pid,
3524
+ events: page.events.map((event) => ({
3525
+ pid,
3526
+ sequence: event.sequence,
3527
+ channel: event.channel,
3528
+ chunk: event.chunk,
3529
+ timestampMs: event.timestampMs,
3530
+ })),
3531
+ nextCursor: page.nextCursor,
3532
+ hasMore: page.hasMore,
3533
+ truncated: page.truncated,
3534
+ };
3535
+ }
2351
3536
  const entry = this._processes.get(pid);
2352
- if (!entry) {
3537
+ if (!entry)
2353
3538
  throw new Error(`Process not found: ${pid}`);
3539
+ if (!entry.retainEvents) {
3540
+ throw new Error(`Process ${pid} was not spawned with output.retainEvents enabled`);
2354
3541
  }
2355
- if (entry.proc.exitCode !== null)
2356
- return;
2357
- entry.proc.kill(9);
3542
+ const after = options.after ?? -1;
3543
+ const events = entry.events.filter((event) => event.sequence > after);
3544
+ return {
3545
+ pid,
3546
+ events,
3547
+ nextCursor: String(events.at(-1)?.sequence ?? after),
3548
+ hasMore: false,
3549
+ truncated: entry.events.length === PROCESS_OUTPUT_EVENT_LIMIT &&
3550
+ after < (entry.events[0]?.sequence ?? 0) - 1,
3551
+ };
2358
3552
  }
2359
- async openSession(input) {
3553
+ async _openSession(input) {
2360
3554
  const response = await this._sendAcpRequest({
2361
3555
  tag: "AcpOpenSessionRequest",
2362
3556
  val: {
@@ -2379,7 +3573,7 @@ export class AgentOs {
2379
3573
  throw new Error(`unexpected openSession response: ${response.tag}`);
2380
3574
  }
2381
3575
  }
2382
- async getSession(input) {
3576
+ async _getSession(input) {
2383
3577
  const response = await this._sendAcpRequest({
2384
3578
  tag: "AcpGetDurableSessionRequest",
2385
3579
  val: { sessionId: input?.sessionId ?? null },
@@ -2389,7 +3583,7 @@ export class AgentOs {
2389
3583
  }
2390
3584
  return decodeDurableSessionInfo(response.val.session);
2391
3585
  }
2392
- async listSessions(input) {
3586
+ async _listSessions(input) {
2393
3587
  const response = await this._sendAcpRequest({
2394
3588
  tag: "AcpListDurableSessionsRequest",
2395
3589
  val: { cursor: input?.cursor ?? null, limit: input?.limit ?? null },
@@ -2402,7 +3596,7 @@ export class AgentOs {
2402
3596
  nextCursor: response.val.nextCursor,
2403
3597
  };
2404
3598
  }
2405
- async deleteSession(input = {}) {
3599
+ async _deleteSession(input = {}) {
2406
3600
  const response = await this._sendAcpRequest({
2407
3601
  tag: "AcpDeleteSessionRequest",
2408
3602
  val: { sessionId: input.sessionId ?? null },
@@ -2411,7 +3605,7 @@ export class AgentOs {
2411
3605
  throw new Error(`unexpected deleteSession response: ${response.tag}`);
2412
3606
  }
2413
3607
  }
2414
- async unloadSession(input) {
3608
+ async _unloadSession(input) {
2415
3609
  const response = await this._sendAcpRequest({
2416
3610
  tag: "AcpUnloadSessionRequest",
2417
3611
  val: { sessionId: input?.sessionId ?? null },
@@ -2420,7 +3614,7 @@ export class AgentOs {
2420
3614
  throw new Error(`unexpected unloadSession response: ${response.tag}`);
2421
3615
  }
2422
3616
  }
2423
- async prompt(input) {
3617
+ async _prompt(input) {
2424
3618
  const response = await this._sendAcpRequest({
2425
3619
  tag: "AcpPromptRequest",
2426
3620
  val: {
@@ -2438,7 +3632,7 @@ export class AgentOs {
2438
3632
  stopReason: response.val.stopReason,
2439
3633
  };
2440
3634
  }
2441
- async cancelPrompt(input) {
3635
+ async _cancelPrompt(input) {
2442
3636
  const response = await this._sendAcpRequest({
2443
3637
  tag: "AcpCancelPromptRequest",
2444
3638
  val: { sessionId: input?.sessionId ?? null },
@@ -2452,7 +3646,7 @@ export class AgentOs {
2452
3646
  }
2453
3647
  return { status: response.val.status };
2454
3648
  }
2455
- async respondPermission(input) {
3649
+ async _respondPermission(input) {
2456
3650
  const response = await this._sendAcpRequest({
2457
3651
  tag: "AcpRespondPermissionRequest",
2458
3652
  val: {
@@ -2479,7 +3673,7 @@ export class AgentOs {
2479
3673
  reason: permissionTerminalReason(response.val.reason),
2480
3674
  };
2481
3675
  }
2482
- async readHistory(input) {
3676
+ async _readHistory(input) {
2483
3677
  const response = await this._sendAcpRequest({
2484
3678
  tag: "AcpReadHistoryRequest",
2485
3679
  val: {
@@ -2498,7 +3692,7 @@ export class AgentOs {
2498
3692
  hasMoreAfter: response.val.hasMoreAfter,
2499
3693
  };
2500
3694
  }
2501
- async getSessionConfig(input) {
3695
+ async _getSessionConfig(input) {
2502
3696
  const response = await this._sendAcpRequest({
2503
3697
  tag: "AcpGetSessionConfigRequest",
2504
3698
  val: { sessionId: input?.sessionId ?? null },
@@ -2511,7 +3705,7 @@ export class AgentOs {
2511
3705
  options: JSON.parse(response.val.options),
2512
3706
  };
2513
3707
  }
2514
- async setSessionConfigOption(input) {
3708
+ async _setSessionConfigOption(input) {
2515
3709
  const response = await this._sendAcpRequest({
2516
3710
  tag: "AcpSetSessionConfigOptionRequest",
2517
3711
  val: {
@@ -2528,7 +3722,7 @@ export class AgentOs {
2528
3722
  options: JSON.parse(response.val.options),
2529
3723
  };
2530
3724
  }
2531
- async getSessionCapabilities(input) {
3725
+ async _getSessionCapabilities(input) {
2532
3726
  const response = await this._sendAcpRequest({
2533
3727
  tag: "AcpGetSessionCapabilitiesRequest",
2534
3728
  val: { sessionId: input?.sessionId ?? null },
@@ -2540,7 +3734,7 @@ export class AgentOs {
2540
3734
  ? null
2541
3735
  : normalizeSessionCapabilities(JSON.parse(response.val.capabilities));
2542
3736
  }
2543
- async getSessionAgentInfo(input) {
3737
+ async _getSessionAgentInfo(input) {
2544
3738
  const response = await this._sendAcpRequest({
2545
3739
  tag: "AcpGetSessionAgentInfoRequest",
2546
3740
  val: { sessionId: input?.sessionId ?? null },
@@ -2552,6 +3746,58 @@ export class AgentOs {
2552
3746
  ? null
2553
3747
  : JSON.parse(response.val.agentInfo);
2554
3748
  }
3749
+ /** @deprecated Use `sessions.open()`. */
3750
+ openSession(input) {
3751
+ return this.sessions.open(input);
3752
+ }
3753
+ /** @deprecated Use `sessions.get()`. */
3754
+ getSession(input) {
3755
+ return this.sessions.get(input);
3756
+ }
3757
+ /** @deprecated Use `sessions.list()`. */
3758
+ listSessions(input) {
3759
+ return this.sessions.list(input);
3760
+ }
3761
+ /** @deprecated Use `sessions.delete()`. */
3762
+ deleteSession(input = {}) {
3763
+ return this.sessions.delete(input);
3764
+ }
3765
+ /** @deprecated Use `sessions.unload()`. */
3766
+ unloadSession(input) {
3767
+ return this.sessions.unload(input);
3768
+ }
3769
+ /** @deprecated Use `sessions.prompt()`. */
3770
+ prompt(input) {
3771
+ return this.sessions.prompt(input);
3772
+ }
3773
+ /** @deprecated Use `sessions.cancelPrompt()`. */
3774
+ cancelPrompt(input) {
3775
+ return this.sessions.cancelPrompt(input);
3776
+ }
3777
+ /** @deprecated Use `sessions.respondPermission()`. */
3778
+ respondPermission(input) {
3779
+ return this.sessions.respondPermission(input);
3780
+ }
3781
+ /** @deprecated Use `sessions.readHistory()`. */
3782
+ readHistory(input) {
3783
+ return this.sessions.readHistory(input);
3784
+ }
3785
+ /** @deprecated Use `sessions.getConfig()`. */
3786
+ getSessionConfig(input) {
3787
+ return this.sessions.getConfig(input);
3788
+ }
3789
+ /** @deprecated Use `sessions.setConfigOption()`. */
3790
+ setSessionConfigOption(input) {
3791
+ return this.sessions.setConfigOption(input);
3792
+ }
3793
+ /** @deprecated Use `sessions.getCapabilities()`. */
3794
+ getSessionCapabilities(input) {
3795
+ return this.sessions.getCapabilities(input);
3796
+ }
3797
+ /** @deprecated Use `sessions.getAgentInfo()`. */
3798
+ getSessionAgentInfo(input) {
3799
+ return this.sessions.getAgentInfo(input);
3800
+ }
2555
3801
  /**
2556
3802
  * Dynamically link a software package into the RUNNING VM. The package's
2557
3803
  * `bin/` commands appear under `/opt/agentos/bin` (on `$PATH`) and its `share/man`
@@ -2560,7 +3806,7 @@ export class AgentOs {
2560
3806
  * block registers the package for `openSession({ agent: name })`. Persists for the VM's
2561
3807
  * lifetime (and across a snapshot iff the volume persists).
2562
3808
  */
2563
- async linkSoftware(descriptor) {
3809
+ async _linkSoftware(descriptor) {
2564
3810
  // Forward to the sidecar, which owns the `/opt/agentos` projection and
2565
3811
  // appends the package to its live host-backed staging dir; the commands
2566
3812
  // appear under `/opt/agentos/bin` immediately. The sidecar rejects a
@@ -2580,7 +3826,7 @@ export class AgentOs {
2580
3826
  // picked up by the sidecar (it owns the projected `/opt/agentos` and answers
2581
3827
  // openSession/listAgents from it). Nothing to record client-side.
2582
3828
  }
2583
- async listSoftware() {
3829
+ async _listSoftware() {
2584
3830
  return this._sidecarClient.providedCommands(this._sidecarSession, this._sidecarVm);
2585
3831
  }
2586
3832
  /**
@@ -2589,7 +3835,7 @@ export class AgentOs {
2589
3835
  * projected `/opt/agentos` packages (the client parses no manifests). Every such
2590
3836
  * agent is a package materialized into the VM, so `installed` is always `true`.
2591
3837
  */
2592
- async listAgents() {
3838
+ async _listAgents() {
2593
3839
  const response = await this._sendAcpRequest({
2594
3840
  tag: "AcpListAgentsRequest",
2595
3841
  val: { reserved: false },
@@ -2602,6 +3848,18 @@ export class AgentOs {
2602
3848
  installed: agent.installed,
2603
3849
  }));
2604
3850
  }
3851
+ /** @deprecated Use `software.link()`. */
3852
+ linkSoftware(descriptor) {
3853
+ return this.software.link(descriptor);
3854
+ }
3855
+ /** @deprecated Use `software.list()`. */
3856
+ listSoftware() {
3857
+ return this.software.list();
3858
+ }
3859
+ /** @deprecated Use `agents.list()`. */
3860
+ listAgents() {
3861
+ return this.agents.list();
3862
+ }
2605
3863
  _recordAgentStderr(event) {
2606
3864
  if (!event.sessionId) {
2607
3865
  return;
@@ -2655,6 +3913,81 @@ export class AgentOs {
2655
3913
  }
2656
3914
  }
2657
3915
  _handleSidecarEvent(event) {
3916
+ if (event.payload.type === "execution_output") {
3917
+ const output = mapExecutionOutputEvent(event.payload.event);
3918
+ const pid = this._languageProcessIds.get(output.executionId);
3919
+ if (pid !== undefined) {
3920
+ const process = this._languageProcesses.get(pid);
3921
+ if (process) {
3922
+ const publicOutput = {
3923
+ pid,
3924
+ stream: output.channel === "stderr" ? "stderr" : "stdout",
3925
+ data: output.chunk,
3926
+ };
3927
+ for (const handler of process.outputHandlers) {
3928
+ try {
3929
+ handler(publicOutput);
3930
+ }
3931
+ catch (error) {
3932
+ console.error("AgentOS process output handler failed", error);
3933
+ }
3934
+ }
3935
+ }
3936
+ }
3937
+ for (const key of ["*", output.executionId]) {
3938
+ for (const handler of this._executionOutputHandlers.get(key) ?? []) {
3939
+ try {
3940
+ handler(output);
3941
+ }
3942
+ catch (error) {
3943
+ console.error("AgentOS execution output handler failed", error);
3944
+ }
3945
+ }
3946
+ }
3947
+ return;
3948
+ }
3949
+ if (event.payload.type === "execution_completed") {
3950
+ const completed = mapExecutionCompletedEvent(event.payload.event);
3951
+ const pid = this._languageProcessIds.get(completed.executionId);
3952
+ if (pid !== undefined) {
3953
+ const process = this._languageProcesses.get(pid);
3954
+ if (process && !process.exit) {
3955
+ const exit = {
3956
+ pid,
3957
+ outcome: completed.outcome === "timed_out"
3958
+ ? "timed_out"
3959
+ : completed.outcome === "cancelled"
3960
+ ? "signalled"
3961
+ : "exited",
3962
+ ...(completed.exitCode !== undefined
3963
+ ? { exitCode: completed.exitCode }
3964
+ : {}),
3965
+ ...(process.signal ? { signal: process.signal } : {}),
3966
+ };
3967
+ process.exit = exit;
3968
+ process.descriptor = { ...process.descriptor, state: "exited" };
3969
+ for (const handler of process.exitHandlers) {
3970
+ try {
3971
+ handler(exit);
3972
+ }
3973
+ catch (error) {
3974
+ console.error("AgentOS process exit handler failed", error);
3975
+ }
3976
+ }
3977
+ }
3978
+ }
3979
+ for (const key of ["*", completed.executionId]) {
3980
+ for (const handler of this._executionCompletedHandlers.get(key) ?? []) {
3981
+ try {
3982
+ handler(completed);
3983
+ }
3984
+ catch (error) {
3985
+ console.error("AgentOS execution completion handler failed", error);
3986
+ }
3987
+ }
3988
+ }
3989
+ return;
3990
+ }
2658
3991
  if (event.payload.type === "ext") {
2659
3992
  this._handleAcpExtEvent(event.payload.envelope);
2660
3993
  return;
@@ -3056,12 +4389,12 @@ export class AgentOs {
3056
4389
  this._acpTerminals.set(terminalId, terminal);
3057
4390
  return { terminalId };
3058
4391
  }
3059
- _handleAcpWriteTerminal(params) {
4392
+ async _handleAcpWriteTerminal(params) {
3060
4393
  const method = "terminal/write";
3061
4394
  const terminal = this._requireAcpTerminal(params, method);
3062
4395
  const data = this._requireAcpStringParam(params, "data", method);
3063
4396
  const encoding = this._optionalAcpStringParam(params, "encoding", method);
3064
- terminal.handle.write(encoding === "base64" ? Buffer.from(data, "base64") : data);
4397
+ await terminal.handle.write(encoding === "base64" ? Buffer.from(data, "base64") : data);
3065
4398
  return null;
3066
4399
  }
3067
4400
  _handleAcpReadTerminal(params) {
@@ -3151,17 +4484,29 @@ export class AgentOs {
3151
4484
  }
3152
4485
  // ── Cron ────────────────────────────────────────────────────
3153
4486
  /** Schedule a cron job. Returns a handle with the job ID and a cancel method. */
3154
- scheduleCron(options) {
4487
+ _scheduleCron(options) {
3155
4488
  return this._cronManager.schedule(options);
3156
4489
  }
3157
4490
  /** List all registered cron jobs. */
3158
- listCronJobs() {
4491
+ _listCronJobs() {
3159
4492
  return this._cronManager.list();
3160
4493
  }
3161
4494
  /** Cancel a cron job by ID. */
3162
- cancelCronJob(id) {
4495
+ _cancelCronJob(id) {
3163
4496
  this._cronManager.cancel(id);
3164
4497
  }
4498
+ /** @deprecated Use `cron.schedule()`. */
4499
+ scheduleCron(options) {
4500
+ return this.cron.schedule(options);
4501
+ }
4502
+ /** @deprecated Use `cron.list()`. */
4503
+ listCronJobs() {
4504
+ return this.cron.list();
4505
+ }
4506
+ /** @deprecated Use `cron.cancel()`. */
4507
+ cancelCronJob(id) {
4508
+ this.cron.cancel(id);
4509
+ }
3165
4510
  /** Subscribe to cron lifecycle events (fire, complete, error). */
3166
4511
  onCronEvent(handler) {
3167
4512
  this._cronManager.onEvent(handler);
@@ -3180,6 +4525,8 @@ export class AgentOs {
3180
4525
  }
3181
4526
  this._acpTerminals.clear();
3182
4527
  this._processes.clear();
4528
+ this._executionOutputHandlers.clear();
4529
+ this._executionCompletedHandlers.clear();
3183
4530
  await waitForTrackedExitPromises([...shellExitPromises, ...terminalExitPromises], SHELL_DISPOSE_TIMEOUT_MS);
3184
4531
  this._disposeSidecarEventListener();
3185
4532
  const sidecarLease = this._sidecarLease;