@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/dist/pairing.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { randomCode } from '@moxxy/plugin-vault';
|
|
2
|
+
import { clearHostCodePairing, createHostCodeState, greetPeer, isPeerAuthorized, openHostCodeWindow, submitPeerCode, } from '@moxxy/channel-kit';
|
|
3
|
+
const MSG_FOREIGN_USER = 'This bot is paired with a different Discord account. Access denied.';
|
|
4
|
+
const MSG_NOT_ARMED = 'No pairing window is open. Run `moxxy discord pair` in the moxxy terminal first, then DM me again.';
|
|
5
|
+
const MSG_NOT_PENDING = 'No pairing code is outstanding. DM the bot first — it replies with a code.';
|
|
6
|
+
const MSG_MISMATCH = "That code didn't match. Check the code in the bot's most recent DM reply and try again.";
|
|
7
|
+
const MSG_EXPIRED = 'The pairing code expired. DM the bot again for a fresh one.';
|
|
8
|
+
export function createDiscordPairingState(opts = {}) {
|
|
9
|
+
return {
|
|
10
|
+
kit: createHostCodeState({ authorizedPeer: opts.authorizedUserId ?? null }),
|
|
11
|
+
armed: false,
|
|
12
|
+
pendingUserId: null,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function pairingPhase(state) {
|
|
16
|
+
if (state.kit.phase === 'paired')
|
|
17
|
+
return 'paired';
|
|
18
|
+
if (state.kit.phase === 'awaiting-host-code')
|
|
19
|
+
return 'awaiting-host-code';
|
|
20
|
+
if (state.kit.phase === 'expired')
|
|
21
|
+
return 'expired';
|
|
22
|
+
return state.armed ? 'armed' : 'idle';
|
|
23
|
+
}
|
|
24
|
+
/** Arm the pairing window: DM-ing users will now be issued one-time codes. */
|
|
25
|
+
export function armPairing(state) {
|
|
26
|
+
return { ...state, armed: true };
|
|
27
|
+
}
|
|
28
|
+
export function isUserAuthorized(state, userId) {
|
|
29
|
+
return isPeerAuthorized(state.kit, userId);
|
|
30
|
+
}
|
|
31
|
+
export function clearDiscordPairing(state) {
|
|
32
|
+
return {
|
|
33
|
+
kit: clearHostCodePairing(state.kit),
|
|
34
|
+
armed: state.armed,
|
|
35
|
+
pendingUserId: null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* An unauthorized user DMed the bot while a window may be armed. Mints a
|
|
40
|
+
* one-time code bound to that user (re-arming replaces any earlier pending
|
|
41
|
+
* code — see the module doc for why that fails safe) or rejects with the
|
|
42
|
+
* appropriate wording.
|
|
43
|
+
*/
|
|
44
|
+
export function mintCodeForPeer(state, userId, code = randomCode(6)) {
|
|
45
|
+
const greeted = greetPeer(state.kit, userId);
|
|
46
|
+
if (greeted.action.kind === 'still-paired') {
|
|
47
|
+
return { state, action: { kind: 'still-paired', userId } };
|
|
48
|
+
}
|
|
49
|
+
if (greeted.action.kind === 'rejected-foreign-peer') {
|
|
50
|
+
return { state, action: { kind: 'reject', message: MSG_FOREIGN_USER } };
|
|
51
|
+
}
|
|
52
|
+
if (!state.armed) {
|
|
53
|
+
return { state, action: { kind: 'reject', message: MSG_NOT_ARMED } };
|
|
54
|
+
}
|
|
55
|
+
const opened = openHostCodeWindow(state.kit, { code });
|
|
56
|
+
return {
|
|
57
|
+
state: { kit: opened.state, armed: true, pendingUserId: userId },
|
|
58
|
+
action: { kind: 'code-minted', userId, code: opened.code },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Map a kit action to the Discord-worded decision, reusing `state` when the
|
|
62
|
+
* machine didn't transition. */
|
|
63
|
+
function mapConfirmAction(state, kitState, action) {
|
|
64
|
+
switch (action.kind) {
|
|
65
|
+
case 'paired':
|
|
66
|
+
return {
|
|
67
|
+
state: { kit: kitState, armed: false, pendingUserId: null },
|
|
68
|
+
action: { kind: 'paired', userId: action.peer },
|
|
69
|
+
};
|
|
70
|
+
case 'still-paired':
|
|
71
|
+
return { state, action: { kind: 'still-paired', userId: action.peer } };
|
|
72
|
+
case 'rejected-foreign-peer':
|
|
73
|
+
return { state, action: { kind: 'reject', message: MSG_FOREIGN_USER } };
|
|
74
|
+
case 'window-open-hint':
|
|
75
|
+
case 'no-window':
|
|
76
|
+
case 'not-pending':
|
|
77
|
+
return { state, action: { kind: 'not-pending', message: MSG_NOT_PENDING } };
|
|
78
|
+
case 'expired':
|
|
79
|
+
return {
|
|
80
|
+
state: { kit: kitState, armed: state.armed, pendingUserId: null },
|
|
81
|
+
action: { kind: 'expired', message: MSG_EXPIRED },
|
|
82
|
+
};
|
|
83
|
+
case 'mismatch':
|
|
84
|
+
return { state, action: { kind: 'mismatch', message: MSG_MISMATCH } };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The operator pasted a code into the terminal wizard. Compared against the
|
|
89
|
+
* code minted for the CURRENT pending user; on match that user is authorized.
|
|
90
|
+
*/
|
|
91
|
+
export function confirmPendingCode(state, rawCode, now = Date.now()) {
|
|
92
|
+
if (state.pendingUserId == null) {
|
|
93
|
+
return { state, action: { kind: 'not-pending', message: MSG_NOT_PENDING } };
|
|
94
|
+
}
|
|
95
|
+
const decision = submitPeerCode(state.kit, state.pendingUserId, rawCode, now);
|
|
96
|
+
return mapConfirmAction(state, decision.state, decision.action);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=pairing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing.js","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PendingToolCall, PermissionContext, PermissionDecision, PermissionResolver } from '@moxxy/sdk';
|
|
2
|
+
export interface PendingPermission {
|
|
3
|
+
readonly callId: string;
|
|
4
|
+
readonly call: PendingToolCall;
|
|
5
|
+
readonly ctx: PermissionContext;
|
|
6
|
+
readonly resolve: (decision: PermissionDecision) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Permission resolver that defers each tool call to an external decider (the
|
|
10
|
+
* Discord bot, which renders a button row and waits for a click). The channel
|
|
11
|
+
* registers an interaction handler that invokes `resolvePending(callId,
|
|
12
|
+
* decision)` when the paired user clicks a button. Mirrors the Telegram
|
|
13
|
+
* resolver's shape (pending map + setDecider + abortAll) — the button
|
|
14
|
+
* click arrives on a different code path than the check, so the shared
|
|
15
|
+
* `createDeferredPermissionResolver` (whose prompt must RETURN the decision)
|
|
16
|
+
* doesn't fit; the abortAll contract is preserved here instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare class DiscordPermissionResolver implements PermissionResolver {
|
|
19
|
+
readonly name = "discord";
|
|
20
|
+
private readonly pending;
|
|
21
|
+
private readonly sessionAllows;
|
|
22
|
+
private deciderFn;
|
|
23
|
+
setDecider(fn: (call: PendingToolCall, ctx: PermissionContext) => Promise<void>): void;
|
|
24
|
+
check(call: PendingToolCall, ctx: PermissionContext): Promise<PermissionDecision>;
|
|
25
|
+
/** Called by the interaction handler when a permission button is clicked. */
|
|
26
|
+
resolvePending(callId: string, decision: PermissionDecision): boolean;
|
|
27
|
+
/** Deny every in-flight prompt — MUST run on channel stop so callers don't hang. */
|
|
28
|
+
abortAll(reason?: string): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;;;;GASG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IAClE,QAAQ,CAAC,IAAI,aAAa;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAChE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,SAAS,CACV;IAEP,UAAU,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIhF,KAAK,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAkBvF,6EAA6E;IAC7E,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO;IAQrE,oFAAoF;IACpF,QAAQ,CAAC,MAAM,SAAmB,GAAG,IAAI;CAM1C"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission resolver that defers each tool call to an external decider (the
|
|
3
|
+
* Discord bot, which renders a button row and waits for a click). The channel
|
|
4
|
+
* registers an interaction handler that invokes `resolvePending(callId,
|
|
5
|
+
* decision)` when the paired user clicks a button. Mirrors the Telegram
|
|
6
|
+
* resolver's shape (pending map + setDecider + abortAll) — the button
|
|
7
|
+
* click arrives on a different code path than the check, so the shared
|
|
8
|
+
* `createDeferredPermissionResolver` (whose prompt must RETURN the decision)
|
|
9
|
+
* doesn't fit; the abortAll contract is preserved here instead.
|
|
10
|
+
*/
|
|
11
|
+
export class DiscordPermissionResolver {
|
|
12
|
+
name = 'discord';
|
|
13
|
+
pending = new Map();
|
|
14
|
+
sessionAllows = new Set();
|
|
15
|
+
deciderFn = null;
|
|
16
|
+
setDecider(fn) {
|
|
17
|
+
this.deciderFn = fn;
|
|
18
|
+
}
|
|
19
|
+
async check(call, ctx) {
|
|
20
|
+
if (this.sessionAllows.has(call.name)) {
|
|
21
|
+
return { mode: 'allow_session', reason: 'allow_session previously granted' };
|
|
22
|
+
}
|
|
23
|
+
if (!this.deciderFn) {
|
|
24
|
+
return { mode: 'deny', reason: 'no decider attached (bot not running)' };
|
|
25
|
+
}
|
|
26
|
+
const decision = await new Promise((resolve) => {
|
|
27
|
+
this.pending.set(call.callId, { callId: call.callId, call, ctx, resolve });
|
|
28
|
+
this.deciderFn(call, ctx).catch((err) => {
|
|
29
|
+
this.pending.delete(call.callId);
|
|
30
|
+
resolve({ mode: 'deny', reason: err instanceof Error ? err.message : String(err) });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
if (decision.mode === 'allow_session')
|
|
34
|
+
this.sessionAllows.add(call.name);
|
|
35
|
+
return decision;
|
|
36
|
+
}
|
|
37
|
+
/** Called by the interaction handler when a permission button is clicked. */
|
|
38
|
+
resolvePending(callId, decision) {
|
|
39
|
+
const pending = this.pending.get(callId);
|
|
40
|
+
if (!pending)
|
|
41
|
+
return false;
|
|
42
|
+
this.pending.delete(callId);
|
|
43
|
+
pending.resolve(decision);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/** Deny every in-flight prompt — MUST run on channel stop so callers don't hang. */
|
|
47
|
+
abortAll(reason = 'channel closed') {
|
|
48
|
+
for (const pending of this.pending.values()) {
|
|
49
|
+
pending.resolve({ mode: 'deny', reason });
|
|
50
|
+
}
|
|
51
|
+
this.pending.clear();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=permission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.js","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAcA;;;;;;;;;GASG;AACH,MAAM,OAAO,yBAAyB;IAC3B,IAAI,GAAG,SAAS,CAAC;IACT,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;IAC/C,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3C,SAAS,GACf,IAAI,CAAC;IAEP,UAAU,CAAC,EAAoE;QAC7E,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAqB,EAAE,GAAsB;QACvD,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC;QAC3E,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,EAAE;YACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAC3E,IAAI,CAAC,SAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACvC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACjC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,IAAI,KAAK,eAAe;YAAE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,6EAA6E;IAC7E,cAAc,CAAC,MAAc,EAAE,QAA4B;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,oFAAoF;IACpF,QAAQ,CAAC,MAAM,GAAG,gBAAgB;QAChC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MoxxyEvent } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Discord messages cap at 2000 chars; leave margin for the fence-reopen text a
|
|
4
|
+
* split may prepend to a tail part.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DISCORD_MESSAGE_LIMIT = 1900;
|
|
7
|
+
export declare class DiscordTurnRenderer {
|
|
8
|
+
private chunks;
|
|
9
|
+
private finalAssistant;
|
|
10
|
+
private readonly tools;
|
|
11
|
+
private notices;
|
|
12
|
+
private errorLine;
|
|
13
|
+
private lastFrame;
|
|
14
|
+
/** Returns true when the event changed the frame (schedule an edit). */
|
|
15
|
+
accept(event: MoxxyEvent): boolean;
|
|
16
|
+
snapshot(): string;
|
|
17
|
+
/** The assistant body alone (no activity/error block) — the text a voice
|
|
18
|
+
* reply speaks. Empty for a tool-only turn. */
|
|
19
|
+
finalText(): string;
|
|
20
|
+
reset(): void;
|
|
21
|
+
private renderActivity;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Split Discord markdown into <=`limit`-char parts that each render sanely on
|
|
25
|
+
* their own (mirrors `splitForTelegram`'s shape, adapted from HTML tags to the
|
|
26
|
+
* one markdown construct that breaks when cut: a ``` code fence). Cuts prefer
|
|
27
|
+
* a newline at/under the budget; if the cut lands inside an open fence, the
|
|
28
|
+
* fence is closed at the head part's end and reopened (with its original
|
|
29
|
+
* info-string, e.g. ```diff) at the tail's start, so every part is
|
|
30
|
+
* independently valid. Parts are separate Discord messages, so the newline a
|
|
31
|
+
* cut lands on is consumed (not carried into the tail).
|
|
32
|
+
*/
|
|
33
|
+
export declare function splitForDiscord(text: string, limit?: number): string[];
|
|
34
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,OAAQ,CAAC;AA8B3C,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,SAAS,CAAM;IAEvB,wEAAwE;IACxE,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;IAkDlC,QAAQ,IAAI,MAAM;IAUlB;oDACgD;IAChD,SAAS,IAAI,MAAM;IAInB,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,cAAc;CAcvB;AA2CD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAA8B,GAAG,MAAM,EAAE,CAwB7F"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord messages cap at 2000 chars; leave margin for the fence-reopen text a
|
|
3
|
+
* split may prepend to a tail part.
|
|
4
|
+
*/
|
|
5
|
+
export const DISCORD_MESSAGE_LIMIT = 1_900;
|
|
6
|
+
export class DiscordTurnRenderer {
|
|
7
|
+
chunks = [];
|
|
8
|
+
finalAssistant = null;
|
|
9
|
+
tools = new Map();
|
|
10
|
+
notices = [];
|
|
11
|
+
errorLine = null;
|
|
12
|
+
lastFrame = '';
|
|
13
|
+
/** Returns true when the event changed the frame (schedule an edit). */
|
|
14
|
+
accept(event) {
|
|
15
|
+
switch (event.type) {
|
|
16
|
+
case 'assistant_chunk':
|
|
17
|
+
this.chunks.push(event.delta);
|
|
18
|
+
break;
|
|
19
|
+
case 'assistant_message':
|
|
20
|
+
this.finalAssistant = event.content;
|
|
21
|
+
this.chunks = [];
|
|
22
|
+
break;
|
|
23
|
+
case 'tool_call_requested':
|
|
24
|
+
this.tools.set(String(event.callId), {
|
|
25
|
+
name: event.name,
|
|
26
|
+
inputPreview: previewArgs(event.input),
|
|
27
|
+
status: { kind: 'pending', startedAt: Date.now() },
|
|
28
|
+
});
|
|
29
|
+
break;
|
|
30
|
+
case 'tool_call_denied': {
|
|
31
|
+
const entry = this.tools.get(String(event.callId));
|
|
32
|
+
if (entry)
|
|
33
|
+
entry.status = { kind: 'denied', reason: event.reason };
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'tool_result': {
|
|
37
|
+
const entry = this.tools.get(String(event.callId));
|
|
38
|
+
if (!entry)
|
|
39
|
+
break;
|
|
40
|
+
const started = entry.status.kind === 'pending' ? entry.status.startedAt : Date.now();
|
|
41
|
+
const ms = Date.now() - started;
|
|
42
|
+
entry.status = event.ok
|
|
43
|
+
? { kind: 'ok', ms }
|
|
44
|
+
: {
|
|
45
|
+
kind: 'err',
|
|
46
|
+
ms,
|
|
47
|
+
message: `${event.error?.kind ?? 'error'}: ${event.error?.message ?? ''}`,
|
|
48
|
+
};
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case 'skill_invoked':
|
|
52
|
+
this.notices.push(`💡 skill: **${event.name}**`);
|
|
53
|
+
break;
|
|
54
|
+
case 'error':
|
|
55
|
+
this.errorLine = `❗ **${event.kind}**: ${event.message}`;
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
const frame = this.snapshot();
|
|
61
|
+
const changed = frame !== this.lastFrame;
|
|
62
|
+
this.lastFrame = frame;
|
|
63
|
+
return changed;
|
|
64
|
+
}
|
|
65
|
+
snapshot() {
|
|
66
|
+
const parts = [];
|
|
67
|
+
const activity = this.renderActivity();
|
|
68
|
+
if (activity)
|
|
69
|
+
parts.push(activity);
|
|
70
|
+
const body = this.finalText();
|
|
71
|
+
if (body)
|
|
72
|
+
parts.push(body);
|
|
73
|
+
if (this.errorLine)
|
|
74
|
+
parts.push(this.errorLine);
|
|
75
|
+
return parts.join('\n\n').trim();
|
|
76
|
+
}
|
|
77
|
+
/** The assistant body alone (no activity/error block) — the text a voice
|
|
78
|
+
* reply speaks. Empty for a tool-only turn. */
|
|
79
|
+
finalText() {
|
|
80
|
+
return (this.finalAssistant ?? this.chunks.join('')).trim();
|
|
81
|
+
}
|
|
82
|
+
reset() {
|
|
83
|
+
this.chunks = [];
|
|
84
|
+
this.finalAssistant = null;
|
|
85
|
+
this.tools.clear();
|
|
86
|
+
this.notices = [];
|
|
87
|
+
this.errorLine = null;
|
|
88
|
+
this.lastFrame = '';
|
|
89
|
+
}
|
|
90
|
+
renderActivity() {
|
|
91
|
+
const lines = [];
|
|
92
|
+
const tools = [...this.tools.values()];
|
|
93
|
+
const visible = tools.slice(-10);
|
|
94
|
+
const hidden = tools.length - visible.length;
|
|
95
|
+
if (hidden > 0)
|
|
96
|
+
lines.push(`… ${hidden} earlier tool call${hidden === 1 ? '' : 's'} hidden`);
|
|
97
|
+
for (const t of visible) {
|
|
98
|
+
const args = t.inputPreview ? ` (${t.inputPreview})` : '';
|
|
99
|
+
lines.push(`- \`${t.name}\`${args} — ${statusBadge(t.status)}`);
|
|
100
|
+
}
|
|
101
|
+
for (const n of this.notices.slice(-5))
|
|
102
|
+
lines.push(n);
|
|
103
|
+
if (lines.length === 0)
|
|
104
|
+
return null;
|
|
105
|
+
return lines.map((l) => `> ${l}`).join('\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function statusBadge(status) {
|
|
109
|
+
switch (status.kind) {
|
|
110
|
+
case 'pending':
|
|
111
|
+
return 'running…';
|
|
112
|
+
case 'ok':
|
|
113
|
+
return `done ${fmtElapsed(status.ms)}`;
|
|
114
|
+
case 'err':
|
|
115
|
+
return `error: ${status.message.slice(0, 80)}`;
|
|
116
|
+
case 'denied':
|
|
117
|
+
return `denied: ${status.reason.slice(0, 80)}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function fmtElapsed(ms) {
|
|
121
|
+
if (ms < 1000)
|
|
122
|
+
return `${ms}ms`;
|
|
123
|
+
if (ms < 60_000)
|
|
124
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
125
|
+
return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`;
|
|
126
|
+
}
|
|
127
|
+
function truncJson(value, max = 60) {
|
|
128
|
+
const s = typeof value === 'string' ? value : (JSON.stringify(value) ?? '');
|
|
129
|
+
if (s.length <= max)
|
|
130
|
+
return s;
|
|
131
|
+
return s.slice(0, max - 1) + '…';
|
|
132
|
+
}
|
|
133
|
+
function previewArgs(input) {
|
|
134
|
+
if (input == null || (typeof input === 'object' && Object.keys(input).length === 0)) {
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
if (typeof input === 'string')
|
|
138
|
+
return truncJson(input);
|
|
139
|
+
try {
|
|
140
|
+
const obj = input;
|
|
141
|
+
const keys = Object.keys(obj).slice(0, 3);
|
|
142
|
+
const pairs = keys.map((k) => `${k}=${truncJson(obj[k], 20)}`);
|
|
143
|
+
const tail = Object.keys(obj).length > keys.length ? ', …' : '';
|
|
144
|
+
return pairs.join(', ') + tail;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return truncJson(input);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Split Discord markdown into <=`limit`-char parts that each render sanely on
|
|
152
|
+
* their own (mirrors `splitForTelegram`'s shape, adapted from HTML tags to the
|
|
153
|
+
* one markdown construct that breaks when cut: a ``` code fence). Cuts prefer
|
|
154
|
+
* a newline at/under the budget; if the cut lands inside an open fence, the
|
|
155
|
+
* fence is closed at the head part's end and reopened (with its original
|
|
156
|
+
* info-string, e.g. ```diff) at the tail's start, so every part is
|
|
157
|
+
* independently valid. Parts are separate Discord messages, so the newline a
|
|
158
|
+
* cut lands on is consumed (not carried into the tail).
|
|
159
|
+
*/
|
|
160
|
+
export function splitForDiscord(text, limit = DISCORD_MESSAGE_LIMIT) {
|
|
161
|
+
if (text.length <= limit)
|
|
162
|
+
return text ? [text] : [];
|
|
163
|
+
const out = [];
|
|
164
|
+
/** The fence opener carried over from the previous part (reopened at its head). */
|
|
165
|
+
let carryFence = null;
|
|
166
|
+
let remaining = text;
|
|
167
|
+
while (remaining.length > limit) {
|
|
168
|
+
const prefix = carryFence ? carryFence + '\n' : '';
|
|
169
|
+
// The reopened fence + a possible closing fence eat into the budget.
|
|
170
|
+
const budget = Math.max(1, limit - prefix.length - '\n```'.length);
|
|
171
|
+
let idx = Math.min(budget, remaining.length);
|
|
172
|
+
const nl = remaining.lastIndexOf('\n', idx);
|
|
173
|
+
if (nl > 0)
|
|
174
|
+
idx = nl;
|
|
175
|
+
const openFence = openFenceAt(remaining, idx, carryFence);
|
|
176
|
+
const head = prefix + remaining.slice(0, idx) + (openFence ? '\n```' : '');
|
|
177
|
+
out.push(head);
|
|
178
|
+
carryFence = openFence;
|
|
179
|
+
remaining = remaining.slice(idx);
|
|
180
|
+
// Drop the leading newline the cut left behind (it was consumed by the cut
|
|
181
|
+
// point, and the reopened fence supplies its own).
|
|
182
|
+
if (remaining.startsWith('\n'))
|
|
183
|
+
remaining = remaining.slice(1);
|
|
184
|
+
}
|
|
185
|
+
if (remaining)
|
|
186
|
+
out.push((carryFence ? carryFence + '\n' : '') + remaining);
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Is a ``` fence open at `idx`? Returns the fence's opening line (e.g.
|
|
191
|
+
* "```diff") when open, else null. `seed` is the fence carried into this part
|
|
192
|
+
* from the previous split (the text below it starts inside that fence).
|
|
193
|
+
*/
|
|
194
|
+
function openFenceAt(text, idx, seed) {
|
|
195
|
+
let open = seed;
|
|
196
|
+
const scan = text.slice(0, idx);
|
|
197
|
+
const fenceRe = /(?:^|\n)(```[^\n]*)/g;
|
|
198
|
+
let m;
|
|
199
|
+
while ((m = fenceRe.exec(scan)) !== null) {
|
|
200
|
+
open = open === null ? m[1] : null;
|
|
201
|
+
}
|
|
202
|
+
return open;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound-payload validation (AGENTS.md invariant: zod-validate every inbound
|
|
3
|
+
* frame/body before it touches the session). discord.js hands us rich class
|
|
4
|
+
* instances; we extract ONLY the primitive fields the channel consumes into a
|
|
5
|
+
* plain record and validate + size-cap that, so a malformed/hostile payload is
|
|
6
|
+
* dropped at the boundary instead of flowing into handlers.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from '@moxxy/sdk';
|
|
9
|
+
/**
|
|
10
|
+
* Hard cap on inbound message text we accept. Discord caps user messages at
|
|
11
|
+
* 4000 chars (Nitro); anything past this is not a legitimate prompt, so we
|
|
12
|
+
* drop it rather than truncate (a silently truncated prompt is worse than a
|
|
13
|
+
* clear rejection).
|
|
14
|
+
*/
|
|
15
|
+
export declare const MAX_CONTENT_CHARS = 8000;
|
|
16
|
+
/** Hard cap on an inbound audio attachment we will buffer for transcription. */
|
|
17
|
+
export declare const MAX_AUDIO_BYTES: number;
|
|
18
|
+
declare const attachmentSchema: z.ZodObject<{
|
|
19
|
+
id: z.ZodString;
|
|
20
|
+
url: z.ZodString;
|
|
21
|
+
contentType: z.ZodNullable<z.ZodString>;
|
|
22
|
+
size: z.ZodNumber;
|
|
23
|
+
name: z.ZodNullable<z.ZodString>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
id: string;
|
|
26
|
+
url: string;
|
|
27
|
+
contentType: string | null;
|
|
28
|
+
size: number;
|
|
29
|
+
name: string | null;
|
|
30
|
+
}, {
|
|
31
|
+
id: string;
|
|
32
|
+
url: string;
|
|
33
|
+
contentType: string | null;
|
|
34
|
+
size: number;
|
|
35
|
+
name: string | null;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const inboundMessageSchema: z.ZodObject<{
|
|
38
|
+
id: z.ZodString;
|
|
39
|
+
/** Raw message text (may be empty for attachment-only messages). */
|
|
40
|
+
content: z.ZodString;
|
|
41
|
+
channelId: z.ZodString;
|
|
42
|
+
/** Null for DMs. */
|
|
43
|
+
guildId: z.ZodNullable<z.ZodString>;
|
|
44
|
+
authorId: z.ZodString;
|
|
45
|
+
authorIsBot: z.ZodBoolean;
|
|
46
|
+
attachments: z.ZodArray<z.ZodObject<{
|
|
47
|
+
id: z.ZodString;
|
|
48
|
+
url: z.ZodString;
|
|
49
|
+
contentType: z.ZodNullable<z.ZodString>;
|
|
50
|
+
size: z.ZodNumber;
|
|
51
|
+
name: z.ZodNullable<z.ZodString>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
id: string;
|
|
54
|
+
url: string;
|
|
55
|
+
contentType: string | null;
|
|
56
|
+
size: number;
|
|
57
|
+
name: string | null;
|
|
58
|
+
}, {
|
|
59
|
+
id: string;
|
|
60
|
+
url: string;
|
|
61
|
+
contentType: string | null;
|
|
62
|
+
size: number;
|
|
63
|
+
name: string | null;
|
|
64
|
+
}>, "many">;
|
|
65
|
+
}, "strip", z.ZodTypeAny, {
|
|
66
|
+
id: string;
|
|
67
|
+
content: string;
|
|
68
|
+
channelId: string;
|
|
69
|
+
guildId: string | null;
|
|
70
|
+
authorId: string;
|
|
71
|
+
authorIsBot: boolean;
|
|
72
|
+
attachments: {
|
|
73
|
+
id: string;
|
|
74
|
+
url: string;
|
|
75
|
+
contentType: string | null;
|
|
76
|
+
size: number;
|
|
77
|
+
name: string | null;
|
|
78
|
+
}[];
|
|
79
|
+
}, {
|
|
80
|
+
id: string;
|
|
81
|
+
content: string;
|
|
82
|
+
channelId: string;
|
|
83
|
+
guildId: string | null;
|
|
84
|
+
authorId: string;
|
|
85
|
+
authorIsBot: boolean;
|
|
86
|
+
attachments: {
|
|
87
|
+
id: string;
|
|
88
|
+
url: string;
|
|
89
|
+
contentType: string | null;
|
|
90
|
+
size: number;
|
|
91
|
+
name: string | null;
|
|
92
|
+
}[];
|
|
93
|
+
}>;
|
|
94
|
+
export type InboundMessage = z.infer<typeof inboundMessageSchema>;
|
|
95
|
+
export type InboundAttachment = z.infer<typeof attachmentSchema>;
|
|
96
|
+
/** The structural slice of a discord.js Message we extract fields from. */
|
|
97
|
+
export interface RawMessageLike {
|
|
98
|
+
readonly id?: unknown;
|
|
99
|
+
readonly content?: unknown;
|
|
100
|
+
readonly channelId?: unknown;
|
|
101
|
+
readonly guildId?: unknown;
|
|
102
|
+
readonly author?: {
|
|
103
|
+
readonly id?: unknown;
|
|
104
|
+
readonly bot?: unknown;
|
|
105
|
+
} | null;
|
|
106
|
+
readonly attachments?: {
|
|
107
|
+
values?(): IterableIterator<{
|
|
108
|
+
readonly id?: unknown;
|
|
109
|
+
readonly url?: unknown;
|
|
110
|
+
readonly contentType?: unknown;
|
|
111
|
+
readonly size?: unknown;
|
|
112
|
+
readonly name?: unknown;
|
|
113
|
+
}>;
|
|
114
|
+
} | null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Extract + validate the fields we consume from a discord.js Message. Returns
|
|
118
|
+
* null when the payload doesn't validate (wrong shapes, oversized content,
|
|
119
|
+
* missing ids) — the caller drops it with a warning.
|
|
120
|
+
*/
|
|
121
|
+
export declare function extractInboundMessage(msg: RawMessageLike): InboundMessage | null;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAG/B;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AAEvC,gFAAgF;AAChF,eAAO,MAAM,eAAe,QAAmB,CAAC;AAEhD,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;EAMpB,CAAC;AAEH,eAAO,MAAM,oBAAoB;;IAE/B,oEAAoE;;;IAGpE,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKpB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEjE,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,MAAM,CAAC,IAAI,gBAAgB,CAAC;YAC1B,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;YACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;YACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;SACzB,CAAC,CAAC;KACJ,GAAG,IAAI,CAAC;CACV;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,cAAc,GAAG,IAAI,CA0BhF"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound-payload validation (AGENTS.md invariant: zod-validate every inbound
|
|
3
|
+
* frame/body before it touches the session). discord.js hands us rich class
|
|
4
|
+
* instances; we extract ONLY the primitive fields the channel consumes into a
|
|
5
|
+
* plain record and validate + size-cap that, so a malformed/hostile payload is
|
|
6
|
+
* dropped at the boundary instead of flowing into handlers.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from '@moxxy/sdk';
|
|
9
|
+
import { snowflakeSchema } from './keys.js';
|
|
10
|
+
/**
|
|
11
|
+
* Hard cap on inbound message text we accept. Discord caps user messages at
|
|
12
|
+
* 4000 chars (Nitro); anything past this is not a legitimate prompt, so we
|
|
13
|
+
* drop it rather than truncate (a silently truncated prompt is worse than a
|
|
14
|
+
* clear rejection).
|
|
15
|
+
*/
|
|
16
|
+
export const MAX_CONTENT_CHARS = 8_000;
|
|
17
|
+
/** Hard cap on an inbound audio attachment we will buffer for transcription. */
|
|
18
|
+
export const MAX_AUDIO_BYTES = 20 * 1024 * 1024;
|
|
19
|
+
const attachmentSchema = z.object({
|
|
20
|
+
id: snowflakeSchema,
|
|
21
|
+
url: z.string().url().max(2_048),
|
|
22
|
+
contentType: z.string().max(256).nullable(),
|
|
23
|
+
size: z.number().int().nonnegative(),
|
|
24
|
+
name: z.string().max(1_024).nullable(),
|
|
25
|
+
});
|
|
26
|
+
export const inboundMessageSchema = z.object({
|
|
27
|
+
id: snowflakeSchema,
|
|
28
|
+
/** Raw message text (may be empty for attachment-only messages). */
|
|
29
|
+
content: z.string().max(MAX_CONTENT_CHARS),
|
|
30
|
+
channelId: snowflakeSchema,
|
|
31
|
+
/** Null for DMs. */
|
|
32
|
+
guildId: snowflakeSchema.nullable(),
|
|
33
|
+
authorId: snowflakeSchema,
|
|
34
|
+
authorIsBot: z.boolean(),
|
|
35
|
+
attachments: z.array(attachmentSchema).max(16),
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Extract + validate the fields we consume from a discord.js Message. Returns
|
|
39
|
+
* null when the payload doesn't validate (wrong shapes, oversized content,
|
|
40
|
+
* missing ids) — the caller drops it with a warning.
|
|
41
|
+
*/
|
|
42
|
+
export function extractInboundMessage(msg) {
|
|
43
|
+
const attachments = [];
|
|
44
|
+
try {
|
|
45
|
+
for (const a of msg.attachments?.values?.() ?? []) {
|
|
46
|
+
attachments.push({
|
|
47
|
+
id: a?.id,
|
|
48
|
+
url: a?.url,
|
|
49
|
+
contentType: a?.contentType ?? null,
|
|
50
|
+
size: a?.size,
|
|
51
|
+
name: a?.name ?? null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const candidate = {
|
|
59
|
+
id: msg.id,
|
|
60
|
+
content: msg.content,
|
|
61
|
+
channelId: msg.channelId,
|
|
62
|
+
guildId: msg.guildId ?? null,
|
|
63
|
+
authorId: msg.author?.id,
|
|
64
|
+
authorIsBot: msg.author?.bot === true,
|
|
65
|
+
attachments,
|
|
66
|
+
};
|
|
67
|
+
const parsed = inboundMessageSchema.safeParse(candidate);
|
|
68
|
+
return parsed.success ? parsed.data : null;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,EAAE,EAAE,eAAe;IACnB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC;IAChC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACpC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,EAAE,EAAE,eAAe;IACnB,oEAAoE;IACpE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC1C,SAAS,EAAE,eAAe;IAC1B,oBAAoB;IACpB,OAAO,EAAE,eAAe,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,eAAe;IACzB,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE;IACxB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;CAC/C,CAAC,CAAC;AAuBH;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAmB;IACvD,MAAM,WAAW,GAAc,EAAE,CAAC;IAClC,IAAI,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;YAClD,WAAW,CAAC,IAAI,CAAC;gBACf,EAAE,EAAE,CAAC,EAAE,EAAE;gBACT,GAAG,EAAE,CAAC,EAAE,GAAG;gBACX,WAAW,EAAE,CAAC,EAAE,WAAW,IAAI,IAAI;gBACnC,IAAI,EAAE,CAAC,EAAE,IAAI;gBACb,IAAI,EAAE,CAAC,EAAE,IAAI,IAAI,IAAI;aACtB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,SAAS,GAAG;QAChB,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI;QAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE;QACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI;QACrC,WAAW;KACZ,CAAC;IACF,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC"}
|