@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/git-sweep.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 } 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(fileURLToPath(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 args2 = [];
|
|
975
|
+
if (Array.isArray(stmt.args)) {
|
|
976
|
+
args2 = 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: args2 };
|
|
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: args2 } = normalizeStatement(stmt);
|
|
994
|
+
const response = await sendDaemonRequest({
|
|
995
|
+
type: "db-execute",
|
|
996
|
+
sql,
|
|
997
|
+
args: args2
|
|
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) {
|
|
@@ -1777,10 +2368,11 @@ __export(tasks_crud_exports, {
|
|
|
1777
2368
|
writeCheckpoint: () => writeCheckpoint
|
|
1778
2369
|
});
|
|
1779
2370
|
import crypto3 from "crypto";
|
|
1780
|
-
import
|
|
2371
|
+
import path10 from "path";
|
|
2372
|
+
import os6 from "os";
|
|
1781
2373
|
import { execSync as execSync4 } from "child_process";
|
|
1782
2374
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1783
|
-
import { existsSync as
|
|
2375
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
1784
2376
|
async function writeCheckpoint(input) {
|
|
1785
2377
|
const client = getClient();
|
|
1786
2378
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1821,6 +2413,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1821
2413
|
function slugify(title) {
|
|
1822
2414
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1823
2415
|
}
|
|
2416
|
+
function buildKeywordIndex() {
|
|
2417
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2418
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2419
|
+
for (const kw of keywords) {
|
|
2420
|
+
const existing = idx.get(kw) ?? [];
|
|
2421
|
+
existing.push(role);
|
|
2422
|
+
idx.set(kw, existing);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return idx;
|
|
2426
|
+
}
|
|
2427
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2428
|
+
const employees = loadEmployeesSync();
|
|
2429
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2430
|
+
if (!employee) return void 0;
|
|
2431
|
+
const assigneeRole = employee.role;
|
|
2432
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2433
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2434
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2435
|
+
if (text.includes(keyword)) {
|
|
2436
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2440
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2441
|
+
if (assigneeRole === "COO") return void 0;
|
|
2442
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2443
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2444
|
+
}
|
|
1824
2445
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1825
2446
|
const scope = sessionScopeFilter(scopeSession);
|
|
1826
2447
|
let result = await client.execute({
|
|
@@ -1870,7 +2491,14 @@ async function createTaskCore(input) {
|
|
|
1870
2491
|
const id = crypto3.randomUUID();
|
|
1871
2492
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1872
2493
|
const slug = slugify(input.title);
|
|
1873
|
-
|
|
2494
|
+
let earlySessionScope = null;
|
|
2495
|
+
try {
|
|
2496
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2497
|
+
earlySessionScope = resolveExeSession2();
|
|
2498
|
+
} catch {
|
|
2499
|
+
}
|
|
2500
|
+
const scope = earlySessionScope ?? "default";
|
|
2501
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1874
2502
|
let blockedById = null;
|
|
1875
2503
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1876
2504
|
if (input.blockedBy) {
|
|
@@ -1910,22 +2538,24 @@ async function createTaskCore(input) {
|
|
|
1910
2538
|
if (dupCheck.rows.length > 0) {
|
|
1911
2539
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1912
2540
|
}
|
|
2541
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2542
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2543
|
+
if (laneWarning) {
|
|
2544
|
+
warning = warning ? `${warning}
|
|
2545
|
+
${laneWarning}` : laneWarning;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
1913
2548
|
if (input.baseDir) {
|
|
1914
2549
|
try {
|
|
1915
|
-
await mkdir3(
|
|
1916
|
-
await mkdir3(
|
|
2550
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2551
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1917
2552
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1918
2553
|
await ensureGitignoreExe(input.baseDir);
|
|
1919
2554
|
} catch {
|
|
1920
2555
|
}
|
|
1921
2556
|
}
|
|
1922
2557
|
const complexity = input.complexity ?? "standard";
|
|
1923
|
-
|
|
1924
|
-
try {
|
|
1925
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1926
|
-
sessionScope = resolveExeSession2();
|
|
1927
|
-
} catch {
|
|
1928
|
-
}
|
|
2558
|
+
const sessionScope = earlySessionScope;
|
|
1929
2559
|
await client.execute({
|
|
1930
2560
|
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)
|
|
1931
2561
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1952,6 +2582,43 @@ async function createTaskCore(input) {
|
|
|
1952
2582
|
now
|
|
1953
2583
|
]
|
|
1954
2584
|
});
|
|
2585
|
+
if (input.baseDir) {
|
|
2586
|
+
try {
|
|
2587
|
+
const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
|
|
2588
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2589
|
+
const mdDir = path10.dirname(mdPath);
|
|
2590
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2591
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2592
|
+
const mdContent = `# ${input.title}
|
|
2593
|
+
|
|
2594
|
+
**ID:** ${id}
|
|
2595
|
+
**Status:** ${initialStatus}
|
|
2596
|
+
**Priority:** ${input.priority}
|
|
2597
|
+
**Assigned by:** ${input.assignedBy}
|
|
2598
|
+
**Assigned to:** ${input.assignedTo}
|
|
2599
|
+
**Project:** ${input.projectName}
|
|
2600
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2601
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2602
|
+
**Reviewer:** ${reviewer}
|
|
2603
|
+
|
|
2604
|
+
## Context
|
|
2605
|
+
|
|
2606
|
+
${input.context}
|
|
2607
|
+
|
|
2608
|
+
## MANDATORY: When done
|
|
2609
|
+
|
|
2610
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2611
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2612
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2613
|
+
`;
|
|
2614
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2615
|
+
} catch (err) {
|
|
2616
|
+
process.stderr.write(
|
|
2617
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2618
|
+
`
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
1955
2622
|
return {
|
|
1956
2623
|
id,
|
|
1957
2624
|
title: input.title,
|
|
@@ -2144,7 +2811,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2144
2811
|
return { row, taskFile, now, taskId };
|
|
2145
2812
|
}
|
|
2146
2813
|
}
|
|
2147
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2814
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2148
2815
|
process.stderr.write(
|
|
2149
2816
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2150
2817
|
`
|
|
@@ -2209,9 +2876,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2209
2876
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2210
2877
|
}
|
|
2211
2878
|
async function ensureArchitectureDoc(baseDir, projectName2) {
|
|
2212
|
-
const archPath =
|
|
2879
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2213
2880
|
try {
|
|
2214
|
-
if (
|
|
2881
|
+
if (existsSync10(archPath)) return;
|
|
2215
2882
|
const template = [
|
|
2216
2883
|
`# ${projectName2} \u2014 System Architecture`,
|
|
2217
2884
|
"",
|
|
@@ -2244,10 +2911,10 @@ async function ensureArchitectureDoc(baseDir, projectName2) {
|
|
|
2244
2911
|
}
|
|
2245
2912
|
}
|
|
2246
2913
|
async function ensureGitignoreExe(baseDir) {
|
|
2247
|
-
const gitignorePath =
|
|
2914
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2248
2915
|
try {
|
|
2249
|
-
if (
|
|
2250
|
-
const content =
|
|
2916
|
+
if (existsSync10(gitignorePath)) {
|
|
2917
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2251
2918
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2252
2919
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2253
2920
|
} else {
|
|
@@ -2256,20 +2923,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2256
2923
|
} catch {
|
|
2257
2924
|
}
|
|
2258
2925
|
}
|
|
2259
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2926
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2260
2927
|
var init_tasks_crud = __esm({
|
|
2261
2928
|
"src/lib/tasks-crud.ts"() {
|
|
2262
2929
|
"use strict";
|
|
2263
2930
|
init_database();
|
|
2264
2931
|
init_task_scope();
|
|
2932
|
+
init_employees();
|
|
2933
|
+
LANE_KEYWORDS = {
|
|
2934
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2935
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2936
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2937
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2938
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2939
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2940
|
+
};
|
|
2941
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2265
2942
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2266
2943
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2267
2944
|
}
|
|
2268
2945
|
});
|
|
2269
2946
|
|
|
2270
2947
|
// src/lib/tasks-review.ts
|
|
2271
|
-
import
|
|
2272
|
-
import { existsSync as
|
|
2948
|
+
import path11 from "path";
|
|
2949
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2273
2950
|
async function countPendingReviews(sessionScope) {
|
|
2274
2951
|
const client = getClient();
|
|
2275
2952
|
if (sessionScope) {
|
|
@@ -2291,7 +2968,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2291
2968
|
const result2 = await client.execute({
|
|
2292
2969
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2293
2970
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2294
|
-
AND
|
|
2971
|
+
AND session_scope = ?`,
|
|
2295
2972
|
args: [sinceIso, sessionScope]
|
|
2296
2973
|
});
|
|
2297
2974
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2309,7 +2986,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2309
2986
|
const result2 = await client.execute({
|
|
2310
2987
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2311
2988
|
WHERE status = 'needs_review'
|
|
2312
|
-
AND
|
|
2989
|
+
AND session_scope = ?
|
|
2313
2990
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2314
2991
|
args: [sessionScope, limit]
|
|
2315
2992
|
});
|
|
@@ -2430,14 +3107,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2430
3107
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2431
3108
|
const agent = parts[1];
|
|
2432
3109
|
const slug = parts.slice(2).join("-");
|
|
2433
|
-
const
|
|
3110
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2434
3111
|
const result = await client.execute({
|
|
2435
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2436
|
-
args: [now,
|
|
3112
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3113
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2437
3114
|
});
|
|
2438
3115
|
if (result.rowsAffected > 0) {
|
|
2439
3116
|
process.stderr.write(
|
|
2440
|
-
`[review-cleanup] Cascaded original task to done
|
|
3117
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2441
3118
|
`
|
|
2442
3119
|
);
|
|
2443
3120
|
}
|
|
@@ -2450,11 +3127,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2450
3127
|
);
|
|
2451
3128
|
}
|
|
2452
3129
|
try {
|
|
2453
|
-
const cacheDir =
|
|
2454
|
-
if (
|
|
3130
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3131
|
+
if (existsSync11(cacheDir)) {
|
|
2455
3132
|
for (const f of readdirSync2(cacheDir)) {
|
|
2456
3133
|
if (f.startsWith("review-notified-")) {
|
|
2457
|
-
|
|
3134
|
+
unlinkSync4(path11.join(cacheDir, f));
|
|
2458
3135
|
}
|
|
2459
3136
|
}
|
|
2460
3137
|
}
|
|
@@ -2475,7 +3152,7 @@ var init_tasks_review = __esm({
|
|
|
2475
3152
|
});
|
|
2476
3153
|
|
|
2477
3154
|
// src/lib/tasks-chain.ts
|
|
2478
|
-
import
|
|
3155
|
+
import path12 from "path";
|
|
2479
3156
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2480
3157
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2481
3158
|
const client = getClient();
|
|
@@ -2492,7 +3169,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2492
3169
|
});
|
|
2493
3170
|
for (const ur of unblockedRows.rows) {
|
|
2494
3171
|
try {
|
|
2495
|
-
const ubFile =
|
|
3172
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2496
3173
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2497
3174
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2498
3175
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2561,7 +3238,7 @@ var init_tasks_chain = __esm({
|
|
|
2561
3238
|
|
|
2562
3239
|
// src/lib/project-name.ts
|
|
2563
3240
|
import { execSync as execSync5 } from "child_process";
|
|
2564
|
-
import
|
|
3241
|
+
import path13 from "path";
|
|
2565
3242
|
function getProjectName(cwd) {
|
|
2566
3243
|
const dir = cwd ?? process.cwd();
|
|
2567
3244
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2574,7 +3251,7 @@ function getProjectName(cwd) {
|
|
|
2574
3251
|
timeout: 2e3,
|
|
2575
3252
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2576
3253
|
}).trim();
|
|
2577
|
-
repoRoot =
|
|
3254
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2578
3255
|
} catch {
|
|
2579
3256
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2580
3257
|
cwd: dir,
|
|
@@ -2583,11 +3260,11 @@ function getProjectName(cwd) {
|
|
|
2583
3260
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2584
3261
|
}).trim();
|
|
2585
3262
|
}
|
|
2586
|
-
_cached2 =
|
|
3263
|
+
_cached2 = path13.basename(repoRoot);
|
|
2587
3264
|
_cachedCwd = dir;
|
|
2588
3265
|
return _cached2;
|
|
2589
3266
|
} catch {
|
|
2590
|
-
_cached2 =
|
|
3267
|
+
_cached2 = path13.basename(dir);
|
|
2591
3268
|
_cachedCwd = dir;
|
|
2592
3269
|
return _cached2;
|
|
2593
3270
|
}
|
|
@@ -2619,7 +3296,7 @@ function findSessionForProject(projectName2) {
|
|
|
2619
3296
|
const sessions = listSessions();
|
|
2620
3297
|
for (const s of sessions) {
|
|
2621
3298
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2622
|
-
if (proj === projectName2 &&
|
|
3299
|
+
if (proj === projectName2 && isCoordinatorName(s.agentId)) return s;
|
|
2623
3300
|
}
|
|
2624
3301
|
return null;
|
|
2625
3302
|
}
|
|
@@ -2665,7 +3342,7 @@ var init_session_scope = __esm({
|
|
|
2665
3342
|
|
|
2666
3343
|
// src/lib/tasks-notify.ts
|
|
2667
3344
|
async function dispatchTaskToEmployee(input) {
|
|
2668
|
-
if (
|
|
3345
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2669
3346
|
let crossProject = false;
|
|
2670
3347
|
if (input.projectName) {
|
|
2671
3348
|
try {
|
|
@@ -3060,8 +3737,8 @@ __export(tasks_exports, {
|
|
|
3060
3737
|
updateTaskStatus: () => updateTaskStatus,
|
|
3061
3738
|
writeCheckpoint: () => writeCheckpoint
|
|
3062
3739
|
});
|
|
3063
|
-
import
|
|
3064
|
-
import { writeFileSync as
|
|
3740
|
+
import path14 from "path";
|
|
3741
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3065
3742
|
async function createTask(input) {
|
|
3066
3743
|
const result = await createTaskCore(input);
|
|
3067
3744
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3080,14 +3757,14 @@ async function updateTask(input) {
|
|
|
3080
3757
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3081
3758
|
try {
|
|
3082
3759
|
const agent = String(row.assigned_to);
|
|
3083
|
-
const cacheDir =
|
|
3084
|
-
const cachePath =
|
|
3760
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
3761
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3085
3762
|
if (input.status === "in_progress") {
|
|
3086
|
-
|
|
3087
|
-
|
|
3763
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
3764
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3088
3765
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3089
3766
|
try {
|
|
3090
|
-
|
|
3767
|
+
unlinkSync5(cachePath);
|
|
3091
3768
|
} catch {
|
|
3092
3769
|
}
|
|
3093
3770
|
}
|
|
@@ -3144,7 +3821,7 @@ async function updateTask(input) {
|
|
|
3144
3821
|
}
|
|
3145
3822
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3146
3823
|
if (isTerminal) {
|
|
3147
|
-
const isCoordinator =
|
|
3824
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3148
3825
|
if (!isCoordinator) {
|
|
3149
3826
|
notifyTaskDone();
|
|
3150
3827
|
}
|
|
@@ -3169,7 +3846,7 @@ async function updateTask(input) {
|
|
|
3169
3846
|
}
|
|
3170
3847
|
}
|
|
3171
3848
|
}
|
|
3172
|
-
if (input.status === "done" &&
|
|
3849
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3173
3850
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3174
3851
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3175
3852
|
taskId,
|
|
@@ -3185,7 +3862,7 @@ async function updateTask(input) {
|
|
|
3185
3862
|
});
|
|
3186
3863
|
}
|
|
3187
3864
|
let nextTask;
|
|
3188
|
-
if (isTerminal &&
|
|
3865
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3189
3866
|
try {
|
|
3190
3867
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3191
3868
|
} catch {
|
|
@@ -3529,7 +4206,7 @@ var init_capacity_monitor = __esm({
|
|
|
3529
4206
|
// src/lib/tmux-routing.ts
|
|
3530
4207
|
var tmux_routing_exports = {};
|
|
3531
4208
|
__export(tmux_routing_exports, {
|
|
3532
|
-
acquireSpawnLock: () =>
|
|
4209
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3533
4210
|
employeeSessionName: () => employeeSessionName,
|
|
3534
4211
|
ensureEmployee: () => ensureEmployee,
|
|
3535
4212
|
extractRootExe: () => extractRootExe,
|
|
@@ -3544,20 +4221,20 @@ __export(tmux_routing_exports, {
|
|
|
3544
4221
|
notifyParentExe: () => notifyParentExe,
|
|
3545
4222
|
parseParentExe: () => parseParentExe,
|
|
3546
4223
|
registerParentExe: () => registerParentExe,
|
|
3547
|
-
releaseSpawnLock: () =>
|
|
4224
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3548
4225
|
resolveExeSession: () => resolveExeSession,
|
|
3549
4226
|
sendIntercom: () => sendIntercom,
|
|
3550
4227
|
spawnEmployee: () => spawnEmployee,
|
|
3551
4228
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3552
4229
|
});
|
|
3553
4230
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3554
|
-
import { readFileSync as
|
|
3555
|
-
import
|
|
3556
|
-
import
|
|
3557
|
-
import { fileURLToPath } from "url";
|
|
3558
|
-
import { unlinkSync as
|
|
4231
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
|
|
4232
|
+
import path15 from "path";
|
|
4233
|
+
import os7 from "os";
|
|
4234
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4235
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3559
4236
|
function spawnLockPath(sessionName) {
|
|
3560
|
-
return
|
|
4237
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3561
4238
|
}
|
|
3562
4239
|
function isProcessAlive(pid) {
|
|
3563
4240
|
try {
|
|
@@ -3567,14 +4244,14 @@ function isProcessAlive(pid) {
|
|
|
3567
4244
|
return false;
|
|
3568
4245
|
}
|
|
3569
4246
|
}
|
|
3570
|
-
function
|
|
3571
|
-
if (!
|
|
3572
|
-
|
|
4247
|
+
function acquireSpawnLock2(sessionName) {
|
|
4248
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4249
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3573
4250
|
}
|
|
3574
4251
|
const lockFile = spawnLockPath(sessionName);
|
|
3575
|
-
if (
|
|
4252
|
+
if (existsSync12(lockFile)) {
|
|
3576
4253
|
try {
|
|
3577
|
-
const lock = JSON.parse(
|
|
4254
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
3578
4255
|
const age = Date.now() - lock.timestamp;
|
|
3579
4256
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3580
4257
|
return false;
|
|
@@ -3582,25 +4259,25 @@ function acquireSpawnLock(sessionName) {
|
|
|
3582
4259
|
} catch {
|
|
3583
4260
|
}
|
|
3584
4261
|
}
|
|
3585
|
-
|
|
4262
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3586
4263
|
return true;
|
|
3587
4264
|
}
|
|
3588
|
-
function
|
|
4265
|
+
function releaseSpawnLock2(sessionName) {
|
|
3589
4266
|
try {
|
|
3590
|
-
|
|
4267
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3591
4268
|
} catch {
|
|
3592
4269
|
}
|
|
3593
4270
|
}
|
|
3594
4271
|
function resolveBehaviorsExporterScript() {
|
|
3595
4272
|
try {
|
|
3596
|
-
const thisFile =
|
|
3597
|
-
const scriptPath =
|
|
3598
|
-
|
|
4273
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4274
|
+
const scriptPath = path15.join(
|
|
4275
|
+
path15.dirname(thisFile),
|
|
3599
4276
|
"..",
|
|
3600
4277
|
"bin",
|
|
3601
4278
|
"exe-export-behaviors.js"
|
|
3602
4279
|
);
|
|
3603
|
-
return
|
|
4280
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
3604
4281
|
} catch {
|
|
3605
4282
|
return null;
|
|
3606
4283
|
}
|
|
@@ -3666,12 +4343,12 @@ function extractRootExe(name) {
|
|
|
3666
4343
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3667
4344
|
}
|
|
3668
4345
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3669
|
-
if (!
|
|
3670
|
-
|
|
4346
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4347
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3671
4348
|
}
|
|
3672
4349
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3673
|
-
const filePath =
|
|
3674
|
-
|
|
4350
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4351
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3675
4352
|
parentExe: rootExe,
|
|
3676
4353
|
dispatchedBy: dispatchedBy || rootExe,
|
|
3677
4354
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3679,7 +4356,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3679
4356
|
}
|
|
3680
4357
|
function getParentExe(sessionKey) {
|
|
3681
4358
|
try {
|
|
3682
|
-
const data = JSON.parse(
|
|
4359
|
+
const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3683
4360
|
return data.parentExe || null;
|
|
3684
4361
|
} catch {
|
|
3685
4362
|
return null;
|
|
@@ -3687,8 +4364,8 @@ function getParentExe(sessionKey) {
|
|
|
3687
4364
|
}
|
|
3688
4365
|
function getDispatchedBy(sessionKey) {
|
|
3689
4366
|
try {
|
|
3690
|
-
const data = JSON.parse(
|
|
3691
|
-
|
|
4367
|
+
const data = JSON.parse(readFileSync11(
|
|
4368
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3692
4369
|
"utf8"
|
|
3693
4370
|
));
|
|
3694
4371
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3714,10 +4391,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3714
4391
|
}
|
|
3715
4392
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3716
4393
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3717
|
-
if (!isAlive(base) &&
|
|
4394
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3718
4395
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3719
4396
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3720
|
-
if (!isAlive(candidate) &&
|
|
4397
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3721
4398
|
}
|
|
3722
4399
|
return null;
|
|
3723
4400
|
}
|
|
@@ -3749,32 +4426,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3749
4426
|
}
|
|
3750
4427
|
function readDebounceState() {
|
|
3751
4428
|
try {
|
|
3752
|
-
if (!
|
|
3753
|
-
|
|
4429
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4430
|
+
const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
4431
|
+
const state = {};
|
|
4432
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
4433
|
+
if (typeof val === "number") {
|
|
4434
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
4435
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
4436
|
+
state[key] = val;
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
return state;
|
|
3754
4440
|
} catch {
|
|
3755
4441
|
return {};
|
|
3756
4442
|
}
|
|
3757
4443
|
}
|
|
3758
4444
|
function writeDebounceState(state) {
|
|
3759
4445
|
try {
|
|
3760
|
-
if (!
|
|
3761
|
-
|
|
4446
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4447
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3762
4448
|
} catch {
|
|
3763
4449
|
}
|
|
3764
4450
|
}
|
|
3765
4451
|
function isDebounced(targetSession) {
|
|
3766
4452
|
const state = readDebounceState();
|
|
3767
|
-
const
|
|
3768
|
-
|
|
4453
|
+
const entry = state[targetSession];
|
|
4454
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
4455
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
4456
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
4457
|
+
state[targetSession].pending++;
|
|
4458
|
+
writeDebounceState(state);
|
|
4459
|
+
return true;
|
|
4460
|
+
}
|
|
4461
|
+
return false;
|
|
3769
4462
|
}
|
|
3770
4463
|
function recordDebounce(targetSession) {
|
|
3771
4464
|
const state = readDebounceState();
|
|
3772
|
-
state[targetSession]
|
|
4465
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
4466
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
3773
4467
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
3774
4468
|
for (const key of Object.keys(state)) {
|
|
3775
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4469
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
3776
4470
|
}
|
|
3777
4471
|
writeDebounceState(state);
|
|
4472
|
+
return batched;
|
|
3778
4473
|
}
|
|
3779
4474
|
function logIntercom(msg) {
|
|
3780
4475
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -3819,7 +4514,7 @@ function sendIntercom(targetSession) {
|
|
|
3819
4514
|
return "skipped_exe";
|
|
3820
4515
|
}
|
|
3821
4516
|
if (isDebounced(targetSession)) {
|
|
3822
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
4517
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
3823
4518
|
return "debounced";
|
|
3824
4519
|
}
|
|
3825
4520
|
try {
|
|
@@ -3831,14 +4526,14 @@ function sendIntercom(targetSession) {
|
|
|
3831
4526
|
const sessionState = getSessionState(targetSession);
|
|
3832
4527
|
if (sessionState === "no_claude") {
|
|
3833
4528
|
queueIntercom(targetSession, "claude not running in session");
|
|
3834
|
-
recordDebounce(targetSession);
|
|
3835
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
4529
|
+
const batched2 = recordDebounce(targetSession);
|
|
4530
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3836
4531
|
return "queued";
|
|
3837
4532
|
}
|
|
3838
4533
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3839
4534
|
queueIntercom(targetSession, "session busy at send time");
|
|
3840
|
-
recordDebounce(targetSession);
|
|
3841
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
4535
|
+
const batched2 = recordDebounce(targetSession);
|
|
4536
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3842
4537
|
return "queued";
|
|
3843
4538
|
}
|
|
3844
4539
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -3846,8 +4541,8 @@ function sendIntercom(targetSession) {
|
|
|
3846
4541
|
transport.sendKeys(targetSession, "q");
|
|
3847
4542
|
}
|
|
3848
4543
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3849
|
-
recordDebounce(targetSession);
|
|
3850
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4544
|
+
const batched = recordDebounce(targetSession);
|
|
4545
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
3851
4546
|
return "delivered";
|
|
3852
4547
|
} catch {
|
|
3853
4548
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3877,7 +4572,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3877
4572
|
return true;
|
|
3878
4573
|
}
|
|
3879
4574
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3880
|
-
if (
|
|
4575
|
+
if (isCoordinatorName(employeeName)) {
|
|
3881
4576
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3882
4577
|
}
|
|
3883
4578
|
try {
|
|
@@ -3949,26 +4644,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3949
4644
|
const transport = getTransport();
|
|
3950
4645
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3951
4646
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3952
|
-
const logDir =
|
|
3953
|
-
const logFile =
|
|
3954
|
-
if (!
|
|
3955
|
-
|
|
4647
|
+
const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4648
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4649
|
+
if (!existsSync12(logDir)) {
|
|
4650
|
+
mkdirSync6(logDir, { recursive: true });
|
|
3956
4651
|
}
|
|
3957
4652
|
transport.kill(sessionName);
|
|
3958
4653
|
let cleanupSuffix = "";
|
|
3959
4654
|
try {
|
|
3960
|
-
const thisFile =
|
|
3961
|
-
const cleanupScript =
|
|
3962
|
-
if (
|
|
4655
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4656
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4657
|
+
if (existsSync12(cleanupScript)) {
|
|
3963
4658
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3964
4659
|
}
|
|
3965
4660
|
} catch {
|
|
3966
4661
|
}
|
|
3967
4662
|
try {
|
|
3968
|
-
const claudeJsonPath =
|
|
4663
|
+
const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
|
|
3969
4664
|
let claudeJson = {};
|
|
3970
4665
|
try {
|
|
3971
|
-
claudeJson = JSON.parse(
|
|
4666
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
3972
4667
|
} catch {
|
|
3973
4668
|
}
|
|
3974
4669
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3976,17 +4671,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3976
4671
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3977
4672
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3978
4673
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3979
|
-
|
|
4674
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3980
4675
|
} catch {
|
|
3981
4676
|
}
|
|
3982
4677
|
try {
|
|
3983
|
-
const settingsDir =
|
|
4678
|
+
const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
|
|
3984
4679
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3985
|
-
const projSettingsDir =
|
|
3986
|
-
const settingsPath =
|
|
4680
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
4681
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
3987
4682
|
let settings = {};
|
|
3988
4683
|
try {
|
|
3989
|
-
settings = JSON.parse(
|
|
4684
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
3990
4685
|
} catch {
|
|
3991
4686
|
}
|
|
3992
4687
|
const perms = settings.permissions ?? {};
|
|
@@ -4014,21 +4709,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4014
4709
|
if (changed) {
|
|
4015
4710
|
perms.allow = allow;
|
|
4016
4711
|
settings.permissions = perms;
|
|
4017
|
-
|
|
4018
|
-
|
|
4712
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4713
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4019
4714
|
}
|
|
4020
4715
|
} catch {
|
|
4021
4716
|
}
|
|
4022
4717
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
4023
4718
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
4024
|
-
const
|
|
4719
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
4720
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
4721
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
4722
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
4025
4723
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
4026
4724
|
let identityFlag = "";
|
|
4027
4725
|
let behaviorsFlag = "";
|
|
4028
4726
|
let legacyFallbackWarned = false;
|
|
4029
4727
|
if (!useExeAgent && !useBinSymlink) {
|
|
4030
|
-
const identityPath =
|
|
4031
|
-
|
|
4728
|
+
const identityPath = path15.join(
|
|
4729
|
+
os7.homedir(),
|
|
4032
4730
|
".exe-os",
|
|
4033
4731
|
"identity",
|
|
4034
4732
|
`${employeeName}.md`
|
|
@@ -4037,13 +4735,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4037
4735
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4038
4736
|
if (hasAgentFlag) {
|
|
4039
4737
|
identityFlag = ` --agent ${employeeName}`;
|
|
4040
|
-
} else if (
|
|
4738
|
+
} else if (existsSync12(identityPath)) {
|
|
4041
4739
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4042
4740
|
legacyFallbackWarned = true;
|
|
4043
4741
|
}
|
|
4044
4742
|
const behaviorsFile = exportBehaviorsSync(
|
|
4045
4743
|
employeeName,
|
|
4046
|
-
|
|
4744
|
+
path15.basename(spawnCwd),
|
|
4047
4745
|
sessionName
|
|
4048
4746
|
);
|
|
4049
4747
|
if (behaviorsFile) {
|
|
@@ -4058,16 +4756,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4058
4756
|
}
|
|
4059
4757
|
let sessionContextFlag = "";
|
|
4060
4758
|
try {
|
|
4061
|
-
const ctxDir =
|
|
4062
|
-
|
|
4063
|
-
const ctxFile =
|
|
4759
|
+
const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4760
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
4761
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4064
4762
|
const ctxContent = [
|
|
4065
4763
|
`## Session Context`,
|
|
4066
4764
|
`You are running in tmux session: ${sessionName}.`,
|
|
4067
4765
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4068
4766
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4069
4767
|
].join("\n");
|
|
4070
|
-
|
|
4768
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
4071
4769
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4072
4770
|
} catch {
|
|
4073
4771
|
}
|
|
@@ -4081,9 +4779,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4081
4779
|
}
|
|
4082
4780
|
}
|
|
4083
4781
|
}
|
|
4782
|
+
if (useCodex) {
|
|
4783
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
4784
|
+
if (codexCfg?.apiKeyEnv) {
|
|
4785
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
4786
|
+
if (keyVal) {
|
|
4787
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
4791
|
+
}
|
|
4792
|
+
if (useOpencode) {
|
|
4793
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
4794
|
+
if (ocCfg?.apiKeyEnv) {
|
|
4795
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
4796
|
+
if (keyVal) {
|
|
4797
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4801
|
+
}
|
|
4802
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
4803
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
4804
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
4805
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4084
4808
|
let spawnCommand;
|
|
4085
4809
|
if (useExeAgent) {
|
|
4086
4810
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
4811
|
+
} else if (useCodex) {
|
|
4812
|
+
process.stderr.write(
|
|
4813
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
4814
|
+
`
|
|
4815
|
+
);
|
|
4816
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
4817
|
+
} else if (useOpencode) {
|
|
4818
|
+
const binName = `${employeeName}-opencode`;
|
|
4819
|
+
process.stderr.write(
|
|
4820
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
4821
|
+
`
|
|
4822
|
+
);
|
|
4823
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4087
4824
|
} else if (useBinSymlink) {
|
|
4088
4825
|
const binName = `${employeeName}-${ccProvider}`;
|
|
4089
4826
|
process.stderr.write(
|
|
@@ -4099,17 +4836,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4099
4836
|
command: spawnCommand
|
|
4100
4837
|
});
|
|
4101
4838
|
if (spawnResult.error) {
|
|
4102
|
-
|
|
4839
|
+
releaseSpawnLock2(sessionName);
|
|
4103
4840
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4104
4841
|
}
|
|
4105
4842
|
transport.pipeLog(sessionName, logFile);
|
|
4106
4843
|
try {
|
|
4107
4844
|
const mySession = getMySession();
|
|
4108
|
-
const dispatchInfo =
|
|
4109
|
-
|
|
4845
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4846
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4110
4847
|
dispatchedBy: mySession,
|
|
4111
4848
|
rootExe: exeSession,
|
|
4112
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
4849
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
4850
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
4851
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
4113
4852
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4114
4853
|
}));
|
|
4115
4854
|
} catch {
|
|
@@ -4127,6 +4866,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4127
4866
|
booted = true;
|
|
4128
4867
|
break;
|
|
4129
4868
|
}
|
|
4869
|
+
} else if (useCodex) {
|
|
4870
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
4871
|
+
booted = true;
|
|
4872
|
+
break;
|
|
4873
|
+
}
|
|
4130
4874
|
} else {
|
|
4131
4875
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
4132
4876
|
booted = true;
|
|
@@ -4137,10 +4881,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4137
4881
|
}
|
|
4138
4882
|
}
|
|
4139
4883
|
if (!booted) {
|
|
4140
|
-
|
|
4141
|
-
|
|
4884
|
+
releaseSpawnLock2(sessionName);
|
|
4885
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
4886
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
4142
4887
|
}
|
|
4143
|
-
if (!useExeAgent) {
|
|
4888
|
+
if (!useExeAgent && !useCodex) {
|
|
4144
4889
|
try {
|
|
4145
4890
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
4146
4891
|
} catch {
|
|
@@ -4154,7 +4899,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4154
4899
|
pid: 0,
|
|
4155
4900
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4156
4901
|
});
|
|
4157
|
-
|
|
4902
|
+
releaseSpawnLock2(sessionName);
|
|
4158
4903
|
return { sessionName };
|
|
4159
4904
|
}
|
|
4160
4905
|
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;
|
|
@@ -4167,17 +4912,19 @@ var init_tmux_routing = __esm({
|
|
|
4167
4912
|
init_cc_agent_support();
|
|
4168
4913
|
init_mcp_prefix();
|
|
4169
4914
|
init_provider_table();
|
|
4915
|
+
init_agent_config();
|
|
4916
|
+
init_runtime_table();
|
|
4170
4917
|
init_intercom_queue();
|
|
4171
4918
|
init_plan_limits();
|
|
4172
4919
|
init_employees();
|
|
4173
|
-
SPAWN_LOCK_DIR =
|
|
4174
|
-
SESSION_CACHE =
|
|
4920
|
+
SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4921
|
+
SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4175
4922
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4176
4923
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4177
4924
|
VERIFY_PANE_LINES = 200;
|
|
4178
4925
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4179
|
-
INTERCOM_LOG2 =
|
|
4180
|
-
DEBOUNCE_FILE =
|
|
4926
|
+
INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4927
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4181
4928
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4182
4929
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4183
4930
|
}
|
|
@@ -4218,14 +4965,14 @@ var init_memory = __esm({
|
|
|
4218
4965
|
|
|
4219
4966
|
// src/lib/keychain.ts
|
|
4220
4967
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4221
|
-
import { existsSync as
|
|
4222
|
-
import
|
|
4223
|
-
import
|
|
4968
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4969
|
+
import path16 from "path";
|
|
4970
|
+
import os8 from "os";
|
|
4224
4971
|
function getKeyDir() {
|
|
4225
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4972
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
|
|
4226
4973
|
}
|
|
4227
4974
|
function getKeyPath() {
|
|
4228
|
-
return
|
|
4975
|
+
return path16.join(getKeyDir(), "master.key");
|
|
4229
4976
|
}
|
|
4230
4977
|
async function tryKeytar() {
|
|
4231
4978
|
try {
|
|
@@ -4246,13 +4993,21 @@ async function getMasterKey() {
|
|
|
4246
4993
|
}
|
|
4247
4994
|
}
|
|
4248
4995
|
const keyPath = getKeyPath();
|
|
4249
|
-
if (!
|
|
4996
|
+
if (!existsSync13(keyPath)) {
|
|
4997
|
+
process.stderr.write(
|
|
4998
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4999
|
+
`
|
|
5000
|
+
);
|
|
4250
5001
|
return null;
|
|
4251
5002
|
}
|
|
4252
5003
|
try {
|
|
4253
5004
|
const content = await readFile4(keyPath, "utf-8");
|
|
4254
5005
|
return Buffer.from(content.trim(), "base64");
|
|
4255
|
-
} catch {
|
|
5006
|
+
} catch (err) {
|
|
5007
|
+
process.stderr.write(
|
|
5008
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
5009
|
+
`
|
|
5010
|
+
);
|
|
4256
5011
|
return null;
|
|
4257
5012
|
}
|
|
4258
5013
|
}
|
|
@@ -4278,13 +5033,13 @@ __export(shard_manager_exports, {
|
|
|
4278
5033
|
listShards: () => listShards,
|
|
4279
5034
|
shardExists: () => shardExists
|
|
4280
5035
|
});
|
|
4281
|
-
import
|
|
4282
|
-
import { existsSync as
|
|
5036
|
+
import path17 from "path";
|
|
5037
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
|
|
4283
5038
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4284
5039
|
function initShardManager(encryptionKey) {
|
|
4285
5040
|
_encryptionKey = encryptionKey;
|
|
4286
|
-
if (!
|
|
4287
|
-
|
|
5041
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5042
|
+
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
4288
5043
|
}
|
|
4289
5044
|
_shardingEnabled = true;
|
|
4290
5045
|
}
|
|
@@ -4304,7 +5059,7 @@ function getShardClient(projectName2) {
|
|
|
4304
5059
|
}
|
|
4305
5060
|
const cached = _shards.get(safeName);
|
|
4306
5061
|
if (cached) return cached;
|
|
4307
|
-
const dbPath =
|
|
5062
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
4308
5063
|
const client = createClient2({
|
|
4309
5064
|
url: `file:${dbPath}`,
|
|
4310
5065
|
encryptionKey: _encryptionKey
|
|
@@ -4314,10 +5069,10 @@ function getShardClient(projectName2) {
|
|
|
4314
5069
|
}
|
|
4315
5070
|
function shardExists(projectName2) {
|
|
4316
5071
|
const safeName = projectName2.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4317
|
-
return
|
|
5072
|
+
return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
4318
5073
|
}
|
|
4319
5074
|
function listShards() {
|
|
4320
|
-
if (!
|
|
5075
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
4321
5076
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4322
5077
|
}
|
|
4323
5078
|
async function ensureShardSchema(client) {
|
|
@@ -4503,7 +5258,7 @@ var init_shard_manager = __esm({
|
|
|
4503
5258
|
"src/lib/shard-manager.ts"() {
|
|
4504
5259
|
"use strict";
|
|
4505
5260
|
init_config();
|
|
4506
|
-
SHARDS_DIR =
|
|
5261
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
4507
5262
|
_shards = /* @__PURE__ */ new Map();
|
|
4508
5263
|
_encryptionKey = null;
|
|
4509
5264
|
_shardingEnabled = false;
|
|
@@ -4628,7 +5383,7 @@ __export(global_procedures_exports, {
|
|
|
4628
5383
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4629
5384
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4630
5385
|
});
|
|
4631
|
-
import { randomUUID as
|
|
5386
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4632
5387
|
async function loadGlobalProcedures() {
|
|
4633
5388
|
const client = getClient();
|
|
4634
5389
|
const result = await client.execute({
|
|
@@ -4657,7 +5412,7 @@ ${sections.join("\n\n")}
|
|
|
4657
5412
|
`;
|
|
4658
5413
|
}
|
|
4659
5414
|
async function storeGlobalProcedure(input) {
|
|
4660
|
-
const id =
|
|
5415
|
+
const id = randomUUID3();
|
|
4661
5416
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4662
5417
|
const client = getClient();
|
|
4663
5418
|
await client.execute({
|
|
@@ -4708,6 +5463,7 @@ __export(store_exports, {
|
|
|
4708
5463
|
vectorToBlob: () => vectorToBlob,
|
|
4709
5464
|
writeMemory: () => writeMemory
|
|
4710
5465
|
});
|
|
5466
|
+
import { createHash } from "crypto";
|
|
4711
5467
|
function isBusyError2(err) {
|
|
4712
5468
|
if (err instanceof Error) {
|
|
4713
5469
|
const msg = err.message.toLowerCase();
|
|
@@ -4781,12 +5537,52 @@ function classifyTier(record) {
|
|
|
4781
5537
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4782
5538
|
return 3;
|
|
4783
5539
|
}
|
|
5540
|
+
function inferFilePaths(record) {
|
|
5541
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5542
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5543
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5544
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5545
|
+
}
|
|
5546
|
+
function inferCommitHash(record) {
|
|
5547
|
+
if (record.tool_name !== "Bash") return null;
|
|
5548
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5549
|
+
return match ? match[1] : null;
|
|
5550
|
+
}
|
|
5551
|
+
function inferLanguageType(record) {
|
|
5552
|
+
const text = record.raw_text;
|
|
5553
|
+
if (!text || text.length < 10) return null;
|
|
5554
|
+
const trimmed = text.trimStart();
|
|
5555
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5556
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5557
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5558
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5559
|
+
return "mixed";
|
|
5560
|
+
}
|
|
5561
|
+
function inferDomain(record) {
|
|
5562
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5563
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5564
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5565
|
+
return null;
|
|
5566
|
+
}
|
|
4784
5567
|
async function writeMemory(record) {
|
|
4785
5568
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4786
5569
|
throw new Error(
|
|
4787
5570
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4788
5571
|
);
|
|
4789
5572
|
}
|
|
5573
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5574
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5575
|
+
return;
|
|
5576
|
+
}
|
|
5577
|
+
try {
|
|
5578
|
+
const client = getClient();
|
|
5579
|
+
const existing = await client.execute({
|
|
5580
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5581
|
+
args: [contentHash, record.agent_id]
|
|
5582
|
+
});
|
|
5583
|
+
if (existing.rows.length > 0) return;
|
|
5584
|
+
} catch {
|
|
5585
|
+
}
|
|
4790
5586
|
const dbRow = {
|
|
4791
5587
|
id: record.id,
|
|
4792
5588
|
agent_id: record.agent_id,
|
|
@@ -4816,7 +5612,23 @@ async function writeMemory(record) {
|
|
|
4816
5612
|
supersedes_id: record.supersedes_id ?? null,
|
|
4817
5613
|
draft: record.draft ? 1 : 0,
|
|
4818
5614
|
memory_type: record.memory_type ?? "raw",
|
|
4819
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5615
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5616
|
+
content_hash: contentHash,
|
|
5617
|
+
intent: record.intent ?? null,
|
|
5618
|
+
outcome: record.outcome ?? null,
|
|
5619
|
+
domain: record.domain ?? inferDomain(record),
|
|
5620
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5621
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5622
|
+
chain_position: record.chain_position ?? null,
|
|
5623
|
+
review_status: record.review_status ?? null,
|
|
5624
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5625
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5626
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5627
|
+
duration_ms: record.duration_ms ?? null,
|
|
5628
|
+
token_cost: record.token_cost ?? null,
|
|
5629
|
+
audience: record.audience ?? null,
|
|
5630
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5631
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4820
5632
|
};
|
|
4821
5633
|
_pendingRecords.push(dbRow);
|
|
4822
5634
|
orgBus.emit({
|
|
@@ -4874,80 +5686,85 @@ async function flushBatch() {
|
|
|
4874
5686
|
const draft = row.draft ? 1 : 0;
|
|
4875
5687
|
const memoryType = row.memory_type ?? "raw";
|
|
4876
5688
|
const trajectory = row.trajectory ?? null;
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
5689
|
+
const contentHash = row.content_hash ?? null;
|
|
5690
|
+
const intent = row.intent ?? null;
|
|
5691
|
+
const outcome = row.outcome ?? null;
|
|
5692
|
+
const domain = row.domain ?? null;
|
|
5693
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5694
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5695
|
+
const chainPosition = row.chain_position ?? null;
|
|
5696
|
+
const reviewStatus = row.review_status ?? null;
|
|
5697
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5698
|
+
const filePaths = row.file_paths ?? null;
|
|
5699
|
+
const commitHash = row.commit_hash ?? null;
|
|
5700
|
+
const durationMs = row.duration_ms ?? null;
|
|
5701
|
+
const tokenCost = row.token_cost ?? null;
|
|
5702
|
+
const audience = row.audience ?? null;
|
|
5703
|
+
const languageType = row.language_type ?? null;
|
|
5704
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5705
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4880
5706
|
tool_name, project_name,
|
|
4881
5707
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4882
5708
|
confidence, last_accessed,
|
|
4883
5709
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4884
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
pageNumber,
|
|
4943
|
-
sourcePath,
|
|
4944
|
-
sourceType,
|
|
4945
|
-
tier,
|
|
4946
|
-
supersedesId,
|
|
4947
|
-
draft,
|
|
4948
|
-
memoryType,
|
|
4949
|
-
trajectory
|
|
4950
|
-
]
|
|
5710
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5711
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5712
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5713
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5714
|
+
const metaArgs = [
|
|
5715
|
+
intent,
|
|
5716
|
+
outcome,
|
|
5717
|
+
domain,
|
|
5718
|
+
referencedEntities,
|
|
5719
|
+
retrievalCount,
|
|
5720
|
+
chainPosition,
|
|
5721
|
+
reviewStatus,
|
|
5722
|
+
contextWindowPct,
|
|
5723
|
+
filePaths,
|
|
5724
|
+
commitHash,
|
|
5725
|
+
durationMs,
|
|
5726
|
+
tokenCost,
|
|
5727
|
+
audience,
|
|
5728
|
+
languageType,
|
|
5729
|
+
parentMemoryId
|
|
5730
|
+
];
|
|
5731
|
+
const baseArgs = [
|
|
5732
|
+
row.id,
|
|
5733
|
+
row.agent_id,
|
|
5734
|
+
row.agent_role,
|
|
5735
|
+
row.session_id,
|
|
5736
|
+
row.timestamp,
|
|
5737
|
+
row.tool_name,
|
|
5738
|
+
row.project_name,
|
|
5739
|
+
row.has_error,
|
|
5740
|
+
row.raw_text
|
|
5741
|
+
];
|
|
5742
|
+
const sharedArgs = [
|
|
5743
|
+
row.version,
|
|
5744
|
+
taskId,
|
|
5745
|
+
importance,
|
|
5746
|
+
status,
|
|
5747
|
+
confidence,
|
|
5748
|
+
lastAccessed,
|
|
5749
|
+
workspaceId,
|
|
5750
|
+
documentId,
|
|
5751
|
+
userId,
|
|
5752
|
+
charOffset,
|
|
5753
|
+
pageNumber,
|
|
5754
|
+
sourcePath,
|
|
5755
|
+
sourceType,
|
|
5756
|
+
tier,
|
|
5757
|
+
supersedesId,
|
|
5758
|
+
draft,
|
|
5759
|
+
memoryType,
|
|
5760
|
+
trajectory,
|
|
5761
|
+
contentHash
|
|
5762
|
+
];
|
|
5763
|
+
return {
|
|
5764
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5765
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5766
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5767
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4951
5768
|
};
|
|
4952
5769
|
};
|
|
4953
5770
|
const globalClient = getClient();
|
|
@@ -5402,13 +6219,13 @@ async function sweepTasks(projectName2, options = {}) {
|
|
|
5402
6219
|
}
|
|
5403
6220
|
|
|
5404
6221
|
// src/bin/git-sweep.ts
|
|
5405
|
-
import
|
|
6222
|
+
import path18 from "path";
|
|
5406
6223
|
var args = process.argv.slice(2);
|
|
5407
6224
|
var dryRun = args.includes("--dry-run");
|
|
5408
6225
|
var projectIdx = args.indexOf("--project");
|
|
5409
6226
|
var limitIdx = args.indexOf("--limit");
|
|
5410
6227
|
var staleIdx = args.indexOf("--stale");
|
|
5411
|
-
var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(
|
|
6228
|
+
var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path18.sep).pop();
|
|
5412
6229
|
var commitLimit = limitIdx >= 0 ? Number(args[limitIdx + 1]) : void 0;
|
|
5413
6230
|
var staleMinutes = staleIdx >= 0 ? Number(args[staleIdx + 1]) : void 0;
|
|
5414
6231
|
async function main() {
|