@askexenow/exe-os 0.9.112 → 0.9.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/bin/agentic-ontology-backfill.js +54 -11
- package/dist/bin/agentic-reflection-backfill.js +29 -1
- package/dist/bin/agentic-semantic-label.js +29 -1
- package/dist/bin/backfill-conversations.js +53 -10
- package/dist/bin/backfill-responses.js +54 -11
- package/dist/bin/backfill-vectors.js +29 -1
- package/dist/bin/bulk-sync-postgres.js +55 -12
- package/dist/bin/cleanup-stale-review-tasks.js +75 -15
- package/dist/bin/cli.js +293 -76
- package/dist/bin/exe-agent-config.js +7 -1
- package/dist/bin/exe-agent.js +28 -2
- package/dist/bin/exe-assign.js +54 -11
- package/dist/bin/exe-boot.js +481 -147
- package/dist/bin/exe-call.js +45 -4
- package/dist/bin/exe-cloud.js +93 -15
- package/dist/bin/exe-dispatch.js +369 -24
- package/dist/bin/exe-doctor.js +53 -10
- package/dist/bin/exe-export-behaviors.js +54 -11
- package/dist/bin/exe-forget.js +54 -11
- package/dist/bin/exe-gateway.js +128 -23
- package/dist/bin/exe-heartbeat.js +75 -15
- package/dist/bin/exe-kill.js +54 -11
- package/dist/bin/exe-launch-agent.js +70 -12
- package/dist/bin/exe-new-employee.js +175 -7
- package/dist/bin/exe-pending-messages.js +75 -15
- package/dist/bin/exe-pending-notifications.js +75 -15
- package/dist/bin/exe-pending-reviews.js +75 -15
- package/dist/bin/exe-rename.js +54 -11
- package/dist/bin/exe-review.js +54 -11
- package/dist/bin/exe-search.js +54 -11
- package/dist/bin/exe-session-cleanup.js +491 -146
- package/dist/bin/exe-settings.js +10 -4
- package/dist/bin/exe-start-codex.js +524 -245
- package/dist/bin/exe-start-opencode.js +534 -165
- package/dist/bin/exe-status.js +75 -15
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +54 -11
- package/dist/bin/git-sweep.js +369 -24
- package/dist/bin/graph-backfill.js +54 -11
- package/dist/bin/graph-export.js +54 -11
- package/dist/bin/install.js +62 -4
- package/dist/bin/intercom-check.js +491 -146
- package/dist/bin/pre-publish.js +13 -1
- package/dist/bin/scan-tasks.js +369 -24
- package/dist/bin/setup.js +91 -13
- package/dist/bin/shard-migrate.js +54 -11
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/update.js +3 -3
- package/dist/gateway/index.js +128 -23
- package/dist/hooks/bug-report-worker.js +128 -23
- package/dist/hooks/codex-stop-task-finalizer.js +512 -140
- package/dist/hooks/commit-complete.js +369 -24
- package/dist/hooks/error-recall.js +54 -11
- package/dist/hooks/ingest.js +4575 -252
- package/dist/hooks/instructions-loaded.js +54 -11
- package/dist/hooks/notification.js +54 -11
- package/dist/hooks/post-compact.js +75 -15
- package/dist/hooks/post-tool-combined.js +75 -15
- package/dist/hooks/pre-compact.js +449 -104
- package/dist/hooks/pre-tool-use.js +90 -15
- package/dist/hooks/prompt-submit.js +129 -24
- package/dist/hooks/session-end.js +451 -109
- package/dist/hooks/session-start.js +104 -16
- package/dist/hooks/stop.js +74 -14
- package/dist/hooks/subagent-stop.js +75 -15
- package/dist/hooks/summary-worker.js +73 -7
- package/dist/index.js +128 -23
- package/dist/lib/agent-config.js +16 -1
- package/dist/lib/cloud-sync.js +38 -1
- package/dist/lib/consolidation.js +16 -1
- package/dist/lib/database.js +16 -0
- package/dist/lib/db.js +16 -0
- package/dist/lib/device-registry.js +16 -0
- package/dist/lib/employee-templates.js +29 -3
- package/dist/lib/employees.js +16 -1
- package/dist/lib/exe-daemon.js +268 -42
- package/dist/lib/hybrid-search.js +54 -11
- package/dist/lib/license.js +3 -3
- package/dist/lib/messaging.js +21 -4
- package/dist/lib/schedules.js +29 -1
- package/dist/lib/skill-learning.js +458 -70
- package/dist/lib/status-brief.js +14 -1
- package/dist/lib/store.js +54 -11
- package/dist/lib/tasks.js +393 -91
- package/dist/lib/tmux-routing.js +316 -14
- package/dist/mcp/server.js +169 -30
- package/dist/mcp/tools/create-task.js +75 -13
- package/dist/mcp/tools/deactivate-behavior.js +33 -24
- package/dist/mcp/tools/list-tasks.js +21 -4
- package/dist/mcp/tools/send-message.js +21 -4
- package/dist/mcp/tools/update-task.js +390 -91
- package/dist/runtime/index.js +446 -101
- package/dist/tui/App.js +208 -54
- package/package.json +1 -1
|
@@ -395,11 +395,168 @@ var init_config = __esm({
|
|
|
395
395
|
}
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
+
// src/lib/runtime-table.ts
|
|
399
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
400
|
+
var init_runtime_table = __esm({
|
|
401
|
+
"src/lib/runtime-table.ts"() {
|
|
402
|
+
"use strict";
|
|
403
|
+
RUNTIME_TABLE = {
|
|
404
|
+
codex: {
|
|
405
|
+
binary: "codex",
|
|
406
|
+
launchMode: "interactive",
|
|
407
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
408
|
+
inlineFlag: "--no-alt-screen",
|
|
409
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
410
|
+
defaultModel: "gpt-5.5"
|
|
411
|
+
},
|
|
412
|
+
opencode: {
|
|
413
|
+
binary: "opencode",
|
|
414
|
+
launchMode: "exec",
|
|
415
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
416
|
+
inlineFlag: "",
|
|
417
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
418
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
DEFAULT_RUNTIME = "claude";
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// src/lib/agent-config.ts
|
|
426
|
+
var agent_config_exports = {};
|
|
427
|
+
__export(agent_config_exports, {
|
|
428
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
429
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
430
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
431
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
432
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
433
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
434
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
435
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
436
|
+
setAgentMcps: () => setAgentMcps,
|
|
437
|
+
setAgentRuntime: () => setAgentRuntime
|
|
438
|
+
});
|
|
439
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
440
|
+
import path2 from "path";
|
|
441
|
+
function loadAgentConfig() {
|
|
442
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
443
|
+
try {
|
|
444
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
445
|
+
} catch {
|
|
446
|
+
return {};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function saveAgentConfig(config) {
|
|
450
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
451
|
+
ensurePrivateDirSync(dir);
|
|
452
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
453
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
454
|
+
}
|
|
455
|
+
function getAgentRuntime(agentId) {
|
|
456
|
+
const config = loadAgentConfig();
|
|
457
|
+
const entry = config[agentId];
|
|
458
|
+
if (entry) return entry;
|
|
459
|
+
const orgDefault = config["default"];
|
|
460
|
+
if (orgDefault) return orgDefault;
|
|
461
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
462
|
+
}
|
|
463
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
464
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
465
|
+
if (!knownModels) {
|
|
466
|
+
return {
|
|
467
|
+
ok: false,
|
|
468
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (!knownModels.includes(model)) {
|
|
472
|
+
return {
|
|
473
|
+
ok: false,
|
|
474
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const config = loadAgentConfig();
|
|
478
|
+
const existing = config[agentId];
|
|
479
|
+
const entry = { runtime, model };
|
|
480
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
481
|
+
if (mcps !== void 0) {
|
|
482
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
483
|
+
} else if (existing?.mcps) {
|
|
484
|
+
entry.mcps = existing.mcps;
|
|
485
|
+
}
|
|
486
|
+
config[agentId] = entry;
|
|
487
|
+
saveAgentConfig(config);
|
|
488
|
+
return { ok: true };
|
|
489
|
+
}
|
|
490
|
+
function setAgentMcps(agentId, mcps) {
|
|
491
|
+
const config = loadAgentConfig();
|
|
492
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
493
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
494
|
+
config[agentId] = existing;
|
|
495
|
+
saveAgentConfig(config);
|
|
496
|
+
return { ok: true };
|
|
497
|
+
}
|
|
498
|
+
function clearAgentRuntime(agentId) {
|
|
499
|
+
const config = loadAgentConfig();
|
|
500
|
+
delete config[agentId];
|
|
501
|
+
saveAgentConfig(config);
|
|
502
|
+
}
|
|
503
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
504
|
+
var init_agent_config = __esm({
|
|
505
|
+
"src/lib/agent-config.ts"() {
|
|
506
|
+
"use strict";
|
|
507
|
+
init_config();
|
|
508
|
+
init_runtime_table();
|
|
509
|
+
init_secure_files();
|
|
510
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
511
|
+
KNOWN_RUNTIMES = {
|
|
512
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
513
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
514
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
515
|
+
};
|
|
516
|
+
RUNTIME_LABELS = {
|
|
517
|
+
claude: "Claude Code (Anthropic)",
|
|
518
|
+
codex: "Codex (OpenAI)",
|
|
519
|
+
opencode: "OpenCode (open source)"
|
|
520
|
+
};
|
|
521
|
+
DEFAULT_MODELS = {
|
|
522
|
+
claude: "claude-opus-4.6",
|
|
523
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
524
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
398
529
|
// src/lib/employees.ts
|
|
530
|
+
var employees_exports = {};
|
|
531
|
+
__export(employees_exports, {
|
|
532
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
533
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
534
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
535
|
+
addEmployee: () => addEmployee,
|
|
536
|
+
baseAgentName: () => baseAgentName,
|
|
537
|
+
canCoordinate: () => canCoordinate,
|
|
538
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
539
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
540
|
+
getEmployee: () => getEmployee,
|
|
541
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
542
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
543
|
+
hasRole: () => hasRole,
|
|
544
|
+
hireEmployee: () => hireEmployee,
|
|
545
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
546
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
547
|
+
isMultiInstance: () => isMultiInstance,
|
|
548
|
+
loadEmployees: () => loadEmployees,
|
|
549
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
550
|
+
normalizeRole: () => normalizeRole,
|
|
551
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
552
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
553
|
+
saveEmployees: () => saveEmployees,
|
|
554
|
+
validateEmployeeName: () => validateEmployeeName
|
|
555
|
+
});
|
|
399
556
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
400
|
-
import { existsSync as
|
|
557
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
401
558
|
import { execSync } from "child_process";
|
|
402
|
-
import
|
|
559
|
+
import path3 from "path";
|
|
403
560
|
import os2 from "os";
|
|
404
561
|
function normalizeRole(role) {
|
|
405
562
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -417,8 +574,26 @@ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
|
|
|
417
574
|
if (!agentName2) return false;
|
|
418
575
|
return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
419
576
|
}
|
|
577
|
+
function canCoordinate(agentName2, agentRole, employees = loadEmployeesSync()) {
|
|
578
|
+
return agentName2 === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName2, employees);
|
|
579
|
+
}
|
|
580
|
+
function validateEmployeeName(name) {
|
|
581
|
+
if (!name) {
|
|
582
|
+
return { valid: false, error: "Name is required" };
|
|
583
|
+
}
|
|
584
|
+
if (name.length > 32) {
|
|
585
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
586
|
+
}
|
|
587
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
588
|
+
return {
|
|
589
|
+
valid: false,
|
|
590
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
return { valid: true };
|
|
594
|
+
}
|
|
420
595
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
421
|
-
if (!
|
|
596
|
+
if (!existsSync4(employeesPath)) {
|
|
422
597
|
return [];
|
|
423
598
|
}
|
|
424
599
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -428,10 +603,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
428
603
|
return [];
|
|
429
604
|
}
|
|
430
605
|
}
|
|
606
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
607
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
608
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
609
|
+
}
|
|
431
610
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
432
|
-
if (!
|
|
611
|
+
if (!existsSync4(employeesPath)) return [];
|
|
433
612
|
try {
|
|
434
|
-
return JSON.parse(
|
|
613
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
435
614
|
} catch {
|
|
436
615
|
return [];
|
|
437
616
|
}
|
|
@@ -439,6 +618,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
439
618
|
function getEmployee(employees, name) {
|
|
440
619
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
441
620
|
}
|
|
621
|
+
function getEmployeeByRole(employees, role) {
|
|
622
|
+
const lower = role.toLowerCase();
|
|
623
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
624
|
+
}
|
|
625
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
626
|
+
const lower = role.toLowerCase();
|
|
627
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
628
|
+
}
|
|
629
|
+
function hasRole(agentName2, role) {
|
|
630
|
+
const employees = loadEmployeesSync();
|
|
631
|
+
const emp = getEmployee(employees, agentName2);
|
|
632
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
633
|
+
}
|
|
442
634
|
function baseAgentName(name, employees) {
|
|
443
635
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
444
636
|
if (!match) return name;
|
|
@@ -453,22 +645,147 @@ function isMultiInstance(agentName2, employees) {
|
|
|
453
645
|
if (!emp) return false;
|
|
454
646
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
455
647
|
}
|
|
456
|
-
|
|
648
|
+
function addEmployee(employees, employee) {
|
|
649
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
650
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
651
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
652
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
653
|
+
}
|
|
654
|
+
return [...employees, normalized];
|
|
655
|
+
}
|
|
656
|
+
function appendToCoordinatorTeam(employee) {
|
|
657
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
658
|
+
if (!coordinator) return;
|
|
659
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
660
|
+
if (!existsSync4(idPath)) return;
|
|
661
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
662
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
663
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
664
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
665
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
666
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
667
|
+
const entry = `
|
|
668
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
669
|
+
`;
|
|
670
|
+
let updated;
|
|
671
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
672
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
673
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
674
|
+
} else {
|
|
675
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
676
|
+
}
|
|
677
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
678
|
+
}
|
|
679
|
+
function capitalize(s) {
|
|
680
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
681
|
+
}
|
|
682
|
+
async function hireEmployee(employee) {
|
|
683
|
+
const employees = await loadEmployees();
|
|
684
|
+
const updated = addEmployee(employees, employee);
|
|
685
|
+
await saveEmployees(updated);
|
|
686
|
+
try {
|
|
687
|
+
appendToCoordinatorTeam(employee);
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
692
|
+
const config = loadAgentConfig2();
|
|
693
|
+
const name = employee.name.toLowerCase();
|
|
694
|
+
if (!config[name] && config["default"]) {
|
|
695
|
+
config[name] = { ...config["default"] };
|
|
696
|
+
saveAgentConfig2(config);
|
|
697
|
+
}
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
return updated;
|
|
701
|
+
}
|
|
702
|
+
async function normalizeRosterCase(rosterPath) {
|
|
703
|
+
const employees = await loadEmployees(rosterPath);
|
|
704
|
+
let changed = false;
|
|
705
|
+
for (const emp of employees) {
|
|
706
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
707
|
+
const oldName = emp.name;
|
|
708
|
+
emp.name = emp.name.toLowerCase();
|
|
709
|
+
changed = true;
|
|
710
|
+
try {
|
|
711
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
712
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
713
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
714
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
715
|
+
renameSync2(oldPath, newPath);
|
|
716
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
717
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
718
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
719
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
720
|
+
unlinkSync(oldPath);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch {
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (changed) {
|
|
728
|
+
await saveEmployees(employees, rosterPath);
|
|
729
|
+
}
|
|
730
|
+
return changed;
|
|
731
|
+
}
|
|
732
|
+
function findExeBin() {
|
|
733
|
+
try {
|
|
734
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function registerBinSymlinks(name) {
|
|
740
|
+
const created = [];
|
|
741
|
+
const skipped = [];
|
|
742
|
+
const errors = [];
|
|
743
|
+
const exeBinPath = findExeBin();
|
|
744
|
+
if (!exeBinPath) {
|
|
745
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
746
|
+
return { created, skipped, errors };
|
|
747
|
+
}
|
|
748
|
+
const binDir = path3.dirname(exeBinPath);
|
|
749
|
+
let target;
|
|
750
|
+
try {
|
|
751
|
+
target = readlinkSync(exeBinPath);
|
|
752
|
+
} catch {
|
|
753
|
+
errors.push("Could not read 'exe' symlink");
|
|
754
|
+
return { created, skipped, errors };
|
|
755
|
+
}
|
|
756
|
+
for (const suffix of ["", "-opencode"]) {
|
|
757
|
+
const linkName = `${name}${suffix}`;
|
|
758
|
+
const linkPath = path3.join(binDir, linkName);
|
|
759
|
+
if (existsSync4(linkPath)) {
|
|
760
|
+
skipped.push(linkName);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
symlinkSync(target, linkPath);
|
|
765
|
+
created.push(linkName);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return { created, skipped, errors };
|
|
771
|
+
}
|
|
772
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
457
773
|
var init_employees = __esm({
|
|
458
774
|
"src/lib/employees.ts"() {
|
|
459
775
|
"use strict";
|
|
460
776
|
init_config();
|
|
461
|
-
EMPLOYEES_PATH =
|
|
777
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
462
778
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
463
779
|
COORDINATOR_ROLE = "COO";
|
|
464
780
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
465
|
-
IDENTITY_DIR =
|
|
781
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
782
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
466
783
|
}
|
|
467
784
|
});
|
|
468
785
|
|
|
469
786
|
// src/lib/database-adapter.ts
|
|
470
787
|
import os3 from "os";
|
|
471
|
-
import
|
|
788
|
+
import path4 from "path";
|
|
472
789
|
import { createRequire } from "module";
|
|
473
790
|
import { pathToFileURL } from "url";
|
|
474
791
|
function quotedIdentifier(identifier) {
|
|
@@ -779,8 +1096,8 @@ async function loadPrismaClient() {
|
|
|
779
1096
|
}
|
|
780
1097
|
return new PrismaClient2();
|
|
781
1098
|
}
|
|
782
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
783
|
-
const requireFromExeDb = createRequire(
|
|
1099
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1100
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
784
1101
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
785
1102
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
786
1103
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1052,8 +1369,8 @@ var init_database_adapter = __esm({
|
|
|
1052
1369
|
|
|
1053
1370
|
// src/lib/daemon-auth.ts
|
|
1054
1371
|
import crypto from "crypto";
|
|
1055
|
-
import
|
|
1056
|
-
import { existsSync as
|
|
1372
|
+
import path5 from "path";
|
|
1373
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1057
1374
|
function normalizeToken(token) {
|
|
1058
1375
|
if (!token) return null;
|
|
1059
1376
|
const trimmed = token.trim();
|
|
@@ -1061,8 +1378,8 @@ function normalizeToken(token) {
|
|
|
1061
1378
|
}
|
|
1062
1379
|
function readDaemonToken() {
|
|
1063
1380
|
try {
|
|
1064
|
-
if (!
|
|
1065
|
-
return normalizeToken(
|
|
1381
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
1382
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
1066
1383
|
} catch {
|
|
1067
1384
|
return null;
|
|
1068
1385
|
}
|
|
@@ -1072,7 +1389,7 @@ function ensureDaemonToken(seed) {
|
|
|
1072
1389
|
if (existing) return existing;
|
|
1073
1390
|
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1074
1391
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1075
|
-
|
|
1392
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
1076
1393
|
`, "utf8");
|
|
1077
1394
|
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1078
1395
|
return token;
|
|
@@ -1083,7 +1400,7 @@ var init_daemon_auth = __esm({
|
|
|
1083
1400
|
"use strict";
|
|
1084
1401
|
init_config();
|
|
1085
1402
|
init_secure_files();
|
|
1086
|
-
DAEMON_TOKEN_PATH =
|
|
1403
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
1087
1404
|
}
|
|
1088
1405
|
});
|
|
1089
1406
|
|
|
@@ -1092,8 +1409,8 @@ import net from "net";
|
|
|
1092
1409
|
import os4 from "os";
|
|
1093
1410
|
import { spawn, execSync as execSync2 } from "child_process";
|
|
1094
1411
|
import { randomUUID } from "crypto";
|
|
1095
|
-
import { existsSync as
|
|
1096
|
-
import
|
|
1412
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1413
|
+
import path6 from "path";
|
|
1097
1414
|
import { fileURLToPath } from "url";
|
|
1098
1415
|
function handleData(chunk) {
|
|
1099
1416
|
_buffer += chunk.toString();
|
|
@@ -1129,9 +1446,9 @@ function isZombie(pid) {
|
|
|
1129
1446
|
}
|
|
1130
1447
|
}
|
|
1131
1448
|
function cleanupStaleFiles() {
|
|
1132
|
-
if (
|
|
1449
|
+
if (existsSync6(PID_PATH)) {
|
|
1133
1450
|
try {
|
|
1134
|
-
const pid = parseInt(
|
|
1451
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1135
1452
|
if (pid > 0) {
|
|
1136
1453
|
try {
|
|
1137
1454
|
process.kill(pid, 0);
|
|
@@ -1156,11 +1473,11 @@ function cleanupStaleFiles() {
|
|
|
1156
1473
|
}
|
|
1157
1474
|
}
|
|
1158
1475
|
function findPackageRoot() {
|
|
1159
|
-
let dir =
|
|
1160
|
-
const { root } =
|
|
1476
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1477
|
+
const { root } = path6.parse(dir);
|
|
1161
1478
|
while (dir !== root) {
|
|
1162
|
-
if (
|
|
1163
|
-
dir =
|
|
1479
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
1480
|
+
dir = path6.dirname(dir);
|
|
1164
1481
|
}
|
|
1165
1482
|
return null;
|
|
1166
1483
|
}
|
|
@@ -1178,8 +1495,8 @@ function spawnDaemon() {
|
|
|
1178
1495
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1179
1496
|
return;
|
|
1180
1497
|
}
|
|
1181
|
-
const daemonPath =
|
|
1182
|
-
if (!
|
|
1498
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1499
|
+
if (!existsSync6(daemonPath)) {
|
|
1183
1500
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1184
1501
|
`);
|
|
1185
1502
|
return;
|
|
@@ -1188,7 +1505,7 @@ function spawnDaemon() {
|
|
|
1188
1505
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1189
1506
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1190
1507
|
`);
|
|
1191
|
-
const logPath =
|
|
1508
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
1192
1509
|
let stderrFd = "ignore";
|
|
1193
1510
|
try {
|
|
1194
1511
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1353,9 +1670,9 @@ function killAndRespawnDaemon() {
|
|
|
1353
1670
|
}
|
|
1354
1671
|
try {
|
|
1355
1672
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1356
|
-
if (
|
|
1673
|
+
if (existsSync6(PID_PATH)) {
|
|
1357
1674
|
try {
|
|
1358
|
-
const pid = parseInt(
|
|
1675
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1359
1676
|
if (pid > 0) {
|
|
1360
1677
|
try {
|
|
1361
1678
|
process.kill(pid, "SIGKILL");
|
|
@@ -1481,9 +1798,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1481
1798
|
"use strict";
|
|
1482
1799
|
init_config();
|
|
1483
1800
|
init_daemon_auth();
|
|
1484
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1485
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1486
|
-
SPAWN_LOCK_PATH =
|
|
1801
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1802
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1803
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1487
1804
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1488
1805
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1489
1806
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -1716,7 +2033,7 @@ __export(database_exports, {
|
|
|
1716
2033
|
isInitialized: () => isInitialized,
|
|
1717
2034
|
setExternalClient: () => setExternalClient
|
|
1718
2035
|
});
|
|
1719
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
2036
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1720
2037
|
import { createClient } from "@libsql/client";
|
|
1721
2038
|
import { homedir } from "os";
|
|
1722
2039
|
import { join } from "path";
|
|
@@ -1769,11 +2086,11 @@ function releaseDbLock() {
|
|
|
1769
2086
|
}
|
|
1770
2087
|
async function initDatabase(config) {
|
|
1771
2088
|
acquireDbLock();
|
|
1772
|
-
if (
|
|
2089
|
+
if (existsSync7(config.dbPath)) {
|
|
1773
2090
|
const dbStat = statSync2(config.dbPath);
|
|
1774
2091
|
if (dbStat.size === 0) {
|
|
1775
2092
|
const walPath = config.dbPath + "-wal";
|
|
1776
|
-
if (
|
|
2093
|
+
if (existsSync7(walPath) && statSync2(walPath).size > 0) {
|
|
1777
2094
|
const backupPath = config.dbPath + ".zeroed-" + Date.now();
|
|
1778
2095
|
copyFileSync(config.dbPath, backupPath);
|
|
1779
2096
|
unlinkSync3(config.dbPath);
|
|
@@ -3292,6 +3609,22 @@ async function ensureSchema() {
|
|
|
3292
3609
|
} catch (e) {
|
|
3293
3610
|
logCatchDebug("migration", e);
|
|
3294
3611
|
}
|
|
3612
|
+
try {
|
|
3613
|
+
await client.execute({
|
|
3614
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3615
|
+
args: []
|
|
3616
|
+
});
|
|
3617
|
+
} catch (e) {
|
|
3618
|
+
logCatchDebug("migration", e);
|
|
3619
|
+
}
|
|
3620
|
+
try {
|
|
3621
|
+
await client.execute({
|
|
3622
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3623
|
+
args: []
|
|
3624
|
+
});
|
|
3625
|
+
} catch (e) {
|
|
3626
|
+
logCatchDebug("migration", e);
|
|
3627
|
+
}
|
|
3295
3628
|
}
|
|
3296
3629
|
async function disposeDatabase() {
|
|
3297
3630
|
if (_walCheckpointTimer) {
|
|
@@ -3344,15 +3677,15 @@ var init_database = __esm({
|
|
|
3344
3677
|
|
|
3345
3678
|
// src/lib/keychain.ts
|
|
3346
3679
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3347
|
-
import { existsSync as
|
|
3680
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3348
3681
|
import { execSync as execSync3 } from "child_process";
|
|
3349
|
-
import
|
|
3682
|
+
import path7 from "path";
|
|
3350
3683
|
import os5 from "os";
|
|
3351
3684
|
function getKeyDir() {
|
|
3352
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3685
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
3353
3686
|
}
|
|
3354
3687
|
function getKeyPath() {
|
|
3355
|
-
return
|
|
3688
|
+
return path7.join(getKeyDir(), "master.key");
|
|
3356
3689
|
}
|
|
3357
3690
|
function nativeKeychainAllowed() {
|
|
3358
3691
|
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
@@ -3383,7 +3716,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
|
3383
3716
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3384
3717
|
if (uid === 0) return true;
|
|
3385
3718
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3386
|
-
return Boolean(exeOsDir &&
|
|
3719
|
+
return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
|
|
3387
3720
|
} catch {
|
|
3388
3721
|
return false;
|
|
3389
3722
|
}
|
|
@@ -3580,7 +3913,7 @@ async function getMasterKey() {
|
|
|
3580
3913
|
}
|
|
3581
3914
|
}
|
|
3582
3915
|
const keyPath = getKeyPath();
|
|
3583
|
-
if (!
|
|
3916
|
+
if (!existsSync8(keyPath)) {
|
|
3584
3917
|
process.stderr.write(
|
|
3585
3918
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3586
3919
|
`
|
|
@@ -3914,14 +4247,14 @@ __export(shard_manager_exports, {
|
|
|
3914
4247
|
listShards: () => listShards,
|
|
3915
4248
|
shardExists: () => shardExists
|
|
3916
4249
|
});
|
|
3917
|
-
import
|
|
3918
|
-
import { existsSync as
|
|
4250
|
+
import path8 from "path";
|
|
4251
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
|
|
3919
4252
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3920
4253
|
function initShardManager(encryptionKey) {
|
|
3921
4254
|
_encryptionKey = encryptionKey;
|
|
3922
4255
|
_keyValidated = false;
|
|
3923
4256
|
_keyValidationPromise = null;
|
|
3924
|
-
if (!
|
|
4257
|
+
if (!existsSync9(SHARDS_DIR)) {
|
|
3925
4258
|
mkdirSync3(SHARDS_DIR, { recursive: true });
|
|
3926
4259
|
}
|
|
3927
4260
|
const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
|
|
@@ -3942,7 +4275,7 @@ async function validateEncryptionKey() {
|
|
|
3942
4275
|
return true;
|
|
3943
4276
|
}
|
|
3944
4277
|
for (const shardFile of existingShards.slice(0, 3)) {
|
|
3945
|
-
const dbPath =
|
|
4278
|
+
const dbPath = path8.join(SHARDS_DIR, shardFile);
|
|
3946
4279
|
const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
|
|
3947
4280
|
try {
|
|
3948
4281
|
await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
|
|
@@ -3985,7 +4318,7 @@ function getShardClient(projectName) {
|
|
|
3985
4318
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3986
4319
|
evictLRU();
|
|
3987
4320
|
}
|
|
3988
|
-
const dbPath =
|
|
4321
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
3989
4322
|
const client = createClient2({
|
|
3990
4323
|
url: `file:${dbPath}`,
|
|
3991
4324
|
encryptionKey: _encryptionKey
|
|
@@ -3996,13 +4329,13 @@ function getShardClient(projectName) {
|
|
|
3996
4329
|
}
|
|
3997
4330
|
function shardExists(projectName) {
|
|
3998
4331
|
const safeName = safeShardName(projectName);
|
|
3999
|
-
return
|
|
4332
|
+
return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
4000
4333
|
}
|
|
4001
4334
|
function safeShardName(projectName) {
|
|
4002
4335
|
return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4003
4336
|
}
|
|
4004
4337
|
function listShards() {
|
|
4005
|
-
if (!
|
|
4338
|
+
if (!existsSync9(SHARDS_DIR)) return [];
|
|
4006
4339
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4007
4340
|
}
|
|
4008
4341
|
async function auditShardHealth(options = {}) {
|
|
@@ -4014,7 +4347,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4014
4347
|
const names = listShards();
|
|
4015
4348
|
const shards = [];
|
|
4016
4349
|
for (const name of names) {
|
|
4017
|
-
const dbPath =
|
|
4350
|
+
const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
|
|
4018
4351
|
const stat = statSync4(dbPath);
|
|
4019
4352
|
const item = {
|
|
4020
4353
|
name,
|
|
@@ -4049,7 +4382,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4049
4382
|
_shards.delete(name);
|
|
4050
4383
|
_shardLastAccess.delete(name);
|
|
4051
4384
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4052
|
-
const archivedPath =
|
|
4385
|
+
const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
4053
4386
|
renameSync3(dbPath, archivedPath);
|
|
4054
4387
|
item.archivedPath = archivedPath;
|
|
4055
4388
|
}
|
|
@@ -4277,11 +4610,11 @@ async function getReadyShardClient(projectName) {
|
|
|
4277
4610
|
client.close();
|
|
4278
4611
|
_shards.delete(safeName);
|
|
4279
4612
|
_shardLastAccess.delete(safeName);
|
|
4280
|
-
const dbPath =
|
|
4281
|
-
if (
|
|
4613
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4614
|
+
if (existsSync9(dbPath)) {
|
|
4282
4615
|
const stat = statSync4(dbPath);
|
|
4283
4616
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4284
|
-
const archivedPath =
|
|
4617
|
+
const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
4285
4618
|
renameSync3(dbPath, archivedPath);
|
|
4286
4619
|
process.stderr.write(
|
|
4287
4620
|
`[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
|
|
@@ -4349,7 +4682,7 @@ var init_shard_manager = __esm({
|
|
|
4349
4682
|
"src/lib/shard-manager.ts"() {
|
|
4350
4683
|
"use strict";
|
|
4351
4684
|
init_config();
|
|
4352
|
-
SHARDS_DIR =
|
|
4685
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
4353
4686
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
4354
4687
|
MAX_OPEN_SHARDS = 10;
|
|
4355
4688
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -4415,11 +4748,17 @@ var init_platform_procedures = __esm({
|
|
|
4415
4748
|
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
4416
4749
|
},
|
|
4417
4750
|
{
|
|
4418
|
-
title: "
|
|
4751
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4419
4752
|
domain: "workflow",
|
|
4420
4753
|
priority: "p1",
|
|
4421
4754
|
content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
|
|
4422
4755
|
},
|
|
4756
|
+
{
|
|
4757
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
4758
|
+
domain: "identity",
|
|
4759
|
+
priority: "p0",
|
|
4760
|
+
content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
|
|
4761
|
+
},
|
|
4423
4762
|
{
|
|
4424
4763
|
title: "Single dispatch path \u2014 create_task only",
|
|
4425
4764
|
domain: "workflow",
|
|
@@ -4453,6 +4792,12 @@ var init_platform_procedures = __esm({
|
|
|
4453
4792
|
priority: "p0",
|
|
4454
4793
|
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
4455
4794
|
},
|
|
4795
|
+
{
|
|
4796
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
4797
|
+
domain: "security",
|
|
4798
|
+
priority: "p0",
|
|
4799
|
+
content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
|
|
4800
|
+
},
|
|
4456
4801
|
{
|
|
4457
4802
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4458
4803
|
domain: "support",
|
|
@@ -4738,10 +5083,24 @@ function stableId(memoryId, type, content) {
|
|
|
4738
5083
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
4739
5084
|
}
|
|
4740
5085
|
function cleanText(text) {
|
|
4741
|
-
|
|
5086
|
+
let cleaned = text.replace(
|
|
5087
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5088
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5089
|
+
);
|
|
5090
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5091
|
+
return cleaned;
|
|
4742
5092
|
}
|
|
4743
|
-
function
|
|
4744
|
-
|
|
5093
|
+
function splitSegments(text) {
|
|
5094
|
+
const cleaned = cleanText(text);
|
|
5095
|
+
const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
|
|
5096
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5097
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5098
|
+
if (lines.length > 0) return lines;
|
|
5099
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5100
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
return segments;
|
|
4745
5104
|
}
|
|
4746
5105
|
function inferCardType(sentence, toolName) {
|
|
4747
5106
|
const lower = sentence.toLowerCase();
|
|
@@ -4773,12 +5132,12 @@ function predicateFor(type) {
|
|
|
4773
5132
|
}
|
|
4774
5133
|
}
|
|
4775
5134
|
function extractMemoryCards(row) {
|
|
4776
|
-
const
|
|
5135
|
+
const segments = splitSegments(row.raw_text);
|
|
4777
5136
|
const cards = [];
|
|
4778
|
-
for (const sentence of
|
|
5137
|
+
for (const sentence of segments) {
|
|
4779
5138
|
const type = inferCardType(sentence, row.tool_name);
|
|
4780
5139
|
const subject = extractSubject(sentence, row.agent_id);
|
|
4781
|
-
const content = sentence.length >
|
|
5140
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
4782
5141
|
cards.push({
|
|
4783
5142
|
id: stableId(row.id, type, content),
|
|
4784
5143
|
memory_id: row.id,
|
|
@@ -4874,13 +5233,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
4874
5233
|
last_accessed: String(row.timestamp)
|
|
4875
5234
|
}));
|
|
4876
5235
|
}
|
|
4877
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5236
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
4878
5237
|
var init_memory_cards = __esm({
|
|
4879
5238
|
"src/lib/memory-cards.ts"() {
|
|
4880
5239
|
"use strict";
|
|
4881
5240
|
init_database();
|
|
4882
|
-
MAX_CARDS_PER_MEMORY =
|
|
4883
|
-
|
|
5241
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5242
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5243
|
+
MIN_SEGMENT_CHARS = 20;
|
|
4884
5244
|
}
|
|
4885
5245
|
});
|
|
4886
5246
|
|
|
@@ -5816,12 +6176,12 @@ var init_store = __esm({
|
|
|
5816
6176
|
});
|
|
5817
6177
|
|
|
5818
6178
|
// src/lib/session-registry.ts
|
|
5819
|
-
import { readFileSync as
|
|
5820
|
-
import
|
|
6179
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
|
|
6180
|
+
import path9 from "path";
|
|
5821
6181
|
import os6 from "os";
|
|
5822
6182
|
function registerSession(entry) {
|
|
5823
|
-
const dir =
|
|
5824
|
-
if (!
|
|
6183
|
+
const dir = path9.dirname(REGISTRY_PATH);
|
|
6184
|
+
if (!existsSync10(dir)) {
|
|
5825
6185
|
mkdirSync4(dir, { recursive: true });
|
|
5826
6186
|
}
|
|
5827
6187
|
const sessions = listSessions();
|
|
@@ -5831,11 +6191,11 @@ function registerSession(entry) {
|
|
|
5831
6191
|
} else {
|
|
5832
6192
|
sessions.push(entry);
|
|
5833
6193
|
}
|
|
5834
|
-
|
|
6194
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
5835
6195
|
}
|
|
5836
6196
|
function listSessions() {
|
|
5837
6197
|
try {
|
|
5838
|
-
const raw =
|
|
6198
|
+
const raw = readFileSync6(REGISTRY_PATH, "utf8");
|
|
5839
6199
|
return JSON.parse(raw);
|
|
5840
6200
|
} catch {
|
|
5841
6201
|
return [];
|
|
@@ -5845,7 +6205,7 @@ var REGISTRY_PATH;
|
|
|
5845
6205
|
var init_session_registry = __esm({
|
|
5846
6206
|
"src/lib/session-registry.ts"() {
|
|
5847
6207
|
"use strict";
|
|
5848
|
-
REGISTRY_PATH =
|
|
6208
|
+
REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
5849
6209
|
}
|
|
5850
6210
|
});
|
|
5851
6211
|
|
|
@@ -6107,68 +6467,6 @@ var init_provider_table = __esm({
|
|
|
6107
6467
|
}
|
|
6108
6468
|
});
|
|
6109
6469
|
|
|
6110
|
-
// src/lib/runtime-table.ts
|
|
6111
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
6112
|
-
var init_runtime_table = __esm({
|
|
6113
|
-
"src/lib/runtime-table.ts"() {
|
|
6114
|
-
"use strict";
|
|
6115
|
-
RUNTIME_TABLE = {
|
|
6116
|
-
codex: {
|
|
6117
|
-
binary: "codex",
|
|
6118
|
-
launchMode: "interactive",
|
|
6119
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
6120
|
-
inlineFlag: "--no-alt-screen",
|
|
6121
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
6122
|
-
defaultModel: "gpt-5.5"
|
|
6123
|
-
},
|
|
6124
|
-
opencode: {
|
|
6125
|
-
binary: "opencode",
|
|
6126
|
-
launchMode: "exec",
|
|
6127
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
6128
|
-
inlineFlag: "",
|
|
6129
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
6130
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
6131
|
-
}
|
|
6132
|
-
};
|
|
6133
|
-
DEFAULT_RUNTIME = "claude";
|
|
6134
|
-
}
|
|
6135
|
-
});
|
|
6136
|
-
|
|
6137
|
-
// src/lib/agent-config.ts
|
|
6138
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
|
|
6139
|
-
import path9 from "path";
|
|
6140
|
-
function loadAgentConfig() {
|
|
6141
|
-
if (!existsSync10(AGENT_CONFIG_PATH)) return {};
|
|
6142
|
-
try {
|
|
6143
|
-
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
6144
|
-
} catch {
|
|
6145
|
-
return {};
|
|
6146
|
-
}
|
|
6147
|
-
}
|
|
6148
|
-
function getAgentRuntime(agentId) {
|
|
6149
|
-
const config = loadAgentConfig();
|
|
6150
|
-
const entry = config[agentId];
|
|
6151
|
-
if (entry) return entry;
|
|
6152
|
-
const orgDefault = config["default"];
|
|
6153
|
-
if (orgDefault) return orgDefault;
|
|
6154
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
6155
|
-
}
|
|
6156
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
6157
|
-
var init_agent_config = __esm({
|
|
6158
|
-
"src/lib/agent-config.ts"() {
|
|
6159
|
-
"use strict";
|
|
6160
|
-
init_config();
|
|
6161
|
-
init_runtime_table();
|
|
6162
|
-
init_secure_files();
|
|
6163
|
-
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
6164
|
-
DEFAULT_MODELS = {
|
|
6165
|
-
claude: "claude-opus-4.6",
|
|
6166
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
6167
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
6168
|
-
};
|
|
6169
|
-
}
|
|
6170
|
-
});
|
|
6171
|
-
|
|
6172
6470
|
// src/lib/intercom-queue.ts
|
|
6173
6471
|
var intercom_queue_exports = {};
|
|
6174
6472
|
__export(intercom_queue_exports, {
|
|
@@ -6663,7 +6961,7 @@ async function assertVpsLicense(opts) {
|
|
|
6663
6961
|
}
|
|
6664
6962
|
if (!transientFailure) {
|
|
6665
6963
|
throw new Error(
|
|
6666
|
-
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com
|
|
6964
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
6667
6965
|
);
|
|
6668
6966
|
}
|
|
6669
6967
|
const fresh = await getCachedLicense();
|
|
@@ -6700,7 +6998,7 @@ async function assertVpsLicense(opts) {
|
|
|
6700
6998
|
} catch {
|
|
6701
6999
|
}
|
|
6702
7000
|
throw new Error(
|
|
6703
|
-
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com
|
|
7001
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
|
|
6704
7002
|
);
|
|
6705
7003
|
}
|
|
6706
7004
|
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
@@ -6732,7 +7030,7 @@ var init_license = __esm({
|
|
|
6732
7030
|
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
6733
7031
|
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
6734
7032
|
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
6735
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
7033
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
6736
7034
|
RETRY_DELAY_MS = 500;
|
|
6737
7035
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
6738
7036
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
@@ -7198,6 +7496,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
7198
7496
|
args: [identifier, ...scope.args]
|
|
7199
7497
|
});
|
|
7200
7498
|
if (result.rows.length === 1) return result.rows[0];
|
|
7499
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
7500
|
+
result = await client.execute({
|
|
7501
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
7502
|
+
args: [`${identifier}%`]
|
|
7503
|
+
});
|
|
7504
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
7505
|
+
if (result.rows.length > 1) {
|
|
7506
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
7507
|
+
throw new Error(
|
|
7508
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
7509
|
+
);
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7201
7512
|
result = await client.execute({
|
|
7202
7513
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
7203
7514
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -8052,12 +8363,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
8052
8363
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
8053
8364
|
args: [now, taskId]
|
|
8054
8365
|
});
|
|
8055
|
-
if (
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8366
|
+
if (unblocked.rowsAffected === 0) return;
|
|
8367
|
+
const ubScope = sessionScopeFilter();
|
|
8368
|
+
const unblockedRows = await client.execute({
|
|
8369
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
8370
|
+
args: [now, ...ubScope.args]
|
|
8371
|
+
});
|
|
8372
|
+
if (baseDir) {
|
|
8061
8373
|
for (const ur of unblockedRows.rows) {
|
|
8062
8374
|
try {
|
|
8063
8375
|
const ubFile = path18.join(baseDir, String(ur.task_file));
|
|
@@ -8069,6 +8381,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
8069
8381
|
}
|
|
8070
8382
|
}
|
|
8071
8383
|
}
|
|
8384
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
8385
|
+
try {
|
|
8386
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
8387
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
8388
|
+
for (const ur of unblockedRows.rows) {
|
|
8389
|
+
const assignee = String(ur.assigned_to);
|
|
8390
|
+
if (dispatched.has(assignee)) continue;
|
|
8391
|
+
dispatched.add(assignee);
|
|
8392
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
8393
|
+
}
|
|
8394
|
+
} catch {
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
8072
8397
|
}
|
|
8073
8398
|
async function findNextTask(assignedTo) {
|
|
8074
8399
|
const client = getClient();
|
|
@@ -8278,6 +8603,15 @@ var init_embedder = __esm({
|
|
|
8278
8603
|
// src/lib/behaviors.ts
|
|
8279
8604
|
import crypto5 from "crypto";
|
|
8280
8605
|
async function storeBehavior(opts) {
|
|
8606
|
+
try {
|
|
8607
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
8608
|
+
const roster = loadEmployeesSync2();
|
|
8609
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
8610
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
8611
|
+
}
|
|
8612
|
+
} catch (e) {
|
|
8613
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
8614
|
+
}
|
|
8281
8615
|
const client = getClient();
|
|
8282
8616
|
const id = crypto5.randomUUID();
|
|
8283
8617
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -8731,6 +9065,12 @@ async function updateTask(input) {
|
|
|
8731
9065
|
}
|
|
8732
9066
|
}
|
|
8733
9067
|
}
|
|
9068
|
+
if (input.status === "cancelled") {
|
|
9069
|
+
try {
|
|
9070
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
9071
|
+
} catch {
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
8734
9074
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
8735
9075
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
8736
9076
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -9262,11 +9602,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
9262
9602
|
}
|
|
9263
9603
|
}
|
|
9264
9604
|
function resolveExeSession() {
|
|
9605
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
9606
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
9607
|
+
if (fromEnv) return fromEnv;
|
|
9608
|
+
}
|
|
9265
9609
|
const mySession = getMySession();
|
|
9266
9610
|
if (!mySession) {
|
|
9267
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
9268
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
9269
|
-
}
|
|
9270
9611
|
return null;
|
|
9271
9612
|
}
|
|
9272
9613
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -9281,6 +9622,10 @@ function resolveExeSession() {
|
|
|
9281
9622
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
9282
9623
|
`
|
|
9283
9624
|
);
|
|
9625
|
+
try {
|
|
9626
|
+
registerParentExe(key, fromSessionName);
|
|
9627
|
+
} catch {
|
|
9628
|
+
}
|
|
9284
9629
|
candidate = fromSessionName;
|
|
9285
9630
|
} else {
|
|
9286
9631
|
candidate = fromCache;
|