@askexenow/exe-os 0.9.83 → 0.9.85
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/deploy/compose/.env.customer.example +1 -1
- package/deploy/compose/.env.example +1 -1
- package/deploy/compose/docker-compose.yml +1 -1
- package/deploy/stack-manifests/v0.9.json +69 -1
- package/dist/bin/cli.js +80 -13
- package/dist/bin/exe-launch-agent.js +130 -57
- package/dist/bin/exe-new-employee.js +32 -0
- package/dist/bin/install.js +32 -0
- package/dist/bin/stack-update.js +44 -9
- package/package.json +1 -1
- package/stack.release.json +4 -4
|
@@ -30,7 +30,7 @@ WIKI_SIG_SALT=CHANGEME_WIKI_SIG_SALT
|
|
|
30
30
|
WIKI_HOST_PORT=3001
|
|
31
31
|
|
|
32
32
|
# --- exed ---
|
|
33
|
-
EXED_IMAGE_TAG=registry.askexe.com/askexe/exed:v0.9.
|
|
33
|
+
EXED_IMAGE_TAG=registry.askexe.com/askexe/exed:v0.9.7
|
|
34
34
|
EXED_MCP_TOKEN=CHANGEME_EXED_MCP_TOKEN
|
|
35
35
|
EXED_DEVICE_ID=hygo-vps
|
|
36
36
|
# VPS-only: enables cloud/local SQLite -> exe-db Postgres projection.
|
|
@@ -30,7 +30,7 @@ WIKI_SIG_SALT=CHANGEME_WIKI_SIG_SALT
|
|
|
30
30
|
WIKI_HOST_PORT=3001
|
|
31
31
|
|
|
32
32
|
# --- exed ---
|
|
33
|
-
EXED_IMAGE_TAG=registry.askexe.com/askexe/exed:v0.9.
|
|
33
|
+
EXED_IMAGE_TAG=registry.askexe.com/askexe/exed:v0.9.7
|
|
34
34
|
EXED_MCP_TOKEN=CHANGEME_EXED_MCP_TOKEN
|
|
35
35
|
EXED_DEVICE_ID=hygo-vps
|
|
36
36
|
# VPS-only: enables cloud/local SQLite -> exe-db Postgres projection.
|
|
@@ -236,7 +236,7 @@ services:
|
|
|
236
236
|
options: { max-size: "10m", max-file: "3" }
|
|
237
237
|
|
|
238
238
|
exed:
|
|
239
|
-
image: ${EXED_IMAGE_TAG:-registry.askexe.com/askexe/exed:v0.9.
|
|
239
|
+
image: ${EXED_IMAGE_TAG:-registry.askexe.com/askexe/exed:v0.9.7}
|
|
240
240
|
container_name: exed
|
|
241
241
|
restart: unless-stopped
|
|
242
242
|
env_file:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"latest": "0.9.
|
|
3
|
+
"latest": "0.9.7",
|
|
4
4
|
"stacks": {
|
|
5
5
|
"0.9.0": {
|
|
6
6
|
"version": "0.9.0",
|
|
@@ -429,6 +429,74 @@
|
|
|
429
429
|
},
|
|
430
430
|
"date": "2026-05-12",
|
|
431
431
|
"sourceRef": "stack-v0.9.6"
|
|
432
|
+
},
|
|
433
|
+
"0.9.7": {
|
|
434
|
+
"version": "0.9.7",
|
|
435
|
+
"releasedAt": "2026-05-12T00:00:00Z",
|
|
436
|
+
"notes": "Stack update auth unblock: default manifest now uses the packaged signed/customer manifest when available, so cold-start installs do not require update.askexe.com entitlement provisioning. Remote update service remains opt-in via EXE_STACK_MANIFEST.",
|
|
437
|
+
"breakingChanges": [
|
|
438
|
+
{
|
|
439
|
+
"id": "whatsapp_relink_required",
|
|
440
|
+
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
441
|
+
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
442
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
443
|
+
"expectedDowntimeMinutes": "2-5",
|
|
444
|
+
"requiresConfirmation": true
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
"services": {
|
|
448
|
+
"crm": {
|
|
449
|
+
"env": "CRM_IMAGE_TAG",
|
|
450
|
+
"image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
|
|
451
|
+
"healthUrl": "http://127.0.0.1:3000/healthz",
|
|
452
|
+
"deploymentScope": "customer"
|
|
453
|
+
},
|
|
454
|
+
"wiki": {
|
|
455
|
+
"env": "WIKI_IMAGE_TAG",
|
|
456
|
+
"image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
|
|
457
|
+
"healthUrl": "http://127.0.0.1:3001/api/ping",
|
|
458
|
+
"deploymentScope": "customer"
|
|
459
|
+
},
|
|
460
|
+
"exed": {
|
|
461
|
+
"env": "EXED_IMAGE_TAG",
|
|
462
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.7",
|
|
463
|
+
"healthUrl": "http://127.0.0.1:8765/health",
|
|
464
|
+
"deploymentScope": "customer"
|
|
465
|
+
},
|
|
466
|
+
"gateway": {
|
|
467
|
+
"env": "GATEWAY_IMAGE_TAG",
|
|
468
|
+
"image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
|
|
469
|
+
"healthUrl": "http://127.0.0.1:3100/health",
|
|
470
|
+
"deploymentScope": "customer"
|
|
471
|
+
},
|
|
472
|
+
"monitorAgent": {
|
|
473
|
+
"env": "MONITOR_AGENT_IMAGE_TAG",
|
|
474
|
+
"image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
|
|
475
|
+
"deploymentScope": "customer"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
"releaseDescriptors": {
|
|
479
|
+
"exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
|
|
480
|
+
"crm": "AskExe/exe-crm@0.9.3:stack.release.json",
|
|
481
|
+
"wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
|
|
482
|
+
"gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
|
|
483
|
+
"db": "AskExe/exe-db@0.9.3:stack.release.json",
|
|
484
|
+
"monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
|
|
485
|
+
},
|
|
486
|
+
"componentVersions": {
|
|
487
|
+
"exe-os": {
|
|
488
|
+
"repo": "AskExe/exe-os",
|
|
489
|
+
"npmPackage": "@askexenow/exe-os",
|
|
490
|
+
"npmVersion": "0.9.84",
|
|
491
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.4",
|
|
492
|
+
"imageVersion": "0.9.7",
|
|
493
|
+
"note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
|
|
494
|
+
"sourceRef": "stack-v0.9.4",
|
|
495
|
+
"commit": "main@stack-v0.9.7"
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
"date": "2026-05-12",
|
|
499
|
+
"sourceRef": "stack-v0.9.7"
|
|
432
500
|
}
|
|
433
501
|
}
|
|
434
502
|
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -1241,6 +1241,7 @@ async function registerMcpServer(packageRoot, homeDir = os6.homedir()) {
|
|
|
1241
1241
|
`
|
|
1242
1242
|
);
|
|
1243
1243
|
}
|
|
1244
|
+
await registerExeRustMcpConfig(packageRoot, homeDir);
|
|
1244
1245
|
return false;
|
|
1245
1246
|
}
|
|
1246
1247
|
claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
|
|
@@ -1254,8 +1255,39 @@ async function registerMcpServer(packageRoot, homeDir = os6.homedir()) {
|
|
|
1254
1255
|
`
|
|
1255
1256
|
);
|
|
1256
1257
|
}
|
|
1258
|
+
await registerExeRustMcpConfig(packageRoot, homeDir);
|
|
1257
1259
|
return true;
|
|
1258
1260
|
}
|
|
1261
|
+
async function registerExeRustMcpConfig(packageRoot, homeDir) {
|
|
1262
|
+
const exeDir = path7.join(homeDir, ".exe");
|
|
1263
|
+
const exeMcpPath = path7.join(exeDir, "mcp.json");
|
|
1264
|
+
await mkdir3(exeDir, { recursive: true });
|
|
1265
|
+
let config = { servers: {} };
|
|
1266
|
+
if (existsSync8(exeMcpPath)) {
|
|
1267
|
+
try {
|
|
1268
|
+
config = JSON.parse(await readFile3(exeMcpPath, "utf-8"));
|
|
1269
|
+
if (!config.servers) config.servers = {};
|
|
1270
|
+
} catch {
|
|
1271
|
+
config = { servers: {} };
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
const stdioEntry = {
|
|
1275
|
+
command: "node",
|
|
1276
|
+
args: [path7.join(packageRoot, "dist", "mcp", "server.js")],
|
|
1277
|
+
env: {},
|
|
1278
|
+
shared: true
|
|
1279
|
+
};
|
|
1280
|
+
const existing = config.servers[MCP_PRIMARY_KEY];
|
|
1281
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(stdioEntry)) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
if (config.servers[MCP_LEGACY_KEY]) {
|
|
1285
|
+
delete config.servers[MCP_LEGACY_KEY];
|
|
1286
|
+
}
|
|
1287
|
+
config.servers[MCP_PRIMARY_KEY] = stdioEntry;
|
|
1288
|
+
await writeFile3(exeMcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1289
|
+
process.stderr.write("exe-os: registered MCP server in ~/.exe/mcp.json (Rust runtime)\n");
|
|
1290
|
+
}
|
|
1259
1291
|
async function cleanSettingsJsonMcp(settingsPath) {
|
|
1260
1292
|
if (!existsSync8(settingsPath)) return;
|
|
1261
1293
|
try {
|
|
@@ -20113,12 +20145,25 @@ function assertHostReadyForApply(report) {
|
|
|
20113
20145
|
if (blockers.length > 0) throw new Error(`Stack host is not ready:
|
|
20114
20146
|
- ${blockers.join("\n- ")}`);
|
|
20115
20147
|
}
|
|
20148
|
+
function areStackContainersRunning(composeFile, envFile) {
|
|
20149
|
+
try {
|
|
20150
|
+
const result = spawnSync("docker", ["compose", "--file", composeFile, "--env-file", envFile, "ps", "-q"], {
|
|
20151
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
20152
|
+
timeout: 15e3
|
|
20153
|
+
});
|
|
20154
|
+
return result.status === 0 && (result.stdout?.toString().trim().length ?? 0) > 0;
|
|
20155
|
+
} catch {
|
|
20156
|
+
return false;
|
|
20157
|
+
}
|
|
20158
|
+
}
|
|
20116
20159
|
async function runStackUpdate(options) {
|
|
20117
20160
|
const exec2 = options.exec ?? defaultExec;
|
|
20118
20161
|
const now2 = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
20119
20162
|
if (options.rollback) return rollbackStackUpdate(options);
|
|
20120
|
-
|
|
20121
|
-
|
|
20163
|
+
if (options.bootstrap !== false) {
|
|
20164
|
+
const report = bootstrapStackHost({ ...options, installDocker: options.installDocker ?? !!options.yes });
|
|
20165
|
+
if (!options.dryRun) assertHostReadyForApply(report);
|
|
20166
|
+
}
|
|
20122
20167
|
const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey, options.manifestAuthToken);
|
|
20123
20168
|
const envRaw = readFileSync29(options.envFile, "utf8");
|
|
20124
20169
|
const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
|
|
@@ -20134,7 +20179,8 @@ async function runStackUpdate(options) {
|
|
|
20134
20179
|
});
|
|
20135
20180
|
const lockFile = options.lockFile ?? path42.join(path42.dirname(options.envFile), ".exe-stack-lock.json");
|
|
20136
20181
|
const previousVersion = readCurrentStackVersion(lockFile);
|
|
20137
|
-
|
|
20182
|
+
const containersRunning = plan.changes.length === 0 ? areStackContainersRunning(options.composeFile, options.envFile) : true;
|
|
20183
|
+
if (options.dryRun || plan.changes.length === 0 && containersRunning) {
|
|
20138
20184
|
return { status: "planned", targetVersion: plan.targetVersion, changes: plan.changes, lockFile };
|
|
20139
20185
|
}
|
|
20140
20186
|
await postDeployAudit(options, "started", plan.targetVersion, previousVersion);
|
|
@@ -20286,12 +20332,16 @@ async function defaultPostJson(url, body, authToken) {
|
|
|
20286
20332
|
function defaultStackPaths() {
|
|
20287
20333
|
const cwdCompose = path42.resolve("docker-compose.yml");
|
|
20288
20334
|
const cwdEnv = path42.resolve(".env");
|
|
20335
|
+
const packagedManifest = path42.join(resolvePackageRoot2(), "deploy", "stack-manifests", "v0.9.json");
|
|
20336
|
+
const manifestRef = process.env.EXE_STACK_MANIFEST || (existsSync34(packagedManifest) ? packagedManifest : "https://update.askexe.com/stack-manifest.json");
|
|
20289
20337
|
return {
|
|
20290
20338
|
composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync34(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
|
|
20291
20339
|
envFile: process.env.EXE_STACK_ENV_FILE || (existsSync34(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
|
|
20292
|
-
manifestRef
|
|
20293
|
-
|
|
20294
|
-
|
|
20340
|
+
manifestRef,
|
|
20341
|
+
// Only call update.askexe.com if explicitly configured or if a remote manifest was requested.
|
|
20342
|
+
// Packaged manifests keep cold-start installs unblocked even before update-service entitlements are provisioned.
|
|
20343
|
+
auditUrl: process.env.EXE_STACK_AUDIT_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/deploy-audits" : void 0),
|
|
20344
|
+
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/image-credentials" : void 0),
|
|
20295
20345
|
manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN || process.env.EXE_LICENSE_KEY || loadLicense() || void 0,
|
|
20296
20346
|
manifestPublicKey: loadDefaultPublicKey()
|
|
20297
20347
|
};
|
|
@@ -20318,6 +20368,7 @@ __export(stack_update_exports, {
|
|
|
20318
20368
|
runStackUpdateCli: () => main7
|
|
20319
20369
|
});
|
|
20320
20370
|
import { readFileSync as readFileSync30 } from "fs";
|
|
20371
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
20321
20372
|
function parseArgs4(args2) {
|
|
20322
20373
|
const defaults = defaultStackPaths();
|
|
20323
20374
|
const opts = {
|
|
@@ -20427,9 +20478,14 @@ Options:
|
|
|
20427
20478
|
-y, --yes Non-interactive confirmation
|
|
20428
20479
|
`);
|
|
20429
20480
|
}
|
|
20430
|
-
function printChanges(changes) {
|
|
20481
|
+
function printChanges(changes, composeFile, envFile) {
|
|
20431
20482
|
if (changes.length === 0) {
|
|
20432
|
-
|
|
20483
|
+
const running = areCliContainersRunning(composeFile, envFile);
|
|
20484
|
+
if (running) {
|
|
20485
|
+
console.log("\u2705 Stack already matches target manifest.");
|
|
20486
|
+
} else {
|
|
20487
|
+
console.log("\u26A0\uFE0F Stack .env matches target manifest but containers are not running. Will start them.");
|
|
20488
|
+
}
|
|
20433
20489
|
return;
|
|
20434
20490
|
}
|
|
20435
20491
|
console.log("Planned image tag changes:");
|
|
@@ -20438,6 +20494,17 @@ function printChanges(changes) {
|
|
|
20438
20494
|
console.log(` ${c.before ?? "<unset>"} \u2192 ${c.after}`);
|
|
20439
20495
|
}
|
|
20440
20496
|
}
|
|
20497
|
+
function areCliContainersRunning(composeFile, envFile) {
|
|
20498
|
+
try {
|
|
20499
|
+
const result = spawnSync2("docker", ["compose", "--file", composeFile, "--env-file", envFile, "ps", "-q"], {
|
|
20500
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
20501
|
+
timeout: 15e3
|
|
20502
|
+
});
|
|
20503
|
+
return result.status === 0 && (result.stdout?.toString().trim().length ?? 0) > 0;
|
|
20504
|
+
} catch {
|
|
20505
|
+
return false;
|
|
20506
|
+
}
|
|
20507
|
+
}
|
|
20441
20508
|
function printBreaking(changes) {
|
|
20442
20509
|
if (changes.length === 0) return;
|
|
20443
20510
|
console.log("\nBreaking-change notices:");
|
|
@@ -20492,7 +20559,7 @@ async function main7(args2 = process.argv.slice(2)) {
|
|
|
20492
20559
|
console.log(`Compose: ${opts.composeFile}`);
|
|
20493
20560
|
console.log(`Env: ${opts.envFile}
|
|
20494
20561
|
`);
|
|
20495
|
-
printChanges(plan.changes);
|
|
20562
|
+
printChanges(plan.changes, opts.composeFile, opts.envFile);
|
|
20496
20563
|
printBreaking(plan.breakingChanges);
|
|
20497
20564
|
if (opts.check || opts.dryRun) return;
|
|
20498
20565
|
if (!opts.yes) {
|
|
@@ -35057,7 +35124,7 @@ __export(code_context_index_exports, {
|
|
|
35057
35124
|
import crypto14 from "crypto";
|
|
35058
35125
|
import path51 from "path";
|
|
35059
35126
|
import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, readdirSync as readdirSync11, statSync as statSync7, writeFileSync as writeFileSync26 } from "fs";
|
|
35060
|
-
import { spawnSync as
|
|
35127
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
35061
35128
|
function normalizeProjectRoot(projectRoot) {
|
|
35062
35129
|
return path51.resolve(projectRoot || process.cwd());
|
|
35063
35130
|
}
|
|
@@ -35075,7 +35142,7 @@ function getCodeContextIndexPath(projectRoot) {
|
|
|
35075
35142
|
return path51.join(indexDir(), `${rootHash}.json`);
|
|
35076
35143
|
}
|
|
35077
35144
|
function currentBranch(projectRoot) {
|
|
35078
|
-
const result =
|
|
35145
|
+
const result = spawnSync3("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
|
|
35079
35146
|
const branch = result.status === 0 ? result.stdout.trim() : "";
|
|
35080
35147
|
return branch || "detached-or-unknown";
|
|
35081
35148
|
}
|
|
@@ -35094,12 +35161,12 @@ function listRecursive(projectRoot, dir = projectRoot, out = []) {
|
|
|
35094
35161
|
return out;
|
|
35095
35162
|
}
|
|
35096
35163
|
function listCodeFiles(projectRoot, maxFiles) {
|
|
35097
|
-
const git =
|
|
35164
|
+
const git = spawnSync3("git", ["ls-files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
|
|
35098
35165
|
let files = [];
|
|
35099
35166
|
if (git.status === 0 && git.stdout.trim()) {
|
|
35100
35167
|
files = git.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
35101
35168
|
} else {
|
|
35102
|
-
const rg =
|
|
35169
|
+
const rg = spawnSync3("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
|
|
35103
35170
|
files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
|
|
35104
35171
|
}
|
|
35105
35172
|
return files.map((file) => file.replaceAll(path51.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
|
|
@@ -3332,8 +3332,8 @@ function deriveMachineKey() {
|
|
|
3332
3332
|
}
|
|
3333
3333
|
function readMachineId() {
|
|
3334
3334
|
try {
|
|
3335
|
-
const { readFileSync:
|
|
3336
|
-
return
|
|
3335
|
+
const { readFileSync: readFileSync8 } = __require("fs");
|
|
3336
|
+
return readFileSync8("/etc/machine-id", "utf-8").trim();
|
|
3337
3337
|
} catch {
|
|
3338
3338
|
return "";
|
|
3339
3339
|
}
|
|
@@ -5473,6 +5473,68 @@ var init_store = __esm({
|
|
|
5473
5473
|
}
|
|
5474
5474
|
});
|
|
5475
5475
|
|
|
5476
|
+
// src/lib/runtime-table.ts
|
|
5477
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
5478
|
+
var init_runtime_table = __esm({
|
|
5479
|
+
"src/lib/runtime-table.ts"() {
|
|
5480
|
+
"use strict";
|
|
5481
|
+
RUNTIME_TABLE = {
|
|
5482
|
+
codex: {
|
|
5483
|
+
binary: "codex",
|
|
5484
|
+
launchMode: "interactive",
|
|
5485
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
5486
|
+
inlineFlag: "--no-alt-screen",
|
|
5487
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
5488
|
+
defaultModel: "gpt-5.5"
|
|
5489
|
+
},
|
|
5490
|
+
opencode: {
|
|
5491
|
+
binary: "opencode",
|
|
5492
|
+
launchMode: "exec",
|
|
5493
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
5494
|
+
inlineFlag: "",
|
|
5495
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
5496
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
5497
|
+
}
|
|
5498
|
+
};
|
|
5499
|
+
DEFAULT_RUNTIME = "claude";
|
|
5500
|
+
}
|
|
5501
|
+
});
|
|
5502
|
+
|
|
5503
|
+
// src/lib/agent-config.ts
|
|
5504
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
|
|
5505
|
+
import path9 from "path";
|
|
5506
|
+
function loadAgentConfig() {
|
|
5507
|
+
if (!existsSync9(AGENT_CONFIG_PATH)) return {};
|
|
5508
|
+
try {
|
|
5509
|
+
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
5510
|
+
} catch {
|
|
5511
|
+
return {};
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
function getAgentRuntime(agentId) {
|
|
5515
|
+
const config = loadAgentConfig();
|
|
5516
|
+
const entry = config[agentId];
|
|
5517
|
+
if (entry) return entry;
|
|
5518
|
+
const orgDefault = config["default"];
|
|
5519
|
+
if (orgDefault) return orgDefault;
|
|
5520
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
5521
|
+
}
|
|
5522
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
5523
|
+
var init_agent_config = __esm({
|
|
5524
|
+
"src/lib/agent-config.ts"() {
|
|
5525
|
+
"use strict";
|
|
5526
|
+
init_config();
|
|
5527
|
+
init_runtime_table();
|
|
5528
|
+
init_secure_files();
|
|
5529
|
+
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
5530
|
+
DEFAULT_MODELS = {
|
|
5531
|
+
claude: "claude-opus-4.6",
|
|
5532
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
5533
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
5534
|
+
};
|
|
5535
|
+
}
|
|
5536
|
+
});
|
|
5537
|
+
|
|
5476
5538
|
// src/lib/session-key.ts
|
|
5477
5539
|
import { execSync as execSync4 } from "child_process";
|
|
5478
5540
|
function normalizeCommand(command) {
|
|
@@ -5564,9 +5626,9 @@ __export(active_agent_exports, {
|
|
|
5564
5626
|
resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
|
|
5565
5627
|
writeActiveAgent: () => writeActiveAgent
|
|
5566
5628
|
});
|
|
5567
|
-
import { readFileSync as
|
|
5629
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
5568
5630
|
import { execSync as execSync5 } from "child_process";
|
|
5569
|
-
import
|
|
5631
|
+
import path10 from "path";
|
|
5570
5632
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
5571
5633
|
if (candidate === baseName) return true;
|
|
5572
5634
|
if (!candidate.startsWith(baseName)) return false;
|
|
@@ -5610,12 +5672,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
5610
5672
|
return null;
|
|
5611
5673
|
}
|
|
5612
5674
|
function getMarkerPath() {
|
|
5613
|
-
return
|
|
5675
|
+
return path10.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
5614
5676
|
}
|
|
5615
5677
|
function writeActiveAgent(agentId, agentRole) {
|
|
5616
5678
|
try {
|
|
5617
5679
|
mkdirSync4(CACHE_DIR, { recursive: true });
|
|
5618
|
-
|
|
5680
|
+
writeFileSync5(
|
|
5619
5681
|
getMarkerPath(),
|
|
5620
5682
|
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
5621
5683
|
);
|
|
@@ -5633,7 +5695,7 @@ function getActiveAgent() {
|
|
|
5633
5695
|
if (httpCtx) return httpCtx;
|
|
5634
5696
|
try {
|
|
5635
5697
|
const markerPath = getMarkerPath();
|
|
5636
|
-
const raw =
|
|
5698
|
+
const raw = readFileSync6(markerPath, "utf8");
|
|
5637
5699
|
const data = JSON.parse(raw);
|
|
5638
5700
|
if (data.agentId) {
|
|
5639
5701
|
if (data.startedAt) {
|
|
@@ -5681,14 +5743,14 @@ function getAllActiveAgents() {
|
|
|
5681
5743
|
const key = file.slice("active-agent-".length, -".json".length);
|
|
5682
5744
|
if (key === "undefined") continue;
|
|
5683
5745
|
try {
|
|
5684
|
-
const raw =
|
|
5746
|
+
const raw = readFileSync6(path10.join(CACHE_DIR, file), "utf8");
|
|
5685
5747
|
const data = JSON.parse(raw);
|
|
5686
5748
|
if (!data.agentId) continue;
|
|
5687
5749
|
if (data.startedAt) {
|
|
5688
5750
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
5689
5751
|
if (age > STALE_MS) {
|
|
5690
5752
|
try {
|
|
5691
|
-
unlinkSync4(
|
|
5753
|
+
unlinkSync4(path10.join(CACHE_DIR, file));
|
|
5692
5754
|
} catch {
|
|
5693
5755
|
}
|
|
5694
5756
|
continue;
|
|
@@ -5711,11 +5773,11 @@ function getAllActiveAgents() {
|
|
|
5711
5773
|
function cleanupSessionMarkers() {
|
|
5712
5774
|
const key = getSessionKey();
|
|
5713
5775
|
try {
|
|
5714
|
-
unlinkSync4(
|
|
5776
|
+
unlinkSync4(path10.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
5715
5777
|
} catch {
|
|
5716
5778
|
}
|
|
5717
5779
|
try {
|
|
5718
|
-
unlinkSync4(
|
|
5780
|
+
unlinkSync4(path10.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
5719
5781
|
} catch {
|
|
5720
5782
|
}
|
|
5721
5783
|
}
|
|
@@ -5727,7 +5789,7 @@ var init_active_agent = __esm({
|
|
|
5727
5789
|
init_session_key();
|
|
5728
5790
|
init_agent_context();
|
|
5729
5791
|
init_employees();
|
|
5730
|
-
CACHE_DIR =
|
|
5792
|
+
CACHE_DIR = path10.join(EXE_AI_DIR, "session-cache");
|
|
5731
5793
|
STALE_MS = 24 * 60 * 60 * 1e3;
|
|
5732
5794
|
}
|
|
5733
5795
|
});
|
|
@@ -5735,8 +5797,8 @@ var init_active_agent = __esm({
|
|
|
5735
5797
|
// src/bin/exe-launch-agent.ts
|
|
5736
5798
|
init_store();
|
|
5737
5799
|
import os7 from "os";
|
|
5738
|
-
import
|
|
5739
|
-
import { existsSync as
|
|
5800
|
+
import path11 from "path";
|
|
5801
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync4 } from "fs";
|
|
5740
5802
|
import { spawnSync } from "child_process";
|
|
5741
5803
|
|
|
5742
5804
|
// src/bin/fast-db-init.ts
|
|
@@ -6108,6 +6170,7 @@ var PROVIDER_TABLE = {
|
|
|
6108
6170
|
var DEFAULT_PROVIDER = "default";
|
|
6109
6171
|
|
|
6110
6172
|
// src/bin/exe-launch-agent.ts
|
|
6173
|
+
init_agent_config();
|
|
6111
6174
|
function getKnownAgents() {
|
|
6112
6175
|
try {
|
|
6113
6176
|
return loadEmployeesSync().map((e) => e.name);
|
|
@@ -6125,7 +6188,7 @@ function parseBasename(basename) {
|
|
|
6125
6188
|
return { agent, provider };
|
|
6126
6189
|
}
|
|
6127
6190
|
function resolveAgent(argv) {
|
|
6128
|
-
const invokedAs =
|
|
6191
|
+
const invokedAs = path11.basename(argv[1] ?? "");
|
|
6129
6192
|
if (invokedAs && invokedAs !== "exe-launch-agent" && !invokedAs.endsWith(".js")) {
|
|
6130
6193
|
const { agent: agent2, provider } = parseBasename(invokedAs.toLowerCase());
|
|
6131
6194
|
return { agent: agent2, provider, passthrough: argv.slice(2) };
|
|
@@ -6154,13 +6217,13 @@ async function isKnownAgent(agent) {
|
|
|
6154
6217
|
}
|
|
6155
6218
|
}
|
|
6156
6219
|
function identityPathFor(agent) {
|
|
6157
|
-
const dir =
|
|
6158
|
-
const exactPath =
|
|
6159
|
-
if (
|
|
6220
|
+
const dir = path11.join(os7.homedir(), ".exe-os", "identity");
|
|
6221
|
+
const exactPath = path11.join(dir, `${agent}.md`);
|
|
6222
|
+
if (existsSync10(exactPath)) return exactPath;
|
|
6160
6223
|
try {
|
|
6161
6224
|
const files = readdirSync4(dir);
|
|
6162
6225
|
const match = files.find((f) => f.toLowerCase() === `${agent.toLowerCase()}.md`);
|
|
6163
|
-
if (match) return
|
|
6226
|
+
if (match) return path11.join(dir, match);
|
|
6164
6227
|
} catch {
|
|
6165
6228
|
}
|
|
6166
6229
|
return exactPath;
|
|
@@ -6182,13 +6245,13 @@ function leanMcpEnabled() {
|
|
|
6182
6245
|
function collectAllMcpServers() {
|
|
6183
6246
|
const servers = {};
|
|
6184
6247
|
const sources = [
|
|
6185
|
-
|
|
6186
|
-
|
|
6248
|
+
path11.join(os7.homedir(), ".claude.json"),
|
|
6249
|
+
path11.join(os7.homedir(), ".claude", "settings.json")
|
|
6187
6250
|
];
|
|
6188
6251
|
for (const src of sources) {
|
|
6189
6252
|
try {
|
|
6190
|
-
if (!
|
|
6191
|
-
const data = JSON.parse(
|
|
6253
|
+
if (!existsSync10(src)) continue;
|
|
6254
|
+
const data = JSON.parse(readFileSync7(src, "utf-8"));
|
|
6192
6255
|
const block = data.mcpServers;
|
|
6193
6256
|
if (!block) continue;
|
|
6194
6257
|
for (const [name, cfg] of Object.entries(block)) {
|
|
@@ -6214,18 +6277,18 @@ function generateLeanMcpConfig(agent, role) {
|
|
|
6214
6277
|
}
|
|
6215
6278
|
if (Object.keys(leanServers).length >= Object.keys(allServers).length) return null;
|
|
6216
6279
|
if (!leanServers["exe-mem"]) {
|
|
6217
|
-
const packageRoot =
|
|
6280
|
+
const packageRoot = path11.resolve(path11.dirname(new URL(import.meta.url).pathname), "..", "..");
|
|
6218
6281
|
leanServers["exe-mem"] = {
|
|
6219
6282
|
type: "stdio",
|
|
6220
6283
|
command: "node",
|
|
6221
|
-
args: [
|
|
6284
|
+
args: [path11.join(packageRoot, "dist", "mcp", "server.js")],
|
|
6222
6285
|
env: {}
|
|
6223
6286
|
};
|
|
6224
6287
|
}
|
|
6225
|
-
const configDir =
|
|
6288
|
+
const configDir = path11.join(os7.homedir(), ".exe-os", "mcp-configs");
|
|
6226
6289
|
mkdirSync5(configDir, { recursive: true });
|
|
6227
|
-
const configPath =
|
|
6228
|
-
|
|
6290
|
+
const configPath = path11.join(configDir, `${agent}-lean.json`);
|
|
6291
|
+
writeFileSync6(configPath, JSON.stringify({ mcpServers: leanServers }, null, 2), "utf-8");
|
|
6229
6292
|
const saved = Object.keys(allServers).length - Object.keys(leanServers).length;
|
|
6230
6293
|
if (saved > 0) {
|
|
6231
6294
|
process.stderr.write(
|
|
@@ -6244,8 +6307,8 @@ function generateLeanMcpConfig(agent, role) {
|
|
|
6244
6307
|
}
|
|
6245
6308
|
function leanMcpConfigFor(agent) {
|
|
6246
6309
|
if (!leanMcpEnabled()) return null;
|
|
6247
|
-
const p =
|
|
6248
|
-
return
|
|
6310
|
+
const p = path11.join(os7.homedir(), ".exe-os", "mcp-configs", `${agent}-lean.json`);
|
|
6311
|
+
return existsSync10(p) ? p : null;
|
|
6249
6312
|
}
|
|
6250
6313
|
var _ccHelpOutput = null;
|
|
6251
6314
|
function getCcHelpOutput() {
|
|
@@ -6268,39 +6331,39 @@ function _resetCcHelpCache() {
|
|
|
6268
6331
|
function buildLaunchPlan(agent, behaviorsPath, passthrough, _hasAgentFlag, _provider) {
|
|
6269
6332
|
const args = ["--dangerously-skip-permissions"];
|
|
6270
6333
|
const idPath = identityPathFor(agent);
|
|
6271
|
-
const ccAgentPath =
|
|
6334
|
+
const ccAgentPath = path11.join(os7.homedir(), ".claude", "agents", `${agent}.md`);
|
|
6272
6335
|
let effectiveCcPath = null;
|
|
6273
|
-
if (
|
|
6336
|
+
if (existsSync10(ccAgentPath)) {
|
|
6274
6337
|
effectiveCcPath = ccAgentPath;
|
|
6275
6338
|
} else {
|
|
6276
6339
|
try {
|
|
6277
|
-
const ccAgentDir =
|
|
6340
|
+
const ccAgentDir = path11.join(os7.homedir(), ".claude", "agents");
|
|
6278
6341
|
const ccFiles = readdirSync4(ccAgentDir);
|
|
6279
6342
|
const ccMatch = ccFiles.find((f) => f.toLowerCase() === `${agent.toLowerCase()}.md`);
|
|
6280
|
-
if (ccMatch) effectiveCcPath =
|
|
6343
|
+
if (ccMatch) effectiveCcPath = path11.join(ccAgentDir, ccMatch);
|
|
6281
6344
|
} catch {
|
|
6282
6345
|
}
|
|
6283
6346
|
}
|
|
6284
|
-
const effectiveIdPath =
|
|
6347
|
+
const effectiveIdPath = existsSync10(idPath) ? idPath : effectiveCcPath;
|
|
6285
6348
|
let identityContent = null;
|
|
6286
|
-
if (effectiveIdPath &&
|
|
6349
|
+
if (effectiveIdPath && existsSync10(effectiveIdPath)) {
|
|
6287
6350
|
try {
|
|
6288
|
-
const content =
|
|
6351
|
+
const content = readFileSync7(effectiveIdPath, "utf-8");
|
|
6289
6352
|
if (content.trim().length > 0) identityContent = content;
|
|
6290
6353
|
} catch {
|
|
6291
6354
|
}
|
|
6292
6355
|
}
|
|
6293
6356
|
if (!identityContent) {
|
|
6294
6357
|
try {
|
|
6295
|
-
const rosterPath =
|
|
6296
|
-
if (
|
|
6297
|
-
const roster = JSON.parse(
|
|
6358
|
+
const rosterPath = path11.join(os7.homedir(), ".exe-os", "exe-employees.json");
|
|
6359
|
+
if (existsSync10(rosterPath)) {
|
|
6360
|
+
const roster = JSON.parse(readFileSync7(rosterPath, "utf8"));
|
|
6298
6361
|
const emp = roster.find((e) => e.name.toLowerCase() === agent.toLowerCase());
|
|
6299
6362
|
if (emp?.systemPrompt && emp.systemPrompt.trim().length > 20) {
|
|
6300
6363
|
identityContent = emp.systemPrompt;
|
|
6301
6364
|
try {
|
|
6302
|
-
const dir =
|
|
6303
|
-
if (!
|
|
6365
|
+
const dir = path11.dirname(idPath);
|
|
6366
|
+
if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
|
|
6304
6367
|
const hasFrontmatter = identityContent.trimStart().startsWith("---");
|
|
6305
6368
|
const fileContent = hasFrontmatter ? identityContent : `---
|
|
6306
6369
|
role: ${(emp.role ?? "employee").toLowerCase()}
|
|
@@ -6312,7 +6375,7 @@ updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
6312
6375
|
---
|
|
6313
6376
|
|
|
6314
6377
|
${identityContent}`;
|
|
6315
|
-
|
|
6378
|
+
writeFileSync6(idPath, fileContent, "utf-8");
|
|
6316
6379
|
identityContent = fileContent;
|
|
6317
6380
|
process.stderr.write(`[exe-launch-agent] self-healed missing identity file: ${idPath}
|
|
6318
6381
|
`);
|
|
@@ -6340,15 +6403,15 @@ ${identityContent}`;
|
|
|
6340
6403
|
args.push("--system-prompt", getSessionPrompt(identityContent));
|
|
6341
6404
|
} else {
|
|
6342
6405
|
try {
|
|
6343
|
-
const tmpPath =
|
|
6344
|
-
mkdirSync5(
|
|
6345
|
-
|
|
6406
|
+
const tmpPath = path11.join(os7.homedir(), ".exe-os", "session-cache", `${agent}-identity.md`);
|
|
6407
|
+
mkdirSync5(path11.dirname(tmpPath), { recursive: true });
|
|
6408
|
+
writeFileSync6(tmpPath, identityContent, "utf-8");
|
|
6346
6409
|
args.push("--append-system-prompt-file", tmpPath);
|
|
6347
6410
|
} catch {
|
|
6348
6411
|
}
|
|
6349
6412
|
}
|
|
6350
6413
|
}
|
|
6351
|
-
if (behaviorsPath &&
|
|
6414
|
+
if (behaviorsPath && existsSync10(behaviorsPath)) {
|
|
6352
6415
|
args.push("--append-system-prompt-file", behaviorsPath);
|
|
6353
6416
|
}
|
|
6354
6417
|
const leanMcp = leanMcpConfigFor(agent);
|
|
@@ -6440,28 +6503,28 @@ async function main() {
|
|
|
6440
6503
|
_resetCcAgentSupportCache();
|
|
6441
6504
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
6442
6505
|
if (hasAgentFlag) {
|
|
6443
|
-
const ccAgentDir =
|
|
6444
|
-
const ccAgentFile =
|
|
6445
|
-
if (!
|
|
6506
|
+
const ccAgentDir = path11.join(os7.homedir(), ".claude", "agents");
|
|
6507
|
+
const ccAgentFile = path11.join(ccAgentDir, `${agent}.md`);
|
|
6508
|
+
if (!existsSync10(ccAgentFile)) {
|
|
6446
6509
|
const exeIdentity = identityPathFor(agent);
|
|
6447
6510
|
let sourceFile = null;
|
|
6448
|
-
if (
|
|
6511
|
+
if (existsSync10(exeIdentity)) {
|
|
6449
6512
|
sourceFile = exeIdentity;
|
|
6450
6513
|
} else {
|
|
6451
6514
|
try {
|
|
6452
|
-
const identityDir =
|
|
6515
|
+
const identityDir = path11.dirname(exeIdentity);
|
|
6453
6516
|
const files = readdirSync4(identityDir);
|
|
6454
6517
|
const match = files.find((f) => f.toLowerCase() === `${agent.toLowerCase()}.md`);
|
|
6455
|
-
if (match) sourceFile =
|
|
6518
|
+
if (match) sourceFile = path11.join(identityDir, match);
|
|
6456
6519
|
} catch {
|
|
6457
6520
|
}
|
|
6458
6521
|
}
|
|
6459
6522
|
if (sourceFile) {
|
|
6460
6523
|
try {
|
|
6461
6524
|
mkdirSync5(ccAgentDir, { recursive: true });
|
|
6462
|
-
let content =
|
|
6525
|
+
let content = readFileSync7(sourceFile, "utf-8");
|
|
6463
6526
|
content = content.replace(/\$\{agent_id\}/g, baseAgentName(agent));
|
|
6464
|
-
|
|
6527
|
+
writeFileSync6(ccAgentFile, content, "utf-8");
|
|
6465
6528
|
process.stderr.write(
|
|
6466
6529
|
`[exe-launch-agent] auto-provisioned ${ccAgentFile} from ${sourceFile}
|
|
6467
6530
|
`
|
|
@@ -6474,8 +6537,8 @@ async function main() {
|
|
|
6474
6537
|
const memoryAgent = baseAgentName(agent);
|
|
6475
6538
|
const empRole = (() => {
|
|
6476
6539
|
try {
|
|
6477
|
-
const emps =
|
|
6478
|
-
|
|
6540
|
+
const emps = readFileSync7(
|
|
6541
|
+
path11.join(os7.homedir(), ".exe-os", "exe-employees.json"),
|
|
6479
6542
|
"utf-8"
|
|
6480
6543
|
);
|
|
6481
6544
|
const found = JSON.parse(emps).find(
|
|
@@ -6504,6 +6567,16 @@ async function main() {
|
|
|
6504
6567
|
if (!process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING) {
|
|
6505
6568
|
process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING = "1";
|
|
6506
6569
|
}
|
|
6570
|
+
if (provider === DEFAULT_PROVIDER) {
|
|
6571
|
+
const rtConfig = getAgentRuntime(memoryAgent);
|
|
6572
|
+
if (rtConfig.runtime === "claude" && rtConfig.model) {
|
|
6573
|
+
let ccModel = rtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
6574
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
6575
|
+
ccModel += "[1m]";
|
|
6576
|
+
}
|
|
6577
|
+
process.env.ANTHROPIC_MODEL = ccModel;
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6507
6580
|
const child = spawnSync(plan.command, plan.args, { stdio: "inherit" });
|
|
6508
6581
|
if (child.error) {
|
|
6509
6582
|
const err = child.error;
|
|
@@ -1662,6 +1662,7 @@ async function registerMcpServer(packageRoot, homeDir = os8.homedir()) {
|
|
|
1662
1662
|
`
|
|
1663
1663
|
);
|
|
1664
1664
|
}
|
|
1665
|
+
await registerExeRustMcpConfig(packageRoot, homeDir);
|
|
1665
1666
|
return false;
|
|
1666
1667
|
}
|
|
1667
1668
|
claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
|
|
@@ -1675,8 +1676,39 @@ async function registerMcpServer(packageRoot, homeDir = os8.homedir()) {
|
|
|
1675
1676
|
`
|
|
1676
1677
|
);
|
|
1677
1678
|
}
|
|
1679
|
+
await registerExeRustMcpConfig(packageRoot, homeDir);
|
|
1678
1680
|
return true;
|
|
1679
1681
|
}
|
|
1682
|
+
async function registerExeRustMcpConfig(packageRoot, homeDir) {
|
|
1683
|
+
const exeDir = path12.join(homeDir, ".exe");
|
|
1684
|
+
const exeMcpPath = path12.join(exeDir, "mcp.json");
|
|
1685
|
+
await mkdir3(exeDir, { recursive: true });
|
|
1686
|
+
let config = { servers: {} };
|
|
1687
|
+
if (existsSync12(exeMcpPath)) {
|
|
1688
|
+
try {
|
|
1689
|
+
config = JSON.parse(await readFile3(exeMcpPath, "utf-8"));
|
|
1690
|
+
if (!config.servers) config.servers = {};
|
|
1691
|
+
} catch {
|
|
1692
|
+
config = { servers: {} };
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
const stdioEntry = {
|
|
1696
|
+
command: "node",
|
|
1697
|
+
args: [path12.join(packageRoot, "dist", "mcp", "server.js")],
|
|
1698
|
+
env: {},
|
|
1699
|
+
shared: true
|
|
1700
|
+
};
|
|
1701
|
+
const existing = config.servers[MCP_PRIMARY_KEY];
|
|
1702
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(stdioEntry)) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
if (config.servers[MCP_LEGACY_KEY]) {
|
|
1706
|
+
delete config.servers[MCP_LEGACY_KEY];
|
|
1707
|
+
}
|
|
1708
|
+
config.servers[MCP_PRIMARY_KEY] = stdioEntry;
|
|
1709
|
+
await writeFile3(exeMcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1710
|
+
process.stderr.write("exe-os: registered MCP server in ~/.exe/mcp.json (Rust runtime)\n");
|
|
1711
|
+
}
|
|
1680
1712
|
async function cleanSettingsJsonMcp(settingsPath) {
|
|
1681
1713
|
if (!existsSync12(settingsPath)) return;
|
|
1682
1714
|
try {
|
package/dist/bin/install.js
CHANGED
|
@@ -991,6 +991,7 @@ async function registerMcpServer(packageRoot, homeDir = os6.homedir()) {
|
|
|
991
991
|
`
|
|
992
992
|
);
|
|
993
993
|
}
|
|
994
|
+
await registerExeRustMcpConfig(packageRoot, homeDir);
|
|
994
995
|
return false;
|
|
995
996
|
}
|
|
996
997
|
claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
|
|
@@ -1004,8 +1005,39 @@ async function registerMcpServer(packageRoot, homeDir = os6.homedir()) {
|
|
|
1004
1005
|
`
|
|
1005
1006
|
);
|
|
1006
1007
|
}
|
|
1008
|
+
await registerExeRustMcpConfig(packageRoot, homeDir);
|
|
1007
1009
|
return true;
|
|
1008
1010
|
}
|
|
1011
|
+
async function registerExeRustMcpConfig(packageRoot, homeDir) {
|
|
1012
|
+
const exeDir = path7.join(homeDir, ".exe");
|
|
1013
|
+
const exeMcpPath = path7.join(exeDir, "mcp.json");
|
|
1014
|
+
await mkdir3(exeDir, { recursive: true });
|
|
1015
|
+
let config = { servers: {} };
|
|
1016
|
+
if (existsSync8(exeMcpPath)) {
|
|
1017
|
+
try {
|
|
1018
|
+
config = JSON.parse(await readFile3(exeMcpPath, "utf-8"));
|
|
1019
|
+
if (!config.servers) config.servers = {};
|
|
1020
|
+
} catch {
|
|
1021
|
+
config = { servers: {} };
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
const stdioEntry = {
|
|
1025
|
+
command: "node",
|
|
1026
|
+
args: [path7.join(packageRoot, "dist", "mcp", "server.js")],
|
|
1027
|
+
env: {},
|
|
1028
|
+
shared: true
|
|
1029
|
+
};
|
|
1030
|
+
const existing = config.servers[MCP_PRIMARY_KEY];
|
|
1031
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(stdioEntry)) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (config.servers[MCP_LEGACY_KEY]) {
|
|
1035
|
+
delete config.servers[MCP_LEGACY_KEY];
|
|
1036
|
+
}
|
|
1037
|
+
config.servers[MCP_PRIMARY_KEY] = stdioEntry;
|
|
1038
|
+
await writeFile3(exeMcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1039
|
+
process.stderr.write("exe-os: registered MCP server in ~/.exe/mcp.json (Rust runtime)\n");
|
|
1040
|
+
}
|
|
1009
1041
|
async function cleanSettingsJsonMcp(settingsPath) {
|
|
1010
1042
|
if (!existsSync8(settingsPath)) return;
|
|
1011
1043
|
try {
|
package/dist/bin/stack-update.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/bin/stack-update.ts
|
|
4
4
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
6
|
|
|
6
7
|
// src/lib/is-main.ts
|
|
7
8
|
import { realpathSync } from "fs";
|
|
@@ -510,12 +511,25 @@ function assertHostReadyForApply(report) {
|
|
|
510
511
|
if (blockers.length > 0) throw new Error(`Stack host is not ready:
|
|
511
512
|
- ${blockers.join("\n- ")}`);
|
|
512
513
|
}
|
|
514
|
+
function areStackContainersRunning(composeFile, envFile) {
|
|
515
|
+
try {
|
|
516
|
+
const result = spawnSync("docker", ["compose", "--file", composeFile, "--env-file", envFile, "ps", "-q"], {
|
|
517
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
518
|
+
timeout: 15e3
|
|
519
|
+
});
|
|
520
|
+
return result.status === 0 && (result.stdout?.toString().trim().length ?? 0) > 0;
|
|
521
|
+
} catch {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
513
525
|
async function runStackUpdate(options) {
|
|
514
526
|
const exec = options.exec ?? defaultExec;
|
|
515
527
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
516
528
|
if (options.rollback) return rollbackStackUpdate(options);
|
|
517
|
-
|
|
518
|
-
|
|
529
|
+
if (options.bootstrap !== false) {
|
|
530
|
+
const report = bootstrapStackHost({ ...options, installDocker: options.installDocker ?? !!options.yes });
|
|
531
|
+
if (!options.dryRun) assertHostReadyForApply(report);
|
|
532
|
+
}
|
|
519
533
|
const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey, options.manifestAuthToken);
|
|
520
534
|
const envRaw = readFileSync3(options.envFile, "utf8");
|
|
521
535
|
const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
|
|
@@ -531,7 +545,8 @@ async function runStackUpdate(options) {
|
|
|
531
545
|
});
|
|
532
546
|
const lockFile = options.lockFile ?? path3.join(path3.dirname(options.envFile), ".exe-stack-lock.json");
|
|
533
547
|
const previousVersion = readCurrentStackVersion(lockFile);
|
|
534
|
-
|
|
548
|
+
const containersRunning = plan.changes.length === 0 ? areStackContainersRunning(options.composeFile, options.envFile) : true;
|
|
549
|
+
if (options.dryRun || plan.changes.length === 0 && containersRunning) {
|
|
535
550
|
return { status: "planned", targetVersion: plan.targetVersion, changes: plan.changes, lockFile };
|
|
536
551
|
}
|
|
537
552
|
await postDeployAudit(options, "started", plan.targetVersion, previousVersion);
|
|
@@ -683,12 +698,16 @@ async function defaultPostJson(url, body, authToken) {
|
|
|
683
698
|
function defaultStackPaths() {
|
|
684
699
|
const cwdCompose = path3.resolve("docker-compose.yml");
|
|
685
700
|
const cwdEnv = path3.resolve(".env");
|
|
701
|
+
const packagedManifest = path3.join(resolvePackageRoot(), "deploy", "stack-manifests", "v0.9.json");
|
|
702
|
+
const manifestRef = process.env.EXE_STACK_MANIFEST || (existsSync4(packagedManifest) ? packagedManifest : "https://update.askexe.com/stack-manifest.json");
|
|
686
703
|
return {
|
|
687
704
|
composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync4(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
|
|
688
705
|
envFile: process.env.EXE_STACK_ENV_FILE || (existsSync4(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
|
|
689
|
-
manifestRef
|
|
690
|
-
|
|
691
|
-
|
|
706
|
+
manifestRef,
|
|
707
|
+
// Only call update.askexe.com if explicitly configured or if a remote manifest was requested.
|
|
708
|
+
// Packaged manifests keep cold-start installs unblocked even before update-service entitlements are provisioned.
|
|
709
|
+
auditUrl: process.env.EXE_STACK_AUDIT_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/deploy-audits" : void 0),
|
|
710
|
+
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/image-credentials" : void 0),
|
|
692
711
|
manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN || process.env.EXE_LICENSE_KEY || loadLicense() || void 0,
|
|
693
712
|
manifestPublicKey: loadDefaultPublicKey()
|
|
694
713
|
};
|
|
@@ -811,9 +830,14 @@ Options:
|
|
|
811
830
|
-y, --yes Non-interactive confirmation
|
|
812
831
|
`);
|
|
813
832
|
}
|
|
814
|
-
function printChanges(changes) {
|
|
833
|
+
function printChanges(changes, composeFile, envFile) {
|
|
815
834
|
if (changes.length === 0) {
|
|
816
|
-
|
|
835
|
+
const running = areCliContainersRunning(composeFile, envFile);
|
|
836
|
+
if (running) {
|
|
837
|
+
console.log("\u2705 Stack already matches target manifest.");
|
|
838
|
+
} else {
|
|
839
|
+
console.log("\u26A0\uFE0F Stack .env matches target manifest but containers are not running. Will start them.");
|
|
840
|
+
}
|
|
817
841
|
return;
|
|
818
842
|
}
|
|
819
843
|
console.log("Planned image tag changes:");
|
|
@@ -822,6 +846,17 @@ function printChanges(changes) {
|
|
|
822
846
|
console.log(` ${c.before ?? "<unset>"} \u2192 ${c.after}`);
|
|
823
847
|
}
|
|
824
848
|
}
|
|
849
|
+
function areCliContainersRunning(composeFile, envFile) {
|
|
850
|
+
try {
|
|
851
|
+
const result = spawnSync2("docker", ["compose", "--file", composeFile, "--env-file", envFile, "ps", "-q"], {
|
|
852
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
853
|
+
timeout: 15e3
|
|
854
|
+
});
|
|
855
|
+
return result.status === 0 && (result.stdout?.toString().trim().length ?? 0) > 0;
|
|
856
|
+
} catch {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
825
860
|
function printBreaking(changes) {
|
|
826
861
|
if (changes.length === 0) return;
|
|
827
862
|
console.log("\nBreaking-change notices:");
|
|
@@ -876,7 +911,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
876
911
|
console.log(`Compose: ${opts.composeFile}`);
|
|
877
912
|
console.log(`Env: ${opts.envFile}
|
|
878
913
|
`);
|
|
879
|
-
printChanges(plan.changes);
|
|
914
|
+
printChanges(plan.changes, opts.composeFile, opts.envFile);
|
|
880
915
|
printBreaking(plan.breakingChanges);
|
|
881
916
|
if (opts.check || opts.dryRun) return;
|
|
882
917
|
if (!opts.yes) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.85",
|
|
4
4
|
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
package/stack.release.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"repo": "AskExe/exe-os",
|
|
5
5
|
"service": "exed",
|
|
6
6
|
"packageName": "@askexenow/exe-os",
|
|
7
|
-
"version": "0.9.
|
|
8
|
-
"image": "ghcr.io/askexe/exed:v0.9.
|
|
7
|
+
"version": "0.9.7",
|
|
8
|
+
"image": "ghcr.io/askexe/exed:v0.9.7",
|
|
9
9
|
"imageEnv": "EXED_IMAGE_TAG",
|
|
10
10
|
"stackParticipation": {
|
|
11
11
|
"required": true,
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"breakingChanges": [],
|
|
43
43
|
"dataSovereignty": "Customer-local memory/tasks/behaviors stay in SQLCipher/local storage. Updates must not overwrite roster, identity, behavior, or local memory files.",
|
|
44
44
|
"releaseLine": "v0.9 private/customer pilot; v1.0 is public-beta stable.",
|
|
45
|
-
"highGhostStack": "0.9.
|
|
45
|
+
"highGhostStack": "0.9.7",
|
|
46
46
|
"deploymentScope": "customer"
|
|
47
47
|
},
|
|
48
48
|
"deploymentScope": "customer",
|
|
49
|
-
"highGhostStack": "0.9.
|
|
49
|
+
"highGhostStack": "0.9.7"
|
|
50
50
|
}
|