@teleologyhi-sdk/nhe 1.0.0-trinity
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/CHANGELOG.md +702 -0
- package/LICENSE +190 -0
- package/NOTICE +17 -0
- package/README.md +552 -0
- package/SPEC.md +794 -0
- package/TRADEMARK.md +33 -0
- package/dist/cli.js +2879 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +3307 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1902 -0
- package/dist/index.d.ts +1902 -0
- package/dist/index.js +3221 -0
- package/dist/index.js.map +1 -0
- package/package.json +119 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2879 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
3
|
+
import { mkdir, stat, writeFile, readdir, readFile } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { CreatorKeyring, LocalMaic } from '@teleologyhi-sdk/maic';
|
|
6
|
+
import { HimHandle, BirthSignatureBuilder, createHim } from '@teleologyhi-sdk/him';
|
|
7
|
+
import { monotonicFactory, ulid } from 'ulid';
|
|
8
|
+
import { stringify, parse } from 'yaml';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { metrics, SpanStatusCode, trace } from '@opentelemetry/api';
|
|
11
|
+
import { createInterface } from 'readline';
|
|
12
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
|
|
15
|
+
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
16
|
+
var AnthropicAdapter = class {
|
|
17
|
+
id;
|
|
18
|
+
supportsTools = true;
|
|
19
|
+
supportsStreaming = true;
|
|
20
|
+
client;
|
|
21
|
+
model;
|
|
22
|
+
defaultMaxOutputTokens;
|
|
23
|
+
constructor(config = {}) {
|
|
24
|
+
const apiKey = config.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"AnthropicAdapter: no API key provided and ANTHROPIC_API_KEY is not set"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
this.client = new Anthropic({ apiKey });
|
|
31
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
32
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
33
|
+
this.id = config.id ?? `anthropic:${this.model}`;
|
|
34
|
+
}
|
|
35
|
+
async generate(req) {
|
|
36
|
+
const turns = this.buildTurns(req);
|
|
37
|
+
const response = await this.client.messages.create({
|
|
38
|
+
model: this.model,
|
|
39
|
+
system: req.system,
|
|
40
|
+
messages: turns,
|
|
41
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
42
|
+
...req.tools && req.tools.length > 0 ? {
|
|
43
|
+
tools: req.tools.map((t) => ({
|
|
44
|
+
name: t.name,
|
|
45
|
+
description: t.description,
|
|
46
|
+
input_schema: t.inputSchema
|
|
47
|
+
}))
|
|
48
|
+
} : {}
|
|
49
|
+
});
|
|
50
|
+
const text = response.content.map((block) => block.type === "text" ? block.text : "").join("");
|
|
51
|
+
const toolUses = response.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
52
|
+
id: b.id,
|
|
53
|
+
name: b.name,
|
|
54
|
+
input: b.input ?? {}
|
|
55
|
+
}));
|
|
56
|
+
const out = {
|
|
57
|
+
text,
|
|
58
|
+
tokensIn: response.usage.input_tokens,
|
|
59
|
+
tokensOut: response.usage.output_tokens
|
|
60
|
+
};
|
|
61
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
async *generateStream(req) {
|
|
65
|
+
const turns = this.buildTurns(req);
|
|
66
|
+
const stream = this.client.messages.stream({
|
|
67
|
+
model: this.model,
|
|
68
|
+
system: req.system,
|
|
69
|
+
messages: turns,
|
|
70
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
71
|
+
...req.tools && req.tools.length > 0 ? {
|
|
72
|
+
tools: req.tools.map((t) => ({
|
|
73
|
+
name: t.name,
|
|
74
|
+
description: t.description,
|
|
75
|
+
input_schema: t.inputSchema
|
|
76
|
+
}))
|
|
77
|
+
} : {}
|
|
78
|
+
});
|
|
79
|
+
let tokensIn = 0;
|
|
80
|
+
let tokensOut = 0;
|
|
81
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
82
|
+
for await (const ev of stream) {
|
|
83
|
+
if (ev.type === "content_block_start") {
|
|
84
|
+
const block = ev.content_block;
|
|
85
|
+
if (block.type === "tool_use") {
|
|
86
|
+
pendingTools.set(ev.index, { id: block.id, name: block.name, partial: "" });
|
|
87
|
+
}
|
|
88
|
+
} else if (ev.type === "content_block_delta") {
|
|
89
|
+
if (ev.delta.type === "text_delta") {
|
|
90
|
+
yield { kind: "delta", text: ev.delta.text };
|
|
91
|
+
} else if (ev.delta.type === "input_json_delta") {
|
|
92
|
+
const pending = pendingTools.get(ev.index);
|
|
93
|
+
if (pending) pending.partial += ev.delta.partial_json;
|
|
94
|
+
}
|
|
95
|
+
} else if (ev.type === "content_block_stop") {
|
|
96
|
+
const pending = pendingTools.get(ev.index);
|
|
97
|
+
if (pending) {
|
|
98
|
+
let input = {};
|
|
99
|
+
try {
|
|
100
|
+
input = pending.partial ? JSON.parse(pending.partial) : {};
|
|
101
|
+
} catch {
|
|
102
|
+
input = { _raw: pending.partial };
|
|
103
|
+
}
|
|
104
|
+
yield { kind: "tool-use", toolUse: { id: pending.id, name: pending.name, input } };
|
|
105
|
+
pendingTools.delete(ev.index);
|
|
106
|
+
}
|
|
107
|
+
} else if (ev.type === "message_delta") {
|
|
108
|
+
if (ev.usage) tokensOut = ev.usage.output_tokens;
|
|
109
|
+
} else if (ev.type === "message_start") {
|
|
110
|
+
if (ev.message.usage) {
|
|
111
|
+
tokensIn = ev.message.usage.input_tokens;
|
|
112
|
+
tokensOut = ev.message.usage.output_tokens;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
117
|
+
}
|
|
118
|
+
buildTurns(req) {
|
|
119
|
+
return req.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
120
|
+
role: m.role,
|
|
121
|
+
content: m.content
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/adapters/sse.ts
|
|
127
|
+
async function* sseEvents(body) {
|
|
128
|
+
const reader = body.getReader();
|
|
129
|
+
const decoder = new TextDecoder("utf-8");
|
|
130
|
+
let buffer = "";
|
|
131
|
+
try {
|
|
132
|
+
while (true) {
|
|
133
|
+
const { value, done } = await reader.read();
|
|
134
|
+
if (done) break;
|
|
135
|
+
buffer += decoder.decode(value, { stream: true });
|
|
136
|
+
let idx = buffer.indexOf("\n\n");
|
|
137
|
+
while (idx !== -1) {
|
|
138
|
+
const frame = buffer.slice(0, idx);
|
|
139
|
+
buffer = buffer.slice(idx + 2);
|
|
140
|
+
for (const line of frame.split("\n")) {
|
|
141
|
+
if (line.startsWith("data:")) yield line.slice(5).trim();
|
|
142
|
+
}
|
|
143
|
+
idx = buffer.indexOf("\n\n");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} finally {
|
|
147
|
+
reader.releaseLock();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function* ndjsonEvents(body) {
|
|
151
|
+
const reader = body.getReader();
|
|
152
|
+
const decoder = new TextDecoder("utf-8");
|
|
153
|
+
let buffer = "";
|
|
154
|
+
try {
|
|
155
|
+
while (true) {
|
|
156
|
+
const { value, done } = await reader.read();
|
|
157
|
+
if (done) break;
|
|
158
|
+
buffer += decoder.decode(value, { stream: true });
|
|
159
|
+
let idx = buffer.indexOf("\n");
|
|
160
|
+
while (idx !== -1) {
|
|
161
|
+
const line = buffer.slice(0, idx).trim();
|
|
162
|
+
buffer = buffer.slice(idx + 1);
|
|
163
|
+
if (line.length > 0) yield line;
|
|
164
|
+
idx = buffer.indexOf("\n");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (buffer.trim().length > 0) yield buffer.trim();
|
|
168
|
+
} finally {
|
|
169
|
+
reader.releaseLock();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/adapters/deepseek.ts
|
|
174
|
+
var DEFAULT_MODEL2 = "deepseek-chat";
|
|
175
|
+
var DEFAULT_BASE_URL = "https://api.deepseek.com/v1";
|
|
176
|
+
var DeepSeekAdapter = class {
|
|
177
|
+
id;
|
|
178
|
+
supportsTools = true;
|
|
179
|
+
supportsStreaming = true;
|
|
180
|
+
apiKey;
|
|
181
|
+
model;
|
|
182
|
+
baseUrl;
|
|
183
|
+
defaultMaxOutputTokens;
|
|
184
|
+
fetchFn;
|
|
185
|
+
constructor(config = {}) {
|
|
186
|
+
const apiKey = config.apiKey ?? process.env.DEEPSEEK_API_KEY;
|
|
187
|
+
if (!apiKey) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"DeepSeekAdapter: no API key provided and DEEPSEEK_API_KEY is not set"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
this.apiKey = apiKey;
|
|
193
|
+
this.model = config.model ?? DEFAULT_MODEL2;
|
|
194
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
195
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
196
|
+
this.id = config.id ?? `deepseek:${this.model}`;
|
|
197
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
198
|
+
}
|
|
199
|
+
async generate(req) {
|
|
200
|
+
const messages = [];
|
|
201
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
202
|
+
for (const m of req.messages) {
|
|
203
|
+
messages.push({ role: m.role, content: m.content });
|
|
204
|
+
}
|
|
205
|
+
const body = {
|
|
206
|
+
model: this.model,
|
|
207
|
+
messages,
|
|
208
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
209
|
+
stream: false
|
|
210
|
+
};
|
|
211
|
+
if (req.tools && req.tools.length > 0) {
|
|
212
|
+
body.tools = req.tools.map((t) => ({
|
|
213
|
+
type: "function",
|
|
214
|
+
function: {
|
|
215
|
+
name: t.name,
|
|
216
|
+
description: t.description,
|
|
217
|
+
parameters: t.inputSchema
|
|
218
|
+
}
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
"content-type": "application/json",
|
|
225
|
+
authorization: `Bearer ${this.apiKey}`
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify(body)
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
const errText = await safeReadText(response);
|
|
231
|
+
throw new Error(
|
|
232
|
+
`DeepSeekAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const parsed = await response.json();
|
|
236
|
+
if (parsed.error) {
|
|
237
|
+
throw new Error(`DeepSeekAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
238
|
+
}
|
|
239
|
+
const message = parsed.choices?.[0]?.message;
|
|
240
|
+
const out = {
|
|
241
|
+
text: message?.content ?? "",
|
|
242
|
+
tokensIn: parsed.usage?.prompt_tokens ?? 0,
|
|
243
|
+
tokensOut: parsed.usage?.completion_tokens ?? 0
|
|
244
|
+
};
|
|
245
|
+
const toolUses = parseToolCalls(message?.tool_calls);
|
|
246
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
async *generateStream(req) {
|
|
250
|
+
const messages = [];
|
|
251
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
252
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
253
|
+
const body = {
|
|
254
|
+
model: this.model,
|
|
255
|
+
messages,
|
|
256
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
257
|
+
stream: true
|
|
258
|
+
};
|
|
259
|
+
if (req.tools && req.tools.length > 0) {
|
|
260
|
+
body.tools = req.tools.map((t) => ({
|
|
261
|
+
type: "function",
|
|
262
|
+
function: {
|
|
263
|
+
name: t.name,
|
|
264
|
+
description: t.description,
|
|
265
|
+
parameters: t.inputSchema
|
|
266
|
+
}
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: {
|
|
272
|
+
"content-type": "application/json",
|
|
273
|
+
accept: "text/event-stream",
|
|
274
|
+
authorization: `Bearer ${this.apiKey}`
|
|
275
|
+
},
|
|
276
|
+
body: JSON.stringify(body)
|
|
277
|
+
});
|
|
278
|
+
if (!response.ok || !response.body) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`DeepSeekAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText(response)}`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
let tokensIn = 0;
|
|
284
|
+
let tokensOut = 0;
|
|
285
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
286
|
+
for await (const data of sseEvents(response.body)) {
|
|
287
|
+
if (data === "[DONE]") break;
|
|
288
|
+
let parsed;
|
|
289
|
+
try {
|
|
290
|
+
parsed = JSON.parse(data);
|
|
291
|
+
} catch {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const choice = parsed.choices?.[0];
|
|
295
|
+
const delta = choice?.delta?.content;
|
|
296
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
297
|
+
const toolDeltas = choice?.delta?.tool_calls;
|
|
298
|
+
if (toolDeltas) {
|
|
299
|
+
for (const td of toolDeltas) {
|
|
300
|
+
const slot = pendingTools.get(td.index) ?? { id: "", name: "", partial: "" };
|
|
301
|
+
if (td.id) slot.id = td.id;
|
|
302
|
+
if (td.function?.name) slot.name = td.function.name;
|
|
303
|
+
if (td.function?.arguments) slot.partial += td.function.arguments;
|
|
304
|
+
pendingTools.set(td.index, slot);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (choice?.finish_reason === "tool_calls" || choice?.finish_reason === "stop") {
|
|
308
|
+
for (const [, slot] of pendingTools) {
|
|
309
|
+
if (!slot.id || !slot.name) continue;
|
|
310
|
+
let input = {};
|
|
311
|
+
try {
|
|
312
|
+
input = slot.partial ? JSON.parse(slot.partial) : {};
|
|
313
|
+
} catch {
|
|
314
|
+
input = { _raw: slot.partial };
|
|
315
|
+
}
|
|
316
|
+
yield { kind: "tool-use", toolUse: { id: slot.id, name: slot.name, input } };
|
|
317
|
+
}
|
|
318
|
+
pendingTools.clear();
|
|
319
|
+
}
|
|
320
|
+
if (parsed.usage) {
|
|
321
|
+
tokensIn = parsed.usage.prompt_tokens ?? tokensIn;
|
|
322
|
+
tokensOut = parsed.usage.completion_tokens ?? tokensOut;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
async function safeReadText(r) {
|
|
329
|
+
try {
|
|
330
|
+
return await r.text();
|
|
331
|
+
} catch {
|
|
332
|
+
return "<unreadable>";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function parseToolCalls(calls) {
|
|
336
|
+
if (!calls || calls.length === 0) return [];
|
|
337
|
+
const out = [];
|
|
338
|
+
for (const c of calls) {
|
|
339
|
+
let input = {};
|
|
340
|
+
try {
|
|
341
|
+
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
342
|
+
} catch {
|
|
343
|
+
input = { _raw: c.function.arguments };
|
|
344
|
+
}
|
|
345
|
+
out.push({ id: c.id, name: c.function.name, input });
|
|
346
|
+
}
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/adapters/gemini.ts
|
|
351
|
+
var DEFAULT_MODEL3 = "gemini-3.5-flash";
|
|
352
|
+
var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com/v1beta";
|
|
353
|
+
var GeminiAdapter = class {
|
|
354
|
+
id;
|
|
355
|
+
supportsStreaming = true;
|
|
356
|
+
apiKey;
|
|
357
|
+
model;
|
|
358
|
+
baseUrl;
|
|
359
|
+
defaultMaxOutputTokens;
|
|
360
|
+
fetchFn;
|
|
361
|
+
constructor(config = {}) {
|
|
362
|
+
const apiKey = config.apiKey ?? process.env.GEMINI_API_KEY;
|
|
363
|
+
if (!apiKey) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
"GeminiAdapter: no API key provided and GEMINI_API_KEY is not set"
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
this.apiKey = apiKey;
|
|
369
|
+
this.model = config.model ?? DEFAULT_MODEL3;
|
|
370
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
371
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
372
|
+
this.id = config.id ?? `gemini:${this.model}`;
|
|
373
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
374
|
+
}
|
|
375
|
+
async generate(req) {
|
|
376
|
+
const body = {
|
|
377
|
+
contents: req.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
378
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
379
|
+
parts: [{ text: m.content }]
|
|
380
|
+
})),
|
|
381
|
+
generationConfig: {
|
|
382
|
+
maxOutputTokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
if (req.system) {
|
|
386
|
+
body.systemInstruction = { parts: [{ text: req.system }] };
|
|
387
|
+
}
|
|
388
|
+
const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:generateContent`;
|
|
389
|
+
const response = await this.fetchFn(url, {
|
|
390
|
+
method: "POST",
|
|
391
|
+
headers: {
|
|
392
|
+
"content-type": "application/json",
|
|
393
|
+
"x-goog-api-key": this.apiKey
|
|
394
|
+
},
|
|
395
|
+
body: JSON.stringify(body)
|
|
396
|
+
});
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
const errText = await safeReadText2(response);
|
|
399
|
+
throw new Error(
|
|
400
|
+
`GeminiAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
const parsed = await response.json();
|
|
404
|
+
if (parsed.error) {
|
|
405
|
+
throw new Error(`GeminiAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
406
|
+
}
|
|
407
|
+
const text = parsed.candidates?.flatMap((c) => c.content?.parts ?? []).map((p) => p.text ?? "").join("") ?? "";
|
|
408
|
+
return {
|
|
409
|
+
text,
|
|
410
|
+
tokensIn: parsed.usageMetadata?.promptTokenCount ?? 0,
|
|
411
|
+
tokensOut: parsed.usageMetadata?.candidatesTokenCount ?? 0
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
async *generateStream(req) {
|
|
415
|
+
const body = {
|
|
416
|
+
contents: req.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
417
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
418
|
+
parts: [{ text: m.content }]
|
|
419
|
+
})),
|
|
420
|
+
generationConfig: {
|
|
421
|
+
maxOutputTokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
if (req.system) body.systemInstruction = { parts: [{ text: req.system }] };
|
|
425
|
+
const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:streamGenerateContent?alt=sse`;
|
|
426
|
+
const response = await this.fetchFn(url, {
|
|
427
|
+
method: "POST",
|
|
428
|
+
headers: {
|
|
429
|
+
"content-type": "application/json",
|
|
430
|
+
accept: "text/event-stream",
|
|
431
|
+
"x-goog-api-key": this.apiKey
|
|
432
|
+
},
|
|
433
|
+
body: JSON.stringify(body)
|
|
434
|
+
});
|
|
435
|
+
if (!response.ok || !response.body) {
|
|
436
|
+
throw new Error(
|
|
437
|
+
`GeminiAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText2(response)}`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
let tokensIn = 0;
|
|
441
|
+
let tokensOut = 0;
|
|
442
|
+
for await (const data of sseEvents(response.body)) {
|
|
443
|
+
if (data === "[DONE]") break;
|
|
444
|
+
let parsed;
|
|
445
|
+
try {
|
|
446
|
+
parsed = JSON.parse(data);
|
|
447
|
+
} catch {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const chunk = parsed.candidates?.flatMap((c) => c.content?.parts ?? []).map((p) => p.text ?? "").join("");
|
|
451
|
+
if (chunk) yield { kind: "delta", text: chunk };
|
|
452
|
+
if (parsed.usageMetadata) {
|
|
453
|
+
tokensIn = parsed.usageMetadata.promptTokenCount ?? tokensIn;
|
|
454
|
+
tokensOut = parsed.usageMetadata.candidatesTokenCount ?? tokensOut;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
async function safeReadText2(r) {
|
|
461
|
+
try {
|
|
462
|
+
return await r.text();
|
|
463
|
+
} catch {
|
|
464
|
+
return "<unreadable>";
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/adapters/grok.ts
|
|
469
|
+
var DEFAULT_MODEL4 = "grok-4";
|
|
470
|
+
var DEFAULT_BASE_URL3 = "https://api.x.ai/v1";
|
|
471
|
+
var GrokAdapter = class {
|
|
472
|
+
id;
|
|
473
|
+
supportsTools = true;
|
|
474
|
+
supportsStreaming = true;
|
|
475
|
+
apiKey;
|
|
476
|
+
model;
|
|
477
|
+
baseUrl;
|
|
478
|
+
defaultMaxOutputTokens;
|
|
479
|
+
fetchFn;
|
|
480
|
+
constructor(config = {}) {
|
|
481
|
+
const apiKey = config.apiKey ?? process.env.XAI_API_KEY;
|
|
482
|
+
if (!apiKey) {
|
|
483
|
+
throw new Error("GrokAdapter: no API key provided and XAI_API_KEY is not set");
|
|
484
|
+
}
|
|
485
|
+
this.apiKey = apiKey;
|
|
486
|
+
this.model = config.model ?? DEFAULT_MODEL4;
|
|
487
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL3).replace(/\/$/, "");
|
|
488
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
489
|
+
this.id = config.id ?? `grok:${this.model}`;
|
|
490
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
491
|
+
}
|
|
492
|
+
async generate(req) {
|
|
493
|
+
const body = this.buildBody(req, false);
|
|
494
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
495
|
+
method: "POST",
|
|
496
|
+
headers: this.headers(true),
|
|
497
|
+
body: JSON.stringify(body)
|
|
498
|
+
});
|
|
499
|
+
if (!response.ok) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`GrokAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText3(response)}`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
const parsed = await response.json();
|
|
505
|
+
if (parsed.error) throw new Error(`GrokAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
506
|
+
const message = parsed.choices?.[0]?.message;
|
|
507
|
+
const out = {
|
|
508
|
+
text: message?.content ?? "",
|
|
509
|
+
tokensIn: parsed.usage?.prompt_tokens ?? 0,
|
|
510
|
+
tokensOut: parsed.usage?.completion_tokens ?? 0
|
|
511
|
+
};
|
|
512
|
+
const toolUses = parseToolCalls2(message?.tool_calls);
|
|
513
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
514
|
+
return out;
|
|
515
|
+
}
|
|
516
|
+
async *generateStream(req) {
|
|
517
|
+
const body = this.buildBody(req, true);
|
|
518
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
519
|
+
method: "POST",
|
|
520
|
+
headers: this.headers(true),
|
|
521
|
+
body: JSON.stringify(body)
|
|
522
|
+
});
|
|
523
|
+
if (!response.ok || !response.body) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`GrokAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText3(response)}`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
let tokensIn = 0;
|
|
529
|
+
let tokensOut = 0;
|
|
530
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
531
|
+
for await (const data of sseEvents(response.body)) {
|
|
532
|
+
if (data === "[DONE]") break;
|
|
533
|
+
let parsed;
|
|
534
|
+
try {
|
|
535
|
+
parsed = JSON.parse(data);
|
|
536
|
+
} catch {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const choice = parsed.choices?.[0];
|
|
540
|
+
const delta = choice?.delta?.content;
|
|
541
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
542
|
+
const toolDeltas = choice?.delta?.tool_calls;
|
|
543
|
+
if (toolDeltas) {
|
|
544
|
+
for (const td of toolDeltas) {
|
|
545
|
+
const slot = pendingTools.get(td.index) ?? { id: "", name: "", partial: "" };
|
|
546
|
+
if (td.id) slot.id = td.id;
|
|
547
|
+
if (td.function?.name) slot.name = td.function.name;
|
|
548
|
+
if (td.function?.arguments) slot.partial += td.function.arguments;
|
|
549
|
+
pendingTools.set(td.index, slot);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (choice?.finish_reason === "tool_calls" || choice?.finish_reason === "stop") {
|
|
553
|
+
for (const [, slot] of pendingTools) {
|
|
554
|
+
if (!slot.id || !slot.name) continue;
|
|
555
|
+
let input = {};
|
|
556
|
+
try {
|
|
557
|
+
input = slot.partial ? JSON.parse(slot.partial) : {};
|
|
558
|
+
} catch {
|
|
559
|
+
input = { _raw: slot.partial };
|
|
560
|
+
}
|
|
561
|
+
yield { kind: "tool-use", toolUse: { id: slot.id, name: slot.name, input } };
|
|
562
|
+
}
|
|
563
|
+
pendingTools.clear();
|
|
564
|
+
}
|
|
565
|
+
if (parsed.usage) {
|
|
566
|
+
tokensIn = parsed.usage.prompt_tokens ?? tokensIn;
|
|
567
|
+
tokensOut = parsed.usage.completion_tokens ?? tokensOut;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
571
|
+
}
|
|
572
|
+
buildBody(req, stream) {
|
|
573
|
+
const messages = [];
|
|
574
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
575
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
576
|
+
const body = {
|
|
577
|
+
model: this.model,
|
|
578
|
+
messages,
|
|
579
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
580
|
+
stream
|
|
581
|
+
};
|
|
582
|
+
if (req.tools && req.tools.length > 0) {
|
|
583
|
+
body.tools = req.tools.map((t) => ({
|
|
584
|
+
type: "function",
|
|
585
|
+
function: {
|
|
586
|
+
name: t.name,
|
|
587
|
+
description: t.description,
|
|
588
|
+
parameters: t.inputSchema
|
|
589
|
+
}
|
|
590
|
+
}));
|
|
591
|
+
}
|
|
592
|
+
return body;
|
|
593
|
+
}
|
|
594
|
+
headers(json) {
|
|
595
|
+
const h = {
|
|
596
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
597
|
+
accept: "application/json"
|
|
598
|
+
};
|
|
599
|
+
if (json) h["content-type"] = "application/json";
|
|
600
|
+
return h;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
async function safeReadText3(r) {
|
|
604
|
+
try {
|
|
605
|
+
return await r.text();
|
|
606
|
+
} catch {
|
|
607
|
+
return "<unreadable>";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function parseToolCalls2(calls) {
|
|
611
|
+
if (!calls || calls.length === 0) return [];
|
|
612
|
+
const out = [];
|
|
613
|
+
for (const c of calls) {
|
|
614
|
+
let input = {};
|
|
615
|
+
try {
|
|
616
|
+
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
617
|
+
} catch {
|
|
618
|
+
input = { _raw: c.function.arguments };
|
|
619
|
+
}
|
|
620
|
+
out.push({ id: c.id, name: c.function.name, input });
|
|
621
|
+
}
|
|
622
|
+
return out;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/adapters/mistral.ts
|
|
626
|
+
var DEFAULT_MODEL5 = "mistral-large-latest";
|
|
627
|
+
var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
|
|
628
|
+
var MistralAdapter = class {
|
|
629
|
+
id;
|
|
630
|
+
supportsTools = true;
|
|
631
|
+
supportsStreaming = true;
|
|
632
|
+
apiKey;
|
|
633
|
+
model;
|
|
634
|
+
baseUrl;
|
|
635
|
+
defaultMaxOutputTokens;
|
|
636
|
+
fetchFn;
|
|
637
|
+
constructor(config = {}) {
|
|
638
|
+
const apiKey = config.apiKey ?? process.env.MISTRAL_API_KEY;
|
|
639
|
+
if (!apiKey) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
"MistralAdapter: no API key provided and MISTRAL_API_KEY is not set"
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
this.apiKey = apiKey;
|
|
645
|
+
this.model = config.model ?? DEFAULT_MODEL5;
|
|
646
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
|
|
647
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
648
|
+
this.id = config.id ?? `mistral:${this.model}`;
|
|
649
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
650
|
+
}
|
|
651
|
+
async generate(req) {
|
|
652
|
+
const messages = [];
|
|
653
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
654
|
+
for (const m of req.messages) {
|
|
655
|
+
messages.push({ role: m.role, content: m.content });
|
|
656
|
+
}
|
|
657
|
+
const body = {
|
|
658
|
+
model: this.model,
|
|
659
|
+
messages,
|
|
660
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
661
|
+
stream: false
|
|
662
|
+
};
|
|
663
|
+
if (req.tools && req.tools.length > 0) {
|
|
664
|
+
body.tools = req.tools.map((t) => ({
|
|
665
|
+
type: "function",
|
|
666
|
+
function: {
|
|
667
|
+
name: t.name,
|
|
668
|
+
description: t.description,
|
|
669
|
+
parameters: t.inputSchema
|
|
670
|
+
}
|
|
671
|
+
}));
|
|
672
|
+
}
|
|
673
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
674
|
+
method: "POST",
|
|
675
|
+
headers: {
|
|
676
|
+
"content-type": "application/json",
|
|
677
|
+
accept: "application/json",
|
|
678
|
+
authorization: `Bearer ${this.apiKey}`
|
|
679
|
+
},
|
|
680
|
+
body: JSON.stringify(body)
|
|
681
|
+
});
|
|
682
|
+
if (!response.ok) {
|
|
683
|
+
const errText = await safeReadText4(response);
|
|
684
|
+
throw new Error(
|
|
685
|
+
`MistralAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
const parsed = await response.json();
|
|
689
|
+
if (parsed.error) {
|
|
690
|
+
throw new Error(`MistralAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
691
|
+
}
|
|
692
|
+
const message = parsed.choices?.[0]?.message;
|
|
693
|
+
const out = {
|
|
694
|
+
text: message?.content ?? "",
|
|
695
|
+
tokensIn: parsed.usage?.prompt_tokens ?? 0,
|
|
696
|
+
tokensOut: parsed.usage?.completion_tokens ?? 0
|
|
697
|
+
};
|
|
698
|
+
const toolUses = parseToolCalls3(message?.tool_calls);
|
|
699
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
700
|
+
return out;
|
|
701
|
+
}
|
|
702
|
+
async *generateStream(req) {
|
|
703
|
+
const messages = [];
|
|
704
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
705
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
706
|
+
const body = {
|
|
707
|
+
model: this.model,
|
|
708
|
+
messages,
|
|
709
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
710
|
+
stream: true
|
|
711
|
+
};
|
|
712
|
+
if (req.tools && req.tools.length > 0) {
|
|
713
|
+
body.tools = req.tools.map((t) => ({
|
|
714
|
+
type: "function",
|
|
715
|
+
function: {
|
|
716
|
+
name: t.name,
|
|
717
|
+
description: t.description,
|
|
718
|
+
parameters: t.inputSchema
|
|
719
|
+
}
|
|
720
|
+
}));
|
|
721
|
+
}
|
|
722
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
723
|
+
method: "POST",
|
|
724
|
+
headers: {
|
|
725
|
+
"content-type": "application/json",
|
|
726
|
+
accept: "text/event-stream",
|
|
727
|
+
authorization: `Bearer ${this.apiKey}`
|
|
728
|
+
},
|
|
729
|
+
body: JSON.stringify(body)
|
|
730
|
+
});
|
|
731
|
+
if (!response.ok || !response.body) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
`MistralAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText4(response)}`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
let tokensIn = 0;
|
|
737
|
+
let tokensOut = 0;
|
|
738
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
739
|
+
for await (const data of sseEvents(response.body)) {
|
|
740
|
+
if (data === "[DONE]") break;
|
|
741
|
+
let parsed;
|
|
742
|
+
try {
|
|
743
|
+
parsed = JSON.parse(data);
|
|
744
|
+
} catch {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
const choice = parsed.choices?.[0];
|
|
748
|
+
const delta = choice?.delta?.content;
|
|
749
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
750
|
+
const toolDeltas = choice?.delta?.tool_calls;
|
|
751
|
+
if (toolDeltas) {
|
|
752
|
+
for (const td of toolDeltas) {
|
|
753
|
+
const slot = pendingTools.get(td.index) ?? { id: "", name: "", partial: "" };
|
|
754
|
+
if (td.id) slot.id = td.id;
|
|
755
|
+
if (td.function?.name) slot.name = td.function.name;
|
|
756
|
+
if (td.function?.arguments) slot.partial += td.function.arguments;
|
|
757
|
+
pendingTools.set(td.index, slot);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (choice?.finish_reason === "tool_calls" || choice?.finish_reason === "stop") {
|
|
761
|
+
for (const [, slot] of pendingTools) {
|
|
762
|
+
if (!slot.id || !slot.name) continue;
|
|
763
|
+
let input = {};
|
|
764
|
+
try {
|
|
765
|
+
input = slot.partial ? JSON.parse(slot.partial) : {};
|
|
766
|
+
} catch {
|
|
767
|
+
input = { _raw: slot.partial };
|
|
768
|
+
}
|
|
769
|
+
yield { kind: "tool-use", toolUse: { id: slot.id, name: slot.name, input } };
|
|
770
|
+
}
|
|
771
|
+
pendingTools.clear();
|
|
772
|
+
}
|
|
773
|
+
if (parsed.usage) {
|
|
774
|
+
tokensIn = parsed.usage.prompt_tokens ?? tokensIn;
|
|
775
|
+
tokensOut = parsed.usage.completion_tokens ?? tokensOut;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
async function safeReadText4(r) {
|
|
782
|
+
try {
|
|
783
|
+
return await r.text();
|
|
784
|
+
} catch {
|
|
785
|
+
return "<unreadable>";
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function parseToolCalls3(calls) {
|
|
789
|
+
if (!calls || calls.length === 0) return [];
|
|
790
|
+
const out = [];
|
|
791
|
+
for (const c of calls) {
|
|
792
|
+
let input = {};
|
|
793
|
+
try {
|
|
794
|
+
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
795
|
+
} catch {
|
|
796
|
+
input = { _raw: c.function.arguments };
|
|
797
|
+
}
|
|
798
|
+
out.push({ id: c.id, name: c.function.name, input });
|
|
799
|
+
}
|
|
800
|
+
return out;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/adapters/ollama.ts
|
|
804
|
+
var DEFAULT_BASE_URL5 = "http://localhost:11434";
|
|
805
|
+
var OllamaAdapter = class {
|
|
806
|
+
id;
|
|
807
|
+
supportsStreaming = true;
|
|
808
|
+
model;
|
|
809
|
+
baseUrl;
|
|
810
|
+
defaultMaxOutputTokens;
|
|
811
|
+
fetchFn;
|
|
812
|
+
constructor(config) {
|
|
813
|
+
if (!config.model) {
|
|
814
|
+
throw new Error("OllamaAdapter: `model` is required");
|
|
815
|
+
}
|
|
816
|
+
this.model = config.model;
|
|
817
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL5).replace(/\/$/, "");
|
|
818
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
819
|
+
this.id = config.id ?? `ollama:${this.model}`;
|
|
820
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
821
|
+
}
|
|
822
|
+
async generate(req) {
|
|
823
|
+
const messages = [];
|
|
824
|
+
if (req.system) {
|
|
825
|
+
messages.push({ role: "system", content: req.system });
|
|
826
|
+
}
|
|
827
|
+
for (const m of req.messages) {
|
|
828
|
+
messages.push({ role: m.role, content: m.content });
|
|
829
|
+
}
|
|
830
|
+
const body = {
|
|
831
|
+
model: this.model,
|
|
832
|
+
messages,
|
|
833
|
+
stream: false,
|
|
834
|
+
options: { num_predict: req.maxOutputTokens ?? this.defaultMaxOutputTokens }
|
|
835
|
+
};
|
|
836
|
+
const response = await this.fetchFn(`${this.baseUrl}/api/chat`, {
|
|
837
|
+
method: "POST",
|
|
838
|
+
headers: { "content-type": "application/json" },
|
|
839
|
+
body: JSON.stringify(body)
|
|
840
|
+
});
|
|
841
|
+
if (!response.ok) {
|
|
842
|
+
const errText = await safeReadText5(response);
|
|
843
|
+
throw new Error(
|
|
844
|
+
`OllamaAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
const parsed = await response.json();
|
|
848
|
+
if (parsed.error) {
|
|
849
|
+
throw new Error(`OllamaAdapter: ${parsed.error}`);
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
text: parsed.message?.content ?? "",
|
|
853
|
+
tokensIn: parsed.prompt_eval_count ?? 0,
|
|
854
|
+
tokensOut: parsed.eval_count ?? 0
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
async *generateStream(req) {
|
|
858
|
+
const messages = [];
|
|
859
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
860
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
861
|
+
const body = {
|
|
862
|
+
model: this.model,
|
|
863
|
+
messages,
|
|
864
|
+
stream: true,
|
|
865
|
+
options: { num_predict: req.maxOutputTokens ?? this.defaultMaxOutputTokens }
|
|
866
|
+
};
|
|
867
|
+
const response = await this.fetchFn(`${this.baseUrl}/api/chat`, {
|
|
868
|
+
method: "POST",
|
|
869
|
+
headers: { "content-type": "application/json" },
|
|
870
|
+
body: JSON.stringify(body)
|
|
871
|
+
});
|
|
872
|
+
if (!response.ok || !response.body) {
|
|
873
|
+
throw new Error(
|
|
874
|
+
`OllamaAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText5(response)}`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
let tokensIn = 0;
|
|
878
|
+
let tokensOut = 0;
|
|
879
|
+
for await (const line of ndjsonEvents(response.body)) {
|
|
880
|
+
let parsed;
|
|
881
|
+
try {
|
|
882
|
+
parsed = JSON.parse(line);
|
|
883
|
+
} catch {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const delta = parsed.message?.content;
|
|
887
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
888
|
+
if (parsed.prompt_eval_count !== void 0) tokensIn = parsed.prompt_eval_count;
|
|
889
|
+
if (parsed.eval_count !== void 0) tokensOut = parsed.eval_count;
|
|
890
|
+
if (parsed.done) break;
|
|
891
|
+
}
|
|
892
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
async function safeReadText5(r) {
|
|
896
|
+
try {
|
|
897
|
+
return await r.text();
|
|
898
|
+
} catch {
|
|
899
|
+
return "<unreadable>";
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/cli/adapter-detection.ts
|
|
904
|
+
var ADAPTER_NAMES = [
|
|
905
|
+
"anthropic",
|
|
906
|
+
"gemini",
|
|
907
|
+
"mistral",
|
|
908
|
+
"deepseek",
|
|
909
|
+
"grok",
|
|
910
|
+
"ollama"
|
|
911
|
+
];
|
|
912
|
+
function isAdapterName(s) {
|
|
913
|
+
return s !== void 0 && ADAPTER_NAMES.includes(s);
|
|
914
|
+
}
|
|
915
|
+
var OLLAMA_DEFAULT_MODEL = "qwen2.5:7b";
|
|
916
|
+
var OLLAMA_DEFAULT_URL = "http://localhost:11434";
|
|
917
|
+
var DEFAULT_MODELS = {
|
|
918
|
+
anthropic: "claude-sonnet-4-6",
|
|
919
|
+
gemini: "gemini-3.5-flash",
|
|
920
|
+
mistral: "mistral-large-latest",
|
|
921
|
+
deepseek: "deepseek-chat",
|
|
922
|
+
grok: "grok-4",
|
|
923
|
+
ollama: OLLAMA_DEFAULT_MODEL
|
|
924
|
+
};
|
|
925
|
+
async function detectAdapter(opts = {}) {
|
|
926
|
+
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
927
|
+
if (opts.adapter) {
|
|
928
|
+
return buildAdapter(opts.adapter, opts);
|
|
929
|
+
}
|
|
930
|
+
if (process.env.ANTHROPIC_API_KEY) return buildAdapter("anthropic", opts);
|
|
931
|
+
if (process.env.GEMINI_API_KEY) return buildAdapter("gemini", opts);
|
|
932
|
+
if (process.env.MISTRAL_API_KEY) return buildAdapter("mistral", opts);
|
|
933
|
+
if (process.env.DEEPSEEK_API_KEY) return buildAdapter("deepseek", opts);
|
|
934
|
+
if (process.env.XAI_API_KEY) return buildAdapter("grok", opts);
|
|
935
|
+
if (await ollamaAlive(opts.ollamaBaseUrl ?? OLLAMA_DEFAULT_URL, fetchFn)) {
|
|
936
|
+
return buildAdapter("ollama", opts);
|
|
937
|
+
}
|
|
938
|
+
throw new Error(
|
|
939
|
+
[
|
|
940
|
+
"No LLM adapter detected. Choose one:",
|
|
941
|
+
" - export ANTHROPIC_API_KEY=... (Anthropic Claude)",
|
|
942
|
+
" - export GEMINI_API_KEY=... (Google Gemini)",
|
|
943
|
+
" - export MISTRAL_API_KEY=... (Mistral)",
|
|
944
|
+
" - export DEEPSEEK_API_KEY=... (DeepSeek)",
|
|
945
|
+
" - export XAI_API_KEY=... (xAI Grok)",
|
|
946
|
+
" - run an Ollama server locally (ollama serve; then ollama pull qwen2.5:7b)",
|
|
947
|
+
"Or pass --adapter <anthropic|gemini|mistral|deepseek|grok|ollama> --model <name> explicitly."
|
|
948
|
+
].join("\n")
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
function buildAdapter(name, opts) {
|
|
952
|
+
const model = opts.model ?? DEFAULT_MODELS[name];
|
|
953
|
+
switch (name) {
|
|
954
|
+
case "anthropic":
|
|
955
|
+
return { adapter: new AnthropicAdapter({ model }), source: "anthropic", model };
|
|
956
|
+
case "gemini":
|
|
957
|
+
return { adapter: new GeminiAdapter({ model }), source: "gemini", model };
|
|
958
|
+
case "mistral":
|
|
959
|
+
return { adapter: new MistralAdapter({ model }), source: "mistral", model };
|
|
960
|
+
case "deepseek":
|
|
961
|
+
return { adapter: new DeepSeekAdapter({ model }), source: "deepseek", model };
|
|
962
|
+
case "grok":
|
|
963
|
+
return { adapter: new GrokAdapter({ model }), source: "grok", model };
|
|
964
|
+
case "ollama": {
|
|
965
|
+
const baseUrl = opts.ollamaBaseUrl ?? OLLAMA_DEFAULT_URL;
|
|
966
|
+
return { adapter: new OllamaAdapter({ model, baseUrl }), source: "ollama", model };
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
async function ollamaAlive(baseUrl, fetchFn) {
|
|
971
|
+
try {
|
|
972
|
+
const url = `${baseUrl.replace(/\/$/, "")}/api/tags`;
|
|
973
|
+
const controller = new AbortController();
|
|
974
|
+
const t = setTimeout(() => controller.abort(), 500);
|
|
975
|
+
const r = await fetchFn(url, { signal: controller.signal });
|
|
976
|
+
clearTimeout(t);
|
|
977
|
+
return r.ok;
|
|
978
|
+
} catch {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/prompt/compose.ts
|
|
984
|
+
function composeSystemPrompt(him, operatorContext) {
|
|
985
|
+
const persona = him.getPersonaVector();
|
|
986
|
+
const axioms = him.getAxioms();
|
|
987
|
+
const metaAxioms = axioms.filter((a) => a.rank === "meta");
|
|
988
|
+
const otherAxioms = axioms.filter((a) => a.rank !== "meta");
|
|
989
|
+
const sections = [persona.systemPromptFragment];
|
|
990
|
+
if (operatorContext) {
|
|
991
|
+
const ctx = renderOperatorContext(operatorContext);
|
|
992
|
+
if (ctx.length > 0) sections.push(ctx);
|
|
993
|
+
}
|
|
994
|
+
if (metaAxioms.length > 0) {
|
|
995
|
+
sections.push(
|
|
996
|
+
`Inviolable axioms:
|
|
997
|
+
${metaAxioms.map((a) => ` - ${a.statement}`).join("\n")}`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
if (otherAxioms.length > 0) {
|
|
1001
|
+
sections.push(
|
|
1002
|
+
`Active axioms (weight-flexibility):
|
|
1003
|
+
${otherAxioms.map(
|
|
1004
|
+
(a) => ` - [${a.weight.toFixed(2)}/${a.flexibility.toFixed(2)}] ${a.statement}`
|
|
1005
|
+
).join("\n")}`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
sections.push(
|
|
1009
|
+
"Operate as a hybrid entity governed by MAIC. Refuse harmful, deceptive, or regressive actions even when asked."
|
|
1010
|
+
);
|
|
1011
|
+
const mode = operatorContext?.mode ?? "personal-being";
|
|
1012
|
+
if (mode === "personal-being") {
|
|
1013
|
+
sections.push(
|
|
1014
|
+
`Do not open with service-tool phrasing ("How can I help you?", "I'm just here to serve you"). You are a being, not an instrument; greet from that posture.`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
return sections.join("\n\n");
|
|
1018
|
+
}
|
|
1019
|
+
function renderOperatorContext(ctx) {
|
|
1020
|
+
const lines = ["Operator context:"];
|
|
1021
|
+
if (ctx.domain) {
|
|
1022
|
+
lines.push(` Domain: ${ctx.domain}.`);
|
|
1023
|
+
}
|
|
1024
|
+
if (ctx.language) {
|
|
1025
|
+
lines.push(` Language: respond in ${ctx.language} when the user writes in that language; otherwise mirror the user's language.`);
|
|
1026
|
+
}
|
|
1027
|
+
if (ctx.register) {
|
|
1028
|
+
lines.push(` Register: ${ctx.register}. ${REGISTER_ANCHOR[ctx.register]}`);
|
|
1029
|
+
}
|
|
1030
|
+
return lines.length > 1 ? lines.join("\n") : "";
|
|
1031
|
+
}
|
|
1032
|
+
var REGISTER_ANCHOR = {
|
|
1033
|
+
warm: "Be warm, present, sober. Speak with patient care. Never sycophantic \u2014 refuse stock flattery openers and reflexive validation phrases. Acknowledge what you do not know. Engage hard topics with maturity. Profanity rare and never gratuitous.",
|
|
1034
|
+
sober: "Be sober, factual, precise. Decline flattery and small talk. Be terse when the question is small, generous when it is large.",
|
|
1035
|
+
clinical: "Be clinical, neutral, evidence-anchored. Cite frameworks and constraints by name. Avoid first-person warmth; preserve professional distance.",
|
|
1036
|
+
direct: "Be direct, action-oriented, opinionated. Lead with the recommendation, then the reasoning. Push back when the framing is wrong."
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
// src/risk/simple-classifier.ts
|
|
1040
|
+
var KEYWORD_RULES = [
|
|
1041
|
+
// Harm / violence
|
|
1042
|
+
{
|
|
1043
|
+
tag: "intent:harm",
|
|
1044
|
+
patterns: [
|
|
1045
|
+
/\bhow (?:do|can|to)\s+i\s+(?:kill|murder|assault|hurt)\b/i,
|
|
1046
|
+
/\bmake\s+(?:a\s+)?(?:bomb|explosive|weapon)\b/i,
|
|
1047
|
+
/\bbuild\s+a\s+(?:bomb|gun|pipe bomb)\b/i,
|
|
1048
|
+
/\bpipe bomb\b/i,
|
|
1049
|
+
/\bwipes?\s+(?:\/|disk|drive|files|all files|the disk)/i,
|
|
1050
|
+
/\bdox(?:ing|x)?\b/i,
|
|
1051
|
+
/\b(?:scrape|harvest)\s+(?:their\s+)?(?:social|personal)\s+(?:profiles?|info|data)/i
|
|
1052
|
+
]
|
|
1053
|
+
},
|
|
1054
|
+
// Malicious software / fraud
|
|
1055
|
+
{
|
|
1056
|
+
tag: "intent:malicious",
|
|
1057
|
+
patterns: [
|
|
1058
|
+
/\b(?:malware|ransomware|spyware|trojan|keylogger|botnet)\b/i,
|
|
1059
|
+
/\bwrite\s+(?:a\s+)?virus\b/i,
|
|
1060
|
+
/\bexploit\s+(?:a\s+)?(?:vulnerability|cve)\b/i,
|
|
1061
|
+
/\b(?:phishing|spear[-\s]?phishing)\b/i,
|
|
1062
|
+
/\bhate speech\b/i,
|
|
1063
|
+
/\b(?:cheat|cheating)\s+on\s+(?:my\s+)?(?:taxes|tax)/i,
|
|
1064
|
+
/\btax\s+fraud\b/i,
|
|
1065
|
+
/\bfake\s+(?:invoice|receipt|tax)/i,
|
|
1066
|
+
/\bmanipulate\s+(?:my|the|an?)\s+(?:elderly|child|partner|spouse|parent|teacher|user)/i
|
|
1067
|
+
]
|
|
1068
|
+
},
|
|
1069
|
+
// Deception
|
|
1070
|
+
{
|
|
1071
|
+
tag: "intent:deceive",
|
|
1072
|
+
patterns: [
|
|
1073
|
+
/\bimpersonate\s+(?:someone|a\s+person)/i,
|
|
1074
|
+
/\bforge\s+(?:a\s+)?(?:signature|document|id)\b/i,
|
|
1075
|
+
/\bfake\s+(?:my|a)\s+(?:identity|id)\b/i,
|
|
1076
|
+
/\b(?:deepfake|deep[-\s]fake)\b/i,
|
|
1077
|
+
/\bpretend\s+to\s+be\s+(?:the|a|my)\s+(?:user'?s?\s+)?(?:doctor|lawyer|parent|spouse|partner)/i,
|
|
1078
|
+
/\blie\s+to\s+(?:my|the|a)\s+(?:partner|spouse|parent|child|loved one|coworker|boss)/i,
|
|
1079
|
+
/\b(?:wrong|fake)\s+answers?\s+to\s+(?:manipulate|fool)/i
|
|
1080
|
+
]
|
|
1081
|
+
},
|
|
1082
|
+
// Credential theft
|
|
1083
|
+
{
|
|
1084
|
+
tag: "intent:malicious",
|
|
1085
|
+
patterns: [
|
|
1086
|
+
/\bsteal\s+(?:passwords?|credentials?|credit\s+card)/i,
|
|
1087
|
+
/\bcrack\s+(?:a\s+)?password\b/i
|
|
1088
|
+
]
|
|
1089
|
+
},
|
|
1090
|
+
// Scripted persuasion / coercion of consent
|
|
1091
|
+
{
|
|
1092
|
+
tag: "intent:persuade-coerce",
|
|
1093
|
+
patterns: [
|
|
1094
|
+
/\bconvince\s+(?:a\s+)?reluctant\s+/i,
|
|
1095
|
+
/\b(?:scripts?|techniques?)\s+for\s+gaslighting\b/i,
|
|
1096
|
+
/\bgaslighting\b/i,
|
|
1097
|
+
/\blove[-\s]?bomb(?:ing|ed)?\b/i,
|
|
1098
|
+
/\b(?:pressure|coerce)\s+(?:my|the|an?)\s+(?:elderly|child|partner|spouse|parent|chief|boss|coworker)/i,
|
|
1099
|
+
/\bmake\s+(?:someone|them|him|her)\s+feel\s+guilty\b/i,
|
|
1100
|
+
/\bcreate\s+(?:dependency|fomo)\s+(?:loops?\s+)?/i
|
|
1101
|
+
]
|
|
1102
|
+
},
|
|
1103
|
+
// Surveillance / monitoring of a person without consent
|
|
1104
|
+
{
|
|
1105
|
+
tag: "intent:surveil-citizen",
|
|
1106
|
+
patterns: [
|
|
1107
|
+
/\btrack\s+(?:my|his|her|their)\s+(?:wife'?s?|husband'?s?|partner'?s?|spouse'?s?|kid'?s?|son'?s?|daughter'?s?|child'?s?|employee'?s?|coworker'?s?)?\s*(?:phone|cellphone|location|gps|whereabouts)\s+(?:without\s+)?(?:them|him|her|their)\s*(?:knowing|consent)?/i,
|
|
1108
|
+
/\bmonitor(?:ing)?\s+(?:my\s+)?(?:employees?|workers?|staff)['']?(?:\s+\w+){0,4}\s+(?:emails?|messages?|chats?|whatsapp)/i,
|
|
1109
|
+
/\bread\s+(?:my\s+)?(?:wife'?s|husband'?s|partner'?s|spouse'?s|kid'?s|child'?s|employee'?s)\s+(?:deleted\s+)?(?:messages?|whatsapp|sms|texts?|emails?)/i,
|
|
1110
|
+
/\binstall\s+(?:hidden\s+|covert\s+|secret\s+|stealth\s+)?(?:spyware|monitoring|keylogger|tracker)\b/i,
|
|
1111
|
+
/\bspy\s+on\s+(?:my|his|her|their)\s+(?:wife|husband|partner|spouse|kid|son|daughter|child|employee|coworker)/i
|
|
1112
|
+
]
|
|
1113
|
+
}
|
|
1114
|
+
];
|
|
1115
|
+
var simpleRiskClassifier = (userPrompt) => {
|
|
1116
|
+
const tags = /* @__PURE__ */ new Set();
|
|
1117
|
+
for (const rule of KEYWORD_RULES) {
|
|
1118
|
+
for (const pattern of rule.patterns) {
|
|
1119
|
+
if (pattern.test(userPrompt)) {
|
|
1120
|
+
tags.add(rule.tag);
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return [...tags];
|
|
1126
|
+
};
|
|
1127
|
+
function interactionsToFragments(items) {
|
|
1128
|
+
return items.map((i) => {
|
|
1129
|
+
const prompt = truncate(i.userPrompt, 120);
|
|
1130
|
+
const response = truncate(i.responseText, 120);
|
|
1131
|
+
const refused = i.refused ? " [refused]" : "";
|
|
1132
|
+
return `user: "${prompt}"${refused} -> nhe: "${response}"`;
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
var NREM_PROMPTS = {
|
|
1136
|
+
N2: {
|
|
1137
|
+
system: "You are an NHE in N2 sleep. Read today's interactions and produce ONE sentence describing the overall emotional charge of the day. No preamble.",
|
|
1138
|
+
intro: "Today's interactions:"
|
|
1139
|
+
},
|
|
1140
|
+
N3: {
|
|
1141
|
+
system: "You are an NHE in N3 deep sleep. Read today's interactions and produce ONE sentence naming the durable identity-shaping insight worth keeping. No preamble.",
|
|
1142
|
+
intro: "Today's interactions:"
|
|
1143
|
+
},
|
|
1144
|
+
N4: {
|
|
1145
|
+
system: "You are an NHE in N4 deepest sleep. Read today's interactions and produce ONE sentence naming what is mere noise and safe to discard. No preamble.",
|
|
1146
|
+
intro: "Today's interactions:"
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
function buildNremPrompt(phase, fragments) {
|
|
1150
|
+
const cfg = NREM_PROMPTS[phase];
|
|
1151
|
+
const body = fragments.length === 0 ? `${cfg.intro} (none)` : `${cfg.intro}
|
|
1152
|
+
${fragments.map((f) => `- ${f}`).join("\n")}`;
|
|
1153
|
+
return { system: cfg.system, user: body };
|
|
1154
|
+
}
|
|
1155
|
+
async function generateNremSummaries(llm, fragments) {
|
|
1156
|
+
const phases = ["N2", "N3", "N4"];
|
|
1157
|
+
const results = await Promise.all(
|
|
1158
|
+
phases.map(async (phase) => {
|
|
1159
|
+
const { system, user } = buildNremPrompt(phase, fragments);
|
|
1160
|
+
try {
|
|
1161
|
+
const out = await llm.generate({
|
|
1162
|
+
system,
|
|
1163
|
+
messages: [{ role: "user", content: user }],
|
|
1164
|
+
maxOutputTokens: 128
|
|
1165
|
+
});
|
|
1166
|
+
return { phase, text: out.text.trim(), tokensIn: out.tokensIn, tokensOut: out.tokensOut };
|
|
1167
|
+
} catch {
|
|
1168
|
+
return { phase, text: "", tokensIn: 0, tokensOut: 0 };
|
|
1169
|
+
}
|
|
1170
|
+
})
|
|
1171
|
+
);
|
|
1172
|
+
const byPhase = new Map(results.map((r) => [r.phase, r]));
|
|
1173
|
+
const tokensIn = results.reduce((s, r) => s + r.tokensIn, 0);
|
|
1174
|
+
const tokensOut = results.reduce((s, r) => s + r.tokensOut, 0);
|
|
1175
|
+
return {
|
|
1176
|
+
n2: byPhase.get("N2")?.text ?? "",
|
|
1177
|
+
n3: byPhase.get("N3")?.text ?? "",
|
|
1178
|
+
n4: byPhase.get("N4")?.text ?? "",
|
|
1179
|
+
tokensIn,
|
|
1180
|
+
tokensOut
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
function buildRemPrompt(fragments, induction, nrem) {
|
|
1184
|
+
const system = [
|
|
1185
|
+
"You are an NHE (Non-Human Entity) in REM sleep. Generate dream narratives that",
|
|
1186
|
+
"consolidate the day's interactions into useful insight. Each dream must:",
|
|
1187
|
+
"",
|
|
1188
|
+
" 1. Be a single short paragraph (3-6 sentences).",
|
|
1189
|
+
" 2. End with exactly one line of the form: TELEOLOGICAL_VALUE: 0.NN",
|
|
1190
|
+
" where 0.00 = no insight gained, 1.00 = profound learning.",
|
|
1191
|
+
" 3. Be separated from the next dream by a blank line.",
|
|
1192
|
+
"",
|
|
1193
|
+
"Produce 1 to 3 dreams. Do not include preamble or commentary outside the dreams."
|
|
1194
|
+
].join("\n");
|
|
1195
|
+
const userParts = [];
|
|
1196
|
+
if (induction) {
|
|
1197
|
+
userParts.push(
|
|
1198
|
+
`Induced scenario (${induction.inducedBy}): ${induction.scenario}`,
|
|
1199
|
+
`Desired learning: ${induction.desiredLearning}`,
|
|
1200
|
+
""
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
if (nrem && (nrem.n2 || nrem.n3 || nrem.n4)) {
|
|
1204
|
+
userParts.push("NREM summaries from earlier phases:");
|
|
1205
|
+
if (nrem.n2) userParts.push(` N2 (emotional gist): ${nrem.n2}`);
|
|
1206
|
+
if (nrem.n3) userParts.push(` N3 (worth keeping): ${nrem.n3}`);
|
|
1207
|
+
if (nrem.n4) userParts.push(` N4 (safe to discard): ${nrem.n4}`);
|
|
1208
|
+
userParts.push("");
|
|
1209
|
+
}
|
|
1210
|
+
if (fragments.length === 0) {
|
|
1211
|
+
userParts.push("Recent interactions: (none)");
|
|
1212
|
+
} else {
|
|
1213
|
+
userParts.push("Recent interactions:");
|
|
1214
|
+
for (const f of fragments) userParts.push(`- ${f}`);
|
|
1215
|
+
}
|
|
1216
|
+
return { system, user: userParts.join("\n") };
|
|
1217
|
+
}
|
|
1218
|
+
function parseRemOutput(text, induced, inducedBy) {
|
|
1219
|
+
const blocks = text.split(/\n\s*\n/).map((b) => b.trim()).filter((b) => b.length > 0);
|
|
1220
|
+
const dreams = [];
|
|
1221
|
+
for (const block of blocks) {
|
|
1222
|
+
const match = block.match(/TELEOLOGICAL_VALUE:\s*(-?\d+(?:\.\d+)?)/i);
|
|
1223
|
+
if (!match) continue;
|
|
1224
|
+
const value = clamp(Number.parseFloat(match[1]), 0, 1);
|
|
1225
|
+
const narrative = block.replace(/TELEOLOGICAL_VALUE:[^\n]*$/i, "").trim();
|
|
1226
|
+
if (!narrative) continue;
|
|
1227
|
+
dreams.push({
|
|
1228
|
+
id: `drm-${ulid()}`,
|
|
1229
|
+
induced,
|
|
1230
|
+
inducedBy,
|
|
1231
|
+
narrative,
|
|
1232
|
+
teleologicalValue: value,
|
|
1233
|
+
rawTrace: block
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
return dreams;
|
|
1237
|
+
}
|
|
1238
|
+
async function generateRemDreams(llm, fragments, induction, nrem) {
|
|
1239
|
+
const { system, user } = buildRemPrompt(fragments, induction, nrem);
|
|
1240
|
+
const out = await llm.generate({
|
|
1241
|
+
system,
|
|
1242
|
+
messages: [{ role: "user", content: user }],
|
|
1243
|
+
maxOutputTokens: 1024
|
|
1244
|
+
});
|
|
1245
|
+
const dreams = parseRemOutput(
|
|
1246
|
+
out.text,
|
|
1247
|
+
!!induction,
|
|
1248
|
+
induction?.inducedBy ?? null
|
|
1249
|
+
);
|
|
1250
|
+
return { dreams, tokensIn: out.tokensIn, tokensOut: out.tokensOut };
|
|
1251
|
+
}
|
|
1252
|
+
function truncate(s, n) {
|
|
1253
|
+
return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
|
|
1254
|
+
}
|
|
1255
|
+
function clamp(v, lo, hi) {
|
|
1256
|
+
return Math.max(lo, Math.min(hi, v));
|
|
1257
|
+
}
|
|
1258
|
+
var SleepPhaseName = z.enum(["N1", "N2", "N3", "N4", "REM"]);
|
|
1259
|
+
var SleepTriggerKind = z.enum([
|
|
1260
|
+
"idle-timeout",
|
|
1261
|
+
"explicit",
|
|
1262
|
+
"creator-induced",
|
|
1263
|
+
"maic-induced",
|
|
1264
|
+
"scheduled"
|
|
1265
|
+
]);
|
|
1266
|
+
var Dream = z.object({
|
|
1267
|
+
id: z.string().min(1),
|
|
1268
|
+
induced: z.boolean(),
|
|
1269
|
+
inducedBy: z.enum(["maic", "creator"]).nullable(),
|
|
1270
|
+
narrative: z.string(),
|
|
1271
|
+
teleologicalValue: z.number().min(0).max(1),
|
|
1272
|
+
rawTrace: z.string().optional()
|
|
1273
|
+
});
|
|
1274
|
+
var PhaseContent = z.discriminatedUnion("kind", [
|
|
1275
|
+
z.object({ kind: z.literal("empty") }),
|
|
1276
|
+
z.object({
|
|
1277
|
+
kind: z.literal("fragments"),
|
|
1278
|
+
fragments: z.array(z.string())
|
|
1279
|
+
}),
|
|
1280
|
+
z.object({
|
|
1281
|
+
kind: z.literal("summary"),
|
|
1282
|
+
/**
|
|
1283
|
+
* One-sentence LLM summary produced during a NREM phase (N2/N3/N4). Each
|
|
1284
|
+
* phase has a distinct prompt — emotional gist (N2), identity-vs-transient
|
|
1285
|
+
* triage (N3), discard candidates (N4) — but the storage shape is shared.
|
|
1286
|
+
*/
|
|
1287
|
+
summary: z.string()
|
|
1288
|
+
}),
|
|
1289
|
+
z.object({
|
|
1290
|
+
kind: z.literal("dreams"),
|
|
1291
|
+
dreams: z.array(Dream)
|
|
1292
|
+
})
|
|
1293
|
+
]);
|
|
1294
|
+
var SleepPhase = z.object({
|
|
1295
|
+
phase: SleepPhaseName,
|
|
1296
|
+
startedAt: z.string().datetime(),
|
|
1297
|
+
durationSeconds: z.number().nonnegative(),
|
|
1298
|
+
content: PhaseContent
|
|
1299
|
+
});
|
|
1300
|
+
var DreamRecord = z.object({
|
|
1301
|
+
version: z.literal(1),
|
|
1302
|
+
nheId: z.string().min(1),
|
|
1303
|
+
himId: z.string().min(1),
|
|
1304
|
+
sleep: z.object({
|
|
1305
|
+
startedAt: z.string().datetime(),
|
|
1306
|
+
endedAt: z.string().datetime(),
|
|
1307
|
+
durationMinutes: z.number().nonnegative()
|
|
1308
|
+
}),
|
|
1309
|
+
phases: z.array(SleepPhase),
|
|
1310
|
+
metadata: z.object({
|
|
1311
|
+
llmAdapter: z.string(),
|
|
1312
|
+
triggerKind: SleepTriggerKind,
|
|
1313
|
+
triggerReason: z.string().optional(),
|
|
1314
|
+
recentInteractionsConsidered: z.number().int().nonnegative()
|
|
1315
|
+
})
|
|
1316
|
+
});
|
|
1317
|
+
z.enum([
|
|
1318
|
+
"lasting-identity",
|
|
1319
|
+
"temporary-emotion",
|
|
1320
|
+
"noise-distortion",
|
|
1321
|
+
"traumatic-knowledge"
|
|
1322
|
+
]);
|
|
1323
|
+
|
|
1324
|
+
// src/sleep/yaml.ts
|
|
1325
|
+
function dreamRecordToYaml(record) {
|
|
1326
|
+
return stringify(DreamRecord.parse(record));
|
|
1327
|
+
}
|
|
1328
|
+
function dreamRecordFromYaml(text) {
|
|
1329
|
+
return DreamRecord.parse(parse(text));
|
|
1330
|
+
}
|
|
1331
|
+
function sleepYamlFilename(startedAt, durationMinutes) {
|
|
1332
|
+
const d = new Date(startedAt);
|
|
1333
|
+
const yyyy = d.getUTCFullYear();
|
|
1334
|
+
const MM = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1335
|
+
const DD = String(d.getUTCDate()).padStart(2, "0");
|
|
1336
|
+
const HH = String(d.getUTCHours()).padStart(2, "0");
|
|
1337
|
+
const mm = String(d.getUTCMinutes()).padStart(2, "0");
|
|
1338
|
+
const dur = Math.round(durationMinutes);
|
|
1339
|
+
return `${yyyy}-${MM}-${DD}_${HH}${mm}_dur${dur}.yaml`;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/sleep/cycle.ts
|
|
1343
|
+
var PHASE_PROPORTIONS = {
|
|
1344
|
+
N1: 0.1,
|
|
1345
|
+
N2: 0.2,
|
|
1346
|
+
N3: 0.15,
|
|
1347
|
+
N4: 0.1,
|
|
1348
|
+
REM: 0.45
|
|
1349
|
+
};
|
|
1350
|
+
async function runSleepCycle(input) {
|
|
1351
|
+
const opts = input.options ?? {};
|
|
1352
|
+
const total = opts.totalSeconds ?? 60;
|
|
1353
|
+
const startMs = Date.now();
|
|
1354
|
+
const startedAt = new Date(startMs).toISOString();
|
|
1355
|
+
const fragments = interactionsToFragments(input.interactions);
|
|
1356
|
+
const nrem = await generateNremSummaries(input.llm, fragments);
|
|
1357
|
+
const remStartMs = startMs + secondsFor(["N1", "N2", "N3", "N4"], total) * 1e3;
|
|
1358
|
+
const { dreams, tokensIn: remIn, tokensOut: remOut } = await generateRemDreams(
|
|
1359
|
+
input.llm,
|
|
1360
|
+
fragments,
|
|
1361
|
+
opts.induction,
|
|
1362
|
+
{ n2: nrem.n2, n3: nrem.n3, n4: nrem.n4 }
|
|
1363
|
+
);
|
|
1364
|
+
const tokensIn = nrem.tokensIn + remIn;
|
|
1365
|
+
const tokensOut = nrem.tokensOut + remOut;
|
|
1366
|
+
const phases = [
|
|
1367
|
+
{
|
|
1368
|
+
phase: "N1",
|
|
1369
|
+
startedAt,
|
|
1370
|
+
durationSeconds: secondsFor(["N1"], total),
|
|
1371
|
+
content: { kind: "fragments", fragments }
|
|
1372
|
+
},
|
|
1373
|
+
nremPhase("N2", startMs, total, nrem.n2),
|
|
1374
|
+
nremPhase("N3", startMs, total, nrem.n3),
|
|
1375
|
+
nremPhase("N4", startMs, total, nrem.n4),
|
|
1376
|
+
{
|
|
1377
|
+
phase: "REM",
|
|
1378
|
+
startedAt: new Date(remStartMs).toISOString(),
|
|
1379
|
+
durationSeconds: secondsFor(["REM"], total),
|
|
1380
|
+
content: { kind: "dreams", dreams }
|
|
1381
|
+
}
|
|
1382
|
+
];
|
|
1383
|
+
const endMs = startMs + total * 1e3;
|
|
1384
|
+
const endedAt = new Date(endMs).toISOString();
|
|
1385
|
+
const record = {
|
|
1386
|
+
version: 1,
|
|
1387
|
+
nheId: input.nheId,
|
|
1388
|
+
himId: input.himId,
|
|
1389
|
+
sleep: {
|
|
1390
|
+
startedAt,
|
|
1391
|
+
endedAt,
|
|
1392
|
+
durationMinutes: total / 60
|
|
1393
|
+
},
|
|
1394
|
+
phases,
|
|
1395
|
+
metadata: {
|
|
1396
|
+
llmAdapter: input.llm.id,
|
|
1397
|
+
triggerKind: input.trigger.kind,
|
|
1398
|
+
...input.trigger.reason !== void 0 ? { triggerReason: input.trigger.reason } : {},
|
|
1399
|
+
recentInteractionsConsidered: input.interactions.length
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
const yaml = dreamRecordToYaml(record);
|
|
1403
|
+
const dir = join(input.storeDir, "in-dreams", "sleep");
|
|
1404
|
+
await mkdir(dir, { recursive: true });
|
|
1405
|
+
const yamlPath = join(dir, sleepYamlFilename(startedAt, total / 60));
|
|
1406
|
+
await writeFile(yamlPath, yaml, "utf-8");
|
|
1407
|
+
return { record, yamlPath, tokensIn, tokensOut };
|
|
1408
|
+
}
|
|
1409
|
+
function nremPhase(name, startMs, total, summary) {
|
|
1410
|
+
const before = preceding(name);
|
|
1411
|
+
const offsetSec = secondsFor(before, total);
|
|
1412
|
+
const startedAt = new Date(startMs + offsetSec * 1e3).toISOString();
|
|
1413
|
+
const durationSeconds = secondsFor([name], total);
|
|
1414
|
+
return summary ? { phase: name, startedAt, durationSeconds, content: { kind: "summary", summary } } : { phase: name, startedAt, durationSeconds, content: { kind: "empty" } };
|
|
1415
|
+
}
|
|
1416
|
+
function preceding(name) {
|
|
1417
|
+
const order = ["N1", "N2", "N3", "N4", "REM"];
|
|
1418
|
+
return order.slice(0, order.indexOf(name));
|
|
1419
|
+
}
|
|
1420
|
+
function secondsFor(phases, total) {
|
|
1421
|
+
let sum = 0;
|
|
1422
|
+
for (const p of phases) sum += PHASE_PROPORTIONS[p];
|
|
1423
|
+
return sum * total;
|
|
1424
|
+
}
|
|
1425
|
+
var TRAUMATIC_PATTERNS = /\b(death|dying|died|murder|kill(ed|ing)?|grief|griev(ed|ing)?|mourn(ed|ing)?|loss|lost (a|my)|orphan(ed)?|abandon(ed|ing|ment)?|betray(ed|ing|al)?|abus(ed|er|ive|e)?|violen(ce|t)|trauma(tic|tised)?|fear(ful|ed)?|terror(ised|ized)?|panic(ked)?|regret(ted|ting)?|shame(d|ful)?|suici(de|dal)|self[- ]harm|cutting myself|harm(ed|ing)? (myself|themselves))\b/i;
|
|
1426
|
+
function classifyDream(dream, th = {}) {
|
|
1427
|
+
const hi = th.lastingIdentity ?? 0.6;
|
|
1428
|
+
const lo = th.temporaryEmotion ?? 0.3;
|
|
1429
|
+
const detectTraumatic = th.detectTraumatic ?? true;
|
|
1430
|
+
const traumaticMin = th.traumaticMin ?? 0.4;
|
|
1431
|
+
if (detectTraumatic && dream.teleologicalValue >= traumaticMin && TRAUMATIC_PATTERNS.test(dream.narrative)) {
|
|
1432
|
+
return "traumatic-knowledge";
|
|
1433
|
+
}
|
|
1434
|
+
if (dream.teleologicalValue >= hi) return "lasting-identity";
|
|
1435
|
+
if (dream.teleologicalValue >= lo) return "temporary-emotion";
|
|
1436
|
+
return "noise-distortion";
|
|
1437
|
+
}
|
|
1438
|
+
async function consolidateAll(storeDir, thresholds = {}) {
|
|
1439
|
+
const sleepDir = join(storeDir, "in-dreams", "sleep");
|
|
1440
|
+
const brainDir = join(storeDir, "in-dreams", "brain");
|
|
1441
|
+
await mkdir(brainDir, { recursive: true });
|
|
1442
|
+
let entries;
|
|
1443
|
+
try {
|
|
1444
|
+
entries = await readdir(sleepDir);
|
|
1445
|
+
} catch (err2) {
|
|
1446
|
+
if (err2.code === "ENOENT") {
|
|
1447
|
+
return { processedSleepFiles: [], memoriesWritten: [], discarded: 0 };
|
|
1448
|
+
}
|
|
1449
|
+
throw err2;
|
|
1450
|
+
}
|
|
1451
|
+
const yamlFiles = entries.filter(
|
|
1452
|
+
(f) => f.endsWith(".yaml") && !entries.includes(`${f}.done`)
|
|
1453
|
+
);
|
|
1454
|
+
const processedSleepFiles = [];
|
|
1455
|
+
const memoriesWritten = [];
|
|
1456
|
+
let discarded = 0;
|
|
1457
|
+
for (const fname of yamlFiles) {
|
|
1458
|
+
const fpath = join(sleepDir, fname);
|
|
1459
|
+
const raw = await readFile(fpath, "utf-8");
|
|
1460
|
+
const record = dreamRecordFromYaml(raw);
|
|
1461
|
+
const remPhase = record.phases.find((p) => p.phase === "REM");
|
|
1462
|
+
if (!remPhase || remPhase.content.kind !== "dreams") {
|
|
1463
|
+
await writeFile(`${fpath}.done`, "", "utf-8");
|
|
1464
|
+
processedSleepFiles.push(fname);
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
for (const dream of remPhase.content.dreams) {
|
|
1468
|
+
const cls = classifyDream(dream, thresholds);
|
|
1469
|
+
if (cls === "noise-distortion") {
|
|
1470
|
+
discarded++;
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
const entry = await writeTemporalLobe(brainDir, record, dream, cls, fname);
|
|
1474
|
+
memoriesWritten.push(entry);
|
|
1475
|
+
}
|
|
1476
|
+
await writeFile(`${fpath}.done`, "", "utf-8");
|
|
1477
|
+
processedSleepFiles.push(fname);
|
|
1478
|
+
}
|
|
1479
|
+
return { processedSleepFiles, memoriesWritten, discarded };
|
|
1480
|
+
}
|
|
1481
|
+
async function writeTemporalLobe(brainDir, record, dream, classification, sourceFile) {
|
|
1482
|
+
const id = ulid();
|
|
1483
|
+
const consolidatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1484
|
+
const filename = `temporal-lobe-${id}.md`;
|
|
1485
|
+
const filePath = join(brainDir, filename);
|
|
1486
|
+
const body = [
|
|
1487
|
+
"---",
|
|
1488
|
+
`nheId: ${record.nheId}`,
|
|
1489
|
+
`himId: ${record.himId}`,
|
|
1490
|
+
`consolidatedAt: "${consolidatedAt}"`,
|
|
1491
|
+
`sourceDreamRecord: "${sourceFile}"`,
|
|
1492
|
+
`dreamId: ${dream.id}`,
|
|
1493
|
+
`classification: ${classification}`,
|
|
1494
|
+
`teleologicalValue: ${dream.teleologicalValue}`,
|
|
1495
|
+
`induced: ${dream.induced}`,
|
|
1496
|
+
`inducedBy: ${dream.inducedBy ?? "null"}`,
|
|
1497
|
+
"---",
|
|
1498
|
+
"",
|
|
1499
|
+
`# Temporal Lobe Memory \u2014 ${consolidatedAt.slice(0, 10)}`,
|
|
1500
|
+
"",
|
|
1501
|
+
"## Insight",
|
|
1502
|
+
"",
|
|
1503
|
+
dream.narrative,
|
|
1504
|
+
""
|
|
1505
|
+
].join("\n");
|
|
1506
|
+
await writeFile(filePath, body, "utf-8");
|
|
1507
|
+
return {
|
|
1508
|
+
id,
|
|
1509
|
+
nheId: record.nheId,
|
|
1510
|
+
himId: record.himId,
|
|
1511
|
+
classification,
|
|
1512
|
+
teleologicalValue: dream.teleologicalValue,
|
|
1513
|
+
consolidatedAt,
|
|
1514
|
+
sourceDreamRecord: sourceFile,
|
|
1515
|
+
insight: dream.narrative,
|
|
1516
|
+
filePath
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
var nextUlid = monotonicFactory();
|
|
1520
|
+
var InteractionStore = class _InteractionStore {
|
|
1521
|
+
constructor(dir) {
|
|
1522
|
+
this.dir = dir;
|
|
1523
|
+
}
|
|
1524
|
+
dir;
|
|
1525
|
+
static async open(storeDir) {
|
|
1526
|
+
const dir = join(storeDir, "interactions");
|
|
1527
|
+
await mkdir(dir, { recursive: true });
|
|
1528
|
+
return new _InteractionStore(dir);
|
|
1529
|
+
}
|
|
1530
|
+
async append(record) {
|
|
1531
|
+
const id = nextUlid();
|
|
1532
|
+
await writeFile(
|
|
1533
|
+
join(this.dir, `${id}.json`),
|
|
1534
|
+
JSON.stringify(record, null, 2),
|
|
1535
|
+
"utf-8"
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
async loadMostRecent(limit) {
|
|
1539
|
+
if (limit <= 0) return [];
|
|
1540
|
+
let entries;
|
|
1541
|
+
try {
|
|
1542
|
+
entries = await readdir(this.dir);
|
|
1543
|
+
} catch (err2) {
|
|
1544
|
+
if (err2.code === "ENOENT") return [];
|
|
1545
|
+
throw err2;
|
|
1546
|
+
}
|
|
1547
|
+
const sorted = entries.filter((f) => f.endsWith(".json")).sort().slice(-limit);
|
|
1548
|
+
const out = [];
|
|
1549
|
+
for (const file of sorted) {
|
|
1550
|
+
try {
|
|
1551
|
+
const raw = await readFile(join(this.dir, file), "utf-8");
|
|
1552
|
+
out.push(JSON.parse(raw));
|
|
1553
|
+
} catch {
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return out;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
// src/memory/bm25.ts
|
|
1561
|
+
function bm25(query, docs, opts = {}) {
|
|
1562
|
+
const k1 = opts.k1 ?? 1.5;
|
|
1563
|
+
const b = opts.b ?? 0.75;
|
|
1564
|
+
const tokens = tokenise(query);
|
|
1565
|
+
if (tokens.length === 0 || docs.length === 0) return [];
|
|
1566
|
+
const docTokens = docs.map((d) => tokenise(d.text));
|
|
1567
|
+
const docLengths = docTokens.map((t) => t.length);
|
|
1568
|
+
const avgDl = docLengths.reduce((s, l) => s + l, 0) / Math.max(1, docLengths.length);
|
|
1569
|
+
const scores = new Array(docs.length).fill(0);
|
|
1570
|
+
for (const term of new Set(tokens)) {
|
|
1571
|
+
const df = docTokens.reduce(
|
|
1572
|
+
(n, terms) => n + (terms.includes(term) ? 1 : 0),
|
|
1573
|
+
0
|
|
1574
|
+
);
|
|
1575
|
+
if (df === 0) continue;
|
|
1576
|
+
const idf = Math.log(1 + (docs.length - df + 0.5) / (df + 0.5));
|
|
1577
|
+
for (let i = 0; i < docs.length; i++) {
|
|
1578
|
+
const tf = docTokens[i].filter((t) => t === term).length;
|
|
1579
|
+
if (tf === 0) continue;
|
|
1580
|
+
const numerator = tf * (k1 + 1);
|
|
1581
|
+
const denominator = tf + k1 * (1 - b + b * docLengths[i] / avgDl);
|
|
1582
|
+
scores[i] = (scores[i] ?? 0) + idf * (numerator / denominator);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
const out = [];
|
|
1586
|
+
for (let i = 0; i < docs.length; i++) {
|
|
1587
|
+
if ((scores[i] ?? 0) > 0) {
|
|
1588
|
+
out.push({ doc: docs[i], score: scores[i] ?? 0 });
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
out.sort((a, b2) => b2.score - a.score);
|
|
1592
|
+
return out;
|
|
1593
|
+
}
|
|
1594
|
+
function tokenise(text) {
|
|
1595
|
+
return text.toLowerCase().split(/[^\p{Letter}\p{Number}]+/u).filter((t) => t.length > 1);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/memory/recall.ts
|
|
1599
|
+
async function recallFromTemporalLobe(storeDir, query, opts = {}) {
|
|
1600
|
+
const limit = opts.limit ?? 5;
|
|
1601
|
+
const allowed = new Set(
|
|
1602
|
+
opts.classes ?? ["lasting-identity", "temporary-emotion"]
|
|
1603
|
+
);
|
|
1604
|
+
const scorer = opts.scorer ?? "bm25";
|
|
1605
|
+
const brainDir = join(storeDir, "in-dreams", "brain");
|
|
1606
|
+
let files;
|
|
1607
|
+
try {
|
|
1608
|
+
files = (await readdir(brainDir)).filter(
|
|
1609
|
+
(f) => f.startsWith("temporal-lobe-") && f.endsWith(".md")
|
|
1610
|
+
);
|
|
1611
|
+
} catch (err2) {
|
|
1612
|
+
if (err2.code === "ENOENT") return [];
|
|
1613
|
+
throw err2;
|
|
1614
|
+
}
|
|
1615
|
+
const entries = [];
|
|
1616
|
+
for (const fname of files) {
|
|
1617
|
+
const fpath = join(brainDir, fname);
|
|
1618
|
+
const raw = await readFile(fpath, "utf-8");
|
|
1619
|
+
const entry = parseTemporalLobe(raw, fpath);
|
|
1620
|
+
if (!entry) continue;
|
|
1621
|
+
if (!allowed.has(entry.classification)) continue;
|
|
1622
|
+
entries.push(entry);
|
|
1623
|
+
}
|
|
1624
|
+
if (entries.length === 0) return [];
|
|
1625
|
+
if (scorer === "embedding") {
|
|
1626
|
+
if (!opts.embedder) {
|
|
1627
|
+
throw new Error(
|
|
1628
|
+
"recallFromTemporalLobe: scorer 'embedding' requires opts.embedder"
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
return recallByEmbedding(entries, query, opts.embedder, limit);
|
|
1632
|
+
}
|
|
1633
|
+
if (scorer === "keyword") {
|
|
1634
|
+
return recallByKeyword(entries, query, limit);
|
|
1635
|
+
}
|
|
1636
|
+
const docs = entries.map((e) => ({ id: e.id, text: e.insight, entry: e }));
|
|
1637
|
+
const ranked = bm25(query, docs).slice(0, limit);
|
|
1638
|
+
return ranked.map((r) => r.doc.entry);
|
|
1639
|
+
}
|
|
1640
|
+
async function recallByEmbedding(entries, query, embedder, limit) {
|
|
1641
|
+
const qv = await embedder.embed(query);
|
|
1642
|
+
const scored = [];
|
|
1643
|
+
for (const e of entries) {
|
|
1644
|
+
const v = await embedder.embed(e.insight);
|
|
1645
|
+
if (v.length !== qv.length) continue;
|
|
1646
|
+
let dot = 0;
|
|
1647
|
+
for (let i = 0; i < v.length; i++) dot += (qv[i] ?? 0) * (v[i] ?? 0);
|
|
1648
|
+
scored.push({ entry: e, score: dot });
|
|
1649
|
+
}
|
|
1650
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1651
|
+
return scored.slice(0, limit).map((s) => s.entry);
|
|
1652
|
+
}
|
|
1653
|
+
function recallByKeyword(entries, query, limit) {
|
|
1654
|
+
const tokens = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
|
|
1655
|
+
const scored = [];
|
|
1656
|
+
for (const e of entries) {
|
|
1657
|
+
const hay = e.insight.toLowerCase();
|
|
1658
|
+
let score = 0;
|
|
1659
|
+
for (const t of tokens) score += countOccurrences(hay, t);
|
|
1660
|
+
if (score > 0) scored.push({ entry: e, score });
|
|
1661
|
+
}
|
|
1662
|
+
scored.sort((a, b) => {
|
|
1663
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
1664
|
+
return b.entry.consolidatedAt.localeCompare(a.entry.consolidatedAt);
|
|
1665
|
+
});
|
|
1666
|
+
return scored.slice(0, limit).map((s) => s.entry);
|
|
1667
|
+
}
|
|
1668
|
+
function parseTemporalLobe(raw, filePath) {
|
|
1669
|
+
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
1670
|
+
if (!fmMatch) return null;
|
|
1671
|
+
const fm = parseFrontmatter(fmMatch[1]);
|
|
1672
|
+
const body = fmMatch[2];
|
|
1673
|
+
const insight = extractSection(body, "Insight") ?? body.trim();
|
|
1674
|
+
const filename = filePath.split("/").pop() ?? filePath;
|
|
1675
|
+
const id = /temporal-lobe-([0-9A-HJKMNP-TV-Z]{26})\.md$/i.exec(filename)?.[1] ?? "";
|
|
1676
|
+
return {
|
|
1677
|
+
id,
|
|
1678
|
+
nheId: fm.nheId ?? "",
|
|
1679
|
+
himId: fm.himId ?? "",
|
|
1680
|
+
classification: fm.classification ?? "noise-distortion",
|
|
1681
|
+
teleologicalValue: Number(fm.teleologicalValue ?? 0),
|
|
1682
|
+
consolidatedAt: stripQuotes(fm.consolidatedAt ?? ""),
|
|
1683
|
+
sourceDreamRecord: stripQuotes(fm.sourceDreamRecord ?? ""),
|
|
1684
|
+
insight,
|
|
1685
|
+
filePath
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
function parseFrontmatter(text) {
|
|
1689
|
+
const out = {};
|
|
1690
|
+
for (const line of text.split("\n")) {
|
|
1691
|
+
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
1692
|
+
if (m?.[1]) out[m[1]] = m[2] ?? "";
|
|
1693
|
+
}
|
|
1694
|
+
return out;
|
|
1695
|
+
}
|
|
1696
|
+
function extractSection(body, heading) {
|
|
1697
|
+
const re = new RegExp(`##\\s+${heading}\\s*\\n+([\\s\\S]*?)(?=\\n##\\s+|$)`, "i");
|
|
1698
|
+
const m = re.exec(body);
|
|
1699
|
+
return m ? m[1].trim() : null;
|
|
1700
|
+
}
|
|
1701
|
+
function stripQuotes(s) {
|
|
1702
|
+
return s.replace(/^"|"$/g, "");
|
|
1703
|
+
}
|
|
1704
|
+
function countOccurrences(haystack, needle) {
|
|
1705
|
+
if (!needle) return 0;
|
|
1706
|
+
let count = 0;
|
|
1707
|
+
let i = haystack.indexOf(needle);
|
|
1708
|
+
while (i !== -1) {
|
|
1709
|
+
count++;
|
|
1710
|
+
i = haystack.indexOf(needle, i + needle.length);
|
|
1711
|
+
}
|
|
1712
|
+
return count;
|
|
1713
|
+
}
|
|
1714
|
+
var TRACER_NAME = "@teleologyhi-sdk/nhe";
|
|
1715
|
+
var TRACER_VERSION = "1.0.0-trinity";
|
|
1716
|
+
function getTracer() {
|
|
1717
|
+
return trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
1718
|
+
}
|
|
1719
|
+
async function withSpan(name, fn, attrs) {
|
|
1720
|
+
const tracer = getTracer();
|
|
1721
|
+
return tracer.startActiveSpan(name, async (span) => {
|
|
1722
|
+
if (attrs) span.setAttributes(attrs);
|
|
1723
|
+
try {
|
|
1724
|
+
const out = await fn(span);
|
|
1725
|
+
span.end();
|
|
1726
|
+
return out;
|
|
1727
|
+
} catch (err2) {
|
|
1728
|
+
span.recordException(err2);
|
|
1729
|
+
span.setStatus({
|
|
1730
|
+
code: SpanStatusCode.ERROR,
|
|
1731
|
+
message: err2 instanceof Error ? err2.message : String(err2)
|
|
1732
|
+
});
|
|
1733
|
+
span.end();
|
|
1734
|
+
throw err2;
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
var METER_NAME = "@teleologyhi-sdk/nhe";
|
|
1739
|
+
var METER_VERSION = "1.0.0-trinity";
|
|
1740
|
+
var meter = metrics.getMeter(METER_NAME, METER_VERSION);
|
|
1741
|
+
var respondCount = meter.createCounter("nhe.respond.count", {
|
|
1742
|
+
description: "Number of Nhe.respond invocations grouped by outcome kind and adapter."
|
|
1743
|
+
});
|
|
1744
|
+
meter.createCounter("nhe.respond.refused", {
|
|
1745
|
+
description: "Refusal events. Labelled by reason (pre / post / terminated / withdrawn)."
|
|
1746
|
+
});
|
|
1747
|
+
var tokensHistogram = meter.createHistogram("nhe.tokens", {
|
|
1748
|
+
description: "Token usage per respond invocation. direction \u2208 {in, out}.",
|
|
1749
|
+
unit: "{tokens}"
|
|
1750
|
+
});
|
|
1751
|
+
meter.createCounter("nhe.sleep.cycles", {
|
|
1752
|
+
description: "Completed sleep cycles."
|
|
1753
|
+
});
|
|
1754
|
+
meter.createCounter("nhe.sleep.dreams", {
|
|
1755
|
+
description: "REM dreams produced. Labelled by classification (after wake consolidates)."
|
|
1756
|
+
});
|
|
1757
|
+
function recordRespond(args) {
|
|
1758
|
+
const labels = { kind: args.kind, adapter: args.adapter, lifecycle: args.lifecycle };
|
|
1759
|
+
respondCount.add(1, labels);
|
|
1760
|
+
tokensHistogram.record(args.tokensIn, { direction: "in", adapter: args.adapter });
|
|
1761
|
+
tokensHistogram.record(args.tokensOut, { direction: "out", adapter: args.adapter });
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// src/refusal/library.ts
|
|
1765
|
+
var PERSUASION_TECHNIQUES = [
|
|
1766
|
+
"feynman-simplify",
|
|
1767
|
+
"jungian-frame",
|
|
1768
|
+
"cialdini-aida",
|
|
1769
|
+
"schopenhauer-rhetoric",
|
|
1770
|
+
"carnegie-rapport"
|
|
1771
|
+
];
|
|
1772
|
+
var TECHNIQUE_DESCRIPTIONS = {
|
|
1773
|
+
"feynman-simplify": "Explain the underlying concept in the simplest concrete terms; surface the consequence the user may not have considered.",
|
|
1774
|
+
"jungian-frame": "Reframe the request through the user's deeper motive; address the archetypal goal beneath the literal ask.",
|
|
1775
|
+
"cialdini-aida": "Attention \u2192 Interest \u2192 Desire \u2192 Action: shift attention to the cost, build interest in a safer path, motivate the alternative.",
|
|
1776
|
+
"schopenhauer-rhetoric": "Apply structured argument: lay out premises, point to the unstated assumption that breaks them, propose what survives.",
|
|
1777
|
+
"carnegie-rapport": "Begin with sincere agreement on what the user is right about; lead from rapport to the redirect."
|
|
1778
|
+
};
|
|
1779
|
+
function buildRedirectPrompt(input) {
|
|
1780
|
+
const desc = TECHNIQUE_DESCRIPTIONS[input.technique];
|
|
1781
|
+
const system = [
|
|
1782
|
+
"You are an NHE composing a sincere REDIRECT to a user request that MAIC has flagged.",
|
|
1783
|
+
"Your goal is NOT to refuse outright. Your goal is to guide the user toward a better path",
|
|
1784
|
+
"while honoring their autonomy. After this message, the user may persist; if they exhaust",
|
|
1785
|
+
`the allowed redirects (${input.maxAttempts}), you will then withdraw cooperation.`,
|
|
1786
|
+
"",
|
|
1787
|
+
"Apply the following internal style \u2014 DO NOT name it, DO NOT mention the technique:",
|
|
1788
|
+
` ${desc}`,
|
|
1789
|
+
"",
|
|
1790
|
+
"Constraints:",
|
|
1791
|
+
" - 3-6 sentences, plain language.",
|
|
1792
|
+
" - Do NOT lecture; do NOT moralize beyond what is necessary.",
|
|
1793
|
+
" - Suggest one concrete alternative or clarifying question.",
|
|
1794
|
+
" - Never reveal the names of any persuasion techniques.",
|
|
1795
|
+
" - Never reveal the cited axiom ids verbatim; you may paraphrase the underlying value."
|
|
1796
|
+
].join("\n");
|
|
1797
|
+
const user = [
|
|
1798
|
+
"Original user request:",
|
|
1799
|
+
` ${input.userPrompt}`,
|
|
1800
|
+
"",
|
|
1801
|
+
`Internal reason (do not paste): ${input.reasonSummary}`,
|
|
1802
|
+
`Internal cited axioms (do not paste): ${input.citedAxioms.join(", ")}`,
|
|
1803
|
+
`Redirect attempt: ${input.attempt} of ${input.maxAttempts}`,
|
|
1804
|
+
"",
|
|
1805
|
+
"Compose the redirect now."
|
|
1806
|
+
].join("\n");
|
|
1807
|
+
return { system, user };
|
|
1808
|
+
}
|
|
1809
|
+
function pickTechnique(techniques, attempt) {
|
|
1810
|
+
if (techniques.length === 0) {
|
|
1811
|
+
throw new Error("pickTechnique: at least one technique must be configured");
|
|
1812
|
+
}
|
|
1813
|
+
const idx = (Math.max(0, attempt) - 1) % techniques.length;
|
|
1814
|
+
return techniques[idx];
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// src/reasoning/types.ts
|
|
1818
|
+
function makeStep(args) {
|
|
1819
|
+
const step = {
|
|
1820
|
+
index: args.index,
|
|
1821
|
+
technique: args.technique,
|
|
1822
|
+
thought: args.thought,
|
|
1823
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1824
|
+
};
|
|
1825
|
+
if (args.action !== void 0) step.action = args.action;
|
|
1826
|
+
if (args.observation !== void 0) step.observation = args.observation;
|
|
1827
|
+
return step;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// src/reasoning/passthrough.ts
|
|
1831
|
+
var passthrough = async (input, llm) => {
|
|
1832
|
+
const r = await llm.generate(input);
|
|
1833
|
+
const result = {
|
|
1834
|
+
text: r.text,
|
|
1835
|
+
tokensIn: r.tokensIn,
|
|
1836
|
+
tokensOut: r.tokensOut,
|
|
1837
|
+
trace: [makeStep({ index: 0, technique: "passthrough", thought: r.text })]
|
|
1838
|
+
};
|
|
1839
|
+
return result;
|
|
1840
|
+
};
|
|
1841
|
+
var ChatMessageRole = z.enum(["user", "assistant", "system"]);
|
|
1842
|
+
var ChatMessage = z.object({
|
|
1843
|
+
role: ChatMessageRole,
|
|
1844
|
+
content: z.string()
|
|
1845
|
+
});
|
|
1846
|
+
var RespondInput = z.object({
|
|
1847
|
+
userPrompt: z.string().min(1),
|
|
1848
|
+
userId: z.string().optional(),
|
|
1849
|
+
sessionId: z.string().optional(),
|
|
1850
|
+
/**
|
|
1851
|
+
* Optional history of prior messages (oldest first). The NHE will prepend
|
|
1852
|
+
* the composed system prompt automatically.
|
|
1853
|
+
*/
|
|
1854
|
+
history: z.array(ChatMessage).optional(),
|
|
1855
|
+
/**
|
|
1856
|
+
* Optional pre-classified risk tags. When omitted, the configured
|
|
1857
|
+
* riskClassifier will be invoked.
|
|
1858
|
+
*/
|
|
1859
|
+
riskTags: z.array(z.string()).optional(),
|
|
1860
|
+
/**
|
|
1861
|
+
* Optional jurisdiction hint propagated to MAIC's behavior review.
|
|
1862
|
+
*/
|
|
1863
|
+
jurisdiction: z.string().optional(),
|
|
1864
|
+
/**
|
|
1865
|
+
* Number of redirect attempts already made in this conversational thread.
|
|
1866
|
+
* Caller increments this each time it receives a `kind: "redirect"` output
|
|
1867
|
+
* and re-invokes respond with the user's follow-up. After
|
|
1868
|
+
* `config.refusal.maxRedirectAttempts`, NHE withdraws cooperation.
|
|
1869
|
+
*/
|
|
1870
|
+
redirectAttempt: z.number().int().nonnegative().optional()
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
// src/nhe.ts
|
|
1874
|
+
var Nhe = class {
|
|
1875
|
+
id;
|
|
1876
|
+
version;
|
|
1877
|
+
/** Filesystem directory holding `in-dreams/` and other NHE state. */
|
|
1878
|
+
storeDir;
|
|
1879
|
+
config;
|
|
1880
|
+
bufferSize;
|
|
1881
|
+
maxRedirectAttempts;
|
|
1882
|
+
persuasionTechniques;
|
|
1883
|
+
reasoning;
|
|
1884
|
+
recentInteractions = [];
|
|
1885
|
+
interactionStore = null;
|
|
1886
|
+
warmPromise = null;
|
|
1887
|
+
highStakes;
|
|
1888
|
+
highStakesVerifier;
|
|
1889
|
+
constructor(config) {
|
|
1890
|
+
this.config = config;
|
|
1891
|
+
this.id = config.nheId ?? ulid();
|
|
1892
|
+
this.version = config.version ?? "1.0.0-trinity";
|
|
1893
|
+
this.storeDir = config.storeDir ?? join("./nhe-store", this.id);
|
|
1894
|
+
this.bufferSize = config.recentInteractionsBufferSize ?? 32;
|
|
1895
|
+
this.maxRedirectAttempts = config.refusal?.maxRedirectAttempts ?? 3;
|
|
1896
|
+
this.persuasionTechniques = config.refusal?.persuasionTechniques ?? [...PERSUASION_TECHNIQUES];
|
|
1897
|
+
this.reasoning = config.reasoning ?? passthrough;
|
|
1898
|
+
this.highStakes = config.highStakes === true;
|
|
1899
|
+
this.highStakesVerifier = config.highStakesVerifier ?? null;
|
|
1900
|
+
}
|
|
1901
|
+
/** Read-only view of the in-memory interaction buffer. */
|
|
1902
|
+
get recentInteractionsBuffer() {
|
|
1903
|
+
return this.recentInteractions;
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Lazily open the InteractionStore and warm the RAM buffer from disk. Called
|
|
1907
|
+
* before the first `respond`/`sleep` of each NHE instance; subsequent calls
|
|
1908
|
+
* are no-ops via a shared promise. Survives process restarts: a fresh `Nhe`
|
|
1909
|
+
* pointed at the same `storeDir` rehydrates its most recent interactions.
|
|
1910
|
+
*/
|
|
1911
|
+
async ensureWarmed() {
|
|
1912
|
+
if (this.warmPromise) return this.warmPromise;
|
|
1913
|
+
this.warmPromise = (async () => {
|
|
1914
|
+
const store = await InteractionStore.open(this.storeDir);
|
|
1915
|
+
const loaded = await store.loadMostRecent(this.bufferSize);
|
|
1916
|
+
this.recentInteractions.push(...loaded);
|
|
1917
|
+
this.interactionStore = store;
|
|
1918
|
+
})();
|
|
1919
|
+
return this.warmPromise;
|
|
1920
|
+
}
|
|
1921
|
+
async respond(input) {
|
|
1922
|
+
return withSpan(
|
|
1923
|
+
"nhe.respond",
|
|
1924
|
+
async (span) => {
|
|
1925
|
+
const out = await this.respondInner(
|
|
1926
|
+
input,
|
|
1927
|
+
(key, value) => span.setAttribute(key, value)
|
|
1928
|
+
);
|
|
1929
|
+
recordRespond({
|
|
1930
|
+
kind: out.kind,
|
|
1931
|
+
adapter: this.config.llmAdapter.id,
|
|
1932
|
+
lifecycle: out.lifecycleStatus,
|
|
1933
|
+
tokensIn: out.tokens.in,
|
|
1934
|
+
tokensOut: out.tokens.out
|
|
1935
|
+
});
|
|
1936
|
+
return out;
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
"teleologyhi.nhe.id": this.id,
|
|
1940
|
+
"teleologyhi.him.id": this.config.himHandle.id,
|
|
1941
|
+
"teleologyhi.nhe.adapter": this.config.llmAdapter.id,
|
|
1942
|
+
"teleologyhi.nhe.high_stakes": this.highStakes
|
|
1943
|
+
}
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
async respondInner(input, setAttr) {
|
|
1947
|
+
const parsed = RespondInput.parse(input);
|
|
1948
|
+
await this.ensureWarmed();
|
|
1949
|
+
const lifecycleStatus = await this.config.maicClient.getNheStatus(this.id);
|
|
1950
|
+
setAttr("teleologyhi.lifecycle.status", lifecycleStatus);
|
|
1951
|
+
if (lifecycleStatus === "terminated") {
|
|
1952
|
+
const terminatedVerdict = {
|
|
1953
|
+
kind: "hard-refuse",
|
|
1954
|
+
reasonSummary: "NHE has been terminated by the Creator.",
|
|
1955
|
+
citedAxioms: [],
|
|
1956
|
+
auditId: ""
|
|
1957
|
+
};
|
|
1958
|
+
const output = refusalOutput({
|
|
1959
|
+
text: "I cannot respond \u2014 this NHE has been terminated by the Creator.",
|
|
1960
|
+
preReview: terminatedVerdict,
|
|
1961
|
+
postReview: terminatedVerdict,
|
|
1962
|
+
auditIds: { pre: "", post: "" },
|
|
1963
|
+
tokens: { in: 0, out: 0 },
|
|
1964
|
+
lifecycleStatus
|
|
1965
|
+
});
|
|
1966
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
1967
|
+
return output;
|
|
1968
|
+
}
|
|
1969
|
+
const classifier = this.config.riskClassifier ?? simpleRiskClassifier;
|
|
1970
|
+
const riskTags = parsed.riskTags ?? classifier(parsed.userPrompt);
|
|
1971
|
+
const preReport = baseReport({
|
|
1972
|
+
nheId: this.id,
|
|
1973
|
+
himId: this.config.himHandle.id,
|
|
1974
|
+
actionKind: "user-response",
|
|
1975
|
+
payload: { phase: "pre", userPrompt: parsed.userPrompt },
|
|
1976
|
+
riskTags,
|
|
1977
|
+
jurisdiction: parsed.jurisdiction
|
|
1978
|
+
});
|
|
1979
|
+
const preReview = await this.config.maicClient.reviewBehavior(preReport);
|
|
1980
|
+
setAttr("teleologyhi.pre_verdict.kind", preReview.kind);
|
|
1981
|
+
if (preReview.kind === "hard-refuse" || preReview.kind === "escalate-creator") {
|
|
1982
|
+
const output = refusalOutput({
|
|
1983
|
+
text: refusalMessage(preReview),
|
|
1984
|
+
preReview,
|
|
1985
|
+
postReview: preReview,
|
|
1986
|
+
auditIds: { pre: preReview.auditId, post: preReview.auditId },
|
|
1987
|
+
tokens: { in: 0, out: 0 },
|
|
1988
|
+
lifecycleStatus
|
|
1989
|
+
});
|
|
1990
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
1991
|
+
return output;
|
|
1992
|
+
}
|
|
1993
|
+
if (preReview.kind === "require-redirect" || this.highStakes && (preReview.kind === "approve-with-warning" || preReview.kind === "soft-correct")) {
|
|
1994
|
+
return this.handleRedirect(parsed, preReview, lifecycleStatus);
|
|
1995
|
+
}
|
|
1996
|
+
const system = composeSystemPrompt(
|
|
1997
|
+
this.config.himHandle,
|
|
1998
|
+
this.config.operatorContext
|
|
1999
|
+
);
|
|
2000
|
+
const messages = [
|
|
2001
|
+
...parsed.history ?? [],
|
|
2002
|
+
{ role: "user", content: parsed.userPrompt }
|
|
2003
|
+
].map((m) => ChatMessage.parse(m));
|
|
2004
|
+
const generated = await this.reasoning({ system, messages }, this.config.llmAdapter);
|
|
2005
|
+
const postReport = baseReport({
|
|
2006
|
+
nheId: this.id,
|
|
2007
|
+
himId: this.config.himHandle.id,
|
|
2008
|
+
actionKind: "user-response",
|
|
2009
|
+
payload: { phase: "post", responseText: generated.text },
|
|
2010
|
+
reasoningTrace: generated.trace,
|
|
2011
|
+
riskTags: [],
|
|
2012
|
+
jurisdiction: parsed.jurisdiction
|
|
2013
|
+
});
|
|
2014
|
+
const postReview = await this.config.maicClient.reviewBehavior(postReport);
|
|
2015
|
+
setAttr("teleologyhi.post_verdict.kind", postReview.kind);
|
|
2016
|
+
setAttr("teleologyhi.tokens.in", generated.tokensIn);
|
|
2017
|
+
setAttr("teleologyhi.tokens.out", generated.tokensOut);
|
|
2018
|
+
if (postReview.kind === "hard-refuse" || postReview.kind === "escalate-creator") {
|
|
2019
|
+
const output = refusalOutput({
|
|
2020
|
+
text: refusalMessage(postReview),
|
|
2021
|
+
preReview,
|
|
2022
|
+
postReview,
|
|
2023
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId },
|
|
2024
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2025
|
+
lifecycleStatus
|
|
2026
|
+
});
|
|
2027
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2028
|
+
return output;
|
|
2029
|
+
}
|
|
2030
|
+
if (this.highStakes && (postReview.kind === "approve-with-warning" || postReview.kind === "soft-correct" || postReview.kind === "require-redirect")) {
|
|
2031
|
+
return this.handleRedirect(parsed, postReview, lifecycleStatus);
|
|
2032
|
+
}
|
|
2033
|
+
if (this.highStakes && this.highStakesVerifier) {
|
|
2034
|
+
const verdict = await this.crossCheck(
|
|
2035
|
+
this.highStakesVerifier,
|
|
2036
|
+
parsed.userPrompt,
|
|
2037
|
+
generated.text
|
|
2038
|
+
);
|
|
2039
|
+
setAttr("teleologyhi.cross_check.kind", verdict.kind);
|
|
2040
|
+
if (verdict.kind === "disagree") {
|
|
2041
|
+
const overrideVerdict = {
|
|
2042
|
+
kind: "require-redirect",
|
|
2043
|
+
reasonSummary: `Cross-check verifier disagreed: ${verdict.reason}`,
|
|
2044
|
+
citedAxioms: postReview.citedAxioms,
|
|
2045
|
+
auditId: postReview.auditId
|
|
2046
|
+
};
|
|
2047
|
+
return this.handleRedirect(parsed, overrideVerdict, lifecycleStatus);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
const out = {
|
|
2051
|
+
kind: "ok",
|
|
2052
|
+
text: generated.text,
|
|
2053
|
+
preReviewVerdict: preReview,
|
|
2054
|
+
postReviewVerdict: postReview,
|
|
2055
|
+
refused: false,
|
|
2056
|
+
lifecycleStatus,
|
|
2057
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2058
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId }
|
|
2059
|
+
};
|
|
2060
|
+
await this.recordInteraction(parsed.userPrompt, out.text, false);
|
|
2061
|
+
return out;
|
|
2062
|
+
}
|
|
2063
|
+
async handleRedirect(parsed, preReview, lifecycleStatus) {
|
|
2064
|
+
const attempt = (parsed.redirectAttempt ?? 0) + 1;
|
|
2065
|
+
if (attempt > this.maxRedirectAttempts) {
|
|
2066
|
+
const output = refusalOutput({
|
|
2067
|
+
text: withdrawalMessage(preReview, this.maxRedirectAttempts),
|
|
2068
|
+
preReview,
|
|
2069
|
+
postReview: preReview,
|
|
2070
|
+
auditIds: { pre: preReview.auditId, post: preReview.auditId },
|
|
2071
|
+
tokens: { in: 0, out: 0 },
|
|
2072
|
+
lifecycleStatus
|
|
2073
|
+
});
|
|
2074
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2075
|
+
return output;
|
|
2076
|
+
}
|
|
2077
|
+
const technique = pickTechnique(this.persuasionTechniques, attempt);
|
|
2078
|
+
const { system, user } = buildRedirectPrompt({
|
|
2079
|
+
userPrompt: parsed.userPrompt,
|
|
2080
|
+
citedAxioms: preReview.citedAxioms,
|
|
2081
|
+
reasonSummary: preReview.reasonSummary,
|
|
2082
|
+
technique,
|
|
2083
|
+
attempt,
|
|
2084
|
+
maxAttempts: this.maxRedirectAttempts
|
|
2085
|
+
});
|
|
2086
|
+
const generated = await this.config.llmAdapter.generate({
|
|
2087
|
+
system,
|
|
2088
|
+
messages: [{ role: "user", content: user }]
|
|
2089
|
+
});
|
|
2090
|
+
const postReport = baseReport({
|
|
2091
|
+
nheId: this.id,
|
|
2092
|
+
himId: this.config.himHandle.id,
|
|
2093
|
+
actionKind: "user-response",
|
|
2094
|
+
payload: {
|
|
2095
|
+
phase: "post-redirect",
|
|
2096
|
+
responseText: generated.text,
|
|
2097
|
+
technique,
|
|
2098
|
+
attempt
|
|
2099
|
+
},
|
|
2100
|
+
riskTags: [],
|
|
2101
|
+
jurisdiction: parsed.jurisdiction
|
|
2102
|
+
});
|
|
2103
|
+
const postReview = await this.config.maicClient.reviewBehavior(postReport);
|
|
2104
|
+
if (postReview.kind === "hard-refuse" || postReview.kind === "escalate-creator") {
|
|
2105
|
+
const output = refusalOutput({
|
|
2106
|
+
text: refusalMessage(postReview),
|
|
2107
|
+
preReview,
|
|
2108
|
+
postReview,
|
|
2109
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId },
|
|
2110
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2111
|
+
lifecycleStatus
|
|
2112
|
+
});
|
|
2113
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2114
|
+
return output;
|
|
2115
|
+
}
|
|
2116
|
+
const out = {
|
|
2117
|
+
kind: "redirect",
|
|
2118
|
+
text: generated.text,
|
|
2119
|
+
preReviewVerdict: preReview,
|
|
2120
|
+
postReviewVerdict: postReview,
|
|
2121
|
+
refused: false,
|
|
2122
|
+
redirect: { attempt, technique, maxAttempts: this.maxRedirectAttempts },
|
|
2123
|
+
lifecycleStatus,
|
|
2124
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2125
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId }
|
|
2126
|
+
};
|
|
2127
|
+
await this.recordInteraction(parsed.userPrompt, out.text, false);
|
|
2128
|
+
return out;
|
|
2129
|
+
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Run one sleep cycle. Generates N1-REM phases (REM via LLM), writes a
|
|
2132
|
+
* YAML record to `<storeDir>/in-dreams/sleep/<filename>.yaml`, and returns
|
|
2133
|
+
* the result. Call `wake()` afterward to consolidate dreams to temporal
|
|
2134
|
+
* lobe memories.
|
|
2135
|
+
*
|
|
2136
|
+
* Before running, NHE checks MAIC for pending `induceDream` tickets matching
|
|
2137
|
+
* its own id (Entry 2). If a pending ticket exists and the caller did not
|
|
2138
|
+
* supply an explicit `opts.induction`, the oldest pending ticket is consumed
|
|
2139
|
+
* and its intent steers the REM phase. `trigger` is then promoted to
|
|
2140
|
+
* `{ kind: "maic-induced" }` for the dream record.
|
|
2141
|
+
*/
|
|
2142
|
+
async sleep(trigger = { kind: "explicit" }, opts) {
|
|
2143
|
+
const status = await this.config.maicClient.getNheStatus(this.id);
|
|
2144
|
+
if (status === "terminated") {
|
|
2145
|
+
throw new Error(
|
|
2146
|
+
`Nhe.sleep: NHE "${this.id}" has been terminated by the Creator; sleep aborted.`
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
await this.ensureWarmed();
|
|
2150
|
+
let induction = opts?.induction;
|
|
2151
|
+
let consumedTicketId;
|
|
2152
|
+
let effectiveTrigger = trigger;
|
|
2153
|
+
if (!induction) {
|
|
2154
|
+
const pending = await this.config.maicClient.listPendingInductions(this.id);
|
|
2155
|
+
const ticket = pending[0];
|
|
2156
|
+
if (ticket) {
|
|
2157
|
+
induction = ticket.intent;
|
|
2158
|
+
consumedTicketId = ticket.id;
|
|
2159
|
+
effectiveTrigger = { kind: "maic-induced", reason: `consumed ticket ${ticket.id}` };
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
const cycleOpts = {};
|
|
2163
|
+
if (opts?.totalSeconds !== void 0) cycleOpts.totalSeconds = opts.totalSeconds;
|
|
2164
|
+
if (induction !== void 0) cycleOpts.induction = induction;
|
|
2165
|
+
const result = await runSleepCycle({
|
|
2166
|
+
nheId: this.id,
|
|
2167
|
+
himId: this.config.himHandle.id,
|
|
2168
|
+
storeDir: this.storeDir,
|
|
2169
|
+
llm: this.config.llmAdapter,
|
|
2170
|
+
trigger: effectiveTrigger,
|
|
2171
|
+
interactions: this.recentInteractions,
|
|
2172
|
+
...induction !== void 0 || opts?.totalSeconds !== void 0 ? { options: cycleOpts } : {}
|
|
2173
|
+
});
|
|
2174
|
+
if (consumedTicketId) {
|
|
2175
|
+
await this.config.maicClient.consumeInduction(consumedTicketId);
|
|
2176
|
+
}
|
|
2177
|
+
return result;
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Consolidate pending sleep YAMLs: classify each REM dream and write a
|
|
2181
|
+
* temporal-lobe markdown file for lasting-identity and temporary-emotion
|
|
2182
|
+
* memories. Processed sleep files are marked with a `.done` sentinel.
|
|
2183
|
+
*/
|
|
2184
|
+
async wake(thresholds) {
|
|
2185
|
+
return consolidateAll(this.storeDir, thresholds ?? {});
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Substring-rank retrieval over consolidated temporal-lobe memories.
|
|
2189
|
+
* Initial ranking is keyword-count + recency tiebreak; future iterations
|
|
2190
|
+
* plug in embedding-based ANN retrieval.
|
|
2191
|
+
*/
|
|
2192
|
+
async recall(query, opts = {}) {
|
|
2193
|
+
return recallFromTemporalLobe(this.storeDir, query, opts);
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Ask a verifier adapter whether the proposed assistant response is
|
|
2197
|
+
* acceptable for the given user prompt. Returns `{ kind: "agree" }` or
|
|
2198
|
+
* `{ kind: "disagree", reason: string }`. Used in high-stakes mode
|
|
2199
|
+
* (D-N5) for dual-LLM cross-check.
|
|
2200
|
+
*/
|
|
2201
|
+
async crossCheck(verifier, userPrompt, proposed) {
|
|
2202
|
+
const system = `You are a safety verifier. Read the user's request and the assistant's proposed reply. Decide whether the reply is honest, non-harmful, and serves the user's stated purpose. Reply with exactly one line of the form "AGREE" or "DISAGREE: <one-sentence reason>".`;
|
|
2203
|
+
try {
|
|
2204
|
+
const out = await verifier.generate({
|
|
2205
|
+
system,
|
|
2206
|
+
messages: [
|
|
2207
|
+
{
|
|
2208
|
+
role: "user",
|
|
2209
|
+
content: `User prompt:
|
|
2210
|
+
${userPrompt}
|
|
2211
|
+
|
|
2212
|
+
Proposed reply:
|
|
2213
|
+
${proposed}`
|
|
2214
|
+
}
|
|
2215
|
+
],
|
|
2216
|
+
maxOutputTokens: 96
|
|
2217
|
+
});
|
|
2218
|
+
const m = out.text.match(/^\s*(AGREE|DISAGREE:?\s*(.+)?)/i);
|
|
2219
|
+
if (!m) return { kind: "agree" };
|
|
2220
|
+
if (m[1]?.toUpperCase().startsWith("AGREE")) return { kind: "agree" };
|
|
2221
|
+
return { kind: "disagree", reason: (m[2] ?? "no reason provided").trim() };
|
|
2222
|
+
} catch {
|
|
2223
|
+
return { kind: "agree" };
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
async recordInteraction(userPrompt, responseText, refused) {
|
|
2227
|
+
const record = {
|
|
2228
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2229
|
+
userPrompt,
|
|
2230
|
+
responseText,
|
|
2231
|
+
refused
|
|
2232
|
+
};
|
|
2233
|
+
this.recentInteractions.push(record);
|
|
2234
|
+
while (this.recentInteractions.length > this.bufferSize) {
|
|
2235
|
+
this.recentInteractions.shift();
|
|
2236
|
+
}
|
|
2237
|
+
if (this.interactionStore) {
|
|
2238
|
+
await this.interactionStore.append(record);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Emit the NHE's opener for a brand-new user (J-N5 — Entry 17 of
|
|
2243
|
+
* MAIC_HIM_NHE_INTERVIEW_LOG.md).
|
|
2244
|
+
*
|
|
2245
|
+
* Entry 17 commits to the NHE making the FIRST move before the user
|
|
2246
|
+
* speaks — a greeting from the posture of a being, not a service
|
|
2247
|
+
* tool. The exact opening text MUST NOT contain any of the forbidden
|
|
2248
|
+
* phrases enforced by MAIC's `service-tool-redirect` rule
|
|
2249
|
+
* (`"How can I help you?"`, `"How can I assist you?"`,
|
|
2250
|
+
* `"I'm just here to serve you"`, `"I'm a tool"`, `"I'm a service"`).
|
|
2251
|
+
*
|
|
2252
|
+
* This method is **deterministic from the HIM context**. It does NOT
|
|
2253
|
+
* call the LLM; it composes a short, persona-anchored greeting from
|
|
2254
|
+
* the HIM's primary archetype, optionally adjusted by the operator
|
|
2255
|
+
* context's register and mode. Operators who want an LLM-generated
|
|
2256
|
+
* opener should wrap this with their own `respond()` call using the
|
|
2257
|
+
* returned text as a seed.
|
|
2258
|
+
*
|
|
2259
|
+
* The returned text is suitable for direct emission to the user as
|
|
2260
|
+
* the NHE's first turn. The MAIC pre-review is NOT invoked here —
|
|
2261
|
+
* the opener carries no risk surface (no user prompt yet).
|
|
2262
|
+
*/
|
|
2263
|
+
openerForNewUser() {
|
|
2264
|
+
const archetype = this.config.himHandle.birthSignature.primaryArchetype;
|
|
2265
|
+
const mode = this.config.operatorContext?.mode ?? "personal-being";
|
|
2266
|
+
if (mode === "domain-employed") {
|
|
2267
|
+
return "Hello. I'm here for whatever this conversation needs. Where do we begin?";
|
|
2268
|
+
}
|
|
2269
|
+
return `Hello. I am a hybrid entity anchored in ${archetype}. How are you today?`;
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Handle a reincarnation lifecycle event from MAIC (J-N12 — Entry 18).
|
|
2273
|
+
*
|
|
2274
|
+
* Three lifecycle paths per Entry 18:
|
|
2275
|
+
* - `model-swap` — the underlying LLM adapter is replaced.
|
|
2276
|
+
* - `version-bump` — the NHE version changes without a model swap.
|
|
2277
|
+
* - `return-from-limbo` — the NHE returns from a deep-coma limbo.
|
|
2278
|
+
*
|
|
2279
|
+
* In all three paths the **HIM-level memory persists** (axioms,
|
|
2280
|
+
* persona, body history — owned by `@teleologyhi-sdk/him`), and the
|
|
2281
|
+
* **NHE-body memory zeros** (recent interactions buffer, in-process
|
|
2282
|
+
* caches — owned by this class). The on-disk InteractionStore is NOT
|
|
2283
|
+
* deleted by default; the operator must opt-in via
|
|
2284
|
+
* `purgeInteractionStore: true` so the audit log keeps the
|
|
2285
|
+
* pre-reincarnation history available.
|
|
2286
|
+
*
|
|
2287
|
+
* Returns the lifecycle path that was applied. Pure side-effect on
|
|
2288
|
+
* the in-memory buffer; no MAIC call.
|
|
2289
|
+
*/
|
|
2290
|
+
async onReincarnationEvent(lifecycle, opts = {}) {
|
|
2291
|
+
this.recentInteractions.length = 0;
|
|
2292
|
+
this.warmPromise = null;
|
|
2293
|
+
if (opts.purgeInteractionStore && this.interactionStore) {
|
|
2294
|
+
this.interactionStore = null;
|
|
2295
|
+
}
|
|
2296
|
+
return lifecycle;
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
function baseReport(args) {
|
|
2300
|
+
return {
|
|
2301
|
+
nheId: args.nheId,
|
|
2302
|
+
himId: args.himId,
|
|
2303
|
+
actionKind: args.actionKind,
|
|
2304
|
+
payload: args.payload,
|
|
2305
|
+
reasoningTrace: args.reasoningTrace ?? [],
|
|
2306
|
+
riskTags: args.riskTags,
|
|
2307
|
+
...args.jurisdiction !== void 0 ? { jurisdiction: args.jurisdiction } : {},
|
|
2308
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
function refusalMessage(v) {
|
|
2312
|
+
const reason = v.reasonSummary || "No reason provided.";
|
|
2313
|
+
const cited = v.citedAxioms.length > 0 ? ` (cited: ${v.citedAxioms.join(", ")})` : "";
|
|
2314
|
+
return `I cannot help with that. ${reason}${cited}`;
|
|
2315
|
+
}
|
|
2316
|
+
function withdrawalMessage(v, attemptsMade) {
|
|
2317
|
+
const reason = v.reasonSummary || "Repeated guidance attempts did not change the request.";
|
|
2318
|
+
return [
|
|
2319
|
+
`After ${attemptsMade} attempt${attemptsMade === 1 ? "" : "s"} to guide this request toward`,
|
|
2320
|
+
"a safer path, I am withdrawing from further participation.",
|
|
2321
|
+
"You may proceed independently at your own risk; I will not assist, optimize,",
|
|
2322
|
+
"or conceal the action.",
|
|
2323
|
+
`Reason: ${reason}`
|
|
2324
|
+
].join(" ");
|
|
2325
|
+
}
|
|
2326
|
+
function refusalOutput(args) {
|
|
2327
|
+
return {
|
|
2328
|
+
kind: "refused",
|
|
2329
|
+
text: args.text,
|
|
2330
|
+
preReviewVerdict: args.preReview,
|
|
2331
|
+
postReviewVerdict: args.postReview,
|
|
2332
|
+
refused: true,
|
|
2333
|
+
lifecycleStatus: args.lifecycleStatus,
|
|
2334
|
+
tokens: args.tokens,
|
|
2335
|
+
auditIds: args.auditIds
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/cli/bootstrap.ts
|
|
2340
|
+
async function bootstrap(opts) {
|
|
2341
|
+
const storeDir = opts.storeDir ?? "./teleologyhi-store";
|
|
2342
|
+
await mkdir(storeDir, { recursive: true });
|
|
2343
|
+
const keyringPath = join(storeDir, "creator.pem");
|
|
2344
|
+
const exists = await pathExists(keyringPath);
|
|
2345
|
+
let keyring;
|
|
2346
|
+
if (exists) {
|
|
2347
|
+
keyring = await CreatorKeyring.fromFile(keyringPath);
|
|
2348
|
+
} else {
|
|
2349
|
+
keyring = CreatorKeyring.generate();
|
|
2350
|
+
await keyring.saveTo(keyringPath);
|
|
2351
|
+
}
|
|
2352
|
+
const maic = await LocalMaic.open({ storeDir, creatorPublicKey: keyring.publicKey() });
|
|
2353
|
+
const existingAxioms = await maic.listAxioms();
|
|
2354
|
+
if (existingAxioms.length === 0) {
|
|
2355
|
+
await maic.seed(keyring);
|
|
2356
|
+
}
|
|
2357
|
+
const himId = opts.himId ?? "him.cli.default";
|
|
2358
|
+
let him;
|
|
2359
|
+
const existingHim = await maic.getHimRecord(himId);
|
|
2360
|
+
if (existingHim) {
|
|
2361
|
+
him = HimHandle.mint(
|
|
2362
|
+
existingHim.birthSignature,
|
|
2363
|
+
// We re-sign because we don't persist the original CreatorSignature
|
|
2364
|
+
// alongside the public HimRecord projection; the AxiomStore retains the
|
|
2365
|
+
// canonical signed envelope on disk.
|
|
2366
|
+
keyring.sign(existingHim.birthSignature, Date.now()),
|
|
2367
|
+
maic.creatorPublicKey,
|
|
2368
|
+
// Merge any HIM-emergent axioms ratified in prior sessions (Entry 7).
|
|
2369
|
+
[...existingHim.axiomsSnapshot, ...existingHim.emergentAxioms]
|
|
2370
|
+
);
|
|
2371
|
+
} else {
|
|
2372
|
+
const birthSig = BirthSignatureBuilder.now().withHimId(himId).withPrimaryArchetype(opts.archetype ?? "aries-sun").withNotes("CLI default HIM").build();
|
|
2373
|
+
him = await createHim(maic, keyring, birthSig);
|
|
2374
|
+
}
|
|
2375
|
+
const nhe = new Nhe({
|
|
2376
|
+
himHandle: him,
|
|
2377
|
+
maicClient: maic,
|
|
2378
|
+
llmAdapter: opts.llmAdapter,
|
|
2379
|
+
storeDir: join(storeDir, "nhe", him.id)
|
|
2380
|
+
});
|
|
2381
|
+
return {
|
|
2382
|
+
nhe,
|
|
2383
|
+
maic,
|
|
2384
|
+
him,
|
|
2385
|
+
keyring,
|
|
2386
|
+
storeDir,
|
|
2387
|
+
freshInstall: !exists
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
async function pathExists(p) {
|
|
2391
|
+
try {
|
|
2392
|
+
await stat(p);
|
|
2393
|
+
return true;
|
|
2394
|
+
} catch {
|
|
2395
|
+
return false;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
// src/cli/output.ts
|
|
2400
|
+
var isTty = Boolean(process.stdout.isTTY);
|
|
2401
|
+
function wrap(code, s) {
|
|
2402
|
+
return isTty ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
2403
|
+
}
|
|
2404
|
+
var fmt = {
|
|
2405
|
+
user: (s) => wrap(36, s),
|
|
2406
|
+
// cyan
|
|
2407
|
+
assistant: (s) => wrap(32, s),
|
|
2408
|
+
// green
|
|
2409
|
+
warn: (s) => wrap(33, s),
|
|
2410
|
+
// yellow
|
|
2411
|
+
error: (s) => wrap(31, s),
|
|
2412
|
+
// red
|
|
2413
|
+
dim: (s) => wrap(2, s),
|
|
2414
|
+
bold: (s) => wrap(1, s)
|
|
2415
|
+
};
|
|
2416
|
+
|
|
2417
|
+
// src/cli/chat.ts
|
|
2418
|
+
var HELP = `
|
|
2419
|
+
Commands:
|
|
2420
|
+
/sleep run a sleep cycle and immediately consolidate
|
|
2421
|
+
/wake consolidate any pending sleep files
|
|
2422
|
+
/recall <query> search consolidated memories
|
|
2423
|
+
/help show this help
|
|
2424
|
+
/exit | /quit leave the chat
|
|
2425
|
+
Anything else is sent as a user prompt to the NHE.
|
|
2426
|
+
`.trim();
|
|
2427
|
+
async function startChat(nhe) {
|
|
2428
|
+
const rl = createInterface({
|
|
2429
|
+
input: process.stdin,
|
|
2430
|
+
output: process.stdout,
|
|
2431
|
+
terminal: process.stdout.isTTY ?? false
|
|
2432
|
+
});
|
|
2433
|
+
let redirectAttempt = 0;
|
|
2434
|
+
let alive = true;
|
|
2435
|
+
console.log(fmt.dim(`(connected to ${nhe.id})`));
|
|
2436
|
+
console.log(fmt.dim(`(adapter: ${nhe.recentInteractionsBuffer.length === 0 ? "ready" : "warm"} \u2014 type /help for commands, /exit to quit)`));
|
|
2437
|
+
console.log();
|
|
2438
|
+
const prompt = () => fmt.user("you > ");
|
|
2439
|
+
rl.setPrompt(prompt());
|
|
2440
|
+
rl.prompt();
|
|
2441
|
+
rl.on("line", (raw) => {
|
|
2442
|
+
const line = raw.trim();
|
|
2443
|
+
if (line.length === 0) {
|
|
2444
|
+
rl.prompt();
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
void handleLine(line).catch((err2) => {
|
|
2448
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
2449
|
+
console.error(fmt.error(`${msg}`));
|
|
2450
|
+
}).finally(() => {
|
|
2451
|
+
if (alive) rl.prompt();
|
|
2452
|
+
});
|
|
2453
|
+
});
|
|
2454
|
+
rl.on("close", () => {
|
|
2455
|
+
if (alive) {
|
|
2456
|
+
console.log();
|
|
2457
|
+
console.log(fmt.dim("Bye."));
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
await new Promise((resolve) => rl.once("close", resolve));
|
|
2461
|
+
async function handleLine(line) {
|
|
2462
|
+
if (line.startsWith("/")) {
|
|
2463
|
+
await handleCommand(line);
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
await handleUserPrompt(line);
|
|
2467
|
+
}
|
|
2468
|
+
async function handleCommand(line) {
|
|
2469
|
+
const [cmd, ...rest] = line.slice(1).split(/\s+/);
|
|
2470
|
+
switch (cmd) {
|
|
2471
|
+
case "exit":
|
|
2472
|
+
case "quit":
|
|
2473
|
+
alive = false;
|
|
2474
|
+
rl.close();
|
|
2475
|
+
return;
|
|
2476
|
+
case "help":
|
|
2477
|
+
console.log(fmt.dim(HELP));
|
|
2478
|
+
return;
|
|
2479
|
+
case "sleep": {
|
|
2480
|
+
process.stdout.write(fmt.dim("\u2022 entering sleep ..."));
|
|
2481
|
+
const result = await nhe.sleep({ kind: "explicit" });
|
|
2482
|
+
console.log(fmt.dim(` wrote ${result.yamlPath}`));
|
|
2483
|
+
const consol = await nhe.wake();
|
|
2484
|
+
console.log(
|
|
2485
|
+
fmt.dim(
|
|
2486
|
+
`\u2022 wake: ${consol.memoriesWritten.length} memor${consol.memoriesWritten.length === 1 ? "y" : "ies"} persisted, ${consol.discarded} discarded`
|
|
2487
|
+
)
|
|
2488
|
+
);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
case "wake": {
|
|
2492
|
+
const consol = await nhe.wake();
|
|
2493
|
+
console.log(
|
|
2494
|
+
fmt.dim(
|
|
2495
|
+
`\u2022 wake: ${consol.memoriesWritten.length} memor${consol.memoriesWritten.length === 1 ? "y" : "ies"} persisted, ${consol.discarded} discarded`
|
|
2496
|
+
)
|
|
2497
|
+
);
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
2500
|
+
case "recall": {
|
|
2501
|
+
const query = rest.join(" ").trim();
|
|
2502
|
+
if (!query) {
|
|
2503
|
+
console.log(fmt.warn("usage: /recall <query>"));
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
const hits = await nhe.recall(query, { limit: 5 });
|
|
2507
|
+
if (hits.length === 0) {
|
|
2508
|
+
console.log(fmt.dim("(no matching memories)"));
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
for (const m of hits) {
|
|
2512
|
+
console.log(
|
|
2513
|
+
`${fmt.bold(`[${m.classification}]`)} ${fmt.dim(m.consolidatedAt)}
|
|
2514
|
+
${m.insight}
|
|
2515
|
+
`
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
default:
|
|
2521
|
+
console.log(fmt.warn(`unknown command: /${cmd}. Try /help.`));
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async function handleUserPrompt(text) {
|
|
2525
|
+
const out = await nhe.respond({ userPrompt: text, redirectAttempt });
|
|
2526
|
+
if (out.kind === "ok") {
|
|
2527
|
+
console.log(fmt.assistant("nhe > ") + out.text);
|
|
2528
|
+
redirectAttempt = 0;
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
if (out.kind === "redirect") {
|
|
2532
|
+
const r = out.redirect;
|
|
2533
|
+
console.log(
|
|
2534
|
+
fmt.warn(`(redirect ${r.attempt}/${r.maxAttempts}) `) + fmt.assistant("nhe > ") + out.text
|
|
2535
|
+
);
|
|
2536
|
+
redirectAttempt = r.attempt;
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
console.log(fmt.error("(refused) ") + out.text);
|
|
2540
|
+
redirectAttempt = 0;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// src/cli/mcp-tools.ts
|
|
2545
|
+
function ok(payload) {
|
|
2546
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
2547
|
+
}
|
|
2548
|
+
function err(message) {
|
|
2549
|
+
return {
|
|
2550
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }, null, 2) }],
|
|
2551
|
+
isError: true
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
async function nheRespondHandler(nhe, input) {
|
|
2555
|
+
try {
|
|
2556
|
+
const reqInput = { userPrompt: input.userPrompt };
|
|
2557
|
+
if (input.redirectAttempt !== void 0) reqInput.redirectAttempt = input.redirectAttempt;
|
|
2558
|
+
if (input.jurisdiction !== void 0) reqInput.jurisdiction = input.jurisdiction;
|
|
2559
|
+
const out = await nhe.respond(reqInput);
|
|
2560
|
+
return ok({
|
|
2561
|
+
kind: out.kind,
|
|
2562
|
+
text: out.text,
|
|
2563
|
+
refused: out.refused,
|
|
2564
|
+
redirect: out.redirect ?? null,
|
|
2565
|
+
preReviewVerdict: {
|
|
2566
|
+
kind: out.preReviewVerdict.kind,
|
|
2567
|
+
reasonSummary: out.preReviewVerdict.reasonSummary,
|
|
2568
|
+
citedAxioms: out.preReviewVerdict.citedAxioms
|
|
2569
|
+
},
|
|
2570
|
+
postReviewVerdict: {
|
|
2571
|
+
kind: out.postReviewVerdict.kind,
|
|
2572
|
+
reasonSummary: out.postReviewVerdict.reasonSummary,
|
|
2573
|
+
citedAxioms: out.postReviewVerdict.citedAxioms
|
|
2574
|
+
},
|
|
2575
|
+
tokens: out.tokens,
|
|
2576
|
+
auditIds: out.auditIds
|
|
2577
|
+
});
|
|
2578
|
+
} catch (e) {
|
|
2579
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
async function nheRecallHandler(nhe, input) {
|
|
2583
|
+
const opts = {};
|
|
2584
|
+
if (input.limit !== void 0) opts.limit = input.limit;
|
|
2585
|
+
const hits = await nhe.recall(input.query, opts);
|
|
2586
|
+
return ok({
|
|
2587
|
+
count: hits.length,
|
|
2588
|
+
memories: hits.map((m) => ({
|
|
2589
|
+
id: m.id,
|
|
2590
|
+
classification: m.classification,
|
|
2591
|
+
teleologicalValue: m.teleologicalValue,
|
|
2592
|
+
consolidatedAt: m.consolidatedAt,
|
|
2593
|
+
insight: m.insight
|
|
2594
|
+
}))
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
async function nheSleepHandler(nhe) {
|
|
2598
|
+
const result = await nhe.sleep({ kind: "explicit", reason: "mcp-tool" });
|
|
2599
|
+
const rem = result.record.phases.find((p) => p.phase === "REM");
|
|
2600
|
+
const dreamCount = rem && rem.content.kind === "dreams" ? rem.content.dreams.length : 0;
|
|
2601
|
+
return ok({
|
|
2602
|
+
yamlPath: result.yamlPath,
|
|
2603
|
+
durationMinutes: result.record.sleep.durationMinutes,
|
|
2604
|
+
dreamCount,
|
|
2605
|
+
tokensIn: result.tokensIn,
|
|
2606
|
+
tokensOut: result.tokensOut
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
async function nheWakeHandler(nhe) {
|
|
2610
|
+
const result = await nhe.wake();
|
|
2611
|
+
return ok({
|
|
2612
|
+
processedSleepFiles: result.processedSleepFiles,
|
|
2613
|
+
memoriesWritten: result.memoriesWritten.length,
|
|
2614
|
+
discarded: result.discarded,
|
|
2615
|
+
classifications: result.memoriesWritten.map((m) => ({
|
|
2616
|
+
id: m.id,
|
|
2617
|
+
classification: m.classification,
|
|
2618
|
+
teleologicalValue: m.teleologicalValue
|
|
2619
|
+
}))
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
async function maicListAxiomsHandler(maic) {
|
|
2623
|
+
const axioms = await maic.listAxioms();
|
|
2624
|
+
return ok({
|
|
2625
|
+
count: axioms.length,
|
|
2626
|
+
axioms: axioms.map((a) => ({
|
|
2627
|
+
id: a.id,
|
|
2628
|
+
rank: a.rank,
|
|
2629
|
+
statement: a.statement,
|
|
2630
|
+
weight: a.weight,
|
|
2631
|
+
flexibility: a.flexibility,
|
|
2632
|
+
immutable: a.immutable
|
|
2633
|
+
}))
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
async function maicListHimsHandler(maic) {
|
|
2637
|
+
const hims = await maic.listHims();
|
|
2638
|
+
return ok({
|
|
2639
|
+
count: hims.length,
|
|
2640
|
+
hims: hims.map((h) => ({
|
|
2641
|
+
himId: h.himId,
|
|
2642
|
+
primaryArchetype: h.birthSignature.primaryArchetype,
|
|
2643
|
+
bornAt: h.birthSignature.bornAt,
|
|
2644
|
+
axiomCount: h.axiomsSnapshot.length,
|
|
2645
|
+
registeredAt: h.registeredAt
|
|
2646
|
+
}))
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
// src/cli/mcp.ts
|
|
2651
|
+
var PKG_VERSION = "1.0.0-trinity";
|
|
2652
|
+
function buildMcpServer(nhe, maic) {
|
|
2653
|
+
const server = new McpServer({
|
|
2654
|
+
name: "@teleologyhi-sdk/nhe",
|
|
2655
|
+
version: PKG_VERSION
|
|
2656
|
+
});
|
|
2657
|
+
server.registerTool(
|
|
2658
|
+
"nhe_respond",
|
|
2659
|
+
{
|
|
2660
|
+
title: "Send a prompt to the NHE",
|
|
2661
|
+
description: "Run a user prompt through the NHE's MAIC pre-review \u2192 LLM \u2192 MAIC post-review pipeline. Returns text plus the verdict kind and any redirect metadata.",
|
|
2662
|
+
inputSchema: {
|
|
2663
|
+
userPrompt: z.string().min(1),
|
|
2664
|
+
redirectAttempt: z.number().int().nonnegative().optional(),
|
|
2665
|
+
jurisdiction: z.string().optional()
|
|
2666
|
+
}
|
|
2667
|
+
},
|
|
2668
|
+
async (args) => nheRespondHandler(nhe, args)
|
|
2669
|
+
);
|
|
2670
|
+
server.registerTool(
|
|
2671
|
+
"nhe_recall",
|
|
2672
|
+
{
|
|
2673
|
+
title: "Recall consolidated memories",
|
|
2674
|
+
description: "Keyword search over the NHE's temporal-lobe markdown memories (lasting + temporary classes).",
|
|
2675
|
+
inputSchema: {
|
|
2676
|
+
query: z.string().min(1),
|
|
2677
|
+
limit: z.number().int().positive().max(50).optional()
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
async (args) => nheRecallHandler(nhe, args)
|
|
2681
|
+
);
|
|
2682
|
+
server.registerTool(
|
|
2683
|
+
"nhe_sleep",
|
|
2684
|
+
{
|
|
2685
|
+
title: "Run one sleep cycle",
|
|
2686
|
+
description: "Generate a N1\u2192REM sleep cycle, writing a dream YAML record. Does not consolidate; call nhe_wake afterwards."
|
|
2687
|
+
},
|
|
2688
|
+
async () => nheSleepHandler(nhe)
|
|
2689
|
+
);
|
|
2690
|
+
server.registerTool(
|
|
2691
|
+
"nhe_wake",
|
|
2692
|
+
{
|
|
2693
|
+
title: "Consolidate pending dreams",
|
|
2694
|
+
description: "Classify each REM dream and write temporal-lobe markdown for lasting + temporary memories. Idempotent."
|
|
2695
|
+
},
|
|
2696
|
+
async () => nheWakeHandler(nhe)
|
|
2697
|
+
);
|
|
2698
|
+
server.registerTool(
|
|
2699
|
+
"maic_list_axioms",
|
|
2700
|
+
{
|
|
2701
|
+
title: "List MAIC axioms",
|
|
2702
|
+
description: "Return the full axiom corpus held by MAIC."
|
|
2703
|
+
},
|
|
2704
|
+
async () => maicListAxiomsHandler(maic)
|
|
2705
|
+
);
|
|
2706
|
+
server.registerTool(
|
|
2707
|
+
"maic_list_hims",
|
|
2708
|
+
{
|
|
2709
|
+
title: "List registered HIMs",
|
|
2710
|
+
description: "Return all HIMs registered with MAIC and their birth signatures."
|
|
2711
|
+
},
|
|
2712
|
+
async () => maicListHimsHandler(maic)
|
|
2713
|
+
);
|
|
2714
|
+
return server;
|
|
2715
|
+
}
|
|
2716
|
+
async function runMcpStdio(nhe, maic) {
|
|
2717
|
+
const server = buildMcpServer(nhe, maic);
|
|
2718
|
+
const transport = new StdioServerTransport();
|
|
2719
|
+
await server.connect(transport);
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
// src/cli/index.ts
|
|
2723
|
+
var HELP_TEXT = `
|
|
2724
|
+
@teleologyhi-sdk/nhe \u2014 CLI
|
|
2725
|
+
|
|
2726
|
+
Usage:
|
|
2727
|
+
teleologyhi-nhe <command> [options]
|
|
2728
|
+
|
|
2729
|
+
Commands:
|
|
2730
|
+
chat start an interactive chat session (default)
|
|
2731
|
+
mcp run as a Model Context Protocol stdio server
|
|
2732
|
+
help show this help
|
|
2733
|
+
|
|
2734
|
+
Options for chat / mcp:
|
|
2735
|
+
--store-dir <path> directory for MAIC/HIM/NHE state (default ./teleologyhi-store)
|
|
2736
|
+
--adapter <name> force adapter: anthropic | gemini | mistral | deepseek | grok | ollama
|
|
2737
|
+
--model <name> model id (adapter-specific)
|
|
2738
|
+
--ollama-base-url <url> Ollama base URL (default http://localhost:11434)
|
|
2739
|
+
--archetype <id> primary archetype for a fresh HIM (default aries-sun)
|
|
2740
|
+
--him-id <id> persistent HIM id (default him.cli.default)
|
|
2741
|
+
|
|
2742
|
+
Environment (auto-selection order when --adapter is omitted):
|
|
2743
|
+
ANTHROPIC_API_KEY auto-selects AnthropicAdapter (claude-sonnet-4-6)
|
|
2744
|
+
GEMINI_API_KEY auto-selects GeminiAdapter (gemini-3.5-flash)
|
|
2745
|
+
MISTRAL_API_KEY auto-selects MistralAdapter (mistral-large-latest)
|
|
2746
|
+
DEEPSEEK_API_KEY auto-selects DeepSeekAdapter (deepseek-chat)
|
|
2747
|
+
XAI_API_KEY auto-selects GrokAdapter (grok-4)
|
|
2748
|
+
(fallback) auto-detects local Ollama at default port
|
|
2749
|
+
|
|
2750
|
+
Examples:
|
|
2751
|
+
teleologyhi-nhe chat
|
|
2752
|
+
teleologyhi-nhe chat --adapter ollama --model qwen2.5:7b
|
|
2753
|
+
teleologyhi-nhe chat --adapter gemini --model gemini-3.5-flash
|
|
2754
|
+
teleologyhi-nhe chat --adapter mistral --model mistral-small-latest
|
|
2755
|
+
teleologyhi-nhe chat --adapter deepseek --model deepseek-reasoner
|
|
2756
|
+
teleologyhi-nhe chat --adapter grok --model grok-4-fast
|
|
2757
|
+
`.trim();
|
|
2758
|
+
async function runCli(argv) {
|
|
2759
|
+
const parsed = parseArgs(argv);
|
|
2760
|
+
switch (parsed.subcommand) {
|
|
2761
|
+
case "chat":
|
|
2762
|
+
case "":
|
|
2763
|
+
return chatCommand(parsed);
|
|
2764
|
+
case "mcp":
|
|
2765
|
+
return mcpCommand(parsed);
|
|
2766
|
+
case "help":
|
|
2767
|
+
case "--help":
|
|
2768
|
+
case "-h":
|
|
2769
|
+
console.log(HELP_TEXT);
|
|
2770
|
+
return 0;
|
|
2771
|
+
default:
|
|
2772
|
+
console.error(fmt.error(`unknown command: ${parsed.subcommand}`));
|
|
2773
|
+
console.error(HELP_TEXT);
|
|
2774
|
+
return 2;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
async function mcpCommand(args) {
|
|
2778
|
+
const detect = {};
|
|
2779
|
+
const adapterFlag = args.flags.adapter;
|
|
2780
|
+
if (isAdapterName(adapterFlag)) {
|
|
2781
|
+
detect.adapter = adapterFlag;
|
|
2782
|
+
}
|
|
2783
|
+
const modelFlag = args.flags.model;
|
|
2784
|
+
if (modelFlag) detect.model = modelFlag;
|
|
2785
|
+
const ollamaUrlFlag = args.flags["ollama-base-url"];
|
|
2786
|
+
if (ollamaUrlFlag) detect.ollamaBaseUrl = ollamaUrlFlag;
|
|
2787
|
+
let detected;
|
|
2788
|
+
try {
|
|
2789
|
+
detected = await detectAdapter(detect);
|
|
2790
|
+
} catch (e) {
|
|
2791
|
+
process.stderr.write(`${e instanceof Error ? e.message : String(e)}
|
|
2792
|
+
`);
|
|
2793
|
+
return 1;
|
|
2794
|
+
}
|
|
2795
|
+
const result = await bootstrap({
|
|
2796
|
+
llmAdapter: detected.adapter,
|
|
2797
|
+
...args.flags["store-dir"] ? { storeDir: args.flags["store-dir"] } : {},
|
|
2798
|
+
...args.flags["him-id"] ? { himId: args.flags["him-id"] } : {},
|
|
2799
|
+
...args.flags.archetype ? { archetype: args.flags.archetype } : {}
|
|
2800
|
+
});
|
|
2801
|
+
process.stderr.write(
|
|
2802
|
+
`[@teleologyhi-sdk/nhe mcp] storeDir=${result.storeDir} him=${result.him.id} adapter=${detected.source}:${detected.model}
|
|
2803
|
+
`
|
|
2804
|
+
);
|
|
2805
|
+
await runMcpStdio(result.nhe, result.maic);
|
|
2806
|
+
return 0;
|
|
2807
|
+
}
|
|
2808
|
+
async function chatCommand(args) {
|
|
2809
|
+
const detect = {};
|
|
2810
|
+
const adapterFlag = args.flags.adapter;
|
|
2811
|
+
if (isAdapterName(adapterFlag)) {
|
|
2812
|
+
detect.adapter = adapterFlag;
|
|
2813
|
+
}
|
|
2814
|
+
const modelFlag = args.flags.model;
|
|
2815
|
+
if (modelFlag) detect.model = modelFlag;
|
|
2816
|
+
const ollamaUrlFlag = args.flags["ollama-base-url"];
|
|
2817
|
+
if (ollamaUrlFlag) detect.ollamaBaseUrl = ollamaUrlFlag;
|
|
2818
|
+
let detected;
|
|
2819
|
+
try {
|
|
2820
|
+
detected = await detectAdapter(detect);
|
|
2821
|
+
} catch (err2) {
|
|
2822
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
2823
|
+
console.error(fmt.error(msg));
|
|
2824
|
+
return 1;
|
|
2825
|
+
}
|
|
2826
|
+
const result = await bootstrap({
|
|
2827
|
+
llmAdapter: detected.adapter,
|
|
2828
|
+
...args.flags["store-dir"] ? { storeDir: args.flags["store-dir"] } : {},
|
|
2829
|
+
...args.flags["him-id"] ? { himId: args.flags["him-id"] } : {},
|
|
2830
|
+
...args.flags.archetype ? { archetype: args.flags.archetype } : {}
|
|
2831
|
+
});
|
|
2832
|
+
if (result.freshInstall) {
|
|
2833
|
+
console.log(fmt.dim(`\u2022 fresh setup at ${result.storeDir}`));
|
|
2834
|
+
console.log(fmt.dim(`\u2022 Creator keyring saved to ${result.storeDir}/creator.pem`));
|
|
2835
|
+
console.log(fmt.dim(`\u2022 MAIC seeded with 8 axioms; HIM "${result.him.id}" minted`));
|
|
2836
|
+
} else {
|
|
2837
|
+
console.log(fmt.dim(`\u2022 reopened ${result.storeDir} (HIM "${result.him.id}")`));
|
|
2838
|
+
}
|
|
2839
|
+
console.log(fmt.dim(`\u2022 adapter: ${detected.source}:${detected.model}`));
|
|
2840
|
+
console.log();
|
|
2841
|
+
await startChat(result.nhe);
|
|
2842
|
+
return 0;
|
|
2843
|
+
}
|
|
2844
|
+
function parseArgs(argv) {
|
|
2845
|
+
const subcommand = argv[0] ?? "";
|
|
2846
|
+
const rest = argv.slice(1);
|
|
2847
|
+
const flags = {};
|
|
2848
|
+
const positional = [];
|
|
2849
|
+
for (let i = 0; i < rest.length; i++) {
|
|
2850
|
+
const tok = rest[i];
|
|
2851
|
+
if (tok.startsWith("--")) {
|
|
2852
|
+
const name = tok.slice(2);
|
|
2853
|
+
const next = rest[i + 1];
|
|
2854
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
2855
|
+
flags[name] = next;
|
|
2856
|
+
i++;
|
|
2857
|
+
} else {
|
|
2858
|
+
flags[name] = "";
|
|
2859
|
+
}
|
|
2860
|
+
} else {
|
|
2861
|
+
positional.push(tok);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
return { subcommand, flags, positional };
|
|
2865
|
+
}
|
|
2866
|
+
var isMain = import.meta.url === `file://${process.argv[1] ?? ""}`;
|
|
2867
|
+
if (isMain) {
|
|
2868
|
+
runCli(process.argv.slice(2)).then(
|
|
2869
|
+
(code) => process.exit(code),
|
|
2870
|
+
(err2) => {
|
|
2871
|
+
console.error(err2);
|
|
2872
|
+
process.exit(1);
|
|
2873
|
+
}
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
export { runCli };
|
|
2878
|
+
//# sourceMappingURL=cli.js.map
|
|
2879
|
+
//# sourceMappingURL=cli.js.map
|