@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
|
@@ -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 path2 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 = path2.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 = path2.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 path3 from "path";
|
|
338
495
|
import os2 from "os";
|
|
339
496
|
function normalizeRole(role) {
|
|
340
497
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -355,10 +512,40 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
355
512
|
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
356
513
|
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
357
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
|
+
}
|
|
530
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
531
|
+
if (!existsSync4(employeesPath)) {
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
534
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
535
|
+
try {
|
|
536
|
+
return JSON.parse(raw);
|
|
537
|
+
} catch {
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
542
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
543
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
544
|
+
}
|
|
358
545
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
359
|
-
if (!
|
|
546
|
+
if (!existsSync4(employeesPath)) return [];
|
|
360
547
|
try {
|
|
361
|
-
return JSON.parse(
|
|
548
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
362
549
|
} catch {
|
|
363
550
|
return [];
|
|
364
551
|
}
|
|
@@ -366,6 +553,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
366
553
|
function getEmployee(employees, name) {
|
|
367
554
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
368
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
|
+
}
|
|
369
569
|
function baseAgentName(name, employees) {
|
|
370
570
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
371
571
|
if (!match) return name;
|
|
@@ -374,15 +574,147 @@ function baseAgentName(name, employees) {
|
|
|
374
574
|
if (getEmployee(roster, base)) return base;
|
|
375
575
|
return name;
|
|
376
576
|
}
|
|
377
|
-
|
|
577
|
+
function isMultiInstance(agentName, employees) {
|
|
578
|
+
const roster = employees ?? loadEmployeesSync();
|
|
579
|
+
const emp = getEmployee(roster, agentName);
|
|
580
|
+
if (!emp) return false;
|
|
581
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
582
|
+
}
|
|
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 = path3.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 = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
647
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
648
|
+
const newPath = path3.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 = path3.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 = path3.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;
|
|
378
708
|
var init_employees = __esm({
|
|
379
709
|
"src/lib/employees.ts"() {
|
|
380
710
|
"use strict";
|
|
381
711
|
init_config();
|
|
382
|
-
EMPLOYEES_PATH =
|
|
712
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
383
713
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
384
714
|
COORDINATOR_ROLE = "COO";
|
|
385
|
-
|
|
715
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
716
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
717
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
386
718
|
}
|
|
387
719
|
});
|
|
388
720
|
|
|
@@ -443,7 +775,7 @@ var init_db_retry = __esm({
|
|
|
443
775
|
|
|
444
776
|
// src/lib/database-adapter.ts
|
|
445
777
|
import os3 from "os";
|
|
446
|
-
import
|
|
778
|
+
import path4 from "path";
|
|
447
779
|
import { createRequire } from "module";
|
|
448
780
|
import { pathToFileURL } from "url";
|
|
449
781
|
function quotedIdentifier(identifier) {
|
|
@@ -754,8 +1086,8 @@ async function loadPrismaClient() {
|
|
|
754
1086
|
}
|
|
755
1087
|
return new PrismaClient2();
|
|
756
1088
|
}
|
|
757
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
758
|
-
const requireFromExeDb = createRequire(
|
|
1089
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1090
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
759
1091
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
760
1092
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
761
1093
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1036,8 +1368,8 @@ var init_memory = __esm({
|
|
|
1036
1368
|
|
|
1037
1369
|
// src/lib/daemon-auth.ts
|
|
1038
1370
|
import crypto from "crypto";
|
|
1039
|
-
import
|
|
1040
|
-
import { existsSync as
|
|
1371
|
+
import path5 from "path";
|
|
1372
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1041
1373
|
function normalizeToken(token) {
|
|
1042
1374
|
if (!token) return null;
|
|
1043
1375
|
const trimmed = token.trim();
|
|
@@ -1045,8 +1377,8 @@ function normalizeToken(token) {
|
|
|
1045
1377
|
}
|
|
1046
1378
|
function readDaemonToken() {
|
|
1047
1379
|
try {
|
|
1048
|
-
if (!
|
|
1049
|
-
return normalizeToken(
|
|
1380
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
1381
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
1050
1382
|
} catch {
|
|
1051
1383
|
return null;
|
|
1052
1384
|
}
|
|
@@ -1056,7 +1388,7 @@ function ensureDaemonToken(seed) {
|
|
|
1056
1388
|
if (existing) return existing;
|
|
1057
1389
|
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1058
1390
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1059
|
-
|
|
1391
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
1060
1392
|
`, "utf8");
|
|
1061
1393
|
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1062
1394
|
return token;
|
|
@@ -1067,7 +1399,7 @@ var init_daemon_auth = __esm({
|
|
|
1067
1399
|
"use strict";
|
|
1068
1400
|
init_config();
|
|
1069
1401
|
init_secure_files();
|
|
1070
|
-
DAEMON_TOKEN_PATH =
|
|
1402
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
1071
1403
|
}
|
|
1072
1404
|
});
|
|
1073
1405
|
|
|
@@ -1087,8 +1419,8 @@ import net from "net";
|
|
|
1087
1419
|
import os4 from "os";
|
|
1088
1420
|
import { spawn, execSync as execSync2 } from "child_process";
|
|
1089
1421
|
import { randomUUID } from "crypto";
|
|
1090
|
-
import { existsSync as
|
|
1091
|
-
import
|
|
1422
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1423
|
+
import path6 from "path";
|
|
1092
1424
|
import { fileURLToPath } from "url";
|
|
1093
1425
|
function handleData(chunk) {
|
|
1094
1426
|
_buffer += chunk.toString();
|
|
@@ -1124,9 +1456,9 @@ function isZombie(pid) {
|
|
|
1124
1456
|
}
|
|
1125
1457
|
}
|
|
1126
1458
|
function cleanupStaleFiles() {
|
|
1127
|
-
if (
|
|
1459
|
+
if (existsSync6(PID_PATH)) {
|
|
1128
1460
|
try {
|
|
1129
|
-
const pid = parseInt(
|
|
1461
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1130
1462
|
if (pid > 0) {
|
|
1131
1463
|
try {
|
|
1132
1464
|
process.kill(pid, 0);
|
|
@@ -1151,11 +1483,11 @@ function cleanupStaleFiles() {
|
|
|
1151
1483
|
}
|
|
1152
1484
|
}
|
|
1153
1485
|
function findPackageRoot() {
|
|
1154
|
-
let dir =
|
|
1155
|
-
const { root } =
|
|
1486
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1487
|
+
const { root } = path6.parse(dir);
|
|
1156
1488
|
while (dir !== root) {
|
|
1157
|
-
if (
|
|
1158
|
-
dir =
|
|
1489
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
1490
|
+
dir = path6.dirname(dir);
|
|
1159
1491
|
}
|
|
1160
1492
|
return null;
|
|
1161
1493
|
}
|
|
@@ -1173,8 +1505,8 @@ function spawnDaemon() {
|
|
|
1173
1505
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1174
1506
|
return;
|
|
1175
1507
|
}
|
|
1176
|
-
const daemonPath =
|
|
1177
|
-
if (!
|
|
1508
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1509
|
+
if (!existsSync6(daemonPath)) {
|
|
1178
1510
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1179
1511
|
`);
|
|
1180
1512
|
return;
|
|
@@ -1183,7 +1515,7 @@ function spawnDaemon() {
|
|
|
1183
1515
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1184
1516
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1185
1517
|
`);
|
|
1186
|
-
const logPath =
|
|
1518
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
1187
1519
|
let stderrFd = "ignore";
|
|
1188
1520
|
try {
|
|
1189
1521
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1348,9 +1680,9 @@ function killAndRespawnDaemon() {
|
|
|
1348
1680
|
}
|
|
1349
1681
|
try {
|
|
1350
1682
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1351
|
-
if (
|
|
1683
|
+
if (existsSync6(PID_PATH)) {
|
|
1352
1684
|
try {
|
|
1353
|
-
const pid = parseInt(
|
|
1685
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1354
1686
|
if (pid > 0) {
|
|
1355
1687
|
try {
|
|
1356
1688
|
process.kill(pid, "SIGKILL");
|
|
@@ -1496,9 +1828,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1496
1828
|
"use strict";
|
|
1497
1829
|
init_config();
|
|
1498
1830
|
init_daemon_auth();
|
|
1499
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1500
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1501
|
-
SPAWN_LOCK_PATH =
|
|
1831
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1832
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1833
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1502
1834
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1503
1835
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1504
1836
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -1766,7 +2098,7 @@ __export(database_exports, {
|
|
|
1766
2098
|
isInitialized: () => isInitialized,
|
|
1767
2099
|
setExternalClient: () => setExternalClient
|
|
1768
2100
|
});
|
|
1769
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
2101
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1770
2102
|
import { createClient } from "@libsql/client";
|
|
1771
2103
|
import { homedir } from "os";
|
|
1772
2104
|
import { join } from "path";
|
|
@@ -1819,11 +2151,11 @@ function releaseDbLock() {
|
|
|
1819
2151
|
}
|
|
1820
2152
|
async function initDatabase(config) {
|
|
1821
2153
|
acquireDbLock();
|
|
1822
|
-
if (
|
|
2154
|
+
if (existsSync7(config.dbPath)) {
|
|
1823
2155
|
const dbStat = statSync2(config.dbPath);
|
|
1824
2156
|
if (dbStat.size === 0) {
|
|
1825
2157
|
const walPath = config.dbPath + "-wal";
|
|
1826
|
-
if (
|
|
2158
|
+
if (existsSync7(walPath) && statSync2(walPath).size > 0) {
|
|
1827
2159
|
const backupPath = config.dbPath + ".zeroed-" + Date.now();
|
|
1828
2160
|
copyFileSync(config.dbPath, backupPath);
|
|
1829
2161
|
unlinkSync3(config.dbPath);
|
|
@@ -2119,6 +2451,13 @@ async function ensureSchema() {
|
|
|
2119
2451
|
} catch (e) {
|
|
2120
2452
|
logCatchDebug("migration", e);
|
|
2121
2453
|
}
|
|
2454
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2455
|
+
try {
|
|
2456
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2457
|
+
} catch (e) {
|
|
2458
|
+
logCatchDebug("migration", e);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2122
2461
|
try {
|
|
2123
2462
|
await client.execute({
|
|
2124
2463
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3335,6 +3674,22 @@ async function ensureSchema() {
|
|
|
3335
3674
|
} catch (e) {
|
|
3336
3675
|
logCatchDebug("migration", e);
|
|
3337
3676
|
}
|
|
3677
|
+
try {
|
|
3678
|
+
await client.execute({
|
|
3679
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3680
|
+
args: []
|
|
3681
|
+
});
|
|
3682
|
+
} catch (e) {
|
|
3683
|
+
logCatchDebug("migration", e);
|
|
3684
|
+
}
|
|
3685
|
+
try {
|
|
3686
|
+
await client.execute({
|
|
3687
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3688
|
+
args: []
|
|
3689
|
+
});
|
|
3690
|
+
} catch (e) {
|
|
3691
|
+
logCatchDebug("migration", e);
|
|
3692
|
+
}
|
|
3338
3693
|
}
|
|
3339
3694
|
async function disposeDatabase() {
|
|
3340
3695
|
if (_walCheckpointTimer) {
|
|
@@ -3387,15 +3742,15 @@ var init_database = __esm({
|
|
|
3387
3742
|
|
|
3388
3743
|
// src/lib/keychain.ts
|
|
3389
3744
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3390
|
-
import { existsSync as
|
|
3745
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3391
3746
|
import { execSync as execSync3 } from "child_process";
|
|
3392
|
-
import
|
|
3747
|
+
import path7 from "path";
|
|
3393
3748
|
import os5 from "os";
|
|
3394
3749
|
function getKeyDir() {
|
|
3395
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3750
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
3396
3751
|
}
|
|
3397
3752
|
function getKeyPath() {
|
|
3398
|
-
return
|
|
3753
|
+
return path7.join(getKeyDir(), "master.key");
|
|
3399
3754
|
}
|
|
3400
3755
|
function nativeKeychainAllowed() {
|
|
3401
3756
|
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
@@ -3426,7 +3781,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
|
3426
3781
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3427
3782
|
if (uid === 0) return true;
|
|
3428
3783
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3429
|
-
return Boolean(exeOsDir &&
|
|
3784
|
+
return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
|
|
3430
3785
|
} catch {
|
|
3431
3786
|
return false;
|
|
3432
3787
|
}
|
|
@@ -3623,7 +3978,7 @@ async function getMasterKey() {
|
|
|
3623
3978
|
}
|
|
3624
3979
|
}
|
|
3625
3980
|
const keyPath = getKeyPath();
|
|
3626
|
-
if (!
|
|
3981
|
+
if (!existsSync8(keyPath)) {
|
|
3627
3982
|
process.stderr.write(
|
|
3628
3983
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3629
3984
|
`
|
|
@@ -3957,14 +4312,14 @@ __export(shard_manager_exports, {
|
|
|
3957
4312
|
listShards: () => listShards,
|
|
3958
4313
|
shardExists: () => shardExists
|
|
3959
4314
|
});
|
|
3960
|
-
import
|
|
3961
|
-
import { existsSync as
|
|
4315
|
+
import path8 from "path";
|
|
4316
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
|
|
3962
4317
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3963
4318
|
function initShardManager(encryptionKey) {
|
|
3964
4319
|
_encryptionKey = encryptionKey;
|
|
3965
4320
|
_keyValidated = false;
|
|
3966
4321
|
_keyValidationPromise = null;
|
|
3967
|
-
if (!
|
|
4322
|
+
if (!existsSync9(SHARDS_DIR)) {
|
|
3968
4323
|
mkdirSync3(SHARDS_DIR, { recursive: true });
|
|
3969
4324
|
}
|
|
3970
4325
|
const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
|
|
@@ -3985,7 +4340,7 @@ async function validateEncryptionKey() {
|
|
|
3985
4340
|
return true;
|
|
3986
4341
|
}
|
|
3987
4342
|
for (const shardFile of existingShards.slice(0, 3)) {
|
|
3988
|
-
const dbPath =
|
|
4343
|
+
const dbPath = path8.join(SHARDS_DIR, shardFile);
|
|
3989
4344
|
const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
|
|
3990
4345
|
try {
|
|
3991
4346
|
await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
|
|
@@ -4028,7 +4383,7 @@ function getShardClient(projectName) {
|
|
|
4028
4383
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
4029
4384
|
evictLRU();
|
|
4030
4385
|
}
|
|
4031
|
-
const dbPath =
|
|
4386
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4032
4387
|
const client = createClient2({
|
|
4033
4388
|
url: `file:${dbPath}`,
|
|
4034
4389
|
encryptionKey: _encryptionKey
|
|
@@ -4039,13 +4394,13 @@ function getShardClient(projectName) {
|
|
|
4039
4394
|
}
|
|
4040
4395
|
function shardExists(projectName) {
|
|
4041
4396
|
const safeName = safeShardName(projectName);
|
|
4042
|
-
return
|
|
4397
|
+
return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
4043
4398
|
}
|
|
4044
4399
|
function safeShardName(projectName) {
|
|
4045
4400
|
return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4046
4401
|
}
|
|
4047
4402
|
function listShards() {
|
|
4048
|
-
if (!
|
|
4403
|
+
if (!existsSync9(SHARDS_DIR)) return [];
|
|
4049
4404
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4050
4405
|
}
|
|
4051
4406
|
async function auditShardHealth(options = {}) {
|
|
@@ -4057,7 +4412,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4057
4412
|
const names = listShards();
|
|
4058
4413
|
const shards = [];
|
|
4059
4414
|
for (const name of names) {
|
|
4060
|
-
const dbPath =
|
|
4415
|
+
const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
|
|
4061
4416
|
const stat = statSync4(dbPath);
|
|
4062
4417
|
const item = {
|
|
4063
4418
|
name,
|
|
@@ -4092,7 +4447,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4092
4447
|
_shards.delete(name);
|
|
4093
4448
|
_shardLastAccess.delete(name);
|
|
4094
4449
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4095
|
-
const archivedPath =
|
|
4450
|
+
const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
4096
4451
|
renameSync3(dbPath, archivedPath);
|
|
4097
4452
|
item.archivedPath = archivedPath;
|
|
4098
4453
|
}
|
|
@@ -4320,11 +4675,11 @@ async function getReadyShardClient(projectName) {
|
|
|
4320
4675
|
client.close();
|
|
4321
4676
|
_shards.delete(safeName);
|
|
4322
4677
|
_shardLastAccess.delete(safeName);
|
|
4323
|
-
const dbPath =
|
|
4324
|
-
if (
|
|
4678
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4679
|
+
if (existsSync9(dbPath)) {
|
|
4325
4680
|
const stat = statSync4(dbPath);
|
|
4326
4681
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4327
|
-
const archivedPath =
|
|
4682
|
+
const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
4328
4683
|
renameSync3(dbPath, archivedPath);
|
|
4329
4684
|
process.stderr.write(
|
|
4330
4685
|
`[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
|
|
@@ -4392,7 +4747,7 @@ var init_shard_manager = __esm({
|
|
|
4392
4747
|
"src/lib/shard-manager.ts"() {
|
|
4393
4748
|
"use strict";
|
|
4394
4749
|
init_config();
|
|
4395
|
-
SHARDS_DIR =
|
|
4750
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
4396
4751
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
4397
4752
|
MAX_OPEN_SHARDS = 10;
|
|
4398
4753
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -4458,11 +4813,17 @@ var init_platform_procedures = __esm({
|
|
|
4458
4813
|
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."
|
|
4459
4814
|
},
|
|
4460
4815
|
{
|
|
4461
|
-
title: "
|
|
4816
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4462
4817
|
domain: "workflow",
|
|
4463
4818
|
priority: "p1",
|
|
4464
4819
|
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."
|
|
4465
4820
|
},
|
|
4821
|
+
{
|
|
4822
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
4823
|
+
domain: "identity",
|
|
4824
|
+
priority: "p0",
|
|
4825
|
+
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."
|
|
4826
|
+
},
|
|
4466
4827
|
{
|
|
4467
4828
|
title: "Single dispatch path \u2014 create_task only",
|
|
4468
4829
|
domain: "workflow",
|
|
@@ -4496,6 +4857,12 @@ var init_platform_procedures = __esm({
|
|
|
4496
4857
|
priority: "p0",
|
|
4497
4858
|
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."
|
|
4498
4859
|
},
|
|
4860
|
+
{
|
|
4861
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
4862
|
+
domain: "security",
|
|
4863
|
+
priority: "p0",
|
|
4864
|
+
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."
|
|
4865
|
+
},
|
|
4499
4866
|
{
|
|
4500
4867
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4501
4868
|
domain: "support",
|
|
@@ -4647,7 +5014,7 @@ var init_platform_procedures = __esm({
|
|
|
4647
5014
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
4648
5015
|
domain: "tool-use",
|
|
4649
5016
|
priority: "p0",
|
|
4650
|
-
content: 'exe-os MCP tools
|
|
5017
|
+
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.'
|
|
4651
5018
|
},
|
|
4652
5019
|
{
|
|
4653
5020
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -4781,10 +5148,24 @@ function stableId(memoryId, type, content) {
|
|
|
4781
5148
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
4782
5149
|
}
|
|
4783
5150
|
function cleanText(text) {
|
|
4784
|
-
|
|
5151
|
+
let cleaned = text.replace(
|
|
5152
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5153
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5154
|
+
);
|
|
5155
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5156
|
+
return cleaned;
|
|
4785
5157
|
}
|
|
4786
|
-
function
|
|
4787
|
-
|
|
5158
|
+
function splitSegments(text) {
|
|
5159
|
+
const cleaned = cleanText(text);
|
|
5160
|
+
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);
|
|
5161
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5162
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5163
|
+
if (lines.length > 0) return lines;
|
|
5164
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5165
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
return segments;
|
|
4788
5169
|
}
|
|
4789
5170
|
function inferCardType(sentence, toolName) {
|
|
4790
5171
|
const lower = sentence.toLowerCase();
|
|
@@ -4816,12 +5197,12 @@ function predicateFor(type) {
|
|
|
4816
5197
|
}
|
|
4817
5198
|
}
|
|
4818
5199
|
function extractMemoryCards(row) {
|
|
4819
|
-
const
|
|
5200
|
+
const segments = splitSegments(row.raw_text);
|
|
4820
5201
|
const cards = [];
|
|
4821
|
-
for (const sentence of
|
|
5202
|
+
for (const sentence of segments) {
|
|
4822
5203
|
const type = inferCardType(sentence, row.tool_name);
|
|
4823
5204
|
const subject = extractSubject(sentence, row.agent_id);
|
|
4824
|
-
const content = sentence.length >
|
|
5205
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
4825
5206
|
cards.push({
|
|
4826
5207
|
id: stableId(row.id, type, content),
|
|
4827
5208
|
memory_id: row.id,
|
|
@@ -4917,13 +5298,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
4917
5298
|
last_accessed: String(row.timestamp)
|
|
4918
5299
|
}));
|
|
4919
5300
|
}
|
|
4920
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5301
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
4921
5302
|
var init_memory_cards = __esm({
|
|
4922
5303
|
"src/lib/memory-cards.ts"() {
|
|
4923
5304
|
"use strict";
|
|
4924
5305
|
init_database();
|
|
4925
|
-
MAX_CARDS_PER_MEMORY =
|
|
4926
|
-
|
|
5306
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5307
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5308
|
+
MIN_SEGMENT_CHARS = 20;
|
|
4927
5309
|
}
|
|
4928
5310
|
});
|
|
4929
5311
|
|
|
@@ -5859,13 +6241,13 @@ var init_store = __esm({
|
|
|
5859
6241
|
});
|
|
5860
6242
|
|
|
5861
6243
|
// src/lib/session-registry.ts
|
|
5862
|
-
import
|
|
6244
|
+
import path9 from "path";
|
|
5863
6245
|
import os6 from "os";
|
|
5864
6246
|
var REGISTRY_PATH;
|
|
5865
6247
|
var init_session_registry = __esm({
|
|
5866
6248
|
"src/lib/session-registry.ts"() {
|
|
5867
6249
|
"use strict";
|
|
5868
|
-
REGISTRY_PATH =
|
|
6250
|
+
REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
5869
6251
|
}
|
|
5870
6252
|
});
|
|
5871
6253
|
|
|
@@ -6083,68 +6465,6 @@ var init_provider_table = __esm({
|
|
|
6083
6465
|
}
|
|
6084
6466
|
});
|
|
6085
6467
|
|
|
6086
|
-
// src/lib/runtime-table.ts
|
|
6087
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
6088
|
-
var init_runtime_table = __esm({
|
|
6089
|
-
"src/lib/runtime-table.ts"() {
|
|
6090
|
-
"use strict";
|
|
6091
|
-
RUNTIME_TABLE = {
|
|
6092
|
-
codex: {
|
|
6093
|
-
binary: "codex",
|
|
6094
|
-
launchMode: "interactive",
|
|
6095
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
6096
|
-
inlineFlag: "--no-alt-screen",
|
|
6097
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
6098
|
-
defaultModel: "gpt-5.5"
|
|
6099
|
-
},
|
|
6100
|
-
opencode: {
|
|
6101
|
-
binary: "opencode",
|
|
6102
|
-
launchMode: "exec",
|
|
6103
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
6104
|
-
inlineFlag: "",
|
|
6105
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
6106
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
6107
|
-
}
|
|
6108
|
-
};
|
|
6109
|
-
DEFAULT_RUNTIME = "claude";
|
|
6110
|
-
}
|
|
6111
|
-
});
|
|
6112
|
-
|
|
6113
|
-
// src/lib/agent-config.ts
|
|
6114
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync9 } from "fs";
|
|
6115
|
-
import path9 from "path";
|
|
6116
|
-
function loadAgentConfig() {
|
|
6117
|
-
if (!existsSync9(AGENT_CONFIG_PATH)) return {};
|
|
6118
|
-
try {
|
|
6119
|
-
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
6120
|
-
} catch {
|
|
6121
|
-
return {};
|
|
6122
|
-
}
|
|
6123
|
-
}
|
|
6124
|
-
function getAgentRuntime(agentId) {
|
|
6125
|
-
const config = loadAgentConfig();
|
|
6126
|
-
const entry = config[agentId];
|
|
6127
|
-
if (entry) return entry;
|
|
6128
|
-
const orgDefault = config["default"];
|
|
6129
|
-
if (orgDefault) return orgDefault;
|
|
6130
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
6131
|
-
}
|
|
6132
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
6133
|
-
var init_agent_config = __esm({
|
|
6134
|
-
"src/lib/agent-config.ts"() {
|
|
6135
|
-
"use strict";
|
|
6136
|
-
init_config();
|
|
6137
|
-
init_runtime_table();
|
|
6138
|
-
init_secure_files();
|
|
6139
|
-
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
6140
|
-
DEFAULT_MODELS = {
|
|
6141
|
-
claude: "claude-opus-4.6",
|
|
6142
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
6143
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
6144
|
-
};
|
|
6145
|
-
}
|
|
6146
|
-
});
|
|
6147
|
-
|
|
6148
6468
|
// src/lib/intercom-queue.ts
|
|
6149
6469
|
var intercom_queue_exports = {};
|
|
6150
6470
|
__export(intercom_queue_exports, {
|
|
@@ -6272,6 +6592,23 @@ var init_intercom_queue = __esm({
|
|
|
6272
6592
|
});
|
|
6273
6593
|
|
|
6274
6594
|
// src/lib/license.ts
|
|
6595
|
+
var license_exports = {};
|
|
6596
|
+
__export(license_exports, {
|
|
6597
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
6598
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
6599
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
6600
|
+
checkLicense: () => checkLicense,
|
|
6601
|
+
getCachedLicense: () => getCachedLicense,
|
|
6602
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
6603
|
+
loadDeviceId: () => loadDeviceId,
|
|
6604
|
+
loadLicense: () => loadLicense,
|
|
6605
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
6606
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
6607
|
+
saveLicense: () => saveLicense,
|
|
6608
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
6609
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
6610
|
+
validateLicense: () => validateLicense
|
|
6611
|
+
});
|
|
6275
6612
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
|
|
6276
6613
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6277
6614
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -6279,7 +6616,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
6279
6616
|
import os8 from "os";
|
|
6280
6617
|
import path11 from "path";
|
|
6281
6618
|
import { jwtVerify, importSPKI } from "jose";
|
|
6282
|
-
|
|
6619
|
+
async function fetchRetry(url, init) {
|
|
6620
|
+
try {
|
|
6621
|
+
return await fetch(url, init);
|
|
6622
|
+
} catch {
|
|
6623
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
6624
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
6625
|
+
}
|
|
6626
|
+
}
|
|
6627
|
+
function loadDeviceId() {
|
|
6628
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
6629
|
+
try {
|
|
6630
|
+
if (existsSync11(deviceJsonPath)) {
|
|
6631
|
+
const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
|
|
6632
|
+
if (data.deviceId) return data.deviceId;
|
|
6633
|
+
}
|
|
6634
|
+
} catch {
|
|
6635
|
+
}
|
|
6636
|
+
try {
|
|
6637
|
+
if (existsSync11(DEVICE_ID_PATH)) {
|
|
6638
|
+
const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
|
|
6639
|
+
if (id2) return id2;
|
|
6640
|
+
}
|
|
6641
|
+
} catch {
|
|
6642
|
+
}
|
|
6643
|
+
const id = randomUUID3();
|
|
6644
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
6645
|
+
writeFileSync5(DEVICE_ID_PATH, id, "utf8");
|
|
6646
|
+
return id;
|
|
6647
|
+
}
|
|
6648
|
+
function loadLicense() {
|
|
6649
|
+
try {
|
|
6650
|
+
if (!existsSync11(LICENSE_PATH)) return null;
|
|
6651
|
+
return readFileSync7(LICENSE_PATH, "utf8").trim();
|
|
6652
|
+
} catch {
|
|
6653
|
+
return null;
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
function saveLicense(apiKey) {
|
|
6657
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
6658
|
+
writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
6659
|
+
}
|
|
6660
|
+
async function verifyLicenseJwt(token) {
|
|
6661
|
+
try {
|
|
6662
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
6663
|
+
const { payload } = await jwtVerify(token, key, {
|
|
6664
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
6665
|
+
});
|
|
6666
|
+
const plan = payload.plan ?? "free";
|
|
6667
|
+
const email = payload.sub ?? "";
|
|
6668
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6669
|
+
return {
|
|
6670
|
+
valid: true,
|
|
6671
|
+
plan,
|
|
6672
|
+
email,
|
|
6673
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6674
|
+
deviceLimit: limits.devices,
|
|
6675
|
+
employeeLimit: limits.employees,
|
|
6676
|
+
memoryLimit: limits.memories
|
|
6677
|
+
};
|
|
6678
|
+
} catch {
|
|
6679
|
+
return null;
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
async function getCachedLicense() {
|
|
6683
|
+
try {
|
|
6684
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
6685
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
6686
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
6687
|
+
return await verifyLicenseJwt(raw.token);
|
|
6688
|
+
} catch {
|
|
6689
|
+
return null;
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
function readCachedLicenseToken() {
|
|
6693
|
+
try {
|
|
6694
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
6695
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
6696
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
6697
|
+
} catch {
|
|
6698
|
+
return null;
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
function getRawCachedPlan() {
|
|
6702
|
+
try {
|
|
6703
|
+
const token = readCachedLicenseToken();
|
|
6704
|
+
if (!token) return null;
|
|
6705
|
+
const parts = token.split(".");
|
|
6706
|
+
if (parts.length !== 3) return null;
|
|
6707
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
6708
|
+
const plan = payload.plan ?? "free";
|
|
6709
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6710
|
+
process.stderr.write(
|
|
6711
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
6712
|
+
`
|
|
6713
|
+
);
|
|
6714
|
+
return {
|
|
6715
|
+
valid: true,
|
|
6716
|
+
plan,
|
|
6717
|
+
email: payload.sub ?? "",
|
|
6718
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6719
|
+
deviceLimit: limits.devices,
|
|
6720
|
+
employeeLimit: limits.employees,
|
|
6721
|
+
memoryLimit: limits.memories
|
|
6722
|
+
};
|
|
6723
|
+
} catch {
|
|
6724
|
+
return null;
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
function cacheResponse(token) {
|
|
6728
|
+
try {
|
|
6729
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
6730
|
+
} catch {
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
function loadPrismaForLicense() {
|
|
6734
|
+
if (_prismaFailed) return null;
|
|
6735
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
6736
|
+
if (!dbUrl) {
|
|
6737
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6738
|
+
if (!existsSync11(path11.join(exeDbRoot, "package.json"))) {
|
|
6739
|
+
_prismaFailed = true;
|
|
6740
|
+
return null;
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
if (!_prismaPromise) {
|
|
6744
|
+
_prismaPromise = (async () => {
|
|
6745
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
6746
|
+
if (explicitPath) {
|
|
6747
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
6748
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
6749
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
6750
|
+
return new Ctor2();
|
|
6751
|
+
}
|
|
6752
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6753
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
6754
|
+
const entry = req.resolve("@prisma/client");
|
|
6755
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
6756
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
6757
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
6758
|
+
return new Ctor();
|
|
6759
|
+
})().catch((err) => {
|
|
6760
|
+
_prismaFailed = true;
|
|
6761
|
+
_prismaPromise = null;
|
|
6762
|
+
throw err;
|
|
6763
|
+
});
|
|
6764
|
+
}
|
|
6765
|
+
return _prismaPromise;
|
|
6766
|
+
}
|
|
6767
|
+
async function validateViaPostgres(apiKey) {
|
|
6768
|
+
const loader = loadPrismaForLicense();
|
|
6769
|
+
if (!loader) return null;
|
|
6770
|
+
try {
|
|
6771
|
+
const prisma = await loader;
|
|
6772
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
6773
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
6774
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
6775
|
+
apiKey
|
|
6776
|
+
);
|
|
6777
|
+
if (!rows || rows.length === 0) return null;
|
|
6778
|
+
const row = rows[0];
|
|
6779
|
+
if (row.status !== "active") return null;
|
|
6780
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
6781
|
+
const plan = row.plan;
|
|
6782
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6783
|
+
return {
|
|
6784
|
+
valid: true,
|
|
6785
|
+
plan,
|
|
6786
|
+
email: row.email,
|
|
6787
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
6788
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
6789
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
6790
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
6791
|
+
};
|
|
6792
|
+
} catch {
|
|
6793
|
+
return null;
|
|
6794
|
+
}
|
|
6795
|
+
}
|
|
6796
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
6797
|
+
try {
|
|
6798
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
6799
|
+
method: "POST",
|
|
6800
|
+
headers: { "Content-Type": "application/json" },
|
|
6801
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
6802
|
+
signal: AbortSignal.timeout(1e4)
|
|
6803
|
+
});
|
|
6804
|
+
if (!res.ok) return null;
|
|
6805
|
+
const data = await res.json();
|
|
6806
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
6807
|
+
if (!data.valid) return null;
|
|
6808
|
+
if (data.token) {
|
|
6809
|
+
cacheResponse(data.token);
|
|
6810
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
6811
|
+
if (verified) return verified;
|
|
6812
|
+
}
|
|
6813
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
6814
|
+
return {
|
|
6815
|
+
valid: data.valid,
|
|
6816
|
+
plan: data.plan,
|
|
6817
|
+
email: data.email,
|
|
6818
|
+
expiresAt: data.expiresAt,
|
|
6819
|
+
deviceLimit: limits.devices,
|
|
6820
|
+
employeeLimit: limits.employees,
|
|
6821
|
+
memoryLimit: limits.memories
|
|
6822
|
+
};
|
|
6823
|
+
} catch {
|
|
6824
|
+
return null;
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
async function validateLicense(apiKey, deviceId) {
|
|
6828
|
+
const did = deviceId ?? loadDeviceId();
|
|
6829
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
6830
|
+
if (pgResult) {
|
|
6831
|
+
try {
|
|
6832
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
6833
|
+
} catch {
|
|
6834
|
+
}
|
|
6835
|
+
return pgResult;
|
|
6836
|
+
}
|
|
6837
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
6838
|
+
if (cfResult) return cfResult;
|
|
6839
|
+
const cached = await getCachedLicense();
|
|
6840
|
+
if (cached) return cached;
|
|
6841
|
+
try {
|
|
6842
|
+
if (existsSync11(CACHE_PATH)) {
|
|
6843
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
6844
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
6845
|
+
return raw.pgLicense;
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
} catch {
|
|
6849
|
+
}
|
|
6850
|
+
const rawFallback = getRawCachedPlan();
|
|
6851
|
+
if (rawFallback) return rawFallback;
|
|
6852
|
+
return { ...FREE_LICENSE, valid: false };
|
|
6853
|
+
}
|
|
6854
|
+
function getCacheAgeMs() {
|
|
6855
|
+
try {
|
|
6856
|
+
const { statSync: statSync5 } = __require("fs");
|
|
6857
|
+
const s = statSync5(CACHE_PATH);
|
|
6858
|
+
return Date.now() - s.mtimeMs;
|
|
6859
|
+
} catch {
|
|
6860
|
+
return Infinity;
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
async function checkLicense() {
|
|
6864
|
+
let key = loadLicense();
|
|
6865
|
+
if (!key) {
|
|
6866
|
+
try {
|
|
6867
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
6868
|
+
if (existsSync11(configPath)) {
|
|
6869
|
+
const raw = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
6870
|
+
const cloud = raw.cloud;
|
|
6871
|
+
if (cloud?.apiKey) {
|
|
6872
|
+
key = cloud.apiKey;
|
|
6873
|
+
saveLicense(key);
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
} catch {
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
if (!key) return FREE_LICENSE;
|
|
6880
|
+
const cached = await getCachedLicense();
|
|
6881
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
6882
|
+
const deviceId = loadDeviceId();
|
|
6883
|
+
return validateLicense(key, deviceId);
|
|
6884
|
+
}
|
|
6885
|
+
function isFeatureAllowed(license, feature) {
|
|
6886
|
+
switch (feature) {
|
|
6887
|
+
case "cloud_sync":
|
|
6888
|
+
case "external_agents":
|
|
6889
|
+
case "wiki":
|
|
6890
|
+
return license.plan !== "free";
|
|
6891
|
+
case "unlimited_employees":
|
|
6892
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6895
|
+
function mirrorLicenseKey(apiKey) {
|
|
6896
|
+
const trimmed = apiKey.trim();
|
|
6897
|
+
if (!trimmed) return;
|
|
6898
|
+
saveLicense(trimmed);
|
|
6899
|
+
}
|
|
6900
|
+
async function assertVpsLicense(opts) {
|
|
6901
|
+
const env = opts?.env ?? process.env;
|
|
6902
|
+
const inProduction = env.NODE_ENV === "production";
|
|
6903
|
+
if (!opts?.force && !inProduction) {
|
|
6904
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
6905
|
+
}
|
|
6906
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
6907
|
+
if (envKey) {
|
|
6908
|
+
saveLicense(envKey);
|
|
6909
|
+
}
|
|
6910
|
+
const apiKey = envKey ?? loadLicense();
|
|
6911
|
+
if (!apiKey) {
|
|
6912
|
+
throw new Error(
|
|
6913
|
+
"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."
|
|
6914
|
+
);
|
|
6915
|
+
}
|
|
6916
|
+
const deviceId = loadDeviceId();
|
|
6917
|
+
let backendResponse = null;
|
|
6918
|
+
let explicitRejection = false;
|
|
6919
|
+
let transientFailure = false;
|
|
6920
|
+
try {
|
|
6921
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
6922
|
+
method: "POST",
|
|
6923
|
+
headers: { "Content-Type": "application/json" },
|
|
6924
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
6925
|
+
signal: AbortSignal.timeout(1e4)
|
|
6926
|
+
});
|
|
6927
|
+
if (res.ok) {
|
|
6928
|
+
backendResponse = await res.json();
|
|
6929
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
6930
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
6931
|
+
explicitRejection = true;
|
|
6932
|
+
} else {
|
|
6933
|
+
transientFailure = true;
|
|
6934
|
+
}
|
|
6935
|
+
} catch {
|
|
6936
|
+
transientFailure = true;
|
|
6937
|
+
}
|
|
6938
|
+
if (backendResponse?.valid) {
|
|
6939
|
+
if (backendResponse.token) {
|
|
6940
|
+
cacheResponse(backendResponse.token);
|
|
6941
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
6942
|
+
if (verified) return verified;
|
|
6943
|
+
}
|
|
6944
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
6945
|
+
return {
|
|
6946
|
+
valid: true,
|
|
6947
|
+
plan: backendResponse.plan,
|
|
6948
|
+
email: backendResponse.email,
|
|
6949
|
+
expiresAt: backendResponse.expiresAt,
|
|
6950
|
+
deviceLimit: limits.devices,
|
|
6951
|
+
employeeLimit: limits.employees,
|
|
6952
|
+
memoryLimit: limits.memories
|
|
6953
|
+
};
|
|
6954
|
+
}
|
|
6955
|
+
if (explicitRejection) {
|
|
6956
|
+
throw new Error(
|
|
6957
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
6958
|
+
);
|
|
6959
|
+
}
|
|
6960
|
+
if (!transientFailure) {
|
|
6961
|
+
throw new Error(
|
|
6962
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
6963
|
+
);
|
|
6964
|
+
}
|
|
6965
|
+
const fresh = await getCachedLicense();
|
|
6966
|
+
if (fresh && fresh.valid) return fresh;
|
|
6967
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
6968
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
6969
|
+
try {
|
|
6970
|
+
const token = readCachedLicenseToken();
|
|
6971
|
+
if (token) {
|
|
6972
|
+
const payloadB64 = token.split(".")[1];
|
|
6973
|
+
if (payloadB64) {
|
|
6974
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
6975
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
6976
|
+
if (Date.now() < expMs + graceMs) {
|
|
6977
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
6978
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
6979
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
6980
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
6981
|
+
});
|
|
6982
|
+
const plan = verified.plan ?? "free";
|
|
6983
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6984
|
+
return {
|
|
6985
|
+
valid: true,
|
|
6986
|
+
plan,
|
|
6987
|
+
email: verified.sub ?? "",
|
|
6988
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
6989
|
+
deviceLimit: limits.devices,
|
|
6990
|
+
employeeLimit: limits.employees,
|
|
6991
|
+
memoryLimit: limits.memories
|
|
6992
|
+
};
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
} catch {
|
|
6997
|
+
}
|
|
6998
|
+
throw new Error(
|
|
6999
|
+
`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.`
|
|
7000
|
+
);
|
|
7001
|
+
}
|
|
7002
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
7003
|
+
if (_revalTimer) return;
|
|
7004
|
+
_revalTimer = setInterval(async () => {
|
|
7005
|
+
try {
|
|
7006
|
+
const license = await checkLicense();
|
|
7007
|
+
if (!license.valid) {
|
|
7008
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
7009
|
+
}
|
|
7010
|
+
} catch {
|
|
7011
|
+
}
|
|
7012
|
+
}, intervalMs);
|
|
7013
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
7014
|
+
_revalTimer.unref();
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
7017
|
+
function stopLicenseRevalidation() {
|
|
7018
|
+
if (_revalTimer) {
|
|
7019
|
+
clearInterval(_revalTimer);
|
|
7020
|
+
_revalTimer = null;
|
|
7021
|
+
}
|
|
7022
|
+
}
|
|
7023
|
+
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;
|
|
6283
7024
|
var init_license = __esm({
|
|
6284
7025
|
"src/lib/license.ts"() {
|
|
6285
7026
|
"use strict";
|
|
@@ -6287,7 +7028,33 @@ var init_license = __esm({
|
|
|
6287
7028
|
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
6288
7029
|
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
6289
7030
|
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
6290
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
7031
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
7032
|
+
RETRY_DELAY_MS = 500;
|
|
7033
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
7034
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
7035
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
7036
|
+
-----END PUBLIC KEY-----`;
|
|
7037
|
+
LICENSE_JWT_ALG = "ES256";
|
|
7038
|
+
PLAN_LIMITS = {
|
|
7039
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
7040
|
+
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
7041
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
7042
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
7043
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
7044
|
+
};
|
|
7045
|
+
FREE_LICENSE = {
|
|
7046
|
+
valid: true,
|
|
7047
|
+
plan: "free",
|
|
7048
|
+
email: "",
|
|
7049
|
+
expiresAt: null,
|
|
7050
|
+
deviceLimit: 1,
|
|
7051
|
+
employeeLimit: 1,
|
|
7052
|
+
memoryLimit: 5e3
|
|
7053
|
+
};
|
|
7054
|
+
_prismaPromise = null;
|
|
7055
|
+
_prismaFailed = false;
|
|
7056
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
7057
|
+
_revalTimer = null;
|
|
6291
7058
|
}
|
|
6292
7059
|
});
|
|
6293
7060
|
|
|
@@ -6340,6 +7107,18 @@ function extractRootExe(name) {
|
|
|
6340
7107
|
const parts = name.split("-").filter(Boolean);
|
|
6341
7108
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
6342
7109
|
}
|
|
7110
|
+
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
7111
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
7112
|
+
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
7113
|
+
}
|
|
7114
|
+
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
7115
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
7116
|
+
writeFileSync6(filePath, JSON.stringify({
|
|
7117
|
+
parentExe: rootExe,
|
|
7118
|
+
dispatchedBy: dispatchedBy || rootExe,
|
|
7119
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7120
|
+
}));
|
|
7121
|
+
}
|
|
6343
7122
|
function getParentExe(sessionKey) {
|
|
6344
7123
|
try {
|
|
6345
7124
|
const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
@@ -6360,11 +7139,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6360
7139
|
}
|
|
6361
7140
|
}
|
|
6362
7141
|
function resolveExeSession() {
|
|
7142
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7143
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7144
|
+
if (fromEnv) return fromEnv;
|
|
7145
|
+
}
|
|
6363
7146
|
const mySession = getMySession();
|
|
6364
7147
|
if (!mySession) {
|
|
6365
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6366
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6367
|
-
}
|
|
6368
7148
|
return null;
|
|
6369
7149
|
}
|
|
6370
7150
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6379,6 +7159,10 @@ function resolveExeSession() {
|
|
|
6379
7159
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6380
7160
|
`
|
|
6381
7161
|
);
|
|
7162
|
+
try {
|
|
7163
|
+
registerParentExe(key, fromSessionName);
|
|
7164
|
+
} catch {
|
|
7165
|
+
}
|
|
6382
7166
|
candidate = fromSessionName;
|
|
6383
7167
|
} else {
|
|
6384
7168
|
candidate = fromCache;
|
|
@@ -6781,6 +7565,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
6781
7565
|
args: [identifier, ...scope.args]
|
|
6782
7566
|
});
|
|
6783
7567
|
if (result.rows.length === 1) return result.rows[0];
|
|
7568
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
7569
|
+
result = await client.execute({
|
|
7570
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
7571
|
+
args: [`${identifier}%`]
|
|
7572
|
+
});
|
|
7573
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
7574
|
+
if (result.rows.length > 1) {
|
|
7575
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
7576
|
+
throw new Error(
|
|
7577
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
7578
|
+
);
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
6784
7581
|
result = await client.execute({
|
|
6785
7582
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
6786
7583
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -7161,12 +7958,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7161
7958
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
7162
7959
|
args: [now, taskId]
|
|
7163
7960
|
});
|
|
7164
|
-
if (
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7961
|
+
if (unblocked.rowsAffected === 0) return;
|
|
7962
|
+
const ubScope = sessionScopeFilter();
|
|
7963
|
+
const unblockedRows = await client.execute({
|
|
7964
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
7965
|
+
args: [now, ...ubScope.args]
|
|
7966
|
+
});
|
|
7967
|
+
if (baseDir) {
|
|
7170
7968
|
for (const ur of unblockedRows.rows) {
|
|
7171
7969
|
try {
|
|
7172
7970
|
const ubFile = path18.join(baseDir, String(ur.task_file));
|
|
@@ -7178,6 +7976,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7178
7976
|
}
|
|
7179
7977
|
}
|
|
7180
7978
|
}
|
|
7979
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
7980
|
+
try {
|
|
7981
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
7982
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
7983
|
+
for (const ur of unblockedRows.rows) {
|
|
7984
|
+
const assignee = String(ur.assigned_to);
|
|
7985
|
+
if (dispatched.has(assignee)) continue;
|
|
7986
|
+
dispatched.add(assignee);
|
|
7987
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
7988
|
+
}
|
|
7989
|
+
} catch {
|
|
7990
|
+
}
|
|
7991
|
+
}
|
|
7181
7992
|
}
|
|
7182
7993
|
async function findNextTask(assignedTo) {
|
|
7183
7994
|
const client = getClient();
|
|
@@ -7338,6 +8149,15 @@ var init_embedder = __esm({
|
|
|
7338
8149
|
// src/lib/behaviors.ts
|
|
7339
8150
|
import crypto4 from "crypto";
|
|
7340
8151
|
async function storeBehavior(opts) {
|
|
8152
|
+
try {
|
|
8153
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
8154
|
+
const roster = loadEmployeesSync2();
|
|
8155
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
8156
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
8157
|
+
}
|
|
8158
|
+
} catch (e) {
|
|
8159
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
8160
|
+
}
|
|
7341
8161
|
const client = getClient();
|
|
7342
8162
|
const id = crypto4.randomUUID();
|
|
7343
8163
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7348,10 +8168,18 @@ async function storeBehavior(opts) {
|
|
|
7348
8168
|
vector = new Float32Array(vec);
|
|
7349
8169
|
} catch {
|
|
7350
8170
|
}
|
|
8171
|
+
let createdByDevice = null;
|
|
8172
|
+
try {
|
|
8173
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
8174
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
8175
|
+
} catch {
|
|
8176
|
+
}
|
|
8177
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
8178
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
7351
8179
|
await client.execute({
|
|
7352
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
7353
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
7354
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
8180
|
+
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)
|
|
8181
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
8182
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
7355
8183
|
});
|
|
7356
8184
|
return id;
|
|
7357
8185
|
}
|
|
@@ -7749,6 +8577,12 @@ async function updateTask(input) {
|
|
|
7749
8577
|
}
|
|
7750
8578
|
}
|
|
7751
8579
|
}
|
|
8580
|
+
if (input.status === "cancelled") {
|
|
8581
|
+
try {
|
|
8582
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
8583
|
+
} catch {
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
7752
8586
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
7753
8587
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
7754
8588
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|