talon-agent 4.3.2 → 4.3.3

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 (61) hide show
  1. package/package.json +1 -1
  2. package/src/backend/claude-sdk/doctor.ts +1 -1
  3. package/src/backend/codex/doctor.ts +1 -1
  4. package/src/backend/openai-agents/doctor.ts +1 -1
  5. package/src/bootstrap.ts +1 -1
  6. package/src/cli/doctor.ts +1 -1
  7. package/src/core/agent-runtime/backend-registry.ts +1 -1
  8. package/src/core/auth/expiry-monitor.ts +1 -1
  9. package/src/core/{doctor.ts → doctor/index.ts} +11 -11
  10. package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
  11. package/src/core/plugin/native-runtimes.ts +1 -1
  12. package/src/frontend/discord/commands/admin.ts +1 -1
  13. package/src/frontend/discord/render.ts +1 -1
  14. package/src/frontend/telegram/actions/index.ts +1 -1
  15. package/src/frontend/telegram/actions/moderation/chat.ts +63 -0
  16. package/src/frontend/telegram/actions/moderation/index.ts +74 -0
  17. package/src/frontend/telegram/actions/moderation/members.ts +77 -0
  18. package/src/frontend/telegram/actions/moderation/permissions.ts +69 -0
  19. package/src/frontend/telegram/actions/moderation/topics.ts +48 -0
  20. package/src/frontend/telegram/actions/moderation/types.ts +16 -0
  21. package/src/frontend/telegram/admin/background.ts +66 -0
  22. package/src/frontend/telegram/admin/chunked-reply.ts +20 -0
  23. package/src/frontend/telegram/admin/health.ts +81 -0
  24. package/src/frontend/telegram/admin/sessions.ts +105 -0
  25. package/src/frontend/telegram/admin.ts +56 -261
  26. package/src/frontend/telegram/callbacks/model/backend.ts +156 -0
  27. package/src/frontend/telegram/callbacks/model/control.ts +29 -0
  28. package/src/frontend/telegram/callbacks/model/nav.ts +54 -0
  29. package/src/frontend/telegram/callbacks/model/select.ts +100 -0
  30. package/src/frontend/telegram/callbacks/model/types.ts +39 -0
  31. package/src/frontend/telegram/callbacks/model/views.ts +116 -0
  32. package/src/frontend/telegram/callbacks/model.ts +61 -331
  33. package/src/frontend/telegram/commands/admin.ts +108 -77
  34. package/src/frontend/telegram/commands/settings.ts +65 -43
  35. package/src/frontend/telegram/commands/whatsapp-pairing.ts +1 -1
  36. package/src/frontend/telegram/helpers/diagnostics.ts +1 -1
  37. package/src/frontend/telegram/middleware.ts +166 -201
  38. package/src/frontend/terminal/builtins/context.ts +184 -0
  39. package/src/frontend/terminal/builtins/help.ts +35 -0
  40. package/src/frontend/terminal/builtins/index.ts +23 -0
  41. package/src/frontend/terminal/builtins/model.ts +198 -0
  42. package/src/frontend/terminal/builtins/session.ts +101 -0
  43. package/src/frontend/terminal/builtins/status.ts +202 -0
  44. package/src/frontend/terminal/command-registry.ts +80 -0
  45. package/src/frontend/terminal/commands.ts +11 -674
  46. package/src/frontend/terminal/input/history.ts +64 -0
  47. package/src/frontend/terminal/input/keys.ts +143 -0
  48. package/src/frontend/terminal/input/parts.ts +33 -0
  49. package/src/frontend/terminal/input/paste.ts +53 -0
  50. package/src/frontend/terminal/input/render.ts +42 -0
  51. package/src/frontend/terminal/input/state.ts +60 -0
  52. package/src/frontend/terminal/input.ts +27 -342
  53. package/src/frontend/whatsapp/connection.ts +1 -1
  54. package/src/frontend/whatsapp/index.ts +1 -1
  55. package/src/frontend/whatsapp/pairing-service.ts +1 -1
  56. package/src/plugins/github/provision.ts +1 -1
  57. package/src/plugins/mempalace/provision.ts +1 -1
  58. package/src/plugins/playwright/provision.ts +1 -1
  59. package/src/frontend/telegram/actions/moderation.ts +0 -255
  60. /package/src/core/{doctor-types.ts → doctor/types.ts} +0 -0
  61. /package/src/core/{pairing-broker.ts → frontend-runtime/pairing-broker.ts} +0 -0
@@ -4,9 +4,18 @@
4
4
  * Input is a list of parts: text segments and collapsed paste blocks.
5
5
  * You can type, paste, type more, paste again. Backspace removes from the end.
6
6
  * Enter submits everything. Ctrl+U clears all. Up/Down walk prompt history.
7
+ *
8
+ * State lives in `input/state.ts`; paste, keys and drawing are each their own
9
+ * module under `input/`. This file is the wiring.
7
10
  */
8
11
 
9
- import pc from "picocolors";
12
+ import { createInputState } from "./input/state.js";
13
+ import { consumePasteChunk } from "./input/paste.js";
14
+ import { handleKeyChunk } from "./input/keys.js";
15
+ import { redraw } from "./input/render.js";
16
+
17
+ export type { InputPart } from "./input/parts.js";
18
+ export { PromptHistory } from "./input/history.js";
10
19
 
11
20
  // ── Types ────────────────────────────────────────────────────────────────────
12
21
 
@@ -19,232 +28,10 @@ export type InputHandler = {
19
28
  resume(): void;
20
29
  };
21
30
 
22
- export type InputPart =
23
- { type: "text"; content: string } | { type: "paste"; content: string };
24
- type TextPart = Extract<InputPart, { type: "text" }>;
25
- type PastePart = Extract<InputPart, { type: "paste" }>;
26
-
27
- // ── Constants ────────────────────────────────────────────────────────────────
28
-
29
- const PASTE_COLLAPSE_LINES = 3;
30
- const PASTE_COLLAPSE_CHARS = 150;
31
- const PASTE_START = "\x1b[200~";
32
- const PASTE_END = "\x1b[201~";
33
- const MAX_HISTORY_ITEMS = 100;
34
-
35
- function cloneParts(parts: readonly InputPart[]): InputPart[] {
36
- return parts.map((part) => ({ ...part }));
37
- }
38
-
39
- function partsText(parts: readonly InputPart[]): string {
40
- return parts
41
- .map((part) => part.content)
42
- .join("\n")
43
- .trim();
44
- }
45
-
46
- /**
47
- * In-process prompt history. Keeping this out of the persistent stores avoids
48
- * creating a second plaintext transcript while still giving terminal users
49
- * the expected Up/Down workflow.
50
- */
51
- export class PromptHistory {
52
- private readonly entries: InputPart[][] = [];
53
- private cursor = 0;
54
- private draft: InputPart[] | null = null;
55
-
56
- constructor(private readonly maxItems = MAX_HISTORY_ITEMS) {}
57
-
58
- add(parts: readonly InputPart[]): void {
59
- const text = partsText(parts);
60
- if (!text) return;
61
-
62
- const latest = this.entries.at(-1);
63
- if (!latest || partsText(latest) !== text) {
64
- this.entries.push(cloneParts(parts));
65
- if (this.entries.length > this.maxItems) this.entries.shift();
66
- }
67
- this.resetNavigation();
68
- }
69
-
70
- move(
71
- direction: "previous" | "next",
72
- current: readonly InputPart[],
73
- ): InputPart[] | undefined {
74
- if (this.entries.length === 0) return undefined;
75
-
76
- if (direction === "previous") {
77
- if (this.cursor === this.entries.length) {
78
- this.draft = cloneParts(current);
79
- }
80
- if (this.cursor > 0) this.cursor--;
81
- return cloneParts(this.entries[this.cursor]!);
82
- }
83
-
84
- if (this.cursor === this.entries.length) return undefined;
85
- if (this.cursor < this.entries.length - 1) {
86
- this.cursor++;
87
- return cloneParts(this.entries[this.cursor]!);
88
- }
89
-
90
- this.cursor = this.entries.length;
91
- return cloneParts(this.draft ?? [{ type: "text", content: "" }]);
92
- }
93
-
94
- /** Preserve an edited recalled prompt as the current draft. */
95
- detach(parts: readonly InputPart[]): void {
96
- if (this.cursor === this.entries.length) return;
97
- this.cursor = this.entries.length;
98
- this.draft = cloneParts(parts);
99
- }
100
-
101
- resetNavigation(): void {
102
- this.cursor = this.entries.length;
103
- this.draft = null;
104
- }
105
- }
106
-
107
31
  // ── Factory ──────────────────────────────────────────────────────────────────
108
32
 
109
33
  export function createInput(promptStr: string): InputHandler {
110
- let lineCallback: ((text: string) => void) | null = null;
111
- let pendingResolve: ((value: string) => void) | null = null;
112
- let paused = false;
113
-
114
- // Input is an ordered list of parts
115
- let parts: InputPart[] = [{ type: "text", content: "" }];
116
- const history = new PromptHistory();
117
-
118
- // Bracketed paste accumulation
119
- let inPaste = false;
120
- let pasteAccum = "";
121
-
122
- // ── Helpers ──
123
-
124
- function lastPart(): InputPart {
125
- return parts[parts.length - 1]!;
126
- }
127
-
128
- /** Ensure the last part is a text part (for typing into). */
129
- function ensureTrailingText(): TextPart {
130
- const last = lastPart();
131
- if (last.type === "text") return last;
132
- const t: TextPart = { type: "text", content: "" };
133
- parts.push(t);
134
- return t;
135
- }
136
-
137
- function pasteTag(p: PastePart): string {
138
- const lines = p.content.split("\n").length;
139
- return lines > 1
140
- ? `[Pasted ~${lines} lines]`
141
- : `[Pasted ${p.content.length} chars]`;
142
- }
143
-
144
- // ── Drawing ──
145
- // Track how many visual rows the last render occupied.
146
- // Move up to the first row, clear to end of screen, rewrite.
147
-
148
- let prevRows = 1;
149
-
150
- function redraw(): void {
151
- const cols = process.stdout.columns || 80;
152
-
153
- // Build display string and measure visible length (strip ANSI)
154
- let display = promptStr;
155
- let visLen = 4; // " ❯ " = 4 visible chars
156
- for (const p of parts) {
157
- if (p.type === "text") {
158
- display += p.content;
159
- visLen += p.content.length;
160
- } else {
161
- const tag = pasteTag(p);
162
- display += pc.dim(tag);
163
- visLen += tag.length;
164
- }
165
- }
166
-
167
- // Move cursor to start of the previous render, clear to end of screen
168
- if (prevRows > 1) {
169
- process.stdout.write(`\x1b[${prevRows - 1}A`); // move up
170
- }
171
- process.stdout.write(`\r\x1b[J${display}\x1b[?25h`); // col 0, clear to EOS, write, show cursor
172
-
173
- prevRows = Math.max(1, Math.ceil(visLen / cols));
174
- }
175
-
176
- function getFullText(): string {
177
- return partsText(parts);
178
- }
179
-
180
- function clear(): void {
181
- parts = [{ type: "text", content: "" }];
182
- history.resetNavigation();
183
- }
184
-
185
- function submit(): void {
186
- const text = getFullText();
187
- if (!pendingResolve) history.add(parts);
188
- clear();
189
- process.stdout.write("\n");
190
-
191
- if (pendingResolve) {
192
- const resolve = pendingResolve;
193
- pendingResolve = null;
194
- resolve(text);
195
- return;
196
- }
197
- if (lineCallback) lineCallback(text);
198
- }
199
-
200
- function handlePasteComplete(text: string): void {
201
- const lineCount = text.split("\n").length;
202
- if (
203
- lineCount >= PASTE_COLLAPSE_LINES ||
204
- text.length > PASTE_COLLAPSE_CHARS
205
- ) {
206
- // Collapse into a paste part
207
- parts.push({ type: "paste", content: text });
208
- } else {
209
- // Short paste — inline into current text part
210
- ensureTrailingText().content += text.replace(/\n/g, " ");
211
- }
212
- history.detach(parts);
213
- redraw();
214
- }
215
-
216
- function handleBackspace(): void {
217
- const last = lastPart();
218
- if (last.type === "text" && last.content.length > 0) {
219
- // Delete last char from text
220
- last.content = last.content.slice(0, -1);
221
- } else if (
222
- last.type === "text" &&
223
- last.content === "" &&
224
- parts.length > 1
225
- ) {
226
- // Empty trailing text — remove it, then remove the paste before it
227
- parts.pop();
228
- parts.pop();
229
- // Ensure we always have at least one text part
230
- if (parts.length === 0) parts.push({ type: "text", content: "" });
231
- ensureTrailingText();
232
- } else if (last.type === "paste") {
233
- // Remove the paste block
234
- parts.pop();
235
- if (parts.length === 0) parts.push({ type: "text", content: "" });
236
- ensureTrailingText();
237
- }
238
- history.detach(parts);
239
- redraw();
240
- }
241
-
242
- function navigateHistory(direction: "previous" | "next"): void {
243
- const recalled = history.move(direction, parts);
244
- if (!recalled) return;
245
- parts = recalled;
246
- redraw();
247
- }
34
+ const state = createInputState(promptStr);
248
35
 
249
36
  // ── Raw mode ──
250
37
 
@@ -256,140 +43,38 @@ export function createInput(promptStr: string): InputHandler {
256
43
  process.stdout.write("\x1b[?2004h");
257
44
 
258
45
  process.stdin.on("data", (chunk: string) => {
259
- if (paused) return;
260
-
261
- // ── Bracketed paste ──
262
- if (chunk.includes(PASTE_START)) {
263
- inPaste = true;
264
- pasteAccum = chunk.split(PASTE_START).slice(1).join(PASTE_START);
265
- if (pasteAccum.includes(PASTE_END)) {
266
- inPaste = false;
267
- handlePasteComplete(pasteAccum.split(PASTE_END)[0]!);
268
- pasteAccum = "";
269
- }
270
- return;
271
- }
272
- if (inPaste) {
273
- if (chunk.includes(PASTE_END)) {
274
- pasteAccum += chunk.split(PASTE_END)[0]!;
275
- inPaste = false;
276
- handlePasteComplete(pasteAccum);
277
- pasteAccum = "";
278
- } else {
279
- pasteAccum += chunk;
280
- }
281
- return;
282
- }
283
-
284
- // ── Normal input ──
285
- for (let i = 0; i < chunk.length; i++) {
286
- const ch = chunk[i]!;
287
- const code = chunk.charCodeAt(i);
288
-
289
- if (code === 0x03) {
290
- // Ctrl+C
291
- process.stdout.write("\n\x1b[?2004l");
292
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
293
- process.exit(0);
294
- }
295
-
296
- if (code === 0x15) {
297
- // Ctrl+U
298
- clear();
299
- redraw();
300
- continue;
301
- }
302
-
303
- if (code === 0x0d || code === 0x0a) {
304
- // Enter
305
- if (getFullText()) {
306
- submit();
307
- } else {
308
- process.stdout.write("\n");
309
- redraw();
310
- }
311
- continue;
312
- }
313
-
314
- if (code === 0x7f || code === 0x08) {
315
- // Backspace
316
- handleBackspace();
317
- continue;
318
- }
319
-
320
- if (code === 0x1b) {
321
- if (i + 1 < chunk.length && chunk[i + 1] === "[") {
322
- // CSI escape sequence. Up/Down navigate history; the rest are
323
- // ignored until cursor editing is implemented.
324
- let end = i + 2;
325
- while (end < chunk.length && chunk.charCodeAt(end) < 0x40) end++;
326
- const final = chunk[end];
327
- if (final === "A") navigateHistory("previous");
328
- if (final === "B") navigateHistory("next");
329
- i = end;
330
- } else if (
331
- i + 2 < chunk.length &&
332
- chunk[i + 1] === "O" &&
333
- (chunk[i + 2] === "A" || chunk[i + 2] === "B")
334
- ) {
335
- // Some terminals use application-cursor sequences (ESC O A/B).
336
- navigateHistory(chunk[i + 2] === "A" ? "previous" : "next");
337
- i += 2;
338
- } else if (pendingResolve) {
339
- // Bare Escape during waitForInput — cancel
340
- const resolve = pendingResolve;
341
- pendingResolve = null;
342
- clear();
343
- process.stdout.write("\n");
344
- resolve("");
345
- }
346
- continue;
347
- }
348
-
349
- if (code === 0x09) {
350
- // Tab
351
- ensureTrailingText().content += " ";
352
- history.detach(parts);
353
- redraw();
354
- continue;
355
- }
356
-
357
- if (code < 0x20) continue;
358
-
359
- // Printable char
360
- ensureTrailingText().content += ch;
361
- history.detach(parts);
362
- redraw();
363
- }
46
+ if (state.paused) return;
47
+ if (consumePasteChunk(state, chunk)) return;
48
+ handleKeyChunk(state, chunk);
364
49
  });
365
50
 
366
51
  return {
367
52
  onLine(callback) {
368
- lineCallback = callback;
53
+ state.lineCallback = callback;
369
54
  },
370
55
  prompt() {
371
- paused = false;
372
- prevRows = 1; // fresh prompt = 1 row
373
- redraw();
56
+ state.paused = false;
57
+ state.prevRows = 1; // fresh prompt = 1 row
58
+ redraw(state);
374
59
  },
375
60
  waitForInput(): Promise<string> {
376
61
  return new Promise((resolve) => {
377
- pendingResolve = resolve;
378
- paused = false;
379
- prevRows = 1;
380
- redraw();
62
+ state.pendingResolve = resolve;
63
+ state.paused = false;
64
+ state.prevRows = 1;
65
+ redraw(state);
381
66
  });
382
67
  },
383
68
  pause() {
384
- paused = true;
69
+ state.paused = true;
385
70
  },
386
71
  resume() {
387
- paused = false;
72
+ state.paused = false;
388
73
  },
389
74
  close() {
390
- if (pendingResolve) {
391
- const resolve = pendingResolve;
392
- pendingResolve = null;
75
+ if (state.pendingResolve) {
76
+ const resolve = state.pendingResolve;
77
+ state.pendingResolve = null;
393
78
  resolve("");
394
79
  }
395
80
  process.stdout.write("\x1b[?2004l");
@@ -11,7 +11,7 @@ import makeWASocket, { type AuthenticationState, type WASocket } from "baileys";
11
11
  import qrcode from "qrcode-terminal";
12
12
  import { log, logError, logWarn } from "../../util/log.js";
13
13
  import { dirs } from "../../util/paths.js";
14
- import { notifyAdmin } from "../../core/notify.js";
14
+ import { notifyAdmin } from "../../core/frontend-runtime/admin-notify.js";
15
15
  import { useAtomicAuthState } from "./auth-state.js";
16
16
  import { bareId } from "./identity.js";
17
17
  import { handleInbound } from "./inbound.js";
@@ -32,7 +32,7 @@ import {
32
32
  pruneWhatsAppMessages,
33
33
  } from "../../storage/whatsapp-messages.js";
34
34
  import { flushAuthWrites } from "./auth-state.js";
35
- import { registerPairingProvider } from "../../core/pairing-broker.js";
35
+ import { registerPairingProvider } from "../../core/frontend-runtime/pairing-broker.js";
36
36
  import { beginPairingAttempt } from "./pairing-service.js";
37
37
  import { runConnectionLoop } from "./connection.js";
38
38
  import { lookupWhatsAppChat, type WhatsAppChatInfo } from "./registry.js";
@@ -16,7 +16,7 @@ import QRCode from "qrcode";
16
16
  import type {
17
17
  PairingAttempt,
18
18
  PairingOutcome,
19
- } from "../../core/pairing-broker.js";
19
+ } from "../../core/frontend-runtime/pairing-broker.js";
20
20
  import { log, logError } from "../../util/log.js";
21
21
  import { dirs } from "../../util/paths.js";
22
22
  import { useAtomicAuthState, flushAuthWrites } from "./auth-state.js";
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { join } from "node:path";
12
- import type { DoctorCheck } from "../../core/doctor.js";
12
+ import type { DoctorCheck } from "../../core/doctor/index.js";
13
13
  import {
14
14
  failDetail,
15
15
  loadProvisionState,
@@ -23,7 +23,7 @@
23
23
  import { existsSync } from "node:fs";
24
24
  import { homedir } from "node:os";
25
25
  import { dirname, join, resolve } from "node:path";
26
- import type { DoctorCheck } from "../../core/doctor.js";
26
+ import type { DoctorCheck } from "../../core/doctor/index.js";
27
27
  import {
28
28
  compareVersions,
29
29
  expandHome,
@@ -16,7 +16,7 @@
16
16
  import { existsSync, readFileSync, readdirSync } from "node:fs";
17
17
  import { homedir } from "node:os";
18
18
  import { dirname, join, resolve } from "node:path";
19
- import type { DoctorCheck } from "../../core/doctor.js";
19
+ import type { DoctorCheck } from "../../core/doctor/index.js";
20
20
  import {
21
21
  failDetail,
22
22
  loadProvisionState,