talon-agent 4.3.1 → 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.
- package/package.json +1 -1
- package/src/app.ts +1 -1
- package/src/backend/claude-sdk/doctor.ts +1 -1
- package/src/backend/codex/doctor.ts +1 -1
- package/src/backend/openai-agents/doctor.ts +1 -1
- package/src/bootstrap.ts +3 -3
- package/src/cli/doctor.ts +1 -1
- package/src/cli/skill.ts +2 -2
- package/src/core/agent-runtime/backend-registry.ts +1 -1
- package/src/core/auth/expiry-monitor.ts +1 -1
- package/src/core/background/cron-spec.ts +1 -1
- package/src/core/background/cron.ts +1 -1
- package/src/core/background/heartbeat/agent.ts +1 -1
- package/src/core/background/triggers/command.ts +1 -1
- package/src/core/background/triggers/exit.ts +1 -1
- package/src/core/background/triggers/output.ts +1 -1
- package/src/core/background/triggers/resume.ts +1 -1
- package/src/core/background/triggers/spawn.ts +1 -1
- package/src/core/{doctor.ts → doctor/index.ts} +11 -11
- package/src/core/engine/gateway-actions/cron.ts +2 -2
- package/src/core/engine/gateway-actions/goals.ts +2 -2
- package/src/core/engine/gateway-actions/scripts.ts +1 -1
- package/src/core/engine/gateway-actions/skills.ts +1 -1
- package/src/core/engine/gateway-actions/triggers.ts +2 -2
- package/src/core/engine/gateway-actions/{shared.ts → validation.ts} +2 -2
- package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
- package/src/core/plugin/native-runtimes.ts +1 -1
- package/src/core/prompt/assemble.ts +2 -2
- package/src/core/scripts/runner.ts +1 -1
- package/src/core/soul/forgetting.ts +2 -2
- package/src/core/soul/salience.ts +8 -4
- package/src/frontend/discord/actions/{shared.ts → buttons.ts} +3 -56
- package/src/frontend/discord/actions/channels.ts +34 -0
- package/src/frontend/discord/actions/chat-info.ts +1 -1
- package/src/frontend/discord/actions/index.ts +1 -1
- package/src/frontend/discord/actions/media.ts +1 -1
- package/src/frontend/discord/actions/messaging.ts +4 -2
- package/src/frontend/discord/actions/try-action.ts +24 -0
- package/src/frontend/discord/admin.ts +2 -2
- package/src/frontend/discord/callbacks/components/settings.ts +1 -1
- package/src/frontend/discord/callbacks/modals.ts +1 -1
- package/src/frontend/discord/commands/admin.ts +3 -3
- package/src/frontend/discord/commands/info.ts +2 -2
- package/src/frontend/discord/commands/{shared.ts → interaction.ts} +2 -1
- package/src/frontend/discord/commands/router.ts +1 -1
- package/src/frontend/discord/commands/session.ts +2 -2
- package/src/frontend/discord/commands/settings.ts +2 -2
- package/src/frontend/discord/{helpers.ts → render.ts} +3 -2
- package/src/frontend/native/extensions.ts +1 -1
- package/src/frontend/teams/chat-discovery.ts +69 -0
- package/src/frontend/teams/commands.ts +141 -0
- package/src/frontend/teams/index.ts +24 -350
- package/src/frontend/teams/outbound.ts +44 -0
- package/src/frontend/teams/poll.ts +101 -0
- package/src/frontend/teams/runtime.ts +51 -0
- package/src/frontend/teams/turn.ts +97 -0
- package/src/frontend/telegram/actions/index.ts +1 -1
- package/src/frontend/telegram/actions/messaging.ts +1 -1
- package/src/frontend/telegram/actions/moderation/chat.ts +63 -0
- package/src/frontend/telegram/actions/moderation/index.ts +74 -0
- package/src/frontend/telegram/actions/moderation/members.ts +77 -0
- package/src/frontend/telegram/actions/moderation/permissions.ts +69 -0
- package/src/frontend/telegram/actions/moderation/topics.ts +48 -0
- package/src/frontend/telegram/actions/moderation/types.ts +16 -0
- package/src/frontend/telegram/actions/shared.ts +4 -4
- package/src/frontend/telegram/admin/background.ts +66 -0
- package/src/frontend/telegram/admin/chunked-reply.ts +20 -0
- package/src/frontend/telegram/admin/health.ts +81 -0
- package/src/frontend/telegram/admin/sessions.ts +105 -0
- package/src/frontend/telegram/admin.ts +56 -261
- package/src/frontend/telegram/callbacks/model/backend.ts +156 -0
- package/src/frontend/telegram/callbacks/model/control.ts +29 -0
- package/src/frontend/telegram/callbacks/model/nav.ts +54 -0
- package/src/frontend/telegram/callbacks/model/select.ts +100 -0
- package/src/frontend/telegram/callbacks/model/types.ts +39 -0
- package/src/frontend/telegram/callbacks/model/views.ts +116 -0
- package/src/frontend/telegram/callbacks/model.ts +61 -331
- package/src/frontend/telegram/commands/admin.ts +108 -77
- package/src/frontend/telegram/commands/settings.ts +65 -43
- package/src/frontend/telegram/commands/whatsapp-pairing.ts +1 -1
- package/src/frontend/telegram/helpers/diagnostics.ts +1 -1
- package/src/frontend/telegram/middleware.ts +166 -201
- package/src/frontend/telegram/sticker-library.ts +2 -2
- package/src/frontend/terminal/builtins/context.ts +184 -0
- package/src/frontend/terminal/builtins/help.ts +35 -0
- package/src/frontend/terminal/builtins/index.ts +23 -0
- package/src/frontend/terminal/builtins/model.ts +198 -0
- package/src/frontend/terminal/builtins/session.ts +101 -0
- package/src/frontend/terminal/builtins/status.ts +202 -0
- package/src/frontend/terminal/command-registry.ts +80 -0
- package/src/frontend/terminal/commands.ts +11 -674
- package/src/frontend/terminal/input/history.ts +64 -0
- package/src/frontend/terminal/input/keys.ts +143 -0
- package/src/frontend/terminal/input/parts.ts +33 -0
- package/src/frontend/terminal/input/paste.ts +53 -0
- package/src/frontend/terminal/input/render.ts +42 -0
- package/src/frontend/terminal/input/state.ts +60 -0
- package/src/frontend/terminal/input.ts +27 -342
- package/src/frontend/whatsapp/actions/account.ts +3 -6
- package/src/frontend/whatsapp/actions/chat-info.ts +2 -1
- package/src/frontend/whatsapp/actions/coerce.ts +30 -0
- package/src/frontend/whatsapp/actions/history.ts +1 -1
- package/src/frontend/whatsapp/actions/media-upload.ts +82 -0
- package/src/frontend/whatsapp/actions/media.ts +3 -8
- package/src/frontend/whatsapp/actions/messaging.ts +2 -7
- package/src/frontend/whatsapp/actions/moderation.ts +2 -1
- package/src/frontend/whatsapp/actions/{shared.ts → send.ts} +4 -124
- package/src/frontend/whatsapp/actions/try-action.ts +24 -0
- package/src/frontend/whatsapp/commands.ts +1 -1
- package/src/frontend/whatsapp/connection.ts +1 -1
- package/src/frontend/whatsapp/inbound.ts +1 -1
- package/src/frontend/whatsapp/index.ts +2 -2
- package/src/frontend/whatsapp/pairing-service.ts +1 -1
- package/src/plugins/github/provision.ts +1 -1
- package/src/plugins/mempalace/provision.ts +1 -1
- package/src/plugins/playwright/provision.ts +1 -1
- package/src/storage/repositories/cron-repo.ts +1 -1
- package/src/storage/repositories/goals-repo.ts +1 -1
- package/src/storage/repositories/scripts-repo.ts +1 -1
- package/src/storage/repositories/triggers-repo.ts +1 -1
- package/src/storage/sql/schema.sql +1 -1
- package/src/storage/sql/statements.generated.ts +2 -2
- package/src/storage/sql/triggers.sql +1 -1
- package/src/frontend/telegram/actions/moderation.ts +0 -255
- /package/src/core/{doctor-types.ts → doctor/types.ts} +0 -0
- /package/src/core/{pairing-broker.ts → frontend-runtime/pairing-broker.ts} +0 -0
- /package/src/storage/{cron-store.ts → cron.ts} +0 -0
- /package/src/storage/{goal-store.ts → goals.ts} +0 -0
- /package/src/storage/{scheduled-store.ts → scheduled.ts} +0 -0
- /package/src/storage/{script-store.ts → scripts.ts} +0 -0
- /package/src/storage/{skill-store.ts → skills.ts} +0 -0
- /package/src/storage/{sticker-store.ts → stickers.ts} +0 -0
- /package/src/storage/{trigger-store.ts → triggers.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
|
|
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
|
-
|
|
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
|
-
|
|
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");
|
|
@@ -16,12 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
import { jidNormalizedUser, type WASocket } from "baileys";
|
|
18
18
|
import type { ActionResult } from "../../../core/types.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
toUserJid,
|
|
23
|
-
tryAction,
|
|
24
|
-
} from "./shared.js";
|
|
19
|
+
import { tryAction } from "./try-action.js";
|
|
20
|
+
import { resolveMediaUpload } from "./media-upload.js";
|
|
21
|
+
import { readStatusText, toUserJid } from "./coerce.js";
|
|
25
22
|
import type { WhatsAppActionContext, WhatsAppActionHandlers } from "./types.js";
|
|
26
23
|
|
|
27
24
|
/** The account's own JID, device suffix stripped. */
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* retrieval (get_message_by_id, download_media) lives in history.ts.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { tryAction } from "./try-action.js";
|
|
11
|
+
import { readStatusText, toUserJid } from "./coerce.js";
|
|
11
12
|
import type { WhatsAppActionHandlers } from "./types.js";
|
|
12
13
|
|
|
13
14
|
/** Render one participant the way the other frontends render members. */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape coercion for the member-facing WhatsApp actions: model-supplied user
|
|
3
|
+
* references → JIDs, and Baileys' several `fetchStatus` result shapes → text.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Coerce a user reference the model supplied — bare number, JID, or the
|
|
8
|
+
* numeric id from a member listing — into a WhatsApp user JID.
|
|
9
|
+
*/
|
|
10
|
+
export function toUserJid(value: unknown): string | null {
|
|
11
|
+
const raw = String(value ?? "").trim();
|
|
12
|
+
if (!raw) return null;
|
|
13
|
+
if (raw.includes("@")) return raw;
|
|
14
|
+
const digits = raw.replace(/[^0-9]/g, "");
|
|
15
|
+
return digits ? `${digits}@s.whatsapp.net` : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pull the about text out of a `fetchStatus` result. Baileys has
|
|
20
|
+
* returned both a bare object and a one-element array across versions,
|
|
21
|
+
* and the payload itself nests `status` either as a string or as
|
|
22
|
+
* `{ status }` — accept all four shapes rather than guess one.
|
|
23
|
+
*/
|
|
24
|
+
export function readStatusText(result: unknown): string | undefined {
|
|
25
|
+
const entry = (Array.isArray(result) ? result[0] : result) as
|
|
26
|
+
{ status?: string | { status?: string } } | undefined;
|
|
27
|
+
return typeof entry?.status === "string"
|
|
28
|
+
? entry.status
|
|
29
|
+
: entry?.status?.status;
|
|
30
|
+
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "../../../storage/history.js";
|
|
20
20
|
import { saveInboundMedia } from "../media-store.js";
|
|
21
21
|
import { lookupMessage, resolveKey } from "../message-store.js";
|
|
22
|
-
import { tryAction } from "./
|
|
22
|
+
import { tryAction } from "./try-action.js";
|
|
23
23
|
import type { WhatsAppActionHandlers } from "./types.js";
|
|
24
24
|
|
|
25
25
|
function toMsgId(raw: unknown): number {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media-source resolution for the WhatsApp send actions: workspace path or
|
|
3
|
+
* public URL → something Baileys can upload, plus the mimetype guess the
|
|
4
|
+
* document/audio paths need.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, statSync } from "node:fs";
|
|
8
|
+
import { basename, extname } from "node:path";
|
|
9
|
+
import { expandFsPath } from "../../../util/fs-path.js";
|
|
10
|
+
|
|
11
|
+
/** WhatsApp's own ceiling for a media upload. */
|
|
12
|
+
const MAX_MEDIA_BYTES = 64 * 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
/** Baileys accepts a Buffer, a stream, or `{ url }` for local paths and HTTP. */
|
|
15
|
+
export type MediaUpload = { url: string };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve one media input to something Baileys can upload. Two sources:
|
|
19
|
+
* a public URL (WhatsApp's uploader fetches it) or a workspace file path
|
|
20
|
+
* (streamed from disk). `file_id` is a Telegram concept with no WhatsApp
|
|
21
|
+
* equivalent — say so instead of failing obscurely.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveMediaUpload(
|
|
24
|
+
src: { file_path?: unknown; url?: unknown; file_id?: unknown },
|
|
25
|
+
label: string,
|
|
26
|
+
): { media: MediaUpload; fileName: string } | { error: string } {
|
|
27
|
+
if (src.url) {
|
|
28
|
+
const url = String(src.url);
|
|
29
|
+
return {
|
|
30
|
+
media: { url },
|
|
31
|
+
fileName: basename(new URL(url).pathname) || "file",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (src.file_id) {
|
|
35
|
+
return {
|
|
36
|
+
error:
|
|
37
|
+
`${label}: WhatsApp has no file_id — re-send by url (public) or ` +
|
|
38
|
+
`file_path (workspace file)`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (!src.file_path) {
|
|
42
|
+
return {
|
|
43
|
+
error: `${label}: provide file_path (workspace file) or url (public)`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const filePath = expandFsPath(String(src.file_path));
|
|
47
|
+
if (!existsSync(filePath)) {
|
|
48
|
+
return {
|
|
49
|
+
error: `File not found: ${filePath} — check the workspace path, or send by url instead`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (statSync(filePath).size > MAX_MEDIA_BYTES) {
|
|
53
|
+
return { error: `${label}: file exceeds WhatsApp's 64MB limit` };
|
|
54
|
+
}
|
|
55
|
+
return { media: { url: filePath }, fileName: basename(filePath) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Extension → mimetype for the document/audio paths that require one. */
|
|
59
|
+
const MIME_BY_EXT: Record<string, string> = {
|
|
60
|
+
".pdf": "application/pdf",
|
|
61
|
+
".txt": "text/plain",
|
|
62
|
+
".md": "text/markdown",
|
|
63
|
+
".json": "application/json",
|
|
64
|
+
".csv": "text/csv",
|
|
65
|
+
".zip": "application/zip",
|
|
66
|
+
".png": "image/png",
|
|
67
|
+
".jpg": "image/jpeg",
|
|
68
|
+
".jpeg": "image/jpeg",
|
|
69
|
+
".gif": "image/gif",
|
|
70
|
+
".webp": "image/webp",
|
|
71
|
+
".mp4": "video/mp4",
|
|
72
|
+
".mov": "video/quicktime",
|
|
73
|
+
".mp3": "audio/mpeg",
|
|
74
|
+
".m4a": "audio/mp4",
|
|
75
|
+
".ogg": "audio/ogg",
|
|
76
|
+
".opus": "audio/ogg; codecs=opus",
|
|
77
|
+
".wav": "audio/wav",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export function guessMimetype(fileName: string, fallback: string): string {
|
|
81
|
+
return MIME_BY_EXT[extname(fileName).toLowerCase()] ?? fallback;
|
|
82
|
+
}
|
|
@@ -10,14 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import type { AnyMessageContent } from "baileys";
|
|
12
12
|
import { toWhatsAppText } from "../formatting.js";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
resolveQuoted,
|
|
17
|
-
sendContent,
|
|
18
|
-
sendText,
|
|
19
|
-
tryAction,
|
|
20
|
-
} from "./shared.js";
|
|
13
|
+
import { tryAction } from "./try-action.js";
|
|
14
|
+
import { guessMimetype, resolveMediaUpload } from "./media-upload.js";
|
|
15
|
+
import { resolveQuoted, sendContent, sendText } from "./send.js";
|
|
21
16
|
import type { WhatsAppActionHandlers } from "./types.js";
|
|
22
17
|
|
|
23
18
|
/** Optional caption, translated to WhatsApp's dialect. */
|
|
@@ -13,13 +13,8 @@ import { log } from "../../../util/log.js";
|
|
|
13
13
|
import { toWhatsAppText } from "../formatting.js";
|
|
14
14
|
import { lookupMessage, resolveKey } from "../message-store.js";
|
|
15
15
|
import { recordPin, listPins, forgetPin } from "../pins.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
sendContent,
|
|
19
|
-
sendText,
|
|
20
|
-
tryAction,
|
|
21
|
-
boundedSend,
|
|
22
|
-
} from "./shared.js";
|
|
16
|
+
import { tryAction } from "./try-action.js";
|
|
17
|
+
import { resolveQuoted, sendContent, sendText, boundedSend } from "./send.js";
|
|
23
18
|
import type { WhatsAppActionHandlers } from "./types.js";
|
|
24
19
|
|
|
25
20
|
/** WhatsApp keeps a pin for 24h, 7d, or 30d — no indefinite option. */
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* rather than retrying something the platform cannot do.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { tryAction } from "./try-action.js";
|
|
13
|
+
import { toUserJid } from "./coerce.js";
|
|
13
14
|
import type { WhatsAppActionHandlers } from "./types.js";
|
|
14
15
|
|
|
15
16
|
/** Ops that act on one member. */
|