@ai-development-environment/control-agent 0.0.143 → 0.0.144
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/control-agent.js +161 -20
- package/package.json +1 -1
package/dist/control-agent.js
CHANGED
|
@@ -7445,17 +7445,18 @@ var AgentGraphQLClient = class {
|
|
|
7445
7445
|
throw new Error("Downloaded dSYM checksum did not match");
|
|
7446
7446
|
}
|
|
7447
7447
|
}
|
|
7448
|
-
completeJob(jobId, status2, result, error, executionToken) {
|
|
7448
|
+
completeJob(jobId, status2, result, error, executionToken, cleanupVerified = true) {
|
|
7449
7449
|
return this.request(
|
|
7450
|
-
`mutation CompleteJob($jobId: ID!, $status: AgentJobStatus!, $result: JSON, $error: String, $executionToken: String) {
|
|
7451
|
-
completeAgentJob(jobId: $jobId, status: $status, result: $result, error: $error, executionToken: $executionToken) { id status }
|
|
7450
|
+
`mutation CompleteJob($jobId: ID!, $status: AgentJobStatus!, $result: JSON, $error: String, $executionToken: String, $cleanupVerified: Boolean!) {
|
|
7451
|
+
completeAgentJob(jobId: $jobId, status: $status, result: $result, error: $error, executionToken: $executionToken, cleanupVerified: $cleanupVerified) { id status }
|
|
7452
7452
|
}`,
|
|
7453
7453
|
{
|
|
7454
7454
|
jobId,
|
|
7455
7455
|
status: status2,
|
|
7456
7456
|
result,
|
|
7457
7457
|
error,
|
|
7458
|
-
...executionToken ? { executionToken } : {}
|
|
7458
|
+
...executionToken ? { executionToken } : {},
|
|
7459
|
+
cleanupVerified
|
|
7459
7460
|
}
|
|
7460
7461
|
);
|
|
7461
7462
|
}
|
|
@@ -11742,7 +11743,7 @@ function tailscaleServeRemovePayload(value) {
|
|
|
11742
11743
|
// package.json
|
|
11743
11744
|
var package_default = {
|
|
11744
11745
|
name: "@ai-development-environment/control-agent",
|
|
11745
|
-
version: "0.0.
|
|
11746
|
+
version: "0.0.144",
|
|
11746
11747
|
description: "Control agent for the AI Development Environment",
|
|
11747
11748
|
license: "MIT",
|
|
11748
11749
|
repository: {
|
|
@@ -11900,6 +11901,12 @@ import {
|
|
|
11900
11901
|
writeFileSync
|
|
11901
11902
|
} from "node:fs";
|
|
11902
11903
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
11904
|
+
var ProcessInspectionTimeoutError = class extends Error {
|
|
11905
|
+
constructor() {
|
|
11906
|
+
super("Process inspection timed out after 30 seconds");
|
|
11907
|
+
this.name = "ProcessInspectionTimeoutError";
|
|
11908
|
+
}
|
|
11909
|
+
};
|
|
11903
11910
|
var executions = new AsyncLocalStorage();
|
|
11904
11911
|
function executionEnvironment() {
|
|
11905
11912
|
const journal = executions.getStore();
|
|
@@ -12043,11 +12050,18 @@ var spawnExecutionProcess = ((command3, args = [], options = {}) => {
|
|
|
12043
12050
|
});
|
|
12044
12051
|
function processTable(environment = false) {
|
|
12045
12052
|
const args = environment ? ["axeww", "-o", "pid=,pgid=,stat=,command="] : ["axww", "-o", "pid=,pgid=,stat=,command="];
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12053
|
+
let output;
|
|
12054
|
+
try {
|
|
12055
|
+
output = execFileSync("/bin/ps", args, {
|
|
12056
|
+
encoding: "utf8",
|
|
12057
|
+
timeout: 3e4,
|
|
12058
|
+
maxBuffer: 32 * 1024 * 1024
|
|
12059
|
+
});
|
|
12060
|
+
} catch (error) {
|
|
12061
|
+
if (error.code === "ETIMEDOUT")
|
|
12062
|
+
throw new ProcessInspectionTimeoutError();
|
|
12063
|
+
throw error;
|
|
12064
|
+
}
|
|
12051
12065
|
return output.split("\n").flatMap((line) => {
|
|
12052
12066
|
const match = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.*)$/.exec(line);
|
|
12053
12067
|
return match ? [
|
|
@@ -12086,6 +12100,7 @@ var delay = (ms, signal2) => new Promise((resolve13) => {
|
|
|
12086
12100
|
if (signal2?.aborted) finish();
|
|
12087
12101
|
});
|
|
12088
12102
|
async function cleanupGroups(journal, signal2) {
|
|
12103
|
+
if (!journal.groups.length) return;
|
|
12089
12104
|
const groups = new Set(journal.groups);
|
|
12090
12105
|
const ownGroup = process.platform !== "win32" ? processTable().find((entry) => entry.pid === process.pid)?.group : process.pid;
|
|
12091
12106
|
if (process.platform !== "win32") {
|
|
@@ -12166,12 +12181,40 @@ async function withExecutionJournal(identity, task) {
|
|
|
12166
12181
|
try {
|
|
12167
12182
|
return await executions.run(
|
|
12168
12183
|
journal,
|
|
12169
|
-
() => task({
|
|
12184
|
+
() => task({
|
|
12185
|
+
cleanupProcesses: () => cleanupGroups(journal),
|
|
12186
|
+
settle,
|
|
12187
|
+
checkpointCompletion: (completion) => {
|
|
12188
|
+
journal.completion = completion;
|
|
12189
|
+
persist(journal);
|
|
12190
|
+
}
|
|
12191
|
+
})
|
|
12170
12192
|
);
|
|
12171
12193
|
} finally {
|
|
12172
12194
|
await settle();
|
|
12173
12195
|
}
|
|
12174
12196
|
}
|
|
12197
|
+
function executionCompletion(identity) {
|
|
12198
|
+
let journal;
|
|
12199
|
+
try {
|
|
12200
|
+
journal = JSON.parse(readFileSync(journalPath(identity), "utf8"));
|
|
12201
|
+
} catch (error) {
|
|
12202
|
+
if (error.code === "ENOENT") return void 0;
|
|
12203
|
+
throw error;
|
|
12204
|
+
}
|
|
12205
|
+
if (journal.version !== 1 || journal.agentId !== identity.agentId || journal.jobId !== identity.jobId || journal.executionToken !== identity.executionToken || journal.marker !== digest(identity.executionToken) || !Array.isArray(journal.groups))
|
|
12206
|
+
throw new Error(
|
|
12207
|
+
"Execution journal identity is invalid; its claim remains protected"
|
|
12208
|
+
);
|
|
12209
|
+
const completion = journal.completion;
|
|
12210
|
+
if (completion && (!["SUCCEEDED", "FAILED", "CANCELLED", "TIMED_OUT"].includes(
|
|
12211
|
+
completion.status
|
|
12212
|
+
) || !completion.result || typeof completion.result !== "object" || typeof completion.result.cancelled !== "boolean" || typeof completion.result.timedOut !== "boolean" || !(completion.result.exitCode === null || Number.isInteger(completion.result.exitCode))))
|
|
12213
|
+
throw new Error(
|
|
12214
|
+
"Execution completion checkpoint is invalid; its claim remains protected"
|
|
12215
|
+
);
|
|
12216
|
+
return completion;
|
|
12217
|
+
}
|
|
12175
12218
|
async function recoverExecution(identity, signal2) {
|
|
12176
12219
|
let journal;
|
|
12177
12220
|
try {
|
|
@@ -50043,7 +50086,7 @@ var runIosBuildWithPaths = async (payload, timeoutMs, signal2, onLog, context) =
|
|
|
50043
50086
|
finalSignal
|
|
50044
50087
|
);
|
|
50045
50088
|
const codeStateObservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
50046
|
-
|
|
50089
|
+
const completedResult = {
|
|
50047
50090
|
exitCode,
|
|
50048
50091
|
signal: buildResult?.signal ?? preflightResult?.signal ?? null,
|
|
50049
50092
|
timedOut,
|
|
@@ -50058,6 +50101,8 @@ var runIosBuildWithPaths = async (payload, timeoutMs, signal2, onLog, context) =
|
|
|
50058
50101
|
finalStateHash,
|
|
50059
50102
|
codeStateObservedAt
|
|
50060
50103
|
};
|
|
50104
|
+
await context?.finalizeBuildResult?.(completedResult);
|
|
50105
|
+
return completedResult;
|
|
50061
50106
|
};
|
|
50062
50107
|
let enteredProtectedBuild = false;
|
|
50063
50108
|
try {
|
|
@@ -57175,6 +57220,16 @@ async function refreshFetchedCodebase(client, input, timeoutMs, signal2, withIns
|
|
|
57175
57220
|
}
|
|
57176
57221
|
|
|
57177
57222
|
// src/job-executor.ts
|
|
57223
|
+
var PROCESS_INSPECTION_WARNING = "Process inspection timed out after 30 seconds. The completed build result and artifacts were preserved; process cleanup will be retried.";
|
|
57224
|
+
var resultStatus = (result) => result.cancelled ? "CANCELLED" : result.timedOut ? "TIMED_OUT" : result.exitCode === 0 ? "SUCCEEDED" : "FAILED";
|
|
57225
|
+
function warnPreservedBuild(completion) {
|
|
57226
|
+
completion.result.warnings = [
|
|
57227
|
+
.../* @__PURE__ */ new Set([
|
|
57228
|
+
...completion.result.warnings ?? [],
|
|
57229
|
+
PROCESS_INSPECTION_WARNING
|
|
57230
|
+
])
|
|
57231
|
+
];
|
|
57232
|
+
}
|
|
57178
57233
|
function resourceSignature(value) {
|
|
57179
57234
|
return JSON.stringify(
|
|
57180
57235
|
parseOperationDescriptor(value).resources.map(
|
|
@@ -57198,18 +57253,20 @@ var JobExecutor = class {
|
|
|
57198
57253
|
repositoryCoordinator;
|
|
57199
57254
|
running = /* @__PURE__ */ new Map();
|
|
57200
57255
|
stopping = false;
|
|
57256
|
+
cleanupPending = /* @__PURE__ */ new Set();
|
|
57201
57257
|
shutdown = new AbortController();
|
|
57202
57258
|
execute(job) {
|
|
57203
57259
|
if (this.stopping || this.running.has(job.id)) return;
|
|
57204
57260
|
const controller = new AbortController();
|
|
57205
|
-
const task = this.run(job, controller).finally(
|
|
57206
|
-
|
|
57207
|
-
|
|
57261
|
+
const task = this.run(job, controller).finally(() => {
|
|
57262
|
+
this.running.delete(job.id);
|
|
57263
|
+
this.cleanupPending.delete(job.id);
|
|
57264
|
+
});
|
|
57208
57265
|
this.running.set(job.id, { controller, task });
|
|
57209
57266
|
}
|
|
57210
57267
|
cancel(jobId) {
|
|
57211
57268
|
const running = this.running.get(jobId);
|
|
57212
|
-
running?.controller.abort();
|
|
57269
|
+
if (!this.cleanupPending.has(jobId)) running?.controller.abort();
|
|
57213
57270
|
return Boolean(running);
|
|
57214
57271
|
}
|
|
57215
57272
|
/** Hold source resources until interrupted subprocess cleanup is verified. */
|
|
@@ -57233,19 +57290,46 @@ var JobExecutor = class {
|
|
|
57233
57290
|
async () => {
|
|
57234
57291
|
entered.resolve();
|
|
57235
57292
|
let reported = false;
|
|
57293
|
+
let completion;
|
|
57294
|
+
let checkpointRead = false;
|
|
57295
|
+
let completedStatus = status2;
|
|
57236
57296
|
while (!this.shutdown.signal.aborted) {
|
|
57237
57297
|
try {
|
|
57298
|
+
if (!checkpointRead) {
|
|
57299
|
+
completion = job.kind === IOS_BUILD_JOB_KIND ? executionCompletion(identity) : void 0;
|
|
57300
|
+
checkpointRead = true;
|
|
57301
|
+
if (completion && ["RUNNING", "CANCELLING"].includes(job.status))
|
|
57302
|
+
completedStatus = completion.status;
|
|
57303
|
+
}
|
|
57238
57304
|
await recoverExecution(identity, this.shutdown.signal);
|
|
57239
57305
|
await this.client.completeJob(
|
|
57240
57306
|
job.id,
|
|
57241
|
-
|
|
57242
|
-
|
|
57243
|
-
"Agent service restarted; interrupted processes were stopped and verified",
|
|
57307
|
+
completedStatus,
|
|
57308
|
+
completion?.result,
|
|
57309
|
+
completion?.error ?? (completion ? void 0 : "Agent service restarted; interrupted processes were stopped and verified"),
|
|
57244
57310
|
job.executionToken
|
|
57245
57311
|
);
|
|
57246
57312
|
forgetExecution(identity);
|
|
57247
57313
|
return;
|
|
57248
57314
|
} catch (error) {
|
|
57315
|
+
if (completion && error instanceof ProcessInspectionTimeoutError) {
|
|
57316
|
+
warnPreservedBuild(completion);
|
|
57317
|
+
try {
|
|
57318
|
+
await this.client.completeJob(
|
|
57319
|
+
job.id,
|
|
57320
|
+
completedStatus,
|
|
57321
|
+
completion.result,
|
|
57322
|
+
completion.error,
|
|
57323
|
+
job.executionToken,
|
|
57324
|
+
false
|
|
57325
|
+
);
|
|
57326
|
+
} catch (reportError) {
|
|
57327
|
+
console.error(
|
|
57328
|
+
`Could not preserve completed build ${job.id}:`,
|
|
57329
|
+
reportError
|
|
57330
|
+
);
|
|
57331
|
+
}
|
|
57332
|
+
}
|
|
57249
57333
|
if (!reported) {
|
|
57250
57334
|
console.error(
|
|
57251
57335
|
`Cleanup verification required for job ${job.id}; execution resources remain protected:`,
|
|
@@ -57317,6 +57401,7 @@ var JobExecutor = class {
|
|
|
57317
57401
|
const handler = handlers[claimed.kind];
|
|
57318
57402
|
if (!handler)
|
|
57319
57403
|
throw new Error(`No local handler is registered for ${claimed.kind}`);
|
|
57404
|
+
let execution;
|
|
57320
57405
|
const runHandler = () => {
|
|
57321
57406
|
controller.signal.throwIfAborted();
|
|
57322
57407
|
return handler(
|
|
@@ -57372,6 +57457,58 @@ var JobExecutor = class {
|
|
|
57372
57457
|
)
|
|
57373
57458
|
),
|
|
57374
57459
|
reportBuildProgress: (input) => this.client.reportBuildProgress(input),
|
|
57460
|
+
...resourceAware && claimed.kind === IOS_BUILD_JOB_KIND ? {
|
|
57461
|
+
finalizeBuildResult: async (completedResult) => {
|
|
57462
|
+
if (!execution)
|
|
57463
|
+
throw new Error("Build execution journal is unavailable");
|
|
57464
|
+
result = completedResult;
|
|
57465
|
+
const completion = {
|
|
57466
|
+
status: resultStatus(completedResult),
|
|
57467
|
+
result: completedResult,
|
|
57468
|
+
...completedResult.exitCode !== 0 && !completedResult.cancelled && !completedResult.timedOut ? {
|
|
57469
|
+
error: `Process exited with code ${completedResult.exitCode}`
|
|
57470
|
+
} : {}
|
|
57471
|
+
};
|
|
57472
|
+
execution.checkpointCompletion(completion);
|
|
57473
|
+
this.cleanupPending.add(claimed.id);
|
|
57474
|
+
try {
|
|
57475
|
+
await execution.settle();
|
|
57476
|
+
return;
|
|
57477
|
+
} catch (error) {
|
|
57478
|
+
if (!(error instanceof ProcessInspectionTimeoutError))
|
|
57479
|
+
throw error;
|
|
57480
|
+
}
|
|
57481
|
+
warnPreservedBuild(completion);
|
|
57482
|
+
execution.checkpointCompletion(completion);
|
|
57483
|
+
let preserved = false;
|
|
57484
|
+
while (!this.shutdown.signal.aborted) {
|
|
57485
|
+
try {
|
|
57486
|
+
if (!preserved) {
|
|
57487
|
+
await this.client.completeJob(
|
|
57488
|
+
claimed.id,
|
|
57489
|
+
completion.status,
|
|
57490
|
+
completion.result,
|
|
57491
|
+
completion.error,
|
|
57492
|
+
claimed.executionToken,
|
|
57493
|
+
false
|
|
57494
|
+
);
|
|
57495
|
+
preserved = true;
|
|
57496
|
+
}
|
|
57497
|
+
await execution.settle();
|
|
57498
|
+
return;
|
|
57499
|
+
} catch (error) {
|
|
57500
|
+
console.error(
|
|
57501
|
+
`Build ${claimed.id} result is preserved; cleanup verification will retry:`,
|
|
57502
|
+
error instanceof Error ? error.message : error
|
|
57503
|
+
);
|
|
57504
|
+
await waitForAgentRetry(1e3, this.shutdown.signal);
|
|
57505
|
+
}
|
|
57506
|
+
}
|
|
57507
|
+
throw new Error(
|
|
57508
|
+
"Build cleanup interrupted by agent shutdown; its result remains protected"
|
|
57509
|
+
);
|
|
57510
|
+
}
|
|
57511
|
+
} : {},
|
|
57375
57512
|
reportBuildDeploymentLaunched: (deploymentId, launchedAt) => this.client.reportBuildDeploymentLaunched(
|
|
57376
57513
|
claimed.id,
|
|
57377
57514
|
deploymentId,
|
|
@@ -57396,7 +57533,10 @@ var JobExecutor = class {
|
|
|
57396
57533
|
jobId: claimed.id,
|
|
57397
57534
|
executionToken: claimed.executionToken
|
|
57398
57535
|
},
|
|
57399
|
-
|
|
57536
|
+
(journal) => {
|
|
57537
|
+
execution = journal;
|
|
57538
|
+
return runHandler();
|
|
57539
|
+
}
|
|
57400
57540
|
);
|
|
57401
57541
|
else if (!codebaseId) result = await runHandler();
|
|
57402
57542
|
else if (claimed.kind === IOS_BUILD_JOB_KIND) {
|
|
@@ -57425,6 +57565,7 @@ var JobExecutor = class {
|
|
|
57425
57565
|
status2 = controller.signal.aborted ? "CANCELLED" : "FAILED";
|
|
57426
57566
|
console.error(`Job ${claimed.id} failed:`, completionError);
|
|
57427
57567
|
}
|
|
57568
|
+
if (this.stopping && this.cleanupPending.has(claimed.id)) return;
|
|
57428
57569
|
for (let attempt = 1; ; attempt++) {
|
|
57429
57570
|
try {
|
|
57430
57571
|
if (resourceAware && claimed.executionToken)
|