dsh-connect-minimaxcode 0.6.1 → 0.6.3
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.en.md +14 -0
- package/README.md +14 -0
- package/lib/client.js +139 -10
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -384,6 +384,20 @@ The OAuth token is valid for about an hour and is refreshed automatically by the
|
|
|
384
384
|
|
|
385
385
|
## Changelog
|
|
386
386
|
|
|
387
|
+
### 0.6.3 (2026-11-11)
|
|
388
|
+
|
|
389
|
+
- **"Could not send it to the conversation" now names the missing piece**: delivering a picture needs several pieces of the host — the session identifier, the sessions service, the session binding, a way to send or to hold a draft, and the input box's attachment channel — and they arrive in different ways. There used to be one blanket failure message; each absence now has its own: the host named no session / offers no sessions service / this session is not reachable / it cannot send / it cannot hold drafts / the input box takes no attachments.
|
|
390
|
+
- **The plugin checks itself and reports**: the card tells the plugin's own route what the host actually offered when it mounts, so the status document carries that report — working on a failure no longer depends only on the sentence on the screen.
|
|
391
|
+
- Also fixed a real defect the tests caught at once: the report referred to the session identifier before it was defined, and the host fires effects early, so the script broke.
|
|
392
|
+
- `tests/generate-dock.spec.ts` grows to 8 cases, covering the report's content and the wording when no session is named.
|
|
393
|
+
|
|
394
|
+
### 0.6.2 (2026-11-11)
|
|
395
|
+
|
|
396
|
+
- **Fixed "could not send it to the conversation"**: the previous version looked for the session among the slot's props, but the host hands the session to the slot's **injection callback** (the slot is session-scoped), so the identifier was always missing and the picture had nowhere to go. The session is now taken the way the host gives it.
|
|
397
|
+
- **One more way to deliver**: when the host offers no session face to send with, the plugin falls back to the input box's own attachment channel — the very road a picture dragged in by hand takes, so it appears in the input box and you press send.
|
|
398
|
+
- The picture's arrival is the feedback, so nothing is left on screen and the composer stays clean.
|
|
399
|
+
- `tests/generate-dock.spec.ts` rewritten to drive the plugin **the way the host does** (6 cases): the session identifier arrives through the injection, the picture travels as `{type:"image", mediaType, data}` with the prompt, the fallback into the input box when there is no way to send, and the message shown when neither is there.
|
|
400
|
+
|
|
387
401
|
### 0.6.1 (2026-11-11)
|
|
388
402
|
|
|
389
403
|
- **A generated picture can now go straight into the conversation**: the composer tool row gained a **Generate an image** entry. Write a prompt, press generate, and the picture is sent as part of this turn — through the host's own road (`ctx.sessions.binding(id).session.prompt(content, mode, signal, requestId)`, the very call the send button makes), so the host validates it, commits it as a durable attachment and treats it as an ordinary message.
|
package/README.md
CHANGED
|
@@ -395,6 +395,20 @@ OAuth 令牌有效期约 1 小时,由客户端自动续期。插件**只在自
|
|
|
395
395
|
|
|
396
396
|
## 更新记录
|
|
397
397
|
|
|
398
|
+
### 0.6.3(2026-11-11)
|
|
399
|
+
|
|
400
|
+
- **「无法发送到对话」现在会说明缺哪一步**:把图片送进对话需要宿主的好几块能力(会话标识、会话服务、会话绑定、发送或草稿通道、输入框的附件通道),它们分别以不同方式交到插件手上。原先只有一句笼统的失败提示,现在每种缺失都有各自的话:宿主没给会话标识 / 没有会话服务 / 当前会话不可达 / 会话不支持发送 / 不支持草稿 / 输入框不接受附件。
|
|
401
|
+
- **插件会自检并上报**:卡片在挂载时把宿主实际提供了什么报告给插件自己的路由,因此状态文档里能读到这份自检结果 —— 排查时不必只靠屏幕上的那句话。
|
|
402
|
+
- 顺带修掉一处真实缺陷:自检报告引用了后面才定义的会话标识,宿主在效果阶段先跑,导致脚本崩溃(测试当场抓到)。
|
|
403
|
+
- `tests/generate-dock.spec.ts` 增至 8 个用例:覆盖自检报告的内容、以及缺会话标识时的话术。
|
|
404
|
+
|
|
405
|
+
### 0.6.2(2026-11-11)
|
|
406
|
+
|
|
407
|
+
- **修复「无法发送到对话」**:上一版从插槽的 props 里找会话标识,但宿主是把会话交给**插槽的注入回调**的(该插槽是会话作用域的),于是标识始终为空,图片自然送不出去。现在按宿主的方式取会话标识。
|
|
408
|
+
- **多一条投递路径**:宿主若不提供直接发送的会话面,插件会退回到输入框自己的附件通道 —— 与手动拖一张图进输入框完全同路,图片出现在输入框里,由你按下发送。
|
|
409
|
+
- 图片到达本身就是反馈,成功后不再留提示条,输入框保持干净。
|
|
410
|
+
- `tests/generate-dock.spec.ts` 重写为**按宿主的真实投递方式**驱动插件(6 个用例):会话标识来自注入回调、图片作为 `{type:"image", mediaType, data}` 随提示词发出、没有发送通道时退回输入框、以及两条路都没有时的提示。
|
|
411
|
+
|
|
398
412
|
### 0.6.1(2026-11-11)
|
|
399
413
|
|
|
400
414
|
- **生成的图片现在能直接进对话**:输入框工具行新增一个「生成图片」入口,写下提示词点生成,图片会作为**这一轮消息的一部分**发给模型 —— 走的是宿主自己那条路(`ctx.sessions.binding(id).session.prompt(content, mode, signal, requestId)`,与输入框按发送键完全相同),因此图片会被宿主校验、落成持久附件,并按普通消息参与对话。
|
package/lib/client.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
const STATUS_PATH = '/plugins/dsh-connect-minimaxcode/status';
|
|
21
21
|
const ACTION_PATH = '/plugins/dsh-connect-minimaxcode/action';
|
|
22
22
|
const GENERATE_PATH = '/plugins/dsh-connect-minimaxcode/generate';
|
|
23
|
+
const CAPABILITY_PATH = '/plugins/dsh-connect-minimaxcode/capability';
|
|
23
24
|
const POLL_INTERVAL_MS = 20000;
|
|
24
25
|
/**
|
|
25
26
|
* The provider id the host side registers with `llm.registerAdapter`.
|
|
@@ -97,7 +98,14 @@
|
|
|
97
98
|
generateToChat: '生成图片',
|
|
98
99
|
generateToChatHint: '生成的图片会作为这一轮消息的一部分发给模型。',
|
|
99
100
|
generateSent: '图片已发送到对话',
|
|
101
|
+
generateDrafted: '图片已放入输入框',
|
|
100
102
|
generateSendFailed: '无法发送到对话,请到插件页生成后下载。',
|
|
103
|
+
generateNoSession: '宿主没有给出会话标识,无法定位对话。',
|
|
104
|
+
generateNoSessions: '宿主没有提供会话服务,无法定位对话。',
|
|
105
|
+
generateNoBinding: '当前会话不可达(宿主没有给出它的绑定)。',
|
|
106
|
+
generateNoPrompt: '当前会话不支持发送消息。',
|
|
107
|
+
generateNoDraft: '当前会话不支持草稿附件。',
|
|
108
|
+
generateNoInput: '输入框不接受附件。',
|
|
101
109
|
};
|
|
102
110
|
const en = {
|
|
103
111
|
title: 'MiniMax Code credits',
|
|
@@ -168,7 +176,14 @@
|
|
|
168
176
|
generateToChat: 'Generate an image',
|
|
169
177
|
generateToChatHint: 'The generated picture is sent as part of this turn.',
|
|
170
178
|
generateSent: 'The picture is on its way to the conversation',
|
|
179
|
+
generateDrafted: 'The picture is in the input box',
|
|
171
180
|
generateSendFailed: 'Could not send it to the conversation; generate on the plugin page and download it instead.',
|
|
181
|
+
generateNoSession: 'The host named no session, so the conversation cannot be located.',
|
|
182
|
+
generateNoSessions: 'The host offers no sessions service, so the conversation cannot be located.',
|
|
183
|
+
generateNoBinding: 'This session is not reachable (the host gave no binding for it).',
|
|
184
|
+
generateNoPrompt: 'This session cannot send messages.',
|
|
185
|
+
generateNoDraft: 'This session cannot hold draft attachments.',
|
|
186
|
+
generateNoInput: 'The input box does not take attachments.',
|
|
172
187
|
};
|
|
173
188
|
const loader = globalThis.__ModuleLoader__;
|
|
174
189
|
if (loader === undefined)
|
|
@@ -452,6 +467,48 @@
|
|
|
452
467
|
return template.replace(/\\{(\\w+)\\}/g, (_match, name) => String(params?.[name] ?? ''));
|
|
453
468
|
});
|
|
454
469
|
const [open, setOpen] = React.useState(false);
|
|
470
|
+
/**
|
|
471
|
+
* The session the picture belongs to.
|
|
472
|
+
*
|
|
473
|
+
* The host hands the session id to the slot through its injection,
|
|
474
|
+
* because the slot is session-scoped; it is also carried on the
|
|
475
|
+
* standard props, so either may deliver it. It is read here, before
|
|
476
|
+
* anything else in the dock uses it, because an effect can fire
|
|
477
|
+
* before the rest of the body has run.
|
|
478
|
+
*/
|
|
479
|
+
const sessionId = () => props.sessionId;
|
|
480
|
+
/**
|
|
481
|
+
* What the host offers this dock, described once.
|
|
482
|
+
*
|
|
483
|
+
* Delivering a picture needs several pieces of the host and they
|
|
484
|
+
* arrive in different ways, so a failure otherwise reads as one
|
|
485
|
+
* undifferentiated message. The report is sent to the plugin's own
|
|
486
|
+
* route, so it can be read from the status document — by whoever is
|
|
487
|
+
* looking at the problem, not only by the person who hit it.
|
|
488
|
+
*/
|
|
489
|
+
const reportCapability = () => {
|
|
490
|
+
const id = sessionId();
|
|
491
|
+
const services = clientContext?.sessions;
|
|
492
|
+
const binding = id === undefined ? undefined : services?.binding?.(id);
|
|
493
|
+
const scoped = id === undefined ? undefined : services?.scope?.(id);
|
|
494
|
+
const conversation = scoped?.get?.('conversation');
|
|
495
|
+
const findings = {
|
|
496
|
+
sessionId: id === undefined ? 'missing' : 'present',
|
|
497
|
+
sessionsService: services === undefined ? 'missing' : 'present',
|
|
498
|
+
binding: binding === undefined ? 'missing' : 'present',
|
|
499
|
+
sessionPrompt: typeof binding?.session?.prompt === 'function' ? 'present' : 'missing',
|
|
500
|
+
scope: scoped === undefined ? 'missing' : 'present',
|
|
501
|
+
conversationService: conversation === undefined ? 'missing' : 'present',
|
|
502
|
+
createDrafts: typeof conversation?.createDrafts === 'function' ? 'present' : 'missing',
|
|
503
|
+
inputActions: typeof props.inputActions?.addAttachments === 'function' ? 'present' : 'missing',
|
|
504
|
+
};
|
|
505
|
+
void fetch(`${CAPABILITY_PATH}?report=${encodeURIComponent(JSON.stringify(findings))}`).catch(() => {
|
|
506
|
+
// The report is a courtesy; failing to send it changes nothing.
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
React.useEffect(() => {
|
|
510
|
+
reportCapability();
|
|
511
|
+
}, []);
|
|
455
512
|
const [prompt, setPrompt] = React.useState('');
|
|
456
513
|
const [busy, setBusy] = React.useState(false);
|
|
457
514
|
const [note, setNote] = React.useState('');
|
|
@@ -463,13 +520,43 @@
|
|
|
463
520
|
* sent this way is a normal turn.
|
|
464
521
|
*/
|
|
465
522
|
const sessionFace = () => {
|
|
466
|
-
const
|
|
467
|
-
if (
|
|
523
|
+
const id = sessionId();
|
|
524
|
+
if (id === undefined)
|
|
468
525
|
return undefined;
|
|
469
|
-
const binding = clientContext?.sessions?.binding?.(
|
|
526
|
+
const binding = clientContext?.sessions?.binding?.(id);
|
|
470
527
|
const send = binding?.session?.prompt;
|
|
471
528
|
return typeof send === 'function' ? { prompt: send } : undefined;
|
|
472
529
|
};
|
|
530
|
+
/**
|
|
531
|
+
* The conversation's own draft channel.
|
|
532
|
+
*
|
|
533
|
+
* The composer builds a draft from a browser File and hands its id to
|
|
534
|
+
* the input box, which is how a picture attached by hand appears
|
|
535
|
+
* there. Using the same two steps leaves the picture in the input box
|
|
536
|
+
* for the person to send, rather than sending on their behalf.
|
|
537
|
+
*/
|
|
538
|
+
const draftFace = () => {
|
|
539
|
+
const id = sessionId();
|
|
540
|
+
if (id === undefined)
|
|
541
|
+
return undefined;
|
|
542
|
+
const scoped = clientContext?.sessions?.scope?.(id);
|
|
543
|
+
const conversation = scoped?.get?.('conversation');
|
|
544
|
+
const create = conversation?.createDrafts;
|
|
545
|
+
return typeof create === 'function'
|
|
546
|
+
? { createDrafts: create.bind(conversation) }
|
|
547
|
+
: undefined;
|
|
548
|
+
};
|
|
549
|
+
/** The bytes behind a data URL. */
|
|
550
|
+
const base64Bytes = (dataUrl) => {
|
|
551
|
+
const comma = dataUrl.indexOf(',');
|
|
552
|
+
const base64 = comma < 0 ? dataUrl : dataUrl.slice(comma + 1);
|
|
553
|
+
const binary = atob(base64);
|
|
554
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
555
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
556
|
+
bytes[index] = binary.charCodeAt(index);
|
|
557
|
+
}
|
|
558
|
+
return bytes;
|
|
559
|
+
};
|
|
473
560
|
/** Turn a data URL into the parts the session's admission accepts. */
|
|
474
561
|
const imagePart = (dataUrl, mediaType) => {
|
|
475
562
|
const comma = dataUrl.indexOf(',');
|
|
@@ -495,15 +582,54 @@
|
|
|
495
582
|
return;
|
|
496
583
|
}
|
|
497
584
|
const part = imagePart(body.dataUrl, body.mediaType ?? 'image/jpeg');
|
|
585
|
+
if (part === undefined) {
|
|
586
|
+
setNote(t('generateFailed'));
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// Sending the picture as a turn is what the dock promises; the
|
|
590
|
+
// draft channel is the fallback for a host that only offers that.
|
|
498
591
|
const face = sessionFace();
|
|
499
|
-
if (
|
|
500
|
-
|
|
592
|
+
if (face === undefined) {
|
|
593
|
+
const id = sessionId();
|
|
594
|
+
setNote(id === undefined
|
|
595
|
+
? t('generateNoSession')
|
|
596
|
+
: clientContext?.sessions === undefined
|
|
597
|
+
? t('generateNoSessions')
|
|
598
|
+
: t('generateNoBinding'));
|
|
599
|
+
}
|
|
600
|
+
if (face !== undefined) {
|
|
601
|
+
await face.prompt([part, { type: 'text', text }], 'queue', undefined, `minimax-h3-${Date.now()}`);
|
|
602
|
+
setPrompt('');
|
|
603
|
+
setOpen(false);
|
|
604
|
+
// The picture's own arrival is the feedback: it is in the
|
|
605
|
+
// conversation now, so the composer needs no banner.
|
|
606
|
+
setNote('');
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const drafts = draftFace();
|
|
610
|
+
const addAttachments = props.inputActions?.addAttachments;
|
|
611
|
+
if (drafts === undefined) {
|
|
612
|
+
setNote(sessionId() === undefined
|
|
613
|
+
? t('generateNoSession')
|
|
614
|
+
: clientContext?.sessions === undefined
|
|
615
|
+
? t('generateNoSessions')
|
|
616
|
+
: t('generateNoDraft'));
|
|
501
617
|
return;
|
|
502
618
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
619
|
+
if (typeof addAttachments !== 'function') {
|
|
620
|
+
setNote(t('generateNoInput'));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
{
|
|
624
|
+
const file = new File([base64Bytes(body.dataUrl)], 'minimax-h3-image.jpg', { type: body.mediaType ?? 'image/jpeg' });
|
|
625
|
+
const created = drafts.createDrafts(sessionId(), [file], true);
|
|
626
|
+
addAttachments(created.map(draft => draft.id));
|
|
627
|
+
setPrompt('');
|
|
628
|
+
setOpen(false);
|
|
629
|
+
setNote('');
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
setNote(t('generateSendFailed'));
|
|
507
633
|
}
|
|
508
634
|
catch {
|
|
509
635
|
setNote(t('generateSendFailed'));
|
|
@@ -997,7 +1123,10 @@
|
|
|
997
1123
|
id: 'minimax-generate',
|
|
998
1124
|
order: 5,
|
|
999
1125
|
locale: NAMESPACE,
|
|
1000
|
-
|
|
1126
|
+
// The slot is session-scoped, and its injection receives the
|
|
1127
|
+
// session it is scoped to — which is how the dock learns which
|
|
1128
|
+
// conversation a picture belongs to.
|
|
1129
|
+
inject: (sessionId) => ({ t, sessionId }),
|
|
1001
1130
|
}, GenerateDock));
|
|
1002
1131
|
// The plugin's own page, keyed by the package name, where the
|
|
1003
1132
|
// balance and the expiry schedule are spelled out.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-minimaxcode",
|
|
3
3
|
"displayName": "DSH Connect MiniMax Code",
|
|
4
4
|
"description": "把本机已登录的 MiniMax Code 桌面 App 模型接入 DeepSeek Harness:零配置、Anthropic Messages 兼容、动态模型目录,并显示积分余额与到期 — bring MiniMax Code desktop app models into DSH with a dynamic model catalog and a credit balance with expiry details.",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.3",
|
|
6
6
|
"author": "JeffTsuiCUG",
|
|
7
7
|
"contributors": [
|
|
8
8
|
{
|