@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
|
@@ -386,11 +386,168 @@ var init_config = __esm({
|
|
|
386
386
|
}
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
+
// src/lib/runtime-table.ts
|
|
390
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
391
|
+
var init_runtime_table = __esm({
|
|
392
|
+
"src/lib/runtime-table.ts"() {
|
|
393
|
+
"use strict";
|
|
394
|
+
RUNTIME_TABLE = {
|
|
395
|
+
codex: {
|
|
396
|
+
binary: "codex",
|
|
397
|
+
launchMode: "interactive",
|
|
398
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
399
|
+
inlineFlag: "--no-alt-screen",
|
|
400
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
401
|
+
defaultModel: "gpt-5.5"
|
|
402
|
+
},
|
|
403
|
+
opencode: {
|
|
404
|
+
binary: "opencode",
|
|
405
|
+
launchMode: "exec",
|
|
406
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
407
|
+
inlineFlag: "",
|
|
408
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
409
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
DEFAULT_RUNTIME = "claude";
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// src/lib/agent-config.ts
|
|
417
|
+
var agent_config_exports = {};
|
|
418
|
+
__export(agent_config_exports, {
|
|
419
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
420
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
421
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
422
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
423
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
424
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
425
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
426
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
427
|
+
setAgentMcps: () => setAgentMcps,
|
|
428
|
+
setAgentRuntime: () => setAgentRuntime
|
|
429
|
+
});
|
|
430
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
431
|
+
import path2 from "path";
|
|
432
|
+
function loadAgentConfig() {
|
|
433
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
434
|
+
try {
|
|
435
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
436
|
+
} catch {
|
|
437
|
+
return {};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function saveAgentConfig(config) {
|
|
441
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
442
|
+
ensurePrivateDirSync(dir);
|
|
443
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
444
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
445
|
+
}
|
|
446
|
+
function getAgentRuntime(agentId) {
|
|
447
|
+
const config = loadAgentConfig();
|
|
448
|
+
const entry = config[agentId];
|
|
449
|
+
if (entry) return entry;
|
|
450
|
+
const orgDefault = config["default"];
|
|
451
|
+
if (orgDefault) return orgDefault;
|
|
452
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
453
|
+
}
|
|
454
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
455
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
456
|
+
if (!knownModels) {
|
|
457
|
+
return {
|
|
458
|
+
ok: false,
|
|
459
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
if (!knownModels.includes(model)) {
|
|
463
|
+
return {
|
|
464
|
+
ok: false,
|
|
465
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const config = loadAgentConfig();
|
|
469
|
+
const existing = config[agentId];
|
|
470
|
+
const entry = { runtime, model };
|
|
471
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
472
|
+
if (mcps !== void 0) {
|
|
473
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
474
|
+
} else if (existing?.mcps) {
|
|
475
|
+
entry.mcps = existing.mcps;
|
|
476
|
+
}
|
|
477
|
+
config[agentId] = entry;
|
|
478
|
+
saveAgentConfig(config);
|
|
479
|
+
return { ok: true };
|
|
480
|
+
}
|
|
481
|
+
function setAgentMcps(agentId, mcps) {
|
|
482
|
+
const config = loadAgentConfig();
|
|
483
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
484
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
485
|
+
config[agentId] = existing;
|
|
486
|
+
saveAgentConfig(config);
|
|
487
|
+
return { ok: true };
|
|
488
|
+
}
|
|
489
|
+
function clearAgentRuntime(agentId) {
|
|
490
|
+
const config = loadAgentConfig();
|
|
491
|
+
delete config[agentId];
|
|
492
|
+
saveAgentConfig(config);
|
|
493
|
+
}
|
|
494
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
495
|
+
var init_agent_config = __esm({
|
|
496
|
+
"src/lib/agent-config.ts"() {
|
|
497
|
+
"use strict";
|
|
498
|
+
init_config();
|
|
499
|
+
init_runtime_table();
|
|
500
|
+
init_secure_files();
|
|
501
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
502
|
+
KNOWN_RUNTIMES = {
|
|
503
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
504
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
505
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
506
|
+
};
|
|
507
|
+
RUNTIME_LABELS = {
|
|
508
|
+
claude: "Claude Code (Anthropic)",
|
|
509
|
+
codex: "Codex (OpenAI)",
|
|
510
|
+
opencode: "OpenCode (open source)"
|
|
511
|
+
};
|
|
512
|
+
DEFAULT_MODELS = {
|
|
513
|
+
claude: "claude-opus-4.6",
|
|
514
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
515
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
389
520
|
// src/lib/employees.ts
|
|
521
|
+
var employees_exports = {};
|
|
522
|
+
__export(employees_exports, {
|
|
523
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
524
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
525
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
526
|
+
addEmployee: () => addEmployee,
|
|
527
|
+
baseAgentName: () => baseAgentName,
|
|
528
|
+
canCoordinate: () => canCoordinate,
|
|
529
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
530
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
531
|
+
getEmployee: () => getEmployee,
|
|
532
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
533
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
534
|
+
hasRole: () => hasRole,
|
|
535
|
+
hireEmployee: () => hireEmployee,
|
|
536
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
537
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
538
|
+
isMultiInstance: () => isMultiInstance,
|
|
539
|
+
loadEmployees: () => loadEmployees,
|
|
540
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
541
|
+
normalizeRole: () => normalizeRole,
|
|
542
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
543
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
544
|
+
saveEmployees: () => saveEmployees,
|
|
545
|
+
validateEmployeeName: () => validateEmployeeName
|
|
546
|
+
});
|
|
390
547
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
391
|
-
import { existsSync as
|
|
548
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
392
549
|
import { execSync } from "child_process";
|
|
393
|
-
import
|
|
550
|
+
import path3 from "path";
|
|
394
551
|
import os2 from "os";
|
|
395
552
|
function normalizeRole(role) {
|
|
396
553
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -408,8 +565,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
408
565
|
if (!agentName) return false;
|
|
409
566
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
410
567
|
}
|
|
568
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
569
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
570
|
+
}
|
|
571
|
+
function validateEmployeeName(name) {
|
|
572
|
+
if (!name) {
|
|
573
|
+
return { valid: false, error: "Name is required" };
|
|
574
|
+
}
|
|
575
|
+
if (name.length > 32) {
|
|
576
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
577
|
+
}
|
|
578
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
579
|
+
return {
|
|
580
|
+
valid: false,
|
|
581
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return { valid: true };
|
|
585
|
+
}
|
|
411
586
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
412
|
-
if (!
|
|
587
|
+
if (!existsSync4(employeesPath)) {
|
|
413
588
|
return [];
|
|
414
589
|
}
|
|
415
590
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -419,10 +594,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
419
594
|
return [];
|
|
420
595
|
}
|
|
421
596
|
}
|
|
597
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
598
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
599
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
600
|
+
}
|
|
422
601
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
423
|
-
if (!
|
|
602
|
+
if (!existsSync4(employeesPath)) return [];
|
|
424
603
|
try {
|
|
425
|
-
return JSON.parse(
|
|
604
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
426
605
|
} catch {
|
|
427
606
|
return [];
|
|
428
607
|
}
|
|
@@ -430,6 +609,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
430
609
|
function getEmployee(employees, name) {
|
|
431
610
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
432
611
|
}
|
|
612
|
+
function getEmployeeByRole(employees, role) {
|
|
613
|
+
const lower = role.toLowerCase();
|
|
614
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
615
|
+
}
|
|
616
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
617
|
+
const lower = role.toLowerCase();
|
|
618
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
619
|
+
}
|
|
620
|
+
function hasRole(agentName, role) {
|
|
621
|
+
const employees = loadEmployeesSync();
|
|
622
|
+
const emp = getEmployee(employees, agentName);
|
|
623
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
624
|
+
}
|
|
433
625
|
function baseAgentName(name, employees) {
|
|
434
626
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
435
627
|
if (!match) return name;
|
|
@@ -444,22 +636,147 @@ function isMultiInstance(agentName, employees) {
|
|
|
444
636
|
if (!emp) return false;
|
|
445
637
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
446
638
|
}
|
|
447
|
-
|
|
639
|
+
function addEmployee(employees, employee) {
|
|
640
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
641
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
642
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
643
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
644
|
+
}
|
|
645
|
+
return [...employees, normalized];
|
|
646
|
+
}
|
|
647
|
+
function appendToCoordinatorTeam(employee) {
|
|
648
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
649
|
+
if (!coordinator) return;
|
|
650
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
651
|
+
if (!existsSync4(idPath)) return;
|
|
652
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
653
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
654
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
655
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
656
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
657
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
658
|
+
const entry = `
|
|
659
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
660
|
+
`;
|
|
661
|
+
let updated;
|
|
662
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
663
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
664
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
665
|
+
} else {
|
|
666
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
667
|
+
}
|
|
668
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
669
|
+
}
|
|
670
|
+
function capitalize(s) {
|
|
671
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
672
|
+
}
|
|
673
|
+
async function hireEmployee(employee) {
|
|
674
|
+
const employees = await loadEmployees();
|
|
675
|
+
const updated = addEmployee(employees, employee);
|
|
676
|
+
await saveEmployees(updated);
|
|
677
|
+
try {
|
|
678
|
+
appendToCoordinatorTeam(employee);
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
683
|
+
const config = loadAgentConfig2();
|
|
684
|
+
const name = employee.name.toLowerCase();
|
|
685
|
+
if (!config[name] && config["default"]) {
|
|
686
|
+
config[name] = { ...config["default"] };
|
|
687
|
+
saveAgentConfig2(config);
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
return updated;
|
|
692
|
+
}
|
|
693
|
+
async function normalizeRosterCase(rosterPath) {
|
|
694
|
+
const employees = await loadEmployees(rosterPath);
|
|
695
|
+
let changed = false;
|
|
696
|
+
for (const emp of employees) {
|
|
697
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
698
|
+
const oldName = emp.name;
|
|
699
|
+
emp.name = emp.name.toLowerCase();
|
|
700
|
+
changed = true;
|
|
701
|
+
try {
|
|
702
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
703
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
704
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
705
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
706
|
+
renameSync2(oldPath, newPath);
|
|
707
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
708
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
709
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
710
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
711
|
+
unlinkSync(oldPath);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} catch {
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (changed) {
|
|
719
|
+
await saveEmployees(employees, rosterPath);
|
|
720
|
+
}
|
|
721
|
+
return changed;
|
|
722
|
+
}
|
|
723
|
+
function findExeBin() {
|
|
724
|
+
try {
|
|
725
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
726
|
+
} catch {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function registerBinSymlinks(name) {
|
|
731
|
+
const created = [];
|
|
732
|
+
const skipped = [];
|
|
733
|
+
const errors = [];
|
|
734
|
+
const exeBinPath = findExeBin();
|
|
735
|
+
if (!exeBinPath) {
|
|
736
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
737
|
+
return { created, skipped, errors };
|
|
738
|
+
}
|
|
739
|
+
const binDir = path3.dirname(exeBinPath);
|
|
740
|
+
let target;
|
|
741
|
+
try {
|
|
742
|
+
target = readlinkSync(exeBinPath);
|
|
743
|
+
} catch {
|
|
744
|
+
errors.push("Could not read 'exe' symlink");
|
|
745
|
+
return { created, skipped, errors };
|
|
746
|
+
}
|
|
747
|
+
for (const suffix of ["", "-opencode"]) {
|
|
748
|
+
const linkName = `${name}${suffix}`;
|
|
749
|
+
const linkPath = path3.join(binDir, linkName);
|
|
750
|
+
if (existsSync4(linkPath)) {
|
|
751
|
+
skipped.push(linkName);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
symlinkSync(target, linkPath);
|
|
756
|
+
created.push(linkName);
|
|
757
|
+
} catch (err) {
|
|
758
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return { created, skipped, errors };
|
|
762
|
+
}
|
|
763
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
448
764
|
var init_employees = __esm({
|
|
449
765
|
"src/lib/employees.ts"() {
|
|
450
766
|
"use strict";
|
|
451
767
|
init_config();
|
|
452
|
-
EMPLOYEES_PATH =
|
|
768
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
453
769
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
454
770
|
COORDINATOR_ROLE = "COO";
|
|
455
771
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
456
|
-
IDENTITY_DIR =
|
|
772
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
773
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
457
774
|
}
|
|
458
775
|
});
|
|
459
776
|
|
|
460
777
|
// src/lib/database-adapter.ts
|
|
461
778
|
import os3 from "os";
|
|
462
|
-
import
|
|
779
|
+
import path4 from "path";
|
|
463
780
|
import { createRequire } from "module";
|
|
464
781
|
import { pathToFileURL } from "url";
|
|
465
782
|
function quotedIdentifier(identifier) {
|
|
@@ -770,8 +1087,8 @@ async function loadPrismaClient() {
|
|
|
770
1087
|
}
|
|
771
1088
|
return new PrismaClient2();
|
|
772
1089
|
}
|
|
773
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
774
|
-
const requireFromExeDb = createRequire(
|
|
1090
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1091
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
775
1092
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
776
1093
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
777
1094
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1052,8 +1369,8 @@ var init_memory = __esm({
|
|
|
1052
1369
|
|
|
1053
1370
|
// src/lib/daemon-auth.ts
|
|
1054
1371
|
import crypto from "crypto";
|
|
1055
|
-
import
|
|
1056
|
-
import { existsSync as
|
|
1372
|
+
import path5 from "path";
|
|
1373
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1057
1374
|
function normalizeToken(token) {
|
|
1058
1375
|
if (!token) return null;
|
|
1059
1376
|
const trimmed = token.trim();
|
|
@@ -1061,8 +1378,8 @@ function normalizeToken(token) {
|
|
|
1061
1378
|
}
|
|
1062
1379
|
function readDaemonToken() {
|
|
1063
1380
|
try {
|
|
1064
|
-
if (!
|
|
1065
|
-
return normalizeToken(
|
|
1381
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
1382
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
1066
1383
|
} catch {
|
|
1067
1384
|
return null;
|
|
1068
1385
|
}
|
|
@@ -1072,7 +1389,7 @@ function ensureDaemonToken(seed) {
|
|
|
1072
1389
|
if (existing) return existing;
|
|
1073
1390
|
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1074
1391
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1075
|
-
|
|
1392
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
1076
1393
|
`, "utf8");
|
|
1077
1394
|
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1078
1395
|
return token;
|
|
@@ -1083,7 +1400,7 @@ var init_daemon_auth = __esm({
|
|
|
1083
1400
|
"use strict";
|
|
1084
1401
|
init_config();
|
|
1085
1402
|
init_secure_files();
|
|
1086
|
-
DAEMON_TOKEN_PATH =
|
|
1403
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
1087
1404
|
}
|
|
1088
1405
|
});
|
|
1089
1406
|
|
|
@@ -1103,8 +1420,8 @@ import net from "net";
|
|
|
1103
1420
|
import os4 from "os";
|
|
1104
1421
|
import { spawn, execSync as execSync2 } from "child_process";
|
|
1105
1422
|
import { randomUUID } from "crypto";
|
|
1106
|
-
import { existsSync as
|
|
1107
|
-
import
|
|
1423
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1424
|
+
import path6 from "path";
|
|
1108
1425
|
import { fileURLToPath } from "url";
|
|
1109
1426
|
function handleData(chunk) {
|
|
1110
1427
|
_buffer += chunk.toString();
|
|
@@ -1140,9 +1457,9 @@ function isZombie(pid) {
|
|
|
1140
1457
|
}
|
|
1141
1458
|
}
|
|
1142
1459
|
function cleanupStaleFiles() {
|
|
1143
|
-
if (
|
|
1460
|
+
if (existsSync6(PID_PATH)) {
|
|
1144
1461
|
try {
|
|
1145
|
-
const pid = parseInt(
|
|
1462
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1146
1463
|
if (pid > 0) {
|
|
1147
1464
|
try {
|
|
1148
1465
|
process.kill(pid, 0);
|
|
@@ -1167,11 +1484,11 @@ function cleanupStaleFiles() {
|
|
|
1167
1484
|
}
|
|
1168
1485
|
}
|
|
1169
1486
|
function findPackageRoot() {
|
|
1170
|
-
let dir =
|
|
1171
|
-
const { root } =
|
|
1487
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1488
|
+
const { root } = path6.parse(dir);
|
|
1172
1489
|
while (dir !== root) {
|
|
1173
|
-
if (
|
|
1174
|
-
dir =
|
|
1490
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
1491
|
+
dir = path6.dirname(dir);
|
|
1175
1492
|
}
|
|
1176
1493
|
return null;
|
|
1177
1494
|
}
|
|
@@ -1189,8 +1506,8 @@ function spawnDaemon() {
|
|
|
1189
1506
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1190
1507
|
return;
|
|
1191
1508
|
}
|
|
1192
|
-
const daemonPath =
|
|
1193
|
-
if (!
|
|
1509
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1510
|
+
if (!existsSync6(daemonPath)) {
|
|
1194
1511
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1195
1512
|
`);
|
|
1196
1513
|
return;
|
|
@@ -1199,7 +1516,7 @@ function spawnDaemon() {
|
|
|
1199
1516
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1200
1517
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1201
1518
|
`);
|
|
1202
|
-
const logPath =
|
|
1519
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
1203
1520
|
let stderrFd = "ignore";
|
|
1204
1521
|
try {
|
|
1205
1522
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1364,9 +1681,9 @@ function killAndRespawnDaemon() {
|
|
|
1364
1681
|
}
|
|
1365
1682
|
try {
|
|
1366
1683
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1367
|
-
if (
|
|
1684
|
+
if (existsSync6(PID_PATH)) {
|
|
1368
1685
|
try {
|
|
1369
|
-
const pid = parseInt(
|
|
1686
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1370
1687
|
if (pid > 0) {
|
|
1371
1688
|
try {
|
|
1372
1689
|
process.kill(pid, "SIGKILL");
|
|
@@ -1512,9 +1829,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1512
1829
|
"use strict";
|
|
1513
1830
|
init_config();
|
|
1514
1831
|
init_daemon_auth();
|
|
1515
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1516
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1517
|
-
SPAWN_LOCK_PATH =
|
|
1832
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1833
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1834
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1518
1835
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1519
1836
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1520
1837
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -1782,7 +2099,7 @@ __export(database_exports, {
|
|
|
1782
2099
|
isInitialized: () => isInitialized,
|
|
1783
2100
|
setExternalClient: () => setExternalClient
|
|
1784
2101
|
});
|
|
1785
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
2102
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1786
2103
|
import { createClient } from "@libsql/client";
|
|
1787
2104
|
import { homedir } from "os";
|
|
1788
2105
|
import { join } from "path";
|
|
@@ -1835,11 +2152,11 @@ function releaseDbLock() {
|
|
|
1835
2152
|
}
|
|
1836
2153
|
async function initDatabase(config) {
|
|
1837
2154
|
acquireDbLock();
|
|
1838
|
-
if (
|
|
2155
|
+
if (existsSync7(config.dbPath)) {
|
|
1839
2156
|
const dbStat = statSync2(config.dbPath);
|
|
1840
2157
|
if (dbStat.size === 0) {
|
|
1841
2158
|
const walPath = config.dbPath + "-wal";
|
|
1842
|
-
if (
|
|
2159
|
+
if (existsSync7(walPath) && statSync2(walPath).size > 0) {
|
|
1843
2160
|
const backupPath = config.dbPath + ".zeroed-" + Date.now();
|
|
1844
2161
|
copyFileSync(config.dbPath, backupPath);
|
|
1845
2162
|
unlinkSync3(config.dbPath);
|
|
@@ -2135,6 +2452,13 @@ async function ensureSchema() {
|
|
|
2135
2452
|
} catch (e) {
|
|
2136
2453
|
logCatchDebug("migration", e);
|
|
2137
2454
|
}
|
|
2455
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2456
|
+
try {
|
|
2457
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2458
|
+
} catch (e) {
|
|
2459
|
+
logCatchDebug("migration", e);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2138
2462
|
try {
|
|
2139
2463
|
await client.execute({
|
|
2140
2464
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3351,6 +3675,22 @@ async function ensureSchema() {
|
|
|
3351
3675
|
} catch (e) {
|
|
3352
3676
|
logCatchDebug("migration", e);
|
|
3353
3677
|
}
|
|
3678
|
+
try {
|
|
3679
|
+
await client.execute({
|
|
3680
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3681
|
+
args: []
|
|
3682
|
+
});
|
|
3683
|
+
} catch (e) {
|
|
3684
|
+
logCatchDebug("migration", e);
|
|
3685
|
+
}
|
|
3686
|
+
try {
|
|
3687
|
+
await client.execute({
|
|
3688
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3689
|
+
args: []
|
|
3690
|
+
});
|
|
3691
|
+
} catch (e) {
|
|
3692
|
+
logCatchDebug("migration", e);
|
|
3693
|
+
}
|
|
3354
3694
|
}
|
|
3355
3695
|
async function disposeDatabase() {
|
|
3356
3696
|
if (_walCheckpointTimer) {
|
|
@@ -3403,15 +3743,15 @@ var init_database = __esm({
|
|
|
3403
3743
|
|
|
3404
3744
|
// src/lib/keychain.ts
|
|
3405
3745
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3406
|
-
import { existsSync as
|
|
3746
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3407
3747
|
import { execSync as execSync3 } from "child_process";
|
|
3408
|
-
import
|
|
3748
|
+
import path7 from "path";
|
|
3409
3749
|
import os5 from "os";
|
|
3410
3750
|
function getKeyDir() {
|
|
3411
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3751
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
3412
3752
|
}
|
|
3413
3753
|
function getKeyPath() {
|
|
3414
|
-
return
|
|
3754
|
+
return path7.join(getKeyDir(), "master.key");
|
|
3415
3755
|
}
|
|
3416
3756
|
function nativeKeychainAllowed() {
|
|
3417
3757
|
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
@@ -3442,7 +3782,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
|
3442
3782
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3443
3783
|
if (uid === 0) return true;
|
|
3444
3784
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3445
|
-
return Boolean(exeOsDir &&
|
|
3785
|
+
return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
|
|
3446
3786
|
} catch {
|
|
3447
3787
|
return false;
|
|
3448
3788
|
}
|
|
@@ -3639,7 +3979,7 @@ async function getMasterKey() {
|
|
|
3639
3979
|
}
|
|
3640
3980
|
}
|
|
3641
3981
|
const keyPath = getKeyPath();
|
|
3642
|
-
if (!
|
|
3982
|
+
if (!existsSync8(keyPath)) {
|
|
3643
3983
|
process.stderr.write(
|
|
3644
3984
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3645
3985
|
`
|
|
@@ -3973,14 +4313,14 @@ __export(shard_manager_exports, {
|
|
|
3973
4313
|
listShards: () => listShards,
|
|
3974
4314
|
shardExists: () => shardExists
|
|
3975
4315
|
});
|
|
3976
|
-
import
|
|
3977
|
-
import { existsSync as
|
|
4316
|
+
import path8 from "path";
|
|
4317
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
|
|
3978
4318
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3979
4319
|
function initShardManager(encryptionKey) {
|
|
3980
4320
|
_encryptionKey = encryptionKey;
|
|
3981
4321
|
_keyValidated = false;
|
|
3982
4322
|
_keyValidationPromise = null;
|
|
3983
|
-
if (!
|
|
4323
|
+
if (!existsSync9(SHARDS_DIR)) {
|
|
3984
4324
|
mkdirSync3(SHARDS_DIR, { recursive: true });
|
|
3985
4325
|
}
|
|
3986
4326
|
const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
|
|
@@ -4001,7 +4341,7 @@ async function validateEncryptionKey() {
|
|
|
4001
4341
|
return true;
|
|
4002
4342
|
}
|
|
4003
4343
|
for (const shardFile of existingShards.slice(0, 3)) {
|
|
4004
|
-
const dbPath =
|
|
4344
|
+
const dbPath = path8.join(SHARDS_DIR, shardFile);
|
|
4005
4345
|
const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
|
|
4006
4346
|
try {
|
|
4007
4347
|
await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
|
|
@@ -4044,7 +4384,7 @@ function getShardClient(projectName) {
|
|
|
4044
4384
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
4045
4385
|
evictLRU();
|
|
4046
4386
|
}
|
|
4047
|
-
const dbPath =
|
|
4387
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4048
4388
|
const client = createClient2({
|
|
4049
4389
|
url: `file:${dbPath}`,
|
|
4050
4390
|
encryptionKey: _encryptionKey
|
|
@@ -4055,13 +4395,13 @@ function getShardClient(projectName) {
|
|
|
4055
4395
|
}
|
|
4056
4396
|
function shardExists(projectName) {
|
|
4057
4397
|
const safeName = safeShardName(projectName);
|
|
4058
|
-
return
|
|
4398
|
+
return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
4059
4399
|
}
|
|
4060
4400
|
function safeShardName(projectName) {
|
|
4061
4401
|
return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4062
4402
|
}
|
|
4063
4403
|
function listShards() {
|
|
4064
|
-
if (!
|
|
4404
|
+
if (!existsSync9(SHARDS_DIR)) return [];
|
|
4065
4405
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4066
4406
|
}
|
|
4067
4407
|
async function auditShardHealth(options = {}) {
|
|
@@ -4073,7 +4413,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4073
4413
|
const names = listShards();
|
|
4074
4414
|
const shards = [];
|
|
4075
4415
|
for (const name of names) {
|
|
4076
|
-
const dbPath =
|
|
4416
|
+
const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
|
|
4077
4417
|
const stat = statSync4(dbPath);
|
|
4078
4418
|
const item = {
|
|
4079
4419
|
name,
|
|
@@ -4108,7 +4448,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4108
4448
|
_shards.delete(name);
|
|
4109
4449
|
_shardLastAccess.delete(name);
|
|
4110
4450
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4111
|
-
const archivedPath =
|
|
4451
|
+
const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
4112
4452
|
renameSync3(dbPath, archivedPath);
|
|
4113
4453
|
item.archivedPath = archivedPath;
|
|
4114
4454
|
}
|
|
@@ -4336,11 +4676,11 @@ async function getReadyShardClient(projectName) {
|
|
|
4336
4676
|
client.close();
|
|
4337
4677
|
_shards.delete(safeName);
|
|
4338
4678
|
_shardLastAccess.delete(safeName);
|
|
4339
|
-
const dbPath =
|
|
4340
|
-
if (
|
|
4679
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4680
|
+
if (existsSync9(dbPath)) {
|
|
4341
4681
|
const stat = statSync4(dbPath);
|
|
4342
4682
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4343
|
-
const archivedPath =
|
|
4683
|
+
const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
4344
4684
|
renameSync3(dbPath, archivedPath);
|
|
4345
4685
|
process.stderr.write(
|
|
4346
4686
|
`[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
|
|
@@ -4408,7 +4748,7 @@ var init_shard_manager = __esm({
|
|
|
4408
4748
|
"src/lib/shard-manager.ts"() {
|
|
4409
4749
|
"use strict";
|
|
4410
4750
|
init_config();
|
|
4411
|
-
SHARDS_DIR =
|
|
4751
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
4412
4752
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
4413
4753
|
MAX_OPEN_SHARDS = 10;
|
|
4414
4754
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -4474,11 +4814,17 @@ var init_platform_procedures = __esm({
|
|
|
4474
4814
|
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."
|
|
4475
4815
|
},
|
|
4476
4816
|
{
|
|
4477
|
-
title: "
|
|
4817
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4478
4818
|
domain: "workflow",
|
|
4479
4819
|
priority: "p1",
|
|
4480
4820
|
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."
|
|
4481
4821
|
},
|
|
4822
|
+
{
|
|
4823
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
4824
|
+
domain: "identity",
|
|
4825
|
+
priority: "p0",
|
|
4826
|
+
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."
|
|
4827
|
+
},
|
|
4482
4828
|
{
|
|
4483
4829
|
title: "Single dispatch path \u2014 create_task only",
|
|
4484
4830
|
domain: "workflow",
|
|
@@ -4512,6 +4858,12 @@ var init_platform_procedures = __esm({
|
|
|
4512
4858
|
priority: "p0",
|
|
4513
4859
|
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."
|
|
4514
4860
|
},
|
|
4861
|
+
{
|
|
4862
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
4863
|
+
domain: "security",
|
|
4864
|
+
priority: "p0",
|
|
4865
|
+
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."
|
|
4866
|
+
},
|
|
4515
4867
|
{
|
|
4516
4868
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4517
4869
|
domain: "support",
|
|
@@ -4663,7 +5015,7 @@ var init_platform_procedures = __esm({
|
|
|
4663
5015
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
4664
5016
|
domain: "tool-use",
|
|
4665
5017
|
priority: "p0",
|
|
4666
|
-
content: 'exe-os MCP tools
|
|
5018
|
+
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.'
|
|
4667
5019
|
},
|
|
4668
5020
|
{
|
|
4669
5021
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -4797,10 +5149,24 @@ function stableId(memoryId, type, content) {
|
|
|
4797
5149
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
4798
5150
|
}
|
|
4799
5151
|
function cleanText(text) {
|
|
4800
|
-
|
|
5152
|
+
let cleaned = text.replace(
|
|
5153
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5154
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5155
|
+
);
|
|
5156
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5157
|
+
return cleaned;
|
|
4801
5158
|
}
|
|
4802
|
-
function
|
|
4803
|
-
|
|
5159
|
+
function splitSegments(text) {
|
|
5160
|
+
const cleaned = cleanText(text);
|
|
5161
|
+
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);
|
|
5162
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5163
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5164
|
+
if (lines.length > 0) return lines;
|
|
5165
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5166
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
return segments;
|
|
4804
5170
|
}
|
|
4805
5171
|
function inferCardType(sentence, toolName) {
|
|
4806
5172
|
const lower = sentence.toLowerCase();
|
|
@@ -4832,12 +5198,12 @@ function predicateFor(type) {
|
|
|
4832
5198
|
}
|
|
4833
5199
|
}
|
|
4834
5200
|
function extractMemoryCards(row) {
|
|
4835
|
-
const
|
|
5201
|
+
const segments = splitSegments(row.raw_text);
|
|
4836
5202
|
const cards = [];
|
|
4837
|
-
for (const sentence of
|
|
5203
|
+
for (const sentence of segments) {
|
|
4838
5204
|
const type = inferCardType(sentence, row.tool_name);
|
|
4839
5205
|
const subject = extractSubject(sentence, row.agent_id);
|
|
4840
|
-
const content = sentence.length >
|
|
5206
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
4841
5207
|
cards.push({
|
|
4842
5208
|
id: stableId(row.id, type, content),
|
|
4843
5209
|
memory_id: row.id,
|
|
@@ -4933,13 +5299,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
4933
5299
|
last_accessed: String(row.timestamp)
|
|
4934
5300
|
}));
|
|
4935
5301
|
}
|
|
4936
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5302
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
4937
5303
|
var init_memory_cards = __esm({
|
|
4938
5304
|
"src/lib/memory-cards.ts"() {
|
|
4939
5305
|
"use strict";
|
|
4940
5306
|
init_database();
|
|
4941
|
-
MAX_CARDS_PER_MEMORY =
|
|
4942
|
-
|
|
5307
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5308
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5309
|
+
MIN_SEGMENT_CHARS = 20;
|
|
4943
5310
|
}
|
|
4944
5311
|
});
|
|
4945
5312
|
|
|
@@ -5947,12 +6314,12 @@ var init_fast_db_init = __esm({
|
|
|
5947
6314
|
});
|
|
5948
6315
|
|
|
5949
6316
|
// src/lib/session-registry.ts
|
|
5950
|
-
import { readFileSync as
|
|
5951
|
-
import
|
|
6317
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
|
|
6318
|
+
import path9 from "path";
|
|
5952
6319
|
import os6 from "os";
|
|
5953
6320
|
function registerSession(entry) {
|
|
5954
|
-
const dir =
|
|
5955
|
-
if (!
|
|
6321
|
+
const dir = path9.dirname(REGISTRY_PATH);
|
|
6322
|
+
if (!existsSync10(dir)) {
|
|
5956
6323
|
mkdirSync4(dir, { recursive: true });
|
|
5957
6324
|
}
|
|
5958
6325
|
const sessions = listSessions();
|
|
@@ -5962,11 +6329,11 @@ function registerSession(entry) {
|
|
|
5962
6329
|
} else {
|
|
5963
6330
|
sessions.push(entry);
|
|
5964
6331
|
}
|
|
5965
|
-
|
|
6332
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
5966
6333
|
}
|
|
5967
6334
|
function listSessions() {
|
|
5968
6335
|
try {
|
|
5969
|
-
const raw =
|
|
6336
|
+
const raw = readFileSync6(REGISTRY_PATH, "utf8");
|
|
5970
6337
|
return JSON.parse(raw);
|
|
5971
6338
|
} catch {
|
|
5972
6339
|
return [];
|
|
@@ -5976,7 +6343,7 @@ var REGISTRY_PATH;
|
|
|
5976
6343
|
var init_session_registry = __esm({
|
|
5977
6344
|
"src/lib/session-registry.ts"() {
|
|
5978
6345
|
"use strict";
|
|
5979
|
-
REGISTRY_PATH =
|
|
6346
|
+
REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
5980
6347
|
}
|
|
5981
6348
|
});
|
|
5982
6349
|
|
|
@@ -6250,68 +6617,6 @@ var init_provider_table = __esm({
|
|
|
6250
6617
|
}
|
|
6251
6618
|
});
|
|
6252
6619
|
|
|
6253
|
-
// src/lib/runtime-table.ts
|
|
6254
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
6255
|
-
var init_runtime_table = __esm({
|
|
6256
|
-
"src/lib/runtime-table.ts"() {
|
|
6257
|
-
"use strict";
|
|
6258
|
-
RUNTIME_TABLE = {
|
|
6259
|
-
codex: {
|
|
6260
|
-
binary: "codex",
|
|
6261
|
-
launchMode: "interactive",
|
|
6262
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
6263
|
-
inlineFlag: "--no-alt-screen",
|
|
6264
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
6265
|
-
defaultModel: "gpt-5.5"
|
|
6266
|
-
},
|
|
6267
|
-
opencode: {
|
|
6268
|
-
binary: "opencode",
|
|
6269
|
-
launchMode: "exec",
|
|
6270
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
6271
|
-
inlineFlag: "",
|
|
6272
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
6273
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
6274
|
-
}
|
|
6275
|
-
};
|
|
6276
|
-
DEFAULT_RUNTIME = "claude";
|
|
6277
|
-
}
|
|
6278
|
-
});
|
|
6279
|
-
|
|
6280
|
-
// src/lib/agent-config.ts
|
|
6281
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
|
|
6282
|
-
import path9 from "path";
|
|
6283
|
-
function loadAgentConfig() {
|
|
6284
|
-
if (!existsSync10(AGENT_CONFIG_PATH)) return {};
|
|
6285
|
-
try {
|
|
6286
|
-
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
6287
|
-
} catch {
|
|
6288
|
-
return {};
|
|
6289
|
-
}
|
|
6290
|
-
}
|
|
6291
|
-
function getAgentRuntime(agentId) {
|
|
6292
|
-
const config = loadAgentConfig();
|
|
6293
|
-
const entry = config[agentId];
|
|
6294
|
-
if (entry) return entry;
|
|
6295
|
-
const orgDefault = config["default"];
|
|
6296
|
-
if (orgDefault) return orgDefault;
|
|
6297
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
6298
|
-
}
|
|
6299
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
6300
|
-
var init_agent_config = __esm({
|
|
6301
|
-
"src/lib/agent-config.ts"() {
|
|
6302
|
-
"use strict";
|
|
6303
|
-
init_config();
|
|
6304
|
-
init_runtime_table();
|
|
6305
|
-
init_secure_files();
|
|
6306
|
-
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
6307
|
-
DEFAULT_MODELS = {
|
|
6308
|
-
claude: "claude-opus-4.6",
|
|
6309
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
6310
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
6311
|
-
};
|
|
6312
|
-
}
|
|
6313
|
-
});
|
|
6314
|
-
|
|
6315
6620
|
// src/lib/intercom-queue.ts
|
|
6316
6621
|
var intercom_queue_exports = {};
|
|
6317
6622
|
__export(intercom_queue_exports, {
|
|
@@ -6439,6 +6744,23 @@ var init_intercom_queue = __esm({
|
|
|
6439
6744
|
});
|
|
6440
6745
|
|
|
6441
6746
|
// src/lib/license.ts
|
|
6747
|
+
var license_exports = {};
|
|
6748
|
+
__export(license_exports, {
|
|
6749
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
6750
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
6751
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
6752
|
+
checkLicense: () => checkLicense,
|
|
6753
|
+
getCachedLicense: () => getCachedLicense,
|
|
6754
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
6755
|
+
loadDeviceId: () => loadDeviceId,
|
|
6756
|
+
loadLicense: () => loadLicense,
|
|
6757
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
6758
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
6759
|
+
saveLicense: () => saveLicense,
|
|
6760
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
6761
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
6762
|
+
validateLicense: () => validateLicense
|
|
6763
|
+
});
|
|
6442
6764
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
|
|
6443
6765
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6444
6766
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -6446,7 +6768,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
6446
6768
|
import os8 from "os";
|
|
6447
6769
|
import path11 from "path";
|
|
6448
6770
|
import { jwtVerify, importSPKI } from "jose";
|
|
6449
|
-
|
|
6771
|
+
async function fetchRetry(url, init) {
|
|
6772
|
+
try {
|
|
6773
|
+
return await fetch(url, init);
|
|
6774
|
+
} catch {
|
|
6775
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
6776
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
6777
|
+
}
|
|
6778
|
+
}
|
|
6779
|
+
function loadDeviceId() {
|
|
6780
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
6781
|
+
try {
|
|
6782
|
+
if (existsSync12(deviceJsonPath)) {
|
|
6783
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
6784
|
+
if (data.deviceId) return data.deviceId;
|
|
6785
|
+
}
|
|
6786
|
+
} catch {
|
|
6787
|
+
}
|
|
6788
|
+
try {
|
|
6789
|
+
if (existsSync12(DEVICE_ID_PATH)) {
|
|
6790
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
6791
|
+
if (id2) return id2;
|
|
6792
|
+
}
|
|
6793
|
+
} catch {
|
|
6794
|
+
}
|
|
6795
|
+
const id = randomUUID3();
|
|
6796
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
6797
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
6798
|
+
return id;
|
|
6799
|
+
}
|
|
6800
|
+
function loadLicense() {
|
|
6801
|
+
try {
|
|
6802
|
+
if (!existsSync12(LICENSE_PATH)) return null;
|
|
6803
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
6804
|
+
} catch {
|
|
6805
|
+
return null;
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
function saveLicense(apiKey) {
|
|
6809
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
6810
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
6811
|
+
}
|
|
6812
|
+
async function verifyLicenseJwt(token) {
|
|
6813
|
+
try {
|
|
6814
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
6815
|
+
const { payload } = await jwtVerify(token, key, {
|
|
6816
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
6817
|
+
});
|
|
6818
|
+
const plan = payload.plan ?? "free";
|
|
6819
|
+
const email = payload.sub ?? "";
|
|
6820
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6821
|
+
return {
|
|
6822
|
+
valid: true,
|
|
6823
|
+
plan,
|
|
6824
|
+
email,
|
|
6825
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6826
|
+
deviceLimit: limits.devices,
|
|
6827
|
+
employeeLimit: limits.employees,
|
|
6828
|
+
memoryLimit: limits.memories
|
|
6829
|
+
};
|
|
6830
|
+
} catch {
|
|
6831
|
+
return null;
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
async function getCachedLicense() {
|
|
6835
|
+
try {
|
|
6836
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
6837
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6838
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
6839
|
+
return await verifyLicenseJwt(raw.token);
|
|
6840
|
+
} catch {
|
|
6841
|
+
return null;
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6844
|
+
function readCachedLicenseToken() {
|
|
6845
|
+
try {
|
|
6846
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
6847
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6848
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
6849
|
+
} catch {
|
|
6850
|
+
return null;
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
function getRawCachedPlan() {
|
|
6854
|
+
try {
|
|
6855
|
+
const token = readCachedLicenseToken();
|
|
6856
|
+
if (!token) return null;
|
|
6857
|
+
const parts = token.split(".");
|
|
6858
|
+
if (parts.length !== 3) return null;
|
|
6859
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
6860
|
+
const plan = payload.plan ?? "free";
|
|
6861
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6862
|
+
process.stderr.write(
|
|
6863
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
6864
|
+
`
|
|
6865
|
+
);
|
|
6866
|
+
return {
|
|
6867
|
+
valid: true,
|
|
6868
|
+
plan,
|
|
6869
|
+
email: payload.sub ?? "",
|
|
6870
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6871
|
+
deviceLimit: limits.devices,
|
|
6872
|
+
employeeLimit: limits.employees,
|
|
6873
|
+
memoryLimit: limits.memories
|
|
6874
|
+
};
|
|
6875
|
+
} catch {
|
|
6876
|
+
return null;
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
function cacheResponse(token) {
|
|
6880
|
+
try {
|
|
6881
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
6882
|
+
} catch {
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
function loadPrismaForLicense() {
|
|
6886
|
+
if (_prismaFailed) return null;
|
|
6887
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
6888
|
+
if (!dbUrl) {
|
|
6889
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6890
|
+
if (!existsSync12(path11.join(exeDbRoot, "package.json"))) {
|
|
6891
|
+
_prismaFailed = true;
|
|
6892
|
+
return null;
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6895
|
+
if (!_prismaPromise) {
|
|
6896
|
+
_prismaPromise = (async () => {
|
|
6897
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
6898
|
+
if (explicitPath) {
|
|
6899
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
6900
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
6901
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
6902
|
+
return new Ctor2();
|
|
6903
|
+
}
|
|
6904
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6905
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
6906
|
+
const entry = req.resolve("@prisma/client");
|
|
6907
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
6908
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
6909
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
6910
|
+
return new Ctor();
|
|
6911
|
+
})().catch((err) => {
|
|
6912
|
+
_prismaFailed = true;
|
|
6913
|
+
_prismaPromise = null;
|
|
6914
|
+
throw err;
|
|
6915
|
+
});
|
|
6916
|
+
}
|
|
6917
|
+
return _prismaPromise;
|
|
6918
|
+
}
|
|
6919
|
+
async function validateViaPostgres(apiKey) {
|
|
6920
|
+
const loader = loadPrismaForLicense();
|
|
6921
|
+
if (!loader) return null;
|
|
6922
|
+
try {
|
|
6923
|
+
const prisma = await loader;
|
|
6924
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
6925
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
6926
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
6927
|
+
apiKey
|
|
6928
|
+
);
|
|
6929
|
+
if (!rows || rows.length === 0) return null;
|
|
6930
|
+
const row = rows[0];
|
|
6931
|
+
if (row.status !== "active") return null;
|
|
6932
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
6933
|
+
const plan = row.plan;
|
|
6934
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6935
|
+
return {
|
|
6936
|
+
valid: true,
|
|
6937
|
+
plan,
|
|
6938
|
+
email: row.email,
|
|
6939
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
6940
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
6941
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
6942
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
6943
|
+
};
|
|
6944
|
+
} catch {
|
|
6945
|
+
return null;
|
|
6946
|
+
}
|
|
6947
|
+
}
|
|
6948
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
6949
|
+
try {
|
|
6950
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
6951
|
+
method: "POST",
|
|
6952
|
+
headers: { "Content-Type": "application/json" },
|
|
6953
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
6954
|
+
signal: AbortSignal.timeout(1e4)
|
|
6955
|
+
});
|
|
6956
|
+
if (!res.ok) return null;
|
|
6957
|
+
const data = await res.json();
|
|
6958
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
6959
|
+
if (!data.valid) return null;
|
|
6960
|
+
if (data.token) {
|
|
6961
|
+
cacheResponse(data.token);
|
|
6962
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
6963
|
+
if (verified) return verified;
|
|
6964
|
+
}
|
|
6965
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
6966
|
+
return {
|
|
6967
|
+
valid: data.valid,
|
|
6968
|
+
plan: data.plan,
|
|
6969
|
+
email: data.email,
|
|
6970
|
+
expiresAt: data.expiresAt,
|
|
6971
|
+
deviceLimit: limits.devices,
|
|
6972
|
+
employeeLimit: limits.employees,
|
|
6973
|
+
memoryLimit: limits.memories
|
|
6974
|
+
};
|
|
6975
|
+
} catch {
|
|
6976
|
+
return null;
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6979
|
+
async function validateLicense(apiKey, deviceId) {
|
|
6980
|
+
const did = deviceId ?? loadDeviceId();
|
|
6981
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
6982
|
+
if (pgResult) {
|
|
6983
|
+
try {
|
|
6984
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
6985
|
+
} catch {
|
|
6986
|
+
}
|
|
6987
|
+
return pgResult;
|
|
6988
|
+
}
|
|
6989
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
6990
|
+
if (cfResult) return cfResult;
|
|
6991
|
+
const cached = await getCachedLicense();
|
|
6992
|
+
if (cached) return cached;
|
|
6993
|
+
try {
|
|
6994
|
+
if (existsSync12(CACHE_PATH)) {
|
|
6995
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6996
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
6997
|
+
return raw.pgLicense;
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
} catch {
|
|
7001
|
+
}
|
|
7002
|
+
const rawFallback = getRawCachedPlan();
|
|
7003
|
+
if (rawFallback) return rawFallback;
|
|
7004
|
+
return { ...FREE_LICENSE, valid: false };
|
|
7005
|
+
}
|
|
7006
|
+
function getCacheAgeMs() {
|
|
7007
|
+
try {
|
|
7008
|
+
const { statSync: statSync5 } = __require("fs");
|
|
7009
|
+
const s = statSync5(CACHE_PATH);
|
|
7010
|
+
return Date.now() - s.mtimeMs;
|
|
7011
|
+
} catch {
|
|
7012
|
+
return Infinity;
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
7015
|
+
async function checkLicense() {
|
|
7016
|
+
let key = loadLicense();
|
|
7017
|
+
if (!key) {
|
|
7018
|
+
try {
|
|
7019
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
7020
|
+
if (existsSync12(configPath)) {
|
|
7021
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
7022
|
+
const cloud = raw.cloud;
|
|
7023
|
+
if (cloud?.apiKey) {
|
|
7024
|
+
key = cloud.apiKey;
|
|
7025
|
+
saveLicense(key);
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
7028
|
+
} catch {
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
if (!key) return FREE_LICENSE;
|
|
7032
|
+
const cached = await getCachedLicense();
|
|
7033
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
7034
|
+
const deviceId = loadDeviceId();
|
|
7035
|
+
return validateLicense(key, deviceId);
|
|
7036
|
+
}
|
|
7037
|
+
function isFeatureAllowed(license, feature) {
|
|
7038
|
+
switch (feature) {
|
|
7039
|
+
case "cloud_sync":
|
|
7040
|
+
case "external_agents":
|
|
7041
|
+
case "wiki":
|
|
7042
|
+
return license.plan !== "free";
|
|
7043
|
+
case "unlimited_employees":
|
|
7044
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
function mirrorLicenseKey(apiKey) {
|
|
7048
|
+
const trimmed = apiKey.trim();
|
|
7049
|
+
if (!trimmed) return;
|
|
7050
|
+
saveLicense(trimmed);
|
|
7051
|
+
}
|
|
7052
|
+
async function assertVpsLicense(opts) {
|
|
7053
|
+
const env = opts?.env ?? process.env;
|
|
7054
|
+
const inProduction = env.NODE_ENV === "production";
|
|
7055
|
+
if (!opts?.force && !inProduction) {
|
|
7056
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
7057
|
+
}
|
|
7058
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
7059
|
+
if (envKey) {
|
|
7060
|
+
saveLicense(envKey);
|
|
7061
|
+
}
|
|
7062
|
+
const apiKey = envKey ?? loadLicense();
|
|
7063
|
+
if (!apiKey) {
|
|
7064
|
+
throw new Error(
|
|
7065
|
+
"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."
|
|
7066
|
+
);
|
|
7067
|
+
}
|
|
7068
|
+
const deviceId = loadDeviceId();
|
|
7069
|
+
let backendResponse = null;
|
|
7070
|
+
let explicitRejection = false;
|
|
7071
|
+
let transientFailure = false;
|
|
7072
|
+
try {
|
|
7073
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
7074
|
+
method: "POST",
|
|
7075
|
+
headers: { "Content-Type": "application/json" },
|
|
7076
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
7077
|
+
signal: AbortSignal.timeout(1e4)
|
|
7078
|
+
});
|
|
7079
|
+
if (res.ok) {
|
|
7080
|
+
backendResponse = await res.json();
|
|
7081
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
7082
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
7083
|
+
explicitRejection = true;
|
|
7084
|
+
} else {
|
|
7085
|
+
transientFailure = true;
|
|
7086
|
+
}
|
|
7087
|
+
} catch {
|
|
7088
|
+
transientFailure = true;
|
|
7089
|
+
}
|
|
7090
|
+
if (backendResponse?.valid) {
|
|
7091
|
+
if (backendResponse.token) {
|
|
7092
|
+
cacheResponse(backendResponse.token);
|
|
7093
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
7094
|
+
if (verified) return verified;
|
|
7095
|
+
}
|
|
7096
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
7097
|
+
return {
|
|
7098
|
+
valid: true,
|
|
7099
|
+
plan: backendResponse.plan,
|
|
7100
|
+
email: backendResponse.email,
|
|
7101
|
+
expiresAt: backendResponse.expiresAt,
|
|
7102
|
+
deviceLimit: limits.devices,
|
|
7103
|
+
employeeLimit: limits.employees,
|
|
7104
|
+
memoryLimit: limits.memories
|
|
7105
|
+
};
|
|
7106
|
+
}
|
|
7107
|
+
if (explicitRejection) {
|
|
7108
|
+
throw new Error(
|
|
7109
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
7110
|
+
);
|
|
7111
|
+
}
|
|
7112
|
+
if (!transientFailure) {
|
|
7113
|
+
throw new Error(
|
|
7114
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
7115
|
+
);
|
|
7116
|
+
}
|
|
7117
|
+
const fresh = await getCachedLicense();
|
|
7118
|
+
if (fresh && fresh.valid) return fresh;
|
|
7119
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
7120
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
7121
|
+
try {
|
|
7122
|
+
const token = readCachedLicenseToken();
|
|
7123
|
+
if (token) {
|
|
7124
|
+
const payloadB64 = token.split(".")[1];
|
|
7125
|
+
if (payloadB64) {
|
|
7126
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
7127
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
7128
|
+
if (Date.now() < expMs + graceMs) {
|
|
7129
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
7130
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
7131
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
7132
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
7133
|
+
});
|
|
7134
|
+
const plan = verified.plan ?? "free";
|
|
7135
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
7136
|
+
return {
|
|
7137
|
+
valid: true,
|
|
7138
|
+
plan,
|
|
7139
|
+
email: verified.sub ?? "",
|
|
7140
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
7141
|
+
deviceLimit: limits.devices,
|
|
7142
|
+
employeeLimit: limits.employees,
|
|
7143
|
+
memoryLimit: limits.memories
|
|
7144
|
+
};
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
} catch {
|
|
7149
|
+
}
|
|
7150
|
+
throw new Error(
|
|
7151
|
+
`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.`
|
|
7152
|
+
);
|
|
7153
|
+
}
|
|
7154
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
7155
|
+
if (_revalTimer) return;
|
|
7156
|
+
_revalTimer = setInterval(async () => {
|
|
7157
|
+
try {
|
|
7158
|
+
const license = await checkLicense();
|
|
7159
|
+
if (!license.valid) {
|
|
7160
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
7161
|
+
}
|
|
7162
|
+
} catch {
|
|
7163
|
+
}
|
|
7164
|
+
}, intervalMs);
|
|
7165
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
7166
|
+
_revalTimer.unref();
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
7169
|
+
function stopLicenseRevalidation() {
|
|
7170
|
+
if (_revalTimer) {
|
|
7171
|
+
clearInterval(_revalTimer);
|
|
7172
|
+
_revalTimer = null;
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
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;
|
|
6450
7176
|
var init_license = __esm({
|
|
6451
7177
|
"src/lib/license.ts"() {
|
|
6452
7178
|
"use strict";
|
|
@@ -6454,7 +7180,13 @@ var init_license = __esm({
|
|
|
6454
7180
|
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
6455
7181
|
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
6456
7182
|
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
6457
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
7183
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
7184
|
+
RETRY_DELAY_MS = 500;
|
|
7185
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
7186
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
7187
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
7188
|
+
-----END PUBLIC KEY-----`;
|
|
7189
|
+
LICENSE_JWT_ALG = "ES256";
|
|
6458
7190
|
PLAN_LIMITS = {
|
|
6459
7191
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
6460
7192
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -6462,6 +7194,19 @@ var init_license = __esm({
|
|
|
6462
7194
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
6463
7195
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
6464
7196
|
};
|
|
7197
|
+
FREE_LICENSE = {
|
|
7198
|
+
valid: true,
|
|
7199
|
+
plan: "free",
|
|
7200
|
+
email: "",
|
|
7201
|
+
expiresAt: null,
|
|
7202
|
+
deviceLimit: 1,
|
|
7203
|
+
employeeLimit: 1,
|
|
7204
|
+
memoryLimit: 5e3
|
|
7205
|
+
};
|
|
7206
|
+
_prismaPromise = null;
|
|
7207
|
+
_prismaFailed = false;
|
|
7208
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
7209
|
+
_revalTimer = null;
|
|
6465
7210
|
}
|
|
6466
7211
|
});
|
|
6467
7212
|
|
|
@@ -6837,6 +7582,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
6837
7582
|
args: [identifier, ...scope.args]
|
|
6838
7583
|
});
|
|
6839
7584
|
if (result.rows.length === 1) return result.rows[0];
|
|
7585
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
7586
|
+
result = await client.execute({
|
|
7587
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
7588
|
+
args: [`${identifier}%`]
|
|
7589
|
+
});
|
|
7590
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
7591
|
+
if (result.rows.length > 1) {
|
|
7592
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
7593
|
+
throw new Error(
|
|
7594
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
7595
|
+
);
|
|
7596
|
+
}
|
|
7597
|
+
}
|
|
6840
7598
|
result = await client.execute({
|
|
6841
7599
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
6842
7600
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -7383,12 +8141,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7383
8141
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
7384
8142
|
args: [now, taskId]
|
|
7385
8143
|
});
|
|
7386
|
-
if (
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
8144
|
+
if (unblocked.rowsAffected === 0) return;
|
|
8145
|
+
const ubScope = sessionScopeFilter();
|
|
8146
|
+
const unblockedRows = await client.execute({
|
|
8147
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
8148
|
+
args: [now, ...ubScope.args]
|
|
8149
|
+
});
|
|
8150
|
+
if (baseDir) {
|
|
7392
8151
|
for (const ur of unblockedRows.rows) {
|
|
7393
8152
|
try {
|
|
7394
8153
|
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
@@ -7400,6 +8159,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7400
8159
|
}
|
|
7401
8160
|
}
|
|
7402
8161
|
}
|
|
8162
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
8163
|
+
try {
|
|
8164
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
8165
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
8166
|
+
for (const ur of unblockedRows.rows) {
|
|
8167
|
+
const assignee = String(ur.assigned_to);
|
|
8168
|
+
if (dispatched.has(assignee)) continue;
|
|
8169
|
+
dispatched.add(assignee);
|
|
8170
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
8171
|
+
}
|
|
8172
|
+
} catch {
|
|
8173
|
+
}
|
|
8174
|
+
}
|
|
7403
8175
|
}
|
|
7404
8176
|
async function findNextTask(assignedTo) {
|
|
7405
8177
|
const client = getClient();
|
|
@@ -7609,6 +8381,15 @@ var init_embedder = __esm({
|
|
|
7609
8381
|
// src/lib/behaviors.ts
|
|
7610
8382
|
import crypto4 from "crypto";
|
|
7611
8383
|
async function storeBehavior(opts) {
|
|
8384
|
+
try {
|
|
8385
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
8386
|
+
const roster = loadEmployeesSync2();
|
|
8387
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
8388
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
8389
|
+
}
|
|
8390
|
+
} catch (e) {
|
|
8391
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
8392
|
+
}
|
|
7612
8393
|
const client = getClient();
|
|
7613
8394
|
const id = crypto4.randomUUID();
|
|
7614
8395
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7619,10 +8400,18 @@ async function storeBehavior(opts) {
|
|
|
7619
8400
|
vector = new Float32Array(vec);
|
|
7620
8401
|
} catch {
|
|
7621
8402
|
}
|
|
8403
|
+
let createdByDevice = null;
|
|
8404
|
+
try {
|
|
8405
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
8406
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
8407
|
+
} catch {
|
|
8408
|
+
}
|
|
8409
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
8410
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
7622
8411
|
await client.execute({
|
|
7623
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
7624
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
7625
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
8412
|
+
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)
|
|
8413
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
8414
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
7626
8415
|
});
|
|
7627
8416
|
return id;
|
|
7628
8417
|
}
|
|
@@ -8054,6 +8843,12 @@ async function updateTask(input) {
|
|
|
8054
8843
|
}
|
|
8055
8844
|
}
|
|
8056
8845
|
}
|
|
8846
|
+
if (input.status === "cancelled") {
|
|
8847
|
+
try {
|
|
8848
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
8849
|
+
} catch {
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8057
8852
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
8058
8853
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
8059
8854
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -8585,11 +9380,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
8585
9380
|
}
|
|
8586
9381
|
}
|
|
8587
9382
|
function resolveExeSession() {
|
|
9383
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
9384
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
9385
|
+
if (fromEnv) return fromEnv;
|
|
9386
|
+
}
|
|
8588
9387
|
const mySession = getMySession();
|
|
8589
9388
|
if (!mySession) {
|
|
8590
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
8591
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
8592
|
-
}
|
|
8593
9389
|
return null;
|
|
8594
9390
|
}
|
|
8595
9391
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -8604,6 +9400,10 @@ function resolveExeSession() {
|
|
|
8604
9400
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
8605
9401
|
`
|
|
8606
9402
|
);
|
|
9403
|
+
try {
|
|
9404
|
+
registerParentExe(key, fromSessionName);
|
|
9405
|
+
} catch {
|
|
9406
|
+
}
|
|
8607
9407
|
candidate = fromSessionName;
|
|
8608
9408
|
} else {
|
|
8609
9409
|
candidate = fromCache;
|