pi-braid-telegram 0.0.0-stage → 0.1.5

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.
@@ -0,0 +1,110 @@
1
+ # Turn-Complete Auto-Notify: Design Decisions
2
+
3
+ <!-- status: current -->
4
+
5
+ Status: **pending owner sign-off**. This list is the deliverable-1 surface; the
6
+ fixture tests in `packages/telegram/test/notify.test.mjs` pin the implemented
7
+ defaults. Any owner change to a default below is a config/env change, not code.
8
+
9
+ ## Feature recap
10
+
11
+ When the agent finishes a turn with a completed plain-text answer (no trailing
12
+ tool call), a SECOND LLM call (the "crafting model") distills the answer into
13
+ an owner update capped at `braid.telegram.notifyMaxChars` (default 1200) and
14
+ sends it over Telegram through the SAME
15
+ outbound seam as `telegram_send` / `/telegram send`. The `[tg-end]` marker is
16
+ an INBOUND parse boundary only and never appears on outbound.
17
+
18
+ ## Decision 1: which turns notify
19
+
20
+ **Implemented: completed-answer turns only.**
21
+ `shouldNotifyTurnEnd(event)` notifies iff the `turn_end` message is an assistant
22
+ message with non-empty text AND `stopReason !== "toolUse"`. A `toolUse`
23
+ stopReason means the model's last action was a trailing tool call (work
24
+ continues next turn) → no notify. This satisfies the brief's "NO crafting when
25
+ last tool call trailing".
26
+ Rejected alternatives:
27
+
28
+ - _Always_ (every turn, including mid-work tool turns) - would spam the owner.
29
+ - _Quiet-window_ (only during owner-defined quiet hours) - no owner-supplied
30
+ quiet-hours config exists; OBE once the owner defines one.
31
+ - _Per-session opt-in_ (agent decides each session) - the agent is the one
32
+ being notified about; it should not be the gatekeeper.
33
+
34
+ ## Decision 2: opt-in vs always-on
35
+
36
+ **Implemented: opt-in via `BRAID_TG_NOTIFY_ENABLED=1` (default OFF).**
37
+ No unsolicited messages until the owner enables it. The telegram extension
38
+ already requires `BRAID_TG_BOT_TOKEN`, so this is the only extra flag. When the
39
+ flag is off, the `turn_end` listener is NOT registered at all (verified by
40
+ `test/index.test.mjs`).
41
+
42
+ ## Decision 3: utility vs max-power model
43
+
44
+ **Implemented: session model by default, env override wins.**
45
+ `resolveNotifyModel()` (notify/model.ts): `BRAID_TG_NOTIFY_MODEL`
46
+ (`provider/model-id`) → else `ctx.model` (session model) → else first available
47
+ registry model. The call is key-ring-backed: the API key comes from
48
+ `ctx.modelRegistry.getApiKeyForProvider(provider)`, which
49
+ pi-braid-providers wires to the key-ring (`ring-lifecycle.ts` auth.resolve →
50
+ `getActiveKey`) - never the bot token.
51
+ Note: core's `/utility-model` role key (`/max-power-model` is a deprecated
52
+ alias of it) lives in core's global-state (unreachable from the standalone
53
+ telegram package without a cross-package import). The env override is the
54
+ telegram-native equivalent.
55
+ Owner call: if honoring the /utility-model role key is required, that needs the
56
+ core-extension placement (Architecture B) instead - documented as a follow-up.
57
+
58
+ ## Decision 4: rate limit
59
+
60
+ **Implemented: sliding window, max 4/min by default.**
61
+ `SlidingWindowLimiter` (notify/rate-limit.ts), override via
62
+ `BRAID_TG_NOTIFY_MAX_PER_MIN`. Deterministic by explicit-timestamp `try(now)`.
63
+ Sliding window (vs fixed minute) so a turn at 59.9s does not immediately reset.
64
+
65
+ ## Decision 5: content template
66
+
67
+ **Implemented: distilling prompt** (`buildNotifyPrompts` in notify/craft.ts):
68
+ "state what was done / key result, ≤ `braid.telegram.notifyMaxChars` chars
69
+ (default 1200), plain text, no greetings/no
70
+ sign-off/no markdown; reply DONE if no actionable content." The crafting model
71
+ produces the message; `truncateGuard` (same cap, ellipsis) is the guard-fallback
72
+ when crafting fails or returns something malformed. The model's output budget
73
+ scales with the cap (`craftMaxTokensFor`) so a long target is not cut mid-sentence.
74
+
75
+ ## Decision 6: telegram_send / /telegram send reuse the same seam
76
+
77
+ `runTelegramSend` gained an optional `craft` hook. When text exceeds the
78
+ settings-driven send cap (`braid.telegram.sendMaxChars`, default 4096), the
79
+ crafting model produces the message (replacing the old dumb truncation);
80
+ the cap stays as the guard threshold. Short text skips crafting
81
+ (no model call). `/telegram send` now routes through `runTelegramSend` -
82
+ the exact same outbound path - so crafting, cap fallback, and
83
+ no-[tg-end] behavior are shared.
84
+
85
+ ## Placement decision (why telegram, not a new core extension)
86
+
87
+ The brief leaned "a new small extension under packages/core/src/extensions/"
88
+ but allowed "or a new extension" with "read how existing extensions register
89
+ listeners and mirror it". Chosen: the notify feature lives in the TELEGRAM
90
+ package because:
91
+
92
+ - It owns the outbound seam + client/token/chatId - deliverable 3 ("same
93
+ outbound seam") is literal, with zero cross-package coupling.
94
+ - It already registers pi.on listeners in its factory (16f pattern); a
95
+ `turn_end` listener mirrors it exactly.
96
+ - The alternative (core notify extension + delivery sink registered by
97
+ telegram) requires a telegram→core import. It works today via the workspace
98
+ symlink but is a phantom dependency and makes the telegram test chain depend
99
+ on core's dist being freshly built - fragile under the "no package-lock diff"
100
+ gate. Avoided.
101
+ - The crafting LLM call reuses "our systems": `completeSimple` from
102
+ `@earendil-works/pi-ai/compat` (the exact path core's llm-call.ts wraps at
103
+ llm-call.ts:50) + `ctx.modelRegistry` for model + key-ring key.
104
+
105
+ ## Forbidden-files check
106
+
107
+ - No packages/providers source changes by this task (a pre-existing,
108
+ unrelated working-tree modification was left untouched).
109
+ - No package-lock.json diff, no npm install, no git checkout/reset/stash.
110
+ - No changes to packages/core or packages/providers source.
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "pi-braid-telegram",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.5",
4
+ "description": "Bidirectional Telegram channel for pi-braid: telegram_send tool (outbound) + inbound long-poll injector (backward channel)",
5
+ "type": "module",
6
+ "files": [
7
+ "src/",
8
+ "docs/",
9
+ "README.md",
10
+ "LICENSE",
11
+ "build-info.json"
12
+ ],
13
+ "exports": {
14
+ ".": "./src/index.ts"
15
+ },
16
+ "keywords": [
17
+ "pi-package",
18
+ "pi",
19
+ "pi-coding-agent",
20
+ "bot",
21
+ "extension",
22
+ "telegram",
23
+ "channel",
24
+ "messaging"
25
+ ],
26
+ "pi": {
27
+ "extensions": [
28
+ "./src/index.ts"
29
+ ]
30
+ },
31
+ "scripts": {
32
+ "prepublishOnly": "npm run typecheck",
33
+ "typecheck": "tsc --noEmit",
34
+ "build": "tsc -p tsconfig.build.json",
35
+ "test": "node test/run-tests.mjs",
36
+ "check:doc-index": "node ../../packages/core/scripts/check-doc-index.mjs",
37
+ "publish:local": "node ../../packages/core/scripts/publish-local.mjs telegram",
38
+ "prepack": "npm run build && node ../../packages/core/scripts/pack-provenance.mjs write",
39
+ "postpack": "node ../../packages/core/scripts/pack-provenance.mjs remove"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "6.0.2",
43
+ "typebox": "1.1.38"
44
+ },
45
+ "peerDependencies": {
46
+ "@earendil-works/pi-ai": "^1.2.0",
47
+ "@earendil-works/pi-coding-agent": "^1.2.0",
48
+ "typebox": "^1.1.38"
49
+ },
50
+ "author": "kifirkin",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/kifirkin/pi-braid.git",
55
+ "directory": "packages/telegram"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/kifirkin/pi-braid/issues"
59
+ },
60
+ "homepage": "https://github.com/kifirkin/pi-braid/tree/main/packages/telegram",
61
+ "engines": {
62
+ "node": ">=22.19.0"
63
+ }
64
+ }