@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
|
@@ -148,7 +148,7 @@ function wrapWithRetry(client) {
|
|
|
148
148
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
149
149
|
}
|
|
150
150
|
if (prop === "batch") {
|
|
151
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
151
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
152
152
|
}
|
|
153
153
|
return Reflect.get(target, prop, receiver);
|
|
154
154
|
}
|
|
@@ -164,6 +164,300 @@ var init_db_retry = __esm({
|
|
|
164
164
|
}
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
// src/lib/config.ts
|
|
168
|
+
var config_exports = {};
|
|
169
|
+
__export(config_exports, {
|
|
170
|
+
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
171
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
172
|
+
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
173
|
+
DB_PATH: () => DB_PATH,
|
|
174
|
+
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
175
|
+
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
176
|
+
MODELS_DIR: () => MODELS_DIR,
|
|
177
|
+
loadConfig: () => loadConfig,
|
|
178
|
+
loadConfigFrom: () => loadConfigFrom,
|
|
179
|
+
loadConfigSync: () => loadConfigSync,
|
|
180
|
+
migrateConfig: () => migrateConfig,
|
|
181
|
+
saveConfig: () => saveConfig
|
|
182
|
+
});
|
|
183
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
184
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
|
|
185
|
+
import path3 from "path";
|
|
186
|
+
import os from "os";
|
|
187
|
+
function resolveDataDir() {
|
|
188
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
189
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
190
|
+
const newDir = path3.join(os.homedir(), ".exe-os");
|
|
191
|
+
const legacyDir = path3.join(os.homedir(), ".exe-mem");
|
|
192
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
193
|
+
try {
|
|
194
|
+
renameSync(legacyDir, newDir);
|
|
195
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
196
|
+
`);
|
|
197
|
+
} catch {
|
|
198
|
+
return legacyDir;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return newDir;
|
|
202
|
+
}
|
|
203
|
+
function migrateLegacyConfig(raw) {
|
|
204
|
+
if ("r2" in raw) {
|
|
205
|
+
process.stderr.write(
|
|
206
|
+
"[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"
|
|
207
|
+
);
|
|
208
|
+
delete raw.r2;
|
|
209
|
+
}
|
|
210
|
+
if ("syncIntervalMs" in raw) {
|
|
211
|
+
delete raw.syncIntervalMs;
|
|
212
|
+
}
|
|
213
|
+
return raw;
|
|
214
|
+
}
|
|
215
|
+
function migrateConfig(raw) {
|
|
216
|
+
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
217
|
+
let currentVersion = fromVersion;
|
|
218
|
+
let migrated = false;
|
|
219
|
+
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
220
|
+
return { config: raw, migrated: false, fromVersion };
|
|
221
|
+
}
|
|
222
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
223
|
+
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
224
|
+
raw = migration.migrate(raw);
|
|
225
|
+
currentVersion = migration.to;
|
|
226
|
+
migrated = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { config: raw, migrated, fromVersion };
|
|
230
|
+
}
|
|
231
|
+
function normalizeScalingRoadmap(raw) {
|
|
232
|
+
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
233
|
+
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
234
|
+
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
235
|
+
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
236
|
+
userAuto.enabled = raw.rerankerEnabled;
|
|
237
|
+
}
|
|
238
|
+
raw.scalingRoadmap = {
|
|
239
|
+
...userRoadmap,
|
|
240
|
+
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function normalizeSessionLifecycle(raw) {
|
|
244
|
+
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
245
|
+
const userSL = raw.sessionLifecycle ?? {};
|
|
246
|
+
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
247
|
+
}
|
|
248
|
+
function normalizeAutoUpdate(raw) {
|
|
249
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
250
|
+
const userAU = raw.autoUpdate ?? {};
|
|
251
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
252
|
+
}
|
|
253
|
+
async function loadConfig() {
|
|
254
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
255
|
+
await mkdir(dir, { recursive: true });
|
|
256
|
+
const configPath = path3.join(dir, "config.json");
|
|
257
|
+
if (!existsSync2(configPath)) {
|
|
258
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
259
|
+
}
|
|
260
|
+
const raw = await readFile(configPath, "utf-8");
|
|
261
|
+
try {
|
|
262
|
+
let parsed = JSON.parse(raw);
|
|
263
|
+
parsed = migrateLegacyConfig(parsed);
|
|
264
|
+
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
265
|
+
if (migrated) {
|
|
266
|
+
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
267
|
+
`);
|
|
268
|
+
try {
|
|
269
|
+
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
270
|
+
} catch {
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
274
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
275
|
+
normalizeAutoUpdate(migratedCfg);
|
|
276
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
277
|
+
if (config.dbPath.startsWith("~")) {
|
|
278
|
+
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
279
|
+
}
|
|
280
|
+
return config;
|
|
281
|
+
} catch {
|
|
282
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function loadConfigSync() {
|
|
286
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
287
|
+
const configPath = path3.join(dir, "config.json");
|
|
288
|
+
if (!existsSync2(configPath)) {
|
|
289
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
293
|
+
let parsed = JSON.parse(raw);
|
|
294
|
+
parsed = migrateLegacyConfig(parsed);
|
|
295
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
296
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
297
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
298
|
+
normalizeAutoUpdate(migratedCfg);
|
|
299
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
300
|
+
} catch {
|
|
301
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function saveConfig(config) {
|
|
305
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
306
|
+
await mkdir(dir, { recursive: true });
|
|
307
|
+
const configPath = path3.join(dir, "config.json");
|
|
308
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
309
|
+
if (config.cloud?.apiKey) {
|
|
310
|
+
await chmod(configPath, 384);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function loadConfigFrom(configPath) {
|
|
314
|
+
const raw = await readFile(configPath, "utf-8");
|
|
315
|
+
try {
|
|
316
|
+
let parsed = JSON.parse(raw);
|
|
317
|
+
parsed = migrateLegacyConfig(parsed);
|
|
318
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
319
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
320
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
321
|
+
normalizeAutoUpdate(migratedCfg);
|
|
322
|
+
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
323
|
+
} catch {
|
|
324
|
+
return { ...DEFAULT_CONFIG };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
328
|
+
var init_config = __esm({
|
|
329
|
+
"src/lib/config.ts"() {
|
|
330
|
+
"use strict";
|
|
331
|
+
EXE_AI_DIR = resolveDataDir();
|
|
332
|
+
DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
|
|
333
|
+
MODELS_DIR = path3.join(EXE_AI_DIR, "models");
|
|
334
|
+
CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
|
|
335
|
+
LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
|
|
336
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
337
|
+
DEFAULT_CONFIG = {
|
|
338
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
339
|
+
dbPath: DB_PATH,
|
|
340
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
341
|
+
embeddingDim: 1024,
|
|
342
|
+
batchSize: 20,
|
|
343
|
+
flushIntervalMs: 1e4,
|
|
344
|
+
autoIngestion: true,
|
|
345
|
+
autoRetrieval: true,
|
|
346
|
+
searchMode: "hybrid",
|
|
347
|
+
hookSearchMode: "hybrid",
|
|
348
|
+
fileGrepEnabled: true,
|
|
349
|
+
splashEffect: true,
|
|
350
|
+
consolidationEnabled: true,
|
|
351
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
352
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
353
|
+
consolidationMaxCallsPerRun: 20,
|
|
354
|
+
selfQueryRouter: true,
|
|
355
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
356
|
+
rerankerEnabled: true,
|
|
357
|
+
scalingRoadmap: {
|
|
358
|
+
rerankerAutoTrigger: {
|
|
359
|
+
enabled: true,
|
|
360
|
+
broadQueryMinCardinality: 5e4,
|
|
361
|
+
fetchTopK: 150,
|
|
362
|
+
returnTopK: 5
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
graphRagEnabled: true,
|
|
366
|
+
wikiEnabled: false,
|
|
367
|
+
wikiUrl: "",
|
|
368
|
+
wikiApiKey: "",
|
|
369
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
370
|
+
wikiWorkspaceMapping: {},
|
|
371
|
+
wikiAutoUpdate: true,
|
|
372
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
373
|
+
wikiAutoUpdateCreateNew: true,
|
|
374
|
+
skillLearning: true,
|
|
375
|
+
skillThreshold: 3,
|
|
376
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
377
|
+
exeHeartbeat: {
|
|
378
|
+
enabled: true,
|
|
379
|
+
intervalSeconds: 60,
|
|
380
|
+
staleInProgressThresholdHours: 2
|
|
381
|
+
},
|
|
382
|
+
sessionLifecycle: {
|
|
383
|
+
idleKillEnabled: true,
|
|
384
|
+
idleKillTicksRequired: 3,
|
|
385
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
386
|
+
maxAutoInstances: 10
|
|
387
|
+
},
|
|
388
|
+
autoUpdate: {
|
|
389
|
+
checkOnBoot: true,
|
|
390
|
+
autoInstall: false,
|
|
391
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
CONFIG_MIGRATIONS = [
|
|
395
|
+
{
|
|
396
|
+
from: 0,
|
|
397
|
+
to: 1,
|
|
398
|
+
migrate: (cfg) => {
|
|
399
|
+
cfg.config_version = 1;
|
|
400
|
+
return cfg;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
];
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// src/lib/employees.ts
|
|
408
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
409
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
410
|
+
import { execSync as execSync3 } from "child_process";
|
|
411
|
+
import path4 from "path";
|
|
412
|
+
import os2 from "os";
|
|
413
|
+
function normalizeRole(role) {
|
|
414
|
+
return (role ?? "").trim().toLowerCase();
|
|
415
|
+
}
|
|
416
|
+
function isCoordinatorRole(role) {
|
|
417
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
418
|
+
}
|
|
419
|
+
function getCoordinatorEmployee(employees) {
|
|
420
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
421
|
+
}
|
|
422
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
423
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
424
|
+
}
|
|
425
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
426
|
+
if (!agentName) return false;
|
|
427
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
428
|
+
}
|
|
429
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
430
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
431
|
+
}
|
|
432
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
433
|
+
if (!existsSync3(employeesPath)) return [];
|
|
434
|
+
try {
|
|
435
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
436
|
+
} catch {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function getEmployee(employees, name) {
|
|
441
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
442
|
+
}
|
|
443
|
+
function isMultiInstance(agentName, employees) {
|
|
444
|
+
const roster = employees ?? loadEmployeesSync();
|
|
445
|
+
const emp = getEmployee(roster, agentName);
|
|
446
|
+
if (!emp) return false;
|
|
447
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
448
|
+
}
|
|
449
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
450
|
+
var init_employees = __esm({
|
|
451
|
+
"src/lib/employees.ts"() {
|
|
452
|
+
"use strict";
|
|
453
|
+
init_config();
|
|
454
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
455
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
456
|
+
COORDINATOR_ROLE = "COO";
|
|
457
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
167
461
|
// src/lib/database.ts
|
|
168
462
|
var database_exports = {};
|
|
169
463
|
__export(database_exports, {
|
|
@@ -311,22 +605,24 @@ async function ensureSchema() {
|
|
|
311
605
|
ON behaviors(agent_id, active);
|
|
312
606
|
`);
|
|
313
607
|
try {
|
|
608
|
+
const coordinatorName = getCoordinatorName();
|
|
314
609
|
const existing = await client.execute({
|
|
315
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
316
|
-
args: []
|
|
610
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
611
|
+
args: [coordinatorName]
|
|
317
612
|
});
|
|
318
613
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
614
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
615
|
+
for (const [domain, content] of [
|
|
616
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
617
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
618
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
619
|
+
]) {
|
|
620
|
+
await client.execute({
|
|
621
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
622
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
623
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
624
|
+
});
|
|
625
|
+
}
|
|
330
626
|
}
|
|
331
627
|
} catch {
|
|
332
628
|
}
|
|
@@ -993,296 +1289,82 @@ async function ensureSchema() {
|
|
|
993
1289
|
await client.execute({
|
|
994
1290
|
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
995
1291
|
args: []
|
|
996
|
-
});
|
|
997
|
-
} catch {
|
|
998
|
-
}
|
|
999
|
-
try {
|
|
1000
|
-
await client.execute({
|
|
1001
|
-
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1002
|
-
args: []
|
|
1003
|
-
});
|
|
1004
|
-
} catch {
|
|
1005
|
-
}
|
|
1006
|
-
try {
|
|
1007
|
-
await client.execute(
|
|
1008
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1009
|
-
);
|
|
1010
|
-
} catch {
|
|
1011
|
-
}
|
|
1012
|
-
for (const col of [
|
|
1013
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1014
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1015
|
-
]) {
|
|
1016
|
-
try {
|
|
1017
|
-
await client.execute(col);
|
|
1018
|
-
} catch {
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
async function disposeDatabase() {
|
|
1023
|
-
if (_client) {
|
|
1024
|
-
_client.close();
|
|
1025
|
-
_client = null;
|
|
1026
|
-
_resilientClient = null;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1030
|
-
var init_database = __esm({
|
|
1031
|
-
"src/lib/database.ts"() {
|
|
1032
|
-
"use strict";
|
|
1033
|
-
init_db_retry();
|
|
1034
|
-
_client = null;
|
|
1035
|
-
_resilientClient = null;
|
|
1036
|
-
initTurso = initDatabase;
|
|
1037
|
-
disposeTurso = disposeDatabase;
|
|
1038
|
-
}
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
// src/lib/config.ts
|
|
1042
|
-
var config_exports = {};
|
|
1043
|
-
__export(config_exports, {
|
|
1044
|
-
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
1045
|
-
CONFIG_PATH: () => CONFIG_PATH,
|
|
1046
|
-
COO_AGENT_NAME: () => COO_AGENT_NAME,
|
|
1047
|
-
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
1048
|
-
DB_PATH: () => DB_PATH,
|
|
1049
|
-
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
1050
|
-
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
1051
|
-
MODELS_DIR: () => MODELS_DIR,
|
|
1052
|
-
loadConfig: () => loadConfig,
|
|
1053
|
-
loadConfigFrom: () => loadConfigFrom,
|
|
1054
|
-
loadConfigSync: () => loadConfigSync,
|
|
1055
|
-
migrateConfig: () => migrateConfig,
|
|
1056
|
-
saveConfig: () => saveConfig
|
|
1057
|
-
});
|
|
1058
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
1059
|
-
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
1060
|
-
import path4 from "path";
|
|
1061
|
-
import os2 from "os";
|
|
1062
|
-
function resolveDataDir() {
|
|
1063
|
-
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
1064
|
-
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
1065
|
-
const newDir = path4.join(os2.homedir(), ".exe-os");
|
|
1066
|
-
const legacyDir = path4.join(os2.homedir(), ".exe-mem");
|
|
1067
|
-
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
1068
|
-
try {
|
|
1069
|
-
renameSync(legacyDir, newDir);
|
|
1070
|
-
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
1071
|
-
`);
|
|
1072
|
-
} catch {
|
|
1073
|
-
return legacyDir;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
return newDir;
|
|
1077
|
-
}
|
|
1078
|
-
function migrateLegacyConfig(raw) {
|
|
1079
|
-
if ("r2" in raw) {
|
|
1080
|
-
process.stderr.write(
|
|
1081
|
-
"[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"
|
|
1082
|
-
);
|
|
1083
|
-
delete raw.r2;
|
|
1084
|
-
}
|
|
1085
|
-
if ("syncIntervalMs" in raw) {
|
|
1086
|
-
delete raw.syncIntervalMs;
|
|
1087
|
-
}
|
|
1088
|
-
return raw;
|
|
1089
|
-
}
|
|
1090
|
-
function migrateConfig(raw) {
|
|
1091
|
-
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
1092
|
-
let currentVersion = fromVersion;
|
|
1093
|
-
let migrated = false;
|
|
1094
|
-
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
1095
|
-
return { config: raw, migrated: false, fromVersion };
|
|
1096
|
-
}
|
|
1097
|
-
for (const migration of CONFIG_MIGRATIONS) {
|
|
1098
|
-
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
1099
|
-
raw = migration.migrate(raw);
|
|
1100
|
-
currentVersion = migration.to;
|
|
1101
|
-
migrated = true;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
return { config: raw, migrated, fromVersion };
|
|
1105
|
-
}
|
|
1106
|
-
function normalizeScalingRoadmap(raw) {
|
|
1107
|
-
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
1108
|
-
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
1109
|
-
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
1110
|
-
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
1111
|
-
userAuto.enabled = raw.rerankerEnabled;
|
|
1112
|
-
}
|
|
1113
|
-
raw.scalingRoadmap = {
|
|
1114
|
-
...userRoadmap,
|
|
1115
|
-
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
1116
|
-
};
|
|
1117
|
-
}
|
|
1118
|
-
function normalizeSessionLifecycle(raw) {
|
|
1119
|
-
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
1120
|
-
const userSL = raw.sessionLifecycle ?? {};
|
|
1121
|
-
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1122
|
-
}
|
|
1123
|
-
function normalizeAutoUpdate(raw) {
|
|
1124
|
-
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1125
|
-
const userAU = raw.autoUpdate ?? {};
|
|
1126
|
-
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1127
|
-
}
|
|
1128
|
-
async function loadConfig() {
|
|
1129
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1130
|
-
await mkdir2(dir, { recursive: true });
|
|
1131
|
-
const configPath = path4.join(dir, "config.json");
|
|
1132
|
-
if (!existsSync3(configPath)) {
|
|
1133
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1292
|
+
});
|
|
1293
|
+
} catch {
|
|
1134
1294
|
}
|
|
1135
|
-
const raw = await readFile2(configPath, "utf-8");
|
|
1136
1295
|
try {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1296
|
+
await client.execute({
|
|
1297
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1298
|
+
args: []
|
|
1299
|
+
});
|
|
1300
|
+
} catch {
|
|
1301
|
+
}
|
|
1302
|
+
try {
|
|
1303
|
+
await client.execute(
|
|
1304
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1305
|
+
);
|
|
1306
|
+
} catch {
|
|
1307
|
+
}
|
|
1308
|
+
for (const col of [
|
|
1309
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1310
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1311
|
+
]) {
|
|
1312
|
+
try {
|
|
1313
|
+
await client.execute(col);
|
|
1314
|
+
} catch {
|
|
1154
1315
|
}
|
|
1155
|
-
|
|
1316
|
+
}
|
|
1317
|
+
try {
|
|
1318
|
+
await client.execute({
|
|
1319
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1320
|
+
args: []
|
|
1321
|
+
});
|
|
1156
1322
|
} catch {
|
|
1157
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1158
1323
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1324
|
+
try {
|
|
1325
|
+
await client.execute(
|
|
1326
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1327
|
+
);
|
|
1328
|
+
} catch {
|
|
1165
1329
|
}
|
|
1166
1330
|
try {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1172
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1173
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1174
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
|
|
1331
|
+
await client.execute({
|
|
1332
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1333
|
+
args: []
|
|
1334
|
+
});
|
|
1175
1335
|
} catch {
|
|
1176
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1177
1336
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1184
|
-
if (config.cloud?.apiKey) {
|
|
1185
|
-
await chmod2(configPath, 384);
|
|
1337
|
+
try {
|
|
1338
|
+
await client.execute(
|
|
1339
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1340
|
+
);
|
|
1341
|
+
} catch {
|
|
1186
1342
|
}
|
|
1187
|
-
}
|
|
1188
|
-
async function loadConfigFrom(configPath) {
|
|
1189
|
-
const raw = await readFile2(configPath, "utf-8");
|
|
1190
1343
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1196
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1197
|
-
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
1344
|
+
await client.execute({
|
|
1345
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1346
|
+
args: []
|
|
1347
|
+
});
|
|
1198
1348
|
} catch {
|
|
1199
|
-
return { ...DEFAULT_CONFIG };
|
|
1200
1349
|
}
|
|
1201
1350
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1351
|
+
async function disposeDatabase() {
|
|
1352
|
+
if (_client) {
|
|
1353
|
+
_client.close();
|
|
1354
|
+
_client = null;
|
|
1355
|
+
_resilientClient = null;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1359
|
+
var init_database = __esm({
|
|
1360
|
+
"src/lib/database.ts"() {
|
|
1205
1361
|
"use strict";
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
CURRENT_CONFIG_VERSION = 1;
|
|
1213
|
-
DEFAULT_CONFIG = {
|
|
1214
|
-
config_version: CURRENT_CONFIG_VERSION,
|
|
1215
|
-
dbPath: DB_PATH,
|
|
1216
|
-
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
1217
|
-
embeddingDim: 1024,
|
|
1218
|
-
batchSize: 20,
|
|
1219
|
-
flushIntervalMs: 1e4,
|
|
1220
|
-
autoIngestion: true,
|
|
1221
|
-
autoRetrieval: true,
|
|
1222
|
-
searchMode: "hybrid",
|
|
1223
|
-
hookSearchMode: "hybrid",
|
|
1224
|
-
fileGrepEnabled: true,
|
|
1225
|
-
splashEffect: true,
|
|
1226
|
-
consolidationEnabled: true,
|
|
1227
|
-
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
1228
|
-
consolidationModel: "claude-haiku-4-5-20251001",
|
|
1229
|
-
consolidationMaxCallsPerRun: 20,
|
|
1230
|
-
selfQueryRouter: true,
|
|
1231
|
-
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
1232
|
-
rerankerEnabled: true,
|
|
1233
|
-
scalingRoadmap: {
|
|
1234
|
-
rerankerAutoTrigger: {
|
|
1235
|
-
enabled: true,
|
|
1236
|
-
broadQueryMinCardinality: 5e4,
|
|
1237
|
-
fetchTopK: 150,
|
|
1238
|
-
returnTopK: 5
|
|
1239
|
-
}
|
|
1240
|
-
},
|
|
1241
|
-
graphRagEnabled: true,
|
|
1242
|
-
wikiEnabled: false,
|
|
1243
|
-
wikiUrl: "",
|
|
1244
|
-
wikiApiKey: "",
|
|
1245
|
-
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
1246
|
-
wikiWorkspaceMapping: {
|
|
1247
|
-
exe: "Executive",
|
|
1248
|
-
yoshi: "Engineering",
|
|
1249
|
-
mari: "Marketing",
|
|
1250
|
-
tom: "Engineering",
|
|
1251
|
-
sasha: "Production"
|
|
1252
|
-
},
|
|
1253
|
-
wikiAutoUpdate: true,
|
|
1254
|
-
wikiAutoUpdateThreshold: 0.5,
|
|
1255
|
-
wikiAutoUpdateCreateNew: true,
|
|
1256
|
-
skillLearning: true,
|
|
1257
|
-
skillThreshold: 3,
|
|
1258
|
-
skillModel: "claude-haiku-4-5-20251001",
|
|
1259
|
-
exeHeartbeat: {
|
|
1260
|
-
enabled: true,
|
|
1261
|
-
intervalSeconds: 60,
|
|
1262
|
-
staleInProgressThresholdHours: 2
|
|
1263
|
-
},
|
|
1264
|
-
sessionLifecycle: {
|
|
1265
|
-
idleKillEnabled: true,
|
|
1266
|
-
idleKillTicksRequired: 3,
|
|
1267
|
-
idleKillIntercomAckWindowMs: 1e4,
|
|
1268
|
-
maxAutoInstances: 10
|
|
1269
|
-
},
|
|
1270
|
-
autoUpdate: {
|
|
1271
|
-
checkOnBoot: true,
|
|
1272
|
-
autoInstall: false,
|
|
1273
|
-
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1274
|
-
}
|
|
1275
|
-
};
|
|
1276
|
-
CONFIG_MIGRATIONS = [
|
|
1277
|
-
{
|
|
1278
|
-
from: 0,
|
|
1279
|
-
to: 1,
|
|
1280
|
-
migrate: (cfg) => {
|
|
1281
|
-
cfg.config_version = 1;
|
|
1282
|
-
return cfg;
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
];
|
|
1362
|
+
init_db_retry();
|
|
1363
|
+
init_employees();
|
|
1364
|
+
_client = null;
|
|
1365
|
+
_resilientClient = null;
|
|
1366
|
+
initTurso = initDatabase;
|
|
1367
|
+
disposeTurso = disposeDatabase;
|
|
1286
1368
|
}
|
|
1287
1369
|
});
|
|
1288
1370
|
|
|
@@ -1354,12 +1436,12 @@ __export(shard_manager_exports, {
|
|
|
1354
1436
|
listShards: () => listShards,
|
|
1355
1437
|
shardExists: () => shardExists
|
|
1356
1438
|
});
|
|
1357
|
-
import
|
|
1358
|
-
import { existsSync as
|
|
1439
|
+
import path6 from "path";
|
|
1440
|
+
import { existsSync as existsSync5, mkdirSync, readdirSync as readdirSync2 } from "fs";
|
|
1359
1441
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1360
1442
|
function initShardManager(encryptionKey) {
|
|
1361
1443
|
_encryptionKey = encryptionKey;
|
|
1362
|
-
if (!
|
|
1444
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
1363
1445
|
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
1364
1446
|
}
|
|
1365
1447
|
_shardingEnabled = true;
|
|
@@ -1380,7 +1462,7 @@ function getShardClient(projectName) {
|
|
|
1380
1462
|
}
|
|
1381
1463
|
const cached = _shards.get(safeName);
|
|
1382
1464
|
if (cached) return cached;
|
|
1383
|
-
const dbPath =
|
|
1465
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1384
1466
|
const client = createClient2({
|
|
1385
1467
|
url: `file:${dbPath}`,
|
|
1386
1468
|
encryptionKey: _encryptionKey
|
|
@@ -1390,10 +1472,10 @@ function getShardClient(projectName) {
|
|
|
1390
1472
|
}
|
|
1391
1473
|
function shardExists(projectName) {
|
|
1392
1474
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1393
|
-
return
|
|
1475
|
+
return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1394
1476
|
}
|
|
1395
1477
|
function listShards() {
|
|
1396
|
-
if (!
|
|
1478
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1397
1479
|
return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1398
1480
|
}
|
|
1399
1481
|
async function ensureShardSchema(client) {
|
|
@@ -1463,7 +1545,11 @@ async function ensureShardSchema(client) {
|
|
|
1463
1545
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1464
1546
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1465
1547
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1466
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1548
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
1549
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1550
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1551
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1552
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
1467
1553
|
]) {
|
|
1468
1554
|
try {
|
|
1469
1555
|
await client.execute(col);
|
|
@@ -1575,7 +1661,7 @@ var init_shard_manager = __esm({
|
|
|
1575
1661
|
"src/lib/shard-manager.ts"() {
|
|
1576
1662
|
"use strict";
|
|
1577
1663
|
init_config();
|
|
1578
|
-
SHARDS_DIR =
|
|
1664
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
1579
1665
|
_shards = /* @__PURE__ */ new Map();
|
|
1580
1666
|
_encryptionKey = null;
|
|
1581
1667
|
_shardingEnabled = false;
|
|
@@ -1593,26 +1679,26 @@ var init_platform_procedures = __esm({
|
|
|
1593
1679
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
1594
1680
|
domain: "architecture",
|
|
1595
1681
|
priority: "p0",
|
|
1596
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
1682
|
+
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."
|
|
1597
1683
|
},
|
|
1598
1684
|
{
|
|
1599
1685
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
1600
1686
|
domain: "architecture",
|
|
1601
1687
|
priority: "p0",
|
|
1602
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
1688
|
+
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."
|
|
1603
1689
|
},
|
|
1604
1690
|
{
|
|
1605
|
-
title: "Sessions explained \u2014
|
|
1691
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
1606
1692
|
domain: "architecture",
|
|
1607
1693
|
priority: "p0",
|
|
1608
|
-
content: "Each
|
|
1694
|
+
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."
|
|
1609
1695
|
},
|
|
1610
1696
|
// --- Hierarchy and dispatch ---
|
|
1611
1697
|
{
|
|
1612
1698
|
title: "Chain of command \u2014 who talks to whom",
|
|
1613
1699
|
domain: "workflow",
|
|
1614
1700
|
priority: "p0",
|
|
1615
|
-
content: "Founder
|
|
1701
|
+
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."
|
|
1616
1702
|
},
|
|
1617
1703
|
{
|
|
1618
1704
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -1622,30 +1708,30 @@ var init_platform_procedures = __esm({
|
|
|
1622
1708
|
},
|
|
1623
1709
|
// --- Session isolation ---
|
|
1624
1710
|
{
|
|
1625
|
-
title: "Session scoping \u2014 stay in your
|
|
1711
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
1626
1712
|
domain: "security",
|
|
1627
1713
|
priority: "p0",
|
|
1628
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
1714
|
+
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."
|
|
1629
1715
|
},
|
|
1630
1716
|
{
|
|
1631
1717
|
title: "Session isolation \u2014 never touch another session's work",
|
|
1632
1718
|
domain: "workflow",
|
|
1633
1719
|
priority: "p0",
|
|
1634
|
-
content:
|
|
1720
|
+
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."
|
|
1635
1721
|
},
|
|
1636
1722
|
// --- Engineering: session scoping in code ---
|
|
1637
1723
|
{
|
|
1638
1724
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
1639
1725
|
domain: "architecture",
|
|
1640
1726
|
priority: "p0",
|
|
1641
|
-
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
|
|
1727
|
+
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."
|
|
1642
1728
|
},
|
|
1643
1729
|
// --- Hard constraints ---
|
|
1644
1730
|
{
|
|
1645
1731
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
1646
1732
|
domain: "security",
|
|
1647
1733
|
priority: "p0",
|
|
1648
|
-
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
|
|
1734
|
+
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."
|
|
1649
1735
|
},
|
|
1650
1736
|
// --- Operations ---
|
|
1651
1737
|
{
|
|
@@ -1765,13 +1851,13 @@ ${p.content}`).join("\n\n");
|
|
|
1765
1851
|
|
|
1766
1852
|
// src/lib/notifications.ts
|
|
1767
1853
|
import crypto2 from "crypto";
|
|
1768
|
-
import
|
|
1769
|
-
import
|
|
1854
|
+
import path7 from "path";
|
|
1855
|
+
import os4 from "os";
|
|
1770
1856
|
import {
|
|
1771
|
-
readFileSync as
|
|
1857
|
+
readFileSync as readFileSync4,
|
|
1772
1858
|
readdirSync as readdirSync3,
|
|
1773
|
-
unlinkSync,
|
|
1774
|
-
existsSync as
|
|
1859
|
+
unlinkSync as unlinkSync2,
|
|
1860
|
+
existsSync as existsSync6,
|
|
1775
1861
|
rmdirSync
|
|
1776
1862
|
} from "fs";
|
|
1777
1863
|
async function writeNotification(notification) {
|
|
@@ -1815,39 +1901,6 @@ var init_notifications = __esm({
|
|
|
1815
1901
|
}
|
|
1816
1902
|
});
|
|
1817
1903
|
|
|
1818
|
-
// src/lib/employees.ts
|
|
1819
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1820
|
-
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
1821
|
-
import { execSync as execSync3 } from "child_process";
|
|
1822
|
-
import path7 from "path";
|
|
1823
|
-
import os4 from "os";
|
|
1824
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1825
|
-
if (!existsSync6(employeesPath)) return [];
|
|
1826
|
-
try {
|
|
1827
|
-
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
1828
|
-
} catch {
|
|
1829
|
-
return [];
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
function getEmployee(employees, name) {
|
|
1833
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
1834
|
-
}
|
|
1835
|
-
function isMultiInstance(agentName, employees) {
|
|
1836
|
-
const roster = employees ?? loadEmployeesSync();
|
|
1837
|
-
const emp = getEmployee(roster, agentName);
|
|
1838
|
-
if (!emp) return false;
|
|
1839
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1840
|
-
}
|
|
1841
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1842
|
-
var init_employees = __esm({
|
|
1843
|
-
"src/lib/employees.ts"() {
|
|
1844
|
-
"use strict";
|
|
1845
|
-
init_config();
|
|
1846
|
-
EMPLOYEES_PATH = path7.join(EXE_AI_DIR, "exe-employees.json");
|
|
1847
|
-
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1848
|
-
}
|
|
1849
|
-
});
|
|
1850
|
-
|
|
1851
1904
|
// src/lib/license.ts
|
|
1852
1905
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1853
1906
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -2265,6 +2318,10 @@ function spawnDaemon() {
|
|
|
2265
2318
|
stdio: ["ignore", "ignore", stderrFd],
|
|
2266
2319
|
env: {
|
|
2267
2320
|
...process.env,
|
|
2321
|
+
TMUX: void 0,
|
|
2322
|
+
// Daemon is global — must not inherit session scope
|
|
2323
|
+
TMUX_PANE: void 0,
|
|
2324
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
2268
2325
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2269
2326
|
EXE_DAEMON_PID: PID_PATH
|
|
2270
2327
|
}
|
|
@@ -2971,7 +3028,7 @@ function _resetLastRelaunchCache() {
|
|
|
2971
3028
|
}
|
|
2972
3029
|
async function lastResumeCreatedAtMs(agentId) {
|
|
2973
3030
|
const client = getClient();
|
|
2974
|
-
const cmScope = sessionScopeFilter();
|
|
3031
|
+
const cmScope = sessionScopeFilter(null);
|
|
2975
3032
|
const result = await client.execute({
|
|
2976
3033
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
2977
3034
|
FROM tasks
|
|
@@ -2996,7 +3053,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
2996
3053
|
const client = getClient();
|
|
2997
3054
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2998
3055
|
const context = buildResumeContext(agentId, openTasks);
|
|
2999
|
-
const rdScope = sessionScopeFilter();
|
|
3056
|
+
const rdScope = sessionScopeFilter(null);
|
|
3000
3057
|
const existing = await client.execute({
|
|
3001
3058
|
sql: `SELECT id FROM tasks
|
|
3002
3059
|
WHERE assigned_to = ?
|
|
@@ -3030,7 +3087,7 @@ async function pollCapacityDead() {
|
|
|
3030
3087
|
const transport = getTransport();
|
|
3031
3088
|
const relaunched = [];
|
|
3032
3089
|
const registered = listSessions().filter(
|
|
3033
|
-
(s) => s.agentId !== "exe"
|
|
3090
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
3034
3091
|
);
|
|
3035
3092
|
if (registered.length === 0) return [];
|
|
3036
3093
|
let liveSessions;
|
|
@@ -3090,7 +3147,7 @@ async function pollCapacityDead() {
|
|
|
3090
3147
|
reason: "capacity"
|
|
3091
3148
|
});
|
|
3092
3149
|
const client = getClient();
|
|
3093
|
-
const rlScope = sessionScopeFilter();
|
|
3150
|
+
const rlScope = sessionScopeFilter(null);
|
|
3094
3151
|
const openTasks = await client.execute({
|
|
3095
3152
|
sql: `SELECT id, title, priority, task_file, status
|
|
3096
3153
|
FROM tasks
|
|
@@ -3144,6 +3201,7 @@ var init_capacity_monitor = __esm({
|
|
|
3144
3201
|
init_session_kill_telemetry();
|
|
3145
3202
|
init_tmux_routing();
|
|
3146
3203
|
init_task_scope();
|
|
3204
|
+
init_employees();
|
|
3147
3205
|
CAPACITY_PATTERNS = [
|
|
3148
3206
|
/conversation is too long/i,
|
|
3149
3207
|
/maximum context length/i,
|
|
@@ -3293,7 +3351,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
3293
3351
|
exeSession = root;
|
|
3294
3352
|
} else {
|
|
3295
3353
|
throw new Error(
|
|
3296
|
-
`Invalid
|
|
3354
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3297
3355
|
);
|
|
3298
3356
|
}
|
|
3299
3357
|
}
|
|
@@ -3313,8 +3371,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
3313
3371
|
return match?.[1] ?? null;
|
|
3314
3372
|
}
|
|
3315
3373
|
function extractRootExe(name) {
|
|
3316
|
-
|
|
3317
|
-
|
|
3374
|
+
if (!name) return null;
|
|
3375
|
+
if (!name.includes("-")) return name;
|
|
3376
|
+
const parts = name.split("-").filter(Boolean);
|
|
3377
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3318
3378
|
}
|
|
3319
3379
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3320
3380
|
if (!existsSync12(SESSION_CACHE)) {
|
|
@@ -3459,12 +3519,14 @@ function isSessionBusy(sessionName) {
|
|
|
3459
3519
|
return state === "thinking" || state === "tool";
|
|
3460
3520
|
}
|
|
3461
3521
|
function isExeSession(sessionName) {
|
|
3462
|
-
|
|
3522
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
3523
|
+
const coordinatorName = getCoordinatorName();
|
|
3524
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
3463
3525
|
}
|
|
3464
3526
|
function sendIntercom(targetSession) {
|
|
3465
3527
|
const transport = getTransport();
|
|
3466
3528
|
if (isExeSession(targetSession)) {
|
|
3467
|
-
logIntercom(`
|
|
3529
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
3468
3530
|
return "skipped_exe";
|
|
3469
3531
|
}
|
|
3470
3532
|
if (isDebounced(targetSession)) {
|
|
@@ -3516,7 +3578,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3516
3578
|
if (result === "failed") {
|
|
3517
3579
|
const rootExe = resolveExeSession();
|
|
3518
3580
|
if (rootExe && rootExe !== target) {
|
|
3519
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
3581
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
3520
3582
|
`);
|
|
3521
3583
|
const fallback = sendIntercom(rootExe);
|
|
3522
3584
|
return fallback !== "failed";
|
|
@@ -3526,8 +3588,8 @@ function notifyParentExe(sessionKey) {
|
|
|
3526
3588
|
return true;
|
|
3527
3589
|
}
|
|
3528
3590
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3529
|
-
if (employeeName === "exe") {
|
|
3530
|
-
return { status: "failed", sessionName: "", error: "
|
|
3591
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
3592
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3531
3593
|
}
|
|
3532
3594
|
try {
|
|
3533
3595
|
assertEmployeeLimitSync();
|
|
@@ -3536,8 +3598,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3536
3598
|
return { status: "failed", sessionName: "", error: err.message };
|
|
3537
3599
|
}
|
|
3538
3600
|
}
|
|
3539
|
-
if (
|
|
3540
|
-
const bare = employeeName.
|
|
3601
|
+
if (employeeName.includes("-")) {
|
|
3602
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
3541
3603
|
return {
|
|
3542
3604
|
status: "failed",
|
|
3543
3605
|
sessionName: "",
|
|
@@ -3556,7 +3618,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3556
3618
|
return {
|
|
3557
3619
|
status: "failed",
|
|
3558
3620
|
sessionName: "",
|
|
3559
|
-
error: `Invalid
|
|
3621
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3560
3622
|
};
|
|
3561
3623
|
}
|
|
3562
3624
|
}
|
|
@@ -3713,8 +3775,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3713
3775
|
const ctxContent = [
|
|
3714
3776
|
`## Session Context`,
|
|
3715
3777
|
`You are running in tmux session: ${sessionName}.`,
|
|
3716
|
-
`Your parent
|
|
3717
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
3778
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
3779
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3718
3780
|
].join("\n");
|
|
3719
3781
|
writeFileSync5(ctxFile, ctxContent);
|
|
3720
3782
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -3818,6 +3880,7 @@ var init_tmux_routing = __esm({
|
|
|
3818
3880
|
init_provider_table();
|
|
3819
3881
|
init_intercom_queue();
|
|
3820
3882
|
init_plan_limits();
|
|
3883
|
+
init_employees();
|
|
3821
3884
|
SPAWN_LOCK_DIR = path13.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
3822
3885
|
SESSION_CACHE = path13.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3823
3886
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -4100,6 +4163,36 @@ async function listTasks(input2) {
|
|
|
4100
4163
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
4101
4164
|
}));
|
|
4102
4165
|
}
|
|
4166
|
+
function isTmuxSessionAlive(identifier) {
|
|
4167
|
+
if (!identifier || identifier === "unknown") return true;
|
|
4168
|
+
try {
|
|
4169
|
+
if (identifier.startsWith("%")) {
|
|
4170
|
+
const output = execSync7("tmux list-panes -a -F '#{pane_id}'", {
|
|
4171
|
+
timeout: 2e3,
|
|
4172
|
+
encoding: "utf8",
|
|
4173
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4174
|
+
});
|
|
4175
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
4176
|
+
} else {
|
|
4177
|
+
execSync7(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
4178
|
+
timeout: 2e3,
|
|
4179
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4180
|
+
});
|
|
4181
|
+
return true;
|
|
4182
|
+
}
|
|
4183
|
+
} catch {
|
|
4184
|
+
if (identifier.startsWith("%")) return true;
|
|
4185
|
+
try {
|
|
4186
|
+
execSync7("tmux list-sessions", {
|
|
4187
|
+
timeout: 2e3,
|
|
4188
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4189
|
+
});
|
|
4190
|
+
return false;
|
|
4191
|
+
} catch {
|
|
4192
|
+
return true;
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4103
4196
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
4104
4197
|
if (!taskContext) return null;
|
|
4105
4198
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -4162,13 +4255,59 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4162
4255
|
});
|
|
4163
4256
|
if (claim.rowsAffected === 0) {
|
|
4164
4257
|
const current = await client.execute({
|
|
4165
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
4258
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
4166
4259
|
args: [taskId]
|
|
4167
4260
|
});
|
|
4168
4261
|
const cur = current.rows[0];
|
|
4169
|
-
const
|
|
4170
|
-
const
|
|
4171
|
-
|
|
4262
|
+
const curStatus = cur?.status ?? "unknown";
|
|
4263
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
4264
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
4265
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
4266
|
+
process.stderr.write(
|
|
4267
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
4268
|
+
`
|
|
4269
|
+
);
|
|
4270
|
+
await client.execute({
|
|
4271
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
4272
|
+
args: [now, taskId]
|
|
4273
|
+
});
|
|
4274
|
+
const retried = await client.execute({
|
|
4275
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
4276
|
+
args: [tmuxSession, now, taskId]
|
|
4277
|
+
});
|
|
4278
|
+
if (retried.rowsAffected > 0) {
|
|
4279
|
+
try {
|
|
4280
|
+
await writeCheckpoint({
|
|
4281
|
+
taskId,
|
|
4282
|
+
step: "reclaimed_dead_session",
|
|
4283
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
4284
|
+
});
|
|
4285
|
+
} catch {
|
|
4286
|
+
}
|
|
4287
|
+
return { row, taskFile, now, taskId };
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || input2.callerAgentId === "exe")) {
|
|
4291
|
+
process.stderr.write(
|
|
4292
|
+
`[tasks] Assigner override: ${input2.callerAgentId} reclaiming ${taskId}
|
|
4293
|
+
`
|
|
4294
|
+
);
|
|
4295
|
+
await client.execute({
|
|
4296
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
4297
|
+
args: [tmuxSession, now, taskId]
|
|
4298
|
+
});
|
|
4299
|
+
try {
|
|
4300
|
+
await writeCheckpoint({
|
|
4301
|
+
taskId,
|
|
4302
|
+
step: "assigner_override",
|
|
4303
|
+
contextSummary: `Task force-reclaimed by assigner ${input2.callerAgentId}.`
|
|
4304
|
+
});
|
|
4305
|
+
} catch {
|
|
4306
|
+
}
|
|
4307
|
+
return { row, taskFile, now, taskId };
|
|
4308
|
+
}
|
|
4309
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
4310
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
4172
4311
|
}
|
|
4173
4312
|
try {
|
|
4174
4313
|
await writeCheckpoint({
|
|
@@ -4266,7 +4405,7 @@ var init_tasks_crud = __esm({
|
|
|
4266
4405
|
"use strict";
|
|
4267
4406
|
init_database();
|
|
4268
4407
|
init_task_scope();
|
|
4269
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
4408
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
4270
4409
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
4271
4410
|
}
|
|
4272
4411
|
});
|
|
@@ -4581,7 +4720,7 @@ function findSessionForProject(projectName) {
|
|
|
4581
4720
|
const sessions = listSessions();
|
|
4582
4721
|
for (const s of sessions) {
|
|
4583
4722
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4584
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
4723
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
4585
4724
|
}
|
|
4586
4725
|
return null;
|
|
4587
4726
|
}
|
|
@@ -4621,12 +4760,13 @@ var init_session_scope = __esm({
|
|
|
4621
4760
|
init_session_registry();
|
|
4622
4761
|
init_project_name();
|
|
4623
4762
|
init_tmux_routing();
|
|
4763
|
+
init_employees();
|
|
4624
4764
|
}
|
|
4625
4765
|
});
|
|
4626
4766
|
|
|
4627
4767
|
// src/lib/tasks-notify.ts
|
|
4628
4768
|
async function dispatchTaskToEmployee(input2) {
|
|
4629
|
-
if (input2.assignedTo === "exe") return { dispatched: "skipped" };
|
|
4769
|
+
if (input2.assignedTo === "exe" || isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
4630
4770
|
let crossProject = false;
|
|
4631
4771
|
if (input2.projectName) {
|
|
4632
4772
|
try {
|
|
@@ -5069,6 +5209,24 @@ async function updateTask(input2) {
|
|
|
5069
5209
|
});
|
|
5070
5210
|
} catch {
|
|
5071
5211
|
}
|
|
5212
|
+
const assignedAgent = String(row.assigned_to);
|
|
5213
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
5214
|
+
try {
|
|
5215
|
+
const draftClient = getClient();
|
|
5216
|
+
if (input2.status === "done") {
|
|
5217
|
+
await draftClient.execute({
|
|
5218
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5219
|
+
args: [assignedAgent]
|
|
5220
|
+
});
|
|
5221
|
+
} else if (input2.status === "cancelled") {
|
|
5222
|
+
await draftClient.execute({
|
|
5223
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
5224
|
+
args: [assignedAgent]
|
|
5225
|
+
});
|
|
5226
|
+
}
|
|
5227
|
+
} catch {
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5072
5230
|
try {
|
|
5073
5231
|
const client = getClient();
|
|
5074
5232
|
const cascaded = await client.execute({
|
|
@@ -5087,8 +5245,8 @@ async function updateTask(input2) {
|
|
|
5087
5245
|
}
|
|
5088
5246
|
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
5089
5247
|
if (isTerminal) {
|
|
5090
|
-
const
|
|
5091
|
-
if (!
|
|
5248
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
5249
|
+
if (!isCoordinator) {
|
|
5092
5250
|
notifyTaskDone();
|
|
5093
5251
|
}
|
|
5094
5252
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -5112,7 +5270,7 @@ async function updateTask(input2) {
|
|
|
5112
5270
|
}
|
|
5113
5271
|
}
|
|
5114
5272
|
}
|
|
5115
|
-
if (input2.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
5273
|
+
if (input2.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5116
5274
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5117
5275
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5118
5276
|
taskId,
|
|
@@ -5128,7 +5286,7 @@ async function updateTask(input2) {
|
|
|
5128
5286
|
});
|
|
5129
5287
|
}
|
|
5130
5288
|
let nextTask;
|
|
5131
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
5289
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
5132
5290
|
try {
|
|
5133
5291
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
5134
5292
|
} catch {
|
|
@@ -5155,12 +5313,14 @@ async function updateTask(input2) {
|
|
|
5155
5313
|
async function deleteTask(taskId, baseDir) {
|
|
5156
5314
|
const client = getClient();
|
|
5157
5315
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
5158
|
-
const
|
|
5316
|
+
const coordinatorName = getCoordinatorName();
|
|
5317
|
+
const reviewer = assignedBy || coordinatorName;
|
|
5159
5318
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
5160
5319
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
5320
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
5161
5321
|
await client.execute({
|
|
5162
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
5163
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
5322
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
5323
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
5164
5324
|
});
|
|
5165
5325
|
await markAsReadByTaskFile(taskFile);
|
|
5166
5326
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -5172,6 +5332,7 @@ var init_tasks = __esm({
|
|
|
5172
5332
|
init_config();
|
|
5173
5333
|
init_notifications();
|
|
5174
5334
|
init_state_bus();
|
|
5335
|
+
init_employees();
|
|
5175
5336
|
init_tasks_crud();
|
|
5176
5337
|
init_tasks_review();
|
|
5177
5338
|
init_tasks_crud();
|
|
@@ -5406,17 +5567,17 @@ init_memory();
|
|
|
5406
5567
|
init_database();
|
|
5407
5568
|
|
|
5408
5569
|
// src/lib/keychain.ts
|
|
5409
|
-
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
5410
|
-
import { existsSync as
|
|
5411
|
-
import
|
|
5412
|
-
import
|
|
5570
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
5571
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5572
|
+
import path5 from "path";
|
|
5573
|
+
import os3 from "os";
|
|
5413
5574
|
var SERVICE = "exe-mem";
|
|
5414
5575
|
var ACCOUNT = "master-key";
|
|
5415
5576
|
function getKeyDir() {
|
|
5416
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5577
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os3.homedir(), ".exe-os");
|
|
5417
5578
|
}
|
|
5418
5579
|
function getKeyPath() {
|
|
5419
|
-
return
|
|
5580
|
+
return path5.join(getKeyDir(), "master.key");
|
|
5420
5581
|
}
|
|
5421
5582
|
async function tryKeytar() {
|
|
5422
5583
|
try {
|
|
@@ -5437,11 +5598,11 @@ async function getMasterKey() {
|
|
|
5437
5598
|
}
|
|
5438
5599
|
}
|
|
5439
5600
|
const keyPath = getKeyPath();
|
|
5440
|
-
if (!
|
|
5601
|
+
if (!existsSync4(keyPath)) {
|
|
5441
5602
|
return null;
|
|
5442
5603
|
}
|
|
5443
5604
|
try {
|
|
5444
|
-
const content = await
|
|
5605
|
+
const content = await readFile3(keyPath, "utf-8");
|
|
5445
5606
|
return Buffer.from(content.trim(), "base64");
|
|
5446
5607
|
} catch {
|
|
5447
5608
|
return null;
|
|
@@ -5564,7 +5725,10 @@ async function writeMemory(record) {
|
|
|
5564
5725
|
source_path: record.source_path ?? null,
|
|
5565
5726
|
source_type: record.source_type ?? null,
|
|
5566
5727
|
tier: record.tier ?? classifyTier(record),
|
|
5567
|
-
supersedes_id: record.supersedes_id ?? null
|
|
5728
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
5729
|
+
draft: record.draft ? 1 : 0,
|
|
5730
|
+
memory_type: record.memory_type ?? "raw",
|
|
5731
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5568
5732
|
};
|
|
5569
5733
|
_pendingRecords.push(dbRow);
|
|
5570
5734
|
orgBus.emit({
|
|
@@ -5619,6 +5783,9 @@ async function flushBatch() {
|
|
|
5619
5783
|
const sourceType = row.source_type ?? null;
|
|
5620
5784
|
const tier = row.tier ?? 3;
|
|
5621
5785
|
const supersedesId = row.supersedes_id ?? null;
|
|
5786
|
+
const draft = row.draft ? 1 : 0;
|
|
5787
|
+
const memoryType = row.memory_type ?? "raw";
|
|
5788
|
+
const trajectory = row.trajectory ?? null;
|
|
5622
5789
|
return {
|
|
5623
5790
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
5624
5791
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -5626,15 +5793,15 @@ async function flushBatch() {
|
|
|
5626
5793
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5627
5794
|
confidence, last_accessed,
|
|
5628
5795
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5629
|
-
source_path, source_type, tier, supersedes_id)
|
|
5630
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
5796
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
5797
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
5631
5798
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
5632
5799
|
tool_name, project_name,
|
|
5633
5800
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5634
5801
|
confidence, last_accessed,
|
|
5635
5802
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5636
|
-
source_path, source_type, tier, supersedes_id)
|
|
5637
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5803
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
5804
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5638
5805
|
args: hasVector ? [
|
|
5639
5806
|
row.id,
|
|
5640
5807
|
row.agent_id,
|
|
@@ -5660,7 +5827,10 @@ async function flushBatch() {
|
|
|
5660
5827
|
sourcePath,
|
|
5661
5828
|
sourceType,
|
|
5662
5829
|
tier,
|
|
5663
|
-
supersedesId
|
|
5830
|
+
supersedesId,
|
|
5831
|
+
draft,
|
|
5832
|
+
memoryType,
|
|
5833
|
+
trajectory
|
|
5664
5834
|
] : [
|
|
5665
5835
|
row.id,
|
|
5666
5836
|
row.agent_id,
|
|
@@ -5685,7 +5855,10 @@ async function flushBatch() {
|
|
|
5685
5855
|
sourcePath,
|
|
5686
5856
|
sourceType,
|
|
5687
5857
|
tier,
|
|
5688
|
-
supersedesId
|
|
5858
|
+
supersedesId,
|
|
5859
|
+
draft,
|
|
5860
|
+
memoryType,
|
|
5861
|
+
trajectory
|
|
5689
5862
|
]
|
|
5690
5863
|
};
|
|
5691
5864
|
};
|
|
@@ -5918,6 +6091,7 @@ function extractResponseText(response) {
|
|
|
5918
6091
|
|
|
5919
6092
|
// src/adapters/claude/hooks/ingest-worker.ts
|
|
5920
6093
|
init_plan_limits();
|
|
6094
|
+
init_employees();
|
|
5921
6095
|
process.env.EXE_EMBED_PRIORITY = "low";
|
|
5922
6096
|
function assignConfidence(toolName, agentId) {
|
|
5923
6097
|
if (agentId === "default") return 0.9;
|
|
@@ -5980,19 +6154,33 @@ process.stdin.on("end", async () => {
|
|
|
5980
6154
|
} catch {
|
|
5981
6155
|
}
|
|
5982
6156
|
const agentId = process.env.AGENT_ID;
|
|
6157
|
+
const agentRole = process.env.AGENT_ROLE ?? "unknown";
|
|
6158
|
+
const isDraft = taskId !== null && !canCoordinate(agentId, agentRole);
|
|
6159
|
+
const hasError = detectError(data);
|
|
6160
|
+
const toolInputStr = JSON.stringify(data.tool_input ?? "").slice(0, 200);
|
|
6161
|
+
const toolOutputStr = JSON.stringify(data.tool_response ?? "").slice(0, 200);
|
|
6162
|
+
const trajectory = {
|
|
6163
|
+
input: toolInputStr,
|
|
6164
|
+
tool: data.tool_name,
|
|
6165
|
+
output: toolOutputStr,
|
|
6166
|
+
result_type: hasError ? "error" : "success"
|
|
6167
|
+
};
|
|
5983
6168
|
await writeMemory({
|
|
5984
6169
|
id: crypto7.randomUUID(),
|
|
5985
6170
|
agent_id: agentId,
|
|
5986
|
-
agent_role:
|
|
6171
|
+
agent_role: agentRole,
|
|
5987
6172
|
session_id: data.session_id,
|
|
5988
6173
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5989
6174
|
tool_name: data.tool_name,
|
|
5990
6175
|
project_name: getProjectName(data.cwd ?? process.cwd()),
|
|
5991
|
-
has_error:
|
|
6176
|
+
has_error: hasError,
|
|
5992
6177
|
raw_text: rawText,
|
|
5993
6178
|
vector,
|
|
5994
6179
|
task_id: taskId,
|
|
5995
|
-
confidence: assignConfidence(data.tool_name, agentId)
|
|
6180
|
+
confidence: assignConfidence(data.tool_name, agentId),
|
|
6181
|
+
draft: isDraft,
|
|
6182
|
+
memory_type: "raw",
|
|
6183
|
+
trajectory
|
|
5996
6184
|
});
|
|
5997
6185
|
await flushBatch();
|
|
5998
6186
|
if (needsBackfill) {
|
|
@@ -6005,8 +6193,7 @@ process.stdin.on("end", async () => {
|
|
|
6005
6193
|
`);
|
|
6006
6194
|
}
|
|
6007
6195
|
}
|
|
6008
|
-
|
|
6009
|
-
if (agentId !== "exe" && agentId !== "default" && (data.tool_name === "Edit" || data.tool_name === "Write")) {
|
|
6196
|
+
if (!canCoordinate(agentId, agentRole) && (data.tool_name === "Edit" || data.tool_name === "Write")) {
|
|
6010
6197
|
const filePath = data.tool_input?.file_path ?? "";
|
|
6011
6198
|
const taskFileMatch = filePath.match(/exe\/([^/]+)\/([^/]+\.md)$/);
|
|
6012
6199
|
if (taskFileMatch) {
|
|
@@ -6074,7 +6261,7 @@ process.stdin.on("end", async () => {
|
|
|
6074
6261
|
const priMatch = fileContent.match(/^\*\*Priority:\*\*\s*(\w+)/im);
|
|
6075
6262
|
const priority = priMatch?.[1]?.toLowerCase() ?? "p1";
|
|
6076
6263
|
const assignedByMatch = fileContent.match(/^\*\*Assigned by:\*\*\s*(\w+)/im);
|
|
6077
|
-
const assignedBy = assignedByMatch?.[1] ??
|
|
6264
|
+
const assignedBy = assignedByMatch?.[1] ?? getCoordinatorName();
|
|
6078
6265
|
const projMatch = fileContent.match(/^\*\*Project:\*\*\s*(.+)/im);
|
|
6079
6266
|
const projectName = projMatch?.[1]?.trim() ?? getProjectName(data.cwd ?? process.cwd());
|
|
6080
6267
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|