@askexenow/exe-os 0.8.33 → 0.8.37
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 +341 -349
- package/dist/bin/backfill-responses.js +81 -13
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1737 -1117
- package/dist/bin/exe-assign.js +89 -19
- package/dist/bin/exe-boot.js +951 -101
- package/dist/bin/exe-call.js +61 -2
- package/dist/bin/exe-dispatch.js +61 -13
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +71 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +178 -45
- package/dist/bin/exe-heartbeat.js +79 -14
- package/dist/bin/exe-kill.js +71 -3
- package/dist/bin/exe-launch-agent.js +148 -14
- package/dist/bin/exe-link.js +1437 -0
- package/dist/bin/exe-new-employee.js +98 -13
- package/dist/bin/exe-pending-messages.js +74 -8
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +77 -11
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +73 -5
- package/dist/bin/exe-search.js +88 -14
- package/dist/bin/exe-session-cleanup.js +102 -28
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +80 -5
- package/dist/bin/graph-backfill.js +71 -3
- package/dist/bin/graph-export.js +71 -3
- package/dist/bin/install.js +38 -8
- package/dist/bin/scan-tasks.js +80 -5
- package/dist/bin/setup.js +128 -10
- package/dist/bin/shard-migrate.js +71 -3
- package/dist/bin/wiki-sync.js +71 -3
- package/dist/gateway/index.js +179 -46
- package/dist/hooks/bug-report-worker.js +254 -28
- package/dist/hooks/commit-complete.js +80 -5
- package/dist/hooks/error-recall.js +89 -15
- package/dist/hooks/exe-heartbeat-hook.js +1 -1
- package/dist/hooks/ingest-worker.js +185 -51
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +81 -6
- package/dist/hooks/notification.js +81 -6
- package/dist/hooks/post-compact.js +81 -6
- package/dist/hooks/pre-compact.js +81 -6
- package/dist/hooks/pre-tool-use.js +423 -196
- package/dist/hooks/prompt-ingest-worker.js +91 -23
- package/dist/hooks/prompt-submit.js +159 -45
- package/dist/hooks/response-ingest-worker.js +96 -23
- package/dist/hooks/session-end.js +81 -6
- package/dist/hooks/session-start.js +89 -15
- package/dist/hooks/stop.js +81 -6
- package/dist/hooks/subagent-stop.js +81 -6
- package/dist/hooks/summary-worker.js +807 -55
- package/dist/index.js +198 -60
- package/dist/lib/cloud-sync.js +703 -18
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +26 -0
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +207 -74
- package/dist/lib/hybrid-search.js +88 -14
- package/dist/lib/identity-templates.js +51 -0
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +65 -17
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +73 -4
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +95 -28
- package/dist/lib/tmux-routing.js +92 -23
- package/dist/mcp/server.js +800 -74
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +198 -31
- package/dist/mcp/tools/deactivate-behavior.js +4 -4
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +19 -9
- package/dist/mcp/tools/send-message.js +69 -21
- package/dist/mcp/tools/update-task.js +28 -18
- package/dist/runtime/index.js +166 -28
- package/dist/tui/App.js +193 -40
- package/package.json +7 -3
- package/src/commands/exe/afk.md +116 -0
- package/src/commands/exe/rename.md +12 -0
|
@@ -262,7 +262,7 @@ function listShards() {
|
|
|
262
262
|
}
|
|
263
263
|
async function ensureShardSchema(client) {
|
|
264
264
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
265
|
-
await client.execute("PRAGMA busy_timeout =
|
|
265
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
266
266
|
try {
|
|
267
267
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
268
268
|
} catch {
|
|
@@ -446,124 +446,79 @@ var init_shard_manager = __esm({
|
|
|
446
446
|
}
|
|
447
447
|
});
|
|
448
448
|
|
|
449
|
-
// src/lib/employees.ts
|
|
450
|
-
var employees_exports = {};
|
|
451
|
-
__export(employees_exports, {
|
|
452
|
-
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
453
|
-
addEmployee: () => addEmployee,
|
|
454
|
-
getEmployee: () => getEmployee,
|
|
455
|
-
loadEmployees: () => loadEmployees,
|
|
456
|
-
registerBinSymlinks: () => registerBinSymlinks,
|
|
457
|
-
saveEmployees: () => saveEmployees,
|
|
458
|
-
validateEmployeeName: () => validateEmployeeName
|
|
459
|
-
});
|
|
460
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
461
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
|
|
462
|
-
import { execSync } from "child_process";
|
|
463
|
-
import path5 from "path";
|
|
464
|
-
function validateEmployeeName(name) {
|
|
465
|
-
if (!name) {
|
|
466
|
-
return { valid: false, error: "Name is required" };
|
|
467
|
-
}
|
|
468
|
-
if (name.length > 32) {
|
|
469
|
-
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
470
|
-
}
|
|
471
|
-
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
472
|
-
return {
|
|
473
|
-
valid: false,
|
|
474
|
-
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
return { valid: true };
|
|
478
|
-
}
|
|
479
|
-
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
480
|
-
if (!existsSync5(employeesPath)) {
|
|
481
|
-
return [];
|
|
482
|
-
}
|
|
483
|
-
const raw = await readFile3(employeesPath, "utf-8");
|
|
484
|
-
try {
|
|
485
|
-
return JSON.parse(raw);
|
|
486
|
-
} catch {
|
|
487
|
-
return [];
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
491
|
-
await mkdir3(path5.dirname(employeesPath), { recursive: true });
|
|
492
|
-
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
493
|
-
}
|
|
494
|
-
function getEmployee(employees, name) {
|
|
495
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
496
|
-
}
|
|
497
|
-
function addEmployee(employees, employee) {
|
|
498
|
-
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
499
|
-
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
500
|
-
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
501
|
-
}
|
|
502
|
-
return [...employees, normalized];
|
|
503
|
-
}
|
|
504
|
-
function registerBinSymlinks(name) {
|
|
505
|
-
const created = [];
|
|
506
|
-
const skipped = [];
|
|
507
|
-
const errors = [];
|
|
508
|
-
let exeBinPath;
|
|
509
|
-
try {
|
|
510
|
-
exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
|
|
511
|
-
} catch {
|
|
512
|
-
errors.push("Could not find 'exe' in PATH");
|
|
513
|
-
return { created, skipped, errors };
|
|
514
|
-
}
|
|
515
|
-
const binDir = path5.dirname(exeBinPath);
|
|
516
|
-
let target;
|
|
517
|
-
try {
|
|
518
|
-
target = readlinkSync(exeBinPath);
|
|
519
|
-
} catch {
|
|
520
|
-
errors.push("Could not read 'exe' symlink");
|
|
521
|
-
return { created, skipped, errors };
|
|
522
|
-
}
|
|
523
|
-
for (const suffix of ["", "-opencode"]) {
|
|
524
|
-
const linkName = `${name}${suffix}`;
|
|
525
|
-
const linkPath = path5.join(binDir, linkName);
|
|
526
|
-
if (existsSync5(linkPath)) {
|
|
527
|
-
skipped.push(linkName);
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
try {
|
|
531
|
-
symlinkSync(target, linkPath);
|
|
532
|
-
created.push(linkName);
|
|
533
|
-
} catch (err) {
|
|
534
|
-
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return { created, skipped, errors };
|
|
538
|
-
}
|
|
539
|
-
var EMPLOYEES_PATH;
|
|
540
|
-
var init_employees = __esm({
|
|
541
|
-
"src/lib/employees.ts"() {
|
|
542
|
-
"use strict";
|
|
543
|
-
init_config();
|
|
544
|
-
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
|
|
548
449
|
// src/bin/backfill-conversations.ts
|
|
549
450
|
import crypto2 from "crypto";
|
|
550
451
|
import { createReadStream } from "fs";
|
|
551
452
|
import { readdir, stat } from "fs/promises";
|
|
552
|
-
import
|
|
453
|
+
import path5 from "path";
|
|
553
454
|
import { createInterface } from "readline";
|
|
554
455
|
import { homedir } from "os";
|
|
456
|
+
import { parseArgs } from "util";
|
|
555
457
|
|
|
556
458
|
// src/types/memory.ts
|
|
557
459
|
var EMBEDDING_DIM = 1024;
|
|
558
460
|
|
|
559
461
|
// src/lib/database.ts
|
|
560
462
|
import { createClient } from "@libsql/client";
|
|
463
|
+
|
|
464
|
+
// src/lib/db-retry.ts
|
|
465
|
+
var MAX_RETRIES = 3;
|
|
466
|
+
var BASE_DELAY_MS = 200;
|
|
467
|
+
var MAX_JITTER_MS = 300;
|
|
468
|
+
function isBusyError(err) {
|
|
469
|
+
if (err instanceof Error) {
|
|
470
|
+
const msg = err.message.toLowerCase();
|
|
471
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
function delay(ms) {
|
|
476
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
477
|
+
}
|
|
478
|
+
async function retryOnBusy(fn, label) {
|
|
479
|
+
let lastError;
|
|
480
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
481
|
+
try {
|
|
482
|
+
return await fn();
|
|
483
|
+
} catch (err) {
|
|
484
|
+
lastError = err;
|
|
485
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
488
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
489
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
490
|
+
process.stderr.write(
|
|
491
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
492
|
+
`
|
|
493
|
+
);
|
|
494
|
+
await delay(backoff + jitter);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
throw lastError;
|
|
498
|
+
}
|
|
499
|
+
function wrapWithRetry(client) {
|
|
500
|
+
return new Proxy(client, {
|
|
501
|
+
get(target, prop, receiver) {
|
|
502
|
+
if (prop === "execute") {
|
|
503
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
504
|
+
}
|
|
505
|
+
if (prop === "batch") {
|
|
506
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
507
|
+
}
|
|
508
|
+
return Reflect.get(target, prop, receiver);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/lib/database.ts
|
|
561
514
|
var _client = null;
|
|
515
|
+
var _resilientClient = null;
|
|
562
516
|
var initTurso = initDatabase;
|
|
563
517
|
async function initDatabase(config) {
|
|
564
518
|
if (_client) {
|
|
565
519
|
_client.close();
|
|
566
520
|
_client = null;
|
|
521
|
+
_resilientClient = null;
|
|
567
522
|
}
|
|
568
523
|
const opts = {
|
|
569
524
|
url: `file:${config.dbPath}`
|
|
@@ -572,17 +527,24 @@ async function initDatabase(config) {
|
|
|
572
527
|
opts.encryptionKey = config.encryptionKey;
|
|
573
528
|
}
|
|
574
529
|
_client = createClient(opts);
|
|
530
|
+
_resilientClient = wrapWithRetry(_client);
|
|
575
531
|
}
|
|
576
532
|
function getClient() {
|
|
533
|
+
if (!_resilientClient) {
|
|
534
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
535
|
+
}
|
|
536
|
+
return _resilientClient;
|
|
537
|
+
}
|
|
538
|
+
function getRawClient() {
|
|
577
539
|
if (!_client) {
|
|
578
540
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
579
541
|
}
|
|
580
542
|
return _client;
|
|
581
543
|
}
|
|
582
544
|
async function ensureSchema() {
|
|
583
|
-
const client =
|
|
545
|
+
const client = getRawClient();
|
|
584
546
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
585
|
-
await client.execute("PRAGMA busy_timeout =
|
|
547
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
586
548
|
try {
|
|
587
549
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
588
550
|
} catch {
|
|
@@ -1483,7 +1445,8 @@ async function writeMemory(record) {
|
|
|
1483
1445
|
has_error: record.has_error ? 1 : 0,
|
|
1484
1446
|
raw_text: record.raw_text,
|
|
1485
1447
|
vector: record.vector,
|
|
1486
|
-
version:
|
|
1448
|
+
version: 0,
|
|
1449
|
+
// Placeholder — assigned atomically at flush time
|
|
1487
1450
|
task_id: record.task_id ?? null,
|
|
1488
1451
|
importance: record.importance ?? 5,
|
|
1489
1452
|
status: record.status ?? "active",
|
|
@@ -1517,6 +1480,13 @@ async function flushBatch() {
|
|
|
1517
1480
|
_flushing = true;
|
|
1518
1481
|
try {
|
|
1519
1482
|
const batch = _pendingRecords.slice(0);
|
|
1483
|
+
const client = getClient();
|
|
1484
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1485
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1486
|
+
for (const row of batch) {
|
|
1487
|
+
row.version = baseVersion++;
|
|
1488
|
+
}
|
|
1489
|
+
_nextVersion = baseVersion;
|
|
1520
1490
|
const buildStmt = (row) => {
|
|
1521
1491
|
const hasVector = row.vector !== null;
|
|
1522
1492
|
const taskId = row.task_id ?? null;
|
|
@@ -1831,11 +1801,11 @@ async function connectEmbedDaemon() {
|
|
|
1831
1801
|
}
|
|
1832
1802
|
}
|
|
1833
1803
|
const start = Date.now();
|
|
1834
|
-
let
|
|
1804
|
+
let delay2 = 100;
|
|
1835
1805
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1836
|
-
await new Promise((r) => setTimeout(r,
|
|
1806
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1837
1807
|
if (await connectToSocket()) return true;
|
|
1838
|
-
|
|
1808
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1839
1809
|
}
|
|
1840
1810
|
return false;
|
|
1841
1811
|
}
|
|
@@ -1927,11 +1897,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
1927
1897
|
`);
|
|
1928
1898
|
killAndRespawnDaemon();
|
|
1929
1899
|
const start = Date.now();
|
|
1930
|
-
let
|
|
1900
|
+
let delay2 = 200;
|
|
1931
1901
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1932
|
-
await new Promise((r) => setTimeout(r,
|
|
1902
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1933
1903
|
if (await connectToSocket()) break;
|
|
1934
|
-
|
|
1904
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1935
1905
|
}
|
|
1936
1906
|
if (!_connected) return null;
|
|
1937
1907
|
}
|
|
@@ -1943,11 +1913,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
1943
1913
|
`);
|
|
1944
1914
|
killAndRespawnDaemon();
|
|
1945
1915
|
const start = Date.now();
|
|
1946
|
-
let
|
|
1916
|
+
let delay2 = 200;
|
|
1947
1917
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1948
|
-
await new Promise((r) => setTimeout(r,
|
|
1918
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1949
1919
|
if (await connectToSocket()) break;
|
|
1950
|
-
|
|
1920
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1951
1921
|
}
|
|
1952
1922
|
if (!_connected) return null;
|
|
1953
1923
|
const retry = await sendRequest([text], priority);
|
|
@@ -1973,9 +1943,11 @@ function isMainModule(importMetaUrl) {
|
|
|
1973
1943
|
}
|
|
1974
1944
|
|
|
1975
1945
|
// src/bin/backfill-conversations.ts
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1946
|
+
var TOOL_NAME = "backfill-conversation";
|
|
1947
|
+
var MIN_MESSAGES = 3;
|
|
1948
|
+
var MAX_SUMMARY_LENGTH = 4e3;
|
|
1949
|
+
async function findJsonlFiles(sinceDate, projectFilter) {
|
|
1950
|
+
const projectsDir = path5.join(homedir(), ".claude", "projects");
|
|
1979
1951
|
const files = [];
|
|
1980
1952
|
async function walk(dir) {
|
|
1981
1953
|
let entries;
|
|
@@ -1985,61 +1957,67 @@ async function findJsonlFiles(cutoffMs) {
|
|
|
1985
1957
|
return;
|
|
1986
1958
|
}
|
|
1987
1959
|
for (const entry of entries) {
|
|
1988
|
-
const full =
|
|
1960
|
+
const full = path5.join(dir, entry.name);
|
|
1989
1961
|
if (entry.isDirectory()) {
|
|
1962
|
+
if (entry.name === "subagents" || entry.name === "tool-results") continue;
|
|
1990
1963
|
await walk(full);
|
|
1991
1964
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1992
1965
|
try {
|
|
1993
1966
|
const s = await stat(full);
|
|
1994
|
-
if (s.mtimeMs
|
|
1967
|
+
if (sinceDate && s.mtimeMs < sinceDate.getTime()) continue;
|
|
1968
|
+
files.push(full);
|
|
1995
1969
|
} catch {
|
|
1996
1970
|
}
|
|
1997
1971
|
}
|
|
1998
1972
|
}
|
|
1999
1973
|
}
|
|
2000
|
-
|
|
1974
|
+
if (projectFilter) {
|
|
1975
|
+
let projectDirs;
|
|
1976
|
+
try {
|
|
1977
|
+
projectDirs = await readdir(projectsDir, { withFileTypes: true });
|
|
1978
|
+
} catch {
|
|
1979
|
+
return files;
|
|
1980
|
+
}
|
|
1981
|
+
for (const entry of projectDirs) {
|
|
1982
|
+
if (!entry.isDirectory()) continue;
|
|
1983
|
+
const decoded = decodeProjectDir(entry.name);
|
|
1984
|
+
if (decoded.toLowerCase().includes(projectFilter.toLowerCase())) {
|
|
1985
|
+
await walk(path5.join(projectsDir, entry.name));
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
} else {
|
|
1989
|
+
await walk(projectsDir);
|
|
1990
|
+
}
|
|
2001
1991
|
return files;
|
|
2002
1992
|
}
|
|
2003
|
-
function
|
|
2004
|
-
const projectsDir = path6.join(homedir(), ".claude", "projects");
|
|
2005
|
-
const relative = path6.relative(projectsDir, filePath);
|
|
2006
|
-
const projectDir = relative.split(path6.sep)[0] ?? relative;
|
|
1993
|
+
function decodeProjectDir(dirName) {
|
|
2007
1994
|
const homeEncoded = homedir().replaceAll("/", "-");
|
|
2008
|
-
if (
|
|
2009
|
-
return
|
|
1995
|
+
if (dirName.startsWith(homeEncoded + "-")) {
|
|
1996
|
+
return dirName.slice(homeEncoded.length + 1);
|
|
2010
1997
|
}
|
|
2011
|
-
if (
|
|
2012
|
-
return
|
|
1998
|
+
if (dirName === homeEncoded) return "home";
|
|
1999
|
+
return dirName;
|
|
2013
2000
|
}
|
|
2014
|
-
function
|
|
2015
|
-
|
|
2016
|
-
const
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
texts.push(block.text);
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
return texts.join("\n");
|
|
2023
|
-
}
|
|
2024
|
-
function extractUserText(content) {
|
|
2025
|
-
if (typeof content === "string") return content;
|
|
2026
|
-
const texts = [];
|
|
2027
|
-
for (const block of content) {
|
|
2028
|
-
if (block.type === "text" && block.text) {
|
|
2029
|
-
texts.push(block.text);
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
return texts.join("\n");
|
|
2001
|
+
function projectNameFromPath(filePath) {
|
|
2002
|
+
const projectsDir = path5.join(homedir(), ".claude", "projects");
|
|
2003
|
+
const relative = path5.relative(projectsDir, filePath);
|
|
2004
|
+
const projectDir = relative.split(path5.sep)[0] ?? "unknown";
|
|
2005
|
+
return decodeProjectDir(projectDir);
|
|
2033
2006
|
}
|
|
2034
|
-
async function
|
|
2035
|
-
const
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2007
|
+
async function parseConversation(filePath) {
|
|
2008
|
+
const conv = {
|
|
2009
|
+
sessionId: path5.basename(filePath, ".jsonl"),
|
|
2010
|
+
projectName: projectNameFromPath(filePath),
|
|
2011
|
+
cwd: void 0,
|
|
2012
|
+
startTime: void 0,
|
|
2013
|
+
endTime: void 0,
|
|
2014
|
+
userMessages: [],
|
|
2015
|
+
toolCounts: {},
|
|
2016
|
+
filesTouched: /* @__PURE__ */ new Set(),
|
|
2017
|
+
errorCount: 0,
|
|
2018
|
+
totalMessages: 0,
|
|
2019
|
+
agentId: "default"
|
|
2020
|
+
};
|
|
2043
2021
|
const rl = createInterface({
|
|
2044
2022
|
input: createReadStream(filePath, { encoding: "utf8" }),
|
|
2045
2023
|
crlfDelay: Infinity
|
|
@@ -2052,251 +2030,265 @@ async function extractConversationPairs(filePath, projectFilter) {
|
|
|
2052
2030
|
} catch {
|
|
2053
2031
|
continue;
|
|
2054
2032
|
}
|
|
2055
|
-
if (entry.cwd
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2033
|
+
if (entry.cwd && typeof entry.cwd === "string") {
|
|
2034
|
+
conv.cwd = entry.cwd;
|
|
2035
|
+
}
|
|
2036
|
+
const ts = entry.timestamp;
|
|
2037
|
+
if (ts) {
|
|
2038
|
+
if (!conv.startTime || ts < conv.startTime) conv.startTime = ts;
|
|
2039
|
+
if (!conv.endTime || ts > conv.endTime) conv.endTime = ts;
|
|
2040
|
+
}
|
|
2041
|
+
const entryType = entry.type;
|
|
2042
|
+
if (entryType === "user") {
|
|
2043
|
+
conv.totalMessages++;
|
|
2044
|
+
const message = entry.message;
|
|
2045
|
+
if (message?.content) {
|
|
2046
|
+
const text = extractUserText(message.content);
|
|
2047
|
+
if (text && text.length > 10) {
|
|
2048
|
+
conv.userMessages.push(text);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
} else if (entryType === "assistant") {
|
|
2052
|
+
conv.totalMessages++;
|
|
2053
|
+
const message = entry.message;
|
|
2054
|
+
if (message?.content && Array.isArray(message.content)) {
|
|
2055
|
+
for (const block of message.content) {
|
|
2056
|
+
if (typeof block !== "object" || block === null) continue;
|
|
2057
|
+
const b = block;
|
|
2058
|
+
if (b.type === "tool_use") {
|
|
2059
|
+
const toolName = b.name;
|
|
2060
|
+
conv.toolCounts[toolName] = (conv.toolCounts[toolName] || 0) + 1;
|
|
2061
|
+
const input = b.input;
|
|
2062
|
+
if (input?.file_path && typeof input.file_path === "string") {
|
|
2063
|
+
if (toolName === "Write" || toolName === "Edit") {
|
|
2064
|
+
conv.filesTouched.add(input.file_path);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2065
2069
|
}
|
|
2066
|
-
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
2067
|
-
const assistantText = extractAssistantText(entry.message.content);
|
|
2068
|
-
if (!assistantText.trim()) continue;
|
|
2069
|
-
const resolvedProject = fileCwd ? path6.basename(fileCwd) : fallbackProject;
|
|
2070
|
-
const userText = pendingUser?.text ?? "";
|
|
2071
|
-
const timestamp = entry.timestamp ?? pendingUser?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2072
|
-
pairs.push({
|
|
2073
|
-
userText,
|
|
2074
|
-
assistantText,
|
|
2075
|
-
timestamp,
|
|
2076
|
-
sessionId: fileSessionId || path6.basename(filePath, ".jsonl"),
|
|
2077
|
-
project: resolvedProject,
|
|
2078
|
-
cwd: fileCwd || fallbackProject
|
|
2079
|
-
});
|
|
2080
|
-
pendingUser = null;
|
|
2081
2070
|
}
|
|
2082
2071
|
}
|
|
2083
|
-
|
|
2072
|
+
if (conv.cwd) {
|
|
2073
|
+
conv.projectName = path5.basename(conv.cwd);
|
|
2074
|
+
const worktreeMatch = conv.cwd.match(/\.worktrees\/([^/]+)/);
|
|
2075
|
+
if (worktreeMatch?.[1]) {
|
|
2076
|
+
conv.agentId = worktreeMatch[1];
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
return conv;
|
|
2084
2080
|
}
|
|
2085
|
-
function
|
|
2086
|
-
|
|
2081
|
+
function extractUserText(content) {
|
|
2082
|
+
if (typeof content === "string") return content;
|
|
2083
|
+
if (Array.isArray(content)) {
|
|
2084
|
+
const parts = [];
|
|
2085
|
+
for (const block of content) {
|
|
2086
|
+
if (typeof block === "string") {
|
|
2087
|
+
parts.push(block);
|
|
2088
|
+
} else if (typeof block === "object" && block !== null) {
|
|
2089
|
+
const b = block;
|
|
2090
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
2091
|
+
parts.push(b.text);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
return parts.join("\n");
|
|
2096
|
+
}
|
|
2097
|
+
return "";
|
|
2087
2098
|
}
|
|
2088
|
-
|
|
2099
|
+
function buildSummary(conv) {
|
|
2100
|
+
const parts = [];
|
|
2101
|
+
parts.push(`Session: ${conv.sessionId}`);
|
|
2102
|
+
parts.push(`Project: ${conv.projectName}`);
|
|
2103
|
+
if (conv.startTime) {
|
|
2104
|
+
parts.push(`Time: ${conv.startTime}${conv.endTime ? ` \u2192 ${conv.endTime}` : ""}`);
|
|
2105
|
+
}
|
|
2106
|
+
parts.push(`Messages: ${conv.totalMessages}`);
|
|
2107
|
+
if (conv.agentId !== "default") {
|
|
2108
|
+
parts.push(`Agent: ${conv.agentId}`);
|
|
2109
|
+
}
|
|
2110
|
+
parts.push("");
|
|
2111
|
+
if (conv.userMessages.length > 0) {
|
|
2112
|
+
parts.push("## What was asked");
|
|
2113
|
+
const prompts = conv.userMessages.slice(0, 5);
|
|
2114
|
+
for (const prompt of prompts) {
|
|
2115
|
+
const truncated = prompt.length > 300 ? prompt.slice(0, 300) + "..." : prompt;
|
|
2116
|
+
const cleaned = truncated.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
2117
|
+
if (cleaned) parts.push(`- ${cleaned}`);
|
|
2118
|
+
}
|
|
2119
|
+
if (conv.userMessages.length > 5) {
|
|
2120
|
+
parts.push(`- ... and ${conv.userMessages.length - 5} more prompts`);
|
|
2121
|
+
}
|
|
2122
|
+
parts.push("");
|
|
2123
|
+
}
|
|
2124
|
+
const toolEntries = Object.entries(conv.toolCounts).sort((a, b) => b[1] - a[1]);
|
|
2125
|
+
if (toolEntries.length > 0) {
|
|
2126
|
+
parts.push("## Tools used");
|
|
2127
|
+
for (const [tool, count] of toolEntries) {
|
|
2128
|
+
parts.push(`- ${tool}: ${count}`);
|
|
2129
|
+
}
|
|
2130
|
+
parts.push("");
|
|
2131
|
+
}
|
|
2132
|
+
if (conv.filesTouched.size > 0) {
|
|
2133
|
+
parts.push("## Files modified");
|
|
2134
|
+
const fileList = [...conv.filesTouched].sort();
|
|
2135
|
+
const shown = fileList.slice(0, 20);
|
|
2136
|
+
for (const f of shown) {
|
|
2137
|
+
parts.push(`- ${f}`);
|
|
2138
|
+
}
|
|
2139
|
+
if (fileList.length > 20) {
|
|
2140
|
+
parts.push(`- ... and ${fileList.length - 20} more files`);
|
|
2141
|
+
}
|
|
2142
|
+
parts.push("");
|
|
2143
|
+
}
|
|
2144
|
+
if (conv.errorCount > 0) {
|
|
2145
|
+
parts.push(`Errors: ${conv.errorCount}`);
|
|
2146
|
+
}
|
|
2147
|
+
let summary = parts.join("\n");
|
|
2148
|
+
if (summary.length > MAX_SUMMARY_LENGTH) {
|
|
2149
|
+
summary = summary.slice(0, MAX_SUMMARY_LENGTH);
|
|
2150
|
+
}
|
|
2151
|
+
return summary;
|
|
2152
|
+
}
|
|
2153
|
+
async function loadExistingSourcePaths() {
|
|
2089
2154
|
const client = getClient();
|
|
2090
|
-
const
|
|
2155
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2091
2156
|
let offset = 0;
|
|
2092
2157
|
const batchSize = 500;
|
|
2093
2158
|
while (true) {
|
|
2094
2159
|
const result = await client.execute({
|
|
2095
|
-
sql: `SELECT
|
|
2096
|
-
WHERE
|
|
2160
|
+
sql: `SELECT source_path FROM memories
|
|
2161
|
+
WHERE tool_name = ? AND source_path IS NOT NULL
|
|
2097
2162
|
ORDER BY id LIMIT ? OFFSET ?`,
|
|
2098
|
-
args: [
|
|
2163
|
+
args: [TOOL_NAME, batchSize, offset]
|
|
2099
2164
|
});
|
|
2100
2165
|
if (result.rows.length === 0) break;
|
|
2101
2166
|
for (const row of result.rows) {
|
|
2102
|
-
|
|
2103
|
-
hashPair(
|
|
2104
|
-
row.content_text ?? "",
|
|
2105
|
-
row.agent_response ?? ""
|
|
2106
|
-
)
|
|
2107
|
-
);
|
|
2167
|
+
paths.add(row.source_path);
|
|
2108
2168
|
}
|
|
2109
2169
|
offset += batchSize;
|
|
2110
2170
|
}
|
|
2111
|
-
return
|
|
2112
|
-
}
|
|
2113
|
-
var MAX_CONTENT_LENGTH = 8e3;
|
|
2114
|
-
var MIN_ASSISTANT_LENGTH = 50;
|
|
2115
|
-
async function storePair(pair, daemonConnected, stats, agentId) {
|
|
2116
|
-
const client = getClient();
|
|
2117
|
-
const id = crypto2.randomUUID();
|
|
2118
|
-
const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
|
|
2119
|
-
const assistTrunc = pair.assistantText.length > MAX_CONTENT_LENGTH ? pair.assistantText.slice(0, MAX_CONTENT_LENGTH) : pair.assistantText;
|
|
2120
|
-
await client.execute({
|
|
2121
|
-
sql: `INSERT INTO conversations
|
|
2122
|
-
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
2123
|
-
recipient_id, channel_id, thread_id, reply_to_id,
|
|
2124
|
-
content_text, content_media, agent_response, agent_name,
|
|
2125
|
-
timestamp, ingested_at)
|
|
2126
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2127
|
-
args: [
|
|
2128
|
-
id,
|
|
2129
|
-
"claude-code",
|
|
2130
|
-
null,
|
|
2131
|
-
"user",
|
|
2132
|
-
"user",
|
|
2133
|
-
null,
|
|
2134
|
-
null,
|
|
2135
|
-
null,
|
|
2136
|
-
pair.project,
|
|
2137
|
-
pair.sessionId,
|
|
2138
|
-
null,
|
|
2139
|
-
userTrunc,
|
|
2140
|
-
null,
|
|
2141
|
-
assistTrunc,
|
|
2142
|
-
"claude",
|
|
2143
|
-
pair.timestamp,
|
|
2144
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
2145
|
-
]
|
|
2146
|
-
});
|
|
2147
|
-
stats.conversationsStored++;
|
|
2148
|
-
const rawText = [
|
|
2149
|
-
`[claude-code] Conversation in ${pair.project}`,
|
|
2150
|
-
userTrunc ? `User: ${userTrunc}` : null,
|
|
2151
|
-
`Assistant: ${assistTrunc}`
|
|
2152
|
-
].filter(Boolean).join("\n");
|
|
2153
|
-
let vector = null;
|
|
2154
|
-
if (daemonConnected) {
|
|
2155
|
-
try {
|
|
2156
|
-
vector = await embedViaClient(rawText, "low");
|
|
2157
|
-
if (!vector) stats.embedFailed++;
|
|
2158
|
-
} catch {
|
|
2159
|
-
stats.embedFailed++;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
await writeMemory({
|
|
2163
|
-
id: crypto2.randomUUID(),
|
|
2164
|
-
agent_id: agentId,
|
|
2165
|
-
agent_role: "coo",
|
|
2166
|
-
session_id: pair.sessionId,
|
|
2167
|
-
timestamp: pair.timestamp,
|
|
2168
|
-
tool_name: "ConversationBackfill",
|
|
2169
|
-
project_name: pair.project,
|
|
2170
|
-
has_error: false,
|
|
2171
|
-
raw_text: rawText,
|
|
2172
|
-
vector,
|
|
2173
|
-
importance: 3
|
|
2174
|
-
});
|
|
2175
|
-
stats.memoriesStored++;
|
|
2171
|
+
return paths;
|
|
2176
2172
|
}
|
|
2177
2173
|
async function backfillConversations(options) {
|
|
2178
2174
|
const stats = {
|
|
2179
2175
|
filesScanned: 0,
|
|
2180
2176
|
conversationsStored: 0,
|
|
2181
|
-
memoriesStored: 0,
|
|
2182
2177
|
skippedDedup: 0,
|
|
2183
|
-
|
|
2178
|
+
skippedTooShort: 0,
|
|
2184
2179
|
embedFailed: 0
|
|
2185
2180
|
};
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
try {
|
|
2190
|
-
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
2191
|
-
const employees = await loadEmployees2();
|
|
2192
|
-
const coo = employees.find((e) => e.role === "COO");
|
|
2193
|
-
if (coo) cooAgentId = coo.name.toLowerCase();
|
|
2194
|
-
} catch {
|
|
2181
|
+
const sinceDate = options.since ? new Date(options.since) : void 0;
|
|
2182
|
+
if (sinceDate && isNaN(sinceDate.getTime())) {
|
|
2183
|
+
throw new Error(`Invalid --since date: ${options.since}`);
|
|
2195
2184
|
}
|
|
2196
|
-
process.stderr.write(
|
|
2197
|
-
|
|
2185
|
+
process.stderr.write("[backfill-conversations] Initializing store...\n");
|
|
2186
|
+
await initStore();
|
|
2187
|
+
let daemonConnected = false;
|
|
2198
2188
|
if (!options.dryRun) {
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
sql: "SELECT COUNT(*) as cnt FROM memories WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
|
|
2205
|
-
args: [cooAgentId]
|
|
2206
|
-
});
|
|
2207
|
-
const count = Number(old.rows[0]?.cnt ?? 0);
|
|
2208
|
-
if (count > 0) {
|
|
2209
|
-
await client.execute({
|
|
2210
|
-
sql: "UPDATE memories SET agent_id = ?, agent_role = 'coo' WHERE agent_id = 'backfill' OR (tool_name = 'ConversationBackfill' AND agent_id != ?)",
|
|
2211
|
-
args: [cooAgentId, cooAgentId]
|
|
2212
|
-
});
|
|
2213
|
-
process.stderr.write(`[backfill-conversations] Migrated ${count} records \u2192 agent_id='${cooAgentId}'
|
|
2214
|
-
`);
|
|
2215
|
-
}
|
|
2216
|
-
} catch {
|
|
2189
|
+
daemonConnected = await connectEmbedDaemon();
|
|
2190
|
+
if (!daemonConnected) {
|
|
2191
|
+
process.stderr.write(
|
|
2192
|
+
"[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
|
|
2193
|
+
);
|
|
2217
2194
|
}
|
|
2218
2195
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
"[backfill-conversations] WARNING: Daemon unavailable \u2014 vectors will be NULL (backfill-vectors can fix later)\n"
|
|
2223
|
-
);
|
|
2224
|
-
}
|
|
2225
|
-
let seenHashes = /* @__PURE__ */ new Set();
|
|
2226
|
-
if (!options.dryRun) {
|
|
2227
|
-
process.stderr.write("[backfill-conversations] Loading existing hashes for dedup...\n");
|
|
2228
|
-
seenHashes = await loadExistingHashes(cutoffIso);
|
|
2229
|
-
process.stderr.write(`[backfill-conversations] ${seenHashes.size} existing conversations loaded
|
|
2196
|
+
process.stderr.write("[backfill-conversations] Loading already-ingested conversations...\n");
|
|
2197
|
+
const existingPaths = options.dryRun ? /* @__PURE__ */ new Set() : await loadExistingSourcePaths();
|
|
2198
|
+
process.stderr.write(`[backfill-conversations] ${existingPaths.size} conversations already ingested
|
|
2230
2199
|
`);
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files
|
|
2200
|
+
const files = await findJsonlFiles(sinceDate, options.project);
|
|
2201
|
+
process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
|
|
2234
2202
|
`);
|
|
2203
|
+
process.env.EXE_EMBED_PRIORITY = "low";
|
|
2235
2204
|
for (const file of files) {
|
|
2236
|
-
const pairs = await extractConversationPairs(file, options.projectFilter);
|
|
2237
2205
|
stats.filesScanned++;
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2206
|
+
if (existingPaths.has(file)) {
|
|
2207
|
+
stats.skippedDedup++;
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
const conv = await parseConversation(file);
|
|
2211
|
+
if (conv.totalMessages < MIN_MESSAGES) {
|
|
2212
|
+
stats.skippedTooShort++;
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
const summary = buildSummary(conv);
|
|
2216
|
+
if (options.dryRun) {
|
|
2217
|
+
process.stdout.write(`
|
|
2218
|
+
\u2500\u2500\u2500 ${file} \u2500\u2500\u2500
|
|
2219
|
+
`);
|
|
2220
|
+
process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
|
|
2221
|
+
process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
|
|
2222
|
+
process.stdout.write(` | Files: ${conv.filesTouched.size}
|
|
2223
|
+
`);
|
|
2224
|
+
const firstPrompt = conv.userMessages[0];
|
|
2225
|
+
if (firstPrompt) {
|
|
2226
|
+
process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
|
|
2227
|
+
`);
|
|
2247
2228
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2229
|
+
stats.conversationsStored++;
|
|
2230
|
+
continue;
|
|
2231
|
+
}
|
|
2232
|
+
let vector = null;
|
|
2233
|
+
if (daemonConnected) {
|
|
2234
|
+
try {
|
|
2235
|
+
vector = await embedViaClient(summary, "low");
|
|
2236
|
+
if (!vector) stats.embedFailed++;
|
|
2237
|
+
} catch {
|
|
2238
|
+
stats.embedFailed++;
|
|
2254
2239
|
}
|
|
2255
2240
|
}
|
|
2256
|
-
|
|
2241
|
+
await writeMemory({
|
|
2242
|
+
id: crypto2.randomUUID(),
|
|
2243
|
+
agent_id: conv.agentId,
|
|
2244
|
+
agent_role: conv.agentId === "exe" ? "COO" : "specialist",
|
|
2245
|
+
session_id: conv.sessionId,
|
|
2246
|
+
timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2247
|
+
tool_name: TOOL_NAME,
|
|
2248
|
+
project_name: conv.projectName,
|
|
2249
|
+
has_error: conv.errorCount > 0,
|
|
2250
|
+
raw_text: summary,
|
|
2251
|
+
vector,
|
|
2252
|
+
source_path: file,
|
|
2253
|
+
source_type: "conversation"
|
|
2254
|
+
});
|
|
2255
|
+
existingPaths.add(file);
|
|
2256
|
+
stats.conversationsStored++;
|
|
2257
|
+
if (stats.filesScanned % 50 === 0) {
|
|
2257
2258
|
process.stderr.write(
|
|
2258
2259
|
`[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
|
|
2259
2260
|
`
|
|
2260
2261
|
);
|
|
2261
|
-
|
|
2262
|
+
await flushBatch();
|
|
2262
2263
|
}
|
|
2263
2264
|
}
|
|
2264
|
-
if (!options.dryRun)
|
|
2265
|
+
if (!options.dryRun) {
|
|
2266
|
+
await flushBatch();
|
|
2267
|
+
}
|
|
2265
2268
|
process.stderr.write(
|
|
2266
|
-
`[backfill-conversations] Done.
|
|
2269
|
+
`[backfill-conversations] Done. Scanned: ${stats.filesScanned}, Stored: ${stats.conversationsStored}, Dedup: ${stats.skippedDedup}, TooShort: ${stats.skippedTooShort}, EmbedFail: ${stats.embedFailed}
|
|
2267
2270
|
`
|
|
2268
2271
|
);
|
|
2269
2272
|
return stats;
|
|
2270
2273
|
}
|
|
2271
|
-
function parseArgs(argv) {
|
|
2272
|
-
let days = 30;
|
|
2273
|
-
let projectFilter;
|
|
2274
|
-
let dryRun = false;
|
|
2275
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2276
|
-
switch (argv[i]) {
|
|
2277
|
-
case "--days":
|
|
2278
|
-
days = parseInt(argv[++i] ?? "30", 10) || 30;
|
|
2279
|
-
break;
|
|
2280
|
-
case "--project":
|
|
2281
|
-
projectFilter = argv[++i] ?? void 0;
|
|
2282
|
-
break;
|
|
2283
|
-
case "--dry-run":
|
|
2284
|
-
dryRun = true;
|
|
2285
|
-
break;
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
return { days, projectFilter, dryRun };
|
|
2289
|
-
}
|
|
2290
2274
|
if (isMainModule(import.meta.url)) {
|
|
2291
|
-
const
|
|
2292
|
-
|
|
2275
|
+
const { values } = parseArgs({
|
|
2276
|
+
options: {
|
|
2277
|
+
since: { type: "string" },
|
|
2278
|
+
project: { type: "string" },
|
|
2279
|
+
"dry-run": { type: "boolean", default: false }
|
|
2280
|
+
},
|
|
2281
|
+
strict: true
|
|
2282
|
+
});
|
|
2283
|
+
backfillConversations({
|
|
2284
|
+
since: values.since,
|
|
2285
|
+
project: values.project,
|
|
2286
|
+
dryRun: values["dry-run"]
|
|
2287
|
+
}).then((result) => {
|
|
2293
2288
|
console.log(JSON.stringify(result, null, 2));
|
|
2294
2289
|
process.exit(0);
|
|
2295
2290
|
}).catch((err) => {
|
|
2296
|
-
console.error(
|
|
2297
|
-
"Backfill failed:",
|
|
2298
|
-
err instanceof Error ? err.message : String(err)
|
|
2299
|
-
);
|
|
2291
|
+
console.error("Backfill failed:", err instanceof Error ? err.message : String(err));
|
|
2300
2292
|
process.exit(1);
|
|
2301
2293
|
});
|
|
2302
2294
|
}
|