agent-tower 0.6.0-beta.11 → 0.6.0-beta.13
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/app.d.ts.map +1 -1
- package/dist/app.js +4 -0
- package/dist/app.js.map +1 -1
- package/dist/cli.js +39 -19
- package/dist/cli.js.map +1 -1
- package/dist/executors/__tests__/base.executor.test.js +133 -1
- package/dist/executors/__tests__/base.executor.test.js.map +1 -1
- package/dist/executors/__tests__/stdin-prompt.executors.test.js +18 -0
- package/dist/executors/__tests__/stdin-prompt.executors.test.js.map +1 -1
- package/dist/executors/base.executor.d.ts +8 -0
- package/dist/executors/base.executor.d.ts.map +1 -1
- package/dist/executors/base.executor.js +336 -56
- package/dist/executors/base.executor.js.map +1 -1
- package/dist/executors/index.d.ts +2 -1
- package/dist/executors/index.d.ts.map +1 -1
- package/dist/executors/index.js +1 -1
- package/dist/executors/index.js.map +1 -1
- package/dist/executors/start-error.d.ts +17 -0
- package/dist/executors/start-error.d.ts.map +1 -1
- package/dist/executors/start-error.js +36 -1
- package/dist/executors/start-error.js.map +1 -1
- package/dist/index.js +39 -29
- package/dist/index.js.map +1 -1
- package/dist/mcp/http-client.test.js +20 -0
- package/dist/mcp/http-client.test.js.map +1 -1
- package/dist/mcp/tools/__tests__/sessions.test.d.ts +2 -0
- package/dist/mcp/tools/__tests__/sessions.test.d.ts.map +1 -0
- package/dist/mcp/tools/__tests__/sessions.test.js +33 -0
- package/dist/mcp/tools/__tests__/sessions.test.js.map +1 -0
- package/dist/routes/__tests__/sessions-runtime.test.js +75 -3
- package/dist/routes/__tests__/sessions-runtime.test.js.map +1 -1
- package/dist/routes/conversations.d.ts.map +1 -1
- package/dist/routes/conversations.js +5 -1
- package/dist/routes/conversations.js.map +1 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +13 -2
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/team-runs.d.ts.map +1 -1
- package/dist/routes/team-runs.js +13 -1
- package/dist/routes/team-runs.js.map +1 -1
- package/dist/runtime/__tests__/acp-driver-lifecycle.test.js +86 -0
- package/dist/runtime/__tests__/acp-driver-lifecycle.test.js.map +1 -1
- package/dist/runtime/__tests__/acp-process-manager.test.js +393 -6
- package/dist/runtime/__tests__/acp-process-manager.test.js.map +1 -1
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.d.ts +2 -0
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.js +244 -0
- package/dist/runtime/__tests__/cli-driver-lifecycle.test.js.map +1 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.d.ts +2 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.js +154 -0
- package/dist/runtime/__tests__/launch-cleanup-registry.test.js.map +1 -0
- package/dist/runtime/__tests__/runtime-coordinator.test.js +117 -1
- package/dist/runtime/__tests__/runtime-coordinator.test.js.map +1 -1
- package/dist/runtime/__tests__/server-entry-shutdown.test.d.ts +2 -0
- package/dist/runtime/__tests__/server-entry-shutdown.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/server-entry-shutdown.test.js +38 -0
- package/dist/runtime/__tests__/server-entry-shutdown.test.js.map +1 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.d.ts +2 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.js +28 -0
- package/dist/runtime/__tests__/shutdown-coordinator.test.js.map +1 -0
- package/dist/runtime/acp/acp-driver.d.ts.map +1 -1
- package/dist/runtime/acp/acp-driver.js +108 -35
- package/dist/runtime/acp/acp-driver.js.map +1 -1
- package/dist/runtime/acp/agents/managed-directory.d.ts.map +1 -1
- package/dist/runtime/acp/agents/managed-directory.js +3 -1
- package/dist/runtime/acp/agents/managed-directory.js.map +1 -1
- package/dist/runtime/acp/agents/managed-directory.test.d.ts +2 -0
- package/dist/runtime/acp/agents/managed-directory.test.d.ts.map +1 -0
- package/dist/runtime/acp/agents/managed-directory.test.js +25 -0
- package/dist/runtime/acp/agents/managed-directory.test.js.map +1 -0
- package/dist/runtime/acp/launch-cleanup-registry.d.ts +32 -0
- package/dist/runtime/acp/launch-cleanup-registry.d.ts.map +1 -0
- package/dist/runtime/acp/launch-cleanup-registry.js +176 -0
- package/dist/runtime/acp/launch-cleanup-registry.js.map +1 -0
- package/dist/runtime/acp/process-manager.d.ts +58 -0
- package/dist/runtime/acp/process-manager.d.ts.map +1 -1
- package/dist/runtime/acp/process-manager.js +489 -33
- package/dist/runtime/acp/process-manager.js.map +1 -1
- package/dist/runtime/cli-driver.d.ts.map +1 -1
- package/dist/runtime/cli-driver.js +230 -46
- package/dist/runtime/cli-driver.js.map +1 -1
- package/dist/runtime/contracts.d.ts +21 -2
- package/dist/runtime/contracts.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/runtime-coordinator.d.ts +8 -1
- package/dist/runtime/runtime-coordinator.d.ts.map +1 -1
- package/dist/runtime/runtime-coordinator.js +118 -18
- package/dist/runtime/runtime-coordinator.js.map +1 -1
- package/dist/runtime/server-entry-shutdown.d.ts +12 -0
- package/dist/runtime/server-entry-shutdown.d.ts.map +1 -0
- package/dist/runtime/server-entry-shutdown.js +23 -0
- package/dist/runtime/server-entry-shutdown.js.map +1 -0
- package/dist/runtime/shutdown-coordinator.d.ts +17 -0
- package/dist/runtime/shutdown-coordinator.d.ts.map +1 -0
- package/dist/runtime/shutdown-coordinator.js +67 -0
- package/dist/runtime/shutdown-coordinator.js.map +1 -0
- package/dist/services/__tests__/conversation.service.test.js +160 -0
- package/dist/services/__tests__/conversation.service.test.js.map +1 -1
- package/dist/services/__tests__/database-maintenance.service.test.js +97 -0
- package/dist/services/__tests__/database-maintenance.service.test.js.map +1 -1
- package/dist/services/__tests__/member-heartbeat-scheduler.test.js +6 -0
- package/dist/services/__tests__/member-heartbeat-scheduler.test.js.map +1 -1
- package/dist/services/__tests__/member-heartbeat.test.js +7 -11
- package/dist/services/__tests__/member-heartbeat.test.js.map +1 -1
- package/dist/services/__tests__/session-manager.lifecycle.test.js +402 -12
- package/dist/services/__tests__/session-manager.lifecycle.test.js.map +1 -1
- package/dist/services/__tests__/session-manager.team-run.test.js +422 -29
- package/dist/services/__tests__/session-manager.team-run.test.js.map +1 -1
- package/dist/services/__tests__/team-queue-recovery.integration.test.js +1 -0
- package/dist/services/__tests__/team-queue-recovery.integration.test.js.map +1 -1
- package/dist/services/__tests__/team-reconciler.service.test.js +1282 -4
- package/dist/services/__tests__/team-reconciler.service.test.js.map +1 -1
- package/dist/services/__tests__/team-run.service.test.js +161 -1
- package/dist/services/__tests__/team-run.service.test.js.map +1 -1
- package/dist/services/__tests__/team-scheduler.service.test.js +334 -6
- package/dist/services/__tests__/team-scheduler.service.test.js.map +1 -1
- package/dist/services/conversation.service.d.ts.map +1 -1
- package/dist/services/conversation.service.js +21 -2
- package/dist/services/conversation.service.js.map +1 -1
- package/dist/services/database-maintenance.service.d.ts.map +1 -1
- package/dist/services/database-maintenance.service.js +68 -17
- package/dist/services/database-maintenance.service.js.map +1 -1
- package/dist/services/member-heartbeat-scheduler.d.ts +6 -5
- package/dist/services/member-heartbeat-scheduler.d.ts.map +1 -1
- package/dist/services/member-heartbeat-scheduler.js +7 -5
- package/dist/services/member-heartbeat-scheduler.js.map +1 -1
- package/dist/services/session-manager.d.ts +124 -14
- package/dist/services/session-manager.d.ts.map +1 -1
- package/dist/services/session-manager.js +1035 -116
- package/dist/services/session-manager.js.map +1 -1
- package/dist/services/session-runtime-cleanup-gate.d.ts +25 -0
- package/dist/services/session-runtime-cleanup-gate.d.ts.map +1 -0
- package/dist/services/session-runtime-cleanup-gate.js +284 -0
- package/dist/services/session-runtime-cleanup-gate.js.map +1 -0
- package/dist/services/task.service.d.ts +2 -2
- package/dist/services/team-member-admission-barrier.d.ts +3 -0
- package/dist/services/team-member-admission-barrier.d.ts.map +1 -0
- package/dist/services/team-member-admission-barrier.js +26 -0
- package/dist/services/team-member-admission-barrier.js.map +1 -0
- package/dist/services/team-reconciler.service.d.ts +30 -7
- package/dist/services/team-reconciler.service.d.ts.map +1 -1
- package/dist/services/team-reconciler.service.js +528 -111
- package/dist/services/team-reconciler.service.js.map +1 -1
- package/dist/services/team-run.service.d.ts.map +1 -1
- package/dist/services/team-run.service.js +37 -13
- package/dist/services/team-run.service.js.map +1 -1
- package/dist/services/team-scheduler.service.d.ts +8 -6
- package/dist/services/team-scheduler.service.d.ts.map +1 -1
- package/dist/services/team-scheduler.service.js +323 -120
- package/dist/services/team-scheduler.service.js.map +1 -1
- package/dist/services/workspace-background-process-manager.d.ts.map +1 -1
- package/dist/services/workspace-background-process-manager.js +2 -5
- package/dist/services/workspace-background-process-manager.js.map +1 -1
- package/dist/services/workspace.service.d.ts +15 -8
- package/dist/services/workspace.service.d.ts.map +1 -1
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +27 -14
- package/dist/socket/index.js.map +1 -1
- package/dist/utils/__tests__/unix-process-identity.test.d.ts +2 -0
- package/dist/utils/__tests__/unix-process-identity.test.d.ts.map +1 -0
- package/dist/utils/__tests__/unix-process-identity.test.js +73 -0
- package/dist/utils/__tests__/unix-process-identity.test.js.map +1 -0
- package/dist/utils/error-log.d.ts +2 -0
- package/dist/utils/error-log.d.ts.map +1 -1
- package/dist/utils/error-log.js +40 -3
- package/dist/utils/error-log.js.map +1 -1
- package/dist/utils/error-log.test.js +14 -2
- package/dist/utils/error-log.test.js.map +1 -1
- package/dist/utils/process-launch.d.ts +2 -2
- package/dist/utils/process-launch.d.ts.map +1 -1
- package/dist/utils/process-launch.js +339 -79
- package/dist/utils/process-launch.js.map +1 -1
- package/dist/utils/process-launch.test.js +168 -10
- package/dist/utils/process-launch.test.js.map +1 -1
- package/dist/utils/spawned-process-identity.d.ts +7 -0
- package/dist/utils/spawned-process-identity.d.ts.map +1 -0
- package/dist/utils/spawned-process-identity.js +36 -0
- package/dist/utils/spawned-process-identity.js.map +1 -0
- package/dist/utils/tree-cleanup-channel.d.ts +18 -0
- package/dist/utils/tree-cleanup-channel.d.ts.map +1 -0
- package/dist/utils/tree-cleanup-channel.js +16 -0
- package/dist/utils/tree-cleanup-channel.js.map +1 -0
- package/dist/utils/tree-cleanup-channel.test.d.ts +2 -0
- package/dist/utils/tree-cleanup-channel.test.d.ts.map +1 -0
- package/dist/utils/tree-cleanup-channel.test.js +15 -0
- package/dist/utils/tree-cleanup-channel.test.js.map +1 -0
- package/dist/utils/unix-process-identity.d.ts +13 -1
- package/dist/utils/unix-process-identity.d.ts.map +1 -1
- package/dist/utils/unix-process-identity.js +146 -25
- package/dist/utils/unix-process-identity.js.map +1 -1
- package/node_modules/@agent-tower/shared/dist/types.d.ts +1 -0
- package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
- package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260824000000_add_execution_process_ownership/migration.sql +10 -0
- package/prisma/migrations/20260824010000_add_runtime_cleanup_recovery/migration.sql +10 -0
- package/prisma/migrations/20260824020000_add_runtime_launch_admission/migration.sql +72 -0
- package/prisma/migrations/20260826000000_add_conversation_turn_queue/migration.sql +19 -0
- package/prisma/schema.prisma +50 -7
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { Readable, Transform, Writable } from 'node:stream';
|
|
3
4
|
import { StringDecoder } from 'node:string_decoder';
|
|
4
5
|
import { AgentRuntimeError } from '../errors.js';
|
|
6
|
+
import { markPreChildProcessFailure } from '../../executors/start-error.js';
|
|
7
|
+
import { createUnixProcessIdentityAdapter, } from '../../utils/unix-process-identity.js';
|
|
8
|
+
import { acpLaunchCleanupRegistry } from './launch-cleanup-registry.js';
|
|
5
9
|
const MAX_STDOUT_LINE_BYTES = 1024 * 1024;
|
|
6
10
|
const MAX_STDERR_BYTES = 8 * 1024;
|
|
7
11
|
const DEFAULT_KILL_GRACE_MS = 2_000;
|
|
@@ -13,9 +17,23 @@ export class AcpProcessManager {
|
|
|
13
17
|
settled;
|
|
14
18
|
stopPromise;
|
|
15
19
|
stderr = '';
|
|
20
|
+
platform;
|
|
21
|
+
unixProcessAdapter;
|
|
22
|
+
windowsProcessAdapter;
|
|
23
|
+
ownershipToken = randomUUID();
|
|
24
|
+
rootIdentity;
|
|
25
|
+
rootWindowsIdentity;
|
|
26
|
+
ownedWindowsProcesses = new Map();
|
|
16
27
|
exitListeners = new Set();
|
|
17
|
-
|
|
28
|
+
cleanupOwnerId;
|
|
29
|
+
cleanupRetryPromise;
|
|
30
|
+
constructor(launch, options = {}) {
|
|
18
31
|
this.launch = launch;
|
|
32
|
+
this.platform = options.platform ?? process.platform;
|
|
33
|
+
this.unixProcessAdapter = options.unixProcessAdapter
|
|
34
|
+
?? createUnixProcessIdentityAdapter(this.platform);
|
|
35
|
+
this.windowsProcessAdapter = options.windowsProcessAdapter
|
|
36
|
+
?? createWindowsProcessTreeAdapter();
|
|
19
37
|
}
|
|
20
38
|
async start() {
|
|
21
39
|
if (this.child) {
|
|
@@ -25,14 +43,18 @@ export class AcpProcessManager {
|
|
|
25
43
|
try {
|
|
26
44
|
child = spawn(this.launch.command, this.launch.args, {
|
|
27
45
|
cwd: this.launch.cwd,
|
|
28
|
-
env:
|
|
29
|
-
|
|
46
|
+
env: {
|
|
47
|
+
...this.launch.env,
|
|
48
|
+
AGENT_TOWER_PROCESS_IDENTITY: this.ownershipToken,
|
|
49
|
+
AGENT_TOWER_PROCESS_BIRTH_MARKER: this.ownershipToken,
|
|
50
|
+
},
|
|
51
|
+
detached: this.platform !== 'win32',
|
|
30
52
|
shell: false,
|
|
31
53
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
32
54
|
});
|
|
33
55
|
}
|
|
34
56
|
catch (error) {
|
|
35
|
-
throw new AgentRuntimeError('spawn_failed', 'spawn', 'Could not start the ACP adapter', true, { cause: error });
|
|
57
|
+
throw markPreChildProcessFailure(new AgentRuntimeError('spawn_failed', 'spawn', 'Could not start the ACP adapter', true, { cause: error }));
|
|
36
58
|
}
|
|
37
59
|
this.child = child;
|
|
38
60
|
this.exitPromise = new Promise((resolve) => {
|
|
@@ -51,7 +73,7 @@ export class AcpProcessManager {
|
|
|
51
73
|
child.once('error', (error) => {
|
|
52
74
|
this.settle({ exitCode: null, signal: null, stderrExcerpt: this.stderr });
|
|
53
75
|
if (!spawned) {
|
|
54
|
-
reject(new AgentRuntimeError('spawn_failed', 'spawn', 'Could not start the ACP adapter', true, { cause: error }));
|
|
76
|
+
reject(markPreChildProcessFailure(new AgentRuntimeError('spawn_failed', 'spawn', 'Could not start the ACP adapter', true, { cause: error })));
|
|
55
77
|
}
|
|
56
78
|
});
|
|
57
79
|
child.once('close', (exitCode, signal) => {
|
|
@@ -59,17 +81,59 @@ export class AcpProcessManager {
|
|
|
59
81
|
});
|
|
60
82
|
});
|
|
61
83
|
if (!child.pid) {
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
return this.failAfterSpawn(new AgentRuntimeError('spawn_failed', 'spawn', 'ACP adapter did not report a process id', true));
|
|
85
|
+
}
|
|
86
|
+
// Attach the stdout validator before the identity probe. A short-lived
|
|
87
|
+
// adapter may exit while its launch identity is being sampled; attaching
|
|
88
|
+
// later would miss already-buffered protocol frames.
|
|
89
|
+
let output;
|
|
90
|
+
try {
|
|
91
|
+
const validatedOutput = createValidatedOutput(child.stdout, {
|
|
92
|
+
maxInputFrameBytes: this.launch.maxStdoutFrameBytes ?? MAX_STDOUT_LINE_BYTES,
|
|
93
|
+
transformFrame: this.launch.transformStdoutFrame,
|
|
94
|
+
});
|
|
95
|
+
output = Readable.toWeb(validatedOutput);
|
|
96
|
+
if (this.platform === 'win32') {
|
|
97
|
+
this.rootWindowsIdentity = await this.captureRootWindowsIdentity(child.pid);
|
|
98
|
+
if (this.rootWindowsIdentity) {
|
|
99
|
+
this.ownedWindowsProcesses.set(this.rootWindowsIdentity.pid, this.rootWindowsIdentity);
|
|
100
|
+
}
|
|
101
|
+
else if (!this.settled) {
|
|
102
|
+
return this.failAfterSpawn(new AgentRuntimeError('spawn_failed', 'spawn', 'ACP adapter process identity could not be verified', true));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// The exact ChildProcess handle is settled. Only accept a missing
|
|
106
|
+
// root when the adapter can also prove that no descendant remains.
|
|
107
|
+
await this.captureWindowsDescendants();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.rootIdentity = await this.captureRootIdentity(child.pid);
|
|
112
|
+
if (!this.rootIdentity && !this.settled) {
|
|
113
|
+
return this.failAfterSpawn(new AgentRuntimeError('spawn_failed', 'spawn', 'ACP adapter process identity could not be verified', true));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return this.failAfterSpawn(error);
|
|
119
|
+
}
|
|
120
|
+
// A short-lived adapter can settle before the OS identity probe observes
|
|
121
|
+
// it. The ChildProcess exit event is authoritative for that exact handle;
|
|
122
|
+
// persist a generation-unique marker without signalling any PID. A live
|
|
123
|
+
// child without a verifiable OS identity remains an unresolved launch.
|
|
124
|
+
const birthMarker = this.rootIdentity?.birthIdentity
|
|
125
|
+
?? this.rootWindowsIdentity?.birthMarker
|
|
126
|
+
?? (this.settled ? `settled:${this.ownershipToken}` : undefined);
|
|
127
|
+
if (!birthMarker) {
|
|
128
|
+
return this.failAfterSpawn(new AgentRuntimeError('process_identity_mismatch', 'spawn', 'ACP adapter launched without a durable birth identity', true));
|
|
64
129
|
}
|
|
65
|
-
const validatedOutput = createValidatedOutput(child.stdout, {
|
|
66
|
-
maxInputFrameBytes: this.launch.maxStdoutFrameBytes ?? MAX_STDOUT_LINE_BYTES,
|
|
67
|
-
transformFrame: this.launch.transformStdoutFrame,
|
|
68
|
-
});
|
|
69
130
|
return {
|
|
70
131
|
pid: child.pid,
|
|
132
|
+
processGroupId: this.rootIdentity ? String(this.rootIdentity.pgid) : String(child.pid),
|
|
133
|
+
birthMarker,
|
|
134
|
+
ownershipToken: this.ownershipToken,
|
|
71
135
|
input: Writable.toWeb(child.stdin),
|
|
72
|
-
output
|
|
136
|
+
output,
|
|
73
137
|
};
|
|
74
138
|
}
|
|
75
139
|
onExit(listener) {
|
|
@@ -78,43 +142,205 @@ export class AcpProcessManager {
|
|
|
78
142
|
listener(this.settled);
|
|
79
143
|
return () => this.exitListeners.delete(listener);
|
|
80
144
|
}
|
|
145
|
+
/** Exposes the retry owner for startup/recovery diagnostics and tests. */
|
|
146
|
+
getPostSpawnCleanupOwnerId() {
|
|
147
|
+
return this.cleanupOwnerId;
|
|
148
|
+
}
|
|
81
149
|
async stop() {
|
|
82
150
|
if (this.stopPromise)
|
|
83
151
|
return this.stopPromise;
|
|
84
|
-
this.stopPromise = this.stopOnce()
|
|
152
|
+
this.stopPromise = this.stopOnce().catch((error) => {
|
|
153
|
+
this.stopPromise = undefined;
|
|
154
|
+
// A caller may lose the manager after an initialization failure. Keep a
|
|
155
|
+
// registry owner reachable even when this stop attempt is the first
|
|
156
|
+
// post-spawn failure observed by the driver.
|
|
157
|
+
void this.retryCleanupOwner().catch(() => undefined);
|
|
158
|
+
throw error;
|
|
159
|
+
});
|
|
85
160
|
return this.stopPromise;
|
|
86
161
|
}
|
|
87
162
|
async stopOnce() {
|
|
88
163
|
if (!this.child || !this.exitPromise)
|
|
89
164
|
return undefined;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
165
|
+
const settled = this.settled;
|
|
166
|
+
if (!this.child.pid) {
|
|
167
|
+
if (settled)
|
|
168
|
+
return settled;
|
|
169
|
+
await this.terminateChildHandle('SIGTERM');
|
|
170
|
+
const graceful = await within(this.exitPromise, DEFAULT_KILL_GRACE_MS);
|
|
171
|
+
if (graceful)
|
|
172
|
+
return graceful;
|
|
173
|
+
await this.terminateChildHandle('SIGKILL');
|
|
174
|
+
const forced = await within(this.exitPromise, DEFAULT_KILL_GRACE_MS);
|
|
175
|
+
if (forced)
|
|
176
|
+
return forced;
|
|
177
|
+
throw processTreeTimeout();
|
|
178
|
+
}
|
|
179
|
+
let groups = this.platform === 'win32' ? [] : await this.captureOwnedGroups();
|
|
180
|
+
if (this.platform === 'win32') {
|
|
181
|
+
await this.captureWindowsDescendants();
|
|
182
|
+
await this.signal('SIGTERM', groups);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
await this.signal('SIGTERM', groups);
|
|
186
|
+
}
|
|
187
|
+
const graceful = settled ?? await within(this.exitPromise, DEFAULT_KILL_GRACE_MS);
|
|
188
|
+
const treeGraceMs = graceful ? Math.min(DEFAULT_KILL_GRACE_MS, 500) : DEFAULT_KILL_GRACE_MS;
|
|
189
|
+
if (graceful && await this.waitForOwnedTreeExit(groups, treeGraceMs))
|
|
95
190
|
return graceful;
|
|
96
|
-
this.
|
|
191
|
+
if (this.platform !== 'win32') {
|
|
192
|
+
// The ownership marker is launch-scoped. Once the root exits, descendants
|
|
193
|
+
// may have moved to a new session/process group; filtering by the root
|
|
194
|
+
// PGID would permanently hide those escaped groups from cleanup.
|
|
195
|
+
groups = mergeUnixGroups(groups, await this.captureOwnedGroups());
|
|
196
|
+
}
|
|
197
|
+
await this.signal('SIGKILL', groups);
|
|
97
198
|
const forced = await within(this.exitPromise, DEFAULT_KILL_GRACE_MS);
|
|
98
|
-
if (forced)
|
|
199
|
+
if (forced && await this.waitForOwnedTreeExit(groups, DEFAULT_KILL_GRACE_MS))
|
|
99
200
|
return forced;
|
|
100
201
|
throw new AgentRuntimeError('process_exit_timeout', 'close', 'ACP adapter did not confirm termination before the shutdown deadline', true);
|
|
101
202
|
}
|
|
102
|
-
signal(signal) {
|
|
203
|
+
async signal(signal, groups) {
|
|
103
204
|
const child = this.child;
|
|
104
|
-
if (!child?.pid
|
|
205
|
+
if (!child?.pid)
|
|
206
|
+
return;
|
|
207
|
+
if (this.platform === 'win32') {
|
|
208
|
+
await this.captureWindowsDescendants();
|
|
209
|
+
const candidates = [...this.ownedWindowsProcesses.values()]
|
|
210
|
+
.sort((left, right) => right.pid - left.pid);
|
|
211
|
+
for (const identity of candidates) {
|
|
212
|
+
if (!await this.windowsProcessAdapter.isProcessAlive(identity))
|
|
213
|
+
continue;
|
|
214
|
+
await this.windowsProcessAdapter.terminateTree(identity.pid);
|
|
215
|
+
}
|
|
105
216
|
return;
|
|
106
|
-
try {
|
|
107
|
-
if (process.platform === 'win32')
|
|
108
|
-
child.kill(signal);
|
|
109
|
-
else
|
|
110
|
-
process.kill(-child.pid, signal);
|
|
111
217
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
218
|
+
await Promise.all(groups.map((group) => this.unixProcessAdapter.signalProcessGroup(group, signal)));
|
|
219
|
+
if (this.rootIdentity) {
|
|
220
|
+
await this.unixProcessAdapter.signalProcess(this.rootIdentity, signal);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async waitForOwnedTreeExit(initialGroups, timeoutMs) {
|
|
224
|
+
let groups = initialGroups;
|
|
225
|
+
const deadline = Date.now() + timeoutMs;
|
|
226
|
+
while (Date.now() < deadline) {
|
|
227
|
+
if (!await this.isOwnedTreeAlive(groups)) {
|
|
228
|
+
if (this.platform === 'win32') {
|
|
229
|
+
await this.captureWindowsDescendants();
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
groups = mergeUnixGroups(groups, await this.captureOwnedGroups());
|
|
233
|
+
}
|
|
234
|
+
if (!await this.isOwnedTreeAlive(groups))
|
|
235
|
+
return true;
|
|
115
236
|
}
|
|
116
|
-
|
|
117
|
-
|
|
237
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
238
|
+
}
|
|
239
|
+
if (this.platform === 'win32') {
|
|
240
|
+
await this.captureWindowsDescendants();
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
groups = mergeUnixGroups(groups, await this.captureOwnedGroups());
|
|
244
|
+
}
|
|
245
|
+
return !await this.isOwnedTreeAlive(groups);
|
|
246
|
+
}
|
|
247
|
+
async isOwnedTreeAlive(groups) {
|
|
248
|
+
const pid = this.child?.pid;
|
|
249
|
+
if (!pid)
|
|
250
|
+
return false;
|
|
251
|
+
if (this.platform === 'win32') {
|
|
252
|
+
for (const identity of this.ownedWindowsProcesses.values()) {
|
|
253
|
+
if (await this.windowsProcessAdapter.isProcessAlive(identity))
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (!this.settled && this.rootIdentity && await this.unixProcessAdapter.isProcessAlive(this.rootIdentity))
|
|
259
|
+
return true;
|
|
260
|
+
const liveGroups = groups
|
|
261
|
+
.map((group) => this.settled
|
|
262
|
+
? { ...group, members: group.members.filter((member) => member.pid !== pid) }
|
|
263
|
+
: group)
|
|
264
|
+
.filter((group) => group.members.length > 0);
|
|
265
|
+
return (await Promise.all(liveGroups.map((group) => this.unixProcessAdapter.isProcessGroupAlive(group)))).some(Boolean);
|
|
266
|
+
}
|
|
267
|
+
async captureRootIdentity(pid) {
|
|
268
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
269
|
+
const identity = await this.unixProcessAdapter.captureProcess(pid, this.ownershipToken);
|
|
270
|
+
if (identity)
|
|
271
|
+
return identity;
|
|
272
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
async captureRootWindowsIdentity(pid) {
|
|
277
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
278
|
+
const identity = await this.windowsProcessAdapter.captureProcess(pid);
|
|
279
|
+
if (identity)
|
|
280
|
+
return identity;
|
|
281
|
+
if (this.settled)
|
|
282
|
+
return undefined;
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
async captureOwnedGroups(processGroupId) {
|
|
288
|
+
if (this.platform === 'win32')
|
|
289
|
+
return [];
|
|
290
|
+
const groups = this.unixProcessAdapter.captureOwnedGroups
|
|
291
|
+
? processGroupId === undefined
|
|
292
|
+
? await this.unixProcessAdapter.captureOwnedGroups(this.ownershipToken)
|
|
293
|
+
: await this.unixProcessAdapter.captureOwnedGroups(this.ownershipToken, processGroupId)
|
|
294
|
+
: this.rootIdentity
|
|
295
|
+
? await this.unixProcessAdapter.captureDescendantGroups(this.rootIdentity)
|
|
296
|
+
: [];
|
|
297
|
+
if (!this.settled || !this.child?.pid)
|
|
298
|
+
return groups;
|
|
299
|
+
return groups
|
|
300
|
+
.map((group) => ({ ...group, members: group.members.filter((member) => member.pid !== this.child.pid) }))
|
|
301
|
+
.filter((group) => group.members.length > 0);
|
|
302
|
+
}
|
|
303
|
+
async captureWindowsDescendants() {
|
|
304
|
+
const rootPid = this.child?.pid;
|
|
305
|
+
if (!rootPid)
|
|
306
|
+
return;
|
|
307
|
+
const currentRoot = await this.windowsProcessAdapter.captureProcess(rootPid);
|
|
308
|
+
// Once the root disappears, a fresh parent/child snapshot cannot prove
|
|
309
|
+
// ownership: the PID may already have been reused. Only descendants that
|
|
310
|
+
// were captured while the root identity was still observable remain safe.
|
|
311
|
+
if (!currentRoot) {
|
|
312
|
+
if (this.settled) {
|
|
313
|
+
const descendants = await this.windowsProcessAdapter.captureDescendants(rootPid);
|
|
314
|
+
for (const identity of descendants) {
|
|
315
|
+
const existing = this.ownedWindowsProcesses.get(identity.pid);
|
|
316
|
+
if (!existing || existing.birthMarker === identity.birthMarker) {
|
|
317
|
+
this.ownedWindowsProcesses.set(identity.pid, identity);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (descendants.length > 0) {
|
|
321
|
+
throw processOwnershipUnverifiable();
|
|
322
|
+
}
|
|
323
|
+
if (![...this.ownedWindowsProcesses].some(([pid]) => pid !== rootPid)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (![...this.ownedWindowsProcesses].some(([pid]) => pid !== rootPid)) {
|
|
328
|
+
throw processOwnershipUnverifiable();
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!this.rootWindowsIdentity
|
|
333
|
+
|| currentRoot.birthMarker !== this.rootWindowsIdentity.birthMarker) {
|
|
334
|
+
if (![...this.ownedWindowsProcesses].some(([pid]) => pid !== rootPid)) {
|
|
335
|
+
throw processOwnershipUnverifiable();
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const descendants = await this.windowsProcessAdapter.captureDescendants(rootPid);
|
|
340
|
+
for (const identity of descendants) {
|
|
341
|
+
const existing = this.ownedWindowsProcesses.get(identity.pid);
|
|
342
|
+
if (!existing || existing.birthMarker === identity.birthMarker) {
|
|
343
|
+
this.ownedWindowsProcesses.set(identity.pid, identity);
|
|
118
344
|
}
|
|
119
345
|
}
|
|
120
346
|
}
|
|
@@ -126,6 +352,236 @@ export class AcpProcessManager {
|
|
|
126
352
|
for (const listener of [...this.exitListeners])
|
|
127
353
|
listener(this.settled);
|
|
128
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Keep a post-spawn manager reachable even when `start()` rejects before a
|
|
357
|
+
* DriverSession exists. The shared registry retries tree cleanup and is
|
|
358
|
+
* drained with the application runtime.
|
|
359
|
+
*/
|
|
360
|
+
async failAfterSpawn(error) {
|
|
361
|
+
const ownerId = this.ensureCleanupOwner();
|
|
362
|
+
try {
|
|
363
|
+
await this.stop();
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// Continue to the registry retries even when the first stop fails.
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
await this.retryCleanupOwner(ownerId);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
// The registry retains the owner and schedules bounded retries.
|
|
373
|
+
}
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
ensureCleanupOwner() {
|
|
377
|
+
return this.cleanupOwnerId ??= acpLaunchCleanupRegistry.register(() => this.stop().then(() => undefined), `acp-process:${this.launch.command}`);
|
|
378
|
+
}
|
|
379
|
+
retryCleanupOwner(ownerId = this.ensureCleanupOwner()) {
|
|
380
|
+
if (!this.cleanupRetryPromise) {
|
|
381
|
+
const retry = acpLaunchCleanupRegistry.runWithImmediateRetries(ownerId);
|
|
382
|
+
let tracked;
|
|
383
|
+
tracked = retry.finally(() => {
|
|
384
|
+
if (this.cleanupRetryPromise === tracked)
|
|
385
|
+
this.cleanupRetryPromise = undefined;
|
|
386
|
+
if (!acpLaunchCleanupRegistry.getState(ownerId))
|
|
387
|
+
this.cleanupOwnerId = undefined;
|
|
388
|
+
});
|
|
389
|
+
this.cleanupRetryPromise = tracked;
|
|
390
|
+
}
|
|
391
|
+
return this.cleanupRetryPromise;
|
|
392
|
+
}
|
|
393
|
+
async terminateChildHandle(signal) {
|
|
394
|
+
try {
|
|
395
|
+
this.child?.kill(signal);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// The child may have exited between the handle check and kill.
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
export async function cleanupPersistedAcpProcessTree(identity, options = {}) {
|
|
403
|
+
const platform = options.platform ?? process.platform;
|
|
404
|
+
const graceMs = options.graceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
405
|
+
if (platform === 'win32') {
|
|
406
|
+
const adapter = options.windowsProcessAdapter ?? createWindowsProcessTreeAdapter();
|
|
407
|
+
if (!identity.birthMarker) {
|
|
408
|
+
throw processOwnershipUnverifiable();
|
|
409
|
+
}
|
|
410
|
+
const root = { pid: identity.pid, parentPid: 0, birthMarker: identity.birthMarker };
|
|
411
|
+
const currentRoot = await adapter.captureProcess(identity.pid);
|
|
412
|
+
// A missing root is not evidence that its descendants belong to the old
|
|
413
|
+
// launch. Without a platform-level persisted ownership primitive, refuse
|
|
414
|
+
// recovery rather than risking taskkill against a reused PID tree.
|
|
415
|
+
if (!currentRoot || currentRoot.birthMarker !== root.birthMarker) {
|
|
416
|
+
throw processOwnershipUnverifiable();
|
|
417
|
+
}
|
|
418
|
+
const owned = new Map();
|
|
419
|
+
owned.set(root.pid, root);
|
|
420
|
+
for (const descendant of await adapter.captureDescendants(identity.pid)) {
|
|
421
|
+
owned.set(descendant.pid, descendant);
|
|
422
|
+
}
|
|
423
|
+
for (const processIdentity of [...owned.values()].sort((left, right) => right.pid - left.pid)) {
|
|
424
|
+
if (await adapter.isProcessAlive(processIdentity)) {
|
|
425
|
+
await adapter.terminateTree(processIdentity.pid);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (!await waitForWindowsTreeExit(adapter, owned, graceMs)) {
|
|
429
|
+
throw processTreeTimeout();
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const adapter = options.unixProcessAdapter ?? createUnixProcessIdentityAdapter(platform);
|
|
434
|
+
if (!adapter.captureOwnedGroups || !identity.processGroupId || !identity.birthMarker) {
|
|
435
|
+
throw processOwnershipUnverifiable();
|
|
436
|
+
}
|
|
437
|
+
const pgid = Number(identity.processGroupId);
|
|
438
|
+
const root = Number.isInteger(pgid) && pgid > 0
|
|
439
|
+
? {
|
|
440
|
+
pid: identity.pid,
|
|
441
|
+
pgid,
|
|
442
|
+
birthIdentity: identity.birthMarker,
|
|
443
|
+
ownershipToken: identity.ownershipToken,
|
|
444
|
+
}
|
|
445
|
+
: undefined;
|
|
446
|
+
const captureGroups = async () => adapter.captureOwnedGroups(identity.ownershipToken);
|
|
447
|
+
let groups = await captureGroups();
|
|
448
|
+
if (root && await adapter.isProcessAlive(root)) {
|
|
449
|
+
const descendants = await adapter.captureDescendantGroups(root);
|
|
450
|
+
groups = mergeUnixGroups(groups, descendants);
|
|
451
|
+
}
|
|
452
|
+
await Promise.all(groups.map((group) => adapter.signalProcessGroup(group, 'SIGTERM')));
|
|
453
|
+
if (root)
|
|
454
|
+
await adapter.signalProcess(root, 'SIGTERM');
|
|
455
|
+
if (await waitForUnixTreeExit(adapter, root, groups, graceMs, captureGroups))
|
|
456
|
+
return;
|
|
457
|
+
groups = mergeUnixGroups(groups, await captureGroups());
|
|
458
|
+
await Promise.all(groups.map((group) => adapter.signalProcessGroup(group, 'SIGKILL')));
|
|
459
|
+
if (root)
|
|
460
|
+
await adapter.signalProcess(root, 'SIGKILL');
|
|
461
|
+
if (!await waitForUnixTreeExit(adapter, root, groups, graceMs, captureGroups)) {
|
|
462
|
+
throw processTreeTimeout();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function createWindowsProcessTreeAdapter() {
|
|
466
|
+
const listProcesses = () => {
|
|
467
|
+
const script = [
|
|
468
|
+
'Get-CimInstance Win32_Process',
|
|
469
|
+
'Select-Object ProcessId,ParentProcessId,CreationDate',
|
|
470
|
+
'ConvertTo-Json -Compress',
|
|
471
|
+
].join(' | ');
|
|
472
|
+
try {
|
|
473
|
+
const output = execFileSync('powershell.exe', [
|
|
474
|
+
'-NoProfile',
|
|
475
|
+
'-NonInteractive',
|
|
476
|
+
'-Command',
|
|
477
|
+
script,
|
|
478
|
+
], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
479
|
+
if (!output)
|
|
480
|
+
return [];
|
|
481
|
+
const parsed = JSON.parse(output);
|
|
482
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
483
|
+
return rows.map((row) => {
|
|
484
|
+
const pid = Number(row.ProcessId);
|
|
485
|
+
const parentPid = Number(row.ParentProcessId);
|
|
486
|
+
const birthMarker = typeof row.CreationDate === 'string' ? row.CreationDate : '';
|
|
487
|
+
return pid > 0 && Number.isFinite(parentPid) && birthMarker
|
|
488
|
+
? { pid, parentPid, birthMarker }
|
|
489
|
+
: null;
|
|
490
|
+
}).filter((row) => row !== null);
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
throw processEnumerationFailed(error);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
return {
|
|
497
|
+
async captureProcess(pid) {
|
|
498
|
+
return listProcesses().find((processIdentity) => processIdentity.pid === pid) ?? null;
|
|
499
|
+
},
|
|
500
|
+
async captureDescendants(rootPid) {
|
|
501
|
+
const rows = listProcesses();
|
|
502
|
+
const descendantIds = new Set([rootPid]);
|
|
503
|
+
let changed = true;
|
|
504
|
+
while (changed) {
|
|
505
|
+
changed = false;
|
|
506
|
+
for (const row of rows) {
|
|
507
|
+
if (descendantIds.has(row.pid) || !descendantIds.has(row.parentPid))
|
|
508
|
+
continue;
|
|
509
|
+
descendantIds.add(row.pid);
|
|
510
|
+
changed = true;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return rows.filter((row) => row.pid !== rootPid && descendantIds.has(row.pid));
|
|
514
|
+
},
|
|
515
|
+
async isProcessAlive(identity) {
|
|
516
|
+
const current = listProcesses().find((row) => row.pid === identity.pid);
|
|
517
|
+
return current?.birthMarker === identity.birthMarker;
|
|
518
|
+
},
|
|
519
|
+
async terminateTree(pid) {
|
|
520
|
+
try {
|
|
521
|
+
execFileSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
// The identity is rechecked before signalling; a concurrent exit is harmless.
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function mergeUnixGroups(left, right) {
|
|
530
|
+
const groups = new Map();
|
|
531
|
+
for (const group of [...left, ...right]) {
|
|
532
|
+
const members = groups.get(group.pgid) ?? [];
|
|
533
|
+
const pids = new Set(members.map((member) => member.pid));
|
|
534
|
+
for (const member of group.members) {
|
|
535
|
+
if (!pids.has(member.pid)) {
|
|
536
|
+
members.push(member);
|
|
537
|
+
pids.add(member.pid);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
groups.set(group.pgid, members);
|
|
541
|
+
}
|
|
542
|
+
return [...groups].map(([pgid, members]) => ({ pgid, members }));
|
|
543
|
+
}
|
|
544
|
+
async function waitForUnixTreeExit(adapter, root, groups, timeoutMs, captureGroups) {
|
|
545
|
+
let knownGroups = groups;
|
|
546
|
+
const deadline = Date.now() + timeoutMs;
|
|
547
|
+
do {
|
|
548
|
+
const rootAlive = root ? await adapter.isProcessAlive(root) : false;
|
|
549
|
+
const groupAlive = (await Promise.all(knownGroups.map((group) => adapter.isProcessGroupAlive(group)))).some(Boolean);
|
|
550
|
+
if (!rootAlive && !groupAlive) {
|
|
551
|
+
if (captureGroups) {
|
|
552
|
+
knownGroups = mergeUnixGroups(knownGroups, await captureGroups());
|
|
553
|
+
}
|
|
554
|
+
const refreshedGroupAlive = (await Promise.all(knownGroups.map((group) => adapter.isProcessGroupAlive(group)))).some(Boolean);
|
|
555
|
+
if (!refreshedGroupAlive)
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
559
|
+
} while (Date.now() < deadline);
|
|
560
|
+
if (captureGroups) {
|
|
561
|
+
knownGroups = mergeUnixGroups(knownGroups, await captureGroups());
|
|
562
|
+
}
|
|
563
|
+
const rootAlive = root ? await adapter.isProcessAlive(root) : false;
|
|
564
|
+
const groupAlive = (await Promise.all(knownGroups.map((group) => adapter.isProcessGroupAlive(group)))).some(Boolean);
|
|
565
|
+
return !rootAlive && !groupAlive;
|
|
566
|
+
}
|
|
567
|
+
async function waitForWindowsTreeExit(adapter, owned, timeoutMs) {
|
|
568
|
+
const deadline = Date.now() + timeoutMs;
|
|
569
|
+
do {
|
|
570
|
+
const alive = (await Promise.all([...owned.values()].map((item) => adapter.isProcessAlive(item)))).some(Boolean);
|
|
571
|
+
if (!alive)
|
|
572
|
+
return true;
|
|
573
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
574
|
+
} while (Date.now() < deadline);
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
function processTreeTimeout() {
|
|
578
|
+
return new AgentRuntimeError('process_exit_timeout', 'close', 'ACP adapter did not confirm termination before the shutdown deadline', true);
|
|
579
|
+
}
|
|
580
|
+
function processOwnershipUnverifiable() {
|
|
581
|
+
return new AgentRuntimeError('process_identity_mismatch', 'close', 'ACP adapter process ownership could not be verified safely', true);
|
|
582
|
+
}
|
|
583
|
+
function processEnumerationFailed(cause) {
|
|
584
|
+
return new AgentRuntimeError('process_identity_mismatch', 'close', 'ACP adapter process enumeration failed; ownership could not be verified safely', true, cause === undefined ? undefined : { cause });
|
|
129
585
|
}
|
|
130
586
|
function createValidatedOutput(input, options) {
|
|
131
587
|
const decoder = new StringDecoder('utf8');
|