blun-king-cli 9.0.0 → 9.0.1

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.
Files changed (55) hide show
  1. package/LIESMICH.txt +1 -7
  2. package/README.md +4 -16
  3. package/bin/blun.js +248 -160
  4. package/bin/core-bootstrap.js +47 -0
  5. package/bin/king.js +277 -1
  6. package/bin/launcher-mode.js +2 -1
  7. package/bin/launcher-runtime.js +221 -0
  8. package/bin/plugin-bootstrap.js +0 -0
  9. package/bin/private-paths.js +0 -0
  10. package/bin/update-lease.js +399 -0
  11. package/bin/update-notice.js +1094 -0
  12. package/blun.mjs +4060 -6667
  13. package/package.json +3 -10
  14. package/skills/screenshot-lesen/SKILL.md +0 -1
  15. package/skills/web-lesen/SKILL.md +0 -1
  16. package/telegram-plugin/dist/bridge.mjs +1 -21
  17. package/mnemo/access_routes.js +0 -692
  18. package/mnemo/agent_governance.js +0 -4242
  19. package/mnemo/agent_mail.js +0 -901
  20. package/mnemo/bootstrap_auto.js +0 -137
  21. package/mnemo/brief_coordination.js +0 -226
  22. package/mnemo/code_read_tools.js +0 -375
  23. package/mnemo/context_preview_tools.js +0 -603
  24. package/mnemo/embeddings.js +0 -66
  25. package/mnemo/external_repo_ops.js +0 -575
  26. package/mnemo/facts/example-project-rules.json +0 -90
  27. package/mnemo/facts/example.json +0 -34
  28. package/mnemo/identity_schema.sql +0 -139
  29. package/mnemo/journal_schema.js +0 -561
  30. package/mnemo/loop_doctor_tools.js +0 -661
  31. package/mnemo/mail_secret_refs.js +0 -150
  32. package/mnemo/mcp.js +0 -9309
  33. package/mnemo/memory_consolidation.js +0 -1914
  34. package/mnemo/memory_health_tools.js +0 -165
  35. package/mnemo/package.json +0 -79
  36. package/mnemo/protected_scope_gate.js +0 -627
  37. package/mnemo/resource_access_control.js +0 -684
  38. package/mnemo/runtime_governance.js +0 -1256
  39. package/mnemo/runtime_turn_gate.js +0 -862
  40. package/mnemo/sandbox.js +0 -143
  41. package/mnemo/schema.sql +0 -389
  42. package/mnemo/shared_utils.js +0 -763
  43. package/mnemo/skills/agent-auto-resume/SKILL.md +0 -56
  44. package/mnemo/skills/agent_hand/SKILL.md +0 -43
  45. package/mnemo/skills/agent_hand/run.js +0 -63
  46. package/mnemo/skills/book_flight/SKILL.md +0 -34
  47. package/mnemo/skills/external_repo_review/SKILL.md +0 -43
  48. package/mnemo/skills/external_repo_review/run.js +0 -73
  49. package/mnemo/skills/pay_invoice/SKILL.md +0 -34
  50. package/mnemo/team_quality_ops.js +0 -944
  51. package/mnemo/timeline_report_tools.js +0 -810
  52. package/mnemo/write_gate_risk.js +0 -80
  53. package/mnemo/writer_health.js +0 -152
  54. package/skills/doku-ingestion/SKILL.md +0 -48
  55. package/skills/doku-ingestion/ingest_docs.py +0 -133
@@ -1,862 +0,0 @@
1
- "use strict";
2
-
3
- const crypto = require("crypto");
4
- const {
5
- boolFlag,
6
- cleanScope,
7
- compactContent,
8
- jsonSafe,
9
- normalizeAgentName,
10
- parseMaybeJson,
11
- } = require("./shared_utils");
12
- const { runtimePolicyCheck } = require("./runtime_governance");
13
-
14
- const DEFAULT_SCOPE = "default";
15
-
16
- function nowIso() {
17
- return new Date().toISOString();
18
- }
19
-
20
- function sha(value) {
21
- return crypto.createHash("sha256").update(String(value)).digest("hex");
22
- }
23
-
24
- function scopeName(scope) {
25
- return cleanScope(scope || DEFAULT_SCOPE);
26
- }
27
-
28
- function normalizeRuntimeName(value) {
29
- return String(value || "")
30
- .trim()
31
- .toLowerCase()
32
- .replace(/[^a-z0-9._-]+/g, "-")
33
- .replace(/^-+|-+$/g, "") || "runtime";
34
- }
35
-
36
- function normalizePart(value, fallback = "*") {
37
- const raw = String(value == null ? "" : value).trim();
38
- if (!raw || raw === "*") return fallback;
39
- return raw.toLowerCase();
40
- }
41
-
42
- function intFlag(value, fallback, min = 0, max = 1000000) {
43
- if (value === undefined || value === null || value === "") return fallback;
44
- const n = Number.parseInt(value, 10);
45
- if (!Number.isFinite(n)) return fallback;
46
- return Math.min(Math.max(n, min), max);
47
- }
48
-
49
- function safeJson(value, fallback) {
50
- if (value === undefined) return JSON.stringify(fallback);
51
- return jsonSafe(value, 50000) || JSON.stringify(fallback);
52
- }
53
-
54
- function parseJson(value, fallback) {
55
- return parseMaybeJson(value, fallback);
56
- }
57
-
58
- function ensureRuntimeTurnSchema(db) {
59
- db.exec(`
60
- CREATE TABLE IF NOT EXISTS runtime_turn_state (
61
- scope TEXT NOT NULL DEFAULT 'default',
62
- turn_key TEXT NOT NULL,
63
- runtime_name TEXT NOT NULL,
64
- agent_name TEXT NOT NULL,
65
- channel TEXT,
66
- project TEXT,
67
- board TEXT,
68
- thread_id TEXT,
69
- message_count INTEGER NOT NULL DEFAULT 0,
70
- message_count_since_full_sync INTEGER NOT NULL DEFAULT 0,
71
- last_message_capture_at TEXT,
72
- last_recall_at TEXT,
73
- last_brief_pull_at TEXT,
74
- last_project_board_at TEXT,
75
- last_chat_sync_at TEXT,
76
- last_memory_update_at TEXT,
77
- last_full_sync_at TEXT,
78
- last_audit_id INTEGER,
79
- meta_json TEXT,
80
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
81
- updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
82
- PRIMARY KEY(scope, turn_key)
83
- );
84
- CREATE INDEX IF NOT EXISTS idx_runtime_turn_state_agent ON runtime_turn_state(agent_name, project, updated_at DESC);
85
- CREATE INDEX IF NOT EXISTS idx_runtime_turn_state_runtime ON runtime_turn_state(runtime_name, channel, updated_at DESC);
86
- `);
87
- }
88
-
89
- function inferBoard(input = {}) {
90
- const explicit = String(input.board || input.project_board || "").trim();
91
- if (explicit) return explicit;
92
- const project = String(input.project || "").toLowerCase();
93
- if (project.includes("builder_v2")) return "builder-v2-board";
94
- return "";
95
- }
96
-
97
- function inferThreadId(input = {}) {
98
- return String(
99
- input.thread_id ||
100
- input.session_id ||
101
- input.session_key ||
102
- input.conversation_id ||
103
- input.chat_id ||
104
- (input.meta && (input.meta.thread_id || input.meta.session_id || input.meta.chat_id)) ||
105
- "default"
106
- ).trim() || "default";
107
- }
108
-
109
- function buildTurnKey(input = {}) {
110
- const explicit = String(input.turn_key || input.runtime_turn_key || "").trim();
111
- if (explicit) return explicit.toLowerCase();
112
- const scope = scopeName(input.scope);
113
- const runtime = normalizeRuntimeName(input.runtime_name || input.runtime || input.adapter || "external");
114
- const agent = normalizeAgentName(input.agent_name || input.agent || "agent") || "agent";
115
- const channel = normalizePart(input.channel || "runtime");
116
- const project = normalizePart(input.project || "");
117
- const thread = inferThreadId(input);
118
- return sha([scope, runtime, agent, channel, project, thread].join("|")).slice(0, 32);
119
- }
120
-
121
- function stateFromRow(row) {
122
- if (!row) return null;
123
- return Object.assign({}, row, {
124
- message_count: Number(row.message_count || 0),
125
- message_count_since_full_sync: Number(row.message_count_since_full_sync || 0),
126
- meta: parseJson(row.meta_json, {}),
127
- });
128
- }
129
-
130
- function upsertTurnState(db, input = {}, updates = {}) {
131
- ensureRuntimeTurnSchema(db);
132
- const scope = scopeName(input.scope);
133
- const turnKey = buildTurnKey(input);
134
- const runtime = normalizeRuntimeName(input.runtime_name || input.runtime || input.adapter || "external");
135
- const agent = normalizeAgentName(input.agent_name || input.agent || "agent") || "agent";
136
- const channel = input.channel || "runtime";
137
- const project = input.project || null;
138
- const board = inferBoard(input) || null;
139
- const threadId = inferThreadId(input);
140
- const current = stateFromRow(db.prepare("SELECT * FROM runtime_turn_state WHERE scope=? AND turn_key=?").get(scope, turnKey));
141
- const meta = Object.assign({}, current && current.meta || {}, input.meta || {}, updates.meta || {});
142
- const next = {
143
- message_count: current ? current.message_count + (updates.increment === false ? 0 : 1) : (updates.increment === false ? 0 : 1),
144
- message_count_since_full_sync: current ? current.message_count_since_full_sync + (updates.increment === false ? 0 : 1) : (updates.increment === false ? 0 : 1),
145
- last_message_capture_at: updates.last_message_capture_at || (current && current.last_message_capture_at) || null,
146
- last_recall_at: updates.last_recall_at || (current && current.last_recall_at) || null,
147
- last_brief_pull_at: updates.last_brief_pull_at || (current && current.last_brief_pull_at) || null,
148
- last_project_board_at: updates.last_project_board_at || (current && current.last_project_board_at) || null,
149
- last_chat_sync_at: updates.last_chat_sync_at || (current && current.last_chat_sync_at) || null,
150
- last_memory_update_at: updates.last_memory_update_at || (current && current.last_memory_update_at) || null,
151
- last_full_sync_at: updates.last_full_sync_at || (current && current.last_full_sync_at) || null,
152
- last_audit_id: updates.last_audit_id || (current && current.last_audit_id) || null,
153
- };
154
- if (updates.reset_full_sync_counter) next.message_count_since_full_sync = 0;
155
- db.prepare(`
156
- INSERT INTO runtime_turn_state
157
- (scope, turn_key, runtime_name, agent_name, channel, project, board, thread_id, message_count, message_count_since_full_sync, last_message_capture_at, last_recall_at, last_brief_pull_at, last_project_board_at, last_chat_sync_at, last_memory_update_at, last_full_sync_at, last_audit_id, meta_json)
158
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
159
- ON CONFLICT(scope, turn_key) DO UPDATE SET
160
- runtime_name=excluded.runtime_name,
161
- agent_name=excluded.agent_name,
162
- channel=excluded.channel,
163
- project=excluded.project,
164
- board=COALESCE(excluded.board, runtime_turn_state.board),
165
- thread_id=excluded.thread_id,
166
- message_count=excluded.message_count,
167
- message_count_since_full_sync=excluded.message_count_since_full_sync,
168
- last_message_capture_at=COALESCE(excluded.last_message_capture_at, runtime_turn_state.last_message_capture_at),
169
- last_recall_at=COALESCE(excluded.last_recall_at, runtime_turn_state.last_recall_at),
170
- last_brief_pull_at=COALESCE(excluded.last_brief_pull_at, runtime_turn_state.last_brief_pull_at),
171
- last_project_board_at=COALESCE(excluded.last_project_board_at, runtime_turn_state.last_project_board_at),
172
- last_chat_sync_at=COALESCE(excluded.last_chat_sync_at, runtime_turn_state.last_chat_sync_at),
173
- last_memory_update_at=COALESCE(excluded.last_memory_update_at, runtime_turn_state.last_memory_update_at),
174
- last_full_sync_at=COALESCE(excluded.last_full_sync_at, runtime_turn_state.last_full_sync_at),
175
- last_audit_id=COALESCE(excluded.last_audit_id, runtime_turn_state.last_audit_id),
176
- meta_json=excluded.meta_json,
177
- updated_at=strftime('%Y-%m-%dT%H:%M:%fZ','now')
178
- `).run(
179
- scope,
180
- turnKey,
181
- runtime,
182
- agent,
183
- channel,
184
- project,
185
- board,
186
- threadId,
187
- next.message_count,
188
- next.message_count_since_full_sync,
189
- next.last_message_capture_at,
190
- next.last_recall_at,
191
- next.last_brief_pull_at,
192
- next.last_project_board_at,
193
- next.last_chat_sync_at,
194
- next.last_memory_update_at,
195
- next.last_full_sync_at,
196
- next.last_audit_id,
197
- safeJson(meta, {})
198
- );
199
- return stateFromRow(db.prepare("SELECT * FROM runtime_turn_state WHERE scope=? AND turn_key=?").get(scope, turnKey));
200
- }
201
-
202
- function checkInputFromState(input = {}, state = {}, extra = {}) {
203
- return {
204
- scope: input.scope,
205
- runtime_name: state.runtime_name || input.runtime_name || input.runtime || input.adapter || "external",
206
- agent_name: state.agent_name || input.agent_name || input.agent || "agent",
207
- channel: state.channel || input.channel || "runtime",
208
- project: state.project || input.project || null,
209
- board: state.board || inferBoard(input) || input.board || input.project_board || null,
210
- project_board: state.board || inferBoard(input) || input.board || input.project_board || null,
211
- message_count_since_full_sync: state.message_count_since_full_sync || 0,
212
- turn_number: state.message_count || 0,
213
- has_brief_pull: !!state.last_brief_pull_at,
214
- has_recall: !!state.last_recall_at || !!extra.recall_ok,
215
- has_project_board: !!state.last_project_board_at,
216
- has_chat_sync: !!state.last_chat_sync_at || !!extra.capture_ok,
217
- has_memory_update: !!state.last_memory_update_at || !!extra.capture_ok,
218
- has_message_capture: !!state.last_message_capture_at || !!extra.capture_ok,
219
- brief_pull_at: state.last_brief_pull_at,
220
- recall_at: state.last_recall_at,
221
- project_board_at: state.last_project_board_at,
222
- chat_sync_at: state.last_chat_sync_at,
223
- memory_update_at: state.last_memory_update_at,
224
- message_capture_at: state.last_message_capture_at,
225
- message_ref: input.message_ref || input.ref_id || input.message_id || null,
226
- session_key: input.session_key || input.session_id || input.thread_id || null,
227
- meta: Object.assign({}, input.meta || {}, { runtime_turn_key: state.turn_key }),
228
- };
229
- }
230
-
231
- function shouldUseTelegramEnvelope(input = {}) {
232
- if (input.ref_kind === "telegram_message" || input.telegram === true) return true;
233
- const meta = input.meta || {};
234
- return !!(input.chat_id || meta.chat_id);
235
- }
236
-
237
- function capturePayload(input = {}, state = {}) {
238
- const metaInput = input.meta || {};
239
- const telegram = shouldUseTelegramEnvelope(input);
240
- const messageId = input.message_id || metaInput.message_id || input.ref_id || null;
241
- const refId = input.ref_id || messageId || `${state.turn_key}:${state.message_count || 1}`;
242
- const source = input.source || `runtime:${state.runtime_name || normalizeRuntimeName(input.runtime_name || input.runtime || input.adapter || "external")}`;
243
- const content = compactContent(input.content !== undefined ? input.content : (input.text !== undefined ? input.text : input.message), input.max_content_chars || 12000) || "";
244
- const hasMedia = boolFlag(input.has_media, false) ||
245
- !!(input.media_path || input.file_path || input.file_name ||
246
- metaInput.media_path || metaInput.file_path || metaInput.file_name ||
247
- input.data_base64 || input.content_base64 || metaInput.data_base64 || metaInput.content_base64);
248
- const meta = Object.assign({}, metaInput, {
249
- runtime_name: state.runtime_name,
250
- agent_name: state.agent_name,
251
- project: state.project || input.project || null,
252
- board: state.board || inferBoard(input) || null,
253
- runtime_turn_key: state.turn_key,
254
- turn_number: state.message_count,
255
- });
256
- if (input.chat_id && !meta.chat_id) meta.chat_id = input.chat_id;
257
- if (messageId && !meta.message_id) meta.message_id = messageId;
258
- return {
259
- source,
260
- channel: state.channel || input.channel || "runtime",
261
- direction: input.direction || "inbound",
262
- actor: input.actor || input.speaker || input.user || input.user_name || "user",
263
- actor_id: input.actor_id || input.user_id || meta.actor_id || meta.user_id || null,
264
- event_kind: input.event_kind || (hasMedia ? (telegram ? "telegram_attachment" : "media_attachment") : "runtime_message"),
265
- ref_kind: input.ref_kind || (telegram ? (hasMedia ? "telegram_attachment" : "telegram_message") : (hasMedia ? "media_attachment" : "runtime_message")),
266
- ref_id: String(refId),
267
- source_ref: input.source_ref || `${source}:${refId}`,
268
- thread_id: state.thread_id || inferThreadId(input),
269
- occurred_at: input.occurred_at || nowIso(),
270
- content,
271
- promote_transcript: input.promote_transcript !== false,
272
- promote_memory: input.promote_memory !== false,
273
- remember: input.remember !== false,
274
- importance: input.importance != null ? input.importance : 4,
275
- media_path: input.media_path || input.file_path || metaInput.media_path || metaInput.file_path || null,
276
- file_path: input.file_path || input.media_path || metaInput.file_path || metaInput.media_path || null,
277
- file_name: input.file_name || metaInput.file_name || null,
278
- media_kind: input.media_kind || metaInput.media_kind || null,
279
- media_type: input.media_type || metaInput.media_type || null,
280
- data_base64: input.data_base64 || input.content_base64 || metaInput.data_base64 || metaInput.content_base64 || null,
281
- content_base64: input.content_base64 || input.data_base64 || metaInput.content_base64 || metaInput.data_base64 || null,
282
- title: input.title || metaInput.title || null,
283
- notes: input.notes || metaInput.notes || null,
284
- labels: input.labels || metaInput.labels || null,
285
- page_url: input.page_url || metaInput.page_url || metaInput.url || null,
286
- route: input.route || metaInput.route || null,
287
- meta,
288
- };
289
- }
290
-
291
- function compactRows(rows, limit = 5) {
292
- return (Array.isArray(rows) ? rows : []).slice(0, limit).map((row) => ({
293
- surface: row.surface || row.kind || null,
294
- ref_id: row.ref_id || row.id || null,
295
- actor: row.actor || row.agent_name || null,
296
- topic: row.topic || row.kind || null,
297
- occurred_at: row.occurred_at || null,
298
- preview: compactContent(row.preview || row.text || row.content || "", 240),
299
- }));
300
- }
301
-
302
- function looksLikeMediaQuery(input = {}, content = "") {
303
- if (boolFlag(input.media_recall, false)) return true;
304
- const q = [
305
- input.media_query,
306
- input.recall_query,
307
- content,
308
- ].filter(Boolean).join(" ").toLowerCase();
309
- return /\b(video|videos|bild|bilder|foto|fotos|photo|photos|datei|dateien|file|files|anhang|anhänge|anhaenge|attachment|attachments|screenshot|screenshots|mp4|mov|webm|mp3|wav|fredrik|frerik)\b/.test(q);
310
- }
311
-
312
- function mediaRecallQuery(input = {}, content = "") {
313
- return compactContent(input.media_query || input.recall_query || content || [
314
- input.file_name,
315
- input.title,
316
- input.project,
317
- ].filter(Boolean).join(" "), 500);
318
- }
319
-
320
- function compactMediaRows(rows, limit = 5) {
321
- return (Array.isArray(rows) ? rows : []).slice(0, limit).map((row) => ({
322
- id: row.id || null,
323
- title: row.title || null,
324
- media_kind: row.media_kind || null,
325
- media_type: row.media_type || null,
326
- project: row.project || null,
327
- actor: row.actor || null,
328
- occurred_at: row.occurred_at || null,
329
- original_file_name: row.original_file_name || null,
330
- file_name: row.file_name || row.canonical_name || null,
331
- media_path: row.media_path || null,
332
- storage_path: row.storage_path || null,
333
- status: row.status || null,
334
- }));
335
- }
336
-
337
- function makeContextBlock(result = {}) {
338
- const lines = [
339
- "[Mnemo Runtime Turn]",
340
- `status: ${result.status || (result.ok ? "ok" : "error")}`,
341
- `allowed: ${result.allowed ? "yes" : "no"}`,
342
- `runtime: ${result.runtime_name || ""}`,
343
- `agent: ${result.agent_name || ""}`,
344
- `project: ${result.project || ""}`,
345
- `turn_key: ${result.turn_key || ""}`,
346
- `message_captured: ${result.capture && result.capture.ok ? "yes" : "no"}`,
347
- `memory_checked: ${result.recall && result.recall.ok ? "yes" : "no"}`,
348
- `media_checked: ${result.media_recall && result.media_recall.ok ? "yes" : "no"}`,
349
- `governance_checked: ${result.governance_recall && result.governance_recall.ok ? "yes" : "no"}`,
350
- `resume_pack: ${result.resume_pack_loaded ? "yes" : "no"}`,
351
- `full_sync: ${result.full_sync_ran ? "yes" : "no"}`,
352
- `audit_id: ${result.audit_id || ""}`,
353
- ];
354
- if (result.warning_token) lines.push(`warning: ${result.warning_token}`);
355
- if (result.blockers && result.blockers.length) lines.push(`blockers: ${result.blockers.join("; ")}`);
356
- if (result.next_actions && result.next_actions.length) lines.push(`next_actions: ${result.next_actions.join("; ")}`);
357
- lines.push("[/Mnemo Runtime Turn]");
358
- return lines.join("\n");
359
- }
360
-
361
- function runtimeTurnBegin(db, input = {}, ops = {}) {
362
- ensureRuntimeTurnSchema(db);
363
- const content = compactContent(input.content !== undefined ? input.content : (input.text !== undefined ? input.text : input.message), input.max_content_chars || 12000) || "";
364
- if (!content && !boolFlag(input.has_media, false)) return { ok: false, allowed: false, status: "error", error: "content/text/message required" };
365
-
366
- let state = upsertTurnState(db, input, { meta: { last_input_ref: input.message_ref || input.ref_id || input.message_id || null } });
367
- const now = nowIso();
368
-
369
- let capture = { ok: false, error: "capture op missing" };
370
- if (typeof ops.capture === "function") {
371
- try { capture = ops.capture(capturePayload(input, state)); }
372
- catch (e) { capture = { ok: false, error: String(e.message || e) }; }
373
- }
374
- if (capture && capture.ok) {
375
- state = upsertTurnState(db, input, {
376
- increment: false,
377
- last_message_capture_at: now,
378
- last_chat_sync_at: now,
379
- last_memory_update_at: now,
380
- meta: { last_capture_status: capture.status || "ok", last_capture_id: capture.event_id || capture.memory_id || null },
381
- });
382
- }
383
-
384
- let recallRows = [];
385
- let recall = { ok: false, error: "recall op missing", count: 0, rows: [] };
386
- if (typeof ops.recall === "function") {
387
- try {
388
- recallRows = ops.recall({
389
- query: input.recall_query || content,
390
- limit: intFlag(input.recall_limit, 8, 1, 50),
391
- mode: input.recall_mode || "hybrid",
392
- include_journal: input.include_journal !== false,
393
- actor: input.recall_actor || null,
394
- }) || [];
395
- recall = { ok: true, count: Array.isArray(recallRows) ? recallRows.length : 0, rows: compactRows(recallRows, 6) };
396
- } catch (e) {
397
- recall = { ok: false, error: String(e.message || e), count: 0, rows: [] };
398
- }
399
- }
400
- if (recall.ok) {
401
- state = upsertTurnState(db, input, {
402
- increment: false,
403
- last_recall_at: now,
404
- meta: { last_recall_count: recall.count },
405
- });
406
- }
407
-
408
- let mediaRecall = { ok: false, error: "media search skipped", count: 0, media: [] };
409
- if (typeof ops.mediaSearch === "function" && looksLikeMediaQuery(input, content)) {
410
- try {
411
- const mediaRows = ops.mediaSearch({
412
- query: mediaRecallQuery(input, content),
413
- project: input.media_project || input.project || null,
414
- media_kind: input.media_kind_filter || input.media_kind || null,
415
- limit: intFlag(input.media_recall_limit, 8, 1, 50),
416
- }) || {};
417
- const media = Array.isArray(mediaRows.media) ? mediaRows.media : (Array.isArray(mediaRows) ? mediaRows : []);
418
- mediaRecall = { ok: true, count: media.length, media: compactMediaRows(media, 6) };
419
- } catch (e) {
420
- mediaRecall = { ok: false, error: String(e.message || e), count: 0, media: [] };
421
- }
422
- }
423
-
424
- let governanceRows = [];
425
- let governanceRecall = { ok: false, error: "recall op missing", count: 0, rows: [] };
426
- if (typeof ops.recall === "function") {
427
- const governanceQuery = [
428
- state.project || input.project || "",
429
- state.agent_name || input.agent_name || "",
430
- "owner rule forbidden no-go protected scope final decision scar incident never again correction completed done handoff"
431
- ].filter(Boolean).join(" ");
432
- try {
433
- governanceRows = ops.recall({
434
- query: input.governance_recall_query || governanceQuery,
435
- limit: intFlag(input.governance_recall_limit, 8, 1, 30),
436
- mode: "hybrid",
437
- include_journal: true,
438
- journal_scopes: ["transcript", "brief", "event"],
439
- like_fallback: true,
440
- }) || [];
441
- governanceRecall = { ok: true, count: Array.isArray(governanceRows) ? governanceRows.length : 0, rows: compactRows(governanceRows, 8) };
442
- state = upsertTurnState(db, input, {
443
- increment: false,
444
- meta: { last_governance_recall_at: now, last_governance_recall_count: governanceRecall.count },
445
- });
446
- } catch (e) {
447
- governanceRecall = { ok: false, error: String(e.message || e), count: 0, rows: [] };
448
- }
449
- }
450
-
451
- const preliminary = runtimePolicyCheck(db, checkInputFromState(input, state, { capture_ok: capture && capture.ok, recall_ok: recall.ok && governanceRecall.ok }));
452
- const required = new Set(preliminary.required_actions || []);
453
- const missingRequirements = new Set((preliminary.missing_context || []).map((entry) => entry.requirement));
454
- const fullSyncDue = !!preliminary.full_sync_due || missingRequirements.has("full_sync_every_messages");
455
- const fullSync = { ran: false };
456
- const actionErrors = [];
457
-
458
- if (typeof ops.briefPull === "function" && (required.has("mem_brief_pull") || missingRequirements.has("mem_brief_pull") || fullSyncDue)) {
459
- try {
460
- fullSync.brief_pull = ops.briefPull({
461
- agent_name: state.agent_name,
462
- limit: intFlag(input.brief_limit, 20, 1, 100),
463
- peek: input.brief_peek !== false,
464
- });
465
- fullSync.ran = true;
466
- state = upsertTurnState(db, input, { increment: false, last_brief_pull_at: now });
467
- } catch (e) {
468
- actionErrors.push("brief_pull: " + String(e.message || e));
469
- fullSync.brief_pull = { ok: false, error: String(e.message || e) };
470
- }
471
- }
472
-
473
- if (typeof ops.projectBoard === "function" && (required.has("mem_project_board") || missingRequirements.has("mem_project_board") || fullSyncDue)) {
474
- try {
475
- fullSync.project_board = ops.projectBoard({
476
- project: state.project || input.project || "default",
477
- name: state.board || inferBoard(input) || undefined,
478
- include_done: false,
479
- include_ingested_briefs: true,
480
- limit: intFlag(input.board_limit, 20, 1, 100),
481
- });
482
- fullSync.ran = true;
483
- state = upsertTurnState(db, input, { increment: false, last_project_board_at: now });
484
- } catch (e) {
485
- actionErrors.push("project_board: " + String(e.message || e));
486
- fullSync.project_board = { ok: false, error: String(e.message || e) };
487
- }
488
- }
489
-
490
- const resumePackDue = state.message_count <= 1 || fullSyncDue || required.has("mem_work_report_feed") || required.has("mem_project_timeline_report");
491
- if (resumePackDue && typeof ops.workReportFeed === "function") {
492
- try {
493
- fullSync.work_report_feed = ops.workReportFeed({
494
- project: state.project || input.project || null,
495
- agent_name: state.agent_name,
496
- include_blocked: true,
497
- limit: intFlag(input.resume_limit, 12, 1, 50),
498
- });
499
- fullSync.ran = true;
500
- state = upsertTurnState(db, input, {
501
- increment: false,
502
- meta: {
503
- last_resume_pack_at: now,
504
- last_work_report_feed_count: fullSync.work_report_feed && fullSync.work_report_feed.feed_count || 0,
505
- },
506
- });
507
- } catch (e) {
508
- actionErrors.push("work_report_feed: " + String(e.message || e));
509
- fullSync.work_report_feed = { ok: false, error: String(e.message || e) };
510
- }
511
- }
512
-
513
- if (resumePackDue && typeof ops.timelineReport === "function" && (state.project || input.project)) {
514
- try {
515
- fullSync.project_timeline_report = ops.timelineReport({
516
- project: state.project || input.project,
517
- agent_name: state.agent_name,
518
- days: intFlag(input.resume_days, 30, 1, 3650),
519
- max_items: intFlag(input.resume_limit, 12, 3, 50),
520
- token_budget: intFlag(input.resume_token_budget, 3600, 800, 24000),
521
- });
522
- fullSync.ran = true;
523
- state = upsertTurnState(db, input, {
524
- increment: false,
525
- meta: {
526
- last_resume_timeline_at: now,
527
- last_resume_timeline_status: fullSync.project_timeline_report && fullSync.project_timeline_report.status || null,
528
- },
529
- });
530
- } catch (e) {
531
- actionErrors.push("project_timeline_report: " + String(e.message || e));
532
- fullSync.project_timeline_report = { ok: false, error: String(e.message || e) };
533
- }
534
- }
535
-
536
- if (typeof ops.eventLog === "function" && (fullSyncDue || fullSync.ran)) {
537
- try {
538
- const event = ops.eventLog({
539
- source: "runtime_turn_gate",
540
- channel: state.channel,
541
- direction: "internal",
542
- actor: state.agent_name,
543
- event_kind: fullSyncDue ? "runtime_full_sync" : "runtime_context_sync",
544
- ref_kind: "runtime_turn_state",
545
- ref_id: state.turn_key,
546
- thread_id: state.thread_id,
547
- status: actionErrors.length ? "error" : "ok",
548
- content: `runtime turn sync for ${state.agent_name} ${state.project || ""}`.trim(),
549
- payload: {
550
- message_count: state.message_count,
551
- message_count_since_full_sync: state.message_count_since_full_sync,
552
- capture_ok: !!(capture && capture.ok),
553
- recall_count: recall.count,
554
- brief_count: fullSync.brief_pull && fullSync.brief_pull.count,
555
- },
556
- meta: { errors: actionErrors, runtime_name: state.runtime_name, board: state.board },
557
- });
558
- fullSync.event_log = event;
559
- if (fullSyncDue && !actionErrors.length) {
560
- state = upsertTurnState(db, input, {
561
- increment: false,
562
- reset_full_sync_counter: true,
563
- last_full_sync_at: now,
564
- meta: { last_full_sync_event_id: event && event.id || null },
565
- });
566
- }
567
- } catch (e) {
568
- actionErrors.push("event_log: " + String(e.message || e));
569
- fullSync.event_log = { ok: false, error: String(e.message || e) };
570
- }
571
- }
572
-
573
- const finalCheck = runtimePolicyCheck(db, Object.assign(
574
- {},
575
- checkInputFromState(input, state, { capture_ok: capture && capture.ok, recall_ok: recall.ok && governanceRecall.ok }),
576
- { has_full_sync: fullSyncDue && fullSync.ran && !actionErrors.length }
577
- ));
578
- state = upsertTurnState(db, input, {
579
- increment: false,
580
- last_audit_id: finalCheck.audit_id || null,
581
- meta: { last_policy_status: finalCheck.status },
582
- });
583
-
584
- const result = {
585
- ok: finalCheck.allowed && actionErrors.length === 0,
586
- allowed: finalCheck.allowed && actionErrors.length === 0,
587
- response_allowed: finalCheck.response_allowed && actionErrors.length === 0,
588
- status: actionErrors.length ? "error" : finalCheck.status,
589
- runtime_name: state.runtime_name,
590
- agent_name: state.agent_name,
591
- channel: state.channel,
592
- project: state.project,
593
- board: state.board,
594
- turn_key: state.turn_key,
595
- thread_id: state.thread_id,
596
- message_count: state.message_count,
597
- message_count_since_full_sync: state.message_count_since_full_sync,
598
- audit_id: finalCheck.audit_id,
599
- warning_token: finalCheck.warning_token || null,
600
- blockers: actionErrors.concat((finalCheck.missing_context || []).map((entry) => entry.reason || entry.requirement)),
601
- next_actions: finalCheck.required_actions || [],
602
- capture,
603
- recall,
604
- media_recall: mediaRecall,
605
- governance_recall: governanceRecall,
606
- resume_pack_loaded: !!(fullSync.work_report_feed || fullSync.project_timeline_report) && !actionErrors.some((err) => /work_report_feed|project_timeline_report/.test(err)),
607
- full_sync_ran: !!(fullSync.ran || fullSyncDue),
608
- full_sync_due: fullSyncDue,
609
- full_sync: {
610
- brief_pull_count: fullSync.brief_pull && fullSync.brief_pull.count || 0,
611
- project_board_loaded: !!fullSync.project_board && !fullSync.project_board.error,
612
- work_report_feed_count: fullSync.work_report_feed && fullSync.work_report_feed.feed_count || 0,
613
- project_timeline_loaded: !!fullSync.project_timeline_report && !fullSync.project_timeline_report.error,
614
- event_log_id: fullSync.event_log && fullSync.event_log.id || null,
615
- },
616
- policy_check: finalCheck,
617
- };
618
- result.context_block = makeContextBlock(result);
619
- return result;
620
- }
621
-
622
- function runtimeTurnFinish(db, input = {}, ops = {}) {
623
- ensureRuntimeTurnSchema(db);
624
- const content = compactContent(
625
- input.response !== undefined ? input.response : (input.content !== undefined ? input.content : (input.text !== undefined ? input.text : input.message)),
626
- input.max_content_chars || 12000
627
- ) || "";
628
- if (!content && !boolFlag(input.has_media, false)) return { ok: false, status: "error", error: "response/content/text/message required" };
629
-
630
- const state = upsertTurnState(db, input, {
631
- increment: false,
632
- meta: { last_outbound_ref: input.message_ref || input.ref_id || input.message_id || null },
633
- });
634
- const now = nowIso();
635
- const runtime = state.runtime_name || normalizeRuntimeName(input.runtime_name || input.runtime || input.adapter || "external");
636
- const agent = state.agent_name || normalizeAgentName(input.agent_name || input.agent || "agent") || "agent";
637
- const telegram = shouldUseTelegramEnvelope(input);
638
- const messageId = input.message_id || (input.meta && input.meta.message_id) || input.ref_id || null;
639
- const refId = input.ref_id || messageId || `out:${state.turn_key}:${Date.now()}`;
640
- const meta = Object.assign({}, input.meta || {}, {
641
- runtime_name: runtime,
642
- agent_name: agent,
643
- project: state.project || input.project || null,
644
- board: state.board || inferBoard(input) || null,
645
- runtime_turn_key: state.turn_key,
646
- outbound: true,
647
- recipient: input.recipient || input.target || input.to || null,
648
- reply_to_ref: input.reply_to_ref || input.reply_to_message_id || input.in_reply_to || null,
649
- });
650
- if (input.chat_id && !meta.chat_id) meta.chat_id = input.chat_id;
651
- if (messageId && !meta.message_id) meta.message_id = messageId;
652
-
653
- let capture = { ok: false, error: "capture op missing" };
654
- if (typeof ops.capture === "function") {
655
- try {
656
- capture = ops.capture({
657
- source: input.source || `runtime:${runtime}:outbound`,
658
- channel: state.channel || input.channel || "runtime",
659
- direction: "outbound",
660
- actor: input.actor || input.speaker || agent,
661
- actor_id: input.actor_id || input.user_id || meta.actor_id || meta.user_id || null,
662
- speaker: input.speaker || input.actor || agent,
663
- event_kind: input.event_kind || "runtime_response",
664
- ref_kind: telegram ? "telegram_message" : (input.ref_kind || "runtime_response"),
665
- ref_id: String(refId),
666
- source_ref: input.source_ref || `runtime_response:${runtime}:${agent}:${refId}`,
667
- thread_id: state.thread_id || inferThreadId(input),
668
- occurred_at: input.occurred_at || now,
669
- content,
670
- promote_transcript: input.promote_transcript !== false,
671
- promote_memory: input.promote_memory !== false,
672
- remember: input.remember !== false,
673
- importance: input.importance != null ? input.importance : 4,
674
- meta,
675
- });
676
- } catch (e) {
677
- capture = { ok: false, error: String(e.message || e) };
678
- }
679
- }
680
-
681
- let event = null;
682
- if (typeof ops.eventLog === "function") {
683
- try {
684
- event = ops.eventLog({
685
- source: "runtime_turn_gate",
686
- channel: state.channel,
687
- direction: "outbound",
688
- actor: agent,
689
- event_kind: "runtime_outbound_capture",
690
- ref_kind: "runtime_turn_state",
691
- ref_id: state.turn_key,
692
- thread_id: state.thread_id,
693
- status: capture && capture.ok ? "ok" : "error",
694
- content: `runtime outbound capture for ${agent}`,
695
- payload: {
696
- capture_ok: !!(capture && capture.ok),
697
- response_ref: String(refId),
698
- recipient: meta.recipient || null,
699
- },
700
- meta: { runtime_name: runtime, error: capture && capture.error || null },
701
- });
702
- } catch {}
703
- }
704
-
705
- const updated = capture && capture.ok
706
- ? upsertTurnState(db, input, {
707
- increment: false,
708
- last_chat_sync_at: now,
709
- last_memory_update_at: now,
710
- meta: { last_outbound_capture_id: capture.event_id || capture.memory_id || null },
711
- })
712
- : state;
713
-
714
- return {
715
- ok: !!(capture && capture.ok),
716
- status: capture && capture.ok ? "captured" : "error",
717
- runtime_name: runtime,
718
- agent_name: agent,
719
- channel: updated.channel,
720
- project: updated.project,
721
- board: updated.board,
722
- turn_key: updated.turn_key,
723
- thread_id: updated.thread_id,
724
- direction: "outbound",
725
- capture,
726
- event_log_id: event && event.id || null,
727
- context_block: [
728
- "[Mnemo Runtime Outbound]",
729
- `status: ${capture && capture.ok ? "captured" : "error"}`,
730
- `runtime: ${runtime}`,
731
- `agent: ${agent}`,
732
- `project: ${updated.project || ""}`,
733
- `turn_key: ${updated.turn_key}`,
734
- `outbound_captured: ${capture && capture.ok ? "yes" : "no"}`,
735
- `event_log_id: ${event && event.id || ""}`,
736
- "[/Mnemo Runtime Outbound]",
737
- ].join("\n"),
738
- };
739
- }
740
-
741
- const RUNTIME_TURN_TOOL_DEFS = {
742
- mem_runtime_turn_begin: {
743
- description: "Runtime-neutral pre-answer gate for runtime adapter, runtime-a, and portal chat: capture the inbound message, recall memory, refresh briefs/project board as required, run the every-N-message full sync, and return an allow/block context block before the agent may answer.",
744
- inputSchema: {
745
- type: "object",
746
- properties: {
747
- scope: { type: "string" },
748
- runtime_name: { type: "string" },
749
- runtime: { type: "string" },
750
- adapter: { type: "string" },
751
- agent_name: { type: "string" },
752
- agent: { type: "string" },
753
- channel: { type: "string" },
754
- project: { type: "string" },
755
- board: { type: "string" },
756
- project_board: { type: "string" },
757
- thread_id: { type: "string" },
758
- session_id: { type: "string" },
759
- session_key: { type: "string" },
760
- conversation_id: { type: "string" },
761
- chat_id: { type: "string" },
762
- message_id: { type: "string" },
763
- message_ref: { type: "string" },
764
- ref_kind: { type: "string" },
765
- ref_id: { type: "string" },
766
- source: { type: "string" },
767
- source_ref: { type: "string" },
768
- direction: { type: "string" },
769
- actor: { type: "string" },
770
- speaker: { type: "string" },
771
- user: { type: "string" },
772
- user_name: { type: "string" },
773
- actor_id: { type: "string" },
774
- user_id: { type: "string" },
775
- content: { type: "string" },
776
- text: { type: "string" },
777
- message: { type: "string" },
778
- recall_query: { type: "string" },
779
- recall_limit: { type: "integer" },
780
- media_query: { type: "string" },
781
- media_recall: { type: "boolean" },
782
- media_recall_limit: { type: "integer" },
783
- media_path: { type: "string" },
784
- file_path: { type: "string" },
785
- file_name: { type: "string" },
786
- media_kind: { type: "string" },
787
- media_type: { type: "string" },
788
- data_base64: { type: "string" },
789
- content_base64: { type: "string" },
790
- title: { type: "string" },
791
- notes: { type: "string" },
792
- labels: { type: "array", items: { type: "string" } },
793
- has_media: { type: "boolean" },
794
- brief_limit: { type: "integer" },
795
- board_limit: { type: "integer" },
796
- promote_memory: { type: "boolean" },
797
- promote_transcript: { type: "boolean" },
798
- remember: { type: "boolean" },
799
- telegram: { type: "boolean" },
800
- meta: { type: "object" },
801
- },
802
- required: ["agent_name"],
803
- },
804
- },
805
- mem_runtime_turn_finish: {
806
- description: "Runtime-neutral outbound capture hook. Call after an agent sends a reply to owner, Telegram, another agent, email, or a portal chat so agent-to-agent and agent-to-human messages are captured in Mnemo, not only inbound user prompts.",
807
- inputSchema: {
808
- type: "object",
809
- properties: {
810
- scope: { type: "string" },
811
- runtime_name: { type: "string" },
812
- runtime: { type: "string" },
813
- adapter: { type: "string" },
814
- agent_name: { type: "string" },
815
- agent: { type: "string" },
816
- channel: { type: "string" },
817
- project: { type: "string" },
818
- board: { type: "string" },
819
- project_board: { type: "string" },
820
- thread_id: { type: "string" },
821
- session_id: { type: "string" },
822
- session_key: { type: "string" },
823
- conversation_id: { type: "string" },
824
- chat_id: { type: "string" },
825
- message_id: { type: "string" },
826
- message_ref: { type: "string" },
827
- ref_kind: { type: "string" },
828
- ref_id: { type: "string" },
829
- source: { type: "string" },
830
- source_ref: { type: "string" },
831
- actor: { type: "string" },
832
- speaker: { type: "string" },
833
- actor_id: { type: "string" },
834
- user_id: { type: "string" },
835
- recipient: { type: "string" },
836
- target: { type: "string" },
837
- to: { type: "string" },
838
- reply_to_ref: { type: "string" },
839
- reply_to_message_id: { type: "string" },
840
- in_reply_to: { type: "string" },
841
- response: { type: "string" },
842
- content: { type: "string" },
843
- text: { type: "string" },
844
- message: { type: "string" },
845
- promote_memory: { type: "boolean" },
846
- promote_transcript: { type: "boolean" },
847
- remember: { type: "boolean" },
848
- telegram: { type: "boolean" },
849
- meta: { type: "object" },
850
- },
851
- required: ["agent_name"],
852
- },
853
- },
854
- };
855
-
856
- module.exports = {
857
- RUNTIME_TURN_TOOL_DEFS,
858
- ensureRuntimeTurnSchema,
859
- runtimeTurnBegin,
860
- runtimeTurnFinish,
861
- buildTurnKey,
862
- };