@pleri/olam-cli 0.1.135 → 0.1.137
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/kg-build.d.ts +29 -31
- package/dist/commands/kg-build.d.ts.map +1 -1
- package/dist/commands/kg-build.js +102 -190
- package/dist/commands/kg-build.js.map +1 -1
- package/dist/commands/kg-service-container.d.ts.map +1 -1
- package/dist/commands/kg-service-container.js +10 -0
- package/dist/commands/kg-service-container.js.map +1 -1
- package/dist/commands/memory/start.d.ts.map +1 -1
- package/dist/commands/memory/start.js +6 -0
- package/dist/commands/memory/start.js.map +1 -1
- package/dist/image-digests.json +6 -6
- package/dist/index.js +324 -306
- package/dist/mcp-server.js +209 -51
- package/host-cp/src/plan-chat-secret.mjs +92 -0
- package/host-cp/src/plan-chat-service.mjs +271 -0
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -11142,7 +11142,7 @@ function loadRepoManifest(repoDir) {
|
|
|
11142
11142
|
}
|
|
11143
11143
|
return { ...body, source };
|
|
11144
11144
|
}
|
|
11145
|
-
var runtimeSchema, appSchema, serviceSchema, deploySchema, BootstrapKindSchema, BootstrapStepSchema, RepoManifestSchema, KNOWN_TOP_LEVEL_KEYS, FORBIDDEN_KEYS;
|
|
11145
|
+
var runtimeSchema, appSchema, serviceSchema, deploySchema, planChatSchema, BootstrapKindSchema, BootstrapStepSchema, RepoManifestSchema, KNOWN_TOP_LEVEL_KEYS, FORBIDDEN_KEYS;
|
|
11146
11146
|
var init_repo_manifest = __esm({
|
|
11147
11147
|
"../core/dist/world/repo-manifest.js"() {
|
|
11148
11148
|
"use strict";
|
|
@@ -11248,6 +11248,9 @@ var init_repo_manifest = __esm({
|
|
|
11248
11248
|
deploySchema = external_exports.object({
|
|
11249
11249
|
tags: external_exports.array(external_exports.string()).optional()
|
|
11250
11250
|
}).passthrough();
|
|
11251
|
+
planChatSchema = external_exports.object({
|
|
11252
|
+
enabled: external_exports.boolean()
|
|
11253
|
+
}).strict();
|
|
11251
11254
|
BootstrapKindSchema = external_exports.enum(["gems", "node", "pg"]);
|
|
11252
11255
|
BootstrapStepSchema = external_exports.union([
|
|
11253
11256
|
external_exports.string(),
|
|
@@ -11270,7 +11273,8 @@ var init_repo_manifest = __esm({
|
|
|
11270
11273
|
secrets: external_exports.string().optional(),
|
|
11271
11274
|
bootstrap: external_exports.array(BootstrapStepSchema).optional(),
|
|
11272
11275
|
start: external_exports.string().optional(),
|
|
11273
|
-
deploy: deploySchema.optional()
|
|
11276
|
+
deploy: deploySchema.optional(),
|
|
11277
|
+
plan_chat: planChatSchema.optional()
|
|
11274
11278
|
// TODO(phase-C): runtime field consumed by stack-install.ts (T16).
|
|
11275
11279
|
// TODO(phase-D): app.fixed (sticky port) consumed by port-allocation.
|
|
11276
11280
|
// TODO(phase-D): idempotent_check on bootstrap steps in bootstrap-runner.
|
|
@@ -11278,11 +11282,11 @@ var init_repo_manifest = __esm({
|
|
|
11278
11282
|
}).passthrough().superRefine((val, ctx) => {
|
|
11279
11283
|
refineForbiddenKeys(val, [], ctx, true);
|
|
11280
11284
|
}).superRefine((val, ctx) => {
|
|
11281
|
-
const hasContent = val.version !== void 0 || val.runtime !== void 0 || val.app !== void 0 || val.services !== void 0 || val.env !== void 0 || val.secrets !== void 0 || val.bootstrap !== void 0 || val.start !== void 0 || val.deploy !== void 0;
|
|
11285
|
+
const hasContent = val.version !== void 0 || val.runtime !== void 0 || val.app !== void 0 || val.services !== void 0 || val.env !== void 0 || val.secrets !== void 0 || val.bootstrap !== void 0 || val.start !== void 0 || val.deploy !== void 0 || val.plan_chat !== void 0;
|
|
11282
11286
|
if (!hasContent) {
|
|
11283
11287
|
ctx.addIssue({
|
|
11284
11288
|
code: external_exports.ZodIssueCode.custom,
|
|
11285
|
-
message: "Manifest must declare at least one of: version, runtime, app, services, env, secrets, bootstrap, start, deploy"
|
|
11289
|
+
message: "Manifest must declare at least one of: version, runtime, app, services, env, secrets, bootstrap, start, deploy, plan_chat"
|
|
11286
11290
|
});
|
|
11287
11291
|
}
|
|
11288
11292
|
});
|
|
@@ -11296,6 +11300,10 @@ var init_repo_manifest = __esm({
|
|
|
11296
11300
|
"bootstrap",
|
|
11297
11301
|
"start",
|
|
11298
11302
|
"deploy",
|
|
11303
|
+
// olam-plan-chat-chunks-substrate Phase A task A4: opt-in for the chunks
|
|
11304
|
+
// thought substrate. Read by `resolveThoughtSubstrate` in
|
|
11305
|
+
// `packages/core/src/thought/substrate-router.ts`.
|
|
11306
|
+
"plan_chat",
|
|
11299
11307
|
// F-7 (olam-hybrid-shared-postgres dogfood finding): native inheritance —
|
|
11300
11308
|
// `.olam.yaml` may declare `inherits: .adb.yaml` to deep-merge an adb-shaped
|
|
11301
11309
|
// base manifest into itself. Retires the atlas-one `bin/olam-create-wrap.sh`
|
|
@@ -11576,7 +11584,24 @@ var init_schema2 = __esm({
|
|
|
11576
11584
|
* (B1/C4) reads this file and forwards the value as
|
|
11577
11585
|
* `AGENTMEMORY_SECRET` into worlds.
|
|
11578
11586
|
*/
|
|
11579
|
-
secret_ref: external_exports.string().min(1).optional().default("~/.olam/cloud-memory-secret")
|
|
11587
|
+
secret_ref: external_exports.string().min(1).optional().default("~/.olam/cloud-memory-secret"),
|
|
11588
|
+
/**
|
|
11589
|
+
* DO SQLite write-through bridge toggle. Defaults to `true` (bridge
|
|
11590
|
+
* active — every state-mutating call captures the engine's export to
|
|
11591
|
+
* a DO snapshot, restored on cold-start).
|
|
11592
|
+
*
|
|
11593
|
+
* Operators set `bridge: false` to bisect bridge vs engine bugs:
|
|
11594
|
+
* the CLI threads this into the Worker as `OLAM_BRIDGE_DISABLED=1`,
|
|
11595
|
+
* which makes `AgentMemoryContainer.fetch` a plain pass-through
|
|
11596
|
+
* (no capture, no restore). The next deploy with `bridge: true`
|
|
11597
|
+
* resumes captures automatically.
|
|
11598
|
+
*
|
|
11599
|
+
* See `docs/design/olam-agent-memory-do-bridge-schema.md` for the
|
|
11600
|
+
* full storage contract; the bridge is OFF only as a diagnosis aid.
|
|
11601
|
+
*
|
|
11602
|
+
* Plan reference: docs/plans/olam-agent-memory-do-sqlite-bridge/phase-c-tasks.md C3
|
|
11603
|
+
*/
|
|
11604
|
+
bridge: external_exports.boolean().optional().default(true)
|
|
11580
11605
|
});
|
|
11581
11606
|
MemorySchema = external_exports.object({
|
|
11582
11607
|
mode: external_exports.enum(["local", "cloud"]).optional().default("local"),
|
|
@@ -24222,6 +24247,10 @@ var createWorldContainer = async (docker, worldId, worldName, image, env, resour
|
|
|
24222
24247
|
opts.push(`size=${m.size}`);
|
|
24223
24248
|
if (m.mode !== void 0)
|
|
24224
24249
|
opts.push(`mode=${m.mode.toString(8).padStart(4, "0")}`);
|
|
24250
|
+
if (m.uid !== void 0)
|
|
24251
|
+
opts.push(`uid=${m.uid}`);
|
|
24252
|
+
if (m.gid !== void 0)
|
|
24253
|
+
opts.push(`gid=${m.gid}`);
|
|
24225
24254
|
acc[m.target] = opts.join(",");
|
|
24226
24255
|
return acc;
|
|
24227
24256
|
}, {})
|
|
@@ -30679,6 +30708,119 @@ function createServer4(ctx, initError) {
|
|
|
30679
30708
|
return server;
|
|
30680
30709
|
}
|
|
30681
30710
|
|
|
30711
|
+
// ../mcp-server/src/utils/native-probe.ts
|
|
30712
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
30713
|
+
var PROBE_REQUIRE = createRequire4(import.meta.url);
|
|
30714
|
+
function runtimeModuleVersion() {
|
|
30715
|
+
return Number.parseInt(process.versions.modules, 10);
|
|
30716
|
+
}
|
|
30717
|
+
function parseAbiMessage(message) {
|
|
30718
|
+
const compiledMatch = message.match(/NODE_MODULE_VERSION\s+(\d+)/);
|
|
30719
|
+
const requiredMatch = message.match(/requires\s+NODE_MODULE_VERSION\s+(\d+)/);
|
|
30720
|
+
const compiledRaw = compiledMatch?.[1];
|
|
30721
|
+
const requiredRaw = requiredMatch?.[1];
|
|
30722
|
+
return {
|
|
30723
|
+
compiledFor: compiledRaw !== void 0 ? Number.parseInt(compiledRaw, 10) : void 0,
|
|
30724
|
+
required: requiredRaw !== void 0 ? Number.parseInt(requiredRaw, 10) : void 0
|
|
30725
|
+
};
|
|
30726
|
+
}
|
|
30727
|
+
function classifyLoadError(err, bindingPath) {
|
|
30728
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
30729
|
+
if (err.code === "MODULE_NOT_FOUND" || /Cannot find module 'better-sqlite3'/.test(message)) {
|
|
30730
|
+
return {
|
|
30731
|
+
kind: "missing",
|
|
30732
|
+
diagnostic: formatMissingPackage(err)
|
|
30733
|
+
};
|
|
30734
|
+
}
|
|
30735
|
+
const abi = parseAbiMessage(message);
|
|
30736
|
+
if (err.code === "ERR_DLOPEN_FAILED" || abi.compiledFor !== void 0 || abi.required !== void 0) {
|
|
30737
|
+
return {
|
|
30738
|
+
kind: "abi",
|
|
30739
|
+
diagnostic: formatAbiMismatch(err, abi.compiledFor, abi.required, bindingPath),
|
|
30740
|
+
compiledFor: abi.compiledFor,
|
|
30741
|
+
required: abi.required
|
|
30742
|
+
};
|
|
30743
|
+
}
|
|
30744
|
+
return { kind: "unclassified", diagnostic: "" };
|
|
30745
|
+
}
|
|
30746
|
+
function formatMissingPackage(err) {
|
|
30747
|
+
return [
|
|
30748
|
+
"",
|
|
30749
|
+
"\u274C MCP olam server cannot start.",
|
|
30750
|
+
" Reason: better-sqlite3 native module is not installed.",
|
|
30751
|
+
` Runtime: ${process.version} (NODE_MODULE_VERSION ${runtimeModuleVersion()})`,
|
|
30752
|
+
` Detail: ${err.message}`,
|
|
30753
|
+
"",
|
|
30754
|
+
"Fix:",
|
|
30755
|
+
" Run `npm install` in the workspace that hosts this binary,",
|
|
30756
|
+
" or reinstall the CLI: `npm install -g @pleri/olam-cli`.",
|
|
30757
|
+
""
|
|
30758
|
+
].join("\n");
|
|
30759
|
+
}
|
|
30760
|
+
function formatAbiMismatch(err, compiledFor, required2, bindingPath) {
|
|
30761
|
+
const lines = [
|
|
30762
|
+
"",
|
|
30763
|
+
"\u274C MCP olam server cannot start.",
|
|
30764
|
+
" Reason: better-sqlite3 native binding is ABI-incompatible.",
|
|
30765
|
+
` Runtime: ${process.version} (NODE_MODULE_VERSION ${required2 ?? runtimeModuleVersion()})`
|
|
30766
|
+
];
|
|
30767
|
+
if (compiledFor !== void 0) {
|
|
30768
|
+
lines.push(` Binding: compiled for NODE_MODULE_VERSION ${compiledFor}`);
|
|
30769
|
+
}
|
|
30770
|
+
lines.push(` Path: ${bindingPath}`);
|
|
30771
|
+
lines.push(` Node: ${process.execPath}`);
|
|
30772
|
+
lines.push("");
|
|
30773
|
+
lines.push("Fix:");
|
|
30774
|
+
lines.push(" Rebuild the native binding against the runtime Node version:");
|
|
30775
|
+
lines.push(" npm rebuild better-sqlite3");
|
|
30776
|
+
lines.push("");
|
|
30777
|
+
lines.push(" If you installed under a different Node version (via nvm/asdf/mise),");
|
|
30778
|
+
lines.push(" switch to that version first, then rebuild, OR reinstall the package");
|
|
30779
|
+
lines.push(" so prebuild-install fetches the correct binary for the current Node.");
|
|
30780
|
+
lines.push("");
|
|
30781
|
+
lines.push(` Underlying error: ${err.message.split("\n")[0]}`);
|
|
30782
|
+
lines.push("");
|
|
30783
|
+
return lines.join("\n");
|
|
30784
|
+
}
|
|
30785
|
+
function defaultResolver() {
|
|
30786
|
+
try {
|
|
30787
|
+
return PROBE_REQUIRE.resolve("better-sqlite3");
|
|
30788
|
+
} catch {
|
|
30789
|
+
return "(unresolved)";
|
|
30790
|
+
}
|
|
30791
|
+
}
|
|
30792
|
+
function defaultLoader() {
|
|
30793
|
+
return PROBE_REQUIRE("better-sqlite3");
|
|
30794
|
+
}
|
|
30795
|
+
function defaultEmit(message) {
|
|
30796
|
+
process.stderr.write(message.endsWith("\n") ? message : `${message}
|
|
30797
|
+
`);
|
|
30798
|
+
}
|
|
30799
|
+
var defaultExit = (code) => process.exit(code);
|
|
30800
|
+
function assertBetterSqlite3Loadable(deps = {}) {
|
|
30801
|
+
const loader = deps.loader ?? defaultLoader;
|
|
30802
|
+
const resolver = deps.resolver ?? defaultResolver;
|
|
30803
|
+
const emit = deps.emit ?? defaultEmit;
|
|
30804
|
+
const exit = deps.exit ?? defaultExit;
|
|
30805
|
+
try {
|
|
30806
|
+
const Database = loader();
|
|
30807
|
+
const db = new Database(":memory:");
|
|
30808
|
+
try {
|
|
30809
|
+
db.prepare("SELECT 1").get();
|
|
30810
|
+
} finally {
|
|
30811
|
+
db.close();
|
|
30812
|
+
}
|
|
30813
|
+
} catch (raw) {
|
|
30814
|
+
const err = raw;
|
|
30815
|
+
const classified = classifyLoadError(err, resolver());
|
|
30816
|
+
if (classified.kind === "unclassified") {
|
|
30817
|
+
throw err;
|
|
30818
|
+
}
|
|
30819
|
+
emit(classified.diagnostic);
|
|
30820
|
+
exit(1);
|
|
30821
|
+
}
|
|
30822
|
+
}
|
|
30823
|
+
|
|
30682
30824
|
// ../mcp-server/src/index.ts
|
|
30683
30825
|
init_loader();
|
|
30684
30826
|
|
|
@@ -31707,18 +31849,18 @@ function computeFingerprint(runtimes) {
|
|
|
31707
31849
|
const joined = parts.join("_");
|
|
31708
31850
|
return sanitizeTag(joined);
|
|
31709
31851
|
}
|
|
31710
|
-
function computeImageTag(runtimes) {
|
|
31711
|
-
const baseDigest =
|
|
31852
|
+
function computeImageTag(runtimes, baseImageRef = `${BASE_IMAGE}:latest`) {
|
|
31853
|
+
const baseDigest = getImageDigest(baseImageRef);
|
|
31712
31854
|
const prefix = baseDigest.slice(0, 8);
|
|
31713
31855
|
const fingerprint = computeFingerprint(runtimes);
|
|
31714
31856
|
const tag = `${prefix}_${fingerprint}`;
|
|
31715
31857
|
return sanitizeTag(tag);
|
|
31716
31858
|
}
|
|
31717
|
-
function computeImageName(runtimes) {
|
|
31718
|
-
return `${BASE_IMAGE}:${computeImageTag(runtimes)}`;
|
|
31859
|
+
function computeImageName(runtimes, baseImageRef = `${BASE_IMAGE}:latest`) {
|
|
31860
|
+
return `${BASE_IMAGE}:${computeImageTag(runtimes, baseImageRef)}`;
|
|
31719
31861
|
}
|
|
31720
|
-
function lookupCachedImage(runtimes) {
|
|
31721
|
-
const tag = computeImageTag(runtimes);
|
|
31862
|
+
function lookupCachedImage(runtimes, baseImageRef = `${BASE_IMAGE}:latest`) {
|
|
31863
|
+
const tag = computeImageTag(runtimes, baseImageRef);
|
|
31722
31864
|
const imageName = `${BASE_IMAGE}:${tag}`;
|
|
31723
31865
|
try {
|
|
31724
31866
|
execSync3(`docker image inspect ${imageName} > /dev/null 2>&1`, {
|
|
@@ -31735,19 +31877,25 @@ function commitAsImage(containerName, imageName) {
|
|
|
31735
31877
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31736
31878
|
execSync3(`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 });
|
|
31737
31879
|
}
|
|
31738
|
-
var
|
|
31739
|
-
function
|
|
31740
|
-
|
|
31741
|
-
|
|
31880
|
+
var cachedImageDigests = /* @__PURE__ */ new Map();
|
|
31881
|
+
function getImageDigest(imageRef) {
|
|
31882
|
+
const memo = cachedImageDigests.get(imageRef);
|
|
31883
|
+
if (memo)
|
|
31884
|
+
return memo;
|
|
31742
31885
|
try {
|
|
31743
|
-
const digest = execSync3(`docker inspect ${
|
|
31744
|
-
|
|
31745
|
-
|
|
31886
|
+
const digest = execSync3(`docker inspect ${imageRef} --format '{{.Id}}'`, { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
31887
|
+
const short = digest.replace("sha256:", "").slice(0, 16);
|
|
31888
|
+
cachedImageDigests.set(imageRef, short);
|
|
31889
|
+
return short;
|
|
31746
31890
|
} catch {
|
|
31747
|
-
|
|
31748
|
-
|
|
31891
|
+
const fallback = crypto4.createHash("sha256").update(imageRef).digest("hex").slice(0, 16);
|
|
31892
|
+
cachedImageDigests.set(imageRef, fallback);
|
|
31893
|
+
return fallback;
|
|
31749
31894
|
}
|
|
31750
31895
|
}
|
|
31896
|
+
function getBaseImageDigest() {
|
|
31897
|
+
return getImageDigest(`${BASE_IMAGE}:latest`);
|
|
31898
|
+
}
|
|
31751
31899
|
function sanitizeTag(raw) {
|
|
31752
31900
|
let tag = raw.toLowerCase().replace(/[^a-z0-9._-]/g, "-");
|
|
31753
31901
|
if (tag.length > MAX_TAG_LENGTH) {
|
|
@@ -33636,9 +33784,32 @@ ${detail}`);
|
|
|
33636
33784
|
}
|
|
33637
33785
|
}
|
|
33638
33786
|
const repoSecretUrls = enrichedRepos.filter((r) => typeof r.manifest?.secrets === "string" && r.manifest.secrets.length > 0).map((r) => ({ repoName: r.name, secretsUrl: r.manifest.secrets }));
|
|
33639
|
-
let stackCacheHit = false;
|
|
33640
33787
|
let selectedImage;
|
|
33788
|
+
let cacheArchOverride;
|
|
33789
|
+
const selected = selectDevboxImageForWorld({
|
|
33790
|
+
config: this.config,
|
|
33791
|
+
repos,
|
|
33792
|
+
worldspec: opts.worldspec
|
|
33793
|
+
});
|
|
33794
|
+
if (selected) {
|
|
33795
|
+
selectedImage = selected.image;
|
|
33796
|
+
cacheArchOverride = selected.cacheArch;
|
|
33797
|
+
if (selected.source === "worldspec") {
|
|
33798
|
+
console.log(`[WorldManager] worldspec override \u2014 using ${selected.image} (tag=${selected.tag})`);
|
|
33799
|
+
} else {
|
|
33800
|
+
console.log(`[WorldManager] image_selector matched \u2014 using ${selected.image} (tag=${selected.tag}${selected.cacheArch ? `, cache_arch=${selected.cacheArch}` : ""})`);
|
|
33801
|
+
}
|
|
33802
|
+
} else {
|
|
33803
|
+
const hasRailsRepo = repos.some((r) => r.type === "rails");
|
|
33804
|
+
if (hasRailsRepo) {
|
|
33805
|
+
selectedImage = resolveDevboxImage(this.config, "amd64");
|
|
33806
|
+
cacheArchOverride = "x64";
|
|
33807
|
+
console.log(`[WorldManager] Rails repo detected \u2014 using ${selectedImage} + x64 mise-cache (Rosetta path)`);
|
|
33808
|
+
}
|
|
33809
|
+
}
|
|
33810
|
+
let stackCacheHit = false;
|
|
33641
33811
|
const preDetectedStacks = /* @__PURE__ */ new Map();
|
|
33812
|
+
const cacheBaseRef = selectedImage ?? "olam-devbox:latest";
|
|
33642
33813
|
if (this.provider.capabilities.supportsCustomImages) {
|
|
33643
33814
|
try {
|
|
33644
33815
|
const hostExec = makeHostExecFn();
|
|
@@ -33653,7 +33824,7 @@ ${detail}`);
|
|
|
33653
33824
|
}
|
|
33654
33825
|
const runtimes = collectUniqueRuntimes(Array.from(preDetectedStacks.values()));
|
|
33655
33826
|
if (runtimes.size > 0) {
|
|
33656
|
-
const cacheResult = lookupCachedImage(runtimes);
|
|
33827
|
+
const cacheResult = lookupCachedImage(runtimes, cacheBaseRef);
|
|
33657
33828
|
if (cacheResult.hit) {
|
|
33658
33829
|
selectedImage = cacheResult.imageName;
|
|
33659
33830
|
stackCacheHit = true;
|
|
@@ -33667,30 +33838,6 @@ ${detail}`);
|
|
|
33667
33838
|
console.warn(`[WorldManager] host-side stack detection failed: ${msg}`);
|
|
33668
33839
|
}
|
|
33669
33840
|
}
|
|
33670
|
-
let cacheArchOverride;
|
|
33671
|
-
if (!stackCacheHit) {
|
|
33672
|
-
const selected = selectDevboxImageForWorld({
|
|
33673
|
-
config: this.config,
|
|
33674
|
-
repos,
|
|
33675
|
-
worldspec: opts.worldspec
|
|
33676
|
-
});
|
|
33677
|
-
if (selected) {
|
|
33678
|
-
selectedImage = selected.image;
|
|
33679
|
-
cacheArchOverride = selected.cacheArch;
|
|
33680
|
-
if (selected.source === "worldspec") {
|
|
33681
|
-
console.log(`[WorldManager] worldspec override \u2014 using ${selected.image} (tag=${selected.tag})`);
|
|
33682
|
-
} else {
|
|
33683
|
-
console.log(`[WorldManager] image_selector matched \u2014 using ${selected.image} (tag=${selected.tag}${selected.cacheArch ? `, cache_arch=${selected.cacheArch}` : ""})`);
|
|
33684
|
-
}
|
|
33685
|
-
} else {
|
|
33686
|
-
const hasRailsRepo = repos.some((r) => r.type === "rails");
|
|
33687
|
-
if (hasRailsRepo) {
|
|
33688
|
-
selectedImage = resolveDevboxImage(this.config, "amd64");
|
|
33689
|
-
cacheArchOverride = "x64";
|
|
33690
|
-
console.log(`[WorldManager] Rails repo detected \u2014 using ${selectedImage} + x64 mise-cache (Rosetta path)`);
|
|
33691
|
-
}
|
|
33692
|
-
}
|
|
33693
|
-
}
|
|
33694
33841
|
const appPorts = [];
|
|
33695
33842
|
for (const repo of enrichedRepos) {
|
|
33696
33843
|
const manifestPort = repo.manifest?.app?.port;
|
|
@@ -33844,12 +33991,22 @@ ${detail}`);
|
|
|
33844
33991
|
...extraNetworks ? { extraNetworks } : {},
|
|
33845
33992
|
// Closes SEC-002 residual: hybrid worlds get a tmpfs mount at
|
|
33846
33993
|
// /run/olam to receive the per-world postgres credentials. Size
|
|
33847
|
-
// 256K is generous for a few env files; mode 0700
|
|
33848
|
-
//
|
|
33849
|
-
//
|
|
33850
|
-
//
|
|
33994
|
+
// 256K is generous for a few env files; mode 0700 keeps the dir
|
|
33995
|
+
// owner-only. uid/gid 999 matches the `olam` user from the Phase
|
|
33996
|
+
// E E4 base image contract — without setting these, the tmpfs is
|
|
33997
|
+
// root-owned and `docker exec` (which runs as the image's USER,
|
|
33998
|
+
// i.e. olam) hits EACCES on credential writes. Non-hybrid worlds
|
|
33999
|
+
// skip the mount entirely — no behavior change.
|
|
33851
34000
|
...tmpfsPostgresCredContent ? {
|
|
33852
|
-
tmpfsMounts: [
|
|
34001
|
+
tmpfsMounts: [
|
|
34002
|
+
{
|
|
34003
|
+
target: "/run/olam",
|
|
34004
|
+
size: 256 * 1024,
|
|
34005
|
+
mode: 448,
|
|
34006
|
+
uid: 999,
|
|
34007
|
+
gid: 999
|
|
34008
|
+
}
|
|
34009
|
+
],
|
|
33853
34010
|
tmpfsCredentialWrites: [
|
|
33854
34011
|
{ path: "/run/olam/postgres.env", content: tmpfsPostgresCredContent }
|
|
33855
34012
|
]
|
|
@@ -35915,6 +36072,7 @@ function loadProjectEnv(startDir = process.cwd()) {
|
|
|
35915
36072
|
|
|
35916
36073
|
// ../mcp-server/src/index.ts
|
|
35917
36074
|
async function main() {
|
|
36075
|
+
assertBetterSqlite3Loadable();
|
|
35918
36076
|
let ctx;
|
|
35919
36077
|
let initError;
|
|
35920
36078
|
try {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Bearer-secret management for plan-chat-service.mjs.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors the agent-memory-service pattern from the sibling olam-agent-memory
|
|
4
|
+
// repo: a single 0600 file at ~/.olam/plan-chat-secret holds the bearer
|
|
5
|
+
// hex string. Helpers generate, read, and rotate atomically. Rotation
|
|
6
|
+
// writes to a tmpfile and renames; mid-rotation reads see either the old
|
|
7
|
+
// or new value, never a partial write.
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import crypto from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
export const SECRET_DIR = path.join(os.homedir(), '.olam');
|
|
15
|
+
export const SECRET_PATH = path.join(SECRET_DIR, 'plan-chat-secret');
|
|
16
|
+
const SECRET_BYTES = 32; // 64 hex chars
|
|
17
|
+
const SECRET_MODE = 0o600;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate a fresh hex bearer (64 chars; 256 bits of entropy).
|
|
21
|
+
*/
|
|
22
|
+
export function generateSecret() {
|
|
23
|
+
return crypto.randomBytes(SECRET_BYTES).toString('hex');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read the on-disk bearer. Returns null if absent. Throws on permission errors.
|
|
28
|
+
*/
|
|
29
|
+
export function readSecret(secretPath = SECRET_PATH) {
|
|
30
|
+
try {
|
|
31
|
+
const value = fs.readFileSync(secretPath, 'utf8').trim();
|
|
32
|
+
if (!value) return null;
|
|
33
|
+
return value;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') return null;
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Write the bearer to disk atomically. Creates `~/.olam` if missing. Enforces
|
|
42
|
+
* 0600 perms on the destination (older mode permissions on the tmpfile are
|
|
43
|
+
* tightened immediately after write).
|
|
44
|
+
*/
|
|
45
|
+
export function writeSecret(value, secretPath = SECRET_PATH) {
|
|
46
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
47
|
+
throw new Error('plan-chat-secret: refusing to write empty bearer');
|
|
48
|
+
}
|
|
49
|
+
fs.mkdirSync(path.dirname(secretPath), { recursive: true, mode: 0o700 });
|
|
50
|
+
const tmp = `${secretPath}.tmp-${process.pid}-${Date.now()}`;
|
|
51
|
+
fs.writeFileSync(tmp, value + '\n', { mode: SECRET_MODE });
|
|
52
|
+
try {
|
|
53
|
+
fs.chmodSync(tmp, SECRET_MODE);
|
|
54
|
+
fs.renameSync(tmp, secretPath);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
try { fs.unlinkSync(tmp); } catch { /* swallow */ }
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Read the bearer if it exists, else generate, write, and return it.
|
|
63
|
+
* Idempotent across processes; first writer wins (rename is atomic).
|
|
64
|
+
*/
|
|
65
|
+
export function ensureSecret(secretPath = SECRET_PATH) {
|
|
66
|
+
const existing = readSecret(secretPath);
|
|
67
|
+
if (existing) return existing;
|
|
68
|
+
const fresh = generateSecret();
|
|
69
|
+
writeSecret(fresh, secretPath);
|
|
70
|
+
return fresh;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rotate: generate a new bearer, write atomically, return the new value.
|
|
75
|
+
* Callers should restart any running plan-chat-service so it re-reads.
|
|
76
|
+
*/
|
|
77
|
+
export function rotateSecret(secretPath = SECRET_PATH) {
|
|
78
|
+
const fresh = generateSecret();
|
|
79
|
+
writeSecret(fresh, secretPath);
|
|
80
|
+
return fresh;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Constant-time compare. Returns true iff both strings are non-empty and
|
|
85
|
+
* byte-equal. Avoids leaking timing on bearer comparison.
|
|
86
|
+
*/
|
|
87
|
+
export function timingSafeEqual(a, b) {
|
|
88
|
+
if (typeof a !== 'string' || typeof b !== 'string') return false;
|
|
89
|
+
if (a.length === 0 || b.length === 0) return false;
|
|
90
|
+
if (a.length !== b.length) return false;
|
|
91
|
+
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
92
|
+
}
|