@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
package/dist/bin/scan-tasks.js
CHANGED
|
@@ -269,123 +269,19 @@ var init_provider_table = __esm({
|
|
|
269
269
|
}
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
-
// src/lib/intercom-queue.ts
|
|
273
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
274
|
-
import path2 from "path";
|
|
275
|
-
import os2 from "os";
|
|
276
|
-
function ensureDir() {
|
|
277
|
-
const dir = path2.dirname(QUEUE_PATH);
|
|
278
|
-
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
279
|
-
}
|
|
280
|
-
function readQueue() {
|
|
281
|
-
try {
|
|
282
|
-
if (!existsSync2(QUEUE_PATH)) return [];
|
|
283
|
-
return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
|
|
284
|
-
} catch {
|
|
285
|
-
return [];
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
function writeQueue(queue) {
|
|
289
|
-
ensureDir();
|
|
290
|
-
const tmp = `${QUEUE_PATH}.tmp`;
|
|
291
|
-
writeFileSync2(tmp, JSON.stringify(queue, null, 2));
|
|
292
|
-
renameSync(tmp, QUEUE_PATH);
|
|
293
|
-
}
|
|
294
|
-
function queueIntercom(targetSession, reason) {
|
|
295
|
-
const queue = readQueue();
|
|
296
|
-
const existing = queue.find((q) => q.targetSession === targetSession);
|
|
297
|
-
if (existing) {
|
|
298
|
-
existing.attempts++;
|
|
299
|
-
existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
300
|
-
existing.reason = reason;
|
|
301
|
-
} else {
|
|
302
|
-
queue.push({
|
|
303
|
-
targetSession,
|
|
304
|
-
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
305
|
-
attempts: 0,
|
|
306
|
-
reason
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
writeQueue(queue);
|
|
310
|
-
}
|
|
311
|
-
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
312
|
-
var init_intercom_queue = __esm({
|
|
313
|
-
"src/lib/intercom-queue.ts"() {
|
|
314
|
-
"use strict";
|
|
315
|
-
QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
|
|
316
|
-
TTL_MS = 60 * 60 * 1e3;
|
|
317
|
-
INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// src/lib/db-retry.ts
|
|
322
|
-
function isBusyError(err) {
|
|
323
|
-
if (err instanceof Error) {
|
|
324
|
-
const msg = err.message.toLowerCase();
|
|
325
|
-
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
326
|
-
}
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
function delay(ms) {
|
|
330
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
331
|
-
}
|
|
332
|
-
async function retryOnBusy(fn, label) {
|
|
333
|
-
let lastError;
|
|
334
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
335
|
-
try {
|
|
336
|
-
return await fn();
|
|
337
|
-
} catch (err) {
|
|
338
|
-
lastError = err;
|
|
339
|
-
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
340
|
-
throw err;
|
|
341
|
-
}
|
|
342
|
-
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
343
|
-
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
344
|
-
process.stderr.write(
|
|
345
|
-
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
346
|
-
`
|
|
347
|
-
);
|
|
348
|
-
await delay(backoff + jitter);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
throw lastError;
|
|
352
|
-
}
|
|
353
|
-
function wrapWithRetry(client) {
|
|
354
|
-
return new Proxy(client, {
|
|
355
|
-
get(target, prop, receiver) {
|
|
356
|
-
if (prop === "execute") {
|
|
357
|
-
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
358
|
-
}
|
|
359
|
-
if (prop === "batch") {
|
|
360
|
-
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
361
|
-
}
|
|
362
|
-
return Reflect.get(target, prop, receiver);
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
367
|
-
var init_db_retry = __esm({
|
|
368
|
-
"src/lib/db-retry.ts"() {
|
|
369
|
-
"use strict";
|
|
370
|
-
MAX_RETRIES = 3;
|
|
371
|
-
BASE_DELAY_MS = 200;
|
|
372
|
-
MAX_JITTER_MS = 300;
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
|
|
376
272
|
// src/lib/config.ts
|
|
377
273
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
378
|
-
import { readFileSync as
|
|
379
|
-
import
|
|
380
|
-
import
|
|
274
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
|
|
275
|
+
import path2 from "path";
|
|
276
|
+
import os2 from "os";
|
|
381
277
|
function resolveDataDir() {
|
|
382
278
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
383
279
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
384
|
-
const newDir =
|
|
385
|
-
const legacyDir =
|
|
386
|
-
if (!
|
|
280
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
281
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
282
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
387
283
|
try {
|
|
388
|
-
|
|
284
|
+
renameSync(legacyDir, newDir);
|
|
389
285
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
390
286
|
`);
|
|
391
287
|
} catch {
|
|
@@ -447,9 +343,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
447
343
|
async function loadConfig() {
|
|
448
344
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
449
345
|
await mkdir(dir, { recursive: true });
|
|
450
|
-
const configPath =
|
|
451
|
-
if (!
|
|
452
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
346
|
+
const configPath = path2.join(dir, "config.json");
|
|
347
|
+
if (!existsSync2(configPath)) {
|
|
348
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
453
349
|
}
|
|
454
350
|
const raw = await readFile(configPath, "utf-8");
|
|
455
351
|
try {
|
|
@@ -467,13 +363,13 @@ async function loadConfig() {
|
|
|
467
363
|
normalizeScalingRoadmap(migratedCfg);
|
|
468
364
|
normalizeSessionLifecycle(migratedCfg);
|
|
469
365
|
normalizeAutoUpdate(migratedCfg);
|
|
470
|
-
const config = { ...DEFAULT_CONFIG, dbPath:
|
|
366
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
471
367
|
if (config.dbPath.startsWith("~")) {
|
|
472
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
368
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
473
369
|
}
|
|
474
370
|
return config;
|
|
475
371
|
} catch {
|
|
476
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
372
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
477
373
|
}
|
|
478
374
|
}
|
|
479
375
|
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
@@ -481,10 +377,10 @@ var init_config = __esm({
|
|
|
481
377
|
"src/lib/config.ts"() {
|
|
482
378
|
"use strict";
|
|
483
379
|
EXE_AI_DIR = resolveDataDir();
|
|
484
|
-
DB_PATH =
|
|
485
|
-
MODELS_DIR =
|
|
486
|
-
CONFIG_PATH =
|
|
487
|
-
LEGACY_LANCE_PATH =
|
|
380
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
381
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
382
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
383
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
488
384
|
CURRENT_CONFIG_VERSION = 1;
|
|
489
385
|
DEFAULT_CONFIG = {
|
|
490
386
|
config_version: CURRENT_CONFIG_VERSION,
|
|
@@ -556,11 +452,166 @@ var init_config = __esm({
|
|
|
556
452
|
}
|
|
557
453
|
});
|
|
558
454
|
|
|
455
|
+
// src/lib/runtime-table.ts
|
|
456
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
457
|
+
var init_runtime_table = __esm({
|
|
458
|
+
"src/lib/runtime-table.ts"() {
|
|
459
|
+
"use strict";
|
|
460
|
+
RUNTIME_TABLE = {
|
|
461
|
+
codex: {
|
|
462
|
+
binary: "codex",
|
|
463
|
+
launchMode: "exec",
|
|
464
|
+
autoApproveFlag: "--full-auto",
|
|
465
|
+
inlineFlag: "--no-alt-screen",
|
|
466
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
467
|
+
defaultModel: "gpt-5.4"
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
DEFAULT_RUNTIME = "claude";
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// src/lib/agent-config.ts
|
|
475
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
476
|
+
import path3 from "path";
|
|
477
|
+
function loadAgentConfig() {
|
|
478
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
479
|
+
try {
|
|
480
|
+
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
481
|
+
} catch {
|
|
482
|
+
return {};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function getAgentRuntime(agentId) {
|
|
486
|
+
const config = loadAgentConfig();
|
|
487
|
+
const entry = config[agentId];
|
|
488
|
+
if (entry) return entry;
|
|
489
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
490
|
+
}
|
|
491
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
492
|
+
var init_agent_config = __esm({
|
|
493
|
+
"src/lib/agent-config.ts"() {
|
|
494
|
+
"use strict";
|
|
495
|
+
init_config();
|
|
496
|
+
init_runtime_table();
|
|
497
|
+
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
498
|
+
DEFAULT_MODELS = {
|
|
499
|
+
claude: "claude-opus-4",
|
|
500
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
501
|
+
opencode: "minimax-m2.7"
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// src/lib/intercom-queue.ts
|
|
507
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
508
|
+
import path4 from "path";
|
|
509
|
+
import os3 from "os";
|
|
510
|
+
function ensureDir() {
|
|
511
|
+
const dir = path4.dirname(QUEUE_PATH);
|
|
512
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
513
|
+
}
|
|
514
|
+
function readQueue() {
|
|
515
|
+
try {
|
|
516
|
+
if (!existsSync4(QUEUE_PATH)) return [];
|
|
517
|
+
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
518
|
+
} catch {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function writeQueue(queue) {
|
|
523
|
+
ensureDir();
|
|
524
|
+
const tmp = `${QUEUE_PATH}.tmp`;
|
|
525
|
+
writeFileSync3(tmp, JSON.stringify(queue, null, 2));
|
|
526
|
+
renameSync2(tmp, QUEUE_PATH);
|
|
527
|
+
}
|
|
528
|
+
function queueIntercom(targetSession, reason) {
|
|
529
|
+
const queue = readQueue();
|
|
530
|
+
const existing = queue.find((q) => q.targetSession === targetSession);
|
|
531
|
+
if (existing) {
|
|
532
|
+
existing.attempts++;
|
|
533
|
+
existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
534
|
+
existing.reason = reason;
|
|
535
|
+
} else {
|
|
536
|
+
queue.push({
|
|
537
|
+
targetSession,
|
|
538
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
539
|
+
attempts: 0,
|
|
540
|
+
reason
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
writeQueue(queue);
|
|
544
|
+
}
|
|
545
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
546
|
+
var init_intercom_queue = __esm({
|
|
547
|
+
"src/lib/intercom-queue.ts"() {
|
|
548
|
+
"use strict";
|
|
549
|
+
QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
550
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
551
|
+
INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// src/lib/db-retry.ts
|
|
556
|
+
function isBusyError(err) {
|
|
557
|
+
if (err instanceof Error) {
|
|
558
|
+
const msg = err.message.toLowerCase();
|
|
559
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
560
|
+
}
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
function delay(ms) {
|
|
564
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
565
|
+
}
|
|
566
|
+
async function retryOnBusy(fn, label) {
|
|
567
|
+
let lastError;
|
|
568
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
569
|
+
try {
|
|
570
|
+
return await fn();
|
|
571
|
+
} catch (err) {
|
|
572
|
+
lastError = err;
|
|
573
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
574
|
+
throw err;
|
|
575
|
+
}
|
|
576
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
577
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
578
|
+
process.stderr.write(
|
|
579
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
580
|
+
`
|
|
581
|
+
);
|
|
582
|
+
await delay(backoff + jitter);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
throw lastError;
|
|
586
|
+
}
|
|
587
|
+
function wrapWithRetry(client) {
|
|
588
|
+
return new Proxy(client, {
|
|
589
|
+
get(target, prop, receiver) {
|
|
590
|
+
if (prop === "execute") {
|
|
591
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
592
|
+
}
|
|
593
|
+
if (prop === "batch") {
|
|
594
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
595
|
+
}
|
|
596
|
+
return Reflect.get(target, prop, receiver);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
601
|
+
var init_db_retry = __esm({
|
|
602
|
+
"src/lib/db-retry.ts"() {
|
|
603
|
+
"use strict";
|
|
604
|
+
MAX_RETRIES = 3;
|
|
605
|
+
BASE_DELAY_MS = 200;
|
|
606
|
+
MAX_JITTER_MS = 300;
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
559
610
|
// src/lib/employees.ts
|
|
560
611
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
561
|
-
import { existsSync as
|
|
612
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
562
613
|
import { execSync as execSync3 } from "child_process";
|
|
563
|
-
import
|
|
614
|
+
import path5 from "path";
|
|
564
615
|
import os4 from "os";
|
|
565
616
|
function normalizeRole(role) {
|
|
566
617
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -579,9 +630,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
579
630
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
580
631
|
}
|
|
581
632
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
582
|
-
if (!
|
|
633
|
+
if (!existsSync5(employeesPath)) return [];
|
|
583
634
|
try {
|
|
584
|
-
return JSON.parse(
|
|
635
|
+
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
585
636
|
} catch {
|
|
586
637
|
return [];
|
|
587
638
|
}
|
|
@@ -600,13 +651,450 @@ var init_employees = __esm({
|
|
|
600
651
|
"src/lib/employees.ts"() {
|
|
601
652
|
"use strict";
|
|
602
653
|
init_config();
|
|
603
|
-
EMPLOYEES_PATH =
|
|
654
|
+
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
604
655
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
605
656
|
COORDINATOR_ROLE = "COO";
|
|
606
657
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
607
658
|
}
|
|
608
659
|
});
|
|
609
660
|
|
|
661
|
+
// src/lib/exe-daemon-client.ts
|
|
662
|
+
import net from "net";
|
|
663
|
+
import { spawn } from "child_process";
|
|
664
|
+
import { randomUUID } from "crypto";
|
|
665
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
|
|
666
|
+
import path6 from "path";
|
|
667
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
668
|
+
function handleData(chunk) {
|
|
669
|
+
_buffer += chunk.toString();
|
|
670
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
671
|
+
_buffer = "";
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
let newlineIdx;
|
|
675
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
676
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
677
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
678
|
+
if (!line) continue;
|
|
679
|
+
try {
|
|
680
|
+
const response = JSON.parse(line);
|
|
681
|
+
const id = response.id;
|
|
682
|
+
if (!id) continue;
|
|
683
|
+
const entry = _pending.get(id);
|
|
684
|
+
if (entry) {
|
|
685
|
+
clearTimeout(entry.timer);
|
|
686
|
+
_pending.delete(id);
|
|
687
|
+
entry.resolve(response);
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function cleanupStaleFiles() {
|
|
694
|
+
if (existsSync6(PID_PATH)) {
|
|
695
|
+
try {
|
|
696
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
697
|
+
if (pid > 0) {
|
|
698
|
+
try {
|
|
699
|
+
process.kill(pid, 0);
|
|
700
|
+
return;
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
} catch {
|
|
705
|
+
}
|
|
706
|
+
try {
|
|
707
|
+
unlinkSync2(PID_PATH);
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
unlinkSync2(SOCKET_PATH);
|
|
712
|
+
} catch {
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function findPackageRoot() {
|
|
717
|
+
let dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
718
|
+
const { root } = path6.parse(dir);
|
|
719
|
+
while (dir !== root) {
|
|
720
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
721
|
+
dir = path6.dirname(dir);
|
|
722
|
+
}
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
function spawnDaemon() {
|
|
726
|
+
const pkgRoot = findPackageRoot();
|
|
727
|
+
if (!pkgRoot) {
|
|
728
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
732
|
+
if (!existsSync6(daemonPath)) {
|
|
733
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
734
|
+
`);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const resolvedPath = daemonPath;
|
|
738
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
739
|
+
`);
|
|
740
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
741
|
+
let stderrFd = "ignore";
|
|
742
|
+
try {
|
|
743
|
+
stderrFd = openSync(logPath, "a");
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
747
|
+
detached: true,
|
|
748
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
749
|
+
env: {
|
|
750
|
+
...process.env,
|
|
751
|
+
TMUX: void 0,
|
|
752
|
+
// Daemon is global — must not inherit session scope
|
|
753
|
+
TMUX_PANE: void 0,
|
|
754
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
755
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
756
|
+
EXE_DAEMON_PID: PID_PATH
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
child.unref();
|
|
760
|
+
if (typeof stderrFd === "number") {
|
|
761
|
+
try {
|
|
762
|
+
closeSync(stderrFd);
|
|
763
|
+
} catch {
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function acquireSpawnLock() {
|
|
768
|
+
try {
|
|
769
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
770
|
+
closeSync(fd);
|
|
771
|
+
return true;
|
|
772
|
+
} catch {
|
|
773
|
+
try {
|
|
774
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
775
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
776
|
+
try {
|
|
777
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
782
|
+
closeSync(fd);
|
|
783
|
+
return true;
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function releaseSpawnLock() {
|
|
793
|
+
try {
|
|
794
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function connectToSocket() {
|
|
799
|
+
return new Promise((resolve) => {
|
|
800
|
+
if (_socket && _connected) {
|
|
801
|
+
resolve(true);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
805
|
+
const connectTimeout = setTimeout(() => {
|
|
806
|
+
socket.destroy();
|
|
807
|
+
resolve(false);
|
|
808
|
+
}, 2e3);
|
|
809
|
+
socket.on("connect", () => {
|
|
810
|
+
clearTimeout(connectTimeout);
|
|
811
|
+
_socket = socket;
|
|
812
|
+
_connected = true;
|
|
813
|
+
_buffer = "";
|
|
814
|
+
socket.on("data", handleData);
|
|
815
|
+
socket.on("close", () => {
|
|
816
|
+
_connected = false;
|
|
817
|
+
_socket = null;
|
|
818
|
+
for (const [id, entry] of _pending) {
|
|
819
|
+
clearTimeout(entry.timer);
|
|
820
|
+
_pending.delete(id);
|
|
821
|
+
entry.resolve({ error: "Connection closed" });
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
socket.on("error", () => {
|
|
825
|
+
_connected = false;
|
|
826
|
+
_socket = null;
|
|
827
|
+
});
|
|
828
|
+
resolve(true);
|
|
829
|
+
});
|
|
830
|
+
socket.on("error", () => {
|
|
831
|
+
clearTimeout(connectTimeout);
|
|
832
|
+
resolve(false);
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
async function connectEmbedDaemon() {
|
|
837
|
+
if (_socket && _connected) return true;
|
|
838
|
+
if (await connectToSocket()) return true;
|
|
839
|
+
if (acquireSpawnLock()) {
|
|
840
|
+
try {
|
|
841
|
+
cleanupStaleFiles();
|
|
842
|
+
spawnDaemon();
|
|
843
|
+
} finally {
|
|
844
|
+
releaseSpawnLock();
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const start = Date.now();
|
|
848
|
+
let delay2 = 100;
|
|
849
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
850
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
851
|
+
if (await connectToSocket()) return true;
|
|
852
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
853
|
+
}
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
857
|
+
return new Promise((resolve) => {
|
|
858
|
+
if (!_socket || !_connected) {
|
|
859
|
+
resolve({ error: "Not connected" });
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const id = randomUUID();
|
|
863
|
+
const timer = setTimeout(() => {
|
|
864
|
+
_pending.delete(id);
|
|
865
|
+
resolve({ error: "Request timeout" });
|
|
866
|
+
}, timeoutMs);
|
|
867
|
+
_pending.set(id, { resolve, timer });
|
|
868
|
+
try {
|
|
869
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
870
|
+
} catch {
|
|
871
|
+
clearTimeout(timer);
|
|
872
|
+
_pending.delete(id);
|
|
873
|
+
resolve({ error: "Write failed" });
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
function isClientConnected() {
|
|
878
|
+
return _connected;
|
|
879
|
+
}
|
|
880
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
881
|
+
var init_exe_daemon_client = __esm({
|
|
882
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
883
|
+
"use strict";
|
|
884
|
+
init_config();
|
|
885
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
886
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
887
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
888
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
889
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
890
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
891
|
+
_socket = null;
|
|
892
|
+
_connected = false;
|
|
893
|
+
_buffer = "";
|
|
894
|
+
_pending = /* @__PURE__ */ new Map();
|
|
895
|
+
MAX_BUFFER = 1e7;
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// src/lib/daemon-protocol.ts
|
|
900
|
+
function serializeValue(v) {
|
|
901
|
+
if (v === null || v === void 0) return null;
|
|
902
|
+
if (typeof v === "bigint") return Number(v);
|
|
903
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
904
|
+
if (v instanceof Uint8Array) {
|
|
905
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
906
|
+
}
|
|
907
|
+
if (ArrayBuffer.isView(v)) {
|
|
908
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
909
|
+
}
|
|
910
|
+
if (v instanceof ArrayBuffer) {
|
|
911
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
912
|
+
}
|
|
913
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
914
|
+
return String(v);
|
|
915
|
+
}
|
|
916
|
+
function deserializeValue(v) {
|
|
917
|
+
if (v === null) return null;
|
|
918
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
919
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
920
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
921
|
+
}
|
|
922
|
+
return v;
|
|
923
|
+
}
|
|
924
|
+
function deserializeResultSet(srs) {
|
|
925
|
+
const rows = srs.rows.map((obj) => {
|
|
926
|
+
const values = srs.columns.map(
|
|
927
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
928
|
+
);
|
|
929
|
+
const row = values;
|
|
930
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
931
|
+
const col = srs.columns[i];
|
|
932
|
+
if (col !== void 0) {
|
|
933
|
+
row[col] = values[i] ?? null;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
Object.defineProperty(row, "length", {
|
|
937
|
+
value: values.length,
|
|
938
|
+
enumerable: false
|
|
939
|
+
});
|
|
940
|
+
return row;
|
|
941
|
+
});
|
|
942
|
+
return {
|
|
943
|
+
columns: srs.columns,
|
|
944
|
+
columnTypes: srs.columnTypes ?? [],
|
|
945
|
+
rows,
|
|
946
|
+
rowsAffected: srs.rowsAffected,
|
|
947
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
948
|
+
toJSON: () => ({
|
|
949
|
+
columns: srs.columns,
|
|
950
|
+
columnTypes: srs.columnTypes ?? [],
|
|
951
|
+
rows: srs.rows,
|
|
952
|
+
rowsAffected: srs.rowsAffected,
|
|
953
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
954
|
+
})
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
var init_daemon_protocol = __esm({
|
|
958
|
+
"src/lib/daemon-protocol.ts"() {
|
|
959
|
+
"use strict";
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
// src/lib/db-daemon-client.ts
|
|
964
|
+
var db_daemon_client_exports = {};
|
|
965
|
+
__export(db_daemon_client_exports, {
|
|
966
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
967
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
968
|
+
});
|
|
969
|
+
function normalizeStatement(stmt) {
|
|
970
|
+
if (typeof stmt === "string") {
|
|
971
|
+
return { sql: stmt, args: [] };
|
|
972
|
+
}
|
|
973
|
+
const sql = stmt.sql;
|
|
974
|
+
let args = [];
|
|
975
|
+
if (Array.isArray(stmt.args)) {
|
|
976
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
977
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
978
|
+
const named = {};
|
|
979
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
980
|
+
named[key] = serializeValue(val);
|
|
981
|
+
}
|
|
982
|
+
return { sql, args: named };
|
|
983
|
+
}
|
|
984
|
+
return { sql, args };
|
|
985
|
+
}
|
|
986
|
+
function createDaemonDbClient(fallbackClient) {
|
|
987
|
+
let _useDaemon = false;
|
|
988
|
+
const client = {
|
|
989
|
+
async execute(stmt) {
|
|
990
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
991
|
+
return fallbackClient.execute(stmt);
|
|
992
|
+
}
|
|
993
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
994
|
+
const response = await sendDaemonRequest({
|
|
995
|
+
type: "db-execute",
|
|
996
|
+
sql,
|
|
997
|
+
args
|
|
998
|
+
});
|
|
999
|
+
if (response.error) {
|
|
1000
|
+
const errMsg = String(response.error);
|
|
1001
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1002
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
1003
|
+
`);
|
|
1004
|
+
return fallbackClient.execute(stmt);
|
|
1005
|
+
}
|
|
1006
|
+
throw new Error(errMsg);
|
|
1007
|
+
}
|
|
1008
|
+
if (response.db) {
|
|
1009
|
+
return deserializeResultSet(response.db);
|
|
1010
|
+
}
|
|
1011
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
1012
|
+
return fallbackClient.execute(stmt);
|
|
1013
|
+
},
|
|
1014
|
+
async batch(stmts, mode) {
|
|
1015
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1016
|
+
return fallbackClient.batch(stmts, mode);
|
|
1017
|
+
}
|
|
1018
|
+
const statements = stmts.map(normalizeStatement);
|
|
1019
|
+
const response = await sendDaemonRequest({
|
|
1020
|
+
type: "db-batch",
|
|
1021
|
+
statements,
|
|
1022
|
+
mode: mode ?? "deferred"
|
|
1023
|
+
});
|
|
1024
|
+
if (response.error) {
|
|
1025
|
+
const errMsg = String(response.error);
|
|
1026
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1027
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
1028
|
+
`);
|
|
1029
|
+
return fallbackClient.batch(stmts, mode);
|
|
1030
|
+
}
|
|
1031
|
+
throw new Error(errMsg);
|
|
1032
|
+
}
|
|
1033
|
+
const batchResults = response["db-batch"];
|
|
1034
|
+
if (batchResults) {
|
|
1035
|
+
return batchResults.map(deserializeResultSet);
|
|
1036
|
+
}
|
|
1037
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
1038
|
+
return fallbackClient.batch(stmts, mode);
|
|
1039
|
+
},
|
|
1040
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
1041
|
+
async transaction(mode) {
|
|
1042
|
+
return fallbackClient.transaction(mode);
|
|
1043
|
+
},
|
|
1044
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
1045
|
+
async executeMultiple(sql) {
|
|
1046
|
+
return fallbackClient.executeMultiple(sql);
|
|
1047
|
+
},
|
|
1048
|
+
// migrate — delegate to fallback
|
|
1049
|
+
async migrate(stmts) {
|
|
1050
|
+
return fallbackClient.migrate(stmts);
|
|
1051
|
+
},
|
|
1052
|
+
// Sync mode — delegate to fallback
|
|
1053
|
+
sync() {
|
|
1054
|
+
return fallbackClient.sync();
|
|
1055
|
+
},
|
|
1056
|
+
close() {
|
|
1057
|
+
_useDaemon = false;
|
|
1058
|
+
},
|
|
1059
|
+
get closed() {
|
|
1060
|
+
return fallbackClient.closed;
|
|
1061
|
+
},
|
|
1062
|
+
get protocol() {
|
|
1063
|
+
return fallbackClient.protocol;
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
return {
|
|
1067
|
+
...client,
|
|
1068
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1069
|
+
_enableDaemon() {
|
|
1070
|
+
_useDaemon = true;
|
|
1071
|
+
},
|
|
1072
|
+
/** Check if daemon routing is active */
|
|
1073
|
+
_isDaemonActive() {
|
|
1074
|
+
return _useDaemon && isClientConnected();
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1079
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1080
|
+
const connected = await connectEmbedDaemon();
|
|
1081
|
+
if (!connected) {
|
|
1082
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1086
|
+
client._enableDaemon();
|
|
1087
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1088
|
+
return client;
|
|
1089
|
+
}
|
|
1090
|
+
var init_db_daemon_client = __esm({
|
|
1091
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1092
|
+
"use strict";
|
|
1093
|
+
init_exe_daemon_client();
|
|
1094
|
+
init_daemon_protocol();
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
610
1098
|
// src/lib/database.ts
|
|
611
1099
|
var database_exports = {};
|
|
612
1100
|
__export(database_exports, {
|
|
@@ -615,6 +1103,7 @@ __export(database_exports, {
|
|
|
615
1103
|
ensureSchema: () => ensureSchema,
|
|
616
1104
|
getClient: () => getClient,
|
|
617
1105
|
getRawClient: () => getRawClient,
|
|
1106
|
+
initDaemonClient: () => initDaemonClient,
|
|
618
1107
|
initDatabase: () => initDatabase,
|
|
619
1108
|
initTurso: () => initTurso,
|
|
620
1109
|
isInitialized: () => isInitialized
|
|
@@ -642,8 +1131,27 @@ function getClient() {
|
|
|
642
1131
|
if (!_resilientClient) {
|
|
643
1132
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
644
1133
|
}
|
|
1134
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1135
|
+
return _resilientClient;
|
|
1136
|
+
}
|
|
1137
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1138
|
+
return _daemonClient;
|
|
1139
|
+
}
|
|
645
1140
|
return _resilientClient;
|
|
646
1141
|
}
|
|
1142
|
+
async function initDaemonClient() {
|
|
1143
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1144
|
+
if (!_resilientClient) return;
|
|
1145
|
+
try {
|
|
1146
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1147
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
process.stderr.write(
|
|
1150
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1151
|
+
`
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
647
1155
|
function getRawClient() {
|
|
648
1156
|
if (!_client) {
|
|
649
1157
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -1130,6 +1638,12 @@ async function ensureSchema() {
|
|
|
1130
1638
|
} catch {
|
|
1131
1639
|
}
|
|
1132
1640
|
}
|
|
1641
|
+
try {
|
|
1642
|
+
await client.execute(
|
|
1643
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1644
|
+
);
|
|
1645
|
+
} catch {
|
|
1646
|
+
}
|
|
1133
1647
|
await client.executeMultiple(`
|
|
1134
1648
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1135
1649
|
id TEXT PRIMARY KEY,
|
|
@@ -1182,7 +1696,30 @@ async function ensureSchema() {
|
|
|
1182
1696
|
entity_id TEXT NOT NULL,
|
|
1183
1697
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1184
1698
|
);
|
|
1699
|
+
|
|
1700
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1701
|
+
name,
|
|
1702
|
+
content=entities,
|
|
1703
|
+
content_rowid=rowid
|
|
1704
|
+
);
|
|
1705
|
+
|
|
1706
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1707
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1708
|
+
END;
|
|
1709
|
+
|
|
1710
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1711
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1712
|
+
END;
|
|
1713
|
+
|
|
1714
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1715
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1716
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1717
|
+
END;
|
|
1185
1718
|
`);
|
|
1719
|
+
try {
|
|
1720
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1721
|
+
} catch {
|
|
1722
|
+
}
|
|
1186
1723
|
await client.executeMultiple(`
|
|
1187
1724
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1188
1725
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1363,6 +1900,33 @@ async function ensureSchema() {
|
|
|
1363
1900
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1364
1901
|
ON conversations(channel_id);
|
|
1365
1902
|
`);
|
|
1903
|
+
await client.executeMultiple(`
|
|
1904
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1905
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1906
|
+
agent_id TEXT NOT NULL,
|
|
1907
|
+
session_name TEXT,
|
|
1908
|
+
task_id TEXT,
|
|
1909
|
+
project_name TEXT,
|
|
1910
|
+
started_at TEXT NOT NULL
|
|
1911
|
+
);
|
|
1912
|
+
|
|
1913
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1914
|
+
ON session_agent_map(agent_id);
|
|
1915
|
+
`);
|
|
1916
|
+
try {
|
|
1917
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1918
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1919
|
+
await client.execute({
|
|
1920
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1921
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1922
|
+
FROM memories
|
|
1923
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1924
|
+
GROUP BY session_id, agent_id`,
|
|
1925
|
+
args: []
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
} catch {
|
|
1929
|
+
}
|
|
1366
1930
|
try {
|
|
1367
1931
|
await client.execute({
|
|
1368
1932
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1496,15 +2060,41 @@ async function ensureSchema() {
|
|
|
1496
2060
|
});
|
|
1497
2061
|
} catch {
|
|
1498
2062
|
}
|
|
2063
|
+
for (const col of [
|
|
2064
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2065
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2066
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2067
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2068
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2069
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2070
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2071
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2072
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2073
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2074
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2075
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2076
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2077
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2078
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2079
|
+
]) {
|
|
2080
|
+
try {
|
|
2081
|
+
await client.execute(col);
|
|
2082
|
+
} catch {
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
1499
2085
|
}
|
|
1500
2086
|
async function disposeDatabase() {
|
|
2087
|
+
if (_daemonClient) {
|
|
2088
|
+
_daemonClient.close();
|
|
2089
|
+
_daemonClient = null;
|
|
2090
|
+
}
|
|
1501
2091
|
if (_client) {
|
|
1502
2092
|
_client.close();
|
|
1503
2093
|
_client = null;
|
|
1504
2094
|
_resilientClient = null;
|
|
1505
2095
|
}
|
|
1506
2096
|
}
|
|
1507
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2097
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1508
2098
|
var init_database = __esm({
|
|
1509
2099
|
"src/lib/database.ts"() {
|
|
1510
2100
|
"use strict";
|
|
@@ -1512,24 +2102,25 @@ var init_database = __esm({
|
|
|
1512
2102
|
init_employees();
|
|
1513
2103
|
_client = null;
|
|
1514
2104
|
_resilientClient = null;
|
|
2105
|
+
_daemonClient = null;
|
|
1515
2106
|
initTurso = initDatabase;
|
|
1516
2107
|
disposeTurso = disposeDatabase;
|
|
1517
2108
|
}
|
|
1518
2109
|
});
|
|
1519
2110
|
|
|
1520
2111
|
// src/lib/license.ts
|
|
1521
|
-
import { readFileSync as
|
|
1522
|
-
import { randomUUID } from "crypto";
|
|
1523
|
-
import
|
|
2112
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2113
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2114
|
+
import path7 from "path";
|
|
1524
2115
|
import { jwtVerify, importSPKI } from "jose";
|
|
1525
2116
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1526
2117
|
var init_license = __esm({
|
|
1527
2118
|
"src/lib/license.ts"() {
|
|
1528
2119
|
"use strict";
|
|
1529
2120
|
init_config();
|
|
1530
|
-
LICENSE_PATH =
|
|
1531
|
-
CACHE_PATH =
|
|
1532
|
-
DEVICE_ID_PATH =
|
|
2121
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
2122
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
2123
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1533
2124
|
PLAN_LIMITS = {
|
|
1534
2125
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1535
2126
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1541,12 +2132,12 @@ var init_license = __esm({
|
|
|
1541
2132
|
});
|
|
1542
2133
|
|
|
1543
2134
|
// src/lib/plan-limits.ts
|
|
1544
|
-
import { readFileSync as
|
|
1545
|
-
import
|
|
2135
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
2136
|
+
import path8 from "path";
|
|
1546
2137
|
function getLicenseSync() {
|
|
1547
2138
|
try {
|
|
1548
|
-
if (!
|
|
1549
|
-
const raw = JSON.parse(
|
|
2139
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2140
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
1550
2141
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1551
2142
|
const parts = raw.token.split(".");
|
|
1552
2143
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1584,8 +2175,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1584
2175
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1585
2176
|
let count = 0;
|
|
1586
2177
|
try {
|
|
1587
|
-
if (
|
|
1588
|
-
const raw =
|
|
2178
|
+
if (existsSync8(filePath)) {
|
|
2179
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
1589
2180
|
const employees = JSON.parse(raw);
|
|
1590
2181
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1591
2182
|
}
|
|
@@ -1614,19 +2205,19 @@ var init_plan_limits = __esm({
|
|
|
1614
2205
|
this.name = "PlanLimitError";
|
|
1615
2206
|
}
|
|
1616
2207
|
};
|
|
1617
|
-
CACHE_PATH2 =
|
|
2208
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
1618
2209
|
}
|
|
1619
2210
|
});
|
|
1620
2211
|
|
|
1621
2212
|
// src/lib/notifications.ts
|
|
1622
2213
|
import crypto from "crypto";
|
|
1623
|
-
import
|
|
2214
|
+
import path9 from "path";
|
|
1624
2215
|
import os5 from "os";
|
|
1625
2216
|
import {
|
|
1626
|
-
readFileSync as
|
|
2217
|
+
readFileSync as readFileSync9,
|
|
1627
2218
|
readdirSync,
|
|
1628
|
-
unlinkSync as
|
|
1629
|
-
existsSync as
|
|
2219
|
+
unlinkSync as unlinkSync3,
|
|
2220
|
+
existsSync as existsSync9,
|
|
1630
2221
|
rmdirSync
|
|
1631
2222
|
} from "fs";
|
|
1632
2223
|
async function writeNotification(notification) {
|
|
@@ -1761,10 +2352,11 @@ var init_state_bus = __esm({
|
|
|
1761
2352
|
|
|
1762
2353
|
// src/lib/tasks-crud.ts
|
|
1763
2354
|
import crypto3 from "crypto";
|
|
1764
|
-
import
|
|
2355
|
+
import path10 from "path";
|
|
2356
|
+
import os6 from "os";
|
|
1765
2357
|
import { execSync as execSync4 } from "child_process";
|
|
1766
2358
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1767
|
-
import { existsSync as
|
|
2359
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
1768
2360
|
async function writeCheckpoint(input) {
|
|
1769
2361
|
const client = getClient();
|
|
1770
2362
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1805,6 +2397,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1805
2397
|
function slugify(title) {
|
|
1806
2398
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1807
2399
|
}
|
|
2400
|
+
function buildKeywordIndex() {
|
|
2401
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2402
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2403
|
+
for (const kw of keywords) {
|
|
2404
|
+
const existing = idx.get(kw) ?? [];
|
|
2405
|
+
existing.push(role);
|
|
2406
|
+
idx.set(kw, existing);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return idx;
|
|
2410
|
+
}
|
|
2411
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2412
|
+
const employees = loadEmployeesSync();
|
|
2413
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2414
|
+
if (!employee) return void 0;
|
|
2415
|
+
const assigneeRole = employee.role;
|
|
2416
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2417
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2418
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2419
|
+
if (text.includes(keyword)) {
|
|
2420
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2424
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2425
|
+
if (assigneeRole === "COO") return void 0;
|
|
2426
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2427
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2428
|
+
}
|
|
1808
2429
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1809
2430
|
const scope = sessionScopeFilter(scopeSession);
|
|
1810
2431
|
let result = await client.execute({
|
|
@@ -1854,7 +2475,14 @@ async function createTaskCore(input) {
|
|
|
1854
2475
|
const id = crypto3.randomUUID();
|
|
1855
2476
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1856
2477
|
const slug = slugify(input.title);
|
|
1857
|
-
|
|
2478
|
+
let earlySessionScope = null;
|
|
2479
|
+
try {
|
|
2480
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2481
|
+
earlySessionScope = resolveExeSession2();
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2484
|
+
const scope = earlySessionScope ?? "default";
|
|
2485
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1858
2486
|
let blockedById = null;
|
|
1859
2487
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1860
2488
|
if (input.blockedBy) {
|
|
@@ -1894,22 +2522,24 @@ async function createTaskCore(input) {
|
|
|
1894
2522
|
if (dupCheck.rows.length > 0) {
|
|
1895
2523
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1896
2524
|
}
|
|
2525
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2526
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2527
|
+
if (laneWarning) {
|
|
2528
|
+
warning = warning ? `${warning}
|
|
2529
|
+
${laneWarning}` : laneWarning;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
1897
2532
|
if (input.baseDir) {
|
|
1898
2533
|
try {
|
|
1899
|
-
await mkdir3(
|
|
1900
|
-
await mkdir3(
|
|
2534
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2535
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1901
2536
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1902
2537
|
await ensureGitignoreExe(input.baseDir);
|
|
1903
2538
|
} catch {
|
|
1904
2539
|
}
|
|
1905
2540
|
}
|
|
1906
2541
|
const complexity = input.complexity ?? "standard";
|
|
1907
|
-
|
|
1908
|
-
try {
|
|
1909
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1910
|
-
sessionScope = resolveExeSession2();
|
|
1911
|
-
} catch {
|
|
1912
|
-
}
|
|
2542
|
+
const sessionScope = earlySessionScope;
|
|
1913
2543
|
await client.execute({
|
|
1914
2544
|
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)
|
|
1915
2545
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1936,6 +2566,43 @@ async function createTaskCore(input) {
|
|
|
1936
2566
|
now
|
|
1937
2567
|
]
|
|
1938
2568
|
});
|
|
2569
|
+
if (input.baseDir) {
|
|
2570
|
+
try {
|
|
2571
|
+
const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
|
|
2572
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2573
|
+
const mdDir = path10.dirname(mdPath);
|
|
2574
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2575
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2576
|
+
const mdContent = `# ${input.title}
|
|
2577
|
+
|
|
2578
|
+
**ID:** ${id}
|
|
2579
|
+
**Status:** ${initialStatus}
|
|
2580
|
+
**Priority:** ${input.priority}
|
|
2581
|
+
**Assigned by:** ${input.assignedBy}
|
|
2582
|
+
**Assigned to:** ${input.assignedTo}
|
|
2583
|
+
**Project:** ${input.projectName}
|
|
2584
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2585
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2586
|
+
**Reviewer:** ${reviewer}
|
|
2587
|
+
|
|
2588
|
+
## Context
|
|
2589
|
+
|
|
2590
|
+
${input.context}
|
|
2591
|
+
|
|
2592
|
+
## MANDATORY: When done
|
|
2593
|
+
|
|
2594
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2595
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2596
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2597
|
+
`;
|
|
2598
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2599
|
+
} catch (err) {
|
|
2600
|
+
process.stderr.write(
|
|
2601
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2602
|
+
`
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
1939
2606
|
return {
|
|
1940
2607
|
id,
|
|
1941
2608
|
title: input.title,
|
|
@@ -2128,7 +2795,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2128
2795
|
return { row, taskFile, now, taskId };
|
|
2129
2796
|
}
|
|
2130
2797
|
}
|
|
2131
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2798
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2132
2799
|
process.stderr.write(
|
|
2133
2800
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2134
2801
|
`
|
|
@@ -2193,9 +2860,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2193
2860
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2194
2861
|
}
|
|
2195
2862
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2196
|
-
const archPath =
|
|
2863
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2197
2864
|
try {
|
|
2198
|
-
if (
|
|
2865
|
+
if (existsSync10(archPath)) return;
|
|
2199
2866
|
const template = [
|
|
2200
2867
|
`# ${projectName} \u2014 System Architecture`,
|
|
2201
2868
|
"",
|
|
@@ -2228,10 +2895,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2228
2895
|
}
|
|
2229
2896
|
}
|
|
2230
2897
|
async function ensureGitignoreExe(baseDir) {
|
|
2231
|
-
const gitignorePath =
|
|
2898
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2232
2899
|
try {
|
|
2233
|
-
if (
|
|
2234
|
-
const content =
|
|
2900
|
+
if (existsSync10(gitignorePath)) {
|
|
2901
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2235
2902
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2236
2903
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2237
2904
|
} else {
|
|
@@ -2240,20 +2907,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2240
2907
|
} catch {
|
|
2241
2908
|
}
|
|
2242
2909
|
}
|
|
2243
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2910
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2244
2911
|
var init_tasks_crud = __esm({
|
|
2245
2912
|
"src/lib/tasks-crud.ts"() {
|
|
2246
2913
|
"use strict";
|
|
2247
2914
|
init_database();
|
|
2248
2915
|
init_task_scope();
|
|
2916
|
+
init_employees();
|
|
2917
|
+
LANE_KEYWORDS = {
|
|
2918
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2919
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2920
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2921
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2922
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2923
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2924
|
+
};
|
|
2925
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2249
2926
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2250
2927
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2251
2928
|
}
|
|
2252
2929
|
});
|
|
2253
2930
|
|
|
2254
2931
|
// src/lib/tasks-review.ts
|
|
2255
|
-
import
|
|
2256
|
-
import { existsSync as
|
|
2932
|
+
import path11 from "path";
|
|
2933
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2257
2934
|
async function countPendingReviews(sessionScope) {
|
|
2258
2935
|
const client = getClient();
|
|
2259
2936
|
if (sessionScope) {
|
|
@@ -2275,7 +2952,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2275
2952
|
const result2 = await client.execute({
|
|
2276
2953
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2277
2954
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2278
|
-
AND
|
|
2955
|
+
AND session_scope = ?`,
|
|
2279
2956
|
args: [sinceIso, sessionScope]
|
|
2280
2957
|
});
|
|
2281
2958
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2293,7 +2970,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2293
2970
|
const result2 = await client.execute({
|
|
2294
2971
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2295
2972
|
WHERE status = 'needs_review'
|
|
2296
|
-
AND
|
|
2973
|
+
AND session_scope = ?
|
|
2297
2974
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2298
2975
|
args: [sessionScope, limit]
|
|
2299
2976
|
});
|
|
@@ -2414,14 +3091,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2414
3091
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2415
3092
|
const agent = parts[1];
|
|
2416
3093
|
const slug = parts.slice(2).join("-");
|
|
2417
|
-
const
|
|
3094
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2418
3095
|
const result = await client.execute({
|
|
2419
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2420
|
-
args: [now,
|
|
3096
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3097
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2421
3098
|
});
|
|
2422
3099
|
if (result.rowsAffected > 0) {
|
|
2423
3100
|
process.stderr.write(
|
|
2424
|
-
`[review-cleanup] Cascaded original task to done
|
|
3101
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2425
3102
|
`
|
|
2426
3103
|
);
|
|
2427
3104
|
}
|
|
@@ -2434,11 +3111,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2434
3111
|
);
|
|
2435
3112
|
}
|
|
2436
3113
|
try {
|
|
2437
|
-
const cacheDir =
|
|
2438
|
-
if (
|
|
3114
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3115
|
+
if (existsSync11(cacheDir)) {
|
|
2439
3116
|
for (const f of readdirSync2(cacheDir)) {
|
|
2440
3117
|
if (f.startsWith("review-notified-")) {
|
|
2441
|
-
|
|
3118
|
+
unlinkSync4(path11.join(cacheDir, f));
|
|
2442
3119
|
}
|
|
2443
3120
|
}
|
|
2444
3121
|
}
|
|
@@ -2459,7 +3136,7 @@ var init_tasks_review = __esm({
|
|
|
2459
3136
|
});
|
|
2460
3137
|
|
|
2461
3138
|
// src/lib/tasks-chain.ts
|
|
2462
|
-
import
|
|
3139
|
+
import path12 from "path";
|
|
2463
3140
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2464
3141
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2465
3142
|
const client = getClient();
|
|
@@ -2476,7 +3153,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2476
3153
|
});
|
|
2477
3154
|
for (const ur of unblockedRows.rows) {
|
|
2478
3155
|
try {
|
|
2479
|
-
const ubFile =
|
|
3156
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2480
3157
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2481
3158
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2482
3159
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2550,7 +3227,7 @@ __export(project_name_exports, {
|
|
|
2550
3227
|
getProjectName: () => getProjectName
|
|
2551
3228
|
});
|
|
2552
3229
|
import { execSync as execSync5 } from "child_process";
|
|
2553
|
-
import
|
|
3230
|
+
import path13 from "path";
|
|
2554
3231
|
function getProjectName(cwd) {
|
|
2555
3232
|
const dir = cwd ?? process.cwd();
|
|
2556
3233
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2563,7 +3240,7 @@ function getProjectName(cwd) {
|
|
|
2563
3240
|
timeout: 2e3,
|
|
2564
3241
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2565
3242
|
}).trim();
|
|
2566
|
-
repoRoot =
|
|
3243
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2567
3244
|
} catch {
|
|
2568
3245
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2569
3246
|
cwd: dir,
|
|
@@ -2572,11 +3249,11 @@ function getProjectName(cwd) {
|
|
|
2572
3249
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2573
3250
|
}).trim();
|
|
2574
3251
|
}
|
|
2575
|
-
_cached2 =
|
|
3252
|
+
_cached2 = path13.basename(repoRoot);
|
|
2576
3253
|
_cachedCwd = dir;
|
|
2577
3254
|
return _cached2;
|
|
2578
3255
|
} catch {
|
|
2579
|
-
_cached2 =
|
|
3256
|
+
_cached2 = path13.basename(dir);
|
|
2580
3257
|
_cachedCwd = dir;
|
|
2581
3258
|
return _cached2;
|
|
2582
3259
|
}
|
|
@@ -2612,7 +3289,7 @@ function findSessionForProject(projectName) {
|
|
|
2612
3289
|
const sessions = listSessions();
|
|
2613
3290
|
for (const s of sessions) {
|
|
2614
3291
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2615
|
-
if (proj === projectName &&
|
|
3292
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2616
3293
|
}
|
|
2617
3294
|
return null;
|
|
2618
3295
|
}
|
|
@@ -2658,7 +3335,7 @@ var init_session_scope = __esm({
|
|
|
2658
3335
|
|
|
2659
3336
|
// src/lib/tasks-notify.ts
|
|
2660
3337
|
async function dispatchTaskToEmployee(input) {
|
|
2661
|
-
if (
|
|
3338
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2662
3339
|
let crossProject = false;
|
|
2663
3340
|
if (input.projectName) {
|
|
2664
3341
|
try {
|
|
@@ -3053,8 +3730,8 @@ __export(tasks_exports, {
|
|
|
3053
3730
|
updateTaskStatus: () => updateTaskStatus,
|
|
3054
3731
|
writeCheckpoint: () => writeCheckpoint
|
|
3055
3732
|
});
|
|
3056
|
-
import
|
|
3057
|
-
import { writeFileSync as
|
|
3733
|
+
import path14 from "path";
|
|
3734
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3058
3735
|
async function createTask(input) {
|
|
3059
3736
|
const result = await createTaskCore(input);
|
|
3060
3737
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3073,14 +3750,14 @@ async function updateTask(input) {
|
|
|
3073
3750
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3074
3751
|
try {
|
|
3075
3752
|
const agent = String(row.assigned_to);
|
|
3076
|
-
const cacheDir =
|
|
3077
|
-
const cachePath =
|
|
3753
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
3754
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3078
3755
|
if (input.status === "in_progress") {
|
|
3079
|
-
|
|
3080
|
-
|
|
3756
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
3757
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3081
3758
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3082
3759
|
try {
|
|
3083
|
-
|
|
3760
|
+
unlinkSync5(cachePath);
|
|
3084
3761
|
} catch {
|
|
3085
3762
|
}
|
|
3086
3763
|
}
|
|
@@ -3137,7 +3814,7 @@ async function updateTask(input) {
|
|
|
3137
3814
|
}
|
|
3138
3815
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3139
3816
|
if (isTerminal) {
|
|
3140
|
-
const isCoordinator =
|
|
3817
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3141
3818
|
if (!isCoordinator) {
|
|
3142
3819
|
notifyTaskDone();
|
|
3143
3820
|
}
|
|
@@ -3162,7 +3839,7 @@ async function updateTask(input) {
|
|
|
3162
3839
|
}
|
|
3163
3840
|
}
|
|
3164
3841
|
}
|
|
3165
|
-
if (input.status === "done" &&
|
|
3842
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3166
3843
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3167
3844
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3168
3845
|
taskId,
|
|
@@ -3178,7 +3855,7 @@ async function updateTask(input) {
|
|
|
3178
3855
|
});
|
|
3179
3856
|
}
|
|
3180
3857
|
let nextTask;
|
|
3181
|
-
if (isTerminal &&
|
|
3858
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3182
3859
|
try {
|
|
3183
3860
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3184
3861
|
} catch {
|
|
@@ -3522,7 +4199,7 @@ var init_capacity_monitor = __esm({
|
|
|
3522
4199
|
// src/lib/tmux-routing.ts
|
|
3523
4200
|
var tmux_routing_exports = {};
|
|
3524
4201
|
__export(tmux_routing_exports, {
|
|
3525
|
-
acquireSpawnLock: () =>
|
|
4202
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3526
4203
|
employeeSessionName: () => employeeSessionName,
|
|
3527
4204
|
ensureEmployee: () => ensureEmployee,
|
|
3528
4205
|
extractRootExe: () => extractRootExe,
|
|
@@ -3537,20 +4214,20 @@ __export(tmux_routing_exports, {
|
|
|
3537
4214
|
notifyParentExe: () => notifyParentExe,
|
|
3538
4215
|
parseParentExe: () => parseParentExe,
|
|
3539
4216
|
registerParentExe: () => registerParentExe,
|
|
3540
|
-
releaseSpawnLock: () =>
|
|
4217
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3541
4218
|
resolveExeSession: () => resolveExeSession,
|
|
3542
4219
|
sendIntercom: () => sendIntercom,
|
|
3543
4220
|
spawnEmployee: () => spawnEmployee,
|
|
3544
4221
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3545
4222
|
});
|
|
3546
4223
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3547
|
-
import { readFileSync as
|
|
3548
|
-
import
|
|
3549
|
-
import
|
|
3550
|
-
import { fileURLToPath as
|
|
3551
|
-
import { unlinkSync as
|
|
4224
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
|
|
4225
|
+
import path15 from "path";
|
|
4226
|
+
import os7 from "os";
|
|
4227
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4228
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3552
4229
|
function spawnLockPath(sessionName) {
|
|
3553
|
-
return
|
|
4230
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3554
4231
|
}
|
|
3555
4232
|
function isProcessAlive(pid) {
|
|
3556
4233
|
try {
|
|
@@ -3560,14 +4237,14 @@ function isProcessAlive(pid) {
|
|
|
3560
4237
|
return false;
|
|
3561
4238
|
}
|
|
3562
4239
|
}
|
|
3563
|
-
function
|
|
3564
|
-
if (!
|
|
3565
|
-
|
|
4240
|
+
function acquireSpawnLock2(sessionName) {
|
|
4241
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4242
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3566
4243
|
}
|
|
3567
4244
|
const lockFile = spawnLockPath(sessionName);
|
|
3568
|
-
if (
|
|
4245
|
+
if (existsSync12(lockFile)) {
|
|
3569
4246
|
try {
|
|
3570
|
-
const lock = JSON.parse(
|
|
4247
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
3571
4248
|
const age = Date.now() - lock.timestamp;
|
|
3572
4249
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3573
4250
|
return false;
|
|
@@ -3575,25 +4252,25 @@ function acquireSpawnLock(sessionName) {
|
|
|
3575
4252
|
} catch {
|
|
3576
4253
|
}
|
|
3577
4254
|
}
|
|
3578
|
-
|
|
4255
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3579
4256
|
return true;
|
|
3580
4257
|
}
|
|
3581
|
-
function
|
|
4258
|
+
function releaseSpawnLock2(sessionName) {
|
|
3582
4259
|
try {
|
|
3583
|
-
|
|
4260
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3584
4261
|
} catch {
|
|
3585
4262
|
}
|
|
3586
4263
|
}
|
|
3587
4264
|
function resolveBehaviorsExporterScript() {
|
|
3588
4265
|
try {
|
|
3589
|
-
const thisFile =
|
|
3590
|
-
const scriptPath =
|
|
3591
|
-
|
|
4266
|
+
const thisFile = fileURLToPath3(import.meta.url);
|
|
4267
|
+
const scriptPath = path15.join(
|
|
4268
|
+
path15.dirname(thisFile),
|
|
3592
4269
|
"..",
|
|
3593
4270
|
"bin",
|
|
3594
4271
|
"exe-export-behaviors.js"
|
|
3595
4272
|
);
|
|
3596
|
-
return
|
|
4273
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
3597
4274
|
} catch {
|
|
3598
4275
|
return null;
|
|
3599
4276
|
}
|
|
@@ -3659,12 +4336,12 @@ function extractRootExe(name) {
|
|
|
3659
4336
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3660
4337
|
}
|
|
3661
4338
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3662
|
-
if (!
|
|
3663
|
-
|
|
4339
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4340
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3664
4341
|
}
|
|
3665
4342
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3666
|
-
const filePath =
|
|
3667
|
-
|
|
4343
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4344
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3668
4345
|
parentExe: rootExe,
|
|
3669
4346
|
dispatchedBy: dispatchedBy || rootExe,
|
|
3670
4347
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3672,7 +4349,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3672
4349
|
}
|
|
3673
4350
|
function getParentExe(sessionKey) {
|
|
3674
4351
|
try {
|
|
3675
|
-
const data = JSON.parse(
|
|
4352
|
+
const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3676
4353
|
return data.parentExe || null;
|
|
3677
4354
|
} catch {
|
|
3678
4355
|
return null;
|
|
@@ -3680,8 +4357,8 @@ function getParentExe(sessionKey) {
|
|
|
3680
4357
|
}
|
|
3681
4358
|
function getDispatchedBy(sessionKey) {
|
|
3682
4359
|
try {
|
|
3683
|
-
const data = JSON.parse(
|
|
3684
|
-
|
|
4360
|
+
const data = JSON.parse(readFileSync11(
|
|
4361
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3685
4362
|
"utf8"
|
|
3686
4363
|
));
|
|
3687
4364
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3707,10 +4384,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3707
4384
|
}
|
|
3708
4385
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3709
4386
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3710
|
-
if (!isAlive(base) &&
|
|
4387
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3711
4388
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3712
4389
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3713
|
-
if (!isAlive(candidate) &&
|
|
4390
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3714
4391
|
}
|
|
3715
4392
|
return null;
|
|
3716
4393
|
}
|
|
@@ -3742,32 +4419,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3742
4419
|
}
|
|
3743
4420
|
function readDebounceState() {
|
|
3744
4421
|
try {
|
|
3745
|
-
if (!
|
|
3746
|
-
|
|
4422
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4423
|
+
const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
4424
|
+
const state = {};
|
|
4425
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
4426
|
+
if (typeof val === "number") {
|
|
4427
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
4428
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
4429
|
+
state[key] = val;
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
return state;
|
|
3747
4433
|
} catch {
|
|
3748
4434
|
return {};
|
|
3749
4435
|
}
|
|
3750
4436
|
}
|
|
3751
4437
|
function writeDebounceState(state) {
|
|
3752
4438
|
try {
|
|
3753
|
-
if (!
|
|
3754
|
-
|
|
4439
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4440
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3755
4441
|
} catch {
|
|
3756
4442
|
}
|
|
3757
4443
|
}
|
|
3758
4444
|
function isDebounced(targetSession) {
|
|
3759
4445
|
const state = readDebounceState();
|
|
3760
|
-
const
|
|
3761
|
-
|
|
4446
|
+
const entry = state[targetSession];
|
|
4447
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
4448
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
4449
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
4450
|
+
state[targetSession].pending++;
|
|
4451
|
+
writeDebounceState(state);
|
|
4452
|
+
return true;
|
|
4453
|
+
}
|
|
4454
|
+
return false;
|
|
3762
4455
|
}
|
|
3763
4456
|
function recordDebounce(targetSession) {
|
|
3764
4457
|
const state = readDebounceState();
|
|
3765
|
-
state[targetSession]
|
|
4458
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
4459
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
3766
4460
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
3767
4461
|
for (const key of Object.keys(state)) {
|
|
3768
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4462
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
3769
4463
|
}
|
|
3770
4464
|
writeDebounceState(state);
|
|
4465
|
+
return batched;
|
|
3771
4466
|
}
|
|
3772
4467
|
function logIntercom(msg) {
|
|
3773
4468
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -3812,7 +4507,7 @@ function sendIntercom(targetSession) {
|
|
|
3812
4507
|
return "skipped_exe";
|
|
3813
4508
|
}
|
|
3814
4509
|
if (isDebounced(targetSession)) {
|
|
3815
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
4510
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
3816
4511
|
return "debounced";
|
|
3817
4512
|
}
|
|
3818
4513
|
try {
|
|
@@ -3824,14 +4519,14 @@ function sendIntercom(targetSession) {
|
|
|
3824
4519
|
const sessionState = getSessionState(targetSession);
|
|
3825
4520
|
if (sessionState === "no_claude") {
|
|
3826
4521
|
queueIntercom(targetSession, "claude not running in session");
|
|
3827
|
-
recordDebounce(targetSession);
|
|
3828
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
4522
|
+
const batched2 = recordDebounce(targetSession);
|
|
4523
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3829
4524
|
return "queued";
|
|
3830
4525
|
}
|
|
3831
4526
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3832
4527
|
queueIntercom(targetSession, "session busy at send time");
|
|
3833
|
-
recordDebounce(targetSession);
|
|
3834
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
4528
|
+
const batched2 = recordDebounce(targetSession);
|
|
4529
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3835
4530
|
return "queued";
|
|
3836
4531
|
}
|
|
3837
4532
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -3839,8 +4534,8 @@ function sendIntercom(targetSession) {
|
|
|
3839
4534
|
transport.sendKeys(targetSession, "q");
|
|
3840
4535
|
}
|
|
3841
4536
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3842
|
-
recordDebounce(targetSession);
|
|
3843
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4537
|
+
const batched = recordDebounce(targetSession);
|
|
4538
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
3844
4539
|
return "delivered";
|
|
3845
4540
|
} catch {
|
|
3846
4541
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3870,7 +4565,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3870
4565
|
return true;
|
|
3871
4566
|
}
|
|
3872
4567
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3873
|
-
if (
|
|
4568
|
+
if (isCoordinatorName(employeeName)) {
|
|
3874
4569
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3875
4570
|
}
|
|
3876
4571
|
try {
|
|
@@ -3942,26 +4637,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3942
4637
|
const transport = getTransport();
|
|
3943
4638
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3944
4639
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3945
|
-
const logDir =
|
|
3946
|
-
const logFile =
|
|
3947
|
-
if (!
|
|
3948
|
-
|
|
4640
|
+
const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4641
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4642
|
+
if (!existsSync12(logDir)) {
|
|
4643
|
+
mkdirSync6(logDir, { recursive: true });
|
|
3949
4644
|
}
|
|
3950
4645
|
transport.kill(sessionName);
|
|
3951
4646
|
let cleanupSuffix = "";
|
|
3952
4647
|
try {
|
|
3953
|
-
const thisFile =
|
|
3954
|
-
const cleanupScript =
|
|
3955
|
-
if (
|
|
4648
|
+
const thisFile = fileURLToPath3(import.meta.url);
|
|
4649
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4650
|
+
if (existsSync12(cleanupScript)) {
|
|
3956
4651
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3957
4652
|
}
|
|
3958
4653
|
} catch {
|
|
3959
4654
|
}
|
|
3960
4655
|
try {
|
|
3961
|
-
const claudeJsonPath =
|
|
4656
|
+
const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
|
|
3962
4657
|
let claudeJson = {};
|
|
3963
4658
|
try {
|
|
3964
|
-
claudeJson = JSON.parse(
|
|
4659
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
3965
4660
|
} catch {
|
|
3966
4661
|
}
|
|
3967
4662
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3969,17 +4664,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3969
4664
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3970
4665
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3971
4666
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3972
|
-
|
|
4667
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3973
4668
|
} catch {
|
|
3974
4669
|
}
|
|
3975
4670
|
try {
|
|
3976
|
-
const settingsDir =
|
|
4671
|
+
const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
|
|
3977
4672
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3978
|
-
const projSettingsDir =
|
|
3979
|
-
const settingsPath =
|
|
4673
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
4674
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
3980
4675
|
let settings = {};
|
|
3981
4676
|
try {
|
|
3982
|
-
settings = JSON.parse(
|
|
4677
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
3983
4678
|
} catch {
|
|
3984
4679
|
}
|
|
3985
4680
|
const perms = settings.permissions ?? {};
|
|
@@ -4007,21 +4702,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4007
4702
|
if (changed) {
|
|
4008
4703
|
perms.allow = allow;
|
|
4009
4704
|
settings.permissions = perms;
|
|
4010
|
-
|
|
4011
|
-
|
|
4705
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4706
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4012
4707
|
}
|
|
4013
4708
|
} catch {
|
|
4014
4709
|
}
|
|
4015
4710
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
4016
4711
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
4017
|
-
const
|
|
4712
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
4713
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
4714
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
4715
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
4018
4716
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
4019
4717
|
let identityFlag = "";
|
|
4020
4718
|
let behaviorsFlag = "";
|
|
4021
4719
|
let legacyFallbackWarned = false;
|
|
4022
4720
|
if (!useExeAgent && !useBinSymlink) {
|
|
4023
|
-
const identityPath =
|
|
4024
|
-
|
|
4721
|
+
const identityPath = path15.join(
|
|
4722
|
+
os7.homedir(),
|
|
4025
4723
|
".exe-os",
|
|
4026
4724
|
"identity",
|
|
4027
4725
|
`${employeeName}.md`
|
|
@@ -4030,13 +4728,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4030
4728
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4031
4729
|
if (hasAgentFlag) {
|
|
4032
4730
|
identityFlag = ` --agent ${employeeName}`;
|
|
4033
|
-
} else if (
|
|
4731
|
+
} else if (existsSync12(identityPath)) {
|
|
4034
4732
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4035
4733
|
legacyFallbackWarned = true;
|
|
4036
4734
|
}
|
|
4037
4735
|
const behaviorsFile = exportBehaviorsSync(
|
|
4038
4736
|
employeeName,
|
|
4039
|
-
|
|
4737
|
+
path15.basename(spawnCwd),
|
|
4040
4738
|
sessionName
|
|
4041
4739
|
);
|
|
4042
4740
|
if (behaviorsFile) {
|
|
@@ -4051,16 +4749,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4051
4749
|
}
|
|
4052
4750
|
let sessionContextFlag = "";
|
|
4053
4751
|
try {
|
|
4054
|
-
const ctxDir =
|
|
4055
|
-
|
|
4056
|
-
const ctxFile =
|
|
4752
|
+
const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4753
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
4754
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4057
4755
|
const ctxContent = [
|
|
4058
4756
|
`## Session Context`,
|
|
4059
4757
|
`You are running in tmux session: ${sessionName}.`,
|
|
4060
4758
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4061
4759
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4062
4760
|
].join("\n");
|
|
4063
|
-
|
|
4761
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
4064
4762
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4065
4763
|
} catch {
|
|
4066
4764
|
}
|
|
@@ -4074,9 +4772,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4074
4772
|
}
|
|
4075
4773
|
}
|
|
4076
4774
|
}
|
|
4775
|
+
if (useCodex) {
|
|
4776
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
4777
|
+
if (codexCfg?.apiKeyEnv) {
|
|
4778
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
4779
|
+
if (keyVal) {
|
|
4780
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
4784
|
+
}
|
|
4785
|
+
if (useOpencode) {
|
|
4786
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
4787
|
+
if (ocCfg?.apiKeyEnv) {
|
|
4788
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
4789
|
+
if (keyVal) {
|
|
4790
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4794
|
+
}
|
|
4795
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
4796
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
4797
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
4798
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4077
4801
|
let spawnCommand;
|
|
4078
4802
|
if (useExeAgent) {
|
|
4079
4803
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
4804
|
+
} else if (useCodex) {
|
|
4805
|
+
process.stderr.write(
|
|
4806
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
4807
|
+
`
|
|
4808
|
+
);
|
|
4809
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
4810
|
+
} else if (useOpencode) {
|
|
4811
|
+
const binName = `${employeeName}-opencode`;
|
|
4812
|
+
process.stderr.write(
|
|
4813
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
4814
|
+
`
|
|
4815
|
+
);
|
|
4816
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4080
4817
|
} else if (useBinSymlink) {
|
|
4081
4818
|
const binName = `${employeeName}-${ccProvider}`;
|
|
4082
4819
|
process.stderr.write(
|
|
@@ -4092,17 +4829,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4092
4829
|
command: spawnCommand
|
|
4093
4830
|
});
|
|
4094
4831
|
if (spawnResult.error) {
|
|
4095
|
-
|
|
4832
|
+
releaseSpawnLock2(sessionName);
|
|
4096
4833
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4097
4834
|
}
|
|
4098
4835
|
transport.pipeLog(sessionName, logFile);
|
|
4099
4836
|
try {
|
|
4100
4837
|
const mySession = getMySession();
|
|
4101
|
-
const dispatchInfo =
|
|
4102
|
-
|
|
4838
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4839
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4103
4840
|
dispatchedBy: mySession,
|
|
4104
4841
|
rootExe: exeSession,
|
|
4105
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
4842
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
4843
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
4844
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
4106
4845
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4107
4846
|
}));
|
|
4108
4847
|
} catch {
|
|
@@ -4120,6 +4859,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4120
4859
|
booted = true;
|
|
4121
4860
|
break;
|
|
4122
4861
|
}
|
|
4862
|
+
} else if (useCodex) {
|
|
4863
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
4864
|
+
booted = true;
|
|
4865
|
+
break;
|
|
4866
|
+
}
|
|
4123
4867
|
} else {
|
|
4124
4868
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
4125
4869
|
booted = true;
|
|
@@ -4130,10 +4874,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4130
4874
|
}
|
|
4131
4875
|
}
|
|
4132
4876
|
if (!booted) {
|
|
4133
|
-
|
|
4134
|
-
|
|
4877
|
+
releaseSpawnLock2(sessionName);
|
|
4878
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
4879
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
4135
4880
|
}
|
|
4136
|
-
if (!useExeAgent) {
|
|
4881
|
+
if (!useExeAgent && !useCodex) {
|
|
4137
4882
|
try {
|
|
4138
4883
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
4139
4884
|
} catch {
|
|
@@ -4147,7 +4892,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4147
4892
|
pid: 0,
|
|
4148
4893
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4149
4894
|
});
|
|
4150
|
-
|
|
4895
|
+
releaseSpawnLock2(sessionName);
|
|
4151
4896
|
return { sessionName };
|
|
4152
4897
|
}
|
|
4153
4898
|
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;
|
|
@@ -4160,17 +4905,19 @@ var init_tmux_routing = __esm({
|
|
|
4160
4905
|
init_cc_agent_support();
|
|
4161
4906
|
init_mcp_prefix();
|
|
4162
4907
|
init_provider_table();
|
|
4908
|
+
init_agent_config();
|
|
4909
|
+
init_runtime_table();
|
|
4163
4910
|
init_intercom_queue();
|
|
4164
4911
|
init_plan_limits();
|
|
4165
4912
|
init_employees();
|
|
4166
|
-
SPAWN_LOCK_DIR =
|
|
4167
|
-
SESSION_CACHE =
|
|
4913
|
+
SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4914
|
+
SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4168
4915
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4169
4916
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4170
4917
|
VERIFY_PANE_LINES = 200;
|
|
4171
4918
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4172
|
-
INTERCOM_LOG2 =
|
|
4173
|
-
DEBOUNCE_FILE =
|
|
4919
|
+
INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4920
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4174
4921
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4175
4922
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4176
4923
|
}
|
|
@@ -4211,14 +4958,14 @@ var init_memory = __esm({
|
|
|
4211
4958
|
|
|
4212
4959
|
// src/lib/keychain.ts
|
|
4213
4960
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4214
|
-
import { existsSync as
|
|
4215
|
-
import
|
|
4216
|
-
import
|
|
4961
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4962
|
+
import path16 from "path";
|
|
4963
|
+
import os8 from "os";
|
|
4217
4964
|
function getKeyDir() {
|
|
4218
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4965
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
|
|
4219
4966
|
}
|
|
4220
4967
|
function getKeyPath() {
|
|
4221
|
-
return
|
|
4968
|
+
return path16.join(getKeyDir(), "master.key");
|
|
4222
4969
|
}
|
|
4223
4970
|
async function tryKeytar() {
|
|
4224
4971
|
try {
|
|
@@ -4239,13 +4986,21 @@ async function getMasterKey() {
|
|
|
4239
4986
|
}
|
|
4240
4987
|
}
|
|
4241
4988
|
const keyPath = getKeyPath();
|
|
4242
|
-
if (!
|
|
4989
|
+
if (!existsSync13(keyPath)) {
|
|
4990
|
+
process.stderr.write(
|
|
4991
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4992
|
+
`
|
|
4993
|
+
);
|
|
4243
4994
|
return null;
|
|
4244
4995
|
}
|
|
4245
4996
|
try {
|
|
4246
4997
|
const content = await readFile4(keyPath, "utf-8");
|
|
4247
4998
|
return Buffer.from(content.trim(), "base64");
|
|
4248
|
-
} catch {
|
|
4999
|
+
} catch (err) {
|
|
5000
|
+
process.stderr.write(
|
|
5001
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
5002
|
+
`
|
|
5003
|
+
);
|
|
4249
5004
|
return null;
|
|
4250
5005
|
}
|
|
4251
5006
|
}
|
|
@@ -4271,13 +5026,13 @@ __export(shard_manager_exports, {
|
|
|
4271
5026
|
listShards: () => listShards,
|
|
4272
5027
|
shardExists: () => shardExists
|
|
4273
5028
|
});
|
|
4274
|
-
import
|
|
4275
|
-
import { existsSync as
|
|
5029
|
+
import path17 from "path";
|
|
5030
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
|
|
4276
5031
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4277
5032
|
function initShardManager(encryptionKey) {
|
|
4278
5033
|
_encryptionKey = encryptionKey;
|
|
4279
|
-
if (!
|
|
4280
|
-
|
|
5034
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5035
|
+
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
4281
5036
|
}
|
|
4282
5037
|
_shardingEnabled = true;
|
|
4283
5038
|
}
|
|
@@ -4297,7 +5052,7 @@ function getShardClient(projectName) {
|
|
|
4297
5052
|
}
|
|
4298
5053
|
const cached = _shards.get(safeName);
|
|
4299
5054
|
if (cached) return cached;
|
|
4300
|
-
const dbPath =
|
|
5055
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
4301
5056
|
const client = createClient2({
|
|
4302
5057
|
url: `file:${dbPath}`,
|
|
4303
5058
|
encryptionKey: _encryptionKey
|
|
@@ -4307,10 +5062,10 @@ function getShardClient(projectName) {
|
|
|
4307
5062
|
}
|
|
4308
5063
|
function shardExists(projectName) {
|
|
4309
5064
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4310
|
-
return
|
|
5065
|
+
return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
4311
5066
|
}
|
|
4312
5067
|
function listShards() {
|
|
4313
|
-
if (!
|
|
5068
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
4314
5069
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4315
5070
|
}
|
|
4316
5071
|
async function ensureShardSchema(client) {
|
|
@@ -4496,7 +5251,7 @@ var init_shard_manager = __esm({
|
|
|
4496
5251
|
"src/lib/shard-manager.ts"() {
|
|
4497
5252
|
"use strict";
|
|
4498
5253
|
init_config();
|
|
4499
|
-
SHARDS_DIR =
|
|
5254
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
4500
5255
|
_shards = /* @__PURE__ */ new Map();
|
|
4501
5256
|
_encryptionKey = null;
|
|
4502
5257
|
_shardingEnabled = false;
|
|
@@ -4621,7 +5376,7 @@ __export(global_procedures_exports, {
|
|
|
4621
5376
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4622
5377
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4623
5378
|
});
|
|
4624
|
-
import { randomUUID as
|
|
5379
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4625
5380
|
async function loadGlobalProcedures() {
|
|
4626
5381
|
const client = getClient();
|
|
4627
5382
|
const result = await client.execute({
|
|
@@ -4650,7 +5405,7 @@ ${sections.join("\n\n")}
|
|
|
4650
5405
|
`;
|
|
4651
5406
|
}
|
|
4652
5407
|
async function storeGlobalProcedure(input) {
|
|
4653
|
-
const id =
|
|
5408
|
+
const id = randomUUID3();
|
|
4654
5409
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4655
5410
|
const client = getClient();
|
|
4656
5411
|
await client.execute({
|
|
@@ -4701,6 +5456,7 @@ __export(store_exports, {
|
|
|
4701
5456
|
vectorToBlob: () => vectorToBlob,
|
|
4702
5457
|
writeMemory: () => writeMemory
|
|
4703
5458
|
});
|
|
5459
|
+
import { createHash } from "crypto";
|
|
4704
5460
|
function isBusyError2(err) {
|
|
4705
5461
|
if (err instanceof Error) {
|
|
4706
5462
|
const msg = err.message.toLowerCase();
|
|
@@ -4774,12 +5530,52 @@ function classifyTier(record) {
|
|
|
4774
5530
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4775
5531
|
return 3;
|
|
4776
5532
|
}
|
|
5533
|
+
function inferFilePaths(record) {
|
|
5534
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5535
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5536
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5537
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5538
|
+
}
|
|
5539
|
+
function inferCommitHash(record) {
|
|
5540
|
+
if (record.tool_name !== "Bash") return null;
|
|
5541
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5542
|
+
return match ? match[1] : null;
|
|
5543
|
+
}
|
|
5544
|
+
function inferLanguageType(record) {
|
|
5545
|
+
const text = record.raw_text;
|
|
5546
|
+
if (!text || text.length < 10) return null;
|
|
5547
|
+
const trimmed = text.trimStart();
|
|
5548
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5549
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5550
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5551
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5552
|
+
return "mixed";
|
|
5553
|
+
}
|
|
5554
|
+
function inferDomain(record) {
|
|
5555
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5556
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5557
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5558
|
+
return null;
|
|
5559
|
+
}
|
|
4777
5560
|
async function writeMemory(record) {
|
|
4778
5561
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4779
5562
|
throw new Error(
|
|
4780
5563
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4781
5564
|
);
|
|
4782
5565
|
}
|
|
5566
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5567
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5568
|
+
return;
|
|
5569
|
+
}
|
|
5570
|
+
try {
|
|
5571
|
+
const client = getClient();
|
|
5572
|
+
const existing = await client.execute({
|
|
5573
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5574
|
+
args: [contentHash, record.agent_id]
|
|
5575
|
+
});
|
|
5576
|
+
if (existing.rows.length > 0) return;
|
|
5577
|
+
} catch {
|
|
5578
|
+
}
|
|
4783
5579
|
const dbRow = {
|
|
4784
5580
|
id: record.id,
|
|
4785
5581
|
agent_id: record.agent_id,
|
|
@@ -4809,7 +5605,23 @@ async function writeMemory(record) {
|
|
|
4809
5605
|
supersedes_id: record.supersedes_id ?? null,
|
|
4810
5606
|
draft: record.draft ? 1 : 0,
|
|
4811
5607
|
memory_type: record.memory_type ?? "raw",
|
|
4812
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5608
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5609
|
+
content_hash: contentHash,
|
|
5610
|
+
intent: record.intent ?? null,
|
|
5611
|
+
outcome: record.outcome ?? null,
|
|
5612
|
+
domain: record.domain ?? inferDomain(record),
|
|
5613
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5614
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5615
|
+
chain_position: record.chain_position ?? null,
|
|
5616
|
+
review_status: record.review_status ?? null,
|
|
5617
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5618
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5619
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5620
|
+
duration_ms: record.duration_ms ?? null,
|
|
5621
|
+
token_cost: record.token_cost ?? null,
|
|
5622
|
+
audience: record.audience ?? null,
|
|
5623
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5624
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4813
5625
|
};
|
|
4814
5626
|
_pendingRecords.push(dbRow);
|
|
4815
5627
|
orgBus.emit({
|
|
@@ -4867,80 +5679,85 @@ async function flushBatch() {
|
|
|
4867
5679
|
const draft = row.draft ? 1 : 0;
|
|
4868
5680
|
const memoryType = row.memory_type ?? "raw";
|
|
4869
5681
|
const trajectory = row.trajectory ?? null;
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
5682
|
+
const contentHash = row.content_hash ?? null;
|
|
5683
|
+
const intent = row.intent ?? null;
|
|
5684
|
+
const outcome = row.outcome ?? null;
|
|
5685
|
+
const domain = row.domain ?? null;
|
|
5686
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5687
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5688
|
+
const chainPosition = row.chain_position ?? null;
|
|
5689
|
+
const reviewStatus = row.review_status ?? null;
|
|
5690
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5691
|
+
const filePaths = row.file_paths ?? null;
|
|
5692
|
+
const commitHash = row.commit_hash ?? null;
|
|
5693
|
+
const durationMs = row.duration_ms ?? null;
|
|
5694
|
+
const tokenCost = row.token_cost ?? null;
|
|
5695
|
+
const audience = row.audience ?? null;
|
|
5696
|
+
const languageType = row.language_type ?? null;
|
|
5697
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5698
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4873
5699
|
tool_name, project_name,
|
|
4874
5700
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4875
5701
|
confidence, last_accessed,
|
|
4876
5702
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4877
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
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
|
-
pageNumber,
|
|
4936
|
-
sourcePath,
|
|
4937
|
-
sourceType,
|
|
4938
|
-
tier,
|
|
4939
|
-
supersedesId,
|
|
4940
|
-
draft,
|
|
4941
|
-
memoryType,
|
|
4942
|
-
trajectory
|
|
4943
|
-
]
|
|
5703
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5704
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5705
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5706
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5707
|
+
const metaArgs = [
|
|
5708
|
+
intent,
|
|
5709
|
+
outcome,
|
|
5710
|
+
domain,
|
|
5711
|
+
referencedEntities,
|
|
5712
|
+
retrievalCount,
|
|
5713
|
+
chainPosition,
|
|
5714
|
+
reviewStatus,
|
|
5715
|
+
contextWindowPct,
|
|
5716
|
+
filePaths,
|
|
5717
|
+
commitHash,
|
|
5718
|
+
durationMs,
|
|
5719
|
+
tokenCost,
|
|
5720
|
+
audience,
|
|
5721
|
+
languageType,
|
|
5722
|
+
parentMemoryId
|
|
5723
|
+
];
|
|
5724
|
+
const baseArgs = [
|
|
5725
|
+
row.id,
|
|
5726
|
+
row.agent_id,
|
|
5727
|
+
row.agent_role,
|
|
5728
|
+
row.session_id,
|
|
5729
|
+
row.timestamp,
|
|
5730
|
+
row.tool_name,
|
|
5731
|
+
row.project_name,
|
|
5732
|
+
row.has_error,
|
|
5733
|
+
row.raw_text
|
|
5734
|
+
];
|
|
5735
|
+
const sharedArgs = [
|
|
5736
|
+
row.version,
|
|
5737
|
+
taskId,
|
|
5738
|
+
importance,
|
|
5739
|
+
status,
|
|
5740
|
+
confidence,
|
|
5741
|
+
lastAccessed,
|
|
5742
|
+
workspaceId,
|
|
5743
|
+
documentId,
|
|
5744
|
+
userId,
|
|
5745
|
+
charOffset,
|
|
5746
|
+
pageNumber,
|
|
5747
|
+
sourcePath,
|
|
5748
|
+
sourceType,
|
|
5749
|
+
tier,
|
|
5750
|
+
supersedesId,
|
|
5751
|
+
draft,
|
|
5752
|
+
memoryType,
|
|
5753
|
+
trajectory,
|
|
5754
|
+
contentHash
|
|
5755
|
+
];
|
|
5756
|
+
return {
|
|
5757
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5758
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5759
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5760
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4944
5761
|
};
|
|
4945
5762
|
};
|
|
4946
5763
|
const globalClient = getClient();
|
|
@@ -5191,15 +6008,16 @@ var init_store = __esm({
|
|
|
5191
6008
|
});
|
|
5192
6009
|
|
|
5193
6010
|
// src/bin/scan-tasks.ts
|
|
5194
|
-
import { existsSync as
|
|
5195
|
-
import
|
|
5196
|
-
import
|
|
6011
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
|
|
6012
|
+
import path18 from "path";
|
|
6013
|
+
import os9 from "os";
|
|
5197
6014
|
|
|
5198
6015
|
// src/lib/is-main.ts
|
|
5199
6016
|
import { realpathSync } from "fs";
|
|
5200
6017
|
import { fileURLToPath } from "url";
|
|
5201
6018
|
function isMainModule(importMetaUrl) {
|
|
5202
6019
|
if (process.argv[1] == null) return false;
|
|
6020
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
5203
6021
|
try {
|
|
5204
6022
|
const scriptPath = realpathSync(process.argv[1]);
|
|
5205
6023
|
const modulePath = realpathSync(fileURLToPath(importMetaUrl));
|
|
@@ -5213,14 +6031,14 @@ function isMainModule(importMetaUrl) {
|
|
|
5213
6031
|
init_task_scope();
|
|
5214
6032
|
function checkMcpHealth() {
|
|
5215
6033
|
try {
|
|
5216
|
-
const claudeJson =
|
|
5217
|
-
if (!
|
|
6034
|
+
const claudeJson = path18.join(os9.homedir(), ".claude.json");
|
|
6035
|
+
if (!existsSync15(claudeJson)) {
|
|
5218
6036
|
process.stderr.write(
|
|
5219
6037
|
"\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n"
|
|
5220
6038
|
);
|
|
5221
6039
|
return;
|
|
5222
6040
|
}
|
|
5223
|
-
const config = JSON.parse(
|
|
6041
|
+
const config = JSON.parse(readFileSync12(claudeJson, "utf8"));
|
|
5224
6042
|
const servers = config.mcpServers;
|
|
5225
6043
|
if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
|
|
5226
6044
|
process.stderr.write(
|