@sympoies/dsh-telegram 0.0.0-bootstrap.0 → 0.6.0
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 +589 -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,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choosing which model a Telegram conversation talks to.
|
|
3
|
+
*
|
|
4
|
+
* The web UI has a model picker; a phone had nothing, so every conversation
|
|
5
|
+
* ran on whatever the deployment happened to default to. That is the wrong
|
|
6
|
+
* place to be stuck: the reason to reach for a bot from a phone is often that
|
|
7
|
+
* the question is small and a cheaper model would do, or that it is hard and a
|
|
8
|
+
* better one is worth the wait.
|
|
9
|
+
*
|
|
10
|
+
* The choice is per conversation and durable, so it survives `/new` and a
|
|
11
|
+
* restart. It is applied per turn rather than at session creation, because the
|
|
12
|
+
* harness reads a mutable selection while assembling each step — which is what
|
|
13
|
+
* lets a model change take effect on the very next message.
|
|
14
|
+
*/
|
|
15
|
+
import type { ModelRoute } from '../harness/model-selection.js';
|
|
16
|
+
/** One reasoning effort a model offers. */
|
|
17
|
+
export interface EffortOption {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
}
|
|
21
|
+
/** What a model says about its own reasoning. */
|
|
22
|
+
export interface ReasoningInfo {
|
|
23
|
+
readonly efforts: readonly EffortOption[];
|
|
24
|
+
readonly defaultEffort?: string;
|
|
25
|
+
}
|
|
26
|
+
/** One model a provider advertises. */
|
|
27
|
+
export interface CatalogModel {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly name?: string;
|
|
30
|
+
}
|
|
31
|
+
/** One configured provider and what it offers. */
|
|
32
|
+
export interface CatalogProvider {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly name?: string;
|
|
35
|
+
readonly models: readonly CatalogModel[];
|
|
36
|
+
}
|
|
37
|
+
/** The `ctx.llm` surface this needs. */
|
|
38
|
+
export interface ProviderCatalog {
|
|
39
|
+
listProviders(): readonly {
|
|
40
|
+
id: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
}[];
|
|
43
|
+
listModels(provider: string): Promise<readonly CatalogModel[]>;
|
|
44
|
+
resolveModelInfo(provider: string, model: string): Promise<{
|
|
45
|
+
reasoning?: ReasoningInfo;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The efforts a route offers, and which one it uses by default.
|
|
50
|
+
*
|
|
51
|
+
* Read from the model rather than fixed here: `low`/`medium`/`high` is one
|
|
52
|
+
* provider's vocabulary, not everyone's, and offering an effort a model does
|
|
53
|
+
* not have would fail the turn rather than the command.
|
|
54
|
+
*
|
|
55
|
+
* @param catalog - the harness llm service.
|
|
56
|
+
* @param route - the model to ask about.
|
|
57
|
+
* @returns its reasoning options, or undefined when it has none or cannot say.
|
|
58
|
+
*/
|
|
59
|
+
export declare function effortsFor(catalog: ProviderCatalog, route: ModelRoute | undefined): Promise<ReasoningInfo | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Find the effort a user's words name.
|
|
62
|
+
*
|
|
63
|
+
* @param input - what followed `/effort`.
|
|
64
|
+
* @param efforts - what the current model offers.
|
|
65
|
+
* @returns the exact effort id, or undefined when nothing matches.
|
|
66
|
+
*/
|
|
67
|
+
export declare function matchEffort(input: string, efforts: readonly EffortOption[]): string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Every configured provider with its models, for a message that lists them.
|
|
70
|
+
*
|
|
71
|
+
* A provider whose catalog cannot be read is skipped rather than failing the
|
|
72
|
+
* listing: one misconfigured key should not hide every other model.
|
|
73
|
+
*
|
|
74
|
+
* @param catalog - the harness llm service.
|
|
75
|
+
*/
|
|
76
|
+
export declare function listCatalog(catalog: ProviderCatalog): Promise<CatalogProvider[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Find the route a user's words name.
|
|
79
|
+
*
|
|
80
|
+
* Accepts the full `provider/model`, and a bare model id when exactly one
|
|
81
|
+
* provider offers it — which is what anyone types first, and is unambiguous
|
|
82
|
+
* often enough to be worth supporting. A bare id offered by several providers
|
|
83
|
+
* is reported as ambiguous rather than guessed at.
|
|
84
|
+
*
|
|
85
|
+
* @param input - what followed `/model`.
|
|
86
|
+
* @param providers - the configured catalog.
|
|
87
|
+
*/
|
|
88
|
+
export declare function matchRoute(input: string, providers: readonly CatalogProvider[]): {
|
|
89
|
+
readonly kind: 'route';
|
|
90
|
+
readonly route: ModelRoute;
|
|
91
|
+
} | {
|
|
92
|
+
readonly kind: 'ambiguous';
|
|
93
|
+
readonly candidates: readonly string[];
|
|
94
|
+
} | {
|
|
95
|
+
readonly kind: 'unknown';
|
|
96
|
+
};
|
|
97
|
+
/** Render a route the way it is typed back in. */
|
|
98
|
+
export declare function formatRoute(route: ModelRoute | undefined): string;
|
|
99
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/session/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAG/D,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;CACzC;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,aAAa,IAAI,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAA;IAC9D,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,CAAC,EAAE,aAAa,CAAA;KAAE,CAAC,CAAA;CAC1F;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,UAAU,GAAG,SAAS,GAC5B,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAWpC;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,YAAY,EAAE,GAC/B,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAetF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,SAAS,eAAe,EAAE,GAEnC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CA4B/B;AAED,kDAAkD;AAClD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAEjE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choosing which model a Telegram conversation talks to.
|
|
3
|
+
*
|
|
4
|
+
* The web UI has a model picker; a phone had nothing, so every conversation
|
|
5
|
+
* ran on whatever the deployment happened to default to. That is the wrong
|
|
6
|
+
* place to be stuck: the reason to reach for a bot from a phone is often that
|
|
7
|
+
* the question is small and a cheaper model would do, or that it is hard and a
|
|
8
|
+
* better one is worth the wait.
|
|
9
|
+
*
|
|
10
|
+
* The choice is per conversation and durable, so it survives `/new` and a
|
|
11
|
+
* restart. It is applied per turn rather than at session creation, because the
|
|
12
|
+
* harness reads a mutable selection while assembling each step — which is what
|
|
13
|
+
* lets a model change take effect on the very next message.
|
|
14
|
+
*/
|
|
15
|
+
import { parseRoute } from '../harness/model-selection.js';
|
|
16
|
+
/**
|
|
17
|
+
* The efforts a route offers, and which one it uses by default.
|
|
18
|
+
*
|
|
19
|
+
* Read from the model rather than fixed here: `low`/`medium`/`high` is one
|
|
20
|
+
* provider's vocabulary, not everyone's, and offering an effort a model does
|
|
21
|
+
* not have would fail the turn rather than the command.
|
|
22
|
+
*
|
|
23
|
+
* @param catalog - the harness llm service.
|
|
24
|
+
* @param route - the model to ask about.
|
|
25
|
+
* @returns its reasoning options, or undefined when it has none or cannot say.
|
|
26
|
+
*/
|
|
27
|
+
export async function effortsFor(catalog, route) {
|
|
28
|
+
if (!route)
|
|
29
|
+
return undefined;
|
|
30
|
+
try {
|
|
31
|
+
const info = await catalog.resolveModelInfo(route.provider, route.model);
|
|
32
|
+
return info.reasoning !== undefined && info.reasoning.efforts.length > 0
|
|
33
|
+
? info.reasoning
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Find the effort a user's words name.
|
|
42
|
+
*
|
|
43
|
+
* @param input - what followed `/effort`.
|
|
44
|
+
* @param efforts - what the current model offers.
|
|
45
|
+
* @returns the exact effort id, or undefined when nothing matches.
|
|
46
|
+
*/
|
|
47
|
+
export function matchEffort(input, efforts) {
|
|
48
|
+
const wanted = input.trim().toLowerCase();
|
|
49
|
+
if (wanted === '')
|
|
50
|
+
return undefined;
|
|
51
|
+
const exact = efforts.find((effort) => effort.id.toLowerCase() === wanted);
|
|
52
|
+
if (exact)
|
|
53
|
+
return exact.id;
|
|
54
|
+
// A display name is what a person reads off the list, so it is what they
|
|
55
|
+
// type back. Accepted only while it names one effort.
|
|
56
|
+
const named = efforts.filter((effort) => (effort.name ?? '').toLowerCase() === wanted);
|
|
57
|
+
return named.length === 1 ? named[0].id : undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Every configured provider with its models, for a message that lists them.
|
|
61
|
+
*
|
|
62
|
+
* A provider whose catalog cannot be read is skipped rather than failing the
|
|
63
|
+
* listing: one misconfigured key should not hide every other model.
|
|
64
|
+
*
|
|
65
|
+
* @param catalog - the harness llm service.
|
|
66
|
+
*/
|
|
67
|
+
export async function listCatalog(catalog) {
|
|
68
|
+
const providers = catalog.listProviders();
|
|
69
|
+
const groups = await Promise.all(providers.map(async (provider) => {
|
|
70
|
+
try {
|
|
71
|
+
const models = await catalog.listModels(provider.id);
|
|
72
|
+
return { id: provider.id, ...(provider.name ? { name: provider.name } : {}), models };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
return groups.filter((group) => group !== undefined && group.models.length > 0);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Find the route a user's words name.
|
|
82
|
+
*
|
|
83
|
+
* Accepts the full `provider/model`, and a bare model id when exactly one
|
|
84
|
+
* provider offers it — which is what anyone types first, and is unambiguous
|
|
85
|
+
* often enough to be worth supporting. A bare id offered by several providers
|
|
86
|
+
* is reported as ambiguous rather than guessed at.
|
|
87
|
+
*
|
|
88
|
+
* @param input - what followed `/model`.
|
|
89
|
+
* @param providers - the configured catalog.
|
|
90
|
+
*/
|
|
91
|
+
export function matchRoute(input, providers) {
|
|
92
|
+
const wanted = input.trim();
|
|
93
|
+
if (wanted === '')
|
|
94
|
+
return { kind: 'unknown' };
|
|
95
|
+
const qualified = parseRoute(wanted);
|
|
96
|
+
if (qualified) {
|
|
97
|
+
const provider = providers.find((entry) => entry.id === qualified.provider);
|
|
98
|
+
const known = provider?.models.some((model) => model.id === qualified.model);
|
|
99
|
+
// An unlisted route is still taken when its provider is configured: a
|
|
100
|
+
// catalog can lag behind a model the provider actually serves, and
|
|
101
|
+
// refusing would make this stricter than the web UI's own picker.
|
|
102
|
+
if (provider)
|
|
103
|
+
return { kind: 'route', route: qualified };
|
|
104
|
+
if (known)
|
|
105
|
+
return { kind: 'route', route: qualified };
|
|
106
|
+
}
|
|
107
|
+
const matches = providers.flatMap((provider) => provider.models
|
|
108
|
+
.filter((model) => model.id.toLowerCase() === wanted.toLowerCase())
|
|
109
|
+
.map((model) => `${provider.id}/${model.id}`));
|
|
110
|
+
if (matches.length === 1) {
|
|
111
|
+
const route = parseRoute(matches[0]);
|
|
112
|
+
if (route)
|
|
113
|
+
return { kind: 'route', route };
|
|
114
|
+
}
|
|
115
|
+
if (matches.length > 1)
|
|
116
|
+
return { kind: 'ambiguous', candidates: matches };
|
|
117
|
+
return { kind: 'unknown' };
|
|
118
|
+
}
|
|
119
|
+
/** Render a route the way it is typed back in. */
|
|
120
|
+
export function formatRoute(route) {
|
|
121
|
+
return route === undefined ? 'the deployment default' : `${route.provider}/${route.model}`;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/session/models.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission preset a Telegram conversation runs under.
|
|
3
|
+
*
|
|
4
|
+
* A deployment picks one default for everything it runs, and that choice is
|
|
5
|
+
* usually made for the surface the operator sits in front of: the web UI is
|
|
6
|
+
* loopback-only, with a person watching what the agent does. A Telegram bot is
|
|
7
|
+
* not that. It is reachable from anywhere, gated by a list of user ids, and
|
|
8
|
+
* read on a phone — so the same `danger-full-access` that is merely convenient
|
|
9
|
+
* at a desk is a different proposition over a chat app.
|
|
10
|
+
*
|
|
11
|
+
* This lets the two differ. It is deliberately not a second place to author
|
|
12
|
+
* presets: the names come from the deployment's own table, and all this does
|
|
13
|
+
* is choose among them for conversations that arrive over Telegram.
|
|
14
|
+
*
|
|
15
|
+
* It also decides whether the approval buttons work at all. Under a preset
|
|
16
|
+
* whose approval policy is `never` nothing ever asks, so the buttons this
|
|
17
|
+
* plugin registers can never appear — choosing one that asks is what turns
|
|
18
|
+
* them on.
|
|
19
|
+
*/
|
|
20
|
+
import type { ChatTarget } from '../interact/surface.js';
|
|
21
|
+
import type { Logger } from '../harness/types.js';
|
|
22
|
+
/** The deployment's preset table, narrowed to what this needs. */
|
|
23
|
+
export interface PermissionPresetsLike {
|
|
24
|
+
/** Every preset name the deployment defines. */
|
|
25
|
+
readonly names: readonly string[];
|
|
26
|
+
/** Switch one session onto a preset; an unknown name throws. */
|
|
27
|
+
set(session: unknown, name: string): void;
|
|
28
|
+
}
|
|
29
|
+
/** Looking up a live session by id. */
|
|
30
|
+
export interface SessionLookup {
|
|
31
|
+
get(sessionId: string): unknown;
|
|
32
|
+
}
|
|
33
|
+
/** Construction options. */
|
|
34
|
+
export interface PermissionControlOptions {
|
|
35
|
+
readonly presets?: PermissionPresetsLike;
|
|
36
|
+
readonly sessions?: SessionLookup;
|
|
37
|
+
/**
|
|
38
|
+
* The preset this conversation should run under; empty or undefined leaves
|
|
39
|
+
* the deployment default. Read per session so a change lands on the next
|
|
40
|
+
* conversation rather than the next restart.
|
|
41
|
+
*/
|
|
42
|
+
readonly preset: (target: ChatTarget) => string | undefined;
|
|
43
|
+
readonly logger?: Logger;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Find the preset a user's words name.
|
|
47
|
+
*
|
|
48
|
+
* Matched loosely against the deployment's own table rather than against a
|
|
49
|
+
* fixed list: the names are the deployment's to choose, and `full access`,
|
|
50
|
+
* `full-access` and `danger-full-access` are all the same request from
|
|
51
|
+
* somebody typing on a phone.
|
|
52
|
+
*
|
|
53
|
+
* @param input - what the user typed.
|
|
54
|
+
* @param names - every preset the deployment defines.
|
|
55
|
+
* @returns the exact table key, or undefined when nothing matches.
|
|
56
|
+
*/
|
|
57
|
+
export declare function matchPreset(input: string, names: readonly string[]): string | undefined;
|
|
58
|
+
export declare class PermissionControl {
|
|
59
|
+
private readonly options;
|
|
60
|
+
private readonly logger;
|
|
61
|
+
/** Names already reported as unknown, so one typo is not logged per turn. */
|
|
62
|
+
private readonly complained;
|
|
63
|
+
constructor(options: PermissionControlOptions);
|
|
64
|
+
/** Whether this deployment can be told to use a different preset. */
|
|
65
|
+
get available(): boolean;
|
|
66
|
+
/** Every preset name available, for a message that has to name them. */
|
|
67
|
+
get names(): readonly string[];
|
|
68
|
+
/**
|
|
69
|
+
* Put one session on the configured preset.
|
|
70
|
+
*
|
|
71
|
+
* Applied after the session exists rather than at creation: the harness pins
|
|
72
|
+
* an initial permission before publishing, and switching afterwards is the
|
|
73
|
+
* supported path — it is what the web UI's own picker does mid-session.
|
|
74
|
+
*
|
|
75
|
+
* Failure is never fatal. A session that keeps the deployment default is
|
|
76
|
+
* exactly what an unconfigured deployment gets, so a bad name costs a log
|
|
77
|
+
* line rather than the user's message.
|
|
78
|
+
*
|
|
79
|
+
* @param sessionId - the session to switch.
|
|
80
|
+
*/
|
|
81
|
+
apply(target: ChatTarget, sessionId: string): void;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/session/permission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAGjD,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,gEAAgE;IAChE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1C;AAED,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;CAChC;AAED,4BAA4B;AAC5B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAA;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,GAAG,SAAS,CAAA;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAWvF;AAOD,qBAAa,iBAAiB;IAKhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;gBAElB,OAAO,EAAE,wBAAwB;IAI9D,qEAAqE;IACrE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,wEAAwE;IACxE,IAAI,KAAK,IAAI,SAAS,MAAM,EAAE,CAE7B;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;CA8BnD"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission preset a Telegram conversation runs under.
|
|
3
|
+
*
|
|
4
|
+
* A deployment picks one default for everything it runs, and that choice is
|
|
5
|
+
* usually made for the surface the operator sits in front of: the web UI is
|
|
6
|
+
* loopback-only, with a person watching what the agent does. A Telegram bot is
|
|
7
|
+
* not that. It is reachable from anywhere, gated by a list of user ids, and
|
|
8
|
+
* read on a phone — so the same `danger-full-access` that is merely convenient
|
|
9
|
+
* at a desk is a different proposition over a chat app.
|
|
10
|
+
*
|
|
11
|
+
* This lets the two differ. It is deliberately not a second place to author
|
|
12
|
+
* presets: the names come from the deployment's own table, and all this does
|
|
13
|
+
* is choose among them for conversations that arrive over Telegram.
|
|
14
|
+
*
|
|
15
|
+
* It also decides whether the approval buttons work at all. Under a preset
|
|
16
|
+
* whose approval policy is `never` nothing ever asks, so the buttons this
|
|
17
|
+
* plugin registers can never appear — choosing one that asks is what turns
|
|
18
|
+
* them on.
|
|
19
|
+
*/
|
|
20
|
+
import { SILENT_LOGGER } from '../harness/types.js';
|
|
21
|
+
/**
|
|
22
|
+
* Find the preset a user's words name.
|
|
23
|
+
*
|
|
24
|
+
* Matched loosely against the deployment's own table rather than against a
|
|
25
|
+
* fixed list: the names are the deployment's to choose, and `full access`,
|
|
26
|
+
* `full-access` and `danger-full-access` are all the same request from
|
|
27
|
+
* somebody typing on a phone.
|
|
28
|
+
*
|
|
29
|
+
* @param input - what the user typed.
|
|
30
|
+
* @param names - every preset the deployment defines.
|
|
31
|
+
* @returns the exact table key, or undefined when nothing matches.
|
|
32
|
+
*/
|
|
33
|
+
export function matchPreset(input, names) {
|
|
34
|
+
const wanted = normalize(input);
|
|
35
|
+
if (wanted === '')
|
|
36
|
+
return undefined;
|
|
37
|
+
const exact = names.find((name) => normalize(name) === wanted);
|
|
38
|
+
if (exact !== undefined)
|
|
39
|
+
return exact;
|
|
40
|
+
// A shorthand is accepted only while it names ONE preset. `read` picking
|
|
41
|
+
// `read-only` is helpful; a prefix shared by two would be a coin toss.
|
|
42
|
+
const partial = names.filter((name) => normalize(name).includes(wanted));
|
|
43
|
+
return partial.length === 1 ? partial[0] : undefined;
|
|
44
|
+
}
|
|
45
|
+
/** Compare names the way somebody types them, not the way they are stored. */
|
|
46
|
+
function normalize(text) {
|
|
47
|
+
return text.trim().toLowerCase().replace(/[\s_-]+/g, '');
|
|
48
|
+
}
|
|
49
|
+
export class PermissionControl {
|
|
50
|
+
options;
|
|
51
|
+
logger;
|
|
52
|
+
/** Names already reported as unknown, so one typo is not logged per turn. */
|
|
53
|
+
complained = new Set();
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this.options = options;
|
|
56
|
+
this.logger = options.logger ?? SILENT_LOGGER;
|
|
57
|
+
}
|
|
58
|
+
/** Whether this deployment can be told to use a different preset. */
|
|
59
|
+
get available() {
|
|
60
|
+
return this.options.presets !== undefined && this.options.sessions !== undefined;
|
|
61
|
+
}
|
|
62
|
+
/** Every preset name available, for a message that has to name them. */
|
|
63
|
+
get names() {
|
|
64
|
+
return this.options.presets?.names ?? [];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Put one session on the configured preset.
|
|
68
|
+
*
|
|
69
|
+
* Applied after the session exists rather than at creation: the harness pins
|
|
70
|
+
* an initial permission before publishing, and switching afterwards is the
|
|
71
|
+
* supported path — it is what the web UI's own picker does mid-session.
|
|
72
|
+
*
|
|
73
|
+
* Failure is never fatal. A session that keeps the deployment default is
|
|
74
|
+
* exactly what an unconfigured deployment gets, so a bad name costs a log
|
|
75
|
+
* line rather than the user's message.
|
|
76
|
+
*
|
|
77
|
+
* @param sessionId - the session to switch.
|
|
78
|
+
*/
|
|
79
|
+
apply(target, sessionId) {
|
|
80
|
+
const wanted = this.options.preset(target)?.trim();
|
|
81
|
+
if (!wanted)
|
|
82
|
+
return;
|
|
83
|
+
const { presets, sessions } = this.options;
|
|
84
|
+
if (!presets || !sessions)
|
|
85
|
+
return;
|
|
86
|
+
if (!presets.names.includes(wanted)) {
|
|
87
|
+
if (!this.complained.has(wanted)) {
|
|
88
|
+
this.complained.add(wanted);
|
|
89
|
+
this.logger.warn(`[dsh-telegram] unknown permission preset "${wanted}" ` +
|
|
90
|
+
`(known: ${presets.names.join(', ') || 'none'}); keeping the deployment default`);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const session = sessions.get(sessionId);
|
|
95
|
+
if (session === undefined) {
|
|
96
|
+
this.logger.debug(`[dsh-telegram] session '${sessionId}' is not live; permission unchanged`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
presets.set(session, wanted);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.logger.warn(`[dsh-telegram] could not apply permission preset "${wanted}"`, error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=permission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.js","sourceRoot":"","sources":["../../src/session/permission.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking up a conversation that `/new` left behind.
|
|
3
|
+
*
|
|
4
|
+
* The harness keeps every session's log, but the binding naming the current
|
|
5
|
+
* one is replaced, so from a phone `/new` is a one-way door: someone who
|
|
6
|
+
* starts a fresh conversation to ask one quick thing loses the one they were
|
|
7
|
+
* in the middle of.
|
|
8
|
+
*
|
|
9
|
+
* Offered as buttons rather than as `/resume 3`, for the same reason the
|
|
10
|
+
* recovery offer is: the list is already on screen, and asking someone to read
|
|
11
|
+
* an index off it and type a number back is asking them to do a computer's
|
|
12
|
+
* job. It also means there is nothing to mistype.
|
|
13
|
+
*/
|
|
14
|
+
import type { ChatSurface, ChatTarget } from '../interact/surface.js';
|
|
15
|
+
import type { PendingRegistry } from '../interact/pending.js';
|
|
16
|
+
import type { Logger } from '../harness/types.js';
|
|
17
|
+
import type { ChatHistory } from './history.js';
|
|
18
|
+
/** Everything the picker needs from the rest of the plugin. */
|
|
19
|
+
export interface SessionPickerOptions {
|
|
20
|
+
readonly surface: ChatSurface;
|
|
21
|
+
readonly pending: PendingRegistry<unknown>;
|
|
22
|
+
readonly history: ChatHistory;
|
|
23
|
+
/** The session this conversation is on now, so it is not offered to itself. */
|
|
24
|
+
readonly currentSession: (target: ChatTarget) => string | undefined;
|
|
25
|
+
/** Point the conversation at an existing session. */
|
|
26
|
+
readonly adopt: (target: ChatTarget, sessionId: string) => Promise<void>;
|
|
27
|
+
readonly logger?: Logger;
|
|
28
|
+
}
|
|
29
|
+
export declare class SessionPicker {
|
|
30
|
+
private readonly options;
|
|
31
|
+
private readonly logger;
|
|
32
|
+
constructor(options: SessionPickerOptions);
|
|
33
|
+
/**
|
|
34
|
+
* Offer this chat's earlier conversations.
|
|
35
|
+
*
|
|
36
|
+
* @param target - the conversation asking.
|
|
37
|
+
*/
|
|
38
|
+
offer(target: ChatTarget): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Route one button press.
|
|
41
|
+
*
|
|
42
|
+
* @param data - raw `callback_data` from the update.
|
|
43
|
+
* @returns whether the press belonged to an open picker.
|
|
44
|
+
*/
|
|
45
|
+
handleCallback(data: string | undefined): boolean;
|
|
46
|
+
/** Post one message, swallowing delivery failures. */
|
|
47
|
+
private say;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/session/picker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAGjD,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,cAAc,CAAA;AAQ5D,+DAA+D;AAC/D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;IAC1C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,GAAG,SAAS,CAAA;IACnE,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,qBAAa,aAAa;IAGZ,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEF,OAAO,EAAE,oBAAoB;IAI1D;;;;OAIG;IACG,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAuD9C;;;;;OAKG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO;IAajD,sDAAsD;YACxC,GAAG;CAWlB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking up a conversation that `/new` left behind.
|
|
3
|
+
*
|
|
4
|
+
* The harness keeps every session's log, but the binding naming the current
|
|
5
|
+
* one is replaced, so from a phone `/new` is a one-way door: someone who
|
|
6
|
+
* starts a fresh conversation to ask one quick thing loses the one they were
|
|
7
|
+
* in the middle of.
|
|
8
|
+
*
|
|
9
|
+
* Offered as buttons rather than as `/resume 3`, for the same reason the
|
|
10
|
+
* recovery offer is: the list is already on screen, and asking someone to read
|
|
11
|
+
* an index off it and type a number back is asking them to do a computer's
|
|
12
|
+
* job. It also means there is nothing to mistype.
|
|
13
|
+
*/
|
|
14
|
+
import { escapeHtml } from '../render/escape.js';
|
|
15
|
+
import { SILENT_LOGGER } from '../harness/types.js';
|
|
16
|
+
/** Prefix marking callback data as belonging to a session picker. */
|
|
17
|
+
const KIND = 's';
|
|
18
|
+
/** How many to offer; Telegram keyboards get unusable long before this. */
|
|
19
|
+
const OFFER = 8;
|
|
20
|
+
export class SessionPicker {
|
|
21
|
+
options;
|
|
22
|
+
logger;
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.options = options;
|
|
25
|
+
this.logger = options.logger ?? SILENT_LOGGER;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Offer this chat's earlier conversations.
|
|
29
|
+
*
|
|
30
|
+
* @param target - the conversation asking.
|
|
31
|
+
*/
|
|
32
|
+
async offer(target) {
|
|
33
|
+
const current = this.options.currentSession(target);
|
|
34
|
+
const past = this.options
|
|
35
|
+
.history.forChat(target)
|
|
36
|
+
.filter((entry) => entry.sessionId !== current)
|
|
37
|
+
.slice(0, OFFER);
|
|
38
|
+
if (past.length === 0) {
|
|
39
|
+
await this.say(target, current === undefined
|
|
40
|
+
? 'No conversations yet — send a message to start one.'
|
|
41
|
+
: 'This is the only conversation in this chat so far.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const waiter = this.options.pending.open({});
|
|
45
|
+
const keyboard = past.map((entry, index) => [
|
|
46
|
+
{
|
|
47
|
+
text: labelOf(entry),
|
|
48
|
+
callbackData: `${KIND}:${waiter.token}:${index}`,
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
await this.say(target, [
|
|
52
|
+
'<b>Earlier conversations</b>',
|
|
53
|
+
'',
|
|
54
|
+
'Pick one to carry on with. The one you are in now stays where it is.',
|
|
55
|
+
].join('\n'), keyboard);
|
|
56
|
+
const pressed = await waiter.promise;
|
|
57
|
+
if (typeof pressed !== 'string')
|
|
58
|
+
return;
|
|
59
|
+
const index = Number.parseInt(pressed, 10);
|
|
60
|
+
const chosen = past[index];
|
|
61
|
+
if (!chosen)
|
|
62
|
+
return;
|
|
63
|
+
try {
|
|
64
|
+
await this.options.adopt(target, chosen.sessionId);
|
|
65
|
+
await this.say(target, `↩️ Back in <b>${escapeHtml(labelOf(chosen))}</b>.\n\n` +
|
|
66
|
+
`<code>${escapeHtml(chosen.cwd)}</code>`);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
this.logger.error('[dsh-telegram] could not adopt an earlier session', error);
|
|
70
|
+
await this.say(target, '⚠️ That conversation could not be reopened.');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Route one button press.
|
|
75
|
+
*
|
|
76
|
+
* @param data - raw `callback_data` from the update.
|
|
77
|
+
* @returns whether the press belonged to an open picker.
|
|
78
|
+
*/
|
|
79
|
+
handleCallback(data) {
|
|
80
|
+
if (data === undefined)
|
|
81
|
+
return false;
|
|
82
|
+
const parts = data.split(':');
|
|
83
|
+
if (parts.length !== 3 || parts[0] !== KIND)
|
|
84
|
+
return false;
|
|
85
|
+
const token = parts[1];
|
|
86
|
+
const index = parts[2];
|
|
87
|
+
if (token === '' || !/^\d+$/.test(index))
|
|
88
|
+
return false;
|
|
89
|
+
return this.options.pending.settle(token, index);
|
|
90
|
+
}
|
|
91
|
+
/** Post one message, swallowing delivery failures. */
|
|
92
|
+
async say(target, html, keyboard) {
|
|
93
|
+
try {
|
|
94
|
+
await this.options.surface.send(target, html, keyboard);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
this.logger.warn('[dsh-telegram] could not offer the session list', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** A button label: what the conversation opened with, or when it started. */
|
|
102
|
+
function labelOf(entry) {
|
|
103
|
+
if (entry.label !== undefined && entry.label !== '')
|
|
104
|
+
return entry.label;
|
|
105
|
+
return new Date(entry.startedAt).toISOString().slice(0, 16).replace('T', ' ');
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../../src/session/picker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One durable string a conversation chose for itself.
|
|
3
|
+
*
|
|
4
|
+
* Two things need this and they are the same shape: the directory a chat works
|
|
5
|
+
* in, and the model it talks to. Both are per conversation, both must outlive
|
|
6
|
+
* `/new` — a preference discarded with the session would silently snap back to
|
|
7
|
+
* the deployment default on the next message — and both must outlive a
|
|
8
|
+
* restart.
|
|
9
|
+
*
|
|
10
|
+
* Kept apart from the session binding for exactly that reason: a binding is
|
|
11
|
+
* one conversation with the agent and dies with `/new`, while a preference
|
|
12
|
+
* belongs to the CHAT.
|
|
13
|
+
*/
|
|
14
|
+
import type { ChatTarget } from '../interact/surface.js';
|
|
15
|
+
/** Construction options. */
|
|
16
|
+
export interface ChatPreferenceOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Whether a stored value is still usable.
|
|
19
|
+
*
|
|
20
|
+
* Applied on read rather than only on write, because the file outlives the
|
|
21
|
+
* process that wrote it: a path that was absolute, or a model that was
|
|
22
|
+
* configured, may be neither by the time it is read back.
|
|
23
|
+
*/
|
|
24
|
+
readonly accept: (value: string) => boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare class ChatPreferences {
|
|
27
|
+
private readonly file;
|
|
28
|
+
private readonly accept;
|
|
29
|
+
private values;
|
|
30
|
+
private constructor();
|
|
31
|
+
/**
|
|
32
|
+
* Load the store, or start empty when there is nothing readable yet.
|
|
33
|
+
*
|
|
34
|
+
* A corrupt file costs the conversation its preference, which one command
|
|
35
|
+
* restores; refusing to start would cost the whole bot.
|
|
36
|
+
*
|
|
37
|
+
* @param file - absolute path to the JSON document.
|
|
38
|
+
* @param options - how to tell a usable value from one to drop.
|
|
39
|
+
*/
|
|
40
|
+
static open(file: string, options: ChatPreferenceOptions): Promise<ChatPreferences>;
|
|
41
|
+
/** What this conversation chose, if it chose anything. */
|
|
42
|
+
forChat(target: ChatTarget): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Remember a conversation's choice.
|
|
45
|
+
*
|
|
46
|
+
* @param target - the conversation.
|
|
47
|
+
* @param value - the value, already checked by the caller.
|
|
48
|
+
*/
|
|
49
|
+
set(target: ChatTarget, value: string): Promise<void>;
|
|
50
|
+
/** Forget it, returning the conversation to the deployment default. */
|
|
51
|
+
clear(target: ChatTarget): Promise<void>;
|
|
52
|
+
/** Write the document atomically, so a crash cannot leave a half file. */
|
|
53
|
+
private persist;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=preferences.d.ts.map
|