@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
package/dist/runtime/index.js
CHANGED
|
@@ -330,11 +330,168 @@ var init_config = __esm({
|
|
|
330
330
|
}
|
|
331
331
|
});
|
|
332
332
|
|
|
333
|
+
// src/lib/runtime-table.ts
|
|
334
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
335
|
+
var init_runtime_table = __esm({
|
|
336
|
+
"src/lib/runtime-table.ts"() {
|
|
337
|
+
"use strict";
|
|
338
|
+
RUNTIME_TABLE = {
|
|
339
|
+
codex: {
|
|
340
|
+
binary: "codex",
|
|
341
|
+
launchMode: "interactive",
|
|
342
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
343
|
+
inlineFlag: "--no-alt-screen",
|
|
344
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
345
|
+
defaultModel: "gpt-5.5"
|
|
346
|
+
},
|
|
347
|
+
opencode: {
|
|
348
|
+
binary: "opencode",
|
|
349
|
+
launchMode: "exec",
|
|
350
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
351
|
+
inlineFlag: "",
|
|
352
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
353
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
DEFAULT_RUNTIME = "claude";
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// src/lib/agent-config.ts
|
|
361
|
+
var agent_config_exports = {};
|
|
362
|
+
__export(agent_config_exports, {
|
|
363
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
364
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
365
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
366
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
367
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
368
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
369
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
370
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
371
|
+
setAgentMcps: () => setAgentMcps,
|
|
372
|
+
setAgentRuntime: () => setAgentRuntime
|
|
373
|
+
});
|
|
374
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
375
|
+
import path3 from "path";
|
|
376
|
+
function loadAgentConfig() {
|
|
377
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
378
|
+
try {
|
|
379
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
380
|
+
} catch {
|
|
381
|
+
return {};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function saveAgentConfig(config) {
|
|
385
|
+
const dir = path3.dirname(AGENT_CONFIG_PATH);
|
|
386
|
+
ensurePrivateDirSync(dir);
|
|
387
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
388
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
389
|
+
}
|
|
390
|
+
function getAgentRuntime(agentId) {
|
|
391
|
+
const config = loadAgentConfig();
|
|
392
|
+
const entry = config[agentId];
|
|
393
|
+
if (entry) return entry;
|
|
394
|
+
const orgDefault = config["default"];
|
|
395
|
+
if (orgDefault) return orgDefault;
|
|
396
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
397
|
+
}
|
|
398
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
399
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
400
|
+
if (!knownModels) {
|
|
401
|
+
return {
|
|
402
|
+
ok: false,
|
|
403
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (!knownModels.includes(model)) {
|
|
407
|
+
return {
|
|
408
|
+
ok: false,
|
|
409
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const config = loadAgentConfig();
|
|
413
|
+
const existing = config[agentId];
|
|
414
|
+
const entry = { runtime, model };
|
|
415
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
416
|
+
if (mcps !== void 0) {
|
|
417
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
418
|
+
} else if (existing?.mcps) {
|
|
419
|
+
entry.mcps = existing.mcps;
|
|
420
|
+
}
|
|
421
|
+
config[agentId] = entry;
|
|
422
|
+
saveAgentConfig(config);
|
|
423
|
+
return { ok: true };
|
|
424
|
+
}
|
|
425
|
+
function setAgentMcps(agentId, mcps) {
|
|
426
|
+
const config = loadAgentConfig();
|
|
427
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
428
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
429
|
+
config[agentId] = existing;
|
|
430
|
+
saveAgentConfig(config);
|
|
431
|
+
return { ok: true };
|
|
432
|
+
}
|
|
433
|
+
function clearAgentRuntime(agentId) {
|
|
434
|
+
const config = loadAgentConfig();
|
|
435
|
+
delete config[agentId];
|
|
436
|
+
saveAgentConfig(config);
|
|
437
|
+
}
|
|
438
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
439
|
+
var init_agent_config = __esm({
|
|
440
|
+
"src/lib/agent-config.ts"() {
|
|
441
|
+
"use strict";
|
|
442
|
+
init_config();
|
|
443
|
+
init_runtime_table();
|
|
444
|
+
init_secure_files();
|
|
445
|
+
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
446
|
+
KNOWN_RUNTIMES = {
|
|
447
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
448
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
449
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
450
|
+
};
|
|
451
|
+
RUNTIME_LABELS = {
|
|
452
|
+
claude: "Claude Code (Anthropic)",
|
|
453
|
+
codex: "Codex (OpenAI)",
|
|
454
|
+
opencode: "OpenCode (open source)"
|
|
455
|
+
};
|
|
456
|
+
DEFAULT_MODELS = {
|
|
457
|
+
claude: "claude-opus-4.6",
|
|
458
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
459
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
333
464
|
// src/lib/employees.ts
|
|
465
|
+
var employees_exports = {};
|
|
466
|
+
__export(employees_exports, {
|
|
467
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
468
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
469
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
470
|
+
addEmployee: () => addEmployee,
|
|
471
|
+
baseAgentName: () => baseAgentName,
|
|
472
|
+
canCoordinate: () => canCoordinate,
|
|
473
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
474
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
475
|
+
getEmployee: () => getEmployee,
|
|
476
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
477
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
478
|
+
hasRole: () => hasRole,
|
|
479
|
+
hireEmployee: () => hireEmployee,
|
|
480
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
481
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
482
|
+
isMultiInstance: () => isMultiInstance,
|
|
483
|
+
loadEmployees: () => loadEmployees,
|
|
484
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
485
|
+
normalizeRole: () => normalizeRole,
|
|
486
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
487
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
488
|
+
saveEmployees: () => saveEmployees,
|
|
489
|
+
validateEmployeeName: () => validateEmployeeName
|
|
490
|
+
});
|
|
334
491
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
335
|
-
import { existsSync as
|
|
492
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
336
493
|
import { execSync } from "child_process";
|
|
337
|
-
import
|
|
494
|
+
import path4 from "path";
|
|
338
495
|
import os3 from "os";
|
|
339
496
|
function normalizeRole(role) {
|
|
340
497
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -352,8 +509,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
352
509
|
if (!agentName) return false;
|
|
353
510
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
354
511
|
}
|
|
512
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
513
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
514
|
+
}
|
|
515
|
+
function validateEmployeeName(name) {
|
|
516
|
+
if (!name) {
|
|
517
|
+
return { valid: false, error: "Name is required" };
|
|
518
|
+
}
|
|
519
|
+
if (name.length > 32) {
|
|
520
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
521
|
+
}
|
|
522
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
523
|
+
return {
|
|
524
|
+
valid: false,
|
|
525
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return { valid: true };
|
|
529
|
+
}
|
|
355
530
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
356
|
-
if (!
|
|
531
|
+
if (!existsSync4(employeesPath)) {
|
|
357
532
|
return [];
|
|
358
533
|
}
|
|
359
534
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -363,10 +538,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
363
538
|
return [];
|
|
364
539
|
}
|
|
365
540
|
}
|
|
541
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
542
|
+
await mkdir2(path4.dirname(employeesPath), { recursive: true });
|
|
543
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
544
|
+
}
|
|
366
545
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
367
|
-
if (!
|
|
546
|
+
if (!existsSync4(employeesPath)) return [];
|
|
368
547
|
try {
|
|
369
|
-
return JSON.parse(
|
|
548
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
370
549
|
} catch {
|
|
371
550
|
return [];
|
|
372
551
|
}
|
|
@@ -374,6 +553,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
374
553
|
function getEmployee(employees, name) {
|
|
375
554
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
376
555
|
}
|
|
556
|
+
function getEmployeeByRole(employees, role) {
|
|
557
|
+
const lower = role.toLowerCase();
|
|
558
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
559
|
+
}
|
|
560
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
561
|
+
const lower = role.toLowerCase();
|
|
562
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
563
|
+
}
|
|
564
|
+
function hasRole(agentName, role) {
|
|
565
|
+
const employees = loadEmployeesSync();
|
|
566
|
+
const emp = getEmployee(employees, agentName);
|
|
567
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
568
|
+
}
|
|
377
569
|
function baseAgentName(name, employees) {
|
|
378
570
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
379
571
|
if (!match) return name;
|
|
@@ -388,16 +580,141 @@ function isMultiInstance(agentName, employees) {
|
|
|
388
580
|
if (!emp) return false;
|
|
389
581
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
390
582
|
}
|
|
391
|
-
|
|
583
|
+
function addEmployee(employees, employee) {
|
|
584
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
585
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
586
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
587
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
588
|
+
}
|
|
589
|
+
return [...employees, normalized];
|
|
590
|
+
}
|
|
591
|
+
function appendToCoordinatorTeam(employee) {
|
|
592
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
593
|
+
if (!coordinator) return;
|
|
594
|
+
const idPath = path4.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
595
|
+
if (!existsSync4(idPath)) return;
|
|
596
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
597
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
598
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
599
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
600
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
601
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
602
|
+
const entry = `
|
|
603
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
604
|
+
`;
|
|
605
|
+
let updated;
|
|
606
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
607
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
608
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
609
|
+
} else {
|
|
610
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
611
|
+
}
|
|
612
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
613
|
+
}
|
|
614
|
+
function capitalize(s) {
|
|
615
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
616
|
+
}
|
|
617
|
+
async function hireEmployee(employee) {
|
|
618
|
+
const employees = await loadEmployees();
|
|
619
|
+
const updated = addEmployee(employees, employee);
|
|
620
|
+
await saveEmployees(updated);
|
|
621
|
+
try {
|
|
622
|
+
appendToCoordinatorTeam(employee);
|
|
623
|
+
} catch {
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
627
|
+
const config = loadAgentConfig2();
|
|
628
|
+
const name = employee.name.toLowerCase();
|
|
629
|
+
if (!config[name] && config["default"]) {
|
|
630
|
+
config[name] = { ...config["default"] };
|
|
631
|
+
saveAgentConfig2(config);
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
return updated;
|
|
636
|
+
}
|
|
637
|
+
async function normalizeRosterCase(rosterPath) {
|
|
638
|
+
const employees = await loadEmployees(rosterPath);
|
|
639
|
+
let changed = false;
|
|
640
|
+
for (const emp of employees) {
|
|
641
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
642
|
+
const oldName = emp.name;
|
|
643
|
+
emp.name = emp.name.toLowerCase();
|
|
644
|
+
changed = true;
|
|
645
|
+
try {
|
|
646
|
+
const identityDir = path4.join(os3.homedir(), ".exe-os", "identity");
|
|
647
|
+
const oldPath = path4.join(identityDir, `${oldName}.md`);
|
|
648
|
+
const newPath = path4.join(identityDir, `${emp.name}.md`);
|
|
649
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
650
|
+
renameSync2(oldPath, newPath);
|
|
651
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
652
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
653
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
654
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
655
|
+
unlinkSync(oldPath);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
} catch {
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (changed) {
|
|
663
|
+
await saveEmployees(employees, rosterPath);
|
|
664
|
+
}
|
|
665
|
+
return changed;
|
|
666
|
+
}
|
|
667
|
+
function findExeBin() {
|
|
668
|
+
try {
|
|
669
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
670
|
+
} catch {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function registerBinSymlinks(name) {
|
|
675
|
+
const created = [];
|
|
676
|
+
const skipped = [];
|
|
677
|
+
const errors = [];
|
|
678
|
+
const exeBinPath = findExeBin();
|
|
679
|
+
if (!exeBinPath) {
|
|
680
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
681
|
+
return { created, skipped, errors };
|
|
682
|
+
}
|
|
683
|
+
const binDir = path4.dirname(exeBinPath);
|
|
684
|
+
let target;
|
|
685
|
+
try {
|
|
686
|
+
target = readlinkSync(exeBinPath);
|
|
687
|
+
} catch {
|
|
688
|
+
errors.push("Could not read 'exe' symlink");
|
|
689
|
+
return { created, skipped, errors };
|
|
690
|
+
}
|
|
691
|
+
for (const suffix of ["", "-opencode"]) {
|
|
692
|
+
const linkName = `${name}${suffix}`;
|
|
693
|
+
const linkPath = path4.join(binDir, linkName);
|
|
694
|
+
if (existsSync4(linkPath)) {
|
|
695
|
+
skipped.push(linkName);
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
symlinkSync(target, linkPath);
|
|
700
|
+
created.push(linkName);
|
|
701
|
+
} catch (err) {
|
|
702
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return { created, skipped, errors };
|
|
706
|
+
}
|
|
707
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
392
708
|
var init_employees = __esm({
|
|
393
709
|
"src/lib/employees.ts"() {
|
|
394
710
|
"use strict";
|
|
395
711
|
init_config();
|
|
396
|
-
EMPLOYEES_PATH =
|
|
712
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
397
713
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
398
714
|
COORDINATOR_ROLE = "COO";
|
|
399
715
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
400
|
-
IDENTITY_DIR =
|
|
716
|
+
IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
|
|
717
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
401
718
|
}
|
|
402
719
|
});
|
|
403
720
|
|
|
@@ -409,13 +726,13 @@ __export(session_registry_exports, {
|
|
|
409
726
|
refreshSessionProject: () => refreshSessionProject,
|
|
410
727
|
registerSession: () => registerSession
|
|
411
728
|
});
|
|
412
|
-
import { readFileSync as
|
|
729
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
413
730
|
import { execSync as execSync2 } from "child_process";
|
|
414
|
-
import
|
|
731
|
+
import path5 from "path";
|
|
415
732
|
import os4 from "os";
|
|
416
733
|
function registerSession(entry) {
|
|
417
|
-
const dir =
|
|
418
|
-
if (!
|
|
734
|
+
const dir = path5.dirname(REGISTRY_PATH);
|
|
735
|
+
if (!existsSync5(dir)) {
|
|
419
736
|
mkdirSync2(dir, { recursive: true });
|
|
420
737
|
}
|
|
421
738
|
const sessions = listSessions();
|
|
@@ -425,7 +742,7 @@ function registerSession(entry) {
|
|
|
425
742
|
} else {
|
|
426
743
|
sessions.push(entry);
|
|
427
744
|
}
|
|
428
|
-
|
|
745
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
429
746
|
}
|
|
430
747
|
function refreshSessionProject(windowName, projectDir) {
|
|
431
748
|
const sessions = listSessions();
|
|
@@ -433,13 +750,13 @@ function refreshSessionProject(windowName, projectDir) {
|
|
|
433
750
|
if (!entry || entry.projectDir === projectDir) return;
|
|
434
751
|
entry.projectDir = projectDir;
|
|
435
752
|
try {
|
|
436
|
-
|
|
753
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
437
754
|
} catch {
|
|
438
755
|
}
|
|
439
756
|
}
|
|
440
757
|
function listSessions() {
|
|
441
758
|
try {
|
|
442
|
-
const raw =
|
|
759
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
443
760
|
return JSON.parse(raw);
|
|
444
761
|
} catch {
|
|
445
762
|
return [];
|
|
@@ -460,7 +777,7 @@ function pruneStaleSessions() {
|
|
|
460
777
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
461
778
|
const pruned = sessions.length - alive.length;
|
|
462
779
|
if (pruned > 0) {
|
|
463
|
-
|
|
780
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
464
781
|
}
|
|
465
782
|
return pruned;
|
|
466
783
|
}
|
|
@@ -468,7 +785,7 @@ var REGISTRY_PATH;
|
|
|
468
785
|
var init_session_registry = __esm({
|
|
469
786
|
"src/lib/session-registry.ts"() {
|
|
470
787
|
"use strict";
|
|
471
|
-
REGISTRY_PATH =
|
|
788
|
+
REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
472
789
|
}
|
|
473
790
|
});
|
|
474
791
|
|
|
@@ -740,68 +1057,6 @@ var init_provider_table = __esm({
|
|
|
740
1057
|
}
|
|
741
1058
|
});
|
|
742
1059
|
|
|
743
|
-
// src/lib/runtime-table.ts
|
|
744
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
745
|
-
var init_runtime_table = __esm({
|
|
746
|
-
"src/lib/runtime-table.ts"() {
|
|
747
|
-
"use strict";
|
|
748
|
-
RUNTIME_TABLE = {
|
|
749
|
-
codex: {
|
|
750
|
-
binary: "codex",
|
|
751
|
-
launchMode: "interactive",
|
|
752
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
753
|
-
inlineFlag: "--no-alt-screen",
|
|
754
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
755
|
-
defaultModel: "gpt-5.5"
|
|
756
|
-
},
|
|
757
|
-
opencode: {
|
|
758
|
-
binary: "opencode",
|
|
759
|
-
launchMode: "exec",
|
|
760
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
761
|
-
inlineFlag: "",
|
|
762
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
763
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
DEFAULT_RUNTIME = "claude";
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// src/lib/agent-config.ts
|
|
771
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
772
|
-
import path5 from "path";
|
|
773
|
-
function loadAgentConfig() {
|
|
774
|
-
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
775
|
-
try {
|
|
776
|
-
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
777
|
-
} catch {
|
|
778
|
-
return {};
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
function getAgentRuntime(agentId) {
|
|
782
|
-
const config = loadAgentConfig();
|
|
783
|
-
const entry = config[agentId];
|
|
784
|
-
if (entry) return entry;
|
|
785
|
-
const orgDefault = config["default"];
|
|
786
|
-
if (orgDefault) return orgDefault;
|
|
787
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
788
|
-
}
|
|
789
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
790
|
-
var init_agent_config = __esm({
|
|
791
|
-
"src/lib/agent-config.ts"() {
|
|
792
|
-
"use strict";
|
|
793
|
-
init_config();
|
|
794
|
-
init_runtime_table();
|
|
795
|
-
init_secure_files();
|
|
796
|
-
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
797
|
-
DEFAULT_MODELS = {
|
|
798
|
-
claude: "claude-opus-4.6",
|
|
799
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
800
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
|
|
805
1060
|
// src/lib/intercom-queue.ts
|
|
806
1061
|
var intercom_queue_exports = {};
|
|
807
1062
|
__export(intercom_queue_exports, {
|
|
@@ -2595,6 +2850,13 @@ async function ensureSchema() {
|
|
|
2595
2850
|
} catch (e) {
|
|
2596
2851
|
logCatchDebug("migration", e);
|
|
2597
2852
|
}
|
|
2853
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2854
|
+
try {
|
|
2855
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2856
|
+
} catch (e) {
|
|
2857
|
+
logCatchDebug("migration", e);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2598
2860
|
try {
|
|
2599
2861
|
await client.execute({
|
|
2600
2862
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3811,6 +4073,22 @@ async function ensureSchema() {
|
|
|
3811
4073
|
} catch (e) {
|
|
3812
4074
|
logCatchDebug("migration", e);
|
|
3813
4075
|
}
|
|
4076
|
+
try {
|
|
4077
|
+
await client.execute({
|
|
4078
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
4079
|
+
args: []
|
|
4080
|
+
});
|
|
4081
|
+
} catch (e) {
|
|
4082
|
+
logCatchDebug("migration", e);
|
|
4083
|
+
}
|
|
4084
|
+
try {
|
|
4085
|
+
await client.execute({
|
|
4086
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
4087
|
+
args: []
|
|
4088
|
+
});
|
|
4089
|
+
} catch (e) {
|
|
4090
|
+
logCatchDebug("migration", e);
|
|
4091
|
+
}
|
|
3814
4092
|
}
|
|
3815
4093
|
async function disposeDatabase() {
|
|
3816
4094
|
if (_walCheckpointTimer) {
|
|
@@ -3862,6 +4140,23 @@ var init_database = __esm({
|
|
|
3862
4140
|
});
|
|
3863
4141
|
|
|
3864
4142
|
// src/lib/license.ts
|
|
4143
|
+
var license_exports = {};
|
|
4144
|
+
__export(license_exports, {
|
|
4145
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
4146
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
4147
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
4148
|
+
checkLicense: () => checkLicense,
|
|
4149
|
+
getCachedLicense: () => getCachedLicense,
|
|
4150
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
4151
|
+
loadDeviceId: () => loadDeviceId,
|
|
4152
|
+
loadLicense: () => loadLicense,
|
|
4153
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
4154
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
4155
|
+
saveLicense: () => saveLicense,
|
|
4156
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
4157
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
4158
|
+
validateLicense: () => validateLicense
|
|
4159
|
+
});
|
|
3865
4160
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
3866
4161
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3867
4162
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -3869,7 +4164,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
3869
4164
|
import os8 from "os";
|
|
3870
4165
|
import path10 from "path";
|
|
3871
4166
|
import { jwtVerify, importSPKI } from "jose";
|
|
3872
|
-
|
|
4167
|
+
async function fetchRetry(url, init) {
|
|
4168
|
+
try {
|
|
4169
|
+
return await fetch(url, init);
|
|
4170
|
+
} catch {
|
|
4171
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
4172
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
function loadDeviceId() {
|
|
4176
|
+
const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
|
|
4177
|
+
try {
|
|
4178
|
+
if (existsSync10(deviceJsonPath)) {
|
|
4179
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
4180
|
+
if (data.deviceId) return data.deviceId;
|
|
4181
|
+
}
|
|
4182
|
+
} catch {
|
|
4183
|
+
}
|
|
4184
|
+
try {
|
|
4185
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
4186
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
4187
|
+
if (id2) return id2;
|
|
4188
|
+
}
|
|
4189
|
+
} catch {
|
|
4190
|
+
}
|
|
4191
|
+
const id = randomUUID3();
|
|
4192
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
4193
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
4194
|
+
return id;
|
|
4195
|
+
}
|
|
4196
|
+
function loadLicense() {
|
|
4197
|
+
try {
|
|
4198
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
4199
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
4200
|
+
} catch {
|
|
4201
|
+
return null;
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
function saveLicense(apiKey) {
|
|
4205
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
4206
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
4207
|
+
}
|
|
4208
|
+
async function verifyLicenseJwt(token) {
|
|
4209
|
+
try {
|
|
4210
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4211
|
+
const { payload } = await jwtVerify(token, key, {
|
|
4212
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
4213
|
+
});
|
|
4214
|
+
const plan = payload.plan ?? "free";
|
|
4215
|
+
const email = payload.sub ?? "";
|
|
4216
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4217
|
+
return {
|
|
4218
|
+
valid: true,
|
|
4219
|
+
plan,
|
|
4220
|
+
email,
|
|
4221
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4222
|
+
deviceLimit: limits.devices,
|
|
4223
|
+
employeeLimit: limits.employees,
|
|
4224
|
+
memoryLimit: limits.memories
|
|
4225
|
+
};
|
|
4226
|
+
} catch {
|
|
4227
|
+
return null;
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
async function getCachedLicense() {
|
|
4231
|
+
try {
|
|
4232
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4233
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4234
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
4235
|
+
return await verifyLicenseJwt(raw.token);
|
|
4236
|
+
} catch {
|
|
4237
|
+
return null;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
function readCachedLicenseToken() {
|
|
4241
|
+
try {
|
|
4242
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4243
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4244
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
4245
|
+
} catch {
|
|
4246
|
+
return null;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
function getRawCachedPlan() {
|
|
4250
|
+
try {
|
|
4251
|
+
const token = readCachedLicenseToken();
|
|
4252
|
+
if (!token) return null;
|
|
4253
|
+
const parts = token.split(".");
|
|
4254
|
+
if (parts.length !== 3) return null;
|
|
4255
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
4256
|
+
const plan = payload.plan ?? "free";
|
|
4257
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4258
|
+
process.stderr.write(
|
|
4259
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
4260
|
+
`
|
|
4261
|
+
);
|
|
4262
|
+
return {
|
|
4263
|
+
valid: true,
|
|
4264
|
+
plan,
|
|
4265
|
+
email: payload.sub ?? "",
|
|
4266
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4267
|
+
deviceLimit: limits.devices,
|
|
4268
|
+
employeeLimit: limits.employees,
|
|
4269
|
+
memoryLimit: limits.memories
|
|
4270
|
+
};
|
|
4271
|
+
} catch {
|
|
4272
|
+
return null;
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
function cacheResponse(token) {
|
|
4276
|
+
try {
|
|
4277
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
4278
|
+
} catch {
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
function loadPrismaForLicense() {
|
|
4282
|
+
if (_prismaFailed) return null;
|
|
4283
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
4284
|
+
if (!dbUrl) {
|
|
4285
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(os8.homedir(), "exe-db");
|
|
4286
|
+
if (!existsSync10(path10.join(exeDbRoot, "package.json"))) {
|
|
4287
|
+
_prismaFailed = true;
|
|
4288
|
+
return null;
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
if (!_prismaPromise) {
|
|
4292
|
+
_prismaPromise = (async () => {
|
|
4293
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4294
|
+
if (explicitPath) {
|
|
4295
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
4296
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
4297
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4298
|
+
return new Ctor2();
|
|
4299
|
+
}
|
|
4300
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(os8.homedir(), "exe-db");
|
|
4301
|
+
const req = createRequire2(path10.join(exeDbRoot, "package.json"));
|
|
4302
|
+
const entry = req.resolve("@prisma/client");
|
|
4303
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
4304
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4305
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
4306
|
+
return new Ctor();
|
|
4307
|
+
})().catch((err) => {
|
|
4308
|
+
_prismaFailed = true;
|
|
4309
|
+
_prismaPromise = null;
|
|
4310
|
+
throw err;
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
return _prismaPromise;
|
|
4314
|
+
}
|
|
4315
|
+
async function validateViaPostgres(apiKey) {
|
|
4316
|
+
const loader = loadPrismaForLicense();
|
|
4317
|
+
if (!loader) return null;
|
|
4318
|
+
try {
|
|
4319
|
+
const prisma = await loader;
|
|
4320
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
4321
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
4322
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
4323
|
+
apiKey
|
|
4324
|
+
);
|
|
4325
|
+
if (!rows || rows.length === 0) return null;
|
|
4326
|
+
const row = rows[0];
|
|
4327
|
+
if (row.status !== "active") return null;
|
|
4328
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
4329
|
+
const plan = row.plan;
|
|
4330
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4331
|
+
return {
|
|
4332
|
+
valid: true,
|
|
4333
|
+
plan,
|
|
4334
|
+
email: row.email,
|
|
4335
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
4336
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
4337
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
4338
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4339
|
+
};
|
|
4340
|
+
} catch {
|
|
4341
|
+
return null;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
4345
|
+
try {
|
|
4346
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4347
|
+
method: "POST",
|
|
4348
|
+
headers: { "Content-Type": "application/json" },
|
|
4349
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4350
|
+
signal: AbortSignal.timeout(1e4)
|
|
4351
|
+
});
|
|
4352
|
+
if (!res.ok) return null;
|
|
4353
|
+
const data = await res.json();
|
|
4354
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
4355
|
+
if (!data.valid) return null;
|
|
4356
|
+
if (data.token) {
|
|
4357
|
+
cacheResponse(data.token);
|
|
4358
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4359
|
+
if (verified) return verified;
|
|
4360
|
+
}
|
|
4361
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4362
|
+
return {
|
|
4363
|
+
valid: data.valid,
|
|
4364
|
+
plan: data.plan,
|
|
4365
|
+
email: data.email,
|
|
4366
|
+
expiresAt: data.expiresAt,
|
|
4367
|
+
deviceLimit: limits.devices,
|
|
4368
|
+
employeeLimit: limits.employees,
|
|
4369
|
+
memoryLimit: limits.memories
|
|
4370
|
+
};
|
|
4371
|
+
} catch {
|
|
4372
|
+
return null;
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4376
|
+
const did = deviceId ?? loadDeviceId();
|
|
4377
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
4378
|
+
if (pgResult) {
|
|
4379
|
+
try {
|
|
4380
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
4381
|
+
} catch {
|
|
4382
|
+
}
|
|
4383
|
+
return pgResult;
|
|
4384
|
+
}
|
|
4385
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
4386
|
+
if (cfResult) return cfResult;
|
|
4387
|
+
const cached = await getCachedLicense();
|
|
4388
|
+
if (cached) return cached;
|
|
4389
|
+
try {
|
|
4390
|
+
if (existsSync10(CACHE_PATH)) {
|
|
4391
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4392
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
4393
|
+
return raw.pgLicense;
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
} catch {
|
|
4397
|
+
}
|
|
4398
|
+
const rawFallback = getRawCachedPlan();
|
|
4399
|
+
if (rawFallback) return rawFallback;
|
|
4400
|
+
return { ...FREE_LICENSE, valid: false };
|
|
4401
|
+
}
|
|
4402
|
+
function getCacheAgeMs() {
|
|
4403
|
+
try {
|
|
4404
|
+
const { statSync: statSync5 } = __require("fs");
|
|
4405
|
+
const s = statSync5(CACHE_PATH);
|
|
4406
|
+
return Date.now() - s.mtimeMs;
|
|
4407
|
+
} catch {
|
|
4408
|
+
return Infinity;
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
async function checkLicense() {
|
|
4412
|
+
let key = loadLicense();
|
|
4413
|
+
if (!key) {
|
|
4414
|
+
try {
|
|
4415
|
+
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
4416
|
+
if (existsSync10(configPath)) {
|
|
4417
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
4418
|
+
const cloud = raw.cloud;
|
|
4419
|
+
if (cloud?.apiKey) {
|
|
4420
|
+
key = cloud.apiKey;
|
|
4421
|
+
saveLicense(key);
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
} catch {
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
if (!key) return FREE_LICENSE;
|
|
4428
|
+
const cached = await getCachedLicense();
|
|
4429
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
4430
|
+
const deviceId = loadDeviceId();
|
|
4431
|
+
return validateLicense(key, deviceId);
|
|
4432
|
+
}
|
|
4433
|
+
function isFeatureAllowed(license, feature) {
|
|
4434
|
+
switch (feature) {
|
|
4435
|
+
case "cloud_sync":
|
|
4436
|
+
case "external_agents":
|
|
4437
|
+
case "wiki":
|
|
4438
|
+
return license.plan !== "free";
|
|
4439
|
+
case "unlimited_employees":
|
|
4440
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
function mirrorLicenseKey(apiKey) {
|
|
4444
|
+
const trimmed = apiKey.trim();
|
|
4445
|
+
if (!trimmed) return;
|
|
4446
|
+
saveLicense(trimmed);
|
|
4447
|
+
}
|
|
4448
|
+
async function assertVpsLicense(opts) {
|
|
4449
|
+
const env = opts?.env ?? process.env;
|
|
4450
|
+
const inProduction = env.NODE_ENV === "production";
|
|
4451
|
+
if (!opts?.force && !inProduction) {
|
|
4452
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
4453
|
+
}
|
|
4454
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
4455
|
+
if (envKey) {
|
|
4456
|
+
saveLicense(envKey);
|
|
4457
|
+
}
|
|
4458
|
+
const apiKey = envKey ?? loadLicense();
|
|
4459
|
+
if (!apiKey) {
|
|
4460
|
+
throw new Error(
|
|
4461
|
+
"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."
|
|
4462
|
+
);
|
|
4463
|
+
}
|
|
4464
|
+
const deviceId = loadDeviceId();
|
|
4465
|
+
let backendResponse = null;
|
|
4466
|
+
let explicitRejection = false;
|
|
4467
|
+
let transientFailure = false;
|
|
4468
|
+
try {
|
|
4469
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4470
|
+
method: "POST",
|
|
4471
|
+
headers: { "Content-Type": "application/json" },
|
|
4472
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4473
|
+
signal: AbortSignal.timeout(1e4)
|
|
4474
|
+
});
|
|
4475
|
+
if (res.ok) {
|
|
4476
|
+
backendResponse = await res.json();
|
|
4477
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
4478
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4479
|
+
explicitRejection = true;
|
|
4480
|
+
} else {
|
|
4481
|
+
transientFailure = true;
|
|
4482
|
+
}
|
|
4483
|
+
} catch {
|
|
4484
|
+
transientFailure = true;
|
|
4485
|
+
}
|
|
4486
|
+
if (backendResponse?.valid) {
|
|
4487
|
+
if (backendResponse.token) {
|
|
4488
|
+
cacheResponse(backendResponse.token);
|
|
4489
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
4490
|
+
if (verified) return verified;
|
|
4491
|
+
}
|
|
4492
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
4493
|
+
return {
|
|
4494
|
+
valid: true,
|
|
4495
|
+
plan: backendResponse.plan,
|
|
4496
|
+
email: backendResponse.email,
|
|
4497
|
+
expiresAt: backendResponse.expiresAt,
|
|
4498
|
+
deviceLimit: limits.devices,
|
|
4499
|
+
employeeLimit: limits.employees,
|
|
4500
|
+
memoryLimit: limits.memories
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
if (explicitRejection) {
|
|
4504
|
+
throw new Error(
|
|
4505
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
4506
|
+
);
|
|
4507
|
+
}
|
|
4508
|
+
if (!transientFailure) {
|
|
4509
|
+
throw new Error(
|
|
4510
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
const fresh = await getCachedLicense();
|
|
4514
|
+
if (fresh && fresh.valid) return fresh;
|
|
4515
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
4516
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
4517
|
+
try {
|
|
4518
|
+
const token = readCachedLicenseToken();
|
|
4519
|
+
if (token) {
|
|
4520
|
+
const payloadB64 = token.split(".")[1];
|
|
4521
|
+
if (payloadB64) {
|
|
4522
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
4523
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
4524
|
+
if (Date.now() < expMs + graceMs) {
|
|
4525
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4526
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
4527
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
4528
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
4529
|
+
});
|
|
4530
|
+
const plan = verified.plan ?? "free";
|
|
4531
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4532
|
+
return {
|
|
4533
|
+
valid: true,
|
|
4534
|
+
plan,
|
|
4535
|
+
email: verified.sub ?? "",
|
|
4536
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
4537
|
+
deviceLimit: limits.devices,
|
|
4538
|
+
employeeLimit: limits.employees,
|
|
4539
|
+
memoryLimit: limits.memories
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
} catch {
|
|
4545
|
+
}
|
|
4546
|
+
throw new Error(
|
|
4547
|
+
`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.`
|
|
4548
|
+
);
|
|
4549
|
+
}
|
|
4550
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
4551
|
+
if (_revalTimer) return;
|
|
4552
|
+
_revalTimer = setInterval(async () => {
|
|
4553
|
+
try {
|
|
4554
|
+
const license = await checkLicense();
|
|
4555
|
+
if (!license.valid) {
|
|
4556
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
4557
|
+
}
|
|
4558
|
+
} catch {
|
|
4559
|
+
}
|
|
4560
|
+
}, intervalMs);
|
|
4561
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
4562
|
+
_revalTimer.unref();
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
function stopLicenseRevalidation() {
|
|
4566
|
+
if (_revalTimer) {
|
|
4567
|
+
clearInterval(_revalTimer);
|
|
4568
|
+
_revalTimer = null;
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
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;
|
|
3873
4572
|
var init_license = __esm({
|
|
3874
4573
|
"src/lib/license.ts"() {
|
|
3875
4574
|
"use strict";
|
|
@@ -3877,7 +4576,13 @@ var init_license = __esm({
|
|
|
3877
4576
|
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3878
4577
|
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3879
4578
|
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
3880
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
4579
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
4580
|
+
RETRY_DELAY_MS = 500;
|
|
4581
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4582
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
4583
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
4584
|
+
-----END PUBLIC KEY-----`;
|
|
4585
|
+
LICENSE_JWT_ALG = "ES256";
|
|
3881
4586
|
PLAN_LIMITS = {
|
|
3882
4587
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3883
4588
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3885,6 +4590,19 @@ var init_license = __esm({
|
|
|
3885
4590
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3886
4591
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3887
4592
|
};
|
|
4593
|
+
FREE_LICENSE = {
|
|
4594
|
+
valid: true,
|
|
4595
|
+
plan: "free",
|
|
4596
|
+
email: "",
|
|
4597
|
+
expiresAt: null,
|
|
4598
|
+
deviceLimit: 1,
|
|
4599
|
+
employeeLimit: 1,
|
|
4600
|
+
memoryLimit: 5e3
|
|
4601
|
+
};
|
|
4602
|
+
_prismaPromise = null;
|
|
4603
|
+
_prismaFailed = false;
|
|
4604
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
4605
|
+
_revalTimer = null;
|
|
3888
4606
|
}
|
|
3889
4607
|
});
|
|
3890
4608
|
|
|
@@ -4402,6 +5120,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4402
5120
|
args: [identifier, ...scope.args]
|
|
4403
5121
|
});
|
|
4404
5122
|
if (result.rows.length === 1) return result.rows[0];
|
|
5123
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
5124
|
+
result = await client.execute({
|
|
5125
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
5126
|
+
args: [`${identifier}%`]
|
|
5127
|
+
});
|
|
5128
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5129
|
+
if (result.rows.length > 1) {
|
|
5130
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
5131
|
+
throw new Error(
|
|
5132
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
5133
|
+
);
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
4405
5136
|
result = await client.execute({
|
|
4406
5137
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
4407
5138
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -5256,12 +5987,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5256
5987
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
5257
5988
|
args: [now, taskId]
|
|
5258
5989
|
});
|
|
5259
|
-
if (
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5990
|
+
if (unblocked.rowsAffected === 0) return;
|
|
5991
|
+
const ubScope = sessionScopeFilter();
|
|
5992
|
+
const unblockedRows = await client.execute({
|
|
5993
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
5994
|
+
args: [now, ...ubScope.args]
|
|
5995
|
+
});
|
|
5996
|
+
if (baseDir) {
|
|
5265
5997
|
for (const ur of unblockedRows.rows) {
|
|
5266
5998
|
try {
|
|
5267
5999
|
const ubFile = path17.join(baseDir, String(ur.task_file));
|
|
@@ -5273,6 +6005,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5273
6005
|
}
|
|
5274
6006
|
}
|
|
5275
6007
|
}
|
|
6008
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
6009
|
+
try {
|
|
6010
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
6011
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
6012
|
+
for (const ur of unblockedRows.rows) {
|
|
6013
|
+
const assignee = String(ur.assigned_to);
|
|
6014
|
+
if (dispatched.has(assignee)) continue;
|
|
6015
|
+
dispatched.add(assignee);
|
|
6016
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
6017
|
+
}
|
|
6018
|
+
} catch {
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
5276
6021
|
}
|
|
5277
6022
|
async function findNextTask(assignedTo) {
|
|
5278
6023
|
const client = getClient();
|
|
@@ -5489,6 +6234,15 @@ __export(behaviors_exports, {
|
|
|
5489
6234
|
});
|
|
5490
6235
|
import crypto5 from "crypto";
|
|
5491
6236
|
async function storeBehavior(opts) {
|
|
6237
|
+
try {
|
|
6238
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
6239
|
+
const roster = loadEmployeesSync2();
|
|
6240
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
6241
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
6242
|
+
}
|
|
6243
|
+
} catch (e) {
|
|
6244
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
6245
|
+
}
|
|
5492
6246
|
const client = getClient();
|
|
5493
6247
|
const id = crypto5.randomUUID();
|
|
5494
6248
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5499,17 +6253,25 @@ async function storeBehavior(opts) {
|
|
|
5499
6253
|
vector = new Float32Array(vec);
|
|
5500
6254
|
} catch {
|
|
5501
6255
|
}
|
|
6256
|
+
let createdByDevice = null;
|
|
6257
|
+
try {
|
|
6258
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
6259
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
6260
|
+
} catch {
|
|
6261
|
+
}
|
|
6262
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
6263
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
5502
6264
|
await client.execute({
|
|
5503
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
5504
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
5505
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
6265
|
+
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)
|
|
6266
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
6267
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
5506
6268
|
});
|
|
5507
6269
|
return id;
|
|
5508
6270
|
}
|
|
5509
6271
|
async function listBehaviors(agentId, projectName, limit = 30) {
|
|
5510
6272
|
const client = getClient();
|
|
5511
6273
|
const result = await client.execute({
|
|
5512
|
-
sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector
|
|
6274
|
+
sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id
|
|
5513
6275
|
FROM behaviors
|
|
5514
6276
|
WHERE agent_id = ? AND active = 1
|
|
5515
6277
|
AND (project_name IS NULL OR project_name = ?)
|
|
@@ -5538,7 +6300,10 @@ async function listBehaviors(agentId, projectName, limit = 30) {
|
|
|
5538
6300
|
active: Number(r.active),
|
|
5539
6301
|
created_at: String(r.created_at),
|
|
5540
6302
|
updated_at: String(r.updated_at),
|
|
5541
|
-
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
|
|
6303
|
+
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
|
|
6304
|
+
created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
|
|
6305
|
+
created_by_device: r.created_by_device ? String(r.created_by_device) : null,
|
|
6306
|
+
source_session_id: r.source_session_id ? String(r.source_session_id) : null
|
|
5542
6307
|
}));
|
|
5543
6308
|
}
|
|
5544
6309
|
async function listBehaviorsByDomain(agentId, domain) {
|
|
@@ -5559,7 +6324,10 @@ async function listBehaviorsByDomain(agentId, domain) {
|
|
|
5559
6324
|
active: Number(r.active),
|
|
5560
6325
|
created_at: String(r.created_at),
|
|
5561
6326
|
updated_at: String(r.updated_at),
|
|
5562
|
-
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
|
|
6327
|
+
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
|
|
6328
|
+
created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
|
|
6329
|
+
created_by_device: r.created_by_device ? String(r.created_by_device) : null,
|
|
6330
|
+
source_session_id: r.source_session_id ? String(r.source_session_id) : null
|
|
5563
6331
|
}));
|
|
5564
6332
|
}
|
|
5565
6333
|
async function deactivateBehavior(id) {
|
|
@@ -5998,6 +6766,12 @@ async function updateTask(input) {
|
|
|
5998
6766
|
}
|
|
5999
6767
|
}
|
|
6000
6768
|
}
|
|
6769
|
+
if (input.status === "cancelled") {
|
|
6770
|
+
try {
|
|
6771
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
6772
|
+
} catch {
|
|
6773
|
+
}
|
|
6774
|
+
}
|
|
6001
6775
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6002
6776
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6003
6777
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -6529,11 +7303,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6529
7303
|
}
|
|
6530
7304
|
}
|
|
6531
7305
|
function resolveExeSession() {
|
|
7306
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7307
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7308
|
+
if (fromEnv) return fromEnv;
|
|
7309
|
+
}
|
|
6532
7310
|
const mySession = getMySession();
|
|
6533
7311
|
if (!mySession) {
|
|
6534
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6535
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6536
|
-
}
|
|
6537
7312
|
return null;
|
|
6538
7313
|
}
|
|
6539
7314
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6548,6 +7323,10 @@ function resolveExeSession() {
|
|
|
6548
7323
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6549
7324
|
`
|
|
6550
7325
|
);
|
|
7326
|
+
try {
|
|
7327
|
+
registerParentExe(key, fromSessionName);
|
|
7328
|
+
} catch {
|
|
7329
|
+
}
|
|
6551
7330
|
candidate = fromSessionName;
|
|
6552
7331
|
} else {
|
|
6553
7332
|
candidate = fromCache;
|
|
@@ -8242,11 +9021,17 @@ var init_platform_procedures = __esm({
|
|
|
8242
9021
|
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."
|
|
8243
9022
|
},
|
|
8244
9023
|
{
|
|
8245
|
-
title: "
|
|
9024
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
8246
9025
|
domain: "workflow",
|
|
8247
9026
|
priority: "p1",
|
|
8248
9027
|
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."
|
|
8249
9028
|
},
|
|
9029
|
+
{
|
|
9030
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
9031
|
+
domain: "identity",
|
|
9032
|
+
priority: "p0",
|
|
9033
|
+
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."
|
|
9034
|
+
},
|
|
8250
9035
|
{
|
|
8251
9036
|
title: "Single dispatch path \u2014 create_task only",
|
|
8252
9037
|
domain: "workflow",
|
|
@@ -8280,6 +9065,12 @@ var init_platform_procedures = __esm({
|
|
|
8280
9065
|
priority: "p0",
|
|
8281
9066
|
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."
|
|
8282
9067
|
},
|
|
9068
|
+
{
|
|
9069
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
9070
|
+
domain: "security",
|
|
9071
|
+
priority: "p0",
|
|
9072
|
+
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."
|
|
9073
|
+
},
|
|
8283
9074
|
{
|
|
8284
9075
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
8285
9076
|
domain: "support",
|
|
@@ -8431,7 +9222,7 @@ var init_platform_procedures = __esm({
|
|
|
8431
9222
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
8432
9223
|
domain: "tool-use",
|
|
8433
9224
|
priority: "p0",
|
|
8434
|
-
content: 'exe-os MCP tools
|
|
9225
|
+
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.'
|
|
8435
9226
|
},
|
|
8436
9227
|
{
|
|
8437
9228
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -8565,10 +9356,24 @@ function stableId(memoryId, type, content) {
|
|
|
8565
9356
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
8566
9357
|
}
|
|
8567
9358
|
function cleanText(text) {
|
|
8568
|
-
|
|
9359
|
+
let cleaned = text.replace(
|
|
9360
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
9361
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
9362
|
+
);
|
|
9363
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9364
|
+
return cleaned;
|
|
8569
9365
|
}
|
|
8570
|
-
function
|
|
8571
|
-
|
|
9366
|
+
function splitSegments(text) {
|
|
9367
|
+
const cleaned = cleanText(text);
|
|
9368
|
+
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);
|
|
9369
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9370
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
9371
|
+
if (lines.length > 0) return lines;
|
|
9372
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9373
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
9374
|
+
}
|
|
9375
|
+
}
|
|
9376
|
+
return segments;
|
|
8572
9377
|
}
|
|
8573
9378
|
function inferCardType(sentence, toolName) {
|
|
8574
9379
|
const lower = sentence.toLowerCase();
|
|
@@ -8600,12 +9405,12 @@ function predicateFor(type) {
|
|
|
8600
9405
|
}
|
|
8601
9406
|
}
|
|
8602
9407
|
function extractMemoryCards(row) {
|
|
8603
|
-
const
|
|
9408
|
+
const segments = splitSegments(row.raw_text);
|
|
8604
9409
|
const cards = [];
|
|
8605
|
-
for (const sentence of
|
|
9410
|
+
for (const sentence of segments) {
|
|
8606
9411
|
const type = inferCardType(sentence, row.tool_name);
|
|
8607
9412
|
const subject = extractSubject(sentence, row.agent_id);
|
|
8608
|
-
const content = sentence.length >
|
|
9413
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
8609
9414
|
cards.push({
|
|
8610
9415
|
id: stableId(row.id, type, content),
|
|
8611
9416
|
memory_id: row.id,
|
|
@@ -8701,13 +9506,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
8701
9506
|
last_accessed: String(row.timestamp)
|
|
8702
9507
|
}));
|
|
8703
9508
|
}
|
|
8704
|
-
var MAX_CARDS_PER_MEMORY,
|
|
9509
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
8705
9510
|
var init_memory_cards = __esm({
|
|
8706
9511
|
"src/lib/memory-cards.ts"() {
|
|
8707
9512
|
"use strict";
|
|
8708
9513
|
init_database();
|
|
8709
|
-
MAX_CARDS_PER_MEMORY =
|
|
8710
|
-
|
|
9514
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9515
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9516
|
+
MIN_SEGMENT_CHARS = 20;
|
|
8711
9517
|
}
|
|
8712
9518
|
});
|
|
8713
9519
|
|