@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
|
@@ -432,6 +432,7 @@ __export(agent_config_exports, {
|
|
|
432
432
|
getAgentRuntime: () => getAgentRuntime,
|
|
433
433
|
loadAgentConfig: () => loadAgentConfig,
|
|
434
434
|
saveAgentConfig: () => saveAgentConfig,
|
|
435
|
+
setAgentMcps: () => setAgentMcps,
|
|
435
436
|
setAgentRuntime: () => setAgentRuntime
|
|
436
437
|
});
|
|
437
438
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -458,7 +459,7 @@ function getAgentRuntime(agentId) {
|
|
|
458
459
|
if (orgDefault) return orgDefault;
|
|
459
460
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
460
461
|
}
|
|
461
|
-
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
462
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
462
463
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
463
464
|
if (!knownModels) {
|
|
464
465
|
return {
|
|
@@ -473,12 +474,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
|
473
474
|
};
|
|
474
475
|
}
|
|
475
476
|
const config = loadAgentConfig();
|
|
477
|
+
const existing = config[agentId];
|
|
476
478
|
const entry = { runtime, model };
|
|
477
479
|
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
480
|
+
if (mcps !== void 0) {
|
|
481
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
482
|
+
} else if (existing?.mcps) {
|
|
483
|
+
entry.mcps = existing.mcps;
|
|
484
|
+
}
|
|
478
485
|
config[agentId] = entry;
|
|
479
486
|
saveAgentConfig(config);
|
|
480
487
|
return { ok: true };
|
|
481
488
|
}
|
|
489
|
+
function setAgentMcps(agentId, mcps) {
|
|
490
|
+
const config = loadAgentConfig();
|
|
491
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
492
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
493
|
+
config[agentId] = existing;
|
|
494
|
+
saveAgentConfig(config);
|
|
495
|
+
return { ok: true };
|
|
496
|
+
}
|
|
482
497
|
function clearAgentRuntime(agentId) {
|
|
483
498
|
const config = loadAgentConfig();
|
|
484
499
|
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
|
|
|
@@ -7777,6 +7842,23 @@ var init_intercom_queue = __esm({
|
|
|
7777
7842
|
});
|
|
7778
7843
|
|
|
7779
7844
|
// src/lib/license.ts
|
|
7845
|
+
var license_exports = {};
|
|
7846
|
+
__export(license_exports, {
|
|
7847
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
7848
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
7849
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
7850
|
+
checkLicense: () => checkLicense,
|
|
7851
|
+
getCachedLicense: () => getCachedLicense,
|
|
7852
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
7853
|
+
loadDeviceId: () => loadDeviceId,
|
|
7854
|
+
loadLicense: () => loadLicense,
|
|
7855
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
7856
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
7857
|
+
saveLicense: () => saveLicense,
|
|
7858
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
7859
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
7860
|
+
validateLicense: () => validateLicense
|
|
7861
|
+
});
|
|
7780
7862
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
|
|
7781
7863
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
7782
7864
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -7784,7 +7866,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
7784
7866
|
import os9 from "os";
|
|
7785
7867
|
import path16 from "path";
|
|
7786
7868
|
import { jwtVerify, importSPKI } from "jose";
|
|
7787
|
-
|
|
7869
|
+
async function fetchRetry(url, init) {
|
|
7870
|
+
try {
|
|
7871
|
+
return await fetch(url, init);
|
|
7872
|
+
} catch {
|
|
7873
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
7874
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7877
|
+
function loadDeviceId() {
|
|
7878
|
+
const deviceJsonPath = path16.join(EXE_AI_DIR, "device.json");
|
|
7879
|
+
try {
|
|
7880
|
+
if (existsSync15(deviceJsonPath)) {
|
|
7881
|
+
const data = JSON.parse(readFileSync11(deviceJsonPath, "utf8"));
|
|
7882
|
+
if (data.deviceId) return data.deviceId;
|
|
7883
|
+
}
|
|
7884
|
+
} catch {
|
|
7885
|
+
}
|
|
7886
|
+
try {
|
|
7887
|
+
if (existsSync15(DEVICE_ID_PATH)) {
|
|
7888
|
+
const id2 = readFileSync11(DEVICE_ID_PATH, "utf8").trim();
|
|
7889
|
+
if (id2) return id2;
|
|
7890
|
+
}
|
|
7891
|
+
} catch {
|
|
7892
|
+
}
|
|
7893
|
+
const id = randomUUID3();
|
|
7894
|
+
mkdirSync8(EXE_AI_DIR, { recursive: true });
|
|
7895
|
+
writeFileSync8(DEVICE_ID_PATH, id, "utf8");
|
|
7896
|
+
return id;
|
|
7897
|
+
}
|
|
7898
|
+
function loadLicense() {
|
|
7899
|
+
try {
|
|
7900
|
+
if (!existsSync15(LICENSE_PATH)) return null;
|
|
7901
|
+
return readFileSync11(LICENSE_PATH, "utf8").trim();
|
|
7902
|
+
} catch {
|
|
7903
|
+
return null;
|
|
7904
|
+
}
|
|
7905
|
+
}
|
|
7906
|
+
function saveLicense(apiKey) {
|
|
7907
|
+
mkdirSync8(EXE_AI_DIR, { recursive: true });
|
|
7908
|
+
writeFileSync8(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
7909
|
+
}
|
|
7910
|
+
async function verifyLicenseJwt(token) {
|
|
7911
|
+
try {
|
|
7912
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
7913
|
+
const { payload } = await jwtVerify(token, key, {
|
|
7914
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
7915
|
+
});
|
|
7916
|
+
const plan = payload.plan ?? "free";
|
|
7917
|
+
const email = payload.sub ?? "";
|
|
7918
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
7919
|
+
return {
|
|
7920
|
+
valid: true,
|
|
7921
|
+
plan,
|
|
7922
|
+
email,
|
|
7923
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
7924
|
+
deviceLimit: limits.devices,
|
|
7925
|
+
employeeLimit: limits.employees,
|
|
7926
|
+
memoryLimit: limits.memories
|
|
7927
|
+
};
|
|
7928
|
+
} catch {
|
|
7929
|
+
return null;
|
|
7930
|
+
}
|
|
7931
|
+
}
|
|
7932
|
+
async function getCachedLicense() {
|
|
7933
|
+
try {
|
|
7934
|
+
if (!existsSync15(CACHE_PATH)) return null;
|
|
7935
|
+
const raw = JSON.parse(readFileSync11(CACHE_PATH, "utf8"));
|
|
7936
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
7937
|
+
return await verifyLicenseJwt(raw.token);
|
|
7938
|
+
} catch {
|
|
7939
|
+
return null;
|
|
7940
|
+
}
|
|
7941
|
+
}
|
|
7942
|
+
function readCachedLicenseToken() {
|
|
7943
|
+
try {
|
|
7944
|
+
if (!existsSync15(CACHE_PATH)) return null;
|
|
7945
|
+
const raw = JSON.parse(readFileSync11(CACHE_PATH, "utf8"));
|
|
7946
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
7947
|
+
} catch {
|
|
7948
|
+
return null;
|
|
7949
|
+
}
|
|
7950
|
+
}
|
|
7951
|
+
function getRawCachedPlan() {
|
|
7952
|
+
try {
|
|
7953
|
+
const token = readCachedLicenseToken();
|
|
7954
|
+
if (!token) return null;
|
|
7955
|
+
const parts = token.split(".");
|
|
7956
|
+
if (parts.length !== 3) return null;
|
|
7957
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
7958
|
+
const plan = payload.plan ?? "free";
|
|
7959
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
7960
|
+
process.stderr.write(
|
|
7961
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
7962
|
+
`
|
|
7963
|
+
);
|
|
7964
|
+
return {
|
|
7965
|
+
valid: true,
|
|
7966
|
+
plan,
|
|
7967
|
+
email: payload.sub ?? "",
|
|
7968
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
7969
|
+
deviceLimit: limits.devices,
|
|
7970
|
+
employeeLimit: limits.employees,
|
|
7971
|
+
memoryLimit: limits.memories
|
|
7972
|
+
};
|
|
7973
|
+
} catch {
|
|
7974
|
+
return null;
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
function cacheResponse(token) {
|
|
7978
|
+
try {
|
|
7979
|
+
writeFileSync8(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
7980
|
+
} catch {
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
7983
|
+
function loadPrismaForLicense() {
|
|
7984
|
+
if (_prismaFailed) return null;
|
|
7985
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
7986
|
+
if (!dbUrl) {
|
|
7987
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path16.join(os9.homedir(), "exe-db");
|
|
7988
|
+
if (!existsSync15(path16.join(exeDbRoot, "package.json"))) {
|
|
7989
|
+
_prismaFailed = true;
|
|
7990
|
+
return null;
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
if (!_prismaPromise) {
|
|
7994
|
+
_prismaPromise = (async () => {
|
|
7995
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
7996
|
+
if (explicitPath) {
|
|
7997
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
7998
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
7999
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
8000
|
+
return new Ctor2();
|
|
8001
|
+
}
|
|
8002
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path16.join(os9.homedir(), "exe-db");
|
|
8003
|
+
const req = createRequire2(path16.join(exeDbRoot, "package.json"));
|
|
8004
|
+
const entry = req.resolve("@prisma/client");
|
|
8005
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
8006
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
8007
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
8008
|
+
return new Ctor();
|
|
8009
|
+
})().catch((err) => {
|
|
8010
|
+
_prismaFailed = true;
|
|
8011
|
+
_prismaPromise = null;
|
|
8012
|
+
throw err;
|
|
8013
|
+
});
|
|
8014
|
+
}
|
|
8015
|
+
return _prismaPromise;
|
|
8016
|
+
}
|
|
8017
|
+
async function validateViaPostgres(apiKey) {
|
|
8018
|
+
const loader = loadPrismaForLicense();
|
|
8019
|
+
if (!loader) return null;
|
|
8020
|
+
try {
|
|
8021
|
+
const prisma = await loader;
|
|
8022
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
8023
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
8024
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
8025
|
+
apiKey
|
|
8026
|
+
);
|
|
8027
|
+
if (!rows || rows.length === 0) return null;
|
|
8028
|
+
const row = rows[0];
|
|
8029
|
+
if (row.status !== "active") return null;
|
|
8030
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
8031
|
+
const plan = row.plan;
|
|
8032
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
8033
|
+
return {
|
|
8034
|
+
valid: true,
|
|
8035
|
+
plan,
|
|
8036
|
+
email: row.email,
|
|
8037
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
8038
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
8039
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
8040
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
8041
|
+
};
|
|
8042
|
+
} catch {
|
|
8043
|
+
return null;
|
|
8044
|
+
}
|
|
8045
|
+
}
|
|
8046
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
8047
|
+
try {
|
|
8048
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
8049
|
+
method: "POST",
|
|
8050
|
+
headers: { "Content-Type": "application/json" },
|
|
8051
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
8052
|
+
signal: AbortSignal.timeout(1e4)
|
|
8053
|
+
});
|
|
8054
|
+
if (!res.ok) return null;
|
|
8055
|
+
const data = await res.json();
|
|
8056
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
8057
|
+
if (!data.valid) return null;
|
|
8058
|
+
if (data.token) {
|
|
8059
|
+
cacheResponse(data.token);
|
|
8060
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
8061
|
+
if (verified) return verified;
|
|
8062
|
+
}
|
|
8063
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
8064
|
+
return {
|
|
8065
|
+
valid: data.valid,
|
|
8066
|
+
plan: data.plan,
|
|
8067
|
+
email: data.email,
|
|
8068
|
+
expiresAt: data.expiresAt,
|
|
8069
|
+
deviceLimit: limits.devices,
|
|
8070
|
+
employeeLimit: limits.employees,
|
|
8071
|
+
memoryLimit: limits.memories
|
|
8072
|
+
};
|
|
8073
|
+
} catch {
|
|
8074
|
+
return null;
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8077
|
+
async function validateLicense(apiKey, deviceId) {
|
|
8078
|
+
const did = deviceId ?? loadDeviceId();
|
|
8079
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
8080
|
+
if (pgResult) {
|
|
8081
|
+
try {
|
|
8082
|
+
writeFileSync8(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
8083
|
+
} catch {
|
|
8084
|
+
}
|
|
8085
|
+
return pgResult;
|
|
8086
|
+
}
|
|
8087
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
8088
|
+
if (cfResult) return cfResult;
|
|
8089
|
+
const cached = await getCachedLicense();
|
|
8090
|
+
if (cached) return cached;
|
|
8091
|
+
try {
|
|
8092
|
+
if (existsSync15(CACHE_PATH)) {
|
|
8093
|
+
const raw = JSON.parse(readFileSync11(CACHE_PATH, "utf8"));
|
|
8094
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
8095
|
+
return raw.pgLicense;
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
8098
|
+
} catch {
|
|
8099
|
+
}
|
|
8100
|
+
const rawFallback = getRawCachedPlan();
|
|
8101
|
+
if (rawFallback) return rawFallback;
|
|
8102
|
+
return { ...FREE_LICENSE, valid: false };
|
|
8103
|
+
}
|
|
8104
|
+
function getCacheAgeMs() {
|
|
8105
|
+
try {
|
|
8106
|
+
const { statSync: statSync7 } = __require("fs");
|
|
8107
|
+
const s = statSync7(CACHE_PATH);
|
|
8108
|
+
return Date.now() - s.mtimeMs;
|
|
8109
|
+
} catch {
|
|
8110
|
+
return Infinity;
|
|
8111
|
+
}
|
|
8112
|
+
}
|
|
8113
|
+
async function checkLicense() {
|
|
8114
|
+
let key = loadLicense();
|
|
8115
|
+
if (!key) {
|
|
8116
|
+
try {
|
|
8117
|
+
const configPath = path16.join(EXE_AI_DIR, "config.json");
|
|
8118
|
+
if (existsSync15(configPath)) {
|
|
8119
|
+
const raw = JSON.parse(readFileSync11(configPath, "utf8"));
|
|
8120
|
+
const cloud = raw.cloud;
|
|
8121
|
+
if (cloud?.apiKey) {
|
|
8122
|
+
key = cloud.apiKey;
|
|
8123
|
+
saveLicense(key);
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
8126
|
+
} catch {
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8129
|
+
if (!key) return FREE_LICENSE;
|
|
8130
|
+
const cached = await getCachedLicense();
|
|
8131
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
8132
|
+
const deviceId = loadDeviceId();
|
|
8133
|
+
return validateLicense(key, deviceId);
|
|
8134
|
+
}
|
|
8135
|
+
function isFeatureAllowed(license, feature) {
|
|
8136
|
+
switch (feature) {
|
|
8137
|
+
case "cloud_sync":
|
|
8138
|
+
case "external_agents":
|
|
8139
|
+
case "wiki":
|
|
8140
|
+
return license.plan !== "free";
|
|
8141
|
+
case "unlimited_employees":
|
|
8142
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
8145
|
+
function mirrorLicenseKey(apiKey) {
|
|
8146
|
+
const trimmed = apiKey.trim();
|
|
8147
|
+
if (!trimmed) return;
|
|
8148
|
+
saveLicense(trimmed);
|
|
8149
|
+
}
|
|
8150
|
+
async function assertVpsLicense(opts) {
|
|
8151
|
+
const env = opts?.env ?? process.env;
|
|
8152
|
+
const inProduction = env.NODE_ENV === "production";
|
|
8153
|
+
if (!opts?.force && !inProduction) {
|
|
8154
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
8155
|
+
}
|
|
8156
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
8157
|
+
if (envKey) {
|
|
8158
|
+
saveLicense(envKey);
|
|
8159
|
+
}
|
|
8160
|
+
const apiKey = envKey ?? loadLicense();
|
|
8161
|
+
if (!apiKey) {
|
|
8162
|
+
throw new Error(
|
|
8163
|
+
"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."
|
|
8164
|
+
);
|
|
8165
|
+
}
|
|
8166
|
+
const deviceId = loadDeviceId();
|
|
8167
|
+
let backendResponse = null;
|
|
8168
|
+
let explicitRejection = false;
|
|
8169
|
+
let transientFailure = false;
|
|
8170
|
+
try {
|
|
8171
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
8172
|
+
method: "POST",
|
|
8173
|
+
headers: { "Content-Type": "application/json" },
|
|
8174
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
8175
|
+
signal: AbortSignal.timeout(1e4)
|
|
8176
|
+
});
|
|
8177
|
+
if (res.ok) {
|
|
8178
|
+
backendResponse = await res.json();
|
|
8179
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
8180
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
8181
|
+
explicitRejection = true;
|
|
8182
|
+
} else {
|
|
8183
|
+
transientFailure = true;
|
|
8184
|
+
}
|
|
8185
|
+
} catch {
|
|
8186
|
+
transientFailure = true;
|
|
8187
|
+
}
|
|
8188
|
+
if (backendResponse?.valid) {
|
|
8189
|
+
if (backendResponse.token) {
|
|
8190
|
+
cacheResponse(backendResponse.token);
|
|
8191
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
8192
|
+
if (verified) return verified;
|
|
8193
|
+
}
|
|
8194
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
8195
|
+
return {
|
|
8196
|
+
valid: true,
|
|
8197
|
+
plan: backendResponse.plan,
|
|
8198
|
+
email: backendResponse.email,
|
|
8199
|
+
expiresAt: backendResponse.expiresAt,
|
|
8200
|
+
deviceLimit: limits.devices,
|
|
8201
|
+
employeeLimit: limits.employees,
|
|
8202
|
+
memoryLimit: limits.memories
|
|
8203
|
+
};
|
|
8204
|
+
}
|
|
8205
|
+
if (explicitRejection) {
|
|
8206
|
+
throw new Error(
|
|
8207
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
8208
|
+
);
|
|
8209
|
+
}
|
|
8210
|
+
if (!transientFailure) {
|
|
8211
|
+
throw new Error(
|
|
8212
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
8213
|
+
);
|
|
8214
|
+
}
|
|
8215
|
+
const fresh = await getCachedLicense();
|
|
8216
|
+
if (fresh && fresh.valid) return fresh;
|
|
8217
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
8218
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
8219
|
+
try {
|
|
8220
|
+
const token = readCachedLicenseToken();
|
|
8221
|
+
if (token) {
|
|
8222
|
+
const payloadB64 = token.split(".")[1];
|
|
8223
|
+
if (payloadB64) {
|
|
8224
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
8225
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
8226
|
+
if (Date.now() < expMs + graceMs) {
|
|
8227
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
8228
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
8229
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
8230
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
8231
|
+
});
|
|
8232
|
+
const plan = verified.plan ?? "free";
|
|
8233
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
8234
|
+
return {
|
|
8235
|
+
valid: true,
|
|
8236
|
+
plan,
|
|
8237
|
+
email: verified.sub ?? "",
|
|
8238
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
8239
|
+
deviceLimit: limits.devices,
|
|
8240
|
+
employeeLimit: limits.employees,
|
|
8241
|
+
memoryLimit: limits.memories
|
|
8242
|
+
};
|
|
8243
|
+
}
|
|
8244
|
+
}
|
|
8245
|
+
}
|
|
8246
|
+
} catch {
|
|
8247
|
+
}
|
|
8248
|
+
throw new Error(
|
|
8249
|
+
`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.`
|
|
8250
|
+
);
|
|
8251
|
+
}
|
|
8252
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
8253
|
+
if (_revalTimer) return;
|
|
8254
|
+
_revalTimer = setInterval(async () => {
|
|
8255
|
+
try {
|
|
8256
|
+
const license = await checkLicense();
|
|
8257
|
+
if (!license.valid) {
|
|
8258
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
8259
|
+
}
|
|
8260
|
+
} catch {
|
|
8261
|
+
}
|
|
8262
|
+
}, intervalMs);
|
|
8263
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
8264
|
+
_revalTimer.unref();
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
function stopLicenseRevalidation() {
|
|
8268
|
+
if (_revalTimer) {
|
|
8269
|
+
clearInterval(_revalTimer);
|
|
8270
|
+
_revalTimer = null;
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
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;
|
|
7788
8274
|
var init_license = __esm({
|
|
7789
8275
|
"src/lib/license.ts"() {
|
|
7790
8276
|
"use strict";
|
|
@@ -7792,7 +8278,13 @@ var init_license = __esm({
|
|
|
7792
8278
|
LICENSE_PATH = path16.join(EXE_AI_DIR, "license.key");
|
|
7793
8279
|
CACHE_PATH = path16.join(EXE_AI_DIR, "license-cache.json");
|
|
7794
8280
|
DEVICE_ID_PATH = path16.join(EXE_AI_DIR, "device-id");
|
|
7795
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
8281
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
8282
|
+
RETRY_DELAY_MS = 500;
|
|
8283
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
8284
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
8285
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
8286
|
+
-----END PUBLIC KEY-----`;
|
|
8287
|
+
LICENSE_JWT_ALG = "ES256";
|
|
7796
8288
|
PLAN_LIMITS = {
|
|
7797
8289
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
7798
8290
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -7800,6 +8292,19 @@ var init_license = __esm({
|
|
|
7800
8292
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
7801
8293
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
7802
8294
|
};
|
|
8295
|
+
FREE_LICENSE = {
|
|
8296
|
+
valid: true,
|
|
8297
|
+
plan: "free",
|
|
8298
|
+
email: "",
|
|
8299
|
+
expiresAt: null,
|
|
8300
|
+
deviceLimit: 1,
|
|
8301
|
+
employeeLimit: 1,
|
|
8302
|
+
memoryLimit: 5e3
|
|
8303
|
+
};
|
|
8304
|
+
_prismaPromise = null;
|
|
8305
|
+
_prismaFailed = false;
|
|
8306
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
8307
|
+
_revalTimer = null;
|
|
7803
8308
|
}
|
|
7804
8309
|
});
|
|
7805
8310
|
|
|
@@ -8214,6 +8719,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
8214
8719
|
args: [identifier, ...scope.args]
|
|
8215
8720
|
});
|
|
8216
8721
|
if (result.rows.length === 1) return result.rows[0];
|
|
8722
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
8723
|
+
result = await client.execute({
|
|
8724
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
8725
|
+
args: [`${identifier}%`]
|
|
8726
|
+
});
|
|
8727
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
8728
|
+
if (result.rows.length > 1) {
|
|
8729
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
8730
|
+
throw new Error(
|
|
8731
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
8732
|
+
);
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8217
8735
|
result = await client.execute({
|
|
8218
8736
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
8219
8737
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -9068,12 +9586,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
9068
9586
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
9069
9587
|
args: [now, taskId]
|
|
9070
9588
|
});
|
|
9071
|
-
if (
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9589
|
+
if (unblocked.rowsAffected === 0) return;
|
|
9590
|
+
const ubScope = sessionScopeFilter();
|
|
9591
|
+
const unblockedRows = await client.execute({
|
|
9592
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
9593
|
+
args: [now, ...ubScope.args]
|
|
9594
|
+
});
|
|
9595
|
+
if (baseDir) {
|
|
9077
9596
|
for (const ur of unblockedRows.rows) {
|
|
9078
9597
|
try {
|
|
9079
9598
|
const ubFile = path22.join(baseDir, String(ur.task_file));
|
|
@@ -9085,6 +9604,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
9085
9604
|
}
|
|
9086
9605
|
}
|
|
9087
9606
|
}
|
|
9607
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
9608
|
+
try {
|
|
9609
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
9610
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
9611
|
+
for (const ur of unblockedRows.rows) {
|
|
9612
|
+
const assignee = String(ur.assigned_to);
|
|
9613
|
+
if (dispatched.has(assignee)) continue;
|
|
9614
|
+
dispatched.add(assignee);
|
|
9615
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
9616
|
+
}
|
|
9617
|
+
} catch {
|
|
9618
|
+
}
|
|
9619
|
+
}
|
|
9088
9620
|
}
|
|
9089
9621
|
async function findNextTask(assignedTo) {
|
|
9090
9622
|
const client = getClient();
|
|
@@ -9233,6 +9765,15 @@ __export(behaviors_exports, {
|
|
|
9233
9765
|
});
|
|
9234
9766
|
import crypto6 from "crypto";
|
|
9235
9767
|
async function storeBehavior(opts) {
|
|
9768
|
+
try {
|
|
9769
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
9770
|
+
const roster = loadEmployeesSync2();
|
|
9771
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
9772
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
9773
|
+
}
|
|
9774
|
+
} catch (e) {
|
|
9775
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
9776
|
+
}
|
|
9236
9777
|
const client = getClient();
|
|
9237
9778
|
const id = crypto6.randomUUID();
|
|
9238
9779
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9243,17 +9784,25 @@ async function storeBehavior(opts) {
|
|
|
9243
9784
|
vector = new Float32Array(vec);
|
|
9244
9785
|
} catch {
|
|
9245
9786
|
}
|
|
9787
|
+
let createdByDevice = null;
|
|
9788
|
+
try {
|
|
9789
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
9790
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
9791
|
+
} catch {
|
|
9792
|
+
}
|
|
9793
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
9794
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
9246
9795
|
await client.execute({
|
|
9247
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
9248
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
9249
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
9796
|
+
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)
|
|
9797
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
9798
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
9250
9799
|
});
|
|
9251
9800
|
return id;
|
|
9252
9801
|
}
|
|
9253
9802
|
async function listBehaviors(agentId, projectName, limit = 30) {
|
|
9254
9803
|
const client = getClient();
|
|
9255
9804
|
const result = await client.execute({
|
|
9256
|
-
sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector
|
|
9805
|
+
sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id
|
|
9257
9806
|
FROM behaviors
|
|
9258
9807
|
WHERE agent_id = ? AND active = 1
|
|
9259
9808
|
AND (project_name IS NULL OR project_name = ?)
|
|
@@ -9282,7 +9831,10 @@ async function listBehaviors(agentId, projectName, limit = 30) {
|
|
|
9282
9831
|
active: Number(r.active),
|
|
9283
9832
|
created_at: String(r.created_at),
|
|
9284
9833
|
updated_at: String(r.updated_at),
|
|
9285
|
-
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
|
|
9834
|
+
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
|
|
9835
|
+
created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
|
|
9836
|
+
created_by_device: r.created_by_device ? String(r.created_by_device) : null,
|
|
9837
|
+
source_session_id: r.source_session_id ? String(r.source_session_id) : null
|
|
9286
9838
|
}));
|
|
9287
9839
|
}
|
|
9288
9840
|
async function listBehaviorsByDomain(agentId, domain) {
|
|
@@ -9303,7 +9855,10 @@ async function listBehaviorsByDomain(agentId, domain) {
|
|
|
9303
9855
|
active: Number(r.active),
|
|
9304
9856
|
created_at: String(r.created_at),
|
|
9305
9857
|
updated_at: String(r.updated_at),
|
|
9306
|
-
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
|
|
9858
|
+
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
|
|
9859
|
+
created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
|
|
9860
|
+
created_by_device: r.created_by_device ? String(r.created_by_device) : null,
|
|
9861
|
+
source_session_id: r.source_session_id ? String(r.source_session_id) : null
|
|
9307
9862
|
}));
|
|
9308
9863
|
}
|
|
9309
9864
|
async function deactivateBehavior(id) {
|
|
@@ -9742,6 +10297,12 @@ async function updateTask(input2) {
|
|
|
9742
10297
|
}
|
|
9743
10298
|
}
|
|
9744
10299
|
}
|
|
10300
|
+
if (input2.status === "cancelled") {
|
|
10301
|
+
try {
|
|
10302
|
+
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
10303
|
+
} catch {
|
|
10304
|
+
}
|
|
10305
|
+
}
|
|
9745
10306
|
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
9746
10307
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
9747
10308
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -10273,11 +10834,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
10273
10834
|
}
|
|
10274
10835
|
}
|
|
10275
10836
|
function resolveExeSession() {
|
|
10837
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
10838
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
10839
|
+
if (fromEnv) return fromEnv;
|
|
10840
|
+
}
|
|
10276
10841
|
const mySession = getMySession();
|
|
10277
10842
|
if (!mySession) {
|
|
10278
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
10279
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
10280
|
-
}
|
|
10281
10843
|
return null;
|
|
10282
10844
|
}
|
|
10283
10845
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -10292,6 +10854,10 @@ function resolveExeSession() {
|
|
|
10292
10854
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
10293
10855
|
`
|
|
10294
10856
|
);
|
|
10857
|
+
try {
|
|
10858
|
+
registerParentExe(key, fromSessionName);
|
|
10859
|
+
} catch {
|
|
10860
|
+
}
|
|
10295
10861
|
candidate = fromSessionName;
|
|
10296
10862
|
} else {
|
|
10297
10863
|
candidate = fromCache;
|
|
@@ -11753,7 +12319,7 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
11753
12319
|
try {
|
|
11754
12320
|
const client = getClient();
|
|
11755
12321
|
void client.execute({
|
|
11756
|
-
sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1 WHERE id IN (${placeholders})`,
|
|
12322
|
+
sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1, strength = MIN(1.0, COALESCE(strength, 1.0) + 0.1) WHERE id IN (${placeholders})`,
|
|
11757
12323
|
args: [now, ...ids]
|
|
11758
12324
|
}).catch(() => {
|
|
11759
12325
|
});
|