Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/agentctx/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* agentctx must never break a Claude Code session (issue 2/7). The hook
* dispatcher and everything else load lazily.
*/
import { describeError } from "./errors.js";

const argv = process.argv.slice(2);

if (argv[0] === "hook") {
Expand All @@ -24,7 +26,7 @@ if (argv[0] === "hook") {
process.exitCode = code;
})
.catch((error) => {
console.error(`agentctx: ${error instanceof Error ? error.message : String(error)}`);
console.error(`agentctx: ${describeError(error)}`);
process.exitCode = 1;
});
}
4 changes: 3 additions & 1 deletion packages/agentctx/src/cli/node-support.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describeError } from "../errors.js";

/**
* Node support matrix (OQ-1, ADR-003).
*
Expand Down Expand Up @@ -44,7 +46,7 @@ export function unsupportedNodeReason(version: string = process.versions.node):
* missing binding (no prebuild existed and install skipped the compile).
*/
export function describeNativeLoadError(error: unknown): string | null {
const message = error instanceof Error ? error.message : String(error);
const message = describeError(error);
const abiMismatch = message.includes("NODE_MODULE_VERSION");
// ERR_DLOPEN_FAILED is raised for any native module; only claim it when
// the message names better-sqlite3 — generic advice would be wrong advice.
Expand Down
3 changes: 2 additions & 1 deletion packages/agentctx/src/consolidate/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { existsSync } from "node:fs";
import type { Database } from "better-sqlite3";
import type { CliEnv } from "../cli/env.js";
import { loadConfig } from "../config.js";
import { describeError } from "../errors.js";
import { openDatabase } from "../storage/db.js";
import { resolveProjectId } from "../storage/namespace.js";
import { GLOBAL_PROJECT_ID, type RecordType } from "../storage/types.js";
Expand Down Expand Up @@ -54,7 +55,7 @@ export async function runConsolidate(env: CliEnv, _args: string[] = []): Promise
}
} catch (error) {
// Detached background pass: never escalate (SPEC §8 rung 5).
env.io.err(`agentctx consolidate: ${error instanceof Error ? error.message : String(error)}`);
env.io.err(`agentctx consolidate: ${describeError(error)}`);
}
return 0;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/agentctx/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function describeError(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
7 changes: 2 additions & 5 deletions packages/agentctx/src/extract/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* count-threshold transition itself runs in `agentctx consolidate`.
*/
import type { Database } from "better-sqlite3";
import { describeError } from "../errors.js";
import { getRecord, insertRecord } from "../storage/records.js";
import {
BODY_MAX_CHARS,
Expand Down Expand Up @@ -175,7 +176,7 @@ function ingestCandidate(
stats.written++;
} catch (error) {
stats.dropped++;
log(`ingest: dropped ${candidate.type} entry: ${describe(error)}`);
log(`ingest: dropped ${candidate.type} entry: ${describeError(error)}`);
}
}

Expand Down Expand Up @@ -226,7 +227,3 @@ function clampTitle(text: string): string {
function bullets(items: string[]): string {
return items.map((item) => `- ${item}`).join("\n");
}

function describe(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
7 changes: 2 additions & 5 deletions packages/agentctx/src/extract/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { join } from "node:path";
import { parseArgs } from "node:util";
import type { CliEnv } from "../cli/env.js";
import { loadConfig } from "../config.js";
import { describeError } from "../errors.js";
import { openDatabase } from "../storage/db.js";
import { resolveProjectId } from "../storage/namespace.js";
import { type FetchLike, requestCompletion } from "./api.js";
Expand Down Expand Up @@ -59,7 +60,7 @@ export async function runExtract(
try {
return await extract(env, sessionId, transcriptPath, values["no-llm"] === true, deps, log);
} catch (error) {
log(`extract failed for session ${sessionId}: ${describe(error)}`);
log(`extract failed for session ${sessionId}: ${describeError(error)}`);
return 0; // never a session error (SPEC §6)
}
}
Expand Down Expand Up @@ -190,7 +191,3 @@ function makeLog(env: CliEnv): (message: string) => void {
}
};
}

function describe(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
7 changes: 2 additions & 5 deletions packages/agentctx/src/hooks/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { existsSync, rmSync } from "node:fs";
import { join } from "node:path";
import { CONFIG_FILE_NAME, DEFAULT_CONFIG, loadConfig } from "../config.js";
import { describeError } from "../errors.js";
import { openDatabase } from "../storage/db.js";
import { resolveProjectId } from "../storage/namespace.js";
import { dedupFilePath } from "./dedup.js";
Expand Down Expand Up @@ -51,7 +52,7 @@ export async function runSessionEnd(env: HookEnv, payload: HookPayload): Promise
db.close();
}
} catch (error) {
env.log(`session-end: bookkeeping failed: ${describe(error)}`);
env.log(`session-end: bookkeeping failed: ${describeError(error)}`);
}
}
}
Expand Down Expand Up @@ -79,7 +80,3 @@ function effectiveConfig(env: HookEnv): { llm: boolean } {
return DEFAULT_CONFIG;
}
}

function describe(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
9 changes: 3 additions & 6 deletions packages/agentctx/src/hooks/session-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* re-counted in `sessions.tokens_injected` because it is re-paid.
*/
import { existsSync } from "node:fs";
import { describeError } from "../errors.js";
import { openDatabase } from "../storage/db.js";
import { resolveProjectId } from "../storage/namespace.js";
import { listRecords } from "../storage/records.js";
Expand Down Expand Up @@ -60,7 +61,7 @@ function profileOnlyFallback(env: HookEnv, projectId: string): string {
db.close();
}
} catch (error) {
env.log(`session-start: profile fallback failed: ${describe(error)}`);
env.log(`session-start: profile fallback failed: ${describeError(error)}`);
return "";
}
}
Expand All @@ -83,10 +84,6 @@ function accountInjection(
db.close();
}
} catch (error) {
env.log(`session-start: token accounting failed: ${describe(error)}`);
env.log(`session-start: token accounting failed: ${describeError(error)}`);
}
}

function describe(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
7 changes: 2 additions & 5 deletions packages/agentctx/src/hooks/user-prompt-submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Every failure path degrades to "inject nothing" — never an error.
*/
import { existsSync } from "node:fs";
import { describeError } from "../errors.js";
import { openDatabase } from "../storage/db.js";
import { resolveProjectId } from "../storage/namespace.js";
import { type SearchHit, searchRecords } from "../storage/search.js";
Expand Down Expand Up @@ -65,7 +66,7 @@ export async function runUserPromptSubmit(env: HookEnv, payload: HookPayload): P
at: env.now().toISOString(),
});
} catch (error) {
env.log(`user-prompt-submit: token accounting failed: ${describe(error)}`);
env.log(`user-prompt-submit: token accounting failed: ${describeError(error)}`);
}
}
} finally {
Expand Down Expand Up @@ -110,7 +111,3 @@ export function formatInjection(
}
return { text, ids, tokens: estimateTokens(text) };
}

function describe(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
3 changes: 2 additions & 1 deletion packages/agentctx/src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* nothing throws raw into the channel.
*/
import { createInterface } from "node:readline";
import { describeError } from "../errors.js";
import { VERSION } from "../version.js";
import { type ToolContext, type ToolDefinition, callTool } from "./tools.js";

Expand Down Expand Up @@ -72,7 +73,7 @@ export function serveMcp(options: McpServerOptions): Promise<void> {
} catch (err) {
// Defense in depth — handlers are expected to capture their own
// failures; anything that escapes is logged and answered, never thrown.
log(`agentctx mcp: ${err instanceof Error ? err.message : String(err)}`);
log(`agentctx mcp: ${describeError(err)}`);
send(error(idOf(message), INVALID_REQUEST, "internal error"));
}
});
Expand Down
3 changes: 2 additions & 1 deletion packages/agentctx/src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { existsSync, readFileSync } from "node:fs";
import { basename, join, resolve } from "node:path";
import type { Database } from "better-sqlite3";
import { buildSyncReport } from "../consolidate/drift.js";
import { describeError } from "../errors.js";
import { PROFILE_TITLES } from "../profile/detect.js";
import { getRecord, insertRecord, listRecords, supersedeRecord } from "../storage/records.js";
import { searchRecords } from "../storage/search.js";
Expand Down Expand Up @@ -461,7 +462,7 @@ export function callTool(
return { payload: { error: err.message }, isError: true };
}
return {
payload: { error: err instanceof Error ? err.message : String(err) },
payload: { error: describeError(err) },
isError: true,
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/agentctx/test/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { describe, expect, it } from "vitest";
import { describeError } from "../src/errors.js";
import { VERSION } from "../src/index.js";

describe("package surface", () => {
it("exposes the current version", () => {
expect(VERSION).toMatch(/^\d+\.\d+\.\d+$/);
});

it("describes Error and non-Error throwables consistently", () => {
expect(describeError(new Error("boom"))).toBe("boom");
expect(describeError("plain failure")).toBe("plain failure");
});
});
Loading