@remnic/plugin-pi 9.3.577 → 9.3.578

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/publisher.js CHANGED
@@ -13,6 +13,8 @@ import {
13
13
  saveTokenStore
14
14
  } from "@remnic/core";
15
15
  var DEFAULT_DAEMON_PORT = 4318;
16
+ var PI_EXTENSION_OWNED_FILES = ["remnic.config.json", "index.ts", "README.md"];
17
+ var PI_EXTENSION_OWNED_TEMP_FILE_PATTERN = /^(?:remnic\.config\.json|index\.ts|README\.md)\.tmp-\d+-\d+$/u;
16
18
  var PiMemoryExtensionPublisher = class {
17
19
  hostId = "pi";
18
20
  static capabilities = {
@@ -58,9 +60,7 @@ var PiMemoryExtensionPublisher = class {
58
60
  const filesWritten = [];
59
61
  const skipped = [];
60
62
  ctx.log.info(`Publishing Pi memory extension to ${extensionRoot}`);
61
- const configPath = path.join(extensionRoot, "remnic.config.json");
62
- const wrapperPath = path.join(extensionRoot, "index.ts");
63
- const readmePath = path.join(extensionRoot, "README.md");
63
+ const [configPath, wrapperPath, readmePath] = piExtensionOwnedPaths(extensionRoot);
64
64
  const rootExisted = fs.existsSync(extensionRoot);
65
65
  const snapshots = snapshotFiles([configPath, wrapperPath, readmePath]);
66
66
  const priorTokenEntry = ctx.rollbackTokenEntry === void 0 ? snapshotPiTokenEntry() : cloneTokenEntry(ctx.rollbackTokenEntry);
@@ -126,11 +126,28 @@ var PiMemoryExtensionPublisher = class {
126
126
  async unpublish() {
127
127
  const extensionRoot = await this.resolveExtensionRoot();
128
128
  assertSafePiExtensionRoot(extensionRoot, process.env);
129
- if (fs.existsSync(extensionRoot)) {
130
- fs.rmSync(extensionRoot, { recursive: true, force: true });
129
+ if (!fs.existsSync(extensionRoot)) {
130
+ return;
131
131
  }
132
+ const removableFiles = removableOwnedExtensionFiles(piExtensionOwnedUnpublishPaths(extensionRoot));
133
+ for (const filePath of removableFiles) {
134
+ fs.rmSync(filePath, { force: true });
135
+ }
136
+ removeEmptyDirectory(extensionRoot);
132
137
  }
133
138
  };
139
+ function piExtensionOwnedPaths(extensionRoot) {
140
+ return PI_EXTENSION_OWNED_FILES.map((fileName) => path.join(extensionRoot, fileName));
141
+ }
142
+ function piExtensionOwnedUnpublishPaths(extensionRoot) {
143
+ const ownedPaths = piExtensionOwnedPaths(extensionRoot);
144
+ for (const fileName of fs.readdirSync(extensionRoot)) {
145
+ if (PI_EXTENSION_OWNED_TEMP_FILE_PATTERN.test(fileName)) {
146
+ ownedPaths.push(path.join(extensionRoot, fileName));
147
+ }
148
+ }
149
+ return ownedPaths;
150
+ }
134
151
  function resolveDaemonUrl(ctx) {
135
152
  if (ctx.config.daemonUrl && ctx.config.daemonUrl.trim().length > 0) {
136
153
  return trimTrailingSlashes(ctx.config.daemonUrl.trim());
@@ -242,6 +259,28 @@ function removeEmptyDirectory(dirPath) {
242
259
  if (fs.readdirSync(dirPath).length > 0) return;
243
260
  fs.rmdirSync(dirPath);
244
261
  }
262
+ function removableOwnedExtensionFiles(filePaths) {
263
+ const removableFiles = [];
264
+ for (const filePath of filePaths) {
265
+ const stat = statOwnedExtensionPath(filePath);
266
+ if (stat === null) continue;
267
+ if (stat.isSymbolicLink()) {
268
+ throw new Error(`Pi extension path must not be a symlink: ${filePath}`);
269
+ }
270
+ if (stat.isFile()) removableFiles.push(filePath);
271
+ }
272
+ return removableFiles;
273
+ }
274
+ function statOwnedExtensionPath(filePath) {
275
+ let stat;
276
+ try {
277
+ stat = fs.lstatSync(filePath);
278
+ } catch (err) {
279
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
280
+ throw err;
281
+ }
282
+ return stat;
283
+ }
245
284
  function mkdirPiExtensionRoot(extensionRoot, env) {
246
285
  const extensionsDir = path.join(resolvePiAgentHome(env), "extensions");
247
286
  assertSafePiExtensionRoot(extensionRoot, env);
@@ -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 type MemoryExtensionPublisher,\n type PublishContext,\n type PublishResult,\n type PublisherCapabilities,\n type TokenEntry,\n getConnectorToken,\n loadTokenStore,\n saveTokenStore,\n} from \"@remnic/core\";\n\nimport { 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(\n `Pi extension rollback failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`\n );\n }\n try {\n restorePiTokenEntry(priorTokenEntry);\n } catch (tokenErr) {\n ctx.log.warn(\n `Pi connector token rollback failed: ${tokenErr instanceof Error ? tokenErr.message : String(tokenErr)}`\n );\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 && !canCleanNewExtensionRoot(extensionRoot)) return;\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 if (!rootExisted) {\n removeEmptyDirectory(extensionRoot);\n }\n}\n\nfunction canCleanNewExtensionRoot(extensionRoot: string): boolean {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(extensionRoot);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return false;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Pi extension path must not be a symlink: ${extensionRoot}`);\n }\n return stat.isDirectory();\n}\n\nfunction removeEmptyDirectory(dirPath: string): void {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(dirPath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Pi extension path must not be a symlink: ${dirPath}`);\n }\n if (!stat.isDirectory()) return;\n if (fs.readdirSync(dirPath).length > 0) return;\n fs.rmdirSync(dirPath);\n}\n\nfunction 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":[]}
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 type MemoryExtensionPublisher,\n type PublishContext,\n type PublishResult,\n type PublisherCapabilities,\n type TokenEntry,\n getConnectorToken,\n loadTokenStore,\n saveTokenStore,\n} from \"@remnic/core\";\n\nimport { resolvePiAgentHome, resolvePiExtensionRoot } from \"./paths.js\";\n\nconst DEFAULT_DAEMON_PORT = 4318;\nconst PI_EXTENSION_OWNED_FILES = [\"remnic.config.json\", \"index.ts\", \"README.md\"] as const;\nconst PI_EXTENSION_OWNED_TEMP_FILE_PATTERN = /^(?:remnic\\.config\\.json|index\\.ts|README\\.md)\\.tmp-\\d+-\\d+$/u;\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, wrapperPath, readmePath] = piExtensionOwnedPaths(extensionRoot);\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(\n `Pi extension rollback failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`\n );\n }\n try {\n restorePiTokenEntry(priorTokenEntry);\n } catch (tokenErr) {\n ctx.log.warn(\n `Pi connector token rollback failed: ${tokenErr instanceof Error ? tokenErr.message : String(tokenErr)}`\n );\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 return;\n }\n\n const removableFiles = removableOwnedExtensionFiles(piExtensionOwnedUnpublishPaths(extensionRoot));\n for (const filePath of removableFiles) {\n fs.rmSync(filePath, { force: true });\n }\n removeEmptyDirectory(extensionRoot);\n }\n}\n\nfunction piExtensionOwnedPaths(extensionRoot: string): string[] {\n return PI_EXTENSION_OWNED_FILES.map((fileName) => path.join(extensionRoot, fileName));\n}\n\nfunction piExtensionOwnedUnpublishPaths(extensionRoot: string): string[] {\n const ownedPaths = piExtensionOwnedPaths(extensionRoot);\n for (const fileName of fs.readdirSync(extensionRoot)) {\n if (PI_EXTENSION_OWNED_TEMP_FILE_PATTERN.test(fileName)) {\n ownedPaths.push(path.join(extensionRoot, fileName));\n }\n }\n return ownedPaths;\n}\n\nfunction resolveDaemonUrl(ctx: PublishContext): string {\n if (ctx.config.daemonUrl && ctx.config.daemonUrl.trim().length > 0) {\n return trimTrailingSlashes(ctx.config.daemonUrl.trim());\n }\n return `http://127.0.0.1:${ctx.config.daemonPort ?? DEFAULT_DAEMON_PORT}`;\n}\n\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;\n return value.slice(0, end);\n}\n\nfunction resolveExtensionModulePath(): string {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const built = path.join(moduleDir, \"index.js\");\n if (fs.existsSync(built)) return built;\n\n const source = path.join(moduleDir, \"index.ts\");\n if (fs.existsSync(source)) return source;\n\n return built;\n}\n\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 && !canCleanNewExtensionRoot(extensionRoot)) return;\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 if (!rootExisted) {\n removeEmptyDirectory(extensionRoot);\n }\n}\n\nfunction canCleanNewExtensionRoot(extensionRoot: string): boolean {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(extensionRoot);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return false;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Pi extension path must not be a symlink: ${extensionRoot}`);\n }\n return stat.isDirectory();\n}\n\nfunction removeEmptyDirectory(dirPath: string): void {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(dirPath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return;\n throw err;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Pi extension path must not be a symlink: ${dirPath}`);\n }\n if (!stat.isDirectory()) return;\n if (fs.readdirSync(dirPath).length > 0) return;\n fs.rmdirSync(dirPath);\n}\n\nfunction removableOwnedExtensionFiles(filePaths: string[]): string[] {\n const removableFiles: string[] = [];\n for (const filePath of filePaths) {\n const stat = statOwnedExtensionPath(filePath);\n if (stat === null) continue;\n if (stat.isSymbolicLink()) {\n throw new Error(`Pi extension path must not be a symlink: ${filePath}`);\n }\n if (stat.isFile()) removableFiles.push(filePath);\n }\n return removableFiles;\n}\n\nfunction statOwnedExtensionPath(filePath: string): fs.Stats | null {\n let stat: fs.Stats;\n try {\n stat = fs.lstatSync(filePath);\n } catch (err) {\n if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") return null;\n throw err;\n }\n return stat;\n}\n\nfunction 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": "9.3.577",
3
+ "version": "9.3.578",
4
4
  "description": "Remnic memory extension for Pi Coding Agent",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@sinclair/typebox": "^0.34.0",
45
- "@remnic/core": "^9.3.577"
45
+ "@remnic/core": "^9.3.578"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@earendil-works/pi-coding-agent": "*"