@dosu/cli 0.53.0-beta.13 → 0.53.0-beta.15
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/bin/dosu.js +624 -417
- package/package.json +1 -1
package/bin/dosu.js
CHANGED
|
@@ -5018,7 +5018,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
|
|
|
5018
5018
|
function getVersionString() {
|
|
5019
5019
|
return `v${VERSION}`;
|
|
5020
5020
|
}
|
|
5021
|
-
var VERSION = "0.53.0-beta.
|
|
5021
|
+
var VERSION = "0.53.0-beta.15", INSTALL_CHANNEL = "npm";
|
|
5022
5022
|
|
|
5023
5023
|
// src/debug/logger.ts
|
|
5024
5024
|
import {
|
|
@@ -8376,16 +8376,164 @@ var init_agents = __esm(() => {
|
|
|
8376
8376
|
init_formats();
|
|
8377
8377
|
});
|
|
8378
8378
|
|
|
8379
|
+
// src/sessions/project-dir.ts
|
|
8380
|
+
import {
|
|
8381
|
+
closeSync as closeSync2,
|
|
8382
|
+
existsSync as existsSync9,
|
|
8383
|
+
mkdirSync as mkdirSync6,
|
|
8384
|
+
openSync as openSync2,
|
|
8385
|
+
readFileSync as readFileSync9,
|
|
8386
|
+
readSync,
|
|
8387
|
+
renameSync as renameSync2,
|
|
8388
|
+
statSync as statSync3,
|
|
8389
|
+
writeFileSync as writeFileSync6
|
|
8390
|
+
} from "node:fs";
|
|
8391
|
+
import { join as join8 } from "node:path";
|
|
8392
|
+
function cachePath(configDir) {
|
|
8393
|
+
return join8(configDir, CACHE_FILENAME2);
|
|
8394
|
+
}
|
|
8395
|
+
function loadCacheFile(configDir) {
|
|
8396
|
+
try {
|
|
8397
|
+
const raw = JSON.parse(readFileSync9(cachePath(configDir), "utf-8"));
|
|
8398
|
+
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
8399
|
+
return {};
|
|
8400
|
+
return raw.entries;
|
|
8401
|
+
} catch {
|
|
8402
|
+
return {};
|
|
8403
|
+
}
|
|
8404
|
+
}
|
|
8405
|
+
function saveCacheFile(configDir, entries) {
|
|
8406
|
+
try {
|
|
8407
|
+
if (!existsSync9(configDir))
|
|
8408
|
+
mkdirSync6(configDir, { recursive: true, mode: 448 });
|
|
8409
|
+
const path3 = cachePath(configDir);
|
|
8410
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8411
|
+
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
8412
|
+
writeFileSync6(tmp, JSON.stringify(file), { mode: 384 });
|
|
8413
|
+
renameSync2(tmp, path3);
|
|
8414
|
+
} catch {}
|
|
8415
|
+
}
|
|
8416
|
+
function readHead(path3, bytes = HEAD_BYTES) {
|
|
8417
|
+
let fd;
|
|
8418
|
+
try {
|
|
8419
|
+
fd = openSync2(path3, "r");
|
|
8420
|
+
} catch {
|
|
8421
|
+
return null;
|
|
8422
|
+
}
|
|
8423
|
+
try {
|
|
8424
|
+
const buf = Buffer.alloc(bytes);
|
|
8425
|
+
const read = readSync(fd, buf, 0, bytes, 0);
|
|
8426
|
+
return buf.subarray(0, read).toString("utf-8");
|
|
8427
|
+
} catch {
|
|
8428
|
+
return null;
|
|
8429
|
+
} finally {
|
|
8430
|
+
closeSync2(fd);
|
|
8431
|
+
}
|
|
8432
|
+
}
|
|
8433
|
+
function cwdFromJsonlHead(text) {
|
|
8434
|
+
for (const line of text.split(`
|
|
8435
|
+
`).slice(0, HEAD_LINES)) {
|
|
8436
|
+
if (!line.includes('"cwd"'))
|
|
8437
|
+
continue;
|
|
8438
|
+
try {
|
|
8439
|
+
const obj = JSON.parse(line);
|
|
8440
|
+
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
8441
|
+
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
8442
|
+
return cwd;
|
|
8443
|
+
} catch {}
|
|
8444
|
+
}
|
|
8445
|
+
return null;
|
|
8446
|
+
}
|
|
8447
|
+
function unmungeSlug(slug, exists = existsSync9) {
|
|
8448
|
+
const tokens = slug.replace(/^-/, "").split("-");
|
|
8449
|
+
if (tokens.length === 0 || tokens[0] === "")
|
|
8450
|
+
return null;
|
|
8451
|
+
let budget = 5000;
|
|
8452
|
+
const walk = (prefix, index) => {
|
|
8453
|
+
if (budget-- <= 0)
|
|
8454
|
+
return null;
|
|
8455
|
+
if (index === tokens.length)
|
|
8456
|
+
return exists(prefix) ? prefix : null;
|
|
8457
|
+
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
8458
|
+
if (asSegment)
|
|
8459
|
+
return asSegment;
|
|
8460
|
+
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
8461
|
+
};
|
|
8462
|
+
return walk("", 0);
|
|
8463
|
+
}
|
|
8464
|
+
function fileMtime(path3) {
|
|
8465
|
+
try {
|
|
8466
|
+
return statSync3(path3).mtime.toISOString();
|
|
8467
|
+
} catch {
|
|
8468
|
+
return "";
|
|
8469
|
+
}
|
|
8470
|
+
}
|
|
8471
|
+
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
8472
|
+
const exists = deps.exists ?? existsSync9;
|
|
8473
|
+
const head = deps.readHead ?? readHead;
|
|
8474
|
+
const mtime = deps.mtime ?? fileMtime;
|
|
8475
|
+
const entries = loadCacheFile(configDir);
|
|
8476
|
+
let dirty = false;
|
|
8477
|
+
const compute = (session) => {
|
|
8478
|
+
switch (session.harness) {
|
|
8479
|
+
case "opencode":
|
|
8480
|
+
return session.project ?? null;
|
|
8481
|
+
case "claude": {
|
|
8482
|
+
const text = head(session.path);
|
|
8483
|
+
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
8484
|
+
if (cwd)
|
|
8485
|
+
return cwd;
|
|
8486
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8487
|
+
}
|
|
8488
|
+
case "codex": {
|
|
8489
|
+
const text = head(session.path);
|
|
8490
|
+
return text ? cwdFromJsonlHead(text) : null;
|
|
8491
|
+
}
|
|
8492
|
+
case "cursor":
|
|
8493
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8494
|
+
default:
|
|
8495
|
+
return null;
|
|
8496
|
+
}
|
|
8497
|
+
};
|
|
8498
|
+
return {
|
|
8499
|
+
cached(key) {
|
|
8500
|
+
return entries[key]?.dir ?? null;
|
|
8501
|
+
},
|
|
8502
|
+
resolve(session) {
|
|
8503
|
+
const key = `${session.harness}/${session.id}`;
|
|
8504
|
+
const cached = entries[key];
|
|
8505
|
+
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
8506
|
+
return cached.dir;
|
|
8507
|
+
}
|
|
8508
|
+
const dir = compute(session);
|
|
8509
|
+
entries[key] = { dir, mtime: mtime(session.path) };
|
|
8510
|
+
dirty = true;
|
|
8511
|
+
return dir;
|
|
8512
|
+
},
|
|
8513
|
+
flush() {
|
|
8514
|
+
if (!dirty)
|
|
8515
|
+
return;
|
|
8516
|
+
saveCacheFile(configDir, entries);
|
|
8517
|
+
dirty = false;
|
|
8518
|
+
}
|
|
8519
|
+
};
|
|
8520
|
+
}
|
|
8521
|
+
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
8522
|
+
var init_project_dir = __esm(() => {
|
|
8523
|
+
init_config();
|
|
8524
|
+
HEAD_BYTES = 128 * 1024;
|
|
8525
|
+
});
|
|
8526
|
+
|
|
8379
8527
|
// src/sessions/scan.ts
|
|
8380
|
-
import { existsSync as
|
|
8528
|
+
import { existsSync as existsSync10, readdirSync, statSync as statSync4 } from "node:fs";
|
|
8381
8529
|
import { createRequire as createRequire2 } from "node:module";
|
|
8382
8530
|
import { homedir as homedir3 } from "node:os";
|
|
8383
|
-
import { basename, join as
|
|
8531
|
+
import { basename, join as join9 } from "node:path";
|
|
8384
8532
|
function listDir(dir) {
|
|
8385
8533
|
try {
|
|
8386
8534
|
return readdirSync(dir, { withFileTypes: true }).map((entry) => ({
|
|
8387
8535
|
name: entry.name,
|
|
8388
|
-
path:
|
|
8536
|
+
path: join9(dir, entry.name),
|
|
8389
8537
|
isDir: entry.isDirectory()
|
|
8390
8538
|
}));
|
|
8391
8539
|
} catch {
|
|
@@ -8398,7 +8546,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8398
8546
|
return null;
|
|
8399
8547
|
let mtime;
|
|
8400
8548
|
try {
|
|
8401
|
-
mtime =
|
|
8549
|
+
mtime = statSync4(filePath).mtime;
|
|
8402
8550
|
} catch {
|
|
8403
8551
|
return null;
|
|
8404
8552
|
}
|
|
@@ -8412,7 +8560,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8412
8560
|
}
|
|
8413
8561
|
function scanClaude(home) {
|
|
8414
8562
|
const sessions = [];
|
|
8415
|
-
for (const project of listDir(
|
|
8563
|
+
for (const project of listDir(join9(home, ".claude", "projects"))) {
|
|
8416
8564
|
if (!project.isDir)
|
|
8417
8565
|
continue;
|
|
8418
8566
|
for (const entry of listDir(project.path)) {
|
|
@@ -8427,10 +8575,10 @@ function scanClaude(home) {
|
|
|
8427
8575
|
}
|
|
8428
8576
|
function scanCursor(home) {
|
|
8429
8577
|
const sessions = [];
|
|
8430
|
-
for (const project of listDir(
|
|
8578
|
+
for (const project of listDir(join9(home, ".cursor", "projects"))) {
|
|
8431
8579
|
if (!project.isDir)
|
|
8432
8580
|
continue;
|
|
8433
|
-
for (const entry of listDir(
|
|
8581
|
+
for (const entry of listDir(join9(project.path, "agent-transcripts"))) {
|
|
8434
8582
|
const files = entry.isDir ? listDir(entry.path).filter((f2) => !f2.isDir) : [entry];
|
|
8435
8583
|
for (const file of files) {
|
|
8436
8584
|
const session = sessionFromFile(file.path, "cursor", project.name);
|
|
@@ -8442,9 +8590,9 @@ function scanCursor(home) {
|
|
|
8442
8590
|
return sessions;
|
|
8443
8591
|
}
|
|
8444
8592
|
function scanCodex(home, env) {
|
|
8445
|
-
const codexHome2 = env.CODEX_HOME ??
|
|
8593
|
+
const codexHome2 = env.CODEX_HOME ?? join9(home, ".codex");
|
|
8446
8594
|
const sessions = [];
|
|
8447
|
-
for (const year of listDir(
|
|
8595
|
+
for (const year of listDir(join9(codexHome2, "sessions"))) {
|
|
8448
8596
|
if (!year.isDir)
|
|
8449
8597
|
continue;
|
|
8450
8598
|
for (const month of listDir(year.path)) {
|
|
@@ -8500,9 +8648,9 @@ function querySqlite(dbPath, sql) {
|
|
|
8500
8648
|
}
|
|
8501
8649
|
}
|
|
8502
8650
|
function scanOpencode(home, env) {
|
|
8503
|
-
const dataDir = env.XDG_DATA_HOME ??
|
|
8504
|
-
const dbPath =
|
|
8505
|
-
if (!
|
|
8651
|
+
const dataDir = env.XDG_DATA_HOME ?? join9(home, ".local", "share");
|
|
8652
|
+
const dbPath = join9(dataDir, "opencode", "opencode.db");
|
|
8653
|
+
if (!existsSync10(dbPath))
|
|
8506
8654
|
return [];
|
|
8507
8655
|
const rows = querySqlite(dbPath, "SELECT id, directory, time_updated FROM session WHERE parent_id IS NULL");
|
|
8508
8656
|
if (!rows)
|
|
@@ -8540,10 +8688,10 @@ function scanAgentSessions(options = {}) {
|
|
|
8540
8688
|
var init_scan = () => {};
|
|
8541
8689
|
|
|
8542
8690
|
// src/sync/watermark.ts
|
|
8543
|
-
import { existsSync as
|
|
8544
|
-
import { join as
|
|
8691
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8692
|
+
import { join as join10 } from "node:path";
|
|
8545
8693
|
function syncStatePath(configDir = getConfigDir()) {
|
|
8546
|
-
return
|
|
8694
|
+
return join10(configDir, STATE_FILENAME);
|
|
8547
8695
|
}
|
|
8548
8696
|
function loadSyncState(configDir = getConfigDir()) {
|
|
8549
8697
|
const empty = {
|
|
@@ -8552,13 +8700,13 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
8552
8700
|
consecutive_failures: 0
|
|
8553
8701
|
};
|
|
8554
8702
|
const path3 = syncStatePath(configDir);
|
|
8555
|
-
if (!
|
|
8703
|
+
if (!existsSync11(path3))
|
|
8556
8704
|
return empty;
|
|
8557
8705
|
try {
|
|
8558
|
-
const raw = JSON.parse(
|
|
8706
|
+
const raw = JSON.parse(readFileSync10(path3, "utf-8"));
|
|
8559
8707
|
if (raw.schema_version !== STATE_SCHEMA_VERSION)
|
|
8560
8708
|
return empty;
|
|
8561
|
-
const
|
|
8709
|
+
const studiedSessions = Array.isArray(raw.mined_sessions) ? raw.mined_sessions.filter((record) => typeof record === "object" && record !== null && typeof record.at === "string" && typeof record.session === "string").map((record) => ({
|
|
8562
8710
|
at: record.at,
|
|
8563
8711
|
session: record.session,
|
|
8564
8712
|
...typeof record.project === "string" ? { project: record.project } : {}
|
|
@@ -8572,8 +8720,8 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
8572
8720
|
watermark: typeof raw.watermark === "string" ? raw.watermark : null,
|
|
8573
8721
|
last_attempt_at: typeof raw.last_attempt_at === "string" ? raw.last_attempt_at : undefined,
|
|
8574
8722
|
consecutive_failures: typeof raw.consecutive_failures === "number" && raw.consecutive_failures >= 0 ? raw.consecutive_failures : 0,
|
|
8575
|
-
mined_sessions:
|
|
8576
|
-
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined :
|
|
8723
|
+
mined_sessions: studiedSessions,
|
|
8724
|
+
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined : studiedSessions.length,
|
|
8577
8725
|
total_notes: typeof raw.total_notes === "number" && raw.total_notes >= 0 ? raw.total_notes : 0,
|
|
8578
8726
|
total_learning_tokens: typeof raw.total_learning_tokens === "number" && raw.total_learning_tokens >= 0 ? raw.total_learning_tokens : 0,
|
|
8579
8727
|
...lastRefusal ? { last_refusal: lastRefusal } : {},
|
|
@@ -8607,12 +8755,12 @@ function resetSyncState(configDir = getConfigDir()) {
|
|
|
8607
8755
|
saveSyncState(fresh, configDir);
|
|
8608
8756
|
}
|
|
8609
8757
|
function saveSyncState(state, configDir = getConfigDir()) {
|
|
8610
|
-
if (!
|
|
8611
|
-
|
|
8758
|
+
if (!existsSync11(configDir))
|
|
8759
|
+
mkdirSync7(configDir, { recursive: true, mode: 448 });
|
|
8612
8760
|
const path3 = syncStatePath(configDir);
|
|
8613
8761
|
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8614
|
-
|
|
8615
|
-
|
|
8762
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
8763
|
+
renameSync3(tmp, path3);
|
|
8616
8764
|
}
|
|
8617
8765
|
function backoffUntil(state) {
|
|
8618
8766
|
if (state.consecutive_failures === 0 || !state.last_attempt_at)
|
|
@@ -8656,7 +8804,7 @@ function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAU
|
|
|
8656
8804
|
}
|
|
8657
8805
|
return { ready, open: open2 };
|
|
8658
8806
|
}
|
|
8659
|
-
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS,
|
|
8807
|
+
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, STUDIED_HISTORY_LIMIT = 500, UNKNOWN_PROJECT = "(unknown)";
|
|
8660
8808
|
var init_watermark = __esm(() => {
|
|
8661
8809
|
init_config();
|
|
8662
8810
|
DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
|
|
@@ -8672,20 +8820,34 @@ async function fetchReportNotes(cfg) {
|
|
|
8672
8820
|
}
|
|
8673
8821
|
const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
|
|
8674
8822
|
const client = createTypedClient2(cfg);
|
|
8675
|
-
const
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
...
|
|
8685
|
-
|
|
8686
|
-
|
|
8823
|
+
const notes = [];
|
|
8824
|
+
let cursor3;
|
|
8825
|
+
let truncated = false;
|
|
8826
|
+
for (let page = 0;page < REPORT_NOTES_MAX_PAGES; page++) {
|
|
8827
|
+
const result = await client.notes.listMine.query({
|
|
8828
|
+
org_id: orgId,
|
|
8829
|
+
limit: REPORT_NOTES_LIMIT,
|
|
8830
|
+
...cursor3 ? { cursor: cursor3 } : {}
|
|
8831
|
+
});
|
|
8832
|
+
notes.push(...result.notes.map((note) => ({
|
|
8833
|
+
title: note.title,
|
|
8834
|
+
content: note.body,
|
|
8835
|
+
at: note.created_at,
|
|
8836
|
+
...note.transcript_id ? { transcript_id: note.transcript_id } : {},
|
|
8837
|
+
...note.repo ? { repo: note.repo } : {},
|
|
8838
|
+
...note.branch ? { branch: note.branch } : {}
|
|
8839
|
+
})));
|
|
8840
|
+
cursor3 = result.next_cursor;
|
|
8841
|
+
if (!cursor3) {
|
|
8842
|
+
truncated = result.notes.length === REPORT_NOTES_LIMIT;
|
|
8843
|
+
break;
|
|
8844
|
+
}
|
|
8845
|
+
if (page === REPORT_NOTES_MAX_PAGES - 1)
|
|
8846
|
+
truncated = true;
|
|
8847
|
+
}
|
|
8848
|
+
return { notes, truncated };
|
|
8687
8849
|
}
|
|
8688
|
-
var REPORT_NOTES_LIMIT = 500;
|
|
8850
|
+
var REPORT_NOTES_LIMIT = 500, REPORT_NOTES_MAX_PAGES = 40;
|
|
8689
8851
|
|
|
8690
8852
|
// src/report/css.ts
|
|
8691
8853
|
var REPORT_CSS = `
|
|
@@ -8860,38 +9022,6 @@ function applyStatusDefaults(candidates, dryRun) {
|
|
|
8860
9022
|
return { ...c2, status: known.has(current) ? current : fallback };
|
|
8861
9023
|
});
|
|
8862
9024
|
}
|
|
8863
|
-
function tokenTotalsFromCandidates(candidates, inventory) {
|
|
8864
|
-
if (candidates.length === 0)
|
|
8865
|
-
return null;
|
|
8866
|
-
let replaced = 0;
|
|
8867
|
-
let hasRediscovery = false;
|
|
8868
|
-
let readCost = 0;
|
|
8869
|
-
for (const c2 of candidates) {
|
|
8870
|
-
const raw = c2.approx_rediscovery_tokens;
|
|
8871
|
-
if (raw != null) {
|
|
8872
|
-
hasRediscovery = true;
|
|
8873
|
-
replaced += Math.max(0, Number(raw) || 0);
|
|
8874
|
-
}
|
|
8875
|
-
const blob = `${c2.title ?? ""}
|
|
8876
|
-
${c2.content ?? ""}`;
|
|
8877
|
-
readCost += Math.round(blob.length / CHARS_PER_TOKEN);
|
|
8878
|
-
}
|
|
8879
|
-
if (!hasRediscovery)
|
|
8880
|
-
return null;
|
|
8881
|
-
let baseline = inventory.totals?.learning_tokens ?? 0;
|
|
8882
|
-
if (!baseline) {
|
|
8883
|
-
baseline = inventory.transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
8884
|
-
}
|
|
8885
|
-
const saved = Math.max(0, replaced - readCost);
|
|
8886
|
-
const pct = baseline ? Math.round(1000 * saved / baseline) / 10 : 0;
|
|
8887
|
-
return {
|
|
8888
|
-
baseline_tokens: baseline,
|
|
8889
|
-
replaced_baseline_tokens: replaced,
|
|
8890
|
-
read_knowledge_tokens: readCost,
|
|
8891
|
-
tokens_saved: saved,
|
|
8892
|
-
pct_saved: pct
|
|
8893
|
-
};
|
|
8894
|
-
}
|
|
8895
9025
|
function presentationCopy(c2) {
|
|
8896
9026
|
const idea = (c2.plain_english || "").trim() || (c2.content || "").trim();
|
|
8897
9027
|
const how = (c2.how_found || "").trim();
|
|
@@ -9265,11 +9395,9 @@ function buildReportHtml(options) {
|
|
|
9265
9395
|
return bv - av;
|
|
9266
9396
|
});
|
|
9267
9397
|
const org = options.orgName || "Your team";
|
|
9268
|
-
const
|
|
9269
|
-
const branch = options.branch || "—";
|
|
9398
|
+
const projects = options.projects ?? [];
|
|
9270
9399
|
const summary = options.summary || "Local agent session logs were studied and distilled into Dosu notes so the next task can reuse them — reducing rediscovery cost.";
|
|
9271
9400
|
const generated = `${(options.generatedAt ?? new Date).toISOString().replace("T", " ").slice(0, 16)} UTC`;
|
|
9272
|
-
const derived = tokenTotalsFromCandidates(candidates, inventory);
|
|
9273
9401
|
const [notesHeading, notesLede, footerTitle, footerBody] = notesSectionCopy(candidates);
|
|
9274
9402
|
const candidateRows = candidates.map((c2, i2) => {
|
|
9275
9403
|
const [idea, work] = presentationCopy(c2);
|
|
@@ -9309,26 +9437,18 @@ function buildReportHtml(options) {
|
|
|
9309
9437
|
<td>${esc(query)}</td>
|
|
9310
9438
|
</tr>`;
|
|
9311
9439
|
}).join("");
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9440
|
+
const scannedTokens = inventory.totals?.learning_tokens || transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
9441
|
+
const writtenCount = candidates.filter((c2) => (c2.status || "written").toLowerCase() === "written").length;
|
|
9442
|
+
const writtenLabel = options.dryRun ? "Notes proposed (dry run)" : "Notes written to Dosu";
|
|
9443
|
+
const writtenValue = options.dryRun ? candidates.length : writtenCount;
|
|
9444
|
+
const tokenSection = `
|
|
9315
9445
|
<section>
|
|
9316
|
-
<h2>
|
|
9317
|
-
<p class="lede">Counterfactual: replace rediscovery stretches with a Dosu <code>read_knowledge</code> hit.</p>
|
|
9446
|
+
<h2>This run</h2>
|
|
9318
9447
|
<div class="stats">
|
|
9319
|
-
<div class="stat"><div class="label">
|
|
9320
|
-
<div class="stat"><div class="label"
|
|
9321
|
-
<div class="stat"><div class="label">Read cost</div><div class="value">${fmtInt(derived.read_knowledge_tokens)}</div></div>
|
|
9322
|
-
<div class="stat highlight"><div class="label">Est. tokens saved</div><div class="value">${fmtInt(derived.tokens_saved)} <span class="pct">(${esc(`${derived.pct_saved}%`)})</span></div></div>
|
|
9448
|
+
<div class="stat"><div class="label">Tokens scanned</div><div class="value">${fmtInt(scannedTokens)}</div></div>
|
|
9449
|
+
<div class="stat highlight"><div class="label">${esc(writtenLabel)}</div><div class="value">${fmtInt(writtenValue)}</div></div>
|
|
9323
9450
|
</div>
|
|
9324
9451
|
</section>`;
|
|
9325
|
-
} else {
|
|
9326
|
-
tokenSection = `
|
|
9327
|
-
<section>
|
|
9328
|
-
<h2>Estimated context savings</h2>
|
|
9329
|
-
<p class="muted">No rediscovery estimates on the notes yet — each candidate needs <code>approx_rediscovery_tokens</code>.</p>
|
|
9330
|
-
</section>`;
|
|
9331
|
-
}
|
|
9332
9452
|
const notesBody = candidateRows.join("") || '<p class="muted">No write_knowledge payloads yet. Run knowledge sync so Dosu studies your sessions and extracts learnings.</p>';
|
|
9333
9453
|
const lede = notesLede ? `<p class="lede">${esc(notesLede)}</p>` : "";
|
|
9334
9454
|
return `<!DOCTYPE html>
|
|
@@ -9348,9 +9468,8 @@ ${REPORT_CSS}
|
|
|
9348
9468
|
<h1>${esc(org)}</h1>
|
|
9349
9469
|
<p class="lede">${esc(summary)}</p>
|
|
9350
9470
|
<p class="meta-line">
|
|
9351
|
-
|
|
9352
|
-
·
|
|
9353
|
-
· generated ${esc(generated)}
|
|
9471
|
+
${projects.length > 0 ? `Projects ${projects.map((p2) => `<code>${esc(p2)}</code>`).join(", ")} · ` : ""}${transcripts.length} session${transcripts.length === 1 ? "" : "s"} with notes
|
|
9472
|
+
· generated ${esc(generated)}${options.truncated ? " · newest notes only (server capped the fetch)" : ""}
|
|
9354
9473
|
</p>
|
|
9355
9474
|
<div class="toolbar no-print">
|
|
9356
9475
|
<button type="button" onclick="window.print()">Print / Save as PDF</button>
|
|
@@ -9396,7 +9515,7 @@ ${REPORT_CSS}
|
|
|
9396
9515
|
</html>
|
|
9397
9516
|
`;
|
|
9398
9517
|
}
|
|
9399
|
-
var
|
|
9518
|
+
var MAX_TRACE_STEPS = 50, PREVIEW_CHARS = 80, SQL_DISPLAY, LOGS_DISPLAY, SKIP_TOOL_NAMES, TAG_RE, WS_RE;
|
|
9400
9519
|
var init_html = __esm(() => {
|
|
9401
9520
|
SQL_DISPLAY = new Set(["execute_sql", "query_run", "list_tables"]);
|
|
9402
9521
|
LOGS_DISPLAY = new Set(["query_logs"]);
|
|
@@ -9406,7 +9525,7 @@ var init_html = __esm(() => {
|
|
|
9406
9525
|
});
|
|
9407
9526
|
|
|
9408
9527
|
// src/sessions/read.ts
|
|
9409
|
-
import { readFileSync as
|
|
9528
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
9410
9529
|
function asRecord(value) {
|
|
9411
9530
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9412
9531
|
}
|
|
@@ -9522,11 +9641,11 @@ function readSessionTurns(session) {
|
|
|
9522
9641
|
try {
|
|
9523
9642
|
switch (session.harness) {
|
|
9524
9643
|
case "claude":
|
|
9525
|
-
return readClaude(
|
|
9644
|
+
return readClaude(readFileSync11(session.path, "utf8"));
|
|
9526
9645
|
case "cursor":
|
|
9527
|
-
return readCursor(
|
|
9646
|
+
return readCursor(readFileSync11(session.path, "utf8"));
|
|
9528
9647
|
case "codex":
|
|
9529
|
-
return readCodex(
|
|
9648
|
+
return readCodex(readFileSync11(session.path, "utf8"));
|
|
9530
9649
|
case "opencode":
|
|
9531
9650
|
return readOpencode(session.path, session.id);
|
|
9532
9651
|
}
|
|
@@ -9554,7 +9673,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9554
9673
|
case "claude":
|
|
9555
9674
|
case "cursor": {
|
|
9556
9675
|
let n2 = 0;
|
|
9557
|
-
for (const record of jsonlRecords(
|
|
9676
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9558
9677
|
const message = asRecord(record.message);
|
|
9559
9678
|
for (const name of toolNamesFromContent(message?.content)) {
|
|
9560
9679
|
if (isRediscoveryTool(name))
|
|
@@ -9565,7 +9684,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9565
9684
|
}
|
|
9566
9685
|
case "codex": {
|
|
9567
9686
|
let n2 = 0;
|
|
9568
|
-
for (const record of jsonlRecords(
|
|
9687
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9569
9688
|
const payload = asRecord(record.payload);
|
|
9570
9689
|
if (payload?.type === "function_call" && typeof payload.name === "string") {
|
|
9571
9690
|
if (isRediscoveryTool(payload.name))
|
|
@@ -9585,9 +9704,9 @@ function estimateSessionTokens(session) {
|
|
|
9585
9704
|
let chars = 0;
|
|
9586
9705
|
for (const turn of readSessionTurns(session))
|
|
9587
9706
|
chars += turn.text.length;
|
|
9588
|
-
return Math.round(chars /
|
|
9707
|
+
return Math.round(chars / CHARS_PER_TOKEN);
|
|
9589
9708
|
}
|
|
9590
|
-
function
|
|
9709
|
+
function isWorthStudying(session) {
|
|
9591
9710
|
const turns = readSessionTurns(session);
|
|
9592
9711
|
if (turns.length < MIN_WORTH_TURNS)
|
|
9593
9712
|
return false;
|
|
@@ -9599,7 +9718,7 @@ function isWorthMining(session) {
|
|
|
9599
9718
|
}
|
|
9600
9719
|
return false;
|
|
9601
9720
|
}
|
|
9602
|
-
var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS,
|
|
9721
|
+
var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS, CHARS_PER_TOKEN = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
|
|
9603
9722
|
var init_read = __esm(() => {
|
|
9604
9723
|
init_scan();
|
|
9605
9724
|
PLAIN_TEXT = new Set(["text"]);
|
|
@@ -9656,12 +9775,12 @@ var init_queries = __esm(() => {
|
|
|
9656
9775
|
});
|
|
9657
9776
|
|
|
9658
9777
|
// src/report/digest.ts
|
|
9659
|
-
import { readFileSync as
|
|
9778
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
9660
9779
|
function asRecord2(value) {
|
|
9661
9780
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9662
9781
|
}
|
|
9663
9782
|
function estimateTokens(text) {
|
|
9664
|
-
return Math.round(text.length /
|
|
9783
|
+
return Math.round(text.length / CHARS_PER_TOKEN2);
|
|
9665
9784
|
}
|
|
9666
9785
|
function digestEstTokens(texts, tools, resultChars) {
|
|
9667
9786
|
const countable = tools.filter((t2) => t2.name !== "tool_result");
|
|
@@ -9671,7 +9790,7 @@ function digestEstTokens(texts, tools, resultChars) {
|
|
|
9671
9790
|
payload += JSON.stringify(countable);
|
|
9672
9791
|
let tokens = estimateTokens(payload);
|
|
9673
9792
|
if (resultChars > 0)
|
|
9674
|
-
tokens += Math.max(1, Math.round(resultChars /
|
|
9793
|
+
tokens += Math.max(1, Math.round(resultChars / CHARS_PER_TOKEN2));
|
|
9675
9794
|
return tokens;
|
|
9676
9795
|
}
|
|
9677
9796
|
function copyToolInputFields(entry, inp) {
|
|
@@ -9887,16 +10006,16 @@ function sessionToDigest(session) {
|
|
|
9887
10006
|
try {
|
|
9888
10007
|
switch (session.harness) {
|
|
9889
10008
|
case "claude":
|
|
9890
|
-
return { turns: digestClaude(
|
|
10009
|
+
return { turns: digestClaude(readFileSync12(session.path, "utf8")) };
|
|
9891
10010
|
case "cursor":
|
|
9892
|
-
return { turns: digestCursor(
|
|
10011
|
+
return { turns: digestCursor(readFileSync12(session.path, "utf8")) };
|
|
9893
10012
|
case "codex":
|
|
9894
|
-
return { turns: digestCodex(
|
|
10013
|
+
return { turns: digestCodex(readFileSync12(session.path, "utf8")) };
|
|
9895
10014
|
case "opencode": {
|
|
9896
10015
|
const turns = readSessionTurns(session).map((turn, index) => ({
|
|
9897
10016
|
role: turn.role,
|
|
9898
10017
|
line: index + 1,
|
|
9899
|
-
est_tokens: Math.round(turn.text.length /
|
|
10018
|
+
est_tokens: Math.round(turn.text.length / CHARS_PER_TOKEN2),
|
|
9900
10019
|
text: [turn.text],
|
|
9901
10020
|
tools: []
|
|
9902
10021
|
}));
|
|
@@ -9921,7 +10040,7 @@ function digestTurnText(turn) {
|
|
|
9921
10040
|
return texts.filter(Boolean).join(`
|
|
9922
10041
|
`);
|
|
9923
10042
|
}
|
|
9924
|
-
var
|
|
10043
|
+
var CHARS_PER_TOKEN2 = 4, MAX_ASSISTANT_CHARS = 4000, MCP_DIGEST_TOOLS;
|
|
9925
10044
|
var init_digest = __esm(() => {
|
|
9926
10045
|
init_read();
|
|
9927
10046
|
init_queries();
|
|
@@ -10112,17 +10231,17 @@ var init_notes = __esm(() => {
|
|
|
10112
10231
|
});
|
|
10113
10232
|
|
|
10114
10233
|
// src/report/write.ts
|
|
10115
|
-
import { mkdirSync as
|
|
10234
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10116
10235
|
import { tmpdir } from "node:os";
|
|
10117
|
-
import { dirname as dirname4, join as
|
|
10236
|
+
import { dirname as dirname4, join as join11, resolve } from "node:path";
|
|
10118
10237
|
import { pathToFileURL } from "node:url";
|
|
10119
10238
|
function defaultReportPath() {
|
|
10120
|
-
return
|
|
10239
|
+
return join11(tmpdir(), "dosu-knowledge-report.html");
|
|
10121
10240
|
}
|
|
10122
10241
|
async function writeAndOpenReport(options) {
|
|
10123
10242
|
const out = resolve(options.out ?? defaultReportPath());
|
|
10124
|
-
|
|
10125
|
-
|
|
10243
|
+
mkdirSync8(dirname4(out), { recursive: true });
|
|
10244
|
+
writeFileSync8(out, options.html, "utf8");
|
|
10126
10245
|
if (options.open !== false) {
|
|
10127
10246
|
const url = pathToFileURL(out).href;
|
|
10128
10247
|
if (options.openUrl) {
|
|
@@ -10137,17 +10256,26 @@ async function writeAndOpenReport(options) {
|
|
|
10137
10256
|
var init_write = () => {};
|
|
10138
10257
|
|
|
10139
10258
|
// src/report/generate.ts
|
|
10259
|
+
import { basename as basename2 } from "node:path";
|
|
10140
10260
|
function sessionsForNotes(notes, sessions) {
|
|
10141
10261
|
const ids = new Set(notes.map((n2) => n2.transcript_id).filter((id) => Boolean(id)));
|
|
10142
10262
|
return sessions.filter((s) => ids.has(s.id));
|
|
10143
10263
|
}
|
|
10264
|
+
function defaultProjectName() {
|
|
10265
|
+
const resolver = createProjectDirResolver();
|
|
10266
|
+
return (session) => {
|
|
10267
|
+
const dir = resolver.resolve(session);
|
|
10268
|
+
return dir ? basename2(dir) : null;
|
|
10269
|
+
};
|
|
10270
|
+
}
|
|
10144
10271
|
function noteTime(note) {
|
|
10145
10272
|
const parsed = Date.parse(note.at ?? "");
|
|
10146
10273
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10147
10274
|
}
|
|
10148
10275
|
async function emitKnowledgeReport(options = {}) {
|
|
10149
10276
|
const cfg = loadConfig();
|
|
10150
|
-
const
|
|
10277
|
+
const fetched = options.notes ? { notes: options.notes, truncated: false } : await (options.fetchNotes ?? fetchReportNotes)(cfg);
|
|
10278
|
+
const notes = [...fetched.notes].sort((a3, b2) => noteTime(a3) - noteTime(b2));
|
|
10151
10279
|
const scanned = options.sessions ?? scanAgentSessions();
|
|
10152
10280
|
const sessions = sessionsForNotes(notes, scanned);
|
|
10153
10281
|
const candidates = attributeRediscovery(notes, sessions);
|
|
@@ -10156,12 +10284,16 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10156
10284
|
if (inventory.totals && state.total_learning_tokens && !options.sessions) {
|
|
10157
10285
|
inventory.totals.learning_tokens = state.total_learning_tokens;
|
|
10158
10286
|
}
|
|
10287
|
+
const projectName = options.projectName ?? defaultProjectName();
|
|
10288
|
+
const projects = [
|
|
10289
|
+
...new Set(sessions.map((s) => projectName(s) ?? s.project).filter((name) => Boolean(name)))
|
|
10290
|
+
];
|
|
10159
10291
|
const html = buildReportHtml({
|
|
10160
10292
|
inventory,
|
|
10161
10293
|
candidates,
|
|
10162
10294
|
orgName: options.orgName ?? cfg.active_account?.target?.org_name ?? "Your team",
|
|
10163
|
-
|
|
10164
|
-
|
|
10295
|
+
projects,
|
|
10296
|
+
truncated: fetched.truncated,
|
|
10165
10297
|
generatedAt: options.generatedAt,
|
|
10166
10298
|
digests: digestsForSessions(sessions)
|
|
10167
10299
|
});
|
|
@@ -10174,6 +10306,7 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10174
10306
|
}
|
|
10175
10307
|
var init_generate = __esm(() => {
|
|
10176
10308
|
init_config();
|
|
10309
|
+
init_project_dir();
|
|
10177
10310
|
init_scan();
|
|
10178
10311
|
init_watermark();
|
|
10179
10312
|
init_html();
|
|
@@ -10181,151 +10314,6 @@ var init_generate = __esm(() => {
|
|
|
10181
10314
|
init_write();
|
|
10182
10315
|
});
|
|
10183
10316
|
|
|
10184
|
-
// src/sessions/project-dir.ts
|
|
10185
|
-
import {
|
|
10186
|
-
closeSync as closeSync2,
|
|
10187
|
-
existsSync as existsSync11,
|
|
10188
|
-
mkdirSync as mkdirSync8,
|
|
10189
|
-
openSync as openSync2,
|
|
10190
|
-
readFileSync as readFileSync12,
|
|
10191
|
-
readSync,
|
|
10192
|
-
renameSync as renameSync3,
|
|
10193
|
-
statSync as statSync4,
|
|
10194
|
-
writeFileSync as writeFileSync8
|
|
10195
|
-
} from "node:fs";
|
|
10196
|
-
import { join as join11 } from "node:path";
|
|
10197
|
-
function cachePath(configDir) {
|
|
10198
|
-
return join11(configDir, CACHE_FILENAME2);
|
|
10199
|
-
}
|
|
10200
|
-
function loadCacheFile(configDir) {
|
|
10201
|
-
try {
|
|
10202
|
-
const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
|
|
10203
|
-
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
10204
|
-
return {};
|
|
10205
|
-
return raw.entries;
|
|
10206
|
-
} catch {
|
|
10207
|
-
return {};
|
|
10208
|
-
}
|
|
10209
|
-
}
|
|
10210
|
-
function saveCacheFile(configDir, entries) {
|
|
10211
|
-
try {
|
|
10212
|
-
if (!existsSync11(configDir))
|
|
10213
|
-
mkdirSync8(configDir, { recursive: true, mode: 448 });
|
|
10214
|
-
const path3 = cachePath(configDir);
|
|
10215
|
-
const tmp = `${path3}.${process.pid}.tmp`;
|
|
10216
|
-
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
10217
|
-
writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
|
|
10218
|
-
renameSync3(tmp, path3);
|
|
10219
|
-
} catch {}
|
|
10220
|
-
}
|
|
10221
|
-
function readHead(path3, bytes = HEAD_BYTES) {
|
|
10222
|
-
let fd;
|
|
10223
|
-
try {
|
|
10224
|
-
fd = openSync2(path3, "r");
|
|
10225
|
-
} catch {
|
|
10226
|
-
return null;
|
|
10227
|
-
}
|
|
10228
|
-
try {
|
|
10229
|
-
const buf = Buffer.alloc(bytes);
|
|
10230
|
-
const read = readSync(fd, buf, 0, bytes, 0);
|
|
10231
|
-
return buf.subarray(0, read).toString("utf-8");
|
|
10232
|
-
} catch {
|
|
10233
|
-
return null;
|
|
10234
|
-
} finally {
|
|
10235
|
-
closeSync2(fd);
|
|
10236
|
-
}
|
|
10237
|
-
}
|
|
10238
|
-
function cwdFromJsonlHead(text) {
|
|
10239
|
-
for (const line of text.split(`
|
|
10240
|
-
`).slice(0, HEAD_LINES)) {
|
|
10241
|
-
if (!line.includes('"cwd"'))
|
|
10242
|
-
continue;
|
|
10243
|
-
try {
|
|
10244
|
-
const obj = JSON.parse(line);
|
|
10245
|
-
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
10246
|
-
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
10247
|
-
return cwd;
|
|
10248
|
-
} catch {}
|
|
10249
|
-
}
|
|
10250
|
-
return null;
|
|
10251
|
-
}
|
|
10252
|
-
function unmungeSlug(slug, exists = existsSync11) {
|
|
10253
|
-
const tokens = slug.replace(/^-/, "").split("-");
|
|
10254
|
-
if (tokens.length === 0 || tokens[0] === "")
|
|
10255
|
-
return null;
|
|
10256
|
-
let budget = 5000;
|
|
10257
|
-
const walk = (prefix, index) => {
|
|
10258
|
-
if (budget-- <= 0)
|
|
10259
|
-
return null;
|
|
10260
|
-
if (index === tokens.length)
|
|
10261
|
-
return exists(prefix) ? prefix : null;
|
|
10262
|
-
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
10263
|
-
if (asSegment)
|
|
10264
|
-
return asSegment;
|
|
10265
|
-
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
10266
|
-
};
|
|
10267
|
-
return walk("", 0);
|
|
10268
|
-
}
|
|
10269
|
-
function fileMtime(path3) {
|
|
10270
|
-
try {
|
|
10271
|
-
return statSync4(path3).mtime.toISOString();
|
|
10272
|
-
} catch {
|
|
10273
|
-
return "";
|
|
10274
|
-
}
|
|
10275
|
-
}
|
|
10276
|
-
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
10277
|
-
const exists = deps.exists ?? existsSync11;
|
|
10278
|
-
const head = deps.readHead ?? readHead;
|
|
10279
|
-
const mtime = deps.mtime ?? fileMtime;
|
|
10280
|
-
const entries = loadCacheFile(configDir);
|
|
10281
|
-
let dirty = false;
|
|
10282
|
-
const compute = (session) => {
|
|
10283
|
-
switch (session.harness) {
|
|
10284
|
-
case "opencode":
|
|
10285
|
-
return session.project ?? null;
|
|
10286
|
-
case "claude": {
|
|
10287
|
-
const text = head(session.path);
|
|
10288
|
-
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
10289
|
-
if (cwd)
|
|
10290
|
-
return cwd;
|
|
10291
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10292
|
-
}
|
|
10293
|
-
case "codex": {
|
|
10294
|
-
const text = head(session.path);
|
|
10295
|
-
return text ? cwdFromJsonlHead(text) : null;
|
|
10296
|
-
}
|
|
10297
|
-
case "cursor":
|
|
10298
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10299
|
-
default:
|
|
10300
|
-
return null;
|
|
10301
|
-
}
|
|
10302
|
-
};
|
|
10303
|
-
return {
|
|
10304
|
-
resolve(session) {
|
|
10305
|
-
const key = `${session.harness}/${session.id}`;
|
|
10306
|
-
const cached = entries[key];
|
|
10307
|
-
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
10308
|
-
return cached.dir;
|
|
10309
|
-
}
|
|
10310
|
-
const dir = compute(session);
|
|
10311
|
-
entries[key] = { dir, mtime: mtime(session.path) };
|
|
10312
|
-
dirty = true;
|
|
10313
|
-
return dir;
|
|
10314
|
-
},
|
|
10315
|
-
flush() {
|
|
10316
|
-
if (!dirty)
|
|
10317
|
-
return;
|
|
10318
|
-
saveCacheFile(configDir, entries);
|
|
10319
|
-
dirty = false;
|
|
10320
|
-
}
|
|
10321
|
-
};
|
|
10322
|
-
}
|
|
10323
|
-
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
10324
|
-
var init_project_dir = __esm(() => {
|
|
10325
|
-
init_config();
|
|
10326
|
-
HEAD_BYTES = 128 * 1024;
|
|
10327
|
-
});
|
|
10328
|
-
|
|
10329
10317
|
// src/sync/backlog.ts
|
|
10330
10318
|
function listSessionBacklog() {
|
|
10331
10319
|
try {
|
|
@@ -10470,7 +10458,7 @@ function readLockFile(configDir) {
|
|
|
10470
10458
|
}
|
|
10471
10459
|
function recentSyncActivity(log3) {
|
|
10472
10460
|
return log3.split(`
|
|
10473
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[
|
|
10461
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[learner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
10474
10462
|
}
|
|
10475
10463
|
function getSyncStatus(deps = {}) {
|
|
10476
10464
|
const configDir = deps.configDir ?? getConfigDir();
|
|
@@ -10591,14 +10579,14 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10591
10579
|
baseline_mined: state.total_mined ?? 0
|
|
10592
10580
|
};
|
|
10593
10581
|
}
|
|
10594
|
-
const
|
|
10582
|
+
const worthStudying = deps.worthStudying ?? isWorthStudying;
|
|
10595
10583
|
const examined = [];
|
|
10596
10584
|
const batch = [];
|
|
10597
10585
|
let trivial = 0;
|
|
10598
10586
|
for (let i2 = ready.length - 1;i2 >= 0 && batch.length < MINE_BATCH_LIMIT; i2--) {
|
|
10599
10587
|
const candidate = ready[i2];
|
|
10600
10588
|
examined.push(candidate);
|
|
10601
|
-
if (
|
|
10589
|
+
if (worthStudying(candidate)) {
|
|
10602
10590
|
batch.push(candidate);
|
|
10603
10591
|
} else {
|
|
10604
10592
|
trivial += 1;
|
|
@@ -10615,26 +10603,26 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10615
10603
|
return {
|
|
10616
10604
|
status: "nothing-new",
|
|
10617
10605
|
...base,
|
|
10618
|
-
|
|
10606
|
+
studiedSessions: 0,
|
|
10619
10607
|
trivialSessions: trivial
|
|
10620
10608
|
};
|
|
10621
10609
|
}
|
|
10622
10610
|
logger.debug("sync", `studying ${batch.length} of ${ready.length} ready sessions (${trivial} trivial skipped)`);
|
|
10623
|
-
const
|
|
10624
|
-
switch (
|
|
10611
|
+
const learner = await deps.mine(batch);
|
|
10612
|
+
switch (learner.outcome) {
|
|
10625
10613
|
case "completed": {
|
|
10626
10614
|
for (const s of batch) {
|
|
10627
10615
|
logger.debug("sync", `studied session ${s.harness}/${s.id}`);
|
|
10628
10616
|
}
|
|
10629
|
-
const
|
|
10617
|
+
const studiedAt = now().toISOString();
|
|
10630
10618
|
const history = [
|
|
10631
10619
|
...state.mined_sessions ?? [],
|
|
10632
10620
|
...batch.map((s) => ({
|
|
10633
|
-
at:
|
|
10621
|
+
at: studiedAt,
|
|
10634
10622
|
session: `${s.harness}/${s.id}`,
|
|
10635
10623
|
...s.project ? { project: s.project } : {}
|
|
10636
10624
|
}))
|
|
10637
|
-
].slice(-
|
|
10625
|
+
].slice(-STUDIED_HISTORY_LIMIT);
|
|
10638
10626
|
const watermark = batchWatermark(examined);
|
|
10639
10627
|
const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
|
|
10640
10628
|
let batchTokens = 0;
|
|
@@ -10643,21 +10631,21 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10643
10631
|
saveState({
|
|
10644
10632
|
...state,
|
|
10645
10633
|
watermark,
|
|
10646
|
-
last_attempt_at:
|
|
10634
|
+
last_attempt_at: studiedAt,
|
|
10647
10635
|
consecutive_failures: 0,
|
|
10648
10636
|
mined_sessions: history,
|
|
10649
10637
|
total_mined: (state.total_mined ?? 0) + batch.length,
|
|
10650
|
-
total_notes: (state.total_notes ?? 0) +
|
|
10638
|
+
total_notes: (state.total_notes ?? 0) + learner.notesWritten,
|
|
10651
10639
|
total_learning_tokens: (state.total_learning_tokens ?? 0) + batchTokens,
|
|
10652
10640
|
last_refusal: undefined
|
|
10653
10641
|
});
|
|
10654
|
-
logger.debug("sync", `studied ${batch.length} sessions, ${
|
|
10642
|
+
logger.debug("sync", `studied ${batch.length} sessions, ${learner.notesWritten} suggested pages; watermark → ${watermark}`);
|
|
10655
10643
|
return {
|
|
10656
|
-
status: "
|
|
10644
|
+
status: "studied",
|
|
10657
10645
|
...base,
|
|
10658
|
-
|
|
10646
|
+
studiedSessions: batch.length,
|
|
10659
10647
|
trivialSessions: trivial,
|
|
10660
|
-
|
|
10648
|
+
learner
|
|
10661
10649
|
};
|
|
10662
10650
|
}
|
|
10663
10651
|
case "consent_off":
|
|
@@ -10670,12 +10658,12 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10670
10658
|
consecutive_failures: 0,
|
|
10671
10659
|
last_refusal: {
|
|
10672
10660
|
at,
|
|
10673
|
-
outcome:
|
|
10674
|
-
message:
|
|
10661
|
+
outcome: learner.outcome,
|
|
10662
|
+
message: learner.message ?? "Studying unavailable right now."
|
|
10675
10663
|
}
|
|
10676
10664
|
});
|
|
10677
|
-
logger.debug("sync", `studying skipped by gateway: ${
|
|
10678
|
-
return { status: "skipped-gateway", ...base,
|
|
10665
|
+
logger.debug("sync", `studying skipped by gateway: ${learner.outcome}`);
|
|
10666
|
+
return { status: "skipped-gateway", ...base, studiedSessions: 0, learner };
|
|
10679
10667
|
}
|
|
10680
10668
|
default: {
|
|
10681
10669
|
saveState({
|
|
@@ -10683,13 +10671,13 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10683
10671
|
last_attempt_at: now().toISOString(),
|
|
10684
10672
|
consecutive_failures: state.consecutive_failures + 1
|
|
10685
10673
|
});
|
|
10686
|
-
logger.debug("sync", `studying failed: ${
|
|
10674
|
+
logger.debug("sync", `studying failed: ${learner.outcome}; ${learner.message ?? ""}`);
|
|
10687
10675
|
return {
|
|
10688
10676
|
status: "mine-failed",
|
|
10689
10677
|
...base,
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
error:
|
|
10678
|
+
studiedSessions: 0,
|
|
10679
|
+
learner,
|
|
10680
|
+
error: learner.message
|
|
10693
10681
|
};
|
|
10694
10682
|
}
|
|
10695
10683
|
}
|
|
@@ -10835,12 +10823,12 @@ var init_backfill_run = __esm(() => {
|
|
|
10835
10823
|
init_backfill();
|
|
10836
10824
|
});
|
|
10837
10825
|
|
|
10838
|
-
// src/
|
|
10826
|
+
// src/learner/config-dir.ts
|
|
10839
10827
|
import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10840
10828
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
10841
10829
|
import { join as join13 } from "node:path";
|
|
10842
10830
|
function createRunConfigDir() {
|
|
10843
|
-
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-
|
|
10831
|
+
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-learner-"));
|
|
10844
10832
|
writeFileSync10(join13(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
|
|
10845
10833
|
return {
|
|
10846
10834
|
path: path3,
|
|
@@ -10851,7 +10839,7 @@ function createRunConfigDir() {
|
|
|
10851
10839
|
}
|
|
10852
10840
|
var init_config_dir = () => {};
|
|
10853
10841
|
|
|
10854
|
-
// src/
|
|
10842
|
+
// src/learner/conflicts.ts
|
|
10855
10843
|
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
|
|
10856
10844
|
function managedSettingsPaths(platform3 = process.platform) {
|
|
10857
10845
|
switch (platform3) {
|
|
@@ -10909,11 +10897,11 @@ var init_conflicts = __esm(() => {
|
|
|
10909
10897
|
CONFLICTING_ENV_PREFIXES = ["ANTHROPIC_", "CLAUDE_CODE_", "AWS_"];
|
|
10910
10898
|
});
|
|
10911
10899
|
|
|
10912
|
-
// src/
|
|
10900
|
+
// src/learner/env.ts
|
|
10913
10901
|
function isStripped(name) {
|
|
10914
10902
|
return STRIPPED_NAMES.has(name) || STRIPPED_PREFIXES.some((p2) => name.startsWith(p2));
|
|
10915
10903
|
}
|
|
10916
|
-
function
|
|
10904
|
+
function buildLearnerEnv(options) {
|
|
10917
10905
|
const base = options.baseEnv ?? process.env;
|
|
10918
10906
|
const env = {};
|
|
10919
10907
|
for (const [name, value] of Object.entries(base)) {
|
|
@@ -10943,7 +10931,7 @@ var init_env = __esm(() => {
|
|
|
10943
10931
|
STRIPPED_NAMES = new Set(["CLAUDE_CONFIG_DIR", "CLAUDECODE", "AWS_BEARER_TOKEN_BEDROCK"]);
|
|
10944
10932
|
});
|
|
10945
10933
|
|
|
10946
|
-
// src/
|
|
10934
|
+
// src/learner/executable.ts
|
|
10947
10935
|
import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
|
|
10948
10936
|
import { createRequire as createRequire3 } from "node:module";
|
|
10949
10937
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -10990,9 +10978,9 @@ function resolveClaudeExecutable(options = {}) {
|
|
|
10990
10978
|
}
|
|
10991
10979
|
var init_executable = () => {};
|
|
10992
10980
|
|
|
10993
|
-
// src/
|
|
10994
|
-
function
|
|
10995
|
-
return `You are Dosu's knowledge
|
|
10981
|
+
// src/learner/prompt.ts
|
|
10982
|
+
function buildLearnerSystemPrompt(coreRules) {
|
|
10983
|
+
return `You are Dosu's knowledge learner. You read a developer's recent coding-agent sessions and save the durable, non-obvious findings to the team's shared knowledge base so future teammates and agents do not have to rediscover them.
|
|
10996
10984
|
|
|
10997
10985
|
Available tools (the only tools you may use, by these exact names):
|
|
10998
10986
|
- mcp__sessions__list_sessions / mcp__sessions__read_session: the session transcripts in scope for this run.
|
|
@@ -11000,7 +10988,7 @@ Available tools (the only tools you may use, by these exact names):
|
|
|
11000
10988
|
|
|
11001
10989
|
${coreRules}`;
|
|
11002
10990
|
}
|
|
11003
|
-
function
|
|
10991
|
+
function buildLearnerPrompt(sessions) {
|
|
11004
10992
|
const list = sessions.map((s) => `- ${s.id} (${s.harness})`).join(`
|
|
11005
10993
|
`);
|
|
11006
10994
|
return `Mine the following ${sessions.length} coding-agent session(s) for durable knowledge:
|
|
@@ -11010,8 +10998,8 @@ ${list}
|
|
|
11010
10998
|
Work through them ONE AT A TIME: read a single session with read_session, decide what (if anything) is durable per your rules, dedupe against read_knowledge, and write that session's notes with write_knowledge BEFORE moving on to the next session. Each note is attributed to the session you read just before writing it, so do not read the next session until you have written this one's notes. When you are done, reply with a one-line summary: how many sessions you read and how many notes you wrote.`;
|
|
11011
10999
|
}
|
|
11012
11000
|
|
|
11013
|
-
// src/
|
|
11014
|
-
var
|
|
11001
|
+
// src/learner/prompt-core.ts
|
|
11002
|
+
var LEARNER_CORE_RULES = `Rules — non-negotiable:
|
|
11015
11003
|
1. Before writing anything, call read_knowledge with the candidate topic. If the same fact is
|
|
11016
11004
|
already recorded, skip it — never write a duplicate or near-duplicate. If the topic exists but
|
|
11017
11005
|
the session shows it has changed or been superseded, do write: a note stating the current state
|
|
@@ -11039,10 +11027,9 @@ useful.
|
|
|
11039
11027
|
6. Only pass repo/branch to write_knowledge when the session itself verifies them (an explicit
|
|
11040
11028
|
cwd, git remote, or branch mentioned in the transcript). Never infer or guess a repo. When not
|
|
11041
11029
|
verified, omit both.
|
|
11042
|
-
7.
|
|
11043
|
-
8. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
|
|
11030
|
+
7. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
|
|
11044
11031
|
long verbatim transcript spans. Summarize in your own words.
|
|
11045
|
-
|
|
11032
|
+
8. A trivial session with no real user query is normal: skip it silently.`;
|
|
11046
11033
|
|
|
11047
11034
|
// node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
11048
11035
|
var exports_sdk = {};
|
|
@@ -72793,7 +72780,7 @@ var init_redact = __esm(() => {
|
|
|
72793
72780
|
]);
|
|
72794
72781
|
});
|
|
72795
72782
|
|
|
72796
|
-
// src/
|
|
72783
|
+
// src/learner/tools.ts
|
|
72797
72784
|
function textResult(text) {
|
|
72798
72785
|
return { content: [{ type: "text", text }] };
|
|
72799
72786
|
}
|
|
@@ -72852,7 +72839,7 @@ function createSessionToolsServer(sessions) {
|
|
|
72852
72839
|
name: SESSIONS_SERVER_NAME,
|
|
72853
72840
|
version: "1.0.0",
|
|
72854
72841
|
tools: [
|
|
72855
|
-
O6e("list_sessions", "List the coding-agent sessions in scope for this
|
|
72842
|
+
O6e("list_sessions", "List the coding-agent sessions in scope for this study run.", {}, async () => textResult(formatSessionList(sessions))),
|
|
72856
72843
|
O6e("read_session", "Read the conversation turns of one in-scope session. Responses are " + "paginated; pass the returned offset to continue reading.", {
|
|
72857
72844
|
id: exports_external.string().describe("Session id from list_sessions"),
|
|
72858
72845
|
offset: exports_external.number().int().min(0).optional().describe("Turn index to start from")
|
|
@@ -72878,11 +72865,11 @@ var init_tools = __esm(() => {
|
|
|
72878
72865
|
init_redact();
|
|
72879
72866
|
});
|
|
72880
72867
|
|
|
72881
|
-
// src/
|
|
72868
|
+
// src/learner/runner.ts
|
|
72882
72869
|
var exports_runner = {};
|
|
72883
72870
|
__export(exports_runner, {
|
|
72884
72871
|
traceAgentMessage: () => traceAgentMessage,
|
|
72885
|
-
|
|
72872
|
+
runLearner: () => runLearner,
|
|
72886
72873
|
classifyGatewayError: () => classifyGatewayError
|
|
72887
72874
|
});
|
|
72888
72875
|
function classifyGatewayError(text) {
|
|
@@ -72904,12 +72891,12 @@ function traceAgentMessage(message) {
|
|
|
72904
72891
|
return;
|
|
72905
72892
|
for (const block2 of content) {
|
|
72906
72893
|
if (msg.type === "assistant" && block2.type === "text") {
|
|
72907
|
-
logger.debug("
|
|
72894
|
+
logger.debug("learner", `[agent] ${snippet(block2.text)}`);
|
|
72908
72895
|
} else if (msg.type === "assistant" && block2.type === "tool_use") {
|
|
72909
|
-
logger.debug("
|
|
72896
|
+
logger.debug("learner", `[agent] → ${block2.name} ${snippet(block2.input)}`);
|
|
72910
72897
|
} else if (msg.type === "user" && block2.type === "tool_result") {
|
|
72911
72898
|
const full = JSON.stringify(block2.content ?? "");
|
|
72912
|
-
logger.debug("
|
|
72899
|
+
logger.debug("learner", `[agent] ← result ${full.length} chars${block2.is_error ? " (error)" : ""}: ${snippet(block2.content).slice(0, 120)}`);
|
|
72913
72900
|
}
|
|
72914
72901
|
}
|
|
72915
72902
|
}
|
|
@@ -72922,7 +72909,7 @@ function allowedToolNames() {
|
|
|
72922
72909
|
`mcp__${KNOWLEDGE_SERVER_NAME}__finalize_session_knowledge`
|
|
72923
72910
|
]);
|
|
72924
72911
|
}
|
|
72925
|
-
async function
|
|
72912
|
+
async function runLearner(options) {
|
|
72926
72913
|
const conflicts = detectSettingsConflicts();
|
|
72927
72914
|
if (conflicts.length > 0) {
|
|
72928
72915
|
const detail = conflicts.map((c5) => `${c5.file} (${c5.keys.join(", ")})`).join("; ");
|
|
@@ -72945,7 +72932,7 @@ async function runMiner(options) {
|
|
|
72945
72932
|
const { query } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
|
|
72946
72933
|
const claudeExecutable = resolveClaudeExecutable();
|
|
72947
72934
|
if (claudeExecutable) {
|
|
72948
|
-
logger.debug("
|
|
72935
|
+
logger.debug("learner", `using system Claude Code executable: ${claudeExecutable}`);
|
|
72949
72936
|
}
|
|
72950
72937
|
const configDir = createRunConfigDir();
|
|
72951
72938
|
const runID = options.runID ?? crypto.randomUUID();
|
|
@@ -72958,7 +72945,7 @@ async function runMiner(options) {
|
|
|
72958
72945
|
let currentSession;
|
|
72959
72946
|
const readsSinceWrite = new Set;
|
|
72960
72947
|
let turns = 0;
|
|
72961
|
-
const env =
|
|
72948
|
+
const env = buildLearnerEnv({
|
|
72962
72949
|
apiKey: options.apiKey,
|
|
72963
72950
|
gatewayURL,
|
|
72964
72951
|
configDir: configDir.path,
|
|
@@ -72969,9 +72956,9 @@ async function runMiner(options) {
|
|
|
72969
72956
|
});
|
|
72970
72957
|
try {
|
|
72971
72958
|
const run2 = query({
|
|
72972
|
-
prompt:
|
|
72959
|
+
prompt: buildLearnerPrompt(options.sessions),
|
|
72973
72960
|
options: {
|
|
72974
|
-
systemPrompt:
|
|
72961
|
+
systemPrompt: buildLearnerSystemPrompt(LEARNER_CORE_RULES),
|
|
72975
72962
|
env,
|
|
72976
72963
|
abortController: abort,
|
|
72977
72964
|
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
@@ -72987,12 +72974,12 @@ async function runMiner(options) {
|
|
|
72987
72974
|
headers: {
|
|
72988
72975
|
...mcpHeaders(options.apiKey),
|
|
72989
72976
|
"X-Dosu-Session-Id": runID,
|
|
72990
|
-
"X-Dosu-Client": `dosu-cli-
|
|
72977
|
+
"X-Dosu-Client": `dosu-cli-learner/${getVersionString()}`,
|
|
72991
72978
|
...sessionStartedAt ? { "X-Dosu-Session-Started-At": sessionStartedAt } : {}
|
|
72992
72979
|
}
|
|
72993
72980
|
}
|
|
72994
72981
|
},
|
|
72995
|
-
stderr: (data) => logger.debug("
|
|
72982
|
+
stderr: (data) => logger.debug("learner", `[sdk] ${data}`),
|
|
72996
72983
|
canUseTool: async (toolName, input2) => {
|
|
72997
72984
|
if (!allowed.has(toolName)) {
|
|
72998
72985
|
return {
|
|
@@ -73049,7 +73036,7 @@ async function runMiner(options) {
|
|
|
73049
73036
|
};
|
|
73050
73037
|
}
|
|
73051
73038
|
if (message.subtype !== "success" || message.is_error) {
|
|
73052
|
-
logger.debug("
|
|
73039
|
+
logger.debug("learner", `run ${runID} failed: ${text}`);
|
|
73053
73040
|
return {
|
|
73054
73041
|
outcome: "error",
|
|
73055
73042
|
notesWritten,
|
|
@@ -73057,7 +73044,7 @@ async function runMiner(options) {
|
|
|
73057
73044
|
message: "Study run failed; see debug log for details."
|
|
73058
73045
|
};
|
|
73059
73046
|
}
|
|
73060
|
-
logger.debug("
|
|
73047
|
+
logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
|
|
73061
73048
|
return { outcome: "completed", notesWritten, turns, message: text };
|
|
73062
73049
|
}
|
|
73063
73050
|
}
|
|
@@ -73073,7 +73060,7 @@ async function runMiner(options) {
|
|
|
73073
73060
|
if (gatewayError) {
|
|
73074
73061
|
return { outcome: gatewayError.outcome, notesWritten, turns, message: gatewayError.message };
|
|
73075
73062
|
}
|
|
73076
|
-
logger.debug("
|
|
73063
|
+
logger.debug("learner", `run ${runID} threw: ${text}`);
|
|
73077
73064
|
return {
|
|
73078
73065
|
outcome: "error",
|
|
73079
73066
|
notesWritten,
|
|
@@ -73548,7 +73535,7 @@ var init_settings = __esm(() => {
|
|
|
73548
73535
|
});
|
|
73549
73536
|
|
|
73550
73537
|
// src/telemetry/debug-id.ts
|
|
73551
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
73538
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "33c6d65c-87a5-5e97-6475-6e2164756e21";
|
|
73552
73539
|
|
|
73553
73540
|
// src/telemetry/telemetry.ts
|
|
73554
73541
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -75424,6 +75411,140 @@ var init_errors3 = __esm(() => {
|
|
|
75424
75411
|
// src/mcp/constants.ts
|
|
75425
75412
|
var MCP_PROVIDER_SLUG2 = "dosu_mcp";
|
|
75426
75413
|
|
|
75414
|
+
// src/sessions/session-title.ts
|
|
75415
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync24, renameSync as renameSync6, statSync as statSync9, writeFileSync as writeFileSync16 } from "node:fs";
|
|
75416
|
+
import { homedir as homedir10 } from "node:os";
|
|
75417
|
+
import { join as join40 } from "node:path";
|
|
75418
|
+
function cachePath2(configDir) {
|
|
75419
|
+
return join40(configDir, CACHE_FILENAME5);
|
|
75420
|
+
}
|
|
75421
|
+
function loadCacheFile2(configDir) {
|
|
75422
|
+
try {
|
|
75423
|
+
const raw = JSON.parse(readFileSync24(cachePath2(configDir), "utf-8"));
|
|
75424
|
+
if (raw.schema_version !== CACHE_SCHEMA_VERSION2 || typeof raw.entries !== "object")
|
|
75425
|
+
return {};
|
|
75426
|
+
return raw.entries;
|
|
75427
|
+
} catch {
|
|
75428
|
+
return {};
|
|
75429
|
+
}
|
|
75430
|
+
}
|
|
75431
|
+
function saveCacheFile2(configDir, entries) {
|
|
75432
|
+
try {
|
|
75433
|
+
if (!existsSync25(configDir))
|
|
75434
|
+
mkdirSync14(configDir, { recursive: true, mode: 448 });
|
|
75435
|
+
const path3 = cachePath2(configDir);
|
|
75436
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
75437
|
+
const file2 = { schema_version: CACHE_SCHEMA_VERSION2, entries };
|
|
75438
|
+
writeFileSync16(tmp, JSON.stringify(file2), { mode: 384 });
|
|
75439
|
+
renameSync6(tmp, path3);
|
|
75440
|
+
} catch {}
|
|
75441
|
+
}
|
|
75442
|
+
function extractUserQuery(text) {
|
|
75443
|
+
const tagged = text.match(/<user_query>([\s\S]*?)<\/user_query>/);
|
|
75444
|
+
if (tagged)
|
|
75445
|
+
return tagged[1];
|
|
75446
|
+
const named = text.match(/^\s*<[\w-]+\s[^>]*\bname="([^"]+)"/);
|
|
75447
|
+
if (named)
|
|
75448
|
+
return named[1];
|
|
75449
|
+
return text.replace(/^(\s*<([\w-]+)(?:\s[^>]*)?>[\s\S]*?<\/\2>\s*)+/, "");
|
|
75450
|
+
}
|
|
75451
|
+
function cleanSessionName(text) {
|
|
75452
|
+
const line = text.replace(/\s+/g, " ").trim();
|
|
75453
|
+
if (!line)
|
|
75454
|
+
return null;
|
|
75455
|
+
return line.length > SESSION_NAME_MAX ? `${line.slice(0, SESSION_NAME_MAX - 1)}…` : line;
|
|
75456
|
+
}
|
|
75457
|
+
function claudeSummaryFromHead(text) {
|
|
75458
|
+
for (const line of text.split(`
|
|
75459
|
+
`).slice(0, HEAD_LINES2)) {
|
|
75460
|
+
if (!line.includes('"summary"'))
|
|
75461
|
+
continue;
|
|
75462
|
+
try {
|
|
75463
|
+
const obj = JSON.parse(line);
|
|
75464
|
+
if (obj.type === "summary" && typeof obj.summary === "string") {
|
|
75465
|
+
return cleanSessionName(obj.summary);
|
|
75466
|
+
}
|
|
75467
|
+
} catch {}
|
|
75468
|
+
}
|
|
75469
|
+
return null;
|
|
75470
|
+
}
|
|
75471
|
+
function reconstructSession(harness, id2, slug, home = homedir10(), exists = existsSync25) {
|
|
75472
|
+
if (!slug)
|
|
75473
|
+
return null;
|
|
75474
|
+
const candidates = [];
|
|
75475
|
+
if (harness === "claude") {
|
|
75476
|
+
candidates.push(join40(home, ".claude", "projects", slug, `${id2}.jsonl`));
|
|
75477
|
+
} else if (harness === "cursor") {
|
|
75478
|
+
const project = join40(home, ".cursor", "projects", slug);
|
|
75479
|
+
candidates.push(join40(project, "agent-transcripts", id2, `${id2}.jsonl`), join40(project, "agent-transcripts", `${id2}.jsonl`));
|
|
75480
|
+
} else {
|
|
75481
|
+
return null;
|
|
75482
|
+
}
|
|
75483
|
+
for (const path3 of candidates) {
|
|
75484
|
+
if (exists(path3))
|
|
75485
|
+
return { id: id2, harness, path: path3, project: slug, updated: "" };
|
|
75486
|
+
}
|
|
75487
|
+
return null;
|
|
75488
|
+
}
|
|
75489
|
+
function readHead2(path3) {
|
|
75490
|
+
try {
|
|
75491
|
+
return readFileSync24(path3, "utf-8").slice(0, 128 * 1024);
|
|
75492
|
+
} catch {
|
|
75493
|
+
return null;
|
|
75494
|
+
}
|
|
75495
|
+
}
|
|
75496
|
+
function fileMtime2(path3) {
|
|
75497
|
+
try {
|
|
75498
|
+
return statSync9(path3).mtime.toISOString();
|
|
75499
|
+
} catch {
|
|
75500
|
+
return "";
|
|
75501
|
+
}
|
|
75502
|
+
}
|
|
75503
|
+
function createSessionTitleResolver(configDir = getConfigDir(), deps = {}) {
|
|
75504
|
+
const head = deps.readHead ?? readHead2;
|
|
75505
|
+
const turns = deps.readTurns ?? readSessionTurns;
|
|
75506
|
+
const mtime = deps.mtime ?? fileMtime2;
|
|
75507
|
+
const entries = loadCacheFile2(configDir);
|
|
75508
|
+
let dirty = false;
|
|
75509
|
+
const compute = (session) => {
|
|
75510
|
+
if (session.harness === "claude") {
|
|
75511
|
+
const text = head(session.path);
|
|
75512
|
+
const summary = text ? claudeSummaryFromHead(text) : null;
|
|
75513
|
+
if (summary)
|
|
75514
|
+
return summary;
|
|
75515
|
+
}
|
|
75516
|
+
const firstUser = turns(session).find((t9) => t9.role === "user");
|
|
75517
|
+
return firstUser ? cleanSessionName(extractUserQuery(firstUser.text)) : null;
|
|
75518
|
+
};
|
|
75519
|
+
return {
|
|
75520
|
+
cached(key) {
|
|
75521
|
+
return entries[key]?.title ?? null;
|
|
75522
|
+
},
|
|
75523
|
+
resolve(session) {
|
|
75524
|
+
const key = `${session.harness}/${session.id}`;
|
|
75525
|
+
const cached2 = entries[key];
|
|
75526
|
+
if (cached2 && (cached2.title !== null || cached2.mtime === mtime(session.path))) {
|
|
75527
|
+
return cached2.title;
|
|
75528
|
+
}
|
|
75529
|
+
const title = compute(session);
|
|
75530
|
+
entries[key] = { title, mtime: mtime(session.path) };
|
|
75531
|
+
dirty = true;
|
|
75532
|
+
return title;
|
|
75533
|
+
},
|
|
75534
|
+
flush() {
|
|
75535
|
+
if (!dirty)
|
|
75536
|
+
return;
|
|
75537
|
+
saveCacheFile2(configDir, entries);
|
|
75538
|
+
dirty = false;
|
|
75539
|
+
}
|
|
75540
|
+
};
|
|
75541
|
+
}
|
|
75542
|
+
var CACHE_FILENAME5 = "session-titles.json", CACHE_SCHEMA_VERSION2 = 3, HEAD_LINES2 = 50, SESSION_NAME_MAX = 80;
|
|
75543
|
+
var init_session_title = __esm(() => {
|
|
75544
|
+
init_config();
|
|
75545
|
+
init_read();
|
|
75546
|
+
});
|
|
75547
|
+
|
|
75427
75548
|
// src/tui/alt-screen.ts
|
|
75428
75549
|
function enterAltScreen(output2) {
|
|
75429
75550
|
depth += 1;
|
|
@@ -75447,7 +75568,8 @@ var init_alt_screen = __esm(() => {
|
|
|
75447
75568
|
});
|
|
75448
75569
|
|
|
75449
75570
|
// src/tui/activity-view.ts
|
|
75450
|
-
import { readFileSync as
|
|
75571
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25 } from "node:fs";
|
|
75572
|
+
import { basename as basename5 } from "node:path";
|
|
75451
75573
|
function reduceActivityViewKey(key) {
|
|
75452
75574
|
if (key === "q" || key === ESC7 || key === CTRL_C4)
|
|
75453
75575
|
return "back";
|
|
@@ -75495,13 +75617,13 @@ function formatActivityLine(line, width, full = false) {
|
|
|
75495
75617
|
}
|
|
75496
75618
|
function appendSyncActivity(buffer, chunk, max = ACTIVITY_VIEW_BUFFER_LINES) {
|
|
75497
75619
|
const fresh = chunk.split(`
|
|
75498
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[
|
|
75620
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[learner]")).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
75499
75621
|
return [...buffer, ...fresh].slice(-max);
|
|
75500
75622
|
}
|
|
75501
75623
|
function clip(text, max) {
|
|
75502
75624
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
75503
75625
|
}
|
|
75504
|
-
function
|
|
75626
|
+
function formatStudiedRow(record2, full = false, name) {
|
|
75505
75627
|
const match = record2.at.match(/^\d{4}-(\d{2}-\d{2})T(\d{2}:\d{2})/);
|
|
75506
75628
|
const stamp = match ? `${match[1]} ${match[2]}` : record2.at;
|
|
75507
75629
|
const slash = record2.session.indexOf("/");
|
|
@@ -75509,13 +75631,15 @@ function formatMinedRow(record2, full = false) {
|
|
|
75509
75631
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
75510
75632
|
const rawProject = record2.project ?? "-";
|
|
75511
75633
|
const project = full ? rawProject : clip(rawProject, 28);
|
|
75512
|
-
|
|
75634
|
+
const label = full ? name ? `${name} · ${id2}` : id2 : clip(name ?? id2, 44);
|
|
75635
|
+
return `${harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75513
75636
|
}
|
|
75514
|
-
function formatQueuedRow(session, full = false) {
|
|
75637
|
+
function formatQueuedRow(session, full = false, name) {
|
|
75515
75638
|
const stamp = session.updated.replace("T", " ").slice(5, 16);
|
|
75516
75639
|
const rawProject = session.project ?? "-";
|
|
75517
75640
|
const project = full ? rawProject : clip(rawProject, 28);
|
|
75518
|
-
|
|
75641
|
+
const label = full ? name ? `${name} · ${session.id}` : session.id : clip(name ?? session.id, 44);
|
|
75642
|
+
return `${session.harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75519
75643
|
}
|
|
75520
75644
|
function wrapRow(text, width, indent = " ") {
|
|
75521
75645
|
if (text.length <= width)
|
|
@@ -75566,12 +75690,12 @@ function foldRunProgress(progress, chunk) {
|
|
|
75566
75690
|
}
|
|
75567
75691
|
if (!current)
|
|
75568
75692
|
continue;
|
|
75569
|
-
const read = line.match(/\[
|
|
75693
|
+
const read = line.match(/\[learner\] \[agent\] → mcp__sessions__read_session .*?"id":"([^"]+)"/);
|
|
75570
75694
|
if (read) {
|
|
75571
75695
|
current.read.add(read[1]);
|
|
75572
75696
|
continue;
|
|
75573
75697
|
}
|
|
75574
|
-
if (line.includes("[
|
|
75698
|
+
if (line.includes("[learner] [agent] → mcp__dosu__write_knowledge")) {
|
|
75575
75699
|
current.notes += 1;
|
|
75576
75700
|
}
|
|
75577
75701
|
}
|
|
@@ -75607,12 +75731,12 @@ function progressLine(done, ready, width, notes = 0) {
|
|
|
75607
75731
|
const suffix = notes > 0 ? ` · ${notes} suggested page${notes === 1 ? "" : "s"}` : "";
|
|
75608
75732
|
return `${bar} ${done}/${total} studied · ${pct}%${suffix}`;
|
|
75609
75733
|
}
|
|
75610
|
-
function tabBar(tab, queuedCount, openCount,
|
|
75734
|
+
function tabBar(tab, queuedCount, openCount, studiedCount, width) {
|
|
75611
75735
|
return tabStrip([
|
|
75612
|
-
["activity", "
|
|
75613
|
-
["
|
|
75614
|
-
["queued", `
|
|
75615
|
-
["open", `
|
|
75736
|
+
["activity", "activity"],
|
|
75737
|
+
["studied", `studied (${studiedCount})`],
|
|
75738
|
+
["queued", `queued (${queuedCount})`],
|
|
75739
|
+
["open", `open (${openCount})`]
|
|
75616
75740
|
], tab, width);
|
|
75617
75741
|
}
|
|
75618
75742
|
function activityWidth(columns) {
|
|
@@ -75654,28 +75778,41 @@ function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
|
75654
75778
|
import_picocolors30.default.dim(`╰${"─".repeat(inner + 2)}╯`)
|
|
75655
75779
|
];
|
|
75656
75780
|
}
|
|
75657
|
-
function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [],
|
|
75658
|
-
const
|
|
75781
|
+
function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [], studiedBeforeRun = 0, open5 = [], runProgress = null, projectNames = {}, sessionNames = {}) {
|
|
75782
|
+
const studied = status.state.watermark ? `Studied sessions up to ${localTime(status.state.watermark)}` : "Nothing studied yet";
|
|
75659
75783
|
const queueDetail = [];
|
|
75660
75784
|
if (status.backoffUntil) {
|
|
75661
75785
|
queueDetail.push(`retrying after ${localTime(status.backoffUntil)} · s syncs now`);
|
|
75662
75786
|
}
|
|
75663
75787
|
let progress = null;
|
|
75664
75788
|
if (status.running && backlog) {
|
|
75665
|
-
const
|
|
75789
|
+
const studiedDelta = Math.max(0, (status.state.total_mined ?? 0) - studiedBeforeRun);
|
|
75666
75790
|
const stepDone = runProgress ? Math.min(Math.max(0, runProgress.read.size - 1), runProgress.batch - 1, backlog.ready) : 0;
|
|
75667
|
-
progress = progressLine(
|
|
75791
|
+
progress = progressLine(studiedDelta + stepDone, Math.max(0, backlog.ready - stepDone), width, runProgress?.notes ?? 0);
|
|
75668
75792
|
}
|
|
75669
75793
|
const refusal = !status.running && status.state.last_refusal;
|
|
75670
75794
|
const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors30.default.yellow(line)) : [];
|
|
75671
75795
|
const fullRows = Boolean(pane.fullRows);
|
|
75672
|
-
const
|
|
75673
|
-
const
|
|
75796
|
+
const withName = (item, key) => projectNames[key] ? { ...item, project: projectNames[key] } : item;
|
|
75797
|
+
const latestPass = new Map;
|
|
75798
|
+
for (const r9 of status.state.mined_sessions ?? [])
|
|
75799
|
+
latestPass.set(r9.session, r9);
|
|
75800
|
+
const studiedRows = [...latestPass.values()].map((r9) => {
|
|
75801
|
+
const record2 = withName(r9, r9.session);
|
|
75802
|
+
const name = sessionNames[r9.session];
|
|
75803
|
+
return fullRows ? formatStudiedRow(record2, true, name) : formatActivityLine(formatStudiedRow(record2, false, name), width);
|
|
75804
|
+
});
|
|
75805
|
+
const sessionRow = (session) => {
|
|
75806
|
+
const key = `${session.harness}/${session.id}`;
|
|
75807
|
+
const s = withName(session, key);
|
|
75808
|
+
const name = sessionNames[key];
|
|
75809
|
+
return fullRows ? formatQueuedRow(s, true, name) : formatActivityLine(formatQueuedRow(s, false, name), width);
|
|
75810
|
+
};
|
|
75674
75811
|
const queuedRows = queued.map(sessionRow);
|
|
75675
75812
|
const openRows = open5.map(sessionRow);
|
|
75676
|
-
const source = pane.tab === "activity" ? activity.map((line) => formatActivityLine(line, width, fullRows)) : pane.tab === "queued" ? queuedRows : pane.tab === "open" ? openRows :
|
|
75677
|
-
const
|
|
75678
|
-
const empty = pane.tab === "activity" ? "No sync activity in the log yet." : pane.tab === "queued" ? "Queue empty. Finished agent sessions appear here." : pane.tab === "open" ? "No open sessions. Live agent sessions sit here until they go quiet." :
|
|
75813
|
+
const source = pane.tab === "activity" ? activity.map((line) => formatActivityLine(line, width, fullRows)) : pane.tab === "queued" ? queuedRows : pane.tab === "open" ? openRows : studiedRows;
|
|
75814
|
+
const emptyStudied = status.state.watermark ? "No sessions recorded yet. History starts with the next study run." : "No studied sessions yet.";
|
|
75815
|
+
const empty = pane.tab === "activity" ? "No sync activity in the log yet." : pane.tab === "queued" ? "Queue empty. Finished agent sessions appear here." : pane.tab === "open" ? "No open sessions. Live agent sessions sit here until they go quiet." : emptyStudied;
|
|
75679
75816
|
const height = fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75680
75817
|
const { visible, above, below } = windowList(source, pane.scroll, height);
|
|
75681
75818
|
const display = fullRows ? visible.flatMap((row) => wrapRow(row, width)) : visible;
|
|
@@ -75686,15 +75823,15 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
|
|
|
75686
75823
|
if (below > 0)
|
|
75687
75824
|
scrollParts.push(`↓ ${below} newer`);
|
|
75688
75825
|
const lines = [
|
|
75689
|
-
breadcrumb(["
|
|
75826
|
+
breadcrumb(["home", "activity"], width),
|
|
75690
75827
|
"",
|
|
75691
75828
|
statusLine(status),
|
|
75692
|
-
import_picocolors30.default.dim(
|
|
75829
|
+
import_picocolors30.default.dim(studied),
|
|
75693
75830
|
...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors30.default.dim(line)) : [],
|
|
75694
75831
|
...progress ? [progress] : [],
|
|
75695
75832
|
...refusalLines,
|
|
75696
75833
|
"",
|
|
75697
|
-
...tabBar(pane.tab, queued.length, open5.length,
|
|
75834
|
+
...tabBar(pane.tab, queued.length, open5.length, latestPass.size, width),
|
|
75698
75835
|
...listRows,
|
|
75699
75836
|
...scrollParts.length > 0 ? [import_picocolors30.default.dim(clip(scrollParts.join(" · "), width))] : [],
|
|
75700
75837
|
"",
|
|
@@ -75712,9 +75849,72 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
|
|
|
75712
75849
|
return lines.join(`
|
|
75713
75850
|
`);
|
|
75714
75851
|
}
|
|
75852
|
+
function createRowNamer() {
|
|
75853
|
+
const dirs = createProjectDirResolver();
|
|
75854
|
+
const titles = createSessionTitleResolver();
|
|
75855
|
+
const bySlug = new Map;
|
|
75856
|
+
const fromSlug = (slug) => {
|
|
75857
|
+
if (!bySlug.has(slug))
|
|
75858
|
+
bySlug.set(slug, unmungeSlug(slug));
|
|
75859
|
+
return bySlug.get(slug) ?? null;
|
|
75860
|
+
};
|
|
75861
|
+
return (status, backlog) => {
|
|
75862
|
+
const names = { projects: {}, titles: {} };
|
|
75863
|
+
let budget = TITLE_READS_PER_DRAW;
|
|
75864
|
+
let resolved = false;
|
|
75865
|
+
const title = (key, session) => {
|
|
75866
|
+
const hit = titles.cached(key);
|
|
75867
|
+
if (hit) {
|
|
75868
|
+
names.titles[key] = hit;
|
|
75869
|
+
return;
|
|
75870
|
+
}
|
|
75871
|
+
if (budget <= 0)
|
|
75872
|
+
return;
|
|
75873
|
+
budget--;
|
|
75874
|
+
const t9 = titles.resolve(session);
|
|
75875
|
+
resolved = true;
|
|
75876
|
+
if (t9)
|
|
75877
|
+
names.titles[key] = t9;
|
|
75878
|
+
};
|
|
75879
|
+
for (const s of [...backlog.queued, ...backlog.open]) {
|
|
75880
|
+
if (!existsSync26(s.path))
|
|
75881
|
+
continue;
|
|
75882
|
+
const key = `${s.harness}/${s.id}`;
|
|
75883
|
+
const dir = dirs.resolve(s);
|
|
75884
|
+
if (dir)
|
|
75885
|
+
names.projects[key] = basename5(dir);
|
|
75886
|
+
title(key, s);
|
|
75887
|
+
resolved = true;
|
|
75888
|
+
}
|
|
75889
|
+
for (const r9 of [...status.state.mined_sessions ?? []].reverse()) {
|
|
75890
|
+
const key = r9.session;
|
|
75891
|
+
if (!names.projects[key]) {
|
|
75892
|
+
const dir = dirs.cached(key) ?? (r9.project ? fromSlug(r9.project) : null);
|
|
75893
|
+
if (dir)
|
|
75894
|
+
names.projects[key] = basename5(dir);
|
|
75895
|
+
}
|
|
75896
|
+
if (!names.titles[key]) {
|
|
75897
|
+
const hit = titles.cached(key);
|
|
75898
|
+
if (hit) {
|
|
75899
|
+
names.titles[key] = hit;
|
|
75900
|
+
} else if (budget > 0) {
|
|
75901
|
+
const slash = key.indexOf("/");
|
|
75902
|
+
const session = reconstructSession(key.slice(0, slash), key.slice(slash + 1), r9.project);
|
|
75903
|
+
if (session)
|
|
75904
|
+
title(key, session);
|
|
75905
|
+
}
|
|
75906
|
+
}
|
|
75907
|
+
}
|
|
75908
|
+
if (resolved) {
|
|
75909
|
+
dirs.flush();
|
|
75910
|
+
titles.flush();
|
|
75911
|
+
}
|
|
75912
|
+
return names;
|
|
75913
|
+
};
|
|
75914
|
+
}
|
|
75715
75915
|
function defaultReadLog2() {
|
|
75716
75916
|
try {
|
|
75717
|
-
return
|
|
75917
|
+
return readFileSync25(logger.getLogPath(), "utf-8");
|
|
75718
75918
|
} catch {
|
|
75719
75919
|
return "";
|
|
75720
75920
|
}
|
|
@@ -75732,6 +75932,7 @@ function runActivityView(io2 = {}) {
|
|
|
75732
75932
|
const setPaused = io2.setPaused ?? setSyncPaused;
|
|
75733
75933
|
const clearHistory = io2.clearHistory ?? resetSyncState;
|
|
75734
75934
|
const listBacklog = io2.listBacklog ?? listSessionBacklog;
|
|
75935
|
+
const rowNamesFor = io2.rowNames ?? createRowNamer();
|
|
75735
75936
|
const pollMs = io2.pollMs ?? ACTIVITY_VIEW_POLL_MS;
|
|
75736
75937
|
const seed = readLog();
|
|
75737
75938
|
let activity = appendSyncActivity([], seed);
|
|
@@ -75760,18 +75961,18 @@ function runActivityView(io2 = {}) {
|
|
|
75760
75961
|
};
|
|
75761
75962
|
const listHeight = () => fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75762
75963
|
let lastFrame = null;
|
|
75763
|
-
let
|
|
75964
|
+
let studiedBeforeRun = null;
|
|
75764
75965
|
const draw = () => {
|
|
75765
75966
|
status = getStatus();
|
|
75766
75967
|
if (status.running) {
|
|
75767
75968
|
const run2 = status.state.run;
|
|
75768
75969
|
if (run2 && run2.pid === status.pid) {
|
|
75769
|
-
|
|
75970
|
+
studiedBeforeRun = run2.baseline_mined;
|
|
75770
75971
|
} else {
|
|
75771
|
-
|
|
75972
|
+
studiedBeforeRun ??= status.state.total_mined ?? 0;
|
|
75772
75973
|
}
|
|
75773
75974
|
} else {
|
|
75774
|
-
|
|
75975
|
+
studiedBeforeRun = null;
|
|
75775
75976
|
}
|
|
75776
75977
|
if (status.state.watermark !== queuedWatermark) {
|
|
75777
75978
|
queuedWatermark = status.state.watermark;
|
|
@@ -75782,7 +75983,8 @@ function runActivityView(io2 = {}) {
|
|
|
75782
75983
|
if (confirmSync === "stop" && !status.running)
|
|
75783
75984
|
confirmSync = null;
|
|
75784
75985
|
const width = activityWidth(output2.columns ?? 80);
|
|
75785
|
-
const
|
|
75986
|
+
const rowNames = rowNamesFor(status, sessions);
|
|
75987
|
+
const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync ?? undefined, fullRows }, sessions.queued, studiedBeforeRun ?? 0, sessions.open, runProgress, rowNames.projects, rowNames.titles);
|
|
75786
75988
|
if (frame === lastFrame)
|
|
75787
75989
|
return;
|
|
75788
75990
|
lastFrame = frame;
|
|
@@ -75831,7 +76033,7 @@ ${CLEAR_BELOW}`);
|
|
|
75831
76033
|
let note;
|
|
75832
76034
|
if (mode === "clear") {
|
|
75833
76035
|
clearHistory();
|
|
75834
|
-
|
|
76036
|
+
studiedBeforeRun = null;
|
|
75835
76037
|
note = "[sync] study history cleared · the next run reads every local session again";
|
|
75836
76038
|
} else if (mode === "stop") {
|
|
75837
76039
|
const ok2 = status.pid !== undefined && stopSync(status.pid);
|
|
@@ -75893,10 +76095,12 @@ ${CLEAR_BELOW}`);
|
|
|
75893
76095
|
input2.on("data", onData);
|
|
75894
76096
|
});
|
|
75895
76097
|
}
|
|
75896
|
-
var import_picocolors30, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE;
|
|
76098
|
+
var import_picocolors30, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE, TITLE_READS_PER_DRAW = 25;
|
|
75897
76099
|
var init_activity_view = __esm(() => {
|
|
75898
76100
|
init_follow();
|
|
75899
76101
|
init_logger();
|
|
76102
|
+
init_project_dir();
|
|
76103
|
+
init_session_title();
|
|
75900
76104
|
init_styles();
|
|
75901
76105
|
init_backlog();
|
|
75902
76106
|
init_detach();
|
|
@@ -75918,7 +76122,7 @@ var init_activity_view = __esm(() => {
|
|
|
75918
76122
|
CURSOR_HOME = `${ESC7}[H`;
|
|
75919
76123
|
CLEAR_BELOW = `${ESC7}[0J`;
|
|
75920
76124
|
CLEAR_EOL = `${ESC7}[K`;
|
|
75921
|
-
ACTIVITY_VIEW_TABS = ["activity", "
|
|
76125
|
+
ACTIVITY_VIEW_TABS = ["activity", "studied", "queued", "open"];
|
|
75922
76126
|
DEFAULT_PANE = { tab: "activity", scroll: 0 };
|
|
75923
76127
|
});
|
|
75924
76128
|
|
|
@@ -77600,12 +77804,15 @@ function loadPageStatsFromConfig() {
|
|
|
77600
77804
|
}
|
|
77601
77805
|
}
|
|
77602
77806
|
function overviewRows(state) {
|
|
77603
|
-
const
|
|
77807
|
+
const studiedTotal = state.total_mined ?? 0;
|
|
77604
77808
|
const notes = state.total_notes ?? 0;
|
|
77605
77809
|
const tokens = state.total_learning_tokens ?? 0;
|
|
77606
|
-
if (
|
|
77810
|
+
if (studiedTotal === 0 && notes === 0)
|
|
77607
77811
|
return [];
|
|
77608
|
-
const rows = [
|
|
77812
|
+
const rows = [
|
|
77813
|
+
`${label("Sessions studied")}${studiedTotal}`,
|
|
77814
|
+
`${label("Suggested pages")}${notes}`
|
|
77815
|
+
];
|
|
77609
77816
|
if (tokens > 0) {
|
|
77610
77817
|
rows.push(`${label("Investigation distilled")}${formatTokenCount(tokens)} tokens`);
|
|
77611
77818
|
}
|
|
@@ -77662,12 +77869,12 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
|
|
|
77662
77869
|
if (below > 0)
|
|
77663
77870
|
scrollParts.push(`↓ ${below} more`);
|
|
77664
77871
|
const lines = [
|
|
77665
|
-
breadcrumb(["
|
|
77872
|
+
breadcrumb(["home", "analytics"], width),
|
|
77666
77873
|
"",
|
|
77667
77874
|
...tabStrip([
|
|
77668
|
-
["overview", "
|
|
77669
|
-
["projects", "
|
|
77670
|
-
["pages", "
|
|
77875
|
+
["overview", "overview"],
|
|
77876
|
+
["projects", "projects"],
|
|
77877
|
+
["pages", "pages"]
|
|
77671
77878
|
], tab, width, { spread: false }),
|
|
77672
77879
|
"",
|
|
77673
77880
|
...listRows,
|
|
@@ -77830,10 +78037,10 @@ function checklistRows(ctx) {
|
|
|
77830
78037
|
rows.push(["agents", `${on2} ${ctx.agents.join(` ${DOT3} `)}`]);
|
|
77831
78038
|
if (missing.has("hooks"))
|
|
77832
78039
|
rows.push(["hooks", warnRow]);
|
|
77833
|
-
if (ctx.
|
|
78040
|
+
if (ctx.studying) {
|
|
77834
78041
|
rows.push([
|
|
77835
78042
|
"sync",
|
|
77836
|
-
`\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors33.default.dim(`${DOT3} see
|
|
78043
|
+
`\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors33.default.dim(`${DOT3} see activity`)}`
|
|
77837
78044
|
]);
|
|
77838
78045
|
}
|
|
77839
78046
|
if (ctx.update) {
|
|
@@ -78010,7 +78217,7 @@ function renderPagesFrame(model, width, selected) {
|
|
|
78010
78217
|
}
|
|
78011
78218
|
const count = model.phase === "ready" ? ` ${import_picocolors34.default.dim(model.pages.length < model.total ? `(${model.pages.length} of ${model.total})` : `(${model.pages.length})`)}` : "";
|
|
78012
78219
|
const lines = [
|
|
78013
|
-
`${breadcrumb(["
|
|
78220
|
+
`${breadcrumb(["home", "pages"], width)}${count}`,
|
|
78014
78221
|
"",
|
|
78015
78222
|
...body,
|
|
78016
78223
|
...scrollHint ? [import_picocolors34.default.dim(scrollHint)] : [],
|
|
@@ -78040,7 +78247,7 @@ function renderPageFrame(page, content, width, scroll) {
|
|
|
78040
78247
|
}
|
|
78041
78248
|
const meta3 = `${page.type} · ${page.published ? "published" : "draft"} · updated ${page.updated_at.slice(0, 10)}`;
|
|
78042
78249
|
const lines = [
|
|
78043
|
-
breadcrumb(["
|
|
78250
|
+
breadcrumb(["home", "pages", page.title], width),
|
|
78044
78251
|
import_picocolors34.default.dim(clip3(meta3, width)),
|
|
78045
78252
|
"",
|
|
78046
78253
|
...body,
|
|
@@ -78223,8 +78430,8 @@ __export(exports_tui, {
|
|
|
78223
78430
|
runTUI: () => runTUI,
|
|
78224
78431
|
handleLogout: () => handleLogout
|
|
78225
78432
|
});
|
|
78226
|
-
import { homedir as
|
|
78227
|
-
import { basename as
|
|
78433
|
+
import { homedir as homedir11 } from "node:os";
|
|
78434
|
+
import { basename as basename6 } from "node:path";
|
|
78228
78435
|
async function probeSession(cfg) {
|
|
78229
78436
|
if (!isAuthenticated(cfg) || !isTokenExpired(cfg))
|
|
78230
78437
|
return { expired: false };
|
|
@@ -78251,7 +78458,7 @@ function bannerContext(cfg, session) {
|
|
|
78251
78458
|
return {
|
|
78252
78459
|
version: getVersionString(),
|
|
78253
78460
|
webAppHost,
|
|
78254
|
-
directory:
|
|
78461
|
+
directory: basename6(process.cwd()),
|
|
78255
78462
|
signedIn: isAuthenticated(cfg),
|
|
78256
78463
|
sessionExpired: session.expired,
|
|
78257
78464
|
deploymentName: cfg.active_account?.target?.deployment_name,
|
|
@@ -78259,7 +78466,7 @@ function bannerContext(cfg, session) {
|
|
|
78259
78466
|
setupMissing: isAuthenticated(cfg) && !session.expired ? missingSetupSteps(cfg) : [],
|
|
78260
78467
|
...inGitWorkTree() ? { repoAgentsMd: dosuAgentsSectionState() } : {},
|
|
78261
78468
|
agents,
|
|
78262
|
-
|
|
78469
|
+
studying: isStudying(),
|
|
78263
78470
|
...latest ? { update: { version: latest, hint: buildUpdateHint(INSTALL_CHANNEL, isNpxInvocation()) } } : {}
|
|
78264
78471
|
};
|
|
78265
78472
|
}
|
|
@@ -78326,7 +78533,7 @@ function setupHint(cfg) {
|
|
|
78326
78533
|
const started = Boolean(cfg.active_account?.target);
|
|
78327
78534
|
return import_picocolors35.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
|
|
78328
78535
|
}
|
|
78329
|
-
function
|
|
78536
|
+
function isStudying() {
|
|
78330
78537
|
try {
|
|
78331
78538
|
return getSyncStatus({ readLog: () => "" }).running;
|
|
78332
78539
|
} catch {
|
|
@@ -78355,36 +78562,36 @@ async function runMainMenu() {
|
|
|
78355
78562
|
if (session.expired) {
|
|
78356
78563
|
return [
|
|
78357
78564
|
{
|
|
78358
|
-
label: "
|
|
78565
|
+
label: "log in again",
|
|
78359
78566
|
hint: "(session expired · opens your browser)",
|
|
78360
78567
|
value: "auth"
|
|
78361
78568
|
},
|
|
78362
|
-
{ label: "
|
|
78569
|
+
{ label: "exit", value: "exit" }
|
|
78363
78570
|
];
|
|
78364
78571
|
}
|
|
78365
78572
|
if (!isAuthenticated(cfg)) {
|
|
78366
78573
|
return [
|
|
78367
|
-
{ label: "
|
|
78368
|
-
{ label: "
|
|
78574
|
+
{ label: "log in / sign up", hint: "(opens your browser)", value: "auth" },
|
|
78575
|
+
{ label: "exit", value: "exit" }
|
|
78369
78576
|
];
|
|
78370
78577
|
}
|
|
78371
78578
|
if (!isSetUp(cfg)) {
|
|
78372
78579
|
return [
|
|
78373
|
-
{ label: "
|
|
78374
|
-
{ label: "
|
|
78580
|
+
{ label: "setup", hint: setupHint(cfg), value: "setup" },
|
|
78581
|
+
{ label: "exit", value: "exit" }
|
|
78375
78582
|
];
|
|
78376
78583
|
}
|
|
78377
|
-
const
|
|
78584
|
+
const studying = isStudying();
|
|
78378
78585
|
return [
|
|
78379
78586
|
{
|
|
78380
|
-
label:
|
|
78587
|
+
label: studying ? `activity \uD83D\uDCDA ${brand("studying sessions...")}` : "activity",
|
|
78381
78588
|
value: "sync"
|
|
78382
78589
|
},
|
|
78383
|
-
{ label: "
|
|
78384
|
-
{ label: "
|
|
78385
|
-
{ label: "
|
|
78386
|
-
{ label: "
|
|
78387
|
-
{ label: "
|
|
78590
|
+
{ label: "knowledge report", hint: "(opens in browser)", value: "report" },
|
|
78591
|
+
{ label: "analytics", value: "analytics" },
|
|
78592
|
+
{ label: "pages", value: "pages" },
|
|
78593
|
+
{ label: "settings", value: "settings" },
|
|
78594
|
+
{ label: "exit", value: "exit" }
|
|
78388
78595
|
];
|
|
78389
78596
|
};
|
|
78390
78597
|
const home = () => drawHome(cfg, session);
|
|
@@ -78453,19 +78660,19 @@ async function runSettings(cfg) {
|
|
|
78453
78660
|
const target = cfg.active_account?.target;
|
|
78454
78661
|
const library = target?.library_name ?? target?.deployment_name ?? "not configured";
|
|
78455
78662
|
const filter = loadSyncState().project_filter;
|
|
78456
|
-
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" :
|
|
78457
|
-
const action = await menuSelect("
|
|
78458
|
-
{ label: "
|
|
78459
|
-
{ label: "
|
|
78460
|
-
{ label: "
|
|
78461
|
-
{ label: "
|
|
78462
|
-
{ label: "
|
|
78463
|
-
{ label: "
|
|
78663
|
+
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" : basename6(dir)).join(", ") : `${filter.length} projects`;
|
|
78664
|
+
const action = await menuSelect("settings", [
|
|
78665
|
+
{ label: "switch organization", hint: target?.org_name, value: "switch-org" },
|
|
78666
|
+
{ label: "switch library", hint: library, value: "switch-library" },
|
|
78667
|
+
{ label: "study scope", hint: scope, value: "projects" },
|
|
78668
|
+
{ label: "run setup", hint: "rerun the setup wizard", value: "setup" },
|
|
78669
|
+
{ label: "log out", hint: "clear stored credentials", value: "logout" },
|
|
78670
|
+
{ label: "back", value: "back" }
|
|
78464
78671
|
]);
|
|
78465
78672
|
if (action === null || action === "back")
|
|
78466
78673
|
return;
|
|
78467
78674
|
if (action === "projects") {
|
|
78468
|
-
await
|
|
78675
|
+
await runStudyingProjectsSetting();
|
|
78469
78676
|
continue;
|
|
78470
78677
|
}
|
|
78471
78678
|
if (action === "setup") {
|
|
@@ -78488,7 +78695,7 @@ async function runSettings(cfg) {
|
|
|
78488
78695
|
}
|
|
78489
78696
|
}
|
|
78490
78697
|
function displayDir(dir) {
|
|
78491
|
-
const home =
|
|
78698
|
+
const home = homedir11();
|
|
78492
78699
|
return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
|
|
78493
78700
|
}
|
|
78494
78701
|
function discoverProjectDirs() {
|
|
@@ -78503,7 +78710,7 @@ function discoverProjectDirs() {
|
|
|
78503
78710
|
} catch {}
|
|
78504
78711
|
return [...counts.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([dir]) => dir);
|
|
78505
78712
|
}
|
|
78506
|
-
async function
|
|
78713
|
+
async function runStudyingProjectsSetting() {
|
|
78507
78714
|
const dirs = discoverProjectDirs();
|
|
78508
78715
|
if (dirs.length === 0) {
|
|
78509
78716
|
log2.info("No local agent sessions found yet; nothing to scope.");
|
|
@@ -79230,7 +79437,7 @@ import {
|
|
|
79230
79437
|
constants as constants3,
|
|
79231
79438
|
fstatSync,
|
|
79232
79439
|
openSync as openSync5,
|
|
79233
|
-
readFileSync as
|
|
79440
|
+
readFileSync as readFileSync26,
|
|
79234
79441
|
readSync as readSync4,
|
|
79235
79442
|
unlinkSync as unlinkSync5
|
|
79236
79443
|
} from "node:fs";
|
|
@@ -82432,23 +82639,23 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82432
82639
|
console.log(` ID: ${store.id}`);
|
|
82433
82640
|
console.log(` Space ID: ${store.space_id}`);
|
|
82434
82641
|
});
|
|
82435
|
-
cmd.command("sessions").description("List local agent sessions with full project and session ids (the untruncated view of the Activity screen's tabs)").option("--queued", "Only sessions queued for studying").option("--open", "Only live sessions still inside the quiet period").option("--
|
|
82436
|
-
const all = !opts.queued && !opts.open && !opts.
|
|
82642
|
+
cmd.command("sessions").description("List local agent sessions with full project and session ids (the untruncated view of the Activity screen's tabs)").option("--queued", "Only sessions queued for studying").option("--open", "Only live sessions still inside the quiet period").option("--studied", "Only recent studied-session history").option("--json", "Output as JSON").action((opts) => {
|
|
82643
|
+
const all = !opts.queued && !opts.open && !opts.studied;
|
|
82437
82644
|
const wantQueued = all || Boolean(opts.queued);
|
|
82438
82645
|
const wantOpen = all || Boolean(opts.open);
|
|
82439
|
-
const
|
|
82646
|
+
const wantStudied = all || Boolean(opts.studied);
|
|
82440
82647
|
const backlog = wantQueued || wantOpen ? listSessionBacklog() : { queued: [], open: [] };
|
|
82441
|
-
const
|
|
82648
|
+
const studied = wantStudied ? loadSyncState().mined_sessions ?? [] : [];
|
|
82442
82649
|
if (opts.json) {
|
|
82443
82650
|
printResult({
|
|
82444
82651
|
...wantQueued ? { queued: backlog.queued } : {},
|
|
82445
82652
|
...wantOpen ? { open: backlog.open } : {},
|
|
82446
|
-
...
|
|
82653
|
+
...wantStudied ? { studied } : {}
|
|
82447
82654
|
}, opts);
|
|
82448
82655
|
return;
|
|
82449
82656
|
}
|
|
82450
82657
|
const sessionRows = (sessions) => sessions.map((s) => [s.harness, s.updated, s.project ?? "-", s.id]);
|
|
82451
|
-
const
|
|
82658
|
+
const studiedRows = studied.map((record2) => {
|
|
82452
82659
|
const slash = record2.session.indexOf("/");
|
|
82453
82660
|
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
82454
82661
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
@@ -82472,8 +82679,8 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82472
82679
|
if (wantOpen) {
|
|
82473
82680
|
section("Open", sessionRows(backlog.open), "Updated", "No open sessions. Live agent sessions sit here until they go quiet.");
|
|
82474
82681
|
}
|
|
82475
|
-
if (
|
|
82476
|
-
section("Studied",
|
|
82682
|
+
if (wantStudied) {
|
|
82683
|
+
section("Studied", studiedRows, "Studied at", "No studied sessions recorded yet.");
|
|
82477
82684
|
}
|
|
82478
82685
|
});
|
|
82479
82686
|
cmd.command("sync").description("Scan local agent session history and report the study backlog").option("--quiet", "Background mode for hooks: honor backoff, exit 0, print nothing").option("--detach", "Re-spawn detached and return immediately (used by agent hooks)").option("--bootstrap", "Backfill mode: study the full local session history regardless of age and drain the backlog (used by setup)").option("--status", "Show whether a sync is running now, plus watermark and recent activity").option("--report", "Write the same HTML harvest report as the log-to-dosu-knowledge skill and open it").option("--out <path>", "HTML report path (default: tmp/dosu-knowledge-report.html)").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -82497,7 +82704,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82497
82704
|
]);
|
|
82498
82705
|
return;
|
|
82499
82706
|
}
|
|
82500
|
-
const deps = { mine:
|
|
82707
|
+
const deps = { mine: buildLearner(opts.quiet ? "hook" : "manual") };
|
|
82501
82708
|
let outcome = await runKnowledgeSync({
|
|
82502
82709
|
quiet: opts.quiet,
|
|
82503
82710
|
bootstrap: opts.bootstrap,
|
|
@@ -82505,7 +82712,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82505
82712
|
});
|
|
82506
82713
|
if (opts.bootstrap && deps.mine) {
|
|
82507
82714
|
const maxRounds = Math.ceil(outcome.readySessions / MINE_BATCH_LIMIT) + 2;
|
|
82508
|
-
for (let round = 1;outcome.status === "
|
|
82715
|
+
for (let round = 1;outcome.status === "studied" && round < maxRounds; round++) {
|
|
82509
82716
|
if (!opts.quiet && !opts.json)
|
|
82510
82717
|
printSyncOutcome(outcome);
|
|
82511
82718
|
outcome = await runKnowledgeSync({ quiet: opts.quiet, bootstrap: true, deps });
|
|
@@ -82567,7 +82774,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82567
82774
|
cmd.addCommand(hooksCommand());
|
|
82568
82775
|
return cmd;
|
|
82569
82776
|
}
|
|
82570
|
-
function
|
|
82777
|
+
function buildLearner(trigger) {
|
|
82571
82778
|
const cfg = loadConfig();
|
|
82572
82779
|
if (cfg.mode === "oss")
|
|
82573
82780
|
return;
|
|
@@ -82576,8 +82783,8 @@ function buildMiner(trigger) {
|
|
|
82576
82783
|
return;
|
|
82577
82784
|
const { api_key, deployment_id } = target;
|
|
82578
82785
|
return async (sessions) => {
|
|
82579
|
-
const {
|
|
82580
|
-
return
|
|
82786
|
+
const { runLearner: runLearner2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
82787
|
+
return runLearner2({ sessions, apiKey: api_key, deploymentID: deployment_id, trigger });
|
|
82581
82788
|
};
|
|
82582
82789
|
}
|
|
82583
82790
|
function formatAge(iso, now) {
|
|
@@ -82645,21 +82852,21 @@ function printSyncOutcome(outcome) {
|
|
|
82645
82852
|
console.log(import_picocolors18.default.dim("Sign in with 'dosu setup' to enable studying."));
|
|
82646
82853
|
break;
|
|
82647
82854
|
}
|
|
82648
|
-
case "
|
|
82649
|
-
const notes = outcome.
|
|
82650
|
-
const remaining = outcome.readySessions - (outcome.
|
|
82651
|
-
console.log(`✓ Studied ${outcome.
|
|
82855
|
+
case "studied": {
|
|
82856
|
+
const notes = outcome.learner?.notesWritten ?? 0;
|
|
82857
|
+
const remaining = outcome.readySessions - (outcome.studiedSessions ?? 0);
|
|
82858
|
+
console.log(`✓ Studied ${outcome.studiedSessions} session${outcome.studiedSessions === 1 ? "" : "s"}, ${notes} suggested page${notes === 1 ? "" : "s"} created.`);
|
|
82652
82859
|
if (remaining > 0) {
|
|
82653
82860
|
console.log(import_picocolors18.default.dim(`${remaining} more in the backlog; run sync again to continue.`));
|
|
82654
82861
|
}
|
|
82655
82862
|
break;
|
|
82656
82863
|
}
|
|
82657
82864
|
case "skipped-gateway": {
|
|
82658
|
-
console.log(import_picocolors18.default.yellow(outcome.
|
|
82865
|
+
console.log(import_picocolors18.default.yellow(outcome.learner?.message ?? "Studying unavailable right now."));
|
|
82659
82866
|
break;
|
|
82660
82867
|
}
|
|
82661
82868
|
case "mine-failed": {
|
|
82662
|
-
console.error(import_picocolors18.default.red(outcome.
|
|
82869
|
+
console.error(import_picocolors18.default.red(outcome.learner?.message ?? "Study run failed."));
|
|
82663
82870
|
process.exitCode = 1;
|
|
82664
82871
|
break;
|
|
82665
82872
|
}
|
|
@@ -84600,7 +84807,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84600
84807
|
}
|
|
84601
84808
|
if (opts.follow) {
|
|
84602
84809
|
try {
|
|
84603
|
-
const lines =
|
|
84810
|
+
const lines = readFileSync26(logPath, "utf-8").split(`
|
|
84604
84811
|
`);
|
|
84605
84812
|
if (lines.at(-1) === "")
|
|
84606
84813
|
lines.pop();
|
|
@@ -84616,7 +84823,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84616
84823
|
if (opts.tail !== undefined) {
|
|
84617
84824
|
const n5 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
|
|
84618
84825
|
try {
|
|
84619
|
-
const content =
|
|
84826
|
+
const content = readFileSync26(logPath, "utf-8");
|
|
84620
84827
|
const lines = content.split(`
|
|
84621
84828
|
`);
|
|
84622
84829
|
console.log(lines.slice(-n5).join(`
|
|
@@ -84687,4 +84894,4 @@ execute().catch((err) => {
|
|
|
84687
84894
|
process.exit(1);
|
|
84688
84895
|
});
|
|
84689
84896
|
|
|
84690
|
-
//# debugId=
|
|
84897
|
+
//# debugId=33c6d65c-87a5-5e97-6475-6e2164756e21
|