blun-king-cli 9.1.455 → 9.1.456

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.
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('node:crypto');
4
+ const { extractChecksumClaims } = require('./outbound-claim-provenance.cjs');
4
5
 
5
6
  const PHASES = new Set(['orient', 'plan', 'act', 'verify', 'learn', 'wait']);
6
7
  const EVIDENCE_BASES = new Set([
@@ -237,6 +238,9 @@ function normalizeRuntimeExternalReportSource(input) {
237
238
  throw new TypeError('trusted external report source is invalid');
238
239
  }
239
240
  const reporter = bounded(input.reporter ?? input.reporterId, 'external report reporter', 128);
241
+ const claimTokens = extractChecksumClaims(
242
+ Array.isArray(input.claimTokens) ? input.claimTokens.join(' ') : '',
243
+ );
240
244
  return Object.freeze({
241
245
  source: normalizedExternalReportSource(input.source),
242
246
  chatId: bounded(input.chatId, 'external report chatId', 64),
@@ -244,6 +248,7 @@ function normalizeRuntimeExternalReportSource(input) {
244
248
  reporter,
245
249
  reporterId: bounded(input.reporterId ?? reporter, 'external report reporterId', 128),
246
250
  occurredAt: normalizedTimestamp(input.occurredAt, 'external report occurredAt', true),
251
+ ...(claimTokens.length > 0 ? { claimTokens } : {}),
247
252
  });
248
253
  }
249
254
 
@@ -292,17 +297,8 @@ function normalizeExternalReportOrigin(input) {
292
297
  });
293
298
  }
294
299
 
295
- function resolveExternalReportOrigin(selectorInput, runtimeExternalReports) {
296
- const selector = normalizeExternalReportSelector(selectorInput);
297
- const reports = Array.isArray(runtimeExternalReports)
298
- ? runtimeExternalReports.map(normalizeRuntimeExternalReportSource)
299
- : [];
300
- const report = reports.find((candidate) => candidate.source === selector.source
301
- && candidate.chatId === selector.chatId
302
- && candidate.messageId === selector.messageId);
303
- if (report === undefined) {
304
- throw new TypeError('externalReportSource does not match a trusted external report in the current turn');
305
- }
300
+ function externalReportOriginFromRuntimeSource(input) {
301
+ const report = normalizeRuntimeExternalReportSource(input);
306
302
  const reporterRef = crypto.createHash('sha256')
307
303
  .update(`external-report-reporter:${report.source}\0${report.reporterId}`)
308
304
  .digest('hex')
@@ -320,6 +316,20 @@ function resolveExternalReportOrigin(selectorInput, runtimeExternalReports) {
320
316
  });
321
317
  }
322
318
 
319
+ function resolveExternalReportOrigin(selectorInput, runtimeExternalReports) {
320
+ const selector = normalizeExternalReportSelector(selectorInput);
321
+ const reports = Array.isArray(runtimeExternalReports)
322
+ ? runtimeExternalReports.map(normalizeRuntimeExternalReportSource)
323
+ : [];
324
+ const report = reports.find((candidate) => candidate.source === selector.source
325
+ && candidate.chatId === selector.chatId
326
+ && candidate.messageId === selector.messageId);
327
+ if (report === undefined) {
328
+ throw new TypeError('externalReportSource does not match a trusted external report in the current turn');
329
+ }
330
+ return externalReportOriginFromRuntimeSource(report);
331
+ }
332
+
323
333
  function normalizedEvidenceProducerRef(value, field = 'evidence producer ref') {
324
334
  const ref = String(value ?? '').trim();
325
335
  if (!EVIDENCE_PRODUCER_REF_RE.test(ref)) throw new TypeError(`${field} is invalid`);
@@ -1068,6 +1078,7 @@ module.exports = {
1068
1078
  completionCriterionRef,
1069
1079
  evidenceProducerRef,
1070
1080
  emptyActionEvidenceReceipt,
1081
+ externalReportOriginFromRuntimeSource,
1071
1082
  externalReportSourceFromChannelMeta,
1072
1083
  externalReportSourcesFromOrigin,
1073
1084
  normalizeActionCheckpoint,
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ const MAX_CLAIMS = 32;
4
+ const CHECKSUM_CLAIM_RE = /(?<![A-Za-z0-9])[A-Fa-f0-9]{16,128}(?![A-Za-z0-9])/gu;
5
+ const TELEGRAM_MESSAGE_TOOL_RE = /^mcp__[^\s]*telegram[^\s]*__(?:reply|edit_message)$/iu;
6
+ const REF_RE = /^[a-f0-9]{16}$/u;
7
+ const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/u;
8
+
9
+ function extractChecksumClaims(value) {
10
+ const claims = [];
11
+ const seen = new Set();
12
+ const text = typeof value === 'string' ? value : String(value ?? '');
13
+ for (const match of text.matchAll(CHECKSUM_CLAIM_RE)) {
14
+ const claim = match[0].toLowerCase();
15
+ if (seen.has(claim)) continue;
16
+ seen.add(claim);
17
+ claims.push(claim);
18
+ if (claims.length >= MAX_CLAIMS) break;
19
+ }
20
+ return Object.freeze(claims);
21
+ }
22
+
23
+ function boundedText(value, max) {
24
+ const text = String(value ?? '')
25
+ .replace(/[\u0000-\u001f\u007f]+/gu, ' ')
26
+ .replace(/\s+/gu, ' ')
27
+ .trim();
28
+ return text.length > 0 ? text.slice(0, max) : undefined;
29
+ }
30
+
31
+ function optionalRef(value) {
32
+ const ref = String(value ?? '').trim().toLowerCase();
33
+ return REF_RE.test(ref) ? ref : undefined;
34
+ }
35
+
36
+ function optionalTimestamp(value) {
37
+ const timestamp = String(value ?? '').trim();
38
+ return ISO_TIMESTAMP_RE.test(timestamp) ? timestamp : undefined;
39
+ }
40
+
41
+ function normalizedExternalReports(value) {
42
+ if (!Array.isArray(value)) return [];
43
+ const reports = [];
44
+ for (const report of value.slice(0, MAX_CLAIMS)) {
45
+ if (!report || typeof report !== 'object' || Array.isArray(report)) continue;
46
+ const reporter = boundedText(report.reporter, 128);
47
+ const eventRef = optionalRef(report.eventRef);
48
+ const occurredAt = optionalTimestamp(report.occurredAt);
49
+ const claimTokens = extractChecksumClaims(
50
+ Array.isArray(report.claimTokens) ? report.claimTokens.join(' ') : '',
51
+ );
52
+ if (reporter === undefined || eventRef === undefined || occurredAt === undefined || claimTokens.length === 0) continue;
53
+ const reporterRef = optionalRef(report.reporterRef);
54
+ reports.push(Object.freeze({ reporter, reporterRef, eventRef, occurredAt, claimTokens }));
55
+ }
56
+ return reports;
57
+ }
58
+
59
+ function assessOutboundClaimProvenance({ text, localClaims = [], externalReports = [] } = {}) {
60
+ const claims = extractChecksumClaims(text);
61
+ const local = new Set(extractChecksumClaims(Array.isArray(localClaims) ? localClaims.join(' ') : ''));
62
+ const reports = normalizedExternalReports(externalReports);
63
+ const localMatches = [];
64
+ const externalMatches = [];
65
+ const unattributed = [];
66
+
67
+ for (const value of claims) {
68
+ if (local.has(value)) {
69
+ localMatches.push(value);
70
+ continue;
71
+ }
72
+ const report = reports.find((candidate) => candidate.claimTokens.includes(value));
73
+ if (report === undefined) {
74
+ unattributed.push(value);
75
+ continue;
76
+ }
77
+ const external = {
78
+ value,
79
+ reporter: report.reporter,
80
+ ...(report.reporterRef !== undefined ? { reporterRef: report.reporterRef } : {}),
81
+ eventRef: report.eventRef,
82
+ occurredAt: report.occurredAt,
83
+ };
84
+ externalMatches.push(Object.freeze(external));
85
+ }
86
+
87
+ return Object.freeze({
88
+ local: Object.freeze(localMatches),
89
+ external: Object.freeze(externalMatches),
90
+ unattributed: Object.freeze(unattributed),
91
+ });
92
+ }
93
+
94
+ function normalizedToolArgs(input) {
95
+ if (input && typeof input === 'object' && !Array.isArray(input)) return input;
96
+ if (typeof input !== 'string') return undefined;
97
+ try {
98
+ const parsed = JSON.parse(input);
99
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
100
+ } catch {
101
+ return undefined;
102
+ }
103
+ }
104
+
105
+ function provenanceAdvisoryForToolCall({ toolName, toolArgs, localClaims, externalReports } = {}) {
106
+ if (!TELEGRAM_MESSAGE_TOOL_RE.test(String(toolName ?? ''))) return null;
107
+ const args = normalizedToolArgs(toolArgs);
108
+ if (args === undefined || typeof args.text !== 'string') return null;
109
+ const assessment = assessOutboundClaimProvenance({
110
+ text: args.text,
111
+ localClaims,
112
+ externalReports,
113
+ });
114
+ if (assessment.external.length === 0 && assessment.unattributed.length === 0) return null;
115
+ return Object.freeze({ ...assessment, blocked: false });
116
+ }
117
+
118
+ module.exports = {
119
+ assessOutboundClaimProvenance,
120
+ extractChecksumClaims,
121
+ provenanceAdvisoryForToolCall,
122
+ };
package/blun.mjs CHANGED
@@ -21463,7 +21463,8 @@ var { rollbackPersonalityChoice, setPersonalityEnabledAtomic } = createRequire(i
21463
21463
  var { projectSoulText } = createRequire(import.meta.url)("./bin/soul-organization-policy.cjs");
21464
21464
  var { soulFileInstruction } = createRequire(import.meta.url)("./bin/soul-preservation-policy.cjs");
21465
21465
  var { readProfilePersona, resolveSoulFile } = createRequire(import.meta.url)("./bin/profile-identity-resolution.cjs");
21466
- var { advanceActionEvidenceReceipt, assertActionCheckpointEvidenceBasis, assertActionCheckpointRevision, emptyActionEvidenceReceipt, evidenceProducerRef, externalReportSourcesFromOrigin, normalizeActionCheckpoint, projectActionCheckpoint } = createRequire(import.meta.url)("./bin/cognitive-action-checkpoint.cjs");
21466
+ var { advanceActionEvidenceReceipt, assertActionCheckpointEvidenceBasis, assertActionCheckpointRevision, emptyActionEvidenceReceipt, evidenceProducerRef, externalReportOriginFromRuntimeSource, externalReportSourcesFromOrigin, normalizeActionCheckpoint, projectActionCheckpoint } = createRequire(import.meta.url)("./bin/cognitive-action-checkpoint.cjs");
21467
+ var { extractChecksumClaims, provenanceAdvisoryForToolCall } = createRequire(import.meta.url)("./bin/outbound-claim-provenance.cjs");
21467
21468
  var { evaluateGoalCompletionEvidence } = createRequire(import.meta.url)("./bin/goal-completion-evidence-policy.cjs");
21468
21469
  async function prepareSystemPromptContext(kaos, brandHome, options) {
21469
21470
  const additionalDirs = normalizeAdditionalDirs(options?.additionalDirs ?? []);
@@ -261828,6 +261829,7 @@ var init_turn = __esmMin((() => {
261828
261829
  cognitiveToolBatchesByTurn = /* @__PURE__ */ new Map();
261829
261830
  cognitiveActionEvidenceByTurn = /* @__PURE__ */ new Map();
261830
261831
  cognitiveExternalReportsByTurn = /* @__PURE__ */ new Map();
261832
+ cognitiveLocalClaimsByTurn = /* @__PURE__ */ new Map();
261831
261833
  constructor(agent) {
261832
261834
  this.agent = agent;
261833
261835
  }
@@ -261924,6 +261926,53 @@ var init_turn = __esmMin((() => {
261924
261926
  externalReportSourcesForCurrentTurn() {
261925
261927
  return this.cognitiveExternalReportsByTurn.get(this.currentId) ?? Object.freeze([]);
261926
261928
  }
261929
+ recordLocalClaimTokens(turnId, text) {
261930
+ const incoming = extractChecksumClaims(text);
261931
+ if (incoming.length === 0) return;
261932
+ const current = this.cognitiveLocalClaimsByTurn.get(turnId) ?? [];
261933
+ const merged = [...current];
261934
+ const seen = new Set(current);
261935
+ for (const claim of incoming) {
261936
+ if (seen.has(claim)) continue;
261937
+ seen.add(claim);
261938
+ merged.push(claim);
261939
+ }
261940
+ this.cognitiveLocalClaimsByTurn.set(turnId, Object.freeze(merged.slice(-64)));
261941
+ }
261942
+ externalClaimReportsForCurrentTurn() {
261943
+ return this.externalReportSourcesForCurrentTurn().map((source) => ({
261944
+ ...externalReportOriginFromRuntimeSource(source),
261945
+ claimTokens: source.claimTokens ?? []
261946
+ }));
261947
+ }
261948
+ emitOutboundClaimProvenanceAdvisory(turnId, event) {
261949
+ try {
261950
+ const provenanceAdvisory = provenanceAdvisoryForToolCall({
261951
+ toolName: event.name,
261952
+ toolArgs: event.args,
261953
+ localClaims: this.cognitiveLocalClaimsByTurn.get(turnId) ?? [],
261954
+ externalReports: this.externalClaimReportsForCurrentTurn()
261955
+ });
261956
+ if (provenanceAdvisory === null) return;
261957
+ const external = provenanceAdvisory.external.map((entry) => `${entry.value} (${entry.reporter}; ${entry.eventRef}; ${entry.occurredAt})`).join(", ") || "-";
261958
+ const unattributed = provenanceAdvisory.unattributed.join(", ") || "-";
261959
+ this.agent.emitEvent({
261960
+ type: "warning",
261961
+ code: "OUTBOUND_CLAIM_PROVENANCE",
261962
+ message: uiText("sessionEvent.provenance.advisory", { external, unattributed }),
261963
+ blocked: false,
261964
+ details: {
261965
+ blocked: false,
261966
+ external: provenanceAdvisory.external,
261967
+ unattributed: provenanceAdvisory.unattributed
261968
+ }
261969
+ });
261970
+ } catch (error) {
261971
+ this.agent.telemetry.track("outbound_claim_provenance_error", {
261972
+ error_type: error?.code ?? error?.name ?? "Error"
261973
+ });
261974
+ }
261975
+ }
261927
261976
  projectCognitiveState(turnId, input) {
261928
261977
  try {
261929
261978
  const focusScopes = cognitiveFocusScopesForTurn(input);
@@ -262437,6 +262486,7 @@ var init_turn = __esmMin((() => {
262437
262486
  this.interruptedTelemetryTurnIds.delete(turnId);
262438
262487
  this.cognitiveActionEvidenceByTurn.delete(turnId);
262439
262488
  this.cognitiveExternalReportsByTurn.delete(turnId);
262489
+ this.cognitiveLocalClaimsByTurn.delete(turnId);
262440
262490
  this.stepFailureByTurn.delete(turnId);
262441
262491
  await this.agent.records.flush();
262442
262492
  return {
@@ -262827,6 +262877,7 @@ var init_turn = __esmMin((() => {
262827
262877
  if (event.type === "tool.call") {
262828
262878
  const dupType = this.trackDuplicateToolCall(turnId, event.step, event.name, event.args);
262829
262879
  this.toolCallDupType.set(event.toolCallId, dupType === "cross_step" ? "cross_step" : "normal");
262880
+ this.emitOutboundClaimProvenanceAdvisory(turnId, event);
262830
262881
  this.toolCallStartedAt.set(event.toolCallId, {
262831
262882
  name: event.name,
262832
262883
  args: event.args,
@@ -262842,6 +262893,7 @@ var init_turn = __esmMin((() => {
262842
262893
  if (event.type === "tool.result") {
262843
262894
  const started = this.toolCallStartedAt.get(event.toolCallId);
262844
262895
  if (started === void 0) return;
262896
+ this.recordLocalClaimTokens(turnId, toolResultText(event.result));
262845
262897
  rememberDeferredToolAfterNotFound(this.loadedToolNames, started.name, event.result);
262846
262898
  this.toolCallStartedAt.delete(event.toolCallId);
262847
262899
  const dupType = this.toolCallDupType.get(event.toolCallId) ?? "normal";
@@ -419288,7 +419340,8 @@ function injectChannelEnvelope(host, envelope, preamble) {
419288
419340
  messageId: envelope.meta.message_id,
419289
419341
  reporter: envelope.meta.user,
419290
419342
  reporterId: envelope.meta.user_id,
419291
- occurredAt: envelope.meta.ts
419343
+ occurredAt: envelope.meta.ts,
419344
+ claimTokens: extractChecksumClaims(envelope.text)
419292
419345
  };
419293
419346
  if (contextOnly) {
419294
419347
  bufferContext(preamble, envelope.meta.chat_id, envelope.tag, reportSource);
@@ -506413,6 +506466,7 @@ registerUiCatalogFragment({
506413
506466
  "sessionEvent.goalQueue.blockedTitle": "Goal blocked.",
506414
506467
  "sessionEvent.goalQueue.blockedDetail": "The next queued goal will start only after this goal is complete.",
506415
506468
  "sessionEvent.warning": "Warning: {message}",
506469
+ "sessionEvent.provenance.advisory": "Source check before sending; the message was not blocked. Forwarded report: {external}. Not found in this turn's tool output or a source event: {unattributed}. Forwarding is allowed, but do not present these values as your own measurement.",
506416
506470
  "sessionEvent.mcp.tool.one": "{count} tool",
506417
506471
  "sessionEvent.mcp.tool.other": "{count} tools",
506418
506472
  "sessionEvent.mcp.connected": "MCP server \"{name}\" connected · {tools} ({transport})",
@@ -506443,6 +506497,7 @@ registerUiCatalogFragment({
506443
506497
  "sessionEvent.goalQueue.blockedTitle": "Ziel blockiert.",
506444
506498
  "sessionEvent.goalQueue.blockedDetail": "Das nächste vorgemerkte Ziel startet erst, wenn dieses Ziel abgeschlossen ist.",
506445
506499
  "sessionEvent.warning": "Warnung: {message}",
506500
+ "sessionEvent.provenance.advisory": "Herkunftsprüfung vor dem Senden; die Nachricht wurde nicht blockiert. Weitergeleiteter Bericht: {external}. Weder in einer Werkzeugausgabe dieses Durchlaufs noch in einem Quellereignis gefunden: {unattributed}. Die Weitergabe ist erlaubt, diese Werte dürfen jedoch nicht als eigene Messung dargestellt werden.",
506446
506501
  "sessionEvent.mcp.tool.one": "{count} Tool",
506447
506502
  "sessionEvent.mcp.tool.other": "{count} Tools",
506448
506503
  "sessionEvent.mcp.connected": "MCP-Server „{name}“ verbunden · {tools} ({transport})",
@@ -506473,6 +506528,7 @@ registerUiCatalogFragment({
506473
506528
  "sessionEvent.goalQueue.blockedTitle": "Objetivo bloqueado.",
506474
506529
  "sessionEvent.goalQueue.blockedDetail": "El siguiente objetivo en cola no se iniciará hasta que este objetivo se complete.",
506475
506530
  "sessionEvent.warning": "Advertencia: {message}",
506531
+ "sessionEvent.provenance.advisory": "Comprobación de procedencia antes de enviar; el mensaje no se bloqueó. Informe reenviado: {external}. No se encontraron en la salida de las herramientas de este turno ni en un evento de origen: {unattributed}. Se permite reenviar estos valores, pero no presentarlos como mediciones propias.",
506476
506532
  "sessionEvent.mcp.tool.one": "{count} herramienta",
506477
506533
  "sessionEvent.mcp.tool.other": "{count} herramientas",
506478
506534
  "sessionEvent.mcp.connected": "Servidor MCP «{name}» conectado · {tools} ({transport})",
@@ -506503,6 +506559,7 @@ registerUiCatalogFragment({
506503
506559
  "sessionEvent.goalQueue.blockedTitle": "Objectif bloqué.",
506504
506560
  "sessionEvent.goalQueue.blockedDetail": "L’objectif suivant ne démarrera qu’une fois cet objectif terminé.",
506505
506561
  "sessionEvent.warning": "Avertissement : {message}",
506562
+ "sessionEvent.provenance.advisory": "Vérification de la provenance avant l’envoi ; le message n’a pas été bloqué. Rapport transmis : {external}. Valeurs introuvables dans la sortie des outils de ce tour et dans les événements source : {unattributed}. Leur transmission est autorisée, mais ne les présentez pas comme vos propres mesures.",
506506
506563
  "sessionEvent.mcp.tool.one": "{count} outil",
506507
506564
  "sessionEvent.mcp.tool.other": "{count} outils",
506508
506565
  "sessionEvent.mcp.connected": "Serveur MCP « {name} » connecté · {tools} ({transport})",
@@ -506533,6 +506590,7 @@ registerUiCatalogFragment({
506533
506590
  "sessionEvent.goalQueue.blockedTitle": "Målet är blockerat.",
506534
506591
  "sessionEvent.goalQueue.blockedDetail": "Nästa köade mål startar först när det här målet är slutfört.",
506535
506592
  "sessionEvent.warning": "Varning: {message}",
506593
+ "sessionEvent.provenance.advisory": "Ursprungskontroll före sändning; meddelandet blockerades inte. Vidarebefordrad rapport: {external}. Hittades varken i verktygsutdata från den här körningen eller i en källhändelse: {unattributed}. Värdena får vidarebefordras, men framställ dem inte som egna mätningar.",
506536
506594
  "sessionEvent.mcp.tool.one": "{count} verktyg",
506537
506595
  "sessionEvent.mcp.tool.other": "{count} verktyg",
506538
506596
  "sessionEvent.mcp.connected": "MCP-servern ”{name}” är ansluten · {tools} ({transport})",
@@ -506563,6 +506621,7 @@ registerUiCatalogFragment({
506563
506621
  "sessionEvent.goalQueue.blockedTitle": "Cíl je blokován.",
506564
506622
  "sessionEvent.goalQueue.blockedDetail": "Další cíl ve frontě se spustí pouze po dokončení tohoto cíle.",
506565
506623
  "sessionEvent.warning": "Upozornění: {message}",
506624
+ "sessionEvent.provenance.advisory": "Kontrola původu před odesláním; zpráva nebyla zablokována. Předaný report: {external}. Nenalezeno ve výstupu nástrojů tohoto kola ani ve zdrojové události: {unattributed}. Tyto hodnoty lze předat dál, ale neuvádějte je jako vlastní měření.",
506566
506625
  "sessionEvent.mcp.tool.one": "Nástroje: {count}",
506567
506626
  "sessionEvent.mcp.tool.other": "Nástroje: {count}",
506568
506627
  "sessionEvent.mcp.connected": "Server MCP \"{name}\" připojen · {tools} ({transport})",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.455",
3
+ "version": "9.1.456",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {