@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/gateway/index.js
CHANGED
|
@@ -734,6 +734,7 @@ __export(agent_config_exports, {
|
|
|
734
734
|
getAgentRuntime: () => getAgentRuntime,
|
|
735
735
|
loadAgentConfig: () => loadAgentConfig,
|
|
736
736
|
saveAgentConfig: () => saveAgentConfig,
|
|
737
|
+
setAgentMcps: () => setAgentMcps,
|
|
737
738
|
setAgentRuntime: () => setAgentRuntime
|
|
738
739
|
});
|
|
739
740
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -760,7 +761,7 @@ function getAgentRuntime(agentId) {
|
|
|
760
761
|
if (orgDefault) return orgDefault;
|
|
761
762
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
762
763
|
}
|
|
763
|
-
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
764
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
764
765
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
765
766
|
if (!knownModels) {
|
|
766
767
|
return {
|
|
@@ -775,12 +776,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
|
775
776
|
};
|
|
776
777
|
}
|
|
777
778
|
const config2 = loadAgentConfig();
|
|
779
|
+
const existing = config2[agentId];
|
|
778
780
|
const entry = { runtime, model };
|
|
779
781
|
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
782
|
+
if (mcps !== void 0) {
|
|
783
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
784
|
+
} else if (existing?.mcps) {
|
|
785
|
+
entry.mcps = existing.mcps;
|
|
786
|
+
}
|
|
780
787
|
config2[agentId] = entry;
|
|
781
788
|
saveAgentConfig(config2);
|
|
782
789
|
return { ok: true };
|
|
783
790
|
}
|
|
791
|
+
function setAgentMcps(agentId, mcps) {
|
|
792
|
+
const config2 = loadAgentConfig();
|
|
793
|
+
const existing = config2[agentId] ?? getAgentRuntime(agentId);
|
|
794
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
795
|
+
config2[agentId] = existing;
|
|
796
|
+
saveAgentConfig(config2);
|
|
797
|
+
return { ok: true };
|
|
798
|
+
}
|
|
784
799
|
function clearAgentRuntime(agentId) {
|
|
785
800
|
const config2 = loadAgentConfig();
|
|
786
801
|
delete config2[agentId];
|
|
@@ -2681,6 +2696,13 @@ async function ensureSchema() {
|
|
|
2681
2696
|
} catch (e) {
|
|
2682
2697
|
logCatchDebug("migration", e);
|
|
2683
2698
|
}
|
|
2699
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2700
|
+
try {
|
|
2701
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2702
|
+
} catch (e) {
|
|
2703
|
+
logCatchDebug("migration", e);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2684
2706
|
try {
|
|
2685
2707
|
await client.execute({
|
|
2686
2708
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3897,6 +3919,22 @@ async function ensureSchema() {
|
|
|
3897
3919
|
} catch (e) {
|
|
3898
3920
|
logCatchDebug("migration", e);
|
|
3899
3921
|
}
|
|
3922
|
+
try {
|
|
3923
|
+
await client.execute({
|
|
3924
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3925
|
+
args: []
|
|
3926
|
+
});
|
|
3927
|
+
} catch (e) {
|
|
3928
|
+
logCatchDebug("migration", e);
|
|
3929
|
+
}
|
|
3930
|
+
try {
|
|
3931
|
+
await client.execute({
|
|
3932
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3933
|
+
args: []
|
|
3934
|
+
});
|
|
3935
|
+
} catch (e) {
|
|
3936
|
+
logCatchDebug("migration", e);
|
|
3937
|
+
}
|
|
3900
3938
|
}
|
|
3901
3939
|
async function disposeDatabase() {
|
|
3902
3940
|
if (_walCheckpointTimer) {
|
|
@@ -5033,11 +5071,17 @@ var init_platform_procedures = __esm({
|
|
|
5033
5071
|
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."
|
|
5034
5072
|
},
|
|
5035
5073
|
{
|
|
5036
|
-
title: "
|
|
5074
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
5037
5075
|
domain: "workflow",
|
|
5038
5076
|
priority: "p1",
|
|
5039
5077
|
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."
|
|
5040
5078
|
},
|
|
5079
|
+
{
|
|
5080
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
5081
|
+
domain: "identity",
|
|
5082
|
+
priority: "p0",
|
|
5083
|
+
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."
|
|
5084
|
+
},
|
|
5041
5085
|
{
|
|
5042
5086
|
title: "Single dispatch path \u2014 create_task only",
|
|
5043
5087
|
domain: "workflow",
|
|
@@ -5071,6 +5115,12 @@ var init_platform_procedures = __esm({
|
|
|
5071
5115
|
priority: "p0",
|
|
5072
5116
|
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."
|
|
5073
5117
|
},
|
|
5118
|
+
{
|
|
5119
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
5120
|
+
domain: "security",
|
|
5121
|
+
priority: "p0",
|
|
5122
|
+
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."
|
|
5123
|
+
},
|
|
5074
5124
|
{
|
|
5075
5125
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
5076
5126
|
domain: "support",
|
|
@@ -5222,7 +5272,7 @@ var init_platform_procedures = __esm({
|
|
|
5222
5272
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
5223
5273
|
domain: "tool-use",
|
|
5224
5274
|
priority: "p0",
|
|
5225
|
-
content: 'exe-os MCP tools
|
|
5275
|
+
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.'
|
|
5226
5276
|
},
|
|
5227
5277
|
{
|
|
5228
5278
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -5356,10 +5406,24 @@ function stableId(memoryId, type, content) {
|
|
|
5356
5406
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
5357
5407
|
}
|
|
5358
5408
|
function cleanText(text) {
|
|
5359
|
-
|
|
5409
|
+
let cleaned = text.replace(
|
|
5410
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5411
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5412
|
+
);
|
|
5413
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5414
|
+
return cleaned;
|
|
5360
5415
|
}
|
|
5361
|
-
function
|
|
5362
|
-
|
|
5416
|
+
function splitSegments(text) {
|
|
5417
|
+
const cleaned = cleanText(text);
|
|
5418
|
+
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);
|
|
5419
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5420
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5421
|
+
if (lines.length > 0) return lines;
|
|
5422
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5423
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
return segments;
|
|
5363
5427
|
}
|
|
5364
5428
|
function inferCardType(sentence, toolName) {
|
|
5365
5429
|
const lower = sentence.toLowerCase();
|
|
@@ -5391,12 +5455,12 @@ function predicateFor(type) {
|
|
|
5391
5455
|
}
|
|
5392
5456
|
}
|
|
5393
5457
|
function extractMemoryCards(row) {
|
|
5394
|
-
const
|
|
5458
|
+
const segments = splitSegments(row.raw_text);
|
|
5395
5459
|
const cards = [];
|
|
5396
|
-
for (const sentence of
|
|
5460
|
+
for (const sentence of segments) {
|
|
5397
5461
|
const type = inferCardType(sentence, row.tool_name);
|
|
5398
5462
|
const subject = extractSubject(sentence, row.agent_id);
|
|
5399
|
-
const content = sentence.length >
|
|
5463
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
5400
5464
|
cards.push({
|
|
5401
5465
|
id: stableId(row.id, type, content),
|
|
5402
5466
|
memory_id: row.id,
|
|
@@ -5492,13 +5556,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
5492
5556
|
last_accessed: String(row.timestamp)
|
|
5493
5557
|
}));
|
|
5494
5558
|
}
|
|
5495
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5559
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
5496
5560
|
var init_memory_cards = __esm({
|
|
5497
5561
|
"src/lib/memory-cards.ts"() {
|
|
5498
5562
|
"use strict";
|
|
5499
5563
|
init_database();
|
|
5500
|
-
MAX_CARDS_PER_MEMORY =
|
|
5501
|
-
|
|
5564
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5565
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5566
|
+
MIN_SEGMENT_CHARS = 20;
|
|
5502
5567
|
}
|
|
5503
5568
|
});
|
|
5504
5569
|
|
|
@@ -8048,6 +8113,23 @@ var init_intercom_queue = __esm({
|
|
|
8048
8113
|
});
|
|
8049
8114
|
|
|
8050
8115
|
// src/lib/license.ts
|
|
8116
|
+
var license_exports = {};
|
|
8117
|
+
__export(license_exports, {
|
|
8118
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
8119
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
8120
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
8121
|
+
checkLicense: () => checkLicense,
|
|
8122
|
+
getCachedLicense: () => getCachedLicense,
|
|
8123
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
8124
|
+
loadDeviceId: () => loadDeviceId,
|
|
8125
|
+
loadLicense: () => loadLicense,
|
|
8126
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
8127
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
8128
|
+
saveLicense: () => saveLicense,
|
|
8129
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
8130
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
8131
|
+
validateLicense: () => validateLicense
|
|
8132
|
+
});
|
|
8051
8133
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
|
|
8052
8134
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
8053
8135
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -8055,7 +8137,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
8055
8137
|
import os9 from "os";
|
|
8056
8138
|
import path12 from "path";
|
|
8057
8139
|
import { jwtVerify, importSPKI } from "jose";
|
|
8058
|
-
|
|
8140
|
+
async function fetchRetry(url, init) {
|
|
8141
|
+
try {
|
|
8142
|
+
return await fetch(url, init);
|
|
8143
|
+
} catch {
|
|
8144
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
8145
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
8146
|
+
}
|
|
8147
|
+
}
|
|
8148
|
+
function loadDeviceId() {
|
|
8149
|
+
const deviceJsonPath = path12.join(EXE_AI_DIR, "device.json");
|
|
8150
|
+
try {
|
|
8151
|
+
if (existsSync12(deviceJsonPath)) {
|
|
8152
|
+
const data = JSON.parse(readFileSync9(deviceJsonPath, "utf8"));
|
|
8153
|
+
if (data.deviceId) return data.deviceId;
|
|
8154
|
+
}
|
|
8155
|
+
} catch {
|
|
8156
|
+
}
|
|
8157
|
+
try {
|
|
8158
|
+
if (existsSync12(DEVICE_ID_PATH)) {
|
|
8159
|
+
const id2 = readFileSync9(DEVICE_ID_PATH, "utf8").trim();
|
|
8160
|
+
if (id2) return id2;
|
|
8161
|
+
}
|
|
8162
|
+
} catch {
|
|
8163
|
+
}
|
|
8164
|
+
const id = randomUUID11();
|
|
8165
|
+
mkdirSync7(EXE_AI_DIR, { recursive: true });
|
|
8166
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
8167
|
+
return id;
|
|
8168
|
+
}
|
|
8169
|
+
function loadLicense() {
|
|
8170
|
+
try {
|
|
8171
|
+
if (!existsSync12(LICENSE_PATH)) return null;
|
|
8172
|
+
return readFileSync9(LICENSE_PATH, "utf8").trim();
|
|
8173
|
+
} catch {
|
|
8174
|
+
return null;
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
8177
|
+
function saveLicense(apiKey) {
|
|
8178
|
+
mkdirSync7(EXE_AI_DIR, { recursive: true });
|
|
8179
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
8180
|
+
}
|
|
8181
|
+
async function verifyLicenseJwt(token) {
|
|
8182
|
+
try {
|
|
8183
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
8184
|
+
const { payload } = await jwtVerify(token, key, {
|
|
8185
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
8186
|
+
});
|
|
8187
|
+
const plan = payload.plan ?? "free";
|
|
8188
|
+
const email = payload.sub ?? "";
|
|
8189
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
8190
|
+
return {
|
|
8191
|
+
valid: true,
|
|
8192
|
+
plan,
|
|
8193
|
+
email,
|
|
8194
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
8195
|
+
deviceLimit: limits.devices,
|
|
8196
|
+
employeeLimit: limits.employees,
|
|
8197
|
+
memoryLimit: limits.memories
|
|
8198
|
+
};
|
|
8199
|
+
} catch {
|
|
8200
|
+
return null;
|
|
8201
|
+
}
|
|
8202
|
+
}
|
|
8203
|
+
async function getCachedLicense() {
|
|
8204
|
+
try {
|
|
8205
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
8206
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
|
|
8207
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
8208
|
+
return await verifyLicenseJwt(raw.token);
|
|
8209
|
+
} catch {
|
|
8210
|
+
return null;
|
|
8211
|
+
}
|
|
8212
|
+
}
|
|
8213
|
+
function readCachedLicenseToken() {
|
|
8214
|
+
try {
|
|
8215
|
+
if (!existsSync12(CACHE_PATH)) return null;
|
|
8216
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
|
|
8217
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
8218
|
+
} catch {
|
|
8219
|
+
return null;
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
function getRawCachedPlan() {
|
|
8223
|
+
try {
|
|
8224
|
+
const token = readCachedLicenseToken();
|
|
8225
|
+
if (!token) return null;
|
|
8226
|
+
const parts = token.split(".");
|
|
8227
|
+
if (parts.length !== 3) return null;
|
|
8228
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
8229
|
+
const plan = payload.plan ?? "free";
|
|
8230
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
8231
|
+
process.stderr.write(
|
|
8232
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
8233
|
+
`
|
|
8234
|
+
);
|
|
8235
|
+
return {
|
|
8236
|
+
valid: true,
|
|
8237
|
+
plan,
|
|
8238
|
+
email: payload.sub ?? "",
|
|
8239
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
8240
|
+
deviceLimit: limits.devices,
|
|
8241
|
+
employeeLimit: limits.employees,
|
|
8242
|
+
memoryLimit: limits.memories
|
|
8243
|
+
};
|
|
8244
|
+
} catch {
|
|
8245
|
+
return null;
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
function cacheResponse(token) {
|
|
8249
|
+
try {
|
|
8250
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
8251
|
+
} catch {
|
|
8252
|
+
}
|
|
8253
|
+
}
|
|
8254
|
+
function loadPrismaForLicense() {
|
|
8255
|
+
if (_prismaFailed) return null;
|
|
8256
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
8257
|
+
if (!dbUrl) {
|
|
8258
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path12.join(os9.homedir(), "exe-db");
|
|
8259
|
+
if (!existsSync12(path12.join(exeDbRoot, "package.json"))) {
|
|
8260
|
+
_prismaFailed = true;
|
|
8261
|
+
return null;
|
|
8262
|
+
}
|
|
8263
|
+
}
|
|
8264
|
+
if (!_prismaPromise) {
|
|
8265
|
+
_prismaPromise = (async () => {
|
|
8266
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
8267
|
+
if (explicitPath) {
|
|
8268
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
8269
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
8270
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
8271
|
+
return new Ctor2();
|
|
8272
|
+
}
|
|
8273
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path12.join(os9.homedir(), "exe-db");
|
|
8274
|
+
const req = createRequire2(path12.join(exeDbRoot, "package.json"));
|
|
8275
|
+
const entry = req.resolve("@prisma/client");
|
|
8276
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
8277
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
8278
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
8279
|
+
return new Ctor();
|
|
8280
|
+
})().catch((err) => {
|
|
8281
|
+
_prismaFailed = true;
|
|
8282
|
+
_prismaPromise = null;
|
|
8283
|
+
throw err;
|
|
8284
|
+
});
|
|
8285
|
+
}
|
|
8286
|
+
return _prismaPromise;
|
|
8287
|
+
}
|
|
8288
|
+
async function validateViaPostgres(apiKey) {
|
|
8289
|
+
const loader = loadPrismaForLicense();
|
|
8290
|
+
if (!loader) return null;
|
|
8291
|
+
try {
|
|
8292
|
+
const prisma = await loader;
|
|
8293
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
8294
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
8295
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
8296
|
+
apiKey
|
|
8297
|
+
);
|
|
8298
|
+
if (!rows || rows.length === 0) return null;
|
|
8299
|
+
const row = rows[0];
|
|
8300
|
+
if (row.status !== "active") return null;
|
|
8301
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
8302
|
+
const plan = row.plan;
|
|
8303
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
8304
|
+
return {
|
|
8305
|
+
valid: true,
|
|
8306
|
+
plan,
|
|
8307
|
+
email: row.email,
|
|
8308
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
8309
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
8310
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
8311
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
8312
|
+
};
|
|
8313
|
+
} catch {
|
|
8314
|
+
return null;
|
|
8315
|
+
}
|
|
8316
|
+
}
|
|
8317
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
8318
|
+
try {
|
|
8319
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
8320
|
+
method: "POST",
|
|
8321
|
+
headers: { "Content-Type": "application/json" },
|
|
8322
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
8323
|
+
signal: AbortSignal.timeout(1e4)
|
|
8324
|
+
});
|
|
8325
|
+
if (!res.ok) return null;
|
|
8326
|
+
const data = await res.json();
|
|
8327
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
8328
|
+
if (!data.valid) return null;
|
|
8329
|
+
if (data.token) {
|
|
8330
|
+
cacheResponse(data.token);
|
|
8331
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
8332
|
+
if (verified) return verified;
|
|
8333
|
+
}
|
|
8334
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
8335
|
+
return {
|
|
8336
|
+
valid: data.valid,
|
|
8337
|
+
plan: data.plan,
|
|
8338
|
+
email: data.email,
|
|
8339
|
+
expiresAt: data.expiresAt,
|
|
8340
|
+
deviceLimit: limits.devices,
|
|
8341
|
+
employeeLimit: limits.employees,
|
|
8342
|
+
memoryLimit: limits.memories
|
|
8343
|
+
};
|
|
8344
|
+
} catch {
|
|
8345
|
+
return null;
|
|
8346
|
+
}
|
|
8347
|
+
}
|
|
8348
|
+
async function validateLicense(apiKey, deviceId) {
|
|
8349
|
+
const did = deviceId ?? loadDeviceId();
|
|
8350
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
8351
|
+
if (pgResult) {
|
|
8352
|
+
try {
|
|
8353
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
8354
|
+
} catch {
|
|
8355
|
+
}
|
|
8356
|
+
return pgResult;
|
|
8357
|
+
}
|
|
8358
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
8359
|
+
if (cfResult) return cfResult;
|
|
8360
|
+
const cached = await getCachedLicense();
|
|
8361
|
+
if (cached) return cached;
|
|
8362
|
+
try {
|
|
8363
|
+
if (existsSync12(CACHE_PATH)) {
|
|
8364
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
|
|
8365
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
8366
|
+
return raw.pgLicense;
|
|
8367
|
+
}
|
|
8368
|
+
}
|
|
8369
|
+
} catch {
|
|
8370
|
+
}
|
|
8371
|
+
const rawFallback = getRawCachedPlan();
|
|
8372
|
+
if (rawFallback) return rawFallback;
|
|
8373
|
+
return { ...FREE_LICENSE, valid: false };
|
|
8374
|
+
}
|
|
8375
|
+
function getCacheAgeMs() {
|
|
8376
|
+
try {
|
|
8377
|
+
const { statSync: statSync5 } = __require("fs");
|
|
8378
|
+
const s = statSync5(CACHE_PATH);
|
|
8379
|
+
return Date.now() - s.mtimeMs;
|
|
8380
|
+
} catch {
|
|
8381
|
+
return Infinity;
|
|
8382
|
+
}
|
|
8383
|
+
}
|
|
8384
|
+
async function checkLicense() {
|
|
8385
|
+
let key = loadLicense();
|
|
8386
|
+
if (!key) {
|
|
8387
|
+
try {
|
|
8388
|
+
const configPath = path12.join(EXE_AI_DIR, "config.json");
|
|
8389
|
+
if (existsSync12(configPath)) {
|
|
8390
|
+
const raw = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
8391
|
+
const cloud = raw.cloud;
|
|
8392
|
+
if (cloud?.apiKey) {
|
|
8393
|
+
key = cloud.apiKey;
|
|
8394
|
+
saveLicense(key);
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
8397
|
+
} catch {
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
8400
|
+
if (!key) return FREE_LICENSE;
|
|
8401
|
+
const cached = await getCachedLicense();
|
|
8402
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
8403
|
+
const deviceId = loadDeviceId();
|
|
8404
|
+
return validateLicense(key, deviceId);
|
|
8405
|
+
}
|
|
8406
|
+
function isFeatureAllowed(license, feature) {
|
|
8407
|
+
switch (feature) {
|
|
8408
|
+
case "cloud_sync":
|
|
8409
|
+
case "external_agents":
|
|
8410
|
+
case "wiki":
|
|
8411
|
+
return license.plan !== "free";
|
|
8412
|
+
case "unlimited_employees":
|
|
8413
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
8414
|
+
}
|
|
8415
|
+
}
|
|
8416
|
+
function mirrorLicenseKey(apiKey) {
|
|
8417
|
+
const trimmed = apiKey.trim();
|
|
8418
|
+
if (!trimmed) return;
|
|
8419
|
+
saveLicense(trimmed);
|
|
8420
|
+
}
|
|
8421
|
+
async function assertVpsLicense(opts) {
|
|
8422
|
+
const env = opts?.env ?? process.env;
|
|
8423
|
+
const inProduction = env.NODE_ENV === "production";
|
|
8424
|
+
if (!opts?.force && !inProduction) {
|
|
8425
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
8426
|
+
}
|
|
8427
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
8428
|
+
if (envKey) {
|
|
8429
|
+
saveLicense(envKey);
|
|
8430
|
+
}
|
|
8431
|
+
const apiKey = envKey ?? loadLicense();
|
|
8432
|
+
if (!apiKey) {
|
|
8433
|
+
throw new Error(
|
|
8434
|
+
"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."
|
|
8435
|
+
);
|
|
8436
|
+
}
|
|
8437
|
+
const deviceId = loadDeviceId();
|
|
8438
|
+
let backendResponse = null;
|
|
8439
|
+
let explicitRejection = false;
|
|
8440
|
+
let transientFailure = false;
|
|
8441
|
+
try {
|
|
8442
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
8443
|
+
method: "POST",
|
|
8444
|
+
headers: { "Content-Type": "application/json" },
|
|
8445
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
8446
|
+
signal: AbortSignal.timeout(1e4)
|
|
8447
|
+
});
|
|
8448
|
+
if (res.ok) {
|
|
8449
|
+
backendResponse = await res.json();
|
|
8450
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
8451
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
8452
|
+
explicitRejection = true;
|
|
8453
|
+
} else {
|
|
8454
|
+
transientFailure = true;
|
|
8455
|
+
}
|
|
8456
|
+
} catch {
|
|
8457
|
+
transientFailure = true;
|
|
8458
|
+
}
|
|
8459
|
+
if (backendResponse?.valid) {
|
|
8460
|
+
if (backendResponse.token) {
|
|
8461
|
+
cacheResponse(backendResponse.token);
|
|
8462
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
8463
|
+
if (verified) return verified;
|
|
8464
|
+
}
|
|
8465
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
8466
|
+
return {
|
|
8467
|
+
valid: true,
|
|
8468
|
+
plan: backendResponse.plan,
|
|
8469
|
+
email: backendResponse.email,
|
|
8470
|
+
expiresAt: backendResponse.expiresAt,
|
|
8471
|
+
deviceLimit: limits.devices,
|
|
8472
|
+
employeeLimit: limits.employees,
|
|
8473
|
+
memoryLimit: limits.memories
|
|
8474
|
+
};
|
|
8475
|
+
}
|
|
8476
|
+
if (explicitRejection) {
|
|
8477
|
+
throw new Error(
|
|
8478
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
8479
|
+
);
|
|
8480
|
+
}
|
|
8481
|
+
if (!transientFailure) {
|
|
8482
|
+
throw new Error(
|
|
8483
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
8484
|
+
);
|
|
8485
|
+
}
|
|
8486
|
+
const fresh = await getCachedLicense();
|
|
8487
|
+
if (fresh && fresh.valid) return fresh;
|
|
8488
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
8489
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
8490
|
+
try {
|
|
8491
|
+
const token = readCachedLicenseToken();
|
|
8492
|
+
if (token) {
|
|
8493
|
+
const payloadB64 = token.split(".")[1];
|
|
8494
|
+
if (payloadB64) {
|
|
8495
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
8496
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
8497
|
+
if (Date.now() < expMs + graceMs) {
|
|
8498
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
8499
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
8500
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
8501
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
8502
|
+
});
|
|
8503
|
+
const plan = verified.plan ?? "free";
|
|
8504
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
8505
|
+
return {
|
|
8506
|
+
valid: true,
|
|
8507
|
+
plan,
|
|
8508
|
+
email: verified.sub ?? "",
|
|
8509
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
8510
|
+
deviceLimit: limits.devices,
|
|
8511
|
+
employeeLimit: limits.employees,
|
|
8512
|
+
memoryLimit: limits.memories
|
|
8513
|
+
};
|
|
8514
|
+
}
|
|
8515
|
+
}
|
|
8516
|
+
}
|
|
8517
|
+
} catch {
|
|
8518
|
+
}
|
|
8519
|
+
throw new Error(
|
|
8520
|
+
`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.`
|
|
8521
|
+
);
|
|
8522
|
+
}
|
|
8523
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
8524
|
+
if (_revalTimer) return;
|
|
8525
|
+
_revalTimer = setInterval(async () => {
|
|
8526
|
+
try {
|
|
8527
|
+
const license = await checkLicense();
|
|
8528
|
+
if (!license.valid) {
|
|
8529
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
8530
|
+
}
|
|
8531
|
+
} catch {
|
|
8532
|
+
}
|
|
8533
|
+
}, intervalMs);
|
|
8534
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
8535
|
+
_revalTimer.unref();
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
function stopLicenseRevalidation() {
|
|
8539
|
+
if (_revalTimer) {
|
|
8540
|
+
clearInterval(_revalTimer);
|
|
8541
|
+
_revalTimer = null;
|
|
8542
|
+
}
|
|
8543
|
+
}
|
|
8544
|
+
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;
|
|
8059
8545
|
var init_license = __esm({
|
|
8060
8546
|
"src/lib/license.ts"() {
|
|
8061
8547
|
"use strict";
|
|
@@ -8063,7 +8549,13 @@ var init_license = __esm({
|
|
|
8063
8549
|
LICENSE_PATH = path12.join(EXE_AI_DIR, "license.key");
|
|
8064
8550
|
CACHE_PATH = path12.join(EXE_AI_DIR, "license-cache.json");
|
|
8065
8551
|
DEVICE_ID_PATH = path12.join(EXE_AI_DIR, "device-id");
|
|
8066
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
8552
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
8553
|
+
RETRY_DELAY_MS = 500;
|
|
8554
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
8555
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
8556
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
8557
|
+
-----END PUBLIC KEY-----`;
|
|
8558
|
+
LICENSE_JWT_ALG = "ES256";
|
|
8067
8559
|
PLAN_LIMITS = {
|
|
8068
8560
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
8069
8561
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -8071,6 +8563,19 @@ var init_license = __esm({
|
|
|
8071
8563
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
8072
8564
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
8073
8565
|
};
|
|
8566
|
+
FREE_LICENSE = {
|
|
8567
|
+
valid: true,
|
|
8568
|
+
plan: "free",
|
|
8569
|
+
email: "",
|
|
8570
|
+
expiresAt: null,
|
|
8571
|
+
deviceLimit: 1,
|
|
8572
|
+
employeeLimit: 1,
|
|
8573
|
+
memoryLimit: 5e3
|
|
8574
|
+
};
|
|
8575
|
+
_prismaPromise = null;
|
|
8576
|
+
_prismaFailed = false;
|
|
8577
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
8578
|
+
_revalTimer = null;
|
|
8074
8579
|
}
|
|
8075
8580
|
});
|
|
8076
8581
|
|
|
@@ -8527,6 +9032,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
8527
9032
|
args: [identifier, ...scope.args]
|
|
8528
9033
|
});
|
|
8529
9034
|
if (result.rows.length === 1) return result.rows[0];
|
|
9035
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
9036
|
+
result = await client.execute({
|
|
9037
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
9038
|
+
args: [`${identifier}%`]
|
|
9039
|
+
});
|
|
9040
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
9041
|
+
if (result.rows.length > 1) {
|
|
9042
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
9043
|
+
throw new Error(
|
|
9044
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
9045
|
+
);
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
8530
9048
|
result = await client.execute({
|
|
8531
9049
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
8532
9050
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -9381,12 +9899,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
9381
9899
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
9382
9900
|
args: [now, taskId]
|
|
9383
9901
|
});
|
|
9384
|
-
if (
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9902
|
+
if (unblocked.rowsAffected === 0) return;
|
|
9903
|
+
const ubScope = sessionScopeFilter();
|
|
9904
|
+
const unblockedRows = await client.execute({
|
|
9905
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
9906
|
+
args: [now, ...ubScope.args]
|
|
9907
|
+
});
|
|
9908
|
+
if (baseDir) {
|
|
9390
9909
|
for (const ur of unblockedRows.rows) {
|
|
9391
9910
|
try {
|
|
9392
9911
|
const ubFile = path19.join(baseDir, String(ur.task_file));
|
|
@@ -9398,6 +9917,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
9398
9917
|
}
|
|
9399
9918
|
}
|
|
9400
9919
|
}
|
|
9920
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
9921
|
+
try {
|
|
9922
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
9923
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
9924
|
+
for (const ur of unblockedRows.rows) {
|
|
9925
|
+
const assignee = String(ur.assigned_to);
|
|
9926
|
+
if (dispatched.has(assignee)) continue;
|
|
9927
|
+
dispatched.add(assignee);
|
|
9928
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
9929
|
+
}
|
|
9930
|
+
} catch {
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9401
9933
|
}
|
|
9402
9934
|
async function findNextTask(assignedTo) {
|
|
9403
9935
|
const client = getClient();
|
|
@@ -9539,6 +10071,15 @@ var init_tasks_notify = __esm({
|
|
|
9539
10071
|
// src/lib/behaviors.ts
|
|
9540
10072
|
import crypto7 from "crypto";
|
|
9541
10073
|
async function storeBehavior(opts) {
|
|
10074
|
+
try {
|
|
10075
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
10076
|
+
const roster = loadEmployeesSync2();
|
|
10077
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
10078
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
10079
|
+
}
|
|
10080
|
+
} catch (e) {
|
|
10081
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
10082
|
+
}
|
|
9542
10083
|
const client = getClient();
|
|
9543
10084
|
const id = crypto7.randomUUID();
|
|
9544
10085
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9549,10 +10090,18 @@ async function storeBehavior(opts) {
|
|
|
9549
10090
|
vector = new Float32Array(vec);
|
|
9550
10091
|
} catch {
|
|
9551
10092
|
}
|
|
10093
|
+
let createdByDevice = null;
|
|
10094
|
+
try {
|
|
10095
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
10096
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
10097
|
+
} catch {
|
|
10098
|
+
}
|
|
10099
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
10100
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
9552
10101
|
await client.execute({
|
|
9553
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
9554
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
9555
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
10102
|
+
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)
|
|
10103
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
10104
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
9556
10105
|
});
|
|
9557
10106
|
return id;
|
|
9558
10107
|
}
|
|
@@ -9984,6 +10533,12 @@ async function updateTask(input) {
|
|
|
9984
10533
|
}
|
|
9985
10534
|
}
|
|
9986
10535
|
}
|
|
10536
|
+
if (input.status === "cancelled") {
|
|
10537
|
+
try {
|
|
10538
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
10539
|
+
} catch {
|
|
10540
|
+
}
|
|
10541
|
+
}
|
|
9987
10542
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
9988
10543
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
9989
10544
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -10515,11 +11070,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
10515
11070
|
}
|
|
10516
11071
|
}
|
|
10517
11072
|
function resolveExeSession() {
|
|
11073
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
11074
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
11075
|
+
if (fromEnv) return fromEnv;
|
|
11076
|
+
}
|
|
10518
11077
|
const mySession = getMySession();
|
|
10519
11078
|
if (!mySession) {
|
|
10520
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
10521
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
10522
|
-
}
|
|
10523
11079
|
return null;
|
|
10524
11080
|
}
|
|
10525
11081
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -10534,6 +11090,10 @@ function resolveExeSession() {
|
|
|
10534
11090
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
10535
11091
|
`
|
|
10536
11092
|
);
|
|
11093
|
+
try {
|
|
11094
|
+
registerParentExe(key, fromSessionName);
|
|
11095
|
+
} catch {
|
|
11096
|
+
}
|
|
10537
11097
|
candidate = fromSessionName;
|
|
10538
11098
|
} else {
|
|
10539
11099
|
candidate = fromCache;
|