grok-telegram-bot 2.3.1 → 2.4.0
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/.env.example +26 -0
- package/CHANGELOG.md +37 -0
- package/package.json +1 -1
- package/scripts/analyze-jsonl.ts +33 -0
- package/scripts/delayed-restart.ps1 +29 -0
- package/scripts/probe-exit-response-shape.py +77 -0
- package/scripts/probe-plan-exit.py +60 -0
- package/scripts/probe-plan-exit2.py +48 -0
- package/scripts/probe-plan-fields.py +41 -0
- package/scripts/probe-plan-fields2.py +58 -0
- package/scripts/probe-plan-response-path.py +48 -0
- package/scripts/sample-claude-tooluse.ts +21 -0
- package/scripts/sample-kiro-events.ts +31 -0
- package/scripts/smoke-exit-plan.ts +274 -0
- package/scripts/smoke-exit-shapes.ts +252 -0
- package/scripts/smoke-import.mjs +82 -0
- package/scripts/smoke-import.ts +73 -0
- package/src/app/accounts.ts +84 -0
- package/src/app/instance-lock.ts +6 -0
- package/src/app/types.ts +19 -2
- package/src/app/updater.ts +17 -6
- package/src/app/usage.ts +204 -7
- package/src/bot/account-rotator.ts +10 -0
- package/src/bot/bot.ts +36 -0
- package/src/bot/chat-controller.ts +35 -0
- package/src/bot/commands.ts +2 -0
- package/src/bot/complexity-gate.ts +69 -0
- package/src/bot/deps.ts +19 -0
- package/src/bot/handlers/accounts.ts +51 -1
- package/src/bot/handlers/import-session.ts +290 -0
- package/src/bot/handlers/menu.ts +17 -38
- package/src/bot/handlers/message.ts +1 -0
- package/src/bot/handlers/running.ts +35 -5
- package/src/bot/handlers/session-card.ts +12 -0
- package/src/bot/handlers/sessions.ts +14 -3
- package/src/bot/handlers/usage.ts +118 -16
- package/src/bot/menu/keyboard.ts +5 -4
- package/src/bot/menu/status-panel.ts +19 -6
- package/src/bot/prompt-content.ts +4 -0
- package/src/bot/session-fork.ts +11 -0
- package/src/bot/session-runtime.ts +740 -58
- package/src/bot/suggestions.ts +429 -0
- package/src/config.ts +41 -0
- package/src/grok/client.ts +91 -16
- package/src/grok/plan-approval.ts +72 -0
- package/src/grok/session-log.ts +16 -0
- package/src/grok/types.ts +21 -2
- package/src/import/build-import.ts +132 -0
- package/src/import/history-readers.ts +681 -0
- package/src/import/list-running.ts +100 -0
- package/src/import/sources.ts +78 -0
- package/src/index.ts +179 -24
- package/src/render/diff.ts +11 -2
- package/src/render/file-summary.ts +31 -1
- package/src/render/markdown.ts +293 -35
- package/src/render/plan.ts +127 -0
- package/src/render/session-comment.ts +261 -0
- package/src/render/tool-call-detail.ts +400 -19
- package/src/render/tool-call-merge.ts +115 -0
- package/src/render/tool-call.ts +405 -142
- package/src/render/truncate.ts +85 -0
- package/src/service/windows.ts +14 -2
- package/src/sessions/history.ts +57 -0
- package/src/sessions/store.ts +3 -0
- package/src/sessions/types.ts +5 -0
- package/src/stream/streamer.ts +73 -9
- package/src/tasks/runner.ts +4 -3
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* /usage —
|
|
2
|
+
* /usage — Grok CLI live monthly quota + session context + bot-tracked
|
|
3
|
+
* per-account turn stats.
|
|
3
4
|
*/
|
|
4
5
|
import type { Bot, Context } from "grammy";
|
|
6
|
+
import type { StoredAccount } from "../../app/accounts.js";
|
|
7
|
+
import { formatCliBillingLines } from "../../app/usage.js";
|
|
5
8
|
import type { BotDeps } from "../deps.js";
|
|
6
9
|
|
|
7
10
|
export async function showUsage(ctx: Context, deps: BotDeps): Promise<void> {
|
|
@@ -10,29 +13,128 @@ export async function showUsage(ctx: Context, deps: BotDeps): Promise<void> {
|
|
|
10
13
|
const acct = await deps.usage.account();
|
|
11
14
|
const meta = rt.contextInfo();
|
|
12
15
|
const ctx100 = meta?.contextUsagePercentage;
|
|
13
|
-
const
|
|
16
|
+
const list = deps.accounts.list();
|
|
17
|
+
const activeId = deps.accounts.activeAccountId();
|
|
18
|
+
const activeMeta = activeId ? deps.accounts.get(activeId) : undefined;
|
|
19
|
+
const { billing, error: billingError } = await deps.usage.cliBilling();
|
|
14
20
|
|
|
15
|
-
const lines = [
|
|
16
|
-
"\u{1F4CA} Usage &
|
|
17
|
-
acct?.email ? `\u{1F464} ${acct.email}` : "",
|
|
18
|
-
acct?.accountType ? `\u{1F511} ${acct.accountType}${acct.region ? ` \u00B7 ${acct.region}` : ""}` : "",
|
|
21
|
+
const lines: string[] = [
|
|
22
|
+
"\u{1F4CA} Usage & accounts",
|
|
19
23
|
"",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
"\u{1F464} Current login",
|
|
25
|
+
acct?.email ? ` ${acct.email}` : " (identity unknown)",
|
|
26
|
+
];
|
|
27
|
+
if (acct?.accountType) {
|
|
28
|
+
lines.push(` \u{1F511} ${acct.accountType}${acct.region ? ` \u00B7 ${acct.region}` : ""}`);
|
|
29
|
+
}
|
|
30
|
+
if (acct?.teamId) lines.push(` Team: ${acct.teamId.slice(0, 8)}\u2026`);
|
|
31
|
+
if (activeMeta) {
|
|
32
|
+
const uLine = deps.accounts.formatUsageLine(activeMeta);
|
|
33
|
+
lines.push(` Saved as: ${activeMeta.label}`);
|
|
34
|
+
if (uLine) lines.push(` Bot-tracked: ${uLine}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
lines.push("");
|
|
38
|
+
if (billing) {
|
|
39
|
+
lines.push(...formatCliBillingLines(billing));
|
|
40
|
+
} else {
|
|
41
|
+
lines.push(
|
|
42
|
+
"\u{1F4B3} Grok CLI monthly quota",
|
|
43
|
+
` \u26A0\uFE0F ${billingError || "unavailable"}`,
|
|
44
|
+
" (Requires `grok login` OIDC token — not XAI_API_KEY alone.)",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
lines.push(
|
|
27
49
|
"",
|
|
28
|
-
"\
|
|
29
|
-
|
|
50
|
+
"\u{1F9F5} This session (ACP)",
|
|
51
|
+
` Id: ${rt.sessionId ? rt.sessionId.slice(0, 8) : "none"}`,
|
|
52
|
+
` Model: ${rt.model || "default"}`,
|
|
53
|
+
` Context used: ${ctx100 !== undefined ? `${ctx100.toFixed(0)}%` : "\u2014"}`,
|
|
54
|
+
` Turns this session: ${rt.turns}`,
|
|
55
|
+
);
|
|
56
|
+
if (meta?.credits !== undefined) {
|
|
57
|
+
lines.push(` Credits (session report): ${fmtNum(meta.credits)}`);
|
|
58
|
+
}
|
|
59
|
+
if (meta?.effort) lines.push(` Effort: ${meta.effort}`);
|
|
60
|
+
if (meta?.totalTokens !== undefined) {
|
|
61
|
+
lines.push(` Tokens (session report): ${meta.totalTokens.toLocaleString("en-US")}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (list.length > 0) {
|
|
65
|
+
lines.push("", "\u{1F465} Saved accounts (bot-tracked turns on this machine)");
|
|
66
|
+
for (const a of list) {
|
|
67
|
+
lines.push(accountUsageBlock(deps, a, a.id === activeId));
|
|
68
|
+
}
|
|
69
|
+
const totals = aggregate(list);
|
|
70
|
+
lines.push(
|
|
71
|
+
"",
|
|
72
|
+
`\u{1F4CA} Bot totals: ${totals.turns} turn${totals.turns === 1 ? "" : "s"}` +
|
|
73
|
+
(totals.credits > 0 ? ` \u00B7 ${fmtNum(totals.credits)} session credits` : "") +
|
|
74
|
+
` \u00B7 ${totals.withUsage}/${list.length} accounts used`,
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
lines.push("", "\u{1F465} No saved accounts yet \u2014 /accounts to save & switch.");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
lines.push(
|
|
81
|
+
"",
|
|
82
|
+
"\u2139\uFE0F Monthly quota is live from Grok CLI (`cli-chat-proxy` billing). Bot-tracked turns are local to this bot.",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!acct) lines.splice(3, 0, " (account info unavailable \u2014 is grok logged in?)");
|
|
30
86
|
|
|
31
|
-
if (!acct) lines.splice(1, 0, "(account info unavailable \u2014 is grok logged in?)");
|
|
32
87
|
await deps.ephemeral.open(ctx);
|
|
33
88
|
await deps.ephemeral.reply(ctx, lines.join("\n"));
|
|
34
89
|
}
|
|
35
90
|
|
|
91
|
+
function accountUsageBlock(deps: BotDeps, a: StoredAccount, active: boolean): string {
|
|
92
|
+
const mark = a.warning ? "\u26A0\uFE0F" : active ? "\u2705" : "\u{1F464}";
|
|
93
|
+
const u = a.usage;
|
|
94
|
+
const head = `${mark} ${a.label}${active ? " (active)" : ""}`;
|
|
95
|
+
if (!u || (u.turns <= 0 && u.credits <= 0 && !u.lastUsedAt)) {
|
|
96
|
+
return `${head}\n no bot-tracked usage yet`;
|
|
97
|
+
}
|
|
98
|
+
const bits: string[] = [];
|
|
99
|
+
bits.push(`${u.turns || 0} turn${(u.turns || 0) === 1 ? "" : "s"}`);
|
|
100
|
+
if (u.credits > 0) bits.push(`${fmtNum(u.credits)} session credits`);
|
|
101
|
+
if (u.lastTurnCredits !== undefined) bits.push(`last turn ${fmtNum(u.lastTurnCredits)}`);
|
|
102
|
+
if (u.lastContextPct !== undefined) bits.push(`last ctx ${u.lastContextPct.toFixed(0)}%`);
|
|
103
|
+
if (u.lastUsedAt) bits.push(`last ${shortWhen(u.lastUsedAt)}`);
|
|
104
|
+
return `${head}\n ${bits.join(" \u00B7 ")}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function aggregate(list: StoredAccount[]): { turns: number; credits: number; withUsage: number } {
|
|
108
|
+
let turns = 0;
|
|
109
|
+
let credits = 0;
|
|
110
|
+
let withUsage = 0;
|
|
111
|
+
for (const a of list) {
|
|
112
|
+
const u = a.usage;
|
|
113
|
+
if (!u) continue;
|
|
114
|
+
turns += u.turns || 0;
|
|
115
|
+
credits += u.credits || 0;
|
|
116
|
+
if (u.turns > 0 || u.credits > 0 || u.lastUsedAt) withUsage++;
|
|
117
|
+
}
|
|
118
|
+
return { turns, credits, withUsage };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function fmtNum(n: number): string {
|
|
122
|
+
if (!Number.isFinite(n)) return String(n);
|
|
123
|
+
if (Number.isInteger(n)) return n.toLocaleString("en-US");
|
|
124
|
+
return n.toFixed(2);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function shortWhen(iso: string): string {
|
|
128
|
+
const t = Date.parse(iso);
|
|
129
|
+
if (!Number.isFinite(t)) return iso.slice(0, 10);
|
|
130
|
+
const sec = Math.max(0, Math.round((Date.now() - t) / 1000));
|
|
131
|
+
if (sec < 60) return "just now";
|
|
132
|
+
if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
|
|
133
|
+
if (sec < 86_400) return `${Math.floor(sec / 3600)}h ago`;
|
|
134
|
+
if (sec < 86_400 * 14) return `${Math.floor(sec / 86_400)}d ago`;
|
|
135
|
+
return new Date(t).toISOString().slice(0, 10);
|
|
136
|
+
}
|
|
137
|
+
|
|
36
138
|
export function registerUsage(bot: Bot, deps: BotDeps): void {
|
|
37
139
|
bot.command("usage", (ctx) => showUsage(ctx, deps));
|
|
38
140
|
}
|
package/src/bot/menu/keyboard.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Menu surfaces:
|
|
3
3
|
* - a tiny PERSISTENT bar (☰ Menu · 🧭 Running · ⏹ Stop) — minimal footprint;
|
|
4
4
|
* - a full, organized INLINE menu opened on demand (and hideable).
|
|
5
|
-
* Live state (project/
|
|
6
|
-
* so the bar stays clean.
|
|
5
|
+
* Live state (project/model/reasoning/context) lives in the pinned panel,
|
|
6
|
+
* so the bar stays clean. (Agent picker removed — Grok has no useful headless
|
|
7
|
+
* agent switch; plan mode is entered automatically when the agent judges the task complex.)
|
|
7
8
|
*/
|
|
8
9
|
import { InlineKeyboard, Keyboard } from "grammy";
|
|
9
10
|
|
|
@@ -18,7 +19,7 @@ export function compactKeyboard(): Keyboard {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/** The full, grouped inline menu (opened via ☰ Menu or /menu). */
|
|
21
|
-
export function mainMenuInline(state: {
|
|
22
|
+
export function mainMenuInline(state: { model: string; reasoning: string }): InlineKeyboard {
|
|
22
23
|
const t = (s: string, n: number): string => (s.length > n ? s.slice(0, n - 1) + "\u2026" : s);
|
|
23
24
|
return new InlineKeyboard()
|
|
24
25
|
.text("\u{1F4C1} Project", "m:project")
|
|
@@ -27,7 +28,7 @@ export function mainMenuInline(state: { agent: string; model: string; reasoning:
|
|
|
27
28
|
.text("\u{1F9ED} Running", "m:running")
|
|
28
29
|
.text("\u{1F5C2} Sessions", "m:sessions")
|
|
29
30
|
.row()
|
|
30
|
-
.text(
|
|
31
|
+
.text("\u{1F4E5} Import session", "m:import")
|
|
31
32
|
.row()
|
|
32
33
|
.text(`\u{1F9E9} Model \u00B7 ${t(state.model, 24)}`, "m:model")
|
|
33
34
|
.row()
|
|
@@ -49,11 +49,15 @@ export class StatusPanel {
|
|
|
49
49
|
const SEP = " | "; // pipe delimiter between inline fields
|
|
50
50
|
const lines: string[] = [];
|
|
51
51
|
|
|
52
|
-
// 1)
|
|
53
|
-
//
|
|
52
|
+
// 1) Active plan board first (always above the progress bar) so the user
|
|
53
|
+
// always sees done / in-progress / pending steps while a plan is live.
|
|
54
|
+
const plan = rt.planBoard;
|
|
55
|
+
if (plan) lines.push(plan);
|
|
56
|
+
|
|
57
|
+
// 2) Progress — only while a turn is live (cleared when it ends).
|
|
54
58
|
if (progress !== undefined) lines.push(`\u{1F4C8} ${progressBar(progress)}`);
|
|
55
59
|
|
|
56
|
-
//
|
|
60
|
+
// 3) Activity: state + only the counters that currently apply.
|
|
57
61
|
const activity: string[] = [rt.isBusy ? "\u23F3 Working" : "\u2705 Idle"];
|
|
58
62
|
if (rt.queueLength > 0) activity.push(`\u{1F4E5} ${rt.queueLength} queued`);
|
|
59
63
|
if (running > 1) activity.push(`\u{1F9ED} ${running} sessions`);
|
|
@@ -61,13 +65,22 @@ export class StatusPanel {
|
|
|
61
65
|
if (subagents) activity.push(`\u{1F465} ${subagents}`);
|
|
62
66
|
lines.push(activity.join(SEP));
|
|
63
67
|
|
|
64
|
-
//
|
|
68
|
+
// 3b) What is happening now / last summary (same source as Running cards).
|
|
69
|
+
// Prefer plan one-liner over a redundant tool-step when a plan is active.
|
|
70
|
+
const comment = rt.planSummary && rt.isBusy ? undefined : rt.cardComment;
|
|
71
|
+
if (comment) {
|
|
72
|
+
const icon = rt.isBusy ? "\u23F3" : "\u{1F4AC}";
|
|
73
|
+
const shown = comment.length > 120 ? `${comment.slice(0, 119)}\u2026` : comment;
|
|
74
|
+
lines.push(`${icon} ${shown}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4) Where: project | session | context usage.
|
|
65
78
|
const loc = [`\u{1F4C1} ${project}`, `\u{1F9F5} ${session}`];
|
|
66
79
|
if (ctxPct !== undefined) loc.push(`\u{1F4CA} ${ctxPct.toFixed(0)}% context`);
|
|
67
80
|
lines.push(loc.join(SEP));
|
|
68
81
|
|
|
69
|
-
//
|
|
70
|
-
lines.push([`\u{
|
|
82
|
+
// 5) How: reasoning | model (agent picker removed — plan mode is automatic).
|
|
83
|
+
lines.push([`\u{1F9E0} ${reasoningLabel(s.reasoning)}`, `\u{1F9E9} ${s.model || "default"}`].join(SEP));
|
|
71
84
|
|
|
72
85
|
return lines.join("\n");
|
|
73
86
|
}
|
|
@@ -66,6 +66,9 @@ export function mergeInputs(inputs: PromptInput[]): PromptInput {
|
|
|
66
66
|
const quotes = inputs
|
|
67
67
|
.map((i) => i.quotedText?.trim())
|
|
68
68
|
.filter((q): q is string => !!q);
|
|
69
|
+
// Preserve meta flags: auto-suggestion batches / self-recheck must not re-arm
|
|
70
|
+
// another recheck after merge (dropping this caused infinite recheck loops).
|
|
71
|
+
const skipSelfRecheck = inputs.some((i) => i.skipSelfRecheck);
|
|
69
72
|
return {
|
|
70
73
|
text: inputs
|
|
71
74
|
.map((i) => i.text)
|
|
@@ -75,6 +78,7 @@ export function mergeInputs(inputs: PromptInput[]): PromptInput {
|
|
|
75
78
|
resourceLinks: inputs.flatMap((i) => i.resourceLinks ?? []),
|
|
76
79
|
replyTo: inputs.find((i) => i.replyTo !== undefined)?.replyTo,
|
|
77
80
|
quotedText: quotes.length > 0 ? [...new Set(quotes)].join("\n\n---\n\n") : undefined,
|
|
81
|
+
skipSelfRecheck: skipSelfRecheck || undefined,
|
|
78
82
|
};
|
|
79
83
|
}
|
|
80
84
|
|
package/src/bot/session-fork.ts
CHANGED
|
@@ -33,3 +33,14 @@ export function buildPriming(transcript: string): string {
|
|
|
33
33
|
"=== END TRANSCRIPT ===",
|
|
34
34
|
].join("\n");
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* First-turn prompt body used after a foreign-session import. The heavy
|
|
39
|
+
* transcript lives in {@link SessionRuntime}'s primingContext; this is the
|
|
40
|
+
* short user message that flushes priming into the live Grok session.
|
|
41
|
+
*/
|
|
42
|
+
export const IMPORT_CONFIRM_PROMPT =
|
|
43
|
+
"Session import complete. Confirm you have the full imported context " +
|
|
44
|
+
"(read the transcript file if anything was truncated inline). " +
|
|
45
|
+
"Reply with a one-line ready confirmation: project path + one-sentence " +
|
|
46
|
+
"summary of the task so far. Do not continue work until I send the next message.";
|