@moxxy/plugin-channel-discord 0.27.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 +21 -0
- package/dist/allow-list.d.ts +20 -0
- package/dist/allow-list.d.ts.map +1 -0
- package/dist/allow-list.js +12 -0
- package/dist/allow-list.js.map +1 -0
- package/dist/approval.d.ts +29 -0
- package/dist/approval.d.ts.map +1 -0
- package/dist/approval.js +65 -0
- package/dist/approval.js.map +1 -0
- package/dist/channel/allow-list-store.d.ts +22 -0
- package/dist/channel/allow-list-store.d.ts.map +1 -0
- package/dist/channel/allow-list-store.js +45 -0
- package/dist/channel/allow-list-store.js.map +1 -0
- package/dist/channel/approval-prompt.d.ts +16 -0
- package/dist/channel/approval-prompt.d.ts.map +1 -0
- package/dist/channel/approval-prompt.js +27 -0
- package/dist/channel/approval-prompt.js.map +1 -0
- package/dist/channel/components.d.ts +27 -0
- package/dist/channel/components.d.ts.map +1 -0
- package/dist/channel/components.js +34 -0
- package/dist/channel/components.js.map +1 -0
- package/dist/channel/discord-like.d.ts +31 -0
- package/dist/channel/discord-like.d.ts.map +1 -0
- package/dist/channel/discord-like.js +9 -0
- package/dist/channel/discord-like.js.map +1 -0
- package/dist/channel/interaction-handler.d.ts +67 -0
- package/dist/channel/interaction-handler.d.ts.map +1 -0
- package/dist/channel/interaction-handler.js +155 -0
- package/dist/channel/interaction-handler.js.map +1 -0
- package/dist/channel/message-handler.d.ts +60 -0
- package/dist/channel/message-handler.d.ts.map +1 -0
- package/dist/channel/message-handler.js +135 -0
- package/dist/channel/message-handler.js.map +1 -0
- package/dist/channel/pairing-handler.d.ts +58 -0
- package/dist/channel/pairing-handler.d.ts.map +1 -0
- package/dist/channel/pairing-handler.js +109 -0
- package/dist/channel/pairing-handler.js.map +1 -0
- package/dist/channel/permission-prompt.d.ts +19 -0
- package/dist/channel/permission-prompt.d.ts.map +1 -0
- package/dist/channel/permission-prompt.js +38 -0
- package/dist/channel/permission-prompt.js.map +1 -0
- package/dist/channel/slash-handler.d.ts +45 -0
- package/dist/channel/slash-handler.d.ts.map +1 -0
- package/dist/channel/slash-handler.js +107 -0
- package/dist/channel/slash-handler.js.map +1 -0
- package/dist/channel/turn-runner.d.ts +49 -0
- package/dist/channel/turn-runner.d.ts.map +1 -0
- package/dist/channel/turn-runner.js +116 -0
- package/dist/channel/turn-runner.js.map +1 -0
- package/dist/channel/typing-indicator.d.ts +12 -0
- package/dist/channel/typing-indicator.d.ts.map +1 -0
- package/dist/channel/typing-indicator.js +28 -0
- package/dist/channel/typing-indicator.js.map +1 -0
- package/dist/channel/voice-handler.d.ts +41 -0
- package/dist/channel/voice-handler.d.ts.map +1 -0
- package/dist/channel/voice-handler.js +105 -0
- package/dist/channel/voice-handler.js.map +1 -0
- package/dist/channel.d.ts +107 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +464 -0
- package/dist/channel.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +64 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +85 -0
- package/dist/keys.js.map +1 -0
- package/dist/pair-flow.d.ts +16 -0
- package/dist/pair-flow.d.ts.map +1 -0
- package/dist/pair-flow.js +109 -0
- package/dist/pair-flow.js.map +1 -0
- package/dist/pairing.d.ts +83 -0
- package/dist/pairing.d.ts.map +1 -0
- package/dist/pairing.js +98 -0
- package/dist/pairing.js.map +1 -0
- package/dist/permission.d.ts +30 -0
- package/dist/permission.d.ts.map +1 -0
- package/dist/permission.js +54 -0
- package/dist/permission.js.map +1 -0
- package/dist/render.d.ts +34 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +204 -0
- package/dist/render.js.map +1 -0
- package/dist/schema.d.ts +123 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +70 -0
- package/dist/schema.js.map +1 -0
- package/dist/setup-wizard.d.ts +13 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +133 -0
- package/dist/setup-wizard.js.map +1 -0
- package/package.json +89 -0
- package/src/allow-list.test.ts +64 -0
- package/src/allow-list.ts +30 -0
- package/src/approval.ts +76 -0
- package/src/channel/allow-list-store.ts +52 -0
- package/src/channel/approval-prompt.ts +43 -0
- package/src/channel/components.ts +49 -0
- package/src/channel/discord-like.ts +34 -0
- package/src/channel/interaction-handler.test.ts +212 -0
- package/src/channel/interaction-handler.ts +237 -0
- package/src/channel/message-handler.test.ts +295 -0
- package/src/channel/message-handler.ts +195 -0
- package/src/channel/pairing-handler.ts +147 -0
- package/src/channel/permission-prompt.ts +54 -0
- package/src/channel/slash-handler.ts +136 -0
- package/src/channel/turn-runner.test.ts +177 -0
- package/src/channel/turn-runner.ts +139 -0
- package/src/channel/typing-indicator.ts +31 -0
- package/src/channel/voice-handler.test.ts +177 -0
- package/src/channel/voice-handler.ts +150 -0
- package/src/channel/voice-reply.test.ts +142 -0
- package/src/channel.ts +582 -0
- package/src/index.ts +247 -0
- package/src/keys.ts +100 -0
- package/src/pair-flow.ts +122 -0
- package/src/pairing.test.ts +99 -0
- package/src/pairing.ts +178 -0
- package/src/permission.ts +70 -0
- package/src/render.test.ts +82 -0
- package/src/render.ts +228 -0
- package/src/schema.ts +94 -0
- package/src/setup-wizard.ts +162 -0
- package/src/subcommands.test.ts +182 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { InboundMessage } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where the paired principal may drive the session from:
|
|
4
|
+
* - their own DM with the bot — always allowed once paired;
|
|
5
|
+
* - a guild channel — only when that channel id is on the allow-list
|
|
6
|
+
* (managed by the paired user via the local /allow and /deny commands).
|
|
7
|
+
*
|
|
8
|
+
* Authorship is checked FIRST: a message from anyone but the paired user never
|
|
9
|
+
* reaches the session, no matter where it was sent (single-operator model,
|
|
10
|
+
* matching Telegram's single paired chat).
|
|
11
|
+
*/
|
|
12
|
+
export type GateVerdict = {
|
|
13
|
+
readonly ok: true;
|
|
14
|
+
readonly context: 'dm' | 'guild';
|
|
15
|
+
} | {
|
|
16
|
+
readonly ok: false;
|
|
17
|
+
readonly reason: 'not-paired' | 'foreign-user' | 'channel-not-allowed';
|
|
18
|
+
};
|
|
19
|
+
export declare function gateInbound(msg: Pick<InboundMessage, 'authorId' | 'guildId' | 'channelId'>, authorizedUserId: string | null, allowedChannels: ReadonlySet<string>): GateVerdict;
|
|
20
|
+
//# sourceMappingURL=allow-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allow-list.d.ts","sourceRoot":"","sources":["../src/allow-list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,OAAO,CAAA;CAAE,GACvD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,GAAG,qBAAqB,CAAC;CACxE,CAAC;AAEN,wBAAgB,WAAW,CACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,GAAG,SAAS,GAAG,WAAW,CAAC,EAC/D,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAC/B,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GACnC,WAAW,CAMb"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function gateInbound(msg, authorizedUserId, allowedChannels) {
|
|
2
|
+
if (!authorizedUserId)
|
|
3
|
+
return { ok: false, reason: 'not-paired' };
|
|
4
|
+
if (msg.authorId !== authorizedUserId)
|
|
5
|
+
return { ok: false, reason: 'foreign-user' };
|
|
6
|
+
if (msg.guildId == null)
|
|
7
|
+
return { ok: true, context: 'dm' };
|
|
8
|
+
if (allowedChannels.has(msg.channelId))
|
|
9
|
+
return { ok: true, context: 'guild' };
|
|
10
|
+
return { ok: false, reason: 'channel-not-allowed' };
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=allow-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allow-list.js","sourceRoot":"","sources":["../src/allow-list.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,WAAW,CACzB,GAA+D,EAC/D,gBAA+B,EAC/B,eAAoC;IAEpC,IAAI,CAAC,gBAAgB;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAClE,IAAI,GAAG,CAAC,QAAQ,KAAK,gBAAgB;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACpF,IAAI,GAAG,CAAC,OAAO,IAAI,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC5D,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC9E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ApprovalDecision, ApprovalRequest, ApprovalResolver } from '@moxxy/sdk';
|
|
2
|
+
export interface PendingApproval {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly request: ApprovalRequest;
|
|
5
|
+
readonly resolve: (decision: ApprovalDecision) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generic approval resolver for the Discord channel — counterpart to the TUI's
|
|
9
|
+
* ApprovalDialog. The channel renders the request as a message with a button
|
|
10
|
+
* per option and routes the user's click (or follow-up text, for options with
|
|
11
|
+
* `requestsText: true`) back here via `resolvePending` /
|
|
12
|
+
* `resolvePendingWithText`. Mirrors the Telegram approval resolver's shape.
|
|
13
|
+
*/
|
|
14
|
+
export declare class DiscordApprovalResolver implements ApprovalResolver {
|
|
15
|
+
readonly name = "discord-approval";
|
|
16
|
+
private readonly pending;
|
|
17
|
+
private deciderFn;
|
|
18
|
+
private nextId;
|
|
19
|
+
setDecider(fn: (id: string, request: ApprovalRequest) => Promise<void>): void;
|
|
20
|
+
confirm(request: ApprovalRequest): Promise<ApprovalDecision>;
|
|
21
|
+
/** Look up a pending approval without resolving it (text follow-up latch). */
|
|
22
|
+
getPending(id: string): PendingApproval | undefined;
|
|
23
|
+
/** Resolve directly with an option id (no text). */
|
|
24
|
+
resolvePending(id: string, optionId: string): boolean;
|
|
25
|
+
/** Resolve with an option id AND a free-text follow-up. */
|
|
26
|
+
resolvePendingWithText(id: string, optionId: string, text: string): boolean;
|
|
27
|
+
abortAll(reason?: string): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACxD;AAED;;;;;;GAMG;AACH,qBAAa,uBAAwB,YAAW,gBAAgB;IAC9D,QAAQ,CAAC,IAAI,sBAAsB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,SAAS,CAA0E;IAC3F,OAAO,CAAC,MAAM,CAAK;IAEnB,UAAU,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIvE,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkBlE,8EAA8E;IAC9E,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAInD,oDAAoD;IACpD,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQrD,2DAA2D;IAC3D,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAQ3E,QAAQ,CAAC,MAAM,SAAmB,GAAG,IAAI;CAS1C"}
|
package/dist/approval.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic approval resolver for the Discord channel — counterpart to the TUI's
|
|
3
|
+
* ApprovalDialog. The channel renders the request as a message with a button
|
|
4
|
+
* per option and routes the user's click (or follow-up text, for options with
|
|
5
|
+
* `requestsText: true`) back here via `resolvePending` /
|
|
6
|
+
* `resolvePendingWithText`. Mirrors the Telegram approval resolver's shape.
|
|
7
|
+
*/
|
|
8
|
+
export class DiscordApprovalResolver {
|
|
9
|
+
name = 'discord-approval';
|
|
10
|
+
pending = new Map();
|
|
11
|
+
deciderFn = null;
|
|
12
|
+
nextId = 1;
|
|
13
|
+
setDecider(fn) {
|
|
14
|
+
this.deciderFn = fn;
|
|
15
|
+
}
|
|
16
|
+
async confirm(request) {
|
|
17
|
+
if (!this.deciderFn) {
|
|
18
|
+
// No bot wired up — best fallback is the request's defaultOptionId.
|
|
19
|
+
return { optionId: request.defaultOptionId ?? request.options[0]?.id ?? 'approve' };
|
|
20
|
+
}
|
|
21
|
+
const id = `appr_${this.nextId++}`;
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
this.pending.set(id, { id, request, resolve });
|
|
24
|
+
this.deciderFn(id, request).catch((err) => {
|
|
25
|
+
this.pending.delete(id);
|
|
26
|
+
resolve({
|
|
27
|
+
optionId: request.defaultOptionId ?? request.options[0]?.id ?? 'cancel',
|
|
28
|
+
text: `decider failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** Look up a pending approval without resolving it (text follow-up latch). */
|
|
34
|
+
getPending(id) {
|
|
35
|
+
return this.pending.get(id);
|
|
36
|
+
}
|
|
37
|
+
/** Resolve directly with an option id (no text). */
|
|
38
|
+
resolvePending(id, optionId) {
|
|
39
|
+
const pending = this.pending.get(id);
|
|
40
|
+
if (!pending)
|
|
41
|
+
return false;
|
|
42
|
+
this.pending.delete(id);
|
|
43
|
+
pending.resolve({ optionId });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/** Resolve with an option id AND a free-text follow-up. */
|
|
47
|
+
resolvePendingWithText(id, optionId, text) {
|
|
48
|
+
const pending = this.pending.get(id);
|
|
49
|
+
if (!pending)
|
|
50
|
+
return false;
|
|
51
|
+
this.pending.delete(id);
|
|
52
|
+
pending.resolve({ optionId, text });
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
abortAll(reason = 'channel closed') {
|
|
56
|
+
for (const pending of this.pending.values()) {
|
|
57
|
+
pending.resolve({
|
|
58
|
+
optionId: pending.request.defaultOptionId ?? pending.request.options[0]?.id ?? 'cancel',
|
|
59
|
+
text: reason,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
this.pending.clear();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { VaultStore } from '@moxxy/plugin-vault';
|
|
2
|
+
/**
|
|
3
|
+
* Vault-backed guild-channel allow-list: the channels (beyond the paired
|
|
4
|
+
* user's DM) the paired user may drive the session from. Managed via the
|
|
5
|
+
* local `/allow` and `/deny` commands; persisted as a JSON snowflake array
|
|
6
|
+
* under {@link DISCORD_ALLOWED_CHANNELS_KEY} (corrupt values read as empty —
|
|
7
|
+
* deny-by-default).
|
|
8
|
+
*/
|
|
9
|
+
export declare class AllowListStore {
|
|
10
|
+
private readonly vault;
|
|
11
|
+
private ids;
|
|
12
|
+
constructor(vault: VaultStore);
|
|
13
|
+
load(): Promise<void>;
|
|
14
|
+
snapshot(): ReadonlySet<string>;
|
|
15
|
+
has(channelId: string): boolean;
|
|
16
|
+
/** Add a channel id. Returns false (no write) for a non-snowflake id. */
|
|
17
|
+
add(channelId: string): Promise<boolean>;
|
|
18
|
+
/** Remove a channel id. Returns true when it was present. */
|
|
19
|
+
remove(channelId: string): Promise<boolean>;
|
|
20
|
+
private persist;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=allow-list-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allow-list-store.d.ts","sourceRoot":"","sources":["../../src/channel/allow-list-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAQtD;;;;;;GAMG;AACH,qBAAa,cAAc;IAGb,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,OAAO,CAAC,GAAG,CAAqB;gBAEH,KAAK,EAAE,UAAU;IAExC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,QAAQ,IAAI,WAAW,CAAC,MAAM,CAAC;IAI/B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B,yEAAyE;IACnE,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ9C,6DAA6D;IACvD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAMnC,OAAO;CAGtB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DISCORD_ALLOWED_CHANNELS_KEY, parseAllowedChannels, serializeAllowedChannels, snowflakeSchema, } from '../keys.js';
|
|
2
|
+
/**
|
|
3
|
+
* Vault-backed guild-channel allow-list: the channels (beyond the paired
|
|
4
|
+
* user's DM) the paired user may drive the session from. Managed via the
|
|
5
|
+
* local `/allow` and `/deny` commands; persisted as a JSON snowflake array
|
|
6
|
+
* under {@link DISCORD_ALLOWED_CHANNELS_KEY} (corrupt values read as empty —
|
|
7
|
+
* deny-by-default).
|
|
8
|
+
*/
|
|
9
|
+
export class AllowListStore {
|
|
10
|
+
vault;
|
|
11
|
+
ids = new Set();
|
|
12
|
+
constructor(vault) {
|
|
13
|
+
this.vault = vault;
|
|
14
|
+
}
|
|
15
|
+
async load() {
|
|
16
|
+
this.ids = new Set(parseAllowedChannels(await this.vault.get(DISCORD_ALLOWED_CHANNELS_KEY)));
|
|
17
|
+
}
|
|
18
|
+
snapshot() {
|
|
19
|
+
return this.ids;
|
|
20
|
+
}
|
|
21
|
+
has(channelId) {
|
|
22
|
+
return this.ids.has(channelId);
|
|
23
|
+
}
|
|
24
|
+
/** Add a channel id. Returns false (no write) for a non-snowflake id. */
|
|
25
|
+
async add(channelId) {
|
|
26
|
+
if (!snowflakeSchema.safeParse(channelId).success)
|
|
27
|
+
return false;
|
|
28
|
+
if (this.ids.has(channelId))
|
|
29
|
+
return true;
|
|
30
|
+
this.ids.add(channelId);
|
|
31
|
+
await this.persist();
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
/** Remove a channel id. Returns true when it was present. */
|
|
35
|
+
async remove(channelId) {
|
|
36
|
+
const removed = this.ids.delete(channelId);
|
|
37
|
+
if (removed)
|
|
38
|
+
await this.persist();
|
|
39
|
+
return removed;
|
|
40
|
+
}
|
|
41
|
+
async persist() {
|
|
42
|
+
await this.vault.set(DISCORD_ALLOWED_CHANNELS_KEY, serializeAllowedChannels(this.ids));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=allow-list-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allow-list-store.js","sourceRoot":"","sources":["../../src/channel/allow-list-store.ts"],"names":[],"mappings":"AACA,OAAO,EACL,4BAA4B,EAC5B,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IAGI;IAFrB,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,YAA6B,KAAiB;QAAjB,UAAK,GAAL,KAAK,CAAY;IAAG,CAAC;IAElD,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC;IAC/F,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAED,GAAG,CAAC,SAAiB;QACnB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,GAAG,CAAC,SAAiB;QACzB,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAChE,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACxB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6DAA6D;IAC7D,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,OAAO;YAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,4BAA4B,EAAE,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACzF,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ApprovalRequest } from '@moxxy/sdk';
|
|
2
|
+
import type { DiscordApprovalResolver } from '../approval.js';
|
|
3
|
+
import type { ChannelLogger, SendableChannelLike } from './discord-like.js';
|
|
4
|
+
export interface ApprovalPromptDeps {
|
|
5
|
+
readonly channel: SendableChannelLike | null;
|
|
6
|
+
readonly resolver: DiscordApprovalResolver;
|
|
7
|
+
readonly logger?: ChannelLogger;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Render an approval request (e.g. plan-execute "validate plan") as a message
|
|
11
|
+
* with a button per option. Options with `requestsText` are still picked here;
|
|
12
|
+
* the interaction handler then captures the user's NEXT message as the
|
|
13
|
+
* follow-up text via the channel's awaiting-text state.
|
|
14
|
+
*/
|
|
15
|
+
export declare function askForApproval(id: string, request: ApprovalRequest, deps: ApprovalPromptDeps): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=approval-prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-prompt.d.ts","sourceRoot":"","sources":["../../src/channel/approval-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE5E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BUTTON_STYLE, button, packRows } from './components.js';
|
|
2
|
+
/**
|
|
3
|
+
* Render an approval request (e.g. plan-execute "validate plan") as a message
|
|
4
|
+
* with a button per option. Options with `requestsText` are still picked here;
|
|
5
|
+
* the interaction handler then captures the user's NEXT message as the
|
|
6
|
+
* follow-up text via the channel's awaiting-text state.
|
|
7
|
+
*/
|
|
8
|
+
export async function askForApproval(id, request, deps) {
|
|
9
|
+
if (!deps.channel)
|
|
10
|
+
return;
|
|
11
|
+
const rows = packRows(request.options.map((opt) => button(`appr:${id}:${opt.id}`, opt.label, BUTTON_STYLE.secondary)));
|
|
12
|
+
// Discord caps messages at 2000 chars; keep the body well under so the
|
|
13
|
+
// prompt always sends. The full plan streams in as assistant messages.
|
|
14
|
+
const body = request.body.length > 1500 ? request.body.slice(0, 1499) + '…' : request.body;
|
|
15
|
+
const summary = `📋 **${request.title}**\n\n${body}\n\n` +
|
|
16
|
+
`Pick an option below. Some options (e.g. Redraft) prompt for follow-up text after you click.`;
|
|
17
|
+
try {
|
|
18
|
+
await deps.channel.send({ content: summary, components: rows });
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
deps.logger?.warn('discord approval send failed', { err: String(err) });
|
|
22
|
+
// Default-resolve on send failure so the loop strategy doesn't hang.
|
|
23
|
+
const fallback = request.defaultOptionId ?? request.options[0]?.id ?? 'cancel';
|
|
24
|
+
deps.resolver.resolvePending(id, fallback);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=approval-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-prompt.js","sourceRoot":"","sources":["../../src/channel/approval-prompt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AASjE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAU,EACV,OAAwB,EACxB,IAAwB;IAExB,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO;IAC1B,MAAM,IAAI,GAAG,QAAQ,CACnB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC1B,MAAM,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,SAAS,CAAC,CAClE,CACF,CAAC;IACF,uEAAuE;IACvE,uEAAuE;IACvE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3F,MAAM,OAAO,GACX,QAAQ,OAAO,CAAC,KAAK,SAAS,IAAI,MAAM;QACxC,8FAA8F,CAAC;IACjG,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,8BAA8B,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxE,qEAAqE;QACrE,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,QAAQ,CAAC;QAC/E,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw Discord message-component JSON (API shape) for the button rows the
|
|
3
|
+
* channel renders. Built as plain objects rather than discord.js Builders so
|
|
4
|
+
* the prompt modules stay dependency-free and unit-testable — discord.js
|
|
5
|
+
* accepts raw API component data in `send()` payloads.
|
|
6
|
+
*/
|
|
7
|
+
/** Discord API button styles. */
|
|
8
|
+
export declare const BUTTON_STYLE: {
|
|
9
|
+
readonly primary: 1;
|
|
10
|
+
readonly secondary: 2;
|
|
11
|
+
readonly success: 3;
|
|
12
|
+
readonly danger: 4;
|
|
13
|
+
};
|
|
14
|
+
export interface ApiButton {
|
|
15
|
+
readonly type: 2;
|
|
16
|
+
readonly style: number;
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly custom_id: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ApiActionRow {
|
|
21
|
+
readonly type: 1;
|
|
22
|
+
readonly components: ReadonlyArray<ApiButton>;
|
|
23
|
+
}
|
|
24
|
+
export declare function button(customId: string, label: string, style: number): ApiButton;
|
|
25
|
+
/** Pack buttons into action rows (5 per row, 5 rows max — 25 button cap). */
|
|
26
|
+
export declare function packRows(buttons: ReadonlyArray<ApiButton>): ApiActionRow[];
|
|
27
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/channel/components.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iCAAiC;AACjC,eAAO,MAAM,YAAY;;;;;CAKf,CAAC;AAEX,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CAC/C;AAOD,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAOhF;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,YAAY,EAAE,CAM1E"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw Discord message-component JSON (API shape) for the button rows the
|
|
3
|
+
* channel renders. Built as plain objects rather than discord.js Builders so
|
|
4
|
+
* the prompt modules stay dependency-free and unit-testable — discord.js
|
|
5
|
+
* accepts raw API component data in `send()` payloads.
|
|
6
|
+
*/
|
|
7
|
+
/** Discord API button styles. */
|
|
8
|
+
export const BUTTON_STYLE = {
|
|
9
|
+
primary: 1,
|
|
10
|
+
secondary: 2,
|
|
11
|
+
success: 3,
|
|
12
|
+
danger: 4,
|
|
13
|
+
};
|
|
14
|
+
const MAX_LABEL_CHARS = 80;
|
|
15
|
+
const MAX_CUSTOM_ID_CHARS = 100;
|
|
16
|
+
const MAX_BUTTONS_PER_ROW = 5;
|
|
17
|
+
const MAX_ROWS = 5;
|
|
18
|
+
export function button(customId, label, style) {
|
|
19
|
+
return {
|
|
20
|
+
type: 2,
|
|
21
|
+
style,
|
|
22
|
+
label: label.slice(0, MAX_LABEL_CHARS) || '…',
|
|
23
|
+
custom_id: customId.slice(0, MAX_CUSTOM_ID_CHARS),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Pack buttons into action rows (5 per row, 5 rows max — 25 button cap). */
|
|
27
|
+
export function packRows(buttons) {
|
|
28
|
+
const rows = [];
|
|
29
|
+
for (let i = 0; i < buttons.length && rows.length < MAX_ROWS; i += MAX_BUTTONS_PER_ROW) {
|
|
30
|
+
rows.push({ type: 1, components: buttons.slice(i, i + MAX_BUTTONS_PER_ROW) });
|
|
31
|
+
}
|
|
32
|
+
return rows;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../src/channel/components.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iCAAiC;AACjC,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,CAAC;IACV,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;CACD,CAAC;AAcX,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC;AAEnB,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,KAAa,EAAE,KAAa;IACnE,OAAO;QACL,IAAI,EAAE,CAAC;QACP,KAAK;QACL,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,IAAI,GAAG;QAC7C,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC;KAClD,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,QAAQ,CAAC,OAAiC;IACxD,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC,IAAI,mBAAmB,EAAE,CAAC;QACvF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural slices of the discord.js surface the handlers touch. Keeping the
|
|
3
|
+
* handlers typed against these (instead of discord.js classes) keeps the
|
|
4
|
+
* messenger-specific transport at the channel edge and lets unit tests drive
|
|
5
|
+
* every gating / streaming / pairing path with plain fake objects — no gateway
|
|
6
|
+
* connection, no discord.js instances.
|
|
7
|
+
*/
|
|
8
|
+
/** A message we sent and can edit in place (the streamed frame). */
|
|
9
|
+
export interface SentMessageLike {
|
|
10
|
+
edit(content: string): Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** Payload accepted by `send` — text plus optional component rows (buttons) or
|
|
13
|
+
* file attachments (a synthesized voice reply). `content` is optional so a
|
|
14
|
+
* file-only message (audio, no text) is expressible. */
|
|
15
|
+
export interface OutboundPayload {
|
|
16
|
+
readonly content?: string;
|
|
17
|
+
readonly components?: ReadonlyArray<unknown>;
|
|
18
|
+
/** File attachments (e.g. a discord.js `AttachmentBuilder`). */
|
|
19
|
+
readonly files?: ReadonlyArray<unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** A channel (DM or guild text channel) we can post into. */
|
|
22
|
+
export interface SendableChannelLike {
|
|
23
|
+
send(payload: string | OutboundPayload): Promise<SentMessageLike>;
|
|
24
|
+
/** Discord's typing indicator lasts ~10s per call; refresh to keep it alive. */
|
|
25
|
+
sendTyping?(): Promise<unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface ChannelLogger {
|
|
28
|
+
info?(msg: string, meta?: Record<string, unknown>): void;
|
|
29
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=discord-like.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-like.d.ts","sourceRoot":"","sources":["../../src/channel/discord-like.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzC;AAED;;yDAEyD;AACzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC7C,gEAAgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CACzC;AAED,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAClE,gFAAgF;IAChF,UAAU,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural slices of the discord.js surface the handlers touch. Keeping the
|
|
3
|
+
* handlers typed against these (instead of discord.js classes) keeps the
|
|
4
|
+
* messenger-specific transport at the channel edge and lets unit tests drive
|
|
5
|
+
* every gating / streaming / pairing path with plain fake objects — no gateway
|
|
6
|
+
* connection, no discord.js instances.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=discord-like.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-like.js","sourceRoot":"","sources":["../../src/channel/discord-like.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ClientSession as Session } from '@moxxy/sdk';
|
|
2
|
+
import type { DiscordApprovalResolver } from '../approval.js';
|
|
3
|
+
import type { DiscordPermissionResolver } from '../permission.js';
|
|
4
|
+
import type { AllowListStore } from './allow-list-store.js';
|
|
5
|
+
import type { ChannelLogger } from './discord-like.js';
|
|
6
|
+
import type { AwaitingApprovalText } from './message-handler.js';
|
|
7
|
+
import type { PairingHandler } from './pairing-handler.js';
|
|
8
|
+
/**
|
|
9
|
+
* Structural slice of a discord.js Interaction the handler needs — buttons
|
|
10
|
+
* (permission / approval prompts) and chat-input (slash) commands. Kept
|
|
11
|
+
* structural so tests drive it with plain objects.
|
|
12
|
+
*/
|
|
13
|
+
export interface InteractionLike {
|
|
14
|
+
isButton(): boolean;
|
|
15
|
+
isChatInputCommand(): boolean;
|
|
16
|
+
/** Button custom id (`perm:<callId>:<choice>` / `appr:<id>:<optionId>`). */
|
|
17
|
+
readonly customId?: string;
|
|
18
|
+
/** Slash-command name. */
|
|
19
|
+
readonly commandName?: string;
|
|
20
|
+
readonly user: {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
};
|
|
23
|
+
readonly channelId?: string | null;
|
|
24
|
+
readonly guildId?: string | null;
|
|
25
|
+
reply(payload: {
|
|
26
|
+
content: string;
|
|
27
|
+
ephemeral?: boolean;
|
|
28
|
+
}): Promise<unknown>;
|
|
29
|
+
/** Buttons only: edit the prompt message (used to clear the button row).
|
|
30
|
+
* NB: this ACKS the interaction — any later message must use followUp. */
|
|
31
|
+
update?(payload: {
|
|
32
|
+
components: ReadonlyArray<unknown>;
|
|
33
|
+
}): Promise<unknown>;
|
|
34
|
+
/** Post-ack follow-up message (valid only after update/reply succeeded). */
|
|
35
|
+
followUp?(payload: {
|
|
36
|
+
content: string;
|
|
37
|
+
ephemeral?: boolean;
|
|
38
|
+
}): Promise<unknown>;
|
|
39
|
+
}
|
|
40
|
+
export interface InteractionState {
|
|
41
|
+
readonly session: Session | null;
|
|
42
|
+
readonly turnController: AbortController | null;
|
|
43
|
+
}
|
|
44
|
+
export interface InteractionDeps {
|
|
45
|
+
readonly pairing: PairingHandler;
|
|
46
|
+
readonly allowList: AllowListStore;
|
|
47
|
+
readonly permissionResolver: DiscordPermissionResolver;
|
|
48
|
+
readonly approvalResolver: DiscordApprovalResolver;
|
|
49
|
+
readonly logger?: ChannelLogger;
|
|
50
|
+
}
|
|
51
|
+
export interface InteractionCallbacks {
|
|
52
|
+
readonly setAwaitingApprovalText: (state: AwaitingApprovalText | null) => void;
|
|
53
|
+
readonly toggleYolo: () => boolean;
|
|
54
|
+
/** Handle `/voice [on|off|status]` — persist + apply, return the reply text. */
|
|
55
|
+
readonly voice: (arg: string) => Promise<string>;
|
|
56
|
+
readonly performSessionAction: (action: 'new' | 'clear' | 'exit', notice: string | undefined) => Promise<string>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Interaction router (button clicks + slash commands). The authorization gate
|
|
60
|
+
* runs FIRST and for EVERY path (AGENTS.md A46 — Telegram's callback handlers
|
|
61
|
+
* once skipped it): button clicks resolve permission prompts and approvals,
|
|
62
|
+
* slash commands reach the session, and a prompt message can be seen (and its
|
|
63
|
+
* buttons clicked) by any member of a guild channel — so an unpaired user's
|
|
64
|
+
* interactions must be refused exactly like their messages.
|
|
65
|
+
*/
|
|
66
|
+
export declare function handleInteraction(interaction: InteractionLike, state: InteractionState, deps: InteractionDeps, cb: InteractionCallbacks): Promise<void>;
|
|
67
|
+
//# sourceMappingURL=interaction-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-handler.d.ts","sourceRoot":"","sources":["../../src/channel/interaction-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,IAAI,OAAO,CAAC;IACpB,kBAAkB,IAAI,OAAO,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,KAAK,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E;+EAC2E;IAC3E,MAAM,CAAC,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E,4EAA4E;IAC5E,QAAQ,CAAC,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,eAAe,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;IACvD,QAAQ,CAAC,gBAAgB,EAAE,uBAAuB,CAAC;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,uBAAuB,EAAE,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/E,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;IACnC,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,oBAAoB,EAAE,CAC7B,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,EAChC,MAAM,EAAE,MAAM,GAAG,SAAS,KACvB,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,eAAe,EAC5B,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,oBAAoB,GACvB,OAAO,CAAC,IAAI,CAAC,CAaf"}
|