@xmanrui/dsh-im 4.22.0 → 4.24.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/README.en.md +5 -4
- package/README.md +5 -4
- package/lib/client.js +2584 -1153
- package/lib/index.js +290 -298
- package/package.json +14 -2
- package/plugin-src/client/channel-card-meta.js +2 -0
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -1
- package/plugin-src/client/channels/dingtalk/index.js +15 -13
- package/plugin-src/client/channels/email/api.js +52 -0
- package/plugin-src/client/channels/email/index.js +728 -0
- package/plugin-src/client/channels/email/styles.js +51 -0
- package/plugin-src/client/channels/feishu/api.js +4 -2
- package/plugin-src/client/channels/feishu/index.js +23 -7
- package/plugin-src/client/channels/imessage/index.js +2 -1
- package/plugin-src/client/channels/office/api.js +2 -0
- package/plugin-src/client/channels/office/index.js +6 -5
- package/plugin-src/client/channels/qq/api.js +7 -0
- package/plugin-src/client/channels/qq/index.js +16 -3
- package/plugin-src/client/channels/shared/token-api.js +8 -1
- package/plugin-src/client/channels/shared/token-channel.js +28 -3
- package/plugin-src/client/channels/slack/index.js +2 -1
- package/plugin-src/client/channels/telegram/index.js +3 -0
- package/plugin-src/client/channels/telegram/styles.js +12 -0
- package/plugin-src/client/channels/telegram/thinking-traces.js +25 -0
- package/plugin-src/client/channels/wecom/api.js +7 -1
- package/plugin-src/client/channels/wecom/index.js +16 -3
- package/plugin-src/client/channels/wecom-app/api.js +6 -1
- package/plugin-src/client/channels/wecom-app/index.js +16 -3
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/channels/weixin/connection-error.js +1 -71
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +16 -3
- package/plugin-src/client/connection-error.js +87 -0
- package/plugin-src/client/global-settings.js +81 -1
- package/plugin-src/client/i18n.js +69 -1
- package/plugin-src/client/index.js +40 -4
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +8 -2
- package/plugin-src/host/build.mjs +4 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
- package/plugin-src/host/channels/email/availability.mjs +60 -0
- package/plugin-src/host/channels/email/index.mjs +36 -0
- package/plugin-src/host/channels/email/production.mjs +58 -0
- package/plugin-src/host/channels/email/rpc.mjs +163 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
- package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
- package/plugin-src/host/channels/office/rpc.mjs +3 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/qq/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/production.mjs +52 -5
- package/plugin-src/host/channels/shared/rpc.mjs +14 -3
- package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
- package/plugin-src/host/channels/shared/startup.mjs +11 -6
- package/plugin-src/host/channels/shared/thinking-traces-rpc.mjs +11 -0
- package/plugin-src/host/channels/slack/production.mjs +2 -0
- package/plugin-src/host/channels/slack/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom/production.mjs +2 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
- package/plugin-src/host/channels/weixin/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
- package/plugin-src/host/delivery-adapter.mjs +11 -0
- package/plugin-src/host/image-input-rpc.mjs +24 -0
- package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/host/modern-harness-api.mjs +7 -2
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +13 -5
- package/src/channels/dingtalk/connection-error.mjs +5 -12
- package/src/channels/dingtalk/device-auth.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
- package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
- package/src/channels/discord/discord-api.mjs +3 -3
- package/src/channels/discord/discord-runtime.mjs +18 -12
- package/src/channels/email/config-store.mjs +194 -0
- package/src/channels/email/email-api.mjs +20 -0
- package/src/channels/email/email-bridge.mjs +17 -0
- package/src/channels/email/email-controller.mjs +824 -0
- package/src/channels/email/email-runtime.mjs +699 -0
- package/src/channels/email/harness-client.mjs +7 -0
- package/src/channels/email/mail-format.mjs +91 -0
- package/src/channels/email/state-store.mjs +176 -0
- package/src/channels/email/transport.mjs +64 -0
- package/src/channels/email/transports/agent-mail.mjs +604 -0
- package/src/channels/email/transports/agently-cli.mjs +288 -0
- package/src/channels/email/transports/imap-smtp.mjs +165 -0
- package/src/channels/feishu/bridge.mjs +39 -11
- package/src/channels/feishu/feishu-channel.mjs +35 -0
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/live-cot.mjs +260 -0
- package/src/channels/feishu/message-utils.mjs +3 -2
- package/src/channels/feishu/multi-bot-controller.mjs +56 -47
- package/src/channels/feishu/registration-manager.mjs +8 -4
- package/src/channels/feishu/repair-manager.mjs +2 -1
- package/src/channels/feishu/slash-command-registry.mjs +17 -0
- package/src/channels/feishu/step-push-mode.mjs +10 -4
- package/src/channels/imessage/runtime.mjs +9 -5
- package/src/channels/office/office-controller.mjs +23 -12
- package/src/channels/office/office-runtime.mjs +6 -3
- package/src/channels/office/office-transport.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +7 -5
- package/src/channels/qq/qq-controller.mjs +56 -42
- package/src/channels/qq/qq-runtime.mjs +16 -11
- package/src/channels/shared/bot-workspace-store.mjs +29 -10
- package/src/channels/shared/connection-error.mjs +191 -0
- package/src/channels/shared/connection-test.mjs +3 -1
- package/src/channels/shared/conversation-state-store.mjs +46 -1
- package/src/channels/shared/diagnostic-details.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +287 -57
- package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
- package/src/channels/shared/i18n-en/email.mjs +25 -0
- package/src/channels/shared/i18n-en/image-input.mjs +17 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
- package/src/channels/shared/i18n-en.mjs +6 -0
- package/src/channels/shared/image-input-policy.mjs +35 -0
- package/src/channels/shared/image-input-settings-store.mjs +60 -0
- package/src/channels/shared/image-input.mjs +124 -0
- package/src/channels/shared/image-prompt.mjs +15 -7
- package/src/channels/shared/message-failure.mjs +12 -0
- package/src/channels/shared/semantic/reply-reference.mjs +3 -3
- package/src/channels/shared/text-harness-bridge.mjs +150 -25
- package/src/channels/shared/token-bot-controller.mjs +52 -47
- package/src/channels/slack/slack-api.mjs +3 -3
- package/src/channels/slack/slack-controller.mjs +59 -51
- package/src/channels/slack/slack-runtime.mjs +18 -12
- package/src/channels/telegram/config-store.mjs +4 -1
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-controller.mjs +13 -1
- package/src/channels/telegram/telegram-runtime.mjs +215 -14
- package/src/channels/wecom/wecom-bridge.mjs +15 -6
- package/src/channels/wecom/wecom-controller.mjs +51 -37
- package/src/channels/wecom/wecom-runtime.mjs +10 -6
- package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
- package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
- package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
- package/src/channels/weixin/connection-error.mjs +26 -92
- package/src/channels/weixin/diagnostic-details.mjs +1 -63
- package/src/channels/weixin/weixin-bridge.mjs +7 -5
- package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
- package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
imageFileSourcesFromContent,
|
|
16
16
|
isModelImageRejection,
|
|
17
17
|
} from './image-prompt.mjs';
|
|
18
|
+
import { imageInputLimits } from './image-input-policy.mjs';
|
|
19
|
+
import { inboundImagesAsFiles, imageContentFromStaged, isImageAdmissionRejection, IMAGE_HOST_LIMIT_FALLBACK_PROMPT } from './image-input.mjs';
|
|
18
20
|
import { imSourceGuidance } from './im-source-guidance.mjs';
|
|
19
21
|
import { outboundArtifactRegistry } from './semantic/artifact.mjs';
|
|
20
22
|
import { t } from './i18n.mjs';
|
|
@@ -354,10 +356,23 @@ export function textFromHarnessContent(content) {
|
|
|
354
356
|
.trim();
|
|
355
357
|
}
|
|
356
358
|
|
|
359
|
+
/** Join only reasoning blocks from one Harness message payload. */
|
|
360
|
+
export function reasoningFromHarnessContent(content) {
|
|
361
|
+
return (Array.isArray(content) ? content : [])
|
|
362
|
+
.filter((part) => part?.type === 'reasoning' && typeof part.text === 'string')
|
|
363
|
+
.map((part) => part.text)
|
|
364
|
+
.join('\n')
|
|
365
|
+
.trim();
|
|
366
|
+
}
|
|
367
|
+
|
|
357
368
|
function assistantMessageText(event) {
|
|
358
369
|
return textFromHarnessContent(event?.data?.message?.content);
|
|
359
370
|
}
|
|
360
371
|
|
|
372
|
+
function assistantReasoningText(event) {
|
|
373
|
+
return reasoningFromHarnessContent(event?.data?.message?.content);
|
|
374
|
+
}
|
|
375
|
+
|
|
361
376
|
/** Aggregate assistant text in stable step/index order for one Harness Turn. */
|
|
362
377
|
export class AssistantTextAccumulator {
|
|
363
378
|
#steps = new Map();
|
|
@@ -413,6 +428,29 @@ function toolResultErrorText(error) {
|
|
|
413
428
|
return null;
|
|
414
429
|
}
|
|
415
430
|
|
|
431
|
+
/** Join the visible text carried by a Harness tool result. */
|
|
432
|
+
function toolResultText(data) {
|
|
433
|
+
const blocks = Array.isArray(data?.message?.content) ? data.message.content : [];
|
|
434
|
+
const nested = blocks.flatMap((block) => (
|
|
435
|
+
Array.isArray(block?.content) ? block.content : [block]
|
|
436
|
+
));
|
|
437
|
+
const text = nested
|
|
438
|
+
.filter((block) => block?.type === 'text' && typeof block.text === 'string')
|
|
439
|
+
.map((block) => block.text)
|
|
440
|
+
.join('');
|
|
441
|
+
return text || nonEmptyText(data?.text) || toolResultErrorText(data?.error) || '';
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function toolResultCallId(data) {
|
|
445
|
+
const blocks = Array.isArray(data?.message?.content) ? data.message.content : [];
|
|
446
|
+
return blocks
|
|
447
|
+
.map((block) => nonEmptyText(block?.toolCallId))
|
|
448
|
+
.find(Boolean)
|
|
449
|
+
?? nonEmptyText(data?.message?.source?.callId)
|
|
450
|
+
?? nonEmptyText(data?.callId)
|
|
451
|
+
?? nonEmptyText(data?.subCallId);
|
|
452
|
+
}
|
|
453
|
+
|
|
416
454
|
function consumeInteractionOwnership(ownership, entries) {
|
|
417
455
|
const ordered = [...entries]
|
|
418
456
|
.map((entry) => entry?.event ?? entry)
|
|
@@ -503,10 +541,18 @@ export class HarnessReplyTracker {
|
|
|
503
541
|
#reason = null;
|
|
504
542
|
#toolNames = new Map();
|
|
505
543
|
#lastToolName = null;
|
|
544
|
+
#transientSeqs = new Set();
|
|
545
|
+
#pendingReasoning = new Map();
|
|
546
|
+
#pendingReasoningChars = 0;
|
|
547
|
+
#reasoning = false;
|
|
506
548
|
|
|
507
|
-
constructor({ promptRpcId, afterSeq = -1 }) {
|
|
549
|
+
constructor({ promptRpcId, afterSeq = -1, reasoning = false }) {
|
|
508
550
|
this.#promptRpcId = promptRpcId;
|
|
509
551
|
this.#lastSeq = afterSeq;
|
|
552
|
+
// Reasoning updates are opt-in per consumer: only channels that surface
|
|
553
|
+
// thinking traces (Telegram thinking mode) subscribe; every other channel
|
|
554
|
+
// keeps its pre-thinking-traces update stream untouched.
|
|
555
|
+
this.#reasoning = reasoning === true;
|
|
510
556
|
}
|
|
511
557
|
|
|
512
558
|
get finished() {
|
|
@@ -540,7 +586,35 @@ export class HarnessReplyTracker {
|
|
|
540
586
|
pushUpdate({ type: 'text', text });
|
|
541
587
|
}
|
|
542
588
|
|
|
543
|
-
|
|
589
|
+
#bufferReasoning(event) {
|
|
590
|
+
const text = event.data?.chunk?.text;
|
|
591
|
+
if (typeof text !== 'string' || !text || text.length > 65536
|
|
592
|
+
|| this.#pendingReasoning.has(event.seq)) return;
|
|
593
|
+
this.#pendingReasoning.set(event.seq, event);
|
|
594
|
+
this.#pendingReasoningChars += text.length;
|
|
595
|
+
while (this.#pendingReasoning.size > 256 || this.#pendingReasoningChars > 65536) {
|
|
596
|
+
const [seq, oldest] = this.#pendingReasoning.entries().next().value;
|
|
597
|
+
this.#pendingReasoning.delete(seq);
|
|
598
|
+
this.#pendingReasoningChars -= oldest.data.chunk.text.length;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
#takePendingReasoning() {
|
|
603
|
+
const events = [...this.#pendingReasoning.values()];
|
|
604
|
+
this.#pendingReasoning.clear();
|
|
605
|
+
this.#pendingReasoningChars = 0;
|
|
606
|
+
return events;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
#emitReasoning(event, pushUpdate) {
|
|
610
|
+
const text = event.data?.chunk?.text;
|
|
611
|
+
if (event.data?.turn !== this.#targetTurn || this.#transientSeqs.has(event.seq)
|
|
612
|
+
|| typeof text !== 'string' || !text) return;
|
|
613
|
+
this.#transientSeqs.add(event.seq);
|
|
614
|
+
pushUpdate({ type: 'reasoning', turn: this.#targetTurn, text });
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
consumeAll(entries, { live = false, fromMux = false } = {}) {
|
|
544
618
|
const updates = [];
|
|
545
619
|
// 同一批轮询内的 text 帧只保留最新累积,其余事件逐帧透出,
|
|
546
620
|
// 让消费方能按顺序看到每个工具调用与结果。
|
|
@@ -554,20 +628,53 @@ export class HarnessReplyTracker {
|
|
|
554
628
|
}
|
|
555
629
|
updates.push(update);
|
|
556
630
|
};
|
|
557
|
-
const ordered = [...entries]
|
|
631
|
+
const ordered = [...(fromMux ? [] : this.#takePendingReasoning()), ...entries]
|
|
558
632
|
.map((entry) => entry?.event ?? entry)
|
|
559
633
|
.filter(Boolean)
|
|
560
634
|
.sort((left, right) => (left.seq ?? -1) - (right.seq ?? -1));
|
|
561
635
|
|
|
562
636
|
for (const event of ordered) {
|
|
637
|
+
if (this.#finished) break;
|
|
563
638
|
const seq = event.seq ?? -1;
|
|
639
|
+
const isReasoning = event.type === 'assistant/chunk'
|
|
640
|
+
&& event.data?.chunk?.type === 'reasoning-delta';
|
|
641
|
+
// The mux is lossy across reconnects. Only history may advance the
|
|
642
|
+
// durable cursor or finish the reply; the mux supplements reasoning.
|
|
643
|
+
if (fromMux && !isReasoning) continue;
|
|
644
|
+
if (isReasoning) {
|
|
645
|
+
if (!live || !Number.isFinite(seq)) continue;
|
|
646
|
+
if (this.#targetTurn === null) this.#bufferReasoning(event);
|
|
647
|
+
else this.#emitReasoning(event, pushUpdate);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (this.#targetTurn === null) {
|
|
651
|
+
if (seq <= this.#lastSeq) continue;
|
|
652
|
+
if (event.type === 'turn/start') {
|
|
653
|
+
this.#openTurn = event.data?.turn ?? null;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (event.type === 'user/message' && event.data?.source?.rpcId === this.#promptRpcId) {
|
|
657
|
+
this.#lastSeq = seq;
|
|
658
|
+
this.#targetTurn = event.data?.turn ?? this.#openTurn;
|
|
659
|
+
if (live && this.#targetTurn !== null) {
|
|
660
|
+
pushUpdate({ type: 'turn-start', turn: this.#targetTurn });
|
|
661
|
+
// Only earlier fragments flush here. Later steps remain in the
|
|
662
|
+
// sorted batch, interleaved with their tool calls and results.
|
|
663
|
+
for (const pending of this.#takePendingReasoning()) {
|
|
664
|
+
this.#emitReasoning(pending, pushUpdate);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
564
670
|
if (seq <= this.#lastSeq) continue;
|
|
565
671
|
this.#lastSeq = seq;
|
|
566
672
|
|
|
567
673
|
if (event.type === 'turn/start') this.#openTurn = event.data?.turn ?? null;
|
|
568
674
|
|
|
569
675
|
if (event.type === 'user/message' && event.data?.source?.rpcId === this.#promptRpcId) {
|
|
570
|
-
this.#targetTurn = this.#openTurn;
|
|
676
|
+
this.#targetTurn = event.data?.turn ?? this.#openTurn;
|
|
677
|
+
if (live) pushUpdate({ type: 'turn-start', turn: this.#targetTurn });
|
|
571
678
|
continue;
|
|
572
679
|
}
|
|
573
680
|
if (this.#targetTurn === null) continue;
|
|
@@ -577,6 +684,13 @@ export class HarnessReplyTracker {
|
|
|
577
684
|
this.#finished = true;
|
|
578
685
|
this.#reason = event.data?.reason ?? null;
|
|
579
686
|
this.#openTurn = null;
|
|
687
|
+
if (live) {
|
|
688
|
+
pushUpdate({
|
|
689
|
+
type: 'turn-end',
|
|
690
|
+
turn: this.#targetTurn,
|
|
691
|
+
reason: this.#reason,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
580
694
|
continue;
|
|
581
695
|
}
|
|
582
696
|
if (event.data?.turn !== this.#targetTurn) continue;
|
|
@@ -595,7 +709,22 @@ export class HarnessReplyTracker {
|
|
|
595
709
|
this.#assistantText.setCanonical(step, text);
|
|
596
710
|
// canonical 定稿且非空时按 step 透出,供分步推送消费方使用;
|
|
597
711
|
// 先于 commitText 透出,保持 text 更新作为批次末尾的既有语义。
|
|
598
|
-
if (text)
|
|
712
|
+
if (text) {
|
|
713
|
+
pushUpdate({
|
|
714
|
+
type: 'assistant-message',
|
|
715
|
+
step,
|
|
716
|
+
text,
|
|
717
|
+
...(live ? { turn: this.#targetTurn } : {}),
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
// Thinking-trace channels consume this as the 💭 line that precedes the
|
|
721
|
+
// tool call it explains; only consumers that explicitly subscribed
|
|
722
|
+
// (reasoning: true) see these updates, so default-mode channels keep
|
|
723
|
+
// their pre-thinking-traces progress stream.
|
|
724
|
+
if (this.#reasoning) {
|
|
725
|
+
const reasoning = assistantReasoningText(event);
|
|
726
|
+
if (reasoning) pushUpdate({ type: 'reasoning', step, text: reasoning });
|
|
727
|
+
}
|
|
599
728
|
this.#commitText(this.#assistantText.text, pushUpdate);
|
|
600
729
|
continue;
|
|
601
730
|
}
|
|
@@ -618,20 +747,38 @@ export class HarnessReplyTracker {
|
|
|
618
747
|
}
|
|
619
748
|
}
|
|
620
749
|
}
|
|
621
|
-
pushUpdate({
|
|
750
|
+
pushUpdate({
|
|
751
|
+
type: 'tool',
|
|
752
|
+
name,
|
|
753
|
+
...(argsText ? { arguments: argsText } : {}),
|
|
754
|
+
...(callId ? { callId } : {}),
|
|
755
|
+
...(live ? { turn: this.#targetTurn } : {}),
|
|
756
|
+
});
|
|
622
757
|
} else if (event.type === 'tool/result') {
|
|
623
|
-
const callId =
|
|
624
|
-
?? nonEmptyText(event.data?.callId)
|
|
625
|
-
?? nonEmptyText(event.data?.subCallId);
|
|
758
|
+
const callId = toolResultCallId(event.data);
|
|
626
759
|
const toolName = (callId ? this.#toolNames.get(callId) : null)
|
|
627
760
|
?? this.#lastToolName;
|
|
628
761
|
const error = toolResultErrorText(event.data?.error);
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
762
|
+
if (live) {
|
|
763
|
+
const providerErrorCode = event.data?.error?.code;
|
|
764
|
+
pushUpdate({
|
|
765
|
+
type: 'tool-result',
|
|
766
|
+
turn: this.#targetTurn,
|
|
767
|
+
...(callId ? { callId } : {}),
|
|
768
|
+
...(toolName ? { toolName } : {}),
|
|
769
|
+
text: toolResultText(event.data),
|
|
770
|
+
...(providerErrorCode !== undefined && providerErrorCode !== null
|
|
771
|
+
? { errorCode: String(providerErrorCode) }
|
|
772
|
+
: {}),
|
|
773
|
+
});
|
|
774
|
+
} else {
|
|
775
|
+
pushUpdate({
|
|
776
|
+
type: 'status',
|
|
777
|
+
text: t('正在整理结果…'),
|
|
778
|
+
...(toolName ? { toolName } : {}),
|
|
779
|
+
...(error ? { error } : {}),
|
|
780
|
+
});
|
|
781
|
+
}
|
|
635
782
|
}
|
|
636
783
|
}
|
|
637
784
|
return updates;
|
|
@@ -642,6 +789,19 @@ export class HarnessReplyTracker {
|
|
|
642
789
|
}
|
|
643
790
|
}
|
|
644
791
|
|
|
792
|
+
/**
|
|
793
|
+
* Progress updates an ask() consumer actually receives. latest 模式只投递一条
|
|
794
|
+
* 最新进展;assistant-message 是分步推送专用更新,且 canonical 去重后可能成为
|
|
795
|
+
* 批次唯一变化,绝不能冒充进度投给全部渠道。reasoning 同为思考留痕渠道专用,
|
|
796
|
+
* 默认模式下不得冒充进度(钉钉、企微等会展示其 text)。
|
|
797
|
+
*/
|
|
798
|
+
export function visibleProgressUpdates(updates, progressMode) {
|
|
799
|
+
if (progressMode === 'all') return updates;
|
|
800
|
+
return updates
|
|
801
|
+
.filter((update) => update.type !== 'assistant-message' && update.type !== 'reasoning')
|
|
802
|
+
.slice(-1);
|
|
803
|
+
}
|
|
804
|
+
|
|
645
805
|
export class HarnessRpcError extends Error {
|
|
646
806
|
constructor(method, error) {
|
|
647
807
|
super(`${method}: ${error?.message ?? 'unknown Harness RPC error'}`);
|
|
@@ -706,7 +866,6 @@ function harnessTurnError(reason) {
|
|
|
706
866
|
return new HarnessTurnError('turn-interrupted', { reason });
|
|
707
867
|
}
|
|
708
868
|
if (kind === 'aborted') return new HarnessTurnError('turn-aborted', { reason });
|
|
709
|
-
if (kind === 'completed') return new HarnessTurnError('model-empty-response', { reason });
|
|
710
869
|
return new HarnessTurnError('harness-turn-failed', { reason });
|
|
711
870
|
}
|
|
712
871
|
|
|
@@ -732,6 +891,7 @@ export class HarnessClient {
|
|
|
732
891
|
#controlExecutor;
|
|
733
892
|
#sessionMaintenanceExecutor;
|
|
734
893
|
#fileIngressExecutor;
|
|
894
|
+
#imageInputPolicy;
|
|
735
895
|
#managedProcess = null;
|
|
736
896
|
#interactionRegistry;
|
|
737
897
|
#interactionOwnerships;
|
|
@@ -756,6 +916,7 @@ export class HarnessClient {
|
|
|
756
916
|
controlExecutor,
|
|
757
917
|
sessionMaintenanceExecutor,
|
|
758
918
|
fileIngressExecutor,
|
|
919
|
+
imageInputPolicy = () => undefined,
|
|
759
920
|
}) {
|
|
760
921
|
if (typeof createWebSocket !== 'function') {
|
|
761
922
|
throw new TypeError('createWebSocket must be a function');
|
|
@@ -807,6 +968,7 @@ export class HarnessClient {
|
|
|
807
968
|
this.#controlExecutor = controlExecutor;
|
|
808
969
|
this.#sessionMaintenanceExecutor = sessionMaintenanceExecutor;
|
|
809
970
|
this.#fileIngressExecutor = fileIngressExecutor;
|
|
971
|
+
this.#imageInputPolicy = imageInputPolicy;
|
|
810
972
|
this.#interactionRegistry = interactionRegistry(this.#baseUrl?.origin ?? interactionScope);
|
|
811
973
|
this.#interactionOwnerships = this.#interactionRegistry.ownerships;
|
|
812
974
|
this.#interactionClaims = this.#interactionRegistry.claims;
|
|
@@ -1142,6 +1304,7 @@ export class HarnessClient {
|
|
|
1142
1304
|
signal,
|
|
1143
1305
|
onInteraction,
|
|
1144
1306
|
onResolved,
|
|
1307
|
+
onSessionEvent,
|
|
1145
1308
|
onOpen,
|
|
1146
1309
|
ownership,
|
|
1147
1310
|
} = {}) {
|
|
@@ -1155,6 +1318,9 @@ export class HarnessClient {
|
|
|
1155
1318
|
if (onResolved !== undefined && typeof onResolved !== 'function') {
|
|
1156
1319
|
throw new TypeError('onResolved must be a function');
|
|
1157
1320
|
}
|
|
1321
|
+
if (onSessionEvent !== undefined && typeof onSessionEvent !== 'function') {
|
|
1322
|
+
throw new TypeError('onSessionEvent must be a function');
|
|
1323
|
+
}
|
|
1158
1324
|
if (onOpen !== undefined && typeof onOpen !== 'function') {
|
|
1159
1325
|
throw new TypeError('onOpen must be a function');
|
|
1160
1326
|
}
|
|
@@ -1165,6 +1331,7 @@ export class HarnessClient {
|
|
|
1165
1331
|
signal,
|
|
1166
1332
|
onInteraction,
|
|
1167
1333
|
onResolved,
|
|
1334
|
+
onSessionEvent,
|
|
1168
1335
|
onOpen,
|
|
1169
1336
|
ownership,
|
|
1170
1337
|
});
|
|
@@ -1436,12 +1603,21 @@ export class HarnessClient {
|
|
|
1436
1603
|
});
|
|
1437
1604
|
}
|
|
1438
1605
|
|
|
1606
|
+
async getImageInputLimits() {
|
|
1607
|
+
return imageInputLimits(await this.#imageInputPolicy());
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1439
1610
|
async ask(sessionId, prompt, options = {}) {
|
|
1440
1611
|
if (typeof options === 'number') options = { timeoutMs: options };
|
|
1441
1612
|
const timeoutMs = options.timeoutMs ?? 600_000;
|
|
1442
1613
|
const signal = options.signal;
|
|
1443
1614
|
const onUpdate = typeof options.onUpdate === 'function' ? options.onUpdate : null;
|
|
1444
|
-
const progressMode = options.progressMode === '
|
|
1615
|
+
const progressMode = options.progressMode === 'live'
|
|
1616
|
+
? 'live'
|
|
1617
|
+
: options.progressMode === 'all' ? 'all' : 'latest';
|
|
1618
|
+
// Reasoning updates only exist for consumers that opt in (thinking-trace
|
|
1619
|
+
// mode); default ask() consumers keep the pre-thinking-traces stream.
|
|
1620
|
+
const reasoning = options.reasoning === true;
|
|
1445
1621
|
const onArtifact = typeof options.onArtifact === 'function' ? options.onArtifact : null;
|
|
1446
1622
|
const onInteraction = typeof options.onInteraction === 'function'
|
|
1447
1623
|
? options.onInteraction
|
|
@@ -1451,6 +1627,7 @@ export class HarnessClient {
|
|
|
1451
1627
|
: undefined;
|
|
1452
1628
|
const control = normalizeControl(options.control);
|
|
1453
1629
|
const inboundFiles = Array.isArray(options.files) ? options.files.filter(Boolean) : [];
|
|
1630
|
+
const inboundImages = Array.isArray(options.images) ? options.images.filter(Boolean) : [];
|
|
1454
1631
|
await this.ensureRunning({ signal });
|
|
1455
1632
|
const before = await this.rpc(
|
|
1456
1633
|
'session.history',
|
|
@@ -1461,8 +1638,39 @@ export class HarnessClient {
|
|
|
1461
1638
|
const baselineSeq = Math.max(-1, ...(before.events ?? []).map(({ event }) => event.seq ?? -1));
|
|
1462
1639
|
const promptRpcId = `${this.#rpcIdPrefix}-${randomUUID()}`;
|
|
1463
1640
|
const releasePromptInputOrigin = registerImInputOrigin(this.#interactionRegistry, promptRpcId);
|
|
1464
|
-
const tracker = new HarnessReplyTracker({ promptRpcId, afterSeq: baselineSeq });
|
|
1641
|
+
const tracker = new HarnessReplyTracker({ promptRpcId, afterSeq: baselineSeq, reasoning });
|
|
1642
|
+
let lastProgressAt = Date.now();
|
|
1643
|
+
let lastPollSeq = tracker.lastSeq;
|
|
1644
|
+
let progressTail = Promise.resolve();
|
|
1645
|
+
const consumeProgress = (entries, { fromMux = false } = {}) => {
|
|
1646
|
+
const updates = tracker.consumeAll(entries, {
|
|
1647
|
+
live: progressMode === 'live',
|
|
1648
|
+
fromMux,
|
|
1649
|
+
});
|
|
1650
|
+
const seqAdvanced = tracker.lastSeq > lastPollSeq;
|
|
1651
|
+
lastPollSeq = tracker.lastSeq;
|
|
1652
|
+
if (seqAdvanced) lastProgressAt = Date.now();
|
|
1653
|
+
if (!onUpdate) return progressTail;
|
|
1654
|
+
const visibleUpdates = progressMode === 'all' || progressMode === 'live'
|
|
1655
|
+
? updates
|
|
1656
|
+
: updates
|
|
1657
|
+
.filter((update) => update.type !== 'assistant-message' && update.type !== 'reasoning')
|
|
1658
|
+
.slice(-1);
|
|
1659
|
+
for (const update of visibleUpdates) {
|
|
1660
|
+
progressTail = progressTail
|
|
1661
|
+
.then(() => onUpdate(update))
|
|
1662
|
+
.catch((error) => {
|
|
1663
|
+
console.warn(
|
|
1664
|
+
'[dsh-im] ignored a progress update failure:',
|
|
1665
|
+
this.#logPrefix,
|
|
1666
|
+
error.message,
|
|
1667
|
+
);
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
return progressTail;
|
|
1671
|
+
};
|
|
1465
1672
|
const interactionController = onInteraction || onInteractionResolved
|
|
1673
|
+
|| (progressMode === 'live' && onUpdate)
|
|
1466
1674
|
? new AbortController()
|
|
1467
1675
|
: null;
|
|
1468
1676
|
const interactionSignal = interactionController
|
|
@@ -1493,6 +1701,7 @@ export class HarnessClient {
|
|
|
1493
1701
|
let interactionTask = null;
|
|
1494
1702
|
let artifactsDelivered = false;
|
|
1495
1703
|
let deliveredArtifactCount = 0;
|
|
1704
|
+
let artifactHandoffError = null;
|
|
1496
1705
|
const stagedBatches = [];
|
|
1497
1706
|
let promptAccepted = false;
|
|
1498
1707
|
let turnFinished = false;
|
|
@@ -1508,6 +1717,7 @@ export class HarnessClient {
|
|
|
1508
1717
|
await onArtifact(artifact);
|
|
1509
1718
|
deliveredArtifactCount += 1;
|
|
1510
1719
|
} catch (error) {
|
|
1720
|
+
artifactHandoffError ??= error;
|
|
1511
1721
|
outboundArtifactRegistry.release(artifact);
|
|
1512
1722
|
console.warn('[dsh-im] ignored an artifact handoff failure:', this.#logPrefix, error.message);
|
|
1513
1723
|
}
|
|
@@ -1530,6 +1740,26 @@ export class HarnessClient {
|
|
|
1530
1740
|
stagedBatches.push(staged);
|
|
1531
1741
|
prompt = appendInboundFilesToPrompt(prompt, staged);
|
|
1532
1742
|
}
|
|
1743
|
+
if (inboundImages.length > 0) {
|
|
1744
|
+
const limits = await this.getImageInputLimits();
|
|
1745
|
+
const sources = inboundImagesAsFiles(inboundImages, limits);
|
|
1746
|
+
let staged;
|
|
1747
|
+
try {
|
|
1748
|
+
staged = await this.#stageWorkspaceFiles(sessionId, sources, signal);
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
// Ingress wraps loader errors; retain the original image diagnostic.
|
|
1751
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
1752
|
+
if (error?.cause?.name === 'ImagePromptError') throw error.cause;
|
|
1753
|
+
throw error;
|
|
1754
|
+
}
|
|
1755
|
+
stagedBatches.push(staged);
|
|
1756
|
+
const images = await imageContentFromStaged(staged, limits, { signal });
|
|
1757
|
+
const originalContent = typeof basePrompt === 'string'
|
|
1758
|
+
? [{ type: 'text', text: basePrompt }] : basePrompt;
|
|
1759
|
+
prompt = appendInboundFilesToPrompt([...originalContent, ...images], {
|
|
1760
|
+
files: stagedBatches.flatMap((batch) => batch.files),
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1533
1763
|
if (interactionSignal) {
|
|
1534
1764
|
let markOpen;
|
|
1535
1765
|
const opened = new Promise((resolve) => { markOpen = resolve; });
|
|
@@ -1537,6 +1767,9 @@ export class HarnessClient {
|
|
|
1537
1767
|
signal: interactionSignal,
|
|
1538
1768
|
onInteraction,
|
|
1539
1769
|
onResolved: onInteractionResolved,
|
|
1770
|
+
onSessionEvent: progressMode === 'live'
|
|
1771
|
+
? (event) => { consumeProgress([event], { fromMux: true }); }
|
|
1772
|
+
: undefined,
|
|
1540
1773
|
onOpen: markOpen,
|
|
1541
1774
|
ownership,
|
|
1542
1775
|
});
|
|
@@ -1577,29 +1810,32 @@ export class HarnessClient {
|
|
|
1577
1810
|
// Session workspace and named in a text manifest — then retry once
|
|
1578
1811
|
// with a text-only prompt. The retry reuses promptRpcId so reply
|
|
1579
1812
|
// tracking, control and interaction ownership stay bound to this ask.
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
stagedImages
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1813
|
+
const originalsStaged = inboundImages.length > 0 && isImageAdmissionRejection(error)
|
|
1814
|
+
&& content.some((part) => part.type === 'image');
|
|
1815
|
+
const imageSources = !originalsStaged && isModelImageRejection(error)
|
|
1816
|
+
? imageFileSourcesFromContent(content) : [];
|
|
1817
|
+
if (!originalsStaged && imageSources.length === 0) throw error;
|
|
1818
|
+
if (!originalsStaged) {
|
|
1819
|
+
let stagedImages;
|
|
1820
|
+
try {
|
|
1821
|
+
stagedImages = await this.#stageWorkspaceFiles(sessionId, imageSources, signal);
|
|
1822
|
+
} catch (stagingError) {
|
|
1823
|
+
if (signal?.aborted) throw signal.reason ?? stagingError;
|
|
1824
|
+
console.warn(
|
|
1825
|
+
'[dsh-im] unable to restage rejected images as workspace files:',
|
|
1826
|
+
this.#logPrefix,
|
|
1827
|
+
stagingError?.message ?? String(stagingError),
|
|
1828
|
+
);
|
|
1829
|
+
throw error;
|
|
1830
|
+
}
|
|
1831
|
+
stagedBatches.push(stagedImages);
|
|
1595
1832
|
}
|
|
1596
|
-
stagedBatches.push(stagedImages);
|
|
1597
1833
|
const baseContent = typeof basePrompt === 'string'
|
|
1598
1834
|
? [{ type: 'text', text: basePrompt }]
|
|
1599
1835
|
: basePrompt;
|
|
1600
1836
|
const fallbackPrompt = appendInboundFilesToPrompt([
|
|
1601
1837
|
...contentWithoutImages(baseContent),
|
|
1602
|
-
{ type: 'text', text: t(IMAGE_FILE_FALLBACK_PROMPT) },
|
|
1838
|
+
{ type: 'text', text: t(isModelImageRejection(error) ? IMAGE_FILE_FALLBACK_PROMPT : IMAGE_HOST_LIMIT_FALLBACK_PROMPT) },
|
|
1603
1839
|
], { files: stagedBatches.flatMap((batch) => batch?.files ?? []) });
|
|
1604
1840
|
await sendPrompt(fallbackPrompt);
|
|
1605
1841
|
}
|
|
@@ -1611,8 +1847,6 @@ export class HarnessClient {
|
|
|
1611
1847
|
// confirm the Session is still running before renewing the wait.
|
|
1612
1848
|
// Interaction ownership is intentionally not a liveness signal: it stays
|
|
1613
1849
|
// active until turn/end and can therefore outlive a stalled turn.
|
|
1614
|
-
let lastProgressAt = Date.now();
|
|
1615
|
-
let lastPollSeq = tracker.lastSeq;
|
|
1616
1850
|
while (true) {
|
|
1617
1851
|
await sleep(300, signal);
|
|
1618
1852
|
const history = await this.rpc(
|
|
@@ -1626,24 +1860,7 @@ export class HarnessClient {
|
|
|
1626
1860
|
this.#consumeInteractionOwnerships(sessionId, history.events ?? []);
|
|
1627
1861
|
if (!wasActive && ownership.active) ownership.reconnect?.();
|
|
1628
1862
|
}
|
|
1629
|
-
|
|
1630
|
-
const seqAdvanced = tracker.lastSeq > lastPollSeq;
|
|
1631
|
-
lastPollSeq = tracker.lastSeq;
|
|
1632
|
-
if (seqAdvanced) lastProgressAt = Date.now();
|
|
1633
|
-
if (onUpdate) {
|
|
1634
|
-
// latest 模式只投递一条最新进展;assistant-message 是分步推送专用更新,
|
|
1635
|
-
// 且 canonical 去重后可能成为批次唯一变化,绝不能冒充进度投给全部渠道。
|
|
1636
|
-
const visibleUpdates = progressMode === 'all'
|
|
1637
|
-
? updates
|
|
1638
|
-
: updates.filter((update) => update.type !== 'assistant-message').slice(-1);
|
|
1639
|
-
for (const update of visibleUpdates) {
|
|
1640
|
-
try {
|
|
1641
|
-
await onUpdate(update);
|
|
1642
|
-
} catch (error) {
|
|
1643
|
-
console.warn('[dsh-im] ignored a progress update failure:', this.#logPrefix, error.message);
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1863
|
+
await consumeProgress(history.events ?? []);
|
|
1647
1864
|
if (tracker.finished) {
|
|
1648
1865
|
turnFinished = true;
|
|
1649
1866
|
if (!ownership?.stopRequested && !harnessTurnSucceeded(tracker.reason)) {
|
|
@@ -1659,6 +1876,12 @@ export class HarnessClient {
|
|
|
1659
1876
|
}
|
|
1660
1877
|
if (artifactCount > 0) return '';
|
|
1661
1878
|
if (ownership?.stopRequested) throw turnStoppedError();
|
|
1879
|
+
if (artifactHandoffError) throw artifactHandoffError;
|
|
1880
|
+
// A completed turn can do all its work through tools without text.
|
|
1881
|
+
// Missing end reasons retain the existing empty-reply failure.
|
|
1882
|
+
if (tracker.reason != null && harnessTurnSucceeded(tracker.reason)) {
|
|
1883
|
+
return t('本轮处理已结束,没有文本回复。');
|
|
1884
|
+
}
|
|
1662
1885
|
throw harnessTurnError(tracker.reason);
|
|
1663
1886
|
}
|
|
1664
1887
|
|
|
@@ -1681,6 +1904,11 @@ export class HarnessClient {
|
|
|
1681
1904
|
throw timeoutError;
|
|
1682
1905
|
}
|
|
1683
1906
|
} catch (error) {
|
|
1907
|
+
if (error instanceof HarnessTurnError) {
|
|
1908
|
+
error.details ??= {
|
|
1909
|
+
sessionId, promptRpcId, baselineSeq, turn: tracker.turn, lastSeq: tracker.lastSeq,
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1684
1912
|
// Once cancellation was accepted, transport/poll failures and timeouts
|
|
1685
1913
|
// describe the convergence of that stop, not an unrelated ask failure.
|
|
1686
1914
|
if (!ownership?.stopRequested) throw error;
|
|
@@ -1716,6 +1944,7 @@ export class HarnessClient {
|
|
|
1716
1944
|
signal,
|
|
1717
1945
|
onInteraction,
|
|
1718
1946
|
onResolved,
|
|
1947
|
+
onSessionEvent,
|
|
1719
1948
|
onOpen,
|
|
1720
1949
|
ownership,
|
|
1721
1950
|
}) {
|
|
@@ -1755,8 +1984,9 @@ export class HarnessClient {
|
|
|
1755
1984
|
};
|
|
1756
1985
|
const processEnvelope = (envelope) => {
|
|
1757
1986
|
const payload = envelope.payload;
|
|
1758
|
-
if (
|
|
1759
|
-
this.#consumeInteractionOwnerships(sessionId, [payload.event]);
|
|
1987
|
+
if (payload.type === 'session/event') {
|
|
1988
|
+
if (ownership) this.#consumeInteractionOwnerships(sessionId, [payload.event]);
|
|
1989
|
+
dispatch(onSessionEvent, payload.event);
|
|
1760
1990
|
return;
|
|
1761
1991
|
}
|
|
1762
1992
|
if (payload.type === 'question/requested' || payload.type === 'approval/requested') {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export default Object.freeze({
|
|
2
|
+
"DSH 版本": "DSH version",
|
|
3
|
+
"依赖版本": "Dependency versions",
|
|
4
|
+
"代理已配置": "Proxy configured",
|
|
5
|
+
"原因链已截断": "Cause chain truncated",
|
|
6
|
+
"请在 macOS 系统设置中授予 DSH 完全磁盘访问权限,以读取 Messages 数据库。": "Grant DSH Full Disk Access in macOS System Settings to read the Messages database.",
|
|
7
|
+
"请在 macOS 系统设置中允许 DSH 自动控制 Messages。": "Allow DSH to control Messages in macOS System Settings.",
|
|
8
|
+
"先在 macOS 系统设置中授予 Messages 权限。": "Grant Messages permissions in macOS System Settings first.",
|
|
9
|
+
|
|
10
|
+
"加载渠道配置": "Load channel configuration",
|
|
11
|
+
"激活账号": "Activate account",
|
|
12
|
+
"启动连接": "Start connection",
|
|
13
|
+
"监测连接与消息": "Monitor connection and messages",
|
|
14
|
+
"停止连接": "Stop connection",
|
|
15
|
+
"服务返回码": "Provider code",
|
|
16
|
+
"验证凭据": "Verify credentials",
|
|
17
|
+
"检查权限": "Check permissions",
|
|
18
|
+
"加载渠道组件": "Load channel component",
|
|
19
|
+
"执行渠道操作": "Perform channel operation",
|
|
20
|
+
"接收消息": "Receive message",
|
|
21
|
+
"发送消息": "Send message",
|
|
22
|
+
"发送测试消息": "Send test message",
|
|
23
|
+
"暂未识别": "Unknown",
|
|
24
|
+
"操作已取消。": "The operation was cancelled.",
|
|
25
|
+
"本地会话状态清理失败,请查看诊断详情。": "Local session state cleanup failed. See the diagnostic details.",
|
|
26
|
+
"账号已移除,但本地状态清理失败。": "The account was removed, but local state cleanup failed.",
|
|
27
|
+
"账号已移除,但登录凭据清理失败。": "The account was removed, but credential cleanup failed.",
|
|
28
|
+
"连接已移除,但登录凭据清理失败。": "The connection was removed, but credential cleanup failed.",
|
|
29
|
+
|
|
30
|
+
"操作失败,请查看原因和诊断详情。": "The operation failed. See the cause and diagnostic details.",
|
|
31
|
+
"检测到多个底层原因,请展开诊断详情逐项检查。": "Multiple underlying causes were detected. Expand the diagnostic details to inspect them.",
|
|
32
|
+
"服务域名解析失败,请检查运行 DSH 的机器的 DNS 和网络设置。": "Service DNS resolution failed. Check DNS and network settings on the machine running DSH.",
|
|
33
|
+
"目标拒绝连接,请检查服务地址、端口和访问限制。": "The connection was refused. Check the service address, port, and access restrictions.",
|
|
34
|
+
"连接已中断,请检查网络和目标服务状态。": "The connection was interrupted. Check the network and target service.",
|
|
35
|
+
"目标网络不可达,请检查运行 DSH 的机器的网络出口和访问限制。": "The target network is unreachable. Check network routing and access restrictions on the machine running DSH.",
|
|
36
|
+
"TLS 或证书校验失败,请检查系统时间、证书链和网络设置。": "TLS or certificate validation failed. Check the system clock, certificate chain, and network settings.",
|
|
37
|
+
"请求超时,请检查目标服务和网络;诊断详情中可查看已知的耗时。": "The request timed out. Check the service and network; available timings are in the diagnostic details.",
|
|
38
|
+
"代理连接或认证失败,请检查 DSH 进程使用的代理配置。": "Proxy connection or authentication failed. Check the proxy configuration used by the DSH process.",
|
|
39
|
+
"服务认证失败,请检查账号和授权信息。": "Service authentication failed. Check the account and credentials.",
|
|
40
|
+
"操作权限不足,请检查对应资源的访问权限。": "Permission was denied. Check access to the affected resource.",
|
|
41
|
+
"所需文件或本地工具不存在,请检查安装和配置。": "A required file or local tool is missing. Check installation and configuration.",
|
|
42
|
+
"服务正在限流,请稍后重试。": "The service is rate limiting requests. Try again later.",
|
|
43
|
+
"服务拒绝了请求,请检查凭据、权限或访问限制;HTTP 状态本身不能确认登录失效。": "The service rejected the request. Check credentials, permissions, or access restrictions; the HTTP status alone does not prove that the login expired.",
|
|
44
|
+
"目标服务暂时异常,请稍后重试。": "The target service is temporarily unavailable. Try again later.",
|
|
45
|
+
"目标服务返回了 HTTP 错误,请查看诊断详情。": "The target service returned an HTTP error. See the diagnostic details.",
|
|
46
|
+
"数据格式无效,请按失败阶段检查服务响应或本地配置。": "The data format is invalid. Check the service response or local configuration indicated by the failing stage.",
|
|
47
|
+
"暂未识别具体原因,请复制诊断信息,并查看相同参考号的 DSH 日志。": "The underlying cause is unknown. Copy the diagnostics and find the matching reference in the DSH logs.",
|
|
48
|
+
"多个原因": "Multiple causes",
|
|
49
|
+
"服务域名": "Service host",
|
|
50
|
+
"请求耗时(毫秒)": "Request duration (ms)",
|
|
51
|
+
"超时阈值(毫秒)": "Timeout (ms)",
|
|
52
|
+
"Node 版本": "Node version",
|
|
53
|
+
"运行平台": "Platform",
|
|
54
|
+
"无法访问 DSH 管理接口,请检查 DSH 连接或重新登录后重试。": "Cannot access the DSH management API. Check the DSH connection or sign in again."
|
|
55
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** English projections for host-side Email channel text. */
|
|
2
|
+
export default Object.freeze({
|
|
3
|
+
'邮箱': 'Email',
|
|
4
|
+
'邮箱地址格式不正确': 'The email address format is invalid',
|
|
5
|
+
'邮箱地址与应用密码均为必填': 'Both the email address and the app password are required',
|
|
6
|
+
'请选择邮箱服务商或填写 IMAP/SMTP 服务器地址':
|
|
7
|
+
'Choose a mail provider or fill in the IMAP/SMTP server addresses',
|
|
8
|
+
'必须至少配置一个允许发件人':
|
|
9
|
+
'At least one allowed sender must be configured',
|
|
10
|
+
'邮箱连接失败,请检查地址、应用密码与服务器设置':
|
|
11
|
+
'Could not connect to the mailbox; check the address, app password, and server settings',
|
|
12
|
+
'未找到该邮箱配置': 'No mailbox configuration was found',
|
|
13
|
+
'邮箱凭据已丢失,请重新绑定':
|
|
14
|
+
'The mailbox credential is missing; please bind the mailbox again',
|
|
15
|
+
'邮箱尚未连接': 'The mailbox is not connected yet',
|
|
16
|
+
'邮箱通道连接正常。': 'The Email channel is connected.',
|
|
17
|
+
'邮箱通道运行正常': 'The Email channel is running normally',
|
|
18
|
+
'邮箱通道尚未连接': 'The Email channel is not connected yet',
|
|
19
|
+
'邮箱访问策略同步失败,请重试': 'Failed to sync the mailbox access policy; please retry',
|
|
20
|
+
'当前环境不支持会话绑定': 'Session binding is not supported in this environment',
|
|
21
|
+
'该接入方式不需要扫码授权': 'This access method does not use QR authorization',
|
|
22
|
+
'授权信息缺失,请重新扫码授权': 'The authorization is missing; please scan again',
|
|
23
|
+
'扫码授权尚未开始': 'QR authorization has not been started',
|
|
24
|
+
'扫码授权已超时,请重新发起': 'QR authorization timed out; please start it again',
|
|
25
|
+
});
|