omnius 1.0.157 → 1.0.158

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/dist/index.js CHANGED
@@ -572826,6 +572826,7 @@ function newDocument(scope) {
572826
572826
  updatedAt: now,
572827
572827
  messageCount: 0,
572828
572828
  participants: {},
572829
+ preferences: {},
572829
572830
  dominantTone: [],
572830
572831
  responseStyle: [
572831
572832
  "Prefer concise, direct answers unless the user asks for depth.",
@@ -572859,6 +572860,7 @@ function normalizeDocument(scope, parsed) {
572859
572860
  label: scope.label || parsed.scope?.label || fresh.scope.label
572860
572861
  },
572861
572862
  participants: parsed.participants && typeof parsed.participants === "object" ? parsed.participants : {},
572863
+ preferences: parsed.preferences && typeof parsed.preferences === "object" ? parsed.preferences : {},
572862
572864
  dominantTone: Array.isArray(parsed.dominantTone) ? parsed.dominantTone : [],
572863
572865
  responseStyle: Array.isArray(parsed.responseStyle) && parsed.responseStyle.length > 0 ? parsed.responseStyle : fresh.responseStyle,
572864
572866
  relationshipModel: Array.isArray(parsed.relationshipModel) && parsed.relationshipModel.length > 0 ? parsed.relationshipModel : fresh.relationshipModel,
@@ -572878,6 +572880,41 @@ function normalizeDocument(scope, parsed) {
572878
572880
  }
572879
572881
  return doc;
572880
572882
  }
572883
+ function updateScopedPersonalityReplyPreference(scope, preference) {
572884
+ const doc = loadScopedPersonality(scope);
572885
+ const now = new Date(preference.ts ?? Date.now()).toISOString();
572886
+ const existing = doc.preferences?.replyMode;
572887
+ const evidence = preference.evidenceMessageId === void 0 ? existing?.evidenceMessageIds ?? [] : [.../* @__PURE__ */ new Set([...existing?.evidenceMessageIds ?? [], preference.evidenceMessageId])].slice(-12);
572888
+ doc.preferences = {
572889
+ ...doc.preferences ?? {},
572890
+ replyMode: {
572891
+ mode: preference.mode,
572892
+ scope: preference.preferenceScope,
572893
+ updatedAt: now,
572894
+ confidence: Math.max(existing?.confidence ?? 0, Math.max(0, Math.min(1, preference.confidence ?? 0.84))),
572895
+ evidenceMessageIds: evidence,
572896
+ note: preference.note ? compactLine(preference.note, 220) : existing?.note,
572897
+ source: preference.source ? compactLine(preference.source, 80) : existing?.source
572898
+ }
572899
+ };
572900
+ doc.updatedAt = now;
572901
+ const line = `${now} preference/reply_mode: ${preference.mode} (${preference.preferenceScope})${preference.note ? ` - ${compactLine(preference.note, 160)}` : ""}`;
572902
+ if (!doc.durableObservations.includes(line)) doc.durableObservations.push(line);
572903
+ doc.durableObservations = doc.durableObservations.slice(-MAX_PROFILE_DURABLE_OBSERVATIONS);
572904
+ appendScopedPersonalityEvent(scope, {
572905
+ ts: preference.ts ?? Date.now(),
572906
+ iso: now,
572907
+ role: "system",
572908
+ mode: "reply-preference",
572909
+ replyMode: preference.mode,
572910
+ preferenceScope: preference.preferenceScope,
572911
+ evidenceMessageId: preference.evidenceMessageId,
572912
+ note: preference.note,
572913
+ source: preference.source
572914
+ });
572915
+ saveScopedPersonality(scope, doc);
572916
+ return doc;
572917
+ }
572881
572918
  function loadScopedPersonality(scope) {
572882
572919
  const paths = scopedPersonalityPaths(scope);
572883
572920
  if (!existsSync88(paths.json)) return newDocument(scope);
@@ -573001,6 +573038,7 @@ ${samples}` : ""}`;
573001
573038
  const topTopics = Object.entries(doc.topicCounts ?? {}).sort((a2, b) => b[1] - a2[1]).slice(0, 18).map(([topic, count]) => `- ${topic}: ${count}`);
573002
573039
  const durable = doc.durableObservations.slice(-18).map((line) => `- ${line}`);
573003
573040
  const relationships = doc.relationshipEvents.slice(-10).map((line) => `- ${line}`);
573041
+ const replyMode = doc.preferences?.replyMode;
573004
573042
  return [
573005
573043
  `# Scoped Personality Profile`,
573006
573044
  ``,
@@ -573014,6 +573052,9 @@ ${samples}` : ""}`;
573014
573052
  `## Response Style`,
573015
573053
  doc.responseStyle.map((line) => `- ${line}`).join("\n"),
573016
573054
  ``,
573055
+ `## Stored Preferences`,
573056
+ replyMode ? `- reply_mode: ${replyMode.mode}; scope=${replyMode.scope}; confidence=${replyMode.confidence.toFixed(2)}; updated=${replyMode.updatedAt}${replyMode.note ? `; note=${replyMode.note}` : ""}` : "- none",
573057
+ ``,
573017
573058
  `## Relationship Model`,
573018
573059
  doc.relationshipModel.map((line) => `- ${line}`).join("\n"),
573019
573060
  ``,
@@ -620429,10 +620470,10 @@ function formatSystemObservations(sessionKey) {
620429
620470
  if (recent.length > 0) {
620430
620471
  const sends = recent.filter((e2) => e2.kind.startsWith("telegram.send."));
620431
620472
  const reactions = recent.filter((e2) => e2.kind.startsWith("emoji."));
620432
- const forbidden = sends.filter((e2) => e2.kind === "telegram.send.forbidden").length;
620433
- const rateLimited = sends.filter((e2) => e2.kind === "telegram.send.rate_limited").length;
620434
- if (forbidden > 0) lines.push(`This chat has refused ${forbidden} recent send attempt(s) (e.g. no rights to post). Treat as a strong silence signal.`);
620435
- if (rateLimited > 0) lines.push(`This chat rate-limited ${rateLimited} recent send(s). Slow cadence.`);
620473
+ const failures = sends.filter((e2) => e2.kind === "telegram.send.failed");
620474
+ if (failures.length > 0) {
620475
+ lines.push(`This chat has ${failures.length} recent raw Telegram send failure(s). Latest raw failure: ${failures[failures.length - 1]?.reason ?? "unknown"}.`);
620476
+ }
620436
620477
  if (reactions.length > 0) {
620437
620478
  const reactSummary = reactions.filter((e2) => e2.kind === "emoji.reaction.received").map((e2) => e2.emoji).join("");
620438
620479
  if (reactSummary) lines.push(`Recent inbound reactions in this chat: ${reactSummary}`);
@@ -621879,6 +621920,15 @@ function telegramSocialActorKind(actor) {
621879
621920
  function telegramSocialThreadKey(input) {
621880
621921
  return input.messageThreadId !== void 0 ? `chat:${String(input.chatId)}:thread:${input.messageThreadId}` : `chat:${String(input.chatId)}`;
621881
621922
  }
621923
+ function telegramSocialChatPreferenceKey(chatId) {
621924
+ return `chat:${String(chatId)}`;
621925
+ }
621926
+ function telegramSocialUserInChatPreferenceKey(chatId, actorKey) {
621927
+ return `${telegramSocialChatPreferenceKey(chatId)}:${actorKey}`;
621928
+ }
621929
+ function telegramTextDeliveryCapabilityKey(input) {
621930
+ return input.messageThreadId !== void 0 ? `${telegramSocialChatPreferenceKey(input.chatId)}:thread:${input.messageThreadId}:text` : `${telegramSocialChatPreferenceKey(input.chatId)}:text`;
621931
+ }
621882
621932
  function appendUnique(items, value2, max) {
621883
621933
  if (value2 === void 0 || value2 === null) return items.slice(-max);
621884
621934
  const next = items.filter((item) => item !== value2);
@@ -621951,14 +622001,29 @@ function formatTelegramSocialStateContext(state, input) {
621951
622001
  const salience = state.salience.filter((item) => item.senderKey === senderKey3 || item.chatId === String(input.chatId)).sort((a2, b) => b.ts - a2.ts).slice(0, limit);
621952
622002
  const daydreams = Object.values(state.daydreamOpportunities).filter((item) => item.lifecycle !== "retired").sort((a2, b) => b.updatedAt - a2.updatedAt).slice(0, Math.min(5, limit));
621953
622003
  const preferences = preferenceLines(state.preferences[senderKey3]);
622004
+ const chatPreferenceKey = telegramSocialChatPreferenceKey(input.chatId);
622005
+ const userChatPreferenceKey = telegramSocialUserInChatPreferenceKey(input.chatId, senderKey3);
622006
+ const scopedPreferences = [
622007
+ ...preferenceLines(state.preferences[userChatPreferenceKey], "user-in-chat"),
622008
+ ...preferenceLines(state.preferences[chatPreferenceKey], "chat")
622009
+ ];
622010
+ const resolvedReplyMode = resolveReplyModePreferenceForContext(state, input.chatId, senderKey3);
622011
+ const delivery = state.deliveryCapabilities[telegramTextDeliveryCapabilityKey({
622012
+ chatId: input.chatId,
622013
+ messageThreadId: input.messageThreadId
622014
+ })] ?? state.deliveryCapabilities[telegramTextDeliveryCapabilityKey({ chatId: input.chatId })];
621954
622015
  return [
621955
622016
  "### Telegram Structured Social State",
621956
622017
  selfKey ? `Agent self node: ${selfKey}` : "Agent self node: unknown",
621957
622018
  `Identity boundary: the agent is the self node only. Current actor ${senderKey3} is ${senderIdentity}; reply target ${replyKey ?? "none"} is ${replyIdentity}. Participant first-person claims belong to their actor node, not the agent, unless that actor is the self node.`,
621958
622019
  `Current actor node: ${senderKey3} [${participant?.actorKind || telegramSocialActorKind(input)}] messages=${participant?.messageCount ?? 0}${participant?.lastText ? ` last=${jsonLine(participant.lastText, 140)}` : ""}`,
621959
622020
  thread ? `Active channel/thread: ${thread.key}; messages=${thread.messageCount}; participants=${thread.participantKeys.slice(-8).join(", ") || "none"}; last_outcomes=${thread.lastOutcomeIds.slice(-5).join(", ") || "none"}` : "",
622021
+ delivery ? `Telegram text delivery raw warning stream: ${delivery.status}; key=${telegramTextDeliveryCapabilityKey({ chatId: input.chatId, messageThreadId: input.messageThreadId })}${delivery.reason ? `; raw=${jsonLine(delivery.reason, 180)}` : ""}` : "Telegram text delivery raw warning stream: none recorded",
622022
+ resolvedReplyMode ? `Resolved reply mode: ${resolvedReplyMode.item.mode} from ${resolvedReplyMode.item.scope} (${resolvedReplyMode.key}); confidence=${resolvedReplyMode.item.confidence.toFixed(2)}${resolvedReplyMode.item.note ? ` note=${jsonLine(resolvedReplyMode.item.note, 120)}` : ""}` : "Resolved reply mode: notes_then_reply (system default)",
621960
622023
  preferences.length ? `Relevant preference vector for ${senderKey3}:
621961
622024
  ${preferences.join("\n")}` : `Relevant preference vector for ${senderKey3}: no durable preferences yet`,
622025
+ scopedPreferences.length ? `Scoped reply preferences:
622026
+ ${scopedPreferences.join("\n")}` : "",
621962
622027
  salience.length ? `Recent salience observations:
621963
622028
  ${salience.map((item) => `- ${iso(item.ts)} ${item.senderKey}: ${item.signals.join(", ") || "none"}; text=${jsonLine(item.textPreview, 120)}`).join("\n")}` : "",
621964
622029
  edges.length ? `Relevant relationship edges:
@@ -621969,11 +622034,26 @@ ${outcomes.map(formatOutcome).join("\n")}` : "Prior response outcomes: none yet"
621969
622034
  ${daydreams.map((item) => `- ${item.id} [${item.lifecycle}] confidence=${item.confidence.toFixed(2)} trigger=${jsonLine(item.trigger, 140)}`).join("\n")}` : ""
621970
622035
  ].filter(Boolean).join("\n");
621971
622036
  }
621972
- function preferenceLines(vector) {
622037
+ function resolveReplyModePreferenceForContext(state, chatId, senderKey3) {
622038
+ for (const key of [
622039
+ telegramSocialUserInChatPreferenceKey(chatId, senderKey3),
622040
+ senderKey3,
622041
+ telegramSocialChatPreferenceKey(chatId)
622042
+ ]) {
622043
+ const item = state.preferences[key]?.replyMode;
622044
+ if (item) return { key, item };
622045
+ }
622046
+ return void 0;
622047
+ }
622048
+ function preferenceLines(vector, prefix) {
621973
622049
  if (!vector) return [];
621974
622050
  return Object.entries(vector).map(([key, evidence]) => {
622051
+ if (key === "replyMode") {
622052
+ const item2 = evidence;
622053
+ return `- ${prefix ? `${prefix}.` : ""}${key}: mode=${item2.mode} scope=${item2.scope} confidence=${item2.confidence.toFixed(2)} evidence=${item2.evidenceMessageIds.join(",") || "none"}${item2.note ? ` note=${jsonLine(item2.note, 120)}` : ""}`;
622054
+ }
621975
622055
  const item = evidence;
621976
- return `- ${key}: value=${item.value.toFixed(2)} confidence=${item.confidence.toFixed(2)} evidence=${item.evidenceMessageIds.join(",") || "none"}${item.note ? ` note=${jsonLine(item.note, 120)}` : ""}`;
622056
+ return `- ${prefix ? `${prefix}.` : ""}${key}: value=${item.value.toFixed(2)} confidence=${item.confidence.toFixed(2)} evidence=${item.evidenceMessageIds.join(",") || "none"}${item.note ? ` note=${jsonLine(item.note, 120)}` : ""}`;
621977
622057
  });
621978
622058
  }
621979
622059
  function formatEdge(edge) {
@@ -622003,6 +622083,7 @@ function createTelegramSocialState(now = Date.now()) {
622003
622083
  participants: {},
622004
622084
  relationships: [],
622005
622085
  preferences: {},
622086
+ deliveryCapabilities: {},
622006
622087
  threads: {},
622007
622088
  salience: [],
622008
622089
  outcomes: [],
@@ -622019,6 +622100,7 @@ function normalizeTelegramSocialState(raw) {
622019
622100
  state.participants = normalizeRecord(value2.participants, normalizeParticipant);
622020
622101
  state.relationships = Array.isArray(value2.relationships) ? value2.relationships.map(normalizeRelationship).filter(Boolean).slice(-TELEGRAM_SOCIAL_LIMITS.relationships) : [];
622021
622102
  state.preferences = normalizePreferences(value2.preferences);
622103
+ state.deliveryCapabilities = normalizeRecord(value2.deliveryCapabilities, normalizeDeliveryCapability);
622022
622104
  state.threads = normalizeRecord(value2.threads, normalizeThread);
622023
622105
  state.salience = Array.isArray(value2.salience) ? value2.salience.map(normalizeSalience).filter(Boolean).slice(-TELEGRAM_SOCIAL_LIMITS.salience) : [];
622024
622106
  state.outcomes = Array.isArray(value2.outcomes) ? value2.outcomes.map(normalizeOutcome).filter(Boolean).slice(-TELEGRAM_SOCIAL_LIMITS.outcomes) : [];
@@ -622088,9 +622170,55 @@ function normalizePreferences(raw) {
622088
622170
  note: compactOptional(evidence.note, 220)
622089
622171
  };
622090
622172
  }
622173
+ const replyMode = vector["replyMode"];
622174
+ if (replyMode && typeof replyMode === "object" && !Array.isArray(replyMode)) {
622175
+ const record = replyMode;
622176
+ const mode = normalizeReplyMode(record["mode"]);
622177
+ if (mode) {
622178
+ out[actorKey].replyMode = {
622179
+ mode,
622180
+ scope: normalizeReplyPreferenceScope(record["scope"]),
622181
+ confidence: clamp0110(numberOr(record["confidence"], 0.8)),
622182
+ updatedAt: numberOr(record["updatedAt"], Date.now()),
622183
+ evidenceMessageIds: Array.isArray(record["evidenceMessageIds"]) ? record["evidenceMessageIds"].filter(isNumber).slice(-12) : [],
622184
+ note: compactOptional(record["note"], 220),
622185
+ source: compactOptional(record["source"], 80)
622186
+ };
622187
+ }
622188
+ }
622091
622189
  }
622092
622190
  return out;
622093
622191
  }
622192
+ function normalizeReplyMode(raw) {
622193
+ return raw === "reply_then_notes" || raw === "notes_then_reply" || raw === "reply_only" ? raw : void 0;
622194
+ }
622195
+ function normalizeReplyPreferenceScope(raw) {
622196
+ return raw === "chat" || raw === "user_in_chat" || raw === "user" ? raw : "user_in_chat";
622197
+ }
622198
+ function normalizeDeliveryCapability(raw) {
622199
+ if (!raw || typeof raw !== "object") return null;
622200
+ const value2 = raw;
622201
+ const key = compact2(value2.key || "", 180);
622202
+ const chatId = compact2(value2.chatId || "", 120);
622203
+ if (!key || !chatId) return null;
622204
+ const status = normalizeDeliveryStatus(value2.status);
622205
+ return {
622206
+ key,
622207
+ chatId,
622208
+ messageThreadId: typeof value2.messageThreadId === "number" ? value2.messageThreadId : void 0,
622209
+ status,
622210
+ canSendText: typeof value2.canSendText === "boolean" ? value2.canSendText : status === "allowed" ? true : void 0,
622211
+ checkedAt: numberOr(value2.checkedAt, Date.now()),
622212
+ lastSuccessAt: typeof value2.lastSuccessAt === "number" ? value2.lastSuccessAt : void 0,
622213
+ lastFailureAt: typeof value2.lastFailureAt === "number" ? value2.lastFailureAt : void 0,
622214
+ reason: compactOptional(value2.reason, 320),
622215
+ retryAfterSec: typeof value2.retryAfterSec === "number" ? Math.max(0, Math.floor(value2.retryAfterSec)) : void 0,
622216
+ source: compactOptional(value2.source, 80)
622217
+ };
622218
+ }
622219
+ function normalizeDeliveryStatus(raw) {
622220
+ return raw === "allowed" || raw === "failed" ? raw : "unknown";
622221
+ }
622094
622222
  function normalizeThread(raw) {
622095
622223
  if (!raw || typeof raw !== "object") return null;
622096
622224
  const value2 = raw;
@@ -622291,6 +622419,73 @@ function markTelegramDaydreamOpportunitiesConsidered(state, opportunities, messa
622291
622419
  }
622292
622420
  return registered;
622293
622421
  }
622422
+ function setTelegramReplyModePreference(state, input) {
622423
+ const now = input.ts ?? Date.now();
622424
+ const actorKey = input.actor ? telegramSocialActorKey(input.actor) : void 0;
622425
+ const key = input.scope === "chat" ? telegramSocialChatPreferenceKey(input.chatId) : input.scope === "user_in_chat" && actorKey ? telegramSocialUserInChatPreferenceKey(input.chatId, actorKey) : actorKey ?? telegramSocialChatPreferenceKey(input.chatId);
622426
+ const vector = state.preferences[key] ?? {};
622427
+ const existing = vector.replyMode;
622428
+ const next = {
622429
+ mode: input.mode,
622430
+ scope: input.scope,
622431
+ confidence: Math.max(existing?.confidence ?? 0, clamp0110(input.confidence ?? 0.84)),
622432
+ updatedAt: now,
622433
+ evidenceMessageIds: appendUnique(existing?.evidenceMessageIds ?? [], input.messageId, 12),
622434
+ note: compactOptional(input.note, 220),
622435
+ source: compactOptional(input.source, 80)
622436
+ };
622437
+ vector.replyMode = next;
622438
+ state.preferences[key] = vector;
622439
+ state.updatedAt = now;
622440
+ return next;
622441
+ }
622442
+ function resolveTelegramReplyModePreference(state, input) {
622443
+ const actorKey = input.actor ? telegramSocialActorKey(input.actor) : void 0;
622444
+ const keys = [
622445
+ actorKey ? telegramSocialUserInChatPreferenceKey(input.chatId, actorKey) : "",
622446
+ actorKey ?? "",
622447
+ telegramSocialChatPreferenceKey(input.chatId)
622448
+ ].filter(Boolean);
622449
+ for (const key of keys) {
622450
+ const pref = state.preferences[key]?.replyMode;
622451
+ if (!pref) continue;
622452
+ return {
622453
+ key,
622454
+ mode: pref.mode,
622455
+ scope: pref.scope,
622456
+ confidence: pref.confidence,
622457
+ updatedAt: pref.updatedAt,
622458
+ note: pref.note,
622459
+ source: pref.source
622460
+ };
622461
+ }
622462
+ return void 0;
622463
+ }
622464
+ function setTelegramTextDeliveryCapability(state, input) {
622465
+ const now = input.ts ?? Date.now();
622466
+ const key = telegramTextDeliveryCapabilityKey(input);
622467
+ const existing = state.deliveryCapabilities[key];
622468
+ const canSendText = input.status === "allowed" ? true : input.status === "failed" ? void 0 : input.canSendText ?? existing?.canSendText;
622469
+ const next = {
622470
+ key,
622471
+ chatId: String(input.chatId),
622472
+ messageThreadId: input.messageThreadId,
622473
+ status: input.status,
622474
+ canSendText,
622475
+ checkedAt: now,
622476
+ lastSuccessAt: input.status === "allowed" ? now : existing?.lastSuccessAt,
622477
+ lastFailureAt: input.status === "failed" ? now : existing?.lastFailureAt,
622478
+ reason: compactOptional(input.reason, 320) || existing?.reason,
622479
+ retryAfterSec: input.retryAfterSec,
622480
+ source: compactOptional(input.source, 80)
622481
+ };
622482
+ state.deliveryCapabilities[key] = next;
622483
+ state.updatedAt = now;
622484
+ return next;
622485
+ }
622486
+ function telegramTextDeliveryCapabilityFor(state, input) {
622487
+ return state.deliveryCapabilities[telegramTextDeliveryCapabilityKey(input)] ?? state.deliveryCapabilities[telegramTextDeliveryCapabilityKey({ chatId: input.chatId })];
622488
+ }
622294
622489
  function commitDecisionEdges(state, input, senderKey3, selfKey, now) {
622295
622490
  if (!selfKey) return;
622296
622491
  if (input.shouldReply) {
@@ -622706,6 +622901,28 @@ function telegramDecisionNumber(parsed, keys, nestedKeys = ["internal_notes", "i
622706
622901
  }
622707
622902
  return void 0;
622708
622903
  }
622904
+ function parseTelegramReplyMode(raw) {
622905
+ return raw === "reply_then_notes" || raw === "notes_then_reply" || raw === "reply_only" ? raw : void 0;
622906
+ }
622907
+ function parseTelegramReplyPreferenceToolScope(raw) {
622908
+ return raw === "current_user_in_chat" || raw === "current_user_global" || raw === "current_group" ? raw : void 0;
622909
+ }
622910
+ function parseTelegramReplyPreferenceUpdate(parsed) {
622911
+ const raw = parsed["reply_mode_preference"] ?? parsed["replyModePreference"] ?? parsed["preference_update"] ?? parsed["preferenceUpdate"];
622912
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
622913
+ const record = raw;
622914
+ const replyMode = parseTelegramReplyMode(record["reply_mode"] ?? record["replyMode"] ?? record["mode"]);
622915
+ const scope = parseTelegramReplyPreferenceToolScope(record["scope"]);
622916
+ if (!replyMode || !scope) return void 0;
622917
+ const confidence2 = Number(record["confidence"]);
622918
+ return {
622919
+ scope,
622920
+ replyMode,
622921
+ confidence: Number.isFinite(confidence2) ? Math.max(0, Math.min(1, confidence2)) : void 0,
622922
+ note: cleanTelegramDecisionNote(record["note"] ?? record["reason"] ?? record["rationale"]),
622923
+ source: cleanTelegramDecisionNote(record["source"])
622924
+ };
622925
+ }
622709
622926
  function uniqueTelegramJsonCandidates(candidates) {
622710
622927
  const seen = /* @__PURE__ */ new Set();
622711
622928
  const unique = [];
@@ -623048,7 +623265,8 @@ function parseTelegramInteractionDecision(text, forcedRoute, options2 = {}) {
623048
623265
  scenarioLabel: telegramDecisionNote(parsed, ["scenario_label", "scenarioLabel", "label"], ["scenario", "classification"]),
623049
623266
  scenarioConfidence: telegramDecisionNumber(parsed, ["scenario_confidence", "scenarioConfidence", "confidence"], ["scenario", "classification"]),
623050
623267
  scenarioObjective: telegramDecisionNote(parsed, ["scenario_objective", "scenarioObjective", "objective"], ["scenario", "classification"]),
623051
- scenarioStateLoop: telegramDecisionNote(parsed, ["scenario_state_loop", "scenarioStateLoop", "state_loop", "stateLoop"], ["scenario", "classification"])
623268
+ scenarioStateLoop: telegramDecisionNote(parsed, ["scenario_state_loop", "scenarioStateLoop", "state_loop", "stateLoop"], ["scenario", "classification"]),
623269
+ replyPreferenceUpdate: parseTelegramReplyPreferenceUpdate(parsed)
623052
623270
  };
623053
623271
  } catch {
623054
623272
  continue;
@@ -625273,6 +625491,7 @@ External acquisition contract:
625273
625491
  decision2.voiceNote ? `voice note: ${decision2.voiceNote}` : "",
625274
625492
  decision2.scenarioNote ? `scenario note: ${decision2.scenarioNote}` : "",
625275
625493
  decision2.scenarioId ? `scenario classification: ${decision2.scenarioId}${decision2.scenarioLabel ? ` (${decision2.scenarioLabel})` : ""}` : "",
625494
+ decision2.replyPreferenceUpdate ? `reply preference update: ${decision2.replyPreferenceUpdate.replyMode} (${this.telegramReplyPreferenceScopeLabel(decision2.replyPreferenceUpdate.scope)})` : "",
625276
625495
  cadence ? `next attention sample: ${cadence}` : ""
625277
625496
  ].filter(Boolean);
625278
625497
  if (viewId && this.subAgentViewCallbacks) {
@@ -625293,7 +625512,8 @@ External acquisition contract:
625293
625512
  decision2.procedureNote ? `procedure note: ${decision2.procedureNote}` : "",
625294
625513
  decision2.voiceNote ? `voice note: ${decision2.voiceNote}` : "",
625295
625514
  decision2.scenarioNote ? `scenario note: ${decision2.scenarioNote}` : "",
625296
- decision2.scenarioId ? `scenario classification: ${decision2.scenarioId}${decision2.scenarioLabel ? ` (${decision2.scenarioLabel})` : ""}` : ""
625515
+ decision2.scenarioId ? `scenario classification: ${decision2.scenarioId}${decision2.scenarioLabel ? ` (${decision2.scenarioLabel})` : ""}` : "",
625516
+ decision2.replyPreferenceUpdate ? `reply preference update: ${decision2.replyPreferenceUpdate.replyMode} (${this.telegramReplyPreferenceScopeLabel(decision2.replyPreferenceUpdate.scope)})` : ""
625297
625517
  ].filter(Boolean);
625298
625518
  this.tuiWrite(() => {
625299
625519
  renderTelegramSubAgentEvent(msg.username, primary);
@@ -625710,6 +625930,7 @@ ${message2}`)
625710
625930
  decision2.voiceNote ? `voice_note: ${decision2.voiceNote}` : "",
625711
625931
  decision2.memoryNote ? `memory_note: ${decision2.memoryNote}` : "",
625712
625932
  decision2.relationshipNote ? `relationship_note: ${decision2.relationshipNote}` : "",
625933
+ decision2.replyPreferenceUpdate ? `reply_preference_update: ${decision2.replyPreferenceUpdate.replyMode} (${this.telegramReplyPreferenceScopeLabel(decision2.replyPreferenceUpdate.scope)})` : "",
625713
625934
  "Use this as classifier output from the prior attention stage, not as a new instruction to spawn or route again."
625714
625935
  ].filter(Boolean).join("\n");
625715
625936
  }
@@ -626515,6 +626736,158 @@ ${mediaContext}` : ""
626515
626736
  repoRoot: this.repoRoot || "."
626516
626737
  };
626517
626738
  }
626739
+ telegramUserPersonalityScope(msg) {
626740
+ const id = msg.fromUserId ? `telegram-user:${msg.fromUserId}` : `telegram-user:${msg.username || msg.firstName || "unknown"}`;
626741
+ const label = `user-${msg.username || msg.firstName || msg.fromUserId || "unknown"}`;
626742
+ return {
626743
+ kind: "telegram-user",
626744
+ id,
626745
+ label,
626746
+ repoRoot: this.repoRoot || "."
626747
+ };
626748
+ }
626749
+ telegramUserInChatPersonalityScope(sessionKey, msg) {
626750
+ const user = msg.fromUserId ? `user:${msg.fromUserId}` : `user:${msg.username || msg.firstName || "unknown"}`;
626751
+ const chat = msg.chatTitle || msg.chatType || String(msg.chatId);
626752
+ return {
626753
+ kind: "telegram-user-in-chat",
626754
+ id: `${sessionKey}:${user}`,
626755
+ label: `${chat}-${msg.username || msg.firstName || msg.fromUserId || "unknown"}`,
626756
+ repoRoot: this.repoRoot || "."
626757
+ };
626758
+ }
626759
+ telegramSocialPreferenceScope(scope) {
626760
+ if (scope === "current_group") return "chat";
626761
+ if (scope === "current_user_global") return "user";
626762
+ return "user_in_chat";
626763
+ }
626764
+ telegramReplyPreferenceScopeLabel(scope) {
626765
+ if (scope === "current_group") return "current group";
626766
+ if (scope === "current_user_global") return "current user globally";
626767
+ return "current user in this group/chat";
626768
+ }
626769
+ scopedPersonalityForReplyPreference(sessionKey, msg, scope) {
626770
+ if (scope === "current_group") return this.telegramPersonalityScope(sessionKey, msg);
626771
+ if (scope === "current_user_global") return this.telegramUserPersonalityScope(msg);
626772
+ return this.telegramUserInChatPersonalityScope(sessionKey, msg);
626773
+ }
626774
+ applyTelegramReplyPreferenceUpdate(sessionKey, msg, update2, source) {
626775
+ if (!update2) return void 0;
626776
+ const preferenceScope = this.telegramSocialPreferenceScope(update2.scope);
626777
+ const evidence = setTelegramReplyModePreference(this.telegramSocialStateForSession(sessionKey), {
626778
+ chatId: msg.chatId,
626779
+ actor: this.telegramMessageSocialActorInput(msg),
626780
+ scope: preferenceScope,
626781
+ mode: update2.replyMode,
626782
+ confidence: update2.confidence,
626783
+ messageId: msg.messageId,
626784
+ note: update2.note,
626785
+ source: update2.source || source
626786
+ });
626787
+ try {
626788
+ updateScopedPersonalityReplyPreference(
626789
+ this.scopedPersonalityForReplyPreference(sessionKey, msg, update2.scope),
626790
+ {
626791
+ mode: update2.replyMode,
626792
+ preferenceScope,
626793
+ confidence: evidence.confidence,
626794
+ evidenceMessageId: msg.messageId,
626795
+ note: update2.note,
626796
+ source: update2.source || source
626797
+ }
626798
+ );
626799
+ } catch {
626800
+ }
626801
+ this.saveTelegramConversationState(sessionKey);
626802
+ return `stored reply_mode=${update2.replyMode} for ${this.telegramReplyPreferenceScopeLabel(update2.scope)}`;
626803
+ }
626804
+ hydrateTelegramReplyPreferencesFromPersona(sessionKey, msg) {
626805
+ for (const [scope, personalityScope] of [
626806
+ ["current_group", this.telegramPersonalityScope(sessionKey, msg)],
626807
+ ["current_user_global", this.telegramUserPersonalityScope(msg)],
626808
+ ["current_user_in_chat", this.telegramUserInChatPersonalityScope(sessionKey, msg)]
626809
+ ]) {
626810
+ try {
626811
+ const pref = loadScopedPersonality(personalityScope).preferences?.replyMode;
626812
+ if (!pref) continue;
626813
+ setTelegramReplyModePreference(this.telegramSocialStateForSession(sessionKey), {
626814
+ chatId: msg.chatId,
626815
+ actor: this.telegramMessageSocialActorInput(msg),
626816
+ scope: pref.scope,
626817
+ mode: pref.mode,
626818
+ confidence: pref.confidence,
626819
+ note: pref.note,
626820
+ source: pref.source || `persona:${scope}`,
626821
+ ts: Number.isFinite(Date.parse(pref.updatedAt)) ? Date.parse(pref.updatedAt) : Date.now()
626822
+ });
626823
+ } catch {
626824
+ }
626825
+ }
626826
+ }
626827
+ resolvedTelegramReplyMode(sessionKey, msg) {
626828
+ try {
626829
+ this.hydrateTelegramReplyPreferencesFromPersona(sessionKey, msg);
626830
+ return resolveTelegramReplyModePreference(this.telegramSocialStateForSession(sessionKey), {
626831
+ chatId: msg.chatId,
626832
+ actor: this.telegramMessageSocialActorInput(msg)
626833
+ })?.mode ?? "notes_then_reply";
626834
+ } catch {
626835
+ return "notes_then_reply";
626836
+ }
626837
+ }
626838
+ stripTelegramDecisionNotes(decision2) {
626839
+ return {
626840
+ ...decision2,
626841
+ silentDisposition: void 0,
626842
+ mentalNote: void 0,
626843
+ memoryNote: void 0,
626844
+ relationshipNote: void 0,
626845
+ procedureNote: void 0,
626846
+ voiceNote: void 0,
626847
+ scenarioNote: void 0,
626848
+ scenarioId: void 0,
626849
+ scenarioLabel: void 0,
626850
+ scenarioConfidence: void 0,
626851
+ scenarioObjective: void 0,
626852
+ scenarioStateLoop: void 0
626853
+ };
626854
+ }
626855
+ telegramTextDeliveryCapability(sessionKey, chatId, messageThreadId) {
626856
+ try {
626857
+ return telegramTextDeliveryCapabilityFor(this.telegramSocialStateForSession(sessionKey), {
626858
+ chatId,
626859
+ messageThreadId
626860
+ });
626861
+ } catch {
626862
+ return void 0;
626863
+ }
626864
+ }
626865
+ updateTelegramTextDeliveryCapability(chatId, input) {
626866
+ const sessionKey = `chat:${String(chatId)}`;
626867
+ try {
626868
+ const capability = setTelegramTextDeliveryCapability(this.telegramSocialStateForSession(sessionKey), {
626869
+ chatId,
626870
+ messageThreadId: input.messageThreadId,
626871
+ status: input.status,
626872
+ canSendText: input.canSendText,
626873
+ reason: input.reason,
626874
+ retryAfterSec: input.retryAfterSec,
626875
+ source: input.source
626876
+ });
626877
+ this.saveTelegramConversationState(sessionKey);
626878
+ return capability;
626879
+ } catch {
626880
+ return void 0;
626881
+ }
626882
+ }
626883
+ formatTelegramDeliveryCapabilityContext(sessionKey, msg) {
626884
+ const capability = this.telegramTextDeliveryCapability(sessionKey, msg.chatId, msg.messageThreadId);
626885
+ if (!capability) return "Telegram text delivery raw warning stream for this chat: none recorded.";
626886
+ return [
626887
+ `Telegram text delivery raw warning stream for this chat: ${capability.status}.`,
626888
+ capability.reason ? `Latest raw Telegram warning/error text: ${capability.reason}` : ""
626889
+ ].filter(Boolean).join(" ");
626890
+ }
626518
626891
  ensureTelegramConversationLoaded(sessionKey) {
626519
626892
  if (this.loadedConversationState.has(sessionKey)) return;
626520
626893
  this.loadedConversationState.add(sessionKey);
@@ -627024,7 +627397,7 @@ ${mediaContext}` : ""
627024
627397
  convertMarkdownToTelegramHTML(decision2.text),
627025
627398
  replyTo
627026
627399
  );
627027
- this.recordTelegramAssistantMessage({
627400
+ if (sentMessageId !== null) this.recordTelegramAssistantMessage({
627028
627401
  chatId,
627029
627402
  chatType: artifact.chatType,
627030
627403
  chatTitle: artifact.chatTitle,
@@ -627037,6 +627410,7 @@ ${mediaContext}` : ""
627037
627410
  messageId: sentMessageId,
627038
627411
  replyToMessageId: replyTo
627039
627412
  });
627413
+ if (sentMessageId === null) return { sent: false, reason: "Telegram returned no delivered message id" };
627040
627414
  state.lastFollowupAt = now;
627041
627415
  this.saveTelegramConversationState(sessionKey);
627042
627416
  this.tuiWrite(() => renderTelegramSubAgentEvent("reflection", `sent scoped Telegram follow-up (${decision2.confidence.toFixed(2)}): ${decision2.reason}`));
@@ -627127,6 +627501,7 @@ ${mediaContext}` : ""
627127
627501
  buildTelegramSocialStateContext(sessionKey, msg, salienceSignals = []) {
627128
627502
  try {
627129
627503
  this.registerLatestTelegramDaydreamOpportunities(sessionKey);
627504
+ this.hydrateTelegramReplyPreferencesFromPersona(sessionKey, msg);
627130
627505
  return formatTelegramSocialStateContext(this.telegramSocialStateForSession(sessionKey), {
627131
627506
  sessionKey,
627132
627507
  chatId: msg.chatId,
@@ -627255,6 +627630,28 @@ ${mediaContext}` : ""
627255
627630
  msg.chatType !== "private" ? `Group participant ${telegramSpeakerLabel(msg)} in ${msg.chatTitle || "unknown group"}` : `Private Telegram chat with ${telegramSpeakerLabel(msg)}`
627256
627631
  ]
627257
627632
  });
627633
+ updateScopedPersonality(this.telegramUserPersonalityScope(msg), {
627634
+ speaker: telegramSpeakerLabel(msg),
627635
+ text,
627636
+ role: "user",
627637
+ mode,
627638
+ ts: entry.ts,
627639
+ toneTags: inferTelegramToneTags(text),
627640
+ relationshipHints: [
627641
+ `Telegram user identity ${telegramSpeakerLabel(msg)} observed in ${msg.chatType}`
627642
+ ]
627643
+ });
627644
+ updateScopedPersonality(this.telegramUserInChatPersonalityScope(sessionKey, msg), {
627645
+ speaker: telegramSpeakerLabel(msg),
627646
+ text,
627647
+ role: "user",
627648
+ mode,
627649
+ ts: entry.ts,
627650
+ toneTags: inferTelegramToneTags(text),
627651
+ relationshipHints: [
627652
+ msg.chatType !== "private" ? `Participant-specific preference/memory scope for ${telegramSpeakerLabel(msg)} in ${msg.chatTitle || "unknown group"}` : `Participant-specific preference/memory scope for private chat with ${telegramSpeakerLabel(msg)}`
627653
+ ]
627654
+ });
627258
627655
  } catch {
627259
627656
  }
627260
627657
  this.saveTelegramConversationState(sessionKey);
@@ -628986,7 +629383,7 @@ ${this.quoteTelegramContextBlock(msg.text, 1200)}`,
628986
629383
  routeInstruction,
628987
629384
  ``,
628988
629385
  `Return JSON only with this schema:`,
628989
- `{"recoverable":true|false,"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason","attention_state":"idle"|"observing"|"engaged"|"cooldown","attention_delta":-1.0..1.0,"next_check_after_messages":1..12,"silent_disposition":"short outcome-level disposition","mental_note":"short outcome-level observation","memory_note":"short memory/summary update","relationship_note":"short relationship/thread note","procedure_note":"short tree/procedure note","voice_note":"short final-voice note","scenario_note":"short scenario/transition note","scenario_id":"dynamic inferred scenario id","scenario_label":"human readable dynamic scenario label","scenario_confidence":0.0-1.0,"scenario_objective":"current scenario objective","scenario_state_loop":"state loop to maintain until transition"}`,
629386
+ `{"recoverable":true|false,"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason","attention_state":"idle"|"observing"|"engaged"|"cooldown","attention_delta":-1.0..1.0,"next_check_after_messages":1..12,"silent_disposition":"short outcome-level disposition","mental_note":"short outcome-level observation","memory_note":"short memory/summary update","relationship_note":"short relationship/thread note","procedure_note":"short tree/procedure note","voice_note":"short final-voice note","scenario_note":"short scenario/transition note","scenario_id":"dynamic inferred scenario id","scenario_label":"human readable dynamic scenario label","scenario_confidence":0.0-1.0,"scenario_objective":"current scenario objective","scenario_state_loop":"state loop to maintain until transition","reply_mode_preference":null|{"scope":"current_user_in_chat"|"current_user_global"|"current_group","reply_mode":"reply_then_notes"|"notes_then_reply"|"reply_only","confidence":0.0-1.0,"note":"why this durable preference was explicitly expressed"}}`,
628990
629387
  ``,
628991
629388
  `Original router output:`,
628992
629389
  rawPreview,
@@ -629050,7 +629447,7 @@ ${userPrompt.slice(-4e3)}` : userPrompt;
629050
629447
  `Return exactly one JSON object and no prose. No <think> tags. No commentary.`,
629051
629448
  routeInstruction,
629052
629449
  ``,
629053
- `Required schema: {"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason","attention_state":"idle"|"observing"|"engaged"|"cooldown","attention_delta":-1.0..1.0,"next_check_after_messages":1..12,"silent_disposition":"short outcome-level disposition","mental_note":"short outcome-level observation","memory_note":"short memory/summary update","relationship_note":"short relationship/thread note","procedure_note":"short tree/procedure note","voice_note":"short final-voice note","scenario_note":"short scenario/transition note","scenario_id":"dynamic inferred scenario id","scenario_label":"human readable dynamic scenario label","scenario_confidence":0.0-1.0,"scenario_objective":"current scenario objective","scenario_state_loop":"state loop to maintain until transition"}`,
629450
+ `Required schema: {"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason","attention_state":"idle"|"observing"|"engaged"|"cooldown","attention_delta":-1.0..1.0,"next_check_after_messages":1..12,"silent_disposition":"short outcome-level disposition","mental_note":"short outcome-level observation","memory_note":"short memory/summary update","relationship_note":"short relationship/thread note","procedure_note":"short tree/procedure note","voice_note":"short final-voice note","scenario_note":"short scenario/transition note","scenario_id":"dynamic inferred scenario id","scenario_label":"human readable dynamic scenario label","scenario_confidence":0.0-1.0,"scenario_objective":"current scenario objective","scenario_state_loop":"state loop to maintain until transition","reply_mode_preference":null|{"scope":"current_user_in_chat"|"current_user_global"|"current_group","reply_mode":"reply_then_notes"|"notes_then_reply"|"reply_only","confidence":0.0-1.0,"note":"why this durable preference was explicitly expressed"}}`,
629054
629451
  ``,
629055
629452
  `Invalid previous output, for diagnostics only:`,
629056
629453
  invalidPreview,
@@ -629350,6 +629747,7 @@ ${retryText}`,
629350
629747
  `Platform salience signals (context only, not triggers): ${identitySalienceSignals.length ? identitySalienceSignals.join(", ") : "none"}`,
629351
629748
  `Current chat type: ${msg.chatType}`,
629352
629749
  `Current sender: ${telegramSpeakerLabel(msg)} [${telegramActorKindLabel(msg)}]`,
629750
+ this.formatTelegramDeliveryCapabilityContext(sessionKey, msg),
629353
629751
  msg.replyToMessageId ? `Current message replies to message_id ${msg.replyToMessageId}` : "",
629354
629752
  msg.replyToUsername ? `Current message replies to @${msg.replyToUsername}${msg.replyToBot ? " [bot]" : " [human/unknown]"}` : "",
629355
629753
  currentReplyContext,
@@ -629414,7 +629812,7 @@ ${stimulationProbe.context}`,
629414
629812
  `Use the persona docs below as binding behavioral guidance for whether speaking is appropriate and how to avoid over-eager or adversarially bad interventions.`,
629415
629813
  `Return JSON only, with no markdown and no explanation outside JSON.`,
629416
629814
  ``,
629417
- `Schema: {"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason","attention_state":"idle"|"observing"|"engaged"|"cooldown","attention_delta":-1.0..1.0,"next_check_after_messages":1..12,"silent_disposition":"short outcome-level disposition","mental_note":"short outcome-level observation","memory_note":"short memory/summary update","relationship_note":"short relationship/thread note","procedure_note":"short tree/procedure note","voice_note":"short final-voice note","scenario_note":"short scenario/transition note","scenario_id":"dynamic inferred scenario id","scenario_label":"human readable dynamic scenario label","scenario_confidence":0.0-1.0,"scenario_objective":"current scenario objective","scenario_state_loop":"state loop to maintain until transition"}`,
629815
+ `Schema: {"route":"chat"|"action","should_reply":true|false,"confidence":0.0-1.0,"reason":"short reason","attention_state":"idle"|"observing"|"engaged"|"cooldown","attention_delta":-1.0..1.0,"next_check_after_messages":1..12,"silent_disposition":"short outcome-level disposition","mental_note":"short outcome-level observation","memory_note":"short memory/summary update","relationship_note":"short relationship/thread note","procedure_note":"short tree/procedure note","voice_note":"short final-voice note","scenario_note":"short scenario/transition note","scenario_id":"dynamic inferred scenario id","scenario_label":"human readable dynamic scenario label","scenario_confidence":0.0-1.0,"scenario_objective":"current scenario objective","scenario_state_loop":"state loop to maintain until transition","reply_mode_preference":null|{"scope":"current_user_in_chat"|"current_user_global"|"current_group","reply_mode":"reply_then_notes"|"notes_then_reply"|"reply_only","confidence":0.0-1.0,"note":"why this durable preference was explicitly expressed"}}`,
629418
629816
  ``,
629419
629817
  `Route meanings:`,
629420
629818
  `- chat: a short conversational answer can be produced without tools.`,
@@ -629426,6 +629824,8 @@ ${stimulationProbe.context}`,
629426
629824
  `High-salience evidence: private DMs, exact @username matches, display-name self references, and replies to this bot are notification-like signals. They should usually move attention toward deeper processing and a likely response unless the surrounding context makes silence more natural.`,
629427
629825
  `No keyword routing: do not use static keyword rules. Infer whether identity references are actually aimed at this bot from syntax, tone, recent turns, and relationships.`,
629428
629826
  `Observation notes: always fill silent_disposition, mental_note, memory_note, relationship_note, procedure_note, voice_note, scenario_note, scenario_id, scenario_label, scenario_confidence, scenario_objective, and scenario_state_loop with concise outcome-level notes for the TUI. Do not expose hidden chain-of-thought; summarize what the bot did with the heard message.`,
629827
+ `Reply-mode preference capture: if and only if the current sender explicitly expresses a durable preference for reply cadence/order, populate reply_mode_preference as a typed update. Do not infer this from style, keywords, tone, or one-off task shape. Use null otherwise.`,
629828
+ `Reply-mode meanings: reply_then_notes means visible reply first and internal notes afterward; notes_then_reply means internal notes before visible reply; reply_only means one user-facing reply with tools/context and no extra internal notes stage.`,
629429
629829
  `Bot-to-bot discipline: sender labels include [bot] or [human]. Treat peer bots as autonomous actors, not human users. Avoid bot loops by replying only when the exchange context makes a response socially useful.`,
629430
629830
  `Ingress discipline: this Telegram message has already been retained as chat context. should_reply controls only whether to emit a visible reply.`,
629431
629831
  `Memory discipline: use durable associative user memory, relationships, prior actions, and recent context to infer whether this speaker is continuing a bot-related thread. A mention is not required when the semantic target is clearly the bot or an ongoing bot-mediated discussion.`,
@@ -630233,6 +630633,11 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
630233
630633
  if (replyParameters) body["reply_parameters"] = replyParameters;
630234
630634
  const result = await this.apiCall("sendMessage", body);
630235
630635
  this.state.messagesSent++;
630636
+ this.updateTelegramTextDeliveryCapability(chatId, {
630637
+ status: "allowed",
630638
+ canSendText: true,
630639
+ source: "sendLiveMessage"
630640
+ });
630236
630641
  return result.result?.message_id ?? null;
630237
630642
  } catch {
630238
630643
  try {
@@ -630244,8 +630649,26 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
630244
630649
  if (replyParameters) body["reply_parameters"] = replyParameters;
630245
630650
  const result = await this.apiCall("sendMessage", body);
630246
630651
  this.state.messagesSent++;
630652
+ this.updateTelegramTextDeliveryCapability(chatId, {
630653
+ status: "allowed",
630654
+ canSendText: true,
630655
+ source: "sendLiveMessage.plain_fallback"
630656
+ });
630247
630657
  return result.result?.message_id ?? null;
630248
- } catch {
630658
+ } catch (err) {
630659
+ const errStr = err instanceof Error ? err.message : String(err);
630660
+ this.tuiWrite(() => renderWarning(`Failed to send Telegram live message: ${errStr}`));
630661
+ this.updateTelegramTextDeliveryCapability(chatId, {
630662
+ status: "failed",
630663
+ reason: errStr,
630664
+ source: "sendLiveMessage"
630665
+ });
630666
+ getSoulObservationStream().emit({
630667
+ kind: "telegram.send.failed",
630668
+ sessionKey: String(chatId),
630669
+ reason: errStr,
630670
+ ts: Date.now()
630671
+ });
630249
630672
  return null;
630250
630673
  }
630251
630674
  }
@@ -630480,14 +630903,29 @@ Join: ${newUrl}`);
630480
630903
  } catch (err) {
630481
630904
  decision2 = this.fallbackTelegramRouterDecision(msg, toolContext, err);
630482
630905
  }
630483
- this.deliverTelegramAttentionDecision(
630906
+ const storedPreference = this.applyTelegramReplyPreferenceUpdate(
630484
630907
  sessionKey,
630485
630908
  msg,
630486
- attentionViewId,
630487
- decision2,
630488
- this.telegramMessageIdentitySalienceSignals(msg),
630489
- this.markLatestTelegramDaydreamOpportunitiesConsidered(sessionKey, msg)
630909
+ decision2.replyPreferenceUpdate,
630910
+ "telegram-router"
630490
630911
  );
630912
+ if (storedPreference) this.subAgentViewCallbacks?.onWrite(attentionViewId || this.viewIdForMessage(msg), `preference: ${storedPreference}`);
630913
+ const replyMode = this.resolvedTelegramReplyMode(sessionKey, msg);
630914
+ const deliveredDecision = replyMode === "reply_only" ? this.stripTelegramDecisionNotes(decision2) : decision2;
630915
+ const daydreamOpportunities = this.markLatestTelegramDaydreamOpportunitiesConsidered(sessionKey, msg);
630916
+ const shouldDeferNotes = replyMode === "reply_then_notes" && decision2.shouldReply;
630917
+ if (!shouldDeferNotes) {
630918
+ this.deliverTelegramAttentionDecision(
630919
+ sessionKey,
630920
+ msg,
630921
+ attentionViewId,
630922
+ deliveredDecision,
630923
+ this.telegramMessageIdentitySalienceSignals(msg),
630924
+ daydreamOpportunities
630925
+ );
630926
+ } else if (attentionViewId && this.subAgentViewCallbacks) {
630927
+ this.subAgentViewCallbacks.onWrite(attentionViewId, `reply mode: reply_then_notes; internal notes will be committed after visible reply attempt`);
630928
+ }
630491
630929
  this.markLastTelegramUserMessageMode(msg, decision2.shouldReply ? decision2.route : "ambient");
630492
630930
  this.subAgentViewCallbacks?.onWrite(
630493
630931
  this.viewIdForMessage(msg),
@@ -630497,7 +630935,7 @@ Join: ${newUrl}`);
630497
630935
  this.maybeLogTelegramGroupSkip(msg, `live inference: no reply — ${decision2.reason}`);
630498
630936
  return;
630499
630937
  }
630500
- const decisionContext = this.formatTelegramAttentionDecisionContext(decision2);
630938
+ const decisionContext = this.formatTelegramAttentionDecisionContext(deliveredDecision);
630501
630939
  const existingAfterDecision = this.subAgents.get(sessionKey);
630502
630940
  if (existingAfterDecision && !existingAfterDecision.aborted) {
630503
630941
  this.subAgentViewCallbacks?.onWrite(
@@ -630506,11 +630944,31 @@ Join: ${newUrl}`);
630506
630944
  );
630507
630945
  this.enqueueTelegramSubAgentContext(sessionKey, existingAfterDecision, decisionContext, msg.username);
630508
630946
  await this.enqueueTelegramQueuedSessionWorkForExistingSubAgent(work, existingAfterDecision);
630947
+ if (shouldDeferNotes) {
630948
+ this.deliverTelegramAttentionDecision(
630949
+ sessionKey,
630950
+ msg,
630951
+ attentionViewId,
630952
+ deliveredDecision,
630953
+ this.telegramMessageIdentitySalienceSignals(msg),
630954
+ daydreamOpportunities
630955
+ );
630956
+ }
630509
630957
  return;
630510
630958
  }
630511
630959
  const subAgentProfile = decision2.route === "chat" ? "chat" : "action";
630512
630960
  if (decision2.route === "chat" && msg.chatType === "private") {
630513
630961
  await this.handleTelegramChatCompletion(msg, toolContext, [decisionContext, rapidContext].filter(Boolean).join("\n\n"));
630962
+ if (shouldDeferNotes) {
630963
+ this.deliverTelegramAttentionDecision(
630964
+ sessionKey,
630965
+ msg,
630966
+ attentionViewId,
630967
+ deliveredDecision,
630968
+ this.telegramMessageIdentitySalienceSignals(msg),
630969
+ daydreamOpportunities
630970
+ );
630971
+ }
630514
630972
  return;
630515
630973
  }
630516
630974
  const subAgent = {
@@ -630595,6 +631053,16 @@ Join: ${newUrl}`);
630595
631053
  await this.deleteLiveMessage(msg.chatId, subAgent.liveMessageId).catch(() => {
630596
631054
  });
630597
631055
  }
631056
+ if (shouldDeferNotes) {
631057
+ this.deliverTelegramAttentionDecision(
631058
+ sessionKey,
631059
+ msg,
631060
+ attentionViewId,
631061
+ deliveredDecision,
631062
+ this.telegramMessageIdentitySalienceSignals(msg),
631063
+ daydreamOpportunities
631064
+ );
631065
+ }
630598
631066
  return;
630599
631067
  }
630600
631068
  if (subAgent.liveMessagePromise) {
@@ -630603,16 +631071,30 @@ Join: ${newUrl}`);
630603
631071
  }
630604
631072
  const finalHtml = convertMarkdownToTelegramHTML(finalText);
630605
631073
  const sentMessageId = await this.sendOrEditFinalTelegramHTML(msg, finalHtml, subAgent.liveMessageId);
630606
- this.recordTelegramAssistantMessage(msg, finalText, subAgentProfile, {
630607
- messageId: sentMessageId,
630608
- replyToMessageId: msg.chatType !== "private" ? msg.messageId : void 0
630609
- });
631074
+ if (sentMessageId !== null || msg.guestQueryId) {
631075
+ this.recordTelegramAssistantMessage(msg, finalText, subAgentProfile, {
631076
+ messageId: sentMessageId,
631077
+ replyToMessageId: msg.chatType !== "private" ? msg.messageId : void 0
631078
+ });
631079
+ } else {
631080
+ this.subAgentViewCallbacks?.onWrite(subAgent.viewId, "Telegram text send returned no delivered message id");
631081
+ }
630610
631082
  await this.sendGeneratedArtifactsFromSubAgent(
630611
631083
  msg,
630612
631084
  subAgent,
630613
631085
  finalText,
630614
631086
  Boolean(subAgent.liveMessageId && !msg.guestQueryId)
630615
631087
  );
631088
+ if (shouldDeferNotes) {
631089
+ this.deliverTelegramAttentionDecision(
631090
+ sessionKey,
631091
+ msg,
631092
+ attentionViewId,
631093
+ deliveredDecision,
631094
+ this.telegramMessageIdentitySalienceSignals(msg),
631095
+ daydreamOpportunities
631096
+ );
631097
+ }
630616
631098
  this.tuiWrite(() => renderTelegramSubAgentComplete(msg.username, finalText));
630617
631099
  this.subAgentViewCallbacks?.onWrite(subAgent.viewId, `completed: ${finalText}`);
630618
631100
  this.subAgentViewCallbacks?.onStatus(subAgent.viewId, "completed");
@@ -630633,6 +631115,16 @@ Join: ${newUrl}`);
630633
631115
  await this.deleteLiveMessage(msg.chatId, subAgent.liveMessageId).catch(() => {
630634
631116
  });
630635
631117
  }
631118
+ if (shouldDeferNotes) {
631119
+ this.deliverTelegramAttentionDecision(
631120
+ sessionKey,
631121
+ msg,
631122
+ attentionViewId,
631123
+ deliveredDecision,
631124
+ this.telegramMessageIdentitySalienceSignals(msg),
631125
+ daydreamOpportunities
631126
+ );
631127
+ }
630636
631128
  } finally {
630637
631129
  this.clearTelegramSubAgentContextBuffer(sessionKey);
630638
631130
  this.subAgents.delete(sessionKey);
@@ -630716,10 +631208,14 @@ Join: ${newUrl}`);
630716
631208
  }
630717
631209
  const finalHtml = convertMarkdownToTelegramHTML(finalText);
630718
631210
  const sentMessageId = await this.sendOrEditFinalTelegramHTML(msg, finalHtml, subAgent.liveMessageId);
630719
- this.recordTelegramAssistantMessage(msg, finalText, "chat", {
630720
- messageId: sentMessageId,
630721
- replyToMessageId: msg.chatType !== "private" ? msg.messageId : void 0
630722
- });
631211
+ if (sentMessageId !== null || msg.guestQueryId) {
631212
+ this.recordTelegramAssistantMessage(msg, finalText, "chat", {
631213
+ messageId: sentMessageId,
631214
+ replyToMessageId: msg.chatType !== "private" ? msg.messageId : void 0
631215
+ });
631216
+ } else {
631217
+ this.subAgentViewCallbacks?.onWrite(subAgent.viewId, "Telegram text send returned no delivered message id");
631218
+ }
630723
631219
  await this.sendGeneratedArtifactsFromSubAgent(
630724
631220
  msg,
630725
631221
  subAgent,
@@ -630845,10 +631341,14 @@ Join: ${newUrl}`);
630845
631341
  }
630846
631342
  const finalHtml = convertMarkdownToTelegramHTML(cleaned);
630847
631343
  const sentMessageId = await this.sendOrEditFinalTelegramHTML(msg, finalHtml, liveMessageId);
630848
- this.recordTelegramAssistantMessage(msg, cleaned, "chat", {
630849
- messageId: sentMessageId,
630850
- replyToMessageId: msg.chatType !== "private" ? msg.messageId : void 0
630851
- });
631344
+ if (sentMessageId !== null || msg.guestQueryId) {
631345
+ this.recordTelegramAssistantMessage(msg, cleaned, "chat", {
631346
+ messageId: sentMessageId,
631347
+ replyToMessageId: msg.chatType !== "private" ? msg.messageId : void 0
631348
+ });
631349
+ } else {
631350
+ this.subAgentViewCallbacks?.onWrite(viewId, "Telegram text send returned no delivered message id");
631351
+ }
630852
631352
  this.subAgentViewCallbacks?.onWrite(viewId, `completed: ${cleaned}`);
630853
631353
  this.subAgentViewCallbacks?.onStatus(viewId, "completed");
630854
631354
  } catch (err) {
@@ -631244,6 +631744,7 @@ ${currentTelegramPrompt}`;
631244
631744
  "You have access to isolated per-chat memory (memory_write, memory_read, memory_search) scoped to this conversation.",
631245
631745
  "memory_search may use scope=group/current_chat for this group or scope=user with user_id/username for a participant in this same group. Other groups, admin chats, and private DMs are not accessible here.",
631246
631746
  "You can remember facts about users and retrieve them later. Durable associative memory in the prompt includes participant profiles, relationships, scoped facts, and prior actions retained across days, sessions, and Omnius updates. You also have web_search and web_fetch to look up information.",
631747
+ "If a user explicitly states a durable preference for reply cadence/order, call telegram_preference_set. Do not infer or classify reply-mode preferences from keywords, style, tone, or task type.",
631247
631748
  "You have the full scoped Telegram media-analysis stack by default: telegram_media_recent, image_read, ocr, ocr_image_advanced, vision, pdf_to_text, ocr_pdf, transcribe_file, video_understand, audio_analyze, and identity_memory. For complex textual imagery, screenshots, forms, scans, or dense labels, prefer ocr_image_advanced after resolving media with path='reply' or path='latest'.",
631248
631749
  formatIdentityMemoryContext(chatLabel || "Telegram private chat"),
631249
631750
  reminderToolContract,
@@ -631996,6 +632497,7 @@ Scoped workspace: ${scopedRoot}`,
631996
632497
  new SkillListTool(repoRoot),
631997
632498
  sharedSkillExtractTool,
631998
632499
  this.buildTelegramMediaRecentTool(chatId, msg),
632500
+ this.buildTelegramReplyPreferenceTool(context2, chatId, msg),
631999
632501
  this.buildTelegramTool(context2, repoRoot, chatId, msg),
632000
632502
  ...this.buildTelegramReminderTools(context2, repoRoot, chatId, msg)
632001
632503
  ];
@@ -632061,6 +632563,7 @@ Scoped workspace: ${scopedRoot}`,
632061
632563
  new SkillListTool(repoRoot),
632062
632564
  new SkillExecuteTool(repoRoot),
632063
632565
  adminSkillExtractTool,
632566
+ this.buildTelegramReplyPreferenceTool(context2, chatId, msg),
632064
632567
  this.buildTelegramTool(context2, repoRoot, chatId, msg),
632065
632568
  ...this.buildTelegramReminderTools(context2, repoRoot, chatId, msg),
632066
632569
  fullSubAgentTool,
@@ -632318,8 +632821,9 @@ Scoped workspace: ${scopedRoot}`,
632318
632821
  const text = String(args["text"] || "").trim();
632319
632822
  if (!text) throw new Error("text is required for send_message.");
632320
632823
  if (dryRun) return this.telegramDryRun(action, targetChatId, { text });
632321
- const sent = await this.sendMessage(targetChatId, text);
632322
- return { action, telegram_method: "sendMessage", ok: true, chat_id: targetChatId, message_id: sent };
632824
+ const sent = await this.sendMessageDetailed(targetChatId, text);
632825
+ if (!sent.ok) throw new Error(sent.reason || `Telegram send_message ${sent.status}`);
632826
+ return { action, telegram_method: "sendMessage", ok: true, chat_id: targetChatId, message_id: sent.messageId ?? null };
632323
632827
  }
632324
632828
  case "edit_message_text": {
632325
632829
  if (targetChatId === void 0 || messageId === void 0) throw new Error("target/chat_id and message_id are required for edit_message_text.");
@@ -633233,6 +633737,64 @@ Scoped workspace: ${scopedRoot}`,
633233
633737
  resolveMedia: (ref, args) => this.resolveTelegramIdentityMedia(chatId, currentMsg, ref, args)
633234
633738
  });
633235
633739
  }
633740
+ buildTelegramReplyPreferenceTool(_context, _chatId, currentMsg) {
633741
+ const bridge = this;
633742
+ return {
633743
+ name: "telegram_preference_set",
633744
+ description: [
633745
+ "Store an explicitly stated Telegram reply-mode preference for the current user or group.",
633746
+ "Use only when the user directly expresses a durable preference; do not infer from style, tone, keywords, or task type.",
633747
+ "This stores internal behavior preferences only and does not send a Telegram message."
633748
+ ].join(" "),
633749
+ parameters: {
633750
+ type: "object",
633751
+ properties: {
633752
+ scope: {
633753
+ type: "string",
633754
+ enum: ["current_user_in_chat", "current_user_global", "current_group"],
633755
+ description: "Where to store the preference. Default current_user_in_chat for public/group contexts."
633756
+ },
633757
+ reply_mode: {
633758
+ type: "string",
633759
+ enum: ["reply_then_notes", "notes_then_reply", "reply_only"],
633760
+ description: "reply_then_notes = visible reply first, then internal notes; notes_then_reply = internal notes first; reply_only = one reply with tools/context and no extra internal notes stage."
633761
+ },
633762
+ confidence: { type: "number", description: "Confidence that the user explicitly expressed this durable preference, 0.0-1.0." },
633763
+ note: { type: "string", description: "Short evidence/rationale from the user's message." }
633764
+ },
633765
+ required: ["reply_mode"]
633766
+ },
633767
+ async execute(args) {
633768
+ const start2 = performance.now();
633769
+ if (!currentMsg) {
633770
+ return { success: false, output: "", error: "No current Telegram message is available for preference scope.", durationMs: performance.now() - start2 };
633771
+ }
633772
+ const replyMode = parseTelegramReplyMode(args["reply_mode"] ?? args["replyMode"] ?? args["mode"]);
633773
+ if (!replyMode) {
633774
+ return { success: false, output: "", error: "reply_mode must be reply_then_notes, notes_then_reply, or reply_only.", durationMs: performance.now() - start2 };
633775
+ }
633776
+ const scope = parseTelegramReplyPreferenceToolScope(args["scope"]) ?? "current_user_in_chat";
633777
+ const confidenceRaw = Number(args["confidence"]);
633778
+ const update2 = {
633779
+ scope,
633780
+ replyMode,
633781
+ confidence: Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(1, confidenceRaw)) : void 0,
633782
+ note: typeof args["note"] === "string" ? args["note"].trim().slice(0, 220) : void 0,
633783
+ source: "telegram_preference_set"
633784
+ };
633785
+ const sessionKey = bridge.sessionKeyForMessage(currentMsg);
633786
+ const stored = bridge.applyTelegramReplyPreferenceUpdate(sessionKey, currentMsg, update2, "telegram_preference_set");
633787
+ return {
633788
+ success: true,
633789
+ output: stored || `stored reply_mode=${replyMode}`,
633790
+ llmContent: stored || `Stored Telegram reply preference ${replyMode}.`,
633791
+ durationMs: performance.now() - start2,
633792
+ mutated: true,
633793
+ mutatedFiles: []
633794
+ };
633795
+ }
633796
+ };
633797
+ }
633236
633798
  buildTelegramMediaRecentTool(chatId, currentMsg) {
633237
633799
  const bridge = this;
633238
633800
  return {
@@ -633743,17 +634305,25 @@ ${text}`.trim());
633743
634305
  // ── Message sending ───────────────────────────────────────────────────
633744
634306
  /** Send a response back to a Telegram chat (Markdown → HTML conversion) */
633745
634307
  async sendMessage(chatId, text, replyToMessageId, options2 = {}) {
634308
+ const result = await this.sendMessageDetailed(chatId, text, replyToMessageId, options2);
634309
+ return result.messageId ?? null;
634310
+ }
634311
+ async sendMessageDetailed(chatId, text, replyToMessageId, options2 = {}) {
633746
634312
  const extracted = extractMediaReferences(text);
633747
634313
  const mediaRefs = this.filterTelegramMediaReferences(extracted.media, options2);
633748
634314
  const html = convertMarkdownToTelegramHTML(extracted.text);
633749
- const msgId = extracted.text.trim() ? await this.sendMessageHTML(chatId, html, replyToMessageId, options2) : null;
634315
+ const textResult = extracted.text.trim() ? await this.sendMessageHTMLDetailed(chatId, html, replyToMessageId, options2) : { ok: true, status: "sent", chatId, messageId: null };
633750
634316
  for (const media of mediaRefs) {
633751
634317
  await this.sendMediaReference(chatId, media, { replyToMessageId }).catch(() => null);
633752
634318
  }
633753
- return msgId;
634319
+ return textResult;
633754
634320
  }
633755
634321
  /** Send an HTML-formatted message to a Telegram chat */
633756
634322
  async sendMessageHTML(chatId, html, replyToMessageId, options2 = {}) {
634323
+ const result = await this.sendMessageHTMLDetailed(chatId, html, replyToMessageId, options2);
634324
+ return result.messageId ?? null;
634325
+ }
634326
+ async sendMessageHTMLDetailed(chatId, html, replyToMessageId, options2 = {}) {
633757
634327
  const extracted = extractMediaReferences(html);
633758
634328
  const mediaRefs = this.filterTelegramMediaReferences(extracted.media, options2);
633759
634329
  const sendHtml = extracted.text || (extracted.media.length > 0 ? "" : html);
@@ -633768,7 +634338,7 @@ ${text}`.trim());
633768
634338
  ).catch(() => null);
633769
634339
  if (sentId === null) sentId = mediaId;
633770
634340
  }
633771
- return sentId;
634341
+ return { ok: sentId !== null || mediaRefs.length === 0, status: "sent", chatId, messageId: sentId };
633772
634342
  }
633773
634343
  const chunks = splitTelegramMessageText(sendHtml, 3900);
633774
634344
  for (let idx = 0; idx < chunks.length; idx++) {
@@ -633786,6 +634356,11 @@ ${text}`.trim());
633786
634356
  if (result.ok === false) throw new Error(String(result.description || "Telegram sendMessage failed"));
633787
634357
  this.state.messagesSent++;
633788
634358
  if (sentId === null) sentId = result.result?.message_id ?? null;
634359
+ this.updateTelegramTextDeliveryCapability(chatId, {
634360
+ status: "allowed",
634361
+ canSendText: true,
634362
+ source: "sendMessage"
634363
+ });
633789
634364
  getSoulObservationStream().emit({
633790
634365
  kind: "telegram.send.success",
633791
634366
  sessionKey: sessionKeyForObs,
@@ -633801,6 +634376,11 @@ ${text}`.trim());
633801
634376
  if (result.ok === false) throw new Error(String(result.description || "Telegram sendMessage failed"));
633802
634377
  this.state.messagesSent++;
633803
634378
  if (sentId === null) sentId = result.result?.message_id ?? null;
634379
+ this.updateTelegramTextDeliveryCapability(chatId, {
634380
+ status: "allowed",
634381
+ canSendText: true,
634382
+ source: "sendMessage.plain_fallback"
634383
+ });
633804
634384
  getSoulObservationStream().emit({
633805
634385
  kind: "telegram.send.success",
633806
634386
  sessionKey: sessionKeyForObs,
@@ -633810,30 +634390,25 @@ ${text}`.trim());
633810
634390
  } catch (err) {
633811
634391
  this.tuiWrite(() => renderWarning(`Failed to send Telegram message: ${err instanceof Error ? err.message : String(err)}`));
633812
634392
  const errStr = err instanceof Error ? err.message : String(err);
633813
- const lc = errStr.toLowerCase();
633814
- if (/(not enough rights|forbidden|chat_write_forbidden|user_banned|kicked|chat_admin_required)/.test(lc)) {
633815
- getSoulObservationStream().emit({
633816
- kind: "telegram.send.forbidden",
633817
- sessionKey: sessionKeyForObs,
633818
- reason: errStr,
633819
- ts: Date.now()
633820
- });
633821
- } else if (/too many requests|retry after/.test(lc)) {
633822
- const m2 = lc.match(/retry after (\d+)/);
633823
- getSoulObservationStream().emit({
633824
- kind: "telegram.send.rate_limited",
633825
- sessionKey: sessionKeyForObs,
633826
- retryAfterSec: m2 ? parseInt(m2[1], 10) : void 0,
633827
- ts: Date.now()
633828
- });
633829
- }
634393
+ this.updateTelegramTextDeliveryCapability(chatId, {
634394
+ status: "failed",
634395
+ reason: errStr,
634396
+ source: "sendMessage"
634397
+ });
634398
+ getSoulObservationStream().emit({
634399
+ kind: "telegram.send.failed",
634400
+ sessionKey: sessionKeyForObs,
634401
+ reason: errStr,
634402
+ ts: Date.now()
634403
+ });
634404
+ return { ok: false, status: "failed", chatId, messageId: sentId, reason: errStr };
633830
634405
  }
633831
634406
  }
633832
634407
  }
633833
634408
  for (const media of mediaRefs) {
633834
634409
  await this.sendMediaReference(chatId, media).catch(() => null);
633835
634410
  }
633836
- return sentId;
634411
+ return { ok: sentId !== null, status: "sent", chatId, messageId: sentId };
633837
634412
  }
633838
634413
  filterTelegramMediaReferences(media, options2) {
633839
634414
  const suppress = options2.suppressMedia;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.157",
3
+ "version": "1.0.158",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.157",
9
+ "version": "1.0.158",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.157",
3
+ "version": "1.0.158",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",