@ai-development-environment/control-agent 0.0.129 → 0.0.131
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 +232 -66
- package/package.json +1 -1
package/dist/control-agent.js
CHANGED
|
@@ -2288,7 +2288,7 @@ var require_websocket = __commonJS({
|
|
|
2288
2288
|
var http = __require("http");
|
|
2289
2289
|
var net = __require("net");
|
|
2290
2290
|
var tls = __require("tls");
|
|
2291
|
-
var { randomBytes, createHash:
|
|
2291
|
+
var { randomBytes, createHash: createHash9 } = __require("crypto");
|
|
2292
2292
|
var { Duplex, Readable: Readable2 } = __require("stream");
|
|
2293
2293
|
var { URL: URL2 } = __require("url");
|
|
2294
2294
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2969,7 +2969,7 @@ var require_websocket = __commonJS({
|
|
|
2969
2969
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2970
2970
|
return;
|
|
2971
2971
|
}
|
|
2972
|
-
const digest =
|
|
2972
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
2973
2973
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
2974
2974
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2975
2975
|
return;
|
|
@@ -3338,7 +3338,7 @@ var require_websocket_server = __commonJS({
|
|
|
3338
3338
|
var EventEmitter = __require("events");
|
|
3339
3339
|
var http = __require("http");
|
|
3340
3340
|
var { Duplex } = __require("stream");
|
|
3341
|
-
var { createHash:
|
|
3341
|
+
var { createHash: createHash9 } = __require("crypto");
|
|
3342
3342
|
var extension2 = require_extension();
|
|
3343
3343
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3344
3344
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3645,7 +3645,7 @@ var require_websocket_server = __commonJS({
|
|
|
3645
3645
|
);
|
|
3646
3646
|
}
|
|
3647
3647
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3648
|
-
const digest =
|
|
3648
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
3649
3649
|
const headers = [
|
|
3650
3650
|
"HTTP/1.1 101 Switching Protocols",
|
|
3651
3651
|
"Upgrade: websocket",
|
|
@@ -6438,6 +6438,42 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
6438
6438
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
6439
6439
|
var wrapper_default = import_websocket.default;
|
|
6440
6440
|
|
|
6441
|
+
// src/request-errors.ts
|
|
6442
|
+
var MAX_AGENT_REQUEST_ATTEMPTS = 5;
|
|
6443
|
+
var AgentRequestError = class extends Error {
|
|
6444
|
+
retryable;
|
|
6445
|
+
constructor(message2, status2, codes = []) {
|
|
6446
|
+
super(message2);
|
|
6447
|
+
this.name = "AgentRequestError";
|
|
6448
|
+
this.retryable = (status2 < 400 || status2 >= 500 || [408, 425, 429].includes(status2)) && !codes.some(
|
|
6449
|
+
(code) => [
|
|
6450
|
+
"UNAUTHENTICATED",
|
|
6451
|
+
"FORBIDDEN",
|
|
6452
|
+
"BAD_USER_INPUT",
|
|
6453
|
+
"GRAPHQL_VALIDATION_FAILED",
|
|
6454
|
+
"NOT_FOUND"
|
|
6455
|
+
].includes(code)
|
|
6456
|
+
) && !/^(?:Job|Run|Run attempt|Run command) not found(?: for this agent)?$/.test(
|
|
6457
|
+
message2
|
|
6458
|
+
);
|
|
6459
|
+
}
|
|
6460
|
+
};
|
|
6461
|
+
function isRetryableAgentError(error) {
|
|
6462
|
+
return !(error instanceof AgentRequestError) || error.retryable;
|
|
6463
|
+
}
|
|
6464
|
+
function waitForAgentRetry(delay3, signal2) {
|
|
6465
|
+
return new Promise((resolve11) => {
|
|
6466
|
+
const finish = () => {
|
|
6467
|
+
clearTimeout(timer);
|
|
6468
|
+
signal2.removeEventListener("abort", finish);
|
|
6469
|
+
resolve11();
|
|
6470
|
+
};
|
|
6471
|
+
const timer = setTimeout(finish, delay3);
|
|
6472
|
+
if (signal2.aborted) finish();
|
|
6473
|
+
else signal2.addEventListener("abort", finish, { once: true });
|
|
6474
|
+
});
|
|
6475
|
+
}
|
|
6476
|
+
|
|
6441
6477
|
// src/graphql-client.ts
|
|
6442
6478
|
async function writeArtifactTransferBytes(writer, bytes, position) {
|
|
6443
6479
|
let written = 0;
|
|
@@ -6532,8 +6568,9 @@ var AgentGraphQLClient = class {
|
|
|
6532
6568
|
}
|
|
6533
6569
|
if (!response.ok || typeof body?.mode !== "string") {
|
|
6534
6570
|
const detail = `HTTP ${response.status} ${response.statusText}`.trim();
|
|
6535
|
-
throw new
|
|
6536
|
-
!body && raw ? `${detail}: ${raw.slice(0, 500)}` : detail
|
|
6571
|
+
throw new AgentRequestError(
|
|
6572
|
+
!body && raw ? `${detail}: ${raw.slice(0, 500)}` : detail,
|
|
6573
|
+
response.status
|
|
6537
6574
|
);
|
|
6538
6575
|
}
|
|
6539
6576
|
return { mode: body.mode };
|
|
@@ -6557,8 +6594,12 @@ var AgentGraphQLClient = class {
|
|
|
6557
6594
|
}
|
|
6558
6595
|
if (!response.ok || body?.errors?.length || !body?.data) {
|
|
6559
6596
|
const detail = body?.errors?.map((error) => error.message).join("; ") || `HTTP ${response.status} ${response.statusText}`.trim();
|
|
6560
|
-
throw new
|
|
6561
|
-
!body && raw ? `${detail}: ${raw.slice(0, 500)}` : detail
|
|
6597
|
+
throw new AgentRequestError(
|
|
6598
|
+
!body && raw ? `${detail}: ${raw.slice(0, 500)}` : detail,
|
|
6599
|
+
response.status,
|
|
6600
|
+
body?.errors?.flatMap(
|
|
6601
|
+
({ extensions }) => extensions?.code ? [extensions.code] : []
|
|
6602
|
+
)
|
|
6562
6603
|
);
|
|
6563
6604
|
}
|
|
6564
6605
|
return body.data;
|
|
@@ -11105,7 +11146,7 @@ function tailscaleServeRemovePayload(value) {
|
|
|
11105
11146
|
// package.json
|
|
11106
11147
|
var package_default = {
|
|
11107
11148
|
name: "@ai-development-environment/control-agent",
|
|
11108
|
-
version: "0.0.
|
|
11149
|
+
version: "0.0.131",
|
|
11109
11150
|
description: "Control agent for the AI Development Environment",
|
|
11110
11151
|
license: "MIT",
|
|
11111
11152
|
repository: {
|
|
@@ -53986,6 +54027,7 @@ var JobExecutor = class {
|
|
|
53986
54027
|
repositoryCoordinator;
|
|
53987
54028
|
running = /* @__PURE__ */ new Map();
|
|
53988
54029
|
stopping = false;
|
|
54030
|
+
shutdown = new AbortController();
|
|
53989
54031
|
execute(job) {
|
|
53990
54032
|
if (this.stopping || this.running.has(job.id)) return;
|
|
53991
54033
|
const controller = new AbortController();
|
|
@@ -54001,6 +54043,7 @@ var JobExecutor = class {
|
|
|
54001
54043
|
}
|
|
54002
54044
|
async cancelAll() {
|
|
54003
54045
|
this.stopping = true;
|
|
54046
|
+
this.shutdown.abort();
|
|
54004
54047
|
closeAllWorktreeWatches();
|
|
54005
54048
|
const jobs = [...this.running.values()];
|
|
54006
54049
|
for (const { controller } of jobs) controller.abort();
|
|
@@ -54059,8 +54102,7 @@ var JobExecutor = class {
|
|
|
54059
54102
|
status2 = controller.signal.aborted ? "CANCELLED" : "FAILED";
|
|
54060
54103
|
console.error(`Job ${claimed.id} failed:`, completionError);
|
|
54061
54104
|
}
|
|
54062
|
-
let
|
|
54063
|
-
while (true) {
|
|
54105
|
+
for (let attempt = 1; ; attempt++) {
|
|
54064
54106
|
try {
|
|
54065
54107
|
await this.client.completeJob(
|
|
54066
54108
|
claimed.id,
|
|
@@ -54070,13 +54112,21 @@ var JobExecutor = class {
|
|
|
54070
54112
|
);
|
|
54071
54113
|
return;
|
|
54072
54114
|
} catch (error) {
|
|
54115
|
+
if (this.stopping) return;
|
|
54116
|
+
if (!isRetryableAgentError(error)) {
|
|
54117
|
+
console.error(
|
|
54118
|
+
`Could not report completion for ${claimed.id}; giving up after ${attempt} attempts:`,
|
|
54119
|
+
error instanceof Error ? error.message : error
|
|
54120
|
+
);
|
|
54121
|
+
return;
|
|
54122
|
+
}
|
|
54073
54123
|
console.error(
|
|
54074
54124
|
`Could not report completion for ${claimed.id}; retrying:`,
|
|
54075
54125
|
error instanceof Error ? error.message : error
|
|
54076
54126
|
);
|
|
54127
|
+
const delay3 = Math.min(3e4, 1e3 * 2 ** Math.min(attempt - 1, 5));
|
|
54128
|
+
await waitForAgentRetry(delay3, this.shutdown.signal);
|
|
54077
54129
|
if (this.stopping) return;
|
|
54078
|
-
const delay3 = Math.min(3e4, 1e3 * 2 ** Math.min(retry++, 5));
|
|
54079
|
-
await new Promise((resolve11) => setTimeout(resolve11, delay3));
|
|
54080
54130
|
}
|
|
54081
54131
|
}
|
|
54082
54132
|
}
|
|
@@ -54232,18 +54282,22 @@ var CodebaseMonitor = class {
|
|
|
54232
54282
|
|
|
54233
54283
|
// src/runs/run-manager.ts
|
|
54234
54284
|
import { execFile as execFile5 } from "node:child_process";
|
|
54285
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
54235
54286
|
import { mkdir as mkdir10, rm as rm20 } from "node:fs/promises";
|
|
54236
54287
|
import { dirname as dirname13, join as join23 } from "node:path";
|
|
54237
54288
|
import { promisify as promisify5 } from "node:util";
|
|
54238
54289
|
|
|
54239
54290
|
// src/runs/journal.ts
|
|
54240
54291
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
54292
|
+
import { createReadStream as createReadStream6 } from "node:fs";
|
|
54241
54293
|
import {
|
|
54242
54294
|
appendFile,
|
|
54243
54295
|
mkdir as mkdir9,
|
|
54244
54296
|
readFile as readFile12,
|
|
54245
54297
|
rename as rename7,
|
|
54246
54298
|
rm as rm19,
|
|
54299
|
+
stat as stat12,
|
|
54300
|
+
truncate,
|
|
54247
54301
|
writeFile as writeFile11
|
|
54248
54302
|
} from "node:fs/promises";
|
|
54249
54303
|
import { dirname as dirname12, join as join22 } from "node:path";
|
|
@@ -54256,6 +54310,7 @@ var RunJournal = class {
|
|
|
54256
54310
|
attemptId;
|
|
54257
54311
|
nextSequence = 0;
|
|
54258
54312
|
acknowledged = -1;
|
|
54313
|
+
acknowledgedOffset = 0;
|
|
54259
54314
|
loaded = false;
|
|
54260
54315
|
loading;
|
|
54261
54316
|
appending = Promise.resolve();
|
|
@@ -54288,14 +54343,33 @@ var RunJournal = class {
|
|
|
54288
54343
|
const state = JSON.parse(await readFile12(this.statePath, "utf8"));
|
|
54289
54344
|
this.acknowledged = state.acknowledged ?? -1;
|
|
54290
54345
|
this.nextSequence = state.nextSequence ?? 0;
|
|
54291
|
-
|
|
54292
|
-
|
|
54293
|
-
|
|
54294
|
-
|
|
54295
|
-
|
|
54296
|
-
|
|
54346
|
+
this.acknowledgedOffset = state.acknowledgedOffset ?? 0;
|
|
54347
|
+
} catch (error) {
|
|
54348
|
+
if (!(error instanceof SyntaxError) && error.code !== "ENOENT")
|
|
54349
|
+
throw error;
|
|
54350
|
+
}
|
|
54351
|
+
const size = await stat12(this.eventsPath).then(
|
|
54352
|
+
({ size: size2 }) => size2,
|
|
54353
|
+
(error) => {
|
|
54354
|
+
if (error.code === "ENOENT") return 0;
|
|
54355
|
+
throw error;
|
|
54297
54356
|
}
|
|
54357
|
+
);
|
|
54358
|
+
if (!Number.isSafeInteger(this.acknowledgedOffset) || this.acknowledgedOffset < 0 || this.acknowledgedOffset > size)
|
|
54359
|
+
this.acknowledgedOffset = 0;
|
|
54360
|
+
this.nextSequence = Math.max(this.nextSequence, this.acknowledged + 1);
|
|
54361
|
+
let completeOffset = this.acknowledgedOffset;
|
|
54362
|
+
for await (const { event, endOffset } of this.readEvents(completeOffset)) {
|
|
54363
|
+
this.nextSequence = Math.max(this.nextSequence, event.sequence + 1);
|
|
54364
|
+
if (event.sequence <= this.acknowledged) {
|
|
54365
|
+
this.acknowledgedOffset = endOffset;
|
|
54366
|
+
}
|
|
54367
|
+
completeOffset = endOffset;
|
|
54368
|
+
}
|
|
54369
|
+
if (size > completeOffset) {
|
|
54370
|
+
await truncate(this.eventsPath, completeOffset);
|
|
54298
54371
|
}
|
|
54372
|
+
await this.saveState();
|
|
54299
54373
|
this.loaded = true;
|
|
54300
54374
|
}
|
|
54301
54375
|
async append(event) {
|
|
@@ -54326,27 +54400,28 @@ var RunJournal = class {
|
|
|
54326
54400
|
return this.nextSequence - 1;
|
|
54327
54401
|
}
|
|
54328
54402
|
async flush(client) {
|
|
54329
|
-
|
|
54330
|
-
|
|
54331
|
-
|
|
54332
|
-
this.
|
|
54333
|
-
|
|
54334
|
-
|
|
54403
|
+
const previous = this.flushing;
|
|
54404
|
+
const operation = (async () => {
|
|
54405
|
+
await previous?.catch(() => void 0);
|
|
54406
|
+
await this.load();
|
|
54407
|
+
await this.appending;
|
|
54408
|
+
await this.flushInternal(client);
|
|
54409
|
+
})();
|
|
54410
|
+
this.flushing = operation;
|
|
54411
|
+
try {
|
|
54412
|
+
await operation;
|
|
54413
|
+
} finally {
|
|
54414
|
+
if (this.flushing === operation) this.flushing = void 0;
|
|
54415
|
+
}
|
|
54335
54416
|
}
|
|
54336
54417
|
async flushInternal(client) {
|
|
54337
|
-
|
|
54338
|
-
|
|
54339
|
-
)
|
|
54340
|
-
|
|
54341
|
-
const attemptId = pending[index].attemptId ?? this.attemptId;
|
|
54342
|
-
let end = index + 1;
|
|
54343
|
-
while (end < pending.length && end - index < 100 && (pending[end].attemptId ?? this.attemptId) === attemptId) {
|
|
54344
|
-
end += 1;
|
|
54345
|
-
}
|
|
54346
|
-
const chunk = pending.slice(index, end);
|
|
54418
|
+
let chunk = [];
|
|
54419
|
+
let endOffset = this.acknowledgedOffset;
|
|
54420
|
+
const upload = async () => {
|
|
54421
|
+
if (!chunk.length) return;
|
|
54347
54422
|
await client.appendRunEvents(
|
|
54348
54423
|
this.runId,
|
|
54349
|
-
attemptId,
|
|
54424
|
+
chunk[0].attemptId ?? this.attemptId,
|
|
54350
54425
|
chunk.map((value) => {
|
|
54351
54426
|
const event = { ...value };
|
|
54352
54427
|
Reflect.deleteProperty(event, "attemptId");
|
|
@@ -54354,20 +54429,49 @@ var RunJournal = class {
|
|
|
54354
54429
|
})
|
|
54355
54430
|
);
|
|
54356
54431
|
this.acknowledged = chunk.at(-1).sequence;
|
|
54432
|
+
this.acknowledgedOffset = endOffset;
|
|
54357
54433
|
await this.saveState();
|
|
54358
|
-
|
|
54359
|
-
}
|
|
54360
|
-
|
|
54361
|
-
|
|
54434
|
+
chunk = [];
|
|
54435
|
+
};
|
|
54436
|
+
for await (const entry of this.readEvents(this.acknowledgedOffset)) {
|
|
54437
|
+
if (chunk.length && (chunk.length === 100 || (chunk[0].attemptId ?? this.attemptId) !== (entry.event.attemptId ?? this.attemptId)))
|
|
54438
|
+
await upload();
|
|
54439
|
+
chunk.push(entry.event);
|
|
54440
|
+
endOffset = entry.endOffset;
|
|
54441
|
+
}
|
|
54442
|
+
await upload();
|
|
54443
|
+
}
|
|
54444
|
+
async *readEvents(start) {
|
|
54445
|
+
const stream = createReadStream6(this.eventsPath, { start });
|
|
54446
|
+
let pending = Buffer.alloc(0);
|
|
54447
|
+
let offset = start;
|
|
54362
54448
|
try {
|
|
54363
|
-
|
|
54449
|
+
for await (const bytes of stream) {
|
|
54450
|
+
const buffer = Buffer.concat([pending, bytes]);
|
|
54451
|
+
let index = 0;
|
|
54452
|
+
let newline;
|
|
54453
|
+
while ((newline = buffer.indexOf(10, index)) !== -1) {
|
|
54454
|
+
const line = buffer.subarray(index, newline);
|
|
54455
|
+
offset += newline - index + 1;
|
|
54456
|
+
index = newline + 1;
|
|
54457
|
+
if (line.length) {
|
|
54458
|
+
yield {
|
|
54459
|
+
event: JSON.parse(line.toString("utf8")),
|
|
54460
|
+
endOffset: offset
|
|
54461
|
+
};
|
|
54462
|
+
}
|
|
54463
|
+
}
|
|
54464
|
+
pending = buffer.subarray(index);
|
|
54465
|
+
}
|
|
54364
54466
|
} catch (error) {
|
|
54365
|
-
if (error.code === "ENOENT") return
|
|
54467
|
+
if (error.code === "ENOENT") return;
|
|
54366
54468
|
throw error;
|
|
54469
|
+
} finally {
|
|
54470
|
+
stream.destroy();
|
|
54367
54471
|
}
|
|
54368
54472
|
}
|
|
54369
54473
|
saveState() {
|
|
54370
|
-
const contents = `${JSON.stringify({ acknowledged: this.acknowledged, nextSequence: this.nextSequence })}
|
|
54474
|
+
const contents = `${JSON.stringify({ acknowledged: this.acknowledged, nextSequence: this.nextSequence, acknowledgedOffset: this.acknowledgedOffset })}
|
|
54371
54475
|
`;
|
|
54372
54476
|
const operation = this.saving.then(async () => {
|
|
54373
54477
|
const temporary = `${this.statePath}.${process.pid}.${randomUUID7()}.tmp`;
|
|
@@ -55548,11 +55652,14 @@ var RunManager = class {
|
|
|
55548
55652
|
runQueues = /* @__PURE__ */ new Map();
|
|
55549
55653
|
interruptionRequests = /* @__PURE__ */ new Map();
|
|
55550
55654
|
importTimer;
|
|
55655
|
+
importTask;
|
|
55656
|
+
importedSnapshots = /* @__PURE__ */ new Map();
|
|
55551
55657
|
commandTimer;
|
|
55552
55658
|
stopped = false;
|
|
55659
|
+
shutdown = new AbortController();
|
|
55553
55660
|
async start() {
|
|
55554
55661
|
if (typeof this.client.pendingRunCommands !== "function") return;
|
|
55555
|
-
|
|
55662
|
+
void this.syncImports();
|
|
55556
55663
|
try {
|
|
55557
55664
|
const commands = await this.client.pendingRunCommands();
|
|
55558
55665
|
await Promise.all(commands.map((command3) => this.execute(command3, true)));
|
|
@@ -55868,7 +55975,14 @@ var RunManager = class {
|
|
|
55868
55975
|
journal,
|
|
55869
55976
|
adapter,
|
|
55870
55977
|
baseline
|
|
55871
|
-
)
|
|
55978
|
+
).catch((error) => {
|
|
55979
|
+
if (!this.stopped) {
|
|
55980
|
+
console.error(
|
|
55981
|
+
`Run ${run.id} finalization failed:`,
|
|
55982
|
+
error instanceof Error ? error.message : error
|
|
55983
|
+
);
|
|
55984
|
+
}
|
|
55985
|
+
});
|
|
55872
55986
|
const requestedAfterStart = this.interruptionRequests.get(run.id);
|
|
55873
55987
|
if (requestedAfterStart)
|
|
55874
55988
|
await this.interruptActive(active, requestedAfterStart);
|
|
@@ -55890,7 +56004,7 @@ var RunManager = class {
|
|
|
55890
56004
|
baseline,
|
|
55891
56005
|
finalCheckpoint
|
|
55892
56006
|
);
|
|
55893
|
-
await this.
|
|
56007
|
+
await this.retryFinalization(
|
|
55894
56008
|
() => this.client.reportRunCheckpoint(run.id, attemptId, {
|
|
55895
56009
|
...finalCheckpoint,
|
|
55896
56010
|
diffPatch: comparison.rollbackPatch
|
|
@@ -55908,14 +56022,14 @@ var RunManager = class {
|
|
|
55908
56022
|
).catch(() => void 0);
|
|
55909
56023
|
}
|
|
55910
56024
|
}
|
|
55911
|
-
await this.
|
|
56025
|
+
await this.retryFinalization(
|
|
55912
56026
|
() => this.client.finishRunAttempt(attemptId, {
|
|
55913
56027
|
status: completion.status,
|
|
55914
56028
|
finalOutput: completion.finalOutput,
|
|
55915
56029
|
error: completion.error
|
|
55916
56030
|
})
|
|
55917
56031
|
);
|
|
55918
|
-
await this.
|
|
56032
|
+
await this.retryFinalization(
|
|
55919
56033
|
() => this.client.completeRunCommand(
|
|
55920
56034
|
command3.id,
|
|
55921
56035
|
completion.status === "FAILED" ? "FAILED" : "SUCCEEDED",
|
|
@@ -55923,13 +56037,14 @@ var RunManager = class {
|
|
|
55923
56037
|
)
|
|
55924
56038
|
);
|
|
55925
56039
|
} catch (error) {
|
|
55926
|
-
|
|
56040
|
+
if (this.stopped) return;
|
|
56041
|
+
await this.retryFinalization(
|
|
55927
56042
|
() => this.client.finishRunAttempt(attemptId, {
|
|
55928
56043
|
status: "FAILED",
|
|
55929
56044
|
error: error instanceof Error ? error.message : String(error)
|
|
55930
56045
|
})
|
|
55931
56046
|
);
|
|
55932
|
-
await this.
|
|
56047
|
+
await this.retryFinalization(
|
|
55933
56048
|
() => this.client.completeRunCommand(
|
|
55934
56049
|
command3.id,
|
|
55935
56050
|
"FAILED",
|
|
@@ -56212,10 +56327,22 @@ var RunManager = class {
|
|
|
56212
56327
|
}
|
|
56213
56328
|
return null;
|
|
56214
56329
|
}
|
|
56215
|
-
|
|
56330
|
+
syncImports() {
|
|
56331
|
+
if (this.stopped) return Promise.resolve();
|
|
56332
|
+
if (!this.importTask) {
|
|
56333
|
+
this.importTask = this.syncImportsInternal().finally(() => {
|
|
56334
|
+
this.importTask = void 0;
|
|
56335
|
+
});
|
|
56336
|
+
}
|
|
56337
|
+
return this.importTask;
|
|
56338
|
+
}
|
|
56339
|
+
async syncImportsInternal() {
|
|
56216
56340
|
if (this.stopped) return;
|
|
56341
|
+
if (this.importTimer) clearTimeout(this.importTimer);
|
|
56342
|
+
this.importTimer = void 0;
|
|
56217
56343
|
try {
|
|
56218
56344
|
const codebases = await this.client.agentCodebases();
|
|
56345
|
+
if (this.stopped) return;
|
|
56219
56346
|
const worktrees = codebases.flatMap(
|
|
56220
56347
|
(codebase) => codebase.worktrees.map((worktree) => ({
|
|
56221
56348
|
id: worktree.id,
|
|
@@ -56224,29 +56351,59 @@ var RunManager = class {
|
|
|
56224
56351
|
}))
|
|
56225
56352
|
);
|
|
56226
56353
|
for (const adapter of this.adapters.values()) {
|
|
56354
|
+
if (this.stopped) return;
|
|
56227
56355
|
try {
|
|
56228
56356
|
const catalog = await adapter.catalog?.(worktrees);
|
|
56357
|
+
if (this.stopped) return;
|
|
56229
56358
|
await this.client.reportRunProviderImportStatus(
|
|
56230
56359
|
adapter.key,
|
|
56231
56360
|
"SYNCING",
|
|
56232
56361
|
void 0,
|
|
56233
56362
|
{ ...catalog, capabilities: adapter.capabilities }
|
|
56234
56363
|
);
|
|
56364
|
+
if (this.stopped) return;
|
|
56235
56365
|
const runs = await adapter.discover(worktrees);
|
|
56366
|
+
if (this.stopped) return;
|
|
56236
56367
|
let batch = [];
|
|
56237
56368
|
let batchBytes = 0;
|
|
56369
|
+
let snapshots = [];
|
|
56370
|
+
const upload = async () => {
|
|
56371
|
+
await this.client.importProviderRuns(adapter.key, batch);
|
|
56372
|
+
for (const [key, digest] of snapshots) {
|
|
56373
|
+
this.importedSnapshots.set(key, digest);
|
|
56374
|
+
}
|
|
56375
|
+
batch = [];
|
|
56376
|
+
batchBytes = 0;
|
|
56377
|
+
snapshots = [];
|
|
56378
|
+
};
|
|
56238
56379
|
for (const run of runs) {
|
|
56239
|
-
|
|
56380
|
+
if (this.stopped) return;
|
|
56381
|
+
const serialized2 = JSON.stringify(run);
|
|
56382
|
+
const key = JSON.stringify([
|
|
56383
|
+
adapter.key,
|
|
56384
|
+
run.nativeId,
|
|
56385
|
+
run.worktreeId
|
|
56386
|
+
]);
|
|
56387
|
+
const digest = createHash8("sha256").update(serialized2).digest("hex");
|
|
56388
|
+
const active = [
|
|
56389
|
+
"ACTIVE",
|
|
56390
|
+
"RUNNING",
|
|
56391
|
+
"STARTING",
|
|
56392
|
+
"IN_PROGRESS",
|
|
56393
|
+
"WAITING_FOR_ANSWER"
|
|
56394
|
+
].includes(run.status?.toUpperCase() ?? "");
|
|
56395
|
+
if (!active && this.importedSnapshots.get(key) === digest) continue;
|
|
56396
|
+
const bytes = Buffer.byteLength(serialized2, "utf8");
|
|
56240
56397
|
if (batch.length && batchBytes + bytes > 4 * 1024 * 1024) {
|
|
56241
|
-
await
|
|
56242
|
-
batch = [];
|
|
56243
|
-
batchBytes = 0;
|
|
56398
|
+
await upload();
|
|
56244
56399
|
}
|
|
56245
56400
|
batch.push(run);
|
|
56246
56401
|
batchBytes += bytes;
|
|
56402
|
+
snapshots.push([key, digest]);
|
|
56247
56403
|
}
|
|
56248
|
-
await
|
|
56404
|
+
await upload();
|
|
56249
56405
|
} catch (error) {
|
|
56406
|
+
if (this.stopped) return;
|
|
56250
56407
|
await this.client.reportRunProviderImportStatus(
|
|
56251
56408
|
adapter.key,
|
|
56252
56409
|
"FAILED",
|
|
@@ -56259,10 +56416,12 @@ var RunManager = class {
|
|
|
56259
56416
|
}
|
|
56260
56417
|
}
|
|
56261
56418
|
} catch (error) {
|
|
56262
|
-
|
|
56263
|
-
|
|
56264
|
-
|
|
56265
|
-
|
|
56419
|
+
if (!this.stopped) {
|
|
56420
|
+
console.error(
|
|
56421
|
+
"Provider history sync failed:",
|
|
56422
|
+
error instanceof Error ? error.message : error
|
|
56423
|
+
);
|
|
56424
|
+
}
|
|
56266
56425
|
} finally {
|
|
56267
56426
|
if (!this.stopped) {
|
|
56268
56427
|
this.importTimer = setTimeout(
|
|
@@ -56273,14 +56432,19 @@ var RunManager = class {
|
|
|
56273
56432
|
}
|
|
56274
56433
|
}
|
|
56275
56434
|
}
|
|
56276
|
-
|
|
56435
|
+
retryFinalization(operation) {
|
|
56436
|
+
return this.retry(operation, Infinity);
|
|
56437
|
+
}
|
|
56438
|
+
async retry(operation, attempts = MAX_AGENT_REQUEST_ATTEMPTS) {
|
|
56277
56439
|
let delay3 = 1e3;
|
|
56278
56440
|
for (let attempt = 1; ; attempt += 1) {
|
|
56279
56441
|
try {
|
|
56280
56442
|
return await operation();
|
|
56281
56443
|
} catch (error) {
|
|
56282
|
-
if (this.stopped || attempt >= attempts
|
|
56283
|
-
|
|
56444
|
+
if (this.stopped || attempt >= attempts || !isRetryableAgentError(error))
|
|
56445
|
+
throw error;
|
|
56446
|
+
await waitForAgentRetry(delay3, this.shutdown.signal);
|
|
56447
|
+
if (this.stopped) throw error;
|
|
56284
56448
|
delay3 = Math.min(delay3 * 2, 3e4);
|
|
56285
56449
|
}
|
|
56286
56450
|
}
|
|
@@ -56293,14 +56457,16 @@ var RunManager = class {
|
|
|
56293
56457
|
}
|
|
56294
56458
|
async close() {
|
|
56295
56459
|
this.stopped = true;
|
|
56460
|
+
this.shutdown.abort();
|
|
56296
56461
|
if (this.importTimer) clearTimeout(this.importTimer);
|
|
56297
56462
|
if (this.commandTimer) clearTimeout(this.commandTimer);
|
|
56298
56463
|
for (const active of this.active.values()) {
|
|
56299
56464
|
await active.handle.interrupt("PAUSED").catch(() => void 0);
|
|
56300
56465
|
}
|
|
56301
|
-
await Promise.allSettled(
|
|
56302
|
-
this.
|
|
56303
|
-
|
|
56466
|
+
await Promise.allSettled([
|
|
56467
|
+
this.importTask,
|
|
56468
|
+
...this.adapters.values().map((adapter) => adapter.close?.())
|
|
56469
|
+
]);
|
|
56304
56470
|
}
|
|
56305
56471
|
};
|
|
56306
56472
|
|