@addozhang/dsh-discord 0.1.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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/cordis.patch.yml +14 -0
  4. package/lib/client/DiscordSettingsCard.d.ts +6 -0
  5. package/lib/client/PluginCard.d.ts +43 -0
  6. package/lib/client/card-controller.d.ts +22 -0
  7. package/lib/client/card-form.d.ts +111 -0
  8. package/lib/client/fields.d.ts +50 -0
  9. package/lib/client/index.d.ts +12 -0
  10. package/lib/client/nav-icon.d.ts +21 -0
  11. package/lib/client/settings-model.d.ts +49 -0
  12. package/lib/client/slot-contract.d.ts +57 -0
  13. package/lib/client/snapshot-store.d.ts +9 -0
  14. package/lib/client.js +1622 -0
  15. package/lib/compose.d.ts +158 -0
  16. package/lib/compose.js +227 -0
  17. package/lib/credential.d.ts +20 -0
  18. package/lib/credential.js +24 -0
  19. package/lib/discord/commands.d.ts +45 -0
  20. package/lib/discord/commands.js +87 -0
  21. package/lib/discord/components.d.ts +29 -0
  22. package/lib/discord/components.js +57 -0
  23. package/lib/discord/delivery.d.ts +21 -0
  24. package/lib/discord/delivery.js +31 -0
  25. package/lib/discord/interaction-lifecycle.d.ts +42 -0
  26. package/lib/discord/interaction-lifecycle.js +78 -0
  27. package/lib/discord/rest.d.ts +74 -0
  28. package/lib/discord/rest.js +149 -0
  29. package/lib/discord/selector.d.ts +39 -0
  30. package/lib/discord/selector.js +70 -0
  31. package/lib/discord/thread-port.d.ts +37 -0
  32. package/lib/discord/thread-port.js +58 -0
  33. package/lib/dsh/api-proxy-face.d.ts +253 -0
  34. package/lib/dsh/api-proxy-face.js +370 -0
  35. package/lib/features/adapter-status.d.ts +111 -0
  36. package/lib/features/adapter-status.js +103 -0
  37. package/lib/features/approval-expiry.d.ts +27 -0
  38. package/lib/features/approval-expiry.js +36 -0
  39. package/lib/features/approval-routing.d.ts +50 -0
  40. package/lib/features/approval-routing.js +57 -0
  41. package/lib/features/approval-store.d.ts +86 -0
  42. package/lib/features/approval-store.js +119 -0
  43. package/lib/features/approval-view.d.ts +47 -0
  44. package/lib/features/approval-view.js +48 -0
  45. package/lib/features/ask-wiring.d.ts +74 -0
  46. package/lib/features/ask-wiring.js +151 -0
  47. package/lib/features/host-status.d.ts +41 -0
  48. package/lib/features/host-status.js +24 -0
  49. package/lib/features/image-collection.d.ts +56 -0
  50. package/lib/features/image-collection.js +79 -0
  51. package/lib/features/image-download.d.ts +48 -0
  52. package/lib/features/image-download.js +77 -0
  53. package/lib/features/image-submission.d.ts +67 -0
  54. package/lib/features/image-submission.js +57 -0
  55. package/lib/features/interaction-router.d.ts +84 -0
  56. package/lib/features/interaction-router.js +655 -0
  57. package/lib/features/model-control.d.ts +79 -0
  58. package/lib/features/model-control.js +51 -0
  59. package/lib/features/preset-control.d.ts +84 -0
  60. package/lib/features/preset-control.js +43 -0
  61. package/lib/features/project-bind.d.ts +69 -0
  62. package/lib/features/project-bind.js +57 -0
  63. package/lib/features/project-info.d.ts +25 -0
  64. package/lib/features/project-info.js +22 -0
  65. package/lib/features/project-list.d.ts +79 -0
  66. package/lib/features/project-list.js +93 -0
  67. package/lib/features/prompt-submission.d.ts +53 -0
  68. package/lib/features/prompt-submission.js +47 -0
  69. package/lib/features/question-expiry.d.ts +53 -0
  70. package/lib/features/question-expiry.js +56 -0
  71. package/lib/features/question-routing.d.ts +108 -0
  72. package/lib/features/question-routing.js +162 -0
  73. package/lib/features/question-store.d.ts +124 -0
  74. package/lib/features/question-store.js +186 -0
  75. package/lib/features/question-view.d.ts +58 -0
  76. package/lib/features/question-view.js +81 -0
  77. package/lib/features/queue-control.d.ts +63 -0
  78. package/lib/features/queue-control.js +31 -0
  79. package/lib/features/reconcile-bindings.d.ts +75 -0
  80. package/lib/features/reconcile-bindings.js +54 -0
  81. package/lib/features/reconcile-deliveries.d.ts +29 -0
  82. package/lib/features/reconcile-deliveries.js +28 -0
  83. package/lib/features/reconcile-events.d.ts +49 -0
  84. package/lib/features/reconcile-events.js +50 -0
  85. package/lib/features/reconcile-intents.d.ts +32 -0
  86. package/lib/features/reconcile-intents.js +32 -0
  87. package/lib/features/reconcile-interactions.d.ts +59 -0
  88. package/lib/features/reconcile-interactions.js +54 -0
  89. package/lib/features/session-adopt.d.ts +89 -0
  90. package/lib/features/session-adopt.js +77 -0
  91. package/lib/features/session-creation.d.ts +49 -0
  92. package/lib/features/session-creation.js +43 -0
  93. package/lib/features/session-mainline.d.ts +75 -0
  94. package/lib/features/session-mainline.js +96 -0
  95. package/lib/features/session-resume.d.ts +44 -0
  96. package/lib/features/session-resume.js +40 -0
  97. package/lib/features/skill-run.d.ts +48 -0
  98. package/lib/features/skill-run.js +25 -0
  99. package/lib/features/steer-control.d.ts +37 -0
  100. package/lib/features/steer-control.js +28 -0
  101. package/lib/features/stop-control.d.ts +38 -0
  102. package/lib/features/stop-control.js +31 -0
  103. package/lib/features/task-admission.d.ts +29 -0
  104. package/lib/features/task-admission.js +21 -0
  105. package/lib/features/thread-continuation.d.ts +36 -0
  106. package/lib/features/thread-continuation.js +18 -0
  107. package/lib/features/thread-creation.d.ts +81 -0
  108. package/lib/features/thread-creation.js +81 -0
  109. package/lib/features/thread-routing.d.ts +49 -0
  110. package/lib/features/thread-routing.js +34 -0
  111. package/lib/features/turn-ownership.d.ts +39 -0
  112. package/lib/features/turn-ownership.js +40 -0
  113. package/lib/features/unbound-mention.d.ts +49 -0
  114. package/lib/features/unbound-mention.js +31 -0
  115. package/lib/features/workspace-channel.d.ts +41 -0
  116. package/lib/features/workspace-channel.js +47 -0
  117. package/lib/gateway/gateway.d.ts +50 -0
  118. package/lib/gateway/gateway.js +229 -0
  119. package/lib/gateway/inbound.d.ts +85 -0
  120. package/lib/gateway/inbound.js +186 -0
  121. package/lib/gateway/ingress.d.ts +15 -0
  122. package/lib/gateway/ingress.js +22 -0
  123. package/lib/i18n.d.ts +92 -0
  124. package/lib/i18n.js +180 -0
  125. package/lib/index.d.ts +17 -0
  126. package/lib/index.js +869 -0
  127. package/lib/lifecycle.d.ts +44 -0
  128. package/lib/lifecycle.js +84 -0
  129. package/lib/policy/authorization.d.ts +41 -0
  130. package/lib/policy/authorization.js +64 -0
  131. package/lib/policy/disclosure.d.ts +93 -0
  132. package/lib/policy/disclosure.js +153 -0
  133. package/lib/policy/guard.d.ts +31 -0
  134. package/lib/policy/guard.js +46 -0
  135. package/lib/policy/suppress.d.ts +13 -0
  136. package/lib/policy/suppress.js +20 -0
  137. package/lib/settings-namespace.d.ts +7 -0
  138. package/lib/settings-namespace.js +6 -0
  139. package/lib/settings.d.ts +37 -0
  140. package/lib/settings.js +102 -0
  141. package/lib/startup.d.ts +16 -0
  142. package/lib/startup.js +60 -0
  143. package/lib/state/bindings.d.ts +46 -0
  144. package/lib/state/bindings.js +55 -0
  145. package/lib/state/channel-bindings.d.ts +37 -0
  146. package/lib/state/channel-bindings.js +43 -0
  147. package/lib/state/domain.d.ts +82 -0
  148. package/lib/state/domain.js +86 -0
  149. package/lib/state/effect-machine.d.ts +37 -0
  150. package/lib/state/effect-machine.js +52 -0
  151. package/lib/state/fail-closed.d.ts +44 -0
  152. package/lib/state/fail-closed.js +57 -0
  153. package/lib/state/intents.d.ts +51 -0
  154. package/lib/state/intents.js +73 -0
  155. package/lib/state/records.d.ts +37 -0
  156. package/lib/state/records.js +30 -0
  157. package/lib/state/retention.d.ts +74 -0
  158. package/lib/state/retention.js +68 -0
  159. package/lib/state/session-owners.d.ts +50 -0
  160. package/lib/state/session-owners.js +50 -0
  161. package/lib/stream/finalizer.d.ts +49 -0
  162. package/lib/stream/finalizer.js +67 -0
  163. package/lib/stream/icons.d.ts +15 -0
  164. package/lib/stream/icons.js +36 -0
  165. package/lib/stream/live.d.ts +140 -0
  166. package/lib/stream/live.js +515 -0
  167. package/lib/stream/markdown.d.ts +24 -0
  168. package/lib/stream/markdown.js +95 -0
  169. package/lib/stream/outbound.d.ts +27 -0
  170. package/lib/stream/outbound.js +25 -0
  171. package/lib/stream/render-fence.d.ts +27 -0
  172. package/lib/stream/render-fence.js +37 -0
  173. package/lib/stream/render-model.d.ts +48 -0
  174. package/lib/stream/render-model.js +68 -0
  175. package/lib/stream/splitter.d.ts +8 -0
  176. package/lib/stream/splitter.js +58 -0
  177. package/lib/stream/tool-view.d.ts +40 -0
  178. package/lib/stream/tool-view.js +39 -0
  179. package/lib/stream/typing.d.ts +22 -0
  180. package/lib/stream/typing.js +58 -0
  181. package/lib/stream/update-scheduler.d.ts +20 -0
  182. package/lib/stream/update-scheduler.js +65 -0
  183. package/package.json +86 -0
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Normalized inbound Gateway events. Every Discord dispatch this module
3
+ * accepts is untrusted wire data: validation failures are values, never
4
+ * throws, so one malformed payload can never break the dispatch loop. Only
5
+ * guild-scoped events the adapter understands survive this boundary; DMs and
6
+ * foreign event shapes are rejected here, before any business logic runs.
7
+ */
8
+ /** Numeric string of 17-20 digits, the Discord snowflake wire shape. */
9
+ const SNOWFLAKE = /^\d{17,20}$/u;
10
+ /** Validate the snowflake shape of an untrusted string. */
11
+ export function isDiscordSnowflake(value) {
12
+ return typeof value === 'string' && SNOWFLAKE.test(value);
13
+ }
14
+ /** Gateway dispatch event names the adapter consumes; everything else is rejected. */
15
+ const SUPPORTED_EVENTS = new Set(['MESSAGE_CREATE', 'INTERACTION_CREATE']);
16
+ /** Interaction types the adapter consumes (2=command, 3=component, 4=autocomplete, 5=modal). */
17
+ const SUPPORTED_INTERACTION_TYPES = new Set([2, 3, 4, 5]);
18
+ /**
19
+ * Detect and strip the adapter's own mention (`<@id>` or `<@!id>`) from
20
+ * message content. Other users' mentions are left intact; a mention-only
21
+ * message yields empty text.
22
+ */
23
+ export function extractBotMention(content, selfUserId) {
24
+ const pattern = new RegExp(`<@!?${selfUserId}>`, 'gu');
25
+ const mentioned = pattern.test(content);
26
+ if (!mentioned)
27
+ return { mentioned: false, text: content };
28
+ return { mentioned: true, text: content.replace(pattern, ' ').trim() };
29
+ }
30
+ function isRecord(value) {
31
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
32
+ }
33
+ /** Extract the member's role ids; server-provided, so lenient when absent. */
34
+ function extractRoleIds(payload) {
35
+ const member = payload['member'];
36
+ if (!isRecord(member) || !Array.isArray(member['roles']))
37
+ return [];
38
+ return member['roles'].filter((role) => typeof role === 'string');
39
+ }
40
+ function asSnowflake(value) {
41
+ return isDiscordSnowflake(value) ? value : undefined;
42
+ }
43
+ function parseMessage(payload, selfUserId) {
44
+ // Webhook-authored messages are app-driven surfaces, not member input:
45
+ // their author object need not carry `bot: true`, and anyone with
46
+ // Manage Webhooks could otherwise drive prompts through one.
47
+ if (typeof payload['webhook_id'] === 'string')
48
+ return { accepted: false, reason: 'bot-authored' };
49
+ const author = payload['author'];
50
+ if (!isRecord(author))
51
+ return { accepted: false, reason: 'malformed-payload' };
52
+ const authorId = asSnowflake(author['id']);
53
+ if (authorId === undefined)
54
+ return { accepted: false, reason: 'malformed-payload' };
55
+ if (author['bot'] === true || authorId === selfUserId) {
56
+ return { accepted: false, reason: 'bot-authored' };
57
+ }
58
+ const messageId = asSnowflake(payload['id']);
59
+ const guildId = asSnowflake(payload['guild_id']);
60
+ const channelId = asSnowflake(payload['channel_id']);
61
+ if (messageId === undefined || channelId === undefined) {
62
+ return { accepted: false, reason: 'malformed-payload' };
63
+ }
64
+ // A message outside a guild is a DM or foreign surface: reject at the
65
+ // earliest boundary so it can never reach authorization or DSH.
66
+ if (guildId === undefined)
67
+ return { accepted: false, reason: 'non-guild-event' };
68
+ const content = typeof payload['content'] === 'string' ? payload['content'] : undefined;
69
+ if (content === undefined)
70
+ return { accepted: false, reason: 'malformed-payload' };
71
+ const reference = payload['message_reference'];
72
+ const repliedToId = isRecord(reference) ? asSnowflake(reference['message_id']) : undefined;
73
+ if (reference !== undefined && !isRecord(reference)) {
74
+ return { accepted: false, reason: 'malformed-payload' };
75
+ }
76
+ if (isRecord(reference) && reference['message_id'] !== undefined && repliedToId === undefined) {
77
+ return { accepted: false, reason: 'malformed-payload' };
78
+ }
79
+ const stripped = extractBotMention(content, selfUserId);
80
+ const rawPermissions = typeof (isRecord(payload['member']) ? payload['member'] : {})['permissions'] === 'string'
81
+ ? payload['member'].permissions
82
+ : undefined;
83
+ return {
84
+ accepted: true,
85
+ event: {
86
+ kind: 'message',
87
+ messageId,
88
+ guildId,
89
+ channelId,
90
+ authorId,
91
+ roleIds: extractRoleIds(payload),
92
+ memberPermissions: rawPermissions,
93
+ content: stripped.text,
94
+ mentionedBot: stripped.mentioned,
95
+ repliedToId,
96
+ },
97
+ };
98
+ }
99
+ function parseInteraction(payload) {
100
+ const interactionType = payload['type'];
101
+ if (typeof interactionType !== 'number' || !SUPPORTED_INTERACTION_TYPES.has(interactionType)) {
102
+ return { accepted: false, reason: 'unsupported-event' };
103
+ }
104
+ const interactionId = asSnowflake(payload['id']);
105
+ const guildId = asSnowflake(payload['guild_id']);
106
+ const channelId = asSnowflake(payload['channel_id']);
107
+ if (interactionId === undefined || guildId === undefined || channelId === undefined) {
108
+ return { accepted: false, reason: 'malformed-payload' };
109
+ }
110
+ const member = payload['member'];
111
+ const user = payload['user'];
112
+ const authorId = isRecord(member)
113
+ ? (isRecord(member['user']) ? asSnowflake(member['user']['id']) : undefined)
114
+ : isRecord(user)
115
+ ? asSnowflake(user['id'])
116
+ : undefined;
117
+ if (authorId === undefined)
118
+ return { accepted: false, reason: 'malformed-payload' };
119
+ const data = payload['data'];
120
+ if (data !== undefined && !isRecord(data))
121
+ return { accepted: false, reason: 'malformed-payload' };
122
+ const table = isRecord(data) ? data : undefined;
123
+ const commandName = typeof table?.['name'] === 'string' ? table['name'] : undefined;
124
+ if ((interactionType === 2 || interactionType === 4) && commandName === undefined) {
125
+ return { accepted: false, reason: 'malformed-payload' };
126
+ }
127
+ const memberUser = isRecord(member) && isRecord(member['user']) ? member['user'] : undefined;
128
+ const wireUser = isRecord(user) ? user : undefined;
129
+ const rawPermissions = isRecord(member) && typeof member['permissions'] === 'string'
130
+ ? member['permissions']
131
+ : undefined;
132
+ // Component parent message + select values + modal fields (the question
133
+ // and approval flows need all three; absent for commands/autocomplete).
134
+ const wireMessage = payload['message'];
135
+ const componentMessageId = isRecord(wireMessage) ? asSnowflake(wireMessage['id']) : undefined;
136
+ const selectValues = isRecord(data) && Array.isArray(data['values'])
137
+ ? data['values'].filter((value) => typeof value === 'string')
138
+ : [];
139
+ const modalRows = isRecord(data) && Array.isArray(data['components']) ? data['components'] : [];
140
+ const modalFields = [];
141
+ for (const row of modalRows) {
142
+ if (!isRecord(row) || !Array.isArray(row['components']))
143
+ continue;
144
+ for (const field of row['components']) {
145
+ if (!isRecord(field))
146
+ continue;
147
+ if (typeof field['custom_id'] === 'string' && typeof field['value'] === 'string') {
148
+ modalFields.push({ customId: field['custom_id'], value: field['value'] });
149
+ }
150
+ }
151
+ }
152
+ return {
153
+ accepted: true,
154
+ event: {
155
+ kind: 'interaction',
156
+ interactionId,
157
+ interactionType,
158
+ guildId,
159
+ channelId,
160
+ actorId: authorId,
161
+ roleIds: extractRoleIds(payload),
162
+ memberPermissions: rawPermissions,
163
+ isBot: (memberUser?.['bot'] ?? wireUser?.['bot']) === true,
164
+ commandName,
165
+ data: table ?? {},
166
+ componentMessageId,
167
+ selectValues,
168
+ modalFields,
169
+ },
170
+ };
171
+ }
172
+ /**
173
+ * Validate and normalize one untrusted Gateway dispatch. Returns a rejection
174
+ * value for every unsupported or malformed input and never throws.
175
+ */
176
+ export function parseGatewayDispatch(dispatch, selfUserId) {
177
+ if (!isRecord(dispatch) || typeof dispatch['t'] !== 'string' || !SUPPORTED_EVENTS.has(dispatch['t'])) {
178
+ return { accepted: false, reason: 'unsupported-event' };
179
+ }
180
+ const payload = dispatch['d'];
181
+ if (!isRecord(payload))
182
+ return { accepted: false, reason: 'malformed-payload' };
183
+ return dispatch['t'] === 'MESSAGE_CREATE'
184
+ ? parseMessage(payload, selfUserId)
185
+ : parseInteraction(payload);
186
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The adapter's earliest normalized ingress boundary: dispatch parsing plus
3
+ * non-guild (DM) rejection. Guild allowlisting and member authorization live
4
+ * one layer up in `createAuthorizedIngress` (policy/guard.ts), the single
5
+ * production gate — the former standalone allowlist gate was removed as
6
+ * redundant (review N8).
7
+ */
8
+ import { type DiscordSnowflake, type GatewayDispatch, type IngestResult, type NormalizedInteraction, type NormalizedMessage } from './inbound.js';
9
+ export type NormalizedInboundEvent = NormalizedMessage | NormalizedInteraction;
10
+ export { parseGatewayDispatch } from './inbound.js';
11
+ /**
12
+ * Validate and normalize one dispatch, rejecting non-guild (DM) events at
13
+ * the earliest boundary. Pure: no allowlist, no delivery.
14
+ */
15
+ export declare function normalizeInbound(dispatch: GatewayDispatch, selfUserId: DiscordSnowflake): IngestResult;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The adapter's earliest normalized ingress boundary: dispatch parsing plus
3
+ * non-guild (DM) rejection. Guild allowlisting and member authorization live
4
+ * one layer up in `createAuthorizedIngress` (policy/guard.ts), the single
5
+ * production gate — the former standalone allowlist gate was removed as
6
+ * redundant (review N8).
7
+ */
8
+ import { parseGatewayDispatch, } from './inbound.js';
9
+ export { parseGatewayDispatch } from './inbound.js';
10
+ /**
11
+ * Validate and normalize one dispatch, rejecting non-guild (DM) events at
12
+ * the earliest boundary. Pure: no allowlist, no delivery.
13
+ */
14
+ export function normalizeInbound(dispatch, selfUserId) {
15
+ const result = parseGatewayDispatch(dispatch, selfUserId);
16
+ if (!result.accepted)
17
+ return result;
18
+ if (result.event.kind === 'message' && typeof result.event.guildId !== 'string') {
19
+ return { accepted: false, reason: 'non-guild-event' };
20
+ }
21
+ return result;
22
+ }
package/lib/i18n.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Bilingual copy for every user-facing Discord string (design 16.25).
3
+ * The adapter copy language is a plugin setting (`language`: 'zh' | 'en',
4
+ * default 'zh') — the Host locale itself is not exposed to host-side
5
+ * plugins (the settings provider is namespace-scoped). `CopyTable` is a
6
+ * plain object of named members so call sites stay typed and discoverable;
7
+ * the English table is type-checked against the Chinese one, so a missing
8
+ * translation is a compile error.
9
+ */
10
+ export type Language = 'zh' | 'en';
11
+ declare const zh: {
12
+ bindConfirmButton: string;
13
+ bindCancelButton: string;
14
+ bindAdminOnly: string;
15
+ bindUsage: string;
16
+ bindWorkspaceGone: string;
17
+ bindWorkspaceUnknown: string;
18
+ bindWorkspaceUnavailable: string;
19
+ bindChannelExists: (title: string, existing: string) => string;
20
+ bindConfirmPrompt: (title: string) => string;
21
+ bindCancelled: string;
22
+ bindChannelFailed: string;
23
+ bindChannelCreated: (title: string, channelId: string) => string;
24
+ bindChannelExisting: (title: string, channelId: string) => string;
25
+ listEmpty: string;
26
+ listHeader: string;
27
+ listTruncated: (shown: number, total: number) => string;
28
+ infoUnboundAllowed: string;
29
+ infoUnbound: string;
30
+ infoMemberOnly: string;
31
+ infoUnknown: string;
32
+ infoStale: string;
33
+ infoUnavailable: string;
34
+ infoRevision: (revision: string, binder: string) => string;
35
+ infoPath: (path: string) => string;
36
+ unknownSubcommand: string;
37
+ stopNotSessionThread: string;
38
+ stopNothingRunning: string;
39
+ stopNotOwner: string;
40
+ stopStopped: string;
41
+ stopStoppedQueuePreserved: string;
42
+ stopRejected: string;
43
+ stopUnknown: string;
44
+ steerUsage: string;
45
+ steerNothingRunning: string;
46
+ steerNotOwner: string;
47
+ steerQueued: string;
48
+ steerRejected: string;
49
+ steerUnknown: string;
50
+ queueNotSessionThread: string;
51
+ queueRemoveUsage: string;
52
+ queueItemNotFound: string;
53
+ queueRemoved: (summary: string) => string;
54
+ queueRemoveRejected: string;
55
+ queueRemoveUnknown: string;
56
+ queueNoData: string;
57
+ queueEmpty: string;
58
+ queueHeader: string;
59
+ queueRemoveHint: string;
60
+ forgetOperatorOnly: string;
61
+ forgetConfirmPrompt: string;
62
+ forgetConfirmButton: string;
63
+ forgetDone: string;
64
+ commandFailed: string;
65
+ confirmNotOwner: string;
66
+ submittedAck: string;
67
+ unresolvedAck: string;
68
+ expiredControl: string;
69
+ questionSubmitted: string;
70
+ questionDenied: string;
71
+ questionAlreadyResolved: string;
72
+ questionIncomplete: string;
73
+ questionInvalid: (reason: string) => string;
74
+ questionUnresolved: string;
75
+ questionResolvedElsewhere: string;
76
+ mainlineThreadConflict: string;
77
+ mainlineThreadFailed: string;
78
+ mainlineSessionRejected: string;
79
+ mainlineSessionUnknown: string;
80
+ mainlinePromptRejected: string;
81
+ mainlinePromptUnknown: string;
82
+ continuationRejected: string;
83
+ continuationUnknown: string;
84
+ unboundNoticeAdministrator: string;
85
+ unboundNoticeMember: string;
86
+ interruptedMarker: string;
87
+ approvalRequired: (label: string) => string;
88
+ };
89
+ export type CopyTable = typeof zh;
90
+ /** Discord-visible copy for the configured language. */
91
+ export declare function createCopy(language: Language): CopyTable;
92
+ export {};
package/lib/i18n.js ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Bilingual copy for every user-facing Discord string (design 16.25).
3
+ * The adapter copy language is a plugin setting (`language`: 'zh' | 'en',
4
+ * default 'zh') — the Host locale itself is not exposed to host-side
5
+ * plugins (the settings provider is namespace-scoped). `CopyTable` is a
6
+ * plain object of named members so call sites stay typed and discoverable;
7
+ * the English table is type-checked against the Chinese one, so a missing
8
+ * translation is a compile error.
9
+ */
10
+ const zh = {
11
+ // ── /project bind ────────────────────────────────────────────────────
12
+ bindConfirmButton: '确认绑定',
13
+ bindCancelButton: '取消',
14
+ bindAdminOnly: '⚠️ 只有工作区管理员可以绑定频道。',
15
+ bindUsage: '💡 用法:/project bind workspace:<从候选中选择>',
16
+ bindWorkspaceGone: '⚠️ 该工作区已不存在,请用 /project bind 的候选重新选择。',
17
+ bindWorkspaceUnknown: '⚠️ 工作区目录未在限时内确认(结果未知),请稍后重试。',
18
+ bindWorkspaceUnavailable: '⚠️ 工作区目录暂时不可用,请稍后重试。',
19
+ bindChannelExists: (title, existing) => `工作区「${title}」的频道已存在于:<#${existing}>`,
20
+ bindConfirmPrompt: (title) => `将为工作区「${title}」创建专属频道(DeepSeek Harness 分类下)?`,
21
+ bindCancelled: '已取消,未创建频道。',
22
+ bindChannelFailed: '⚠️ 频道创建失败,请稍后重试。',
23
+ bindChannelCreated: (title, channelId) => `💡 已为工作区「${title}」创建频道:<#${channelId}>`,
24
+ bindChannelExisting: (title, channelId) => `💡 工作区「${title}」的频道已存在于:<#${channelId}>`,
25
+ // ── /project list / info ─────────────────────────────────────────────
26
+ listEmpty: '(没有已注册的工作区)',
27
+ listHeader: '**可用工作区**',
28
+ listTruncated: (shown, total) => `\n(共 ${String(total)} 个,仅列出前 ${String(shown)} 个)`,
29
+ infoUnboundAllowed: '此频道未绑定工作区;请到工作区的专属频道中使用(/project bind 可创建)。',
30
+ infoUnbound: '⚠️ 此频道未绑定工作区。',
31
+ infoMemberOnly: '⚠️ 只有成员可以查看此频道的绑定。',
32
+ infoUnknown: '⚠️ 工作区目录未在限时内确认(结果未知),请稍后重试。',
33
+ infoStale: '⚠️ 绑定的工作区已不存在;请用 /project bind 重新选择。',
34
+ infoUnavailable: '⚠️ 无法读取工作区目录,请稍后重试。',
35
+ infoRevision: (revision, binder) => `修订 ${revision}(由 <@${binder}> 绑定)`,
36
+ infoPath: (path) => `路径:\`${path}\``,
37
+ unknownSubcommand: '💡 未知子命令。',
38
+ // ── /stop /steer /queue ──────────────────────────────────────────────
39
+ stopNotSessionThread: '⚠️ 此频道不是适配器拥有的会话线程。',
40
+ stopNothingRunning: '此线程当前没有可停止的运行中任务。',
41
+ stopNotOwner: '⚠️ 当前运行中的任务不是由此线程提交的,无法停止。',
42
+ stopStopped: '🛑 已停止。',
43
+ stopStoppedQueuePreserved: '🛑 已停止;队列中的待处理消息已保留。',
44
+ stopRejected: '⚠️ DSH 拒绝了停止请求。',
45
+ stopUnknown: '⚠️ 停止结果未知,请到 DSH Web 确认。',
46
+ steerUsage: '💡 用法:/steer prompt:<插话内容>',
47
+ steerNothingRunning: '此线程当前没有运行中的任务可插话。',
48
+ steerNotOwner: '⚠️ 当前任务不是由此线程提交的,无法插话。',
49
+ steerQueued: '↪️ 已插话。',
50
+ steerRejected: '⚠️ DSH 拒绝了插话。',
51
+ steerUnknown: '⚠️ 插话结果未知。',
52
+ queueNotSessionThread: '⚠️ 此频道不是适配器拥有的会话线程。',
53
+ queueRemoveUsage: '💡 用法:/queue remove item:</queue list 中的编号>',
54
+ queueItemNotFound: '未找到该队列项;请先运行 /queue list 获取最新编号。',
55
+ queueRemoved: (summary) => `⏳ 已移除:${summary}`,
56
+ queueRemoveRejected: '⚠️ DSH 拒绝了移除请求(该项可能已被处理)。',
57
+ queueRemoveUnknown: '⚠️ 移除结果未知;请用 /queue list 确认后再试。',
58
+ queueNoData: '⚠️ 暂无队列数据(进程可能刚重启);等待会话活动后会自动同步。',
59
+ queueEmpty: '⏳ (队列为空)',
60
+ queueHeader: '⏳ **队列**',
61
+ queueRemoveHint: '💡 用 `/queue remove <编号>` 移除。',
62
+ // ── guild forget ─────────────────────────────────────────────────────
63
+ forgetOperatorOnly: '⚠️ 只有 Host 操作员可以忘记 Guild。',
64
+ forgetConfirmPrompt: '⚠️ 将删除本 Guild 的全部适配器记录(绑定/意图;DSH 工作区与 Session 不受影响)。确认?',
65
+ forgetConfirmButton: '确认忘记',
66
+ forgetDone: '💡 已忘记本 Guild:适配器记录(绑定/意图)已删除;DSH 工作区与 Session 未受影响。',
67
+ // ── interaction failures / denials ───────────────────────────────────
68
+ commandFailed: '⚠️ 命令处理失败,请稍后重试。',
69
+ confirmNotOwner: '⚠️ 此确认不属于你。',
70
+ submittedAck: '💡 已提交你的选择。',
71
+ unresolvedAck: '⚠️ 应答结果未知;DSH 未确认,请勿重复点击。',
72
+ expiredControl: '⚠️ 此控件已过期或已失效;请重新运行对应命令。',
73
+ // ── question settle copies ───────────────────────────────────────────
74
+ questionSubmitted: '💡 回答已提交。',
75
+ questionDenied: '⚠️ 此问题不属于你。',
76
+ questionAlreadyResolved: '💡 该问题已被处理。',
77
+ questionIncomplete: '💡 还有问题未回答;请完成后再提交。',
78
+ questionInvalid: (reason) => `⚠️ 回答无效:${reason}`,
79
+ questionUnresolved: '⚠️ 应答结果未知;DSH 未确认。',
80
+ questionResolvedElsewhere: '💡 该问题已被其他客户端处理。',
81
+ // ── mainline failure copies (per outcome) ────────────────────────────
82
+ mainlineThreadConflict: '⚠️ 这条消息已被用于另一个会话任务,无法重复创建线程。',
83
+ mainlineThreadFailed: '⚠️ 线程创建失败,请稍后重试。',
84
+ mainlineSessionRejected: '⚠️ DSH 拒绝了会话创建(工作区可能已失效);请稍后重试或重新 /project bind。',
85
+ mainlineSessionUnknown: '⚠️ 会话创建结果未知;请到 DSH Web 确认后再重试,不会自动重复创建。',
86
+ mainlinePromptRejected: '⚠️ DSH 拒绝了任务提交。',
87
+ mainlinePromptUnknown: '⚠️ 任务提交结果未知;为避免重复执行不会自动重发,请确认后重试。',
88
+ continuationRejected: '⚠️ 消息提交被 DSH 拒绝。',
89
+ continuationUnknown: '⚠️ 消息提交结果未知;不会自动重发,请确认后重试。',
90
+ // ── unbound-channel mention affordance ───────────────────────────────
91
+ unboundNoticeAdministrator: '💡 此频道未绑定工作区。工作区管理员可运行 `/project bind` 创建并绑定项目频道。',
92
+ unboundNoticeMember: '💡 此频道未绑定工作区;请工作区管理员运行 `/project bind`。',
93
+ // ── stream renderer ──────────────────────────────────────────────────
94
+ interruptedMarker: '*(已被中断)*',
95
+ // ── approval / question cards ────────────────────────────────────────
96
+ approvalRequired: (label) => `Approval required — ${label}`,
97
+ };
98
+ const en = {
99
+ bindConfirmButton: 'Confirm bind',
100
+ bindCancelButton: 'Cancel',
101
+ bindAdminOnly: '⚠️ Only workspace administrators can bind channels.',
102
+ bindUsage: '💡 Usage: /project bind workspace:<pick from candidates>',
103
+ bindWorkspaceGone: '⚠️ That workspace no longer exists; pick again from /project bind candidates.',
104
+ bindWorkspaceUnknown: '⚠️ The workspace directory could not be confirmed in time (unknown); try again later.',
105
+ bindWorkspaceUnavailable: '⚠️ The workspace directory is temporarily unavailable; try again later.',
106
+ bindChannelExists: (title, existing) => `A channel for workspace "${title}" already exists at <#${existing}>`,
107
+ bindConfirmPrompt: title => `Create a dedicated channel for workspace "${title}" (under the DeepSeek Harness category)?`,
108
+ bindCancelled: 'Cancelled — no channel was created.',
109
+ bindChannelFailed: '⚠️ Channel creation failed; try again later.',
110
+ bindChannelCreated: (title, channelId) => `💡 Created a channel for workspace "${title}": <#${channelId}>`,
111
+ bindChannelExisting: (title, channelId) => `💡 Workspace "${title}" already has a channel: <#${channelId}>`,
112
+ listEmpty: '(no registered workspaces)',
113
+ listHeader: '**Available workspaces**',
114
+ listTruncated: (shown, total) => `\n(showing ${String(shown)} of ${String(total)})`,
115
+ infoUnboundAllowed: 'This channel is not bound to a workspace; use the workspace home channel (/project bind creates one).',
116
+ infoUnbound: '⚠️ This channel is not bound to a workspace.',
117
+ infoMemberOnly: '⚠️ Only members can view this channel binding.',
118
+ infoUnknown: '⚠️ The workspace directory could not be confirmed in time (unknown); try again later.',
119
+ infoStale: '⚠️ The bound workspace no longer exists; re-run /project bind.',
120
+ infoUnavailable: '⚠️ The workspace directory is temporarily unavailable; try again later.',
121
+ infoRevision: (revision, binder) => `Revision ${revision} (bound by <@${binder}>)`,
122
+ infoPath: path => `Path: \`${path}\``,
123
+ unknownSubcommand: '💡 Unknown subcommand.',
124
+ stopNotSessionThread: '⚠️ This channel is not an adapter-owned session thread.',
125
+ stopNothingRunning: 'This thread has no running task to stop.',
126
+ stopNotOwner: '⚠️ The running task was not submitted from this thread and cannot be stopped here.',
127
+ stopStopped: '🛑 Stopped.',
128
+ stopStoppedQueuePreserved: '🛑 Stopped; queued messages are preserved.',
129
+ stopRejected: '⚠️ DSH rejected the stop request.',
130
+ stopUnknown: '⚠️ Stop outcome unknown; check the DSH web console.',
131
+ steerUsage: '💡 Usage: /steer prompt:<steering text>',
132
+ steerNothingRunning: 'This thread has no running task to steer.',
133
+ steerNotOwner: '⚠️ The running task was not submitted from this thread and cannot be steered here.',
134
+ steerQueued: '↪️ Steering delivered.',
135
+ steerRejected: '⚠️ DSH rejected the steering.',
136
+ steerUnknown: '⚠️ Steering outcome unknown.',
137
+ queueNotSessionThread: '⚠️ This channel is not an adapter-owned session thread.',
138
+ queueRemoveUsage: '💡 Usage: /queue remove item:<position from /queue list>',
139
+ queueItemNotFound: 'Queue item not found; run /queue list for fresh positions.',
140
+ queueRemoved: summary => `⏳ Removed: ${summary}`,
141
+ queueRemoveRejected: '⚠️ DSH rejected the removal (the item may already be handled).',
142
+ queueRemoveUnknown: '⚠️ Removal outcome unknown; verify with /queue list and retry.',
143
+ queueNoData: '⚠️ No queue data yet (the process may have just restarted); it syncs after session activity.',
144
+ queueEmpty: '⏳ (queue is empty)',
145
+ queueHeader: '⏳ **Queue**',
146
+ queueRemoveHint: '💡 Use `/queue remove <position>` to remove.',
147
+ forgetOperatorOnly: '⚠️ Only Host operators can forget a Guild.',
148
+ forgetConfirmPrompt: '⚠️ This deletes every adapter-owned record for this Guild (bindings/intents; DSH workspaces and sessions are untouched). Continue?',
149
+ forgetConfirmButton: 'Confirm forget',
150
+ forgetDone: '💡 Guild forgotten: adapter records (bindings/intents) are deleted; DSH workspaces and sessions are untouched.',
151
+ commandFailed: '⚠️ Command handling failed; try again later.',
152
+ confirmNotOwner: '⚠️ This confirmation is not yours.',
153
+ submittedAck: '💡 Your choice has been submitted.',
154
+ unresolvedAck: '⚠️ Outcome unknown; DSH did not confirm — do not repeat the click.',
155
+ expiredControl: '⚠️ This control has expired or is no longer valid; run the command again.',
156
+ questionSubmitted: '💡 Answers submitted.',
157
+ questionDenied: '⚠️ This question is not yours.',
158
+ questionAlreadyResolved: '💡 This question has already been handled.',
159
+ questionIncomplete: '💡 Some questions are unanswered; complete them before submitting.',
160
+ questionInvalid: reason => `⚠️ Invalid answer: ${reason}`,
161
+ questionUnresolved: '⚠️ Outcome unknown; DSH did not confirm.',
162
+ questionResolvedElsewhere: '💡 This question was already handled by another client.',
163
+ mainlineThreadConflict: '⚠️ This message was already used for another session task; the thread cannot be recreated.',
164
+ mainlineThreadFailed: '⚠️ Thread creation failed; try again later.',
165
+ mainlineSessionRejected: '⚠️ DSH rejected the session creation (the workspace may be stale); try again later or re-run /project bind.',
166
+ mainlineSessionUnknown: '⚠️ Session creation outcome unknown; confirm on the DSH web console before retrying. It will not be resubmitted automatically.',
167
+ mainlinePromptRejected: '⚠️ DSH rejected the task submission.',
168
+ mainlinePromptUnknown: '⚠️ Task submission outcome unknown; it will not be resent automatically to avoid double execution. Confirm and retry.',
169
+ continuationRejected: '⚠️ The message submission was rejected by DSH.',
170
+ continuationUnknown: '⚠️ Message submission outcome unknown; it will not be resent automatically. Confirm and retry.',
171
+ unboundNoticeAdministrator: '💡 This channel is not bound to a workspace. A workspace administrator can run `/project bind` to create and bind the project channel.',
172
+ unboundNoticeMember: '💡 This channel is not bound to a workspace; ask a workspace administrator to run `/project bind`.',
173
+ interruptedMarker: '*(interrupted)*',
174
+ // ── approval / question cards ────────────────────────────────────────
175
+ approvalRequired: label => `Approval required — ${label}`,
176
+ };
177
+ /** Discord-visible copy for the configured language. */
178
+ export function createCopy(language) {
179
+ return language === 'en' ? en : zh;
180
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type z from '@deepseek-ai/schemastery';
3
+ import { type DiscordSettings } from './settings.js';
4
+ /** Stable Cordis plugin name for diagnostics. */
5
+ export declare const name = "dsh-discord";
6
+ /** Host services required by the complete embedded adapter. */
7
+ export declare const inject: string[];
8
+ export type Config = DiscordSettings;
9
+ export declare const Config: z<Config>;
10
+ /**
11
+ * Mount the embedded Discord adapter. Composition of the runtime path
12
+ * (Gateway → ingress → command/mention dispatch → Discord REST, plus the
13
+ * reconciliation sweeps) lives in `src/compose.ts` and is started from here.
14
+ */
15
+ export declare function apply(ctx: Context, config?: Config): void;
16
+ export { DEFAULT_DISCORD_SETTINGS, DISCORD_SETTINGS_NAMESPACE, DiscordSettingsSchema, normalizeDiscordSettings, validateDiscordSettings, } from './settings.js';
17
+ export { DISCORD_BOT_TOKEN_REF, describeDiscordCredential, resolveDiscordBotToken, } from './credential.js';