@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
|
@@ -395,11 +395,168 @@ var init_config = __esm({
|
|
|
395
395
|
}
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
+
// src/lib/runtime-table.ts
|
|
399
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
400
|
+
var init_runtime_table = __esm({
|
|
401
|
+
"src/lib/runtime-table.ts"() {
|
|
402
|
+
"use strict";
|
|
403
|
+
RUNTIME_TABLE = {
|
|
404
|
+
codex: {
|
|
405
|
+
binary: "codex",
|
|
406
|
+
launchMode: "interactive",
|
|
407
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
408
|
+
inlineFlag: "--no-alt-screen",
|
|
409
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
410
|
+
defaultModel: "gpt-5.5"
|
|
411
|
+
},
|
|
412
|
+
opencode: {
|
|
413
|
+
binary: "opencode",
|
|
414
|
+
launchMode: "exec",
|
|
415
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
416
|
+
inlineFlag: "",
|
|
417
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
418
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
DEFAULT_RUNTIME = "claude";
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// src/lib/agent-config.ts
|
|
426
|
+
var agent_config_exports = {};
|
|
427
|
+
__export(agent_config_exports, {
|
|
428
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
429
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
430
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
431
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
432
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
433
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
434
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
435
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
436
|
+
setAgentMcps: () => setAgentMcps,
|
|
437
|
+
setAgentRuntime: () => setAgentRuntime
|
|
438
|
+
});
|
|
439
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
440
|
+
import path2 from "path";
|
|
441
|
+
function loadAgentConfig() {
|
|
442
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
443
|
+
try {
|
|
444
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
445
|
+
} catch {
|
|
446
|
+
return {};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function saveAgentConfig(config) {
|
|
450
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
451
|
+
ensurePrivateDirSync(dir);
|
|
452
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
453
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
454
|
+
}
|
|
455
|
+
function getAgentRuntime(agentId) {
|
|
456
|
+
const config = loadAgentConfig();
|
|
457
|
+
const entry = config[agentId];
|
|
458
|
+
if (entry) return entry;
|
|
459
|
+
const orgDefault = config["default"];
|
|
460
|
+
if (orgDefault) return orgDefault;
|
|
461
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
462
|
+
}
|
|
463
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
464
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
465
|
+
if (!knownModels) {
|
|
466
|
+
return {
|
|
467
|
+
ok: false,
|
|
468
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (!knownModels.includes(model)) {
|
|
472
|
+
return {
|
|
473
|
+
ok: false,
|
|
474
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const config = loadAgentConfig();
|
|
478
|
+
const existing = config[agentId];
|
|
479
|
+
const entry = { runtime, model };
|
|
480
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
481
|
+
if (mcps !== void 0) {
|
|
482
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
483
|
+
} else if (existing?.mcps) {
|
|
484
|
+
entry.mcps = existing.mcps;
|
|
485
|
+
}
|
|
486
|
+
config[agentId] = entry;
|
|
487
|
+
saveAgentConfig(config);
|
|
488
|
+
return { ok: true };
|
|
489
|
+
}
|
|
490
|
+
function setAgentMcps(agentId, mcps) {
|
|
491
|
+
const config = loadAgentConfig();
|
|
492
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
493
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
494
|
+
config[agentId] = existing;
|
|
495
|
+
saveAgentConfig(config);
|
|
496
|
+
return { ok: true };
|
|
497
|
+
}
|
|
498
|
+
function clearAgentRuntime(agentId) {
|
|
499
|
+
const config = loadAgentConfig();
|
|
500
|
+
delete config[agentId];
|
|
501
|
+
saveAgentConfig(config);
|
|
502
|
+
}
|
|
503
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
504
|
+
var init_agent_config = __esm({
|
|
505
|
+
"src/lib/agent-config.ts"() {
|
|
506
|
+
"use strict";
|
|
507
|
+
init_config();
|
|
508
|
+
init_runtime_table();
|
|
509
|
+
init_secure_files();
|
|
510
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
511
|
+
KNOWN_RUNTIMES = {
|
|
512
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
513
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
514
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
515
|
+
};
|
|
516
|
+
RUNTIME_LABELS = {
|
|
517
|
+
claude: "Claude Code (Anthropic)",
|
|
518
|
+
codex: "Codex (OpenAI)",
|
|
519
|
+
opencode: "OpenCode (open source)"
|
|
520
|
+
};
|
|
521
|
+
DEFAULT_MODELS = {
|
|
522
|
+
claude: "claude-opus-4.6",
|
|
523
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
524
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
398
529
|
// src/lib/employees.ts
|
|
530
|
+
var employees_exports = {};
|
|
531
|
+
__export(employees_exports, {
|
|
532
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
533
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
534
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
535
|
+
addEmployee: () => addEmployee,
|
|
536
|
+
baseAgentName: () => baseAgentName,
|
|
537
|
+
canCoordinate: () => canCoordinate,
|
|
538
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
539
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
540
|
+
getEmployee: () => getEmployee,
|
|
541
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
542
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
543
|
+
hasRole: () => hasRole,
|
|
544
|
+
hireEmployee: () => hireEmployee,
|
|
545
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
546
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
547
|
+
isMultiInstance: () => isMultiInstance,
|
|
548
|
+
loadEmployees: () => loadEmployees,
|
|
549
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
550
|
+
normalizeRole: () => normalizeRole,
|
|
551
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
552
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
553
|
+
saveEmployees: () => saveEmployees,
|
|
554
|
+
validateEmployeeName: () => validateEmployeeName
|
|
555
|
+
});
|
|
399
556
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
400
|
-
import { existsSync as
|
|
557
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
401
558
|
import { execSync } from "child_process";
|
|
402
|
-
import
|
|
559
|
+
import path3 from "path";
|
|
403
560
|
import os2 from "os";
|
|
404
561
|
function normalizeRole(role) {
|
|
405
562
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -417,8 +574,26 @@ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
|
|
|
417
574
|
if (!agentName2) return false;
|
|
418
575
|
return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
419
576
|
}
|
|
577
|
+
function canCoordinate(agentName2, agentRole, employees = loadEmployeesSync()) {
|
|
578
|
+
return agentName2 === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName2, employees);
|
|
579
|
+
}
|
|
580
|
+
function validateEmployeeName(name) {
|
|
581
|
+
if (!name) {
|
|
582
|
+
return { valid: false, error: "Name is required" };
|
|
583
|
+
}
|
|
584
|
+
if (name.length > 32) {
|
|
585
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
586
|
+
}
|
|
587
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
588
|
+
return {
|
|
589
|
+
valid: false,
|
|
590
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
return { valid: true };
|
|
594
|
+
}
|
|
420
595
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
421
|
-
if (!
|
|
596
|
+
if (!existsSync4(employeesPath)) {
|
|
422
597
|
return [];
|
|
423
598
|
}
|
|
424
599
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -428,10 +603,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
428
603
|
return [];
|
|
429
604
|
}
|
|
430
605
|
}
|
|
606
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
607
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
608
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
609
|
+
}
|
|
431
610
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
432
|
-
if (!
|
|
611
|
+
if (!existsSync4(employeesPath)) return [];
|
|
433
612
|
try {
|
|
434
|
-
return JSON.parse(
|
|
613
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
435
614
|
} catch {
|
|
436
615
|
return [];
|
|
437
616
|
}
|
|
@@ -439,6 +618,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
439
618
|
function getEmployee(employees, name) {
|
|
440
619
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
441
620
|
}
|
|
621
|
+
function getEmployeeByRole(employees, role) {
|
|
622
|
+
const lower = role.toLowerCase();
|
|
623
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
624
|
+
}
|
|
625
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
626
|
+
const lower = role.toLowerCase();
|
|
627
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
628
|
+
}
|
|
629
|
+
function hasRole(agentName2, role) {
|
|
630
|
+
const employees = loadEmployeesSync();
|
|
631
|
+
const emp = getEmployee(employees, agentName2);
|
|
632
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
633
|
+
}
|
|
442
634
|
function baseAgentName(name, employees) {
|
|
443
635
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
444
636
|
if (!match) return name;
|
|
@@ -453,22 +645,147 @@ function isMultiInstance(agentName2, employees) {
|
|
|
453
645
|
if (!emp) return false;
|
|
454
646
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
455
647
|
}
|
|
456
|
-
|
|
648
|
+
function addEmployee(employees, employee) {
|
|
649
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
650
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
651
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
652
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
653
|
+
}
|
|
654
|
+
return [...employees, normalized];
|
|
655
|
+
}
|
|
656
|
+
function appendToCoordinatorTeam(employee) {
|
|
657
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
658
|
+
if (!coordinator) return;
|
|
659
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
660
|
+
if (!existsSync4(idPath)) return;
|
|
661
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
662
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
663
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
664
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
665
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
666
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
667
|
+
const entry = `
|
|
668
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
669
|
+
`;
|
|
670
|
+
let updated;
|
|
671
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
672
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
673
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
674
|
+
} else {
|
|
675
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
676
|
+
}
|
|
677
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
678
|
+
}
|
|
679
|
+
function capitalize(s) {
|
|
680
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
681
|
+
}
|
|
682
|
+
async function hireEmployee(employee) {
|
|
683
|
+
const employees = await loadEmployees();
|
|
684
|
+
const updated = addEmployee(employees, employee);
|
|
685
|
+
await saveEmployees(updated);
|
|
686
|
+
try {
|
|
687
|
+
appendToCoordinatorTeam(employee);
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
692
|
+
const config = loadAgentConfig2();
|
|
693
|
+
const name = employee.name.toLowerCase();
|
|
694
|
+
if (!config[name] && config["default"]) {
|
|
695
|
+
config[name] = { ...config["default"] };
|
|
696
|
+
saveAgentConfig2(config);
|
|
697
|
+
}
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
return updated;
|
|
701
|
+
}
|
|
702
|
+
async function normalizeRosterCase(rosterPath) {
|
|
703
|
+
const employees = await loadEmployees(rosterPath);
|
|
704
|
+
let changed = false;
|
|
705
|
+
for (const emp of employees) {
|
|
706
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
707
|
+
const oldName = emp.name;
|
|
708
|
+
emp.name = emp.name.toLowerCase();
|
|
709
|
+
changed = true;
|
|
710
|
+
try {
|
|
711
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
712
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
713
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
714
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
715
|
+
renameSync2(oldPath, newPath);
|
|
716
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
717
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
718
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
719
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
720
|
+
unlinkSync(oldPath);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch {
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (changed) {
|
|
728
|
+
await saveEmployees(employees, rosterPath);
|
|
729
|
+
}
|
|
730
|
+
return changed;
|
|
731
|
+
}
|
|
732
|
+
function findExeBin() {
|
|
733
|
+
try {
|
|
734
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function registerBinSymlinks(name) {
|
|
740
|
+
const created = [];
|
|
741
|
+
const skipped = [];
|
|
742
|
+
const errors = [];
|
|
743
|
+
const exeBinPath = findExeBin();
|
|
744
|
+
if (!exeBinPath) {
|
|
745
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
746
|
+
return { created, skipped, errors };
|
|
747
|
+
}
|
|
748
|
+
const binDir = path3.dirname(exeBinPath);
|
|
749
|
+
let target;
|
|
750
|
+
try {
|
|
751
|
+
target = readlinkSync(exeBinPath);
|
|
752
|
+
} catch {
|
|
753
|
+
errors.push("Could not read 'exe' symlink");
|
|
754
|
+
return { created, skipped, errors };
|
|
755
|
+
}
|
|
756
|
+
for (const suffix of ["", "-opencode"]) {
|
|
757
|
+
const linkName = `${name}${suffix}`;
|
|
758
|
+
const linkPath = path3.join(binDir, linkName);
|
|
759
|
+
if (existsSync4(linkPath)) {
|
|
760
|
+
skipped.push(linkName);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
symlinkSync(target, linkPath);
|
|
765
|
+
created.push(linkName);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return { created, skipped, errors };
|
|
771
|
+
}
|
|
772
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
457
773
|
var init_employees = __esm({
|
|
458
774
|
"src/lib/employees.ts"() {
|
|
459
775
|
"use strict";
|
|
460
776
|
init_config();
|
|
461
|
-
EMPLOYEES_PATH =
|
|
777
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
462
778
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
463
779
|
COORDINATOR_ROLE = "COO";
|
|
464
780
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
465
|
-
IDENTITY_DIR =
|
|
781
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
782
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
466
783
|
}
|
|
467
784
|
});
|
|
468
785
|
|
|
469
786
|
// src/lib/database-adapter.ts
|
|
470
787
|
import os3 from "os";
|
|
471
|
-
import
|
|
788
|
+
import path4 from "path";
|
|
472
789
|
import { createRequire } from "module";
|
|
473
790
|
import { pathToFileURL } from "url";
|
|
474
791
|
function quotedIdentifier(identifier) {
|
|
@@ -779,8 +1096,8 @@ async function loadPrismaClient() {
|
|
|
779
1096
|
}
|
|
780
1097
|
return new PrismaClient2();
|
|
781
1098
|
}
|
|
782
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
783
|
-
const requireFromExeDb = createRequire(
|
|
1099
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1100
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
784
1101
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
785
1102
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
786
1103
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1052,8 +1369,8 @@ var init_database_adapter = __esm({
|
|
|
1052
1369
|
|
|
1053
1370
|
// src/lib/daemon-auth.ts
|
|
1054
1371
|
import crypto from "crypto";
|
|
1055
|
-
import
|
|
1056
|
-
import { existsSync as
|
|
1372
|
+
import path5 from "path";
|
|
1373
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1057
1374
|
function normalizeToken(token) {
|
|
1058
1375
|
if (!token) return null;
|
|
1059
1376
|
const trimmed = token.trim();
|
|
@@ -1061,8 +1378,8 @@ function normalizeToken(token) {
|
|
|
1061
1378
|
}
|
|
1062
1379
|
function readDaemonToken() {
|
|
1063
1380
|
try {
|
|
1064
|
-
if (!
|
|
1065
|
-
return normalizeToken(
|
|
1381
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
1382
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
1066
1383
|
} catch {
|
|
1067
1384
|
return null;
|
|
1068
1385
|
}
|
|
@@ -1072,7 +1389,7 @@ function ensureDaemonToken(seed) {
|
|
|
1072
1389
|
if (existing) return existing;
|
|
1073
1390
|
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1074
1391
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1075
|
-
|
|
1392
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
1076
1393
|
`, "utf8");
|
|
1077
1394
|
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1078
1395
|
return token;
|
|
@@ -1083,7 +1400,7 @@ var init_daemon_auth = __esm({
|
|
|
1083
1400
|
"use strict";
|
|
1084
1401
|
init_config();
|
|
1085
1402
|
init_secure_files();
|
|
1086
|
-
DAEMON_TOKEN_PATH =
|
|
1403
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
1087
1404
|
}
|
|
1088
1405
|
});
|
|
1089
1406
|
|
|
@@ -1092,8 +1409,8 @@ import net from "net";
|
|
|
1092
1409
|
import os4 from "os";
|
|
1093
1410
|
import { spawn, execSync as execSync2 } from "child_process";
|
|
1094
1411
|
import { randomUUID } from "crypto";
|
|
1095
|
-
import { existsSync as
|
|
1096
|
-
import
|
|
1412
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1413
|
+
import path6 from "path";
|
|
1097
1414
|
import { fileURLToPath } from "url";
|
|
1098
1415
|
function handleData(chunk) {
|
|
1099
1416
|
_buffer += chunk.toString();
|
|
@@ -1129,9 +1446,9 @@ function isZombie(pid) {
|
|
|
1129
1446
|
}
|
|
1130
1447
|
}
|
|
1131
1448
|
function cleanupStaleFiles() {
|
|
1132
|
-
if (
|
|
1449
|
+
if (existsSync6(PID_PATH)) {
|
|
1133
1450
|
try {
|
|
1134
|
-
const pid = parseInt(
|
|
1451
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1135
1452
|
if (pid > 0) {
|
|
1136
1453
|
try {
|
|
1137
1454
|
process.kill(pid, 0);
|
|
@@ -1156,11 +1473,11 @@ function cleanupStaleFiles() {
|
|
|
1156
1473
|
}
|
|
1157
1474
|
}
|
|
1158
1475
|
function findPackageRoot() {
|
|
1159
|
-
let dir =
|
|
1160
|
-
const { root } =
|
|
1476
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1477
|
+
const { root } = path6.parse(dir);
|
|
1161
1478
|
while (dir !== root) {
|
|
1162
|
-
if (
|
|
1163
|
-
dir =
|
|
1479
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
1480
|
+
dir = path6.dirname(dir);
|
|
1164
1481
|
}
|
|
1165
1482
|
return null;
|
|
1166
1483
|
}
|
|
@@ -1178,8 +1495,8 @@ function spawnDaemon() {
|
|
|
1178
1495
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1179
1496
|
return;
|
|
1180
1497
|
}
|
|
1181
|
-
const daemonPath =
|
|
1182
|
-
if (!
|
|
1498
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1499
|
+
if (!existsSync6(daemonPath)) {
|
|
1183
1500
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1184
1501
|
`);
|
|
1185
1502
|
return;
|
|
@@ -1188,7 +1505,7 @@ function spawnDaemon() {
|
|
|
1188
1505
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1189
1506
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1190
1507
|
`);
|
|
1191
|
-
const logPath =
|
|
1508
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
1192
1509
|
let stderrFd = "ignore";
|
|
1193
1510
|
try {
|
|
1194
1511
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1353,9 +1670,9 @@ function killAndRespawnDaemon() {
|
|
|
1353
1670
|
}
|
|
1354
1671
|
try {
|
|
1355
1672
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1356
|
-
if (
|
|
1673
|
+
if (existsSync6(PID_PATH)) {
|
|
1357
1674
|
try {
|
|
1358
|
-
const pid = parseInt(
|
|
1675
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1359
1676
|
if (pid > 0) {
|
|
1360
1677
|
try {
|
|
1361
1678
|
process.kill(pid, "SIGKILL");
|
|
@@ -1481,9 +1798,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1481
1798
|
"use strict";
|
|
1482
1799
|
init_config();
|
|
1483
1800
|
init_daemon_auth();
|
|
1484
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1485
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1486
|
-
SPAWN_LOCK_PATH =
|
|
1801
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1802
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1803
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1487
1804
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1488
1805
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1489
1806
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -1716,7 +2033,7 @@ __export(database_exports, {
|
|
|
1716
2033
|
isInitialized: () => isInitialized,
|
|
1717
2034
|
setExternalClient: () => setExternalClient
|
|
1718
2035
|
});
|
|
1719
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
2036
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1720
2037
|
import { createClient } from "@libsql/client";
|
|
1721
2038
|
import { homedir } from "os";
|
|
1722
2039
|
import { join } from "path";
|
|
@@ -1769,11 +2086,11 @@ function releaseDbLock() {
|
|
|
1769
2086
|
}
|
|
1770
2087
|
async function initDatabase(config) {
|
|
1771
2088
|
acquireDbLock();
|
|
1772
|
-
if (
|
|
2089
|
+
if (existsSync7(config.dbPath)) {
|
|
1773
2090
|
const dbStat = statSync2(config.dbPath);
|
|
1774
2091
|
if (dbStat.size === 0) {
|
|
1775
2092
|
const walPath = config.dbPath + "-wal";
|
|
1776
|
-
if (
|
|
2093
|
+
if (existsSync7(walPath) && statSync2(walPath).size > 0) {
|
|
1777
2094
|
const backupPath = config.dbPath + ".zeroed-" + Date.now();
|
|
1778
2095
|
copyFileSync(config.dbPath, backupPath);
|
|
1779
2096
|
unlinkSync3(config.dbPath);
|
|
@@ -2069,6 +2386,13 @@ async function ensureSchema() {
|
|
|
2069
2386
|
} catch (e) {
|
|
2070
2387
|
logCatchDebug("migration", e);
|
|
2071
2388
|
}
|
|
2389
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2390
|
+
try {
|
|
2391
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2392
|
+
} catch (e) {
|
|
2393
|
+
logCatchDebug("migration", e);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2072
2396
|
try {
|
|
2073
2397
|
await client.execute({
|
|
2074
2398
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3285,6 +3609,22 @@ async function ensureSchema() {
|
|
|
3285
3609
|
} catch (e) {
|
|
3286
3610
|
logCatchDebug("migration", e);
|
|
3287
3611
|
}
|
|
3612
|
+
try {
|
|
3613
|
+
await client.execute({
|
|
3614
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3615
|
+
args: []
|
|
3616
|
+
});
|
|
3617
|
+
} catch (e) {
|
|
3618
|
+
logCatchDebug("migration", e);
|
|
3619
|
+
}
|
|
3620
|
+
try {
|
|
3621
|
+
await client.execute({
|
|
3622
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3623
|
+
args: []
|
|
3624
|
+
});
|
|
3625
|
+
} catch (e) {
|
|
3626
|
+
logCatchDebug("migration", e);
|
|
3627
|
+
}
|
|
3288
3628
|
}
|
|
3289
3629
|
async function disposeDatabase() {
|
|
3290
3630
|
if (_walCheckpointTimer) {
|
|
@@ -3337,15 +3677,15 @@ var init_database = __esm({
|
|
|
3337
3677
|
|
|
3338
3678
|
// src/lib/keychain.ts
|
|
3339
3679
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3340
|
-
import { existsSync as
|
|
3680
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3341
3681
|
import { execSync as execSync3 } from "child_process";
|
|
3342
|
-
import
|
|
3682
|
+
import path7 from "path";
|
|
3343
3683
|
import os5 from "os";
|
|
3344
3684
|
function getKeyDir() {
|
|
3345
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3685
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
3346
3686
|
}
|
|
3347
3687
|
function getKeyPath() {
|
|
3348
|
-
return
|
|
3688
|
+
return path7.join(getKeyDir(), "master.key");
|
|
3349
3689
|
}
|
|
3350
3690
|
function nativeKeychainAllowed() {
|
|
3351
3691
|
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
@@ -3376,7 +3716,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
|
3376
3716
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3377
3717
|
if (uid === 0) return true;
|
|
3378
3718
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3379
|
-
return Boolean(exeOsDir &&
|
|
3719
|
+
return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
|
|
3380
3720
|
} catch {
|
|
3381
3721
|
return false;
|
|
3382
3722
|
}
|
|
@@ -3573,7 +3913,7 @@ async function getMasterKey() {
|
|
|
3573
3913
|
}
|
|
3574
3914
|
}
|
|
3575
3915
|
const keyPath = getKeyPath();
|
|
3576
|
-
if (!
|
|
3916
|
+
if (!existsSync8(keyPath)) {
|
|
3577
3917
|
process.stderr.write(
|
|
3578
3918
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3579
3919
|
`
|
|
@@ -3907,14 +4247,14 @@ __export(shard_manager_exports, {
|
|
|
3907
4247
|
listShards: () => listShards,
|
|
3908
4248
|
shardExists: () => shardExists
|
|
3909
4249
|
});
|
|
3910
|
-
import
|
|
3911
|
-
import { existsSync as
|
|
4250
|
+
import path8 from "path";
|
|
4251
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
|
|
3912
4252
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3913
4253
|
function initShardManager(encryptionKey) {
|
|
3914
4254
|
_encryptionKey = encryptionKey;
|
|
3915
4255
|
_keyValidated = false;
|
|
3916
4256
|
_keyValidationPromise = null;
|
|
3917
|
-
if (!
|
|
4257
|
+
if (!existsSync9(SHARDS_DIR)) {
|
|
3918
4258
|
mkdirSync3(SHARDS_DIR, { recursive: true });
|
|
3919
4259
|
}
|
|
3920
4260
|
const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
|
|
@@ -3935,7 +4275,7 @@ async function validateEncryptionKey() {
|
|
|
3935
4275
|
return true;
|
|
3936
4276
|
}
|
|
3937
4277
|
for (const shardFile of existingShards.slice(0, 3)) {
|
|
3938
|
-
const dbPath =
|
|
4278
|
+
const dbPath = path8.join(SHARDS_DIR, shardFile);
|
|
3939
4279
|
const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
|
|
3940
4280
|
try {
|
|
3941
4281
|
await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
|
|
@@ -3978,7 +4318,7 @@ function getShardClient(projectName) {
|
|
|
3978
4318
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3979
4319
|
evictLRU();
|
|
3980
4320
|
}
|
|
3981
|
-
const dbPath =
|
|
4321
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
3982
4322
|
const client = createClient2({
|
|
3983
4323
|
url: `file:${dbPath}`,
|
|
3984
4324
|
encryptionKey: _encryptionKey
|
|
@@ -3989,13 +4329,13 @@ function getShardClient(projectName) {
|
|
|
3989
4329
|
}
|
|
3990
4330
|
function shardExists(projectName) {
|
|
3991
4331
|
const safeName = safeShardName(projectName);
|
|
3992
|
-
return
|
|
4332
|
+
return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
3993
4333
|
}
|
|
3994
4334
|
function safeShardName(projectName) {
|
|
3995
4335
|
return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3996
4336
|
}
|
|
3997
4337
|
function listShards() {
|
|
3998
|
-
if (!
|
|
4338
|
+
if (!existsSync9(SHARDS_DIR)) return [];
|
|
3999
4339
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4000
4340
|
}
|
|
4001
4341
|
async function auditShardHealth(options = {}) {
|
|
@@ -4007,7 +4347,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4007
4347
|
const names = listShards();
|
|
4008
4348
|
const shards = [];
|
|
4009
4349
|
for (const name of names) {
|
|
4010
|
-
const dbPath =
|
|
4350
|
+
const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
|
|
4011
4351
|
const stat = statSync4(dbPath);
|
|
4012
4352
|
const item = {
|
|
4013
4353
|
name,
|
|
@@ -4042,7 +4382,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4042
4382
|
_shards.delete(name);
|
|
4043
4383
|
_shardLastAccess.delete(name);
|
|
4044
4384
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4045
|
-
const archivedPath =
|
|
4385
|
+
const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
4046
4386
|
renameSync3(dbPath, archivedPath);
|
|
4047
4387
|
item.archivedPath = archivedPath;
|
|
4048
4388
|
}
|
|
@@ -4270,11 +4610,11 @@ async function getReadyShardClient(projectName) {
|
|
|
4270
4610
|
client.close();
|
|
4271
4611
|
_shards.delete(safeName);
|
|
4272
4612
|
_shardLastAccess.delete(safeName);
|
|
4273
|
-
const dbPath =
|
|
4274
|
-
if (
|
|
4613
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4614
|
+
if (existsSync9(dbPath)) {
|
|
4275
4615
|
const stat = statSync4(dbPath);
|
|
4276
4616
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4277
|
-
const archivedPath =
|
|
4617
|
+
const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
4278
4618
|
renameSync3(dbPath, archivedPath);
|
|
4279
4619
|
process.stderr.write(
|
|
4280
4620
|
`[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
|
|
@@ -4342,7 +4682,7 @@ var init_shard_manager = __esm({
|
|
|
4342
4682
|
"src/lib/shard-manager.ts"() {
|
|
4343
4683
|
"use strict";
|
|
4344
4684
|
init_config();
|
|
4345
|
-
SHARDS_DIR =
|
|
4685
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
4346
4686
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
4347
4687
|
MAX_OPEN_SHARDS = 10;
|
|
4348
4688
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -4408,11 +4748,17 @@ var init_platform_procedures = __esm({
|
|
|
4408
4748
|
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
4409
4749
|
},
|
|
4410
4750
|
{
|
|
4411
|
-
title: "
|
|
4751
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4412
4752
|
domain: "workflow",
|
|
4413
4753
|
priority: "p1",
|
|
4414
4754
|
content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
|
|
4415
4755
|
},
|
|
4756
|
+
{
|
|
4757
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
4758
|
+
domain: "identity",
|
|
4759
|
+
priority: "p0",
|
|
4760
|
+
content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
|
|
4761
|
+
},
|
|
4416
4762
|
{
|
|
4417
4763
|
title: "Single dispatch path \u2014 create_task only",
|
|
4418
4764
|
domain: "workflow",
|
|
@@ -4446,6 +4792,12 @@ var init_platform_procedures = __esm({
|
|
|
4446
4792
|
priority: "p0",
|
|
4447
4793
|
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
4448
4794
|
},
|
|
4795
|
+
{
|
|
4796
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
4797
|
+
domain: "security",
|
|
4798
|
+
priority: "p0",
|
|
4799
|
+
content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
|
|
4800
|
+
},
|
|
4449
4801
|
{
|
|
4450
4802
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4451
4803
|
domain: "support",
|
|
@@ -4597,7 +4949,7 @@ var init_platform_procedures = __esm({
|
|
|
4597
4949
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
4598
4950
|
domain: "tool-use",
|
|
4599
4951
|
priority: "p0",
|
|
4600
|
-
content: 'exe-os MCP tools
|
|
4952
|
+
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.'
|
|
4601
4953
|
},
|
|
4602
4954
|
{
|
|
4603
4955
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -4731,10 +5083,24 @@ function stableId(memoryId, type, content) {
|
|
|
4731
5083
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
4732
5084
|
}
|
|
4733
5085
|
function cleanText(text) {
|
|
4734
|
-
|
|
5086
|
+
let cleaned = text.replace(
|
|
5087
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5088
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5089
|
+
);
|
|
5090
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5091
|
+
return cleaned;
|
|
4735
5092
|
}
|
|
4736
|
-
function
|
|
4737
|
-
|
|
5093
|
+
function splitSegments(text) {
|
|
5094
|
+
const cleaned = cleanText(text);
|
|
5095
|
+
const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
|
|
5096
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5097
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5098
|
+
if (lines.length > 0) return lines;
|
|
5099
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5100
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
return segments;
|
|
4738
5104
|
}
|
|
4739
5105
|
function inferCardType(sentence, toolName) {
|
|
4740
5106
|
const lower = sentence.toLowerCase();
|
|
@@ -4766,12 +5132,12 @@ function predicateFor(type) {
|
|
|
4766
5132
|
}
|
|
4767
5133
|
}
|
|
4768
5134
|
function extractMemoryCards(row) {
|
|
4769
|
-
const
|
|
5135
|
+
const segments = splitSegments(row.raw_text);
|
|
4770
5136
|
const cards = [];
|
|
4771
|
-
for (const sentence of
|
|
5137
|
+
for (const sentence of segments) {
|
|
4772
5138
|
const type = inferCardType(sentence, row.tool_name);
|
|
4773
5139
|
const subject = extractSubject(sentence, row.agent_id);
|
|
4774
|
-
const content = sentence.length >
|
|
5140
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
4775
5141
|
cards.push({
|
|
4776
5142
|
id: stableId(row.id, type, content),
|
|
4777
5143
|
memory_id: row.id,
|
|
@@ -4867,13 +5233,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
4867
5233
|
last_accessed: String(row.timestamp)
|
|
4868
5234
|
}));
|
|
4869
5235
|
}
|
|
4870
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5236
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
4871
5237
|
var init_memory_cards = __esm({
|
|
4872
5238
|
"src/lib/memory-cards.ts"() {
|
|
4873
5239
|
"use strict";
|
|
4874
5240
|
init_database();
|
|
4875
|
-
MAX_CARDS_PER_MEMORY =
|
|
4876
|
-
|
|
5241
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5242
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5243
|
+
MIN_SEGMENT_CHARS = 20;
|
|
4877
5244
|
}
|
|
4878
5245
|
});
|
|
4879
5246
|
|
|
@@ -5809,12 +6176,12 @@ var init_store = __esm({
|
|
|
5809
6176
|
});
|
|
5810
6177
|
|
|
5811
6178
|
// src/lib/session-registry.ts
|
|
5812
|
-
import { readFileSync as
|
|
5813
|
-
import
|
|
6179
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
|
|
6180
|
+
import path9 from "path";
|
|
5814
6181
|
import os6 from "os";
|
|
5815
6182
|
function registerSession(entry) {
|
|
5816
|
-
const dir =
|
|
5817
|
-
if (!
|
|
6183
|
+
const dir = path9.dirname(REGISTRY_PATH);
|
|
6184
|
+
if (!existsSync10(dir)) {
|
|
5818
6185
|
mkdirSync4(dir, { recursive: true });
|
|
5819
6186
|
}
|
|
5820
6187
|
const sessions = listSessions();
|
|
@@ -5824,11 +6191,11 @@ function registerSession(entry) {
|
|
|
5824
6191
|
} else {
|
|
5825
6192
|
sessions.push(entry);
|
|
5826
6193
|
}
|
|
5827
|
-
|
|
6194
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
5828
6195
|
}
|
|
5829
6196
|
function listSessions() {
|
|
5830
6197
|
try {
|
|
5831
|
-
const raw =
|
|
6198
|
+
const raw = readFileSync6(REGISTRY_PATH, "utf8");
|
|
5832
6199
|
return JSON.parse(raw);
|
|
5833
6200
|
} catch {
|
|
5834
6201
|
return [];
|
|
@@ -5838,7 +6205,7 @@ var REGISTRY_PATH;
|
|
|
5838
6205
|
var init_session_registry = __esm({
|
|
5839
6206
|
"src/lib/session-registry.ts"() {
|
|
5840
6207
|
"use strict";
|
|
5841
|
-
REGISTRY_PATH =
|
|
6208
|
+
REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
5842
6209
|
}
|
|
5843
6210
|
});
|
|
5844
6211
|
|
|
@@ -6100,68 +6467,6 @@ var init_provider_table = __esm({
|
|
|
6100
6467
|
}
|
|
6101
6468
|
});
|
|
6102
6469
|
|
|
6103
|
-
// src/lib/runtime-table.ts
|
|
6104
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
6105
|
-
var init_runtime_table = __esm({
|
|
6106
|
-
"src/lib/runtime-table.ts"() {
|
|
6107
|
-
"use strict";
|
|
6108
|
-
RUNTIME_TABLE = {
|
|
6109
|
-
codex: {
|
|
6110
|
-
binary: "codex",
|
|
6111
|
-
launchMode: "interactive",
|
|
6112
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
6113
|
-
inlineFlag: "--no-alt-screen",
|
|
6114
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
6115
|
-
defaultModel: "gpt-5.5"
|
|
6116
|
-
},
|
|
6117
|
-
opencode: {
|
|
6118
|
-
binary: "opencode",
|
|
6119
|
-
launchMode: "exec",
|
|
6120
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
6121
|
-
inlineFlag: "",
|
|
6122
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
6123
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
6124
|
-
}
|
|
6125
|
-
};
|
|
6126
|
-
DEFAULT_RUNTIME = "claude";
|
|
6127
|
-
}
|
|
6128
|
-
});
|
|
6129
|
-
|
|
6130
|
-
// src/lib/agent-config.ts
|
|
6131
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
|
|
6132
|
-
import path9 from "path";
|
|
6133
|
-
function loadAgentConfig() {
|
|
6134
|
-
if (!existsSync10(AGENT_CONFIG_PATH)) return {};
|
|
6135
|
-
try {
|
|
6136
|
-
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
6137
|
-
} catch {
|
|
6138
|
-
return {};
|
|
6139
|
-
}
|
|
6140
|
-
}
|
|
6141
|
-
function getAgentRuntime(agentId) {
|
|
6142
|
-
const config = loadAgentConfig();
|
|
6143
|
-
const entry = config[agentId];
|
|
6144
|
-
if (entry) return entry;
|
|
6145
|
-
const orgDefault = config["default"];
|
|
6146
|
-
if (orgDefault) return orgDefault;
|
|
6147
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
6148
|
-
}
|
|
6149
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
6150
|
-
var init_agent_config = __esm({
|
|
6151
|
-
"src/lib/agent-config.ts"() {
|
|
6152
|
-
"use strict";
|
|
6153
|
-
init_config();
|
|
6154
|
-
init_runtime_table();
|
|
6155
|
-
init_secure_files();
|
|
6156
|
-
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
6157
|
-
DEFAULT_MODELS = {
|
|
6158
|
-
claude: "claude-opus-4.6",
|
|
6159
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
6160
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
6161
|
-
};
|
|
6162
|
-
}
|
|
6163
|
-
});
|
|
6164
|
-
|
|
6165
6470
|
// src/lib/intercom-queue.ts
|
|
6166
6471
|
var intercom_queue_exports = {};
|
|
6167
6472
|
__export(intercom_queue_exports, {
|
|
@@ -6289,6 +6594,23 @@ var init_intercom_queue = __esm({
|
|
|
6289
6594
|
});
|
|
6290
6595
|
|
|
6291
6596
|
// src/lib/license.ts
|
|
6597
|
+
var license_exports = {};
|
|
6598
|
+
__export(license_exports, {
|
|
6599
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
6600
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
6601
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
6602
|
+
checkLicense: () => checkLicense,
|
|
6603
|
+
getCachedLicense: () => getCachedLicense,
|
|
6604
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
6605
|
+
loadDeviceId: () => loadDeviceId,
|
|
6606
|
+
loadLicense: () => loadLicense,
|
|
6607
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
6608
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
6609
|
+
saveLicense: () => saveLicense,
|
|
6610
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
6611
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
6612
|
+
validateLicense: () => validateLicense
|
|
6613
|
+
});
|
|
6292
6614
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
|
|
6293
6615
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6294
6616
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -6296,7 +6618,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
6296
6618
|
import os8 from "os";
|
|
6297
6619
|
import path11 from "path";
|
|
6298
6620
|
import { jwtVerify, importSPKI } from "jose";
|
|
6299
|
-
|
|
6621
|
+
async function fetchRetry(url, init) {
|
|
6622
|
+
try {
|
|
6623
|
+
return await fetch(url, init);
|
|
6624
|
+
} catch {
|
|
6625
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
6626
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6629
|
+
function loadDeviceId() {
|
|
6630
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
6631
|
+
try {
|
|
6632
|
+
if (existsSync12(deviceJsonPath)) {
|
|
6633
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
6634
|
+
if (data.deviceId) return data.deviceId;
|
|
6635
|
+
}
|
|
6636
|
+
} catch {
|
|
6637
|
+
}
|
|
6638
|
+
try {
|
|
6639
|
+
if (existsSync12(DEVICE_ID_PATH)) {
|
|
6640
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
6641
|
+
if (id2) return id2;
|
|
6642
|
+
}
|
|
6643
|
+
} catch {
|
|
6644
|
+
}
|
|
6645
|
+
const id = randomUUID3();
|
|
6646
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
6647
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
6648
|
+
return id;
|
|
6649
|
+
}
|
|
6650
|
+
function loadLicense() {
|
|
6651
|
+
try {
|
|
6652
|
+
if (!existsSync12(LICENSE_PATH)) return null;
|
|
6653
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
6654
|
+
} catch {
|
|
6655
|
+
return null;
|
|
6656
|
+
}
|
|
6657
|
+
}
|
|
6658
|
+
function saveLicense(apiKey) {
|
|
6659
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
6660
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
6661
|
+
}
|
|
6662
|
+
async function verifyLicenseJwt(token) {
|
|
6663
|
+
try {
|
|
6664
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
6665
|
+
const { payload } = await jwtVerify(token, key, {
|
|
6666
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
6667
|
+
});
|
|
6668
|
+
const plan = payload.plan ?? "free";
|
|
6669
|
+
const email = payload.sub ?? "";
|
|
6670
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6671
|
+
return {
|
|
6672
|
+
valid: true,
|
|
6673
|
+
plan,
|
|
6674
|
+
email,
|
|
6675
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6676
|
+
deviceLimit: limits.devices,
|
|
6677
|
+
employeeLimit: limits.employees,
|
|
6678
|
+
memoryLimit: limits.memories
|
|
6679
|
+
};
|
|
6680
|
+
} catch {
|
|
6681
|
+
return null;
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
async function getCachedLicense() {
|
|
6685
|
+
try {
|
|
6686
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
6687
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6688
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
6689
|
+
return await verifyLicenseJwt(raw.token);
|
|
6690
|
+
} catch {
|
|
6691
|
+
return null;
|
|
6692
|
+
}
|
|
6693
|
+
}
|
|
6694
|
+
function readCachedLicenseToken() {
|
|
6695
|
+
try {
|
|
6696
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
6697
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6698
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
6699
|
+
} catch {
|
|
6700
|
+
return null;
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
function getRawCachedPlan() {
|
|
6704
|
+
try {
|
|
6705
|
+
const token = readCachedLicenseToken();
|
|
6706
|
+
if (!token) return null;
|
|
6707
|
+
const parts = token.split(".");
|
|
6708
|
+
if (parts.length !== 3) return null;
|
|
6709
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
6710
|
+
const plan = payload.plan ?? "free";
|
|
6711
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6712
|
+
process.stderr.write(
|
|
6713
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
6714
|
+
`
|
|
6715
|
+
);
|
|
6716
|
+
return {
|
|
6717
|
+
valid: true,
|
|
6718
|
+
plan,
|
|
6719
|
+
email: payload.sub ?? "",
|
|
6720
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6721
|
+
deviceLimit: limits.devices,
|
|
6722
|
+
employeeLimit: limits.employees,
|
|
6723
|
+
memoryLimit: limits.memories
|
|
6724
|
+
};
|
|
6725
|
+
} catch {
|
|
6726
|
+
return null;
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
function cacheResponse(token) {
|
|
6730
|
+
try {
|
|
6731
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
6732
|
+
} catch {
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
function loadPrismaForLicense() {
|
|
6736
|
+
if (_prismaFailed) return null;
|
|
6737
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
6738
|
+
if (!dbUrl) {
|
|
6739
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6740
|
+
if (!existsSync12(path11.join(exeDbRoot, "package.json"))) {
|
|
6741
|
+
_prismaFailed = true;
|
|
6742
|
+
return null;
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
if (!_prismaPromise) {
|
|
6746
|
+
_prismaPromise = (async () => {
|
|
6747
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
6748
|
+
if (explicitPath) {
|
|
6749
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
6750
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
6751
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
6752
|
+
return new Ctor2();
|
|
6753
|
+
}
|
|
6754
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6755
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
6756
|
+
const entry = req.resolve("@prisma/client");
|
|
6757
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
6758
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
6759
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
6760
|
+
return new Ctor();
|
|
6761
|
+
})().catch((err) => {
|
|
6762
|
+
_prismaFailed = true;
|
|
6763
|
+
_prismaPromise = null;
|
|
6764
|
+
throw err;
|
|
6765
|
+
});
|
|
6766
|
+
}
|
|
6767
|
+
return _prismaPromise;
|
|
6768
|
+
}
|
|
6769
|
+
async function validateViaPostgres(apiKey) {
|
|
6770
|
+
const loader = loadPrismaForLicense();
|
|
6771
|
+
if (!loader) return null;
|
|
6772
|
+
try {
|
|
6773
|
+
const prisma = await loader;
|
|
6774
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
6775
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
6776
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
6777
|
+
apiKey
|
|
6778
|
+
);
|
|
6779
|
+
if (!rows || rows.length === 0) return null;
|
|
6780
|
+
const row = rows[0];
|
|
6781
|
+
if (row.status !== "active") return null;
|
|
6782
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
6783
|
+
const plan = row.plan;
|
|
6784
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6785
|
+
return {
|
|
6786
|
+
valid: true,
|
|
6787
|
+
plan,
|
|
6788
|
+
email: row.email,
|
|
6789
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
6790
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
6791
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
6792
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
6793
|
+
};
|
|
6794
|
+
} catch {
|
|
6795
|
+
return null;
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
6799
|
+
try {
|
|
6800
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
6801
|
+
method: "POST",
|
|
6802
|
+
headers: { "Content-Type": "application/json" },
|
|
6803
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
6804
|
+
signal: AbortSignal.timeout(1e4)
|
|
6805
|
+
});
|
|
6806
|
+
if (!res.ok) return null;
|
|
6807
|
+
const data = await res.json();
|
|
6808
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
6809
|
+
if (!data.valid) return null;
|
|
6810
|
+
if (data.token) {
|
|
6811
|
+
cacheResponse(data.token);
|
|
6812
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
6813
|
+
if (verified) return verified;
|
|
6814
|
+
}
|
|
6815
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
6816
|
+
return {
|
|
6817
|
+
valid: data.valid,
|
|
6818
|
+
plan: data.plan,
|
|
6819
|
+
email: data.email,
|
|
6820
|
+
expiresAt: data.expiresAt,
|
|
6821
|
+
deviceLimit: limits.devices,
|
|
6822
|
+
employeeLimit: limits.employees,
|
|
6823
|
+
memoryLimit: limits.memories
|
|
6824
|
+
};
|
|
6825
|
+
} catch {
|
|
6826
|
+
return null;
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
async function validateLicense(apiKey, deviceId) {
|
|
6830
|
+
const did = deviceId ?? loadDeviceId();
|
|
6831
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
6832
|
+
if (pgResult) {
|
|
6833
|
+
try {
|
|
6834
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
6835
|
+
} catch {
|
|
6836
|
+
}
|
|
6837
|
+
return pgResult;
|
|
6838
|
+
}
|
|
6839
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
6840
|
+
if (cfResult) return cfResult;
|
|
6841
|
+
const cached = await getCachedLicense();
|
|
6842
|
+
if (cached) return cached;
|
|
6843
|
+
try {
|
|
6844
|
+
if (existsSync12(CACHE_PATH)) {
|
|
6845
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6846
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
6847
|
+
return raw.pgLicense;
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
} catch {
|
|
6851
|
+
}
|
|
6852
|
+
const rawFallback = getRawCachedPlan();
|
|
6853
|
+
if (rawFallback) return rawFallback;
|
|
6854
|
+
return { ...FREE_LICENSE, valid: false };
|
|
6855
|
+
}
|
|
6856
|
+
function getCacheAgeMs() {
|
|
6857
|
+
try {
|
|
6858
|
+
const { statSync: statSync5 } = __require("fs");
|
|
6859
|
+
const s = statSync5(CACHE_PATH);
|
|
6860
|
+
return Date.now() - s.mtimeMs;
|
|
6861
|
+
} catch {
|
|
6862
|
+
return Infinity;
|
|
6863
|
+
}
|
|
6864
|
+
}
|
|
6865
|
+
async function checkLicense() {
|
|
6866
|
+
let key = loadLicense();
|
|
6867
|
+
if (!key) {
|
|
6868
|
+
try {
|
|
6869
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
6870
|
+
if (existsSync12(configPath)) {
|
|
6871
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
6872
|
+
const cloud = raw.cloud;
|
|
6873
|
+
if (cloud?.apiKey) {
|
|
6874
|
+
key = cloud.apiKey;
|
|
6875
|
+
saveLicense(key);
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
} catch {
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
if (!key) return FREE_LICENSE;
|
|
6882
|
+
const cached = await getCachedLicense();
|
|
6883
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
6884
|
+
const deviceId = loadDeviceId();
|
|
6885
|
+
return validateLicense(key, deviceId);
|
|
6886
|
+
}
|
|
6887
|
+
function isFeatureAllowed(license, feature) {
|
|
6888
|
+
switch (feature) {
|
|
6889
|
+
case "cloud_sync":
|
|
6890
|
+
case "external_agents":
|
|
6891
|
+
case "wiki":
|
|
6892
|
+
return license.plan !== "free";
|
|
6893
|
+
case "unlimited_employees":
|
|
6894
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
function mirrorLicenseKey(apiKey) {
|
|
6898
|
+
const trimmed = apiKey.trim();
|
|
6899
|
+
if (!trimmed) return;
|
|
6900
|
+
saveLicense(trimmed);
|
|
6901
|
+
}
|
|
6902
|
+
async function assertVpsLicense(opts) {
|
|
6903
|
+
const env = opts?.env ?? process.env;
|
|
6904
|
+
const inProduction = env.NODE_ENV === "production";
|
|
6905
|
+
if (!opts?.force && !inProduction) {
|
|
6906
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
6907
|
+
}
|
|
6908
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
6909
|
+
if (envKey) {
|
|
6910
|
+
saveLicense(envKey);
|
|
6911
|
+
}
|
|
6912
|
+
const apiKey = envKey ?? loadLicense();
|
|
6913
|
+
if (!apiKey) {
|
|
6914
|
+
throw new Error(
|
|
6915
|
+
"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."
|
|
6916
|
+
);
|
|
6917
|
+
}
|
|
6918
|
+
const deviceId = loadDeviceId();
|
|
6919
|
+
let backendResponse = null;
|
|
6920
|
+
let explicitRejection = false;
|
|
6921
|
+
let transientFailure = false;
|
|
6922
|
+
try {
|
|
6923
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
6924
|
+
method: "POST",
|
|
6925
|
+
headers: { "Content-Type": "application/json" },
|
|
6926
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
6927
|
+
signal: AbortSignal.timeout(1e4)
|
|
6928
|
+
});
|
|
6929
|
+
if (res.ok) {
|
|
6930
|
+
backendResponse = await res.json();
|
|
6931
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
6932
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
6933
|
+
explicitRejection = true;
|
|
6934
|
+
} else {
|
|
6935
|
+
transientFailure = true;
|
|
6936
|
+
}
|
|
6937
|
+
} catch {
|
|
6938
|
+
transientFailure = true;
|
|
6939
|
+
}
|
|
6940
|
+
if (backendResponse?.valid) {
|
|
6941
|
+
if (backendResponse.token) {
|
|
6942
|
+
cacheResponse(backendResponse.token);
|
|
6943
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
6944
|
+
if (verified) return verified;
|
|
6945
|
+
}
|
|
6946
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
6947
|
+
return {
|
|
6948
|
+
valid: true,
|
|
6949
|
+
plan: backendResponse.plan,
|
|
6950
|
+
email: backendResponse.email,
|
|
6951
|
+
expiresAt: backendResponse.expiresAt,
|
|
6952
|
+
deviceLimit: limits.devices,
|
|
6953
|
+
employeeLimit: limits.employees,
|
|
6954
|
+
memoryLimit: limits.memories
|
|
6955
|
+
};
|
|
6956
|
+
}
|
|
6957
|
+
if (explicitRejection) {
|
|
6958
|
+
throw new Error(
|
|
6959
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
6960
|
+
);
|
|
6961
|
+
}
|
|
6962
|
+
if (!transientFailure) {
|
|
6963
|
+
throw new Error(
|
|
6964
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
6965
|
+
);
|
|
6966
|
+
}
|
|
6967
|
+
const fresh = await getCachedLicense();
|
|
6968
|
+
if (fresh && fresh.valid) return fresh;
|
|
6969
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
6970
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
6971
|
+
try {
|
|
6972
|
+
const token = readCachedLicenseToken();
|
|
6973
|
+
if (token) {
|
|
6974
|
+
const payloadB64 = token.split(".")[1];
|
|
6975
|
+
if (payloadB64) {
|
|
6976
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
6977
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
6978
|
+
if (Date.now() < expMs + graceMs) {
|
|
6979
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
6980
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
6981
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
6982
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
6983
|
+
});
|
|
6984
|
+
const plan = verified.plan ?? "free";
|
|
6985
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6986
|
+
return {
|
|
6987
|
+
valid: true,
|
|
6988
|
+
plan,
|
|
6989
|
+
email: verified.sub ?? "",
|
|
6990
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
6991
|
+
deviceLimit: limits.devices,
|
|
6992
|
+
employeeLimit: limits.employees,
|
|
6993
|
+
memoryLimit: limits.memories
|
|
6994
|
+
};
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
} catch {
|
|
6999
|
+
}
|
|
7000
|
+
throw new Error(
|
|
7001
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
|
|
7002
|
+
);
|
|
7003
|
+
}
|
|
7004
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
7005
|
+
if (_revalTimer) return;
|
|
7006
|
+
_revalTimer = setInterval(async () => {
|
|
7007
|
+
try {
|
|
7008
|
+
const license = await checkLicense();
|
|
7009
|
+
if (!license.valid) {
|
|
7010
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
7011
|
+
}
|
|
7012
|
+
} catch {
|
|
7013
|
+
}
|
|
7014
|
+
}, intervalMs);
|
|
7015
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
7016
|
+
_revalTimer.unref();
|
|
7017
|
+
}
|
|
7018
|
+
}
|
|
7019
|
+
function stopLicenseRevalidation() {
|
|
7020
|
+
if (_revalTimer) {
|
|
7021
|
+
clearInterval(_revalTimer);
|
|
7022
|
+
_revalTimer = null;
|
|
7023
|
+
}
|
|
7024
|
+
}
|
|
7025
|
+
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;
|
|
6300
7026
|
var init_license = __esm({
|
|
6301
7027
|
"src/lib/license.ts"() {
|
|
6302
7028
|
"use strict";
|
|
@@ -6304,7 +7030,13 @@ var init_license = __esm({
|
|
|
6304
7030
|
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
6305
7031
|
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
6306
7032
|
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
6307
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
7033
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
7034
|
+
RETRY_DELAY_MS = 500;
|
|
7035
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
7036
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
7037
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
7038
|
+
-----END PUBLIC KEY-----`;
|
|
7039
|
+
LICENSE_JWT_ALG = "ES256";
|
|
6308
7040
|
PLAN_LIMITS = {
|
|
6309
7041
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
6310
7042
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -6312,6 +7044,19 @@ var init_license = __esm({
|
|
|
6312
7044
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
6313
7045
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
6314
7046
|
};
|
|
7047
|
+
FREE_LICENSE = {
|
|
7048
|
+
valid: true,
|
|
7049
|
+
plan: "free",
|
|
7050
|
+
email: "",
|
|
7051
|
+
expiresAt: null,
|
|
7052
|
+
deviceLimit: 1,
|
|
7053
|
+
employeeLimit: 1,
|
|
7054
|
+
memoryLimit: 5e3
|
|
7055
|
+
};
|
|
7056
|
+
_prismaPromise = null;
|
|
7057
|
+
_prismaFailed = false;
|
|
7058
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
7059
|
+
_revalTimer = null;
|
|
6315
7060
|
}
|
|
6316
7061
|
});
|
|
6317
7062
|
|
|
@@ -6751,6 +7496,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
6751
7496
|
args: [identifier, ...scope.args]
|
|
6752
7497
|
});
|
|
6753
7498
|
if (result.rows.length === 1) return result.rows[0];
|
|
7499
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
7500
|
+
result = await client.execute({
|
|
7501
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
7502
|
+
args: [`${identifier}%`]
|
|
7503
|
+
});
|
|
7504
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
7505
|
+
if (result.rows.length > 1) {
|
|
7506
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
7507
|
+
throw new Error(
|
|
7508
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
7509
|
+
);
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
6754
7512
|
result = await client.execute({
|
|
6755
7513
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
6756
7514
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -7605,12 +8363,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7605
8363
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
7606
8364
|
args: [now, taskId]
|
|
7607
8365
|
});
|
|
7608
|
-
if (
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
8366
|
+
if (unblocked.rowsAffected === 0) return;
|
|
8367
|
+
const ubScope = sessionScopeFilter();
|
|
8368
|
+
const unblockedRows = await client.execute({
|
|
8369
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
8370
|
+
args: [now, ...ubScope.args]
|
|
8371
|
+
});
|
|
8372
|
+
if (baseDir) {
|
|
7614
8373
|
for (const ur of unblockedRows.rows) {
|
|
7615
8374
|
try {
|
|
7616
8375
|
const ubFile = path18.join(baseDir, String(ur.task_file));
|
|
@@ -7622,6 +8381,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7622
8381
|
}
|
|
7623
8382
|
}
|
|
7624
8383
|
}
|
|
8384
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
8385
|
+
try {
|
|
8386
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
8387
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
8388
|
+
for (const ur of unblockedRows.rows) {
|
|
8389
|
+
const assignee = String(ur.assigned_to);
|
|
8390
|
+
if (dispatched.has(assignee)) continue;
|
|
8391
|
+
dispatched.add(assignee);
|
|
8392
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
8393
|
+
}
|
|
8394
|
+
} catch {
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
7625
8397
|
}
|
|
7626
8398
|
async function findNextTask(assignedTo) {
|
|
7627
8399
|
const client = getClient();
|
|
@@ -7831,6 +8603,15 @@ var init_embedder = __esm({
|
|
|
7831
8603
|
// src/lib/behaviors.ts
|
|
7832
8604
|
import crypto5 from "crypto";
|
|
7833
8605
|
async function storeBehavior(opts) {
|
|
8606
|
+
try {
|
|
8607
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
8608
|
+
const roster = loadEmployeesSync2();
|
|
8609
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
8610
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
8611
|
+
}
|
|
8612
|
+
} catch (e) {
|
|
8613
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
8614
|
+
}
|
|
7834
8615
|
const client = getClient();
|
|
7835
8616
|
const id = crypto5.randomUUID();
|
|
7836
8617
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7841,10 +8622,18 @@ async function storeBehavior(opts) {
|
|
|
7841
8622
|
vector = new Float32Array(vec);
|
|
7842
8623
|
} catch {
|
|
7843
8624
|
}
|
|
8625
|
+
let createdByDevice = null;
|
|
8626
|
+
try {
|
|
8627
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
8628
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
8629
|
+
} catch {
|
|
8630
|
+
}
|
|
8631
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
8632
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
7844
8633
|
await client.execute({
|
|
7845
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
7846
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
7847
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
8634
|
+
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)
|
|
8635
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
8636
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
7848
8637
|
});
|
|
7849
8638
|
return id;
|
|
7850
8639
|
}
|
|
@@ -8276,6 +9065,12 @@ async function updateTask(input) {
|
|
|
8276
9065
|
}
|
|
8277
9066
|
}
|
|
8278
9067
|
}
|
|
9068
|
+
if (input.status === "cancelled") {
|
|
9069
|
+
try {
|
|
9070
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
9071
|
+
} catch {
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
8279
9074
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
8280
9075
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
8281
9076
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -8807,11 +9602,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
8807
9602
|
}
|
|
8808
9603
|
}
|
|
8809
9604
|
function resolveExeSession() {
|
|
9605
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
9606
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
9607
|
+
if (fromEnv) return fromEnv;
|
|
9608
|
+
}
|
|
8810
9609
|
const mySession = getMySession();
|
|
8811
9610
|
if (!mySession) {
|
|
8812
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
8813
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
8814
|
-
}
|
|
8815
9611
|
return null;
|
|
8816
9612
|
}
|
|
8817
9613
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -8826,6 +9622,10 @@ function resolveExeSession() {
|
|
|
8826
9622
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
8827
9623
|
`
|
|
8828
9624
|
);
|
|
9625
|
+
try {
|
|
9626
|
+
registerParentExe(key, fromSessionName);
|
|
9627
|
+
} catch {
|
|
9628
|
+
}
|
|
8829
9629
|
candidate = fromSessionName;
|
|
8830
9630
|
} else {
|
|
8831
9631
|
candidate = fromCache;
|