ever-works-node 0.2.2 → 0.2.4
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/cli.js +1224 -651
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -3767,10 +3767,14 @@ var USER_SELECTABLE_WORK_KINDS = [
|
|
|
3767
3767
|
"repo",
|
|
3768
3768
|
"app"
|
|
3769
3769
|
];
|
|
3770
|
-
var WORK_KINDS = [...USER_SELECTABLE_WORK_KINDS, "company", "campaign", "default"];
|
|
3770
|
+
var WORK_KINDS = [...USER_SELECTABLE_WORK_KINDS, "company", "campaign", "project", "default"];
|
|
3771
3771
|
var WORK_KIND_SET = new Set(WORK_KINDS);
|
|
3772
3772
|
var GOAL_KINDS = ["metric", "delivery"];
|
|
3773
3773
|
var GOAL_KIND_SET = new Set(GOAL_KINDS);
|
|
3774
|
+
var RECORD_VISIBILITIES = ["private", "team", "organization"];
|
|
3775
|
+
var RECORD_VISIBILITY_SET = new Set(RECORD_VISIBILITIES);
|
|
3776
|
+
var RECORD_LIST_VIEWS = ["mine", "shared", "organization"];
|
|
3777
|
+
var RECORD_LIST_VIEW_SET = new Set(RECORD_LIST_VIEWS);
|
|
3774
3778
|
var APP_DEPENDENCY_BACKUP_OVERDUE_MS = 26 * 36e5;
|
|
3775
3779
|
var APP_DEPENDENCY_MANAGED = {
|
|
3776
3780
|
pgRoleConnectionLimit: 20,
|
|
@@ -6119,6 +6123,145 @@ var EVER_STATS_SIGNATURE_HEADER = CONSTANTS.stats_headers.signature;
|
|
|
6119
6123
|
var EVER_STATS_KEY_ID_HEADER = CONSTANTS.stats_headers.key_id;
|
|
6120
6124
|
var EVER_STATS_SIGNATURE_PREFIX = CONSTANTS.stats_signature_prefix;
|
|
6121
6125
|
var EVER_STATS_MAX_BODY_BYTES = CONSTANTS.stats.max_bytes;
|
|
6126
|
+
var DOMAIN_EVENT_CATALOG = [
|
|
6127
|
+
{ type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
|
|
6128
|
+
{
|
|
6129
|
+
type: "organization.member.added",
|
|
6130
|
+
domain: "organization",
|
|
6131
|
+
aggregateType: "organization",
|
|
6132
|
+
phase: "P1",
|
|
6133
|
+
child: "member"
|
|
6134
|
+
},
|
|
6135
|
+
{
|
|
6136
|
+
type: "organization.member.updated",
|
|
6137
|
+
domain: "organization",
|
|
6138
|
+
aggregateType: "organization",
|
|
6139
|
+
phase: "P1",
|
|
6140
|
+
child: "member"
|
|
6141
|
+
},
|
|
6142
|
+
{
|
|
6143
|
+
type: "organization.member.removed",
|
|
6144
|
+
domain: "organization",
|
|
6145
|
+
aggregateType: "organization",
|
|
6146
|
+
phase: "P1",
|
|
6147
|
+
child: "member"
|
|
6148
|
+
},
|
|
6149
|
+
{ type: "agent.created", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
6150
|
+
{ type: "agent.updated", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
6151
|
+
{ type: "agent.status_changed", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
6152
|
+
{ type: "agent.archived", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
6153
|
+
{ type: "agent.deleted", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
6154
|
+
{ type: "team.created", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
6155
|
+
{ type: "team.updated", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
6156
|
+
{ type: "team.deleted", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
6157
|
+
{ type: "team.member.added", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
6158
|
+
{ type: "team.member.updated", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
6159
|
+
{ type: "team.member.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
6160
|
+
{ type: "team.resource.added", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
6161
|
+
{ type: "team.resource.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
6162
|
+
{ type: "work.created", domain: "works", aggregateType: "work", phase: "P1" },
|
|
6163
|
+
{ type: "work.updated", domain: "works", aggregateType: "work", phase: "P1" },
|
|
6164
|
+
{ type: "work.archived", domain: "works", aggregateType: "work", phase: "P1" },
|
|
6165
|
+
{ type: "work.deleted", domain: "works", aggregateType: "work", phase: "P1" },
|
|
6166
|
+
{ type: "work.member.added", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
6167
|
+
{ type: "work.member.updated", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
6168
|
+
{ type: "work.member.removed", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
6169
|
+
{ type: "mission.created", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6170
|
+
{ type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6171
|
+
{ type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6172
|
+
{ type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6173
|
+
{ type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6174
|
+
{ type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6175
|
+
{ type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6176
|
+
{ type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
6177
|
+
{ type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
6178
|
+
{ type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
6179
|
+
{ type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
6180
|
+
{ type: "goal.archived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
6181
|
+
{ type: "goal.unarchived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
6182
|
+
{ type: "goal.deleted", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
6183
|
+
{ type: "goal.progress_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
6184
|
+
{ type: "idea.created", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
6185
|
+
{ type: "idea.updated", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
6186
|
+
{ type: "idea.status_changed", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
6187
|
+
{ type: "idea.deleted", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
6188
|
+
{ type: "task.created", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
6189
|
+
{ type: "task.updated", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
6190
|
+
{ type: "task.transitioned", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
6191
|
+
{ type: "task.deleted", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
6192
|
+
{ type: "task.assignee.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
6193
|
+
{ type: "task.assignee.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
6194
|
+
{ type: "task.watcher.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
6195
|
+
{ type: "task.watcher.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
6196
|
+
{ type: "task.block.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
6197
|
+
{ type: "task.block.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
6198
|
+
{ type: "task.relation.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
6199
|
+
{ type: "task.relation.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
6200
|
+
{ type: "task.comment.created", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
6201
|
+
{ type: "task.comment.updated", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
6202
|
+
{ type: "task.comment.deleted", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
6203
|
+
{ type: "task.attachment.added", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
6204
|
+
{ type: "task.attachment.removed", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
6205
|
+
{ type: "task.runtime_updated", domain: "tasks", aggregateType: "task", phase: "P1", optIn: true },
|
|
6206
|
+
{ type: "inbox.item.created", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
6207
|
+
{ type: "inbox.item.answered", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
6208
|
+
{ type: "inbox.item.state_changed", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
6209
|
+
{ type: "inbox.item.deleted", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
6210
|
+
{ type: "kb.document.created", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
6211
|
+
{ type: "kb.document.updated", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
6212
|
+
{ type: "kb.document.moved", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
6213
|
+
{ type: "kb.document.archived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
6214
|
+
{ type: "kb.document.unarchived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
6215
|
+
{ type: "kb.document.deleted", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
6216
|
+
{ type: "kb.document.review_changed", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
6217
|
+
{ type: "kb.folder.created", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
6218
|
+
{ type: "kb.folder.updated", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
6219
|
+
{ type: "kb.folder.deleted", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
6220
|
+
{ type: "email.message.received", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
6221
|
+
{ type: "email.message.sent", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
6222
|
+
{ type: "email.message.status_changed", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
6223
|
+
{ type: "task.visibility_changed", domain: "visibility", aggregateType: "task", phase: "P1" },
|
|
6224
|
+
{ type: "agent.visibility_changed", domain: "visibility", aggregateType: "agent", phase: "P1" },
|
|
6225
|
+
{ type: "goal.visibility_changed", domain: "visibility", aggregateType: "goal", phase: "P2" },
|
|
6226
|
+
{ type: "mission.visibility_changed", domain: "visibility", aggregateType: "mission", phase: "P2" },
|
|
6227
|
+
{ type: "idea.visibility_changed", domain: "visibility", aggregateType: "idea", phase: "P2" }
|
|
6228
|
+
];
|
|
6229
|
+
var DOMAIN_EVENT_TYPES = DOMAIN_EVENT_CATALOG.map((entry) => entry.type);
|
|
6230
|
+
var CATALOG_BY_TYPE = new Map(
|
|
6231
|
+
DOMAIN_EVENT_CATALOG.map((entry) => [entry.type, entry])
|
|
6232
|
+
);
|
|
6233
|
+
var INTEGRATION_SCOPES = [
|
|
6234
|
+
"org:read",
|
|
6235
|
+
"org:write",
|
|
6236
|
+
"members:read",
|
|
6237
|
+
"members:write",
|
|
6238
|
+
"agents:read",
|
|
6239
|
+
"teams:read",
|
|
6240
|
+
"teams:write",
|
|
6241
|
+
"works:read",
|
|
6242
|
+
"works:write",
|
|
6243
|
+
"missions:read",
|
|
6244
|
+
"missions:write",
|
|
6245
|
+
"goals:read",
|
|
6246
|
+
"goals:write",
|
|
6247
|
+
"ideas:read",
|
|
6248
|
+
"ideas:write",
|
|
6249
|
+
"tasks:read",
|
|
6250
|
+
"tasks:write",
|
|
6251
|
+
"inbox:read",
|
|
6252
|
+
"inbox:write",
|
|
6253
|
+
"kb:read",
|
|
6254
|
+
"kb:write",
|
|
6255
|
+
"email:read",
|
|
6256
|
+
"email:write",
|
|
6257
|
+
"events:read",
|
|
6258
|
+
"webhooks:manage",
|
|
6259
|
+
"links:write"
|
|
6260
|
+
];
|
|
6261
|
+
var INTEGRATION_OPT_IN_SCOPES = ["org:write", "email:write"];
|
|
6262
|
+
var EVER_GAUZY_PRESET_SCOPES = INTEGRATION_SCOPES.filter(
|
|
6263
|
+
(scope) => !INTEGRATION_OPT_IN_SCOPES.includes(scope)
|
|
6264
|
+
);
|
|
6122
6265
|
|
|
6123
6266
|
// src/core/gpu-probe.ts
|
|
6124
6267
|
var VENDOR_PATTERNS = [
|
|
@@ -7232,272 +7375,609 @@ function readPendingComputerSessions(payload) {
|
|
|
7232
7375
|
}
|
|
7233
7376
|
__name(readPendingComputerSessions, "readPendingComputerSessions");
|
|
7234
7377
|
|
|
7235
|
-
// src/core/
|
|
7236
|
-
var
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7378
|
+
// src/core/heartbeat.ts
|
|
7379
|
+
var systemScheduler = {
|
|
7380
|
+
setTimeout: /* @__PURE__ */ __name((callback, ms) => setTimeout(callback, ms), "setTimeout"),
|
|
7381
|
+
clearTimeout: /* @__PURE__ */ __name((handle) => clearTimeout(handle), "clearTimeout")
|
|
7382
|
+
};
|
|
7383
|
+
var OPTIONAL_DESCRIPTION_FIELD_TIERS = [
|
|
7384
|
+
// NEWEST first. Node lifecycle + remote node limits (self-build slices
|
|
7385
|
+
// AR, AS). Tiered (review): `cliVersions` goes out on EVERY beat of a
|
|
7386
|
+
// node with a pinned CLI, so an upgraded node talking to a platform that
|
|
7387
|
+
// predates it would otherwise drop the worker state and housekeeping
|
|
7388
|
+
// along with it — losing health signals that platform DOES understand,
|
|
7389
|
+
// for the life of the process. Stripping the newest tier first keeps them.
|
|
7390
|
+
["cliVersions", "maxConcurrentJobs", "maxCpuPercent", "maxMemoryMb"],
|
|
7391
|
+
// Fleet health signals (EW-776) and node housekeeping (EW-803).
|
|
7392
|
+
[
|
|
7393
|
+
"workerState",
|
|
7394
|
+
"workerStateReason",
|
|
7395
|
+
"minFreeDiskBytes",
|
|
7396
|
+
"workspaceCount",
|
|
7397
|
+
"workspaceBytes",
|
|
7398
|
+
"lastReclaimAt",
|
|
7399
|
+
"lastReclaimFreedBytes"
|
|
7400
|
+
]
|
|
7401
|
+
];
|
|
7402
|
+
function untilHeartbeatAccepted(loop, shouldWait = () => true) {
|
|
7403
|
+
const verdict = /* @__PURE__ */ __name((state) => {
|
|
7404
|
+
if (state.state === "connected") return true;
|
|
7405
|
+
if (state.state === "stopped" || !shouldWait()) return false;
|
|
7240
7406
|
return null;
|
|
7241
|
-
}
|
|
7242
|
-
const
|
|
7243
|
-
|
|
7407
|
+
}, "verdict");
|
|
7408
|
+
const now = verdict(loop.getState());
|
|
7409
|
+
if (now !== null) return Promise.resolve(now);
|
|
7410
|
+
return new Promise((resolve9) => {
|
|
7411
|
+
const unsubscribe = loop.onChange((state) => {
|
|
7412
|
+
const settled = verdict(state);
|
|
7413
|
+
if (settled === null) return;
|
|
7414
|
+
unsubscribe();
|
|
7415
|
+
resolve9(settled);
|
|
7416
|
+
});
|
|
7417
|
+
});
|
|
7244
7418
|
}
|
|
7245
|
-
__name(
|
|
7246
|
-
function
|
|
7247
|
-
|
|
7248
|
-
|
|
7419
|
+
__name(untilHeartbeatAccepted, "untilHeartbeatAccepted");
|
|
7420
|
+
function computeBackoffDelay(intervalMs, failures, maxMs) {
|
|
7421
|
+
if (failures <= 0) {
|
|
7422
|
+
return intervalMs;
|
|
7423
|
+
}
|
|
7424
|
+
const exponent = Math.min(failures, 30);
|
|
7425
|
+
const delay2 = intervalMs * 2 ** exponent;
|
|
7426
|
+
return Math.min(delay2, maxMs);
|
|
7249
7427
|
}
|
|
7250
|
-
__name(
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
if (pinnedTo === null) {
|
|
7255
|
-
return { url: configuredUrl, source: "config", configuredUrl, pinnedTo: null, mismatch: false };
|
|
7428
|
+
__name(computeBackoffDelay, "computeBackoffDelay");
|
|
7429
|
+
var HeartbeatLoop = class {
|
|
7430
|
+
static {
|
|
7431
|
+
__name(this, "HeartbeatLoop");
|
|
7256
7432
|
}
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7433
|
+
options;
|
|
7434
|
+
scheduler;
|
|
7435
|
+
now;
|
|
7436
|
+
listeners = /* @__PURE__ */ new Set();
|
|
7437
|
+
timer = null;
|
|
7438
|
+
running = false;
|
|
7439
|
+
inFlight = null;
|
|
7440
|
+
/**
|
|
7441
|
+
* How many optional-field TIERS (newest first) this platform has proven
|
|
7442
|
+
* it predates; from then on the beat leaves those tiers out. 0 = it
|
|
7443
|
+
* takes everything. Process-scoped on purpose — a platform upgrade is a
|
|
7444
|
+
* restart-shaped event for the node, and re-probing on every beat would
|
|
7445
|
+
* mean one wasted round trip per beat, forever, against an API that
|
|
7446
|
+
* will never accept them.
|
|
7447
|
+
*/
|
|
7448
|
+
strippedTiers = 0;
|
|
7449
|
+
state = {
|
|
7450
|
+
state: "idle",
|
|
7451
|
+
lastHeartbeatAt: null,
|
|
7452
|
+
consecutiveFailures: 0,
|
|
7453
|
+
nextAttemptInMs: null,
|
|
7454
|
+
lastError: null,
|
|
7455
|
+
lastErrorKind: null,
|
|
7456
|
+
node: null
|
|
7263
7457
|
};
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7458
|
+
constructor(options) {
|
|
7459
|
+
this.options = {
|
|
7460
|
+
...options,
|
|
7461
|
+
intervalMs: options.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
7462
|
+
maxBackoffMs: options.maxBackoffMs ?? MAX_HEARTBEAT_BACKOFF_MS
|
|
7463
|
+
};
|
|
7464
|
+
this.scheduler = options.scheduler ?? systemScheduler;
|
|
7465
|
+
this.now = options.now ?? (() => Date.now());
|
|
7466
|
+
this.options.logger?.protect(options.secret);
|
|
7269
7467
|
}
|
|
7270
|
-
|
|
7271
|
-
return
|
|
7468
|
+
getState() {
|
|
7469
|
+
return { ...this.state };
|
|
7272
7470
|
}
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
if (typeof stdout !== "string") return null;
|
|
7284
|
-
const match = /\b(\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?)\b/.exec(stdout);
|
|
7285
|
-
return match ? match[1] : null;
|
|
7286
|
-
}
|
|
7287
|
-
__name(parseCliVersion, "parseCliVersion");
|
|
7288
|
-
async function detectAgentCliVersion(runner, candidates = AGENT_CLI_CANDIDATES) {
|
|
7289
|
-
for (const command of candidates) {
|
|
7290
|
-
try {
|
|
7291
|
-
const result = await runner.run(command, ["--version"]);
|
|
7292
|
-
if (result.code !== 0) continue;
|
|
7293
|
-
const version = parseCliVersion(`${result.stdout ?? ""}
|
|
7294
|
-
${result.stderr ?? ""}`);
|
|
7295
|
-
if (!version) continue;
|
|
7296
|
-
return `${command} ${version}`.slice(0, FLEET_MAX_CLI_VERSION_LENGTH);
|
|
7297
|
-
} catch {
|
|
7298
|
-
continue;
|
|
7471
|
+
onChange(listener) {
|
|
7472
|
+
this.listeners.add(listener);
|
|
7473
|
+
return () => {
|
|
7474
|
+
this.listeners.delete(listener);
|
|
7475
|
+
};
|
|
7476
|
+
}
|
|
7477
|
+
/** Start beating. Resolves once the FIRST beat has settled. */
|
|
7478
|
+
start() {
|
|
7479
|
+
if (this.running) {
|
|
7480
|
+
return this.inFlight ?? Promise.resolve();
|
|
7299
7481
|
}
|
|
7482
|
+
this.running = true;
|
|
7483
|
+
return this.tick();
|
|
7300
7484
|
}
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
const free = await io.freeBytes(path2);
|
|
7307
|
-
if (typeof free !== "number" || !Number.isFinite(free) || free < 0) return null;
|
|
7308
|
-
return Math.floor(free);
|
|
7309
|
-
} catch {
|
|
7310
|
-
return null;
|
|
7485
|
+
/** Stop beating and cancel any pending timer. */
|
|
7486
|
+
stop() {
|
|
7487
|
+
this.running = false;
|
|
7488
|
+
this.cancelTimer();
|
|
7489
|
+
this.patch({ state: "stopped", nextAttemptInMs: null });
|
|
7311
7490
|
}
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
function parseClaudeAuthStatus(stdout) {
|
|
7316
|
-
let parsed;
|
|
7317
|
-
try {
|
|
7318
|
-
parsed = JSON.parse(stdout.trim());
|
|
7319
|
-
} catch {
|
|
7320
|
-
return null;
|
|
7491
|
+
/** Await the in-flight beat, if any (test/shutdown helper). */
|
|
7492
|
+
async settled() {
|
|
7493
|
+
await this.inFlight;
|
|
7321
7494
|
}
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
if (text.includes("logged in")) return "codex: logged in";
|
|
7341
|
-
return null;
|
|
7342
|
-
}
|
|
7343
|
-
__name(parseCodexLoginStatus, "parseCodexLoginStatus");
|
|
7344
|
-
function compactString(value) {
|
|
7345
|
-
if (typeof value !== "string") return null;
|
|
7346
|
-
const cleaned = value.replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
|
|
7347
|
-
return cleaned ? cleaned : null;
|
|
7348
|
-
}
|
|
7349
|
-
__name(compactString, "compactString");
|
|
7350
|
-
async function detectModelIdentity(runner, paths = {}) {
|
|
7351
|
-
const candidates = [
|
|
7352
|
-
{ command: paths["claude-code"] || "claude", args: ["auth", "status", "--json"], parse: parseClaudeAuthStatus },
|
|
7353
|
-
{ command: paths.codex || "codex", args: ["login", "status"], parse: parseCodexLoginStatus }
|
|
7354
|
-
];
|
|
7355
|
-
for (const candidate of candidates) {
|
|
7495
|
+
/** Run one beat now. Exposed so the UI can offer "retry" and tests can step. */
|
|
7496
|
+
tick() {
|
|
7497
|
+
const run = this.runOnce().finally(() => {
|
|
7498
|
+
if (this.inFlight === run) {
|
|
7499
|
+
this.inFlight = null;
|
|
7500
|
+
}
|
|
7501
|
+
});
|
|
7502
|
+
this.inFlight = run;
|
|
7503
|
+
return run;
|
|
7504
|
+
}
|
|
7505
|
+
async runOnce() {
|
|
7506
|
+
if (!this.running) {
|
|
7507
|
+
return;
|
|
7508
|
+
}
|
|
7509
|
+
this.cancelTimer();
|
|
7510
|
+
if (this.state.state !== "connected") {
|
|
7511
|
+
this.patch({ state: "connecting" });
|
|
7512
|
+
}
|
|
7356
7513
|
try {
|
|
7357
|
-
const
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
${stderr}`);
|
|
7362
|
-
if (label) return label.slice(0, FLEET_MAX_MODEL_IDENTITY_LENGTH);
|
|
7363
|
-
} catch {
|
|
7364
|
-
continue;
|
|
7514
|
+
const description = await this.options.describe();
|
|
7515
|
+
this.onSuccess(await this.beat(description));
|
|
7516
|
+
} catch (error) {
|
|
7517
|
+
this.onFailure(error);
|
|
7365
7518
|
}
|
|
7519
|
+
this.scheduleNext();
|
|
7366
7520
|
}
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7521
|
+
/**
|
|
7522
|
+
* Send one beat, tolerating a platform that predates the optional
|
|
7523
|
+
* self-description fields (the worker state, EW-776; the housekeeping
|
|
7524
|
+
* report, EW-803).
|
|
7525
|
+
*
|
|
7526
|
+
* The API validates heartbeats with `whitelist + forbidNonWhitelisted`,
|
|
7527
|
+
* so a field an older build does not know is not ignored — it 400s the
|
|
7528
|
+
* whole request. A 400 is a FAILED beat, a failed beat backs off, and
|
|
7529
|
+
* enough of them sweep the node to `offline`. Which would mean shipping
|
|
7530
|
+
* a health-reporting feature whose failure mode is "every node in a
|
|
7531
|
+
* mixed-version fleet goes dark": the exact class of outage this slice
|
|
7532
|
+
* was written to end.
|
|
7533
|
+
*
|
|
7534
|
+
* So a 400 on a beat that CARRIED those fields is retried once,
|
|
7535
|
+
* immediately, without them. If that succeeds the platform is simply
|
|
7536
|
+
* older than this daemon; we latch, say so once, and keep reporting
|
|
7537
|
+
* liveness. The retry costs one request, once per process.
|
|
7538
|
+
*
|
|
7539
|
+
* Inferring "the field was rejected" from a bare 400 is deliberate and
|
|
7540
|
+
* has a cost worth stating: `FleetClient` never surfaces server bodies
|
|
7541
|
+
* (fixed client-authored messages only), so a 400 caused by something
|
|
7542
|
+
* else on such a beat also latches, and this node reports liveness only
|
|
7543
|
+
* until it restarts. That is the safe direction — the node stays
|
|
7544
|
+
* observable either way, and only the extra signal is lost.
|
|
7545
|
+
*/
|
|
7546
|
+
async beat(description) {
|
|
7547
|
+
const credential = { nodeId: this.options.nodeId, secret: this.options.secret };
|
|
7548
|
+
let sent = stripTiers(description, this.strippedTiers);
|
|
7549
|
+
try {
|
|
7550
|
+
return await this.options.client.heartbeat({ ...credential, ...sent });
|
|
7551
|
+
} catch (error) {
|
|
7552
|
+
if (!isFieldRejection(error)) throw error;
|
|
7553
|
+
let lastError = error;
|
|
7554
|
+
for (let tiers = this.strippedTiers + 1; tiers <= OPTIONAL_DESCRIPTION_FIELD_TIERS.length; tiers += 1) {
|
|
7555
|
+
const candidate = stripTiers(description, tiers);
|
|
7556
|
+
if (Object.keys(candidate).length === Object.keys(sent).length) continue;
|
|
7557
|
+
sent = candidate;
|
|
7558
|
+
try {
|
|
7559
|
+
const result = await this.options.client.heartbeat({ ...credential, ...candidate });
|
|
7560
|
+
this.strippedTiers = tiers;
|
|
7561
|
+
this.options.logger?.warn(
|
|
7562
|
+
tiers >= OPTIONAL_DESCRIPTION_FIELD_TIERS.length ? "Platform rejected the optional self-description fields (worker state, housekeeping); it predates them. Reporting liveness only until this node restarts." : "Platform rejected the newest optional self-description fields (pinned CLI versions, enforced limits); it predates them. Still reporting worker state and housekeeping; the newer fields stay off until this node restarts."
|
|
7563
|
+
);
|
|
7564
|
+
return result;
|
|
7565
|
+
} catch (retryError) {
|
|
7566
|
+
if (!isFieldRejection(retryError)) throw retryError;
|
|
7567
|
+
lastError = retryError;
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
throw lastError;
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7573
|
+
onSuccess(result) {
|
|
7574
|
+
this.patch({
|
|
7575
|
+
state: "connected",
|
|
7576
|
+
lastHeartbeatAt: this.now(),
|
|
7577
|
+
consecutiveFailures: 0,
|
|
7578
|
+
lastError: null,
|
|
7579
|
+
lastErrorKind: null,
|
|
7580
|
+
node: result.node
|
|
7383
7581
|
});
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
var MODEL_CLI_EMPTY_MCP_CONFIG = `${JSON.stringify({ mcpServers: {} }, null, 2)}
|
|
7391
|
-
`;
|
|
7392
|
-
function modelCliMcpIsolationEnv(provider) {
|
|
7393
|
-
return provider === "claude-code" ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : null;
|
|
7394
|
-
}
|
|
7395
|
-
__name(modelCliMcpIsolationEnv, "modelCliMcpIsolationEnv");
|
|
7396
|
-
function claudeManagedMcpConfigPath(platform = process.platform) {
|
|
7397
|
-
if (platform === "win32") return "C:\\Program Files\\ClaudeCode\\managed-mcp.json";
|
|
7398
|
-
if (platform === "darwin") return "/Library/Application Support/ClaudeCode/managed-mcp.json";
|
|
7399
|
-
return "/etc/claude-code/managed-mcp.json";
|
|
7400
|
-
}
|
|
7401
|
-
__name(claudeManagedMcpConfigPath, "claudeManagedMcpConfigPath");
|
|
7402
|
-
function claudeManagedMcpConfigInEffect(raw) {
|
|
7403
|
-
if (typeof raw !== "string" || !raw.trim()) return false;
|
|
7404
|
-
try {
|
|
7405
|
-
const parsed = JSON.parse(raw);
|
|
7406
|
-
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
|
|
7407
|
-
} catch {
|
|
7408
|
-
return false;
|
|
7582
|
+
if (this.options.onAccepted) {
|
|
7583
|
+
try {
|
|
7584
|
+
this.options.onAccepted(result);
|
|
7585
|
+
} catch {
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7409
7588
|
}
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7589
|
+
onFailure(error) {
|
|
7590
|
+
const kind = error instanceof FleetClientError ? error.kind : "network";
|
|
7591
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
7592
|
+
const message = this.options.logger?.redact(raw) ?? raw;
|
|
7593
|
+
const failures = this.state.consecutiveFailures + 1;
|
|
7594
|
+
this.patch({
|
|
7595
|
+
// `unauthorized` is sticky and visible: the operator has to act
|
|
7596
|
+
// (re-enable the node in Fleet, or re-enroll). We keep retrying at
|
|
7597
|
+
// the backoff ceiling so a re-enabled node recovers on its own.
|
|
7598
|
+
state: kind === "unauthorized" ? "unauthorized" : "retrying",
|
|
7599
|
+
consecutiveFailures: failures,
|
|
7600
|
+
lastError: message,
|
|
7601
|
+
lastErrorKind: kind
|
|
7602
|
+
});
|
|
7603
|
+
this.options.logger?.warn(`Heartbeat failed (attempt ${failures}): ${message}`);
|
|
7415
7604
|
}
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7605
|
+
scheduleNext() {
|
|
7606
|
+
if (!this.running) {
|
|
7607
|
+
return;
|
|
7608
|
+
}
|
|
7609
|
+
const delay2 = computeBackoffDelay(
|
|
7610
|
+
this.options.intervalMs,
|
|
7611
|
+
this.state.consecutiveFailures,
|
|
7612
|
+
this.options.maxBackoffMs
|
|
7613
|
+
);
|
|
7614
|
+
this.patch({ nextAttemptInMs: delay2 });
|
|
7615
|
+
this.timer = this.scheduler.setTimeout(() => {
|
|
7616
|
+
this.timer = null;
|
|
7617
|
+
void this.tick();
|
|
7618
|
+
}, delay2);
|
|
7419
7619
|
}
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
var UNSAFE_PATH_CHARS = /[\0\r\n"'`$&|;<>%!^]/;
|
|
7426
|
-
function quoteShellPath(path2, platform = process.platform) {
|
|
7427
|
-
if (typeof path2 !== "string" || !path2.trim()) {
|
|
7428
|
-
throw new ModelCliCommandError("Path must not be empty");
|
|
7620
|
+
cancelTimer() {
|
|
7621
|
+
if (this.timer !== null) {
|
|
7622
|
+
this.scheduler.clearTimeout(this.timer);
|
|
7623
|
+
this.timer = null;
|
|
7624
|
+
}
|
|
7429
7625
|
}
|
|
7430
|
-
|
|
7431
|
-
|
|
7626
|
+
patch(next) {
|
|
7627
|
+
this.state = { ...this.state, ...next };
|
|
7628
|
+
const snapshot = this.getState();
|
|
7629
|
+
for (const listener of this.listeners) {
|
|
7630
|
+
listener(snapshot);
|
|
7631
|
+
}
|
|
7432
7632
|
}
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7633
|
+
};
|
|
7634
|
+
function stripTiers(description, tiers) {
|
|
7635
|
+
const out = {};
|
|
7636
|
+
const dropped = new Set(OPTIONAL_DESCRIPTION_FIELD_TIERS.slice(0, tiers).flat());
|
|
7637
|
+
for (const [key, value] of Object.entries(description)) {
|
|
7638
|
+
if (value === void 0 || dropped.has(key)) continue;
|
|
7639
|
+
out[key] = value;
|
|
7436
7640
|
}
|
|
7437
|
-
return
|
|
7641
|
+
return out;
|
|
7438
7642
|
}
|
|
7439
|
-
__name(
|
|
7440
|
-
function
|
|
7441
|
-
|
|
7442
|
-
throw new ModelCliCommandError("MCP server name is not an opaque identifier");
|
|
7443
|
-
}
|
|
7444
|
-
return name;
|
|
7643
|
+
__name(stripTiers, "stripTiers");
|
|
7644
|
+
function isFieldRejection(error) {
|
|
7645
|
+
return error instanceof FleetClientError && error.kind === "invalid-request" && error.status === 400;
|
|
7445
7646
|
}
|
|
7446
|
-
__name(
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7647
|
+
__name(isFieldRejection, "isFieldRejection");
|
|
7648
|
+
|
|
7649
|
+
// src/core/api-base.ts
|
|
7650
|
+
var API_URL_ENV = "EVER_WORKS_NODE_API_URL";
|
|
7651
|
+
function rawPin(env) {
|
|
7652
|
+
const value = env[API_URL_ENV];
|
|
7653
|
+
if (typeof value !== "string") {
|
|
7654
|
+
return null;
|
|
7450
7655
|
}
|
|
7451
|
-
|
|
7656
|
+
const trimmed = value.trim();
|
|
7657
|
+
return trimmed ? trimmed : null;
|
|
7452
7658
|
}
|
|
7453
|
-
__name(
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
if (model === void 0) return null;
|
|
7458
|
-
if (!FLEET_AGENT_EXECUTION_MODEL_PATTERN.test(model)) {
|
|
7459
|
-
throw new ModelCliCommandError("Model id is not an opaque identifier");
|
|
7460
|
-
}
|
|
7461
|
-
return model;
|
|
7659
|
+
__name(rawPin, "rawPin");
|
|
7660
|
+
function readApiUrlPin(env = process.env) {
|
|
7661
|
+
const raw = rawPin(env);
|
|
7662
|
+
return raw === null ? null : normalizeApiUrl(raw);
|
|
7462
7663
|
}
|
|
7463
|
-
__name(
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
if (!supportedFlags) return [];
|
|
7470
|
-
const wanted = [];
|
|
7471
|
-
if (execution.provider === "claude-code") {
|
|
7472
|
-
if (execution.effort) wanted.push("--effort");
|
|
7473
|
-
if (execution.maxBudgetUsd !== void 0) wanted.push("--max-budget-usd");
|
|
7664
|
+
__name(readApiUrlPin, "readApiUrlPin");
|
|
7665
|
+
function resolveApiBase(config, env = process.env) {
|
|
7666
|
+
const configuredUrl = config.apiUrl;
|
|
7667
|
+
const pinnedTo = readApiUrlPin(env);
|
|
7668
|
+
if (pinnedTo === null) {
|
|
7669
|
+
return { url: configuredUrl, source: "config", configuredUrl, pinnedTo: null, mismatch: false };
|
|
7474
7670
|
}
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7671
|
+
return {
|
|
7672
|
+
url: pinnedTo,
|
|
7673
|
+
source: "pin",
|
|
7674
|
+
configuredUrl,
|
|
7675
|
+
pinnedTo,
|
|
7676
|
+
mismatch: pinnedTo !== configuredUrl
|
|
7677
|
+
};
|
|
7479
7678
|
}
|
|
7480
|
-
__name(
|
|
7481
|
-
function
|
|
7482
|
-
if (
|
|
7483
|
-
|
|
7484
|
-
throw new ModelCliCommandError(`Provider '${String(provider)}' cannot resume a CLI session on the fleet`);
|
|
7485
|
-
}
|
|
7486
|
-
if (!isFleetAgentModelSessionId(sessionId)) {
|
|
7487
|
-
throw new ModelCliCommandError("CLI session id is not an opaque identifier");
|
|
7679
|
+
__name(resolveApiBase, "resolveApiBase");
|
|
7680
|
+
function describeApiBase(base) {
|
|
7681
|
+
if (base.source === "config") {
|
|
7682
|
+
return `${base.url} (from the enrolled config)`;
|
|
7488
7683
|
}
|
|
7489
|
-
|
|
7490
|
-
}
|
|
7491
|
-
__name(assertResumableSession, "assertResumableSession");
|
|
7492
|
-
function formatBudget(value) {
|
|
7493
|
-
if (value === void 0) return null;
|
|
7494
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
7495
|
-
throw new ModelCliCommandError("maxBudgetUsd must be a positive number");
|
|
7684
|
+
if (!base.mismatch) {
|
|
7685
|
+
return `${base.url} (pinned via ${API_URL_ENV}; matches the enrolled origin)`;
|
|
7496
7686
|
}
|
|
7497
|
-
return
|
|
7687
|
+
return `${base.url} (PINNED via ${API_URL_ENV}) \u2014 enrolled against ${base.configuredUrl}. The pin does not match the enrolled origin: this node has no credential on the pinned platform, so every call will be refused with 401 until the pin is corrected or the node is re-enrolled.`;
|
|
7498
7688
|
}
|
|
7499
|
-
__name(
|
|
7500
|
-
|
|
7689
|
+
__name(describeApiBase, "describeApiBase");
|
|
7690
|
+
|
|
7691
|
+
// src/core/auth-client.ts
|
|
7692
|
+
var EVER_ID_DEVICE_SCOPE = ["openid", "email", EVER_ID_SCOPES.SESSION_EXCHANGE].join(" ");
|
|
7693
|
+
|
|
7694
|
+
// src/core/telemetry-probe.ts
|
|
7695
|
+
var AGENT_CLI_CANDIDATES = ["claude", "codex", "gemini", "opencode"];
|
|
7696
|
+
function parseCliVersion(stdout) {
|
|
7697
|
+
if (typeof stdout !== "string") return null;
|
|
7698
|
+
const match = /\b(\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?)\b/.exec(stdout);
|
|
7699
|
+
return match ? match[1] : null;
|
|
7700
|
+
}
|
|
7701
|
+
__name(parseCliVersion, "parseCliVersion");
|
|
7702
|
+
async function detectAgentCliVersion(runner, candidates = AGENT_CLI_CANDIDATES) {
|
|
7703
|
+
for (const command of candidates) {
|
|
7704
|
+
try {
|
|
7705
|
+
const result = await runner.run(command, ["--version"]);
|
|
7706
|
+
if (result.code !== 0) continue;
|
|
7707
|
+
const version = parseCliVersion(`${result.stdout ?? ""}
|
|
7708
|
+
${result.stderr ?? ""}`);
|
|
7709
|
+
if (!version) continue;
|
|
7710
|
+
return `${command} ${version}`.slice(0, FLEET_MAX_CLI_VERSION_LENGTH);
|
|
7711
|
+
} catch {
|
|
7712
|
+
continue;
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
return null;
|
|
7716
|
+
}
|
|
7717
|
+
__name(detectAgentCliVersion, "detectAgentCliVersion");
|
|
7718
|
+
async function detectDiskFreeBytes(io, path2) {
|
|
7719
|
+
try {
|
|
7720
|
+
const free = await io.freeBytes(path2);
|
|
7721
|
+
if (typeof free !== "number" || !Number.isFinite(free) || free < 0) return null;
|
|
7722
|
+
return Math.floor(free);
|
|
7723
|
+
} catch {
|
|
7724
|
+
return null;
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7727
|
+
__name(detectDiskFreeBytes, "detectDiskFreeBytes");
|
|
7728
|
+
var MODEL_IDENTITY_CACHE_TTL_MS = 5 * 6e4;
|
|
7729
|
+
function parseClaudeAuthStatus(stdout) {
|
|
7730
|
+
let parsed;
|
|
7731
|
+
try {
|
|
7732
|
+
parsed = JSON.parse(stdout.trim());
|
|
7733
|
+
} catch {
|
|
7734
|
+
return null;
|
|
7735
|
+
}
|
|
7736
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
7737
|
+
const status = parsed;
|
|
7738
|
+
if (status.loggedIn !== true) return "claude-code: not logged in";
|
|
7739
|
+
const email = compactString(status.email);
|
|
7740
|
+
const org = compactString(status.orgName);
|
|
7741
|
+
const plan = compactString(status.subscriptionType);
|
|
7742
|
+
const method = compactString(status.authMethod);
|
|
7743
|
+
const who = email ?? (method ? `${method} login` : "logged in");
|
|
7744
|
+
const details = [org, plan].filter((part) => part !== null);
|
|
7745
|
+
return `claude-code: ${who}${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
|
|
7746
|
+
}
|
|
7747
|
+
__name(parseClaudeAuthStatus, "parseClaudeAuthStatus");
|
|
7748
|
+
function parseCodexLoginStatus(stdout) {
|
|
7749
|
+
const text = stdout.trim().toLowerCase();
|
|
7750
|
+
if (!text) return null;
|
|
7751
|
+
if (text.includes("not logged in")) return "codex: not logged in";
|
|
7752
|
+
if (text.includes("chatgpt")) return "codex: chatgpt";
|
|
7753
|
+
if (text.includes("api key")) return "codex: api-key";
|
|
7754
|
+
if (text.includes("logged in")) return "codex: logged in";
|
|
7755
|
+
return null;
|
|
7756
|
+
}
|
|
7757
|
+
__name(parseCodexLoginStatus, "parseCodexLoginStatus");
|
|
7758
|
+
function compactString(value) {
|
|
7759
|
+
if (typeof value !== "string") return null;
|
|
7760
|
+
const cleaned = value.replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
|
|
7761
|
+
return cleaned ? cleaned : null;
|
|
7762
|
+
}
|
|
7763
|
+
__name(compactString, "compactString");
|
|
7764
|
+
async function detectModelIdentity(runner, paths = {}) {
|
|
7765
|
+
const candidates = [
|
|
7766
|
+
{ command: paths["claude-code"] || "claude", args: ["auth", "status", "--json"], parse: parseClaudeAuthStatus },
|
|
7767
|
+
{ command: paths.codex || "codex", args: ["login", "status"], parse: parseCodexLoginStatus }
|
|
7768
|
+
];
|
|
7769
|
+
for (const candidate of candidates) {
|
|
7770
|
+
try {
|
|
7771
|
+
const result = await runner.run(candidate.command, candidate.args);
|
|
7772
|
+
const stdout = result.stdout ?? "";
|
|
7773
|
+
const stderr = result.stderr ?? "";
|
|
7774
|
+
const label = candidate.parse(stdout) ?? candidate.parse(`${stdout}
|
|
7775
|
+
${stderr}`);
|
|
7776
|
+
if (label) return label.slice(0, FLEET_MAX_MODEL_IDENTITY_LENGTH);
|
|
7777
|
+
} catch {
|
|
7778
|
+
continue;
|
|
7779
|
+
}
|
|
7780
|
+
}
|
|
7781
|
+
return null;
|
|
7782
|
+
}
|
|
7783
|
+
__name(detectModelIdentity, "detectModelIdentity");
|
|
7784
|
+
function cacheProbe(probe, ttlMs = MODEL_IDENTITY_CACHE_TTL_MS, now = Date.now) {
|
|
7785
|
+
let value = null;
|
|
7786
|
+
let expiresAt = Number.NEGATIVE_INFINITY;
|
|
7787
|
+
let inFlight = null;
|
|
7788
|
+
return async () => {
|
|
7789
|
+
if (now() < expiresAt) return value;
|
|
7790
|
+
if (inFlight) return inFlight;
|
|
7791
|
+
inFlight = probe().then((fresh) => {
|
|
7792
|
+
value = fresh;
|
|
7793
|
+
expiresAt = now() + ttlMs;
|
|
7794
|
+
return fresh;
|
|
7795
|
+
}).finally(() => {
|
|
7796
|
+
inFlight = null;
|
|
7797
|
+
});
|
|
7798
|
+
return inFlight;
|
|
7799
|
+
};
|
|
7800
|
+
}
|
|
7801
|
+
__name(cacheProbe, "cacheProbe");
|
|
7802
|
+
|
|
7803
|
+
// src/core/executors/model-cli.ts
|
|
7804
|
+
var MODEL_CLI_EMPTY_MCP_CONFIG = `${JSON.stringify({ mcpServers: {} }, null, 2)}
|
|
7805
|
+
`;
|
|
7806
|
+
var CODEX_SETTING_VALUE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
7807
|
+
var TOML_STRING_ASSIGNMENT = /^([A-Za-z0-9_-]+)[ \t]*=[ \t]*(?:"([^"\\]*)"|'([^']*)')(?:[ \t]+#.*|#.*)?$/;
|
|
7808
|
+
var TOML_MULTILINE_VALUE_START = /^[^=#]*=[ \t]*("""|''')/;
|
|
7809
|
+
function readCodexUserSettings(configToml) {
|
|
7810
|
+
const settings = {};
|
|
7811
|
+
if (typeof configToml !== "string" || configToml.length === 0) return settings;
|
|
7812
|
+
let table = "";
|
|
7813
|
+
let openMultiline = null;
|
|
7814
|
+
for (const raw of configToml.split(/\r?\n/)) {
|
|
7815
|
+
if (openMultiline !== null) {
|
|
7816
|
+
if (indexOfTomlClosingDelimiter(raw, openMultiline) !== -1) openMultiline = null;
|
|
7817
|
+
continue;
|
|
7818
|
+
}
|
|
7819
|
+
const line = raw.trim();
|
|
7820
|
+
const opening = TOML_MULTILINE_VALUE_START.exec(line);
|
|
7821
|
+
if (opening) {
|
|
7822
|
+
const delimiter = opening[1];
|
|
7823
|
+
if (indexOfTomlClosingDelimiter(line.slice(opening[0].length), delimiter) === -1) {
|
|
7824
|
+
openMultiline = delimiter;
|
|
7825
|
+
continue;
|
|
7826
|
+
}
|
|
7827
|
+
}
|
|
7828
|
+
if (line.startsWith("[")) {
|
|
7829
|
+
if (line.startsWith("[[")) {
|
|
7830
|
+
table = "\0array";
|
|
7831
|
+
} else {
|
|
7832
|
+
const close = line.indexOf("]");
|
|
7833
|
+
table = close > 1 ? line.slice(1, close).trim() : "\0unknown";
|
|
7834
|
+
}
|
|
7835
|
+
continue;
|
|
7836
|
+
}
|
|
7837
|
+
const assignment = TOML_STRING_ASSIGNMENT.exec(line);
|
|
7838
|
+
if (!assignment) continue;
|
|
7839
|
+
const key = assignment[1];
|
|
7840
|
+
const value = assignment[2] ?? assignment[3] ?? "";
|
|
7841
|
+
if (!CODEX_SETTING_VALUE.test(value)) continue;
|
|
7842
|
+
if (table === "" && key === "model" && settings.model === void 0) settings.model = value;
|
|
7843
|
+
else if (table === "" && key === "model_reasoning_effort" && settings.reasoningEffort === void 0) {
|
|
7844
|
+
settings.reasoningEffort = value;
|
|
7845
|
+
} else if (table === "windows" && key === "sandbox" && settings.windowsSandbox === void 0) {
|
|
7846
|
+
settings.windowsSandbox = value;
|
|
7847
|
+
}
|
|
7848
|
+
}
|
|
7849
|
+
return settings;
|
|
7850
|
+
}
|
|
7851
|
+
__name(readCodexUserSettings, "readCodexUserSettings");
|
|
7852
|
+
function indexOfTomlClosingDelimiter(text, delimiter) {
|
|
7853
|
+
let from = 0;
|
|
7854
|
+
for (; ; ) {
|
|
7855
|
+
const at = text.indexOf(delimiter, from);
|
|
7856
|
+
if (at === -1 || delimiter === "'''") return at;
|
|
7857
|
+
let backslashes = 0;
|
|
7858
|
+
for (let i = at - 1; i >= 0 && text[i] === "\\"; i -= 1) backslashes += 1;
|
|
7859
|
+
if (backslashes % 2 === 0) return at;
|
|
7860
|
+
from = at + 1;
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
__name(indexOfTomlClosingDelimiter, "indexOfTomlClosingDelimiter");
|
|
7864
|
+
function assertCodexSetting(name, value) {
|
|
7865
|
+
if (value === void 0) return void 0;
|
|
7866
|
+
if (!CODEX_SETTING_VALUE.test(value)) {
|
|
7867
|
+
throw new ModelCliCommandError(`Refusing to forward the Codex setting '${name}': not a bare identifier`);
|
|
7868
|
+
}
|
|
7869
|
+
return value;
|
|
7870
|
+
}
|
|
7871
|
+
__name(assertCodexSetting, "assertCodexSetting");
|
|
7872
|
+
function modelCliMcpIsolationEnv(provider) {
|
|
7873
|
+
return provider === "claude-code" ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : null;
|
|
7874
|
+
}
|
|
7875
|
+
__name(modelCliMcpIsolationEnv, "modelCliMcpIsolationEnv");
|
|
7876
|
+
function claudeManagedMcpConfigPath(platform = process.platform) {
|
|
7877
|
+
if (platform === "win32") return "C:\\Program Files\\ClaudeCode\\managed-mcp.json";
|
|
7878
|
+
if (platform === "darwin") return "/Library/Application Support/ClaudeCode/managed-mcp.json";
|
|
7879
|
+
return "/etc/claude-code/managed-mcp.json";
|
|
7880
|
+
}
|
|
7881
|
+
__name(claudeManagedMcpConfigPath, "claudeManagedMcpConfigPath");
|
|
7882
|
+
function claudeManagedMcpConfigInEffect(raw) {
|
|
7883
|
+
if (typeof raw !== "string" || !raw.trim()) return false;
|
|
7884
|
+
try {
|
|
7885
|
+
const parsed = JSON.parse(raw);
|
|
7886
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
|
|
7887
|
+
} catch {
|
|
7888
|
+
return false;
|
|
7889
|
+
}
|
|
7890
|
+
}
|
|
7891
|
+
__name(claudeManagedMcpConfigInEffect, "claudeManagedMcpConfigInEffect");
|
|
7892
|
+
var ModelCliCommandError = class extends Error {
|
|
7893
|
+
static {
|
|
7894
|
+
__name(this, "ModelCliCommandError");
|
|
7895
|
+
}
|
|
7896
|
+
constructor(message) {
|
|
7897
|
+
super(message);
|
|
7898
|
+
this.name = "ModelCliCommandError";
|
|
7899
|
+
}
|
|
7900
|
+
};
|
|
7901
|
+
var MODEL_CLI_STEP_ID = "model";
|
|
7902
|
+
var MODEL_CLI_OUTPUT_TAIL_BYTES = 8 * 1024;
|
|
7903
|
+
var MODEL_CLI_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
7904
|
+
var ABSOLUTE_WIN32 = /^[A-Za-z]:[\\/]/;
|
|
7905
|
+
var UNSAFE_PATH_CHARS = /[\0\r\n"'`$&|;<>%!^]/;
|
|
7906
|
+
function quoteShellPath(path2, platform = process.platform) {
|
|
7907
|
+
if (typeof path2 !== "string" || !path2.trim()) {
|
|
7908
|
+
throw new ModelCliCommandError("Path must not be empty");
|
|
7909
|
+
}
|
|
7910
|
+
if (UNSAFE_PATH_CHARS.test(path2)) {
|
|
7911
|
+
throw new ModelCliCommandError("Path contains characters the shell could interpret");
|
|
7912
|
+
}
|
|
7913
|
+
const absolute = platform === "win32" ? ABSOLUTE_WIN32.test(path2) || path2.startsWith("\\\\") : path2.startsWith("/");
|
|
7914
|
+
if (!absolute) {
|
|
7915
|
+
throw new ModelCliCommandError("Path must be absolute");
|
|
7916
|
+
}
|
|
7917
|
+
return `"${path2}"`;
|
|
7918
|
+
}
|
|
7919
|
+
__name(quoteShellPath, "quoteShellPath");
|
|
7920
|
+
function assertMcpServerName(name) {
|
|
7921
|
+
if (typeof name !== "string" || !MCP_SERVER_NAME_PATTERN.test(name)) {
|
|
7922
|
+
throw new ModelCliCommandError("MCP server name is not an opaque identifier");
|
|
7923
|
+
}
|
|
7924
|
+
return name;
|
|
7925
|
+
}
|
|
7926
|
+
__name(assertMcpServerName, "assertMcpServerName");
|
|
7927
|
+
function assertLoopbackUrl(url) {
|
|
7928
|
+
if (typeof url !== "string" || !MCP_LOOPBACK_URL_PATTERN.test(url)) {
|
|
7929
|
+
throw new ModelCliCommandError("MCP bridge URL is not a loopback URL this node produced");
|
|
7930
|
+
}
|
|
7931
|
+
return url;
|
|
7932
|
+
}
|
|
7933
|
+
__name(assertLoopbackUrl, "assertLoopbackUrl");
|
|
7934
|
+
var MCP_SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
7935
|
+
var MCP_LOOPBACK_URL_PATTERN = /^http:\/\/127\.0\.0\.1:\d{1,5}\/mcp\/[a-f0-9]{8,64}$/;
|
|
7936
|
+
function assertModelId(model) {
|
|
7937
|
+
if (model === void 0) return null;
|
|
7938
|
+
if (!FLEET_AGENT_EXECUTION_MODEL_PATTERN.test(model)) {
|
|
7939
|
+
throw new ModelCliCommandError("Model id is not an opaque identifier");
|
|
7940
|
+
}
|
|
7941
|
+
return model;
|
|
7942
|
+
}
|
|
7943
|
+
__name(assertModelId, "assertModelId");
|
|
7944
|
+
var MODEL_CLI_OPTIONAL_FLAGS = {
|
|
7945
|
+
"claude-code": ["--effort", "--max-budget-usd"],
|
|
7946
|
+
codex: []
|
|
7947
|
+
};
|
|
7948
|
+
function unsupportedOptionalModelCliFlags(execution, supportedFlags) {
|
|
7949
|
+
if (!supportedFlags) return [];
|
|
7950
|
+
const wanted = [];
|
|
7951
|
+
if (execution.provider === "claude-code") {
|
|
7952
|
+
if (execution.effort) wanted.push("--effort");
|
|
7953
|
+
if (execution.maxBudgetUsd !== void 0) wanted.push("--max-budget-usd");
|
|
7954
|
+
}
|
|
7955
|
+
const droppable = new Set(
|
|
7956
|
+
MODEL_CLI_OPTIONAL_FLAGS[execution.provider] ?? []
|
|
7957
|
+
);
|
|
7958
|
+
return wanted.filter((flag) => droppable.has(flag) && !supportedFlags.has(flag));
|
|
7959
|
+
}
|
|
7960
|
+
__name(unsupportedOptionalModelCliFlags, "unsupportedOptionalModelCliFlags");
|
|
7961
|
+
function assertResumableSession(provider, sessionId) {
|
|
7962
|
+
if (sessionId === void 0) return null;
|
|
7963
|
+
if (!fleetAgentExecutionProviderSupportsSessionResume(provider)) {
|
|
7964
|
+
throw new ModelCliCommandError(`Provider '${String(provider)}' cannot resume a CLI session on the fleet`);
|
|
7965
|
+
}
|
|
7966
|
+
if (!isFleetAgentModelSessionId(sessionId)) {
|
|
7967
|
+
throw new ModelCliCommandError("CLI session id is not an opaque identifier");
|
|
7968
|
+
}
|
|
7969
|
+
return sessionId;
|
|
7970
|
+
}
|
|
7971
|
+
__name(assertResumableSession, "assertResumableSession");
|
|
7972
|
+
function formatBudget(value) {
|
|
7973
|
+
if (value === void 0) return null;
|
|
7974
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
7975
|
+
throw new ModelCliCommandError("maxBudgetUsd must be a positive number");
|
|
7976
|
+
}
|
|
7977
|
+
return String(Math.round(value * 100) / 100);
|
|
7978
|
+
}
|
|
7979
|
+
__name(formatBudget, "formatBudget");
|
|
7980
|
+
function buildModelCliCommand(input) {
|
|
7501
7981
|
const platform = input.platform ?? process.platform;
|
|
7502
7982
|
const { execution } = input;
|
|
7503
7983
|
const resumeSessionId = assertResumableSession(execution.provider, input.resumeSessionId);
|
|
@@ -7553,7 +8033,21 @@ function buildModelCliCommand(input) {
|
|
|
7553
8033
|
if (roots.length > 0) args.push("--add-dir", ...roots);
|
|
7554
8034
|
} else if (execution.provider === "codex") {
|
|
7555
8035
|
const sandbox = permissionMode === "plan" ? "read-only" : "workspace-write";
|
|
7556
|
-
args.push(
|
|
8036
|
+
args.push(
|
|
8037
|
+
"exec",
|
|
8038
|
+
"--ignore-user-config",
|
|
8039
|
+
"--json",
|
|
8040
|
+
"--sandbox",
|
|
8041
|
+
sandbox,
|
|
8042
|
+
"-C",
|
|
8043
|
+
quoteShellPath(input.workspacePath, platform)
|
|
8044
|
+
);
|
|
8045
|
+
const kept = input.codexUserSettings ?? {};
|
|
8046
|
+
const windowsSandbox = assertCodexSetting("windows.sandbox", kept.windowsSandbox);
|
|
8047
|
+
const reasoningEffort = assertCodexSetting("model_reasoning_effort", kept.reasoningEffort);
|
|
8048
|
+
const ownerModel = assertCodexSetting("model", kept.model);
|
|
8049
|
+
if (windowsSandbox) args.push("-c", `windows.sandbox=${windowsSandbox}`);
|
|
8050
|
+
if (reasoningEffort) args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
|
|
7557
8051
|
if (sandbox === "workspace-write") {
|
|
7558
8052
|
for (const root of grantedRoots(writableMounts)) args.push("--add-dir", root);
|
|
7559
8053
|
}
|
|
@@ -7563,7 +8057,8 @@ function buildModelCliCommand(input) {
|
|
|
7563
8057
|
`mcp_servers.${assertMcpServerName(mcp.serverName)}.url=${assertLoopbackUrl(mcp.serverUrl)}`
|
|
7564
8058
|
);
|
|
7565
8059
|
}
|
|
7566
|
-
|
|
8060
|
+
const codexModel = model ?? ownerModel;
|
|
8061
|
+
if (codexModel) args.push("-m", codexModel);
|
|
7567
8062
|
if (execution.skipPermissions === true) args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
7568
8063
|
args.push("-");
|
|
7569
8064
|
} else {
|
|
@@ -7586,7 +8081,16 @@ function assertMountGrantsInCommand(input) {
|
|
|
7586
8081
|
}
|
|
7587
8082
|
__name(assertMountGrantsInCommand, "assertMountGrantsInCommand");
|
|
7588
8083
|
var STRICT_MCP_CONFIG_IN_COMMAND = / --mcp-config "[^"]+" --strict-mcp-config /;
|
|
8084
|
+
var CODEX_IGNORE_USER_CONFIG_IN_COMMAND = / exec --ignore-user-config /;
|
|
7589
8085
|
function assertMcpIsolationInCommand(input) {
|
|
8086
|
+
if (input.execution.provider === "codex") {
|
|
8087
|
+
if (!CODEX_IGNORE_USER_CONFIG_IN_COMMAND.test(input.command)) {
|
|
8088
|
+
throw new ModelCliCommandError(
|
|
8089
|
+
"The codex command line does not carry exec --ignore-user-config, so the run would load the machine owner's own config.toml: its MCP servers, plugins and notify hook"
|
|
8090
|
+
);
|
|
8091
|
+
}
|
|
8092
|
+
return;
|
|
8093
|
+
}
|
|
7590
8094
|
if (input.execution.provider !== "claude-code") return;
|
|
7591
8095
|
if (!STRICT_MCP_CONFIG_IN_COMMAND.test(input.command)) {
|
|
7592
8096
|
throw new ModelCliCommandError(
|
|
@@ -7914,424 +8418,183 @@ var MODEL_CLI_REQUIRED_FLAGS = {
|
|
|
7914
8418
|
// bridge's. A CLI without them cannot run fleet work at all, so `doctor`
|
|
7915
8419
|
// must say INCOMPATIBLE, not "runs that need them will fail".
|
|
7916
8420
|
"claude-code": ["-p", "--output-format", "--permission-mode", "--mcp-config", "--strict-mcp-config"],
|
|
7917
|
-
codex: ["--json", "--sandbox", "-C"]
|
|
8421
|
+
codex: ["--json", "--sandbox", "-C", "--ignore-user-config"]
|
|
7918
8422
|
};
|
|
7919
8423
|
var MODEL_CLI_CONDITIONAL_FLAGS = {
|
|
7920
|
-
"claude-code": [
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
/** One pinned binary. */
|
|
7942
|
-
async probe(provider, executable) {
|
|
7943
|
-
const stamp = await this.stamp(executable);
|
|
7944
|
-
const key = `${provider}|${executable}|${stamp ? `${stamp.mtimeMs}:${stamp.size}` : "unstamped"}`;
|
|
7945
|
-
const cached = this.cache.get(`${provider}|${executable}`);
|
|
7946
|
-
if (cached && cached.key === key && (cached.permanent || this.now() - cached.probedAt < cached.ttlMs)) {
|
|
7947
|
-
return cached.result;
|
|
7948
|
-
}
|
|
7949
|
-
const pending = this.inFlight.get(key);
|
|
7950
|
-
if (pending) return pending;
|
|
7951
|
-
const run = this.run(provider, executable).then(({ result, answered }) => {
|
|
7952
|
-
this.cache.set(`${provider}|${executable}`, {
|
|
7953
|
-
key,
|
|
7954
|
-
// Only a COMPLETE answer from a stamped file is kept until the
|
|
7955
|
-
// file changes. An incomplete one is retried soon, and an
|
|
7956
|
-
// unstamped one on the slower clock.
|
|
7957
|
-
permanent: answered && stamp !== null,
|
|
7958
|
-
ttlMs: answered ? this.unstampedTtlMs : this.incompleteRetryMs,
|
|
7959
|
-
probedAt: this.now(),
|
|
7960
|
-
result
|
|
7961
|
-
});
|
|
7962
|
-
return result;
|
|
7963
|
-
}).finally(() => {
|
|
7964
|
-
this.inFlight.delete(key);
|
|
7965
|
-
});
|
|
7966
|
-
this.inFlight.set(key, run);
|
|
7967
|
-
return run;
|
|
7968
|
-
}
|
|
7969
|
-
/** Every pinned binary in `paths`, in provider order. Unpinned providers are skipped. */
|
|
7970
|
-
async probeAll(paths) {
|
|
7971
|
-
const out = [];
|
|
7972
|
-
for (const provider of MODEL_CLI_PROVIDERS) {
|
|
7973
|
-
const executable = paths?.[provider];
|
|
7974
|
-
if (typeof executable !== "string" || !executable.trim()) continue;
|
|
7975
|
-
out.push(await this.probe(provider, executable));
|
|
7976
|
-
}
|
|
7977
|
-
return out;
|
|
7978
|
-
}
|
|
7979
|
-
async stamp(executable) {
|
|
7980
|
-
if (!this.options.statFile) return null;
|
|
7981
|
-
try {
|
|
7982
|
-
const stamp = await this.options.statFile(executable);
|
|
7983
|
-
return stamp && Number.isFinite(stamp.mtimeMs) && Number.isFinite(stamp.size) ? stamp : null;
|
|
7984
|
-
} catch {
|
|
7985
|
-
return null;
|
|
7986
|
-
}
|
|
7987
|
-
}
|
|
7988
|
-
async run(provider, executable) {
|
|
7989
|
-
const command = this.commandFor(executable);
|
|
7990
|
-
const versionOutput = await this.ask(command, ["--version"]);
|
|
7991
|
-
const version = versionOutput === null ? null : parseCliVersion(versionOutput);
|
|
7992
|
-
const help = await this.ask(command, [...HELP_ARGS[provider]]);
|
|
7993
|
-
return {
|
|
7994
|
-
result: judgeModelCliHelp(provider, executable, version, help),
|
|
7995
|
-
// "Answered" = both invocations produced output. An UNRECOGNISED
|
|
7996
|
-
// help text is still an answer — deterministic for that file — and
|
|
7997
|
-
// re-asking every minute would learn nothing.
|
|
7998
|
-
answered: versionOutput !== null && help !== null
|
|
7999
|
-
};
|
|
8000
|
-
}
|
|
8001
|
-
/**
|
|
8002
|
-
* The runner on Windows goes through `cmd.exe` (`shell: true`), which
|
|
8003
|
-
* splits an unquoted `C:\Program Files\…` at the space.
|
|
8004
|
-
*/
|
|
8005
|
-
commandFor(executable) {
|
|
8006
|
-
return this.options.platform === "win32" && /\s/.test(executable) && !executable.startsWith('"') ? `"${executable}"` : executable;
|
|
8007
|
-
}
|
|
8008
|
-
/** stdout+stderr of one invocation, or null when it could not be run at all. */
|
|
8009
|
-
async ask(command, args) {
|
|
8010
|
-
try {
|
|
8011
|
-
const result = await this.options.runner.run(command, args);
|
|
8012
|
-
if (result.timedOut) return null;
|
|
8013
|
-
const text = `${result.stdout ?? ""}
|
|
8014
|
-
${result.stderr ?? ""}`;
|
|
8015
|
-
return result.code === 0 || text.trim() ? text : null;
|
|
8016
|
-
} catch {
|
|
8017
|
-
return null;
|
|
8018
|
-
}
|
|
8019
|
-
}
|
|
8020
|
-
};
|
|
8021
|
-
function parseAdvertisedFlags(help) {
|
|
8022
|
-
const flags = /* @__PURE__ */ new Set();
|
|
8023
|
-
const pattern = /(?:^|[\s,[(|])(--?[A-Za-z][A-Za-z0-9-]*)(?=$|[\s,=<[\])|.:])/gm;
|
|
8024
|
-
for (const match of help.matchAll(pattern)) {
|
|
8025
|
-
flags.add(match[1]);
|
|
8026
|
-
}
|
|
8027
|
-
return flags;
|
|
8028
|
-
}
|
|
8029
|
-
__name(parseAdvertisedFlags, "parseAdvertisedFlags");
|
|
8030
|
-
function judgeModelCliHelp(provider, executable, version, help) {
|
|
8031
|
-
const required = MODEL_CLI_REQUIRED_FLAGS[provider];
|
|
8032
|
-
const base = {
|
|
8033
|
-
provider,
|
|
8034
|
-
executable,
|
|
8035
|
-
version,
|
|
8036
|
-
supportedFlags: null,
|
|
8037
|
-
missingRequiredFlags: [],
|
|
8038
|
-
missingConditionalFlags: [],
|
|
8039
|
-
unsupportedOptionalFlags: [],
|
|
8040
|
-
note: null
|
|
8041
|
-
};
|
|
8042
|
-
if (help === null) {
|
|
8043
|
-
return { ...base, note: "the binary did not answer --help; nothing is dropped" };
|
|
8044
|
-
}
|
|
8045
|
-
const advertised = parseAdvertisedFlags(help);
|
|
8046
|
-
if (!required.some((flag) => advertised.has(flag))) {
|
|
8047
|
-
return {
|
|
8048
|
-
...base,
|
|
8049
|
-
note: `the help text lists none of the flags this node always passes (${required.join(", ")}); its format is not recognised, so nothing is dropped`
|
|
8050
|
-
};
|
|
8051
|
-
}
|
|
8052
|
-
const missing = /* @__PURE__ */ __name((list) => list.filter((flag) => !advertised.has(flag)), "missing");
|
|
8053
|
-
return {
|
|
8054
|
-
...base,
|
|
8055
|
-
supportedFlags: advertised,
|
|
8056
|
-
missingRequiredFlags: missing(required),
|
|
8057
|
-
missingConditionalFlags: missing(MODEL_CLI_CONDITIONAL_FLAGS[provider]),
|
|
8058
|
-
unsupportedOptionalFlags: missing(MODEL_CLI_OPTIONAL_FLAGS[provider])
|
|
8059
|
-
};
|
|
8060
|
-
}
|
|
8061
|
-
__name(judgeModelCliHelp, "judgeModelCliHelp");
|
|
8062
|
-
function isModelCliCompatible(compat) {
|
|
8063
|
-
return compat.missingRequiredFlags.length === 0;
|
|
8064
|
-
}
|
|
8065
|
-
__name(isModelCliCompatible, "isModelCliCompatible");
|
|
8066
|
-
function describeCliVersions(results) {
|
|
8067
|
-
return results.map((result) => `${result.provider} ${result.version ?? "unknown"}`.slice(0, FLEET_MAX_CLI_VERSION_LENGTH)).slice(0, FLEET_MAX_CLI_VERSIONS);
|
|
8068
|
-
}
|
|
8069
|
-
__name(describeCliVersions, "describeCliVersions");
|
|
8070
|
-
var LEGACY_COMMAND_NAME = {
|
|
8071
|
-
"claude-code": "claude",
|
|
8072
|
-
codex: "codex"
|
|
8073
|
-
};
|
|
8074
|
-
function primaryPinnedCliVersion(results) {
|
|
8075
|
-
const first = results.find((result) => result.version !== null);
|
|
8076
|
-
return first ? `${LEGACY_COMMAND_NAME[first.provider]} ${first.version}`.slice(0, FLEET_MAX_CLI_VERSION_LENGTH) : null;
|
|
8077
|
-
}
|
|
8078
|
-
__name(primaryPinnedCliVersion, "primaryPinnedCliVersion");
|
|
8079
|
-
|
|
8080
|
-
// src/core/worker-loop.ts
|
|
8081
|
-
var import_node_perf_hooks = require("node:perf_hooks");
|
|
8082
|
-
|
|
8083
|
-
// src/core/heartbeat.ts
|
|
8084
|
-
var systemScheduler = {
|
|
8085
|
-
setTimeout: /* @__PURE__ */ __name((callback, ms) => setTimeout(callback, ms), "setTimeout"),
|
|
8086
|
-
clearTimeout: /* @__PURE__ */ __name((handle) => clearTimeout(handle), "clearTimeout")
|
|
8087
|
-
};
|
|
8088
|
-
var OPTIONAL_DESCRIPTION_FIELD_TIERS = [
|
|
8089
|
-
// NEWEST first. Node lifecycle + remote node limits (self-build slices
|
|
8090
|
-
// AR, AS). Tiered (review): `cliVersions` goes out on EVERY beat of a
|
|
8091
|
-
// node with a pinned CLI, so an upgraded node talking to a platform that
|
|
8092
|
-
// predates it would otherwise drop the worker state and housekeeping
|
|
8093
|
-
// along with it — losing health signals that platform DOES understand,
|
|
8094
|
-
// for the life of the process. Stripping the newest tier first keeps them.
|
|
8095
|
-
["cliVersions", "maxConcurrentJobs", "maxCpuPercent", "maxMemoryMb"],
|
|
8096
|
-
// Fleet health signals (EW-776) and node housekeeping (EW-803).
|
|
8097
|
-
[
|
|
8098
|
-
"workerState",
|
|
8099
|
-
"workerStateReason",
|
|
8100
|
-
"minFreeDiskBytes",
|
|
8101
|
-
"workspaceCount",
|
|
8102
|
-
"workspaceBytes",
|
|
8103
|
-
"lastReclaimAt",
|
|
8104
|
-
"lastReclaimFreedBytes"
|
|
8105
|
-
]
|
|
8106
|
-
];
|
|
8107
|
-
function computeBackoffDelay(intervalMs, failures, maxMs) {
|
|
8108
|
-
if (failures <= 0) {
|
|
8109
|
-
return intervalMs;
|
|
8110
|
-
}
|
|
8111
|
-
const exponent = Math.min(failures, 30);
|
|
8112
|
-
const delay2 = intervalMs * 2 ** exponent;
|
|
8113
|
-
return Math.min(delay2, maxMs);
|
|
8114
|
-
}
|
|
8115
|
-
__name(computeBackoffDelay, "computeBackoffDelay");
|
|
8116
|
-
var HeartbeatLoop = class {
|
|
8117
|
-
static {
|
|
8118
|
-
__name(this, "HeartbeatLoop");
|
|
8119
|
-
}
|
|
8120
|
-
options;
|
|
8121
|
-
scheduler;
|
|
8122
|
-
now;
|
|
8123
|
-
listeners = /* @__PURE__ */ new Set();
|
|
8124
|
-
timer = null;
|
|
8125
|
-
running = false;
|
|
8126
|
-
inFlight = null;
|
|
8127
|
-
/**
|
|
8128
|
-
* How many optional-field TIERS (newest first) this platform has proven
|
|
8129
|
-
* it predates; from then on the beat leaves those tiers out. 0 = it
|
|
8130
|
-
* takes everything. Process-scoped on purpose — a platform upgrade is a
|
|
8131
|
-
* restart-shaped event for the node, and re-probing on every beat would
|
|
8132
|
-
* mean one wasted round trip per beat, forever, against an API that
|
|
8133
|
-
* will never accept them.
|
|
8134
|
-
*/
|
|
8135
|
-
strippedTiers = 0;
|
|
8136
|
-
state = {
|
|
8137
|
-
state: "idle",
|
|
8138
|
-
lastHeartbeatAt: null,
|
|
8139
|
-
consecutiveFailures: 0,
|
|
8140
|
-
nextAttemptInMs: null,
|
|
8141
|
-
lastError: null,
|
|
8142
|
-
lastErrorKind: null,
|
|
8143
|
-
node: null
|
|
8144
|
-
};
|
|
8145
|
-
constructor(options) {
|
|
8146
|
-
this.options = {
|
|
8147
|
-
...options,
|
|
8148
|
-
intervalMs: options.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
8149
|
-
maxBackoffMs: options.maxBackoffMs ?? MAX_HEARTBEAT_BACKOFF_MS
|
|
8150
|
-
};
|
|
8151
|
-
this.scheduler = options.scheduler ?? systemScheduler;
|
|
8152
|
-
this.now = options.now ?? (() => Date.now());
|
|
8153
|
-
this.options.logger?.protect(options.secret);
|
|
8154
|
-
}
|
|
8155
|
-
getState() {
|
|
8156
|
-
return { ...this.state };
|
|
8157
|
-
}
|
|
8158
|
-
onChange(listener) {
|
|
8159
|
-
this.listeners.add(listener);
|
|
8160
|
-
return () => {
|
|
8161
|
-
this.listeners.delete(listener);
|
|
8162
|
-
};
|
|
8163
|
-
}
|
|
8164
|
-
/** Start beating. Resolves once the FIRST beat has settled. */
|
|
8165
|
-
start() {
|
|
8166
|
-
if (this.running) {
|
|
8167
|
-
return this.inFlight ?? Promise.resolve();
|
|
8168
|
-
}
|
|
8169
|
-
this.running = true;
|
|
8170
|
-
return this.tick();
|
|
8171
|
-
}
|
|
8172
|
-
/** Stop beating and cancel any pending timer. */
|
|
8173
|
-
stop() {
|
|
8174
|
-
this.running = false;
|
|
8175
|
-
this.cancelTimer();
|
|
8176
|
-
this.patch({ state: "stopped", nextAttemptInMs: null });
|
|
8177
|
-
}
|
|
8178
|
-
/** Await the in-flight beat, if any (test/shutdown helper). */
|
|
8179
|
-
async settled() {
|
|
8180
|
-
await this.inFlight;
|
|
8181
|
-
}
|
|
8182
|
-
/** Run one beat now. Exposed so the UI can offer "retry" and tests can step. */
|
|
8183
|
-
tick() {
|
|
8184
|
-
const run = this.runOnce().finally(() => {
|
|
8185
|
-
if (this.inFlight === run) {
|
|
8186
|
-
this.inFlight = null;
|
|
8187
|
-
}
|
|
8188
|
-
});
|
|
8189
|
-
this.inFlight = run;
|
|
8190
|
-
return run;
|
|
8191
|
-
}
|
|
8192
|
-
async runOnce() {
|
|
8193
|
-
if (!this.running) {
|
|
8194
|
-
return;
|
|
8195
|
-
}
|
|
8196
|
-
this.cancelTimer();
|
|
8197
|
-
if (this.state.state !== "connected") {
|
|
8198
|
-
this.patch({ state: "connecting" });
|
|
8199
|
-
}
|
|
8200
|
-
try {
|
|
8201
|
-
const description = await this.options.describe();
|
|
8202
|
-
this.onSuccess(await this.beat(description));
|
|
8203
|
-
} catch (error) {
|
|
8204
|
-
this.onFailure(error);
|
|
8205
|
-
}
|
|
8206
|
-
this.scheduleNext();
|
|
8207
|
-
}
|
|
8208
|
-
/**
|
|
8209
|
-
* Send one beat, tolerating a platform that predates the optional
|
|
8210
|
-
* self-description fields (the worker state, EW-776; the housekeeping
|
|
8211
|
-
* report, EW-803).
|
|
8212
|
-
*
|
|
8213
|
-
* The API validates heartbeats with `whitelist + forbidNonWhitelisted`,
|
|
8214
|
-
* so a field an older build does not know is not ignored — it 400s the
|
|
8215
|
-
* whole request. A 400 is a FAILED beat, a failed beat backs off, and
|
|
8216
|
-
* enough of them sweep the node to `offline`. Which would mean shipping
|
|
8217
|
-
* a health-reporting feature whose failure mode is "every node in a
|
|
8218
|
-
* mixed-version fleet goes dark": the exact class of outage this slice
|
|
8219
|
-
* was written to end.
|
|
8220
|
-
*
|
|
8221
|
-
* So a 400 on a beat that CARRIED those fields is retried once,
|
|
8222
|
-
* immediately, without them. If that succeeds the platform is simply
|
|
8223
|
-
* older than this daemon; we latch, say so once, and keep reporting
|
|
8224
|
-
* liveness. The retry costs one request, once per process.
|
|
8225
|
-
*
|
|
8226
|
-
* Inferring "the field was rejected" from a bare 400 is deliberate and
|
|
8227
|
-
* has a cost worth stating: `FleetClient` never surfaces server bodies
|
|
8228
|
-
* (fixed client-authored messages only), so a 400 caused by something
|
|
8229
|
-
* else on such a beat also latches, and this node reports liveness only
|
|
8230
|
-
* until it restarts. That is the safe direction — the node stays
|
|
8231
|
-
* observable either way, and only the extra signal is lost.
|
|
8232
|
-
*/
|
|
8233
|
-
async beat(description) {
|
|
8234
|
-
const credential = { nodeId: this.options.nodeId, secret: this.options.secret };
|
|
8235
|
-
let sent = stripTiers(description, this.strippedTiers);
|
|
8236
|
-
try {
|
|
8237
|
-
return await this.options.client.heartbeat({ ...credential, ...sent });
|
|
8238
|
-
} catch (error) {
|
|
8239
|
-
if (!isFieldRejection(error)) throw error;
|
|
8240
|
-
let lastError = error;
|
|
8241
|
-
for (let tiers = this.strippedTiers + 1; tiers <= OPTIONAL_DESCRIPTION_FIELD_TIERS.length; tiers += 1) {
|
|
8242
|
-
const candidate = stripTiers(description, tiers);
|
|
8243
|
-
if (Object.keys(candidate).length === Object.keys(sent).length) continue;
|
|
8244
|
-
sent = candidate;
|
|
8245
|
-
try {
|
|
8246
|
-
const result = await this.options.client.heartbeat({ ...credential, ...candidate });
|
|
8247
|
-
this.strippedTiers = tiers;
|
|
8248
|
-
this.options.logger?.warn(
|
|
8249
|
-
tiers >= OPTIONAL_DESCRIPTION_FIELD_TIERS.length ? "Platform rejected the optional self-description fields (worker state, housekeeping); it predates them. Reporting liveness only until this node restarts." : "Platform rejected the newest optional self-description fields (pinned CLI versions, enforced limits); it predates them. Still reporting worker state and housekeeping; the newer fields stay off until this node restarts."
|
|
8250
|
-
);
|
|
8251
|
-
return result;
|
|
8252
|
-
} catch (retryError) {
|
|
8253
|
-
if (!isFieldRejection(retryError)) throw retryError;
|
|
8254
|
-
lastError = retryError;
|
|
8255
|
-
}
|
|
8256
|
-
}
|
|
8257
|
-
throw lastError;
|
|
8258
|
-
}
|
|
8424
|
+
"claude-code": [
|
|
8425
|
+
"--model",
|
|
8426
|
+
"--add-dir",
|
|
8427
|
+
"--allowedTools",
|
|
8428
|
+
"--dangerously-skip-permissions",
|
|
8429
|
+
"--verbose",
|
|
8430
|
+
"--resume",
|
|
8431
|
+
"--fork-session"
|
|
8432
|
+
],
|
|
8433
|
+
codex: ["--add-dir", "-c", "-m", "--dangerously-bypass-approvals-and-sandbox"]
|
|
8434
|
+
};
|
|
8435
|
+
var MODEL_CLI_COMPAT_UNSTAMPED_TTL_MS = 10 * 6e4;
|
|
8436
|
+
var MODEL_CLI_COMPAT_STAMPED_MAX_AGE_MS = 60 * 6e4;
|
|
8437
|
+
var MODEL_CLI_COMPAT_INCOMPLETE_RETRY_MS = 6e4;
|
|
8438
|
+
var ModelCliCompatibilityProbe = class {
|
|
8439
|
+
constructor(options) {
|
|
8440
|
+
this.options = options;
|
|
8441
|
+
this.now = options.now ?? (() => Date.now());
|
|
8442
|
+
this.unstampedTtlMs = options.unstampedTtlMs ?? MODEL_CLI_COMPAT_UNSTAMPED_TTL_MS;
|
|
8443
|
+
this.incompleteRetryMs = options.incompleteRetryMs ?? MODEL_CLI_COMPAT_INCOMPLETE_RETRY_MS;
|
|
8444
|
+
this.stampedMaxAgeMs = options.stampedMaxAgeMs ?? MODEL_CLI_COMPAT_STAMPED_MAX_AGE_MS;
|
|
8259
8445
|
}
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
lastHeartbeatAt: this.now(),
|
|
8264
|
-
consecutiveFailures: 0,
|
|
8265
|
-
lastError: null,
|
|
8266
|
-
lastErrorKind: null,
|
|
8267
|
-
node: result.node
|
|
8268
|
-
});
|
|
8269
|
-
if (this.options.onAccepted) {
|
|
8270
|
-
try {
|
|
8271
|
-
this.options.onAccepted(result);
|
|
8272
|
-
} catch {
|
|
8273
|
-
}
|
|
8274
|
-
}
|
|
8446
|
+
options;
|
|
8447
|
+
static {
|
|
8448
|
+
__name(this, "ModelCliCompatibilityProbe");
|
|
8275
8449
|
}
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8450
|
+
cache = /* @__PURE__ */ new Map();
|
|
8451
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
8452
|
+
now;
|
|
8453
|
+
unstampedTtlMs;
|
|
8454
|
+
incompleteRetryMs;
|
|
8455
|
+
stampedMaxAgeMs;
|
|
8456
|
+
/** One pinned binary. */
|
|
8457
|
+
async probe(provider, executable) {
|
|
8458
|
+
const stamp = await this.stamp(executable);
|
|
8459
|
+
const key = `${provider}|${executable}|${stamp ? `${stamp.mtimeMs}:${stamp.size}` : "unstamped"}`;
|
|
8460
|
+
const cached = this.cache.get(`${provider}|${executable}`);
|
|
8461
|
+
if (cached && cached.key === key && this.now() - cached.probedAt < cached.ttlMs) {
|
|
8462
|
+
return cached.result;
|
|
8463
|
+
}
|
|
8464
|
+
const pending = this.inFlight.get(key);
|
|
8465
|
+
if (pending) return pending;
|
|
8466
|
+
const run = this.run(provider, executable).then(({ result, answered }) => {
|
|
8467
|
+
this.cache.set(`${provider}|${executable}`, {
|
|
8468
|
+
key,
|
|
8469
|
+
// A COMPLETE answer from a stamped file is kept until the file
|
|
8470
|
+
// changes or, at the latest, for `stampedMaxAgeMs` (an upgrade
|
|
8471
|
+
// behind a stable wrapper does not change the stamp). An
|
|
8472
|
+
// incomplete one is retried soon, and an unstamped one on the
|
|
8473
|
+
// ten-minute clock.
|
|
8474
|
+
ttlMs: !answered ? this.incompleteRetryMs : stamp !== null ? this.stampedMaxAgeMs : this.unstampedTtlMs,
|
|
8475
|
+
probedAt: this.now(),
|
|
8476
|
+
result
|
|
8477
|
+
});
|
|
8478
|
+
return result;
|
|
8479
|
+
}).finally(() => {
|
|
8480
|
+
this.inFlight.delete(key);
|
|
8289
8481
|
});
|
|
8290
|
-
this.
|
|
8482
|
+
this.inFlight.set(key, run);
|
|
8483
|
+
return run;
|
|
8291
8484
|
}
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8485
|
+
/** Every pinned binary in `paths`, in provider order. Unpinned providers are skipped. */
|
|
8486
|
+
async probeAll(paths) {
|
|
8487
|
+
const out = [];
|
|
8488
|
+
for (const provider of MODEL_CLI_PROVIDERS) {
|
|
8489
|
+
const executable = paths?.[provider];
|
|
8490
|
+
if (typeof executable !== "string" || !executable.trim()) continue;
|
|
8491
|
+
out.push(await this.probe(provider, executable));
|
|
8295
8492
|
}
|
|
8296
|
-
|
|
8297
|
-
this.options.intervalMs,
|
|
8298
|
-
this.state.consecutiveFailures,
|
|
8299
|
-
this.options.maxBackoffMs
|
|
8300
|
-
);
|
|
8301
|
-
this.patch({ nextAttemptInMs: delay2 });
|
|
8302
|
-
this.timer = this.scheduler.setTimeout(() => {
|
|
8303
|
-
this.timer = null;
|
|
8304
|
-
void this.tick();
|
|
8305
|
-
}, delay2);
|
|
8493
|
+
return out;
|
|
8306
8494
|
}
|
|
8307
|
-
|
|
8308
|
-
if (this.
|
|
8309
|
-
|
|
8310
|
-
|
|
8495
|
+
async stamp(executable) {
|
|
8496
|
+
if (!this.options.statFile) return null;
|
|
8497
|
+
try {
|
|
8498
|
+
const stamp = await this.options.statFile(executable);
|
|
8499
|
+
return stamp && Number.isFinite(stamp.mtimeMs) && Number.isFinite(stamp.size) ? stamp : null;
|
|
8500
|
+
} catch {
|
|
8501
|
+
return null;
|
|
8311
8502
|
}
|
|
8312
8503
|
}
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
const
|
|
8316
|
-
|
|
8317
|
-
|
|
8504
|
+
async run(provider, executable) {
|
|
8505
|
+
const command = this.commandFor(executable);
|
|
8506
|
+
const versionOutput = await this.ask(command, ["--version"]);
|
|
8507
|
+
const version = versionOutput === null ? null : parseCliVersion(versionOutput);
|
|
8508
|
+
const help = await this.ask(command, [...HELP_ARGS[provider]]);
|
|
8509
|
+
return {
|
|
8510
|
+
result: judgeModelCliHelp(provider, executable, version, help),
|
|
8511
|
+
// "Answered" = both invocations produced output. An UNRECOGNISED
|
|
8512
|
+
// help text is still an answer — deterministic for that file — and
|
|
8513
|
+
// re-asking every minute would learn nothing.
|
|
8514
|
+
answered: versionOutput !== null && help !== null
|
|
8515
|
+
};
|
|
8516
|
+
}
|
|
8517
|
+
/**
|
|
8518
|
+
* The runner on Windows goes through `cmd.exe` (`shell: true`), which
|
|
8519
|
+
* splits an unquoted `C:\Program Files\…` at the space.
|
|
8520
|
+
*/
|
|
8521
|
+
commandFor(executable) {
|
|
8522
|
+
return this.options.platform === "win32" && /\s/.test(executable) && !executable.startsWith('"') ? `"${executable}"` : executable;
|
|
8523
|
+
}
|
|
8524
|
+
/** stdout+stderr of one invocation, or null when it could not be run at all. */
|
|
8525
|
+
async ask(command, args) {
|
|
8526
|
+
try {
|
|
8527
|
+
const result = await this.options.runner.run(command, args);
|
|
8528
|
+
if (result.timedOut) return null;
|
|
8529
|
+
const text = `${result.stdout ?? ""}
|
|
8530
|
+
${result.stderr ?? ""}`;
|
|
8531
|
+
return result.code === 0 || text.trim() ? text : null;
|
|
8532
|
+
} catch {
|
|
8533
|
+
return null;
|
|
8318
8534
|
}
|
|
8319
8535
|
}
|
|
8320
8536
|
};
|
|
8321
|
-
function
|
|
8322
|
-
const
|
|
8323
|
-
const
|
|
8324
|
-
for (const
|
|
8325
|
-
|
|
8326
|
-
out[key] = value;
|
|
8537
|
+
function parseAdvertisedFlags(help) {
|
|
8538
|
+
const flags = /* @__PURE__ */ new Set();
|
|
8539
|
+
const pattern = /(?:^|[\s,[(|])(--?[A-Za-z][A-Za-z0-9-]*)(?=$|[\s,=<[\])|.:])/gm;
|
|
8540
|
+
for (const match of help.matchAll(pattern)) {
|
|
8541
|
+
flags.add(match[1]);
|
|
8327
8542
|
}
|
|
8328
|
-
return
|
|
8543
|
+
return flags;
|
|
8329
8544
|
}
|
|
8330
|
-
__name(
|
|
8331
|
-
function
|
|
8332
|
-
|
|
8545
|
+
__name(parseAdvertisedFlags, "parseAdvertisedFlags");
|
|
8546
|
+
function judgeModelCliHelp(provider, executable, version, help) {
|
|
8547
|
+
const required = MODEL_CLI_REQUIRED_FLAGS[provider];
|
|
8548
|
+
const base = {
|
|
8549
|
+
provider,
|
|
8550
|
+
executable,
|
|
8551
|
+
version,
|
|
8552
|
+
supportedFlags: null,
|
|
8553
|
+
missingRequiredFlags: [],
|
|
8554
|
+
missingConditionalFlags: [],
|
|
8555
|
+
unsupportedOptionalFlags: [],
|
|
8556
|
+
note: null
|
|
8557
|
+
};
|
|
8558
|
+
if (help === null) {
|
|
8559
|
+
return { ...base, note: "the binary did not answer --help; nothing is dropped" };
|
|
8560
|
+
}
|
|
8561
|
+
const advertised = parseAdvertisedFlags(help);
|
|
8562
|
+
if (!required.some((flag) => advertised.has(flag))) {
|
|
8563
|
+
return {
|
|
8564
|
+
...base,
|
|
8565
|
+
note: `the help text lists none of the flags this node always passes (${required.join(", ")}); its format is not recognised, so nothing is dropped`
|
|
8566
|
+
};
|
|
8567
|
+
}
|
|
8568
|
+
const missing = /* @__PURE__ */ __name((list) => list.filter((flag) => !advertised.has(flag)), "missing");
|
|
8569
|
+
return {
|
|
8570
|
+
...base,
|
|
8571
|
+
supportedFlags: advertised,
|
|
8572
|
+
missingRequiredFlags: missing(required),
|
|
8573
|
+
missingConditionalFlags: missing(MODEL_CLI_CONDITIONAL_FLAGS[provider]),
|
|
8574
|
+
unsupportedOptionalFlags: missing(MODEL_CLI_OPTIONAL_FLAGS[provider])
|
|
8575
|
+
};
|
|
8333
8576
|
}
|
|
8334
|
-
__name(
|
|
8577
|
+
__name(judgeModelCliHelp, "judgeModelCliHelp");
|
|
8578
|
+
function isModelCliCompatible(compat) {
|
|
8579
|
+
return compat.missingRequiredFlags.length === 0;
|
|
8580
|
+
}
|
|
8581
|
+
__name(isModelCliCompatible, "isModelCliCompatible");
|
|
8582
|
+
function describeCliVersions(results) {
|
|
8583
|
+
return results.map((result) => `${result.provider} ${result.version ?? "unknown"}`.slice(0, FLEET_MAX_CLI_VERSION_LENGTH)).slice(0, FLEET_MAX_CLI_VERSIONS);
|
|
8584
|
+
}
|
|
8585
|
+
__name(describeCliVersions, "describeCliVersions");
|
|
8586
|
+
var LEGACY_COMMAND_NAME = {
|
|
8587
|
+
"claude-code": "claude",
|
|
8588
|
+
codex: "codex"
|
|
8589
|
+
};
|
|
8590
|
+
function primaryPinnedCliVersion(results) {
|
|
8591
|
+
const first = results.find((result) => result.version !== null);
|
|
8592
|
+
return first ? `${LEGACY_COMMAND_NAME[first.provider]} ${first.version}`.slice(0, FLEET_MAX_CLI_VERSION_LENGTH) : null;
|
|
8593
|
+
}
|
|
8594
|
+
__name(primaryPinnedCliVersion, "primaryPinnedCliVersion");
|
|
8595
|
+
|
|
8596
|
+
// src/core/worker-loop.ts
|
|
8597
|
+
var import_node_perf_hooks = require("node:perf_hooks");
|
|
8335
8598
|
|
|
8336
8599
|
// src/core/resource-limits.ts
|
|
8337
8600
|
var ADMIT = { admit: true, reason: null };
|
|
@@ -12945,6 +13208,11 @@ async function runModelStep(jobId, execution, workspacePath, mounts, bridgeSpec,
|
|
|
12945
13208
|
emptyMcpConfigPath = mcpConfigPath;
|
|
12946
13209
|
}
|
|
12947
13210
|
const droppedFlags = await optionalFlagsToDrop(jobId, execution, executable, io);
|
|
13211
|
+
const codexUserSettings = execution.provider === "codex" ? await readOwnerCodexSettings(
|
|
13212
|
+
jobId,
|
|
13213
|
+
io,
|
|
13214
|
+
effectiveCodexHome(io, modelEnvOverlay, execution.envPassthrough, execution.envGrants)
|
|
13215
|
+
) : void 0;
|
|
12948
13216
|
const invoke = /* @__PURE__ */ __name(async (instructions, resumeSessionId) => {
|
|
12949
13217
|
await scratchFs.writeFile(scratch.instructionsPath, instructions);
|
|
12950
13218
|
let command;
|
|
@@ -12957,6 +13225,7 @@ async function runModelStep(jobId, execution, workspacePath, mounts, bridgeSpec,
|
|
|
12957
13225
|
...mounts && mounts.length > 0 ? { mounts } : {},
|
|
12958
13226
|
...bridge.cli ? { mcp: bridge.cli } : {},
|
|
12959
13227
|
...emptyMcpConfigPath ? { emptyMcpConfigPath } : {},
|
|
13228
|
+
...codexUserSettings ? { codexUserSettings } : {},
|
|
12960
13229
|
...resumeSessionId ? { resumeSessionId } : {},
|
|
12961
13230
|
...io.platform ? { platform: io.platform } : {},
|
|
12962
13231
|
...streamClaude ? { stream: true } : {},
|
|
@@ -13175,6 +13444,32 @@ function resolveRealHomeDir(io) {
|
|
|
13175
13444
|
return (0, import_os2.homedir)();
|
|
13176
13445
|
}
|
|
13177
13446
|
__name(resolveRealHomeDir, "resolveRealHomeDir");
|
|
13447
|
+
async function readOwnerCodexSettings(jobId, io, codexHome) {
|
|
13448
|
+
const read = io.sessionConfigFs?.readFile?.bind(io.sessionConfigFs);
|
|
13449
|
+
if (!read) return {};
|
|
13450
|
+
try {
|
|
13451
|
+
return readCodexUserSettings(await read((0, import_path4.join)(codexHome, "config.toml")));
|
|
13452
|
+
} catch (error) {
|
|
13453
|
+
io.logger?.warn(
|
|
13454
|
+
`[fleet-node] job ${jobId}: could not read the Codex config to keep its sandbox and model defaults; the run uses Codex's own defaults (still under --ignore-user-config): ${describeContainmentError(error)}`
|
|
13455
|
+
);
|
|
13456
|
+
return {};
|
|
13457
|
+
}
|
|
13458
|
+
}
|
|
13459
|
+
__name(readOwnerCodexSettings, "readOwnerCodexSettings");
|
|
13460
|
+
function effectiveCodexHome(io, envOverlay, envPassthrough, envGrants) {
|
|
13461
|
+
const isCodexHome = /* @__PURE__ */ __name((name) => name.toUpperCase() === "CODEX_HOME", "isCodexHome");
|
|
13462
|
+
const childEnv = buildNodeCheckEnv(
|
|
13463
|
+
envPassthrough?.filter(isCodexHome) ?? null,
|
|
13464
|
+
io.parentEnv,
|
|
13465
|
+
envGrants?.filter(isCodexHome) ?? null,
|
|
13466
|
+
envOverlay
|
|
13467
|
+
);
|
|
13468
|
+
const key = Object.keys(childEnv).find(isCodexHome);
|
|
13469
|
+
const value = key === void 0 ? void 0 : childEnv[key];
|
|
13470
|
+
return typeof value === "string" && value.trim() ? value.trim() : (0, import_path4.join)(resolveRealHomeDir(io), ".codex");
|
|
13471
|
+
}
|
|
13472
|
+
__name(effectiveCodexHome, "effectiveCodexHome");
|
|
13178
13473
|
function describeContainmentError(error) {
|
|
13179
13474
|
return error instanceof Error ? error.message : String(error);
|
|
13180
13475
|
}
|
|
@@ -14986,10 +15281,14 @@ var USER_SELECTABLE_WORK_KINDS2 = [
|
|
|
14986
15281
|
"repo",
|
|
14987
15282
|
"app"
|
|
14988
15283
|
];
|
|
14989
|
-
var WORK_KINDS2 = [...USER_SELECTABLE_WORK_KINDS2, "company", "campaign", "default"];
|
|
15284
|
+
var WORK_KINDS2 = [...USER_SELECTABLE_WORK_KINDS2, "company", "campaign", "project", "default"];
|
|
14990
15285
|
new Set(WORK_KINDS2);
|
|
14991
15286
|
var GOAL_KINDS2 = ["metric", "delivery"];
|
|
14992
15287
|
new Set(GOAL_KINDS2);
|
|
15288
|
+
var RECORD_VISIBILITIES2 = ["private", "team", "organization"];
|
|
15289
|
+
new Set(RECORD_VISIBILITIES2);
|
|
15290
|
+
var RECORD_LIST_VIEWS2 = ["mine", "shared", "organization"];
|
|
15291
|
+
new Set(RECORD_LIST_VIEWS2);
|
|
14993
15292
|
var VERSION2 = "1.0.0";
|
|
14994
15293
|
var GTM_SKILLS2 = [
|
|
14995
15294
|
// ── research ────────────────────────────────────────────────────────
|
|
@@ -15695,6 +15994,113 @@ var ACTION_CATEGORIES2 = Object.freeze([
|
|
|
15695
15994
|
Object.freeze(
|
|
15696
15995
|
ACTION_CATEGORIES2.filter((c) => c !== "read.internal")
|
|
15697
15996
|
);
|
|
15997
|
+
var DOMAIN_EVENT_CATALOG2 = [
|
|
15998
|
+
{ type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
|
|
15999
|
+
{
|
|
16000
|
+
type: "organization.member.added",
|
|
16001
|
+
domain: "organization",
|
|
16002
|
+
aggregateType: "organization",
|
|
16003
|
+
phase: "P1",
|
|
16004
|
+
child: "member"
|
|
16005
|
+
},
|
|
16006
|
+
{
|
|
16007
|
+
type: "organization.member.updated",
|
|
16008
|
+
domain: "organization",
|
|
16009
|
+
aggregateType: "organization",
|
|
16010
|
+
phase: "P1",
|
|
16011
|
+
child: "member"
|
|
16012
|
+
},
|
|
16013
|
+
{
|
|
16014
|
+
type: "organization.member.removed",
|
|
16015
|
+
domain: "organization",
|
|
16016
|
+
aggregateType: "organization",
|
|
16017
|
+
phase: "P1",
|
|
16018
|
+
child: "member"
|
|
16019
|
+
},
|
|
16020
|
+
{ type: "agent.created", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
16021
|
+
{ type: "agent.updated", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
16022
|
+
{ type: "agent.status_changed", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
16023
|
+
{ type: "agent.archived", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
16024
|
+
{ type: "agent.deleted", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
16025
|
+
{ type: "team.created", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
16026
|
+
{ type: "team.updated", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
16027
|
+
{ type: "team.deleted", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
16028
|
+
{ type: "team.member.added", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
16029
|
+
{ type: "team.member.updated", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
16030
|
+
{ type: "team.member.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
16031
|
+
{ type: "team.resource.added", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
16032
|
+
{ type: "team.resource.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
16033
|
+
{ type: "work.created", domain: "works", aggregateType: "work", phase: "P1" },
|
|
16034
|
+
{ type: "work.updated", domain: "works", aggregateType: "work", phase: "P1" },
|
|
16035
|
+
{ type: "work.archived", domain: "works", aggregateType: "work", phase: "P1" },
|
|
16036
|
+
{ type: "work.deleted", domain: "works", aggregateType: "work", phase: "P1" },
|
|
16037
|
+
{ type: "work.member.added", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
16038
|
+
{ type: "work.member.updated", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
16039
|
+
{ type: "work.member.removed", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
16040
|
+
{ type: "mission.created", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16041
|
+
{ type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16042
|
+
{ type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16043
|
+
{ type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16044
|
+
{ type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16045
|
+
{ type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16046
|
+
{ type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16047
|
+
{ type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
16048
|
+
{ type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
16049
|
+
{ type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
16050
|
+
{ type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
16051
|
+
{ type: "goal.archived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
16052
|
+
{ type: "goal.unarchived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
16053
|
+
{ type: "goal.deleted", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
16054
|
+
{ type: "goal.progress_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
16055
|
+
{ type: "idea.created", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
16056
|
+
{ type: "idea.updated", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
16057
|
+
{ type: "idea.status_changed", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
16058
|
+
{ type: "idea.deleted", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
16059
|
+
{ type: "task.created", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
16060
|
+
{ type: "task.updated", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
16061
|
+
{ type: "task.transitioned", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
16062
|
+
{ type: "task.deleted", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
16063
|
+
{ type: "task.assignee.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
16064
|
+
{ type: "task.assignee.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
16065
|
+
{ type: "task.watcher.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
16066
|
+
{ type: "task.watcher.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
16067
|
+
{ type: "task.block.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
16068
|
+
{ type: "task.block.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
16069
|
+
{ type: "task.relation.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
16070
|
+
{ type: "task.relation.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
16071
|
+
{ type: "task.comment.created", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
16072
|
+
{ type: "task.comment.updated", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
16073
|
+
{ type: "task.comment.deleted", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
16074
|
+
{ type: "task.attachment.added", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
16075
|
+
{ type: "task.attachment.removed", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
16076
|
+
{ type: "task.runtime_updated", domain: "tasks", aggregateType: "task", phase: "P1", optIn: true },
|
|
16077
|
+
{ type: "inbox.item.created", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
16078
|
+
{ type: "inbox.item.answered", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
16079
|
+
{ type: "inbox.item.state_changed", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
16080
|
+
{ type: "inbox.item.deleted", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
16081
|
+
{ type: "kb.document.created", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
16082
|
+
{ type: "kb.document.updated", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
16083
|
+
{ type: "kb.document.moved", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
16084
|
+
{ type: "kb.document.archived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
16085
|
+
{ type: "kb.document.unarchived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
16086
|
+
{ type: "kb.document.deleted", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
16087
|
+
{ type: "kb.document.review_changed", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
16088
|
+
{ type: "kb.folder.created", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
16089
|
+
{ type: "kb.folder.updated", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
16090
|
+
{ type: "kb.folder.deleted", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
16091
|
+
{ type: "email.message.received", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
16092
|
+
{ type: "email.message.sent", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
16093
|
+
{ type: "email.message.status_changed", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
16094
|
+
{ type: "task.visibility_changed", domain: "visibility", aggregateType: "task", phase: "P1" },
|
|
16095
|
+
{ type: "agent.visibility_changed", domain: "visibility", aggregateType: "agent", phase: "P1" },
|
|
16096
|
+
{ type: "goal.visibility_changed", domain: "visibility", aggregateType: "goal", phase: "P2" },
|
|
16097
|
+
{ type: "mission.visibility_changed", domain: "visibility", aggregateType: "mission", phase: "P2" },
|
|
16098
|
+
{ type: "idea.visibility_changed", domain: "visibility", aggregateType: "idea", phase: "P2" }
|
|
16099
|
+
];
|
|
16100
|
+
DOMAIN_EVENT_CATALOG2.map((entry) => entry.type);
|
|
16101
|
+
new Map(
|
|
16102
|
+
DOMAIN_EVENT_CATALOG2.map((entry) => [entry.type, entry])
|
|
16103
|
+
);
|
|
15698
16104
|
var WorkspaceNotProvisionedError = class extends Error {
|
|
15699
16105
|
static {
|
|
15700
16106
|
__name(this, "WorkspaceNotProvisionedError");
|
|
@@ -19193,10 +19599,14 @@ var USER_SELECTABLE_WORK_KINDS3 = [
|
|
|
19193
19599
|
"repo",
|
|
19194
19600
|
"app"
|
|
19195
19601
|
];
|
|
19196
|
-
var WORK_KINDS3 = [...USER_SELECTABLE_WORK_KINDS3, "company", "campaign", "default"];
|
|
19602
|
+
var WORK_KINDS3 = [...USER_SELECTABLE_WORK_KINDS3, "company", "campaign", "project", "default"];
|
|
19197
19603
|
new Set(WORK_KINDS3);
|
|
19198
19604
|
var GOAL_KINDS3 = ["metric", "delivery"];
|
|
19199
19605
|
new Set(GOAL_KINDS3);
|
|
19606
|
+
var RECORD_VISIBILITIES3 = ["private", "team", "organization"];
|
|
19607
|
+
new Set(RECORD_VISIBILITIES3);
|
|
19608
|
+
var RECORD_LIST_VIEWS3 = ["mine", "shared", "organization"];
|
|
19609
|
+
new Set(RECORD_LIST_VIEWS3);
|
|
19200
19610
|
var VERSION3 = "1.0.0";
|
|
19201
19611
|
var GTM_SKILLS3 = [
|
|
19202
19612
|
// ── research ────────────────────────────────────────────────────────
|
|
@@ -19902,6 +20312,113 @@ var ACTION_CATEGORIES3 = Object.freeze([
|
|
|
19902
20312
|
Object.freeze(
|
|
19903
20313
|
ACTION_CATEGORIES3.filter((c) => c !== "read.internal")
|
|
19904
20314
|
);
|
|
20315
|
+
var DOMAIN_EVENT_CATALOG3 = [
|
|
20316
|
+
{ type: "organization.updated", domain: "organization", aggregateType: "organization", phase: "P1" },
|
|
20317
|
+
{
|
|
20318
|
+
type: "organization.member.added",
|
|
20319
|
+
domain: "organization",
|
|
20320
|
+
aggregateType: "organization",
|
|
20321
|
+
phase: "P1",
|
|
20322
|
+
child: "member"
|
|
20323
|
+
},
|
|
20324
|
+
{
|
|
20325
|
+
type: "organization.member.updated",
|
|
20326
|
+
domain: "organization",
|
|
20327
|
+
aggregateType: "organization",
|
|
20328
|
+
phase: "P1",
|
|
20329
|
+
child: "member"
|
|
20330
|
+
},
|
|
20331
|
+
{
|
|
20332
|
+
type: "organization.member.removed",
|
|
20333
|
+
domain: "organization",
|
|
20334
|
+
aggregateType: "organization",
|
|
20335
|
+
phase: "P1",
|
|
20336
|
+
child: "member"
|
|
20337
|
+
},
|
|
20338
|
+
{ type: "agent.created", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
20339
|
+
{ type: "agent.updated", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
20340
|
+
{ type: "agent.status_changed", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
20341
|
+
{ type: "agent.archived", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
20342
|
+
{ type: "agent.deleted", domain: "agents", aggregateType: "agent", phase: "P1" },
|
|
20343
|
+
{ type: "team.created", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
20344
|
+
{ type: "team.updated", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
20345
|
+
{ type: "team.deleted", domain: "teams", aggregateType: "team", phase: "P1" },
|
|
20346
|
+
{ type: "team.member.added", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
20347
|
+
{ type: "team.member.updated", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
20348
|
+
{ type: "team.member.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "member" },
|
|
20349
|
+
{ type: "team.resource.added", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
20350
|
+
{ type: "team.resource.removed", domain: "teams", aggregateType: "team", phase: "P1", child: "resource" },
|
|
20351
|
+
{ type: "work.created", domain: "works", aggregateType: "work", phase: "P1" },
|
|
20352
|
+
{ type: "work.updated", domain: "works", aggregateType: "work", phase: "P1" },
|
|
20353
|
+
{ type: "work.archived", domain: "works", aggregateType: "work", phase: "P1" },
|
|
20354
|
+
{ type: "work.deleted", domain: "works", aggregateType: "work", phase: "P1" },
|
|
20355
|
+
{ type: "work.member.added", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
20356
|
+
{ type: "work.member.updated", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
20357
|
+
{ type: "work.member.removed", domain: "works", aggregateType: "work", phase: "P1", child: "member" },
|
|
20358
|
+
{ type: "mission.created", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20359
|
+
{ type: "mission.updated", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20360
|
+
{ type: "mission.status_changed", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20361
|
+
{ type: "mission.deleted", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20362
|
+
{ type: "mission.goal.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20363
|
+
{ type: "mission.goal.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20364
|
+
{ type: "mission.work.linked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20365
|
+
{ type: "mission.work.unlinked", domain: "missions", aggregateType: "mission", phase: "P2" },
|
|
20366
|
+
{ type: "goal.created", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
20367
|
+
{ type: "goal.updated", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
20368
|
+
{ type: "goal.status_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
20369
|
+
{ type: "goal.archived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
20370
|
+
{ type: "goal.unarchived", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
20371
|
+
{ type: "goal.deleted", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
20372
|
+
{ type: "goal.progress_changed", domain: "goals", aggregateType: "goal", phase: "P2" },
|
|
20373
|
+
{ type: "idea.created", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
20374
|
+
{ type: "idea.updated", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
20375
|
+
{ type: "idea.status_changed", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
20376
|
+
{ type: "idea.deleted", domain: "ideas", aggregateType: "idea", phase: "P2" },
|
|
20377
|
+
{ type: "task.created", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
20378
|
+
{ type: "task.updated", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
20379
|
+
{ type: "task.transitioned", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
20380
|
+
{ type: "task.deleted", domain: "tasks", aggregateType: "task", phase: "P1" },
|
|
20381
|
+
{ type: "task.assignee.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
20382
|
+
{ type: "task.assignee.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "assignee" },
|
|
20383
|
+
{ type: "task.watcher.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
20384
|
+
{ type: "task.watcher.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "watcher" },
|
|
20385
|
+
{ type: "task.block.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
20386
|
+
{ type: "task.block.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "block" },
|
|
20387
|
+
{ type: "task.relation.added", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
20388
|
+
{ type: "task.relation.removed", domain: "tasks", aggregateType: "task", phase: "P1", child: "relation" },
|
|
20389
|
+
{ type: "task.comment.created", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
20390
|
+
{ type: "task.comment.updated", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
20391
|
+
{ type: "task.comment.deleted", domain: "tasks", aggregateType: "task", phase: "P1", child: "comment" },
|
|
20392
|
+
{ type: "task.attachment.added", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
20393
|
+
{ type: "task.attachment.removed", domain: "tasks", aggregateType: "task", phase: "P3", child: "attachment" },
|
|
20394
|
+
{ type: "task.runtime_updated", domain: "tasks", aggregateType: "task", phase: "P1", optIn: true },
|
|
20395
|
+
{ type: "inbox.item.created", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
20396
|
+
{ type: "inbox.item.answered", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
20397
|
+
{ type: "inbox.item.state_changed", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
20398
|
+
{ type: "inbox.item.deleted", domain: "inbox", aggregateType: "inbox_item", phase: "P3" },
|
|
20399
|
+
{ type: "kb.document.created", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
20400
|
+
{ type: "kb.document.updated", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
20401
|
+
{ type: "kb.document.moved", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
20402
|
+
{ type: "kb.document.archived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
20403
|
+
{ type: "kb.document.unarchived", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
20404
|
+
{ type: "kb.document.deleted", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
20405
|
+
{ type: "kb.document.review_changed", domain: "kb", aggregateType: "kb_document", phase: "P3" },
|
|
20406
|
+
{ type: "kb.folder.created", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
20407
|
+
{ type: "kb.folder.updated", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
20408
|
+
{ type: "kb.folder.deleted", domain: "kb", aggregateType: "kb_folder", phase: "P3" },
|
|
20409
|
+
{ type: "email.message.received", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
20410
|
+
{ type: "email.message.sent", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
20411
|
+
{ type: "email.message.status_changed", domain: "email", aggregateType: "email_message", phase: "P4" },
|
|
20412
|
+
{ type: "task.visibility_changed", domain: "visibility", aggregateType: "task", phase: "P1" },
|
|
20413
|
+
{ type: "agent.visibility_changed", domain: "visibility", aggregateType: "agent", phase: "P1" },
|
|
20414
|
+
{ type: "goal.visibility_changed", domain: "visibility", aggregateType: "goal", phase: "P2" },
|
|
20415
|
+
{ type: "mission.visibility_changed", domain: "visibility", aggregateType: "mission", phase: "P2" },
|
|
20416
|
+
{ type: "idea.visibility_changed", domain: "visibility", aggregateType: "idea", phase: "P2" }
|
|
20417
|
+
];
|
|
20418
|
+
DOMAIN_EVENT_CATALOG3.map((entry) => entry.type);
|
|
20419
|
+
new Map(
|
|
20420
|
+
DOMAIN_EVENT_CATALOG3.map((entry) => [entry.type, entry])
|
|
20421
|
+
);
|
|
19905
20422
|
var TerminalNotProvisionedError = class extends Error {
|
|
19906
20423
|
static {
|
|
19907
20424
|
__name(this, "TerminalNotProvisionedError");
|
|
@@ -22805,14 +23322,47 @@ async function runStart(deps, options) {
|
|
|
22805
23322
|
});
|
|
22806
23323
|
}
|
|
22807
23324
|
await loop.start();
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
23325
|
+
let shuttingDown = false;
|
|
23326
|
+
const startLanes = /* @__PURE__ */ __name(async () => {
|
|
23327
|
+
if (worker) {
|
|
23328
|
+
await worker.start();
|
|
23329
|
+
}
|
|
23330
|
+
if (attendedWorker) {
|
|
23331
|
+
await attendedWorker.start();
|
|
23332
|
+
deps.out(
|
|
23333
|
+
runtime.attended?.captureBackend ? "Live view enabled \u2014 the owner can watch this machine from the dashboard (screen and terminal)" : "Live view enabled \u2014 terminal only: no browser was found here to show a screen"
|
|
23334
|
+
);
|
|
23335
|
+
}
|
|
23336
|
+
}, "startLanes");
|
|
23337
|
+
let lanesStarted = Promise.resolve();
|
|
23338
|
+
let laneStartFailed = false;
|
|
23339
|
+
let laneStartFailure = void 0;
|
|
23340
|
+
if (worker || attendedWorker) {
|
|
23341
|
+
if (loop.getState().state === "connected") {
|
|
23342
|
+
await startLanes();
|
|
23343
|
+
} else {
|
|
23344
|
+
deps.out(
|
|
23345
|
+
"Waiting for the platform to accept a heartbeat before leasing work (its answer carries this node\u2019s limits and version verdict)"
|
|
23346
|
+
);
|
|
23347
|
+
lanesStarted = startLanesAfterAcceptedHeartbeat(
|
|
23348
|
+
loop,
|
|
23349
|
+
async () => {
|
|
23350
|
+
deps.io.logger.info("Heartbeat accepted \u2014 starting the worker lanes");
|
|
23351
|
+
await startLanes();
|
|
23352
|
+
},
|
|
23353
|
+
() => !shuttingDown
|
|
23354
|
+
).then((outcome) => {
|
|
23355
|
+
if (!outcome.failed) return;
|
|
23356
|
+
laneStartFailed = true;
|
|
23357
|
+
laneStartFailure = outcome.error;
|
|
23358
|
+
deps.io.logger.error(
|
|
23359
|
+
`Worker lanes failed to start after the first accepted heartbeat: ${describeError4(
|
|
23360
|
+
outcome.error
|
|
23361
|
+
)} \u2014 shutting down`
|
|
23362
|
+
);
|
|
23363
|
+
requestShutdown();
|
|
23364
|
+
});
|
|
23365
|
+
}
|
|
22816
23366
|
}
|
|
22817
23367
|
const reaper = worker && runtime.workspaceRoot ? startWorkspaceReaperTimer({
|
|
22818
23368
|
rootPath: runtime.workspaceRoot,
|
|
@@ -22850,8 +23400,10 @@ async function runStart(deps, options) {
|
|
|
22850
23400
|
}
|
|
22851
23401
|
await (deps.waitForShutdown?.() ?? (deps.signals ? signalled : Promise.resolve()));
|
|
22852
23402
|
reaper?.stop();
|
|
23403
|
+
shuttingDown = true;
|
|
22853
23404
|
loop.stop();
|
|
22854
23405
|
await loop.settled();
|
|
23406
|
+
await lanesStarted;
|
|
22855
23407
|
if (attendedWorker) {
|
|
22856
23408
|
for (const jobId of attendedWorker.getState().activeJobIds) {
|
|
22857
23409
|
attendedWorker.cancelJob(jobId, "This machine is shutting down");
|
|
@@ -22864,8 +23416,29 @@ async function runStart(deps, options) {
|
|
|
22864
23416
|
deps.out(`Worker host stopped (completed ${state.completed}, failed ${state.failed}).`);
|
|
22865
23417
|
}
|
|
22866
23418
|
deps.out("Stopped.");
|
|
23419
|
+
if (laneStartFailed) {
|
|
23420
|
+
throw new CliError(
|
|
23421
|
+
`Worker lanes failed to start after the first accepted heartbeat: ${describeError4(laneStartFailure)}`,
|
|
23422
|
+
EXIT_FAILURE
|
|
23423
|
+
);
|
|
23424
|
+
}
|
|
22867
23425
|
}
|
|
22868
23426
|
__name(runStart, "runStart");
|
|
23427
|
+
async function startLanesAfterAcceptedHeartbeat(loop, startLanes, shouldWait) {
|
|
23428
|
+
const accepted = await untilHeartbeatAccepted(loop, shouldWait);
|
|
23429
|
+
if (!accepted || !shouldWait()) return { started: false, failed: false };
|
|
23430
|
+
try {
|
|
23431
|
+
await startLanes();
|
|
23432
|
+
return { started: true, failed: false };
|
|
23433
|
+
} catch (error) {
|
|
23434
|
+
return { started: false, failed: true, error };
|
|
23435
|
+
}
|
|
23436
|
+
}
|
|
23437
|
+
__name(startLanesAfterAcceptedHeartbeat, "startLanesAfterAcceptedHeartbeat");
|
|
23438
|
+
function describeError4(error) {
|
|
23439
|
+
return error instanceof Error ? error.message : String(error);
|
|
23440
|
+
}
|
|
23441
|
+
__name(describeError4, "describeError");
|
|
22869
23442
|
function parseConcurrency(raw) {
|
|
22870
23443
|
if (raw === void 0) return void 0;
|
|
22871
23444
|
const parsed = Number.parseInt(raw, 10);
|
|
@@ -23532,7 +24105,7 @@ function createLogger(options = {}) {
|
|
|
23532
24105
|
__name(createLogger, "createLogger");
|
|
23533
24106
|
|
|
23534
24107
|
// src/version.ts
|
|
23535
|
-
var NODE_APP_VERSION = "0.2.
|
|
24108
|
+
var NODE_APP_VERSION = "0.2.4";
|
|
23536
24109
|
|
|
23537
24110
|
// src/cli.ts
|
|
23538
24111
|
function buildDeps() {
|