@ai-development-environment/control-agent 0.0.76 → 0.0.77
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/README.md +36 -0
- package/dist/control-agent.js +465 -222
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,42 @@ control-agent enroll \
|
|
|
21
21
|
control-agent run
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
### Local and remote endpoints
|
|
25
|
+
|
|
26
|
+
One control plane is often reachable at two addresses — a LAN address at the desk and a public or VPN address elsewhere. Configure both and the agent prefers the local one, moving to the remote one when the local address stops answering and the remote one is confirmed to be answering. A control plane that is down at _every_ configured address is not a failover: the agent keeps its session and the jobs running under it, and reconnects when the server comes back.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
control-agent enroll \
|
|
30
|
+
--server http://192.168.1.10:3090 \
|
|
31
|
+
--remote-server https://control.example.com \
|
|
32
|
+
--enrollment-token <one-time-token>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Change the addresses on an enrolled agent, or print the current ones by passing no flags:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
control-agent endpoints
|
|
39
|
+
control-agent endpoints --remote-server https://control.example.com
|
|
40
|
+
control-agent endpoints --clear-remote
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The websocket address of each endpoint is derived from its server URL unless `--websocket-server` or `--remote-websocket-server` sets it explicitly.
|
|
44
|
+
|
|
45
|
+
Changing a server address re-derives its websocket address, because one stored against the old server no longer points anywhere useful. If the websocket is served from somewhere other than `/graphql` on the same host and port — behind a reverse proxy, say — pass it alongside the new server address:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
control-agent endpoints --server http://192.168.1.20:3090 --websocket-server ws://192.168.1.20:3092/graphql
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Enrollment
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
control-agent enrollment
|
|
55
|
+
control-agent unenroll
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`enrollment` reports whether this machine is enrolled and whether each configured endpoint answers for it. `unenroll` removes the local credential; the control plane still lists the agent until it is deleted there.
|
|
59
|
+
|
|
24
60
|
Diagnostics:
|
|
25
61
|
|
|
26
62
|
```bash
|
package/dist/control-agent.js
CHANGED
|
@@ -4204,6 +4204,9 @@ var require_cross_spawn = __commonJS({
|
|
|
4204
4204
|
}
|
|
4205
4205
|
});
|
|
4206
4206
|
|
|
4207
|
+
// src/index.ts
|
|
4208
|
+
import { rm as rm14 } from "node:fs/promises";
|
|
4209
|
+
|
|
4207
4210
|
// src/config.ts
|
|
4208
4211
|
import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4209
4212
|
import { homedir } from "node:os";
|
|
@@ -4226,6 +4229,32 @@ function defaultWebSocketServer(server) {
|
|
|
4226
4229
|
url.hash = "";
|
|
4227
4230
|
return url.toString();
|
|
4228
4231
|
}
|
|
4232
|
+
function agentEndpoints(config) {
|
|
4233
|
+
const endpoints2 = [
|
|
4234
|
+
{
|
|
4235
|
+
kind: "local",
|
|
4236
|
+
server: config.server,
|
|
4237
|
+
websocketServer: config.websocketServer
|
|
4238
|
+
}
|
|
4239
|
+
];
|
|
4240
|
+
if (config.remoteServer) {
|
|
4241
|
+
endpoints2.push({
|
|
4242
|
+
kind: "remote",
|
|
4243
|
+
server: config.remoteServer,
|
|
4244
|
+
websocketServer: config.remoteWebSocketServer ?? defaultWebSocketServer(config.remoteServer)
|
|
4245
|
+
});
|
|
4246
|
+
}
|
|
4247
|
+
return endpoints2;
|
|
4248
|
+
}
|
|
4249
|
+
function configForEndpoint(config, endpoint) {
|
|
4250
|
+
return {
|
|
4251
|
+
...config,
|
|
4252
|
+
server: endpoint.server,
|
|
4253
|
+
websocketServer: endpoint.websocketServer,
|
|
4254
|
+
remoteServer: null,
|
|
4255
|
+
remoteWebSocketServer: null
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4229
4258
|
async function loadConfig(path = configPath()) {
|
|
4230
4259
|
try {
|
|
4231
4260
|
const value = JSON.parse(await readFile(path, "utf8"));
|
|
@@ -4249,107 +4278,6 @@ async function saveConfig(config, path = configPath()) {
|
|
|
4249
4278
|
await rename(temporaryPath, path);
|
|
4250
4279
|
}
|
|
4251
4280
|
|
|
4252
|
-
// ../agent-contract/src/index.ts
|
|
4253
|
-
var CCUSAGE_REPORT_JOB_KIND = "ccusage.report";
|
|
4254
|
-
var DEFAULT_AGENT_HEARTBEAT_INTERVAL_SECONDS = 15;
|
|
4255
|
-
var MIN_AGENT_HEARTBEAT_INTERVAL_SECONDS = 5;
|
|
4256
|
-
var MAX_AGENT_HEARTBEAT_INTERVAL_SECONDS = 300;
|
|
4257
|
-
var DEFAULT_AGENT_JOB_RECONCILIATION_INTERVAL_SECONDS = 15;
|
|
4258
|
-
var MIN_AGENT_JOB_RECONCILIATION_INTERVAL_SECONDS = 5;
|
|
4259
|
-
var MAX_AGENT_JOB_RECONCILIATION_INTERVAL_SECONDS = 3600;
|
|
4260
|
-
function objectValue(value, name) {
|
|
4261
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4262
|
-
throw new Error(`${name} must be an object`);
|
|
4263
|
-
}
|
|
4264
|
-
return value;
|
|
4265
|
-
}
|
|
4266
|
-
function numericValue(value, name) {
|
|
4267
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
4268
|
-
throw new Error(`${name} must be a non-negative finite number`);
|
|
4269
|
-
}
|
|
4270
|
-
return value;
|
|
4271
|
-
}
|
|
4272
|
-
function stringValue(value, name) {
|
|
4273
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
4274
|
-
throw new Error(`${name} must be a non-empty string`);
|
|
4275
|
-
}
|
|
4276
|
-
return value;
|
|
4277
|
-
}
|
|
4278
|
-
function stringArray(value, name) {
|
|
4279
|
-
if (!Array.isArray(value)) throw new Error(`${name} must be an array`);
|
|
4280
|
-
return value.map((item, index) => stringValue(item, `${name}[${index}]`));
|
|
4281
|
-
}
|
|
4282
|
-
function tokenTotals(value, name) {
|
|
4283
|
-
return {
|
|
4284
|
-
inputTokens: numericValue(value.inputTokens, `${name}.inputTokens`),
|
|
4285
|
-
outputTokens: numericValue(value.outputTokens, `${name}.outputTokens`),
|
|
4286
|
-
cacheCreationTokens: numericValue(
|
|
4287
|
-
value.cacheCreationTokens,
|
|
4288
|
-
`${name}.cacheCreationTokens`
|
|
4289
|
-
),
|
|
4290
|
-
cacheReadTokens: numericValue(
|
|
4291
|
-
value.cacheReadTokens,
|
|
4292
|
-
`${name}.cacheReadTokens`
|
|
4293
|
-
),
|
|
4294
|
-
totalTokens: numericValue(value.totalTokens, `${name}.totalTokens`),
|
|
4295
|
-
totalCost: numericValue(value.totalCost, `${name}.totalCost`)
|
|
4296
|
-
};
|
|
4297
|
-
}
|
|
4298
|
-
function modelBreakdown(value, name) {
|
|
4299
|
-
const model2 = objectValue(value, name);
|
|
4300
|
-
return {
|
|
4301
|
-
modelName: stringValue(model2.modelName, `${name}.modelName`),
|
|
4302
|
-
inputTokens: numericValue(model2.inputTokens, `${name}.inputTokens`),
|
|
4303
|
-
outputTokens: numericValue(model2.outputTokens, `${name}.outputTokens`),
|
|
4304
|
-
cacheCreationTokens: numericValue(
|
|
4305
|
-
model2.cacheCreationTokens,
|
|
4306
|
-
`${name}.cacheCreationTokens`
|
|
4307
|
-
),
|
|
4308
|
-
cacheReadTokens: numericValue(
|
|
4309
|
-
model2.cacheReadTokens,
|
|
4310
|
-
`${name}.cacheReadTokens`
|
|
4311
|
-
),
|
|
4312
|
-
cost: numericValue(model2.cost, `${name}.cost`)
|
|
4313
|
-
};
|
|
4314
|
-
}
|
|
4315
|
-
function dailyEntry(value, index) {
|
|
4316
|
-
const name = `ccusage.daily[${index}]`;
|
|
4317
|
-
const entry = objectValue(value, name);
|
|
4318
|
-
const metadata = objectValue(entry.metadata, `${name}.metadata`);
|
|
4319
|
-
if (!Array.isArray(entry.modelBreakdowns)) {
|
|
4320
|
-
throw new Error(`${name}.modelBreakdowns must be an array`);
|
|
4321
|
-
}
|
|
4322
|
-
const period = stringValue(entry.period, `${name}.period`);
|
|
4323
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(period)) {
|
|
4324
|
-
throw new Error(`${name}.period must use YYYY-MM-DD`);
|
|
4325
|
-
}
|
|
4326
|
-
return {
|
|
4327
|
-
...tokenTotals(entry, name),
|
|
4328
|
-
agent: stringValue(entry.agent, `${name}.agent`),
|
|
4329
|
-
period,
|
|
4330
|
-
metadata: {
|
|
4331
|
-
agents: stringArray(metadata.agents, `${name}.metadata.agents`)
|
|
4332
|
-
},
|
|
4333
|
-
modelBreakdowns: entry.modelBreakdowns.map(
|
|
4334
|
-
(model2, modelIndex) => modelBreakdown(model2, `${name}.modelBreakdowns[${modelIndex}]`)
|
|
4335
|
-
),
|
|
4336
|
-
modelsUsed: stringArray(entry.modelsUsed, `${name}.modelsUsed`)
|
|
4337
|
-
};
|
|
4338
|
-
}
|
|
4339
|
-
function parseCcusageReport(value) {
|
|
4340
|
-
const report = objectValue(value, "ccusage report");
|
|
4341
|
-
if (!Array.isArray(report.daily)) {
|
|
4342
|
-
throw new Error("ccusage.daily must be an array");
|
|
4343
|
-
}
|
|
4344
|
-
return {
|
|
4345
|
-
daily: report.daily.map((entry, index) => dailyEntry(entry, index)),
|
|
4346
|
-
totals: tokenTotals(
|
|
4347
|
-
objectValue(report.totals, "ccusage.totals"),
|
|
4348
|
-
"ccusage.totals"
|
|
4349
|
-
)
|
|
4350
|
-
};
|
|
4351
|
-
}
|
|
4352
|
-
|
|
4353
4281
|
// src/graphql-client.ts
|
|
4354
4282
|
import { createReadStream } from "node:fs";
|
|
4355
4283
|
import { rm, stat } from "node:fs/promises";
|
|
@@ -5588,6 +5516,140 @@ function subscribeToAgentEvents(client2, agentId, onEvent) {
|
|
|
5588
5516
|
};
|
|
5589
5517
|
}
|
|
5590
5518
|
|
|
5519
|
+
// src/endpoint-selection.ts
|
|
5520
|
+
var PROBE_TIMEOUT_MS = 5e3;
|
|
5521
|
+
var probeEndpoint = async (endpoint, config) => {
|
|
5522
|
+
try {
|
|
5523
|
+
const response = await new AgentGraphQLClient(
|
|
5524
|
+
endpoint.server,
|
|
5525
|
+
config.credential,
|
|
5526
|
+
PROBE_TIMEOUT_MS,
|
|
5527
|
+
config.headers
|
|
5528
|
+
).self();
|
|
5529
|
+
return response.agentSelf?.id === config.agentId;
|
|
5530
|
+
} catch {
|
|
5531
|
+
return false;
|
|
5532
|
+
}
|
|
5533
|
+
};
|
|
5534
|
+
async function selectAgentEndpoint(config, signal, probe = probeEndpoint) {
|
|
5535
|
+
const endpoints2 = agentEndpoints(config);
|
|
5536
|
+
if (endpoints2.length === 1) return endpoints2[0];
|
|
5537
|
+
for (const endpoint of endpoints2) {
|
|
5538
|
+
if (signal.aborted) break;
|
|
5539
|
+
if (await probe(endpoint, config)) return endpoint;
|
|
5540
|
+
}
|
|
5541
|
+
return endpoints2[0];
|
|
5542
|
+
}
|
|
5543
|
+
async function findHealthyAlternate(config, active, signal, probe = probeEndpoint) {
|
|
5544
|
+
for (const endpoint of agentEndpoints(config)) {
|
|
5545
|
+
if (signal.aborted) break;
|
|
5546
|
+
if (endpoint.server === active.server) continue;
|
|
5547
|
+
if (await probe(endpoint, config)) return endpoint;
|
|
5548
|
+
}
|
|
5549
|
+
return void 0;
|
|
5550
|
+
}
|
|
5551
|
+
|
|
5552
|
+
// ../agent-contract/src/index.ts
|
|
5553
|
+
var CCUSAGE_REPORT_JOB_KIND = "ccusage.report";
|
|
5554
|
+
var DEFAULT_AGENT_HEARTBEAT_INTERVAL_SECONDS = 15;
|
|
5555
|
+
var MIN_AGENT_HEARTBEAT_INTERVAL_SECONDS = 5;
|
|
5556
|
+
var MAX_AGENT_HEARTBEAT_INTERVAL_SECONDS = 300;
|
|
5557
|
+
var DEFAULT_AGENT_JOB_RECONCILIATION_INTERVAL_SECONDS = 15;
|
|
5558
|
+
var MIN_AGENT_JOB_RECONCILIATION_INTERVAL_SECONDS = 5;
|
|
5559
|
+
var MAX_AGENT_JOB_RECONCILIATION_INTERVAL_SECONDS = 3600;
|
|
5560
|
+
function objectValue(value, name) {
|
|
5561
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5562
|
+
throw new Error(`${name} must be an object`);
|
|
5563
|
+
}
|
|
5564
|
+
return value;
|
|
5565
|
+
}
|
|
5566
|
+
function numericValue(value, name) {
|
|
5567
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
5568
|
+
throw new Error(`${name} must be a non-negative finite number`);
|
|
5569
|
+
}
|
|
5570
|
+
return value;
|
|
5571
|
+
}
|
|
5572
|
+
function stringValue(value, name) {
|
|
5573
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
5574
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
5575
|
+
}
|
|
5576
|
+
return value;
|
|
5577
|
+
}
|
|
5578
|
+
function stringArray(value, name) {
|
|
5579
|
+
if (!Array.isArray(value)) throw new Error(`${name} must be an array`);
|
|
5580
|
+
return value.map((item, index) => stringValue(item, `${name}[${index}]`));
|
|
5581
|
+
}
|
|
5582
|
+
function tokenTotals(value, name) {
|
|
5583
|
+
return {
|
|
5584
|
+
inputTokens: numericValue(value.inputTokens, `${name}.inputTokens`),
|
|
5585
|
+
outputTokens: numericValue(value.outputTokens, `${name}.outputTokens`),
|
|
5586
|
+
cacheCreationTokens: numericValue(
|
|
5587
|
+
value.cacheCreationTokens,
|
|
5588
|
+
`${name}.cacheCreationTokens`
|
|
5589
|
+
),
|
|
5590
|
+
cacheReadTokens: numericValue(
|
|
5591
|
+
value.cacheReadTokens,
|
|
5592
|
+
`${name}.cacheReadTokens`
|
|
5593
|
+
),
|
|
5594
|
+
totalTokens: numericValue(value.totalTokens, `${name}.totalTokens`),
|
|
5595
|
+
totalCost: numericValue(value.totalCost, `${name}.totalCost`)
|
|
5596
|
+
};
|
|
5597
|
+
}
|
|
5598
|
+
function modelBreakdown(value, name) {
|
|
5599
|
+
const model2 = objectValue(value, name);
|
|
5600
|
+
return {
|
|
5601
|
+
modelName: stringValue(model2.modelName, `${name}.modelName`),
|
|
5602
|
+
inputTokens: numericValue(model2.inputTokens, `${name}.inputTokens`),
|
|
5603
|
+
outputTokens: numericValue(model2.outputTokens, `${name}.outputTokens`),
|
|
5604
|
+
cacheCreationTokens: numericValue(
|
|
5605
|
+
model2.cacheCreationTokens,
|
|
5606
|
+
`${name}.cacheCreationTokens`
|
|
5607
|
+
),
|
|
5608
|
+
cacheReadTokens: numericValue(
|
|
5609
|
+
model2.cacheReadTokens,
|
|
5610
|
+
`${name}.cacheReadTokens`
|
|
5611
|
+
),
|
|
5612
|
+
cost: numericValue(model2.cost, `${name}.cost`)
|
|
5613
|
+
};
|
|
5614
|
+
}
|
|
5615
|
+
function dailyEntry(value, index) {
|
|
5616
|
+
const name = `ccusage.daily[${index}]`;
|
|
5617
|
+
const entry = objectValue(value, name);
|
|
5618
|
+
const metadata = objectValue(entry.metadata, `${name}.metadata`);
|
|
5619
|
+
if (!Array.isArray(entry.modelBreakdowns)) {
|
|
5620
|
+
throw new Error(`${name}.modelBreakdowns must be an array`);
|
|
5621
|
+
}
|
|
5622
|
+
const period = stringValue(entry.period, `${name}.period`);
|
|
5623
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(period)) {
|
|
5624
|
+
throw new Error(`${name}.period must use YYYY-MM-DD`);
|
|
5625
|
+
}
|
|
5626
|
+
return {
|
|
5627
|
+
...tokenTotals(entry, name),
|
|
5628
|
+
agent: stringValue(entry.agent, `${name}.agent`),
|
|
5629
|
+
period,
|
|
5630
|
+
metadata: {
|
|
5631
|
+
agents: stringArray(metadata.agents, `${name}.metadata.agents`)
|
|
5632
|
+
},
|
|
5633
|
+
modelBreakdowns: entry.modelBreakdowns.map(
|
|
5634
|
+
(model2, modelIndex) => modelBreakdown(model2, `${name}.modelBreakdowns[${modelIndex}]`)
|
|
5635
|
+
),
|
|
5636
|
+
modelsUsed: stringArray(entry.modelsUsed, `${name}.modelsUsed`)
|
|
5637
|
+
};
|
|
5638
|
+
}
|
|
5639
|
+
function parseCcusageReport(value) {
|
|
5640
|
+
const report = objectValue(value, "ccusage report");
|
|
5641
|
+
if (!Array.isArray(report.daily)) {
|
|
5642
|
+
throw new Error("ccusage.daily must be an array");
|
|
5643
|
+
}
|
|
5644
|
+
return {
|
|
5645
|
+
daily: report.daily.map((entry, index) => dailyEntry(entry, index)),
|
|
5646
|
+
totals: tokenTotals(
|
|
5647
|
+
objectValue(report.totals, "ccusage.totals"),
|
|
5648
|
+
"ccusage.totals"
|
|
5649
|
+
)
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5591
5653
|
// src/inventory.ts
|
|
5592
5654
|
import { statfsSync } from "node:fs";
|
|
5593
5655
|
import {
|
|
@@ -12062,6 +12124,41 @@ var deleteSigningIdentity = async (payload, timeoutMs, signal) => {
|
|
|
12062
12124
|
// src/handlers/ccusage.ts
|
|
12063
12125
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
12064
12126
|
|
|
12127
|
+
// src/executable-lookup.ts
|
|
12128
|
+
import { accessSync, constants } from "node:fs";
|
|
12129
|
+
import { homedir as homedir4 } from "node:os";
|
|
12130
|
+
import { delimiter, dirname as dirname3, join as join6 } from "node:path";
|
|
12131
|
+
function isExecutable(path) {
|
|
12132
|
+
try {
|
|
12133
|
+
accessSync(path, constants.X_OK);
|
|
12134
|
+
return true;
|
|
12135
|
+
} catch {
|
|
12136
|
+
return false;
|
|
12137
|
+
}
|
|
12138
|
+
}
|
|
12139
|
+
function findExecutable(name, options = {}) {
|
|
12140
|
+
const env = options.env ?? process.env;
|
|
12141
|
+
const home = options.home ?? homedir4();
|
|
12142
|
+
const platform2 = options.platform ?? process.platform;
|
|
12143
|
+
const executable = platform2 === "win32" ? `${name}.exe` : name;
|
|
12144
|
+
const directories = [
|
|
12145
|
+
...(env.PATH ?? "").split(delimiter).filter(Boolean),
|
|
12146
|
+
join6(home, ".local", "bin"),
|
|
12147
|
+
join6(home, ".bun", "bin"),
|
|
12148
|
+
join6(home, ".npm-global", "bin"),
|
|
12149
|
+
// Global npm installs land next to the node binary running this agent.
|
|
12150
|
+
options.nodeDirectory ?? dirname3(process.execPath),
|
|
12151
|
+
...platform2 === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin"] : []
|
|
12152
|
+
];
|
|
12153
|
+
const override = options.overrideVariable ? env[options.overrideVariable]?.trim() : void 0;
|
|
12154
|
+
const candidates = [
|
|
12155
|
+
...override ? [override] : [],
|
|
12156
|
+
...directories.map((directory) => join6(directory, executable))
|
|
12157
|
+
];
|
|
12158
|
+
const canExecute = options.isExecutable ?? isExecutable;
|
|
12159
|
+
return [...new Set(candidates)].find(canExecute);
|
|
12160
|
+
}
|
|
12161
|
+
|
|
12065
12162
|
// src/process-runner.ts
|
|
12066
12163
|
import { spawn as spawn5 } from "node:child_process";
|
|
12067
12164
|
async function runProcess(options) {
|
|
@@ -12180,11 +12277,19 @@ function validateCcusagePayload(payload) {
|
|
|
12180
12277
|
}
|
|
12181
12278
|
async function runCcusage(payload, timeoutMs, signal, onLog) {
|
|
12182
12279
|
validateCcusagePayload(payload);
|
|
12280
|
+
const command3 = findExecutable("ccusage", {
|
|
12281
|
+
overrideVariable: "CONTROL_AGENT_CCUSAGE_EXECUTABLE"
|
|
12282
|
+
});
|
|
12283
|
+
if (!command3) {
|
|
12284
|
+
throw new Error(
|
|
12285
|
+
"ccusage was not found. Install it (brew install ccusage, or npm install -g ccusage) or set CONTROL_AGENT_CCUSAGE_EXECUTABLE to its full path."
|
|
12286
|
+
);
|
|
12287
|
+
}
|
|
12183
12288
|
const stdout = [];
|
|
12184
12289
|
let stdoutBytes = 0;
|
|
12185
12290
|
let exceededLimit = false;
|
|
12186
12291
|
const result = await runProcess({
|
|
12187
|
-
command:
|
|
12292
|
+
command: command3,
|
|
12188
12293
|
args: ["--json"],
|
|
12189
12294
|
timeoutMs,
|
|
12190
12295
|
signal,
|
|
@@ -12229,8 +12334,8 @@ import {
|
|
|
12229
12334
|
stat as stat5,
|
|
12230
12335
|
statfs
|
|
12231
12336
|
} from "node:fs/promises";
|
|
12232
|
-
import { homedir as
|
|
12233
|
-
import { basename as basename4, dirname as
|
|
12337
|
+
import { homedir as homedir5 } from "node:os";
|
|
12338
|
+
import { basename as basename4, dirname as dirname4, isAbsolute as isAbsolute4, join as join7, resolve as resolve4 } from "node:path";
|
|
12234
12339
|
var successfulProcess4 = {
|
|
12235
12340
|
exitCode: 0,
|
|
12236
12341
|
signal: null,
|
|
@@ -12247,7 +12352,7 @@ function safeRelativePath3(value) {
|
|
|
12247
12352
|
}
|
|
12248
12353
|
async function configuredRoots(payload) {
|
|
12249
12354
|
if (payload.mode === "DEFAULT") {
|
|
12250
|
-
return [
|
|
12355
|
+
return [join7(homedir5(), "Library", "Developer", "Xcode", "DerivedData")];
|
|
12251
12356
|
}
|
|
12252
12357
|
if (payload.mode === "ABSOLUTE") {
|
|
12253
12358
|
if (!payload.path || !isAbsolute4(payload.path)) {
|
|
@@ -12309,10 +12414,10 @@ async function scanRoot(configuredRoot, timeoutMs, signal) {
|
|
|
12309
12414
|
for (const child of children) {
|
|
12310
12415
|
signal.throwIfAborted();
|
|
12311
12416
|
if (!child.isDirectory()) continue;
|
|
12312
|
-
const path =
|
|
12417
|
+
const path = join7(rootPath2, child.name);
|
|
12313
12418
|
const information = await lstat2(path);
|
|
12314
12419
|
const projectPath = await workspacePath(
|
|
12315
|
-
|
|
12420
|
+
join7(path, "info.plist"),
|
|
12316
12421
|
timeoutMs,
|
|
12317
12422
|
signal
|
|
12318
12423
|
);
|
|
@@ -12330,8 +12435,8 @@ async function scanRoot(configuredRoot, timeoutMs, signal) {
|
|
|
12330
12435
|
return { entries, warning: null };
|
|
12331
12436
|
}
|
|
12332
12437
|
async function scanDeviceSupport(signal) {
|
|
12333
|
-
const configuredRoot =
|
|
12334
|
-
process.env.ADE_IOS_DEVICE_SUPPORT_DIRECTORY ??
|
|
12438
|
+
const configuredRoot = join7(
|
|
12439
|
+
process.env.ADE_IOS_DEVICE_SUPPORT_DIRECTORY ?? homedir5(),
|
|
12335
12440
|
...process.env.ADE_IOS_DEVICE_SUPPORT_DIRECTORY ? [] : ["Library", "Developer", "Xcode", "iOS DeviceSupport"]
|
|
12336
12441
|
);
|
|
12337
12442
|
signal.throwIfAborted();
|
|
@@ -12351,7 +12456,7 @@ async function scanDeviceSupport(signal) {
|
|
|
12351
12456
|
const children = await readdir3(rootPath2, { withFileTypes: true });
|
|
12352
12457
|
return {
|
|
12353
12458
|
entries: children.filter((child) => child.isDirectory() && !child.isSymbolicLink()).map((child) => ({
|
|
12354
|
-
path:
|
|
12459
|
+
path: join7(rootPath2, child.name),
|
|
12355
12460
|
rootPath: rootPath2,
|
|
12356
12461
|
name: child.name,
|
|
12357
12462
|
kind: "DEVICE_SUPPORT",
|
|
@@ -12398,7 +12503,7 @@ function assertDirectChild(rootPath2, path) {
|
|
|
12398
12503
|
}
|
|
12399
12504
|
const root = resolve4(rootPath2);
|
|
12400
12505
|
const target2 = resolve4(path);
|
|
12401
|
-
if (target2 === root ||
|
|
12506
|
+
if (target2 === root || dirname4(target2) !== root || basename4(target2) !== basename4(path)) {
|
|
12402
12507
|
throw new Error("Build Data target must be a direct child of its root");
|
|
12403
12508
|
}
|
|
12404
12509
|
}
|
|
@@ -12412,7 +12517,7 @@ async function allocatedBytes(path, signal) {
|
|
|
12412
12517
|
try {
|
|
12413
12518
|
for await (const entry of directory) {
|
|
12414
12519
|
signal.throwIfAborted();
|
|
12415
|
-
total += await allocatedBytes(
|
|
12520
|
+
total += await allocatedBytes(join7(path, entry.name), signal);
|
|
12416
12521
|
}
|
|
12417
12522
|
} finally {
|
|
12418
12523
|
await directory.close().catch(() => void 0);
|
|
@@ -12427,7 +12532,7 @@ var sizeBuildData = async (rawPayload, _timeoutMs, signal) => {
|
|
|
12427
12532
|
try {
|
|
12428
12533
|
assertDirectChild(target2.rootPath, target2.path);
|
|
12429
12534
|
const rootPath2 = await realpath4(target2.rootPath);
|
|
12430
|
-
if (rootPath2 !== resolve4(target2.rootPath) ||
|
|
12535
|
+
if (rootPath2 !== resolve4(target2.rootPath) || dirname4(resolve4(target2.path)) !== rootPath2) {
|
|
12431
12536
|
throw new Error("Build Data root changed since the scan");
|
|
12432
12537
|
}
|
|
12433
12538
|
const stats = await lstat2(target2.path);
|
|
@@ -12458,7 +12563,7 @@ var deleteBuildData = async (rawPayload, _timeoutMs, signal) => {
|
|
|
12458
12563
|
try {
|
|
12459
12564
|
assertDirectChild(target2.rootPath, target2.path);
|
|
12460
12565
|
const rootPath2 = await realpath4(target2.rootPath);
|
|
12461
|
-
if (rootPath2 !== resolve4(target2.rootPath) ||
|
|
12566
|
+
if (rootPath2 !== resolve4(target2.rootPath) || dirname4(resolve4(target2.path)) !== rootPath2) {
|
|
12462
12567
|
throw new Error("Build Data root changed since the scan");
|
|
12463
12568
|
}
|
|
12464
12569
|
const stats = await lstat2(target2.path);
|
|
@@ -12512,13 +12617,13 @@ async function inspectVolume(configuredPath, role, signal, allowMissing) {
|
|
|
12512
12617
|
let candidate = resolve4(configuredPath);
|
|
12513
12618
|
for (; ; ) {
|
|
12514
12619
|
signal.throwIfAborted();
|
|
12515
|
-
const parent =
|
|
12620
|
+
const parent = dirname4(candidate);
|
|
12516
12621
|
if (parent === candidate) throw error;
|
|
12517
12622
|
missingSegments.unshift(basename4(candidate));
|
|
12518
12623
|
candidate = parent;
|
|
12519
12624
|
try {
|
|
12520
12625
|
inspectionPath = await realpath4(candidate);
|
|
12521
|
-
canonicalPath =
|
|
12626
|
+
canonicalPath = join7(inspectionPath, ...missingSegments);
|
|
12522
12627
|
break;
|
|
12523
12628
|
} catch (parentError) {
|
|
12524
12629
|
if (parentError.code !== "ENOENT") {
|
|
@@ -12625,8 +12730,8 @@ async function collectAgentDiskSpace(configuration, signal) {
|
|
|
12625
12730
|
|
|
12626
12731
|
// src/handlers/codebases.ts
|
|
12627
12732
|
import { readdir as readdir4, realpath as realpath5, stat as stat6 } from "node:fs/promises";
|
|
12628
|
-
import { homedir as
|
|
12629
|
-
import { dirname as
|
|
12733
|
+
import { homedir as homedir6 } from "node:os";
|
|
12734
|
+
import { dirname as dirname5, join as join8, resolve as resolve5 } from "node:path";
|
|
12630
12735
|
var successfulProcess5 = {
|
|
12631
12736
|
exitCode: 0,
|
|
12632
12737
|
signal: null,
|
|
@@ -12703,7 +12808,7 @@ function baseSnapshot(folder) {
|
|
|
12703
12808
|
}
|
|
12704
12809
|
async function fetchedAt(commonDirectory) {
|
|
12705
12810
|
try {
|
|
12706
|
-
return (await stat6(
|
|
12811
|
+
return (await stat6(join8(commonDirectory, "FETCH_HEAD"))).mtime.toISOString();
|
|
12707
12812
|
} catch {
|
|
12708
12813
|
return null;
|
|
12709
12814
|
}
|
|
@@ -12882,7 +12987,7 @@ async function inspectCodebase(selectedFolder, timeoutMs, signal, expectedOrigin
|
|
|
12882
12987
|
}
|
|
12883
12988
|
var browseCodebaseDirectories = async (payload) => {
|
|
12884
12989
|
const input = codebaseBrowsePayload(payload);
|
|
12885
|
-
const homePath = await realpath5(
|
|
12990
|
+
const homePath = await realpath5(homedir6());
|
|
12886
12991
|
const path = await realpath5(input.path ? resolve5(input.path) : homePath);
|
|
12887
12992
|
if (!(await stat6(path)).isDirectory())
|
|
12888
12993
|
throw new Error("Path is not a directory");
|
|
@@ -12892,12 +12997,12 @@ var browseCodebaseDirectories = async (payload) => {
|
|
|
12892
12997
|
if (candidate.isDirectory()) {
|
|
12893
12998
|
entries.push({
|
|
12894
12999
|
name: candidate.name,
|
|
12895
|
-
path:
|
|
13000
|
+
path: join8(path, candidate.name),
|
|
12896
13001
|
hidden: candidate.name.startsWith(".")
|
|
12897
13002
|
});
|
|
12898
13003
|
} else if (candidate.isSymbolicLink()) {
|
|
12899
13004
|
try {
|
|
12900
|
-
const target2 =
|
|
13005
|
+
const target2 = join8(path, candidate.name);
|
|
12901
13006
|
if ((await stat6(target2)).isDirectory()) {
|
|
12902
13007
|
entries.push({
|
|
12903
13008
|
name: candidate.name,
|
|
@@ -12912,7 +13017,7 @@ var browseCodebaseDirectories = async (payload) => {
|
|
|
12912
13017
|
entries.sort((first, second) => first.name.localeCompare(second.name));
|
|
12913
13018
|
const listing = {
|
|
12914
13019
|
path,
|
|
12915
|
-
parentPath:
|
|
13020
|
+
parentPath: dirname5(path) === path ? null : dirname5(path),
|
|
12916
13021
|
homePath,
|
|
12917
13022
|
entries: entries.slice(0, 1e3),
|
|
12918
13023
|
truncated: entries.length > 1e3
|
|
@@ -13510,8 +13615,8 @@ import {
|
|
|
13510
13615
|
stat as stat7,
|
|
13511
13616
|
writeFile as writeFile5
|
|
13512
13617
|
} from "node:fs/promises";
|
|
13513
|
-
import { homedir as
|
|
13514
|
-
import { dirname as
|
|
13618
|
+
import { homedir as homedir7 } from "node:os";
|
|
13619
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, join as join9, relative as relative6, resolve as resolve6, sep as sep6 } from "node:path";
|
|
13515
13620
|
import { promisify } from "node:util";
|
|
13516
13621
|
var executeFile = promisify(execFile);
|
|
13517
13622
|
var successfulProcess6 = {
|
|
@@ -13572,17 +13677,17 @@ async function directoryExists(path) {
|
|
|
13572
13677
|
}
|
|
13573
13678
|
}
|
|
13574
13679
|
async function configuredTools(tools) {
|
|
13575
|
-
const homePath =
|
|
13680
|
+
const homePath = homedir7();
|
|
13576
13681
|
return Promise.all(
|
|
13577
13682
|
AI_TOOLS.filter((tool) => tools.includes(tool)).map(async (tool) => ({
|
|
13578
13683
|
tool,
|
|
13579
|
-
configured: await directoryExists(
|
|
13684
|
+
configured: await directoryExists(join9(homePath, ...CONFIG_PATHS[tool])),
|
|
13580
13685
|
homePath
|
|
13581
13686
|
}))
|
|
13582
13687
|
);
|
|
13583
13688
|
}
|
|
13584
13689
|
function rootPath(location) {
|
|
13585
|
-
const base = location.scope === "GLOBAL" ?
|
|
13690
|
+
const base = location.scope === "GLOBAL" ? homedir7() : location.folder;
|
|
13586
13691
|
const parts = location.scope === "GLOBAL" ? GLOBAL_ROOTS[location.rootKind] : PROJECT_ROOTS[location.rootKind];
|
|
13587
13692
|
const path = resolve6(base, ...parts);
|
|
13588
13693
|
const normalizedBase = resolve6(base);
|
|
@@ -13599,7 +13704,7 @@ async function listPackageFiles(skillDirectory) {
|
|
|
13599
13704
|
let totalBytes = 0;
|
|
13600
13705
|
const visit = async (directory) => {
|
|
13601
13706
|
for (const entry of await readdir5(directory, { withFileTypes: true })) {
|
|
13602
|
-
const path =
|
|
13707
|
+
const path = join9(directory, entry.name);
|
|
13603
13708
|
const metadata = await lstat3(path);
|
|
13604
13709
|
if (metadata.isSymbolicLink()) {
|
|
13605
13710
|
throw new Error(
|
|
@@ -13688,7 +13793,7 @@ async function scanRoot2(location, configured, targets, warnings) {
|
|
|
13688
13793
|
const installations = [];
|
|
13689
13794
|
for (const entry of await readdir5(root, { withFileTypes: true })) {
|
|
13690
13795
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
13691
|
-
const directory =
|
|
13796
|
+
const directory = join9(root, entry.name);
|
|
13692
13797
|
try {
|
|
13693
13798
|
const skillPackage = await readPackage(directory);
|
|
13694
13799
|
const relativeDirectory = location.folder ? relative6(location.folder, directory).split(sep6).join("/") : "";
|
|
@@ -13767,7 +13872,7 @@ var readSkills = async (payload) => {
|
|
|
13767
13872
|
}
|
|
13768
13873
|
packages.push({
|
|
13769
13874
|
...request,
|
|
13770
|
-
package: await readPackage(
|
|
13875
|
+
package: await readPackage(join9(root, request.skillName))
|
|
13771
13876
|
});
|
|
13772
13877
|
}
|
|
13773
13878
|
return { ...successfulProcess6, packages };
|
|
@@ -13785,7 +13890,7 @@ async function gitExcludePath(folder) {
|
|
|
13785
13890
|
}
|
|
13786
13891
|
async function updateManagedExclude(folder, relativeSkillPath, present) {
|
|
13787
13892
|
const excludePath = await gitExcludePath(folder);
|
|
13788
|
-
await mkdir4(
|
|
13893
|
+
await mkdir4(dirname6(excludePath), { recursive: true });
|
|
13789
13894
|
let existing = "";
|
|
13790
13895
|
try {
|
|
13791
13896
|
existing = await readFile5(excludePath, "utf8");
|
|
@@ -13829,18 +13934,18 @@ async function writePackage(location, skillPackage) {
|
|
|
13829
13934
|
throw new Error("New skill copies may only be written to shared roots");
|
|
13830
13935
|
}
|
|
13831
13936
|
const root = rootPath(location);
|
|
13832
|
-
const destination =
|
|
13937
|
+
const destination = join9(root, skillPackage.name);
|
|
13833
13938
|
await assertUntracked(location, destination);
|
|
13834
13939
|
await mkdir4(root, { recursive: true });
|
|
13835
|
-
const temporary =
|
|
13836
|
-
const backup =
|
|
13940
|
+
const temporary = join9(root, `.${skillPackage.name}.${randomUUID3()}.tmp`);
|
|
13941
|
+
const backup = join9(root, `.${skillPackage.name}.${randomUUID3()}.bak`);
|
|
13837
13942
|
await mkdir4(temporary, { recursive: true });
|
|
13838
13943
|
try {
|
|
13839
13944
|
for (const file of skillPackage.files) {
|
|
13840
13945
|
const path = resolve6(temporary, ...file.path.split("/"));
|
|
13841
13946
|
if (!path.startsWith(`${temporary}${sep6}`))
|
|
13842
13947
|
throw new Error("Skill file escaped temporary directory");
|
|
13843
|
-
await mkdir4(
|
|
13948
|
+
await mkdir4(dirname6(path), { recursive: true });
|
|
13844
13949
|
await writeFile5(path, Buffer.from(file.contentsBase64, "base64"));
|
|
13845
13950
|
await chmod3(path, file.executable ? 493 : 420);
|
|
13846
13951
|
}
|
|
@@ -13871,7 +13976,7 @@ var applySkills = async (payload) => {
|
|
|
13871
13976
|
for (const operation of operations) {
|
|
13872
13977
|
const root = rootPath(operation);
|
|
13873
13978
|
const skillName = operation.kind === "WRITE" ? operation.package.name : operation.skillName;
|
|
13874
|
-
const destination =
|
|
13979
|
+
const destination = join9(root, skillName);
|
|
13875
13980
|
await assertUntracked(operation, destination);
|
|
13876
13981
|
if (operation.kind === "WRITE") {
|
|
13877
13982
|
await writePackage(operation, operation.package);
|
|
@@ -13901,8 +14006,8 @@ var applySkills = async (payload) => {
|
|
|
13901
14006
|
|
|
13902
14007
|
// src/handlers/runs.ts
|
|
13903
14008
|
import { readFile as readFile6, readdir as readdir6, stat as stat8 } from "node:fs/promises";
|
|
13904
|
-
import { homedir as
|
|
13905
|
-
import { join as
|
|
14009
|
+
import { homedir as homedir8 } from "node:os";
|
|
14010
|
+
import { join as join10 } from "node:path";
|
|
13906
14011
|
var successfulProcess7 = {
|
|
13907
14012
|
exitCode: 0,
|
|
13908
14013
|
signal: null,
|
|
@@ -13917,7 +14022,7 @@ async function fileExists(path) {
|
|
|
13917
14022
|
}
|
|
13918
14023
|
}
|
|
13919
14024
|
async function findClaudeSessionFile(nativeId, home) {
|
|
13920
|
-
const projectsDir =
|
|
14025
|
+
const projectsDir = join10(home, ".claude", "projects");
|
|
13921
14026
|
let entries;
|
|
13922
14027
|
try {
|
|
13923
14028
|
entries = await readdir6(projectsDir, { withFileTypes: true });
|
|
@@ -13927,13 +14032,13 @@ async function findClaudeSessionFile(nativeId, home) {
|
|
|
13927
14032
|
const target2 = `${nativeId}.jsonl`;
|
|
13928
14033
|
for (const entry of entries) {
|
|
13929
14034
|
if (!entry.isDirectory()) continue;
|
|
13930
|
-
const candidate =
|
|
14035
|
+
const candidate = join10(projectsDir, entry.name, target2);
|
|
13931
14036
|
if (await fileExists(candidate)) return candidate;
|
|
13932
14037
|
}
|
|
13933
14038
|
return null;
|
|
13934
14039
|
}
|
|
13935
14040
|
async function findCodexSessionFile(nativeId, home) {
|
|
13936
|
-
const sessionsDir =
|
|
14041
|
+
const sessionsDir = join10(home, ".codex", "sessions");
|
|
13937
14042
|
const suffix = `-${nativeId}.jsonl`;
|
|
13938
14043
|
const walk = async (dir) => {
|
|
13939
14044
|
let entries;
|
|
@@ -13943,7 +14048,7 @@ async function findCodexSessionFile(nativeId, home) {
|
|
|
13943
14048
|
return null;
|
|
13944
14049
|
}
|
|
13945
14050
|
for (const entry of entries) {
|
|
13946
|
-
const path =
|
|
14051
|
+
const path = join10(dir, entry.name);
|
|
13947
14052
|
if (entry.isDirectory()) {
|
|
13948
14053
|
const found = await walk(path);
|
|
13949
14054
|
if (found) return found;
|
|
@@ -13960,7 +14065,7 @@ async function locateSessionFile(payload, home) {
|
|
|
13960
14065
|
}
|
|
13961
14066
|
var readRunSession = async (payload) => {
|
|
13962
14067
|
const input = parseRunSessionReadPayload(payload);
|
|
13963
|
-
const path = await locateSessionFile(input,
|
|
14068
|
+
const path = await locateSessionFile(input, homedir8());
|
|
13964
14069
|
if (!path) {
|
|
13965
14070
|
throw new Error(
|
|
13966
14071
|
`No ${input.provider === "CLAUDE" ? "Claude" : "Codex"} session file found for this run on this agent`
|
|
@@ -13990,13 +14095,13 @@ import {
|
|
|
13990
14095
|
writeFile as writeFile6
|
|
13991
14096
|
} from "node:fs/promises";
|
|
13992
14097
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
13993
|
-
import { join as
|
|
14098
|
+
import { join as join12 } from "node:path";
|
|
13994
14099
|
|
|
13995
14100
|
// src/runs/git-checkpoint.ts
|
|
13996
14101
|
import { execFile as execFile2 } from "node:child_process";
|
|
13997
14102
|
import { copyFile, mkdtemp as mkdtemp4, rm as rm7 } from "node:fs/promises";
|
|
13998
14103
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13999
|
-
import { join as
|
|
14104
|
+
import { join as join11 } from "node:path";
|
|
14000
14105
|
import { promisify as promisify2 } from "node:util";
|
|
14001
14106
|
var execFileAsync = promisify2(execFile2);
|
|
14002
14107
|
async function git2(cwd, args, env) {
|
|
@@ -14111,12 +14216,12 @@ async function captureGitCheckpoint(cwd, runId, kind) {
|
|
|
14111
14216
|
const manifest = await optionalGit(cwd, ["status", "--porcelain=v2", "-z"]);
|
|
14112
14217
|
const staged = await optionalGit(cwd, ["diff", "--cached", "--stat", "HEAD"]);
|
|
14113
14218
|
const unstaged = await optionalGit(cwd, ["diff", "--stat", "HEAD"]);
|
|
14114
|
-
const directory = await mkdtemp4(
|
|
14219
|
+
const directory = await mkdtemp4(join11(tmpdir4(), "aide-run-index-"));
|
|
14115
14220
|
let worktreeTree = null;
|
|
14116
14221
|
let refName = null;
|
|
14117
14222
|
try {
|
|
14118
14223
|
const actualIndex = await git2(cwd, ["rev-parse", "--git-path", "index"]);
|
|
14119
|
-
const temporaryIndex =
|
|
14224
|
+
const temporaryIndex = join11(directory, "index");
|
|
14120
14225
|
try {
|
|
14121
14226
|
await copyFile(actualIndex, temporaryIndex);
|
|
14122
14227
|
} catch {
|
|
@@ -14186,10 +14291,10 @@ var runWorkflowTerminal = async (payloadValue, timeoutMs, signal, onLog, context
|
|
|
14186
14291
|
const information = await stat9(cwd);
|
|
14187
14292
|
if (!information.isDirectory())
|
|
14188
14293
|
throw new Error("Workflow working directory is not a directory");
|
|
14189
|
-
const directory = await mkdtemp5(
|
|
14294
|
+
const directory = await mkdtemp5(join12(tmpdir5(), "aide-workflow-"));
|
|
14190
14295
|
const extension2 = payload.interpreter === "NODE" ? "mjs" : "sh";
|
|
14191
|
-
const scriptPath =
|
|
14192
|
-
const sessionPath =
|
|
14296
|
+
const scriptPath = join12(directory, `step.${extension2}`);
|
|
14297
|
+
const sessionPath = join12(directory, "session.json");
|
|
14193
14298
|
try {
|
|
14194
14299
|
await writeFile6(scriptPath, `${payload.script}
|
|
14195
14300
|
`, { mode: 384 });
|
|
@@ -14282,7 +14387,7 @@ var runWorkflowGitCheckpoint = async (payloadValue, _timeoutMs, signal) => {
|
|
|
14282
14387
|
import { createWriteStream as createWriteStream2, watch } from "node:fs";
|
|
14283
14388
|
import { mkdtemp as mkdtemp6, open as open2, readFile as readFile8, realpath as realpath8, rm as rm9, stat as stat10 } from "node:fs/promises";
|
|
14284
14389
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
14285
|
-
import { basename as basename5, dirname as
|
|
14390
|
+
import { basename as basename5, dirname as dirname7, isAbsolute as isAbsolute6, join as join13, relative as relative7 } from "node:path";
|
|
14286
14391
|
import { spawn as spawn6 } from "node:child_process";
|
|
14287
14392
|
var successfulProcess8 = {
|
|
14288
14393
|
exitCode: 0,
|
|
@@ -14337,7 +14442,7 @@ async function rebaseInProgress(folder, timeoutMs, signal) {
|
|
|
14337
14442
|
const candidate = pathResult.stdout.trim();
|
|
14338
14443
|
if (!candidate) continue;
|
|
14339
14444
|
try {
|
|
14340
|
-
await stat10(isAbsolute6(candidate) ? candidate :
|
|
14445
|
+
await stat10(isAbsolute6(candidate) ? candidate : join13(folder, candidate));
|
|
14341
14446
|
return true;
|
|
14342
14447
|
} catch {
|
|
14343
14448
|
}
|
|
@@ -14357,7 +14462,7 @@ async function rebaseBranch(folder, timeoutMs, signal) {
|
|
|
14357
14462
|
if (!candidate) continue;
|
|
14358
14463
|
try {
|
|
14359
14464
|
const headName = (await readFile8(
|
|
14360
|
-
isAbsolute6(candidate) ? candidate :
|
|
14465
|
+
isAbsolute6(candidate) ? candidate : join13(folder, candidate),
|
|
14361
14466
|
"utf8"
|
|
14362
14467
|
)).trim();
|
|
14363
14468
|
if (headName.startsWith("refs/heads/")) {
|
|
@@ -15003,7 +15108,7 @@ async function comparisonSides(input, folder, timeoutMs, signal) {
|
|
|
15003
15108
|
if (!input.path) return { before: null, after: null };
|
|
15004
15109
|
const path = input.path;
|
|
15005
15110
|
const previousPath = input.previousPath ?? path;
|
|
15006
|
-
const currentPath =
|
|
15111
|
+
const currentPath = join13(folder, path);
|
|
15007
15112
|
if (input.scope === "UNTRACKED") {
|
|
15008
15113
|
return { before: null, after: { kind: "FILE", path: currentPath } };
|
|
15009
15114
|
}
|
|
@@ -15178,7 +15283,7 @@ async function inspectRequestedDiff(input, folder, timeoutMs, signal) {
|
|
|
15178
15283
|
let patch;
|
|
15179
15284
|
if (input.scope === "UNTRACKED") {
|
|
15180
15285
|
const bounded = await readBoundedFile(
|
|
15181
|
-
|
|
15286
|
+
join13(folder, input.path),
|
|
15182
15287
|
MAX_DIFF_BYTES
|
|
15183
15288
|
);
|
|
15184
15289
|
const contents = bounded.contents;
|
|
@@ -15360,8 +15465,8 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal, _onLog, conte
|
|
|
15360
15465
|
if (!/^\d+$/.test(size) || Number(size) > 20 * 1024 * 1024) {
|
|
15361
15466
|
throw new Error("Diff image exceeds the 20 MiB limit");
|
|
15362
15467
|
}
|
|
15363
|
-
temporaryDirectory = await mkdtemp6(
|
|
15364
|
-
uploadPath =
|
|
15468
|
+
temporaryDirectory = await mkdtemp6(join13(tmpdir6(), "ade-diff-image-"));
|
|
15469
|
+
uploadPath = join13(temporaryDirectory, basename5(input.path));
|
|
15365
15470
|
await writeGitObject(
|
|
15366
15471
|
folder,
|
|
15367
15472
|
selected.specification,
|
|
@@ -15624,8 +15729,8 @@ var branchWorktree = async (payload, timeoutMs, signal) => {
|
|
|
15624
15729
|
timeoutMs,
|
|
15625
15730
|
signal
|
|
15626
15731
|
) : input.candidates[0];
|
|
15627
|
-
const targetFolder = input.action === "CREATE" ?
|
|
15628
|
-
|
|
15732
|
+
const targetFolder = input.action === "CREATE" ? join13(
|
|
15733
|
+
dirname7(rootFolder),
|
|
15629
15734
|
`${basename5(rootFolder)}-${branch.replaceAll("/", "-")}`
|
|
15630
15735
|
) : changeFolder;
|
|
15631
15736
|
if (input.action === "CREATE") {
|
|
@@ -15864,8 +15969,8 @@ var checkoutMovedWorktree = async (payload, timeoutMs, signal) => {
|
|
|
15864
15969
|
`Branch ${input.branch} is already checked out in ${occupiedFolder}`
|
|
15865
15970
|
);
|
|
15866
15971
|
}
|
|
15867
|
-
targetFolder =
|
|
15868
|
-
|
|
15972
|
+
targetFolder = join13(
|
|
15973
|
+
dirname7(rootFolder),
|
|
15869
15974
|
`${basename5(rootFolder)}-${input.branch.replaceAll("/", "-")}`
|
|
15870
15975
|
);
|
|
15871
15976
|
try {
|
|
@@ -16804,7 +16909,7 @@ var CodebaseMonitor = class {
|
|
|
16804
16909
|
// src/runs/run-manager.ts
|
|
16805
16910
|
import { execFile as execFile4 } from "node:child_process";
|
|
16806
16911
|
import { mkdir as mkdir6, rm as rm13 } from "node:fs/promises";
|
|
16807
|
-
import { dirname as
|
|
16912
|
+
import { dirname as dirname9, join as join17 } from "node:path";
|
|
16808
16913
|
import { promisify as promisify4 } from "node:util";
|
|
16809
16914
|
|
|
16810
16915
|
// src/runs/journal.ts
|
|
@@ -16817,7 +16922,7 @@ import {
|
|
|
16817
16922
|
rm as rm10,
|
|
16818
16923
|
writeFile as writeFile7
|
|
16819
16924
|
} from "node:fs/promises";
|
|
16820
|
-
import { dirname as
|
|
16925
|
+
import { dirname as dirname8, join as join14 } from "node:path";
|
|
16821
16926
|
var RunJournal = class {
|
|
16822
16927
|
constructor(runId, attemptId) {
|
|
16823
16928
|
this.runId = runId;
|
|
@@ -16836,13 +16941,13 @@ var RunJournal = class {
|
|
|
16836
16941
|
this.attemptId = attemptId;
|
|
16837
16942
|
}
|
|
16838
16943
|
get directory() {
|
|
16839
|
-
return
|
|
16944
|
+
return join14(dirname8(configPath()), "runs", this.runId);
|
|
16840
16945
|
}
|
|
16841
16946
|
get eventsPath() {
|
|
16842
|
-
return
|
|
16947
|
+
return join14(this.directory, "events.jsonl");
|
|
16843
16948
|
}
|
|
16844
16949
|
get statePath() {
|
|
16845
|
-
return
|
|
16950
|
+
return join14(this.directory, "state.json");
|
|
16846
16951
|
}
|
|
16847
16952
|
async load() {
|
|
16848
16953
|
if (this.loaded) return;
|
|
@@ -16957,9 +17062,6 @@ var RunJournal = class {
|
|
|
16957
17062
|
|
|
16958
17063
|
// src/runs/claude-adapter.ts
|
|
16959
17064
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
16960
|
-
import { accessSync, constants } from "node:fs";
|
|
16961
|
-
import { homedir as homedir8 } from "node:os";
|
|
16962
|
-
import { delimiter, join as join15 } from "node:path";
|
|
16963
17065
|
|
|
16964
17066
|
// ../../node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
16965
17067
|
import { createRequire as RV } from "node:module";
|
|
@@ -42767,27 +42869,11 @@ function answerArrays(value) {
|
|
|
42767
42869
|
}
|
|
42768
42870
|
|
|
42769
42871
|
// src/runs/claude-adapter.ts
|
|
42770
|
-
function isExecutable(path) {
|
|
42771
|
-
try {
|
|
42772
|
-
accessSync(path, constants.X_OK);
|
|
42773
|
-
return true;
|
|
42774
|
-
} catch {
|
|
42775
|
-
return false;
|
|
42776
|
-
}
|
|
42777
|
-
}
|
|
42778
42872
|
function findClaudeCodeExecutable(options = {}) {
|
|
42779
|
-
|
|
42780
|
-
|
|
42781
|
-
|
|
42782
|
-
|
|
42783
|
-
const candidates = [
|
|
42784
|
-
env.CONTROL_AGENT_CLAUDE_EXECUTABLE?.trim(),
|
|
42785
|
-
...(env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join15(directory, executable)),
|
|
42786
|
-
join15(home, ".local", "bin", executable),
|
|
42787
|
-
...platform2 === "darwin" ? ["/opt/homebrew/bin/claude", "/usr/local/bin/claude"] : []
|
|
42788
|
-
].filter((candidate) => Boolean(candidate));
|
|
42789
|
-
const canExecute = options.isExecutable ?? isExecutable;
|
|
42790
|
-
return [...new Set(candidates)].find(canExecute);
|
|
42873
|
+
return findExecutable("claude", {
|
|
42874
|
+
...options,
|
|
42875
|
+
overrideVariable: "CONTROL_AGENT_CLAUDE_EXECUTABLE"
|
|
42876
|
+
});
|
|
42791
42877
|
}
|
|
42792
42878
|
var MessageQueue = class {
|
|
42793
42879
|
values = [];
|
|
@@ -50011,7 +50097,7 @@ var RunManager = class {
|
|
|
50011
50097
|
5
|
|
50012
50098
|
);
|
|
50013
50099
|
await this.completeCommand(command3.id, "SUCCEEDED");
|
|
50014
|
-
await rm13(join17(
|
|
50100
|
+
await rm13(join17(dirname9(configPath()), "runs", run.id, "attachments"), {
|
|
50015
50101
|
recursive: true,
|
|
50016
50102
|
force: true
|
|
50017
50103
|
}).catch(() => void 0);
|
|
@@ -50193,7 +50279,7 @@ var RunManager = class {
|
|
|
50193
50279
|
this.active.delete(run.id);
|
|
50194
50280
|
this.interruptionRequests.delete(run.id);
|
|
50195
50281
|
this.executingCommands.delete(command3.id);
|
|
50196
|
-
await rm13(join17(
|
|
50282
|
+
await rm13(join17(dirname9(configPath()), "runs", run.id, "attachments"), {
|
|
50197
50283
|
recursive: true,
|
|
50198
50284
|
force: true
|
|
50199
50285
|
}).catch(() => void 0);
|
|
@@ -50421,7 +50507,7 @@ var RunManager = class {
|
|
|
50421
50507
|
}
|
|
50422
50508
|
async stageAttachments(runId, attachments) {
|
|
50423
50509
|
if (!attachments.length) return [];
|
|
50424
|
-
const directory = join17(
|
|
50510
|
+
const directory = join17(dirname9(configPath()), "runs", runId, "attachments");
|
|
50425
50511
|
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
50426
50512
|
return Promise.all(
|
|
50427
50513
|
attachments.map(async (attachment) => {
|
|
@@ -50549,7 +50635,8 @@ var DISK_SPACE_STALE_AFTER_SECONDS = DISK_SPACE_POLL_INTERVAL_SECONDS * 2;
|
|
|
50549
50635
|
function configuredIntervalMs(value, fallbackSeconds, minSeconds, maxSeconds) {
|
|
50550
50636
|
return (Number.isInteger(value) && value >= minSeconds && value <= maxSeconds ? value : fallbackSeconds) * 1e3;
|
|
50551
50637
|
}
|
|
50552
|
-
|
|
50638
|
+
var UNHEALTHY_HEARTBEAT_FAILURES = 3;
|
|
50639
|
+
async function runAgentSession(config, signal, onUnhealthy) {
|
|
50553
50640
|
const client2 = new AgentGraphQLClient(
|
|
50554
50641
|
config.server,
|
|
50555
50642
|
config.credential,
|
|
@@ -50571,6 +50658,7 @@ async function runAgent(config, signal) {
|
|
|
50571
50658
|
let diskSpaceRunning = false;
|
|
50572
50659
|
let diskSpaceIntervalMs = DISK_SPACE_POLL_INTERVAL_SECONDS * 1e3;
|
|
50573
50660
|
let startupRecoveryPending = true;
|
|
50661
|
+
let heartbeatFailures = 0;
|
|
50574
50662
|
const interruptedJobs = /* @__PURE__ */ new Set();
|
|
50575
50663
|
const reconcileJobs = async (recoverInterrupted) => {
|
|
50576
50664
|
let jobs;
|
|
@@ -50653,11 +50741,17 @@ async function runAgent(config, signal) {
|
|
|
50653
50741
|
heartbeatTimer = void 0;
|
|
50654
50742
|
try {
|
|
50655
50743
|
await client2.heartbeat(collectInventory());
|
|
50744
|
+
heartbeatFailures = 0;
|
|
50656
50745
|
} catch (error) {
|
|
50746
|
+
heartbeatFailures += 1;
|
|
50657
50747
|
console.error(
|
|
50658
50748
|
"Heartbeat failed:",
|
|
50659
50749
|
error instanceof Error ? error.message : error
|
|
50660
50750
|
);
|
|
50751
|
+
if (heartbeatFailures >= UNHEALTHY_HEARTBEAT_FAILURES && onUnhealthy) {
|
|
50752
|
+
heartbeatFailures = 0;
|
|
50753
|
+
onUnhealthy();
|
|
50754
|
+
}
|
|
50661
50755
|
} finally {
|
|
50662
50756
|
heartbeatRunning = false;
|
|
50663
50757
|
scheduleHeartbeat();
|
|
@@ -50802,6 +50896,53 @@ async function runAgent(config, signal) {
|
|
|
50802
50896
|
await executor.cancelAll();
|
|
50803
50897
|
await runManager.close();
|
|
50804
50898
|
}
|
|
50899
|
+
async function runAgent(config, signal) {
|
|
50900
|
+
const failoverEnabled = agentEndpoints(config).length > 1;
|
|
50901
|
+
let preferred;
|
|
50902
|
+
while (!signal.aborted) {
|
|
50903
|
+
const endpoint = preferred ?? await selectAgentEndpoint(config, signal);
|
|
50904
|
+
preferred = void 0;
|
|
50905
|
+
if (signal.aborted) break;
|
|
50906
|
+
if (failoverEnabled) {
|
|
50907
|
+
console.log(
|
|
50908
|
+
`Using the ${endpoint.kind} control plane at ${endpoint.server}`
|
|
50909
|
+
);
|
|
50910
|
+
}
|
|
50911
|
+
const session = new AbortController();
|
|
50912
|
+
const abortSession = () => session.abort();
|
|
50913
|
+
signal.addEventListener("abort", abortSession, { once: true });
|
|
50914
|
+
let probing = false;
|
|
50915
|
+
const onUnhealthy = () => {
|
|
50916
|
+
if (probing || session.signal.aborted) return;
|
|
50917
|
+
probing = true;
|
|
50918
|
+
void findHealthyAlternate(config, endpoint, session.signal).then((alternate) => {
|
|
50919
|
+
if (session.signal.aborted) return;
|
|
50920
|
+
if (!alternate) {
|
|
50921
|
+
console.log(
|
|
50922
|
+
`The ${endpoint.kind} control plane is not answering and no other endpoint is either; keeping the session and retrying`
|
|
50923
|
+
);
|
|
50924
|
+
return;
|
|
50925
|
+
}
|
|
50926
|
+
console.log(
|
|
50927
|
+
`The ${endpoint.kind} control plane stopped responding; switching to the ${alternate.kind} control plane at ${alternate.server}`
|
|
50928
|
+
);
|
|
50929
|
+
preferred = alternate;
|
|
50930
|
+
session.abort();
|
|
50931
|
+
}).finally(() => {
|
|
50932
|
+
probing = false;
|
|
50933
|
+
});
|
|
50934
|
+
};
|
|
50935
|
+
try {
|
|
50936
|
+
await runAgentSession(
|
|
50937
|
+
configForEndpoint(config, endpoint),
|
|
50938
|
+
session.signal,
|
|
50939
|
+
failoverEnabled ? onUnhealthy : void 0
|
|
50940
|
+
);
|
|
50941
|
+
} finally {
|
|
50942
|
+
signal.removeEventListener("abort", abortSession);
|
|
50943
|
+
}
|
|
50944
|
+
}
|
|
50945
|
+
}
|
|
50805
50946
|
|
|
50806
50947
|
// src/dev-runtime.ts
|
|
50807
50948
|
var DEFAULT_SERVER_WAIT_MS = 12e4;
|
|
@@ -50883,10 +51024,10 @@ async function prepareDevelopmentAgent(options, signal, dependencies = defaultDe
|
|
|
50883
51024
|
}
|
|
50884
51025
|
} catch {
|
|
50885
51026
|
}
|
|
50886
|
-
const
|
|
51027
|
+
const enrollment2 = await anonymousClient.createEnrollmentToken();
|
|
50887
51028
|
const response = await anonymousClient.enroll({
|
|
50888
51029
|
...inventory,
|
|
50889
|
-
enrollmentToken:
|
|
51030
|
+
enrollmentToken: enrollment2.createAgentEnrollmentToken.token,
|
|
50890
51031
|
name
|
|
50891
51032
|
});
|
|
50892
51033
|
const config = {
|
|
@@ -50936,12 +51077,17 @@ function redactedRequestHeaders(headers) {
|
|
|
50936
51077
|
}
|
|
50937
51078
|
|
|
50938
51079
|
// src/index.ts
|
|
51080
|
+
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["clear-remote"]);
|
|
50939
51081
|
function flags(args) {
|
|
50940
51082
|
const result = {};
|
|
50941
51083
|
const headerValues = [];
|
|
50942
51084
|
for (let index = 0; index < args.length; index += 1) {
|
|
50943
51085
|
const argument = args[index];
|
|
50944
51086
|
if (!argument.startsWith("--")) continue;
|
|
51087
|
+
if (BOOLEAN_FLAGS.has(argument.slice(2))) {
|
|
51088
|
+
result[argument.slice(2)] = "true";
|
|
51089
|
+
continue;
|
|
51090
|
+
}
|
|
50945
51091
|
const value = args[index + 1];
|
|
50946
51092
|
if (!value || value.startsWith("--"))
|
|
50947
51093
|
throw new Error(`Missing value for ${argument}`);
|
|
@@ -50955,26 +51101,50 @@ function usage() {
|
|
|
50955
51101
|
console.log(`control-agent <command>
|
|
50956
51102
|
|
|
50957
51103
|
Commands:
|
|
50958
|
-
enroll --enrollment-token <token> [--server http://127.0.0.1:3090] [--websocket-server ws://127.0.0.1:3090/graphql] [--name <name>] [--header "Name: value"]...
|
|
51104
|
+
enroll --enrollment-token <token> [--server http://127.0.0.1:3090] [--websocket-server ws://127.0.0.1:3090/graphql] [--remote-server https://control.example.com] [--remote-websocket-server wss://control.example.com/graphql] [--name <name>] [--header "Name: value"]...
|
|
51105
|
+
endpoints [--server <url>] [--websocket-server <url>] [--remote-server <url>] [--remote-websocket-server <url>] [--clear-remote]
|
|
51106
|
+
enrollment
|
|
51107
|
+
unenroll
|
|
50959
51108
|
dev [--server http://127.0.0.1:3000] [--websocket-server ws://127.0.0.1:3092/graphql] [--name <name>]
|
|
50960
51109
|
run
|
|
50961
51110
|
status
|
|
50962
51111
|
doctor`);
|
|
50963
51112
|
}
|
|
51113
|
+
function endpointOptions(options, fallback) {
|
|
51114
|
+
const server = normalizeServer(
|
|
51115
|
+
options.server ?? fallback?.server ?? "http://127.0.0.1:3090"
|
|
51116
|
+
);
|
|
51117
|
+
const websocketServer = options["websocket-server"] ?? // A new local address invalidates a websocket address derived from the old
|
|
51118
|
+
// one, so only an explicitly stored value survives a server change.
|
|
51119
|
+
(options.server ? void 0 : fallback?.websocketServer) ?? defaultWebSocketServer(server);
|
|
51120
|
+
const clearRemote = options["clear-remote"] === "true";
|
|
51121
|
+
const remote = options["remote-server"] ?? (clearRemote ? null : void 0);
|
|
51122
|
+
const remoteServer = remote === void 0 ? fallback?.remoteServer ?? null : remote && normalizeServer(remote);
|
|
51123
|
+
const remoteWebSocketServer = !remoteServer ? null : options["remote-websocket-server"] ?? (options["remote-server"] ? void 0 : fallback?.remoteWebSocketServer) ?? defaultWebSocketServer(remoteServer);
|
|
51124
|
+
return {
|
|
51125
|
+
server,
|
|
51126
|
+
websocketServer,
|
|
51127
|
+
remoteServer,
|
|
51128
|
+
remoteWebSocketServer
|
|
51129
|
+
};
|
|
51130
|
+
}
|
|
50964
51131
|
async function enroll(args) {
|
|
50965
51132
|
const parsed = flags(args);
|
|
50966
51133
|
const options = parsed.values;
|
|
50967
51134
|
const enrollmentToken = options["enrollment-token"];
|
|
50968
51135
|
if (!enrollmentToken) throw new Error("--enrollment-token is required");
|
|
50969
|
-
const
|
|
50970
|
-
const websocketServer = options["websocket-server"] ?? defaultWebSocketServer(server);
|
|
51136
|
+
const addresses = endpointOptions(options);
|
|
50971
51137
|
const inventory = collectInventory();
|
|
50972
51138
|
const name = options.name ?? inventory.hostname;
|
|
50973
|
-
const client2 = new AgentGraphQLClient(
|
|
51139
|
+
const client2 = new AgentGraphQLClient(
|
|
51140
|
+
addresses.server,
|
|
51141
|
+
null,
|
|
51142
|
+
1e4,
|
|
51143
|
+
parsed.headers
|
|
51144
|
+
);
|
|
50974
51145
|
const response = await client2.enroll({ ...inventory, enrollmentToken, name });
|
|
50975
51146
|
await saveConfig({
|
|
50976
|
-
|
|
50977
|
-
websocketServer,
|
|
51147
|
+
...addresses,
|
|
50978
51148
|
agentId: response.enrollAgent.agent.id,
|
|
50979
51149
|
credential: response.enrollAgent.credential,
|
|
50980
51150
|
name,
|
|
@@ -50982,6 +51152,63 @@ async function enroll(args) {
|
|
|
50982
51152
|
});
|
|
50983
51153
|
console.log(`Enrolled agent ${name} (${response.enrollAgent.agent.id})`);
|
|
50984
51154
|
}
|
|
51155
|
+
async function endpoints(args) {
|
|
51156
|
+
const options = flags(args).values;
|
|
51157
|
+
const config = await loadConfig();
|
|
51158
|
+
const changing = [
|
|
51159
|
+
"server",
|
|
51160
|
+
"websocket-server",
|
|
51161
|
+
"remote-server",
|
|
51162
|
+
"remote-websocket-server",
|
|
51163
|
+
"clear-remote"
|
|
51164
|
+
].some((flag) => flag in options);
|
|
51165
|
+
const updated = changing ? { ...config, ...endpointOptions(options, config) } : config;
|
|
51166
|
+
if (changing) await saveConfig(updated);
|
|
51167
|
+
for (const endpoint of agentEndpoints(updated)) {
|
|
51168
|
+
console.log(
|
|
51169
|
+
`${endpoint.kind}: ${endpoint.server} (${endpoint.websocketServer})`
|
|
51170
|
+
);
|
|
51171
|
+
}
|
|
51172
|
+
if (!updated.remoteServer) console.log("remote: not configured");
|
|
51173
|
+
}
|
|
51174
|
+
async function enrollment() {
|
|
51175
|
+
let config;
|
|
51176
|
+
try {
|
|
51177
|
+
config = await loadConfig();
|
|
51178
|
+
} catch (error) {
|
|
51179
|
+
console.log("Enrolled: no");
|
|
51180
|
+
console.log(error instanceof Error ? error.message : String(error));
|
|
51181
|
+
process.exitCode = 1;
|
|
51182
|
+
return;
|
|
51183
|
+
}
|
|
51184
|
+
console.log("Enrolled: yes");
|
|
51185
|
+
console.log(`Agent: ${config.name} (${config.agentId})`);
|
|
51186
|
+
console.log(`Configuration: ${configPath()}`);
|
|
51187
|
+
let reachable = false;
|
|
51188
|
+
for (const endpoint of agentEndpoints(config)) {
|
|
51189
|
+
const ok2 = await probeEndpoint(endpoint, config);
|
|
51190
|
+
reachable ||= ok2;
|
|
51191
|
+
console.log(
|
|
51192
|
+
`${endpoint.kind}: ${endpoint.server} \u2014 ${ok2 ? "reachable" : "unreachable"}`
|
|
51193
|
+
);
|
|
51194
|
+
}
|
|
51195
|
+
if (!reachable) process.exitCode = 1;
|
|
51196
|
+
}
|
|
51197
|
+
async function unenroll() {
|
|
51198
|
+
const path = configPath();
|
|
51199
|
+
let config = null;
|
|
51200
|
+
try {
|
|
51201
|
+
config = await loadConfig(path);
|
|
51202
|
+
} catch {
|
|
51203
|
+
}
|
|
51204
|
+
await rm14(path, { force: true });
|
|
51205
|
+
console.log(
|
|
51206
|
+
config ? `Removed the enrollment for ${config.name} (${config.agentId}) from ${path}` : `Removed ${path}`
|
|
51207
|
+
);
|
|
51208
|
+
console.log(
|
|
51209
|
+
"The control plane still lists this agent; delete it there to finish removing it."
|
|
51210
|
+
);
|
|
51211
|
+
}
|
|
50985
51212
|
async function status() {
|
|
50986
51213
|
const config = await loadConfig();
|
|
50987
51214
|
const response = await new AgentGraphQLClient(
|
|
@@ -51018,26 +51245,39 @@ async function doctor() {
|
|
|
51018
51245
|
detail: error instanceof Error ? error.message : String(error)
|
|
51019
51246
|
});
|
|
51020
51247
|
}
|
|
51021
|
-
|
|
51022
|
-
|
|
51023
|
-
|
|
51024
|
-
server,
|
|
51025
|
-
|
|
51026
|
-
|
|
51027
|
-
|
|
51028
|
-
|
|
51029
|
-
|
|
51030
|
-
|
|
51031
|
-
|
|
51032
|
-
|
|
51033
|
-
|
|
51034
|
-
|
|
51035
|
-
|
|
51036
|
-
|
|
51037
|
-
|
|
51038
|
-
|
|
51039
|
-
|
|
51040
|
-
|
|
51248
|
+
const reachable = config ? agentEndpoints(config) : [
|
|
51249
|
+
{
|
|
51250
|
+
kind: "local",
|
|
51251
|
+
server: "http://127.0.0.1:3090",
|
|
51252
|
+
websocketServer: "ws://127.0.0.1:3090/graphql"
|
|
51253
|
+
}
|
|
51254
|
+
];
|
|
51255
|
+
const results = await Promise.all(
|
|
51256
|
+
reachable.map(async (endpoint) => {
|
|
51257
|
+
try {
|
|
51258
|
+
const health = await new AgentGraphQLClient(
|
|
51259
|
+
endpoint.server,
|
|
51260
|
+
null,
|
|
51261
|
+
1e4,
|
|
51262
|
+
config?.headers
|
|
51263
|
+
).health();
|
|
51264
|
+
return {
|
|
51265
|
+
ok: health.health === "ok",
|
|
51266
|
+
detail: `${endpoint.kind} ${endpoint.server}: ${health.health}`
|
|
51267
|
+
};
|
|
51268
|
+
} catch (error) {
|
|
51269
|
+
return {
|
|
51270
|
+
ok: false,
|
|
51271
|
+
detail: `${endpoint.kind} ${endpoint.server}: ${error instanceof Error ? error.message : String(error)}`
|
|
51272
|
+
};
|
|
51273
|
+
}
|
|
51274
|
+
})
|
|
51275
|
+
);
|
|
51276
|
+
checks.push({
|
|
51277
|
+
check: "control plane",
|
|
51278
|
+
ok: results.some((result) => result.ok),
|
|
51279
|
+
detail: results.map((result) => result.detail).join("; ")
|
|
51280
|
+
});
|
|
51041
51281
|
for (const check of checks)
|
|
51042
51282
|
console.log(
|
|
51043
51283
|
`${check.ok ? "PASS" : "FAIL"} ${check.check}: ${check.detail}`
|
|
@@ -51051,6 +51291,9 @@ async function main() {
|
|
|
51051
51291
|
return;
|
|
51052
51292
|
}
|
|
51053
51293
|
if (command3 === "enroll") return enroll(args);
|
|
51294
|
+
if (command3 === "endpoints") return endpoints(args);
|
|
51295
|
+
if (command3 === "enrollment") return enrollment();
|
|
51296
|
+
if (command3 === "unenroll") return unenroll();
|
|
51054
51297
|
if (command3 === "status") return status();
|
|
51055
51298
|
if (command3 === "doctor") return doctor();
|
|
51056
51299
|
if (command3 === "run" || command3 === "dev") {
|