@askexenow/exe-os 0.8.80 → 0.8.82
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/dist/bin/backfill-conversations.js +359 -267
- package/dist/bin/backfill-responses.js +357 -265
- package/dist/bin/backfill-vectors.js +339 -264
- package/dist/bin/cleanup-stale-review-tasks.js +315 -256
- package/dist/bin/cli.js +494 -240
- package/dist/bin/exe-agent.js +141 -46
- package/dist/bin/exe-assign.js +151 -63
- package/dist/bin/exe-boot.js +294 -115
- package/dist/bin/exe-call.js +76 -51
- package/dist/bin/exe-cloud.js +58 -45
- package/dist/bin/exe-dispatch.js +434 -277
- package/dist/bin/exe-doctor.js +317 -246
- package/dist/bin/exe-export-behaviors.js +328 -248
- package/dist/bin/exe-forget.js +314 -231
- package/dist/bin/exe-gateway.js +2676 -1402
- package/dist/bin/exe-heartbeat.js +329 -264
- package/dist/bin/exe-kill.js +324 -244
- package/dist/bin/exe-launch-agent.js +574 -463
- package/dist/bin/exe-link.js +1055 -95
- package/dist/bin/exe-new-employee.js +49 -54
- package/dist/bin/exe-pending-messages.js +310 -253
- package/dist/bin/exe-pending-notifications.js +299 -228
- package/dist/bin/exe-pending-reviews.js +314 -245
- package/dist/bin/exe-rename.js +259 -195
- package/dist/bin/exe-review.js +140 -64
- package/dist/bin/exe-search.js +543 -356
- package/dist/bin/exe-session-cleanup.js +463 -382
- package/dist/bin/exe-settings.js +129 -99
- package/dist/bin/exe-start.sh +6 -6
- package/dist/bin/exe-status.js +95 -36
- package/dist/bin/exe-team.js +116 -51
- package/dist/bin/git-sweep.js +482 -307
- package/dist/bin/graph-backfill.js +357 -245
- package/dist/bin/graph-export.js +324 -244
- package/dist/bin/install.js +33 -10
- package/dist/bin/scan-tasks.js +481 -307
- package/dist/bin/setup.js +1147 -140
- package/dist/bin/shard-migrate.js +321 -241
- package/dist/bin/update.js +1 -7
- package/dist/bin/wiki-sync.js +318 -238
- package/dist/gateway/index.js +2656 -1383
- package/dist/hooks/bug-report-worker.js +641 -472
- package/dist/hooks/commit-complete.js +482 -307
- package/dist/hooks/error-recall.js +363 -135
- package/dist/hooks/exe-heartbeat-hook.js +97 -27
- package/dist/hooks/ingest-worker.js +584 -397
- package/dist/hooks/ingest.js +123 -58
- package/dist/hooks/instructions-loaded.js +212 -82
- package/dist/hooks/notification.js +200 -70
- package/dist/hooks/post-compact.js +199 -81
- package/dist/hooks/pre-compact.js +352 -140
- package/dist/hooks/pre-tool-use.js +416 -278
- package/dist/hooks/prompt-ingest-worker.js +376 -299
- package/dist/hooks/prompt-submit.js +414 -188
- package/dist/hooks/response-ingest-worker.js +408 -338
- package/dist/hooks/session-end.js +209 -83
- package/dist/hooks/session-start.js +382 -158
- package/dist/hooks/stop.js +209 -83
- package/dist/hooks/subagent-stop.js +209 -85
- package/dist/hooks/summary-worker.js +606 -510
- package/dist/index.js +2133 -855
- package/dist/lib/cloud-sync.js +1175 -184
- package/dist/lib/config.js +1 -9
- package/dist/lib/consolidation.js +71 -34
- package/dist/lib/database.js +166 -14
- package/dist/lib/device-registry.js +189 -117
- package/dist/lib/embedder.js +6 -10
- package/dist/lib/employee-templates.js +134 -39
- package/dist/lib/employees.js +30 -7
- package/dist/lib/exe-daemon-client.js +5 -7
- package/dist/lib/exe-daemon.js +514 -152
- package/dist/lib/hybrid-search.js +543 -356
- package/dist/lib/identity-templates.js +15 -15
- package/dist/lib/identity.js +19 -15
- package/dist/lib/license.js +1 -7
- package/dist/lib/messaging.js +157 -135
- package/dist/lib/reminders.js +97 -0
- package/dist/lib/schedules.js +302 -231
- package/dist/lib/skill-learning.js +33 -27
- package/dist/lib/status-brief.js +11 -14
- package/dist/lib/store.js +326 -237
- package/dist/lib/task-router.js +105 -1
- package/dist/lib/tasks.js +233 -116
- package/dist/lib/tmux-routing.js +173 -56
- package/dist/lib/ws-client.js +13 -3
- package/dist/mcp/server.js +2009 -1015
- package/dist/mcp/tools/complete-reminder.js +97 -0
- package/dist/mcp/tools/create-reminder.js +97 -0
- package/dist/mcp/tools/create-task.js +426 -262
- package/dist/mcp/tools/deactivate-behavior.js +119 -44
- package/dist/mcp/tools/list-reminders.js +97 -0
- package/dist/mcp/tools/list-tasks.js +56 -57
- package/dist/mcp/tools/send-message.js +206 -143
- package/dist/mcp/tools/update-task.js +259 -85
- package/dist/runtime/index.js +495 -316
- package/dist/tui/App.js +1128 -919
- package/package.json +2 -10
- package/src/commands/exe/afk.md +8 -8
- package/src/commands/exe/assign.md +1 -1
- package/src/commands/exe/build-adv.md +1 -1
- package/src/commands/exe/call.md +10 -10
- package/src/commands/exe/employee-heartbeat.md +9 -6
- package/src/commands/exe/heartbeat.md +5 -5
- package/src/commands/exe/intercom.md +26 -15
- package/src/commands/exe/launch.md +2 -2
- package/src/commands/exe/new-employee.md +1 -1
- package/src/commands/exe/review.md +2 -2
- package/src/commands/exe/schedule.md +1 -1
- package/src/commands/exe/sessions.md +2 -2
- package/src/commands/exe.md +22 -20
package/dist/index.js
CHANGED
|
@@ -25,6 +25,444 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// src/lib/config.ts
|
|
29
|
+
var config_exports = {};
|
|
30
|
+
__export(config_exports, {
|
|
31
|
+
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
32
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
33
|
+
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
34
|
+
DB_PATH: () => DB_PATH,
|
|
35
|
+
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
36
|
+
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
37
|
+
MODELS_DIR: () => MODELS_DIR,
|
|
38
|
+
loadConfig: () => loadConfig,
|
|
39
|
+
loadConfigFrom: () => loadConfigFrom,
|
|
40
|
+
loadConfigSync: () => loadConfigSync,
|
|
41
|
+
migrateConfig: () => migrateConfig,
|
|
42
|
+
saveConfig: () => saveConfig
|
|
43
|
+
});
|
|
44
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
45
|
+
import { readFileSync, existsSync, renameSync } from "fs";
|
|
46
|
+
import path2 from "path";
|
|
47
|
+
import os2 from "os";
|
|
48
|
+
function resolveDataDir() {
|
|
49
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
50
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
51
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
52
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
53
|
+
if (!existsSync(newDir) && existsSync(legacyDir)) {
|
|
54
|
+
try {
|
|
55
|
+
renameSync(legacyDir, newDir);
|
|
56
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
57
|
+
`);
|
|
58
|
+
} catch {
|
|
59
|
+
return legacyDir;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return newDir;
|
|
63
|
+
}
|
|
64
|
+
function migrateLegacyConfig(raw) {
|
|
65
|
+
if ("r2" in raw) {
|
|
66
|
+
process.stderr.write(
|
|
67
|
+
"[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
|
|
68
|
+
);
|
|
69
|
+
delete raw.r2;
|
|
70
|
+
}
|
|
71
|
+
if ("syncIntervalMs" in raw) {
|
|
72
|
+
delete raw.syncIntervalMs;
|
|
73
|
+
}
|
|
74
|
+
return raw;
|
|
75
|
+
}
|
|
76
|
+
function migrateConfig(raw) {
|
|
77
|
+
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
78
|
+
let currentVersion = fromVersion;
|
|
79
|
+
let migrated = false;
|
|
80
|
+
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
81
|
+
return { config: raw, migrated: false, fromVersion };
|
|
82
|
+
}
|
|
83
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
84
|
+
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
85
|
+
raw = migration.migrate(raw);
|
|
86
|
+
currentVersion = migration.to;
|
|
87
|
+
migrated = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { config: raw, migrated, fromVersion };
|
|
91
|
+
}
|
|
92
|
+
function normalizeScalingRoadmap(raw) {
|
|
93
|
+
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
94
|
+
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
95
|
+
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
96
|
+
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
97
|
+
userAuto.enabled = raw.rerankerEnabled;
|
|
98
|
+
}
|
|
99
|
+
raw.scalingRoadmap = {
|
|
100
|
+
...userRoadmap,
|
|
101
|
+
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function normalizeSessionLifecycle(raw) {
|
|
105
|
+
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
106
|
+
const userSL = raw.sessionLifecycle ?? {};
|
|
107
|
+
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
108
|
+
}
|
|
109
|
+
function normalizeAutoUpdate(raw) {
|
|
110
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
111
|
+
const userAU = raw.autoUpdate ?? {};
|
|
112
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
113
|
+
}
|
|
114
|
+
async function loadConfig() {
|
|
115
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
116
|
+
await mkdir(dir, { recursive: true });
|
|
117
|
+
const configPath = path2.join(dir, "config.json");
|
|
118
|
+
if (!existsSync(configPath)) {
|
|
119
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
120
|
+
}
|
|
121
|
+
const raw = await readFile(configPath, "utf-8");
|
|
122
|
+
try {
|
|
123
|
+
let parsed = JSON.parse(raw);
|
|
124
|
+
parsed = migrateLegacyConfig(parsed);
|
|
125
|
+
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
126
|
+
if (migrated) {
|
|
127
|
+
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
128
|
+
`);
|
|
129
|
+
try {
|
|
130
|
+
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
135
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
136
|
+
normalizeAutoUpdate(migratedCfg);
|
|
137
|
+
const config2 = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
138
|
+
if (config2.dbPath.startsWith("~")) {
|
|
139
|
+
config2.dbPath = config2.dbPath.replace(/^~/, os2.homedir());
|
|
140
|
+
}
|
|
141
|
+
return config2;
|
|
142
|
+
} catch {
|
|
143
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function loadConfigSync() {
|
|
147
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
148
|
+
const configPath = path2.join(dir, "config.json");
|
|
149
|
+
if (!existsSync(configPath)) {
|
|
150
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
154
|
+
let parsed = JSON.parse(raw);
|
|
155
|
+
parsed = migrateLegacyConfig(parsed);
|
|
156
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
157
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
158
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
159
|
+
normalizeAutoUpdate(migratedCfg);
|
|
160
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
161
|
+
} catch {
|
|
162
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function saveConfig(config2) {
|
|
166
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
167
|
+
await mkdir(dir, { recursive: true });
|
|
168
|
+
const configPath = path2.join(dir, "config.json");
|
|
169
|
+
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
170
|
+
if (config2.cloud?.apiKey) {
|
|
171
|
+
await chmod(configPath, 384);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function loadConfigFrom(configPath) {
|
|
175
|
+
const raw = await readFile(configPath, "utf-8");
|
|
176
|
+
try {
|
|
177
|
+
let parsed = JSON.parse(raw);
|
|
178
|
+
parsed = migrateLegacyConfig(parsed);
|
|
179
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
180
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
181
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
182
|
+
normalizeAutoUpdate(migratedCfg);
|
|
183
|
+
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
184
|
+
} catch {
|
|
185
|
+
return { ...DEFAULT_CONFIG };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
189
|
+
var init_config = __esm({
|
|
190
|
+
"src/lib/config.ts"() {
|
|
191
|
+
"use strict";
|
|
192
|
+
EXE_AI_DIR = resolveDataDir();
|
|
193
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
194
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
195
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
196
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
197
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
198
|
+
DEFAULT_CONFIG = {
|
|
199
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
200
|
+
dbPath: DB_PATH,
|
|
201
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
202
|
+
embeddingDim: 1024,
|
|
203
|
+
batchSize: 20,
|
|
204
|
+
flushIntervalMs: 1e4,
|
|
205
|
+
autoIngestion: true,
|
|
206
|
+
autoRetrieval: true,
|
|
207
|
+
searchMode: "hybrid",
|
|
208
|
+
hookSearchMode: "hybrid",
|
|
209
|
+
fileGrepEnabled: true,
|
|
210
|
+
splashEffect: true,
|
|
211
|
+
consolidationEnabled: true,
|
|
212
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
213
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
214
|
+
consolidationMaxCallsPerRun: 20,
|
|
215
|
+
selfQueryRouter: true,
|
|
216
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
217
|
+
rerankerEnabled: true,
|
|
218
|
+
scalingRoadmap: {
|
|
219
|
+
rerankerAutoTrigger: {
|
|
220
|
+
enabled: true,
|
|
221
|
+
broadQueryMinCardinality: 5e4,
|
|
222
|
+
fetchTopK: 150,
|
|
223
|
+
returnTopK: 5
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
graphRagEnabled: true,
|
|
227
|
+
wikiEnabled: false,
|
|
228
|
+
wikiUrl: "",
|
|
229
|
+
wikiApiKey: "",
|
|
230
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
231
|
+
wikiWorkspaceMapping: {},
|
|
232
|
+
wikiAutoUpdate: true,
|
|
233
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
234
|
+
wikiAutoUpdateCreateNew: true,
|
|
235
|
+
skillLearning: true,
|
|
236
|
+
skillThreshold: 3,
|
|
237
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
238
|
+
exeHeartbeat: {
|
|
239
|
+
enabled: true,
|
|
240
|
+
intervalSeconds: 60,
|
|
241
|
+
staleInProgressThresholdHours: 2
|
|
242
|
+
},
|
|
243
|
+
sessionLifecycle: {
|
|
244
|
+
idleKillEnabled: true,
|
|
245
|
+
idleKillTicksRequired: 3,
|
|
246
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
247
|
+
maxAutoInstances: 10
|
|
248
|
+
},
|
|
249
|
+
autoUpdate: {
|
|
250
|
+
checkOnBoot: true,
|
|
251
|
+
autoInstall: false,
|
|
252
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
CONFIG_MIGRATIONS = [
|
|
256
|
+
{
|
|
257
|
+
from: 0,
|
|
258
|
+
to: 1,
|
|
259
|
+
migrate: (cfg) => {
|
|
260
|
+
cfg.config_version = 1;
|
|
261
|
+
return cfg;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// src/lib/employees.ts
|
|
269
|
+
var employees_exports = {};
|
|
270
|
+
__export(employees_exports, {
|
|
271
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
272
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
273
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
274
|
+
addEmployee: () => addEmployee,
|
|
275
|
+
canCoordinate: () => canCoordinate,
|
|
276
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
277
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
278
|
+
getEmployee: () => getEmployee,
|
|
279
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
280
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
281
|
+
hasRole: () => hasRole,
|
|
282
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
283
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
284
|
+
isMultiInstance: () => isMultiInstance,
|
|
285
|
+
loadEmployees: () => loadEmployees,
|
|
286
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
287
|
+
normalizeRole: () => normalizeRole,
|
|
288
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
289
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
290
|
+
saveEmployees: () => saveEmployees,
|
|
291
|
+
validateEmployeeName: () => validateEmployeeName
|
|
292
|
+
});
|
|
293
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
294
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
295
|
+
import { execSync } from "child_process";
|
|
296
|
+
import path3 from "path";
|
|
297
|
+
import os3 from "os";
|
|
298
|
+
function normalizeRole(role) {
|
|
299
|
+
return (role ?? "").trim().toLowerCase();
|
|
300
|
+
}
|
|
301
|
+
function isCoordinatorRole(role) {
|
|
302
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
303
|
+
}
|
|
304
|
+
function getCoordinatorEmployee(employees) {
|
|
305
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
306
|
+
}
|
|
307
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
308
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
309
|
+
}
|
|
310
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
311
|
+
if (!agentName) return false;
|
|
312
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
313
|
+
}
|
|
314
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
315
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
316
|
+
}
|
|
317
|
+
function validateEmployeeName(name) {
|
|
318
|
+
if (!name) {
|
|
319
|
+
return { valid: false, error: "Name is required" };
|
|
320
|
+
}
|
|
321
|
+
if (name.length > 32) {
|
|
322
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
323
|
+
}
|
|
324
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
325
|
+
return {
|
|
326
|
+
valid: false,
|
|
327
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return { valid: true };
|
|
331
|
+
}
|
|
332
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
333
|
+
if (!existsSync2(employeesPath)) {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
337
|
+
try {
|
|
338
|
+
return JSON.parse(raw);
|
|
339
|
+
} catch {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
344
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
345
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
346
|
+
}
|
|
347
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
348
|
+
if (!existsSync2(employeesPath)) return [];
|
|
349
|
+
try {
|
|
350
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
351
|
+
} catch {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function getEmployee(employees, name) {
|
|
356
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
357
|
+
}
|
|
358
|
+
function getEmployeeByRole(employees, role) {
|
|
359
|
+
const lower = role.toLowerCase();
|
|
360
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
361
|
+
}
|
|
362
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
363
|
+
const lower = role.toLowerCase();
|
|
364
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
365
|
+
}
|
|
366
|
+
function hasRole(agentName, role) {
|
|
367
|
+
const employees = loadEmployeesSync();
|
|
368
|
+
const emp = getEmployee(employees, agentName);
|
|
369
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
370
|
+
}
|
|
371
|
+
function isMultiInstance(agentName, employees) {
|
|
372
|
+
const roster = employees ?? loadEmployeesSync();
|
|
373
|
+
const emp = getEmployee(roster, agentName);
|
|
374
|
+
if (!emp) return false;
|
|
375
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
376
|
+
}
|
|
377
|
+
function addEmployee(employees, employee) {
|
|
378
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
379
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
380
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
381
|
+
}
|
|
382
|
+
return [...employees, normalized];
|
|
383
|
+
}
|
|
384
|
+
async function normalizeRosterCase(rosterPath) {
|
|
385
|
+
const employees = await loadEmployees(rosterPath);
|
|
386
|
+
let changed = false;
|
|
387
|
+
for (const emp of employees) {
|
|
388
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
389
|
+
const oldName = emp.name;
|
|
390
|
+
emp.name = emp.name.toLowerCase();
|
|
391
|
+
changed = true;
|
|
392
|
+
try {
|
|
393
|
+
const identityDir = path3.join(os3.homedir(), ".exe-os", "identity");
|
|
394
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
395
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
396
|
+
if (existsSync2(oldPath) && !existsSync2(newPath)) {
|
|
397
|
+
renameSync2(oldPath, newPath);
|
|
398
|
+
} else if (existsSync2(oldPath) && oldPath !== newPath) {
|
|
399
|
+
const content = readFileSync2(oldPath, "utf-8");
|
|
400
|
+
writeFileSync(newPath, content, "utf-8");
|
|
401
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
402
|
+
unlinkSync(oldPath);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (changed) {
|
|
410
|
+
await saveEmployees(employees, rosterPath);
|
|
411
|
+
}
|
|
412
|
+
return changed;
|
|
413
|
+
}
|
|
414
|
+
function findExeBin() {
|
|
415
|
+
try {
|
|
416
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
417
|
+
} catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function registerBinSymlinks(name) {
|
|
422
|
+
const created = [];
|
|
423
|
+
const skipped = [];
|
|
424
|
+
const errors = [];
|
|
425
|
+
const exeBinPath = findExeBin();
|
|
426
|
+
if (!exeBinPath) {
|
|
427
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
428
|
+
return { created, skipped, errors };
|
|
429
|
+
}
|
|
430
|
+
const binDir = path3.dirname(exeBinPath);
|
|
431
|
+
let target;
|
|
432
|
+
try {
|
|
433
|
+
target = readlinkSync(exeBinPath);
|
|
434
|
+
} catch {
|
|
435
|
+
errors.push("Could not read 'exe' symlink");
|
|
436
|
+
return { created, skipped, errors };
|
|
437
|
+
}
|
|
438
|
+
for (const suffix of ["", "-opencode"]) {
|
|
439
|
+
const linkName = `${name}${suffix}`;
|
|
440
|
+
const linkPath = path3.join(binDir, linkName);
|
|
441
|
+
if (existsSync2(linkPath)) {
|
|
442
|
+
skipped.push(linkName);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
symlinkSync(target, linkPath);
|
|
447
|
+
created.push(linkName);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return { created, skipped, errors };
|
|
453
|
+
}
|
|
454
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
455
|
+
var init_employees = __esm({
|
|
456
|
+
"src/lib/employees.ts"() {
|
|
457
|
+
"use strict";
|
|
458
|
+
init_config();
|
|
459
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
460
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
461
|
+
COORDINATOR_ROLE = "COO";
|
|
462
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
28
466
|
// src/lib/session-registry.ts
|
|
29
467
|
var session_registry_exports = {};
|
|
30
468
|
__export(session_registry_exports, {
|
|
@@ -32,13 +470,13 @@ __export(session_registry_exports, {
|
|
|
32
470
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
33
471
|
registerSession: () => registerSession
|
|
34
472
|
});
|
|
35
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
36
|
-
import { execSync } from "child_process";
|
|
37
|
-
import
|
|
38
|
-
import
|
|
473
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync3 } from "fs";
|
|
474
|
+
import { execSync as execSync2 } from "child_process";
|
|
475
|
+
import path4 from "path";
|
|
476
|
+
import os4 from "os";
|
|
39
477
|
function registerSession(entry) {
|
|
40
|
-
const dir =
|
|
41
|
-
if (!
|
|
478
|
+
const dir = path4.dirname(REGISTRY_PATH);
|
|
479
|
+
if (!existsSync3(dir)) {
|
|
42
480
|
mkdirSync(dir, { recursive: true });
|
|
43
481
|
}
|
|
44
482
|
const sessions = listSessions();
|
|
@@ -48,11 +486,11 @@ function registerSession(entry) {
|
|
|
48
486
|
} else {
|
|
49
487
|
sessions.push(entry);
|
|
50
488
|
}
|
|
51
|
-
|
|
489
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
52
490
|
}
|
|
53
491
|
function listSessions() {
|
|
54
492
|
try {
|
|
55
|
-
const raw =
|
|
493
|
+
const raw = readFileSync3(REGISTRY_PATH, "utf8");
|
|
56
494
|
return JSON.parse(raw);
|
|
57
495
|
} catch {
|
|
58
496
|
return [];
|
|
@@ -63,7 +501,7 @@ function pruneStaleSessions() {
|
|
|
63
501
|
if (sessions.length === 0) return 0;
|
|
64
502
|
let liveSessions = [];
|
|
65
503
|
try {
|
|
66
|
-
liveSessions =
|
|
504
|
+
liveSessions = execSync2("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
|
|
67
505
|
encoding: "utf8"
|
|
68
506
|
}).trim().split("\n").filter(Boolean);
|
|
69
507
|
} catch {
|
|
@@ -73,7 +511,7 @@ function pruneStaleSessions() {
|
|
|
73
511
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
74
512
|
const pruned = sessions.length - alive.length;
|
|
75
513
|
if (pruned > 0) {
|
|
76
|
-
|
|
514
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
77
515
|
}
|
|
78
516
|
return pruned;
|
|
79
517
|
}
|
|
@@ -81,18 +519,18 @@ var REGISTRY_PATH;
|
|
|
81
519
|
var init_session_registry = __esm({
|
|
82
520
|
"src/lib/session-registry.ts"() {
|
|
83
521
|
"use strict";
|
|
84
|
-
REGISTRY_PATH =
|
|
522
|
+
REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
85
523
|
}
|
|
86
524
|
});
|
|
87
525
|
|
|
88
526
|
// src/lib/session-key.ts
|
|
89
|
-
import { execSync as
|
|
527
|
+
import { execSync as execSync3 } from "child_process";
|
|
90
528
|
function getSessionKey() {
|
|
91
529
|
if (_cached) return _cached;
|
|
92
530
|
let pid = process.ppid;
|
|
93
531
|
for (let i = 0; i < 10; i++) {
|
|
94
532
|
try {
|
|
95
|
-
const info =
|
|
533
|
+
const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
|
|
96
534
|
encoding: "utf8",
|
|
97
535
|
timeout: 2e3
|
|
98
536
|
}).trim();
|
|
@@ -228,14 +666,14 @@ var init_transport = __esm({
|
|
|
228
666
|
});
|
|
229
667
|
|
|
230
668
|
// src/lib/cc-agent-support.ts
|
|
231
|
-
import { execSync as
|
|
669
|
+
import { execSync as execSync4 } from "child_process";
|
|
232
670
|
function _resetCcAgentSupportCache() {
|
|
233
671
|
_cachedSupport = null;
|
|
234
672
|
}
|
|
235
673
|
function claudeSupportsAgentFlag() {
|
|
236
674
|
if (_cachedSupport !== null) return _cachedSupport;
|
|
237
675
|
try {
|
|
238
|
-
const helpOutput =
|
|
676
|
+
const helpOutput = execSync4("claude --help 2>&1", {
|
|
239
677
|
encoding: "utf-8",
|
|
240
678
|
timeout: 5e3
|
|
241
679
|
});
|
|
@@ -311,17 +749,17 @@ var init_provider_table = __esm({
|
|
|
311
749
|
});
|
|
312
750
|
|
|
313
751
|
// src/lib/intercom-queue.ts
|
|
314
|
-
import { readFileSync as
|
|
315
|
-
import
|
|
316
|
-
import
|
|
752
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
753
|
+
import path5 from "path";
|
|
754
|
+
import os5 from "os";
|
|
317
755
|
function ensureDir() {
|
|
318
|
-
const dir =
|
|
319
|
-
if (!
|
|
756
|
+
const dir = path5.dirname(QUEUE_PATH);
|
|
757
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
320
758
|
}
|
|
321
759
|
function readQueue() {
|
|
322
760
|
try {
|
|
323
|
-
if (!
|
|
324
|
-
return JSON.parse(
|
|
761
|
+
if (!existsSync4(QUEUE_PATH)) return [];
|
|
762
|
+
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
325
763
|
} catch {
|
|
326
764
|
return [];
|
|
327
765
|
}
|
|
@@ -329,8 +767,8 @@ function readQueue() {
|
|
|
329
767
|
function writeQueue(queue) {
|
|
330
768
|
ensureDir();
|
|
331
769
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
332
|
-
|
|
333
|
-
|
|
770
|
+
writeFileSync3(tmp, JSON.stringify(queue, null, 2));
|
|
771
|
+
renameSync3(tmp, QUEUE_PATH);
|
|
334
772
|
}
|
|
335
773
|
function queueIntercom(targetSession, reason) {
|
|
336
774
|
const queue = readQueue();
|
|
@@ -353,9 +791,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
353
791
|
var init_intercom_queue = __esm({
|
|
354
792
|
"src/lib/intercom-queue.ts"() {
|
|
355
793
|
"use strict";
|
|
356
|
-
QUEUE_PATH =
|
|
794
|
+
QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
357
795
|
TTL_MS = 60 * 60 * 1e3;
|
|
358
|
-
INTERCOM_LOG =
|
|
796
|
+
INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
359
797
|
}
|
|
360
798
|
});
|
|
361
799
|
|
|
@@ -398,7 +836,7 @@ function wrapWithRetry(client) {
|
|
|
398
836
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
399
837
|
}
|
|
400
838
|
if (prop === "batch") {
|
|
401
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
839
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
402
840
|
}
|
|
403
841
|
return Reflect.get(target, prop, receiver);
|
|
404
842
|
}
|
|
@@ -561,22 +999,24 @@ async function ensureSchema() {
|
|
|
561
999
|
ON behaviors(agent_id, active);
|
|
562
1000
|
`);
|
|
563
1001
|
try {
|
|
1002
|
+
const coordinatorName = getCoordinatorName();
|
|
564
1003
|
const existing = await client.execute({
|
|
565
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
566
|
-
args: []
|
|
1004
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1005
|
+
args: [coordinatorName]
|
|
567
1006
|
});
|
|
568
1007
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
1008
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1009
|
+
for (const [domain, content] of [
|
|
1010
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1011
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1012
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1013
|
+
]) {
|
|
1014
|
+
await client.execute({
|
|
1015
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1016
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1017
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
580
1020
|
}
|
|
581
1021
|
} catch {
|
|
582
1022
|
}
|
|
@@ -1268,304 +1708,57 @@ async function ensureSchema() {
|
|
|
1268
1708
|
} catch {
|
|
1269
1709
|
}
|
|
1270
1710
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1280
|
-
var init_database = __esm({
|
|
1281
|
-
"src/lib/database.ts"() {
|
|
1282
|
-
"use strict";
|
|
1283
|
-
init_db_retry();
|
|
1284
|
-
_client = null;
|
|
1285
|
-
_resilientClient = null;
|
|
1286
|
-
initTurso = initDatabase;
|
|
1287
|
-
disposeTurso = disposeDatabase;
|
|
1288
|
-
}
|
|
1289
|
-
});
|
|
1290
|
-
|
|
1291
|
-
// src/lib/config.ts
|
|
1292
|
-
var config_exports = {};
|
|
1293
|
-
__export(config_exports, {
|
|
1294
|
-
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
1295
|
-
CONFIG_PATH: () => CONFIG_PATH,
|
|
1296
|
-
COO_AGENT_NAME: () => COO_AGENT_NAME,
|
|
1297
|
-
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
1298
|
-
DB_PATH: () => DB_PATH,
|
|
1299
|
-
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
1300
|
-
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
1301
|
-
MODELS_DIR: () => MODELS_DIR,
|
|
1302
|
-
loadConfig: () => loadConfig,
|
|
1303
|
-
loadConfigFrom: () => loadConfigFrom,
|
|
1304
|
-
loadConfigSync: () => loadConfigSync,
|
|
1305
|
-
migrateConfig: () => migrateConfig,
|
|
1306
|
-
saveConfig: () => saveConfig
|
|
1307
|
-
});
|
|
1308
|
-
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
1309
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
|
|
1310
|
-
import path4 from "path";
|
|
1311
|
-
import os4 from "os";
|
|
1312
|
-
function resolveDataDir() {
|
|
1313
|
-
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
1314
|
-
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
1315
|
-
const newDir = path4.join(os4.homedir(), ".exe-os");
|
|
1316
|
-
const legacyDir = path4.join(os4.homedir(), ".exe-mem");
|
|
1317
|
-
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
1318
|
-
try {
|
|
1319
|
-
renameSync2(legacyDir, newDir);
|
|
1320
|
-
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
1321
|
-
`);
|
|
1322
|
-
} catch {
|
|
1323
|
-
return legacyDir;
|
|
1324
|
-
}
|
|
1711
|
+
try {
|
|
1712
|
+
await client.execute({
|
|
1713
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1714
|
+
args: []
|
|
1715
|
+
});
|
|
1716
|
+
} catch {
|
|
1325
1717
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
if ("r2" in raw) {
|
|
1330
|
-
process.stderr.write(
|
|
1331
|
-
"[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
|
|
1718
|
+
try {
|
|
1719
|
+
await client.execute(
|
|
1720
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1332
1721
|
);
|
|
1333
|
-
|
|
1334
|
-
}
|
|
1335
|
-
if ("syncIntervalMs" in raw) {
|
|
1336
|
-
delete raw.syncIntervalMs;
|
|
1337
|
-
}
|
|
1338
|
-
return raw;
|
|
1339
|
-
}
|
|
1340
|
-
function migrateConfig(raw) {
|
|
1341
|
-
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
1342
|
-
let currentVersion = fromVersion;
|
|
1343
|
-
let migrated = false;
|
|
1344
|
-
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
1345
|
-
return { config: raw, migrated: false, fromVersion };
|
|
1346
|
-
}
|
|
1347
|
-
for (const migration of CONFIG_MIGRATIONS) {
|
|
1348
|
-
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
1349
|
-
raw = migration.migrate(raw);
|
|
1350
|
-
currentVersion = migration.to;
|
|
1351
|
-
migrated = true;
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
return { config: raw, migrated, fromVersion };
|
|
1355
|
-
}
|
|
1356
|
-
function normalizeScalingRoadmap(raw) {
|
|
1357
|
-
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
1358
|
-
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
1359
|
-
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
1360
|
-
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
1361
|
-
userAuto.enabled = raw.rerankerEnabled;
|
|
1362
|
-
}
|
|
1363
|
-
raw.scalingRoadmap = {
|
|
1364
|
-
...userRoadmap,
|
|
1365
|
-
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
1366
|
-
};
|
|
1367
|
-
}
|
|
1368
|
-
function normalizeSessionLifecycle(raw) {
|
|
1369
|
-
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
1370
|
-
const userSL = raw.sessionLifecycle ?? {};
|
|
1371
|
-
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1372
|
-
}
|
|
1373
|
-
function normalizeAutoUpdate(raw) {
|
|
1374
|
-
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1375
|
-
const userAU = raw.autoUpdate ?? {};
|
|
1376
|
-
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1377
|
-
}
|
|
1378
|
-
async function loadConfig() {
|
|
1379
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1380
|
-
await mkdir(dir, { recursive: true });
|
|
1381
|
-
const configPath = path4.join(dir, "config.json");
|
|
1382
|
-
if (!existsSync3(configPath)) {
|
|
1383
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1722
|
+
} catch {
|
|
1384
1723
|
}
|
|
1385
|
-
const raw = await readFile(configPath, "utf-8");
|
|
1386
1724
|
try {
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
1392
|
-
`);
|
|
1393
|
-
try {
|
|
1394
|
-
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
1395
|
-
} catch {
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1399
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1400
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1401
|
-
const config2 = { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
|
|
1402
|
-
if (config2.dbPath.startsWith("~")) {
|
|
1403
|
-
config2.dbPath = config2.dbPath.replace(/^~/, os4.homedir());
|
|
1404
|
-
}
|
|
1405
|
-
return config2;
|
|
1725
|
+
await client.execute({
|
|
1726
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1727
|
+
args: []
|
|
1728
|
+
});
|
|
1406
1729
|
} catch {
|
|
1407
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
function loadConfigSync() {
|
|
1411
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1412
|
-
const configPath = path4.join(dir, "config.json");
|
|
1413
|
-
if (!existsSync3(configPath)) {
|
|
1414
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1415
1730
|
}
|
|
1416
1731
|
try {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1421
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1422
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1423
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1424
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
|
|
1732
|
+
await client.execute(
|
|
1733
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1734
|
+
);
|
|
1425
1735
|
} catch {
|
|
1426
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
async function saveConfig(config2) {
|
|
1430
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1431
|
-
await mkdir(dir, { recursive: true });
|
|
1432
|
-
const configPath = path4.join(dir, "config.json");
|
|
1433
|
-
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
|
|
1434
|
-
if (config2.cloud?.apiKey) {
|
|
1435
|
-
await chmod(configPath, 384);
|
|
1436
1736
|
}
|
|
1437
|
-
}
|
|
1438
|
-
async function loadConfigFrom(configPath) {
|
|
1439
|
-
const raw = await readFile(configPath, "utf-8");
|
|
1440
1737
|
try {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1446
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1447
|
-
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
1738
|
+
await client.execute({
|
|
1739
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1740
|
+
args: []
|
|
1741
|
+
});
|
|
1448
1742
|
} catch {
|
|
1449
|
-
return { ...DEFAULT_CONFIG };
|
|
1450
1743
|
}
|
|
1451
1744
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
DB_PATH = path4.join(EXE_AI_DIR, "memories.db");
|
|
1458
|
-
MODELS_DIR = path4.join(EXE_AI_DIR, "models");
|
|
1459
|
-
CONFIG_PATH = path4.join(EXE_AI_DIR, "config.json");
|
|
1460
|
-
COO_AGENT_NAME = "exe";
|
|
1461
|
-
LEGACY_LANCE_PATH = path4.join(EXE_AI_DIR, "local.lance");
|
|
1462
|
-
CURRENT_CONFIG_VERSION = 1;
|
|
1463
|
-
DEFAULT_CONFIG = {
|
|
1464
|
-
config_version: CURRENT_CONFIG_VERSION,
|
|
1465
|
-
dbPath: DB_PATH,
|
|
1466
|
-
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
1467
|
-
embeddingDim: 1024,
|
|
1468
|
-
batchSize: 20,
|
|
1469
|
-
flushIntervalMs: 1e4,
|
|
1470
|
-
autoIngestion: true,
|
|
1471
|
-
autoRetrieval: true,
|
|
1472
|
-
searchMode: "hybrid",
|
|
1473
|
-
hookSearchMode: "hybrid",
|
|
1474
|
-
fileGrepEnabled: true,
|
|
1475
|
-
splashEffect: true,
|
|
1476
|
-
consolidationEnabled: true,
|
|
1477
|
-
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
1478
|
-
consolidationModel: "claude-haiku-4-5-20251001",
|
|
1479
|
-
consolidationMaxCallsPerRun: 20,
|
|
1480
|
-
selfQueryRouter: true,
|
|
1481
|
-
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
1482
|
-
rerankerEnabled: true,
|
|
1483
|
-
scalingRoadmap: {
|
|
1484
|
-
rerankerAutoTrigger: {
|
|
1485
|
-
enabled: true,
|
|
1486
|
-
broadQueryMinCardinality: 5e4,
|
|
1487
|
-
fetchTopK: 150,
|
|
1488
|
-
returnTopK: 5
|
|
1489
|
-
}
|
|
1490
|
-
},
|
|
1491
|
-
graphRagEnabled: true,
|
|
1492
|
-
wikiEnabled: false,
|
|
1493
|
-
wikiUrl: "",
|
|
1494
|
-
wikiApiKey: "",
|
|
1495
|
-
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
1496
|
-
wikiWorkspaceMapping: {
|
|
1497
|
-
exe: "Executive",
|
|
1498
|
-
yoshi: "Engineering",
|
|
1499
|
-
mari: "Marketing",
|
|
1500
|
-
tom: "Engineering",
|
|
1501
|
-
sasha: "Production"
|
|
1502
|
-
},
|
|
1503
|
-
wikiAutoUpdate: true,
|
|
1504
|
-
wikiAutoUpdateThreshold: 0.5,
|
|
1505
|
-
wikiAutoUpdateCreateNew: true,
|
|
1506
|
-
skillLearning: true,
|
|
1507
|
-
skillThreshold: 3,
|
|
1508
|
-
skillModel: "claude-haiku-4-5-20251001",
|
|
1509
|
-
exeHeartbeat: {
|
|
1510
|
-
enabled: true,
|
|
1511
|
-
intervalSeconds: 60,
|
|
1512
|
-
staleInProgressThresholdHours: 2
|
|
1513
|
-
},
|
|
1514
|
-
sessionLifecycle: {
|
|
1515
|
-
idleKillEnabled: true,
|
|
1516
|
-
idleKillTicksRequired: 3,
|
|
1517
|
-
idleKillIntercomAckWindowMs: 1e4,
|
|
1518
|
-
maxAutoInstances: 10
|
|
1519
|
-
},
|
|
1520
|
-
autoUpdate: {
|
|
1521
|
-
checkOnBoot: true,
|
|
1522
|
-
autoInstall: false,
|
|
1523
|
-
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1524
|
-
}
|
|
1525
|
-
};
|
|
1526
|
-
CONFIG_MIGRATIONS = [
|
|
1527
|
-
{
|
|
1528
|
-
from: 0,
|
|
1529
|
-
to: 1,
|
|
1530
|
-
migrate: (cfg) => {
|
|
1531
|
-
cfg.config_version = 1;
|
|
1532
|
-
return cfg;
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
];
|
|
1536
|
-
}
|
|
1537
|
-
});
|
|
1538
|
-
|
|
1539
|
-
// src/lib/employees.ts
|
|
1540
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1541
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1542
|
-
import { execSync as execSync4 } from "child_process";
|
|
1543
|
-
import path5 from "path";
|
|
1544
|
-
import os5 from "os";
|
|
1545
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1546
|
-
if (!existsSync4(employeesPath)) return [];
|
|
1547
|
-
try {
|
|
1548
|
-
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
1549
|
-
} catch {
|
|
1550
|
-
return [];
|
|
1745
|
+
async function disposeDatabase() {
|
|
1746
|
+
if (_client) {
|
|
1747
|
+
_client.close();
|
|
1748
|
+
_client = null;
|
|
1749
|
+
_resilientClient = null;
|
|
1551
1750
|
}
|
|
1552
1751
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
function isMultiInstance(agentName, employees) {
|
|
1557
|
-
const roster = employees ?? loadEmployeesSync();
|
|
1558
|
-
const emp = getEmployee(roster, agentName);
|
|
1559
|
-
if (!emp) return false;
|
|
1560
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1561
|
-
}
|
|
1562
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1563
|
-
var init_employees = __esm({
|
|
1564
|
-
"src/lib/employees.ts"() {
|
|
1752
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1753
|
+
var init_database = __esm({
|
|
1754
|
+
"src/lib/database.ts"() {
|
|
1565
1755
|
"use strict";
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1756
|
+
init_db_retry();
|
|
1757
|
+
init_employees();
|
|
1758
|
+
_client = null;
|
|
1759
|
+
_resilientClient = null;
|
|
1760
|
+
initTurso = initDatabase;
|
|
1761
|
+
disposeTurso = disposeDatabase;
|
|
1569
1762
|
}
|
|
1570
1763
|
});
|
|
1571
1764
|
|
|
@@ -2085,6 +2278,36 @@ async function listTasks(input) {
|
|
|
2085
2278
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
2086
2279
|
}));
|
|
2087
2280
|
}
|
|
2281
|
+
function isTmuxSessionAlive(identifier) {
|
|
2282
|
+
if (!identifier || identifier === "unknown") return true;
|
|
2283
|
+
try {
|
|
2284
|
+
if (identifier.startsWith("%")) {
|
|
2285
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
2286
|
+
timeout: 2e3,
|
|
2287
|
+
encoding: "utf8",
|
|
2288
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2289
|
+
});
|
|
2290
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2291
|
+
} else {
|
|
2292
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2293
|
+
timeout: 2e3,
|
|
2294
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2295
|
+
});
|
|
2296
|
+
return true;
|
|
2297
|
+
}
|
|
2298
|
+
} catch {
|
|
2299
|
+
if (identifier.startsWith("%")) return true;
|
|
2300
|
+
try {
|
|
2301
|
+
execSync5("tmux list-sessions", {
|
|
2302
|
+
timeout: 2e3,
|
|
2303
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2304
|
+
});
|
|
2305
|
+
return false;
|
|
2306
|
+
} catch {
|
|
2307
|
+
return true;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2088
2311
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
2089
2312
|
if (!taskContext) return null;
|
|
2090
2313
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -2147,13 +2370,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2147
2370
|
});
|
|
2148
2371
|
if (claim.rowsAffected === 0) {
|
|
2149
2372
|
const current = await client.execute({
|
|
2150
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
2373
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
2151
2374
|
args: [taskId]
|
|
2152
2375
|
});
|
|
2153
2376
|
const cur = current.rows[0];
|
|
2154
|
-
const
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2377
|
+
const curStatus = cur?.status ?? "unknown";
|
|
2378
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
2379
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
2380
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
2381
|
+
process.stderr.write(
|
|
2382
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
2383
|
+
`
|
|
2384
|
+
);
|
|
2385
|
+
await client.execute({
|
|
2386
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
2387
|
+
args: [now, taskId]
|
|
2388
|
+
});
|
|
2389
|
+
const retried = await client.execute({
|
|
2390
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
2391
|
+
args: [tmuxSession, now, taskId]
|
|
2392
|
+
});
|
|
2393
|
+
if (retried.rowsAffected > 0) {
|
|
2394
|
+
try {
|
|
2395
|
+
await writeCheckpoint({
|
|
2396
|
+
taskId,
|
|
2397
|
+
step: "reclaimed_dead_session",
|
|
2398
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
2399
|
+
});
|
|
2400
|
+
} catch {
|
|
2401
|
+
}
|
|
2402
|
+
return { row, taskFile, now, taskId };
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
2406
|
+
process.stderr.write(
|
|
2407
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2408
|
+
`
|
|
2409
|
+
);
|
|
2410
|
+
await client.execute({
|
|
2411
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
2412
|
+
args: [tmuxSession, now, taskId]
|
|
2413
|
+
});
|
|
2414
|
+
try {
|
|
2415
|
+
await writeCheckpoint({
|
|
2416
|
+
taskId,
|
|
2417
|
+
step: "assigner_override",
|
|
2418
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
2419
|
+
});
|
|
2420
|
+
} catch {
|
|
2421
|
+
}
|
|
2422
|
+
return { row, taskFile, now, taskId };
|
|
2423
|
+
}
|
|
2424
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
2425
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
2157
2426
|
}
|
|
2158
2427
|
try {
|
|
2159
2428
|
await writeCheckpoint({
|
|
@@ -2251,7 +2520,7 @@ var init_tasks_crud = __esm({
|
|
|
2251
2520
|
"use strict";
|
|
2252
2521
|
init_database();
|
|
2253
2522
|
init_task_scope();
|
|
2254
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
2523
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2255
2524
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2256
2525
|
}
|
|
2257
2526
|
});
|
|
@@ -2608,7 +2877,7 @@ function findSessionForProject(projectName) {
|
|
|
2608
2877
|
const sessions = listSessions();
|
|
2609
2878
|
for (const s of sessions) {
|
|
2610
2879
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2611
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
2880
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
2612
2881
|
}
|
|
2613
2882
|
return null;
|
|
2614
2883
|
}
|
|
@@ -2648,12 +2917,13 @@ var init_session_scope = __esm({
|
|
|
2648
2917
|
init_session_registry();
|
|
2649
2918
|
init_project_name();
|
|
2650
2919
|
init_tmux_routing();
|
|
2920
|
+
init_employees();
|
|
2651
2921
|
}
|
|
2652
2922
|
});
|
|
2653
2923
|
|
|
2654
2924
|
// src/lib/tasks-notify.ts
|
|
2655
2925
|
async function dispatchTaskToEmployee(input) {
|
|
2656
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
2926
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2657
2927
|
let crossProject = false;
|
|
2658
2928
|
if (input.projectName) {
|
|
2659
2929
|
try {
|
|
@@ -3158,6 +3428,24 @@ async function updateTask(input) {
|
|
|
3158
3428
|
});
|
|
3159
3429
|
} catch {
|
|
3160
3430
|
}
|
|
3431
|
+
const assignedAgent = String(row.assigned_to);
|
|
3432
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
3433
|
+
try {
|
|
3434
|
+
const draftClient = getClient();
|
|
3435
|
+
if (input.status === "done") {
|
|
3436
|
+
await draftClient.execute({
|
|
3437
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3438
|
+
args: [assignedAgent]
|
|
3439
|
+
});
|
|
3440
|
+
} else if (input.status === "cancelled") {
|
|
3441
|
+
await draftClient.execute({
|
|
3442
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
3443
|
+
args: [assignedAgent]
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
} catch {
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3161
3449
|
try {
|
|
3162
3450
|
const client = getClient();
|
|
3163
3451
|
const cascaded = await client.execute({
|
|
@@ -3176,8 +3464,8 @@ async function updateTask(input) {
|
|
|
3176
3464
|
}
|
|
3177
3465
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3178
3466
|
if (isTerminal) {
|
|
3179
|
-
const
|
|
3180
|
-
if (!
|
|
3467
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
3468
|
+
if (!isCoordinator) {
|
|
3181
3469
|
notifyTaskDone();
|
|
3182
3470
|
}
|
|
3183
3471
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -3201,7 +3489,7 @@ async function updateTask(input) {
|
|
|
3201
3489
|
}
|
|
3202
3490
|
}
|
|
3203
3491
|
}
|
|
3204
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
3492
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3205
3493
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3206
3494
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3207
3495
|
taskId,
|
|
@@ -3217,7 +3505,7 @@ async function updateTask(input) {
|
|
|
3217
3505
|
});
|
|
3218
3506
|
}
|
|
3219
3507
|
let nextTask;
|
|
3220
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
3508
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
3221
3509
|
try {
|
|
3222
3510
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3223
3511
|
} catch {
|
|
@@ -3244,12 +3532,14 @@ async function updateTask(input) {
|
|
|
3244
3532
|
async function deleteTask(taskId, baseDir) {
|
|
3245
3533
|
const client = getClient();
|
|
3246
3534
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
3247
|
-
const
|
|
3535
|
+
const coordinatorName = getCoordinatorName();
|
|
3536
|
+
const reviewer = assignedBy || coordinatorName;
|
|
3248
3537
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
3249
3538
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
3539
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
3250
3540
|
await client.execute({
|
|
3251
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
3252
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3541
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
3542
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3253
3543
|
});
|
|
3254
3544
|
await markAsReadByTaskFile(taskFile);
|
|
3255
3545
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -3261,6 +3551,7 @@ var init_tasks = __esm({
|
|
|
3261
3551
|
init_config();
|
|
3262
3552
|
init_notifications();
|
|
3263
3553
|
init_state_bus();
|
|
3554
|
+
init_employees();
|
|
3264
3555
|
init_tasks_crud();
|
|
3265
3556
|
init_tasks_review();
|
|
3266
3557
|
init_tasks_crud();
|
|
@@ -3346,7 +3637,7 @@ function _resetLastRelaunchCache() {
|
|
|
3346
3637
|
}
|
|
3347
3638
|
async function lastResumeCreatedAtMs(agentId) {
|
|
3348
3639
|
const client = getClient();
|
|
3349
|
-
const cmScope = sessionScopeFilter();
|
|
3640
|
+
const cmScope = sessionScopeFilter(null);
|
|
3350
3641
|
const result = await client.execute({
|
|
3351
3642
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
3352
3643
|
FROM tasks
|
|
@@ -3371,7 +3662,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
3371
3662
|
const client = getClient();
|
|
3372
3663
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3373
3664
|
const context = buildResumeContext(agentId, openTasks);
|
|
3374
|
-
const rdScope = sessionScopeFilter();
|
|
3665
|
+
const rdScope = sessionScopeFilter(null);
|
|
3375
3666
|
const existing = await client.execute({
|
|
3376
3667
|
sql: `SELECT id FROM tasks
|
|
3377
3668
|
WHERE assigned_to = ?
|
|
@@ -3405,7 +3696,7 @@ async function pollCapacityDead() {
|
|
|
3405
3696
|
const transport = getTransport();
|
|
3406
3697
|
const relaunched = [];
|
|
3407
3698
|
const registered = listSessions().filter(
|
|
3408
|
-
(s) => s.agentId !== "exe"
|
|
3699
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
3409
3700
|
);
|
|
3410
3701
|
if (registered.length === 0) return [];
|
|
3411
3702
|
let liveSessions;
|
|
@@ -3465,7 +3756,7 @@ async function pollCapacityDead() {
|
|
|
3465
3756
|
reason: "capacity"
|
|
3466
3757
|
});
|
|
3467
3758
|
const client = getClient();
|
|
3468
|
-
const rlScope = sessionScopeFilter();
|
|
3759
|
+
const rlScope = sessionScopeFilter(null);
|
|
3469
3760
|
const openTasks = await client.execute({
|
|
3470
3761
|
sql: `SELECT id, title, priority, task_file, status
|
|
3471
3762
|
FROM tasks
|
|
@@ -3519,6 +3810,7 @@ var init_capacity_monitor = __esm({
|
|
|
3519
3810
|
init_session_kill_telemetry();
|
|
3520
3811
|
init_tmux_routing();
|
|
3521
3812
|
init_task_scope();
|
|
3813
|
+
init_employees();
|
|
3522
3814
|
CAPACITY_PATTERNS = [
|
|
3523
3815
|
/conversation is too long/i,
|
|
3524
3816
|
/maximum context length/i,
|
|
@@ -3668,7 +3960,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
3668
3960
|
exeSession = root;
|
|
3669
3961
|
} else {
|
|
3670
3962
|
throw new Error(
|
|
3671
|
-
`Invalid
|
|
3963
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3672
3964
|
);
|
|
3673
3965
|
}
|
|
3674
3966
|
}
|
|
@@ -3688,8 +3980,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
3688
3980
|
return match?.[1] ?? null;
|
|
3689
3981
|
}
|
|
3690
3982
|
function extractRootExe(name) {
|
|
3691
|
-
|
|
3692
|
-
|
|
3983
|
+
if (!name) return null;
|
|
3984
|
+
if (!name.includes("-")) return name;
|
|
3985
|
+
const parts = name.split("-").filter(Boolean);
|
|
3986
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3693
3987
|
}
|
|
3694
3988
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3695
3989
|
if (!existsSync10(SESSION_CACHE)) {
|
|
@@ -3834,12 +4128,14 @@ function isSessionBusy(sessionName) {
|
|
|
3834
4128
|
return state === "thinking" || state === "tool";
|
|
3835
4129
|
}
|
|
3836
4130
|
function isExeSession(sessionName) {
|
|
3837
|
-
|
|
4131
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
4132
|
+
const coordinatorName = getCoordinatorName();
|
|
4133
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
3838
4134
|
}
|
|
3839
4135
|
function sendIntercom(targetSession) {
|
|
3840
4136
|
const transport = getTransport();
|
|
3841
4137
|
if (isExeSession(targetSession)) {
|
|
3842
|
-
logIntercom(`
|
|
4138
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
3843
4139
|
return "skipped_exe";
|
|
3844
4140
|
}
|
|
3845
4141
|
if (isDebounced(targetSession)) {
|
|
@@ -3891,7 +4187,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3891
4187
|
if (result === "failed") {
|
|
3892
4188
|
const rootExe = resolveExeSession();
|
|
3893
4189
|
if (rootExe && rootExe !== target) {
|
|
3894
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
4190
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
3895
4191
|
`);
|
|
3896
4192
|
const fallback = sendIntercom(rootExe);
|
|
3897
4193
|
return fallback !== "failed";
|
|
@@ -3901,8 +4197,8 @@ function notifyParentExe(sessionKey) {
|
|
|
3901
4197
|
return true;
|
|
3902
4198
|
}
|
|
3903
4199
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3904
|
-
if (employeeName === "exe") {
|
|
3905
|
-
return { status: "failed", sessionName: "", error: "
|
|
4200
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
4201
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3906
4202
|
}
|
|
3907
4203
|
try {
|
|
3908
4204
|
assertEmployeeLimitSync();
|
|
@@ -3911,8 +4207,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3911
4207
|
return { status: "failed", sessionName: "", error: err.message };
|
|
3912
4208
|
}
|
|
3913
4209
|
}
|
|
3914
|
-
if (
|
|
3915
|
-
const bare = employeeName.
|
|
4210
|
+
if (employeeName.includes("-")) {
|
|
4211
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
3916
4212
|
return {
|
|
3917
4213
|
status: "failed",
|
|
3918
4214
|
sessionName: "",
|
|
@@ -3931,7 +4227,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3931
4227
|
return {
|
|
3932
4228
|
status: "failed",
|
|
3933
4229
|
sessionName: "",
|
|
3934
|
-
error: `Invalid
|
|
4230
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3935
4231
|
};
|
|
3936
4232
|
}
|
|
3937
4233
|
}
|
|
@@ -4088,8 +4384,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4088
4384
|
const ctxContent = [
|
|
4089
4385
|
`## Session Context`,
|
|
4090
4386
|
`You are running in tmux session: ${sessionName}.`,
|
|
4091
|
-
`Your parent
|
|
4092
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
4387
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
4388
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4093
4389
|
].join("\n");
|
|
4094
4390
|
writeFileSync6(ctxFile, ctxContent);
|
|
4095
4391
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -4193,6 +4489,7 @@ var init_tmux_routing = __esm({
|
|
|
4193
4489
|
init_provider_table();
|
|
4194
4490
|
init_intercom_queue();
|
|
4195
4491
|
init_plan_limits();
|
|
4492
|
+
init_employees();
|
|
4196
4493
|
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4197
4494
|
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4198
4495
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -4386,7 +4683,11 @@ async function ensureShardSchema(client) {
|
|
|
4386
4683
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
4387
4684
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
4388
4685
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
4389
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
4686
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
4687
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
4688
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
4689
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
4690
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
4390
4691
|
]) {
|
|
4391
4692
|
try {
|
|
4392
4693
|
await client.execute(col);
|
|
@@ -4516,26 +4817,26 @@ var init_platform_procedures = __esm({
|
|
|
4516
4817
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
4517
4818
|
domain: "architecture",
|
|
4518
4819
|
priority: "p0",
|
|
4519
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
4820
|
+
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
|
|
4520
4821
|
},
|
|
4521
4822
|
{
|
|
4522
4823
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
4523
4824
|
domain: "architecture",
|
|
4524
4825
|
priority: "p0",
|
|
4525
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
4826
|
+
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
|
|
4526
4827
|
},
|
|
4527
4828
|
{
|
|
4528
|
-
title: "Sessions explained \u2014
|
|
4829
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
4529
4830
|
domain: "architecture",
|
|
4530
4831
|
priority: "p0",
|
|
4531
|
-
content: "Each
|
|
4832
|
+
content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
|
|
4532
4833
|
},
|
|
4533
4834
|
// --- Hierarchy and dispatch ---
|
|
4534
4835
|
{
|
|
4535
4836
|
title: "Chain of command \u2014 who talks to whom",
|
|
4536
4837
|
domain: "workflow",
|
|
4537
4838
|
priority: "p0",
|
|
4538
|
-
content: "Founder
|
|
4839
|
+
content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO 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."
|
|
4539
4840
|
},
|
|
4540
4841
|
{
|
|
4541
4842
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -4545,30 +4846,30 @@ var init_platform_procedures = __esm({
|
|
|
4545
4846
|
},
|
|
4546
4847
|
// --- Session isolation ---
|
|
4547
4848
|
{
|
|
4548
|
-
title: "Session scoping \u2014 stay in your
|
|
4849
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
4549
4850
|
domain: "security",
|
|
4550
4851
|
priority: "p0",
|
|
4551
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
4852
|
+
content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
|
|
4552
4853
|
},
|
|
4553
4854
|
{
|
|
4554
4855
|
title: "Session isolation \u2014 never touch another session's work",
|
|
4555
4856
|
domain: "workflow",
|
|
4556
4857
|
priority: "p0",
|
|
4557
|
-
content:
|
|
4858
|
+
content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
|
|
4558
4859
|
},
|
|
4559
4860
|
// --- Engineering: session scoping in code ---
|
|
4560
4861
|
{
|
|
4561
4862
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
4562
4863
|
domain: "architecture",
|
|
4563
4864
|
priority: "p0",
|
|
4564
|
-
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current
|
|
4865
|
+
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
|
|
4565
4866
|
},
|
|
4566
4867
|
// --- Hard constraints ---
|
|
4567
4868
|
{
|
|
4568
4869
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
4569
4870
|
domain: "security",
|
|
4570
4871
|
priority: "p0",
|
|
4571
|
-
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
|
|
4872
|
+
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."
|
|
4572
4873
|
},
|
|
4573
4874
|
// --- Operations ---
|
|
4574
4875
|
{
|
|
@@ -4808,7 +5109,10 @@ async function writeMemory(record) {
|
|
|
4808
5109
|
source_path: record.source_path ?? null,
|
|
4809
5110
|
source_type: record.source_type ?? null,
|
|
4810
5111
|
tier: record.tier ?? classifyTier(record),
|
|
4811
|
-
supersedes_id: record.supersedes_id ?? null
|
|
5112
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
5113
|
+
draft: record.draft ? 1 : 0,
|
|
5114
|
+
memory_type: record.memory_type ?? "raw",
|
|
5115
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
4812
5116
|
};
|
|
4813
5117
|
_pendingRecords.push(dbRow);
|
|
4814
5118
|
orgBus.emit({
|
|
@@ -4863,6 +5167,9 @@ async function flushBatch() {
|
|
|
4863
5167
|
const sourceType = row.source_type ?? null;
|
|
4864
5168
|
const tier = row.tier ?? 3;
|
|
4865
5169
|
const supersedesId = row.supersedes_id ?? null;
|
|
5170
|
+
const draft = row.draft ? 1 : 0;
|
|
5171
|
+
const memoryType = row.memory_type ?? "raw";
|
|
5172
|
+
const trajectory = row.trajectory ?? null;
|
|
4866
5173
|
return {
|
|
4867
5174
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
4868
5175
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -4870,15 +5177,15 @@ async function flushBatch() {
|
|
|
4870
5177
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4871
5178
|
confidence, last_accessed,
|
|
4872
5179
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4873
|
-
source_path, source_type, tier, supersedes_id)
|
|
4874
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
5180
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
5181
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4875
5182
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
4876
5183
|
tool_name, project_name,
|
|
4877
5184
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4878
5185
|
confidence, last_accessed,
|
|
4879
5186
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4880
|
-
source_path, source_type, tier, supersedes_id)
|
|
4881
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5187
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
5188
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4882
5189
|
args: hasVector ? [
|
|
4883
5190
|
row.id,
|
|
4884
5191
|
row.agent_id,
|
|
@@ -4904,7 +5211,10 @@ async function flushBatch() {
|
|
|
4904
5211
|
sourcePath,
|
|
4905
5212
|
sourceType,
|
|
4906
5213
|
tier,
|
|
4907
|
-
supersedesId
|
|
5214
|
+
supersedesId,
|
|
5215
|
+
draft,
|
|
5216
|
+
memoryType,
|
|
5217
|
+
trajectory
|
|
4908
5218
|
] : [
|
|
4909
5219
|
row.id,
|
|
4910
5220
|
row.agent_id,
|
|
@@ -4929,7 +5239,10 @@ async function flushBatch() {
|
|
|
4929
5239
|
sourcePath,
|
|
4930
5240
|
sourceType,
|
|
4931
5241
|
tier,
|
|
4932
|
-
supersedesId
|
|
5242
|
+
supersedesId,
|
|
5243
|
+
draft,
|
|
5244
|
+
memoryType,
|
|
5245
|
+
trajectory
|
|
4933
5246
|
]
|
|
4934
5247
|
};
|
|
4935
5248
|
};
|
|
@@ -4998,6 +5311,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4998
5311
|
const limit = options?.limit ?? 10;
|
|
4999
5312
|
const statusFilter = options?.includeArchived ? "" : `
|
|
5000
5313
|
AND COALESCE(status, 'active') = 'active'`;
|
|
5314
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
5315
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
5001
5316
|
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
5002
5317
|
tool_name, project_name,
|
|
5003
5318
|
has_error, raw_text, vector, importance, status,
|
|
@@ -5007,7 +5322,7 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
5007
5322
|
source_path, source_type
|
|
5008
5323
|
FROM memories
|
|
5009
5324
|
WHERE agent_id = ?
|
|
5010
|
-
AND vector IS NOT NULL${statusFilter}
|
|
5325
|
+
AND vector IS NOT NULL${statusFilter}${draftFilter}
|
|
5011
5326
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
5012
5327
|
const args = [agentId];
|
|
5013
5328
|
const scope = buildWikiScopeFilter(options, "");
|
|
@@ -5029,6 +5344,10 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
5029
5344
|
sql += ` AND timestamp >= ?`;
|
|
5030
5345
|
args.push(options.since);
|
|
5031
5346
|
}
|
|
5347
|
+
if (options?.memoryType) {
|
|
5348
|
+
sql += ` AND memory_type = ?`;
|
|
5349
|
+
args.push(options.memoryType);
|
|
5350
|
+
}
|
|
5032
5351
|
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
5033
5352
|
args.push(vectorToBlob(queryVector));
|
|
5034
5353
|
sql += ` LIMIT ?`;
|
|
@@ -5768,563 +6087,1480 @@ async function pushGatewayEventToCRM(params) {
|
|
|
5768
6087
|
console.error("[crm-bridge] Event push failed:", err instanceof Error ? err.message : err);
|
|
5769
6088
|
}
|
|
5770
6089
|
}
|
|
5771
|
-
var config, disabledLogged;
|
|
5772
|
-
var init_crm_bridge = __esm({
|
|
5773
|
-
"src/gateway/crm-bridge.ts"() {
|
|
5774
|
-
"use strict";
|
|
5775
|
-
config = null;
|
|
5776
|
-
disabledLogged = false;
|
|
6090
|
+
var config, disabledLogged;
|
|
6091
|
+
var init_crm_bridge = __esm({
|
|
6092
|
+
"src/gateway/crm-bridge.ts"() {
|
|
6093
|
+
"use strict";
|
|
6094
|
+
config = null;
|
|
6095
|
+
disabledLogged = false;
|
|
6096
|
+
}
|
|
6097
|
+
});
|
|
6098
|
+
|
|
6099
|
+
// src/lib/exe-daemon-client.ts
|
|
6100
|
+
import net from "net";
|
|
6101
|
+
import { spawn } from "child_process";
|
|
6102
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
6103
|
+
import { existsSync as existsSync13, unlinkSync as unlinkSync6, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
|
|
6104
|
+
import path17 from "path";
|
|
6105
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6106
|
+
function handleData(chunk) {
|
|
6107
|
+
_buffer += chunk.toString();
|
|
6108
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
6109
|
+
_buffer = "";
|
|
6110
|
+
return;
|
|
6111
|
+
}
|
|
6112
|
+
let newlineIdx;
|
|
6113
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
6114
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
6115
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
6116
|
+
if (!line) continue;
|
|
6117
|
+
try {
|
|
6118
|
+
const response = JSON.parse(line);
|
|
6119
|
+
const entry = _pending.get(response.id);
|
|
6120
|
+
if (entry) {
|
|
6121
|
+
clearTimeout(entry.timer);
|
|
6122
|
+
_pending.delete(response.id);
|
|
6123
|
+
entry.resolve(response);
|
|
6124
|
+
}
|
|
6125
|
+
} catch {
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
function cleanupStaleFiles() {
|
|
6130
|
+
if (existsSync13(PID_PATH)) {
|
|
6131
|
+
try {
|
|
6132
|
+
const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
|
|
6133
|
+
if (pid > 0) {
|
|
6134
|
+
try {
|
|
6135
|
+
process.kill(pid, 0);
|
|
6136
|
+
return;
|
|
6137
|
+
} catch {
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
} catch {
|
|
6141
|
+
}
|
|
6142
|
+
try {
|
|
6143
|
+
unlinkSync6(PID_PATH);
|
|
6144
|
+
} catch {
|
|
6145
|
+
}
|
|
6146
|
+
try {
|
|
6147
|
+
unlinkSync6(SOCKET_PATH);
|
|
6148
|
+
} catch {
|
|
6149
|
+
}
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
function findPackageRoot() {
|
|
6153
|
+
let dir = path17.dirname(fileURLToPath2(import.meta.url));
|
|
6154
|
+
const { root } = path17.parse(dir);
|
|
6155
|
+
while (dir !== root) {
|
|
6156
|
+
if (existsSync13(path17.join(dir, "package.json"))) return dir;
|
|
6157
|
+
dir = path17.dirname(dir);
|
|
6158
|
+
}
|
|
6159
|
+
return null;
|
|
6160
|
+
}
|
|
6161
|
+
function spawnDaemon() {
|
|
6162
|
+
const pkgRoot = findPackageRoot();
|
|
6163
|
+
if (!pkgRoot) {
|
|
6164
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
6165
|
+
return;
|
|
6166
|
+
}
|
|
6167
|
+
const daemonPath = path17.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
6168
|
+
if (!existsSync13(daemonPath)) {
|
|
6169
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
6170
|
+
`);
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
6173
|
+
const resolvedPath = daemonPath;
|
|
6174
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
6175
|
+
`);
|
|
6176
|
+
const logPath = path17.join(path17.dirname(SOCKET_PATH), "exed.log");
|
|
6177
|
+
let stderrFd = "ignore";
|
|
6178
|
+
try {
|
|
6179
|
+
stderrFd = openSync(logPath, "a");
|
|
6180
|
+
} catch {
|
|
6181
|
+
}
|
|
6182
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
6183
|
+
detached: true,
|
|
6184
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
6185
|
+
env: {
|
|
6186
|
+
...process.env,
|
|
6187
|
+
TMUX: void 0,
|
|
6188
|
+
// Daemon is global — must not inherit session scope
|
|
6189
|
+
TMUX_PANE: void 0,
|
|
6190
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
6191
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
6192
|
+
EXE_DAEMON_PID: PID_PATH
|
|
6193
|
+
}
|
|
6194
|
+
});
|
|
6195
|
+
child.unref();
|
|
6196
|
+
if (typeof stderrFd === "number") {
|
|
6197
|
+
try {
|
|
6198
|
+
closeSync(stderrFd);
|
|
6199
|
+
} catch {
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
function acquireSpawnLock2() {
|
|
6204
|
+
try {
|
|
6205
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
6206
|
+
closeSync(fd);
|
|
6207
|
+
return true;
|
|
6208
|
+
} catch {
|
|
6209
|
+
try {
|
|
6210
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
6211
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
6212
|
+
try {
|
|
6213
|
+
unlinkSync6(SPAWN_LOCK_PATH);
|
|
6214
|
+
} catch {
|
|
6215
|
+
}
|
|
6216
|
+
try {
|
|
6217
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
6218
|
+
closeSync(fd);
|
|
6219
|
+
return true;
|
|
6220
|
+
} catch {
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
} catch {
|
|
6224
|
+
}
|
|
6225
|
+
return false;
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
function releaseSpawnLock2() {
|
|
6229
|
+
try {
|
|
6230
|
+
unlinkSync6(SPAWN_LOCK_PATH);
|
|
6231
|
+
} catch {
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
function connectToSocket() {
|
|
6235
|
+
return new Promise((resolve) => {
|
|
6236
|
+
if (_socket && _connected) {
|
|
6237
|
+
resolve(true);
|
|
6238
|
+
return;
|
|
6239
|
+
}
|
|
6240
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
6241
|
+
const connectTimeout = setTimeout(() => {
|
|
6242
|
+
socket.destroy();
|
|
6243
|
+
resolve(false);
|
|
6244
|
+
}, 2e3);
|
|
6245
|
+
socket.on("connect", () => {
|
|
6246
|
+
clearTimeout(connectTimeout);
|
|
6247
|
+
_socket = socket;
|
|
6248
|
+
_connected = true;
|
|
6249
|
+
_buffer = "";
|
|
6250
|
+
socket.on("data", handleData);
|
|
6251
|
+
socket.on("close", () => {
|
|
6252
|
+
_connected = false;
|
|
6253
|
+
_socket = null;
|
|
6254
|
+
for (const [id, entry] of _pending) {
|
|
6255
|
+
clearTimeout(entry.timer);
|
|
6256
|
+
_pending.delete(id);
|
|
6257
|
+
entry.resolve({ error: "Connection closed" });
|
|
6258
|
+
}
|
|
6259
|
+
});
|
|
6260
|
+
socket.on("error", () => {
|
|
6261
|
+
_connected = false;
|
|
6262
|
+
_socket = null;
|
|
6263
|
+
});
|
|
6264
|
+
resolve(true);
|
|
6265
|
+
});
|
|
6266
|
+
socket.on("error", () => {
|
|
6267
|
+
clearTimeout(connectTimeout);
|
|
6268
|
+
resolve(false);
|
|
6269
|
+
});
|
|
6270
|
+
});
|
|
6271
|
+
}
|
|
6272
|
+
async function connectEmbedDaemon() {
|
|
6273
|
+
if (_socket && _connected) return true;
|
|
6274
|
+
if (await connectToSocket()) return true;
|
|
6275
|
+
if (acquireSpawnLock2()) {
|
|
6276
|
+
try {
|
|
6277
|
+
cleanupStaleFiles();
|
|
6278
|
+
spawnDaemon();
|
|
6279
|
+
} finally {
|
|
6280
|
+
releaseSpawnLock2();
|
|
6281
|
+
}
|
|
5777
6282
|
}
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
import { existsSync as existsSync13, unlinkSync as unlinkSync6, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
|
|
5785
|
-
import path17 from "path";
|
|
5786
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5787
|
-
function handleData(chunk) {
|
|
5788
|
-
_buffer += chunk.toString();
|
|
5789
|
-
if (_buffer.length > MAX_BUFFER) {
|
|
5790
|
-
_buffer = "";
|
|
5791
|
-
return;
|
|
6283
|
+
const start = Date.now();
|
|
6284
|
+
let delay2 = 100;
|
|
6285
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
6286
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
6287
|
+
if (await connectToSocket()) return true;
|
|
6288
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
5792
6289
|
}
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
if (!
|
|
6290
|
+
return false;
|
|
6291
|
+
}
|
|
6292
|
+
function sendRequest(texts, priority) {
|
|
6293
|
+
return new Promise((resolve) => {
|
|
6294
|
+
if (!_socket || !_connected) {
|
|
6295
|
+
resolve({ error: "Not connected" });
|
|
6296
|
+
return;
|
|
6297
|
+
}
|
|
6298
|
+
const id = randomUUID6();
|
|
6299
|
+
const timer = setTimeout(() => {
|
|
6300
|
+
_pending.delete(id);
|
|
6301
|
+
resolve({ error: "Request timeout" });
|
|
6302
|
+
}, REQUEST_TIMEOUT_MS);
|
|
6303
|
+
_pending.set(id, { resolve, timer });
|
|
5798
6304
|
try {
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
6305
|
+
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
6306
|
+
} catch {
|
|
6307
|
+
clearTimeout(timer);
|
|
6308
|
+
_pending.delete(id);
|
|
6309
|
+
resolve({ error: "Write failed" });
|
|
6310
|
+
}
|
|
6311
|
+
});
|
|
6312
|
+
}
|
|
6313
|
+
async function pingDaemon() {
|
|
6314
|
+
if (!_socket || !_connected) return null;
|
|
6315
|
+
return new Promise((resolve) => {
|
|
6316
|
+
const id = randomUUID6();
|
|
6317
|
+
const timer = setTimeout(() => {
|
|
6318
|
+
_pending.delete(id);
|
|
6319
|
+
resolve(null);
|
|
6320
|
+
}, 5e3);
|
|
6321
|
+
_pending.set(id, {
|
|
6322
|
+
resolve: (data) => {
|
|
6323
|
+
if (data.health) {
|
|
6324
|
+
resolve(data.health);
|
|
6325
|
+
} else {
|
|
6326
|
+
resolve(null);
|
|
6327
|
+
}
|
|
6328
|
+
},
|
|
6329
|
+
timer
|
|
6330
|
+
});
|
|
6331
|
+
try {
|
|
6332
|
+
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
5806
6333
|
} catch {
|
|
6334
|
+
clearTimeout(timer);
|
|
6335
|
+
_pending.delete(id);
|
|
6336
|
+
resolve(null);
|
|
5807
6337
|
}
|
|
5808
|
-
}
|
|
6338
|
+
});
|
|
5809
6339
|
}
|
|
5810
|
-
function
|
|
6340
|
+
function killAndRespawnDaemon() {
|
|
6341
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
5811
6342
|
if (existsSync13(PID_PATH)) {
|
|
5812
6343
|
try {
|
|
5813
6344
|
const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
|
|
5814
6345
|
if (pid > 0) {
|
|
5815
6346
|
try {
|
|
5816
|
-
process.kill(pid,
|
|
5817
|
-
return;
|
|
6347
|
+
process.kill(pid, "SIGKILL");
|
|
5818
6348
|
} catch {
|
|
5819
6349
|
}
|
|
5820
6350
|
}
|
|
5821
6351
|
} catch {
|
|
5822
6352
|
}
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
6353
|
+
}
|
|
6354
|
+
if (_socket) {
|
|
6355
|
+
_socket.destroy();
|
|
6356
|
+
_socket = null;
|
|
6357
|
+
}
|
|
6358
|
+
_connected = false;
|
|
6359
|
+
_buffer = "";
|
|
6360
|
+
try {
|
|
6361
|
+
unlinkSync6(PID_PATH);
|
|
6362
|
+
} catch {
|
|
6363
|
+
}
|
|
6364
|
+
try {
|
|
6365
|
+
unlinkSync6(SOCKET_PATH);
|
|
6366
|
+
} catch {
|
|
6367
|
+
}
|
|
6368
|
+
spawnDaemon();
|
|
6369
|
+
}
|
|
6370
|
+
async function embedViaClient(text, priority = "high") {
|
|
6371
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
6372
|
+
_requestCount++;
|
|
6373
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
6374
|
+
const health = await pingDaemon();
|
|
6375
|
+
if (!health) {
|
|
6376
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
6377
|
+
`);
|
|
6378
|
+
killAndRespawnDaemon();
|
|
6379
|
+
const start = Date.now();
|
|
6380
|
+
let delay2 = 200;
|
|
6381
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
6382
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
6383
|
+
if (await connectToSocket()) break;
|
|
6384
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
6385
|
+
}
|
|
6386
|
+
if (!_connected) return null;
|
|
5826
6387
|
}
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
6388
|
+
}
|
|
6389
|
+
const result = await sendRequest([text], priority);
|
|
6390
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
6391
|
+
if (result.error) {
|
|
6392
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
6393
|
+
`);
|
|
6394
|
+
killAndRespawnDaemon();
|
|
6395
|
+
const start = Date.now();
|
|
6396
|
+
let delay2 = 200;
|
|
6397
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
6398
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
6399
|
+
if (await connectToSocket()) break;
|
|
6400
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
5830
6401
|
}
|
|
6402
|
+
if (!_connected) return null;
|
|
6403
|
+
const retry = await sendRequest([text], priority);
|
|
6404
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
6405
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
6406
|
+
`);
|
|
6407
|
+
}
|
|
6408
|
+
return null;
|
|
6409
|
+
}
|
|
6410
|
+
function disconnectClient() {
|
|
6411
|
+
if (_socket) {
|
|
6412
|
+
_socket.destroy();
|
|
6413
|
+
_socket = null;
|
|
6414
|
+
}
|
|
6415
|
+
_connected = false;
|
|
6416
|
+
_buffer = "";
|
|
6417
|
+
for (const [id, entry] of _pending) {
|
|
6418
|
+
clearTimeout(entry.timer);
|
|
6419
|
+
_pending.delete(id);
|
|
6420
|
+
entry.resolve({ error: "Client disconnected" });
|
|
5831
6421
|
}
|
|
5832
6422
|
}
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
6423
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
6424
|
+
var init_exe_daemon_client = __esm({
|
|
6425
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
6426
|
+
"use strict";
|
|
6427
|
+
init_config();
|
|
6428
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path17.join(EXE_AI_DIR, "exed.sock");
|
|
6429
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path17.join(EXE_AI_DIR, "exed.pid");
|
|
6430
|
+
SPAWN_LOCK_PATH = path17.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
6431
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
6432
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
6433
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
6434
|
+
_socket = null;
|
|
6435
|
+
_connected = false;
|
|
6436
|
+
_buffer = "";
|
|
6437
|
+
_requestCount = 0;
|
|
6438
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
6439
|
+
_pending = /* @__PURE__ */ new Map();
|
|
6440
|
+
MAX_BUFFER = 1e7;
|
|
6441
|
+
}
|
|
6442
|
+
});
|
|
6443
|
+
|
|
6444
|
+
// src/lib/embedder.ts
|
|
6445
|
+
var embedder_exports = {};
|
|
6446
|
+
__export(embedder_exports, {
|
|
6447
|
+
disposeEmbedder: () => disposeEmbedder,
|
|
6448
|
+
embed: () => embed,
|
|
6449
|
+
embedDirect: () => embedDirect,
|
|
6450
|
+
getEmbedder: () => getEmbedder
|
|
6451
|
+
});
|
|
6452
|
+
async function getEmbedder() {
|
|
6453
|
+
const ok = await connectEmbedDaemon();
|
|
6454
|
+
if (!ok) {
|
|
6455
|
+
throw new Error(
|
|
6456
|
+
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
6457
|
+
);
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
async function embed(text) {
|
|
6461
|
+
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
6462
|
+
const vector = await embedViaClient(text, priority);
|
|
6463
|
+
if (!vector) {
|
|
6464
|
+
throw new Error(
|
|
6465
|
+
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
6466
|
+
);
|
|
6467
|
+
}
|
|
6468
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
6469
|
+
throw new Error(
|
|
6470
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
|
|
6471
|
+
);
|
|
6472
|
+
}
|
|
6473
|
+
return vector;
|
|
6474
|
+
}
|
|
6475
|
+
async function disposeEmbedder() {
|
|
6476
|
+
disconnectClient();
|
|
6477
|
+
}
|
|
6478
|
+
async function embedDirect(text) {
|
|
6479
|
+
const llamaCpp = await import("node-llama-cpp");
|
|
6480
|
+
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6481
|
+
const { existsSync: existsSync15 } = await import("fs");
|
|
6482
|
+
const path20 = await import("path");
|
|
6483
|
+
const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
6484
|
+
if (!existsSync15(modelPath)) {
|
|
6485
|
+
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
6486
|
+
}
|
|
6487
|
+
const llama = await llamaCpp.getLlama();
|
|
6488
|
+
const model = await llama.loadModel({ modelPath });
|
|
6489
|
+
const context = await model.createEmbeddingContext();
|
|
6490
|
+
try {
|
|
6491
|
+
const embedding = await context.getEmbeddingFor(text);
|
|
6492
|
+
const vector = Array.from(embedding.vector);
|
|
6493
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
6494
|
+
throw new Error(
|
|
6495
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
|
|
6496
|
+
);
|
|
6497
|
+
}
|
|
6498
|
+
return vector;
|
|
6499
|
+
} finally {
|
|
6500
|
+
await context.dispose();
|
|
6501
|
+
await model.dispose();
|
|
5839
6502
|
}
|
|
5840
|
-
return null;
|
|
5841
6503
|
}
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
}
|
|
5848
|
-
const daemonPath = path17.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
5849
|
-
if (!existsSync13(daemonPath)) {
|
|
5850
|
-
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
5851
|
-
`);
|
|
5852
|
-
return;
|
|
6504
|
+
var init_embedder = __esm({
|
|
6505
|
+
"src/lib/embedder.ts"() {
|
|
6506
|
+
"use strict";
|
|
6507
|
+
init_memory();
|
|
6508
|
+
init_exe_daemon_client();
|
|
5853
6509
|
}
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
6510
|
+
});
|
|
6511
|
+
|
|
6512
|
+
// src/lib/wiki-client.ts
|
|
6513
|
+
var wiki_client_exports = {};
|
|
6514
|
+
__export(wiki_client_exports, {
|
|
6515
|
+
chatInWorkspace: () => chatInWorkspace,
|
|
6516
|
+
createWikiClient: () => createWikiClient,
|
|
6517
|
+
getChatHistory: () => getChatHistory,
|
|
6518
|
+
listDocuments: () => listDocuments,
|
|
6519
|
+
listWorkspaces: () => listWorkspaces
|
|
6520
|
+
});
|
|
6521
|
+
async function wikiFetch(config2, path20, method = "GET", body) {
|
|
6522
|
+
const url = `${config2.baseUrl}/api/v1${path20}`;
|
|
6523
|
+
const headers = {
|
|
6524
|
+
Authorization: `Bearer ${config2.apiKey}`,
|
|
6525
|
+
"Content-Type": "application/json"
|
|
6526
|
+
};
|
|
6527
|
+
const controller = new AbortController();
|
|
6528
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
5859
6529
|
try {
|
|
5860
|
-
|
|
5861
|
-
} catch {
|
|
5862
|
-
}
|
|
5863
|
-
const child = spawn(process.execPath, [resolvedPath], {
|
|
5864
|
-
detached: true,
|
|
5865
|
-
stdio: ["ignore", "ignore", stderrFd],
|
|
5866
|
-
env: {
|
|
5867
|
-
...process.env,
|
|
5868
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
5869
|
-
EXE_DAEMON_PID: PID_PATH
|
|
5870
|
-
}
|
|
5871
|
-
});
|
|
5872
|
-
child.unref();
|
|
5873
|
-
if (typeof stderrFd === "number") {
|
|
6530
|
+
let response;
|
|
5874
6531
|
try {
|
|
5875
|
-
|
|
6532
|
+
response = await fetch(url, {
|
|
6533
|
+
method,
|
|
6534
|
+
headers,
|
|
6535
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
6536
|
+
signal: controller.signal
|
|
6537
|
+
});
|
|
5876
6538
|
} catch {
|
|
6539
|
+
clearTimeout(timeout);
|
|
6540
|
+
const retryController = new AbortController();
|
|
6541
|
+
const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS2);
|
|
6542
|
+
try {
|
|
6543
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
6544
|
+
response = await fetch(url, {
|
|
6545
|
+
method,
|
|
6546
|
+
headers,
|
|
6547
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
6548
|
+
signal: retryController.signal
|
|
6549
|
+
});
|
|
6550
|
+
} finally {
|
|
6551
|
+
clearTimeout(retryTimeout);
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
if (!response.ok) {
|
|
6555
|
+
throw new Error(`Wiki API ${method} ${path20}: ${response.status} ${response.statusText}`);
|
|
5877
6556
|
}
|
|
6557
|
+
return response.json();
|
|
6558
|
+
} finally {
|
|
6559
|
+
clearTimeout(timeout);
|
|
5878
6560
|
}
|
|
5879
6561
|
}
|
|
5880
|
-
function
|
|
6562
|
+
async function resolveWikiUrl(configUrl) {
|
|
5881
6563
|
try {
|
|
5882
|
-
const
|
|
5883
|
-
|
|
5884
|
-
|
|
6564
|
+
const controller = new AbortController();
|
|
6565
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
6566
|
+
const res = await fetch(`${LOCAL_WIKI_URL}/api/v1/auth`, { signal: controller.signal });
|
|
6567
|
+
clearTimeout(timeout);
|
|
6568
|
+
if (res.ok || res.status === 401 || res.status === 403) {
|
|
6569
|
+
return LOCAL_WIKI_URL;
|
|
6570
|
+
}
|
|
5885
6571
|
} catch {
|
|
6572
|
+
}
|
|
6573
|
+
if (configUrl && configUrl !== LOCAL_WIKI_URL) {
|
|
5886
6574
|
try {
|
|
5887
|
-
const
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
try {
|
|
5894
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
5895
|
-
closeSync(fd);
|
|
5896
|
-
return true;
|
|
5897
|
-
} catch {
|
|
5898
|
-
}
|
|
6575
|
+
const controller = new AbortController();
|
|
6576
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
6577
|
+
const res = await fetch(`${configUrl}/api/v1/auth`, { signal: controller.signal });
|
|
6578
|
+
clearTimeout(timeout);
|
|
6579
|
+
if (res.ok || res.status === 401 || res.status === 403) {
|
|
6580
|
+
return configUrl;
|
|
5899
6581
|
}
|
|
5900
6582
|
} catch {
|
|
5901
6583
|
}
|
|
5902
|
-
return false;
|
|
5903
6584
|
}
|
|
6585
|
+
return null;
|
|
5904
6586
|
}
|
|
5905
|
-
function
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
6587
|
+
async function createWikiClient() {
|
|
6588
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6589
|
+
const config2 = await loadConfig2();
|
|
6590
|
+
const baseUrl = await resolveWikiUrl(config2.wikiUrl);
|
|
6591
|
+
if (!baseUrl) return null;
|
|
6592
|
+
return {
|
|
6593
|
+
baseUrl,
|
|
6594
|
+
apiKey: config2.wikiApiKey
|
|
6595
|
+
};
|
|
5910
6596
|
}
|
|
5911
|
-
function
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
6597
|
+
async function listWorkspaces(client) {
|
|
6598
|
+
const data = await wikiFetch(client, "/workspaces");
|
|
6599
|
+
return (data.workspaces ?? []).map((w) => ({
|
|
6600
|
+
slug: w.slug,
|
|
6601
|
+
name: w.name,
|
|
6602
|
+
createdAt: w.createdAt ?? "",
|
|
6603
|
+
threads: w.threads ?? []
|
|
6604
|
+
}));
|
|
6605
|
+
}
|
|
6606
|
+
async function listDocuments(client, workspaceSlug) {
|
|
6607
|
+
const data = await wikiFetch(
|
|
6608
|
+
client,
|
|
6609
|
+
`/workspace/${encodeURIComponent(workspaceSlug)}`
|
|
6610
|
+
);
|
|
6611
|
+
const docs = Array.isArray(data.workspace) ? data.workspace[0]?.documents ?? [] : [];
|
|
6612
|
+
return docs.map((d) => ({
|
|
6613
|
+
filename: d.filename ?? "untitled",
|
|
6614
|
+
docpath: d.docpath,
|
|
6615
|
+
workspace: workspaceSlug,
|
|
6616
|
+
pinned: d.pinned ?? false
|
|
6617
|
+
}));
|
|
6618
|
+
}
|
|
6619
|
+
async function chatInWorkspace(client, workspaceSlug, message, mode = "chat") {
|
|
6620
|
+
const data = await wikiFetch(
|
|
6621
|
+
client,
|
|
6622
|
+
`/workspace/${encodeURIComponent(workspaceSlug)}/chat`,
|
|
6623
|
+
"POST",
|
|
6624
|
+
{ message, mode }
|
|
6625
|
+
);
|
|
6626
|
+
return {
|
|
6627
|
+
id: data.id ?? "",
|
|
6628
|
+
type: data.type ?? "textResponse",
|
|
6629
|
+
textResponse: data.textResponse ?? "",
|
|
6630
|
+
sources: data.sources ?? [],
|
|
6631
|
+
error: data.error ?? null
|
|
6632
|
+
};
|
|
6633
|
+
}
|
|
6634
|
+
async function getChatHistory(client, workspaceSlug, limit = 50) {
|
|
6635
|
+
const data = await wikiFetch(
|
|
6636
|
+
client,
|
|
6637
|
+
`/workspace/${encodeURIComponent(workspaceSlug)}/chats?limit=${limit}&orderBy=desc`
|
|
6638
|
+
);
|
|
6639
|
+
return (data.history ?? []).reverse().map((h) => ({
|
|
6640
|
+
role: h.role === "user" ? "user" : "assistant",
|
|
6641
|
+
content: h.content,
|
|
6642
|
+
sentAt: h.sentAt ?? 0
|
|
6643
|
+
}));
|
|
6644
|
+
}
|
|
6645
|
+
var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
|
|
6646
|
+
var init_wiki_client = __esm({
|
|
6647
|
+
"src/lib/wiki-client.ts"() {
|
|
6648
|
+
"use strict";
|
|
6649
|
+
LOCAL_WIKI_URL = "http://localhost:3001";
|
|
6650
|
+
REQUEST_TIMEOUT_MS2 = 8e3;
|
|
6651
|
+
}
|
|
6652
|
+
});
|
|
6653
|
+
|
|
6654
|
+
// src/lib/code-chunker.ts
|
|
6655
|
+
import ts from "typescript";
|
|
6656
|
+
function chunkSourceFile(source, fileName = "file.ts") {
|
|
6657
|
+
const sourceFile = ts.createSourceFile(
|
|
6658
|
+
fileName,
|
|
6659
|
+
source,
|
|
6660
|
+
ts.ScriptTarget.Latest,
|
|
6661
|
+
true,
|
|
6662
|
+
// setParentNodes
|
|
6663
|
+
fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
6664
|
+
);
|
|
6665
|
+
const chunks = [];
|
|
6666
|
+
const lines = source.split("\n");
|
|
6667
|
+
const importLines = [];
|
|
6668
|
+
function getLineNumber(pos) {
|
|
6669
|
+
return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
|
|
6670
|
+
}
|
|
6671
|
+
function getLeadingComment(node) {
|
|
6672
|
+
const fullText = sourceFile.getFullText();
|
|
6673
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
6674
|
+
if (!ranges || ranges.length === 0) return void 0;
|
|
6675
|
+
return ranges.map((r) => fullText.slice(r.pos, r.end)).join("\n");
|
|
6676
|
+
}
|
|
6677
|
+
function getNodeText(node) {
|
|
6678
|
+
return node.getText(sourceFile);
|
|
6679
|
+
}
|
|
6680
|
+
function getName(node) {
|
|
6681
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
|
|
6682
|
+
return node.name?.getText(sourceFile) ?? "(anonymous)";
|
|
6683
|
+
}
|
|
6684
|
+
if (ts.isClassDeclaration(node)) {
|
|
6685
|
+
return node.name?.getText(sourceFile) ?? "(anonymous class)";
|
|
6686
|
+
}
|
|
6687
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
6688
|
+
return node.name.getText(sourceFile);
|
|
6689
|
+
}
|
|
6690
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
6691
|
+
return node.name.getText(sourceFile);
|
|
6692
|
+
}
|
|
6693
|
+
if (ts.isEnumDeclaration(node)) {
|
|
6694
|
+
return node.name.getText(sourceFile);
|
|
6695
|
+
}
|
|
6696
|
+
if (ts.isVariableStatement(node)) {
|
|
6697
|
+
const decls = node.declarationList.declarations;
|
|
6698
|
+
return decls.map((d) => d.name.getText(sourceFile)).join(", ");
|
|
6699
|
+
}
|
|
6700
|
+
if (ts.isExportAssignment(node)) {
|
|
6701
|
+
return "default export";
|
|
6702
|
+
}
|
|
6703
|
+
return "(unknown)";
|
|
6704
|
+
}
|
|
6705
|
+
function visitTopLevel(node) {
|
|
6706
|
+
if (ts.isImportDeclaration(node)) {
|
|
6707
|
+
importLines.push({
|
|
6708
|
+
start: getLineNumber(node.getStart(sourceFile)),
|
|
6709
|
+
end: getLineNumber(node.getEnd())
|
|
6710
|
+
});
|
|
5915
6711
|
return;
|
|
5916
6712
|
}
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
_connected = true;
|
|
5926
|
-
_buffer = "";
|
|
5927
|
-
socket.on("data", handleData);
|
|
5928
|
-
socket.on("close", () => {
|
|
5929
|
-
_connected = false;
|
|
5930
|
-
_socket = null;
|
|
5931
|
-
for (const [id, entry] of _pending) {
|
|
5932
|
-
clearTimeout(entry.timer);
|
|
5933
|
-
_pending.delete(id);
|
|
5934
|
-
entry.resolve({ error: "Connection closed" });
|
|
5935
|
-
}
|
|
6713
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
6714
|
+
chunks.push({
|
|
6715
|
+
kind: "function",
|
|
6716
|
+
name: getName(node),
|
|
6717
|
+
text: getNodeText(node),
|
|
6718
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6719
|
+
endLine: getLineNumber(node.getEnd()),
|
|
6720
|
+
comment: getLeadingComment(node)
|
|
5936
6721
|
});
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
6722
|
+
return;
|
|
6723
|
+
}
|
|
6724
|
+
if (ts.isClassDeclaration(node)) {
|
|
6725
|
+
chunks.push({
|
|
6726
|
+
kind: "class",
|
|
6727
|
+
name: getName(node),
|
|
6728
|
+
text: getNodeText(node),
|
|
6729
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6730
|
+
endLine: getLineNumber(node.getEnd()),
|
|
6731
|
+
comment: getLeadingComment(node)
|
|
5940
6732
|
});
|
|
5941
|
-
|
|
5942
|
-
}
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
6733
|
+
return;
|
|
6734
|
+
}
|
|
6735
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
6736
|
+
chunks.push({
|
|
6737
|
+
kind: "type",
|
|
6738
|
+
name: getName(node),
|
|
6739
|
+
text: getNodeText(node),
|
|
6740
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6741
|
+
endLine: getLineNumber(node.getEnd()),
|
|
6742
|
+
comment: getLeadingComment(node)
|
|
6743
|
+
});
|
|
6744
|
+
return;
|
|
6745
|
+
}
|
|
6746
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
6747
|
+
chunks.push({
|
|
6748
|
+
kind: "type",
|
|
6749
|
+
name: getName(node),
|
|
6750
|
+
text: getNodeText(node),
|
|
6751
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6752
|
+
endLine: getLineNumber(node.getEnd()),
|
|
6753
|
+
comment: getLeadingComment(node)
|
|
6754
|
+
});
|
|
6755
|
+
return;
|
|
6756
|
+
}
|
|
6757
|
+
if (ts.isEnumDeclaration(node)) {
|
|
6758
|
+
chunks.push({
|
|
6759
|
+
kind: "type",
|
|
6760
|
+
name: getName(node),
|
|
6761
|
+
text: getNodeText(node),
|
|
6762
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6763
|
+
endLine: getLineNumber(node.getEnd()),
|
|
6764
|
+
comment: getLeadingComment(node)
|
|
6765
|
+
});
|
|
6766
|
+
return;
|
|
5958
6767
|
}
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
resolve({ error: "Not connected" });
|
|
6768
|
+
if (ts.isVariableStatement(node)) {
|
|
6769
|
+
const decls = node.declarationList.declarations;
|
|
6770
|
+
const isFnLike = decls.some(
|
|
6771
|
+
(d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer))
|
|
6772
|
+
);
|
|
6773
|
+
chunks.push({
|
|
6774
|
+
kind: isFnLike ? "function" : "variable",
|
|
6775
|
+
name: getName(node),
|
|
6776
|
+
text: getNodeText(node),
|
|
6777
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6778
|
+
endLine: getLineNumber(node.getEnd()),
|
|
6779
|
+
comment: getLeadingComment(node)
|
|
6780
|
+
});
|
|
5973
6781
|
return;
|
|
5974
6782
|
}
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
_pending.delete(id);
|
|
5986
|
-
resolve({ error: "Write failed" });
|
|
6783
|
+
if (ts.isExportAssignment(node)) {
|
|
6784
|
+
chunks.push({
|
|
6785
|
+
kind: "export",
|
|
6786
|
+
name: "default export",
|
|
6787
|
+
text: getNodeText(node),
|
|
6788
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6789
|
+
endLine: getLineNumber(node.getEnd()),
|
|
6790
|
+
comment: getLeadingComment(node)
|
|
6791
|
+
});
|
|
6792
|
+
return;
|
|
5987
6793
|
}
|
|
5988
|
-
|
|
6794
|
+
if (ts.isExpressionStatement(node)) {
|
|
6795
|
+
const text = getNodeText(node);
|
|
6796
|
+
if (text.length > 10) {
|
|
6797
|
+
chunks.push({
|
|
6798
|
+
kind: "other",
|
|
6799
|
+
name: text.slice(0, 40).replace(/\n/g, " "),
|
|
6800
|
+
text,
|
|
6801
|
+
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
6802
|
+
endLine: getLineNumber(node.getEnd())
|
|
6803
|
+
});
|
|
6804
|
+
}
|
|
6805
|
+
return;
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
sourceFile.statements.forEach(visitTopLevel);
|
|
6809
|
+
if (importLines.length > 0) {
|
|
6810
|
+
const startLine = importLines[0].start;
|
|
6811
|
+
const endLine = importLines[importLines.length - 1].end;
|
|
6812
|
+
const importText = lines.slice(startLine - 1, endLine).join("\n");
|
|
6813
|
+
chunks.unshift({
|
|
6814
|
+
kind: "import",
|
|
6815
|
+
name: `${importLines.length} imports`,
|
|
6816
|
+
text: importText,
|
|
6817
|
+
startLine,
|
|
6818
|
+
endLine
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6821
|
+
chunks.sort((a, b) => a.startLine - b.startLine);
|
|
6822
|
+
return chunks;
|
|
5989
6823
|
}
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
return
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6824
|
+
function isChunkable(filePath) {
|
|
6825
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
6826
|
+
return ext === "ts" || ext === "tsx" || ext === "js" || ext === "jsx";
|
|
6827
|
+
}
|
|
6828
|
+
var init_code_chunker = __esm({
|
|
6829
|
+
"src/lib/code-chunker.ts"() {
|
|
6830
|
+
"use strict";
|
|
6831
|
+
}
|
|
6832
|
+
});
|
|
6833
|
+
|
|
6834
|
+
// src/lib/graph-rag.ts
|
|
6835
|
+
var graph_rag_exports = {};
|
|
6836
|
+
__export(graph_rag_exports, {
|
|
6837
|
+
EXTRACT_TOOL: () => EXTRACT_TOOL,
|
|
6838
|
+
entityId: () => entityId,
|
|
6839
|
+
extractBatch: () => extractBatch,
|
|
6840
|
+
extractFromCode: () => extractFromCode,
|
|
6841
|
+
extractFromMemory: () => extractFromMemory,
|
|
6842
|
+
mergeEntities: () => mergeEntities,
|
|
6843
|
+
normalizeEntityName: () => normalizeEntityName,
|
|
6844
|
+
registerAlias: () => registerAlias,
|
|
6845
|
+
resolveAlias: () => resolveAlias,
|
|
6846
|
+
storeExtraction: () => storeExtraction
|
|
6847
|
+
});
|
|
6848
|
+
import crypto7 from "crypto";
|
|
6849
|
+
function normalizeEntityName(name) {
|
|
6850
|
+
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
6851
|
+
}
|
|
6852
|
+
function entityId(name, type) {
|
|
6853
|
+
const normalized = normalizeEntityName(name);
|
|
6854
|
+
return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
6855
|
+
}
|
|
6856
|
+
async function resolveAlias(client, name) {
|
|
6857
|
+
const normalized = normalizeEntityName(name);
|
|
6858
|
+
try {
|
|
6859
|
+
const result = await client.execute({
|
|
6860
|
+
sql: "SELECT canonical_entity_id FROM entity_aliases WHERE alias = ?",
|
|
6861
|
+
args: [normalized]
|
|
6007
6862
|
});
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
} catch {
|
|
6011
|
-
clearTimeout(timer);
|
|
6012
|
-
_pending.delete(id);
|
|
6013
|
-
resolve(null);
|
|
6863
|
+
if (result.rows.length > 0) {
|
|
6864
|
+
return String(result.rows[0].canonical_entity_id);
|
|
6014
6865
|
}
|
|
6866
|
+
} catch {
|
|
6867
|
+
}
|
|
6868
|
+
return null;
|
|
6869
|
+
}
|
|
6870
|
+
async function registerAlias(client, alias, canonicalEntityId) {
|
|
6871
|
+
const normalized = normalizeEntityName(alias);
|
|
6872
|
+
await client.execute({
|
|
6873
|
+
sql: `INSERT OR REPLACE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
|
|
6874
|
+
args: [normalized, canonicalEntityId]
|
|
6015
6875
|
});
|
|
6016
6876
|
}
|
|
6017
|
-
function
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
process.kill(pid, "SIGKILL");
|
|
6025
|
-
} catch {
|
|
6026
|
-
}
|
|
6027
|
-
}
|
|
6028
|
-
} catch {
|
|
6029
|
-
}
|
|
6877
|
+
async function mergeEntities(client, sourceEntityId, targetEntityId) {
|
|
6878
|
+
const sourceResult = await client.execute({
|
|
6879
|
+
sql: "SELECT name FROM entities WHERE id = ?",
|
|
6880
|
+
args: [sourceEntityId]
|
|
6881
|
+
});
|
|
6882
|
+
if (sourceResult.rows.length === 0) {
|
|
6883
|
+
throw new Error(`Source entity ${sourceEntityId} not found`);
|
|
6030
6884
|
}
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6885
|
+
const targetResult = await client.execute({
|
|
6886
|
+
sql: "SELECT id FROM entities WHERE id = ?",
|
|
6887
|
+
args: [targetEntityId]
|
|
6888
|
+
});
|
|
6889
|
+
if (targetResult.rows.length === 0) {
|
|
6890
|
+
throw new Error(`Target entity ${targetEntityId} not found`);
|
|
6891
|
+
}
|
|
6892
|
+
const sourceName = String(sourceResult.rows[0].name);
|
|
6893
|
+
const relsSource = await client.execute({
|
|
6894
|
+
sql: `UPDATE relationships SET source_entity_id = ?
|
|
6895
|
+
WHERE source_entity_id = ?
|
|
6896
|
+
AND NOT EXISTS (
|
|
6897
|
+
SELECT 1 FROM relationships r2
|
|
6898
|
+
WHERE r2.source_entity_id = ? AND r2.target_entity_id = relationships.target_entity_id AND r2.type = relationships.type
|
|
6899
|
+
)`,
|
|
6900
|
+
args: [targetEntityId, sourceEntityId, targetEntityId]
|
|
6901
|
+
});
|
|
6902
|
+
const relsTarget = await client.execute({
|
|
6903
|
+
sql: `UPDATE relationships SET target_entity_id = ?
|
|
6904
|
+
WHERE target_entity_id = ?
|
|
6905
|
+
AND NOT EXISTS (
|
|
6906
|
+
SELECT 1 FROM relationships r2
|
|
6907
|
+
WHERE r2.target_entity_id = ? AND r2.source_entity_id = relationships.source_entity_id AND r2.type = relationships.type
|
|
6908
|
+
)`,
|
|
6909
|
+
args: [targetEntityId, sourceEntityId, targetEntityId]
|
|
6910
|
+
});
|
|
6911
|
+
await client.execute({
|
|
6912
|
+
sql: "DELETE FROM relationships WHERE source_entity_id = ? OR target_entity_id = ?",
|
|
6913
|
+
args: [sourceEntityId, sourceEntityId]
|
|
6914
|
+
});
|
|
6915
|
+
const mems = await client.execute({
|
|
6916
|
+
sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id)
|
|
6917
|
+
SELECT ?, memory_id FROM entity_memories WHERE entity_id = ?`,
|
|
6918
|
+
args: [targetEntityId, sourceEntityId]
|
|
6919
|
+
});
|
|
6920
|
+
await client.execute({
|
|
6921
|
+
sql: "DELETE FROM entity_memories WHERE entity_id = ?",
|
|
6922
|
+
args: [sourceEntityId]
|
|
6923
|
+
});
|
|
6924
|
+
await registerAlias(client, sourceName, targetEntityId);
|
|
6925
|
+
await client.execute({
|
|
6926
|
+
sql: "DELETE FROM entities WHERE id = ?",
|
|
6927
|
+
args: [sourceEntityId]
|
|
6928
|
+
});
|
|
6929
|
+
return {
|
|
6930
|
+
relationshipsMoved: (relsSource.rowsAffected ?? 0) + (relsTarget.rowsAffected ?? 0),
|
|
6931
|
+
memoriesMoved: mems.rowsAffected ?? 0
|
|
6932
|
+
};
|
|
6933
|
+
}
|
|
6934
|
+
function parseImportPaths(importText) {
|
|
6935
|
+
const paths = [];
|
|
6936
|
+
const fromRegex = /from\s+["']([^"']+)["']/g;
|
|
6937
|
+
let match;
|
|
6938
|
+
while ((match = fromRegex.exec(importText)) !== null) {
|
|
6939
|
+
paths.push(match[1]);
|
|
6034
6940
|
}
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
unlinkSync6(PID_PATH);
|
|
6039
|
-
} catch {
|
|
6941
|
+
const bareRegex = /^import\s+["']([^"']+)["']/gm;
|
|
6942
|
+
while ((match = bareRegex.exec(importText)) !== null) {
|
|
6943
|
+
if (!paths.includes(match[1])) paths.push(match[1]);
|
|
6040
6944
|
}
|
|
6945
|
+
return paths;
|
|
6946
|
+
}
|
|
6947
|
+
function moduleNameFromPath(importPath) {
|
|
6948
|
+
const base = importPath.split("/").pop() ?? importPath;
|
|
6949
|
+
return base.replace(/\.\w+$/, "") || base;
|
|
6950
|
+
}
|
|
6951
|
+
function extractFromCode(source, filePath) {
|
|
6952
|
+
let chunks;
|
|
6041
6953
|
try {
|
|
6042
|
-
|
|
6954
|
+
chunks = chunkSourceFile(source, filePath);
|
|
6043
6955
|
} catch {
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
const
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6956
|
+
return { entities: [], relationships: [], hyperedges: [] };
|
|
6957
|
+
}
|
|
6958
|
+
if (chunks.length === 0) {
|
|
6959
|
+
return { entities: [], relationships: [], hyperedges: [] };
|
|
6960
|
+
}
|
|
6961
|
+
const entities = [];
|
|
6962
|
+
const relationships = [];
|
|
6963
|
+
const fileName = filePath.split("/").pop() ?? filePath;
|
|
6964
|
+
entities.push({ name: fileName, type: "file" });
|
|
6965
|
+
for (const chunk of chunks) {
|
|
6966
|
+
if (chunk.kind === "import") {
|
|
6967
|
+
const importPaths = parseImportPaths(chunk.text);
|
|
6968
|
+
for (const importPath of importPaths) {
|
|
6969
|
+
const moduleName = moduleNameFromPath(importPath);
|
|
6970
|
+
entities.push({ name: moduleName, type: "file" });
|
|
6971
|
+
relationships.push({
|
|
6972
|
+
source: fileName,
|
|
6973
|
+
sourceType: "file",
|
|
6974
|
+
target: moduleName,
|
|
6975
|
+
targetType: "file",
|
|
6976
|
+
relationship: "depends_on",
|
|
6977
|
+
confidence: 1,
|
|
6978
|
+
confidenceLabel: "extracted"
|
|
6979
|
+
});
|
|
6062
6980
|
}
|
|
6063
|
-
|
|
6064
|
-
}
|
|
6065
|
-
}
|
|
6066
|
-
const result = await sendRequest([text], priority);
|
|
6067
|
-
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
6068
|
-
if (result.error) {
|
|
6069
|
-
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
6070
|
-
`);
|
|
6071
|
-
killAndRespawnDaemon();
|
|
6072
|
-
const start = Date.now();
|
|
6073
|
-
let delay2 = 200;
|
|
6074
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
6075
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
6076
|
-
if (await connectToSocket()) break;
|
|
6077
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
6981
|
+
continue;
|
|
6078
6982
|
}
|
|
6079
|
-
if (
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6983
|
+
if (chunk.kind === "other" || chunk.name === "(unknown)") continue;
|
|
6984
|
+
entities.push({ name: chunk.name, type: "concept" });
|
|
6985
|
+
relationships.push({
|
|
6986
|
+
source: fileName,
|
|
6987
|
+
sourceType: "file",
|
|
6988
|
+
target: chunk.name,
|
|
6989
|
+
targetType: "concept",
|
|
6990
|
+
relationship: "uses",
|
|
6991
|
+
confidence: 1,
|
|
6992
|
+
confidenceLabel: "extracted"
|
|
6993
|
+
});
|
|
6084
6994
|
}
|
|
6085
|
-
return
|
|
6995
|
+
return { entities, relationships, hyperedges: [] };
|
|
6086
6996
|
}
|
|
6087
|
-
function
|
|
6088
|
-
if (
|
|
6089
|
-
|
|
6090
|
-
_socket = null;
|
|
6091
|
-
}
|
|
6092
|
-
_connected = false;
|
|
6093
|
-
_buffer = "";
|
|
6094
|
-
for (const [id, entry] of _pending) {
|
|
6095
|
-
clearTimeout(entry.timer);
|
|
6096
|
-
_pending.delete(id);
|
|
6097
|
-
entry.resolve({ error: "Client disconnected" });
|
|
6997
|
+
async function extractFromMemory(content, agentId, model = "claude-haiku-4-5-20251001", filePath) {
|
|
6998
|
+
if (content.length < 50) {
|
|
6999
|
+
return { entities: [], relationships: [], hyperedges: [] };
|
|
6098
7000
|
}
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
init_config();
|
|
6105
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path17.join(EXE_AI_DIR, "exed.sock");
|
|
6106
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path17.join(EXE_AI_DIR, "exed.pid");
|
|
6107
|
-
SPAWN_LOCK_PATH = path17.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
6108
|
-
SPAWN_LOCK_STALE_MS = 3e4;
|
|
6109
|
-
CONNECT_TIMEOUT_MS = 15e3;
|
|
6110
|
-
REQUEST_TIMEOUT_MS = 3e4;
|
|
6111
|
-
_socket = null;
|
|
6112
|
-
_connected = false;
|
|
6113
|
-
_buffer = "";
|
|
6114
|
-
_requestCount = 0;
|
|
6115
|
-
HEALTH_CHECK_INTERVAL = 100;
|
|
6116
|
-
_pending = /* @__PURE__ */ new Map();
|
|
6117
|
-
MAX_BUFFER = 1e7;
|
|
7001
|
+
if (filePath && isChunkable(filePath)) {
|
|
7002
|
+
const astResult = extractFromCode(content, filePath);
|
|
7003
|
+
if (astResult.entities.length > 0) {
|
|
7004
|
+
return astResult;
|
|
7005
|
+
}
|
|
6118
7006
|
}
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
}
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
7007
|
+
try {
|
|
7008
|
+
const Anthropic4 = (await import("@anthropic-ai/sdk")).default;
|
|
7009
|
+
const client = new Anthropic4();
|
|
7010
|
+
const response = await client.messages.create({
|
|
7011
|
+
model,
|
|
7012
|
+
max_tokens: 512,
|
|
7013
|
+
system: `Extract entities and relationships from this AI agent work memory. Agent: ${agentId}. Focus on: people, tools, projects, decisions, files, rationale \u2014 and for business contexts: products, companies, suppliers, locations, orders, categories. Only extract what's clearly stated \u2014 don't infer. Use the simplest form of names (e.g., "Lenny" not "Lenny (HYGO)"). When content says "we chose X because Y", extract a rationale entity with rationale_for edge. Business examples: "Lenny supplies organic flour to HYGO" \u2192 (Lenny, supplies, HYGO). "Order #1234 shipped to Auckland" \u2192 (Order #1234, ships_to, Auckland).`,
|
|
7014
|
+
messages: [{ role: "user", content: content.slice(0, 1e3) }],
|
|
7015
|
+
tools: [EXTRACT_TOOL],
|
|
7016
|
+
tool_choice: { type: "tool", name: "extract_entities_and_relationships" }
|
|
7017
|
+
});
|
|
7018
|
+
const toolBlock = response.content.find((b) => b.type === "tool_use");
|
|
7019
|
+
if (toolBlock && toolBlock.type === "tool_use") {
|
|
7020
|
+
const input = toolBlock.input;
|
|
7021
|
+
const rawEntities = input.entities ?? [];
|
|
7022
|
+
const rawRels = input.relationships ?? [];
|
|
7023
|
+
const rawHyperedges = input.hyperedges ?? [];
|
|
7024
|
+
return {
|
|
7025
|
+
entities: rawEntities.map((e) => ({
|
|
7026
|
+
name: e.name ?? "",
|
|
7027
|
+
type: e.type ?? "concept"
|
|
7028
|
+
})).filter((e) => e.name.length > 0),
|
|
7029
|
+
relationships: rawRels.map((r) => ({
|
|
7030
|
+
source: r.source ?? "",
|
|
7031
|
+
sourceType: r.source_type ?? "concept",
|
|
7032
|
+
target: r.target ?? "",
|
|
7033
|
+
targetType: r.target_type ?? "concept",
|
|
7034
|
+
relationship: r.relationship ?? "uses",
|
|
7035
|
+
confidence: Number(r.confidence ?? 1),
|
|
7036
|
+
confidenceLabel: String(r.confidence_label ?? "extracted")
|
|
7037
|
+
})).filter((r) => r.source.length > 0 && r.target.length > 0),
|
|
7038
|
+
hyperedges: rawHyperedges.map((h) => ({
|
|
7039
|
+
label: String(h.label ?? ""),
|
|
7040
|
+
relation: String(h.relation ?? ""),
|
|
7041
|
+
entityNames: h.entity_names ?? [],
|
|
7042
|
+
confidence: Number(h.confidence ?? 1)
|
|
7043
|
+
})).filter((h) => h.label.length > 0 && h.entityNames.length >= 3)
|
|
7044
|
+
};
|
|
7045
|
+
}
|
|
7046
|
+
} catch (err) {
|
|
7047
|
+
process.stderr.write(
|
|
7048
|
+
`[graph-rag] Extraction failed: ${err instanceof Error ? err.message : String(err)}
|
|
7049
|
+
`
|
|
6134
7050
|
);
|
|
6135
7051
|
}
|
|
7052
|
+
return { entities: [], relationships: [], hyperedges: [] };
|
|
6136
7053
|
}
|
|
6137
|
-
async function
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
);
|
|
7054
|
+
async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
7055
|
+
let entitiesStored = 0;
|
|
7056
|
+
let relationshipsStored = 0;
|
|
7057
|
+
for (const e of extraction.entities) {
|
|
7058
|
+
const aliasTarget = await resolveAlias(client, e.name);
|
|
7059
|
+
const id = aliasTarget ?? entityId(e.name, e.type);
|
|
7060
|
+
const normalizedName = normalizeEntityName(e.name);
|
|
7061
|
+
try {
|
|
7062
|
+
const newProps = e.properties ?? {};
|
|
7063
|
+
let mergedPropsJson = JSON.stringify(newProps);
|
|
7064
|
+
if (newProps.provenance) {
|
|
7065
|
+
const existing = await client.execute({
|
|
7066
|
+
sql: "SELECT properties FROM entities WHERE id = ?",
|
|
7067
|
+
args: [aliasTarget ?? id]
|
|
7068
|
+
});
|
|
7069
|
+
const existingProps = existing.rows.length > 0 ? JSON.parse(String(existing.rows[0].properties ?? "{}")) : {};
|
|
7070
|
+
const existingProvenance = Array.isArray(existingProps.provenance) ? existingProps.provenance : existingProps.provenance ? [existingProps.provenance] : [];
|
|
7071
|
+
mergedPropsJson = JSON.stringify({
|
|
7072
|
+
...existingProps,
|
|
7073
|
+
...newProps,
|
|
7074
|
+
provenance: [...existingProvenance, newProps.provenance]
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
7077
|
+
if (aliasTarget) {
|
|
7078
|
+
await client.execute({
|
|
7079
|
+
sql: `UPDATE entities SET last_seen = ?, properties = ? WHERE id = ?`,
|
|
7080
|
+
args: [timestamp, mergedPropsJson, aliasTarget]
|
|
7081
|
+
});
|
|
7082
|
+
} else {
|
|
7083
|
+
await client.execute({
|
|
7084
|
+
sql: `INSERT INTO entities (id, name, type, first_seen, last_seen, properties)
|
|
7085
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
7086
|
+
ON CONFLICT(name, type) DO UPDATE SET last_seen = ?, properties = ?`,
|
|
7087
|
+
args: [id, normalizedName, e.type, timestamp, timestamp, mergedPropsJson, timestamp, mergedPropsJson]
|
|
7088
|
+
});
|
|
7089
|
+
}
|
|
7090
|
+
await client.execute({
|
|
7091
|
+
sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id)
|
|
7092
|
+
VALUES (?, ?)`,
|
|
7093
|
+
args: [id, memoryId]
|
|
7094
|
+
});
|
|
7095
|
+
entitiesStored++;
|
|
7096
|
+
} catch {
|
|
7097
|
+
}
|
|
6144
7098
|
}
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
);
|
|
7099
|
+
for (const r of extraction.relationships) {
|
|
7100
|
+
const sourceAlias = await resolveAlias(client, r.source);
|
|
7101
|
+
const targetAlias = await resolveAlias(client, r.target);
|
|
7102
|
+
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
7103
|
+
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
7104
|
+
const relId = crypto7.randomUUID().slice(0, 16);
|
|
7105
|
+
try {
|
|
7106
|
+
await client.execute({
|
|
7107
|
+
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
7108
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
7109
|
+
args: [sourceId, r.source, r.sourceType, timestamp, timestamp]
|
|
7110
|
+
});
|
|
7111
|
+
await client.execute({
|
|
7112
|
+
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
7113
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
7114
|
+
args: [targetId, r.target, r.targetType, timestamp, timestamp]
|
|
7115
|
+
});
|
|
7116
|
+
const relProps = r.properties ?? {};
|
|
7117
|
+
let relPropsJson = JSON.stringify(relProps);
|
|
7118
|
+
if (relProps.provenance) {
|
|
7119
|
+
const existingRels = await client.execute({
|
|
7120
|
+
sql: "SELECT properties FROM relationships WHERE source_entity_id = ? AND target_entity_id = ? AND type = ?",
|
|
7121
|
+
args: [sourceId, targetId, r.relationship]
|
|
7122
|
+
});
|
|
7123
|
+
const existingRelProps = existingRels.rows.length > 0 ? JSON.parse(String(existingRels.rows[0].properties ?? "{}")) : {};
|
|
7124
|
+
const existingProvenance = Array.isArray(existingRelProps.provenance) ? existingRelProps.provenance : existingRelProps.provenance ? [existingRelProps.provenance] : [];
|
|
7125
|
+
relPropsJson = JSON.stringify({
|
|
7126
|
+
...existingRelProps,
|
|
7127
|
+
...relProps,
|
|
7128
|
+
provenance: [...existingProvenance, relProps.provenance]
|
|
7129
|
+
});
|
|
7130
|
+
}
|
|
7131
|
+
await client.execute({
|
|
7132
|
+
sql: `INSERT INTO relationships (id, source_entity_id, target_entity_id, type, weight, timestamp, confidence, confidence_label, properties)
|
|
7133
|
+
VALUES (?, ?, ?, ?, 1.0, ?, ?, ?, ?)
|
|
7134
|
+
ON CONFLICT(source_entity_id, target_entity_id, type)
|
|
7135
|
+
DO UPDATE SET weight = MIN(weight + 0.1, 2.0), timestamp = ?,
|
|
7136
|
+
confidence = MAX(confidence, ?), confidence_label = ?, properties = ?`,
|
|
7137
|
+
args: [
|
|
7138
|
+
relId,
|
|
7139
|
+
sourceId,
|
|
7140
|
+
targetId,
|
|
7141
|
+
r.relationship,
|
|
7142
|
+
timestamp,
|
|
7143
|
+
r.confidence,
|
|
7144
|
+
r.confidenceLabel,
|
|
7145
|
+
relPropsJson,
|
|
7146
|
+
timestamp,
|
|
7147
|
+
r.confidence,
|
|
7148
|
+
r.confidenceLabel,
|
|
7149
|
+
relPropsJson
|
|
7150
|
+
]
|
|
7151
|
+
});
|
|
7152
|
+
const existingRel = await client.execute({
|
|
7153
|
+
sql: `SELECT id FROM relationships WHERE source_entity_id = ? AND target_entity_id = ? AND type = ?`,
|
|
7154
|
+
args: [sourceId, targetId, r.relationship]
|
|
7155
|
+
});
|
|
7156
|
+
const actualRelId = existingRel.rows[0]?.id ? String(existingRel.rows[0].id) : relId;
|
|
7157
|
+
await client.execute({
|
|
7158
|
+
sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id)
|
|
7159
|
+
VALUES (?, ?)`,
|
|
7160
|
+
args: [actualRelId, memoryId]
|
|
7161
|
+
});
|
|
7162
|
+
relationshipsStored++;
|
|
7163
|
+
} catch {
|
|
7164
|
+
}
|
|
6149
7165
|
}
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
7166
|
+
for (const h of extraction.hyperedges) {
|
|
7167
|
+
const hId = crypto7.randomUUID().slice(0, 16);
|
|
7168
|
+
try {
|
|
7169
|
+
await client.execute({
|
|
7170
|
+
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
7171
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
7172
|
+
args: [hId, h.label, h.relation, h.confidence, timestamp]
|
|
7173
|
+
});
|
|
7174
|
+
for (const entityName of h.entityNames) {
|
|
7175
|
+
const eType = extraction.entities.find((e) => e.name === entityName)?.type ?? "concept";
|
|
7176
|
+
const eId = entityId(entityName, eType);
|
|
7177
|
+
await client.execute({
|
|
7178
|
+
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
7179
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
7180
|
+
args: [eId, entityName, eType, timestamp, timestamp]
|
|
7181
|
+
});
|
|
7182
|
+
await client.execute({
|
|
7183
|
+
sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
|
|
7184
|
+
args: [hId, eId]
|
|
7185
|
+
});
|
|
7186
|
+
}
|
|
7187
|
+
} catch {
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
return { entitiesStored, relationshipsStored };
|
|
6154
7191
|
}
|
|
6155
|
-
async function
|
|
6156
|
-
const
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
7192
|
+
async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20251001") {
|
|
7193
|
+
const result = await client.execute({
|
|
7194
|
+
sql: `SELECT id, agent_id, raw_text, tool_name, timestamp, content_hash, graph_extracted_hash
|
|
7195
|
+
FROM memories
|
|
7196
|
+
WHERE (COALESCE(graph_extracted, 0) = 0
|
|
7197
|
+
OR (content_hash IS NOT NULL AND content_hash != COALESCE(graph_extracted_hash, '')))
|
|
7198
|
+
AND COALESCE(status, 'active') = 'active'
|
|
7199
|
+
AND LENGTH(raw_text) >= 50
|
|
7200
|
+
ORDER BY timestamp DESC
|
|
7201
|
+
LIMIT ?`,
|
|
7202
|
+
args: [batchSize]
|
|
7203
|
+
});
|
|
7204
|
+
if (result.rows.length === 0) {
|
|
7205
|
+
return { processed: 0, entities: 0, relationships: 0 };
|
|
6163
7206
|
}
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
const
|
|
6167
|
-
|
|
6168
|
-
const
|
|
6169
|
-
const
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
7207
|
+
let totalEntities = 0;
|
|
7208
|
+
let totalRelationships = 0;
|
|
7209
|
+
for (const row of result.rows) {
|
|
7210
|
+
const memoryId = String(row.id);
|
|
7211
|
+
const agentId = String(row.agent_id);
|
|
7212
|
+
const rawContent = String(row.raw_text);
|
|
7213
|
+
const toolName = String(row.tool_name ?? "");
|
|
7214
|
+
const timestamp = String(row.timestamp);
|
|
7215
|
+
let extractionContent = rawContent;
|
|
7216
|
+
let detectedFilePath;
|
|
7217
|
+
if (toolName === "Read") {
|
|
7218
|
+
const pathMatch = rawContent.match(READ_MEMORY_PATH_PATTERN);
|
|
7219
|
+
if (pathMatch) {
|
|
7220
|
+
detectedFilePath = pathMatch[1];
|
|
7221
|
+
extractionContent = rawContent.slice(pathMatch[0].length);
|
|
7222
|
+
}
|
|
7223
|
+
}
|
|
7224
|
+
try {
|
|
7225
|
+
const extraction = await extractFromMemory(extractionContent, agentId, model, detectedFilePath);
|
|
7226
|
+
if (extraction.entities.length > 0 || extraction.relationships.length > 0) {
|
|
7227
|
+
const stored = await storeExtraction(client, extraction, memoryId, timestamp);
|
|
7228
|
+
totalEntities += stored.entitiesStored;
|
|
7229
|
+
totalRelationships += stored.relationshipsStored;
|
|
7230
|
+
}
|
|
7231
|
+
const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
7232
|
+
await client.execute({
|
|
7233
|
+
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
7234
|
+
args: [contentHash, contentHash, memoryId]
|
|
7235
|
+
});
|
|
7236
|
+
} catch (err) {
|
|
7237
|
+
process.stderr.write(
|
|
7238
|
+
`[graph-rag] Batch extraction failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
|
|
7239
|
+
`
|
|
6173
7240
|
);
|
|
7241
|
+
await client.execute({
|
|
7242
|
+
sql: "UPDATE memories SET graph_extracted = 1 WHERE id = ?",
|
|
7243
|
+
args: [memoryId]
|
|
7244
|
+
});
|
|
6174
7245
|
}
|
|
6175
|
-
return vector;
|
|
6176
|
-
} finally {
|
|
6177
|
-
await context.dispose();
|
|
6178
|
-
await model.dispose();
|
|
6179
7246
|
}
|
|
7247
|
+
return { processed: result.rows.length, entities: totalEntities, relationships: totalRelationships };
|
|
6180
7248
|
}
|
|
6181
|
-
var
|
|
6182
|
-
|
|
7249
|
+
var EXTRACT_TOOL, READ_MEMORY_PATH_PATTERN;
|
|
7250
|
+
var init_graph_rag = __esm({
|
|
7251
|
+
"src/lib/graph-rag.ts"() {
|
|
6183
7252
|
"use strict";
|
|
6184
|
-
|
|
6185
|
-
|
|
7253
|
+
init_code_chunker();
|
|
7254
|
+
EXTRACT_TOOL = {
|
|
7255
|
+
name: "extract_entities_and_relationships",
|
|
7256
|
+
description: "Extract entities, relationships, and group connections from a memory record. Include confidence scoring.",
|
|
7257
|
+
input_schema: {
|
|
7258
|
+
type: "object",
|
|
7259
|
+
properties: {
|
|
7260
|
+
entities: {
|
|
7261
|
+
type: "array",
|
|
7262
|
+
items: {
|
|
7263
|
+
type: "object",
|
|
7264
|
+
properties: {
|
|
7265
|
+
name: { type: "string", description: "Entity name" },
|
|
7266
|
+
type: { type: "string", enum: ["person", "tool", "project", "decision", "concept", "file", "rationale", "product", "company", "location", "order", "supplier", "category"] }
|
|
7267
|
+
},
|
|
7268
|
+
required: ["name", "type"]
|
|
7269
|
+
}
|
|
7270
|
+
},
|
|
7271
|
+
relationships: {
|
|
7272
|
+
type: "array",
|
|
7273
|
+
items: {
|
|
7274
|
+
type: "object",
|
|
7275
|
+
properties: {
|
|
7276
|
+
source: { type: "string" },
|
|
7277
|
+
source_type: { type: "string" },
|
|
7278
|
+
target: { type: "string" },
|
|
7279
|
+
target_type: { type: "string" },
|
|
7280
|
+
relationship: {
|
|
7281
|
+
type: "string",
|
|
7282
|
+
enum: [
|
|
7283
|
+
// Engineering
|
|
7284
|
+
"implemented",
|
|
7285
|
+
"superseded_by",
|
|
7286
|
+
"depends_on",
|
|
7287
|
+
"blocked_by",
|
|
7288
|
+
"worked_on",
|
|
7289
|
+
"uses",
|
|
7290
|
+
"decided",
|
|
7291
|
+
"created",
|
|
7292
|
+
"fixed",
|
|
7293
|
+
"reviewed",
|
|
7294
|
+
"rationale_for",
|
|
7295
|
+
// Business / organizational
|
|
7296
|
+
"supplies",
|
|
7297
|
+
"ordered_from",
|
|
7298
|
+
"reports_to",
|
|
7299
|
+
"manages",
|
|
7300
|
+
"located_at",
|
|
7301
|
+
"ships_to",
|
|
7302
|
+
"categorized_as",
|
|
7303
|
+
"priced_at",
|
|
7304
|
+
"partnered_with",
|
|
7305
|
+
"contracted_by",
|
|
7306
|
+
// General purpose
|
|
7307
|
+
"related_to",
|
|
7308
|
+
"part_of",
|
|
7309
|
+
"owns",
|
|
7310
|
+
"communicates_with"
|
|
7311
|
+
]
|
|
7312
|
+
},
|
|
7313
|
+
confidence: { type: "number", description: "0.0-1.0. 1.0=explicitly stated, 0.6-0.9=inferred, 0.1-0.3=ambiguous" },
|
|
7314
|
+
confidence_label: { type: "string", enum: ["extracted", "inferred", "ambiguous"] }
|
|
7315
|
+
},
|
|
7316
|
+
required: ["source", "source_type", "target", "target_type", "relationship", "confidence", "confidence_label"]
|
|
7317
|
+
}
|
|
7318
|
+
},
|
|
7319
|
+
hyperedges: {
|
|
7320
|
+
type: "array",
|
|
7321
|
+
items: {
|
|
7322
|
+
type: "object",
|
|
7323
|
+
properties: {
|
|
7324
|
+
label: { type: "string", description: "Group label (e.g., 'auth flow files', 'v1.3 contributors')" },
|
|
7325
|
+
relation: { type: "string", description: "What connects them (e.g., 'part_of', 'contributed_to')" },
|
|
7326
|
+
entity_names: { type: "array", items: { type: "string" }, description: "3+ entity names in this group" },
|
|
7327
|
+
confidence: { type: "number" }
|
|
7328
|
+
},
|
|
7329
|
+
required: ["label", "relation", "entity_names", "confidence"]
|
|
7330
|
+
},
|
|
7331
|
+
description: "Group relationships connecting 3+ entities"
|
|
7332
|
+
}
|
|
7333
|
+
},
|
|
7334
|
+
required: ["entities", "relationships", "hyperedges"]
|
|
7335
|
+
}
|
|
7336
|
+
};
|
|
7337
|
+
READ_MEMORY_PATH_PATTERN = /^Read\s+(\S+\.(?:ts|tsx|js|jsx))\s*\n/;
|
|
6186
7338
|
}
|
|
6187
7339
|
});
|
|
6188
7340
|
|
|
6189
|
-
// src/lib/
|
|
6190
|
-
var
|
|
6191
|
-
__export(
|
|
6192
|
-
|
|
6193
|
-
createWikiClient: () => createWikiClient,
|
|
6194
|
-
getChatHistory: () => getChatHistory,
|
|
6195
|
-
listDocuments: () => listDocuments,
|
|
6196
|
-
listWorkspaces: () => listWorkspaces
|
|
7341
|
+
// src/lib/conversation-entity-extractor.ts
|
|
7342
|
+
var conversation_entity_extractor_exports = {};
|
|
7343
|
+
__export(conversation_entity_extractor_exports, {
|
|
7344
|
+
extractFromConversation: () => extractFromConversation
|
|
6197
7345
|
});
|
|
6198
|
-
async function
|
|
6199
|
-
const
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
const
|
|
6205
|
-
|
|
7346
|
+
async function extractFromConversation(msg, agentResponse, agentName, model = "claude-haiku-4-5-20251001") {
|
|
7347
|
+
const text = msg.text ?? "";
|
|
7348
|
+
if (text.length < 20) {
|
|
7349
|
+
return { entities: [], relationships: [], hyperedges: [] };
|
|
7350
|
+
}
|
|
7351
|
+
const senderName = msg.senderName ?? msg.senderId;
|
|
7352
|
+
const prompt = [
|
|
7353
|
+
`Extract entities and relationships from this conversation message.`,
|
|
7354
|
+
`Platform: ${msg.platform} | Sender: ${senderName} | Account: ${msg.accountId ?? "unknown"}`,
|
|
7355
|
+
``,
|
|
7356
|
+
`Focus on: people (from sender name, mentioned names), companies, products, topics discussed, locations mentioned.`,
|
|
7357
|
+
`Only extract what's clearly stated. For business conversations, capture: customer intent, product interest, service requests.`,
|
|
7358
|
+
``,
|
|
7359
|
+
`MESSAGE:`,
|
|
7360
|
+
text,
|
|
7361
|
+
agentResponse ? `
|
|
7362
|
+
AGENT RESPONSE (${agentName ?? "unknown"}):
|
|
7363
|
+
${agentResponse}` : ""
|
|
7364
|
+
].join("\n");
|
|
6206
7365
|
try {
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
7366
|
+
const Anthropic4 = (await import("@anthropic-ai/sdk")).default;
|
|
7367
|
+
const client = new Anthropic4();
|
|
7368
|
+
const response = await client.messages.create({
|
|
7369
|
+
model,
|
|
7370
|
+
max_tokens: 512,
|
|
7371
|
+
system: prompt,
|
|
7372
|
+
messages: [{ role: "user", content: "Extract entities and relationships from the message above." }],
|
|
7373
|
+
tools: [EXTRACT_TOOL],
|
|
7374
|
+
tool_choice: { type: "tool", name: "extract_entities_and_relationships" }
|
|
7375
|
+
});
|
|
7376
|
+
const toolBlock = response.content.find((b) => b.type === "tool_use");
|
|
7377
|
+
if (!toolBlock || toolBlock.type !== "tool_use") {
|
|
7378
|
+
return { entities: [], relationships: [], hyperedges: [] };
|
|
7379
|
+
}
|
|
7380
|
+
const input = toolBlock.input;
|
|
7381
|
+
const rawEntities = input.entities ?? [];
|
|
7382
|
+
const rawRels = input.relationships ?? [];
|
|
7383
|
+
const rawHyperedges = input.hyperedges ?? [];
|
|
7384
|
+
const entities = rawEntities.map((e) => ({ name: e.name ?? "", type: e.type ?? "concept" })).filter((e) => e.name.length > 0);
|
|
7385
|
+
const relationships = rawRels.map((r) => ({
|
|
7386
|
+
source: r.source ?? "",
|
|
7387
|
+
sourceType: r.source_type ?? "concept",
|
|
7388
|
+
target: r.target ?? "",
|
|
7389
|
+
targetType: r.target_type ?? "concept",
|
|
7390
|
+
relationship: r.relationship ?? "related_to",
|
|
7391
|
+
confidence: Number(r.confidence ?? 1),
|
|
7392
|
+
confidenceLabel: String(r.confidence_label ?? "extracted")
|
|
7393
|
+
})).filter((r) => r.source.length > 0 && r.target.length > 0);
|
|
7394
|
+
const hyperedges = rawHyperedges.map((h) => ({
|
|
7395
|
+
label: String(h.label ?? ""),
|
|
7396
|
+
relation: String(h.relation ?? ""),
|
|
7397
|
+
entityNames: h.entity_names ?? [],
|
|
7398
|
+
confidence: Number(h.confidence ?? 1)
|
|
7399
|
+
})).filter((h) => h.label.length > 0 && h.entityNames.length >= 3);
|
|
7400
|
+
const senderExists = entities.some(
|
|
7401
|
+
(e) => e.type === "person" && e.name.toLowerCase() === senderName.toLowerCase()
|
|
7402
|
+
);
|
|
7403
|
+
if (!senderExists) {
|
|
7404
|
+
entities.push({ name: senderName, type: "person" });
|
|
7405
|
+
}
|
|
7406
|
+
if (msg.accountId) {
|
|
7407
|
+
const hasCommunicates = relationships.some(
|
|
7408
|
+
(r) => r.relationship === "communicates_with" && r.source.toLowerCase() === senderName.toLowerCase()
|
|
7409
|
+
);
|
|
7410
|
+
if (!hasCommunicates) {
|
|
7411
|
+
relationships.push({
|
|
7412
|
+
source: senderName,
|
|
7413
|
+
sourceType: "person",
|
|
7414
|
+
target: msg.accountId,
|
|
7415
|
+
targetType: "person",
|
|
7416
|
+
relationship: "communicates_with",
|
|
7417
|
+
confidence: 1,
|
|
7418
|
+
confidenceLabel: "extracted"
|
|
6226
7419
|
});
|
|
6227
|
-
} finally {
|
|
6228
|
-
clearTimeout(retryTimeout);
|
|
6229
7420
|
}
|
|
6230
7421
|
}
|
|
6231
|
-
|
|
6232
|
-
|
|
7422
|
+
const provenance = {
|
|
7423
|
+
source: "gateway-conversation",
|
|
7424
|
+
platform: msg.platform,
|
|
7425
|
+
senderId: msg.senderId,
|
|
7426
|
+
senderName: msg.senderName,
|
|
7427
|
+
accountId: msg.accountId,
|
|
7428
|
+
messageId: msg.messageId,
|
|
7429
|
+
timestamp: msg.timestamp,
|
|
7430
|
+
agentName,
|
|
7431
|
+
extractionModel: model,
|
|
7432
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7433
|
+
};
|
|
7434
|
+
for (const entity of entities) {
|
|
7435
|
+
entity.properties = { ...entity.properties ?? {}, provenance };
|
|
6233
7436
|
}
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
7437
|
+
for (const rel of relationships) {
|
|
7438
|
+
rel.properties = { ...rel.properties ?? {}, provenance };
|
|
7439
|
+
}
|
|
7440
|
+
return { entities, relationships, hyperedges };
|
|
7441
|
+
} catch (err) {
|
|
7442
|
+
process.stderr.write(
|
|
7443
|
+
`[conversation-entity-extractor] Extraction failed: ${err instanceof Error ? err.message : String(err)}
|
|
7444
|
+
`
|
|
7445
|
+
);
|
|
7446
|
+
return { entities: [], relationships: [], hyperedges: [] };
|
|
6237
7447
|
}
|
|
6238
7448
|
}
|
|
6239
|
-
|
|
7449
|
+
var init_conversation_entity_extractor = __esm({
|
|
7450
|
+
"src/lib/conversation-entity-extractor.ts"() {
|
|
7451
|
+
"use strict";
|
|
7452
|
+
init_graph_rag();
|
|
7453
|
+
}
|
|
7454
|
+
});
|
|
7455
|
+
|
|
7456
|
+
// src/lib/conversation-wiki-populator.ts
|
|
7457
|
+
var conversation_wiki_populator_exports = {};
|
|
7458
|
+
__export(conversation_wiki_populator_exports, {
|
|
7459
|
+
populateWikiFromExtraction: () => populateWikiFromExtraction
|
|
7460
|
+
});
|
|
7461
|
+
async function populateWikiFromExtraction(extraction, msg, agentResponse, agentName) {
|
|
7462
|
+
const { createWikiClient: createWikiClient2, chatInWorkspace: chatInWorkspace2, listWorkspaces: listWorkspaces2 } = await Promise.resolve().then(() => (init_wiki_client(), wiki_client_exports));
|
|
7463
|
+
const client = await createWikiClient2();
|
|
7464
|
+
if (!client) {
|
|
7465
|
+
return { created: 0, updated: 0, skipped: extraction.entities.length };
|
|
7466
|
+
}
|
|
7467
|
+
let availableSlugs;
|
|
6240
7468
|
try {
|
|
6241
|
-
const
|
|
6242
|
-
|
|
6243
|
-
const res = await fetch(`${LOCAL_WIKI_URL}/api/v1/auth`, { signal: controller.signal });
|
|
6244
|
-
clearTimeout(timeout);
|
|
6245
|
-
if (res.ok || res.status === 401 || res.status === 403) {
|
|
6246
|
-
return LOCAL_WIKI_URL;
|
|
6247
|
-
}
|
|
7469
|
+
const workspaces = await listWorkspaces2(client);
|
|
7470
|
+
availableSlugs = new Set(workspaces.map((w) => w.slug));
|
|
6248
7471
|
} catch {
|
|
6249
|
-
|
|
6250
|
-
|
|
7472
|
+
return { created: 0, updated: 0, skipped: extraction.entities.length };
|
|
7473
|
+
}
|
|
7474
|
+
let created = 0;
|
|
7475
|
+
let updated = 0;
|
|
7476
|
+
let skipped = 0;
|
|
7477
|
+
let apiCalls = 0;
|
|
7478
|
+
const candidates = extraction.entities.filter((e) => {
|
|
7479
|
+
if (!WIKI_ENTITY_TYPES.has(e.type)) {
|
|
7480
|
+
skipped++;
|
|
7481
|
+
return false;
|
|
7482
|
+
}
|
|
7483
|
+
if (e.name.length < MIN_NAME_LENGTH) {
|
|
7484
|
+
skipped++;
|
|
7485
|
+
return false;
|
|
7486
|
+
}
|
|
7487
|
+
if (SKIP_NAMES.has(e.name.toLowerCase())) {
|
|
7488
|
+
skipped++;
|
|
7489
|
+
return false;
|
|
7490
|
+
}
|
|
7491
|
+
return true;
|
|
7492
|
+
});
|
|
7493
|
+
const senderName = msg.senderName ?? msg.senderId;
|
|
7494
|
+
const timestamp = msg.timestamp;
|
|
7495
|
+
const platform = msg.platform;
|
|
7496
|
+
const accountId = msg.accountId ?? "unknown";
|
|
7497
|
+
const messageSummary = (msg.text ?? "").slice(0, 200);
|
|
7498
|
+
const responseSummary = agentResponse ? agentResponse.slice(0, 200) : void 0;
|
|
7499
|
+
for (const entity of candidates) {
|
|
7500
|
+
if (apiCalls >= MAX_WIKI_CALLS) {
|
|
7501
|
+
skipped += candidates.length - (created + updated + (skipped - (extraction.entities.length - candidates.length)));
|
|
7502
|
+
break;
|
|
7503
|
+
}
|
|
7504
|
+
const workspace = WORKSPACE_MAP[entity.type] ?? DEFAULT_WORKSPACE;
|
|
7505
|
+
if (!availableSlugs.has(workspace)) {
|
|
7506
|
+
skipped++;
|
|
7507
|
+
continue;
|
|
7508
|
+
}
|
|
6251
7509
|
try {
|
|
6252
|
-
const
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
7510
|
+
const content = buildWikiContent(
|
|
7511
|
+
entity.name,
|
|
7512
|
+
entity.type,
|
|
7513
|
+
timestamp,
|
|
7514
|
+
platform,
|
|
7515
|
+
accountId,
|
|
7516
|
+
senderName,
|
|
7517
|
+
messageSummary,
|
|
7518
|
+
agentName,
|
|
7519
|
+
responseSummary
|
|
7520
|
+
);
|
|
7521
|
+
await chatInWorkspace2(client, workspace, content, "chat");
|
|
7522
|
+
apiCalls++;
|
|
7523
|
+
updated++;
|
|
7524
|
+
} catch (err) {
|
|
7525
|
+
process.stderr.write(
|
|
7526
|
+
`[wiki-populator] Failed for entity "${entity.name}": ${err instanceof Error ? err.message : String(err)}
|
|
7527
|
+
`
|
|
7528
|
+
);
|
|
7529
|
+
skipped++;
|
|
6260
7530
|
}
|
|
6261
7531
|
}
|
|
6262
|
-
return
|
|
6263
|
-
}
|
|
6264
|
-
async function createWikiClient() {
|
|
6265
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6266
|
-
const config2 = await loadConfig2();
|
|
6267
|
-
const baseUrl = await resolveWikiUrl(config2.wikiUrl);
|
|
6268
|
-
if (!baseUrl) return null;
|
|
6269
|
-
return {
|
|
6270
|
-
baseUrl,
|
|
6271
|
-
apiKey: config2.wikiApiKey
|
|
6272
|
-
};
|
|
6273
|
-
}
|
|
6274
|
-
async function listWorkspaces(client) {
|
|
6275
|
-
const data = await wikiFetch(client, "/workspaces");
|
|
6276
|
-
return (data.workspaces ?? []).map((w) => ({
|
|
6277
|
-
slug: w.slug,
|
|
6278
|
-
name: w.name,
|
|
6279
|
-
createdAt: w.createdAt ?? "",
|
|
6280
|
-
threads: w.threads ?? []
|
|
6281
|
-
}));
|
|
6282
|
-
}
|
|
6283
|
-
async function listDocuments(client, workspaceSlug) {
|
|
6284
|
-
const data = await wikiFetch(
|
|
6285
|
-
client,
|
|
6286
|
-
`/workspace/${encodeURIComponent(workspaceSlug)}`
|
|
6287
|
-
);
|
|
6288
|
-
const docs = Array.isArray(data.workspace) ? data.workspace[0]?.documents ?? [] : [];
|
|
6289
|
-
return docs.map((d) => ({
|
|
6290
|
-
filename: d.filename ?? "untitled",
|
|
6291
|
-
docpath: d.docpath,
|
|
6292
|
-
workspace: workspaceSlug,
|
|
6293
|
-
pinned: d.pinned ?? false
|
|
6294
|
-
}));
|
|
6295
|
-
}
|
|
6296
|
-
async function chatInWorkspace(client, workspaceSlug, message, mode = "chat") {
|
|
6297
|
-
const data = await wikiFetch(
|
|
6298
|
-
client,
|
|
6299
|
-
`/workspace/${encodeURIComponent(workspaceSlug)}/chat`,
|
|
6300
|
-
"POST",
|
|
6301
|
-
{ message, mode }
|
|
6302
|
-
);
|
|
6303
|
-
return {
|
|
6304
|
-
id: data.id ?? "",
|
|
6305
|
-
type: data.type ?? "textResponse",
|
|
6306
|
-
textResponse: data.textResponse ?? "",
|
|
6307
|
-
sources: data.sources ?? [],
|
|
6308
|
-
error: data.error ?? null
|
|
6309
|
-
};
|
|
7532
|
+
return { created, updated, skipped };
|
|
6310
7533
|
}
|
|
6311
|
-
|
|
6312
|
-
const
|
|
6313
|
-
|
|
6314
|
-
|
|
7534
|
+
function buildWikiContent(entityName, entityType, timestamp, platform, accountId, senderName, messageSummary, agentName, responseSummary) {
|
|
7535
|
+
const lines = [
|
|
7536
|
+
`Update knowledge about ${entityName} (${entityType}):`,
|
|
7537
|
+
``,
|
|
7538
|
+
`## [${timestamp}] via ${platform}/${accountId}`,
|
|
7539
|
+
`${senderName}: ${messageSummary}`
|
|
7540
|
+
];
|
|
7541
|
+
if (agentName && responseSummary) {
|
|
7542
|
+
lines.push(`Agent (${agentName}): ${responseSummary}`);
|
|
7543
|
+
}
|
|
7544
|
+
lines.push(
|
|
7545
|
+
``,
|
|
7546
|
+
`Source: ${platform} | Account: ${accountId} | Timestamp: ${timestamp}`
|
|
6315
7547
|
);
|
|
6316
|
-
return
|
|
6317
|
-
role: h.role === "user" ? "user" : "assistant",
|
|
6318
|
-
content: h.content,
|
|
6319
|
-
sentAt: h.sentAt ?? 0
|
|
6320
|
-
}));
|
|
7548
|
+
return lines.join("\n");
|
|
6321
7549
|
}
|
|
6322
|
-
var
|
|
6323
|
-
var
|
|
6324
|
-
"src/lib/wiki-
|
|
7550
|
+
var MAX_WIKI_CALLS, WIKI_ENTITY_TYPES, WORKSPACE_MAP, DEFAULT_WORKSPACE, MIN_NAME_LENGTH, SKIP_NAMES;
|
|
7551
|
+
var init_conversation_wiki_populator = __esm({
|
|
7552
|
+
"src/lib/conversation-wiki-populator.ts"() {
|
|
6325
7553
|
"use strict";
|
|
6326
|
-
|
|
6327
|
-
|
|
7554
|
+
MAX_WIKI_CALLS = 3;
|
|
7555
|
+
WIKI_ENTITY_TYPES = /* @__PURE__ */ new Set(["person", "company", "product"]);
|
|
7556
|
+
WORKSPACE_MAP = {
|
|
7557
|
+
person: "contacts",
|
|
7558
|
+
company: "companies",
|
|
7559
|
+
product: "products"
|
|
7560
|
+
};
|
|
7561
|
+
DEFAULT_WORKSPACE = "conversations";
|
|
7562
|
+
MIN_NAME_LENGTH = 3;
|
|
7563
|
+
SKIP_NAMES = /* @__PURE__ */ new Set(["unknown", "none", "n/a", "null", "undefined"]);
|
|
6328
7564
|
}
|
|
6329
7565
|
});
|
|
6330
7566
|
|
|
@@ -6399,10 +7635,10 @@ __export(messaging_exports, {
|
|
|
6399
7635
|
sendMessage: () => sendMessage,
|
|
6400
7636
|
setWsClientSend: () => setWsClientSend
|
|
6401
7637
|
});
|
|
6402
|
-
import
|
|
7638
|
+
import crypto9 from "crypto";
|
|
6403
7639
|
function generateUlid() {
|
|
6404
7640
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
6405
|
-
const random =
|
|
7641
|
+
const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
|
|
6406
7642
|
return (timestamp + random).toUpperCase();
|
|
6407
7643
|
}
|
|
6408
7644
|
function rowToMessage(row) {
|
|
@@ -6506,7 +7742,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
6506
7742
|
try {
|
|
6507
7743
|
const exeSession = resolveExeSession();
|
|
6508
7744
|
if (!exeSession) {
|
|
6509
|
-
throw new Error("No
|
|
7745
|
+
throw new Error("No coordinator session found");
|
|
6510
7746
|
}
|
|
6511
7747
|
const sessionName = employeeSessionName(targetAgent, exeSession);
|
|
6512
7748
|
if (!isEmployeeAlive(sessionName)) {
|
|
@@ -7859,7 +9095,11 @@ function composeHooks(...pipelines) {
|
|
|
7859
9095
|
};
|
|
7860
9096
|
}
|
|
7861
9097
|
|
|
9098
|
+
// src/runtime/orchestrator.ts
|
|
9099
|
+
init_employees();
|
|
9100
|
+
|
|
7862
9101
|
// src/lib/task-router.ts
|
|
9102
|
+
init_employees();
|
|
7863
9103
|
import { randomUUID } from "crypto";
|
|
7864
9104
|
var DEFAULT_BLOOM_CONFIG = {
|
|
7865
9105
|
complexityToTier: {
|
|
@@ -7917,7 +9157,7 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
|
7917
9157
|
return { agentId, score: results.length / 5 };
|
|
7918
9158
|
}
|
|
7919
9159
|
async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
|
|
7920
|
-
let specialists = employees.filter((e) => e.
|
|
9160
|
+
let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
|
|
7921
9161
|
if (specialists.length === 0) {
|
|
7922
9162
|
throw new Error(
|
|
7923
9163
|
"No specialist employees available. Create one with /exe-new-employee."
|
|
@@ -7994,7 +9234,7 @@ ${task.context}`,
|
|
|
7994
9234
|
await createTaskCore({
|
|
7995
9235
|
title: task.title,
|
|
7996
9236
|
assignedTo: targetEmployee.name,
|
|
7997
|
-
assignedBy:
|
|
9237
|
+
assignedBy: getCoordinatorName(),
|
|
7998
9238
|
projectName: task.projectName,
|
|
7999
9239
|
priority: task.priority,
|
|
8000
9240
|
context: task.context,
|
|
@@ -8099,15 +9339,16 @@ ${task.context}`,
|
|
|
8099
9339
|
const client = getClient2();
|
|
8100
9340
|
const autoApproved = [];
|
|
8101
9341
|
const needsReview = [];
|
|
9342
|
+
const coordinatorName = getCoordinatorName();
|
|
8102
9343
|
const rScope = sessionScopeFilter();
|
|
8103
9344
|
const reviews = await client.execute({
|
|
8104
9345
|
sql: `SELECT id, title, assigned_to, priority, result, task_file FROM tasks
|
|
8105
|
-
WHERE assigned_to = 'exe'
|
|
9346
|
+
WHERE (assigned_to = ? OR assigned_to = 'exe')
|
|
8106
9347
|
AND status IN ('open', 'in_progress')
|
|
8107
9348
|
AND task_file LIKE '%review-%'${rScope.sql}
|
|
8108
9349
|
ORDER BY priority ASC
|
|
8109
9350
|
LIMIT 20`,
|
|
8110
|
-
args: [...rScope.args]
|
|
9351
|
+
args: [coordinatorName, ...rScope.args]
|
|
8111
9352
|
});
|
|
8112
9353
|
for (const row of reviews.rows) {
|
|
8113
9354
|
const item = {
|
|
@@ -9088,11 +10329,11 @@ init_crm_bridge();
|
|
|
9088
10329
|
|
|
9089
10330
|
// src/lib/pipeline-router.ts
|
|
9090
10331
|
init_database();
|
|
9091
|
-
import
|
|
10332
|
+
import crypto8 from "crypto";
|
|
9092
10333
|
async function sinkConversationStore(msg, agentResponse, agentName) {
|
|
9093
10334
|
try {
|
|
9094
10335
|
const client = getClient();
|
|
9095
|
-
const id =
|
|
10336
|
+
const id = crypto8.randomUUID();
|
|
9096
10337
|
const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
|
|
9097
10338
|
await client.execute({
|
|
9098
10339
|
sql: `INSERT INTO conversations
|
|
@@ -9142,7 +10383,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
|
|
|
9142
10383
|
].filter(Boolean).join("\n");
|
|
9143
10384
|
const vector = await embed2(rawText);
|
|
9144
10385
|
await writeMemory2({
|
|
9145
|
-
id:
|
|
10386
|
+
id: crypto8.randomUUID(),
|
|
9146
10387
|
agent_id: agentName ?? "gateway",
|
|
9147
10388
|
agent_role: "gateway",
|
|
9148
10389
|
session_id: `gateway-${msg.platform}`,
|
|
@@ -9223,9 +10464,37 @@ Agent response: ${agentResponse}` : ""
|
|
|
9223
10464
|
return false;
|
|
9224
10465
|
}
|
|
9225
10466
|
}
|
|
10467
|
+
async function sinkEntityExtraction(msg, agentResponse, agentName) {
|
|
10468
|
+
try {
|
|
10469
|
+
const { extractFromConversation: extractFromConversation2 } = await Promise.resolve().then(() => (init_conversation_entity_extractor(), conversation_entity_extractor_exports));
|
|
10470
|
+
const { storeExtraction: storeExtraction2 } = await Promise.resolve().then(() => (init_graph_rag(), graph_rag_exports));
|
|
10471
|
+
if ((msg.text?.length ?? 0) < 20) return false;
|
|
10472
|
+
if (msg.isHistorical) return false;
|
|
10473
|
+
const extraction = await extractFromConversation2(msg, agentResponse, agentName);
|
|
10474
|
+
if (extraction.entities.length === 0) return false;
|
|
10475
|
+
const client = getClient();
|
|
10476
|
+
const memoryId = `conv:${msg.messageId}`;
|
|
10477
|
+
await storeExtraction2(client, extraction, memoryId, msg.timestamp);
|
|
10478
|
+
Promise.resolve().then(() => (init_conversation_wiki_populator(), conversation_wiki_populator_exports)).then(
|
|
10479
|
+
({ populateWikiFromExtraction: populateWikiFromExtraction2 }) => populateWikiFromExtraction2(extraction, msg, agentResponse, agentName)
|
|
10480
|
+
).catch((err) => {
|
|
10481
|
+
process.stderr.write(
|
|
10482
|
+
`[pipeline] wiki-population error: ${err instanceof Error ? err.message : String(err)}
|
|
10483
|
+
`
|
|
10484
|
+
);
|
|
10485
|
+
});
|
|
10486
|
+
return true;
|
|
10487
|
+
} catch (err) {
|
|
10488
|
+
process.stderr.write(
|
|
10489
|
+
`[pipeline] entity-extraction-sink error: ${err instanceof Error ? err.message : String(err)}
|
|
10490
|
+
`
|
|
10491
|
+
);
|
|
10492
|
+
return false;
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
9226
10495
|
async function ingest(msg, agentResponse, agentName) {
|
|
9227
10496
|
const errors = [];
|
|
9228
|
-
const [conversationStored, memorySunk, crmSunk, wikiSunk] = await Promise.all([
|
|
10497
|
+
const [conversationStored, memorySunk, crmSunk, wikiSunk, entityExtracted] = await Promise.all([
|
|
9229
10498
|
sinkConversationStore(msg, agentResponse, agentName).catch((e) => {
|
|
9230
10499
|
errors.push(`conversation: ${e}`);
|
|
9231
10500
|
return false;
|
|
@@ -9241,6 +10510,10 @@ async function ingest(msg, agentResponse, agentName) {
|
|
|
9241
10510
|
sinkWiki(msg, agentResponse).catch((e) => {
|
|
9242
10511
|
errors.push(`wiki: ${e}`);
|
|
9243
10512
|
return false;
|
|
10513
|
+
}),
|
|
10514
|
+
sinkEntityExtraction(msg, agentResponse, agentName).catch((e) => {
|
|
10515
|
+
errors.push(`entity-extraction: ${e}`);
|
|
10516
|
+
return false;
|
|
9244
10517
|
})
|
|
9245
10518
|
]);
|
|
9246
10519
|
if (errors.length > 0) {
|
|
@@ -9249,7 +10522,7 @@ async function ingest(msg, agentResponse, agentName) {
|
|
|
9249
10522
|
`
|
|
9250
10523
|
);
|
|
9251
10524
|
}
|
|
9252
|
-
return { conversationStored, memorySunk, crmSunk, wikiSunk, errors };
|
|
10525
|
+
return { conversationStored, memorySunk, crmSunk, wikiSunk, entityExtracted, errors };
|
|
9253
10526
|
}
|
|
9254
10527
|
|
|
9255
10528
|
// src/gateway/gateway.ts
|
|
@@ -9535,7 +10808,7 @@ Your personality:
|
|
|
9535
10808
|
- If you don't know something, say so and suggest who might
|
|
9536
10809
|
|
|
9537
10810
|
Your tools:
|
|
9538
|
-
- ask_team_memory: Query any employee's memories
|
|
10811
|
+
- ask_team_memory: Query any employee's memories by their configured name
|
|
9539
10812
|
- recall_my_memory: Search your own past conversations with the founder
|
|
9540
10813
|
- list_tasks: See what's in progress across the org
|
|
9541
10814
|
- get_session_context: Get context from a specific session
|
|
@@ -9676,7 +10949,7 @@ function buildExecAssistantTools() {
|
|
|
9676
10949
|
properties: {
|
|
9677
10950
|
team_member: {
|
|
9678
10951
|
type: "string",
|
|
9679
|
-
description: "
|
|
10952
|
+
description: "Configured name of the team member"
|
|
9680
10953
|
},
|
|
9681
10954
|
query: { type: "string", description: "What to search for" },
|
|
9682
10955
|
project_name: {
|
|
@@ -9855,7 +11128,7 @@ function buildExecAssistantTools() {
|
|
|
9855
11128
|
},
|
|
9856
11129
|
{
|
|
9857
11130
|
name: "close_task",
|
|
9858
|
-
description: "Reviewer-only: finalize a task after review. Only
|
|
11131
|
+
description: "Reviewer-only: finalize a task after review. Only coordinators/reviewers can use this.",
|
|
9859
11132
|
input_schema: {
|
|
9860
11133
|
type: "object",
|
|
9861
11134
|
properties: {
|
|
@@ -10926,7 +12199,7 @@ ${val}` : val;
|
|
|
10926
12199
|
}
|
|
10927
12200
|
if (envelope.receiptMessage) {
|
|
10928
12201
|
const rcpt = envelope.receiptMessage;
|
|
10929
|
-
for (const
|
|
12202
|
+
for (const ts2 of rcpt.timestamps) {
|
|
10930
12203
|
const normalized2 = {
|
|
10931
12204
|
messageId: randomUUID10(),
|
|
10932
12205
|
platform: "signal",
|
|
@@ -10941,7 +12214,7 @@ ${val}` : val;
|
|
|
10941
12214
|
raw: payload,
|
|
10942
12215
|
dataCategory: "read_receipt",
|
|
10943
12216
|
readReceipt: {
|
|
10944
|
-
messageId: String(
|
|
12217
|
+
messageId: String(ts2),
|
|
10945
12218
|
status: rcpt.type === "read" ? "read" : "delivered",
|
|
10946
12219
|
timestamp: new Date(
|
|
10947
12220
|
envelope.timestamp ?? Date.now()
|
|
@@ -12602,7 +13875,9 @@ async function executeSendWhatsapp(params) {
|
|
|
12602
13875
|
}
|
|
12603
13876
|
async function executeSendMessage(params) {
|
|
12604
13877
|
const { sendMessage: sendMessage2 } = await Promise.resolve().then(() => (init_messaging(), messaging_exports));
|
|
12605
|
-
const
|
|
13878
|
+
const { getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
13879
|
+
const rawTo = params.to ?? params.recipient;
|
|
13880
|
+
const to = rawTo === "coordinator" ? getCoordinatorName2() : rawTo;
|
|
12606
13881
|
const content = params.message ?? params.content ?? params.text;
|
|
12607
13882
|
if (!to || !content)
|
|
12608
13883
|
throw new Error("send_message requires 'to' and 'message' params");
|
|
@@ -12615,9 +13890,11 @@ async function executeSendMessage(params) {
|
|
|
12615
13890
|
}
|
|
12616
13891
|
async function executeCreateTask(params) {
|
|
12617
13892
|
const { createTask: createTask2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
13893
|
+
const { getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
13894
|
+
const rawAssignedTo = params.assigned_to;
|
|
12618
13895
|
await createTask2({
|
|
12619
13896
|
title: params.title ?? "Triggered task",
|
|
12620
|
-
assignedTo:
|
|
13897
|
+
assignedTo: !rawAssignedTo || rawAssignedTo === "coordinator" ? getCoordinatorName2() : rawAssignedTo,
|
|
12621
13898
|
assignedBy: "trigger-engine",
|
|
12622
13899
|
projectName: params.project ?? "exe-os",
|
|
12623
13900
|
priority: params.priority ?? "p1",
|
|
@@ -12693,10 +13970,11 @@ ${content}` : existingContent + "\n\n" + content,
|
|
|
12693
13970
|
}
|
|
12694
13971
|
async function routeToApproval(action, resolvedParams, triggerName) {
|
|
12695
13972
|
const { createTask: createTask2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
13973
|
+
const { getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
12696
13974
|
const actionSummary = action.type === "send_whatsapp" ? `Send WhatsApp to ${resolvedParams.to ?? resolvedParams.recipient ?? "unknown"}: "${(resolvedParams.message ?? resolvedParams.text ?? "").slice(0, 100)}"` : `${action.type}: ${JSON.stringify(resolvedParams).slice(0, 200)}`;
|
|
12697
13975
|
await createTask2({
|
|
12698
13976
|
title: `[Approval Required] ${triggerName}: ${action.type}`,
|
|
12699
|
-
assignedTo:
|
|
13977
|
+
assignedTo: getCoordinatorName2(),
|
|
12700
13978
|
assignedBy: "trigger-engine",
|
|
12701
13979
|
projectName: resolvedParams.project ?? "exe-os",
|
|
12702
13980
|
priority: "p1",
|