instar 0.28.73 → 0.28.74
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +51 -23
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SessionManager.d.ts +4 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +15 -4
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/types.d.ts +15 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +8 -0
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +31 -1
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +28 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +79 -1
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/TelegramMarkdownFormatter.d.ts +76 -0
- package/dist/messaging/TelegramMarkdownFormatter.d.ts.map +1 -0
- package/dist/messaging/TelegramMarkdownFormatter.js +493 -0
- package/dist/messaging/TelegramMarkdownFormatter.js.map +1 -0
- package/dist/messaging/imessage/IMessageAdapter.d.ts +3 -6
- package/dist/messaging/imessage/IMessageAdapter.d.ts.map +1 -1
- package/dist/messaging/imessage/IMessageAdapter.js +20 -21
- package/dist/messaging/imessage/IMessageAdapter.js.map +1 -1
- package/dist/messaging/imessage/NativeBackend.d.ts +26 -0
- package/dist/messaging/imessage/NativeBackend.d.ts.map +1 -1
- package/dist/messaging/imessage/NativeBackend.js +133 -0
- package/dist/messaging/imessage/NativeBackend.js.map +1 -1
- package/dist/messaging/telegramFormatMetrics.d.ts +22 -0
- package/dist/messaging/telegramFormatMetrics.d.ts.map +1 -0
- package/dist/messaging/telegramFormatMetrics.js +38 -0
- package/dist/messaging/telegramFormatMetrics.js.map +1 -0
- package/dist/messaging/types.d.ts +7 -0
- package/dist/messaging/types.d.ts.map +1 -1
- package/dist/messaging/types.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +5 -5
- package/src/templates/scripts/telegram-reply.sh +52 -8
- package/upgrades/0.28.74.md +48 -0
- package/upgrades/side-effects/telegram-markdown-renderer-pr1.md +124 -0
- /package/upgrades/side-effects/{capability-map-user-provenance-fallback.md → 0.28.73.md} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-04-
|
|
5
|
-
"instarVersion": "0.28.
|
|
4
|
+
"generatedAt": "2026-04-25T00:12:18.203Z",
|
|
5
|
+
"instarVersion": "0.28.74",
|
|
6
6
|
"entryCount": 186,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -1112,7 +1112,7 @@
|
|
|
1112
1112
|
"type": "template",
|
|
1113
1113
|
"domain": "operations",
|
|
1114
1114
|
"sourcePath": "src/templates/scripts/telegram-reply.sh",
|
|
1115
|
-
"contentHash": "
|
|
1115
|
+
"contentHash": "3d08c63c6280d0a7ba94a345c259673a461ee5c1d116cb47c95c7626c67cee23",
|
|
1116
1116
|
"since": "2025-01-01"
|
|
1117
1117
|
},
|
|
1118
1118
|
"template:whatsapp-reply.sh": {
|
|
@@ -1408,7 +1408,7 @@
|
|
|
1408
1408
|
"type": "subsystem",
|
|
1409
1409
|
"domain": "sessions",
|
|
1410
1410
|
"sourcePath": "src/core/SessionManager.ts",
|
|
1411
|
-
"contentHash": "
|
|
1411
|
+
"contentHash": "b47fdc0040c512bada61574b4fd1505da3c9bbe2adc793915d6f5bd479faad06",
|
|
1412
1412
|
"since": "2025-01-01"
|
|
1413
1413
|
},
|
|
1414
1414
|
"subsystem:auto-updater": {
|
|
@@ -1464,7 +1464,7 @@
|
|
|
1464
1464
|
"type": "subsystem",
|
|
1465
1465
|
"domain": "communication",
|
|
1466
1466
|
"sourcePath": "src/lifeline/TelegramLifeline.ts",
|
|
1467
|
-
"contentHash": "
|
|
1467
|
+
"contentHash": "22d7d3827ba85d345862e55457fd2cca83e4f38201da61e20a7f0e539052322b",
|
|
1468
1468
|
"since": "2025-01-01"
|
|
1469
1469
|
},
|
|
1470
1470
|
"subsystem:orphan-process-reaper": {
|
|
@@ -3,18 +3,52 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Usage:
|
|
5
5
|
# ./telegram-reply.sh TOPIC_ID "message text"
|
|
6
|
+
# ./telegram-reply.sh --format markdown TOPIC_ID "**bold**"
|
|
6
7
|
# echo "message text" | ./telegram-reply.sh TOPIC_ID
|
|
7
8
|
# cat <<'EOF' | ./telegram-reply.sh TOPIC_ID
|
|
8
9
|
# Multi-line message here
|
|
9
10
|
# EOF
|
|
10
11
|
#
|
|
12
|
+
# Flags:
|
|
13
|
+
# --format <mode> Override server-side format mode for this send.
|
|
14
|
+
# Valid: plain, code, markdown, legacy-passthrough
|
|
15
|
+
# ('html' is reserved for trusted internal callers.)
|
|
16
|
+
# When absent, the server's configured default applies.
|
|
17
|
+
#
|
|
11
18
|
# Reads INSTAR_PORT from environment (default: 4040).
|
|
12
19
|
|
|
20
|
+
FORMAT=""
|
|
21
|
+
|
|
22
|
+
# Parse leading flags before positional args.
|
|
23
|
+
while [ $# -gt 0 ]; do
|
|
24
|
+
case "$1" in
|
|
25
|
+
--format)
|
|
26
|
+
FORMAT="$2"
|
|
27
|
+
shift 2
|
|
28
|
+
;;
|
|
29
|
+
--format=*)
|
|
30
|
+
FORMAT="${1#--format=}"
|
|
31
|
+
shift
|
|
32
|
+
;;
|
|
33
|
+
--)
|
|
34
|
+
shift
|
|
35
|
+
break
|
|
36
|
+
;;
|
|
37
|
+
-*)
|
|
38
|
+
echo "Unknown flag: $1" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
break
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
13
47
|
TOPIC_ID="$1"
|
|
14
48
|
shift
|
|
15
49
|
|
|
16
50
|
if [ -z "$TOPIC_ID" ]; then
|
|
17
|
-
echo "Usage: telegram-reply.sh TOPIC_ID [message]" >&2
|
|
51
|
+
echo "Usage: telegram-reply.sh [--format MODE] TOPIC_ID [message]" >&2
|
|
18
52
|
exit 1
|
|
19
53
|
fi
|
|
20
54
|
|
|
@@ -38,22 +72,32 @@ if [ -f ".instar/config.json" ]; then
|
|
|
38
72
|
AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null)
|
|
39
73
|
fi
|
|
40
74
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
75
|
+
# Build JSON body (text + optional format).
|
|
76
|
+
JSON_BODY=$(python3 -c '
|
|
77
|
+
import sys, json
|
|
78
|
+
msg = sys.argv[1]
|
|
79
|
+
fmt = sys.argv[2]
|
|
80
|
+
body = {"text": msg}
|
|
81
|
+
if fmt:
|
|
82
|
+
body["format"] = fmt
|
|
83
|
+
print(json.dumps(body))
|
|
84
|
+
' "$MSG" "$FORMAT" 2>/dev/null)
|
|
85
|
+
|
|
86
|
+
if [ -z "$JSON_BODY" ]; then
|
|
87
|
+
# Fallback if python3 not available: basic escape, no format override.
|
|
88
|
+
ESCAPED=$(printf '%s' "$MSG" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\n/\\n/g')
|
|
89
|
+
JSON_BODY="{\"text\":\"${ESCAPED}\"}"
|
|
46
90
|
fi
|
|
47
91
|
|
|
48
92
|
if [ -n "$AUTH_TOKEN" ]; then
|
|
49
93
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "http://localhost:${PORT}/telegram/reply/${TOPIC_ID}" \
|
|
50
94
|
-H 'Content-Type: application/json' \
|
|
51
95
|
-H "Authorization: Bearer ${AUTH_TOKEN}" \
|
|
52
|
-
-d "
|
|
96
|
+
-d "$JSON_BODY")
|
|
53
97
|
else
|
|
54
98
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "http://localhost:${PORT}/telegram/reply/${TOPIC_ID}" \
|
|
55
99
|
-H 'Content-Type: application/json' \
|
|
56
|
-
-d "
|
|
100
|
+
-d "$JSON_BODY")
|
|
57
101
|
fi
|
|
58
102
|
|
|
59
103
|
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Upgrade Guide — v0.28.74
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
The scope-coherence stop hook (`scope-coherence-checkpoint.js`) previously
|
|
8
|
+
suggested running `/grounding` as the only recovery path when the
|
|
9
|
+
implementation-depth threshold was crossed. `/grounding` is a Claude-Code-side
|
|
10
|
+
skill convention; Instar does not ship it, so agents whose harness lacks that
|
|
11
|
+
skill saw a dead-end suggestion with no working manual reset path.
|
|
12
|
+
|
|
13
|
+
The checkpoint message now lists three real options: read the relevant
|
|
14
|
+
spec/proposal, invoke `/grounding` if the harness has it, or reset directly via
|
|
15
|
+
the local Instar server's reset endpoint. The endpoint already exists and is
|
|
16
|
+
what `session-start.sh` invokes automatically — this release just surfaces it
|
|
17
|
+
as a manual fallback in the message agents see when they hit the checkpoint.
|
|
18
|
+
|
|
19
|
+
No code paths changed beyond the human-readable message string. The reset
|
|
20
|
+
endpoint and the depth-accumulation logic are unchanged.
|
|
21
|
+
|
|
22
|
+
## What to Tell Your User
|
|
23
|
+
|
|
24
|
+
- The scope-coherence checkpoint now points to a manual reset path that always works, so if the suggested skill is not installed in your setup you still have a one-line recovery I can run for you.
|
|
25
|
+
|
|
26
|
+
## Summary of New Capabilities
|
|
27
|
+
|
|
28
|
+
| Capability | How to Use |
|
|
29
|
+
|-----------|-----------|
|
|
30
|
+
| Manual scope-coherence reset surfaced in checkpoint message | `curl -X POST http://localhost:4040/scope-coherence/reset` |
|
|
31
|
+
|
|
32
|
+
## Evidence
|
|
33
|
+
|
|
34
|
+
Cluster `cluster-scope-coherence-hook-counter-never-resets-across-sessions`
|
|
35
|
+
flagged that `/grounding` is not a registered skill or command in Instar and
|
|
36
|
+
that there is no user-facing reset path shown in the checkpoint message.
|
|
37
|
+
|
|
38
|
+
Verification: `ls skills/` confirms no `grounding` skill ships with Instar.
|
|
39
|
+
The reset endpoint is implemented at `src/server/routes.ts:2925` and is
|
|
40
|
+
invoked by `.instar/hooks/instar/session-start.sh` when the local server is
|
|
41
|
+
healthy — so the underlying counter reset already works on every session
|
|
42
|
+
start; this release makes the manual fallback path discoverable from the
|
|
43
|
+
hook's own message.
|
|
44
|
+
|
|
45
|
+
Reproduction: trigger the checkpoint (implementation depth ≥ 20, cooldown
|
|
46
|
+
elapsed, server running) and inspect the `reason` field in the hook's
|
|
47
|
+
output. Before: ends with `or /grounding`. After: ends with the three-option
|
|
48
|
+
block including the explicit curl command for manual reset.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Side-Effects Review — Telegram markdown renderer (PR1: formatter module, disabled)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `telegram-markdown-renderer-pr1`
|
|
4
|
+
**Date:** `2026-04-24`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required (no block/allow surface, no wiring into send paths, shipped disabled)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
PR1 of the two-PR plan in `docs/specs/TELEGRAM-MARKDOWN-RENDERER-SPEC.md` (approved 2026-04-24 by Justin via Telegram topic 8183). Adds a new pure-function module `src/messaging/TelegramMarkdownFormatter.ts` that ports Dawn's `telegram_format.py` (the-portal) to TypeScript, plus a Vitest test suite `tests/unit/telegram-markdown-formatter.test.ts` with 105 passing tests.
|
|
11
|
+
|
|
12
|
+
The module exports `formatForTelegram(text, mode)`, `format(text, mode)`, `lintTelegramMarkdown(text)`, and primitives (`escapeHtmlText`, `escapeHtmlAttribute`, `isSafeUrl`). It implements the spec's full 12-step markdown→Telegram-HTML pipeline, a balanced-paren URL scanner (Wikipedia-style parens), WHATWG URL scheme allowlist, distinct HTML-text vs HTML-attribute escapers, NUL + Supplementary-PUA-B stripping for sentinel-collision safety, a 32KB input guard that falls back to plain mode without byte loss (`conversionSkipped: true`), and a `legacy-passthrough` mode that returns input byte-for-byte unchanged with `parseMode: undefined` so callers retain their historical `parse_mode`.
|
|
13
|
+
|
|
14
|
+
**Critically, no send path is wired to this module.** `TelegramAdapter.ts` and `TelegramLifeline.ts` are untouched. The module is dead code at runtime until PR2 wires `apiCall()` behind the `telegramFormatMode` config accessor. PR2 ships with `legacy-passthrough` as the default, so PR1 has zero behavioral effect on any agent even after PR2 merges.
|
|
15
|
+
|
|
16
|
+
## Decision-point inventory
|
|
17
|
+
|
|
18
|
+
This PR contains no runtime decision points — the module is not called by anything. The future decision points it enables (documented here for completeness; all are PR2 scope):
|
|
19
|
+
|
|
20
|
+
- `TelegramAdapter.apiCall` — formatter invocation conditional on `method === 'sendMessage' || method === 'editMessageText'`. **Not in this PR.**
|
|
21
|
+
- `TelegramLifeline.apiCall` — same. **Not in this PR.**
|
|
22
|
+
- Trusted-internal-caller allowlist for `html` mode. **Not in this PR.**
|
|
23
|
+
|
|
24
|
+
Decision points inside the module itself (pure functions, no runtime authority):
|
|
25
|
+
|
|
26
|
+
- `isSafeUrl(raw)` — scheme allowlist (http/https/tg/mailto). Pure function; refuses unsafe schemes by returning `null`. Caller decides what to do with the rejection (emit literal text).
|
|
27
|
+
- `lintTelegramMarkdown(text)` — pure detector returning string array. Advisory-only; no blocking authority in this PR.
|
|
28
|
+
- 32KB length guard in `formatForTelegram` — falls back to plain mode on oversized input. Pure transformation; no network/storage side effect.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Over-block
|
|
33
|
+
|
|
34
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
35
|
+
|
|
36
|
+
No block/allow surface in PR1 — module is not wired. Future-facing concerns the module itself introduces:
|
|
37
|
+
|
|
38
|
+
- `legacy-passthrough` mode emits no lint issues (by design — it's the rollback target and must be behaviorally identical to pre-cutover). A future operator might want lint-as-observation in passthrough mode; noted as open question for PR2.
|
|
39
|
+
- `isSafeUrl` rejects `javascript:`, `data:`, `file:`, `vbscript:`, and any non-allowlisted scheme. Rejected links become literal text (`[click](javascript:...)` rendered verbatim) — a legitimate agent-authored `tg://resolve?domain=foo` link is permitted (mode allowed). Wikipedia-style `https://...wiki/X_(y)` URLs parse correctly via the balanced-paren scanner (covered by fixture test).
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Under-block
|
|
44
|
+
|
|
45
|
+
**What failure modes does this still miss?**
|
|
46
|
+
|
|
47
|
+
No block/allow surface in PR1. Module-internal known gaps (documented in spec, accepted as v1):
|
|
48
|
+
|
|
49
|
+
- Italic between emoji (`🎉*bold*🎉`) renders literal asterisks because the punctuation-class lookaround doesn't include emoji — accepted v1 per spec iteration-2 L4.
|
|
50
|
+
- `tg://` deep links are permitted (spec allowlists them); future spec may tighten.
|
|
51
|
+
- PUA-B range is stripped from input before sentinels are inserted — this means a user message legitimately containing PUA-B codepoints will lose those bytes. Near-zero real-world usage; accepted trade-off per spec. Not flagged as `truncated` because "byte loss" in the spec is reserved for the oversized-`<pre>` fallback.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 3. Level-of-abstraction fit
|
|
56
|
+
|
|
57
|
+
**Is this at the right layer?**
|
|
58
|
+
|
|
59
|
+
Yes. The module is a pure string-transform library at the same layer as `MessageFormatter.ts`. It owns no I/O, no config reading, no network, no logging. It does not hold blocking authority. The decision "should this send happen at all?" stays with the adapter/lifeline chokepoints; the formatter only answers "given this text and mode, what bytes go on the wire?"
|
|
60
|
+
|
|
61
|
+
This is consistent with the spec's architectural intent: the formatter is a detector+transformer producing `{ text, parseMode, lintIssues }`, consumed downstream by the adapter which holds the authoritative send/no-send decision.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 4. Signal vs authority compliance
|
|
66
|
+
|
|
67
|
+
Per `docs/signal-vs-authority.md`:
|
|
68
|
+
|
|
69
|
+
- **Lint is a signal, not an authority.** `lintTelegramMarkdown` returns canonical prose-string issues. It does not reject a send. The spec specifies lint-strict mode (which a downstream authority can consume to return 422) as PR2 scope; PR1's lint is purely observational.
|
|
70
|
+
- **Scheme allowlist is a deterministic transform, not a policy decision.** `isSafeUrl` is brittle-by-design (WHATWG URL + scheme set). It does not block a send — it causes a link to render as literal text. The outbound send still happens. This satisfies the "brittle logic must not hold blocking authority" rule: brittle logic (regex/parse) produces a transformation, not a decision to refuse a user action.
|
|
71
|
+
- **No sentinel, gate, watchdog, or sentinel-class name is introduced by this PR.**
|
|
72
|
+
|
|
73
|
+
Compliance: PASS.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 5. Interactions
|
|
78
|
+
|
|
79
|
+
- No shadow/shadowed interactions in PR1 — module is not called by anything. Search confirms no existing import site.
|
|
80
|
+
- Module name collision check: `src/messaging/` contains `MessageFormatter.ts` (different purpose: envelope formatting) and no `TelegramMarkdownFormatter.ts` prior to this PR. No collision.
|
|
81
|
+
- Test file name: `tests/unit/telegram-markdown-formatter.test.ts`. No existing test file of that name.
|
|
82
|
+
- PR2 will introduce interactions with `TelegramAdapter.apiCall`, `TelegramLifeline.apiCall`, `MessageStore` (raw/sent fields), and `GitSyncTransport` (envelope flag). Those interactions get their own artifact at PR2 time.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 6. External surfaces
|
|
87
|
+
|
|
88
|
+
**Does it change anything visible to other agents, other users, other systems?**
|
|
89
|
+
|
|
90
|
+
No. PR1 ships dead code. No route changes, no message format changes on the wire, no config schema changes applied (config fields `telegramFormatMode` / `telegramLintStrict` are spec'd but deliberately not added in PR1 — they land in PR2 alongside the wiring), no database changes, no envelope changes.
|
|
91
|
+
|
|
92
|
+
- Bot API: untouched — adapter still sends exactly the bytes it sends today with exactly the `parse_mode` it uses today.
|
|
93
|
+
- MessageStore: untouched.
|
|
94
|
+
- GitSyncTransport: untouched.
|
|
95
|
+
- Shell-script `.claude/scripts/telegram-reply.sh`: untouched.
|
|
96
|
+
- Agent dashboards / self-knowledge: untouched.
|
|
97
|
+
|
|
98
|
+
No timing dependencies, no conversation state dependencies.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 7. Rollback cost
|
|
103
|
+
|
|
104
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
105
|
+
|
|
106
|
+
Trivial. PR1 is a code-only addition with no runtime invocation. Rollback options in order of cost:
|
|
107
|
+
|
|
108
|
+
1. **Do nothing.** The module is not on any call path; a latent bug is invisible until PR2.
|
|
109
|
+
2. **Revert the merge commit.** Standard `git revert` — affects only two new files. No data migration, no agent state repair, no config flip.
|
|
110
|
+
3. **Fix forward.** The module is pure functions with 105 unit tests; most fixes ship as deltas.
|
|
111
|
+
|
|
112
|
+
PR2 itself is governed by the spec's staged-rollout (ships with `legacy-passthrough` default; canary via config flip). That's PR2's rollback story — out of scope for this artifact.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Second-pass review
|
|
117
|
+
|
|
118
|
+
Not required. PR1 has no block/allow surface, no session-lifecycle surface, no sentinel/gate/watchdog, no coherence/idempotency/trust change, and is shipped disabled. High-risk criteria from Phase 5 are not met.
|
|
119
|
+
|
|
120
|
+
## Spec linkage
|
|
121
|
+
|
|
122
|
+
- Approved spec: `docs/specs/TELEGRAM-MARKDOWN-RENDERER-SPEC.md` (approved 2026-04-24T20:53:47Z by Justin via Telegram topic 8183).
|
|
123
|
+
- Convergence report: `docs/specs/reports/telegram-markdown-renderer-convergence.md`.
|
|
124
|
+
- Dawn reference impl: `the-portal/.claude/scripts/telegram_format.py` (merged 2026-04-24T17:57Z).
|
|
File without changes
|