@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
|
@@ -423,6 +423,7 @@ __export(agent_config_exports, {
|
|
|
423
423
|
getAgentRuntime: () => getAgentRuntime,
|
|
424
424
|
loadAgentConfig: () => loadAgentConfig,
|
|
425
425
|
saveAgentConfig: () => saveAgentConfig,
|
|
426
|
+
setAgentMcps: () => setAgentMcps,
|
|
426
427
|
setAgentRuntime: () => setAgentRuntime
|
|
427
428
|
});
|
|
428
429
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -449,7 +450,7 @@ function getAgentRuntime(agentId) {
|
|
|
449
450
|
if (orgDefault) return orgDefault;
|
|
450
451
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
451
452
|
}
|
|
452
|
-
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
453
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
453
454
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
454
455
|
if (!knownModels) {
|
|
455
456
|
return {
|
|
@@ -464,12 +465,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
|
464
465
|
};
|
|
465
466
|
}
|
|
466
467
|
const config = loadAgentConfig();
|
|
468
|
+
const existing = config[agentId];
|
|
467
469
|
const entry = { runtime, model };
|
|
468
470
|
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
471
|
+
if (mcps !== void 0) {
|
|
472
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
473
|
+
} else if (existing?.mcps) {
|
|
474
|
+
entry.mcps = existing.mcps;
|
|
475
|
+
}
|
|
469
476
|
config[agentId] = entry;
|
|
470
477
|
saveAgentConfig(config);
|
|
471
478
|
return { ok: true };
|
|
472
479
|
}
|
|
480
|
+
function setAgentMcps(agentId, mcps) {
|
|
481
|
+
const config = loadAgentConfig();
|
|
482
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
483
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
484
|
+
config[agentId] = existing;
|
|
485
|
+
saveAgentConfig(config);
|
|
486
|
+
return { ok: true };
|
|
487
|
+
}
|
|
473
488
|
function clearAgentRuntime(agentId) {
|
|
474
489
|
const config = loadAgentConfig();
|
|
475
490
|
delete config[agentId];
|
|
@@ -2436,6 +2451,13 @@ async function ensureSchema() {
|
|
|
2436
2451
|
} catch (e) {
|
|
2437
2452
|
logCatchDebug("migration", e);
|
|
2438
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
|
+
}
|
|
2439
2461
|
try {
|
|
2440
2462
|
await client.execute({
|
|
2441
2463
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3652,6 +3674,22 @@ async function ensureSchema() {
|
|
|
3652
3674
|
} catch (e) {
|
|
3653
3675
|
logCatchDebug("migration", e);
|
|
3654
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
|
+
}
|
|
3655
3693
|
}
|
|
3656
3694
|
async function disposeDatabase() {
|
|
3657
3695
|
if (_walCheckpointTimer) {
|
|
@@ -4775,11 +4813,17 @@ var init_platform_procedures = __esm({
|
|
|
4775
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."
|
|
4776
4814
|
},
|
|
4777
4815
|
{
|
|
4778
|
-
title: "
|
|
4816
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4779
4817
|
domain: "workflow",
|
|
4780
4818
|
priority: "p1",
|
|
4781
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."
|
|
4782
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
|
+
},
|
|
4783
4827
|
{
|
|
4784
4828
|
title: "Single dispatch path \u2014 create_task only",
|
|
4785
4829
|
domain: "workflow",
|
|
@@ -4813,6 +4857,12 @@ var init_platform_procedures = __esm({
|
|
|
4813
4857
|
priority: "p0",
|
|
4814
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."
|
|
4815
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
|
+
},
|
|
4816
4866
|
{
|
|
4817
4867
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4818
4868
|
domain: "support",
|
|
@@ -4964,7 +5014,7 @@ var init_platform_procedures = __esm({
|
|
|
4964
5014
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
4965
5015
|
domain: "tool-use",
|
|
4966
5016
|
priority: "p0",
|
|
4967
|
-
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.'
|
|
4968
5018
|
},
|
|
4969
5019
|
{
|
|
4970
5020
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -5098,10 +5148,24 @@ function stableId(memoryId, type, content) {
|
|
|
5098
5148
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
5099
5149
|
}
|
|
5100
5150
|
function cleanText(text) {
|
|
5101
|
-
|
|
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;
|
|
5102
5157
|
}
|
|
5103
|
-
function
|
|
5104
|
-
|
|
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;
|
|
5105
5169
|
}
|
|
5106
5170
|
function inferCardType(sentence, toolName) {
|
|
5107
5171
|
const lower = sentence.toLowerCase();
|
|
@@ -5133,12 +5197,12 @@ function predicateFor(type) {
|
|
|
5133
5197
|
}
|
|
5134
5198
|
}
|
|
5135
5199
|
function extractMemoryCards(row) {
|
|
5136
|
-
const
|
|
5200
|
+
const segments = splitSegments(row.raw_text);
|
|
5137
5201
|
const cards = [];
|
|
5138
|
-
for (const sentence of
|
|
5202
|
+
for (const sentence of segments) {
|
|
5139
5203
|
const type = inferCardType(sentence, row.tool_name);
|
|
5140
5204
|
const subject = extractSubject(sentence, row.agent_id);
|
|
5141
|
-
const content = sentence.length >
|
|
5205
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
5142
5206
|
cards.push({
|
|
5143
5207
|
id: stableId(row.id, type, content),
|
|
5144
5208
|
memory_id: row.id,
|
|
@@ -5234,13 +5298,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
5234
5298
|
last_accessed: String(row.timestamp)
|
|
5235
5299
|
}));
|
|
5236
5300
|
}
|
|
5237
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5301
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
5238
5302
|
var init_memory_cards = __esm({
|
|
5239
5303
|
"src/lib/memory-cards.ts"() {
|
|
5240
5304
|
"use strict";
|
|
5241
5305
|
init_database();
|
|
5242
|
-
MAX_CARDS_PER_MEMORY =
|
|
5243
|
-
|
|
5306
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5307
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5308
|
+
MIN_SEGMENT_CHARS = 20;
|
|
5244
5309
|
}
|
|
5245
5310
|
});
|
|
5246
5311
|
|
|
@@ -6594,6 +6659,23 @@ var init_intercom_queue = __esm({
|
|
|
6594
6659
|
});
|
|
6595
6660
|
|
|
6596
6661
|
// src/lib/license.ts
|
|
6662
|
+
var license_exports = {};
|
|
6663
|
+
__export(license_exports, {
|
|
6664
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
6665
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
6666
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
6667
|
+
checkLicense: () => checkLicense,
|
|
6668
|
+
getCachedLicense: () => getCachedLicense,
|
|
6669
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
6670
|
+
loadDeviceId: () => loadDeviceId,
|
|
6671
|
+
loadLicense: () => loadLicense,
|
|
6672
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
6673
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
6674
|
+
saveLicense: () => saveLicense,
|
|
6675
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
6676
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
6677
|
+
validateLicense: () => validateLicense
|
|
6678
|
+
});
|
|
6597
6679
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
|
|
6598
6680
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6599
6681
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -6601,7 +6683,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
6601
6683
|
import os8 from "os";
|
|
6602
6684
|
import path11 from "path";
|
|
6603
6685
|
import { jwtVerify, importSPKI } from "jose";
|
|
6604
|
-
|
|
6686
|
+
async function fetchRetry(url, init) {
|
|
6687
|
+
try {
|
|
6688
|
+
return await fetch(url, init);
|
|
6689
|
+
} catch {
|
|
6690
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
6691
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
6692
|
+
}
|
|
6693
|
+
}
|
|
6694
|
+
function loadDeviceId() {
|
|
6695
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
6696
|
+
try {
|
|
6697
|
+
if (existsSync12(deviceJsonPath)) {
|
|
6698
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
6699
|
+
if (data.deviceId) return data.deviceId;
|
|
6700
|
+
}
|
|
6701
|
+
} catch {
|
|
6702
|
+
}
|
|
6703
|
+
try {
|
|
6704
|
+
if (existsSync12(DEVICE_ID_PATH)) {
|
|
6705
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
6706
|
+
if (id2) return id2;
|
|
6707
|
+
}
|
|
6708
|
+
} catch {
|
|
6709
|
+
}
|
|
6710
|
+
const id = randomUUID3();
|
|
6711
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
6712
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
6713
|
+
return id;
|
|
6714
|
+
}
|
|
6715
|
+
function loadLicense() {
|
|
6716
|
+
try {
|
|
6717
|
+
if (!existsSync12(LICENSE_PATH)) return null;
|
|
6718
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
6719
|
+
} catch {
|
|
6720
|
+
return null;
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
function saveLicense(apiKey) {
|
|
6724
|
+
mkdirSync6(EXE_AI_DIR, { recursive: true });
|
|
6725
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
6726
|
+
}
|
|
6727
|
+
async function verifyLicenseJwt(token) {
|
|
6728
|
+
try {
|
|
6729
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
6730
|
+
const { payload } = await jwtVerify(token, key, {
|
|
6731
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
6732
|
+
});
|
|
6733
|
+
const plan = payload.plan ?? "free";
|
|
6734
|
+
const email = payload.sub ?? "";
|
|
6735
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6736
|
+
return {
|
|
6737
|
+
valid: true,
|
|
6738
|
+
plan,
|
|
6739
|
+
email,
|
|
6740
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6741
|
+
deviceLimit: limits.devices,
|
|
6742
|
+
employeeLimit: limits.employees,
|
|
6743
|
+
memoryLimit: limits.memories
|
|
6744
|
+
};
|
|
6745
|
+
} catch {
|
|
6746
|
+
return null;
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
async function getCachedLicense() {
|
|
6750
|
+
try {
|
|
6751
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
6752
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6753
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
6754
|
+
return await verifyLicenseJwt(raw.token);
|
|
6755
|
+
} catch {
|
|
6756
|
+
return null;
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
6759
|
+
function readCachedLicenseToken() {
|
|
6760
|
+
try {
|
|
6761
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
6762
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6763
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
6764
|
+
} catch {
|
|
6765
|
+
return null;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
function getRawCachedPlan() {
|
|
6769
|
+
try {
|
|
6770
|
+
const token = readCachedLicenseToken();
|
|
6771
|
+
if (!token) return null;
|
|
6772
|
+
const parts = token.split(".");
|
|
6773
|
+
if (parts.length !== 3) return null;
|
|
6774
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
6775
|
+
const plan = payload.plan ?? "free";
|
|
6776
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6777
|
+
process.stderr.write(
|
|
6778
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
6779
|
+
`
|
|
6780
|
+
);
|
|
6781
|
+
return {
|
|
6782
|
+
valid: true,
|
|
6783
|
+
plan,
|
|
6784
|
+
email: payload.sub ?? "",
|
|
6785
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
6786
|
+
deviceLimit: limits.devices,
|
|
6787
|
+
employeeLimit: limits.employees,
|
|
6788
|
+
memoryLimit: limits.memories
|
|
6789
|
+
};
|
|
6790
|
+
} catch {
|
|
6791
|
+
return null;
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
function cacheResponse(token) {
|
|
6795
|
+
try {
|
|
6796
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
6797
|
+
} catch {
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
function loadPrismaForLicense() {
|
|
6801
|
+
if (_prismaFailed) return null;
|
|
6802
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
6803
|
+
if (!dbUrl) {
|
|
6804
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6805
|
+
if (!existsSync12(path11.join(exeDbRoot, "package.json"))) {
|
|
6806
|
+
_prismaFailed = true;
|
|
6807
|
+
return null;
|
|
6808
|
+
}
|
|
6809
|
+
}
|
|
6810
|
+
if (!_prismaPromise) {
|
|
6811
|
+
_prismaPromise = (async () => {
|
|
6812
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
6813
|
+
if (explicitPath) {
|
|
6814
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
6815
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
6816
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
6817
|
+
return new Ctor2();
|
|
6818
|
+
}
|
|
6819
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
6820
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
6821
|
+
const entry = req.resolve("@prisma/client");
|
|
6822
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
6823
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
6824
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
6825
|
+
return new Ctor();
|
|
6826
|
+
})().catch((err) => {
|
|
6827
|
+
_prismaFailed = true;
|
|
6828
|
+
_prismaPromise = null;
|
|
6829
|
+
throw err;
|
|
6830
|
+
});
|
|
6831
|
+
}
|
|
6832
|
+
return _prismaPromise;
|
|
6833
|
+
}
|
|
6834
|
+
async function validateViaPostgres(apiKey) {
|
|
6835
|
+
const loader = loadPrismaForLicense();
|
|
6836
|
+
if (!loader) return null;
|
|
6837
|
+
try {
|
|
6838
|
+
const prisma = await loader;
|
|
6839
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
6840
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
6841
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
6842
|
+
apiKey
|
|
6843
|
+
);
|
|
6844
|
+
if (!rows || rows.length === 0) return null;
|
|
6845
|
+
const row = rows[0];
|
|
6846
|
+
if (row.status !== "active") return null;
|
|
6847
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
6848
|
+
const plan = row.plan;
|
|
6849
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
6850
|
+
return {
|
|
6851
|
+
valid: true,
|
|
6852
|
+
plan,
|
|
6853
|
+
email: row.email,
|
|
6854
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
6855
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
6856
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
6857
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
6858
|
+
};
|
|
6859
|
+
} catch {
|
|
6860
|
+
return null;
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
6864
|
+
try {
|
|
6865
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
6866
|
+
method: "POST",
|
|
6867
|
+
headers: { "Content-Type": "application/json" },
|
|
6868
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
6869
|
+
signal: AbortSignal.timeout(1e4)
|
|
6870
|
+
});
|
|
6871
|
+
if (!res.ok) return null;
|
|
6872
|
+
const data = await res.json();
|
|
6873
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
6874
|
+
if (!data.valid) return null;
|
|
6875
|
+
if (data.token) {
|
|
6876
|
+
cacheResponse(data.token);
|
|
6877
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
6878
|
+
if (verified) return verified;
|
|
6879
|
+
}
|
|
6880
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
6881
|
+
return {
|
|
6882
|
+
valid: data.valid,
|
|
6883
|
+
plan: data.plan,
|
|
6884
|
+
email: data.email,
|
|
6885
|
+
expiresAt: data.expiresAt,
|
|
6886
|
+
deviceLimit: limits.devices,
|
|
6887
|
+
employeeLimit: limits.employees,
|
|
6888
|
+
memoryLimit: limits.memories
|
|
6889
|
+
};
|
|
6890
|
+
} catch {
|
|
6891
|
+
return null;
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
async function validateLicense(apiKey, deviceId) {
|
|
6895
|
+
const did = deviceId ?? loadDeviceId();
|
|
6896
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
6897
|
+
if (pgResult) {
|
|
6898
|
+
try {
|
|
6899
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
6900
|
+
} catch {
|
|
6901
|
+
}
|
|
6902
|
+
return pgResult;
|
|
6903
|
+
}
|
|
6904
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
6905
|
+
if (cfResult) return cfResult;
|
|
6906
|
+
const cached = await getCachedLicense();
|
|
6907
|
+
if (cached) return cached;
|
|
6908
|
+
try {
|
|
6909
|
+
if (existsSync12(CACHE_PATH)) {
|
|
6910
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
6911
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
6912
|
+
return raw.pgLicense;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
} catch {
|
|
6916
|
+
}
|
|
6917
|
+
const rawFallback = getRawCachedPlan();
|
|
6918
|
+
if (rawFallback) return rawFallback;
|
|
6919
|
+
return { ...FREE_LICENSE, valid: false };
|
|
6920
|
+
}
|
|
6921
|
+
function getCacheAgeMs() {
|
|
6922
|
+
try {
|
|
6923
|
+
const { statSync: statSync5 } = __require("fs");
|
|
6924
|
+
const s = statSync5(CACHE_PATH);
|
|
6925
|
+
return Date.now() - s.mtimeMs;
|
|
6926
|
+
} catch {
|
|
6927
|
+
return Infinity;
|
|
6928
|
+
}
|
|
6929
|
+
}
|
|
6930
|
+
async function checkLicense() {
|
|
6931
|
+
let key = loadLicense();
|
|
6932
|
+
if (!key) {
|
|
6933
|
+
try {
|
|
6934
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
6935
|
+
if (existsSync12(configPath)) {
|
|
6936
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
6937
|
+
const cloud = raw.cloud;
|
|
6938
|
+
if (cloud?.apiKey) {
|
|
6939
|
+
key = cloud.apiKey;
|
|
6940
|
+
saveLicense(key);
|
|
6941
|
+
}
|
|
6942
|
+
}
|
|
6943
|
+
} catch {
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
if (!key) return FREE_LICENSE;
|
|
6947
|
+
const cached = await getCachedLicense();
|
|
6948
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
6949
|
+
const deviceId = loadDeviceId();
|
|
6950
|
+
return validateLicense(key, deviceId);
|
|
6951
|
+
}
|
|
6952
|
+
function isFeatureAllowed(license, feature) {
|
|
6953
|
+
switch (feature) {
|
|
6954
|
+
case "cloud_sync":
|
|
6955
|
+
case "external_agents":
|
|
6956
|
+
case "wiki":
|
|
6957
|
+
return license.plan !== "free";
|
|
6958
|
+
case "unlimited_employees":
|
|
6959
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
6960
|
+
}
|
|
6961
|
+
}
|
|
6962
|
+
function mirrorLicenseKey(apiKey) {
|
|
6963
|
+
const trimmed = apiKey.trim();
|
|
6964
|
+
if (!trimmed) return;
|
|
6965
|
+
saveLicense(trimmed);
|
|
6966
|
+
}
|
|
6967
|
+
async function assertVpsLicense(opts) {
|
|
6968
|
+
const env = opts?.env ?? process.env;
|
|
6969
|
+
const inProduction = env.NODE_ENV === "production";
|
|
6970
|
+
if (!opts?.force && !inProduction) {
|
|
6971
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
6972
|
+
}
|
|
6973
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
6974
|
+
if (envKey) {
|
|
6975
|
+
saveLicense(envKey);
|
|
6976
|
+
}
|
|
6977
|
+
const apiKey = envKey ?? loadLicense();
|
|
6978
|
+
if (!apiKey) {
|
|
6979
|
+
throw new Error(
|
|
6980
|
+
"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."
|
|
6981
|
+
);
|
|
6982
|
+
}
|
|
6983
|
+
const deviceId = loadDeviceId();
|
|
6984
|
+
let backendResponse = null;
|
|
6985
|
+
let explicitRejection = false;
|
|
6986
|
+
let transientFailure = false;
|
|
6987
|
+
try {
|
|
6988
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
6989
|
+
method: "POST",
|
|
6990
|
+
headers: { "Content-Type": "application/json" },
|
|
6991
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
6992
|
+
signal: AbortSignal.timeout(1e4)
|
|
6993
|
+
});
|
|
6994
|
+
if (res.ok) {
|
|
6995
|
+
backendResponse = await res.json();
|
|
6996
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
6997
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
6998
|
+
explicitRejection = true;
|
|
6999
|
+
} else {
|
|
7000
|
+
transientFailure = true;
|
|
7001
|
+
}
|
|
7002
|
+
} catch {
|
|
7003
|
+
transientFailure = true;
|
|
7004
|
+
}
|
|
7005
|
+
if (backendResponse?.valid) {
|
|
7006
|
+
if (backendResponse.token) {
|
|
7007
|
+
cacheResponse(backendResponse.token);
|
|
7008
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
7009
|
+
if (verified) return verified;
|
|
7010
|
+
}
|
|
7011
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
7012
|
+
return {
|
|
7013
|
+
valid: true,
|
|
7014
|
+
plan: backendResponse.plan,
|
|
7015
|
+
email: backendResponse.email,
|
|
7016
|
+
expiresAt: backendResponse.expiresAt,
|
|
7017
|
+
deviceLimit: limits.devices,
|
|
7018
|
+
employeeLimit: limits.employees,
|
|
7019
|
+
memoryLimit: limits.memories
|
|
7020
|
+
};
|
|
7021
|
+
}
|
|
7022
|
+
if (explicitRejection) {
|
|
7023
|
+
throw new Error(
|
|
7024
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
7025
|
+
);
|
|
7026
|
+
}
|
|
7027
|
+
if (!transientFailure) {
|
|
7028
|
+
throw new Error(
|
|
7029
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
7030
|
+
);
|
|
7031
|
+
}
|
|
7032
|
+
const fresh = await getCachedLicense();
|
|
7033
|
+
if (fresh && fresh.valid) return fresh;
|
|
7034
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
7035
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
7036
|
+
try {
|
|
7037
|
+
const token = readCachedLicenseToken();
|
|
7038
|
+
if (token) {
|
|
7039
|
+
const payloadB64 = token.split(".")[1];
|
|
7040
|
+
if (payloadB64) {
|
|
7041
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
7042
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
7043
|
+
if (Date.now() < expMs + graceMs) {
|
|
7044
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
7045
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
7046
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
7047
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
7048
|
+
});
|
|
7049
|
+
const plan = verified.plan ?? "free";
|
|
7050
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
7051
|
+
return {
|
|
7052
|
+
valid: true,
|
|
7053
|
+
plan,
|
|
7054
|
+
email: verified.sub ?? "",
|
|
7055
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
7056
|
+
deviceLimit: limits.devices,
|
|
7057
|
+
employeeLimit: limits.employees,
|
|
7058
|
+
memoryLimit: limits.memories
|
|
7059
|
+
};
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
} catch {
|
|
7064
|
+
}
|
|
7065
|
+
throw new Error(
|
|
7066
|
+
`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.`
|
|
7067
|
+
);
|
|
7068
|
+
}
|
|
7069
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
7070
|
+
if (_revalTimer) return;
|
|
7071
|
+
_revalTimer = setInterval(async () => {
|
|
7072
|
+
try {
|
|
7073
|
+
const license = await checkLicense();
|
|
7074
|
+
if (!license.valid) {
|
|
7075
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
7076
|
+
}
|
|
7077
|
+
} catch {
|
|
7078
|
+
}
|
|
7079
|
+
}, intervalMs);
|
|
7080
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
7081
|
+
_revalTimer.unref();
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
function stopLicenseRevalidation() {
|
|
7085
|
+
if (_revalTimer) {
|
|
7086
|
+
clearInterval(_revalTimer);
|
|
7087
|
+
_revalTimer = null;
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
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;
|
|
6605
7091
|
var init_license = __esm({
|
|
6606
7092
|
"src/lib/license.ts"() {
|
|
6607
7093
|
"use strict";
|
|
@@ -6609,7 +7095,13 @@ var init_license = __esm({
|
|
|
6609
7095
|
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
6610
7096
|
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
6611
7097
|
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
6612
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
7098
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
7099
|
+
RETRY_DELAY_MS = 500;
|
|
7100
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
7101
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
7102
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
7103
|
+
-----END PUBLIC KEY-----`;
|
|
7104
|
+
LICENSE_JWT_ALG = "ES256";
|
|
6613
7105
|
PLAN_LIMITS = {
|
|
6614
7106
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
6615
7107
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -6617,6 +7109,19 @@ var init_license = __esm({
|
|
|
6617
7109
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
6618
7110
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
6619
7111
|
};
|
|
7112
|
+
FREE_LICENSE = {
|
|
7113
|
+
valid: true,
|
|
7114
|
+
plan: "free",
|
|
7115
|
+
email: "",
|
|
7116
|
+
expiresAt: null,
|
|
7117
|
+
deviceLimit: 1,
|
|
7118
|
+
employeeLimit: 1,
|
|
7119
|
+
memoryLimit: 5e3
|
|
7120
|
+
};
|
|
7121
|
+
_prismaPromise = null;
|
|
7122
|
+
_prismaFailed = false;
|
|
7123
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
7124
|
+
_revalTimer = null;
|
|
6620
7125
|
}
|
|
6621
7126
|
});
|
|
6622
7127
|
|
|
@@ -7561,11 +8066,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
7561
8066
|
}
|
|
7562
8067
|
}
|
|
7563
8068
|
function resolveExeSession() {
|
|
8069
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
8070
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
8071
|
+
if (fromEnv) return fromEnv;
|
|
8072
|
+
}
|
|
7564
8073
|
const mySession = getMySession();
|
|
7565
8074
|
if (!mySession) {
|
|
7566
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
7567
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7568
|
-
}
|
|
7569
8075
|
return null;
|
|
7570
8076
|
}
|
|
7571
8077
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -7580,6 +8086,10 @@ function resolveExeSession() {
|
|
|
7580
8086
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
7581
8087
|
`
|
|
7582
8088
|
);
|
|
8089
|
+
try {
|
|
8090
|
+
registerParentExe(key, fromSessionName);
|
|
8091
|
+
} catch {
|
|
8092
|
+
}
|
|
7583
8093
|
candidate = fromSessionName;
|
|
7584
8094
|
} else {
|
|
7585
8095
|
candidate = fromCache;
|
|
@@ -8533,6 +9043,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
8533
9043
|
args: [identifier, ...scope.args]
|
|
8534
9044
|
});
|
|
8535
9045
|
if (result.rows.length === 1) return result.rows[0];
|
|
9046
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
9047
|
+
result = await client.execute({
|
|
9048
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
9049
|
+
args: [`${identifier}%`]
|
|
9050
|
+
});
|
|
9051
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
9052
|
+
if (result.rows.length > 1) {
|
|
9053
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
9054
|
+
throw new Error(
|
|
9055
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
9056
|
+
);
|
|
9057
|
+
}
|
|
9058
|
+
}
|
|
8536
9059
|
result = await client.execute({
|
|
8537
9060
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
8538
9061
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -9079,12 +9602,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
9079
9602
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
9080
9603
|
args: [now, taskId]
|
|
9081
9604
|
});
|
|
9082
|
-
if (
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9605
|
+
if (unblocked.rowsAffected === 0) return;
|
|
9606
|
+
const ubScope = sessionScopeFilter();
|
|
9607
|
+
const unblockedRows = await client.execute({
|
|
9608
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
9609
|
+
args: [now, ...ubScope.args]
|
|
9610
|
+
});
|
|
9611
|
+
if (baseDir) {
|
|
9088
9612
|
for (const ur of unblockedRows.rows) {
|
|
9089
9613
|
try {
|
|
9090
9614
|
const ubFile = path19.join(baseDir, String(ur.task_file));
|
|
@@ -9096,6 +9620,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
9096
9620
|
}
|
|
9097
9621
|
}
|
|
9098
9622
|
}
|
|
9623
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
9624
|
+
try {
|
|
9625
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
9626
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
9627
|
+
for (const ur of unblockedRows.rows) {
|
|
9628
|
+
const assignee = String(ur.assigned_to);
|
|
9629
|
+
if (dispatched.has(assignee)) continue;
|
|
9630
|
+
dispatched.add(assignee);
|
|
9631
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
9632
|
+
}
|
|
9633
|
+
} catch {
|
|
9634
|
+
}
|
|
9635
|
+
}
|
|
9099
9636
|
}
|
|
9100
9637
|
async function findNextTask(assignedTo) {
|
|
9101
9638
|
const client = getClient();
|
|
@@ -9305,6 +9842,15 @@ var init_embedder = __esm({
|
|
|
9305
9842
|
// src/lib/behaviors.ts
|
|
9306
9843
|
import crypto5 from "crypto";
|
|
9307
9844
|
async function storeBehavior(opts) {
|
|
9845
|
+
try {
|
|
9846
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
9847
|
+
const roster = loadEmployeesSync2();
|
|
9848
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
9849
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
9850
|
+
}
|
|
9851
|
+
} catch (e) {
|
|
9852
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
9853
|
+
}
|
|
9308
9854
|
const client = getClient();
|
|
9309
9855
|
const id = crypto5.randomUUID();
|
|
9310
9856
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9315,10 +9861,18 @@ async function storeBehavior(opts) {
|
|
|
9315
9861
|
vector = new Float32Array(vec);
|
|
9316
9862
|
} catch {
|
|
9317
9863
|
}
|
|
9864
|
+
let createdByDevice = null;
|
|
9865
|
+
try {
|
|
9866
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
9867
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
9868
|
+
} catch {
|
|
9869
|
+
}
|
|
9870
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
9871
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
9318
9872
|
await client.execute({
|
|
9319
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
9320
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
9321
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
9873
|
+
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)
|
|
9874
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
9875
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
9322
9876
|
});
|
|
9323
9877
|
return id;
|
|
9324
9878
|
}
|
|
@@ -9750,6 +10304,12 @@ async function updateTask(input) {
|
|
|
9750
10304
|
}
|
|
9751
10305
|
}
|
|
9752
10306
|
}
|
|
10307
|
+
if (input.status === "cancelled") {
|
|
10308
|
+
try {
|
|
10309
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
10310
|
+
} catch {
|
|
10311
|
+
}
|
|
10312
|
+
}
|
|
9753
10313
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
9754
10314
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
9755
10315
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|