@dosu/cli 0.53.0-beta.8 → 0.53.0
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/README.md +1 -1
- package/bin/dosu.js +1166 -643
- 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
|
|
5021
|
+
var VERSION = "0.53.0", INSTALL_CHANNEL = "npm";
|
|
5022
5022
|
|
|
5023
5023
|
// src/debug/logger.ts
|
|
5024
5024
|
import {
|
|
@@ -5712,12 +5712,34 @@ function answeredEcho(message, answer) {
|
|
|
5712
5712
|
function optionLabel(option) {
|
|
5713
5713
|
return option.label ?? String(option.value);
|
|
5714
5714
|
}
|
|
5715
|
+
function viewport(count, cursor3, top, rows, chrome) {
|
|
5716
|
+
const visible = Math.min(count, Math.max(4, (rows ?? 24) - chrome));
|
|
5717
|
+
let next = top;
|
|
5718
|
+
if (cursor3 < next)
|
|
5719
|
+
next = cursor3;
|
|
5720
|
+
if (cursor3 >= next + visible)
|
|
5721
|
+
next = cursor3 - visible + 1;
|
|
5722
|
+
next = Math.max(0, Math.min(next, count - visible));
|
|
5723
|
+
return { top: next, visible, above: next, below: count - next - visible };
|
|
5724
|
+
}
|
|
5725
|
+
function moreAbove(n2) {
|
|
5726
|
+
return n2 > 0 ? [import_picocolors11.default.dim(` ↑ ${n2} more`)] : [];
|
|
5727
|
+
}
|
|
5728
|
+
function moreBelow(n2) {
|
|
5729
|
+
return n2 > 0 ? [import_picocolors11.default.dim(` ↓ ${n2} more`)] : [];
|
|
5730
|
+
}
|
|
5715
5731
|
function select(opts, io = {}) {
|
|
5732
|
+
const { output } = resolveIO(io);
|
|
5716
5733
|
let selected = 0;
|
|
5734
|
+
let top = 0;
|
|
5717
5735
|
const labelWidth = Math.max(...opts.options.map((o2) => optionLabel(o2).length));
|
|
5718
5736
|
return runInteractive({
|
|
5719
5737
|
render() {
|
|
5720
|
-
const
|
|
5738
|
+
const count = opts.options.length;
|
|
5739
|
+
const view = viewport(count, selected, top, output.rows, 8);
|
|
5740
|
+
top = view.top;
|
|
5741
|
+
const rows = opts.options.slice(top, top + view.visible).map((option, offset) => {
|
|
5742
|
+
const index = top + offset;
|
|
5721
5743
|
const label = optionLabel(option).padEnd(labelWidth);
|
|
5722
5744
|
const hint = option.hint ? ` ${import_picocolors11.default.dim(option.hint)}` : "";
|
|
5723
5745
|
return index === selected ? `${brand(POINTER2)} ${import_picocolors11.default.bold(brand(label))}${hint}` : ` ${import_picocolors11.default.dim(label)}${hint}`;
|
|
@@ -5725,7 +5747,9 @@ function select(opts, io = {}) {
|
|
|
5725
5747
|
return [
|
|
5726
5748
|
import_picocolors11.default.bold(opts.message),
|
|
5727
5749
|
"",
|
|
5750
|
+
...moreAbove(view.above),
|
|
5728
5751
|
...rows,
|
|
5752
|
+
...moreBelow(view.below),
|
|
5729
5753
|
"",
|
|
5730
5754
|
import_picocolors11.default.dim(`↑↓ move ${DOT} enter select ${DOT} esc cancel`)
|
|
5731
5755
|
];
|
|
@@ -5763,13 +5787,9 @@ function multiselect2(opts, io = {}) {
|
|
|
5763
5787
|
return runInteractive({
|
|
5764
5788
|
render() {
|
|
5765
5789
|
const count = opts.options.length;
|
|
5766
|
-
const
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
if (cursor3 >= top + visible)
|
|
5770
|
-
top = cursor3 - visible + 1;
|
|
5771
|
-
top = Math.max(0, Math.min(top, count - visible));
|
|
5772
|
-
const rows = opts.options.slice(top, top + visible).map((option, offset) => {
|
|
5790
|
+
const view = viewport(count, cursor3, top, output.rows, 9);
|
|
5791
|
+
top = view.top;
|
|
5792
|
+
const rows = opts.options.slice(top, top + view.visible).map((option, offset) => {
|
|
5773
5793
|
const index = top + offset;
|
|
5774
5794
|
const isPicked = picked.has(option.value);
|
|
5775
5795
|
const box = isPicked ? brand(BOX_ON) : import_picocolors11.default.dim(BOX_OFF);
|
|
@@ -5778,15 +5798,13 @@ function multiselect2(opts, io = {}) {
|
|
|
5778
5798
|
const suffix = status ? ` ${status}` : "";
|
|
5779
5799
|
return index === cursor3 ? `${brand(POINTER2)} ${box} ${import_picocolors11.default.bold(brand(label))}${suffix}` : ` ${box} ${import_picocolors11.default.dim(label)}${suffix}`;
|
|
5780
5800
|
});
|
|
5781
|
-
const above = top;
|
|
5782
|
-
const below = count - top - visible;
|
|
5783
5801
|
const summary = opts.summary?.(pickedValues());
|
|
5784
5802
|
return [
|
|
5785
5803
|
import_picocolors11.default.bold(opts.message),
|
|
5786
5804
|
"",
|
|
5787
|
-
...
|
|
5805
|
+
...moreAbove(view.above),
|
|
5788
5806
|
...rows,
|
|
5789
|
-
...
|
|
5807
|
+
...moreBelow(view.below),
|
|
5790
5808
|
"",
|
|
5791
5809
|
...error ? [import_picocolors11.default.yellow(error)] : summary ? [import_picocolors11.default.dim(summary)] : [],
|
|
5792
5810
|
import_picocolors11.default.dim(`↑↓ move ${DOT} space toggle ${DOT} a all ${DOT} enter confirm ${DOT} esc cancel`)
|
|
@@ -7982,6 +8000,14 @@ function mcpURL(deploymentID) {
|
|
|
7982
8000
|
function mcpBaseURL() {
|
|
7983
8001
|
return `${getBackendURL()}/v1/mcp`;
|
|
7984
8002
|
}
|
|
8003
|
+
function mcpEndpoint(cfg) {
|
|
8004
|
+
if (cfg.mode === MODE_OSS)
|
|
8005
|
+
return mcpBaseURL();
|
|
8006
|
+
const deploymentID = cfg.active_account?.target?.deployment_id;
|
|
8007
|
+
if (!deploymentID)
|
|
8008
|
+
throw new Error("deployment ID is required");
|
|
8009
|
+
return mcpURL(deploymentID);
|
|
8010
|
+
}
|
|
7985
8011
|
function mcpHeaders(apiKey) {
|
|
7986
8012
|
if (!apiKey) {
|
|
7987
8013
|
throw new Error("API key is required. Run 'dosu setup' to create one.");
|
|
@@ -8008,18 +8034,23 @@ function mcpRemoteServer(url, apiKey) {
|
|
|
8008
8034
|
};
|
|
8009
8035
|
}
|
|
8010
8036
|
function loadJSONConfig(path3) {
|
|
8037
|
+
try {
|
|
8038
|
+
return readJSONConfig(path3);
|
|
8039
|
+
} catch {
|
|
8040
|
+
return {};
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
function readJSONConfig(path3) {
|
|
8011
8044
|
if (!existsSync7(path3))
|
|
8012
8045
|
return {};
|
|
8013
|
-
|
|
8014
|
-
if (!
|
|
8046
|
+
const raw = readFileSync7(path3, "utf-8").trim();
|
|
8047
|
+
if (!raw)
|
|
8015
8048
|
return {};
|
|
8016
|
-
if (path3.endsWith(".jsonc")) {
|
|
8017
|
-
data = stripJSONComments(data);
|
|
8018
|
-
}
|
|
8019
8049
|
try {
|
|
8020
|
-
return JSON.parse(
|
|
8021
|
-
} catch {
|
|
8022
|
-
|
|
8050
|
+
return JSON.parse(stripTrailingCommas(stripJSONComments(raw)));
|
|
8051
|
+
} catch (err) {
|
|
8052
|
+
const detail = err.message;
|
|
8053
|
+
throw new Error(`Could not parse ${path3} as JSON (${detail}). Fix the file and retry.`);
|
|
8023
8054
|
}
|
|
8024
8055
|
}
|
|
8025
8056
|
function stripJSONComments(data) {
|
|
@@ -8027,25 +8058,7 @@ function stripJSONComments(data) {
|
|
|
8027
8058
|
let i2 = 0;
|
|
8028
8059
|
while (i2 < data.length) {
|
|
8029
8060
|
if (data[i2] === '"') {
|
|
8030
|
-
|
|
8031
|
-
i2++;
|
|
8032
|
-
while (i2 < data.length && data[i2] !== '"') {
|
|
8033
|
-
if (data[i2] === "\\") {
|
|
8034
|
-
result.push(data[i2]);
|
|
8035
|
-
i2++;
|
|
8036
|
-
if (i2 < data.length) {
|
|
8037
|
-
result.push(data[i2]);
|
|
8038
|
-
i2++;
|
|
8039
|
-
}
|
|
8040
|
-
continue;
|
|
8041
|
-
}
|
|
8042
|
-
result.push(data[i2]);
|
|
8043
|
-
i2++;
|
|
8044
|
-
}
|
|
8045
|
-
if (i2 < data.length) {
|
|
8046
|
-
result.push(data[i2]);
|
|
8047
|
-
i2++;
|
|
8048
|
-
}
|
|
8061
|
+
i2 = copyStringLiteral(data, i2, result);
|
|
8049
8062
|
continue;
|
|
8050
8063
|
}
|
|
8051
8064
|
if (i2 + 1 < data.length && data[i2] === "/" && data[i2 + 1] === "/") {
|
|
@@ -8059,8 +8072,7 @@ function stripJSONComments(data) {
|
|
|
8059
8072
|
i2 += 2;
|
|
8060
8073
|
while (i2 + 1 < data.length && !(data[i2] === "*" && data[i2 + 1] === "/"))
|
|
8061
8074
|
i2++;
|
|
8062
|
-
|
|
8063
|
-
i2 += 2;
|
|
8075
|
+
i2 = i2 + 1 < data.length ? i2 + 2 : data.length;
|
|
8064
8076
|
continue;
|
|
8065
8077
|
}
|
|
8066
8078
|
result.push(data[i2]);
|
|
@@ -8068,6 +8080,43 @@ function stripJSONComments(data) {
|
|
|
8068
8080
|
}
|
|
8069
8081
|
return result.join("");
|
|
8070
8082
|
}
|
|
8083
|
+
function copyStringLiteral(data, start, out) {
|
|
8084
|
+
let i2 = start + 1;
|
|
8085
|
+
out.push('"');
|
|
8086
|
+
while (i2 < data.length && data[i2] !== '"') {
|
|
8087
|
+
out.push(data[i2]);
|
|
8088
|
+
if (data[i2] === "\\" && i2 + 1 < data.length)
|
|
8089
|
+
out.push(data[++i2]);
|
|
8090
|
+
i2++;
|
|
8091
|
+
}
|
|
8092
|
+
if (i2 < data.length) {
|
|
8093
|
+
out.push('"');
|
|
8094
|
+
i2++;
|
|
8095
|
+
}
|
|
8096
|
+
return i2;
|
|
8097
|
+
}
|
|
8098
|
+
function stripTrailingCommas(data) {
|
|
8099
|
+
const result = [];
|
|
8100
|
+
let i2 = 0;
|
|
8101
|
+
while (i2 < data.length) {
|
|
8102
|
+
if (data[i2] === '"') {
|
|
8103
|
+
i2 = copyStringLiteral(data, i2, result);
|
|
8104
|
+
continue;
|
|
8105
|
+
}
|
|
8106
|
+
if (data[i2] === ",") {
|
|
8107
|
+
let j = i2 + 1;
|
|
8108
|
+
while (j < data.length && /\s/.test(data[j]))
|
|
8109
|
+
j++;
|
|
8110
|
+
if (data[j] === "}" || data[j] === "]") {
|
|
8111
|
+
i2++;
|
|
8112
|
+
continue;
|
|
8113
|
+
}
|
|
8114
|
+
}
|
|
8115
|
+
result.push(data[i2]);
|
|
8116
|
+
i2++;
|
|
8117
|
+
}
|
|
8118
|
+
return result.join("");
|
|
8119
|
+
}
|
|
8071
8120
|
function saveJSONConfig(path3, cfg) {
|
|
8072
8121
|
writeSecureFile(path3, JSON.stringify(cfg, null, 2));
|
|
8073
8122
|
}
|
|
@@ -8086,7 +8135,7 @@ function isJSONKeyConfigured(configPath, topLevelKey) {
|
|
|
8086
8135
|
return "dosu" in section;
|
|
8087
8136
|
}
|
|
8088
8137
|
function installJSONServer(configPath, topKey, server) {
|
|
8089
|
-
const jsonCfg =
|
|
8138
|
+
const jsonCfg = readJSONConfig(configPath);
|
|
8090
8139
|
let section = jsonCfg[topKey];
|
|
8091
8140
|
if (typeof section !== "object" || section === null) {
|
|
8092
8141
|
section = {};
|
|
@@ -8098,18 +8147,19 @@ function installJSONServer(configPath, topKey, server) {
|
|
|
8098
8147
|
function removeJSONServer(configPath, topKey) {
|
|
8099
8148
|
let jsonCfg;
|
|
8100
8149
|
try {
|
|
8101
|
-
jsonCfg =
|
|
8150
|
+
jsonCfg = readJSONConfig(configPath);
|
|
8102
8151
|
} catch {
|
|
8103
8152
|
return;
|
|
8104
8153
|
}
|
|
8105
8154
|
const section = jsonCfg[topKey];
|
|
8106
|
-
if (typeof section
|
|
8107
|
-
|
|
8108
|
-
|
|
8155
|
+
if (typeof section !== "object" || section === null || !("dosu" in section))
|
|
8156
|
+
return;
|
|
8157
|
+
delete section.dosu;
|
|
8109
8158
|
saveJSONConfig(configPath, jsonCfg);
|
|
8110
8159
|
}
|
|
8111
8160
|
var import_write_file_atomic, writeFileAtomic, MCP_REMOTE_VERSION = "0.1.38";
|
|
8112
8161
|
var init_config_helpers = __esm(() => {
|
|
8162
|
+
init_config();
|
|
8113
8163
|
import_write_file_atomic = __toESM(require_lib(), 1);
|
|
8114
8164
|
writeFileAtomic = import_write_file_atomic.default;
|
|
8115
8165
|
});
|
|
@@ -8358,16 +8408,164 @@ var init_agents = __esm(() => {
|
|
|
8358
8408
|
init_formats();
|
|
8359
8409
|
});
|
|
8360
8410
|
|
|
8411
|
+
// src/sessions/project-dir.ts
|
|
8412
|
+
import {
|
|
8413
|
+
closeSync as closeSync2,
|
|
8414
|
+
existsSync as existsSync9,
|
|
8415
|
+
mkdirSync as mkdirSync6,
|
|
8416
|
+
openSync as openSync2,
|
|
8417
|
+
readFileSync as readFileSync9,
|
|
8418
|
+
readSync,
|
|
8419
|
+
renameSync as renameSync2,
|
|
8420
|
+
statSync as statSync3,
|
|
8421
|
+
writeFileSync as writeFileSync6
|
|
8422
|
+
} from "node:fs";
|
|
8423
|
+
import { join as join8 } from "node:path";
|
|
8424
|
+
function cachePath(configDir) {
|
|
8425
|
+
return join8(configDir, CACHE_FILENAME2);
|
|
8426
|
+
}
|
|
8427
|
+
function loadCacheFile(configDir) {
|
|
8428
|
+
try {
|
|
8429
|
+
const raw = JSON.parse(readFileSync9(cachePath(configDir), "utf-8"));
|
|
8430
|
+
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
8431
|
+
return {};
|
|
8432
|
+
return raw.entries;
|
|
8433
|
+
} catch {
|
|
8434
|
+
return {};
|
|
8435
|
+
}
|
|
8436
|
+
}
|
|
8437
|
+
function saveCacheFile(configDir, entries) {
|
|
8438
|
+
try {
|
|
8439
|
+
if (!existsSync9(configDir))
|
|
8440
|
+
mkdirSync6(configDir, { recursive: true, mode: 448 });
|
|
8441
|
+
const path3 = cachePath(configDir);
|
|
8442
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8443
|
+
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
8444
|
+
writeFileSync6(tmp, JSON.stringify(file), { mode: 384 });
|
|
8445
|
+
renameSync2(tmp, path3);
|
|
8446
|
+
} catch {}
|
|
8447
|
+
}
|
|
8448
|
+
function readHead(path3, bytes = HEAD_BYTES) {
|
|
8449
|
+
let fd;
|
|
8450
|
+
try {
|
|
8451
|
+
fd = openSync2(path3, "r");
|
|
8452
|
+
} catch {
|
|
8453
|
+
return null;
|
|
8454
|
+
}
|
|
8455
|
+
try {
|
|
8456
|
+
const buf = Buffer.alloc(bytes);
|
|
8457
|
+
const read = readSync(fd, buf, 0, bytes, 0);
|
|
8458
|
+
return buf.subarray(0, read).toString("utf-8");
|
|
8459
|
+
} catch {
|
|
8460
|
+
return null;
|
|
8461
|
+
} finally {
|
|
8462
|
+
closeSync2(fd);
|
|
8463
|
+
}
|
|
8464
|
+
}
|
|
8465
|
+
function cwdFromJsonlHead(text) {
|
|
8466
|
+
for (const line of text.split(`
|
|
8467
|
+
`).slice(0, HEAD_LINES)) {
|
|
8468
|
+
if (!line.includes('"cwd"'))
|
|
8469
|
+
continue;
|
|
8470
|
+
try {
|
|
8471
|
+
const obj = JSON.parse(line);
|
|
8472
|
+
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
8473
|
+
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
8474
|
+
return cwd;
|
|
8475
|
+
} catch {}
|
|
8476
|
+
}
|
|
8477
|
+
return null;
|
|
8478
|
+
}
|
|
8479
|
+
function unmungeSlug(slug, exists = existsSync9) {
|
|
8480
|
+
const tokens = slug.replace(/^-/, "").split("-");
|
|
8481
|
+
if (tokens.length === 0 || tokens[0] === "")
|
|
8482
|
+
return null;
|
|
8483
|
+
let budget = 5000;
|
|
8484
|
+
const walk = (prefix, index) => {
|
|
8485
|
+
if (budget-- <= 0)
|
|
8486
|
+
return null;
|
|
8487
|
+
if (index === tokens.length)
|
|
8488
|
+
return exists(prefix) ? prefix : null;
|
|
8489
|
+
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
8490
|
+
if (asSegment)
|
|
8491
|
+
return asSegment;
|
|
8492
|
+
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
8493
|
+
};
|
|
8494
|
+
return walk("", 0);
|
|
8495
|
+
}
|
|
8496
|
+
function fileMtime(path3) {
|
|
8497
|
+
try {
|
|
8498
|
+
return statSync3(path3).mtime.toISOString();
|
|
8499
|
+
} catch {
|
|
8500
|
+
return "";
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8503
|
+
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
8504
|
+
const exists = deps.exists ?? existsSync9;
|
|
8505
|
+
const head = deps.readHead ?? readHead;
|
|
8506
|
+
const mtime = deps.mtime ?? fileMtime;
|
|
8507
|
+
const entries = loadCacheFile(configDir);
|
|
8508
|
+
let dirty = false;
|
|
8509
|
+
const compute = (session) => {
|
|
8510
|
+
switch (session.harness) {
|
|
8511
|
+
case "opencode":
|
|
8512
|
+
return session.project ?? null;
|
|
8513
|
+
case "claude": {
|
|
8514
|
+
const text = head(session.path);
|
|
8515
|
+
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
8516
|
+
if (cwd)
|
|
8517
|
+
return cwd;
|
|
8518
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8519
|
+
}
|
|
8520
|
+
case "codex": {
|
|
8521
|
+
const text = head(session.path);
|
|
8522
|
+
return text ? cwdFromJsonlHead(text) : null;
|
|
8523
|
+
}
|
|
8524
|
+
case "cursor":
|
|
8525
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8526
|
+
default:
|
|
8527
|
+
return null;
|
|
8528
|
+
}
|
|
8529
|
+
};
|
|
8530
|
+
return {
|
|
8531
|
+
cached(key) {
|
|
8532
|
+
return entries[key]?.dir ?? null;
|
|
8533
|
+
},
|
|
8534
|
+
resolve(session) {
|
|
8535
|
+
const key = `${session.harness}/${session.id}`;
|
|
8536
|
+
const cached = entries[key];
|
|
8537
|
+
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
8538
|
+
return cached.dir;
|
|
8539
|
+
}
|
|
8540
|
+
const dir = compute(session);
|
|
8541
|
+
entries[key] = { dir, mtime: mtime(session.path) };
|
|
8542
|
+
dirty = true;
|
|
8543
|
+
return dir;
|
|
8544
|
+
},
|
|
8545
|
+
flush() {
|
|
8546
|
+
if (!dirty)
|
|
8547
|
+
return;
|
|
8548
|
+
saveCacheFile(configDir, entries);
|
|
8549
|
+
dirty = false;
|
|
8550
|
+
}
|
|
8551
|
+
};
|
|
8552
|
+
}
|
|
8553
|
+
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
8554
|
+
var init_project_dir = __esm(() => {
|
|
8555
|
+
init_config();
|
|
8556
|
+
HEAD_BYTES = 128 * 1024;
|
|
8557
|
+
});
|
|
8558
|
+
|
|
8361
8559
|
// src/sessions/scan.ts
|
|
8362
|
-
import { existsSync as
|
|
8560
|
+
import { existsSync as existsSync10, readdirSync, statSync as statSync4 } from "node:fs";
|
|
8363
8561
|
import { createRequire as createRequire2 } from "node:module";
|
|
8364
8562
|
import { homedir as homedir3 } from "node:os";
|
|
8365
|
-
import { basename, join as
|
|
8563
|
+
import { basename, join as join9 } from "node:path";
|
|
8366
8564
|
function listDir(dir) {
|
|
8367
8565
|
try {
|
|
8368
8566
|
return readdirSync(dir, { withFileTypes: true }).map((entry) => ({
|
|
8369
8567
|
name: entry.name,
|
|
8370
|
-
path:
|
|
8568
|
+
path: join9(dir, entry.name),
|
|
8371
8569
|
isDir: entry.isDirectory()
|
|
8372
8570
|
}));
|
|
8373
8571
|
} catch {
|
|
@@ -8380,7 +8578,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8380
8578
|
return null;
|
|
8381
8579
|
let mtime;
|
|
8382
8580
|
try {
|
|
8383
|
-
mtime =
|
|
8581
|
+
mtime = statSync4(filePath).mtime;
|
|
8384
8582
|
} catch {
|
|
8385
8583
|
return null;
|
|
8386
8584
|
}
|
|
@@ -8394,7 +8592,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8394
8592
|
}
|
|
8395
8593
|
function scanClaude(home) {
|
|
8396
8594
|
const sessions = [];
|
|
8397
|
-
for (const project of listDir(
|
|
8595
|
+
for (const project of listDir(join9(home, ".claude", "projects"))) {
|
|
8398
8596
|
if (!project.isDir)
|
|
8399
8597
|
continue;
|
|
8400
8598
|
for (const entry of listDir(project.path)) {
|
|
@@ -8409,10 +8607,10 @@ function scanClaude(home) {
|
|
|
8409
8607
|
}
|
|
8410
8608
|
function scanCursor(home) {
|
|
8411
8609
|
const sessions = [];
|
|
8412
|
-
for (const project of listDir(
|
|
8610
|
+
for (const project of listDir(join9(home, ".cursor", "projects"))) {
|
|
8413
8611
|
if (!project.isDir)
|
|
8414
8612
|
continue;
|
|
8415
|
-
for (const entry of listDir(
|
|
8613
|
+
for (const entry of listDir(join9(project.path, "agent-transcripts"))) {
|
|
8416
8614
|
const files = entry.isDir ? listDir(entry.path).filter((f2) => !f2.isDir) : [entry];
|
|
8417
8615
|
for (const file of files) {
|
|
8418
8616
|
const session = sessionFromFile(file.path, "cursor", project.name);
|
|
@@ -8424,9 +8622,9 @@ function scanCursor(home) {
|
|
|
8424
8622
|
return sessions;
|
|
8425
8623
|
}
|
|
8426
8624
|
function scanCodex(home, env) {
|
|
8427
|
-
const codexHome2 = env.CODEX_HOME ??
|
|
8625
|
+
const codexHome2 = env.CODEX_HOME ?? join9(home, ".codex");
|
|
8428
8626
|
const sessions = [];
|
|
8429
|
-
for (const year of listDir(
|
|
8627
|
+
for (const year of listDir(join9(codexHome2, "sessions"))) {
|
|
8430
8628
|
if (!year.isDir)
|
|
8431
8629
|
continue;
|
|
8432
8630
|
for (const month of listDir(year.path)) {
|
|
@@ -8482,9 +8680,9 @@ function querySqlite(dbPath, sql) {
|
|
|
8482
8680
|
}
|
|
8483
8681
|
}
|
|
8484
8682
|
function scanOpencode(home, env) {
|
|
8485
|
-
const dataDir = env.XDG_DATA_HOME ??
|
|
8486
|
-
const dbPath =
|
|
8487
|
-
if (!
|
|
8683
|
+
const dataDir = env.XDG_DATA_HOME ?? join9(home, ".local", "share");
|
|
8684
|
+
const dbPath = join9(dataDir, "opencode", "opencode.db");
|
|
8685
|
+
if (!existsSync10(dbPath))
|
|
8488
8686
|
return [];
|
|
8489
8687
|
const rows = querySqlite(dbPath, "SELECT id, directory, time_updated FROM session WHERE parent_id IS NULL");
|
|
8490
8688
|
if (!rows)
|
|
@@ -8522,10 +8720,10 @@ function scanAgentSessions(options = {}) {
|
|
|
8522
8720
|
var init_scan = () => {};
|
|
8523
8721
|
|
|
8524
8722
|
// src/sync/watermark.ts
|
|
8525
|
-
import { existsSync as
|
|
8526
|
-
import { join as
|
|
8723
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8724
|
+
import { join as join10 } from "node:path";
|
|
8527
8725
|
function syncStatePath(configDir = getConfigDir()) {
|
|
8528
|
-
return
|
|
8726
|
+
return join10(configDir, STATE_FILENAME);
|
|
8529
8727
|
}
|
|
8530
8728
|
function loadSyncState(configDir = getConfigDir()) {
|
|
8531
8729
|
const empty = {
|
|
@@ -8534,13 +8732,13 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
8534
8732
|
consecutive_failures: 0
|
|
8535
8733
|
};
|
|
8536
8734
|
const path3 = syncStatePath(configDir);
|
|
8537
|
-
if (!
|
|
8735
|
+
if (!existsSync11(path3))
|
|
8538
8736
|
return empty;
|
|
8539
8737
|
try {
|
|
8540
|
-
const raw = JSON.parse(
|
|
8738
|
+
const raw = JSON.parse(readFileSync10(path3, "utf-8"));
|
|
8541
8739
|
if (raw.schema_version !== STATE_SCHEMA_VERSION)
|
|
8542
8740
|
return empty;
|
|
8543
|
-
const
|
|
8741
|
+
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) => ({
|
|
8544
8742
|
at: record.at,
|
|
8545
8743
|
session: record.session,
|
|
8546
8744
|
...typeof record.project === "string" ? { project: record.project } : {}
|
|
@@ -8554,8 +8752,8 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
8554
8752
|
watermark: typeof raw.watermark === "string" ? raw.watermark : null,
|
|
8555
8753
|
last_attempt_at: typeof raw.last_attempt_at === "string" ? raw.last_attempt_at : undefined,
|
|
8556
8754
|
consecutive_failures: typeof raw.consecutive_failures === "number" && raw.consecutive_failures >= 0 ? raw.consecutive_failures : 0,
|
|
8557
|
-
mined_sessions:
|
|
8558
|
-
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined :
|
|
8755
|
+
mined_sessions: studiedSessions,
|
|
8756
|
+
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined : studiedSessions.length,
|
|
8559
8757
|
total_notes: typeof raw.total_notes === "number" && raw.total_notes >= 0 ? raw.total_notes : 0,
|
|
8560
8758
|
total_learning_tokens: typeof raw.total_learning_tokens === "number" && raw.total_learning_tokens >= 0 ? raw.total_learning_tokens : 0,
|
|
8561
8759
|
...lastRefusal ? { last_refusal: lastRefusal } : {},
|
|
@@ -8577,13 +8775,24 @@ function setSyncPaused(paused, configDir = getConfigDir()) {
|
|
|
8577
8775
|
delete state.paused;
|
|
8578
8776
|
saveSyncState(state, configDir);
|
|
8579
8777
|
}
|
|
8778
|
+
function resetSyncState(configDir = getConfigDir()) {
|
|
8779
|
+
const previous = loadSyncState(configDir);
|
|
8780
|
+
const fresh = {
|
|
8781
|
+
schema_version: STATE_SCHEMA_VERSION,
|
|
8782
|
+
watermark: null,
|
|
8783
|
+
consecutive_failures: 0,
|
|
8784
|
+
...previous.project_filter ? { project_filter: previous.project_filter } : {},
|
|
8785
|
+
...previous.paused ? { paused: true } : {}
|
|
8786
|
+
};
|
|
8787
|
+
saveSyncState(fresh, configDir);
|
|
8788
|
+
}
|
|
8580
8789
|
function saveSyncState(state, configDir = getConfigDir()) {
|
|
8581
|
-
if (!
|
|
8582
|
-
|
|
8790
|
+
if (!existsSync11(configDir))
|
|
8791
|
+
mkdirSync7(configDir, { recursive: true, mode: 448 });
|
|
8583
8792
|
const path3 = syncStatePath(configDir);
|
|
8584
8793
|
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8585
|
-
|
|
8586
|
-
|
|
8794
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
8795
|
+
renameSync3(tmp, path3);
|
|
8587
8796
|
}
|
|
8588
8797
|
function backoffUntil(state) {
|
|
8589
8798
|
if (state.consecutive_failures === 0 || !state.last_attempt_at)
|
|
@@ -8627,7 +8836,7 @@ function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAU
|
|
|
8627
8836
|
}
|
|
8628
8837
|
return { ready, open: open2 };
|
|
8629
8838
|
}
|
|
8630
|
-
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS,
|
|
8839
|
+
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)";
|
|
8631
8840
|
var init_watermark = __esm(() => {
|
|
8632
8841
|
init_config();
|
|
8633
8842
|
DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
|
|
@@ -8643,20 +8852,34 @@ async function fetchReportNotes(cfg) {
|
|
|
8643
8852
|
}
|
|
8644
8853
|
const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
|
|
8645
8854
|
const client = createTypedClient2(cfg);
|
|
8646
|
-
const
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
...
|
|
8656
|
-
|
|
8657
|
-
|
|
8855
|
+
const notes = [];
|
|
8856
|
+
let cursor3;
|
|
8857
|
+
let truncated = false;
|
|
8858
|
+
for (let page = 0;page < REPORT_NOTES_MAX_PAGES; page++) {
|
|
8859
|
+
const result = await client.notes.listMine.query({
|
|
8860
|
+
org_id: orgId,
|
|
8861
|
+
limit: REPORT_NOTES_LIMIT,
|
|
8862
|
+
...cursor3 ? { cursor: cursor3 } : {}
|
|
8863
|
+
});
|
|
8864
|
+
notes.push(...result.notes.map((note) => ({
|
|
8865
|
+
title: note.title,
|
|
8866
|
+
content: note.body,
|
|
8867
|
+
at: note.created_at,
|
|
8868
|
+
...note.transcript_id ? { transcript_id: note.transcript_id } : {},
|
|
8869
|
+
...note.repo ? { repo: note.repo } : {},
|
|
8870
|
+
...note.branch ? { branch: note.branch } : {}
|
|
8871
|
+
})));
|
|
8872
|
+
cursor3 = result.next_cursor;
|
|
8873
|
+
if (!cursor3) {
|
|
8874
|
+
truncated = result.notes.length === REPORT_NOTES_LIMIT;
|
|
8875
|
+
break;
|
|
8876
|
+
}
|
|
8877
|
+
if (page === REPORT_NOTES_MAX_PAGES - 1)
|
|
8878
|
+
truncated = true;
|
|
8879
|
+
}
|
|
8880
|
+
return { notes, truncated };
|
|
8658
8881
|
}
|
|
8659
|
-
var REPORT_NOTES_LIMIT = 500;
|
|
8882
|
+
var REPORT_NOTES_LIMIT = 500, REPORT_NOTES_MAX_PAGES = 40;
|
|
8660
8883
|
|
|
8661
8884
|
// src/report/css.ts
|
|
8662
8885
|
var REPORT_CSS = `
|
|
@@ -8831,38 +9054,6 @@ function applyStatusDefaults(candidates, dryRun) {
|
|
|
8831
9054
|
return { ...c2, status: known.has(current) ? current : fallback };
|
|
8832
9055
|
});
|
|
8833
9056
|
}
|
|
8834
|
-
function tokenTotalsFromCandidates(candidates, inventory) {
|
|
8835
|
-
if (candidates.length === 0)
|
|
8836
|
-
return null;
|
|
8837
|
-
let replaced = 0;
|
|
8838
|
-
let hasRediscovery = false;
|
|
8839
|
-
let readCost = 0;
|
|
8840
|
-
for (const c2 of candidates) {
|
|
8841
|
-
const raw = c2.approx_rediscovery_tokens;
|
|
8842
|
-
if (raw != null) {
|
|
8843
|
-
hasRediscovery = true;
|
|
8844
|
-
replaced += Math.max(0, Number(raw) || 0);
|
|
8845
|
-
}
|
|
8846
|
-
const blob = `${c2.title ?? ""}
|
|
8847
|
-
${c2.content ?? ""}`;
|
|
8848
|
-
readCost += Math.round(blob.length / CHARS_PER_TOKEN);
|
|
8849
|
-
}
|
|
8850
|
-
if (!hasRediscovery)
|
|
8851
|
-
return null;
|
|
8852
|
-
let baseline = inventory.totals?.learning_tokens ?? 0;
|
|
8853
|
-
if (!baseline) {
|
|
8854
|
-
baseline = inventory.transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
8855
|
-
}
|
|
8856
|
-
const saved = Math.max(0, replaced - readCost);
|
|
8857
|
-
const pct = baseline ? Math.round(1000 * saved / baseline) / 10 : 0;
|
|
8858
|
-
return {
|
|
8859
|
-
baseline_tokens: baseline,
|
|
8860
|
-
replaced_baseline_tokens: replaced,
|
|
8861
|
-
read_knowledge_tokens: readCost,
|
|
8862
|
-
tokens_saved: saved,
|
|
8863
|
-
pct_saved: pct
|
|
8864
|
-
};
|
|
8865
|
-
}
|
|
8866
9057
|
function presentationCopy(c2) {
|
|
8867
9058
|
const idea = (c2.plain_english || "").trim() || (c2.content || "").trim();
|
|
8868
9059
|
const how = (c2.how_found || "").trim();
|
|
@@ -9236,11 +9427,9 @@ function buildReportHtml(options) {
|
|
|
9236
9427
|
return bv - av;
|
|
9237
9428
|
});
|
|
9238
9429
|
const org = options.orgName || "Your team";
|
|
9239
|
-
const
|
|
9240
|
-
const
|
|
9241
|
-
const summary = options.summary || "Local agent session logs were mined into Dosu notes so the next task can reuse them — reducing rediscovery cost.";
|
|
9430
|
+
const projects = options.projects ?? [];
|
|
9431
|
+
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.";
|
|
9242
9432
|
const generated = `${(options.generatedAt ?? new Date).toISOString().replace("T", " ").slice(0, 16)} UTC`;
|
|
9243
|
-
const derived = tokenTotalsFromCandidates(candidates, inventory);
|
|
9244
9433
|
const [notesHeading, notesLede, footerTitle, footerBody] = notesSectionCopy(candidates);
|
|
9245
9434
|
const candidateRows = candidates.map((c2, i2) => {
|
|
9246
9435
|
const [idea, work] = presentationCopy(c2);
|
|
@@ -9280,27 +9469,19 @@ function buildReportHtml(options) {
|
|
|
9280
9469
|
<td>${esc(query)}</td>
|
|
9281
9470
|
</tr>`;
|
|
9282
9471
|
}).join("");
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9472
|
+
const scannedTokens = inventory.totals?.learning_tokens || transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
9473
|
+
const writtenCount = candidates.filter((c2) => (c2.status || "written").toLowerCase() === "written").length;
|
|
9474
|
+
const writtenLabel = options.dryRun ? "Notes proposed (dry run)" : "Notes written to Dosu";
|
|
9475
|
+
const writtenValue = options.dryRun ? candidates.length : writtenCount;
|
|
9476
|
+
const tokenSection = `
|
|
9286
9477
|
<section>
|
|
9287
|
-
<h2>
|
|
9288
|
-
<p class="lede">Counterfactual: replace rediscovery stretches with a Dosu <code>read_knowledge</code> hit.</p>
|
|
9478
|
+
<h2>This run</h2>
|
|
9289
9479
|
<div class="stats">
|
|
9290
|
-
<div class="stat"><div class="label">
|
|
9291
|
-
<div class="stat"><div class="label"
|
|
9292
|
-
<div class="stat"><div class="label">Read cost</div><div class="value">${fmtInt(derived.read_knowledge_tokens)}</div></div>
|
|
9293
|
-
<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>
|
|
9480
|
+
<div class="stat"><div class="label">Tokens scanned</div><div class="value">${fmtInt(scannedTokens)}</div></div>
|
|
9481
|
+
<div class="stat highlight"><div class="label">${esc(writtenLabel)}</div><div class="value">${fmtInt(writtenValue)}</div></div>
|
|
9294
9482
|
</div>
|
|
9295
9483
|
</section>`;
|
|
9296
|
-
|
|
9297
|
-
tokenSection = `
|
|
9298
|
-
<section>
|
|
9299
|
-
<h2>Estimated context savings</h2>
|
|
9300
|
-
<p class="muted">No rediscovery estimates on the notes yet — each candidate needs <code>approx_rediscovery_tokens</code>.</p>
|
|
9301
|
-
</section>`;
|
|
9302
|
-
}
|
|
9303
|
-
const notesBody = candidateRows.join("") || '<p class="muted">No write_knowledge payloads yet. Run knowledge sync so the miner extracts learnings.</p>';
|
|
9484
|
+
const notesBody = candidateRows.join("") || '<p class="muted">No write_knowledge payloads yet. Run knowledge sync so Dosu studies your sessions and extracts learnings.</p>';
|
|
9304
9485
|
const lede = notesLede ? `<p class="lede">${esc(notesLede)}</p>` : "";
|
|
9305
9486
|
return `<!DOCTYPE html>
|
|
9306
9487
|
<html lang="en">
|
|
@@ -9319,9 +9500,8 @@ ${REPORT_CSS}
|
|
|
9319
9500
|
<h1>${esc(org)}</h1>
|
|
9320
9501
|
<p class="lede">${esc(summary)}</p>
|
|
9321
9502
|
<p class="meta-line">
|
|
9322
|
-
|
|
9323
|
-
·
|
|
9324
|
-
· generated ${esc(generated)}
|
|
9503
|
+
${projects.length > 0 ? `Projects ${projects.map((p2) => `<code>${esc(p2)}</code>`).join(", ")} · ` : ""}${transcripts.length} session${transcripts.length === 1 ? "" : "s"} with notes
|
|
9504
|
+
· generated ${esc(generated)}${options.truncated ? " · newest notes only (server capped the fetch)" : ""}
|
|
9325
9505
|
</p>
|
|
9326
9506
|
<div class="toolbar no-print">
|
|
9327
9507
|
<button type="button" onclick="window.print()">Print / Save as PDF</button>
|
|
@@ -9367,7 +9547,7 @@ ${REPORT_CSS}
|
|
|
9367
9547
|
</html>
|
|
9368
9548
|
`;
|
|
9369
9549
|
}
|
|
9370
|
-
var
|
|
9550
|
+
var MAX_TRACE_STEPS = 50, PREVIEW_CHARS = 80, SQL_DISPLAY, LOGS_DISPLAY, SKIP_TOOL_NAMES, TAG_RE, WS_RE;
|
|
9371
9551
|
var init_html = __esm(() => {
|
|
9372
9552
|
SQL_DISPLAY = new Set(["execute_sql", "query_run", "list_tables"]);
|
|
9373
9553
|
LOGS_DISPLAY = new Set(["query_logs"]);
|
|
@@ -9377,7 +9557,7 @@ var init_html = __esm(() => {
|
|
|
9377
9557
|
});
|
|
9378
9558
|
|
|
9379
9559
|
// src/sessions/read.ts
|
|
9380
|
-
import { readFileSync as
|
|
9560
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
9381
9561
|
function asRecord(value) {
|
|
9382
9562
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9383
9563
|
}
|
|
@@ -9493,11 +9673,11 @@ function readSessionTurns(session) {
|
|
|
9493
9673
|
try {
|
|
9494
9674
|
switch (session.harness) {
|
|
9495
9675
|
case "claude":
|
|
9496
|
-
return readClaude(
|
|
9676
|
+
return readClaude(readFileSync11(session.path, "utf8"));
|
|
9497
9677
|
case "cursor":
|
|
9498
|
-
return readCursor(
|
|
9678
|
+
return readCursor(readFileSync11(session.path, "utf8"));
|
|
9499
9679
|
case "codex":
|
|
9500
|
-
return readCodex(
|
|
9680
|
+
return readCodex(readFileSync11(session.path, "utf8"));
|
|
9501
9681
|
case "opencode":
|
|
9502
9682
|
return readOpencode(session.path, session.id);
|
|
9503
9683
|
}
|
|
@@ -9525,7 +9705,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9525
9705
|
case "claude":
|
|
9526
9706
|
case "cursor": {
|
|
9527
9707
|
let n2 = 0;
|
|
9528
|
-
for (const record of jsonlRecords(
|
|
9708
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9529
9709
|
const message = asRecord(record.message);
|
|
9530
9710
|
for (const name of toolNamesFromContent(message?.content)) {
|
|
9531
9711
|
if (isRediscoveryTool(name))
|
|
@@ -9536,7 +9716,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9536
9716
|
}
|
|
9537
9717
|
case "codex": {
|
|
9538
9718
|
let n2 = 0;
|
|
9539
|
-
for (const record of jsonlRecords(
|
|
9719
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9540
9720
|
const payload = asRecord(record.payload);
|
|
9541
9721
|
if (payload?.type === "function_call" && typeof payload.name === "string") {
|
|
9542
9722
|
if (isRediscoveryTool(payload.name))
|
|
@@ -9556,9 +9736,9 @@ function estimateSessionTokens(session) {
|
|
|
9556
9736
|
let chars = 0;
|
|
9557
9737
|
for (const turn of readSessionTurns(session))
|
|
9558
9738
|
chars += turn.text.length;
|
|
9559
|
-
return Math.round(chars /
|
|
9739
|
+
return Math.round(chars / CHARS_PER_TOKEN);
|
|
9560
9740
|
}
|
|
9561
|
-
function
|
|
9741
|
+
function isWorthStudying(session) {
|
|
9562
9742
|
const turns = readSessionTurns(session);
|
|
9563
9743
|
if (turns.length < MIN_WORTH_TURNS)
|
|
9564
9744
|
return false;
|
|
@@ -9570,7 +9750,7 @@ function isWorthMining(session) {
|
|
|
9570
9750
|
}
|
|
9571
9751
|
return false;
|
|
9572
9752
|
}
|
|
9573
|
-
var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS,
|
|
9753
|
+
var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS, CHARS_PER_TOKEN = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
|
|
9574
9754
|
var init_read = __esm(() => {
|
|
9575
9755
|
init_scan();
|
|
9576
9756
|
PLAIN_TEXT = new Set(["text"]);
|
|
@@ -9627,12 +9807,12 @@ var init_queries = __esm(() => {
|
|
|
9627
9807
|
});
|
|
9628
9808
|
|
|
9629
9809
|
// src/report/digest.ts
|
|
9630
|
-
import { readFileSync as
|
|
9810
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
9631
9811
|
function asRecord2(value) {
|
|
9632
9812
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9633
9813
|
}
|
|
9634
9814
|
function estimateTokens(text) {
|
|
9635
|
-
return Math.round(text.length /
|
|
9815
|
+
return Math.round(text.length / CHARS_PER_TOKEN2);
|
|
9636
9816
|
}
|
|
9637
9817
|
function digestEstTokens(texts, tools, resultChars) {
|
|
9638
9818
|
const countable = tools.filter((t2) => t2.name !== "tool_result");
|
|
@@ -9642,7 +9822,7 @@ function digestEstTokens(texts, tools, resultChars) {
|
|
|
9642
9822
|
payload += JSON.stringify(countable);
|
|
9643
9823
|
let tokens = estimateTokens(payload);
|
|
9644
9824
|
if (resultChars > 0)
|
|
9645
|
-
tokens += Math.max(1, Math.round(resultChars /
|
|
9825
|
+
tokens += Math.max(1, Math.round(resultChars / CHARS_PER_TOKEN2));
|
|
9646
9826
|
return tokens;
|
|
9647
9827
|
}
|
|
9648
9828
|
function copyToolInputFields(entry, inp) {
|
|
@@ -9858,16 +10038,16 @@ function sessionToDigest(session) {
|
|
|
9858
10038
|
try {
|
|
9859
10039
|
switch (session.harness) {
|
|
9860
10040
|
case "claude":
|
|
9861
|
-
return { turns: digestClaude(
|
|
10041
|
+
return { turns: digestClaude(readFileSync12(session.path, "utf8")) };
|
|
9862
10042
|
case "cursor":
|
|
9863
|
-
return { turns: digestCursor(
|
|
10043
|
+
return { turns: digestCursor(readFileSync12(session.path, "utf8")) };
|
|
9864
10044
|
case "codex":
|
|
9865
|
-
return { turns: digestCodex(
|
|
10045
|
+
return { turns: digestCodex(readFileSync12(session.path, "utf8")) };
|
|
9866
10046
|
case "opencode": {
|
|
9867
10047
|
const turns = readSessionTurns(session).map((turn, index) => ({
|
|
9868
10048
|
role: turn.role,
|
|
9869
10049
|
line: index + 1,
|
|
9870
|
-
est_tokens: Math.round(turn.text.length /
|
|
10050
|
+
est_tokens: Math.round(turn.text.length / CHARS_PER_TOKEN2),
|
|
9871
10051
|
text: [turn.text],
|
|
9872
10052
|
tools: []
|
|
9873
10053
|
}));
|
|
@@ -9892,7 +10072,7 @@ function digestTurnText(turn) {
|
|
|
9892
10072
|
return texts.filter(Boolean).join(`
|
|
9893
10073
|
`);
|
|
9894
10074
|
}
|
|
9895
|
-
var
|
|
10075
|
+
var CHARS_PER_TOKEN2 = 4, MAX_ASSISTANT_CHARS = 4000, MCP_DIGEST_TOOLS;
|
|
9896
10076
|
var init_digest = __esm(() => {
|
|
9897
10077
|
init_read();
|
|
9898
10078
|
init_queries();
|
|
@@ -10083,17 +10263,17 @@ var init_notes = __esm(() => {
|
|
|
10083
10263
|
});
|
|
10084
10264
|
|
|
10085
10265
|
// src/report/write.ts
|
|
10086
|
-
import { mkdirSync as
|
|
10266
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10087
10267
|
import { tmpdir } from "node:os";
|
|
10088
|
-
import { dirname as dirname4, join as
|
|
10268
|
+
import { dirname as dirname4, join as join11, resolve } from "node:path";
|
|
10089
10269
|
import { pathToFileURL } from "node:url";
|
|
10090
10270
|
function defaultReportPath() {
|
|
10091
|
-
return
|
|
10271
|
+
return join11(tmpdir(), "dosu-knowledge-report.html");
|
|
10092
10272
|
}
|
|
10093
10273
|
async function writeAndOpenReport(options) {
|
|
10094
10274
|
const out = resolve(options.out ?? defaultReportPath());
|
|
10095
|
-
|
|
10096
|
-
|
|
10275
|
+
mkdirSync8(dirname4(out), { recursive: true });
|
|
10276
|
+
writeFileSync8(out, options.html, "utf8");
|
|
10097
10277
|
if (options.open !== false) {
|
|
10098
10278
|
const url = pathToFileURL(out).href;
|
|
10099
10279
|
if (options.openUrl) {
|
|
@@ -10108,17 +10288,26 @@ async function writeAndOpenReport(options) {
|
|
|
10108
10288
|
var init_write = () => {};
|
|
10109
10289
|
|
|
10110
10290
|
// src/report/generate.ts
|
|
10291
|
+
import { basename as basename2 } from "node:path";
|
|
10111
10292
|
function sessionsForNotes(notes, sessions) {
|
|
10112
10293
|
const ids = new Set(notes.map((n2) => n2.transcript_id).filter((id) => Boolean(id)));
|
|
10113
10294
|
return sessions.filter((s) => ids.has(s.id));
|
|
10114
10295
|
}
|
|
10296
|
+
function defaultProjectName() {
|
|
10297
|
+
const resolver = createProjectDirResolver();
|
|
10298
|
+
return (session) => {
|
|
10299
|
+
const dir = resolver.resolve(session);
|
|
10300
|
+
return dir ? basename2(dir) : null;
|
|
10301
|
+
};
|
|
10302
|
+
}
|
|
10115
10303
|
function noteTime(note) {
|
|
10116
10304
|
const parsed = Date.parse(note.at ?? "");
|
|
10117
10305
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10118
10306
|
}
|
|
10119
10307
|
async function emitKnowledgeReport(options = {}) {
|
|
10120
10308
|
const cfg = loadConfig();
|
|
10121
|
-
const
|
|
10309
|
+
const fetched = options.notes ? { notes: options.notes, truncated: false } : await (options.fetchNotes ?? fetchReportNotes)(cfg);
|
|
10310
|
+
const notes = [...fetched.notes].sort((a3, b2) => noteTime(a3) - noteTime(b2));
|
|
10122
10311
|
const scanned = options.sessions ?? scanAgentSessions();
|
|
10123
10312
|
const sessions = sessionsForNotes(notes, scanned);
|
|
10124
10313
|
const candidates = attributeRediscovery(notes, sessions);
|
|
@@ -10127,12 +10316,16 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10127
10316
|
if (inventory.totals && state.total_learning_tokens && !options.sessions) {
|
|
10128
10317
|
inventory.totals.learning_tokens = state.total_learning_tokens;
|
|
10129
10318
|
}
|
|
10319
|
+
const projectName = options.projectName ?? defaultProjectName();
|
|
10320
|
+
const projects = [
|
|
10321
|
+
...new Set(sessions.map((s) => projectName(s) ?? s.project).filter((name) => Boolean(name)))
|
|
10322
|
+
];
|
|
10130
10323
|
const html = buildReportHtml({
|
|
10131
10324
|
inventory,
|
|
10132
10325
|
candidates,
|
|
10133
10326
|
orgName: options.orgName ?? cfg.active_account?.target?.org_name ?? "Your team",
|
|
10134
|
-
|
|
10135
|
-
|
|
10327
|
+
projects,
|
|
10328
|
+
truncated: fetched.truncated,
|
|
10136
10329
|
generatedAt: options.generatedAt,
|
|
10137
10330
|
digests: digestsForSessions(sessions)
|
|
10138
10331
|
});
|
|
@@ -10145,6 +10338,7 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10145
10338
|
}
|
|
10146
10339
|
var init_generate = __esm(() => {
|
|
10147
10340
|
init_config();
|
|
10341
|
+
init_project_dir();
|
|
10148
10342
|
init_scan();
|
|
10149
10343
|
init_watermark();
|
|
10150
10344
|
init_html();
|
|
@@ -10152,151 +10346,6 @@ var init_generate = __esm(() => {
|
|
|
10152
10346
|
init_write();
|
|
10153
10347
|
});
|
|
10154
10348
|
|
|
10155
|
-
// src/sessions/project-dir.ts
|
|
10156
|
-
import {
|
|
10157
|
-
closeSync as closeSync2,
|
|
10158
|
-
existsSync as existsSync11,
|
|
10159
|
-
mkdirSync as mkdirSync8,
|
|
10160
|
-
openSync as openSync2,
|
|
10161
|
-
readFileSync as readFileSync12,
|
|
10162
|
-
readSync,
|
|
10163
|
-
renameSync as renameSync3,
|
|
10164
|
-
statSync as statSync4,
|
|
10165
|
-
writeFileSync as writeFileSync8
|
|
10166
|
-
} from "node:fs";
|
|
10167
|
-
import { join as join11 } from "node:path";
|
|
10168
|
-
function cachePath(configDir) {
|
|
10169
|
-
return join11(configDir, CACHE_FILENAME2);
|
|
10170
|
-
}
|
|
10171
|
-
function loadCacheFile(configDir) {
|
|
10172
|
-
try {
|
|
10173
|
-
const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
|
|
10174
|
-
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
10175
|
-
return {};
|
|
10176
|
-
return raw.entries;
|
|
10177
|
-
} catch {
|
|
10178
|
-
return {};
|
|
10179
|
-
}
|
|
10180
|
-
}
|
|
10181
|
-
function saveCacheFile(configDir, entries) {
|
|
10182
|
-
try {
|
|
10183
|
-
if (!existsSync11(configDir))
|
|
10184
|
-
mkdirSync8(configDir, { recursive: true, mode: 448 });
|
|
10185
|
-
const path3 = cachePath(configDir);
|
|
10186
|
-
const tmp = `${path3}.${process.pid}.tmp`;
|
|
10187
|
-
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
10188
|
-
writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
|
|
10189
|
-
renameSync3(tmp, path3);
|
|
10190
|
-
} catch {}
|
|
10191
|
-
}
|
|
10192
|
-
function readHead(path3, bytes = HEAD_BYTES) {
|
|
10193
|
-
let fd;
|
|
10194
|
-
try {
|
|
10195
|
-
fd = openSync2(path3, "r");
|
|
10196
|
-
} catch {
|
|
10197
|
-
return null;
|
|
10198
|
-
}
|
|
10199
|
-
try {
|
|
10200
|
-
const buf = Buffer.alloc(bytes);
|
|
10201
|
-
const read = readSync(fd, buf, 0, bytes, 0);
|
|
10202
|
-
return buf.subarray(0, read).toString("utf-8");
|
|
10203
|
-
} catch {
|
|
10204
|
-
return null;
|
|
10205
|
-
} finally {
|
|
10206
|
-
closeSync2(fd);
|
|
10207
|
-
}
|
|
10208
|
-
}
|
|
10209
|
-
function cwdFromJsonlHead(text) {
|
|
10210
|
-
for (const line of text.split(`
|
|
10211
|
-
`).slice(0, HEAD_LINES)) {
|
|
10212
|
-
if (!line.includes('"cwd"'))
|
|
10213
|
-
continue;
|
|
10214
|
-
try {
|
|
10215
|
-
const obj = JSON.parse(line);
|
|
10216
|
-
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
10217
|
-
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
10218
|
-
return cwd;
|
|
10219
|
-
} catch {}
|
|
10220
|
-
}
|
|
10221
|
-
return null;
|
|
10222
|
-
}
|
|
10223
|
-
function unmungeSlug(slug, exists = existsSync11) {
|
|
10224
|
-
const tokens = slug.replace(/^-/, "").split("-");
|
|
10225
|
-
if (tokens.length === 0 || tokens[0] === "")
|
|
10226
|
-
return null;
|
|
10227
|
-
let budget = 5000;
|
|
10228
|
-
const walk = (prefix, index) => {
|
|
10229
|
-
if (budget-- <= 0)
|
|
10230
|
-
return null;
|
|
10231
|
-
if (index === tokens.length)
|
|
10232
|
-
return exists(prefix) ? prefix : null;
|
|
10233
|
-
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
10234
|
-
if (asSegment)
|
|
10235
|
-
return asSegment;
|
|
10236
|
-
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
10237
|
-
};
|
|
10238
|
-
return walk("", 0);
|
|
10239
|
-
}
|
|
10240
|
-
function fileMtime(path3) {
|
|
10241
|
-
try {
|
|
10242
|
-
return statSync4(path3).mtime.toISOString();
|
|
10243
|
-
} catch {
|
|
10244
|
-
return "";
|
|
10245
|
-
}
|
|
10246
|
-
}
|
|
10247
|
-
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
10248
|
-
const exists = deps.exists ?? existsSync11;
|
|
10249
|
-
const head = deps.readHead ?? readHead;
|
|
10250
|
-
const mtime = deps.mtime ?? fileMtime;
|
|
10251
|
-
const entries = loadCacheFile(configDir);
|
|
10252
|
-
let dirty = false;
|
|
10253
|
-
const compute = (session) => {
|
|
10254
|
-
switch (session.harness) {
|
|
10255
|
-
case "opencode":
|
|
10256
|
-
return session.project ?? null;
|
|
10257
|
-
case "claude": {
|
|
10258
|
-
const text = head(session.path);
|
|
10259
|
-
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
10260
|
-
if (cwd)
|
|
10261
|
-
return cwd;
|
|
10262
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10263
|
-
}
|
|
10264
|
-
case "codex": {
|
|
10265
|
-
const text = head(session.path);
|
|
10266
|
-
return text ? cwdFromJsonlHead(text) : null;
|
|
10267
|
-
}
|
|
10268
|
-
case "cursor":
|
|
10269
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10270
|
-
default:
|
|
10271
|
-
return null;
|
|
10272
|
-
}
|
|
10273
|
-
};
|
|
10274
|
-
return {
|
|
10275
|
-
resolve(session) {
|
|
10276
|
-
const key = `${session.harness}/${session.id}`;
|
|
10277
|
-
const cached = entries[key];
|
|
10278
|
-
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
10279
|
-
return cached.dir;
|
|
10280
|
-
}
|
|
10281
|
-
const dir = compute(session);
|
|
10282
|
-
entries[key] = { dir, mtime: mtime(session.path) };
|
|
10283
|
-
dirty = true;
|
|
10284
|
-
return dir;
|
|
10285
|
-
},
|
|
10286
|
-
flush() {
|
|
10287
|
-
if (!dirty)
|
|
10288
|
-
return;
|
|
10289
|
-
saveCacheFile(configDir, entries);
|
|
10290
|
-
dirty = false;
|
|
10291
|
-
}
|
|
10292
|
-
};
|
|
10293
|
-
}
|
|
10294
|
-
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
10295
|
-
var init_project_dir = __esm(() => {
|
|
10296
|
-
init_config();
|
|
10297
|
-
HEAD_BYTES = 128 * 1024;
|
|
10298
|
-
});
|
|
10299
|
-
|
|
10300
10349
|
// src/sync/backlog.ts
|
|
10301
10350
|
function listSessionBacklog() {
|
|
10302
10351
|
try {
|
|
@@ -10441,7 +10490,7 @@ function readLockFile(configDir) {
|
|
|
10441
10490
|
}
|
|
10442
10491
|
function recentSyncActivity(log3) {
|
|
10443
10492
|
return log3.split(`
|
|
10444
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[
|
|
10493
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[learner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
10445
10494
|
}
|
|
10446
10495
|
function getSyncStatus(deps = {}) {
|
|
10447
10496
|
const configDir = deps.configDir ?? getConfigDir();
|
|
@@ -10490,7 +10539,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10490
10539
|
const state = loadState();
|
|
10491
10540
|
if (options.quiet) {
|
|
10492
10541
|
if (state.paused) {
|
|
10493
|
-
logger.debug("sync", "skipping quiet sync:
|
|
10542
|
+
logger.debug("sync", "skipping quiet sync: studying is paused");
|
|
10494
10543
|
return { status: "skipped-paused", readySessions: 0, inFlightSessions: 0, sessions: [] };
|
|
10495
10544
|
}
|
|
10496
10545
|
const retryAt = backoffUntil(state);
|
|
@@ -10500,7 +10549,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10500
10549
|
}
|
|
10501
10550
|
} else if (state.paused) {
|
|
10502
10551
|
delete state.paused;
|
|
10503
|
-
logger.debug("sync", "manual sync resumes paused
|
|
10552
|
+
logger.debug("sync", "manual sync resumes paused studying");
|
|
10504
10553
|
}
|
|
10505
10554
|
let ready;
|
|
10506
10555
|
let open2;
|
|
@@ -10562,14 +10611,14 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10562
10611
|
baseline_mined: state.total_mined ?? 0
|
|
10563
10612
|
};
|
|
10564
10613
|
}
|
|
10565
|
-
const
|
|
10614
|
+
const worthStudying = deps.worthStudying ?? isWorthStudying;
|
|
10566
10615
|
const examined = [];
|
|
10567
10616
|
const batch = [];
|
|
10568
10617
|
let trivial = 0;
|
|
10569
10618
|
for (let i2 = ready.length - 1;i2 >= 0 && batch.length < MINE_BATCH_LIMIT; i2--) {
|
|
10570
10619
|
const candidate = ready[i2];
|
|
10571
10620
|
examined.push(candidate);
|
|
10572
|
-
if (
|
|
10621
|
+
if (worthStudying(candidate)) {
|
|
10573
10622
|
batch.push(candidate);
|
|
10574
10623
|
} else {
|
|
10575
10624
|
trivial += 1;
|
|
@@ -10586,26 +10635,26 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10586
10635
|
return {
|
|
10587
10636
|
status: "nothing-new",
|
|
10588
10637
|
...base,
|
|
10589
|
-
|
|
10638
|
+
studiedSessions: 0,
|
|
10590
10639
|
trivialSessions: trivial
|
|
10591
10640
|
};
|
|
10592
10641
|
}
|
|
10593
|
-
logger.debug("sync", `
|
|
10594
|
-
const
|
|
10595
|
-
switch (
|
|
10642
|
+
logger.debug("sync", `studying ${batch.length} of ${ready.length} ready sessions (${trivial} trivial skipped)`);
|
|
10643
|
+
const learner = await deps.mine(batch);
|
|
10644
|
+
switch (learner.outcome) {
|
|
10596
10645
|
case "completed": {
|
|
10597
10646
|
for (const s of batch) {
|
|
10598
|
-
logger.debug("sync", `
|
|
10647
|
+
logger.debug("sync", `studied session ${s.harness}/${s.id}`);
|
|
10599
10648
|
}
|
|
10600
|
-
const
|
|
10649
|
+
const studiedAt = now().toISOString();
|
|
10601
10650
|
const history = [
|
|
10602
10651
|
...state.mined_sessions ?? [],
|
|
10603
10652
|
...batch.map((s) => ({
|
|
10604
|
-
at:
|
|
10653
|
+
at: studiedAt,
|
|
10605
10654
|
session: `${s.harness}/${s.id}`,
|
|
10606
10655
|
...s.project ? { project: s.project } : {}
|
|
10607
10656
|
}))
|
|
10608
|
-
].slice(-
|
|
10657
|
+
].slice(-STUDIED_HISTORY_LIMIT);
|
|
10609
10658
|
const watermark = batchWatermark(examined);
|
|
10610
10659
|
const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
|
|
10611
10660
|
let batchTokens = 0;
|
|
@@ -10614,21 +10663,21 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10614
10663
|
saveState({
|
|
10615
10664
|
...state,
|
|
10616
10665
|
watermark,
|
|
10617
|
-
last_attempt_at:
|
|
10666
|
+
last_attempt_at: studiedAt,
|
|
10618
10667
|
consecutive_failures: 0,
|
|
10619
10668
|
mined_sessions: history,
|
|
10620
10669
|
total_mined: (state.total_mined ?? 0) + batch.length,
|
|
10621
|
-
total_notes: (state.total_notes ?? 0) +
|
|
10670
|
+
total_notes: (state.total_notes ?? 0) + learner.notesWritten,
|
|
10622
10671
|
total_learning_tokens: (state.total_learning_tokens ?? 0) + batchTokens,
|
|
10623
10672
|
last_refusal: undefined
|
|
10624
10673
|
});
|
|
10625
|
-
logger.debug("sync", `
|
|
10674
|
+
logger.debug("sync", `studied ${batch.length} sessions, ${learner.notesWritten} suggested pages; watermark → ${watermark}`);
|
|
10626
10675
|
return {
|
|
10627
|
-
status: "
|
|
10676
|
+
status: "studied",
|
|
10628
10677
|
...base,
|
|
10629
|
-
|
|
10678
|
+
studiedSessions: batch.length,
|
|
10630
10679
|
trivialSessions: trivial,
|
|
10631
|
-
|
|
10680
|
+
learner
|
|
10632
10681
|
};
|
|
10633
10682
|
}
|
|
10634
10683
|
case "consent_off":
|
|
@@ -10641,12 +10690,12 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10641
10690
|
consecutive_failures: 0,
|
|
10642
10691
|
last_refusal: {
|
|
10643
10692
|
at,
|
|
10644
|
-
outcome:
|
|
10645
|
-
message:
|
|
10693
|
+
outcome: learner.outcome,
|
|
10694
|
+
message: learner.message ?? "Studying unavailable right now."
|
|
10646
10695
|
}
|
|
10647
10696
|
});
|
|
10648
|
-
logger.debug("sync", `
|
|
10649
|
-
return { status: "skipped-gateway", ...base,
|
|
10697
|
+
logger.debug("sync", `studying skipped by gateway: ${learner.outcome}`);
|
|
10698
|
+
return { status: "skipped-gateway", ...base, studiedSessions: 0, learner };
|
|
10650
10699
|
}
|
|
10651
10700
|
default: {
|
|
10652
10701
|
saveState({
|
|
@@ -10654,13 +10703,13 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10654
10703
|
last_attempt_at: now().toISOString(),
|
|
10655
10704
|
consecutive_failures: state.consecutive_failures + 1
|
|
10656
10705
|
});
|
|
10657
|
-
logger.debug("sync", `
|
|
10706
|
+
logger.debug("sync", `studying failed: ${learner.outcome}; ${learner.message ?? ""}`);
|
|
10658
10707
|
return {
|
|
10659
10708
|
status: "mine-failed",
|
|
10660
10709
|
...base,
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
error:
|
|
10710
|
+
studiedSessions: 0,
|
|
10711
|
+
learner,
|
|
10712
|
+
error: learner.message
|
|
10664
10713
|
};
|
|
10665
10714
|
}
|
|
10666
10715
|
}
|
|
@@ -10806,12 +10855,12 @@ var init_backfill_run = __esm(() => {
|
|
|
10806
10855
|
init_backfill();
|
|
10807
10856
|
});
|
|
10808
10857
|
|
|
10809
|
-
// src/
|
|
10858
|
+
// src/learner/config-dir.ts
|
|
10810
10859
|
import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10811
10860
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
10812
10861
|
import { join as join13 } from "node:path";
|
|
10813
10862
|
function createRunConfigDir() {
|
|
10814
|
-
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-
|
|
10863
|
+
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-learner-"));
|
|
10815
10864
|
writeFileSync10(join13(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
|
|
10816
10865
|
return {
|
|
10817
10866
|
path: path3,
|
|
@@ -10822,7 +10871,7 @@ function createRunConfigDir() {
|
|
|
10822
10871
|
}
|
|
10823
10872
|
var init_config_dir = () => {};
|
|
10824
10873
|
|
|
10825
|
-
// src/
|
|
10874
|
+
// src/learner/conflicts.ts
|
|
10826
10875
|
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
|
|
10827
10876
|
function managedSettingsPaths(platform3 = process.platform) {
|
|
10828
10877
|
switch (platform3) {
|
|
@@ -10880,11 +10929,11 @@ var init_conflicts = __esm(() => {
|
|
|
10880
10929
|
CONFLICTING_ENV_PREFIXES = ["ANTHROPIC_", "CLAUDE_CODE_", "AWS_"];
|
|
10881
10930
|
});
|
|
10882
10931
|
|
|
10883
|
-
// src/
|
|
10932
|
+
// src/learner/env.ts
|
|
10884
10933
|
function isStripped(name) {
|
|
10885
10934
|
return STRIPPED_NAMES.has(name) || STRIPPED_PREFIXES.some((p2) => name.startsWith(p2));
|
|
10886
10935
|
}
|
|
10887
|
-
function
|
|
10936
|
+
function buildLearnerEnv(options) {
|
|
10888
10937
|
const base = options.baseEnv ?? process.env;
|
|
10889
10938
|
const env = {};
|
|
10890
10939
|
for (const [name, value] of Object.entries(base)) {
|
|
@@ -10914,7 +10963,7 @@ var init_env = __esm(() => {
|
|
|
10914
10963
|
STRIPPED_NAMES = new Set(["CLAUDE_CONFIG_DIR", "CLAUDECODE", "AWS_BEARER_TOKEN_BEDROCK"]);
|
|
10915
10964
|
});
|
|
10916
10965
|
|
|
10917
|
-
// src/
|
|
10966
|
+
// src/learner/executable.ts
|
|
10918
10967
|
import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
|
|
10919
10968
|
import { createRequire as createRequire3 } from "node:module";
|
|
10920
10969
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -10961,9 +11010,9 @@ function resolveClaudeExecutable(options = {}) {
|
|
|
10961
11010
|
}
|
|
10962
11011
|
var init_executable = () => {};
|
|
10963
11012
|
|
|
10964
|
-
// src/
|
|
10965
|
-
function
|
|
10966
|
-
return `You are Dosu's knowledge
|
|
11013
|
+
// src/learner/prompt.ts
|
|
11014
|
+
function buildLearnerSystemPrompt(coreRules) {
|
|
11015
|
+
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.
|
|
10967
11016
|
|
|
10968
11017
|
Available tools (the only tools you may use, by these exact names):
|
|
10969
11018
|
- mcp__sessions__list_sessions / mcp__sessions__read_session: the session transcripts in scope for this run.
|
|
@@ -10971,18 +11020,18 @@ Available tools (the only tools you may use, by these exact names):
|
|
|
10971
11020
|
|
|
10972
11021
|
${coreRules}`;
|
|
10973
11022
|
}
|
|
10974
|
-
function
|
|
11023
|
+
function buildLearnerPrompt(sessions) {
|
|
10975
11024
|
const list = sessions.map((s) => `- ${s.id} (${s.harness})`).join(`
|
|
10976
11025
|
`);
|
|
10977
11026
|
return `Mine the following ${sessions.length} coding-agent session(s) for durable knowledge:
|
|
10978
11027
|
|
|
10979
11028
|
${list}
|
|
10980
11029
|
|
|
10981
|
-
|
|
11030
|
+
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.`;
|
|
10982
11031
|
}
|
|
10983
11032
|
|
|
10984
|
-
// src/
|
|
10985
|
-
var
|
|
11033
|
+
// src/learner/prompt-core.ts
|
|
11034
|
+
var LEARNER_CORE_RULES = `Rules — non-negotiable:
|
|
10986
11035
|
1. Before writing anything, call read_knowledge with the candidate topic. If the same fact is
|
|
10987
11036
|
already recorded, skip it — never write a duplicate or near-duplicate. If the topic exists but
|
|
10988
11037
|
the session shows it has changed or been superseded, do write: a note stating the current state
|
|
@@ -11010,10 +11059,9 @@ useful.
|
|
|
11010
11059
|
6. Only pass repo/branch to write_knowledge when the session itself verifies them (an explicit
|
|
11011
11060
|
cwd, git remote, or branch mentioned in the transcript). Never infer or guess a repo. When not
|
|
11012
11061
|
verified, omit both.
|
|
11013
|
-
7.
|
|
11014
|
-
8. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
|
|
11062
|
+
7. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
|
|
11015
11063
|
long verbatim transcript spans. Summarize in your own words.
|
|
11016
|
-
|
|
11064
|
+
8. A trivial session with no real user query is normal: skip it silently.`;
|
|
11017
11065
|
|
|
11018
11066
|
// node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
11019
11067
|
var exports_sdk = {};
|
|
@@ -72764,7 +72812,7 @@ var init_redact = __esm(() => {
|
|
|
72764
72812
|
]);
|
|
72765
72813
|
});
|
|
72766
72814
|
|
|
72767
|
-
// src/
|
|
72815
|
+
// src/learner/tools.ts
|
|
72768
72816
|
function textResult(text) {
|
|
72769
72817
|
return { content: [{ type: "text", text }] };
|
|
72770
72818
|
}
|
|
@@ -72823,7 +72871,7 @@ function createSessionToolsServer(sessions) {
|
|
|
72823
72871
|
name: SESSIONS_SERVER_NAME,
|
|
72824
72872
|
version: "1.0.0",
|
|
72825
72873
|
tools: [
|
|
72826
|
-
O6e("list_sessions", "List the coding-agent sessions in scope for this
|
|
72874
|
+
O6e("list_sessions", "List the coding-agent sessions in scope for this study run.", {}, async () => textResult(formatSessionList(sessions))),
|
|
72827
72875
|
O6e("read_session", "Read the conversation turns of one in-scope session. Responses are " + "paginated; pass the returned offset to continue reading.", {
|
|
72828
72876
|
id: exports_external.string().describe("Session id from list_sessions"),
|
|
72829
72877
|
offset: exports_external.number().int().min(0).optional().describe("Turn index to start from")
|
|
@@ -72849,11 +72897,11 @@ var init_tools = __esm(() => {
|
|
|
72849
72897
|
init_redact();
|
|
72850
72898
|
});
|
|
72851
72899
|
|
|
72852
|
-
// src/
|
|
72900
|
+
// src/learner/runner.ts
|
|
72853
72901
|
var exports_runner = {};
|
|
72854
72902
|
__export(exports_runner, {
|
|
72855
72903
|
traceAgentMessage: () => traceAgentMessage,
|
|
72856
|
-
|
|
72904
|
+
runLearner: () => runLearner,
|
|
72857
72905
|
classifyGatewayError: () => classifyGatewayError
|
|
72858
72906
|
});
|
|
72859
72907
|
function classifyGatewayError(text) {
|
|
@@ -72875,12 +72923,12 @@ function traceAgentMessage(message) {
|
|
|
72875
72923
|
return;
|
|
72876
72924
|
for (const block2 of content) {
|
|
72877
72925
|
if (msg.type === "assistant" && block2.type === "text") {
|
|
72878
|
-
logger.debug("
|
|
72926
|
+
logger.debug("learner", `[agent] ${snippet(block2.text)}`);
|
|
72879
72927
|
} else if (msg.type === "assistant" && block2.type === "tool_use") {
|
|
72880
|
-
logger.debug("
|
|
72928
|
+
logger.debug("learner", `[agent] → ${block2.name} ${snippet(block2.input)}`);
|
|
72881
72929
|
} else if (msg.type === "user" && block2.type === "tool_result") {
|
|
72882
72930
|
const full = JSON.stringify(block2.content ?? "");
|
|
72883
|
-
logger.debug("
|
|
72931
|
+
logger.debug("learner", `[agent] ← result ${full.length} chars${block2.is_error ? " (error)" : ""}: ${snippet(block2.content).slice(0, 120)}`);
|
|
72884
72932
|
}
|
|
72885
72933
|
}
|
|
72886
72934
|
}
|
|
@@ -72893,7 +72941,7 @@ function allowedToolNames() {
|
|
|
72893
72941
|
`mcp__${KNOWLEDGE_SERVER_NAME}__finalize_session_knowledge`
|
|
72894
72942
|
]);
|
|
72895
72943
|
}
|
|
72896
|
-
async function
|
|
72944
|
+
async function runLearner(options) {
|
|
72897
72945
|
const conflicts = detectSettingsConflicts();
|
|
72898
72946
|
if (conflicts.length > 0) {
|
|
72899
72947
|
const detail = conflicts.map((c5) => `${c5.file} (${c5.keys.join(", ")})`).join("; ");
|
|
@@ -72901,7 +72949,7 @@ async function runMiner(options) {
|
|
|
72901
72949
|
outcome: "settings_conflict",
|
|
72902
72950
|
notesWritten: 0,
|
|
72903
72951
|
turns: 0,
|
|
72904
|
-
message: `Refusing to run: conflicting Claude Code settings would override the
|
|
72952
|
+
message: `Refusing to run: conflicting Claude Code settings would override the study run's auth (${detail})`
|
|
72905
72953
|
};
|
|
72906
72954
|
}
|
|
72907
72955
|
const gatewayURL = options.gatewayURL ?? getLlmGatewayURL();
|
|
@@ -72916,7 +72964,7 @@ async function runMiner(options) {
|
|
|
72916
72964
|
const { query } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
|
|
72917
72965
|
const claudeExecutable = resolveClaudeExecutable();
|
|
72918
72966
|
if (claudeExecutable) {
|
|
72919
|
-
logger.debug("
|
|
72967
|
+
logger.debug("learner", `using system Claude Code executable: ${claudeExecutable}`);
|
|
72920
72968
|
}
|
|
72921
72969
|
const configDir = createRunConfigDir();
|
|
72922
72970
|
const runID = options.runID ?? crypto.randomUUID();
|
|
@@ -72926,9 +72974,10 @@ async function runMiner(options) {
|
|
|
72926
72974
|
const abort = new AbortController;
|
|
72927
72975
|
const timer = setTimeout(() => abort.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
|
|
72928
72976
|
let notesWritten = 0;
|
|
72929
|
-
let
|
|
72977
|
+
let currentSession;
|
|
72978
|
+
const readsSinceWrite = new Set;
|
|
72930
72979
|
let turns = 0;
|
|
72931
|
-
const env =
|
|
72980
|
+
const env = buildLearnerEnv({
|
|
72932
72981
|
apiKey: options.apiKey,
|
|
72933
72982
|
gatewayURL,
|
|
72934
72983
|
configDir: configDir.path,
|
|
@@ -72939,9 +72988,9 @@ async function runMiner(options) {
|
|
|
72939
72988
|
});
|
|
72940
72989
|
try {
|
|
72941
72990
|
const run2 = query({
|
|
72942
|
-
prompt:
|
|
72991
|
+
prompt: buildLearnerPrompt(options.sessions),
|
|
72943
72992
|
options: {
|
|
72944
|
-
systemPrompt:
|
|
72993
|
+
systemPrompt: buildLearnerSystemPrompt(LEARNER_CORE_RULES),
|
|
72945
72994
|
env,
|
|
72946
72995
|
abortController: abort,
|
|
72947
72996
|
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
@@ -72957,21 +73006,25 @@ async function runMiner(options) {
|
|
|
72957
73006
|
headers: {
|
|
72958
73007
|
...mcpHeaders(options.apiKey),
|
|
72959
73008
|
"X-Dosu-Session-Id": runID,
|
|
72960
|
-
"X-Dosu-Client": `dosu-cli-
|
|
73009
|
+
"X-Dosu-Client": `dosu-cli-learner/${getVersionString()}`,
|
|
72961
73010
|
...sessionStartedAt ? { "X-Dosu-Session-Started-At": sessionStartedAt } : {}
|
|
72962
73011
|
}
|
|
72963
73012
|
}
|
|
72964
73013
|
},
|
|
72965
|
-
stderr: (data) => logger.debug("
|
|
73014
|
+
stderr: (data) => logger.debug("learner", `[sdk] ${data}`),
|
|
72966
73015
|
canUseTool: async (toolName, input2) => {
|
|
72967
73016
|
if (!allowed.has(toolName)) {
|
|
72968
73017
|
return {
|
|
72969
73018
|
behavior: "deny",
|
|
72970
|
-
message: `Tool ${toolName} is not permitted in
|
|
73019
|
+
message: `Tool ${toolName} is not permitted in study runs.`
|
|
72971
73020
|
};
|
|
72972
73021
|
}
|
|
72973
73022
|
if (toolName === `mcp__${SESSIONS_SERVER_NAME}__read_session`) {
|
|
72974
|
-
|
|
73023
|
+
const id2 = sessionIdFromReadInput(input2);
|
|
73024
|
+
if (id2) {
|
|
73025
|
+
currentSession = id2;
|
|
73026
|
+
readsSinceWrite.add(id2);
|
|
73027
|
+
}
|
|
72975
73028
|
}
|
|
72976
73029
|
if (toolName === `mcp__${KNOWLEDGE_SERVER_NAME}__write_knowledge`) {
|
|
72977
73030
|
if (notesWritten >= maxNotes) {
|
|
@@ -72980,10 +73033,20 @@ async function runMiner(options) {
|
|
|
72980
73033
|
message: `Note cap reached (${maxNotes} per run); stop writing and summarize.`
|
|
72981
73034
|
};
|
|
72982
73035
|
}
|
|
73036
|
+
if (readsSinceWrite.size > 1) {
|
|
73037
|
+
readsSinceWrite.clear();
|
|
73038
|
+
currentSession = undefined;
|
|
73039
|
+
return {
|
|
73040
|
+
behavior: "deny",
|
|
73041
|
+
message: "Write each session's notes right after reading THAT session, before reading the next. You have read multiple sessions without writing, so a note cannot be attributed to one. Re-read only the session this note is about, then write it."
|
|
73042
|
+
};
|
|
73043
|
+
}
|
|
72983
73044
|
notesWritten += 1;
|
|
73045
|
+
readsSinceWrite.clear();
|
|
73046
|
+
const { transcript_id: _authoredByModel, ...clean } = input2;
|
|
72984
73047
|
return {
|
|
72985
73048
|
behavior: "allow",
|
|
72986
|
-
updatedInput:
|
|
73049
|
+
updatedInput: currentSession ? { ...clean, transcript_id: currentSession } : clean
|
|
72987
73050
|
};
|
|
72988
73051
|
}
|
|
72989
73052
|
return { behavior: "allow", updatedInput: input2 };
|
|
@@ -73005,15 +73068,15 @@ async function runMiner(options) {
|
|
|
73005
73068
|
};
|
|
73006
73069
|
}
|
|
73007
73070
|
if (message.subtype !== "success" || message.is_error) {
|
|
73008
|
-
logger.debug("
|
|
73071
|
+
logger.debug("learner", `run ${runID} failed: ${text}`);
|
|
73009
73072
|
return {
|
|
73010
73073
|
outcome: "error",
|
|
73011
73074
|
notesWritten,
|
|
73012
73075
|
turns,
|
|
73013
|
-
message: "
|
|
73076
|
+
message: "Study run failed; see debug log for details."
|
|
73014
73077
|
};
|
|
73015
73078
|
}
|
|
73016
|
-
logger.debug("
|
|
73079
|
+
logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
|
|
73017
73080
|
return { outcome: "completed", notesWritten, turns, message: text };
|
|
73018
73081
|
}
|
|
73019
73082
|
}
|
|
@@ -73021,7 +73084,7 @@ async function runMiner(options) {
|
|
|
73021
73084
|
outcome: "error",
|
|
73022
73085
|
notesWritten,
|
|
73023
73086
|
turns,
|
|
73024
|
-
message: "
|
|
73087
|
+
message: "Study run ended without a result."
|
|
73025
73088
|
};
|
|
73026
73089
|
} catch (error61) {
|
|
73027
73090
|
const text = error61 instanceof Error ? error61.message : String(error61);
|
|
@@ -73029,12 +73092,12 @@ async function runMiner(options) {
|
|
|
73029
73092
|
if (gatewayError) {
|
|
73030
73093
|
return { outcome: gatewayError.outcome, notesWritten, turns, message: gatewayError.message };
|
|
73031
73094
|
}
|
|
73032
|
-
logger.debug("
|
|
73095
|
+
logger.debug("learner", `run ${runID} threw: ${text}`);
|
|
73033
73096
|
return {
|
|
73034
73097
|
outcome: "error",
|
|
73035
73098
|
notesWritten,
|
|
73036
73099
|
turns,
|
|
73037
|
-
message: abort.signal.aborted ? "
|
|
73100
|
+
message: abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details."
|
|
73038
73101
|
};
|
|
73039
73102
|
} finally {
|
|
73040
73103
|
clearTimeout(timer);
|
|
@@ -73504,7 +73567,7 @@ var init_settings = __esm(() => {
|
|
|
73504
73567
|
});
|
|
73505
73568
|
|
|
73506
73569
|
// src/telemetry/debug-id.ts
|
|
73507
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
73570
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "e98d2bf5-99f8-402d-6475-6e2164756e21";
|
|
73508
73571
|
|
|
73509
73572
|
// src/telemetry/telemetry.ts
|
|
73510
73573
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -73533,11 +73596,13 @@ function normalizeContext(context) {
|
|
|
73533
73596
|
const isAuthenticated2 = context?.isAuthenticated === true;
|
|
73534
73597
|
const id2 = safeRead(context?.user, "id");
|
|
73535
73598
|
const email3 = safeRead(context?.user, "email");
|
|
73536
|
-
const
|
|
73599
|
+
const orgId = safeRead(context, "orgId");
|
|
73600
|
+
const user = isAuthenticated2 && isUUID2(id2) ? { id: id2, ...validEmail(email3) ? { email: email3 } : {} } : undefined;
|
|
73537
73601
|
return {
|
|
73538
73602
|
mode: context?.mode === "oss" ? "oss" : "cloud",
|
|
73539
73603
|
isAuthenticated: isAuthenticated2,
|
|
73540
|
-
...user ? { user } : {}
|
|
73604
|
+
...user ? { user } : {},
|
|
73605
|
+
...user && isUUID2(orgId) ? { orgId } : {}
|
|
73541
73606
|
};
|
|
73542
73607
|
}
|
|
73543
73608
|
function normalizeExitCode(value, fallback = 1) {
|
|
@@ -73557,12 +73622,9 @@ function normalizeRuntime(runtime) {
|
|
|
73557
73622
|
isTty: runtime.isTty === true
|
|
73558
73623
|
};
|
|
73559
73624
|
}
|
|
73560
|
-
function
|
|
73625
|
+
function isUUID2(value) {
|
|
73561
73626
|
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
73562
73627
|
}
|
|
73563
|
-
function validUserId(value) {
|
|
73564
|
-
return validInstallId(value);
|
|
73565
|
-
}
|
|
73566
73628
|
function validEmail(value) {
|
|
73567
73629
|
return typeof value === "string" && value.length <= 254 && /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/.test(value);
|
|
73568
73630
|
}
|
|
@@ -73712,7 +73774,7 @@ function buildPostHogPayload(input2) {
|
|
|
73712
73774
|
const result = input2.result === "success" || input2.result === "validation_error" ? input2.result : "failure";
|
|
73713
73775
|
return {
|
|
73714
73776
|
api_key: input2.apiKey,
|
|
73715
|
-
distinct_id: context.user?.id ?? (
|
|
73777
|
+
distinct_id: context.user?.id ?? (isUUID2(input2.installId) ? input2.installId : FALLBACK_INSTALL_ID),
|
|
73716
73778
|
event: "cli_command_completed",
|
|
73717
73779
|
properties: {
|
|
73718
73780
|
$geoip_disable: true,
|
|
@@ -73730,6 +73792,10 @@ function buildPostHogPayload(input2) {
|
|
|
73730
73792
|
is_ci: runtime.isCi,
|
|
73731
73793
|
is_tty: runtime.isTty,
|
|
73732
73794
|
mode: context.mode,
|
|
73795
|
+
...context.orgId ? {
|
|
73796
|
+
org_id: context.orgId,
|
|
73797
|
+
$groups: { organization: context.orgId }
|
|
73798
|
+
} : {},
|
|
73733
73799
|
is_authenticated: context.isAuthenticated,
|
|
73734
73800
|
exit_code: normalizeExitCode(input2.exitCode),
|
|
73735
73801
|
...errorCode ? { error_code: errorCode } : {}
|
|
@@ -73844,7 +73910,12 @@ function buildSentryEnvelope(input2) {
|
|
|
73844
73910
|
level: "error",
|
|
73845
73911
|
release: `dosu-cli@${runtime.version}`,
|
|
73846
73912
|
tags: sentryTags(command, context, runtime, error61),
|
|
73847
|
-
...context.user ? {
|
|
73913
|
+
...context.user ? {
|
|
73914
|
+
user: {
|
|
73915
|
+
id: context.user.id,
|
|
73916
|
+
...context.user.email ? { email: context.user.email } : {}
|
|
73917
|
+
}
|
|
73918
|
+
} : {},
|
|
73848
73919
|
...mappedBundle ? {
|
|
73849
73920
|
debug_meta: {
|
|
73850
73921
|
images: [{ type: "sourcemap", code_file: BUNDLE_CODE_FILE, debug_id: debugId }]
|
|
@@ -74038,7 +74109,7 @@ function safeNow(now) {
|
|
|
74038
74109
|
function safeUuid(generate) {
|
|
74039
74110
|
try {
|
|
74040
74111
|
const value = generate();
|
|
74041
|
-
return
|
|
74112
|
+
return isUUID2(value) ? value : undefined;
|
|
74042
74113
|
} catch {
|
|
74043
74114
|
return;
|
|
74044
74115
|
}
|
|
@@ -74070,7 +74141,7 @@ function createCommandTelemetry(settings2, dependencies = {}) {
|
|
|
74070
74141
|
let installIdResolved = false;
|
|
74071
74142
|
let resolvedInstallId = settings2.install_id;
|
|
74072
74143
|
function installId() {
|
|
74073
|
-
if (
|
|
74144
|
+
if (isUUID2(resolvedInstallId))
|
|
74074
74145
|
return resolvedInstallId;
|
|
74075
74146
|
if (installIdResolved)
|
|
74076
74147
|
return;
|
|
@@ -74080,7 +74151,7 @@ function createCommandTelemetry(settings2, dependencies = {}) {
|
|
|
74080
74151
|
} catch {
|
|
74081
74152
|
resolvedInstallId = undefined;
|
|
74082
74153
|
}
|
|
74083
|
-
return
|
|
74154
|
+
return isUUID2(resolvedInstallId) ? resolvedInstallId : undefined;
|
|
74084
74155
|
}
|
|
74085
74156
|
function debugPayload(payload) {
|
|
74086
74157
|
try {
|
|
@@ -74299,16 +74370,6 @@ var init_follow = () => {};
|
|
|
74299
74370
|
|
|
74300
74371
|
// src/mcp/providers/base.ts
|
|
74301
74372
|
function createJSONProvider(opts) {
|
|
74302
|
-
const defaultBuildServer = (cfg) => ({
|
|
74303
|
-
type: "http",
|
|
74304
|
-
url: mcpURL(cfg.active_account.target.deployment_id),
|
|
74305
|
-
headers: mcpHeaders(cfg.active_account?.target?.api_key)
|
|
74306
|
-
});
|
|
74307
|
-
const defaultBuildOSSServer = (cfg) => ({
|
|
74308
|
-
type: "http",
|
|
74309
|
-
url: mcpBaseURL(),
|
|
74310
|
-
headers: mcpHeaders(cfg.active_account?.target?.api_key)
|
|
74311
|
-
});
|
|
74312
74373
|
const buildServer = opts.buildServer ?? defaultBuildServer;
|
|
74313
74374
|
return {
|
|
74314
74375
|
name: () => opts.providerName,
|
|
@@ -74320,8 +74381,10 @@ function createJSONProvider(opts) {
|
|
|
74320
74381
|
globalConfigPath: () => expandHome(opts.globalPath),
|
|
74321
74382
|
isConfigured: () => isJSONKeyConfigured(expandHome(opts.globalPath), opts.topKey),
|
|
74322
74383
|
install(cfg, global2) {
|
|
74323
|
-
|
|
74324
|
-
|
|
74384
|
+
const endpoint = {
|
|
74385
|
+
url: mcpEndpoint(cfg),
|
|
74386
|
+
headers: mcpHeaders(cfg.active_account?.target?.api_key)
|
|
74387
|
+
};
|
|
74325
74388
|
let configPath;
|
|
74326
74389
|
if (global2) {
|
|
74327
74390
|
configPath = expandHome(opts.globalPath);
|
|
@@ -74330,8 +74393,7 @@ function createJSONProvider(opts) {
|
|
|
74330
74393
|
} else {
|
|
74331
74394
|
throw new Error(`${opts.providerName} does not support local installation`);
|
|
74332
74395
|
}
|
|
74333
|
-
|
|
74334
|
-
installJSONServer(configPath, opts.topKey, serverBuilder(cfg));
|
|
74396
|
+
installJSONServer(configPath, opts.topKey, buildServer(endpoint, cfg));
|
|
74335
74397
|
},
|
|
74336
74398
|
remove(global2) {
|
|
74337
74399
|
let configPath;
|
|
@@ -74346,8 +74408,12 @@ function createJSONProvider(opts) {
|
|
|
74346
74408
|
}
|
|
74347
74409
|
};
|
|
74348
74410
|
}
|
|
74411
|
+
var defaultBuildServer = ({ url: url2, headers }) => ({
|
|
74412
|
+
type: "http",
|
|
74413
|
+
url: url2,
|
|
74414
|
+
headers
|
|
74415
|
+
});
|
|
74349
74416
|
var init_base = __esm(() => {
|
|
74350
|
-
init_config();
|
|
74351
74417
|
init_config_helpers();
|
|
74352
74418
|
init_detect();
|
|
74353
74419
|
});
|
|
@@ -74361,13 +74427,12 @@ var AntigravityProvider = () => createJSONProvider({
|
|
|
74361
74427
|
paths: ["~/.gemini"],
|
|
74362
74428
|
globalPath: "~/.gemini/antigravity/mcp_config.json",
|
|
74363
74429
|
topKey: "mcpServers",
|
|
74364
|
-
buildServer: (
|
|
74365
|
-
serverUrl:
|
|
74366
|
-
headers
|
|
74430
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74431
|
+
serverUrl: url2,
|
|
74432
|
+
headers
|
|
74367
74433
|
})
|
|
74368
74434
|
});
|
|
74369
74435
|
var init_antigravity = __esm(() => {
|
|
74370
|
-
init_config_helpers();
|
|
74371
74436
|
init_base();
|
|
74372
74437
|
});
|
|
74373
74438
|
|
|
@@ -74404,9 +74469,7 @@ var ClaudeDesktopProvider = () => ({
|
|
|
74404
74469
|
install(cfg, global2) {
|
|
74405
74470
|
if (!global2)
|
|
74406
74471
|
throw new Error("Claude Desktop does not support local installation");
|
|
74407
|
-
|
|
74408
|
-
throw new Error("deployment ID is required");
|
|
74409
|
-
const url2 = cfg.mode === MODE_OSS ? mcpBaseURL() : mcpURL(cfg.active_account.target.deployment_id);
|
|
74472
|
+
const url2 = mcpEndpoint(cfg);
|
|
74410
74473
|
const npx = findNpx();
|
|
74411
74474
|
const remote = mcpRemoteServer(url2, cfg.active_account?.target?.api_key);
|
|
74412
74475
|
installJSONServer(configPath(), "mcpServers", {
|
|
@@ -74422,7 +74485,6 @@ var ClaudeDesktopProvider = () => ({
|
|
|
74422
74485
|
}
|
|
74423
74486
|
});
|
|
74424
74487
|
var init_claude_desktop = __esm(() => {
|
|
74425
|
-
init_config();
|
|
74426
74488
|
init_config_helpers();
|
|
74427
74489
|
init_detect();
|
|
74428
74490
|
});
|
|
@@ -74437,15 +74499,14 @@ var extensionDir = () => join25(appSupportDir(), "Code", "User", "globalStorage"
|
|
|
74437
74499
|
paths: [extensionDir()],
|
|
74438
74500
|
globalPath: join25(extensionDir(), "settings", "cline_mcp_settings.json"),
|
|
74439
74501
|
topKey: "mcpServers",
|
|
74440
|
-
buildServer: (
|
|
74441
|
-
url:
|
|
74502
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74503
|
+
url: url2,
|
|
74442
74504
|
type: "streamableHttp",
|
|
74443
74505
|
disabled: false,
|
|
74444
|
-
headers
|
|
74506
|
+
headers
|
|
74445
74507
|
})
|
|
74446
74508
|
});
|
|
74447
74509
|
var init_cline = __esm(() => {
|
|
74448
|
-
init_config_helpers();
|
|
74449
74510
|
init_detect();
|
|
74450
74511
|
init_base();
|
|
74451
74512
|
});
|
|
@@ -74463,15 +74524,14 @@ var ClineCliProvider = () => createJSONProvider({
|
|
|
74463
74524
|
paths: [clineDir()],
|
|
74464
74525
|
globalPath: join26(clineDir(), "data", "settings", "cline_mcp_settings.json"),
|
|
74465
74526
|
topKey: "mcpServers",
|
|
74466
|
-
buildServer: (
|
|
74467
|
-
url:
|
|
74527
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74528
|
+
url: url2,
|
|
74468
74529
|
type: "streamableHttp",
|
|
74469
74530
|
disabled: false,
|
|
74470
|
-
headers
|
|
74531
|
+
headers
|
|
74471
74532
|
})
|
|
74472
74533
|
});
|
|
74473
74534
|
var init_cline_cli = __esm(() => {
|
|
74474
|
-
init_config_helpers();
|
|
74475
74535
|
init_detect();
|
|
74476
74536
|
init_base();
|
|
74477
74537
|
});
|
|
@@ -74495,13 +74555,6 @@ function readTOML(path3) {
|
|
|
74495
74555
|
function writeTOML(path3, content) {
|
|
74496
74556
|
writeSecureFile(path3, content);
|
|
74497
74557
|
}
|
|
74498
|
-
function mcpEndpoint(cfg) {
|
|
74499
|
-
if (cfg.mode === MODE_OSS)
|
|
74500
|
-
return mcpBaseURL();
|
|
74501
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74502
|
-
throw new Error("deployment ID is required");
|
|
74503
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74504
|
-
}
|
|
74505
74558
|
function tomlString(value) {
|
|
74506
74559
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
74507
74560
|
}
|
|
@@ -74584,13 +74637,6 @@ function globalPath() {
|
|
|
74584
74637
|
}
|
|
74585
74638
|
return expandHome("~/.copilot/mcp-config.json");
|
|
74586
74639
|
}
|
|
74587
|
-
function mcpEndpoint2(cfg) {
|
|
74588
|
-
if (cfg.mode === MODE_OSS)
|
|
74589
|
-
return mcpBaseURL();
|
|
74590
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74591
|
-
throw new Error("deployment ID is required");
|
|
74592
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74593
|
-
}
|
|
74594
74640
|
var CopilotProvider = () => ({
|
|
74595
74641
|
name: () => "GitHub Copilot CLI",
|
|
74596
74642
|
id: () => "copilot",
|
|
@@ -74601,7 +74647,7 @@ var CopilotProvider = () => ({
|
|
|
74601
74647
|
globalConfigPath: () => globalPath(),
|
|
74602
74648
|
isConfigured: () => isJSONKeyConfigured(globalPath(), "mcpServers"),
|
|
74603
74649
|
install(cfg, global2) {
|
|
74604
|
-
const url2 =
|
|
74650
|
+
const url2 = mcpEndpoint(cfg);
|
|
74605
74651
|
if (global2) {
|
|
74606
74652
|
const server = {
|
|
74607
74653
|
type: "http",
|
|
@@ -74630,7 +74676,6 @@ var CopilotProvider = () => ({
|
|
|
74630
74676
|
}
|
|
74631
74677
|
});
|
|
74632
74678
|
var init_copilot = __esm(() => {
|
|
74633
|
-
init_config();
|
|
74634
74679
|
init_config_helpers();
|
|
74635
74680
|
init_detect();
|
|
74636
74681
|
});
|
|
@@ -74645,14 +74690,13 @@ var CursorProvider = () => createJSONProvider({
|
|
|
74645
74690
|
paths: ["~/.cursor"],
|
|
74646
74691
|
globalPath: "~/.cursor/mcp.json",
|
|
74647
74692
|
topKey: "mcpServers",
|
|
74648
|
-
buildServer: (
|
|
74649
|
-
url:
|
|
74650
|
-
headers
|
|
74693
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74694
|
+
url: url2,
|
|
74695
|
+
headers
|
|
74651
74696
|
}),
|
|
74652
74697
|
localConfigPath: (cwd) => join29(cwd, ".cursor", "mcp.json")
|
|
74653
74698
|
});
|
|
74654
74699
|
var init_cursor = __esm(() => {
|
|
74655
|
-
init_config_helpers();
|
|
74656
74700
|
init_base();
|
|
74657
74701
|
});
|
|
74658
74702
|
|
|
@@ -74689,13 +74733,6 @@ var init_gemini = __esm(() => {
|
|
|
74689
74733
|
});
|
|
74690
74734
|
|
|
74691
74735
|
// src/mcp/providers/manual.ts
|
|
74692
|
-
function mcpEndpoint3(cfg) {
|
|
74693
|
-
if (cfg.mode === MODE_OSS)
|
|
74694
|
-
return mcpBaseURL();
|
|
74695
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74696
|
-
throw new Error("deployment ID is required");
|
|
74697
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74698
|
-
}
|
|
74699
74736
|
function maskSecret(secret) {
|
|
74700
74737
|
if (secret.length <= 8)
|
|
74701
74738
|
return "[hidden]";
|
|
@@ -74707,7 +74744,7 @@ var ManualProvider = () => ({
|
|
|
74707
74744
|
id: () => "manual",
|
|
74708
74745
|
supportsLocal: () => false,
|
|
74709
74746
|
install(cfg, _global, opts = {}) {
|
|
74710
|
-
const url2 =
|
|
74747
|
+
const url2 = mcpEndpoint(cfg);
|
|
74711
74748
|
const apiKey = mcpHeaders(cfg.active_account?.target?.api_key)["X-Dosu-API-Key"];
|
|
74712
74749
|
const headerValue = opts.showSecret ? apiKey : maskSecret(apiKey);
|
|
74713
74750
|
console.log("Use these details to configure the Dosu MCP server in your client:");
|
|
@@ -74727,7 +74764,6 @@ To remove the Dosu MCP server, manually delete the configuration from your clien
|
|
|
74727
74764
|
}
|
|
74728
74765
|
});
|
|
74729
74766
|
var init_manual = __esm(() => {
|
|
74730
|
-
init_config();
|
|
74731
74767
|
init_config_helpers();
|
|
74732
74768
|
});
|
|
74733
74769
|
|
|
@@ -74743,13 +74779,6 @@ function resolveGlobalConfigPath() {
|
|
|
74743
74779
|
return jsoncPath;
|
|
74744
74780
|
return jsonPath;
|
|
74745
74781
|
}
|
|
74746
|
-
function mcpEndpoint4(cfg) {
|
|
74747
|
-
if (cfg.mode === MODE_OSS)
|
|
74748
|
-
return mcpBaseURL();
|
|
74749
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74750
|
-
throw new Error("deployment ID is required");
|
|
74751
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74752
|
-
}
|
|
74753
74782
|
var MCPorterProvider = () => ({
|
|
74754
74783
|
name: () => "MCPorter",
|
|
74755
74784
|
id: () => "mcporter",
|
|
@@ -74763,7 +74792,7 @@ var MCPorterProvider = () => ({
|
|
|
74763
74792
|
const configPath2 = global2 ? resolveGlobalConfigPath() : join32(process.cwd(), "config", "mcporter.json");
|
|
74764
74793
|
const server = {
|
|
74765
74794
|
type: "http",
|
|
74766
|
-
url:
|
|
74795
|
+
url: mcpEndpoint(cfg),
|
|
74767
74796
|
headers: mcpHeaders(cfg.active_account.target.api_key)
|
|
74768
74797
|
};
|
|
74769
74798
|
installJSONServer(configPath2, "mcpServers", server);
|
|
@@ -74774,7 +74803,6 @@ var MCPorterProvider = () => ({
|
|
|
74774
74803
|
}
|
|
74775
74804
|
});
|
|
74776
74805
|
var init_mcporter = __esm(() => {
|
|
74777
|
-
init_config();
|
|
74778
74806
|
init_config_helpers();
|
|
74779
74807
|
init_detect();
|
|
74780
74808
|
});
|
|
@@ -74789,16 +74817,15 @@ var OpenCodeProvider = () => createJSONProvider({
|
|
|
74789
74817
|
paths: ["~/.config/opencode"],
|
|
74790
74818
|
globalPath: "~/.config/opencode/opencode.json",
|
|
74791
74819
|
topKey: "mcp",
|
|
74792
|
-
buildServer: (
|
|
74820
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74793
74821
|
type: "remote",
|
|
74794
|
-
url:
|
|
74822
|
+
url: url2,
|
|
74795
74823
|
enabled: true,
|
|
74796
|
-
headers
|
|
74824
|
+
headers
|
|
74797
74825
|
}),
|
|
74798
74826
|
localConfigPath: (cwd) => join33(cwd, "opencode.json")
|
|
74799
74827
|
});
|
|
74800
74828
|
var init_opencode = __esm(() => {
|
|
74801
|
-
init_config_helpers();
|
|
74802
74829
|
init_base();
|
|
74803
74830
|
});
|
|
74804
74831
|
|
|
@@ -74836,30 +74863,73 @@ var init_windsurf = __esm(() => {
|
|
|
74836
74863
|
});
|
|
74837
74864
|
|
|
74838
74865
|
// src/mcp/providers/zed.ts
|
|
74839
|
-
import {
|
|
74866
|
+
import { rmSync as rmSync5 } from "node:fs";
|
|
74867
|
+
import { homedir as homedir9, platform as platform3 } from "node:os";
|
|
74840
74868
|
import { join as join36 } from "node:path";
|
|
74841
74869
|
function zedConfigDir() {
|
|
74842
74870
|
const os3 = platform3();
|
|
74843
|
-
if (os3 === "
|
|
74871
|
+
if (os3 === "win32")
|
|
74844
74872
|
return join36(appSupportDir(), "Zed");
|
|
74873
|
+
if (os3 === "darwin")
|
|
74874
|
+
return join36(homedir9(), ".config", "zed");
|
|
74845
74875
|
return join36(appSupportDir(), "zed");
|
|
74846
74876
|
}
|
|
74847
|
-
|
|
74848
|
-
|
|
74849
|
-
|
|
74850
|
-
|
|
74851
|
-
|
|
74852
|
-
|
|
74853
|
-
|
|
74854
|
-
|
|
74855
|
-
|
|
74856
|
-
|
|
74857
|
-
|
|
74858
|
-
|
|
74859
|
-
|
|
74860
|
-
|
|
74861
|
-
|
|
74862
|
-
}
|
|
74877
|
+
function zedDataDir() {
|
|
74878
|
+
const os3 = platform3();
|
|
74879
|
+
if (os3 === "darwin")
|
|
74880
|
+
return join36(appSupportDir(), "Zed");
|
|
74881
|
+
if (os3 === "win32")
|
|
74882
|
+
return join36(process.env.LOCALAPPDATA ?? "", "Zed");
|
|
74883
|
+
return join36(process.env.XDG_DATA_HOME ?? join36(homedir9(), ".local", "share"), "zed");
|
|
74884
|
+
}
|
|
74885
|
+
function legacyDarwinSettingsPath() {
|
|
74886
|
+
return platform3() === "darwin" ? join36(appSupportDir(), "Zed", "settings.json") : null;
|
|
74887
|
+
}
|
|
74888
|
+
function removeLegacyDosuEntry(path3) {
|
|
74889
|
+
let cfg;
|
|
74890
|
+
try {
|
|
74891
|
+
cfg = readJSONConfig(path3);
|
|
74892
|
+
} catch {
|
|
74893
|
+
return;
|
|
74894
|
+
}
|
|
74895
|
+
const servers = cfg.context_servers;
|
|
74896
|
+
if (typeof servers !== "object" || servers === null || !("dosu" in servers))
|
|
74897
|
+
return;
|
|
74898
|
+
delete servers.dosu;
|
|
74899
|
+
const nothingElse = Object.keys(cfg).length === 1 && Object.keys(servers).length === 0;
|
|
74900
|
+
if (nothingElse)
|
|
74901
|
+
rmSync5(path3, { force: true });
|
|
74902
|
+
else
|
|
74903
|
+
saveJSONConfig(path3, cfg);
|
|
74904
|
+
}
|
|
74905
|
+
var ZedProvider = (legacyPath = legacyDarwinSettingsPath()) => {
|
|
74906
|
+
const provider = createJSONProvider({
|
|
74907
|
+
providerName: "Zed",
|
|
74908
|
+
providerID: "zed",
|
|
74909
|
+
local: true,
|
|
74910
|
+
priorityValue: 10,
|
|
74911
|
+
paths: [zedConfigDir(), zedDataDir()],
|
|
74912
|
+
globalPath: join36(zedConfigDir(), "settings.json"),
|
|
74913
|
+
topKey: "context_servers",
|
|
74914
|
+
buildServer: ({ url: url2, headers }) => ({ url: url2, headers }),
|
|
74915
|
+
localConfigPath: (cwd) => join36(cwd, ".zed", "settings.json")
|
|
74916
|
+
});
|
|
74917
|
+
if (!legacyPath)
|
|
74918
|
+
return provider;
|
|
74919
|
+
return {
|
|
74920
|
+
...provider,
|
|
74921
|
+
install(cfg, global2) {
|
|
74922
|
+
provider.install(cfg, global2);
|
|
74923
|
+
if (global2)
|
|
74924
|
+
removeLegacyDosuEntry(legacyPath);
|
|
74925
|
+
},
|
|
74926
|
+
remove(global2) {
|
|
74927
|
+
provider.remove(global2);
|
|
74928
|
+
if (global2)
|
|
74929
|
+
removeLegacyDosuEntry(legacyPath);
|
|
74930
|
+
}
|
|
74931
|
+
};
|
|
74932
|
+
};
|
|
74863
74933
|
var init_zed = __esm(() => {
|
|
74864
74934
|
init_config_helpers();
|
|
74865
74935
|
init_detect();
|
|
@@ -75073,13 +75143,13 @@ var init_update_check = __esm(() => {
|
|
|
75073
75143
|
|
|
75074
75144
|
// src/rules/installer.ts
|
|
75075
75145
|
import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync22, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
|
|
75076
|
-
import { homedir as
|
|
75146
|
+
import { homedir as homedir10 } from "node:os";
|
|
75077
75147
|
import { dirname as dirname9, join as join38 } from "node:path";
|
|
75078
75148
|
function claudeConfigDir() {
|
|
75079
|
-
return process.env.CLAUDE_CONFIG_DIR || join38(
|
|
75149
|
+
return process.env.CLAUDE_CONFIG_DIR || join38(homedir10(), ".claude");
|
|
75080
75150
|
}
|
|
75081
75151
|
function codexHome3() {
|
|
75082
|
-
return process.env.CODEX_HOME || join38(
|
|
75152
|
+
return process.env.CODEX_HOME || join38(homedir10(), ".codex");
|
|
75083
75153
|
}
|
|
75084
75154
|
function isRuleAgent(agent) {
|
|
75085
75155
|
return Object.hasOwn(RULE_TARGETS, agent);
|
|
@@ -75225,7 +75295,7 @@ var init_installer = __esm(() => {
|
|
|
75225
75295
|
},
|
|
75226
75296
|
cursor: {
|
|
75227
75297
|
kind: "file",
|
|
75228
|
-
path: () => join38(
|
|
75298
|
+
path: () => join38(homedir10(), ".cursor", "rules", "dosu.mdc"),
|
|
75229
75299
|
cursorFrontmatter: true
|
|
75230
75300
|
},
|
|
75231
75301
|
codex: {
|
|
@@ -75234,15 +75304,15 @@ var init_installer = __esm(() => {
|
|
|
75234
75304
|
},
|
|
75235
75305
|
opencode: {
|
|
75236
75306
|
kind: "section",
|
|
75237
|
-
path: () => join38(
|
|
75307
|
+
path: () => join38(homedir10(), ".config", "opencode", "AGENTS.md")
|
|
75238
75308
|
},
|
|
75239
75309
|
gemini: {
|
|
75240
75310
|
kind: "section",
|
|
75241
|
-
path: () => join38(
|
|
75311
|
+
path: () => join38(homedir10(), ".gemini", "GEMINI.md")
|
|
75242
75312
|
},
|
|
75243
75313
|
antigravity: {
|
|
75244
75314
|
kind: "section",
|
|
75245
|
-
path: () => join38(
|
|
75315
|
+
path: () => join38(homedir10(), ".gemini", "GEMINI.md")
|
|
75246
75316
|
}
|
|
75247
75317
|
};
|
|
75248
75318
|
});
|
|
@@ -75380,6 +75450,140 @@ var init_errors3 = __esm(() => {
|
|
|
75380
75450
|
// src/mcp/constants.ts
|
|
75381
75451
|
var MCP_PROVIDER_SLUG2 = "dosu_mcp";
|
|
75382
75452
|
|
|
75453
|
+
// src/sessions/session-title.ts
|
|
75454
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync24, renameSync as renameSync6, statSync as statSync9, writeFileSync as writeFileSync16 } from "node:fs";
|
|
75455
|
+
import { homedir as homedir11 } from "node:os";
|
|
75456
|
+
import { join as join40 } from "node:path";
|
|
75457
|
+
function cachePath2(configDir) {
|
|
75458
|
+
return join40(configDir, CACHE_FILENAME5);
|
|
75459
|
+
}
|
|
75460
|
+
function loadCacheFile2(configDir) {
|
|
75461
|
+
try {
|
|
75462
|
+
const raw = JSON.parse(readFileSync24(cachePath2(configDir), "utf-8"));
|
|
75463
|
+
if (raw.schema_version !== CACHE_SCHEMA_VERSION2 || typeof raw.entries !== "object")
|
|
75464
|
+
return {};
|
|
75465
|
+
return raw.entries;
|
|
75466
|
+
} catch {
|
|
75467
|
+
return {};
|
|
75468
|
+
}
|
|
75469
|
+
}
|
|
75470
|
+
function saveCacheFile2(configDir, entries) {
|
|
75471
|
+
try {
|
|
75472
|
+
if (!existsSync25(configDir))
|
|
75473
|
+
mkdirSync14(configDir, { recursive: true, mode: 448 });
|
|
75474
|
+
const path3 = cachePath2(configDir);
|
|
75475
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
75476
|
+
const file2 = { schema_version: CACHE_SCHEMA_VERSION2, entries };
|
|
75477
|
+
writeFileSync16(tmp, JSON.stringify(file2), { mode: 384 });
|
|
75478
|
+
renameSync6(tmp, path3);
|
|
75479
|
+
} catch {}
|
|
75480
|
+
}
|
|
75481
|
+
function extractUserQuery(text) {
|
|
75482
|
+
const tagged = text.match(/<user_query>([\s\S]*?)<\/user_query>/);
|
|
75483
|
+
if (tagged)
|
|
75484
|
+
return tagged[1];
|
|
75485
|
+
const named = text.match(/^\s*<[\w-]+\s[^>]*\bname="([^"]+)"/);
|
|
75486
|
+
if (named)
|
|
75487
|
+
return named[1];
|
|
75488
|
+
return text.replace(/^(\s*<([\w-]+)(?:\s[^>]*)?>[\s\S]*?<\/\2>\s*)+/, "");
|
|
75489
|
+
}
|
|
75490
|
+
function cleanSessionName(text) {
|
|
75491
|
+
const line = text.replace(/\s+/g, " ").trim();
|
|
75492
|
+
if (!line)
|
|
75493
|
+
return null;
|
|
75494
|
+
return line.length > SESSION_NAME_MAX ? `${line.slice(0, SESSION_NAME_MAX - 1)}…` : line;
|
|
75495
|
+
}
|
|
75496
|
+
function claudeSummaryFromHead(text) {
|
|
75497
|
+
for (const line of text.split(`
|
|
75498
|
+
`).slice(0, HEAD_LINES2)) {
|
|
75499
|
+
if (!line.includes('"summary"'))
|
|
75500
|
+
continue;
|
|
75501
|
+
try {
|
|
75502
|
+
const obj = JSON.parse(line);
|
|
75503
|
+
if (obj.type === "summary" && typeof obj.summary === "string") {
|
|
75504
|
+
return cleanSessionName(obj.summary);
|
|
75505
|
+
}
|
|
75506
|
+
} catch {}
|
|
75507
|
+
}
|
|
75508
|
+
return null;
|
|
75509
|
+
}
|
|
75510
|
+
function reconstructSession(harness, id2, slug, home = homedir11(), exists = existsSync25) {
|
|
75511
|
+
if (!slug)
|
|
75512
|
+
return null;
|
|
75513
|
+
const candidates = [];
|
|
75514
|
+
if (harness === "claude") {
|
|
75515
|
+
candidates.push(join40(home, ".claude", "projects", slug, `${id2}.jsonl`));
|
|
75516
|
+
} else if (harness === "cursor") {
|
|
75517
|
+
const project = join40(home, ".cursor", "projects", slug);
|
|
75518
|
+
candidates.push(join40(project, "agent-transcripts", id2, `${id2}.jsonl`), join40(project, "agent-transcripts", `${id2}.jsonl`));
|
|
75519
|
+
} else {
|
|
75520
|
+
return null;
|
|
75521
|
+
}
|
|
75522
|
+
for (const path3 of candidates) {
|
|
75523
|
+
if (exists(path3))
|
|
75524
|
+
return { id: id2, harness, path: path3, project: slug, updated: "" };
|
|
75525
|
+
}
|
|
75526
|
+
return null;
|
|
75527
|
+
}
|
|
75528
|
+
function readHead2(path3) {
|
|
75529
|
+
try {
|
|
75530
|
+
return readFileSync24(path3, "utf-8").slice(0, 128 * 1024);
|
|
75531
|
+
} catch {
|
|
75532
|
+
return null;
|
|
75533
|
+
}
|
|
75534
|
+
}
|
|
75535
|
+
function fileMtime2(path3) {
|
|
75536
|
+
try {
|
|
75537
|
+
return statSync9(path3).mtime.toISOString();
|
|
75538
|
+
} catch {
|
|
75539
|
+
return "";
|
|
75540
|
+
}
|
|
75541
|
+
}
|
|
75542
|
+
function createSessionTitleResolver(configDir = getConfigDir(), deps = {}) {
|
|
75543
|
+
const head = deps.readHead ?? readHead2;
|
|
75544
|
+
const turns = deps.readTurns ?? readSessionTurns;
|
|
75545
|
+
const mtime = deps.mtime ?? fileMtime2;
|
|
75546
|
+
const entries = loadCacheFile2(configDir);
|
|
75547
|
+
let dirty = false;
|
|
75548
|
+
const compute = (session) => {
|
|
75549
|
+
if (session.harness === "claude") {
|
|
75550
|
+
const text = head(session.path);
|
|
75551
|
+
const summary = text ? claudeSummaryFromHead(text) : null;
|
|
75552
|
+
if (summary)
|
|
75553
|
+
return summary;
|
|
75554
|
+
}
|
|
75555
|
+
const firstUser = turns(session).find((t9) => t9.role === "user");
|
|
75556
|
+
return firstUser ? cleanSessionName(extractUserQuery(firstUser.text)) : null;
|
|
75557
|
+
};
|
|
75558
|
+
return {
|
|
75559
|
+
cached(key) {
|
|
75560
|
+
return entries[key]?.title ?? null;
|
|
75561
|
+
},
|
|
75562
|
+
resolve(session) {
|
|
75563
|
+
const key = `${session.harness}/${session.id}`;
|
|
75564
|
+
const cached2 = entries[key];
|
|
75565
|
+
if (cached2 && (cached2.title !== null || cached2.mtime === mtime(session.path))) {
|
|
75566
|
+
return cached2.title;
|
|
75567
|
+
}
|
|
75568
|
+
const title = compute(session);
|
|
75569
|
+
entries[key] = { title, mtime: mtime(session.path) };
|
|
75570
|
+
dirty = true;
|
|
75571
|
+
return title;
|
|
75572
|
+
},
|
|
75573
|
+
flush() {
|
|
75574
|
+
if (!dirty)
|
|
75575
|
+
return;
|
|
75576
|
+
saveCacheFile2(configDir, entries);
|
|
75577
|
+
dirty = false;
|
|
75578
|
+
}
|
|
75579
|
+
};
|
|
75580
|
+
}
|
|
75581
|
+
var CACHE_FILENAME5 = "session-titles.json", CACHE_SCHEMA_VERSION2 = 3, HEAD_LINES2 = 50, SESSION_NAME_MAX = 80;
|
|
75582
|
+
var init_session_title = __esm(() => {
|
|
75583
|
+
init_config();
|
|
75584
|
+
init_read();
|
|
75585
|
+
});
|
|
75586
|
+
|
|
75383
75587
|
// src/tui/alt-screen.ts
|
|
75384
75588
|
function enterAltScreen(output2) {
|
|
75385
75589
|
depth += 1;
|
|
@@ -75403,7 +75607,8 @@ var init_alt_screen = __esm(() => {
|
|
|
75403
75607
|
});
|
|
75404
75608
|
|
|
75405
75609
|
// src/tui/activity-view.ts
|
|
75406
|
-
import { readFileSync as
|
|
75610
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25 } from "node:fs";
|
|
75611
|
+
import { basename as basename5 } from "node:path";
|
|
75407
75612
|
function reduceActivityViewKey(key) {
|
|
75408
75613
|
if (key === "q" || key === ESC7 || key === CTRL_C4)
|
|
75409
75614
|
return "back";
|
|
@@ -75417,6 +75622,10 @@ function reduceActivityViewKey(key) {
|
|
|
75417
75622
|
return "down";
|
|
75418
75623
|
if (key === "s")
|
|
75419
75624
|
return "sync";
|
|
75625
|
+
if (key === "f")
|
|
75626
|
+
return "full";
|
|
75627
|
+
if (key === "c")
|
|
75628
|
+
return "clear";
|
|
75420
75629
|
return "none";
|
|
75421
75630
|
}
|
|
75422
75631
|
function cycleTab(tab, delta = 1) {
|
|
@@ -75439,33 +75648,49 @@ function syncConfirmMode(status) {
|
|
|
75439
75648
|
return "resume";
|
|
75440
75649
|
return "start";
|
|
75441
75650
|
}
|
|
75442
|
-
function formatActivityLine(line, width) {
|
|
75651
|
+
function formatActivityLine(line, width, full = false) {
|
|
75443
75652
|
const compact = stripAnsiCodes(line).replace(/^\[(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})[^\]]*\]/, (_match, _date2, time3) => time3);
|
|
75444
|
-
if (compact.length <= width)
|
|
75653
|
+
if (full || compact.length <= width)
|
|
75445
75654
|
return compact;
|
|
75446
75655
|
return `${compact.slice(0, Math.max(0, width - 1))}…`;
|
|
75447
75656
|
}
|
|
75448
75657
|
function appendSyncActivity(buffer, chunk, max = ACTIVITY_VIEW_BUFFER_LINES) {
|
|
75449
75658
|
const fresh = chunk.split(`
|
|
75450
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[
|
|
75659
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[learner]")).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
75451
75660
|
return [...buffer, ...fresh].slice(-max);
|
|
75452
75661
|
}
|
|
75453
75662
|
function clip(text, max) {
|
|
75454
75663
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
75455
75664
|
}
|
|
75456
|
-
function
|
|
75665
|
+
function formatStudiedRow(record2, full = false, name) {
|
|
75457
75666
|
const match = record2.at.match(/^\d{4}-(\d{2}-\d{2})T(\d{2}:\d{2})/);
|
|
75458
75667
|
const stamp = match ? `${match[1]} ${match[2]}` : record2.at;
|
|
75459
75668
|
const slash = record2.session.indexOf("/");
|
|
75460
75669
|
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
75461
75670
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
75462
|
-
const
|
|
75463
|
-
|
|
75671
|
+
const rawProject = record2.project ?? "-";
|
|
75672
|
+
const project = full ? rawProject : clip(rawProject, 28);
|
|
75673
|
+
const label = full ? name ? `${name} · ${id2}` : id2 : clip(name ?? id2, 44);
|
|
75674
|
+
return `${harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75464
75675
|
}
|
|
75465
|
-
function formatQueuedRow(session) {
|
|
75676
|
+
function formatQueuedRow(session, full = false, name) {
|
|
75466
75677
|
const stamp = session.updated.replace("T", " ").slice(5, 16);
|
|
75467
|
-
const
|
|
75468
|
-
|
|
75678
|
+
const rawProject = session.project ?? "-";
|
|
75679
|
+
const project = full ? rawProject : clip(rawProject, 28);
|
|
75680
|
+
const label = full ? name ? `${name} · ${session.id}` : session.id : clip(name ?? session.id, 44);
|
|
75681
|
+
return `${session.harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75682
|
+
}
|
|
75683
|
+
function wrapRow(text, width, indent = " ") {
|
|
75684
|
+
if (text.length <= width)
|
|
75685
|
+
return [text];
|
|
75686
|
+
const out = [text.slice(0, width)];
|
|
75687
|
+
let rest = text.slice(width);
|
|
75688
|
+
const inner = Math.max(1, width - indent.length);
|
|
75689
|
+
while (rest.length > 0) {
|
|
75690
|
+
out.push(indent + rest.slice(0, inner));
|
|
75691
|
+
rest = rest.slice(inner);
|
|
75692
|
+
}
|
|
75693
|
+
return out;
|
|
75469
75694
|
}
|
|
75470
75695
|
function windowList(lines, scroll, height = ACTIVITY_VIEW_LIST_LINES) {
|
|
75471
75696
|
const clamped = Math.max(0, Math.min(scroll, Math.max(0, lines.length - height)));
|
|
@@ -75493,23 +75718,23 @@ function foldRunProgress(progress, chunk) {
|
|
|
75493
75718
|
let current = progress;
|
|
75494
75719
|
for (const line of chunk.split(`
|
|
75495
75720
|
`)) {
|
|
75496
|
-
const start = line.match(/\[sync\] mining (\d+) of \d+ ready sessions/);
|
|
75721
|
+
const start = line.match(/\[sync\] (?:studying|mining) (\d+) of \d+ ready sessions/);
|
|
75497
75722
|
if (start) {
|
|
75498
75723
|
current = { batch: Number.parseInt(start[1], 10), read: new Set, notes: 0 };
|
|
75499
75724
|
continue;
|
|
75500
75725
|
}
|
|
75501
|
-
if (/\[sync\] (mined \d+ sessions|mining failed|
|
|
75726
|
+
if (/\[sync\] (?:studied|mined) \d+ sessions|\[sync\] (?:studying|mining) (?:failed|skipped)/.test(line)) {
|
|
75502
75727
|
current = null;
|
|
75503
75728
|
continue;
|
|
75504
75729
|
}
|
|
75505
75730
|
if (!current)
|
|
75506
75731
|
continue;
|
|
75507
|
-
const read = line.match(/\[
|
|
75732
|
+
const read = line.match(/\[learner\] \[agent\] → mcp__sessions__read_session .*?"id":"([^"]+)"/);
|
|
75508
75733
|
if (read) {
|
|
75509
75734
|
current.read.add(read[1]);
|
|
75510
75735
|
continue;
|
|
75511
75736
|
}
|
|
75512
|
-
if (line.includes("[
|
|
75737
|
+
if (line.includes("[learner] [agent] → mcp__dosu__write_knowledge")) {
|
|
75513
75738
|
current.notes += 1;
|
|
75514
75739
|
}
|
|
75515
75740
|
}
|
|
@@ -75523,15 +75748,15 @@ function statusLine(status) {
|
|
|
75523
75748
|
if (status.running) {
|
|
75524
75749
|
const since = status.startedAt ? ` · since ${localTime(status.startedAt)}` : "";
|
|
75525
75750
|
const pid = status.pid !== undefined ? ` (pid ${status.pid})` : "";
|
|
75526
|
-
return
|
|
75751
|
+
return `\uD83D\uDCDA ${import_picocolors30.default.bold(brand("Studying sessions..."))}${import_picocolors30.default.dim(`${pid}${since}`)}`;
|
|
75527
75752
|
}
|
|
75528
75753
|
if (status.state.paused) {
|
|
75529
|
-
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("·
|
|
75754
|
+
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("· studying stays off until you resume")}`;
|
|
75530
75755
|
}
|
|
75531
75756
|
if (status.staleLock) {
|
|
75532
75757
|
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Not running")} ${import_picocolors30.default.dim("· a previous run exited without cleaning up")}`;
|
|
75533
75758
|
}
|
|
75534
|
-
return `${import_picocolors30.default.dim("○")} ${import_picocolors30.default.bold("Idle")} ${import_picocolors30.default.dim("· hooks
|
|
75759
|
+
return `${import_picocolors30.default.dim("○")} ${import_picocolors30.default.bold("Idle")} ${import_picocolors30.default.dim("· hooks study new sessions automatically")}`;
|
|
75535
75760
|
}
|
|
75536
75761
|
function progressLine(done, ready, width, notes = 0) {
|
|
75537
75762
|
const total = done + ready;
|
|
@@ -75543,14 +75768,14 @@ function progressLine(done, ready, width, notes = 0) {
|
|
|
75543
75768
|
const bar = brand("█".repeat(filled)) + import_picocolors30.default.dim("░".repeat(cells - filled));
|
|
75544
75769
|
const pct = Math.floor(ratio * 100);
|
|
75545
75770
|
const suffix = notes > 0 ? ` · ${notes} suggested page${notes === 1 ? "" : "s"}` : "";
|
|
75546
|
-
return `${bar} ${done}/${total}
|
|
75771
|
+
return `${bar} ${done}/${total} studied · ${pct}%${suffix}`;
|
|
75547
75772
|
}
|
|
75548
|
-
function tabBar(tab, queuedCount, openCount,
|
|
75773
|
+
function tabBar(tab, queuedCount, openCount, studiedCount, width) {
|
|
75549
75774
|
return tabStrip([
|
|
75550
|
-
["activity", "
|
|
75551
|
-
["
|
|
75552
|
-
["queued", `
|
|
75553
|
-
["open", `
|
|
75775
|
+
["activity", "activity"],
|
|
75776
|
+
["studied", `studied (${studiedCount})`],
|
|
75777
|
+
["queued", `queued (${queuedCount})`],
|
|
75778
|
+
["open", `open (${openCount})`]
|
|
75554
75779
|
], tab, width);
|
|
75555
75780
|
}
|
|
75556
75781
|
function activityWidth(columns) {
|
|
@@ -75572,10 +75797,11 @@ function wrapLine(text, width, indent = " ") {
|
|
|
75572
75797
|
return out;
|
|
75573
75798
|
}
|
|
75574
75799
|
function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
75575
|
-
const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} open
|
|
75576
|
-
const
|
|
75577
|
-
const
|
|
75578
|
-
const
|
|
75800
|
+
const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} still open; ${backlog.inFlight === 1 ? "it joins" : "they join"} once quiet)` : "";
|
|
75801
|
+
const sessions = `${queuedCount} session${queuedCount === 1 ? "" : "s"}`;
|
|
75802
|
+
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.`;
|
|
75803
|
+
const title = mode === "clear" ? "Clear study history?" : mode === "stop" ? "Stop studying?" : mode === "resume" ? "Resume studying?" : "Start studying now?";
|
|
75804
|
+
const verb = mode === "clear" ? "clear" : mode === "stop" ? "stop" : mode === "resume" ? "resume" : "start";
|
|
75579
75805
|
const maxInner = Math.max(20, Math.min(width, contentWidth()) - 4);
|
|
75580
75806
|
const rows = [
|
|
75581
75807
|
import_picocolors30.default.bold(brand(title)),
|
|
@@ -75591,64 +75817,143 @@ function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
|
75591
75817
|
import_picocolors30.default.dim(`╰${"─".repeat(inner + 2)}╯`)
|
|
75592
75818
|
];
|
|
75593
75819
|
}
|
|
75594
|
-
function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [],
|
|
75595
|
-
const
|
|
75820
|
+
function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [], studiedBeforeRun = 0, open5 = [], runProgress = null, projectNames = {}, sessionNames = {}) {
|
|
75821
|
+
const studied = status.state.watermark ? `Studied sessions up to ${localTime(status.state.watermark)}` : "Nothing studied yet";
|
|
75596
75822
|
const queueDetail = [];
|
|
75597
75823
|
if (status.backoffUntil) {
|
|
75598
75824
|
queueDetail.push(`retrying after ${localTime(status.backoffUntil)} · s syncs now`);
|
|
75599
75825
|
}
|
|
75600
75826
|
let progress = null;
|
|
75601
75827
|
if (status.running && backlog) {
|
|
75602
|
-
const
|
|
75828
|
+
const studiedDelta = Math.max(0, (status.state.total_mined ?? 0) - studiedBeforeRun);
|
|
75603
75829
|
const stepDone = runProgress ? Math.min(Math.max(0, runProgress.read.size - 1), runProgress.batch - 1, backlog.ready) : 0;
|
|
75604
|
-
progress = progressLine(
|
|
75830
|
+
progress = progressLine(studiedDelta + stepDone, Math.max(0, backlog.ready - stepDone), width, runProgress?.notes ?? 0);
|
|
75605
75831
|
}
|
|
75606
75832
|
const refusal = !status.running && status.state.last_refusal;
|
|
75607
|
-
const refusalLines = refusal ? wrapLine(`!
|
|
75608
|
-
const
|
|
75609
|
-
const
|
|
75610
|
-
const
|
|
75611
|
-
const
|
|
75612
|
-
|
|
75613
|
-
const
|
|
75614
|
-
|
|
75615
|
-
|
|
75833
|
+
const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors30.default.yellow(line)) : [];
|
|
75834
|
+
const fullRows = Boolean(pane.fullRows);
|
|
75835
|
+
const withName = (item, key) => projectNames[key] ? { ...item, project: projectNames[key] } : item;
|
|
75836
|
+
const latestPass = new Map;
|
|
75837
|
+
for (const r9 of status.state.mined_sessions ?? [])
|
|
75838
|
+
latestPass.set(r9.session, r9);
|
|
75839
|
+
const studiedRows = [...latestPass.values()].map((r9) => {
|
|
75840
|
+
const record2 = withName(r9, r9.session);
|
|
75841
|
+
const name = sessionNames[r9.session];
|
|
75842
|
+
return fullRows ? formatStudiedRow(record2, true, name) : formatActivityLine(formatStudiedRow(record2, false, name), width);
|
|
75843
|
+
});
|
|
75844
|
+
const sessionRow = (session) => {
|
|
75845
|
+
const key = `${session.harness}/${session.id}`;
|
|
75846
|
+
const s = withName(session, key);
|
|
75847
|
+
const name = sessionNames[key];
|
|
75848
|
+
return fullRows ? formatQueuedRow(s, true, name) : formatActivityLine(formatQueuedRow(s, false, name), width);
|
|
75849
|
+
};
|
|
75850
|
+
const queuedRows = queued.map(sessionRow);
|
|
75851
|
+
const openRows = open5.map(sessionRow);
|
|
75852
|
+
const source = pane.tab === "activity" ? activity.map((line) => formatActivityLine(line, width, fullRows)) : pane.tab === "queued" ? queuedRows : pane.tab === "open" ? openRows : studiedRows;
|
|
75853
|
+
const emptyStudied = status.state.watermark ? "No sessions recorded yet. History starts with the next study run." : "No studied sessions yet.";
|
|
75854
|
+
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;
|
|
75855
|
+
const height = fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75856
|
+
const { visible, above, below } = windowList(source, pane.scroll, height);
|
|
75857
|
+
const display = fullRows ? visible.flatMap((row) => wrapRow(row, width)) : visible;
|
|
75858
|
+
const listRows = display.length > 0 ? display.map((row) => import_picocolors30.default.dim(row)) : [import_picocolors30.default.dim(empty)];
|
|
75616
75859
|
const scrollParts = [];
|
|
75617
75860
|
if (above > 0)
|
|
75618
75861
|
scrollParts.push(`↑ ${above} earlier`);
|
|
75619
75862
|
if (below > 0)
|
|
75620
75863
|
scrollParts.push(`↓ ${below} newer`);
|
|
75621
|
-
if (pane.tab !== "activity" && visible.length > 0) {
|
|
75622
|
-
scrollParts.push(`full rows: dosu knowledge sessions --${pane.tab}`);
|
|
75623
|
-
}
|
|
75624
75864
|
const lines = [
|
|
75625
|
-
breadcrumb(["
|
|
75865
|
+
breadcrumb(["home", "activity"], width),
|
|
75626
75866
|
"",
|
|
75627
75867
|
statusLine(status),
|
|
75628
|
-
import_picocolors30.default.dim(
|
|
75868
|
+
import_picocolors30.default.dim(studied),
|
|
75629
75869
|
...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors30.default.dim(line)) : [],
|
|
75630
75870
|
...progress ? [progress] : [],
|
|
75631
75871
|
...refusalLines,
|
|
75632
75872
|
"",
|
|
75633
|
-
...tabBar(pane.tab, queued.length, open5.length,
|
|
75873
|
+
...tabBar(pane.tab, queued.length, open5.length, latestPass.size, width),
|
|
75634
75874
|
...listRows,
|
|
75635
|
-
...scrollParts.length > 0 ? [import_picocolors30.default.dim(scrollParts.join(" · "))] : [],
|
|
75875
|
+
...scrollParts.length > 0 ? [import_picocolors30.default.dim(clip(scrollParts.join(" · "), width))] : [],
|
|
75636
75876
|
"",
|
|
75637
75877
|
...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width, pane.confirm), width) : [
|
|
75638
|
-
|
|
75878
|
+
...wrapLine([
|
|
75639
75879
|
"tab switch",
|
|
75640
75880
|
"↑↓ scroll",
|
|
75881
|
+
fullRows ? "f clip" : "f full rows",
|
|
75641
75882
|
status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
|
|
75883
|
+
...!status.running && status.state.watermark ? ["c clear"] : [],
|
|
75642
75884
|
"esc back"
|
|
75643
|
-
].join(" · "))
|
|
75885
|
+
].join(" · "), width, "").map((line) => import_picocolors30.default.dim(line))
|
|
75644
75886
|
]
|
|
75645
75887
|
];
|
|
75646
75888
|
return lines.join(`
|
|
75647
75889
|
`);
|
|
75648
75890
|
}
|
|
75891
|
+
function createRowNamer() {
|
|
75892
|
+
const dirs = createProjectDirResolver();
|
|
75893
|
+
const titles = createSessionTitleResolver();
|
|
75894
|
+
const bySlug = new Map;
|
|
75895
|
+
const fromSlug = (slug) => {
|
|
75896
|
+
if (!bySlug.has(slug))
|
|
75897
|
+
bySlug.set(slug, unmungeSlug(slug));
|
|
75898
|
+
return bySlug.get(slug) ?? null;
|
|
75899
|
+
};
|
|
75900
|
+
return (status, backlog) => {
|
|
75901
|
+
const names = { projects: {}, titles: {} };
|
|
75902
|
+
let budget = TITLE_READS_PER_DRAW;
|
|
75903
|
+
let resolved = false;
|
|
75904
|
+
const title = (key, session) => {
|
|
75905
|
+
const hit = titles.cached(key);
|
|
75906
|
+
if (hit) {
|
|
75907
|
+
names.titles[key] = hit;
|
|
75908
|
+
return;
|
|
75909
|
+
}
|
|
75910
|
+
if (budget <= 0)
|
|
75911
|
+
return;
|
|
75912
|
+
budget--;
|
|
75913
|
+
const t9 = titles.resolve(session);
|
|
75914
|
+
resolved = true;
|
|
75915
|
+
if (t9)
|
|
75916
|
+
names.titles[key] = t9;
|
|
75917
|
+
};
|
|
75918
|
+
for (const s of [...backlog.queued, ...backlog.open]) {
|
|
75919
|
+
if (!existsSync26(s.path))
|
|
75920
|
+
continue;
|
|
75921
|
+
const key = `${s.harness}/${s.id}`;
|
|
75922
|
+
const dir = dirs.resolve(s);
|
|
75923
|
+
if (dir)
|
|
75924
|
+
names.projects[key] = basename5(dir);
|
|
75925
|
+
title(key, s);
|
|
75926
|
+
resolved = true;
|
|
75927
|
+
}
|
|
75928
|
+
for (const r9 of [...status.state.mined_sessions ?? []].reverse()) {
|
|
75929
|
+
const key = r9.session;
|
|
75930
|
+
if (!names.projects[key]) {
|
|
75931
|
+
const dir = dirs.cached(key) ?? (r9.project ? fromSlug(r9.project) : null);
|
|
75932
|
+
if (dir)
|
|
75933
|
+
names.projects[key] = basename5(dir);
|
|
75934
|
+
}
|
|
75935
|
+
if (!names.titles[key]) {
|
|
75936
|
+
const hit = titles.cached(key);
|
|
75937
|
+
if (hit) {
|
|
75938
|
+
names.titles[key] = hit;
|
|
75939
|
+
} else if (budget > 0) {
|
|
75940
|
+
const slash = key.indexOf("/");
|
|
75941
|
+
const session = reconstructSession(key.slice(0, slash), key.slice(slash + 1), r9.project);
|
|
75942
|
+
if (session)
|
|
75943
|
+
title(key, session);
|
|
75944
|
+
}
|
|
75945
|
+
}
|
|
75946
|
+
}
|
|
75947
|
+
if (resolved) {
|
|
75948
|
+
dirs.flush();
|
|
75949
|
+
titles.flush();
|
|
75950
|
+
}
|
|
75951
|
+
return names;
|
|
75952
|
+
};
|
|
75953
|
+
}
|
|
75649
75954
|
function defaultReadLog2() {
|
|
75650
75955
|
try {
|
|
75651
|
-
return
|
|
75956
|
+
return readFileSync25(logger.getLogPath(), "utf-8");
|
|
75652
75957
|
} catch {
|
|
75653
75958
|
return "";
|
|
75654
75959
|
}
|
|
@@ -75664,7 +75969,9 @@ function runActivityView(io2 = {}) {
|
|
|
75664
75969
|
const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--bootstrap"]));
|
|
75665
75970
|
const stopSync = io2.stopSync ?? stopSyncRun;
|
|
75666
75971
|
const setPaused = io2.setPaused ?? setSyncPaused;
|
|
75972
|
+
const clearHistory = io2.clearHistory ?? resetSyncState;
|
|
75667
75973
|
const listBacklog = io2.listBacklog ?? listSessionBacklog;
|
|
75974
|
+
const rowNamesFor = io2.rowNames ?? createRowNamer();
|
|
75668
75975
|
const pollMs = io2.pollMs ?? ACTIVITY_VIEW_POLL_MS;
|
|
75669
75976
|
const seed = readLog();
|
|
75670
75977
|
let activity = appendSyncActivity([], seed);
|
|
@@ -75672,6 +75979,7 @@ function runActivityView(io2 = {}) {
|
|
|
75672
75979
|
let runProgress = foldRunProgress(null, seed);
|
|
75673
75980
|
let tab = "activity";
|
|
75674
75981
|
let scroll = 0;
|
|
75982
|
+
let fullRows = false;
|
|
75675
75983
|
let confirmSync = null;
|
|
75676
75984
|
let status = getStatus();
|
|
75677
75985
|
let sessions = listBacklog();
|
|
@@ -75690,30 +75998,32 @@ function runActivityView(io2 = {}) {
|
|
|
75690
75998
|
return sessions.open.length;
|
|
75691
75999
|
return (status.state.mined_sessions ?? []).length;
|
|
75692
76000
|
};
|
|
76001
|
+
const listHeight = () => fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75693
76002
|
let lastFrame = null;
|
|
75694
|
-
let
|
|
76003
|
+
let studiedBeforeRun = null;
|
|
75695
76004
|
const draw = () => {
|
|
75696
76005
|
status = getStatus();
|
|
75697
76006
|
if (status.running) {
|
|
75698
76007
|
const run2 = status.state.run;
|
|
75699
76008
|
if (run2 && run2.pid === status.pid) {
|
|
75700
|
-
|
|
76009
|
+
studiedBeforeRun = run2.baseline_mined;
|
|
75701
76010
|
} else {
|
|
75702
|
-
|
|
76011
|
+
studiedBeforeRun ??= status.state.total_mined ?? 0;
|
|
75703
76012
|
}
|
|
75704
76013
|
} else {
|
|
75705
|
-
|
|
76014
|
+
studiedBeforeRun = null;
|
|
75706
76015
|
}
|
|
75707
76016
|
if (status.state.watermark !== queuedWatermark) {
|
|
75708
76017
|
queuedWatermark = status.state.watermark;
|
|
75709
76018
|
sessions = listBacklog();
|
|
75710
76019
|
}
|
|
75711
|
-
if (
|
|
76020
|
+
if (confirmSync !== null && confirmSync !== "stop" && status.running)
|
|
75712
76021
|
confirmSync = null;
|
|
75713
76022
|
if (confirmSync === "stop" && !status.running)
|
|
75714
76023
|
confirmSync = null;
|
|
75715
76024
|
const width = activityWidth(output2.columns ?? 80);
|
|
75716
|
-
const
|
|
76025
|
+
const rowNames = rowNamesFor(status, sessions);
|
|
76026
|
+
const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync ?? undefined, fullRows }, sessions.queued, studiedBeforeRun ?? 0, sessions.open, runProgress, rowNames.projects, rowNames.titles);
|
|
75717
76027
|
if (frame === lastFrame)
|
|
75718
76028
|
return;
|
|
75719
76029
|
lastFrame = frame;
|
|
@@ -75760,11 +76070,15 @@ ${CLEAR_BELOW}`);
|
|
|
75760
76070
|
const mode = confirmSync;
|
|
75761
76071
|
confirmSync = null;
|
|
75762
76072
|
let note;
|
|
75763
|
-
if (mode === "
|
|
76073
|
+
if (mode === "clear") {
|
|
76074
|
+
clearHistory();
|
|
76075
|
+
studiedBeforeRun = null;
|
|
76076
|
+
note = "[sync] study history cleared · the next run reads every local session again";
|
|
76077
|
+
} else if (mode === "stop") {
|
|
75764
76078
|
const ok2 = status.pid !== undefined && stopSync(status.pid);
|
|
75765
76079
|
if (ok2)
|
|
75766
76080
|
setPaused(true);
|
|
75767
|
-
note = ok2 ? "[sync]
|
|
76081
|
+
note = ok2 ? "[sync] studying stopped · paused until you resume" : "[sync] could not stop the run · it may have just finished";
|
|
75768
76082
|
} else {
|
|
75769
76083
|
if (mode === "resume")
|
|
75770
76084
|
setPaused(false);
|
|
@@ -75788,14 +76102,23 @@ ${CLEAR_BELOW}`);
|
|
|
75788
76102
|
if (action === "sync") {
|
|
75789
76103
|
confirmSync = syncConfirmMode(status);
|
|
75790
76104
|
draw();
|
|
76105
|
+
} else if (action === "clear") {
|
|
76106
|
+
if (!status.running && status.state.watermark) {
|
|
76107
|
+
confirmSync = "clear";
|
|
76108
|
+
draw();
|
|
76109
|
+
}
|
|
75791
76110
|
} else if (action === "tab" || action === "tab-back") {
|
|
75792
76111
|
tab = cycleTab(tab, action === "tab" ? 1 : -1);
|
|
75793
76112
|
scroll = 0;
|
|
75794
76113
|
if (tab === "queued" || tab === "open")
|
|
75795
76114
|
sessions = listBacklog();
|
|
75796
76115
|
draw();
|
|
76116
|
+
} else if (action === "full") {
|
|
76117
|
+
fullRows = !fullRows;
|
|
76118
|
+
scroll = 0;
|
|
76119
|
+
draw();
|
|
75797
76120
|
} else if (action === "up") {
|
|
75798
|
-
const maxScroll = Math.max(0, activeListLength() -
|
|
76121
|
+
const maxScroll = Math.max(0, activeListLength() - listHeight());
|
|
75799
76122
|
if (scroll < maxScroll) {
|
|
75800
76123
|
scroll += 1;
|
|
75801
76124
|
draw();
|
|
@@ -75811,10 +76134,12 @@ ${CLEAR_BELOW}`);
|
|
|
75811
76134
|
input2.on("data", onData);
|
|
75812
76135
|
});
|
|
75813
76136
|
}
|
|
75814
|
-
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_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE;
|
|
76137
|
+
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;
|
|
75815
76138
|
var init_activity_view = __esm(() => {
|
|
75816
76139
|
init_follow();
|
|
75817
76140
|
init_logger();
|
|
76141
|
+
init_project_dir();
|
|
76142
|
+
init_session_title();
|
|
75818
76143
|
init_styles();
|
|
75819
76144
|
init_backlog();
|
|
75820
76145
|
init_detach();
|
|
@@ -75836,7 +76161,7 @@ var init_activity_view = __esm(() => {
|
|
|
75836
76161
|
CURSOR_HOME = `${ESC7}[H`;
|
|
75837
76162
|
CLEAR_BELOW = `${ESC7}[0J`;
|
|
75838
76163
|
CLEAR_EOL = `${ESC7}[K`;
|
|
75839
|
-
ACTIVITY_VIEW_TABS = ["activity", "
|
|
76164
|
+
ACTIVITY_VIEW_TABS = ["activity", "studied", "queued", "open"];
|
|
75840
76165
|
DEFAULT_PANE = { tab: "activity", scroll: 0 };
|
|
75841
76166
|
});
|
|
75842
76167
|
|
|
@@ -75849,7 +76174,7 @@ async function trackCliOnboardingEvent(cfg, onboardingRunID, event, properties =
|
|
|
75849
76174
|
return;
|
|
75850
76175
|
if (!cfg.active_account?.session.access_token)
|
|
75851
76176
|
return;
|
|
75852
|
-
if (!
|
|
76177
|
+
if (!isUUID3(onboardingRunID))
|
|
75853
76178
|
return;
|
|
75854
76179
|
const input2 = {
|
|
75855
76180
|
event,
|
|
@@ -75872,7 +76197,7 @@ async function trackCliOnboardingPreAuthEvent(onboardingRunID, event, properties
|
|
|
75872
76197
|
return;
|
|
75873
76198
|
if (!analyticsReleaseEnabled())
|
|
75874
76199
|
return;
|
|
75875
|
-
if (!
|
|
76200
|
+
if (!isUUID3(onboardingRunID))
|
|
75876
76201
|
return;
|
|
75877
76202
|
const input2 = {
|
|
75878
76203
|
event,
|
|
@@ -75905,13 +76230,14 @@ function analyticsReleaseEnabled() {
|
|
|
75905
76230
|
return Boolean(parsePostHogProjectToken(process.env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? "phc_Uo9RxhN0HRk8gZkPlT18BCr7eqsM0MPWVW88HZB95V5"));
|
|
75906
76231
|
}
|
|
75907
76232
|
function baseProperties(cfg) {
|
|
76233
|
+
const orgId = cfg.active_account?.target?.org_id;
|
|
75908
76234
|
return {
|
|
75909
76235
|
cli_version: safeIdentifier(VERSION, 32),
|
|
75910
76236
|
install_channel: safeIdentifier(INSTALL_CHANNEL, 32),
|
|
75911
76237
|
platform: safeIdentifier(process.platform, 24),
|
|
75912
76238
|
arch: safeIdentifier(process.arch, 24),
|
|
75913
76239
|
mode: cfg.mode ?? "cloud",
|
|
75914
|
-
org_id:
|
|
76240
|
+
...orgId && isUUID3(orgId) ? { org_id: orgId, $groups: { organization: orgId } } : {},
|
|
75915
76241
|
deployment_id: safeIdentifier(cfg.active_account?.target?.deployment_id, 128),
|
|
75916
76242
|
space_id: safeIdentifier(cfg.active_account?.target?.space_id, 128)
|
|
75917
76243
|
};
|
|
@@ -75946,7 +76272,7 @@ function allowlistedWorkflowProperties(properties) {
|
|
|
75946
76272
|
function safeIdentifier(value, maxLength) {
|
|
75947
76273
|
return typeof value === "string" && value.length <= maxLength && /^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(value) ? value : undefined;
|
|
75948
76274
|
}
|
|
75949
|
-
function
|
|
76275
|
+
function isUUID3(value) {
|
|
75950
76276
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
75951
76277
|
}
|
|
75952
76278
|
function createAnalyticsClient(accessToken, requestSignal) {
|
|
@@ -76669,23 +76995,31 @@ async function stepOfferInitialSync(cfg) {
|
|
|
76669
76995
|
s.start("Checking for recent agent sessions...");
|
|
76670
76996
|
const outcome = await runKnowledgeSync({ bootstrap: true });
|
|
76671
76997
|
if (outcome.status !== "backlog" || outcome.readySessions === 0) {
|
|
76672
|
-
s.stop("No
|
|
76998
|
+
s.stop("No past agent sessions to study. Dosu will learn from new ones as you work.");
|
|
76673
76999
|
return;
|
|
76674
77000
|
}
|
|
76675
77001
|
const n5 = outcome.readySessions;
|
|
76676
|
-
|
|
77002
|
+
const them = n5 === 1 ? "it" : "them";
|
|
77003
|
+
s.stop(`Found ${n5} past agent session${n5 === 1 ? "" : "s"} on this machine that Dosu hasn't studied.`);
|
|
77004
|
+
log2.message([
|
|
77005
|
+
`Dosu can read ${them} and pull out the durable knowledge: decisions, gotchas, and`,
|
|
77006
|
+
"how-things-work that your team and agents would otherwise rediscover.",
|
|
77007
|
+
"Only distilled notes are saved to Dosu; your session logs stay on this machine.",
|
|
77008
|
+
"It runs in the background a few sessions at a time; notes appear in Dosu as each batch finishes."
|
|
77009
|
+
].join(`
|
|
77010
|
+
`));
|
|
76677
77011
|
const mineNow = await confirm2({
|
|
76678
|
-
message: `
|
|
76679
|
-
active: "
|
|
76680
|
-
inactive: "Skip",
|
|
77012
|
+
message: `Study ${them} now?`,
|
|
77013
|
+
active: "Study now \uD83D\uDCDA",
|
|
77014
|
+
inactive: "Skip for now",
|
|
76681
77015
|
initialValue: true
|
|
76682
77016
|
});
|
|
76683
77017
|
if (isCancel2(mineNow) || !mineNow) {
|
|
76684
|
-
log2.info(`Skipped. Dosu
|
|
77018
|
+
log2.info(`Skipped. Dosu studies new sessions in the background as you work; run ${info("dosu knowledge sync")} anytime to study these too.`);
|
|
76685
77019
|
return;
|
|
76686
77020
|
}
|
|
76687
77021
|
if (spawnDetachedSelf(["knowledge", "sync", "--quiet", "--bootstrap"])) {
|
|
76688
|
-
log2.success("
|
|
77022
|
+
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.`);
|
|
76689
77023
|
const watch = await confirm2({
|
|
76690
77024
|
message: "What next?",
|
|
76691
77025
|
active: "Watch it work",
|
|
@@ -77510,12 +77844,15 @@ function loadPageStatsFromConfig() {
|
|
|
77510
77844
|
}
|
|
77511
77845
|
}
|
|
77512
77846
|
function overviewRows(state) {
|
|
77513
|
-
const
|
|
77847
|
+
const studiedTotal = state.total_mined ?? 0;
|
|
77514
77848
|
const notes = state.total_notes ?? 0;
|
|
77515
77849
|
const tokens = state.total_learning_tokens ?? 0;
|
|
77516
|
-
if (
|
|
77850
|
+
if (studiedTotal === 0 && notes === 0)
|
|
77517
77851
|
return [];
|
|
77518
|
-
const rows = [
|
|
77852
|
+
const rows = [
|
|
77853
|
+
`${label("Sessions studied")}${studiedTotal}`,
|
|
77854
|
+
`${label("Suggested pages")}${notes}`
|
|
77855
|
+
];
|
|
77519
77856
|
if (tokens > 0) {
|
|
77520
77857
|
rows.push(`${label("Investigation distilled")}${formatTokenCount(tokens)} tokens`);
|
|
77521
77858
|
}
|
|
@@ -77564,7 +77901,7 @@ function analyticsTabRows(tab, state, pageStats) {
|
|
|
77564
77901
|
function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending = false, width = contentWidth()) {
|
|
77565
77902
|
const rows = analyticsTabRows(tab, state, pageStats);
|
|
77566
77903
|
const { visible, above, below } = windowReport(rows, scroll);
|
|
77567
|
-
const empty = tab === "overview" ? "No analytics yet. They appear after the first
|
|
77904
|
+
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.";
|
|
77568
77905
|
const listRows = visible.length > 0 ? visible.map((row) => import_picocolors32.default.dim(row)) : [import_picocolors32.default.dim(empty)];
|
|
77569
77906
|
const scrollParts = [];
|
|
77570
77907
|
if (above > 0)
|
|
@@ -77572,12 +77909,12 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
|
|
|
77572
77909
|
if (below > 0)
|
|
77573
77910
|
scrollParts.push(`↓ ${below} more`);
|
|
77574
77911
|
const lines = [
|
|
77575
|
-
breadcrumb(["
|
|
77912
|
+
breadcrumb(["home", "analytics"], width),
|
|
77576
77913
|
"",
|
|
77577
77914
|
...tabStrip([
|
|
77578
|
-
["overview", "
|
|
77579
|
-
["projects", "
|
|
77580
|
-
["pages", "
|
|
77915
|
+
["overview", "overview"],
|
|
77916
|
+
["projects", "projects"],
|
|
77917
|
+
["pages", "pages"]
|
|
77581
77918
|
], tab, width, { spread: false }),
|
|
77582
77919
|
"",
|
|
77583
77920
|
...listRows,
|
|
@@ -77716,7 +78053,7 @@ function checklistRows(ctx) {
|
|
|
77716
78053
|
const rows = [["workspace", ctx.directory]];
|
|
77717
78054
|
rows.push([
|
|
77718
78055
|
"account",
|
|
77719
|
-
ctx.signedIn ? `${on2} signed in` : `${off} ${import_picocolors33.default.dim(
|
|
78056
|
+
ctx.sessionExpired ? `${off} ${import_picocolors33.default.yellow("session expired")} ${import_picocolors33.default.dim(`${DOT3} run Log in`)}` : ctx.signedIn ? `${on2} signed in` : `${off} ${import_picocolors33.default.dim(`not signed in ${DOT3} run Setup`)}`
|
|
77720
78057
|
]);
|
|
77721
78058
|
const missing = new Set(ctx.setupMissing ?? []);
|
|
77722
78059
|
const warnRow = `${off} ${import_picocolors33.default.yellow("not configured")} ${import_picocolors33.default.dim(`${DOT3} run Setup`)}`;
|
|
@@ -77740,10 +78077,10 @@ function checklistRows(ctx) {
|
|
|
77740
78077
|
rows.push(["agents", `${on2} ${ctx.agents.join(` ${DOT3} `)}`]);
|
|
77741
78078
|
if (missing.has("hooks"))
|
|
77742
78079
|
rows.push(["hooks", warnRow]);
|
|
77743
|
-
if (ctx.
|
|
78080
|
+
if (ctx.studying) {
|
|
77744
78081
|
rows.push([
|
|
77745
78082
|
"sync",
|
|
77746
|
-
|
|
78083
|
+
`\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors33.default.dim(`${DOT3} see activity`)}`
|
|
77747
78084
|
]);
|
|
77748
78085
|
}
|
|
77749
78086
|
if (ctx.update) {
|
|
@@ -77920,7 +78257,7 @@ function renderPagesFrame(model, width, selected) {
|
|
|
77920
78257
|
}
|
|
77921
78258
|
const count = model.phase === "ready" ? ` ${import_picocolors34.default.dim(model.pages.length < model.total ? `(${model.pages.length} of ${model.total})` : `(${model.pages.length})`)}` : "";
|
|
77922
78259
|
const lines = [
|
|
77923
|
-
`${breadcrumb(["
|
|
78260
|
+
`${breadcrumb(["home", "pages"], width)}${count}`,
|
|
77924
78261
|
"",
|
|
77925
78262
|
...body,
|
|
77926
78263
|
...scrollHint ? [import_picocolors34.default.dim(scrollHint)] : [],
|
|
@@ -77950,7 +78287,7 @@ function renderPageFrame(page, content, width, scroll) {
|
|
|
77950
78287
|
}
|
|
77951
78288
|
const meta3 = `${page.type} · ${page.published ? "published" : "draft"} · updated ${page.updated_at.slice(0, 10)}`;
|
|
77952
78289
|
const lines = [
|
|
77953
|
-
breadcrumb(["
|
|
78290
|
+
breadcrumb(["home", "pages", page.title], width),
|
|
77954
78291
|
import_picocolors34.default.dim(clip3(meta3, width)),
|
|
77955
78292
|
"",
|
|
77956
78293
|
...body,
|
|
@@ -78133,9 +78470,19 @@ __export(exports_tui, {
|
|
|
78133
78470
|
runTUI: () => runTUI,
|
|
78134
78471
|
handleLogout: () => handleLogout
|
|
78135
78472
|
});
|
|
78136
|
-
import { homedir as
|
|
78137
|
-
import { basename as
|
|
78138
|
-
function
|
|
78473
|
+
import { homedir as homedir12 } from "node:os";
|
|
78474
|
+
import { basename as basename6 } from "node:path";
|
|
78475
|
+
async function probeSession(cfg) {
|
|
78476
|
+
if (!isAuthenticated(cfg) || !isTokenExpired(cfg))
|
|
78477
|
+
return { expired: false };
|
|
78478
|
+
try {
|
|
78479
|
+
await new Client(cfg).refreshToken();
|
|
78480
|
+
return { expired: false };
|
|
78481
|
+
} catch (err) {
|
|
78482
|
+
return { expired: err instanceof SessionExpiredError };
|
|
78483
|
+
}
|
|
78484
|
+
}
|
|
78485
|
+
function bannerContext(cfg, session) {
|
|
78139
78486
|
let webAppHost = "app.dosu.dev";
|
|
78140
78487
|
try {
|
|
78141
78488
|
webAppHost = new URL(getWebAppURL()).host;
|
|
@@ -78151,14 +78498,15 @@ function bannerContext(cfg) {
|
|
|
78151
78498
|
return {
|
|
78152
78499
|
version: getVersionString(),
|
|
78153
78500
|
webAppHost,
|
|
78154
|
-
directory:
|
|
78501
|
+
directory: basename6(process.cwd()),
|
|
78155
78502
|
signedIn: isAuthenticated(cfg),
|
|
78503
|
+
sessionExpired: session.expired,
|
|
78156
78504
|
deploymentName: cfg.active_account?.target?.deployment_name,
|
|
78157
78505
|
libraryName: cfg.active_account?.target?.library_name,
|
|
78158
|
-
setupMissing: isAuthenticated(cfg) ? missingSetupSteps(cfg) : [],
|
|
78506
|
+
setupMissing: isAuthenticated(cfg) && !session.expired ? missingSetupSteps(cfg) : [],
|
|
78159
78507
|
...inGitWorkTree() ? { repoAgentsMd: dosuAgentsSectionState() } : {},
|
|
78160
78508
|
agents,
|
|
78161
|
-
|
|
78509
|
+
studying: isStudying(),
|
|
78162
78510
|
...latest ? { update: { version: latest, hint: buildUpdateHint(INSTALL_CHANNEL, isNpxInvocation()) } } : {}
|
|
78163
78511
|
};
|
|
78164
78512
|
}
|
|
@@ -78225,24 +78573,25 @@ function setupHint(cfg) {
|
|
|
78225
78573
|
const started = Boolean(cfg.active_account?.target);
|
|
78226
78574
|
return import_picocolors35.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
|
|
78227
78575
|
}
|
|
78228
|
-
function
|
|
78576
|
+
function isStudying() {
|
|
78229
78577
|
try {
|
|
78230
78578
|
return getSyncStatus({ readLog: () => "" }).running;
|
|
78231
78579
|
} catch {
|
|
78232
78580
|
return false;
|
|
78233
78581
|
}
|
|
78234
78582
|
}
|
|
78235
|
-
function drawHome(cfg) {
|
|
78583
|
+
function drawHome(cfg, session) {
|
|
78236
78584
|
if (process.stdout.isTTY) {
|
|
78237
78585
|
process.stdout.write(CLEAR_SCREEN);
|
|
78238
78586
|
process.stdout.write(`
|
|
78239
78587
|
`.repeat(frameTopMargin()));
|
|
78240
78588
|
}
|
|
78241
|
-
process.stdout.write(`${renderBanner(bannerContext(cfg))}
|
|
78589
|
+
process.stdout.write(`${renderBanner(bannerContext(cfg, session))}
|
|
78242
78590
|
`);
|
|
78243
78591
|
}
|
|
78244
78592
|
async function runMainMenu() {
|
|
78245
78593
|
const cfg = loadConfig();
|
|
78594
|
+
const session = await probeSession(cfg);
|
|
78246
78595
|
const runSetupAndReload = async () => {
|
|
78247
78596
|
await runSetup();
|
|
78248
78597
|
const fresh = loadConfig();
|
|
@@ -78250,34 +78599,44 @@ async function runMainMenu() {
|
|
|
78250
78599
|
cfg.active_account = fresh.active_account;
|
|
78251
78600
|
};
|
|
78252
78601
|
const buildOptions = () => {
|
|
78602
|
+
if (session.expired) {
|
|
78603
|
+
return [
|
|
78604
|
+
{
|
|
78605
|
+
label: "log in again",
|
|
78606
|
+
hint: "(session expired · opens your browser)",
|
|
78607
|
+
value: "auth"
|
|
78608
|
+
},
|
|
78609
|
+
{ label: "exit", value: "exit" }
|
|
78610
|
+
];
|
|
78611
|
+
}
|
|
78253
78612
|
if (!isAuthenticated(cfg)) {
|
|
78254
78613
|
return [
|
|
78255
|
-
{ label: "
|
|
78256
|
-
{ label: "
|
|
78614
|
+
{ label: "log in / sign up", hint: "(opens your browser)", value: "auth" },
|
|
78615
|
+
{ label: "exit", value: "exit" }
|
|
78257
78616
|
];
|
|
78258
78617
|
}
|
|
78259
78618
|
if (!isSetUp(cfg)) {
|
|
78260
78619
|
return [
|
|
78261
|
-
{ label: "
|
|
78262
|
-
{ label: "
|
|
78620
|
+
{ label: "setup", hint: setupHint(cfg), value: "setup" },
|
|
78621
|
+
{ label: "exit", value: "exit" }
|
|
78263
78622
|
];
|
|
78264
78623
|
}
|
|
78265
|
-
const
|
|
78624
|
+
const studying = isStudying();
|
|
78266
78625
|
return [
|
|
78267
78626
|
{
|
|
78268
|
-
label:
|
|
78627
|
+
label: studying ? `activity \uD83D\uDCDA ${brand("studying sessions...")}` : "activity",
|
|
78269
78628
|
value: "sync"
|
|
78270
78629
|
},
|
|
78271
|
-
{ label: "
|
|
78272
|
-
{ label: "
|
|
78273
|
-
{ label: "
|
|
78274
|
-
{ label: "
|
|
78275
|
-
{ label: "
|
|
78630
|
+
{ label: "knowledge report", hint: "(opens in browser)", value: "report" },
|
|
78631
|
+
{ label: "analytics", value: "analytics" },
|
|
78632
|
+
{ label: "pages", value: "pages" },
|
|
78633
|
+
{ label: "settings", value: "settings" },
|
|
78634
|
+
{ label: "exit", value: "exit" }
|
|
78276
78635
|
];
|
|
78277
78636
|
};
|
|
78278
|
-
const home = () => drawHome(cfg);
|
|
78637
|
+
const home = () => drawHome(cfg, session);
|
|
78279
78638
|
home();
|
|
78280
|
-
if (isAuthenticated(cfg) && !isSetUp(cfg)) {
|
|
78639
|
+
if (isAuthenticated(cfg) && !session.expired && !isSetUp(cfg)) {
|
|
78281
78640
|
await runSetupAndReload();
|
|
78282
78641
|
home();
|
|
78283
78642
|
}
|
|
@@ -78311,6 +78670,7 @@ async function runMainMenu() {
|
|
|
78311
78670
|
break;
|
|
78312
78671
|
case "auth":
|
|
78313
78672
|
await handleAuthenticate(cfg);
|
|
78673
|
+
session.expired = session.expired && isTokenExpired(cfg);
|
|
78314
78674
|
home();
|
|
78315
78675
|
if (isAuthenticated(cfg) && !isSetUp(cfg)) {
|
|
78316
78676
|
await runSetupAndReload();
|
|
@@ -78340,19 +78700,19 @@ async function runSettings(cfg) {
|
|
|
78340
78700
|
const target = cfg.active_account?.target;
|
|
78341
78701
|
const library = target?.library_name ?? target?.deployment_name ?? "not configured";
|
|
78342
78702
|
const filter = loadSyncState().project_filter;
|
|
78343
|
-
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" :
|
|
78344
|
-
const action = await menuSelect("
|
|
78345
|
-
{ label: "
|
|
78346
|
-
{ label: "
|
|
78347
|
-
{ label: "
|
|
78348
|
-
{ label: "
|
|
78349
|
-
{ label: "
|
|
78350
|
-
{ label: "
|
|
78703
|
+
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" : basename6(dir)).join(", ") : `${filter.length} projects`;
|
|
78704
|
+
const action = await menuSelect("settings", [
|
|
78705
|
+
{ label: "switch organization", hint: target?.org_name, value: "switch-org" },
|
|
78706
|
+
{ label: "switch library", hint: library, value: "switch-library" },
|
|
78707
|
+
{ label: "study scope", hint: scope, value: "projects" },
|
|
78708
|
+
{ label: "run setup", hint: "rerun the setup wizard", value: "setup" },
|
|
78709
|
+
{ label: "log out", hint: "clear stored credentials", value: "logout" },
|
|
78710
|
+
{ label: "back", value: "back" }
|
|
78351
78711
|
]);
|
|
78352
78712
|
if (action === null || action === "back")
|
|
78353
78713
|
return;
|
|
78354
78714
|
if (action === "projects") {
|
|
78355
|
-
await
|
|
78715
|
+
await runStudyingProjectsSetting();
|
|
78356
78716
|
continue;
|
|
78357
78717
|
}
|
|
78358
78718
|
if (action === "setup") {
|
|
@@ -78375,7 +78735,7 @@ async function runSettings(cfg) {
|
|
|
78375
78735
|
}
|
|
78376
78736
|
}
|
|
78377
78737
|
function displayDir(dir) {
|
|
78378
|
-
const home =
|
|
78738
|
+
const home = homedir12();
|
|
78379
78739
|
return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
|
|
78380
78740
|
}
|
|
78381
78741
|
function discoverProjectDirs() {
|
|
@@ -78390,7 +78750,7 @@ function discoverProjectDirs() {
|
|
|
78390
78750
|
} catch {}
|
|
78391
78751
|
return [...counts.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([dir]) => dir);
|
|
78392
78752
|
}
|
|
78393
|
-
async function
|
|
78753
|
+
async function runStudyingProjectsSetting() {
|
|
78394
78754
|
const dirs = discoverProjectDirs();
|
|
78395
78755
|
if (dirs.length === 0) {
|
|
78396
78756
|
log2.info("No local agent sessions found yet; nothing to scope.");
|
|
@@ -78398,7 +78758,7 @@ async function runMiningProjectsSetting() {
|
|
|
78398
78758
|
}
|
|
78399
78759
|
const current = loadSyncState().project_filter;
|
|
78400
78760
|
const selected = await multiselect2({
|
|
78401
|
-
message: "
|
|
78761
|
+
message: "Study sessions from which folders?",
|
|
78402
78762
|
options: dirs.map((dir) => ({
|
|
78403
78763
|
label: dir === UNKNOWN_PROJECT ? "(unknown folder)" : displayDir(dir),
|
|
78404
78764
|
value: dir
|
|
@@ -78413,7 +78773,7 @@ async function runMiningProjectsSetting() {
|
|
|
78413
78773
|
const all = selected.length === dirs.length;
|
|
78414
78774
|
saveSyncState(all ? state : { ...state, project_filter: [...selected] });
|
|
78415
78775
|
const scope = all ? "all folders" : selected.map(displayDir).join(", ");
|
|
78416
|
-
log2.success(`
|
|
78776
|
+
log2.success(`Study scope ${dim(`· ${scope}`)}`);
|
|
78417
78777
|
}
|
|
78418
78778
|
async function handleAuthenticate(cfg) {
|
|
78419
78779
|
const shouldLogin = await confirm2({ message: "Open browser to log in?" });
|
|
@@ -78459,6 +78819,7 @@ function handleLogout(cfg) {
|
|
|
78459
78819
|
}
|
|
78460
78820
|
var import_picocolors35, ESC10, CLEAR_SCREEN;
|
|
78461
78821
|
var init_tui = __esm(() => {
|
|
78822
|
+
init_client();
|
|
78462
78823
|
init_config();
|
|
78463
78824
|
init_agents();
|
|
78464
78825
|
init_providers();
|
|
@@ -79116,7 +79477,7 @@ import {
|
|
|
79116
79477
|
constants as constants3,
|
|
79117
79478
|
fstatSync,
|
|
79118
79479
|
openSync as openSync5,
|
|
79119
|
-
readFileSync as
|
|
79480
|
+
readFileSync as readFileSync26,
|
|
79120
79481
|
readSync as readSync4,
|
|
79121
79482
|
unlinkSync as unlinkSync5
|
|
79122
79483
|
} from "node:fs";
|
|
@@ -82318,23 +82679,23 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82318
82679
|
console.log(` ID: ${store.id}`);
|
|
82319
82680
|
console.log(` Space ID: ${store.space_id}`);
|
|
82320
82681
|
});
|
|
82321
|
-
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
|
|
82322
|
-
const all = !opts.queued && !opts.open && !opts.
|
|
82682
|
+
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) => {
|
|
82683
|
+
const all = !opts.queued && !opts.open && !opts.studied;
|
|
82323
82684
|
const wantQueued = all || Boolean(opts.queued);
|
|
82324
82685
|
const wantOpen = all || Boolean(opts.open);
|
|
82325
|
-
const
|
|
82686
|
+
const wantStudied = all || Boolean(opts.studied);
|
|
82326
82687
|
const backlog = wantQueued || wantOpen ? listSessionBacklog() : { queued: [], open: [] };
|
|
82327
|
-
const
|
|
82688
|
+
const studied = wantStudied ? loadSyncState().mined_sessions ?? [] : [];
|
|
82328
82689
|
if (opts.json) {
|
|
82329
82690
|
printResult({
|
|
82330
82691
|
...wantQueued ? { queued: backlog.queued } : {},
|
|
82331
82692
|
...wantOpen ? { open: backlog.open } : {},
|
|
82332
|
-
...
|
|
82693
|
+
...wantStudied ? { studied } : {}
|
|
82333
82694
|
}, opts);
|
|
82334
82695
|
return;
|
|
82335
82696
|
}
|
|
82336
82697
|
const sessionRows = (sessions) => sessions.map((s) => [s.harness, s.updated, s.project ?? "-", s.id]);
|
|
82337
|
-
const
|
|
82698
|
+
const studiedRows = studied.map((record2) => {
|
|
82338
82699
|
const slash = record2.session.indexOf("/");
|
|
82339
82700
|
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
82340
82701
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
@@ -82358,11 +82719,11 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82358
82719
|
if (wantOpen) {
|
|
82359
82720
|
section("Open", sessionRows(backlog.open), "Updated", "No open sessions. Live agent sessions sit here until they go quiet.");
|
|
82360
82721
|
}
|
|
82361
|
-
if (
|
|
82362
|
-
section("
|
|
82722
|
+
if (wantStudied) {
|
|
82723
|
+
section("Studied", studiedRows, "Studied at", "No studied sessions recorded yet.");
|
|
82363
82724
|
}
|
|
82364
82725
|
});
|
|
82365
|
-
cmd.command("sync").description("Scan local agent session history and report the
|
|
82726
|
+
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) => {
|
|
82366
82727
|
if (opts.status) {
|
|
82367
82728
|
const status = getSyncStatus();
|
|
82368
82729
|
if (opts.json) {
|
|
@@ -82383,7 +82744,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82383
82744
|
]);
|
|
82384
82745
|
return;
|
|
82385
82746
|
}
|
|
82386
|
-
const deps = { mine:
|
|
82747
|
+
const deps = { mine: buildLearner(opts.quiet ? "hook" : "manual") };
|
|
82387
82748
|
let outcome = await runKnowledgeSync({
|
|
82388
82749
|
quiet: opts.quiet,
|
|
82389
82750
|
bootstrap: opts.bootstrap,
|
|
@@ -82391,7 +82752,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82391
82752
|
});
|
|
82392
82753
|
if (opts.bootstrap && deps.mine) {
|
|
82393
82754
|
const maxRounds = Math.ceil(outcome.readySessions / MINE_BATCH_LIMIT) + 2;
|
|
82394
|
-
for (let round = 1;outcome.status === "
|
|
82755
|
+
for (let round = 1;outcome.status === "studied" && round < maxRounds; round++) {
|
|
82395
82756
|
if (!opts.quiet && !opts.json)
|
|
82396
82757
|
printSyncOutcome(outcome);
|
|
82397
82758
|
outcome = await runKnowledgeSync({ quiet: opts.quiet, bootstrap: true, deps });
|
|
@@ -82448,12 +82809,12 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82448
82809
|
console.log("All your notes already have a transcript — nothing to backfill.");
|
|
82449
82810
|
return;
|
|
82450
82811
|
}
|
|
82451
|
-
console.log(`Attributed ${result.updated} of ${result.candidates} notes (${result.ambiguous} ambiguous, ${result.noBatch} without a local
|
|
82812
|
+
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.`);
|
|
82452
82813
|
});
|
|
82453
82814
|
cmd.addCommand(hooksCommand());
|
|
82454
82815
|
return cmd;
|
|
82455
82816
|
}
|
|
82456
|
-
function
|
|
82817
|
+
function buildLearner(trigger) {
|
|
82457
82818
|
const cfg = loadConfig();
|
|
82458
82819
|
if (cfg.mode === "oss")
|
|
82459
82820
|
return;
|
|
@@ -82462,8 +82823,8 @@ function buildMiner(trigger) {
|
|
|
82462
82823
|
return;
|
|
82463
82824
|
const { api_key, deployment_id } = target;
|
|
82464
82825
|
return async (sessions) => {
|
|
82465
|
-
const {
|
|
82466
|
-
return
|
|
82826
|
+
const { runLearner: runLearner2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
82827
|
+
return runLearner2({ sessions, apiKey: api_key, deploymentID: deployment_id, trigger });
|
|
82467
82828
|
};
|
|
82468
82829
|
}
|
|
82469
82830
|
function formatAge(iso, now) {
|
|
@@ -82489,14 +82850,14 @@ function printSyncStatus(status, now = new Date) {
|
|
|
82489
82850
|
console.log("○ No sync running.");
|
|
82490
82851
|
}
|
|
82491
82852
|
if (status.state.paused) {
|
|
82492
|
-
console.log(import_picocolors18.default.yellow("
|
|
82853
|
+
console.log(import_picocolors18.default.yellow(" Studying paused: stopped by you. Resume from the Activity screen or run 'dosu knowledge sync'."));
|
|
82493
82854
|
}
|
|
82494
82855
|
const wm2 = status.state.watermark;
|
|
82495
|
-
console.log(`
|
|
82856
|
+
console.log(` Studied through: ${wm2 ? `${wm2} (${formatAge(wm2, now)})` : "nothing studied yet"}`);
|
|
82496
82857
|
if (status.state.project_filter?.length) {
|
|
82497
82858
|
const home = homedir5();
|
|
82498
82859
|
const scope = status.state.project_filter.map((dir) => dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir).join(", ");
|
|
82499
|
-
console.log(`
|
|
82860
|
+
console.log(` Study scope: ${scope}`);
|
|
82500
82861
|
}
|
|
82501
82862
|
if ((status.state.total_notes ?? 0) > 0) {
|
|
82502
82863
|
const tokens = status.state.total_learning_tokens ?? 0;
|
|
@@ -82511,7 +82872,7 @@ function printSyncStatus(status, now = new Date) {
|
|
|
82511
82872
|
console.log(import_picocolors18.default.yellow(` Backing off after ${n5} failure${n5 === 1 ? "" : "s"}; background syncs retry after ${status.backoffUntil}.`));
|
|
82512
82873
|
}
|
|
82513
82874
|
if (status.state.last_refusal) {
|
|
82514
|
-
console.log(import_picocolors18.default.yellow(`
|
|
82875
|
+
console.log(import_picocolors18.default.yellow(` Studying paused: ${status.state.last_refusal.message} (${formatAge(status.state.last_refusal.at, now)})`));
|
|
82515
82876
|
}
|
|
82516
82877
|
if (status.recentActivity.length > 0) {
|
|
82517
82878
|
console.log(`
|
|
@@ -82527,25 +82888,25 @@ function printSyncOutcome(outcome) {
|
|
|
82527
82888
|
switch (outcome.status) {
|
|
82528
82889
|
case "backlog": {
|
|
82529
82890
|
const inFlight = outcome.inFlightSessions > 0 ? import_picocolors18.default.dim(` (${outcome.inFlightSessions} more still in progress)`) : "";
|
|
82530
|
-
console.log(`✓ Scanned. ${outcome.readySessions} new session${outcome.readySessions === 1 ? "" : "s"} ready to
|
|
82531
|
-
console.log(import_picocolors18.default.dim("Sign in with 'dosu setup' to enable
|
|
82891
|
+
console.log(`✓ Scanned. ${outcome.readySessions} new session${outcome.readySessions === 1 ? "" : "s"} ready to study${inFlight}.`);
|
|
82892
|
+
console.log(import_picocolors18.default.dim("Sign in with 'dosu setup' to enable studying."));
|
|
82532
82893
|
break;
|
|
82533
82894
|
}
|
|
82534
|
-
case "
|
|
82535
|
-
const notes = outcome.
|
|
82536
|
-
const remaining = outcome.readySessions - (outcome.
|
|
82537
|
-
console.log(`✓
|
|
82895
|
+
case "studied": {
|
|
82896
|
+
const notes = outcome.learner?.notesWritten ?? 0;
|
|
82897
|
+
const remaining = outcome.readySessions - (outcome.studiedSessions ?? 0);
|
|
82898
|
+
console.log(`✓ Studied ${outcome.studiedSessions} session${outcome.studiedSessions === 1 ? "" : "s"}, ${notes} suggested page${notes === 1 ? "" : "s"} created.`);
|
|
82538
82899
|
if (remaining > 0) {
|
|
82539
82900
|
console.log(import_picocolors18.default.dim(`${remaining} more in the backlog; run sync again to continue.`));
|
|
82540
82901
|
}
|
|
82541
82902
|
break;
|
|
82542
82903
|
}
|
|
82543
82904
|
case "skipped-gateway": {
|
|
82544
|
-
console.log(import_picocolors18.default.yellow(outcome.
|
|
82905
|
+
console.log(import_picocolors18.default.yellow(outcome.learner?.message ?? "Studying unavailable right now."));
|
|
82545
82906
|
break;
|
|
82546
82907
|
}
|
|
82547
82908
|
case "mine-failed": {
|
|
82548
|
-
console.error(import_picocolors18.default.red(outcome.
|
|
82909
|
+
console.error(import_picocolors18.default.red(outcome.learner?.message ?? "Study run failed."));
|
|
82549
82910
|
process.exitCode = 1;
|
|
82550
82911
|
break;
|
|
82551
82912
|
}
|
|
@@ -82568,7 +82929,7 @@ function printSyncOutcome(outcome) {
|
|
|
82568
82929
|
break;
|
|
82569
82930
|
}
|
|
82570
82931
|
case "skipped-paused": {
|
|
82571
|
-
console.log(import_picocolors18.default.dim("Skipped:
|
|
82932
|
+
console.log(import_picocolors18.default.dim("Skipped: studying is paused. Run 'dosu knowledge sync' to resume."));
|
|
82572
82933
|
break;
|
|
82573
82934
|
}
|
|
82574
82935
|
}
|
|
@@ -83320,7 +83681,10 @@ init_skill();
|
|
|
83320
83681
|
// src/commands/sources.ts
|
|
83321
83682
|
init_commander();
|
|
83322
83683
|
init_trpc();
|
|
83684
|
+
init_github_step();
|
|
83685
|
+
init_installation_server();
|
|
83323
83686
|
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
83687
|
+
var CONNECT_TIMEOUT_DEFAULT_SECONDS = 600;
|
|
83324
83688
|
function requireConfig11() {
|
|
83325
83689
|
const cfg = requireLoginConfig();
|
|
83326
83690
|
if (!cfg.active_account?.target?.org_id) {
|
|
@@ -83329,6 +83693,15 @@ function requireConfig11() {
|
|
|
83329
83693
|
}
|
|
83330
83694
|
return cfg;
|
|
83331
83695
|
}
|
|
83696
|
+
function rejectWebOnlyProvider(provider, json2) {
|
|
83697
|
+
const url2 = getWebAppURL();
|
|
83698
|
+
if (json2) {
|
|
83699
|
+
printResult({ provider, cli_supported: false, connect_via: "web", url: url2 }, { json: true });
|
|
83700
|
+
} else {
|
|
83701
|
+
console.error(import_picocolors25.default.yellow(`Connecting '${provider}' sources is web-only for now. Open ${url2}, add the source ` + `under Data Sources, then re-run 'dosu sources list'.`));
|
|
83702
|
+
}
|
|
83703
|
+
process.exit(1);
|
|
83704
|
+
}
|
|
83332
83705
|
function sourcesCommand() {
|
|
83333
83706
|
const cmd = new Command("sources").description("Manage connected data sources");
|
|
83334
83707
|
cmd.command("list").description("List all connected data sources").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -83411,6 +83784,154 @@ function sourcesCommand() {
|
|
|
83411
83784
|
}
|
|
83412
83785
|
console.log(import_picocolors25.default.green("Data source deleted."));
|
|
83413
83786
|
});
|
|
83787
|
+
cmd.command("connect").description("Install or update a provider integration (currently GitHub) via the browser").argument("[provider]", "Provider to connect (github)", "github").option("--json", "Emit machine-readable NDJSON progress events").option("--timeout <seconds>", "Seconds to wait for the browser install to complete", String(CONNECT_TIMEOUT_DEFAULT_SECONDS)).option("--no-open", "Print the URL without opening a browser").action(async (provider, opts) => {
|
|
83788
|
+
const providerName = provider.toLowerCase();
|
|
83789
|
+
if (providerName !== "github") {
|
|
83790
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83791
|
+
return;
|
|
83792
|
+
}
|
|
83793
|
+
const cfg = requireConfig11();
|
|
83794
|
+
const client = createTypedClient(cfg);
|
|
83795
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83796
|
+
const timeoutSeconds = Math.max(1, Number.parseInt(opts.timeout ?? "", 10)) || CONNECT_TIMEOUT_DEFAULT_SECONDS;
|
|
83797
|
+
const before = await fetchListForOrg(client, orgID);
|
|
83798
|
+
const { server, installationPromise } = await startInstallationCallbackServer();
|
|
83799
|
+
let timeoutId;
|
|
83800
|
+
try {
|
|
83801
|
+
const connectURL = new URL("/cli/connect-github", getWebAppURL());
|
|
83802
|
+
connectURL.searchParams.set("callback", `http://localhost:${server.port}/callback`);
|
|
83803
|
+
const url2 = connectURL.toString();
|
|
83804
|
+
if (opts.json) {
|
|
83805
|
+
console.log(JSON.stringify({
|
|
83806
|
+
event: "awaiting_install",
|
|
83807
|
+
provider: "github",
|
|
83808
|
+
url: url2,
|
|
83809
|
+
timeout_seconds: timeoutSeconds
|
|
83810
|
+
}));
|
|
83811
|
+
} else {
|
|
83812
|
+
console.log(`Open this URL to install or update the Dosu GitHub App:
|
|
83813
|
+
${import_picocolors25.default.cyan(url2)}`);
|
|
83814
|
+
if (opts.open !== false) {
|
|
83815
|
+
try {
|
|
83816
|
+
const open5 = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
83817
|
+
await open5.default(url2);
|
|
83818
|
+
} catch {}
|
|
83819
|
+
}
|
|
83820
|
+
console.log(import_picocolors25.default.dim("Waiting for the install to complete..."));
|
|
83821
|
+
}
|
|
83822
|
+
const timedOut = new Promise((resolve6) => {
|
|
83823
|
+
timeoutId = setTimeout(() => resolve6(null), timeoutSeconds * 1000);
|
|
83824
|
+
});
|
|
83825
|
+
const result = await Promise.race([installationPromise, timedOut]);
|
|
83826
|
+
if (result === null) {
|
|
83827
|
+
if (opts.json) {
|
|
83828
|
+
console.log(JSON.stringify({ event: "timeout", timeout_seconds: timeoutSeconds }));
|
|
83829
|
+
} else {
|
|
83830
|
+
console.error(import_picocolors25.default.red(`Timed out after ${timeoutSeconds}s. Finish the install in the browser, then re-run 'dosu sources connect github'.`));
|
|
83831
|
+
}
|
|
83832
|
+
process.exit(1);
|
|
83833
|
+
return;
|
|
83834
|
+
}
|
|
83835
|
+
const refresh = await waitForRepositoryRefresh(client, orgID, before);
|
|
83836
|
+
const beforeIds = new Set(before.map((repo) => repo.repository_id));
|
|
83837
|
+
const newRepositories = refresh.repos.filter((repo) => !beforeIds.has(repo.repository_id)).map((repo) => ({ slug: repo.slug, repository_id: repo.repository_id }));
|
|
83838
|
+
if (opts.json) {
|
|
83839
|
+
console.log(JSON.stringify({
|
|
83840
|
+
event: "installed",
|
|
83841
|
+
installation_id: result.installation_id,
|
|
83842
|
+
new_repositories: newRepositories
|
|
83843
|
+
}));
|
|
83844
|
+
return;
|
|
83845
|
+
}
|
|
83846
|
+
console.log(import_picocolors25.default.green(`GitHub App connected (installation ${result.installation_id}).`));
|
|
83847
|
+
if (newRepositories.length > 0) {
|
|
83848
|
+
for (const repo of newRepositories) {
|
|
83849
|
+
console.log(` ${repo.slug}`);
|
|
83850
|
+
}
|
|
83851
|
+
console.log(import_picocolors25.default.dim("Next: dosu sources create github --repo <owner/name> --library <library-id>"));
|
|
83852
|
+
} else {
|
|
83853
|
+
console.log(import_picocolors25.default.dim("No new repositories visible yet — GitHub may still be syncing. " + "Re-run 'dosu sources connect github' or check 'dosu sources list' shortly."));
|
|
83854
|
+
}
|
|
83855
|
+
} finally {
|
|
83856
|
+
clearTimeout(timeoutId);
|
|
83857
|
+
server.close();
|
|
83858
|
+
}
|
|
83859
|
+
});
|
|
83860
|
+
cmd.command("create").description("Create a data source from an installed provider and attach it to a library (requires confirmation)").argument("<provider>", "Provider (currently: github)").requiredOption("--repo <owner/name>", "GitHub repository slug visible to the Dosu app").option("--library <id>", "Library (space) ID to attach to; defaults to the active library").option("--confirm", "Apply without the interactive prompt").option("--json", "Output as JSON").action(async (provider, opts) => {
|
|
83861
|
+
const providerName = provider.toLowerCase();
|
|
83862
|
+
if (providerName !== "github") {
|
|
83863
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83864
|
+
return;
|
|
83865
|
+
}
|
|
83866
|
+
const cfg = requireConfig11();
|
|
83867
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83868
|
+
const libraryID = opts.library ?? cfg.active_account?.target?.space_id;
|
|
83869
|
+
if (!libraryID) {
|
|
83870
|
+
console.error(import_picocolors25.default.red("No library specified and no active library. Pass --library <id>."));
|
|
83871
|
+
process.exit(1);
|
|
83872
|
+
return;
|
|
83873
|
+
}
|
|
83874
|
+
const client = createTypedClient(cfg);
|
|
83875
|
+
const repos = await fetchListForOrg(client, orgID);
|
|
83876
|
+
const repo = repos.find((r9) => r9.slug.toLowerCase() === opts.repo.toLowerCase());
|
|
83877
|
+
if (!repo) {
|
|
83878
|
+
console.error(import_picocolors25.default.red(`Repository '${opts.repo}' is not visible to Dosu. Run ` + `'dosu sources connect github' to grant the GitHub App access, then retry.`));
|
|
83879
|
+
process.exit(1);
|
|
83880
|
+
return;
|
|
83881
|
+
}
|
|
83882
|
+
if (repo.is_fork === true) {
|
|
83883
|
+
console.error(import_picocolors25.default.red(`'${repo.slug}' is a fork and can't be synced.` + (repo.fork_parent_slug ? ` Connect '${repo.fork_parent_slug}' instead.` : "")));
|
|
83884
|
+
process.exit(1);
|
|
83885
|
+
return;
|
|
83886
|
+
}
|
|
83887
|
+
if (!await confirmAction({
|
|
83888
|
+
confirmed: opts.confirm,
|
|
83889
|
+
json: opts.json,
|
|
83890
|
+
message: `Create a data source for ${repo.slug} and attach it to library ${libraryID}?`,
|
|
83891
|
+
preview: {
|
|
83892
|
+
action: "create_source",
|
|
83893
|
+
provider: "github",
|
|
83894
|
+
repository: repo.slug,
|
|
83895
|
+
repository_id: repo.repository_id,
|
|
83896
|
+
library_id: libraryID
|
|
83897
|
+
}
|
|
83898
|
+
}))
|
|
83899
|
+
return;
|
|
83900
|
+
const existingDeployments = await fetchOrgGithubDeployments(client, orgID);
|
|
83901
|
+
const existingDataSources = await fetchOrgGithubDataSources(client, orgID);
|
|
83902
|
+
const created = await createDeploymentForRepo(client, orgID, libraryID, repo, {
|
|
83903
|
+
deploymentID: existingDeployments.get(repo.repository_id),
|
|
83904
|
+
dataSourceID: existingDataSources.get(repo.repository_id)
|
|
83905
|
+
});
|
|
83906
|
+
if (!created) {
|
|
83907
|
+
console.error(import_picocolors25.default.red("Could not create the data source. Check 'dosu logs --tail 50' for details."));
|
|
83908
|
+
process.exit(1);
|
|
83909
|
+
return;
|
|
83910
|
+
}
|
|
83911
|
+
const survivors = await verifyDataSourcesPersist(client, orgID, [created.data_source_id]);
|
|
83912
|
+
if (survivors.dropped.size > 0) {
|
|
83913
|
+
await deleteOrphanDeployment(client, created.deployment_id, repo.slug);
|
|
83914
|
+
console.error(import_picocolors25.default.red(`Dosu couldn't sync '${repo.slug}' (the GitHub App may not have access to it). ` + "Nothing was created."));
|
|
83915
|
+
process.exit(1);
|
|
83916
|
+
return;
|
|
83917
|
+
}
|
|
83918
|
+
const receipt = {
|
|
83919
|
+
provider: "github",
|
|
83920
|
+
repository: repo.slug,
|
|
83921
|
+
repository_id: repo.repository_id,
|
|
83922
|
+
data_source_id: created.data_source_id,
|
|
83923
|
+
deployment_id: created.deployment_id,
|
|
83924
|
+
library_id: libraryID,
|
|
83925
|
+
attached: true
|
|
83926
|
+
};
|
|
83927
|
+
if (opts.json) {
|
|
83928
|
+
printResult(receipt, opts);
|
|
83929
|
+
return;
|
|
83930
|
+
}
|
|
83931
|
+
console.log(import_picocolors25.default.green(`Connected ${repo.slug} to library ${libraryID}.`));
|
|
83932
|
+
console.log(import_picocolors25.default.dim(` data source ${created.data_source_id}`));
|
|
83933
|
+
console.log(import_picocolors25.default.dim(` deployment ${created.deployment_id}`));
|
|
83934
|
+
});
|
|
83414
83935
|
return cmd;
|
|
83415
83936
|
}
|
|
83416
83937
|
|
|
@@ -83924,10 +84445,12 @@ function commandTelemetryContext() {
|
|
|
83924
84445
|
const tokenUserID = getAccessTokenUserID(accessToken);
|
|
83925
84446
|
const userID = configUserID && configUserID === tokenUserID ? configUserID : undefined;
|
|
83926
84447
|
const email3 = userID ? getAccessTokenEmail(accessToken) : undefined;
|
|
84448
|
+
const orgId = userID ? cfg.active_account?.target?.org_id : undefined;
|
|
83927
84449
|
return {
|
|
83928
84450
|
mode: cfg.mode === MODE_OSS ? "oss" : "cloud",
|
|
83929
84451
|
isAuthenticated: authenticated,
|
|
83930
|
-
...userID ? { user: { id: userID, ...email3 ? { email: email3 } : {} } } : {}
|
|
84452
|
+
...userID ? { user: { id: userID, ...email3 ? { email: email3 } : {} } } : {},
|
|
84453
|
+
...orgId ? { orgId } : {}
|
|
83931
84454
|
};
|
|
83932
84455
|
} catch {
|
|
83933
84456
|
return { mode: "cloud", isAuthenticated: false };
|
|
@@ -84326,7 +84849,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84326
84849
|
}
|
|
84327
84850
|
if (opts.follow) {
|
|
84328
84851
|
try {
|
|
84329
|
-
const lines =
|
|
84852
|
+
const lines = readFileSync26(logPath, "utf-8").split(`
|
|
84330
84853
|
`);
|
|
84331
84854
|
if (lines.at(-1) === "")
|
|
84332
84855
|
lines.pop();
|
|
@@ -84342,7 +84865,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84342
84865
|
if (opts.tail !== undefined) {
|
|
84343
84866
|
const n5 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
|
|
84344
84867
|
try {
|
|
84345
|
-
const content =
|
|
84868
|
+
const content = readFileSync26(logPath, "utf-8");
|
|
84346
84869
|
const lines = content.split(`
|
|
84347
84870
|
`);
|
|
84348
84871
|
console.log(lines.slice(-n5).join(`
|
|
@@ -84413,4 +84936,4 @@ execute().catch((err) => {
|
|
|
84413
84936
|
process.exit(1);
|
|
84414
84937
|
});
|
|
84415
84938
|
|
|
84416
|
-
//# debugId=
|
|
84939
|
+
//# debugId=e98d2bf5-99f8-402d-6475-6e2164756e21
|