@eleboucher/pi-memini 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +97 -48
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28,9 +28,72 @@ function validateNamespace(ns) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// ../../../packages/memini-client/src/capture.ts
|
|
32
|
+
var TRUNCATION_MARKER = "\n[...truncated]";
|
|
33
|
+
function truncateForCapture(s, max) {
|
|
34
|
+
if (typeof max !== "number" || !Number.isFinite(max) || max <= 0) return s;
|
|
35
|
+
const cap = Math.floor(max);
|
|
36
|
+
if (s.length <= cap) return s;
|
|
37
|
+
let i = 0;
|
|
38
|
+
for (let n = 0; i < s.length && n < cap; n++) {
|
|
39
|
+
i += s.codePointAt(i) > 65535 ? 2 : 1;
|
|
40
|
+
}
|
|
41
|
+
if (i >= s.length) return s;
|
|
42
|
+
return s.slice(0, i) + TRUNCATION_MARKER;
|
|
43
|
+
}
|
|
44
|
+
function buildTurnCapture(userText, assistantText, userMax, assistantMax) {
|
|
45
|
+
return `${truncateForCapture(userText, userMax)}
|
|
46
|
+
|
|
47
|
+
${truncateForCapture(assistantText, assistantMax)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
// ../../../packages/memini-client/src/session.ts
|
|
32
51
|
var SESSION_CWD_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
33
52
|
|
|
53
|
+
// ../../../packages/memini-client/src/bootstrap.ts
|
|
54
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
55
|
+
var MIN_TIMEOUT_MS = 100;
|
|
56
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
57
|
+
function envTimeoutMs(raw, fallback = DEFAULT_TIMEOUT_MS) {
|
|
58
|
+
if (raw == null || raw.trim() === "") return fallback;
|
|
59
|
+
const n = Number(raw.trim());
|
|
60
|
+
if (!Number.isFinite(n)) return fallback;
|
|
61
|
+
return Math.max(MIN_TIMEOUT_MS, Math.trunc(n));
|
|
62
|
+
}
|
|
63
|
+
function envEnabled(raw, defaultOn) {
|
|
64
|
+
if (raw == null || raw === "") return defaultOn;
|
|
65
|
+
return !/^(0|false|no|off)$/i.test(raw.trim());
|
|
66
|
+
}
|
|
67
|
+
function readBootstrap(env = process.env) {
|
|
68
|
+
return {
|
|
69
|
+
baseUrl: env["MEMINI_BASE_URL"] || "http://localhost:8080",
|
|
70
|
+
apiKey: env["MEMINI_API_KEY"] || "",
|
|
71
|
+
requireHttps: envEnabled(env["MEMINI_REQUIRE_HTTPS"], false),
|
|
72
|
+
debug: envEnabled(env["MEMINI_DEBUG"], false),
|
|
73
|
+
agent: env["MEMINI_AGENT"] || "",
|
|
74
|
+
namespaceEnv: (env["MEMINI_NAMESPACE"] || "").trim(),
|
|
75
|
+
namespacePrefixEnv: (env["MEMINI_NAMESPACE_PREFIX"] || "").trim(),
|
|
76
|
+
homeEnv: (env["MEMINI_HOME"] || "").trim(),
|
|
77
|
+
timeoutMs: envTimeoutMs(env["MEMINI_TIMEOUT_MS"])
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function isPlaintextBearerUnsafe(baseUrl, secret) {
|
|
81
|
+
if (!secret) return false;
|
|
82
|
+
try {
|
|
83
|
+
const u = new URL(baseUrl);
|
|
84
|
+
return u.protocol === "http:" && !LOOPBACK_HOSTS.has(u.hostname.replace(/^\[|\]$/g, "").toLowerCase());
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function assertBearerTransportSafe(baseUrl, secret, env = process.env) {
|
|
90
|
+
if (!isPlaintextBearerUnsafe(baseUrl, secret)) return;
|
|
91
|
+
if (!envEnabled(env["MEMINI_REQUIRE_HTTPS"], false)) return;
|
|
92
|
+
throw new Error(
|
|
93
|
+
`memini: a bearer token is configured for plaintext HTTP to ${baseUrl}. The token and memory payloads can be observed on the network; use HTTPS or an SSH tunnel.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
34
97
|
// ../../../packages/memini-client/src/settings.ts
|
|
35
98
|
var BEHAVIOR_KNOBS = [
|
|
36
99
|
{ envName: "MEMINI_CAPTURE_TURNS", wireKey: "capture_turns", kind: "bool", default: true },
|
|
@@ -38,6 +101,7 @@ var BEHAVIOR_KNOBS = [
|
|
|
38
101
|
{ envName: "MEMINI_INLINE_EXTRACT", wireKey: "inline_extract", kind: "bool", default: true },
|
|
39
102
|
{ envName: "MEMINI_AUTO_SAVE", wireKey: "auto_save", kind: "bool", default: true },
|
|
40
103
|
{ envName: "MEMINI_AUTO_SAVE_INTERVAL", wireKey: "auto_save_interval", kind: "int", default: 10 },
|
|
104
|
+
{ envName: "MEMINI_AUTO_SAVE_MIN_EVENTS", wireKey: "auto_save_min_events", kind: "int", default: 3 },
|
|
41
105
|
{ envName: "MEMINI_INJECT_BRIEFING_PINNED", wireKey: "inject_briefing_pinned", kind: "int", default: 5 },
|
|
42
106
|
{ envName: "MEMINI_INJECT_BRIEFING_FACTS", wireKey: "inject_briefing_facts", kind: "int", default: 5 },
|
|
43
107
|
{ envName: "MEMINI_INJECT_BRIEFING_PROCEDURES", wireKey: "inject_briefing_procedures", kind: "int", default: 5 },
|
|
@@ -59,11 +123,30 @@ var BEHAVIOR_KNOBS = [
|
|
|
59
123
|
{ envName: "MEMINI_RECALL_LIMIT", wireKey: "recall_limit", kind: "int", default: 3 },
|
|
60
124
|
{ envName: "MEMINI_INJECT_RECALL_MAX_TOK", wireKey: "inject_recall_max_tok", kind: "int", default: 0 },
|
|
61
125
|
{ envName: "MEMINI_INJECT_RECALL_MIN_SCORE", wireKey: "inject_recall_min_score", kind: "float", default: 0 },
|
|
62
|
-
{ envName: "MEMINI_MIN_CAPTURE_CHARS", wireKey: "min_capture_chars", kind: "int", default: 0 }
|
|
126
|
+
{ envName: "MEMINI_MIN_CAPTURE_CHARS", wireKey: "min_capture_chars", kind: "int", default: 0 },
|
|
127
|
+
{ envName: "MEMINI_CAPTURE_USER_MAX_CHARS", wireKey: "capture_user_max_chars", kind: "int", default: 1e3 },
|
|
128
|
+
{
|
|
129
|
+
envName: "MEMINI_CAPTURE_ASSISTANT_MAX_CHARS",
|
|
130
|
+
wireKey: "capture_assistant_max_chars",
|
|
131
|
+
kind: "int",
|
|
132
|
+
default: 3e3
|
|
133
|
+
},
|
|
134
|
+
// Also read at the transport layer (bootstrap.ts's timeoutMs), because a
|
|
135
|
+
// request timeout has to exist before the handshake that fetches settings.
|
|
136
|
+
// Both spellings share DEFAULT_TIMEOUT_MS/MIN_TIMEOUT_MS, so there is exactly
|
|
137
|
+
// one number: raising it here (server-side) or in the env both work.
|
|
138
|
+
{
|
|
139
|
+
envName: "MEMINI_TIMEOUT_MS",
|
|
140
|
+
wireKey: "request_timeout_ms",
|
|
141
|
+
kind: "int",
|
|
142
|
+
default: DEFAULT_TIMEOUT_MS,
|
|
143
|
+
min: MIN_TIMEOUT_MS
|
|
144
|
+
}
|
|
63
145
|
];
|
|
64
|
-
function parseIntKnob(raw, fallback) {
|
|
146
|
+
function parseIntKnob(raw, fallback, min = 0) {
|
|
65
147
|
const n = Number.parseInt(raw, 10);
|
|
66
|
-
|
|
148
|
+
if (!Number.isFinite(n) || n < 0) return fallback;
|
|
149
|
+
return Math.max(min, n);
|
|
67
150
|
}
|
|
68
151
|
function parseFloatKnob(raw, fallback) {
|
|
69
152
|
const n = Number.parseFloat(raw);
|
|
@@ -81,7 +164,7 @@ function effectiveSetting(knob2, server, env = process.env) {
|
|
|
81
164
|
value = !/^(0|false|no|off)$/i.test(raw.trim());
|
|
82
165
|
break;
|
|
83
166
|
case "int":
|
|
84
|
-
value = parseIntKnob(raw, knob2.default);
|
|
167
|
+
value = parseIntKnob(raw, knob2.default, knob2.min);
|
|
85
168
|
break;
|
|
86
169
|
case "float":
|
|
87
170
|
value = parseFloatKnob(raw, knob2.default);
|
|
@@ -98,41 +181,6 @@ function effectiveSetting(knob2, server, env = process.env) {
|
|
|
98
181
|
return { value: knob2.default, source: "default" };
|
|
99
182
|
}
|
|
100
183
|
|
|
101
|
-
// ../../../packages/memini-client/src/bootstrap.ts
|
|
102
|
-
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
103
|
-
function envEnabled(raw, defaultOn) {
|
|
104
|
-
if (raw == null || raw === "") return defaultOn;
|
|
105
|
-
return !/^(0|false|no|off)$/i.test(raw.trim());
|
|
106
|
-
}
|
|
107
|
-
function readBootstrap(env = process.env) {
|
|
108
|
-
return {
|
|
109
|
-
baseUrl: env["MEMINI_BASE_URL"] || "http://localhost:8080",
|
|
110
|
-
apiKey: env["MEMINI_API_KEY"] || "",
|
|
111
|
-
requireHttps: envEnabled(env["MEMINI_REQUIRE_HTTPS"], false),
|
|
112
|
-
debug: envEnabled(env["MEMINI_DEBUG"], false),
|
|
113
|
-
agent: env["MEMINI_AGENT"] || "",
|
|
114
|
-
namespaceEnv: (env["MEMINI_NAMESPACE"] || "").trim(),
|
|
115
|
-
namespacePrefixEnv: (env["MEMINI_NAMESPACE_PREFIX"] || "").trim(),
|
|
116
|
-
homeEnv: (env["MEMINI_HOME"] || "").trim()
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
function isPlaintextBearerUnsafe(baseUrl, secret) {
|
|
120
|
-
if (!secret) return false;
|
|
121
|
-
try {
|
|
122
|
-
const u = new URL(baseUrl);
|
|
123
|
-
return u.protocol === "http:" && !LOOPBACK_HOSTS.has(u.hostname.replace(/^\[|\]$/g, "").toLowerCase());
|
|
124
|
-
} catch {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
function assertBearerTransportSafe(baseUrl, secret, env = process.env) {
|
|
129
|
-
if (!isPlaintextBearerUnsafe(baseUrl, secret)) return;
|
|
130
|
-
if (!envEnabled(env["MEMINI_REQUIRE_HTTPS"], false)) return;
|
|
131
|
-
throw new Error(
|
|
132
|
-
`memini: a bearer token is configured for plaintext HTTP to ${baseUrl}. The token and memory payloads can be observed on the network; use HTTPS or an SSH tunnel.`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
184
|
// ../../../packages/memini-client/src/facts.ts
|
|
137
185
|
import { execFileSync } from "node:child_process";
|
|
138
186
|
import path from "node:path";
|
|
@@ -269,7 +317,7 @@ async function performHandshake(boot, facts, opts = {}) {
|
|
|
269
317
|
}
|
|
270
318
|
|
|
271
319
|
// src/index.ts
|
|
272
|
-
var
|
|
320
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
273
321
|
var DEFAULT_RECALL_LIMIT = 3;
|
|
274
322
|
var STATUS_TIMEOUT_MS = 4e3;
|
|
275
323
|
var HANDSHAKE_TIMEOUT_MS = 2500;
|
|
@@ -349,7 +397,7 @@ function resolveStaticConfig(env = process.env) {
|
|
|
349
397
|
const homeEnv = (e.MEMINI_HOME || "").trim();
|
|
350
398
|
return {
|
|
351
399
|
home: homeEnv || void 0,
|
|
352
|
-
timeout_ms: Number(e.MEMINI_TIMEOUT_MS ||
|
|
400
|
+
timeout_ms: Number(e.MEMINI_TIMEOUT_MS || DEFAULT_TIMEOUT_MS2),
|
|
353
401
|
fallback_on_error: envBool(e.MEMINI_FALLBACK, true)
|
|
354
402
|
};
|
|
355
403
|
}
|
|
@@ -369,7 +417,9 @@ function resolveLiveConfig(boot, facts, hs, env = process.env) {
|
|
|
369
417
|
capture: effectiveSetting(knob("capture"), server, env).value,
|
|
370
418
|
recall_limit: effectiveSetting(knob("recall_limit"), server, env).value,
|
|
371
419
|
recall_max_tokens: effectiveSetting(knob("inject_recall_max_tok"), server, env).value,
|
|
372
|
-
recall_min_score: effectiveSetting(knob("inject_recall_min_score"), server, env).value
|
|
420
|
+
recall_min_score: effectiveSetting(knob("inject_recall_min_score"), server, env).value,
|
|
421
|
+
capture_user_max_chars: effectiveSetting(knob("capture_user_max_chars"), server, env).value,
|
|
422
|
+
capture_assistant_max_chars: effectiveSetting(knob("capture_assistant_max_chars"), server, env).value
|
|
373
423
|
};
|
|
374
424
|
}
|
|
375
425
|
async function sessionLive(ctx, env = process.env) {
|
|
@@ -537,10 +587,8 @@ function extractLastAssistantText(messages) {
|
|
|
537
587
|
}
|
|
538
588
|
return "";
|
|
539
589
|
}
|
|
540
|
-
function buildTurnContent(userText, assistantText) {
|
|
541
|
-
return
|
|
542
|
-
|
|
543
|
-
${String(assistantText).slice(0, 3e3)}`;
|
|
590
|
+
function buildTurnContent(userText, assistantText, userMax, assistantMax) {
|
|
591
|
+
return buildTurnCapture(String(userText), String(assistantText), userMax, assistantMax);
|
|
544
592
|
}
|
|
545
593
|
function pinKeyFacts(facts) {
|
|
546
594
|
const out = {};
|
|
@@ -1003,7 +1051,7 @@ function meminiExtension(pi) {
|
|
|
1003
1051
|
const stored = await client.postJson(
|
|
1004
1052
|
"/v1/memories",
|
|
1005
1053
|
{
|
|
1006
|
-
content: buildTurnContent(userText, assistantText),
|
|
1054
|
+
content: buildTurnContent(userText, assistantText, live.capture_user_max_chars, live.capture_assistant_max_chars),
|
|
1007
1055
|
tags: ["pi"],
|
|
1008
1056
|
metadata
|
|
1009
1057
|
},
|
|
@@ -1119,7 +1167,8 @@ function meminiExtension(pi) {
|
|
|
1119
1167
|
pi.registerTool({
|
|
1120
1168
|
name: "memory_remember",
|
|
1121
1169
|
label: "Remember",
|
|
1122
|
-
|
|
1170
|
+
// Save-policy invariants are canonical in internal/api/mcp/mcp.go serverInstructions.
|
|
1171
|
+
description: "Store a fact, decision, preference, or event for later recall. Do not wait to be asked \u2014 call this the moment you learn: a decision and why it was made, a bug's root cause, a project convention, a stated user preference, a correction from the user (a correction IS a durable preference), an environment or tool quirk, or a non-obvious command/workflow. When the user says 'remember this', 'note that', 'don't forget', 'going forward...', or corrects you, call this tool FIRST, then acknowledge \u2014 and on an explicit request save unconditionally, even if it seems trivial or already stored; secrets and credentials are the one exception. Keep memories atomic \u2014 one self-contained fact per call; search works better on small records. Do NOT store secrets or credentials, transient session state, task progress, or facts already in project docs/CLAUDE.md or trivially recoverable from code. To correct an existing memory, pass its id \u2014 the write updates it in place. If a stored memory proves wrong or outdated, fix it immediately: re-save the corrected fact with the existing id, or delete it with memory_forget if it should not exist \u2014 never leave a known-incorrect memory in place. visibility decides who should know: 'project' (default) keeps it here; 'personal' follows the user everywhere; or name an ancestor from the memory_briefing Scope line to share it up that chain. reinforced=true in the result means the fact was ALREADY KNOWN: no new memory was created, the existing one was strengthened, and `id` names that pre-existing memory rather than anything you just wrote \u2014 do not report it to the user as a new save.",
|
|
1123
1172
|
parameters: Type.Object({
|
|
1124
1173
|
content: Type.String({ description: "The fact to remember \u2014 atomic and self-contained." }),
|
|
1125
1174
|
id: Type.Optional(
|