kiro-telegram-bot 1.6.0 → 1.7.2
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 +11 -0
- package/CHANGELOG.md +186 -0
- package/README.md +73 -16
- package/package.json +4 -1
- package/scripts/setup.mjs +51 -11
- package/src/acp/client.ts +110 -15
- package/src/app/auth-service.ts +325 -0
- package/src/app/instance-lock.ts +139 -0
- package/src/bot/auth.ts +14 -3
- package/src/bot/bot.ts +9 -0
- package/src/bot/chat-controller.ts +10 -0
- package/src/bot/commands.ts +1 -0
- package/src/bot/handlers/auth.ts +89 -0
- package/src/bot/handlers/control.ts +2 -2
- package/src/bot/handlers/kill.ts +1 -18
- package/src/bot/handlers/running.ts +2 -0
- package/src/bot/handlers/session-card.ts +16 -0
- package/src/bot/handlers/session-kill.ts +95 -0
- package/src/bot/handlers/sessions.ts +2 -1
- package/src/bot/menu/status-panel.ts +53 -16
- package/src/bot/prompt-content.ts +5 -0
- package/src/bot/reauth-controller.ts +462 -0
- package/src/bot/session-runtime.ts +55 -9
- package/src/cli.ts +5 -4
- package/src/config.ts +36 -14
- package/src/index.ts +15 -1
- package/src/render/device-flow.ts +76 -0
- package/src/render/progress-estimate.ts +63 -0
- package/src/render/progress.ts +80 -0
- package/src/service/windows.ts +116 -21
- package/src/sessions/history.ts +12 -1
- package/src/sessions/process.ts +30 -0
- package/src/stream/streamer.ts +73 -5
package/src/stream/streamer.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
import type { Api } from "grammy";
|
|
14
14
|
import { chunkMarkdown } from "../render/chunk.js";
|
|
15
15
|
import { toTelegramMarkdown } from "../render/markdown.js";
|
|
16
|
+
import { extractProgress, progressBar } from "../render/progress.js";
|
|
17
|
+
import { estimateProgress } from "../render/progress-estimate.js";
|
|
16
18
|
import { safeEdit, safeSend } from "../bot/telegram-io.js";
|
|
17
19
|
|
|
18
20
|
const SOFT_LIMIT = 3500;
|
|
@@ -32,6 +34,16 @@ export class ResponseStreamer {
|
|
|
32
34
|
private dirty = false;
|
|
33
35
|
private flushing = false;
|
|
34
36
|
private closed = false;
|
|
37
|
+
/** Latest task-progress % parsed from the agent's `{progress: N%}` markers
|
|
38
|
+
* (sticky across flushes; rendered as a bar on the live message). */
|
|
39
|
+
private progress: number | undefined;
|
|
40
|
+
/** True once the agent emitted a real `{progress}` marker — from then on its
|
|
41
|
+
* values are authoritative and the bot fallback stops contributing. */
|
|
42
|
+
private agentReported = false;
|
|
43
|
+
/** Real work signals for the fallback estimate (monotonic within a turn). */
|
|
44
|
+
private toolCalls = 0;
|
|
45
|
+
private outChars = 0;
|
|
46
|
+
private thoughtChars = 0;
|
|
35
47
|
|
|
36
48
|
constructor(
|
|
37
49
|
private readonly api: Api,
|
|
@@ -39,6 +51,11 @@ export class ResponseStreamer {
|
|
|
39
51
|
private readonly throttleMs: number,
|
|
40
52
|
private replyTo?: number,
|
|
41
53
|
private footer?: string,
|
|
54
|
+
private readonly onProgress?: (pct: number) => void,
|
|
55
|
+
/** Show a bot-computed bar when the agent emits no marker. */
|
|
56
|
+
private readonly fallbackEnabled = false,
|
|
57
|
+
/** Turn start time, used by the fallback's elapsed-time signal. */
|
|
58
|
+
private readonly turnStartedAt = Date.now(),
|
|
42
59
|
) {}
|
|
43
60
|
|
|
44
61
|
/** Replace the hashtag footer (used after a logical fork swaps the session id
|
|
@@ -52,6 +69,49 @@ export class ResponseStreamer {
|
|
|
52
69
|
return this.footer ? `\n\n${this.footer}` : "";
|
|
53
70
|
}
|
|
54
71
|
|
|
72
|
+
/** Strip `{progress: N%}` markers from rendered text, remembering the latest
|
|
73
|
+
* value (sticky across flushes) and notifying the owner when it changes. */
|
|
74
|
+
private captureProgress(text: string): string {
|
|
75
|
+
const { value, cleaned } = extractProgress(text);
|
|
76
|
+
if (value !== undefined) this.setProgressValue(value, true);
|
|
77
|
+
return cleaned;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Record a progress value, enforcing global monotonicity (never decreases)
|
|
81
|
+
* and notifying the owner on change. Agent markers are authoritative: once
|
|
82
|
+
* one arrives, the bot fallback stops contributing. */
|
|
83
|
+
private setProgressValue(pct: number, fromAgent: boolean): void {
|
|
84
|
+
if (fromAgent) this.agentReported = true;
|
|
85
|
+
const next = Math.max(this.progress ?? 0, Math.round(pct));
|
|
86
|
+
if (next === this.progress) return;
|
|
87
|
+
this.progress = next;
|
|
88
|
+
try {
|
|
89
|
+
this.onProgress?.(next);
|
|
90
|
+
} catch {
|
|
91
|
+
/* non-fatal */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Advance the fallback estimate from real activity signals, but only while
|
|
96
|
+
* the agent itself hasn't reported a value. No-op when fallback is off. */
|
|
97
|
+
private applyFallback(): void {
|
|
98
|
+
if (!this.fallbackEnabled || this.agentReported) return;
|
|
99
|
+
const est = estimateProgress({
|
|
100
|
+
toolCalls: this.toolCalls,
|
|
101
|
+
outputChars: this.outChars,
|
|
102
|
+
thoughtChars: this.thoughtChars,
|
|
103
|
+
elapsedMs: Date.now() - this.turnStartedAt,
|
|
104
|
+
});
|
|
105
|
+
if (est > 0) this.setProgressValue(est, false);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Called when the turn finishes successfully: if the agent never reported
|
|
109
|
+
* its own progress, fill the fallback bar to 100. No-op otherwise. */
|
|
110
|
+
completeFallback(): void {
|
|
111
|
+
if (!this.fallbackEnabled || this.agentReported) return;
|
|
112
|
+
this.setProgressValue(100, false);
|
|
113
|
+
}
|
|
114
|
+
|
|
55
115
|
/** reply_parameters threading EVERY message of the turn to the user's prompt,
|
|
56
116
|
* so the whole response (all bubbles, tool calls and continuations) stays in
|
|
57
117
|
* one thread — not just the first message. */
|
|
@@ -62,18 +122,21 @@ export class ResponseStreamer {
|
|
|
62
122
|
|
|
63
123
|
appendOutput(text: string): void {
|
|
64
124
|
if (!text) return;
|
|
125
|
+
this.outChars += text.length;
|
|
65
126
|
this.merge("out", text);
|
|
66
127
|
this.schedule();
|
|
67
128
|
}
|
|
68
129
|
|
|
69
130
|
appendThought(text: string): void {
|
|
70
131
|
if (!text) return;
|
|
132
|
+
this.thoughtChars += text.length;
|
|
71
133
|
this.merge("think", text);
|
|
72
134
|
this.schedule();
|
|
73
135
|
}
|
|
74
136
|
|
|
75
137
|
addTool(rawMarkdown: string): void {
|
|
76
138
|
if (!rawMarkdown) return;
|
|
139
|
+
this.toolCalls += 1;
|
|
77
140
|
this.segs.push({ kind: "tool", text: rawMarkdown });
|
|
78
141
|
this.schedule();
|
|
79
142
|
}
|
|
@@ -117,11 +180,16 @@ export class ResponseStreamer {
|
|
|
117
180
|
this.dirty = false;
|
|
118
181
|
try {
|
|
119
182
|
await this.sealOverflow();
|
|
120
|
-
const base = renderSegs(this.segs.slice(this.sealedIdx));
|
|
183
|
+
const base = this.captureProgress(renderSegs(this.segs.slice(this.sealedIdx)));
|
|
184
|
+
this.applyFallback();
|
|
185
|
+
// Never send an empty / progress-only bubble. The bar is appended only to
|
|
186
|
+
// real streamed content; the live status panel shows the standalone bar.
|
|
121
187
|
if (!base.trim()) return;
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
const
|
|
188
|
+
// The live (still-streaming) bubble carries the hashtag footer AND a fresh
|
|
189
|
+
// progress bar at the bottom (sealed bubbles below get neither bar).
|
|
190
|
+
const parts: string[] = [base];
|
|
191
|
+
if (this.progress !== undefined) parts.push(progressBar(this.progress));
|
|
192
|
+
const src = `${parts.join("\n\n")}${this.footerSuffix()}`;
|
|
125
193
|
const rendered = toTelegramMarkdown(src);
|
|
126
194
|
const chunks = chunkMarkdown(rendered);
|
|
127
195
|
const plain = chunkMarkdown(src);
|
|
@@ -158,7 +226,7 @@ export class ResponseStreamer {
|
|
|
158
226
|
}
|
|
159
227
|
|
|
160
228
|
private async seal(from: number, to: number): Promise<void> {
|
|
161
|
-
const base = renderSegs(this.segs.slice(from, to));
|
|
229
|
+
const base = this.captureProgress(renderSegs(this.segs.slice(from, to)));
|
|
162
230
|
if (!base.trim()) return;
|
|
163
231
|
// A sealed bubble is finished, so it carries the footer (hashtags).
|
|
164
232
|
const src = `${base}${this.footerSuffix()}`;
|