@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
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
3
9
|
var __esm = (fn, res) => function __init() {
|
|
4
10
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
11
|
};
|
|
@@ -313,6 +319,394 @@ var init_config = __esm({
|
|
|
313
319
|
}
|
|
314
320
|
});
|
|
315
321
|
|
|
322
|
+
// src/lib/runtime-table.ts
|
|
323
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
324
|
+
var init_runtime_table = __esm({
|
|
325
|
+
"src/lib/runtime-table.ts"() {
|
|
326
|
+
"use strict";
|
|
327
|
+
RUNTIME_TABLE = {
|
|
328
|
+
codex: {
|
|
329
|
+
binary: "codex",
|
|
330
|
+
launchMode: "interactive",
|
|
331
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
332
|
+
inlineFlag: "--no-alt-screen",
|
|
333
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
334
|
+
defaultModel: "gpt-5.5"
|
|
335
|
+
},
|
|
336
|
+
opencode: {
|
|
337
|
+
binary: "opencode",
|
|
338
|
+
launchMode: "exec",
|
|
339
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
340
|
+
inlineFlag: "",
|
|
341
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
342
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
DEFAULT_RUNTIME = "claude";
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// src/lib/agent-config.ts
|
|
350
|
+
var agent_config_exports = {};
|
|
351
|
+
__export(agent_config_exports, {
|
|
352
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
353
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
354
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
355
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
356
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
357
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
358
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
359
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
360
|
+
setAgentMcps: () => setAgentMcps,
|
|
361
|
+
setAgentRuntime: () => setAgentRuntime
|
|
362
|
+
});
|
|
363
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
364
|
+
import path2 from "path";
|
|
365
|
+
function loadAgentConfig() {
|
|
366
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
367
|
+
try {
|
|
368
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
369
|
+
} catch {
|
|
370
|
+
return {};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function saveAgentConfig(config) {
|
|
374
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
375
|
+
ensurePrivateDirSync(dir);
|
|
376
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
377
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
378
|
+
}
|
|
379
|
+
function getAgentRuntime(agentId) {
|
|
380
|
+
const config = loadAgentConfig();
|
|
381
|
+
const entry = config[agentId];
|
|
382
|
+
if (entry) return entry;
|
|
383
|
+
const orgDefault = config["default"];
|
|
384
|
+
if (orgDefault) return orgDefault;
|
|
385
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
386
|
+
}
|
|
387
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
388
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
389
|
+
if (!knownModels) {
|
|
390
|
+
return {
|
|
391
|
+
ok: false,
|
|
392
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (!knownModels.includes(model)) {
|
|
396
|
+
return {
|
|
397
|
+
ok: false,
|
|
398
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const config = loadAgentConfig();
|
|
402
|
+
const existing = config[agentId];
|
|
403
|
+
const entry = { runtime, model };
|
|
404
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
405
|
+
if (mcps !== void 0) {
|
|
406
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
407
|
+
} else if (existing?.mcps) {
|
|
408
|
+
entry.mcps = existing.mcps;
|
|
409
|
+
}
|
|
410
|
+
config[agentId] = entry;
|
|
411
|
+
saveAgentConfig(config);
|
|
412
|
+
return { ok: true };
|
|
413
|
+
}
|
|
414
|
+
function setAgentMcps(agentId, mcps) {
|
|
415
|
+
const config = loadAgentConfig();
|
|
416
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
417
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
418
|
+
config[agentId] = existing;
|
|
419
|
+
saveAgentConfig(config);
|
|
420
|
+
return { ok: true };
|
|
421
|
+
}
|
|
422
|
+
function clearAgentRuntime(agentId) {
|
|
423
|
+
const config = loadAgentConfig();
|
|
424
|
+
delete config[agentId];
|
|
425
|
+
saveAgentConfig(config);
|
|
426
|
+
}
|
|
427
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
428
|
+
var init_agent_config = __esm({
|
|
429
|
+
"src/lib/agent-config.ts"() {
|
|
430
|
+
"use strict";
|
|
431
|
+
init_config();
|
|
432
|
+
init_runtime_table();
|
|
433
|
+
init_secure_files();
|
|
434
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
435
|
+
KNOWN_RUNTIMES = {
|
|
436
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
437
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
438
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
439
|
+
};
|
|
440
|
+
RUNTIME_LABELS = {
|
|
441
|
+
claude: "Claude Code (Anthropic)",
|
|
442
|
+
codex: "Codex (OpenAI)",
|
|
443
|
+
opencode: "OpenCode (open source)"
|
|
444
|
+
};
|
|
445
|
+
DEFAULT_MODELS = {
|
|
446
|
+
claude: "claude-opus-4.6",
|
|
447
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
448
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// src/lib/employees.ts
|
|
454
|
+
var employees_exports = {};
|
|
455
|
+
__export(employees_exports, {
|
|
456
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
457
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
458
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
459
|
+
addEmployee: () => addEmployee,
|
|
460
|
+
baseAgentName: () => baseAgentName,
|
|
461
|
+
canCoordinate: () => canCoordinate,
|
|
462
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
463
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
464
|
+
getEmployee: () => getEmployee,
|
|
465
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
466
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
467
|
+
hasRole: () => hasRole,
|
|
468
|
+
hireEmployee: () => hireEmployee,
|
|
469
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
470
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
471
|
+
isMultiInstance: () => isMultiInstance,
|
|
472
|
+
loadEmployees: () => loadEmployees,
|
|
473
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
474
|
+
normalizeRole: () => normalizeRole,
|
|
475
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
476
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
477
|
+
saveEmployees: () => saveEmployees,
|
|
478
|
+
validateEmployeeName: () => validateEmployeeName
|
|
479
|
+
});
|
|
480
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
481
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
482
|
+
import { execSync } from "child_process";
|
|
483
|
+
import path3 from "path";
|
|
484
|
+
import os2 from "os";
|
|
485
|
+
function normalizeRole(role) {
|
|
486
|
+
return (role ?? "").trim().toLowerCase();
|
|
487
|
+
}
|
|
488
|
+
function isCoordinatorRole(role) {
|
|
489
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
490
|
+
}
|
|
491
|
+
function getCoordinatorEmployee(employees) {
|
|
492
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
493
|
+
}
|
|
494
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
495
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
496
|
+
}
|
|
497
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
498
|
+
if (!agentName) return false;
|
|
499
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
500
|
+
}
|
|
501
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
502
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
503
|
+
}
|
|
504
|
+
function validateEmployeeName(name) {
|
|
505
|
+
if (!name) {
|
|
506
|
+
return { valid: false, error: "Name is required" };
|
|
507
|
+
}
|
|
508
|
+
if (name.length > 32) {
|
|
509
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
510
|
+
}
|
|
511
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
512
|
+
return {
|
|
513
|
+
valid: false,
|
|
514
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return { valid: true };
|
|
518
|
+
}
|
|
519
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
520
|
+
if (!existsSync4(employeesPath)) {
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
524
|
+
try {
|
|
525
|
+
return JSON.parse(raw);
|
|
526
|
+
} catch {
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
531
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
532
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
533
|
+
}
|
|
534
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
535
|
+
if (!existsSync4(employeesPath)) return [];
|
|
536
|
+
try {
|
|
537
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
538
|
+
} catch {
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function getEmployee(employees, name) {
|
|
543
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
544
|
+
}
|
|
545
|
+
function getEmployeeByRole(employees, role) {
|
|
546
|
+
const lower = role.toLowerCase();
|
|
547
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
548
|
+
}
|
|
549
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
550
|
+
const lower = role.toLowerCase();
|
|
551
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
552
|
+
}
|
|
553
|
+
function hasRole(agentName, role) {
|
|
554
|
+
const employees = loadEmployeesSync();
|
|
555
|
+
const emp = getEmployee(employees, agentName);
|
|
556
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
557
|
+
}
|
|
558
|
+
function baseAgentName(name, employees) {
|
|
559
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
560
|
+
if (!match) return name;
|
|
561
|
+
const base = match[1];
|
|
562
|
+
const roster = employees ?? loadEmployeesSync();
|
|
563
|
+
if (getEmployee(roster, base)) return base;
|
|
564
|
+
return name;
|
|
565
|
+
}
|
|
566
|
+
function isMultiInstance(agentName, employees) {
|
|
567
|
+
const roster = employees ?? loadEmployeesSync();
|
|
568
|
+
const emp = getEmployee(roster, agentName);
|
|
569
|
+
if (!emp) return false;
|
|
570
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
571
|
+
}
|
|
572
|
+
function addEmployee(employees, employee) {
|
|
573
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
574
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
575
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
576
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
577
|
+
}
|
|
578
|
+
return [...employees, normalized];
|
|
579
|
+
}
|
|
580
|
+
function appendToCoordinatorTeam(employee) {
|
|
581
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
582
|
+
if (!coordinator) return;
|
|
583
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
584
|
+
if (!existsSync4(idPath)) return;
|
|
585
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
586
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
587
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
588
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
589
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
590
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
591
|
+
const entry = `
|
|
592
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
593
|
+
`;
|
|
594
|
+
let updated;
|
|
595
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
596
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
597
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
598
|
+
} else {
|
|
599
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
600
|
+
}
|
|
601
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
602
|
+
}
|
|
603
|
+
function capitalize(s) {
|
|
604
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
605
|
+
}
|
|
606
|
+
async function hireEmployee(employee) {
|
|
607
|
+
const employees = await loadEmployees();
|
|
608
|
+
const updated = addEmployee(employees, employee);
|
|
609
|
+
await saveEmployees(updated);
|
|
610
|
+
try {
|
|
611
|
+
appendToCoordinatorTeam(employee);
|
|
612
|
+
} catch {
|
|
613
|
+
}
|
|
614
|
+
try {
|
|
615
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
616
|
+
const config = loadAgentConfig2();
|
|
617
|
+
const name = employee.name.toLowerCase();
|
|
618
|
+
if (!config[name] && config["default"]) {
|
|
619
|
+
config[name] = { ...config["default"] };
|
|
620
|
+
saveAgentConfig2(config);
|
|
621
|
+
}
|
|
622
|
+
} catch {
|
|
623
|
+
}
|
|
624
|
+
return updated;
|
|
625
|
+
}
|
|
626
|
+
async function normalizeRosterCase(rosterPath) {
|
|
627
|
+
const employees = await loadEmployees(rosterPath);
|
|
628
|
+
let changed = false;
|
|
629
|
+
for (const emp of employees) {
|
|
630
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
631
|
+
const oldName = emp.name;
|
|
632
|
+
emp.name = emp.name.toLowerCase();
|
|
633
|
+
changed = true;
|
|
634
|
+
try {
|
|
635
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
636
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
637
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
638
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
639
|
+
renameSync2(oldPath, newPath);
|
|
640
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
641
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
642
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
643
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
644
|
+
unlinkSync(oldPath);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
} catch {
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (changed) {
|
|
652
|
+
await saveEmployees(employees, rosterPath);
|
|
653
|
+
}
|
|
654
|
+
return changed;
|
|
655
|
+
}
|
|
656
|
+
function findExeBin() {
|
|
657
|
+
try {
|
|
658
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
659
|
+
} catch {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function registerBinSymlinks(name) {
|
|
664
|
+
const created = [];
|
|
665
|
+
const skipped = [];
|
|
666
|
+
const errors = [];
|
|
667
|
+
const exeBinPath = findExeBin();
|
|
668
|
+
if (!exeBinPath) {
|
|
669
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
670
|
+
return { created, skipped, errors };
|
|
671
|
+
}
|
|
672
|
+
const binDir = path3.dirname(exeBinPath);
|
|
673
|
+
let target;
|
|
674
|
+
try {
|
|
675
|
+
target = readlinkSync(exeBinPath);
|
|
676
|
+
} catch {
|
|
677
|
+
errors.push("Could not read 'exe' symlink");
|
|
678
|
+
return { created, skipped, errors };
|
|
679
|
+
}
|
|
680
|
+
for (const suffix of ["", "-opencode"]) {
|
|
681
|
+
const linkName = `${name}${suffix}`;
|
|
682
|
+
const linkPath = path3.join(binDir, linkName);
|
|
683
|
+
if (existsSync4(linkPath)) {
|
|
684
|
+
skipped.push(linkName);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
symlinkSync(target, linkPath);
|
|
689
|
+
created.push(linkName);
|
|
690
|
+
} catch (err) {
|
|
691
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return { created, skipped, errors };
|
|
695
|
+
}
|
|
696
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
697
|
+
var init_employees = __esm({
|
|
698
|
+
"src/lib/employees.ts"() {
|
|
699
|
+
"use strict";
|
|
700
|
+
init_config();
|
|
701
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
702
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
703
|
+
COORDINATOR_ROLE = "COO";
|
|
704
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
705
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
706
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
|
|
316
710
|
// src/types/memory.ts
|
|
317
711
|
var EMBEDDING_DIM;
|
|
318
712
|
var init_memory = __esm({
|
|
@@ -324,8 +718,8 @@ var init_memory = __esm({
|
|
|
324
718
|
|
|
325
719
|
// src/lib/daemon-auth.ts
|
|
326
720
|
import crypto from "crypto";
|
|
327
|
-
import
|
|
328
|
-
import { existsSync as
|
|
721
|
+
import path5 from "path";
|
|
722
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
329
723
|
function normalizeToken(token) {
|
|
330
724
|
if (!token) return null;
|
|
331
725
|
const trimmed = token.trim();
|
|
@@ -333,8 +727,8 @@ function normalizeToken(token) {
|
|
|
333
727
|
}
|
|
334
728
|
function readDaemonToken() {
|
|
335
729
|
try {
|
|
336
|
-
if (!
|
|
337
|
-
return normalizeToken(
|
|
730
|
+
if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
|
|
731
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
338
732
|
} catch {
|
|
339
733
|
return null;
|
|
340
734
|
}
|
|
@@ -344,7 +738,7 @@ function ensureDaemonToken(seed) {
|
|
|
344
738
|
if (existing) return existing;
|
|
345
739
|
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
346
740
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
347
|
-
|
|
741
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
348
742
|
`, "utf8");
|
|
349
743
|
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
350
744
|
return token;
|
|
@@ -355,7 +749,7 @@ var init_daemon_auth = __esm({
|
|
|
355
749
|
"use strict";
|
|
356
750
|
init_config();
|
|
357
751
|
init_secure_files();
|
|
358
|
-
DAEMON_TOKEN_PATH =
|
|
752
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
359
753
|
}
|
|
360
754
|
});
|
|
361
755
|
|
|
@@ -364,8 +758,8 @@ import net from "net";
|
|
|
364
758
|
import os4 from "os";
|
|
365
759
|
import { spawn, execSync as execSync2 } from "child_process";
|
|
366
760
|
import { randomUUID } from "crypto";
|
|
367
|
-
import { existsSync as
|
|
368
|
-
import
|
|
761
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync2 } from "fs";
|
|
762
|
+
import path6 from "path";
|
|
369
763
|
import { fileURLToPath } from "url";
|
|
370
764
|
function handleData(chunk) {
|
|
371
765
|
_buffer += chunk.toString();
|
|
@@ -401,9 +795,9 @@ function isZombie(pid) {
|
|
|
401
795
|
}
|
|
402
796
|
}
|
|
403
797
|
function cleanupStaleFiles() {
|
|
404
|
-
if (
|
|
798
|
+
if (existsSync7(PID_PATH)) {
|
|
405
799
|
try {
|
|
406
|
-
const pid = parseInt(
|
|
800
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
407
801
|
if (pid > 0) {
|
|
408
802
|
try {
|
|
409
803
|
process.kill(pid, 0);
|
|
@@ -428,11 +822,11 @@ function cleanupStaleFiles() {
|
|
|
428
822
|
}
|
|
429
823
|
}
|
|
430
824
|
function findPackageRoot() {
|
|
431
|
-
let dir =
|
|
432
|
-
const { root } =
|
|
825
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
826
|
+
const { root } = path6.parse(dir);
|
|
433
827
|
while (dir !== root) {
|
|
434
|
-
if (
|
|
435
|
-
dir =
|
|
828
|
+
if (existsSync7(path6.join(dir, "package.json"))) return dir;
|
|
829
|
+
dir = path6.dirname(dir);
|
|
436
830
|
}
|
|
437
831
|
return null;
|
|
438
832
|
}
|
|
@@ -450,8 +844,8 @@ function spawnDaemon() {
|
|
|
450
844
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
451
845
|
return;
|
|
452
846
|
}
|
|
453
|
-
const daemonPath =
|
|
454
|
-
if (!
|
|
847
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
848
|
+
if (!existsSync7(daemonPath)) {
|
|
455
849
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
456
850
|
`);
|
|
457
851
|
return;
|
|
@@ -460,7 +854,7 @@ function spawnDaemon() {
|
|
|
460
854
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
461
855
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
462
856
|
`);
|
|
463
|
-
const logPath =
|
|
857
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
464
858
|
let stderrFd = "ignore";
|
|
465
859
|
try {
|
|
466
860
|
stderrFd = openSync2(logPath, "a");
|
|
@@ -625,9 +1019,9 @@ function killAndRespawnDaemon() {
|
|
|
625
1019
|
}
|
|
626
1020
|
try {
|
|
627
1021
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
628
|
-
if (
|
|
1022
|
+
if (existsSync7(PID_PATH)) {
|
|
629
1023
|
try {
|
|
630
|
-
const pid = parseInt(
|
|
1024
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
631
1025
|
if (pid > 0) {
|
|
632
1026
|
try {
|
|
633
1027
|
process.kill(pid, "SIGKILL");
|
|
@@ -750,9 +1144,9 @@ var init_exe_daemon_client = __esm({
|
|
|
750
1144
|
"use strict";
|
|
751
1145
|
init_config();
|
|
752
1146
|
init_daemon_auth();
|
|
753
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
754
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
755
|
-
SPAWN_LOCK_PATH =
|
|
1147
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1148
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1149
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
756
1150
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
757
1151
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
758
1152
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -808,10 +1202,10 @@ async function disposeEmbedder() {
|
|
|
808
1202
|
async function embedDirect(text) {
|
|
809
1203
|
const llamaCpp = await import("node-llama-cpp");
|
|
810
1204
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
811
|
-
const { existsSync:
|
|
812
|
-
const
|
|
813
|
-
const modelPath =
|
|
814
|
-
if (!
|
|
1205
|
+
const { existsSync: existsSync9 } = await import("fs");
|
|
1206
|
+
const path8 = await import("path");
|
|
1207
|
+
const modelPath = path8.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
1208
|
+
if (!existsSync9(modelPath)) {
|
|
815
1209
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
816
1210
|
}
|
|
817
1211
|
const llama = await llamaCpp.getLlama();
|
|
@@ -839,28 +1233,486 @@ var init_embedder = __esm({
|
|
|
839
1233
|
}
|
|
840
1234
|
});
|
|
841
1235
|
|
|
1236
|
+
// src/lib/license.ts
|
|
1237
|
+
var license_exports = {};
|
|
1238
|
+
__export(license_exports, {
|
|
1239
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
1240
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
1241
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
1242
|
+
checkLicense: () => checkLicense,
|
|
1243
|
+
getCachedLicense: () => getCachedLicense,
|
|
1244
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
1245
|
+
loadDeviceId: () => loadDeviceId,
|
|
1246
|
+
loadLicense: () => loadLicense,
|
|
1247
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
1248
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
1249
|
+
saveLicense: () => saveLicense,
|
|
1250
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
1251
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
1252
|
+
validateLicense: () => validateLicense
|
|
1253
|
+
});
|
|
1254
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
1255
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1256
|
+
import { createRequire as createRequire2 } from "module";
|
|
1257
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1258
|
+
import os5 from "os";
|
|
1259
|
+
import path7 from "path";
|
|
1260
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
1261
|
+
async function fetchRetry(url, init) {
|
|
1262
|
+
try {
|
|
1263
|
+
return await fetch(url, init);
|
|
1264
|
+
} catch {
|
|
1265
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1266
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
function loadDeviceId() {
|
|
1270
|
+
const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
|
|
1271
|
+
try {
|
|
1272
|
+
if (existsSync8(deviceJsonPath)) {
|
|
1273
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
1274
|
+
if (data.deviceId) return data.deviceId;
|
|
1275
|
+
}
|
|
1276
|
+
} catch {
|
|
1277
|
+
}
|
|
1278
|
+
try {
|
|
1279
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
1280
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
1281
|
+
if (id2) return id2;
|
|
1282
|
+
}
|
|
1283
|
+
} catch {
|
|
1284
|
+
}
|
|
1285
|
+
const id = randomUUID2();
|
|
1286
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
1287
|
+
writeFileSync4(DEVICE_ID_PATH, id, "utf8");
|
|
1288
|
+
return id;
|
|
1289
|
+
}
|
|
1290
|
+
function loadLicense() {
|
|
1291
|
+
try {
|
|
1292
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
1293
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
1294
|
+
} catch {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function saveLicense(apiKey) {
|
|
1299
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
1300
|
+
writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1301
|
+
}
|
|
1302
|
+
async function verifyLicenseJwt(token) {
|
|
1303
|
+
try {
|
|
1304
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1305
|
+
const { payload } = await jwtVerify(token, key, {
|
|
1306
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
1307
|
+
});
|
|
1308
|
+
const plan = payload.plan ?? "free";
|
|
1309
|
+
const email = payload.sub ?? "";
|
|
1310
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1311
|
+
return {
|
|
1312
|
+
valid: true,
|
|
1313
|
+
plan,
|
|
1314
|
+
email,
|
|
1315
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1316
|
+
deviceLimit: limits.devices,
|
|
1317
|
+
employeeLimit: limits.employees,
|
|
1318
|
+
memoryLimit: limits.memories
|
|
1319
|
+
};
|
|
1320
|
+
} catch {
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async function getCachedLicense() {
|
|
1325
|
+
try {
|
|
1326
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
1327
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1328
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1329
|
+
return await verifyLicenseJwt(raw.token);
|
|
1330
|
+
} catch {
|
|
1331
|
+
return null;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
function readCachedLicenseToken() {
|
|
1335
|
+
try {
|
|
1336
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
1337
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1338
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
1339
|
+
} catch {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
function getRawCachedPlan() {
|
|
1344
|
+
try {
|
|
1345
|
+
const token = readCachedLicenseToken();
|
|
1346
|
+
if (!token) return null;
|
|
1347
|
+
const parts = token.split(".");
|
|
1348
|
+
if (parts.length !== 3) return null;
|
|
1349
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1350
|
+
const plan = payload.plan ?? "free";
|
|
1351
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1352
|
+
process.stderr.write(
|
|
1353
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
1354
|
+
`
|
|
1355
|
+
);
|
|
1356
|
+
return {
|
|
1357
|
+
valid: true,
|
|
1358
|
+
plan,
|
|
1359
|
+
email: payload.sub ?? "",
|
|
1360
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1361
|
+
deviceLimit: limits.devices,
|
|
1362
|
+
employeeLimit: limits.employees,
|
|
1363
|
+
memoryLimit: limits.memories
|
|
1364
|
+
};
|
|
1365
|
+
} catch {
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function cacheResponse(token) {
|
|
1370
|
+
try {
|
|
1371
|
+
writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1372
|
+
} catch {
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
function loadPrismaForLicense() {
|
|
1376
|
+
if (_prismaFailed) return null;
|
|
1377
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
1378
|
+
if (!dbUrl) {
|
|
1379
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
1380
|
+
if (!existsSync8(path7.join(exeDbRoot, "package.json"))) {
|
|
1381
|
+
_prismaFailed = true;
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (!_prismaPromise) {
|
|
1386
|
+
_prismaPromise = (async () => {
|
|
1387
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1388
|
+
if (explicitPath) {
|
|
1389
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
1390
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
1391
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
1392
|
+
return new Ctor2();
|
|
1393
|
+
}
|
|
1394
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
1395
|
+
const req = createRequire2(path7.join(exeDbRoot, "package.json"));
|
|
1396
|
+
const entry = req.resolve("@prisma/client");
|
|
1397
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
1398
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
1399
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
1400
|
+
return new Ctor();
|
|
1401
|
+
})().catch((err) => {
|
|
1402
|
+
_prismaFailed = true;
|
|
1403
|
+
_prismaPromise = null;
|
|
1404
|
+
throw err;
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
return _prismaPromise;
|
|
1408
|
+
}
|
|
1409
|
+
async function validateViaPostgres(apiKey) {
|
|
1410
|
+
const loader = loadPrismaForLicense();
|
|
1411
|
+
if (!loader) return null;
|
|
1412
|
+
try {
|
|
1413
|
+
const prisma = await loader;
|
|
1414
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1415
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
1416
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
1417
|
+
apiKey
|
|
1418
|
+
);
|
|
1419
|
+
if (!rows || rows.length === 0) return null;
|
|
1420
|
+
const row = rows[0];
|
|
1421
|
+
if (row.status !== "active") return null;
|
|
1422
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
1423
|
+
const plan = row.plan;
|
|
1424
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1425
|
+
return {
|
|
1426
|
+
valid: true,
|
|
1427
|
+
plan,
|
|
1428
|
+
email: row.email,
|
|
1429
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
1430
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
1431
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
1432
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
1433
|
+
};
|
|
1434
|
+
} catch {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
1439
|
+
try {
|
|
1440
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1441
|
+
method: "POST",
|
|
1442
|
+
headers: { "Content-Type": "application/json" },
|
|
1443
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
1444
|
+
signal: AbortSignal.timeout(1e4)
|
|
1445
|
+
});
|
|
1446
|
+
if (!res.ok) return null;
|
|
1447
|
+
const data = await res.json();
|
|
1448
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
1449
|
+
if (!data.valid) return null;
|
|
1450
|
+
if (data.token) {
|
|
1451
|
+
cacheResponse(data.token);
|
|
1452
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
1453
|
+
if (verified) return verified;
|
|
1454
|
+
}
|
|
1455
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1456
|
+
return {
|
|
1457
|
+
valid: data.valid,
|
|
1458
|
+
plan: data.plan,
|
|
1459
|
+
email: data.email,
|
|
1460
|
+
expiresAt: data.expiresAt,
|
|
1461
|
+
deviceLimit: limits.devices,
|
|
1462
|
+
employeeLimit: limits.employees,
|
|
1463
|
+
memoryLimit: limits.memories
|
|
1464
|
+
};
|
|
1465
|
+
} catch {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
async function validateLicense(apiKey, deviceId) {
|
|
1470
|
+
const did = deviceId ?? loadDeviceId();
|
|
1471
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
1472
|
+
if (pgResult) {
|
|
1473
|
+
try {
|
|
1474
|
+
writeFileSync4(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
1475
|
+
} catch {
|
|
1476
|
+
}
|
|
1477
|
+
return pgResult;
|
|
1478
|
+
}
|
|
1479
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
1480
|
+
if (cfResult) return cfResult;
|
|
1481
|
+
const cached = await getCachedLicense();
|
|
1482
|
+
if (cached) return cached;
|
|
1483
|
+
try {
|
|
1484
|
+
if (existsSync8(CACHE_PATH)) {
|
|
1485
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1486
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
1487
|
+
return raw.pgLicense;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1492
|
+
const rawFallback = getRawCachedPlan();
|
|
1493
|
+
if (rawFallback) return rawFallback;
|
|
1494
|
+
return { ...FREE_LICENSE, valid: false };
|
|
1495
|
+
}
|
|
1496
|
+
function getCacheAgeMs() {
|
|
1497
|
+
try {
|
|
1498
|
+
const { statSync: statSync3 } = __require("fs");
|
|
1499
|
+
const s = statSync3(CACHE_PATH);
|
|
1500
|
+
return Date.now() - s.mtimeMs;
|
|
1501
|
+
} catch {
|
|
1502
|
+
return Infinity;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
async function checkLicense() {
|
|
1506
|
+
let key = loadLicense();
|
|
1507
|
+
if (!key) {
|
|
1508
|
+
try {
|
|
1509
|
+
const configPath = path7.join(EXE_AI_DIR, "config.json");
|
|
1510
|
+
if (existsSync8(configPath)) {
|
|
1511
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
1512
|
+
const cloud = raw.cloud;
|
|
1513
|
+
if (cloud?.apiKey) {
|
|
1514
|
+
key = cloud.apiKey;
|
|
1515
|
+
saveLicense(key);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
} catch {
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (!key) return FREE_LICENSE;
|
|
1522
|
+
const cached = await getCachedLicense();
|
|
1523
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
1524
|
+
const deviceId = loadDeviceId();
|
|
1525
|
+
return validateLicense(key, deviceId);
|
|
1526
|
+
}
|
|
1527
|
+
function isFeatureAllowed(license, feature) {
|
|
1528
|
+
switch (feature) {
|
|
1529
|
+
case "cloud_sync":
|
|
1530
|
+
case "external_agents":
|
|
1531
|
+
case "wiki":
|
|
1532
|
+
return license.plan !== "free";
|
|
1533
|
+
case "unlimited_employees":
|
|
1534
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
function mirrorLicenseKey(apiKey) {
|
|
1538
|
+
const trimmed = apiKey.trim();
|
|
1539
|
+
if (!trimmed) return;
|
|
1540
|
+
saveLicense(trimmed);
|
|
1541
|
+
}
|
|
1542
|
+
async function assertVpsLicense(opts) {
|
|
1543
|
+
const env = opts?.env ?? process.env;
|
|
1544
|
+
const inProduction = env.NODE_ENV === "production";
|
|
1545
|
+
if (!opts?.force && !inProduction) {
|
|
1546
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
1547
|
+
}
|
|
1548
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
1549
|
+
if (envKey) {
|
|
1550
|
+
saveLicense(envKey);
|
|
1551
|
+
}
|
|
1552
|
+
const apiKey = envKey ?? loadLicense();
|
|
1553
|
+
if (!apiKey) {
|
|
1554
|
+
throw new Error(
|
|
1555
|
+
"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."
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
const deviceId = loadDeviceId();
|
|
1559
|
+
let backendResponse = null;
|
|
1560
|
+
let explicitRejection = false;
|
|
1561
|
+
let transientFailure = false;
|
|
1562
|
+
try {
|
|
1563
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1564
|
+
method: "POST",
|
|
1565
|
+
headers: { "Content-Type": "application/json" },
|
|
1566
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
1567
|
+
signal: AbortSignal.timeout(1e4)
|
|
1568
|
+
});
|
|
1569
|
+
if (res.ok) {
|
|
1570
|
+
backendResponse = await res.json();
|
|
1571
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
1572
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
1573
|
+
explicitRejection = true;
|
|
1574
|
+
} else {
|
|
1575
|
+
transientFailure = true;
|
|
1576
|
+
}
|
|
1577
|
+
} catch {
|
|
1578
|
+
transientFailure = true;
|
|
1579
|
+
}
|
|
1580
|
+
if (backendResponse?.valid) {
|
|
1581
|
+
if (backendResponse.token) {
|
|
1582
|
+
cacheResponse(backendResponse.token);
|
|
1583
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
1584
|
+
if (verified) return verified;
|
|
1585
|
+
}
|
|
1586
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
1587
|
+
return {
|
|
1588
|
+
valid: true,
|
|
1589
|
+
plan: backendResponse.plan,
|
|
1590
|
+
email: backendResponse.email,
|
|
1591
|
+
expiresAt: backendResponse.expiresAt,
|
|
1592
|
+
deviceLimit: limits.devices,
|
|
1593
|
+
employeeLimit: limits.employees,
|
|
1594
|
+
memoryLimit: limits.memories
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
if (explicitRejection) {
|
|
1598
|
+
throw new Error(
|
|
1599
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
if (!transientFailure) {
|
|
1603
|
+
throw new Error(
|
|
1604
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
const fresh = await getCachedLicense();
|
|
1608
|
+
if (fresh && fresh.valid) return fresh;
|
|
1609
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
1610
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
1611
|
+
try {
|
|
1612
|
+
const token = readCachedLicenseToken();
|
|
1613
|
+
if (token) {
|
|
1614
|
+
const payloadB64 = token.split(".")[1];
|
|
1615
|
+
if (payloadB64) {
|
|
1616
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
1617
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
1618
|
+
if (Date.now() < expMs + graceMs) {
|
|
1619
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1620
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
1621
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
1622
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
1623
|
+
});
|
|
1624
|
+
const plan = verified.plan ?? "free";
|
|
1625
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1626
|
+
return {
|
|
1627
|
+
valid: true,
|
|
1628
|
+
plan,
|
|
1629
|
+
email: verified.sub ?? "",
|
|
1630
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
1631
|
+
deviceLimit: limits.devices,
|
|
1632
|
+
employeeLimit: limits.employees,
|
|
1633
|
+
memoryLimit: limits.memories
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
} catch {
|
|
1639
|
+
}
|
|
1640
|
+
throw new Error(
|
|
1641
|
+
`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.`
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
1645
|
+
if (_revalTimer) return;
|
|
1646
|
+
_revalTimer = setInterval(async () => {
|
|
1647
|
+
try {
|
|
1648
|
+
const license = await checkLicense();
|
|
1649
|
+
if (!license.valid) {
|
|
1650
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
1651
|
+
}
|
|
1652
|
+
} catch {
|
|
1653
|
+
}
|
|
1654
|
+
}, intervalMs);
|
|
1655
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
1656
|
+
_revalTimer.unref();
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
function stopLicenseRevalidation() {
|
|
1660
|
+
if (_revalTimer) {
|
|
1661
|
+
clearInterval(_revalTimer);
|
|
1662
|
+
_revalTimer = null;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
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;
|
|
1666
|
+
var init_license = __esm({
|
|
1667
|
+
"src/lib/license.ts"() {
|
|
1668
|
+
"use strict";
|
|
1669
|
+
init_config();
|
|
1670
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
1671
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1672
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1673
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
1674
|
+
RETRY_DELAY_MS = 500;
|
|
1675
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1676
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1677
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
1678
|
+
-----END PUBLIC KEY-----`;
|
|
1679
|
+
LICENSE_JWT_ALG = "ES256";
|
|
1680
|
+
PLAN_LIMITS = {
|
|
1681
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1682
|
+
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
1683
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
1684
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
1685
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
1686
|
+
};
|
|
1687
|
+
FREE_LICENSE = {
|
|
1688
|
+
valid: true,
|
|
1689
|
+
plan: "free",
|
|
1690
|
+
email: "",
|
|
1691
|
+
expiresAt: null,
|
|
1692
|
+
deviceLimit: 1,
|
|
1693
|
+
employeeLimit: 1,
|
|
1694
|
+
memoryLimit: 5e3
|
|
1695
|
+
};
|
|
1696
|
+
_prismaPromise = null;
|
|
1697
|
+
_prismaFailed = false;
|
|
1698
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
1699
|
+
_revalTimer = null;
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
|
|
842
1703
|
// src/lib/skill-learning.ts
|
|
843
1704
|
import crypto3 from "crypto";
|
|
844
1705
|
|
|
845
1706
|
// src/lib/database.ts
|
|
846
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
1707
|
+
import { chmodSync as chmodSync2, existsSync as existsSync5, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
|
|
847
1708
|
import { createClient } from "@libsql/client";
|
|
848
1709
|
import { homedir } from "os";
|
|
849
1710
|
import { join } from "path";
|
|
850
|
-
|
|
851
|
-
// src/lib/employees.ts
|
|
852
|
-
init_config();
|
|
853
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
854
|
-
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
855
|
-
import { execSync } from "child_process";
|
|
856
|
-
import path2 from "path";
|
|
857
|
-
import os2 from "os";
|
|
858
|
-
var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
859
|
-
var IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
1711
|
+
init_employees();
|
|
860
1712
|
|
|
861
1713
|
// src/lib/database-adapter.ts
|
|
862
1714
|
import os3 from "os";
|
|
863
|
-
import
|
|
1715
|
+
import path4 from "path";
|
|
864
1716
|
import { createRequire } from "module";
|
|
865
1717
|
import { pathToFileURL } from "url";
|
|
866
1718
|
var BOOLEAN_COLUMNS_BY_TABLE = {
|
|
@@ -916,6 +1768,15 @@ function getClient() {
|
|
|
916
1768
|
// src/lib/behaviors.ts
|
|
917
1769
|
import crypto2 from "crypto";
|
|
918
1770
|
async function storeBehavior(opts) {
|
|
1771
|
+
try {
|
|
1772
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
1773
|
+
const roster = loadEmployeesSync2();
|
|
1774
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
1775
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
1776
|
+
}
|
|
1777
|
+
} catch (e) {
|
|
1778
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
1779
|
+
}
|
|
919
1780
|
const client = getClient();
|
|
920
1781
|
const id = crypto2.randomUUID();
|
|
921
1782
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -926,10 +1787,18 @@ async function storeBehavior(opts) {
|
|
|
926
1787
|
vector = new Float32Array(vec);
|
|
927
1788
|
} catch {
|
|
928
1789
|
}
|
|
1790
|
+
let createdByDevice = null;
|
|
1791
|
+
try {
|
|
1792
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
1793
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
1797
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
929
1798
|
await client.execute({
|
|
930
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
931
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
932
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
1799
|
+
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)
|
|
1800
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
1801
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
933
1802
|
});
|
|
934
1803
|
return id;
|
|
935
1804
|
}
|