@cortexkit/opencode-magic-context 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/diagnostics.d.ts +38 -5
- package/dist/cli/diagnostics.d.ts.map +1 -1
- package/dist/cli/logs.d.ts.map +1 -1
- package/dist/cli.js +158 -7
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/nudger.d.ts +1 -1
- package/dist/hooks/magic-context/nudger.d.ts.map +1 -1
- package/dist/index.js +91 -11
- package/dist/shared/error-message.d.ts +30 -0
- package/dist/shared/error-message.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shared/error-message.test.ts +94 -0
- package/src/shared/error-message.ts +112 -0
|
@@ -37,12 +37,45 @@ export interface DiagnosticReport {
|
|
|
37
37
|
historianDumps: {
|
|
38
38
|
dir: string;
|
|
39
39
|
count: number;
|
|
40
|
-
recent:
|
|
41
|
-
name: string;
|
|
42
|
-
ageMinutes: number;
|
|
43
|
-
sizeKb: number;
|
|
44
|
-
}[];
|
|
40
|
+
recent: HistorianDumpSummary[];
|
|
45
41
|
};
|
|
42
|
+
/** Most recent historian-failure rows from session_meta across all sessions. */
|
|
43
|
+
historianFailures: HistorianFailureSummary[];
|
|
44
|
+
}
|
|
45
|
+
export interface HistorianDumpSummary {
|
|
46
|
+
name: string;
|
|
47
|
+
ageMinutes: number;
|
|
48
|
+
sizeKb: number;
|
|
49
|
+
/** Parsed metadata — only structural fields, never raw XML content. */
|
|
50
|
+
meta?: HistorianDumpMeta;
|
|
51
|
+
/** If the XML could not be parsed, reason for failure. */
|
|
52
|
+
parseError?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface HistorianDumpMeta {
|
|
55
|
+
/** Number of <compartment> elements found. */
|
|
56
|
+
compartmentCount: number;
|
|
57
|
+
/** Smallest start ordinal across compartments, or null if none. */
|
|
58
|
+
minStart: number | null;
|
|
59
|
+
/** Largest end ordinal across compartments, or null if none. */
|
|
60
|
+
maxEnd: number | null;
|
|
61
|
+
/** Value of <unprocessed_from> tag, if present. */
|
|
62
|
+
unprocessedFrom: number | null;
|
|
63
|
+
/** Number of <fact> items grouped by category. */
|
|
64
|
+
factCountByCategory: Record<string, number>;
|
|
65
|
+
/** Number of <user_observations> items. */
|
|
66
|
+
userObservationCount: number;
|
|
67
|
+
/** Total number of compartment ordinal gaps (missing ranges between consecutive compartments). */
|
|
68
|
+
ordinalGapCount: number;
|
|
69
|
+
/** Total number of overlapping compartment ranges. */
|
|
70
|
+
ordinalOverlapCount: number;
|
|
71
|
+
}
|
|
72
|
+
export interface HistorianFailureSummary {
|
|
73
|
+
sessionId: string;
|
|
74
|
+
failureCount: number;
|
|
75
|
+
/** Sanitized truncated last-error text. May be empty if never set. */
|
|
76
|
+
lastError: string;
|
|
77
|
+
/** ISO timestamp of last failure, or empty if never failed. */
|
|
78
|
+
lastFailureAt: string;
|
|
46
79
|
}
|
|
47
80
|
export declare function collectDiagnostics(): Promise<DiagnosticReport>;
|
|
48
81
|
export declare function renderDiagnosticsMarkdown(report: DiagnosticReport): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/cli/diagnostics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/cli/diagnostics.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,gBAAgB,CAAC;AAMrE,MAAM,WAAW,gBAAgB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,uBAAuB,EAAE,OAAO,CAAC;IACjC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,CAAC;IACF,WAAW,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,OAAO,CAAC;QAChB,kBAAkB,EAAE,MAAM,CAAC;KAC9B,CAAC;IACF,SAAS,EAAE;QACP,WAAW,EAAE,OAAO,CAAC;QACrB,OAAO,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,OAAO,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,cAAc,EAAE;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,oBAAoB,EAAE,CAAC;KAClC,CAAC;IACF,gFAAgF;IAChF,iBAAiB,EAAE,uBAAuB,EAAE,CAAC;CAChD;AAED,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAC9B,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,mDAAmD;IACnD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kDAAkD;IAClD,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,2CAA2C;IAC3C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kGAAkG;IAClG,eAAe,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,mBAAmB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;CACzB;AA+OD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAgDpE;AASD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CA8E1E"}
|
package/dist/cli/logs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/cli/logs.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,gBAAgB,EAA6B,MAAM,eAAe,CAAC;AAMjF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAe1D;AAeD,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/cli/logs.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,gBAAgB,EAA6B,MAAM,eAAe,CAAC;AAMjF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAe1D;AAeD,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACxB;AA0CD,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC,CAkD7B"}
|
package/dist/cli.js
CHANGED
|
@@ -8656,11 +8656,59 @@ function detectConfigPaths() {
|
|
|
8656
8656
|
|
|
8657
8657
|
// src/cli/diagnostics.ts
|
|
8658
8658
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
8659
|
+
import { Database } from "bun:sqlite";
|
|
8659
8660
|
import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
8660
8661
|
import { createRequire as createRequire2 } from "node:module";
|
|
8661
8662
|
import { homedir as homedir4, tmpdir as tmpdir2, userInfo } from "node:os";
|
|
8662
8663
|
import { join as join7 } from "node:path";
|
|
8663
8664
|
|
|
8665
|
+
// src/hooks/magic-context/compartment-parser.ts
|
|
8666
|
+
var COMPARTMENT_REGEX = /<compartment\s+(?:id="[^"]*"\s+)?start="(\d+)"\s+end="(\d+)"\s+title="([^"]+)"\s*>(.*?)<\/compartment>/gs;
|
|
8667
|
+
var CATEGORY_BLOCK_REGEX = /<(WORKFLOW_RULES|ARCHITECTURE_DECISIONS|CONSTRAINTS|CONFIG_DEFAULTS|KNOWN_ISSUES|ENVIRONMENT|NAMING|USER_PREFERENCES|USER_DIRECTIVES)>(.*?)<\/\1>/gs;
|
|
8668
|
+
var FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
|
|
8669
|
+
var UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
|
|
8670
|
+
var USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
|
|
8671
|
+
var USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
|
|
8672
|
+
function parseCompartmentOutput(text) {
|
|
8673
|
+
const compartments = [];
|
|
8674
|
+
const facts = [];
|
|
8675
|
+
for (const match of text.matchAll(COMPARTMENT_REGEX)) {
|
|
8676
|
+
const startMessage = parseInt(match[1], 10);
|
|
8677
|
+
const endMessage = parseInt(match[2], 10);
|
|
8678
|
+
const title = unescapeXml(match[3]);
|
|
8679
|
+
const content = unescapeXml(match[4].trim());
|
|
8680
|
+
if (!Number.isNaN(startMessage) && !Number.isNaN(endMessage) && title && content) {
|
|
8681
|
+
compartments.push({ startMessage, endMessage, title, content });
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8684
|
+
for (const categoryMatch of text.matchAll(CATEGORY_BLOCK_REGEX)) {
|
|
8685
|
+
const category = categoryMatch[1];
|
|
8686
|
+
const blockContent = categoryMatch[2];
|
|
8687
|
+
for (const itemMatch of blockContent.matchAll(FACT_ITEM_REGEX)) {
|
|
8688
|
+
const content = unescapeXml(itemMatch[1].trim());
|
|
8689
|
+
if (content) {
|
|
8690
|
+
facts.push({ category, content });
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
const unprocessedMatch = text.match(UNPROCESSED_REGEX);
|
|
8695
|
+
const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
|
|
8696
|
+
const userObservations = [];
|
|
8697
|
+
const userObsMatch = text.match(USER_OBSERVATIONS_REGEX);
|
|
8698
|
+
if (userObsMatch) {
|
|
8699
|
+
for (const itemMatch of userObsMatch[1].matchAll(USER_OBS_ITEM_REGEX)) {
|
|
8700
|
+
const obs = unescapeXml(itemMatch[1].trim());
|
|
8701
|
+
if (obs)
|
|
8702
|
+
userObservations.push(obs);
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
compartments.sort((a, b) => a.startMessage - b.startMessage);
|
|
8706
|
+
return { compartments, facts, unprocessedFrom, userObservations };
|
|
8707
|
+
}
|
|
8708
|
+
function unescapeXml(s) {
|
|
8709
|
+
return s.replace(/&/g, "&").replace(/'/g, "'").replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">");
|
|
8710
|
+
}
|
|
8711
|
+
|
|
8664
8712
|
// src/cli/opencode-helpers.ts
|
|
8665
8713
|
import { execSync } from "node:child_process";
|
|
8666
8714
|
function isOpenCodeInstalled() {
|
|
@@ -8840,6 +8888,40 @@ function configHasPluginEntry(config) {
|
|
|
8840
8888
|
return false;
|
|
8841
8889
|
});
|
|
8842
8890
|
}
|
|
8891
|
+
function parseHistorianDumpMeta(path2) {
|
|
8892
|
+
try {
|
|
8893
|
+
const xml = readFileSync4(path2, "utf-8");
|
|
8894
|
+
const parsed = parseCompartmentOutput(xml);
|
|
8895
|
+
const factCountByCategory = {};
|
|
8896
|
+
for (const fact of parsed.facts) {
|
|
8897
|
+
factCountByCategory[fact.category] = (factCountByCategory[fact.category] ?? 0) + 1;
|
|
8898
|
+
}
|
|
8899
|
+
const starts = parsed.compartments.map((c) => c.startMessage);
|
|
8900
|
+
const ends = parsed.compartments.map((c) => c.endMessage);
|
|
8901
|
+
let gaps = 0;
|
|
8902
|
+
let overlaps = 0;
|
|
8903
|
+
for (let i = 1;i < parsed.compartments.length; i++) {
|
|
8904
|
+
const prev = parsed.compartments[i - 1];
|
|
8905
|
+
const curr = parsed.compartments[i];
|
|
8906
|
+
if (curr.startMessage > prev.endMessage + 1)
|
|
8907
|
+
gaps += 1;
|
|
8908
|
+
else if (curr.startMessage <= prev.endMessage)
|
|
8909
|
+
overlaps += 1;
|
|
8910
|
+
}
|
|
8911
|
+
return {
|
|
8912
|
+
compartmentCount: parsed.compartments.length,
|
|
8913
|
+
minStart: starts.length > 0 ? Math.min(...starts) : null,
|
|
8914
|
+
maxEnd: ends.length > 0 ? Math.max(...ends) : null,
|
|
8915
|
+
unprocessedFrom: parsed.unprocessedFrom,
|
|
8916
|
+
factCountByCategory,
|
|
8917
|
+
userObservationCount: parsed.userObservations.length,
|
|
8918
|
+
ordinalGapCount: gaps,
|
|
8919
|
+
ordinalOverlapCount: overlaps
|
|
8920
|
+
};
|
|
8921
|
+
} catch (error) {
|
|
8922
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
8923
|
+
}
|
|
8924
|
+
}
|
|
8843
8925
|
function collectHistorianDumps() {
|
|
8844
8926
|
const dir = join7(tmpdir2(), "magic-context-historian");
|
|
8845
8927
|
if (!existsSync6(dir)) {
|
|
@@ -8855,16 +8937,49 @@ function collectHistorianDumps() {
|
|
|
8855
8937
|
};
|
|
8856
8938
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
8857
8939
|
const now = Date.now();
|
|
8858
|
-
const recent = entries.slice(0,
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8940
|
+
const recent = entries.slice(0, 5).map((entry) => {
|
|
8941
|
+
const meta = parseHistorianDumpMeta(join7(dir, entry.name));
|
|
8942
|
+
const summary = {
|
|
8943
|
+
name: entry.name,
|
|
8944
|
+
ageMinutes: Math.round((now - entry.mtime) / 60000),
|
|
8945
|
+
sizeKb: entry.sizeKb
|
|
8946
|
+
};
|
|
8947
|
+
if ("error" in meta) {
|
|
8948
|
+
summary.parseError = meta.error;
|
|
8949
|
+
} else {
|
|
8950
|
+
summary.meta = meta;
|
|
8951
|
+
}
|
|
8952
|
+
return summary;
|
|
8953
|
+
});
|
|
8863
8954
|
return { dir, count: entries.length, recent };
|
|
8864
8955
|
} catch {
|
|
8865
8956
|
return { dir, count: 0, recent: [] };
|
|
8866
8957
|
}
|
|
8867
8958
|
}
|
|
8959
|
+
function collectHistorianFailures(storageDirPath) {
|
|
8960
|
+
const contextDbPath = join7(storageDirPath, "context.db");
|
|
8961
|
+
if (!existsSync6(contextDbPath))
|
|
8962
|
+
return [];
|
|
8963
|
+
let db = null;
|
|
8964
|
+
try {
|
|
8965
|
+
db = new Database(contextDbPath, { readonly: true });
|
|
8966
|
+
const rows = db.prepare("SELECT session_id, historian_failure_count, historian_last_error, historian_last_failure_at FROM session_meta WHERE historian_failure_count > 0 ORDER BY historian_last_failure_at DESC LIMIT 10").all();
|
|
8967
|
+
return rows.map((row) => {
|
|
8968
|
+
const sessionId = typeof row.session_id === "string" ? row.session_id : "<unknown>";
|
|
8969
|
+
const failureCount = typeof row.historian_failure_count === "number" ? row.historian_failure_count : 0;
|
|
8970
|
+
const rawError = typeof row.historian_last_error === "string" ? row.historian_last_error : "";
|
|
8971
|
+
const lastAt = typeof row.historian_last_failure_at === "number" ? new Date(row.historian_last_failure_at).toISOString() : "";
|
|
8972
|
+
const lastError = sanitizeString(rawError.replace(/\s+/g, " ").trim().slice(0, 400));
|
|
8973
|
+
return { sessionId, failureCount, lastError, lastFailureAt: lastAt };
|
|
8974
|
+
});
|
|
8975
|
+
} catch {
|
|
8976
|
+
return [];
|
|
8977
|
+
} finally {
|
|
8978
|
+
try {
|
|
8979
|
+
db?.close();
|
|
8980
|
+
} catch {}
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8868
8983
|
async function collectDiagnostics() {
|
|
8869
8984
|
const pluginVersion = getSelfVersion();
|
|
8870
8985
|
const configPaths = detectConfigPaths();
|
|
@@ -8907,7 +9022,8 @@ async function collectDiagnostics() {
|
|
|
8907
9022
|
exists: existsSync6(logPath),
|
|
8908
9023
|
sizeKb: Math.round(logFileSize / 1024)
|
|
8909
9024
|
},
|
|
8910
|
-
historianDumps: collectHistorianDumps()
|
|
9025
|
+
historianDumps: collectHistorianDumps(),
|
|
9026
|
+
historianFailures: collectHistorianFailures(storageDirPath)
|
|
8911
9027
|
};
|
|
8912
9028
|
}
|
|
8913
9029
|
function formatBytes(bytes) {
|
|
@@ -8976,9 +9092,14 @@ function renderDiagnosticsMarkdown(report) {
|
|
|
8976
9092
|
"```",
|
|
8977
9093
|
"",
|
|
8978
9094
|
"### Historian dumps",
|
|
9095
|
+
"(Metadata only — XML content is not included in this report.)",
|
|
8979
9096
|
"```json",
|
|
8980
9097
|
JSON.stringify(historianDumps, null, 2),
|
|
8981
9098
|
"```",
|
|
9099
|
+
"",
|
|
9100
|
+
"### Historian failures (session_meta)",
|
|
9101
|
+
report.historianFailures.length === 0 ? "_No sessions with historian failures._" : ["```json", JSON.stringify(report.historianFailures, null, 2), "```"].join(`
|
|
9102
|
+
`),
|
|
8982
9103
|
"",
|
|
8983
9104
|
"### Log file",
|
|
8984
9105
|
`- Path: ${sanitizeString(report.logFile.path)}`,
|
|
@@ -9022,11 +9143,36 @@ function formatTimestamp(date) {
|
|
|
9022
9143
|
pad(date.getSeconds())
|
|
9023
9144
|
].join("");
|
|
9024
9145
|
}
|
|
9146
|
+
var HISTORIAN_LOG_PATTERNS = [
|
|
9147
|
+
/historian failure:/,
|
|
9148
|
+
/historian failure recorded:/,
|
|
9149
|
+
/historian prompt failed:/,
|
|
9150
|
+
/## Historian alert/,
|
|
9151
|
+
/historian alert suppressed/,
|
|
9152
|
+
/EMERGENCY: aborting session/,
|
|
9153
|
+
/historian: prompt attempt \d+ failed:/
|
|
9154
|
+
];
|
|
9155
|
+
function isHistorianLogLine(line) {
|
|
9156
|
+
return HISTORIAN_LOG_PATTERNS.some((rx) => rx.test(line));
|
|
9157
|
+
}
|
|
9158
|
+
function extractHistorianFailureLines(sanitized, limit = 30) {
|
|
9159
|
+
const matches = [];
|
|
9160
|
+
const lines = sanitized.split(/\r?\n/);
|
|
9161
|
+
for (let i = lines.length - 1;i >= 0 && matches.length < limit; i -= 1) {
|
|
9162
|
+
if (isHistorianLogLine(lines[i])) {
|
|
9163
|
+
matches.push(lines[i]);
|
|
9164
|
+
}
|
|
9165
|
+
}
|
|
9166
|
+
return matches.reverse();
|
|
9167
|
+
}
|
|
9025
9168
|
async function bundleIssueReport(report, description, _title) {
|
|
9026
|
-
const LOG_TAIL_LINES =
|
|
9169
|
+
const LOG_TAIL_LINES = 400;
|
|
9027
9170
|
const logLines = report.logFile.exists ? readFileSync5(report.logFile.path, "utf-8").split(/\r?\n/) : [];
|
|
9028
9171
|
const recentLog = sanitizeLogContent(logLines.slice(-LOG_TAIL_LINES).join(`
|
|
9029
9172
|
`)).trim();
|
|
9173
|
+
const historianScanWindow = sanitizeLogContent(logLines.slice(-4000).join(`
|
|
9174
|
+
`));
|
|
9175
|
+
const historianFailureLines = extractHistorianFailureLines(historianScanWindow, 30);
|
|
9030
9176
|
const configBody = JSON.stringify(report.magicContextConfig.flags, null, 2);
|
|
9031
9177
|
const sanitizedConfigPath = report.configPaths.magicContextConfig.replace(homedir5(), "~");
|
|
9032
9178
|
const bodyMarkdown = [
|
|
@@ -9047,6 +9193,11 @@ async function bundleIssueReport(report, description, _title) {
|
|
|
9047
9193
|
"",
|
|
9048
9194
|
"## Diagnostics",
|
|
9049
9195
|
renderDiagnosticsMarkdown(report),
|
|
9196
|
+
"",
|
|
9197
|
+
"## Historian failure signals (log, sanitized)",
|
|
9198
|
+
historianFailureLines.length === 0 ? "_No historian failure log lines found in recent history._" : ["```", historianFailureLines.join(`
|
|
9199
|
+
`), "```"].join(`
|
|
9200
|
+
`),
|
|
9050
9201
|
"",
|
|
9051
9202
|
`## Log (last ${LOG_TAIL_LINES} lines, sanitized)`,
|
|
9052
9203
|
"```",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-meta-persisted.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/storage-meta-persisted.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"storage-meta-persisted.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/storage-meta-persisted.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compartment-runner-incremental.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/compartment-runner-incremental.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAoBxE,+DAA+D;AAC/D,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"compartment-runner-incremental.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/compartment-runner-incremental.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAoBxE,+DAA+D;AAC/D,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoQpF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getOrCreateSessionMeta, getTopNBySize } from "../../features/magic-context/storage";
|
|
1
|
+
import { getOrCreateSessionMeta, type getTopNBySize } from "../../features/magic-context/storage";
|
|
2
2
|
import type { ContextUsage, SessionMeta, TagEntry } from "../../features/magic-context/types";
|
|
3
3
|
type ContextDatabase = Parameters<typeof getOrCreateSessionMeta>[0];
|
|
4
4
|
export type ContextNudge = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nudger.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/nudger.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,EAGtB,aAAa,
|
|
1
|
+
{"version":3,"file":"nudger.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/nudger.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,EAGtB,KAAK,aAAa,EAErB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAY9F,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAC/D,eAAO,MAAM,2BAA2B,QAAgB,CAAC;AA+BzD,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,yBAAyB,EAAE,MAAM,CAAC;IAClC,4BAA4B,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACvF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C,IAIO,WAAW,MAAM,EACjB,cAAc,YAAY,EAC1B,IAAI,eAAe,EACnB,QAAQ,OAAO,aAAa,EAC5B,gBAAgB,QAAQ,EAAE,EAC1B,wBAAwB,MAAM,EAC9B,uBAAuB,WAAW,KACnC,YAAY,GAAG,IAAI,CAyIzB"}
|
package/dist/index.js
CHANGED
|
@@ -15615,6 +15615,77 @@ var init_data_path = () => {};
|
|
|
15615
15615
|
function getErrorMessage(error48) {
|
|
15616
15616
|
return error48 instanceof Error ? error48.message : String(error48);
|
|
15617
15617
|
}
|
|
15618
|
+
function readString(value) {
|
|
15619
|
+
if (typeof value === "string" && value.length > 0)
|
|
15620
|
+
return value;
|
|
15621
|
+
if (typeof value === "number")
|
|
15622
|
+
return String(value);
|
|
15623
|
+
return;
|
|
15624
|
+
}
|
|
15625
|
+
function clip(value, max) {
|
|
15626
|
+
if (value.length <= max)
|
|
15627
|
+
return value;
|
|
15628
|
+
return `${value.slice(0, max)}\u2026`;
|
|
15629
|
+
}
|
|
15630
|
+
function describeError(error48) {
|
|
15631
|
+
const stringForm = clip(safeString(error48), 400);
|
|
15632
|
+
if (!(error48 instanceof Error) && !(error48 && typeof error48 === "object")) {
|
|
15633
|
+
return {
|
|
15634
|
+
name: typeof error48,
|
|
15635
|
+
message: "",
|
|
15636
|
+
stringForm,
|
|
15637
|
+
brief: stringForm || "<empty>"
|
|
15638
|
+
};
|
|
15639
|
+
}
|
|
15640
|
+
const obj = error48;
|
|
15641
|
+
const nameFromField = readString(obj.name);
|
|
15642
|
+
const nameFromCtor = error48?.constructor?.name;
|
|
15643
|
+
const name = nameFromField ?? nameFromCtor ?? "Error";
|
|
15644
|
+
const message = readString(obj.message) ?? "";
|
|
15645
|
+
const status = readString(obj.status) ?? readString(obj.statusCode);
|
|
15646
|
+
const code = readString(obj.code);
|
|
15647
|
+
let causeName;
|
|
15648
|
+
const cause = obj.cause;
|
|
15649
|
+
if (cause && typeof cause === "object") {
|
|
15650
|
+
const causeRecord = cause;
|
|
15651
|
+
causeName = readString(causeRecord.name) ?? cause.constructor?.name;
|
|
15652
|
+
}
|
|
15653
|
+
const stack = readString(obj.stack);
|
|
15654
|
+
const stackHead = stack ? stack.split(`
|
|
15655
|
+
`).slice(0, 4).map((l) => l.trim()).filter((l) => l.length > 0).join(" | ") : undefined;
|
|
15656
|
+
const briefParts = [];
|
|
15657
|
+
if (name)
|
|
15658
|
+
briefParts.push(name);
|
|
15659
|
+
if (message)
|
|
15660
|
+
briefParts.push(`message="${clip(message, 200)}"`);
|
|
15661
|
+
if (status)
|
|
15662
|
+
briefParts.push(`status=${status}`);
|
|
15663
|
+
if (code)
|
|
15664
|
+
briefParts.push(`code=${code}`);
|
|
15665
|
+
if (causeName)
|
|
15666
|
+
briefParts.push(`cause=${causeName}`);
|
|
15667
|
+
if (!message && stringForm && stringForm !== name) {
|
|
15668
|
+
briefParts.push(`str="${clip(stringForm, 200)}"`);
|
|
15669
|
+
}
|
|
15670
|
+
const brief = briefParts.join(" ") || stringForm || name;
|
|
15671
|
+
return {
|
|
15672
|
+
name,
|
|
15673
|
+
message,
|
|
15674
|
+
...status ? { status } : {},
|
|
15675
|
+
...code ? { code } : {},
|
|
15676
|
+
...causeName ? { causeName } : {},
|
|
15677
|
+
...stackHead ? { stackHead } : {},
|
|
15678
|
+
stringForm,
|
|
15679
|
+
brief
|
|
15680
|
+
};
|
|
15681
|
+
}
|
|
15682
|
+
function safeString(value) {
|
|
15683
|
+
try {
|
|
15684
|
+
return String(value);
|
|
15685
|
+
} catch {
|
|
15686
|
+
return "<unstringifiable>";
|
|
15687
|
+
}
|
|
15688
|
+
}
|
|
15618
15689
|
|
|
15619
15690
|
// src/features/magic-context/memory/storage-memory-embeddings.ts
|
|
15620
15691
|
function isEmbeddingBlob(value) {
|
|
@@ -151183,7 +151254,10 @@ function incrementHistorianFailure(db, sessionId, error48) {
|
|
|
151183
151254
|
db.transaction(() => {
|
|
151184
151255
|
ensureSessionMetaRow(db, sessionId);
|
|
151185
151256
|
const current = getHistorianFailureState(db, sessionId);
|
|
151186
|
-
|
|
151257
|
+
const nextCount = current.failureCount + 1;
|
|
151258
|
+
db.prepare("UPDATE session_meta SET historian_failure_count = ?, historian_last_error = ?, historian_last_failure_at = ? WHERE session_id = ?").run(nextCount, error48, Date.now(), sessionId);
|
|
151259
|
+
const reason = error48.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
151260
|
+
sessionLog(sessionId, `historian failure recorded: count=${nextCount} reason="${reason}"`);
|
|
151187
151261
|
})();
|
|
151188
151262
|
}
|
|
151189
151263
|
function clearHistorianFailureState(db, sessionId) {
|
|
@@ -151267,6 +151341,7 @@ function removeStrippedPlaceholderId(db, sessionId, messageId) {
|
|
|
151267
151341
|
return true;
|
|
151268
151342
|
}
|
|
151269
151343
|
var init_storage_meta_persisted = __esm(() => {
|
|
151344
|
+
init_logger();
|
|
151270
151345
|
init_storage_meta_shared();
|
|
151271
151346
|
});
|
|
151272
151347
|
|
|
@@ -152157,14 +152232,12 @@ async function runHistorianPrompt(args) {
|
|
|
152157
152232
|
const dumpPath = dumpHistorianResponse(parentSessionId, dumpLabel ?? "historian-response", result);
|
|
152158
152233
|
return { ok: true, result, dumpPath };
|
|
152159
152234
|
} catch (modelError) {
|
|
152160
|
-
const
|
|
152161
|
-
|
|
152162
|
-
|
|
152163
|
-
|
|
152164
|
-
|
|
152165
|
-
|
|
152166
|
-
});
|
|
152167
|
-
return { ok: false, error: `Historian failed while processing this session: ${modelMsg}` };
|
|
152235
|
+
const desc = describeError(modelError);
|
|
152236
|
+
sessionLog(parentSessionId, `historian prompt failed: ${desc.brief} promptLength=${prompt.length}${desc.stackHead ? ` stackHead="${desc.stackHead}"` : ""}`);
|
|
152237
|
+
return {
|
|
152238
|
+
ok: false,
|
|
152239
|
+
error: `Historian failed while processing this session: ${desc.brief}`
|
|
152240
|
+
};
|
|
152168
152241
|
} finally {
|
|
152169
152242
|
if (agentSessionId) {
|
|
152170
152243
|
await client.session.delete({ path: { id: agentSessionId }, query: { directory: sessionDirectory } }).catch((e) => {
|
|
@@ -154317,6 +154390,7 @@ async function runCompartmentAgent(deps) {
|
|
|
154317
154390
|
const priorFacts = existingFacts;
|
|
154318
154391
|
const existingValidationError = validateStoredCompartments(priorCompartments);
|
|
154319
154392
|
if (existingValidationError) {
|
|
154393
|
+
sessionLog(sessionId, `historian failure: source=existing-validation reason="${existingValidationError}"`);
|
|
154320
154394
|
await notifyHistorianIssue(`## Historian alert
|
|
154321
154395
|
|
|
154322
154396
|
Historian skipped this session because existing stored compartments are invalid: ${existingValidationError}
|
|
@@ -154335,6 +154409,7 @@ No new compartments or facts were written. Rebuild or clear the broken compartme
|
|
|
154335
154409
|
}
|
|
154336
154410
|
const chunkCoverageError = validateChunkCoverage(chunk);
|
|
154337
154411
|
if (chunkCoverageError) {
|
|
154412
|
+
sessionLog(sessionId, `historian failure: source=chunk-coverage reason="${chunkCoverageError}" chunkRange=${chunk.startIndex}-${chunk.endIndex}`);
|
|
154338
154413
|
await notifyHistorianIssue(`## Historian alert
|
|
154339
154414
|
|
|
154340
154415
|
Historian skipped this session because the raw chunk could not be represented safely: ${chunkCoverageError}
|
|
@@ -154370,6 +154445,7 @@ ${chunk.text}`);
|
|
|
154370
154445
|
twoPass: deps.historianTwoPass
|
|
154371
154446
|
});
|
|
154372
154447
|
if (!validatedPass.ok) {
|
|
154448
|
+
sessionLog(sessionId, `historian failure: source=validation reason="${validatedPass.error}" chunkRange=${chunk.startIndex}-${chunk.endIndex} fallbackModel=${deps.fallbackModelId ?? "<none>"} twoPass=${deps.historianTwoPass ? "true" : "false"}`);
|
|
154373
154449
|
incrementHistorianFailure(db, sessionId, validatedPass.error);
|
|
154374
154450
|
await notifyHistorianIssue(`## Historian alert
|
|
154375
154451
|
|
|
@@ -154381,6 +154457,8 @@ No new compartments or facts were written. Check the historian model/output and
|
|
|
154381
154457
|
const newCompartments = validatedPass.compartments;
|
|
154382
154458
|
const lastNewEnd = newCompartments[newCompartments.length - 1]?.endMessage ?? 0;
|
|
154383
154459
|
if (lastNewEnd + 1 <= offset) {
|
|
154460
|
+
sessionLog(sessionId, `historian failure: source=no-progress reason="historian returned compartments that did not advance past raw message ${offset - 1}" newCompartmentCount=${newCompartments.length} lastNewEnd=${lastNewEnd} priorEnd=${offset - 1}`);
|
|
154461
|
+
incrementHistorianFailure(db, sessionId, `no forward progress beyond raw message ${offset - 1}`);
|
|
154384
154462
|
await notifyHistorianIssue(`## Historian alert
|
|
154385
154463
|
|
|
154386
154464
|
Historian returned compartments that made no forward progress beyond raw message ${offset - 1}.
|
|
@@ -154434,11 +154512,13 @@ No new compartments or facts were written. Check the historian model/output and
|
|
|
154434
154512
|
}
|
|
154435
154513
|
}
|
|
154436
154514
|
} catch (error48) {
|
|
154437
|
-
const
|
|
154515
|
+
const desc = describeError(error48);
|
|
154516
|
+
sessionLog(sessionId, `historian failure: source=exception ${desc.brief}${desc.stackHead ? ` stackHead="${desc.stackHead}"` : ""}`);
|
|
154438
154517
|
if (!issueNotified) {
|
|
154518
|
+
incrementHistorianFailure(db, sessionId, desc.brief);
|
|
154439
154519
|
await notifyHistorianIssue(`## Historian alert
|
|
154440
154520
|
|
|
154441
|
-
Historian failed unexpectedly: ${
|
|
154521
|
+
Historian failed unexpectedly: ${desc.brief}
|
|
154442
154522
|
|
|
154443
154523
|
No new compartments or facts were written. Check the historian model/output and try again.`);
|
|
154444
154524
|
}
|
|
@@ -1,2 +1,32 @@
|
|
|
1
1
|
export declare function getErrorMessage(error: unknown): string;
|
|
2
|
+
/**
|
|
3
|
+
* Produce a rich, safe-to-log description of any thrown value.
|
|
4
|
+
*
|
|
5
|
+
* Motivated by SDK errors whose `.message` is empty while `.name`/`toString()`
|
|
6
|
+
* carry the actual signal (e.g. `NotFoundError` with no message on OpenCode
|
|
7
|
+
* session-delete races). Using {@link getErrorMessage} alone erases that signal.
|
|
8
|
+
*
|
|
9
|
+
* Captures:
|
|
10
|
+
* - `name` from the Error (defaults to `constructor.name`)
|
|
11
|
+
* - `message` (may be empty)
|
|
12
|
+
* - first few stack frames
|
|
13
|
+
* - `String(error)` so objects and custom toString surfaces are visible
|
|
14
|
+
* - Common HTTP-shape fields (`status`, `statusCode`, `code`)
|
|
15
|
+
* - `cause` chain summary (first level only)
|
|
16
|
+
*
|
|
17
|
+
* Returns a compact, single-line-friendly string suitable for log lines,
|
|
18
|
+
* plus a structured object for callers that want individual fields.
|
|
19
|
+
*/
|
|
20
|
+
export interface ErrorDescription {
|
|
21
|
+
name: string;
|
|
22
|
+
message: string;
|
|
23
|
+
status?: string;
|
|
24
|
+
code?: string;
|
|
25
|
+
causeName?: string;
|
|
26
|
+
stackHead?: string;
|
|
27
|
+
stringForm: string;
|
|
28
|
+
/** Best short summary for human-readable logs. Never empty. */
|
|
29
|
+
brief: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function describeError(error: unknown): ErrorDescription;
|
|
2
32
|
//# sourceMappingURL=error-message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-message.d.ts","sourceRoot":"","sources":["../../src/shared/error-message.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEtD"}
|
|
1
|
+
{"version":3,"file":"error-message.d.ts","sourceRoot":"","sources":["../../src/shared/error-message.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEtD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;CACjB;AAaD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CA6D9D"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { describeError, getErrorMessage } from "./error-message";
|
|
3
|
+
|
|
4
|
+
describe("describeError", () => {
|
|
5
|
+
it("extracts name and message from standard Error", () => {
|
|
6
|
+
const err = new TypeError("boom");
|
|
7
|
+
const desc = describeError(err);
|
|
8
|
+
expect(desc.name).toBe("TypeError");
|
|
9
|
+
expect(desc.message).toBe("boom");
|
|
10
|
+
expect(desc.brief).toContain("TypeError");
|
|
11
|
+
expect(desc.brief).toContain('message="boom"');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("surfaces class name when message is empty (NotFoundError case)", () => {
|
|
15
|
+
class NotFoundError extends Error {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("");
|
|
18
|
+
this.name = "NotFoundError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const err = new NotFoundError();
|
|
22
|
+
const desc = describeError(err);
|
|
23
|
+
expect(desc.name).toBe("NotFoundError");
|
|
24
|
+
expect(desc.message).toBe("");
|
|
25
|
+
// brief must still carry a useful signal even with empty message
|
|
26
|
+
expect(desc.brief).toContain("NotFoundError");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("falls back to constructor.name when .name is missing", () => {
|
|
30
|
+
// Simulate an SDK-shaped object where .name is an empty string
|
|
31
|
+
const err = Object.assign(new Error("x"), { name: "" });
|
|
32
|
+
const desc = describeError(err);
|
|
33
|
+
// Falls back to constructor.name ("Error")
|
|
34
|
+
expect(desc.name).toBe("Error");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("extracts status/code fields from HTTP-style errors", () => {
|
|
38
|
+
const err = Object.assign(new Error("not found"), {
|
|
39
|
+
status: 404,
|
|
40
|
+
code: "ENOTFOUND",
|
|
41
|
+
});
|
|
42
|
+
const desc = describeError(err);
|
|
43
|
+
expect(desc.status).toBe("404");
|
|
44
|
+
expect(desc.code).toBe("ENOTFOUND");
|
|
45
|
+
expect(desc.brief).toContain("status=404");
|
|
46
|
+
expect(desc.brief).toContain("code=ENOTFOUND");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("captures first stack frames in stackHead", () => {
|
|
50
|
+
const err = new Error("with stack");
|
|
51
|
+
const desc = describeError(err);
|
|
52
|
+
expect(desc.stackHead).toBeDefined();
|
|
53
|
+
expect(desc.stackHead?.length).toBeGreaterThan(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("handles non-Error thrown values", () => {
|
|
57
|
+
const desc = describeError("plain string");
|
|
58
|
+
expect(desc.brief).toContain("plain string");
|
|
59
|
+
expect(desc.stringForm).toContain("plain string");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("handles objects without .message", () => {
|
|
63
|
+
const desc = describeError({ name: "WeirdError" });
|
|
64
|
+
expect(desc.name).toBe("WeirdError");
|
|
65
|
+
expect(desc.message).toBe("");
|
|
66
|
+
expect(desc.brief).toContain("WeirdError");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("handles undefined/null", () => {
|
|
70
|
+
expect(describeError(undefined).brief).toBeTruthy();
|
|
71
|
+
expect(describeError(null).brief).toBeTruthy();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("surfaces cause name when present", () => {
|
|
75
|
+
const cause = new TypeError("inner");
|
|
76
|
+
const outer = new Error("outer");
|
|
77
|
+
(outer as unknown as { cause: unknown }).cause = cause;
|
|
78
|
+
const desc = describeError(outer);
|
|
79
|
+
expect(desc.causeName).toBe("TypeError");
|
|
80
|
+
expect(desc.brief).toContain("cause=TypeError");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("clips very long messages in brief", () => {
|
|
84
|
+
const long = "x".repeat(1000);
|
|
85
|
+
const err = new Error(long);
|
|
86
|
+
const desc = describeError(err);
|
|
87
|
+
expect(desc.brief.length).toBeLessThan(500);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("getErrorMessage still works as before", () => {
|
|
91
|
+
expect(getErrorMessage(new Error("foo"))).toBe("foo");
|
|
92
|
+
expect(getErrorMessage("bar")).toBe("bar");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -1,3 +1,115 @@
|
|
|
1
1
|
export function getErrorMessage(error: unknown): string {
|
|
2
2
|
return error instanceof Error ? error.message : String(error);
|
|
3
3
|
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Produce a rich, safe-to-log description of any thrown value.
|
|
7
|
+
*
|
|
8
|
+
* Motivated by SDK errors whose `.message` is empty while `.name`/`toString()`
|
|
9
|
+
* carry the actual signal (e.g. `NotFoundError` with no message on OpenCode
|
|
10
|
+
* session-delete races). Using {@link getErrorMessage} alone erases that signal.
|
|
11
|
+
*
|
|
12
|
+
* Captures:
|
|
13
|
+
* - `name` from the Error (defaults to `constructor.name`)
|
|
14
|
+
* - `message` (may be empty)
|
|
15
|
+
* - first few stack frames
|
|
16
|
+
* - `String(error)` so objects and custom toString surfaces are visible
|
|
17
|
+
* - Common HTTP-shape fields (`status`, `statusCode`, `code`)
|
|
18
|
+
* - `cause` chain summary (first level only)
|
|
19
|
+
*
|
|
20
|
+
* Returns a compact, single-line-friendly string suitable for log lines,
|
|
21
|
+
* plus a structured object for callers that want individual fields.
|
|
22
|
+
*/
|
|
23
|
+
export interface ErrorDescription {
|
|
24
|
+
name: string;
|
|
25
|
+
message: string;
|
|
26
|
+
status?: string;
|
|
27
|
+
code?: string;
|
|
28
|
+
causeName?: string;
|
|
29
|
+
stackHead?: string;
|
|
30
|
+
stringForm: string;
|
|
31
|
+
/** Best short summary for human-readable logs. Never empty. */
|
|
32
|
+
brief: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readString(value: unknown): string | undefined {
|
|
36
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
37
|
+
if (typeof value === "number") return String(value);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function clip(value: string, max: number): string {
|
|
42
|
+
if (value.length <= max) return value;
|
|
43
|
+
return `${value.slice(0, max)}…`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function describeError(error: unknown): ErrorDescription {
|
|
47
|
+
const stringForm = clip(safeString(error), 400);
|
|
48
|
+
|
|
49
|
+
if (!(error instanceof Error) && !(error && typeof error === "object")) {
|
|
50
|
+
return {
|
|
51
|
+
name: typeof error,
|
|
52
|
+
message: "",
|
|
53
|
+
stringForm,
|
|
54
|
+
brief: stringForm || "<empty>",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const obj = error as Record<string, unknown>;
|
|
59
|
+
const nameFromField = readString(obj.name);
|
|
60
|
+
const nameFromCtor = error?.constructor?.name;
|
|
61
|
+
const name = nameFromField ?? nameFromCtor ?? "Error";
|
|
62
|
+
|
|
63
|
+
const message = readString(obj.message) ?? "";
|
|
64
|
+
const status = readString(obj.status) ?? readString(obj.statusCode);
|
|
65
|
+
const code = readString(obj.code);
|
|
66
|
+
|
|
67
|
+
let causeName: string | undefined;
|
|
68
|
+
const cause = obj.cause;
|
|
69
|
+
if (cause && typeof cause === "object") {
|
|
70
|
+
const causeRecord = cause as Record<string, unknown>;
|
|
71
|
+
causeName =
|
|
72
|
+
readString(causeRecord.name) ??
|
|
73
|
+
(cause as { constructor?: { name?: string } }).constructor?.name;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stack = readString(obj.stack);
|
|
77
|
+
const stackHead = stack
|
|
78
|
+
? stack
|
|
79
|
+
.split("\n")
|
|
80
|
+
.slice(0, 4)
|
|
81
|
+
.map((l) => l.trim())
|
|
82
|
+
.filter((l) => l.length > 0)
|
|
83
|
+
.join(" | ")
|
|
84
|
+
: undefined;
|
|
85
|
+
|
|
86
|
+
const briefParts: string[] = [];
|
|
87
|
+
if (name) briefParts.push(name);
|
|
88
|
+
if (message) briefParts.push(`message="${clip(message, 200)}"`);
|
|
89
|
+
if (status) briefParts.push(`status=${status}`);
|
|
90
|
+
if (code) briefParts.push(`code=${code}`);
|
|
91
|
+
if (causeName) briefParts.push(`cause=${causeName}`);
|
|
92
|
+
if (!message && stringForm && stringForm !== name) {
|
|
93
|
+
briefParts.push(`str="${clip(stringForm, 200)}"`);
|
|
94
|
+
}
|
|
95
|
+
const brief = briefParts.join(" ") || stringForm || name;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
name,
|
|
99
|
+
message,
|
|
100
|
+
...(status ? { status } : {}),
|
|
101
|
+
...(code ? { code } : {}),
|
|
102
|
+
...(causeName ? { causeName } : {}),
|
|
103
|
+
...(stackHead ? { stackHead } : {}),
|
|
104
|
+
stringForm,
|
|
105
|
+
brief,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function safeString(value: unknown): string {
|
|
110
|
+
try {
|
|
111
|
+
return String(value);
|
|
112
|
+
} catch {
|
|
113
|
+
return "<unstringifiable>";
|
|
114
|
+
}
|
|
115
|
+
}
|