@a1hvdy/cc-openclaw 0.24.0 → 0.25.1
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/src/channels/telegram-mirror/burst-accumulator.d.ts +96 -0
- package/dist/src/channels/telegram-mirror/burst-accumulator.js +130 -0
- package/dist/src/channels/telegram-mirror/burst-accumulator.js.map +1 -0
- package/dist/src/channels/telegram-mirror/callback-mapping.d.ts +61 -0
- package/dist/src/channels/telegram-mirror/callback-mapping.js +99 -0
- package/dist/src/channels/telegram-mirror/callback-mapping.js.map +1 -0
- package/dist/src/channels/telegram-mirror/card-renderer.d.ts +74 -0
- package/dist/src/channels/telegram-mirror/card-renderer.js +131 -0
- package/dist/src/channels/telegram-mirror/card-renderer.js.map +1 -0
- package/dist/src/channels/telegram-mirror/commands.d.ts +142 -0
- package/dist/src/channels/telegram-mirror/commands.js +389 -0
- package/dist/src/channels/telegram-mirror/commands.js.map +1 -0
- package/dist/src/channels/telegram-mirror/compose-buffer.d.ts +71 -0
- package/dist/src/channels/telegram-mirror/compose-buffer.js +110 -0
- package/dist/src/channels/telegram-mirror/compose-buffer.js.map +1 -0
- package/dist/src/channels/telegram-mirror/cost-views.d.ts +58 -0
- package/dist/src/channels/telegram-mirror/cost-views.js +121 -0
- package/dist/src/channels/telegram-mirror/cost-views.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.d.ts +21 -0
- package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.js +30 -0
- package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/gateway-down.d.ts +15 -0
- package/dist/src/channels/telegram-mirror/failure/gateway-down.js +27 -0
- package/dist/src/channels/telegram-mirror/failure/gateway-down.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.d.ts +15 -0
- package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.js +27 -0
- package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/index.d.ts +23 -0
- package/dist/src/channels/telegram-mirror/failure/index.js +35 -0
- package/dist/src/channels/telegram-mirror/failure/index.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/model-5xx.d.ts +16 -0
- package/dist/src/channels/telegram-mirror/failure/model-5xx.js +24 -0
- package/dist/src/channels/telegram-mirror/failure/model-5xx.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/network-blip.d.ts +17 -0
- package/dist/src/channels/telegram-mirror/failure/network-blip.js +25 -0
- package/dist/src/channels/telegram-mirror/failure/network-blip.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.d.ts +15 -0
- package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.js +24 -0
- package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/rate-limit.d.ts +16 -0
- package/dist/src/channels/telegram-mirror/failure/rate-limit.js +26 -0
- package/dist/src/channels/telegram-mirror/failure/rate-limit.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/returning-after-24h.d.ts +14 -0
- package/dist/src/channels/telegram-mirror/failure/returning-after-24h.js +33 -0
- package/dist/src/channels/telegram-mirror/failure/returning-after-24h.js.map +1 -0
- package/dist/src/channels/telegram-mirror/failure/types.d.ts +30 -0
- package/dist/src/channels/telegram-mirror/failure/types.js +9 -0
- package/dist/src/channels/telegram-mirror/failure/types.js.map +1 -0
- package/dist/src/channels/telegram-mirror/inbound-handler.d.ts +52 -0
- package/dist/src/channels/telegram-mirror/inbound-handler.js +115 -0
- package/dist/src/channels/telegram-mirror/inbound-handler.js.map +1 -0
- package/dist/src/channels/telegram-mirror/index.d.ts +30 -0
- package/dist/src/channels/telegram-mirror/index.js +49 -0
- package/dist/src/channels/telegram-mirror/index.js.map +1 -0
- package/dist/src/channels/telegram-mirror/plan-attachment.d.ts +120 -0
- package/dist/src/channels/telegram-mirror/plan-attachment.js +138 -0
- package/dist/src/channels/telegram-mirror/plan-attachment.js.map +1 -0
- package/dist/src/channels/telegram-mirror/quota-reader.d.ts +41 -0
- package/dist/src/channels/telegram-mirror/quota-reader.js +29 -0
- package/dist/src/channels/telegram-mirror/quota-reader.js.map +1 -0
- package/dist/src/channels/telegram-mirror/sessions-keyboard.d.ts +84 -0
- package/dist/src/channels/telegram-mirror/sessions-keyboard.js +113 -0
- package/dist/src/channels/telegram-mirror/sessions-keyboard.js.map +1 -0
- package/dist/src/channels/telegram-mirror/soak-log.d.ts +98 -0
- package/dist/src/channels/telegram-mirror/soak-log.js +136 -0
- package/dist/src/channels/telegram-mirror/soak-log.js.map +1 -0
- package/dist/src/channels/telegram-mirror/state-machine.d.ts +102 -0
- package/dist/src/channels/telegram-mirror/state-machine.js +117 -0
- package/dist/src/channels/telegram-mirror/state-machine.js.map +1 -0
- package/dist/src/channels/telegram-mirror/sync-commands.d.ts +63 -0
- package/dist/src/channels/telegram-mirror/sync-commands.js +51 -0
- package/dist/src/channels/telegram-mirror/sync-commands.js.map +1 -0
- package/dist/src/channels/telegram-mirror/threshold-watcher.d.ts +54 -0
- package/dist/src/channels/telegram-mirror/threshold-watcher.js +77 -0
- package/dist/src/channels/telegram-mirror/threshold-watcher.js.map +1 -0
- package/dist/src/command-router/cc-handler.js +1 -1
- package/dist/src/command-router/cc-handler.js.map +1 -1
- package/dist/src/index.js +12 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/auto-recovery.js +1 -1
- package/dist/src/lib/auto-recovery.js.map +1 -1
- package/dist/src/lib/drift-detector.js +1 -1
- package/dist/src/lib/drift-detector.js.map +1 -1
- package/dist/src/lib/error-renderer.d.ts +23 -0
- package/dist/src/lib/error-renderer.js +106 -0
- package/dist/src/lib/error-renderer.js.map +1 -0
- package/dist/src/lib/perf/speculative-bubble.d.ts +27 -0
- package/dist/src/lib/perf/speculative-bubble.js +36 -0
- package/dist/src/lib/perf/speculative-bubble.js.map +1 -0
- package/dist/src/lib/session-registry.d.ts +66 -0
- package/dist/src/lib/session-registry.js +188 -0
- package/dist/src/lib/session-registry.js.map +1 -0
- package/dist/src/lib/telegram-bot-api.d.ts +100 -0
- package/dist/src/lib/telegram-bot-api.js +204 -0
- package/dist/src/lib/telegram-bot-api.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/burst-accumulator.ts — v0.25.0 M8.
|
|
3
|
+
*
|
|
4
|
+
* Per-chat 5-second settle window for non-slash inbound messages. When
|
|
5
|
+
* the user fires multiple message bubbles within 5s of each other, the
|
|
6
|
+
* accumulator concatenates them into one user-message payload (joined by
|
|
7
|
+
* "\n\n") before delivery to the engine. A gap > 5s flushes the prior
|
|
8
|
+
* buffer immediately and starts a new one.
|
|
9
|
+
*
|
|
10
|
+
* Decision: ADR-007 — 5s is the empirical saddle point. <3s gaps are
|
|
11
|
+
* almost always related thoughts; >8s gaps are almost always new turns.
|
|
12
|
+
* 5s matches the WARMUP_COOLDOWN_MS in typing-prefetch.ts (consistency).
|
|
13
|
+
*
|
|
14
|
+
* Routing responsibility lives in the inbound dispatcher (later milestone):
|
|
15
|
+
* • Slash messages bypass this class — dispatched immediately via
|
|
16
|
+
* dispatchCommand (commands.ts).
|
|
17
|
+
* • Compose-active chats bypass this class — drafts go to ComposeBuffer
|
|
18
|
+
* (compose-buffer.ts). M7 takes priority over M8 per ADR-007.
|
|
19
|
+
* • Everything else flows through submit() here.
|
|
20
|
+
*
|
|
21
|
+
* The flush flow has TWO triggers:
|
|
22
|
+
* 1. A new submit() that arrives AFTER the settle window — that submit
|
|
23
|
+
* returns the prior buffer in `flushed` and starts a fresh buffer.
|
|
24
|
+
* 2. A periodic flushIfReady() check that runs on a timer when no new
|
|
25
|
+
* inbound has arrived since the window opened.
|
|
26
|
+
*
|
|
27
|
+
* Tests inject a deterministic clock; production passes Date.now.
|
|
28
|
+
*/
|
|
29
|
+
export interface BurstAccumulatorOptions {
|
|
30
|
+
/** Settle window in milliseconds (default 5000 per ADR-007). */
|
|
31
|
+
settleMs?: number;
|
|
32
|
+
/** Injectable clock for tests; defaults to Date.now. */
|
|
33
|
+
now?: () => number;
|
|
34
|
+
/** Custom separator between concatenated drafts (default "\n\n"). */
|
|
35
|
+
separator?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface SubmitResult {
|
|
38
|
+
/**
|
|
39
|
+
* When the new submit() arrives AFTER the prior buffer's settle window,
|
|
40
|
+
* the prior buffer is returned here for immediate delivery; the new
|
|
41
|
+
* message starts a fresh buffer in its place. Undefined otherwise.
|
|
42
|
+
*/
|
|
43
|
+
flushed?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare class BurstAccumulator {
|
|
46
|
+
private readonly sessions;
|
|
47
|
+
private readonly settleMs;
|
|
48
|
+
private readonly now;
|
|
49
|
+
private readonly separator;
|
|
50
|
+
constructor(opts?: BurstAccumulatorOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Submit a non-slash inbound message. Returns `flushed` populated when
|
|
53
|
+
* the prior buffer for the chat had timed out and this submit forced
|
|
54
|
+
* its flush; in that case the new message starts a fresh buffer.
|
|
55
|
+
*
|
|
56
|
+
* The returned `flushed` payload is the only value the caller delivers
|
|
57
|
+
* to the engine — the new message itself stays buffered for at least
|
|
58
|
+
* `settleMs` more, so it gets a chance to coalesce with follow-ups.
|
|
59
|
+
*/
|
|
60
|
+
submit(chatId: string, text: string): SubmitResult;
|
|
61
|
+
/**
|
|
62
|
+
* Periodic check — when the settle window has elapsed without any new
|
|
63
|
+
* inbound, returns the buffered payload and clears the session. When
|
|
64
|
+
* the window has NOT yet elapsed, returns undefined and leaves the
|
|
65
|
+
* buffer intact. Returns undefined for chats without an active buffer.
|
|
66
|
+
*
|
|
67
|
+
* Production wiring: call from a 1Hz interval timer per known chatId
|
|
68
|
+
* (or from the inbound dispatcher's idle-tick).
|
|
69
|
+
*/
|
|
70
|
+
flushIfReady(chatId: string): string | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Force-flush the buffer for a chat regardless of timing. Used when an
|
|
73
|
+
* explicit signal arrives that the current buffer must deliver now —
|
|
74
|
+
* e.g. a slash command was typed (its dispatcher flushes burst first,
|
|
75
|
+
* then routes the slash, so the bursted text reaches the engine before
|
|
76
|
+
* the slash response).
|
|
77
|
+
*/
|
|
78
|
+
flushNow(chatId: string): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* True when a buffer exists for the chat (regardless of settle state).
|
|
81
|
+
* Used by the inbound dispatcher to decide whether a slash arrival
|
|
82
|
+
* should trigger flushNow before routing.
|
|
83
|
+
*/
|
|
84
|
+
hasBuffer(chatId: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Number of drafts currently buffered for the chat. Useful for status
|
|
87
|
+
* surfaces and for tests asserting the buffer state.
|
|
88
|
+
*/
|
|
89
|
+
draftCount(chatId: string): number;
|
|
90
|
+
/**
|
|
91
|
+
* Clear all sessions. Test helper.
|
|
92
|
+
*/
|
|
93
|
+
clear(): void;
|
|
94
|
+
private fresh;
|
|
95
|
+
private format;
|
|
96
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/burst-accumulator.ts — v0.25.0 M8.
|
|
3
|
+
*
|
|
4
|
+
* Per-chat 5-second settle window for non-slash inbound messages. When
|
|
5
|
+
* the user fires multiple message bubbles within 5s of each other, the
|
|
6
|
+
* accumulator concatenates them into one user-message payload (joined by
|
|
7
|
+
* "\n\n") before delivery to the engine. A gap > 5s flushes the prior
|
|
8
|
+
* buffer immediately and starts a new one.
|
|
9
|
+
*
|
|
10
|
+
* Decision: ADR-007 — 5s is the empirical saddle point. <3s gaps are
|
|
11
|
+
* almost always related thoughts; >8s gaps are almost always new turns.
|
|
12
|
+
* 5s matches the WARMUP_COOLDOWN_MS in typing-prefetch.ts (consistency).
|
|
13
|
+
*
|
|
14
|
+
* Routing responsibility lives in the inbound dispatcher (later milestone):
|
|
15
|
+
* • Slash messages bypass this class — dispatched immediately via
|
|
16
|
+
* dispatchCommand (commands.ts).
|
|
17
|
+
* • Compose-active chats bypass this class — drafts go to ComposeBuffer
|
|
18
|
+
* (compose-buffer.ts). M7 takes priority over M8 per ADR-007.
|
|
19
|
+
* • Everything else flows through submit() here.
|
|
20
|
+
*
|
|
21
|
+
* The flush flow has TWO triggers:
|
|
22
|
+
* 1. A new submit() that arrives AFTER the settle window — that submit
|
|
23
|
+
* returns the prior buffer in `flushed` and starts a fresh buffer.
|
|
24
|
+
* 2. A periodic flushIfReady() check that runs on a timer when no new
|
|
25
|
+
* inbound has arrived since the window opened.
|
|
26
|
+
*
|
|
27
|
+
* Tests inject a deterministic clock; production passes Date.now.
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_BURST_SETTLE_MS = 5_000;
|
|
30
|
+
export class BurstAccumulator {
|
|
31
|
+
sessions = new Map();
|
|
32
|
+
settleMs;
|
|
33
|
+
now;
|
|
34
|
+
separator;
|
|
35
|
+
constructor(opts = {}) {
|
|
36
|
+
this.settleMs = opts.settleMs ?? DEFAULT_BURST_SETTLE_MS;
|
|
37
|
+
this.now = opts.now ?? Date.now;
|
|
38
|
+
this.separator = opts.separator ?? '\n\n';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Submit a non-slash inbound message. Returns `flushed` populated when
|
|
42
|
+
* the prior buffer for the chat had timed out and this submit forced
|
|
43
|
+
* its flush; in that case the new message starts a fresh buffer.
|
|
44
|
+
*
|
|
45
|
+
* The returned `flushed` payload is the only value the caller delivers
|
|
46
|
+
* to the engine — the new message itself stays buffered for at least
|
|
47
|
+
* `settleMs` more, so it gets a chance to coalesce with follow-ups.
|
|
48
|
+
*/
|
|
49
|
+
submit(chatId, text) {
|
|
50
|
+
const now = this.now();
|
|
51
|
+
const session = this.sessions.get(chatId);
|
|
52
|
+
if (session && now - session.lastTs > this.settleMs) {
|
|
53
|
+
// Gap exceeded — flush the prior buffer, then start a new one
|
|
54
|
+
// containing only the current message.
|
|
55
|
+
const flushed = this.format(session);
|
|
56
|
+
this.sessions.set(chatId, this.fresh(chatId, text, now));
|
|
57
|
+
return { flushed };
|
|
58
|
+
}
|
|
59
|
+
if (!session) {
|
|
60
|
+
this.sessions.set(chatId, this.fresh(chatId, text, now));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
session.pending.push({ text, ts: now });
|
|
64
|
+
session.lastTs = now;
|
|
65
|
+
}
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Periodic check — when the settle window has elapsed without any new
|
|
70
|
+
* inbound, returns the buffered payload and clears the session. When
|
|
71
|
+
* the window has NOT yet elapsed, returns undefined and leaves the
|
|
72
|
+
* buffer intact. Returns undefined for chats without an active buffer.
|
|
73
|
+
*
|
|
74
|
+
* Production wiring: call from a 1Hz interval timer per known chatId
|
|
75
|
+
* (or from the inbound dispatcher's idle-tick).
|
|
76
|
+
*/
|
|
77
|
+
flushIfReady(chatId) {
|
|
78
|
+
const session = this.sessions.get(chatId);
|
|
79
|
+
if (!session)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (this.now() - session.lastTs <= this.settleMs)
|
|
82
|
+
return undefined;
|
|
83
|
+
const out = this.format(session);
|
|
84
|
+
this.sessions.delete(chatId);
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Force-flush the buffer for a chat regardless of timing. Used when an
|
|
89
|
+
* explicit signal arrives that the current buffer must deliver now —
|
|
90
|
+
* e.g. a slash command was typed (its dispatcher flushes burst first,
|
|
91
|
+
* then routes the slash, so the bursted text reaches the engine before
|
|
92
|
+
* the slash response).
|
|
93
|
+
*/
|
|
94
|
+
flushNow(chatId) {
|
|
95
|
+
const session = this.sessions.get(chatId);
|
|
96
|
+
if (!session)
|
|
97
|
+
return undefined;
|
|
98
|
+
const out = this.format(session);
|
|
99
|
+
this.sessions.delete(chatId);
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* True when a buffer exists for the chat (regardless of settle state).
|
|
104
|
+
* Used by the inbound dispatcher to decide whether a slash arrival
|
|
105
|
+
* should trigger flushNow before routing.
|
|
106
|
+
*/
|
|
107
|
+
hasBuffer(chatId) {
|
|
108
|
+
return this.sessions.has(chatId);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Number of drafts currently buffered for the chat. Useful for status
|
|
112
|
+
* surfaces and for tests asserting the buffer state.
|
|
113
|
+
*/
|
|
114
|
+
draftCount(chatId) {
|
|
115
|
+
return this.sessions.get(chatId)?.pending.length ?? 0;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Clear all sessions. Test helper.
|
|
119
|
+
*/
|
|
120
|
+
clear() {
|
|
121
|
+
this.sessions.clear();
|
|
122
|
+
}
|
|
123
|
+
fresh(chatId, text, ts) {
|
|
124
|
+
return { chatId, pending: [{ text, ts }], lastTs: ts };
|
|
125
|
+
}
|
|
126
|
+
format(session) {
|
|
127
|
+
return session.pending.map((p) => p.text).join(this.separator);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=burst-accumulator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"burst-accumulator.js","sourceRoot":"","sources":["../../../../src/channels/telegram-mirror/burst-accumulator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/callback-mapping.ts — v0.25.0 M3.
|
|
3
|
+
*
|
|
4
|
+
* ADR-004 — Telegram's callback_data is capped at 64 bytes UTF-8. For
|
|
5
|
+
* payloads larger than that (most action+slug combinations are), the bot
|
|
6
|
+
* writes a short opaque base36 id into callback_data and stashes the real
|
|
7
|
+
* payload here. TTL prevents unbounded memory growth as users tap through
|
|
8
|
+
* keyboards or never tap at all.
|
|
9
|
+
*
|
|
10
|
+
* Default TTL is 10 minutes per ADR-004 monitoring signal (callback miss
|
|
11
|
+
* rate > 2% in soak → revisit TTL). The class accepts an injectable clock
|
|
12
|
+
* so tests can fast-forward without setTimeout games.
|
|
13
|
+
*
|
|
14
|
+
* No I/O, no global state. Each subsystem that hands out callback_data
|
|
15
|
+
* owns its own CallbackMap instance — keeps ownership boundaries clean
|
|
16
|
+
* for M4 (per-command), M9 (plan keyboards), M11 (cost subview).
|
|
17
|
+
*/
|
|
18
|
+
export interface CallbackMapOptions {
|
|
19
|
+
/** TTL in milliseconds (default 10 minutes per ADR-004). */
|
|
20
|
+
ttlMs?: number;
|
|
21
|
+
/** Injectable clock for tests; defaults to Date.now. */
|
|
22
|
+
now?: () => number;
|
|
23
|
+
/** Length of the opaque id string (default 8 base36 chars ≈ 41 bits entropy). */
|
|
24
|
+
idLength?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare class CallbackMap {
|
|
27
|
+
private readonly entries;
|
|
28
|
+
private readonly ttlMs;
|
|
29
|
+
private readonly now;
|
|
30
|
+
private readonly idLength;
|
|
31
|
+
constructor(opts?: CallbackMapOptions);
|
|
32
|
+
/**
|
|
33
|
+
* Allocate a fresh opaque id and stash the payload behind it. Returns the
|
|
34
|
+
* id, which fits in a Telegram callback_data field with room to spare.
|
|
35
|
+
* Idempotent only at the data-shape level — calling create() with the
|
|
36
|
+
* same payload twice produces two distinct ids.
|
|
37
|
+
*/
|
|
38
|
+
create(payload: unknown): string;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a callback id to its stashed payload. Returns undefined if the
|
|
41
|
+
* id was never minted OR if it has expired since. Stale entries are
|
|
42
|
+
* dropped lazily on access — no background timer needed.
|
|
43
|
+
*/
|
|
44
|
+
get(id: string): unknown | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Manually evict an id (e.g., one-shot Approve/Reject buttons after the
|
|
47
|
+
* user taps once). Returns true if an entry was deleted.
|
|
48
|
+
*/
|
|
49
|
+
delete(id: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Current entry count (after pruning expired). Useful for monitoring
|
|
52
|
+
* and the 2% callback-miss-rate signal in soak.
|
|
53
|
+
*/
|
|
54
|
+
size(): number;
|
|
55
|
+
/**
|
|
56
|
+
* Drop everything. Test helper; production code never calls this.
|
|
57
|
+
*/
|
|
58
|
+
clear(): void;
|
|
59
|
+
private prune;
|
|
60
|
+
private randomId;
|
|
61
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/callback-mapping.ts — v0.25.0 M3.
|
|
3
|
+
*
|
|
4
|
+
* ADR-004 — Telegram's callback_data is capped at 64 bytes UTF-8. For
|
|
5
|
+
* payloads larger than that (most action+slug combinations are), the bot
|
|
6
|
+
* writes a short opaque base36 id into callback_data and stashes the real
|
|
7
|
+
* payload here. TTL prevents unbounded memory growth as users tap through
|
|
8
|
+
* keyboards or never tap at all.
|
|
9
|
+
*
|
|
10
|
+
* Default TTL is 10 minutes per ADR-004 monitoring signal (callback miss
|
|
11
|
+
* rate > 2% in soak → revisit TTL). The class accepts an injectable clock
|
|
12
|
+
* so tests can fast-forward without setTimeout games.
|
|
13
|
+
*
|
|
14
|
+
* No I/O, no global state. Each subsystem that hands out callback_data
|
|
15
|
+
* owns its own CallbackMap instance — keeps ownership boundaries clean
|
|
16
|
+
* for M4 (per-command), M9 (plan keyboards), M11 (cost subview).
|
|
17
|
+
*/
|
|
18
|
+
import { randomBytes } from 'crypto';
|
|
19
|
+
const DEFAULT_TTL_MS = 10 * 60_000;
|
|
20
|
+
const DEFAULT_ID_LENGTH = 8;
|
|
21
|
+
export class CallbackMap {
|
|
22
|
+
entries = new Map();
|
|
23
|
+
ttlMs;
|
|
24
|
+
now;
|
|
25
|
+
idLength;
|
|
26
|
+
constructor(opts = {}) {
|
|
27
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
28
|
+
this.now = opts.now ?? Date.now;
|
|
29
|
+
this.idLength = opts.idLength ?? DEFAULT_ID_LENGTH;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Allocate a fresh opaque id and stash the payload behind it. Returns the
|
|
33
|
+
* id, which fits in a Telegram callback_data field with room to spare.
|
|
34
|
+
* Idempotent only at the data-shape level — calling create() with the
|
|
35
|
+
* same payload twice produces two distinct ids.
|
|
36
|
+
*/
|
|
37
|
+
create(payload) {
|
|
38
|
+
this.prune();
|
|
39
|
+
let id = this.randomId();
|
|
40
|
+
// Defensive — base36 ids of length 8 have ~3T possible values, so
|
|
41
|
+
// collisions are astronomically unlikely, but the loop is cheap.
|
|
42
|
+
while (this.entries.has(id)) {
|
|
43
|
+
id = this.randomId();
|
|
44
|
+
}
|
|
45
|
+
this.entries.set(id, { payload, expiresAt: this.now() + this.ttlMs });
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a callback id to its stashed payload. Returns undefined if the
|
|
50
|
+
* id was never minted OR if it has expired since. Stale entries are
|
|
51
|
+
* dropped lazily on access — no background timer needed.
|
|
52
|
+
*/
|
|
53
|
+
get(id) {
|
|
54
|
+
const entry = this.entries.get(id);
|
|
55
|
+
if (!entry)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (entry.expiresAt <= this.now()) {
|
|
58
|
+
this.entries.delete(id);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return entry.payload;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Manually evict an id (e.g., one-shot Approve/Reject buttons after the
|
|
65
|
+
* user taps once). Returns true if an entry was deleted.
|
|
66
|
+
*/
|
|
67
|
+
delete(id) {
|
|
68
|
+
return this.entries.delete(id);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Current entry count (after pruning expired). Useful for monitoring
|
|
72
|
+
* and the 2% callback-miss-rate signal in soak.
|
|
73
|
+
*/
|
|
74
|
+
size() {
|
|
75
|
+
this.prune();
|
|
76
|
+
return this.entries.size;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Drop everything. Test helper; production code never calls this.
|
|
80
|
+
*/
|
|
81
|
+
clear() {
|
|
82
|
+
this.entries.clear();
|
|
83
|
+
}
|
|
84
|
+
prune() {
|
|
85
|
+
const now = this.now();
|
|
86
|
+
for (const [id, entry] of this.entries) {
|
|
87
|
+
if (entry.expiresAt <= now)
|
|
88
|
+
this.entries.delete(id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
randomId() {
|
|
92
|
+
// 8 random bytes → 64 bits → BigInt → base36 → take left N chars.
|
|
93
|
+
// Leading zeroes are fine — base36 keeps id length bounded.
|
|
94
|
+
const hex = randomBytes(8).toString('hex');
|
|
95
|
+
const base36 = BigInt('0x' + hex).toString(36);
|
|
96
|
+
return base36.padStart(this.idLength, '0').slice(0, this.idLength);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=callback-mapping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-mapping.js","sourceRoot":"","sources":["../../../../src/channels/telegram-mirror/callback-mapping.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/card-renderer.ts — v0.25.0 M2.
|
|
3
|
+
*
|
|
4
|
+
* Pure-function renderer: Turn (from state-machine.ts) → message body string
|
|
5
|
+
* suitable for sendMessage / editMessageText. Telegram-API HTTP wiring
|
|
6
|
+
* (sendTg / editTg) lands in a later milestone alongside the dispatch path
|
|
7
|
+
* — M2's exit criterion is just "render a single turn (working → done)",
|
|
8
|
+
* which is satisfied by this pure function.
|
|
9
|
+
*
|
|
10
|
+
* Aesthetic: terminal-mimic per ADR-001. Header glyph ("▶" working,
|
|
11
|
+
* "✓" done) + tool call list (each prefixed with status glyph) + assistant
|
|
12
|
+
* text. Compact enough to fit in a single Telegram message bubble.
|
|
13
|
+
*
|
|
14
|
+
* Tests live at tests/channels/telegram-mirror/m2-render-pipeline.test.ts.
|
|
15
|
+
*/
|
|
16
|
+
import type { Turn, ToolCallRecord } from './state-machine.js';
|
|
17
|
+
/**
|
|
18
|
+
* Format one tool-call line for inclusion in the rendered card body.
|
|
19
|
+
* "✓ Bash · ls -la"
|
|
20
|
+
* "… Read · src/index.ts"
|
|
21
|
+
* "✗ Edit · …error glyph at left"
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderToolLine(tc: ToolCallRecord): string;
|
|
24
|
+
/**
|
|
25
|
+
* Render the thinking block (M6). Each line of the thinking text is
|
|
26
|
+
* prefixed with "> " to read as a block quote in plain-text Telegram
|
|
27
|
+
* bubbles — clearly visually distinct from the assistant text below it
|
|
28
|
+
* without depending on MarkdownV2 escapes. Empty thinking returns ''.
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderThinkingBlock(thinkingText: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Render the full turn into a Telegram-safe message body. The mirror keeps
|
|
33
|
+
* a single message per turn that gets edited in place — this function is
|
|
34
|
+
* idempotent for a given turn snapshot and produces the next body string.
|
|
35
|
+
*
|
|
36
|
+
* Body shape (lines separated by \n):
|
|
37
|
+
*
|
|
38
|
+
* ▶ Working ← or "✓ Done" when state==='done'
|
|
39
|
+
* ✓ Bash · ls -la ← one line per tool call, in arrival order
|
|
40
|
+
* … Read · src/x.ts
|
|
41
|
+
* <empty line>
|
|
42
|
+
* 💭 Thinking ← M6, only when thinkingText non-empty
|
|
43
|
+
* > reasoning line 1
|
|
44
|
+
* > reasoning line 2
|
|
45
|
+
* <empty line>
|
|
46
|
+
* <assistant text> ← present only when non-empty;
|
|
47
|
+
* ★ Insight ─ blocks preserved INLINE
|
|
48
|
+
* at their emission position (M6).
|
|
49
|
+
*
|
|
50
|
+
* Lines with no content (e.g. empty tool list, empty assistant text) are
|
|
51
|
+
* omitted entirely so the rendered body never has trailing whitespace.
|
|
52
|
+
*/
|
|
53
|
+
export declare function renderTurn(turn: Turn): string;
|
|
54
|
+
/**
|
|
55
|
+
* A render action — abstraction the dispatch layer (added in a later
|
|
56
|
+
* milestone) consumes. M2 doesn't dispatch; it just builds the action so
|
|
57
|
+
* tests can assert against the planned message lifecycle.
|
|
58
|
+
*
|
|
59
|
+
* - 'send' on turn start (no prior messageId)
|
|
60
|
+
* - 'edit' on every subsequent state mutation
|
|
61
|
+
*/
|
|
62
|
+
export interface RenderAction {
|
|
63
|
+
type: 'send' | 'edit';
|
|
64
|
+
chatId: string;
|
|
65
|
+
text: string;
|
|
66
|
+
/** Defined only when type === 'edit'. */
|
|
67
|
+
messageId?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Decide whether the current turn snapshot should be sent (first render)
|
|
71
|
+
* or edited (subsequent renders). The dispatcher tracks the (chatId →
|
|
72
|
+
* messageId) map; this function just produces the action shape.
|
|
73
|
+
*/
|
|
74
|
+
export declare function planRenderAction(turn: Turn, knownMessageId: number | undefined): RenderAction;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/card-renderer.ts — v0.25.0 M2.
|
|
3
|
+
*
|
|
4
|
+
* Pure-function renderer: Turn (from state-machine.ts) → message body string
|
|
5
|
+
* suitable for sendMessage / editMessageText. Telegram-API HTTP wiring
|
|
6
|
+
* (sendTg / editTg) lands in a later milestone alongside the dispatch path
|
|
7
|
+
* — M2's exit criterion is just "render a single turn (working → done)",
|
|
8
|
+
* which is satisfied by this pure function.
|
|
9
|
+
*
|
|
10
|
+
* Aesthetic: terminal-mimic per ADR-001. Header glyph ("▶" working,
|
|
11
|
+
* "✓" done) + tool call list (each prefixed with status glyph) + assistant
|
|
12
|
+
* text. Compact enough to fit in a single Telegram message bubble.
|
|
13
|
+
*
|
|
14
|
+
* Tests live at tests/channels/telegram-mirror/m2-render-pipeline.test.ts.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Glyph picked per tool-call status. Mirrors terminal "running / done /
|
|
18
|
+
* errored" iconography that A1 already reads in the live Claude Code TUI.
|
|
19
|
+
*/
|
|
20
|
+
function toolGlyph(tc) {
|
|
21
|
+
if (tc.isError)
|
|
22
|
+
return '✗';
|
|
23
|
+
if (tc.result !== undefined)
|
|
24
|
+
return '✓';
|
|
25
|
+
return '…';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render the input argument summary for a tool call. Kept tight — full
|
|
29
|
+
* input is in the result; the mirror line is meant to read at a glance.
|
|
30
|
+
*/
|
|
31
|
+
function toolInputSummary(tc) {
|
|
32
|
+
const input = tc.input;
|
|
33
|
+
if (!input || typeof input !== 'object')
|
|
34
|
+
return '';
|
|
35
|
+
// Prefer common single-field shapes used by Bash/Read/Edit/Write.
|
|
36
|
+
const single = typeof input.command === 'string'
|
|
37
|
+
? input.command
|
|
38
|
+
: typeof input.file_path === 'string'
|
|
39
|
+
? input.file_path
|
|
40
|
+
: typeof input.path === 'string'
|
|
41
|
+
? input.path
|
|
42
|
+
: typeof input.url === 'string'
|
|
43
|
+
? input.url
|
|
44
|
+
: '';
|
|
45
|
+
if (single) {
|
|
46
|
+
return single.length > 60 ? single.slice(0, 57) + '...' : single;
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Format one tool-call line for inclusion in the rendered card body.
|
|
52
|
+
* "✓ Bash · ls -la"
|
|
53
|
+
* "… Read · src/index.ts"
|
|
54
|
+
* "✗ Edit · …error glyph at left"
|
|
55
|
+
*/
|
|
56
|
+
export function renderToolLine(tc) {
|
|
57
|
+
const glyph = toolGlyph(tc);
|
|
58
|
+
const summary = toolInputSummary(tc);
|
|
59
|
+
return summary ? `${glyph} ${tc.name} · ${summary}` : `${glyph} ${tc.name}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Render the thinking block (M6). Each line of the thinking text is
|
|
63
|
+
* prefixed with "> " to read as a block quote in plain-text Telegram
|
|
64
|
+
* bubbles — clearly visually distinct from the assistant text below it
|
|
65
|
+
* without depending on MarkdownV2 escapes. Empty thinking returns ''.
|
|
66
|
+
*/
|
|
67
|
+
export function renderThinkingBlock(thinkingText) {
|
|
68
|
+
const trimmed = thinkingText.trim();
|
|
69
|
+
if (trimmed.length === 0)
|
|
70
|
+
return '';
|
|
71
|
+
const lines = trimmed.split('\n');
|
|
72
|
+
return ['💭 Thinking', ...lines.map((l) => `> ${l}`)].join('\n');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Render the full turn into a Telegram-safe message body. The mirror keeps
|
|
76
|
+
* a single message per turn that gets edited in place — this function is
|
|
77
|
+
* idempotent for a given turn snapshot and produces the next body string.
|
|
78
|
+
*
|
|
79
|
+
* Body shape (lines separated by \n):
|
|
80
|
+
*
|
|
81
|
+
* ▶ Working ← or "✓ Done" when state==='done'
|
|
82
|
+
* ✓ Bash · ls -la ← one line per tool call, in arrival order
|
|
83
|
+
* … Read · src/x.ts
|
|
84
|
+
* <empty line>
|
|
85
|
+
* 💭 Thinking ← M6, only when thinkingText non-empty
|
|
86
|
+
* > reasoning line 1
|
|
87
|
+
* > reasoning line 2
|
|
88
|
+
* <empty line>
|
|
89
|
+
* <assistant text> ← present only when non-empty;
|
|
90
|
+
* ★ Insight ─ blocks preserved INLINE
|
|
91
|
+
* at their emission position (M6).
|
|
92
|
+
*
|
|
93
|
+
* Lines with no content (e.g. empty tool list, empty assistant text) are
|
|
94
|
+
* omitted entirely so the rendered body never has trailing whitespace.
|
|
95
|
+
*/
|
|
96
|
+
export function renderTurn(turn) {
|
|
97
|
+
const header = turn.state === 'done' ? '✓ Done' : '▶ Working';
|
|
98
|
+
const lines = [header];
|
|
99
|
+
if (turn.toolCalls.length > 0) {
|
|
100
|
+
for (const tc of turn.toolCalls) {
|
|
101
|
+
lines.push(renderToolLine(tc));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Defensive default — Turn.thinkingText is required by the interface, but
|
|
105
|
+
// some test helpers and pre-M6 fixtures construct Turn-shaped objects
|
|
106
|
+
// without setting it. Treat absent / undefined as empty.
|
|
107
|
+
const thinking = renderThinkingBlock(turn.thinkingText ?? '');
|
|
108
|
+
if (thinking.length > 0) {
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push(thinking);
|
|
111
|
+
}
|
|
112
|
+
const text = turn.assistantText.trim();
|
|
113
|
+
if (text.length > 0) {
|
|
114
|
+
lines.push('');
|
|
115
|
+
lines.push(text);
|
|
116
|
+
}
|
|
117
|
+
return lines.join('\n');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Decide whether the current turn snapshot should be sent (first render)
|
|
121
|
+
* or edited (subsequent renders). The dispatcher tracks the (chatId →
|
|
122
|
+
* messageId) map; this function just produces the action shape.
|
|
123
|
+
*/
|
|
124
|
+
export function planRenderAction(turn, knownMessageId) {
|
|
125
|
+
const text = renderTurn(turn);
|
|
126
|
+
if (knownMessageId === undefined) {
|
|
127
|
+
return { type: 'send', chatId: turn.chatId, text };
|
|
128
|
+
}
|
|
129
|
+
return { type: 'edit', chatId: turn.chatId, text, messageId: knownMessageId };
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=card-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-renderer.js","sourceRoot":"","sources":["../../../../src/channels/telegram-mirror/card-renderer.ts"],"names":[],"mappings":"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"}
|