@sympoies/dsh-telegram 0.0.0-bootstrap.0 → 0.6.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.
- package/LICENSE +1 -2
- package/NOTICE +19 -0
- package/README.md +588 -2
- package/cordis.patch.yml +14 -0
- package/lib/access.d.ts +76 -0
- package/lib/access.d.ts.map +1 -0
- package/lib/access.js +137 -0
- package/lib/access.js.map +1 -0
- package/lib/client.js +835 -0
- package/lib/commands.d.ts +51 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +83 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +125 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +97 -0
- package/lib/config.js.map +1 -0
- package/lib/diagnostics.d.ts +58 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +59 -0
- package/lib/diagnostics.js.map +1 -0
- package/lib/failures.d.ts +71 -0
- package/lib/failures.d.ts.map +1 -0
- package/lib/failures.js +139 -0
- package/lib/failures.js.map +1 -0
- package/lib/harness/host.d.ts +106 -0
- package/lib/harness/host.d.ts.map +1 -0
- package/lib/harness/host.js +142 -0
- package/lib/harness/host.js.map +1 -0
- package/lib/harness/message.d.ts +45 -0
- package/lib/harness/message.d.ts.map +1 -0
- package/lib/harness/message.js +28 -0
- package/lib/harness/message.js.map +1 -0
- package/lib/harness/model-selection.d.ts +82 -0
- package/lib/harness/model-selection.d.ts.map +1 -0
- package/lib/harness/model-selection.js +103 -0
- package/lib/harness/model-selection.js.map +1 -0
- package/lib/harness/questions-seam.d.ts +34 -0
- package/lib/harness/questions-seam.d.ts.map +1 -0
- package/lib/harness/questions-seam.js +45 -0
- package/lib/harness/questions-seam.js.map +1 -0
- package/lib/harness/types.d.ts +108 -0
- package/lib/harness/types.d.ts.map +1 -0
- package/lib/harness/types.js +23 -0
- package/lib/harness/types.js.map +1 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +977 -0
- package/lib/index.js.map +1 -0
- package/lib/interact/approvals.d.ts +59 -0
- package/lib/interact/approvals.d.ts.map +1 -0
- package/lib/interact/approvals.js +132 -0
- package/lib/interact/approvals.js.map +1 -0
- package/lib/interact/pending.d.ts +56 -0
- package/lib/interact/pending.d.ts.map +1 -0
- package/lib/interact/pending.js +89 -0
- package/lib/interact/pending.js.map +1 -0
- package/lib/interact/questions.d.ts +100 -0
- package/lib/interact/questions.d.ts.map +1 -0
- package/lib/interact/questions.js +255 -0
- package/lib/interact/questions.js.map +1 -0
- package/lib/interact/surface.d.ts +33 -0
- package/lib/interact/surface.d.ts.map +1 -0
- package/lib/interact/surface.js +39 -0
- package/lib/interact/surface.js.map +1 -0
- package/lib/interact/text-capture.d.ts +38 -0
- package/lib/interact/text-capture.d.ts.map +1 -0
- package/lib/interact/text-capture.js +75 -0
- package/lib/interact/text-capture.js.map +1 -0
- package/lib/media/collect.d.ts +157 -0
- package/lib/media/collect.d.ts.map +1 -0
- package/lib/media/collect.js +235 -0
- package/lib/media/collect.js.map +1 -0
- package/lib/media/extractor.d.ts +117 -0
- package/lib/media/extractor.d.ts.map +1 -0
- package/lib/media/extractor.js +207 -0
- package/lib/media/extractor.js.map +1 -0
- package/lib/media/intake.d.ts +57 -0
- package/lib/media/intake.d.ts.map +1 -0
- package/lib/media/intake.js +115 -0
- package/lib/media/intake.js.map +1 -0
- package/lib/media/limits.d.ts +69 -0
- package/lib/media/limits.d.ts.map +1 -0
- package/lib/media/limits.js +97 -0
- package/lib/media/limits.js.map +1 -0
- package/lib/media/ocr.d.ts +66 -0
- package/lib/media/ocr.d.ts.map +1 -0
- package/lib/media/ocr.js +131 -0
- package/lib/media/ocr.js.map +1 -0
- package/lib/media/screenshot.d.ts +57 -0
- package/lib/media/screenshot.d.ts.map +1 -0
- package/lib/media/screenshot.js +101 -0
- package/lib/media/screenshot.js.map +1 -0
- package/lib/media/vision.d.ts +60 -0
- package/lib/media/vision.d.ts.map +1 -0
- package/lib/media/vision.js +74 -0
- package/lib/media/vision.js.map +1 -0
- package/lib/render/clamp.d.ts +34 -0
- package/lib/render/clamp.d.ts.map +1 -0
- package/lib/render/clamp.js +65 -0
- package/lib/render/clamp.js.map +1 -0
- package/lib/render/escape.d.ts +15 -0
- package/lib/render/escape.d.ts.map +1 -0
- package/lib/render/escape.js +17 -0
- package/lib/render/escape.js.map +1 -0
- package/lib/reply/activity.d.ts +31 -0
- package/lib/reply/activity.d.ts.map +1 -0
- package/lib/reply/activity.js +88 -0
- package/lib/reply/activity.js.map +1 -0
- package/lib/reply/rich-stream.d.ts +173 -0
- package/lib/reply/rich-stream.d.ts.map +1 -0
- package/lib/reply/rich-stream.js +286 -0
- package/lib/reply/rich-stream.js.map +1 -0
- package/lib/reply/split-markdown.d.ts +21 -0
- package/lib/reply/split-markdown.d.ts.map +1 -0
- package/lib/reply/split-markdown.js +87 -0
- package/lib/reply/split-markdown.js.map +1 -0
- package/lib/reply/turn-bridge.d.ts +143 -0
- package/lib/reply/turn-bridge.d.ts.map +1 -0
- package/lib/reply/turn-bridge.js +182 -0
- package/lib/reply/turn-bridge.js.map +1 -0
- package/lib/router.d.ts +415 -0
- package/lib/router.d.ts.map +1 -0
- package/lib/router.js +663 -0
- package/lib/router.js.map +1 -0
- package/lib/secrets.d.ts +30 -0
- package/lib/secrets.d.ts.map +1 -0
- package/lib/secrets.js +51 -0
- package/lib/secrets.js.map +1 -0
- package/lib/session/bindings.d.ts +81 -0
- package/lib/session/bindings.d.ts.map +1 -0
- package/lib/session/bindings.js +170 -0
- package/lib/session/bindings.js.map +1 -0
- package/lib/session/history.d.ts +50 -0
- package/lib/session/history.d.ts.map +1 -0
- package/lib/session/history.js +127 -0
- package/lib/session/history.js.map +1 -0
- package/lib/session/models.d.ts +99 -0
- package/lib/session/models.d.ts.map +1 -0
- package/lib/session/models.js +123 -0
- package/lib/session/models.js.map +1 -0
- package/lib/session/permission.d.ts +83 -0
- package/lib/session/permission.d.ts.map +1 -0
- package/lib/session/permission.js +107 -0
- package/lib/session/permission.js.map +1 -0
- package/lib/session/picker.d.ts +49 -0
- package/lib/session/picker.d.ts.map +1 -0
- package/lib/session/picker.js +107 -0
- package/lib/session/picker.js.map +1 -0
- package/lib/session/preferences.d.ts +55 -0
- package/lib/session/preferences.d.ts.map +1 -0
- package/lib/session/preferences.js +102 -0
- package/lib/session/preferences.js.map +1 -0
- package/lib/session/recovery.d.ts +66 -0
- package/lib/session/recovery.d.ts.map +1 -0
- package/lib/session/recovery.js +112 -0
- package/lib/session/recovery.js.map +1 -0
- package/lib/session/runner.d.ts +223 -0
- package/lib/session/runner.d.ts.map +1 -0
- package/lib/session/runner.js +260 -0
- package/lib/session/runner.js.map +1 -0
- package/lib/session/workspaces.d.ts +28 -0
- package/lib/session/workspaces.d.ts.map +1 -0
- package/lib/session/workspaces.js +53 -0
- package/lib/session/workspaces.js.map +1 -0
- package/lib/telegram/addressing.d.ts +39 -0
- package/lib/telegram/addressing.d.ts.map +1 -0
- package/lib/telegram/addressing.js +81 -0
- package/lib/telegram/addressing.js.map +1 -0
- package/lib/telegram/albums.d.ts +46 -0
- package/lib/telegram/albums.d.ts.map +1 -0
- package/lib/telegram/albums.js +104 -0
- package/lib/telegram/albums.js.map +1 -0
- package/lib/telegram/api.d.ts +238 -0
- package/lib/telegram/api.d.ts.map +1 -0
- package/lib/telegram/api.js +518 -0
- package/lib/telegram/api.js.map +1 -0
- package/lib/telegram/poller.d.ts +59 -0
- package/lib/telegram/poller.d.ts.map +1 -0
- package/lib/telegram/poller.js +94 -0
- package/lib/telegram/poller.js.map +1 -0
- package/lib/telegram/types.d.ts +130 -0
- package/lib/telegram/types.d.ts.map +1 -0
- package/lib/telegram/types.js +9 -0
- package/lib/telegram/types.js.map +1 -0
- package/lib/telegram/typing.d.ts +47 -0
- package/lib/telegram/typing.d.ts.map +1 -0
- package/lib/telegram/typing.js +91 -0
- package/lib/telegram/typing.js.map +1 -0
- package/lib/versions.d.ts +63 -0
- package/lib/versions.d.ts.map +1 -0
- package/lib/versions.js +135 -0
- package/lib/versions.js.map +1 -0
- package/package.json +94 -10
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash commands.
|
|
3
|
+
*
|
|
4
|
+
* Telegram delivers a command as ordinary message text, and in a group it
|
|
5
|
+
* arrives addressed — `/new@my_bot` — because several bots may share the chat.
|
|
6
|
+
* Parsing is therefore a real step rather than a `startsWith`, and a command
|
|
7
|
+
* addressed to a different bot must be ignored rather than executed.
|
|
8
|
+
*/
|
|
9
|
+
/** A recognised command and the text that followed it. */
|
|
10
|
+
export interface ParsedCommand {
|
|
11
|
+
/** Command name, lowercased, without the slash or the bot suffix. */
|
|
12
|
+
readonly name: string;
|
|
13
|
+
/** Everything after the command, trimmed; empty when there was nothing. */
|
|
14
|
+
readonly args: string;
|
|
15
|
+
}
|
|
16
|
+
/** Commands the plugin answers, with the one-line help shown by `/help`. */
|
|
17
|
+
export declare const COMMANDS: Readonly<Record<string, string>>;
|
|
18
|
+
/**
|
|
19
|
+
* Parse a message as a command.
|
|
20
|
+
*
|
|
21
|
+
* @param text - the raw message text.
|
|
22
|
+
* @param botUsername - this bot's username, so `/cmd@other_bot` is ignored.
|
|
23
|
+
* @returns the command, or undefined when the text is not one for us.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseCommand(text: string, botUsername?: string): ParsedCommand | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* The command menu to publish, for the list Telegram shows on `/`.
|
|
28
|
+
*
|
|
29
|
+
* `/claim` is left out once the bot has an owner: it is the one command that
|
|
30
|
+
* stops working the moment it succeeds, and offering it forever invites
|
|
31
|
+
* everyone who opens the chat to try a code that can no longer be right.
|
|
32
|
+
*
|
|
33
|
+
* @param claimable - whether the bot is still waiting to be claimed.
|
|
34
|
+
* @returns entries in menu order, descriptions clipped to Telegram's limit.
|
|
35
|
+
*/
|
|
36
|
+
export declare function commandMenu(claimable: boolean): {
|
|
37
|
+
command: string;
|
|
38
|
+
description: string;
|
|
39
|
+
}[];
|
|
40
|
+
/**
|
|
41
|
+
* The `/help` body, rendered as Telegram HTML.
|
|
42
|
+
*
|
|
43
|
+
* The descriptions are prose, not markup, so they are escaped on the way in.
|
|
44
|
+
* `/claim <code>` is the reason: sent raw with `parse_mode: HTML`, Telegram
|
|
45
|
+
* read `<code>` as an unclosed tag and rejected the WHOLE message with a 400 —
|
|
46
|
+
* so `/help` answered with silence, which reads as a dead bot rather than as a
|
|
47
|
+
* malformed message. Escaping here means a description added later cannot do
|
|
48
|
+
* it again.
|
|
49
|
+
*/
|
|
50
|
+
export declare function helpText(): string;
|
|
51
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAKD,4EAA4E;AAC5E,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBrD,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAa1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EAAE,CAO1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAKjC"}
|
package/lib/commands.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash commands.
|
|
3
|
+
*
|
|
4
|
+
* Telegram delivers a command as ordinary message text, and in a group it
|
|
5
|
+
* arrives addressed — `/new@my_bot` — because several bots may share the chat.
|
|
6
|
+
* Parsing is therefore a real step rather than a `startsWith`, and a command
|
|
7
|
+
* addressed to a different bot must be ignored rather than executed.
|
|
8
|
+
*/
|
|
9
|
+
import { escapeHtml } from './render/escape.js';
|
|
10
|
+
/** Telegram refuses a menu description longer than this. */
|
|
11
|
+
const MAX_MENU_DESCRIPTION = 256;
|
|
12
|
+
/** Commands the plugin answers, with the one-line help shown by `/help`. */
|
|
13
|
+
export const COMMANDS = {
|
|
14
|
+
start: 'Show what this bot is and whether you may use it',
|
|
15
|
+
help: 'List the commands',
|
|
16
|
+
claim: 'Take ownership of an unclaimed bot: /claim <code>',
|
|
17
|
+
new: 'Start a fresh conversation, forgetting the current one',
|
|
18
|
+
cd: 'Show or change the working directory: /cd ~/projects/app',
|
|
19
|
+
model: 'Show or change the model: /model list, or /model provider/model',
|
|
20
|
+
effort: 'Show or change the reasoning effort: /effort high',
|
|
21
|
+
vision: 'Show or change the model that reads images: /vision off',
|
|
22
|
+
permission: 'Show or change what the agent may do: /permission read-only',
|
|
23
|
+
diag: 'Show what the plugin can see about itself, and recent failures',
|
|
24
|
+
screenshot: "Send a picture of the harness machine's screen",
|
|
25
|
+
sessions: 'Pick up an earlier conversation from this chat',
|
|
26
|
+
status: 'Show the session, working directory, and who owns the bot',
|
|
27
|
+
stop: 'Cancel whatever the agent is doing right now',
|
|
28
|
+
whoami: 'Show your Telegram user id',
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Parse a message as a command.
|
|
32
|
+
*
|
|
33
|
+
* @param text - the raw message text.
|
|
34
|
+
* @param botUsername - this bot's username, so `/cmd@other_bot` is ignored.
|
|
35
|
+
* @returns the command, or undefined when the text is not one for us.
|
|
36
|
+
*/
|
|
37
|
+
export function parseCommand(text, botUsername) {
|
|
38
|
+
const match = /^\/([a-z0-9_]+)(?:@([a-z0-9_]+))?(?:\s+([\s\S]*))?$/i.exec(text.trim());
|
|
39
|
+
if (!match)
|
|
40
|
+
return undefined;
|
|
41
|
+
const addressed = match[2];
|
|
42
|
+
if (addressed !== undefined && botUsername !== undefined) {
|
|
43
|
+
if (addressed.toLowerCase() !== botUsername.toLowerCase())
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const name = match[1].toLowerCase();
|
|
47
|
+
if (!(name in COMMANDS))
|
|
48
|
+
return undefined;
|
|
49
|
+
return { name, args: (match[3] ?? '').trim() };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The command menu to publish, for the list Telegram shows on `/`.
|
|
53
|
+
*
|
|
54
|
+
* `/claim` is left out once the bot has an owner: it is the one command that
|
|
55
|
+
* stops working the moment it succeeds, and offering it forever invites
|
|
56
|
+
* everyone who opens the chat to try a code that can no longer be right.
|
|
57
|
+
*
|
|
58
|
+
* @param claimable - whether the bot is still waiting to be claimed.
|
|
59
|
+
* @returns entries in menu order, descriptions clipped to Telegram's limit.
|
|
60
|
+
*/
|
|
61
|
+
export function commandMenu(claimable) {
|
|
62
|
+
return Object.entries(COMMANDS)
|
|
63
|
+
.filter(([name]) => claimable || name !== 'claim')
|
|
64
|
+
.map(([command, description]) => ({
|
|
65
|
+
command,
|
|
66
|
+
description: description.slice(0, MAX_MENU_DESCRIPTION),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The `/help` body, rendered as Telegram HTML.
|
|
71
|
+
*
|
|
72
|
+
* The descriptions are prose, not markup, so they are escaped on the way in.
|
|
73
|
+
* `/claim <code>` is the reason: sent raw with `parse_mode: HTML`, Telegram
|
|
74
|
+
* read `<code>` as an unclosed tag and rejected the WHOLE message with a 400 —
|
|
75
|
+
* so `/help` answered with silence, which reads as a dead bot rather than as a
|
|
76
|
+
* malformed message. Escaping here means a description added later cannot do
|
|
77
|
+
* it again.
|
|
78
|
+
*/
|
|
79
|
+
export function helpText() {
|
|
80
|
+
const lines = Object.entries(COMMANDS).map(([name, description]) => `/${name} — ${escapeHtml(description)}`);
|
|
81
|
+
return `<b>Commands</b>\n${lines.join('\n')}`;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAU/C,4DAA4D;AAC5D,MAAM,oBAAoB,GAAG,GAAG,CAAA;AAEhC,4EAA4E;AAC5E,MAAM,CAAC,MAAM,QAAQ,GAAqC;IACxD,KAAK,EAAE,kDAAkD;IACzD,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,mDAAmD;IAC1D,GAAG,EAAE,wDAAwD;IAC7D,EAAE,EAAE,0DAA0D;IAC9D,KAAK,EAAE,iEAAiE;IACxE,MAAM,EAAE,mDAAmD;IAC3D,MAAM,EAAE,yDAAyD;IACjE,UAAU,EAAE,6DAA6D;IACzE,IAAI,EAAE,gEAAgE;IACtE,UAAU,EAAE,gDAAgD;IAC5D,QAAQ,EAAE,gDAAgD;IAC1D,MAAM,EAAE,2DAA2D;IACnE,IAAI,EAAE,8CAA8C;IACpD,MAAM,EAAE,4BAA4B;CACrC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,WAAoB;IAC7D,MAAM,KAAK,GAAG,sDAAsD,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IACtF,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAE5B,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IAC1B,IAAI,SAAS,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QACzD,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,WAAW,CAAC,WAAW,EAAE;YAAE,OAAO,SAAS,CAAA;IAC7E,CAAC;IAED,MAAM,IAAI,GAAI,KAAK,CAAC,CAAC,CAAY,CAAC,WAAW,EAAE,CAAA;IAC/C,IAAI,CAAC,CAAC,IAAI,IAAI,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAA;IAEzC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;AAChD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,SAAkB;IAC5C,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;SAC5B,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI,KAAK,OAAO,CAAC;SACjD,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;QAChC,OAAO;QACP,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC;KACxD,CAAC,CAAC,CAAA;AACP,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CACxC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,MAAM,UAAU,CAAC,WAAW,CAAC,EAAE,CACjE,CAAA;IACD,OAAO,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAC/C,CAAC"}
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin configuration.
|
|
3
|
+
*
|
|
4
|
+
* The bot token is deliberately absent. Configuration carries a *reference* to
|
|
5
|
+
* the credential (`tokenRef`), and the value lives with the harness credential
|
|
6
|
+
* provider — so a profile config stays safe to read, sync, and show in a UI,
|
|
7
|
+
* and rotating the token touches no file here.
|
|
8
|
+
*/
|
|
9
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
10
|
+
/** Default credential reference for the bot token. */
|
|
11
|
+
export declare const DEFAULT_TOKEN_REF = "TELEGRAM_BOT_TOKEN";
|
|
12
|
+
export declare const Config: Schema<Schemastery.ObjectS<{
|
|
13
|
+
enabled: Schema<boolean, boolean>;
|
|
14
|
+
tokenRef: Schema<string, string>;
|
|
15
|
+
baseUrl: Schema<string, string>;
|
|
16
|
+
allowFrom: Schema<number[], number[]>;
|
|
17
|
+
cwd: Schema<string, string>;
|
|
18
|
+
timeoutMs: Schema<number, number>;
|
|
19
|
+
longPollSeconds: Schema<number, number>;
|
|
20
|
+
streaming: Schema<Schemastery.ObjectS<{
|
|
21
|
+
enabled: Schema<boolean, boolean>;
|
|
22
|
+
throttleMs: Schema<number, number>;
|
|
23
|
+
}>, Schemastery.ObjectT<{
|
|
24
|
+
enabled: Schema<boolean, boolean>;
|
|
25
|
+
throttleMs: Schema<number, number>;
|
|
26
|
+
}>>;
|
|
27
|
+
media: Schema<Schemastery.ObjectS<{
|
|
28
|
+
enabled: Schema<boolean, boolean>;
|
|
29
|
+
maxBytes: Schema<number, number>;
|
|
30
|
+
maxTextChars: Schema<number, number>;
|
|
31
|
+
ocr: Schema<Schemastery.ObjectS<{
|
|
32
|
+
enabled: Schema<boolean, boolean>;
|
|
33
|
+
languages: Schema<string, string>;
|
|
34
|
+
}>, Schemastery.ObjectT<{
|
|
35
|
+
enabled: Schema<boolean, boolean>;
|
|
36
|
+
languages: Schema<string, string>;
|
|
37
|
+
}>>;
|
|
38
|
+
visionModel: Schema<string, string>;
|
|
39
|
+
}>, Schemastery.ObjectT<{
|
|
40
|
+
enabled: Schema<boolean, boolean>;
|
|
41
|
+
maxBytes: Schema<number, number>;
|
|
42
|
+
maxTextChars: Schema<number, number>;
|
|
43
|
+
ocr: Schema<Schemastery.ObjectS<{
|
|
44
|
+
enabled: Schema<boolean, boolean>;
|
|
45
|
+
languages: Schema<string, string>;
|
|
46
|
+
}>, Schemastery.ObjectT<{
|
|
47
|
+
enabled: Schema<boolean, boolean>;
|
|
48
|
+
languages: Schema<string, string>;
|
|
49
|
+
}>>;
|
|
50
|
+
visionModel: Schema<string, string>;
|
|
51
|
+
}>>;
|
|
52
|
+
agentPreset: Schema<string, string>;
|
|
53
|
+
requireMentionInGroups: Schema<boolean, boolean>;
|
|
54
|
+
permissionPreset: Schema<string, string>;
|
|
55
|
+
screenshot: Schema<Schemastery.ObjectS<{
|
|
56
|
+
enabled: Schema<boolean, boolean>;
|
|
57
|
+
}>, Schemastery.ObjectT<{
|
|
58
|
+
enabled: Schema<boolean, boolean>;
|
|
59
|
+
}>>;
|
|
60
|
+
reconnect: Schema<Schemastery.ObjectS<{
|
|
61
|
+
baseDelayMs: Schema<number, number>;
|
|
62
|
+
maxDelayMs: Schema<number, number>;
|
|
63
|
+
}>, Schemastery.ObjectT<{
|
|
64
|
+
baseDelayMs: Schema<number, number>;
|
|
65
|
+
maxDelayMs: Schema<number, number>;
|
|
66
|
+
}>>;
|
|
67
|
+
}>, Schemastery.ObjectT<{
|
|
68
|
+
enabled: Schema<boolean, boolean>;
|
|
69
|
+
tokenRef: Schema<string, string>;
|
|
70
|
+
baseUrl: Schema<string, string>;
|
|
71
|
+
allowFrom: Schema<number[], number[]>;
|
|
72
|
+
cwd: Schema<string, string>;
|
|
73
|
+
timeoutMs: Schema<number, number>;
|
|
74
|
+
longPollSeconds: Schema<number, number>;
|
|
75
|
+
streaming: Schema<Schemastery.ObjectS<{
|
|
76
|
+
enabled: Schema<boolean, boolean>;
|
|
77
|
+
throttleMs: Schema<number, number>;
|
|
78
|
+
}>, Schemastery.ObjectT<{
|
|
79
|
+
enabled: Schema<boolean, boolean>;
|
|
80
|
+
throttleMs: Schema<number, number>;
|
|
81
|
+
}>>;
|
|
82
|
+
media: Schema<Schemastery.ObjectS<{
|
|
83
|
+
enabled: Schema<boolean, boolean>;
|
|
84
|
+
maxBytes: Schema<number, number>;
|
|
85
|
+
maxTextChars: Schema<number, number>;
|
|
86
|
+
ocr: Schema<Schemastery.ObjectS<{
|
|
87
|
+
enabled: Schema<boolean, boolean>;
|
|
88
|
+
languages: Schema<string, string>;
|
|
89
|
+
}>, Schemastery.ObjectT<{
|
|
90
|
+
enabled: Schema<boolean, boolean>;
|
|
91
|
+
languages: Schema<string, string>;
|
|
92
|
+
}>>;
|
|
93
|
+
visionModel: Schema<string, string>;
|
|
94
|
+
}>, Schemastery.ObjectT<{
|
|
95
|
+
enabled: Schema<boolean, boolean>;
|
|
96
|
+
maxBytes: Schema<number, number>;
|
|
97
|
+
maxTextChars: Schema<number, number>;
|
|
98
|
+
ocr: Schema<Schemastery.ObjectS<{
|
|
99
|
+
enabled: Schema<boolean, boolean>;
|
|
100
|
+
languages: Schema<string, string>;
|
|
101
|
+
}>, Schemastery.ObjectT<{
|
|
102
|
+
enabled: Schema<boolean, boolean>;
|
|
103
|
+
languages: Schema<string, string>;
|
|
104
|
+
}>>;
|
|
105
|
+
visionModel: Schema<string, string>;
|
|
106
|
+
}>>;
|
|
107
|
+
agentPreset: Schema<string, string>;
|
|
108
|
+
requireMentionInGroups: Schema<boolean, boolean>;
|
|
109
|
+
permissionPreset: Schema<string, string>;
|
|
110
|
+
screenshot: Schema<Schemastery.ObjectS<{
|
|
111
|
+
enabled: Schema<boolean, boolean>;
|
|
112
|
+
}>, Schemastery.ObjectT<{
|
|
113
|
+
enabled: Schema<boolean, boolean>;
|
|
114
|
+
}>>;
|
|
115
|
+
reconnect: Schema<Schemastery.ObjectS<{
|
|
116
|
+
baseDelayMs: Schema<number, number>;
|
|
117
|
+
maxDelayMs: Schema<number, number>;
|
|
118
|
+
}>, Schemastery.ObjectT<{
|
|
119
|
+
baseDelayMs: Schema<number, number>;
|
|
120
|
+
maxDelayMs: Schema<number, number>;
|
|
121
|
+
}>>;
|
|
122
|
+
}>>;
|
|
123
|
+
/** Resolved plugin configuration. */
|
|
124
|
+
export type TelegramConfig = ReturnType<typeof Config>;
|
|
125
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAE7C,sDAAsD;AACtD,eAAO,MAAM,iBAAiB,uBAAuB,CAAA;AAErD,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoHjB,CAAA;AAEF,qCAAqC;AACrC,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAA"}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin configuration.
|
|
3
|
+
*
|
|
4
|
+
* The bot token is deliberately absent. Configuration carries a *reference* to
|
|
5
|
+
* the credential (`tokenRef`), and the value lives with the harness credential
|
|
6
|
+
* provider — so a profile config stays safe to read, sync, and show in a UI,
|
|
7
|
+
* and rotating the token touches no file here.
|
|
8
|
+
*/
|
|
9
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
10
|
+
/** Default credential reference for the bot token. */
|
|
11
|
+
export const DEFAULT_TOKEN_REF = 'TELEGRAM_BOT_TOKEN';
|
|
12
|
+
export const Config = Schema.object({
|
|
13
|
+
enabled: Schema.boolean()
|
|
14
|
+
.default(true)
|
|
15
|
+
.description('Whether the Telegram connection starts with the harness.'),
|
|
16
|
+
tokenRef: Schema.string()
|
|
17
|
+
.default(DEFAULT_TOKEN_REF)
|
|
18
|
+
.description('Credential reference holding the bot token — never the token itself.'),
|
|
19
|
+
baseUrl: Schema.string()
|
|
20
|
+
.default('https://api.telegram.org')
|
|
21
|
+
.description('Bot API origin; change it only for a local proxy.'),
|
|
22
|
+
allowFrom: Schema.array(Schema.number())
|
|
23
|
+
.default([])
|
|
24
|
+
.description('Telegram user ids allowed to drive the agent. Leave empty to claim the bot once with /claim.'),
|
|
25
|
+
cwd: Schema.string().description('Working directory new conversations start in. Defaults to the harness working directory.'),
|
|
26
|
+
timeoutMs: Schema.natural().default(30_000).description('Per-request Bot API timeout.'),
|
|
27
|
+
longPollSeconds: Schema.natural()
|
|
28
|
+
.default(25)
|
|
29
|
+
.description('How long Telegram holds an empty poll open.'),
|
|
30
|
+
streaming: Schema.object({
|
|
31
|
+
enabled: Schema.boolean()
|
|
32
|
+
.default(true)
|
|
33
|
+
.description('Edit one message as the answer streams, instead of sending it once at the end.'),
|
|
34
|
+
throttleMs: Schema.natural()
|
|
35
|
+
.default(1200)
|
|
36
|
+
.description('Minimum gap between edits; Telegram rate-limits rapid edits to one chat.'),
|
|
37
|
+
}),
|
|
38
|
+
media: Schema.object({
|
|
39
|
+
enabled: Schema.boolean()
|
|
40
|
+
.default(true)
|
|
41
|
+
.description('Read images and text files the user sends.'),
|
|
42
|
+
maxBytes: Schema.natural()
|
|
43
|
+
.default(20 * 1024 * 1024)
|
|
44
|
+
.description('Refuse anything larger. Telegram caps bot downloads at 20 MB.'),
|
|
45
|
+
maxTextChars: Schema.natural()
|
|
46
|
+
.default(60_000)
|
|
47
|
+
.description('Truncate an inlined text file to this many characters.'),
|
|
48
|
+
ocr: Schema.object({
|
|
49
|
+
enabled: Schema.boolean()
|
|
50
|
+
.default(true)
|
|
51
|
+
.description('Read an image\'s text with tesseract when no vision model is ' +
|
|
52
|
+
'configured, or when the one configured could not be reached. Does ' +
|
|
53
|
+
'nothing unless tesseract is installed, which no operating system ' +
|
|
54
|
+
'does by default. It reads text and does not see: a diagram or a ' +
|
|
55
|
+
'chart comes back as scattered words.'),
|
|
56
|
+
languages: Schema.string()
|
|
57
|
+
.default('eng')
|
|
58
|
+
.description('Languages to read, as tesseract names them; join several with +, ' +
|
|
59
|
+
'as in eng+ind. Only languages installed on this machine work — ' +
|
|
60
|
+
'`tesseract --list-langs` says which.'),
|
|
61
|
+
}),
|
|
62
|
+
visionModel: Schema.string()
|
|
63
|
+
.default('')
|
|
64
|
+
.description('Model that reads an image, as provider/model. The picture goes to a ' +
|
|
65
|
+
'session of its own there and only what it says joins the conversation. ' +
|
|
66
|
+
'Empty sends the image to the conversation itself, which must then accept it.'),
|
|
67
|
+
}),
|
|
68
|
+
agentPreset: Schema.string()
|
|
69
|
+
.default('')
|
|
70
|
+
.description('Agent preset Telegram conversations are composed from — the roster in ' +
|
|
71
|
+
'Settings owns the list. Empty takes the deployment default. The preset ' +
|
|
72
|
+
'is what supplies the tools, so this decides what the agent can do.'),
|
|
73
|
+
requireMentionInGroups: Schema.boolean()
|
|
74
|
+
.default(true)
|
|
75
|
+
.description('In a group, answer only when the bot is @mentioned or replied to. Off ' +
|
|
76
|
+
'answers every message from an allowed user, which is rarely wanted in ' +
|
|
77
|
+
'a room where people also talk to each other. Private chats are never ' +
|
|
78
|
+
'affected.'),
|
|
79
|
+
permissionPreset: Schema.string()
|
|
80
|
+
.default('')
|
|
81
|
+
.description('Permission preset Telegram conversations run under — one of the names ' +
|
|
82
|
+
'the deployment defines. Empty follows the deployment default. A preset ' +
|
|
83
|
+
'whose approval policy asks is what makes the approval buttons appear.'),
|
|
84
|
+
screenshot: Schema.object({
|
|
85
|
+
enabled: Schema.boolean()
|
|
86
|
+
.default(false)
|
|
87
|
+
.description('Allow /screenshot. Off by default: a screen holds whatever happens to ' +
|
|
88
|
+
'be on it, and this is the one thing here that sends the machine\'s ' +
|
|
89
|
+
'own contents outward without the agent being involved. macOS also ' +
|
|
90
|
+
'needs Screen Recording permission for the harness process.'),
|
|
91
|
+
}),
|
|
92
|
+
reconnect: Schema.object({
|
|
93
|
+
baseDelayMs: Schema.natural().default(1000).description('First reconnect delay.'),
|
|
94
|
+
maxDelayMs: Schema.natural().default(30_000).description('Longest reconnect delay.'),
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A status file, because the log is not always readable.
|
|
3
|
+
*
|
|
4
|
+
* `ctx.logger` reaches whatever sink the deployment composed — and several
|
|
5
|
+
* profiles compose none, so a plugin that only logs its failures is silent
|
|
6
|
+
* about them. That is how a bot ends up not answering with nothing anywhere
|
|
7
|
+
* to say why: the token was missing, or Telegram refused it, and the one line
|
|
8
|
+
* that said so went nowhere.
|
|
9
|
+
*
|
|
10
|
+
* So the plugin also writes what it is doing to a small JSON file beside its
|
|
11
|
+
* other state. It is written on every transition, is safe to read while the
|
|
12
|
+
* plugin runs, and never contains the token.
|
|
13
|
+
*/
|
|
14
|
+
/** What the connection is doing right now. */
|
|
15
|
+
export type ConnectionState =
|
|
16
|
+
/** Loaded but deliberately not connecting: disabled, or no token yet. */
|
|
17
|
+
'idle'
|
|
18
|
+
/** Opening: verifying the token and clearing any webhook. */
|
|
19
|
+
| 'connecting'
|
|
20
|
+
/** Long-polling for updates. */
|
|
21
|
+
| 'connected'
|
|
22
|
+
/** Stopped by an error; `detail` says which. */
|
|
23
|
+
| 'failed';
|
|
24
|
+
/** One published status. */
|
|
25
|
+
export interface StatusSnapshot {
|
|
26
|
+
readonly state: ConnectionState;
|
|
27
|
+
/** The bot's @username, once known. */
|
|
28
|
+
readonly bot?: string;
|
|
29
|
+
/** Why, for the states that need a why. Never contains the token. */
|
|
30
|
+
readonly detail?: string;
|
|
31
|
+
/** When this was written, ISO-8601. */
|
|
32
|
+
readonly updatedAt: string;
|
|
33
|
+
}
|
|
34
|
+
/** Publishes the connection's state where a person can read it. */
|
|
35
|
+
export declare class StatusFile {
|
|
36
|
+
private readonly file;
|
|
37
|
+
/** Strips the bot token from any text bound for the file. */
|
|
38
|
+
private readonly redact;
|
|
39
|
+
constructor(file: string,
|
|
40
|
+
/** Strips the bot token from any text bound for the file. */
|
|
41
|
+
redact?: (text: string) => string);
|
|
42
|
+
/**
|
|
43
|
+
* Publish a state.
|
|
44
|
+
*
|
|
45
|
+
* Failures are swallowed: a plugin must not fall over because it could not
|
|
46
|
+
* write its own diagnostics.
|
|
47
|
+
*
|
|
48
|
+
* @param state - what the connection is doing.
|
|
49
|
+
* @param extra - the bot username and reason, where they apply.
|
|
50
|
+
*/
|
|
51
|
+
publish(state: ConnectionState, extra?: {
|
|
52
|
+
bot?: string;
|
|
53
|
+
detail?: string | undefined;
|
|
54
|
+
}): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/** Reduce any thrown value to one readable line. */
|
|
57
|
+
export declare function describeError(error: unknown): string;
|
|
58
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,8CAA8C;AAC9C,MAAM,MAAM,eAAe;AACzB,yEAAyE;AACvE,MAAM;AACR,6DAA6D;GAC3D,YAAY;AACd,gCAAgC;GAC9B,WAAW;AACb,gDAAgD;GAC9C,QAAQ,CAAA;AAEZ,4BAA4B;AAC5B,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,mEAAmE;AACnE,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,IAAI,EAAE,MAAM;IAC7B,6DAA6D;IAC5C,MAAM,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAuB;IAGpE;;;;;;;;OAQG;IACG,OAAO,CACX,KAAK,EAAE,eAAe,EACtB,KAAK,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAO,GACxD,OAAO,CAAC,IAAI,CAAC;CAiBjB;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGpD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A status file, because the log is not always readable.
|
|
3
|
+
*
|
|
4
|
+
* `ctx.logger` reaches whatever sink the deployment composed — and several
|
|
5
|
+
* profiles compose none, so a plugin that only logs its failures is silent
|
|
6
|
+
* about them. That is how a bot ends up not answering with nothing anywhere
|
|
7
|
+
* to say why: the token was missing, or Telegram refused it, and the one line
|
|
8
|
+
* that said so went nowhere.
|
|
9
|
+
*
|
|
10
|
+
* So the plugin also writes what it is doing to a small JSON file beside its
|
|
11
|
+
* other state. It is written on every transition, is safe to read while the
|
|
12
|
+
* plugin runs, and never contains the token.
|
|
13
|
+
*/
|
|
14
|
+
import { mkdir, rename, writeFile } from 'node:fs/promises';
|
|
15
|
+
import { dirname } from 'node:path';
|
|
16
|
+
/** Publishes the connection's state where a person can read it. */
|
|
17
|
+
export class StatusFile {
|
|
18
|
+
file;
|
|
19
|
+
redact;
|
|
20
|
+
constructor(file,
|
|
21
|
+
/** Strips the bot token from any text bound for the file. */
|
|
22
|
+
redact = (text) => text) {
|
|
23
|
+
this.file = file;
|
|
24
|
+
this.redact = redact;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Publish a state.
|
|
28
|
+
*
|
|
29
|
+
* Failures are swallowed: a plugin must not fall over because it could not
|
|
30
|
+
* write its own diagnostics.
|
|
31
|
+
*
|
|
32
|
+
* @param state - what the connection is doing.
|
|
33
|
+
* @param extra - the bot username and reason, where they apply.
|
|
34
|
+
*/
|
|
35
|
+
async publish(state, extra = {}) {
|
|
36
|
+
const snapshot = {
|
|
37
|
+
state,
|
|
38
|
+
...(extra.bot !== undefined ? { bot: extra.bot } : {}),
|
|
39
|
+
...(extra.detail !== undefined ? { detail: this.redact(extra.detail) } : {}),
|
|
40
|
+
updatedAt: new Date().toISOString(),
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
const temporary = `${this.file}.${process.pid}.tmp`;
|
|
44
|
+
await mkdir(dirname(this.file), { recursive: true });
|
|
45
|
+
await writeFile(temporary, `${JSON.stringify(snapshot, undefined, 2)}\n`, 'utf8');
|
|
46
|
+
await rename(temporary, this.file);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Diagnostics must never be the thing that breaks.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Reduce any thrown value to one readable line. */
|
|
54
|
+
export function describeError(error) {
|
|
55
|
+
if (error instanceof Error)
|
|
56
|
+
return error.message;
|
|
57
|
+
return String(error);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAwBnC,mEAAmE;AACnE,MAAM,OAAO,UAAU;IAEF;IAEA;IAHnB,YACmB,IAAY;IAC7B,6DAA6D;IAC5C,SAAmC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;QAFjD,SAAI,GAAJ,IAAI,CAAQ;QAEZ,WAAM,GAAN,MAAM,CAA2C;IACjE,CAAC;IAEJ;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,CACX,KAAsB,EACtB,QAAuD,EAAE;QAEzD,MAAM,QAAQ,GAAmB;YAC/B,KAAK;YACL,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAA;QAED,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAA;YACnD,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACpD,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YACjF,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;IACH,CAAC;CACF;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC,OAAO,CAAA;IAChD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;AACtB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeping the last things that went wrong where somebody can see them.
|
|
3
|
+
*
|
|
4
|
+
* `ctx.logger` reaches whatever sink the deployment composed, and several
|
|
5
|
+
* profiles compose none. A plugin that only logs its failures is then silent
|
|
6
|
+
* about them: the status file says whether the connection is up, and nothing
|
|
7
|
+
* says that a screenshot failed, an attachment was refused, or a turn threw.
|
|
8
|
+
*
|
|
9
|
+
* That is not a hypothetical. Every fault found in this plugin so far was
|
|
10
|
+
* found by someone noticing odd behaviour in a chat and asking about it, not
|
|
11
|
+
* by reading a log — because there was no log to read.
|
|
12
|
+
*
|
|
13
|
+
* So warnings and errors are also kept here: a small ring in memory for
|
|
14
|
+
* `/diag`, and the same ring on disk for when the bot itself is too broken to
|
|
15
|
+
* answer a command.
|
|
16
|
+
*/
|
|
17
|
+
import type { Logger } from './harness/types.js';
|
|
18
|
+
/** One thing that went wrong. */
|
|
19
|
+
export interface Failure {
|
|
20
|
+
readonly at: string;
|
|
21
|
+
readonly level: 'warn' | 'error';
|
|
22
|
+
readonly message: string;
|
|
23
|
+
}
|
|
24
|
+
/** Construction options. */
|
|
25
|
+
export interface FailureLogOptions {
|
|
26
|
+
/** Where to mirror the ring; absent keeps it in memory only. */
|
|
27
|
+
readonly file?: string;
|
|
28
|
+
/** Strips anything secret before it is written down. */
|
|
29
|
+
readonly redact?: (text: string) => string;
|
|
30
|
+
readonly keep?: number;
|
|
31
|
+
readonly flushMs?: number;
|
|
32
|
+
/** Injected so tests need no clock. */
|
|
33
|
+
readonly now?: () => Date;
|
|
34
|
+
}
|
|
35
|
+
export declare class FailureLog {
|
|
36
|
+
private readonly options;
|
|
37
|
+
private entries;
|
|
38
|
+
private timer;
|
|
39
|
+
private writing;
|
|
40
|
+
constructor(options?: FailureLogOptions);
|
|
41
|
+
/**
|
|
42
|
+
* Record one failure.
|
|
43
|
+
*
|
|
44
|
+
* @param level - how bad it was.
|
|
45
|
+
* @param message - what happened, already joined into one line.
|
|
46
|
+
*/
|
|
47
|
+
record(level: 'warn' | 'error', message: string): void;
|
|
48
|
+
/** The failures kept, newest first. */
|
|
49
|
+
recent(): readonly Failure[];
|
|
50
|
+
/** Write now, whatever the batching timer was waiting for. */
|
|
51
|
+
flush(): Promise<void>;
|
|
52
|
+
/** Stop batching — the plugin is unloading. */
|
|
53
|
+
dispose(): void;
|
|
54
|
+
/** Arm the batching timer, unless one is already armed. */
|
|
55
|
+
private schedule;
|
|
56
|
+
/** Mirror the ring to disk, one write at a time. */
|
|
57
|
+
private persist;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A logger that also records what it warns and errors about.
|
|
61
|
+
*
|
|
62
|
+
* Wrapping rather than replacing: whatever sink the deployment composed still
|
|
63
|
+
* gets everything, and this only adds a copy of the parts worth looking back
|
|
64
|
+
* at. Debug and info are deliberately not kept — a ring of twenty filled with
|
|
65
|
+
* routine chatter would push out the one line that mattered.
|
|
66
|
+
*
|
|
67
|
+
* @param base - the harness logger.
|
|
68
|
+
* @param log - the ring to tee into.
|
|
69
|
+
*/
|
|
70
|
+
export declare function recordingLogger(base: Logger, log: FailureLog): Logger;
|
|
71
|
+
//# sourceMappingURL=failures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures.d.ts","sourceRoot":"","sources":["../src/failures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAQhD,iCAAiC;AACjC,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,4BAA4B;AAC5B,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,uCAAuC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,qBAAa,UAAU;IAKT,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,OAAO,CAAmC;gBAErB,OAAO,GAAE,iBAAsB;IAE5D;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAatD,uCAAuC;IACvC,MAAM,IAAI,SAAS,OAAO,EAAE;IAI5B,8DAA8D;IACxD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,+CAA+C;IAC/C,OAAO,IAAI,IAAI;IAKf,2DAA2D;IAC3D,OAAO,CAAC,QAAQ;IAYhB,oDAAoD;YACtC,OAAO;CAmBtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAgBrE"}
|