@askexenow/exe-os 0.9.111 → 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 +62 -12
- package/dist/bin/agentic-reflection-backfill.js +37 -2
- package/dist/bin/agentic-semantic-label.js +37 -2
- package/dist/bin/backfill-conversations.js +61 -11
- package/dist/bin/backfill-responses.js +62 -12
- package/dist/bin/backfill-vectors.js +37 -2
- package/dist/bin/bulk-sync-postgres.js +63 -13
- package/dist/bin/cleanup-stale-review-tasks.js +83 -16
- package/dist/bin/cli.js +312 -80
- package/dist/bin/exe-agent-config.js +7 -1
- package/dist/bin/exe-agent.js +29 -3
- package/dist/bin/exe-assign.js +62 -12
- package/dist/bin/exe-boot.js +500 -151
- package/dist/bin/exe-call.js +46 -5
- package/dist/bin/exe-cloud.js +101 -16
- package/dist/bin/exe-dispatch.js +827 -27
- package/dist/bin/exe-doctor.js +61 -11
- package/dist/bin/exe-export-behaviors.js +67 -14
- package/dist/bin/exe-forget.js +62 -12
- package/dist/bin/exe-gateway.js +147 -27
- package/dist/bin/exe-heartbeat.js +83 -16
- package/dist/bin/exe-kill.js +62 -12
- package/dist/bin/exe-launch-agent.js +83 -15
- package/dist/bin/exe-new-employee.js +176 -8
- package/dist/bin/exe-pending-messages.js +83 -16
- package/dist/bin/exe-pending-notifications.js +83 -16
- package/dist/bin/exe-pending-reviews.js +83 -16
- package/dist/bin/exe-rename.js +62 -12
- package/dist/bin/exe-review.js +62 -12
- package/dist/bin/exe-search.js +62 -12
- package/dist/bin/exe-session-cleanup.js +949 -149
- package/dist/bin/exe-settings.js +10 -4
- package/dist/bin/exe-start-codex.js +537 -248
- package/dist/bin/exe-start-opencode.js +547 -168
- package/dist/bin/exe-status.js +83 -16
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +62 -12
- package/dist/bin/git-sweep.js +827 -27
- package/dist/bin/graph-backfill.js +62 -12
- package/dist/bin/graph-export.js +62 -12
- package/dist/bin/install.js +62 -4
- package/dist/bin/intercom-check.js +949 -149
- package/dist/bin/pre-publish.js +14 -2
- package/dist/bin/scan-tasks.js +827 -27
- package/dist/bin/setup.js +99 -14
- package/dist/bin/shard-migrate.js +62 -12
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/update.js +3 -3
- package/dist/gateway/index.js +586 -26
- package/dist/hooks/bug-report-worker.js +586 -26
- package/dist/hooks/codex-stop-task-finalizer.js +977 -143
- package/dist/hooks/commit-complete.js +827 -27
- package/dist/hooks/error-recall.js +62 -12
- package/dist/hooks/ingest.js +4579 -249
- package/dist/hooks/instructions-loaded.js +62 -12
- package/dist/hooks/notification.js +62 -12
- package/dist/hooks/post-compact.js +83 -16
- package/dist/hooks/post-tool-combined.js +83 -16
- package/dist/hooks/pre-compact.js +907 -107
- package/dist/hooks/pre-tool-use.js +98 -16
- package/dist/hooks/prompt-submit.js +596 -30
- package/dist/hooks/session-end.js +909 -112
- package/dist/hooks/session-start.js +112 -17
- package/dist/hooks/stop.js +82 -15
- package/dist/hooks/subagent-stop.js +83 -16
- package/dist/hooks/summary-worker.js +81 -8
- package/dist/index.js +595 -29
- package/dist/lib/agent-config.js +16 -1
- package/dist/lib/cloud-sync.js +45 -1
- package/dist/lib/consolidation.js +16 -1
- package/dist/lib/database.js +23 -0
- package/dist/lib/db.js +23 -0
- package/dist/lib/device-registry.js +23 -0
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +16 -1
- package/dist/lib/exe-daemon.js +482 -52
- package/dist/lib/hybrid-search.js +62 -12
- package/dist/lib/license.js +3 -3
- package/dist/lib/messaging.js +21 -4
- package/dist/lib/schedules.js +37 -2
- package/dist/lib/skill-learning.js +910 -41
- package/dist/lib/status-brief.js +14 -1
- package/dist/lib/store.js +62 -12
- package/dist/lib/tasks.js +843 -93
- package/dist/lib/tmux-routing.js +766 -16
- package/dist/mcp/server.js +238 -41
- package/dist/mcp/tools/create-task.js +525 -15
- 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 +840 -93
- package/dist/runtime/index.js +913 -107
- package/dist/tui/App.js +227 -58
- package/package.json +1 -1
|
@@ -398,11 +398,168 @@ var init_session_key = __esm({
|
|
|
398
398
|
}
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
+
// src/lib/runtime-table.ts
|
|
402
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
403
|
+
var init_runtime_table = __esm({
|
|
404
|
+
"src/lib/runtime-table.ts"() {
|
|
405
|
+
"use strict";
|
|
406
|
+
RUNTIME_TABLE = {
|
|
407
|
+
codex: {
|
|
408
|
+
binary: "codex",
|
|
409
|
+
launchMode: "interactive",
|
|
410
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
411
|
+
inlineFlag: "--no-alt-screen",
|
|
412
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
413
|
+
defaultModel: "gpt-5.5"
|
|
414
|
+
},
|
|
415
|
+
opencode: {
|
|
416
|
+
binary: "opencode",
|
|
417
|
+
launchMode: "exec",
|
|
418
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
419
|
+
inlineFlag: "",
|
|
420
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
421
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
DEFAULT_RUNTIME = "claude";
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// src/lib/agent-config.ts
|
|
429
|
+
var agent_config_exports = {};
|
|
430
|
+
__export(agent_config_exports, {
|
|
431
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
432
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
433
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
434
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
435
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
436
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
437
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
438
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
439
|
+
setAgentMcps: () => setAgentMcps,
|
|
440
|
+
setAgentRuntime: () => setAgentRuntime
|
|
441
|
+
});
|
|
442
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
443
|
+
import path2 from "path";
|
|
444
|
+
function loadAgentConfig() {
|
|
445
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
446
|
+
try {
|
|
447
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
448
|
+
} catch {
|
|
449
|
+
return {};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function saveAgentConfig(config) {
|
|
453
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
454
|
+
ensurePrivateDirSync(dir);
|
|
455
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
456
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
457
|
+
}
|
|
458
|
+
function getAgentRuntime(agentId) {
|
|
459
|
+
const config = loadAgentConfig();
|
|
460
|
+
const entry = config[agentId];
|
|
461
|
+
if (entry) return entry;
|
|
462
|
+
const orgDefault = config["default"];
|
|
463
|
+
if (orgDefault) return orgDefault;
|
|
464
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
465
|
+
}
|
|
466
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
467
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
468
|
+
if (!knownModels) {
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (!knownModels.includes(model)) {
|
|
475
|
+
return {
|
|
476
|
+
ok: false,
|
|
477
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const config = loadAgentConfig();
|
|
481
|
+
const existing = config[agentId];
|
|
482
|
+
const entry = { runtime, model };
|
|
483
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
484
|
+
if (mcps !== void 0) {
|
|
485
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
486
|
+
} else if (existing?.mcps) {
|
|
487
|
+
entry.mcps = existing.mcps;
|
|
488
|
+
}
|
|
489
|
+
config[agentId] = entry;
|
|
490
|
+
saveAgentConfig(config);
|
|
491
|
+
return { ok: true };
|
|
492
|
+
}
|
|
493
|
+
function setAgentMcps(agentId, mcps) {
|
|
494
|
+
const config = loadAgentConfig();
|
|
495
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
496
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
497
|
+
config[agentId] = existing;
|
|
498
|
+
saveAgentConfig(config);
|
|
499
|
+
return { ok: true };
|
|
500
|
+
}
|
|
501
|
+
function clearAgentRuntime(agentId) {
|
|
502
|
+
const config = loadAgentConfig();
|
|
503
|
+
delete config[agentId];
|
|
504
|
+
saveAgentConfig(config);
|
|
505
|
+
}
|
|
506
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
507
|
+
var init_agent_config = __esm({
|
|
508
|
+
"src/lib/agent-config.ts"() {
|
|
509
|
+
"use strict";
|
|
510
|
+
init_config();
|
|
511
|
+
init_runtime_table();
|
|
512
|
+
init_secure_files();
|
|
513
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
514
|
+
KNOWN_RUNTIMES = {
|
|
515
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
516
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
517
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
518
|
+
};
|
|
519
|
+
RUNTIME_LABELS = {
|
|
520
|
+
claude: "Claude Code (Anthropic)",
|
|
521
|
+
codex: "Codex (OpenAI)",
|
|
522
|
+
opencode: "OpenCode (open source)"
|
|
523
|
+
};
|
|
524
|
+
DEFAULT_MODELS = {
|
|
525
|
+
claude: "claude-opus-4.6",
|
|
526
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
527
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
401
532
|
// src/lib/employees.ts
|
|
533
|
+
var employees_exports = {};
|
|
534
|
+
__export(employees_exports, {
|
|
535
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
536
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
537
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
538
|
+
addEmployee: () => addEmployee,
|
|
539
|
+
baseAgentName: () => baseAgentName,
|
|
540
|
+
canCoordinate: () => canCoordinate,
|
|
541
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
542
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
543
|
+
getEmployee: () => getEmployee,
|
|
544
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
545
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
546
|
+
hasRole: () => hasRole,
|
|
547
|
+
hireEmployee: () => hireEmployee,
|
|
548
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
549
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
550
|
+
isMultiInstance: () => isMultiInstance,
|
|
551
|
+
loadEmployees: () => loadEmployees,
|
|
552
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
553
|
+
normalizeRole: () => normalizeRole,
|
|
554
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
555
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
556
|
+
saveEmployees: () => saveEmployees,
|
|
557
|
+
validateEmployeeName: () => validateEmployeeName
|
|
558
|
+
});
|
|
402
559
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
403
|
-
import { existsSync as
|
|
560
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
404
561
|
import { execSync as execSync2 } from "child_process";
|
|
405
|
-
import
|
|
562
|
+
import path3 from "path";
|
|
406
563
|
import os2 from "os";
|
|
407
564
|
function normalizeRole(role) {
|
|
408
565
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -423,8 +580,23 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
423
580
|
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
424
581
|
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
425
582
|
}
|
|
583
|
+
function validateEmployeeName(name) {
|
|
584
|
+
if (!name) {
|
|
585
|
+
return { valid: false, error: "Name is required" };
|
|
586
|
+
}
|
|
587
|
+
if (name.length > 32) {
|
|
588
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
589
|
+
}
|
|
590
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
591
|
+
return {
|
|
592
|
+
valid: false,
|
|
593
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
return { valid: true };
|
|
597
|
+
}
|
|
426
598
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
427
|
-
if (!
|
|
599
|
+
if (!existsSync4(employeesPath)) {
|
|
428
600
|
return [];
|
|
429
601
|
}
|
|
430
602
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -434,10 +606,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
434
606
|
return [];
|
|
435
607
|
}
|
|
436
608
|
}
|
|
609
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
610
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
611
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
612
|
+
}
|
|
437
613
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
438
|
-
if (!
|
|
614
|
+
if (!existsSync4(employeesPath)) return [];
|
|
439
615
|
try {
|
|
440
|
-
return JSON.parse(
|
|
616
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
441
617
|
} catch {
|
|
442
618
|
return [];
|
|
443
619
|
}
|
|
@@ -445,6 +621,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
445
621
|
function getEmployee(employees, name) {
|
|
446
622
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
447
623
|
}
|
|
624
|
+
function getEmployeeByRole(employees, role) {
|
|
625
|
+
const lower = role.toLowerCase();
|
|
626
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
627
|
+
}
|
|
628
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
629
|
+
const lower = role.toLowerCase();
|
|
630
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
631
|
+
}
|
|
632
|
+
function hasRole(agentName, role) {
|
|
633
|
+
const employees = loadEmployeesSync();
|
|
634
|
+
const emp = getEmployee(employees, agentName);
|
|
635
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
636
|
+
}
|
|
448
637
|
function baseAgentName(name, employees) {
|
|
449
638
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
450
639
|
if (!match) return name;
|
|
@@ -459,26 +648,151 @@ function isMultiInstance(agentName, employees) {
|
|
|
459
648
|
if (!emp) return false;
|
|
460
649
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
461
650
|
}
|
|
462
|
-
|
|
651
|
+
function addEmployee(employees, employee) {
|
|
652
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
653
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
654
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
655
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
656
|
+
}
|
|
657
|
+
return [...employees, normalized];
|
|
658
|
+
}
|
|
659
|
+
function appendToCoordinatorTeam(employee) {
|
|
660
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
661
|
+
if (!coordinator) return;
|
|
662
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
663
|
+
if (!existsSync4(idPath)) return;
|
|
664
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
665
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
666
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
667
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
668
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
669
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
670
|
+
const entry = `
|
|
671
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
672
|
+
`;
|
|
673
|
+
let updated;
|
|
674
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
675
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
676
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
677
|
+
} else {
|
|
678
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
679
|
+
}
|
|
680
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
681
|
+
}
|
|
682
|
+
function capitalize(s) {
|
|
683
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
684
|
+
}
|
|
685
|
+
async function hireEmployee(employee) {
|
|
686
|
+
const employees = await loadEmployees();
|
|
687
|
+
const updated = addEmployee(employees, employee);
|
|
688
|
+
await saveEmployees(updated);
|
|
689
|
+
try {
|
|
690
|
+
appendToCoordinatorTeam(employee);
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
695
|
+
const config = loadAgentConfig2();
|
|
696
|
+
const name = employee.name.toLowerCase();
|
|
697
|
+
if (!config[name] && config["default"]) {
|
|
698
|
+
config[name] = { ...config["default"] };
|
|
699
|
+
saveAgentConfig2(config);
|
|
700
|
+
}
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
return updated;
|
|
704
|
+
}
|
|
705
|
+
async function normalizeRosterCase(rosterPath) {
|
|
706
|
+
const employees = await loadEmployees(rosterPath);
|
|
707
|
+
let changed = false;
|
|
708
|
+
for (const emp of employees) {
|
|
709
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
710
|
+
const oldName = emp.name;
|
|
711
|
+
emp.name = emp.name.toLowerCase();
|
|
712
|
+
changed = true;
|
|
713
|
+
try {
|
|
714
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
715
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
716
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
717
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
718
|
+
renameSync2(oldPath, newPath);
|
|
719
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
720
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
721
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
722
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
723
|
+
unlinkSync(oldPath);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (changed) {
|
|
731
|
+
await saveEmployees(employees, rosterPath);
|
|
732
|
+
}
|
|
733
|
+
return changed;
|
|
734
|
+
}
|
|
735
|
+
function findExeBin() {
|
|
736
|
+
try {
|
|
737
|
+
return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
738
|
+
} catch {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function registerBinSymlinks(name) {
|
|
743
|
+
const created = [];
|
|
744
|
+
const skipped = [];
|
|
745
|
+
const errors = [];
|
|
746
|
+
const exeBinPath = findExeBin();
|
|
747
|
+
if (!exeBinPath) {
|
|
748
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
749
|
+
return { created, skipped, errors };
|
|
750
|
+
}
|
|
751
|
+
const binDir = path3.dirname(exeBinPath);
|
|
752
|
+
let target;
|
|
753
|
+
try {
|
|
754
|
+
target = readlinkSync(exeBinPath);
|
|
755
|
+
} catch {
|
|
756
|
+
errors.push("Could not read 'exe' symlink");
|
|
757
|
+
return { created, skipped, errors };
|
|
758
|
+
}
|
|
759
|
+
for (const suffix of ["", "-opencode"]) {
|
|
760
|
+
const linkName = `${name}${suffix}`;
|
|
761
|
+
const linkPath = path3.join(binDir, linkName);
|
|
762
|
+
if (existsSync4(linkPath)) {
|
|
763
|
+
skipped.push(linkName);
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
try {
|
|
767
|
+
symlinkSync(target, linkPath);
|
|
768
|
+
created.push(linkName);
|
|
769
|
+
} catch (err) {
|
|
770
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return { created, skipped, errors };
|
|
774
|
+
}
|
|
775
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
463
776
|
var init_employees = __esm({
|
|
464
777
|
"src/lib/employees.ts"() {
|
|
465
778
|
"use strict";
|
|
466
779
|
init_config();
|
|
467
|
-
EMPLOYEES_PATH =
|
|
780
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
468
781
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
469
782
|
COORDINATOR_ROLE = "COO";
|
|
470
783
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
471
|
-
IDENTITY_DIR =
|
|
784
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
785
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
472
786
|
}
|
|
473
787
|
});
|
|
474
788
|
|
|
475
789
|
// src/lib/session-registry.ts
|
|
476
|
-
import { readFileSync as
|
|
477
|
-
import
|
|
790
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
|
|
791
|
+
import path6 from "path";
|
|
478
792
|
import os4 from "os";
|
|
479
793
|
function registerSession(entry) {
|
|
480
|
-
const dir =
|
|
481
|
-
if (!
|
|
794
|
+
const dir = path6.dirname(REGISTRY_PATH);
|
|
795
|
+
if (!existsSync6(dir)) {
|
|
482
796
|
mkdirSync4(dir, { recursive: true });
|
|
483
797
|
}
|
|
484
798
|
const sessions = listSessions();
|
|
@@ -488,11 +802,11 @@ function registerSession(entry) {
|
|
|
488
802
|
} else {
|
|
489
803
|
sessions.push(entry);
|
|
490
804
|
}
|
|
491
|
-
|
|
805
|
+
writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
492
806
|
}
|
|
493
807
|
function listSessions() {
|
|
494
808
|
try {
|
|
495
|
-
const raw =
|
|
809
|
+
const raw = readFileSync6(REGISTRY_PATH, "utf8");
|
|
496
810
|
return JSON.parse(raw);
|
|
497
811
|
} catch {
|
|
498
812
|
return [];
|
|
@@ -502,7 +816,7 @@ var REGISTRY_PATH;
|
|
|
502
816
|
var init_session_registry = __esm({
|
|
503
817
|
"src/lib/session-registry.ts"() {
|
|
504
818
|
"use strict";
|
|
505
|
-
REGISTRY_PATH =
|
|
819
|
+
REGISTRY_PATH = path6.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
506
820
|
}
|
|
507
821
|
});
|
|
508
822
|
|
|
@@ -696,68 +1010,6 @@ var init_provider_table = __esm({
|
|
|
696
1010
|
}
|
|
697
1011
|
});
|
|
698
1012
|
|
|
699
|
-
// src/lib/runtime-table.ts
|
|
700
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
701
|
-
var init_runtime_table = __esm({
|
|
702
|
-
"src/lib/runtime-table.ts"() {
|
|
703
|
-
"use strict";
|
|
704
|
-
RUNTIME_TABLE = {
|
|
705
|
-
codex: {
|
|
706
|
-
binary: "codex",
|
|
707
|
-
launchMode: "interactive",
|
|
708
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
709
|
-
inlineFlag: "--no-alt-screen",
|
|
710
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
711
|
-
defaultModel: "gpt-5.5"
|
|
712
|
-
},
|
|
713
|
-
opencode: {
|
|
714
|
-
binary: "opencode",
|
|
715
|
-
launchMode: "exec",
|
|
716
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
717
|
-
inlineFlag: "",
|
|
718
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
719
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
720
|
-
}
|
|
721
|
-
};
|
|
722
|
-
DEFAULT_RUNTIME = "claude";
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
// src/lib/agent-config.ts
|
|
727
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
|
|
728
|
-
import path6 from "path";
|
|
729
|
-
function loadAgentConfig() {
|
|
730
|
-
if (!existsSync6(AGENT_CONFIG_PATH)) return {};
|
|
731
|
-
try {
|
|
732
|
-
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
733
|
-
} catch {
|
|
734
|
-
return {};
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
function getAgentRuntime(agentId) {
|
|
738
|
-
const config = loadAgentConfig();
|
|
739
|
-
const entry = config[agentId];
|
|
740
|
-
if (entry) return entry;
|
|
741
|
-
const orgDefault = config["default"];
|
|
742
|
-
if (orgDefault) return orgDefault;
|
|
743
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
744
|
-
}
|
|
745
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
746
|
-
var init_agent_config = __esm({
|
|
747
|
-
"src/lib/agent-config.ts"() {
|
|
748
|
-
"use strict";
|
|
749
|
-
init_config();
|
|
750
|
-
init_runtime_table();
|
|
751
|
-
init_secure_files();
|
|
752
|
-
AGENT_CONFIG_PATH = path6.join(EXE_AI_DIR, "agent-config.json");
|
|
753
|
-
DEFAULT_MODELS = {
|
|
754
|
-
claude: "claude-opus-4.6",
|
|
755
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
756
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
|
|
761
1013
|
// src/lib/intercom-queue.ts
|
|
762
1014
|
var intercom_queue_exports = {};
|
|
763
1015
|
__export(intercom_queue_exports, {
|
|
@@ -2617,6 +2869,13 @@ async function ensureSchema() {
|
|
|
2617
2869
|
} catch (e) {
|
|
2618
2870
|
logCatchDebug("migration", e);
|
|
2619
2871
|
}
|
|
2872
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2873
|
+
try {
|
|
2874
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2875
|
+
} catch (e) {
|
|
2876
|
+
logCatchDebug("migration", e);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2620
2879
|
try {
|
|
2621
2880
|
await client.execute({
|
|
2622
2881
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3833,6 +4092,22 @@ async function ensureSchema() {
|
|
|
3833
4092
|
} catch (e) {
|
|
3834
4093
|
logCatchDebug("migration", e);
|
|
3835
4094
|
}
|
|
4095
|
+
try {
|
|
4096
|
+
await client.execute({
|
|
4097
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
4098
|
+
args: []
|
|
4099
|
+
});
|
|
4100
|
+
} catch (e) {
|
|
4101
|
+
logCatchDebug("migration", e);
|
|
4102
|
+
}
|
|
4103
|
+
try {
|
|
4104
|
+
await client.execute({
|
|
4105
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
4106
|
+
args: []
|
|
4107
|
+
});
|
|
4108
|
+
} catch (e) {
|
|
4109
|
+
logCatchDebug("migration", e);
|
|
4110
|
+
}
|
|
3836
4111
|
}
|
|
3837
4112
|
async function disposeDatabase() {
|
|
3838
4113
|
if (_walCheckpointTimer) {
|
|
@@ -3884,6 +4159,23 @@ var init_database = __esm({
|
|
|
3884
4159
|
});
|
|
3885
4160
|
|
|
3886
4161
|
// src/lib/license.ts
|
|
4162
|
+
var license_exports = {};
|
|
4163
|
+
__export(license_exports, {
|
|
4164
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
4165
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
4166
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
4167
|
+
checkLicense: () => checkLicense,
|
|
4168
|
+
getCachedLicense: () => getCachedLicense,
|
|
4169
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
4170
|
+
loadDeviceId: () => loadDeviceId,
|
|
4171
|
+
loadLicense: () => loadLicense,
|
|
4172
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
4173
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
4174
|
+
saveLicense: () => saveLicense,
|
|
4175
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
4176
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
4177
|
+
validateLicense: () => validateLicense
|
|
4178
|
+
});
|
|
3887
4179
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "fs";
|
|
3888
4180
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3889
4181
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -3891,7 +4183,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
3891
4183
|
import os8 from "os";
|
|
3892
4184
|
import path11 from "path";
|
|
3893
4185
|
import { jwtVerify, importSPKI } from "jose";
|
|
3894
|
-
|
|
4186
|
+
async function fetchRetry(url, init) {
|
|
4187
|
+
try {
|
|
4188
|
+
return await fetch(url, init);
|
|
4189
|
+
} catch {
|
|
4190
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
4191
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
function loadDeviceId() {
|
|
4195
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
4196
|
+
try {
|
|
4197
|
+
if (existsSync11(deviceJsonPath)) {
|
|
4198
|
+
const data = JSON.parse(readFileSync10(deviceJsonPath, "utf8"));
|
|
4199
|
+
if (data.deviceId) return data.deviceId;
|
|
4200
|
+
}
|
|
4201
|
+
} catch {
|
|
4202
|
+
}
|
|
4203
|
+
try {
|
|
4204
|
+
if (existsSync11(DEVICE_ID_PATH)) {
|
|
4205
|
+
const id2 = readFileSync10(DEVICE_ID_PATH, "utf8").trim();
|
|
4206
|
+
if (id2) return id2;
|
|
4207
|
+
}
|
|
4208
|
+
} catch {
|
|
4209
|
+
}
|
|
4210
|
+
const id = randomUUID2();
|
|
4211
|
+
mkdirSync7(EXE_AI_DIR, { recursive: true });
|
|
4212
|
+
writeFileSync8(DEVICE_ID_PATH, id, "utf8");
|
|
4213
|
+
return id;
|
|
4214
|
+
}
|
|
4215
|
+
function loadLicense() {
|
|
4216
|
+
try {
|
|
4217
|
+
if (!existsSync11(LICENSE_PATH)) return null;
|
|
4218
|
+
return readFileSync10(LICENSE_PATH, "utf8").trim();
|
|
4219
|
+
} catch {
|
|
4220
|
+
return null;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
function saveLicense(apiKey) {
|
|
4224
|
+
mkdirSync7(EXE_AI_DIR, { recursive: true });
|
|
4225
|
+
writeFileSync8(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
4226
|
+
}
|
|
4227
|
+
async function verifyLicenseJwt(token) {
|
|
4228
|
+
try {
|
|
4229
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4230
|
+
const { payload } = await jwtVerify(token, key, {
|
|
4231
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
4232
|
+
});
|
|
4233
|
+
const plan = payload.plan ?? "free";
|
|
4234
|
+
const email = payload.sub ?? "";
|
|
4235
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4236
|
+
return {
|
|
4237
|
+
valid: true,
|
|
4238
|
+
plan,
|
|
4239
|
+
email,
|
|
4240
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4241
|
+
deviceLimit: limits.devices,
|
|
4242
|
+
employeeLimit: limits.employees,
|
|
4243
|
+
memoryLimit: limits.memories
|
|
4244
|
+
};
|
|
4245
|
+
} catch {
|
|
4246
|
+
return null;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
async function getCachedLicense() {
|
|
4250
|
+
try {
|
|
4251
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
4252
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH, "utf8"));
|
|
4253
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
4254
|
+
return await verifyLicenseJwt(raw.token);
|
|
4255
|
+
} catch {
|
|
4256
|
+
return null;
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
function readCachedLicenseToken() {
|
|
4260
|
+
try {
|
|
4261
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
4262
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH, "utf8"));
|
|
4263
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
4264
|
+
} catch {
|
|
4265
|
+
return null;
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
function getRawCachedPlan() {
|
|
4269
|
+
try {
|
|
4270
|
+
const token = readCachedLicenseToken();
|
|
4271
|
+
if (!token) return null;
|
|
4272
|
+
const parts = token.split(".");
|
|
4273
|
+
if (parts.length !== 3) return null;
|
|
4274
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
4275
|
+
const plan = payload.plan ?? "free";
|
|
4276
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4277
|
+
process.stderr.write(
|
|
4278
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
4279
|
+
`
|
|
4280
|
+
);
|
|
4281
|
+
return {
|
|
4282
|
+
valid: true,
|
|
4283
|
+
plan,
|
|
4284
|
+
email: payload.sub ?? "",
|
|
4285
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4286
|
+
deviceLimit: limits.devices,
|
|
4287
|
+
employeeLimit: limits.employees,
|
|
4288
|
+
memoryLimit: limits.memories
|
|
4289
|
+
};
|
|
4290
|
+
} catch {
|
|
4291
|
+
return null;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
function cacheResponse(token) {
|
|
4295
|
+
try {
|
|
4296
|
+
writeFileSync8(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
4297
|
+
} catch {
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
function loadPrismaForLicense() {
|
|
4301
|
+
if (_prismaFailed) return null;
|
|
4302
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
4303
|
+
if (!dbUrl) {
|
|
4304
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
4305
|
+
if (!existsSync11(path11.join(exeDbRoot, "package.json"))) {
|
|
4306
|
+
_prismaFailed = true;
|
|
4307
|
+
return null;
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
if (!_prismaPromise) {
|
|
4311
|
+
_prismaPromise = (async () => {
|
|
4312
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4313
|
+
if (explicitPath) {
|
|
4314
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
4315
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
4316
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4317
|
+
return new Ctor2();
|
|
4318
|
+
}
|
|
4319
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
4320
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
4321
|
+
const entry = req.resolve("@prisma/client");
|
|
4322
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
4323
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4324
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
4325
|
+
return new Ctor();
|
|
4326
|
+
})().catch((err) => {
|
|
4327
|
+
_prismaFailed = true;
|
|
4328
|
+
_prismaPromise = null;
|
|
4329
|
+
throw err;
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4332
|
+
return _prismaPromise;
|
|
4333
|
+
}
|
|
4334
|
+
async function validateViaPostgres(apiKey) {
|
|
4335
|
+
const loader = loadPrismaForLicense();
|
|
4336
|
+
if (!loader) return null;
|
|
4337
|
+
try {
|
|
4338
|
+
const prisma = await loader;
|
|
4339
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
4340
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
4341
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
4342
|
+
apiKey
|
|
4343
|
+
);
|
|
4344
|
+
if (!rows || rows.length === 0) return null;
|
|
4345
|
+
const row = rows[0];
|
|
4346
|
+
if (row.status !== "active") return null;
|
|
4347
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
4348
|
+
const plan = row.plan;
|
|
4349
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4350
|
+
return {
|
|
4351
|
+
valid: true,
|
|
4352
|
+
plan,
|
|
4353
|
+
email: row.email,
|
|
4354
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
4355
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
4356
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
4357
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4358
|
+
};
|
|
4359
|
+
} catch {
|
|
4360
|
+
return null;
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
4364
|
+
try {
|
|
4365
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4366
|
+
method: "POST",
|
|
4367
|
+
headers: { "Content-Type": "application/json" },
|
|
4368
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4369
|
+
signal: AbortSignal.timeout(1e4)
|
|
4370
|
+
});
|
|
4371
|
+
if (!res.ok) return null;
|
|
4372
|
+
const data = await res.json();
|
|
4373
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
4374
|
+
if (!data.valid) return null;
|
|
4375
|
+
if (data.token) {
|
|
4376
|
+
cacheResponse(data.token);
|
|
4377
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4378
|
+
if (verified) return verified;
|
|
4379
|
+
}
|
|
4380
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4381
|
+
return {
|
|
4382
|
+
valid: data.valid,
|
|
4383
|
+
plan: data.plan,
|
|
4384
|
+
email: data.email,
|
|
4385
|
+
expiresAt: data.expiresAt,
|
|
4386
|
+
deviceLimit: limits.devices,
|
|
4387
|
+
employeeLimit: limits.employees,
|
|
4388
|
+
memoryLimit: limits.memories
|
|
4389
|
+
};
|
|
4390
|
+
} catch {
|
|
4391
|
+
return null;
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4395
|
+
const did = deviceId ?? loadDeviceId();
|
|
4396
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
4397
|
+
if (pgResult) {
|
|
4398
|
+
try {
|
|
4399
|
+
writeFileSync8(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
4400
|
+
} catch {
|
|
4401
|
+
}
|
|
4402
|
+
return pgResult;
|
|
4403
|
+
}
|
|
4404
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
4405
|
+
if (cfResult) return cfResult;
|
|
4406
|
+
const cached = await getCachedLicense();
|
|
4407
|
+
if (cached) return cached;
|
|
4408
|
+
try {
|
|
4409
|
+
if (existsSync11(CACHE_PATH)) {
|
|
4410
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH, "utf8"));
|
|
4411
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
4412
|
+
return raw.pgLicense;
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
} catch {
|
|
4416
|
+
}
|
|
4417
|
+
const rawFallback = getRawCachedPlan();
|
|
4418
|
+
if (rawFallback) return rawFallback;
|
|
4419
|
+
return { ...FREE_LICENSE, valid: false };
|
|
4420
|
+
}
|
|
4421
|
+
function getCacheAgeMs() {
|
|
4422
|
+
try {
|
|
4423
|
+
const { statSync: statSync6 } = __require("fs");
|
|
4424
|
+
const s = statSync6(CACHE_PATH);
|
|
4425
|
+
return Date.now() - s.mtimeMs;
|
|
4426
|
+
} catch {
|
|
4427
|
+
return Infinity;
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
async function checkLicense() {
|
|
4431
|
+
let key = loadLicense();
|
|
4432
|
+
if (!key) {
|
|
4433
|
+
try {
|
|
4434
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
4435
|
+
if (existsSync11(configPath)) {
|
|
4436
|
+
const raw = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
4437
|
+
const cloud = raw.cloud;
|
|
4438
|
+
if (cloud?.apiKey) {
|
|
4439
|
+
key = cloud.apiKey;
|
|
4440
|
+
saveLicense(key);
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
} catch {
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
if (!key) return FREE_LICENSE;
|
|
4447
|
+
const cached = await getCachedLicense();
|
|
4448
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
4449
|
+
const deviceId = loadDeviceId();
|
|
4450
|
+
return validateLicense(key, deviceId);
|
|
4451
|
+
}
|
|
4452
|
+
function isFeatureAllowed(license, feature) {
|
|
4453
|
+
switch (feature) {
|
|
4454
|
+
case "cloud_sync":
|
|
4455
|
+
case "external_agents":
|
|
4456
|
+
case "wiki":
|
|
4457
|
+
return license.plan !== "free";
|
|
4458
|
+
case "unlimited_employees":
|
|
4459
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
function mirrorLicenseKey(apiKey) {
|
|
4463
|
+
const trimmed = apiKey.trim();
|
|
4464
|
+
if (!trimmed) return;
|
|
4465
|
+
saveLicense(trimmed);
|
|
4466
|
+
}
|
|
4467
|
+
async function assertVpsLicense(opts) {
|
|
4468
|
+
const env = opts?.env ?? process.env;
|
|
4469
|
+
const inProduction = env.NODE_ENV === "production";
|
|
4470
|
+
if (!opts?.force && !inProduction) {
|
|
4471
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
4472
|
+
}
|
|
4473
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
4474
|
+
if (envKey) {
|
|
4475
|
+
saveLicense(envKey);
|
|
4476
|
+
}
|
|
4477
|
+
const apiKey = envKey ?? loadLicense();
|
|
4478
|
+
if (!apiKey) {
|
|
4479
|
+
throw new Error(
|
|
4480
|
+
"License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
|
|
4481
|
+
);
|
|
4482
|
+
}
|
|
4483
|
+
const deviceId = loadDeviceId();
|
|
4484
|
+
let backendResponse = null;
|
|
4485
|
+
let explicitRejection = false;
|
|
4486
|
+
let transientFailure = false;
|
|
4487
|
+
try {
|
|
4488
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4489
|
+
method: "POST",
|
|
4490
|
+
headers: { "Content-Type": "application/json" },
|
|
4491
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4492
|
+
signal: AbortSignal.timeout(1e4)
|
|
4493
|
+
});
|
|
4494
|
+
if (res.ok) {
|
|
4495
|
+
backendResponse = await res.json();
|
|
4496
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
4497
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4498
|
+
explicitRejection = true;
|
|
4499
|
+
} else {
|
|
4500
|
+
transientFailure = true;
|
|
4501
|
+
}
|
|
4502
|
+
} catch {
|
|
4503
|
+
transientFailure = true;
|
|
4504
|
+
}
|
|
4505
|
+
if (backendResponse?.valid) {
|
|
4506
|
+
if (backendResponse.token) {
|
|
4507
|
+
cacheResponse(backendResponse.token);
|
|
4508
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
4509
|
+
if (verified) return verified;
|
|
4510
|
+
}
|
|
4511
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
4512
|
+
return {
|
|
4513
|
+
valid: true,
|
|
4514
|
+
plan: backendResponse.plan,
|
|
4515
|
+
email: backendResponse.email,
|
|
4516
|
+
expiresAt: backendResponse.expiresAt,
|
|
4517
|
+
deviceLimit: limits.devices,
|
|
4518
|
+
employeeLimit: limits.employees,
|
|
4519
|
+
memoryLimit: limits.memories
|
|
4520
|
+
};
|
|
4521
|
+
}
|
|
4522
|
+
if (explicitRejection) {
|
|
4523
|
+
throw new Error(
|
|
4524
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
4525
|
+
);
|
|
4526
|
+
}
|
|
4527
|
+
if (!transientFailure) {
|
|
4528
|
+
throw new Error(
|
|
4529
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
4530
|
+
);
|
|
4531
|
+
}
|
|
4532
|
+
const fresh = await getCachedLicense();
|
|
4533
|
+
if (fresh && fresh.valid) return fresh;
|
|
4534
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
4535
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
4536
|
+
try {
|
|
4537
|
+
const token = readCachedLicenseToken();
|
|
4538
|
+
if (token) {
|
|
4539
|
+
const payloadB64 = token.split(".")[1];
|
|
4540
|
+
if (payloadB64) {
|
|
4541
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
4542
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
4543
|
+
if (Date.now() < expMs + graceMs) {
|
|
4544
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4545
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
4546
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
4547
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
4548
|
+
});
|
|
4549
|
+
const plan = verified.plan ?? "free";
|
|
4550
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4551
|
+
return {
|
|
4552
|
+
valid: true,
|
|
4553
|
+
plan,
|
|
4554
|
+
email: verified.sub ?? "",
|
|
4555
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
4556
|
+
deviceLimit: limits.devices,
|
|
4557
|
+
employeeLimit: limits.employees,
|
|
4558
|
+
memoryLimit: limits.memories
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
} catch {
|
|
4564
|
+
}
|
|
4565
|
+
throw new Error(
|
|
4566
|
+
`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.`
|
|
4567
|
+
);
|
|
4568
|
+
}
|
|
4569
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
4570
|
+
if (_revalTimer) return;
|
|
4571
|
+
_revalTimer = setInterval(async () => {
|
|
4572
|
+
try {
|
|
4573
|
+
const license = await checkLicense();
|
|
4574
|
+
if (!license.valid) {
|
|
4575
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
4576
|
+
}
|
|
4577
|
+
} catch {
|
|
4578
|
+
}
|
|
4579
|
+
}, intervalMs);
|
|
4580
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
4581
|
+
_revalTimer.unref();
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
function stopLicenseRevalidation() {
|
|
4585
|
+
if (_revalTimer) {
|
|
4586
|
+
clearInterval(_revalTimer);
|
|
4587
|
+
_revalTimer = null;
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
3895
4591
|
var init_license = __esm({
|
|
3896
4592
|
"src/lib/license.ts"() {
|
|
3897
4593
|
"use strict";
|
|
@@ -3899,7 +4595,13 @@ var init_license = __esm({
|
|
|
3899
4595
|
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
3900
4596
|
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
3901
4597
|
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
3902
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
4598
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
4599
|
+
RETRY_DELAY_MS = 500;
|
|
4600
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4601
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
4602
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
4603
|
+
-----END PUBLIC KEY-----`;
|
|
4604
|
+
LICENSE_JWT_ALG = "ES256";
|
|
3903
4605
|
PLAN_LIMITS = {
|
|
3904
4606
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3905
4607
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3907,6 +4609,19 @@ var init_license = __esm({
|
|
|
3907
4609
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3908
4610
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3909
4611
|
};
|
|
4612
|
+
FREE_LICENSE = {
|
|
4613
|
+
valid: true,
|
|
4614
|
+
plan: "free",
|
|
4615
|
+
email: "",
|
|
4616
|
+
expiresAt: null,
|
|
4617
|
+
deviceLimit: 1,
|
|
4618
|
+
employeeLimit: 1,
|
|
4619
|
+
memoryLimit: 5e3
|
|
4620
|
+
};
|
|
4621
|
+
_prismaPromise = null;
|
|
4622
|
+
_prismaFailed = false;
|
|
4623
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
4624
|
+
_revalTimer = null;
|
|
3910
4625
|
}
|
|
3911
4626
|
});
|
|
3912
4627
|
|
|
@@ -4591,6 +5306,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4591
5306
|
args: [identifier, ...scope.args]
|
|
4592
5307
|
});
|
|
4593
5308
|
if (result.rows.length === 1) return result.rows[0];
|
|
5309
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
5310
|
+
result = await client.execute({
|
|
5311
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
5312
|
+
args: [`${identifier}%`]
|
|
5313
|
+
});
|
|
5314
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5315
|
+
if (result.rows.length > 1) {
|
|
5316
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
5317
|
+
throw new Error(
|
|
5318
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
5319
|
+
);
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
4594
5322
|
result = await client.execute({
|
|
4595
5323
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
4596
5324
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -5445,12 +6173,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5445
6173
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
5446
6174
|
args: [now, taskId]
|
|
5447
6175
|
});
|
|
5448
|
-
if (
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
6176
|
+
if (unblocked.rowsAffected === 0) return;
|
|
6177
|
+
const ubScope = sessionScopeFilter();
|
|
6178
|
+
const unblockedRows = await client.execute({
|
|
6179
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
6180
|
+
args: [now, ...ubScope.args]
|
|
6181
|
+
});
|
|
6182
|
+
if (baseDir) {
|
|
5454
6183
|
for (const ur of unblockedRows.rows) {
|
|
5455
6184
|
try {
|
|
5456
6185
|
const ubFile = path18.join(baseDir, String(ur.task_file));
|
|
@@ -5462,6 +6191,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5462
6191
|
}
|
|
5463
6192
|
}
|
|
5464
6193
|
}
|
|
6194
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
6195
|
+
try {
|
|
6196
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
6197
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
6198
|
+
for (const ur of unblockedRows.rows) {
|
|
6199
|
+
const assignee = String(ur.assigned_to);
|
|
6200
|
+
if (dispatched.has(assignee)) continue;
|
|
6201
|
+
dispatched.add(assignee);
|
|
6202
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
6203
|
+
}
|
|
6204
|
+
} catch {
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
5465
6207
|
}
|
|
5466
6208
|
async function findNextTask(assignedTo) {
|
|
5467
6209
|
const client = getClient();
|
|
@@ -5671,6 +6413,15 @@ var init_embedder = __esm({
|
|
|
5671
6413
|
// src/lib/behaviors.ts
|
|
5672
6414
|
import crypto5 from "crypto";
|
|
5673
6415
|
async function storeBehavior(opts) {
|
|
6416
|
+
try {
|
|
6417
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
6418
|
+
const roster = loadEmployeesSync2();
|
|
6419
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
6420
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
6421
|
+
}
|
|
6422
|
+
} catch (e) {
|
|
6423
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
6424
|
+
}
|
|
5674
6425
|
const client = getClient();
|
|
5675
6426
|
const id = crypto5.randomUUID();
|
|
5676
6427
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5681,10 +6432,18 @@ async function storeBehavior(opts) {
|
|
|
5681
6432
|
vector = new Float32Array(vec);
|
|
5682
6433
|
} catch {
|
|
5683
6434
|
}
|
|
6435
|
+
let createdByDevice = null;
|
|
6436
|
+
try {
|
|
6437
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
6438
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
6439
|
+
} catch {
|
|
6440
|
+
}
|
|
6441
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
6442
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
5684
6443
|
await client.execute({
|
|
5685
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
5686
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
5687
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
6444
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id)
|
|
6445
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
6446
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
5688
6447
|
});
|
|
5689
6448
|
return id;
|
|
5690
6449
|
}
|
|
@@ -6116,6 +6875,12 @@ async function updateTask(input2) {
|
|
|
6116
6875
|
}
|
|
6117
6876
|
}
|
|
6118
6877
|
}
|
|
6878
|
+
if (input2.status === "cancelled") {
|
|
6879
|
+
try {
|
|
6880
|
+
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
6881
|
+
} catch {
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6119
6884
|
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6120
6885
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6121
6886
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -6647,11 +7412,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6647
7412
|
}
|
|
6648
7413
|
}
|
|
6649
7414
|
function resolveExeSession() {
|
|
7415
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7416
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7417
|
+
if (fromEnv) return fromEnv;
|
|
7418
|
+
}
|
|
6650
7419
|
const mySession = getMySession();
|
|
6651
7420
|
if (!mySession) {
|
|
6652
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6653
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6654
|
-
}
|
|
6655
7421
|
return null;
|
|
6656
7422
|
}
|
|
6657
7423
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6666,6 +7432,10 @@ function resolveExeSession() {
|
|
|
6666
7432
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6667
7433
|
`
|
|
6668
7434
|
);
|
|
7435
|
+
try {
|
|
7436
|
+
registerParentExe(key, fromSessionName);
|
|
7437
|
+
} catch {
|
|
7438
|
+
}
|
|
6669
7439
|
candidate = fromSessionName;
|
|
6670
7440
|
} else {
|
|
6671
7441
|
candidate = fromCache;
|
|
@@ -8393,11 +9163,17 @@ var init_platform_procedures = __esm({
|
|
|
8393
9163
|
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."
|
|
8394
9164
|
},
|
|
8395
9165
|
{
|
|
8396
|
-
title: "
|
|
9166
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
8397
9167
|
domain: "workflow",
|
|
8398
9168
|
priority: "p1",
|
|
8399
9169
|
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."
|
|
8400
9170
|
},
|
|
9171
|
+
{
|
|
9172
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
9173
|
+
domain: "identity",
|
|
9174
|
+
priority: "p0",
|
|
9175
|
+
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."
|
|
9176
|
+
},
|
|
8401
9177
|
{
|
|
8402
9178
|
title: "Single dispatch path \u2014 create_task only",
|
|
8403
9179
|
domain: "workflow",
|
|
@@ -8431,6 +9207,12 @@ var init_platform_procedures = __esm({
|
|
|
8431
9207
|
priority: "p0",
|
|
8432
9208
|
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."
|
|
8433
9209
|
},
|
|
9210
|
+
{
|
|
9211
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
9212
|
+
domain: "security",
|
|
9213
|
+
priority: "p0",
|
|
9214
|
+
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."
|
|
9215
|
+
},
|
|
8434
9216
|
{
|
|
8435
9217
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
8436
9218
|
domain: "support",
|
|
@@ -8582,7 +9364,7 @@ var init_platform_procedures = __esm({
|
|
|
8582
9364
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
8583
9365
|
domain: "tool-use",
|
|
8584
9366
|
priority: "p0",
|
|
8585
|
-
content: 'exe-os MCP tools
|
|
9367
|
+
content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
|
|
8586
9368
|
},
|
|
8587
9369
|
{
|
|
8588
9370
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -8716,10 +9498,24 @@ function stableId(memoryId, type, content) {
|
|
|
8716
9498
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
8717
9499
|
}
|
|
8718
9500
|
function cleanText(text) {
|
|
8719
|
-
|
|
9501
|
+
let cleaned = text.replace(
|
|
9502
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
9503
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
9504
|
+
);
|
|
9505
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9506
|
+
return cleaned;
|
|
8720
9507
|
}
|
|
8721
|
-
function
|
|
8722
|
-
|
|
9508
|
+
function splitSegments(text) {
|
|
9509
|
+
const cleaned = cleanText(text);
|
|
9510
|
+
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);
|
|
9511
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9512
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
9513
|
+
if (lines.length > 0) return lines;
|
|
9514
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9515
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9518
|
+
return segments;
|
|
8723
9519
|
}
|
|
8724
9520
|
function inferCardType(sentence, toolName) {
|
|
8725
9521
|
const lower = sentence.toLowerCase();
|
|
@@ -8751,12 +9547,12 @@ function predicateFor(type) {
|
|
|
8751
9547
|
}
|
|
8752
9548
|
}
|
|
8753
9549
|
function extractMemoryCards(row) {
|
|
8754
|
-
const
|
|
9550
|
+
const segments = splitSegments(row.raw_text);
|
|
8755
9551
|
const cards = [];
|
|
8756
|
-
for (const sentence of
|
|
9552
|
+
for (const sentence of segments) {
|
|
8757
9553
|
const type = inferCardType(sentence, row.tool_name);
|
|
8758
9554
|
const subject = extractSubject(sentence, row.agent_id);
|
|
8759
|
-
const content = sentence.length >
|
|
9555
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
8760
9556
|
cards.push({
|
|
8761
9557
|
id: stableId(row.id, type, content),
|
|
8762
9558
|
memory_id: row.id,
|
|
@@ -8852,13 +9648,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
8852
9648
|
last_accessed: String(row.timestamp)
|
|
8853
9649
|
}));
|
|
8854
9650
|
}
|
|
8855
|
-
var MAX_CARDS_PER_MEMORY,
|
|
9651
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
8856
9652
|
var init_memory_cards = __esm({
|
|
8857
9653
|
"src/lib/memory-cards.ts"() {
|
|
8858
9654
|
"use strict";
|
|
8859
9655
|
init_database();
|
|
8860
|
-
MAX_CARDS_PER_MEMORY =
|
|
8861
|
-
|
|
9656
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9657
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9658
|
+
MIN_SEGMENT_CHARS = 20;
|
|
8862
9659
|
}
|
|
8863
9660
|
});
|
|
8864
9661
|
|
|
@@ -10195,9 +10992,9 @@ var init_git_task_sweep = __esm({
|
|
|
10195
10992
|
// src/lib/active-agent.ts
|
|
10196
10993
|
init_config();
|
|
10197
10994
|
init_session_key();
|
|
10198
|
-
import { readFileSync as
|
|
10995
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
10199
10996
|
import { execSync as execSync3 } from "child_process";
|
|
10200
|
-
import
|
|
10997
|
+
import path4 from "path";
|
|
10201
10998
|
|
|
10202
10999
|
// src/mcp/agent-context.ts
|
|
10203
11000
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -10208,7 +11005,7 @@ function getAgentContext() {
|
|
|
10208
11005
|
|
|
10209
11006
|
// src/lib/active-agent.ts
|
|
10210
11007
|
init_employees();
|
|
10211
|
-
var CACHE_DIR =
|
|
11008
|
+
var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
|
|
10212
11009
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
10213
11010
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
10214
11011
|
if (candidate === baseName) return true;
|
|
@@ -10253,7 +11050,7 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
10253
11050
|
return null;
|
|
10254
11051
|
}
|
|
10255
11052
|
function getMarkerPath() {
|
|
10256
|
-
return
|
|
11053
|
+
return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
10257
11054
|
}
|
|
10258
11055
|
function clearActiveAgent() {
|
|
10259
11056
|
try {
|
|
@@ -10266,7 +11063,7 @@ function getActiveAgent() {
|
|
|
10266
11063
|
if (httpCtx) return httpCtx;
|
|
10267
11064
|
try {
|
|
10268
11065
|
const markerPath = getMarkerPath();
|
|
10269
|
-
const raw =
|
|
11066
|
+
const raw = readFileSync4(markerPath, "utf8");
|
|
10270
11067
|
const data = JSON.parse(raw);
|
|
10271
11068
|
if (data.agentId) {
|
|
10272
11069
|
if (data.startedAt) {
|
|
@@ -10311,15 +11108,15 @@ init_session_key();
|
|
|
10311
11108
|
|
|
10312
11109
|
// src/lib/cache-warmth.ts
|
|
10313
11110
|
import os3 from "os";
|
|
10314
|
-
import
|
|
10315
|
-
import { existsSync as
|
|
11111
|
+
import path5 from "path";
|
|
11112
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
10316
11113
|
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
10317
|
-
var CACHE_DIR2 =
|
|
10318
|
-
process.env.EXE_OS_DIR ??
|
|
11114
|
+
var CACHE_DIR2 = path5.join(
|
|
11115
|
+
process.env.EXE_OS_DIR ?? path5.join(os3.homedir(), ".exe-os"),
|
|
10319
11116
|
"session-cache"
|
|
10320
11117
|
);
|
|
10321
11118
|
function getStatePath(sessionKey) {
|
|
10322
|
-
return
|
|
11119
|
+
return path5.join(CACHE_DIR2, `cache-warmth-${sessionKey}.json`);
|
|
10323
11120
|
}
|
|
10324
11121
|
function clearCacheState(sessionKey) {
|
|
10325
11122
|
try {
|