@dosu/cli 0.56.0 → 0.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dosu.js +258 -67
- package/package.json +1 -1
package/bin/dosu.js
CHANGED
|
@@ -1050,7 +1050,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
|
|
|
1050
1050
|
function getVersionString() {
|
|
1051
1051
|
return `v${VERSION}`;
|
|
1052
1052
|
}
|
|
1053
|
-
var VERSION = "0.56.
|
|
1053
|
+
var VERSION = "0.56.1", INSTALL_CHANNEL = "npm";
|
|
1054
1054
|
|
|
1055
1055
|
// src/debug/logger.ts
|
|
1056
1056
|
import {
|
|
@@ -1902,6 +1902,7 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
1902
1902
|
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) => ({
|
|
1903
1903
|
at: record.at,
|
|
1904
1904
|
session: record.session,
|
|
1905
|
+
...typeof record.updated === "string" ? { updated: record.updated } : {},
|
|
1905
1906
|
...typeof record.project === "string" ? { project: record.project } : {}
|
|
1906
1907
|
})) : [];
|
|
1907
1908
|
const rawRefusal = raw.last_refusal;
|
|
@@ -13429,6 +13430,27 @@ function batchWatermark(batch) {
|
|
|
13429
13430
|
}
|
|
13430
13431
|
return newest;
|
|
13431
13432
|
}
|
|
13433
|
+
function sessionKey(session) {
|
|
13434
|
+
return `${session.harness}/${session.id}`;
|
|
13435
|
+
}
|
|
13436
|
+
function studiedRecords(sessions, at) {
|
|
13437
|
+
return sessions.map((s) => ({
|
|
13438
|
+
at,
|
|
13439
|
+
session: sessionKey(s),
|
|
13440
|
+
updated: s.updated,
|
|
13441
|
+
...s.project ? { project: s.project } : {}
|
|
13442
|
+
}));
|
|
13443
|
+
}
|
|
13444
|
+
function lastStudiedSnapshot(history) {
|
|
13445
|
+
const latest = new Map;
|
|
13446
|
+
for (const record of history ?? []) {
|
|
13447
|
+
const updated = record.updated ? Date.parse(record.updated) : Number.NaN;
|
|
13448
|
+
if (!Number.isNaN(updated) && updated > (latest.get(record.session) ?? Number.NEGATIVE_INFINITY)) {
|
|
13449
|
+
latest.set(record.session, updated);
|
|
13450
|
+
}
|
|
13451
|
+
}
|
|
13452
|
+
return latest;
|
|
13453
|
+
}
|
|
13432
13454
|
async function runKnowledgeSync(options = {}) {
|
|
13433
13455
|
const deps = options.deps ?? {};
|
|
13434
13456
|
const loadState = deps.loadState ?? loadSyncState;
|
|
@@ -13511,14 +13533,20 @@ async function runKnowledgeSync(options = {}) {
|
|
|
13511
13533
|
}
|
|
13512
13534
|
const worthStudying = deps.worthStudying ?? isWorthStudying;
|
|
13513
13535
|
const isIncognito = deps.isIncognito ?? isIncognitoSession;
|
|
13536
|
+
const studiedSnapshot = lastStudiedSnapshot(state.mined_sessions);
|
|
13514
13537
|
const examined = [];
|
|
13515
13538
|
const batch = [];
|
|
13516
13539
|
let trivial = 0;
|
|
13517
13540
|
let incognito = 0;
|
|
13541
|
+
let alreadyStudied = 0;
|
|
13518
13542
|
for (let i = ready.length - 1;i >= 0 && batch.length < MINE_BATCH_LIMIT; i--) {
|
|
13519
13543
|
const candidate = ready[i];
|
|
13520
13544
|
examined.push(candidate);
|
|
13521
|
-
|
|
13545
|
+
const key = sessionKey(candidate);
|
|
13546
|
+
if (Date.parse(candidate.updated) <= (studiedSnapshot.get(key) ?? Number.NEGATIVE_INFINITY)) {
|
|
13547
|
+
alreadyStudied += 1;
|
|
13548
|
+
logger.debug("sync", `skipping already-studied session ${key}`);
|
|
13549
|
+
} else if (isIncognito(candidate)) {
|
|
13522
13550
|
incognito += 1;
|
|
13523
13551
|
logger.debug("sync", `skipping incognito session ${candidate.harness}/${candidate.id}`);
|
|
13524
13552
|
} else if (worthStudying(candidate)) {
|
|
@@ -13527,7 +13555,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
13527
13555
|
trivial += 1;
|
|
13528
13556
|
}
|
|
13529
13557
|
}
|
|
13530
|
-
const skippedNote = `${trivial} trivial, ${incognito} incognito skipped`;
|
|
13558
|
+
const skippedNote = `${trivial} trivial, ${incognito} incognito${alreadyStudied > 0 ? `, ${alreadyStudied} already studied` : ""} skipped`;
|
|
13531
13559
|
if (batch.length === 0) {
|
|
13532
13560
|
saveState({
|
|
13533
13561
|
...state,
|
|
@@ -13546,29 +13574,41 @@ async function runKnowledgeSync(options = {}) {
|
|
|
13546
13574
|
}
|
|
13547
13575
|
logger.debug("sync", `studying ${batch.length} of ${ready.length} ready sessions (${skippedNote})`);
|
|
13548
13576
|
const learner = await deps.mine(batch);
|
|
13577
|
+
const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
|
|
13578
|
+
const noted = new Set(learner.notedSessions ?? []);
|
|
13579
|
+
const notedBatch = batch.filter((s) => noted.has(sessionKey(s)));
|
|
13580
|
+
const partialProgress = (at) => {
|
|
13581
|
+
if (notedBatch.length === 0)
|
|
13582
|
+
return {};
|
|
13583
|
+
let tokens = 0;
|
|
13584
|
+
for (const s of notedBatch)
|
|
13585
|
+
tokens += sessionTokens(s);
|
|
13586
|
+
logger.debug("sync", `recorded ${notedBatch.length} noted sessions from the ${learner.outcome} run`);
|
|
13587
|
+
return {
|
|
13588
|
+
mined_sessions: [...state.mined_sessions ?? [], ...studiedRecords(notedBatch, at)].slice(-STUDIED_HISTORY_LIMIT),
|
|
13589
|
+
total_mined: (state.total_mined ?? 0) + notedBatch.length,
|
|
13590
|
+
total_notes: (state.total_notes ?? 0) + learner.notesWritten,
|
|
13591
|
+
total_learning_tokens: (state.total_learning_tokens ?? 0) + tokens
|
|
13592
|
+
};
|
|
13593
|
+
};
|
|
13549
13594
|
switch (learner.outcome) {
|
|
13550
13595
|
case "completed": {
|
|
13551
13596
|
for (const s of batch) {
|
|
13552
13597
|
logger.debug("sync", `studied session ${s.harness}/${s.id}`);
|
|
13553
13598
|
}
|
|
13554
|
-
const
|
|
13599
|
+
const completedAt = now().toISOString();
|
|
13555
13600
|
const history = [
|
|
13556
13601
|
...state.mined_sessions ?? [],
|
|
13557
|
-
...batch
|
|
13558
|
-
at: studiedAt,
|
|
13559
|
-
session: `${s.harness}/${s.id}`,
|
|
13560
|
-
...s.project ? { project: s.project } : {}
|
|
13561
|
-
}))
|
|
13602
|
+
...studiedRecords(batch, completedAt)
|
|
13562
13603
|
].slice(-STUDIED_HISTORY_LIMIT);
|
|
13563
13604
|
const watermark = batchWatermark(examined);
|
|
13564
|
-
const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
|
|
13565
13605
|
let batchTokens = 0;
|
|
13566
13606
|
for (const s of batch)
|
|
13567
13607
|
batchTokens += sessionTokens(s);
|
|
13568
13608
|
saveState({
|
|
13569
13609
|
...state,
|
|
13570
13610
|
watermark,
|
|
13571
|
-
last_attempt_at:
|
|
13611
|
+
last_attempt_at: completedAt,
|
|
13572
13612
|
consecutive_failures: 0,
|
|
13573
13613
|
mined_sessions: history,
|
|
13574
13614
|
total_mined: (state.total_mined ?? 0) + batch.length,
|
|
@@ -13588,10 +13628,12 @@ async function runKnowledgeSync(options = {}) {
|
|
|
13588
13628
|
}
|
|
13589
13629
|
case "consent_off":
|
|
13590
13630
|
case "credit_limit":
|
|
13591
|
-
case "quota_exceeded":
|
|
13631
|
+
case "quota_exceeded":
|
|
13632
|
+
case "claude_code_missing": {
|
|
13592
13633
|
const at = now().toISOString();
|
|
13593
13634
|
saveState({
|
|
13594
13635
|
...state,
|
|
13636
|
+
...partialProgress(at),
|
|
13595
13637
|
last_attempt_at: at,
|
|
13596
13638
|
consecutive_failures: 0,
|
|
13597
13639
|
last_refusal: {
|
|
@@ -13600,13 +13642,14 @@ async function runKnowledgeSync(options = {}) {
|
|
|
13600
13642
|
message: learner.message ?? "Studying unavailable right now."
|
|
13601
13643
|
}
|
|
13602
13644
|
});
|
|
13603
|
-
logger.debug("sync", `studying skipped
|
|
13645
|
+
logger.debug("sync", `studying skipped: ${learner.outcome}`);
|
|
13604
13646
|
return { status: "skipped-gateway", ...base, studiedSessions: 0, learner };
|
|
13605
13647
|
}
|
|
13606
13648
|
default: {
|
|
13607
13649
|
const at = now().toISOString();
|
|
13608
13650
|
saveState({
|
|
13609
13651
|
...state,
|
|
13652
|
+
...partialProgress(at),
|
|
13610
13653
|
last_attempt_at: at,
|
|
13611
13654
|
consecutive_failures: state.consecutive_failures + 1,
|
|
13612
13655
|
...learner.outcome === "gateway_rejected" && learner.message ? { last_refusal: { at, outcome: learner.outcome, message: learner.message } } : {}
|
|
@@ -13637,7 +13680,7 @@ var init_sync = __esm(() => {
|
|
|
13637
13680
|
});
|
|
13638
13681
|
|
|
13639
13682
|
// src/telemetry/debug-id.ts
|
|
13640
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
13683
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "93db7db7-907f-4d14-6475-6e2164756e21";
|
|
13641
13684
|
|
|
13642
13685
|
// src/telemetry/telemetry.ts
|
|
13643
13686
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -13862,12 +13905,19 @@ function consumeCommandFacets() {
|
|
|
13862
13905
|
function allowlisted(value, allowed) {
|
|
13863
13906
|
return typeof value === "string" && allowed.has(value) ? value : undefined;
|
|
13864
13907
|
}
|
|
13908
|
+
function matching(value, pattern) {
|
|
13909
|
+
return typeof value === "string" && pattern.test(value) ? value : undefined;
|
|
13910
|
+
}
|
|
13865
13911
|
function sanitizeFacets(facets) {
|
|
13866
13912
|
if (!facets || typeof facets !== "object")
|
|
13867
13913
|
return {};
|
|
13868
13914
|
const trigger = allowlisted(facets.sync_trigger, SYNC_TRIGGERS);
|
|
13869
13915
|
const status = allowlisted(facets.sync_status, SYNC_STATUSES);
|
|
13870
13916
|
const learner = allowlisted(facets.learner_outcome, LEARNER_OUTCOMES);
|
|
13917
|
+
const gatewayReason = allowlisted(facets.gateway_reason, GATEWAY_REASONS);
|
|
13918
|
+
const claudeSource = allowlisted(facets.claude_code_source, CLAUDE_CODE_SOURCES);
|
|
13919
|
+
const claudeVersion = matching(facets.claude_code_version, CLAUDE_CODE_VERSION_PATTERN);
|
|
13920
|
+
const model = matching(facets.learner_model, LEARNER_MODEL_PATTERN);
|
|
13871
13921
|
const backfill = allowlisted(facets.backfill_offer, BACKFILL_OFFERS);
|
|
13872
13922
|
return {
|
|
13873
13923
|
...trigger ? { sync_trigger: trigger } : {},
|
|
@@ -13875,6 +13925,10 @@ function sanitizeFacets(facets) {
|
|
|
13875
13925
|
...facets.sessions_studied !== undefined ? { sessions_studied: countBucket(facets.sessions_studied) } : {},
|
|
13876
13926
|
...facets.notes_written !== undefined ? { notes_written: countBucket(facets.notes_written) } : {},
|
|
13877
13927
|
...learner ? { learner_outcome: learner } : {},
|
|
13928
|
+
...gatewayReason ? { gateway_reason: gatewayReason } : {},
|
|
13929
|
+
...claudeSource ? { claude_code_source: claudeSource } : {},
|
|
13930
|
+
...claudeVersion ? { claude_code_version: claudeVersion } : {},
|
|
13931
|
+
...model ? { learner_model: model } : {},
|
|
13878
13932
|
...backfill ? { backfill_offer: backfill } : {}
|
|
13879
13933
|
};
|
|
13880
13934
|
}
|
|
@@ -13964,7 +14018,13 @@ function parseSentryDsn(value) {
|
|
|
13964
14018
|
return null;
|
|
13965
14019
|
}
|
|
13966
14020
|
}
|
|
13967
|
-
function sentryTags(command, context, runtime, error) {
|
|
14021
|
+
function sentryTags(command, context, runtime, error, facets) {
|
|
14022
|
+
const facetTags = {};
|
|
14023
|
+
for (const key of SENTRY_FACET_TAGS) {
|
|
14024
|
+
const value = facets[key];
|
|
14025
|
+
if (value)
|
|
14026
|
+
facetTags[key] = value;
|
|
14027
|
+
}
|
|
13968
14028
|
return {
|
|
13969
14029
|
schema_version: "1",
|
|
13970
14030
|
command,
|
|
@@ -13980,9 +14040,21 @@ function sentryTags(command, context, runtime, error) {
|
|
|
13980
14040
|
is_authenticated: String(context.isAuthenticated),
|
|
13981
14041
|
...error.code ? { error_code: error.code } : {},
|
|
13982
14042
|
...error.status ? { http_status: String(error.status) } : {},
|
|
13983
|
-
...error.exitCode === undefined ? {} : { exit_code: String(error.exitCode) }
|
|
14043
|
+
...error.exitCode === undefined ? {} : { exit_code: String(error.exitCode) },
|
|
14044
|
+
...facetTags
|
|
13984
14045
|
};
|
|
13985
14046
|
}
|
|
14047
|
+
function failedLearnerOutcome(facets) {
|
|
14048
|
+
return facets.learner_outcome === "completed" ? undefined : facets.learner_outcome;
|
|
14049
|
+
}
|
|
14050
|
+
function exceptionSummary(command, error, facets) {
|
|
14051
|
+
if (facets.learner_outcome === "completed")
|
|
14052
|
+
return error.code ?? error.type;
|
|
14053
|
+
const outcome = facets.learner_outcome ?? facets.sync_status;
|
|
14054
|
+
if (!outcome)
|
|
14055
|
+
return error.code ?? error.type;
|
|
14056
|
+
return `${command}: ${outcome}${facets.gateway_reason ? ` (${facets.gateway_reason})` : ""}`;
|
|
14057
|
+
}
|
|
13986
14058
|
function buildSentryEnvelope(input) {
|
|
13987
14059
|
const parsedDsn = parseSentryDsn(input.dsn);
|
|
13988
14060
|
if (!parsedDsn || !validEventId(input.eventId))
|
|
@@ -13990,6 +14062,7 @@ function buildSentryEnvelope(input) {
|
|
|
13990
14062
|
const context = normalizeContext(input.context);
|
|
13991
14063
|
const runtime = normalizeRuntime(input.runtime);
|
|
13992
14064
|
const command = canonicalCommand(input.command);
|
|
14065
|
+
const facets = sanitizeFacets(input.facets);
|
|
13993
14066
|
const debugId = validDebugId(input.debugId) ? input.debugId.toLowerCase() : undefined;
|
|
13994
14067
|
const error = {
|
|
13995
14068
|
type: validErrorType(input.error.type) ? input.error.type : "Error",
|
|
@@ -14009,7 +14082,7 @@ function buildSentryEnvelope(input) {
|
|
|
14009
14082
|
};
|
|
14010
14083
|
const exceptionValue = {
|
|
14011
14084
|
type: error.type,
|
|
14012
|
-
value:
|
|
14085
|
+
value: exceptionSummary(command, error, facets),
|
|
14013
14086
|
...error.frames.length > 0 ? { stacktrace: { frames: error.frames } } : {}
|
|
14014
14087
|
};
|
|
14015
14088
|
const newestFrame = error.frames.at(-1);
|
|
@@ -14022,7 +14095,7 @@ function buildSentryEnvelope(input) {
|
|
|
14022
14095
|
platform: "node",
|
|
14023
14096
|
level: "error",
|
|
14024
14097
|
release: `dosu-cli@${runtime.version}`,
|
|
14025
|
-
tags: sentryTags(command, context, runtime, error),
|
|
14098
|
+
tags: sentryTags(command, context, runtime, error, facets),
|
|
14026
14099
|
...context.user ? {
|
|
14027
14100
|
user: {
|
|
14028
14101
|
id: context.user.id,
|
|
@@ -14034,7 +14107,15 @@ function buildSentryEnvelope(input) {
|
|
|
14034
14107
|
images: [{ type: "sourcemap", code_file: BUNDLE_CODE_FILE, debug_id: debugId }]
|
|
14035
14108
|
}
|
|
14036
14109
|
} : {},
|
|
14037
|
-
fingerprint: [
|
|
14110
|
+
fingerprint: [
|
|
14111
|
+
"dosu-cli",
|
|
14112
|
+
command,
|
|
14113
|
+
error.type,
|
|
14114
|
+
error.code ?? "unknown",
|
|
14115
|
+
safeCallsite,
|
|
14116
|
+
...failedLearnerOutcome(facets) ? [facets.learner_outcome] : [],
|
|
14117
|
+
...facets.gateway_reason ? [facets.gateway_reason] : []
|
|
14118
|
+
],
|
|
14038
14119
|
exception: { values: [exceptionValue] }
|
|
14039
14120
|
};
|
|
14040
14121
|
const envelopeHeader = {
|
|
@@ -14275,15 +14356,17 @@ function createCommandTelemetry(settings, dependencies = {}) {
|
|
|
14275
14356
|
async function dispatch(result, exitCode, error) {
|
|
14276
14357
|
try {
|
|
14277
14358
|
const tasks = [];
|
|
14359
|
+
let facets;
|
|
14360
|
+
if (!disabled) {
|
|
14361
|
+
try {
|
|
14362
|
+
facets = resolveFacets();
|
|
14363
|
+
} catch {
|
|
14364
|
+
facets = undefined;
|
|
14365
|
+
}
|
|
14366
|
+
}
|
|
14278
14367
|
if (!disabled && analyticsToken) {
|
|
14279
14368
|
const id = context.user?.id ?? installId();
|
|
14280
14369
|
if (id) {
|
|
14281
|
-
let facets;
|
|
14282
|
-
try {
|
|
14283
|
-
facets = resolveFacets();
|
|
14284
|
-
} catch {
|
|
14285
|
-
facets = undefined;
|
|
14286
|
-
}
|
|
14287
14370
|
const payload = buildPostHogPayload({
|
|
14288
14371
|
apiKey: analyticsToken,
|
|
14289
14372
|
...context.user ? {} : { installId: id },
|
|
@@ -14311,14 +14394,16 @@ function createCommandTelemetry(settings, dependencies = {}) {
|
|
|
14311
14394
|
}
|
|
14312
14395
|
}
|
|
14313
14396
|
}
|
|
14314
|
-
|
|
14397
|
+
const sentryError = result === "failure" && error && shouldSendToSentry(error) ? error : result === "success" && sanitizeFacets(facets).sync_status === "mine-failed" ? { type: "LearnerRunFailed", frames: [] } : undefined;
|
|
14398
|
+
if (!disabled && sentryError && sentryDsn) {
|
|
14315
14399
|
const id = eventId(generateUuid);
|
|
14316
14400
|
const envelope = id ? buildSentryEnvelope({
|
|
14317
14401
|
dsn: sentryDsn,
|
|
14318
14402
|
command,
|
|
14319
14403
|
context,
|
|
14320
14404
|
runtime,
|
|
14321
|
-
error,
|
|
14405
|
+
error: sentryError,
|
|
14406
|
+
...facets ? { facets } : {},
|
|
14322
14407
|
eventId: id,
|
|
14323
14408
|
timestampMs: safeNow(now),
|
|
14324
14409
|
debugId: BUNDLE_DEBUG_ID_PLACEHOLDER
|
|
@@ -14370,7 +14455,7 @@ function createCommandTelemetry(settings, dependencies = {}) {
|
|
|
14370
14455
|
function shouldSendToSentry(error) {
|
|
14371
14456
|
return !["SESSION_EXPIRED", "SESSION_PERSISTENCE_ERROR"].includes(error.code ?? "");
|
|
14372
14457
|
}
|
|
14373
|
-
var REQUEST_TIMEOUT_MS = 500, MAX_RESPONSE_BYTES, MAX_STACK_FRAMES = 20, UNKNOWN_COMMAND = "unknown", FALLBACK_INSTALL_ID = "00000000-0000-4000-8000-000000000000", BUNDLE_CODE_FILE = "app:///bin/dosu.js", CLI_PACKAGE_ROOT, SAFE_ERROR_TYPES, SAFE_ERROR_CODES, SYNC_TRIGGERS, SYNC_STATUSES, LEARNER_OUTCOMES, BACKFILL_OFFERS, pendingFacets;
|
|
14458
|
+
var REQUEST_TIMEOUT_MS = 500, MAX_RESPONSE_BYTES, MAX_STACK_FRAMES = 20, UNKNOWN_COMMAND = "unknown", FALLBACK_INSTALL_ID = "00000000-0000-4000-8000-000000000000", BUNDLE_CODE_FILE = "app:///bin/dosu.js", CLI_PACKAGE_ROOT, SAFE_ERROR_TYPES, SAFE_ERROR_CODES, SYNC_TRIGGERS, SYNC_STATUSES, LEARNER_OUTCOMES, GATEWAY_REASONS, CLAUDE_CODE_SOURCES, CLAUDE_CODE_VERSION_PATTERN, LEARNER_MODEL_PATTERN, BACKFILL_OFFERS, SENTRY_FACET_TAGS, pendingFacets;
|
|
14374
14459
|
var init_telemetry = __esm(() => {
|
|
14375
14460
|
MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
14376
14461
|
CLI_PACKAGE_ROOT = detectCliPackageRoot();
|
|
@@ -14381,6 +14466,7 @@ var init_telemetry = __esm(() => {
|
|
|
14381
14466
|
"CliUsageError",
|
|
14382
14467
|
"CommandExitError",
|
|
14383
14468
|
"Error",
|
|
14469
|
+
"LearnerRunFailed",
|
|
14384
14470
|
"OAuthCallbackError",
|
|
14385
14471
|
"RangeError",
|
|
14386
14472
|
"ReferenceError",
|
|
@@ -14474,8 +14560,21 @@ var init_telemetry = __esm(() => {
|
|
|
14474
14560
|
"credit_limit",
|
|
14475
14561
|
"quota_exceeded",
|
|
14476
14562
|
"gateway_rejected",
|
|
14563
|
+
"claude_code_missing",
|
|
14477
14564
|
"error"
|
|
14478
14565
|
]);
|
|
14566
|
+
GATEWAY_REASONS = new Set([
|
|
14567
|
+
"system_role_unsupported",
|
|
14568
|
+
"adaptive_thinking_unsupported",
|
|
14569
|
+
"effort_unsupported",
|
|
14570
|
+
"unsupported_request",
|
|
14571
|
+
"max_tokens",
|
|
14572
|
+
"context_length",
|
|
14573
|
+
"other"
|
|
14574
|
+
]);
|
|
14575
|
+
CLAUDE_CODE_SOURCES = new Set(["sdk", "system", "missing"]);
|
|
14576
|
+
CLAUDE_CODE_VERSION_PATTERN = /^\d{1,4}\.\d{1,4}\.\d{1,6}(?:-[0-9A-Za-z]{1,16}(?:\.[0-9A-Za-z]{1,16}){0,3})?$/;
|
|
14577
|
+
LEARNER_MODEL_PATTERN = /^claude-[a-z0-9.-]{1,56}$/;
|
|
14479
14578
|
BACKFILL_OFFERS = new Set([
|
|
14480
14579
|
"not-offered",
|
|
14481
14580
|
"accepted",
|
|
@@ -14483,6 +14582,15 @@ var init_telemetry = __esm(() => {
|
|
|
14483
14582
|
"cancelled",
|
|
14484
14583
|
"spawn-failed"
|
|
14485
14584
|
]);
|
|
14585
|
+
SENTRY_FACET_TAGS = [
|
|
14586
|
+
"sync_trigger",
|
|
14587
|
+
"sync_status",
|
|
14588
|
+
"learner_outcome",
|
|
14589
|
+
"gateway_reason",
|
|
14590
|
+
"claude_code_source",
|
|
14591
|
+
"claude_code_version",
|
|
14592
|
+
"learner_model"
|
|
14593
|
+
];
|
|
14486
14594
|
});
|
|
14487
14595
|
|
|
14488
14596
|
// src/commands/agent-select.ts
|
|
@@ -15073,12 +15181,14 @@ function buildLearnerEnv(options) {
|
|
|
15073
15181
|
const headers = [
|
|
15074
15182
|
`x-dosu-run-id: ${options.runID}`,
|
|
15075
15183
|
`x-dosu-trigger: ${options.trigger}`,
|
|
15076
|
-
`x-dosu-cli-version: ${options.cliVersion}
|
|
15184
|
+
`x-dosu-cli-version: ${options.cliVersion}`,
|
|
15185
|
+
`x-dosu-expected-model: ${options.model}`
|
|
15077
15186
|
];
|
|
15078
15187
|
if (options.deploymentID)
|
|
15079
15188
|
headers.push(`x-dosu-deployment-id: ${options.deploymentID}`);
|
|
15080
15189
|
env.ANTHROPIC_BASE_URL = options.gatewayURL;
|
|
15081
15190
|
env.ANTHROPIC_AUTH_TOKEN = options.apiKey;
|
|
15191
|
+
env.ANTHROPIC_MODEL = options.model;
|
|
15082
15192
|
env.ANTHROPIC_CUSTOM_HEADERS = headers.join(`
|
|
15083
15193
|
`);
|
|
15084
15194
|
env.CLAUDE_CONFIG_DIR = options.configDir;
|
|
@@ -15136,11 +15246,44 @@ function findSystemClaude(env = process.env, homeDir = homedir6()) {
|
|
|
15136
15246
|
function resolveClaudeExecutable(options = {}) {
|
|
15137
15247
|
const hasSdkBinary = options.sdkBinaryExists ?? sdkNativeBinaryExists;
|
|
15138
15248
|
if (hasSdkBinary())
|
|
15139
|
-
return;
|
|
15140
|
-
|
|
15249
|
+
return { kind: "sdk" };
|
|
15250
|
+
const path = findSystemClaude(options.env, options.homeDir);
|
|
15251
|
+
return path ? { kind: "system", path } : { kind: "missing" };
|
|
15141
15252
|
}
|
|
15142
15253
|
var init_executable = () => {};
|
|
15143
15254
|
|
|
15255
|
+
// src/learner/model.ts
|
|
15256
|
+
async function resolveServedModel(options) {
|
|
15257
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
15258
|
+
try {
|
|
15259
|
+
const base = options.gatewayURL.replace(/\/+$/, "");
|
|
15260
|
+
const resp = await fetchImpl(`${base}/capabilities`, {
|
|
15261
|
+
method: "GET",
|
|
15262
|
+
headers: { Authorization: `Bearer ${options.apiKey}` },
|
|
15263
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? CAPABILITIES_TIMEOUT_MS)
|
|
15264
|
+
});
|
|
15265
|
+
if (!resp.ok) {
|
|
15266
|
+
logger.debug("learner", `gateway capabilities returned ${resp.status}; using default model`);
|
|
15267
|
+
return DEFAULT_LEARNER_MODEL;
|
|
15268
|
+
}
|
|
15269
|
+
const body = await resp.json();
|
|
15270
|
+
const model = body?.model;
|
|
15271
|
+
if (typeof model === "string" && CLAUDE_MODEL_PATTERN.test(model))
|
|
15272
|
+
return model;
|
|
15273
|
+
logger.debug("learner", "gateway capabilities named no usable model; using default model");
|
|
15274
|
+
return DEFAULT_LEARNER_MODEL;
|
|
15275
|
+
} catch (error) {
|
|
15276
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
15277
|
+
logger.debug("learner", `gateway capabilities failed (${text}); using default model`);
|
|
15278
|
+
return DEFAULT_LEARNER_MODEL;
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
var DEFAULT_LEARNER_MODEL = "claude-haiku-4-5", CAPABILITIES_TIMEOUT_MS = 5000, CLAUDE_MODEL_PATTERN;
|
|
15282
|
+
var init_model = __esm(() => {
|
|
15283
|
+
init_logger();
|
|
15284
|
+
CLAUDE_MODEL_PATTERN = /^claude-[a-z0-9.-]+$/;
|
|
15285
|
+
});
|
|
15286
|
+
|
|
15144
15287
|
// src/learner/prompt.ts
|
|
15145
15288
|
function buildLearnerSystemPrompt(coreRules) {
|
|
15146
15289
|
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.
|
|
@@ -58104,6 +58247,13 @@ function snippet(value) {
|
|
|
58104
58247
|
const flat = (text ?? "").replace(/\s+/g, " ").trim();
|
|
58105
58248
|
return flat.length > TRACE_SNIPPET_LIMIT ? `${flat.slice(0, TRACE_SNIPPET_LIMIT)}…` : flat;
|
|
58106
58249
|
}
|
|
58250
|
+
function classifyRejectionReason(detail) {
|
|
58251
|
+
for (const [pattern, reason] of REJECTION_REASONS) {
|
|
58252
|
+
if (pattern.test(detail))
|
|
58253
|
+
return reason;
|
|
58254
|
+
}
|
|
58255
|
+
return "other";
|
|
58256
|
+
}
|
|
58107
58257
|
function classifyGatewayRejection(text) {
|
|
58108
58258
|
const match = API_400_PATTERN.exec(text);
|
|
58109
58259
|
if (!match)
|
|
@@ -58116,7 +58266,8 @@ function classifyGatewayRejection(text) {
|
|
|
58116
58266
|
} catch {}
|
|
58117
58267
|
return {
|
|
58118
58268
|
outcome: "gateway_rejected",
|
|
58119
|
-
message: `LLM gateway rejected the study run: ${snippet(detail)}
|
|
58269
|
+
message: `LLM gateway rejected the study run: ${snippet(detail)}`,
|
|
58270
|
+
reason: classifyRejectionReason(detail.trim())
|
|
58120
58271
|
};
|
|
58121
58272
|
}
|
|
58122
58273
|
function traceAgentMessage(message) {
|
|
@@ -58164,11 +58315,26 @@ async function runLearner(options) {
|
|
|
58164
58315
|
message: "LLM gateway URL is not set. From source, use `bun run dev` (production endpoints) or `bun run dev:local` (local stack)."
|
|
58165
58316
|
};
|
|
58166
58317
|
}
|
|
58167
|
-
await Promise.resolve().then(() => init_sdk());
|
|
58168
58318
|
const claudeExecutable = resolveClaudeExecutable();
|
|
58169
|
-
if (claudeExecutable) {
|
|
58170
|
-
|
|
58319
|
+
if (claudeExecutable.kind === "missing") {
|
|
58320
|
+
return {
|
|
58321
|
+
outcome: "claude_code_missing",
|
|
58322
|
+
notesWritten: 0,
|
|
58323
|
+
turns: 0,
|
|
58324
|
+
claudeCodeSource: "missing",
|
|
58325
|
+
message: "Studying runs on Claude Code, which isn't installed here. Install Claude Code, then run `dosu knowledge sync`."
|
|
58326
|
+
};
|
|
58327
|
+
}
|
|
58328
|
+
if (claudeExecutable.kind === "system") {
|
|
58329
|
+
logger.debug("learner", `using system Claude Code executable: ${claudeExecutable.path}`);
|
|
58171
58330
|
}
|
|
58331
|
+
const model = await resolveServedModel({ gatewayURL, apiKey: options.apiKey });
|
|
58332
|
+
logger.debug("learner", `study run model: ${model}`);
|
|
58333
|
+
const diagnostics = {
|
|
58334
|
+
claudeCodeSource: claudeExecutable.kind,
|
|
58335
|
+
model
|
|
58336
|
+
};
|
|
58337
|
+
await Promise.resolve().then(() => init_sdk());
|
|
58172
58338
|
const configDir = createRunConfigDir();
|
|
58173
58339
|
const runID = options.runID ?? crypto.randomUUID();
|
|
58174
58340
|
const sessionStartedAt = options.sessions.map((s) => s.updated).sort()[0];
|
|
@@ -58179,7 +58345,19 @@ async function runLearner(options) {
|
|
|
58179
58345
|
let notesWritten = 0;
|
|
58180
58346
|
let currentSession;
|
|
58181
58347
|
const readsSinceWrite = new Set;
|
|
58348
|
+
const sessionKeys = new Map(options.sessions.map((s) => [s.id, `${s.harness}/${s.id}`]));
|
|
58349
|
+
const notedSessions = new Set;
|
|
58350
|
+
const pendingNotes = new Map;
|
|
58182
58351
|
let turns = 0;
|
|
58352
|
+
const finish = (outcome, message, gatewayReason) => ({
|
|
58353
|
+
outcome,
|
|
58354
|
+
notesWritten,
|
|
58355
|
+
notedSessions: [...notedSessions],
|
|
58356
|
+
turns,
|
|
58357
|
+
...message === undefined ? {} : { message },
|
|
58358
|
+
...gatewayReason ? { gatewayReason } : {},
|
|
58359
|
+
...diagnostics
|
|
58360
|
+
});
|
|
58183
58361
|
const env = buildLearnerEnv({
|
|
58184
58362
|
apiKey: options.apiKey,
|
|
58185
58363
|
gatewayURL,
|
|
@@ -58187,19 +58365,21 @@ async function runLearner(options) {
|
|
|
58187
58365
|
runID,
|
|
58188
58366
|
trigger: options.trigger,
|
|
58189
58367
|
cliVersion: getVersionString(),
|
|
58190
|
-
deploymentID: options.deploymentID
|
|
58368
|
+
deploymentID: options.deploymentID,
|
|
58369
|
+
model
|
|
58191
58370
|
});
|
|
58192
58371
|
try {
|
|
58193
58372
|
const run = PGt({
|
|
58194
58373
|
prompt: buildLearnerPrompt(options.sessions),
|
|
58195
58374
|
options: {
|
|
58196
58375
|
systemPrompt: buildLearnerSystemPrompt(LEARNER_CORE_RULES),
|
|
58376
|
+
model,
|
|
58197
58377
|
env,
|
|
58198
58378
|
abortController: abort,
|
|
58199
58379
|
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
58200
58380
|
settingSources: [],
|
|
58201
58381
|
persistSession: false,
|
|
58202
|
-
...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
|
|
58382
|
+
...claudeExecutable.kind === "system" ? { pathToClaudeCodeExecutable: claudeExecutable.path } : {},
|
|
58203
58383
|
sandbox: { enabled: true, failIfUnavailable: false },
|
|
58204
58384
|
mcpServers: {
|
|
58205
58385
|
[SESSIONS_SERVER_NAME]: createSessionToolsServer(options.sessions),
|
|
@@ -58215,7 +58395,7 @@ async function runLearner(options) {
|
|
|
58215
58395
|
}
|
|
58216
58396
|
},
|
|
58217
58397
|
stderr: (data) => logger.debug("learner", `[sdk] ${data}`),
|
|
58218
|
-
canUseTool: async (toolName, input) => {
|
|
58398
|
+
canUseTool: async (toolName, input, { toolUseID }) => {
|
|
58219
58399
|
if (!allowed.has(toolName)) {
|
|
58220
58400
|
return {
|
|
58221
58401
|
behavior: "deny",
|
|
@@ -58246,6 +58426,9 @@ async function runLearner(options) {
|
|
|
58246
58426
|
}
|
|
58247
58427
|
notesWritten += 1;
|
|
58248
58428
|
readsSinceWrite.clear();
|
|
58429
|
+
const noted = currentSession && sessionKeys.get(currentSession);
|
|
58430
|
+
if (noted)
|
|
58431
|
+
pendingNotes.set(toolUseID, noted);
|
|
58249
58432
|
const { transcript_id: _authoredByModel, ...clean } = input;
|
|
58250
58433
|
return {
|
|
58251
58434
|
behavior: "allow",
|
|
@@ -58258,56 +58441,46 @@ async function runLearner(options) {
|
|
|
58258
58441
|
});
|
|
58259
58442
|
for await (const message of run) {
|
|
58260
58443
|
traceAgentMessage(message);
|
|
58444
|
+
if (message.type === "user" && Array.isArray(message.message.content)) {
|
|
58445
|
+
for (const block of message.message.content) {
|
|
58446
|
+
const noted = block.type === "tool_result" && pendingNotes.get(block.tool_use_id);
|
|
58447
|
+
if (noted && block.is_error !== true)
|
|
58448
|
+
notedSessions.add(noted);
|
|
58449
|
+
}
|
|
58450
|
+
}
|
|
58451
|
+
if (message.type === "system" && message.subtype === "init" && typeof message.claude_code_version === "string") {
|
|
58452
|
+
diagnostics.claudeCodeVersion = message.claude_code_version;
|
|
58453
|
+
}
|
|
58261
58454
|
if (message.type === "result") {
|
|
58262
58455
|
turns = message.num_turns;
|
|
58263
58456
|
const text = message.subtype === "success" ? message.result : message.subtype;
|
|
58264
58457
|
const gatewayError = classifyGatewayError(text ?? "") ?? (message.is_error ? classifyGatewayRejection(text ?? "") : null);
|
|
58265
58458
|
if (gatewayError) {
|
|
58266
|
-
return
|
|
58267
|
-
outcome: gatewayError.outcome,
|
|
58268
|
-
notesWritten,
|
|
58269
|
-
turns,
|
|
58270
|
-
message: gatewayError.message
|
|
58271
|
-
};
|
|
58459
|
+
return finish(gatewayError.outcome, gatewayError.message, gatewayError.reason);
|
|
58272
58460
|
}
|
|
58273
58461
|
if (message.subtype !== "success" || message.is_error) {
|
|
58274
58462
|
logger.debug("learner", `run ${runID} failed: ${text}`);
|
|
58275
|
-
return
|
|
58276
|
-
outcome: "error",
|
|
58277
|
-
notesWritten,
|
|
58278
|
-
turns,
|
|
58279
|
-
message: "Study run failed; see debug log for details."
|
|
58280
|
-
};
|
|
58463
|
+
return finish("error", "Study run failed; see debug log for details.");
|
|
58281
58464
|
}
|
|
58282
58465
|
logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
|
|
58283
|
-
return
|
|
58466
|
+
return finish("completed", text);
|
|
58284
58467
|
}
|
|
58285
58468
|
}
|
|
58286
|
-
return
|
|
58287
|
-
outcome: "error",
|
|
58288
|
-
notesWritten,
|
|
58289
|
-
turns,
|
|
58290
|
-
message: "Study run ended without a result."
|
|
58291
|
-
};
|
|
58469
|
+
return finish("error", "Study run ended without a result.");
|
|
58292
58470
|
} catch (error) {
|
|
58293
58471
|
const text = error instanceof Error ? error.message : String(error);
|
|
58294
58472
|
const gatewayError = classifyGatewayError(text) ?? classifyGatewayRejection(text);
|
|
58295
58473
|
if (gatewayError) {
|
|
58296
|
-
return
|
|
58474
|
+
return finish(gatewayError.outcome, gatewayError.message, gatewayError.reason);
|
|
58297
58475
|
}
|
|
58298
58476
|
logger.debug("learner", `run ${runID} threw: ${text}`);
|
|
58299
|
-
return
|
|
58300
|
-
outcome: "error",
|
|
58301
|
-
notesWritten,
|
|
58302
|
-
turns,
|
|
58303
|
-
message: abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details."
|
|
58304
|
-
};
|
|
58477
|
+
return finish("error", abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details.");
|
|
58305
58478
|
} finally {
|
|
58306
58479
|
clearTimeout(timer);
|
|
58307
58480
|
configDir.cleanup();
|
|
58308
58481
|
}
|
|
58309
58482
|
}
|
|
58310
|
-
var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLEDGE_SERVER_NAME = "dosu", GATEWAY_ERRORS, TRACE_SNIPPET_LIMIT = 400, API_400_PATTERN;
|
|
58483
|
+
var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLEDGE_SERVER_NAME = "dosu", GATEWAY_ERRORS, TRACE_SNIPPET_LIMIT = 400, REJECTION_REASONS, API_400_PATTERN;
|
|
58311
58484
|
var init_runner = __esm(() => {
|
|
58312
58485
|
init_logger();
|
|
58313
58486
|
init_config_helpers();
|
|
@@ -58316,6 +58489,7 @@ var init_runner = __esm(() => {
|
|
|
58316
58489
|
init_conflicts();
|
|
58317
58490
|
init_env();
|
|
58318
58491
|
init_executable();
|
|
58492
|
+
init_model();
|
|
58319
58493
|
init_tools();
|
|
58320
58494
|
DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1000;
|
|
58321
58495
|
GATEWAY_ERRORS = {
|
|
@@ -58332,6 +58506,14 @@ var init_runner = __esm(() => {
|
|
|
58332
58506
|
message: "Daily Dosu Remote Sessions budget reached; runs resume tomorrow."
|
|
58333
58507
|
}
|
|
58334
58508
|
};
|
|
58509
|
+
REJECTION_REASONS = [
|
|
58510
|
+
[/^dosu_unsupported_request:/, "unsupported_request"],
|
|
58511
|
+
[/\beffort\b|output_config/i, "effort_unsupported"],
|
|
58512
|
+
[/\brole\b.*\bsystem\b|\bsystem\b.*\brole\b|messages\.\d+\.role/i, "system_role_unsupported"],
|
|
58513
|
+
[/\badaptive\b/i, "adaptive_thinking_unsupported"],
|
|
58514
|
+
[/prompt is too long|context (?:limit|length|window)/i, "context_length"],
|
|
58515
|
+
[/max_tokens/i, "max_tokens"]
|
|
58516
|
+
];
|
|
58335
58517
|
API_400_PATTERN = /API Error: 400\b\s*(.*)/s;
|
|
58336
58518
|
});
|
|
58337
58519
|
|
|
@@ -58495,7 +58677,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
58495
58677
|
sync_status: outcome.status,
|
|
58496
58678
|
sessions_studied: sessionsStudied,
|
|
58497
58679
|
notes_written: notesWritten,
|
|
58498
|
-
...outcome.learner ?
|
|
58680
|
+
...outcome.learner ? learnerFacets(outcome.learner) : {}
|
|
58499
58681
|
});
|
|
58500
58682
|
if (opts.quiet)
|
|
58501
58683
|
return;
|
|
@@ -58555,6 +58737,15 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
58555
58737
|
cmd.addCommand(statuslineCommand2());
|
|
58556
58738
|
return cmd;
|
|
58557
58739
|
}
|
|
58740
|
+
function learnerFacets(learner) {
|
|
58741
|
+
return {
|
|
58742
|
+
learner_outcome: learner.outcome,
|
|
58743
|
+
...learner.gatewayReason ? { gateway_reason: learner.gatewayReason } : {},
|
|
58744
|
+
...learner.claudeCodeSource ? { claude_code_source: learner.claudeCodeSource } : {},
|
|
58745
|
+
...learner.claudeCodeVersion ? { claude_code_version: learner.claudeCodeVersion } : {},
|
|
58746
|
+
...learner.model ? { learner_model: learner.model } : {}
|
|
58747
|
+
};
|
|
58748
|
+
}
|
|
58558
58749
|
function buildLearner(trigger) {
|
|
58559
58750
|
const cfg = loadConfig();
|
|
58560
58751
|
if (cfg.mode === "oss")
|
|
@@ -66416,4 +66607,4 @@ main().catch((err) => {
|
|
|
66416
66607
|
process.exit(1);
|
|
66417
66608
|
});
|
|
66418
66609
|
|
|
66419
|
-
//# debugId=
|
|
66610
|
+
//# debugId=93db7db7-907f-4d14-6475-6e2164756e21
|