grok-telegram-bot 2.3.0 → 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 +55 -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 +71 -2
- 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 +55 -5
- 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/reauth-controller.ts +2 -2
- package/src/bot/session-fork.ts +11 -0
- package/src/bot/session-runtime.ts +831 -64
- package/src/bot/suggestions.ts +429 -0
- package/src/config.ts +41 -0
- package/src/grok/client.ts +106 -20
- 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
|
@@ -7,7 +7,7 @@ import { type Bot, type Context, InlineKeyboard } from "grammy";
|
|
|
7
7
|
import type { RunningSession, SwitchResult } from "../chat-controller.js";
|
|
8
8
|
import type { BotDeps } from "../deps.js";
|
|
9
9
|
import type { HistoryEntry } from "../../sessions/types.js";
|
|
10
|
-
import { jsonlMtimeMs, readFirstPrompt } from "../../sessions/history.js";
|
|
10
|
+
import { jsonlMtimeMs, readFirstPrompt, readLastCardSummary } from "../../sessions/history.js";
|
|
11
11
|
import { progressBar } from "../../render/progress.js";
|
|
12
12
|
import { refreshMenu } from "../menu/refresh.js";
|
|
13
13
|
import { sendMarkdownDoc } from "../telegram-io.js";
|
|
@@ -49,27 +49,47 @@ function cleanPrompt(raw: string): string {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/** Build a rich card (plain text, no MarkdownV2) + buttons for one controlled
|
|
52
|
-
* session: Switch / History / Close.
|
|
52
|
+
* session: Switch / History / Close.
|
|
53
|
+
*
|
|
54
|
+
* The comment line always answers "what is happening / what was solved":
|
|
55
|
+
* busy → live current step (tool / thinking / working on …)
|
|
56
|
+
* idle → last-turn outcome (assistant result + files), never bare import noise
|
|
57
|
+
*/
|
|
53
58
|
function buildRunningCard(s: RunningSession, deps: BotDeps, now: number): { text: string; kb: InlineKeyboard } {
|
|
54
59
|
const dot = s.foreground ? "\u25B6\uFE0F" : s.busy ? "\u{1F7E0}" : "\u26AA";
|
|
55
60
|
const state = s.foreground ? "foreground" : s.busy ? "working" : "idle";
|
|
56
61
|
|
|
57
62
|
let when = "new";
|
|
58
|
-
let
|
|
63
|
+
let historySummary = "";
|
|
64
|
+
let firstPrompt = "";
|
|
59
65
|
if (s.sessionId) {
|
|
60
66
|
const path = deps.store.jsonlPath(s.sessionId);
|
|
61
67
|
const mtime = jsonlMtimeMs(path);
|
|
62
68
|
if (mtime) when = timeAgo(now - mtime);
|
|
63
|
-
prompt
|
|
69
|
+
// Last assistant outcome beats first-prompt import-confirm noise.
|
|
70
|
+
historySummary = readLastCardSummary(path);
|
|
71
|
+
firstPrompt = cleanPrompt(readFirstPrompt(path));
|
|
72
|
+
if (/session import complete/i.test(firstPrompt)) firstPrompt = "";
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
const diskComment = s.sessionId ? deps.store.get(s.sessionId)?.comment?.trim() : undefined;
|
|
76
|
+
// Order: live runtime comment → persisted last-turn summary → history tail → first prompt.
|
|
77
|
+
const comment =
|
|
78
|
+
(s.comment && s.comment.trim()) ||
|
|
79
|
+
diskComment ||
|
|
80
|
+
historySummary ||
|
|
81
|
+
firstPrompt;
|
|
82
|
+
|
|
66
83
|
const meta = [when, state];
|
|
67
84
|
if (s.busy) meta.push("\u23F3");
|
|
68
85
|
if (s.unread > 0) meta.push(`${s.unread} \u{1F4EC} unread`);
|
|
69
86
|
|
|
87
|
+
const commentLabel = s.busy ? "\u23F3" : "\u{1F4AC}";
|
|
70
88
|
const lines = [
|
|
71
89
|
`${dot} ${s.projectName}`,
|
|
72
|
-
|
|
90
|
+
comment
|
|
91
|
+
? `${commentLabel} ${trunc(comment, 200)}`
|
|
92
|
+
: "\u{1F4AC} (no messages yet)",
|
|
73
93
|
`\u{1F552} ${meta.join(" \u00B7 ")}`,
|
|
74
94
|
];
|
|
75
95
|
if (s.progress !== undefined) lines.push(`\u{1F4C8} ${progressBar(s.progress)}`);
|
|
@@ -171,6 +191,16 @@ async function deliverSwitch(ctx: Context, deps: BotDeps, res: SwitchResult): Pr
|
|
|
171
191
|
if (!res.busy && res.rt.lastTurnSummary) {
|
|
172
192
|
await ctx.reply(res.rt.lastTurnSummary);
|
|
173
193
|
}
|
|
194
|
+
|
|
195
|
+
// Re-show post-turn suggestions generated while this session was in the
|
|
196
|
+
// background (or if the user missed the Done notify). Buttons stay wired to
|
|
197
|
+
// the same batch ids so taps still submit the follow-up.
|
|
198
|
+
if (!res.busy) {
|
|
199
|
+
const sug = res.rt.peekPendingSuggestions();
|
|
200
|
+
if (sug) {
|
|
201
|
+
await ctx.reply(sug.text, { reply_markup: sug.markup }).catch(() => {});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
174
204
|
}
|
|
175
205
|
|
|
176
206
|
function fmtEntry(e: HistoryEntry): string {
|
|
@@ -10,6 +10,7 @@ import { InlineKeyboard } from "grammy";
|
|
|
10
10
|
import { basename } from "node:path";
|
|
11
11
|
import { progressBar } from "../../render/progress.js";
|
|
12
12
|
import type { SessionMeta } from "../../sessions/types.js";
|
|
13
|
+
// Note: callers may pass `comment` from runtime or history (last-turn outcome).
|
|
13
14
|
|
|
14
15
|
export interface SessionCardExtras {
|
|
15
16
|
/** Context-usage %, when the session is loaded in the current ACP process. */
|
|
@@ -22,6 +23,11 @@ export interface SessionCardExtras {
|
|
|
22
23
|
selfPid?: number;
|
|
23
24
|
/** Latest task-completion % (0–100) for this session, if this chat runs it. */
|
|
24
25
|
progress?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Live step (while working) or chat summary (when idle). Overrides
|
|
28
|
+
* `m.comment` when provided by the controlling chat runtime.
|
|
29
|
+
*/
|
|
30
|
+
comment?: string;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export interface SessionCard {
|
|
@@ -34,9 +40,15 @@ export function buildSessionCard(m: SessionMeta, extra: SessionCardExtras = {}):
|
|
|
34
40
|
const dot = m.active ? "\u{1F7E2}" : "\u26AA";
|
|
35
41
|
const state = m.active ? `running${m.lockPid ? ` \u00B7 pid ${m.lockPid}` : ""}` : "idle";
|
|
36
42
|
const proj = m.cwd ? basename(m.cwd) : "(no project)";
|
|
43
|
+
const comment = (extra.comment || m.comment || "").trim();
|
|
37
44
|
|
|
38
45
|
const lines = [`${dot} ${m.title}`, `\u{1F4C1} ${proj}`];
|
|
39
46
|
if (m.cwd) lines.push(` ${m.cwd}`);
|
|
47
|
+
// Always surface "what is happening / what was done" when we have it.
|
|
48
|
+
if (comment) {
|
|
49
|
+
const icon = m.active || typeof extra.progress === "number" ? "\u23F3" : "\u{1F4AC}";
|
|
50
|
+
lines.push(`${icon} ${comment.length > 160 ? comment.slice(0, 159) + "\u2026" : comment}`);
|
|
51
|
+
}
|
|
40
52
|
lines.push(`\u{1F552} updated ${relTime(m.updatedAt)} \u00B7 created ${relTime(m.createdAt)}`);
|
|
41
53
|
const ctx = typeof extra.contextPct === "number" ? ` \u00B7 \u{1F9E0} ctx ${Math.round(extra.contextPct)}%` : "";
|
|
42
54
|
lines.push(`\u{1F4CA} ${state} \u00B7 \u{1F4DC} history ${humanSize(m.historyBytes)}${ctx}`);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { type Bot, type Context, InlineKeyboard } from "grammy";
|
|
11
11
|
import { basename } from "node:path";
|
|
12
12
|
import type { BotDeps } from "../deps.js";
|
|
13
|
-
import { readHistory } from "../../sessions/history.js";
|
|
13
|
+
import { readHistory, readLastCardSummary } from "../../sessions/history.js";
|
|
14
14
|
import type { SessionMeta } from "../../sessions/types.js";
|
|
15
15
|
import { refreshMenu } from "../menu/refresh.js";
|
|
16
16
|
import { showHistory } from "./history.js";
|
|
@@ -52,8 +52,19 @@ async function renderSessionPage(ctx: Context, deps: BotDeps, page: number): Pro
|
|
|
52
52
|
|
|
53
53
|
for (const m of slice) {
|
|
54
54
|
const contextPct = deps.acp.metadataFor(m.sessionId)?.contextUsagePercentage;
|
|
55
|
-
const
|
|
56
|
-
const
|
|
55
|
+
const ctrl = deps.registry.controller(ctx.chat!.id);
|
|
56
|
+
const progress = ctrl.progressFor(m.sessionId);
|
|
57
|
+
// Live runtime → persisted comment → last assistant outcome from history.
|
|
58
|
+
const comment =
|
|
59
|
+
ctrl.commentFor(m.sessionId) ||
|
|
60
|
+
m.comment ||
|
|
61
|
+
readLastCardSummary(deps.store.jsonlPath(m.sessionId));
|
|
62
|
+
const { text, keyboard } = buildSessionCard(m, {
|
|
63
|
+
contextPct,
|
|
64
|
+
selfPid: deps.acp.pid,
|
|
65
|
+
progress,
|
|
66
|
+
comment,
|
|
67
|
+
});
|
|
57
68
|
await deps.ephemeral.reply(ctx, text, { reply_markup: keyboard });
|
|
58
69
|
}
|
|
59
70
|
|
|
@@ -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
|
|
|
@@ -180,7 +180,7 @@ export class ReauthController {
|
|
|
180
180
|
}
|
|
181
181
|
s.phase = "restarting";
|
|
182
182
|
await this.render(s);
|
|
183
|
-
await this.grok.start();
|
|
183
|
+
await this.grok.start(true);
|
|
184
184
|
agentDown = false;
|
|
185
185
|
s.accountLabel = accountLabel(await this.getAccount?.().catch(() => undefined));
|
|
186
186
|
s.phase = "done";
|
|
@@ -191,7 +191,7 @@ export class ReauthController {
|
|
|
191
191
|
} finally {
|
|
192
192
|
s.abort = undefined;
|
|
193
193
|
this.stopAnim(s);
|
|
194
|
-
if (agentDown) await this.grok.start().catch((e) => log.warn("post-reauth restart failed:", (e as Error).message));
|
|
194
|
+
if (agentDown) await this.grok.start(true).catch((e) => log.warn("post-reauth restart failed:", (e as Error).message));
|
|
195
195
|
await this.render(s);
|
|
196
196
|
}
|
|
197
197
|
}
|
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.";
|