@agent-vm/agent-vm 0.0.78 → 0.0.79

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.
Files changed (75) hide show
  1. package/dist/cli/commands/controller-definition.d.ts.map +1 -1
  2. package/dist/cli/commands/controller-definition.js +7 -3
  3. package/dist/cli/commands/controller-definition.js.map +1 -1
  4. package/dist/cli/manual-templates.d.ts.map +1 -1
  5. package/dist/cli/manual-templates.js +7 -3
  6. package/dist/cli/manual-templates.js.map +1 -1
  7. package/dist/controller/controller-runtime-types.d.ts +4 -0
  8. package/dist/controller/controller-runtime-types.d.ts.map +1 -1
  9. package/dist/controller/controller-runtime.d.ts.map +1 -1
  10. package/dist/controller/controller-runtime.js +28 -3
  11. package/dist/controller/controller-runtime.js.map +1 -1
  12. package/dist/controller/http/controller-http-route-support.d.ts +12 -0
  13. package/dist/controller/http/controller-http-route-support.d.ts.map +1 -1
  14. package/dist/controller/http/controller-http-route-support.js +12 -0
  15. package/dist/controller/http/controller-http-route-support.js.map +1 -1
  16. package/dist/controller/http/controller-http-routes.d.ts +4 -1
  17. package/dist/controller/http/controller-http-routes.d.ts.map +1 -1
  18. package/dist/controller/http/controller-http-routes.js +75 -24
  19. package/dist/controller/http/controller-http-routes.js.map +1 -1
  20. package/dist/controller/http/controller-lease-response-types.d.ts +1 -0
  21. package/dist/controller/http/controller-lease-response-types.d.ts.map +1 -1
  22. package/dist/controller/http/controller-lease-response-types.js +1 -0
  23. package/dist/controller/http/controller-lease-response-types.js.map +1 -1
  24. package/dist/controller/http/controller-request-schemas.d.ts +1 -0
  25. package/dist/controller/http/controller-request-schemas.d.ts.map +1 -1
  26. package/dist/controller/http/controller-request-schemas.js +15 -1
  27. package/dist/controller/http/controller-request-schemas.js.map +1 -1
  28. package/dist/controller/http/controller-zone-operation-routes.d.ts +4 -2
  29. package/dist/controller/http/controller-zone-operation-routes.d.ts.map +1 -1
  30. package/dist/controller/http/controller-zone-operation-routes.js +50 -1
  31. package/dist/controller/http/controller-zone-operation-routes.js.map +1 -1
  32. package/dist/controller/leases/lease-manager.d.ts +17 -1
  33. package/dist/controller/leases/lease-manager.d.ts.map +1 -1
  34. package/dist/controller/leases/lease-manager.js +133 -34
  35. package/dist/controller/leases/lease-manager.js.map +1 -1
  36. package/dist/controller/leases/tcp-pool.d.ts +3 -0
  37. package/dist/controller/leases/tcp-pool.d.ts.map +1 -1
  38. package/dist/controller/leases/tcp-pool.js +12 -1
  39. package/dist/controller/leases/tcp-pool.js.map +1 -1
  40. package/dist/controller/leases/tool-vm-recovery.d.ts +31 -0
  41. package/dist/controller/leases/tool-vm-recovery.d.ts.map +1 -0
  42. package/dist/controller/leases/tool-vm-recovery.js +178 -0
  43. package/dist/controller/leases/tool-vm-recovery.js.map +1 -0
  44. package/dist/controller/leases/tool-vm-runtime-record.d.ts +59 -0
  45. package/dist/controller/leases/tool-vm-runtime-record.d.ts.map +1 -0
  46. package/dist/controller/leases/tool-vm-runtime-record.js +159 -0
  47. package/dist/controller/leases/tool-vm-runtime-record.js.map +1 -0
  48. package/dist/gateway/gateway-recovery.d.ts +8 -5
  49. package/dist/gateway/gateway-recovery.d.ts.map +1 -1
  50. package/dist/gateway/gateway-recovery.js +63 -123
  51. package/dist/gateway/gateway-recovery.js.map +1 -1
  52. package/dist/gateway/gateway-runtime-record.d.ts +24 -16
  53. package/dist/gateway/gateway-runtime-record.d.ts.map +1 -1
  54. package/dist/gateway/gateway-runtime-record.js +53 -68
  55. package/dist/gateway/gateway-runtime-record.js.map +1 -1
  56. package/dist/gateway/gateway-zone-orchestrator.d.ts +6 -0
  57. package/dist/gateway/gateway-zone-orchestrator.d.ts.map +1 -1
  58. package/dist/gateway/gateway-zone-orchestrator.js +157 -40
  59. package/dist/gateway/gateway-zone-orchestrator.js.map +1 -1
  60. package/dist/operations/controller-offline-cleanup.d.ts +3 -0
  61. package/dist/operations/controller-offline-cleanup.d.ts.map +1 -1
  62. package/dist/operations/controller-offline-cleanup.js +16 -6
  63. package/dist/operations/controller-offline-cleanup.js.map +1 -1
  64. package/dist/operations/destroy-zone.d.ts.map +1 -1
  65. package/dist/operations/destroy-zone.js +5 -0
  66. package/dist/operations/destroy-zone.js.map +1 -1
  67. package/dist/shared/managed-vm-process.d.ts +33 -0
  68. package/dist/shared/managed-vm-process.d.ts.map +1 -0
  69. package/dist/shared/managed-vm-process.js +248 -0
  70. package/dist/shared/managed-vm-process.js.map +1 -0
  71. package/dist/shared/port-owner.d.ts +18 -0
  72. package/dist/shared/port-owner.d.ts.map +1 -0
  73. package/dist/shared/port-owner.js +60 -0
  74. package/dist/shared/port-owner.js.map +1 -0
  75. package/package.json +11 -11
@@ -0,0 +1,248 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ export function isProcessAlive(pid) {
5
+ try {
6
+ process.kill(pid, 0);
7
+ return true;
8
+ }
9
+ catch (error) {
10
+ if (typeof error === 'object' && error !== null && 'code' in error) {
11
+ if (error.code === 'EPERM') {
12
+ return true;
13
+ }
14
+ if (error.code === 'ESRCH') {
15
+ return false;
16
+ }
17
+ }
18
+ throw error;
19
+ }
20
+ }
21
+ export async function readProcessCommand(pid) {
22
+ try {
23
+ const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'command=']);
24
+ const command = stdout.trim();
25
+ return command.length > 0 ? command : null;
26
+ }
27
+ catch (error) {
28
+ if (typeof error === 'object' && error !== null && 'code' in error && error.code === 1) {
29
+ return null;
30
+ }
31
+ throw error;
32
+ }
33
+ }
34
+ export async function readProcessIdentity(pid) {
35
+ try {
36
+ // `lstart` and `command` are separated by whitespace by default. Use a
37
+ // custom delimiter via two separate columns so we can split safely.
38
+ // macOS + Linux both support `-o lstart=,command=`.
39
+ const { stdout } = await execFileAsync('ps', [
40
+ '-p',
41
+ String(pid),
42
+ '-o',
43
+ 'lstart=',
44
+ '-o',
45
+ 'command=',
46
+ ]);
47
+ const trimmed = stdout.trim();
48
+ if (trimmed.length === 0) {
49
+ return null;
50
+ }
51
+ return parseProcessIdentityOutput(trimmed);
52
+ }
53
+ catch (error) {
54
+ if (typeof error === 'object' && error !== null && 'code' in error && error.code === 1) {
55
+ return null;
56
+ }
57
+ throw error;
58
+ }
59
+ }
60
+ export function parseProcessIdentityOutput(line) {
61
+ const splitIndex = findLstartCommandBoundary(line.trim());
62
+ if (splitIndex === null) {
63
+ return null;
64
+ }
65
+ const lstart = line.slice(0, splitIndex).trim();
66
+ const command = line.slice(splitIndex).trim();
67
+ if (lstart.length === 0 || command.length === 0) {
68
+ return null;
69
+ }
70
+ return { command, lstart };
71
+ }
72
+ function findLstartCommandBoundary(line) {
73
+ // `lstart` is "Day Mon DD HH:MM:SS YYYY" — exactly five
74
+ // whitespace-separated tokens. Find the end of the fifth token; everything
75
+ // after is `command`.
76
+ let tokenCount = 0;
77
+ let inToken = false;
78
+ for (let index = 0; index < line.length; index += 1) {
79
+ const character = line[index];
80
+ const isSpace = character === ' ' || character === '\t';
81
+ if (!isSpace && !inToken) {
82
+ inToken = true;
83
+ }
84
+ else if (isSpace && inToken) {
85
+ inToken = false;
86
+ tokenCount += 1;
87
+ if (tokenCount === 5) {
88
+ return index;
89
+ }
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ // Gateway VMs and Tool VMs are both backed by the same Gondolin runtime
95
+ // (qemu-system on the QEMU backend or krun on the libkrun backend). Cleanup
96
+ // flows MUST verify the recorded host PID still maps to one of these process
97
+ // commands before signaling, so a recycled PID belonging to an unrelated
98
+ // program is never killed by mistake.
99
+ export function isManagedVmProcess(command) {
100
+ return /\b(qemu-system|krun)\b/u.test(command);
101
+ }
102
+ export function processIdentityMatches(recorded, current) {
103
+ return recorded.lstart === current.lstart && recorded.command === current.command;
104
+ }
105
+ export function killProcess(pid, signal) {
106
+ try {
107
+ process.kill(pid, signal);
108
+ }
109
+ catch (error) {
110
+ if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ESRCH') {
111
+ return;
112
+ }
113
+ if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'EPERM') {
114
+ throw new Error(`Permission denied while sending ${signal} to managed VM pid ${pid}. The process is still running and may require elevated privileges to terminate.`, { cause: error });
115
+ }
116
+ throw error;
117
+ }
118
+ }
119
+ export function isNoSuchProcessError(error) {
120
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ESRCH';
121
+ }
122
+ export async function sleep(delayMs) {
123
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
124
+ }
125
+ export async function waitForExit(options) {
126
+ const deadline = Date.now() + options.timeoutMs;
127
+ while (Date.now() < deadline) {
128
+ if (!options.processIsAlive(options.pid)) {
129
+ return true;
130
+ }
131
+ // oxlint-disable-next-line no-await-in-loop -- polling loop must wait between liveness checks
132
+ await options.sleep(100);
133
+ }
134
+ return !options.processIsAlive(options.pid);
135
+ }
136
+ // Re-verify the recorded process identity (lstart + command) immediately before
137
+ // sending a signal. Returns `null` if the process is gone (signal not needed),
138
+ // otherwise either resolves to the matching identity or throws to refuse the
139
+ // signal because PID was reused by an unrelated process.
140
+ async function verifyIdentityBeforeSignal(options) {
141
+ if (options.recordedIdentity !== undefined && options.readProcessIdentity !== undefined) {
142
+ const currentIdentity = await options.readProcessIdentity(options.pid);
143
+ if (currentIdentity === null) {
144
+ return { proceed: false };
145
+ }
146
+ if (!processIdentityMatches(options.recordedIdentity, currentIdentity)) {
147
+ throw new Error(`${options.contextLabel} refusing ${options.currentSignalLabel} to pid ${options.pid}: process identity changed (recorded ${JSON.stringify(options.recordedIdentity)}, current ${JSON.stringify(currentIdentity)}). PID was likely reused.`);
148
+ }
149
+ if (!isManagedVmProcess(currentIdentity.command)) {
150
+ throw new Error(`${options.contextLabel} refusing ${options.currentSignalLabel} to pid ${options.pid}: current command is not a managed VM process: ${currentIdentity.command}.`);
151
+ }
152
+ return { proceed: true };
153
+ }
154
+ // No recorded identity (legacy record) → fall back to the looser command-
155
+ // only check at the SIGTERM point. Subsequent signals trust the first
156
+ // check. This is the pre-identity behavior.
157
+ const command = await options.readProcessCommand(options.pid);
158
+ if (command === null) {
159
+ return { proceed: false };
160
+ }
161
+ if (!isManagedVmProcess(command)) {
162
+ throw new Error(`${options.contextLabel} points at unexpected live process ${options.pid}: ${command}.`);
163
+ }
164
+ return { proceed: true };
165
+ }
166
+ // Generic "kill an orphaned managed VM process" with a SIGTERM→2s→SIGKILL→2s
167
+ // bounded sequence (total ≤ 4 s). The caller is responsible for owning the
168
+ // runtime record (read + fence-check + delete after this returns).
169
+ //
170
+ // Strong PID identity defense: if `recordedIdentity` is supplied, the live
171
+ // process identity (ps `lstart` + `command`) is re-read IMMEDIATELY before
172
+ // each signal and must match exactly. This makes PID reuse during the
173
+ // read-record → signal window detectable; we refuse rather than killing the
174
+ // wrong process.
175
+ //
176
+ // Returns the PID that was signalled, or null if it was already dead before
177
+ // the first signal landed.
178
+ //
179
+ // Throws if:
180
+ // - the recorded PID is alive but its identity does not match (PID reused)
181
+ // - the recorded PID is alive but its command is NOT a managed VM process
182
+ // - the process survives both SIGTERM and SIGKILL (a stuck D-state QEMU)
183
+ export async function killOrphanedManagedVmProcess(options) {
184
+ const { contextLabel, dependencies, pid, recordedIdentity } = options;
185
+ if (!dependencies.isProcessAlive(pid)) {
186
+ return null;
187
+ }
188
+ const beforeTerm = await verifyIdentityBeforeSignal({
189
+ contextLabel,
190
+ currentSignalLabel: 'SIGTERM',
191
+ pid,
192
+ readProcessCommand: dependencies.readProcessCommand,
193
+ ...(dependencies.readProcessIdentity !== undefined
194
+ ? { readProcessIdentity: dependencies.readProcessIdentity }
195
+ : {}),
196
+ ...(recordedIdentity !== undefined ? { recordedIdentity } : {}),
197
+ });
198
+ if (!beforeTerm.proceed) {
199
+ return null;
200
+ }
201
+ try {
202
+ dependencies.killProcess(pid, 'SIGTERM');
203
+ }
204
+ catch (error) {
205
+ if (!isNoSuchProcessError(error)) {
206
+ throw error;
207
+ }
208
+ }
209
+ if (await waitForExit({
210
+ pid,
211
+ processIsAlive: dependencies.isProcessAlive,
212
+ sleep: dependencies.sleep,
213
+ timeoutMs: 2_000,
214
+ })) {
215
+ return pid;
216
+ }
217
+ const beforeKill = await verifyIdentityBeforeSignal({
218
+ contextLabel,
219
+ currentSignalLabel: 'SIGKILL',
220
+ pid,
221
+ readProcessCommand: dependencies.readProcessCommand,
222
+ ...(dependencies.readProcessIdentity !== undefined
223
+ ? { readProcessIdentity: dependencies.readProcessIdentity }
224
+ : {}),
225
+ ...(recordedIdentity !== undefined ? { recordedIdentity } : {}),
226
+ });
227
+ if (!beforeKill.proceed) {
228
+ return pid;
229
+ }
230
+ try {
231
+ dependencies.killProcess(pid, 'SIGKILL');
232
+ }
233
+ catch (error) {
234
+ if (!isNoSuchProcessError(error)) {
235
+ throw error;
236
+ }
237
+ }
238
+ if (await waitForExit({
239
+ pid,
240
+ processIsAlive: dependencies.isProcessAlive,
241
+ sleep: dependencies.sleep,
242
+ timeoutMs: 2_000,
243
+ })) {
244
+ return pid;
245
+ }
246
+ throw new Error(`Failed to terminate orphaned managed VM process ${pid} (${contextLabel}).`);
247
+ }
248
+ //# sourceMappingURL=managed-vm-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-vm-process.js","sourceRoot":"","sources":["../../src/shared/managed-vm-process.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export interface PortOwner {
2
+ readonly command: string;
3
+ readonly pid: number;
4
+ }
5
+ export interface ExecFileOutput {
6
+ readonly stderr: string;
7
+ readonly stdout: string;
8
+ }
9
+ export type ExecFileFunction = (file: string, args: readonly string[]) => Promise<ExecFileOutput>;
10
+ export interface ReadTcpListenPortOwnerDependencies {
11
+ readonly execFile?: ExecFileFunction;
12
+ }
13
+ export declare class PortOwnerDependencyError extends Error {
14
+ constructor(message: string, options?: ErrorOptions);
15
+ }
16
+ export declare function parseLsofPortOwnerOutput(output: string): PortOwner | null;
17
+ export declare function readTcpListenPortOwner(port: number, dependencies?: ReadTcpListenPortOwnerDependencies): Promise<PortOwner | null>;
18
+ //# sourceMappingURL=port-owner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-owner.d.ts","sourceRoot":"","sources":["../../src/shared/port-owner.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAElG,MAAM,WAAW,kCAAkC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,qBAAa,wBAAyB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAI1D;AAUD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAmBzE;AAED,wBAAsB,sBAAsB,CAC3C,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,kCAAuC,GACnD,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAwB3B"}
@@ -0,0 +1,60 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ export class PortOwnerDependencyError extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = 'PortOwnerDependencyError';
8
+ }
9
+ }
10
+ function errorCode(error) {
11
+ if (typeof error !== 'object' || error === null || !('code' in error)) {
12
+ return undefined;
13
+ }
14
+ const code = error.code;
15
+ return typeof code === 'string' || typeof code === 'number' ? code : undefined;
16
+ }
17
+ export function parseLsofPortOwnerOutput(output) {
18
+ const lines = output
19
+ .split('\n')
20
+ .map((line) => line.trim())
21
+ .filter((line) => line.length > 0);
22
+ const pidLine = lines.find((line) => line.startsWith('p'));
23
+ const commandLine = lines.find((line) => line.startsWith('c'));
24
+ if (!pidLine || !commandLine) {
25
+ return null;
26
+ }
27
+ const pid = Number.parseInt(pidLine.slice(1), 10);
28
+ if (!Number.isInteger(pid) || pid <= 0) {
29
+ return null;
30
+ }
31
+ const command = commandLine.slice(1);
32
+ if (command.length === 0) {
33
+ return null;
34
+ }
35
+ return { command, pid };
36
+ }
37
+ export async function readTcpListenPortOwner(port, dependencies = {}) {
38
+ const runExecFile = dependencies.execFile ?? execFileAsync;
39
+ try {
40
+ const { stdout } = await runExecFile('lsof', [
41
+ '-nP',
42
+ `-iTCP:${String(port)}`,
43
+ '-sTCP:LISTEN',
44
+ '-F',
45
+ 'pc',
46
+ ]);
47
+ return parseLsofPortOwnerOutput(stdout);
48
+ }
49
+ catch (error) {
50
+ const code = errorCode(error);
51
+ if (code === 1) {
52
+ return null;
53
+ }
54
+ if (code === 'ENOENT') {
55
+ throw new PortOwnerDependencyError(`Tool VM/gateway recovery requires 'lsof' on PATH to verify TCP listener ownership for port ${String(port)}.`, { cause: error });
56
+ }
57
+ throw error;
58
+ }
59
+ }
60
+ //# sourceMappingURL=port-owner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-owner.js","sourceRoot":"","sources":["../../src/shared/port-owner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-vm/agent-vm",
3
- "version": "0.0.78",
3
+ "version": "0.0.79",
4
4
  "description": "Controller CLI and HTTP server for sandboxed VM coding agents.",
5
5
  "homepage": "https://github.com/ShravanSunder/agent-vm#readme",
6
6
  "bugs": {
@@ -40,16 +40,16 @@
40
40
  "jsonc-parser": "^3.3.1",
41
41
  "tasuku": "^2.3.0",
42
42
  "zod": "^4.4.3",
43
- "@agent-vm/agent-vm-worker": "0.0.78",
44
- "@agent-vm/gondolin-adapter": "0.0.78",
45
- "@agent-vm/gateway-interface": "0.0.78",
46
- "@agent-vm/openclaw-gateway": "0.0.78",
47
- "@agent-vm/openclaw-mcp-portal-plugin": "0.0.78",
48
- "@agent-vm/secret-management": "0.0.78",
49
- "@agent-vm/worker-gateway": "0.0.78",
50
- "@agent-vm/config-contracts": "0.0.78",
51
- "@agent-vm/mcp-portal": "0.0.78",
52
- "@agent-vm/openclaw-agent-vm-plugin": "0.0.78"
43
+ "@agent-vm/agent-vm-worker": "0.0.79",
44
+ "@agent-vm/config-contracts": "0.0.79",
45
+ "@agent-vm/gateway-interface": "0.0.79",
46
+ "@agent-vm/gondolin-adapter": "0.0.79",
47
+ "@agent-vm/mcp-portal": "0.0.79",
48
+ "@agent-vm/openclaw-gateway": "0.0.79",
49
+ "@agent-vm/openclaw-mcp-portal-plugin": "0.0.79",
50
+ "@agent-vm/secret-management": "0.0.79",
51
+ "@agent-vm/worker-gateway": "0.0.79",
52
+ "@agent-vm/openclaw-agent-vm-plugin": "0.0.79"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "rm -rf dist && tsc -p tsconfig.build.json",