@remnic/plugin-pi 1.0.9 → 9.3.515

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.d.ts CHANGED
@@ -87,7 +87,8 @@ declare function createRemnicPiExtension(options?: RemnicPiExtensionOptions): (p
87
87
  declare function remnicPiExtension(pi: PiApi): Promise<void>;
88
88
  declare function toPiToolParametersSchema(inputSchema: unknown): TSchema;
89
89
  declare function stripSessionOwnedSchemaFields(inputSchema: unknown): Record<string, unknown>;
90
+ declare function stripSessionOwnedRuntimeFields(value: unknown): unknown;
90
91
  declare function observeMessages(ctx: any, client: RemnicClient, rawMessages: unknown[], observedHashes: Set<string>, liveObservedReplayKeys?: Map<string, number>): Promise<void>;
91
92
  declare function buildCompactionSummary(preparation: any): string;
92
93
 
93
- export { type RemnicPiExtensionOptions, buildCompactionSummary, createRemnicPiExtension, remnicPiExtension as default, observeMessages, stripSessionOwnedSchemaFields, textFromMessage, toPiToolParametersSchema };
94
+ export { type RemnicPiExtensionOptions, buildCompactionSummary, createRemnicPiExtension, remnicPiExtension as default, observeMessages, stripSessionOwnedRuntimeFields, stripSessionOwnedSchemaFields, textFromMessage, toPiToolParametersSchema };
package/dist/index.js CHANGED
@@ -243,9 +243,21 @@ var RemnicClient = class {
243
243
  signal: controller.signal
244
244
  });
245
245
  const text = await response.text();
246
- const payload = text ? JSON.parse(text) : {};
246
+ let payload = {};
247
+ let parseError;
248
+ if (text) {
249
+ try {
250
+ payload = JSON.parse(text);
251
+ } catch (err) {
252
+ parseError = err;
253
+ }
254
+ }
247
255
  if (!response.ok) {
248
- throw new RemnicHttpError(response.status, typeof payload?.error === "string" ? payload.error : response.statusText);
256
+ throw new RemnicHttpError(response.status, responseErrorMessage(response, text, payload, parseError));
257
+ }
258
+ if (parseError) {
259
+ const reason = parseError instanceof Error ? parseError.message : String(parseError);
260
+ throw new Error(`Invalid JSON response from Remnic daemon (${response.status} ${response.statusText || "OK"}): ${reason}`);
249
261
  }
250
262
  return payload;
251
263
  } catch (err) {
@@ -277,6 +289,23 @@ function isMcpTool(value) {
277
289
  function isAbortError(err) {
278
290
  return err instanceof Error && (err.name === "AbortError" || err.message === "This operation was aborted");
279
291
  }
292
+ function responseErrorMessage(response, text, payload, parseError) {
293
+ if (!parseError && payload && typeof payload === "object") {
294
+ const error = payload.error;
295
+ if (typeof error === "string" && error.trim().length > 0) {
296
+ return error;
297
+ }
298
+ const message = payload.message;
299
+ if (typeof message === "string" && message.trim().length > 0) {
300
+ return message;
301
+ }
302
+ }
303
+ const snippet = text.trim().replace(/\s+/g, " ").slice(0, 200);
304
+ if (snippet.length > 0) {
305
+ return response.statusText ? `${response.statusText}: ${snippet}` : snippet;
306
+ }
307
+ return response.statusText || `HTTP ${response.status}`;
308
+ }
280
309
 
281
310
  // src/messages.ts
282
311
  import { createHash } from "crypto";
@@ -296,21 +325,25 @@ function textFromMessage(message) {
296
325
  }
297
326
  return textFromContent(obj.content).trim();
298
327
  }
299
- function latestUserQuery(messages) {
328
+ function latestUserRecallTarget(messages) {
300
329
  for (let index = messages.length - 1; index >= 0; index--) {
301
330
  const message = messages[index];
302
- if (isExcludedFromContext(message)) continue;
303
- if (message?.role === "user") {
304
- const text = textFromMessage(message);
305
- if (text.length > 0) return text;
306
- }
331
+ if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;
332
+ if (message?.role !== "user") continue;
333
+ const query = textFromMessage(message);
334
+ if (query.length === 0) continue;
335
+ const identity = stableObservedMessageIdentity(message);
336
+ return {
337
+ query,
338
+ dedupeKey: identity ? `message:${identity}:${query}` : `query:${query}`
339
+ };
307
340
  }
308
- return "";
341
+ return null;
309
342
  }
310
343
  function toObserveMessage(message) {
311
344
  if (!message || typeof message !== "object") return null;
312
345
  const obj = message;
313
- if (isExcludedFromContext(obj)) return null;
346
+ if (isExcludedFromContext(obj) || isRemnicInjected(obj)) return null;
314
347
  const role = obj.role === "user" || obj.role === "bashExecution" ? "user" : "assistant";
315
348
  const content = textFromMessage(obj);
316
349
  if (content.length === 0) return null;
@@ -333,7 +366,7 @@ function summarizeMessages(messages, maxChars) {
333
366
  const chunks = [];
334
367
  let used = 0;
335
368
  for (const message of messages) {
336
- if (isExcludedFromContext(message)) continue;
369
+ if (isExcludedFromContext(message) || isRemnicInjected(message)) continue;
337
370
  const text = textFromMessage(message);
338
371
  if (!text) continue;
339
372
  const role = typeof message?.role === "string" ? message.role : "message";
@@ -351,6 +384,9 @@ function summarizeMessages(messages, maxChars) {
351
384
  function isExcludedFromContext(message) {
352
385
  return !!message && typeof message === "object" && message.excludeFromContext === true;
353
386
  }
387
+ function isRemnicInjected(message) {
388
+ return !!message && typeof message === "object" && message.remnicInjected === true;
389
+ }
354
390
  function textFromContent(content) {
355
391
  if (typeof content === "string") return content;
356
392
  if (!Array.isArray(content)) return "";
@@ -411,6 +447,7 @@ var MAX_OBSERVED_HASHES = 2e3;
411
447
  var MAX_SESSION_STATES = 50;
412
448
  var MAX_CONTEXT_CHARS = 12e3;
413
449
  var TRUNCATION_NOTICE = "\n\n[Remnic context truncated]";
450
+ var SESSION_OWNED_FIELDS = /* @__PURE__ */ new Set(["sessionKey", "namespace", "cwd"]);
414
451
  function createRemnicPiExtension(options = {}) {
415
452
  const config = options.config ?? loadConfig(options);
416
453
  const client = new RemnicClient(config);
@@ -424,16 +461,17 @@ function createRemnicPiExtension(options = {}) {
424
461
  });
425
462
  pi.on("context", async (event, ctx) => {
426
463
  if (!config.recallEnabled || !config.authToken) return;
427
- const query = latestUserQuery(Array.isArray(event.messages) ? event.messages : []);
428
- if (!query) return;
464
+ const recallTarget = latestUserRecallTarget(Array.isArray(event.messages) ? event.messages : []);
465
+ if (!recallTarget) return;
466
+ const { query } = recallTarget;
429
467
  const sessionKey = sessionKeyFromContext(ctx);
430
468
  const { state } = getSessionState(ctx, sessionStates);
431
- if (query === state.lastInjectedQuery) return;
469
+ if (recallTarget.dedupeKey === state.lastInjectedRecallKey) return;
432
470
  try {
433
471
  const recalled = await client.recall(query, sessionKey, ctx.cwd);
434
472
  const context = trimContext(recalled.context ?? "", config.recallBudgetChars);
435
473
  if (!context) return;
436
- state.lastInjectedQuery = query;
474
+ state.lastInjectedRecallKey = recallTarget.dedupeKey;
437
475
  return {
438
476
  messages: [
439
477
  {
@@ -441,6 +479,7 @@ function createRemnicPiExtension(options = {}) {
441
479
  content: [{ type: "text", text: `Remnic recalled context for this turn:
442
480
 
443
481
  ${context}` }],
482
+ remnicInjected: true,
444
483
  timestamp: Date.now()
445
484
  },
446
485
  ...event.messages
@@ -482,13 +521,21 @@ ${context}` }],
482
521
  notify(ctx, `Remnic LCM flush failed: ${errorMessage(err)}`, "warning");
483
522
  return;
484
523
  }
485
- const summary = buildCompactionSummary(preparation);
486
- if (!summary.trim()) return;
487
- void client.contextCheckpoint(sessionKey, summary).catch(() => void 0);
488
524
  const tokensBefore = finiteTokenCount(preparation.tokensBefore);
489
525
  const tokensAfter = finiteTokenCount(preparation.tokensAfter);
490
526
  if (tokensBefore !== null && tokensAfter !== null) {
491
- void client.lcmCompactionRecord(sessionKey, tokensBefore, tokensAfter).catch(() => void 0);
527
+ try {
528
+ await client.lcmCompactionRecord(sessionKey, tokensBefore, tokensAfter);
529
+ } catch (err) {
530
+ notify(ctx, `Remnic LCM compaction token record failed: ${errorMessage(err)}`, "warning");
531
+ }
532
+ }
533
+ const summary = buildCompactionSummary(preparation);
534
+ if (!summary.trim()) return;
535
+ try {
536
+ await client.contextCheckpoint(sessionKey, summary);
537
+ } catch (err) {
538
+ notify(ctx, `Remnic context checkpoint failed: ${errorMessage(err)}`, "warning");
492
539
  }
493
540
  const details = fileDetailsFromPreparation(preparation);
494
541
  return {
@@ -597,12 +644,7 @@ async function registerMcpTools(pi, client, config) {
597
644
  parameters: toPiToolParametersSchema(tool.inputSchema),
598
645
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
599
646
  const sessionKey = sessionKeyFromContext(ctx);
600
- const {
601
- sessionKey: _ignoredSessionKey,
602
- namespace: _ignoredNamespace,
603
- cwd: _ignoredCwd,
604
- ...safeParams
605
- } = params ?? {};
647
+ const safeParams = stripSessionOwnedRuntimeFields(params ?? {});
606
648
  const result = await client.mcpTool(tool.name, {
607
649
  ...safeParams,
608
650
  sessionKey,
@@ -624,19 +666,55 @@ function stripSessionOwnedSchemaFields(inputSchema) {
624
666
  if (!isRecord(inputSchema)) {
625
667
  return { type: "object", properties: {}, additionalProperties: true };
626
668
  }
627
- const schema = { ...inputSchema };
628
- const properties = isRecord(inputSchema.properties) ? { ...inputSchema.properties } : {};
629
- delete properties.sessionKey;
630
- delete properties.namespace;
631
- delete properties.cwd;
632
- schema.properties = properties;
633
- if (Array.isArray(inputSchema.required)) {
634
- schema.required = inputSchema.required.filter(
635
- (field) => field !== "sessionKey" && field !== "namespace" && field !== "cwd"
669
+ return stripSessionOwnedSchemaNode(inputSchema);
670
+ }
671
+ function stripSessionOwnedSchemaNode(value) {
672
+ if (Array.isArray(value)) {
673
+ return value.map((entry) => stripSessionOwnedSchemaNode(entry));
674
+ }
675
+ if (!isRecord(value)) {
676
+ return value;
677
+ }
678
+ const schema = { ...value };
679
+ if (isRecord(value.properties)) {
680
+ const properties = {};
681
+ for (const [key, property] of Object.entries(value.properties)) {
682
+ if (SESSION_OWNED_FIELDS.has(key)) continue;
683
+ properties[key] = stripSessionOwnedSchemaNode(property);
684
+ }
685
+ schema.properties = properties;
686
+ }
687
+ if (Array.isArray(value.required)) {
688
+ schema.required = value.required.filter(
689
+ (field) => typeof field !== "string" || !SESSION_OWNED_FIELDS.has(field)
636
690
  );
637
691
  }
692
+ for (const key of ["items", "additionalProperties", "not"]) {
693
+ if (isRecord(value[key])) {
694
+ schema[key] = stripSessionOwnedSchemaNode(value[key]);
695
+ }
696
+ }
697
+ for (const key of ["oneOf", "anyOf", "allOf"]) {
698
+ if (Array.isArray(value[key])) {
699
+ schema[key] = value[key].map((entry) => stripSessionOwnedSchemaNode(entry));
700
+ }
701
+ }
638
702
  return schema;
639
703
  }
704
+ function stripSessionOwnedRuntimeFields(value) {
705
+ if (Array.isArray(value)) {
706
+ return value.map((entry) => stripSessionOwnedRuntimeFields(entry));
707
+ }
708
+ if (!isRecord(value)) {
709
+ return value;
710
+ }
711
+ const sanitized = {};
712
+ for (const [key, child] of Object.entries(value)) {
713
+ if (SESSION_OWNED_FIELDS.has(key)) continue;
714
+ sanitized[key] = stripSessionOwnedRuntimeFields(child);
715
+ }
716
+ return sanitized;
717
+ }
640
718
  function isRecord(value) {
641
719
  return !!value && typeof value === "object" && !Array.isArray(value);
642
720
  }
@@ -650,7 +728,7 @@ function getSessionState(ctx, states) {
650
728
  state = {
651
729
  observedHashes: /* @__PURE__ */ new Set(),
652
730
  liveObservedReplayKeys: /* @__PURE__ */ new Map(),
653
- lastInjectedQuery: ""
731
+ lastInjectedRecallKey: ""
654
732
  };
655
733
  states.set(sessionKey, state);
656
734
  pruneSessionStates(states);
@@ -850,6 +928,7 @@ export {
850
928
  createRemnicPiExtension,
851
929
  remnicPiExtension as default,
852
930
  observeMessages,
931
+ stripSessionOwnedRuntimeFields,
853
932
  stripSessionOwnedSchemaFields,
854
933
  textFromMessage,
855
934
  toPiToolParametersSchema
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, type McpTool, type ObserveMessage } from \"./client.js\";\nimport {\n hashObservedMessage,\n latestUserQuery,\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]\";\n\ntype PiSessionState = {\n observedHashes: Set<string>;\n liveObservedReplayKeys: Map<string, number>;\n lastInjectedQuery: string;\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 { state } = getSessionState(ctx, sessionStates);\n restoreObservedState(ctx, state.observedHashes);\n if (!config.statusEnabled) return;\n await setStatus(ctx, client, config);\n });\n\n pi.on(\"context\", async (event, ctx) => {\n if (!config.recallEnabled || !config.authToken) return;\n const query = latestUserQuery(Array.isArray(event.messages) ? event.messages : []);\n if (!query) return;\n const sessionKey = sessionKeyFromContext(ctx);\n const { state } = getSessionState(ctx, sessionStates);\n if (query === state.lastInjectedQuery) return;\n\n try {\n const recalled = await client.recall(query, sessionKey, ctx.cwd);\n const context = trimContext(recalled.context ?? \"\", config.recallBudgetChars);\n if (!context) return;\n state.lastInjectedQuery = query;\n return {\n messages: [\n {\n role: \"user\",\n content: [{ type: \"text\", text: `Remnic recalled context for this turn:\\n\\n${context}` }],\n timestamp: Date.now(),\n },\n ...event.messages,\n ],\n };\n } catch (err) {\n notify(ctx, `Remnic recall unavailable: ${errorMessage(err)}`, \"warning\");\n }\n });\n\n pi.on(\"message_end\", async (event, ctx) => {\n if (!config.observeEnabled || !isUserMessage(event.message)) return;\n const { state } = getSessionState(ctx, sessionStates);\n await observeMessages(ctx, client, [event.message], state.observedHashes, state.liveObservedReplayKeys);\n });\n\n pi.on(\"turn_end\", async (event, ctx) => {\n if (!config.observeEnabled) return;\n const messages = [event.message, ...(Array.isArray(event.toolResults) ? event.toolResults : [])];\n const { state } = getSessionState(ctx, sessionStates);\n await observeMessages(ctx, client, messages, state.observedHashes, state.liveObservedReplayKeys);\n });\n\n pi.on(\"session_shutdown\", async (_event, ctx) => {\n const { sessionKey, state } = getSessionState(ctx, sessionStates);\n if (config.observeEnabled) {\n const branch = safeBranch(ctx);\n const branchMessages = branchMessagesWithEntryIdentity(branch);\n const unobservedBranchMessages = skipLiveObservedReplayMessages(ctx, branchMessages, state.liveObservedReplayKeys);\n if (unobservedBranchMessages.length > 0) await observeMessages(ctx, client, unobservedBranchMessages, state.observedHashes);\n }\n persistObservedState(pi, state.observedHashes);\n sessionStates.delete(sessionKey);\n });\n\n pi.on(\"session_before_compact\", async (event, ctx) => {\n if (!config.compactionEnabled || !config.authToken) return;\n const sessionKey = sessionKeyFromContext(ctx);\n const preparation = event.preparation ?? {};\n try {\n await client.lcmCompactionFlush(sessionKey);\n } catch (err) {\n notify(ctx, `Remnic LCM flush failed: ${errorMessage(err)}`, \"warning\");\n return;\n }\n\n const summary = buildCompactionSummary(preparation);\n if (!summary.trim()) return;\n void client.contextCheckpoint(sessionKey, summary).catch(() => undefined);\n const tokensBefore = finiteTokenCount(preparation.tokensBefore);\n const tokensAfter = finiteTokenCount(preparation.tokensAfter);\n if (tokensBefore !== null && tokensAfter !== null) {\n void client.lcmCompactionRecord(sessionKey, tokensBefore, tokensAfter).catch(() => undefined);\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) => {\n const health = await client.health();\n notify(ctx, `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) => {\n const query = args.trim();\n if (!query) {\n notify(ctx, \"Usage: /remnic-recall <query>\", \"warning\");\n return;\n }\n const result = await client.recall(query, sessionKeyFromContext(ctx), ctx.cwd);\n notify(ctx, 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) => {\n const content = args.trim();\n if (!content) {\n notify(ctx, \"Usage: /remnic-remember <memory>\", \"warning\");\n return;\n }\n await client.storeMemory(content, sessionKeyFromContext(ctx));\n notify(ctx, \"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) => {\n const query = args.trim();\n if (!query) {\n notify(ctx, \"Usage: /remnic-lcm-search <query>\", \"warning\");\n return;\n }\n const result = await client.lcmSearch(query, sessionKeyFromContext(ctx));\n notify(ctx, 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) => {\n const result = await client.recallExplain(sessionKeyFromContext(ctx));\n notify(ctx, 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) => {\n ctx.compact?.();\n notify(ctx, \"Compaction requested\", \"info\");\n }),\n });\n}\n\nfunction commandHandler(handler: (args: string, ctx: any) => Promise<void>): (args: string, ctx: any) => Promise<void> {\n return async (args, ctx) => {\n try {\n await handler(args, ctx);\n } catch (err) {\n notify(ctx, `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();\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 sessionKey = sessionKeyFromContext(ctx);\n const {\n sessionKey: _ignoredSessionKey,\n namespace: _ignoredNamespace,\n cwd: _ignoredCwd,\n ...safeParams\n } = params ?? {};\n const result = await client.mcpTool(tool.name, {\n ...safeParams,\n sessionKey,\n namespace: config.namespace,\n cwd: ctx.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 const schema: Record<string, unknown> = { ...inputSchema };\n const properties = isRecord(inputSchema.properties)\n ? { ...inputSchema.properties }\n : {};\n delete properties.sessionKey;\n delete properties.namespace;\n delete properties.cwd;\n schema.properties = properties;\n if (Array.isArray(inputSchema.required)) {\n schema.required = inputSchema.required.filter(\n (field) => field !== \"sessionKey\" && field !== \"namespace\" && field !== \"cwd\",\n );\n }\n return schema;\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(ctx: any, states: Map<string, PiSessionState>): { sessionKey: string; state: PiSessionState } {\n const sessionKey = sessionKeyFromContext(ctx);\n let state = states.get(sessionKey);\n if (!state) {\n state = {\n observedHashes: new Set<string>(),\n liveObservedReplayKeys: new Map<string, number>(),\n lastInjectedQuery: \"\",\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 sessionKey = sessionKeyFromContext(ctx);\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, 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 try {\n await client.observe(sessionKey, ctx.cwd, messages);\n for (const hash of pendingHashes) rememberObservedHash(observedHashes, hash);\n if (liveObservedReplayKeys) {\n for (const message of messages) {\n rememberLiveObservedReplayKey(liveObservedReplayKeys, liveReplayKey(message, sessionKey));\n }\n }\n } catch (err) {\n notify(ctx, `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(ctx: any, observedHashes: Set<string>): void {\n for (const entry of safeEntries(ctx)) {\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 ctx: any,\n rawMessages: unknown[],\n liveObservedReplayKeys: Map<string, number>,\n): unknown[] {\n if (liveObservedReplayKeys.size === 0) return rawMessages;\n const sessionKey = sessionKeyFromContext(ctx);\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\nasync function setStatus(ctx: any, client: RemnicClient, config: RemnicPiConfig): Promise<void> {\n try {\n await client.health();\n ctx.ui?.setStatus?.(\"remnic\", `Remnic ${config.namespace ? `(${config.namespace})` : \"ready\"}`);\n } catch {\n ctx.ui?.setStatus?.(\"remnic\", \"Remnic offline\");\n }\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\nfunction notify(ctx: any, message: string, level: \"info\" | \"success\" | \"warning\" | \"error\"): void {\n if (ctx?.hasUI === false) return;\n ctx?.ui?.notify?.(message, level);\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\nimport { expandTildePath } from \"@remnic/core\";\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}\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};\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\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 return expandTildePath(options.configPath || env.REMNIC_PI_CONFIG || defaultConfigPath(env));\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 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: coercePositiveInt(fileConfig.requestTimeoutMs, DEFAULT_CONFIG.requestTimeoutMs, 60_000, \"requestTimeoutMs\"),\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 class RemnicHttpError extends Error {\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message);\n }\n}\n\nexport class RemnicClient {\n private requestId = 0;\n\n constructor(private readonly config: RemnicPiConfig) {}\n\n async health(): Promise<Record<string, unknown>> {\n return this.request(\"GET\", \"/engram/v1/health\");\n }\n\n async recall(query: string, sessionKey: string, cwd: string): Promise<RecallResponse> {\n return this.request(\"POST\", \"/engram/v1/recall\", {\n query,\n sessionKey,\n cwd,\n namespace: this.config.namespace,\n topK: this.config.recallTopK,\n mode: this.config.recallMode,\n });\n }\n\n async recallExplain(sessionKey: string): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/engram/v1/recall/explain\", {\n sessionKey,\n namespace: this.config.namespace,\n });\n }\n\n async observe(sessionKey: string, cwd: string, messages: ObserveMessage[]): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/engram/v1/observe\", {\n sessionKey,\n cwd,\n namespace: this.config.namespace,\n skipExtraction: this.config.observeSkipExtraction,\n messages,\n });\n }\n\n async storeMemory(content: string, sessionKey: string): Promise<Record<string, unknown>> {\n return this.request(\"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 });\n }\n\n async lcmSearch(query: string, sessionKey: string, limit = 10): Promise<Record<string, unknown>> {\n return this.request(\"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.request(\"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.request(\"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(): Promise<McpTool[]> {\n const result = await this.mcpRequest(\"tools/list\", {});\n const tools = (result as { tools?: unknown }).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 private async request<T = Record<string, unknown>>(method: string, pathname: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);\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 const payload = text ? JSON.parse(text) : {};\n if (!response.ok) {\n throw new RemnicHttpError(response.status, typeof payload?.error === \"string\" ? payload.error : response.statusText);\n }\n return payload as T;\n } catch (err) {\n if (isAbortError(err)) {\n throw new Error(`Remnic request timed out after ${this.config.requestTimeoutMs}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n private async mcpRequest(method: string, params: Record<string, unknown>): 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 });\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\" && typeof (value as { name?: unknown }).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","import { createHash } from \"node:crypto\";\n\nimport { parsePiMessageParts, type LcmMessagePartInput } from \"@remnic/core\";\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)) 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 toObserveMessage(message: unknown): ObserveMessage | null {\n if (!message || typeof message !== \"object\") return null;\n const obj = message as PiMessage;\n if (isExcludedFromContext(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)) 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\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":["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, 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\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 { state } = getSessionState(ctx, sessionStates);\n restoreObservedState(ctx, state.observedHashes);\n if (!config.statusEnabled) return;\n await setStatus(ctx, client, config);\n });\n\n pi.on(\"context\", async (event, ctx) => {\n if (!config.recallEnabled || !config.authToken) return;\n const recallTarget = latestUserRecallTarget(Array.isArray(event.messages) ? event.messages : []);\n if (!recallTarget) return;\n const { query } = recallTarget;\n const sessionKey = sessionKeyFromContext(ctx);\n const { state } = getSessionState(ctx, sessionStates);\n if (recallTarget.dedupeKey === state.lastInjectedRecallKey) return;\n\n try {\n const recalled = await client.recall(query, sessionKey, ctx.cwd);\n const context = trimContext(recalled.context ?? \"\", config.recallBudgetChars);\n if (!context) return;\n state.lastInjectedRecallKey = recallTarget.dedupeKey;\n return {\n 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 ...event.messages,\n ],\n };\n } catch (err) {\n notify(ctx, `Remnic recall unavailable: ${errorMessage(err)}`, \"warning\");\n }\n });\n\n pi.on(\"message_end\", async (event, ctx) => {\n if (!config.observeEnabled || !isUserMessage(event.message)) return;\n const { state } = getSessionState(ctx, sessionStates);\n await observeMessages(ctx, client, [event.message], state.observedHashes, state.liveObservedReplayKeys);\n });\n\n pi.on(\"turn_end\", async (event, ctx) => {\n if (!config.observeEnabled) return;\n const messages = [event.message, ...(Array.isArray(event.toolResults) ? event.toolResults : [])];\n const { state } = getSessionState(ctx, sessionStates);\n await observeMessages(ctx, client, messages, state.observedHashes, state.liveObservedReplayKeys);\n });\n\n pi.on(\"session_shutdown\", async (_event, ctx) => {\n const { sessionKey, state } = getSessionState(ctx, sessionStates);\n if (config.observeEnabled) {\n const branch = safeBranch(ctx);\n const branchMessages = branchMessagesWithEntryIdentity(branch);\n const unobservedBranchMessages = skipLiveObservedReplayMessages(ctx, branchMessages, state.liveObservedReplayKeys);\n if (unobservedBranchMessages.length > 0) await observeMessages(ctx, client, unobservedBranchMessages, state.observedHashes);\n }\n persistObservedState(pi, state.observedHashes);\n sessionStates.delete(sessionKey);\n });\n\n pi.on(\"session_before_compact\", async (event, ctx) => {\n if (!config.compactionEnabled || !config.authToken) return;\n const sessionKey = sessionKeyFromContext(ctx);\n const preparation = event.preparation ?? {};\n try {\n await client.lcmCompactionFlush(sessionKey);\n } catch (err) {\n notify(ctx, `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(sessionKey, tokensBefore, tokensAfter);\n } catch (err) {\n notify(ctx, `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(sessionKey, summary);\n } catch (err) {\n notify(ctx, `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) => {\n const health = await client.health();\n notify(ctx, `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) => {\n const query = args.trim();\n if (!query) {\n notify(ctx, \"Usage: /remnic-recall <query>\", \"warning\");\n return;\n }\n const result = await client.recall(query, sessionKeyFromContext(ctx), ctx.cwd);\n notify(ctx, 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) => {\n const content = args.trim();\n if (!content) {\n notify(ctx, \"Usage: /remnic-remember <memory>\", \"warning\");\n return;\n }\n await client.storeMemory(content, sessionKeyFromContext(ctx));\n notify(ctx, \"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) => {\n const query = args.trim();\n if (!query) {\n notify(ctx, \"Usage: /remnic-lcm-search <query>\", \"warning\");\n return;\n }\n const result = await client.lcmSearch(query, sessionKeyFromContext(ctx));\n notify(ctx, 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) => {\n const result = await client.recallExplain(sessionKeyFromContext(ctx));\n notify(ctx, 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) => {\n ctx.compact?.();\n notify(ctx, \"Compaction requested\", \"info\");\n }),\n });\n}\n\nfunction commandHandler(handler: (args: string, ctx: any) => Promise<void>): (args: string, ctx: any) => Promise<void> {\n return async (args, ctx) => {\n try {\n await handler(args, ctx);\n } catch (err) {\n notify(ctx, `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();\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 sessionKey = sessionKeyFromContext(ctx);\n const safeParams = stripSessionOwnedRuntimeFields(params ?? {}) as Record<string, unknown>;\n const result = await client.mcpTool(tool.name, {\n ...safeParams,\n sessionKey,\n namespace: config.namespace,\n cwd: ctx.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(ctx: any, states: Map<string, PiSessionState>): { sessionKey: string; state: PiSessionState } {\n const sessionKey = sessionKeyFromContext(ctx);\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 sessionKey = sessionKeyFromContext(ctx);\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, 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 try {\n await client.observe(sessionKey, ctx.cwd, messages);\n for (const hash of pendingHashes) rememberObservedHash(observedHashes, hash);\n if (liveObservedReplayKeys) {\n for (const message of messages) {\n rememberLiveObservedReplayKey(liveObservedReplayKeys, liveReplayKey(message, sessionKey));\n }\n }\n } catch (err) {\n notify(ctx, `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(ctx: any, observedHashes: Set<string>): void {\n for (const entry of safeEntries(ctx)) {\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 ctx: any,\n rawMessages: unknown[],\n liveObservedReplayKeys: Map<string, number>,\n): unknown[] {\n if (liveObservedReplayKeys.size === 0) return rawMessages;\n const sessionKey = sessionKeyFromContext(ctx);\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\nasync function setStatus(ctx: any, client: RemnicClient, config: RemnicPiConfig): Promise<void> {\n try {\n await client.health();\n ctx.ui?.setStatus?.(\"remnic\", `Remnic ${config.namespace ? `(${config.namespace})` : \"ready\"}`);\n } catch {\n ctx.ui?.setStatus?.(\"remnic\", \"Remnic offline\");\n }\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\nfunction notify(ctx: any, message: string, level: \"info\" | \"success\" | \"warning\" | \"error\"): void {\n if (ctx?.hasUI === false) return;\n ctx?.ui?.notify?.(message, level);\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\nimport { expandTildePath } from \"@remnic/core\";\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}\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};\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\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 return expandTildePath(options.configPath || env.REMNIC_PI_CONFIG || defaultConfigPath(env));\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 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: coercePositiveInt(fileConfig.requestTimeoutMs, DEFAULT_CONFIG.requestTimeoutMs, 60_000, \"requestTimeoutMs\"),\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 class RemnicHttpError extends Error {\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message);\n }\n}\n\nexport class RemnicClient {\n private requestId = 0;\n\n constructor(private readonly config: RemnicPiConfig) {}\n\n async health(): Promise<Record<string, unknown>> {\n return this.request(\"GET\", \"/engram/v1/health\");\n }\n\n async recall(query: string, sessionKey: string, cwd: string): Promise<RecallResponse> {\n return this.request(\"POST\", \"/engram/v1/recall\", {\n query,\n sessionKey,\n cwd,\n namespace: this.config.namespace,\n topK: this.config.recallTopK,\n mode: this.config.recallMode,\n });\n }\n\n async recallExplain(sessionKey: string): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/engram/v1/recall/explain\", {\n sessionKey,\n namespace: this.config.namespace,\n });\n }\n\n async observe(sessionKey: string, cwd: string, messages: ObserveMessage[]): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/engram/v1/observe\", {\n sessionKey,\n cwd,\n namespace: this.config.namespace,\n skipExtraction: this.config.observeSkipExtraction,\n messages,\n });\n }\n\n async storeMemory(content: string, sessionKey: string): Promise<Record<string, unknown>> {\n return this.request(\"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 });\n }\n\n async lcmSearch(query: string, sessionKey: string, limit = 10): Promise<Record<string, unknown>> {\n return this.request(\"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.request(\"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.request(\"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(): Promise<McpTool[]> {\n const result = await this.mcpRequest(\"tools/list\", {});\n const tools = (result as { tools?: unknown }).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 private async request<T = Record<string, unknown>>(method: string, pathname: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);\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 throw new RemnicHttpError(response.status, responseErrorMessage(response, text, payload, parseError));\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 ${this.config.requestTimeoutMs}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n private async mcpRequest(method: string, params: Record<string, unknown>): 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 });\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\" && typeof (value as { name?: unknown }).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\nfunction responseErrorMessage(response: Response, text: string, payload: unknown, parseError: unknown): string {\n if (!parseError && payload && typeof payload === \"object\") {\n const error = (payload as { error?: unknown }).error;\n if (typeof error === \"string\" && error.trim().length > 0) {\n return error;\n }\n const message = (payload as { message?: unknown }).message;\n if (typeof message === \"string\" && message.trim().length > 0) {\n return 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","import { createHash } from \"node:crypto\";\n\nimport { parsePiMessageParts, type LcmMessagePartInput } from \"@remnic/core\";\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":["remnicPiExtension"]}
package/dist/publisher.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // openclaw-engram: Local-first memory plugin
2
2
  import {
3
+ resolvePiAgentHome,
3
4
  resolvePiExtensionRoot
4
5
  } from "./chunk-VGPUF4JQ.js";
5
6
 
@@ -54,6 +55,7 @@ var PiMemoryExtensionPublisher = class {
54
55
  }
55
56
  async publish(ctx) {
56
57
  const extensionRoot = await this.resolveExtensionRoot();
58
+ assertSafePiExtensionRoot(extensionRoot, process.env);
57
59
  const filesWritten = [];
58
60
  const skipped = [];
59
61
  ctx.log.info(`Publishing Pi memory extension to ${extensionRoot}`);
@@ -62,34 +64,34 @@ var PiMemoryExtensionPublisher = class {
62
64
  const readmePath = path.join(extensionRoot, "README.md");
63
65
  const rootExisted = fs.existsSync(extensionRoot);
64
66
  const snapshots = snapshotFiles([configPath, wrapperPath, readmePath]);
65
- const priorConfig = readPriorConfig(configPath);
66
67
  const priorTokenEntry = ctx.rollbackTokenEntry === void 0 ? snapshotPiTokenEntry() : cloneTokenEntry(ctx.rollbackTokenEntry);
67
68
  const token = getConnectorToken("pi");
68
69
  if (!token) {
69
70
  skipped.push("auth token unavailable; run `remnic token generate pi` and reinstall the connector");
70
71
  }
71
- const config = {
72
- recallMode: "auto",
73
- recallTopK: 8,
74
- recallBudgetChars: 12e3,
75
- recallEnabled: true,
76
- observeEnabled: true,
77
- observeSkipExtraction: false,
78
- compactionEnabled: true,
79
- mcpToolsEnabled: true,
80
- statusEnabled: true,
81
- requestTimeoutMs: 6e4,
82
- ...priorConfig,
83
- remnicDaemonUrl: resolveDaemonUrl(ctx)
84
- };
85
- if (token) {
86
- config.authToken = token;
87
- }
88
- if (ctx.config.namespace) {
89
- config.namespace = ctx.config.namespace;
90
- }
91
72
  try {
92
- fs.mkdirSync(extensionRoot, { recursive: true });
73
+ const priorConfig = readPriorConfig(configPath);
74
+ const config = {
75
+ recallMode: "auto",
76
+ recallTopK: 8,
77
+ recallBudgetChars: 12e3,
78
+ recallEnabled: true,
79
+ observeEnabled: true,
80
+ observeSkipExtraction: false,
81
+ compactionEnabled: true,
82
+ mcpToolsEnabled: true,
83
+ statusEnabled: true,
84
+ requestTimeoutMs: 6e4,
85
+ ...priorConfig,
86
+ remnicDaemonUrl: resolveDaemonUrl(ctx)
87
+ };
88
+ if (token) {
89
+ config.authToken = token;
90
+ }
91
+ if (ctx.config.namespace) {
92
+ config.namespace = ctx.config.namespace;
93
+ }
94
+ mkdirPiExtensionRoot(extensionRoot, process.env);
93
95
  atomicWriteFile(configPath, `${JSON.stringify(config, null, 2)}
94
96
  `, 384);
95
97
  filesWritten.push(configPath);
@@ -120,6 +122,7 @@ var PiMemoryExtensionPublisher = class {
120
122
  }
121
123
  async unpublish() {
122
124
  const extensionRoot = await this.resolveExtensionRoot();
125
+ assertSafePiExtensionRoot(extensionRoot, process.env);
123
126
  if (fs.existsSync(extensionRoot)) {
124
127
  fs.rmSync(extensionRoot, { recursive: true, force: true });
125
128
  }
@@ -154,6 +157,7 @@ function renderWrapper(extensionModulePath, configPath) {
154
157
  ].join("\n");
155
158
  }
156
159
  function atomicWriteFile(filePath, content, mode) {
160
+ rejectSymlinkPath(filePath);
157
161
  const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
158
162
  try {
159
163
  fs.writeFileSync(tmpPath, content, { encoding: "utf-8", mode });
@@ -173,7 +177,10 @@ function atomicWriteFile(filePath, content, mode) {
173
177
  function snapshotFiles(paths) {
174
178
  return paths.map((filePath) => {
175
179
  if (!fs.existsSync(filePath)) return { path: filePath, existed: false };
176
- const stat = fs.statSync(filePath);
180
+ const stat = fs.lstatSync(filePath);
181
+ if (stat.isSymbolicLink()) {
182
+ throw new Error(`Pi extension path must not be a symlink: ${filePath}`);
183
+ }
177
184
  if (!stat.isFile()) return { path: filePath, existed: false };
178
185
  return {
179
186
  path: filePath,
@@ -185,11 +192,13 @@ function snapshotFiles(paths) {
185
192
  }
186
193
  function restorePublishSnapshot(extensionRoot, rootExisted, snapshots) {
187
194
  if (!rootExisted) {
195
+ assertSafeExistingPath(extensionRoot);
188
196
  fs.rmSync(extensionRoot, { recursive: true, force: true });
189
197
  return;
190
198
  }
191
199
  for (const snapshot of snapshots) {
192
200
  if (!snapshot.existed) {
201
+ assertSafeExistingPath(snapshot.path);
193
202
  fs.rmSync(snapshot.path, { force: true });
194
203
  continue;
195
204
  }
@@ -203,6 +212,44 @@ function restorePublishSnapshot(extensionRoot, rootExisted, snapshots) {
203
212
  }
204
213
  }
205
214
  }
215
+ function mkdirPiExtensionRoot(extensionRoot, env) {
216
+ const extensionsDir = path.join(resolvePiAgentHome(env), "extensions");
217
+ assertSafePiExtensionRoot(extensionRoot, env);
218
+ fs.mkdirSync(extensionsDir, { recursive: true });
219
+ rejectSymlinkPath(extensionsDir);
220
+ fs.mkdirSync(extensionRoot, { recursive: true });
221
+ rejectSymlinkPath(extensionRoot);
222
+ }
223
+ function assertSafePiExtensionRoot(extensionRoot, env) {
224
+ const expected = path.join(resolvePiAgentHome(env), "extensions", "remnic");
225
+ if (path.resolve(extensionRoot) !== path.resolve(expected)) {
226
+ throw new Error(`Pi extension root is outside the configured Pi extensions directory: ${extensionRoot}`);
227
+ }
228
+ const agentHome = path.resolve(resolvePiAgentHome(env));
229
+ const extensionsDir = path.join(agentHome, "extensions");
230
+ assertPathContained(agentHome, extensionsDir);
231
+ assertPathContained(extensionsDir, extensionRoot);
232
+ rejectSymlinkPath(agentHome);
233
+ if (fs.existsSync(extensionsDir)) rejectSymlinkPath(extensionsDir);
234
+ if (fs.existsSync(extensionRoot)) rejectSymlinkPath(extensionRoot);
235
+ }
236
+ function assertSafeExistingPath(filePath) {
237
+ if (fs.existsSync(filePath)) rejectSymlinkPath(filePath);
238
+ }
239
+ function rejectSymlinkPath(filePath) {
240
+ if (!fs.existsSync(filePath)) return;
241
+ const stat = fs.lstatSync(filePath);
242
+ if (stat.isSymbolicLink()) {
243
+ throw new Error(`Pi extension path must not be a symlink: ${filePath}`);
244
+ }
245
+ }
246
+ function assertPathContained(root, candidate) {
247
+ const rootResolved = path.resolve(root);
248
+ const candidateResolved = path.resolve(candidate);
249
+ const relative = path.relative(rootResolved, candidateResolved);
250
+ if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) return;
251
+ throw new Error(`Pi extension path escapes allowed root: ${candidate}`);
252
+ }
206
253
  function readPriorConfig(configPath) {
207
254
  if (!fs.existsSync(configPath)) return {};
208
255
  try {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/publisher.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nimport {\n getConnectorToken,\n loadTokenStore,\n saveTokenStore,\n type MemoryExtensionPublisher,\n type PublishContext,\n type PublishResult,\n type PublisherCapabilities,\n type TokenEntry,\n} from \"@remnic/core\";\n\nimport { resolvePiExtensionRoot } from \"./paths.js\";\n\nconst DEFAULT_DAEMON_PORT = 4318;\n\ntype FileSnapshot = {\n path: string;\n existed: boolean;\n content?: Buffer;\n mode?: number;\n};\n\nexport class PiMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"pi\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n async resolveExtensionRoot(env?: NodeJS.ProcessEnv): Promise<string> {\n return resolvePiExtensionRoot(env ?? process.env);\n }\n\n async isHostAvailable(): Promise<boolean> {\n // Pi auto-discovers extensions from ~/.pi/agent/extensions. The directory can\n // be created before Pi has been launched, so availability should not block\n // 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 Pi\",\n \"\",\n \"Remnic provides memory, retrieval, observation, MCP tools, and long-context compaction coordination for Pi Coding Agent.\",\n \"\",\n \"## Installed Capabilities\",\n \"\",\n \"- Recall relevant Remnic context in Pi's `context` hook before agent turns.\",\n \"- Observe Pi user, assistant, and tool messages with `sourceFormat: \\\"pi\\\"`.\",\n \"- Coordinate Pi `session_before_compact` with Remnic LCM flush and checkpoint recording.\",\n \"- Register Remnic MCP tools as Pi tools when daemon authentication is configured.\",\n \"- Persist lightweight dedupe state in Pi 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 filesWritten: string[] = [];\n const skipped: string[] = [];\n\n ctx.log.info(`Publishing Pi memory extension to ${extensionRoot}`);\n\n const configPath = path.join(extensionRoot, \"remnic.config.json\");\n const wrapperPath = path.join(extensionRoot, \"index.ts\");\n const readmePath = path.join(extensionRoot, \"README.md\");\n const rootExisted = fs.existsSync(extensionRoot);\n const snapshots = snapshotFiles([configPath, wrapperPath, readmePath]);\n const priorConfig = readPriorConfig(configPath);\n const priorTokenEntry =\n ctx.rollbackTokenEntry === undefined ? snapshotPiTokenEntry() : cloneTokenEntry(ctx.rollbackTokenEntry);\n\n const token = getConnectorToken(\"pi\");\n if (!token) {\n skipped.push(\"auth token unavailable; run `remnic token generate pi` and reinstall the connector\");\n }\n\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 ...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 try {\n fs.mkdirSync(extensionRoot, { recursive: true });\n\n atomicWriteFile(configPath, `${JSON.stringify(config, null, 2)}\\n`, 0o600);\n filesWritten.push(configPath);\n\n atomicWriteFile(wrapperPath, renderWrapper(resolveExtensionModulePath(), configPath), 0o644);\n filesWritten.push(wrapperPath);\n\n atomicWriteFile(readmePath, `${await this.renderInstructions(ctx)}\\n`, 0o644);\n filesWritten.push(readmePath);\n } catch (err) {\n try {\n restorePublishSnapshot(extensionRoot, rootExisted, snapshots);\n } catch (restoreErr) {\n ctx.log.warn(`Pi extension rollback failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);\n }\n try {\n restorePiTokenEntry(priorTokenEntry);\n } catch (tokenErr) {\n ctx.log.warn(`Pi connector token rollback failed: ${tokenErr instanceof Error ? tokenErr.message : String(tokenErr)}`);\n }\n throw err;\n }\n\n return {\n hostId: this.hostId,\n extensionRoot,\n filesWritten,\n skipped,\n };\n }\n\n async unpublish(): Promise<void> {\n const extensionRoot = await this.resolveExtensionRoot();\n if (fs.existsSync(extensionRoot)) {\n fs.rmSync(extensionRoot, { recursive: true, force: true });\n }\n }\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\nfunction renderWrapper(extensionModulePath: string, configPath: string): string {\n const moduleUrl = pathToFileURL(extensionModulePath).href;\n return [\n `import { createRemnicPiExtension } from ${JSON.stringify(moduleUrl)};`,\n \"\",\n `export default createRemnicPiExtension({ configPath: ${JSON.stringify(configPath)} });`,\n \"\",\n ].join(\"\\n\");\n}\n\nfunction atomicWriteFile(filePath: string, content: string, mode: number): void {\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.statSync(filePath);\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) {\n fs.rmSync(extensionRoot, { recursive: true, force: true });\n return;\n }\n\n for (const snapshot of snapshots) {\n if (!snapshot.existed) {\n fs.rmSync(snapshot.path, { force: true });\n continue;\n }\n fs.mkdirSync(path.dirname(snapshot.path), { recursive: true });\n fs.writeFileSync(snapshot.path, snapshot.content ?? Buffer.alloc(0), { mode: snapshot.mode });\n if (snapshot.mode !== undefined) {\n try {\n fs.chmodSync(snapshot.path, snapshot.mode);\n } catch {\n // Best effort for platforms that do not support chmod.\n }\n }\n }\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 snapshotPiTokenEntry(): TokenEntry | null {\n const entry = loadTokenStore().tokens.find((candidate) => candidate.connector === \"pi\");\n return cloneTokenEntry(entry ?? null);\n}\n\nfunction cloneTokenEntry(entry: TokenEntry | null): TokenEntry | null {\n return entry ? { ...entry } : null;\n}\n\nfunction restorePiTokenEntry(priorEntry: TokenEntry | null): void {\n const store = loadTokenStore();\n store.tokens = store.tokens.filter((entry) => entry.connector !== \"pi\");\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 path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nimport {\n getConnectorToken,\n loadTokenStore,\n saveTokenStore,\n type MemoryExtensionPublisher,\n type PublishContext,\n type PublishResult,\n type PublisherCapabilities,\n type TokenEntry,\n} from \"@remnic/core\";\n\nimport { resolvePiAgentHome, resolvePiExtensionRoot } from \"./paths.js\";\n\nconst DEFAULT_DAEMON_PORT = 4318;\n\ntype FileSnapshot = {\n path: string;\n existed: boolean;\n content?: Buffer;\n mode?: number;\n};\n\nexport class PiMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"pi\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n async resolveExtensionRoot(env?: NodeJS.ProcessEnv): Promise<string> {\n return resolvePiExtensionRoot(env ?? process.env);\n }\n\n async isHostAvailable(): Promise<boolean> {\n // Pi auto-discovers extensions from ~/.pi/agent/extensions. The directory can\n // be created before Pi has been launched, so availability should not block\n // 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 Pi\",\n \"\",\n \"Remnic provides memory, retrieval, observation, MCP tools, and long-context compaction coordination for Pi Coding Agent.\",\n \"\",\n \"## Installed Capabilities\",\n \"\",\n \"- Recall relevant Remnic context in Pi's `context` hook before agent turns.\",\n \"- Observe Pi user, assistant, and tool messages with `sourceFormat: \\\"pi\\\"`.\",\n \"- Coordinate Pi `session_before_compact` with Remnic LCM flush and checkpoint recording.\",\n \"- Register Remnic MCP tools as Pi tools when daemon authentication is configured.\",\n \"- Persist lightweight dedupe state in Pi 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 assertSafePiExtensionRoot(extensionRoot, process.env);\n const filesWritten: string[] = [];\n const skipped: string[] = [];\n\n ctx.log.info(`Publishing Pi memory extension to ${extensionRoot}`);\n\n const configPath = path.join(extensionRoot, \"remnic.config.json\");\n const wrapperPath = path.join(extensionRoot, \"index.ts\");\n const readmePath = path.join(extensionRoot, \"README.md\");\n const rootExisted = fs.existsSync(extensionRoot);\n const snapshots = snapshotFiles([configPath, wrapperPath, readmePath]);\n const priorTokenEntry =\n ctx.rollbackTokenEntry === undefined ? snapshotPiTokenEntry() : cloneTokenEntry(ctx.rollbackTokenEntry);\n\n const token = getConnectorToken(\"pi\");\n if (!token) {\n skipped.push(\"auth token unavailable; run `remnic token generate pi` and reinstall the connector\");\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 ...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 mkdirPiExtensionRoot(extensionRoot, process.env);\n\n atomicWriteFile(configPath, `${JSON.stringify(config, null, 2)}\\n`, 0o600);\n filesWritten.push(configPath);\n\n atomicWriteFile(wrapperPath, renderWrapper(resolveExtensionModulePath(), configPath), 0o644);\n filesWritten.push(wrapperPath);\n\n atomicWriteFile(readmePath, `${await this.renderInstructions(ctx)}\\n`, 0o644);\n filesWritten.push(readmePath);\n } catch (err) {\n try {\n restorePublishSnapshot(extensionRoot, rootExisted, snapshots);\n } catch (restoreErr) {\n ctx.log.warn(`Pi extension rollback failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);\n }\n try {\n restorePiTokenEntry(priorTokenEntry);\n } catch (tokenErr) {\n ctx.log.warn(`Pi connector token rollback failed: ${tokenErr instanceof Error ? tokenErr.message : String(tokenErr)}`);\n }\n throw err;\n }\n\n return {\n hostId: this.hostId,\n extensionRoot,\n filesWritten,\n skipped,\n };\n }\n\n async unpublish(): Promise<void> {\n const extensionRoot = await this.resolveExtensionRoot();\n assertSafePiExtensionRoot(extensionRoot, process.env);\n if (fs.existsSync(extensionRoot)) {\n fs.rmSync(extensionRoot, { recursive: true, force: true });\n }\n }\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\nfunction renderWrapper(extensionModulePath: string, configPath: string): string {\n const moduleUrl = pathToFileURL(extensionModulePath).href;\n return [\n `import { createRemnicPiExtension } from ${JSON.stringify(moduleUrl)};`,\n \"\",\n `export default createRemnicPiExtension({ configPath: ${JSON.stringify(configPath)} });`,\n \"\",\n ].join(\"\\n\");\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(`Pi 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) {\n assertSafeExistingPath(extensionRoot);\n fs.rmSync(extensionRoot, { recursive: true, force: true });\n return;\n }\n\n for (const snapshot of snapshots) {\n if (!snapshot.existed) {\n assertSafeExistingPath(snapshot.path);\n fs.rmSync(snapshot.path, { force: true });\n continue;\n }\n fs.mkdirSync(path.dirname(snapshot.path), { recursive: true });\n fs.writeFileSync(snapshot.path, snapshot.content ?? Buffer.alloc(0), { mode: snapshot.mode });\n if (snapshot.mode !== undefined) {\n try {\n fs.chmodSync(snapshot.path, snapshot.mode);\n } catch {\n // Best effort for platforms that do not support chmod.\n }\n }\n }\n}\n\nfunction mkdirPiExtensionRoot(extensionRoot: string, env: NodeJS.ProcessEnv): void {\n const extensionsDir = path.join(resolvePiAgentHome(env), \"extensions\");\n assertSafePiExtensionRoot(extensionRoot, env);\n fs.mkdirSync(extensionsDir, { recursive: true });\n rejectSymlinkPath(extensionsDir);\n fs.mkdirSync(extensionRoot, { recursive: true });\n rejectSymlinkPath(extensionRoot);\n}\n\nfunction assertSafePiExtensionRoot(extensionRoot: string, env: NodeJS.ProcessEnv): void {\n const expected = path.join(resolvePiAgentHome(env), \"extensions\", \"remnic\");\n if (path.resolve(extensionRoot) !== path.resolve(expected)) {\n throw new Error(`Pi extension root is outside the configured Pi extensions directory: ${extensionRoot}`);\n }\n const agentHome = path.resolve(resolvePiAgentHome(env));\n const extensionsDir = path.join(agentHome, \"extensions\");\n assertPathContained(agentHome, extensionsDir);\n assertPathContained(extensionsDir, extensionRoot);\n rejectSymlinkPath(agentHome);\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(`Pi 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(`Pi 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 snapshotPiTokenEntry(): TokenEntry | null {\n const entry = loadTokenStore().tokens.find((candidate) => candidate.connector === \"pi\");\n return cloneTokenEntry(entry ?? null);\n}\n\nfunction cloneTokenEntry(entry: TokenEntry | null): TokenEntry | null {\n return entry ? { ...entry } : null;\n}\n\nfunction restorePiTokenEntry(priorEntry: TokenEntry | null): void {\n const store = loadTokenStore();\n store.tokens = store.tokens.filter((entry) => entry.connector !== \"pi\");\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": "1.0.9",
3
+ "version": "9.3.515",
4
4
  "description": "Remnic memory extension for Pi Coding Agent",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@sinclair/typebox": "^0.34.0",
41
- "@remnic/core": "^1.1.29"
41
+ "@remnic/core": "^9.3.515"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@earendil-works/pi-coding-agent": "*"