Files
Dhravya Shah a4ecd312b1 Fail open with 3s network cap on all hooks; async capture; token count in recall banner (#108)
* feat: fail open with 3s network cap on all hooks; async capture; token count in recall banner

- All hook API calls hard-capped at 3s (was 15s default, 4s recall);
  every failure path continues the session with a visible error
- Stop/capture hook runs with "async": true — fire-and-forget save, so
  the user gets their prompt back immediately. Injection hooks stay
  sync (async hooks' output is discarded, which would kill recall
  injection and auto-approval); tightened their backstop timeouts
- Network failures now say "Supermemory unreachable" instead of raw
  fetch errors, and session-start no longer claims "no previous
  memories" when the fetch failed — only a 404 means empty
- Recall banner shows the injected context cost: "recalled 5 memories (32 tok)"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfsBZgW6vDm8GEcy9ML3dT

* feat: statusline tally counts recalled memories in context, not recall events

Search state accumulates a cumulative memories counter (fresh injections
only); the resting tally shows "9 recalled" instead of "6 recalls".
Sessions with only MCP-tool searches (nothing hook-injected) keep the
event-count fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfsBZgW6vDm8GEcy9ML3dT

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:59:59 -06:00

66 lines
2.7 KiB
JavaScript

const AGENT_ENTITY_CONTEXT = `Shared coding-agent memory for one software repository.
RULES:
- Try to remember things that a human would remember — a teammate recalls decisions and lessons, not what state the working tree was in
- Preserve durable context that helps a coding agent continue the work
- Condense assistant responses into decisions, outcomes, and reusable knowledge
- Keep user preferences and project facts concise and independently understandable
EXTRACT:
- User preferences, accepted decisions, durable workflows, actions, and learnings
- Architecture: "uses monorepo with turborepo", "API in /apps/api"
- Conventions: "components in PascalCase", "hooks prefixed with use"
- Patterns: "all API routes use withAuth wrapper", "errors thrown as ApiError"
- Setup: "requires .env with DATABASE_URL", "run pnpm db:migrate first"
- Decisions: "chose Drizzle over Prisma for performance", "using RSC for data fetching"
SKIP:
- Transient repo state git already tracks: uncommitted file lists, current branch position, in-flight commit/push status
- Generic assistant suggestions the user did not accept
- Transient command output and low-value implementation chatter
- Granular details that do not help future work`;
// Hooks sit between the user and Claude — a slow or dead network must never
// hold the session hostage, so every request is capped hard at 3s and callers
// treat failure as "no memory this time", not a blocker.
const REQUEST_TIMEOUT_MS = 3000;
async function post(baseUrl, apiKey, path, body, timeoutMs = REQUEST_TIMEOUT_MS) {
const response = await fetch(`${baseUrl.replace(/\/+$/, '')}${path}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'x-sm-source': 'claude-code',
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(timeoutMs),
});
if (!response.ok) {
const text = await response.text().catch(() => '');
throw Object.assign(
new Error(`Supermemory API ${response.status}: ${text.slice(0, 200)}`),
{ status: response.status },
);
}
return response.json();
}
function getProfile(baseUrl, apiKey, containerTag, query, options = {}) {
return post(baseUrl, apiKey, '/v4/profile', { containerTag, q: query }, options.timeoutMs);
}
function addMemory(baseUrl, apiKey, content, containerTag, metadata, options = {}) {
const body = {
content,
containerTag,
metadata: { sm_source: 'claude-code', ...metadata },
};
if (options.customId) body.customId = options.customId;
if (options.entityContext) body.entityContext = options.entityContext;
return post(baseUrl, apiKey, '/v3/documents', body, options.timeoutMs);
}
module.exports = { AGENT_ENTITY_CONTEXT, getProfile, addMemory };