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.
- package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
- package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
- package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
- package/agent-spine-plugin/.mcp.json +8 -8
- package/agent-spine-plugin/CONTRIBUTING.md +52 -0
- package/agent-spine-plugin/LICENSE +186 -186
- package/agent-spine-plugin/README.md +394 -0
- package/agent-spine-plugin/SECURITY.md +47 -0
- package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
- package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
- package/agent-spine-plugin/bin/agentspine.js +7 -7
- package/agent-spine-plugin/blun.plugin.json +33 -33
- package/agent-spine-plugin/hooks/codex.json +47 -47
- package/agent-spine-plugin/hooks/hooks.json +106 -106
- package/agent-spine-plugin/hooks/version.json +5 -5
- package/agent-spine-plugin/package.json +69 -69
- package/agent-spine-plugin/scripts/check-hosts.js +199 -199
- package/agent-spine-plugin/skill/SKILL.md +76 -76
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
- package/agent-spine-plugin/src/cli.js +1488 -1442
- package/agent-spine-plugin/src/hook.js +886 -812
- package/agent-spine-plugin/src/index.js +93 -93
- package/agent-spine-plugin/src/lib/acceptance.js +333 -333
- package/agent-spine-plugin/src/lib/attention.js +755 -755
- package/agent-spine-plugin/src/lib/audit.js +351 -342
- package/agent-spine-plugin/src/lib/authentication.js +515 -515
- package/agent-spine-plugin/src/lib/briefing.js +317 -317
- package/agent-spine-plugin/src/lib/catalog.js +167 -167
- package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
- package/agent-spine-plugin/src/lib/context.js +154 -154
- package/agent-spine-plugin/src/lib/continuity.js +338 -338
- package/agent-spine-plugin/src/lib/coordination.js +577 -577
- package/agent-spine-plugin/src/lib/documents.js +217 -217
- package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
- package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
- package/agent-spine-plugin/src/lib/gateway-runtime.js +1119 -760
- package/agent-spine-plugin/src/lib/graph.js +337 -337
- package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
- package/agent-spine-plugin/src/lib/https-transport.js +392 -392
- package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
- package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
- package/agent-spine-plugin/src/lib/learning.js +6923 -6466
- package/agent-spine-plugin/src/lib/object-transport.js +206 -206
- package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
- package/agent-spine-plugin/src/lib/paths.js +109 -109
- package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
- package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
- package/agent-spine-plugin/src/lib/preflight.js +702 -702
- package/agent-spine-plugin/src/lib/runtime.js +13 -13
- package/agent-spine-plugin/src/lib/selfstarter.js +892 -819
- package/agent-spine-plugin/src/lib/sharing.js +969 -969
- package/agent-spine-plugin/src/lib/source-roots.js +529 -482
- package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
- package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
- package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
- package/agent-spine-plugin/src/mcp.js +597 -572
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +202 -195
- package/bin/active-steer-priority-policy.cjs +24 -0
- package/bin/launcher-runtime.js +8 -1
- package/bin/mnemo-tool-agent-policy.cjs +22 -0
- package/bin/thinking-activity-status-policy.cjs +132 -0
- package/bin/thinking-only-guard.cjs +75 -0
- package/bin/tool-call-loop-policy.cjs +53 -0
- package/bin/turn-thinking-policy.cjs +25 -1
- package/blun.mjs +554 -64
- package/package.json +4 -1
- package/standard-skills/translate-native/LICENSE +21 -21
- package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
- package/standard-skills/translate-native/references/native-orthography.md +79 -79
- package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
- package/standard-skills/translate-native/references/structured-content.md +72 -72
- package/standard-skills/translate-native/references/translationese-review.md +77 -77
- package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
- package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
- package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
- package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
- package/standard-skills/translate-native/scripts/language_quality.py +377 -377
- package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
- package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
- package/standard-tools/language-guard/blun_language_guard.py +697 -697
- package/standard-tools/language-guard/check_diacritics.py +353 -353
- package/standard-tools/language-guard/guard_service_client.py +264 -264
- package/standard-tools/language-guard/language_quality.py +377 -377
- 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
|
+
}
|