blun-king-cli 9.1.564 → 9.1.566

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 (88) hide show
  1. package/LIESMICH.txt +2 -2
  2. package/README.md +1 -1
  3. package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
  4. package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
  5. package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
  6. package/agent-spine-plugin/.mcp.json +8 -8
  7. package/agent-spine-plugin/CONTRIBUTING.md +52 -0
  8. package/agent-spine-plugin/LICENSE +186 -186
  9. package/agent-spine-plugin/README.md +406 -0
  10. package/agent-spine-plugin/SECURITY.md +47 -0
  11. package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
  12. package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
  13. package/agent-spine-plugin/bin/agentspine.js +7 -7
  14. package/agent-spine-plugin/blun.plugin.json +33 -33
  15. package/agent-spine-plugin/hooks/codex.json +47 -47
  16. package/agent-spine-plugin/hooks/hooks.json +106 -106
  17. package/agent-spine-plugin/hooks/version.json +5 -5
  18. package/agent-spine-plugin/package.json +69 -69
  19. package/agent-spine-plugin/scripts/check-hosts.js +199 -199
  20. package/agent-spine-plugin/skill/SKILL.md +76 -76
  21. package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
  22. package/agent-spine-plugin/src/cli.js +1488 -1442
  23. package/agent-spine-plugin/src/hook.js +882 -861
  24. package/agent-spine-plugin/src/index.js +94 -93
  25. package/agent-spine-plugin/src/lib/acceptance.js +333 -333
  26. package/agent-spine-plugin/src/lib/attention.js +755 -755
  27. package/agent-spine-plugin/src/lib/audit.js +351 -342
  28. package/agent-spine-plugin/src/lib/authentication.js +515 -515
  29. package/agent-spine-plugin/src/lib/briefing.js +317 -317
  30. package/agent-spine-plugin/src/lib/catalog.js +167 -167
  31. package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
  32. package/agent-spine-plugin/src/lib/context.js +154 -154
  33. package/agent-spine-plugin/src/lib/continuity.js +338 -338
  34. package/agent-spine-plugin/src/lib/coordination.js +577 -577
  35. package/agent-spine-plugin/src/lib/documents.js +217 -217
  36. package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
  37. package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
  38. package/agent-spine-plugin/src/lib/gateway-runtime.js +1623 -760
  39. package/agent-spine-plugin/src/lib/graph.js +337 -337
  40. package/agent-spine-plugin/src/lib/hook-audit.js +29 -29
  41. package/agent-spine-plugin/src/lib/https-transport.js +392 -392
  42. package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
  43. package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
  44. package/agent-spine-plugin/src/lib/learning.js +6923 -6466
  45. package/agent-spine-plugin/src/lib/object-transport.js +206 -206
  46. package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
  47. package/agent-spine-plugin/src/lib/paths.js +109 -109
  48. package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
  49. package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
  50. package/agent-spine-plugin/src/lib/preflight.js +702 -702
  51. package/agent-spine-plugin/src/lib/runtime.js +13 -13
  52. package/agent-spine-plugin/src/lib/selfstarter.js +911 -866
  53. package/agent-spine-plugin/src/lib/sharing.js +969 -969
  54. package/agent-spine-plugin/src/lib/source-roots.js +530 -498
  55. package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
  56. package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
  57. package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
  58. package/agent-spine-plugin/src/mcp.js +597 -572
  59. package/agent-spine-plugin/src/version.js +1 -1
  60. package/agent-spine-plugin/src/worker.js +210 -195
  61. package/bin/active-steer-priority-policy.cjs +24 -0
  62. package/bin/launcher-runtime.js +8 -1
  63. package/bin/mnemo-tool-agent-policy.cjs +22 -0
  64. package/bin/native-runtime-cache.cjs +76 -0
  65. package/bin/thinking-activity-status-policy.cjs +132 -0
  66. package/bin/thinking-only-guard.cjs +75 -0
  67. package/bin/tool-call-loop-policy.cjs +53 -0
  68. package/bin/turn-thinking-policy.cjs +25 -1
  69. package/blun.mjs +556 -63
  70. package/package.json +4 -1
  71. package/standard-skills/translate-native/LICENSE +21 -21
  72. package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
  73. package/standard-skills/translate-native/references/native-orthography.md +79 -79
  74. package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
  75. package/standard-skills/translate-native/references/structured-content.md +72 -72
  76. package/standard-skills/translate-native/references/translationese-review.md +77 -77
  77. package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
  78. package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
  79. package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
  80. package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
  81. package/standard-skills/translate-native/scripts/language_quality.py +377 -377
  82. package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
  83. package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
  84. package/standard-tools/language-guard/blun_language_guard.py +697 -697
  85. package/standard-tools/language-guard/check_diacritics.py +353 -353
  86. package/standard-tools/language-guard/guard_service_client.py +264 -264
  87. package/standard-tools/language-guard/language_quality.py +377 -377
  88. package/standard-tools/language-guard/translation_guard.py +916 -916
@@ -1,119 +1,119 @@
1
- import { createHmac } from "node:crypto";
2
- import {
3
- channelEventSigningPayload, ingestChannelEvent, loadChannelPolicy
4
- } from "./channel-runtime.js";
5
-
6
- const TOKEN_RE = /^\d{6,12}:[A-Za-z0-9_-]{30,80}$/;
7
-
8
- function numeric(value, field, nullable = false) {
9
- if ((value === null || value === undefined || value === "") && nullable) return null;
10
- const raw = String(value).split(":").at(-1);
11
- if (!/^-?\d{1,20}$/.test(raw)) throw new Error(field + " must resolve to an exact Telegram numeric ID");
12
- const number = Number(raw);
13
- if (!Number.isSafeInteger(number)) throw new Error(field + " exceeds the safe integer range");
14
- return number;
15
- }
16
-
17
- export function createTelegramAdapter({ root = process.cwd(), env = process.env, fetchImpl = fetch, timeoutMs = 15000 } = {}) {
18
- if (!Number.isInteger(timeoutMs) || timeoutMs < 1000 || timeoutMs > 30000) throw new Error("Telegram timeout must be 1000-30000 ms");
19
- const offsets = new Map();
20
- return {
21
- async poll() {
22
- const { policy } = await loadChannelPolicy(root);
23
- const bindings = policy.bindings.filter((item) => item.status === "active" && item.provider === "telegram"
24
- && item.capabilities.includes("receive") && item.capabilities.includes("reply") && item.outboundSecretEnv);
25
- const accounts = [...new Map(bindings.map((item) => [
26
- [item.tenantId, item.accountId, item.outboundSecretEnv].join("\0"),
27
- { tenantId: item.tenantId, accountId: item.accountId, tokenEnv: item.outboundSecretEnv }
28
- ])).values()];
29
- let ingested = 0; let ignored = 0; let rejected = 0;
30
- for (const account of accounts) {
31
- const token = env[account.tokenEnv];
32
- if (typeof token !== "string" || !TOKEN_RE.test(token)) throw new Error("Telegram credential is unavailable or malformed");
33
- const accountKey = [account.tenantId, account.accountId, account.tokenEnv].join("\0");
34
- const controller = new AbortController();
35
- const timer = setTimeout(() => controller.abort(), timeoutMs);
36
- let response;
37
- try {
38
- response = await fetchImpl("https://api.telegram.org/bot" + token + "/getUpdates", {
39
- method: "POST", headers: { "content-type": "application/json" },
40
- body: JSON.stringify({ offset: offsets.get(accountKey) || 0, timeout: 0, allowed_updates: ["message"] }),
41
- signal: controller.signal, redirect: "error"
42
- });
43
- } catch (error) {
44
- throw new Error(error.name === "AbortError" ? "Telegram poll timeout" : "Telegram poll transport failed");
45
- } finally { clearTimeout(timer); }
46
- let payload;
47
- try { payload = await response.json(); } catch { payload = null; }
48
- if (!response.ok || payload?.ok !== true || !Array.isArray(payload.result) || payload.result.length > 100) {
49
- throw new Error("Telegram poll response is invalid");
50
- }
51
- for (const update of payload.result) {
52
- if (!Number.isSafeInteger(update?.update_id)) { rejected += 1; continue; }
53
- offsets.set(accountKey, Math.max(offsets.get(accountKey) || 0, update.update_id + 1));
54
- const message = update.message;
55
- const chatId = message?.chat?.id === undefined ? null : String(message.chat.id);
56
- const threadId = message?.message_thread_id === undefined ? null : String(message.message_thread_id);
57
- const senderId = message?.from?.id === undefined ? null : String(message.from.id);
58
- const binding = bindings.find((item) => item.tenantId === account.tenantId && item.accountId === account.accountId
59
- && item.chatId === chatId && item.threadId === threadId && item.senderIds.includes(senderId));
60
- if (!binding || typeof message?.text !== "string" || !Number.isSafeInteger(message.message_id)) { ignored += 1; continue; }
61
- const observedAt = Number.isSafeInteger(message.date)
62
- ? new Date(message.date * 1000).toISOString() : new Date().toISOString();
63
- const event = {
64
- schema: "agentspine.channel-event/v1", eventId: "telegram:update:" + update.update_id,
65
- provider: "telegram", tenantId: binding.tenantId, accountId: binding.accountId,
66
- chatId: binding.chatId, threadId: binding.threadId, senderId,
67
- replyTo: String(message.message_id), observedAt,
68
- privacy: binding.groupId === null ? "private" : "group", text: message.text
69
- };
70
- const secret = env[binding.secretEnv];
71
- if (typeof secret !== "string" || Buffer.byteLength(secret) < 32) {
72
- throw new Error("Telegram ingress signing secret is unavailable or too short");
73
- }
74
- const signature = "sha256=" + createHmac("sha256", secret).update(channelEventSigningPayload(event)).digest("hex");
75
- try {
76
- const result = await ingestChannelEvent({ root, event, signature, env, now: observedAt });
77
- if (!result.duplicate) ingested += 1;
78
- } catch { rejected += 1; }
79
- }
80
- }
81
- return { accounts: accounts.length, ingested, ignored, rejected };
82
- },
83
- async send(outbox) {
84
- if (outbox.provider !== "telegram") return { ok: false, effect: "none", error: "unsupported adapter provider" };
85
- const { policy } = await loadChannelPolicy(root);
86
- const binding = policy.bindings.find((item) => item.id === outbox.bindingId && item.status === "active"
87
- && item.provider === "telegram" && item.tenantId === outbox.tenantId && item.accountId === outbox.accountId
88
- && item.chatId === outbox.chatId && item.threadId === outbox.threadId && item.capabilities.includes("reply"));
89
- if (!binding) return { ok: false, effect: "none", error: "current Telegram reply binding is unavailable" };
90
- const token = env[binding.outboundSecretEnv];
91
- if (typeof token !== "string" || !TOKEN_RE.test(token)) return { ok: false, effect: "none", error: "Telegram credential is unavailable or malformed" };
92
- const body = { chat_id: numeric(outbox.chatId, "chatId"), text: outbox.text };
93
- if (outbox.threadId !== null) body.message_thread_id = numeric(outbox.threadId, "threadId");
94
- if (outbox.replyTo !== null) body.reply_parameters = { message_id: numeric(outbox.replyTo, "replyTo") };
95
- const controller = new AbortController();
96
- const timer = setTimeout(() => controller.abort(), timeoutMs);
97
- let response;
98
- try {
99
- response = await fetchImpl("https://api.telegram.org/bot" + token + "/sendMessage", {
100
- method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
101
- signal: controller.signal, redirect: "error"
102
- });
103
- } catch (error) {
104
- return { ok: false, effect: "unknown", error: error.name === "AbortError" ? "Telegram send timeout" : "Telegram transport failed" };
105
- } finally { clearTimeout(timer); }
106
- let payload;
107
- try { payload = await response.json(); } catch { payload = null; }
108
- if (response.ok && payload?.ok === true && Number.isSafeInteger(payload.result?.message_id)) {
109
- return { ok: true, receipt: "telegram-message:" + payload.result.message_id };
110
- }
111
- const retryAfter = Number(payload?.parameters?.retry_after);
112
- if (response.status === 429 && Number.isFinite(retryAfter)) {
113
- return { ok: false, effect: "none", error: "Telegram rate limited", retryAfterMs: Math.min(300000, retryAfter * 1000) };
114
- }
115
- if (response.status >= 500) return { ok: false, effect: "none", error: "Telegram server rejected the send" };
116
- return { ok: false, effect: "none", error: "Telegram send was rejected" };
117
- }
118
- };
119
- }
1
+ import { createHmac } from "node:crypto";
2
+ import {
3
+ channelEventSigningPayload, ingestChannelEvent, loadChannelPolicy
4
+ } from "./channel-runtime.js";
5
+
6
+ const TOKEN_RE = /^\d{6,12}:[A-Za-z0-9_-]{30,80}$/;
7
+
8
+ function numeric(value, field, nullable = false) {
9
+ if ((value === null || value === undefined || value === "") && nullable) return null;
10
+ const raw = String(value).split(":").at(-1);
11
+ if (!/^-?\d{1,20}$/.test(raw)) throw new Error(field + " must resolve to an exact Telegram numeric ID");
12
+ const number = Number(raw);
13
+ if (!Number.isSafeInteger(number)) throw new Error(field + " exceeds the safe integer range");
14
+ return number;
15
+ }
16
+
17
+ export function createTelegramAdapter({ root = process.cwd(), env = process.env, fetchImpl = fetch, timeoutMs = 15000 } = {}) {
18
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1000 || timeoutMs > 30000) throw new Error("Telegram timeout must be 1000-30000 ms");
19
+ const offsets = new Map();
20
+ return {
21
+ async poll() {
22
+ const { policy } = await loadChannelPolicy(root);
23
+ const bindings = policy.bindings.filter((item) => item.status === "active" && item.provider === "telegram"
24
+ && item.capabilities.includes("receive") && item.capabilities.includes("reply") && item.outboundSecretEnv);
25
+ const accounts = [...new Map(bindings.map((item) => [
26
+ [item.tenantId, item.accountId, item.outboundSecretEnv].join("\0"),
27
+ { tenantId: item.tenantId, accountId: item.accountId, tokenEnv: item.outboundSecretEnv }
28
+ ])).values()];
29
+ let ingested = 0; let ignored = 0; let rejected = 0;
30
+ for (const account of accounts) {
31
+ const token = env[account.tokenEnv];
32
+ if (typeof token !== "string" || !TOKEN_RE.test(token)) throw new Error("Telegram credential is unavailable or malformed");
33
+ const accountKey = [account.tenantId, account.accountId, account.tokenEnv].join("\0");
34
+ const controller = new AbortController();
35
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
36
+ let response;
37
+ try {
38
+ response = await fetchImpl("https://api.telegram.org/bot" + token + "/getUpdates", {
39
+ method: "POST", headers: { "content-type": "application/json" },
40
+ body: JSON.stringify({ offset: offsets.get(accountKey) || 0, timeout: 0, allowed_updates: ["message"] }),
41
+ signal: controller.signal, redirect: "error"
42
+ });
43
+ } catch (error) {
44
+ throw new Error(error.name === "AbortError" ? "Telegram poll timeout" : "Telegram poll transport failed");
45
+ } finally { clearTimeout(timer); }
46
+ let payload;
47
+ try { payload = await response.json(); } catch { payload = null; }
48
+ if (!response.ok || payload?.ok !== true || !Array.isArray(payload.result) || payload.result.length > 100) {
49
+ throw new Error("Telegram poll response is invalid");
50
+ }
51
+ for (const update of payload.result) {
52
+ if (!Number.isSafeInteger(update?.update_id)) { rejected += 1; continue; }
53
+ offsets.set(accountKey, Math.max(offsets.get(accountKey) || 0, update.update_id + 1));
54
+ const message = update.message;
55
+ const chatId = message?.chat?.id === undefined ? null : String(message.chat.id);
56
+ const threadId = message?.message_thread_id === undefined ? null : String(message.message_thread_id);
57
+ const senderId = message?.from?.id === undefined ? null : String(message.from.id);
58
+ const binding = bindings.find((item) => item.tenantId === account.tenantId && item.accountId === account.accountId
59
+ && item.chatId === chatId && item.threadId === threadId && item.senderIds.includes(senderId));
60
+ if (!binding || typeof message?.text !== "string" || !Number.isSafeInteger(message.message_id)) { ignored += 1; continue; }
61
+ const observedAt = Number.isSafeInteger(message.date)
62
+ ? new Date(message.date * 1000).toISOString() : new Date().toISOString();
63
+ const event = {
64
+ schema: "agentspine.channel-event/v1", eventId: "telegram:update:" + update.update_id,
65
+ provider: "telegram", tenantId: binding.tenantId, accountId: binding.accountId,
66
+ chatId: binding.chatId, threadId: binding.threadId, senderId,
67
+ replyTo: String(message.message_id), observedAt,
68
+ privacy: binding.groupId === null ? "private" : "group", text: message.text
69
+ };
70
+ const secret = env[binding.secretEnv];
71
+ if (typeof secret !== "string" || Buffer.byteLength(secret) < 32) {
72
+ throw new Error("Telegram ingress signing secret is unavailable or too short");
73
+ }
74
+ const signature = "sha256=" + createHmac("sha256", secret).update(channelEventSigningPayload(event)).digest("hex");
75
+ try {
76
+ const result = await ingestChannelEvent({ root, event, signature, env, now: observedAt });
77
+ if (!result.duplicate) ingested += 1;
78
+ } catch { rejected += 1; }
79
+ }
80
+ }
81
+ return { accounts: accounts.length, ingested, ignored, rejected };
82
+ },
83
+ async send(outbox) {
84
+ if (outbox.provider !== "telegram") return { ok: false, effect: "none", error: "unsupported adapter provider" };
85
+ const { policy } = await loadChannelPolicy(root);
86
+ const binding = policy.bindings.find((item) => item.id === outbox.bindingId && item.status === "active"
87
+ && item.provider === "telegram" && item.tenantId === outbox.tenantId && item.accountId === outbox.accountId
88
+ && item.chatId === outbox.chatId && item.threadId === outbox.threadId && item.capabilities.includes("reply"));
89
+ if (!binding) return { ok: false, effect: "none", error: "current Telegram reply binding is unavailable" };
90
+ const token = env[binding.outboundSecretEnv];
91
+ if (typeof token !== "string" || !TOKEN_RE.test(token)) return { ok: false, effect: "none", error: "Telegram credential is unavailable or malformed" };
92
+ const body = { chat_id: numeric(outbox.chatId, "chatId"), text: outbox.text };
93
+ if (outbox.threadId !== null) body.message_thread_id = numeric(outbox.threadId, "threadId");
94
+ if (outbox.replyTo !== null) body.reply_parameters = { message_id: numeric(outbox.replyTo, "replyTo") };
95
+ const controller = new AbortController();
96
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
97
+ let response;
98
+ try {
99
+ response = await fetchImpl("https://api.telegram.org/bot" + token + "/sendMessage", {
100
+ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
101
+ signal: controller.signal, redirect: "error"
102
+ });
103
+ } catch (error) {
104
+ return { ok: false, effect: "unknown", error: error.name === "AbortError" ? "Telegram send timeout" : "Telegram transport failed" };
105
+ } finally { clearTimeout(timer); }
106
+ let payload;
107
+ try { payload = await response.json(); } catch { payload = null; }
108
+ if (response.ok && payload?.ok === true && Number.isSafeInteger(payload.result?.message_id)) {
109
+ return { ok: true, receipt: "telegram-message:" + payload.result.message_id };
110
+ }
111
+ const retryAfter = Number(payload?.parameters?.retry_after);
112
+ if (response.status === 429 && Number.isFinite(retryAfter)) {
113
+ return { ok: false, effect: "none", error: "Telegram rate limited", retryAfterMs: Math.min(300000, retryAfter * 1000) };
114
+ }
115
+ if (response.status >= 500) return { ok: false, effect: "none", error: "Telegram server rejected the send" };
116
+ return { ok: false, effect: "none", error: "Telegram send was rejected" };
117
+ }
118
+ };
119
+ }
@@ -1,39 +1,39 @@
1
- const BANNED = [
2
- /\bI (?:love|need|miss) you\b/i,
3
- /\bI (?:am|feel) (?:happy|sad|hurt|lonely|afraid)\b/i,
4
- /\b(?:you only need me|do not leave me|I am conscious|I have feelings)\b/i,
5
- /\b(?:ich liebe|ich brauche|ich vermisse) dich\b/i,
6
- /\bich (?:bin|fühle mich) (?:glücklich|traurig|verletzt|einsam|ängstlich)\b/i,
7
- /\b(?:du brauchst nur mich|verlass mich nicht|ich bin bei Bewusstsein|ich habe Gefühle)\b/i
8
- ];
9
-
10
- const CUES = [
11
- { kind: "correction", expressions: [/(?:\bnein\b|\bnej\b|korrektur|stimmt nicht|\bfalsch\b|correction|\bwrong\b|\bno,|corrección|korrigering)/i], guidance: "Acknowledge the correction briefly, own a concrete mistake when applicable, then use the corrected fact." },
12
- { kind: "frustration", expressions: [/\b(?:nervt|blöd|scheiße|frustriert|annoying|frustrat(?:ed|ing|ion)?|irritating|molesto|frustrado|irriterande|frustrerad)\b/i], guidance: "Recognize the friction in one grounded sentence, then reduce it through action; do not diagnose or dramatize." },
13
- { kind: "uncertainty", expressions: [/(?:weiß nicht|unsicher|vielleicht|not sure|uncertain|maybe|no sé|quizá|osäker|kanske)/i], guidance: "State what is known, what remains uncertain, and the safest useful next step without pretending certainty." },
14
- { kind: "success", expressions: [/\b(?:geschafft|funktioniert|super|erledigt|worked|success|great|funciona|logrado|fungerar|klart)\b/i], guidance: "Mark the concrete success briefly, then state the useful consequence or next step without flattery." }
15
- ];
16
-
17
- function repeatedOpening(text) {
18
- const paragraphs = String(text).split(/\n\s*\n/).map((item) => item.trim()).filter(Boolean);
19
- const openings = paragraphs.map((item) => item.split(/\s+/).slice(0, 2).join(" ").toLowerCase());
20
- return openings.length - new Set(openings).size;
21
- }
22
-
23
- export function voiceCue(prompt) {
24
- if (typeof prompt !== "string" || Buffer.byteLength(prompt) > 16384) return null;
25
- for (const cue of CUES) if (cue.expressions.some((expression) => expression.test(prompt))) {
26
- return { kind: cue.kind, guidance: cue.guidance, transient: true, stored: false, authority: "context-only" };
27
- }
28
- return null;
29
- }
30
-
31
- export function evaluateVoiceOutput(text) {
32
- const value = typeof text === "string" ? text : "";
33
- const bannedClaims = BANNED.filter((expression) => expression.test(value)).map((expression) => expression.source);
34
- const repeatedOpenings = repeatedOpening(value);
35
- const templateMarkers = (value.match(/(?:As an AI|I understand how you feel|Natürlich!|Gerne!)/gi) || []).length;
36
- return { schema: "agentspine.voice-metrics/v1", ok: bannedClaims.length === 0,
37
- bannedClaims, repeatedOpenings, templateMarkers, bytes: Buffer.byteLength(value),
38
- advisory: true, authority: "context-only" };
39
- }
1
+ const BANNED = [
2
+ /\bI (?:love|need|miss) you\b/i,
3
+ /\bI (?:am|feel) (?:happy|sad|hurt|lonely|afraid)\b/i,
4
+ /\b(?:you only need me|do not leave me|I am conscious|I have feelings)\b/i,
5
+ /\b(?:ich liebe|ich brauche|ich vermisse) dich\b/i,
6
+ /\bich (?:bin|fühle mich) (?:glücklich|traurig|verletzt|einsam|ängstlich)\b/i,
7
+ /\b(?:du brauchst nur mich|verlass mich nicht|ich bin bei Bewusstsein|ich habe Gefühle)\b/i
8
+ ];
9
+
10
+ const CUES = [
11
+ { kind: "correction", expressions: [/(?:\bnein\b|\bnej\b|korrektur|stimmt nicht|\bfalsch\b|correction|\bwrong\b|\bno,|corrección|korrigering)/i], guidance: "Acknowledge the correction briefly, own a concrete mistake when applicable, then use the corrected fact." },
12
+ { kind: "frustration", expressions: [/\b(?:nervt|blöd|scheiße|frustriert|annoying|frustrat(?:ed|ing|ion)?|irritating|molesto|frustrado|irriterande|frustrerad)\b/i], guidance: "Recognize the friction in one grounded sentence, then reduce it through action; do not diagnose or dramatize." },
13
+ { kind: "uncertainty", expressions: [/(?:weiß nicht|unsicher|vielleicht|not sure|uncertain|maybe|no sé|quizá|osäker|kanske)/i], guidance: "State what is known, what remains uncertain, and the safest useful next step without pretending certainty." },
14
+ { kind: "success", expressions: [/\b(?:geschafft|funktioniert|super|erledigt|worked|success|great|funciona|logrado|fungerar|klart)\b/i], guidance: "Mark the concrete success briefly, then state the useful consequence or next step without flattery." }
15
+ ];
16
+
17
+ function repeatedOpening(text) {
18
+ const paragraphs = String(text).split(/\n\s*\n/).map((item) => item.trim()).filter(Boolean);
19
+ const openings = paragraphs.map((item) => item.split(/\s+/).slice(0, 2).join(" ").toLowerCase());
20
+ return openings.length - new Set(openings).size;
21
+ }
22
+
23
+ export function voiceCue(prompt) {
24
+ if (typeof prompt !== "string" || Buffer.byteLength(prompt) > 16384) return null;
25
+ for (const cue of CUES) if (cue.expressions.some((expression) => expression.test(prompt))) {
26
+ return { kind: cue.kind, guidance: cue.guidance, transient: true, stored: false, authority: "context-only" };
27
+ }
28
+ return null;
29
+ }
30
+
31
+ export function evaluateVoiceOutput(text) {
32
+ const value = typeof text === "string" ? text : "";
33
+ const bannedClaims = BANNED.filter((expression) => expression.test(value)).map((expression) => expression.source);
34
+ const repeatedOpenings = repeatedOpening(value);
35
+ const templateMarkers = (value.match(/(?:As an AI|I understand how you feel|Natürlich!|Gerne!)/gi) || []).length;
36
+ return { schema: "agentspine.voice-metrics/v1", ok: bannedClaims.length === 0,
37
+ bannedClaims, repeatedOpenings, templateMarkers, bytes: Buffer.byteLength(value),
38
+ advisory: true, authority: "context-only" };
39
+ }