@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();
|
|
@@ -420,8 +577,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
420
577
|
if (!agentName) return false;
|
|
421
578
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
422
579
|
}
|
|
580
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
581
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
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
|
+
}
|
|
423
598
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
424
|
-
if (!
|
|
599
|
+
if (!existsSync4(employeesPath)) {
|
|
425
600
|
return [];
|
|
426
601
|
}
|
|
427
602
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -431,10 +606,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
431
606
|
return [];
|
|
432
607
|
}
|
|
433
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
|
+
}
|
|
434
613
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
435
|
-
if (!
|
|
614
|
+
if (!existsSync4(employeesPath)) return [];
|
|
436
615
|
try {
|
|
437
|
-
return JSON.parse(
|
|
616
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
438
617
|
} catch {
|
|
439
618
|
return [];
|
|
440
619
|
}
|
|
@@ -442,6 +621,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
442
621
|
function getEmployee(employees, name) {
|
|
443
622
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
444
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
|
+
}
|
|
445
637
|
function baseAgentName(name, employees) {
|
|
446
638
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
447
639
|
if (!match) return name;
|
|
@@ -456,26 +648,151 @@ function isMultiInstance(agentName, employees) {
|
|
|
456
648
|
if (!emp) return false;
|
|
457
649
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
458
650
|
}
|
|
459
|
-
|
|
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;
|
|
460
776
|
var init_employees = __esm({
|
|
461
777
|
"src/lib/employees.ts"() {
|
|
462
778
|
"use strict";
|
|
463
779
|
init_config();
|
|
464
|
-
EMPLOYEES_PATH =
|
|
780
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
465
781
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
466
782
|
COORDINATOR_ROLE = "COO";
|
|
467
783
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
468
|
-
IDENTITY_DIR =
|
|
784
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
785
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
469
786
|
}
|
|
470
787
|
});
|
|
471
788
|
|
|
472
789
|
// src/lib/session-registry.ts
|
|
473
|
-
import { readFileSync as
|
|
474
|
-
import
|
|
790
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
|
|
791
|
+
import path5 from "path";
|
|
475
792
|
import os3 from "os";
|
|
476
793
|
function registerSession(entry) {
|
|
477
|
-
const dir =
|
|
478
|
-
if (!
|
|
794
|
+
const dir = path5.dirname(REGISTRY_PATH);
|
|
795
|
+
if (!existsSync5(dir)) {
|
|
479
796
|
mkdirSync3(dir, { recursive: true });
|
|
480
797
|
}
|
|
481
798
|
const sessions = listSessions();
|
|
@@ -485,11 +802,11 @@ function registerSession(entry) {
|
|
|
485
802
|
} else {
|
|
486
803
|
sessions.push(entry);
|
|
487
804
|
}
|
|
488
|
-
|
|
805
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
489
806
|
}
|
|
490
807
|
function listSessions() {
|
|
491
808
|
try {
|
|
492
|
-
const raw =
|
|
809
|
+
const raw = readFileSync5(REGISTRY_PATH, "utf8");
|
|
493
810
|
return JSON.parse(raw);
|
|
494
811
|
} catch {
|
|
495
812
|
return [];
|
|
@@ -499,7 +816,7 @@ var REGISTRY_PATH;
|
|
|
499
816
|
var init_session_registry = __esm({
|
|
500
817
|
"src/lib/session-registry.ts"() {
|
|
501
818
|
"use strict";
|
|
502
|
-
REGISTRY_PATH =
|
|
819
|
+
REGISTRY_PATH = path5.join(os3.homedir(), ".exe-os", "session-registry.json");
|
|
503
820
|
}
|
|
504
821
|
});
|
|
505
822
|
|
|
@@ -693,68 +1010,6 @@ var init_provider_table = __esm({
|
|
|
693
1010
|
}
|
|
694
1011
|
});
|
|
695
1012
|
|
|
696
|
-
// src/lib/runtime-table.ts
|
|
697
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
698
|
-
var init_runtime_table = __esm({
|
|
699
|
-
"src/lib/runtime-table.ts"() {
|
|
700
|
-
"use strict";
|
|
701
|
-
RUNTIME_TABLE = {
|
|
702
|
-
codex: {
|
|
703
|
-
binary: "codex",
|
|
704
|
-
launchMode: "interactive",
|
|
705
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
706
|
-
inlineFlag: "--no-alt-screen",
|
|
707
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
708
|
-
defaultModel: "gpt-5.5"
|
|
709
|
-
},
|
|
710
|
-
opencode: {
|
|
711
|
-
binary: "opencode",
|
|
712
|
-
launchMode: "exec",
|
|
713
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
714
|
-
inlineFlag: "",
|
|
715
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
716
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
DEFAULT_RUNTIME = "claude";
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// src/lib/agent-config.ts
|
|
724
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
725
|
-
import path5 from "path";
|
|
726
|
-
function loadAgentConfig() {
|
|
727
|
-
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
728
|
-
try {
|
|
729
|
-
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
730
|
-
} catch {
|
|
731
|
-
return {};
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
function getAgentRuntime(agentId) {
|
|
735
|
-
const config = loadAgentConfig();
|
|
736
|
-
const entry = config[agentId];
|
|
737
|
-
if (entry) return entry;
|
|
738
|
-
const orgDefault = config["default"];
|
|
739
|
-
if (orgDefault) return orgDefault;
|
|
740
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
741
|
-
}
|
|
742
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
743
|
-
var init_agent_config = __esm({
|
|
744
|
-
"src/lib/agent-config.ts"() {
|
|
745
|
-
"use strict";
|
|
746
|
-
init_config();
|
|
747
|
-
init_runtime_table();
|
|
748
|
-
init_secure_files();
|
|
749
|
-
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
750
|
-
DEFAULT_MODELS = {
|
|
751
|
-
claude: "claude-opus-4.6",
|
|
752
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
753
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
|
|
758
1013
|
// src/lib/intercom-queue.ts
|
|
759
1014
|
var intercom_queue_exports = {};
|
|
760
1015
|
__export(intercom_queue_exports, {
|
|
@@ -2614,6 +2869,13 @@ async function ensureSchema() {
|
|
|
2614
2869
|
} catch (e) {
|
|
2615
2870
|
logCatchDebug("migration", e);
|
|
2616
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
|
+
}
|
|
2617
2879
|
try {
|
|
2618
2880
|
await client.execute({
|
|
2619
2881
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3830,6 +4092,22 @@ async function ensureSchema() {
|
|
|
3830
4092
|
} catch (e) {
|
|
3831
4093
|
logCatchDebug("migration", e);
|
|
3832
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
|
+
}
|
|
3833
4111
|
}
|
|
3834
4112
|
async function disposeDatabase() {
|
|
3835
4113
|
if (_walCheckpointTimer) {
|
|
@@ -3881,6 +4159,23 @@ var init_database = __esm({
|
|
|
3881
4159
|
});
|
|
3882
4160
|
|
|
3883
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
|
+
});
|
|
3884
4179
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
|
|
3885
4180
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3886
4181
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -3888,7 +4183,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
3888
4183
|
import os7 from "os";
|
|
3889
4184
|
import path10 from "path";
|
|
3890
4185
|
import { jwtVerify, importSPKI } from "jose";
|
|
3891
|
-
|
|
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 = path10.join(EXE_AI_DIR, "device.json");
|
|
4196
|
+
try {
|
|
4197
|
+
if (existsSync10(deviceJsonPath)) {
|
|
4198
|
+
const data = JSON.parse(readFileSync9(deviceJsonPath, "utf8"));
|
|
4199
|
+
if (data.deviceId) return data.deviceId;
|
|
4200
|
+
}
|
|
4201
|
+
} catch {
|
|
4202
|
+
}
|
|
4203
|
+
try {
|
|
4204
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
4205
|
+
const id2 = readFileSync9(DEVICE_ID_PATH, "utf8").trim();
|
|
4206
|
+
if (id2) return id2;
|
|
4207
|
+
}
|
|
4208
|
+
} catch {
|
|
4209
|
+
}
|
|
4210
|
+
const id = randomUUID2();
|
|
4211
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
4212
|
+
writeFileSync7(DEVICE_ID_PATH, id, "utf8");
|
|
4213
|
+
return id;
|
|
4214
|
+
}
|
|
4215
|
+
function loadLicense() {
|
|
4216
|
+
try {
|
|
4217
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
4218
|
+
return readFileSync9(LICENSE_PATH, "utf8").trim();
|
|
4219
|
+
} catch {
|
|
4220
|
+
return null;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
function saveLicense(apiKey) {
|
|
4224
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
4225
|
+
writeFileSync7(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 (!existsSync10(CACHE_PATH)) return null;
|
|
4252
|
+
const raw = JSON.parse(readFileSync9(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 (!existsSync10(CACHE_PATH)) return null;
|
|
4262
|
+
const raw = JSON.parse(readFileSync9(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
|
+
writeFileSync7(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 ?? path10.join(os7.homedir(), "exe-db");
|
|
4305
|
+
if (!existsSync10(path10.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 ?? path10.join(os7.homedir(), "exe-db");
|
|
4320
|
+
const req = createRequire2(path10.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
|
+
writeFileSync7(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 (existsSync10(CACHE_PATH)) {
|
|
4410
|
+
const raw = JSON.parse(readFileSync9(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 = path10.join(EXE_AI_DIR, "config.json");
|
|
4435
|
+
if (existsSync10(configPath)) {
|
|
4436
|
+
const raw = JSON.parse(readFileSync9(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;
|
|
3892
4591
|
var init_license = __esm({
|
|
3893
4592
|
"src/lib/license.ts"() {
|
|
3894
4593
|
"use strict";
|
|
@@ -3896,7 +4595,13 @@ var init_license = __esm({
|
|
|
3896
4595
|
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3897
4596
|
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3898
4597
|
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
3899
|
-
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";
|
|
3900
4605
|
PLAN_LIMITS = {
|
|
3901
4606
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3902
4607
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3904,6 +4609,19 @@ var init_license = __esm({
|
|
|
3904
4609
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3905
4610
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3906
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;
|
|
3907
4625
|
}
|
|
3908
4626
|
});
|
|
3909
4627
|
|
|
@@ -4382,6 +5100,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4382
5100
|
args: [identifier, ...scope.args]
|
|
4383
5101
|
});
|
|
4384
5102
|
if (result.rows.length === 1) return result.rows[0];
|
|
5103
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
5104
|
+
result = await client.execute({
|
|
5105
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
5106
|
+
args: [`${identifier}%`]
|
|
5107
|
+
});
|
|
5108
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5109
|
+
if (result.rows.length > 1) {
|
|
5110
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
5111
|
+
throw new Error(
|
|
5112
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
5113
|
+
);
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
4385
5116
|
result = await client.execute({
|
|
4386
5117
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
4387
5118
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -5236,12 +5967,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5236
5967
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
5237
5968
|
args: [now, taskId]
|
|
5238
5969
|
});
|
|
5239
|
-
if (
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5970
|
+
if (unblocked.rowsAffected === 0) return;
|
|
5971
|
+
const ubScope = sessionScopeFilter();
|
|
5972
|
+
const unblockedRows = await client.execute({
|
|
5973
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
5974
|
+
args: [now, ...ubScope.args]
|
|
5975
|
+
});
|
|
5976
|
+
if (baseDir) {
|
|
5245
5977
|
for (const ur of unblockedRows.rows) {
|
|
5246
5978
|
try {
|
|
5247
5979
|
const ubFile = path17.join(baseDir, String(ur.task_file));
|
|
@@ -5253,6 +5985,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5253
5985
|
}
|
|
5254
5986
|
}
|
|
5255
5987
|
}
|
|
5988
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
5989
|
+
try {
|
|
5990
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
5991
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
5992
|
+
for (const ur of unblockedRows.rows) {
|
|
5993
|
+
const assignee = String(ur.assigned_to);
|
|
5994
|
+
if (dispatched.has(assignee)) continue;
|
|
5995
|
+
dispatched.add(assignee);
|
|
5996
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
5997
|
+
}
|
|
5998
|
+
} catch {
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
5256
6001
|
}
|
|
5257
6002
|
async function findNextTask(assignedTo) {
|
|
5258
6003
|
const client = getClient();
|
|
@@ -5462,6 +6207,15 @@ var init_embedder = __esm({
|
|
|
5462
6207
|
// src/lib/behaviors.ts
|
|
5463
6208
|
import crypto5 from "crypto";
|
|
5464
6209
|
async function storeBehavior(opts) {
|
|
6210
|
+
try {
|
|
6211
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
6212
|
+
const roster = loadEmployeesSync2();
|
|
6213
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
6214
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
6215
|
+
}
|
|
6216
|
+
} catch (e) {
|
|
6217
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
6218
|
+
}
|
|
5465
6219
|
const client = getClient();
|
|
5466
6220
|
const id = crypto5.randomUUID();
|
|
5467
6221
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5472,10 +6226,18 @@ async function storeBehavior(opts) {
|
|
|
5472
6226
|
vector = new Float32Array(vec);
|
|
5473
6227
|
} catch {
|
|
5474
6228
|
}
|
|
6229
|
+
let createdByDevice = null;
|
|
6230
|
+
try {
|
|
6231
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
6232
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
6233
|
+
} catch {
|
|
6234
|
+
}
|
|
6235
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
6236
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
5475
6237
|
await client.execute({
|
|
5476
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
5477
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
5478
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
6238
|
+
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)
|
|
6239
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
6240
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
5479
6241
|
});
|
|
5480
6242
|
return id;
|
|
5481
6243
|
}
|
|
@@ -5907,6 +6669,12 @@ async function updateTask(input2) {
|
|
|
5907
6669
|
}
|
|
5908
6670
|
}
|
|
5909
6671
|
}
|
|
6672
|
+
if (input2.status === "cancelled") {
|
|
6673
|
+
try {
|
|
6674
|
+
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
6675
|
+
} catch {
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
5910
6678
|
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5911
6679
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5912
6680
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -6438,11 +7206,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6438
7206
|
}
|
|
6439
7207
|
}
|
|
6440
7208
|
function resolveExeSession() {
|
|
7209
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7210
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7211
|
+
if (fromEnv) return fromEnv;
|
|
7212
|
+
}
|
|
6441
7213
|
const mySession = getMySession();
|
|
6442
7214
|
if (!mySession) {
|
|
6443
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6444
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6445
|
-
}
|
|
6446
7215
|
return null;
|
|
6447
7216
|
}
|
|
6448
7217
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6457,6 +7226,10 @@ function resolveExeSession() {
|
|
|
6457
7226
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6458
7227
|
`
|
|
6459
7228
|
);
|
|
7229
|
+
try {
|
|
7230
|
+
registerParentExe(key, fromSessionName);
|
|
7231
|
+
} catch {
|
|
7232
|
+
}
|
|
6460
7233
|
candidate = fromSessionName;
|
|
6461
7234
|
} else {
|
|
6462
7235
|
candidate = fromCache;
|
|
@@ -8184,11 +8957,17 @@ var init_platform_procedures = __esm({
|
|
|
8184
8957
|
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."
|
|
8185
8958
|
},
|
|
8186
8959
|
{
|
|
8187
|
-
title: "
|
|
8960
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
8188
8961
|
domain: "workflow",
|
|
8189
8962
|
priority: "p1",
|
|
8190
8963
|
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."
|
|
8191
8964
|
},
|
|
8965
|
+
{
|
|
8966
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
8967
|
+
domain: "identity",
|
|
8968
|
+
priority: "p0",
|
|
8969
|
+
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."
|
|
8970
|
+
},
|
|
8192
8971
|
{
|
|
8193
8972
|
title: "Single dispatch path \u2014 create_task only",
|
|
8194
8973
|
domain: "workflow",
|
|
@@ -8222,6 +9001,12 @@ var init_platform_procedures = __esm({
|
|
|
8222
9001
|
priority: "p0",
|
|
8223
9002
|
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."
|
|
8224
9003
|
},
|
|
9004
|
+
{
|
|
9005
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
9006
|
+
domain: "security",
|
|
9007
|
+
priority: "p0",
|
|
9008
|
+
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."
|
|
9009
|
+
},
|
|
8225
9010
|
{
|
|
8226
9011
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
8227
9012
|
domain: "support",
|
|
@@ -8373,7 +9158,7 @@ var init_platform_procedures = __esm({
|
|
|
8373
9158
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
8374
9159
|
domain: "tool-use",
|
|
8375
9160
|
priority: "p0",
|
|
8376
|
-
content: 'exe-os MCP tools
|
|
9161
|
+
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.'
|
|
8377
9162
|
},
|
|
8378
9163
|
{
|
|
8379
9164
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -8507,10 +9292,24 @@ function stableId(memoryId, type, content) {
|
|
|
8507
9292
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
8508
9293
|
}
|
|
8509
9294
|
function cleanText(text) {
|
|
8510
|
-
|
|
9295
|
+
let cleaned = text.replace(
|
|
9296
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
9297
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
9298
|
+
);
|
|
9299
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9300
|
+
return cleaned;
|
|
8511
9301
|
}
|
|
8512
|
-
function
|
|
8513
|
-
|
|
9302
|
+
function splitSegments(text) {
|
|
9303
|
+
const cleaned = cleanText(text);
|
|
9304
|
+
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);
|
|
9305
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9306
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
9307
|
+
if (lines.length > 0) return lines;
|
|
9308
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9309
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
9310
|
+
}
|
|
9311
|
+
}
|
|
9312
|
+
return segments;
|
|
8514
9313
|
}
|
|
8515
9314
|
function inferCardType(sentence, toolName) {
|
|
8516
9315
|
const lower = sentence.toLowerCase();
|
|
@@ -8542,12 +9341,12 @@ function predicateFor(type) {
|
|
|
8542
9341
|
}
|
|
8543
9342
|
}
|
|
8544
9343
|
function extractMemoryCards(row) {
|
|
8545
|
-
const
|
|
9344
|
+
const segments = splitSegments(row.raw_text);
|
|
8546
9345
|
const cards = [];
|
|
8547
|
-
for (const sentence of
|
|
9346
|
+
for (const sentence of segments) {
|
|
8548
9347
|
const type = inferCardType(sentence, row.tool_name);
|
|
8549
9348
|
const subject = extractSubject(sentence, row.agent_id);
|
|
8550
|
-
const content = sentence.length >
|
|
9349
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
8551
9350
|
cards.push({
|
|
8552
9351
|
id: stableId(row.id, type, content),
|
|
8553
9352
|
memory_id: row.id,
|
|
@@ -8643,13 +9442,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
8643
9442
|
last_accessed: String(row.timestamp)
|
|
8644
9443
|
}));
|
|
8645
9444
|
}
|
|
8646
|
-
var MAX_CARDS_PER_MEMORY,
|
|
9445
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
8647
9446
|
var init_memory_cards = __esm({
|
|
8648
9447
|
"src/lib/memory-cards.ts"() {
|
|
8649
9448
|
"use strict";
|
|
8650
9449
|
init_database();
|
|
8651
|
-
MAX_CARDS_PER_MEMORY =
|
|
8652
|
-
|
|
9450
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9451
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9452
|
+
MIN_SEGMENT_CHARS = 20;
|
|
8653
9453
|
}
|
|
8654
9454
|
});
|
|
8655
9455
|
|
|
@@ -9659,9 +10459,9 @@ var init_fast_db_init = __esm({
|
|
|
9659
10459
|
// src/lib/active-agent.ts
|
|
9660
10460
|
init_config();
|
|
9661
10461
|
init_session_key();
|
|
9662
|
-
import { readFileSync as
|
|
10462
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
9663
10463
|
import { execSync as execSync3 } from "child_process";
|
|
9664
|
-
import
|
|
10464
|
+
import path4 from "path";
|
|
9665
10465
|
|
|
9666
10466
|
// src/mcp/agent-context.ts
|
|
9667
10467
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -9672,7 +10472,7 @@ function getAgentContext() {
|
|
|
9672
10472
|
|
|
9673
10473
|
// src/lib/active-agent.ts
|
|
9674
10474
|
init_employees();
|
|
9675
|
-
var CACHE_DIR =
|
|
10475
|
+
var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
|
|
9676
10476
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
9677
10477
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
9678
10478
|
if (candidate === baseName) return true;
|
|
@@ -9717,14 +10517,14 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
9717
10517
|
return null;
|
|
9718
10518
|
}
|
|
9719
10519
|
function getMarkerPath() {
|
|
9720
|
-
return
|
|
10520
|
+
return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
9721
10521
|
}
|
|
9722
10522
|
function getActiveAgent() {
|
|
9723
10523
|
const httpCtx = getAgentContext();
|
|
9724
10524
|
if (httpCtx) return httpCtx;
|
|
9725
10525
|
try {
|
|
9726
10526
|
const markerPath = getMarkerPath();
|
|
9727
|
-
const raw =
|
|
10527
|
+
const raw = readFileSync4(markerPath, "utf8");
|
|
9728
10528
|
const data = JSON.parse(raw);
|
|
9729
10529
|
if (data.agentId) {
|
|
9730
10530
|
if (data.startedAt) {
|