@pleri/olam-cli 0.1.40 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/__tests__/clean.test.d.ts +9 -0
- package/dist/commands/__tests__/clean.test.d.ts.map +1 -0
- package/dist/commands/__tests__/clean.test.js +105 -0
- package/dist/commands/__tests__/clean.test.js.map +1 -0
- package/dist/commands/clean.d.ts +41 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +382 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/image-digests.json +2 -2
- package/dist/index.js +1264 -1119
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +611 -779
- package/host-cp/src/server.mjs +31 -1
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -21213,7 +21213,7 @@ __export(auth_exports, {
|
|
|
21213
21213
|
register: () => register2
|
|
21214
21214
|
});
|
|
21215
21215
|
|
|
21216
|
-
// ../core/
|
|
21216
|
+
// ../core/dist/auth/secret.js
|
|
21217
21217
|
import * as crypto from "node:crypto";
|
|
21218
21218
|
import * as fs2 from "node:fs";
|
|
21219
21219
|
import * as os from "node:os";
|
|
@@ -21226,11 +21226,13 @@ function getSecretFilePath() {
|
|
|
21226
21226
|
}
|
|
21227
21227
|
function getOrCreateSecret() {
|
|
21228
21228
|
const fromEnv = process.env[SECRET_ENV_VAR];
|
|
21229
|
-
if (fromEnv && fromEnv.length > 0)
|
|
21229
|
+
if (fromEnv && fromEnv.length > 0)
|
|
21230
|
+
return fromEnv;
|
|
21230
21231
|
const file = getSecretFilePath();
|
|
21231
21232
|
if (fs2.existsSync(file)) {
|
|
21232
21233
|
const value = fs2.readFileSync(file, "utf-8").trim();
|
|
21233
|
-
if (value.length > 0)
|
|
21234
|
+
if (value.length > 0)
|
|
21235
|
+
return value;
|
|
21234
21236
|
}
|
|
21235
21237
|
const generated = crypto.randomBytes(SECRET_BYTES).toString("base64url");
|
|
21236
21238
|
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
@@ -21243,14 +21245,16 @@ function getOrCreateSecret() {
|
|
|
21243
21245
|
}
|
|
21244
21246
|
function readSecretIfExists() {
|
|
21245
21247
|
const fromEnv = process.env[SECRET_ENV_VAR];
|
|
21246
|
-
if (fromEnv && fromEnv.length > 0)
|
|
21248
|
+
if (fromEnv && fromEnv.length > 0)
|
|
21249
|
+
return fromEnv;
|
|
21247
21250
|
const file = getSecretFilePath();
|
|
21248
|
-
if (!fs2.existsSync(file))
|
|
21251
|
+
if (!fs2.existsSync(file))
|
|
21252
|
+
return null;
|
|
21249
21253
|
const value = fs2.readFileSync(file, "utf-8").trim();
|
|
21250
21254
|
return value.length > 0 ? value : null;
|
|
21251
21255
|
}
|
|
21252
21256
|
|
|
21253
|
-
// ../core/
|
|
21257
|
+
// ../core/dist/auth/client.js
|
|
21254
21258
|
var DEFAULT_BASE_URL = "http://127.0.0.1:9999";
|
|
21255
21259
|
var DEFAULT_TIMEOUT_MS = 3e3;
|
|
21256
21260
|
var RETRY_COUNT = 2;
|
|
@@ -21295,7 +21299,8 @@ var AuthClient = class {
|
|
|
21295
21299
|
params.set("account", options.account);
|
|
21296
21300
|
}
|
|
21297
21301
|
const res = await this.request("GET", `/credentials?${params.toString()}`);
|
|
21298
|
-
if (res.status === 404)
|
|
21302
|
+
if (res.status === 404)
|
|
21303
|
+
return null;
|
|
21299
21304
|
if (!res.ok) {
|
|
21300
21305
|
throw new Error(`auth service returned ${res.status} fetching credentials`);
|
|
21301
21306
|
}
|
|
@@ -21332,19 +21337,13 @@ var AuthClient = class {
|
|
|
21332
21337
|
}
|
|
21333
21338
|
}
|
|
21334
21339
|
async disableAccount(accountId) {
|
|
21335
|
-
const res = await this.request(
|
|
21336
|
-
"POST",
|
|
21337
|
-
`/credentials/${encodeURIComponent(accountId)}/disable`
|
|
21338
|
-
);
|
|
21340
|
+
const res = await this.request("POST", `/credentials/${encodeURIComponent(accountId)}/disable`);
|
|
21339
21341
|
if (!res.ok) {
|
|
21340
21342
|
throw new Error(`failed to disable account ${accountId} (HTTP ${res.status})`);
|
|
21341
21343
|
}
|
|
21342
21344
|
}
|
|
21343
21345
|
async enableAccount(accountId) {
|
|
21344
|
-
const res = await this.request(
|
|
21345
|
-
"POST",
|
|
21346
|
-
`/credentials/${encodeURIComponent(accountId)}/enable`
|
|
21347
|
-
);
|
|
21346
|
+
const res = await this.request("POST", `/credentials/${encodeURIComponent(accountId)}/enable`);
|
|
21348
21347
|
if (!res.ok) {
|
|
21349
21348
|
throw new Error(`failed to enable account ${accountId} (HTTP ${res.status})`);
|
|
21350
21349
|
}
|
|
@@ -21355,11 +21354,7 @@ var AuthClient = class {
|
|
|
21355
21354
|
* `anthropic-ratelimit-reset` (epoch seconds) — the server normalizes.
|
|
21356
21355
|
*/
|
|
21357
21356
|
async reportRateLimit(accountId, info) {
|
|
21358
|
-
const res = await this.request(
|
|
21359
|
-
"POST",
|
|
21360
|
-
`/credentials/${encodeURIComponent(accountId)}/rate-limited`,
|
|
21361
|
-
info
|
|
21362
|
-
);
|
|
21357
|
+
const res = await this.request("POST", `/credentials/${encodeURIComponent(accountId)}/rate-limited`, info);
|
|
21363
21358
|
if (!res.ok) {
|
|
21364
21359
|
throw new Error(`failed to report rate-limit for ${accountId} (HTTP ${res.status})`);
|
|
21365
21360
|
}
|
|
@@ -21369,8 +21364,10 @@ var AuthClient = class {
|
|
|
21369
21364
|
const controller = new AbortController();
|
|
21370
21365
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
21371
21366
|
const headers = {};
|
|
21372
|
-
if (body)
|
|
21373
|
-
|
|
21367
|
+
if (body)
|
|
21368
|
+
headers["Content-Type"] = "application/json";
|
|
21369
|
+
if (this.secret)
|
|
21370
|
+
headers["X-Olam-Secret"] = this.secret;
|
|
21374
21371
|
try {
|
|
21375
21372
|
return await fetch(url, {
|
|
21376
21373
|
method,
|
|
@@ -21390,12 +21387,14 @@ var AuthClient = class {
|
|
|
21390
21387
|
}
|
|
21391
21388
|
};
|
|
21392
21389
|
function isTransient(err) {
|
|
21393
|
-
if (!(err instanceof Error))
|
|
21390
|
+
if (!(err instanceof Error))
|
|
21391
|
+
return false;
|
|
21394
21392
|
const message = err.message.toLowerCase();
|
|
21395
21393
|
return message.includes("econnrefused") || message.includes("econnreset") || message.includes("fetch failed");
|
|
21396
21394
|
}
|
|
21397
21395
|
function describeError(err) {
|
|
21398
|
-
if (err instanceof Error)
|
|
21396
|
+
if (err instanceof Error)
|
|
21397
|
+
return err.message;
|
|
21399
21398
|
return "unknown error";
|
|
21400
21399
|
}
|
|
21401
21400
|
async function safeText(res) {
|
|
@@ -21409,7 +21408,7 @@ function sleep(ms) {
|
|
|
21409
21408
|
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
21410
21409
|
}
|
|
21411
21410
|
|
|
21412
|
-
// ../core/
|
|
21411
|
+
// ../core/dist/auth/container.js
|
|
21413
21412
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
21414
21413
|
import { existsSync as existsSync3 } from "node:fs";
|
|
21415
21414
|
import * as path3 from "node:path";
|
|
@@ -21433,11 +21432,7 @@ var AuthContainerController = class {
|
|
|
21433
21432
|
this.imageTag = options.imageTag ?? DEFAULT_IMAGE;
|
|
21434
21433
|
}
|
|
21435
21434
|
status() {
|
|
21436
|
-
const inspect = spawnSync(
|
|
21437
|
-
"docker",
|
|
21438
|
-
["inspect", "--format", "{{.State.Status}}|{{.Id}}", this.containerName],
|
|
21439
|
-
{ encoding: "utf-8" }
|
|
21440
|
-
);
|
|
21435
|
+
const inspect = spawnSync("docker", ["inspect", "--format", "{{.State.Status}}|{{.Id}}", this.containerName], { encoding: "utf-8" });
|
|
21441
21436
|
if (inspect.status === 0) {
|
|
21442
21437
|
const [stateRaw, id] = inspect.stdout.trim().split("|");
|
|
21443
21438
|
const state = stateRaw === "running" ? "running" : "stopped";
|
|
@@ -21460,7 +21455,8 @@ var AuthContainerController = class {
|
|
|
21460
21455
|
*/
|
|
21461
21456
|
start() {
|
|
21462
21457
|
const current = this.status();
|
|
21463
|
-
if (current.state === "running")
|
|
21458
|
+
if (current.state === "running")
|
|
21459
|
+
return;
|
|
21464
21460
|
if (current.state === "stopped") {
|
|
21465
21461
|
execFileSync("docker", ["start", this.containerName], { stdio: "pipe" });
|
|
21466
21462
|
return;
|
|
@@ -21483,15 +21479,9 @@ var AuthContainerController = class {
|
|
|
21483
21479
|
buildImage() {
|
|
21484
21480
|
const dockerfileDir = resolveAuthServicePath();
|
|
21485
21481
|
if (!existsSync3(path3.join(dockerfileDir, "Dockerfile"))) {
|
|
21486
|
-
throw new Error(
|
|
21487
|
-
`auth image '${this.imageTag}' is not present locally and no Dockerfile is available at ${dockerfileDir} to build from. Run \`olam bootstrap\` to pull the published image, or check out the olam source tree.`
|
|
21488
|
-
);
|
|
21482
|
+
throw new Error(`auth image '${this.imageTag}' is not present locally and no Dockerfile is available at ${dockerfileDir} to build from. Run \`olam bootstrap\` to pull the published image, or check out the olam source tree.`);
|
|
21489
21483
|
}
|
|
21490
|
-
execFileSync(
|
|
21491
|
-
"docker",
|
|
21492
|
-
["build", "--tag", this.imageTag, dockerfileDir],
|
|
21493
|
-
{ stdio: "inherit" }
|
|
21494
|
-
);
|
|
21484
|
+
execFileSync("docker", ["build", "--tag", this.imageTag, dockerfileDir], { stdio: "inherit" });
|
|
21495
21485
|
}
|
|
21496
21486
|
/**
|
|
21497
21487
|
* `docker run` the auth container. Injects the shared secret so every
|
|
@@ -21499,33 +21489,30 @@ var AuthContainerController = class {
|
|
|
21499
21489
|
*/
|
|
21500
21490
|
runContainer() {
|
|
21501
21491
|
const secret = getOrCreateSecret();
|
|
21502
|
-
execFileSync(
|
|
21503
|
-
"
|
|
21504
|
-
|
|
21505
|
-
|
|
21506
|
-
|
|
21507
|
-
|
|
21508
|
-
|
|
21509
|
-
|
|
21510
|
-
|
|
21511
|
-
|
|
21512
|
-
|
|
21513
|
-
|
|
21514
|
-
|
|
21515
|
-
|
|
21516
|
-
|
|
21517
|
-
|
|
21518
|
-
|
|
21519
|
-
|
|
21520
|
-
|
|
21521
|
-
this.imageTag
|
|
21522
|
-
],
|
|
21523
|
-
{ stdio: "pipe" }
|
|
21524
|
-
);
|
|
21492
|
+
execFileSync("docker", [
|
|
21493
|
+
"run",
|
|
21494
|
+
"--detach",
|
|
21495
|
+
"--name",
|
|
21496
|
+
this.containerName,
|
|
21497
|
+
"--restart",
|
|
21498
|
+
"unless-stopped",
|
|
21499
|
+
"--publish",
|
|
21500
|
+
`${this.port}:9999`,
|
|
21501
|
+
"--volume",
|
|
21502
|
+
`${this.volume}:/auth-data`,
|
|
21503
|
+
"--env",
|
|
21504
|
+
`AUTH_BIND=0.0.0.0`,
|
|
21505
|
+
"--env",
|
|
21506
|
+
`AUTH_DATA_DIR=/auth-data`,
|
|
21507
|
+
"--env",
|
|
21508
|
+
`OLAM_AUTH_SECRET=${secret}`,
|
|
21509
|
+
this.imageTag
|
|
21510
|
+
], { stdio: "pipe" });
|
|
21525
21511
|
}
|
|
21526
21512
|
stop() {
|
|
21527
21513
|
const current = this.status();
|
|
21528
|
-
if (current.state !== "running")
|
|
21514
|
+
if (current.state !== "running")
|
|
21515
|
+
return;
|
|
21529
21516
|
execFileSync("docker", ["stop", this.containerName], { stdio: "pipe" });
|
|
21530
21517
|
}
|
|
21531
21518
|
remove() {
|
|
@@ -21537,7 +21524,8 @@ var AuthContainerController = class {
|
|
|
21537
21524
|
const deadline = Date.now() + timeoutMs;
|
|
21538
21525
|
while (Date.now() < deadline) {
|
|
21539
21526
|
const health = await client.health();
|
|
21540
|
-
if (health.ok)
|
|
21527
|
+
if (health.ok)
|
|
21528
|
+
return true;
|
|
21541
21529
|
await sleep2(500);
|
|
21542
21530
|
}
|
|
21543
21531
|
return false;
|
|
@@ -21552,7 +21540,7 @@ function sleep2(ms) {
|
|
|
21552
21540
|
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
21553
21541
|
}
|
|
21554
21542
|
|
|
21555
|
-
// ../core/
|
|
21543
|
+
// ../core/dist/auth/preflight.js
|
|
21556
21544
|
async function runAuthPreflight(options = {}) {
|
|
21557
21545
|
const client = new AuthClient(options);
|
|
21558
21546
|
const controller = new AuthContainerController();
|
|
@@ -21760,7 +21748,7 @@ __export(pr_exports, {
|
|
|
21760
21748
|
register: () => register3
|
|
21761
21749
|
});
|
|
21762
21750
|
|
|
21763
|
-
// ../core/
|
|
21751
|
+
// ../core/dist/world/registry.js
|
|
21764
21752
|
import { createRequire } from "node:module";
|
|
21765
21753
|
import * as net from "node:net";
|
|
21766
21754
|
import * as os2 from "node:os";
|
|
@@ -21795,15 +21783,18 @@ function rowToMetadata(row) {
|
|
|
21795
21783
|
let expectedServices;
|
|
21796
21784
|
let appPortUrls;
|
|
21797
21785
|
try {
|
|
21798
|
-
if (row.readiness_chain)
|
|
21786
|
+
if (row.readiness_chain)
|
|
21787
|
+
readinessChain = JSON.parse(row.readiness_chain);
|
|
21799
21788
|
} catch {
|
|
21800
21789
|
}
|
|
21801
21790
|
try {
|
|
21802
|
-
if (row.expected_services)
|
|
21791
|
+
if (row.expected_services)
|
|
21792
|
+
expectedServices = JSON.parse(row.expected_services);
|
|
21803
21793
|
} catch {
|
|
21804
21794
|
}
|
|
21805
21795
|
try {
|
|
21806
|
-
if (row.app_port_urls)
|
|
21796
|
+
if (row.app_port_urls)
|
|
21797
|
+
appPortUrls = JSON.parse(row.app_port_urls);
|
|
21807
21798
|
} catch {
|
|
21808
21799
|
}
|
|
21809
21800
|
return {
|
|
@@ -21884,9 +21875,7 @@ var WorldRegistry = class {
|
|
|
21884
21875
|
if (!row) {
|
|
21885
21876
|
this.db.prepare("INSERT INTO meta (key, value) VALUES (?, ?)").run("schema_version", SCHEMA_VERSION);
|
|
21886
21877
|
}
|
|
21887
|
-
const cols = this.db.prepare("PRAGMA table_info(worlds)").all().map(
|
|
21888
|
-
(r) => r.name
|
|
21889
|
-
);
|
|
21878
|
+
const cols = this.db.prepare("PRAGMA table_info(worlds)").all().map((r) => r.name);
|
|
21890
21879
|
if (!cols.includes("pr_url")) {
|
|
21891
21880
|
this.db.exec("ALTER TABLE worlds ADD COLUMN pr_url TEXT");
|
|
21892
21881
|
this.db.exec("ALTER TABLE worlds ADD COLUMN pr_number INTEGER");
|
|
@@ -21894,43 +21883,16 @@ var WorldRegistry = class {
|
|
|
21894
21883
|
this.db.exec("ALTER TABLE worlds ADD COLUMN pr_created_at TEXT");
|
|
21895
21884
|
this.db.exec("ALTER TABLE worlds ADD COLUMN pr_state TEXT NOT NULL DEFAULT 'none'");
|
|
21896
21885
|
this.db.exec("ALTER TABLE worlds ADD COLUMN pr_merged_at TEXT");
|
|
21897
|
-
this.db.exec(
|
|
21898
|
-
"ALTER TABLE worlds ADD COLUMN auto_destroy_on_merge INTEGER NOT NULL DEFAULT 1"
|
|
21899
|
-
);
|
|
21886
|
+
this.db.exec("ALTER TABLE worlds ADD COLUMN auto_destroy_on_merge INTEGER NOT NULL DEFAULT 1");
|
|
21900
21887
|
}
|
|
21901
21888
|
}
|
|
21902
21889
|
register(world) {
|
|
21903
|
-
this.db.prepare(
|
|
21904
|
-
`INSERT INTO worlds
|
|
21890
|
+
this.db.prepare(`INSERT INTO worlds
|
|
21905
21891
|
(id, name, status, repos, branch, port_offset, workspace_path,
|
|
21906
21892
|
compute_provider, total_cost_usd, thought_count, created_at, updated_at,
|
|
21907
21893
|
pr_url, pr_number, pr_repo, pr_created_at, pr_state, pr_merged_at, auto_destroy_on_merge,
|
|
21908
21894
|
readiness_chain, expected_services, app_port_urls)
|
|
21909
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
21910
|
-
).run(
|
|
21911
|
-
world.id,
|
|
21912
|
-
world.name,
|
|
21913
|
-
world.status,
|
|
21914
|
-
JSON.stringify(world.repos),
|
|
21915
|
-
world.branch,
|
|
21916
|
-
world.portOffset,
|
|
21917
|
-
world.workspacePath,
|
|
21918
|
-
world.computeProvider,
|
|
21919
|
-
world.totalCostUsd,
|
|
21920
|
-
world.thoughtCount,
|
|
21921
|
-
world.createdAt,
|
|
21922
|
-
world.updatedAt,
|
|
21923
|
-
world.prUrl ?? null,
|
|
21924
|
-
world.prNumber ?? null,
|
|
21925
|
-
world.prRepo ?? null,
|
|
21926
|
-
world.prCreatedAt ?? null,
|
|
21927
|
-
world.prState ?? "none",
|
|
21928
|
-
world.prMergedAt ?? null,
|
|
21929
|
-
world.autoDestroyOnMerge === false ? 0 : 1,
|
|
21930
|
-
world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null,
|
|
21931
|
-
world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null,
|
|
21932
|
-
world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null
|
|
21933
|
-
);
|
|
21895
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(world.id, world.name, world.status, JSON.stringify(world.repos), world.branch, world.portOffset, world.workspacePath, world.computeProvider, world.totalCostUsd, world.thoughtCount, world.createdAt, world.updatedAt, world.prUrl ?? null, world.prNumber ?? null, world.prRepo ?? null, world.prCreatedAt ?? null, world.prState ?? "none", world.prMergedAt ?? null, world.autoDestroyOnMerge === false ? 0 : 1, world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null, world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null, world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null);
|
|
21934
21896
|
}
|
|
21935
21897
|
update(worldId, updates) {
|
|
21936
21898
|
const setClauses = [];
|
|
@@ -21971,7 +21933,8 @@ var WorldRegistry = class {
|
|
|
21971
21933
|
}
|
|
21972
21934
|
}
|
|
21973
21935
|
}
|
|
21974
|
-
if (setClauses.length === 0)
|
|
21936
|
+
if (setClauses.length === 0)
|
|
21937
|
+
return;
|
|
21975
21938
|
if (!updates.updatedAt) {
|
|
21976
21939
|
setClauses.push("updated_at = ?");
|
|
21977
21940
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -21996,9 +21959,7 @@ var WorldRegistry = class {
|
|
|
21996
21959
|
return rows.map(rowToMetadata);
|
|
21997
21960
|
}
|
|
21998
21961
|
listWithPr() {
|
|
21999
|
-
const rows = this.db.prepare(
|
|
22000
|
-
"SELECT * FROM worlds WHERE pr_url IS NOT NULL AND pr_state != 'merged_destroyed' ORDER BY created_at DESC"
|
|
22001
|
-
).all();
|
|
21962
|
+
const rows = this.db.prepare("SELECT * FROM worlds WHERE pr_url IS NOT NULL AND pr_state != 'merged_destroyed' ORDER BY created_at DESC").all();
|
|
22002
21963
|
return rows.map(rowToMetadata);
|
|
22003
21964
|
}
|
|
22004
21965
|
remove(worldId) {
|
|
@@ -22010,13 +21971,13 @@ var WorldRegistry = class {
|
|
|
22010
21971
|
* control plane port (19080+offset) is not already bound on the host.
|
|
22011
21972
|
*/
|
|
22012
21973
|
getNextPortOffset() {
|
|
22013
|
-
const rows = this.db.prepare(
|
|
22014
|
-
`SELECT port_offset FROM worlds WHERE status != 'destroyed' ORDER BY port_offset`
|
|
22015
|
-
).all();
|
|
21974
|
+
const rows = this.db.prepare(`SELECT port_offset FROM worlds WHERE status != 'destroyed' ORDER BY port_offset`).all();
|
|
22016
21975
|
const usedOffsets = new Set(rows.map((r) => r.port_offset));
|
|
22017
21976
|
for (let candidate = 0; candidate <= 1e4; candidate += 100) {
|
|
22018
|
-
if (usedOffsets.has(candidate))
|
|
22019
|
-
|
|
21977
|
+
if (usedOffsets.has(candidate))
|
|
21978
|
+
continue;
|
|
21979
|
+
if (isPortInUse(19080 + candidate))
|
|
21980
|
+
continue;
|
|
22020
21981
|
return candidate;
|
|
22021
21982
|
}
|
|
22022
21983
|
const max = rows.length > 0 ? rows[rows.length - 1].port_offset : -100;
|
|
@@ -22027,7 +21988,7 @@ var WorldRegistry = class {
|
|
|
22027
21988
|
}
|
|
22028
21989
|
};
|
|
22029
21990
|
|
|
22030
|
-
// ../core/
|
|
21991
|
+
// ../core/dist/pr-gate/client.js
|
|
22031
21992
|
var HOST_CONTROL_PLANE_BASE = 19080;
|
|
22032
21993
|
var DEFAULT_TIMEOUT_MS2 = 3e3;
|
|
22033
21994
|
function worldBaseUrl(portOffset) {
|
|
@@ -22044,24 +22005,24 @@ async function request(url, init = {}, timeoutMs = DEFAULT_TIMEOUT_MS2) {
|
|
|
22044
22005
|
}
|
|
22045
22006
|
async function listGates(portOffset) {
|
|
22046
22007
|
const res = await request(`${worldBaseUrl(portOffset)}/api/pr-gate`);
|
|
22047
|
-
if (!res.ok)
|
|
22008
|
+
if (!res.ok)
|
|
22009
|
+
throw new Error(`list gates failed: HTTP ${res.status}`);
|
|
22048
22010
|
return await res.json();
|
|
22049
22011
|
}
|
|
22050
22012
|
async function getGate(portOffset, id) {
|
|
22051
22013
|
const res = await request(`${worldBaseUrl(portOffset)}/api/pr-gate/${encodeURIComponent(id)}`);
|
|
22052
|
-
if (res.status === 404)
|
|
22053
|
-
|
|
22014
|
+
if (res.status === 404)
|
|
22015
|
+
return null;
|
|
22016
|
+
if (!res.ok)
|
|
22017
|
+
throw new Error(`get gate failed: HTTP ${res.status}`);
|
|
22054
22018
|
return await res.json();
|
|
22055
22019
|
}
|
|
22056
22020
|
async function decideGate(portOffset, id, payload) {
|
|
22057
|
-
const res = await request(
|
|
22058
|
-
|
|
22059
|
-
{
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
body: JSON.stringify(payload)
|
|
22063
|
-
}
|
|
22064
|
-
);
|
|
22021
|
+
const res = await request(`${worldBaseUrl(portOffset)}/api/pr-gate/${encodeURIComponent(id)}/decision`, {
|
|
22022
|
+
method: "POST",
|
|
22023
|
+
headers: { "Content-Type": "application/json" },
|
|
22024
|
+
body: JSON.stringify(payload)
|
|
22025
|
+
});
|
|
22065
22026
|
if (!res.ok) {
|
|
22066
22027
|
const text = await res.text().catch(() => "");
|
|
22067
22028
|
throw new Error(`decide gate failed: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
@@ -22260,7 +22221,7 @@ __export(workspace_exports, {
|
|
|
22260
22221
|
});
|
|
22261
22222
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
22262
22223
|
|
|
22263
|
-
// ../core/
|
|
22224
|
+
// ../core/dist/workspace/schema.js
|
|
22264
22225
|
var ReadinessServiceSchema = external_exports.object({
|
|
22265
22226
|
name: external_exports.string().min(1),
|
|
22266
22227
|
port: external_exports.number().int().positive(),
|
|
@@ -22276,10 +22237,7 @@ var RESERVED_REPO_NAMES = /* @__PURE__ */ new Set([
|
|
|
22276
22237
|
"tostring",
|
|
22277
22238
|
"valueof"
|
|
22278
22239
|
]);
|
|
22279
|
-
var repoNameSchema = external_exports.string().min(1).max(64).regex(
|
|
22280
|
-
REPO_NAME_PATTERN,
|
|
22281
|
-
'repo name must be ASCII lowercase + digits + dash (1-64 chars, no leading/trailing dash). E.g. "atlas-core", "diner-app". The `${repos.<name>.<field>}` reference syntax reserves "."; worktree paths reserve "/" and whitespace.'
|
|
22282
|
-
).refine((name) => !RESERVED_REPO_NAMES.has(name), {
|
|
22240
|
+
var repoNameSchema = external_exports.string().min(1).max(64).regex(REPO_NAME_PATTERN, 'repo name must be ASCII lowercase + digits + dash (1-64 chars, no leading/trailing dash). E.g. "atlas-core", "diner-app". The `${repos.<name>.<field>}` reference syntax reserves "."; worktree paths reserve "/" and whitespace.').refine((name) => !RESERVED_REPO_NAMES.has(name), {
|
|
22283
22241
|
message: 'repo name shadows an Object.prototype member (e.g. "constructor", "toString") \u2014 choose a different name to avoid downstream confusion'
|
|
22284
22242
|
});
|
|
22285
22243
|
var WorkspaceRepoSchema = external_exports.object({
|
|
@@ -22325,32 +22283,29 @@ var WorkspaceSchema = external_exports.object({
|
|
|
22325
22283
|
updatedAt: external_exports.number().int().nonnegative()
|
|
22326
22284
|
});
|
|
22327
22285
|
|
|
22328
|
-
// ../core/
|
|
22286
|
+
// ../core/dist/workspace/store.js
|
|
22329
22287
|
import * as fs4 from "node:fs";
|
|
22330
22288
|
import * as os4 from "node:os";
|
|
22331
22289
|
import * as path6 from "node:path";
|
|
22332
22290
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
22333
22291
|
|
|
22334
|
-
// ../core/
|
|
22335
|
-
var versionStringSchema = external_exports.string().regex(
|
|
22336
|
-
/^[\d][\w.+-]*$/,
|
|
22337
|
-
"Version must start with a digit and contain only alphanumeric, dots, dashes, plus signs"
|
|
22338
|
-
);
|
|
22292
|
+
// ../core/dist/config/version.js
|
|
22293
|
+
var versionStringSchema = external_exports.string().regex(/^[\d][\w.+-]*$/, "Version must start with a digit and contain only alphanumeric, dots, dashes, plus signs");
|
|
22339
22294
|
var runtimeVersionSchema = external_exports.object({
|
|
22340
22295
|
ruby: versionStringSchema.optional(),
|
|
22341
22296
|
node: versionStringSchema.optional(),
|
|
22342
22297
|
python: versionStringSchema.optional()
|
|
22343
22298
|
});
|
|
22344
22299
|
|
|
22345
|
-
// ../core/
|
|
22300
|
+
// ../core/dist/world/repo-manifest.js
|
|
22346
22301
|
import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync3 } from "node:fs";
|
|
22347
22302
|
import { join as join5 } from "node:path";
|
|
22348
22303
|
import YAML from "yaml";
|
|
22349
22304
|
|
|
22350
|
-
// ../core/
|
|
22305
|
+
// ../core/dist/manifest/version.js
|
|
22351
22306
|
var MANIFEST_VERSION = 1;
|
|
22352
22307
|
|
|
22353
|
-
// ../core/
|
|
22308
|
+
// ../core/dist/world/repo-manifest.js
|
|
22354
22309
|
var runtimeSchema = external_exports.object({
|
|
22355
22310
|
ruby: versionStringSchema.optional(),
|
|
22356
22311
|
node: versionStringSchema.optional(),
|
|
@@ -22449,12 +22404,7 @@ function refineForbiddenKeys(value, path21, ctx, rejectSource) {
|
|
|
22449
22404
|
});
|
|
22450
22405
|
continue;
|
|
22451
22406
|
}
|
|
22452
|
-
refineForbiddenKeys(
|
|
22453
|
-
value[key],
|
|
22454
|
-
[...path21, key],
|
|
22455
|
-
ctx,
|
|
22456
|
-
false
|
|
22457
|
-
);
|
|
22407
|
+
refineForbiddenKeys(value[key], [...path21, key], ctx, false);
|
|
22458
22408
|
}
|
|
22459
22409
|
}
|
|
22460
22410
|
function rejectForbiddenKeys(value, path21, rejectSource) {
|
|
@@ -22463,20 +22413,12 @@ function rejectForbiddenKeys(value, path21, rejectSource) {
|
|
|
22463
22413
|
}
|
|
22464
22414
|
for (const key of Object.keys(value)) {
|
|
22465
22415
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
22466
|
-
throw new Error(
|
|
22467
|
-
`[manifest] ${path21}: forbidden key "${key}" (prototype-pollution surface)`
|
|
22468
|
-
);
|
|
22416
|
+
throw new Error(`[manifest] ${path21}: forbidden key "${key}" (prototype-pollution surface)`);
|
|
22469
22417
|
}
|
|
22470
22418
|
if (rejectSource && key === "source") {
|
|
22471
|
-
throw new Error(
|
|
22472
|
-
`[manifest] ${path21}: top-level "source" is loader-stamped \u2014 manifests must not author it`
|
|
22473
|
-
);
|
|
22419
|
+
throw new Error(`[manifest] ${path21}: top-level "source" is loader-stamped \u2014 manifests must not author it`);
|
|
22474
22420
|
}
|
|
22475
|
-
rejectForbiddenKeys(
|
|
22476
|
-
value[key],
|
|
22477
|
-
`${path21}.${key}`,
|
|
22478
|
-
false
|
|
22479
|
-
);
|
|
22421
|
+
rejectForbiddenKeys(value[key], `${path21}.${key}`, false);
|
|
22480
22422
|
}
|
|
22481
22423
|
}
|
|
22482
22424
|
function unknownTopLevelKeys(parsed) {
|
|
@@ -22498,64 +22440,51 @@ function loadRepoManifest(repoDir) {
|
|
|
22498
22440
|
}
|
|
22499
22441
|
const stat = lstatSync(manifestPath);
|
|
22500
22442
|
if (stat.isSymbolicLink()) {
|
|
22501
|
-
throw new Error(
|
|
22502
|
-
`[manifest] ${manifestPath}: symbolic links are not permitted`
|
|
22503
|
-
);
|
|
22443
|
+
throw new Error(`[manifest] ${manifestPath}: symbolic links are not permitted`);
|
|
22504
22444
|
}
|
|
22505
22445
|
const raw = readFileSync3(manifestPath, "utf-8");
|
|
22506
22446
|
const parsed = YAML.parse(raw, { maxAliasCount: 100 });
|
|
22507
22447
|
if (parsed === null || parsed === void 0) {
|
|
22508
22448
|
if (source === "olam" && existsSync4(adbPath)) {
|
|
22509
|
-
console.warn(
|
|
22510
|
-
`[manifest] ${manifestPath}: file is empty; .adb.yaml is NOT consulted (delete .olam.yaml to fall back)`
|
|
22511
|
-
);
|
|
22449
|
+
console.warn(`[manifest] ${manifestPath}: file is empty; .adb.yaml is NOT consulted (delete .olam.yaml to fall back)`);
|
|
22512
22450
|
}
|
|
22513
22451
|
return null;
|
|
22514
22452
|
}
|
|
22515
22453
|
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
22516
|
-
throw new Error(
|
|
22517
|
-
`[manifest] ${manifestPath}: expected a YAML mapping at the top level`
|
|
22518
|
-
);
|
|
22454
|
+
throw new Error(`[manifest] ${manifestPath}: expected a YAML mapping at the top level`);
|
|
22519
22455
|
}
|
|
22520
22456
|
rejectForbiddenKeys(parsed, manifestPath, true);
|
|
22521
22457
|
const body = RepoManifestSchema.parse(parsed);
|
|
22522
22458
|
if (parsed["version"] === void 0) {
|
|
22523
|
-
console.warn(
|
|
22524
|
-
`[manifest] ${manifestPath}: missing "version: ${MANIFEST_VERSION}" field \u2014 add it to suppress this warning (backward-compat: file still parses)`
|
|
22525
|
-
);
|
|
22459
|
+
console.warn(`[manifest] ${manifestPath}: missing "version: ${MANIFEST_VERSION}" field \u2014 add it to suppress this warning (backward-compat: file still parses)`);
|
|
22526
22460
|
}
|
|
22527
22461
|
const unknown2 = unknownTopLevelKeys(parsed);
|
|
22528
22462
|
if (unknown2.length > 0) {
|
|
22529
|
-
console.warn(
|
|
22530
|
-
`[manifest] ${manifestPath}: unknown top-level fields preserved (passthrough): ${unknown2.join(", ")}`
|
|
22531
|
-
);
|
|
22463
|
+
console.warn(`[manifest] ${manifestPath}: unknown top-level fields preserved (passthrough): ${unknown2.join(", ")}`);
|
|
22532
22464
|
}
|
|
22533
22465
|
return { ...body, source };
|
|
22534
22466
|
}
|
|
22535
22467
|
|
|
22536
|
-
// ../core/
|
|
22468
|
+
// ../core/dist/util/path.js
|
|
22537
22469
|
import * as os3 from "node:os";
|
|
22538
22470
|
import * as path5 from "node:path";
|
|
22539
22471
|
function resolveTildePath(p) {
|
|
22540
|
-
if (p === "~")
|
|
22541
|
-
|
|
22472
|
+
if (p === "~")
|
|
22473
|
+
return os3.homedir();
|
|
22474
|
+
if (p.startsWith("~/"))
|
|
22475
|
+
return path5.join(os3.homedir(), p.slice(2));
|
|
22542
22476
|
if (p.startsWith("~")) {
|
|
22543
|
-
throw new Error(
|
|
22544
|
-
`[path] POSIX "~user" syntax is not supported (got "${p}"). Use "~/..." for the operator's home or an absolute path.`
|
|
22545
|
-
);
|
|
22477
|
+
throw new Error(`[path] POSIX "~user" syntax is not supported (got "${p}"). Use "~/..." for the operator's home or an absolute path.`);
|
|
22546
22478
|
}
|
|
22547
|
-
if (p.startsWith("/"))
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
);
|
|
22479
|
+
if (p.startsWith("/"))
|
|
22480
|
+
return p;
|
|
22481
|
+
throw new Error(`[path] expected absolute path or "~/..."; got "${p}"`);
|
|
22551
22482
|
}
|
|
22552
22483
|
|
|
22553
|
-
// ../core/
|
|
22484
|
+
// ../core/dist/workspace/store.js
|
|
22554
22485
|
var WorkspaceNameError = class extends Error {
|
|
22555
22486
|
constructor(name) {
|
|
22556
|
-
super(
|
|
22557
|
-
`invalid workspace name "${name}": must match ${WORKSPACE_NAME_PATTERN} (lowercase alnum + dashes, 1\u201364 chars, no leading/trailing dash)`
|
|
22558
|
-
);
|
|
22487
|
+
super(`invalid workspace name "${name}": must match ${WORKSPACE_NAME_PATTERN} (lowercase alnum + dashes, 1\u201364 chars, no leading/trailing dash)`);
|
|
22559
22488
|
this.name = "WorkspaceNameError";
|
|
22560
22489
|
}
|
|
22561
22490
|
};
|
|
@@ -22567,7 +22496,8 @@ var WorkspaceExistsError = class extends Error {
|
|
|
22567
22496
|
};
|
|
22568
22497
|
function workspacesDir() {
|
|
22569
22498
|
const override = process.env["OLAM_WORKSPACES_DIR"];
|
|
22570
|
-
if (override && override.length > 0)
|
|
22499
|
+
if (override && override.length > 0)
|
|
22500
|
+
return override;
|
|
22571
22501
|
return path6.join(os4.homedir(), ".olam", "workspaces");
|
|
22572
22502
|
}
|
|
22573
22503
|
function validateName(name) {
|
|
@@ -22580,7 +22510,8 @@ function filePathFor(name) {
|
|
|
22580
22510
|
}
|
|
22581
22511
|
function listWorkspaces() {
|
|
22582
22512
|
const dir = workspacesDir();
|
|
22583
|
-
if (!fs4.existsSync(dir))
|
|
22513
|
+
if (!fs4.existsSync(dir))
|
|
22514
|
+
return [];
|
|
22584
22515
|
const entries = fs4.readdirSync(dir).filter((f) => f.endsWith(".yaml"));
|
|
22585
22516
|
const result = [];
|
|
22586
22517
|
for (const entry of entries) {
|
|
@@ -22601,7 +22532,8 @@ function listWorkspaces() {
|
|
|
22601
22532
|
function readWorkspace(name) {
|
|
22602
22533
|
validateName(name);
|
|
22603
22534
|
const file = filePathFor(name);
|
|
22604
|
-
if (!fs4.existsSync(file))
|
|
22535
|
+
if (!fs4.existsSync(file))
|
|
22536
|
+
return null;
|
|
22605
22537
|
const raw = fs4.readFileSync(file, "utf-8");
|
|
22606
22538
|
return WorkspaceSchema.parse(parseYaml(raw));
|
|
22607
22539
|
}
|
|
@@ -22621,7 +22553,8 @@ function writeWorkspace(ws, options = {}) {
|
|
|
22621
22553
|
function removeWorkspace(name) {
|
|
22622
22554
|
validateName(name);
|
|
22623
22555
|
const file = filePathFor(name);
|
|
22624
|
-
if (!fs4.existsSync(file))
|
|
22556
|
+
if (!fs4.existsSync(file))
|
|
22557
|
+
return false;
|
|
22625
22558
|
fs4.unlinkSync(file);
|
|
22626
22559
|
return true;
|
|
22627
22560
|
}
|
|
@@ -22633,9 +22566,7 @@ function workspaceToRepoConfigs(ws) {
|
|
|
22633
22566
|
manifest = repoPath ? loadRepoManifest(repoPath) : null;
|
|
22634
22567
|
} catch (err) {
|
|
22635
22568
|
const msg = err instanceof Error ? err.message : String(err);
|
|
22636
|
-
throw new Error(
|
|
22637
|
-
`[workspace "${ws.name}"] repo "${repo.name}" at ${repoPath}: ${msg}`
|
|
22638
|
-
);
|
|
22569
|
+
throw new Error(`[workspace "${ws.name}"] repo "${repo.name}" at ${repoPath}: ${msg}`);
|
|
22639
22570
|
}
|
|
22640
22571
|
if (manifest?.runtime) {
|
|
22641
22572
|
logRuntimeReconciliation(repo.name, manifest.runtime);
|
|
@@ -22657,15 +22588,16 @@ function workspaceToRepoConfigs(ws) {
|
|
|
22657
22588
|
function logRuntimeReconciliation(repoName, runtime) {
|
|
22658
22589
|
const validated = [];
|
|
22659
22590
|
for (const [lang, version2] of Object.entries(runtime)) {
|
|
22660
|
-
if (typeof version2 !== "string" || version2.length === 0)
|
|
22661
|
-
|
|
22591
|
+
if (typeof version2 !== "string" || version2.length === 0)
|
|
22592
|
+
continue;
|
|
22593
|
+
if (!versionStringSchema.safeParse(version2).success)
|
|
22594
|
+
continue;
|
|
22662
22595
|
const safeLang = lang.replace(/[\r\n\x00-\x1f]/g, "?");
|
|
22663
22596
|
validated.push(`${safeLang}=${version2}`);
|
|
22664
22597
|
}
|
|
22665
|
-
if (validated.length === 0)
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
);
|
|
22598
|
+
if (validated.length === 0)
|
|
22599
|
+
return;
|
|
22600
|
+
console.warn(`[manifest] repo "${repoName}" supersedes central stack: ${validated.join(", ")}`);
|
|
22669
22601
|
}
|
|
22670
22602
|
|
|
22671
22603
|
// ../mcp-server/src/tools/workspace.ts
|
|
@@ -24764,18 +24696,18 @@ __export(world_dispatch_exports, {
|
|
|
24764
24696
|
});
|
|
24765
24697
|
import "node:path";
|
|
24766
24698
|
|
|
24767
|
-
// ../core/
|
|
24699
|
+
// ../core/dist/world-paths.js
|
|
24768
24700
|
import * as path8 from "node:path";
|
|
24769
24701
|
var WORLD_DB_FILENAME = "world.db";
|
|
24770
24702
|
function getWorldDbPath(workspacePath) {
|
|
24771
24703
|
return path8.join(workspacePath, WORLD_DB_FILENAME);
|
|
24772
24704
|
}
|
|
24773
24705
|
|
|
24774
|
-
// ../core/
|
|
24706
|
+
// ../core/dist/orchestrator/dispatch.js
|
|
24775
24707
|
var DEEP_MODE_SUFFIX = "\n\nUltrathink and use sub-agents (the Agent tool) to parallelize independent work. Break complex tasks into focused sub-tasks.";
|
|
24776
24708
|
var COMPLEX_PROMPT_RE = /\b(implement|build|create|design|refactor|architect|analyze|research|write.*documentation)\b/i;
|
|
24777
24709
|
|
|
24778
|
-
// ../core/
|
|
24710
|
+
// ../core/dist/thought/local-store.js
|
|
24779
24711
|
import { createRequire as createRequire2 } from "node:module";
|
|
24780
24712
|
var _require2 = createRequire2(import.meta.url);
|
|
24781
24713
|
var _Database2 = null;
|
|
@@ -25007,13 +24939,11 @@ var ThoughtLocalStore = class {
|
|
|
25007
24939
|
}
|
|
25008
24940
|
/** Returns distinct session IDs ordered by earliest appearance. Uses idx_nodes_session. */
|
|
25009
24941
|
getDistinctSessionIds() {
|
|
25010
|
-
const rows = this.db.prepare(
|
|
25011
|
-
`SELECT session_id, MIN(created_at) AS first_at
|
|
24942
|
+
const rows = this.db.prepare(`SELECT session_id, MIN(created_at) AS first_at
|
|
25012
24943
|
FROM thought_nodes
|
|
25013
24944
|
WHERE session_id IS NOT NULL AND session_id != ''
|
|
25014
24945
|
GROUP BY session_id
|
|
25015
|
-
ORDER BY first_at DESC`
|
|
25016
|
-
).all();
|
|
24946
|
+
ORDER BY first_at DESC`).all();
|
|
25017
24947
|
return rows.map((r) => r.session_id);
|
|
25018
24948
|
}
|
|
25019
24949
|
/** Returns all nodes for a given session, ordered by sequence number. */
|
|
@@ -25023,8 +24953,7 @@ var ThoughtLocalStore = class {
|
|
|
25023
24953
|
}
|
|
25024
24954
|
/** Returns per-session aggregates in a single query. */
|
|
25025
24955
|
getSessionAggregates() {
|
|
25026
|
-
const rows = this.db.prepare(
|
|
25027
|
-
`SELECT session_id,
|
|
24956
|
+
const rows = this.db.prepare(`SELECT session_id,
|
|
25028
24957
|
COUNT(*) AS node_count,
|
|
25029
24958
|
MIN(created_at) AS first_at,
|
|
25030
24959
|
MAX(created_at) AS last_at,
|
|
@@ -25032,8 +24961,7 @@ var ThoughtLocalStore = class {
|
|
|
25032
24961
|
FROM thought_nodes
|
|
25033
24962
|
WHERE session_id IS NOT NULL AND session_id != ''
|
|
25034
24963
|
GROUP BY session_id
|
|
25035
|
-
ORDER BY first_at DESC`
|
|
25036
|
-
).all();
|
|
24964
|
+
ORDER BY first_at DESC`).all();
|
|
25037
24965
|
return rows.map((r) => ({
|
|
25038
24966
|
sessionId: r.session_id,
|
|
25039
24967
|
nodeCount: r.node_count,
|
|
@@ -25054,14 +24982,14 @@ var ThoughtLocalStore = class {
|
|
|
25054
24982
|
}
|
|
25055
24983
|
};
|
|
25056
24984
|
|
|
25057
|
-
// ../core/
|
|
24985
|
+
// ../core/dist/world/env-setup.js
|
|
25058
24986
|
import * as crypto2 from "node:crypto";
|
|
25059
24987
|
import * as fs6 from "node:fs";
|
|
25060
24988
|
import * as os6 from "node:os";
|
|
25061
24989
|
import * as path9 from "node:path";
|
|
25062
24990
|
import { globSync } from "node:fs";
|
|
25063
24991
|
|
|
25064
|
-
// ../core/
|
|
24992
|
+
// ../core/dist/thought/hooks-config.js
|
|
25065
24993
|
var DEFAULT_HOOK_SERVER_URL = "http://localhost:8080";
|
|
25066
24994
|
function generateHooksConfig(hookServerUrl = DEFAULT_HOOK_SERVER_URL) {
|
|
25067
24995
|
const makeHookEntry = (hookPath) => [{
|
|
@@ -25133,11 +25061,12 @@ function generateHooksConfig(hookServerUrl = DEFAULT_HOOK_SERVER_URL) {
|
|
|
25133
25061
|
};
|
|
25134
25062
|
}
|
|
25135
25063
|
|
|
25136
|
-
// ../core/
|
|
25064
|
+
// ../core/dist/world/env-setup.js
|
|
25137
25065
|
function copyClaudeConfig(workspacePath, homeDir) {
|
|
25138
25066
|
const sourceClaudeDir = path9.join(homeDir ?? os6.homedir(), ".claude");
|
|
25139
25067
|
const destClaudeDir = path9.join(workspacePath, ".claude-host-config");
|
|
25140
|
-
if (!fs6.existsSync(sourceClaudeDir))
|
|
25068
|
+
if (!fs6.existsSync(sourceClaudeDir))
|
|
25069
|
+
return;
|
|
25141
25070
|
fs6.mkdirSync(destClaudeDir, { recursive: true });
|
|
25142
25071
|
const settingsPath = path9.join(sourceClaudeDir, "settings.json");
|
|
25143
25072
|
let settings = {};
|
|
@@ -25150,10 +25079,7 @@ function copyClaudeConfig(workspacePath, homeDir) {
|
|
|
25150
25079
|
}
|
|
25151
25080
|
const hooksConfig = generateHooksConfig(DEFAULT_HOOK_SERVER_URL);
|
|
25152
25081
|
settings["hooks"] = hooksConfig["hooks"];
|
|
25153
|
-
fs6.writeFileSync(
|
|
25154
|
-
path9.join(destClaudeDir, "settings.json"),
|
|
25155
|
-
JSON.stringify(settings, null, 2)
|
|
25156
|
-
);
|
|
25082
|
+
fs6.writeFileSync(path9.join(destClaudeDir, "settings.json"), JSON.stringify(settings, null, 2));
|
|
25157
25083
|
const claudeMdPath = path9.join(sourceClaudeDir, "CLAUDE.md");
|
|
25158
25084
|
if (fs6.existsSync(claudeMdPath)) {
|
|
25159
25085
|
fs6.copyFileSync(claudeMdPath, path9.join(destClaudeDir, "CLAUDE.md"));
|
|
@@ -25199,14 +25125,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
25199
25125
|
"backups"
|
|
25200
25126
|
]);
|
|
25201
25127
|
function copyDirRecursive(src, dest, depth = 0, skipFiles = /* @__PURE__ */ new Set()) {
|
|
25202
|
-
if (depth > 10)
|
|
25128
|
+
if (depth > 10)
|
|
25129
|
+
return;
|
|
25203
25130
|
fs6.mkdirSync(dest, { recursive: true });
|
|
25204
25131
|
for (const entry of fs6.readdirSync(src, { withFileTypes: true })) {
|
|
25205
25132
|
const { name } = entry;
|
|
25206
|
-
if (skipFiles.has(name))
|
|
25207
|
-
|
|
25208
|
-
if (
|
|
25209
|
-
|
|
25133
|
+
if (skipFiles.has(name))
|
|
25134
|
+
continue;
|
|
25135
|
+
if (SKIP_FILES.has(name))
|
|
25136
|
+
continue;
|
|
25137
|
+
if (DENY_PATTERNS.some((p) => p.test(name)))
|
|
25138
|
+
continue;
|
|
25139
|
+
if (entry.isDirectory() && SKIP_DIRS.has(name))
|
|
25140
|
+
continue;
|
|
25210
25141
|
const srcPath = path9.join(src, name);
|
|
25211
25142
|
const destPath = path9.join(dest, name);
|
|
25212
25143
|
if (entry.isSymbolicLink()) {
|
|
@@ -25217,7 +25148,8 @@ function copyDirRecursive(src, dest, depth = 0, skipFiles = /* @__PURE__ */ new
|
|
|
25217
25148
|
continue;
|
|
25218
25149
|
}
|
|
25219
25150
|
if (stat.isDirectory()) {
|
|
25220
|
-
if (SKIP_DIRS.has(name))
|
|
25151
|
+
if (SKIP_DIRS.has(name))
|
|
25152
|
+
continue;
|
|
25221
25153
|
copyDirRecursive(srcPath, destPath, depth + 1, skipFiles);
|
|
25222
25154
|
} else if (stat.isFile()) {
|
|
25223
25155
|
fs6.copyFileSync(srcPath, destPath);
|
|
@@ -25243,9 +25175,7 @@ async function copyClaudeConfigIntoContainer(containerName) {
|
|
|
25243
25175
|
dockerExec("test -d /home/olam/workspace/.claude-host-config/skills && cp -r /home/olam/workspace/.claude-host-config/skills/* /home/olam/.claude/skills/ 2>/dev/null || true");
|
|
25244
25176
|
dockerExec("mkdir -p /home/olam/.claude/scripts");
|
|
25245
25177
|
dockerExec("test -d /home/olam/workspace/.claude-host-config/scripts && cp -r /home/olam/workspace/.claude-host-config/scripts/* /home/olam/.claude/scripts/ 2>/dev/null || true");
|
|
25246
|
-
dockerExec(
|
|
25247
|
-
'OWNER="$(stat -c %U:%G /home/olam/.claude 2>/dev/null || echo olam:olam)"; chown -R "$OWNER" /home/olam/.claude 2>/dev/null || true'
|
|
25248
|
-
);
|
|
25178
|
+
dockerExec('OWNER="$(stat -c %U:%G /home/olam/.claude 2>/dev/null || echo olam:olam)"; chown -R "$OWNER" /home/olam/.claude 2>/dev/null || true');
|
|
25249
25179
|
await sanitizeContainerClaudeHooks(containerName);
|
|
25250
25180
|
}
|
|
25251
25181
|
async function sanitizeContainerClaudeHooks(containerName) {
|
|
@@ -25293,10 +25223,7 @@ if (changed) {
|
|
|
25293
25223
|
}
|
|
25294
25224
|
`;
|
|
25295
25225
|
try {
|
|
25296
|
-
execSync6(
|
|
25297
|
-
`docker exec ${containerName} /usr/local/bin/node -e ${shQuote(script)}`,
|
|
25298
|
-
{ stdio: "pipe" }
|
|
25299
|
-
);
|
|
25226
|
+
execSync6(`docker exec ${containerName} /usr/local/bin/node -e ${shQuote(script)}`, { stdio: "pipe" });
|
|
25300
25227
|
} catch {
|
|
25301
25228
|
}
|
|
25302
25229
|
}
|
|
@@ -25350,25 +25277,17 @@ function sanitizeEnvKeyValue(repoName, source, raw) {
|
|
|
25350
25277
|
clean[k] = v;
|
|
25351
25278
|
}
|
|
25352
25279
|
if (rejectedKeys.length > 0) {
|
|
25353
|
-
console.warn(
|
|
25354
|
-
`[env] repo "${repoName}" (${source}): dropped ${rejectedKeys.length} entr${rejectedKeys.length === 1 ? "y" : "ies"} with invalid key shape (must match /^[A-Z_][A-Z0-9_]*$/): ${rejectedKeys.join(", ")}`
|
|
25355
|
-
);
|
|
25280
|
+
console.warn(`[env] repo "${repoName}" (${source}): dropped ${rejectedKeys.length} entr${rejectedKeys.length === 1 ? "y" : "ies"} with invalid key shape (must match /^[A-Z_][A-Z0-9_]*$/): ${rejectedKeys.join(", ")}`);
|
|
25356
25281
|
}
|
|
25357
25282
|
if (rejectedValues.length > 0) {
|
|
25358
|
-
console.warn(
|
|
25359
|
-
`[env] repo "${repoName}" (${source}): dropped ${rejectedValues.length} entr${rejectedValues.length === 1 ? "y" : "ies"} with control chars in value: ${rejectedValues.join(", ")}`
|
|
25360
|
-
);
|
|
25283
|
+
console.warn(`[env] repo "${repoName}" (${source}): dropped ${rejectedValues.length} entr${rejectedValues.length === 1 ? "y" : "ies"} with control chars in value: ${rejectedValues.join(", ")}`);
|
|
25361
25284
|
}
|
|
25362
25285
|
return { clean, rejectedKeys, rejectedValues };
|
|
25363
25286
|
}
|
|
25364
25287
|
function buildRepoEnvBundle(repoName, serviceEnv, crossRepoEnv, envOverrides) {
|
|
25365
25288
|
const injectables = {};
|
|
25366
25289
|
if (crossRepoEnv) {
|
|
25367
|
-
const { clean: sanitizedManifest } = sanitizeEnvKeyValue(
|
|
25368
|
-
repoName,
|
|
25369
|
-
"manifest",
|
|
25370
|
-
crossRepoEnv
|
|
25371
|
-
);
|
|
25290
|
+
const { clean: sanitizedManifest } = sanitizeEnvKeyValue(repoName, "manifest", crossRepoEnv);
|
|
25372
25291
|
const droppedProtected = [];
|
|
25373
25292
|
for (const [k, v] of Object.entries(sanitizedManifest)) {
|
|
25374
25293
|
if (PROTECTED_ENV_KEY_SET.has(k)) {
|
|
@@ -25378,18 +25297,10 @@ function buildRepoEnvBundle(repoName, serviceEnv, crossRepoEnv, envOverrides) {
|
|
|
25378
25297
|
injectables[k] = v;
|
|
25379
25298
|
}
|
|
25380
25299
|
if (droppedProtected.length > 0) {
|
|
25381
|
-
console.warn(
|
|
25382
|
-
`[env] repo "${repoName}": manifest tried to set protected key(s) ${droppedProtected.join(
|
|
25383
|
-
", "
|
|
25384
|
-
)}; service-discovery value(s) preserved`
|
|
25385
|
-
);
|
|
25300
|
+
console.warn(`[env] repo "${repoName}": manifest tried to set protected key(s) ${droppedProtected.join(", ")}; service-discovery value(s) preserved`);
|
|
25386
25301
|
}
|
|
25387
25302
|
}
|
|
25388
|
-
const { clean: sanitizedOverrides } = sanitizeEnvKeyValue(
|
|
25389
|
-
repoName,
|
|
25390
|
-
"overrides",
|
|
25391
|
-
envOverrides
|
|
25392
|
-
);
|
|
25303
|
+
const { clean: sanitizedOverrides } = sanitizeEnvKeyValue(repoName, "overrides", envOverrides);
|
|
25393
25304
|
const overrideProtected = [];
|
|
25394
25305
|
for (const [k, v] of Object.entries(sanitizedOverrides)) {
|
|
25395
25306
|
if (PROTECTED_ENV_KEY_SET.has(k)) {
|
|
@@ -25398,11 +25309,7 @@ function buildRepoEnvBundle(repoName, serviceEnv, crossRepoEnv, envOverrides) {
|
|
|
25398
25309
|
injectables[k] = v;
|
|
25399
25310
|
}
|
|
25400
25311
|
if (overrideProtected.length > 0) {
|
|
25401
|
-
console.warn(
|
|
25402
|
-
`[env] repo "${repoName}": env_overrides set protected key(s) ${overrideProtected.join(
|
|
25403
|
-
", "
|
|
25404
|
-
)} \u2014 operator override wins, but verify this is intentional (service discovery may break)`
|
|
25405
|
-
);
|
|
25312
|
+
console.warn(`[env] repo "${repoName}": env_overrides set protected key(s) ${overrideProtected.join(", ")} \u2014 operator override wins, but verify this is intentional (service discovery may break)`);
|
|
25406
25313
|
}
|
|
25407
25314
|
const merged = { ...serviceEnv, ...injectables };
|
|
25408
25315
|
return { merged, injectables };
|
|
@@ -25415,16 +25322,13 @@ function setupWorldEnv(repos, workspacePath, serviceEnv, crossRepoEnv = {}) {
|
|
|
25415
25322
|
for (const repo of repos) {
|
|
25416
25323
|
const worktreePath = path9.join(workspacePath, repo.name);
|
|
25417
25324
|
const sourcePath = repo.path;
|
|
25418
|
-
if (!sourcePath || !fs6.existsSync(worktreePath))
|
|
25325
|
+
if (!sourcePath || !fs6.existsSync(worktreePath))
|
|
25326
|
+
continue;
|
|
25419
25327
|
const envSetup = repo.env_setup;
|
|
25420
|
-
if (!envSetup)
|
|
25328
|
+
if (!envSetup)
|
|
25329
|
+
continue;
|
|
25421
25330
|
const repoCrossEnv = crossRepoEnv[repo.name];
|
|
25422
|
-
const { merged, injectables } = buildRepoEnvBundle(
|
|
25423
|
-
repo.name,
|
|
25424
|
-
serviceEnv,
|
|
25425
|
-
repoCrossEnv,
|
|
25426
|
-
envSetup.env_overrides
|
|
25427
|
-
);
|
|
25331
|
+
const { merged, injectables } = buildRepoEnvBundle(repo.name, serviceEnv, repoCrossEnv, envSetup.env_overrides);
|
|
25428
25332
|
for (const pattern of envSetup.credential_patterns) {
|
|
25429
25333
|
copyMatchingFiles(sourcePath, worktreePath, pattern);
|
|
25430
25334
|
}
|
|
@@ -25462,10 +25366,12 @@ function generateEnvFromExample(repoPath, overrides) {
|
|
|
25462
25366
|
const exampleFiles = [".env.example", ".env.sample", ".env.local.example"];
|
|
25463
25367
|
for (const exampleName of exampleFiles) {
|
|
25464
25368
|
const examplePath = path9.join(repoPath, exampleName);
|
|
25465
|
-
if (!fs6.existsSync(examplePath))
|
|
25369
|
+
if (!fs6.existsSync(examplePath))
|
|
25370
|
+
continue;
|
|
25466
25371
|
const targetName = exampleName.replace(".example", "").replace(".sample", "");
|
|
25467
25372
|
const targetPath = path9.join(repoPath, targetName);
|
|
25468
|
-
if (fs6.existsSync(targetPath))
|
|
25373
|
+
if (fs6.existsSync(targetPath))
|
|
25374
|
+
continue;
|
|
25469
25375
|
const template = fs6.readFileSync(examplePath, "utf-8");
|
|
25470
25376
|
const generated = template.split("\n").map((line) => {
|
|
25471
25377
|
const match = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line);
|
|
@@ -25484,9 +25390,7 @@ function applyEnvOverrides(repoPath, overrides) {
|
|
|
25484
25390
|
const envPath = path9.join(repoPath, ".env");
|
|
25485
25391
|
if (fs6.existsSync(envPath)) {
|
|
25486
25392
|
const existing = fs6.readFileSync(envPath, "utf-8");
|
|
25487
|
-
const existingKeys = new Set(
|
|
25488
|
-
existing.split("\n").map((l) => l.split("=")[0]?.trim()).filter(Boolean)
|
|
25489
|
-
);
|
|
25393
|
+
const existingKeys = new Set(existing.split("\n").map((l) => l.split("=")[0]?.trim()).filter(Boolean));
|
|
25490
25394
|
const additions = [];
|
|
25491
25395
|
for (const [key, value] of Object.entries(overrides)) {
|
|
25492
25396
|
if (!existingKeys.has(key)) {
|
|
@@ -25658,7 +25562,7 @@ __export(world_enter_exports, {
|
|
|
25658
25562
|
register: () => register11
|
|
25659
25563
|
});
|
|
25660
25564
|
|
|
25661
|
-
// ../core/
|
|
25565
|
+
// ../core/dist/orchestrator/enter.js
|
|
25662
25566
|
function getEnterCommand(worldId, containerId, provider, sshHost) {
|
|
25663
25567
|
if (provider === "docker") {
|
|
25664
25568
|
const command = `docker exec -it ${containerId} claude`;
|
|
@@ -25753,7 +25657,7 @@ __export(world_crystallize_exports, {
|
|
|
25753
25657
|
import "node:path";
|
|
25754
25658
|
import * as fs7 from "node:fs";
|
|
25755
25659
|
|
|
25756
|
-
// ../core/
|
|
25660
|
+
// ../core/dist/crystallize/checksum.js
|
|
25757
25661
|
import { createHash as createHash2 } from "node:crypto";
|
|
25758
25662
|
function computeGraphChecksum(nodes, edges) {
|
|
25759
25663
|
const sortedNodes = [...nodes].sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -27711,7 +27615,7 @@ __export(create_from_prompt_exports, {
|
|
|
27711
27615
|
register: () => register21
|
|
27712
27616
|
});
|
|
27713
27617
|
|
|
27714
|
-
// ../core/
|
|
27618
|
+
// ../core/dist/world/infer-repos.js
|
|
27715
27619
|
var PICKER_CONFIDENCE_THRESHOLD = 0.7;
|
|
27716
27620
|
var TOKEN_REGEX = /\b[a-z][a-z0-9]*(?:[-_][a-z0-9]+)+\b/g;
|
|
27717
27621
|
function extractCandidates(prompt) {
|
|
@@ -27760,7 +27664,7 @@ function inferRepos(input) {
|
|
|
27760
27664
|
};
|
|
27761
27665
|
}
|
|
27762
27666
|
|
|
27763
|
-
// ../core/
|
|
27667
|
+
// ../core/dist/world/match-workspace.js
|
|
27764
27668
|
var SUBSET_MATCH_RATIO = 0.5;
|
|
27765
27669
|
function decideWorkspaceMatch(input) {
|
|
27766
27670
|
if (input.exactMatches.length === 1 && input.exactMatches[0]) {
|
|
@@ -27790,16 +27694,14 @@ function decideWorkspaceMatch(input) {
|
|
|
27790
27694
|
for (const ws of input.fullCatalog) {
|
|
27791
27695
|
const wsProjectsLower = ws.projects.map((p) => p.toLowerCase());
|
|
27792
27696
|
const wsProjectSet = new Set(wsProjectsLower);
|
|
27793
|
-
const matched = input.inferredRepos.filter(
|
|
27794
|
-
|
|
27795
|
-
|
|
27796
|
-
if (matched.length === 0) continue;
|
|
27697
|
+
const matched = input.inferredRepos.filter((r) => wsProjectSet.has(r.toLowerCase()));
|
|
27698
|
+
if (matched.length === 0)
|
|
27699
|
+
continue;
|
|
27797
27700
|
const ratio = matched.length / Math.max(1, wsProjectsLower.length);
|
|
27798
|
-
if (ratio < SUBSET_MATCH_RATIO)
|
|
27701
|
+
if (ratio < SUBSET_MATCH_RATIO)
|
|
27702
|
+
continue;
|
|
27799
27703
|
const confidence = 0.5 + 0.4 * ratio;
|
|
27800
|
-
const extraRepos = input.inferredRepos.filter(
|
|
27801
|
-
(r) => !wsProjectSet.has(r.toLowerCase())
|
|
27802
|
-
);
|
|
27704
|
+
const extraRepos = input.inferredRepos.filter((r) => !wsProjectSet.has(r.toLowerCase()));
|
|
27803
27705
|
subsetCandidates.push({
|
|
27804
27706
|
workspace: ws,
|
|
27805
27707
|
confidence,
|
|
@@ -27808,7 +27710,8 @@ function decideWorkspaceMatch(input) {
|
|
|
27808
27710
|
});
|
|
27809
27711
|
}
|
|
27810
27712
|
subsetCandidates.sort((a, b) => {
|
|
27811
|
-
if (b.confidence !== a.confidence)
|
|
27713
|
+
if (b.confidence !== a.confidence)
|
|
27714
|
+
return b.confidence - a.confidence;
|
|
27812
27715
|
return a.workspace.name.localeCompare(b.workspace.name);
|
|
27813
27716
|
});
|
|
27814
27717
|
if (subsetCandidates.length === 1) {
|
|
@@ -27837,7 +27740,7 @@ function decideWorkspaceMatch(input) {
|
|
|
27837
27740
|
};
|
|
27838
27741
|
}
|
|
27839
27742
|
|
|
27840
|
-
// ../core/
|
|
27743
|
+
// ../core/dist/util/open-url.js
|
|
27841
27744
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
27842
27745
|
function openUrl(url) {
|
|
27843
27746
|
if (process.env.CI === "true") {
|
|
@@ -28166,12 +28069,12 @@ function createServer3(ctx, initError) {
|
|
|
28166
28069
|
return server;
|
|
28167
28070
|
}
|
|
28168
28071
|
|
|
28169
|
-
// ../core/
|
|
28072
|
+
// ../core/dist/config/loader.js
|
|
28170
28073
|
import * as fs9 from "node:fs";
|
|
28171
28074
|
import * as path12 from "node:path";
|
|
28172
28075
|
import { parse as parseYaml2 } from "yaml";
|
|
28173
28076
|
|
|
28174
|
-
// ../core/
|
|
28077
|
+
// ../core/dist/config/schema.js
|
|
28175
28078
|
var repoTypeSchema = external_exports.enum(["rails", "node", "python", "generic"]);
|
|
28176
28079
|
var envSetupSchema = external_exports.object({
|
|
28177
28080
|
credential_patterns: external_exports.array(external_exports.string()).optional().default([]),
|
|
@@ -28216,10 +28119,7 @@ var ServiceConfigSchema = external_exports.object({
|
|
|
28216
28119
|
// optional <variant>. Rejects empty string explicitly — dockerode silently
|
|
28217
28120
|
// falls back to host arch when Platform is empty, which would silently
|
|
28218
28121
|
// recreate the very wrong-arch bug T11 exists to prevent.
|
|
28219
|
-
platform: external_exports.string().regex(
|
|
28220
|
-
/^[a-z0-9]+\/[a-z0-9_]+(?:\/v[0-9]+)?$/,
|
|
28221
|
-
"platform must match OCI shape, e.g. linux/amd64, linux/arm64, linux/arm64/v8"
|
|
28222
|
-
).optional()
|
|
28122
|
+
platform: external_exports.string().regex(/^[a-z0-9]+\/[a-z0-9_]+(?:\/v[0-9]+)?$/, "platform must match OCI shape, e.g. linux/amd64, linux/arm64, linux/arm64/v8").optional()
|
|
28223
28123
|
});
|
|
28224
28124
|
var sshHostSchema = external_exports.object({
|
|
28225
28125
|
host: external_exports.string().min(1),
|
|
@@ -28239,8 +28139,10 @@ function isSecureOrLocalhost(value) {
|
|
|
28239
28139
|
} catch {
|
|
28240
28140
|
return false;
|
|
28241
28141
|
}
|
|
28242
|
-
if (parsed.protocol === "https:")
|
|
28243
|
-
|
|
28142
|
+
if (parsed.protocol === "https:")
|
|
28143
|
+
return true;
|
|
28144
|
+
if (parsed.protocol !== "http:")
|
|
28145
|
+
return false;
|
|
28244
28146
|
return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]" || parsed.hostname === "::1";
|
|
28245
28147
|
}
|
|
28246
28148
|
var computeProvidersSchema = external_exports.object({
|
|
@@ -28297,9 +28199,7 @@ var PleriConfigSchema = external_exports.object({
|
|
|
28297
28199
|
var authModeSchema = external_exports.enum(["api_key", "oauth"]);
|
|
28298
28200
|
var AuthConfigSchema = external_exports.object({
|
|
28299
28201
|
mode: authModeSchema.default("oauth"),
|
|
28300
|
-
autoInject: external_exports.boolean().default(true).describe(
|
|
28301
|
-
"Copy host credentials into world at creation. Set false to use browser OAuth instead."
|
|
28302
|
-
)
|
|
28202
|
+
autoInject: external_exports.boolean().default(true).describe("Copy host credentials into world at creation. Set false to use browser OAuth instead.")
|
|
28303
28203
|
});
|
|
28304
28204
|
var DevboxRegistrySchema = external_exports.object({
|
|
28305
28205
|
/**
|
|
@@ -28331,14 +28231,9 @@ var DevboxImageSelectorSchema = external_exports.object({
|
|
|
28331
28231
|
* from corrupting the amd64 container's mise installs.
|
|
28332
28232
|
*/
|
|
28333
28233
|
cache_arch: external_exports.enum(["x64", "arm64"]).optional()
|
|
28334
|
-
}).refine(
|
|
28335
|
-
|
|
28336
|
-
|
|
28337
|
-
),
|
|
28338
|
-
{
|
|
28339
|
-
message: "selector must declare at least one of requires_all, requires_any, or requires_type"
|
|
28340
|
-
}
|
|
28341
|
-
);
|
|
28234
|
+
}).refine((s) => Boolean(s.requires_all && s.requires_all.length > 0 || s.requires_any && s.requires_any.length > 0 || s.requires_type), {
|
|
28235
|
+
message: "selector must declare at least one of requires_all, requires_any, or requires_type"
|
|
28236
|
+
});
|
|
28342
28237
|
var DevboxConfigSchema = external_exports.object({
|
|
28343
28238
|
registry: DevboxRegistrySchema.optional(),
|
|
28344
28239
|
/**
|
|
@@ -28370,12 +28265,12 @@ var OlamConfigSchema = external_exports.object({
|
|
|
28370
28265
|
devbox: DevboxConfigSchema.optional()
|
|
28371
28266
|
});
|
|
28372
28267
|
|
|
28373
|
-
// ../core/
|
|
28268
|
+
// ../core/dist/config/defaults.js
|
|
28374
28269
|
var CONFIG_DIR_NAME = ".olam";
|
|
28375
28270
|
var CONFIG_FILENAME = "config.yaml";
|
|
28376
28271
|
var LEGACY_CONFIG_FILENAME = "olam.yaml";
|
|
28377
28272
|
|
|
28378
|
-
// ../core/
|
|
28273
|
+
// ../core/dist/config/errors.js
|
|
28379
28274
|
var OlamConfigNotFoundError = class extends Error {
|
|
28380
28275
|
code = "CONFIG_NOT_FOUND";
|
|
28381
28276
|
searchedPaths;
|
|
@@ -28435,7 +28330,7 @@ var OlamConfigEnvError = class extends Error {
|
|
|
28435
28330
|
}
|
|
28436
28331
|
};
|
|
28437
28332
|
|
|
28438
|
-
// ../core/
|
|
28333
|
+
// ../core/dist/config/substitute.js
|
|
28439
28334
|
var ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;
|
|
28440
28335
|
function isCrossReference(expr) {
|
|
28441
28336
|
return expr.includes(".");
|
|
@@ -28458,10 +28353,7 @@ function resolveExpression(expr) {
|
|
|
28458
28353
|
return value;
|
|
28459
28354
|
}
|
|
28460
28355
|
function substituteString(input) {
|
|
28461
|
-
return input.replace(
|
|
28462
|
-
ENV_VAR_PATTERN,
|
|
28463
|
-
(_, expr) => resolveExpression(expr)
|
|
28464
|
-
);
|
|
28356
|
+
return input.replace(ENV_VAR_PATTERN, (_, expr) => resolveExpression(expr));
|
|
28465
28357
|
}
|
|
28466
28358
|
function substituteEnvVars(obj) {
|
|
28467
28359
|
if (typeof obj === "string") {
|
|
@@ -28480,16 +28372,19 @@ function substituteEnvVars(obj) {
|
|
|
28480
28372
|
return obj;
|
|
28481
28373
|
}
|
|
28482
28374
|
|
|
28483
|
-
// ../core/
|
|
28375
|
+
// ../core/dist/config/dotenv.js
|
|
28484
28376
|
import * as fs8 from "node:fs";
|
|
28485
28377
|
function loadDotEnv(envPath) {
|
|
28486
|
-
if (!fs8.existsSync(envPath))
|
|
28378
|
+
if (!fs8.existsSync(envPath))
|
|
28379
|
+
return;
|
|
28487
28380
|
const content = fs8.readFileSync(envPath, "utf-8");
|
|
28488
28381
|
for (const line of content.split("\n")) {
|
|
28489
28382
|
const trimmed = line.trim();
|
|
28490
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
28383
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
28384
|
+
continue;
|
|
28491
28385
|
const eqIndex = trimmed.indexOf("=");
|
|
28492
|
-
if (eqIndex === -1)
|
|
28386
|
+
if (eqIndex === -1)
|
|
28387
|
+
continue;
|
|
28493
28388
|
const key = trimmed.slice(0, eqIndex).trim();
|
|
28494
28389
|
const value = trimmed.slice(eqIndex + 1).trim().replace(/^["']|["']$/g, "");
|
|
28495
28390
|
if (process.env[key] === void 0) {
|
|
@@ -28498,7 +28393,7 @@ function loadDotEnv(envPath) {
|
|
|
28498
28393
|
}
|
|
28499
28394
|
}
|
|
28500
28395
|
|
|
28501
|
-
// ../core/
|
|
28396
|
+
// ../core/dist/config/loader.js
|
|
28502
28397
|
function findConfigFile(startDir) {
|
|
28503
28398
|
const searched = [];
|
|
28504
28399
|
let current = path12.resolve(startDir);
|
|
@@ -28513,7 +28408,8 @@ function findConfigFile(startDir) {
|
|
|
28513
28408
|
return { path: legacyLayout, isLegacy: true };
|
|
28514
28409
|
}
|
|
28515
28410
|
const parent = path12.dirname(current);
|
|
28516
|
-
if (parent === current)
|
|
28411
|
+
if (parent === current)
|
|
28412
|
+
break;
|
|
28517
28413
|
current = parent;
|
|
28518
28414
|
}
|
|
28519
28415
|
throw new OlamConfigNotFoundError(searched);
|
|
@@ -28522,11 +28418,9 @@ function loadConfig(startDir) {
|
|
|
28522
28418
|
const dir = startDir ?? process.cwd();
|
|
28523
28419
|
const found = findConfigFile(dir);
|
|
28524
28420
|
if (found.isLegacy) {
|
|
28525
|
-
process.stderr.write(
|
|
28526
|
-
`[olam] DEPRECATED: found legacy config at ${found.path}
|
|
28421
|
+
process.stderr.write(`[olam] DEPRECATED: found legacy config at ${found.path}
|
|
28527
28422
|
Run /olam:init to migrate to .olam/config.yaml
|
|
28528
|
-
`
|
|
28529
|
-
);
|
|
28423
|
+
`);
|
|
28530
28424
|
} else {
|
|
28531
28425
|
const envPath = path12.join(path12.dirname(found.path), ".env");
|
|
28532
28426
|
loadDotEnv(envPath);
|
|
@@ -28535,9 +28429,7 @@ function loadConfig(startDir) {
|
|
|
28535
28429
|
try {
|
|
28536
28430
|
rawContent = fs9.readFileSync(found.path, "utf-8");
|
|
28537
28431
|
} catch (err) {
|
|
28538
|
-
throw new Error(
|
|
28539
|
-
`Failed to read ${found.path}: ${err instanceof Error ? err.message : String(err)}`
|
|
28540
|
-
);
|
|
28432
|
+
throw new Error(`Failed to read ${found.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
28541
28433
|
}
|
|
28542
28434
|
let parsed;
|
|
28543
28435
|
try {
|
|
@@ -28547,9 +28439,7 @@ function loadConfig(startDir) {
|
|
|
28547
28439
|
throw new Error(`Invalid YAML in ${found.path}: ${message}`);
|
|
28548
28440
|
}
|
|
28549
28441
|
if (parsed === null || parsed === void 0 || typeof parsed !== "object") {
|
|
28550
|
-
throw new Error(
|
|
28551
|
-
`Invalid YAML in ${found.path}: expected an object at the document root`
|
|
28552
|
-
);
|
|
28442
|
+
throw new Error(`Invalid YAML in ${found.path}: expected an object at the document root`);
|
|
28553
28443
|
}
|
|
28554
28444
|
const substituted = substituteEnvVars(parsed);
|
|
28555
28445
|
try {
|
|
@@ -28566,14 +28456,14 @@ function loadConfig(startDir) {
|
|
|
28566
28456
|
}
|
|
28567
28457
|
}
|
|
28568
28458
|
|
|
28569
|
-
// ../core/
|
|
28570
|
-
import "node:crypto";
|
|
28459
|
+
// ../core/dist/world/manager.js
|
|
28460
|
+
import * as crypto4 from "node:crypto";
|
|
28571
28461
|
import { execSync as execSync4 } from "node:child_process";
|
|
28572
28462
|
import * as fs14 from "node:fs";
|
|
28573
28463
|
import * as os8 from "node:os";
|
|
28574
28464
|
import * as path18 from "node:path";
|
|
28575
28465
|
|
|
28576
|
-
// ../core/
|
|
28466
|
+
// ../core/dist/world/state.js
|
|
28577
28467
|
var VALID_TRANSITIONS = {
|
|
28578
28468
|
creating: ["running", "error"],
|
|
28579
28469
|
running: ["paused", "crystallizing", "destroyed", "error"],
|
|
@@ -28583,12 +28473,12 @@ var VALID_TRANSITIONS = {
|
|
|
28583
28473
|
error: ["destroyed"]
|
|
28584
28474
|
};
|
|
28585
28475
|
var WorldStateMachine = class {
|
|
28476
|
+
worldId;
|
|
28477
|
+
_state;
|
|
28586
28478
|
constructor(worldId, initialState) {
|
|
28587
28479
|
this.worldId = worldId;
|
|
28588
28480
|
this._state = initialState;
|
|
28589
28481
|
}
|
|
28590
|
-
worldId;
|
|
28591
|
-
_state;
|
|
28592
28482
|
get state() {
|
|
28593
28483
|
return this._state;
|
|
28594
28484
|
}
|
|
@@ -28598,18 +28488,17 @@ var WorldStateMachine = class {
|
|
|
28598
28488
|
}
|
|
28599
28489
|
transition(to) {
|
|
28600
28490
|
if (!this.canTransition(to)) {
|
|
28601
|
-
throw new Error(
|
|
28602
|
-
`Invalid state transition for world "${this.worldId}": ${this._state} \u2192 ${to}`
|
|
28603
|
-
);
|
|
28491
|
+
throw new Error(`Invalid state transition for world "${this.worldId}": ${this._state} \u2192 ${to}`);
|
|
28604
28492
|
}
|
|
28605
28493
|
this._state = to;
|
|
28606
28494
|
}
|
|
28607
28495
|
};
|
|
28608
28496
|
|
|
28609
|
-
// ../core/
|
|
28497
|
+
// ../core/dist/world/devbox-image.js
|
|
28610
28498
|
function selectDevboxImage(config2, repos) {
|
|
28611
28499
|
const selectors = config2.devbox?.image_selectors;
|
|
28612
|
-
if (!selectors || selectors.length === 0)
|
|
28500
|
+
if (!selectors || selectors.length === 0)
|
|
28501
|
+
return void 0;
|
|
28613
28502
|
const repoNames = new Set(repos.map((r) => r.name));
|
|
28614
28503
|
const repoTypes = new Set(repos.map((r) => r.type));
|
|
28615
28504
|
for (const sel of selectors) {
|
|
@@ -28625,19 +28514,23 @@ function selectDevboxImage(config2, repos) {
|
|
|
28625
28514
|
}
|
|
28626
28515
|
function matches(sel, repoNames, repoTypes) {
|
|
28627
28516
|
if (sel.requires_all && sel.requires_all.length > 0) {
|
|
28628
|
-
if (!sel.requires_all.every((n) => repoNames.has(n)))
|
|
28517
|
+
if (!sel.requires_all.every((n) => repoNames.has(n)))
|
|
28518
|
+
return false;
|
|
28629
28519
|
}
|
|
28630
28520
|
if (sel.requires_any && sel.requires_any.length > 0) {
|
|
28631
|
-
if (!sel.requires_any.some((n) => repoNames.has(n)))
|
|
28521
|
+
if (!sel.requires_any.some((n) => repoNames.has(n)))
|
|
28522
|
+
return false;
|
|
28632
28523
|
}
|
|
28633
28524
|
if (sel.requires_type) {
|
|
28634
|
-
if (!repoTypes.has(sel.requires_type))
|
|
28525
|
+
if (!repoTypes.has(sel.requires_type))
|
|
28526
|
+
return false;
|
|
28635
28527
|
}
|
|
28636
28528
|
return true;
|
|
28637
28529
|
}
|
|
28638
28530
|
function resolveDevboxImage(config2, tag) {
|
|
28639
28531
|
const registry2 = config2.devbox?.registry;
|
|
28640
|
-
if (!registry2)
|
|
28532
|
+
if (!registry2)
|
|
28533
|
+
return `olam-devbox:${tag}`;
|
|
28641
28534
|
if (registry2.tags && registry2.tags[tag]) {
|
|
28642
28535
|
return registry2.tags[tag];
|
|
28643
28536
|
}
|
|
@@ -28648,7 +28541,7 @@ function resolveDevboxImage(config2, tag) {
|
|
|
28648
28541
|
return `olam-devbox:${tag}`;
|
|
28649
28542
|
}
|
|
28650
28543
|
|
|
28651
|
-
// ../core/
|
|
28544
|
+
// ../core/dist/world/worktree.js
|
|
28652
28545
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
28653
28546
|
import * as fs10 from "node:fs";
|
|
28654
28547
|
import * as path13 from "node:path";
|
|
@@ -28656,9 +28549,7 @@ function resolveGitDir(repo) {
|
|
|
28656
28549
|
if (repo.path) {
|
|
28657
28550
|
return repo.path;
|
|
28658
28551
|
}
|
|
28659
|
-
throw new Error(
|
|
28660
|
-
`Repo "${repo.name}" has no local path. Clone from ${repo.url} first.`
|
|
28661
|
-
);
|
|
28552
|
+
throw new Error(`Repo "${repo.name}" has no local path. Clone from ${repo.url} first.`);
|
|
28662
28553
|
}
|
|
28663
28554
|
async function createWorktrees(repos, worldId, workspacePath, branch) {
|
|
28664
28555
|
const created = [];
|
|
@@ -28694,9 +28585,7 @@ async function createWorktrees(repos, worldId, workspacePath, branch) {
|
|
|
28694
28585
|
}
|
|
28695
28586
|
}
|
|
28696
28587
|
const message = err instanceof Error ? err.message : String(err);
|
|
28697
|
-
throw new Error(
|
|
28698
|
-
`Failed to create worktree for repo "${repo.name}": ${message}`
|
|
28699
|
-
);
|
|
28588
|
+
throw new Error(`Failed to create worktree for repo "${repo.name}": ${message}`);
|
|
28700
28589
|
}
|
|
28701
28590
|
}
|
|
28702
28591
|
}
|
|
@@ -28746,11 +28635,7 @@ function removeBranch(repo, branch) {
|
|
|
28746
28635
|
}
|
|
28747
28636
|
let hasUpstream = false;
|
|
28748
28637
|
try {
|
|
28749
|
-
execFileSync2(
|
|
28750
|
-
"git",
|
|
28751
|
-
["rev-parse", "--abbrev-ref", `${branch}@{upstream}`],
|
|
28752
|
-
{ cwd: gitDir, stdio: "pipe" }
|
|
28753
|
-
);
|
|
28638
|
+
execFileSync2("git", ["rev-parse", "--abbrev-ref", `${branch}@{upstream}`], { cwd: gitDir, stdio: "pipe" });
|
|
28754
28639
|
hasUpstream = true;
|
|
28755
28640
|
} catch {
|
|
28756
28641
|
}
|
|
@@ -28759,11 +28644,7 @@ function removeBranch(repo, branch) {
|
|
|
28759
28644
|
}
|
|
28760
28645
|
let localCommitCount = 0;
|
|
28761
28646
|
try {
|
|
28762
|
-
const output = execFileSync2(
|
|
28763
|
-
"git",
|
|
28764
|
-
["rev-list", "--count", branch, "--not", "--remotes"],
|
|
28765
|
-
{ cwd: gitDir, stdio: "pipe" }
|
|
28766
|
-
).toString().trim();
|
|
28647
|
+
const output = execFileSync2("git", ["rev-list", "--count", branch, "--not", "--remotes"], { cwd: gitDir, stdio: "pipe" }).toString().trim();
|
|
28767
28648
|
localCommitCount = parseInt(output, 10) || 0;
|
|
28768
28649
|
} catch {
|
|
28769
28650
|
localCommitCount = 1;
|
|
@@ -28778,14 +28659,12 @@ function removeBranch(repo, branch) {
|
|
|
28778
28659
|
}
|
|
28779
28660
|
return { branch, action: "deleted" };
|
|
28780
28661
|
}
|
|
28781
|
-
process.stderr.write(
|
|
28782
|
-
|
|
28783
|
-
`
|
|
28784
|
-
);
|
|
28662
|
+
process.stderr.write(`[olam] Branch "${branch}" has ${localCommitCount} unpushed commit(s). Keeping branch \u2014 push or merge before re-creating a world with the same branch name.
|
|
28663
|
+
`);
|
|
28785
28664
|
return { branch, action: "kept-local-commits", localCommitCount };
|
|
28786
28665
|
}
|
|
28787
28666
|
|
|
28788
|
-
// ../core/
|
|
28667
|
+
// ../core/dist/world/baseline-diff.js
|
|
28789
28668
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
28790
28669
|
import * as fs11 from "node:fs";
|
|
28791
28670
|
import * as os7 from "node:os";
|
|
@@ -28799,12 +28678,14 @@ function sanitizeRepoFilename(name) {
|
|
|
28799
28678
|
return sanitized.length > 0 ? sanitized : "_";
|
|
28800
28679
|
}
|
|
28801
28680
|
function isOversizeError(err) {
|
|
28802
|
-
if (!err || typeof err !== "object")
|
|
28681
|
+
if (!err || typeof err !== "object")
|
|
28682
|
+
return false;
|
|
28803
28683
|
const code = err.code;
|
|
28804
28684
|
return code === "ENOBUFS";
|
|
28805
28685
|
}
|
|
28806
28686
|
function isNoHeadError(err) {
|
|
28807
|
-
if (!err || typeof err !== "object")
|
|
28687
|
+
if (!err || typeof err !== "object")
|
|
28688
|
+
return false;
|
|
28808
28689
|
const msg = err.message ?? "";
|
|
28809
28690
|
const stderr = String(err.stderr ?? "");
|
|
28810
28691
|
const blob = `${msg}
|
|
@@ -28825,7 +28706,8 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
|
28825
28706
|
const perRepo = [];
|
|
28826
28707
|
let total = 0;
|
|
28827
28708
|
for (const repo of repos) {
|
|
28828
|
-
if (!repo.path)
|
|
28709
|
+
if (!repo.path)
|
|
28710
|
+
continue;
|
|
28829
28711
|
const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
|
|
28830
28712
|
const outPath = path14.join(baselineDir, filename);
|
|
28831
28713
|
const repoPath = expandHome(repo.path, homedir11);
|
|
@@ -28876,9 +28758,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
|
28876
28758
|
} catch (err) {
|
|
28877
28759
|
if (isOversizeError(err)) {
|
|
28878
28760
|
oversized = true;
|
|
28879
|
-
console.warn(
|
|
28880
|
-
`[baseline-diff] ${repo.name}: git diff HEAD exceeded ${DEFAULT_MAX_BUFFER_BYTES / (1024 * 1024)}MB; baseline could not be captured \u2014 reaper PR for this repo will treat all tracked changes as claude's work, review carefully`
|
|
28881
|
-
);
|
|
28761
|
+
console.warn(`[baseline-diff] ${repo.name}: git diff HEAD exceeded ${DEFAULT_MAX_BUFFER_BYTES / (1024 * 1024)}MB; baseline could not be captured \u2014 reaper PR for this repo will treat all tracked changes as claude's work, review carefully`);
|
|
28882
28762
|
} else {
|
|
28883
28763
|
const msg = err instanceof Error ? err.message : String(err);
|
|
28884
28764
|
console.warn(`[baseline-diff] ${repo.name}: git diff failed (${msg}); skipping`);
|
|
@@ -28907,7 +28787,8 @@ function writeBaselineFile(outPath, content) {
|
|
|
28907
28787
|
function stripWorktreeEdits(repos, workspacePath) {
|
|
28908
28788
|
for (const repo of repos) {
|
|
28909
28789
|
const worktreePath = path14.join(workspacePath, repo.name);
|
|
28910
|
-
if (!fs11.existsSync(worktreePath))
|
|
28790
|
+
if (!fs11.existsSync(worktreePath))
|
|
28791
|
+
continue;
|
|
28911
28792
|
try {
|
|
28912
28793
|
execFileSync3("git", ["checkout", "--", "."], {
|
|
28913
28794
|
cwd: worktreePath,
|
|
@@ -28923,8 +28804,10 @@ function formatBaselineSummary(result) {
|
|
|
28923
28804
|
const noHeadRepos = result.perRepo.filter((r) => r.noHead).map((r) => r.name);
|
|
28924
28805
|
const oversizedRepos = result.perRepo.filter((r) => r.oversized).map((r) => r.name);
|
|
28925
28806
|
const flagsSuffix = [];
|
|
28926
|
-
if (noHeadRepos.length > 0)
|
|
28927
|
-
|
|
28807
|
+
if (noHeadRepos.length > 0)
|
|
28808
|
+
flagsSuffix.push(`noHead: ${noHeadRepos.join(", ")}`);
|
|
28809
|
+
if (oversizedRepos.length > 0)
|
|
28810
|
+
flagsSuffix.push(`oversized: ${oversizedRepos.join(", ")}`);
|
|
28928
28811
|
const flagsStr = flagsSuffix.length > 0 ? ` [${flagsSuffix.join("; ")}]` : "";
|
|
28929
28812
|
if (result.totalUncommittedFiles === 0) {
|
|
28930
28813
|
return `[baseline] no uncommitted files; per-repo baseline files written empty${flagsStr}`;
|
|
@@ -28933,7 +28816,7 @@ function formatBaselineSummary(result) {
|
|
|
28933
28816
|
return `[baseline] skipping ${result.totalUncommittedFiles} uncommitted file(s) (${perRepoStr})${flagsStr}; pass --carry-uncommitted to bring them in`;
|
|
28934
28817
|
}
|
|
28935
28818
|
|
|
28936
|
-
// ../core/
|
|
28819
|
+
// ../core/dist/world/context-injection.js
|
|
28937
28820
|
import * as fs12 from "node:fs";
|
|
28938
28821
|
import * as path15 from "node:path";
|
|
28939
28822
|
function injectWorldContext(opts) {
|
|
@@ -29112,7 +28995,8 @@ function formatTaskSource(ctx) {
|
|
|
29112
28995
|
return ctx.source;
|
|
29113
28996
|
}
|
|
29114
28997
|
function hasPlanFile(world) {
|
|
29115
|
-
if (world.repos.length === 0)
|
|
28998
|
+
if (world.repos.length === 0)
|
|
28999
|
+
return false;
|
|
29116
29000
|
const plansDir = path15.join(world.workspacePath, world.repos[0], "docs", "plans");
|
|
29117
29001
|
try {
|
|
29118
29002
|
return fs12.existsSync(plansDir) && fs12.readdirSync(plansDir).length > 0;
|
|
@@ -29121,11 +29005,13 @@ function hasPlanFile(world) {
|
|
|
29121
29005
|
}
|
|
29122
29006
|
}
|
|
29123
29007
|
|
|
29124
|
-
// ../core/
|
|
29008
|
+
// ../core/dist/world/stack-detect.js
|
|
29125
29009
|
function sanitizeVersion(raw) {
|
|
29126
|
-
if (!raw)
|
|
29010
|
+
if (!raw)
|
|
29011
|
+
return void 0;
|
|
29127
29012
|
const trimmed = raw.trim();
|
|
29128
|
-
if (!trimmed)
|
|
29013
|
+
if (!trimmed)
|
|
29014
|
+
return void 0;
|
|
29129
29015
|
return versionStringSchema.safeParse(trimmed).success ? trimmed : void 0;
|
|
29130
29016
|
}
|
|
29131
29017
|
function shellEscapePath(p) {
|
|
@@ -29134,24 +29020,30 @@ function shellEscapePath(p) {
|
|
|
29134
29020
|
async function detectRubyVersion(exec, repoDir) {
|
|
29135
29021
|
const escapedDir = shellEscapePath(repoDir);
|
|
29136
29022
|
const rv = await tryReadFile(exec, `${escapedDir}/.ruby-version`);
|
|
29137
|
-
if (rv)
|
|
29023
|
+
if (rv)
|
|
29024
|
+
return sanitizeVersion(rv);
|
|
29138
29025
|
const gemfile = await tryReadFile(exec, `${escapedDir}/Gemfile`);
|
|
29139
29026
|
if (gemfile) {
|
|
29140
29027
|
const match = gemfile.match(/^\s*ruby\s+['"]([^'"~>=<]+)['"]/m);
|
|
29141
|
-
if (match)
|
|
29028
|
+
if (match)
|
|
29029
|
+
return sanitizeVersion(match[1]);
|
|
29142
29030
|
const rangeMatch = gemfile.match(/^\s*ruby\s+['"]~>\s*(\d+\.\d+(?:\.\d+)?)['"]/m);
|
|
29143
|
-
if (rangeMatch)
|
|
29031
|
+
if (rangeMatch)
|
|
29032
|
+
return sanitizeVersion(rangeMatch[1]);
|
|
29144
29033
|
const minMatch = gemfile.match(/^\s*ruby\s+['"]>=?\s*(\d+\.\d+(?:\.\d+)?)['"]/m);
|
|
29145
|
-
if (minMatch)
|
|
29034
|
+
if (minMatch)
|
|
29035
|
+
return sanitizeVersion(minMatch[1]);
|
|
29146
29036
|
}
|
|
29147
29037
|
const tv = await tryReadToolVersions(exec, escapedDir, "ruby");
|
|
29148
|
-
if (tv)
|
|
29038
|
+
if (tv)
|
|
29039
|
+
return sanitizeVersion(tv);
|
|
29149
29040
|
return void 0;
|
|
29150
29041
|
}
|
|
29151
29042
|
async function detectNodeVersion(exec, repoDir) {
|
|
29152
29043
|
const escapedDir = shellEscapePath(repoDir);
|
|
29153
29044
|
const nv = await tryReadFile(exec, `${escapedDir}/.node-version`);
|
|
29154
|
-
if (nv)
|
|
29045
|
+
if (nv)
|
|
29046
|
+
return sanitizeVersion(nv);
|
|
29155
29047
|
const pkg = await tryReadFile(exec, `${escapedDir}/package.json`);
|
|
29156
29048
|
if (pkg) {
|
|
29157
29049
|
try {
|
|
@@ -29159,30 +29051,37 @@ async function detectNodeVersion(exec, repoDir) {
|
|
|
29159
29051
|
const nodeVersion = parsed.engines?.node;
|
|
29160
29052
|
if (nodeVersion) {
|
|
29161
29053
|
const exact = nodeVersion.match(/(\d+\.\d+\.\d+)/);
|
|
29162
|
-
if (exact)
|
|
29054
|
+
if (exact)
|
|
29055
|
+
return sanitizeVersion(exact[1]);
|
|
29163
29056
|
const major = nodeVersion.match(/(\d+)/);
|
|
29164
|
-
if (major)
|
|
29057
|
+
if (major)
|
|
29058
|
+
return sanitizeVersion(major[1]);
|
|
29165
29059
|
}
|
|
29166
29060
|
} catch {
|
|
29167
29061
|
}
|
|
29168
29062
|
}
|
|
29169
29063
|
const nvmrc = await tryReadFile(exec, `${escapedDir}/.nvmrc`);
|
|
29170
|
-
if (nvmrc)
|
|
29064
|
+
if (nvmrc)
|
|
29065
|
+
return sanitizeVersion(nvmrc);
|
|
29171
29066
|
const tv = await tryReadToolVersions(exec, escapedDir, "nodejs");
|
|
29172
|
-
if (tv)
|
|
29067
|
+
if (tv)
|
|
29068
|
+
return sanitizeVersion(tv);
|
|
29173
29069
|
return void 0;
|
|
29174
29070
|
}
|
|
29175
29071
|
async function detectPythonVersion(exec, repoDir) {
|
|
29176
29072
|
const escapedDir = shellEscapePath(repoDir);
|
|
29177
29073
|
const pv = await tryReadFile(exec, `${escapedDir}/.python-version`);
|
|
29178
|
-
if (pv)
|
|
29074
|
+
if (pv)
|
|
29075
|
+
return sanitizeVersion(pv);
|
|
29179
29076
|
const pyproject = await tryReadFile(exec, `${escapedDir}/pyproject.toml`);
|
|
29180
29077
|
if (pyproject) {
|
|
29181
29078
|
const match = pyproject.match(/requires-python\s*=\s*["']>=?\s*(\d+\.\d+(?:\.\d+)?)/);
|
|
29182
|
-
if (match)
|
|
29079
|
+
if (match)
|
|
29080
|
+
return sanitizeVersion(match[1]);
|
|
29183
29081
|
}
|
|
29184
29082
|
const tv = await tryReadToolVersions(exec, escapedDir, "python");
|
|
29185
|
-
if (tv)
|
|
29083
|
+
if (tv)
|
|
29084
|
+
return sanitizeVersion(tv);
|
|
29186
29085
|
return void 0;
|
|
29187
29086
|
}
|
|
29188
29087
|
async function tryReadFile(exec, escapedPath) {
|
|
@@ -29197,10 +29096,12 @@ async function tryReadFile(exec, escapedPath) {
|
|
|
29197
29096
|
}
|
|
29198
29097
|
async function tryReadToolVersions(exec, escapedDir, runtime) {
|
|
29199
29098
|
const content = await tryReadFile(exec, `${escapedDir}/.tool-versions`);
|
|
29200
|
-
if (!content)
|
|
29099
|
+
if (!content)
|
|
29100
|
+
return void 0;
|
|
29201
29101
|
for (const line of content.split("\n")) {
|
|
29202
29102
|
const trimmed = line.trim();
|
|
29203
|
-
if (trimmed.startsWith("#") || !trimmed)
|
|
29103
|
+
if (trimmed.startsWith("#") || !trimmed)
|
|
29104
|
+
continue;
|
|
29204
29105
|
const parts = trimmed.split(/\s+/);
|
|
29205
29106
|
if (parts[0] === runtime && parts[1]) {
|
|
29206
29107
|
return parts[1].trim();
|
|
@@ -29215,16 +29116,20 @@ async function detectRepoStack(exec, repoDir, repoName) {
|
|
|
29215
29116
|
detectPythonVersion(exec, repoDir)
|
|
29216
29117
|
]);
|
|
29217
29118
|
const versions = {};
|
|
29218
|
-
if (ruby)
|
|
29219
|
-
|
|
29220
|
-
if (
|
|
29119
|
+
if (ruby)
|
|
29120
|
+
versions.ruby = ruby;
|
|
29121
|
+
if (node)
|
|
29122
|
+
versions.node = node;
|
|
29123
|
+
if (python)
|
|
29124
|
+
versions.python = python;
|
|
29221
29125
|
return { repoName, versions };
|
|
29222
29126
|
}
|
|
29223
29127
|
function collectUniqueRuntimes(stacks) {
|
|
29224
29128
|
const runtimes = /* @__PURE__ */ new Map();
|
|
29225
29129
|
for (const stack of stacks) {
|
|
29226
29130
|
for (const [runtime, version2] of Object.entries(stack.versions)) {
|
|
29227
|
-
if (!version2)
|
|
29131
|
+
if (!version2)
|
|
29132
|
+
continue;
|
|
29228
29133
|
const existing = runtimes.get(runtime) ?? /* @__PURE__ */ new Set();
|
|
29229
29134
|
existing.add(version2);
|
|
29230
29135
|
runtimes.set(runtime, existing);
|
|
@@ -29233,7 +29138,7 @@ function collectUniqueRuntimes(stacks) {
|
|
|
29233
29138
|
return runtimes;
|
|
29234
29139
|
}
|
|
29235
29140
|
|
|
29236
|
-
// ../core/
|
|
29141
|
+
// ../core/dist/world/stack-install.js
|
|
29237
29142
|
var MISE_RUNTIME_NAME = {
|
|
29238
29143
|
ruby: "ruby",
|
|
29239
29144
|
node: "node",
|
|
@@ -29297,11 +29202,13 @@ async function installStack(exec, repos, stacks) {
|
|
|
29297
29202
|
}
|
|
29298
29203
|
for (const repo of repos) {
|
|
29299
29204
|
const stack = stacks.get(repo.name);
|
|
29300
|
-
if (!stack || Object.keys(stack.versions).length === 0)
|
|
29205
|
+
if (!stack || Object.keys(stack.versions).length === 0)
|
|
29206
|
+
continue;
|
|
29301
29207
|
const repoDir = `/home/olam/workspace/${shellEscapePath2(repo.name)}`;
|
|
29302
29208
|
const entries = [];
|
|
29303
29209
|
for (const [runtime, version2] of Object.entries(stack.versions)) {
|
|
29304
|
-
if (!version2)
|
|
29210
|
+
if (!version2)
|
|
29211
|
+
continue;
|
|
29305
29212
|
const miseName = MISE_RUNTIME_NAME[runtime] ?? runtime;
|
|
29306
29213
|
entries.push(`${miseName} ${version2}`);
|
|
29307
29214
|
}
|
|
@@ -29317,7 +29224,7 @@ async function installStack(exec, repos, stacks) {
|
|
|
29317
29224
|
};
|
|
29318
29225
|
}
|
|
29319
29226
|
|
|
29320
|
-
// ../core/
|
|
29227
|
+
// ../core/dist/world/stack-image.js
|
|
29321
29228
|
import { execSync as execSync2 } from "node:child_process";
|
|
29322
29229
|
import * as crypto3 from "node:crypto";
|
|
29323
29230
|
var BASE_IMAGE = "olam-devbox";
|
|
@@ -29331,7 +29238,8 @@ function computeFingerprint(runtimes) {
|
|
|
29331
29238
|
}
|
|
29332
29239
|
}
|
|
29333
29240
|
parts.sort();
|
|
29334
|
-
if (parts.length === 0)
|
|
29241
|
+
if (parts.length === 0)
|
|
29242
|
+
return "base";
|
|
29335
29243
|
const joined = parts.join("_");
|
|
29336
29244
|
return sanitizeTag(joined);
|
|
29337
29245
|
}
|
|
@@ -29361,19 +29269,14 @@ function lookupCachedImage(runtimes) {
|
|
|
29361
29269
|
function commitAsImage(containerName, imageName) {
|
|
29362
29270
|
const baseDigest = getBaseImageDigest();
|
|
29363
29271
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
29364
|
-
execSync2(
|
|
29365
|
-
`docker commit --change 'LABEL ${LABEL_PREFIX}=true' --change 'LABEL ${LABEL_PREFIX}.created-at=${now}' --change 'LABEL ${LABEL_PREFIX}.base-digest=${baseDigest}' ${containerName} ${imageName}`,
|
|
29366
|
-
{ stdio: "pipe", timeout: 12e4 }
|
|
29367
|
-
);
|
|
29272
|
+
execSync2(`docker commit --change 'LABEL ${LABEL_PREFIX}=true' --change 'LABEL ${LABEL_PREFIX}.created-at=${now}' --change 'LABEL ${LABEL_PREFIX}.base-digest=${baseDigest}' ${containerName} ${imageName}`, { stdio: "pipe", timeout: 12e4 });
|
|
29368
29273
|
}
|
|
29369
29274
|
var cachedBaseDigest;
|
|
29370
29275
|
function getBaseImageDigest() {
|
|
29371
|
-
if (cachedBaseDigest)
|
|
29276
|
+
if (cachedBaseDigest)
|
|
29277
|
+
return cachedBaseDigest;
|
|
29372
29278
|
try {
|
|
29373
|
-
const digest = execSync2(
|
|
29374
|
-
`docker inspect ${BASE_IMAGE}:latest --format '{{.Id}}'`,
|
|
29375
|
-
{ encoding: "utf-8", timeout: 5e3 }
|
|
29376
|
-
).trim();
|
|
29279
|
+
const digest = execSync2(`docker inspect ${BASE_IMAGE}:latest --format '{{.Id}}'`, { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
29377
29280
|
cachedBaseDigest = digest.replace("sha256:", "").slice(0, 16);
|
|
29378
29281
|
return cachedBaseDigest;
|
|
29379
29282
|
} catch {
|
|
@@ -29390,52 +29293,46 @@ function sanitizeTag(raw) {
|
|
|
29390
29293
|
return tag;
|
|
29391
29294
|
}
|
|
29392
29295
|
|
|
29393
|
-
// ../core/
|
|
29296
|
+
// ../core/dist/world/manager.js
|
|
29394
29297
|
import YAML3 from "yaml";
|
|
29395
29298
|
|
|
29396
|
-
// ../core/
|
|
29299
|
+
// ../core/dist/world/manifest-env.js
|
|
29397
29300
|
var EXPR_PATTERN = /\$\{[^}]+\}/;
|
|
29398
29301
|
var MAX_DEPTH = 8;
|
|
29399
29302
|
var REPO_FIELDS = /* @__PURE__ */ new Set(["url", "path", "port", "branch"]);
|
|
29400
29303
|
var SERVICE_FIELDS = /* @__PURE__ */ new Set(["port", "host"]);
|
|
29401
29304
|
var WORLD_FIELDS = /* @__PURE__ */ new Set(["id", "branch", "portOffset"]);
|
|
29402
29305
|
var EnvSubstCycleError = class extends Error {
|
|
29306
|
+
key;
|
|
29307
|
+
chain;
|
|
29403
29308
|
constructor(key, chain) {
|
|
29404
|
-
super(
|
|
29405
|
-
`[manifest-env] cycle detected for key "${key}" \u2014 values cycled: ${chain.map((v) => `"${v}"`).join(" \u2192 ")}`
|
|
29406
|
-
);
|
|
29309
|
+
super(`[manifest-env] cycle detected for key "${key}" \u2014 values cycled: ${chain.map((v) => `"${v}"`).join(" \u2192 ")}`);
|
|
29407
29310
|
this.key = key;
|
|
29408
29311
|
this.chain = chain;
|
|
29409
29312
|
this.name = "EnvSubstCycleError";
|
|
29410
29313
|
}
|
|
29411
|
-
key;
|
|
29412
|
-
chain;
|
|
29413
29314
|
};
|
|
29414
29315
|
var EnvSubstDepthExceededError = class extends Error {
|
|
29316
|
+
key;
|
|
29317
|
+
lastValue;
|
|
29318
|
+
maxDepth;
|
|
29415
29319
|
constructor(key, lastValue, maxDepth) {
|
|
29416
|
-
super(
|
|
29417
|
-
`[manifest-env] reference for key "${key}" did not resolve within ${maxDepth} passes (last value: "${lastValue}"); chain too deep \u2014 examine for unintended indirection`
|
|
29418
|
-
);
|
|
29320
|
+
super(`[manifest-env] reference for key "${key}" did not resolve within ${maxDepth} passes (last value: "${lastValue}"); chain too deep \u2014 examine for unintended indirection`);
|
|
29419
29321
|
this.key = key;
|
|
29420
29322
|
this.lastValue = lastValue;
|
|
29421
29323
|
this.maxDepth = maxDepth;
|
|
29422
29324
|
this.name = "EnvSubstDepthExceededError";
|
|
29423
29325
|
}
|
|
29424
|
-
key;
|
|
29425
|
-
lastValue;
|
|
29426
|
-
maxDepth;
|
|
29427
29326
|
};
|
|
29428
29327
|
var EnvSubstMissingRepoError = class extends Error {
|
|
29328
|
+
subject;
|
|
29329
|
+
expression;
|
|
29429
29330
|
constructor(subject, expression) {
|
|
29430
|
-
super(
|
|
29431
|
-
`[manifest-env] \${${expression}}: ${subject} unresolved; using empty string`
|
|
29432
|
-
);
|
|
29331
|
+
super(`[manifest-env] \${${expression}}: ${subject} unresolved; using empty string`);
|
|
29433
29332
|
this.subject = subject;
|
|
29434
29333
|
this.expression = expression;
|
|
29435
29334
|
this.name = "EnvSubstMissingRepoError";
|
|
29436
29335
|
}
|
|
29437
|
-
subject;
|
|
29438
|
-
expression;
|
|
29439
29336
|
};
|
|
29440
29337
|
function parseExpression(expr) {
|
|
29441
29338
|
const idx = expr.indexOf(":-");
|
|
@@ -29445,15 +29342,20 @@ function parseExpression(expr) {
|
|
|
29445
29342
|
return { name: expr.slice(0, idx), fallback: expr.slice(idx + 2) };
|
|
29446
29343
|
}
|
|
29447
29344
|
function readWhitelistedField(obj, field, whitelist) {
|
|
29448
|
-
if (!obj)
|
|
29449
|
-
|
|
29450
|
-
if (!
|
|
29345
|
+
if (!obj)
|
|
29346
|
+
return void 0;
|
|
29347
|
+
if (!whitelist.has(field))
|
|
29348
|
+
return void 0;
|
|
29349
|
+
if (!Object.hasOwn(obj, field))
|
|
29350
|
+
return void 0;
|
|
29451
29351
|
const value = obj[field];
|
|
29452
|
-
if (value === void 0 || value === null)
|
|
29352
|
+
if (value === void 0 || value === null)
|
|
29353
|
+
return void 0;
|
|
29453
29354
|
return String(value);
|
|
29454
29355
|
}
|
|
29455
29356
|
function readEnvVar(env, name) {
|
|
29456
|
-
if (!Object.hasOwn(env, name))
|
|
29357
|
+
if (!Object.hasOwn(env, name))
|
|
29358
|
+
return void 0;
|
|
29457
29359
|
const v = env[name];
|
|
29458
29360
|
return v;
|
|
29459
29361
|
}
|
|
@@ -29471,11 +29373,7 @@ function resolveRef(name, ctx) {
|
|
|
29471
29373
|
reason: `repo field "${field}" is not a permitted reference (allowed: ${[...REPO_FIELDS].join(", ")})`
|
|
29472
29374
|
};
|
|
29473
29375
|
}
|
|
29474
|
-
const value = readWhitelistedField(
|
|
29475
|
-
repo,
|
|
29476
|
-
field,
|
|
29477
|
-
REPO_FIELDS
|
|
29478
|
-
);
|
|
29376
|
+
const value = readWhitelistedField(repo, field, REPO_FIELDS);
|
|
29479
29377
|
if (value === void 0) {
|
|
29480
29378
|
return { kind: "miss", reason: `repo "${repoName}" has no field "${field}"` };
|
|
29481
29379
|
}
|
|
@@ -29493,11 +29391,7 @@ function resolveRef(name, ctx) {
|
|
|
29493
29391
|
reason: `service field "${field}" is not a permitted reference (allowed: ${[...SERVICE_FIELDS].join(", ")})`
|
|
29494
29392
|
};
|
|
29495
29393
|
}
|
|
29496
|
-
const value = readWhitelistedField(
|
|
29497
|
-
svc,
|
|
29498
|
-
field,
|
|
29499
|
-
SERVICE_FIELDS
|
|
29500
|
-
);
|
|
29394
|
+
const value = readWhitelistedField(svc, field, SERVICE_FIELDS);
|
|
29501
29395
|
if (value === void 0) {
|
|
29502
29396
|
return { kind: "miss", reason: `service "${serviceName}" has no field "${field}"` };
|
|
29503
29397
|
}
|
|
@@ -29511,11 +29405,7 @@ function resolveRef(name, ctx) {
|
|
|
29511
29405
|
reason: `world field "${field}" is not a permitted reference (allowed: ${[...WORLD_FIELDS].join(", ")})`
|
|
29512
29406
|
};
|
|
29513
29407
|
}
|
|
29514
|
-
const value = readWhitelistedField(
|
|
29515
|
-
ctx.world,
|
|
29516
|
-
field,
|
|
29517
|
-
WORLD_FIELDS
|
|
29518
|
-
);
|
|
29408
|
+
const value = readWhitelistedField(ctx.world, field, WORLD_FIELDS);
|
|
29519
29409
|
if (value === void 0) {
|
|
29520
29410
|
return { kind: "miss", reason: `world has no field "${field}"` };
|
|
29521
29411
|
}
|
|
@@ -29547,11 +29437,11 @@ function substituteOnce(input, ctx, warnings) {
|
|
|
29547
29437
|
return input.replace(/\$\{([^}]+)\}/g, (_, expr) => {
|
|
29548
29438
|
const { name, fallback } = parseExpression(expr);
|
|
29549
29439
|
const ref = resolveRef(name, ctx);
|
|
29550
|
-
if (ref.kind === "ok")
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
);
|
|
29440
|
+
if (ref.kind === "ok")
|
|
29441
|
+
return ref.value;
|
|
29442
|
+
if (fallback !== void 0)
|
|
29443
|
+
return fallback;
|
|
29444
|
+
warnings.push(new EnvSubstMissingRepoError(ref.reason, expr).message);
|
|
29555
29445
|
return "";
|
|
29556
29446
|
});
|
|
29557
29447
|
}
|
|
@@ -29562,9 +29452,11 @@ function resolveManifestEnv(env, ctx) {
|
|
|
29562
29452
|
let current = rawValue;
|
|
29563
29453
|
const seen = /* @__PURE__ */ new Set([current]);
|
|
29564
29454
|
for (let depth = 0; depth < MAX_DEPTH; depth++) {
|
|
29565
|
-
if (!EXPR_PATTERN.test(current))
|
|
29455
|
+
if (!EXPR_PATTERN.test(current))
|
|
29456
|
+
break;
|
|
29566
29457
|
const next = substituteOnce(current, ctx, warnings);
|
|
29567
|
-
if (next === current)
|
|
29458
|
+
if (next === current)
|
|
29459
|
+
break;
|
|
29568
29460
|
if (seen.has(next)) {
|
|
29569
29461
|
throw new EnvSubstCycleError(key, [...seen, next]);
|
|
29570
29462
|
}
|
|
@@ -29579,7 +29471,7 @@ function resolveManifestEnv(env, ctx) {
|
|
|
29579
29471
|
return { env: resolved, warnings: [...new Set(warnings)] };
|
|
29580
29472
|
}
|
|
29581
29473
|
|
|
29582
|
-
// ../core/
|
|
29474
|
+
// ../core/dist/world/bootstrap-runner.js
|
|
29583
29475
|
var BootstrapStepError = class extends Error {
|
|
29584
29476
|
step;
|
|
29585
29477
|
index;
|
|
@@ -29597,12 +29489,12 @@ ${stderr}`);
|
|
|
29597
29489
|
var SAFE_IDENT = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
29598
29490
|
var SAFE_WORKDIR_SEGMENT = /^[\w.-]+$/;
|
|
29599
29491
|
function isSafeWorkdir(workdir) {
|
|
29600
|
-
if (!workdir.startsWith("/"))
|
|
29492
|
+
if (!workdir.startsWith("/"))
|
|
29493
|
+
return false;
|
|
29601
29494
|
const segments = workdir.slice(1).split("/");
|
|
29602
|
-
if (segments.length === 0)
|
|
29603
|
-
|
|
29604
|
-
|
|
29605
|
-
);
|
|
29495
|
+
if (segments.length === 0)
|
|
29496
|
+
return false;
|
|
29497
|
+
return segments.every((seg) => seg.length > 0 && seg !== "." && seg !== ".." && SAFE_WORKDIR_SEGMENT.test(seg));
|
|
29606
29498
|
}
|
|
29607
29499
|
var SENTINEL_PREFIX = ".olam-bootstrap-";
|
|
29608
29500
|
var SENTINEL_SUFFIX = "-done";
|
|
@@ -29611,14 +29503,10 @@ function sentinelPath(workdir, index) {
|
|
|
29611
29503
|
}
|
|
29612
29504
|
async function runBootstrap(containerName, workdir, steps, exec, options = {}) {
|
|
29613
29505
|
if (!SAFE_IDENT.test(containerName)) {
|
|
29614
|
-
throw new Error(
|
|
29615
|
-
`containerName "${containerName}" must match ${SAFE_IDENT} (defensive guard)`
|
|
29616
|
-
);
|
|
29506
|
+
throw new Error(`containerName "${containerName}" must match ${SAFE_IDENT} (defensive guard)`);
|
|
29617
29507
|
}
|
|
29618
29508
|
if (!isSafeWorkdir(workdir)) {
|
|
29619
|
-
throw new Error(
|
|
29620
|
-
`workdir "${workdir}" must be an absolute path with no "." or ".." segments (defensive guard)`
|
|
29621
|
-
);
|
|
29509
|
+
throw new Error(`workdir "${workdir}" must be an absolute path with no "." or ".." segments (defensive guard)`);
|
|
29622
29510
|
}
|
|
29623
29511
|
const clock = options.clock ?? Date.now;
|
|
29624
29512
|
if (options.rebootstrap) {
|
|
@@ -29668,16 +29556,19 @@ function shellQuote(s) {
|
|
|
29668
29556
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
29669
29557
|
}
|
|
29670
29558
|
|
|
29671
|
-
// ../core/
|
|
29559
|
+
// ../core/dist/world/secrets-fetcher.js
|
|
29672
29560
|
import { execSync as execSync3 } from "node:child_process";
|
|
29673
29561
|
function parseGcpSecretUrl(url) {
|
|
29674
|
-
if (!url.startsWith("gcp://"))
|
|
29562
|
+
if (!url.startsWith("gcp://"))
|
|
29563
|
+
return null;
|
|
29675
29564
|
const without = url.slice("gcp://".length);
|
|
29676
29565
|
const slash = without.indexOf("/");
|
|
29677
|
-
if (slash === -1)
|
|
29566
|
+
if (slash === -1)
|
|
29567
|
+
return null;
|
|
29678
29568
|
const project = without.slice(0, slash);
|
|
29679
29569
|
const secretName = without.slice(slash + 1);
|
|
29680
|
-
if (!project || !secretName)
|
|
29570
|
+
if (!project || !secretName)
|
|
29571
|
+
return null;
|
|
29681
29572
|
return { project, secretName };
|
|
29682
29573
|
}
|
|
29683
29574
|
function defaultExecFn(cmd, opts) {
|
|
@@ -29687,27 +29578,18 @@ function fetchGcpSecret(ref, execFn = defaultExecFn) {
|
|
|
29687
29578
|
const { project, secretName } = ref;
|
|
29688
29579
|
let raw;
|
|
29689
29580
|
try {
|
|
29690
|
-
raw = execFn(
|
|
29691
|
-
`gcloud secrets versions access latest --secret=${secretName} --project=${project}`,
|
|
29692
|
-
{ timeout: 15e3 }
|
|
29693
|
-
).trim();
|
|
29581
|
+
raw = execFn(`gcloud secrets versions access latest --secret=${secretName} --project=${project}`, { timeout: 15e3 }).trim();
|
|
29694
29582
|
} catch (err) {
|
|
29695
29583
|
const msg = err instanceof Error ? err.message : String(err);
|
|
29696
29584
|
if (msg.includes("NOT_FOUND") || msg.includes("not found")) {
|
|
29697
|
-
throw new Error(
|
|
29698
|
-
`Secret not found: gcp://${project}/${secretName}. Check the secret name exists in project ${project}.`
|
|
29699
|
-
);
|
|
29585
|
+
throw new Error(`Secret not found: gcp://${project}/${secretName}. Check the secret name exists in project ${project}.`);
|
|
29700
29586
|
}
|
|
29701
29587
|
if (msg.includes("PERMISSION_DENIED") || msg.includes("403")) {
|
|
29702
|
-
throw new Error(
|
|
29703
|
-
`Permission denied fetching gcp://${project}/${secretName}. Grant your gcloud account roles/secretmanager.secretAccessor on this secret.`
|
|
29704
|
-
);
|
|
29588
|
+
throw new Error(`Permission denied fetching gcp://${project}/${secretName}. Grant your gcloud account roles/secretmanager.secretAccessor on this secret.`);
|
|
29705
29589
|
}
|
|
29706
29590
|
if (msg.includes("not authenticated") || msg.includes("credentials") || msg.includes("login") || msg.includes("authenticated")) {
|
|
29707
|
-
throw new Error(
|
|
29708
|
-
|
|
29709
|
-
Run: gcloud auth application-default login`
|
|
29710
|
-
);
|
|
29591
|
+
throw new Error(`No gcloud credentials found while fetching gcp://${project}/${secretName}.
|
|
29592
|
+
Run: gcloud auth application-default login`);
|
|
29711
29593
|
}
|
|
29712
29594
|
throw new Error(`Failed to fetch secret gcp://${project}/${secretName}: ${msg}`);
|
|
29713
29595
|
}
|
|
@@ -29718,14 +29600,10 @@ Run: gcloud auth application-default login`
|
|
|
29718
29600
|
try {
|
|
29719
29601
|
parsed = JSON.parse(raw);
|
|
29720
29602
|
} catch {
|
|
29721
|
-
throw new Error(
|
|
29722
|
-
`Secret gcp://${project}/${secretName} is not valid JSON. The secret value must be a JSON object: {"ENV_VAR": "value", ...}`
|
|
29723
|
-
);
|
|
29603
|
+
throw new Error(`Secret gcp://${project}/${secretName} is not valid JSON. The secret value must be a JSON object: {"ENV_VAR": "value", ...}`);
|
|
29724
29604
|
}
|
|
29725
29605
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
29726
|
-
throw new Error(
|
|
29727
|
-
`Secret gcp://${project}/${secretName} must be a JSON object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.`
|
|
29728
|
-
);
|
|
29606
|
+
throw new Error(`Secret gcp://${project}/${secretName} must be a JSON object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.`);
|
|
29729
29607
|
}
|
|
29730
29608
|
const result = {};
|
|
29731
29609
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -29737,11 +29615,10 @@ var FILE_BACKED_SECRET_KEYS = [
|
|
|
29737
29615
|
{ envVar: "RAILS_MASTER_KEY", filePath: "config/credentials/development.key" }
|
|
29738
29616
|
];
|
|
29739
29617
|
function fetchSecretsForRepos(repoSecretUrls, execFn = defaultExecFn) {
|
|
29740
|
-
if (repoSecretUrls.length === 0)
|
|
29618
|
+
if (repoSecretUrls.length === 0)
|
|
29619
|
+
return { env: {}, fileWrites: [] };
|
|
29741
29620
|
if (!gcloudAvailable(execFn)) {
|
|
29742
|
-
console.warn(
|
|
29743
|
-
"[secrets] WARN: gcloud CLI not installed or not authenticated \u2014 skipping GCP secret injection for ALL repos. To enable: install gcloud and run `gcloud auth application-default login`. World will boot WITHOUT declared GCP secrets."
|
|
29744
|
-
);
|
|
29621
|
+
console.warn("[secrets] WARN: gcloud CLI not installed or not authenticated \u2014 skipping GCP secret injection for ALL repos. To enable: install gcloud and run `gcloud auth application-default login`. World will boot WITHOUT declared GCP secrets.");
|
|
29745
29622
|
return { env: {}, fileWrites: [] };
|
|
29746
29623
|
}
|
|
29747
29624
|
const env = {};
|
|
@@ -29774,9 +29651,7 @@ function fetchSecretsForRepos(repoSecretUrls, execFn = defaultExecFn) {
|
|
|
29774
29651
|
envCount++;
|
|
29775
29652
|
}
|
|
29776
29653
|
}
|
|
29777
|
-
console.log(
|
|
29778
|
-
`[secrets] ${repoName}: ${envCount} env var(s) + ${fileCount} file-backed key(s) from ${secretsUrl}`
|
|
29779
|
-
);
|
|
29654
|
+
console.log(`[secrets] ${repoName}: ${envCount} env var(s) + ${fileCount} file-backed key(s) from ${secretsUrl}`);
|
|
29780
29655
|
}
|
|
29781
29656
|
return { env, fileWrites };
|
|
29782
29657
|
}
|
|
@@ -29789,7 +29664,7 @@ function gcloudAvailable(execFn = defaultExecFn) {
|
|
|
29789
29664
|
}
|
|
29790
29665
|
}
|
|
29791
29666
|
|
|
29792
|
-
// ../core/
|
|
29667
|
+
// ../core/dist/world/olam-yaml.js
|
|
29793
29668
|
import * as path16 from "node:path";
|
|
29794
29669
|
import YAML2 from "yaml";
|
|
29795
29670
|
function enrichReposWithManifests(repos, workspacePath) {
|
|
@@ -29805,18 +29680,20 @@ function enrichReposWithManifests(repos, workspacePath) {
|
|
|
29805
29680
|
const msg = err instanceof Error ? err.message : String(err);
|
|
29806
29681
|
console.warn(`[olam-yaml] failed to load manifest for repo "${repo.name}": ${msg}`);
|
|
29807
29682
|
}
|
|
29808
|
-
if (manifest === null)
|
|
29683
|
+
if (manifest === null)
|
|
29684
|
+
return repo;
|
|
29809
29685
|
return { ...repo, manifest };
|
|
29810
29686
|
});
|
|
29811
29687
|
}
|
|
29812
29688
|
|
|
29813
|
-
// ../core/
|
|
29689
|
+
// ../core/dist/policies/loader.js
|
|
29814
29690
|
import * as fs13 from "node:fs";
|
|
29815
29691
|
import * as path17 from "node:path";
|
|
29816
29692
|
import { parse as parseYaml3 } from "yaml";
|
|
29817
29693
|
function parseFrontmatter(content) {
|
|
29818
29694
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/m.exec(content);
|
|
29819
|
-
if (!match)
|
|
29695
|
+
if (!match)
|
|
29696
|
+
return null;
|
|
29820
29697
|
const [, yamlText = "", body = ""] = match;
|
|
29821
29698
|
try {
|
|
29822
29699
|
const frontmatter = parseYaml3(yamlText);
|
|
@@ -29826,12 +29703,14 @@ function parseFrontmatter(content) {
|
|
|
29826
29703
|
}
|
|
29827
29704
|
}
|
|
29828
29705
|
function toStringArray(v) {
|
|
29829
|
-
if (!Array.isArray(v))
|
|
29706
|
+
if (!Array.isArray(v))
|
|
29707
|
+
return [];
|
|
29830
29708
|
return v.filter((x) => typeof x === "string");
|
|
29831
29709
|
}
|
|
29832
29710
|
function loadPolicies(workspaceRoot) {
|
|
29833
29711
|
const policiesDir = path17.join(workspaceRoot, ".olam", "policies");
|
|
29834
|
-
if (!fs13.existsSync(policiesDir))
|
|
29712
|
+
if (!fs13.existsSync(policiesDir))
|
|
29713
|
+
return [];
|
|
29835
29714
|
let files;
|
|
29836
29715
|
try {
|
|
29837
29716
|
files = fs13.readdirSync(policiesDir).filter((f) => f.endsWith(".md")).sort();
|
|
@@ -29854,9 +29733,7 @@ function loadPolicies(workspaceRoot) {
|
|
|
29854
29733
|
continue;
|
|
29855
29734
|
}
|
|
29856
29735
|
const requires = fm.requires !== null && typeof fm.requires === "object" ? {
|
|
29857
|
-
diff_includes: toStringArray(
|
|
29858
|
-
fm.requires.diff_includes
|
|
29859
|
-
)
|
|
29736
|
+
diff_includes: toStringArray(fm.requires.diff_includes)
|
|
29860
29737
|
} : void 0;
|
|
29861
29738
|
const policy = {
|
|
29862
29739
|
id: fm.id,
|
|
@@ -29876,7 +29753,8 @@ function loadPolicies(workspaceRoot) {
|
|
|
29876
29753
|
return policies;
|
|
29877
29754
|
}
|
|
29878
29755
|
function formatPoliciesBrief(policies) {
|
|
29879
|
-
if (policies.length === 0)
|
|
29756
|
+
if (policies.length === 0)
|
|
29757
|
+
return "";
|
|
29880
29758
|
const lines = [
|
|
29881
29759
|
"## Active policies for this workspace",
|
|
29882
29760
|
"The following policies apply to your work. Read them carefully \u2014 your",
|
|
@@ -29893,15 +29771,13 @@ function formatPoliciesBrief(policies) {
|
|
|
29893
29771
|
return lines.join("\n");
|
|
29894
29772
|
}
|
|
29895
29773
|
|
|
29896
|
-
// ../core/
|
|
29774
|
+
// ../core/dist/world/tmux-supervisor.js
|
|
29897
29775
|
var PortInUseError = class extends Error {
|
|
29898
29776
|
port;
|
|
29899
29777
|
repo;
|
|
29900
29778
|
manifestPath;
|
|
29901
29779
|
constructor(port, repo, manifestPath) {
|
|
29902
|
-
super(
|
|
29903
|
-
`Port ${port} already in use on host (claimed by repo "${repo}"). Free the port or change \`app_port\` in ${manifestPath}.`
|
|
29904
|
-
);
|
|
29780
|
+
super(`Port ${port} already in use on host (claimed by repo "${repo}"). Free the port or change \`app_port\` in ${manifestPath}.`);
|
|
29905
29781
|
this.name = "PortInUseError";
|
|
29906
29782
|
this.port = port;
|
|
29907
29783
|
this.repo = repo;
|
|
@@ -29911,25 +29787,17 @@ var PortInUseError = class extends Error {
|
|
|
29911
29787
|
var SAFE_IDENT2 = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
29912
29788
|
async function startSupervisedApps(containerName, worldId, repos, exec, options = {}) {
|
|
29913
29789
|
if (!SAFE_IDENT2.test(containerName)) {
|
|
29914
|
-
throw new Error(
|
|
29915
|
-
`containerName "${containerName}" must match ${SAFE_IDENT2} (defensive guard)`
|
|
29916
|
-
);
|
|
29790
|
+
throw new Error(`containerName "${containerName}" must match ${SAFE_IDENT2} (defensive guard)`);
|
|
29917
29791
|
}
|
|
29918
29792
|
if (!SAFE_IDENT2.test(worldId)) {
|
|
29919
|
-
throw new Error(
|
|
29920
|
-
`worldId "${worldId}" must match ${SAFE_IDENT2} (defensive guard)`
|
|
29921
|
-
);
|
|
29793
|
+
throw new Error(`worldId "${worldId}" must match ${SAFE_IDENT2} (defensive guard)`);
|
|
29922
29794
|
}
|
|
29923
29795
|
for (const repo of repos) {
|
|
29924
29796
|
if (!SAFE_IDENT2.test(repo.name)) {
|
|
29925
|
-
throw new Error(
|
|
29926
|
-
`repo.name "${repo.name}" must match ${SAFE_IDENT2} (defensive guard)`
|
|
29927
|
-
);
|
|
29797
|
+
throw new Error(`repo.name "${repo.name}" must match ${SAFE_IDENT2} (defensive guard)`);
|
|
29928
29798
|
}
|
|
29929
29799
|
if (!Number.isInteger(repo.hostPort) || repo.hostPort < 1 || repo.hostPort > 65535) {
|
|
29930
|
-
throw new Error(
|
|
29931
|
-
`repo.hostPort ${repo.hostPort} for "${repo.name}" must be an integer in [1, 65535]`
|
|
29932
|
-
);
|
|
29800
|
+
throw new Error(`repo.hostPort ${repo.hostPort} for "${repo.name}" must be an integer in [1, 65535]`);
|
|
29933
29801
|
}
|
|
29934
29802
|
}
|
|
29935
29803
|
const sessionName = `olam-${worldId}`;
|
|
@@ -29943,13 +29811,8 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
|
|
|
29943
29811
|
}
|
|
29944
29812
|
}
|
|
29945
29813
|
exec(containerName, `tmux new-session -A -d -s ${sessionName}`);
|
|
29946
|
-
const existingRaw = exec(
|
|
29947
|
-
|
|
29948
|
-
`tmux list-windows -t ${sessionName} -F '#W' 2>/dev/null || true`
|
|
29949
|
-
);
|
|
29950
|
-
const existingWindows = new Set(
|
|
29951
|
-
existingRaw.split("\n").map((s) => s.trim()).filter(Boolean)
|
|
29952
|
-
);
|
|
29814
|
+
const existingRaw = exec(containerName, `tmux list-windows -t ${sessionName} -F '#W' 2>/dev/null || true`);
|
|
29815
|
+
const existingWindows = new Set(existingRaw.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
29953
29816
|
const windowsCreated = [];
|
|
29954
29817
|
const windowsExisting = [];
|
|
29955
29818
|
for (const repo of repos) {
|
|
@@ -29957,25 +29820,17 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
|
|
|
29957
29820
|
windowsExisting.push(repo.name);
|
|
29958
29821
|
continue;
|
|
29959
29822
|
}
|
|
29960
|
-
const expandedStart = repo.start.replace(
|
|
29961
|
-
/\$\{?PORT\}?/g,
|
|
29962
|
-
String(repo.hostPort)
|
|
29963
|
-
);
|
|
29823
|
+
const expandedStart = repo.start.replace(/\$\{?PORT\}?/g, String(repo.hostPort));
|
|
29964
29824
|
const startCmd = `cd ${shellQuote2(repo.dir)} && exec ${expandedStart}`;
|
|
29965
|
-
exec(
|
|
29966
|
-
containerName,
|
|
29967
|
-
`tmux new-window -t ${sessionName} -n ${repo.name} -d ${shellQuote2(startCmd)}`
|
|
29968
|
-
);
|
|
29825
|
+
exec(containerName, `tmux new-window -t ${sessionName} -n ${repo.name} -d ${shellQuote2(startCmd)}`);
|
|
29969
29826
|
windowsCreated.push(repo.name);
|
|
29970
29827
|
}
|
|
29971
|
-
const health = await Promise.all(
|
|
29972
|
-
|
|
29973
|
-
|
|
29974
|
-
|
|
29975
|
-
|
|
29976
|
-
|
|
29977
|
-
}))
|
|
29978
|
-
);
|
|
29828
|
+
const health = await Promise.all(repos.map((repo) => probeOne(exec, containerName, repo, {
|
|
29829
|
+
probeTimeoutMs,
|
|
29830
|
+
probeIntervalMs,
|
|
29831
|
+
clock,
|
|
29832
|
+
sleep: sleep3
|
|
29833
|
+
})));
|
|
29979
29834
|
return {
|
|
29980
29835
|
sessionName,
|
|
29981
29836
|
windowsCreated,
|
|
@@ -30014,7 +29869,7 @@ function shellQuote2(s) {
|
|
|
30014
29869
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
30015
29870
|
}
|
|
30016
29871
|
|
|
30017
|
-
// ../core/
|
|
29872
|
+
// ../core/dist/world/manager.js
|
|
30018
29873
|
var BotIdentityError = class extends Error {
|
|
30019
29874
|
constructor(message) {
|
|
30020
29875
|
super(message);
|
|
@@ -30030,7 +29885,8 @@ function getTokenScopes(ghToken, _exec = execSync4) {
|
|
|
30030
29885
|
});
|
|
30031
29886
|
const m = out.match(/Token scopes:\s*(.+)/);
|
|
30032
29887
|
const list = m?.[1];
|
|
30033
|
-
if (!list)
|
|
29888
|
+
if (!list)
|
|
29889
|
+
return [];
|
|
30034
29890
|
return list.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter((s) => s.length > 0);
|
|
30035
29891
|
} catch {
|
|
30036
29892
|
return [];
|
|
@@ -30051,19 +29907,16 @@ async function setupContainerGit(containerName, repos, branch) {
|
|
|
30051
29907
|
const SAFE_BOT_NAME = /^[A-Za-z0-9 .,()@_+\-]+$/;
|
|
30052
29908
|
const SAFE_BOT_EMAIL = /^[A-Za-z0-9._+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$/;
|
|
30053
29909
|
if (!SAFE_BOT_NAME.test(actorName)) {
|
|
30054
|
-
throw new BotIdentityError(
|
|
30055
|
-
`OLAM_BOT_NAME contains unsafe characters: ${JSON.stringify(actorName)}. Allowed: alphanumeric + space + .,()@_+-. Shell metacharacters are rejected to prevent injection into the world container.`
|
|
30056
|
-
);
|
|
29910
|
+
throw new BotIdentityError(`OLAM_BOT_NAME contains unsafe characters: ${JSON.stringify(actorName)}. Allowed: alphanumeric + space + .,()@_+-. Shell metacharacters are rejected to prevent injection into the world container.`);
|
|
30057
29911
|
}
|
|
30058
29912
|
if (!SAFE_BOT_EMAIL.test(actorEmail)) {
|
|
30059
|
-
throw new BotIdentityError(
|
|
30060
|
-
`OLAM_BOT_EMAIL is not a valid email or contains unsafe characters: ${JSON.stringify(actorEmail)}. Expected format: localpart@domain.tld with alphanumeric + ._+- only.`
|
|
30061
|
-
);
|
|
29913
|
+
throw new BotIdentityError(`OLAM_BOT_EMAIL is not a valid email or contains unsafe characters: ${JSON.stringify(actorEmail)}. Expected format: localpart@domain.tld with alphanumeric + ._+- only.`);
|
|
30062
29914
|
}
|
|
30063
29915
|
if (ghToken) {
|
|
30064
29916
|
for (const repo of repos) {
|
|
30065
29917
|
const ghMatch = repo.url.match(/(?:git@github\.com:|github\.com\/)([^/]+\/[^.]+)/);
|
|
30066
|
-
if (!ghMatch)
|
|
29918
|
+
if (!ghMatch)
|
|
29919
|
+
continue;
|
|
30067
29920
|
const ownerRepo = ghMatch[1];
|
|
30068
29921
|
try {
|
|
30069
29922
|
execSync4(`gh api repos/${ownerRepo} --silent`, {
|
|
@@ -30080,9 +29933,7 @@ ${stderr.split("\n").slice(0, 3).join(" ")}`);
|
|
|
30080
29933
|
}
|
|
30081
29934
|
const tokenScopes = getTokenScopes(ghToken);
|
|
30082
29935
|
if (tokenScopes.length > 0 && !tokenScopes.includes("workflow")) {
|
|
30083
|
-
console.warn(
|
|
30084
|
-
"[world] WARN: gh token is missing the `workflow` scope. Pushes that modify .github/workflows/* will be rejected by GitHub. If this world will touch CI files, run: `gh auth refresh -h github.com -s workflow` then re-spawn."
|
|
30085
|
-
);
|
|
29936
|
+
console.warn("[world] WARN: gh token is missing the `workflow` scope. Pushes that modify .github/workflows/* will be rejected by GitHub. If this world will touch CI files, run: `gh auth refresh -h github.com -s workflow` then re-spawn.");
|
|
30086
29937
|
}
|
|
30087
29938
|
}
|
|
30088
29939
|
for (const repo of repos) {
|
|
@@ -30115,10 +29966,8 @@ ${stderr.split("\n").slice(0, 3).join(" ")}`);
|
|
|
30115
29966
|
dockerExec(`gh auth setup-git`);
|
|
30116
29967
|
} catch (err) {
|
|
30117
29968
|
const msg = err instanceof Error ? err.message : String(err);
|
|
30118
|
-
console.warn(
|
|
30119
|
-
|
|
30120
|
-
PR push from inside the world will not work. Operator must inject GH_TOKEN manually or run \`gh auth login\` inside the container.`
|
|
30121
|
-
);
|
|
29969
|
+
console.warn(`[world] gh auth setup failed for ${containerName}: ${msg}
|
|
29970
|
+
PR push from inside the world will not work. Operator must inject GH_TOKEN manually or run \`gh auth login\` inside the container.`);
|
|
30122
29971
|
} finally {
|
|
30123
29972
|
if (tokenWritten) {
|
|
30124
29973
|
try {
|
|
@@ -30142,10 +29991,7 @@ function makeHostExecFn() {
|
|
|
30142
29991
|
function makeContainerExecFn(containerName) {
|
|
30143
29992
|
return async (cmd) => {
|
|
30144
29993
|
try {
|
|
30145
|
-
const result = execSync4(
|
|
30146
|
-
`docker exec ${containerName} sh -c '${cmd.replace(/'/g, "'\\''")}'`,
|
|
30147
|
-
{ stdio: "pipe", timeout: 6e5 }
|
|
30148
|
-
);
|
|
29994
|
+
const result = execSync4(`docker exec ${containerName} sh -c '${cmd.replace(/'/g, "'\\''")}'`, { stdio: "pipe", timeout: 6e5 });
|
|
30149
29995
|
return { stdout: result.toString(), stderr: "", exitCode: 0 };
|
|
30150
29996
|
} catch (err) {
|
|
30151
29997
|
const execErr = err;
|
|
@@ -30202,12 +30048,11 @@ function planManifestPipeline(repos, baselineServices, worldId, branch, portOffs
|
|
|
30202
30048
|
const mergedServices = [...baselineServices];
|
|
30203
30049
|
const existingNames = new Set(mergedServices.map((s) => s.name));
|
|
30204
30050
|
for (const repo of repos) {
|
|
30205
|
-
if (!repo.manifest?.services)
|
|
30051
|
+
if (!repo.manifest?.services)
|
|
30052
|
+
continue;
|
|
30206
30053
|
for (const [svcName, svcConf] of Object.entries(repo.manifest.services)) {
|
|
30207
30054
|
if (existingNames.has(svcName)) {
|
|
30208
|
-
console.warn(
|
|
30209
|
-
`[manifest] step 2b service "${svcName}" from "${repo.name}" ignored (already declared by central or earlier manifest); first-write-wins`
|
|
30210
|
-
);
|
|
30055
|
+
console.warn(`[manifest] step 2b service "${svcName}" from "${repo.name}" ignored (already declared by central or earlier manifest); first-write-wins`);
|
|
30211
30056
|
continue;
|
|
30212
30057
|
}
|
|
30213
30058
|
mergedServices.push({
|
|
@@ -30217,79 +30062,63 @@ function planManifestPipeline(repos, baselineServices, worldId, branch, portOffs
|
|
|
30217
30062
|
environment: svcConf.environment
|
|
30218
30063
|
});
|
|
30219
30064
|
existingNames.add(svcName);
|
|
30220
|
-
console.log(
|
|
30221
|
-
`[manifest] step 2b service "${svcName}" registered from "${repo.name}"`
|
|
30222
|
-
);
|
|
30065
|
+
console.log(`[manifest] step 2b service "${svcName}" registered from "${repo.name}"`);
|
|
30223
30066
|
}
|
|
30224
30067
|
}
|
|
30225
30068
|
const ctx = {
|
|
30226
|
-
repos: Object.fromEntries(
|
|
30227
|
-
|
|
30228
|
-
|
|
30229
|
-
|
|
30230
|
-
|
|
30231
|
-
|
|
30232
|
-
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
),
|
|
30237
|
-
services: Object.fromEntries(
|
|
30238
|
-
mergedServices.map((s) => [s.name, { port: s.port, host: s.name }])
|
|
30239
|
-
),
|
|
30069
|
+
repos: Object.fromEntries(repos.map((r) => [
|
|
30070
|
+
r.name,
|
|
30071
|
+
{
|
|
30072
|
+
url: r.url,
|
|
30073
|
+
...r.path !== void 0 ? { path: r.path } : {},
|
|
30074
|
+
...r.manifest?.app?.port !== void 0 || r.app_port !== void 0 ? { port: r.manifest?.app?.port ?? r.app_port } : {},
|
|
30075
|
+
branch
|
|
30076
|
+
}
|
|
30077
|
+
])),
|
|
30078
|
+
services: Object.fromEntries(mergedServices.map((s) => [s.name, { port: s.port, host: s.name }])),
|
|
30240
30079
|
world: { id: worldId, branch, portOffset },
|
|
30241
30080
|
env: process.env
|
|
30242
30081
|
};
|
|
30243
30082
|
const crossRepoEnv = {};
|
|
30244
30083
|
for (const repo of repos) {
|
|
30245
|
-
if (!repo.manifest?.env)
|
|
30084
|
+
if (!repo.manifest?.env)
|
|
30085
|
+
continue;
|
|
30246
30086
|
try {
|
|
30247
|
-
const { env: resolvedEnv, warnings } = resolveManifestEnv(
|
|
30248
|
-
repo.manifest.env,
|
|
30249
|
-
ctx
|
|
30250
|
-
);
|
|
30087
|
+
const { env: resolvedEnv, warnings } = resolveManifestEnv(repo.manifest.env, ctx);
|
|
30251
30088
|
crossRepoEnv[repo.name] = resolvedEnv;
|
|
30252
30089
|
for (const w of warnings) {
|
|
30253
30090
|
console.warn(`[manifest] step 2c env warning for "${repo.name}": ${w}`);
|
|
30254
30091
|
}
|
|
30255
|
-
console.log(
|
|
30256
|
-
`[manifest] step 2c env resolved for "${repo.name}" (${Object.keys(resolvedEnv).length} keys)`
|
|
30257
|
-
);
|
|
30092
|
+
console.log(`[manifest] step 2c env resolved for "${repo.name}" (${Object.keys(resolvedEnv).length} keys)`);
|
|
30258
30093
|
} catch (err) {
|
|
30259
30094
|
if (err instanceof EnvSubstCycleError || err instanceof EnvSubstDepthExceededError) {
|
|
30260
30095
|
throw err;
|
|
30261
30096
|
}
|
|
30262
30097
|
const msg = err instanceof Error ? err.message : String(err);
|
|
30263
|
-
console.warn(
|
|
30264
|
-
`[manifest] step 2c env resolution failed for "${repo.name}": ${msg}`
|
|
30265
|
-
);
|
|
30098
|
+
console.warn(`[manifest] step 2c env resolution failed for "${repo.name}": ${msg}`);
|
|
30266
30099
|
}
|
|
30267
30100
|
}
|
|
30268
30101
|
return { services: mergedServices, crossRepoEnv };
|
|
30269
30102
|
}
|
|
30270
30103
|
async function runManifestRuntime(containerName, worldId, repos, exec) {
|
|
30271
30104
|
const hasAnyManifest = repos.some((r) => r.manifest !== void 0);
|
|
30272
|
-
if (!hasAnyManifest)
|
|
30105
|
+
if (!hasAnyManifest)
|
|
30106
|
+
return {};
|
|
30273
30107
|
const bootstrapTasks = [];
|
|
30274
30108
|
for (const repo of repos) {
|
|
30275
30109
|
const bootstrap = repo.manifest?.bootstrap;
|
|
30276
|
-
if (!bootstrap || bootstrap.length === 0)
|
|
30110
|
+
if (!bootstrap || bootstrap.length === 0)
|
|
30111
|
+
continue;
|
|
30277
30112
|
if (repo.setup_commands.length > 0) {
|
|
30278
|
-
console.warn(
|
|
30279
|
-
`[manifest] step 4e' both bootstrap[] and setup_commands populated for "${repo.name}". HARD-STOP semantics apply to bootstrap[] (OQ9): if any bootstrap step exits non-zero, the world transitions to 'error' and the legacy setup_commands path will NOT run as a fallback. Migrate setup_commands \u2192 manifest.bootstrap and remove the duplicate.`
|
|
30280
|
-
);
|
|
30113
|
+
console.warn(`[manifest] step 4e' both bootstrap[] and setup_commands populated for "${repo.name}". HARD-STOP semantics apply to bootstrap[] (OQ9): if any bootstrap step exits non-zero, the world transitions to 'error' and the legacy setup_commands path will NOT run as a fallback. Migrate setup_commands \u2192 manifest.bootstrap and remove the duplicate.`);
|
|
30281
30114
|
}
|
|
30282
30115
|
const workdir = `/home/olam/workspace/${repo.name}`;
|
|
30283
30116
|
const cmds = bootstrap.map(bootstrapStepCmd);
|
|
30284
|
-
console.log(
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
|
|
30289
|
-
await runBootstrap(containerName, workdir, cmds, exec);
|
|
30290
|
-
console.log(`[manifest] step 4e bootstrap "${repo.name}" complete`);
|
|
30291
|
-
})()
|
|
30292
|
-
);
|
|
30117
|
+
console.log(`[manifest] step 4e bootstrap "${repo.name}" (${cmds.length} steps)`);
|
|
30118
|
+
bootstrapTasks.push((async () => {
|
|
30119
|
+
await runBootstrap(containerName, workdir, cmds, exec);
|
|
30120
|
+
console.log(`[manifest] step 4e bootstrap "${repo.name}" complete`);
|
|
30121
|
+
})());
|
|
30293
30122
|
}
|
|
30294
30123
|
if (bootstrapTasks.length > 0) {
|
|
30295
30124
|
const results = await Promise.allSettled(bootstrapTasks);
|
|
@@ -30301,12 +30130,11 @@ async function runManifestRuntime(containerName, worldId, repos, exec) {
|
|
|
30301
30130
|
const supervisedRepos = [];
|
|
30302
30131
|
for (const repo of repos) {
|
|
30303
30132
|
const start = repo.manifest?.start;
|
|
30304
|
-
if (!start)
|
|
30133
|
+
if (!start)
|
|
30134
|
+
continue;
|
|
30305
30135
|
const hostPort = repo.manifest?.app?.port ?? repo.app_port ?? 0;
|
|
30306
30136
|
if (!Number.isInteger(hostPort) || hostPort < 1) {
|
|
30307
|
-
console.warn(
|
|
30308
|
-
`[manifest] step 4g skipping "${repo.name}" \u2014 no app.port (got ${hostPort})`
|
|
30309
|
-
);
|
|
30137
|
+
console.warn(`[manifest] step 4g skipping "${repo.name}" \u2014 no app.port (got ${hostPort})`);
|
|
30310
30138
|
continue;
|
|
30311
30139
|
}
|
|
30312
30140
|
supervisedRepos.push({
|
|
@@ -30317,19 +30145,11 @@ async function runManifestRuntime(containerName, worldId, repos, exec) {
|
|
|
30317
30145
|
manifestPath: repo.path ? `${repo.path}/.olam.yaml or ${repo.path}/.adb.yaml` : `<unknown manifest path for ${repo.name}>`
|
|
30318
30146
|
});
|
|
30319
30147
|
}
|
|
30320
|
-
if (supervisedRepos.length === 0)
|
|
30321
|
-
|
|
30322
|
-
|
|
30323
|
-
);
|
|
30324
|
-
|
|
30325
|
-
containerName,
|
|
30326
|
-
worldId,
|
|
30327
|
-
supervisedRepos,
|
|
30328
|
-
exec
|
|
30329
|
-
);
|
|
30330
|
-
console.log(
|
|
30331
|
-
`[manifest] step 4g tmux supervisor session=${supervisor.sessionName} created=${supervisor.windowsCreated.length} existing=${supervisor.windowsExisting.length}`
|
|
30332
|
-
);
|
|
30148
|
+
if (supervisedRepos.length === 0)
|
|
30149
|
+
return {};
|
|
30150
|
+
console.log(`[manifest] step 4f port pre-flight (${supervisedRepos.length} repos)`);
|
|
30151
|
+
const supervisor = await startSupervisedApps(containerName, worldId, supervisedRepos, exec);
|
|
30152
|
+
console.log(`[manifest] step 4g tmux supervisor session=${supervisor.sessionName} created=${supervisor.windowsCreated.length} existing=${supervisor.windowsExisting.length}`);
|
|
30333
30153
|
return { supervisor };
|
|
30334
30154
|
}
|
|
30335
30155
|
var ADJECTIVES = ["amber", "blue", "coral", "dawn", "ember", "frost", "gold", "haze", "iron", "jade"];
|
|
@@ -30341,28 +30161,29 @@ function generateWorldId() {
|
|
|
30341
30161
|
return `${adj}-${noun}-${num}`;
|
|
30342
30162
|
}
|
|
30343
30163
|
var AuthPreflightError = class extends Error {
|
|
30164
|
+
verdict;
|
|
30165
|
+
remedy;
|
|
30344
30166
|
constructor(verdict, message, remedy) {
|
|
30345
30167
|
super(message);
|
|
30346
30168
|
this.verdict = verdict;
|
|
30347
30169
|
this.remedy = remedy;
|
|
30348
30170
|
this.name = "AuthPreflightError";
|
|
30349
30171
|
}
|
|
30350
|
-
verdict;
|
|
30351
|
-
remedy;
|
|
30352
30172
|
};
|
|
30353
30173
|
var WorkspaceNotFoundError = class extends Error {
|
|
30174
|
+
workspaceName;
|
|
30354
30175
|
constructor(workspaceName) {
|
|
30355
30176
|
super(`Workspace "${workspaceName}" not found \u2014 run \`olam workspace add ${workspaceName}\` first, or check the name.`);
|
|
30356
30177
|
this.workspaceName = workspaceName;
|
|
30357
30178
|
this.name = "WorkspaceNotFoundError";
|
|
30358
30179
|
}
|
|
30359
|
-
workspaceName;
|
|
30360
30180
|
};
|
|
30361
30181
|
function buildManifestRuntime(worldId, repos) {
|
|
30362
30182
|
const runtimeRepos = [];
|
|
30363
30183
|
for (const repo of repos) {
|
|
30364
30184
|
const m = repo.manifest;
|
|
30365
|
-
if (!m)
|
|
30185
|
+
if (!m)
|
|
30186
|
+
continue;
|
|
30366
30187
|
const services = {};
|
|
30367
30188
|
for (const [name, svc] of Object.entries(m.services ?? {})) {
|
|
30368
30189
|
services[name] = {
|
|
@@ -30383,6 +30204,13 @@ function buildManifestRuntime(worldId, repos) {
|
|
|
30383
30204
|
return { worldId, repos: runtimeRepos };
|
|
30384
30205
|
}
|
|
30385
30206
|
var WorldManager = class {
|
|
30207
|
+
config;
|
|
30208
|
+
provider;
|
|
30209
|
+
registry;
|
|
30210
|
+
dashboardManager;
|
|
30211
|
+
pleriClient;
|
|
30212
|
+
dockerExec;
|
|
30213
|
+
manifestRuntimes = /* @__PURE__ */ new Map();
|
|
30386
30214
|
constructor(config2, provider, registry2, dashboardManager, pleriClient, dockerExec) {
|
|
30387
30215
|
this.config = config2;
|
|
30388
30216
|
this.provider = provider;
|
|
@@ -30391,13 +30219,6 @@ var WorldManager = class {
|
|
|
30391
30219
|
this.pleriClient = pleriClient;
|
|
30392
30220
|
this.dockerExec = dockerExec ?? defaultDockerExec();
|
|
30393
30221
|
}
|
|
30394
|
-
config;
|
|
30395
|
-
provider;
|
|
30396
|
-
registry;
|
|
30397
|
-
dashboardManager;
|
|
30398
|
-
pleriClient;
|
|
30399
|
-
dockerExec;
|
|
30400
|
-
manifestRuntimes = /* @__PURE__ */ new Map();
|
|
30401
30222
|
// -----------------------------------------------------------------------
|
|
30402
30223
|
// createWorld
|
|
30403
30224
|
// -----------------------------------------------------------------------
|
|
@@ -30463,10 +30284,12 @@ var WorldManager = class {
|
|
|
30463
30284
|
".env.test.local"
|
|
30464
30285
|
];
|
|
30465
30286
|
for (const repo of repos) {
|
|
30466
|
-
if (!repo.path)
|
|
30287
|
+
if (!repo.path)
|
|
30288
|
+
continue;
|
|
30467
30289
|
const sourceRoot = repo.path.replace(/^~/, os8.homedir());
|
|
30468
30290
|
const worktreeRoot = path18.join(workspacePath, repo.name);
|
|
30469
|
-
if (!fs14.existsSync(sourceRoot) || !fs14.existsSync(worktreeRoot))
|
|
30291
|
+
if (!fs14.existsSync(sourceRoot) || !fs14.existsSync(worktreeRoot))
|
|
30292
|
+
continue;
|
|
30470
30293
|
let copied = 0;
|
|
30471
30294
|
for (const pattern of RUNTIME_FILE_PATTERNS) {
|
|
30472
30295
|
const matches2 = [];
|
|
@@ -30477,7 +30300,8 @@ var WorldManager = class {
|
|
|
30477
30300
|
const ext = glob.replace(/^\*+/, "");
|
|
30478
30301
|
try {
|
|
30479
30302
|
for (const entry of fs14.readdirSync(sourceDir)) {
|
|
30480
|
-
if (ext === "" || entry.endsWith(ext))
|
|
30303
|
+
if (ext === "" || entry.endsWith(ext))
|
|
30304
|
+
matches2.push(path18.join(dir, entry));
|
|
30481
30305
|
}
|
|
30482
30306
|
} catch {
|
|
30483
30307
|
}
|
|
@@ -30490,7 +30314,8 @@ var WorldManager = class {
|
|
|
30490
30314
|
const dst = path18.join(worktreeRoot, rel);
|
|
30491
30315
|
try {
|
|
30492
30316
|
const st = fs14.statSync(src);
|
|
30493
|
-
if (!st.isFile())
|
|
30317
|
+
if (!st.isFile())
|
|
30318
|
+
continue;
|
|
30494
30319
|
fs14.mkdirSync(path18.dirname(dst), { recursive: true });
|
|
30495
30320
|
fs14.copyFileSync(src, dst);
|
|
30496
30321
|
copied++;
|
|
@@ -30514,13 +30339,7 @@ var WorldManager = class {
|
|
|
30514
30339
|
console.warn(`[manifest] worktree manifest discovery failed (non-fatal): ${msg}`);
|
|
30515
30340
|
}
|
|
30516
30341
|
const baselineServices = this.resolveServices(enrichedRepos);
|
|
30517
|
-
const { services, crossRepoEnv } = planManifestPipeline(
|
|
30518
|
-
enrichedRepos,
|
|
30519
|
-
baselineServices,
|
|
30520
|
-
worldId,
|
|
30521
|
-
branch,
|
|
30522
|
-
portOffset
|
|
30523
|
-
);
|
|
30342
|
+
const { services, crossRepoEnv } = planManifestPipeline(enrichedRepos, baselineServices, worldId, branch, portOffset);
|
|
30524
30343
|
this.manifestRuntimes.set(worldId, buildManifestRuntime(worldId, enrichedRepos));
|
|
30525
30344
|
const serviceEnv = {};
|
|
30526
30345
|
for (const svc of services) {
|
|
@@ -30623,17 +30442,13 @@ var WorldManager = class {
|
|
|
30623
30442
|
if (selected) {
|
|
30624
30443
|
selectedImage = selected.image;
|
|
30625
30444
|
cacheArchOverride = selected.cacheArch;
|
|
30626
|
-
console.log(
|
|
30627
|
-
`[WorldManager] image_selector matched \u2014 using ${selected.image} (tag=${selected.tag}${selected.cacheArch ? `, cache_arch=${selected.cacheArch}` : ""})`
|
|
30628
|
-
);
|
|
30445
|
+
console.log(`[WorldManager] image_selector matched \u2014 using ${selected.image} (tag=${selected.tag}${selected.cacheArch ? `, cache_arch=${selected.cacheArch}` : ""})`);
|
|
30629
30446
|
} else {
|
|
30630
30447
|
const hasRailsRepo = repos.some((r) => r.type === "rails");
|
|
30631
30448
|
if (hasRailsRepo) {
|
|
30632
30449
|
selectedImage = resolveDevboxImage(this.config, "amd64");
|
|
30633
30450
|
cacheArchOverride = "x64";
|
|
30634
|
-
console.log(
|
|
30635
|
-
`[WorldManager] Rails repo detected \u2014 using ${selectedImage} + x64 mise-cache (Rosetta path)`
|
|
30636
|
-
);
|
|
30451
|
+
console.log(`[WorldManager] Rails repo detected \u2014 using ${selectedImage} + x64 mise-cache (Rosetta path)`);
|
|
30637
30452
|
}
|
|
30638
30453
|
}
|
|
30639
30454
|
}
|
|
@@ -30652,18 +30467,24 @@ var WorldManager = class {
|
|
|
30652
30467
|
});
|
|
30653
30468
|
try {
|
|
30654
30469
|
const worldEnv = {};
|
|
30655
|
-
if (opts.task)
|
|
30470
|
+
if (opts.task)
|
|
30471
|
+
worldEnv.OLAM_TASK = opts.task;
|
|
30656
30472
|
const r2CredsPath = path18.join(os8.homedir(), ".olam", "r2-credentials.json");
|
|
30657
30473
|
if (fs14.existsSync(r2CredsPath)) {
|
|
30658
30474
|
try {
|
|
30659
30475
|
const r2Raw = fs14.readFileSync(r2CredsPath, "utf-8").trim();
|
|
30660
30476
|
if (r2Raw.length > 0) {
|
|
30661
30477
|
const r2 = JSON.parse(r2Raw);
|
|
30662
|
-
if (typeof r2.account_id === "string")
|
|
30663
|
-
|
|
30664
|
-
if (typeof r2.
|
|
30665
|
-
|
|
30666
|
-
if (typeof r2.
|
|
30478
|
+
if (typeof r2.account_id === "string")
|
|
30479
|
+
worldEnv.OLAM_R2_ACCOUNT_ID = r2.account_id;
|
|
30480
|
+
if (typeof r2.bucket === "string")
|
|
30481
|
+
worldEnv.OLAM_R2_BUCKET = r2.bucket;
|
|
30482
|
+
if (typeof r2.access_key_id === "string")
|
|
30483
|
+
worldEnv.OLAM_R2_ACCESS_KEY_ID = r2.access_key_id;
|
|
30484
|
+
if (typeof r2.secret_access_key === "string")
|
|
30485
|
+
worldEnv.OLAM_R2_SECRET_ACCESS_KEY = r2.secret_access_key;
|
|
30486
|
+
if (typeof r2.public_url_base === "string")
|
|
30487
|
+
worldEnv.OLAM_R2_PUBLIC_URL_BASE = r2.public_url_base;
|
|
30667
30488
|
}
|
|
30668
30489
|
} catch {
|
|
30669
30490
|
}
|
|
@@ -30676,7 +30497,8 @@ var WorldManager = class {
|
|
|
30676
30497
|
const parsed = YAML3.parse(keysRaw);
|
|
30677
30498
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
30678
30499
|
for (const [k, v] of Object.entries(parsed)) {
|
|
30679
|
-
if (typeof v !== "string")
|
|
30500
|
+
if (typeof v !== "string")
|
|
30501
|
+
continue;
|
|
30680
30502
|
const envKey = `OLAM_LLM_${k.toUpperCase()}`;
|
|
30681
30503
|
if (!(envKey in worldEnv)) {
|
|
30682
30504
|
worldEnv[envKey] = v;
|
|
@@ -30692,14 +30514,14 @@ var WorldManager = class {
|
|
|
30692
30514
|
const seenFromRepo = /* @__PURE__ */ new Map();
|
|
30693
30515
|
for (const repo of repos) {
|
|
30694
30516
|
const repoEnv = crossRepoEnv[repo.name];
|
|
30695
|
-
if (!repoEnv)
|
|
30517
|
+
if (!repoEnv)
|
|
30518
|
+
continue;
|
|
30696
30519
|
for (const [k, v] of Object.entries(repoEnv)) {
|
|
30697
|
-
if (PROTECTED_ENV_KEY_SET.has(k))
|
|
30520
|
+
if (PROTECTED_ENV_KEY_SET.has(k))
|
|
30521
|
+
continue;
|
|
30698
30522
|
const prevRepo = seenFromRepo.get(k);
|
|
30699
30523
|
if (prevRepo !== void 0) {
|
|
30700
|
-
console.warn(
|
|
30701
|
-
`[manifest] cross-repo env collision on "${k}": "${prevRepo}" wins over "${repo.name}" (iteration order)`
|
|
30702
|
-
);
|
|
30524
|
+
console.warn(`[manifest] cross-repo env collision on "${k}": "${prevRepo}" wins over "${repo.name}" (iteration order)`);
|
|
30703
30525
|
continue;
|
|
30704
30526
|
}
|
|
30705
30527
|
worldEnv[k] = v;
|
|
@@ -30713,7 +30535,8 @@ var WorldManager = class {
|
|
|
30713
30535
|
console.warn(`[secrets] ignoring "${k}" from GCP secret \u2014 OLAM_* keys are protected`);
|
|
30714
30536
|
continue;
|
|
30715
30537
|
}
|
|
30716
|
-
if (!(k in worldEnv))
|
|
30538
|
+
if (!(k in worldEnv))
|
|
30539
|
+
worldEnv[k] = v;
|
|
30717
30540
|
}
|
|
30718
30541
|
for (const { repoName, relativePath, content } of fileWrites) {
|
|
30719
30542
|
const absPath = path18.join(workspacePath, repoName, relativePath);
|
|
@@ -30773,7 +30596,8 @@ var WorldManager = class {
|
|
|
30773
30596
|
try {
|
|
30774
30597
|
await setupContainerGit(containerName, repos, branch);
|
|
30775
30598
|
} catch (err) {
|
|
30776
|
-
if (err instanceof BotIdentityError)
|
|
30599
|
+
if (err instanceof BotIdentityError)
|
|
30600
|
+
throw err;
|
|
30777
30601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
30778
30602
|
console.warn(`[WorldManager] container git setup failed: ${msg}`);
|
|
30779
30603
|
}
|
|
@@ -30829,12 +30653,8 @@ var WorldManager = class {
|
|
|
30829
30653
|
} catch (err) {
|
|
30830
30654
|
if (opts.allowBootstrapFailure && err instanceof BootstrapStepError) {
|
|
30831
30655
|
const msg = err instanceof Error ? err.message : String(err);
|
|
30832
|
-
console.warn(
|
|
30833
|
-
|
|
30834
|
-
);
|
|
30835
|
-
console.warn(
|
|
30836
|
-
`[manifest] WARN: world ${worldId} stays running; rerun the failing step manually inside the world if needed.`
|
|
30837
|
-
);
|
|
30656
|
+
console.warn(`[manifest] WARN: bootstrap failure allowed by --allow-bootstrap-failure: ${msg}`);
|
|
30657
|
+
console.warn(`[manifest] WARN: world ${worldId} stays running; rerun the failing step manually inside the world if needed.`);
|
|
30838
30658
|
} else if (err instanceof BootstrapStepError || err instanceof PortInUseError || err instanceof EnvSubstCycleError || err instanceof EnvSubstDepthExceededError) {
|
|
30839
30659
|
sm.transition("error");
|
|
30840
30660
|
this.registry.update(worldId, { status: "error" });
|
|
@@ -30857,10 +30677,7 @@ var WorldManager = class {
|
|
|
30857
30677
|
const escapedDir = `/home/olam/workspace/${repo.name.replace(/["$`\\]/g, "\\$&")}`;
|
|
30858
30678
|
for (const cmd of repo.setup_commands) {
|
|
30859
30679
|
try {
|
|
30860
|
-
execSync4(
|
|
30861
|
-
`docker exec ${containerName} sh -c 'cd "${escapedDir}" && ${cmd.replace(/'/g, "'\\''")}'`,
|
|
30862
|
-
{ stdio: "pipe", timeout: 6e5 }
|
|
30863
|
-
);
|
|
30680
|
+
execSync4(`docker exec ${containerName} sh -c 'cd "${escapedDir}" && ${cmd.replace(/'/g, "'\\''")}'`, { stdio: "pipe", timeout: 6e5 });
|
|
30864
30681
|
} catch (err) {
|
|
30865
30682
|
const msg = err instanceof Error ? err.message : String(err);
|
|
30866
30683
|
console.warn(`[WorldManager] setup command failed for ${repo.name}: ${msg}`);
|
|
@@ -30870,10 +30687,7 @@ var WorldManager = class {
|
|
|
30870
30687
|
}
|
|
30871
30688
|
if (credentialsInjected.claude) {
|
|
30872
30689
|
try {
|
|
30873
|
-
execSync4(
|
|
30874
|
-
`docker exec ${containerName} curl -sf -X POST http://localhost:8080/session/start-agent 2>/dev/null || true`,
|
|
30875
|
-
{ stdio: "pipe", timeout: 45e3 }
|
|
30876
|
-
);
|
|
30690
|
+
execSync4(`docker exec ${containerName} curl -sf -X POST http://localhost:8080/session/start-agent 2>/dev/null || true`, { stdio: "pipe", timeout: 45e3 });
|
|
30877
30691
|
} catch {
|
|
30878
30692
|
}
|
|
30879
30693
|
if (opts.task) {
|
|
@@ -30885,7 +30699,8 @@ var WorldManager = class {
|
|
|
30885
30699
|
});
|
|
30886
30700
|
const seen = /* @__PURE__ */ new Set();
|
|
30887
30701
|
const uniquePolicies = allPolicies.filter((p) => {
|
|
30888
|
-
if (seen.has(p.id))
|
|
30702
|
+
if (seen.has(p.id))
|
|
30703
|
+
return false;
|
|
30889
30704
|
seen.add(p.id);
|
|
30890
30705
|
return true;
|
|
30891
30706
|
});
|
|
@@ -30893,17 +30708,11 @@ var WorldManager = class {
|
|
|
30893
30708
|
const brief = formatPoliciesBrief(uniquePolicies);
|
|
30894
30709
|
taskWithPolicies = `${brief}## Your task
|
|
30895
30710
|
${opts.task}`;
|
|
30896
|
-
execSync4(
|
|
30897
|
-
`docker exec ${containerName} mkdir -p /home/olam/.olam/policies`,
|
|
30898
|
-
{ stdio: "pipe", timeout: 1e4 }
|
|
30899
|
-
);
|
|
30711
|
+
execSync4(`docker exec ${containerName} mkdir -p /home/olam/.olam/policies`, { stdio: "pipe", timeout: 1e4 });
|
|
30900
30712
|
for (const repo of repos) {
|
|
30901
30713
|
const policiesDir = path18.join(workspacePath, repo.name, ".olam", "policies");
|
|
30902
30714
|
if (fs14.existsSync(policiesDir)) {
|
|
30903
|
-
execSync4(
|
|
30904
|
-
`docker cp "${policiesDir}/." "${containerName}:/home/olam/.olam/policies/"`,
|
|
30905
|
-
{ stdio: "pipe", timeout: 15e3 }
|
|
30906
|
-
);
|
|
30715
|
+
execSync4(`docker cp "${policiesDir}/." "${containerName}:/home/olam/.olam/policies/"`, { stdio: "pipe", timeout: 15e3 });
|
|
30907
30716
|
}
|
|
30908
30717
|
}
|
|
30909
30718
|
}
|
|
@@ -30913,10 +30722,7 @@ ${opts.task}`;
|
|
|
30913
30722
|
}
|
|
30914
30723
|
try {
|
|
30915
30724
|
const payload = JSON.stringify({ prompt: taskWithPolicies }).replace(/'/g, "'\\''");
|
|
30916
|
-
execSync4(
|
|
30917
|
-
`docker exec ${containerName} curl -sf -X POST -H 'Content-Type: application/json' -d '${payload}' http://localhost:8080/dispatch 2>/dev/null || true`,
|
|
30918
|
-
{ stdio: "pipe", timeout: 3e4 }
|
|
30919
|
-
);
|
|
30725
|
+
execSync4(`docker exec ${containerName} curl -sf -X POST -H 'Content-Type: application/json' -d '${payload}' http://localhost:8080/dispatch 2>/dev/null || true`, { stdio: "pipe", timeout: 3e4 });
|
|
30920
30726
|
console.log("[world] Task auto-dispatched");
|
|
30921
30727
|
} catch {
|
|
30922
30728
|
}
|
|
@@ -30985,7 +30791,11 @@ ${opts.task}`;
|
|
|
30985
30791
|
}
|
|
30986
30792
|
try {
|
|
30987
30793
|
fs14.rmSync(world.workspacePath, { recursive: true, force: true });
|
|
30988
|
-
|
|
30794
|
+
if (fs14.existsSync(world.workspacePath)) {
|
|
30795
|
+
console.warn(`[WorldManager] destroyWorld(${worldId}): workspace dir ${world.workspacePath} still exists after rmSync. Run \`olam clean --apply\` to reap.`);
|
|
30796
|
+
}
|
|
30797
|
+
} catch (err) {
|
|
30798
|
+
console.warn(`[WorldManager] destroyWorld(${worldId}): failed to remove ${world.workspacePath}: ${err instanceof Error ? err.message : String(err)}. Run \`olam clean --apply\` to reap.`);
|
|
30989
30799
|
}
|
|
30990
30800
|
for (const repo of repos) {
|
|
30991
30801
|
try {
|
|
@@ -31000,7 +30810,8 @@ ${opts.task}`;
|
|
|
31000
30810
|
// -----------------------------------------------------------------------
|
|
31001
30811
|
async pauseWorld(worldId) {
|
|
31002
30812
|
const world = this.registry.get(worldId);
|
|
31003
|
-
if (!world)
|
|
30813
|
+
if (!world)
|
|
30814
|
+
throw new Error(`World "${worldId}" not found`);
|
|
31004
30815
|
const sm = new WorldStateMachine(worldId, world.status);
|
|
31005
30816
|
sm.transition("paused");
|
|
31006
30817
|
if (this.provider.pauseWorld) {
|
|
@@ -31010,7 +30821,8 @@ ${opts.task}`;
|
|
|
31010
30821
|
}
|
|
31011
30822
|
async resumeWorld(worldId) {
|
|
31012
30823
|
const world = this.registry.get(worldId);
|
|
31013
|
-
if (!world)
|
|
30824
|
+
if (!world)
|
|
30825
|
+
throw new Error(`World "${worldId}" not found`);
|
|
31014
30826
|
const sm = new WorldStateMachine(worldId, world.status);
|
|
31015
30827
|
sm.transition("running");
|
|
31016
30828
|
if (this.provider.resumeWorld) {
|
|
@@ -31042,7 +30854,8 @@ ${opts.task}`;
|
|
|
31042
30854
|
}
|
|
31043
30855
|
if (opts.workspace) {
|
|
31044
30856
|
const ws = readWorkspace(opts.workspace);
|
|
31045
|
-
if (!ws)
|
|
30857
|
+
if (!ws)
|
|
30858
|
+
throw new WorkspaceNotFoundError(opts.workspace);
|
|
31046
30859
|
return workspaceToRepoConfigs(ws);
|
|
31047
30860
|
}
|
|
31048
30861
|
return this.resolveRepos(void 0);
|
|
@@ -31066,7 +30879,8 @@ ${opts.task}`;
|
|
|
31066
30879
|
const planContent = fs14.readFileSync(planFilePath, "utf-8");
|
|
31067
30880
|
const planFileName = path18.basename(planFilePath);
|
|
31068
30881
|
const targetRepo = repoNames[0];
|
|
31069
|
-
if (!targetRepo)
|
|
30882
|
+
if (!targetRepo)
|
|
30883
|
+
return;
|
|
31070
30884
|
const plansDir = path18.join(workspacePath, targetRepo, "docs", "plans");
|
|
31071
30885
|
fs14.mkdirSync(plansDir, { recursive: true });
|
|
31072
30886
|
fs14.writeFileSync(path18.join(plansDir, planFileName), planContent);
|
|
@@ -31090,13 +30904,13 @@ ${opts.task}`;
|
|
|
31090
30904
|
}
|
|
31091
30905
|
};
|
|
31092
30906
|
|
|
31093
|
-
// ../core/
|
|
30907
|
+
// ../core/dist/cost/tracker.js
|
|
31094
30908
|
var CostTracker = class {
|
|
30909
|
+
budgetConfig;
|
|
30910
|
+
records = [];
|
|
31095
30911
|
constructor(budgetConfig) {
|
|
31096
30912
|
this.budgetConfig = budgetConfig;
|
|
31097
30913
|
}
|
|
31098
|
-
budgetConfig;
|
|
31099
|
-
records = [];
|
|
31100
30914
|
// ── Mutations ────────────────────────────────────────────────────────────
|
|
31101
30915
|
record(entry) {
|
|
31102
30916
|
this.records.push(entry);
|
|
@@ -31139,26 +30953,29 @@ var CostTracker = class {
|
|
|
31139
30953
|
}
|
|
31140
30954
|
};
|
|
31141
30955
|
function deriveStatus(percentUsed, warningThreshold) {
|
|
31142
|
-
if (percentUsed >= 1)
|
|
31143
|
-
|
|
30956
|
+
if (percentUsed >= 1)
|
|
30957
|
+
return "exceeded";
|
|
30958
|
+
if (percentUsed >= warningThreshold)
|
|
30959
|
+
return "warning";
|
|
31144
30960
|
return "ok";
|
|
31145
30961
|
}
|
|
31146
30962
|
function todayIsoPrefix() {
|
|
31147
30963
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
31148
30964
|
}
|
|
31149
30965
|
|
|
31150
|
-
// ../core/
|
|
31151
|
-
import "node:http";
|
|
30966
|
+
// ../core/dist/dashboard/index.js
|
|
30967
|
+
import * as http2 from "node:http";
|
|
31152
30968
|
|
|
31153
|
-
// ../core/
|
|
30969
|
+
// ../core/dist/dashboard/server.js
|
|
31154
30970
|
import * as http from "node:http";
|
|
31155
30971
|
import * as fs15 from "node:fs";
|
|
31156
30972
|
import * as path19 from "node:path";
|
|
31157
30973
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
31158
30974
|
|
|
31159
|
-
// ../core/
|
|
30975
|
+
// ../core/dist/dashboard/serialize.js
|
|
31160
30976
|
function serializeTokenUsage(usage) {
|
|
31161
|
-
if (!usage)
|
|
30977
|
+
if (!usage)
|
|
30978
|
+
return void 0;
|
|
31162
30979
|
return {
|
|
31163
30980
|
input_tokens: usage.inputTokens,
|
|
31164
30981
|
output_tokens: usage.outputTokens,
|
|
@@ -31201,20 +31018,26 @@ function parsePsAux(stdout) {
|
|
|
31201
31018
|
const result = [];
|
|
31202
31019
|
for (const line of dataLines) {
|
|
31203
31020
|
const trimmed = line.trim();
|
|
31204
|
-
if (!trimmed)
|
|
31021
|
+
if (!trimmed)
|
|
31022
|
+
continue;
|
|
31205
31023
|
const parts = trimmed.split(/\s+/);
|
|
31206
|
-
if (parts.length < 11)
|
|
31024
|
+
if (parts.length < 11)
|
|
31025
|
+
continue;
|
|
31207
31026
|
const pid = parseInt(parts[1], 10);
|
|
31208
|
-
if (isNaN(pid))
|
|
31027
|
+
if (isNaN(pid))
|
|
31028
|
+
continue;
|
|
31209
31029
|
const cpuPercent = parseFloat(parts[2]) || 0;
|
|
31210
31030
|
const rssKb = parseInt(parts[5], 10) || 0;
|
|
31211
31031
|
const stat = parts[7] ?? "";
|
|
31212
31032
|
const command = parts.slice(10).join(" ");
|
|
31213
31033
|
const name = command.split(/\s/)[0]?.split("/").pop() ?? command;
|
|
31214
31034
|
let status = "running";
|
|
31215
|
-
if (stat.startsWith("T"))
|
|
31216
|
-
|
|
31217
|
-
else if (stat.startsWith("
|
|
31035
|
+
if (stat.startsWith("T"))
|
|
31036
|
+
status = "stopped";
|
|
31037
|
+
else if (stat.startsWith("Z"))
|
|
31038
|
+
status = "error";
|
|
31039
|
+
else if (stat.startsWith("S") && cpuPercent === 0)
|
|
31040
|
+
status = "idle";
|
|
31218
31041
|
result.push({
|
|
31219
31042
|
pid,
|
|
31220
31043
|
name,
|
|
@@ -31311,7 +31134,7 @@ function serializeGanttTimeline(timeline) {
|
|
|
31311
31134
|
};
|
|
31312
31135
|
}
|
|
31313
31136
|
|
|
31314
|
-
// ../core/
|
|
31137
|
+
// ../core/dist/dashboard/session-aggregator.js
|
|
31315
31138
|
function sumTokenUsage(nodes) {
|
|
31316
31139
|
let hasAny = false;
|
|
31317
31140
|
let inputTokens = 0;
|
|
@@ -31319,28 +31142,33 @@ function sumTokenUsage(nodes) {
|
|
|
31319
31142
|
let cacheCreation = 0;
|
|
31320
31143
|
let cacheRead = 0;
|
|
31321
31144
|
for (const node of nodes) {
|
|
31322
|
-
if (!node.tokenUsage)
|
|
31145
|
+
if (!node.tokenUsage)
|
|
31146
|
+
continue;
|
|
31323
31147
|
hasAny = true;
|
|
31324
31148
|
inputTokens += node.tokenUsage.inputTokens;
|
|
31325
31149
|
outputTokens += node.tokenUsage.outputTokens;
|
|
31326
31150
|
cacheCreation += node.tokenUsage.cacheCreationInputTokens ?? 0;
|
|
31327
31151
|
cacheRead += node.tokenUsage.cacheReadInputTokens ?? 0;
|
|
31328
31152
|
}
|
|
31329
|
-
if (!hasAny)
|
|
31153
|
+
if (!hasAny)
|
|
31154
|
+
return null;
|
|
31330
31155
|
return { inputTokens, outputTokens, cacheCreationInputTokens: cacheCreation, cacheReadInputTokens: cacheRead };
|
|
31331
31156
|
}
|
|
31332
31157
|
function inferSessionStatus(nodes, lastAt) {
|
|
31333
31158
|
for (const n of nodes) {
|
|
31334
|
-
if (n.nodeType === "lifecycle" && n.hookEventName === "Stop")
|
|
31159
|
+
if (n.nodeType === "lifecycle" && n.hookEventName === "Stop")
|
|
31160
|
+
return "completed";
|
|
31335
31161
|
}
|
|
31336
31162
|
const lastMs = new Date(lastAt).getTime();
|
|
31337
31163
|
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
31338
|
-
if (lastMs > fiveMinAgo)
|
|
31164
|
+
if (lastMs > fiveMinAgo)
|
|
31165
|
+
return "running";
|
|
31339
31166
|
return "idle";
|
|
31340
31167
|
}
|
|
31341
31168
|
function deriveSessionTitle(nodes) {
|
|
31342
31169
|
for (const n of nodes) {
|
|
31343
|
-
if (n.nodeType === "intent" && n.summary)
|
|
31170
|
+
if (n.nodeType === "intent" && n.summary)
|
|
31171
|
+
return n.summary.slice(0, 100);
|
|
31344
31172
|
}
|
|
31345
31173
|
for (const n of nodes) {
|
|
31346
31174
|
if (n.nodeType === "lifecycle" && n.hookEventName === "SessionStart") {
|
|
@@ -31352,13 +31180,16 @@ function deriveSessionTitle(nodes) {
|
|
|
31352
31180
|
function countFilesModified(nodes) {
|
|
31353
31181
|
const files = /* @__PURE__ */ new Set();
|
|
31354
31182
|
for (const node of nodes) {
|
|
31355
|
-
if (node.nodeType !== "action")
|
|
31183
|
+
if (node.nodeType !== "action")
|
|
31184
|
+
continue;
|
|
31356
31185
|
const content = node.content;
|
|
31357
31186
|
const toolName = content.tool_name;
|
|
31358
|
-
if (toolName !== "Write" && toolName !== "Edit")
|
|
31187
|
+
if (toolName !== "Write" && toolName !== "Edit")
|
|
31188
|
+
continue;
|
|
31359
31189
|
const toolInput = content.tool_input;
|
|
31360
31190
|
const filePath = toolInput?.file_path;
|
|
31361
|
-
if (filePath)
|
|
31191
|
+
if (filePath)
|
|
31192
|
+
files.add(filePath);
|
|
31362
31193
|
}
|
|
31363
31194
|
return files.size;
|
|
31364
31195
|
}
|
|
@@ -31452,7 +31283,7 @@ function buildWorldTimeline(sessions) {
|
|
|
31452
31283
|
};
|
|
31453
31284
|
}
|
|
31454
31285
|
|
|
31455
|
-
// ../core/
|
|
31286
|
+
// ../core/dist/dashboard/server.js
|
|
31456
31287
|
var MIME = {
|
|
31457
31288
|
".html": "text/html; charset=utf-8",
|
|
31458
31289
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -31473,7 +31304,8 @@ function notFound(res) {
|
|
|
31473
31304
|
}
|
|
31474
31305
|
function openThoughtStore(workspacePath) {
|
|
31475
31306
|
const dbPath = getWorldDbPath(workspacePath);
|
|
31476
|
-
if (!fs15.existsSync(dbPath))
|
|
31307
|
+
if (!fs15.existsSync(dbPath))
|
|
31308
|
+
return null;
|
|
31477
31309
|
return new ThoughtLocalStore(dbPath);
|
|
31478
31310
|
}
|
|
31479
31311
|
function handleGetWorlds(registry2) {
|
|
@@ -31509,7 +31341,8 @@ function handleGetWorlds(registry2) {
|
|
|
31509
31341
|
}
|
|
31510
31342
|
function handleGetWorld(registry2, worldId) {
|
|
31511
31343
|
const w = registry2.get(worldId);
|
|
31512
|
-
if (!w)
|
|
31344
|
+
if (!w)
|
|
31345
|
+
return null;
|
|
31513
31346
|
let nodeCount = 0;
|
|
31514
31347
|
let edgeCount = 0;
|
|
31515
31348
|
let sessionCount = 0;
|
|
@@ -31537,8 +31370,10 @@ function handleGetWorld(registry2, worldId) {
|
|
|
31537
31370
|
latencyCount += 1;
|
|
31538
31371
|
}
|
|
31539
31372
|
}
|
|
31540
|
-
if (tokenSum > 0)
|
|
31541
|
-
|
|
31373
|
+
if (tokenSum > 0)
|
|
31374
|
+
totalTokens = tokenSum;
|
|
31375
|
+
if (latencyCount > 0)
|
|
31376
|
+
avgLatencyMs = Math.round(latencySum / latencyCount);
|
|
31542
31377
|
} finally {
|
|
31543
31378
|
store.close();
|
|
31544
31379
|
}
|
|
@@ -31560,7 +31395,8 @@ function handleGetWorld(registry2, worldId) {
|
|
|
31560
31395
|
}
|
|
31561
31396
|
function handleGetThoughts(registry2, worldId) {
|
|
31562
31397
|
const w = registry2.get(worldId);
|
|
31563
|
-
if (!w)
|
|
31398
|
+
if (!w)
|
|
31399
|
+
return null;
|
|
31564
31400
|
const store = openThoughtStore(w.workspacePath);
|
|
31565
31401
|
if (!store) {
|
|
31566
31402
|
return { nodes: [], edges: [] };
|
|
@@ -31609,10 +31445,12 @@ function handleGetCostsByWorld(registry2, worldId) {
|
|
|
31609
31445
|
function findSessionNodes(registry2, sessionId) {
|
|
31610
31446
|
for (const w of registry2.list()) {
|
|
31611
31447
|
const store = openThoughtStore(w.workspacePath);
|
|
31612
|
-
if (!store)
|
|
31448
|
+
if (!store)
|
|
31449
|
+
continue;
|
|
31613
31450
|
try {
|
|
31614
31451
|
const nodes = store.getNodesBySession(sessionId);
|
|
31615
|
-
if (nodes.length > 0)
|
|
31452
|
+
if (nodes.length > 0)
|
|
31453
|
+
return { worldId: w.id, nodes };
|
|
31616
31454
|
} finally {
|
|
31617
31455
|
store.close();
|
|
31618
31456
|
}
|
|
@@ -31622,11 +31460,13 @@ function findSessionNodes(registry2, sessionId) {
|
|
|
31622
31460
|
function findSessionInWorld(registry2, sessionId) {
|
|
31623
31461
|
for (const w of registry2.list()) {
|
|
31624
31462
|
const store = openThoughtStore(w.workspacePath);
|
|
31625
|
-
if (!store)
|
|
31463
|
+
if (!store)
|
|
31464
|
+
continue;
|
|
31626
31465
|
try {
|
|
31627
31466
|
const sessions = aggregateSessions(store, w.id);
|
|
31628
31467
|
const found = sessions.find((s) => s.id === sessionId);
|
|
31629
|
-
if (found)
|
|
31468
|
+
if (found)
|
|
31469
|
+
return found;
|
|
31630
31470
|
} finally {
|
|
31631
31471
|
store.close();
|
|
31632
31472
|
}
|
|
@@ -31669,13 +31509,15 @@ function createDashboardServer(opts) {
|
|
|
31669
31509
|
}
|
|
31670
31510
|
if (pathname === "/api/world" && req.method === "GET" && scopedWorldId) {
|
|
31671
31511
|
const data = handleGetWorld(registry2, scopedWorldId);
|
|
31672
|
-
if (!data)
|
|
31512
|
+
if (!data)
|
|
31513
|
+
return notFound(res);
|
|
31673
31514
|
jsonResponse(res, data);
|
|
31674
31515
|
return;
|
|
31675
31516
|
}
|
|
31676
31517
|
if (pathname === "/api/thoughts" && req.method === "GET" && scopedWorldId) {
|
|
31677
31518
|
const data = handleGetThoughts(registry2, scopedWorldId);
|
|
31678
|
-
if (!data)
|
|
31519
|
+
if (!data)
|
|
31520
|
+
return notFound(res);
|
|
31679
31521
|
jsonResponse(res, data);
|
|
31680
31522
|
return;
|
|
31681
31523
|
}
|
|
@@ -31683,7 +31525,8 @@ function createDashboardServer(opts) {
|
|
|
31683
31525
|
if (thoughtsMatch && req.method === "GET") {
|
|
31684
31526
|
const worldId = scopedWorldId ?? thoughtsMatch[1];
|
|
31685
31527
|
const data = handleGetThoughts(registry2, worldId);
|
|
31686
|
-
if (!data)
|
|
31528
|
+
if (!data)
|
|
31529
|
+
return notFound(res);
|
|
31687
31530
|
jsonResponse(res, data);
|
|
31688
31531
|
return;
|
|
31689
31532
|
}
|
|
@@ -31801,7 +31644,8 @@ function createDashboardServer(opts) {
|
|
|
31801
31644
|
if (worldMatch && req.method === "GET") {
|
|
31802
31645
|
const worldId = scopedWorldId ?? worldMatch[1];
|
|
31803
31646
|
const data = handleGetWorld(registry2, worldId);
|
|
31804
|
-
if (!data)
|
|
31647
|
+
if (!data)
|
|
31648
|
+
return notFound(res);
|
|
31805
31649
|
jsonResponse(res, data);
|
|
31806
31650
|
return;
|
|
31807
31651
|
}
|
|
@@ -31908,9 +31752,7 @@ function createDashboardServer(opts) {
|
|
|
31908
31752
|
}
|
|
31909
31753
|
if (!hasPublicDir) {
|
|
31910
31754
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
31911
|
-
res.end(
|
|
31912
|
-
`<html><body style="font-family:system-ui;padding:2rem"><h1>Olam Dashboard</h1><p>The React app has not been built yet.</p><p>Run <code>npm run build:app</code> in <code>packages/control-plane</code> to build it.</p><p>API routes are available at <code>/api/*</code>.</p></body></html>`
|
|
31913
|
-
);
|
|
31755
|
+
res.end(`<html><body style="font-family:system-ui;padding:2rem"><h1>Olam Dashboard</h1><p>The React app has not been built yet.</p><p>Run <code>npm run build:app</code> in <code>packages/control-plane</code> to build it.</p><p>API routes are available at <code>/api/*</code>.</p></body></html>`);
|
|
31914
31756
|
return;
|
|
31915
31757
|
}
|
|
31916
31758
|
let filePath = path19.join(publicDir, pathname === "/" ? "index.html" : pathname);
|
|
@@ -31937,7 +31779,7 @@ function createDashboardServer(opts) {
|
|
|
31937
31779
|
return server;
|
|
31938
31780
|
}
|
|
31939
31781
|
|
|
31940
|
-
// ../core/
|
|
31782
|
+
// ../core/dist/dashboard/state.js
|
|
31941
31783
|
import * as fs16 from "node:fs";
|
|
31942
31784
|
import * as os9 from "node:os";
|
|
31943
31785
|
import * as path20 from "node:path";
|
|
@@ -31962,7 +31804,8 @@ function clearDashboardState() {
|
|
|
31962
31804
|
}
|
|
31963
31805
|
function isDashboardRunning() {
|
|
31964
31806
|
const state = loadDashboardState();
|
|
31965
|
-
if (!state)
|
|
31807
|
+
if (!state)
|
|
31808
|
+
return false;
|
|
31966
31809
|
try {
|
|
31967
31810
|
process.kill(state.pid, 0);
|
|
31968
31811
|
return true;
|
|
@@ -31972,7 +31815,7 @@ function isDashboardRunning() {
|
|
|
31972
31815
|
}
|
|
31973
31816
|
}
|
|
31974
31817
|
|
|
31975
|
-
// ../core/
|
|
31818
|
+
// ../core/dist/dashboard/tunnel.js
|
|
31976
31819
|
import { spawn, execSync as execSync5 } from "node:child_process";
|
|
31977
31820
|
var tunnelProcess = null;
|
|
31978
31821
|
function isCloudflaredAvailable() {
|
|
@@ -31999,7 +31842,8 @@ function startTunnel(port) {
|
|
|
31999
31842
|
}
|
|
32000
31843
|
}, 3e4);
|
|
32001
31844
|
function scan(data) {
|
|
32002
|
-
if (resolved)
|
|
31845
|
+
if (resolved)
|
|
31846
|
+
return;
|
|
32003
31847
|
const text = data.toString();
|
|
32004
31848
|
const match = urlPattern.exec(text);
|
|
32005
31849
|
if (match) {
|
|
@@ -32036,14 +31880,14 @@ function getTunnelPid() {
|
|
|
32036
31880
|
return tunnelProcess?.pid;
|
|
32037
31881
|
}
|
|
32038
31882
|
|
|
32039
|
-
// ../core/
|
|
31883
|
+
// ../core/dist/dashboard/index.js
|
|
32040
31884
|
var DashboardManager = class {
|
|
32041
|
-
constructor(config2 = { port: 9740, tunnel: false }) {
|
|
32042
|
-
this.config = config2;
|
|
32043
|
-
}
|
|
32044
31885
|
config;
|
|
32045
31886
|
server = null;
|
|
32046
31887
|
info = null;
|
|
31888
|
+
constructor(config2 = { port: 9740, tunnel: false }) {
|
|
31889
|
+
this.config = config2;
|
|
31890
|
+
}
|
|
32047
31891
|
/**
|
|
32048
31892
|
* Ensure the dashboard is running. Idempotent — if already started
|
|
32049
31893
|
* (in this process or another), returns existing info.
|
|
@@ -32101,7 +31945,8 @@ var DashboardManager = class {
|
|
|
32101
31945
|
* Get current dashboard info, or null if not running.
|
|
32102
31946
|
*/
|
|
32103
31947
|
getInfo() {
|
|
32104
|
-
if (this.info)
|
|
31948
|
+
if (this.info)
|
|
31949
|
+
return this.info;
|
|
32105
31950
|
if (isDashboardRunning()) {
|
|
32106
31951
|
const state = loadDashboardState();
|
|
32107
31952
|
if (state) {
|
|
@@ -32129,7 +31974,7 @@ var DashboardManager = class {
|
|
|
32129
31974
|
}
|
|
32130
31975
|
};
|
|
32131
31976
|
|
|
32132
|
-
// ../core/
|
|
31977
|
+
// ../core/dist/pleri/client.js
|
|
32133
31978
|
var PleriClient = class {
|
|
32134
31979
|
baseUrl;
|
|
32135
31980
|
planeId;
|
|
@@ -32158,14 +32003,10 @@ var PleriClient = class {
|
|
|
32158
32003
|
if (!res.ok) {
|
|
32159
32004
|
const body = await res.text();
|
|
32160
32005
|
if (res.status === 401 || res.status === 403) {
|
|
32161
|
-
throw new Error(
|
|
32162
|
-
`Pleri authentication error (${res.status}): ${body}. Check your API key and plane membership.`
|
|
32163
|
-
);
|
|
32006
|
+
throw new Error(`Pleri authentication error (${res.status}): ${body}. Check your API key and plane membership.`);
|
|
32164
32007
|
}
|
|
32165
32008
|
if (res.status === 402) {
|
|
32166
|
-
throw new Error(
|
|
32167
|
-
`Pleri budget exhausted (402): ${body}. The plane's budget has been reached.`
|
|
32168
|
-
);
|
|
32009
|
+
throw new Error(`Pleri budget exhausted (402): ${body}. The plane's budget has been reached.`);
|
|
32169
32010
|
}
|
|
32170
32011
|
throw new Error(`Pleri API error (${res.status}): ${body}`);
|
|
32171
32012
|
}
|
|
@@ -32195,13 +32036,10 @@ var PleriClient = class {
|
|
|
32195
32036
|
* Sends optional final cost and crystallization status in the body.
|
|
32196
32037
|
*/
|
|
32197
32038
|
async deregisterWorld(worldId, data) {
|
|
32198
|
-
await this.fetch(
|
|
32199
|
-
|
|
32200
|
-
|
|
32201
|
-
|
|
32202
|
-
body: data ? JSON.stringify(data) : void 0
|
|
32203
|
-
}
|
|
32204
|
-
);
|
|
32039
|
+
await this.fetch(`${this.planeUrl}/worlds/${encodeURIComponent(worldId)}`, {
|
|
32040
|
+
method: "DELETE",
|
|
32041
|
+
body: data ? JSON.stringify(data) : void 0
|
|
32042
|
+
});
|
|
32205
32043
|
}
|
|
32206
32044
|
/**
|
|
32207
32045
|
* PUT {planeUrl}/worlds/{worldId}/status — send a heartbeat.
|
|
@@ -32209,13 +32047,10 @@ var PleriClient = class {
|
|
|
32209
32047
|
*/
|
|
32210
32048
|
async updateStatus(worldId, heartbeat) {
|
|
32211
32049
|
try {
|
|
32212
|
-
await this.fetch(
|
|
32213
|
-
|
|
32214
|
-
|
|
32215
|
-
|
|
32216
|
-
body: JSON.stringify(heartbeat)
|
|
32217
|
-
}
|
|
32218
|
-
);
|
|
32050
|
+
await this.fetch(`${this.planeUrl}/worlds/${encodeURIComponent(worldId)}/status`, {
|
|
32051
|
+
method: "PUT",
|
|
32052
|
+
body: JSON.stringify(heartbeat)
|
|
32053
|
+
});
|
|
32219
32054
|
} catch {
|
|
32220
32055
|
}
|
|
32221
32056
|
}
|
|
@@ -32225,13 +32060,10 @@ var PleriClient = class {
|
|
|
32225
32060
|
*/
|
|
32226
32061
|
async crystallize(req) {
|
|
32227
32062
|
try {
|
|
32228
|
-
return await this.fetch(
|
|
32229
|
-
|
|
32230
|
-
|
|
32231
|
-
|
|
32232
|
-
body: JSON.stringify(req)
|
|
32233
|
-
}
|
|
32234
|
-
);
|
|
32063
|
+
return await this.fetch(`${this.planeUrl}/worlds/${encodeURIComponent(req.worldId)}/crystallize`, {
|
|
32064
|
+
method: "POST",
|
|
32065
|
+
body: JSON.stringify(req)
|
|
32066
|
+
});
|
|
32235
32067
|
} catch (err) {
|
|
32236
32068
|
if (err instanceof Error && /authentication error/i.test(err.message)) {
|
|
32237
32069
|
throw err;
|