@fitlab-ai/agent-infra 0.9.6 → 0.9.7
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/bin/internal-cli.js +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/task-orchestration.js +37 -5
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/sandbox/commands/create.js +103 -51
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +13 -7
- package/dist/lib/sandbox/control/lifecycle.js +238 -29
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +25 -15
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/mounts.js +5 -13
- package/dist/lib/sandbox/recovery.js +59 -42
- package/dist/lib/sandbox/workspace-view.js +57 -40
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +84 -2
- package/dist/lib/task/events.js +139 -2
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +235 -14
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +40 -8
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +33 -4
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/task-orchestration.ts +31 -4
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/sandbox/commands/create.ts +56 -7
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +14 -10
- package/lib/sandbox/control/lifecycle.ts +264 -33
- package/lib/sandbox/control/protocol.ts +31 -5
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +34 -17
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/mounts.ts +5 -15
- package/lib/sandbox/recovery.ts +71 -42
- package/lib/sandbox/workspace-view.ts +61 -40
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +160 -8
- package/lib/task/events.ts +122 -1
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +356 -17
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +51 -16
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +25 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
package/lib/sandbox/recovery.ts
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
type SandboxWorkspaceIdentity
|
|
36
36
|
} from './workspace-identity.ts';
|
|
37
37
|
import { inspectWorktree, type WorktreeSnapshot } from './worktree-safety.ts';
|
|
38
|
-
import { runEngine, runOkEngine, runVerboseEngine } from './shell.ts';
|
|
38
|
+
import { commandForEngine, runEngine, runOkEngine, runProbe, runVerboseEngine } from './shell.ts';
|
|
39
39
|
import {
|
|
40
40
|
declaredTmpfsSeedEntries,
|
|
41
41
|
resolveTools,
|
|
@@ -52,12 +52,16 @@ import {
|
|
|
52
52
|
import { getProcessStartTime, processIdentityMatches, removePidFileIfMatches } from '../server/process-state.ts';
|
|
53
53
|
import {
|
|
54
54
|
acquireSandboxControlBrokerStartup,
|
|
55
|
-
|
|
55
|
+
garbageCollectSandboxControlRoot,
|
|
56
|
+
isSandboxControlRootQuiescing,
|
|
57
|
+
readSandboxControlManifest,
|
|
58
|
+
type BrokerOwner
|
|
56
59
|
} from './control/lifecycle.ts';
|
|
57
60
|
import {
|
|
58
61
|
appendSandboxControlAudit,
|
|
59
62
|
readSandboxControlStatus
|
|
60
63
|
} from './control/state.ts';
|
|
64
|
+
import { inspectSandboxControlContainer } from './control/container-identity.ts';
|
|
61
65
|
import {
|
|
62
66
|
SANDBOX_CONTROL_FUTURE_SKEW_MS,
|
|
63
67
|
SANDBOX_CONTROL_STATUS_STALE_MS,
|
|
@@ -142,8 +146,27 @@ type RecoveryCommandDeps = {
|
|
|
142
146
|
start?: typeof startSandboxContainer;
|
|
143
147
|
fetchRows?: typeof fetchSandboxRows;
|
|
144
148
|
ensureControlBroker?: () => void | Promise<void>;
|
|
149
|
+
probe?: typeof runProbe;
|
|
145
150
|
};
|
|
146
151
|
|
|
152
|
+
export function worktreeProbeForEngine(
|
|
153
|
+
engine: string,
|
|
154
|
+
probe: typeof runProbe = runProbe
|
|
155
|
+
): typeof runProbe {
|
|
156
|
+
return (cmd, args, opts = {}) => {
|
|
157
|
+
const engineArgs = [...args];
|
|
158
|
+
if (engine === 'wsl2' && cmd === 'git') {
|
|
159
|
+
for (let index = 0; index < engineArgs.length - 1; index += 1) {
|
|
160
|
+
if (engineArgs[index] === '-C') {
|
|
161
|
+
engineArgs[index + 1] = toEnginePath(engine, engineArgs[index + 1]!);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const command = commandForEngine(engine, cmd, engineArgs);
|
|
166
|
+
return probe(command.cmd, command.args, opts);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
147
170
|
type EnsureSandboxReadyParams = {
|
|
148
171
|
config: SandboxConfig;
|
|
149
172
|
engine: string;
|
|
@@ -151,6 +174,7 @@ type EnsureSandboxReadyParams = {
|
|
|
151
174
|
workspace?: SandboxWorkspaceIdentity;
|
|
152
175
|
row: SandboxRow;
|
|
153
176
|
allowRecreate?: boolean;
|
|
177
|
+
forceRecreate?: boolean;
|
|
154
178
|
recreate?: (branch: string) => Promise<void>;
|
|
155
179
|
writeWarning?: (message: string) => void;
|
|
156
180
|
deps?: RecoveryCommandDeps;
|
|
@@ -168,7 +192,7 @@ async function waitForSandboxControlBrokerStatus(params: {
|
|
|
168
192
|
statusDir: string;
|
|
169
193
|
generation: string;
|
|
170
194
|
pid: number;
|
|
171
|
-
startTime?:
|
|
195
|
+
startTime?: number;
|
|
172
196
|
}, timeoutMs = 5_000): Promise<void> {
|
|
173
197
|
const deadline = Date.now() + timeoutMs;
|
|
174
198
|
while (Date.now() < deadline) {
|
|
@@ -191,7 +215,7 @@ export async function startSandboxControlBroker(repoRoot: string, manifestPath:
|
|
|
191
215
|
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
192
216
|
const extension = path.extname(fileURLToPath(import.meta.url));
|
|
193
217
|
const internalCli = path.resolve(directory, '..', '..', 'bin', `internal-cli${extension}`);
|
|
194
|
-
const manifest =
|
|
218
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
195
219
|
const root = path.dirname(manifestPath);
|
|
196
220
|
if (isSandboxControlRootQuiescing(root)) throw new Error('SANDBOX_CONTROL_QUIESCING');
|
|
197
221
|
const brokerPath = path.join(root, 'broker.json');
|
|
@@ -207,6 +231,7 @@ export async function startSandboxControlBroker(repoRoot: string, manifestPath:
|
|
|
207
231
|
version?: unknown;
|
|
208
232
|
pid?: unknown;
|
|
209
233
|
startTime?: unknown;
|
|
234
|
+
brokerId?: unknown;
|
|
210
235
|
token?: unknown;
|
|
211
236
|
generation?: unknown;
|
|
212
237
|
};
|
|
@@ -216,15 +241,22 @@ export async function startSandboxControlBroker(repoRoot: string, manifestPath:
|
|
|
216
241
|
} catch {
|
|
217
242
|
// Malformed owner records are stale and replaced below.
|
|
218
243
|
}
|
|
219
|
-
const owner = broker !== null &&
|
|
220
|
-
|
|
244
|
+
const owner = broker !== null && broker.version === 3 && typeof broker.pid === 'number'
|
|
245
|
+
&& Number.isSafeInteger(broker.pid) && broker.pid > 0 && typeof broker.startTime === 'number'
|
|
246
|
+
&& Number.isSafeInteger(broker.startTime) && typeof broker.brokerId === 'string'
|
|
247
|
+
&& broker.brokerId.length > 0 && typeof broker.token === 'string'
|
|
248
|
+
&& typeof broker.generation === 'string'
|
|
249
|
+
? broker as BrokerOwner
|
|
221
250
|
: null;
|
|
222
|
-
|
|
251
|
+
if (broker === null || broker.version !== 3 || owner === null) {
|
|
252
|
+
throw new Error('SANDBOX_CONTROL_BROKER_VERSION_INVALID: rebuild the sandbox container before starting a new broker');
|
|
253
|
+
}
|
|
254
|
+
const live = owner !== null && processIdentityMatches({ pid: owner.pid, startTime: owner.startTime });
|
|
223
255
|
if (
|
|
224
256
|
live
|
|
225
257
|
&& owner !== null
|
|
226
258
|
&& broker !== null
|
|
227
|
-
&& broker.version ===
|
|
259
|
+
&& broker.version === 3
|
|
228
260
|
&& broker.token === manifest.token
|
|
229
261
|
&& broker.generation === manifest.generation
|
|
230
262
|
) {
|
|
@@ -237,27 +269,6 @@ export async function startSandboxControlBroker(repoRoot: string, manifestPath:
|
|
|
237
269
|
});
|
|
238
270
|
return;
|
|
239
271
|
}
|
|
240
|
-
if (live && owner) {
|
|
241
|
-
const deadline = Date.now() + 5_000;
|
|
242
|
-
while (Date.now() < deadline) {
|
|
243
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
244
|
-
try {
|
|
245
|
-
if (fs.readFileSync(brokerPath, 'utf8') !== brokerSnapshot) {
|
|
246
|
-
return startSandboxControlBroker(repoRoot, manifestPath);
|
|
247
|
-
}
|
|
248
|
-
} catch (error) {
|
|
249
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
250
|
-
return startSandboxControlBroker(repoRoot, manifestPath);
|
|
251
|
-
}
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
254
|
-
if (!processIdentityMatches(owner)) {
|
|
255
|
-
removePidFileIfMatches(brokerPath, brokerSnapshot);
|
|
256
|
-
return startSandboxControlBroker(repoRoot, manifestPath);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
throw new Error('SANDBOX_CONTROL_BROKER_OWNER_ACTIVE');
|
|
260
|
-
}
|
|
261
272
|
}
|
|
262
273
|
const startupStartTime = getProcessStartTime(process.pid);
|
|
263
274
|
if (!startupStartTime) throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
@@ -317,9 +328,16 @@ async function ensureSandboxControlBroker(params: {
|
|
|
317
328
|
identity: params.workspace
|
|
318
329
|
});
|
|
319
330
|
if (!fs.existsSync(control.manifestPath)) return;
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
331
|
+
const validatedManifest = readSandboxControlManifest(control.manifestPath);
|
|
332
|
+
const containerObservation = await inspectSandboxControlContainer(validatedManifest);
|
|
333
|
+
if (containerObservation.state === 'unknown') {
|
|
334
|
+
throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${containerObservation.reason}`);
|
|
335
|
+
}
|
|
336
|
+
if (containerObservation.state === 'absent') {
|
|
337
|
+
await garbageCollectSandboxControlRoot(control.root, {
|
|
338
|
+
inspectContainer: (timeoutMs) => inspectSandboxControlContainer(validatedManifest, { timeoutMs })
|
|
339
|
+
});
|
|
340
|
+
throw new Error('SANDBOX_CONTROL_CONTAINER_ABSENT: recreate the sandbox container before broker recovery');
|
|
323
341
|
}
|
|
324
342
|
const brokerPath = path.join(control.root, 'broker.json');
|
|
325
343
|
try {
|
|
@@ -327,20 +345,25 @@ async function ensureSandboxControlBroker(params: {
|
|
|
327
345
|
version?: unknown;
|
|
328
346
|
pid?: unknown;
|
|
329
347
|
startTime?: unknown;
|
|
348
|
+
brokerId?: unknown;
|
|
330
349
|
token?: unknown;
|
|
331
350
|
generation?: unknown;
|
|
332
351
|
};
|
|
333
352
|
if (
|
|
334
|
-
broker.version ===
|
|
353
|
+
broker.version === 3
|
|
335
354
|
&& typeof broker.pid === 'number'
|
|
336
|
-
&&
|
|
337
|
-
&& broker.
|
|
338
|
-
&& broker.
|
|
339
|
-
&&
|
|
355
|
+
&& Number.isSafeInteger(broker.pid)
|
|
356
|
+
&& typeof broker.startTime === 'number'
|
|
357
|
+
&& Number.isSafeInteger(broker.startTime)
|
|
358
|
+
&& typeof broker.brokerId === 'string'
|
|
359
|
+
&& broker.brokerId.length > 0
|
|
360
|
+
&& broker.token === validatedManifest.token
|
|
361
|
+
&& broker.generation === validatedManifest.generation
|
|
362
|
+
&& processIdentityMatches({ pid: broker.pid, startTime: broker.startTime })
|
|
340
363
|
) {
|
|
341
364
|
try {
|
|
342
365
|
const status = readSandboxControlStatus(control.statusDir);
|
|
343
|
-
if (status.generation ===
|
|
366
|
+
if (status.generation === validatedManifest.generation && status.broker.pid === broker.pid
|
|
344
367
|
&& status.broker.startTime === broker.startTime
|
|
345
368
|
&& Date.now() - status.updatedAt <= SANDBOX_CONTROL_STATUS_STALE_MS
|
|
346
369
|
&& status.updatedAt <= Date.now() + SANDBOX_CONTROL_FUTURE_SKEW_MS) return;
|
|
@@ -350,7 +373,7 @@ async function ensureSandboxControlBroker(params: {
|
|
|
350
373
|
await waitForSandboxControlBrokerStatus({
|
|
351
374
|
root: control.root,
|
|
352
375
|
statusDir: control.statusDir,
|
|
353
|
-
generation:
|
|
376
|
+
generation: validatedManifest.generation,
|
|
354
377
|
pid: broker.pid,
|
|
355
378
|
startTime: broker.startTime
|
|
356
379
|
});
|
|
@@ -1048,6 +1071,9 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
1048
1071
|
workspace: params.workspace ?? { mode: 'branch-only' }
|
|
1049
1072
|
});
|
|
1050
1073
|
}
|
|
1074
|
+
if (params.forceRecreate) {
|
|
1075
|
+
throw new Error('Explicit container recreation requested.');
|
|
1076
|
+
}
|
|
1051
1077
|
if (!params.row.running) {
|
|
1052
1078
|
startFn(params.engine, params.row.name);
|
|
1053
1079
|
const initial = assess({
|
|
@@ -1192,7 +1218,8 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
1192
1218
|
`SANDBOX_RECOVERY_WORKTREE_SNAPSHOT_INVALID: expected exactly one existing worktree for '${params.branch}', found ${existingWorktrees.length}. ${remediation}`
|
|
1193
1219
|
);
|
|
1194
1220
|
}
|
|
1195
|
-
const
|
|
1221
|
+
const worktreeProbe = worktreeProbeForEngine(params.engine, deps?.probe);
|
|
1222
|
+
const beforeInspection = inspectWorktree(existingWorktrees[0]!, { probe: worktreeProbe });
|
|
1196
1223
|
if (beforeInspection.status === 'failed') {
|
|
1197
1224
|
throw new Error(`SANDBOX_RECOVERY_WORKTREE_SNAPSHOT_INVALID: ${beforeInspection.message}`);
|
|
1198
1225
|
}
|
|
@@ -1204,7 +1231,9 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
1204
1231
|
);
|
|
1205
1232
|
}
|
|
1206
1233
|
|
|
1207
|
-
const warning =
|
|
1234
|
+
const warning = params.forceRecreate
|
|
1235
|
+
? `Explicit sandbox recreation requested. Replacing only the container. ${dataBoundary}`
|
|
1236
|
+
: `Sandbox recovery failed in place. Replacing only the container. ${dataBoundary}`;
|
|
1208
1237
|
warnings.push(warning);
|
|
1209
1238
|
(params.writeWarning ?? ((message) => process.stderr.write(`${message}\n`)))(warning);
|
|
1210
1239
|
const runVerboseFn = deps?.runVerbose ?? runVerboseEngine;
|
|
@@ -1245,7 +1274,7 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
|
|
|
1245
1274
|
replacementFailure = error;
|
|
1246
1275
|
}
|
|
1247
1276
|
|
|
1248
|
-
const afterInspection = inspectWorktree(before.worktree);
|
|
1277
|
+
const afterInspection = inspectWorktree(before.worktree, { probe: worktreeProbe });
|
|
1249
1278
|
if (afterInspection.status === 'failed' || afterInspection.snapshot.identity !== before.identity) {
|
|
1250
1279
|
const detail = afterInspection.status === 'failed'
|
|
1251
1280
|
? afterInspection.message
|
|
@@ -47,6 +47,7 @@ export function sandboxWorkspaceViewPaths(params: Readonly<{
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export type SandboxControlSetup = Readonly<{
|
|
50
|
+
root: string;
|
|
50
51
|
channelDir: string;
|
|
51
52
|
statusDir: string;
|
|
52
53
|
manifestPath: string;
|
|
@@ -84,44 +85,6 @@ function assertSafeDirectory(directory: string, expectedBase: string): void {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
function assertSafeMountTargetPath(target: string, worktreeRoot: string, canonicalWorktreeRoot: string): void {
|
|
88
|
-
const relative = path.relative(worktreeRoot, target);
|
|
89
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
90
|
-
throw new Error(`Sandbox workspace mount target escapes its worktree: ${target}`);
|
|
91
|
-
}
|
|
92
|
-
let current = worktreeRoot;
|
|
93
|
-
for (const segment of ['', ...relative.split(path.sep).filter(Boolean)]) {
|
|
94
|
-
current = segment ? path.join(current, segment) : current;
|
|
95
|
-
try {
|
|
96
|
-
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
97
|
-
throw new Error(`Sandbox workspace mount target must not contain a symbolic link: ${current}`);
|
|
98
|
-
}
|
|
99
|
-
const canonicalRelative = path.relative(canonicalWorktreeRoot, fs.realpathSync.native(current));
|
|
100
|
-
if (canonicalRelative.startsWith('..') || path.isAbsolute(canonicalRelative)) {
|
|
101
|
-
throw new Error(`Sandbox workspace mount target escapes its real worktree: ${current}`);
|
|
102
|
-
}
|
|
103
|
-
} catch (error) {
|
|
104
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function prepareSandboxWorkspaceMountTargets(worktreeRoot: string): void {
|
|
110
|
-
const resolvedWorktreeRoot = path.resolve(worktreeRoot);
|
|
111
|
-
const canonicalWorktreeRoot = fs.realpathSync.native(resolvedWorktreeRoot);
|
|
112
|
-
const workspaceRoot = path.resolve(resolvedWorktreeRoot, '.agents', 'workspace');
|
|
113
|
-
const statePaths = sandboxWorkspaceViewStatePaths(workspaceRoot);
|
|
114
|
-
const registryTarget = path.join(workspaceRoot, 'active', '.short-ids.json');
|
|
115
|
-
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget]) {
|
|
116
|
-
assertSafeMountTargetPath(target, resolvedWorktreeRoot, canonicalWorktreeRoot);
|
|
117
|
-
}
|
|
118
|
-
fs.mkdirSync(workspaceRoot, { recursive: true, mode: 0o700 });
|
|
119
|
-
for (const { hostPath } of statePaths) {
|
|
120
|
-
fs.mkdirSync(hostPath, { recursive: true, mode: 0o700 });
|
|
121
|
-
}
|
|
122
|
-
fs.closeSync(fs.openSync(registryTarget, 'a', 0o600));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
88
|
export function assertSandboxTaskSource(repoRoot: string, taskId: string): string {
|
|
126
89
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId)) throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
127
90
|
const activeRoot = fs.realpathSync.native(path.join(repoRoot, '.agents', 'workspace', 'active'));
|
|
@@ -137,6 +100,43 @@ export function assertSandboxTaskSource(repoRoot: string, taskId: string): strin
|
|
|
137
100
|
return canonical;
|
|
138
101
|
}
|
|
139
102
|
|
|
103
|
+
export function prepareSandboxWorkspaceMountTarget(worktreeRoot: string): string {
|
|
104
|
+
const worktree = path.resolve(worktreeRoot);
|
|
105
|
+
const canonicalWorktree = fs.realpathSync.native(worktree);
|
|
106
|
+
const agentsRoot = path.join(worktree, '.agents');
|
|
107
|
+
if (fs.existsSync(agentsRoot)) {
|
|
108
|
+
const agentsStat = fs.lstatSync(agentsRoot);
|
|
109
|
+
if (!agentsStat.isDirectory() || agentsStat.isSymbolicLink()) {
|
|
110
|
+
throw new Error(`Sandbox workspace mount ancestor must be a real directory: ${agentsRoot}`);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
fs.mkdirSync(agentsRoot, { mode: 0o700 });
|
|
114
|
+
}
|
|
115
|
+
const canonicalAgents = fs.realpathSync.native(agentsRoot);
|
|
116
|
+
if (path.dirname(canonicalAgents) !== canonicalWorktree) {
|
|
117
|
+
throw new Error(`Sandbox workspace mount ancestor escapes the worktree: ${agentsRoot}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const target = path.join(agentsRoot, 'workspace');
|
|
121
|
+
if (fs.existsSync(target)) {
|
|
122
|
+
const stat = fs.lstatSync(target);
|
|
123
|
+
if (stat.isSymbolicLink()) {
|
|
124
|
+
throw new Error(`Sandbox workspace mount target must not be a symbolic link: ${target}`);
|
|
125
|
+
}
|
|
126
|
+
if (!stat.isDirectory()) {
|
|
127
|
+
throw new Error(`Sandbox workspace mount target must be a directory: ${target}`);
|
|
128
|
+
}
|
|
129
|
+
if (path.dirname(fs.realpathSync.native(target)) !== canonicalAgents) {
|
|
130
|
+
throw new Error(`Sandbox workspace mount target escapes the worktree: ${target}`);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
fs.mkdirSync(target, { mode: 0o700 });
|
|
134
|
+
}
|
|
135
|
+
fs.chmodSync(target, 0o700);
|
|
136
|
+
fs.accessSync(target, fs.constants.W_OK);
|
|
137
|
+
return target;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
140
|
export function materializeSandboxWorkspaceView(params: Readonly<{
|
|
141
141
|
base: string;
|
|
142
142
|
project: string;
|
|
@@ -180,6 +180,7 @@ export function materializeSandboxControl(params: Readonly<{
|
|
|
180
180
|
container: string;
|
|
181
181
|
branch: string;
|
|
182
182
|
identity: SandboxWorkspaceIdentity;
|
|
183
|
+
engine?: string;
|
|
183
184
|
}>): SandboxControlSetup {
|
|
184
185
|
const { root, channelDir, statusDir, processingDir, manifestPath } = sandboxControlPaths(params);
|
|
185
186
|
assertSafeDirectory(root, path.resolve(params.base));
|
|
@@ -201,11 +202,13 @@ export function materializeSandboxControl(params: Readonly<{
|
|
|
201
202
|
const generation = randomBytes(16).toString('hex');
|
|
202
203
|
const repoRoot = fs.realpathSync.native(params.repoRoot);
|
|
203
204
|
const manifest: SandboxControlManifest = {
|
|
204
|
-
version:
|
|
205
|
+
version: 4,
|
|
206
|
+
engine: params.engine ?? 'docker',
|
|
205
207
|
repoRoot,
|
|
206
208
|
worktreeRoot: fs.realpathSync.native(params.worktreeRoot),
|
|
207
209
|
project: params.project,
|
|
208
210
|
container: params.container,
|
|
211
|
+
containerIdentity: { id: '', labels: {} },
|
|
209
212
|
branch: params.branch,
|
|
210
213
|
mode: params.identity.mode,
|
|
211
214
|
taskId: params.identity.mode === 'task-bound' ? params.identity.taskId : null,
|
|
@@ -216,5 +219,23 @@ export function materializeSandboxControl(params: Readonly<{
|
|
|
216
219
|
processingDir
|
|
217
220
|
};
|
|
218
221
|
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest)}\n`, { mode: 0o600 });
|
|
219
|
-
return { channelDir, statusDir, manifestPath, token, generation };
|
|
222
|
+
return { root, channelDir, statusDir, manifestPath, token, generation };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function finalizeSandboxControlManifest(
|
|
226
|
+
setup: SandboxControlSetup,
|
|
227
|
+
identity: Readonly<{ engine: string; id: string; labels: Readonly<Record<string, string>> }>
|
|
228
|
+
): void {
|
|
229
|
+
if (!identity.engine || !identity.id) throw new Error('SANDBOX_CONTROL_CONTAINER_ID_INVALID');
|
|
230
|
+
const manifest = JSON.parse(fs.readFileSync(setup.manifestPath, 'utf8')) as Record<string, unknown>;
|
|
231
|
+
manifest.version = 4;
|
|
232
|
+
manifest.engine = identity.engine;
|
|
233
|
+
manifest.containerIdentity = { id: identity.id, labels: { ...identity.labels } };
|
|
234
|
+
const temporary = `${setup.manifestPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
235
|
+
try {
|
|
236
|
+
fs.writeFileSync(temporary, `${JSON.stringify(manifest)}\n`, { mode: 0o600, flag: 'wx' });
|
|
237
|
+
fs.renameSync(temporary, setup.manifestPath);
|
|
238
|
+
} finally {
|
|
239
|
+
fs.rmSync(temporary, { force: true });
|
|
240
|
+
}
|
|
220
241
|
}
|
package/lib/server/daemon.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
removePidFileIfMatches,
|
|
12
12
|
writePidRecord
|
|
13
13
|
} from './process-state.ts';
|
|
14
|
-
import type { PidRecord } from './process-state.ts';
|
|
14
|
+
import type { PidRecord, ProcessIdentity, ProcessState } from './process-state.ts';
|
|
15
15
|
|
|
16
16
|
export { isProcessAlive } from './process-state.ts';
|
|
17
17
|
|
|
@@ -51,13 +51,13 @@ async function waitForProcessIdentity(pid: number, timeoutMs = 2000): Promise<Pi
|
|
|
51
51
|
const deadline = Date.now() + timeoutMs;
|
|
52
52
|
while (Date.now() < deadline) {
|
|
53
53
|
const startTime = getProcessStartTime(pid);
|
|
54
|
-
if (startTime !== null) return { version:
|
|
54
|
+
if (startTime !== null) return { version: 2, pid, startTime };
|
|
55
55
|
await delay(25);
|
|
56
56
|
}
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function terminateIfMatching(record:
|
|
60
|
+
function terminateIfMatching(record: ProcessIdentity): void {
|
|
61
61
|
if (!processIdentityMatches(record)) return;
|
|
62
62
|
const command = buildStopCommand(record.pid, process.platform);
|
|
63
63
|
try {
|
|
@@ -83,6 +83,9 @@ export async function start({ foreground = false }: StartOptions = {}): Promise<
|
|
|
83
83
|
process.stdout.write(`server already running (pid ${existing.record.pid})\n`);
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
+
if (existing.kind === 'legacy-json' || existing.kind === 'legacy-pid-only' || existing.kind === 'legacy-unknown') {
|
|
87
|
+
throw new Error('server: legacy pid record requires the matching previous version to stop it before starting');
|
|
88
|
+
}
|
|
86
89
|
if (existing.kind !== 'missing') {
|
|
87
90
|
removePidFileIfMatches(pidPath, existing.snapshot);
|
|
88
91
|
}
|
|
@@ -135,6 +138,9 @@ export async function stop(): Promise<void> {
|
|
|
135
138
|
return;
|
|
136
139
|
}
|
|
137
140
|
if (state.kind !== 'running') {
|
|
141
|
+
if (state.kind === 'legacy-json' || state.kind === 'legacy-pid-only' || state.kind === 'legacy-unknown') {
|
|
142
|
+
throw new Error('server: legacy pid record requires the matching previous version to stop it');
|
|
143
|
+
}
|
|
138
144
|
removePidFileIfMatches(config.pidFile, state.snapshot);
|
|
139
145
|
process.stdout.write('server is not running (removed stale pid file)\n');
|
|
140
146
|
return;
|
|
@@ -170,6 +176,10 @@ export function status(): void {
|
|
|
170
176
|
const state = readProcessState(config.pidFile);
|
|
171
177
|
|
|
172
178
|
if (state.kind !== 'running') {
|
|
179
|
+
if (state.kind === 'legacy-json' || state.kind === 'legacy-pid-only' || state.kind === 'legacy-unknown') {
|
|
180
|
+
process.stdout.write('server: legacy pid record requires the matching previous version to stop it\n');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
173
183
|
if (state.kind !== 'missing') removePidFileIfMatches(config.pidFile, state.snapshot);
|
|
174
184
|
process.stdout.write('server: stopped\n');
|
|
175
185
|
return;
|
|
@@ -2,22 +2,34 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
|
|
5
|
-
export type
|
|
6
|
-
|
|
5
|
+
export type ProcessStartTime = number;
|
|
6
|
+
|
|
7
|
+
export type ProcessIdentity = Readonly<{
|
|
7
8
|
pid: number;
|
|
8
|
-
startTime:
|
|
9
|
+
startTime: ProcessStartTime;
|
|
10
|
+
}>;
|
|
11
|
+
|
|
12
|
+
export type PidRecord = ProcessIdentity & {
|
|
13
|
+
version: 2;
|
|
9
14
|
};
|
|
10
15
|
|
|
11
16
|
export type ProcessStartTimeQuery = { command: string; args: string[] };
|
|
12
17
|
|
|
13
18
|
export type ProcessState =
|
|
14
19
|
| { kind: 'missing'; snapshot: null }
|
|
15
|
-
| { kind: '
|
|
16
|
-
| { kind: 'legacy'; snapshot: string;
|
|
20
|
+
| { kind: 'legacy-json'; snapshot: string; legacy: LegacyProcessSnapshot }
|
|
21
|
+
| { kind: 'legacy-pid-only'; snapshot: string; legacy: LegacyProcessSnapshot }
|
|
22
|
+
| { kind: 'legacy-unknown'; snapshot: string; legacy: LegacyProcessSnapshot; reason: string }
|
|
17
23
|
| { kind: 'stale'; snapshot: string; record: PidRecord }
|
|
18
24
|
| { kind: 'running'; snapshot: string; record: PidRecord };
|
|
19
25
|
|
|
20
|
-
export type
|
|
26
|
+
export type LegacyProcessSnapshot = Readonly<{
|
|
27
|
+
pid?: number;
|
|
28
|
+
raw: string;
|
|
29
|
+
format: 'json-v1' | 'pid-only' | 'malformed';
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
export type LinuxProcessStat = { state: string; startTime: number };
|
|
21
33
|
|
|
22
34
|
export function parseLinuxProcessStat(raw: string): LinuxProcessStat | null {
|
|
23
35
|
const closeParen = raw.lastIndexOf(')');
|
|
@@ -28,7 +40,9 @@ export function parseLinuxProcessStat(raw: string): LinuxProcessStat | null {
|
|
|
28
40
|
if (typeof state !== 'string' || state.length !== 1 || !/^\d+$/.test(startTime ?? '')) {
|
|
29
41
|
return null;
|
|
30
42
|
}
|
|
31
|
-
|
|
43
|
+
const numericStartTime = Number(startTime);
|
|
44
|
+
if (!Number.isSafeInteger(numericStartTime)) return null;
|
|
45
|
+
return { state, startTime: numericStartTime };
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
export function buildProcessStartTimeQuery(
|
|
@@ -68,10 +82,22 @@ export function isProcessAlive(pid: number, platform: NodeJS.Platform = process.
|
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
function parseDarwinStartTime(output: string): number | null {
|
|
86
|
+
const match = output.trim().match(/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) ([A-Z][a-z]{2}) (\d{1,2}) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/);
|
|
87
|
+
if (!match) return null;
|
|
88
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
89
|
+
const month = months.indexOf(match[1]!);
|
|
90
|
+
if (month < 0) return null;
|
|
91
|
+
const timestamp = Date.UTC(
|
|
92
|
+
Number(match[6]), month, Number(match[2]), Number(match[3]), Number(match[4]), Number(match[5])
|
|
93
|
+
);
|
|
94
|
+
return Number.isNaN(timestamp) ? null : timestamp;
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
export function getProcessStartTime(
|
|
72
98
|
pid: number,
|
|
73
99
|
platform: NodeJS.Platform = process.platform
|
|
74
|
-
):
|
|
100
|
+
): ProcessStartTime | null {
|
|
75
101
|
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
76
102
|
if (platform === 'linux') {
|
|
77
103
|
try {
|
|
@@ -85,43 +111,71 @@ export function getProcessStartTime(
|
|
|
85
111
|
const query = buildProcessStartTimeQuery(pid, platform);
|
|
86
112
|
if (query === null) return null;
|
|
87
113
|
try {
|
|
88
|
-
const output = execFileSync(query.command, query.args, {
|
|
114
|
+
const output = execFileSync(query.command, query.args, {
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
env: platform === 'darwin' ? { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } : process.env
|
|
117
|
+
}).trim();
|
|
89
118
|
if (output.length === 0) return null;
|
|
90
119
|
if (platform === 'win32') {
|
|
91
120
|
const timestamp = new Date(output);
|
|
92
|
-
return Number.isNaN(timestamp.getTime()) ? null : timestamp.
|
|
121
|
+
return Number.isNaN(timestamp.getTime()) ? null : timestamp.getTime();
|
|
93
122
|
}
|
|
94
|
-
return output;
|
|
123
|
+
if (platform === 'darwin') return parseDarwinStartTime(output);
|
|
124
|
+
return null;
|
|
95
125
|
} catch {
|
|
96
126
|
return null;
|
|
97
127
|
}
|
|
98
128
|
}
|
|
99
129
|
|
|
100
|
-
function parsePidRecord(
|
|
130
|
+
function parsePidRecord(
|
|
131
|
+
raw: string
|
|
132
|
+
):
|
|
133
|
+
| { kind: 'legacy-json'; legacy: LegacyProcessSnapshot }
|
|
134
|
+
| { kind: 'legacy-pid-only'; legacy: LegacyProcessSnapshot }
|
|
135
|
+
| { kind: 'legacy-unknown'; legacy: LegacyProcessSnapshot; reason: string }
|
|
136
|
+
| { kind: 'record'; record: PidRecord } {
|
|
101
137
|
const trimmed = raw.trim();
|
|
102
138
|
if (/^[1-9]\d*$/.test(trimmed)) {
|
|
103
139
|
const pid = Number(trimmed);
|
|
104
|
-
return Number.isSafeInteger(pid)
|
|
140
|
+
return Number.isSafeInteger(pid)
|
|
141
|
+
? { kind: 'legacy-pid-only', legacy: { pid, raw, format: 'pid-only' } }
|
|
142
|
+
: { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'pid is not a safe integer' };
|
|
105
143
|
}
|
|
106
144
|
try {
|
|
107
|
-
const value = JSON.parse(trimmed) as
|
|
145
|
+
const value = JSON.parse(trimmed) as { version?: unknown; pid?: unknown; startTime?: unknown } | null;
|
|
146
|
+
const pidValue: unknown = value?.pid;
|
|
147
|
+
const startTimeValue: unknown = value?.startTime;
|
|
108
148
|
if (
|
|
109
149
|
value !== null &&
|
|
110
|
-
value.version ===
|
|
111
|
-
|
|
112
|
-
(
|
|
113
|
-
|
|
114
|
-
|
|
150
|
+
value.version === 2 &&
|
|
151
|
+
typeof pidValue === 'number' &&
|
|
152
|
+
Number.isSafeInteger(pidValue) &&
|
|
153
|
+
pidValue > 0 &&
|
|
154
|
+
typeof startTimeValue === 'number' &&
|
|
155
|
+
Number.isSafeInteger(startTimeValue) &&
|
|
156
|
+
startTimeValue >= 0
|
|
115
157
|
) {
|
|
158
|
+
const startTime = startTimeValue as number;
|
|
116
159
|
return {
|
|
117
160
|
kind: 'record',
|
|
118
|
-
record: { version:
|
|
161
|
+
record: { version: 2, pid: pidValue, startTime }
|
|
119
162
|
};
|
|
120
163
|
}
|
|
164
|
+
if (
|
|
165
|
+
value !== null &&
|
|
166
|
+
value.version === 1 &&
|
|
167
|
+
typeof pidValue === 'number' &&
|
|
168
|
+
Number.isSafeInteger(pidValue) &&
|
|
169
|
+
pidValue > 0 &&
|
|
170
|
+
typeof value.startTime === 'string' &&
|
|
171
|
+
value.startTime.length > 0
|
|
172
|
+
) {
|
|
173
|
+
return { kind: 'legacy-json', legacy: { pid: pidValue, raw, format: 'json-v1' } };
|
|
174
|
+
}
|
|
175
|
+
return { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'unsupported PID record schema' };
|
|
121
176
|
} catch {
|
|
122
|
-
|
|
177
|
+
return { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'invalid PID record JSON' };
|
|
123
178
|
}
|
|
124
|
-
return { kind: 'invalid' };
|
|
125
179
|
}
|
|
126
180
|
|
|
127
181
|
export function readProcessState(pidFile: string): ProcessState {
|
|
@@ -130,12 +184,13 @@ export function readProcessState(pidFile: string): ProcessState {
|
|
|
130
184
|
snapshot = fs.readFileSync(pidFile, 'utf8');
|
|
131
185
|
} catch (error) {
|
|
132
186
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { kind: 'missing', snapshot: null };
|
|
133
|
-
return { kind: '
|
|
187
|
+
return { kind: 'legacy-unknown', snapshot: '', legacy: { raw: '', format: 'malformed' }, reason: 'PID record cannot be read' };
|
|
134
188
|
}
|
|
135
189
|
|
|
136
190
|
const parsed = parsePidRecord(snapshot);
|
|
137
|
-
if (parsed.kind === '
|
|
138
|
-
if (parsed.kind === 'legacy') return { kind:
|
|
191
|
+
if (parsed.kind === 'legacy-json') return { kind: parsed.kind, snapshot, legacy: parsed.legacy };
|
|
192
|
+
if (parsed.kind === 'legacy-pid-only') return { kind: parsed.kind, snapshot, legacy: parsed.legacy };
|
|
193
|
+
if (parsed.kind === 'legacy-unknown') return { kind: parsed.kind, snapshot, legacy: parsed.legacy, reason: parsed.reason };
|
|
139
194
|
|
|
140
195
|
const currentStartTime = getProcessStartTime(parsed.record.pid);
|
|
141
196
|
if (currentStartTime === null || currentStartTime !== parsed.record.startTime) {
|
|
@@ -178,6 +233,6 @@ export function removePidRecordIfMatches(pidFile: string, expected: PidRecord):
|
|
|
178
233
|
return removePidFileIfMatches(pidFile, state.snapshot);
|
|
179
234
|
}
|
|
180
235
|
|
|
181
|
-
export function processIdentityMatches(
|
|
182
|
-
return getProcessStartTime(
|
|
236
|
+
export function processIdentityMatches(identity: ProcessIdentity): boolean {
|
|
237
|
+
return getProcessStartTime(identity.pid) === identity.startTime;
|
|
183
238
|
}
|