@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,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/commands.ts — v0.25.0 M4.
|
|
3
|
+
*
|
|
4
|
+
* Top-7 phone-tier slash commands per PRP §5 M4:
|
|
5
|
+
* /sessions /new <slug> /stop /status /compact /cost /rewind
|
|
6
|
+
*
|
|
7
|
+
* Each handler is a pure function returning a CommandResult — a list of
|
|
8
|
+
* Telegram API actions the dispatch layer consumes. No I/O happens here
|
|
9
|
+
* beyond reading session-registry (which itself isolates I/O behind an
|
|
10
|
+
* env-overridable path for tests).
|
|
11
|
+
*
|
|
12
|
+
* G-2 resolution (2026-05-19): per-message inline keyboards + typed-form
|
|
13
|
+
* — no persistent ReplyKeyboardMarkup. /sessions and /cost have an
|
|
14
|
+
* inline-keyboard form; the rest are text-only.
|
|
15
|
+
*
|
|
16
|
+
* Decision refs:
|
|
17
|
+
* • ADR-003 — slug registry is the canonical source for /sessions, /new,
|
|
18
|
+
* /stop, /status.
|
|
19
|
+
* • ADR-004 — callback_data slots come from the per-handler CallbackMap.
|
|
20
|
+
* • ADR-008 — failure handlers (M12) consume CommandResult.actions[].
|
|
21
|
+
*
|
|
22
|
+
* Out of scope for M4 (per "no premature resolution"):
|
|
23
|
+
* • Actually spawning / killing claude subprocesses (M5+ wiring).
|
|
24
|
+
* • Real quota numbers in /cost — uses QuotaReader stub (M11 wires real).
|
|
25
|
+
* • Threshold notifications / 95% pause button (M11).
|
|
26
|
+
* • Plan attachment via sendDocument (M9).
|
|
27
|
+
*/
|
|
28
|
+
import { CallbackMap } from './callback-mapping.js';
|
|
29
|
+
import { type SessionState, type InlineButton } from './sessions-keyboard.js';
|
|
30
|
+
import { type QuotaReader } from './quota-reader.js';
|
|
31
|
+
import type { ComposeBuffer } from './compose-buffer.js';
|
|
32
|
+
/**
|
|
33
|
+
* Discriminated union of Telegram API actions a command may emit.
|
|
34
|
+
* Maps 1:1 to the test mock layer at tests/.../__mocks__/telegram-api.ts.
|
|
35
|
+
*/
|
|
36
|
+
export type TelegramAction = {
|
|
37
|
+
type: 'sendMessage';
|
|
38
|
+
chat_id: string | number;
|
|
39
|
+
text: string;
|
|
40
|
+
reply_markup?: {
|
|
41
|
+
inline_keyboard: InlineButton[][];
|
|
42
|
+
};
|
|
43
|
+
parse_mode?: 'MarkdownV2';
|
|
44
|
+
} | {
|
|
45
|
+
type: 'editMessageText';
|
|
46
|
+
chat_id: string | number;
|
|
47
|
+
message_id: number;
|
|
48
|
+
text: string;
|
|
49
|
+
reply_markup?: {
|
|
50
|
+
inline_keyboard: InlineButton[][];
|
|
51
|
+
};
|
|
52
|
+
parse_mode?: 'MarkdownV2';
|
|
53
|
+
} | {
|
|
54
|
+
type: 'sendDocument';
|
|
55
|
+
chat_id: string | number;
|
|
56
|
+
filename: string;
|
|
57
|
+
content: string;
|
|
58
|
+
caption?: string;
|
|
59
|
+
reply_markup?: {
|
|
60
|
+
inline_keyboard: InlineButton[][];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export interface CommandContext {
|
|
64
|
+
chatId: string | number;
|
|
65
|
+
args: string[];
|
|
66
|
+
callbackMap: CallbackMap;
|
|
67
|
+
/** Optional state lookup for status-rich rows; defaults all entries to 'idle'. */
|
|
68
|
+
stateLookup?: (slug: string) => SessionState;
|
|
69
|
+
/** Optional quota reader for /cost; defaults to stubQuotaReader. */
|
|
70
|
+
quotaReader?: QuotaReader;
|
|
71
|
+
/** Optional clock for deterministic last-activity tests. */
|
|
72
|
+
now?: number;
|
|
73
|
+
/** Required for /compose and /send (M7); other handlers ignore. */
|
|
74
|
+
composeBuffer?: ComposeBuffer;
|
|
75
|
+
}
|
|
76
|
+
export interface CommandResult {
|
|
77
|
+
actions: TelegramAction[];
|
|
78
|
+
/**
|
|
79
|
+
* When the command produces user-message content destined for the
|
|
80
|
+
* engine (currently only /send after /compose), the concatenated
|
|
81
|
+
* payload appears here. M5+ engine bridging consumes this field to
|
|
82
|
+
* feed openai-compat as a single user message. Absent when the command
|
|
83
|
+
* is purely a UI surface (most handlers).
|
|
84
|
+
*/
|
|
85
|
+
delivery?: {
|
|
86
|
+
payload: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Tiny parser. Telegram slash messages look like "/cmd arg1 arg2" — split
|
|
91
|
+
* on whitespace, drop the leading '/', strip any "@botname" suffix.
|
|
92
|
+
* Returns undefined for non-slash input.
|
|
93
|
+
*/
|
|
94
|
+
export interface ParsedSlash {
|
|
95
|
+
cmd: string;
|
|
96
|
+
args: string[];
|
|
97
|
+
}
|
|
98
|
+
export declare function parseSlash(text: string): ParsedSlash | undefined;
|
|
99
|
+
export declare function handleSessions(ctx: CommandContext): CommandResult;
|
|
100
|
+
export declare function handleNew(ctx: CommandContext): CommandResult;
|
|
101
|
+
export declare function handleStop(ctx: CommandContext): CommandResult;
|
|
102
|
+
export declare function handleStatus(ctx: CommandContext): CommandResult;
|
|
103
|
+
export declare function handleCompact(ctx: CommandContext): CommandResult;
|
|
104
|
+
export declare function handleCost(ctx: CommandContext): CommandResult;
|
|
105
|
+
export declare function handleRewind(ctx: CommandContext): CommandResult;
|
|
106
|
+
/**
|
|
107
|
+
* Open a compose session for the chat. M7 — drafts append until /send.
|
|
108
|
+
* Returns a force_reply prompt so Telegram nudges the user into reply mode.
|
|
109
|
+
*/
|
|
110
|
+
export declare function handleCompose(ctx: CommandContext): CommandResult;
|
|
111
|
+
/**
|
|
112
|
+
* Commit the active compose buffer. Returns the concatenated payload via
|
|
113
|
+
* CommandResult.delivery for the engine bridge to consume. If no session
|
|
114
|
+
* is active, returns a friendly error message.
|
|
115
|
+
*/
|
|
116
|
+
export declare function handleSend(ctx: CommandContext): CommandResult;
|
|
117
|
+
/**
|
|
118
|
+
* Cancel the active compose buffer without sending. Useful when the user
|
|
119
|
+
* starts composing then changes their mind.
|
|
120
|
+
*/
|
|
121
|
+
export declare function handleCancel(ctx: CommandContext): CommandResult;
|
|
122
|
+
/**
|
|
123
|
+
* Surface a Y/N inline keyboard so A1 records opened-laptop-today for
|
|
124
|
+
* the soak instrumentation. Idempotency is enforced by SoakLogger
|
|
125
|
+
* downstream — handler always emits the keyboard and lets the callback
|
|
126
|
+
* resolver call recordLaptopCheck().
|
|
127
|
+
*/
|
|
128
|
+
export declare function handleSoakCheck(ctx: CommandContext): CommandResult;
|
|
129
|
+
export type CommandHandler = (ctx: CommandContext) => CommandResult;
|
|
130
|
+
export declare const COMMAND_HANDLERS: Record<string, CommandHandler>;
|
|
131
|
+
/**
|
|
132
|
+
* Telegram bot commands list — declarative source for M5's setMyCommands sync.
|
|
133
|
+
*/
|
|
134
|
+
export declare const TOP_7_COMMANDS: ReadonlyArray<{
|
|
135
|
+
command: string;
|
|
136
|
+
description: string;
|
|
137
|
+
}>;
|
|
138
|
+
/**
|
|
139
|
+
* Route a parsed slash command to its handler. Returns undefined for
|
|
140
|
+
* unknown commands so the caller can fall back to "unknown command" UX.
|
|
141
|
+
*/
|
|
142
|
+
export declare function dispatchCommand(parsed: ParsedSlash, ctxBase: Omit<CommandContext, 'args'>): CommandResult | undefined;
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/commands.ts — v0.25.0 M4.
|
|
3
|
+
*
|
|
4
|
+
* Top-7 phone-tier slash commands per PRP §5 M4:
|
|
5
|
+
* /sessions /new <slug> /stop /status /compact /cost /rewind
|
|
6
|
+
*
|
|
7
|
+
* Each handler is a pure function returning a CommandResult — a list of
|
|
8
|
+
* Telegram API actions the dispatch layer consumes. No I/O happens here
|
|
9
|
+
* beyond reading session-registry (which itself isolates I/O behind an
|
|
10
|
+
* env-overridable path for tests).
|
|
11
|
+
*
|
|
12
|
+
* G-2 resolution (2026-05-19): per-message inline keyboards + typed-form
|
|
13
|
+
* — no persistent ReplyKeyboardMarkup. /sessions and /cost have an
|
|
14
|
+
* inline-keyboard form; the rest are text-only.
|
|
15
|
+
*
|
|
16
|
+
* Decision refs:
|
|
17
|
+
* • ADR-003 — slug registry is the canonical source for /sessions, /new,
|
|
18
|
+
* /stop, /status.
|
|
19
|
+
* • ADR-004 — callback_data slots come from the per-handler CallbackMap.
|
|
20
|
+
* • ADR-008 — failure handlers (M12) consume CommandResult.actions[].
|
|
21
|
+
*
|
|
22
|
+
* Out of scope for M4 (per "no premature resolution"):
|
|
23
|
+
* • Actually spawning / killing claude subprocesses (M5+ wiring).
|
|
24
|
+
* • Real quota numbers in /cost — uses QuotaReader stub (M11 wires real).
|
|
25
|
+
* • Threshold notifications / 95% pause button (M11).
|
|
26
|
+
* • Plan attachment via sendDocument (M9).
|
|
27
|
+
*/
|
|
28
|
+
import { register, unregister, list, getBySlug, } from '../../lib/session-registry.js';
|
|
29
|
+
import { buildSessionsKeyboard, formatLastActivity, } from './sessions-keyboard.js';
|
|
30
|
+
import { stubQuotaReader } from './quota-reader.js';
|
|
31
|
+
export function parseSlash(text) {
|
|
32
|
+
const trimmed = text.trim();
|
|
33
|
+
if (!trimmed.startsWith('/'))
|
|
34
|
+
return undefined;
|
|
35
|
+
const tokens = trimmed.slice(1).split(/\s+/).filter((t) => t.length > 0);
|
|
36
|
+
if (tokens.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
const head = tokens[0];
|
|
39
|
+
const cmd = head.includes('@') ? head.split('@')[0] : head;
|
|
40
|
+
return { cmd: cmd.toLowerCase(), args: tokens.slice(1) };
|
|
41
|
+
}
|
|
42
|
+
// ── /sessions ────────────────────────────────────────────────────────────
|
|
43
|
+
function enrichRows(entries, stateLookup) {
|
|
44
|
+
return entries.map((e) => ({
|
|
45
|
+
slug: e.slug,
|
|
46
|
+
sessionName: e.sessionName,
|
|
47
|
+
state: stateLookup(e.slug),
|
|
48
|
+
lastUsedAt: e.lastUsedAt,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
export function handleSessions(ctx) {
|
|
52
|
+
const entries = list();
|
|
53
|
+
const stateLookup = ctx.stateLookup ?? (() => 'idle');
|
|
54
|
+
const rows = enrichRows(entries, stateLookup);
|
|
55
|
+
const kb = buildSessionsKeyboard({
|
|
56
|
+
rows,
|
|
57
|
+
callbackMap: ctx.callbackMap,
|
|
58
|
+
now: ctx.now,
|
|
59
|
+
});
|
|
60
|
+
const header = entries.length === 0
|
|
61
|
+
? 'No sessions registered yet. Tap ➕ New to create one.'
|
|
62
|
+
: `Sessions (${entries.length})${kb.pageCount > 1 ? ` · page ${kb.page + 1}/${kb.pageCount}` : ''}`;
|
|
63
|
+
return {
|
|
64
|
+
actions: [
|
|
65
|
+
{
|
|
66
|
+
type: 'sendMessage',
|
|
67
|
+
chat_id: ctx.chatId,
|
|
68
|
+
text: header,
|
|
69
|
+
reply_markup: { inline_keyboard: kb.inline_keyboard },
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// ── /new <slug> ──────────────────────────────────────────────────────────
|
|
75
|
+
const SLUG_PATTERN = /^[a-z0-9][a-z0-9-_]{0,39}$/i;
|
|
76
|
+
export function handleNew(ctx) {
|
|
77
|
+
const slug = ctx.args[0];
|
|
78
|
+
if (!slug) {
|
|
79
|
+
return {
|
|
80
|
+
actions: [
|
|
81
|
+
{
|
|
82
|
+
type: 'sendMessage',
|
|
83
|
+
chat_id: ctx.chatId,
|
|
84
|
+
text: 'Usage: /new <slug>\nExample: /new cc-openclaw',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (!SLUG_PATTERN.test(slug)) {
|
|
90
|
+
return {
|
|
91
|
+
actions: [
|
|
92
|
+
{
|
|
93
|
+
type: 'sendMessage',
|
|
94
|
+
chat_id: ctx.chatId,
|
|
95
|
+
text: `Invalid slug "${slug}". Use [a-z0-9-_], max 40 chars, starting with [a-z0-9].`,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const existing = getBySlug(slug);
|
|
101
|
+
// Session-name comes from the engine; M4 stores a placeholder that M5+ overwrites.
|
|
102
|
+
const sessionName = existing?.sessionName ?? `pending-${slug}-${Date.now()}`;
|
|
103
|
+
register(slug, sessionName);
|
|
104
|
+
return {
|
|
105
|
+
actions: [
|
|
106
|
+
{
|
|
107
|
+
type: 'sendMessage',
|
|
108
|
+
chat_id: ctx.chatId,
|
|
109
|
+
text: existing
|
|
110
|
+
? `Session "${slug}" already registered — fronted.`
|
|
111
|
+
: `Session "${slug}" registered. Engine wire-up lands in M5.`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// ── /stop ────────────────────────────────────────────────────────────────
|
|
117
|
+
export function handleStop(ctx) {
|
|
118
|
+
const slug = ctx.args[0];
|
|
119
|
+
if (!slug) {
|
|
120
|
+
return {
|
|
121
|
+
actions: [
|
|
122
|
+
{
|
|
123
|
+
type: 'sendMessage',
|
|
124
|
+
chat_id: ctx.chatId,
|
|
125
|
+
text: 'Usage: /stop <slug>',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const removed = unregister(slug);
|
|
131
|
+
return {
|
|
132
|
+
actions: [
|
|
133
|
+
{
|
|
134
|
+
type: 'sendMessage',
|
|
135
|
+
chat_id: ctx.chatId,
|
|
136
|
+
text: removed
|
|
137
|
+
? `Session "${slug}" stopped.`
|
|
138
|
+
: `No registered session named "${slug}".`,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// ── /status ──────────────────────────────────────────────────────────────
|
|
144
|
+
export function handleStatus(ctx) {
|
|
145
|
+
const entries = list();
|
|
146
|
+
const stateLookup = ctx.stateLookup ?? (() => 'idle');
|
|
147
|
+
const now = ctx.now ?? Date.now();
|
|
148
|
+
if (entries.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
actions: [
|
|
151
|
+
{
|
|
152
|
+
type: 'sendMessage',
|
|
153
|
+
chat_id: ctx.chatId,
|
|
154
|
+
text: 'No sessions registered.',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const lines = [`Active sessions: ${entries.length}`];
|
|
160
|
+
for (const e of entries) {
|
|
161
|
+
lines.push(`• ${e.slug} (${stateLookup(e.slug)}, ${formatLastActivity(e.lastUsedAt, now)})`);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
actions: [{ type: 'sendMessage', chat_id: ctx.chatId, text: lines.join('\n') }],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// ── /compact ─────────────────────────────────────────────────────────────
|
|
168
|
+
export function handleCompact(ctx) {
|
|
169
|
+
// M4: surface the intent. Actual context-compaction wiring runs through
|
|
170
|
+
// the cc-handler module (existing /cc compact path) — bridging the
|
|
171
|
+
// mirror to that handler lands in M5 alongside the rest of the engine
|
|
172
|
+
// integration. Without the bridge, the user sees a clear "queued" state.
|
|
173
|
+
return {
|
|
174
|
+
actions: [
|
|
175
|
+
{
|
|
176
|
+
type: 'sendMessage',
|
|
177
|
+
chat_id: ctx.chatId,
|
|
178
|
+
text: '⏳ Compact queued — engine wire-up lands in M5.',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// ── /cost ────────────────────────────────────────────────────────────────
|
|
184
|
+
function formatQuotaSummary(snap) {
|
|
185
|
+
const pct = snap.maxPercent.toFixed(1);
|
|
186
|
+
const burn = snap.weeklyBurn.toFixed(2);
|
|
187
|
+
return `Max 20x: ${pct}% used · Weekly burn: $${burn}`;
|
|
188
|
+
}
|
|
189
|
+
export function handleCost(ctx) {
|
|
190
|
+
const reader = ctx.quotaReader ?? stubQuotaReader;
|
|
191
|
+
const snap = reader.read();
|
|
192
|
+
const summaryText = formatQuotaSummary(snap);
|
|
193
|
+
// Inline detail button — payload tells the callback handler (M11+) which
|
|
194
|
+
// subview to expand: per-day rollup, top sessions, or weekly burn breakdown.
|
|
195
|
+
const detailCb = ctx.callbackMap.create({ action: 'cost-detail' });
|
|
196
|
+
const dailyCb = ctx.callbackMap.create({ action: 'cost-daily' });
|
|
197
|
+
return {
|
|
198
|
+
actions: [
|
|
199
|
+
{
|
|
200
|
+
type: 'sendMessage',
|
|
201
|
+
chat_id: ctx.chatId,
|
|
202
|
+
text: summaryText,
|
|
203
|
+
reply_markup: {
|
|
204
|
+
inline_keyboard: [
|
|
205
|
+
[{ text: '📊 Detail', callback_data: detailCb }],
|
|
206
|
+
[{ text: '📅 Per-day', callback_data: dailyCb }],
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// ── /rewind ──────────────────────────────────────────────────────────────
|
|
214
|
+
export function handleRewind(ctx) {
|
|
215
|
+
// M4: queued — actual rewind walks the cc-handler resume path (deferred
|
|
216
|
+
// to M5 with the rest of engine bridging).
|
|
217
|
+
return {
|
|
218
|
+
actions: [
|
|
219
|
+
{
|
|
220
|
+
type: 'sendMessage',
|
|
221
|
+
chat_id: ctx.chatId,
|
|
222
|
+
text: '⏪ Rewind queued — engine wire-up lands in M5.',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// ── /compose ─────────────────────────────────────────────────────────────
|
|
228
|
+
/**
|
|
229
|
+
* Open a compose session for the chat. M7 — drafts append until /send.
|
|
230
|
+
* Returns a force_reply prompt so Telegram nudges the user into reply mode.
|
|
231
|
+
*/
|
|
232
|
+
export function handleCompose(ctx) {
|
|
233
|
+
if (!ctx.composeBuffer) {
|
|
234
|
+
return {
|
|
235
|
+
actions: [
|
|
236
|
+
{
|
|
237
|
+
type: 'sendMessage',
|
|
238
|
+
chat_id: ctx.chatId,
|
|
239
|
+
text: '⚠️ /compose unavailable — buffer not wired in this context.',
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
ctx.composeBuffer.start(String(ctx.chatId));
|
|
245
|
+
return {
|
|
246
|
+
actions: [
|
|
247
|
+
{
|
|
248
|
+
type: 'sendMessage',
|
|
249
|
+
chat_id: ctx.chatId,
|
|
250
|
+
text: '✍️ Compose mode opened. Send messages; finish with /send (or /cancel to abort).',
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Commit the active compose buffer. Returns the concatenated payload via
|
|
257
|
+
* CommandResult.delivery for the engine bridge to consume. If no session
|
|
258
|
+
* is active, returns a friendly error message.
|
|
259
|
+
*/
|
|
260
|
+
export function handleSend(ctx) {
|
|
261
|
+
if (!ctx.composeBuffer) {
|
|
262
|
+
return {
|
|
263
|
+
actions: [
|
|
264
|
+
{
|
|
265
|
+
type: 'sendMessage',
|
|
266
|
+
chat_id: ctx.chatId,
|
|
267
|
+
text: '⚠️ /send unavailable — buffer not wired in this context.',
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const payload = ctx.composeBuffer.commit(String(ctx.chatId));
|
|
273
|
+
if (payload === undefined) {
|
|
274
|
+
return {
|
|
275
|
+
actions: [
|
|
276
|
+
{
|
|
277
|
+
type: 'sendMessage',
|
|
278
|
+
chat_id: ctx.chatId,
|
|
279
|
+
text: 'No compose session active. Start one with /compose.',
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// Confirmation surfaces a short preview (≤ 120 chars) so the user sees
|
|
285
|
+
// what was sent. The full payload travels via CommandResult.delivery
|
|
286
|
+
// for M5+ engine bridging.
|
|
287
|
+
const preview = payload.length > 120 ? payload.slice(0, 117) + '...' : payload;
|
|
288
|
+
return {
|
|
289
|
+
actions: [
|
|
290
|
+
{
|
|
291
|
+
type: 'sendMessage',
|
|
292
|
+
chat_id: ctx.chatId,
|
|
293
|
+
text: `📤 Sent (${payload.length} chars): ${preview}`,
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
delivery: { payload },
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Cancel the active compose buffer without sending. Useful when the user
|
|
301
|
+
* starts composing then changes their mind.
|
|
302
|
+
*/
|
|
303
|
+
export function handleCancel(ctx) {
|
|
304
|
+
if (!ctx.composeBuffer) {
|
|
305
|
+
return {
|
|
306
|
+
actions: [
|
|
307
|
+
{
|
|
308
|
+
type: 'sendMessage',
|
|
309
|
+
chat_id: ctx.chatId,
|
|
310
|
+
text: '⚠️ /cancel unavailable — buffer not wired in this context.',
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const had = ctx.composeBuffer.cancel(String(ctx.chatId));
|
|
316
|
+
return {
|
|
317
|
+
actions: [
|
|
318
|
+
{
|
|
319
|
+
type: 'sendMessage',
|
|
320
|
+
chat_id: ctx.chatId,
|
|
321
|
+
text: had ? '🚫 Compose cancelled.' : 'No compose session was active.',
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// ── /soak-check (M14) ────────────────────────────────────────────────────
|
|
327
|
+
/**
|
|
328
|
+
* Surface a Y/N inline keyboard so A1 records opened-laptop-today for
|
|
329
|
+
* the soak instrumentation. Idempotency is enforced by SoakLogger
|
|
330
|
+
* downstream — handler always emits the keyboard and lets the callback
|
|
331
|
+
* resolver call recordLaptopCheck().
|
|
332
|
+
*/
|
|
333
|
+
export function handleSoakCheck(ctx) {
|
|
334
|
+
const yesCb = ctx.callbackMap.create({ action: 'soak-check', openedLaptop: true });
|
|
335
|
+
const noCb = ctx.callbackMap.create({ action: 'soak-check', openedLaptop: false });
|
|
336
|
+
return {
|
|
337
|
+
actions: [
|
|
338
|
+
{
|
|
339
|
+
type: 'sendMessage',
|
|
340
|
+
chat_id: ctx.chatId,
|
|
341
|
+
text: 'Opened the laptop today? (one tap per day for v0.25.0 soak)',
|
|
342
|
+
reply_markup: {
|
|
343
|
+
inline_keyboard: [
|
|
344
|
+
[
|
|
345
|
+
{ text: '✅ Yes', callback_data: yesCb },
|
|
346
|
+
{ text: '❌ No', callback_data: noCb },
|
|
347
|
+
],
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
export const COMMAND_HANDLERS = {
|
|
355
|
+
sessions: handleSessions,
|
|
356
|
+
new: handleNew,
|
|
357
|
+
stop: handleStop,
|
|
358
|
+
status: handleStatus,
|
|
359
|
+
compact: handleCompact,
|
|
360
|
+
cost: handleCost,
|
|
361
|
+
rewind: handleRewind,
|
|
362
|
+
compose: handleCompose,
|
|
363
|
+
send: handleSend,
|
|
364
|
+
cancel: handleCancel,
|
|
365
|
+
'soak-check': handleSoakCheck,
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Telegram bot commands list — declarative source for M5's setMyCommands sync.
|
|
369
|
+
*/
|
|
370
|
+
export const TOP_7_COMMANDS = [
|
|
371
|
+
{ command: 'sessions', description: 'List + switch fronted Claude session' },
|
|
372
|
+
{ command: 'new', description: 'Register a new session slug — /new <slug>' },
|
|
373
|
+
{ command: 'stop', description: 'Stop a registered session — /stop <slug>' },
|
|
374
|
+
{ command: 'status', description: 'Show active sessions and their state' },
|
|
375
|
+
{ command: 'compact', description: 'Compact context on the fronted session' },
|
|
376
|
+
{ command: 'cost', description: 'Show Max 20x usage + weekly burn' },
|
|
377
|
+
{ command: 'rewind', description: 'Rewind the fronted session by one user message' },
|
|
378
|
+
];
|
|
379
|
+
/**
|
|
380
|
+
* Route a parsed slash command to its handler. Returns undefined for
|
|
381
|
+
* unknown commands so the caller can fall back to "unknown command" UX.
|
|
382
|
+
*/
|
|
383
|
+
export function dispatchCommand(parsed, ctxBase) {
|
|
384
|
+
const handler = COMMAND_HANDLERS[parsed.cmd];
|
|
385
|
+
if (!handler)
|
|
386
|
+
return undefined;
|
|
387
|
+
return handler({ ...ctxBase, args: parsed.args });
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../../src/channels/telegram-mirror/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/channels/telegram-mirror/compose-buffer.ts — v0.25.0 M7.
|
|
3
|
+
*
|
|
4
|
+
* Per-chat buffer of draft messages between /compose and /send. The
|
|
5
|
+
* dispatch layer feeds non-slash inbound messages here when a chat is in
|
|
6
|
+
* compose mode; /send commits the buffer to one concatenated payload that
|
|
7
|
+
* M5+ engine bridging delivers to openai-compat as a single user message.
|
|
8
|
+
*
|
|
9
|
+
* Decision: ADR-007 — the 5-second rapid-burst window (M8) is a SEPARATE
|
|
10
|
+
* concatenation path. /compose is the explicit, user-controlled buffer
|
|
11
|
+
* with no time limit (within reason); rapid-burst is the implicit window
|
|
12
|
+
* for accidental double-taps. M7's compose buffer takes priority over
|
|
13
|
+
* the M8 burst handler when a compose session is active.
|
|
14
|
+
*
|
|
15
|
+
* Timeout exists purely as a safety valve — a compose session that's
|
|
16
|
+
* been idle for > timeoutMs is considered abandoned and silently cleared
|
|
17
|
+
* on the next interaction. Default 30 minutes; tests inject shorter.
|
|
18
|
+
*/
|
|
19
|
+
export interface ComposeBufferOptions {
|
|
20
|
+
/** Injectable clock for tests; defaults to Date.now. */
|
|
21
|
+
now?: () => number;
|
|
22
|
+
/** Idle-session timeout in milliseconds (default 30 minutes). */
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class ComposeBuffer {
|
|
26
|
+
private readonly sessions;
|
|
27
|
+
private readonly now;
|
|
28
|
+
private readonly timeoutMs;
|
|
29
|
+
constructor(opts?: ComposeBufferOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Open a compose session for the chat. If a session is already open it
|
|
32
|
+
* is replaced — the user is restarting the buffer intentionally.
|
|
33
|
+
*/
|
|
34
|
+
start(chatId: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Append a draft message to the active compose session. Returns true
|
|
37
|
+
* when the message was buffered, false when no session was open (or the
|
|
38
|
+
* session timed out and was discarded). Callers use the return to
|
|
39
|
+
* decide whether to route the message through the regular inbound path
|
|
40
|
+
* instead.
|
|
41
|
+
*/
|
|
42
|
+
append(chatId: string, text: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Commit the active compose session. Returns the concatenated payload
|
|
45
|
+
* (drafts joined by `separator`) and clears the session, or undefined
|
|
46
|
+
* when no session was open / the session timed out. Default separator
|
|
47
|
+
* is "\n\n" — produces natural paragraph breaks between drafts.
|
|
48
|
+
*/
|
|
49
|
+
commit(chatId: string, separator?: string): string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Drop the compose session without committing. Returns true iff a
|
|
52
|
+
* session existed.
|
|
53
|
+
*/
|
|
54
|
+
cancel(chatId: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Lazy-expiring isActive. Returns true only when a non-stale session
|
|
57
|
+
* exists; stale sessions are dropped on the call.
|
|
58
|
+
*/
|
|
59
|
+
isActive(chatId: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Number of drafts buffered (without committing). Useful for /compose
|
|
62
|
+
* status surfaces.
|
|
63
|
+
*/
|
|
64
|
+
draftCount(chatId: string): number;
|
|
65
|
+
/**
|
|
66
|
+
* Inspect drafts without committing — used for preview UX. Returns a
|
|
67
|
+
* copy so callers can't mutate internal state.
|
|
68
|
+
*/
|
|
69
|
+
peek(chatId: string): string[];
|
|
70
|
+
private isExpired;
|
|
71
|
+
}
|