@remnic/plugin-pi 9.36.0 → 9.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/chunk-HRZBFDYV.js +262 -0
- package/dist/chunk-HRZBFDYV.js.map +1 -0
- package/dist/index.d.ts +17 -3
- package/dist/index.js +257 -261
- package/dist/index.js.map +1 -1
- package/dist/publisher.js +5 -2
- package/dist/publisher.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-ASGQGBO2.js +0 -51
- package/dist/chunk-ASGQGBO2.js.map +0 -1
package/README.md
CHANGED
|
@@ -87,6 +87,8 @@ Supported config keys:
|
|
|
87
87
|
| `statusEnabled` | `true` | Set Pi UI status from daemon health |
|
|
88
88
|
| `requestTimeoutMs` | `60000` | HTTP/MCP request timeout for recall/observe/compaction/commands |
|
|
89
89
|
| `startupRequestTimeoutMs` | `1000` | Shorter timeout for startup-sensitive probes (MCP `tools/list` registration and the `session_start` health/status check) so a slow or offline daemon can't stall Pi boot |
|
|
90
|
+
| `recallTimeoutThreshold` | `7` | Permanently disable recall after this many explicit recall timeouts within the rolling window; must be a positive integer no greater than `recallTimeoutWindow` |
|
|
91
|
+
| `recallTimeoutWindow` | `10` | Number of most recent recall calls included in the rolling timeout window; must be a positive integer at least `recallTimeoutThreshold`. Invalid settings fail config loading. |
|
|
90
92
|
|
|
91
93
|
Boolean-like strings such as `"false"`, `"0"`, `"no"`, and `"off"` are treated as false.
|
|
92
94
|
|
|
@@ -107,7 +109,9 @@ Example:
|
|
|
107
109
|
"mcpToolsEnabled": true,
|
|
108
110
|
"statusEnabled": true,
|
|
109
111
|
"requestTimeoutMs": 60000,
|
|
110
|
-
"startupRequestTimeoutMs": 1000
|
|
112
|
+
"startupRequestTimeoutMs": 1000,
|
|
113
|
+
"recallTimeoutThreshold": 7,
|
|
114
|
+
"recallTimeoutWindow": 10
|
|
111
115
|
}
|
|
112
116
|
```
|
|
113
117
|
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// src/paths.ts
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { expandTildePath } from "@remnic/core/utils/path";
|
|
5
|
+
var REMNIC_PI_EXTENSION_DIR_NAME = "remnic";
|
|
6
|
+
function resolvePiAgentHome(env) {
|
|
7
|
+
const explicitCodingAgentDir = env.PI_CODING_AGENT_DIR?.trim();
|
|
8
|
+
if (explicitCodingAgentDir) return path.resolve(expandTildePath(explicitCodingAgentDir));
|
|
9
|
+
const explicitAgentHome = env.PI_AGENT_HOME?.trim();
|
|
10
|
+
if (explicitAgentHome) return path.resolve(expandTildePath(explicitAgentHome));
|
|
11
|
+
const explicitPiHome = env.PI_HOME?.trim();
|
|
12
|
+
if (explicitPiHome) return path.join(path.resolve(expandTildePath(explicitPiHome)), "agent");
|
|
13
|
+
return path.join(env.HOME ?? env.USERPROFILE ?? os.homedir(), ".pi", "agent");
|
|
14
|
+
}
|
|
15
|
+
function resolvePiExtensionRoot(env) {
|
|
16
|
+
return path.join(resolvePiAgentHome(env), "extensions", REMNIC_PI_EXTENSION_DIR_NAME);
|
|
17
|
+
}
|
|
18
|
+
function resolveOmpProfile(env) {
|
|
19
|
+
const raw = env.OMP_PROFILE !== void 0 ? env.OMP_PROFILE : env.PI_PROFILE;
|
|
20
|
+
const trimmed = raw?.trim();
|
|
21
|
+
if (!trimmed || trimmed === "default") return void 0;
|
|
22
|
+
return trimmed;
|
|
23
|
+
}
|
|
24
|
+
function resolveOmpConfigRoot(env) {
|
|
25
|
+
const home = env.HOME ?? env.USERPROFILE ?? os.homedir();
|
|
26
|
+
const configDirName = env.PI_CONFIG_DIR?.trim() || ".omp";
|
|
27
|
+
return path.join(home, configDirName);
|
|
28
|
+
}
|
|
29
|
+
function resolveOmpAgentHome(env) {
|
|
30
|
+
const configRoot = resolveOmpConfigRoot(env);
|
|
31
|
+
const profile = resolveOmpProfile(env);
|
|
32
|
+
if (profile) {
|
|
33
|
+
return path.join(configRoot, "profiles", profile, "agent");
|
|
34
|
+
}
|
|
35
|
+
const explicitCodingAgentDir = env.PI_CODING_AGENT_DIR?.trim();
|
|
36
|
+
if (explicitCodingAgentDir) return path.resolve(expandTildePath(explicitCodingAgentDir));
|
|
37
|
+
return path.join(configRoot, "agent");
|
|
38
|
+
}
|
|
39
|
+
function resolveOmpExtensionRoot(env) {
|
|
40
|
+
return path.join(resolveOmpAgentHome(env), "extensions", REMNIC_PI_EXTENSION_DIR_NAME);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/config.ts
|
|
44
|
+
import { existsSync, readFileSync } from "fs";
|
|
45
|
+
import path2 from "path";
|
|
46
|
+
import { expandTildePath as expandTildePath2 } from "@remnic/core/utils/path";
|
|
47
|
+
var DEFAULT_CONFIG = {
|
|
48
|
+
remnicDaemonUrl: "http://127.0.0.1:4318",
|
|
49
|
+
recallMode: "auto",
|
|
50
|
+
recallTopK: 8,
|
|
51
|
+
recallBudgetChars: 12e3,
|
|
52
|
+
recallEnabled: true,
|
|
53
|
+
observeEnabled: true,
|
|
54
|
+
observeSkipExtraction: false,
|
|
55
|
+
compactionEnabled: true,
|
|
56
|
+
mcpToolsEnabled: true,
|
|
57
|
+
statusEnabled: true,
|
|
58
|
+
requestTimeoutMs: 6e4,
|
|
59
|
+
startupRequestTimeoutMs: 1e3,
|
|
60
|
+
// Default 20 s is comfortably under the Pi/omp 30 s handler budget (#1626).
|
|
61
|
+
turnRequestTimeoutMs: 2e4,
|
|
62
|
+
// Default 100 KiB leaves headroom under the daemon's 128 KiB default (#1600).
|
|
63
|
+
observeMaxBytes: 102400,
|
|
64
|
+
observeMaxRetries: 2,
|
|
65
|
+
// Base cooldown for the circuit breaker; doubles on consecutive failures (#1626).
|
|
66
|
+
daemonCooldownMs: 5e3,
|
|
67
|
+
// Recall-timeout circuit breaker: 7 timeouts in the last 10 recall calls trip permanently.
|
|
68
|
+
recallTimeoutThreshold: 7,
|
|
69
|
+
recallTimeoutWindow: 10
|
|
70
|
+
};
|
|
71
|
+
function defaultConfigPath(env) {
|
|
72
|
+
return path2.join(resolvePiAgentHome(env), "extensions", REMNIC_PI_EXTENSION_DIR_NAME, "remnic.config.json");
|
|
73
|
+
}
|
|
74
|
+
function coerceBoolean(value, fallback, fieldName) {
|
|
75
|
+
if (value === void 0 || value === null) return fallback;
|
|
76
|
+
if (typeof value === "boolean") return value;
|
|
77
|
+
if (typeof value === "string") {
|
|
78
|
+
const normalized = value.trim().toLowerCase();
|
|
79
|
+
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
80
|
+
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Invalid boolean value for Remnic Pi config field ${fieldName}`);
|
|
83
|
+
}
|
|
84
|
+
function coercePositiveInt(value, fallback, max, fieldName) {
|
|
85
|
+
if (value === void 0 || value === null || value === "") return fallback;
|
|
86
|
+
let parsed;
|
|
87
|
+
if (typeof value === "number") {
|
|
88
|
+
parsed = value;
|
|
89
|
+
} else if (typeof value === "string") {
|
|
90
|
+
const trimmed = value.trim();
|
|
91
|
+
if (trimmed.length === 0) return fallback;
|
|
92
|
+
if (!/^[+-]?\d+$/.test(trimmed)) {
|
|
93
|
+
throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);
|
|
94
|
+
}
|
|
95
|
+
parsed = Number(trimmed);
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);
|
|
98
|
+
}
|
|
99
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > max) {
|
|
100
|
+
throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);
|
|
101
|
+
}
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
function coerceNonNegativeInt(value, fallback, max, fieldName) {
|
|
105
|
+
if (value === void 0 || value === null || value === "") return fallback;
|
|
106
|
+
let parsed;
|
|
107
|
+
if (typeof value === "number") {
|
|
108
|
+
parsed = value;
|
|
109
|
+
} else if (typeof value === "string") {
|
|
110
|
+
const trimmed = value.trim();
|
|
111
|
+
if (trimmed.length === 0) return fallback;
|
|
112
|
+
if (!/^[+-]?\d+$/.test(trimmed)) {
|
|
113
|
+
throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);
|
|
114
|
+
}
|
|
115
|
+
parsed = Number(trimmed);
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);
|
|
118
|
+
}
|
|
119
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > max) {
|
|
120
|
+
throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);
|
|
121
|
+
}
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
function coerceOptionalNonEmptyString(value, fieldName) {
|
|
125
|
+
if (value === void 0 || value === null) return void 0;
|
|
126
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
127
|
+
throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);
|
|
128
|
+
}
|
|
129
|
+
function coerceOptionalString(value, fieldName) {
|
|
130
|
+
if (value === void 0 || value === null) return void 0;
|
|
131
|
+
if (typeof value === "string") {
|
|
132
|
+
const trimmed = value.trim();
|
|
133
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);
|
|
136
|
+
}
|
|
137
|
+
function coerceOptionalHttpUrl(value, fieldName) {
|
|
138
|
+
if (value === void 0 || value === null) return void 0;
|
|
139
|
+
if (typeof value !== "string") {
|
|
140
|
+
throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);
|
|
141
|
+
}
|
|
142
|
+
const trimmed = value.trim();
|
|
143
|
+
if (trimmed.length === 0) return void 0;
|
|
144
|
+
try {
|
|
145
|
+
const parsed = new URL(trimmed);
|
|
146
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") return trimTrailingSlashes(trimmed);
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);
|
|
150
|
+
}
|
|
151
|
+
function coerceRecallMode(value) {
|
|
152
|
+
if (value === void 0 || value === null || value === "") return DEFAULT_CONFIG.recallMode;
|
|
153
|
+
if (value === "minimal" || value === "full" || value === "graph_mode" || value === "no_recall" || value === "auto") {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Invalid recallMode value for Remnic Pi config: ${JSON.stringify(value)}`);
|
|
157
|
+
}
|
|
158
|
+
function readConfigFile(configPath) {
|
|
159
|
+
if (!existsSync(configPath)) return {};
|
|
160
|
+
try {
|
|
161
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
162
|
+
const parsed = JSON.parse(raw);
|
|
163
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
164
|
+
return parsed;
|
|
165
|
+
}
|
|
166
|
+
throw new Error("expected a JSON object");
|
|
167
|
+
} catch (err) {
|
|
168
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
169
|
+
throw new Error(`Failed to load Remnic Pi config at ${configPath}: ${reason}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function trimTrailingSlashes(value) {
|
|
173
|
+
let end = value.length;
|
|
174
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
|
|
175
|
+
return value.slice(0, end);
|
|
176
|
+
}
|
|
177
|
+
function resolveConfigPath(options = {}) {
|
|
178
|
+
const env = options.env ?? process.env;
|
|
179
|
+
return expandTildePath2(
|
|
180
|
+
options.configPath || env.REMNIC_PI_CONFIG || env.REMNIC_OMP_CONFIG || defaultConfigPath(env)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
function loadConfig(options = {}) {
|
|
184
|
+
const env = options.env ?? process.env;
|
|
185
|
+
const fileConfig = readConfigFile(resolveConfigPath(options));
|
|
186
|
+
const daemonUrl = coerceOptionalHttpUrl(fileConfig.remnicDaemonUrl, "remnicDaemonUrl") ?? coerceOptionalHttpUrl(env.REMNIC_DAEMON_URL, "REMNIC_DAEMON_URL") ?? DEFAULT_CONFIG.remnicDaemonUrl;
|
|
187
|
+
const authToken = coerceOptionalString(fileConfig.authToken, "authToken") ?? coerceOptionalString(env.REMNIC_PI_AUTH_TOKEN, "REMNIC_PI_AUTH_TOKEN");
|
|
188
|
+
const namespace = coerceOptionalNonEmptyString(fileConfig.namespace, "namespace");
|
|
189
|
+
const requestTimeoutMs = coercePositiveInt(
|
|
190
|
+
fileConfig.requestTimeoutMs,
|
|
191
|
+
DEFAULT_CONFIG.requestTimeoutMs,
|
|
192
|
+
6e4,
|
|
193
|
+
"requestTimeoutMs"
|
|
194
|
+
);
|
|
195
|
+
const turnFallback = Math.min(requestTimeoutMs, DEFAULT_CONFIG.turnRequestTimeoutMs);
|
|
196
|
+
const turnRequestTimeoutMs = coercePositiveInt(
|
|
197
|
+
fileConfig.turnRequestTimeoutMs,
|
|
198
|
+
turnFallback,
|
|
199
|
+
25e3,
|
|
200
|
+
"turnRequestTimeoutMs"
|
|
201
|
+
);
|
|
202
|
+
const recallTimeoutThreshold = coercePositiveInt(
|
|
203
|
+
fileConfig.recallTimeoutThreshold,
|
|
204
|
+
DEFAULT_CONFIG.recallTimeoutThreshold,
|
|
205
|
+
1e3,
|
|
206
|
+
"recallTimeoutThreshold"
|
|
207
|
+
);
|
|
208
|
+
const recallTimeoutWindow = coercePositiveInt(
|
|
209
|
+
fileConfig.recallTimeoutWindow,
|
|
210
|
+
DEFAULT_CONFIG.recallTimeoutWindow,
|
|
211
|
+
1e3,
|
|
212
|
+
"recallTimeoutWindow"
|
|
213
|
+
);
|
|
214
|
+
if (recallTimeoutThreshold > recallTimeoutWindow) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Invalid recall timeout circuit breaker config: threshold (${recallTimeoutThreshold}) cannot exceed window (${recallTimeoutWindow})`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
remnicDaemonUrl: daemonUrl,
|
|
221
|
+
authToken,
|
|
222
|
+
namespace,
|
|
223
|
+
recallMode: coerceRecallMode(fileConfig.recallMode),
|
|
224
|
+
recallTopK: coercePositiveInt(fileConfig.recallTopK, DEFAULT_CONFIG.recallTopK, 50, "recallTopK"),
|
|
225
|
+
recallBudgetChars: coercePositiveInt(fileConfig.recallBudgetChars, DEFAULT_CONFIG.recallBudgetChars, 64e3, "recallBudgetChars"),
|
|
226
|
+
recallEnabled: coerceBoolean(fileConfig.recallEnabled, DEFAULT_CONFIG.recallEnabled, "recallEnabled"),
|
|
227
|
+
observeEnabled: coerceBoolean(fileConfig.observeEnabled, DEFAULT_CONFIG.observeEnabled, "observeEnabled"),
|
|
228
|
+
observeSkipExtraction: coerceBoolean(fileConfig.observeSkipExtraction, DEFAULT_CONFIG.observeSkipExtraction, "observeSkipExtraction"),
|
|
229
|
+
compactionEnabled: coerceBoolean(fileConfig.compactionEnabled, DEFAULT_CONFIG.compactionEnabled, "compactionEnabled"),
|
|
230
|
+
mcpToolsEnabled: coerceBoolean(fileConfig.mcpToolsEnabled, DEFAULT_CONFIG.mcpToolsEnabled, "mcpToolsEnabled"),
|
|
231
|
+
statusEnabled: coerceBoolean(fileConfig.statusEnabled, DEFAULT_CONFIG.statusEnabled, "statusEnabled"),
|
|
232
|
+
requestTimeoutMs,
|
|
233
|
+
startupRequestTimeoutMs: coercePositiveInt(
|
|
234
|
+
fileConfig.startupRequestTimeoutMs,
|
|
235
|
+
DEFAULT_CONFIG.startupRequestTimeoutMs,
|
|
236
|
+
6e4,
|
|
237
|
+
"startupRequestTimeoutMs"
|
|
238
|
+
),
|
|
239
|
+
turnRequestTimeoutMs,
|
|
240
|
+
observeMaxBytes: coercePositiveInt(
|
|
241
|
+
fileConfig.observeMaxBytes,
|
|
242
|
+
DEFAULT_CONFIG.observeMaxBytes,
|
|
243
|
+
8388608,
|
|
244
|
+
"observeMaxBytes"
|
|
245
|
+
),
|
|
246
|
+
observeMaxRetries: coerceNonNegativeInt(fileConfig.observeMaxRetries, DEFAULT_CONFIG.observeMaxRetries, 5, "observeMaxRetries"),
|
|
247
|
+
daemonCooldownMs: coercePositiveInt(fileConfig.daemonCooldownMs, DEFAULT_CONFIG.daemonCooldownMs, 6e4, "daemonCooldownMs"),
|
|
248
|
+
recallTimeoutThreshold,
|
|
249
|
+
recallTimeoutWindow
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export {
|
|
254
|
+
resolvePiAgentHome,
|
|
255
|
+
resolvePiExtensionRoot,
|
|
256
|
+
resolveOmpConfigRoot,
|
|
257
|
+
resolveOmpAgentHome,
|
|
258
|
+
resolveOmpExtensionRoot,
|
|
259
|
+
DEFAULT_CONFIG,
|
|
260
|
+
loadConfig
|
|
261
|
+
};
|
|
262
|
+
//# sourceMappingURL=chunk-HRZBFDYV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/paths.ts","../src/config.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\n\n// Leaf submodule (not the `@remnic/core` barrel) so the omp pre-bundle's\n// `bun build` does not pull the rest of core into the extension bundle.\nimport { expandTildePath } from \"@remnic/core/utils/path\";\n\nexport const REMNIC_PI_EXTENSION_DIR_NAME = \"remnic\";\n\nexport function resolvePiAgentHome(env: NodeJS.ProcessEnv): string {\n const explicitCodingAgentDir = env.PI_CODING_AGENT_DIR?.trim();\n if (explicitCodingAgentDir) return path.resolve(expandTildePath(explicitCodingAgentDir));\n\n const explicitAgentHome = env.PI_AGENT_HOME?.trim();\n if (explicitAgentHome) return path.resolve(expandTildePath(explicitAgentHome));\n\n const explicitPiHome = env.PI_HOME?.trim();\n if (explicitPiHome) return path.join(path.resolve(expandTildePath(explicitPiHome)), \"agent\");\n\n return path.join(env.HOME ?? env.USERPROFILE ?? os.homedir(), \".pi\", \"agent\");\n}\n\nexport function resolvePiExtensionRoot(env: NodeJS.ProcessEnv): string {\n return path.join(resolvePiAgentHome(env), \"extensions\", REMNIC_PI_EXTENSION_DIR_NAME);\n}\n\n/**\n * Resolve the active omp profile from the environment, mirroring omp's\n * `resolveProfileEnv`: `OMP_PROFILE` is authoritative, and `PI_PROFILE` is a\n * compatibility fallback consulted **only** when `OMP_PROFILE` is undefined\n * (an explicitly-empty `OMP_PROFILE` therefore selects the default profile).\n * The reserved name \"default\" and blank values resolve to the base (no profile).\n */\nfunction resolveOmpProfile(env: NodeJS.ProcessEnv): string | undefined {\n const raw = env.OMP_PROFILE !== undefined ? env.OMP_PROFILE : env.PI_PROFILE;\n const trimmed = raw?.trim();\n if (!trimmed || trimmed === \"default\") return undefined;\n return trimmed;\n}\n\n/**\n * Resolve the omp (oh-my-pi) agent home directory that omp auto-discovers\n * extensions from. Mirrors omp's `DirResolver` (packages/utils/src/dirs.ts):\n *\n * - The config dir name is `PI_CONFIG_DIR` (default `.omp`).\n * - When a profile (`OMP_PROFILE`, falling back to `PI_PROFILE`) is active it\n * wins and resolves to `<configRoot>/profiles/<name>/agent`; omp discards\n * the `PI_CODING_AGENT_DIR` override while a profile is active.\n * - Otherwise `PI_CODING_AGENT_DIR` overrides the whole agent dir.\n * - Otherwise the base agent dir is `<configRoot>/agent`.\n *\n * Note: omp's XDG redirection (`XDG_DATA_HOME`, etc.) applies to the `data`,\n * `state`, and `cache` categories (sessions/state/cache) — NOT to the base\n * agent dir that extensions are discovered from — so it is intentionally not\n * consulted here.\n */\n/**\n * The omp config root (`~/<PI_CONFIG_DIR or .omp>`), which contains the base\n * `agent/` dir and any `profiles/<name>/agent/` dirs.\n */\nexport function resolveOmpConfigRoot(env: NodeJS.ProcessEnv): string {\n const home = env.HOME ?? env.USERPROFILE ?? os.homedir();\n const configDirName = env.PI_CONFIG_DIR?.trim() || \".omp\";\n return path.join(home, configDirName);\n}\n\nexport function resolveOmpAgentHome(env: NodeJS.ProcessEnv): string {\n const configRoot = resolveOmpConfigRoot(env);\n\n const profile = resolveOmpProfile(env);\n if (profile) {\n return path.join(configRoot, \"profiles\", profile, \"agent\");\n }\n\n const explicitCodingAgentDir = env.PI_CODING_AGENT_DIR?.trim();\n if (explicitCodingAgentDir) return path.resolve(expandTildePath(explicitCodingAgentDir));\n\n return path.join(configRoot, \"agent\");\n}\n\nexport function resolveOmpExtensionRoot(env: NodeJS.ProcessEnv): string {\n return path.join(resolveOmpAgentHome(env), \"extensions\", REMNIC_PI_EXTENSION_DIR_NAME);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// Leaf submodule (not the `@remnic/core` barrel) so the omp pre-bundle's\n// `bun build` does not pull the rest of core — including the LanceDB native\n// asset — into the extension bundle. See PR #1641.\nimport { expandTildePath } from \"@remnic/core/utils/path\";\n\nimport { REMNIC_PI_EXTENSION_DIR_NAME, resolvePiAgentHome } from \"./paths.js\";\n\nexport interface RemnicPiConfig {\n remnicDaemonUrl: string;\n authToken?: string;\n namespace?: string;\n recallMode: \"auto\" | \"minimal\" | \"full\" | \"graph_mode\" | \"no_recall\";\n recallTopK: number;\n recallBudgetChars: number;\n recallEnabled: boolean;\n observeEnabled: boolean;\n observeSkipExtraction: boolean;\n compactionEnabled: boolean;\n mcpToolsEnabled: boolean;\n statusEnabled: boolean;\n requestTimeoutMs: number;\n startupRequestTimeoutMs: number;\n /**\n * Per-turn request budget for observe/recall. MUST stay below the host's\n * in-handler kill budget (Pi/omp kills handlers at 30 s). Defaults to 20 s,\n * capped at 25 s so a misconfiguration can never produce a structurally\n * unsatisfiable timeout (issue #1626).\n */\n turnRequestTimeoutMs: number;\n /**\n * Soft cap on a single observe POST body in bytes. The client chunks observe\n * batches to stay under this; individual oversized messages are truncated\n * with a marker. Defaults to 100 KiB, safely under the daemon's default\n * 128 KiB `maxBodyBytes` (issue #1600).\n */\n observeMaxBytes: number;\n /**\n * Maximum retry attempts for observe/recall on transient connection-level\n * failures (socket close, ECONNRESET, EPIPE). Observe is dedupe-safe so\n * retrying is harmless (issue #1602).\n */\n observeMaxRetries: number;\n /**\n * Cooldown base for the daemon-reachability circuit breaker. When observe/\n * recall fails on a timeout or connection error, subsequent turns skip fast\n * for an exponentially growing window starting at this value (issue #1626).\n */\n daemonCooldownMs: number;\n /**\n * Number of explicit recall timeout errors in the last {@link recallTimeoutWindow}\n * recall calls that permanently disables automatic recall for the process lifetime.\n */\n recallTimeoutThreshold: number;\n /**\n * Size of the rolling window of recent recall calls used by the recall-timeout\n * circuit breaker.\n */\n recallTimeoutWindow: number;\n}\n\nexport interface LoadConfigOptions {\n configPath?: string;\n env?: NodeJS.ProcessEnv;\n}\n\nexport const DEFAULT_CONFIG: RemnicPiConfig = {\n remnicDaemonUrl: \"http://127.0.0.1:4318\",\n recallMode: \"auto\",\n recallTopK: 8,\n recallBudgetChars: 12000,\n recallEnabled: true,\n observeEnabled: true,\n observeSkipExtraction: false,\n compactionEnabled: true,\n mcpToolsEnabled: true,\n statusEnabled: true,\n requestTimeoutMs: 60000,\n startupRequestTimeoutMs: 1000,\n // Default 20 s is comfortably under the Pi/omp 30 s handler budget (#1626).\n turnRequestTimeoutMs: 20000,\n // Default 100 KiB leaves headroom under the daemon's 128 KiB default (#1600).\n observeMaxBytes: 102400,\n observeMaxRetries: 2,\n // Base cooldown for the circuit breaker; doubles on consecutive failures (#1626).\n daemonCooldownMs: 5000,\n // Recall-timeout circuit breaker: 7 timeouts in the last 10 recall calls trip permanently.\n recallTimeoutThreshold: 7,\n recallTimeoutWindow: 10,\n};\n\nfunction defaultConfigPath(env: NodeJS.ProcessEnv): string {\n return path.join(resolvePiAgentHome(env), \"extensions\", REMNIC_PI_EXTENSION_DIR_NAME, \"remnic.config.json\");\n}\n\nfunction coerceBoolean(value: unknown, fallback: boolean, fieldName: string): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const normalized = value.trim().toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"on\"].includes(normalized)) return true;\n if ([\"false\", \"0\", \"no\", \"off\"].includes(normalized)) return false;\n }\n throw new Error(`Invalid boolean value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coercePositiveInt(value: unknown, fallback: number, max: number, fieldName: string): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n let parsed: number;\n if (typeof value === \"number\") {\n parsed = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.length === 0) return fallback;\n if (!/^[+-]?\\d+$/.test(trimmed)) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n parsed = Number(trimmed);\n } else {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n if (!Number.isInteger(parsed) || parsed <= 0 || parsed > max) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 1 to ${max}`);\n }\n return parsed;\n}\n\n/**\n * Like {@link coercePositiveInt} but allows 0, for knobs where 0 is a\n * meaningful \"disabled\" value (e.g. observeMaxRetries). Still rejects\n * negatives, non-integers, and values above the cap.\n */\nfunction coerceNonNegativeInt(value: unknown, fallback: number, max: number, fieldName: string): number {\n if (value === undefined || value === null || value === \"\") return fallback;\n let parsed: number;\n if (typeof value === \"number\") {\n parsed = value;\n } else if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.length === 0) return fallback;\n if (!/^[+-]?\\d+$/.test(trimmed)) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n parsed = Number(trimmed);\n } else {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > max) {\n throw new Error(`Invalid numeric value for Remnic Pi config field ${fieldName}: expected an integer from 0 to ${max}`);\n }\n return parsed;\n}\n\nfunction coerceOptionalNonEmptyString(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\" && value.trim().length > 0) return value.trim();\n throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coerceOptionalString(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n throw new Error(`Invalid string value for Remnic Pi config field ${fieldName}`);\n}\n\nfunction coerceOptionalHttpUrl(value: unknown, fieldName: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== \"string\") {\n throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n try {\n const parsed = new URL(trimmed);\n if (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") return trimTrailingSlashes(trimmed);\n } catch {\n // Fall through to the shared error below.\n }\n throw new Error(`Invalid URL value for Remnic Pi config field ${fieldName}: expected an http or https URL`);\n}\n\nfunction coerceRecallMode(value: unknown): RemnicPiConfig[\"recallMode\"] {\n if (value === undefined || value === null || value === \"\") return DEFAULT_CONFIG.recallMode;\n if (\n value === \"minimal\" ||\n value === \"full\" ||\n value === \"graph_mode\" ||\n value === \"no_recall\" ||\n value === \"auto\"\n ) {\n return value;\n }\n throw new Error(`Invalid recallMode value for Remnic Pi config: ${JSON.stringify(value)}`);\n}\n\nfunction readConfigFile(configPath: string): Record<string, unknown> {\n if (!existsSync(configPath)) return {};\n try {\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n throw new Error(\"expected a JSON object\");\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to load Remnic Pi config at ${configPath}: ${reason}`);\n }\n}\n\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;\n return value.slice(0, end);\n}\n\nexport function resolveConfigPath(options: LoadConfigOptions = {}): string {\n const env = options.env ?? process.env;\n // REMNIC_PI_CONFIG keeps precedence for upstream Pi; REMNIC_OMP_CONFIG lets an\n // omp (oh-my-pi) direct load (`omp -e npm:@remnic/plugin-pi`) point the shared\n // runtime module at its own config without an explicit configPath. Connector\n // installs always pass an explicit configPath, so this only affects direct loads.\n return expandTildePath(\n options.configPath || env.REMNIC_PI_CONFIG || env.REMNIC_OMP_CONFIG || defaultConfigPath(env),\n );\n}\n\nexport function loadConfig(options: LoadConfigOptions = {}): RemnicPiConfig {\n const env = options.env ?? process.env;\n const fileConfig = readConfigFile(resolveConfigPath(options));\n const daemonUrl =\n coerceOptionalHttpUrl(fileConfig.remnicDaemonUrl, \"remnicDaemonUrl\") ??\n coerceOptionalHttpUrl(env.REMNIC_DAEMON_URL, \"REMNIC_DAEMON_URL\") ??\n DEFAULT_CONFIG.remnicDaemonUrl;\n const authToken =\n coerceOptionalString(fileConfig.authToken, \"authToken\") ??\n coerceOptionalString(env.REMNIC_PI_AUTH_TOKEN, \"REMNIC_PI_AUTH_TOKEN\");\n const namespace = coerceOptionalNonEmptyString(fileConfig.namespace, \"namespace\");\n\n const requestTimeoutMs = coercePositiveInt(\n fileConfig.requestTimeoutMs,\n DEFAULT_CONFIG.requestTimeoutMs,\n 60_000,\n \"requestTimeoutMs\",\n );\n // When turnRequestTimeoutMs is not explicitly set, derive it from the\n // configured requestTimeoutMs (capped at the default turn budget) so an\n // existing install that lowered requestTimeoutMs below 20s keeps its tighter\n // per-turn budget instead of being silently raised back to 20s (codex review).\n const turnFallback = Math.min(requestTimeoutMs, DEFAULT_CONFIG.turnRequestTimeoutMs);\n const turnRequestTimeoutMs = coercePositiveInt(\n fileConfig.turnRequestTimeoutMs,\n turnFallback,\n 25_000,\n \"turnRequestTimeoutMs\",\n );\n const recallTimeoutThreshold = coercePositiveInt(\n fileConfig.recallTimeoutThreshold,\n DEFAULT_CONFIG.recallTimeoutThreshold,\n 1000,\n \"recallTimeoutThreshold\",\n );\n const recallTimeoutWindow = coercePositiveInt(\n fileConfig.recallTimeoutWindow,\n DEFAULT_CONFIG.recallTimeoutWindow,\n 1000,\n \"recallTimeoutWindow\",\n );\n if (recallTimeoutThreshold > recallTimeoutWindow) {\n throw new Error(\n `Invalid recall timeout circuit breaker config: threshold (${recallTimeoutThreshold}) cannot exceed window (${recallTimeoutWindow})`,\n );\n }\n\n return {\n remnicDaemonUrl: daemonUrl,\n authToken,\n namespace,\n recallMode: coerceRecallMode(fileConfig.recallMode),\n recallTopK: coercePositiveInt(fileConfig.recallTopK, DEFAULT_CONFIG.recallTopK, 50, \"recallTopK\"),\n recallBudgetChars: coercePositiveInt(fileConfig.recallBudgetChars, DEFAULT_CONFIG.recallBudgetChars, 64000, \"recallBudgetChars\"),\n recallEnabled: coerceBoolean(fileConfig.recallEnabled, DEFAULT_CONFIG.recallEnabled, \"recallEnabled\"),\n observeEnabled: coerceBoolean(fileConfig.observeEnabled, DEFAULT_CONFIG.observeEnabled, \"observeEnabled\"),\n observeSkipExtraction: coerceBoolean(fileConfig.observeSkipExtraction, DEFAULT_CONFIG.observeSkipExtraction, \"observeSkipExtraction\"),\n compactionEnabled: coerceBoolean(fileConfig.compactionEnabled, DEFAULT_CONFIG.compactionEnabled, \"compactionEnabled\"),\n mcpToolsEnabled: coerceBoolean(fileConfig.mcpToolsEnabled, DEFAULT_CONFIG.mcpToolsEnabled, \"mcpToolsEnabled\"),\n statusEnabled: coerceBoolean(fileConfig.statusEnabled, DEFAULT_CONFIG.statusEnabled, \"statusEnabled\"),\n requestTimeoutMs,\n startupRequestTimeoutMs: coercePositiveInt(\n fileConfig.startupRequestTimeoutMs,\n DEFAULT_CONFIG.startupRequestTimeoutMs,\n 60_000,\n \"startupRequestTimeoutMs\",\n ),\n turnRequestTimeoutMs,\n observeMaxBytes: coercePositiveInt(\n fileConfig.observeMaxBytes,\n DEFAULT_CONFIG.observeMaxBytes,\n 8_388_608,\n \"observeMaxBytes\",\n ),\n observeMaxRetries: coerceNonNegativeInt(fileConfig.observeMaxRetries, DEFAULT_CONFIG.observeMaxRetries, 5, \"observeMaxRetries\"),\n daemonCooldownMs: coercePositiveInt(fileConfig.daemonCooldownMs, DEFAULT_CONFIG.daemonCooldownMs, 60_000, \"daemonCooldownMs\"),\n recallTimeoutThreshold,\n recallTimeoutWindow,\n };\n}\n"],"mappings":"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","names":["path","expandTildePath","path","expandTildePath"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,16 @@ interface RemnicPiConfig {
|
|
|
41
41
|
* for an exponentially growing window starting at this value (issue #1626).
|
|
42
42
|
*/
|
|
43
43
|
daemonCooldownMs: number;
|
|
44
|
+
/**
|
|
45
|
+
* Number of explicit recall timeout errors in the last {@link recallTimeoutWindow}
|
|
46
|
+
* recall calls that permanently disables automatic recall for the process lifetime.
|
|
47
|
+
*/
|
|
48
|
+
recallTimeoutThreshold: number;
|
|
49
|
+
/**
|
|
50
|
+
* Size of the rolling window of recent recall calls used by the recall-timeout
|
|
51
|
+
* circuit breaker.
|
|
52
|
+
*/
|
|
53
|
+
recallTimeoutWindow: number;
|
|
44
54
|
}
|
|
45
55
|
interface LoadConfigOptions {
|
|
46
56
|
configPath?: string;
|
|
@@ -79,6 +89,8 @@ interface McpTool {
|
|
|
79
89
|
}
|
|
80
90
|
interface RequestOptions {
|
|
81
91
|
timeoutMs?: number;
|
|
92
|
+
/** Optional caller abort signal (e.g. shared breaker trip). */
|
|
93
|
+
signal?: AbortSignal;
|
|
82
94
|
/** Transient-retry budget for connection-level failures (socket close, ECONNRESET). */
|
|
83
95
|
maxRetries?: number;
|
|
84
96
|
}
|
|
@@ -140,7 +152,7 @@ declare class RemnicClient {
|
|
|
140
152
|
lcmCompactionRecord(sessionKey: string, tokensBefore: number, tokensAfter: number): Promise<Record<string, unknown>>;
|
|
141
153
|
contextCheckpoint(sessionKey: string, context: string): Promise<Record<string, unknown>>;
|
|
142
154
|
mcpListTools(options?: RequestOptions): Promise<McpTool[]>;
|
|
143
|
-
mcpTool(name: string, args: Record<string, unknown
|
|
155
|
+
mcpTool(name: string, args: Record<string, unknown>, options?: RequestOptions): Promise<Record<string, unknown>>;
|
|
144
156
|
/**
|
|
145
157
|
* Single HTTP attempt with the configured timeout. No retry — retry of
|
|
146
158
|
* transient connection failures lives in {@link requestWithRetry}.
|
|
@@ -169,9 +181,11 @@ type PiApi = {
|
|
|
169
181
|
registerTool(tool: Record<string, unknown>): void;
|
|
170
182
|
appendEntry<T = unknown>(customType: string, data?: T): void;
|
|
171
183
|
};
|
|
184
|
+
type RemnicPiConfigInput = Omit<RemnicPiConfig, "recallTimeoutThreshold" | "recallTimeoutWindow"> & Partial<Pick<RemnicPiConfig, "recallTimeoutThreshold" | "recallTimeoutWindow">>;
|
|
172
185
|
interface RemnicPiExtensionOptions extends LoadConfigOptions {
|
|
173
|
-
config?:
|
|
186
|
+
config?: RemnicPiConfigInput;
|
|
174
187
|
}
|
|
188
|
+
declare function resetProcessRecallBreakerForTest(config: Pick<RemnicPiConfig, "remnicDaemonUrl" | "namespace" | "authToken" | "recallTimeoutThreshold" | "recallTimeoutWindow">): void;
|
|
175
189
|
declare function createRemnicPiExtension(options?: RemnicPiExtensionOptions): (pi: PiApi) => Promise<void>;
|
|
176
190
|
declare function remnicPiExtension(pi: PiApi): Promise<void>;
|
|
177
191
|
declare function toPiToolParametersSchema(inputSchema: unknown): TSchema;
|
|
@@ -181,4 +195,4 @@ declare function observeMessages(ctx: any, client: RemnicClient, rawMessages: un
|
|
|
181
195
|
declare function buildCompactionSummary(preparation: any): string;
|
|
182
196
|
declare function isDaemonUnreachableError(err: unknown): boolean;
|
|
183
197
|
|
|
184
|
-
export { type RemnicPiExtensionOptions, buildCompactionSummary, createRemnicPiExtension, remnicPiExtension as default, isDaemonUnreachableError, observeMessages, stripSessionOwnedRuntimeFields, stripSessionOwnedSchemaFields, textFromMessage, toPiToolParametersSchema };
|
|
198
|
+
export { type PiApi, type RemnicPiConfigInput, type RemnicPiExtensionOptions, buildCompactionSummary, createRemnicPiExtension, remnicPiExtension as default, isDaemonUnreachableError, observeMessages, resetProcessRecallBreakerForTest, stripSessionOwnedRuntimeFields, stripSessionOwnedSchemaFields, textFromMessage, toPiToolParametersSchema };
|