@mxalbert/context-mode 2.0.0 → 2.0.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +4 -14
- package/build/adapters/opencode/plugin.d.ts +2 -0
- package/build/adapters/opencode/plugin.js +92 -1
- package/build/db-base.d.ts +16 -0
- package/build/db-base.js +29 -3
- package/build/store.js +250 -39
- package/cli.bundle.mjs +171 -171
- package/configs/antigravity-cli/plugin.json +1 -1
- package/configs/copilot-cli/.github/plugin/plugin.json +1 -1
- package/hooks/session-db.bundle.mjs +7 -7
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +131 -131
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "2.0.
|
|
9
|
+
"version": "2.0.2"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "context-mode",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
16
|
-
"version": "2.0.
|
|
16
|
+
"version": "2.0.2",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Mert Koseoğlu"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "2.0.
|
|
6
|
+
"version": "2.0.2",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxalbert/context-mode",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/README.md
CHANGED
|
@@ -483,13 +483,7 @@ Full configs: [`configs/cursor/hooks.json`](configs/cursor/hooks.json) | [`confi
|
|
|
483
483
|
|
|
484
484
|
The `plugin` entry registers all 11 `ctx_*` tools natively and enables hooks — OpenCode calls context-mode's TypeScript plugin in-process, so there is no redundant stdio MCP child per session.
|
|
485
485
|
|
|
486
|
-
2.
|
|
487
|
-
|
|
488
|
-
```bash
|
|
489
|
-
cp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.md
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
The plugin already injects its routing guidance (which tools to use, which commands are blocked and why) into the system prompt on every model dispatch — on both opencode v1 and v2. The copy is only useful if you want the same guidance also visible as native project rules (`AGENTS.md`) for sessions without the plugin. Your own `AGENTS.md`/`CLAUDE.md`/`CONTEXT.md` rules are captured automatically for session continuity — no copy needed.
|
|
486
|
+
2. **No AGENTS.md copy needed.** The plugin injects the FULL routing mandate — Think in Code, BLOCKED/REDIRECTED commands, tool selection, memory tables (the complete [`configs/opencode/AGENTS.md`](configs/opencode/AGENTS.md) content, verbatim) — into the system prompt on every model dispatch. An existing copy stays harmless: OpenCode loads a project `AGENTS.md` natively as active project rules, and the plugin auto-skips its own injection when the project file's content matches the shipped template (normalized full-content comparison — an edited copy keeps getting re-injected, deliberately, so your edits don't get shadowed by the stale shipped version).
|
|
493
487
|
|
|
494
488
|
3. Restart OpenCode.
|
|
495
489
|
|
|
@@ -497,7 +491,7 @@ Full configs: [`configs/cursor/hooks.json`](configs/cursor/hooks.json) | [`confi
|
|
|
497
491
|
|
|
498
492
|
**Upgrade note:** If an existing config has BOTH `plugin: ["context-mode"]` AND `mcp.context-mode`, OpenCode will register zero `ctx_*` tools — the plugin path correctly suppresses MCP duplicates, but the legacy MCP entry confuses the loader. Run `context-mode upgrade` to remove the legacy `mcp.context-mode` entry; your other MCP servers are preserved. v1.0.140+ emits a stderr diagnostic with the same guidance when this happens.
|
|
499
493
|
|
|
500
|
-
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after
|
|
494
|
+
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`, and the plugin injects the full routing guidance (condensed block + complete AGENTS.md mandate) into the system prompt on every model dispatch — no copied AGENTS.md is required. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
|
|
501
495
|
|
|
502
496
|
> **Note:** OpenCode lacks a real SessionStart hook ([#14808](https://github.com/sst/opencode/issues/14808), [#5409](https://github.com/sst/opencode/issues/5409)). The plugin uses `experimental.chat.system.transform` as a surrogate — it injects both the routing block and resume snapshots into the system prompt. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
|
|
503
497
|
|
|
@@ -523,11 +517,7 @@ Full configs: [`configs/opencode/opencode.json`](configs/opencode/opencode.json)
|
|
|
523
517
|
|
|
524
518
|
The `plugin` entry registers all 11 `ctx_*` tools natively and enables hooks — KiloCode calls context-mode's TypeScript plugin in-process, so there is no redundant stdio MCP child per session.
|
|
525
519
|
|
|
526
|
-
2.
|
|
527
|
-
|
|
528
|
-
```bash
|
|
529
|
-
cp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.md
|
|
530
|
-
```
|
|
520
|
+
2. **No AGENTS.md copy needed.** KiloCode shares the OpenCode plugin architecture — the plugin injects the FULL routing mandate ([`configs/opencode/AGENTS.md`](configs/opencode/AGENTS.md), verbatim) into the system prompt on every model dispatch. An existing copy is harmless (the host loads it natively as project rules) and identical copies are auto-skipped by the plugin's dedupe guard, so the mandate is never injected twice.
|
|
531
521
|
|
|
532
522
|
3. Restart KiloCode.
|
|
533
523
|
|
|
@@ -535,7 +525,7 @@ Full configs: [`configs/opencode/opencode.json`](configs/opencode/opencode.json)
|
|
|
535
525
|
|
|
536
526
|
**Upgrade note:** If an existing config has BOTH `plugin: ["context-mode"]` AND `mcp.context-mode`, KiloCode will register zero `ctx_*` tools — the plugin path correctly suppresses MCP duplicates, but the legacy MCP entry confuses the loader. Run `context-mode upgrade` to remove the legacy `mcp.context-mode` entry; your other MCP servers are preserved. v1.0.140+ emits a stderr diagnostic with the same guidance when this happens.
|
|
537
527
|
|
|
538
|
-
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after
|
|
528
|
+
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`, and the plugin injects the full routing guidance (condensed block + complete AGENTS.md mandate) into the system prompt on every model dispatch — no copied AGENTS.md is required. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
|
|
539
529
|
|
|
540
530
|
> **Note:** KiloCode shares the same plugin architecture as OpenCode, using the OpenCodeAdapter with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). Like OpenCode, it lacks a real SessionStart hook — the plugin uses `experimental.chat.system.transform` as a surrogate. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
|
|
541
531
|
|
|
@@ -182,6 +182,8 @@ interface ChatMessageHookOutput {
|
|
|
182
182
|
message: unknown;
|
|
183
183
|
parts: ChatMessagePart[];
|
|
184
184
|
}
|
|
185
|
+
/** Test-only: point the template loader at a specific path (tests pass a nonexistent path to simulate a missing template). */
|
|
186
|
+
export declare function __setAgentsTemplatePathForTests(path: string | undefined): void;
|
|
185
187
|
declare const ROUTING_MARKERS: string[];
|
|
186
188
|
declare function systemHasRoutingInstructions(system: string[]): boolean;
|
|
187
189
|
/** Test-only: clear process-global activation + log-dedupe state. */
|
|
@@ -75,6 +75,93 @@ function isSyntheticMessage(text) {
|
|
|
75
75
|
const trimmed = text.trim();
|
|
76
76
|
return SYNTHETIC_MESSAGE_PREFIXES.some((p) => trimmed.startsWith(p));
|
|
77
77
|
}
|
|
78
|
+
// ── Full AGENTS.md mandate injection (user decision: opencode must not
|
|
79
|
+
// require the copied AGENTS.md — the injected guidance carries the FULL
|
|
80
|
+
// mandate, not just the condensed block) ─────────────────
|
|
81
|
+
/** Test-only override for the template path (null/undefined → default). */
|
|
82
|
+
let agentsTemplatePathOverride;
|
|
83
|
+
/** Test-only: point the template loader at a specific path (tests pass a nonexistent path to simulate a missing template). */
|
|
84
|
+
export function __setAgentsTemplatePathForTests(path) {
|
|
85
|
+
agentsTemplatePathOverride = path;
|
|
86
|
+
}
|
|
87
|
+
function agentsTemplatePathFor(buildDir) {
|
|
88
|
+
if (agentsTemplatePathOverride !== undefined)
|
|
89
|
+
return agentsTemplatePathOverride;
|
|
90
|
+
// Same buildDir-relative pattern as the routing .mjs islands: the plugin
|
|
91
|
+
// runs from build/adapters/opencode (or src/adapters/opencode under tsx),
|
|
92
|
+
// so ../../.. is the package root where configs/ lives.
|
|
93
|
+
return resolve(buildDir, "..", "..", "..", "configs", "opencode", "AGENTS.md");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Normalize a markdown document for content comparison: strip \r, trim each
|
|
97
|
+
* line, drop empty lines, re-join with "\n". Absorbs CRLF line endings,
|
|
98
|
+
* trailing whitespace, and blank-line differences — content-equal documents
|
|
99
|
+
* normalize identically; ANY edited line does not.
|
|
100
|
+
*/
|
|
101
|
+
function normalizeMarkdownForCompare(content) {
|
|
102
|
+
return content
|
|
103
|
+
.replace(/\r/g, "")
|
|
104
|
+
.split("\n")
|
|
105
|
+
.map((line) => line.trim())
|
|
106
|
+
.filter((line) => line.length > 0)
|
|
107
|
+
.join("\n");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compose the injected guidance: the condensed routing block FIRST
|
|
111
|
+
* (<context_window_protection> + quorum markers), then the FULL AGENTS.md
|
|
112
|
+
* mandate (Think in Code, BLOCKED/REDIRECTED, tool selection, memory
|
|
113
|
+
* tables, ctx commands) appended VERBATIM — the template uses the SAME
|
|
114
|
+
* tool naming (`context-mode_ctx_*`) as the runtime redirect guidance, so
|
|
115
|
+
* no rewrite is needed, and it does NOT contain the condensed block's XML
|
|
116
|
+
* tag, so there is no tag collision.
|
|
117
|
+
*
|
|
118
|
+
* Best-effort: a missing/unreadable template degrades to the condensed
|
|
119
|
+
* block alone (nothing logged — same degrade-honestly pattern; the
|
|
120
|
+
* condensed block alone must keep working).
|
|
121
|
+
*
|
|
122
|
+
* Dedupe guard (evaluated ONCE at setup) — NORMALIZED FULL-CONTENT
|
|
123
|
+
* EQUALITY: the template is skipped ONLY when the project-root AGENTS.md is
|
|
124
|
+
* content-equal to the template after normalization (CRLF/trailing-
|
|
125
|
+
* whitespace/blank-line differences absorbed). Rationale: the v1 host then
|
|
126
|
+
* loads the identical mandate natively, so appending it again would
|
|
127
|
+
* double-inject and waste context. ANY edited copy re-appends the full
|
|
128
|
+
* template DELIBERATELY — even when the copy still carries the old
|
|
129
|
+
* signature line — because an edited copy is no longer guaranteed to match
|
|
130
|
+
* what the plugin injects, and the injected guidance is the authoritative
|
|
131
|
+
* mandate. Unreadable project files fall through and append (harmless
|
|
132
|
+
* duplicate worst-case; never break setup over a diagnostics read).
|
|
133
|
+
*/
|
|
134
|
+
function composeRoutingGuidance(condensedBlock, templatePath, projectDir) {
|
|
135
|
+
let agentsMdContent = "";
|
|
136
|
+
try {
|
|
137
|
+
if (existsSync(templatePath)) {
|
|
138
|
+
const content = readFileSync(templatePath, "utf-8");
|
|
139
|
+
if (content.trim())
|
|
140
|
+
agentsMdContent = content;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
agentsMdContent = ""; // unreadable template — condensed block alone
|
|
145
|
+
}
|
|
146
|
+
if (!agentsMdContent)
|
|
147
|
+
return condensedBlock;
|
|
148
|
+
try {
|
|
149
|
+
const projectAgentsPath = join(projectDir, "AGENTS.md");
|
|
150
|
+
if (existsSync(projectAgentsPath)) {
|
|
151
|
+
const projectAgents = readFileSync(projectAgentsPath, "utf-8");
|
|
152
|
+
if (normalizeMarkdownForCompare(projectAgents) === normalizeMarkdownForCompare(agentsMdContent)) {
|
|
153
|
+
// Content-equal copy → the v1 host loads the identical mandate
|
|
154
|
+
// natively — skip the template. Edited copies re-append above.
|
|
155
|
+
return condensedBlock;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Unreadable project file — fall through and append (harmless duplicate
|
|
161
|
+
// worst-case; never break setup over a diagnostics read).
|
|
162
|
+
}
|
|
163
|
+
return `${condensedBlock}\n\n${agentsMdContent}`;
|
|
164
|
+
}
|
|
78
165
|
// ── Helpers ───────────────────────────────────────────────
|
|
79
166
|
// Quorum markers — must NOT be substrings of each other (#487).
|
|
80
167
|
// Each token uniquely identifies the routing block / context-mode rules
|
|
@@ -368,12 +455,16 @@ async function createPluginRuntime(ctx, projectDirOverride, adapterOptions) {
|
|
|
368
455
|
// sessionID, so we cache it. createToolNamer accepts both "opencode" and
|
|
369
456
|
// "kilo" per hooks/core/tool-naming.mjs:25-26.
|
|
370
457
|
const toolNamer = toolNamingMod.createToolNamer(platform);
|
|
371
|
-
const
|
|
458
|
+
const condensedRoutingBlock = routingBlockMod.createRoutingBlock(toolNamer);
|
|
372
459
|
// Initialize per-process state. We do NOT fabricate a sessionId here —
|
|
373
460
|
// OpenCode/Kilo provide the real `input.sessionID` on every hook, and a
|
|
374
461
|
// process-global UUID would (a) never match prior-session resume rows and
|
|
375
462
|
// (b) collide across multi-session reuse (Mickey / PR #376 root cause).
|
|
376
463
|
const projectDir = projectDirOverride ?? ctx?.directory ?? process.cwd();
|
|
464
|
+
// Compose the FULL injected guidance (condensed block first + the complete
|
|
465
|
+
// AGENTS.md mandate verbatim, subject to the project-root dedupe guard) —
|
|
466
|
+
// evaluated ONCE per process; needs the resolved project dir.
|
|
467
|
+
const routingBlock = composeRoutingGuidance(condensedRoutingBlock, agentsTemplatePathFor(buildDir), projectDir);
|
|
377
468
|
// C2 narrowing: resolve DB path through the canonical helper directly.
|
|
378
469
|
// BaseAdapter no longer exposes getSessionDBPath; the adapter only owns
|
|
379
470
|
// the sessions DIR (per-platform), the helper owns the per-project FILE
|
package/build/db-base.d.ts
CHANGED
|
@@ -185,6 +185,22 @@ export declare function renameCorruptDB(dbPath: string): void;
|
|
|
185
185
|
* error) go to stderr via console.error under this prefix.
|
|
186
186
|
*/
|
|
187
187
|
export declare const DB_LOG_PREFIX = "[context-mode:db]";
|
|
188
|
+
/**
|
|
189
|
+
* Extract the SQLite error code (e.g. "SQLITE_IOERR") from an arbitrary
|
|
190
|
+
* thrown value, or "" when absent. better-sqlite3 and node:sqlite set
|
|
191
|
+
* `code` on SqliteError; bun:sqlite encodes it in the message instead.
|
|
192
|
+
*
|
|
193
|
+
* Exported so store.ts can append the same `[<code>]` context to the
|
|
194
|
+
* tool-visible `[context-mode:store] …` error messages without
|
|
195
|
+
* duplicating the throw-shape handling.
|
|
196
|
+
*/
|
|
197
|
+
export declare function extractErrorCode(err: unknown): string;
|
|
198
|
+
/**
|
|
199
|
+
* Normalize the human-readable message of an arbitrary thrown value.
|
|
200
|
+
* Exported alongside extractErrorCode for store.ts's tool-error
|
|
201
|
+
* enrichment (non-Error shapes included).
|
|
202
|
+
*/
|
|
203
|
+
export declare function errorMessage(err: unknown): string;
|
|
188
204
|
/**
|
|
189
205
|
* Log a swallowed DB failure to stderr with the stable
|
|
190
206
|
* `[context-mode:db]` prefix: operation name, error code, error message,
|
package/build/db-base.js
CHANGED
|
@@ -490,6 +490,15 @@ export function withRetry(fn, delays = [100, 500, 2000]) {
|
|
|
490
490
|
throw err;
|
|
491
491
|
}
|
|
492
492
|
lastError = err instanceof Error ? err : new Error(errorSignature(err));
|
|
493
|
+
if (!(err instanceof Error)) {
|
|
494
|
+
// Preserve the SQLite code (e.g. SQLITE_IOERR) on the normalized
|
|
495
|
+
// Error — non-Error throw shapes ({ code } objects, strings) would
|
|
496
|
+
// otherwise lose it, leaving callers unable to classify the failure
|
|
497
|
+
// after retries are exhausted.
|
|
498
|
+
const code = extractErrorCode(err);
|
|
499
|
+
if (code)
|
|
500
|
+
lastError.code = code;
|
|
501
|
+
}
|
|
493
502
|
if (attempt < delays.length) {
|
|
494
503
|
const delay = delays[attempt];
|
|
495
504
|
const start = Date.now();
|
|
@@ -497,8 +506,16 @@ export function withRetry(fn, delays = [100, 500, 2000]) {
|
|
|
497
506
|
}
|
|
498
507
|
}
|
|
499
508
|
}
|
|
500
|
-
|
|
509
|
+
const exhausted = new Error(`SQLITE_BUSY/SQLITE_IOERR: transient SQLite error after ${delays.length} retries. ` +
|
|
501
510
|
`Original error: ${lastError?.message}`);
|
|
511
|
+
// Carry the last error's SQLite code on the wrapper (message alone is
|
|
512
|
+
// ambiguous — it names both SQLITE_BUSY and SQLITE_IOERR). store.ts's
|
|
513
|
+
// `[context-mode:store]` enrichment appends this as ` (<code>)` so the
|
|
514
|
+
// tool-visible error is never a bare "disk I/O error".
|
|
515
|
+
const exhaustedCode = lastError ? extractErrorCode(lastError) : "";
|
|
516
|
+
if (exhaustedCode)
|
|
517
|
+
exhausted.code = exhaustedCode;
|
|
518
|
+
throw exhausted;
|
|
502
519
|
}
|
|
503
520
|
// ─────────────────────────────────────────────────────────
|
|
504
521
|
// Corrupt DB recovery (#244)
|
|
@@ -545,8 +562,12 @@ const _recentDbErrors = new Map();
|
|
|
545
562
|
* Extract the SQLite error code (e.g. "SQLITE_IOERR") from an arbitrary
|
|
546
563
|
* thrown value, or "" when absent. better-sqlite3 and node:sqlite set
|
|
547
564
|
* `code` on SqliteError; bun:sqlite encodes it in the message instead.
|
|
565
|
+
*
|
|
566
|
+
* Exported so store.ts can append the same `[<code>]` context to the
|
|
567
|
+
* tool-visible `[context-mode:store] …` error messages without
|
|
568
|
+
* duplicating the throw-shape handling.
|
|
548
569
|
*/
|
|
549
|
-
function extractErrorCode(err) {
|
|
570
|
+
export function extractErrorCode(err) {
|
|
550
571
|
if (err instanceof Error) {
|
|
551
572
|
const code = err.code;
|
|
552
573
|
return typeof code === "string" ? code : "";
|
|
@@ -557,7 +578,12 @@ function extractErrorCode(err) {
|
|
|
557
578
|
}
|
|
558
579
|
return "";
|
|
559
580
|
}
|
|
560
|
-
|
|
581
|
+
/**
|
|
582
|
+
* Normalize the human-readable message of an arbitrary thrown value.
|
|
583
|
+
* Exported alongside extractErrorCode for store.ts's tool-error
|
|
584
|
+
* enrichment (non-Error shapes included).
|
|
585
|
+
*/
|
|
586
|
+
export function errorMessage(err) {
|
|
561
587
|
if (err instanceof Error)
|
|
562
588
|
return err.message;
|
|
563
589
|
if (typeof err === "string")
|