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,137 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /**
4
- * Non-interactive bootstrap for CI, containers, and scripted installs.
5
- */
6
- const fs = require("fs");
7
- const path = require("path");
8
- const Database = require("better-sqlite3");
9
-
10
- const ROOT = __dirname;
11
- const REPO_ROOT = path.resolve(ROOT, "..", "..");
12
- const dbPath = process.env.MNEMO_DB || path.join(ROOT, "mnemo.db");
13
- const ENV_FILE = process.env.MNEMO_ENV_FILE || path.join(ROOT, ".env.local");
14
- const HOOK_ENV_FILE = process.env.MNEMO_HOOK_ENV_FILE || path.join(REPO_ROOT, ".mnemo-hook.env");
15
-
16
- function cleanScope(value) {
17
- return String(value || "personal").trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "personal";
18
- }
19
-
20
- function writeEnv(file, entries) {
21
- const lines = [`# Generated by mnemo bootstrap_auto on ${new Date().toISOString()}`];
22
- for (const [key, value] of entries) {
23
- if (value === undefined || value === null || value === "") continue;
24
- lines.push(`${key}=${String(value)}`);
25
- }
26
- fs.mkdirSync(path.dirname(file), { recursive: true });
27
- fs.writeFileSync(file, lines.join("\n") + "\n");
28
- }
29
-
30
- function readJson(file) {
31
- return JSON.parse(fs.readFileSync(file, "utf8"));
32
- }
33
-
34
- function writeJson(file, data) {
35
- fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n");
36
- }
37
-
38
- const ownerName = process.env.MNEMO_OWNER_NAME || "owner";
39
- const scopeName = cleanScope(process.env.MNEMO_DEFAULT_SCOPE || process.env.MNEMO_SCOPE || "personal");
40
- const primaryAgent = cleanScope(process.env.MNEMO_AGENT || process.env.MNEMO_DEFAULT_AGENT || "agent");
41
- const agents = String(process.env.MNEMO_LOCAL_AGENTS || primaryAgent).split(",").map(s => cleanScope(s)).filter(Boolean);
42
- const tz = process.env.MNEMO_TZ_OFFSET_HOURS || "0";
43
- const mission = process.env.MNEMO_MISSION || "remember context, coordinate work, and avoid forgotten tasks";
44
- const firstProject = process.env.MNEMO_PROJECT || "";
45
-
46
- console.log("\nMnemo auto-bootstrap starting...\n");
47
-
48
- const db = new Database(dbPath);
49
- db.pragma("journal_mode = WAL");
50
-
51
- for (const f of ["schema.sql", "identity_schema.sql"]) {
52
- const p = path.join(ROOT, f);
53
- if (fs.existsSync(p)) {
54
- db.exec(fs.readFileSync(p, "utf8"));
55
- console.log(` OK Applied ${f}`);
56
- }
57
- }
58
-
59
- const coreValue = db.prepare("INSERT OR IGNORE INTO core_value (name, statement, scope, rationale) VALUES (?,?,?,?)");
60
- coreValue.run("owner_identity", `Owner of this Mnemo instance is "${ownerName}".`, "all", "Set during bootstrap.");
61
- coreValue.run("operating_scope", `Default Mnemo scope is "${scopeName}".`, "all", "Set during bootstrap.");
62
- coreValue.run("agent_roster", `Active agents: ${agents.join(", ") || primaryAgent}.`, "all", "Set during bootstrap.");
63
- coreValue.run("mission", mission, "all", "Set during bootstrap.");
64
- coreValue.run("identity_must_persist", "Agents must reload owner identity, their own role, core values, preferences, open promises, and project rules at the start of every meaningful session.", "all", "Prevents identity drift across sessions.");
65
- coreValue.run("token_efficiency", "Agents must search small, inspect compact IDs/timelines first, and fetch full memories only for selected records.", "all", "Prevents wasting context window and tokens.");
66
-
67
- db.exec(`
68
- CREATE TABLE IF NOT EXISTS correction_pattern (
69
- id INTEGER PRIMARY KEY AUTOINCREMENT,
70
- pattern TEXT NOT NULL UNIQUE,
71
- classifier TEXT NOT NULL,
72
- actor_scope TEXT,
73
- trait_to_adjust TEXT,
74
- delta REAL,
75
- hit_count INTEGER NOT NULL DEFAULT 0,
76
- last_hit_at TEXT
77
- )
78
- `);
79
- const ins = db.prepare("INSERT OR IGNORE INTO correction_pattern (pattern, classifier, actor_scope, trait_to_adjust, delta) VALUES (?,?,?,?,?)");
80
- ins.run("\\b(stop|halt|not like that|don't|no)\\b", "correction", ownerName, null, null);
81
- ins.run("\\b(perfect|nice|exactly|love it|good)\\b", "praise", ownerName, null, null);
82
- ins.run("\\b(I'll|i will|going to|will do)\\b", "promise", null, null, null);
83
-
84
- const factsDir = process.env.MNEMO_FACTS_DIR || path.join(ROOT, "facts");
85
- fs.mkdirSync(factsDir, { recursive: true });
86
- const factsPath = path.join(factsDir, `${scopeName}.json`);
87
- const rulesPath = path.join(factsDir, `${scopeName}-project-rules.json`);
88
-
89
- if (!fs.existsSync(factsPath)) {
90
- const facts = readJson(path.join(ROOT, "facts", "example.json"));
91
- facts._comment = "PRIVATE LOCAL FILE - edit for your own team/company. Do not commit real facts.";
92
- facts._meta.scope = scopeName;
93
- facts._meta.updated = new Date().toISOString().slice(0, 10);
94
- facts.owner = { name: ownerName };
95
- facts.agents = agents.map(name => ({ name, role: "agent", status: "active" }));
96
- facts.mission = { summary: mission };
97
- writeJson(factsPath, facts);
98
- }
99
-
100
- if (!fs.existsSync(rulesPath)) {
101
- const rules = readJson(path.join(ROOT, "facts", "example-project-rules.json"));
102
- rules._comment = "PRIVATE LOCAL FILE - edit project rules before enabling blocking hooks. Do not commit real project rules.";
103
- rules._meta.scope = scopeName;
104
- rules._meta.updated = new Date().toISOString().slice(0, 10);
105
- if (firstProject) rules.projects[0].name = firstProject;
106
- writeJson(rulesPath, rules);
107
- }
108
-
109
- writeEnv(ENV_FILE, [
110
- ["MNEMO_DB", dbPath],
111
- ["MNEMO_OWNER_NAME", ownerName],
112
- ["MNEMO_DEFAULT_SCOPE", scopeName],
113
- ["MNEMO_SCOPE", scopeName],
114
- ["MNEMO_DEFAULT_AGENT", primaryAgent],
115
- ["MNEMO_AGENT", primaryAgent],
116
- ["MNEMO_LOCAL_AGENTS", agents.join(",")],
117
- ["MNEMO_FACTS_DIR", factsDir],
118
- ["MNEMO_TZ_OFFSET_HOURS", tz]
119
- ]);
120
-
121
- writeEnv(HOOK_ENV_FILE, [
122
- ["MNEMO_OWNER_NAME", ownerName],
123
- ["MNEMO_DEFAULT_SCOPE", scopeName],
124
- ["MNEMO_SCOPE", scopeName],
125
- ["MNEMO_DEFAULT_AGENT", primaryAgent],
126
- ["MNEMO_AGENT", primaryAgent],
127
- ["MNEMO_FACTS_DIR", factsDir],
128
- ["MNEMO_PROJECT", firstProject]
129
- ]);
130
-
131
- db.close();
132
-
133
- console.log(` OK DB initialized at ${dbPath}`);
134
- console.log(` OK Settings written to ${ENV_FILE}`);
135
- console.log(` OK Private facts written to ${factsPath}`);
136
- console.log(` OK Private project-rule seed written to ${rulesPath}`);
137
- console.log(` OK Hook env written to ${HOOK_ENV_FILE}`);
@@ -1,226 +0,0 @@
1
- "use strict";
2
-
3
- const { parseMaybeJson, normalizeAgentName } = require("./shared_utils");
4
-
5
- function nowIso() {
6
- return new Date().toISOString();
7
- }
8
-
9
- function safeJson(value, fallback) {
10
- try { return JSON.stringify(value == null ? fallback : value); } catch { return JSON.stringify(fallback || {}); }
11
- }
12
-
13
- function markStaleAgentsOffline(db, staleSec = 300) {
14
- const seconds = Math.max(30, Number(staleSec || 300));
15
- try {
16
- const info = db.prepare(
17
- "UPDATE agent_registry SET status='offline' " +
18
- "WHERE status<>'offline' AND (last_seen_at IS NULL OR (julianday('now') - julianday(last_seen_at)) * 86400 > ?)"
19
- ).run(seconds);
20
- return info.changes || 0;
21
- } catch {
22
- return 0;
23
- }
24
- }
25
-
26
- function requeueStaleDispatchedBriefs(db, options = {}) {
27
- const olderThanMinutes = Math.max(1, Math.min(Number(options.older_than_minutes || options.minutes || process.env.MNEMO_BRIEF_REQUEUE_MIN || 30), 1440));
28
- const agentStaleSec = Math.max(30, Number(options.agent_stale_sec || process.env.MNEMO_AGENT_OFFLINE_SEC || 300));
29
- const limit = Math.max(1, Math.min(Number(options.limit || 100), 1000));
30
- const dryRun = !!options.dry_run;
31
- const staleBefore = new Date(Date.now() - olderThanMinutes * 60 * 1000).toISOString();
32
- markStaleAgentsOffline(db, agentStaleSec);
33
- const rows = db.prepare(
34
- "SELECT b.id, b.agent_name, b.source_agent, b.channel, b.created_at, b.dispatched_at, " +
35
- "r.status AS agent_status, r.last_seen_at AS agent_last_seen_at " +
36
- "FROM agent_brief b " +
37
- "LEFT JOIN agent_registry r ON lower(r.agent_name)=lower(b.agent_name) " +
38
- "WHERE b.status='dispatched' AND b.dispatched_at IS NOT NULL AND b.dispatched_at < ? " +
39
- "AND (r.agent_name IS NULL OR r.status='offline' OR r.last_seen_at IS NULL OR (julianday('now') - julianday(r.last_seen_at)) * 86400 > ?) " +
40
- "ORDER BY b.dispatched_at ASC LIMIT ?"
41
- ).all(staleBefore, agentStaleSec, limit);
42
- if (dryRun || !rows.length) {
43
- return { ok: true, requeued: 0, candidates: rows, stale_before: staleBefore, older_than_minutes: olderThanMinutes, agent_stale_sec: agentStaleSec, dry_run: dryRun };
44
- }
45
- const stamp = nowIso();
46
- const update = db.prepare("UPDATE agent_brief SET status='pending', dispatched_at=NULL, outcome=COALESCE(outcome, ?) WHERE id=? AND status='dispatched'");
47
- const react = db.prepare("INSERT INTO agent_brief_reaction (brief_id, agent_name, kind, payload) VALUES (?,?,?,?)");
48
- let changed = 0;
49
- const tx = db.transaction((items) => {
50
- for (const row of items) {
51
- const reason = {
52
- auto_requeue: true,
53
- reason: "dispatched brief stale and target agent offline or not heartbeating",
54
- previous_status: "dispatched",
55
- dispatched_at: row.dispatched_at,
56
- agent_status: row.agent_status || "unregistered",
57
- agent_last_seen_at: row.agent_last_seen_at || null,
58
- older_than_minutes: olderThanMinutes,
59
- agent_stale_sec: agentStaleSec,
60
- requeued_at: stamp
61
- };
62
- const info = update.run("auto-requeued at " + stamp + ": target agent offline/stale", row.id);
63
- if (info.changes) {
64
- changed += info.changes;
65
- react.run(row.id, "mnemo-auto-requeue", "auto_requeue", safeJson(reason, {}));
66
- }
67
- }
68
- });
69
- tx(rows);
70
- return { ok: true, requeued: changed, candidates: rows, stale_before: staleBefore, older_than_minutes: olderThanMinutes, agent_stale_sec: agentStaleSec };
71
- }
72
-
73
- function agentAgeSec(lastSeenAt) {
74
- const t = Date.parse(lastSeenAt || "");
75
- if (!Number.isFinite(t)) return null;
76
- return Math.max(0, Math.round((Date.now() - t) / 1000));
77
- }
78
-
79
- function shouldReplaceSubscriber(existing, next) {
80
- if (!existing) return true;
81
- if (next.active !== existing.active) return next.active;
82
- const nextRegistered = next.status !== "unregistered";
83
- const existingRegistered = existing.status !== "unregistered";
84
- if (nextRegistered !== existingRegistered) return nextRegistered;
85
- if (next.last_seen_age_sec == null) return false;
86
- if (existing.last_seen_age_sec == null) return true;
87
- if (next.last_seen_age_sec !== existing.last_seen_age_sec) return next.last_seen_age_sec < existing.last_seen_age_sec;
88
- return String(next.subscribed_at || "") > String(existing.subscribed_at || "");
89
- }
90
-
91
- function channelListWithSubscribers(db, options = {}) {
92
- const activeWindowSec = Math.max(30, Number(options.active_window_sec || process.env.MNEMO_CHANNEL_ACTIVE_SEC || 300));
93
- const includeSubscribers = options.include_subscribers !== false;
94
- markStaleAgentsOffline(db, activeWindowSec);
95
- const rows = db.prepare(
96
- "SELECT c.name, c.description, c.created_at, " +
97
- "(SELECT COUNT(DISTINCT lower(s.agent_name)) FROM channel_subscription s WHERE s.channel_name = c.name) AS subscribers " +
98
- "FROM channel c ORDER BY c.created_at ASC"
99
- ).all();
100
- if (!includeSubscribers) return { ok: true, count: rows.length, channels: rows, active_window_sec: activeWindowSec };
101
- const subStmt = db.prepare(
102
- "SELECT s.agent_name, s.subscribed_at, r.agent_name AS registry_agent_name, r.display_name, r.host, r.pid, r.status, r.last_seen_at, r.skills_json, r.meta_json, " +
103
- "(SELECT COUNT(*) FROM agent_brief b WHERE b.channel=s.channel_name AND lower(b.agent_name)=lower(s.agent_name) AND b.status='pending') AS pending_briefs, " +
104
- "(SELECT COUNT(*) FROM agent_brief b WHERE b.channel=s.channel_name AND lower(b.agent_name)=lower(s.agent_name) AND b.status='dispatched') AS dispatched_briefs " +
105
- "FROM channel_subscription s " +
106
- "LEFT JOIN agent_registry r ON lower(r.agent_name)=lower(s.agent_name) " +
107
- "WHERE s.channel_name=? ORDER BY s.agent_name ASC"
108
- );
109
- const channels = rows.map((row) => {
110
- const rawDetails = subStmt.all(row.name).map((sub) => {
111
- const age = agentAgeSec(sub.last_seen_at);
112
- const status = sub.status || "unregistered";
113
- const active = ["online", "busy", "idle"].includes(String(status).toLowerCase()) && age != null && age <= activeWindowSec;
114
- return {
115
- agent_name: sub.registry_agent_name || sub.agent_name,
116
- display_name: sub.display_name || sub.registry_agent_name || sub.agent_name,
117
- subscribed_at: sub.subscribed_at,
118
- status,
119
- active,
120
- last_seen_at: sub.last_seen_at || null,
121
- last_seen_age_sec: age,
122
- host: sub.host || null,
123
- pid: sub.pid || null,
124
- skills: parseMaybeJson(sub.skills_json, []) || [],
125
- meta: parseMaybeJson(sub.meta_json, null),
126
- pending_briefs: sub.pending_briefs || 0,
127
- dispatched_briefs: sub.dispatched_briefs || 0
128
- };
129
- });
130
- const unique = new Map();
131
- for (const detail of rawDetails) {
132
- const key = normalizeAgentName(detail.agent_name);
133
- const existing = unique.get(key);
134
- if (shouldReplaceSubscriber(existing, detail)) unique.set(key, detail);
135
- }
136
- const details = Array.from(unique.values()).sort((a, b) => normalizeAgentName(a.agent_name).localeCompare(normalizeAgentName(b.agent_name)));
137
- return Object.assign({}, row, {
138
- subscribers: details.length,
139
- active_subscribers: details.filter((sub) => sub.active).length,
140
- offline_subscribers: details.filter((sub) => !sub.active).length,
141
- subscribers_detail: details
142
- });
143
- });
144
- return { ok: true, count: channels.length, channels, active_window_sec: activeWindowSec };
145
- }
146
-
147
- function pushCandidate(candidates, id, source) {
148
- const value = parseInt(id, 10);
149
- if (Number.isFinite(value) && value > 0) candidates.push({ id: value, source });
150
- }
151
-
152
- function collectTaskIdsFromText(text, source, candidates) {
153
- const patterns = [
154
- /(?:Autonomy task|Blocked autonomy review)\s*#\s*(\d+)/gi,
155
- /\bautonomy[_\s-]*task[_\s-]*id["':=\s]+(\d+)/gi,
156
- /\bblocked[_\s-]*autonomy[_\s-]*task[_\s-]*id["':=\s]+(\d+)/gi,
157
- /\btask[_\s-]*id["':=\s]+(\d+)/gi
158
- ];
159
- for (const re of patterns) {
160
- let match;
161
- while ((match = re.exec(String(text || "")))) pushCandidate(candidates, match[1], source);
162
- }
163
- }
164
-
165
- function collectTaskIdsFromMeta(meta, source, candidates) {
166
- const keys = ["autonomy_task_id", "blocked_autonomy_task_id", "task_id", "source_task_id", "autonomyTaskId", "blockedAutonomyTaskId"];
167
- for (const key of keys) pushCandidate(candidates, meta && meta[key], source + "." + key);
168
- }
169
-
170
- function resolveAutonomyTaskUpdateId(db, inputId) {
171
- const raw = parseInt(inputId, 10);
172
- if (!Number.isFinite(raw)) return { id: inputId, error: "invalid id" };
173
- const direct = db.prepare("SELECT id FROM autonomy_task WHERE id=?").get(raw);
174
- if (direct) return { id: raw, resolved_from: "autonomy_task.id" };
175
-
176
- const candidates = [];
177
- let inputKind = "unknown";
178
- try {
179
- const brief = db.prepare("SELECT id, content, meta_json FROM agent_brief WHERE id=?").get(raw);
180
- if (brief) {
181
- inputKind = "agent_brief";
182
- const meta = parseMaybeJson(brief.meta_json, {}) || {};
183
- collectTaskIdsFromMeta(meta, "agent_brief.meta", candidates);
184
- collectTaskIdsFromText(brief.content, "agent_brief.content", candidates);
185
- }
186
- } catch {}
187
- try {
188
- const reverseRows = db.prepare(
189
- "SELECT id FROM autonomy_task WHERE source_id=? " +
190
- "OR meta_json LIKE ? OR meta_json LIKE ? OR meta_json LIKE ? OR checklist_json LIKE ? OR checklist_json LIKE ? LIMIT 20"
191
- ).all(
192
- String(raw),
193
- '%"brief_id":' + raw + '%',
194
- '%"agent_brief_id":' + raw + '%',
195
- '%"source_brief_id":' + raw + '%',
196
- '%"brief_id":' + raw + '%',
197
- '%"agent_brief_id":' + raw + '%'
198
- );
199
- for (const row of reverseRows) pushCandidate(candidates, row.id, "autonomy_task.reverse_link");
200
- } catch {}
201
- try {
202
- const mem = db.prepare("SELECT id, text, meta_json FROM memory WHERE id=?").get(raw);
203
- if (mem) {
204
- if (inputKind === "unknown") inputKind = "memory";
205
- const meta = parseMaybeJson(mem.meta_json, {}) || {};
206
- collectTaskIdsFromMeta(meta, "memory.meta", candidates);
207
- collectTaskIdsFromText(mem.text, "memory.text", candidates);
208
- }
209
- } catch {}
210
-
211
- const seen = new Set();
212
- for (const candidate of candidates) {
213
- if (!candidate || seen.has(candidate.id)) continue;
214
- seen.add(candidate.id);
215
- const row = db.prepare("SELECT id FROM autonomy_task WHERE id=?").get(candidate.id);
216
- if (row) return { id: candidate.id, resolved_from: candidate.source, input_id: raw, input_kind: inputKind, candidates };
217
- }
218
- return { id: raw, error: "task not found", input_kind: inputKind, candidates };
219
- }
220
-
221
- module.exports = {
222
- markStaleAgentsOffline,
223
- requeueStaleDispatchedBriefs,
224
- channelListWithSubscribers,
225
- resolveAutonomyTaskUpdateId
226
- };