@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/exe-dispatch.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,7 +651,7 @@ 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"]);
|
|
@@ -628,6 +679,12 @@ function getClient() {
|
|
|
628
679
|
if (!_resilientClient) {
|
|
629
680
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
630
681
|
}
|
|
682
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
683
|
+
return _resilientClient;
|
|
684
|
+
}
|
|
685
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
686
|
+
return _daemonClient;
|
|
687
|
+
}
|
|
631
688
|
return _resilientClient;
|
|
632
689
|
}
|
|
633
690
|
function getRawClient() {
|
|
@@ -1116,6 +1173,12 @@ async function ensureSchema() {
|
|
|
1116
1173
|
} catch {
|
|
1117
1174
|
}
|
|
1118
1175
|
}
|
|
1176
|
+
try {
|
|
1177
|
+
await client.execute(
|
|
1178
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1179
|
+
);
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1119
1182
|
await client.executeMultiple(`
|
|
1120
1183
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1121
1184
|
id TEXT PRIMARY KEY,
|
|
@@ -1168,7 +1231,30 @@ async function ensureSchema() {
|
|
|
1168
1231
|
entity_id TEXT NOT NULL,
|
|
1169
1232
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1170
1233
|
);
|
|
1234
|
+
|
|
1235
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1236
|
+
name,
|
|
1237
|
+
content=entities,
|
|
1238
|
+
content_rowid=rowid
|
|
1239
|
+
);
|
|
1240
|
+
|
|
1241
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1242
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1243
|
+
END;
|
|
1244
|
+
|
|
1245
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1246
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1247
|
+
END;
|
|
1248
|
+
|
|
1249
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1250
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1251
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1252
|
+
END;
|
|
1171
1253
|
`);
|
|
1254
|
+
try {
|
|
1255
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1172
1258
|
await client.executeMultiple(`
|
|
1173
1259
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1174
1260
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1349,6 +1435,33 @@ async function ensureSchema() {
|
|
|
1349
1435
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1350
1436
|
ON conversations(channel_id);
|
|
1351
1437
|
`);
|
|
1438
|
+
await client.executeMultiple(`
|
|
1439
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1440
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1441
|
+
agent_id TEXT NOT NULL,
|
|
1442
|
+
session_name TEXT,
|
|
1443
|
+
task_id TEXT,
|
|
1444
|
+
project_name TEXT,
|
|
1445
|
+
started_at TEXT NOT NULL
|
|
1446
|
+
);
|
|
1447
|
+
|
|
1448
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1449
|
+
ON session_agent_map(agent_id);
|
|
1450
|
+
`);
|
|
1451
|
+
try {
|
|
1452
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1453
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1454
|
+
await client.execute({
|
|
1455
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1456
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1457
|
+
FROM memories
|
|
1458
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1459
|
+
GROUP BY session_id, agent_id`,
|
|
1460
|
+
args: []
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
} catch {
|
|
1464
|
+
}
|
|
1352
1465
|
try {
|
|
1353
1466
|
await client.execute({
|
|
1354
1467
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1482,8 +1595,30 @@ async function ensureSchema() {
|
|
|
1482
1595
|
});
|
|
1483
1596
|
} catch {
|
|
1484
1597
|
}
|
|
1598
|
+
for (const col of [
|
|
1599
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1600
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1601
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1602
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1603
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1604
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1605
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1606
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1607
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1608
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1609
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1610
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1611
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1612
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1613
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1614
|
+
]) {
|
|
1615
|
+
try {
|
|
1616
|
+
await client.execute(col);
|
|
1617
|
+
} catch {
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1485
1620
|
}
|
|
1486
|
-
var _client, _resilientClient, initTurso;
|
|
1621
|
+
var _client, _resilientClient, _daemonClient, initTurso;
|
|
1487
1622
|
var init_database = __esm({
|
|
1488
1623
|
"src/lib/database.ts"() {
|
|
1489
1624
|
"use strict";
|
|
@@ -1491,23 +1626,24 @@ var init_database = __esm({
|
|
|
1491
1626
|
init_employees();
|
|
1492
1627
|
_client = null;
|
|
1493
1628
|
_resilientClient = null;
|
|
1629
|
+
_daemonClient = null;
|
|
1494
1630
|
initTurso = initDatabase;
|
|
1495
1631
|
}
|
|
1496
1632
|
});
|
|
1497
1633
|
|
|
1498
1634
|
// src/lib/license.ts
|
|
1499
|
-
import { readFileSync as
|
|
1635
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
1500
1636
|
import { randomUUID } from "crypto";
|
|
1501
|
-
import
|
|
1637
|
+
import path6 from "path";
|
|
1502
1638
|
import { jwtVerify, importSPKI } from "jose";
|
|
1503
1639
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1504
1640
|
var init_license = __esm({
|
|
1505
1641
|
"src/lib/license.ts"() {
|
|
1506
1642
|
"use strict";
|
|
1507
1643
|
init_config();
|
|
1508
|
-
LICENSE_PATH =
|
|
1509
|
-
CACHE_PATH =
|
|
1510
|
-
DEVICE_ID_PATH =
|
|
1644
|
+
LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
1645
|
+
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
1646
|
+
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
1511
1647
|
PLAN_LIMITS = {
|
|
1512
1648
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1513
1649
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1519,12 +1655,12 @@ var init_license = __esm({
|
|
|
1519
1655
|
});
|
|
1520
1656
|
|
|
1521
1657
|
// src/lib/plan-limits.ts
|
|
1522
|
-
import { readFileSync as
|
|
1523
|
-
import
|
|
1658
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
1659
|
+
import path7 from "path";
|
|
1524
1660
|
function getLicenseSync() {
|
|
1525
1661
|
try {
|
|
1526
|
-
if (!
|
|
1527
|
-
const raw = JSON.parse(
|
|
1662
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
1663
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1528
1664
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1529
1665
|
const parts = raw.token.split(".");
|
|
1530
1666
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1562,8 +1698,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1562
1698
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1563
1699
|
let count = 0;
|
|
1564
1700
|
try {
|
|
1565
|
-
if (
|
|
1566
|
-
const raw =
|
|
1701
|
+
if (existsSync7(filePath)) {
|
|
1702
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1567
1703
|
const employees = JSON.parse(raw);
|
|
1568
1704
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1569
1705
|
}
|
|
@@ -1592,19 +1728,19 @@ var init_plan_limits = __esm({
|
|
|
1592
1728
|
this.name = "PlanLimitError";
|
|
1593
1729
|
}
|
|
1594
1730
|
};
|
|
1595
|
-
CACHE_PATH2 =
|
|
1731
|
+
CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1596
1732
|
}
|
|
1597
1733
|
});
|
|
1598
1734
|
|
|
1599
1735
|
// src/lib/notifications.ts
|
|
1600
1736
|
import crypto from "crypto";
|
|
1601
|
-
import
|
|
1737
|
+
import path8 from "path";
|
|
1602
1738
|
import os5 from "os";
|
|
1603
1739
|
import {
|
|
1604
|
-
readFileSync as
|
|
1740
|
+
readFileSync as readFileSync8,
|
|
1605
1741
|
readdirSync,
|
|
1606
1742
|
unlinkSync as unlinkSync2,
|
|
1607
|
-
existsSync as
|
|
1743
|
+
existsSync as existsSync8,
|
|
1608
1744
|
rmdirSync
|
|
1609
1745
|
} from "fs";
|
|
1610
1746
|
async function writeNotification(notification) {
|
|
@@ -1763,10 +1899,11 @@ var init_state_bus = __esm({
|
|
|
1763
1899
|
|
|
1764
1900
|
// src/lib/tasks-crud.ts
|
|
1765
1901
|
import crypto3 from "crypto";
|
|
1766
|
-
import
|
|
1902
|
+
import path9 from "path";
|
|
1903
|
+
import os6 from "os";
|
|
1767
1904
|
import { execSync as execSync4 } from "child_process";
|
|
1768
1905
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1769
|
-
import { existsSync as
|
|
1906
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1770
1907
|
async function writeCheckpoint(input) {
|
|
1771
1908
|
const client = getClient();
|
|
1772
1909
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1807,6 +1944,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1807
1944
|
function slugify(title) {
|
|
1808
1945
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1809
1946
|
}
|
|
1947
|
+
function buildKeywordIndex() {
|
|
1948
|
+
const idx = /* @__PURE__ */ new Map();
|
|
1949
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
1950
|
+
for (const kw of keywords) {
|
|
1951
|
+
const existing = idx.get(kw) ?? [];
|
|
1952
|
+
existing.push(role);
|
|
1953
|
+
idx.set(kw, existing);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
return idx;
|
|
1957
|
+
}
|
|
1958
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
1959
|
+
const employees = loadEmployeesSync();
|
|
1960
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
1961
|
+
if (!employee) return void 0;
|
|
1962
|
+
const assigneeRole = employee.role;
|
|
1963
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
1964
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
1965
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
1966
|
+
if (text.includes(keyword)) {
|
|
1967
|
+
for (const role of roles) matchedRoles.add(role);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
if (matchedRoles.size === 0) return void 0;
|
|
1971
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
1972
|
+
if (assigneeRole === "COO") return void 0;
|
|
1973
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
1974
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
1975
|
+
}
|
|
1810
1976
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1811
1977
|
const scope = sessionScopeFilter(scopeSession);
|
|
1812
1978
|
let result2 = await client.execute({
|
|
@@ -1856,7 +2022,14 @@ async function createTaskCore(input) {
|
|
|
1856
2022
|
const id = crypto3.randomUUID();
|
|
1857
2023
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1858
2024
|
const slug = slugify(input.title);
|
|
1859
|
-
|
|
2025
|
+
let earlySessionScope = null;
|
|
2026
|
+
try {
|
|
2027
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2028
|
+
earlySessionScope = resolveExeSession2();
|
|
2029
|
+
} catch {
|
|
2030
|
+
}
|
|
2031
|
+
const scope = earlySessionScope ?? "default";
|
|
2032
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1860
2033
|
let blockedById = null;
|
|
1861
2034
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1862
2035
|
if (input.blockedBy) {
|
|
@@ -1896,22 +2069,24 @@ async function createTaskCore(input) {
|
|
|
1896
2069
|
if (dupCheck.rows.length > 0) {
|
|
1897
2070
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1898
2071
|
}
|
|
2072
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2073
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2074
|
+
if (laneWarning) {
|
|
2075
|
+
warning = warning ? `${warning}
|
|
2076
|
+
${laneWarning}` : laneWarning;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
1899
2079
|
if (input.baseDir) {
|
|
1900
2080
|
try {
|
|
1901
|
-
await mkdir3(
|
|
1902
|
-
await mkdir3(
|
|
2081
|
+
await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2082
|
+
await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1903
2083
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1904
2084
|
await ensureGitignoreExe(input.baseDir);
|
|
1905
2085
|
} catch {
|
|
1906
2086
|
}
|
|
1907
2087
|
}
|
|
1908
2088
|
const complexity = input.complexity ?? "standard";
|
|
1909
|
-
|
|
1910
|
-
try {
|
|
1911
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1912
|
-
sessionScope = resolveExeSession2();
|
|
1913
|
-
} catch {
|
|
1914
|
-
}
|
|
2089
|
+
const sessionScope = earlySessionScope;
|
|
1915
2090
|
await client.execute({
|
|
1916
2091
|
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)
|
|
1917
2092
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1938,6 +2113,43 @@ async function createTaskCore(input) {
|
|
|
1938
2113
|
now
|
|
1939
2114
|
]
|
|
1940
2115
|
});
|
|
2116
|
+
if (input.baseDir) {
|
|
2117
|
+
try {
|
|
2118
|
+
const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
|
|
2119
|
+
const mdPath = path9.join(EXE_OS_DIR, taskFile);
|
|
2120
|
+
const mdDir = path9.dirname(mdPath);
|
|
2121
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2122
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2123
|
+
const mdContent = `# ${input.title}
|
|
2124
|
+
|
|
2125
|
+
**ID:** ${id}
|
|
2126
|
+
**Status:** ${initialStatus}
|
|
2127
|
+
**Priority:** ${input.priority}
|
|
2128
|
+
**Assigned by:** ${input.assignedBy}
|
|
2129
|
+
**Assigned to:** ${input.assignedTo}
|
|
2130
|
+
**Project:** ${input.projectName}
|
|
2131
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2132
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2133
|
+
**Reviewer:** ${reviewer}
|
|
2134
|
+
|
|
2135
|
+
## Context
|
|
2136
|
+
|
|
2137
|
+
${input.context}
|
|
2138
|
+
|
|
2139
|
+
## MANDATORY: When done
|
|
2140
|
+
|
|
2141
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2142
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2143
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2144
|
+
`;
|
|
2145
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2146
|
+
} catch (err) {
|
|
2147
|
+
process.stderr.write(
|
|
2148
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2149
|
+
`
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
1941
2153
|
return {
|
|
1942
2154
|
id,
|
|
1943
2155
|
title: input.title,
|
|
@@ -2130,7 +2342,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2130
2342
|
return { row, taskFile, now, taskId };
|
|
2131
2343
|
}
|
|
2132
2344
|
}
|
|
2133
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2345
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2134
2346
|
process.stderr.write(
|
|
2135
2347
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2136
2348
|
`
|
|
@@ -2195,9 +2407,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2195
2407
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2196
2408
|
}
|
|
2197
2409
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2198
|
-
const archPath =
|
|
2410
|
+
const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2199
2411
|
try {
|
|
2200
|
-
if (
|
|
2412
|
+
if (existsSync9(archPath)) return;
|
|
2201
2413
|
const template = [
|
|
2202
2414
|
`# ${projectName} \u2014 System Architecture`,
|
|
2203
2415
|
"",
|
|
@@ -2230,10 +2442,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2230
2442
|
}
|
|
2231
2443
|
}
|
|
2232
2444
|
async function ensureGitignoreExe(baseDir) {
|
|
2233
|
-
const gitignorePath =
|
|
2445
|
+
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
2234
2446
|
try {
|
|
2235
|
-
if (
|
|
2236
|
-
const content =
|
|
2447
|
+
if (existsSync9(gitignorePath)) {
|
|
2448
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2237
2449
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2238
2450
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2239
2451
|
} else {
|
|
@@ -2242,20 +2454,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2242
2454
|
} catch {
|
|
2243
2455
|
}
|
|
2244
2456
|
}
|
|
2245
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2457
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2246
2458
|
var init_tasks_crud = __esm({
|
|
2247
2459
|
"src/lib/tasks-crud.ts"() {
|
|
2248
2460
|
"use strict";
|
|
2249
2461
|
init_database();
|
|
2250
2462
|
init_task_scope();
|
|
2463
|
+
init_employees();
|
|
2464
|
+
LANE_KEYWORDS = {
|
|
2465
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2466
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2467
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2468
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2469
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2470
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2471
|
+
};
|
|
2472
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2251
2473
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2252
2474
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2253
2475
|
}
|
|
2254
2476
|
});
|
|
2255
2477
|
|
|
2256
2478
|
// src/lib/tasks-review.ts
|
|
2257
|
-
import
|
|
2258
|
-
import { existsSync as
|
|
2479
|
+
import path10 from "path";
|
|
2480
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
2259
2481
|
async function countPendingReviews(sessionScope) {
|
|
2260
2482
|
const client = getClient();
|
|
2261
2483
|
if (sessionScope) {
|
|
@@ -2277,7 +2499,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2277
2499
|
const result3 = await client.execute({
|
|
2278
2500
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2279
2501
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2280
|
-
AND
|
|
2502
|
+
AND session_scope = ?`,
|
|
2281
2503
|
args: [sinceIso, sessionScope]
|
|
2282
2504
|
});
|
|
2283
2505
|
return Number(result3.rows[0]?.cnt) || 0;
|
|
@@ -2295,7 +2517,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2295
2517
|
const result3 = await client.execute({
|
|
2296
2518
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2297
2519
|
WHERE status = 'needs_review'
|
|
2298
|
-
AND
|
|
2520
|
+
AND session_scope = ?
|
|
2299
2521
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2300
2522
|
args: [sessionScope, limit]
|
|
2301
2523
|
});
|
|
@@ -2416,14 +2638,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2416
2638
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2417
2639
|
const agent = parts[1];
|
|
2418
2640
|
const slug = parts.slice(2).join("-");
|
|
2419
|
-
const
|
|
2641
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2420
2642
|
const result2 = await client.execute({
|
|
2421
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2422
|
-
args: [now,
|
|
2643
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
2644
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2423
2645
|
});
|
|
2424
2646
|
if (result2.rowsAffected > 0) {
|
|
2425
2647
|
process.stderr.write(
|
|
2426
|
-
`[review-cleanup] Cascaded original task to done
|
|
2648
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2427
2649
|
`
|
|
2428
2650
|
);
|
|
2429
2651
|
}
|
|
@@ -2436,11 +2658,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2436
2658
|
);
|
|
2437
2659
|
}
|
|
2438
2660
|
try {
|
|
2439
|
-
const cacheDir =
|
|
2440
|
-
if (
|
|
2661
|
+
const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
|
|
2662
|
+
if (existsSync10(cacheDir)) {
|
|
2441
2663
|
for (const f of readdirSync2(cacheDir)) {
|
|
2442
2664
|
if (f.startsWith("review-notified-")) {
|
|
2443
|
-
unlinkSync3(
|
|
2665
|
+
unlinkSync3(path10.join(cacheDir, f));
|
|
2444
2666
|
}
|
|
2445
2667
|
}
|
|
2446
2668
|
}
|
|
@@ -2461,7 +2683,7 @@ var init_tasks_review = __esm({
|
|
|
2461
2683
|
});
|
|
2462
2684
|
|
|
2463
2685
|
// src/lib/tasks-chain.ts
|
|
2464
|
-
import
|
|
2686
|
+
import path11 from "path";
|
|
2465
2687
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2466
2688
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2467
2689
|
const client = getClient();
|
|
@@ -2478,7 +2700,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2478
2700
|
});
|
|
2479
2701
|
for (const ur of unblockedRows.rows) {
|
|
2480
2702
|
try {
|
|
2481
|
-
const ubFile =
|
|
2703
|
+
const ubFile = path11.join(baseDir, String(ur.task_file));
|
|
2482
2704
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2483
2705
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2484
2706
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2547,7 +2769,7 @@ var init_tasks_chain = __esm({
|
|
|
2547
2769
|
|
|
2548
2770
|
// src/lib/project-name.ts
|
|
2549
2771
|
import { execSync as execSync5 } from "child_process";
|
|
2550
|
-
import
|
|
2772
|
+
import path12 from "path";
|
|
2551
2773
|
function getProjectName(cwd) {
|
|
2552
2774
|
const dir = cwd ?? process.cwd();
|
|
2553
2775
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2560,7 +2782,7 @@ function getProjectName(cwd) {
|
|
|
2560
2782
|
timeout: 2e3,
|
|
2561
2783
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2562
2784
|
}).trim();
|
|
2563
|
-
repoRoot =
|
|
2785
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
2564
2786
|
} catch {
|
|
2565
2787
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2566
2788
|
cwd: dir,
|
|
@@ -2569,11 +2791,11 @@ function getProjectName(cwd) {
|
|
|
2569
2791
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2570
2792
|
}).trim();
|
|
2571
2793
|
}
|
|
2572
|
-
_cached2 =
|
|
2794
|
+
_cached2 = path12.basename(repoRoot);
|
|
2573
2795
|
_cachedCwd = dir;
|
|
2574
2796
|
return _cached2;
|
|
2575
2797
|
} catch {
|
|
2576
|
-
_cached2 =
|
|
2798
|
+
_cached2 = path12.basename(dir);
|
|
2577
2799
|
_cachedCwd = dir;
|
|
2578
2800
|
return _cached2;
|
|
2579
2801
|
}
|
|
@@ -2605,7 +2827,7 @@ function findSessionForProject(projectName) {
|
|
|
2605
2827
|
const sessions = listSessions();
|
|
2606
2828
|
for (const s of sessions) {
|
|
2607
2829
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2608
|
-
if (proj === projectName &&
|
|
2830
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2609
2831
|
}
|
|
2610
2832
|
return null;
|
|
2611
2833
|
}
|
|
@@ -2651,7 +2873,7 @@ var init_session_scope = __esm({
|
|
|
2651
2873
|
|
|
2652
2874
|
// src/lib/tasks-notify.ts
|
|
2653
2875
|
async function dispatchTaskToEmployee(input) {
|
|
2654
|
-
if (
|
|
2876
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2655
2877
|
let crossProject = false;
|
|
2656
2878
|
if (input.projectName) {
|
|
2657
2879
|
try {
|
|
@@ -3046,8 +3268,8 @@ __export(tasks_exports, {
|
|
|
3046
3268
|
updateTaskStatus: () => updateTaskStatus,
|
|
3047
3269
|
writeCheckpoint: () => writeCheckpoint
|
|
3048
3270
|
});
|
|
3049
|
-
import
|
|
3050
|
-
import { writeFileSync as
|
|
3271
|
+
import path13 from "path";
|
|
3272
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
3051
3273
|
async function createTask(input) {
|
|
3052
3274
|
const result2 = await createTaskCore(input);
|
|
3053
3275
|
if (!input.skipDispatch && result2.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3066,11 +3288,11 @@ async function updateTask(input) {
|
|
|
3066
3288
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3067
3289
|
try {
|
|
3068
3290
|
const agent = String(row.assigned_to);
|
|
3069
|
-
const cacheDir =
|
|
3070
|
-
const cachePath =
|
|
3291
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
3292
|
+
const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
|
|
3071
3293
|
if (input.status === "in_progress") {
|
|
3072
|
-
|
|
3073
|
-
|
|
3294
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
3295
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3074
3296
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3075
3297
|
try {
|
|
3076
3298
|
unlinkSync4(cachePath);
|
|
@@ -3130,7 +3352,7 @@ async function updateTask(input) {
|
|
|
3130
3352
|
}
|
|
3131
3353
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3132
3354
|
if (isTerminal) {
|
|
3133
|
-
const isCoordinator =
|
|
3355
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3134
3356
|
if (!isCoordinator) {
|
|
3135
3357
|
notifyTaskDone();
|
|
3136
3358
|
}
|
|
@@ -3155,7 +3377,7 @@ async function updateTask(input) {
|
|
|
3155
3377
|
}
|
|
3156
3378
|
}
|
|
3157
3379
|
}
|
|
3158
|
-
if (input.status === "done" &&
|
|
3380
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3159
3381
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3160
3382
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3161
3383
|
taskId,
|
|
@@ -3171,7 +3393,7 @@ async function updateTask(input) {
|
|
|
3171
3393
|
});
|
|
3172
3394
|
}
|
|
3173
3395
|
let nextTask;
|
|
3174
|
-
if (isTerminal &&
|
|
3396
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3175
3397
|
try {
|
|
3176
3398
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3177
3399
|
} catch {
|
|
@@ -3537,13 +3759,13 @@ __export(tmux_routing_exports, {
|
|
|
3537
3759
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3538
3760
|
});
|
|
3539
3761
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3540
|
-
import { readFileSync as
|
|
3541
|
-
import
|
|
3542
|
-
import
|
|
3762
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
|
|
3763
|
+
import path14 from "path";
|
|
3764
|
+
import os7 from "os";
|
|
3543
3765
|
import { fileURLToPath } from "url";
|
|
3544
3766
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
3545
3767
|
function spawnLockPath(sessionName) {
|
|
3546
|
-
return
|
|
3768
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3547
3769
|
}
|
|
3548
3770
|
function isProcessAlive(pid) {
|
|
3549
3771
|
try {
|
|
@@ -3554,13 +3776,13 @@ function isProcessAlive(pid) {
|
|
|
3554
3776
|
}
|
|
3555
3777
|
}
|
|
3556
3778
|
function acquireSpawnLock(sessionName) {
|
|
3557
|
-
if (!
|
|
3558
|
-
|
|
3779
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3780
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3559
3781
|
}
|
|
3560
3782
|
const lockFile = spawnLockPath(sessionName);
|
|
3561
|
-
if (
|
|
3783
|
+
if (existsSync11(lockFile)) {
|
|
3562
3784
|
try {
|
|
3563
|
-
const lock = JSON.parse(
|
|
3785
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3564
3786
|
const age = Date.now() - lock.timestamp;
|
|
3565
3787
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3566
3788
|
return false;
|
|
@@ -3568,7 +3790,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
3568
3790
|
} catch {
|
|
3569
3791
|
}
|
|
3570
3792
|
}
|
|
3571
|
-
|
|
3793
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3572
3794
|
return true;
|
|
3573
3795
|
}
|
|
3574
3796
|
function releaseSpawnLock(sessionName) {
|
|
@@ -3580,13 +3802,13 @@ function releaseSpawnLock(sessionName) {
|
|
|
3580
3802
|
function resolveBehaviorsExporterScript() {
|
|
3581
3803
|
try {
|
|
3582
3804
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3583
|
-
const scriptPath =
|
|
3584
|
-
|
|
3805
|
+
const scriptPath = path14.join(
|
|
3806
|
+
path14.dirname(thisFile),
|
|
3585
3807
|
"..",
|
|
3586
3808
|
"bin",
|
|
3587
3809
|
"exe-export-behaviors.js"
|
|
3588
3810
|
);
|
|
3589
|
-
return
|
|
3811
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3590
3812
|
} catch {
|
|
3591
3813
|
return null;
|
|
3592
3814
|
}
|
|
@@ -3652,12 +3874,12 @@ function extractRootExe(name) {
|
|
|
3652
3874
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3653
3875
|
}
|
|
3654
3876
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3655
|
-
if (!
|
|
3656
|
-
|
|
3877
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
3878
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3657
3879
|
}
|
|
3658
3880
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3659
|
-
const filePath =
|
|
3660
|
-
|
|
3881
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3882
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3661
3883
|
parentExe: rootExe,
|
|
3662
3884
|
dispatchedBy: dispatchedBy || rootExe,
|
|
3663
3885
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3665,7 +3887,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3665
3887
|
}
|
|
3666
3888
|
function getParentExe(sessionKey) {
|
|
3667
3889
|
try {
|
|
3668
|
-
const data = JSON.parse(
|
|
3890
|
+
const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3669
3891
|
return data.parentExe || null;
|
|
3670
3892
|
} catch {
|
|
3671
3893
|
return null;
|
|
@@ -3673,8 +3895,8 @@ function getParentExe(sessionKey) {
|
|
|
3673
3895
|
}
|
|
3674
3896
|
function getDispatchedBy(sessionKey) {
|
|
3675
3897
|
try {
|
|
3676
|
-
const data = JSON.parse(
|
|
3677
|
-
|
|
3898
|
+
const data = JSON.parse(readFileSync10(
|
|
3899
|
+
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3678
3900
|
"utf8"
|
|
3679
3901
|
));
|
|
3680
3902
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3735,32 +3957,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3735
3957
|
}
|
|
3736
3958
|
function readDebounceState() {
|
|
3737
3959
|
try {
|
|
3738
|
-
if (!
|
|
3739
|
-
|
|
3960
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
3961
|
+
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3962
|
+
const state = {};
|
|
3963
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
3964
|
+
if (typeof val === "number") {
|
|
3965
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
3966
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
3967
|
+
state[key] = val;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
return state;
|
|
3740
3971
|
} catch {
|
|
3741
3972
|
return {};
|
|
3742
3973
|
}
|
|
3743
3974
|
}
|
|
3744
3975
|
function writeDebounceState(state) {
|
|
3745
3976
|
try {
|
|
3746
|
-
if (!
|
|
3747
|
-
|
|
3977
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3978
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3748
3979
|
} catch {
|
|
3749
3980
|
}
|
|
3750
3981
|
}
|
|
3751
3982
|
function isDebounced(targetSession) {
|
|
3752
3983
|
const state = readDebounceState();
|
|
3753
|
-
const
|
|
3754
|
-
|
|
3984
|
+
const entry = state[targetSession];
|
|
3985
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
3986
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
3987
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
3988
|
+
state[targetSession].pending++;
|
|
3989
|
+
writeDebounceState(state);
|
|
3990
|
+
return true;
|
|
3991
|
+
}
|
|
3992
|
+
return false;
|
|
3755
3993
|
}
|
|
3756
3994
|
function recordDebounce(targetSession) {
|
|
3757
3995
|
const state = readDebounceState();
|
|
3758
|
-
state[targetSession]
|
|
3996
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
3997
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
3759
3998
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
3760
3999
|
for (const key of Object.keys(state)) {
|
|
3761
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4000
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
3762
4001
|
}
|
|
3763
4002
|
writeDebounceState(state);
|
|
4003
|
+
return batched;
|
|
3764
4004
|
}
|
|
3765
4005
|
function logIntercom(msg) {
|
|
3766
4006
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -3805,7 +4045,7 @@ function sendIntercom(targetSession) {
|
|
|
3805
4045
|
return "skipped_exe";
|
|
3806
4046
|
}
|
|
3807
4047
|
if (isDebounced(targetSession)) {
|
|
3808
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
4048
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
3809
4049
|
return "debounced";
|
|
3810
4050
|
}
|
|
3811
4051
|
try {
|
|
@@ -3817,14 +4057,14 @@ function sendIntercom(targetSession) {
|
|
|
3817
4057
|
const sessionState = getSessionState(targetSession);
|
|
3818
4058
|
if (sessionState === "no_claude") {
|
|
3819
4059
|
queueIntercom(targetSession, "claude not running in session");
|
|
3820
|
-
recordDebounce(targetSession);
|
|
3821
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
4060
|
+
const batched2 = recordDebounce(targetSession);
|
|
4061
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3822
4062
|
return "queued";
|
|
3823
4063
|
}
|
|
3824
4064
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3825
4065
|
queueIntercom(targetSession, "session busy at send time");
|
|
3826
|
-
recordDebounce(targetSession);
|
|
3827
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
4066
|
+
const batched2 = recordDebounce(targetSession);
|
|
4067
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3828
4068
|
return "queued";
|
|
3829
4069
|
}
|
|
3830
4070
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -3832,8 +4072,8 @@ function sendIntercom(targetSession) {
|
|
|
3832
4072
|
transport.sendKeys(targetSession, "q");
|
|
3833
4073
|
}
|
|
3834
4074
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3835
|
-
recordDebounce(targetSession);
|
|
3836
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4075
|
+
const batched = recordDebounce(targetSession);
|
|
4076
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
3837
4077
|
return "delivered";
|
|
3838
4078
|
} catch {
|
|
3839
4079
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3863,7 +4103,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3863
4103
|
return true;
|
|
3864
4104
|
}
|
|
3865
4105
|
function ensureEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
3866
|
-
if (
|
|
4106
|
+
if (isCoordinatorName(employeeName2)) {
|
|
3867
4107
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3868
4108
|
}
|
|
3869
4109
|
try {
|
|
@@ -3935,26 +4175,26 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
3935
4175
|
const transport = getTransport();
|
|
3936
4176
|
const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
|
|
3937
4177
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
|
|
3938
|
-
const logDir =
|
|
3939
|
-
const logFile =
|
|
3940
|
-
if (!
|
|
3941
|
-
|
|
4178
|
+
const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4179
|
+
const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4180
|
+
if (!existsSync11(logDir)) {
|
|
4181
|
+
mkdirSync6(logDir, { recursive: true });
|
|
3942
4182
|
}
|
|
3943
4183
|
transport.kill(sessionName);
|
|
3944
4184
|
let cleanupSuffix = "";
|
|
3945
4185
|
try {
|
|
3946
4186
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3947
|
-
const cleanupScript =
|
|
3948
|
-
if (
|
|
4187
|
+
const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4188
|
+
if (existsSync11(cleanupScript)) {
|
|
3949
4189
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
|
|
3950
4190
|
}
|
|
3951
4191
|
} catch {
|
|
3952
4192
|
}
|
|
3953
4193
|
try {
|
|
3954
|
-
const claudeJsonPath =
|
|
4194
|
+
const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
|
|
3955
4195
|
let claudeJson = {};
|
|
3956
4196
|
try {
|
|
3957
|
-
claudeJson = JSON.parse(
|
|
4197
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
3958
4198
|
} catch {
|
|
3959
4199
|
}
|
|
3960
4200
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3962,17 +4202,17 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
3962
4202
|
const trustDir = opts?.cwd ?? projectDir2;
|
|
3963
4203
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3964
4204
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3965
|
-
|
|
4205
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3966
4206
|
} catch {
|
|
3967
4207
|
}
|
|
3968
4208
|
try {
|
|
3969
|
-
const settingsDir =
|
|
4209
|
+
const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
|
|
3970
4210
|
const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
|
|
3971
|
-
const projSettingsDir =
|
|
3972
|
-
const settingsPath =
|
|
4211
|
+
const projSettingsDir = path14.join(settingsDir, normalizedKey);
|
|
4212
|
+
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3973
4213
|
let settings = {};
|
|
3974
4214
|
try {
|
|
3975
|
-
settings = JSON.parse(
|
|
4215
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
3976
4216
|
} catch {
|
|
3977
4217
|
}
|
|
3978
4218
|
const perms = settings.permissions ?? {};
|
|
@@ -4000,21 +4240,24 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4000
4240
|
if (changed) {
|
|
4001
4241
|
perms.allow = allow;
|
|
4002
4242
|
settings.permissions = perms;
|
|
4003
|
-
|
|
4004
|
-
|
|
4243
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4244
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4005
4245
|
}
|
|
4006
4246
|
} catch {
|
|
4007
4247
|
}
|
|
4008
4248
|
const spawnCwd = opts?.cwd ?? projectDir2;
|
|
4009
4249
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
4010
|
-
const
|
|
4250
|
+
const agentRtConfig = getAgentRuntime(employeeName2);
|
|
4251
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
4252
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
4253
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
4011
4254
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
4012
4255
|
let identityFlag = "";
|
|
4013
4256
|
let behaviorsFlag = "";
|
|
4014
4257
|
let legacyFallbackWarned = false;
|
|
4015
4258
|
if (!useExeAgent && !useBinSymlink) {
|
|
4016
|
-
const identityPath =
|
|
4017
|
-
|
|
4259
|
+
const identityPath = path14.join(
|
|
4260
|
+
os7.homedir(),
|
|
4018
4261
|
".exe-os",
|
|
4019
4262
|
"identity",
|
|
4020
4263
|
`${employeeName2}.md`
|
|
@@ -4023,13 +4266,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4023
4266
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4024
4267
|
if (hasAgentFlag) {
|
|
4025
4268
|
identityFlag = ` --agent ${employeeName2}`;
|
|
4026
|
-
} else if (
|
|
4269
|
+
} else if (existsSync11(identityPath)) {
|
|
4027
4270
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4028
4271
|
legacyFallbackWarned = true;
|
|
4029
4272
|
}
|
|
4030
4273
|
const behaviorsFile = exportBehaviorsSync(
|
|
4031
4274
|
employeeName2,
|
|
4032
|
-
|
|
4275
|
+
path14.basename(spawnCwd),
|
|
4033
4276
|
sessionName
|
|
4034
4277
|
);
|
|
4035
4278
|
if (behaviorsFile) {
|
|
@@ -4044,16 +4287,16 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4044
4287
|
}
|
|
4045
4288
|
let sessionContextFlag = "";
|
|
4046
4289
|
try {
|
|
4047
|
-
const ctxDir =
|
|
4048
|
-
|
|
4049
|
-
const ctxFile =
|
|
4290
|
+
const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4291
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
4292
|
+
const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4050
4293
|
const ctxContent = [
|
|
4051
4294
|
`## Session Context`,
|
|
4052
4295
|
`You are running in tmux session: ${sessionName}.`,
|
|
4053
4296
|
`Your parent coordinator session is ${exeSession2}.`,
|
|
4054
4297
|
`Your employees (if any) use the -${exeSession2} suffix.`
|
|
4055
4298
|
].join("\n");
|
|
4056
|
-
|
|
4299
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
4057
4300
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4058
4301
|
} catch {
|
|
4059
4302
|
}
|
|
@@ -4067,9 +4310,48 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4067
4310
|
}
|
|
4068
4311
|
}
|
|
4069
4312
|
}
|
|
4313
|
+
if (useCodex) {
|
|
4314
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
4315
|
+
if (codexCfg?.apiKeyEnv) {
|
|
4316
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
4317
|
+
if (keyVal) {
|
|
4318
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
4322
|
+
}
|
|
4323
|
+
if (useOpencode) {
|
|
4324
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
4325
|
+
if (ocCfg?.apiKeyEnv) {
|
|
4326
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
4327
|
+
if (keyVal) {
|
|
4328
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4332
|
+
}
|
|
4333
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
4334
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
4335
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
4336
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4070
4339
|
let spawnCommand;
|
|
4071
4340
|
if (useExeAgent) {
|
|
4072
4341
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName2} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
4342
|
+
} else if (useCodex) {
|
|
4343
|
+
process.stderr.write(
|
|
4344
|
+
`[tmux-routing] agent-config: ${employeeName2} \u2192 codex (${agentRtConfig.model})
|
|
4345
|
+
`
|
|
4346
|
+
);
|
|
4347
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName2}${cleanupSuffix}`;
|
|
4348
|
+
} else if (useOpencode) {
|
|
4349
|
+
const binName = `${employeeName2}-opencode`;
|
|
4350
|
+
process.stderr.write(
|
|
4351
|
+
`[tmux-routing] agent-config: ${employeeName2} \u2192 opencode (${agentRtConfig.model})
|
|
4352
|
+
`
|
|
4353
|
+
);
|
|
4354
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4073
4355
|
} else if (useBinSymlink) {
|
|
4074
4356
|
const binName = `${employeeName2}-${ccProvider}`;
|
|
4075
4357
|
process.stderr.write(
|
|
@@ -4091,11 +4373,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4091
4373
|
transport.pipeLog(sessionName, logFile);
|
|
4092
4374
|
try {
|
|
4093
4375
|
const mySession = getMySession();
|
|
4094
|
-
const dispatchInfo =
|
|
4095
|
-
|
|
4376
|
+
const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4377
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4096
4378
|
dispatchedBy: mySession,
|
|
4097
4379
|
rootExe: exeSession2,
|
|
4098
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
4380
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
4381
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
4382
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
4099
4383
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4100
4384
|
}));
|
|
4101
4385
|
} catch {
|
|
@@ -4113,6 +4397,11 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4113
4397
|
booted = true;
|
|
4114
4398
|
break;
|
|
4115
4399
|
}
|
|
4400
|
+
} else if (useCodex) {
|
|
4401
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
4402
|
+
booted = true;
|
|
4403
|
+
break;
|
|
4404
|
+
}
|
|
4116
4405
|
} else {
|
|
4117
4406
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
4118
4407
|
booted = true;
|
|
@@ -4124,9 +4413,10 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4124
4413
|
}
|
|
4125
4414
|
if (!booted) {
|
|
4126
4415
|
releaseSpawnLock(sessionName);
|
|
4127
|
-
|
|
4416
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
4417
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
4128
4418
|
}
|
|
4129
|
-
if (!useExeAgent) {
|
|
4419
|
+
if (!useExeAgent && !useCodex) {
|
|
4130
4420
|
try {
|
|
4131
4421
|
transport.sendKeys(sessionName, `/exe-call ${employeeName2}`);
|
|
4132
4422
|
} catch {
|
|
@@ -4153,17 +4443,19 @@ var init_tmux_routing = __esm({
|
|
|
4153
4443
|
init_cc_agent_support();
|
|
4154
4444
|
init_mcp_prefix();
|
|
4155
4445
|
init_provider_table();
|
|
4446
|
+
init_agent_config();
|
|
4447
|
+
init_runtime_table();
|
|
4156
4448
|
init_intercom_queue();
|
|
4157
4449
|
init_plan_limits();
|
|
4158
4450
|
init_employees();
|
|
4159
|
-
SPAWN_LOCK_DIR =
|
|
4160
|
-
SESSION_CACHE =
|
|
4451
|
+
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4452
|
+
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4161
4453
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4162
4454
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4163
4455
|
VERIFY_PANE_LINES = 200;
|
|
4164
4456
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4165
|
-
INTERCOM_LOG2 =
|
|
4166
|
-
DEBOUNCE_FILE =
|
|
4457
|
+
INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4458
|
+
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4167
4459
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4168
4460
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4169
4461
|
}
|
|
@@ -4182,13 +4474,13 @@ __export(shard_manager_exports, {
|
|
|
4182
4474
|
listShards: () => listShards,
|
|
4183
4475
|
shardExists: () => shardExists
|
|
4184
4476
|
});
|
|
4185
|
-
import
|
|
4186
|
-
import { existsSync as
|
|
4477
|
+
import path16 from "path";
|
|
4478
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
|
|
4187
4479
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4188
4480
|
function initShardManager(encryptionKey) {
|
|
4189
4481
|
_encryptionKey = encryptionKey;
|
|
4190
|
-
if (!
|
|
4191
|
-
|
|
4482
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
4483
|
+
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
4192
4484
|
}
|
|
4193
4485
|
_shardingEnabled = true;
|
|
4194
4486
|
}
|
|
@@ -4208,7 +4500,7 @@ function getShardClient(projectName) {
|
|
|
4208
4500
|
}
|
|
4209
4501
|
const cached = _shards.get(safeName);
|
|
4210
4502
|
if (cached) return cached;
|
|
4211
|
-
const dbPath =
|
|
4503
|
+
const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
|
|
4212
4504
|
const client = createClient2({
|
|
4213
4505
|
url: `file:${dbPath}`,
|
|
4214
4506
|
encryptionKey: _encryptionKey
|
|
@@ -4218,10 +4510,10 @@ function getShardClient(projectName) {
|
|
|
4218
4510
|
}
|
|
4219
4511
|
function shardExists(projectName) {
|
|
4220
4512
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4221
|
-
return
|
|
4513
|
+
return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
|
|
4222
4514
|
}
|
|
4223
4515
|
function listShards() {
|
|
4224
|
-
if (!
|
|
4516
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
4225
4517
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4226
4518
|
}
|
|
4227
4519
|
async function ensureShardSchema(client) {
|
|
@@ -4407,7 +4699,7 @@ var init_shard_manager = __esm({
|
|
|
4407
4699
|
"src/lib/shard-manager.ts"() {
|
|
4408
4700
|
"use strict";
|
|
4409
4701
|
init_config();
|
|
4410
|
-
SHARDS_DIR =
|
|
4702
|
+
SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
|
|
4411
4703
|
_shards = /* @__PURE__ */ new Map();
|
|
4412
4704
|
_encryptionKey = null;
|
|
4413
4705
|
_shardingEnabled = false;
|
|
@@ -4600,20 +4892,21 @@ init_tmux_routing();
|
|
|
4600
4892
|
init_tasks_crud();
|
|
4601
4893
|
|
|
4602
4894
|
// src/lib/store.ts
|
|
4895
|
+
import { createHash } from "crypto";
|
|
4603
4896
|
init_database();
|
|
4604
4897
|
|
|
4605
4898
|
// src/lib/keychain.ts
|
|
4606
4899
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4607
|
-
import { existsSync as
|
|
4608
|
-
import
|
|
4609
|
-
import
|
|
4900
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4901
|
+
import path15 from "path";
|
|
4902
|
+
import os8 from "os";
|
|
4610
4903
|
var SERVICE = "exe-mem";
|
|
4611
4904
|
var ACCOUNT = "master-key";
|
|
4612
4905
|
function getKeyDir() {
|
|
4613
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4906
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
|
|
4614
4907
|
}
|
|
4615
4908
|
function getKeyPath() {
|
|
4616
|
-
return
|
|
4909
|
+
return path15.join(getKeyDir(), "master.key");
|
|
4617
4910
|
}
|
|
4618
4911
|
async function tryKeytar() {
|
|
4619
4912
|
try {
|
|
@@ -4634,13 +4927,21 @@ async function getMasterKey() {
|
|
|
4634
4927
|
}
|
|
4635
4928
|
}
|
|
4636
4929
|
const keyPath = getKeyPath();
|
|
4637
|
-
if (!
|
|
4930
|
+
if (!existsSync12(keyPath)) {
|
|
4931
|
+
process.stderr.write(
|
|
4932
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4933
|
+
`
|
|
4934
|
+
);
|
|
4638
4935
|
return null;
|
|
4639
4936
|
}
|
|
4640
4937
|
try {
|
|
4641
4938
|
const content = await readFile4(keyPath, "utf-8");
|
|
4642
4939
|
return Buffer.from(content.trim(), "base64");
|
|
4643
|
-
} catch {
|
|
4940
|
+
} catch (err) {
|
|
4941
|
+
process.stderr.write(
|
|
4942
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4943
|
+
`
|
|
4944
|
+
);
|
|
4644
4945
|
return null;
|
|
4645
4946
|
}
|
|
4646
4947
|
}
|