blun-king-cli 9.1.563 → 9.1.565

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 (85) hide show
  1. package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
  2. package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
  3. package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
  4. package/agent-spine-plugin/.mcp.json +8 -8
  5. package/agent-spine-plugin/CONTRIBUTING.md +52 -0
  6. package/agent-spine-plugin/LICENSE +186 -186
  7. package/agent-spine-plugin/README.md +394 -0
  8. package/agent-spine-plugin/SECURITY.md +47 -0
  9. package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
  10. package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
  11. package/agent-spine-plugin/bin/agentspine.js +7 -7
  12. package/agent-spine-plugin/blun.plugin.json +33 -33
  13. package/agent-spine-plugin/hooks/codex.json +47 -47
  14. package/agent-spine-plugin/hooks/hooks.json +106 -106
  15. package/agent-spine-plugin/hooks/version.json +5 -5
  16. package/agent-spine-plugin/package.json +69 -69
  17. package/agent-spine-plugin/scripts/check-hosts.js +199 -199
  18. package/agent-spine-plugin/skill/SKILL.md +76 -76
  19. package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
  20. package/agent-spine-plugin/src/cli.js +1488 -1442
  21. package/agent-spine-plugin/src/hook.js +886 -812
  22. package/agent-spine-plugin/src/index.js +93 -93
  23. package/agent-spine-plugin/src/lib/acceptance.js +333 -333
  24. package/agent-spine-plugin/src/lib/attention.js +755 -755
  25. package/agent-spine-plugin/src/lib/audit.js +351 -342
  26. package/agent-spine-plugin/src/lib/authentication.js +515 -515
  27. package/agent-spine-plugin/src/lib/briefing.js +317 -317
  28. package/agent-spine-plugin/src/lib/catalog.js +167 -167
  29. package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
  30. package/agent-spine-plugin/src/lib/context.js +154 -154
  31. package/agent-spine-plugin/src/lib/continuity.js +338 -338
  32. package/agent-spine-plugin/src/lib/coordination.js +577 -577
  33. package/agent-spine-plugin/src/lib/documents.js +217 -217
  34. package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
  35. package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
  36. package/agent-spine-plugin/src/lib/gateway-runtime.js +1119 -760
  37. package/agent-spine-plugin/src/lib/graph.js +337 -337
  38. package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
  39. package/agent-spine-plugin/src/lib/https-transport.js +392 -392
  40. package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
  41. package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
  42. package/agent-spine-plugin/src/lib/learning.js +6923 -6466
  43. package/agent-spine-plugin/src/lib/object-transport.js +206 -206
  44. package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
  45. package/agent-spine-plugin/src/lib/paths.js +109 -109
  46. package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
  47. package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
  48. package/agent-spine-plugin/src/lib/preflight.js +702 -702
  49. package/agent-spine-plugin/src/lib/runtime.js +13 -13
  50. package/agent-spine-plugin/src/lib/selfstarter.js +892 -819
  51. package/agent-spine-plugin/src/lib/sharing.js +969 -969
  52. package/agent-spine-plugin/src/lib/source-roots.js +529 -482
  53. package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
  54. package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
  55. package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
  56. package/agent-spine-plugin/src/mcp.js +597 -572
  57. package/agent-spine-plugin/src/version.js +1 -1
  58. package/agent-spine-plugin/src/worker.js +202 -195
  59. package/bin/active-steer-priority-policy.cjs +24 -0
  60. package/bin/launcher-runtime.js +8 -1
  61. package/bin/mnemo-tool-agent-policy.cjs +22 -0
  62. package/bin/thinking-activity-status-policy.cjs +132 -0
  63. package/bin/thinking-only-guard.cjs +75 -0
  64. package/bin/tool-call-loop-policy.cjs +53 -0
  65. package/bin/turn-thinking-policy.cjs +25 -1
  66. package/blun.mjs +554 -64
  67. package/package.json +4 -1
  68. package/standard-skills/translate-native/LICENSE +21 -21
  69. package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
  70. package/standard-skills/translate-native/references/native-orthography.md +79 -79
  71. package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
  72. package/standard-skills/translate-native/references/structured-content.md +72 -72
  73. package/standard-skills/translate-native/references/translationese-review.md +77 -77
  74. package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
  75. package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
  76. package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
  77. package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
  78. package/standard-skills/translate-native/scripts/language_quality.py +377 -377
  79. package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
  80. package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
  81. package/standard-tools/language-guard/blun_language_guard.py +697 -697
  82. package/standard-tools/language-guard/check_diacritics.py +353 -353
  83. package/standard-tools/language-guard/guard_service_client.py +264 -264
  84. package/standard-tools/language-guard/language_quality.py +377 -377
  85. 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
+ }