kiro-telegram-bot 1.5.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.
Files changed (83) hide show
  1. package/.env.example +104 -0
  2. package/LICENSE +21 -0
  3. package/README.md +517 -0
  4. package/bin/kiro-tg.mjs +21 -0
  5. package/docs/INSTALL.md +143 -0
  6. package/docs/ops/RELEASE_CHECKLIST.md +39 -0
  7. package/package.json +70 -0
  8. package/scripts/mq.ts +25 -0
  9. package/scripts/setup.mjs +78 -0
  10. package/src/acp/client.ts +456 -0
  11. package/src/acp/server-handlers.ts +85 -0
  12. package/src/acp/transport.ts +50 -0
  13. package/src/acp/types.ts +136 -0
  14. package/src/agents/catalog.ts +44 -0
  15. package/src/app/json-store.ts +54 -0
  16. package/src/app/reasoning.ts +30 -0
  17. package/src/app/settings-store.ts +31 -0
  18. package/src/app/stt.ts +53 -0
  19. package/src/app/types.ts +48 -0
  20. package/src/app/usage.ts +32 -0
  21. package/src/bot/auth.ts +27 -0
  22. package/src/bot/bot.ts +154 -0
  23. package/src/bot/chat-controller.ts +251 -0
  24. package/src/bot/commands.ts +48 -0
  25. package/src/bot/deps.ts +47 -0
  26. package/src/bot/handlers/control.ts +94 -0
  27. package/src/bot/handlers/history.ts +58 -0
  28. package/src/bot/handlers/kill.ts +69 -0
  29. package/src/bot/handlers/mcp.ts +205 -0
  30. package/src/bot/handlers/menu.ts +204 -0
  31. package/src/bot/handlers/message.ts +93 -0
  32. package/src/bot/handlers/photo.ts +108 -0
  33. package/src/bot/handlers/projects.ts +83 -0
  34. package/src/bot/handlers/running.ts +104 -0
  35. package/src/bot/handlers/session-card.ts +65 -0
  36. package/src/bot/handlers/sessions.ts +131 -0
  37. package/src/bot/handlers/system.ts +51 -0
  38. package/src/bot/handlers/tasks.ts +223 -0
  39. package/src/bot/handlers/usage.ts +33 -0
  40. package/src/bot/handlers/voice.ts +53 -0
  41. package/src/bot/image-return.ts +69 -0
  42. package/src/bot/menu/keyboard.ts +47 -0
  43. package/src/bot/menu/refresh.ts +13 -0
  44. package/src/bot/menu/status-panel.ts +78 -0
  45. package/src/bot/permission-service.ts +149 -0
  46. package/src/bot/prompt-content.ts +49 -0
  47. package/src/bot/prompt-retry.ts +70 -0
  48. package/src/bot/registry.ts +178 -0
  49. package/src/bot/session-runtime.ts +670 -0
  50. package/src/bot/telegram-io.ts +109 -0
  51. package/src/bot/typing.ts +35 -0
  52. package/src/bot/wizard/task-wizard.ts +214 -0
  53. package/src/cli.ts +125 -0
  54. package/src/config.ts +190 -0
  55. package/src/index.ts +74 -0
  56. package/src/logger.ts +78 -0
  57. package/src/mcp/config.ts +103 -0
  58. package/src/mcp/probe.ts +218 -0
  59. package/src/mcp/types.ts +68 -0
  60. package/src/projects/manager.ts +88 -0
  61. package/src/render/chunk.ts +57 -0
  62. package/src/render/diff.ts +48 -0
  63. package/src/render/escape.ts +22 -0
  64. package/src/render/markdown.ts +126 -0
  65. package/src/render/subagent.ts +75 -0
  66. package/src/render/tool-call.ts +102 -0
  67. package/src/service/index.ts +24 -0
  68. package/src/service/linux.ts +83 -0
  69. package/src/service/macos.ts +91 -0
  70. package/src/service/platform.ts +59 -0
  71. package/src/service/types.ts +34 -0
  72. package/src/service/windows.ts +103 -0
  73. package/src/sessions/history.ts +181 -0
  74. package/src/sessions/store.ts +133 -0
  75. package/src/sessions/tail.ts +86 -0
  76. package/src/sessions/types.ts +26 -0
  77. package/src/stream/streamer.ts +167 -0
  78. package/src/tasks/runner.ts +82 -0
  79. package/src/tasks/schedule.ts +142 -0
  80. package/src/tasks/scheduler.ts +53 -0
  81. package/src/tasks/store.ts +80 -0
  82. package/src/tasks/types.ts +33 -0
  83. package/tsconfig.json +19 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Tracks one ChatController per Telegram chat (each controlling one or more
3
+ * sessions). `get(chatId)` returns the chat's foreground SessionRuntime so the
4
+ * existing handlers keep operating on "the current session".
5
+ *
6
+ * It also owns **subagent attribution**: Kiro reports a single, process-global
7
+ * subagent list (with no parent session id on the wire), so we attribute new
8
+ * subagents to the chat whose turn is currently running (most-recent first).
9
+ * That mapping drives both subagent *visibility* (routed to the owner's
10
+ * foreground runtime) and *permission* routing (a subagent's permission request
11
+ * is asked of its parent chat).
12
+ */
13
+ import type { Api } from "grammy";
14
+ import type { AcpClient } from "../acp/client.js";
15
+ import type { PendingStage, SubagentInfo } from "../acp/types.js";
16
+ import type { SettingsStore } from "../app/settings-store.js";
17
+ import type { AppConfig } from "../config.js";
18
+ import { subagentSummary } from "../render/subagent.js";
19
+ import type { SessionStore } from "../sessions/store.js";
20
+ import { ChatController } from "./chat-controller.js";
21
+ import type { SessionRuntime } from "./session-runtime.js";
22
+
23
+ export interface SessionDescription {
24
+ /** Chat that owns the session (controlled session or subagent parent). */
25
+ chatId?: number;
26
+ /** True when this is a session the chat directly controls. */
27
+ controlled: boolean;
28
+ /** True when this is a subagent of a controlled turn. */
29
+ subagent: boolean;
30
+ projectName?: string;
31
+ subagentName?: string;
32
+ }
33
+
34
+ export class RuntimeRegistry {
35
+ private readonly controllers = new Map<number, ChatController>();
36
+ private refresher: ((chatId: number) => void) | undefined;
37
+ /** Chat ids with a running turn, most-recently-started last. */
38
+ private readonly activeChats: number[] = [];
39
+ /** Subagent sessionId -> owner chat id. */
40
+ private readonly subagentParents = new Map<string, number>();
41
+
42
+ constructor(
43
+ private readonly api: Api,
44
+ private readonly acp: AcpClient,
45
+ private readonly cfg: AppConfig,
46
+ private readonly settings: SettingsStore,
47
+ private readonly store: SessionStore,
48
+ ) {
49
+ this.acp.on("subagents", (subagents, pending) => this.onSubagents(subagents, pending));
50
+ }
51
+
52
+ setRefresher(fn: (chatId: number) => void): void {
53
+ this.refresher = fn;
54
+ }
55
+
56
+ controller(chatId: number): ChatController {
57
+ let c = this.controllers.get(chatId);
58
+ if (!c) {
59
+ c = new ChatController(
60
+ this.api,
61
+ chatId,
62
+ this.acp,
63
+ this.cfg,
64
+ this.settings,
65
+ this.store,
66
+ (id) => this.refresher?.(id),
67
+ (busy) => this.noteActivity(chatId, busy),
68
+ );
69
+ this.controllers.set(chatId, c);
70
+ }
71
+ return c;
72
+ }
73
+
74
+ /** The chat's foreground runtime (backward-compatible with existing handlers). */
75
+ get(chatId: number): SessionRuntime {
76
+ return this.controller(chatId).foreground();
77
+ }
78
+
79
+ disposeAll(): void {
80
+ for (const c of this.controllers.values()) c.dispose();
81
+ this.controllers.clear();
82
+ }
83
+
84
+ /** Find the chat that currently controls a given session id. */
85
+ findChatBySession(sessionId: string): number | undefined {
86
+ for (const [chatId, c] of this.controllers) {
87
+ if (c.findBySession(sessionId)) return chatId;
88
+ }
89
+ return undefined;
90
+ }
91
+
92
+ isControlledSession(sessionId: string): boolean {
93
+ return this.findChatBySession(sessionId) !== undefined;
94
+ }
95
+
96
+ /**
97
+ * The chat a session belongs to for permission/routing purposes: a directly
98
+ * controlled session, otherwise the parent chat of a subagent.
99
+ */
100
+ ownerChatForSession(sessionId: string): number | undefined {
101
+ return this.findChatBySession(sessionId) ?? this.subagentParents.get(sessionId);
102
+ }
103
+
104
+ /** Describe a session so a permission prompt can label it correctly. */
105
+ describeSession(sessionId: string): SessionDescription {
106
+ const controlledChat = this.findChatBySession(sessionId);
107
+ if (controlledChat !== undefined) {
108
+ const project = this.controller(controlledChat)
109
+ .list()
110
+ .find((s) => s.sessionId === sessionId)?.projectName;
111
+ return { chatId: controlledChat, controlled: true, subagent: false, projectName: project };
112
+ }
113
+ const parent = this.subagentParents.get(sessionId);
114
+ const info = this.acp.subagentById(sessionId);
115
+ if (parent !== undefined || info) {
116
+ return {
117
+ chatId: parent,
118
+ controlled: false,
119
+ subagent: true,
120
+ subagentName: info?.sessionName || info?.agentName || sessionId.slice(0, 8),
121
+ };
122
+ }
123
+ return { controlled: false, subagent: false };
124
+ }
125
+
126
+ /** Subagent summary line for a chat's status panel, or undefined. */
127
+ subagentSummaryForChat(chatId: number): string | undefined {
128
+ const mine = this.acp.currentSubagents().filter((s) => this.subagentParents.get(s.sessionId) === chatId);
129
+ if (mine.length === 0) return undefined;
130
+ return subagentSummary(mine, this.acp.currentPendingStages());
131
+ }
132
+
133
+ // ── subagent attribution ─────────────────────────────────────────────────
134
+
135
+ private noteActivity(chatId: number, busy: boolean): void {
136
+ const i = this.activeChats.indexOf(chatId);
137
+ if (i !== -1) this.activeChats.splice(i, 1);
138
+ if (busy) this.activeChats.push(chatId);
139
+ }
140
+
141
+ /** The chat most likely to own freshly-spawned subagents. */
142
+ private currentOwner(): number | undefined {
143
+ return this.activeChats.at(-1);
144
+ }
145
+
146
+ private onSubagents(subagents: SubagentInfo[], pending: PendingStage[]): void {
147
+ const owner = this.currentOwner();
148
+ // Record parents for any subagent we haven't attributed yet.
149
+ if (owner !== undefined) {
150
+ for (const s of subagents) {
151
+ if (!this.subagentParents.has(s.sessionId)) this.subagentParents.set(s.sessionId, owner);
152
+ }
153
+ }
154
+ // Group by attributed chat and route visibility to each owner's foreground.
155
+ const byChat = new Map<number, SubagentInfo[]>();
156
+ for (const s of subagents) {
157
+ const chatId = this.subagentParents.get(s.sessionId);
158
+ if (chatId === undefined) continue;
159
+ const arr = byChat.get(chatId);
160
+ if (arr) arr.push(s);
161
+ else byChat.set(chatId, [s]);
162
+ }
163
+ for (const [chatId, list] of byChat) {
164
+ try {
165
+ this.controller(chatId).foreground().renderSubagents(list, pending);
166
+ } catch {
167
+ /* non-fatal */
168
+ }
169
+ this.refresher?.(chatId);
170
+ }
171
+ // Prune mappings for subagents no longer present (they're terminated and
172
+ // won't issue further permission requests) so the map stays bounded.
173
+ const live = new Set(subagents.map((s) => s.sessionId));
174
+ for (const sid of this.subagentParents.keys()) {
175
+ if (!live.has(sid)) this.subagentParents.delete(sid);
176
+ }
177
+ }
178
+ }