@remnic/plugin-pi 9.35.5 → 9.37.0
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/index.js +29 -46
- package/dist/index.js.map +1 -1
- package/dist/publisher.js +1 -1
- package/dist/publisher.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -661,21 +661,6 @@ function textFromMessage(message) {
|
|
|
661
661
|
}
|
|
662
662
|
return textFromContent(obj.content).trim();
|
|
663
663
|
}
|
|
664
|
-
function latestUserRecallTarget(messages) {
|
|
665
|
-
for (let index = messages.length - 1; index >= 0; index--) {
|
|
666
|
-
const message = messages[index];
|
|
667
|
-
if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;
|
|
668
|
-
if (message?.role !== "user") continue;
|
|
669
|
-
const query = textFromMessage(message);
|
|
670
|
-
if (query.length === 0) continue;
|
|
671
|
-
const identity = stableObservedMessageIdentity(message);
|
|
672
|
-
return {
|
|
673
|
-
query,
|
|
674
|
-
dedupeKey: identity ? `message:${identity}:${query}` : `query:${query}`
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
return null;
|
|
678
|
-
}
|
|
679
664
|
function toObserveMessage(message) {
|
|
680
665
|
if (!message || typeof message !== "object") return null;
|
|
681
666
|
const obj = message;
|
|
@@ -794,47 +779,44 @@ function createRemnicPiExtension(options = {}) {
|
|
|
794
779
|
if (!session) return;
|
|
795
780
|
const { state } = getSessionState(session.sessionKey, sessionStates);
|
|
796
781
|
restoreObservedState(session, state.observedHashes);
|
|
782
|
+
state.cachedContext = null;
|
|
783
|
+
state.recallCompleted = false;
|
|
797
784
|
const probe = await probeDaemonHealth(client, config);
|
|
798
785
|
if (config.statusEnabled) {
|
|
799
786
|
session.setStatus("remnic", remnicStatusLabel(probe, config.namespace));
|
|
800
787
|
}
|
|
801
788
|
await runNamespacePreflight(pi, session, client, config);
|
|
802
789
|
});
|
|
803
|
-
pi.on("
|
|
790
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
804
791
|
const session = snapshotPiContext(ctx);
|
|
805
792
|
if (!session) return;
|
|
806
|
-
if (!config.recallEnabled || !config.authToken) return;
|
|
807
|
-
if (!client.isReachable()) return;
|
|
808
|
-
const recallTarget = latestUserRecallTarget(Array.isArray(event.messages) ? event.messages : []);
|
|
809
|
-
if (!recallTarget) return;
|
|
810
|
-
const { query } = recallTarget;
|
|
811
793
|
const { state } = getSessionState(session.sessionKey, sessionStates);
|
|
812
|
-
if (
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
{
|
|
825
|
-
role: "user",
|
|
826
|
-
content: [{ type: "text", text: `Remnic recalled context for this turn:
|
|
827
|
-
|
|
828
|
-
${context}` }],
|
|
829
|
-
remnicInjected: true,
|
|
830
|
-
timestamp: Date.now()
|
|
794
|
+
if (!state.recallCompleted && config.recallEnabled && config.authToken && client.isReachable()) {
|
|
795
|
+
const promptText = typeof event.prompt === "string" && event.prompt.trim().length > 0 ? event.prompt : "";
|
|
796
|
+
if (promptText) {
|
|
797
|
+
state.recallCompleted = true;
|
|
798
|
+
try {
|
|
799
|
+
const recalled = await client.recall(promptText, session.sessionKey, session.cwd, {
|
|
800
|
+
timeoutMs: config.turnRequestTimeoutMs
|
|
801
|
+
});
|
|
802
|
+
client.markReachable();
|
|
803
|
+
const context = trimContext(recalled.context ?? "", config.recallBudgetChars);
|
|
804
|
+
if (context) {
|
|
805
|
+
state.cachedContext = context;
|
|
831
806
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
807
|
+
} catch (err) {
|
|
808
|
+
if (isDaemonUnreachableError(err)) client.markUnreachable(config.daemonCooldownMs);
|
|
809
|
+
session.notify(`Remnic recall unavailable: ${errorMessage(err)}`, "warning");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
837
812
|
}
|
|
813
|
+
if (!state.cachedContext) return;
|
|
814
|
+
const basePrompt = typeof event.systemPrompt === "string" ? event.systemPrompt : "";
|
|
815
|
+
return {
|
|
816
|
+
systemPrompt: `${basePrompt}
|
|
817
|
+
|
|
818
|
+
${state.cachedContext}`
|
|
819
|
+
};
|
|
838
820
|
});
|
|
839
821
|
pi.on("message_end", async (event, ctx) => {
|
|
840
822
|
const session = snapshotPiContext(ctx);
|
|
@@ -1094,7 +1076,8 @@ function getSessionState(sessionKey, states) {
|
|
|
1094
1076
|
state = {
|
|
1095
1077
|
observedHashes: /* @__PURE__ */ new Set(),
|
|
1096
1078
|
liveObservedReplayKeys: /* @__PURE__ */ new Map(),
|
|
1097
|
-
|
|
1079
|
+
cachedContext: null,
|
|
1080
|
+
recallCompleted: false
|
|
1098
1081
|
};
|
|
1099
1082
|
states.set(sessionKey, state);
|
|
1100
1083
|
pruneSessionStates(states);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/client.ts","../src/messages.ts"],"sourcesContent":["import { Type, type TSchema } from \"@sinclair/typebox\";\n\nimport { loadConfig, type LoadConfigOptions, type RemnicPiConfig } from \"./config.js\";\nimport { RemnicClient, RemnicHttpError, isTransientNetworkError, type McpTool, type ObserveMessage } from \"./client.js\";\nimport {\n hashObservedMessage,\n latestUserRecallTarget,\n observedMessageDedupeKey,\n sessionKeyFromContext,\n summarizeMessages,\n textFromMessage,\n toObserveMessage,\n} from \"./messages.js\";\n\ntype PiApi = {\n on(event: string, handler: (event: any, ctx: any) => unknown | Promise<unknown>): void;\n registerCommand(name: string, options: { description?: string; handler: (args: string, ctx: any) => Promise<void> }): void;\n registerTool(tool: Record<string, unknown>): void;\n appendEntry<T = unknown>(customType: string, data?: T): void;\n};\n\nexport interface RemnicPiExtensionOptions extends LoadConfigOptions {\n config?: RemnicPiConfig;\n}\n\nconst STATE_CUSTOM_TYPE = \"remnic_state\";\nconst MAX_OBSERVED_HASHES = 2000;\nconst MAX_SESSION_STATES = 50;\nconst MAX_CONTEXT_CHARS = 12000;\nconst TRUNCATION_NOTICE = \"\\n\\n[Remnic context truncated]\";\nconst SESSION_OWNED_FIELDS = new Set([\"sessionKey\", \"namespace\", \"cwd\"]);\n\ntype PiSessionState = {\n observedHashes: Set<string>;\n liveObservedReplayKeys: Map<string, number>;\n lastInjectedRecallKey: string;\n};\n\ntype NotifyLevel = \"info\" | \"success\" | \"warning\" | \"error\";\ntype NotifyFn = (message: string, level: NotifyLevel) => void;\n\ntype PiContextSnapshot = {\n sessionKey: string;\n cwd: string;\n entries: any[];\n branch: any[];\n notify: NotifyFn;\n setStatus: (key: string, value: string) => void;\n compact?: () => unknown;\n};\ntype PiContextSnapshotOptions = {\n includeSessionHistory?: boolean;\n};\n\nexport function createRemnicPiExtension(options: RemnicPiExtensionOptions = {}) {\n const config = options.config ?? loadConfig(options);\n const client = new RemnicClient(config);\n const sessionStates = new Map<string, PiSessionState>();\n\n return async function remnicPiExtension(pi: PiApi): Promise<void> {\n pi.on(\"session_start\", async (_event, ctx) => {\n const session = snapshotPiContext(ctx, { includeSessionHistory: true });\n if (!session) return;\n const { state } = getSessionState(session.sessionKey, sessionStates);\n restoreObservedState(session, state.observedHashes);\n // Probe health + update the circuit breaker UNCONDITIONALLY so an offline\n // daemon is marked unreachable even when the status UI is off; otherwise\n // the namespace preflight and every later hook each burn a full request\n // budget on a doomed call. The status LABEL stays gated on statusEnabled.\n const probe = await probeDaemonHealth(client, config);\n if (config.statusEnabled) {\n session.setStatus(\"remnic\", remnicStatusLabel(probe, config.namespace));\n }\n await runNamespacePreflight(pi, session, client, config);\n });\n\n pi.on(\"context\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n if (!config.recallEnabled || !config.authToken) return;\n // Circuit breaker: skip recall fast while the daemon is known-down so a\n // dead host doesn't block every turn on a doomed request (#1626).\n if (!client.isReachable()) return;\n const recallTarget = latestUserRecallTarget(Array.isArray(event.messages) ? event.messages : []);\n if (!recallTarget) return;\n const { query } = recallTarget;\n const { state } = getSessionState(session.sessionKey, sessionStates);\n if (recallTarget.dedupeKey === state.lastInjectedRecallKey) return;\n\n try {\n const recalled = await client.recall(query, session.sessionKey, session.cwd, {\n timeoutMs: config.turnRequestTimeoutMs,\n });\n client.markReachable();\n const context = trimContext(recalled.context ?? \"\", config.recallBudgetChars);\n if (!context) return;\n state.lastInjectedRecallKey = recallTarget.dedupeKey;\n return {\n messages: [\n ...event.messages,\n {\n role: \"user\",\n content: [{ type: \"text\", text: `Remnic recalled context for this turn:\\n\\n${context}` }],\n remnicInjected: true,\n timestamp: Date.now(),\n },\n ],\n };\n } catch (err) {\n // Only trip the breaker when the daemon is genuinely unreachable\n // (timeout or connection-level failure) — not on a transient HTTP\n // error, so a one-off failure still retries on the next turn (#1626).\n if (isDaemonUnreachableError(err)) client.markUnreachable(config.daemonCooldownMs);\n session.notify(`Remnic recall unavailable: ${errorMessage(err)}`, \"warning\");\n }\n });\n\n pi.on(\"message_end\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n if (!config.observeEnabled || !isUserMessage(event.message)) return;\n const { state } = getSessionState(session.sessionKey, sessionStates);\n await observeMessagesForSession(session, client, [event.message], state.observedHashes, state.liveObservedReplayKeys, config);\n });\n\n pi.on(\"turn_end\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n if (!config.observeEnabled) return;\n const messages = [event.message, ...(Array.isArray(event.toolResults) ? event.toolResults : [])];\n const { state } = getSessionState(session.sessionKey, sessionStates);\n await observeMessagesForSession(session, client, messages, state.observedHashes, state.liveObservedReplayKeys, config);\n });\n\n pi.on(\"session_shutdown\", async (_event, ctx) => {\n const session = snapshotPiContext(ctx, { includeSessionHistory: true });\n if (!session) return;\n const { sessionKey, state } = getSessionState(session.sessionKey, sessionStates);\n if (config.observeEnabled) {\n const branchMessages = branchMessagesWithEntryIdentity(session.branch);\n const unobservedBranchMessages = skipLiveObservedReplayMessages(session.sessionKey, branchMessages, state.liveObservedReplayKeys);\n if (unobservedBranchMessages.length > 0) {\n await observeMessagesForSession(session, client, unobservedBranchMessages, state.observedHashes, undefined, config, true);\n }\n }\n persistObservedState(pi, state.observedHashes);\n sessionStates.delete(sessionKey);\n });\n\n pi.on(\"session_before_compact\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n if (!config.compactionEnabled || !config.authToken) return;\n const preparation = event.preparation ?? {};\n try {\n await client.lcmCompactionFlush(session.sessionKey);\n } catch (err) {\n session.notify(`Remnic LCM flush failed: ${errorMessage(err)}`, \"warning\");\n return;\n }\n\n const tokensBefore = finiteTokenCount(preparation.tokensBefore);\n const tokensAfter = finiteTokenCount(preparation.tokensAfter);\n if (tokensBefore !== null && tokensAfter !== null) {\n try {\n await client.lcmCompactionRecord(session.sessionKey, tokensBefore, tokensAfter);\n } catch (err) {\n session.notify(`Remnic LCM compaction token record failed: ${errorMessage(err)}`, \"warning\");\n }\n }\n\n const summary = buildCompactionSummary(preparation);\n if (!summary.trim()) return;\n try {\n await client.contextCheckpoint(session.sessionKey, summary);\n } catch (err) {\n session.notify(`Remnic context checkpoint failed: ${errorMessage(err)}`, \"warning\");\n }\n const details = fileDetailsFromPreparation(preparation);\n return {\n compaction: {\n summary,\n firstKeptEntryId: preparation.firstKeptEntryId,\n tokensBefore: preparation.tokensBefore,\n details: {\n ...details,\n remnic: { version: 1, source: \"pi\" },\n },\n },\n };\n });\n\n registerCommands(pi, client, config);\n if (config.mcpToolsEnabled && config.authToken) {\n await registerMcpTools(pi, client, config);\n }\n };\n}\n\nexport default async function remnicPiExtension(pi: PiApi): Promise<void> {\n await createRemnicPiExtension()(pi);\n}\n\nfunction registerCommands(pi: PiApi, client: RemnicClient, config: RemnicPiConfig): void {\n pi.registerCommand(\"remnic-status\", {\n description: \"Check Remnic daemon status\",\n handler: commandHandler(async (_args, _ctx, session) => {\n const health = await client.health();\n // The daemon responded (any HTTP result), so clear any stale cooldown a\n // prior timeout left on the shared client (cursor review).\n client.markReachable();\n session.notify(`Remnic ${health.ok ? \"healthy\" : \"unhealthy\"} at ${config.remnicDaemonUrl}`, health.ok ? \"success\" : \"warning\");\n }),\n });\n\n pi.registerCommand(\"remnic-recall\", {\n description: \"Recall Remnic context for a query\",\n handler: commandHandler(async (args, _ctx, session) => {\n const query = args.trim();\n if (!query) {\n session.notify(\"Usage: /remnic-recall <query>\", \"warning\");\n return;\n }\n // Pass the general request budget so requestWithRetry shares ONE deadline\n // across retries (total <= requestTimeoutMs) instead of looping through\n // observeMaxRetries full timeouts and blocking the interactive command\n // for several minutes on a flaky connection (cursor review).\n const result = await client.recall(query, session.sessionKey, session.cwd, {\n timeoutMs: config.requestTimeoutMs,\n });\n // The daemon responded, so clear any stale cooldown a prior timeout left\n // on the shared client (cursor review).\n client.markReachable();\n session.notify(trimContext(result.context ?? \"(no Remnic context)\", MAX_CONTEXT_CHARS), \"info\");\n }),\n });\n\n pi.registerCommand(\"remnic-remember\", {\n description: \"Store a Remnic memory\",\n handler: commandHandler(async (args, _ctx, session) => {\n const content = args.trim();\n if (!content) {\n session.notify(\"Usage: /remnic-remember <memory>\", \"warning\");\n return;\n }\n await client.storeMemory(content, session.sessionKey);\n session.notify(\"Stored Remnic memory\", \"success\");\n }),\n });\n\n pi.registerCommand(\"remnic-lcm-search\", {\n description: \"Search Remnic LCM archived Pi context\",\n handler: commandHandler(async (args, _ctx, session) => {\n const query = args.trim();\n if (!query) {\n session.notify(\"Usage: /remnic-lcm-search <query>\", \"warning\");\n return;\n }\n const result = await client.lcmSearch(query, session.sessionKey);\n session.notify(JSON.stringify(result, null, 2), \"info\");\n }),\n });\n\n pi.registerCommand(\"remnic-why\", {\n description: \"Explain the last Remnic recall\",\n handler: commandHandler(async (_args, _ctx, session) => {\n const result = await client.recallExplain(session.sessionKey);\n session.notify(JSON.stringify(result, null, 2), \"info\");\n }),\n });\n\n pi.registerCommand(\"remnic-compact\", {\n description: \"Trigger Pi compaction with Remnic LCM coordination\",\n handler: commandHandler(async (_args, _ctx, session) => {\n session.compact?.();\n session.notify(\"Compaction requested\", \"info\");\n }),\n });\n}\n\nfunction commandHandler(\n handler: (args: string, ctx: any, session: PiContextSnapshot) => Promise<void>,\n): (args: string, ctx: any) => Promise<void> {\n return async (args, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n try {\n await handler(args, ctx, session);\n } catch (err) {\n session.notify(`Remnic command failed: ${errorMessage(err)}`, \"warning\");\n }\n };\n}\n\nasync function registerMcpTools(pi: PiApi, client: RemnicClient, config: RemnicPiConfig): Promise<void> {\n let tools: McpTool[] = [];\n try {\n tools = await client.mcpListTools({ timeoutMs: config.startupRequestTimeoutMs });\n } catch {\n return;\n }\n for (const tool of tools) {\n if (!tool.name.startsWith(\"remnic.\")) continue;\n const piToolName = tool.name.replace(/^remnic\\./, \"remnic_\").replace(/[^a-zA-Z0-9_]/g, \"_\");\n pi.registerTool({\n name: piToolName,\n label: tool.name,\n description: tool.description ?? `Call ${tool.name}`,\n parameters: toPiToolParametersSchema(tool.inputSchema),\n async execute(_toolCallId: string, params: Record<string, unknown>, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: any) {\n const session = snapshotPiContext(ctx);\n if (!session) {\n return {\n content: [{ type: \"text\", text: \"Remnic tool skipped because the Pi context is no longer active.\" }],\n details: { skipped: true, reason: \"stale_context\" },\n };\n }\n const safeParams = stripSessionOwnedRuntimeFields(params ?? {}) as Record<string, unknown>;\n const result = await client.mcpTool(tool.name, {\n ...safeParams,\n sessionKey: session.sessionKey,\n namespace: config.namespace,\n cwd: session.cwd,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n details: result,\n };\n },\n });\n }\n}\n\nexport function toPiToolParametersSchema(inputSchema: unknown): TSchema {\n return Type.Unsafe(stripSessionOwnedSchemaFields(inputSchema));\n}\n\nexport function stripSessionOwnedSchemaFields(inputSchema: unknown): Record<string, unknown> {\n if (!isRecord(inputSchema)) {\n return { type: \"object\", properties: {}, additionalProperties: true };\n }\n return stripSessionOwnedSchemaNode(inputSchema) as Record<string, unknown>;\n}\n\nfunction stripSessionOwnedSchemaNode(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((entry) => stripSessionOwnedSchemaNode(entry));\n }\n if (!isRecord(value)) {\n return value;\n }\n const schema: Record<string, unknown> = { ...value };\n if (isRecord(value.properties)) {\n const properties: Record<string, unknown> = {};\n for (const [key, property] of Object.entries(value.properties)) {\n if (SESSION_OWNED_FIELDS.has(key)) continue;\n properties[key] = stripSessionOwnedSchemaNode(property);\n }\n schema.properties = properties;\n }\n if (Array.isArray(value.required)) {\n schema.required = value.required.filter(\n (field) => typeof field !== \"string\" || !SESSION_OWNED_FIELDS.has(field),\n );\n }\n for (const key of [\"items\", \"additionalProperties\", \"not\"] as const) {\n if (isRecord(value[key])) {\n schema[key] = stripSessionOwnedSchemaNode(value[key]);\n }\n }\n for (const key of [\"oneOf\", \"anyOf\", \"allOf\"] as const) {\n if (Array.isArray(value[key])) {\n schema[key] = value[key].map((entry) => stripSessionOwnedSchemaNode(entry));\n }\n }\n return schema;\n}\n\nexport function stripSessionOwnedRuntimeFields(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((entry) => stripSessionOwnedRuntimeFields(entry));\n }\n if (!isRecord(value)) {\n return value;\n }\n const sanitized: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n if (SESSION_OWNED_FIELDS.has(key)) continue;\n sanitized[key] = stripSessionOwnedRuntimeFields(child);\n }\n return sanitized;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isUserMessage(message: unknown): boolean {\n return isRecord(message) && message.role === \"user\";\n}\n\nfunction getSessionState(sessionKey: string, states: Map<string, PiSessionState>): { sessionKey: string; state: PiSessionState } {\n let state = states.get(sessionKey);\n if (!state) {\n state = {\n observedHashes: new Set<string>(),\n liveObservedReplayKeys: new Map<string, number>(),\n lastInjectedRecallKey: \"\",\n };\n states.set(sessionKey, state);\n pruneSessionStates(states);\n }\n return { sessionKey, state };\n}\n\nfunction pruneSessionStates(states: Map<string, PiSessionState>): void {\n while (states.size > MAX_SESSION_STATES) {\n const oldest = states.keys().next().value;\n if (typeof oldest !== \"string\") return;\n states.delete(oldest);\n }\n}\n\nexport async function observeMessages(\n ctx: any,\n client: RemnicClient,\n rawMessages: unknown[],\n observedHashes: Set<string>,\n liveObservedReplayKeys?: Map<string, number>,\n): Promise<void> {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n await observeMessagesForSession(session, client, rawMessages, observedHashes, liveObservedReplayKeys);\n}\n\nasync function observeMessagesForSession(\n session: PiContextSnapshot,\n client: RemnicClient,\n rawMessages: unknown[],\n observedHashes: Set<string>,\n liveObservedReplayKeys?: Map<string, number>,\n config?: RemnicPiConfig,\n forceAttempt = false,\n): Promise<void> {\n const messages: ObserveMessage[] = [];\n const pendingHashes = new Set<string>();\n for (const raw of rawMessages) {\n const message = toObserveMessage(raw);\n if (!message) continue;\n const hash = observedMessageDedupeKey(message, session.sessionKey);\n if (hash && (observedHashes.has(hash) || pendingHashes.has(hash))) continue;\n if (hash) pendingHashes.add(hash);\n messages.push(message);\n }\n if (messages.length === 0) return;\n // Circuit breaker: when config is wired (the live Pi handlers always pass\n // it), skip observe fast while the daemon is known-down so a dead host\n // doesn't burn the full per-turn budget on every turn (#1626). Shutdown is\n // the exception — it is the last chance to observe the branch before the\n // session tears down, so force the attempt even mid-cooldown; a failure\n // still trips the breaker normally (codex review).\n if (config && !forceAttempt && !client.isReachable()) return;\n // Live turn hooks are bounded by the per-turn budget to protect the host's\n // ~30s handler window (#1626). Shutdown is teardown with no such constraint,\n // so the forced replay uses the general request budget — otherwise a large\n // unobserved branch would time out exactly when forceAttempt tried to save it\n // (cursor review).\n const observeOptions = config\n ? { timeoutMs: forceAttempt ? config.requestTimeoutMs : config.turnRequestTimeoutMs }\n : undefined;\n try {\n await client.observe(session.sessionKey, session.cwd, messages, observeOptions);\n if (config) client.markReachable();\n for (const hash of pendingHashes) rememberObservedHash(observedHashes, hash);\n if (liveObservedReplayKeys) {\n for (const message of messages) {\n rememberLiveObservedReplayKey(liveObservedReplayKeys, liveReplayKey(message, session.sessionKey));\n }\n }\n } catch (err) {\n if (config && isDaemonUnreachableError(err)) client.markUnreachable(config.daemonCooldownMs);\n session.notify(`Remnic observe failed: ${errorMessage(err)}`, \"warning\");\n }\n}\n\nexport function buildCompactionSummary(preparation: any): string {\n const previousSummary = typeof preparation.previousSummary === \"string\"\n ? preparation.previousSummary.trim()\n : \"\";\n const messages = [\n ...(Array.isArray(preparation.messagesToSummarize) ? preparation.messagesToSummarize : []),\n ...(Array.isArray(preparation.turnPrefixMessages) ? preparation.turnPrefixMessages : []),\n ];\n const transcript = summarizeMessages(messages, 24000);\n const details = fileDetailsFromPreparation(preparation);\n\n if (\n !previousSummary &&\n !transcript &&\n details.readFiles.length === 0 &&\n details.modifiedFiles.length === 0\n ) {\n return \"\";\n }\n\n const sections: string[] = [\n \"## Remnic Pi Context Checkpoint\",\n \"\",\n \"This checkpoint was created by Remnic during Pi context compaction.\",\n ];\n if (previousSummary) sections.push(\"\", \"## Previous Summary\", previousSummary);\n if (transcript) sections.push(\"\", \"## Conversation Excerpt\", transcript);\n if (details.readFiles.length > 0) sections.push(\"\", \"<read-files>\", ...details.readFiles, \"</read-files>\");\n if (details.modifiedFiles.length > 0) sections.push(\"\", \"<modified-files>\", ...details.modifiedFiles, \"</modified-files>\");\n return sections.join(\"\\n\");\n}\n\nfunction fileDetailsFromPreparation(preparation: any): { readFiles: string[]; modifiedFiles: string[] } {\n const fileOps = preparation?.fileOps;\n const read = fileOps?.read instanceof Set ? Array.from(fileOps.read).filter(isString) : [];\n const edited = fileOps?.edited instanceof Set ? Array.from(fileOps.edited).filter(isString) : [];\n const written = fileOps?.written instanceof Set ? Array.from(fileOps.written).filter(isString) : [];\n const modified = new Set([...edited, ...written]);\n return {\n readFiles: read.filter((file) => !modified.has(file)).sort(),\n modifiedFiles: Array.from(modified).sort(),\n };\n}\n\nfunction restoreObservedState(session: PiContextSnapshot, observedHashes: Set<string>): void {\n for (const entry of session.entries) {\n if (entry?.type !== \"custom\" || entry.customType !== STATE_CUSTOM_TYPE) continue;\n const hashes = entry.data?.observedHashes;\n if (Array.isArray(hashes)) {\n for (const hash of hashes) {\n if (typeof hash === \"string\") rememberObservedHash(observedHashes, hash);\n }\n }\n }\n}\n\nfunction rememberObservedHash(observedHashes: Set<string>, hash: string): void {\n if (observedHashes.has(hash)) return;\n while (observedHashes.size >= MAX_OBSERVED_HASHES) {\n const oldest = observedHashes.keys().next().value;\n if (typeof oldest !== \"string\") break;\n observedHashes.delete(oldest);\n }\n observedHashes.add(hash);\n}\n\nfunction rememberLiveObservedReplayKey(liveObservedReplayKeys: Map<string, number>, key: string): void {\n liveObservedReplayKeys.set(key, (liveObservedReplayKeys.get(key) ?? 0) + 1);\n}\n\nfunction consumeLiveObservedReplayKey(liveObservedReplayKeys: Map<string, number>, key: string): boolean {\n const count = liveObservedReplayKeys.get(key) ?? 0;\n if (count <= 0) return false;\n if (count === 1) liveObservedReplayKeys.delete(key);\n else liveObservedReplayKeys.set(key, count - 1);\n return true;\n}\n\nfunction skipLiveObservedReplayMessages(\n sessionKey: string,\n rawMessages: unknown[],\n liveObservedReplayKeys: Map<string, number>,\n): unknown[] {\n if (liveObservedReplayKeys.size === 0) return rawMessages;\n const unobserved: unknown[] = [];\n for (const raw of rawMessages) {\n const message = toObserveMessage(raw);\n if (message && consumeLiveObservedReplayKey(liveObservedReplayKeys, liveReplayKey(message, sessionKey))) {\n continue;\n }\n unobserved.push(raw);\n }\n return unobserved;\n}\n\nfunction liveReplayKey(message: ObserveMessage, sessionKey: string): string {\n return hashObservedMessage(message, sessionKey, \"live-replay\");\n}\n\nfunction persistObservedState(pi: PiApi, observedHashes: Set<string>): void {\n const observed = Array.from(observedHashes).slice(-MAX_OBSERVED_HASHES);\n pi.appendEntry(STATE_CUSTOM_TYPE, {\n observedHashes: observed,\n recordedAt: new Date().toISOString(),\n });\n}\n\n/** Result of the session_start daemon probe. */\ntype DaemonProbeResult = \"ready\" | \"starting\" | \"unreachable\";\n\n/**\n * Probe the daemon and update the shared circuit breaker. Returns the probe\n * outcome so the caller can render a status label. This runs at session_start\n * regardless of `statusEnabled`: the breaker update is a data-path concern (a\n * down daemon must be marked unreachable so the namespace preflight and every\n * later hook fast-skip instead of each burning a full request budget),\n * independent of whether the status UI is shown.\n *\n * A 503 `not_ready` answer is NOT offline (issue #2215): the daemon responded\n * — it is up and serving recall via fallback retrieval while startup search\n * warm-up is still running — so it counts as reachable and renders as\n * \"starting\" instead of tripping the breaker or claiming the service is down.\n */\nasync function probeDaemonHealth(client: RemnicClient, config: RemnicPiConfig): Promise<DaemonProbeResult> {\n try {\n await client.health({ timeoutMs: config.startupRequestTimeoutMs });\n // A successful probe means the daemon is reachable, so clear any stale\n // cooldown a prior recall/observe timeout left on the shared client.\n client.markReachable();\n return \"ready\";\n } catch (err) {\n if (err instanceof RemnicHttpError && err.status === 503 && err.code === \"not_ready\") {\n client.markReachable();\n return \"starting\";\n }\n // Startup just proved the daemon is unreachable, so trip the fast-skip\n // breaker — otherwise the first live hook spends the full turn budget on a\n // doomed request before the breaker would trip on its own.\n if (isDaemonUnreachableError(err)) client.markUnreachable(config.daemonCooldownMs);\n return \"unreachable\";\n }\n}\n\n/** Status-line label for the session_start probe outcome. */\nfunction remnicStatusLabel(probe: DaemonProbeResult, namespace: string | undefined): string {\n if (probe === \"unreachable\") return \"Remnic offline\";\n if (probe === \"starting\") return \"Remnic starting\";\n return `Remnic ${namespace ? `(${namespace})` : \"ready\"}`;\n}\n\n/**\n * Startup namespace-writability preflight (issue #1888 part 3). Runs at each\n * session_start. When the configured namespace is NOT writable for this\n * client's principal, every memory write is rejected and — since the\n * dead-letter quarantine landed — parked, never stored. A silent per-call\n * rejection is invisible; this surfaces it LOUDLY and persistently (an error\n * `remnic_state` entry + error notification, re-emitted every session while\n * broken).\n *\n * `appendEntry` has no delete, so the CURRENT state is always recorded: a\n * `NAMESPACE_OK` entry on a writable result makes the latest `remnic_state`\n * entry authoritative even across an extension/host restart (no in-memory\n * transition tracking that a restart would lose). Only errors notify — the OK\n * entry is a silent heartbeat, never a success toast every healthy session. A\n * daemon that cannot be reached (indeterminate) records nothing, leaving the\n * last known state intact — we neither cry wolf nor falsely clear a real error.\n */\nasync function runNamespacePreflight(\n pi: PiApi,\n session: PiContextSnapshot,\n client: RemnicClient,\n config: RemnicPiConfig,\n): Promise<void> {\n // No token → the client cannot write anyway; known-unreachable → the answer\n // would be indeterminate. Either way, do not touch the recorded state.\n if (!config.authToken || !client.isReachable()) return;\n const result = await client.preflightNamespace(session.sessionKey, {\n timeoutMs: config.startupRequestTimeoutMs,\n });\n if (result.status === \"not_writable\") {\n // The remediation differs by cause: `unsupported` means the daemon has\n // namespaces disabled, so ONLY its default namespace is writable — pointing\n // the operator at namespacePolicies would be misleading.\n const fix =\n result.reason === \"unsupported\"\n ? \"The daemon has namespaces disabled, so only its default namespace is writable — set the client's namespace to the daemon's defaultNamespace (or omit it).\"\n : \"Fix the client's namespace config: it must match a namespacePolicies entry, or be the daemon's defaultNamespace/sharedNamespace.\";\n const message =\n `Remnic: configured namespace \"${result.namespace}\" is NOT writable for this client's principal ` +\n `(${result.reason}). Every memory write will be rejected and dead-lettered (recoverable via ` +\n `\\`remnic quarantine list\\`), NOT stored. ${fix}`;\n session.notify(message, \"error\");\n pi.appendEntry(STATE_CUSTOM_TYPE, {\n level: \"error\",\n code: \"NAMESPACE_NOT_WRITABLE\",\n namespace: result.namespace,\n reason: result.reason,\n message,\n persistent: true,\n recordedAt: new Date().toISOString(),\n });\n return;\n }\n if (result.status === \"writable\") {\n pi.appendEntry(STATE_CUSTOM_TYPE, {\n level: \"info\",\n code: \"NAMESPACE_OK\",\n namespace: result.namespace,\n recordedAt: new Date().toISOString(),\n });\n }\n // indeterminate → record nothing; keep the last known state.\n}\n\nfunction snapshotPiContext(ctx: any, options: PiContextSnapshotOptions = {}): PiContextSnapshot | null {\n const sessionKey = safeSessionKeyFromContext(ctx);\n if (!sessionKey) return null;\n const cwd = safeStringRead(() => ctx?.cwd, \"\");\n const hasUI = safeRead(() => ctx?.hasUI, undefined) === false;\n const ui = hasUI ? undefined : safeRead(() => ctx?.ui, undefined);\n const compact = safeRead(() => ctx?.compact, undefined);\n const includeSessionHistory = options.includeSessionHistory === true;\n return {\n sessionKey,\n cwd,\n entries: includeSessionHistory ? safeEntries(ctx) : [],\n branch: includeSessionHistory ? safeBranch(ctx) : [],\n notify: makeNotifier(ui, hasUI),\n setStatus: makeStatusSetter(ui, hasUI),\n compact: typeof compact === \"function\" ? () => compact.call(ctx) : undefined,\n };\n}\n\nfunction safeSessionKeyFromContext(ctx: any): string | null {\n try {\n return sessionKeyFromContext(ctx);\n } catch {\n return null;\n }\n}\n\nfunction makeNotifier(ui: unknown, hasUI: boolean): NotifyFn {\n if (hasUI || !isRecord(ui) || typeof ui.notify !== \"function\") {\n return () => undefined;\n }\n const notifyFn = ui.notify;\n return (message, level) => {\n try {\n notifyFn.call(ui, message, level);\n } catch {\n // Pi invalidates session-bound UI objects during reload/replacement. A\n // notification failure must not tear down Remnic's hooks.\n }\n };\n}\n\nfunction makeStatusSetter(ui: unknown, hasUI: boolean): PiContextSnapshot[\"setStatus\"] {\n if (hasUI || !isRecord(ui) || typeof ui.setStatus !== \"function\") {\n return () => undefined;\n }\n const setStatusFn = ui.setStatus;\n return (key, value) => {\n try {\n setStatusFn.call(ui, key, value);\n } catch {\n // See makeNotifier: stale UI should not make extension startup fail.\n }\n };\n}\n\nfunction safeRead<T>(read: () => T, fallback: T): T {\n try {\n return read();\n } catch {\n return fallback;\n }\n}\n\nfunction safeStringRead(read: () => unknown, fallback: string): string {\n const value = safeRead(read, fallback);\n return typeof value === \"string\" ? value : fallback;\n}\n\nfunction safeEntries(ctx: any): any[] {\n try {\n const entries = ctx.sessionManager?.getEntries?.();\n return Array.isArray(entries) ? entries : [];\n } catch {\n return [];\n }\n}\n\nfunction safeBranch(ctx: any): any[] {\n try {\n const branch = ctx.sessionManager?.getBranch?.();\n return Array.isArray(branch) ? branch : [];\n } catch {\n return [];\n }\n}\n\nfunction branchMessagesWithEntryIdentity(branch: any[]): unknown[] {\n const messages: unknown[] = [];\n for (const entry of branch) {\n const message = messageWithEntryIdentity(entry);\n if (message) messages.push(message);\n }\n return messages;\n}\n\nfunction messageWithEntryIdentity(entry: any): unknown | null {\n const message = entry?.message;\n if (!message || typeof message !== \"object\" || Array.isArray(message)) return message ?? null;\n\n const source = isRecord(entry) ? entry : {};\n const enriched: Record<string, unknown> = { ...(message as Record<string, unknown>) };\n assignMissingIdentity(enriched, \"entryId\", source.id ?? source.entryId ?? source.entry_id);\n assignMissingIdentity(enriched, \"timestamp\", source.timestamp);\n assignMissingIdentity(enriched, \"createdAt\", source.createdAt ?? source.created_at);\n return enriched;\n}\n\nfunction assignMissingIdentity(target: Record<string, unknown>, field: string, value: unknown): void {\n if (target[field] !== undefined) return;\n if (typeof value === \"string\" && value.length > 0) {\n target[field] = value;\n return;\n }\n if (typeof value === \"number\" && Number.isFinite(value)) {\n target[field] = value;\n }\n}\n\nfunction trimContext(value: string, budget: number): string {\n if (value.length <= budget) return value;\n if (budget <= TRUNCATION_NOTICE.length) return TRUNCATION_NOTICE.slice(0, budget);\n return `${value.slice(0, budget - TRUNCATION_NOTICE.length)}${TRUNCATION_NOTICE}`;\n}\n\n\nexport function isDaemonUnreachableError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (/Remnic request timed out/.test(err.message)) return true;\n // Retry-budget exhaustion means transient failures ate the whole per-turn\n // deadline inside requestWithRetry — the daemon is effectively unreachable\n // for this turn, so trip the breaker and cool down instead of burning another\n // full budget on the next hook (codex review). This error only arises from\n // transient connection failures, never from a semantic HTTP response.\n if (/Remnic request exceeded the \\d+ms budget before retry/.test(err.message)) return true;\n // Multi-chunk observe throws its own budget-exceeded message when the shared\n // per-turn deadline is exhausted across chunks; that is also an effectively-\n // unreachable condition for the turn, so trip the breaker and fast-skip\n // subsequent turns instead of piling on more doomed chunked observes (cursor).\n if (/Remnic observe exceeded the per-turn budget of \\d+ms/.test(err.message)) return true;\n return isTransientNetworkError(err);\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction finiteTokenCount(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : null;\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nexport { textFromMessage };\n","import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// Leaf submodule (not the `@remnic/core` barrel) so the omp pre-bundle's\n// `bun build` does not pull the rest of core — including the LanceDB native\n// asset — into the extension bundle. See PR #1641.\nimport { expandTildePath } from \"@remnic/core/utils/path\";\n\nimport { REMNIC_PI_EXTENSION_DIR_NAME, resolvePiAgentHome } from \"./paths.js\";\n\nexport interface RemnicPiConfig {\n remnicDaemonUrl: string;\n authToken?: string;\n namespace?: string;\n recallMode: \"auto\" | \"minimal\" | \"full\" | \"graph_mode\" | \"no_recall\";\n recallTopK: number;\n recallBudgetChars: number;\n recallEnabled: boolean;\n observeEnabled: boolean;\n observeSkipExtraction: boolean;\n compactionEnabled: boolean;\n mcpToolsEnabled: boolean;\n statusEnabled: boolean;\n requestTimeoutMs: number;\n startupRequestTimeoutMs: number;\n /**\n * Per-turn request budget for observe/recall. MUST stay below the host's\n * in-handler kill budget (Pi/omp kills handlers at 30 s). Defaults to 20 s,\n * capped at 25 s so a misconfiguration can never produce a structurally\n * unsatisfiable timeout (issue #1626).\n */\n turnRequestTimeoutMs: number;\n /**\n * Soft cap on a single observe POST body in bytes. The client chunks observe\n * batches to stay under this; individual oversized messages are truncated\n * with a marker. Defaults to 100 KiB, safely under the daemon's default\n * 128 KiB `maxBodyBytes` (issue #1600).\n */\n observeMaxBytes: number;\n /**\n * Maximum retry attempts for observe/recall on transient connection-level\n * failures (socket close, ECONNRESET, EPIPE). Observe is dedupe-safe so\n * retrying is harmless (issue #1602).\n */\n observeMaxRetries: number;\n /**\n * Cooldown base for the daemon-reachability circuit breaker. When observe/\n * recall fails on a timeout or connection error, subsequent turns skip fast\n * for an exponentially growing window starting at this value (issue #1626).\n */\n daemonCooldownMs: number;\n}\n\nexport interface LoadConfigOptions {\n configPath?: string;\n env?: NodeJS.ProcessEnv;\n}\n\nconst DEFAULT_CONFIG: RemnicPiConfig = {\n remnicDaemonUrl: \"http://127.0.0.1:4318\",\n recallMode: \"auto\",\n recallTopK: 8,\n recallBudgetChars: 12000,\n recallEnabled: true,\n observeEnabled: true,\n observeSkipExtraction: false,\n compactionEnabled: true,\n mcpToolsEnabled: true,\n statusEnabled: true,\n requestTimeoutMs: 60000,\n startupRequestTimeoutMs: 1000,\n // Default 20 s is comfortably under the Pi/omp 30 s handler budget (#1626).\n turnRequestTimeoutMs: 20000,\n // Default 100 KiB leaves headroom under the daemon's 128 KiB default (#1600).\n observeMaxBytes: 102400,\n observeMaxRetries: 2,\n // Base cooldown for the circuit breaker; doubles on consecutive failures (#1626).\n daemonCooldownMs: 5000,\n};\n\nfunction defaultConfigPath(env: NodeJS.ProcessEnv): string {\n return path.join(resolvePiAgentHome(env), \"extensions\", REMNIC_PI_EXTENSION_DIR_NAME, \"remnic.config.json\");\n}\n\nfunction coerceBoolean(value: unknown, fallback: boolean, fieldName: string): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const normalized = value.trim().toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"on\"].includes(normalized)) return true;\n if ([\"false\", \"0\", \"no\", \"off\"].includes(normalized)) return false;\n }\n throw new Error(`Invalid boolean value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coercePositiveInt(value: unknown, fallback: number, max: number, fieldName: string): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n let parsed: number;\n if (typeof value === \"number\") {\n parsed = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.length === 0) return fallback;\n if (!/^[+-]?\\d+$/.test(trimmed)) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n parsed = Number(trimmed);\n } else {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n if (!Number.isInteger(parsed) || parsed <= 0 || parsed > max) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n return parsed;\n}\n\n/**\n * Like {@link coercePositiveInt} but allows 0, for knobs where 0 is a\n * meaningful \"disabled\" value (e.g. observeMaxRetries). Still rejects\n * negatives, non-integers, and values above the cap.\n */\nfunction coerceNonNegativeInt(value: unknown, fallback: number, max: number, fieldName: string): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n let parsed: number;\n if (typeof value === \"number\") {\n parsed = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.length === 0) return fallback;\n if (!/^[+-]?\\d+$/.test(trimmed)) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n parsed = Number(trimmed);\n } else {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > max) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n return parsed;\n}\n\nfunction coerceOptionalNonEmptyString(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\" && value.trim().length > 0) return value.trim();\n throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coerceOptionalString(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coerceOptionalHttpUrl(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== \"string\") {\n throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n try {\n const parsed = new URL(trimmed);\n if (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") return trimTrailingSlashes(trimmed);\n } catch {\n // Fall through to the shared error below.\n }\n throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);\n}\n\nfunction coerceRecallMode(value: unknown): RemnicPiConfig[\"recallMode\"] {\n if (value === undefined || value === null || value === \"\") return DEFAULT_CONFIG.recallMode;\n if (\n value === \"minimal\" ||\n value === \"full\" ||\n value === \"graph_mode\" ||\n value === \"no_recall\" ||\n value === \"auto\"\n ) {\n return value;\n }\n throw new Error(`Invalid recallMode value for Remnic Pi config: ${JSON.stringify(value)}`);\n}\n\nfunction readConfigFile(configPath: string): Record<string, unknown> {\n if (!existsSync(configPath)) return {};\n try {\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n throw new Error(\"expected a JSON object\");\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load Remnic Pi config at ${configPath}: ${reason}`);\n }\n}\n\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;\n return value.slice(0, end);\n}\n\nexport function resolveConfigPath(options: LoadConfigOptions = {}): string {\n const env = options.env ?? process.env;\n // REMNIC_PI_CONFIG keeps precedence for upstream Pi; REMNIC_OMP_CONFIG lets an\n // omp (oh-my-pi) direct load (`omp -e npm:@remnic/plugin-pi`) point the shared\n // runtime module at its own config without an explicit configPath. Connector\n // installs always pass an explicit configPath, so this only affects direct loads.\n return expandTildePath(\n options.configPath || env.REMNIC_PI_CONFIG || env.REMNIC_OMP_CONFIG || defaultConfigPath(env),\n );\n}\n\nexport function loadConfig(options: LoadConfigOptions = {}): RemnicPiConfig {\n const env = options.env ?? process.env;\n const fileConfig = readConfigFile(resolveConfigPath(options));\n const daemonUrl =\n coerceOptionalHttpUrl(fileConfig.remnicDaemonUrl, \"remnicDaemonUrl\") ??\n coerceOptionalHttpUrl(env.REMNIC_DAEMON_URL, \"REMNIC_DAEMON_URL\") ??\n DEFAULT_CONFIG.remnicDaemonUrl;\n const authToken =\n coerceOptionalString(fileConfig.authToken, \"authToken\") ??\n coerceOptionalString(env.REMNIC_PI_AUTH_TOKEN, \"REMNIC_PI_AUTH_TOKEN\");\n const namespace = coerceOptionalNonEmptyString(fileConfig.namespace, \"namespace\");\n\n const requestTimeoutMs = coercePositiveInt(\n fileConfig.requestTimeoutMs,\n DEFAULT_CONFIG.requestTimeoutMs,\n 60_000,\n \"requestTimeoutMs\",\n );\n // When turnRequestTimeoutMs is not explicitly set, derive it from the\n // configured requestTimeoutMs (capped at the default turn budget) so an\n // existing install that lowered requestTimeoutMs below 20s keeps its tighter\n // per-turn budget instead of being silently raised back to 20s (codex review).\n const turnFallback = Math.min(requestTimeoutMs, DEFAULT_CONFIG.turnRequestTimeoutMs);\n const turnRequestTimeoutMs = coercePositiveInt(\n fileConfig.turnRequestTimeoutMs,\n turnFallback,\n 25_000,\n \"turnRequestTimeoutMs\",\n );\n\n return {\n remnicDaemonUrl: daemonUrl,\n authToken,\n namespace,\n recallMode: coerceRecallMode(fileConfig.recallMode),\n recallTopK: coercePositiveInt(fileConfig.recallTopK, DEFAULT_CONFIG.recallTopK, 50, \"recallTopK\"),\n recallBudgetChars: coercePositiveInt(fileConfig.recallBudgetChars, DEFAULT_CONFIG.recallBudgetChars, 64000, \"recallBudgetChars\"),\n recallEnabled: coerceBoolean(fileConfig.recallEnabled, DEFAULT_CONFIG.recallEnabled, \"recallEnabled\"),\n observeEnabled: coerceBoolean(fileConfig.observeEnabled, DEFAULT_CONFIG.observeEnabled, \"observeEnabled\"),\n observeSkipExtraction: coerceBoolean(fileConfig.observeSkipExtraction, DEFAULT_CONFIG.observeSkipExtraction, \"observeSkipExtraction\"),\n compactionEnabled: coerceBoolean(fileConfig.compactionEnabled, DEFAULT_CONFIG.compactionEnabled, \"compactionEnabled\"),\n mcpToolsEnabled: coerceBoolean(fileConfig.mcpToolsEnabled, DEFAULT_CONFIG.mcpToolsEnabled, \"mcpToolsEnabled\"),\n statusEnabled: coerceBoolean(fileConfig.statusEnabled, DEFAULT_CONFIG.statusEnabled, \"statusEnabled\"),\n requestTimeoutMs,\n startupRequestTimeoutMs: coercePositiveInt(\n fileConfig.startupRequestTimeoutMs,\n DEFAULT_CONFIG.startupRequestTimeoutMs,\n 60_000,\n \"startupRequestTimeoutMs\",\n ),\n turnRequestTimeoutMs,\n observeMaxBytes: coercePositiveInt(\n fileConfig.observeMaxBytes,\n DEFAULT_CONFIG.observeMaxBytes,\n 8_388_608,\n \"observeMaxBytes\",\n ),\n observeMaxRetries: coerceNonNegativeInt(fileConfig.observeMaxRetries, DEFAULT_CONFIG.observeMaxRetries, 5, \"observeMaxRetries\"),\n daemonCooldownMs: coercePositiveInt(fileConfig.daemonCooldownMs, DEFAULT_CONFIG.daemonCooldownMs, 60_000, \"daemonCooldownMs\"),\n };\n}\n","import type { RemnicPiConfig } from \"./config.js\";\n\nexport interface RecallResponse {\n context?: string;\n results?: Array<{ id?: string; content?: string; score?: number; category?: string }>;\n count?: number;\n}\n\nexport interface ObserveMessagePart {\n ordinal?: number;\n kind: \"text\" | \"tool_call\" | \"tool_result\" | \"patch\" | \"file_read\" | \"file_write\" | \"step_start\" | \"step_finish\" | \"snapshot\" | \"retry\";\n payload: Record<string, unknown>;\n toolName?: string | null;\n filePath?: string | null;\n createdAt?: string | null;\n}\n\nexport interface ObserveMessage {\n role: \"user\" | \"assistant\";\n content: string;\n sourceFormat?: \"pi\";\n rawContent?: unknown;\n parts?: ObserveMessagePart[];\n}\n\nexport interface McpTool {\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n}\n\nexport interface RequestOptions {\n timeoutMs?: number;\n /** Transient-retry budget for connection-level failures (socket close, ECONNRESET). */\n maxRetries?: number;\n}\n\nexport interface ObserveOptions extends RequestOptions {\n /** Soft cap on a single observe POST body in bytes; oversize batches are chunked. */\n maxBytes?: number;\n}\n\n/**\n * Result of a startup namespace-writability preflight (issue #1888 part 3).\n * `not_writable` is a DEFINITIVE misconfiguration answer from the daemon (the\n * configured namespace resolves as non-writable for this principal), which the\n * client surfaces loudly. `indeterminate` means the daemon could not be reached\n * or answered unexpectedly (timeout, network, auth, 5xx) — the client must NOT\n * cry wolf about the namespace on those, since the answer is unknown.\n */\nexport type NamespacePreflightResult =\n | { readonly status: \"writable\"; readonly namespace: string }\n | { readonly status: \"not_writable\"; readonly reason: \"not_writable\" | \"unsupported\"; readonly namespace: string }\n | { readonly status: \"indeterminate\"; readonly detail: string };\n\nexport class RemnicHttpError extends Error {\n constructor(\n readonly status: number,\n message: string,\n /** Machine-readable error code from the daemon's JSON error body (e.g. `not_ready`). */\n readonly code?: string,\n ) {\n super(message);\n }\n}\n\ninterface ObserveBody {\n sessionKey: string;\n cwd: string;\n namespace?: string;\n skipExtraction: boolean;\n messages: ObserveMessage[];\n}\n\nconst encoder = new TextEncoder();\nconst RETRY_BASE_DELAY_MS = 200;\nconst MAX_COOLDOWN_MS = 60_000;\nconst TRUNCATION_MARKER = \"\\n\\n[Remnic observe truncated: payload exceeded client size cap]\";\n\nexport class RemnicClient {\n private requestId = 0;\n // Circuit-breaker state: when the daemon is known-unreachable, observe/recall\n // callers skip fast instead of blocking every turn on a doomed request (#1626).\n private unreachableUntil = 0;\n private consecutiveFailures = 0;\n\n constructor(private readonly config: RemnicPiConfig) {}\n\n /** True when the daemon is not in a known-unreachable cooldown. */\n isReachable(): boolean {\n return Date.now() >= this.unreachableUntil;\n }\n\n /** Clear the circuit breaker — call after any successful daemon interaction. */\n markReachable(): void {\n this.consecutiveFailures = 0;\n this.unreachableUntil = 0;\n }\n\n /**\n * Enter (or extend) an unreachable cooldown. The cooldown grows exponentially\n * with consecutive failures (base, 2×base, 4×base, …) capped at 60 s, so a\n * flapping daemon is retried gently while a hard-down host backs off hard.\n */\n markUnreachable(baseCooldownMs: number): void {\n this.consecutiveFailures += 1;\n const factor = 2 ** Math.min(this.consecutiveFailures - 1, 4);\n const cooldown = Math.min(baseCooldownMs * factor, MAX_COOLDOWN_MS);\n this.unreachableUntil = Date.now() + cooldown;\n }\n\n async health(options: RequestOptions = {}): Promise<Record<string, unknown>> {\n return this.request(\"GET\", \"/engram/v1/health\", undefined, options);\n }\n\n /**\n * Startup namespace-writability preflight (issue #1888 part 3). Asks the\n * daemon — read-only, no write, no side effect — whether the configured\n * namespace resolves as writable for this client's (token-resolved)\n * principal. A `not_writable` answer is definitive and surfaced loudly; any\n * transport failure returns `indeterminate` so a flaky daemon never triggers\n * a false namespace-misconfig alarm.\n */\n async preflightNamespace(\n sessionKey: string | undefined,\n options: RequestOptions = {},\n ): Promise<NamespacePreflightResult> {\n const params = new URLSearchParams();\n if (this.config.namespace) params.set(\"namespace\", this.config.namespace);\n if (sessionKey) params.set(\"session\", sessionKey);\n // Check the op this client's ENABLED write path uses: automatic turn\n // capture (observe) when observation is on, else the explicit store op.\n params.set(\"op\", this.config.observeEnabled ? \"observe\" : \"memory_store\");\n const qs = params.toString();\n const path = `/engram/v1/namespace/writable${qs ? `?${qs}` : \"\"}`;\n try {\n const payload = await this.request<{ ok?: unknown; reason?: unknown; namespace?: unknown }>(\n \"GET\",\n path,\n undefined,\n options,\n );\n // Both branches require the full contract before they are trusted: an\n // `ok:true` without a concrete namespace, or an `ok:false` without a known\n // reason + concrete namespace, is malformed → indeterminate, never a false\n // writable/not-writable verdict.\n if (\n payload?.ok === true &&\n typeof payload.namespace === \"string\" &&\n payload.namespace.length > 0\n ) {\n return { status: \"writable\", namespace: payload.namespace };\n }\n if (\n payload?.ok === false &&\n (payload.reason === \"not_writable\" || payload.reason === \"unsupported\") &&\n typeof payload.namespace === \"string\" &&\n payload.namespace.length > 0\n ) {\n return { status: \"not_writable\", reason: payload.reason, namespace: payload.namespace };\n }\n return { status: \"indeterminate\", detail: \"unexpected preflight response shape\" };\n } catch (err) {\n return { status: \"indeterminate\", detail: err instanceof Error ? err.message : String(err) };\n }\n }\n\n async recall(\n query: string,\n sessionKey: string,\n cwd: string,\n options: RequestOptions = {},\n ): Promise<RecallResponse> {\n // Recall is a read-only query; retry transient connection failures with the\n // same budget as observe (#1602). Default to the per-turn budget when the\n // caller omits timeoutMs so the retries share one deadline (like observe)\n // instead of each attempt reusing the full general request timeout (cursor\n // review). Callers that need more (e.g. the manual /remnic-recall command)\n // pass an explicit timeoutMs.\n const merged: RequestOptions = {\n ...options,\n timeoutMs: options.timeoutMs ?? this.config.turnRequestTimeoutMs,\n maxRetries: options.maxRetries ?? this.config.observeMaxRetries,\n };\n return this.requestWithRetry(\n \"POST\",\n \"/engram/v1/recall\",\n {\n query,\n sessionKey,\n cwd,\n namespace: this.config.namespace,\n topK: this.config.recallTopK,\n mode: this.config.recallMode,\n },\n merged,\n );\n }\n\n async recallExplain(sessionKey: string, options: RequestOptions = {}): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\n \"POST\",\n \"/engram/v1/recall/explain\",\n {\n sessionKey,\n namespace: this.config.namespace,\n },\n options,\n );\n }\n\n async observe(\n sessionKey: string,\n cwd: string,\n messages: ObserveMessage[],\n options: ObserveOptions = {},\n ): Promise<Record<string, unknown>> {\n const maxBytes = options.maxBytes ?? this.config.observeMaxBytes;\n // Observe runs on the live turn hooks, so bound it by the per-turn budget\n // (#1626) regardless of how many chunks the payload splits into. A missing\n // override previously let single-chunk observe fall back to the 60s general\n // budget while multi-chunk used the 20s turn budget (cursor review). An\n // explicit override is honored so callers outside the turn (shutdown replay,\n // tests) can extend it.\n const turnBudgetMs = options.timeoutMs ?? this.config.turnRequestTimeoutMs;\n const retryOptions: RequestOptions = {\n timeoutMs: turnBudgetMs,\n maxRetries: options.maxRetries ?? this.config.observeMaxRetries,\n };\n const chunks = chunkObservePayload(this.config, sessionKey, cwd, messages, maxBytes);\n if (chunks.length === 1) {\n return this.requestWithRetry(\"POST\", \"/engram/v1/observe\", chunks[0], retryOptions);\n }\n // Multiple chunks: send sequentially within the SAME per-turn deadline so\n // the TOTAL observe time stays under turnBudgetMs (not per-chunk), which\n // keeps it inside the host's ~30s handler budget (#1626). Each chunk is\n // retried independently on transient connection failures; observe is\n // dedupe-safe, so a partial failure just re-sends on the next turn.\n const deadline = Date.now() + turnBudgetMs;\n const results: Record<string, unknown>[] = [];\n for (let i = 0; i < chunks.length; i++) {\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n throw new Error(\n `Remnic observe exceeded the per-turn budget of ${turnBudgetMs}ms across ${chunks.length} chunks (completed ${i})`,\n );\n }\n const chunkOptions: RequestOptions = { ...retryOptions, timeoutMs: remaining };\n const result = await this.requestWithRetry<Record<string, unknown>>(\n \"POST\",\n \"/engram/v1/observe\",\n chunks[i],\n chunkOptions,\n );\n if (result && typeof result === \"object\") {\n results.push(result);\n }\n }\n return mergeObserveResults(results);\n }\n\n async storeMemory(content: string, sessionKey: string, options: RequestOptions = {}): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/memories\", {\n content,\n category: \"fact\",\n sourceReason: \"Captured from Pi via Remnic extension\",\n sessionKey,\n namespace: this.config.namespace,\n }, options);\n }\n\n async lcmSearch(query: string, sessionKey: string, limit = 10): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/lcm/search\", {\n query,\n sessionKey,\n namespace: this.config.namespace,\n limit,\n });\n }\n\n async lcmCompactionFlush(sessionKey: string): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/lcm/compaction/flush\", {\n sessionKey,\n namespace: this.config.namespace,\n });\n }\n\n async lcmCompactionRecord(sessionKey: string, tokensBefore: number, tokensAfter: number): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/lcm/compaction/record\", {\n sessionKey,\n namespace: this.config.namespace,\n tokensBefore,\n tokensAfter,\n });\n }\n\n async contextCheckpoint(sessionKey: string, context: string): Promise<Record<string, unknown>> {\n return this.mcpTool(\"remnic.context_checkpoint\", {\n sessionKey,\n context,\n namespace: this.config.namespace,\n });\n }\n\n async mcpListTools(options: RequestOptions = {}): Promise<McpTool[]> {\n const result = await this.mcpRequest(\"tools/list\", {}, options);\n const tools = result.tools;\n return Array.isArray(tools) ? tools.filter(isMcpTool) : [];\n }\n\n async mcpTool(name: string, args: Record<string, unknown>): Promise<Record<string, unknown>> {\n return this.mcpRequest(\"tools/call\", {\n name,\n arguments: args,\n });\n }\n\n /**\n * Single HTTP attempt with the configured timeout. No retry — retry of\n * transient connection failures lives in {@link requestWithRetry}.\n */\n private async request<T = Record<string, unknown>>(\n method: string,\n pathname: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const controller = new AbortController();\n // A per-request override is honored only when it is a finite positive number;\n // 0, negative, NaN, or non-finite values would make setTimeout abort\n // immediately (or behave erratically), so fall back to the general budget.\n // In practice the override is always sourced from the validated\n // `startupRequestTimeoutMs` / `turnRequestTimeoutMs` config, but this keeps\n // the client robust to any future caller (Copilot review).\n const override = options.timeoutMs;\n const timeoutMs =\n typeof override === \"number\" && Number.isFinite(override) && override > 0\n ? override\n : this.config.requestTimeoutMs;\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(`${this.config.remnicDaemonUrl}${pathname}`, {\n method,\n headers: {\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n ...(this.config.authToken ? { Authorization: `Bearer ${this.config.authToken}` } : {}),\n \"X-Engram-Client-Id\": \"pi\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller.signal,\n });\n const text = await response.text();\n let payload: unknown = {};\n let parseError: unknown;\n if (text) {\n try {\n payload = JSON.parse(text);\n } catch (err) {\n parseError = err;\n }\n }\n if (!response.ok) {\n const message = responseErrorMessage(response, text, payload, parseError);\n const code = responseErrorCode(payload, parseError);\n if (response.status === 413) {\n // Surface the body size so operators can tune the cap (#1600).\n const bodyBytes = body === undefined ? 0 : jsonBytes(body);\n throw new RemnicHttpError(response.status, `${message} (observed body ${bodyBytes} bytes; cap via observeMaxBytes)`, code);\n }\n throw new RemnicHttpError(response.status, message, code);\n }\n if (parseError) {\n const reason = parseError instanceof Error ? parseError.message : String(parseError);\n throw new Error(`Invalid JSON response from Remnic daemon (${response.status} ${response.statusText || \"OK\"}): ${reason}`);\n }\n return payload as T;\n } catch (err) {\n if (isAbortError(err)) {\n throw new Error(`Remnic request timed out after ${timeoutMs}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n /**\n * Wrap {@link request} with a small bounded retry loop for transient\n * connection-level failures (socket close mid-request, ECONNRESET, EPIPE).\n * Timeouts (our own AbortController) and HTTP responses (4xx/5xx) are NOT\n * retried here — timeouts already burned the full budget, and HTTP errors\n * carry semantic meaning the caller must handle. Observe/recall are\n * dedupe-safe so retrying a transiently-failed POST is harmless (#1602).\n */\n private async requestWithRetry<T = Record<string, unknown>>(\n method: string,\n pathname: string,\n body: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const maxRetries = options.maxRetries ?? 0;\n // Share ONE deadline across all attempts (including backoff sleeps) when the\n // caller passes a per-turn/per-operation budget, so a late transient failure\n // cannot burn a full timeout on every retry and overshoot the host's ~30s\n // handler window (#1602/#1626 — cursor + codex reviews). The first attempt\n // keeps the original timeoutMs verbatim (preserving error messages/timing);\n // only retries are tightened to the remaining budget.\n const budgetMs = options.timeoutMs;\n const hasDeadline = typeof budgetMs === \"number\" && Number.isFinite(budgetMs) && budgetMs > 0;\n const deadline = hasDeadline ? Date.now() + budgetMs : Number.POSITIVE_INFINITY;\n let attempt = 0;\n let attemptOptions = options;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n return await this.request<T>(method, pathname, body, attemptOptions);\n } catch (err) {\n if (attempt >= maxRetries || !isTransientNetworkError(err)) throw err;\n const delayMs = RETRY_BASE_DELAY_MS * 2 ** attempt;\n if (hasDeadline) {\n // The backoff sleep counts against the shared deadline; bail BEFORE\n // sleeping if the sleep alone would overshoot the remaining budget,\n // so a sub-backoff timeoutMs never blocks for the full backoff only\n // to then throw (cursor review).\n const remainingBeforeSleep = deadline - Date.now();\n if (remainingBeforeSleep <= delayMs) {\n throw new Error(\n `Remnic request exceeded the ${budgetMs}ms budget before retry ${attempt + 1} (${method} ${pathname})`,\n );\n }\n }\n await sleep(delayMs);\n attempt += 1;\n if (hasDeadline) {\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n throw new Error(\n `Remnic request exceeded the ${budgetMs}ms budget before retry ${attempt} (${method} ${pathname})`,\n );\n }\n attemptOptions = { ...options, timeoutMs: remaining };\n }\n }\n }\n }\n\n private async mcpRequest(\n method: string,\n params: Record<string, unknown>,\n options: RequestOptions = {},\n ): Promise<Record<string, unknown>> {\n this.requestId += 1;\n const payload = await this.request<Record<string, unknown>>(\"POST\", \"/mcp\", {\n jsonrpc: \"2.0\",\n id: this.requestId,\n method,\n params,\n }, options);\n if (payload.error) {\n throw new Error(JSON.stringify(payload.error));\n }\n return (payload.result && typeof payload.result === \"object\" ? payload.result : payload) as Record<string, unknown>;\n }\n}\n\nfunction isMcpTool(value: unknown): value is McpTool {\n return !!value && typeof value === \"object\" && \"name\" in value && typeof value.name === \"string\";\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && (err.name === \"AbortError\" || err.message === \"This operation was aborted\");\n}\n\n/**\n * Classify connection-level failures that are safe to retry: the request never\n * reached the daemon (or died mid-flight), so a retry is idempotent. Excludes\n * our own AbortController timeouts and HTTP responses (those carry meaning).\n */\nexport function isTransientNetworkError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (isAbortError(err)) return false;\n if (err instanceof RemnicHttpError) return false;\n const lower = (err.message ?? \"\").toLowerCase();\n // Bun fetch: \"The socket connection was closed unexpectedly.\"\n if (lower.includes(\"socket connection was closed\")) return true;\n if (lower.includes(\"socket closed\")) return true;\n // Node undici / OS codes surfaced in the message.\n if (lower.includes(\"econnreset\")) return true;\n if (lower.includes(\"epipe\")) return true;\n if (lower.includes(\"und_err_socket\")) return true;\n // Node wraps the real cause in err.cause (TypeError: fetch failed).\n if (lower.includes(\"fetch failed\")) return true;\n // Inspect the cause chain without an unchecked cast. Error.cause is\n // `unknown` in the ES2022 lib; narrow it before reading `.code`.\n const cause = err.cause;\n if (cause && typeof cause === \"object\" && \"code\" in cause) {\n const code = cause.code;\n if (typeof code === \"string\" && (code === \"ECONNRESET\" || code === \"EPIPE\" || code === \"UND_ERR_SOCKET\")) {\n return true;\n }\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n // Plain Promise constructor: avoids Promise.withResolvers (ES2024 / Node 22+),\n // so retry backoff works on Node 20 and other runtimes that load plugin-pi.\n return new Promise<void>(resolve => setTimeout(resolve, ms));\n}\n\nfunction jsonBytes(value: unknown): number {\n return encoder.encode(JSON.stringify(value)).length;\n}\n\nfunction buildObserveEnvelope(config: RemnicPiConfig, sessionKey: string, cwd: string, messages: ObserveMessage[]): ObserveBody {\n return {\n sessionKey,\n cwd,\n namespace: config.namespace,\n skipExtraction: config.observeSkipExtraction,\n messages,\n };\n}\n\n/**\n * Split an observe batch into POST bodies whose serialized JSON stays under\n * `maxBytes`. Single messages that alone exceed the per-message budget are\n * truncated with a marker rather than dropped, so large tool outputs still\n * leave a trace in memory (#1600).\n */\nexport function chunkObservePayload(\n config: RemnicPiConfig,\n sessionKey: string,\n cwd: string,\n messages: ObserveMessage[],\n maxBytes: number,\n): ObserveBody[] {\n const envelopeOverhead = jsonBytes(buildObserveEnvelope(config, sessionKey, cwd, []));\n const messageBudget = maxBytes - envelopeOverhead;\n if (messageBudget <= 0) {\n // The envelope overhead alone meets/exceeds the cap, so no valid body can\n // fit — return a single chunk and let the daemon reject it visibly (this is\n // a degenerate/misconfigured cap, not the common case). A small-but-positive\n // budget (<=1024) is NOT degenerate: truncate/pack normally so oversized\n // messages are shrunk to fit instead of bypassing the #1600 safeguards\n // (cursor review).\n return [buildObserveEnvelope(config, sessionKey, cwd, messages)];\n }\n const chunks: ObserveMessage[][] = [];\n let current: ObserveMessage[] = [];\n let currentSize = 0;\n const flush = (): void => {\n if (current.length > 0) {\n chunks.push(current);\n current = [];\n currentSize = 0;\n }\n };\n for (const message of messages) {\n const size = jsonBytes(message);\n if (size > messageBudget) {\n flush();\n chunks.push([truncateObserveMessage(message, messageBudget)]);\n continue;\n }\n // Account for the JSON array comma separator before this message when it is\n // not the first in the chunk, so the serialized body never overshoots the\n // cap (review: cursor).\n if (current.length > 0 && currentSize + 1 + size > messageBudget) {\n flush();\n }\n if (current.length > 0) currentSize += 1;\n current.push(message);\n currentSize += size;\n }\n flush();\n if (chunks.length === 0) {\n return [buildObserveEnvelope(config, sessionKey, cwd, [])];\n }\n return chunks.map((msgs) => buildObserveEnvelope(config, sessionKey, cwd, msgs));\n}\n\nconst decoder = new TextDecoder();\n\nfunction truncateObserveMessage(message: ObserveMessage, budgetBytes: number): ObserveMessage {\n // A truncated observe keeps ONLY role + a content marker, dropping rawContent\n // and parts. Live Pi turns carry the full original message in rawContent and\n // parsed parts; those fields dominate the serialized size and would keep the\n // chunk over the cap (defeating #1600), so they are removed — the daemon\n // extracts from content. JSON-escape-aware: measures ACTUAL jsonBytes of each\n // candidate so escaping (\\n -> \\\\\\\\n) can't overshoot.\n const slim: ObserveMessage = { role: message.role, content: \"\" };\n const markerOnly = jsonBytes({ ...slim, content: TRUNCATION_MARKER });\n if (markerOnly > budgetBytes) {\n // Pathological: even the marker alone doesn't fit. Keep it anyway so the\n // turn isn't silently dropped.\n return { role: message.role, content: TRUNCATION_MARKER };\n }\n const fullContent = message.content + TRUNCATION_MARKER;\n if (jsonBytes({ ...slim, content: fullContent }) <= budgetBytes) {\n return { role: message.role, content: fullContent };\n }\n // Binary-search the largest content slice whose slim message fits. Slicing by\n // encoded bytes keeps multi-byte sequences intact where possible; the decoder\n // replaces any dangling tail with the replacement char.\n const encoded = encoder.encode(message.content);\n let lo = 0;\n let hi = encoded.length;\n while (lo < hi) {\n const mid = hi - Math.floor((hi - lo) / 2);\n const candidate = decoder.decode(encoded.subarray(0, mid)) + TRUNCATION_MARKER;\n if (jsonBytes({ ...slim, content: candidate }) <= budgetBytes) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n const truncated = lo > 0 ? decoder.decode(encoded.subarray(0, lo)) : \"\";\n return { role: message.role, content: truncated + TRUNCATION_MARKER };\n}\n\nfunction mergeObserveResults(results: Record<string, unknown>[]): Record<string, unknown> {\n if (results.length === 0) return {};\n if (results.length === 1) return results[0];\n const merged: Record<string, unknown> = {};\n let countSum = 0;\n let hasCount = false;\n for (const result of results) {\n for (const key of Object.keys(result)) {\n const value = result[key];\n if (key === \"count\" && typeof value === \"number\" && Number.isFinite(value)) {\n countSum += value;\n hasCount = true;\n } else {\n merged[key] = value;\n }\n }\n }\n if (hasCount) merged.count = countSum;\n return merged;\n}\n\nfunction responseErrorMessage(response: Response, text: string, payload: unknown, parseError: unknown): string {\n if (!parseError && payload && typeof payload === \"object\") {\n if (\"error\" in payload && typeof payload.error === \"string\" && payload.error.trim().length > 0) {\n return payload.error;\n }\n if (\"message\" in payload && typeof payload.message === \"string\" && payload.message.trim().length > 0) {\n return payload.message;\n }\n }\n\n const snippet = text.trim().replace(/\\s+/g, \" \").slice(0, 200);\n if (snippet.length > 0) {\n return response.statusText ? `${response.statusText}: ${snippet}` : snippet;\n }\n return response.statusText || `HTTP ${response.status}`;\n}\n\nfunction responseErrorCode(payload: unknown, parseError: unknown): string | undefined {\n if (parseError || !payload || typeof payload !== \"object\") return undefined;\n if (\"code\" in payload && typeof payload.code === \"string\" && payload.code.trim().length > 0) {\n return payload.code;\n }\n return undefined;\n}\n","import { createHash } from \"node:crypto\";\n\n// Leaf submodule (not the `@remnic/core` barrel) so the omp pre-bundle's\n// `bun build` does not pull the rest of core into the extension bundle.\n// `message-parts` is a pure parser with no storage/native deps. See PR #1641.\nimport { parsePiMessageParts, type LcmMessagePartInput } from \"@remnic/core/message-parts\";\n\nimport type { ObserveMessage, ObserveMessagePart } from \"./client.js\";\n\ntype PiMessage = Record<string, unknown>;\n\nexport function sessionKeyFromContext(ctx: { sessionManager?: { getSessionId?: () => string } }): string {\n const id = ctx.sessionManager?.getSessionId?.();\n return id && id.trim().length > 0 ? `pi:${id}` : \"pi:default\";\n}\n\nexport function textFromMessage(message: unknown): string {\n if (!message || typeof message !== \"object\") return \"\";\n const obj = message as PiMessage;\n const role = typeof obj.role === \"string\" ? obj.role : \"message\";\n if (role === \"bashExecution\") {\n const command = typeof obj.command === \"string\" ? obj.command : \"\";\n const output = typeof obj.output === \"string\" ? obj.output : \"\";\n return [`Ran ${command}`, output].filter(Boolean).join(\"\\n\");\n }\n return textFromContent(obj.content).trim();\n}\n\nexport function latestUserQuery(messages: unknown[]): string {\n for (let index = messages.length - 1; index >= 0; index--) {\n const message = messages[index] as PiMessage;\n if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;\n if (message?.role === \"user\") {\n const text = textFromMessage(message);\n if (text.length > 0) return text;\n }\n }\n return \"\";\n}\n\nexport function latestUserRecallTarget(\n messages: unknown[],\n): { query: string; dedupeKey: string } | null {\n for (let index = messages.length - 1; index >= 0; index--) {\n const message = messages[index] as PiMessage;\n if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;\n if (message?.role !== \"user\") continue;\n const query = textFromMessage(message);\n if (query.length === 0) continue;\n const identity = stableObservedMessageIdentity(message);\n return {\n query,\n dedupeKey: identity ? `message:${identity}:${query}` : `query:${query}`,\n };\n }\n return null;\n}\n\nexport function toObserveMessage(message: unknown): ObserveMessage | null {\n if (!message || typeof message !== \"object\") return null;\n const obj = message as PiMessage;\n if (isExcludedFromContext(obj) || isRemnicInjected(obj)) return null;\n const role = obj.role === \"user\" || obj.role === \"bashExecution\" ? \"user\" : \"assistant\";\n const content = textFromMessage(obj);\n if (content.length === 0) return null;\n return {\n role,\n content,\n sourceFormat: \"pi\",\n rawContent: obj,\n parts: partsFromMessage(obj, content),\n };\n}\n\nexport function hashObservedMessage(message: ObserveMessage, sessionKey = \"\", identity = \"content\"): string {\n return createHash(\"sha256\")\n .update(sessionKey)\n .update(\"\\0\")\n .update(message.role)\n .update(\"\\0\")\n .update(identity)\n .update(\"\\0\")\n .update(message.content)\n .digest(\"hex\");\n}\n\nexport function observedMessageDedupeKey(\n message: ObserveMessage,\n sessionKey = \"\",\n): string | null {\n const identity = stableObservedMessageIdentity(message.rawContent);\n return identity ? hashObservedMessage(message, sessionKey, identity) : null;\n}\n\nexport function summarizeMessages(messages: unknown[], maxChars: number): string {\n const chunks: string[] = [];\n let used = 0;\n for (const message of messages) {\n if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;\n const text = textFromMessage(message);\n if (!text) continue;\n const role = typeof (message as PiMessage)?.role === \"string\" ? (message as PiMessage).role : \"message\";\n const line = `[${role}] ${text}`;\n const separatorLength = chunks.length > 0 ? 2 : 0;\n const remaining = maxChars - used - separatorLength;\n if (remaining <= 0) break;\n const clipped = line.length > remaining ? line.slice(0, remaining) : line;\n if (clipped.length > 0) chunks.push(clipped);\n used += separatorLength + clipped.length;\n if (used >= maxChars) break;\n }\n return chunks.join(\"\\n\\n\");\n}\n\nexport function isExcludedFromContext(message: unknown): boolean {\n return !!message && typeof message === \"object\" && (message as PiMessage).excludeFromContext === true;\n}\n\nexport function isRemnicInjected(message: unknown): boolean {\n return !!message && typeof message === \"object\" && (message as PiMessage).remnicInjected === true;\n}\n\nfunction textFromContent(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n const chunks: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const obj = block as PiMessage;\n if (obj.type === \"text\" && typeof obj.text === \"string\") chunks.push(obj.text);\n if (obj.type === \"toolCall\" && typeof obj.name === \"string\") {\n chunks.push(`Tool ${obj.name} called with ${JSON.stringify(obj.arguments ?? {})}`);\n }\n }\n return chunks.join(\"\\n\");\n}\n\nfunction partsFromMessage(message: PiMessage, renderedContent: string): ObserveMessagePart[] {\n return parsePiMessageParts(message, {\n renderedContent,\n allowRenderedFallback: true,\n }).map(toObserveMessagePart);\n}\n\nfunction toObserveMessagePart(part: LcmMessagePartInput): ObserveMessagePart {\n return {\n ordinal: part.ordinal ?? undefined,\n kind: part.kind,\n payload: part.payload,\n toolName: part.toolName ?? part.tool_name ?? undefined,\n filePath: part.filePath ?? part.file_path ?? undefined,\n createdAt: part.createdAt ?? part.created_at ?? undefined,\n };\n}\n\nfunction stableObservedMessageIdentity(rawContent: unknown): string | null {\n if (rawContent && typeof rawContent === \"object\") {\n const obj = rawContent as PiMessage;\n const fields = [\n \"id\",\n \"entryId\",\n \"entry_id\",\n \"messageId\",\n \"message_id\",\n \"turnId\",\n \"turn_id\",\n \"timestamp\",\n \"createdAt\",\n \"created_at\",\n ];\n for (const field of fields) {\n const value = obj[field];\n if (typeof value === \"string\" && value.length > 0) return `${field}:${value}`;\n if (typeof value === \"number\" && Number.isFinite(value)) return `${field}:${value}`;\n }\n }\n return null;\n}\n"],"mappings":"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","names":["path","remnicPiExtension"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/client.ts","../src/messages.ts"],"sourcesContent":["import { Type, type TSchema } from \"@sinclair/typebox\";\n\nimport { loadConfig, type LoadConfigOptions, type RemnicPiConfig } from \"./config.js\";\nimport { RemnicClient, RemnicHttpError, isTransientNetworkError, type McpTool, type ObserveMessage } from \"./client.js\";\nimport {\n hashObservedMessage,\n observedMessageDedupeKey,\n sessionKeyFromContext,\n summarizeMessages,\n textFromMessage,\n toObserveMessage,\n} from \"./messages.js\";\n\ntype PiApi = {\n on(event: string, handler: (event: any, ctx: any) => unknown | Promise<unknown>): void;\n registerCommand(name: string, options: { description?: string; handler: (args: string, ctx: any) => Promise<void> }): void;\n registerTool(tool: Record<string, unknown>): void;\n appendEntry<T = unknown>(customType: string, data?: T): void;\n};\n\nexport interface RemnicPiExtensionOptions extends LoadConfigOptions {\n config?: RemnicPiConfig;\n}\n\nconst STATE_CUSTOM_TYPE = \"remnic_state\";\nconst MAX_OBSERVED_HASHES = 2000;\nconst MAX_SESSION_STATES = 50;\nconst MAX_CONTEXT_CHARS = 12000;\nconst TRUNCATION_NOTICE = \"\\n\\n[Remnic context truncated]\";\nconst SESSION_OWNED_FIELDS = new Set([\"sessionKey\", \"namespace\", \"cwd\"]);\n\ntype PiSessionState = {\n observedHashes: Set<string>;\n liveObservedReplayKeys: Map<string, number>;\n /** Cached recall context — populated from the first context event's user prompt,\n * reused byte-identically across subsequent turns for KV cache prefix stability. */\n cachedContext: string | null;\n /** True once recall has been attempted (success or failure) for this session. */\n recallCompleted: boolean;\n};\n\ntype NotifyLevel = \"info\" | \"success\" | \"warning\" | \"error\";\ntype NotifyFn = (message: string, level: NotifyLevel) => void;\n\ntype PiContextSnapshot = {\n sessionKey: string;\n cwd: string;\n entries: any[];\n branch: any[];\n notify: NotifyFn;\n setStatus: (key: string, value: string) => void;\n compact?: () => unknown;\n};\ntype PiContextSnapshotOptions = {\n includeSessionHistory?: boolean;\n};\n\nexport function createRemnicPiExtension(options: RemnicPiExtensionOptions = {}) {\n const config = options.config ?? loadConfig(options);\n const client = new RemnicClient(config);\n const sessionStates = new Map<string, PiSessionState>();\n\n return async function remnicPiExtension(pi: PiApi): Promise<void> {\n pi.on(\"session_start\", async (_event, ctx) => {\n const session = snapshotPiContext(ctx, { includeSessionHistory: true });\n if (!session) return;\n const { state } = getSessionState(session.sessionKey, sessionStates);\n restoreObservedState(session, state.observedHashes);\n state.cachedContext = null;\n state.recallCompleted = false;\n\n // Probe health + update the circuit breaker UNCONDITIONALLY so an offline\n // daemon is marked unreachable even when the status UI is off; otherwise\n // the namespace preflight and every later hook each burn a full request\n // budget on a doomed call. The status LABEL stays gated on statusEnabled.\n const probe = await probeDaemonHealth(client, config);\n if (config.statusEnabled) {\n session.setStatus(\"remnic\", remnicStatusLabel(probe, config.namespace));\n }\n await runNamespacePreflight(pi, session, client, config);\n });\n\n pi.on(\"before_agent_start\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n const { state } = getSessionState(session.sessionKey, sessionStates);\n\n // On the first before_agent_start, populate the cache using the user's\n // prompt text so semantic recall produces relevant results. Once\n // populated, the cached context is appended to the system prompt on\n // every turn for KV cache prefix stability (PR #2208).\n if (!state.recallCompleted && config.recallEnabled && config.authToken && client.isReachable()) {\n const promptText = typeof event.prompt === \"string\" && event.prompt.trim().length > 0 ? event.prompt : \"\";\n if (promptText) {\n state.recallCompleted = true;\n try {\n const recalled = await client.recall(promptText, session.sessionKey, session.cwd, {\n timeoutMs: config.turnRequestTimeoutMs,\n });\n client.markReachable();\n const context = trimContext(recalled.context ?? \"\", config.recallBudgetChars);\n if (context) {\n state.cachedContext = context;\n }\n } catch (err) {\n if (isDaemonUnreachableError(err)) client.markUnreachable(config.daemonCooldownMs);\n session.notify(`Remnic recall unavailable: ${errorMessage(err)}`, \"warning\");\n }\n }\n }\n\n // Inject cached context into the system prompt — byte-identical across\n // turns so the KV cache prefix is preserved. This avoids the\n // system-role message issue: Pi's AgentMessage union does not include\n // \"system\", so injecting via the context hook produced an invalid\n // provider payload that Pi drops (codex review).\n if (!state.cachedContext) return;\n const basePrompt = typeof event.systemPrompt === \"string\" ? event.systemPrompt : \"\";\n return {\n systemPrompt: `${basePrompt}\\n\\n${state.cachedContext}`,\n };\n });\n\n pi.on(\"message_end\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n if (!config.observeEnabled || !isUserMessage(event.message)) return;\n const { state } = getSessionState(session.sessionKey, sessionStates);\n await observeMessagesForSession(session, client, [event.message], state.observedHashes, state.liveObservedReplayKeys, config);\n });\n\n pi.on(\"turn_end\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n if (!config.observeEnabled) return;\n const messages = [event.message, ...(Array.isArray(event.toolResults) ? event.toolResults : [])];\n const { state } = getSessionState(session.sessionKey, sessionStates);\n await observeMessagesForSession(session, client, messages, state.observedHashes, state.liveObservedReplayKeys, config);\n });\n\n pi.on(\"session_shutdown\", async (_event, ctx) => {\n const session = snapshotPiContext(ctx, { includeSessionHistory: true });\n if (!session) return;\n const { sessionKey, state } = getSessionState(session.sessionKey, sessionStates);\n if (config.observeEnabled) {\n const branchMessages = branchMessagesWithEntryIdentity(session.branch);\n const unobservedBranchMessages = skipLiveObservedReplayMessages(session.sessionKey, branchMessages, state.liveObservedReplayKeys);\n if (unobservedBranchMessages.length > 0) {\n await observeMessagesForSession(session, client, unobservedBranchMessages, state.observedHashes, undefined, config, true);\n }\n }\n persistObservedState(pi, state.observedHashes);\n sessionStates.delete(sessionKey);\n });\n\n pi.on(\"session_before_compact\", async (event, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n if (!config.compactionEnabled || !config.authToken) return;\n const preparation = event.preparation ?? {};\n try {\n await client.lcmCompactionFlush(session.sessionKey);\n } catch (err) {\n session.notify(`Remnic LCM flush failed: ${errorMessage(err)}`, \"warning\");\n return;\n }\n\n const tokensBefore = finiteTokenCount(preparation.tokensBefore);\n const tokensAfter = finiteTokenCount(preparation.tokensAfter);\n if (tokensBefore !== null && tokensAfter !== null) {\n try {\n await client.lcmCompactionRecord(session.sessionKey, tokensBefore, tokensAfter);\n } catch (err) {\n session.notify(`Remnic LCM compaction token record failed: ${errorMessage(err)}`, \"warning\");\n }\n }\n\n const summary = buildCompactionSummary(preparation);\n if (!summary.trim()) return;\n try {\n await client.contextCheckpoint(session.sessionKey, summary);\n } catch (err) {\n session.notify(`Remnic context checkpoint failed: ${errorMessage(err)}`, \"warning\");\n }\n const details = fileDetailsFromPreparation(preparation);\n return {\n compaction: {\n summary,\n firstKeptEntryId: preparation.firstKeptEntryId,\n tokensBefore: preparation.tokensBefore,\n details: {\n ...details,\n remnic: { version: 1, source: \"pi\" },\n },\n },\n };\n });\n\n registerCommands(pi, client, config);\n if (config.mcpToolsEnabled && config.authToken) {\n await registerMcpTools(pi, client, config);\n }\n };\n}\n\nexport default async function remnicPiExtension(pi: PiApi): Promise<void> {\n await createRemnicPiExtension()(pi);\n}\n\nfunction registerCommands(pi: PiApi, client: RemnicClient, config: RemnicPiConfig): void {\n pi.registerCommand(\"remnic-status\", {\n description: \"Check Remnic daemon status\",\n handler: commandHandler(async (_args, _ctx, session) => {\n const health = await client.health();\n // The daemon responded (any HTTP result), so clear any stale cooldown a\n // prior timeout left on the shared client (cursor review).\n client.markReachable();\n session.notify(`Remnic ${health.ok ? \"healthy\" : \"unhealthy\"} at ${config.remnicDaemonUrl}`, health.ok ? \"success\" : \"warning\");\n }),\n });\n\n pi.registerCommand(\"remnic-recall\", {\n description: \"Recall Remnic context for a query\",\n handler: commandHandler(async (args, _ctx, session) => {\n const query = args.trim();\n if (!query) {\n session.notify(\"Usage: /remnic-recall <query>\", \"warning\");\n return;\n }\n // Pass the general request budget so requestWithRetry shares ONE deadline\n // across retries (total <= requestTimeoutMs) instead of looping through\n // observeMaxRetries full timeouts and blocking the interactive command\n // for several minutes on a flaky connection (cursor review).\n const result = await client.recall(query, session.sessionKey, session.cwd, {\n timeoutMs: config.requestTimeoutMs,\n });\n // The daemon responded, so clear any stale cooldown a prior timeout left\n // on the shared client (cursor review).\n client.markReachable();\n session.notify(trimContext(result.context ?? \"(no Remnic context)\", MAX_CONTEXT_CHARS), \"info\");\n }),\n });\n\n pi.registerCommand(\"remnic-remember\", {\n description: \"Store a Remnic memory\",\n handler: commandHandler(async (args, _ctx, session) => {\n const content = args.trim();\n if (!content) {\n session.notify(\"Usage: /remnic-remember <memory>\", \"warning\");\n return;\n }\n await client.storeMemory(content, session.sessionKey);\n session.notify(\"Stored Remnic memory\", \"success\");\n }),\n });\n\n pi.registerCommand(\"remnic-lcm-search\", {\n description: \"Search Remnic LCM archived Pi context\",\n handler: commandHandler(async (args, _ctx, session) => {\n const query = args.trim();\n if (!query) {\n session.notify(\"Usage: /remnic-lcm-search <query>\", \"warning\");\n return;\n }\n const result = await client.lcmSearch(query, session.sessionKey);\n session.notify(JSON.stringify(result, null, 2), \"info\");\n }),\n });\n\n pi.registerCommand(\"remnic-why\", {\n description: \"Explain the last Remnic recall\",\n handler: commandHandler(async (_args, _ctx, session) => {\n const result = await client.recallExplain(session.sessionKey);\n session.notify(JSON.stringify(result, null, 2), \"info\");\n }),\n });\n\n pi.registerCommand(\"remnic-compact\", {\n description: \"Trigger Pi compaction with Remnic LCM coordination\",\n handler: commandHandler(async (_args, _ctx, session) => {\n session.compact?.();\n session.notify(\"Compaction requested\", \"info\");\n }),\n });\n}\n\nfunction commandHandler(\n handler: (args: string, ctx: any, session: PiContextSnapshot) => Promise<void>,\n): (args: string, ctx: any) => Promise<void> {\n return async (args, ctx) => {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n try {\n await handler(args, ctx, session);\n } catch (err) {\n session.notify(`Remnic command failed: ${errorMessage(err)}`, \"warning\");\n }\n };\n}\n\nasync function registerMcpTools(pi: PiApi, client: RemnicClient, config: RemnicPiConfig): Promise<void> {\n let tools: McpTool[] = [];\n try {\n tools = await client.mcpListTools({ timeoutMs: config.startupRequestTimeoutMs });\n } catch {\n return;\n }\n for (const tool of tools) {\n if (!tool.name.startsWith(\"remnic.\")) continue;\n const piToolName = tool.name.replace(/^remnic\\./, \"remnic_\").replace(/[^a-zA-Z0-9_]/g, \"_\");\n pi.registerTool({\n name: piToolName,\n label: tool.name,\n description: tool.description ?? `Call ${tool.name}`,\n parameters: toPiToolParametersSchema(tool.inputSchema),\n async execute(_toolCallId: string, params: Record<string, unknown>, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: any) {\n const session = snapshotPiContext(ctx);\n if (!session) {\n return {\n content: [{ type: \"text\", text: \"Remnic tool skipped because the Pi context is no longer active.\" }],\n details: { skipped: true, reason: \"stale_context\" },\n };\n }\n const safeParams = stripSessionOwnedRuntimeFields(params ?? {}) as Record<string, unknown>;\n const result = await client.mcpTool(tool.name, {\n ...safeParams,\n sessionKey: session.sessionKey,\n namespace: config.namespace,\n cwd: session.cwd,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n details: result,\n };\n },\n });\n }\n}\n\nexport function toPiToolParametersSchema(inputSchema: unknown): TSchema {\n return Type.Unsafe(stripSessionOwnedSchemaFields(inputSchema));\n}\n\nexport function stripSessionOwnedSchemaFields(inputSchema: unknown): Record<string, unknown> {\n if (!isRecord(inputSchema)) {\n return { type: \"object\", properties: {}, additionalProperties: true };\n }\n return stripSessionOwnedSchemaNode(inputSchema) as Record<string, unknown>;\n}\n\nfunction stripSessionOwnedSchemaNode(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((entry) => stripSessionOwnedSchemaNode(entry));\n }\n if (!isRecord(value)) {\n return value;\n }\n const schema: Record<string, unknown> = { ...value };\n if (isRecord(value.properties)) {\n const properties: Record<string, unknown> = {};\n for (const [key, property] of Object.entries(value.properties)) {\n if (SESSION_OWNED_FIELDS.has(key)) continue;\n properties[key] = stripSessionOwnedSchemaNode(property);\n }\n schema.properties = properties;\n }\n if (Array.isArray(value.required)) {\n schema.required = value.required.filter(\n (field) => typeof field !== \"string\" || !SESSION_OWNED_FIELDS.has(field),\n );\n }\n for (const key of [\"items\", \"additionalProperties\", \"not\"] as const) {\n if (isRecord(value[key])) {\n schema[key] = stripSessionOwnedSchemaNode(value[key]);\n }\n }\n for (const key of [\"oneOf\", \"anyOf\", \"allOf\"] as const) {\n if (Array.isArray(value[key])) {\n schema[key] = value[key].map((entry) => stripSessionOwnedSchemaNode(entry));\n }\n }\n return schema;\n}\n\nexport function stripSessionOwnedRuntimeFields(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((entry) => stripSessionOwnedRuntimeFields(entry));\n }\n if (!isRecord(value)) {\n return value;\n }\n const sanitized: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n if (SESSION_OWNED_FIELDS.has(key)) continue;\n sanitized[key] = stripSessionOwnedRuntimeFields(child);\n }\n return sanitized;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isUserMessage(message: unknown): boolean {\n return isRecord(message) && message.role === \"user\";\n}\n\nfunction getSessionState(sessionKey: string, states: Map<string, PiSessionState>): { sessionKey: string; state: PiSessionState } {\n let state = states.get(sessionKey);\n if (!state) {\n state = {\n observedHashes: new Set<string>(),\n liveObservedReplayKeys: new Map<string, number>(),\n cachedContext: null,\n recallCompleted: false,\n };\n states.set(sessionKey, state);\n pruneSessionStates(states);\n }\n return { sessionKey, state };\n}\n\nfunction pruneSessionStates(states: Map<string, PiSessionState>): void {\n while (states.size > MAX_SESSION_STATES) {\n const oldest = states.keys().next().value;\n if (typeof oldest !== \"string\") return;\n states.delete(oldest);\n }\n}\n\nexport async function observeMessages(\n ctx: any,\n client: RemnicClient,\n rawMessages: unknown[],\n observedHashes: Set<string>,\n liveObservedReplayKeys?: Map<string, number>,\n): Promise<void> {\n const session = snapshotPiContext(ctx);\n if (!session) return;\n await observeMessagesForSession(session, client, rawMessages, observedHashes, liveObservedReplayKeys);\n}\n\nasync function observeMessagesForSession(\n session: PiContextSnapshot,\n client: RemnicClient,\n rawMessages: unknown[],\n observedHashes: Set<string>,\n liveObservedReplayKeys?: Map<string, number>,\n config?: RemnicPiConfig,\n forceAttempt = false,\n): Promise<void> {\n const messages: ObserveMessage[] = [];\n const pendingHashes = new Set<string>();\n for (const raw of rawMessages) {\n const message = toObserveMessage(raw);\n if (!message) continue;\n const hash = observedMessageDedupeKey(message, session.sessionKey);\n if (hash && (observedHashes.has(hash) || pendingHashes.has(hash))) continue;\n if (hash) pendingHashes.add(hash);\n messages.push(message);\n }\n if (messages.length === 0) return;\n // Circuit breaker: when config is wired (the live Pi handlers always pass\n // it), skip observe fast while the daemon is known-down so a dead host\n // doesn't burn the full per-turn budget on every turn (#1626). Shutdown is\n // the exception — it is the last chance to observe the branch before the\n // session tears down, so force the attempt even mid-cooldown; a failure\n // still trips the breaker normally (codex review).\n if (config && !forceAttempt && !client.isReachable()) return;\n // Live turn hooks are bounded by the per-turn budget to protect the host's\n // ~30s handler window (#1626). Shutdown is teardown with no such constraint,\n // so the forced replay uses the general request budget — otherwise a large\n // unobserved branch would time out exactly when forceAttempt tried to save it\n // (cursor review).\n const observeOptions = config\n ? { timeoutMs: forceAttempt ? config.requestTimeoutMs : config.turnRequestTimeoutMs }\n : undefined;\n try {\n await client.observe(session.sessionKey, session.cwd, messages, observeOptions);\n if (config) client.markReachable();\n for (const hash of pendingHashes) rememberObservedHash(observedHashes, hash);\n if (liveObservedReplayKeys) {\n for (const message of messages) {\n rememberLiveObservedReplayKey(liveObservedReplayKeys, liveReplayKey(message, session.sessionKey));\n }\n }\n } catch (err) {\n if (config && isDaemonUnreachableError(err)) client.markUnreachable(config.daemonCooldownMs);\n session.notify(`Remnic observe failed: ${errorMessage(err)}`, \"warning\");\n }\n}\n\nexport function buildCompactionSummary(preparation: any): string {\n const previousSummary = typeof preparation.previousSummary === \"string\"\n ? preparation.previousSummary.trim()\n : \"\";\n const messages = [\n ...(Array.isArray(preparation.messagesToSummarize) ? preparation.messagesToSummarize : []),\n ...(Array.isArray(preparation.turnPrefixMessages) ? preparation.turnPrefixMessages : []),\n ];\n const transcript = summarizeMessages(messages, 24000);\n const details = fileDetailsFromPreparation(preparation);\n\n if (\n !previousSummary &&\n !transcript &&\n details.readFiles.length === 0 &&\n details.modifiedFiles.length === 0\n ) {\n return \"\";\n }\n\n const sections: string[] = [\n \"## Remnic Pi Context Checkpoint\",\n \"\",\n \"This checkpoint was created by Remnic during Pi context compaction.\",\n ];\n if (previousSummary) sections.push(\"\", \"## Previous Summary\", previousSummary);\n if (transcript) sections.push(\"\", \"## Conversation Excerpt\", transcript);\n if (details.readFiles.length > 0) sections.push(\"\", \"<read-files>\", ...details.readFiles, \"</read-files>\");\n if (details.modifiedFiles.length > 0) sections.push(\"\", \"<modified-files>\", ...details.modifiedFiles, \"</modified-files>\");\n return sections.join(\"\\n\");\n}\n\nfunction fileDetailsFromPreparation(preparation: any): { readFiles: string[]; modifiedFiles: string[] } {\n const fileOps = preparation?.fileOps;\n const read = fileOps?.read instanceof Set ? Array.from(fileOps.read).filter(isString) : [];\n const edited = fileOps?.edited instanceof Set ? Array.from(fileOps.edited).filter(isString) : [];\n const written = fileOps?.written instanceof Set ? Array.from(fileOps.written).filter(isString) : [];\n const modified = new Set([...edited, ...written]);\n return {\n readFiles: read.filter((file) => !modified.has(file)).sort(),\n modifiedFiles: Array.from(modified).sort(),\n };\n}\n\nfunction restoreObservedState(session: PiContextSnapshot, observedHashes: Set<string>): void {\n for (const entry of session.entries) {\n if (entry?.type !== \"custom\" || entry.customType !== STATE_CUSTOM_TYPE) continue;\n const hashes = entry.data?.observedHashes;\n if (Array.isArray(hashes)) {\n for (const hash of hashes) {\n if (typeof hash === \"string\") rememberObservedHash(observedHashes, hash);\n }\n }\n }\n}\n\nfunction rememberObservedHash(observedHashes: Set<string>, hash: string): void {\n if (observedHashes.has(hash)) return;\n while (observedHashes.size >= MAX_OBSERVED_HASHES) {\n const oldest = observedHashes.keys().next().value;\n if (typeof oldest !== \"string\") break;\n observedHashes.delete(oldest);\n }\n observedHashes.add(hash);\n}\n\nfunction rememberLiveObservedReplayKey(liveObservedReplayKeys: Map<string, number>, key: string): void {\n liveObservedReplayKeys.set(key, (liveObservedReplayKeys.get(key) ?? 0) + 1);\n}\n\nfunction consumeLiveObservedReplayKey(liveObservedReplayKeys: Map<string, number>, key: string): boolean {\n const count = liveObservedReplayKeys.get(key) ?? 0;\n if (count <= 0) return false;\n if (count === 1) liveObservedReplayKeys.delete(key);\n else liveObservedReplayKeys.set(key, count - 1);\n return true;\n}\n\nfunction skipLiveObservedReplayMessages(\n sessionKey: string,\n rawMessages: unknown[],\n liveObservedReplayKeys: Map<string, number>,\n): unknown[] {\n if (liveObservedReplayKeys.size === 0) return rawMessages;\n const unobserved: unknown[] = [];\n for (const raw of rawMessages) {\n const message = toObserveMessage(raw);\n if (message && consumeLiveObservedReplayKey(liveObservedReplayKeys, liveReplayKey(message, sessionKey))) {\n continue;\n }\n unobserved.push(raw);\n }\n return unobserved;\n}\n\nfunction liveReplayKey(message: ObserveMessage, sessionKey: string): string {\n return hashObservedMessage(message, sessionKey, \"live-replay\");\n}\n\nfunction persistObservedState(pi: PiApi, observedHashes: Set<string>): void {\n const observed = Array.from(observedHashes).slice(-MAX_OBSERVED_HASHES);\n pi.appendEntry(STATE_CUSTOM_TYPE, {\n observedHashes: observed,\n recordedAt: new Date().toISOString(),\n });\n}\n\n/** Result of the session_start daemon probe. */\ntype DaemonProbeResult = \"ready\" | \"starting\" | \"unreachable\";\n\n/**\n * Probe the daemon and update the shared circuit breaker. Returns the probe\n * outcome so the caller can render a status label. This runs at session_start\n * regardless of `statusEnabled`: the breaker update is a data-path concern (a\n * down daemon must be marked unreachable so the namespace preflight and every\n * later hook fast-skip instead of each burning a full request budget),\n * independent of whether the status UI is shown.\n *\n * A 503 `not_ready` answer is NOT offline (issue #2215): the daemon responded\n * — it is up and serving recall via fallback retrieval while startup search\n * warm-up is still running — so it counts as reachable and renders as\n * \"starting\" instead of tripping the breaker or claiming the service is down.\n */\nasync function probeDaemonHealth(client: RemnicClient, config: RemnicPiConfig): Promise<DaemonProbeResult> {\n try {\n await client.health({ timeoutMs: config.startupRequestTimeoutMs });\n // A successful probe means the daemon is reachable, so clear any stale\n // cooldown a prior recall/observe timeout left on the shared client.\n client.markReachable();\n return \"ready\";\n } catch (err) {\n if (err instanceof RemnicHttpError && err.status === 503 && err.code === \"not_ready\") {\n client.markReachable();\n return \"starting\";\n }\n // Startup just proved the daemon is unreachable, so trip the fast-skip\n // breaker — otherwise the first live hook spends the full turn budget on a\n // doomed request before the breaker would trip on its own.\n if (isDaemonUnreachableError(err)) client.markUnreachable(config.daemonCooldownMs);\n return \"unreachable\";\n }\n}\n\n/** Status-line label for the session_start probe outcome. */\nfunction remnicStatusLabel(probe: DaemonProbeResult, namespace: string | undefined): string {\n if (probe === \"unreachable\") return \"Remnic offline\";\n if (probe === \"starting\") return \"Remnic starting\";\n return `Remnic ${namespace ? `(${namespace})` : \"ready\"}`;\n}\n\n/**\n * Startup namespace-writability preflight (issue #1888 part 3). Runs at each\n * session_start. When the configured namespace is NOT writable for this\n * client's principal, every memory write is rejected and — since the\n * dead-letter quarantine landed — parked, never stored. A silent per-call\n * rejection is invisible; this surfaces it LOUDLY and persistently (an error\n * `remnic_state` entry + error notification, re-emitted every session while\n * broken).\n *\n * `appendEntry` has no delete, so the CURRENT state is always recorded: a\n * `NAMESPACE_OK` entry on a writable result makes the latest `remnic_state`\n * entry authoritative even across an extension/host restart (no in-memory\n * transition tracking that a restart would lose). Only errors notify — the OK\n * entry is a silent heartbeat, never a success toast every healthy session. A\n * daemon that cannot be reached (indeterminate) records nothing, leaving the\n * last known state intact — we neither cry wolf nor falsely clear a real error.\n */\nasync function runNamespacePreflight(\n pi: PiApi,\n session: PiContextSnapshot,\n client: RemnicClient,\n config: RemnicPiConfig,\n): Promise<void> {\n // No token → the client cannot write anyway; known-unreachable → the answer\n // would be indeterminate. Either way, do not touch the recorded state.\n if (!config.authToken || !client.isReachable()) return;\n const result = await client.preflightNamespace(session.sessionKey, {\n timeoutMs: config.startupRequestTimeoutMs,\n });\n if (result.status === \"not_writable\") {\n // The remediation differs by cause: `unsupported` means the daemon has\n // namespaces disabled, so ONLY its default namespace is writable — pointing\n // the operator at namespacePolicies would be misleading.\n const fix =\n result.reason === \"unsupported\"\n ? \"The daemon has namespaces disabled, so only its default namespace is writable — set the client's namespace to the daemon's defaultNamespace (or omit it).\"\n : \"Fix the client's namespace config: it must match a namespacePolicies entry, or be the daemon's defaultNamespace/sharedNamespace.\";\n const message =\n `Remnic: configured namespace \"${result.namespace}\" is NOT writable for this client's principal ` +\n `(${result.reason}). Every memory write will be rejected and dead-lettered (recoverable via ` +\n `\\`remnic quarantine list\\`), NOT stored. ${fix}`;\n session.notify(message, \"error\");\n pi.appendEntry(STATE_CUSTOM_TYPE, {\n level: \"error\",\n code: \"NAMESPACE_NOT_WRITABLE\",\n namespace: result.namespace,\n reason: result.reason,\n message,\n persistent: true,\n recordedAt: new Date().toISOString(),\n });\n return;\n }\n if (result.status === \"writable\") {\n pi.appendEntry(STATE_CUSTOM_TYPE, {\n level: \"info\",\n code: \"NAMESPACE_OK\",\n namespace: result.namespace,\n recordedAt: new Date().toISOString(),\n });\n }\n // indeterminate → record nothing; keep the last known state.\n}\n\nfunction snapshotPiContext(ctx: any, options: PiContextSnapshotOptions = {}): PiContextSnapshot | null {\n const sessionKey = safeSessionKeyFromContext(ctx);\n if (!sessionKey) return null;\n const cwd = safeStringRead(() => ctx?.cwd, \"\");\n const hasUI = safeRead(() => ctx?.hasUI, undefined) === false;\n const ui = hasUI ? undefined : safeRead(() => ctx?.ui, undefined);\n const compact = safeRead(() => ctx?.compact, undefined);\n const includeSessionHistory = options.includeSessionHistory === true;\n return {\n sessionKey,\n cwd,\n entries: includeSessionHistory ? safeEntries(ctx) : [],\n branch: includeSessionHistory ? safeBranch(ctx) : [],\n notify: makeNotifier(ui, hasUI),\n setStatus: makeStatusSetter(ui, hasUI),\n compact: typeof compact === \"function\" ? () => compact.call(ctx) : undefined,\n };\n}\n\nfunction safeSessionKeyFromContext(ctx: any): string | null {\n try {\n return sessionKeyFromContext(ctx);\n } catch {\n return null;\n }\n}\n\nfunction makeNotifier(ui: unknown, hasUI: boolean): NotifyFn {\n if (hasUI || !isRecord(ui) || typeof ui.notify !== \"function\") {\n return () => undefined;\n }\n const notifyFn = ui.notify;\n return (message, level) => {\n try {\n notifyFn.call(ui, message, level);\n } catch {\n // Pi invalidates session-bound UI objects during reload/replacement. A\n // notification failure must not tear down Remnic's hooks.\n }\n };\n}\n\nfunction makeStatusSetter(ui: unknown, hasUI: boolean): PiContextSnapshot[\"setStatus\"] {\n if (hasUI || !isRecord(ui) || typeof ui.setStatus !== \"function\") {\n return () => undefined;\n }\n const setStatusFn = ui.setStatus;\n return (key, value) => {\n try {\n setStatusFn.call(ui, key, value);\n } catch {\n // See makeNotifier: stale UI should not make extension startup fail.\n }\n };\n}\n\nfunction safeRead<T>(read: () => T, fallback: T): T {\n try {\n return read();\n } catch {\n return fallback;\n }\n}\n\nfunction safeStringRead(read: () => unknown, fallback: string): string {\n const value = safeRead(read, fallback);\n return typeof value === \"string\" ? value : fallback;\n}\n\nfunction safeEntries(ctx: any): any[] {\n try {\n const entries = ctx.sessionManager?.getEntries?.();\n return Array.isArray(entries) ? entries : [];\n } catch {\n return [];\n }\n}\n\nfunction safeBranch(ctx: any): any[] {\n try {\n const branch = ctx.sessionManager?.getBranch?.();\n return Array.isArray(branch) ? branch : [];\n } catch {\n return [];\n }\n}\n\nfunction branchMessagesWithEntryIdentity(branch: any[]): unknown[] {\n const messages: unknown[] = [];\n for (const entry of branch) {\n const message = messageWithEntryIdentity(entry);\n if (message) messages.push(message);\n }\n return messages;\n}\n\nfunction messageWithEntryIdentity(entry: any): unknown | null {\n const message = entry?.message;\n if (!message || typeof message !== \"object\" || Array.isArray(message)) return message ?? null;\n\n const source = isRecord(entry) ? entry : {};\n const enriched: Record<string, unknown> = { ...(message as Record<string, unknown>) };\n assignMissingIdentity(enriched, \"entryId\", source.id ?? source.entryId ?? source.entry_id);\n assignMissingIdentity(enriched, \"timestamp\", source.timestamp);\n assignMissingIdentity(enriched, \"createdAt\", source.createdAt ?? source.created_at);\n return enriched;\n}\n\nfunction assignMissingIdentity(target: Record<string, unknown>, field: string, value: unknown): void {\n if (target[field] !== undefined) return;\n if (typeof value === \"string\" && value.length > 0) {\n target[field] = value;\n return;\n }\n if (typeof value === \"number\" && Number.isFinite(value)) {\n target[field] = value;\n }\n}\n\nfunction trimContext(value: string, budget: number): string {\n if (value.length <= budget) return value;\n if (budget <= TRUNCATION_NOTICE.length) return TRUNCATION_NOTICE.slice(0, budget);\n return `${value.slice(0, budget - TRUNCATION_NOTICE.length)}${TRUNCATION_NOTICE}`;\n}\n\n\nexport function isDaemonUnreachableError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (/Remnic request timed out/.test(err.message)) return true;\n // Retry-budget exhaustion means transient failures ate the whole per-turn\n // deadline inside requestWithRetry — the daemon is effectively unreachable\n // for this turn, so trip the breaker and cool down instead of burning another\n // full budget on the next hook (codex review). This error only arises from\n // transient connection failures, never from a semantic HTTP response.\n if (/Remnic request exceeded the \\d+ms budget before retry/.test(err.message)) return true;\n // Multi-chunk observe throws its own budget-exceeded message when the shared\n // per-turn deadline is exhausted across chunks; that is also an effectively-\n // unreachable condition for the turn, so trip the breaker and fast-skip\n // subsequent turns instead of piling on more doomed chunked observes (cursor).\n if (/Remnic observe exceeded the per-turn budget of \\d+ms/.test(err.message)) return true;\n return isTransientNetworkError(err);\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction finiteTokenCount(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : null;\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nexport { textFromMessage };\n","import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// Leaf submodule (not the `@remnic/core` barrel) so the omp pre-bundle's\n// `bun build` does not pull the rest of core — including the LanceDB native\n// asset — into the extension bundle. See PR #1641.\nimport { expandTildePath } from \"@remnic/core/utils/path\";\n\nimport { REMNIC_PI_EXTENSION_DIR_NAME, resolvePiAgentHome } from \"./paths.js\";\n\nexport interface RemnicPiConfig {\n remnicDaemonUrl: string;\n authToken?: string;\n namespace?: string;\n recallMode: \"auto\" | \"minimal\" | \"full\" | \"graph_mode\" | \"no_recall\";\n recallTopK: number;\n recallBudgetChars: number;\n recallEnabled: boolean;\n observeEnabled: boolean;\n observeSkipExtraction: boolean;\n compactionEnabled: boolean;\n mcpToolsEnabled: boolean;\n statusEnabled: boolean;\n requestTimeoutMs: number;\n startupRequestTimeoutMs: number;\n /**\n * Per-turn request budget for observe/recall. MUST stay below the host's\n * in-handler kill budget (Pi/omp kills handlers at 30 s). Defaults to 20 s,\n * capped at 25 s so a misconfiguration can never produce a structurally\n * unsatisfiable timeout (issue #1626).\n */\n turnRequestTimeoutMs: number;\n /**\n * Soft cap on a single observe POST body in bytes. The client chunks observe\n * batches to stay under this; individual oversized messages are truncated\n * with a marker. Defaults to 100 KiB, safely under the daemon's default\n * 128 KiB `maxBodyBytes` (issue #1600).\n */\n observeMaxBytes: number;\n /**\n * Maximum retry attempts for observe/recall on transient connection-level\n * failures (socket close, ECONNRESET, EPIPE). Observe is dedupe-safe so\n * retrying is harmless (issue #1602).\n */\n observeMaxRetries: number;\n /**\n * Cooldown base for the daemon-reachability circuit breaker. When observe/\n * recall fails on a timeout or connection error, subsequent turns skip fast\n * for an exponentially growing window starting at this value (issue #1626).\n */\n daemonCooldownMs: number;\n}\n\nexport interface LoadConfigOptions {\n configPath?: string;\n env?: NodeJS.ProcessEnv;\n}\n\nconst DEFAULT_CONFIG: RemnicPiConfig = {\n remnicDaemonUrl: \"http://127.0.0.1:4318\",\n recallMode: \"auto\",\n recallTopK: 8,\n recallBudgetChars: 12000,\n recallEnabled: true,\n observeEnabled: true,\n observeSkipExtraction: false,\n compactionEnabled: true,\n mcpToolsEnabled: true,\n statusEnabled: true,\n requestTimeoutMs: 60000,\n startupRequestTimeoutMs: 1000,\n // Default 20 s is comfortably under the Pi/omp 30 s handler budget (#1626).\n turnRequestTimeoutMs: 20000,\n // Default 100 KiB leaves headroom under the daemon's 128 KiB default (#1600).\n observeMaxBytes: 102400,\n observeMaxRetries: 2,\n // Base cooldown for the circuit breaker; doubles on consecutive failures (#1626).\n daemonCooldownMs: 5000,\n};\n\nfunction defaultConfigPath(env: NodeJS.ProcessEnv): string {\n return path.join(resolvePiAgentHome(env), \"extensions\", REMNIC_PI_EXTENSION_DIR_NAME, \"remnic.config.json\");\n}\n\nfunction coerceBoolean(value: unknown, fallback: boolean, fieldName: string): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const normalized = value.trim().toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"on\"].includes(normalized)) return true;\n if ([\"false\", \"0\", \"no\", \"off\"].includes(normalized)) return false;\n }\n throw new Error(`Invalid boolean value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coercePositiveInt(value: unknown, fallback: number, max: number, fieldName: string): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n let parsed: number;\n if (typeof value === \"number\") {\n parsed = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.length === 0) return fallback;\n if (!/^[+-]?\\d+$/.test(trimmed)) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n parsed = Number(trimmed);\n } else {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n if (!Number.isInteger(parsed) || parsed <= 0 || parsed > max) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n return parsed;\n}\n\n/**\n * Like {@link coercePositiveInt} but allows 0, for knobs where 0 is a\n * meaningful \"disabled\" value (e.g. observeMaxRetries). Still rejects\n * negatives, non-integers, and values above the cap.\n */\nfunction coerceNonNegativeInt(value: unknown, fallback: number, max: number, fieldName: string): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n let parsed: number;\n if (typeof value === \"number\") {\n parsed = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.length === 0) return fallback;\n if (!/^[+-]?\\d+$/.test(trimmed)) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n parsed = Number(trimmed);\n } else {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > max) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n return parsed;\n}\n\nfunction coerceOptionalNonEmptyString(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\" && value.trim().length > 0) return value.trim();\n throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coerceOptionalString(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coerceOptionalHttpUrl(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== \"string\") {\n throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n try {\n const parsed = new URL(trimmed);\n if (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") return trimTrailingSlashes(trimmed);\n } catch {\n // Fall through to the shared error below.\n }\n throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);\n}\n\nfunction coerceRecallMode(value: unknown): RemnicPiConfig[\"recallMode\"] {\n if (value === undefined || value === null || value === \"\") return DEFAULT_CONFIG.recallMode;\n if (\n value === \"minimal\" ||\n value === \"full\" ||\n value === \"graph_mode\" ||\n value === \"no_recall\" ||\n value === \"auto\"\n ) {\n return value;\n }\n throw new Error(`Invalid recallMode value for Remnic Pi config: ${JSON.stringify(value)}`);\n}\n\nfunction readConfigFile(configPath: string): Record<string, unknown> {\n if (!existsSync(configPath)) return {};\n try {\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n throw new Error(\"expected a JSON object\");\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load Remnic Pi config at ${configPath}: ${reason}`);\n }\n}\n\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;\n return value.slice(0, end);\n}\n\nexport function resolveConfigPath(options: LoadConfigOptions = {}): string {\n const env = options.env ?? process.env;\n // REMNIC_PI_CONFIG keeps precedence for upstream Pi; REMNIC_OMP_CONFIG lets an\n // omp (oh-my-pi) direct load (`omp -e npm:@remnic/plugin-pi`) point the shared\n // runtime module at its own config without an explicit configPath. Connector\n // installs always pass an explicit configPath, so this only affects direct loads.\n return expandTildePath(\n options.configPath || env.REMNIC_PI_CONFIG || env.REMNIC_OMP_CONFIG || defaultConfigPath(env),\n );\n}\n\nexport function loadConfig(options: LoadConfigOptions = {}): RemnicPiConfig {\n const env = options.env ?? process.env;\n const fileConfig = readConfigFile(resolveConfigPath(options));\n const daemonUrl =\n coerceOptionalHttpUrl(fileConfig.remnicDaemonUrl, \"remnicDaemonUrl\") ??\n coerceOptionalHttpUrl(env.REMNIC_DAEMON_URL, \"REMNIC_DAEMON_URL\") ??\n DEFAULT_CONFIG.remnicDaemonUrl;\n const authToken =\n coerceOptionalString(fileConfig.authToken, \"authToken\") ??\n coerceOptionalString(env.REMNIC_PI_AUTH_TOKEN, \"REMNIC_PI_AUTH_TOKEN\");\n const namespace = coerceOptionalNonEmptyString(fileConfig.namespace, \"namespace\");\n\n const requestTimeoutMs = coercePositiveInt(\n fileConfig.requestTimeoutMs,\n DEFAULT_CONFIG.requestTimeoutMs,\n 60_000,\n \"requestTimeoutMs\",\n );\n // When turnRequestTimeoutMs is not explicitly set, derive it from the\n // configured requestTimeoutMs (capped at the default turn budget) so an\n // existing install that lowered requestTimeoutMs below 20s keeps its tighter\n // per-turn budget instead of being silently raised back to 20s (codex review).\n const turnFallback = Math.min(requestTimeoutMs, DEFAULT_CONFIG.turnRequestTimeoutMs);\n const turnRequestTimeoutMs = coercePositiveInt(\n fileConfig.turnRequestTimeoutMs,\n turnFallback,\n 25_000,\n \"turnRequestTimeoutMs\",\n );\n\n return {\n remnicDaemonUrl: daemonUrl,\n authToken,\n namespace,\n recallMode: coerceRecallMode(fileConfig.recallMode),\n recallTopK: coercePositiveInt(fileConfig.recallTopK, DEFAULT_CONFIG.recallTopK, 50, \"recallTopK\"),\n recallBudgetChars: coercePositiveInt(fileConfig.recallBudgetChars, DEFAULT_CONFIG.recallBudgetChars, 64000, \"recallBudgetChars\"),\n recallEnabled: coerceBoolean(fileConfig.recallEnabled, DEFAULT_CONFIG.recallEnabled, \"recallEnabled\"),\n observeEnabled: coerceBoolean(fileConfig.observeEnabled, DEFAULT_CONFIG.observeEnabled, \"observeEnabled\"),\n observeSkipExtraction: coerceBoolean(fileConfig.observeSkipExtraction, DEFAULT_CONFIG.observeSkipExtraction, \"observeSkipExtraction\"),\n compactionEnabled: coerceBoolean(fileConfig.compactionEnabled, DEFAULT_CONFIG.compactionEnabled, \"compactionEnabled\"),\n mcpToolsEnabled: coerceBoolean(fileConfig.mcpToolsEnabled, DEFAULT_CONFIG.mcpToolsEnabled, \"mcpToolsEnabled\"),\n statusEnabled: coerceBoolean(fileConfig.statusEnabled, DEFAULT_CONFIG.statusEnabled, \"statusEnabled\"),\n requestTimeoutMs,\n startupRequestTimeoutMs: coercePositiveInt(\n fileConfig.startupRequestTimeoutMs,\n DEFAULT_CONFIG.startupRequestTimeoutMs,\n 60_000,\n \"startupRequestTimeoutMs\",\n ),\n turnRequestTimeoutMs,\n observeMaxBytes: coercePositiveInt(\n fileConfig.observeMaxBytes,\n DEFAULT_CONFIG.observeMaxBytes,\n 8_388_608,\n \"observeMaxBytes\",\n ),\n observeMaxRetries: coerceNonNegativeInt(fileConfig.observeMaxRetries, DEFAULT_CONFIG.observeMaxRetries, 5, \"observeMaxRetries\"),\n daemonCooldownMs: coercePositiveInt(fileConfig.daemonCooldownMs, DEFAULT_CONFIG.daemonCooldownMs, 60_000, \"daemonCooldownMs\"),\n };\n}\n","import type { RemnicPiConfig } from \"./config.js\";\n\nexport interface RecallResponse {\n context?: string;\n results?: Array<{ id?: string; content?: string; score?: number; category?: string }>;\n count?: number;\n}\n\nexport interface ObserveMessagePart {\n ordinal?: number;\n kind: \"text\" | \"tool_call\" | \"tool_result\" | \"patch\" | \"file_read\" | \"file_write\" | \"step_start\" | \"step_finish\" | \"snapshot\" | \"retry\";\n payload: Record<string, unknown>;\n toolName?: string | null;\n filePath?: string | null;\n createdAt?: string | null;\n}\n\nexport interface ObserveMessage {\n role: \"user\" | \"assistant\";\n content: string;\n sourceFormat?: \"pi\";\n rawContent?: unknown;\n parts?: ObserveMessagePart[];\n}\n\nexport interface McpTool {\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n}\n\nexport interface RequestOptions {\n timeoutMs?: number;\n /** Transient-retry budget for connection-level failures (socket close, ECONNRESET). */\n maxRetries?: number;\n}\n\nexport interface ObserveOptions extends RequestOptions {\n /** Soft cap on a single observe POST body in bytes; oversize batches are chunked. */\n maxBytes?: number;\n}\n\n/**\n * Result of a startup namespace-writability preflight (issue #1888 part 3).\n * `not_writable` is a DEFINITIVE misconfiguration answer from the daemon (the\n * configured namespace resolves as non-writable for this principal), which the\n * client surfaces loudly. `indeterminate` means the daemon could not be reached\n * or answered unexpectedly (timeout, network, auth, 5xx) — the client must NOT\n * cry wolf about the namespace on those, since the answer is unknown.\n */\nexport type NamespacePreflightResult =\n | { readonly status: \"writable\"; readonly namespace: string }\n | { readonly status: \"not_writable\"; readonly reason: \"not_writable\" | \"unsupported\"; readonly namespace: string }\n | { readonly status: \"indeterminate\"; readonly detail: string };\n\nexport class RemnicHttpError extends Error {\n constructor(\n readonly status: number,\n message: string,\n /** Machine-readable error code from the daemon's JSON error body (e.g. `not_ready`). */\n readonly code?: string,\n ) {\n super(message);\n }\n}\n\ninterface ObserveBody {\n sessionKey: string;\n cwd: string;\n namespace?: string;\n skipExtraction: boolean;\n messages: ObserveMessage[];\n}\n\nconst encoder = new TextEncoder();\nconst RETRY_BASE_DELAY_MS = 200;\nconst MAX_COOLDOWN_MS = 60_000;\nconst TRUNCATION_MARKER = \"\\n\\n[Remnic observe truncated: payload exceeded client size cap]\";\n\nexport class RemnicClient {\n private requestId = 0;\n // Circuit-breaker state: when the daemon is known-unreachable, observe/recall\n // callers skip fast instead of blocking every turn on a doomed request (#1626).\n private unreachableUntil = 0;\n private consecutiveFailures = 0;\n\n constructor(private readonly config: RemnicPiConfig) {}\n\n /** True when the daemon is not in a known-unreachable cooldown. */\n isReachable(): boolean {\n return Date.now() >= this.unreachableUntil;\n }\n\n /** Clear the circuit breaker — call after any successful daemon interaction. */\n markReachable(): void {\n this.consecutiveFailures = 0;\n this.unreachableUntil = 0;\n }\n\n /**\n * Enter (or extend) an unreachable cooldown. The cooldown grows exponentially\n * with consecutive failures (base, 2×base, 4×base, …) capped at 60 s, so a\n * flapping daemon is retried gently while a hard-down host backs off hard.\n */\n markUnreachable(baseCooldownMs: number): void {\n this.consecutiveFailures += 1;\n const factor = 2 ** Math.min(this.consecutiveFailures - 1, 4);\n const cooldown = Math.min(baseCooldownMs * factor, MAX_COOLDOWN_MS);\n this.unreachableUntil = Date.now() + cooldown;\n }\n\n async health(options: RequestOptions = {}): Promise<Record<string, unknown>> {\n return this.request(\"GET\", \"/engram/v1/health\", undefined, options);\n }\n\n /**\n * Startup namespace-writability preflight (issue #1888 part 3). Asks the\n * daemon — read-only, no write, no side effect — whether the configured\n * namespace resolves as writable for this client's (token-resolved)\n * principal. A `not_writable` answer is definitive and surfaced loudly; any\n * transport failure returns `indeterminate` so a flaky daemon never triggers\n * a false namespace-misconfig alarm.\n */\n async preflightNamespace(\n sessionKey: string | undefined,\n options: RequestOptions = {},\n ): Promise<NamespacePreflightResult> {\n const params = new URLSearchParams();\n if (this.config.namespace) params.set(\"namespace\", this.config.namespace);\n if (sessionKey) params.set(\"session\", sessionKey);\n // Check the op this client's ENABLED write path uses: automatic turn\n // capture (observe) when observation is on, else the explicit store op.\n params.set(\"op\", this.config.observeEnabled ? \"observe\" : \"memory_store\");\n const qs = params.toString();\n const path = `/engram/v1/namespace/writable${qs ? `?${qs}` : \"\"}`;\n try {\n const payload = await this.request<{ ok?: unknown; reason?: unknown; namespace?: unknown }>(\n \"GET\",\n path,\n undefined,\n options,\n );\n // Both branches require the full contract before they are trusted: an\n // `ok:true` without a concrete namespace, or an `ok:false` without a known\n // reason + concrete namespace, is malformed → indeterminate, never a false\n // writable/not-writable verdict.\n if (\n payload?.ok === true &&\n typeof payload.namespace === \"string\" &&\n payload.namespace.length > 0\n ) {\n return { status: \"writable\", namespace: payload.namespace };\n }\n if (\n payload?.ok === false &&\n (payload.reason === \"not_writable\" || payload.reason === \"unsupported\") &&\n typeof payload.namespace === \"string\" &&\n payload.namespace.length > 0\n ) {\n return { status: \"not_writable\", reason: payload.reason, namespace: payload.namespace };\n }\n return { status: \"indeterminate\", detail: \"unexpected preflight response shape\" };\n } catch (err) {\n return { status: \"indeterminate\", detail: err instanceof Error ? err.message : String(err) };\n }\n }\n\n async recall(\n query: string,\n sessionKey: string,\n cwd: string,\n options: RequestOptions = {},\n ): Promise<RecallResponse> {\n // Recall is a read-only query; retry transient connection failures with the\n // same budget as observe (#1602). Default to the per-turn budget when the\n // caller omits timeoutMs so the retries share one deadline (like observe)\n // instead of each attempt reusing the full general request timeout (cursor\n // review). Callers that need more (e.g. the manual /remnic-recall command)\n // pass an explicit timeoutMs.\n const merged: RequestOptions = {\n ...options,\n timeoutMs: options.timeoutMs ?? this.config.turnRequestTimeoutMs,\n maxRetries: options.maxRetries ?? this.config.observeMaxRetries,\n };\n return this.requestWithRetry(\n \"POST\",\n \"/engram/v1/recall\",\n {\n query,\n sessionKey,\n cwd,\n namespace: this.config.namespace,\n topK: this.config.recallTopK,\n mode: this.config.recallMode,\n },\n merged,\n );\n }\n\n async recallExplain(sessionKey: string, options: RequestOptions = {}): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\n \"POST\",\n \"/engram/v1/recall/explain\",\n {\n sessionKey,\n namespace: this.config.namespace,\n },\n options,\n );\n }\n\n async observe(\n sessionKey: string,\n cwd: string,\n messages: ObserveMessage[],\n options: ObserveOptions = {},\n ): Promise<Record<string, unknown>> {\n const maxBytes = options.maxBytes ?? this.config.observeMaxBytes;\n // Observe runs on the live turn hooks, so bound it by the per-turn budget\n // (#1626) regardless of how many chunks the payload splits into. A missing\n // override previously let single-chunk observe fall back to the 60s general\n // budget while multi-chunk used the 20s turn budget (cursor review). An\n // explicit override is honored so callers outside the turn (shutdown replay,\n // tests) can extend it.\n const turnBudgetMs = options.timeoutMs ?? this.config.turnRequestTimeoutMs;\n const retryOptions: RequestOptions = {\n timeoutMs: turnBudgetMs,\n maxRetries: options.maxRetries ?? this.config.observeMaxRetries,\n };\n const chunks = chunkObservePayload(this.config, sessionKey, cwd, messages, maxBytes);\n if (chunks.length === 1) {\n return this.requestWithRetry(\"POST\", \"/engram/v1/observe\", chunks[0], retryOptions);\n }\n // Multiple chunks: send sequentially within the SAME per-turn deadline so\n // the TOTAL observe time stays under turnBudgetMs (not per-chunk), which\n // keeps it inside the host's ~30s handler budget (#1626). Each chunk is\n // retried independently on transient connection failures; observe is\n // dedupe-safe, so a partial failure just re-sends on the next turn.\n const deadline = Date.now() + turnBudgetMs;\n const results: Record<string, unknown>[] = [];\n for (let i = 0; i < chunks.length; i++) {\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n throw new Error(\n `Remnic observe exceeded the per-turn budget of ${turnBudgetMs}ms across ${chunks.length} chunks (completed ${i})`,\n );\n }\n const chunkOptions: RequestOptions = { ...retryOptions, timeoutMs: remaining };\n const result = await this.requestWithRetry<Record<string, unknown>>(\n \"POST\",\n \"/engram/v1/observe\",\n chunks[i],\n chunkOptions,\n );\n if (result && typeof result === \"object\") {\n results.push(result);\n }\n }\n return mergeObserveResults(results);\n }\n\n async storeMemory(content: string, sessionKey: string, options: RequestOptions = {}): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/memories\", {\n content,\n category: \"fact\",\n sourceReason: \"Captured from Pi via Remnic extension\",\n sessionKey,\n namespace: this.config.namespace,\n }, options);\n }\n\n async lcmSearch(query: string, sessionKey: string, limit = 10): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/lcm/search\", {\n query,\n sessionKey,\n namespace: this.config.namespace,\n limit,\n });\n }\n\n async lcmCompactionFlush(sessionKey: string): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/lcm/compaction/flush\", {\n sessionKey,\n namespace: this.config.namespace,\n });\n }\n\n async lcmCompactionRecord(sessionKey: string, tokensBefore: number, tokensAfter: number): Promise<Record<string, unknown>> {\n return this.requestWithRetry(\"POST\", \"/engram/v1/lcm/compaction/record\", {\n sessionKey,\n namespace: this.config.namespace,\n tokensBefore,\n tokensAfter,\n });\n }\n\n async contextCheckpoint(sessionKey: string, context: string): Promise<Record<string, unknown>> {\n return this.mcpTool(\"remnic.context_checkpoint\", {\n sessionKey,\n context,\n namespace: this.config.namespace,\n });\n }\n\n async mcpListTools(options: RequestOptions = {}): Promise<McpTool[]> {\n const result = await this.mcpRequest(\"tools/list\", {}, options);\n const tools = result.tools;\n return Array.isArray(tools) ? tools.filter(isMcpTool) : [];\n }\n\n async mcpTool(name: string, args: Record<string, unknown>): Promise<Record<string, unknown>> {\n return this.mcpRequest(\"tools/call\", {\n name,\n arguments: args,\n });\n }\n\n /**\n * Single HTTP attempt with the configured timeout. No retry — retry of\n * transient connection failures lives in {@link requestWithRetry}.\n */\n private async request<T = Record<string, unknown>>(\n method: string,\n pathname: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const controller = new AbortController();\n // A per-request override is honored only when it is a finite positive number;\n // 0, negative, NaN, or non-finite values would make setTimeout abort\n // immediately (or behave erratically), so fall back to the general budget.\n // In practice the override is always sourced from the validated\n // `startupRequestTimeoutMs` / `turnRequestTimeoutMs` config, but this keeps\n // the client robust to any future caller (Copilot review).\n const override = options.timeoutMs;\n const timeoutMs =\n typeof override === \"number\" && Number.isFinite(override) && override > 0\n ? override\n : this.config.requestTimeoutMs;\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(`${this.config.remnicDaemonUrl}${pathname}`, {\n method,\n headers: {\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n ...(this.config.authToken ? { Authorization: `Bearer ${this.config.authToken}` } : {}),\n \"X-Engram-Client-Id\": \"pi\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller.signal,\n });\n const text = await response.text();\n let payload: unknown = {};\n let parseError: unknown;\n if (text) {\n try {\n payload = JSON.parse(text);\n } catch (err) {\n parseError = err;\n }\n }\n if (!response.ok) {\n const message = responseErrorMessage(response, text, payload, parseError);\n const code = responseErrorCode(payload, parseError);\n if (response.status === 413) {\n // Surface the body size so operators can tune the cap (#1600).\n const bodyBytes = body === undefined ? 0 : jsonBytes(body);\n throw new RemnicHttpError(response.status, `${message} (observed body ${bodyBytes} bytes; cap via observeMaxBytes)`, code);\n }\n throw new RemnicHttpError(response.status, message, code);\n }\n if (parseError) {\n const reason = parseError instanceof Error ? parseError.message : String(parseError);\n throw new Error(`Invalid JSON response from Remnic daemon (${response.status} ${response.statusText || \"OK\"}): ${reason}`);\n }\n return payload as T;\n } catch (err) {\n if (isAbortError(err)) {\n throw new Error(`Remnic request timed out after ${timeoutMs}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n /**\n * Wrap {@link request} with a small bounded retry loop for transient\n * connection-level failures (socket close mid-request, ECONNRESET, EPIPE).\n * Timeouts (our own AbortController) and HTTP responses (4xx/5xx) are NOT\n * retried here — timeouts already burned the full budget, and HTTP errors\n * carry semantic meaning the caller must handle. Observe/recall are\n * dedupe-safe so retrying a transiently-failed POST is harmless (#1602).\n */\n private async requestWithRetry<T = Record<string, unknown>>(\n method: string,\n pathname: string,\n body: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const maxRetries = options.maxRetries ?? 0;\n // Share ONE deadline across all attempts (including backoff sleeps) when the\n // caller passes a per-turn/per-operation budget, so a late transient failure\n // cannot burn a full timeout on every retry and overshoot the host's ~30s\n // handler window (#1602/#1626 — cursor + codex reviews). The first attempt\n // keeps the original timeoutMs verbatim (preserving error messages/timing);\n // only retries are tightened to the remaining budget.\n const budgetMs = options.timeoutMs;\n const hasDeadline = typeof budgetMs === \"number\" && Number.isFinite(budgetMs) && budgetMs > 0;\n const deadline = hasDeadline ? Date.now() + budgetMs : Number.POSITIVE_INFINITY;\n let attempt = 0;\n let attemptOptions = options;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n return await this.request<T>(method, pathname, body, attemptOptions);\n } catch (err) {\n if (attempt >= maxRetries || !isTransientNetworkError(err)) throw err;\n const delayMs = RETRY_BASE_DELAY_MS * 2 ** attempt;\n if (hasDeadline) {\n // The backoff sleep counts against the shared deadline; bail BEFORE\n // sleeping if the sleep alone would overshoot the remaining budget,\n // so a sub-backoff timeoutMs never blocks for the full backoff only\n // to then throw (cursor review).\n const remainingBeforeSleep = deadline - Date.now();\n if (remainingBeforeSleep <= delayMs) {\n throw new Error(\n `Remnic request exceeded the ${budgetMs}ms budget before retry ${attempt + 1} (${method} ${pathname})`,\n );\n }\n }\n await sleep(delayMs);\n attempt += 1;\n if (hasDeadline) {\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n throw new Error(\n `Remnic request exceeded the ${budgetMs}ms budget before retry ${attempt} (${method} ${pathname})`,\n );\n }\n attemptOptions = { ...options, timeoutMs: remaining };\n }\n }\n }\n }\n\n private async mcpRequest(\n method: string,\n params: Record<string, unknown>,\n options: RequestOptions = {},\n ): Promise<Record<string, unknown>> {\n this.requestId += 1;\n const payload = await this.request<Record<string, unknown>>(\"POST\", \"/mcp\", {\n jsonrpc: \"2.0\",\n id: this.requestId,\n method,\n params,\n }, options);\n if (payload.error) {\n throw new Error(JSON.stringify(payload.error));\n }\n return (payload.result && typeof payload.result === \"object\" ? payload.result : payload) as Record<string, unknown>;\n }\n}\n\nfunction isMcpTool(value: unknown): value is McpTool {\n return !!value && typeof value === \"object\" && \"name\" in value && typeof value.name === \"string\";\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && (err.name === \"AbortError\" || err.message === \"This operation was aborted\");\n}\n\n/**\n * Classify connection-level failures that are safe to retry: the request never\n * reached the daemon (or died mid-flight), so a retry is idempotent. Excludes\n * our own AbortController timeouts and HTTP responses (those carry meaning).\n */\nexport function isTransientNetworkError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (isAbortError(err)) return false;\n if (err instanceof RemnicHttpError) return false;\n const lower = (err.message ?? \"\").toLowerCase();\n // Bun fetch: \"The socket connection was closed unexpectedly.\"\n if (lower.includes(\"socket connection was closed\")) return true;\n if (lower.includes(\"socket closed\")) return true;\n // Node undici / OS codes surfaced in the message.\n if (lower.includes(\"econnreset\")) return true;\n if (lower.includes(\"epipe\")) return true;\n if (lower.includes(\"und_err_socket\")) return true;\n // Node wraps the real cause in err.cause (TypeError: fetch failed).\n if (lower.includes(\"fetch failed\")) return true;\n // Inspect the cause chain without an unchecked cast. Error.cause is\n // `unknown` in the ES2022 lib; narrow it before reading `.code`.\n const cause = err.cause;\n if (cause && typeof cause === \"object\" && \"code\" in cause) {\n const code = cause.code;\n if (typeof code === \"string\" && (code === \"ECONNRESET\" || code === \"EPIPE\" || code === \"UND_ERR_SOCKET\")) {\n return true;\n }\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n // Plain Promise constructor: avoids Promise.withResolvers (ES2024 / Node 22+),\n // so retry backoff works on Node 20 and other runtimes that load plugin-pi.\n return new Promise<void>(resolve => setTimeout(resolve, ms));\n}\n\nfunction jsonBytes(value: unknown): number {\n return encoder.encode(JSON.stringify(value)).length;\n}\n\nfunction buildObserveEnvelope(config: RemnicPiConfig, sessionKey: string, cwd: string, messages: ObserveMessage[]): ObserveBody {\n return {\n sessionKey,\n cwd,\n namespace: config.namespace,\n skipExtraction: config.observeSkipExtraction,\n messages,\n };\n}\n\n/**\n * Split an observe batch into POST bodies whose serialized JSON stays under\n * `maxBytes`. Single messages that alone exceed the per-message budget are\n * truncated with a marker rather than dropped, so large tool outputs still\n * leave a trace in memory (#1600).\n */\nexport function chunkObservePayload(\n config: RemnicPiConfig,\n sessionKey: string,\n cwd: string,\n messages: ObserveMessage[],\n maxBytes: number,\n): ObserveBody[] {\n const envelopeOverhead = jsonBytes(buildObserveEnvelope(config, sessionKey, cwd, []));\n const messageBudget = maxBytes - envelopeOverhead;\n if (messageBudget <= 0) {\n // The envelope overhead alone meets/exceeds the cap, so no valid body can\n // fit — return a single chunk and let the daemon reject it visibly (this is\n // a degenerate/misconfigured cap, not the common case). A small-but-positive\n // budget (<=1024) is NOT degenerate: truncate/pack normally so oversized\n // messages are shrunk to fit instead of bypassing the #1600 safeguards\n // (cursor review).\n return [buildObserveEnvelope(config, sessionKey, cwd, messages)];\n }\n const chunks: ObserveMessage[][] = [];\n let current: ObserveMessage[] = [];\n let currentSize = 0;\n const flush = (): void => {\n if (current.length > 0) {\n chunks.push(current);\n current = [];\n currentSize = 0;\n }\n };\n for (const message of messages) {\n const size = jsonBytes(message);\n if (size > messageBudget) {\n flush();\n chunks.push([truncateObserveMessage(message, messageBudget)]);\n continue;\n }\n // Account for the JSON array comma separator before this message when it is\n // not the first in the chunk, so the serialized body never overshoots the\n // cap (review: cursor).\n if (current.length > 0 && currentSize + 1 + size > messageBudget) {\n flush();\n }\n if (current.length > 0) currentSize += 1;\n current.push(message);\n currentSize += size;\n }\n flush();\n if (chunks.length === 0) {\n return [buildObserveEnvelope(config, sessionKey, cwd, [])];\n }\n return chunks.map((msgs) => buildObserveEnvelope(config, sessionKey, cwd, msgs));\n}\n\nconst decoder = new TextDecoder();\n\nfunction truncateObserveMessage(message: ObserveMessage, budgetBytes: number): ObserveMessage {\n // A truncated observe keeps ONLY role + a content marker, dropping rawContent\n // and parts. Live Pi turns carry the full original message in rawContent and\n // parsed parts; those fields dominate the serialized size and would keep the\n // chunk over the cap (defeating #1600), so they are removed — the daemon\n // extracts from content. JSON-escape-aware: measures ACTUAL jsonBytes of each\n // candidate so escaping (\\n -> \\\\\\\\n) can't overshoot.\n const slim: ObserveMessage = { role: message.role, content: \"\" };\n const markerOnly = jsonBytes({ ...slim, content: TRUNCATION_MARKER });\n if (markerOnly > budgetBytes) {\n // Pathological: even the marker alone doesn't fit. Keep it anyway so the\n // turn isn't silently dropped.\n return { role: message.role, content: TRUNCATION_MARKER };\n }\n const fullContent = message.content + TRUNCATION_MARKER;\n if (jsonBytes({ ...slim, content: fullContent }) <= budgetBytes) {\n return { role: message.role, content: fullContent };\n }\n // Binary-search the largest content slice whose slim message fits. Slicing by\n // encoded bytes keeps multi-byte sequences intact where possible; the decoder\n // replaces any dangling tail with the replacement char.\n const encoded = encoder.encode(message.content);\n let lo = 0;\n let hi = encoded.length;\n while (lo < hi) {\n const mid = hi - Math.floor((hi - lo) / 2);\n const candidate = decoder.decode(encoded.subarray(0, mid)) + TRUNCATION_MARKER;\n if (jsonBytes({ ...slim, content: candidate }) <= budgetBytes) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n const truncated = lo > 0 ? decoder.decode(encoded.subarray(0, lo)) : \"\";\n return { role: message.role, content: truncated + TRUNCATION_MARKER };\n}\n\nfunction mergeObserveResults(results: Record<string, unknown>[]): Record<string, unknown> {\n if (results.length === 0) return {};\n if (results.length === 1) return results[0];\n const merged: Record<string, unknown> = {};\n let countSum = 0;\n let hasCount = false;\n for (const result of results) {\n for (const key of Object.keys(result)) {\n const value = result[key];\n if (key === \"count\" && typeof value === \"number\" && Number.isFinite(value)) {\n countSum += value;\n hasCount = true;\n } else {\n merged[key] = value;\n }\n }\n }\n if (hasCount) merged.count = countSum;\n return merged;\n}\n\nfunction responseErrorMessage(response: Response, text: string, payload: unknown, parseError: unknown): string {\n if (!parseError && payload && typeof payload === \"object\") {\n if (\"error\" in payload && typeof payload.error === \"string\" && payload.error.trim().length > 0) {\n return payload.error;\n }\n if (\"message\" in payload && typeof payload.message === \"string\" && payload.message.trim().length > 0) {\n return payload.message;\n }\n }\n\n const snippet = text.trim().replace(/\\s+/g, \" \").slice(0, 200);\n if (snippet.length > 0) {\n return response.statusText ? `${response.statusText}: ${snippet}` : snippet;\n }\n return response.statusText || `HTTP ${response.status}`;\n}\n\nfunction responseErrorCode(payload: unknown, parseError: unknown): string | undefined {\n if (parseError || !payload || typeof payload !== \"object\") return undefined;\n if (\"code\" in payload && typeof payload.code === \"string\" && payload.code.trim().length > 0) {\n return payload.code;\n }\n return undefined;\n}\n","import { createHash } from \"node:crypto\";\n\n// Leaf submodule (not the `@remnic/core` barrel) so the omp pre-bundle's\n// `bun build` does not pull the rest of core into the extension bundle.\n// `message-parts` is a pure parser with no storage/native deps. See PR #1641.\nimport { parsePiMessageParts, type LcmMessagePartInput } from \"@remnic/core/message-parts\";\n\nimport type { ObserveMessage, ObserveMessagePart } from \"./client.js\";\n\ntype PiMessage = Record<string, unknown>;\n\nexport function sessionKeyFromContext(ctx: { sessionManager?: { getSessionId?: () => string } }): string {\n const id = ctx.sessionManager?.getSessionId?.();\n return id && id.trim().length > 0 ? `pi:${id}` : \"pi:default\";\n}\n\nexport function textFromMessage(message: unknown): string {\n if (!message || typeof message !== \"object\") return \"\";\n const obj = message as PiMessage;\n const role = typeof obj.role === \"string\" ? obj.role : \"message\";\n if (role === \"bashExecution\") {\n const command = typeof obj.command === \"string\" ? obj.command : \"\";\n const output = typeof obj.output === \"string\" ? obj.output : \"\";\n return [`Ran ${command}`, output].filter(Boolean).join(\"\\n\");\n }\n return textFromContent(obj.content).trim();\n}\n\nexport function latestUserQuery(messages: unknown[]): string {\n for (let index = messages.length - 1; index >= 0; index--) {\n const message = messages[index] as PiMessage;\n if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;\n if (message?.role === \"user\") {\n const text = textFromMessage(message);\n if (text.length > 0) return text;\n }\n }\n return \"\";\n}\n\nexport function latestUserRecallTarget(\n messages: unknown[],\n): { query: string; dedupeKey: string } | null {\n for (let index = messages.length - 1; index >= 0; index--) {\n const message = messages[index] as PiMessage;\n if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;\n if (message?.role !== \"user\") continue;\n const query = textFromMessage(message);\n if (query.length === 0) continue;\n const identity = stableObservedMessageIdentity(message);\n return {\n query,\n dedupeKey: identity ? `message:${identity}:${query}` : `query:${query}`,\n };\n }\n return null;\n}\n\nexport function toObserveMessage(message: unknown): ObserveMessage | null {\n if (!message || typeof message !== \"object\") return null;\n const obj = message as PiMessage;\n if (isExcludedFromContext(obj) || isRemnicInjected(obj)) return null;\n const role = obj.role === \"user\" || obj.role === \"bashExecution\" ? \"user\" : \"assistant\";\n const content = textFromMessage(obj);\n if (content.length === 0) return null;\n return {\n role,\n content,\n sourceFormat: \"pi\",\n rawContent: obj,\n parts: partsFromMessage(obj, content),\n };\n}\n\nexport function hashObservedMessage(message: ObserveMessage, sessionKey = \"\", identity = \"content\"): string {\n return createHash(\"sha256\")\n .update(sessionKey)\n .update(\"\\0\")\n .update(message.role)\n .update(\"\\0\")\n .update(identity)\n .update(\"\\0\")\n .update(message.content)\n .digest(\"hex\");\n}\n\nexport function observedMessageDedupeKey(\n message: ObserveMessage,\n sessionKey = \"\",\n): string | null {\n const identity = stableObservedMessageIdentity(message.rawContent);\n return identity ? hashObservedMessage(message, sessionKey, identity) : null;\n}\n\nexport function summarizeMessages(messages: unknown[], maxChars: number): string {\n const chunks: string[] = [];\n let used = 0;\n for (const message of messages) {\n if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;\n const text = textFromMessage(message);\n if (!text) continue;\n const role = typeof (message as PiMessage)?.role === \"string\" ? (message as PiMessage).role : \"message\";\n const line = `[${role}] ${text}`;\n const separatorLength = chunks.length > 0 ? 2 : 0;\n const remaining = maxChars - used - separatorLength;\n if (remaining <= 0) break;\n const clipped = line.length > remaining ? line.slice(0, remaining) : line;\n if (clipped.length > 0) chunks.push(clipped);\n used += separatorLength + clipped.length;\n if (used >= maxChars) break;\n }\n return chunks.join(\"\\n\\n\");\n}\n\nexport function isExcludedFromContext(message: unknown): boolean {\n return !!message && typeof message === \"object\" && (message as PiMessage).excludeFromContext === true;\n}\n\nexport function isRemnicInjected(message: unknown): boolean {\n return !!message && typeof message === \"object\" && (message as PiMessage).remnicInjected === true;\n}\n\nfunction textFromContent(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n const chunks: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const obj = block as PiMessage;\n if (obj.type === \"text\" && typeof obj.text === \"string\") chunks.push(obj.text);\n if (obj.type === \"toolCall\" && typeof obj.name === \"string\") {\n chunks.push(`Tool ${obj.name} called with ${JSON.stringify(obj.arguments ?? {})}`);\n }\n }\n return chunks.join(\"\\n\");\n}\n\nfunction partsFromMessage(message: PiMessage, renderedContent: string): ObserveMessagePart[] {\n return parsePiMessageParts(message, {\n renderedContent,\n allowRenderedFallback: true,\n }).map(toObserveMessagePart);\n}\n\nfunction toObserveMessagePart(part: LcmMessagePartInput): ObserveMessagePart {\n return {\n ordinal: part.ordinal ?? undefined,\n kind: part.kind,\n payload: part.payload,\n toolName: part.toolName ?? part.tool_name ?? undefined,\n filePath: part.filePath ?? part.file_path ?? undefined,\n createdAt: part.createdAt ?? part.created_at ?? undefined,\n };\n}\n\nfunction stableObservedMessageIdentity(rawContent: unknown): string | null {\n if (rawContent && typeof rawContent === \"object\") {\n const obj = rawContent as PiMessage;\n const fields = [\n \"id\",\n \"entryId\",\n \"entry_id\",\n \"messageId\",\n \"message_id\",\n \"turnId\",\n \"turn_id\",\n \"timestamp\",\n \"createdAt\",\n \"created_at\",\n ];\n for (const field of fields) {\n const value = obj[field];\n if (typeof value === \"string\" && value.length > 0) return `${field}:${value}`;\n if (typeof value === \"number\" && Number.isFinite(value)) return `${field}:${value}`;\n }\n }\n return null;\n}\n"],"mappings":";;;;;;AAAA,SAAS,YAA0B;;;ACAnC,SAAS,YAAY,oBAAoB;AACzC,OAAO,UAAU;AAKjB,SAAS,uBAAuB;AAoDhC,IAAM,iBAAiC;AAAA,EACrC,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,uBAAuB;AAAA,EACvB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,yBAAyB;AAAA;AAAA,EAEzB,sBAAsB;AAAA;AAAA,EAEtB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA;AAAA,EAEnB,kBAAkB;AACpB;AAEA,SAAS,kBAAkB,KAAgC;AACzD,SAAO,KAAK,KAAK,mBAAmB,GAAG,GAAG,cAAc,8BAA8B,oBAAoB;AAC5G;AAEA,SAAS,cAAc,OAAgB,UAAmB,WAA4B;AACpF,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,aAAa,MAAM,KAAK,EAAE,YAAY;AAC5C,QAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,EAAE,SAAS,UAAU,EAAG,QAAO;AAC5D,QAAI,CAAC,SAAS,KAAK,MAAM,KAAK,EAAE,SAAS,UAAU,EAAG,QAAO;AAAA,EAC/D;AACA,QAAM,IAAI,MAAM,oDAAoD,SAAS,EAAE;AACjF;AAEA,SAAS,kBAAkB,OAAgB,UAAkB,KAAa,WAA2B;AACnG,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,MAAI;AACJ,MAAI,OAAO,UAAU,UAAU;AAC7B,aAAS;AAAA,EACX,WAAW,OAAO,UAAU,UAAU;AACpC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAI,CAAC,aAAa,KAAK,OAAO,GAAG;AAC/B,YAAM,IAAI,MAAM,oDAAoD,SAAS,mCAAmC,GAAG,EAAE;AAAA,IACvH;AACA,aAAS,OAAO,OAAO;AAAA,EACzB,OAAO;AACL,UAAM,IAAI,MAAM,oDAAoD,SAAS,mCAAmC,GAAG,EAAE;AAAA,EACvH;AACA,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,UAAU,KAAK,SAAS,KAAK;AAC5D,UAAM,IAAI,MAAM,oDAAoD,SAAS,mCAAmC,GAAG,EAAE;AAAA,EACvH;AACA,SAAO;AACT;AAOA,SAAS,qBAAqB,OAAgB,UAAkB,KAAa,WAA2B;AACtG,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,MAAI;AACJ,MAAI,OAAO,UAAU,UAAU;AAC7B,aAAS;AAAA,EACX,WAAW,OAAO,UAAU,UAAU;AACpC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAI,CAAC,aAAa,KAAK,OAAO,GAAG;AAC/B,YAAM,IAAI,MAAM,oDAAoD,SAAS,mCAAmC,GAAG,EAAE;AAAA,IACvH;AACA,aAAS,OAAO,OAAO;AAAA,EACzB,OAAO;AACL,UAAM,IAAI,MAAM,oDAAoD,SAAS,mCAAmC,GAAG,EAAE;AAAA,EACvH;AACA,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,KAAK,SAAS,KAAK;AAC3D,UAAM,IAAI,MAAM,oDAAoD,SAAS,mCAAmC,GAAG,EAAE;AAAA,EACvH;AACA,SAAO;AACT;AAEA,SAAS,6BAA6B,OAAgB,WAAuC;AAC3F,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,MAAM,KAAK;AAC5E,QAAM,IAAI,MAAM,mDAAmD,SAAS,EAAE;AAChF;AAEA,SAAS,qBAAqB,OAAgB,WAAuC;AACnF,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,WAAO,QAAQ,SAAS,IAAI,UAAU;AAAA,EACxC;AACA,QAAM,IAAI,MAAM,mDAAmD,SAAS,EAAE;AAChF;AAEA,SAAS,sBAAsB,OAAgB,WAAuC;AACpF,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,MAAM,gDAAgD,SAAS,iCAAiC;AAAA,EAC5G;AACA,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,OAAO;AAC9B,QAAI,OAAO,aAAa,WAAW,OAAO,aAAa,SAAU,QAAO,oBAAoB,OAAO;AAAA,EACrG,QAAQ;AAAA,EAER;AACA,QAAM,IAAI,MAAM,gDAAgD,SAAS,iCAAiC;AAC5G;AAEA,SAAS,iBAAiB,OAA8C;AACtE,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO,eAAe;AACjF,MACE,UAAU,aACV,UAAU,UACV,UAAU,gBACV,UAAU,eACV,UAAU,QACV;AACA,WAAO;AAAA,EACT;AACA,QAAM,IAAI,MAAM,kDAAkD,KAAK,UAAU,KAAK,CAAC,EAAE;AAC3F;AAEA,SAAS,eAAe,YAA6C;AACnE,MAAI,CAAC,WAAW,UAAU,EAAG,QAAO,CAAC;AACrC,MAAI;AACF,UAAM,MAAM,aAAa,YAAY,OAAO;AAC5C,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG;AAClE,aAAO;AAAA,IACT;AACA,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,MAAM,sCAAsC,UAAU,KAAK,MAAM,EAAE;AAAA,EAC/E;AACF;AAEA,SAAS,oBAAoB,OAAuB;AAClD,MAAI,MAAM,MAAM;AAChB,SAAO,MAAM,KAAK,MAAM,WAAW,MAAM,CAAC,MAAM,GAAI,QAAO;AAC3D,SAAO,MAAM,MAAM,GAAG,GAAG;AAC3B;AAEO,SAAS,kBAAkB,UAA6B,CAAC,GAAW;AACzE,QAAM,MAAM,QAAQ,OAAO,QAAQ;AAKnC,SAAO;AAAA,IACL,QAAQ,cAAc,IAAI,oBAAoB,IAAI,qBAAqB,kBAAkB,GAAG;AAAA,EAC9F;AACF;AAEO,SAAS,WAAW,UAA6B,CAAC,GAAmB;AAC1E,QAAM,MAAM,QAAQ,OAAO,QAAQ;AACnC,QAAM,aAAa,eAAe,kBAAkB,OAAO,CAAC;AAC5D,QAAM,YACJ,sBAAsB,WAAW,iBAAiB,iBAAiB,KACnE,sBAAsB,IAAI,mBAAmB,mBAAmB,KAChE,eAAe;AACjB,QAAM,YACJ,qBAAqB,WAAW,WAAW,WAAW,KACtD,qBAAqB,IAAI,sBAAsB,sBAAsB;AACvE,QAAM,YAAY,6BAA6B,WAAW,WAAW,WAAW;AAEhF,QAAM,mBAAmB;AAAA,IACvB,WAAW;AAAA,IACX,eAAe;AAAA,IACf;AAAA,IACA;AAAA,EACF;AAKA,QAAM,eAAe,KAAK,IAAI,kBAAkB,eAAe,oBAAoB;AACnF,QAAM,uBAAuB;AAAA,IAC3B,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,YAAY,iBAAiB,WAAW,UAAU;AAAA,IAClD,YAAY,kBAAkB,WAAW,YAAY,eAAe,YAAY,IAAI,YAAY;AAAA,IAChG,mBAAmB,kBAAkB,WAAW,mBAAmB,eAAe,mBAAmB,MAAO,mBAAmB;AAAA,IAC/H,eAAe,cAAc,WAAW,eAAe,eAAe,eAAe,eAAe;AAAA,IACpG,gBAAgB,cAAc,WAAW,gBAAgB,eAAe,gBAAgB,gBAAgB;AAAA,IACxG,uBAAuB,cAAc,WAAW,uBAAuB,eAAe,uBAAuB,uBAAuB;AAAA,IACpI,mBAAmB,cAAc,WAAW,mBAAmB,eAAe,mBAAmB,mBAAmB;AAAA,IACpH,iBAAiB,cAAc,WAAW,iBAAiB,eAAe,iBAAiB,iBAAiB;AAAA,IAC5G,eAAe,cAAc,WAAW,eAAe,eAAe,eAAe,eAAe;AAAA,IACpG;AAAA,IACA,yBAAyB;AAAA,MACvB,WAAW;AAAA,MACX,eAAe;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,MACf,WAAW;AAAA,MACX,eAAe;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,IACA,mBAAmB,qBAAqB,WAAW,mBAAmB,eAAe,mBAAmB,GAAG,mBAAmB;AAAA,IAC9H,kBAAkB,kBAAkB,WAAW,kBAAkB,eAAe,kBAAkB,KAAQ,kBAAkB;AAAA,EAC9H;AACF;;;AChOO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACW,QACT,SAES,MACT;AACA,UAAM,OAAO;AALJ;AAGA;AAAA,EAGX;AAAA,EANW;AAAA,EAGA;AAIb;AAUA,IAAM,UAAU,IAAI,YAAY;AAChC,IAAM,sBAAsB;AAC5B,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAEnB,IAAM,eAAN,MAAmB;AAAA,EAOxB,YAA6B,QAAwB;AAAxB;AAAA,EAAyB;AAAA,EAAzB;AAAA,EANrB,YAAY;AAAA;AAAA;AAAA,EAGZ,mBAAmB;AAAA,EACnB,sBAAsB;AAAA;AAAA,EAK9B,cAAuB;AACrB,WAAO,KAAK,IAAI,KAAK,KAAK;AAAA,EAC5B;AAAA;AAAA,EAGA,gBAAsB;AACpB,SAAK,sBAAsB;AAC3B,SAAK,mBAAmB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,gBAA8B;AAC5C,SAAK,uBAAuB;AAC5B,UAAM,SAAS,KAAK,KAAK,IAAI,KAAK,sBAAsB,GAAG,CAAC;AAC5D,UAAM,WAAW,KAAK,IAAI,iBAAiB,QAAQ,eAAe;AAClE,SAAK,mBAAmB,KAAK,IAAI,IAAI;AAAA,EACvC;AAAA,EAEA,MAAM,OAAO,UAA0B,CAAC,GAAqC;AAC3E,WAAO,KAAK,QAAQ,OAAO,qBAAqB,QAAW,OAAO;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,mBACJ,YACA,UAA0B,CAAC,GACQ;AACnC,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,KAAK,OAAO,UAAW,QAAO,IAAI,aAAa,KAAK,OAAO,SAAS;AACxE,QAAI,WAAY,QAAO,IAAI,WAAW,UAAU;AAGhD,WAAO,IAAI,MAAM,KAAK,OAAO,iBAAiB,YAAY,cAAc;AACxE,UAAM,KAAK,OAAO,SAAS;AAC3B,UAAMA,QAAO,gCAAgC,KAAK,IAAI,EAAE,KAAK,EAAE;AAC/D,QAAI;AACF,YAAM,UAAU,MAAM,KAAK;AAAA,QACzB;AAAA,QACAA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAKA,UACE,SAAS,OAAO,QAChB,OAAO,QAAQ,cAAc,YAC7B,QAAQ,UAAU,SAAS,GAC3B;AACA,eAAO,EAAE,QAAQ,YAAY,WAAW,QAAQ,UAAU;AAAA,MAC5D;AACA,UACE,SAAS,OAAO,UACf,QAAQ,WAAW,kBAAkB,QAAQ,WAAW,kBACzD,OAAO,QAAQ,cAAc,YAC7B,QAAQ,UAAU,SAAS,GAC3B;AACA,eAAO,EAAE,QAAQ,gBAAgB,QAAQ,QAAQ,QAAQ,WAAW,QAAQ,UAAU;AAAA,MACxF;AACA,aAAO,EAAE,QAAQ,iBAAiB,QAAQ,sCAAsC;AAAA,IAClF,SAAS,KAAK;AACZ,aAAO,EAAE,QAAQ,iBAAiB,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAC7F;AAAA,EACF;AAAA,EAEA,MAAM,OACJ,OACA,YACA,KACA,UAA0B,CAAC,GACF;AAOzB,UAAM,SAAyB;AAAA,MAC7B,GAAG;AAAA,MACH,WAAW,QAAQ,aAAa,KAAK,OAAO;AAAA,MAC5C,YAAY,QAAQ,cAAc,KAAK,OAAO;AAAA,IAChD;AACA,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,KAAK,OAAO;AAAA,QACvB,MAAM,KAAK,OAAO;AAAA,QAClB,MAAM,KAAK,OAAO;AAAA,MACpB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,YAAoB,UAA0B,CAAC,GAAqC;AACtG,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,QACE;AAAA,QACA,WAAW,KAAK,OAAO;AAAA,MACzB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,YACA,KACA,UACA,UAA0B,CAAC,GACO;AAClC,UAAM,WAAW,QAAQ,YAAY,KAAK,OAAO;AAOjD,UAAM,eAAe,QAAQ,aAAa,KAAK,OAAO;AACtD,UAAM,eAA+B;AAAA,MACnC,WAAW;AAAA,MACX,YAAY,QAAQ,cAAc,KAAK,OAAO;AAAA,IAChD;AACA,UAAM,SAAS,oBAAoB,KAAK,QAAQ,YAAY,KAAK,UAAU,QAAQ;AACnF,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,KAAK,iBAAiB,QAAQ,sBAAsB,OAAO,CAAC,GAAG,YAAY;AAAA,IACpF;AAMA,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,UAAM,UAAqC,CAAC;AAC5C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,YAAY,WAAW,KAAK,IAAI;AACtC,UAAI,aAAa,GAAG;AAClB,cAAM,IAAI;AAAA,UACR,kDAAkD,YAAY,aAAa,OAAO,MAAM,sBAAsB,CAAC;AAAA,QACjH;AAAA,MACF;AACA,YAAM,eAA+B,EAAE,GAAG,cAAc,WAAW,UAAU;AAC7E,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB;AAAA,QACA;AAAA,QACA,OAAO,CAAC;AAAA,QACR;AAAA,MACF;AACA,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,gBAAQ,KAAK,MAAM;AAAA,MACrB;AAAA,IACF;AACA,WAAO,oBAAoB,OAAO;AAAA,EACpC;AAAA,EAEA,MAAM,YAAY,SAAiB,YAAoB,UAA0B,CAAC,GAAqC;AACrH,WAAO,KAAK,iBAAiB,QAAQ,uBAAuB;AAAA,MAC1D;AAAA,MACA,UAAU;AAAA,MACV,cAAc;AAAA,MACd;AAAA,MACA,WAAW,KAAK,OAAO;AAAA,IACzB,GAAG,OAAO;AAAA,EACZ;AAAA,EAEA,MAAM,UAAU,OAAe,YAAoB,QAAQ,IAAsC;AAC/F,WAAO,KAAK,iBAAiB,QAAQ,yBAAyB;AAAA,MAC5D;AAAA,MACA;AAAA,MACA,WAAW,KAAK,OAAO;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,mBAAmB,YAAsD;AAC7E,WAAO,KAAK,iBAAiB,QAAQ,mCAAmC;AAAA,MACtE;AAAA,MACA,WAAW,KAAK,OAAO;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,oBAAoB,YAAoB,cAAsB,aAAuD;AACzH,WAAO,KAAK,iBAAiB,QAAQ,oCAAoC;AAAA,MACvE;AAAA,MACA,WAAW,KAAK,OAAO;AAAA,MACvB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,kBAAkB,YAAoB,SAAmD;AAC7F,WAAO,KAAK,QAAQ,6BAA6B;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,WAAW,KAAK,OAAO;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,UAA0B,CAAC,GAAuB;AACnE,UAAM,SAAS,MAAM,KAAK,WAAW,cAAc,CAAC,GAAG,OAAO;AAC9D,UAAM,QAAQ,OAAO;AACrB,WAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,OAAO,SAAS,IAAI,CAAC;AAAA,EAC3D;AAAA,EAEA,MAAM,QAAQ,MAAc,MAAiE;AAC3F,WAAO,KAAK,WAAW,cAAc;AAAA,MACnC;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,QACZ,QACA,UACA,MACA,UAA0B,CAAC,GACf;AACZ,UAAM,aAAa,IAAI,gBAAgB;AAOvC,UAAM,WAAW,QAAQ;AACzB,UAAM,YACJ,OAAO,aAAa,YAAY,OAAO,SAAS,QAAQ,KAAK,WAAW,IACpE,WACA,KAAK,OAAO;AAClB,UAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC9D,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,eAAe,GAAG,QAAQ,IAAI;AAAA,QACxE;AAAA,QACA,SAAS;AAAA,UACP,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,gBAAgB,mBAAmB;AAAA,UACnE,GAAI,KAAK,OAAO,YAAY,EAAE,eAAe,UAAU,KAAK,OAAO,SAAS,GAAG,IAAI,CAAC;AAAA,UACpF,sBAAsB;AAAA,QACxB;AAAA,QACA,MAAM,SAAS,SAAY,SAAY,KAAK,UAAU,IAAI;AAAA,QAC1D,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAI,UAAmB,CAAC;AACxB,UAAI;AACJ,UAAI,MAAM;AACR,YAAI;AACF,oBAAU,KAAK,MAAM,IAAI;AAAA,QAC3B,SAAS,KAAK;AACZ,uBAAa;AAAA,QACf;AAAA,MACF;AACA,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,UAAU,qBAAqB,UAAU,MAAM,SAAS,UAAU;AACxE,cAAM,OAAO,kBAAkB,SAAS,UAAU;AAClD,YAAI,SAAS,WAAW,KAAK;AAE3B,gBAAM,YAAY,SAAS,SAAY,IAAI,UAAU,IAAI;AACzD,gBAAM,IAAI,gBAAgB,SAAS,QAAQ,GAAG,OAAO,mBAAmB,SAAS,oCAAoC,IAAI;AAAA,QAC3H;AACA,cAAM,IAAI,gBAAgB,SAAS,QAAQ,SAAS,IAAI;AAAA,MAC1D;AACA,UAAI,YAAY;AACd,cAAM,SAAS,sBAAsB,QAAQ,WAAW,UAAU,OAAO,UAAU;AACnF,cAAM,IAAI,MAAM,6CAA6C,SAAS,MAAM,IAAI,SAAS,cAAc,IAAI,MAAM,MAAM,EAAE;AAAA,MAC3H;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,UAAI,aAAa,GAAG,GAAG;AACrB,cAAM,IAAI,MAAM,kCAAkC,SAAS,IAAI;AAAA,MACjE;AACA,YAAM;AAAA,IACR,UAAE;AACA,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,iBACZ,QACA,UACA,MACA,UAA0B,CAAC,GACf;AACZ,UAAM,aAAa,QAAQ,cAAc;AAOzC,UAAM,WAAW,QAAQ;AACzB,UAAM,cAAc,OAAO,aAAa,YAAY,OAAO,SAAS,QAAQ,KAAK,WAAW;AAC5F,UAAM,WAAW,cAAc,KAAK,IAAI,IAAI,WAAW,OAAO;AAC9D,QAAI,UAAU;AACd,QAAI,iBAAiB;AAErB,WAAO,MAAM;AACX,UAAI;AACF,eAAO,MAAM,KAAK,QAAW,QAAQ,UAAU,MAAM,cAAc;AAAA,MACrE,SAAS,KAAK;AACZ,YAAI,WAAW,cAAc,CAAC,wBAAwB,GAAG,EAAG,OAAM;AAClE,cAAM,UAAU,sBAAsB,KAAK;AAC3C,YAAI,aAAa;AAKf,gBAAM,uBAAuB,WAAW,KAAK,IAAI;AACjD,cAAI,wBAAwB,SAAS;AACnC,kBAAM,IAAI;AAAA,cACR,+BAA+B,QAAQ,0BAA0B,UAAU,CAAC,KAAK,MAAM,IAAI,QAAQ;AAAA,YACrG;AAAA,UACF;AAAA,QACF;AACA,cAAM,MAAM,OAAO;AACnB,mBAAW;AACX,YAAI,aAAa;AACf,gBAAM,YAAY,WAAW,KAAK,IAAI;AACtC,cAAI,aAAa,GAAG;AAClB,kBAAM,IAAI;AAAA,cACR,+BAA+B,QAAQ,0BAA0B,OAAO,KAAK,MAAM,IAAI,QAAQ;AAAA,YACjG;AAAA,UACF;AACA,2BAAiB,EAAE,GAAG,SAAS,WAAW,UAAU;AAAA,QACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,WACZ,QACA,QACA,UAA0B,CAAC,GACO;AAClC,SAAK,aAAa;AAClB,UAAM,UAAU,MAAM,KAAK,QAAiC,QAAQ,QAAQ;AAAA,MAC1E,SAAS;AAAA,MACT,IAAI,KAAK;AAAA,MACT;AAAA,MACA;AAAA,IACF,GAAG,OAAO;AACV,QAAI,QAAQ,OAAO;AACjB,YAAM,IAAI,MAAM,KAAK,UAAU,QAAQ,KAAK,CAAC;AAAA,IAC/C;AACA,WAAQ,QAAQ,UAAU,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS;AAAA,EAClF;AACF;AAEA,SAAS,UAAU,OAAkC;AACnD,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,UAAU,SAAS,OAAO,MAAM,SAAS;AAC1F;AAEA,SAAS,aAAa,KAAuB;AAC3C,SAAO,eAAe,UAAU,IAAI,SAAS,gBAAgB,IAAI,YAAY;AAC/E;AAOO,SAAS,wBAAwB,KAAuB;AAC7D,MAAI,EAAE,eAAe,OAAQ,QAAO;AACpC,MAAI,aAAa,GAAG,EAAG,QAAO;AAC9B,MAAI,eAAe,gBAAiB,QAAO;AAC3C,QAAM,SAAS,IAAI,WAAW,IAAI,YAAY;AAE9C,MAAI,MAAM,SAAS,8BAA8B,EAAG,QAAO;AAC3D,MAAI,MAAM,SAAS,eAAe,EAAG,QAAO;AAE5C,MAAI,MAAM,SAAS,YAAY,EAAG,QAAO;AACzC,MAAI,MAAM,SAAS,OAAO,EAAG,QAAO;AACpC,MAAI,MAAM,SAAS,gBAAgB,EAAG,QAAO;AAE7C,MAAI,MAAM,SAAS,cAAc,EAAG,QAAO;AAG3C,QAAM,QAAQ,IAAI;AAClB,MAAI,SAAS,OAAO,UAAU,YAAY,UAAU,OAAO;AACzD,UAAM,OAAO,MAAM;AACnB,QAAI,OAAO,SAAS,aAAa,SAAS,gBAAgB,SAAS,WAAW,SAAS,mBAAmB;AACxG,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,MAAM,IAA2B;AAGxC,SAAO,IAAI,QAAc,aAAW,WAAW,SAAS,EAAE,CAAC;AAC7D;AAEA,SAAS,UAAU,OAAwB;AACzC,SAAO,QAAQ,OAAO,KAAK,UAAU,KAAK,CAAC,EAAE;AAC/C;AAEA,SAAS,qBAAqB,QAAwB,YAAoB,KAAa,UAAyC;AAC9H,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW,OAAO;AAAA,IAClB,gBAAgB,OAAO;AAAA,IACvB;AAAA,EACF;AACF;AAQO,SAAS,oBACd,QACA,YACA,KACA,UACA,UACe;AACf,QAAM,mBAAmB,UAAU,qBAAqB,QAAQ,YAAY,KAAK,CAAC,CAAC,CAAC;AACpF,QAAM,gBAAgB,WAAW;AACjC,MAAI,iBAAiB,GAAG;AAOtB,WAAO,CAAC,qBAAqB,QAAQ,YAAY,KAAK,QAAQ,CAAC;AAAA,EACjE;AACA,QAAM,SAA6B,CAAC;AACpC,MAAI,UAA4B,CAAC;AACjC,MAAI,cAAc;AAClB,QAAM,QAAQ,MAAY;AACxB,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,KAAK,OAAO;AACnB,gBAAU,CAAC;AACX,oBAAc;AAAA,IAChB;AAAA,EACF;AACA,aAAW,WAAW,UAAU;AAC9B,UAAM,OAAO,UAAU,OAAO;AAC9B,QAAI,OAAO,eAAe;AACxB,YAAM;AACN,aAAO,KAAK,CAAC,uBAAuB,SAAS,aAAa,CAAC,CAAC;AAC5D;AAAA,IACF;AAIA,QAAI,QAAQ,SAAS,KAAK,cAAc,IAAI,OAAO,eAAe;AAChE,YAAM;AAAA,IACR;AACA,QAAI,QAAQ,SAAS,EAAG,gBAAe;AACvC,YAAQ,KAAK,OAAO;AACpB,mBAAe;AAAA,EACjB;AACA,QAAM;AACN,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,CAAC,qBAAqB,QAAQ,YAAY,KAAK,CAAC,CAAC,CAAC;AAAA,EAC3D;AACA,SAAO,OAAO,IAAI,CAAC,SAAS,qBAAqB,QAAQ,YAAY,KAAK,IAAI,CAAC;AACjF;AAEA,IAAM,UAAU,IAAI,YAAY;AAEhC,SAAS,uBAAuB,SAAyB,aAAqC;AAO5F,QAAM,OAAuB,EAAE,MAAM,QAAQ,MAAM,SAAS,GAAG;AAC/D,QAAM,aAAa,UAAU,EAAE,GAAG,MAAM,SAAS,kBAAkB,CAAC;AACpE,MAAI,aAAa,aAAa;AAG5B,WAAO,EAAE,MAAM,QAAQ,MAAM,SAAS,kBAAkB;AAAA,EAC1D;AACA,QAAM,cAAc,QAAQ,UAAU;AACtC,MAAI,UAAU,EAAE,GAAG,MAAM,SAAS,YAAY,CAAC,KAAK,aAAa;AAC/D,WAAO,EAAE,MAAM,QAAQ,MAAM,SAAS,YAAY;AAAA,EACpD;AAIA,QAAM,UAAU,QAAQ,OAAO,QAAQ,OAAO;AAC9C,MAAI,KAAK;AACT,MAAI,KAAK,QAAQ;AACjB,SAAO,KAAK,IAAI;AACd,UAAM,MAAM,KAAK,KAAK,OAAO,KAAK,MAAM,CAAC;AACzC,UAAM,YAAY,QAAQ,OAAO,QAAQ,SAAS,GAAG,GAAG,CAAC,IAAI;AAC7D,QAAI,UAAU,EAAE,GAAG,MAAM,SAAS,UAAU,CAAC,KAAK,aAAa;AAC7D,WAAK;AAAA,IACP,OAAO;AACL,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AACA,QAAM,YAAY,KAAK,IAAI,QAAQ,OAAO,QAAQ,SAAS,GAAG,EAAE,CAAC,IAAI;AACrE,SAAO,EAAE,MAAM,QAAQ,MAAM,SAAS,YAAY,kBAAkB;AACtE;AAEA,SAAS,oBAAoB,SAA6D;AACxF,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAClC,MAAI,QAAQ,WAAW,EAAG,QAAO,QAAQ,CAAC;AAC1C,QAAM,SAAkC,CAAC;AACzC,MAAI,WAAW;AACf,MAAI,WAAW;AACf,aAAW,UAAU,SAAS;AAC5B,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,QAAQ,WAAW,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AAC1E,oBAAY;AACZ,mBAAW;AAAA,MACb,OAAO;AACL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACA,MAAI,SAAU,QAAO,QAAQ;AAC7B,SAAO;AACT;AAEA,SAAS,qBAAqB,UAAoB,MAAc,SAAkB,YAA6B;AAC7G,MAAI,CAAC,cAAc,WAAW,OAAO,YAAY,UAAU;AACzD,QAAI,WAAW,WAAW,OAAO,QAAQ,UAAU,YAAY,QAAQ,MAAM,KAAK,EAAE,SAAS,GAAG;AAC9F,aAAO,QAAQ;AAAA,IACjB;AACA,QAAI,aAAa,WAAW,OAAO,QAAQ,YAAY,YAAY,QAAQ,QAAQ,KAAK,EAAE,SAAS,GAAG;AACpG,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,KAAK,EAAE,QAAQ,QAAQ,GAAG,EAAE,MAAM,GAAG,GAAG;AAC7D,MAAI,QAAQ,SAAS,GAAG;AACtB,WAAO,SAAS,aAAa,GAAG,SAAS,UAAU,KAAK,OAAO,KAAK;AAAA,EACtE;AACA,SAAO,SAAS,cAAc,QAAQ,SAAS,MAAM;AACvD;AAEA,SAAS,kBAAkB,SAAkB,YAAyC;AACpF,MAAI,cAAc,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AAClE,MAAI,UAAU,WAAW,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,KAAK,EAAE,SAAS,GAAG;AAC3F,WAAO,QAAQ;AAAA,EACjB;AACA,SAAO;AACT;;;ACzpBA,SAAS,kBAAkB;AAK3B,SAAS,2BAAqD;AAMvD,SAAS,sBAAsB,KAAmE;AACvG,QAAM,KAAK,IAAI,gBAAgB,eAAe;AAC9C,SAAO,MAAM,GAAG,KAAK,EAAE,SAAS,IAAI,MAAM,EAAE,KAAK;AACnD;AAEO,SAAS,gBAAgB,SAA0B;AACxD,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,MAAM;AACZ,QAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AACvD,MAAI,SAAS,iBAAiB;AAC5B,UAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;AAChE,UAAM,SAAS,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;AAC7D,WAAO,CAAC,OAAO,OAAO,IAAI,MAAM,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAAA,EAC7D;AACA,SAAO,gBAAgB,IAAI,OAAO,EAAE,KAAK;AAC3C;AAgCO,SAAS,iBAAiB,SAAyC;AACxE,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,MAAM;AACZ,MAAI,sBAAsB,GAAG,KAAK,iBAAiB,GAAG,EAAG,QAAO;AAChE,QAAM,OAAO,IAAI,SAAS,UAAU,IAAI,SAAS,kBAAkB,SAAS;AAC5E,QAAM,UAAU,gBAAgB,GAAG;AACnC,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,OAAO,iBAAiB,KAAK,OAAO;AAAA,EACtC;AACF;AAEO,SAAS,oBAAoB,SAAyB,aAAa,IAAI,WAAW,WAAmB;AAC1G,SAAO,WAAW,QAAQ,EACvB,OAAO,UAAU,EACjB,OAAO,IAAI,EACX,OAAO,QAAQ,IAAI,EACnB,OAAO,IAAI,EACX,OAAO,QAAQ,EACf,OAAO,IAAI,EACX,OAAO,QAAQ,OAAO,EACtB,OAAO,KAAK;AACjB;AAEO,SAAS,yBACd,SACA,aAAa,IACE;AACf,QAAM,WAAW,8BAA8B,QAAQ,UAAU;AACjE,SAAO,WAAW,oBAAoB,SAAS,YAAY,QAAQ,IAAI;AACzE;AAEO,SAAS,kBAAkB,UAAqB,UAA0B;AAC/E,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,aAAW,WAAW,UAAU;AAC9B,QAAI,sBAAsB,OAAO,KAAK,iBAAiB,OAAO,EAAG;AACjE,UAAM,OAAO,gBAAgB,OAAO;AACpC,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,OAAQ,SAAuB,SAAS,WAAY,QAAsB,OAAO;AAC9F,UAAM,OAAO,IAAI,IAAI,KAAK,IAAI;AAC9B,UAAM,kBAAkB,OAAO,SAAS,IAAI,IAAI;AAChD,UAAM,YAAY,WAAW,OAAO;AACpC,QAAI,aAAa,EAAG;AACpB,UAAM,UAAU,KAAK,SAAS,YAAY,KAAK,MAAM,GAAG,SAAS,IAAI;AACrE,QAAI,QAAQ,SAAS,EAAG,QAAO,KAAK,OAAO;AAC3C,YAAQ,kBAAkB,QAAQ;AAClC,QAAI,QAAQ,SAAU;AAAA,EACxB;AACA,SAAO,OAAO,KAAK,MAAM;AAC3B;AAEO,SAAS,sBAAsB,SAA2B;AAC/D,SAAO,CAAC,CAAC,WAAW,OAAO,YAAY,YAAa,QAAsB,uBAAuB;AACnG;AAEO,SAAS,iBAAiB,SAA2B;AAC1D,SAAO,CAAC,CAAC,WAAW,OAAO,YAAY,YAAa,QAAsB,mBAAmB;AAC/F;AAEA,SAAS,gBAAgB,SAA0B;AACjD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,QAAM,SAAmB,CAAC;AAC1B,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AACzC,UAAM,MAAM;AACZ,QAAI,IAAI,SAAS,UAAU,OAAO,IAAI,SAAS,SAAU,QAAO,KAAK,IAAI,IAAI;AAC7E,QAAI,IAAI,SAAS,cAAc,OAAO,IAAI,SAAS,UAAU;AAC3D,aAAO,KAAK,QAAQ,IAAI,IAAI,gBAAgB,KAAK,UAAU,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE;AAAA,IACnF;AAAA,EACF;AACA,SAAO,OAAO,KAAK,IAAI;AACzB;AAEA,SAAS,iBAAiB,SAAoB,iBAA+C;AAC3F,SAAO,oBAAoB,SAAS;AAAA,IAClC;AAAA,IACA,uBAAuB;AAAA,EACzB,CAAC,EAAE,IAAI,oBAAoB;AAC7B;AAEA,SAAS,qBAAqB,MAA+C;AAC3E,SAAO;AAAA,IACL,SAAS,KAAK,WAAW;AAAA,IACzB,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,UAAU,KAAK,YAAY,KAAK,aAAa;AAAA,IAC7C,UAAU,KAAK,YAAY,KAAK,aAAa;AAAA,IAC7C,WAAW,KAAK,aAAa,KAAK,cAAc;AAAA,EAClD;AACF;AAEA,SAAS,8BAA8B,YAAoC;AACzE,MAAI,cAAc,OAAO,eAAe,UAAU;AAChD,UAAM,MAAM;AACZ,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,IAAI,KAAK;AACvB,UAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO,GAAG,KAAK,IAAI,KAAK;AAC3E,UAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO,GAAG,KAAK,IAAI,KAAK;AAAA,IACnF;AAAA,EACF;AACA,SAAO;AACT;;;AHzJA,IAAM,oBAAoB;AAC1B,IAAM,sBAAsB;AAC5B,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB,oBAAI,IAAI,CAAC,cAAc,aAAa,KAAK,CAAC;AA4BhE,SAAS,wBAAwB,UAAoC,CAAC,GAAG;AAC9E,QAAM,SAAS,QAAQ,UAAU,WAAW,OAAO;AACnD,QAAM,SAAS,IAAI,aAAa,MAAM;AACtC,QAAM,gBAAgB,oBAAI,IAA4B;AAEtD,SAAO,eAAeC,mBAAkB,IAA0B;AAChE,OAAG,GAAG,iBAAiB,OAAO,QAAQ,QAAQ;AAC5C,YAAM,UAAU,kBAAkB,KAAK,EAAE,uBAAuB,KAAK,CAAC;AACtE,UAAI,CAAC,QAAS;AACd,YAAM,EAAE,MAAM,IAAI,gBAAgB,QAAQ,YAAY,aAAa;AACnE,2BAAqB,SAAS,MAAM,cAAc;AAClD,YAAM,gBAAgB;AACtB,YAAM,kBAAkB;AAMxB,YAAM,QAAQ,MAAM,kBAAkB,QAAQ,MAAM;AACpD,UAAI,OAAO,eAAe;AACxB,gBAAQ,UAAU,UAAU,kBAAkB,OAAO,OAAO,SAAS,CAAC;AAAA,MACxE;AACA,YAAM,sBAAsB,IAAI,SAAS,QAAQ,MAAM;AAAA,IACzD,CAAC;AAED,OAAG,GAAG,sBAAsB,OAAO,OAAO,QAAQ;AAChD,YAAM,UAAU,kBAAkB,GAAG;AACrC,UAAI,CAAC,QAAS;AACd,YAAM,EAAE,MAAM,IAAI,gBAAgB,QAAQ,YAAY,aAAa;AAMnE,UAAI,CAAC,MAAM,mBAAmB,OAAO,iBAAiB,OAAO,aAAa,OAAO,YAAY,GAAG;AAC9F,cAAM,aAAa,OAAO,MAAM,WAAW,YAAY,MAAM,OAAO,KAAK,EAAE,SAAS,IAAI,MAAM,SAAS;AACvG,YAAI,YAAY;AACd,gBAAM,kBAAkB;AACxB,cAAI;AACF,kBAAM,WAAW,MAAM,OAAO,OAAO,YAAY,QAAQ,YAAY,QAAQ,KAAK;AAAA,cAChF,WAAW,OAAO;AAAA,YACpB,CAAC;AACD,mBAAO,cAAc;AACrB,kBAAM,UAAU,YAAY,SAAS,WAAW,IAAI,OAAO,iBAAiB;AAC5E,gBAAI,SAAS;AACX,oBAAM,gBAAgB;AAAA,YACxB;AAAA,UACF,SAAS,KAAK;AACZ,gBAAI,yBAAyB,GAAG,EAAG,QAAO,gBAAgB,OAAO,gBAAgB;AACjF,oBAAQ,OAAO,8BAA8B,aAAa,GAAG,CAAC,IAAI,SAAS;AAAA,UAC7E;AAAA,QACF;AAAA,MACF;AAOA,UAAI,CAAC,MAAM,cAAe;AAC1B,YAAM,aAAa,OAAO,MAAM,iBAAiB,WAAW,MAAM,eAAe;AACjF,aAAO;AAAA,QACL,cAAc,GAAG,UAAU;AAAA;AAAA,EAAO,MAAM,aAAa;AAAA,MACvD;AAAA,IACF,CAAC;AAED,OAAG,GAAG,eAAe,OAAO,OAAO,QAAQ;AACzC,YAAM,UAAU,kBAAkB,GAAG;AACrC,UAAI,CAAC,QAAS;AACd,UAAI,CAAC,OAAO,kBAAkB,CAAC,cAAc,MAAM,OAAO,EAAG;AAC7D,YAAM,EAAE,MAAM,IAAI,gBAAgB,QAAQ,YAAY,aAAa;AACnE,YAAM,0BAA0B,SAAS,QAAQ,CAAC,MAAM,OAAO,GAAG,MAAM,gBAAgB,MAAM,wBAAwB,MAAM;AAAA,IAC9H,CAAC;AAED,OAAG,GAAG,YAAY,OAAO,OAAO,QAAQ;AACtC,YAAM,UAAU,kBAAkB,GAAG;AACrC,UAAI,CAAC,QAAS;AACd,UAAI,CAAC,OAAO,eAAgB;AAC5B,YAAM,WAAW,CAAC,MAAM,SAAS,GAAI,MAAM,QAAQ,MAAM,WAAW,IAAI,MAAM,cAAc,CAAC,CAAE;AAC/F,YAAM,EAAE,MAAM,IAAI,gBAAgB,QAAQ,YAAY,aAAa;AACnE,YAAM,0BAA0B,SAAS,QAAQ,UAAU,MAAM,gBAAgB,MAAM,wBAAwB,MAAM;AAAA,IACvH,CAAC;AAED,OAAG,GAAG,oBAAoB,OAAO,QAAQ,QAAQ;AAC/C,YAAM,UAAU,kBAAkB,KAAK,EAAE,uBAAuB,KAAK,CAAC;AACtE,UAAI,CAAC,QAAS;AACd,YAAM,EAAE,YAAY,MAAM,IAAI,gBAAgB,QAAQ,YAAY,aAAa;AAC/E,UAAI,OAAO,gBAAgB;AACzB,cAAM,iBAAiB,gCAAgC,QAAQ,MAAM;AACrE,cAAM,2BAA2B,+BAA+B,QAAQ,YAAY,gBAAgB,MAAM,sBAAsB;AAChI,YAAI,yBAAyB,SAAS,GAAG;AACvC,gBAAM,0BAA0B,SAAS,QAAQ,0BAA0B,MAAM,gBAAgB,QAAW,QAAQ,IAAI;AAAA,QAC1H;AAAA,MACF;AACA,2BAAqB,IAAI,MAAM,cAAc;AAC7C,oBAAc,OAAO,UAAU;AAAA,IACjC,CAAC;AAED,OAAG,GAAG,0BAA0B,OAAO,OAAO,QAAQ;AACpD,YAAM,UAAU,kBAAkB,GAAG;AACrC,UAAI,CAAC,QAAS;AACd,UAAI,CAAC,OAAO,qBAAqB,CAAC,OAAO,UAAW;AACpD,YAAM,cAAc,MAAM,eAAe,CAAC;AAC1C,UAAI;AACF,cAAM,OAAO,mBAAmB,QAAQ,UAAU;AAAA,MACpD,SAAS,KAAK;AACZ,gBAAQ,OAAO,4BAA4B,aAAa,GAAG,CAAC,IAAI,SAAS;AACzE;AAAA,MACF;AAEA,YAAM,eAAe,iBAAiB,YAAY,YAAY;AAC9D,YAAM,cAAc,iBAAiB,YAAY,WAAW;AAC5D,UAAI,iBAAiB,QAAQ,gBAAgB,MAAM;AACjD,YAAI;AACF,gBAAM,OAAO,oBAAoB,QAAQ,YAAY,cAAc,WAAW;AAAA,QAChF,SAAS,KAAK;AACZ,kBAAQ,OAAO,8CAA8C,aAAa,GAAG,CAAC,IAAI,SAAS;AAAA,QAC7F;AAAA,MACF;AAEA,YAAM,UAAU,uBAAuB,WAAW;AAClD,UAAI,CAAC,QAAQ,KAAK,EAAG;AACrB,UAAI;AACF,cAAM,OAAO,kBAAkB,QAAQ,YAAY,OAAO;AAAA,MAC5D,SAAS,KAAK;AACZ,gBAAQ,OAAO,qCAAqC,aAAa,GAAG,CAAC,IAAI,SAAS;AAAA,MACpF;AACA,YAAM,UAAU,2BAA2B,WAAW;AACtD,aAAO;AAAA,QACL,YAAY;AAAA,UACV;AAAA,UACA,kBAAkB,YAAY;AAAA,UAC9B,cAAc,YAAY;AAAA,UAC1B,SAAS;AAAA,YACP,GAAG;AAAA,YACH,QAAQ,EAAE,SAAS,GAAG,QAAQ,KAAK;AAAA,UACrC;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,qBAAiB,IAAI,QAAQ,MAAM;AACnC,QAAI,OAAO,mBAAmB,OAAO,WAAW;AAC9C,YAAM,iBAAiB,IAAI,QAAQ,MAAM;AAAA,IAC3C;AAAA,EACF;AACF;AAEA,eAAO,kBAAyC,IAA0B;AACxE,QAAM,wBAAwB,EAAE,EAAE;AACpC;AAEA,SAAS,iBAAiB,IAAW,QAAsB,QAA8B;AACvF,KAAG,gBAAgB,iBAAiB;AAAA,IAClC,aAAa;AAAA,IACb,SAAS,eAAe,OAAO,OAAO,MAAM,YAAY;AACtD,YAAM,SAAS,MAAM,OAAO,OAAO;AAGnC,aAAO,cAAc;AACrB,cAAQ,OAAO,UAAU,OAAO,KAAK,YAAY,WAAW,OAAO,OAAO,eAAe,IAAI,OAAO,KAAK,YAAY,SAAS;AAAA,IAChI,CAAC;AAAA,EACH,CAAC;AAED,KAAG,gBAAgB,iBAAiB;AAAA,IAClC,aAAa;AAAA,IACb,SAAS,eAAe,OAAO,MAAM,MAAM,YAAY;AACrD,YAAM,QAAQ,KAAK,KAAK;AACxB,UAAI,CAAC,OAAO;AACV,gBAAQ,OAAO,iCAAiC,SAAS;AACzD;AAAA,MACF;AAKA,YAAM,SAAS,MAAM,OAAO,OAAO,OAAO,QAAQ,YAAY,QAAQ,KAAK;AAAA,QACzE,WAAW,OAAO;AAAA,MACpB,CAAC;AAGD,aAAO,cAAc;AACrB,cAAQ,OAAO,YAAY,OAAO,WAAW,uBAAuB,iBAAiB,GAAG,MAAM;AAAA,IAChG,CAAC;AAAA,EACH,CAAC;AAED,KAAG,gBAAgB,mBAAmB;AAAA,IACpC,aAAa;AAAA,IACb,SAAS,eAAe,OAAO,MAAM,MAAM,YAAY;AACrD,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,SAAS;AACZ,gBAAQ,OAAO,oCAAoC,SAAS;AAC5D;AAAA,MACF;AACA,YAAM,OAAO,YAAY,SAAS,QAAQ,UAAU;AACpD,cAAQ,OAAO,wBAAwB,SAAS;AAAA,IAClD,CAAC;AAAA,EACH,CAAC;AAED,KAAG,gBAAgB,qBAAqB;AAAA,IACtC,aAAa;AAAA,IACb,SAAS,eAAe,OAAO,MAAM,MAAM,YAAY;AACrD,YAAM,QAAQ,KAAK,KAAK;AACxB,UAAI,CAAC,OAAO;AACV,gBAAQ,OAAO,qCAAqC,SAAS;AAC7D;AAAA,MACF;AACA,YAAM,SAAS,MAAM,OAAO,UAAU,OAAO,QAAQ,UAAU;AAC/D,cAAQ,OAAO,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,MAAM;AAAA,IACxD,CAAC;AAAA,EACH,CAAC;AAED,KAAG,gBAAgB,cAAc;AAAA,IAC/B,aAAa;AAAA,IACb,SAAS,eAAe,OAAO,OAAO,MAAM,YAAY;AACtD,YAAM,SAAS,MAAM,OAAO,cAAc,QAAQ,UAAU;AAC5D,cAAQ,OAAO,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,MAAM;AAAA,IACxD,CAAC;AAAA,EACH,CAAC;AAED,KAAG,gBAAgB,kBAAkB;AAAA,IACnC,aAAa;AAAA,IACb,SAAS,eAAe,OAAO,OAAO,MAAM,YAAY;AACtD,cAAQ,UAAU;AAClB,cAAQ,OAAO,wBAAwB,MAAM;AAAA,IAC/C,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,eACP,SAC2C;AAC3C,SAAO,OAAO,MAAM,QAAQ;AAC1B,UAAM,UAAU,kBAAkB,GAAG;AACrC,QAAI,CAAC,QAAS;AACd,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,OAAO;AAAA,IAClC,SAAS,KAAK;AACZ,cAAQ,OAAO,0BAA0B,aAAa,GAAG,CAAC,IAAI,SAAS;AAAA,IACzE;AAAA,EACF;AACF;AAEA,eAAe,iBAAiB,IAAW,QAAsB,QAAuC;AACtG,MAAI,QAAmB,CAAC;AACxB,MAAI;AACF,YAAQ,MAAM,OAAO,aAAa,EAAE,WAAW,OAAO,wBAAwB,CAAC;AAAA,EACjF,QAAQ;AACN;AAAA,EACF;AACA,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,KAAK,WAAW,SAAS,EAAG;AACtC,UAAM,aAAa,KAAK,KAAK,QAAQ,aAAa,SAAS,EAAE,QAAQ,kBAAkB,GAAG;AAC1F,OAAG,aAAa;AAAA,MACd,MAAM;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,eAAe,QAAQ,KAAK,IAAI;AAAA,MAClD,YAAY,yBAAyB,KAAK,WAAW;AAAA,MACrD,MAAM,QAAQ,aAAqB,QAAiC,SAAkC,WAAoB,KAAU;AAClI,cAAM,UAAU,kBAAkB,GAAG;AACrC,YAAI,CAAC,SAAS;AACZ,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,kEAAkE,CAAC;AAAA,YACnG,SAAS,EAAE,SAAS,MAAM,QAAQ,gBAAgB;AAAA,UACpD;AAAA,QACF;AACA,cAAM,aAAa,+BAA+B,UAAU,CAAC,CAAC;AAC9D,cAAM,SAAS,MAAM,OAAO,QAAQ,KAAK,MAAM;AAAA,UAC7C,GAAG;AAAA,UACH,YAAY,QAAQ;AAAA,UACpB,WAAW,OAAO;AAAA,UAClB,KAAK,QAAQ;AAAA,QACf,CAAC;AACD,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC;AAAA,UACjE,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,SAAS,yBAAyB,aAA+B;AACtE,SAAO,KAAK,OAAO,8BAA8B,WAAW,CAAC;AAC/D;AAEO,SAAS,8BAA8B,aAA+C;AAC3F,MAAI,CAAC,SAAS,WAAW,GAAG;AAC1B,WAAO,EAAE,MAAM,UAAU,YAAY,CAAC,GAAG,sBAAsB,KAAK;AAAA,EACtE;AACA,SAAO,4BAA4B,WAAW;AAChD;AAEA,SAAS,4BAA4B,OAAyB;AAC5D,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,UAAU,4BAA4B,KAAK,CAAC;AAAA,EAChE;AACA,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO;AAAA,EACT;AACA,QAAM,SAAkC,EAAE,GAAG,MAAM;AACnD,MAAI,SAAS,MAAM,UAAU,GAAG;AAC9B,UAAM,aAAsC,CAAC;AAC7C,eAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,MAAM,UAAU,GAAG;AAC9D,UAAI,qBAAqB,IAAI,GAAG,EAAG;AACnC,iBAAW,GAAG,IAAI,4BAA4B,QAAQ;AAAA,IACxD;AACA,WAAO,aAAa;AAAA,EACtB;AACA,MAAI,MAAM,QAAQ,MAAM,QAAQ,GAAG;AACjC,WAAO,WAAW,MAAM,SAAS;AAAA,MAC/B,CAAC,UAAU,OAAO,UAAU,YAAY,CAAC,qBAAqB,IAAI,KAAK;AAAA,IACzE;AAAA,EACF;AACA,aAAW,OAAO,CAAC,SAAS,wBAAwB,KAAK,GAAY;AACnE,QAAI,SAAS,MAAM,GAAG,CAAC,GAAG;AACxB,aAAO,GAAG,IAAI,4BAA4B,MAAM,GAAG,CAAC;AAAA,IACtD;AAAA,EACF;AACA,aAAW,OAAO,CAAC,SAAS,SAAS,OAAO,GAAY;AACtD,QAAI,MAAM,QAAQ,MAAM,GAAG,CAAC,GAAG;AAC7B,aAAO,GAAG,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,UAAU,4BAA4B,KAAK,CAAC;AAAA,IAC5E;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,+BAA+B,OAAyB;AACtE,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,UAAU,+BAA+B,KAAK,CAAC;AAAA,EACnE;AACA,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO;AAAA,EACT;AACA,QAAM,YAAqC,CAAC;AAC5C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,qBAAqB,IAAI,GAAG,EAAG;AACnC,cAAU,GAAG,IAAI,+BAA+B,KAAK;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AACrE;AAEA,SAAS,cAAc,SAA2B;AAChD,SAAO,SAAS,OAAO,KAAK,QAAQ,SAAS;AAC/C;AAEA,SAAS,gBAAgB,YAAoB,QAAoF;AAC/H,MAAI,QAAQ,OAAO,IAAI,UAAU;AACjC,MAAI,CAAC,OAAO;AACV,YAAQ;AAAA,MACN,gBAAgB,oBAAI,IAAY;AAAA,MAChC,wBAAwB,oBAAI,IAAoB;AAAA,MAChD,eAAe;AAAA,MACf,iBAAiB;AAAA,IACnB;AACA,WAAO,IAAI,YAAY,KAAK;AAC5B,uBAAmB,MAAM;AAAA,EAC3B;AACA,SAAO,EAAE,YAAY,MAAM;AAC7B;AAEA,SAAS,mBAAmB,QAA2C;AACrE,SAAO,OAAO,OAAO,oBAAoB;AACvC,UAAM,SAAS,OAAO,KAAK,EAAE,KAAK,EAAE;AACpC,QAAI,OAAO,WAAW,SAAU;AAChC,WAAO,OAAO,MAAM;AAAA,EACtB;AACF;AAEA,eAAsB,gBACpB,KACA,QACA,aACA,gBACA,wBACe;AACf,QAAM,UAAU,kBAAkB,GAAG;AACrC,MAAI,CAAC,QAAS;AACd,QAAM,0BAA0B,SAAS,QAAQ,aAAa,gBAAgB,sBAAsB;AACtG;AAEA,eAAe,0BACb,SACA,QACA,aACA,gBACA,wBACA,QACA,eAAe,OACA;AACf,QAAM,WAA6B,CAAC;AACpC,QAAM,gBAAgB,oBAAI,IAAY;AACtC,aAAW,OAAO,aAAa;AAC7B,UAAM,UAAU,iBAAiB,GAAG;AACpC,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,yBAAyB,SAAS,QAAQ,UAAU;AACjE,QAAI,SAAS,eAAe,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,GAAI;AACnE,QAAI,KAAM,eAAc,IAAI,IAAI;AAChC,aAAS,KAAK,OAAO;AAAA,EACvB;AACA,MAAI,SAAS,WAAW,EAAG;AAO3B,MAAI,UAAU,CAAC,gBAAgB,CAAC,OAAO,YAAY,EAAG;AAMtD,QAAM,iBAAiB,SACnB,EAAE,WAAW,eAAe,OAAO,mBAAmB,OAAO,qBAAqB,IAClF;AACJ,MAAI;AACF,UAAM,OAAO,QAAQ,QAAQ,YAAY,QAAQ,KAAK,UAAU,cAAc;AAC9E,QAAI,OAAQ,QAAO,cAAc;AACjC,eAAW,QAAQ,cAAe,sBAAqB,gBAAgB,IAAI;AAC3E,QAAI,wBAAwB;AAC1B,iBAAW,WAAW,UAAU;AAC9B,sCAA8B,wBAAwB,cAAc,SAAS,QAAQ,UAAU,CAAC;AAAA,MAClG;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,UAAU,yBAAyB,GAAG,EAAG,QAAO,gBAAgB,OAAO,gBAAgB;AAC3F,YAAQ,OAAO,0BAA0B,aAAa,GAAG,CAAC,IAAI,SAAS;AAAA,EACzE;AACF;AAEO,SAAS,uBAAuB,aAA0B;AAC/D,QAAM,kBAAkB,OAAO,YAAY,oBAAoB,WAC3D,YAAY,gBAAgB,KAAK,IACjC;AACJ,QAAM,WAAW;AAAA,IACf,GAAI,MAAM,QAAQ,YAAY,mBAAmB,IAAI,YAAY,sBAAsB,CAAC;AAAA,IACxF,GAAI,MAAM,QAAQ,YAAY,kBAAkB,IAAI,YAAY,qBAAqB,CAAC;AAAA,EACxF;AACA,QAAM,aAAa,kBAAkB,UAAU,IAAK;AACpD,QAAM,UAAU,2BAA2B,WAAW;AAEtD,MACE,CAAC,mBACD,CAAC,cACD,QAAQ,UAAU,WAAW,KAC7B,QAAQ,cAAc,WAAW,GACjC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,WAAqB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,gBAAiB,UAAS,KAAK,IAAI,uBAAuB,eAAe;AAC7E,MAAI,WAAY,UAAS,KAAK,IAAI,2BAA2B,UAAU;AACvE,MAAI,QAAQ,UAAU,SAAS,EAAG,UAAS,KAAK,IAAI,gBAAgB,GAAG,QAAQ,WAAW,eAAe;AACzG,MAAI,QAAQ,cAAc,SAAS,EAAG,UAAS,KAAK,IAAI,oBAAoB,GAAG,QAAQ,eAAe,mBAAmB;AACzH,SAAO,SAAS,KAAK,IAAI;AAC3B;AAEA,SAAS,2BAA2B,aAAoE;AACtG,QAAM,UAAU,aAAa;AAC7B,QAAM,OAAO,SAAS,gBAAgB,MAAM,MAAM,KAAK,QAAQ,IAAI,EAAE,OAAO,QAAQ,IAAI,CAAC;AACzF,QAAM,SAAS,SAAS,kBAAkB,MAAM,MAAM,KAAK,QAAQ,MAAM,EAAE,OAAO,QAAQ,IAAI,CAAC;AAC/F,QAAM,UAAU,SAAS,mBAAmB,MAAM,MAAM,KAAK,QAAQ,OAAO,EAAE,OAAO,QAAQ,IAAI,CAAC;AAClG,QAAM,WAAW,oBAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,OAAO,CAAC;AAChD,SAAO;AAAA,IACL,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK;AAAA,IAC3D,eAAe,MAAM,KAAK,QAAQ,EAAE,KAAK;AAAA,EAC3C;AACF;AAEA,SAAS,qBAAqB,SAA4B,gBAAmC;AAC3F,aAAW,SAAS,QAAQ,SAAS;AACnC,QAAI,OAAO,SAAS,YAAY,MAAM,eAAe,kBAAmB;AACxE,UAAM,SAAS,MAAM,MAAM;AAC3B,QAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,iBAAW,QAAQ,QAAQ;AACzB,YAAI,OAAO,SAAS,SAAU,sBAAqB,gBAAgB,IAAI;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,gBAA6B,MAAoB;AAC7E,MAAI,eAAe,IAAI,IAAI,EAAG;AAC9B,SAAO,eAAe,QAAQ,qBAAqB;AACjD,UAAM,SAAS,eAAe,KAAK,EAAE,KAAK,EAAE;AAC5C,QAAI,OAAO,WAAW,SAAU;AAChC,mBAAe,OAAO,MAAM;AAAA,EAC9B;AACA,iBAAe,IAAI,IAAI;AACzB;AAEA,SAAS,8BAA8B,wBAA6C,KAAmB;AACrG,yBAAuB,IAAI,MAAM,uBAAuB,IAAI,GAAG,KAAK,KAAK,CAAC;AAC5E;AAEA,SAAS,6BAA6B,wBAA6C,KAAsB;AACvG,QAAM,QAAQ,uBAAuB,IAAI,GAAG,KAAK;AACjD,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,UAAU,EAAG,wBAAuB,OAAO,GAAG;AAAA,MAC7C,wBAAuB,IAAI,KAAK,QAAQ,CAAC;AAC9C,SAAO;AACT;AAEA,SAAS,+BACP,YACA,aACA,wBACW;AACX,MAAI,uBAAuB,SAAS,EAAG,QAAO;AAC9C,QAAM,aAAwB,CAAC;AAC/B,aAAW,OAAO,aAAa;AAC7B,UAAM,UAAU,iBAAiB,GAAG;AACpC,QAAI,WAAW,6BAA6B,wBAAwB,cAAc,SAAS,UAAU,CAAC,GAAG;AACvG;AAAA,IACF;AACA,eAAW,KAAK,GAAG;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,cAAc,SAAyB,YAA4B;AAC1E,SAAO,oBAAoB,SAAS,YAAY,aAAa;AAC/D;AAEA,SAAS,qBAAqB,IAAW,gBAAmC;AAC1E,QAAM,WAAW,MAAM,KAAK,cAAc,EAAE,MAAM,CAAC,mBAAmB;AACtE,KAAG,YAAY,mBAAmB;AAAA,IAChC,gBAAgB;AAAA,IAChB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC,CAAC;AACH;AAkBA,eAAe,kBAAkB,QAAsB,QAAoD;AACzG,MAAI;AACF,UAAM,OAAO,OAAO,EAAE,WAAW,OAAO,wBAAwB,CAAC;AAGjE,WAAO,cAAc;AACrB,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,eAAe,mBAAmB,IAAI,WAAW,OAAO,IAAI,SAAS,aAAa;AACpF,aAAO,cAAc;AACrB,aAAO;AAAA,IACT;AAIA,QAAI,yBAAyB,GAAG,EAAG,QAAO,gBAAgB,OAAO,gBAAgB;AACjF,WAAO;AAAA,EACT;AACF;AAGA,SAAS,kBAAkB,OAA0B,WAAuC;AAC1F,MAAI,UAAU,cAAe,QAAO;AACpC,MAAI,UAAU,WAAY,QAAO;AACjC,SAAO,UAAU,YAAY,IAAI,SAAS,MAAM,OAAO;AACzD;AAmBA,eAAe,sBACb,IACA,SACA,QACA,QACe;AAGf,MAAI,CAAC,OAAO,aAAa,CAAC,OAAO,YAAY,EAAG;AAChD,QAAM,SAAS,MAAM,OAAO,mBAAmB,QAAQ,YAAY;AAAA,IACjE,WAAW,OAAO;AAAA,EACpB,CAAC;AACD,MAAI,OAAO,WAAW,gBAAgB;AAIpC,UAAM,MACJ,OAAO,WAAW,gBACd,mKACA;AACN,UAAM,UACJ,iCAAiC,OAAO,SAAS,kDAC7C,OAAO,MAAM,sHAC2B,GAAG;AACjD,YAAQ,OAAO,SAAS,OAAO;AAC/B,OAAG,YAAY,mBAAmB;AAAA,MAChC,OAAO;AAAA,MACP,MAAM;AAAA,MACN,WAAW,OAAO;AAAA,MAClB,QAAQ,OAAO;AAAA,MACf;AAAA,MACA,YAAY;AAAA,MACZ,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC,CAAC;AACD;AAAA,EACF;AACA,MAAI,OAAO,WAAW,YAAY;AAChC,OAAG,YAAY,mBAAmB;AAAA,MAChC,OAAO;AAAA,MACP,MAAM;AAAA,MACN,WAAW,OAAO;AAAA,MAClB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC,CAAC;AAAA,EACH;AAEF;AAEA,SAAS,kBAAkB,KAAU,UAAoC,CAAC,GAA6B;AACrG,QAAM,aAAa,0BAA0B,GAAG;AAChD,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,MAAM,eAAe,MAAM,KAAK,KAAK,EAAE;AAC7C,QAAM,QAAQ,SAAS,MAAM,KAAK,OAAO,MAAS,MAAM;AACxD,QAAM,KAAK,QAAQ,SAAY,SAAS,MAAM,KAAK,IAAI,MAAS;AAChE,QAAM,UAAU,SAAS,MAAM,KAAK,SAAS,MAAS;AACtD,QAAM,wBAAwB,QAAQ,0BAA0B;AAChE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,wBAAwB,YAAY,GAAG,IAAI,CAAC;AAAA,IACrD,QAAQ,wBAAwB,WAAW,GAAG,IAAI,CAAC;AAAA,IACnD,QAAQ,aAAa,IAAI,KAAK;AAAA,IAC9B,WAAW,iBAAiB,IAAI,KAAK;AAAA,IACrC,SAAS,OAAO,YAAY,aAAa,MAAM,QAAQ,KAAK,GAAG,IAAI;AAAA,EACrE;AACF;AAEA,SAAS,0BAA0B,KAAyB;AAC1D,MAAI;AACF,WAAO,sBAAsB,GAAG;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,IAAa,OAA0B;AAC3D,MAAI,SAAS,CAAC,SAAS,EAAE,KAAK,OAAO,GAAG,WAAW,YAAY;AAC7D,WAAO,MAAM;AAAA,EACf;AACA,QAAM,WAAW,GAAG;AACpB,SAAO,CAAC,SAAS,UAAU;AACzB,QAAI;AACF,eAAS,KAAK,IAAI,SAAS,KAAK;AAAA,IAClC,QAAQ;AAAA,IAGR;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,IAAa,OAAgD;AACrF,MAAI,SAAS,CAAC,SAAS,EAAE,KAAK,OAAO,GAAG,cAAc,YAAY;AAChE,WAAO,MAAM;AAAA,EACf;AACA,QAAM,cAAc,GAAG;AACvB,SAAO,CAAC,KAAK,UAAU;AACrB,QAAI;AACF,kBAAY,KAAK,IAAI,KAAK,KAAK;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,SAAS,SAAY,MAAe,UAAgB;AAClD,MAAI;AACF,WAAO,KAAK;AAAA,EACd,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAe,MAAqB,UAA0B;AACrE,QAAM,QAAQ,SAAS,MAAM,QAAQ;AACrC,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAS,YAAY,KAAiB;AACpC,MAAI;AACF,UAAM,UAAU,IAAI,gBAAgB,aAAa;AACjD,WAAO,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC;AAAA,EAC7C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,WAAW,KAAiB;AACnC,MAAI;AACF,UAAM,SAAS,IAAI,gBAAgB,YAAY;AAC/C,WAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC;AAAA,EAC3C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,gCAAgC,QAA0B;AACjE,QAAM,WAAsB,CAAC;AAC7B,aAAW,SAAS,QAAQ;AAC1B,UAAM,UAAU,yBAAyB,KAAK;AAC9C,QAAI,QAAS,UAAS,KAAK,OAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,yBAAyB,OAA4B;AAC5D,QAAM,UAAU,OAAO;AACvB,MAAI,CAAC,WAAW,OAAO,YAAY,YAAY,MAAM,QAAQ,OAAO,EAAG,QAAO,WAAW;AAEzF,QAAM,SAAS,SAAS,KAAK,IAAI,QAAQ,CAAC;AAC1C,QAAM,WAAoC,EAAE,GAAI,QAAoC;AACpF,wBAAsB,UAAU,WAAW,OAAO,MAAM,OAAO,WAAW,OAAO,QAAQ;AACzF,wBAAsB,UAAU,aAAa,OAAO,SAAS;AAC7D,wBAAsB,UAAU,aAAa,OAAO,aAAa,OAAO,UAAU;AAClF,SAAO;AACT;AAEA,SAAS,sBAAsB,QAAiC,OAAe,OAAsB;AACnG,MAAI,OAAO,KAAK,MAAM,OAAW;AACjC,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO,KAAK,IAAI;AAChB;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO,KAAK,IAAI;AAAA,EAClB;AACF;AAEA,SAAS,YAAY,OAAe,QAAwB;AAC1D,MAAI,MAAM,UAAU,OAAQ,QAAO;AACnC,MAAI,UAAU,kBAAkB,OAAQ,QAAO,kBAAkB,MAAM,GAAG,MAAM;AAChF,SAAO,GAAG,MAAM,MAAM,GAAG,SAAS,kBAAkB,MAAM,CAAC,GAAG,iBAAiB;AACjF;AAGO,SAAS,yBAAyB,KAAuB;AAC9D,MAAI,EAAE,eAAe,OAAQ,QAAO;AACpC,MAAI,2BAA2B,KAAK,IAAI,OAAO,EAAG,QAAO;AAMzD,MAAI,wDAAwD,KAAK,IAAI,OAAO,EAAG,QAAO;AAKtF,MAAI,uDAAuD,KAAK,IAAI,OAAO,EAAG,QAAO;AACrF,SAAO,wBAAwB,GAAG;AACpC;AAEA,SAAS,aAAa,KAAsB;AAC1C,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,SAAS,iBAAiB,OAA+B;AACvD,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,SAAS,IAAI,QAAQ;AACrF;AAEA,SAAS,SAAS,OAAiC;AACjD,SAAO,OAAO,UAAU;AAC1B;","names":["path","remnicPiExtension"]}
|
package/dist/publisher.js
CHANGED
|
@@ -125,7 +125,7 @@ var HostMemoryExtensionPublisher = class {
|
|
|
125
125
|
"",
|
|
126
126
|
"## Installed Capabilities",
|
|
127
127
|
"",
|
|
128
|
-
"- Recall relevant Remnic context in the `
|
|
128
|
+
"- Recall relevant Remnic context in the `before_agent_start` hook via system prompt injection.",
|
|
129
129
|
'- Observe user, assistant, and tool messages with `sourceFormat: "pi"`.',
|
|
130
130
|
"- Coordinate `session_before_compact` with Remnic LCM flush and checkpoint recording.",
|
|
131
131
|
"- Register Remnic MCP tools as host tools when daemon authentication is configured.",
|
package/dist/publisher.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/publisher.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport os from \"node:os\";\n\nimport {\n type MemoryExtensionPublisher,\n type PublishContext,\n type PublishResult,\n type PublisherCapabilities,\n type TokenEntry,\n getConnectorToken,\n loadTokenStore,\n saveTokenStore,\n} from \"@remnic/core\";\n\nimport {\n resolveOmpAgentHome,\n resolveOmpConfigRoot,\n resolveOmpExtensionRoot,\n resolvePiAgentHome,\n resolvePiExtensionRoot,\n} from \"./paths.js\";\n\nconst DEFAULT_DAEMON_PORT = 4318;\nconst BASE_OWNED_FILES = [\"remnic.config.json\", \"index.ts\", \"README.md\"] as const;\nconst EXTENSION_OWNED_TEMP_FILE_SUFFIX = /\\.tmp-\\d+-\\d+$/u;\n\ntype FileSnapshot = {\n path: string;\n existed: boolean;\n content?: Buffer;\n mode?: number;\n};\n\ntype DirSnapshot = {\n path: string;\n existed: boolean;\n};\n\n/**\n * Host-specific parameters for a Pi-family memory extension publisher.\n *\n * The Remnic runtime extension is host-neutral (it only uses Pi's extension\n * hooks, which omp preserves as a superset), so the only things that vary\n * between hosts are *where* the extension is installed, *which* connector\n * token it uses, and *how* it is labelled. Everything else — atomic writes,\n * rollback, symlink guards, config merge — is shared.\n */\nexport interface HostPublisherDescriptor {\n readonly hostId: string;\n readonly connectorId: string;\n readonly displayName: string;\n readonly tokenGenerateHint: string;\n resolveAgentHome(env: NodeJS.ProcessEnv): string;\n resolveExtensionRoot(env: NodeJS.ProcessEnv): string;\n /**\n * Optional: every agent home `unpublish` should sweep for a stale extension,\n * beyond the one resolved from the current env. Hosts with env-sensitive\n * install locations (e.g. omp profiles) provide this so `remnic connectors\n * remove` cleans up even when the remove-time env differs from install time.\n */\n listRemovalAgentHomes?(env: NodeJS.ProcessEnv): string[];\n}\n\nconst PI_HOST: HostPublisherDescriptor = {\n hostId: \"pi\",\n connectorId: \"pi\",\n displayName: \"Pi Coding Agent\",\n tokenGenerateHint: \"remnic token generate pi\",\n resolveAgentHome: resolvePiAgentHome,\n resolveExtensionRoot: resolvePiExtensionRoot,\n};\n\nconst OMP_HOST: HostPublisherDescriptor = {\n hostId: \"omp\",\n connectorId: \"omp\",\n displayName: \"Oh My Pi (omp)\",\n tokenGenerateHint: \"remnic token generate omp\",\n resolveAgentHome: resolveOmpAgentHome,\n resolveExtensionRoot: resolveOmpExtensionRoot,\n listRemovalAgentHomes: ompRemovalAgentHomes,\n};\n\n/**\n * Every omp agent home a stale extension might live under, so `unpublish` cleans\n * up regardless of the profile/env active at remove time: the env-resolved home,\n * the base `<configRoot>/agent`, an explicit `PI_CODING_AGENT_DIR`, and every\n * existing `<configRoot>/profiles/<name>/agent`. Symlinked profile dirs are\n * skipped defensively.\n */\nfunction ompRemovalAgentHomes(env: NodeJS.ProcessEnv): string[] {\n const homes = new Set<string>([resolveOmpAgentHome(env)]);\n const configRoot = resolveOmpConfigRoot(env);\n homes.add(path.join(configRoot, \"agent\"));\n\n const explicit = env.PI_CODING_AGENT_DIR?.trim();\n if (explicit) homes.add(path.resolve(explicit));\n\n const profilesDir = path.join(configRoot, \"profiles\");\n let entries: fs.Dirent[] = [];\n try {\n entries = fs.readdirSync(profilesDir, { withFileTypes: true });\n } catch {\n entries = [];\n }\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.isSymbolicLink()) {\n homes.add(path.join(profilesDir, entry.name, \"agent\"));\n }\n }\n return [...homes];\n}\n\n/**\n * Shared publisher for Pi-family hosts. Concrete hosts (Pi, omp) subclass this\n * with a {@link HostPublisherDescriptor}; the install/rollback machinery is\n * identical across hosts.\n */\nexport class HostMemoryExtensionPublisher implements MemoryExtensionPublisher {\n static readonly capabilities: PublisherCapabilities = {\n // Real publisher: writes host config + wrapper + readme, just no\n // instructions.md/skills/citation/read-path-template artefacts. The\n // explicit flag prevents the parity gate from mis-inferring \"all flags\n // false ⇒ stub\" for this host (#1518).\n isStub: false,\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n protected constructor(private readonly host: HostPublisherDescriptor) {}\n\n /**\n * File basenames this publisher owns inside the extension root. The shared\n * set is config + wrapper + readme; subclasses add host-specific files\n * (e.g. omp's pre-bundle loader + package manifest). Used for snapshot,\n * atomic-write rollback, and unpublish cleanup.\n */\n protected get ownedFileNames(): readonly string[] {\n return BASE_OWNED_FILES;\n }\n\n /**\n * Directory names this publisher owns inside the extension root (build\n * outputs). Recursively removed on unpublish and on publish rollback when\n * newly created.\n */\n protected get ownedDirNames(): readonly string[] {\n return [];\n }\n\n /**\n * Whether the generated wrapper must use a bun-buildable import specifier\n * (relative path) instead of a file:// URL. omp pre-bundles the wrapper with\n * `bun build`, which cannot resolve file:// specifiers; pi loads the wrapper\n * directly via tsx and keeps the file:// URL.\n */\n protected get usesBundledWrapper(): boolean {\n return false;\n }\n\n /**\n * Hook for subclasses to write host-specific files and run install-time\n * build steps after the shared config/wrapper/readme are written. Runs\n * inside the publish try-block: a throw triggers full rollback.\n */\n protected finalizePublish(\n _ctx: PublishContext,\n _extensionRoot: string,\n _paths: { configPath: string; wrapperPath: string; pluginPiDistPath: string },\n ): void {\n // No-op by default; subclasses override.\n }\n\n get hostId(): string {\n return this.host.hostId;\n }\n\n async resolveExtensionRoot(env?: NodeJS.ProcessEnv): Promise<string> {\n return this.host.resolveExtensionRoot(env ?? process.env);\n }\n\n async isHostAvailable(): Promise<boolean> {\n // Pi-family agents auto-discover extensions from their agent extensions\n // directory. The directory can be created before the agent has been\n // launched, so availability should not block first-time installation.\n return true;\n }\n\n async renderInstructions(ctx: PublishContext): Promise<string> {\n const namespace = ctx.config.namespace ?? \"default\";\n const daemonUrl = resolveDaemonUrl(ctx);\n return [\n `# Remnic for ${this.host.displayName}`,\n \"\",\n `Remnic provides memory, retrieval, observation, MCP tools, and long-context compaction coordination for ${this.host.displayName}.`,\n \"\",\n \"## Installed Capabilities\",\n \"\",\n \"- Recall relevant Remnic context in the `context` hook before agent turns.\",\n '- Observe user, assistant, and tool messages with `sourceFormat: \"pi\"`.',\n \"- Coordinate `session_before_compact` with Remnic LCM flush and checkpoint recording.\",\n \"- Register Remnic MCP tools as host tools when daemon authentication is configured.\",\n \"- Persist lightweight dedupe state in custom entries via `appendEntry`.\",\n \"\",\n \"## Runtime\",\n \"\",\n `- Remnic daemon: \\`${daemonUrl}\\``,\n `- Namespace: \\`${namespace}\\``,\n `- Memory directory: \\`${ctx.config.memoryDir}\\``,\n \"\",\n \"The private `remnic.config.json` file stores the daemon URL, namespace, and connector auth token with owner-only permissions.\",\n ].join(\"\\n\");\n }\n\n async publish(ctx: PublishContext): Promise<PublishResult> {\n const extensionRoot = await this.resolveExtensionRoot();\n const agentHome = this.host.resolveAgentHome(process.env);\n assertSafeExtensionRoot(extensionRoot, agentHome);\n const filesWritten: string[] = [];\n const skipped: string[] = [];\n\n ctx.log.info(`Publishing ${this.host.displayName} memory extension to ${extensionRoot}`);\n\n const ownedFilePaths = this.ownedFileNames.map((fileName) => path.join(extensionRoot, fileName));\n const configPath = ownedFilePaths[0];\n const wrapperPath = ownedFilePaths[1];\n const readmePath = ownedFilePaths[2];\n const pluginPiDistPath = resolveExtensionModulePath();\n const rootExisted = fs.existsSync(extensionRoot);\n const fileSnapshots = snapshotFiles(ownedFilePaths);\n const dirSnapshots = snapshotDirs(\n this.ownedDirNames.map((dirName) => path.join(extensionRoot, dirName)),\n );\n const priorTokenEntry =\n ctx.rollbackTokenEntry === undefined\n ? snapshotTokenEntry(this.host.connectorId)\n : cloneTokenEntry(ctx.rollbackTokenEntry);\n\n const token = getConnectorToken(this.host.connectorId);\n if (!token) {\n skipped.push(\n `auth token unavailable; run \\`${this.host.tokenGenerateHint}\\` and reinstall the connector`,\n );\n }\n\n try {\n const priorConfig = readPriorConfig(configPath);\n const config: Record<string, unknown> = {\n recallMode: \"auto\",\n recallTopK: 8,\n recallBudgetChars: 12000,\n recallEnabled: true,\n observeEnabled: true,\n observeSkipExtraction: false,\n compactionEnabled: true,\n mcpToolsEnabled: true,\n statusEnabled: true,\n requestTimeoutMs: 60000,\n startupRequestTimeoutMs: 1000,\n ...priorConfig,\n remnicDaemonUrl: resolveDaemonUrl(ctx),\n };\n if (token) {\n config.authToken = token;\n }\n if (ctx.config.namespace) {\n config.namespace = ctx.config.namespace;\n }\n\n mkdirExtensionRoot(extensionRoot, agentHome);\n\n atomicWriteFile(configPath, `${JSON.stringify(config, null, 2)}\\n`, 0o600);\n filesWritten.push(configPath);\n\n atomicWriteFile(\n wrapperPath,\n renderWrapper(\n pluginPiDistPath,\n configPath,\n this.usesBundledWrapper ? extensionRoot : undefined,\n ),\n 0o644,\n );\n filesWritten.push(wrapperPath);\n\n atomicWriteFile(readmePath, `${await this.renderInstructions(ctx)}\\n`, 0o644);\n filesWritten.push(readmePath);\n\n this.finalizePublish(ctx, extensionRoot, { configPath, wrapperPath, pluginPiDistPath });\n for (let i = BASE_OWNED_FILES.length; i < ownedFilePaths.length; i++) {\n filesWritten.push(ownedFilePaths[i]);\n }\n } catch (err) {\n try {\n // Remove newly created owned dirs (e.g. dist-bundle) BEFORE\n // restorePublishSnapshot's removeEmptyDirectory check, otherwise a\n // first-time publish that created dist-bundle would leave an empty\n // extension root behind on rollback.\n restoreDirSnapshots(dirSnapshots);\n restorePublishSnapshot(extensionRoot, rootExisted, fileSnapshots);\n } catch (restoreErr) {\n ctx.log.warn(\n `${this.host.displayName} extension rollback failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`,\n );\n }\n // A failed omp pre-bundle (bun missing or `bun build` failing) is\n // recoverable: the runtime loader self-heals dist-bundle on first load,\n // and the connector token is already committed by the CLI. Rolling it\n // back here would leave the connector registered with no credential and\n // block a non-`--force` reinstall (AGENTS.md #14 — don't destroy\n // committed state before the new state is confirmed). File/dir rollback\n // above still runs, so a failed first-time publish still cleans its root.\n if (!(err instanceof OmpPreBundleError)) {\n try {\n restoreTokenEntry(priorTokenEntry, this.host.connectorId);\n } catch (tokenErr) {\n ctx.log.warn(\n `${this.host.displayName} connector token rollback failed: ${tokenErr instanceof Error ? tokenErr.message : String(tokenErr)}`,\n );\n }\n }\n throw err;\n }\n\n return {\n hostId: this.host.hostId,\n extensionRoot,\n filesWritten,\n skipped,\n };\n }\n\n async unpublish(): Promise<void> {\n const agentHomes = this.host.listRemovalAgentHomes\n ? this.host.listRemovalAgentHomes(process.env)\n : [this.host.resolveAgentHome(process.env)];\n\n const ownedFileNames = this.ownedFileNames;\n const ownedDirNames = this.ownedDirNames;\n const seen = new Set<string>();\n for (const agentHome of agentHomes) {\n const extensionRoot = path.join(path.resolve(agentHome), \"extensions\", \"remnic\");\n if (seen.has(extensionRoot)) continue;\n seen.add(extensionRoot);\n if (!fs.existsSync(extensionRoot)) continue;\n\n assertSafeExtensionRoot(extensionRoot, agentHome);\n const removableFiles = removableOwnedExtensionFiles(\n extensionOwnedUnpublishPaths(extensionRoot, ownedFileNames),\n );\n for (const filePath of removableFiles) {\n fs.rmSync(filePath, { force: true });\n }\n for (const dirName of ownedDirNames) {\n const dirPath = path.join(extensionRoot, dirName);\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(dirPath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") continue;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${dirPath}`);\n }\n if (stat.isDirectory()) {\n fs.rmSync(dirPath, { recursive: true, force: true });\n }\n }\n removeEmptyDirectory(extensionRoot);\n }\n }\n}\n\n/** Publisher for upstream Pi (`~/.pi/agent/extensions/remnic`). */\nexport class PiMemoryExtensionPublisher extends HostMemoryExtensionPublisher {\n constructor() {\n super(PI_HOST);\n }\n}\n\n/**\n * Marks a failure originating from the omp pre-bundle step (bun missing, or the\n * `bun build` itself failing). {@link HostMemoryExtensionPublisher.publish}\n * catches this and rolls back the written files but SKIPS the connector-token\n * rollback: the pre-bundle runs after the install (config + wrapper + token) is\n * already committed, and the runtime loader self-heals `dist-bundle` on first\n * load, so destroying the just-generated token would leave the connector\n * registered with no credential and block a non-`--force` reinstall\n * (AGENTS.md #14 — don't destroy committed state before the new state is\n * confirmed). The message is preserved verbatim so existing `/requires \\`bun\\`/`\n * and `/bun build failed/` assertions still match.\n */\nclass OmpPreBundleError extends Error {}\n\n/** Publisher for Oh My Pi / omp (`~/.omp/agent/extensions/remnic`). */\nexport class OmpMemoryExtensionPublisher extends HostMemoryExtensionPublisher {\n constructor() {\n super(OMP_HOST);\n }\n\n protected get ownedFileNames(): readonly string[] {\n return [...BASE_OWNED_FILES, \"loader.js\", \"package.json\", \"postinstall-bundle.cjs\"];\n }\n\n protected get ownedDirNames(): readonly string[] {\n return [\"dist-bundle\"];\n }\n\n // omp pre-bundles index.ts with `bun build`; the wrapper must use a relative\n // import specifier (bun's bundler cannot resolve file:// URLs).\n protected get usesBundledWrapper(): boolean {\n return true;\n }\n\n protected finalizePublish(\n ctx: PublishContext,\n extensionRoot: string,\n paths: { configPath: string; wrapperPath: string; pluginPiDistPath: string },\n ): void {\n // Resolve once so the install-time build and the generated loader share the\n // same bun path — a loader that hardcodes \"bun\" cannot self-heal when bun\n // is reachable only via REMNIC_OMP_BUN_BIN or a common absolute install\n // path that is not on omp's PATH at runtime.\n const bunBin = resolveBunBinary();\n if (!bunBin) {\n // OmpPreBundleError so publish() keeps the connector token intact (see\n // the class doc); the runtime loader self-heals the bundle once bun is\n // installed.\n throw new OmpPreBundleError(\n \"Remnic omp extension requires `bun` to pre-bundle the extension: omp's embedded \" +\n \"runtime cannot resolve bare npm specifiers from the extension's node_modules. \" +\n \"Install bun from https://bun.sh, then re-run `remnic connectors install omp`.\",\n );\n }\n\n const loaderPath = path.join(extensionRoot, \"loader.js\");\n const packageJsonPath = path.join(extensionRoot, \"package.json\");\n\n const postinstallPath = path.join(extensionRoot, \"postinstall-bundle.cjs\");\n\n atomicWriteFile(loaderPath, renderOmpLoader(paths.pluginPiDistPath, bunBin), 0o644);\n // Cross-platform postinstall helper (Node-only) so npm's default cmd.exe\n // shell on Windows re-bundles after `npm install`; the POSIX one-liner it\n // replaces only ran under bash.\n atomicWriteFile(postinstallPath, renderOmpPostinstall(bunBin), 0o644);\n atomicWriteFile(packageJsonPath, renderOmpPackageJson(), 0o644);\n\n try {\n this.runBundleBuild(ctx, extensionRoot, bunBin);\n } catch (err) {\n // OmpPreBundleError so publish() keeps the connector token intact (see\n // the class doc); the runtime loader self-heals the bundle on next load.\n const message = err instanceof Error ? err.message : String(err);\n throw new OmpPreBundleError(message);\n }\n }\n\n /**\n * Pre-bundles the omp extension with `bun build` so omp's embedded runtime\n * never resolves bare npm specifiers (e.g. @sinclair/typebox) from the\n * extension's node_modules at load time. The bundle is written to a temp\n * directory and swapped into dist-bundle/ on success. The pre-existing\n * dist-bundle is renamed aside (not removed) before the swap, so a failure\n * during the final rename restores the previously working bundle rather than\n * leaving the install with no bundle at all.\n *\n * Override in tests to skip the real bun invocation.\n */\n protected runBundleBuild(ctx: PublishContext, extensionRoot: string, bunBin: string): void {\n const sourceEntry = path.join(extensionRoot, \"index.ts\");\n const tmpOutDir = path.join(extensionRoot, `.dist-bundle.tmp-${process.pid}-${Date.now()}`);\n const finalOutDir = path.join(extensionRoot, \"dist-bundle\");\n\n const result = spawnSync(bunBin, [\"build\", sourceEntry, \"--target=bun\", `--outdir=${tmpOutDir}`], {\n cwd: extensionRoot,\n encoding: \"utf-8\",\n });\n\n if (result.error || result.status !== 0) {\n try {\n fs.rmSync(tmpOutDir, { recursive: true, force: true });\n } catch {\n // best-effort tmp cleanup\n }\n const detail =\n (typeof result.stderr === \"string\" ? result.stderr.trim() : \"\") ||\n (result.error instanceof Error ? result.error.message : \"\") ||\n `bun exited with status ${result.status ?? \"null\"}`;\n throw new Error(\n `Remnic omp extension: bun build failed (${detail}). Resolve the error and re-run ` +\n \"`remnic connectors install omp`, or build manually with \" +\n \"`bun build index.ts --target=bun --outdir=dist-bundle` inside \" +\n `${extensionRoot}.`,\n );\n }\n\n // Swap the freshly built bundle into place without ever leaving the install\n // bundle-less. Rename the existing dist-bundle aside, move the new one in,\n // and only then discard the backup. On any failure mid-swap, restore the\n // backup so the previously working bundle survives (the publish-level\n // rollback only removes newly created dirs — it never restores a removed\n // dist-bundle, so we must not remove it here).\n let backupDir: string | null = null;\n try {\n if (fs.existsSync(finalOutDir)) {\n backupDir = path.join(extensionRoot, `.dist-bundle.bak-${process.pid}-${Date.now()}`);\n fs.renameSync(finalOutDir, backupDir);\n }\n fs.renameSync(tmpOutDir, finalOutDir);\n if (backupDir) {\n try {\n fs.rmSync(backupDir, { recursive: true, force: true });\n } catch {\n // best-effort backup cleanup; leaving it does not break the install\n }\n }\n } catch (err) {\n try {\n if (fs.existsSync(tmpOutDir)) fs.rmSync(tmpOutDir, { recursive: true, force: true });\n } catch {\n // best-effort tmp cleanup\n }\n // Restore the previously working bundle if we moved it aside and the\n // final swap did not land.\n if (backupDir && fs.existsSync(backupDir) && !fs.existsSync(finalOutDir)) {\n try {\n fs.renameSync(backupDir, finalOutDir);\n } catch {\n // best-effort restore; the loader's self-heal rebuilds on next start\n }\n }\n throw new Error(\n `Remnic omp extension: failed to finalize bundle output — ${err instanceof Error ? err.message : String(err)}.`,\n );\n }\n\n ctx.log.info(`Pre-bundled omp extension into ${finalOutDir}`);\n }\n}\n\nfunction extensionOwnedPaths(extensionRoot: string, ownedFileNames: readonly string[]): string[] {\n return ownedFileNames.map((fileName) => path.join(extensionRoot, fileName));\n}\n\nfunction extensionOwnedUnpublishPaths(extensionRoot: string, ownedFileNames: readonly string[]): string[] {\n const ownedBaseNames = new Set(ownedFileNames);\n const ownedPaths = extensionOwnedPaths(extensionRoot, ownedFileNames);\n for (const fileName of fs.readdirSync(extensionRoot)) {\n const match = EXTENSION_OWNED_TEMP_FILE_SUFFIX.exec(fileName);\n if (match && ownedBaseNames.has(fileName.slice(0, match.index))) {\n ownedPaths.push(path.join(extensionRoot, fileName));\n }\n }\n return ownedPaths;\n}\n\nfunction resolveDaemonUrl(ctx: PublishContext): string {\n if (ctx.config.daemonUrl && ctx.config.daemonUrl.trim().length > 0) {\n return trimTrailingSlashes(ctx.config.daemonUrl.trim());\n }\n return `http://127.0.0.1:${ctx.config.daemonPort ?? DEFAULT_DAEMON_PORT}`;\n}\n\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;\n return value.slice(0, end);\n}\n\nfunction resolveExtensionModulePath(): string {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const built = path.join(moduleDir, \"index.js\");\n if (fs.existsSync(built)) return built;\n\n const source = path.join(moduleDir, \"index.ts\");\n if (fs.existsSync(source)) return source;\n\n return built;\n}\n\n/**\n * Resolves the import specifier the omp wrapper uses to reach the\n * `@remnic/plugin-pi` dist entry from the generated `index.ts`. omp pre-bundles\n * that wrapper with `bun build`, whose bundler cannot resolve `file://`\n * specifiers (\"Could not resolve: file://…\" on Bun 1.2–1.3, verified), so the\n * specifier must be a relative path. On Windows, when the extension directory\n * and the plugin-pi install sit on different drives, `path.relative` cannot\n * express a relative path and returns an absolute drive path (e.g. `D:\\…`);\n * prefixing `./` then yields an invalid module specifier that fails `bun build`\n * with a cryptic error. Detect that layout and fail fast with an actionable\n * message instead. (Cross-drive omp installs are unsupported because neither a\n * relative specifier nor a `file://` URL is acceptable to `bun build`.) Drive\n * roots are compared case-insensitively so a same-drive Windows install is not\n * falsely rejected when the agent home and the plugin-pi install report the\n * drive letter in different casing (`C:\\\\` vs `c:\\\\`).\n *\n * Exported so the cross-drive guard can be exercised on non-Windows hosts via\n * `path.win32`.\n */\nexport function resolveOmpWrapperImportSpecifier(\n extensionModulePath: string,\n wrapperDir: string,\n pathApi: typeof path = path,\n): string {\n // Windows drive roots are case-insensitive: `C:\\\\…` (e.g. from the omp agent\n // home) and `c:\\\\…` (e.g. from fileURLToPath(import.meta.url)) are the SAME\n // drive, and path.win32.relative yields a valid relative specifier between\n // them. Compare the parsed roots case-insensitively so a same-drive install\n // isn't falsely rejected as \"different drives\". posix roots (`/`) are\n // unaffected by toLowerCase().\n if (pathApi.parse(wrapperDir).root.toLowerCase() !== pathApi.parse(extensionModulePath).root.toLowerCase()) {\n throw new Error(\n \"Remnic omp extension cannot pre-bundle: the extension directory \" +\n `(${wrapperDir}) and the @remnic/plugin-pi install (${extensionModulePath}) ` +\n \"are on different drives, so no relative import specifier can be generated \" +\n \"for `bun build` (and `bun build` cannot resolve a `file://` specifier). \" +\n \"Move the omp agent home and the Remnic install onto the same drive.\",\n );\n }\n let rel = pathApi.relative(wrapperDir, extensionModulePath);\n rel = rel.split(pathApi.sep).join(\"/\");\n return rel.startsWith(\".\") ? rel : `./${rel}`;\n}\n\nfunction renderWrapper(\n extensionModulePath: string,\n configPath: string,\n wrapperDir?: string,\n): string {\n // omp pre-bundles this entry with `bun build`, whose bundler cannot resolve\n // `file://` specifiers — it exits with \"Could not resolve: file://...\" on\n // Bun 1.2–1.3 (verified). When the wrapper will be bun-built, emit a relative\n // specifier resolved against the wrapper's directory; bun, tsx, and Node ESM\n // all resolve relative specifiers. For tsx-loaded wrappers (pi) the file://\n // URL is retained.\n let importSpecifier: string;\n if (wrapperDir) {\n importSpecifier = resolveOmpWrapperImportSpecifier(extensionModulePath, wrapperDir);\n } else {\n importSpecifier = pathToFileURL(extensionModulePath).href;\n }\n return [\n `import { createRemnicPiExtension } from ${JSON.stringify(importSpecifier)};`,\n \"\",\n `export default createRemnicPiExtension({ configPath: ${JSON.stringify(configPath)} });`,\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Generates the self-healing `loader.js` that omp loads via the package\n * manifest's `omp.extensions` entry. It mtime-compares the pre-bundled\n * `dist-bundle/index.js` against `index.ts` and the underlying @remnic/plugin-pi\n * dist, rebuilds via `bun build` when stale (e.g. after an `npm update`), then\n * imports the self-contained bundle so omp's embedded runtime never resolves\n * bare npm specifiers at load time.\n */\nfunction renderOmpLoader(pluginPiDistPath: string, bunBin: string): string {\n return [\n \"// Auto-generated by Remnic's OmpMemoryExtensionPublisher.\",\n \"// omp's embedded runtime cannot resolve bare npm specifiers from this\",\n \"// extension's node_modules, so we pre-bundle with `bun build` and import\",\n \"// the self-contained bundle here. Rebuilt automatically when index.ts or\",\n \"// the underlying @remnic/plugin-pi dist changes.\",\n \"\",\n 'import { existsSync, renameSync, rmSync, statSync } from \"node:fs\";',\n 'import { spawnSync } from \"node:child_process\";',\n 'import { dirname, join } from \"node:path\";',\n 'import { fileURLToPath, pathToFileURL } from \"node:url\";',\n \"\",\n 'const here = dirname(fileURLToPath(import.meta.url));',\n 'const bundleDir = join(here, \"dist-bundle\");',\n 'const bundleEntry = join(bundleDir, \"index.js\");',\n 'const sourceEntry = join(here, \"index.ts\");',\n `const pluginPiEntry = ${JSON.stringify(pluginPiDistPath)};`,\n // Reuse the bun path resolved at install time (REMNIC_OMP_BUN_BIN, PATH,\n // or a common absolute location). Fall back to \"bun\" on PATH if the\n // resolved path no longer exists (e.g. the extension tree was moved), so\n // self-healing still works when bun is reachable only via PATH.\n `const resolvedBunBin = ${JSON.stringify(bunBin)};`,\n 'const bunForRebuild = resolvedBunBin && existsSync(resolvedBunBin) ? resolvedBunBin : \"bun\";',\n \"\",\n \"function bundleIsStale() {\",\n \" if (!existsSync(bundleEntry)) return true;\",\n \" const bundleMtime = statSync(bundleEntry).mtimeMs;\",\n \" if (existsSync(sourceEntry) && bundleMtime < statSync(sourceEntry).mtimeMs) return true;\",\n \" if (pluginPiEntry && existsSync(pluginPiEntry) && bundleMtime < statSync(pluginPiEntry).mtimeMs) return true;\",\n \" return false;\",\n \"}\",\n \"\",\n \"function rebuildBundle() {\",\n \" // Build to a temp dir and swap, mirroring the install-time build, so a\",\n \" // failed self-heal rebuild never corrupts the working bundle.\",\n ' var tmp = join(here, \".dist-bundle.tmp-\" + process.pid + \"-\" + Date.now());',\n \" var result = spawnSync(bunForRebuild, [\",\n ' \"build\",',\n \" sourceEntry,\",\n ' \"--target=bun\",',\n ' \"--outdir=\" + tmp',\n \" ], {\",\n \" cwd: here,\",\n ' stdio: \"inherit\",',\n \" });\",\n \" if (result.status !== 0 || result.error) {\",\n \" try { rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\",\n \" throw new Error(\",\n ' \"Remnic omp extension: bundle is stale or missing and could not be rebuilt. \" +',\n ' \"Install bun (https://bun.sh), then run \" +',\n ' \"`bun build index.ts --target=bun --outdir=dist-bundle` inside \" + here',\n \" );\",\n \" }\",\n \" var backup = null;\",\n \" try {\",\n \" if (existsSync(bundleDir)) {\",\n ' backup = join(here, \".dist-bundle.bak-\" + process.pid + \"-\" + Date.now());',\n \" renameSync(bundleDir, backup);\",\n \" }\",\n \" renameSync(tmp, bundleDir);\",\n \" if (backup) { try { rmSync(backup, { recursive: true, force: true }); } catch (e) {} }\",\n \" } catch (err) {\",\n \" try { rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\",\n \" if (backup && existsSync(backup) && !existsSync(bundleDir)) { try { renameSync(backup, bundleDir); } catch (e) {} }\",\n \" throw new Error(\",\n ' \"Remnic omp extension: failed to finalize rebuilt bundle - \" + (err && err.message ? err.message : err)',\n \" );\",\n \" }\",\n \"}\",\n\n \"\",\n \"if (bundleIsStale()) rebuildBundle();\",\n \"\",\n \"// Cache-bust so a freshly rebuilt bundle is loaded instead of a stale cached copy.\",\n 'const bundle = await import(pathToFileURL(bundleEntry).href + \"?t=\" + Date.now());',\n \"export default bundle.default;\",\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Generates the `package.json` that tells omp to load `loader.js` (not\n * auto-discover `index.ts`) and re-bundles after `npm install` via postinstall.\n */\nfunction renderOmpPackageJson(): string {\n // Postinstall re-bundles after `npm install` (e.g. a plugin-pi upgrade moved\n // the dist mtime past the bundle). It delegates to postinstall-bundle.cjs — a\n // Node-only helper — so npm's default cmd.exe shell on Windows runs it just as\n // well as POSIX bash. The helper embeds the resolved bun path with a PATH\n // fallback and swaps the bundle atomically.\n const manifest = {\n name: \"remnic-omp-extension\",\n version: \"0.0.0\",\n private: true,\n type: \"module\",\n omp: { extensions: [\"./loader.js\"] },\n // Legacy key so older omp builds that only read `pi.extensions` also\n // resolve loader.js instead of falling through to index.ts.\n pi: { extensions: [\"./loader.js\"] },\n scripts: { postinstall: \"node postinstall-bundle.cjs\" },\n };\n return `${JSON.stringify(manifest, null, 2)}\\n`;\n}\n\n/**\n * Generates the cross-platform `postinstall-bundle.cjs` helper. Node-only, so\n * npm's default cmd.exe shell on Windows re-bundles after `npm install` just as\n * well as POSIX bash. Embeds the bun path resolved at install time with a PATH\n * fallback and writes the new bundle via a temp-dir swap so a failed rebuild\n * never corrupts the working bundle. The emitted script uses string\n * concatenation (no template literals) so it stays parseable everywhere.\n */\nfunction renderOmpPostinstall(bunBin: string): string {\n // Single template literal: the emitted .cjs uses string concatenation (no\n // template literals of its own), so this body has no backticks and the one\n // ${JSON.stringify(bunBin)} interpolation is unambiguous.\n return `// Auto-generated by Remnic's OmpMemoryExtensionPublisher.\n// Re-bundles the omp extension after npm install (e.g. a plugin-pi upgrade)\n// using the bun path resolved at install time, with a PATH fallback. Node-only\n// so it runs under npm's default cmd.exe shell on Windows as well as POSIX bash.\n\"use strict\";\nvar fs = require(\"node:fs\");\nvar cp = require(\"node:child_process\");\nvar path = require(\"node:path\");\n\nvar RESOLVED_BUN = ${JSON.stringify(bunBin)};\nvar dir = __dirname;\nvar entry = path.join(dir, \"index.ts\");\nvar out = path.join(dir, \"dist-bundle\");\n\nfunction pickBun() {\n var env = process.env.REMNIC_OMP_BUN_BIN;\n if (env && fs.existsSync(env)) return env;\n if (RESOLVED_BUN && fs.existsSync(RESOLVED_BUN)) return RESOLVED_BUN;\n return \"bun\";\n}\n\nfunction rebuild() {\n var bun = pickBun();\n var tmp = path.join(dir, \".dist-bundle.tmp-\" + process.pid + \"-\" + Date.now());\n var r = cp.spawnSync(bun, [\"build\", entry, \"--target=bun\", \"--outdir=\" + tmp], { cwd: dir, stdio: \"inherit\" });\n if (r.error || r.status !== 0) {\n try { fs.rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\n throw new Error(\"Remnic omp extension: postinstall bun build failed (bun=\" + bun + \"). Run bun build index.ts --target=bun --outdir=dist-bundle manually inside \" + dir);\n }\n var backup = null;\n try {\n if (fs.existsSync(out)) {\n backup = path.join(dir, \".dist-bundle.bak-\" + process.pid + \"-\" + Date.now());\n fs.renameSync(out, backup);\n }\n fs.renameSync(tmp, out);\n if (backup) { try { fs.rmSync(backup, { recursive: true, force: true }); } catch (e) {} }\n } catch (err) {\n try { fs.rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\n if (backup && fs.existsSync(backup) && !fs.existsSync(out)) { try { fs.renameSync(backup, out); } catch (e) {} }\n throw err;\n }\n}\n\ntry {\n rebuild();\n} catch (err) {\n console.error(err && err.message ? err.message : err);\n process.exit(1);\n}\n`;\n}\n\n/**\n * True when `candidate` is a regular file that the current process can\n * execute. Used by every `bun`-binary candidate selection site so a stale,\n * non-executable file named `bun` (or `bun.exe`) cannot win over a later\n * working binary — matching `which(1)` and the `spawnSync(\"bun\", [\"--version\"])`\n * version probe, which both skip non-executable files. On Windows\n * `fs.accessSync(X_OK)` verifies read access, which holds for real `.exe`\n * files, so the check is a harmless no-op there.\n */\nfunction isExecutableFile(candidate: string): boolean {\n try {\n const stat = fs.statSync(candidate);\n if (!stat.isFile()) return false;\n fs.accessSync(candidate, fs.constants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Walks `PATH` the way a shell does and returns the first `bun` executable it\n * finds, as a realpath-resolved absolute path (or null when nothing on PATH\n * is an executable `bun`). Used so the install-time PATH probe can embed an\n * absolute bun path in the generated loader/postinstall instead of the bare\n * string `\"bun\"`, which would break self-heal rebuilds under a stripped\n * runtime PATH (GUI/service launches). Mirrors `which(1)`; no dependency.\n */\nexport function resolveBunOnPath(): string | null {\n const pathVar = process.env.PATH ?? process.env.Path ?? process.env.path ?? \"\";\n const separator = process.platform === \"win32\" ? \";\" : \":\";\n const candidateNames =\n process.platform === \"win32\" ? [\"bun.exe\", \"bun\"] : [\"bun\"];\n for (const dir of pathVar.split(separator)) {\n if (!dir) continue;\n for (const name of candidateNames) {\n const candidate = path.isAbsolute(dir)\n ? path.join(dir, name)\n : path.resolve(dir, name);\n if (isExecutableFile(candidate)) {\n return fs.realpathSync(candidate);\n }\n }\n }\n return null;\n}\n\n/**\n * Resolves the `bun` binary for the install-time pre-bundle. Honours\n * `REMNIC_OMP_BUN_BIN` (test/override seam), then PATH, then common locations.\n * Returns null when bun is unavailable so the caller can fail with guidance.\n */\nexport function resolveBunBinary(): string | null {\n const override = process.env.REMNIC_OMP_BUN_BIN;\n if (override !== undefined) {\n return fs.existsSync(override) ? override : null;\n }\n\n const pathProbe = spawnSync(\"bun\", [\"--version\"], { encoding: \"utf-8\" });\n if (!pathProbe.error && pathProbe.status === 0) {\n // Resolve the PATH-found bun to an absolute executable so the embedded\n // loader/postinstall don't depend on omp's runtime PATH — GUI/service\n // launches commonly inherit a stripped PATH, which would make a bare\n // \"bun\" self-heal spawn fail even though install found a working binary.\n // Fall back to \"bun\" only if the PATH walk can't locate it (e.g. a shell\n // function/alias that isn't an actual file on PATH).\n return resolveBunOnPath() ?? \"bun\";\n }\n\n // Mirror omp's path helpers, which resolve the agent home as\n // HOME ?? USERPROFILE ?? os.homedir(). Relying on HOME alone breaks the\n // ~/.bun/bin/bun fallback on Windows installs where HOME is unset.\n const home = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();\n // The official Bun installer writes ~/.bun/bin/bun on POSIX and\n // ~/.bun/bin/bun.exe on Windows. Select the first candidate that is an\n // executable regular file (not merely one that exists) so a stale,\n // non-executable ~/.bun/bin/bun cannot win over a later working binary\n // (e.g. /usr/local/bin/bun or /opt/homebrew/bin/bun) — same `which(1)`\n // semantics as the PATH walk above.\n const candidates = [\n path.join(home ?? \"\", \".bun\", \"bin\", \"bun\"),\n path.join(home ?? \"\", \".bun\", \"bin\", \"bun.exe\"),\n \"/usr/local/bin/bun\",\n \"/opt/homebrew/bin/bun\",\n ];\n for (const candidate of candidates) {\n if (isExecutableFile(candidate)) return candidate;\n }\n return null;\n}\n\nfunction atomicWriteFile(filePath: string, content: string, mode: number): void {\n rejectSymlinkPath(filePath);\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n try {\n fs.writeFileSync(tmpPath, content, { encoding: \"utf-8\", mode });\n fs.renameSync(tmpPath, filePath);\n try {\n fs.chmodSync(filePath, mode);\n } catch {\n // Best effort for platforms that do not support chmod.\n }\n } catch (err) {\n try {\n if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);\n } catch {\n // Best-effort cleanup only.\n }\n throw err;\n }\n}\n\nfunction snapshotFiles(paths: string[]): FileSnapshot[] {\n return paths.map((filePath) => {\n if (!fs.existsSync(filePath)) return { path: filePath, existed: false };\n const stat = fs.lstatSync(filePath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${filePath}`);\n }\n if (!stat.isFile()) return { path: filePath, existed: false };\n return {\n path: filePath,\n existed: true,\n content: fs.readFileSync(filePath),\n mode: stat.mode & 0o777,\n };\n });\n}\n\nfunction restorePublishSnapshot(extensionRoot: string, rootExisted: boolean, snapshots: FileSnapshot[]): void {\n if (!rootExisted && !canCleanNewExtensionRoot(extensionRoot)) return;\n\n for (const snapshot of snapshots) {\n restoreOwnedFile(snapshot);\n }\n\n if (!rootExisted) {\n removeEmptyDirectory(extensionRoot);\n }\n}\n\n/**\n * Restores a single owned file to its pre-publish state, atomically.\n *\n * Two cases:\n *\n * - The file did NOT exist before publish (publish created it): remove it to\n * undo the publish. {@link assertSafeExistingPath} re-checks it is not a\n * symlink swapped in after the snapshot; `rmSync` removes a symlink itself\n * rather than following it, but refusing surfaces tampering loudly.\n *\n * - The file DID exist before publish: restore its prior content using\n * \"write-new-before-delete-old\" (rules 42/54). We write the prior content to\n * a temp path in the same directory, then {@link fs.renameSync} it into\n * place. The live file is never truncated, so a mid-restore failure (disk\n * full, EACCES, …) leaves the current on-disk content intact rather than\n * half-written — the restore either fully lands or does nothing. The temp\n * path uses the `.tmp-<pid>-<ts>` suffix tracked by\n * {@link EXTENSION_OWNED_TEMP_FILE_SUFFIX}, so any lingering temp is swept\n * by unpublish. The final `renameSync` does NOT follow a symlink even if one\n * was swapped into the snapshot path after the snapshot (TOCTOU\n * defense-in-depth): `rename(2)` replaces the symlink itself, so no write\n * ever reaches an arbitrary target. We still re-check for a symlink right\n * before the rename so the rollback surfaces tampering instead of silently\n * replacing it.\n */\nfunction restoreOwnedFile(snapshot: FileSnapshot): void {\n if (!snapshot.existed) {\n assertSafeExistingPath(snapshot.path);\n fs.rmSync(snapshot.path, { force: true });\n return;\n }\n\n fs.mkdirSync(path.dirname(snapshot.path), { recursive: true });\n const tmpPath = `${snapshot.path}.tmp-${process.pid}-${Date.now()}`;\n try {\n fs.writeFileSync(tmpPath, snapshot.content ?? Buffer.alloc(0), {\n mode: snapshot.mode ?? 0o644,\n });\n if (snapshot.mode !== undefined) {\n try {\n fs.chmodSync(tmpPath, snapshot.mode);\n } catch {\n // Best effort for platforms that do not support chmod.\n }\n }\n rejectSymlinkPath(snapshot.path);\n fs.renameSync(tmpPath, snapshot.path);\n } catch (err) {\n try {\n if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);\n } catch {\n // Best-effort cleanup only.\n }\n throw err;\n }\n}\n\nfunction snapshotDirs(paths: string[]): DirSnapshot[] {\n return paths.map((dirPath) => {\n let existed = false;\n try {\n const stat = fs.lstatSync(dirPath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${dirPath}`);\n }\n existed = stat.isDirectory();\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") {\n existed = false;\n } else {\n throw err;\n }\n }\n return { path: dirPath, existed };\n });\n}\n\nfunction restoreDirSnapshots(snapshots: DirSnapshot[]): void {\n for (const snapshot of snapshots) {\n if (snapshot.existed) continue;\n try {\n fs.rmSync(snapshot.path, { recursive: true, force: true });\n } catch {\n // best-effort — the loader self-heals at runtime if the dir lingers\n }\n }\n}\n\n\nfunction canCleanNewExtensionRoot(extensionRoot: string): boolean {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(extensionRoot);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return false;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${extensionRoot}`);\n }\n return stat.isDirectory();\n}\n\nfunction removeEmptyDirectory(dirPath: string): void {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(dirPath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${dirPath}`);\n }\n if (!stat.isDirectory()) return;\n if (fs.readdirSync(dirPath).length > 0) return;\n fs.rmdirSync(dirPath);\n}\n\nfunction removableOwnedExtensionFiles(filePaths: string[]): string[] {\n const removableFiles: string[] = [];\n for (const filePath of filePaths) {\n const stat = statOwnedExtensionPath(filePath);\n if (stat === null) continue;\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${filePath}`);\n }\n if (stat.isFile()) removableFiles.push(filePath);\n }\n return removableFiles;\n}\n\nfunction statOwnedExtensionPath(filePath: string): fs.Stats | null {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(filePath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return null;\n throw err;\n }\n return stat;\n}\n\nfunction mkdirExtensionRoot(extensionRoot: string, agentHome: string): void {\n const extensionsDir = path.join(path.resolve(agentHome), \"extensions\");\n assertSafeExtensionRoot(extensionRoot, agentHome);\n fs.mkdirSync(extensionsDir, { recursive: true });\n rejectSymlinkPath(extensionsDir);\n fs.mkdirSync(extensionRoot, { recursive: true });\n rejectSymlinkPath(extensionRoot);\n}\n\nfunction assertSafeExtensionRoot(extensionRoot: string, agentHome: string): void {\n const resolvedAgentHome = path.resolve(agentHome);\n const expected = path.join(resolvedAgentHome, \"extensions\", \"remnic\");\n if (path.resolve(extensionRoot) !== path.resolve(expected)) {\n throw new Error(`Extension root is outside the configured extensions directory: ${extensionRoot}`);\n }\n const extensionsDir = path.join(resolvedAgentHome, \"extensions\");\n assertPathContained(resolvedAgentHome, extensionsDir);\n assertPathContained(extensionsDir, extensionRoot);\n rejectSymlinkPath(resolvedAgentHome);\n if (fs.existsSync(extensionsDir)) rejectSymlinkPath(extensionsDir);\n if (fs.existsSync(extensionRoot)) rejectSymlinkPath(extensionRoot);\n}\n\nfunction assertSafeExistingPath(filePath: string): void {\n if (fs.existsSync(filePath)) rejectSymlinkPath(filePath);\n}\n\nfunction rejectSymlinkPath(filePath: string): void {\n if (!fs.existsSync(filePath)) return;\n const stat = fs.lstatSync(filePath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${filePath}`);\n }\n}\n\nfunction assertPathContained(root: string, candidate: string): void {\n const rootResolved = path.resolve(root);\n const candidateResolved = path.resolve(candidate);\n const relative = path.relative(rootResolved, candidateResolved);\n if (relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative))) return;\n throw new Error(`Extension path escapes allowed root: ${candidate}`);\n}\n\nfunction readPriorConfig(configPath: string): Record<string, unknown> {\n if (!fs.existsSync(configPath)) return {};\n try {\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n throw new Error(\"expected a JSON object\");\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load existing Remnic Pi config at ${configPath}: ${reason}`);\n }\n}\n\nfunction snapshotTokenEntry(connectorId: string): TokenEntry | null {\n const entry = loadTokenStore().tokens.find((candidate) => candidate.connector === connectorId);\n return cloneTokenEntry(entry ?? null);\n}\n\nfunction cloneTokenEntry(entry: TokenEntry | null): TokenEntry | null {\n return entry ? { ...entry } : null;\n}\n\nfunction restoreTokenEntry(priorEntry: TokenEntry | null, connectorId: string): void {\n const store = loadTokenStore();\n store.tokens = store.tokens.filter((entry) => entry.connector !== connectorId);\n if (priorEntry) store.tokens.push(priorEntry);\n saveTokenStore(store);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/publisher.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport os from \"node:os\";\n\nimport {\n type MemoryExtensionPublisher,\n type PublishContext,\n type PublishResult,\n type PublisherCapabilities,\n type TokenEntry,\n getConnectorToken,\n loadTokenStore,\n saveTokenStore,\n} from \"@remnic/core\";\n\nimport {\n resolveOmpAgentHome,\n resolveOmpConfigRoot,\n resolveOmpExtensionRoot,\n resolvePiAgentHome,\n resolvePiExtensionRoot,\n} from \"./paths.js\";\n\nconst DEFAULT_DAEMON_PORT = 4318;\nconst BASE_OWNED_FILES = [\"remnic.config.json\", \"index.ts\", \"README.md\"] as const;\nconst EXTENSION_OWNED_TEMP_FILE_SUFFIX = /\\.tmp-\\d+-\\d+$/u;\n\ntype FileSnapshot = {\n path: string;\n existed: boolean;\n content?: Buffer;\n mode?: number;\n};\n\ntype DirSnapshot = {\n path: string;\n existed: boolean;\n};\n\n/**\n * Host-specific parameters for a Pi-family memory extension publisher.\n *\n * The Remnic runtime extension is host-neutral (it only uses Pi's extension\n * hooks, which omp preserves as a superset), so the only things that vary\n * between hosts are *where* the extension is installed, *which* connector\n * token it uses, and *how* it is labelled. Everything else — atomic writes,\n * rollback, symlink guards, config merge — is shared.\n */\nexport interface HostPublisherDescriptor {\n readonly hostId: string;\n readonly connectorId: string;\n readonly displayName: string;\n readonly tokenGenerateHint: string;\n resolveAgentHome(env: NodeJS.ProcessEnv): string;\n resolveExtensionRoot(env: NodeJS.ProcessEnv): string;\n /**\n * Optional: every agent home `unpublish` should sweep for a stale extension,\n * beyond the one resolved from the current env. Hosts with env-sensitive\n * install locations (e.g. omp profiles) provide this so `remnic connectors\n * remove` cleans up even when the remove-time env differs from install time.\n */\n listRemovalAgentHomes?(env: NodeJS.ProcessEnv): string[];\n}\n\nconst PI_HOST: HostPublisherDescriptor = {\n hostId: \"pi\",\n connectorId: \"pi\",\n displayName: \"Pi Coding Agent\",\n tokenGenerateHint: \"remnic token generate pi\",\n resolveAgentHome: resolvePiAgentHome,\n resolveExtensionRoot: resolvePiExtensionRoot,\n};\n\nconst OMP_HOST: HostPublisherDescriptor = {\n hostId: \"omp\",\n connectorId: \"omp\",\n displayName: \"Oh My Pi (omp)\",\n tokenGenerateHint: \"remnic token generate omp\",\n resolveAgentHome: resolveOmpAgentHome,\n resolveExtensionRoot: resolveOmpExtensionRoot,\n listRemovalAgentHomes: ompRemovalAgentHomes,\n};\n\n/**\n * Every omp agent home a stale extension might live under, so `unpublish` cleans\n * up regardless of the profile/env active at remove time: the env-resolved home,\n * the base `<configRoot>/agent`, an explicit `PI_CODING_AGENT_DIR`, and every\n * existing `<configRoot>/profiles/<name>/agent`. Symlinked profile dirs are\n * skipped defensively.\n */\nfunction ompRemovalAgentHomes(env: NodeJS.ProcessEnv): string[] {\n const homes = new Set<string>([resolveOmpAgentHome(env)]);\n const configRoot = resolveOmpConfigRoot(env);\n homes.add(path.join(configRoot, \"agent\"));\n\n const explicit = env.PI_CODING_AGENT_DIR?.trim();\n if (explicit) homes.add(path.resolve(explicit));\n\n const profilesDir = path.join(configRoot, \"profiles\");\n let entries: fs.Dirent[] = [];\n try {\n entries = fs.readdirSync(profilesDir, { withFileTypes: true });\n } catch {\n entries = [];\n }\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.isSymbolicLink()) {\n homes.add(path.join(profilesDir, entry.name, \"agent\"));\n }\n }\n return [...homes];\n}\n\n/**\n * Shared publisher for Pi-family hosts. Concrete hosts (Pi, omp) subclass this\n * with a {@link HostPublisherDescriptor}; the install/rollback machinery is\n * identical across hosts.\n */\nexport class HostMemoryExtensionPublisher implements MemoryExtensionPublisher {\n static readonly capabilities: PublisherCapabilities = {\n // Real publisher: writes host config + wrapper + readme, just no\n // instructions.md/skills/citation/read-path-template artefacts. The\n // explicit flag prevents the parity gate from mis-inferring \"all flags\n // false ⇒ stub\" for this host (#1518).\n isStub: false,\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n protected constructor(private readonly host: HostPublisherDescriptor) {}\n\n /**\n * File basenames this publisher owns inside the extension root. The shared\n * set is config + wrapper + readme; subclasses add host-specific files\n * (e.g. omp's pre-bundle loader + package manifest). Used for snapshot,\n * atomic-write rollback, and unpublish cleanup.\n */\n protected get ownedFileNames(): readonly string[] {\n return BASE_OWNED_FILES;\n }\n\n /**\n * Directory names this publisher owns inside the extension root (build\n * outputs). Recursively removed on unpublish and on publish rollback when\n * newly created.\n */\n protected get ownedDirNames(): readonly string[] {\n return [];\n }\n\n /**\n * Whether the generated wrapper must use a bun-buildable import specifier\n * (relative path) instead of a file:// URL. omp pre-bundles the wrapper with\n * `bun build`, which cannot resolve file:// specifiers; pi loads the wrapper\n * directly via tsx and keeps the file:// URL.\n */\n protected get usesBundledWrapper(): boolean {\n return false;\n }\n\n /**\n * Hook for subclasses to write host-specific files and run install-time\n * build steps after the shared config/wrapper/readme are written. Runs\n * inside the publish try-block: a throw triggers full rollback.\n */\n protected finalizePublish(\n _ctx: PublishContext,\n _extensionRoot: string,\n _paths: { configPath: string; wrapperPath: string; pluginPiDistPath: string },\n ): void {\n // No-op by default; subclasses override.\n }\n\n get hostId(): string {\n return this.host.hostId;\n }\n\n async resolveExtensionRoot(env?: NodeJS.ProcessEnv): Promise<string> {\n return this.host.resolveExtensionRoot(env ?? process.env);\n }\n\n async isHostAvailable(): Promise<boolean> {\n // Pi-family agents auto-discover extensions from their agent extensions\n // directory. The directory can be created before the agent has been\n // launched, so availability should not block first-time installation.\n return true;\n }\n\n async renderInstructions(ctx: PublishContext): Promise<string> {\n const namespace = ctx.config.namespace ?? \"default\";\n const daemonUrl = resolveDaemonUrl(ctx);\n return [\n `# Remnic for ${this.host.displayName}`,\n \"\",\n `Remnic provides memory, retrieval, observation, MCP tools, and long-context compaction coordination for ${this.host.displayName}.`,\n \"\",\n \"## Installed Capabilities\",\n \"\",\n \"- Recall relevant Remnic context in the `before_agent_start` hook via system prompt injection.\",\n '- Observe user, assistant, and tool messages with `sourceFormat: \"pi\"`.',\n \"- Coordinate `session_before_compact` with Remnic LCM flush and checkpoint recording.\",\n \"- Register Remnic MCP tools as host tools when daemon authentication is configured.\",\n \"- Persist lightweight dedupe state in custom entries via `appendEntry`.\",\n \"\",\n \"## Runtime\",\n \"\",\n `- Remnic daemon: \\`${daemonUrl}\\``,\n `- Namespace: \\`${namespace}\\``,\n `- Memory directory: \\`${ctx.config.memoryDir}\\``,\n \"\",\n \"The private `remnic.config.json` file stores the daemon URL, namespace, and connector auth token with owner-only permissions.\",\n ].join(\"\\n\");\n }\n\n async publish(ctx: PublishContext): Promise<PublishResult> {\n const extensionRoot = await this.resolveExtensionRoot();\n const agentHome = this.host.resolveAgentHome(process.env);\n assertSafeExtensionRoot(extensionRoot, agentHome);\n const filesWritten: string[] = [];\n const skipped: string[] = [];\n\n ctx.log.info(`Publishing ${this.host.displayName} memory extension to ${extensionRoot}`);\n\n const ownedFilePaths = this.ownedFileNames.map((fileName) => path.join(extensionRoot, fileName));\n const configPath = ownedFilePaths[0];\n const wrapperPath = ownedFilePaths[1];\n const readmePath = ownedFilePaths[2];\n const pluginPiDistPath = resolveExtensionModulePath();\n const rootExisted = fs.existsSync(extensionRoot);\n const fileSnapshots = snapshotFiles(ownedFilePaths);\n const dirSnapshots = snapshotDirs(\n this.ownedDirNames.map((dirName) => path.join(extensionRoot, dirName)),\n );\n const priorTokenEntry =\n ctx.rollbackTokenEntry === undefined\n ? snapshotTokenEntry(this.host.connectorId)\n : cloneTokenEntry(ctx.rollbackTokenEntry);\n\n const token = getConnectorToken(this.host.connectorId);\n if (!token) {\n skipped.push(\n `auth token unavailable; run \\`${this.host.tokenGenerateHint}\\` and reinstall the connector`,\n );\n }\n\n try {\n const priorConfig = readPriorConfig(configPath);\n const config: Record<string, unknown> = {\n recallMode: \"auto\",\n recallTopK: 8,\n recallBudgetChars: 12000,\n recallEnabled: true,\n observeEnabled: true,\n observeSkipExtraction: false,\n compactionEnabled: true,\n mcpToolsEnabled: true,\n statusEnabled: true,\n requestTimeoutMs: 60000,\n startupRequestTimeoutMs: 1000,\n ...priorConfig,\n remnicDaemonUrl: resolveDaemonUrl(ctx),\n };\n if (token) {\n config.authToken = token;\n }\n if (ctx.config.namespace) {\n config.namespace = ctx.config.namespace;\n }\n\n mkdirExtensionRoot(extensionRoot, agentHome);\n\n atomicWriteFile(configPath, `${JSON.stringify(config, null, 2)}\\n`, 0o600);\n filesWritten.push(configPath);\n\n atomicWriteFile(\n wrapperPath,\n renderWrapper(\n pluginPiDistPath,\n configPath,\n this.usesBundledWrapper ? extensionRoot : undefined,\n ),\n 0o644,\n );\n filesWritten.push(wrapperPath);\n\n atomicWriteFile(readmePath, `${await this.renderInstructions(ctx)}\\n`, 0o644);\n filesWritten.push(readmePath);\n\n this.finalizePublish(ctx, extensionRoot, { configPath, wrapperPath, pluginPiDistPath });\n for (let i = BASE_OWNED_FILES.length; i < ownedFilePaths.length; i++) {\n filesWritten.push(ownedFilePaths[i]);\n }\n } catch (err) {\n try {\n // Remove newly created owned dirs (e.g. dist-bundle) BEFORE\n // restorePublishSnapshot's removeEmptyDirectory check, otherwise a\n // first-time publish that created dist-bundle would leave an empty\n // extension root behind on rollback.\n restoreDirSnapshots(dirSnapshots);\n restorePublishSnapshot(extensionRoot, rootExisted, fileSnapshots);\n } catch (restoreErr) {\n ctx.log.warn(\n `${this.host.displayName} extension rollback failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`,\n );\n }\n // A failed omp pre-bundle (bun missing or `bun build` failing) is\n // recoverable: the runtime loader self-heals dist-bundle on first load,\n // and the connector token is already committed by the CLI. Rolling it\n // back here would leave the connector registered with no credential and\n // block a non-`--force` reinstall (AGENTS.md #14 — don't destroy\n // committed state before the new state is confirmed). File/dir rollback\n // above still runs, so a failed first-time publish still cleans its root.\n if (!(err instanceof OmpPreBundleError)) {\n try {\n restoreTokenEntry(priorTokenEntry, this.host.connectorId);\n } catch (tokenErr) {\n ctx.log.warn(\n `${this.host.displayName} connector token rollback failed: ${tokenErr instanceof Error ? tokenErr.message : String(tokenErr)}`,\n );\n }\n }\n throw err;\n }\n\n return {\n hostId: this.host.hostId,\n extensionRoot,\n filesWritten,\n skipped,\n };\n }\n\n async unpublish(): Promise<void> {\n const agentHomes = this.host.listRemovalAgentHomes\n ? this.host.listRemovalAgentHomes(process.env)\n : [this.host.resolveAgentHome(process.env)];\n\n const ownedFileNames = this.ownedFileNames;\n const ownedDirNames = this.ownedDirNames;\n const seen = new Set<string>();\n for (const agentHome of agentHomes) {\n const extensionRoot = path.join(path.resolve(agentHome), \"extensions\", \"remnic\");\n if (seen.has(extensionRoot)) continue;\n seen.add(extensionRoot);\n if (!fs.existsSync(extensionRoot)) continue;\n\n assertSafeExtensionRoot(extensionRoot, agentHome);\n const removableFiles = removableOwnedExtensionFiles(\n extensionOwnedUnpublishPaths(extensionRoot, ownedFileNames),\n );\n for (const filePath of removableFiles) {\n fs.rmSync(filePath, { force: true });\n }\n for (const dirName of ownedDirNames) {\n const dirPath = path.join(extensionRoot, dirName);\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(dirPath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") continue;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${dirPath}`);\n }\n if (stat.isDirectory()) {\n fs.rmSync(dirPath, { recursive: true, force: true });\n }\n }\n removeEmptyDirectory(extensionRoot);\n }\n }\n}\n\n/** Publisher for upstream Pi (`~/.pi/agent/extensions/remnic`). */\nexport class PiMemoryExtensionPublisher extends HostMemoryExtensionPublisher {\n constructor() {\n super(PI_HOST);\n }\n}\n\n/**\n * Marks a failure originating from the omp pre-bundle step (bun missing, or the\n * `bun build` itself failing). {@link HostMemoryExtensionPublisher.publish}\n * catches this and rolls back the written files but SKIPS the connector-token\n * rollback: the pre-bundle runs after the install (config + wrapper + token) is\n * already committed, and the runtime loader self-heals `dist-bundle` on first\n * load, so destroying the just-generated token would leave the connector\n * registered with no credential and block a non-`--force` reinstall\n * (AGENTS.md #14 — don't destroy committed state before the new state is\n * confirmed). The message is preserved verbatim so existing `/requires \\`bun\\`/`\n * and `/bun build failed/` assertions still match.\n */\nclass OmpPreBundleError extends Error {}\n\n/** Publisher for Oh My Pi / omp (`~/.omp/agent/extensions/remnic`). */\nexport class OmpMemoryExtensionPublisher extends HostMemoryExtensionPublisher {\n constructor() {\n super(OMP_HOST);\n }\n\n protected get ownedFileNames(): readonly string[] {\n return [...BASE_OWNED_FILES, \"loader.js\", \"package.json\", \"postinstall-bundle.cjs\"];\n }\n\n protected get ownedDirNames(): readonly string[] {\n return [\"dist-bundle\"];\n }\n\n // omp pre-bundles index.ts with `bun build`; the wrapper must use a relative\n // import specifier (bun's bundler cannot resolve file:// URLs).\n protected get usesBundledWrapper(): boolean {\n return true;\n }\n\n protected finalizePublish(\n ctx: PublishContext,\n extensionRoot: string,\n paths: { configPath: string; wrapperPath: string; pluginPiDistPath: string },\n ): void {\n // Resolve once so the install-time build and the generated loader share the\n // same bun path — a loader that hardcodes \"bun\" cannot self-heal when bun\n // is reachable only via REMNIC_OMP_BUN_BIN or a common absolute install\n // path that is not on omp's PATH at runtime.\n const bunBin = resolveBunBinary();\n if (!bunBin) {\n // OmpPreBundleError so publish() keeps the connector token intact (see\n // the class doc); the runtime loader self-heals the bundle once bun is\n // installed.\n throw new OmpPreBundleError(\n \"Remnic omp extension requires `bun` to pre-bundle the extension: omp's embedded \" +\n \"runtime cannot resolve bare npm specifiers from the extension's node_modules. \" +\n \"Install bun from https://bun.sh, then re-run `remnic connectors install omp`.\",\n );\n }\n\n const loaderPath = path.join(extensionRoot, \"loader.js\");\n const packageJsonPath = path.join(extensionRoot, \"package.json\");\n\n const postinstallPath = path.join(extensionRoot, \"postinstall-bundle.cjs\");\n\n atomicWriteFile(loaderPath, renderOmpLoader(paths.pluginPiDistPath, bunBin), 0o644);\n // Cross-platform postinstall helper (Node-only) so npm's default cmd.exe\n // shell on Windows re-bundles after `npm install`; the POSIX one-liner it\n // replaces only ran under bash.\n atomicWriteFile(postinstallPath, renderOmpPostinstall(bunBin), 0o644);\n atomicWriteFile(packageJsonPath, renderOmpPackageJson(), 0o644);\n\n try {\n this.runBundleBuild(ctx, extensionRoot, bunBin);\n } catch (err) {\n // OmpPreBundleError so publish() keeps the connector token intact (see\n // the class doc); the runtime loader self-heals the bundle on next load.\n const message = err instanceof Error ? err.message : String(err);\n throw new OmpPreBundleError(message);\n }\n }\n\n /**\n * Pre-bundles the omp extension with `bun build` so omp's embedded runtime\n * never resolves bare npm specifiers (e.g. @sinclair/typebox) from the\n * extension's node_modules at load time. The bundle is written to a temp\n * directory and swapped into dist-bundle/ on success. The pre-existing\n * dist-bundle is renamed aside (not removed) before the swap, so a failure\n * during the final rename restores the previously working bundle rather than\n * leaving the install with no bundle at all.\n *\n * Override in tests to skip the real bun invocation.\n */\n protected runBundleBuild(ctx: PublishContext, extensionRoot: string, bunBin: string): void {\n const sourceEntry = path.join(extensionRoot, \"index.ts\");\n const tmpOutDir = path.join(extensionRoot, `.dist-bundle.tmp-${process.pid}-${Date.now()}`);\n const finalOutDir = path.join(extensionRoot, \"dist-bundle\");\n\n const result = spawnSync(bunBin, [\"build\", sourceEntry, \"--target=bun\", `--outdir=${tmpOutDir}`], {\n cwd: extensionRoot,\n encoding: \"utf-8\",\n });\n\n if (result.error || result.status !== 0) {\n try {\n fs.rmSync(tmpOutDir, { recursive: true, force: true });\n } catch {\n // best-effort tmp cleanup\n }\n const detail =\n (typeof result.stderr === \"string\" ? result.stderr.trim() : \"\") ||\n (result.error instanceof Error ? result.error.message : \"\") ||\n `bun exited with status ${result.status ?? \"null\"}`;\n throw new Error(\n `Remnic omp extension: bun build failed (${detail}). Resolve the error and re-run ` +\n \"`remnic connectors install omp`, or build manually with \" +\n \"`bun build index.ts --target=bun --outdir=dist-bundle` inside \" +\n `${extensionRoot}.`,\n );\n }\n\n // Swap the freshly built bundle into place without ever leaving the install\n // bundle-less. Rename the existing dist-bundle aside, move the new one in,\n // and only then discard the backup. On any failure mid-swap, restore the\n // backup so the previously working bundle survives (the publish-level\n // rollback only removes newly created dirs — it never restores a removed\n // dist-bundle, so we must not remove it here).\n let backupDir: string | null = null;\n try {\n if (fs.existsSync(finalOutDir)) {\n backupDir = path.join(extensionRoot, `.dist-bundle.bak-${process.pid}-${Date.now()}`);\n fs.renameSync(finalOutDir, backupDir);\n }\n fs.renameSync(tmpOutDir, finalOutDir);\n if (backupDir) {\n try {\n fs.rmSync(backupDir, { recursive: true, force: true });\n } catch {\n // best-effort backup cleanup; leaving it does not break the install\n }\n }\n } catch (err) {\n try {\n if (fs.existsSync(tmpOutDir)) fs.rmSync(tmpOutDir, { recursive: true, force: true });\n } catch {\n // best-effort tmp cleanup\n }\n // Restore the previously working bundle if we moved it aside and the\n // final swap did not land.\n if (backupDir && fs.existsSync(backupDir) && !fs.existsSync(finalOutDir)) {\n try {\n fs.renameSync(backupDir, finalOutDir);\n } catch {\n // best-effort restore; the loader's self-heal rebuilds on next start\n }\n }\n throw new Error(\n `Remnic omp extension: failed to finalize bundle output — ${err instanceof Error ? err.message : String(err)}.`,\n );\n }\n\n ctx.log.info(`Pre-bundled omp extension into ${finalOutDir}`);\n }\n}\n\nfunction extensionOwnedPaths(extensionRoot: string, ownedFileNames: readonly string[]): string[] {\n return ownedFileNames.map((fileName) => path.join(extensionRoot, fileName));\n}\n\nfunction extensionOwnedUnpublishPaths(extensionRoot: string, ownedFileNames: readonly string[]): string[] {\n const ownedBaseNames = new Set(ownedFileNames);\n const ownedPaths = extensionOwnedPaths(extensionRoot, ownedFileNames);\n for (const fileName of fs.readdirSync(extensionRoot)) {\n const match = EXTENSION_OWNED_TEMP_FILE_SUFFIX.exec(fileName);\n if (match && ownedBaseNames.has(fileName.slice(0, match.index))) {\n ownedPaths.push(path.join(extensionRoot, fileName));\n }\n }\n return ownedPaths;\n}\n\nfunction resolveDaemonUrl(ctx: PublishContext): string {\n if (ctx.config.daemonUrl && ctx.config.daemonUrl.trim().length > 0) {\n return trimTrailingSlashes(ctx.config.daemonUrl.trim());\n }\n return `http://127.0.0.1:${ctx.config.daemonPort ?? DEFAULT_DAEMON_PORT}`;\n}\n\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;\n return value.slice(0, end);\n}\n\nfunction resolveExtensionModulePath(): string {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const built = path.join(moduleDir, \"index.js\");\n if (fs.existsSync(built)) return built;\n\n const source = path.join(moduleDir, \"index.ts\");\n if (fs.existsSync(source)) return source;\n\n return built;\n}\n\n/**\n * Resolves the import specifier the omp wrapper uses to reach the\n * `@remnic/plugin-pi` dist entry from the generated `index.ts`. omp pre-bundles\n * that wrapper with `bun build`, whose bundler cannot resolve `file://`\n * specifiers (\"Could not resolve: file://…\" on Bun 1.2–1.3, verified), so the\n * specifier must be a relative path. On Windows, when the extension directory\n * and the plugin-pi install sit on different drives, `path.relative` cannot\n * express a relative path and returns an absolute drive path (e.g. `D:\\…`);\n * prefixing `./` then yields an invalid module specifier that fails `bun build`\n * with a cryptic error. Detect that layout and fail fast with an actionable\n * message instead. (Cross-drive omp installs are unsupported because neither a\n * relative specifier nor a `file://` URL is acceptable to `bun build`.) Drive\n * roots are compared case-insensitively so a same-drive Windows install is not\n * falsely rejected when the agent home and the plugin-pi install report the\n * drive letter in different casing (`C:\\\\` vs `c:\\\\`).\n *\n * Exported so the cross-drive guard can be exercised on non-Windows hosts via\n * `path.win32`.\n */\nexport function resolveOmpWrapperImportSpecifier(\n extensionModulePath: string,\n wrapperDir: string,\n pathApi: typeof path = path,\n): string {\n // Windows drive roots are case-insensitive: `C:\\\\…` (e.g. from the omp agent\n // home) and `c:\\\\…` (e.g. from fileURLToPath(import.meta.url)) are the SAME\n // drive, and path.win32.relative yields a valid relative specifier between\n // them. Compare the parsed roots case-insensitively so a same-drive install\n // isn't falsely rejected as \"different drives\". posix roots (`/`) are\n // unaffected by toLowerCase().\n if (pathApi.parse(wrapperDir).root.toLowerCase() !== pathApi.parse(extensionModulePath).root.toLowerCase()) {\n throw new Error(\n \"Remnic omp extension cannot pre-bundle: the extension directory \" +\n `(${wrapperDir}) and the @remnic/plugin-pi install (${extensionModulePath}) ` +\n \"are on different drives, so no relative import specifier can be generated \" +\n \"for `bun build` (and `bun build` cannot resolve a `file://` specifier). \" +\n \"Move the omp agent home and the Remnic install onto the same drive.\",\n );\n }\n let rel = pathApi.relative(wrapperDir, extensionModulePath);\n rel = rel.split(pathApi.sep).join(\"/\");\n return rel.startsWith(\".\") ? rel : `./${rel}`;\n}\n\nfunction renderWrapper(\n extensionModulePath: string,\n configPath: string,\n wrapperDir?: string,\n): string {\n // omp pre-bundles this entry with `bun build`, whose bundler cannot resolve\n // `file://` specifiers — it exits with \"Could not resolve: file://...\" on\n // Bun 1.2–1.3 (verified). When the wrapper will be bun-built, emit a relative\n // specifier resolved against the wrapper's directory; bun, tsx, and Node ESM\n // all resolve relative specifiers. For tsx-loaded wrappers (pi) the file://\n // URL is retained.\n let importSpecifier: string;\n if (wrapperDir) {\n importSpecifier = resolveOmpWrapperImportSpecifier(extensionModulePath, wrapperDir);\n } else {\n importSpecifier = pathToFileURL(extensionModulePath).href;\n }\n return [\n `import { createRemnicPiExtension } from ${JSON.stringify(importSpecifier)};`,\n \"\",\n `export default createRemnicPiExtension({ configPath: ${JSON.stringify(configPath)} });`,\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Generates the self-healing `loader.js` that omp loads via the package\n * manifest's `omp.extensions` entry. It mtime-compares the pre-bundled\n * `dist-bundle/index.js` against `index.ts` and the underlying @remnic/plugin-pi\n * dist, rebuilds via `bun build` when stale (e.g. after an `npm update`), then\n * imports the self-contained bundle so omp's embedded runtime never resolves\n * bare npm specifiers at load time.\n */\nfunction renderOmpLoader(pluginPiDistPath: string, bunBin: string): string {\n return [\n \"// Auto-generated by Remnic's OmpMemoryExtensionPublisher.\",\n \"// omp's embedded runtime cannot resolve bare npm specifiers from this\",\n \"// extension's node_modules, so we pre-bundle with `bun build` and import\",\n \"// the self-contained bundle here. Rebuilt automatically when index.ts or\",\n \"// the underlying @remnic/plugin-pi dist changes.\",\n \"\",\n 'import { existsSync, renameSync, rmSync, statSync } from \"node:fs\";',\n 'import { spawnSync } from \"node:child_process\";',\n 'import { dirname, join } from \"node:path\";',\n 'import { fileURLToPath, pathToFileURL } from \"node:url\";',\n \"\",\n 'const here = dirname(fileURLToPath(import.meta.url));',\n 'const bundleDir = join(here, \"dist-bundle\");',\n 'const bundleEntry = join(bundleDir, \"index.js\");',\n 'const sourceEntry = join(here, \"index.ts\");',\n `const pluginPiEntry = ${JSON.stringify(pluginPiDistPath)};`,\n // Reuse the bun path resolved at install time (REMNIC_OMP_BUN_BIN, PATH,\n // or a common absolute location). Fall back to \"bun\" on PATH if the\n // resolved path no longer exists (e.g. the extension tree was moved), so\n // self-healing still works when bun is reachable only via PATH.\n `const resolvedBunBin = ${JSON.stringify(bunBin)};`,\n 'const bunForRebuild = resolvedBunBin && existsSync(resolvedBunBin) ? resolvedBunBin : \"bun\";',\n \"\",\n \"function bundleIsStale() {\",\n \" if (!existsSync(bundleEntry)) return true;\",\n \" const bundleMtime = statSync(bundleEntry).mtimeMs;\",\n \" if (existsSync(sourceEntry) && bundleMtime < statSync(sourceEntry).mtimeMs) return true;\",\n \" if (pluginPiEntry && existsSync(pluginPiEntry) && bundleMtime < statSync(pluginPiEntry).mtimeMs) return true;\",\n \" return false;\",\n \"}\",\n \"\",\n \"function rebuildBundle() {\",\n \" // Build to a temp dir and swap, mirroring the install-time build, so a\",\n \" // failed self-heal rebuild never corrupts the working bundle.\",\n ' var tmp = join(here, \".dist-bundle.tmp-\" + process.pid + \"-\" + Date.now());',\n \" var result = spawnSync(bunForRebuild, [\",\n ' \"build\",',\n \" sourceEntry,\",\n ' \"--target=bun\",',\n ' \"--outdir=\" + tmp',\n \" ], {\",\n \" cwd: here,\",\n ' stdio: \"inherit\",',\n \" });\",\n \" if (result.status !== 0 || result.error) {\",\n \" try { rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\",\n \" throw new Error(\",\n ' \"Remnic omp extension: bundle is stale or missing and could not be rebuilt. \" +',\n ' \"Install bun (https://bun.sh), then run \" +',\n ' \"`bun build index.ts --target=bun --outdir=dist-bundle` inside \" + here',\n \" );\",\n \" }\",\n \" var backup = null;\",\n \" try {\",\n \" if (existsSync(bundleDir)) {\",\n ' backup = join(here, \".dist-bundle.bak-\" + process.pid + \"-\" + Date.now());',\n \" renameSync(bundleDir, backup);\",\n \" }\",\n \" renameSync(tmp, bundleDir);\",\n \" if (backup) { try { rmSync(backup, { recursive: true, force: true }); } catch (e) {} }\",\n \" } catch (err) {\",\n \" try { rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\",\n \" if (backup && existsSync(backup) && !existsSync(bundleDir)) { try { renameSync(backup, bundleDir); } catch (e) {} }\",\n \" throw new Error(\",\n ' \"Remnic omp extension: failed to finalize rebuilt bundle - \" + (err && err.message ? err.message : err)',\n \" );\",\n \" }\",\n \"}\",\n\n \"\",\n \"if (bundleIsStale()) rebuildBundle();\",\n \"\",\n \"// Cache-bust so a freshly rebuilt bundle is loaded instead of a stale cached copy.\",\n 'const bundle = await import(pathToFileURL(bundleEntry).href + \"?t=\" + Date.now());',\n \"export default bundle.default;\",\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Generates the `package.json` that tells omp to load `loader.js` (not\n * auto-discover `index.ts`) and re-bundles after `npm install` via postinstall.\n */\nfunction renderOmpPackageJson(): string {\n // Postinstall re-bundles after `npm install` (e.g. a plugin-pi upgrade moved\n // the dist mtime past the bundle). It delegates to postinstall-bundle.cjs — a\n // Node-only helper — so npm's default cmd.exe shell on Windows runs it just as\n // well as POSIX bash. The helper embeds the resolved bun path with a PATH\n // fallback and swaps the bundle atomically.\n const manifest = {\n name: \"remnic-omp-extension\",\n version: \"0.0.0\",\n private: true,\n type: \"module\",\n omp: { extensions: [\"./loader.js\"] },\n // Legacy key so older omp builds that only read `pi.extensions` also\n // resolve loader.js instead of falling through to index.ts.\n pi: { extensions: [\"./loader.js\"] },\n scripts: { postinstall: \"node postinstall-bundle.cjs\" },\n };\n return `${JSON.stringify(manifest, null, 2)}\\n`;\n}\n\n/**\n * Generates the cross-platform `postinstall-bundle.cjs` helper. Node-only, so\n * npm's default cmd.exe shell on Windows re-bundles after `npm install` just as\n * well as POSIX bash. Embeds the bun path resolved at install time with a PATH\n * fallback and writes the new bundle via a temp-dir swap so a failed rebuild\n * never corrupts the working bundle. The emitted script uses string\n * concatenation (no template literals) so it stays parseable everywhere.\n */\nfunction renderOmpPostinstall(bunBin: string): string {\n // Single template literal: the emitted .cjs uses string concatenation (no\n // template literals of its own), so this body has no backticks and the one\n // ${JSON.stringify(bunBin)} interpolation is unambiguous.\n return `// Auto-generated by Remnic's OmpMemoryExtensionPublisher.\n// Re-bundles the omp extension after npm install (e.g. a plugin-pi upgrade)\n// using the bun path resolved at install time, with a PATH fallback. Node-only\n// so it runs under npm's default cmd.exe shell on Windows as well as POSIX bash.\n\"use strict\";\nvar fs = require(\"node:fs\");\nvar cp = require(\"node:child_process\");\nvar path = require(\"node:path\");\n\nvar RESOLVED_BUN = ${JSON.stringify(bunBin)};\nvar dir = __dirname;\nvar entry = path.join(dir, \"index.ts\");\nvar out = path.join(dir, \"dist-bundle\");\n\nfunction pickBun() {\n var env = process.env.REMNIC_OMP_BUN_BIN;\n if (env && fs.existsSync(env)) return env;\n if (RESOLVED_BUN && fs.existsSync(RESOLVED_BUN)) return RESOLVED_BUN;\n return \"bun\";\n}\n\nfunction rebuild() {\n var bun = pickBun();\n var tmp = path.join(dir, \".dist-bundle.tmp-\" + process.pid + \"-\" + Date.now());\n var r = cp.spawnSync(bun, [\"build\", entry, \"--target=bun\", \"--outdir=\" + tmp], { cwd: dir, stdio: \"inherit\" });\n if (r.error || r.status !== 0) {\n try { fs.rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\n throw new Error(\"Remnic omp extension: postinstall bun build failed (bun=\" + bun + \"). Run bun build index.ts --target=bun --outdir=dist-bundle manually inside \" + dir);\n }\n var backup = null;\n try {\n if (fs.existsSync(out)) {\n backup = path.join(dir, \".dist-bundle.bak-\" + process.pid + \"-\" + Date.now());\n fs.renameSync(out, backup);\n }\n fs.renameSync(tmp, out);\n if (backup) { try { fs.rmSync(backup, { recursive: true, force: true }); } catch (e) {} }\n } catch (err) {\n try { fs.rmSync(tmp, { recursive: true, force: true }); } catch (e) {}\n if (backup && fs.existsSync(backup) && !fs.existsSync(out)) { try { fs.renameSync(backup, out); } catch (e) {} }\n throw err;\n }\n}\n\ntry {\n rebuild();\n} catch (err) {\n console.error(err && err.message ? err.message : err);\n process.exit(1);\n}\n`;\n}\n\n/**\n * True when `candidate` is a regular file that the current process can\n * execute. Used by every `bun`-binary candidate selection site so a stale,\n * non-executable file named `bun` (or `bun.exe`) cannot win over a later\n * working binary — matching `which(1)` and the `spawnSync(\"bun\", [\"--version\"])`\n * version probe, which both skip non-executable files. On Windows\n * `fs.accessSync(X_OK)` verifies read access, which holds for real `.exe`\n * files, so the check is a harmless no-op there.\n */\nfunction isExecutableFile(candidate: string): boolean {\n try {\n const stat = fs.statSync(candidate);\n if (!stat.isFile()) return false;\n fs.accessSync(candidate, fs.constants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Walks `PATH` the way a shell does and returns the first `bun` executable it\n * finds, as a realpath-resolved absolute path (or null when nothing on PATH\n * is an executable `bun`). Used so the install-time PATH probe can embed an\n * absolute bun path in the generated loader/postinstall instead of the bare\n * string `\"bun\"`, which would break self-heal rebuilds under a stripped\n * runtime PATH (GUI/service launches). Mirrors `which(1)`; no dependency.\n */\nexport function resolveBunOnPath(): string | null {\n const pathVar = process.env.PATH ?? process.env.Path ?? process.env.path ?? \"\";\n const separator = process.platform === \"win32\" ? \";\" : \":\";\n const candidateNames =\n process.platform === \"win32\" ? [\"bun.exe\", \"bun\"] : [\"bun\"];\n for (const dir of pathVar.split(separator)) {\n if (!dir) continue;\n for (const name of candidateNames) {\n const candidate = path.isAbsolute(dir)\n ? path.join(dir, name)\n : path.resolve(dir, name);\n if (isExecutableFile(candidate)) {\n return fs.realpathSync(candidate);\n }\n }\n }\n return null;\n}\n\n/**\n * Resolves the `bun` binary for the install-time pre-bundle. Honours\n * `REMNIC_OMP_BUN_BIN` (test/override seam), then PATH, then common locations.\n * Returns null when bun is unavailable so the caller can fail with guidance.\n */\nexport function resolveBunBinary(): string | null {\n const override = process.env.REMNIC_OMP_BUN_BIN;\n if (override !== undefined) {\n return fs.existsSync(override) ? override : null;\n }\n\n const pathProbe = spawnSync(\"bun\", [\"--version\"], { encoding: \"utf-8\" });\n if (!pathProbe.error && pathProbe.status === 0) {\n // Resolve the PATH-found bun to an absolute executable so the embedded\n // loader/postinstall don't depend on omp's runtime PATH — GUI/service\n // launches commonly inherit a stripped PATH, which would make a bare\n // \"bun\" self-heal spawn fail even though install found a working binary.\n // Fall back to \"bun\" only if the PATH walk can't locate it (e.g. a shell\n // function/alias that isn't an actual file on PATH).\n return resolveBunOnPath() ?? \"bun\";\n }\n\n // Mirror omp's path helpers, which resolve the agent home as\n // HOME ?? USERPROFILE ?? os.homedir(). Relying on HOME alone breaks the\n // ~/.bun/bin/bun fallback on Windows installs where HOME is unset.\n const home = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();\n // The official Bun installer writes ~/.bun/bin/bun on POSIX and\n // ~/.bun/bin/bun.exe on Windows. Select the first candidate that is an\n // executable regular file (not merely one that exists) so a stale,\n // non-executable ~/.bun/bin/bun cannot win over a later working binary\n // (e.g. /usr/local/bin/bun or /opt/homebrew/bin/bun) — same `which(1)`\n // semantics as the PATH walk above.\n const candidates = [\n path.join(home ?? \"\", \".bun\", \"bin\", \"bun\"),\n path.join(home ?? \"\", \".bun\", \"bin\", \"bun.exe\"),\n \"/usr/local/bin/bun\",\n \"/opt/homebrew/bin/bun\",\n ];\n for (const candidate of candidates) {\n if (isExecutableFile(candidate)) return candidate;\n }\n return null;\n}\n\nfunction atomicWriteFile(filePath: string, content: string, mode: number): void {\n rejectSymlinkPath(filePath);\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n try {\n fs.writeFileSync(tmpPath, content, { encoding: \"utf-8\", mode });\n fs.renameSync(tmpPath, filePath);\n try {\n fs.chmodSync(filePath, mode);\n } catch {\n // Best effort for platforms that do not support chmod.\n }\n } catch (err) {\n try {\n if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);\n } catch {\n // Best-effort cleanup only.\n }\n throw err;\n }\n}\n\nfunction snapshotFiles(paths: string[]): FileSnapshot[] {\n return paths.map((filePath) => {\n if (!fs.existsSync(filePath)) return { path: filePath, existed: false };\n const stat = fs.lstatSync(filePath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${filePath}`);\n }\n if (!stat.isFile()) return { path: filePath, existed: false };\n return {\n path: filePath,\n existed: true,\n content: fs.readFileSync(filePath),\n mode: stat.mode & 0o777,\n };\n });\n}\n\nfunction restorePublishSnapshot(extensionRoot: string, rootExisted: boolean, snapshots: FileSnapshot[]): void {\n if (!rootExisted && !canCleanNewExtensionRoot(extensionRoot)) return;\n\n for (const snapshot of snapshots) {\n restoreOwnedFile(snapshot);\n }\n\n if (!rootExisted) {\n removeEmptyDirectory(extensionRoot);\n }\n}\n\n/**\n * Restores a single owned file to its pre-publish state, atomically.\n *\n * Two cases:\n *\n * - The file did NOT exist before publish (publish created it): remove it to\n * undo the publish. {@link assertSafeExistingPath} re-checks it is not a\n * symlink swapped in after the snapshot; `rmSync` removes a symlink itself\n * rather than following it, but refusing surfaces tampering loudly.\n *\n * - The file DID exist before publish: restore its prior content using\n * \"write-new-before-delete-old\" (rules 42/54). We write the prior content to\n * a temp path in the same directory, then {@link fs.renameSync} it into\n * place. The live file is never truncated, so a mid-restore failure (disk\n * full, EACCES, …) leaves the current on-disk content intact rather than\n * half-written — the restore either fully lands or does nothing. The temp\n * path uses the `.tmp-<pid>-<ts>` suffix tracked by\n * {@link EXTENSION_OWNED_TEMP_FILE_SUFFIX}, so any lingering temp is swept\n * by unpublish. The final `renameSync` does NOT follow a symlink even if one\n * was swapped into the snapshot path after the snapshot (TOCTOU\n * defense-in-depth): `rename(2)` replaces the symlink itself, so no write\n * ever reaches an arbitrary target. We still re-check for a symlink right\n * before the rename so the rollback surfaces tampering instead of silently\n * replacing it.\n */\nfunction restoreOwnedFile(snapshot: FileSnapshot): void {\n if (!snapshot.existed) {\n assertSafeExistingPath(snapshot.path);\n fs.rmSync(snapshot.path, { force: true });\n return;\n }\n\n fs.mkdirSync(path.dirname(snapshot.path), { recursive: true });\n const tmpPath = `${snapshot.path}.tmp-${process.pid}-${Date.now()}`;\n try {\n fs.writeFileSync(tmpPath, snapshot.content ?? Buffer.alloc(0), {\n mode: snapshot.mode ?? 0o644,\n });\n if (snapshot.mode !== undefined) {\n try {\n fs.chmodSync(tmpPath, snapshot.mode);\n } catch {\n // Best effort for platforms that do not support chmod.\n }\n }\n rejectSymlinkPath(snapshot.path);\n fs.renameSync(tmpPath, snapshot.path);\n } catch (err) {\n try {\n if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);\n } catch {\n // Best-effort cleanup only.\n }\n throw err;\n }\n}\n\nfunction snapshotDirs(paths: string[]): DirSnapshot[] {\n return paths.map((dirPath) => {\n let existed = false;\n try {\n const stat = fs.lstatSync(dirPath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${dirPath}`);\n }\n existed = stat.isDirectory();\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") {\n existed = false;\n } else {\n throw err;\n }\n }\n return { path: dirPath, existed };\n });\n}\n\nfunction restoreDirSnapshots(snapshots: DirSnapshot[]): void {\n for (const snapshot of snapshots) {\n if (snapshot.existed) continue;\n try {\n fs.rmSync(snapshot.path, { recursive: true, force: true });\n } catch {\n // best-effort — the loader self-heals at runtime if the dir lingers\n }\n }\n}\n\n\nfunction canCleanNewExtensionRoot(extensionRoot: string): boolean {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(extensionRoot);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return false;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${extensionRoot}`);\n }\n return stat.isDirectory();\n}\n\nfunction removeEmptyDirectory(dirPath: string): void {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(dirPath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${dirPath}`);\n }\n if (!stat.isDirectory()) return;\n if (fs.readdirSync(dirPath).length > 0) return;\n fs.rmdirSync(dirPath);\n}\n\nfunction removableOwnedExtensionFiles(filePaths: string[]): string[] {\n const removableFiles: string[] = [];\n for (const filePath of filePaths) {\n const stat = statOwnedExtensionPath(filePath);\n if (stat === null) continue;\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${filePath}`);\n }\n if (stat.isFile()) removableFiles.push(filePath);\n }\n return removableFiles;\n}\n\nfunction statOwnedExtensionPath(filePath: string): fs.Stats | null {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(filePath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return null;\n throw err;\n }\n return stat;\n}\n\nfunction mkdirExtensionRoot(extensionRoot: string, agentHome: string): void {\n const extensionsDir = path.join(path.resolve(agentHome), \"extensions\");\n assertSafeExtensionRoot(extensionRoot, agentHome);\n fs.mkdirSync(extensionsDir, { recursive: true });\n rejectSymlinkPath(extensionsDir);\n fs.mkdirSync(extensionRoot, { recursive: true });\n rejectSymlinkPath(extensionRoot);\n}\n\nfunction assertSafeExtensionRoot(extensionRoot: string, agentHome: string): void {\n const resolvedAgentHome = path.resolve(agentHome);\n const expected = path.join(resolvedAgentHome, \"extensions\", \"remnic\");\n if (path.resolve(extensionRoot) !== path.resolve(expected)) {\n throw new Error(`Extension root is outside the configured extensions directory: ${extensionRoot}`);\n }\n const extensionsDir = path.join(resolvedAgentHome, \"extensions\");\n assertPathContained(resolvedAgentHome, extensionsDir);\n assertPathContained(extensionsDir, extensionRoot);\n rejectSymlinkPath(resolvedAgentHome);\n if (fs.existsSync(extensionsDir)) rejectSymlinkPath(extensionsDir);\n if (fs.existsSync(extensionRoot)) rejectSymlinkPath(extensionRoot);\n}\n\nfunction assertSafeExistingPath(filePath: string): void {\n if (fs.existsSync(filePath)) rejectSymlinkPath(filePath);\n}\n\nfunction rejectSymlinkPath(filePath: string): void {\n if (!fs.existsSync(filePath)) return;\n const stat = fs.lstatSync(filePath);\n if (stat.isSymbolicLink()) {\n throw new Error(`Extension path must not be a symlink: ${filePath}`);\n }\n}\n\nfunction assertPathContained(root: string, candidate: string): void {\n const rootResolved = path.resolve(root);\n const candidateResolved = path.resolve(candidate);\n const relative = path.relative(rootResolved, candidateResolved);\n if (relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative))) return;\n throw new Error(`Extension path escapes allowed root: ${candidate}`);\n}\n\nfunction readPriorConfig(configPath: string): Record<string, unknown> {\n if (!fs.existsSync(configPath)) return {};\n try {\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n throw new Error(\"expected a JSON object\");\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load existing Remnic Pi config at ${configPath}: ${reason}`);\n }\n}\n\nfunction snapshotTokenEntry(connectorId: string): TokenEntry | null {\n const entry = loadTokenStore().tokens.find((candidate) => candidate.connector === connectorId);\n return cloneTokenEntry(entry ?? null);\n}\n\nfunction cloneTokenEntry(entry: TokenEntry | null): TokenEntry | null {\n return entry ? { ...entry } : null;\n}\n\nfunction restoreTokenEntry(priorEntry: TokenEntry | null, connectorId: string): void {\n const store = loadTokenStore();\n store.tokens = store.tokens.filter((entry) => entry.connector !== connectorId);\n if (priorEntry) store.tokens.push(priorEntry);\n saveTokenStore(store);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/plugin-pi",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.37.0",
|
|
4
4
|
"description": "Remnic memory extension for Pi Coding Agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@sinclair/typebox": "^0.34.0",
|
|
45
|
-
"@remnic/core": "^9.
|
|
45
|
+
"@remnic/core": "^9.37.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@earendil-works/pi-coding-agent": "*"
|