@askexenow/exe-os 0.8.83 → 0.8.86
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +375 -152
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
|
@@ -268,123 +268,19 @@ var init_provider_table = __esm({
|
|
|
268
268
|
}
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
-
// src/lib/intercom-queue.ts
|
|
272
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
273
|
-
import path2 from "path";
|
|
274
|
-
import os2 from "os";
|
|
275
|
-
function ensureDir() {
|
|
276
|
-
const dir = path2.dirname(QUEUE_PATH);
|
|
277
|
-
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
278
|
-
}
|
|
279
|
-
function readQueue() {
|
|
280
|
-
try {
|
|
281
|
-
if (!existsSync2(QUEUE_PATH)) return [];
|
|
282
|
-
return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
|
|
283
|
-
} catch {
|
|
284
|
-
return [];
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function writeQueue(queue) {
|
|
288
|
-
ensureDir();
|
|
289
|
-
const tmp = `${QUEUE_PATH}.tmp`;
|
|
290
|
-
writeFileSync2(tmp, JSON.stringify(queue, null, 2));
|
|
291
|
-
renameSync(tmp, QUEUE_PATH);
|
|
292
|
-
}
|
|
293
|
-
function queueIntercom(targetSession, reason) {
|
|
294
|
-
const queue = readQueue();
|
|
295
|
-
const existing = queue.find((q) => q.targetSession === targetSession);
|
|
296
|
-
if (existing) {
|
|
297
|
-
existing.attempts++;
|
|
298
|
-
existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
299
|
-
existing.reason = reason;
|
|
300
|
-
} else {
|
|
301
|
-
queue.push({
|
|
302
|
-
targetSession,
|
|
303
|
-
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
304
|
-
attempts: 0,
|
|
305
|
-
reason
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
writeQueue(queue);
|
|
309
|
-
}
|
|
310
|
-
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
311
|
-
var init_intercom_queue = __esm({
|
|
312
|
-
"src/lib/intercom-queue.ts"() {
|
|
313
|
-
"use strict";
|
|
314
|
-
QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
|
|
315
|
-
TTL_MS = 60 * 60 * 1e3;
|
|
316
|
-
INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
// src/lib/db-retry.ts
|
|
321
|
-
function isBusyError(err) {
|
|
322
|
-
if (err instanceof Error) {
|
|
323
|
-
const msg = err.message.toLowerCase();
|
|
324
|
-
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
325
|
-
}
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
function delay(ms) {
|
|
329
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
330
|
-
}
|
|
331
|
-
async function retryOnBusy(fn, label) {
|
|
332
|
-
let lastError;
|
|
333
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
334
|
-
try {
|
|
335
|
-
return await fn();
|
|
336
|
-
} catch (err) {
|
|
337
|
-
lastError = err;
|
|
338
|
-
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
339
|
-
throw err;
|
|
340
|
-
}
|
|
341
|
-
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
342
|
-
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
343
|
-
process.stderr.write(
|
|
344
|
-
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
345
|
-
`
|
|
346
|
-
);
|
|
347
|
-
await delay(backoff + jitter);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
throw lastError;
|
|
351
|
-
}
|
|
352
|
-
function wrapWithRetry(client) {
|
|
353
|
-
return new Proxy(client, {
|
|
354
|
-
get(target, prop, receiver) {
|
|
355
|
-
if (prop === "execute") {
|
|
356
|
-
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
357
|
-
}
|
|
358
|
-
if (prop === "batch") {
|
|
359
|
-
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
360
|
-
}
|
|
361
|
-
return Reflect.get(target, prop, receiver);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
366
|
-
var init_db_retry = __esm({
|
|
367
|
-
"src/lib/db-retry.ts"() {
|
|
368
|
-
"use strict";
|
|
369
|
-
MAX_RETRIES = 3;
|
|
370
|
-
BASE_DELAY_MS = 200;
|
|
371
|
-
MAX_JITTER_MS = 300;
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
|
|
375
271
|
// src/lib/config.ts
|
|
376
272
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
377
|
-
import { readFileSync as
|
|
378
|
-
import
|
|
379
|
-
import
|
|
273
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
|
|
274
|
+
import path2 from "path";
|
|
275
|
+
import os2 from "os";
|
|
380
276
|
function resolveDataDir() {
|
|
381
277
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
382
278
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
383
|
-
const newDir =
|
|
384
|
-
const legacyDir =
|
|
385
|
-
if (!
|
|
279
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
280
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
281
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
386
282
|
try {
|
|
387
|
-
|
|
283
|
+
renameSync(legacyDir, newDir);
|
|
388
284
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
389
285
|
`);
|
|
390
286
|
} catch {
|
|
@@ -446,9 +342,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
446
342
|
async function loadConfig() {
|
|
447
343
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
448
344
|
await mkdir(dir, { recursive: true });
|
|
449
|
-
const configPath =
|
|
450
|
-
if (!
|
|
451
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
345
|
+
const configPath = path2.join(dir, "config.json");
|
|
346
|
+
if (!existsSync2(configPath)) {
|
|
347
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
452
348
|
}
|
|
453
349
|
const raw = await readFile(configPath, "utf-8");
|
|
454
350
|
try {
|
|
@@ -466,13 +362,13 @@ async function loadConfig() {
|
|
|
466
362
|
normalizeScalingRoadmap(migratedCfg);
|
|
467
363
|
normalizeSessionLifecycle(migratedCfg);
|
|
468
364
|
normalizeAutoUpdate(migratedCfg);
|
|
469
|
-
const config = { ...DEFAULT_CONFIG, dbPath:
|
|
365
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
470
366
|
if (config.dbPath.startsWith("~")) {
|
|
471
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
367
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
472
368
|
}
|
|
473
369
|
return config;
|
|
474
370
|
} catch {
|
|
475
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
371
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
476
372
|
}
|
|
477
373
|
}
|
|
478
374
|
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
@@ -480,10 +376,10 @@ var init_config = __esm({
|
|
|
480
376
|
"src/lib/config.ts"() {
|
|
481
377
|
"use strict";
|
|
482
378
|
EXE_AI_DIR = resolveDataDir();
|
|
483
|
-
DB_PATH =
|
|
484
|
-
MODELS_DIR =
|
|
485
|
-
CONFIG_PATH =
|
|
486
|
-
LEGACY_LANCE_PATH =
|
|
379
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
380
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
381
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
382
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
487
383
|
CURRENT_CONFIG_VERSION = 1;
|
|
488
384
|
DEFAULT_CONFIG = {
|
|
489
385
|
config_version: CURRENT_CONFIG_VERSION,
|
|
@@ -555,11 +451,166 @@ var init_config = __esm({
|
|
|
555
451
|
}
|
|
556
452
|
});
|
|
557
453
|
|
|
454
|
+
// src/lib/runtime-table.ts
|
|
455
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
456
|
+
var init_runtime_table = __esm({
|
|
457
|
+
"src/lib/runtime-table.ts"() {
|
|
458
|
+
"use strict";
|
|
459
|
+
RUNTIME_TABLE = {
|
|
460
|
+
codex: {
|
|
461
|
+
binary: "codex",
|
|
462
|
+
launchMode: "exec",
|
|
463
|
+
autoApproveFlag: "--full-auto",
|
|
464
|
+
inlineFlag: "--no-alt-screen",
|
|
465
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
466
|
+
defaultModel: "gpt-5.4"
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
DEFAULT_RUNTIME = "claude";
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// src/lib/agent-config.ts
|
|
474
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
475
|
+
import path3 from "path";
|
|
476
|
+
function loadAgentConfig() {
|
|
477
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
478
|
+
try {
|
|
479
|
+
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
480
|
+
} catch {
|
|
481
|
+
return {};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function getAgentRuntime(agentId) {
|
|
485
|
+
const config = loadAgentConfig();
|
|
486
|
+
const entry = config[agentId];
|
|
487
|
+
if (entry) return entry;
|
|
488
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
489
|
+
}
|
|
490
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
491
|
+
var init_agent_config = __esm({
|
|
492
|
+
"src/lib/agent-config.ts"() {
|
|
493
|
+
"use strict";
|
|
494
|
+
init_config();
|
|
495
|
+
init_runtime_table();
|
|
496
|
+
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
497
|
+
DEFAULT_MODELS = {
|
|
498
|
+
claude: "claude-opus-4",
|
|
499
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
500
|
+
opencode: "minimax-m2.7"
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// src/lib/intercom-queue.ts
|
|
506
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
507
|
+
import path4 from "path";
|
|
508
|
+
import os3 from "os";
|
|
509
|
+
function ensureDir() {
|
|
510
|
+
const dir = path4.dirname(QUEUE_PATH);
|
|
511
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
512
|
+
}
|
|
513
|
+
function readQueue() {
|
|
514
|
+
try {
|
|
515
|
+
if (!existsSync4(QUEUE_PATH)) return [];
|
|
516
|
+
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
517
|
+
} catch {
|
|
518
|
+
return [];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function writeQueue(queue) {
|
|
522
|
+
ensureDir();
|
|
523
|
+
const tmp = `${QUEUE_PATH}.tmp`;
|
|
524
|
+
writeFileSync3(tmp, JSON.stringify(queue, null, 2));
|
|
525
|
+
renameSync2(tmp, QUEUE_PATH);
|
|
526
|
+
}
|
|
527
|
+
function queueIntercom(targetSession, reason) {
|
|
528
|
+
const queue = readQueue();
|
|
529
|
+
const existing = queue.find((q) => q.targetSession === targetSession);
|
|
530
|
+
if (existing) {
|
|
531
|
+
existing.attempts++;
|
|
532
|
+
existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
533
|
+
existing.reason = reason;
|
|
534
|
+
} else {
|
|
535
|
+
queue.push({
|
|
536
|
+
targetSession,
|
|
537
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
538
|
+
attempts: 0,
|
|
539
|
+
reason
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
writeQueue(queue);
|
|
543
|
+
}
|
|
544
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
545
|
+
var init_intercom_queue = __esm({
|
|
546
|
+
"src/lib/intercom-queue.ts"() {
|
|
547
|
+
"use strict";
|
|
548
|
+
QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
549
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
550
|
+
INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// src/lib/db-retry.ts
|
|
555
|
+
function isBusyError(err) {
|
|
556
|
+
if (err instanceof Error) {
|
|
557
|
+
const msg = err.message.toLowerCase();
|
|
558
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
559
|
+
}
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
function delay(ms) {
|
|
563
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
564
|
+
}
|
|
565
|
+
async function retryOnBusy(fn, label) {
|
|
566
|
+
let lastError;
|
|
567
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
568
|
+
try {
|
|
569
|
+
return await fn();
|
|
570
|
+
} catch (err) {
|
|
571
|
+
lastError = err;
|
|
572
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
573
|
+
throw err;
|
|
574
|
+
}
|
|
575
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
576
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
577
|
+
process.stderr.write(
|
|
578
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
579
|
+
`
|
|
580
|
+
);
|
|
581
|
+
await delay(backoff + jitter);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
throw lastError;
|
|
585
|
+
}
|
|
586
|
+
function wrapWithRetry(client) {
|
|
587
|
+
return new Proxy(client, {
|
|
588
|
+
get(target, prop, receiver) {
|
|
589
|
+
if (prop === "execute") {
|
|
590
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
591
|
+
}
|
|
592
|
+
if (prop === "batch") {
|
|
593
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
594
|
+
}
|
|
595
|
+
return Reflect.get(target, prop, receiver);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
600
|
+
var init_db_retry = __esm({
|
|
601
|
+
"src/lib/db-retry.ts"() {
|
|
602
|
+
"use strict";
|
|
603
|
+
MAX_RETRIES = 3;
|
|
604
|
+
BASE_DELAY_MS = 200;
|
|
605
|
+
MAX_JITTER_MS = 300;
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
558
609
|
// src/lib/employees.ts
|
|
559
610
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
560
|
-
import { existsSync as
|
|
611
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
561
612
|
import { execSync as execSync3 } from "child_process";
|
|
562
|
-
import
|
|
613
|
+
import path5 from "path";
|
|
563
614
|
import os4 from "os";
|
|
564
615
|
function normalizeRole(role) {
|
|
565
616
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -578,9 +629,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
578
629
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
579
630
|
}
|
|
580
631
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
581
|
-
if (!
|
|
632
|
+
if (!existsSync5(employeesPath)) return [];
|
|
582
633
|
try {
|
|
583
|
-
return JSON.parse(
|
|
634
|
+
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
584
635
|
} catch {
|
|
585
636
|
return [];
|
|
586
637
|
}
|
|
@@ -599,13 +650,450 @@ var init_employees = __esm({
|
|
|
599
650
|
"src/lib/employees.ts"() {
|
|
600
651
|
"use strict";
|
|
601
652
|
init_config();
|
|
602
|
-
EMPLOYEES_PATH =
|
|
653
|
+
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
603
654
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
604
655
|
COORDINATOR_ROLE = "COO";
|
|
605
656
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
606
657
|
}
|
|
607
658
|
});
|
|
608
659
|
|
|
660
|
+
// src/lib/exe-daemon-client.ts
|
|
661
|
+
import net from "net";
|
|
662
|
+
import { spawn } from "child_process";
|
|
663
|
+
import { randomUUID } from "crypto";
|
|
664
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
|
|
665
|
+
import path6 from "path";
|
|
666
|
+
import { fileURLToPath } from "url";
|
|
667
|
+
function handleData(chunk) {
|
|
668
|
+
_buffer += chunk.toString();
|
|
669
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
670
|
+
_buffer = "";
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
let newlineIdx;
|
|
674
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
675
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
676
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
677
|
+
if (!line) continue;
|
|
678
|
+
try {
|
|
679
|
+
const response = JSON.parse(line);
|
|
680
|
+
const id = response.id;
|
|
681
|
+
if (!id) continue;
|
|
682
|
+
const entry = _pending.get(id);
|
|
683
|
+
if (entry) {
|
|
684
|
+
clearTimeout(entry.timer);
|
|
685
|
+
_pending.delete(id);
|
|
686
|
+
entry.resolve(response);
|
|
687
|
+
}
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function cleanupStaleFiles() {
|
|
693
|
+
if (existsSync6(PID_PATH)) {
|
|
694
|
+
try {
|
|
695
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
696
|
+
if (pid > 0) {
|
|
697
|
+
try {
|
|
698
|
+
process.kill(pid, 0);
|
|
699
|
+
return;
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} catch {
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
unlinkSync2(PID_PATH);
|
|
707
|
+
} catch {
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
unlinkSync2(SOCKET_PATH);
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function findPackageRoot() {
|
|
716
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
717
|
+
const { root } = path6.parse(dir);
|
|
718
|
+
while (dir !== root) {
|
|
719
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
720
|
+
dir = path6.dirname(dir);
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function spawnDaemon() {
|
|
725
|
+
const pkgRoot = findPackageRoot();
|
|
726
|
+
if (!pkgRoot) {
|
|
727
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
731
|
+
if (!existsSync6(daemonPath)) {
|
|
732
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
733
|
+
`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const resolvedPath = daemonPath;
|
|
737
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
738
|
+
`);
|
|
739
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
740
|
+
let stderrFd = "ignore";
|
|
741
|
+
try {
|
|
742
|
+
stderrFd = openSync(logPath, "a");
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
746
|
+
detached: true,
|
|
747
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
748
|
+
env: {
|
|
749
|
+
...process.env,
|
|
750
|
+
TMUX: void 0,
|
|
751
|
+
// Daemon is global — must not inherit session scope
|
|
752
|
+
TMUX_PANE: void 0,
|
|
753
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
754
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
755
|
+
EXE_DAEMON_PID: PID_PATH
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
child.unref();
|
|
759
|
+
if (typeof stderrFd === "number") {
|
|
760
|
+
try {
|
|
761
|
+
closeSync(stderrFd);
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function acquireSpawnLock() {
|
|
767
|
+
try {
|
|
768
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
769
|
+
closeSync(fd);
|
|
770
|
+
return true;
|
|
771
|
+
} catch {
|
|
772
|
+
try {
|
|
773
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
774
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
775
|
+
try {
|
|
776
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
777
|
+
} catch {
|
|
778
|
+
}
|
|
779
|
+
try {
|
|
780
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
781
|
+
closeSync(fd);
|
|
782
|
+
return true;
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function releaseSpawnLock() {
|
|
792
|
+
try {
|
|
793
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
794
|
+
} catch {
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function connectToSocket() {
|
|
798
|
+
return new Promise((resolve) => {
|
|
799
|
+
if (_socket && _connected) {
|
|
800
|
+
resolve(true);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
804
|
+
const connectTimeout = setTimeout(() => {
|
|
805
|
+
socket.destroy();
|
|
806
|
+
resolve(false);
|
|
807
|
+
}, 2e3);
|
|
808
|
+
socket.on("connect", () => {
|
|
809
|
+
clearTimeout(connectTimeout);
|
|
810
|
+
_socket = socket;
|
|
811
|
+
_connected = true;
|
|
812
|
+
_buffer = "";
|
|
813
|
+
socket.on("data", handleData);
|
|
814
|
+
socket.on("close", () => {
|
|
815
|
+
_connected = false;
|
|
816
|
+
_socket = null;
|
|
817
|
+
for (const [id, entry] of _pending) {
|
|
818
|
+
clearTimeout(entry.timer);
|
|
819
|
+
_pending.delete(id);
|
|
820
|
+
entry.resolve({ error: "Connection closed" });
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
socket.on("error", () => {
|
|
824
|
+
_connected = false;
|
|
825
|
+
_socket = null;
|
|
826
|
+
});
|
|
827
|
+
resolve(true);
|
|
828
|
+
});
|
|
829
|
+
socket.on("error", () => {
|
|
830
|
+
clearTimeout(connectTimeout);
|
|
831
|
+
resolve(false);
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
async function connectEmbedDaemon() {
|
|
836
|
+
if (_socket && _connected) return true;
|
|
837
|
+
if (await connectToSocket()) return true;
|
|
838
|
+
if (acquireSpawnLock()) {
|
|
839
|
+
try {
|
|
840
|
+
cleanupStaleFiles();
|
|
841
|
+
spawnDaemon();
|
|
842
|
+
} finally {
|
|
843
|
+
releaseSpawnLock();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const start = Date.now();
|
|
847
|
+
let delay2 = 100;
|
|
848
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
849
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
850
|
+
if (await connectToSocket()) return true;
|
|
851
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
852
|
+
}
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
856
|
+
return new Promise((resolve) => {
|
|
857
|
+
if (!_socket || !_connected) {
|
|
858
|
+
resolve({ error: "Not connected" });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const id = randomUUID();
|
|
862
|
+
const timer = setTimeout(() => {
|
|
863
|
+
_pending.delete(id);
|
|
864
|
+
resolve({ error: "Request timeout" });
|
|
865
|
+
}, timeoutMs);
|
|
866
|
+
_pending.set(id, { resolve, timer });
|
|
867
|
+
try {
|
|
868
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
869
|
+
} catch {
|
|
870
|
+
clearTimeout(timer);
|
|
871
|
+
_pending.delete(id);
|
|
872
|
+
resolve({ error: "Write failed" });
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
function isClientConnected() {
|
|
877
|
+
return _connected;
|
|
878
|
+
}
|
|
879
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
880
|
+
var init_exe_daemon_client = __esm({
|
|
881
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
882
|
+
"use strict";
|
|
883
|
+
init_config();
|
|
884
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
885
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
886
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
887
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
888
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
889
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
890
|
+
_socket = null;
|
|
891
|
+
_connected = false;
|
|
892
|
+
_buffer = "";
|
|
893
|
+
_pending = /* @__PURE__ */ new Map();
|
|
894
|
+
MAX_BUFFER = 1e7;
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// src/lib/daemon-protocol.ts
|
|
899
|
+
function serializeValue(v) {
|
|
900
|
+
if (v === null || v === void 0) return null;
|
|
901
|
+
if (typeof v === "bigint") return Number(v);
|
|
902
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
903
|
+
if (v instanceof Uint8Array) {
|
|
904
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
905
|
+
}
|
|
906
|
+
if (ArrayBuffer.isView(v)) {
|
|
907
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
908
|
+
}
|
|
909
|
+
if (v instanceof ArrayBuffer) {
|
|
910
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
911
|
+
}
|
|
912
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
913
|
+
return String(v);
|
|
914
|
+
}
|
|
915
|
+
function deserializeValue(v) {
|
|
916
|
+
if (v === null) return null;
|
|
917
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
918
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
919
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
920
|
+
}
|
|
921
|
+
return v;
|
|
922
|
+
}
|
|
923
|
+
function deserializeResultSet(srs) {
|
|
924
|
+
const rows = srs.rows.map((obj) => {
|
|
925
|
+
const values = srs.columns.map(
|
|
926
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
927
|
+
);
|
|
928
|
+
const row = values;
|
|
929
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
930
|
+
const col = srs.columns[i];
|
|
931
|
+
if (col !== void 0) {
|
|
932
|
+
row[col] = values[i] ?? null;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
Object.defineProperty(row, "length", {
|
|
936
|
+
value: values.length,
|
|
937
|
+
enumerable: false
|
|
938
|
+
});
|
|
939
|
+
return row;
|
|
940
|
+
});
|
|
941
|
+
return {
|
|
942
|
+
columns: srs.columns,
|
|
943
|
+
columnTypes: srs.columnTypes ?? [],
|
|
944
|
+
rows,
|
|
945
|
+
rowsAffected: srs.rowsAffected,
|
|
946
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
947
|
+
toJSON: () => ({
|
|
948
|
+
columns: srs.columns,
|
|
949
|
+
columnTypes: srs.columnTypes ?? [],
|
|
950
|
+
rows: srs.rows,
|
|
951
|
+
rowsAffected: srs.rowsAffected,
|
|
952
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
953
|
+
})
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
var init_daemon_protocol = __esm({
|
|
957
|
+
"src/lib/daemon-protocol.ts"() {
|
|
958
|
+
"use strict";
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// src/lib/db-daemon-client.ts
|
|
963
|
+
var db_daemon_client_exports = {};
|
|
964
|
+
__export(db_daemon_client_exports, {
|
|
965
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
966
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
967
|
+
});
|
|
968
|
+
function normalizeStatement(stmt) {
|
|
969
|
+
if (typeof stmt === "string") {
|
|
970
|
+
return { sql: stmt, args: [] };
|
|
971
|
+
}
|
|
972
|
+
const sql = stmt.sql;
|
|
973
|
+
let args = [];
|
|
974
|
+
if (Array.isArray(stmt.args)) {
|
|
975
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
976
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
977
|
+
const named = {};
|
|
978
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
979
|
+
named[key] = serializeValue(val);
|
|
980
|
+
}
|
|
981
|
+
return { sql, args: named };
|
|
982
|
+
}
|
|
983
|
+
return { sql, args };
|
|
984
|
+
}
|
|
985
|
+
function createDaemonDbClient(fallbackClient) {
|
|
986
|
+
let _useDaemon = false;
|
|
987
|
+
const client = {
|
|
988
|
+
async execute(stmt) {
|
|
989
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
990
|
+
return fallbackClient.execute(stmt);
|
|
991
|
+
}
|
|
992
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
993
|
+
const response = await sendDaemonRequest({
|
|
994
|
+
type: "db-execute",
|
|
995
|
+
sql,
|
|
996
|
+
args
|
|
997
|
+
});
|
|
998
|
+
if (response.error) {
|
|
999
|
+
const errMsg = String(response.error);
|
|
1000
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1001
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
1002
|
+
`);
|
|
1003
|
+
return fallbackClient.execute(stmt);
|
|
1004
|
+
}
|
|
1005
|
+
throw new Error(errMsg);
|
|
1006
|
+
}
|
|
1007
|
+
if (response.db) {
|
|
1008
|
+
return deserializeResultSet(response.db);
|
|
1009
|
+
}
|
|
1010
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
1011
|
+
return fallbackClient.execute(stmt);
|
|
1012
|
+
},
|
|
1013
|
+
async batch(stmts, mode) {
|
|
1014
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1015
|
+
return fallbackClient.batch(stmts, mode);
|
|
1016
|
+
}
|
|
1017
|
+
const statements = stmts.map(normalizeStatement);
|
|
1018
|
+
const response = await sendDaemonRequest({
|
|
1019
|
+
type: "db-batch",
|
|
1020
|
+
statements,
|
|
1021
|
+
mode: mode ?? "deferred"
|
|
1022
|
+
});
|
|
1023
|
+
if (response.error) {
|
|
1024
|
+
const errMsg = String(response.error);
|
|
1025
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1026
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
1027
|
+
`);
|
|
1028
|
+
return fallbackClient.batch(stmts, mode);
|
|
1029
|
+
}
|
|
1030
|
+
throw new Error(errMsg);
|
|
1031
|
+
}
|
|
1032
|
+
const batchResults = response["db-batch"];
|
|
1033
|
+
if (batchResults) {
|
|
1034
|
+
return batchResults.map(deserializeResultSet);
|
|
1035
|
+
}
|
|
1036
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
1037
|
+
return fallbackClient.batch(stmts, mode);
|
|
1038
|
+
},
|
|
1039
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
1040
|
+
async transaction(mode) {
|
|
1041
|
+
return fallbackClient.transaction(mode);
|
|
1042
|
+
},
|
|
1043
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
1044
|
+
async executeMultiple(sql) {
|
|
1045
|
+
return fallbackClient.executeMultiple(sql);
|
|
1046
|
+
},
|
|
1047
|
+
// migrate — delegate to fallback
|
|
1048
|
+
async migrate(stmts) {
|
|
1049
|
+
return fallbackClient.migrate(stmts);
|
|
1050
|
+
},
|
|
1051
|
+
// Sync mode — delegate to fallback
|
|
1052
|
+
sync() {
|
|
1053
|
+
return fallbackClient.sync();
|
|
1054
|
+
},
|
|
1055
|
+
close() {
|
|
1056
|
+
_useDaemon = false;
|
|
1057
|
+
},
|
|
1058
|
+
get closed() {
|
|
1059
|
+
return fallbackClient.closed;
|
|
1060
|
+
},
|
|
1061
|
+
get protocol() {
|
|
1062
|
+
return fallbackClient.protocol;
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
return {
|
|
1066
|
+
...client,
|
|
1067
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1068
|
+
_enableDaemon() {
|
|
1069
|
+
_useDaemon = true;
|
|
1070
|
+
},
|
|
1071
|
+
/** Check if daemon routing is active */
|
|
1072
|
+
_isDaemonActive() {
|
|
1073
|
+
return _useDaemon && isClientConnected();
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1078
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1079
|
+
const connected = await connectEmbedDaemon();
|
|
1080
|
+
if (!connected) {
|
|
1081
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1085
|
+
client._enableDaemon();
|
|
1086
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1087
|
+
return client;
|
|
1088
|
+
}
|
|
1089
|
+
var init_db_daemon_client = __esm({
|
|
1090
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1091
|
+
"use strict";
|
|
1092
|
+
init_exe_daemon_client();
|
|
1093
|
+
init_daemon_protocol();
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
609
1097
|
// src/lib/database.ts
|
|
610
1098
|
var database_exports = {};
|
|
611
1099
|
__export(database_exports, {
|
|
@@ -614,6 +1102,7 @@ __export(database_exports, {
|
|
|
614
1102
|
ensureSchema: () => ensureSchema,
|
|
615
1103
|
getClient: () => getClient,
|
|
616
1104
|
getRawClient: () => getRawClient,
|
|
1105
|
+
initDaemonClient: () => initDaemonClient,
|
|
617
1106
|
initDatabase: () => initDatabase,
|
|
618
1107
|
initTurso: () => initTurso,
|
|
619
1108
|
isInitialized: () => isInitialized
|
|
@@ -641,8 +1130,27 @@ function getClient() {
|
|
|
641
1130
|
if (!_resilientClient) {
|
|
642
1131
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
643
1132
|
}
|
|
1133
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1134
|
+
return _resilientClient;
|
|
1135
|
+
}
|
|
1136
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1137
|
+
return _daemonClient;
|
|
1138
|
+
}
|
|
644
1139
|
return _resilientClient;
|
|
645
1140
|
}
|
|
1141
|
+
async function initDaemonClient() {
|
|
1142
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1143
|
+
if (!_resilientClient) return;
|
|
1144
|
+
try {
|
|
1145
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1146
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
process.stderr.write(
|
|
1149
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1150
|
+
`
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
646
1154
|
function getRawClient() {
|
|
647
1155
|
if (!_client) {
|
|
648
1156
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -1129,6 +1637,12 @@ async function ensureSchema() {
|
|
|
1129
1637
|
} catch {
|
|
1130
1638
|
}
|
|
1131
1639
|
}
|
|
1640
|
+
try {
|
|
1641
|
+
await client.execute(
|
|
1642
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1643
|
+
);
|
|
1644
|
+
} catch {
|
|
1645
|
+
}
|
|
1132
1646
|
await client.executeMultiple(`
|
|
1133
1647
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1134
1648
|
id TEXT PRIMARY KEY,
|
|
@@ -1181,7 +1695,30 @@ async function ensureSchema() {
|
|
|
1181
1695
|
entity_id TEXT NOT NULL,
|
|
1182
1696
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1183
1697
|
);
|
|
1698
|
+
|
|
1699
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1700
|
+
name,
|
|
1701
|
+
content=entities,
|
|
1702
|
+
content_rowid=rowid
|
|
1703
|
+
);
|
|
1704
|
+
|
|
1705
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1706
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1707
|
+
END;
|
|
1708
|
+
|
|
1709
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1710
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1711
|
+
END;
|
|
1712
|
+
|
|
1713
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1714
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1715
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1716
|
+
END;
|
|
1184
1717
|
`);
|
|
1718
|
+
try {
|
|
1719
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1720
|
+
} catch {
|
|
1721
|
+
}
|
|
1185
1722
|
await client.executeMultiple(`
|
|
1186
1723
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1187
1724
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1362,6 +1899,33 @@ async function ensureSchema() {
|
|
|
1362
1899
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1363
1900
|
ON conversations(channel_id);
|
|
1364
1901
|
`);
|
|
1902
|
+
await client.executeMultiple(`
|
|
1903
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1904
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1905
|
+
agent_id TEXT NOT NULL,
|
|
1906
|
+
session_name TEXT,
|
|
1907
|
+
task_id TEXT,
|
|
1908
|
+
project_name TEXT,
|
|
1909
|
+
started_at TEXT NOT NULL
|
|
1910
|
+
);
|
|
1911
|
+
|
|
1912
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1913
|
+
ON session_agent_map(agent_id);
|
|
1914
|
+
`);
|
|
1915
|
+
try {
|
|
1916
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1917
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1918
|
+
await client.execute({
|
|
1919
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1920
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1921
|
+
FROM memories
|
|
1922
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1923
|
+
GROUP BY session_id, agent_id`,
|
|
1924
|
+
args: []
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
} catch {
|
|
1928
|
+
}
|
|
1365
1929
|
try {
|
|
1366
1930
|
await client.execute({
|
|
1367
1931
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1495,15 +2059,41 @@ async function ensureSchema() {
|
|
|
1495
2059
|
});
|
|
1496
2060
|
} catch {
|
|
1497
2061
|
}
|
|
2062
|
+
for (const col of [
|
|
2063
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2064
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2065
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2066
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2067
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2068
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2069
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2070
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2071
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2072
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2073
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2074
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2075
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2076
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2077
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2078
|
+
]) {
|
|
2079
|
+
try {
|
|
2080
|
+
await client.execute(col);
|
|
2081
|
+
} catch {
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
1498
2084
|
}
|
|
1499
2085
|
async function disposeDatabase() {
|
|
2086
|
+
if (_daemonClient) {
|
|
2087
|
+
_daemonClient.close();
|
|
2088
|
+
_daemonClient = null;
|
|
2089
|
+
}
|
|
1500
2090
|
if (_client) {
|
|
1501
2091
|
_client.close();
|
|
1502
2092
|
_client = null;
|
|
1503
2093
|
_resilientClient = null;
|
|
1504
2094
|
}
|
|
1505
2095
|
}
|
|
1506
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2096
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1507
2097
|
var init_database = __esm({
|
|
1508
2098
|
"src/lib/database.ts"() {
|
|
1509
2099
|
"use strict";
|
|
@@ -1511,24 +2101,25 @@ var init_database = __esm({
|
|
|
1511
2101
|
init_employees();
|
|
1512
2102
|
_client = null;
|
|
1513
2103
|
_resilientClient = null;
|
|
2104
|
+
_daemonClient = null;
|
|
1514
2105
|
initTurso = initDatabase;
|
|
1515
2106
|
disposeTurso = disposeDatabase;
|
|
1516
2107
|
}
|
|
1517
2108
|
});
|
|
1518
2109
|
|
|
1519
2110
|
// src/lib/license.ts
|
|
1520
|
-
import { readFileSync as
|
|
1521
|
-
import { randomUUID } from "crypto";
|
|
1522
|
-
import
|
|
2111
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2112
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2113
|
+
import path7 from "path";
|
|
1523
2114
|
import { jwtVerify, importSPKI } from "jose";
|
|
1524
2115
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1525
2116
|
var init_license = __esm({
|
|
1526
2117
|
"src/lib/license.ts"() {
|
|
1527
2118
|
"use strict";
|
|
1528
2119
|
init_config();
|
|
1529
|
-
LICENSE_PATH =
|
|
1530
|
-
CACHE_PATH =
|
|
1531
|
-
DEVICE_ID_PATH =
|
|
2120
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
2121
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
2122
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1532
2123
|
PLAN_LIMITS = {
|
|
1533
2124
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1534
2125
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1540,12 +2131,12 @@ var init_license = __esm({
|
|
|
1540
2131
|
});
|
|
1541
2132
|
|
|
1542
2133
|
// src/lib/plan-limits.ts
|
|
1543
|
-
import { readFileSync as
|
|
1544
|
-
import
|
|
2134
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
2135
|
+
import path8 from "path";
|
|
1545
2136
|
function getLicenseSync() {
|
|
1546
2137
|
try {
|
|
1547
|
-
if (!
|
|
1548
|
-
const raw = JSON.parse(
|
|
2138
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2139
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
1549
2140
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1550
2141
|
const parts = raw.token.split(".");
|
|
1551
2142
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1583,8 +2174,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1583
2174
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1584
2175
|
let count = 0;
|
|
1585
2176
|
try {
|
|
1586
|
-
if (
|
|
1587
|
-
const raw =
|
|
2177
|
+
if (existsSync8(filePath)) {
|
|
2178
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
1588
2179
|
const employees = JSON.parse(raw);
|
|
1589
2180
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1590
2181
|
}
|
|
@@ -1613,19 +2204,19 @@ var init_plan_limits = __esm({
|
|
|
1613
2204
|
this.name = "PlanLimitError";
|
|
1614
2205
|
}
|
|
1615
2206
|
};
|
|
1616
|
-
CACHE_PATH2 =
|
|
2207
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
1617
2208
|
}
|
|
1618
2209
|
});
|
|
1619
2210
|
|
|
1620
2211
|
// src/lib/notifications.ts
|
|
1621
2212
|
import crypto from "crypto";
|
|
1622
|
-
import
|
|
2213
|
+
import path9 from "path";
|
|
1623
2214
|
import os5 from "os";
|
|
1624
2215
|
import {
|
|
1625
|
-
readFileSync as
|
|
2216
|
+
readFileSync as readFileSync9,
|
|
1626
2217
|
readdirSync,
|
|
1627
|
-
unlinkSync as
|
|
1628
|
-
existsSync as
|
|
2218
|
+
unlinkSync as unlinkSync3,
|
|
2219
|
+
existsSync as existsSync9,
|
|
1629
2220
|
rmdirSync
|
|
1630
2221
|
} from "fs";
|
|
1631
2222
|
async function writeNotification(notification) {
|
|
@@ -1776,10 +2367,11 @@ __export(tasks_crud_exports, {
|
|
|
1776
2367
|
writeCheckpoint: () => writeCheckpoint
|
|
1777
2368
|
});
|
|
1778
2369
|
import crypto3 from "crypto";
|
|
1779
|
-
import
|
|
2370
|
+
import path10 from "path";
|
|
2371
|
+
import os6 from "os";
|
|
1780
2372
|
import { execSync as execSync4 } from "child_process";
|
|
1781
2373
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1782
|
-
import { existsSync as
|
|
2374
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
1783
2375
|
async function writeCheckpoint(input) {
|
|
1784
2376
|
const client = getClient();
|
|
1785
2377
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1820,6 +2412,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1820
2412
|
function slugify(title) {
|
|
1821
2413
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1822
2414
|
}
|
|
2415
|
+
function buildKeywordIndex() {
|
|
2416
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2417
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2418
|
+
for (const kw of keywords) {
|
|
2419
|
+
const existing = idx.get(kw) ?? [];
|
|
2420
|
+
existing.push(role);
|
|
2421
|
+
idx.set(kw, existing);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return idx;
|
|
2425
|
+
}
|
|
2426
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2427
|
+
const employees = loadEmployeesSync();
|
|
2428
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2429
|
+
if (!employee) return void 0;
|
|
2430
|
+
const assigneeRole = employee.role;
|
|
2431
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2432
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2433
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2434
|
+
if (text.includes(keyword)) {
|
|
2435
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2439
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2440
|
+
if (assigneeRole === "COO") return void 0;
|
|
2441
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2442
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2443
|
+
}
|
|
1823
2444
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1824
2445
|
const scope = sessionScopeFilter(scopeSession);
|
|
1825
2446
|
let result = await client.execute({
|
|
@@ -1869,7 +2490,14 @@ async function createTaskCore(input) {
|
|
|
1869
2490
|
const id = crypto3.randomUUID();
|
|
1870
2491
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1871
2492
|
const slug = slugify(input.title);
|
|
1872
|
-
|
|
2493
|
+
let earlySessionScope = null;
|
|
2494
|
+
try {
|
|
2495
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2496
|
+
earlySessionScope = resolveExeSession2();
|
|
2497
|
+
} catch {
|
|
2498
|
+
}
|
|
2499
|
+
const scope = earlySessionScope ?? "default";
|
|
2500
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1873
2501
|
let blockedById = null;
|
|
1874
2502
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1875
2503
|
if (input.blockedBy) {
|
|
@@ -1909,22 +2537,24 @@ async function createTaskCore(input) {
|
|
|
1909
2537
|
if (dupCheck.rows.length > 0) {
|
|
1910
2538
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1911
2539
|
}
|
|
2540
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2541
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2542
|
+
if (laneWarning) {
|
|
2543
|
+
warning = warning ? `${warning}
|
|
2544
|
+
${laneWarning}` : laneWarning;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
1912
2547
|
if (input.baseDir) {
|
|
1913
2548
|
try {
|
|
1914
|
-
await mkdir3(
|
|
1915
|
-
await mkdir3(
|
|
2549
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2550
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1916
2551
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1917
2552
|
await ensureGitignoreExe(input.baseDir);
|
|
1918
2553
|
} catch {
|
|
1919
2554
|
}
|
|
1920
2555
|
}
|
|
1921
2556
|
const complexity = input.complexity ?? "standard";
|
|
1922
|
-
|
|
1923
|
-
try {
|
|
1924
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1925
|
-
sessionScope = resolveExeSession2();
|
|
1926
|
-
} catch {
|
|
1927
|
-
}
|
|
2557
|
+
const sessionScope = earlySessionScope;
|
|
1928
2558
|
await client.execute({
|
|
1929
2559
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
1930
2560
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1951,6 +2581,43 @@ async function createTaskCore(input) {
|
|
|
1951
2581
|
now
|
|
1952
2582
|
]
|
|
1953
2583
|
});
|
|
2584
|
+
if (input.baseDir) {
|
|
2585
|
+
try {
|
|
2586
|
+
const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
|
|
2587
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2588
|
+
const mdDir = path10.dirname(mdPath);
|
|
2589
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2590
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2591
|
+
const mdContent = `# ${input.title}
|
|
2592
|
+
|
|
2593
|
+
**ID:** ${id}
|
|
2594
|
+
**Status:** ${initialStatus}
|
|
2595
|
+
**Priority:** ${input.priority}
|
|
2596
|
+
**Assigned by:** ${input.assignedBy}
|
|
2597
|
+
**Assigned to:** ${input.assignedTo}
|
|
2598
|
+
**Project:** ${input.projectName}
|
|
2599
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2600
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2601
|
+
**Reviewer:** ${reviewer}
|
|
2602
|
+
|
|
2603
|
+
## Context
|
|
2604
|
+
|
|
2605
|
+
${input.context}
|
|
2606
|
+
|
|
2607
|
+
## MANDATORY: When done
|
|
2608
|
+
|
|
2609
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2610
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2611
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2612
|
+
`;
|
|
2613
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2614
|
+
} catch (err) {
|
|
2615
|
+
process.stderr.write(
|
|
2616
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2617
|
+
`
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
1954
2621
|
return {
|
|
1955
2622
|
id,
|
|
1956
2623
|
title: input.title,
|
|
@@ -2143,7 +2810,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2143
2810
|
return { row, taskFile, now, taskId };
|
|
2144
2811
|
}
|
|
2145
2812
|
}
|
|
2146
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2813
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2147
2814
|
process.stderr.write(
|
|
2148
2815
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2149
2816
|
`
|
|
@@ -2208,9 +2875,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2208
2875
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2209
2876
|
}
|
|
2210
2877
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2211
|
-
const archPath =
|
|
2878
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2212
2879
|
try {
|
|
2213
|
-
if (
|
|
2880
|
+
if (existsSync10(archPath)) return;
|
|
2214
2881
|
const template = [
|
|
2215
2882
|
`# ${projectName} \u2014 System Architecture`,
|
|
2216
2883
|
"",
|
|
@@ -2243,10 +2910,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2243
2910
|
}
|
|
2244
2911
|
}
|
|
2245
2912
|
async function ensureGitignoreExe(baseDir) {
|
|
2246
|
-
const gitignorePath =
|
|
2913
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2247
2914
|
try {
|
|
2248
|
-
if (
|
|
2249
|
-
const content =
|
|
2915
|
+
if (existsSync10(gitignorePath)) {
|
|
2916
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2250
2917
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2251
2918
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2252
2919
|
} else {
|
|
@@ -2255,20 +2922,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2255
2922
|
} catch {
|
|
2256
2923
|
}
|
|
2257
2924
|
}
|
|
2258
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2925
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2259
2926
|
var init_tasks_crud = __esm({
|
|
2260
2927
|
"src/lib/tasks-crud.ts"() {
|
|
2261
2928
|
"use strict";
|
|
2262
2929
|
init_database();
|
|
2263
2930
|
init_task_scope();
|
|
2931
|
+
init_employees();
|
|
2932
|
+
LANE_KEYWORDS = {
|
|
2933
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2934
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2935
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2936
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2937
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2938
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2939
|
+
};
|
|
2940
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2264
2941
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2265
2942
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2266
2943
|
}
|
|
2267
2944
|
});
|
|
2268
2945
|
|
|
2269
2946
|
// src/lib/tasks-review.ts
|
|
2270
|
-
import
|
|
2271
|
-
import { existsSync as
|
|
2947
|
+
import path11 from "path";
|
|
2948
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2272
2949
|
async function countPendingReviews(sessionScope) {
|
|
2273
2950
|
const client = getClient();
|
|
2274
2951
|
if (sessionScope) {
|
|
@@ -2290,7 +2967,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2290
2967
|
const result2 = await client.execute({
|
|
2291
2968
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2292
2969
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2293
|
-
AND
|
|
2970
|
+
AND session_scope = ?`,
|
|
2294
2971
|
args: [sinceIso, sessionScope]
|
|
2295
2972
|
});
|
|
2296
2973
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2308,7 +2985,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2308
2985
|
const result2 = await client.execute({
|
|
2309
2986
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2310
2987
|
WHERE status = 'needs_review'
|
|
2311
|
-
AND
|
|
2988
|
+
AND session_scope = ?
|
|
2312
2989
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2313
2990
|
args: [sessionScope, limit]
|
|
2314
2991
|
});
|
|
@@ -2429,14 +3106,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2429
3106
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2430
3107
|
const agent = parts[1];
|
|
2431
3108
|
const slug = parts.slice(2).join("-");
|
|
2432
|
-
const
|
|
3109
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2433
3110
|
const result = await client.execute({
|
|
2434
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2435
|
-
args: [now,
|
|
3111
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3112
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2436
3113
|
});
|
|
2437
3114
|
if (result.rowsAffected > 0) {
|
|
2438
3115
|
process.stderr.write(
|
|
2439
|
-
`[review-cleanup] Cascaded original task to done
|
|
3116
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2440
3117
|
`
|
|
2441
3118
|
);
|
|
2442
3119
|
}
|
|
@@ -2449,11 +3126,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2449
3126
|
);
|
|
2450
3127
|
}
|
|
2451
3128
|
try {
|
|
2452
|
-
const cacheDir =
|
|
2453
|
-
if (
|
|
3129
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3130
|
+
if (existsSync11(cacheDir)) {
|
|
2454
3131
|
for (const f of readdirSync2(cacheDir)) {
|
|
2455
3132
|
if (f.startsWith("review-notified-")) {
|
|
2456
|
-
|
|
3133
|
+
unlinkSync4(path11.join(cacheDir, f));
|
|
2457
3134
|
}
|
|
2458
3135
|
}
|
|
2459
3136
|
}
|
|
@@ -2474,7 +3151,7 @@ var init_tasks_review = __esm({
|
|
|
2474
3151
|
});
|
|
2475
3152
|
|
|
2476
3153
|
// src/lib/tasks-chain.ts
|
|
2477
|
-
import
|
|
3154
|
+
import path12 from "path";
|
|
2478
3155
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2479
3156
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2480
3157
|
const client = getClient();
|
|
@@ -2491,7 +3168,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2491
3168
|
});
|
|
2492
3169
|
for (const ur of unblockedRows.rows) {
|
|
2493
3170
|
try {
|
|
2494
|
-
const ubFile =
|
|
3171
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2495
3172
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2496
3173
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2497
3174
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2560,7 +3237,7 @@ var init_tasks_chain = __esm({
|
|
|
2560
3237
|
|
|
2561
3238
|
// src/lib/project-name.ts
|
|
2562
3239
|
import { execSync as execSync5 } from "child_process";
|
|
2563
|
-
import
|
|
3240
|
+
import path13 from "path";
|
|
2564
3241
|
function getProjectName(cwd) {
|
|
2565
3242
|
const dir = cwd ?? process.cwd();
|
|
2566
3243
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2573,7 +3250,7 @@ function getProjectName(cwd) {
|
|
|
2573
3250
|
timeout: 2e3,
|
|
2574
3251
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2575
3252
|
}).trim();
|
|
2576
|
-
repoRoot =
|
|
3253
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2577
3254
|
} catch {
|
|
2578
3255
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2579
3256
|
cwd: dir,
|
|
@@ -2582,11 +3259,11 @@ function getProjectName(cwd) {
|
|
|
2582
3259
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2583
3260
|
}).trim();
|
|
2584
3261
|
}
|
|
2585
|
-
_cached2 =
|
|
3262
|
+
_cached2 = path13.basename(repoRoot);
|
|
2586
3263
|
_cachedCwd = dir;
|
|
2587
3264
|
return _cached2;
|
|
2588
3265
|
} catch {
|
|
2589
|
-
_cached2 =
|
|
3266
|
+
_cached2 = path13.basename(dir);
|
|
2590
3267
|
_cachedCwd = dir;
|
|
2591
3268
|
return _cached2;
|
|
2592
3269
|
}
|
|
@@ -2618,7 +3295,7 @@ function findSessionForProject(projectName) {
|
|
|
2618
3295
|
const sessions = listSessions();
|
|
2619
3296
|
for (const s of sessions) {
|
|
2620
3297
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2621
|
-
if (proj === projectName &&
|
|
3298
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2622
3299
|
}
|
|
2623
3300
|
return null;
|
|
2624
3301
|
}
|
|
@@ -2664,7 +3341,7 @@ var init_session_scope = __esm({
|
|
|
2664
3341
|
|
|
2665
3342
|
// src/lib/tasks-notify.ts
|
|
2666
3343
|
async function dispatchTaskToEmployee(input) {
|
|
2667
|
-
if (
|
|
3344
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2668
3345
|
let crossProject = false;
|
|
2669
3346
|
if (input.projectName) {
|
|
2670
3347
|
try {
|
|
@@ -3059,8 +3736,8 @@ __export(tasks_exports, {
|
|
|
3059
3736
|
updateTaskStatus: () => updateTaskStatus,
|
|
3060
3737
|
writeCheckpoint: () => writeCheckpoint
|
|
3061
3738
|
});
|
|
3062
|
-
import
|
|
3063
|
-
import { writeFileSync as
|
|
3739
|
+
import path14 from "path";
|
|
3740
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3064
3741
|
async function createTask(input) {
|
|
3065
3742
|
const result = await createTaskCore(input);
|
|
3066
3743
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3079,14 +3756,14 @@ async function updateTask(input) {
|
|
|
3079
3756
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3080
3757
|
try {
|
|
3081
3758
|
const agent = String(row.assigned_to);
|
|
3082
|
-
const cacheDir =
|
|
3083
|
-
const cachePath =
|
|
3759
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
3760
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3084
3761
|
if (input.status === "in_progress") {
|
|
3085
|
-
|
|
3086
|
-
|
|
3762
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
3763
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3087
3764
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3088
3765
|
try {
|
|
3089
|
-
|
|
3766
|
+
unlinkSync5(cachePath);
|
|
3090
3767
|
} catch {
|
|
3091
3768
|
}
|
|
3092
3769
|
}
|
|
@@ -3143,7 +3820,7 @@ async function updateTask(input) {
|
|
|
3143
3820
|
}
|
|
3144
3821
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3145
3822
|
if (isTerminal) {
|
|
3146
|
-
const isCoordinator =
|
|
3823
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3147
3824
|
if (!isCoordinator) {
|
|
3148
3825
|
notifyTaskDone();
|
|
3149
3826
|
}
|
|
@@ -3168,7 +3845,7 @@ async function updateTask(input) {
|
|
|
3168
3845
|
}
|
|
3169
3846
|
}
|
|
3170
3847
|
}
|
|
3171
|
-
if (input.status === "done" &&
|
|
3848
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3172
3849
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3173
3850
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3174
3851
|
taskId,
|
|
@@ -3184,7 +3861,7 @@ async function updateTask(input) {
|
|
|
3184
3861
|
});
|
|
3185
3862
|
}
|
|
3186
3863
|
let nextTask;
|
|
3187
|
-
if (isTerminal &&
|
|
3864
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3188
3865
|
try {
|
|
3189
3866
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3190
3867
|
} catch {
|
|
@@ -3528,7 +4205,7 @@ var init_capacity_monitor = __esm({
|
|
|
3528
4205
|
// src/lib/tmux-routing.ts
|
|
3529
4206
|
var tmux_routing_exports = {};
|
|
3530
4207
|
__export(tmux_routing_exports, {
|
|
3531
|
-
acquireSpawnLock: () =>
|
|
4208
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3532
4209
|
employeeSessionName: () => employeeSessionName,
|
|
3533
4210
|
ensureEmployee: () => ensureEmployee,
|
|
3534
4211
|
extractRootExe: () => extractRootExe,
|
|
@@ -3543,20 +4220,20 @@ __export(tmux_routing_exports, {
|
|
|
3543
4220
|
notifyParentExe: () => notifyParentExe,
|
|
3544
4221
|
parseParentExe: () => parseParentExe,
|
|
3545
4222
|
registerParentExe: () => registerParentExe,
|
|
3546
|
-
releaseSpawnLock: () =>
|
|
4223
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3547
4224
|
resolveExeSession: () => resolveExeSession,
|
|
3548
4225
|
sendIntercom: () => sendIntercom,
|
|
3549
4226
|
spawnEmployee: () => spawnEmployee,
|
|
3550
4227
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3551
4228
|
});
|
|
3552
4229
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3553
|
-
import { readFileSync as
|
|
3554
|
-
import
|
|
3555
|
-
import
|
|
3556
|
-
import { fileURLToPath } from "url";
|
|
3557
|
-
import { unlinkSync as
|
|
4230
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
|
|
4231
|
+
import path15 from "path";
|
|
4232
|
+
import os7 from "os";
|
|
4233
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4234
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3558
4235
|
function spawnLockPath(sessionName) {
|
|
3559
|
-
return
|
|
4236
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3560
4237
|
}
|
|
3561
4238
|
function isProcessAlive(pid) {
|
|
3562
4239
|
try {
|
|
@@ -3566,14 +4243,14 @@ function isProcessAlive(pid) {
|
|
|
3566
4243
|
return false;
|
|
3567
4244
|
}
|
|
3568
4245
|
}
|
|
3569
|
-
function
|
|
3570
|
-
if (!
|
|
3571
|
-
|
|
4246
|
+
function acquireSpawnLock2(sessionName) {
|
|
4247
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4248
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3572
4249
|
}
|
|
3573
4250
|
const lockFile = spawnLockPath(sessionName);
|
|
3574
|
-
if (
|
|
4251
|
+
if (existsSync12(lockFile)) {
|
|
3575
4252
|
try {
|
|
3576
|
-
const lock = JSON.parse(
|
|
4253
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
3577
4254
|
const age = Date.now() - lock.timestamp;
|
|
3578
4255
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3579
4256
|
return false;
|
|
@@ -3581,25 +4258,25 @@ function acquireSpawnLock(sessionName) {
|
|
|
3581
4258
|
} catch {
|
|
3582
4259
|
}
|
|
3583
4260
|
}
|
|
3584
|
-
|
|
4261
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3585
4262
|
return true;
|
|
3586
4263
|
}
|
|
3587
|
-
function
|
|
4264
|
+
function releaseSpawnLock2(sessionName) {
|
|
3588
4265
|
try {
|
|
3589
|
-
|
|
4266
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3590
4267
|
} catch {
|
|
3591
4268
|
}
|
|
3592
4269
|
}
|
|
3593
4270
|
function resolveBehaviorsExporterScript() {
|
|
3594
4271
|
try {
|
|
3595
|
-
const thisFile =
|
|
3596
|
-
const scriptPath =
|
|
3597
|
-
|
|
4272
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4273
|
+
const scriptPath = path15.join(
|
|
4274
|
+
path15.dirname(thisFile),
|
|
3598
4275
|
"..",
|
|
3599
4276
|
"bin",
|
|
3600
4277
|
"exe-export-behaviors.js"
|
|
3601
4278
|
);
|
|
3602
|
-
return
|
|
4279
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
3603
4280
|
} catch {
|
|
3604
4281
|
return null;
|
|
3605
4282
|
}
|
|
@@ -3665,12 +4342,12 @@ function extractRootExe(name) {
|
|
|
3665
4342
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3666
4343
|
}
|
|
3667
4344
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3668
|
-
if (!
|
|
3669
|
-
|
|
4345
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4346
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3670
4347
|
}
|
|
3671
4348
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3672
|
-
const filePath =
|
|
3673
|
-
|
|
4349
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4350
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3674
4351
|
parentExe: rootExe,
|
|
3675
4352
|
dispatchedBy: dispatchedBy || rootExe,
|
|
3676
4353
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3678,7 +4355,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3678
4355
|
}
|
|
3679
4356
|
function getParentExe(sessionKey) {
|
|
3680
4357
|
try {
|
|
3681
|
-
const data = JSON.parse(
|
|
4358
|
+
const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3682
4359
|
return data.parentExe || null;
|
|
3683
4360
|
} catch {
|
|
3684
4361
|
return null;
|
|
@@ -3686,8 +4363,8 @@ function getParentExe(sessionKey) {
|
|
|
3686
4363
|
}
|
|
3687
4364
|
function getDispatchedBy(sessionKey) {
|
|
3688
4365
|
try {
|
|
3689
|
-
const data = JSON.parse(
|
|
3690
|
-
|
|
4366
|
+
const data = JSON.parse(readFileSync11(
|
|
4367
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3691
4368
|
"utf8"
|
|
3692
4369
|
));
|
|
3693
4370
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3713,10 +4390,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3713
4390
|
}
|
|
3714
4391
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3715
4392
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3716
|
-
if (!isAlive(base) &&
|
|
4393
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3717
4394
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3718
4395
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3719
|
-
if (!isAlive(candidate) &&
|
|
4396
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3720
4397
|
}
|
|
3721
4398
|
return null;
|
|
3722
4399
|
}
|
|
@@ -3748,32 +4425,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3748
4425
|
}
|
|
3749
4426
|
function readDebounceState() {
|
|
3750
4427
|
try {
|
|
3751
|
-
if (!
|
|
3752
|
-
|
|
4428
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4429
|
+
const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
4430
|
+
const state = {};
|
|
4431
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
4432
|
+
if (typeof val === "number") {
|
|
4433
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
4434
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
4435
|
+
state[key] = val;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
return state;
|
|
3753
4439
|
} catch {
|
|
3754
4440
|
return {};
|
|
3755
4441
|
}
|
|
3756
4442
|
}
|
|
3757
4443
|
function writeDebounceState(state) {
|
|
3758
4444
|
try {
|
|
3759
|
-
if (!
|
|
3760
|
-
|
|
4445
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4446
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3761
4447
|
} catch {
|
|
3762
4448
|
}
|
|
3763
4449
|
}
|
|
3764
4450
|
function isDebounced(targetSession) {
|
|
3765
4451
|
const state = readDebounceState();
|
|
3766
|
-
const
|
|
3767
|
-
|
|
4452
|
+
const entry = state[targetSession];
|
|
4453
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
4454
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
4455
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
4456
|
+
state[targetSession].pending++;
|
|
4457
|
+
writeDebounceState(state);
|
|
4458
|
+
return true;
|
|
4459
|
+
}
|
|
4460
|
+
return false;
|
|
3768
4461
|
}
|
|
3769
4462
|
function recordDebounce(targetSession) {
|
|
3770
4463
|
const state = readDebounceState();
|
|
3771
|
-
state[targetSession]
|
|
4464
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
4465
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
3772
4466
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
3773
4467
|
for (const key of Object.keys(state)) {
|
|
3774
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4468
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
3775
4469
|
}
|
|
3776
4470
|
writeDebounceState(state);
|
|
4471
|
+
return batched;
|
|
3777
4472
|
}
|
|
3778
4473
|
function logIntercom(msg) {
|
|
3779
4474
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -3818,7 +4513,7 @@ function sendIntercom(targetSession) {
|
|
|
3818
4513
|
return "skipped_exe";
|
|
3819
4514
|
}
|
|
3820
4515
|
if (isDebounced(targetSession)) {
|
|
3821
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
4516
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
3822
4517
|
return "debounced";
|
|
3823
4518
|
}
|
|
3824
4519
|
try {
|
|
@@ -3830,14 +4525,14 @@ function sendIntercom(targetSession) {
|
|
|
3830
4525
|
const sessionState = getSessionState(targetSession);
|
|
3831
4526
|
if (sessionState === "no_claude") {
|
|
3832
4527
|
queueIntercom(targetSession, "claude not running in session");
|
|
3833
|
-
recordDebounce(targetSession);
|
|
3834
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
4528
|
+
const batched2 = recordDebounce(targetSession);
|
|
4529
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3835
4530
|
return "queued";
|
|
3836
4531
|
}
|
|
3837
4532
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3838
4533
|
queueIntercom(targetSession, "session busy at send time");
|
|
3839
|
-
recordDebounce(targetSession);
|
|
3840
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
4534
|
+
const batched2 = recordDebounce(targetSession);
|
|
4535
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3841
4536
|
return "queued";
|
|
3842
4537
|
}
|
|
3843
4538
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -3845,8 +4540,8 @@ function sendIntercom(targetSession) {
|
|
|
3845
4540
|
transport.sendKeys(targetSession, "q");
|
|
3846
4541
|
}
|
|
3847
4542
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3848
|
-
recordDebounce(targetSession);
|
|
3849
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4543
|
+
const batched = recordDebounce(targetSession);
|
|
4544
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
3850
4545
|
return "delivered";
|
|
3851
4546
|
} catch {
|
|
3852
4547
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3876,7 +4571,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3876
4571
|
return true;
|
|
3877
4572
|
}
|
|
3878
4573
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3879
|
-
if (
|
|
4574
|
+
if (isCoordinatorName(employeeName)) {
|
|
3880
4575
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3881
4576
|
}
|
|
3882
4577
|
try {
|
|
@@ -3948,26 +4643,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3948
4643
|
const transport = getTransport();
|
|
3949
4644
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3950
4645
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3951
|
-
const logDir =
|
|
3952
|
-
const logFile =
|
|
3953
|
-
if (!
|
|
3954
|
-
|
|
4646
|
+
const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4647
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4648
|
+
if (!existsSync12(logDir)) {
|
|
4649
|
+
mkdirSync6(logDir, { recursive: true });
|
|
3955
4650
|
}
|
|
3956
4651
|
transport.kill(sessionName);
|
|
3957
4652
|
let cleanupSuffix = "";
|
|
3958
4653
|
try {
|
|
3959
|
-
const thisFile =
|
|
3960
|
-
const cleanupScript =
|
|
3961
|
-
if (
|
|
4654
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4655
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4656
|
+
if (existsSync12(cleanupScript)) {
|
|
3962
4657
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3963
4658
|
}
|
|
3964
4659
|
} catch {
|
|
3965
4660
|
}
|
|
3966
4661
|
try {
|
|
3967
|
-
const claudeJsonPath =
|
|
4662
|
+
const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
|
|
3968
4663
|
let claudeJson = {};
|
|
3969
4664
|
try {
|
|
3970
|
-
claudeJson = JSON.parse(
|
|
4665
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
3971
4666
|
} catch {
|
|
3972
4667
|
}
|
|
3973
4668
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3975,17 +4670,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3975
4670
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3976
4671
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3977
4672
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3978
|
-
|
|
4673
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3979
4674
|
} catch {
|
|
3980
4675
|
}
|
|
3981
4676
|
try {
|
|
3982
|
-
const settingsDir =
|
|
4677
|
+
const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
|
|
3983
4678
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3984
|
-
const projSettingsDir =
|
|
3985
|
-
const settingsPath =
|
|
4679
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
4680
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
3986
4681
|
let settings = {};
|
|
3987
4682
|
try {
|
|
3988
|
-
settings = JSON.parse(
|
|
4683
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
3989
4684
|
} catch {
|
|
3990
4685
|
}
|
|
3991
4686
|
const perms = settings.permissions ?? {};
|
|
@@ -4013,21 +4708,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4013
4708
|
if (changed) {
|
|
4014
4709
|
perms.allow = allow;
|
|
4015
4710
|
settings.permissions = perms;
|
|
4016
|
-
|
|
4017
|
-
|
|
4711
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4712
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4018
4713
|
}
|
|
4019
4714
|
} catch {
|
|
4020
4715
|
}
|
|
4021
4716
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
4022
4717
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
4023
|
-
const
|
|
4718
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
4719
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
4720
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
4721
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
4024
4722
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
4025
4723
|
let identityFlag = "";
|
|
4026
4724
|
let behaviorsFlag = "";
|
|
4027
4725
|
let legacyFallbackWarned = false;
|
|
4028
4726
|
if (!useExeAgent && !useBinSymlink) {
|
|
4029
|
-
const identityPath =
|
|
4030
|
-
|
|
4727
|
+
const identityPath = path15.join(
|
|
4728
|
+
os7.homedir(),
|
|
4031
4729
|
".exe-os",
|
|
4032
4730
|
"identity",
|
|
4033
4731
|
`${employeeName}.md`
|
|
@@ -4036,13 +4734,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4036
4734
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4037
4735
|
if (hasAgentFlag) {
|
|
4038
4736
|
identityFlag = ` --agent ${employeeName}`;
|
|
4039
|
-
} else if (
|
|
4737
|
+
} else if (existsSync12(identityPath)) {
|
|
4040
4738
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4041
4739
|
legacyFallbackWarned = true;
|
|
4042
4740
|
}
|
|
4043
4741
|
const behaviorsFile = exportBehaviorsSync(
|
|
4044
4742
|
employeeName,
|
|
4045
|
-
|
|
4743
|
+
path15.basename(spawnCwd),
|
|
4046
4744
|
sessionName
|
|
4047
4745
|
);
|
|
4048
4746
|
if (behaviorsFile) {
|
|
@@ -4057,16 +4755,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4057
4755
|
}
|
|
4058
4756
|
let sessionContextFlag = "";
|
|
4059
4757
|
try {
|
|
4060
|
-
const ctxDir =
|
|
4061
|
-
|
|
4062
|
-
const ctxFile =
|
|
4758
|
+
const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4759
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
4760
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4063
4761
|
const ctxContent = [
|
|
4064
4762
|
`## Session Context`,
|
|
4065
4763
|
`You are running in tmux session: ${sessionName}.`,
|
|
4066
4764
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4067
4765
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4068
4766
|
].join("\n");
|
|
4069
|
-
|
|
4767
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
4070
4768
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4071
4769
|
} catch {
|
|
4072
4770
|
}
|
|
@@ -4080,9 +4778,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4080
4778
|
}
|
|
4081
4779
|
}
|
|
4082
4780
|
}
|
|
4781
|
+
if (useCodex) {
|
|
4782
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
4783
|
+
if (codexCfg?.apiKeyEnv) {
|
|
4784
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
4785
|
+
if (keyVal) {
|
|
4786
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
4790
|
+
}
|
|
4791
|
+
if (useOpencode) {
|
|
4792
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
4793
|
+
if (ocCfg?.apiKeyEnv) {
|
|
4794
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
4795
|
+
if (keyVal) {
|
|
4796
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4800
|
+
}
|
|
4801
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
4802
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
4803
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
4804
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4083
4807
|
let spawnCommand;
|
|
4084
4808
|
if (useExeAgent) {
|
|
4085
4809
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
4810
|
+
} else if (useCodex) {
|
|
4811
|
+
process.stderr.write(
|
|
4812
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
4813
|
+
`
|
|
4814
|
+
);
|
|
4815
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
4816
|
+
} else if (useOpencode) {
|
|
4817
|
+
const binName = `${employeeName}-opencode`;
|
|
4818
|
+
process.stderr.write(
|
|
4819
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
4820
|
+
`
|
|
4821
|
+
);
|
|
4822
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4086
4823
|
} else if (useBinSymlink) {
|
|
4087
4824
|
const binName = `${employeeName}-${ccProvider}`;
|
|
4088
4825
|
process.stderr.write(
|
|
@@ -4098,17 +4835,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4098
4835
|
command: spawnCommand
|
|
4099
4836
|
});
|
|
4100
4837
|
if (spawnResult.error) {
|
|
4101
|
-
|
|
4838
|
+
releaseSpawnLock2(sessionName);
|
|
4102
4839
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4103
4840
|
}
|
|
4104
4841
|
transport.pipeLog(sessionName, logFile);
|
|
4105
4842
|
try {
|
|
4106
4843
|
const mySession = getMySession();
|
|
4107
|
-
const dispatchInfo =
|
|
4108
|
-
|
|
4844
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4845
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4109
4846
|
dispatchedBy: mySession,
|
|
4110
4847
|
rootExe: exeSession,
|
|
4111
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
4848
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
4849
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
4850
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
4112
4851
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4113
4852
|
}));
|
|
4114
4853
|
} catch {
|
|
@@ -4126,6 +4865,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4126
4865
|
booted = true;
|
|
4127
4866
|
break;
|
|
4128
4867
|
}
|
|
4868
|
+
} else if (useCodex) {
|
|
4869
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
4870
|
+
booted = true;
|
|
4871
|
+
break;
|
|
4872
|
+
}
|
|
4129
4873
|
} else {
|
|
4130
4874
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
4131
4875
|
booted = true;
|
|
@@ -4136,10 +4880,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4136
4880
|
}
|
|
4137
4881
|
}
|
|
4138
4882
|
if (!booted) {
|
|
4139
|
-
|
|
4140
|
-
|
|
4883
|
+
releaseSpawnLock2(sessionName);
|
|
4884
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
4885
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
4141
4886
|
}
|
|
4142
|
-
if (!useExeAgent) {
|
|
4887
|
+
if (!useExeAgent && !useCodex) {
|
|
4143
4888
|
try {
|
|
4144
4889
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
4145
4890
|
} catch {
|
|
@@ -4153,7 +4898,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4153
4898
|
pid: 0,
|
|
4154
4899
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4155
4900
|
});
|
|
4156
|
-
|
|
4901
|
+
releaseSpawnLock2(sessionName);
|
|
4157
4902
|
return { sessionName };
|
|
4158
4903
|
}
|
|
4159
4904
|
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
@@ -4166,17 +4911,19 @@ var init_tmux_routing = __esm({
|
|
|
4166
4911
|
init_cc_agent_support();
|
|
4167
4912
|
init_mcp_prefix();
|
|
4168
4913
|
init_provider_table();
|
|
4914
|
+
init_agent_config();
|
|
4915
|
+
init_runtime_table();
|
|
4169
4916
|
init_intercom_queue();
|
|
4170
4917
|
init_plan_limits();
|
|
4171
4918
|
init_employees();
|
|
4172
|
-
SPAWN_LOCK_DIR =
|
|
4173
|
-
SESSION_CACHE =
|
|
4919
|
+
SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4920
|
+
SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4174
4921
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4175
4922
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4176
4923
|
VERIFY_PANE_LINES = 200;
|
|
4177
4924
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4178
|
-
INTERCOM_LOG2 =
|
|
4179
|
-
DEBOUNCE_FILE =
|
|
4925
|
+
INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4926
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4180
4927
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4181
4928
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4182
4929
|
}
|
|
@@ -4217,14 +4964,14 @@ var init_memory = __esm({
|
|
|
4217
4964
|
|
|
4218
4965
|
// src/lib/keychain.ts
|
|
4219
4966
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4220
|
-
import { existsSync as
|
|
4221
|
-
import
|
|
4222
|
-
import
|
|
4967
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4968
|
+
import path16 from "path";
|
|
4969
|
+
import os8 from "os";
|
|
4223
4970
|
function getKeyDir() {
|
|
4224
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4971
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
|
|
4225
4972
|
}
|
|
4226
4973
|
function getKeyPath() {
|
|
4227
|
-
return
|
|
4974
|
+
return path16.join(getKeyDir(), "master.key");
|
|
4228
4975
|
}
|
|
4229
4976
|
async function tryKeytar() {
|
|
4230
4977
|
try {
|
|
@@ -4245,13 +4992,21 @@ async function getMasterKey() {
|
|
|
4245
4992
|
}
|
|
4246
4993
|
}
|
|
4247
4994
|
const keyPath = getKeyPath();
|
|
4248
|
-
if (!
|
|
4995
|
+
if (!existsSync13(keyPath)) {
|
|
4996
|
+
process.stderr.write(
|
|
4997
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4998
|
+
`
|
|
4999
|
+
);
|
|
4249
5000
|
return null;
|
|
4250
5001
|
}
|
|
4251
5002
|
try {
|
|
4252
5003
|
const content = await readFile4(keyPath, "utf-8");
|
|
4253
5004
|
return Buffer.from(content.trim(), "base64");
|
|
4254
|
-
} catch {
|
|
5005
|
+
} catch (err) {
|
|
5006
|
+
process.stderr.write(
|
|
5007
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
5008
|
+
`
|
|
5009
|
+
);
|
|
4255
5010
|
return null;
|
|
4256
5011
|
}
|
|
4257
5012
|
}
|
|
@@ -4277,13 +5032,13 @@ __export(shard_manager_exports, {
|
|
|
4277
5032
|
listShards: () => listShards,
|
|
4278
5033
|
shardExists: () => shardExists
|
|
4279
5034
|
});
|
|
4280
|
-
import
|
|
4281
|
-
import { existsSync as
|
|
5035
|
+
import path17 from "path";
|
|
5036
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
|
|
4282
5037
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4283
5038
|
function initShardManager(encryptionKey) {
|
|
4284
5039
|
_encryptionKey = encryptionKey;
|
|
4285
|
-
if (!
|
|
4286
|
-
|
|
5040
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5041
|
+
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
4287
5042
|
}
|
|
4288
5043
|
_shardingEnabled = true;
|
|
4289
5044
|
}
|
|
@@ -4303,7 +5058,7 @@ function getShardClient(projectName) {
|
|
|
4303
5058
|
}
|
|
4304
5059
|
const cached = _shards.get(safeName);
|
|
4305
5060
|
if (cached) return cached;
|
|
4306
|
-
const dbPath =
|
|
5061
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
4307
5062
|
const client = createClient2({
|
|
4308
5063
|
url: `file:${dbPath}`,
|
|
4309
5064
|
encryptionKey: _encryptionKey
|
|
@@ -4313,10 +5068,10 @@ function getShardClient(projectName) {
|
|
|
4313
5068
|
}
|
|
4314
5069
|
function shardExists(projectName) {
|
|
4315
5070
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4316
|
-
return
|
|
5071
|
+
return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
4317
5072
|
}
|
|
4318
5073
|
function listShards() {
|
|
4319
|
-
if (!
|
|
5074
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
4320
5075
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4321
5076
|
}
|
|
4322
5077
|
async function ensureShardSchema(client) {
|
|
@@ -4502,7 +5257,7 @@ var init_shard_manager = __esm({
|
|
|
4502
5257
|
"src/lib/shard-manager.ts"() {
|
|
4503
5258
|
"use strict";
|
|
4504
5259
|
init_config();
|
|
4505
|
-
SHARDS_DIR =
|
|
5260
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
4506
5261
|
_shards = /* @__PURE__ */ new Map();
|
|
4507
5262
|
_encryptionKey = null;
|
|
4508
5263
|
_shardingEnabled = false;
|
|
@@ -4627,7 +5382,7 @@ __export(global_procedures_exports, {
|
|
|
4627
5382
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4628
5383
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4629
5384
|
});
|
|
4630
|
-
import { randomUUID as
|
|
5385
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4631
5386
|
async function loadGlobalProcedures() {
|
|
4632
5387
|
const client = getClient();
|
|
4633
5388
|
const result = await client.execute({
|
|
@@ -4656,7 +5411,7 @@ ${sections.join("\n\n")}
|
|
|
4656
5411
|
`;
|
|
4657
5412
|
}
|
|
4658
5413
|
async function storeGlobalProcedure(input) {
|
|
4659
|
-
const id =
|
|
5414
|
+
const id = randomUUID3();
|
|
4660
5415
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4661
5416
|
const client = getClient();
|
|
4662
5417
|
await client.execute({
|
|
@@ -4707,6 +5462,7 @@ __export(store_exports, {
|
|
|
4707
5462
|
vectorToBlob: () => vectorToBlob,
|
|
4708
5463
|
writeMemory: () => writeMemory
|
|
4709
5464
|
});
|
|
5465
|
+
import { createHash } from "crypto";
|
|
4710
5466
|
function isBusyError2(err) {
|
|
4711
5467
|
if (err instanceof Error) {
|
|
4712
5468
|
const msg = err.message.toLowerCase();
|
|
@@ -4780,12 +5536,52 @@ function classifyTier(record) {
|
|
|
4780
5536
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4781
5537
|
return 3;
|
|
4782
5538
|
}
|
|
5539
|
+
function inferFilePaths(record) {
|
|
5540
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5541
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5542
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5543
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5544
|
+
}
|
|
5545
|
+
function inferCommitHash(record) {
|
|
5546
|
+
if (record.tool_name !== "Bash") return null;
|
|
5547
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5548
|
+
return match ? match[1] : null;
|
|
5549
|
+
}
|
|
5550
|
+
function inferLanguageType(record) {
|
|
5551
|
+
const text = record.raw_text;
|
|
5552
|
+
if (!text || text.length < 10) return null;
|
|
5553
|
+
const trimmed = text.trimStart();
|
|
5554
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5555
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5556
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5557
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5558
|
+
return "mixed";
|
|
5559
|
+
}
|
|
5560
|
+
function inferDomain(record) {
|
|
5561
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5562
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5563
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5564
|
+
return null;
|
|
5565
|
+
}
|
|
4783
5566
|
async function writeMemory(record) {
|
|
4784
5567
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4785
5568
|
throw new Error(
|
|
4786
5569
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4787
5570
|
);
|
|
4788
5571
|
}
|
|
5572
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5573
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
try {
|
|
5577
|
+
const client = getClient();
|
|
5578
|
+
const existing = await client.execute({
|
|
5579
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5580
|
+
args: [contentHash, record.agent_id]
|
|
5581
|
+
});
|
|
5582
|
+
if (existing.rows.length > 0) return;
|
|
5583
|
+
} catch {
|
|
5584
|
+
}
|
|
4789
5585
|
const dbRow = {
|
|
4790
5586
|
id: record.id,
|
|
4791
5587
|
agent_id: record.agent_id,
|
|
@@ -4815,7 +5611,23 @@ async function writeMemory(record) {
|
|
|
4815
5611
|
supersedes_id: record.supersedes_id ?? null,
|
|
4816
5612
|
draft: record.draft ? 1 : 0,
|
|
4817
5613
|
memory_type: record.memory_type ?? "raw",
|
|
4818
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5614
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5615
|
+
content_hash: contentHash,
|
|
5616
|
+
intent: record.intent ?? null,
|
|
5617
|
+
outcome: record.outcome ?? null,
|
|
5618
|
+
domain: record.domain ?? inferDomain(record),
|
|
5619
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5620
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5621
|
+
chain_position: record.chain_position ?? null,
|
|
5622
|
+
review_status: record.review_status ?? null,
|
|
5623
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5624
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5625
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5626
|
+
duration_ms: record.duration_ms ?? null,
|
|
5627
|
+
token_cost: record.token_cost ?? null,
|
|
5628
|
+
audience: record.audience ?? null,
|
|
5629
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5630
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4819
5631
|
};
|
|
4820
5632
|
_pendingRecords.push(dbRow);
|
|
4821
5633
|
orgBus.emit({
|
|
@@ -4873,80 +5685,85 @@ async function flushBatch() {
|
|
|
4873
5685
|
const draft = row.draft ? 1 : 0;
|
|
4874
5686
|
const memoryType = row.memory_type ?? "raw";
|
|
4875
5687
|
const trajectory = row.trajectory ?? null;
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
5688
|
+
const contentHash = row.content_hash ?? null;
|
|
5689
|
+
const intent = row.intent ?? null;
|
|
5690
|
+
const outcome = row.outcome ?? null;
|
|
5691
|
+
const domain = row.domain ?? null;
|
|
5692
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5693
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5694
|
+
const chainPosition = row.chain_position ?? null;
|
|
5695
|
+
const reviewStatus = row.review_status ?? null;
|
|
5696
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5697
|
+
const filePaths = row.file_paths ?? null;
|
|
5698
|
+
const commitHash = row.commit_hash ?? null;
|
|
5699
|
+
const durationMs = row.duration_ms ?? null;
|
|
5700
|
+
const tokenCost = row.token_cost ?? null;
|
|
5701
|
+
const audience = row.audience ?? null;
|
|
5702
|
+
const languageType = row.language_type ?? null;
|
|
5703
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5704
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4879
5705
|
tool_name, project_name,
|
|
4880
5706
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4881
5707
|
confidence, last_accessed,
|
|
4882
5708
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4883
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
pageNumber,
|
|
4942
|
-
sourcePath,
|
|
4943
|
-
sourceType,
|
|
4944
|
-
tier,
|
|
4945
|
-
supersedesId,
|
|
4946
|
-
draft,
|
|
4947
|
-
memoryType,
|
|
4948
|
-
trajectory
|
|
4949
|
-
]
|
|
5709
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5710
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5711
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5712
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5713
|
+
const metaArgs = [
|
|
5714
|
+
intent,
|
|
5715
|
+
outcome,
|
|
5716
|
+
domain,
|
|
5717
|
+
referencedEntities,
|
|
5718
|
+
retrievalCount,
|
|
5719
|
+
chainPosition,
|
|
5720
|
+
reviewStatus,
|
|
5721
|
+
contextWindowPct,
|
|
5722
|
+
filePaths,
|
|
5723
|
+
commitHash,
|
|
5724
|
+
durationMs,
|
|
5725
|
+
tokenCost,
|
|
5726
|
+
audience,
|
|
5727
|
+
languageType,
|
|
5728
|
+
parentMemoryId
|
|
5729
|
+
];
|
|
5730
|
+
const baseArgs = [
|
|
5731
|
+
row.id,
|
|
5732
|
+
row.agent_id,
|
|
5733
|
+
row.agent_role,
|
|
5734
|
+
row.session_id,
|
|
5735
|
+
row.timestamp,
|
|
5736
|
+
row.tool_name,
|
|
5737
|
+
row.project_name,
|
|
5738
|
+
row.has_error,
|
|
5739
|
+
row.raw_text
|
|
5740
|
+
];
|
|
5741
|
+
const sharedArgs = [
|
|
5742
|
+
row.version,
|
|
5743
|
+
taskId,
|
|
5744
|
+
importance,
|
|
5745
|
+
status,
|
|
5746
|
+
confidence,
|
|
5747
|
+
lastAccessed,
|
|
5748
|
+
workspaceId,
|
|
5749
|
+
documentId,
|
|
5750
|
+
userId,
|
|
5751
|
+
charOffset,
|
|
5752
|
+
pageNumber,
|
|
5753
|
+
sourcePath,
|
|
5754
|
+
sourceType,
|
|
5755
|
+
tier,
|
|
5756
|
+
supersedesId,
|
|
5757
|
+
draft,
|
|
5758
|
+
memoryType,
|
|
5759
|
+
trajectory,
|
|
5760
|
+
contentHash
|
|
5761
|
+
];
|
|
5762
|
+
return {
|
|
5763
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5764
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5765
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5766
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4950
5767
|
};
|
|
4951
5768
|
};
|
|
4952
5769
|
const globalClient = getClient();
|