@addozhang/dsh-discord 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,7 +98,6 @@ The settings card exposes the three high-frequency fields (guild allowlist, auto
98
98
  | `/steer`, `/stop` | session thread | steer or cancel the running turn (owner only) |
99
99
  | `/guild forget` | any channel | operator-only removal of adapter records |
100
100
 
101
- `/model` remains registered ahead of its interactive provider → model → thinking/reasoning cascade (next milestone); `/preset`, `/skill`, and `/host` are deregistered until the router wires them — see Known limitations.
102
101
 
103
102
  ## Design notes
104
103
 
package/README.zh.md CHANGED
@@ -98,7 +98,6 @@ dsh-discord:
98
98
  | `/steer`, `/stop` | 会话线程 | 插话或取消运行中的 Turn(仅属主) |
99
99
  | `/guild forget` | 任意频道 | 仅操作员:移除适配器记录 |
100
100
 
101
- `/model` 保持注册,等待其交互式 provider → 模型 → thinking/reasoning 级联(下个里程碑);`/preset`、`/skill`、`/host` 已取消注册,待路由接线后回归——见已知限制。
102
101
 
103
102
  ## 设计说明
104
103
 
@@ -55,6 +55,15 @@ export interface AskWiringDeps {
55
55
  reason?: string;
56
56
  messageId?: string;
57
57
  }>;
58
+ /**
59
+ * Edit an already-posted control message (PATCH). Retirement greys the
60
+ * controls out on the ORIGINAL message — a POST would only add a new,
61
+ * empty message while the live buttons stayed behind (16.41).
62
+ */
63
+ editMessage(channelId: string, messageId: string, payload: unknown): Promise<{
64
+ stored: boolean;
65
+ reason?: string;
66
+ }>;
58
67
  }
59
68
  export declare function createAskWiring(deps: AskWiringDeps): {
60
69
  /** Retire a rendered control (clear components) once the ask is settled. */
@@ -14,8 +14,16 @@ import { renderQuestionControls } from './question-view.js';
14
14
  import { abandonUnrenderableQuestion } from './question-expiry.js';
15
15
  import { handleRemoteResolution } from './question-routing.js';
16
16
  export function createAskWiring(deps) {
17
- /** Discord message refs for rendered controls, keyed by ask id. */
17
+ /** Discord message refs + rendered rows for controls, keyed by ask id. */
18
18
  const controlMessages = new Map();
19
+ function disabledComponents(components) {
20
+ if (!Array.isArray(components))
21
+ return [];
22
+ return components.map(row => ({
23
+ ...row,
24
+ components: (row.components ?? []).map(control => ({ ...control, disabled: true })),
25
+ }));
26
+ }
19
27
  const disableControl = async (key) => {
20
28
  const target = controlMessages.get(key);
21
29
  if (target === undefined)
@@ -24,9 +32,11 @@ export function createAskWiring(deps) {
24
32
  // Retirement is best-effort: a failed disable must never fail the ask's
25
33
  // settled outcome — the registry TTL keeps stray clicks answerable.
26
34
  try {
27
- const posted = await deps.postMessage(target.channelId, { components: [] });
28
- if (!posted.stored)
29
- deps.log('discord_control_disable_failed', { key });
35
+ const patched = await deps.editMessage(target.channelId, target.messageId, {
36
+ components: disabledComponents(target.components),
37
+ });
38
+ if (!patched.stored)
39
+ deps.log('discord_control_disable_failed', { key, reason: patched.reason });
30
40
  }
31
41
  catch (cause) {
32
42
  deps.log('discord_control_disable_failed', { key, cause: String(cause) });
@@ -77,7 +87,7 @@ export function createAskWiring(deps) {
77
87
  void deps.postMessage(input.threadId, payload)
78
88
  .then(sent => {
79
89
  if (sent.stored && sent.messageId !== undefined) {
80
- controlMessages.set(input.approvalId, { channelId: input.threadId, messageId: sent.messageId });
90
+ controlMessages.set(input.approvalId, { channelId: input.threadId, messageId: sent.messageId, components: payload.components });
81
91
  }
82
92
  })
83
93
  .catch((cause) => { deps.log('discord_approval_render_failed', String(cause)); });
@@ -136,7 +146,7 @@ export function createAskWiring(deps) {
136
146
  abandonQuestion(sent.reason ?? 'post rejected');
137
147
  return;
138
148
  }
139
- controlMessages.set(input.rpcId, { channelId: input.threadId, messageId: sent.messageId });
149
+ controlMessages.set(input.rpcId, { channelId: input.threadId, messageId: sent.messageId, components: payload.components });
140
150
  })
141
151
  .catch((cause) => { abandonQuestion(String(cause)); });
142
152
  },
package/lib/index.js CHANGED
@@ -225,6 +225,21 @@ export function apply(ctx, config = DEFAULT_DISCORD_SETTINGS) {
225
225
  return { stored: false, reason: `HTTP ${String(sent.status)}: response missing message id` };
226
226
  return { stored: true, messageId: sent.body.id };
227
227
  },
228
+ editMessage: async (channelId, messageId, payload) => {
229
+ const rest = await sharedRest();
230
+ if (rest === undefined)
231
+ return { stored: false, reason: 'rest unavailable' };
232
+ const patched = await rest.request('PATCH', `/channels/${channelId}/messages/${messageId}`, payload);
233
+ if (patched.outcome !== 'completed') {
234
+ return {
235
+ stored: false,
236
+ reason: patched.outcome === 'rejected'
237
+ ? `HTTP ${String(patched.status)} ${patched.error.message}`
238
+ : patched.reason,
239
+ };
240
+ }
241
+ return { stored: true };
242
+ },
228
243
  });
229
244
  const componentFollowUp = async (_interactionId, interactionToken, content) => {
230
245
  const rest = await sharedRest();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@addozhang/dsh-discord",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Discord-first adapter for DeepSeek Harness",
5
5
  "license": "MIT",
6
6
  "type": "module",