@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/lib/tasks.js
CHANGED
|
@@ -337,11 +337,168 @@ var init_config = __esm({
|
|
|
337
337
|
}
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
+
// src/lib/runtime-table.ts
|
|
341
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
342
|
+
var init_runtime_table = __esm({
|
|
343
|
+
"src/lib/runtime-table.ts"() {
|
|
344
|
+
"use strict";
|
|
345
|
+
RUNTIME_TABLE = {
|
|
346
|
+
codex: {
|
|
347
|
+
binary: "codex",
|
|
348
|
+
launchMode: "interactive",
|
|
349
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
350
|
+
inlineFlag: "--no-alt-screen",
|
|
351
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
352
|
+
defaultModel: "gpt-5.5"
|
|
353
|
+
},
|
|
354
|
+
opencode: {
|
|
355
|
+
binary: "opencode",
|
|
356
|
+
launchMode: "exec",
|
|
357
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
358
|
+
inlineFlag: "",
|
|
359
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
360
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
DEFAULT_RUNTIME = "claude";
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// src/lib/agent-config.ts
|
|
368
|
+
var agent_config_exports = {};
|
|
369
|
+
__export(agent_config_exports, {
|
|
370
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
371
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
372
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
373
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
374
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
375
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
376
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
377
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
378
|
+
setAgentMcps: () => setAgentMcps,
|
|
379
|
+
setAgentRuntime: () => setAgentRuntime
|
|
380
|
+
});
|
|
381
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
382
|
+
import path2 from "path";
|
|
383
|
+
function loadAgentConfig() {
|
|
384
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
385
|
+
try {
|
|
386
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
387
|
+
} catch {
|
|
388
|
+
return {};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function saveAgentConfig(config) {
|
|
392
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
393
|
+
ensurePrivateDirSync(dir);
|
|
394
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
395
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
396
|
+
}
|
|
397
|
+
function getAgentRuntime(agentId) {
|
|
398
|
+
const config = loadAgentConfig();
|
|
399
|
+
const entry = config[agentId];
|
|
400
|
+
if (entry) return entry;
|
|
401
|
+
const orgDefault = config["default"];
|
|
402
|
+
if (orgDefault) return orgDefault;
|
|
403
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
404
|
+
}
|
|
405
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
406
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
407
|
+
if (!knownModels) {
|
|
408
|
+
return {
|
|
409
|
+
ok: false,
|
|
410
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (!knownModels.includes(model)) {
|
|
414
|
+
return {
|
|
415
|
+
ok: false,
|
|
416
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const config = loadAgentConfig();
|
|
420
|
+
const existing = config[agentId];
|
|
421
|
+
const entry = { runtime, model };
|
|
422
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
423
|
+
if (mcps !== void 0) {
|
|
424
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
425
|
+
} else if (existing?.mcps) {
|
|
426
|
+
entry.mcps = existing.mcps;
|
|
427
|
+
}
|
|
428
|
+
config[agentId] = entry;
|
|
429
|
+
saveAgentConfig(config);
|
|
430
|
+
return { ok: true };
|
|
431
|
+
}
|
|
432
|
+
function setAgentMcps(agentId, mcps) {
|
|
433
|
+
const config = loadAgentConfig();
|
|
434
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
435
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
436
|
+
config[agentId] = existing;
|
|
437
|
+
saveAgentConfig(config);
|
|
438
|
+
return { ok: true };
|
|
439
|
+
}
|
|
440
|
+
function clearAgentRuntime(agentId) {
|
|
441
|
+
const config = loadAgentConfig();
|
|
442
|
+
delete config[agentId];
|
|
443
|
+
saveAgentConfig(config);
|
|
444
|
+
}
|
|
445
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
446
|
+
var init_agent_config = __esm({
|
|
447
|
+
"src/lib/agent-config.ts"() {
|
|
448
|
+
"use strict";
|
|
449
|
+
init_config();
|
|
450
|
+
init_runtime_table();
|
|
451
|
+
init_secure_files();
|
|
452
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
453
|
+
KNOWN_RUNTIMES = {
|
|
454
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
455
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
456
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
457
|
+
};
|
|
458
|
+
RUNTIME_LABELS = {
|
|
459
|
+
claude: "Claude Code (Anthropic)",
|
|
460
|
+
codex: "Codex (OpenAI)",
|
|
461
|
+
opencode: "OpenCode (open source)"
|
|
462
|
+
};
|
|
463
|
+
DEFAULT_MODELS = {
|
|
464
|
+
claude: "claude-opus-4.6",
|
|
465
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
466
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
340
471
|
// src/lib/employees.ts
|
|
472
|
+
var employees_exports = {};
|
|
473
|
+
__export(employees_exports, {
|
|
474
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
475
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
476
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
477
|
+
addEmployee: () => addEmployee,
|
|
478
|
+
baseAgentName: () => baseAgentName,
|
|
479
|
+
canCoordinate: () => canCoordinate,
|
|
480
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
481
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
482
|
+
getEmployee: () => getEmployee,
|
|
483
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
484
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
485
|
+
hasRole: () => hasRole,
|
|
486
|
+
hireEmployee: () => hireEmployee,
|
|
487
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
488
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
489
|
+
isMultiInstance: () => isMultiInstance,
|
|
490
|
+
loadEmployees: () => loadEmployees,
|
|
491
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
492
|
+
normalizeRole: () => normalizeRole,
|
|
493
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
494
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
495
|
+
saveEmployees: () => saveEmployees,
|
|
496
|
+
validateEmployeeName: () => validateEmployeeName
|
|
497
|
+
});
|
|
341
498
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
342
|
-
import { existsSync as
|
|
499
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
343
500
|
import { execSync } from "child_process";
|
|
344
|
-
import
|
|
501
|
+
import path3 from "path";
|
|
345
502
|
import os2 from "os";
|
|
346
503
|
function normalizeRole(role) {
|
|
347
504
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -359,8 +516,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
359
516
|
if (!agentName) return false;
|
|
360
517
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
361
518
|
}
|
|
519
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
520
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
521
|
+
}
|
|
522
|
+
function validateEmployeeName(name) {
|
|
523
|
+
if (!name) {
|
|
524
|
+
return { valid: false, error: "Name is required" };
|
|
525
|
+
}
|
|
526
|
+
if (name.length > 32) {
|
|
527
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
528
|
+
}
|
|
529
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
530
|
+
return {
|
|
531
|
+
valid: false,
|
|
532
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return { valid: true };
|
|
536
|
+
}
|
|
362
537
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
363
|
-
if (!
|
|
538
|
+
if (!existsSync4(employeesPath)) {
|
|
364
539
|
return [];
|
|
365
540
|
}
|
|
366
541
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -370,10 +545,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
370
545
|
return [];
|
|
371
546
|
}
|
|
372
547
|
}
|
|
548
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
549
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
550
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
551
|
+
}
|
|
373
552
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
374
|
-
if (!
|
|
553
|
+
if (!existsSync4(employeesPath)) return [];
|
|
375
554
|
try {
|
|
376
|
-
return JSON.parse(
|
|
555
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
377
556
|
} catch {
|
|
378
557
|
return [];
|
|
379
558
|
}
|
|
@@ -381,6 +560,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
381
560
|
function getEmployee(employees, name) {
|
|
382
561
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
383
562
|
}
|
|
563
|
+
function getEmployeeByRole(employees, role) {
|
|
564
|
+
const lower = role.toLowerCase();
|
|
565
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
566
|
+
}
|
|
567
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
568
|
+
const lower = role.toLowerCase();
|
|
569
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
570
|
+
}
|
|
571
|
+
function hasRole(agentName, role) {
|
|
572
|
+
const employees = loadEmployeesSync();
|
|
573
|
+
const emp = getEmployee(employees, agentName);
|
|
574
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
575
|
+
}
|
|
384
576
|
function baseAgentName(name, employees) {
|
|
385
577
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
386
578
|
if (!match) return name;
|
|
@@ -395,22 +587,147 @@ function isMultiInstance(agentName, employees) {
|
|
|
395
587
|
if (!emp) return false;
|
|
396
588
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
397
589
|
}
|
|
398
|
-
|
|
590
|
+
function addEmployee(employees, employee) {
|
|
591
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
592
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
593
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
594
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
595
|
+
}
|
|
596
|
+
return [...employees, normalized];
|
|
597
|
+
}
|
|
598
|
+
function appendToCoordinatorTeam(employee) {
|
|
599
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
600
|
+
if (!coordinator) return;
|
|
601
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
602
|
+
if (!existsSync4(idPath)) return;
|
|
603
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
604
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
605
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
606
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
607
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
608
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
609
|
+
const entry = `
|
|
610
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
611
|
+
`;
|
|
612
|
+
let updated;
|
|
613
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
614
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
615
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
616
|
+
} else {
|
|
617
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
618
|
+
}
|
|
619
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
620
|
+
}
|
|
621
|
+
function capitalize(s) {
|
|
622
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
623
|
+
}
|
|
624
|
+
async function hireEmployee(employee) {
|
|
625
|
+
const employees = await loadEmployees();
|
|
626
|
+
const updated = addEmployee(employees, employee);
|
|
627
|
+
await saveEmployees(updated);
|
|
628
|
+
try {
|
|
629
|
+
appendToCoordinatorTeam(employee);
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
634
|
+
const config = loadAgentConfig2();
|
|
635
|
+
const name = employee.name.toLowerCase();
|
|
636
|
+
if (!config[name] && config["default"]) {
|
|
637
|
+
config[name] = { ...config["default"] };
|
|
638
|
+
saveAgentConfig2(config);
|
|
639
|
+
}
|
|
640
|
+
} catch {
|
|
641
|
+
}
|
|
642
|
+
return updated;
|
|
643
|
+
}
|
|
644
|
+
async function normalizeRosterCase(rosterPath) {
|
|
645
|
+
const employees = await loadEmployees(rosterPath);
|
|
646
|
+
let changed = false;
|
|
647
|
+
for (const emp of employees) {
|
|
648
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
649
|
+
const oldName = emp.name;
|
|
650
|
+
emp.name = emp.name.toLowerCase();
|
|
651
|
+
changed = true;
|
|
652
|
+
try {
|
|
653
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
654
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
655
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
656
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
657
|
+
renameSync2(oldPath, newPath);
|
|
658
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
659
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
660
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
661
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
662
|
+
unlinkSync(oldPath);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (changed) {
|
|
670
|
+
await saveEmployees(employees, rosterPath);
|
|
671
|
+
}
|
|
672
|
+
return changed;
|
|
673
|
+
}
|
|
674
|
+
function findExeBin() {
|
|
675
|
+
try {
|
|
676
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
677
|
+
} catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function registerBinSymlinks(name) {
|
|
682
|
+
const created = [];
|
|
683
|
+
const skipped = [];
|
|
684
|
+
const errors = [];
|
|
685
|
+
const exeBinPath = findExeBin();
|
|
686
|
+
if (!exeBinPath) {
|
|
687
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
688
|
+
return { created, skipped, errors };
|
|
689
|
+
}
|
|
690
|
+
const binDir = path3.dirname(exeBinPath);
|
|
691
|
+
let target;
|
|
692
|
+
try {
|
|
693
|
+
target = readlinkSync(exeBinPath);
|
|
694
|
+
} catch {
|
|
695
|
+
errors.push("Could not read 'exe' symlink");
|
|
696
|
+
return { created, skipped, errors };
|
|
697
|
+
}
|
|
698
|
+
for (const suffix of ["", "-opencode"]) {
|
|
699
|
+
const linkName = `${name}${suffix}`;
|
|
700
|
+
const linkPath = path3.join(binDir, linkName);
|
|
701
|
+
if (existsSync4(linkPath)) {
|
|
702
|
+
skipped.push(linkName);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
symlinkSync(target, linkPath);
|
|
707
|
+
created.push(linkName);
|
|
708
|
+
} catch (err) {
|
|
709
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return { created, skipped, errors };
|
|
713
|
+
}
|
|
714
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
399
715
|
var init_employees = __esm({
|
|
400
716
|
"src/lib/employees.ts"() {
|
|
401
717
|
"use strict";
|
|
402
718
|
init_config();
|
|
403
|
-
EMPLOYEES_PATH =
|
|
719
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
404
720
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
405
721
|
COORDINATOR_ROLE = "COO";
|
|
406
722
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
407
|
-
IDENTITY_DIR =
|
|
723
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
724
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
408
725
|
}
|
|
409
726
|
});
|
|
410
727
|
|
|
411
728
|
// src/lib/database-adapter.ts
|
|
412
729
|
import os3 from "os";
|
|
413
|
-
import
|
|
730
|
+
import path4 from "path";
|
|
414
731
|
import { createRequire } from "module";
|
|
415
732
|
import { pathToFileURL } from "url";
|
|
416
733
|
var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
|
|
@@ -439,7 +756,7 @@ var init_memory = __esm({
|
|
|
439
756
|
});
|
|
440
757
|
|
|
441
758
|
// src/lib/database.ts
|
|
442
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
759
|
+
import { chmodSync as chmodSync2, existsSync as existsSync5, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
|
|
443
760
|
import { createClient } from "@libsql/client";
|
|
444
761
|
import { homedir } from "os";
|
|
445
762
|
import { join } from "path";
|
|
@@ -492,12 +809,12 @@ var init_database = __esm({
|
|
|
492
809
|
});
|
|
493
810
|
|
|
494
811
|
// src/lib/session-registry.ts
|
|
495
|
-
import { readFileSync as
|
|
496
|
-
import
|
|
812
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
|
|
813
|
+
import path5 from "path";
|
|
497
814
|
import os4 from "os";
|
|
498
815
|
function registerSession(entry) {
|
|
499
|
-
const dir =
|
|
500
|
-
if (!
|
|
816
|
+
const dir = path5.dirname(REGISTRY_PATH);
|
|
817
|
+
if (!existsSync6(dir)) {
|
|
501
818
|
mkdirSync3(dir, { recursive: true });
|
|
502
819
|
}
|
|
503
820
|
const sessions = listSessions();
|
|
@@ -507,11 +824,11 @@ function registerSession(entry) {
|
|
|
507
824
|
} else {
|
|
508
825
|
sessions.push(entry);
|
|
509
826
|
}
|
|
510
|
-
|
|
827
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
511
828
|
}
|
|
512
829
|
function listSessions() {
|
|
513
830
|
try {
|
|
514
|
-
const raw =
|
|
831
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
515
832
|
return JSON.parse(raw);
|
|
516
833
|
} catch {
|
|
517
834
|
return [];
|
|
@@ -521,7 +838,7 @@ var REGISTRY_PATH;
|
|
|
521
838
|
var init_session_registry = __esm({
|
|
522
839
|
"src/lib/session-registry.ts"() {
|
|
523
840
|
"use strict";
|
|
524
|
-
REGISTRY_PATH =
|
|
841
|
+
REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
525
842
|
}
|
|
526
843
|
});
|
|
527
844
|
|
|
@@ -783,68 +1100,6 @@ var init_provider_table = __esm({
|
|
|
783
1100
|
}
|
|
784
1101
|
});
|
|
785
1102
|
|
|
786
|
-
// src/lib/runtime-table.ts
|
|
787
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
788
|
-
var init_runtime_table = __esm({
|
|
789
|
-
"src/lib/runtime-table.ts"() {
|
|
790
|
-
"use strict";
|
|
791
|
-
RUNTIME_TABLE = {
|
|
792
|
-
codex: {
|
|
793
|
-
binary: "codex",
|
|
794
|
-
launchMode: "interactive",
|
|
795
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
796
|
-
inlineFlag: "--no-alt-screen",
|
|
797
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
798
|
-
defaultModel: "gpt-5.5"
|
|
799
|
-
},
|
|
800
|
-
opencode: {
|
|
801
|
-
binary: "opencode",
|
|
802
|
-
launchMode: "exec",
|
|
803
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
804
|
-
inlineFlag: "",
|
|
805
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
806
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
DEFAULT_RUNTIME = "claude";
|
|
810
|
-
}
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
// src/lib/agent-config.ts
|
|
814
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6 } from "fs";
|
|
815
|
-
import path5 from "path";
|
|
816
|
-
function loadAgentConfig() {
|
|
817
|
-
if (!existsSync6(AGENT_CONFIG_PATH)) return {};
|
|
818
|
-
try {
|
|
819
|
-
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
820
|
-
} catch {
|
|
821
|
-
return {};
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
function getAgentRuntime(agentId) {
|
|
825
|
-
const config = loadAgentConfig();
|
|
826
|
-
const entry = config[agentId];
|
|
827
|
-
if (entry) return entry;
|
|
828
|
-
const orgDefault = config["default"];
|
|
829
|
-
if (orgDefault) return orgDefault;
|
|
830
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
831
|
-
}
|
|
832
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
833
|
-
var init_agent_config = __esm({
|
|
834
|
-
"src/lib/agent-config.ts"() {
|
|
835
|
-
"use strict";
|
|
836
|
-
init_config();
|
|
837
|
-
init_runtime_table();
|
|
838
|
-
init_secure_files();
|
|
839
|
-
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
840
|
-
DEFAULT_MODELS = {
|
|
841
|
-
claude: "claude-opus-4.6",
|
|
842
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
843
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
|
|
848
1103
|
// src/lib/intercom-queue.ts
|
|
849
1104
|
var intercom_queue_exports = {};
|
|
850
1105
|
__export(intercom_queue_exports, {
|
|
@@ -972,6 +1227,23 @@ var init_intercom_queue = __esm({
|
|
|
972
1227
|
});
|
|
973
1228
|
|
|
974
1229
|
// src/lib/license.ts
|
|
1230
|
+
var license_exports = {};
|
|
1231
|
+
__export(license_exports, {
|
|
1232
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
1233
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
1234
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
1235
|
+
checkLicense: () => checkLicense,
|
|
1236
|
+
getCachedLicense: () => getCachedLicense,
|
|
1237
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
1238
|
+
loadDeviceId: () => loadDeviceId,
|
|
1239
|
+
loadLicense: () => loadLicense,
|
|
1240
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
1241
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
1242
|
+
saveLicense: () => saveLicense,
|
|
1243
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
1244
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
1245
|
+
validateLicense: () => validateLicense
|
|
1246
|
+
});
|
|
975
1247
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
976
1248
|
import { randomUUID } from "crypto";
|
|
977
1249
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -979,7 +1251,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
979
1251
|
import os6 from "os";
|
|
980
1252
|
import path7 from "path";
|
|
981
1253
|
import { jwtVerify, importSPKI } from "jose";
|
|
982
|
-
|
|
1254
|
+
async function fetchRetry(url, init) {
|
|
1255
|
+
try {
|
|
1256
|
+
return await fetch(url, init);
|
|
1257
|
+
} catch {
|
|
1258
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1259
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
function loadDeviceId() {
|
|
1263
|
+
const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
|
|
1264
|
+
try {
|
|
1265
|
+
if (existsSync8(deviceJsonPath)) {
|
|
1266
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
1267
|
+
if (data.deviceId) return data.deviceId;
|
|
1268
|
+
}
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
1273
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
1274
|
+
if (id2) return id2;
|
|
1275
|
+
}
|
|
1276
|
+
} catch {
|
|
1277
|
+
}
|
|
1278
|
+
const id = randomUUID();
|
|
1279
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
1280
|
+
writeFileSync5(DEVICE_ID_PATH, id, "utf8");
|
|
1281
|
+
return id;
|
|
1282
|
+
}
|
|
1283
|
+
function loadLicense() {
|
|
1284
|
+
try {
|
|
1285
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
1286
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
1287
|
+
} catch {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function saveLicense(apiKey) {
|
|
1292
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
1293
|
+
writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1294
|
+
}
|
|
1295
|
+
async function verifyLicenseJwt(token) {
|
|
1296
|
+
try {
|
|
1297
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1298
|
+
const { payload } = await jwtVerify(token, key, {
|
|
1299
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
1300
|
+
});
|
|
1301
|
+
const plan = payload.plan ?? "free";
|
|
1302
|
+
const email = payload.sub ?? "";
|
|
1303
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1304
|
+
return {
|
|
1305
|
+
valid: true,
|
|
1306
|
+
plan,
|
|
1307
|
+
email,
|
|
1308
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1309
|
+
deviceLimit: limits.devices,
|
|
1310
|
+
employeeLimit: limits.employees,
|
|
1311
|
+
memoryLimit: limits.memories
|
|
1312
|
+
};
|
|
1313
|
+
} catch {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function getCachedLicense() {
|
|
1318
|
+
try {
|
|
1319
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
1320
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1321
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1322
|
+
return await verifyLicenseJwt(raw.token);
|
|
1323
|
+
} catch {
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function readCachedLicenseToken() {
|
|
1328
|
+
try {
|
|
1329
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
1330
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1331
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
1332
|
+
} catch {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function getRawCachedPlan() {
|
|
1337
|
+
try {
|
|
1338
|
+
const token = readCachedLicenseToken();
|
|
1339
|
+
if (!token) return null;
|
|
1340
|
+
const parts = token.split(".");
|
|
1341
|
+
if (parts.length !== 3) return null;
|
|
1342
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1343
|
+
const plan = payload.plan ?? "free";
|
|
1344
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1345
|
+
process.stderr.write(
|
|
1346
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
1347
|
+
`
|
|
1348
|
+
);
|
|
1349
|
+
return {
|
|
1350
|
+
valid: true,
|
|
1351
|
+
plan,
|
|
1352
|
+
email: payload.sub ?? "",
|
|
1353
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1354
|
+
deviceLimit: limits.devices,
|
|
1355
|
+
employeeLimit: limits.employees,
|
|
1356
|
+
memoryLimit: limits.memories
|
|
1357
|
+
};
|
|
1358
|
+
} catch {
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function cacheResponse(token) {
|
|
1363
|
+
try {
|
|
1364
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function loadPrismaForLicense() {
|
|
1369
|
+
if (_prismaFailed) return null;
|
|
1370
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
1371
|
+
if (!dbUrl) {
|
|
1372
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
|
|
1373
|
+
if (!existsSync8(path7.join(exeDbRoot, "package.json"))) {
|
|
1374
|
+
_prismaFailed = true;
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (!_prismaPromise) {
|
|
1379
|
+
_prismaPromise = (async () => {
|
|
1380
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1381
|
+
if (explicitPath) {
|
|
1382
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
1383
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
1384
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
1385
|
+
return new Ctor2();
|
|
1386
|
+
}
|
|
1387
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
|
|
1388
|
+
const req = createRequire2(path7.join(exeDbRoot, "package.json"));
|
|
1389
|
+
const entry = req.resolve("@prisma/client");
|
|
1390
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
1391
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
1392
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
1393
|
+
return new Ctor();
|
|
1394
|
+
})().catch((err) => {
|
|
1395
|
+
_prismaFailed = true;
|
|
1396
|
+
_prismaPromise = null;
|
|
1397
|
+
throw err;
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
return _prismaPromise;
|
|
1401
|
+
}
|
|
1402
|
+
async function validateViaPostgres(apiKey) {
|
|
1403
|
+
const loader = loadPrismaForLicense();
|
|
1404
|
+
if (!loader) return null;
|
|
1405
|
+
try {
|
|
1406
|
+
const prisma = await loader;
|
|
1407
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1408
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
1409
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
1410
|
+
apiKey
|
|
1411
|
+
);
|
|
1412
|
+
if (!rows || rows.length === 0) return null;
|
|
1413
|
+
const row = rows[0];
|
|
1414
|
+
if (row.status !== "active") return null;
|
|
1415
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
1416
|
+
const plan = row.plan;
|
|
1417
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1418
|
+
return {
|
|
1419
|
+
valid: true,
|
|
1420
|
+
plan,
|
|
1421
|
+
email: row.email,
|
|
1422
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
1423
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
1424
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
1425
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
1426
|
+
};
|
|
1427
|
+
} catch {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
1432
|
+
try {
|
|
1433
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1434
|
+
method: "POST",
|
|
1435
|
+
headers: { "Content-Type": "application/json" },
|
|
1436
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
1437
|
+
signal: AbortSignal.timeout(1e4)
|
|
1438
|
+
});
|
|
1439
|
+
if (!res.ok) return null;
|
|
1440
|
+
const data = await res.json();
|
|
1441
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
1442
|
+
if (!data.valid) return null;
|
|
1443
|
+
if (data.token) {
|
|
1444
|
+
cacheResponse(data.token);
|
|
1445
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
1446
|
+
if (verified) return verified;
|
|
1447
|
+
}
|
|
1448
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1449
|
+
return {
|
|
1450
|
+
valid: data.valid,
|
|
1451
|
+
plan: data.plan,
|
|
1452
|
+
email: data.email,
|
|
1453
|
+
expiresAt: data.expiresAt,
|
|
1454
|
+
deviceLimit: limits.devices,
|
|
1455
|
+
employeeLimit: limits.employees,
|
|
1456
|
+
memoryLimit: limits.memories
|
|
1457
|
+
};
|
|
1458
|
+
} catch {
|
|
1459
|
+
return null;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async function validateLicense(apiKey, deviceId) {
|
|
1463
|
+
const did = deviceId ?? loadDeviceId();
|
|
1464
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
1465
|
+
if (pgResult) {
|
|
1466
|
+
try {
|
|
1467
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
1468
|
+
} catch {
|
|
1469
|
+
}
|
|
1470
|
+
return pgResult;
|
|
1471
|
+
}
|
|
1472
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
1473
|
+
if (cfResult) return cfResult;
|
|
1474
|
+
const cached = await getCachedLicense();
|
|
1475
|
+
if (cached) return cached;
|
|
1476
|
+
try {
|
|
1477
|
+
if (existsSync8(CACHE_PATH)) {
|
|
1478
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1479
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
1480
|
+
return raw.pgLicense;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
} catch {
|
|
1484
|
+
}
|
|
1485
|
+
const rawFallback = getRawCachedPlan();
|
|
1486
|
+
if (rawFallback) return rawFallback;
|
|
1487
|
+
return { ...FREE_LICENSE, valid: false };
|
|
1488
|
+
}
|
|
1489
|
+
function getCacheAgeMs() {
|
|
1490
|
+
try {
|
|
1491
|
+
const { statSync: statSync3 } = __require("fs");
|
|
1492
|
+
const s = statSync3(CACHE_PATH);
|
|
1493
|
+
return Date.now() - s.mtimeMs;
|
|
1494
|
+
} catch {
|
|
1495
|
+
return Infinity;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
async function checkLicense() {
|
|
1499
|
+
let key = loadLicense();
|
|
1500
|
+
if (!key) {
|
|
1501
|
+
try {
|
|
1502
|
+
const configPath = path7.join(EXE_AI_DIR, "config.json");
|
|
1503
|
+
if (existsSync8(configPath)) {
|
|
1504
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
1505
|
+
const cloud = raw.cloud;
|
|
1506
|
+
if (cloud?.apiKey) {
|
|
1507
|
+
key = cloud.apiKey;
|
|
1508
|
+
saveLicense(key);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
} catch {
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (!key) return FREE_LICENSE;
|
|
1515
|
+
const cached = await getCachedLicense();
|
|
1516
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
1517
|
+
const deviceId = loadDeviceId();
|
|
1518
|
+
return validateLicense(key, deviceId);
|
|
1519
|
+
}
|
|
1520
|
+
function isFeatureAllowed(license, feature) {
|
|
1521
|
+
switch (feature) {
|
|
1522
|
+
case "cloud_sync":
|
|
1523
|
+
case "external_agents":
|
|
1524
|
+
case "wiki":
|
|
1525
|
+
return license.plan !== "free";
|
|
1526
|
+
case "unlimited_employees":
|
|
1527
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
function mirrorLicenseKey(apiKey) {
|
|
1531
|
+
const trimmed = apiKey.trim();
|
|
1532
|
+
if (!trimmed) return;
|
|
1533
|
+
saveLicense(trimmed);
|
|
1534
|
+
}
|
|
1535
|
+
async function assertVpsLicense(opts) {
|
|
1536
|
+
const env = opts?.env ?? process.env;
|
|
1537
|
+
const inProduction = env.NODE_ENV === "production";
|
|
1538
|
+
if (!opts?.force && !inProduction) {
|
|
1539
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
1540
|
+
}
|
|
1541
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
1542
|
+
if (envKey) {
|
|
1543
|
+
saveLicense(envKey);
|
|
1544
|
+
}
|
|
1545
|
+
const apiKey = envKey ?? loadLicense();
|
|
1546
|
+
if (!apiKey) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
"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."
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
const deviceId = loadDeviceId();
|
|
1552
|
+
let backendResponse = null;
|
|
1553
|
+
let explicitRejection = false;
|
|
1554
|
+
let transientFailure = false;
|
|
1555
|
+
try {
|
|
1556
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1557
|
+
method: "POST",
|
|
1558
|
+
headers: { "Content-Type": "application/json" },
|
|
1559
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
1560
|
+
signal: AbortSignal.timeout(1e4)
|
|
1561
|
+
});
|
|
1562
|
+
if (res.ok) {
|
|
1563
|
+
backendResponse = await res.json();
|
|
1564
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
1565
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
1566
|
+
explicitRejection = true;
|
|
1567
|
+
} else {
|
|
1568
|
+
transientFailure = true;
|
|
1569
|
+
}
|
|
1570
|
+
} catch {
|
|
1571
|
+
transientFailure = true;
|
|
1572
|
+
}
|
|
1573
|
+
if (backendResponse?.valid) {
|
|
1574
|
+
if (backendResponse.token) {
|
|
1575
|
+
cacheResponse(backendResponse.token);
|
|
1576
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
1577
|
+
if (verified) return verified;
|
|
1578
|
+
}
|
|
1579
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
1580
|
+
return {
|
|
1581
|
+
valid: true,
|
|
1582
|
+
plan: backendResponse.plan,
|
|
1583
|
+
email: backendResponse.email,
|
|
1584
|
+
expiresAt: backendResponse.expiresAt,
|
|
1585
|
+
deviceLimit: limits.devices,
|
|
1586
|
+
employeeLimit: limits.employees,
|
|
1587
|
+
memoryLimit: limits.memories
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
if (explicitRejection) {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
if (!transientFailure) {
|
|
1596
|
+
throw new Error(
|
|
1597
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
const fresh = await getCachedLicense();
|
|
1601
|
+
if (fresh && fresh.valid) return fresh;
|
|
1602
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
1603
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
1604
|
+
try {
|
|
1605
|
+
const token = readCachedLicenseToken();
|
|
1606
|
+
if (token) {
|
|
1607
|
+
const payloadB64 = token.split(".")[1];
|
|
1608
|
+
if (payloadB64) {
|
|
1609
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
1610
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
1611
|
+
if (Date.now() < expMs + graceMs) {
|
|
1612
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1613
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
1614
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
1615
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
1616
|
+
});
|
|
1617
|
+
const plan = verified.plan ?? "free";
|
|
1618
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1619
|
+
return {
|
|
1620
|
+
valid: true,
|
|
1621
|
+
plan,
|
|
1622
|
+
email: verified.sub ?? "",
|
|
1623
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
1624
|
+
deviceLimit: limits.devices,
|
|
1625
|
+
employeeLimit: limits.employees,
|
|
1626
|
+
memoryLimit: limits.memories
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
} catch {
|
|
1632
|
+
}
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`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.`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
1638
|
+
if (_revalTimer) return;
|
|
1639
|
+
_revalTimer = setInterval(async () => {
|
|
1640
|
+
try {
|
|
1641
|
+
const license = await checkLicense();
|
|
1642
|
+
if (!license.valid) {
|
|
1643
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
1644
|
+
}
|
|
1645
|
+
} catch {
|
|
1646
|
+
}
|
|
1647
|
+
}, intervalMs);
|
|
1648
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
1649
|
+
_revalTimer.unref();
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
function stopLicenseRevalidation() {
|
|
1653
|
+
if (_revalTimer) {
|
|
1654
|
+
clearInterval(_revalTimer);
|
|
1655
|
+
_revalTimer = null;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
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;
|
|
983
1659
|
var init_license = __esm({
|
|
984
1660
|
"src/lib/license.ts"() {
|
|
985
1661
|
"use strict";
|
|
@@ -987,7 +1663,13 @@ var init_license = __esm({
|
|
|
987
1663
|
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
988
1664
|
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
989
1665
|
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
990
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
1666
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
1667
|
+
RETRY_DELAY_MS = 500;
|
|
1668
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1669
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1670
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
1671
|
+
-----END PUBLIC KEY-----`;
|
|
1672
|
+
LICENSE_JWT_ALG = "ES256";
|
|
991
1673
|
PLAN_LIMITS = {
|
|
992
1674
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
993
1675
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -995,6 +1677,19 @@ var init_license = __esm({
|
|
|
995
1677
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
996
1678
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
997
1679
|
};
|
|
1680
|
+
FREE_LICENSE = {
|
|
1681
|
+
valid: true,
|
|
1682
|
+
plan: "free",
|
|
1683
|
+
email: "",
|
|
1684
|
+
expiresAt: null,
|
|
1685
|
+
deviceLimit: 1,
|
|
1686
|
+
employeeLimit: 1,
|
|
1687
|
+
memoryLimit: 5e3
|
|
1688
|
+
};
|
|
1689
|
+
_prismaPromise = null;
|
|
1690
|
+
_prismaFailed = false;
|
|
1691
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
1692
|
+
_revalTimer = null;
|
|
998
1693
|
}
|
|
999
1694
|
});
|
|
1000
1695
|
|
|
@@ -1994,11 +2689,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
1994
2689
|
}
|
|
1995
2690
|
}
|
|
1996
2691
|
function resolveExeSession() {
|
|
2692
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
2693
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
2694
|
+
if (fromEnv) return fromEnv;
|
|
2695
|
+
}
|
|
1997
2696
|
const mySession = getMySession();
|
|
1998
2697
|
if (!mySession) {
|
|
1999
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
2000
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
2001
|
-
}
|
|
2002
2698
|
return null;
|
|
2003
2699
|
}
|
|
2004
2700
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -2013,6 +2709,10 @@ function resolveExeSession() {
|
|
|
2013
2709
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
2014
2710
|
`
|
|
2015
2711
|
);
|
|
2712
|
+
try {
|
|
2713
|
+
registerParentExe(key, fromSessionName);
|
|
2714
|
+
} catch {
|
|
2715
|
+
}
|
|
2016
2716
|
candidate = fromSessionName;
|
|
2017
2717
|
} else {
|
|
2018
2718
|
candidate = fromCache;
|
|
@@ -2966,6 +3666,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
2966
3666
|
args: [identifier, ...scope.args]
|
|
2967
3667
|
});
|
|
2968
3668
|
if (result.rows.length === 1) return result.rows[0];
|
|
3669
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
3670
|
+
result = await client.execute({
|
|
3671
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
3672
|
+
args: [`${identifier}%`]
|
|
3673
|
+
});
|
|
3674
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
3675
|
+
if (result.rows.length > 1) {
|
|
3676
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
3677
|
+
throw new Error(
|
|
3678
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
3679
|
+
);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
2969
3682
|
result = await client.execute({
|
|
2970
3683
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
2971
3684
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -3512,12 +4225,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3512
4225
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
3513
4226
|
args: [now, taskId]
|
|
3514
4227
|
});
|
|
3515
|
-
if (
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
4228
|
+
if (unblocked.rowsAffected === 0) return;
|
|
4229
|
+
const ubScope = sessionScopeFilter();
|
|
4230
|
+
const unblockedRows = await client.execute({
|
|
4231
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
4232
|
+
args: [now, ...ubScope.args]
|
|
4233
|
+
});
|
|
4234
|
+
if (baseDir) {
|
|
3521
4235
|
for (const ur of unblockedRows.rows) {
|
|
3522
4236
|
try {
|
|
3523
4237
|
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
@@ -3529,6 +4243,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3529
4243
|
}
|
|
3530
4244
|
}
|
|
3531
4245
|
}
|
|
4246
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
4247
|
+
try {
|
|
4248
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4249
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
4250
|
+
for (const ur of unblockedRows.rows) {
|
|
4251
|
+
const assignee = String(ur.assigned_to);
|
|
4252
|
+
if (dispatched.has(assignee)) continue;
|
|
4253
|
+
dispatched.add(assignee);
|
|
4254
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
4255
|
+
}
|
|
4256
|
+
} catch {
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
3532
4259
|
}
|
|
3533
4260
|
async function findNextTask(assignedTo) {
|
|
3534
4261
|
const client = getClient();
|
|
@@ -4187,6 +4914,15 @@ var init_embedder = __esm({
|
|
|
4187
4914
|
// src/lib/behaviors.ts
|
|
4188
4915
|
import crypto5 from "crypto";
|
|
4189
4916
|
async function storeBehavior(opts) {
|
|
4917
|
+
try {
|
|
4918
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
4919
|
+
const roster = loadEmployeesSync2();
|
|
4920
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
4921
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
4922
|
+
}
|
|
4923
|
+
} catch (e) {
|
|
4924
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
4925
|
+
}
|
|
4190
4926
|
const client = getClient();
|
|
4191
4927
|
const id = crypto5.randomUUID();
|
|
4192
4928
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4197,10 +4933,18 @@ async function storeBehavior(opts) {
|
|
|
4197
4933
|
vector = new Float32Array(vec);
|
|
4198
4934
|
} catch {
|
|
4199
4935
|
}
|
|
4936
|
+
let createdByDevice = null;
|
|
4937
|
+
try {
|
|
4938
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
4939
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
4940
|
+
} catch {
|
|
4941
|
+
}
|
|
4942
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
4943
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
4200
4944
|
await client.execute({
|
|
4201
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
4202
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
4203
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
4945
|
+
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)
|
|
4946
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
4947
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
4204
4948
|
});
|
|
4205
4949
|
return id;
|
|
4206
4950
|
}
|
|
@@ -4632,6 +5376,12 @@ async function updateTask(input) {
|
|
|
4632
5376
|
}
|
|
4633
5377
|
}
|
|
4634
5378
|
}
|
|
5379
|
+
if (input.status === "cancelled") {
|
|
5380
|
+
try {
|
|
5381
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5382
|
+
} catch {
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
4635
5385
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4636
5386
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4637
5387
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|