@dosu/cli 0.53.0-beta.12 → 0.53.0-beta.14
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 +675 -435
- 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.14", 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 } : {},
|
|
@@ -8595,13 +8743,24 @@ function setSyncPaused(paused, configDir = getConfigDir()) {
|
|
|
8595
8743
|
delete state.paused;
|
|
8596
8744
|
saveSyncState(state, configDir);
|
|
8597
8745
|
}
|
|
8746
|
+
function resetSyncState(configDir = getConfigDir()) {
|
|
8747
|
+
const previous = loadSyncState(configDir);
|
|
8748
|
+
const fresh = {
|
|
8749
|
+
schema_version: STATE_SCHEMA_VERSION,
|
|
8750
|
+
watermark: null,
|
|
8751
|
+
consecutive_failures: 0,
|
|
8752
|
+
...previous.project_filter ? { project_filter: previous.project_filter } : {},
|
|
8753
|
+
...previous.paused ? { paused: true } : {}
|
|
8754
|
+
};
|
|
8755
|
+
saveSyncState(fresh, configDir);
|
|
8756
|
+
}
|
|
8598
8757
|
function saveSyncState(state, configDir = getConfigDir()) {
|
|
8599
|
-
if (!
|
|
8600
|
-
|
|
8758
|
+
if (!existsSync11(configDir))
|
|
8759
|
+
mkdirSync7(configDir, { recursive: true, mode: 448 });
|
|
8601
8760
|
const path3 = syncStatePath(configDir);
|
|
8602
8761
|
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8603
|
-
|
|
8604
|
-
|
|
8762
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
8763
|
+
renameSync3(tmp, path3);
|
|
8605
8764
|
}
|
|
8606
8765
|
function backoffUntil(state) {
|
|
8607
8766
|
if (state.consecutive_failures === 0 || !state.last_attempt_at)
|
|
@@ -8645,7 +8804,7 @@ function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAU
|
|
|
8645
8804
|
}
|
|
8646
8805
|
return { ready, open: open2 };
|
|
8647
8806
|
}
|
|
8648
|
-
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)";
|
|
8649
8808
|
var init_watermark = __esm(() => {
|
|
8650
8809
|
init_config();
|
|
8651
8810
|
DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
|
|
@@ -8661,20 +8820,34 @@ async function fetchReportNotes(cfg) {
|
|
|
8661
8820
|
}
|
|
8662
8821
|
const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
|
|
8663
8822
|
const client = createTypedClient2(cfg);
|
|
8664
|
-
const
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
...
|
|
8674
|
-
|
|
8675
|
-
|
|
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 };
|
|
8676
8849
|
}
|
|
8677
|
-
var REPORT_NOTES_LIMIT = 500;
|
|
8850
|
+
var REPORT_NOTES_LIMIT = 500, REPORT_NOTES_MAX_PAGES = 40;
|
|
8678
8851
|
|
|
8679
8852
|
// src/report/css.ts
|
|
8680
8853
|
var REPORT_CSS = `
|
|
@@ -8849,38 +9022,6 @@ function applyStatusDefaults(candidates, dryRun) {
|
|
|
8849
9022
|
return { ...c2, status: known.has(current) ? current : fallback };
|
|
8850
9023
|
});
|
|
8851
9024
|
}
|
|
8852
|
-
function tokenTotalsFromCandidates(candidates, inventory) {
|
|
8853
|
-
if (candidates.length === 0)
|
|
8854
|
-
return null;
|
|
8855
|
-
let replaced = 0;
|
|
8856
|
-
let hasRediscovery = false;
|
|
8857
|
-
let readCost = 0;
|
|
8858
|
-
for (const c2 of candidates) {
|
|
8859
|
-
const raw = c2.approx_rediscovery_tokens;
|
|
8860
|
-
if (raw != null) {
|
|
8861
|
-
hasRediscovery = true;
|
|
8862
|
-
replaced += Math.max(0, Number(raw) || 0);
|
|
8863
|
-
}
|
|
8864
|
-
const blob = `${c2.title ?? ""}
|
|
8865
|
-
${c2.content ?? ""}`;
|
|
8866
|
-
readCost += Math.round(blob.length / CHARS_PER_TOKEN);
|
|
8867
|
-
}
|
|
8868
|
-
if (!hasRediscovery)
|
|
8869
|
-
return null;
|
|
8870
|
-
let baseline = inventory.totals?.learning_tokens ?? 0;
|
|
8871
|
-
if (!baseline) {
|
|
8872
|
-
baseline = inventory.transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
8873
|
-
}
|
|
8874
|
-
const saved = Math.max(0, replaced - readCost);
|
|
8875
|
-
const pct = baseline ? Math.round(1000 * saved / baseline) / 10 : 0;
|
|
8876
|
-
return {
|
|
8877
|
-
baseline_tokens: baseline,
|
|
8878
|
-
replaced_baseline_tokens: replaced,
|
|
8879
|
-
read_knowledge_tokens: readCost,
|
|
8880
|
-
tokens_saved: saved,
|
|
8881
|
-
pct_saved: pct
|
|
8882
|
-
};
|
|
8883
|
-
}
|
|
8884
9025
|
function presentationCopy(c2) {
|
|
8885
9026
|
const idea = (c2.plain_english || "").trim() || (c2.content || "").trim();
|
|
8886
9027
|
const how = (c2.how_found || "").trim();
|
|
@@ -9254,11 +9395,9 @@ function buildReportHtml(options) {
|
|
|
9254
9395
|
return bv - av;
|
|
9255
9396
|
});
|
|
9256
9397
|
const org = options.orgName || "Your team";
|
|
9257
|
-
const
|
|
9258
|
-
const
|
|
9259
|
-
const summary = options.summary || "Local agent session logs were mined into Dosu notes so the next task can reuse them — reducing rediscovery cost.";
|
|
9398
|
+
const projects = options.projects ?? [];
|
|
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.";
|
|
9260
9400
|
const generated = `${(options.generatedAt ?? new Date).toISOString().replace("T", " ").slice(0, 16)} UTC`;
|
|
9261
|
-
const derived = tokenTotalsFromCandidates(candidates, inventory);
|
|
9262
9401
|
const [notesHeading, notesLede, footerTitle, footerBody] = notesSectionCopy(candidates);
|
|
9263
9402
|
const candidateRows = candidates.map((c2, i2) => {
|
|
9264
9403
|
const [idea, work] = presentationCopy(c2);
|
|
@@ -9298,27 +9437,19 @@ function buildReportHtml(options) {
|
|
|
9298
9437
|
<td>${esc(query)}</td>
|
|
9299
9438
|
</tr>`;
|
|
9300
9439
|
}).join("");
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
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 = `
|
|
9304
9445
|
<section>
|
|
9305
|
-
<h2>
|
|
9306
|
-
<p class="lede">Counterfactual: replace rediscovery stretches with a Dosu <code>read_knowledge</code> hit.</p>
|
|
9446
|
+
<h2>This run</h2>
|
|
9307
9447
|
<div class="stats">
|
|
9308
|
-
<div class="stat"><div class="label">
|
|
9309
|
-
<div class="stat"><div class="label"
|
|
9310
|
-
<div class="stat"><div class="label">Read cost</div><div class="value">${fmtInt(derived.read_knowledge_tokens)}</div></div>
|
|
9311
|
-
<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>
|
|
9312
9450
|
</div>
|
|
9313
9451
|
</section>`;
|
|
9314
|
-
|
|
9315
|
-
tokenSection = `
|
|
9316
|
-
<section>
|
|
9317
|
-
<h2>Estimated context savings</h2>
|
|
9318
|
-
<p class="muted">No rediscovery estimates on the notes yet — each candidate needs <code>approx_rediscovery_tokens</code>.</p>
|
|
9319
|
-
</section>`;
|
|
9320
|
-
}
|
|
9321
|
-
const notesBody = candidateRows.join("") || '<p class="muted">No write_knowledge payloads yet. Run knowledge sync so the miner extracts learnings.</p>';
|
|
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>';
|
|
9322
9453
|
const lede = notesLede ? `<p class="lede">${esc(notesLede)}</p>` : "";
|
|
9323
9454
|
return `<!DOCTYPE html>
|
|
9324
9455
|
<html lang="en">
|
|
@@ -9337,9 +9468,8 @@ ${REPORT_CSS}
|
|
|
9337
9468
|
<h1>${esc(org)}</h1>
|
|
9338
9469
|
<p class="lede">${esc(summary)}</p>
|
|
9339
9470
|
<p class="meta-line">
|
|
9340
|
-
|
|
9341
|
-
·
|
|
9342
|
-
· 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)" : ""}
|
|
9343
9473
|
</p>
|
|
9344
9474
|
<div class="toolbar no-print">
|
|
9345
9475
|
<button type="button" onclick="window.print()">Print / Save as PDF</button>
|
|
@@ -9385,7 +9515,7 @@ ${REPORT_CSS}
|
|
|
9385
9515
|
</html>
|
|
9386
9516
|
`;
|
|
9387
9517
|
}
|
|
9388
|
-
var
|
|
9518
|
+
var MAX_TRACE_STEPS = 50, PREVIEW_CHARS = 80, SQL_DISPLAY, LOGS_DISPLAY, SKIP_TOOL_NAMES, TAG_RE, WS_RE;
|
|
9389
9519
|
var init_html = __esm(() => {
|
|
9390
9520
|
SQL_DISPLAY = new Set(["execute_sql", "query_run", "list_tables"]);
|
|
9391
9521
|
LOGS_DISPLAY = new Set(["query_logs"]);
|
|
@@ -9395,7 +9525,7 @@ var init_html = __esm(() => {
|
|
|
9395
9525
|
});
|
|
9396
9526
|
|
|
9397
9527
|
// src/sessions/read.ts
|
|
9398
|
-
import { readFileSync as
|
|
9528
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
9399
9529
|
function asRecord(value) {
|
|
9400
9530
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9401
9531
|
}
|
|
@@ -9511,11 +9641,11 @@ function readSessionTurns(session) {
|
|
|
9511
9641
|
try {
|
|
9512
9642
|
switch (session.harness) {
|
|
9513
9643
|
case "claude":
|
|
9514
|
-
return readClaude(
|
|
9644
|
+
return readClaude(readFileSync11(session.path, "utf8"));
|
|
9515
9645
|
case "cursor":
|
|
9516
|
-
return readCursor(
|
|
9646
|
+
return readCursor(readFileSync11(session.path, "utf8"));
|
|
9517
9647
|
case "codex":
|
|
9518
|
-
return readCodex(
|
|
9648
|
+
return readCodex(readFileSync11(session.path, "utf8"));
|
|
9519
9649
|
case "opencode":
|
|
9520
9650
|
return readOpencode(session.path, session.id);
|
|
9521
9651
|
}
|
|
@@ -9543,7 +9673,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9543
9673
|
case "claude":
|
|
9544
9674
|
case "cursor": {
|
|
9545
9675
|
let n2 = 0;
|
|
9546
|
-
for (const record of jsonlRecords(
|
|
9676
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9547
9677
|
const message = asRecord(record.message);
|
|
9548
9678
|
for (const name of toolNamesFromContent(message?.content)) {
|
|
9549
9679
|
if (isRediscoveryTool(name))
|
|
@@ -9554,7 +9684,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9554
9684
|
}
|
|
9555
9685
|
case "codex": {
|
|
9556
9686
|
let n2 = 0;
|
|
9557
|
-
for (const record of jsonlRecords(
|
|
9687
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9558
9688
|
const payload = asRecord(record.payload);
|
|
9559
9689
|
if (payload?.type === "function_call" && typeof payload.name === "string") {
|
|
9560
9690
|
if (isRediscoveryTool(payload.name))
|
|
@@ -9574,9 +9704,9 @@ function estimateSessionTokens(session) {
|
|
|
9574
9704
|
let chars = 0;
|
|
9575
9705
|
for (const turn of readSessionTurns(session))
|
|
9576
9706
|
chars += turn.text.length;
|
|
9577
|
-
return Math.round(chars /
|
|
9707
|
+
return Math.round(chars / CHARS_PER_TOKEN);
|
|
9578
9708
|
}
|
|
9579
|
-
function
|
|
9709
|
+
function isWorthStudying(session) {
|
|
9580
9710
|
const turns = readSessionTurns(session);
|
|
9581
9711
|
if (turns.length < MIN_WORTH_TURNS)
|
|
9582
9712
|
return false;
|
|
@@ -9588,7 +9718,7 @@ function isWorthMining(session) {
|
|
|
9588
9718
|
}
|
|
9589
9719
|
return false;
|
|
9590
9720
|
}
|
|
9591
|
-
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;
|
|
9592
9722
|
var init_read = __esm(() => {
|
|
9593
9723
|
init_scan();
|
|
9594
9724
|
PLAIN_TEXT = new Set(["text"]);
|
|
@@ -9645,12 +9775,12 @@ var init_queries = __esm(() => {
|
|
|
9645
9775
|
});
|
|
9646
9776
|
|
|
9647
9777
|
// src/report/digest.ts
|
|
9648
|
-
import { readFileSync as
|
|
9778
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
9649
9779
|
function asRecord2(value) {
|
|
9650
9780
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9651
9781
|
}
|
|
9652
9782
|
function estimateTokens(text) {
|
|
9653
|
-
return Math.round(text.length /
|
|
9783
|
+
return Math.round(text.length / CHARS_PER_TOKEN2);
|
|
9654
9784
|
}
|
|
9655
9785
|
function digestEstTokens(texts, tools, resultChars) {
|
|
9656
9786
|
const countable = tools.filter((t2) => t2.name !== "tool_result");
|
|
@@ -9660,7 +9790,7 @@ function digestEstTokens(texts, tools, resultChars) {
|
|
|
9660
9790
|
payload += JSON.stringify(countable);
|
|
9661
9791
|
let tokens = estimateTokens(payload);
|
|
9662
9792
|
if (resultChars > 0)
|
|
9663
|
-
tokens += Math.max(1, Math.round(resultChars /
|
|
9793
|
+
tokens += Math.max(1, Math.round(resultChars / CHARS_PER_TOKEN2));
|
|
9664
9794
|
return tokens;
|
|
9665
9795
|
}
|
|
9666
9796
|
function copyToolInputFields(entry, inp) {
|
|
@@ -9876,16 +10006,16 @@ function sessionToDigest(session) {
|
|
|
9876
10006
|
try {
|
|
9877
10007
|
switch (session.harness) {
|
|
9878
10008
|
case "claude":
|
|
9879
|
-
return { turns: digestClaude(
|
|
10009
|
+
return { turns: digestClaude(readFileSync12(session.path, "utf8")) };
|
|
9880
10010
|
case "cursor":
|
|
9881
|
-
return { turns: digestCursor(
|
|
10011
|
+
return { turns: digestCursor(readFileSync12(session.path, "utf8")) };
|
|
9882
10012
|
case "codex":
|
|
9883
|
-
return { turns: digestCodex(
|
|
10013
|
+
return { turns: digestCodex(readFileSync12(session.path, "utf8")) };
|
|
9884
10014
|
case "opencode": {
|
|
9885
10015
|
const turns = readSessionTurns(session).map((turn, index) => ({
|
|
9886
10016
|
role: turn.role,
|
|
9887
10017
|
line: index + 1,
|
|
9888
|
-
est_tokens: Math.round(turn.text.length /
|
|
10018
|
+
est_tokens: Math.round(turn.text.length / CHARS_PER_TOKEN2),
|
|
9889
10019
|
text: [turn.text],
|
|
9890
10020
|
tools: []
|
|
9891
10021
|
}));
|
|
@@ -9910,7 +10040,7 @@ function digestTurnText(turn) {
|
|
|
9910
10040
|
return texts.filter(Boolean).join(`
|
|
9911
10041
|
`);
|
|
9912
10042
|
}
|
|
9913
|
-
var
|
|
10043
|
+
var CHARS_PER_TOKEN2 = 4, MAX_ASSISTANT_CHARS = 4000, MCP_DIGEST_TOOLS;
|
|
9914
10044
|
var init_digest = __esm(() => {
|
|
9915
10045
|
init_read();
|
|
9916
10046
|
init_queries();
|
|
@@ -10101,17 +10231,17 @@ var init_notes = __esm(() => {
|
|
|
10101
10231
|
});
|
|
10102
10232
|
|
|
10103
10233
|
// src/report/write.ts
|
|
10104
|
-
import { mkdirSync as
|
|
10234
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10105
10235
|
import { tmpdir } from "node:os";
|
|
10106
|
-
import { dirname as dirname4, join as
|
|
10236
|
+
import { dirname as dirname4, join as join11, resolve } from "node:path";
|
|
10107
10237
|
import { pathToFileURL } from "node:url";
|
|
10108
10238
|
function defaultReportPath() {
|
|
10109
|
-
return
|
|
10239
|
+
return join11(tmpdir(), "dosu-knowledge-report.html");
|
|
10110
10240
|
}
|
|
10111
10241
|
async function writeAndOpenReport(options) {
|
|
10112
10242
|
const out = resolve(options.out ?? defaultReportPath());
|
|
10113
|
-
|
|
10114
|
-
|
|
10243
|
+
mkdirSync8(dirname4(out), { recursive: true });
|
|
10244
|
+
writeFileSync8(out, options.html, "utf8");
|
|
10115
10245
|
if (options.open !== false) {
|
|
10116
10246
|
const url = pathToFileURL(out).href;
|
|
10117
10247
|
if (options.openUrl) {
|
|
@@ -10126,17 +10256,26 @@ async function writeAndOpenReport(options) {
|
|
|
10126
10256
|
var init_write = () => {};
|
|
10127
10257
|
|
|
10128
10258
|
// src/report/generate.ts
|
|
10259
|
+
import { basename as basename2 } from "node:path";
|
|
10129
10260
|
function sessionsForNotes(notes, sessions) {
|
|
10130
10261
|
const ids = new Set(notes.map((n2) => n2.transcript_id).filter((id) => Boolean(id)));
|
|
10131
10262
|
return sessions.filter((s) => ids.has(s.id));
|
|
10132
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
|
+
}
|
|
10133
10271
|
function noteTime(note) {
|
|
10134
10272
|
const parsed = Date.parse(note.at ?? "");
|
|
10135
10273
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10136
10274
|
}
|
|
10137
10275
|
async function emitKnowledgeReport(options = {}) {
|
|
10138
10276
|
const cfg = loadConfig();
|
|
10139
|
-
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));
|
|
10140
10279
|
const scanned = options.sessions ?? scanAgentSessions();
|
|
10141
10280
|
const sessions = sessionsForNotes(notes, scanned);
|
|
10142
10281
|
const candidates = attributeRediscovery(notes, sessions);
|
|
@@ -10145,12 +10284,16 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10145
10284
|
if (inventory.totals && state.total_learning_tokens && !options.sessions) {
|
|
10146
10285
|
inventory.totals.learning_tokens = state.total_learning_tokens;
|
|
10147
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
|
+
];
|
|
10148
10291
|
const html = buildReportHtml({
|
|
10149
10292
|
inventory,
|
|
10150
10293
|
candidates,
|
|
10151
10294
|
orgName: options.orgName ?? cfg.active_account?.target?.org_name ?? "Your team",
|
|
10152
|
-
|
|
10153
|
-
|
|
10295
|
+
projects,
|
|
10296
|
+
truncated: fetched.truncated,
|
|
10154
10297
|
generatedAt: options.generatedAt,
|
|
10155
10298
|
digests: digestsForSessions(sessions)
|
|
10156
10299
|
});
|
|
@@ -10163,6 +10306,7 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10163
10306
|
}
|
|
10164
10307
|
var init_generate = __esm(() => {
|
|
10165
10308
|
init_config();
|
|
10309
|
+
init_project_dir();
|
|
10166
10310
|
init_scan();
|
|
10167
10311
|
init_watermark();
|
|
10168
10312
|
init_html();
|
|
@@ -10170,151 +10314,6 @@ var init_generate = __esm(() => {
|
|
|
10170
10314
|
init_write();
|
|
10171
10315
|
});
|
|
10172
10316
|
|
|
10173
|
-
// src/sessions/project-dir.ts
|
|
10174
|
-
import {
|
|
10175
|
-
closeSync as closeSync2,
|
|
10176
|
-
existsSync as existsSync11,
|
|
10177
|
-
mkdirSync as mkdirSync8,
|
|
10178
|
-
openSync as openSync2,
|
|
10179
|
-
readFileSync as readFileSync12,
|
|
10180
|
-
readSync,
|
|
10181
|
-
renameSync as renameSync3,
|
|
10182
|
-
statSync as statSync4,
|
|
10183
|
-
writeFileSync as writeFileSync8
|
|
10184
|
-
} from "node:fs";
|
|
10185
|
-
import { join as join11 } from "node:path";
|
|
10186
|
-
function cachePath(configDir) {
|
|
10187
|
-
return join11(configDir, CACHE_FILENAME2);
|
|
10188
|
-
}
|
|
10189
|
-
function loadCacheFile(configDir) {
|
|
10190
|
-
try {
|
|
10191
|
-
const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
|
|
10192
|
-
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
10193
|
-
return {};
|
|
10194
|
-
return raw.entries;
|
|
10195
|
-
} catch {
|
|
10196
|
-
return {};
|
|
10197
|
-
}
|
|
10198
|
-
}
|
|
10199
|
-
function saveCacheFile(configDir, entries) {
|
|
10200
|
-
try {
|
|
10201
|
-
if (!existsSync11(configDir))
|
|
10202
|
-
mkdirSync8(configDir, { recursive: true, mode: 448 });
|
|
10203
|
-
const path3 = cachePath(configDir);
|
|
10204
|
-
const tmp = `${path3}.${process.pid}.tmp`;
|
|
10205
|
-
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
10206
|
-
writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
|
|
10207
|
-
renameSync3(tmp, path3);
|
|
10208
|
-
} catch {}
|
|
10209
|
-
}
|
|
10210
|
-
function readHead(path3, bytes = HEAD_BYTES) {
|
|
10211
|
-
let fd;
|
|
10212
|
-
try {
|
|
10213
|
-
fd = openSync2(path3, "r");
|
|
10214
|
-
} catch {
|
|
10215
|
-
return null;
|
|
10216
|
-
}
|
|
10217
|
-
try {
|
|
10218
|
-
const buf = Buffer.alloc(bytes);
|
|
10219
|
-
const read = readSync(fd, buf, 0, bytes, 0);
|
|
10220
|
-
return buf.subarray(0, read).toString("utf-8");
|
|
10221
|
-
} catch {
|
|
10222
|
-
return null;
|
|
10223
|
-
} finally {
|
|
10224
|
-
closeSync2(fd);
|
|
10225
|
-
}
|
|
10226
|
-
}
|
|
10227
|
-
function cwdFromJsonlHead(text) {
|
|
10228
|
-
for (const line of text.split(`
|
|
10229
|
-
`).slice(0, HEAD_LINES)) {
|
|
10230
|
-
if (!line.includes('"cwd"'))
|
|
10231
|
-
continue;
|
|
10232
|
-
try {
|
|
10233
|
-
const obj = JSON.parse(line);
|
|
10234
|
-
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
10235
|
-
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
10236
|
-
return cwd;
|
|
10237
|
-
} catch {}
|
|
10238
|
-
}
|
|
10239
|
-
return null;
|
|
10240
|
-
}
|
|
10241
|
-
function unmungeSlug(slug, exists = existsSync11) {
|
|
10242
|
-
const tokens = slug.replace(/^-/, "").split("-");
|
|
10243
|
-
if (tokens.length === 0 || tokens[0] === "")
|
|
10244
|
-
return null;
|
|
10245
|
-
let budget = 5000;
|
|
10246
|
-
const walk = (prefix, index) => {
|
|
10247
|
-
if (budget-- <= 0)
|
|
10248
|
-
return null;
|
|
10249
|
-
if (index === tokens.length)
|
|
10250
|
-
return exists(prefix) ? prefix : null;
|
|
10251
|
-
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
10252
|
-
if (asSegment)
|
|
10253
|
-
return asSegment;
|
|
10254
|
-
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
10255
|
-
};
|
|
10256
|
-
return walk("", 0);
|
|
10257
|
-
}
|
|
10258
|
-
function fileMtime(path3) {
|
|
10259
|
-
try {
|
|
10260
|
-
return statSync4(path3).mtime.toISOString();
|
|
10261
|
-
} catch {
|
|
10262
|
-
return "";
|
|
10263
|
-
}
|
|
10264
|
-
}
|
|
10265
|
-
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
10266
|
-
const exists = deps.exists ?? existsSync11;
|
|
10267
|
-
const head = deps.readHead ?? readHead;
|
|
10268
|
-
const mtime = deps.mtime ?? fileMtime;
|
|
10269
|
-
const entries = loadCacheFile(configDir);
|
|
10270
|
-
let dirty = false;
|
|
10271
|
-
const compute = (session) => {
|
|
10272
|
-
switch (session.harness) {
|
|
10273
|
-
case "opencode":
|
|
10274
|
-
return session.project ?? null;
|
|
10275
|
-
case "claude": {
|
|
10276
|
-
const text = head(session.path);
|
|
10277
|
-
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
10278
|
-
if (cwd)
|
|
10279
|
-
return cwd;
|
|
10280
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10281
|
-
}
|
|
10282
|
-
case "codex": {
|
|
10283
|
-
const text = head(session.path);
|
|
10284
|
-
return text ? cwdFromJsonlHead(text) : null;
|
|
10285
|
-
}
|
|
10286
|
-
case "cursor":
|
|
10287
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10288
|
-
default:
|
|
10289
|
-
return null;
|
|
10290
|
-
}
|
|
10291
|
-
};
|
|
10292
|
-
return {
|
|
10293
|
-
resolve(session) {
|
|
10294
|
-
const key = `${session.harness}/${session.id}`;
|
|
10295
|
-
const cached = entries[key];
|
|
10296
|
-
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
10297
|
-
return cached.dir;
|
|
10298
|
-
}
|
|
10299
|
-
const dir = compute(session);
|
|
10300
|
-
entries[key] = { dir, mtime: mtime(session.path) };
|
|
10301
|
-
dirty = true;
|
|
10302
|
-
return dir;
|
|
10303
|
-
},
|
|
10304
|
-
flush() {
|
|
10305
|
-
if (!dirty)
|
|
10306
|
-
return;
|
|
10307
|
-
saveCacheFile(configDir, entries);
|
|
10308
|
-
dirty = false;
|
|
10309
|
-
}
|
|
10310
|
-
};
|
|
10311
|
-
}
|
|
10312
|
-
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
10313
|
-
var init_project_dir = __esm(() => {
|
|
10314
|
-
init_config();
|
|
10315
|
-
HEAD_BYTES = 128 * 1024;
|
|
10316
|
-
});
|
|
10317
|
-
|
|
10318
10317
|
// src/sync/backlog.ts
|
|
10319
10318
|
function listSessionBacklog() {
|
|
10320
10319
|
try {
|
|
@@ -10459,7 +10458,7 @@ function readLockFile(configDir) {
|
|
|
10459
10458
|
}
|
|
10460
10459
|
function recentSyncActivity(log3) {
|
|
10461
10460
|
return log3.split(`
|
|
10462
|
-
`).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)\]/, ""));
|
|
10463
10462
|
}
|
|
10464
10463
|
function getSyncStatus(deps = {}) {
|
|
10465
10464
|
const configDir = deps.configDir ?? getConfigDir();
|
|
@@ -10508,7 +10507,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10508
10507
|
const state = loadState();
|
|
10509
10508
|
if (options.quiet) {
|
|
10510
10509
|
if (state.paused) {
|
|
10511
|
-
logger.debug("sync", "skipping quiet sync:
|
|
10510
|
+
logger.debug("sync", "skipping quiet sync: studying is paused");
|
|
10512
10511
|
return { status: "skipped-paused", readySessions: 0, inFlightSessions: 0, sessions: [] };
|
|
10513
10512
|
}
|
|
10514
10513
|
const retryAt = backoffUntil(state);
|
|
@@ -10518,7 +10517,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10518
10517
|
}
|
|
10519
10518
|
} else if (state.paused) {
|
|
10520
10519
|
delete state.paused;
|
|
10521
|
-
logger.debug("sync", "manual sync resumes paused
|
|
10520
|
+
logger.debug("sync", "manual sync resumes paused studying");
|
|
10522
10521
|
}
|
|
10523
10522
|
let ready;
|
|
10524
10523
|
let open2;
|
|
@@ -10580,14 +10579,14 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10580
10579
|
baseline_mined: state.total_mined ?? 0
|
|
10581
10580
|
};
|
|
10582
10581
|
}
|
|
10583
|
-
const
|
|
10582
|
+
const worthStudying = deps.worthStudying ?? isWorthStudying;
|
|
10584
10583
|
const examined = [];
|
|
10585
10584
|
const batch = [];
|
|
10586
10585
|
let trivial = 0;
|
|
10587
10586
|
for (let i2 = ready.length - 1;i2 >= 0 && batch.length < MINE_BATCH_LIMIT; i2--) {
|
|
10588
10587
|
const candidate = ready[i2];
|
|
10589
10588
|
examined.push(candidate);
|
|
10590
|
-
if (
|
|
10589
|
+
if (worthStudying(candidate)) {
|
|
10591
10590
|
batch.push(candidate);
|
|
10592
10591
|
} else {
|
|
10593
10592
|
trivial += 1;
|
|
@@ -10604,26 +10603,26 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10604
10603
|
return {
|
|
10605
10604
|
status: "nothing-new",
|
|
10606
10605
|
...base,
|
|
10607
|
-
|
|
10606
|
+
studiedSessions: 0,
|
|
10608
10607
|
trivialSessions: trivial
|
|
10609
10608
|
};
|
|
10610
10609
|
}
|
|
10611
|
-
logger.debug("sync", `
|
|
10612
|
-
const
|
|
10613
|
-
switch (
|
|
10610
|
+
logger.debug("sync", `studying ${batch.length} of ${ready.length} ready sessions (${trivial} trivial skipped)`);
|
|
10611
|
+
const learner = await deps.mine(batch);
|
|
10612
|
+
switch (learner.outcome) {
|
|
10614
10613
|
case "completed": {
|
|
10615
10614
|
for (const s of batch) {
|
|
10616
|
-
logger.debug("sync", `
|
|
10615
|
+
logger.debug("sync", `studied session ${s.harness}/${s.id}`);
|
|
10617
10616
|
}
|
|
10618
|
-
const
|
|
10617
|
+
const studiedAt = now().toISOString();
|
|
10619
10618
|
const history = [
|
|
10620
10619
|
...state.mined_sessions ?? [],
|
|
10621
10620
|
...batch.map((s) => ({
|
|
10622
|
-
at:
|
|
10621
|
+
at: studiedAt,
|
|
10623
10622
|
session: `${s.harness}/${s.id}`,
|
|
10624
10623
|
...s.project ? { project: s.project } : {}
|
|
10625
10624
|
}))
|
|
10626
|
-
].slice(-
|
|
10625
|
+
].slice(-STUDIED_HISTORY_LIMIT);
|
|
10627
10626
|
const watermark = batchWatermark(examined);
|
|
10628
10627
|
const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
|
|
10629
10628
|
let batchTokens = 0;
|
|
@@ -10632,21 +10631,21 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10632
10631
|
saveState({
|
|
10633
10632
|
...state,
|
|
10634
10633
|
watermark,
|
|
10635
|
-
last_attempt_at:
|
|
10634
|
+
last_attempt_at: studiedAt,
|
|
10636
10635
|
consecutive_failures: 0,
|
|
10637
10636
|
mined_sessions: history,
|
|
10638
10637
|
total_mined: (state.total_mined ?? 0) + batch.length,
|
|
10639
|
-
total_notes: (state.total_notes ?? 0) +
|
|
10638
|
+
total_notes: (state.total_notes ?? 0) + learner.notesWritten,
|
|
10640
10639
|
total_learning_tokens: (state.total_learning_tokens ?? 0) + batchTokens,
|
|
10641
10640
|
last_refusal: undefined
|
|
10642
10641
|
});
|
|
10643
|
-
logger.debug("sync", `
|
|
10642
|
+
logger.debug("sync", `studied ${batch.length} sessions, ${learner.notesWritten} suggested pages; watermark → ${watermark}`);
|
|
10644
10643
|
return {
|
|
10645
|
-
status: "
|
|
10644
|
+
status: "studied",
|
|
10646
10645
|
...base,
|
|
10647
|
-
|
|
10646
|
+
studiedSessions: batch.length,
|
|
10648
10647
|
trivialSessions: trivial,
|
|
10649
|
-
|
|
10648
|
+
learner
|
|
10650
10649
|
};
|
|
10651
10650
|
}
|
|
10652
10651
|
case "consent_off":
|
|
@@ -10659,12 +10658,12 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10659
10658
|
consecutive_failures: 0,
|
|
10660
10659
|
last_refusal: {
|
|
10661
10660
|
at,
|
|
10662
|
-
outcome:
|
|
10663
|
-
message:
|
|
10661
|
+
outcome: learner.outcome,
|
|
10662
|
+
message: learner.message ?? "Studying unavailable right now."
|
|
10664
10663
|
}
|
|
10665
10664
|
});
|
|
10666
|
-
logger.debug("sync", `
|
|
10667
|
-
return { status: "skipped-gateway", ...base,
|
|
10665
|
+
logger.debug("sync", `studying skipped by gateway: ${learner.outcome}`);
|
|
10666
|
+
return { status: "skipped-gateway", ...base, studiedSessions: 0, learner };
|
|
10668
10667
|
}
|
|
10669
10668
|
default: {
|
|
10670
10669
|
saveState({
|
|
@@ -10672,13 +10671,13 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10672
10671
|
last_attempt_at: now().toISOString(),
|
|
10673
10672
|
consecutive_failures: state.consecutive_failures + 1
|
|
10674
10673
|
});
|
|
10675
|
-
logger.debug("sync", `
|
|
10674
|
+
logger.debug("sync", `studying failed: ${learner.outcome}; ${learner.message ?? ""}`);
|
|
10676
10675
|
return {
|
|
10677
10676
|
status: "mine-failed",
|
|
10678
10677
|
...base,
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
error:
|
|
10678
|
+
studiedSessions: 0,
|
|
10679
|
+
learner,
|
|
10680
|
+
error: learner.message
|
|
10682
10681
|
};
|
|
10683
10682
|
}
|
|
10684
10683
|
}
|
|
@@ -10824,12 +10823,12 @@ var init_backfill_run = __esm(() => {
|
|
|
10824
10823
|
init_backfill();
|
|
10825
10824
|
});
|
|
10826
10825
|
|
|
10827
|
-
// src/
|
|
10826
|
+
// src/learner/config-dir.ts
|
|
10828
10827
|
import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10829
10828
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
10830
10829
|
import { join as join13 } from "node:path";
|
|
10831
10830
|
function createRunConfigDir() {
|
|
10832
|
-
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-
|
|
10831
|
+
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-learner-"));
|
|
10833
10832
|
writeFileSync10(join13(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
|
|
10834
10833
|
return {
|
|
10835
10834
|
path: path3,
|
|
@@ -10840,7 +10839,7 @@ function createRunConfigDir() {
|
|
|
10840
10839
|
}
|
|
10841
10840
|
var init_config_dir = () => {};
|
|
10842
10841
|
|
|
10843
|
-
// src/
|
|
10842
|
+
// src/learner/conflicts.ts
|
|
10844
10843
|
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
|
|
10845
10844
|
function managedSettingsPaths(platform3 = process.platform) {
|
|
10846
10845
|
switch (platform3) {
|
|
@@ -10898,11 +10897,11 @@ var init_conflicts = __esm(() => {
|
|
|
10898
10897
|
CONFLICTING_ENV_PREFIXES = ["ANTHROPIC_", "CLAUDE_CODE_", "AWS_"];
|
|
10899
10898
|
});
|
|
10900
10899
|
|
|
10901
|
-
// src/
|
|
10900
|
+
// src/learner/env.ts
|
|
10902
10901
|
function isStripped(name) {
|
|
10903
10902
|
return STRIPPED_NAMES.has(name) || STRIPPED_PREFIXES.some((p2) => name.startsWith(p2));
|
|
10904
10903
|
}
|
|
10905
|
-
function
|
|
10904
|
+
function buildLearnerEnv(options) {
|
|
10906
10905
|
const base = options.baseEnv ?? process.env;
|
|
10907
10906
|
const env = {};
|
|
10908
10907
|
for (const [name, value] of Object.entries(base)) {
|
|
@@ -10932,7 +10931,7 @@ var init_env = __esm(() => {
|
|
|
10932
10931
|
STRIPPED_NAMES = new Set(["CLAUDE_CONFIG_DIR", "CLAUDECODE", "AWS_BEARER_TOKEN_BEDROCK"]);
|
|
10933
10932
|
});
|
|
10934
10933
|
|
|
10935
|
-
// src/
|
|
10934
|
+
// src/learner/executable.ts
|
|
10936
10935
|
import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
|
|
10937
10936
|
import { createRequire as createRequire3 } from "node:module";
|
|
10938
10937
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -10979,9 +10978,9 @@ function resolveClaudeExecutable(options = {}) {
|
|
|
10979
10978
|
}
|
|
10980
10979
|
var init_executable = () => {};
|
|
10981
10980
|
|
|
10982
|
-
// src/
|
|
10983
|
-
function
|
|
10984
|
-
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.
|
|
10985
10984
|
|
|
10986
10985
|
Available tools (the only tools you may use, by these exact names):
|
|
10987
10986
|
- mcp__sessions__list_sessions / mcp__sessions__read_session: the session transcripts in scope for this run.
|
|
@@ -10989,7 +10988,7 @@ Available tools (the only tools you may use, by these exact names):
|
|
|
10989
10988
|
|
|
10990
10989
|
${coreRules}`;
|
|
10991
10990
|
}
|
|
10992
|
-
function
|
|
10991
|
+
function buildLearnerPrompt(sessions) {
|
|
10993
10992
|
const list = sessions.map((s) => `- ${s.id} (${s.harness})`).join(`
|
|
10994
10993
|
`);
|
|
10995
10994
|
return `Mine the following ${sessions.length} coding-agent session(s) for durable knowledge:
|
|
@@ -10999,8 +10998,8 @@ ${list}
|
|
|
10999
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.`;
|
|
11000
10999
|
}
|
|
11001
11000
|
|
|
11002
|
-
// src/
|
|
11003
|
-
var
|
|
11001
|
+
// src/learner/prompt-core.ts
|
|
11002
|
+
var LEARNER_CORE_RULES = `Rules — non-negotiable:
|
|
11004
11003
|
1. Before writing anything, call read_knowledge with the candidate topic. If the same fact is
|
|
11005
11004
|
already recorded, skip it — never write a duplicate or near-duplicate. If the topic exists but
|
|
11006
11005
|
the session shows it has changed or been superseded, do write: a note stating the current state
|
|
@@ -11028,10 +11027,9 @@ useful.
|
|
|
11028
11027
|
6. Only pass repo/branch to write_knowledge when the session itself verifies them (an explicit
|
|
11029
11028
|
cwd, git remote, or branch mentioned in the transcript). Never infer or guess a repo. When not
|
|
11030
11029
|
verified, omit both.
|
|
11031
|
-
7.
|
|
11032
|
-
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
|
|
11033
11031
|
long verbatim transcript spans. Summarize in your own words.
|
|
11034
|
-
|
|
11032
|
+
8. A trivial session with no real user query is normal: skip it silently.`;
|
|
11035
11033
|
|
|
11036
11034
|
// node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
11037
11035
|
var exports_sdk = {};
|
|
@@ -72782,7 +72780,7 @@ var init_redact = __esm(() => {
|
|
|
72782
72780
|
]);
|
|
72783
72781
|
});
|
|
72784
72782
|
|
|
72785
|
-
// src/
|
|
72783
|
+
// src/learner/tools.ts
|
|
72786
72784
|
function textResult(text) {
|
|
72787
72785
|
return { content: [{ type: "text", text }] };
|
|
72788
72786
|
}
|
|
@@ -72841,7 +72839,7 @@ function createSessionToolsServer(sessions) {
|
|
|
72841
72839
|
name: SESSIONS_SERVER_NAME,
|
|
72842
72840
|
version: "1.0.0",
|
|
72843
72841
|
tools: [
|
|
72844
|
-
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))),
|
|
72845
72843
|
O6e("read_session", "Read the conversation turns of one in-scope session. Responses are " + "paginated; pass the returned offset to continue reading.", {
|
|
72846
72844
|
id: exports_external.string().describe("Session id from list_sessions"),
|
|
72847
72845
|
offset: exports_external.number().int().min(0).optional().describe("Turn index to start from")
|
|
@@ -72867,11 +72865,11 @@ var init_tools = __esm(() => {
|
|
|
72867
72865
|
init_redact();
|
|
72868
72866
|
});
|
|
72869
72867
|
|
|
72870
|
-
// src/
|
|
72868
|
+
// src/learner/runner.ts
|
|
72871
72869
|
var exports_runner = {};
|
|
72872
72870
|
__export(exports_runner, {
|
|
72873
72871
|
traceAgentMessage: () => traceAgentMessage,
|
|
72874
|
-
|
|
72872
|
+
runLearner: () => runLearner,
|
|
72875
72873
|
classifyGatewayError: () => classifyGatewayError
|
|
72876
72874
|
});
|
|
72877
72875
|
function classifyGatewayError(text) {
|
|
@@ -72893,12 +72891,12 @@ function traceAgentMessage(message) {
|
|
|
72893
72891
|
return;
|
|
72894
72892
|
for (const block2 of content) {
|
|
72895
72893
|
if (msg.type === "assistant" && block2.type === "text") {
|
|
72896
|
-
logger.debug("
|
|
72894
|
+
logger.debug("learner", `[agent] ${snippet(block2.text)}`);
|
|
72897
72895
|
} else if (msg.type === "assistant" && block2.type === "tool_use") {
|
|
72898
|
-
logger.debug("
|
|
72896
|
+
logger.debug("learner", `[agent] → ${block2.name} ${snippet(block2.input)}`);
|
|
72899
72897
|
} else if (msg.type === "user" && block2.type === "tool_result") {
|
|
72900
72898
|
const full = JSON.stringify(block2.content ?? "");
|
|
72901
|
-
logger.debug("
|
|
72899
|
+
logger.debug("learner", `[agent] ← result ${full.length} chars${block2.is_error ? " (error)" : ""}: ${snippet(block2.content).slice(0, 120)}`);
|
|
72902
72900
|
}
|
|
72903
72901
|
}
|
|
72904
72902
|
}
|
|
@@ -72911,7 +72909,7 @@ function allowedToolNames() {
|
|
|
72911
72909
|
`mcp__${KNOWLEDGE_SERVER_NAME}__finalize_session_knowledge`
|
|
72912
72910
|
]);
|
|
72913
72911
|
}
|
|
72914
|
-
async function
|
|
72912
|
+
async function runLearner(options) {
|
|
72915
72913
|
const conflicts = detectSettingsConflicts();
|
|
72916
72914
|
if (conflicts.length > 0) {
|
|
72917
72915
|
const detail = conflicts.map((c5) => `${c5.file} (${c5.keys.join(", ")})`).join("; ");
|
|
@@ -72919,7 +72917,7 @@ async function runMiner(options) {
|
|
|
72919
72917
|
outcome: "settings_conflict",
|
|
72920
72918
|
notesWritten: 0,
|
|
72921
72919
|
turns: 0,
|
|
72922
|
-
message: `Refusing to run: conflicting Claude Code settings would override the
|
|
72920
|
+
message: `Refusing to run: conflicting Claude Code settings would override the study run's auth (${detail})`
|
|
72923
72921
|
};
|
|
72924
72922
|
}
|
|
72925
72923
|
const gatewayURL = options.gatewayURL ?? getLlmGatewayURL();
|
|
@@ -72934,7 +72932,7 @@ async function runMiner(options) {
|
|
|
72934
72932
|
const { query } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
|
|
72935
72933
|
const claudeExecutable = resolveClaudeExecutable();
|
|
72936
72934
|
if (claudeExecutable) {
|
|
72937
|
-
logger.debug("
|
|
72935
|
+
logger.debug("learner", `using system Claude Code executable: ${claudeExecutable}`);
|
|
72938
72936
|
}
|
|
72939
72937
|
const configDir = createRunConfigDir();
|
|
72940
72938
|
const runID = options.runID ?? crypto.randomUUID();
|
|
@@ -72947,7 +72945,7 @@ async function runMiner(options) {
|
|
|
72947
72945
|
let currentSession;
|
|
72948
72946
|
const readsSinceWrite = new Set;
|
|
72949
72947
|
let turns = 0;
|
|
72950
|
-
const env =
|
|
72948
|
+
const env = buildLearnerEnv({
|
|
72951
72949
|
apiKey: options.apiKey,
|
|
72952
72950
|
gatewayURL,
|
|
72953
72951
|
configDir: configDir.path,
|
|
@@ -72958,9 +72956,9 @@ async function runMiner(options) {
|
|
|
72958
72956
|
});
|
|
72959
72957
|
try {
|
|
72960
72958
|
const run2 = query({
|
|
72961
|
-
prompt:
|
|
72959
|
+
prompt: buildLearnerPrompt(options.sessions),
|
|
72962
72960
|
options: {
|
|
72963
|
-
systemPrompt:
|
|
72961
|
+
systemPrompt: buildLearnerSystemPrompt(LEARNER_CORE_RULES),
|
|
72964
72962
|
env,
|
|
72965
72963
|
abortController: abort,
|
|
72966
72964
|
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
@@ -72976,17 +72974,17 @@ async function runMiner(options) {
|
|
|
72976
72974
|
headers: {
|
|
72977
72975
|
...mcpHeaders(options.apiKey),
|
|
72978
72976
|
"X-Dosu-Session-Id": runID,
|
|
72979
|
-
"X-Dosu-Client": `dosu-cli-
|
|
72977
|
+
"X-Dosu-Client": `dosu-cli-learner/${getVersionString()}`,
|
|
72980
72978
|
...sessionStartedAt ? { "X-Dosu-Session-Started-At": sessionStartedAt } : {}
|
|
72981
72979
|
}
|
|
72982
72980
|
}
|
|
72983
72981
|
},
|
|
72984
|
-
stderr: (data) => logger.debug("
|
|
72982
|
+
stderr: (data) => logger.debug("learner", `[sdk] ${data}`),
|
|
72985
72983
|
canUseTool: async (toolName, input2) => {
|
|
72986
72984
|
if (!allowed.has(toolName)) {
|
|
72987
72985
|
return {
|
|
72988
72986
|
behavior: "deny",
|
|
72989
|
-
message: `Tool ${toolName} is not permitted in
|
|
72987
|
+
message: `Tool ${toolName} is not permitted in study runs.`
|
|
72990
72988
|
};
|
|
72991
72989
|
}
|
|
72992
72990
|
if (toolName === `mcp__${SESSIONS_SERVER_NAME}__read_session`) {
|
|
@@ -73038,15 +73036,15 @@ async function runMiner(options) {
|
|
|
73038
73036
|
};
|
|
73039
73037
|
}
|
|
73040
73038
|
if (message.subtype !== "success" || message.is_error) {
|
|
73041
|
-
logger.debug("
|
|
73039
|
+
logger.debug("learner", `run ${runID} failed: ${text}`);
|
|
73042
73040
|
return {
|
|
73043
73041
|
outcome: "error",
|
|
73044
73042
|
notesWritten,
|
|
73045
73043
|
turns,
|
|
73046
|
-
message: "
|
|
73044
|
+
message: "Study run failed; see debug log for details."
|
|
73047
73045
|
};
|
|
73048
73046
|
}
|
|
73049
|
-
logger.debug("
|
|
73047
|
+
logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
|
|
73050
73048
|
return { outcome: "completed", notesWritten, turns, message: text };
|
|
73051
73049
|
}
|
|
73052
73050
|
}
|
|
@@ -73054,7 +73052,7 @@ async function runMiner(options) {
|
|
|
73054
73052
|
outcome: "error",
|
|
73055
73053
|
notesWritten,
|
|
73056
73054
|
turns,
|
|
73057
|
-
message: "
|
|
73055
|
+
message: "Study run ended without a result."
|
|
73058
73056
|
};
|
|
73059
73057
|
} catch (error61) {
|
|
73060
73058
|
const text = error61 instanceof Error ? error61.message : String(error61);
|
|
@@ -73062,12 +73060,12 @@ async function runMiner(options) {
|
|
|
73062
73060
|
if (gatewayError) {
|
|
73063
73061
|
return { outcome: gatewayError.outcome, notesWritten, turns, message: gatewayError.message };
|
|
73064
73062
|
}
|
|
73065
|
-
logger.debug("
|
|
73063
|
+
logger.debug("learner", `run ${runID} threw: ${text}`);
|
|
73066
73064
|
return {
|
|
73067
73065
|
outcome: "error",
|
|
73068
73066
|
notesWritten,
|
|
73069
73067
|
turns,
|
|
73070
|
-
message: abort.signal.aborted ? "
|
|
73068
|
+
message: abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details."
|
|
73071
73069
|
};
|
|
73072
73070
|
} finally {
|
|
73073
73071
|
clearTimeout(timer);
|
|
@@ -73537,7 +73535,7 @@ var init_settings = __esm(() => {
|
|
|
73537
73535
|
});
|
|
73538
73536
|
|
|
73539
73537
|
// src/telemetry/debug-id.ts
|
|
73540
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
73538
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "a3c20325-c0f7-811f-6475-6e2164756e21";
|
|
73541
73539
|
|
|
73542
73540
|
// src/telemetry/telemetry.ts
|
|
73543
73541
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -75413,6 +75411,140 @@ var init_errors3 = __esm(() => {
|
|
|
75413
75411
|
// src/mcp/constants.ts
|
|
75414
75412
|
var MCP_PROVIDER_SLUG2 = "dosu_mcp";
|
|
75415
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
|
+
|
|
75416
75548
|
// src/tui/alt-screen.ts
|
|
75417
75549
|
function enterAltScreen(output2) {
|
|
75418
75550
|
depth += 1;
|
|
@@ -75436,7 +75568,8 @@ var init_alt_screen = __esm(() => {
|
|
|
75436
75568
|
});
|
|
75437
75569
|
|
|
75438
75570
|
// src/tui/activity-view.ts
|
|
75439
|
-
import { readFileSync as
|
|
75571
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25 } from "node:fs";
|
|
75572
|
+
import { basename as basename5 } from "node:path";
|
|
75440
75573
|
function reduceActivityViewKey(key) {
|
|
75441
75574
|
if (key === "q" || key === ESC7 || key === CTRL_C4)
|
|
75442
75575
|
return "back";
|
|
@@ -75452,6 +75585,8 @@ function reduceActivityViewKey(key) {
|
|
|
75452
75585
|
return "sync";
|
|
75453
75586
|
if (key === "f")
|
|
75454
75587
|
return "full";
|
|
75588
|
+
if (key === "c")
|
|
75589
|
+
return "clear";
|
|
75455
75590
|
return "none";
|
|
75456
75591
|
}
|
|
75457
75592
|
function cycleTab(tab, delta = 1) {
|
|
@@ -75482,13 +75617,13 @@ function formatActivityLine(line, width, full = false) {
|
|
|
75482
75617
|
}
|
|
75483
75618
|
function appendSyncActivity(buffer, chunk, max = ACTIVITY_VIEW_BUFFER_LINES) {
|
|
75484
75619
|
const fresh = chunk.split(`
|
|
75485
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[
|
|
75620
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[learner]")).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
75486
75621
|
return [...buffer, ...fresh].slice(-max);
|
|
75487
75622
|
}
|
|
75488
75623
|
function clip(text, max) {
|
|
75489
75624
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
75490
75625
|
}
|
|
75491
|
-
function
|
|
75626
|
+
function formatStudiedRow(record2, full = false, name) {
|
|
75492
75627
|
const match = record2.at.match(/^\d{4}-(\d{2}-\d{2})T(\d{2}:\d{2})/);
|
|
75493
75628
|
const stamp = match ? `${match[1]} ${match[2]}` : record2.at;
|
|
75494
75629
|
const slash = record2.session.indexOf("/");
|
|
@@ -75496,13 +75631,15 @@ function formatMinedRow(record2, full = false) {
|
|
|
75496
75631
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
75497
75632
|
const rawProject = record2.project ?? "-";
|
|
75498
75633
|
const project = full ? rawProject : clip(rawProject, 28);
|
|
75499
|
-
|
|
75634
|
+
const label = full ? name ? `${name} · ${id2}` : id2 : clip(name ?? id2, 44);
|
|
75635
|
+
return `${harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75500
75636
|
}
|
|
75501
|
-
function formatQueuedRow(session, full = false) {
|
|
75637
|
+
function formatQueuedRow(session, full = false, name) {
|
|
75502
75638
|
const stamp = session.updated.replace("T", " ").slice(5, 16);
|
|
75503
75639
|
const rawProject = session.project ?? "-";
|
|
75504
75640
|
const project = full ? rawProject : clip(rawProject, 28);
|
|
75505
|
-
|
|
75641
|
+
const label = full ? name ? `${name} · ${session.id}` : session.id : clip(name ?? session.id, 44);
|
|
75642
|
+
return `${session.harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75506
75643
|
}
|
|
75507
75644
|
function wrapRow(text, width, indent = " ") {
|
|
75508
75645
|
if (text.length <= width)
|
|
@@ -75542,23 +75679,23 @@ function foldRunProgress(progress, chunk) {
|
|
|
75542
75679
|
let current = progress;
|
|
75543
75680
|
for (const line of chunk.split(`
|
|
75544
75681
|
`)) {
|
|
75545
|
-
const start = line.match(/\[sync\] mining (\d+) of \d+ ready sessions/);
|
|
75682
|
+
const start = line.match(/\[sync\] (?:studying|mining) (\d+) of \d+ ready sessions/);
|
|
75546
75683
|
if (start) {
|
|
75547
75684
|
current = { batch: Number.parseInt(start[1], 10), read: new Set, notes: 0 };
|
|
75548
75685
|
continue;
|
|
75549
75686
|
}
|
|
75550
|
-
if (/\[sync\] (mined \d+ sessions|mining failed|
|
|
75687
|
+
if (/\[sync\] (?:studied|mined) \d+ sessions|\[sync\] (?:studying|mining) (?:failed|skipped)/.test(line)) {
|
|
75551
75688
|
current = null;
|
|
75552
75689
|
continue;
|
|
75553
75690
|
}
|
|
75554
75691
|
if (!current)
|
|
75555
75692
|
continue;
|
|
75556
|
-
const read = line.match(/\[
|
|
75693
|
+
const read = line.match(/\[learner\] \[agent\] → mcp__sessions__read_session .*?"id":"([^"]+)"/);
|
|
75557
75694
|
if (read) {
|
|
75558
75695
|
current.read.add(read[1]);
|
|
75559
75696
|
continue;
|
|
75560
75697
|
}
|
|
75561
|
-
if (line.includes("[
|
|
75698
|
+
if (line.includes("[learner] [agent] → mcp__dosu__write_knowledge")) {
|
|
75562
75699
|
current.notes += 1;
|
|
75563
75700
|
}
|
|
75564
75701
|
}
|
|
@@ -75572,15 +75709,15 @@ function statusLine(status) {
|
|
|
75572
75709
|
if (status.running) {
|
|
75573
75710
|
const since = status.startedAt ? ` · since ${localTime(status.startedAt)}` : "";
|
|
75574
75711
|
const pid = status.pid !== undefined ? ` (pid ${status.pid})` : "";
|
|
75575
|
-
return
|
|
75712
|
+
return `\uD83D\uDCDA ${import_picocolors30.default.bold(brand("Studying sessions..."))}${import_picocolors30.default.dim(`${pid}${since}`)}`;
|
|
75576
75713
|
}
|
|
75577
75714
|
if (status.state.paused) {
|
|
75578
|
-
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("·
|
|
75715
|
+
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("· studying stays off until you resume")}`;
|
|
75579
75716
|
}
|
|
75580
75717
|
if (status.staleLock) {
|
|
75581
75718
|
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Not running")} ${import_picocolors30.default.dim("· a previous run exited without cleaning up")}`;
|
|
75582
75719
|
}
|
|
75583
|
-
return `${import_picocolors30.default.dim("○")} ${import_picocolors30.default.bold("Idle")} ${import_picocolors30.default.dim("· hooks
|
|
75720
|
+
return `${import_picocolors30.default.dim("○")} ${import_picocolors30.default.bold("Idle")} ${import_picocolors30.default.dim("· hooks study new sessions automatically")}`;
|
|
75584
75721
|
}
|
|
75585
75722
|
function progressLine(done, ready, width, notes = 0) {
|
|
75586
75723
|
const total = done + ready;
|
|
@@ -75592,12 +75729,12 @@ function progressLine(done, ready, width, notes = 0) {
|
|
|
75592
75729
|
const bar = brand("█".repeat(filled)) + import_picocolors30.default.dim("░".repeat(cells - filled));
|
|
75593
75730
|
const pct = Math.floor(ratio * 100);
|
|
75594
75731
|
const suffix = notes > 0 ? ` · ${notes} suggested page${notes === 1 ? "" : "s"}` : "";
|
|
75595
|
-
return `${bar} ${done}/${total}
|
|
75732
|
+
return `${bar} ${done}/${total} studied · ${pct}%${suffix}`;
|
|
75596
75733
|
}
|
|
75597
|
-
function tabBar(tab, queuedCount, openCount,
|
|
75734
|
+
function tabBar(tab, queuedCount, openCount, studiedCount, width) {
|
|
75598
75735
|
return tabStrip([
|
|
75599
75736
|
["activity", "Activity"],
|
|
75600
|
-
["
|
|
75737
|
+
["studied", `Studied (${studiedCount})`],
|
|
75601
75738
|
["queued", `Queued (${queuedCount})`],
|
|
75602
75739
|
["open", `Open (${openCount})`]
|
|
75603
75740
|
], tab, width);
|
|
@@ -75621,10 +75758,11 @@ function wrapLine(text, width, indent = " ") {
|
|
|
75621
75758
|
return out;
|
|
75622
75759
|
}
|
|
75623
75760
|
function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
75624
|
-
const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} open
|
|
75625
|
-
const
|
|
75626
|
-
const
|
|
75627
|
-
const
|
|
75761
|
+
const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} still open; ${backlog.inFlight === 1 ? "it joins" : "they join"} once quiet)` : "";
|
|
75762
|
+
const sessions = `${queuedCount} session${queuedCount === 1 ? "" : "s"}`;
|
|
75763
|
+
const scope = mode === "clear" ? "Forgets which local sessions Dosu has already studied, so the next run reads them all again. Use this to rebuild knowledge from scratch. Notes already saved in Dosu are kept." : mode === "stop" ? "Kills the run mid-batch and pauses studying, so new sessions pile up in the queue instead of being read. Nothing is lost; press s again to resume from where it left off." : mode === "resume" ? `Studying picks up where it stopped: ${sessions} queued${inFlight}. Runs in the background; new notes appear in Dosu as each batch finishes.` : queuedCount > 0 ? `Dosu reads ${sessions}${inFlight} and distills the durable decisions and gotchas into team knowledge. Runs in the background; new notes appear in Dosu as each batch finishes.` : `Queue is empty${inFlight}. A run now would only pick up sessions that finish from here; hooks already do that automatically.`;
|
|
75764
|
+
const title = mode === "clear" ? "Clear study history?" : mode === "stop" ? "Stop studying?" : mode === "resume" ? "Resume studying?" : "Start studying now?";
|
|
75765
|
+
const verb = mode === "clear" ? "clear" : mode === "stop" ? "stop" : mode === "resume" ? "resume" : "start";
|
|
75628
75766
|
const maxInner = Math.max(20, Math.min(width, contentWidth()) - 4);
|
|
75629
75767
|
const rows = [
|
|
75630
75768
|
import_picocolors30.default.bold(brand(title)),
|
|
@@ -75640,28 +75778,41 @@ function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
|
75640
75778
|
import_picocolors30.default.dim(`╰${"─".repeat(inner + 2)}╯`)
|
|
75641
75779
|
];
|
|
75642
75780
|
}
|
|
75643
|
-
function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [],
|
|
75644
|
-
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";
|
|
75645
75783
|
const queueDetail = [];
|
|
75646
75784
|
if (status.backoffUntil) {
|
|
75647
75785
|
queueDetail.push(`retrying after ${localTime(status.backoffUntil)} · s syncs now`);
|
|
75648
75786
|
}
|
|
75649
75787
|
let progress = null;
|
|
75650
75788
|
if (status.running && backlog) {
|
|
75651
|
-
const
|
|
75789
|
+
const studiedDelta = Math.max(0, (status.state.total_mined ?? 0) - studiedBeforeRun);
|
|
75652
75790
|
const stepDone = runProgress ? Math.min(Math.max(0, runProgress.read.size - 1), runProgress.batch - 1, backlog.ready) : 0;
|
|
75653
|
-
progress = progressLine(
|
|
75791
|
+
progress = progressLine(studiedDelta + stepDone, Math.max(0, backlog.ready - stepDone), width, runProgress?.notes ?? 0);
|
|
75654
75792
|
}
|
|
75655
75793
|
const refusal = !status.running && status.state.last_refusal;
|
|
75656
|
-
const refusalLines = refusal ? wrapLine(`!
|
|
75794
|
+
const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors30.default.yellow(line)) : [];
|
|
75657
75795
|
const fullRows = Boolean(pane.fullRows);
|
|
75658
|
-
const
|
|
75659
|
-
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
|
+
};
|
|
75660
75811
|
const queuedRows = queued.map(sessionRow);
|
|
75661
75812
|
const openRows = open5.map(sessionRow);
|
|
75662
|
-
const source = pane.tab === "activity" ? activity.map((line) => formatActivityLine(line, width, fullRows)) : pane.tab === "queued" ? queuedRows : pane.tab === "open" ? openRows :
|
|
75663
|
-
const
|
|
75664
|
-
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;
|
|
75665
75816
|
const height = fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75666
75817
|
const { visible, above, below } = windowList(source, pane.scroll, height);
|
|
75667
75818
|
const display = fullRows ? visible.flatMap((row) => wrapRow(row, width)) : visible;
|
|
@@ -75675,31 +75826,95 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
|
|
|
75675
75826
|
breadcrumb(["Home", "Activity"], width),
|
|
75676
75827
|
"",
|
|
75677
75828
|
statusLine(status),
|
|
75678
|
-
import_picocolors30.default.dim(
|
|
75829
|
+
import_picocolors30.default.dim(studied),
|
|
75679
75830
|
...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors30.default.dim(line)) : [],
|
|
75680
75831
|
...progress ? [progress] : [],
|
|
75681
75832
|
...refusalLines,
|
|
75682
75833
|
"",
|
|
75683
|
-
...tabBar(pane.tab, queued.length, open5.length,
|
|
75834
|
+
...tabBar(pane.tab, queued.length, open5.length, latestPass.size, width),
|
|
75684
75835
|
...listRows,
|
|
75685
75836
|
...scrollParts.length > 0 ? [import_picocolors30.default.dim(clip(scrollParts.join(" · "), width))] : [],
|
|
75686
75837
|
"",
|
|
75687
75838
|
...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width, pane.confirm), width) : [
|
|
75688
|
-
|
|
75839
|
+
...wrapLine([
|
|
75689
75840
|
"tab switch",
|
|
75690
75841
|
"↑↓ scroll",
|
|
75691
75842
|
fullRows ? "f clip" : "f full rows",
|
|
75692
75843
|
status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
|
|
75844
|
+
...!status.running && status.state.watermark ? ["c clear"] : [],
|
|
75693
75845
|
"esc back"
|
|
75694
|
-
].join(" · "))
|
|
75846
|
+
].join(" · "), width, "").map((line) => import_picocolors30.default.dim(line))
|
|
75695
75847
|
]
|
|
75696
75848
|
];
|
|
75697
75849
|
return lines.join(`
|
|
75698
75850
|
`);
|
|
75699
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
|
+
}
|
|
75700
75915
|
function defaultReadLog2() {
|
|
75701
75916
|
try {
|
|
75702
|
-
return
|
|
75917
|
+
return readFileSync25(logger.getLogPath(), "utf-8");
|
|
75703
75918
|
} catch {
|
|
75704
75919
|
return "";
|
|
75705
75920
|
}
|
|
@@ -75715,7 +75930,9 @@ function runActivityView(io2 = {}) {
|
|
|
75715
75930
|
const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--bootstrap"]));
|
|
75716
75931
|
const stopSync = io2.stopSync ?? stopSyncRun;
|
|
75717
75932
|
const setPaused = io2.setPaused ?? setSyncPaused;
|
|
75933
|
+
const clearHistory = io2.clearHistory ?? resetSyncState;
|
|
75718
75934
|
const listBacklog = io2.listBacklog ?? listSessionBacklog;
|
|
75935
|
+
const rowNamesFor = io2.rowNames ?? createRowNamer();
|
|
75719
75936
|
const pollMs = io2.pollMs ?? ACTIVITY_VIEW_POLL_MS;
|
|
75720
75937
|
const seed = readLog();
|
|
75721
75938
|
let activity = appendSyncActivity([], seed);
|
|
@@ -75744,29 +75961,30 @@ function runActivityView(io2 = {}) {
|
|
|
75744
75961
|
};
|
|
75745
75962
|
const listHeight = () => fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75746
75963
|
let lastFrame = null;
|
|
75747
|
-
let
|
|
75964
|
+
let studiedBeforeRun = null;
|
|
75748
75965
|
const draw = () => {
|
|
75749
75966
|
status = getStatus();
|
|
75750
75967
|
if (status.running) {
|
|
75751
75968
|
const run2 = status.state.run;
|
|
75752
75969
|
if (run2 && run2.pid === status.pid) {
|
|
75753
|
-
|
|
75970
|
+
studiedBeforeRun = run2.baseline_mined;
|
|
75754
75971
|
} else {
|
|
75755
|
-
|
|
75972
|
+
studiedBeforeRun ??= status.state.total_mined ?? 0;
|
|
75756
75973
|
}
|
|
75757
75974
|
} else {
|
|
75758
|
-
|
|
75975
|
+
studiedBeforeRun = null;
|
|
75759
75976
|
}
|
|
75760
75977
|
if (status.state.watermark !== queuedWatermark) {
|
|
75761
75978
|
queuedWatermark = status.state.watermark;
|
|
75762
75979
|
sessions = listBacklog();
|
|
75763
75980
|
}
|
|
75764
|
-
if (
|
|
75981
|
+
if (confirmSync !== null && confirmSync !== "stop" && status.running)
|
|
75765
75982
|
confirmSync = null;
|
|
75766
75983
|
if (confirmSync === "stop" && !status.running)
|
|
75767
75984
|
confirmSync = null;
|
|
75768
75985
|
const width = activityWidth(output2.columns ?? 80);
|
|
75769
|
-
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);
|
|
75770
75988
|
if (frame === lastFrame)
|
|
75771
75989
|
return;
|
|
75772
75990
|
lastFrame = frame;
|
|
@@ -75813,11 +76031,15 @@ ${CLEAR_BELOW}`);
|
|
|
75813
76031
|
const mode = confirmSync;
|
|
75814
76032
|
confirmSync = null;
|
|
75815
76033
|
let note;
|
|
75816
|
-
if (mode === "
|
|
76034
|
+
if (mode === "clear") {
|
|
76035
|
+
clearHistory();
|
|
76036
|
+
studiedBeforeRun = null;
|
|
76037
|
+
note = "[sync] study history cleared · the next run reads every local session again";
|
|
76038
|
+
} else if (mode === "stop") {
|
|
75817
76039
|
const ok2 = status.pid !== undefined && stopSync(status.pid);
|
|
75818
76040
|
if (ok2)
|
|
75819
76041
|
setPaused(true);
|
|
75820
|
-
note = ok2 ? "[sync]
|
|
76042
|
+
note = ok2 ? "[sync] studying stopped · paused until you resume" : "[sync] could not stop the run · it may have just finished";
|
|
75821
76043
|
} else {
|
|
75822
76044
|
if (mode === "resume")
|
|
75823
76045
|
setPaused(false);
|
|
@@ -75841,6 +76063,11 @@ ${CLEAR_BELOW}`);
|
|
|
75841
76063
|
if (action === "sync") {
|
|
75842
76064
|
confirmSync = syncConfirmMode(status);
|
|
75843
76065
|
draw();
|
|
76066
|
+
} else if (action === "clear") {
|
|
76067
|
+
if (!status.running && status.state.watermark) {
|
|
76068
|
+
confirmSync = "clear";
|
|
76069
|
+
draw();
|
|
76070
|
+
}
|
|
75844
76071
|
} else if (action === "tab" || action === "tab-back") {
|
|
75845
76072
|
tab = cycleTab(tab, action === "tab" ? 1 : -1);
|
|
75846
76073
|
scroll = 0;
|
|
@@ -75868,10 +76095,12 @@ ${CLEAR_BELOW}`);
|
|
|
75868
76095
|
input2.on("data", onData);
|
|
75869
76096
|
});
|
|
75870
76097
|
}
|
|
75871
|
-
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;
|
|
75872
76099
|
var init_activity_view = __esm(() => {
|
|
75873
76100
|
init_follow();
|
|
75874
76101
|
init_logger();
|
|
76102
|
+
init_project_dir();
|
|
76103
|
+
init_session_title();
|
|
75875
76104
|
init_styles();
|
|
75876
76105
|
init_backlog();
|
|
75877
76106
|
init_detach();
|
|
@@ -75893,7 +76122,7 @@ var init_activity_view = __esm(() => {
|
|
|
75893
76122
|
CURSOR_HOME = `${ESC7}[H`;
|
|
75894
76123
|
CLEAR_BELOW = `${ESC7}[0J`;
|
|
75895
76124
|
CLEAR_EOL = `${ESC7}[K`;
|
|
75896
|
-
ACTIVITY_VIEW_TABS = ["activity", "
|
|
76125
|
+
ACTIVITY_VIEW_TABS = ["activity", "studied", "queued", "open"];
|
|
75897
76126
|
DEFAULT_PANE = { tab: "activity", scroll: 0 };
|
|
75898
76127
|
});
|
|
75899
76128
|
|
|
@@ -76726,23 +76955,31 @@ async function stepOfferInitialSync(cfg) {
|
|
|
76726
76955
|
s.start("Checking for recent agent sessions...");
|
|
76727
76956
|
const outcome = await runKnowledgeSync({ bootstrap: true });
|
|
76728
76957
|
if (outcome.status !== "backlog" || outcome.readySessions === 0) {
|
|
76729
|
-
s.stop("No
|
|
76958
|
+
s.stop("No past agent sessions to study. Dosu will learn from new ones as you work.");
|
|
76730
76959
|
return;
|
|
76731
76960
|
}
|
|
76732
76961
|
const n5 = outcome.readySessions;
|
|
76733
|
-
|
|
76962
|
+
const them = n5 === 1 ? "it" : "them";
|
|
76963
|
+
s.stop(`Found ${n5} past agent session${n5 === 1 ? "" : "s"} on this machine that Dosu hasn't studied.`);
|
|
76964
|
+
log2.message([
|
|
76965
|
+
`Dosu can read ${them} and pull out the durable knowledge: decisions, gotchas, and`,
|
|
76966
|
+
"how-things-work that your team and agents would otherwise rediscover.",
|
|
76967
|
+
"Only distilled notes are saved to Dosu; your session logs stay on this machine.",
|
|
76968
|
+
"It runs in the background a few sessions at a time; notes appear in Dosu as each batch finishes."
|
|
76969
|
+
].join(`
|
|
76970
|
+
`));
|
|
76734
76971
|
const mineNow = await confirm2({
|
|
76735
|
-
message: `
|
|
76736
|
-
active: "
|
|
76737
|
-
inactive: "Skip",
|
|
76972
|
+
message: `Study ${them} now?`,
|
|
76973
|
+
active: "Study now \uD83D\uDCDA",
|
|
76974
|
+
inactive: "Skip for now",
|
|
76738
76975
|
initialValue: true
|
|
76739
76976
|
});
|
|
76740
76977
|
if (isCancel2(mineNow) || !mineNow) {
|
|
76741
|
-
log2.info(`Skipped. Dosu
|
|
76978
|
+
log2.info(`Skipped. Dosu studies new sessions in the background as you work; run ${info("dosu knowledge sync")} anytime to study these too.`);
|
|
76742
76979
|
return;
|
|
76743
76980
|
}
|
|
76744
76981
|
if (spawnDetachedSelf(["knowledge", "sync", "--quiet", "--bootstrap"])) {
|
|
76745
|
-
log2.success("
|
|
76982
|
+
log2.success(`\uD83D\uDCDA Studying ${n5} session${n5 === 1 ? "" : "s"} in the background. Watch progress on the Activity screen; when it finishes, the new notes are in Dosu and agents connected to this MCP can use them.`);
|
|
76746
76983
|
const watch = await confirm2({
|
|
76747
76984
|
message: "What next?",
|
|
76748
76985
|
active: "Watch it work",
|
|
@@ -77567,12 +77804,15 @@ function loadPageStatsFromConfig() {
|
|
|
77567
77804
|
}
|
|
77568
77805
|
}
|
|
77569
77806
|
function overviewRows(state) {
|
|
77570
|
-
const
|
|
77807
|
+
const studiedTotal = state.total_mined ?? 0;
|
|
77571
77808
|
const notes = state.total_notes ?? 0;
|
|
77572
77809
|
const tokens = state.total_learning_tokens ?? 0;
|
|
77573
|
-
if (
|
|
77810
|
+
if (studiedTotal === 0 && notes === 0)
|
|
77574
77811
|
return [];
|
|
77575
|
-
const rows = [
|
|
77812
|
+
const rows = [
|
|
77813
|
+
`${label("Sessions studied")}${studiedTotal}`,
|
|
77814
|
+
`${label("Suggested pages")}${notes}`
|
|
77815
|
+
];
|
|
77576
77816
|
if (tokens > 0) {
|
|
77577
77817
|
rows.push(`${label("Investigation distilled")}${formatTokenCount(tokens)} tokens`);
|
|
77578
77818
|
}
|
|
@@ -77621,7 +77861,7 @@ function analyticsTabRows(tab, state, pageStats) {
|
|
|
77621
77861
|
function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending = false, width = contentWidth()) {
|
|
77622
77862
|
const rows = analyticsTabRows(tab, state, pageStats);
|
|
77623
77863
|
const { visible, above, below } = windowReport(rows, scroll);
|
|
77624
|
-
const empty = tab === "overview" ? "No analytics yet. They appear after the first
|
|
77864
|
+
const empty = tab === "overview" ? "No analytics yet. They appear after the first study run." : tab === "projects" ? "No per-project history yet. It fills in as sessions are studied." : pagesPending ? "Loading page analytics..." : "No page analytics yet.";
|
|
77625
77865
|
const listRows = visible.length > 0 ? visible.map((row) => import_picocolors32.default.dim(row)) : [import_picocolors32.default.dim(empty)];
|
|
77626
77866
|
const scrollParts = [];
|
|
77627
77867
|
if (above > 0)
|
|
@@ -77797,10 +78037,10 @@ function checklistRows(ctx) {
|
|
|
77797
78037
|
rows.push(["agents", `${on2} ${ctx.agents.join(` ${DOT3} `)}`]);
|
|
77798
78038
|
if (missing.has("hooks"))
|
|
77799
78039
|
rows.push(["hooks", warnRow]);
|
|
77800
|
-
if (ctx.
|
|
78040
|
+
if (ctx.studying) {
|
|
77801
78041
|
rows.push([
|
|
77802
78042
|
"sync",
|
|
77803
|
-
|
|
78043
|
+
`\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors33.default.dim(`${DOT3} see Activity`)}`
|
|
77804
78044
|
]);
|
|
77805
78045
|
}
|
|
77806
78046
|
if (ctx.update) {
|
|
@@ -78190,8 +78430,8 @@ __export(exports_tui, {
|
|
|
78190
78430
|
runTUI: () => runTUI,
|
|
78191
78431
|
handleLogout: () => handleLogout
|
|
78192
78432
|
});
|
|
78193
|
-
import { homedir as
|
|
78194
|
-
import { basename as
|
|
78433
|
+
import { homedir as homedir11 } from "node:os";
|
|
78434
|
+
import { basename as basename6 } from "node:path";
|
|
78195
78435
|
async function probeSession(cfg) {
|
|
78196
78436
|
if (!isAuthenticated(cfg) || !isTokenExpired(cfg))
|
|
78197
78437
|
return { expired: false };
|
|
@@ -78218,7 +78458,7 @@ function bannerContext(cfg, session) {
|
|
|
78218
78458
|
return {
|
|
78219
78459
|
version: getVersionString(),
|
|
78220
78460
|
webAppHost,
|
|
78221
|
-
directory:
|
|
78461
|
+
directory: basename6(process.cwd()),
|
|
78222
78462
|
signedIn: isAuthenticated(cfg),
|
|
78223
78463
|
sessionExpired: session.expired,
|
|
78224
78464
|
deploymentName: cfg.active_account?.target?.deployment_name,
|
|
@@ -78226,7 +78466,7 @@ function bannerContext(cfg, session) {
|
|
|
78226
78466
|
setupMissing: isAuthenticated(cfg) && !session.expired ? missingSetupSteps(cfg) : [],
|
|
78227
78467
|
...inGitWorkTree() ? { repoAgentsMd: dosuAgentsSectionState() } : {},
|
|
78228
78468
|
agents,
|
|
78229
|
-
|
|
78469
|
+
studying: isStudying(),
|
|
78230
78470
|
...latest ? { update: { version: latest, hint: buildUpdateHint(INSTALL_CHANNEL, isNpxInvocation()) } } : {}
|
|
78231
78471
|
};
|
|
78232
78472
|
}
|
|
@@ -78293,7 +78533,7 @@ function setupHint(cfg) {
|
|
|
78293
78533
|
const started = Boolean(cfg.active_account?.target);
|
|
78294
78534
|
return import_picocolors35.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
|
|
78295
78535
|
}
|
|
78296
|
-
function
|
|
78536
|
+
function isStudying() {
|
|
78297
78537
|
try {
|
|
78298
78538
|
return getSyncStatus({ readLog: () => "" }).running;
|
|
78299
78539
|
} catch {
|
|
@@ -78341,10 +78581,10 @@ async function runMainMenu() {
|
|
|
78341
78581
|
{ label: "Exit", value: "exit" }
|
|
78342
78582
|
];
|
|
78343
78583
|
}
|
|
78344
|
-
const
|
|
78584
|
+
const studying = isStudying();
|
|
78345
78585
|
return [
|
|
78346
78586
|
{
|
|
78347
|
-
label:
|
|
78587
|
+
label: studying ? `Activity \uD83D\uDCDA ${brand("studying sessions...")}` : "Activity",
|
|
78348
78588
|
value: "sync"
|
|
78349
78589
|
},
|
|
78350
78590
|
{ label: "Knowledge report", hint: "(opens in browser)", value: "report" },
|
|
@@ -78420,11 +78660,11 @@ async function runSettings(cfg) {
|
|
|
78420
78660
|
const target = cfg.active_account?.target;
|
|
78421
78661
|
const library = target?.library_name ?? target?.deployment_name ?? "not configured";
|
|
78422
78662
|
const filter = loadSyncState().project_filter;
|
|
78423
|
-
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" :
|
|
78663
|
+
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" : basename6(dir)).join(", ") : `${filter.length} projects`;
|
|
78424
78664
|
const action = await menuSelect("Settings", [
|
|
78425
78665
|
{ label: "Switch organization", hint: target?.org_name, value: "switch-org" },
|
|
78426
78666
|
{ label: "Switch Library", hint: library, value: "switch-library" },
|
|
78427
|
-
{ label: "
|
|
78667
|
+
{ label: "Study scope", hint: scope, value: "projects" },
|
|
78428
78668
|
{ label: "Run setup", hint: "rerun the setup wizard", value: "setup" },
|
|
78429
78669
|
{ label: "Log out", hint: "clear stored credentials", value: "logout" },
|
|
78430
78670
|
{ label: "Back", value: "back" }
|
|
@@ -78432,7 +78672,7 @@ async function runSettings(cfg) {
|
|
|
78432
78672
|
if (action === null || action === "back")
|
|
78433
78673
|
return;
|
|
78434
78674
|
if (action === "projects") {
|
|
78435
|
-
await
|
|
78675
|
+
await runStudyingProjectsSetting();
|
|
78436
78676
|
continue;
|
|
78437
78677
|
}
|
|
78438
78678
|
if (action === "setup") {
|
|
@@ -78455,7 +78695,7 @@ async function runSettings(cfg) {
|
|
|
78455
78695
|
}
|
|
78456
78696
|
}
|
|
78457
78697
|
function displayDir(dir) {
|
|
78458
|
-
const home =
|
|
78698
|
+
const home = homedir11();
|
|
78459
78699
|
return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
|
|
78460
78700
|
}
|
|
78461
78701
|
function discoverProjectDirs() {
|
|
@@ -78470,7 +78710,7 @@ function discoverProjectDirs() {
|
|
|
78470
78710
|
} catch {}
|
|
78471
78711
|
return [...counts.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([dir]) => dir);
|
|
78472
78712
|
}
|
|
78473
|
-
async function
|
|
78713
|
+
async function runStudyingProjectsSetting() {
|
|
78474
78714
|
const dirs = discoverProjectDirs();
|
|
78475
78715
|
if (dirs.length === 0) {
|
|
78476
78716
|
log2.info("No local agent sessions found yet; nothing to scope.");
|
|
@@ -78478,7 +78718,7 @@ async function runMiningProjectsSetting() {
|
|
|
78478
78718
|
}
|
|
78479
78719
|
const current = loadSyncState().project_filter;
|
|
78480
78720
|
const selected = await multiselect2({
|
|
78481
|
-
message: "
|
|
78721
|
+
message: "Study sessions from which folders?",
|
|
78482
78722
|
options: dirs.map((dir) => ({
|
|
78483
78723
|
label: dir === UNKNOWN_PROJECT ? "(unknown folder)" : displayDir(dir),
|
|
78484
78724
|
value: dir
|
|
@@ -78493,7 +78733,7 @@ async function runMiningProjectsSetting() {
|
|
|
78493
78733
|
const all = selected.length === dirs.length;
|
|
78494
78734
|
saveSyncState(all ? state : { ...state, project_filter: [...selected] });
|
|
78495
78735
|
const scope = all ? "all folders" : selected.map(displayDir).join(", ");
|
|
78496
|
-
log2.success(`
|
|
78736
|
+
log2.success(`Study scope ${dim(`· ${scope}`)}`);
|
|
78497
78737
|
}
|
|
78498
78738
|
async function handleAuthenticate(cfg) {
|
|
78499
78739
|
const shouldLogin = await confirm2({ message: "Open browser to log in?" });
|
|
@@ -79197,7 +79437,7 @@ import {
|
|
|
79197
79437
|
constants as constants3,
|
|
79198
79438
|
fstatSync,
|
|
79199
79439
|
openSync as openSync5,
|
|
79200
|
-
readFileSync as
|
|
79440
|
+
readFileSync as readFileSync26,
|
|
79201
79441
|
readSync as readSync4,
|
|
79202
79442
|
unlinkSync as unlinkSync5
|
|
79203
79443
|
} from "node:fs";
|
|
@@ -82399,23 +82639,23 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82399
82639
|
console.log(` ID: ${store.id}`);
|
|
82400
82640
|
console.log(` Space ID: ${store.space_id}`);
|
|
82401
82641
|
});
|
|
82402
|
-
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
|
|
82403
|
-
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;
|
|
82404
82644
|
const wantQueued = all || Boolean(opts.queued);
|
|
82405
82645
|
const wantOpen = all || Boolean(opts.open);
|
|
82406
|
-
const
|
|
82646
|
+
const wantStudied = all || Boolean(opts.studied);
|
|
82407
82647
|
const backlog = wantQueued || wantOpen ? listSessionBacklog() : { queued: [], open: [] };
|
|
82408
|
-
const
|
|
82648
|
+
const studied = wantStudied ? loadSyncState().mined_sessions ?? [] : [];
|
|
82409
82649
|
if (opts.json) {
|
|
82410
82650
|
printResult({
|
|
82411
82651
|
...wantQueued ? { queued: backlog.queued } : {},
|
|
82412
82652
|
...wantOpen ? { open: backlog.open } : {},
|
|
82413
|
-
...
|
|
82653
|
+
...wantStudied ? { studied } : {}
|
|
82414
82654
|
}, opts);
|
|
82415
82655
|
return;
|
|
82416
82656
|
}
|
|
82417
82657
|
const sessionRows = (sessions) => sessions.map((s) => [s.harness, s.updated, s.project ?? "-", s.id]);
|
|
82418
|
-
const
|
|
82658
|
+
const studiedRows = studied.map((record2) => {
|
|
82419
82659
|
const slash = record2.session.indexOf("/");
|
|
82420
82660
|
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
82421
82661
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
@@ -82439,11 +82679,11 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82439
82679
|
if (wantOpen) {
|
|
82440
82680
|
section("Open", sessionRows(backlog.open), "Updated", "No open sessions. Live agent sessions sit here until they go quiet.");
|
|
82441
82681
|
}
|
|
82442
|
-
if (
|
|
82443
|
-
section("
|
|
82682
|
+
if (wantStudied) {
|
|
82683
|
+
section("Studied", studiedRows, "Studied at", "No studied sessions recorded yet.");
|
|
82444
82684
|
}
|
|
82445
82685
|
});
|
|
82446
|
-
cmd.command("sync").description("Scan local agent session history and report the
|
|
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) => {
|
|
82447
82687
|
if (opts.status) {
|
|
82448
82688
|
const status = getSyncStatus();
|
|
82449
82689
|
if (opts.json) {
|
|
@@ -82464,7 +82704,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82464
82704
|
]);
|
|
82465
82705
|
return;
|
|
82466
82706
|
}
|
|
82467
|
-
const deps = { mine:
|
|
82707
|
+
const deps = { mine: buildLearner(opts.quiet ? "hook" : "manual") };
|
|
82468
82708
|
let outcome = await runKnowledgeSync({
|
|
82469
82709
|
quiet: opts.quiet,
|
|
82470
82710
|
bootstrap: opts.bootstrap,
|
|
@@ -82472,7 +82712,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82472
82712
|
});
|
|
82473
82713
|
if (opts.bootstrap && deps.mine) {
|
|
82474
82714
|
const maxRounds = Math.ceil(outcome.readySessions / MINE_BATCH_LIMIT) + 2;
|
|
82475
|
-
for (let round = 1;outcome.status === "
|
|
82715
|
+
for (let round = 1;outcome.status === "studied" && round < maxRounds; round++) {
|
|
82476
82716
|
if (!opts.quiet && !opts.json)
|
|
82477
82717
|
printSyncOutcome(outcome);
|
|
82478
82718
|
outcome = await runKnowledgeSync({ quiet: opts.quiet, bootstrap: true, deps });
|
|
@@ -82529,12 +82769,12 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82529
82769
|
console.log("All your notes already have a transcript — nothing to backfill.");
|
|
82530
82770
|
return;
|
|
82531
82771
|
}
|
|
82532
|
-
console.log(`Attributed ${result.updated} of ${result.candidates} notes (${result.ambiguous} ambiguous, ${result.noBatch} without a local
|
|
82772
|
+
console.log(`Attributed ${result.updated} of ${result.candidates} notes (${result.ambiguous} ambiguous, ${result.noBatch} without a local study batch). Run 'dosu knowledge report' to see their traces.`);
|
|
82533
82773
|
});
|
|
82534
82774
|
cmd.addCommand(hooksCommand());
|
|
82535
82775
|
return cmd;
|
|
82536
82776
|
}
|
|
82537
|
-
function
|
|
82777
|
+
function buildLearner(trigger) {
|
|
82538
82778
|
const cfg = loadConfig();
|
|
82539
82779
|
if (cfg.mode === "oss")
|
|
82540
82780
|
return;
|
|
@@ -82543,8 +82783,8 @@ function buildMiner(trigger) {
|
|
|
82543
82783
|
return;
|
|
82544
82784
|
const { api_key, deployment_id } = target;
|
|
82545
82785
|
return async (sessions) => {
|
|
82546
|
-
const {
|
|
82547
|
-
return
|
|
82786
|
+
const { runLearner: runLearner2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
82787
|
+
return runLearner2({ sessions, apiKey: api_key, deploymentID: deployment_id, trigger });
|
|
82548
82788
|
};
|
|
82549
82789
|
}
|
|
82550
82790
|
function formatAge(iso, now) {
|
|
@@ -82570,14 +82810,14 @@ function printSyncStatus(status, now = new Date) {
|
|
|
82570
82810
|
console.log("○ No sync running.");
|
|
82571
82811
|
}
|
|
82572
82812
|
if (status.state.paused) {
|
|
82573
|
-
console.log(import_picocolors18.default.yellow("
|
|
82813
|
+
console.log(import_picocolors18.default.yellow(" Studying paused: stopped by you. Resume from the Activity screen or run 'dosu knowledge sync'."));
|
|
82574
82814
|
}
|
|
82575
82815
|
const wm2 = status.state.watermark;
|
|
82576
|
-
console.log(`
|
|
82816
|
+
console.log(` Studied through: ${wm2 ? `${wm2} (${formatAge(wm2, now)})` : "nothing studied yet"}`);
|
|
82577
82817
|
if (status.state.project_filter?.length) {
|
|
82578
82818
|
const home = homedir5();
|
|
82579
82819
|
const scope = status.state.project_filter.map((dir) => dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir).join(", ");
|
|
82580
|
-
console.log(`
|
|
82820
|
+
console.log(` Study scope: ${scope}`);
|
|
82581
82821
|
}
|
|
82582
82822
|
if ((status.state.total_notes ?? 0) > 0) {
|
|
82583
82823
|
const tokens = status.state.total_learning_tokens ?? 0;
|
|
@@ -82592,7 +82832,7 @@ function printSyncStatus(status, now = new Date) {
|
|
|
82592
82832
|
console.log(import_picocolors18.default.yellow(` Backing off after ${n5} failure${n5 === 1 ? "" : "s"}; background syncs retry after ${status.backoffUntil}.`));
|
|
82593
82833
|
}
|
|
82594
82834
|
if (status.state.last_refusal) {
|
|
82595
|
-
console.log(import_picocolors18.default.yellow(`
|
|
82835
|
+
console.log(import_picocolors18.default.yellow(` Studying paused: ${status.state.last_refusal.message} (${formatAge(status.state.last_refusal.at, now)})`));
|
|
82596
82836
|
}
|
|
82597
82837
|
if (status.recentActivity.length > 0) {
|
|
82598
82838
|
console.log(`
|
|
@@ -82608,25 +82848,25 @@ function printSyncOutcome(outcome) {
|
|
|
82608
82848
|
switch (outcome.status) {
|
|
82609
82849
|
case "backlog": {
|
|
82610
82850
|
const inFlight = outcome.inFlightSessions > 0 ? import_picocolors18.default.dim(` (${outcome.inFlightSessions} more still in progress)`) : "";
|
|
82611
|
-
console.log(`✓ Scanned. ${outcome.readySessions} new session${outcome.readySessions === 1 ? "" : "s"} ready to
|
|
82612
|
-
console.log(import_picocolors18.default.dim("Sign in with 'dosu setup' to enable
|
|
82851
|
+
console.log(`✓ Scanned. ${outcome.readySessions} new session${outcome.readySessions === 1 ? "" : "s"} ready to study${inFlight}.`);
|
|
82852
|
+
console.log(import_picocolors18.default.dim("Sign in with 'dosu setup' to enable studying."));
|
|
82613
82853
|
break;
|
|
82614
82854
|
}
|
|
82615
|
-
case "
|
|
82616
|
-
const notes = outcome.
|
|
82617
|
-
const remaining = outcome.readySessions - (outcome.
|
|
82618
|
-
console.log(`✓
|
|
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.`);
|
|
82619
82859
|
if (remaining > 0) {
|
|
82620
82860
|
console.log(import_picocolors18.default.dim(`${remaining} more in the backlog; run sync again to continue.`));
|
|
82621
82861
|
}
|
|
82622
82862
|
break;
|
|
82623
82863
|
}
|
|
82624
82864
|
case "skipped-gateway": {
|
|
82625
|
-
console.log(import_picocolors18.default.yellow(outcome.
|
|
82865
|
+
console.log(import_picocolors18.default.yellow(outcome.learner?.message ?? "Studying unavailable right now."));
|
|
82626
82866
|
break;
|
|
82627
82867
|
}
|
|
82628
82868
|
case "mine-failed": {
|
|
82629
|
-
console.error(import_picocolors18.default.red(outcome.
|
|
82869
|
+
console.error(import_picocolors18.default.red(outcome.learner?.message ?? "Study run failed."));
|
|
82630
82870
|
process.exitCode = 1;
|
|
82631
82871
|
break;
|
|
82632
82872
|
}
|
|
@@ -82649,7 +82889,7 @@ function printSyncOutcome(outcome) {
|
|
|
82649
82889
|
break;
|
|
82650
82890
|
}
|
|
82651
82891
|
case "skipped-paused": {
|
|
82652
|
-
console.log(import_picocolors18.default.dim("Skipped:
|
|
82892
|
+
console.log(import_picocolors18.default.dim("Skipped: studying is paused. Run 'dosu knowledge sync' to resume."));
|
|
82653
82893
|
break;
|
|
82654
82894
|
}
|
|
82655
82895
|
}
|
|
@@ -84567,7 +84807,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84567
84807
|
}
|
|
84568
84808
|
if (opts.follow) {
|
|
84569
84809
|
try {
|
|
84570
|
-
const lines =
|
|
84810
|
+
const lines = readFileSync26(logPath, "utf-8").split(`
|
|
84571
84811
|
`);
|
|
84572
84812
|
if (lines.at(-1) === "")
|
|
84573
84813
|
lines.pop();
|
|
@@ -84583,7 +84823,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84583
84823
|
if (opts.tail !== undefined) {
|
|
84584
84824
|
const n5 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
|
|
84585
84825
|
try {
|
|
84586
|
-
const content =
|
|
84826
|
+
const content = readFileSync26(logPath, "utf-8");
|
|
84587
84827
|
const lines = content.split(`
|
|
84588
84828
|
`);
|
|
84589
84829
|
console.log(lines.slice(-n5).join(`
|
|
@@ -84654,4 +84894,4 @@ execute().catch((err) => {
|
|
|
84654
84894
|
process.exit(1);
|
|
84655
84895
|
});
|
|
84656
84896
|
|
|
84657
|
-
//# debugId=
|
|
84897
|
+
//# debugId=a3c20325-c0f7-811f-6475-6e2164756e21
|