@xmanrui/dsh-im 4.23.0 → 4.24.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.en.md +7 -2
- package/README.md +7 -2
- package/lib/client.js +498 -347
- package/lib/index.js +284 -283
- package/package.json +11 -2
- package/plugin-src/client/channel-card-meta.js +3 -9
- package/plugin-src/client/channels/dingtalk/index.js +9 -8
- package/plugin-src/client/channels/feishu/index.js +21 -17
- package/plugin-src/client/channels/qq/index.js +9 -8
- package/plugin-src/client/channels/shared/collapsible-account.js +49 -26
- package/plugin-src/client/channels/shared/token-api.js +3 -0
- package/plugin-src/client/channels/shared/token-channel.js +9 -8
- 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/index.js +9 -8
- package/plugin-src/client/channels/wecom-app/index.js +9 -8
- package/plugin-src/client/channels/weixin/index.js +9 -8
- package/plugin-src/client/channels/whatsapp/index.js +9 -8
- package/plugin-src/client/i18n.js +13 -1
- package/plugin-src/client/index.js +10 -2
- package/plugin-src/client/styles.js +12 -8
- package/plugin-src/client/update-panel.js +18 -8
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/shared/thinking-traces-rpc.mjs +11 -0
- package/plugin-src/host/modern-harness-api.mjs +7 -2
- package/plugin-src/host/update-service.mjs +19 -14
- package/scripts/verify-package.mjs +11 -5
- package/src/channels/email/email-runtime.mjs +9 -2
- package/src/channels/email/transports/agent-mail.mjs +14 -3
- package/src/channels/feishu/bridge.mjs +122 -46
- package/src/channels/feishu/feishu-channel.mjs +35 -0
- package/src/channels/feishu/live-cot.mjs +260 -0
- package/src/channels/feishu/slash-command-registry.mjs +17 -0
- package/src/channels/feishu/step-push-mode.mjs +10 -4
- package/src/channels/shared/harness-client.mjs +224 -39
- package/src/channels/shared/text-harness-bridge.mjs +60 -14
- package/src/channels/shared/workspace-session.mjs +7 -1
- package/src/channels/telegram/config-store.mjs +4 -1
- package/src/channels/telegram/telegram-controller.mjs +13 -1
- package/src/channels/telegram/telegram-runtime.mjs +198 -1
|
@@ -45,6 +45,8 @@ const EN = Object.freeze({
|
|
|
45
45
|
'IM机器人设置': 'IM bot settings',
|
|
46
46
|
'IM 面板加载失败': 'Could not load the IM panel',
|
|
47
47
|
'更多机器人设置': 'More bot settings',
|
|
48
|
+
'更多设置': 'More settings',
|
|
49
|
+
'常用配置': 'Common settings',
|
|
48
50
|
'机器人设置': 'Bot settings',
|
|
49
51
|
'机器人设置页签': 'Bot settings tabs',
|
|
50
52
|
'投递设置': 'Delivery settings',
|
|
@@ -130,6 +132,8 @@ const EN = Object.freeze({
|
|
|
130
132
|
'已安装版本': 'Installed version',
|
|
131
133
|
'目标 profile': 'Target profile',
|
|
132
134
|
'目标版本': 'Target version',
|
|
135
|
+
'最新版本': 'Latest version',
|
|
136
|
+
'上次更新目标': 'Previous update target',
|
|
133
137
|
'无法确认': 'Unknown',
|
|
134
138
|
'发现新版本': 'Update available',
|
|
135
139
|
'已是最新版本': 'Up to date',
|
|
@@ -143,6 +147,7 @@ const EN = Object.freeze({
|
|
|
143
147
|
'更新已生效': 'Updated version is active',
|
|
144
148
|
'更新失败': 'Update failed',
|
|
145
149
|
'更新请求失败': 'Update request failed',
|
|
150
|
+
'暂时无法安装更新': 'Update installation is unavailable',
|
|
146
151
|
'仅更新 DSH-IM。安装完成后需手动重启后台;本功能不会自动重启或主动刷新页面。': 'Only DSH-IM will be updated. Restart the backend manually after installation. This feature does not automatically restart it or initiate a page refresh.',
|
|
147
152
|
'当前是源码或链接安装,只能检查版本;请手动更新源码,或迁移到 npm 安装。': 'This is a source or linked installation. You can check versions, but must update the source manually or migrate to an npm installation.',
|
|
148
153
|
'无法确认当前 profile,请在对应的 Harness 环境中手动更新。': 'The current profile could not be identified. Update manually in the corresponding Harness environment.',
|
|
@@ -262,11 +267,13 @@ const EN = Object.freeze({
|
|
|
262
267
|
'查看分步直推说明': 'View step push help',
|
|
263
268
|
'开启后逐步推送工具调用与过程说明': 'Push tool calls and process notes step by step',
|
|
264
269
|
'任务过程展示': 'Task progress display',
|
|
265
|
-
'
|
|
270
|
+
'设置任务执行过程的呈现方式:不显示、原生直播、实时卡片或逐步消息': 'Choose how execution is presented: hidden, native live process, one live card, or step-by-step messages',
|
|
266
271
|
'不显示过程(只发送最终答案)': 'Hide the process (send the final answer only)',
|
|
272
|
+
'实时直播(飞书原生思考过程)': 'Live process (native Feishu thinking process)',
|
|
267
273
|
'实时过程卡(全程一张卡片动态更新)': 'Live process card (one card updated throughout)',
|
|
268
274
|
'逐步直播(每一步单独发一条消息)': 'Step-by-step feed (one message per step)',
|
|
269
275
|
'适合日常问答:执行过程中不显示工具调用等中间步骤,只回复最终结果': 'For everyday Q&A: tool calls and other interim steps stay hidden, only the final result is replied',
|
|
276
|
+
'使用飞书原生思考过程展示推理、工具调用与结果,最终答案单独发送': 'Use Feishu’s native thinking process for reasoning, tool calls, and results, then send the final answer separately',
|
|
270
277
|
'推荐长任务使用:过程与最终答案都在同一张卡片里实时更新,不刷屏': 'Recommended for long tasks: the process and the final answer update live in one card without flooding the chat',
|
|
271
278
|
'每一步都单独发一条消息(含工具调用和过程说明);注意长任务会连续发送较多消息': 'Every step is sent as its own message (including tool calls and notes); long tasks may send many messages in a row',
|
|
272
279
|
'分步直推设置保存失败,请重试。': 'Could not save the step push setting. Try again.',
|
|
@@ -667,6 +674,11 @@ const EN = Object.freeze({
|
|
|
667
674
|
'接入 Telegram 机器人': 'Connect a Telegram bot',
|
|
668
675
|
'先通过 @BotFather 获取 Bot Token,再在这里完成接入。': 'Get a Bot Token from @BotFather, then connect it here.',
|
|
669
676
|
'填写 @BotFather 生成的 Bot Token': 'Enter the Bot Token from @BotFather',
|
|
677
|
+
'会话行为': 'Conversation behavior',
|
|
678
|
+
'思考过程留痕': 'Thinking traces',
|
|
679
|
+
'实时显示思考步骤与工具调用,最终答案单独成条': 'Show thinking steps and tool calls as they happen; the final answer arrives as its own message',
|
|
680
|
+
'请提交有效的思考过程留痕设置。': 'Submit a valid thinking-traces setting.',
|
|
681
|
+
'正在使用{name}…': 'Using {name}…',
|
|
670
682
|
'访问模式': 'Access mode',
|
|
671
683
|
'Telegram 访问模式': 'Telegram access mode',
|
|
672
684
|
'查看 Telegram 访问模式说明': 'View Telegram access mode details',
|
|
@@ -324,8 +324,16 @@ export function IMSettingsTab({
|
|
|
324
324
|
'aria-label': 'dsh-im GitHub',
|
|
325
325
|
'aria-describedby': githubTooltipId,
|
|
326
326
|
},
|
|
327
|
-
h('
|
|
328
|
-
|
|
327
|
+
h('svg', {
|
|
328
|
+
width: 18,
|
|
329
|
+
height: 18,
|
|
330
|
+
viewBox: '0 0 16 16',
|
|
331
|
+
fill: 'currentColor',
|
|
332
|
+
focusable: 'false',
|
|
333
|
+
'aria-hidden': 'true',
|
|
334
|
+
}, h('path', {
|
|
335
|
+
d: 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.65 7.65 0 0 1 2-.27c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z',
|
|
336
|
+
}))),
|
|
329
337
|
h('span', {
|
|
330
338
|
id: githubTooltipId,
|
|
331
339
|
className: 'dim-githubTooltip',
|
|
@@ -93,10 +93,10 @@ const CSS = String.raw`
|
|
|
93
93
|
.dim-updateFooter .dim-updateButton:first-child { margin-right: auto; }
|
|
94
94
|
.dim-updatePrimary, .dim-updatePrimary:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); color: #fff; background: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
95
95
|
.dim-githubAction { position: relative; display: inline-flex; flex: none; }
|
|
96
|
-
.dim-githubLink {
|
|
96
|
+
.dim-githubLink { width: 30px; height: 30px; display: grid; place-items: center; flex: none; padding: 0; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease; }
|
|
97
|
+
.dim-githubLink svg { display: block; }
|
|
97
98
|
.dim-githubLink:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
98
99
|
.dim-githubLink:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; }
|
|
99
|
-
.dim-githubArrow { font-size: 13px; line-height: 1; }
|
|
100
100
|
.dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
|
|
101
101
|
.dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
102
102
|
.dim-generalSettingsAction { position: relative; display: inline-flex; flex: none; }
|
|
@@ -417,10 +417,15 @@ const CSS = String.raw`
|
|
|
417
417
|
.dim-panel .dim-botCardBody { position: relative; min-width: 0; width: 100%; max-width: 100%; padding: 12px 8px; }
|
|
418
418
|
.dim-collapsibleAccount { min-width: 0; display: flex; flex-direction: column; }
|
|
419
419
|
.dim-collapsibleHead { min-width: 0; display: flex; align-items: center; gap: 4px; cursor: pointer; user-select: none; -webkit-user-select: none; }
|
|
420
|
-
.dim-collapsibleHead:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; border-radius: 8px; }
|
|
421
420
|
.dim-collapsibleHeaderContent { min-width: 0; flex: 1 1 auto; display: flex; align-items: center; }
|
|
422
|
-
|
|
423
|
-
.dim-
|
|
421
|
+
/* Keep the original header footprint; center both icons with equal side space. */
|
|
422
|
+
.dim-accountSettingsToggle { flex: none; width: 45px; height: 32px; display: inline-flex; gap: 5px; align-items: center; justify-content: center; padding: 0; border: 0; border-radius: 8px; color: var(--dsw-alias-state-business-primary, #3370ff); background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, var(--dsw-alias-bg-layer-1, #fff)); cursor: pointer; }
|
|
423
|
+
.dim-accountSettingsToggle > svg { flex: none; display: block; }
|
|
424
|
+
.dim-accountSettingsToggle:hover { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 14%, var(--dsw-alias-bg-layer-1, #fff)); }
|
|
425
|
+
.dim-accountSettingsToggle:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
426
|
+
.dim-collapsibleChevron { width: 12px; height: 16px; transition: transform .22s cubic-bezier(.4, 0, .2, 1); transform-origin: 50% 50%; }
|
|
427
|
+
.dim-accountSettingsToggle[aria-expanded="true"] .dim-collapsibleChevron { transform: rotate(180deg); }
|
|
428
|
+
.dim-accountSettingsHeader { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--dsw-alias-border-l2, #e5e6eb); color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
|
|
424
429
|
/* Animate height without measuring content; hide collapsed controls from focus and accessibility. */
|
|
425
430
|
.dim-collapsibleBody { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .22s cubic-bezier(.4, 0, .2, 1); }
|
|
426
431
|
.dim-collapsibleAccount.is-open > .dim-collapsibleBody { grid-template-rows: 1fr; }
|
|
@@ -445,11 +450,10 @@ const CSS = String.raw`
|
|
|
445
450
|
.dim-panel .dim-botCard .dim-healthDot[data-tone="warning"] { background: var(--dsw-alias-state-warn-primary, #d97706); }
|
|
446
451
|
.dim-panel .dim-botCard .dim-healthDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); }
|
|
447
452
|
.dim-botSettingsAction { position: relative; flex: none; display: inline-flex; }
|
|
448
|
-
.dim-botSettingsButton {
|
|
453
|
+
.dim-botSettingsButton { min-height: 32px; display: inline-flex; align-items: center; gap: 6px; padding: 0 6px; border: 0; border-radius: 8px; color: var(--dsw-alias-state-business-primary, #3370ff); background: transparent; font: inherit; font-size: 12px; white-space: nowrap; cursor: pointer; transition: color .15s ease, background .15s ease; }
|
|
449
454
|
.dim-botSettingsButton:hover { color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
|
450
455
|
.dim-botSettingsButton:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
451
|
-
.dim-
|
|
452
|
-
.dim-botSettingsAction:hover .dim-botSettingsTooltip, .dim-botSettingsAction:focus-within .dim-botSettingsTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
456
|
+
.dim-moreSettingsChevron { width: 6px; height: 6px; margin-right: 2px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); }
|
|
453
457
|
.dim-deliveryPage { min-width: 0; display: grid; }
|
|
454
458
|
.dim-deliveryHeader { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
|
455
459
|
.dim-botSettingsTabsBar { min-width: 0; margin-top: 10px; border-bottom: 1px solid var(--dsw-alias-border-l2, #dfe1e5); }
|
|
@@ -72,14 +72,17 @@ function summary(snapshot, action, error) {
|
|
|
72
72
|
if (snapshot?.job?.state === 'restart-required' || snapshot?.blockedReason === 'pending-restart') {
|
|
73
73
|
return '已安装,待手动重启';
|
|
74
74
|
}
|
|
75
|
-
if (snapshot?.job?.state === 'completed') return '更新已生效';
|
|
76
|
-
if (snapshot?.job?.state === 'failed') return '更新失败';
|
|
77
|
-
if (snapshot?.job?.state === 'interrupted') return '上次更新已中断,请检查安装状态后重试。';
|
|
78
75
|
if (error) return '更新请求失败';
|
|
76
|
+
if (!snapshot?.job?.recoverable) {
|
|
77
|
+
if (snapshot?.job?.state === 'failed') return '更新失败';
|
|
78
|
+
if (snapshot?.job?.state === 'interrupted') return '上次更新已中断,请检查安装状态后重试。';
|
|
79
|
+
}
|
|
80
|
+
if (snapshot?.blockedReason === 'no-update') return '当前版本无需更新';
|
|
81
|
+
if (snapshot?.blockedReason) return '暂时无法安装更新';
|
|
79
82
|
if (snapshot?.canInstall) return '发现新版本';
|
|
80
83
|
if (snapshot?.checkedAt && snapshot.latestVersion === snapshot.runningVersion) return '已是最新版本';
|
|
81
|
-
if (snapshot?.blockedReason === 'no-update') return '当前版本无需更新';
|
|
82
84
|
if (snapshot?.checkedAt) return '已获取 npm 最新版本';
|
|
85
|
+
if (snapshot?.job?.state === 'completed') return '更新已生效';
|
|
83
86
|
return '检查 npm 最新版本,不会自动安装。';
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -102,7 +105,7 @@ function manualUpdateCommand(snapshot) {
|
|
|
102
105
|
const targets = [
|
|
103
106
|
snapshot.latestVersion,
|
|
104
107
|
pendingRestart ? snapshot.installedVersion : null,
|
|
105
|
-
snapshot.job?.state !== 'completed' ? snapshot.job?.targetVersion : null,
|
|
108
|
+
snapshot.job?.state !== 'completed' && !snapshot.job?.recoverable ? snapshot.job?.targetVersion : null,
|
|
106
109
|
].filter(validVersion);
|
|
107
110
|
// Keep a known target pinned, and never suggest downgrading an installed/running version.
|
|
108
111
|
const version = targets.length
|
|
@@ -394,9 +397,12 @@ export function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
|
|
|
394
397
|
const blocked = BLOCKED_REASONS[snapshot?.blockedReason] ?? ERROR_MESSAGES[snapshot?.blockedReason];
|
|
395
398
|
const canConfirm = snapshot?.canInstall && snapshot.checkId && !activeJob && !restartRequired;
|
|
396
399
|
const versionsDiffer = snapshot && clientVersion && snapshot.runningVersion !== clientVersion;
|
|
397
|
-
const failedJob = ['failed', 'interrupted'].includes(snapshot?.job?.state);
|
|
400
|
+
const failedJob = ['failed', 'interrupted'].includes(snapshot?.job?.state) && !snapshot.job.recoverable;
|
|
398
401
|
const jobMessage = snapshot?.job?.message;
|
|
399
|
-
const targetVersion = snapshot?.job?.targetVersion;
|
|
402
|
+
const targetVersion = activeJob || restartRequired ? snapshot?.job?.targetVersion : null;
|
|
403
|
+
const historicalTargetVersion = !activeJob && !restartRequired ? snapshot?.job?.targetVersion : null;
|
|
404
|
+
const latestVersion = !activeJob && !restartRequired && !error && snapshot?.checkedAt
|
|
405
|
+
? snapshot.latestVersion : null;
|
|
400
406
|
const manualCommand = manualUpdateCommand(snapshot);
|
|
401
407
|
const buttonLabel = action === 'checking' ? '检查中…'
|
|
402
408
|
: action === 'starting' || activeJob ? '正在更新…'
|
|
@@ -413,7 +419,7 @@ export function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
|
|
|
413
419
|
onClick: () => {
|
|
414
420
|
setOpen(true);
|
|
415
421
|
if (restartRequired) void refreshStatus();
|
|
416
|
-
else if (!snapshot?.canInstall && !
|
|
422
|
+
else if (!snapshot?.canInstall && !activeJob && !uncertainInstall) void check();
|
|
417
423
|
},
|
|
418
424
|
}, buttonLabel, buttonLabel === '更新至' ? ` v${snapshot.latestVersion}` : null),
|
|
419
425
|
open ? h(UpdateDialog, { onClose: () => setOpen(false) },
|
|
@@ -425,6 +431,10 @@ export function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
|
|
|
425
431
|
h('dt', null, '已安装版本'), h('dd', null, `v${snapshot.installedVersion}`)) : null,
|
|
426
432
|
targetVersion ? h(React.Fragment, null,
|
|
427
433
|
h('dt', null, '目标版本'), h('dd', null, `v${targetVersion}`)) : null,
|
|
434
|
+
latestVersion ? h(React.Fragment, null,
|
|
435
|
+
h('dt', null, '最新版本'), h('dd', null, `v${latestVersion}`)) : null,
|
|
436
|
+
historicalTargetVersion ? h(React.Fragment, null,
|
|
437
|
+
h('dt', null, '上次更新目标'), h('dd', null, `v${historicalTargetVersion}`)) : null,
|
|
428
438
|
h('dt', null, '目标 profile'), h('dd', null, snapshot?.profileName ?? '无法确认')),
|
|
429
439
|
h('div', {
|
|
430
440
|
className: `dim-updateStatus${error || failedJob ? ' dim-updateStatusError' : ''}`,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
validAgentPresetPayload,
|
|
16
16
|
} from './agent-preset-rpc.mjs';
|
|
17
17
|
import { SET_MODEL_ENDPOINT, validModelPayload } from './model-setting-rpc.mjs';
|
|
18
|
+
import { SET_THINKING_TRACES_ENDPOINT, validThinkingTracesPayload } from './thinking-traces-rpc.mjs';
|
|
18
19
|
|
|
19
20
|
export const TOKEN_BOT_ENDPOINTS = Object.freeze({
|
|
20
21
|
status: 'connection.status',
|
|
@@ -27,6 +28,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
|
|
|
27
28
|
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
|
28
29
|
setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
|
|
29
30
|
setAlias: SET_ALIAS_ENDPOINT,
|
|
31
|
+
setThinkingTraces: SET_THINKING_TRACES_ENDPOINT,
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
const ENDPOINTS = Object.freeze(Object.values(TOKEN_BOT_ENDPOINTS));
|
|
@@ -97,6 +99,10 @@ function payloadFailure(endpoint, payload) {
|
|
|
97
99
|
return validAliasPayload(payload)
|
|
98
100
|
? null : '请输入有效的别名(最多 80 个字符)。';
|
|
99
101
|
}
|
|
102
|
+
if (endpoint === TOKEN_BOT_ENDPOINTS.setThinkingTraces) {
|
|
103
|
+
return validThinkingTracesPayload(payload)
|
|
104
|
+
? null : '请提交有效的思考过程留痕设置。';
|
|
105
|
+
}
|
|
100
106
|
return 'Unknown bot endpoint.';
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -194,6 +200,9 @@ export function createTokenBotRpcHandler(controller, { channel }) {
|
|
|
194
200
|
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setAgentPreset) {
|
|
195
201
|
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
|
196
202
|
value = await controller.updateAgentPreset(payload.botId, payload.agentPreset);
|
|
203
|
+
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setThinkingTraces) {
|
|
204
|
+
if (typeof controller.setThinkingTraces !== 'function') throw new Error('Thinking traces update is unavailable');
|
|
205
|
+
value = await controller.setThinkingTraces(payload.botId, payload.thinkingTraces);
|
|
197
206
|
} else {
|
|
198
207
|
value = await controller.deleteBot(payload.botId);
|
|
199
208
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const SET_THINKING_TRACES_ENDPOINT = 'bot.thinking-traces.set';
|
|
2
|
+
|
|
3
|
+
export function validThinkingTracesPayload(payload) {
|
|
4
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
5
|
+
|| Reflect.ownKeys(payload).length !== 2
|
|
6
|
+
|| !Object.hasOwn(payload, 'botId') || !Object.hasOwn(payload, 'thinkingTraces')
|
|
7
|
+
|| typeof payload.botId !== 'string'
|
|
8
|
+
|| !/^[A-Za-z0-9_-]{1,128}$/.test(payload.botId)
|
|
9
|
+
|| typeof payload.thinkingTraces !== 'boolean') return false;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
@@ -382,7 +382,7 @@ class ModernHarnessApi {
|
|
|
382
382
|
return;
|
|
383
383
|
}
|
|
384
384
|
if (frame.type !== 'chunk' || frame.index !== stream.entries.length) return;
|
|
385
|
-
|
|
385
|
+
const entry = {
|
|
386
386
|
type: 'transient',
|
|
387
387
|
event: {
|
|
388
388
|
type: 'assistant/chunk',
|
|
@@ -392,7 +392,12 @@ class ModernHarnessApi {
|
|
|
392
392
|
time: frame.time,
|
|
393
393
|
data: { turn: stream.turn, step: stream.step, chunk: frame.chunk },
|
|
394
394
|
},
|
|
395
|
-
}
|
|
395
|
+
};
|
|
396
|
+
stream.entries.push(entry);
|
|
397
|
+
// History only exposes these transient chunks while the attempt is open.
|
|
398
|
+
// Broadcast each one as it arrives so live IM renderers cannot miss a
|
|
399
|
+
// short reasoning phase that starts and commits between two history polls.
|
|
400
|
+
this.#broadcast({ type: 'session/event', sessionId, event: entry.event });
|
|
396
401
|
}
|
|
397
402
|
|
|
398
403
|
#withAssistantStream(sessionId, entries) {
|
|
@@ -113,7 +113,7 @@ function processAlive(pid) {
|
|
|
113
113
|
function publicJob(job) {
|
|
114
114
|
if (!job) return null;
|
|
115
115
|
const { id, state, targetVersion, message } = job;
|
|
116
|
-
return { id, state, targetVersion, message };
|
|
116
|
+
return { id, state, targetVersion, message, recoverable: job.recoverable === true };
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/** One update job per profile. Persist intent before starting pnpm, and never apply a restart here. */
|
|
@@ -158,27 +158,32 @@ export function createUpdateService({
|
|
|
158
158
|
if (!JOB_STATES.has(job.state) || typeof job.id !== 'string' || !semver.valid(job.targetVersion)) {
|
|
159
159
|
throw updateError('state-unavailable');
|
|
160
160
|
}
|
|
161
|
-
|
|
161
|
+
// Recovery is derived from the current installation, never a persisted flag.
|
|
162
|
+
job = { ...job, recoverable: false };
|
|
163
|
+
const historical = job.id !== activeJob?.id;
|
|
164
|
+
if (ACTIVE_STATES.has(job.state) && historical) {
|
|
162
165
|
if (lock === NO_LOCK || lock?.id !== job.id || !processAlive(lock?.pid)) {
|
|
163
166
|
job = { ...job, state: 'interrupted', message: 'recovery-required' };
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
// A later Host can retry after a verified manual repair. Never infer this
|
|
169
|
-
// from version equality while an old process lock is still present.
|
|
170
|
-
if (['failed', 'interrupted'].includes(job.state) && environment.packageValid === true
|
|
171
|
-
&& environment.installedVersion === runningVersion && !environment.blockedReason) {
|
|
172
|
-
return job.targetVersion === runningVersion
|
|
173
|
-
? { ...job, state: 'completed', message: 'recovered' }
|
|
174
|
-
: { ...job, recoverable: true };
|
|
175
|
-
}
|
|
169
|
+
if (historical && !ACTIVE_STATES.has(job.state) && lock !== NO_LOCK) {
|
|
170
|
+
return { ...job, state: 'interrupted', message: 'recovery-required' };
|
|
176
171
|
}
|
|
177
172
|
if (job.state === 'restart-required' || job.state === 'completed') {
|
|
178
173
|
if (environment.installedVersion !== job.targetVersion || environment.packageValid !== true) {
|
|
179
|
-
|
|
174
|
+
job = { ...job, state: 'interrupted', message: 'installation-changed' };
|
|
175
|
+
} else {
|
|
176
|
+
job = { ...job, state: runningVersion === job.targetVersion ? 'completed' : 'restart-required' };
|
|
180
177
|
}
|
|
181
|
-
|
|
178
|
+
}
|
|
179
|
+
// Reconcile after deriving the job state so an external upgrade or rollback
|
|
180
|
+
// can also recover. Keep history read-only and do not claim its target ran.
|
|
181
|
+
if (historical && lock === NO_LOCK && ['failed', 'interrupted'].includes(job.state)
|
|
182
|
+
&& environment.packageValid === true && environment.eligible === true
|
|
183
|
+
&& environment.installedVersion === runningVersion && !environment.blockedReason) {
|
|
184
|
+
return job.targetVersion === runningVersion
|
|
185
|
+
? { ...job, state: 'completed', message: 'recovered' }
|
|
186
|
+
: { ...job, recoverable: true };
|
|
182
187
|
}
|
|
183
188
|
return job;
|
|
184
189
|
}
|
|
@@ -142,22 +142,28 @@ if (client.includes('settings.plugins.tab') || clientSources.includes('settings.
|
|
|
142
142
|
throw new Error('client source or bundle still contains the legacy Plugins-tab settings entry');
|
|
143
143
|
}
|
|
144
144
|
// Connections still have no channel-enable toggle. Checkable inputs are owned
|
|
145
|
-
// only by the shared context editor
|
|
145
|
+
// only by the shared context editor, the saved-target Session sync row, the
|
|
146
|
+
// email settings, and the Telegram thinking-traces block.
|
|
146
147
|
// The context editor contains one switch template and one mapped field-input
|
|
147
|
-
// template; the delivery target adds one ordinary checkbox template
|
|
148
|
+
// template; the delivery target adds one ordinary checkbox template; the
|
|
149
|
+
// Telegram thinking-traces block adds one ordinary checkbox template.
|
|
148
150
|
const contextEditorSource = await readFile(resolve(root, 'plugin-src/client/context-enhancement.js'), 'utf8');
|
|
149
151
|
const deliverySettingsSource = await readFile(resolve(root, 'plugin-src/client/delivery-settings.js'), 'utf8');
|
|
150
152
|
const emailSettingsSource = await readFile(resolve(root, 'plugin-src/client/channels/email/index.js'), 'utf8');
|
|
153
|
+
const thinkingTracesSource = await readFile(resolve(root, 'plugin-src/client/channels/telegram/thinking-traces.js'), 'utf8');
|
|
151
154
|
const otherClientSources = clientSources
|
|
152
155
|
.replace(contextEditorSource, '')
|
|
153
156
|
.replace(deliverySettingsSource, '')
|
|
154
|
-
.replace(emailSettingsSource, '')
|
|
157
|
+
.replace(emailSettingsSource, '')
|
|
158
|
+
.replace(thinkingTracesSource, '');
|
|
155
159
|
if (/role:\s*["']switch|type:\s*["']checkbox/.test(otherClientSources)
|
|
156
160
|
|| (deliverySettingsSource.match(/type:\s*["']checkbox["']/g) ?? []).length !== 1
|
|
157
161
|
|| /role:\s*["']switch["']/u.test(deliverySettingsSource)
|
|
162
|
+
|| (thinkingTracesSource.match(/type:\s*["']checkbox["']/g) ?? []).length !== 1
|
|
163
|
+
|| /role:\s*["']switch["']/u.test(thinkingTracesSource)
|
|
158
164
|
|| (client.match(/role:\s*["']switch["']/g) ?? []).length !== 1
|
|
159
|
-
|| (client.match(/type:\s*["']checkbox["']/g) ?? []).length !==
|
|
160
|
-
throw new Error('checkable inputs must be limited to context enhancement and
|
|
165
|
+
|| (client.match(/type:\s*["']checkbox["']/g) ?? []).length !== 4) {
|
|
166
|
+
throw new Error('checkable inputs must be limited to context enhancement, Session sync, email settings, and the Telegram thinking-traces toggle');
|
|
161
167
|
}
|
|
162
168
|
for (const marker of ['bot.context-enhancement.set', '<dsh_im_source>', '<dsh_im_source_guidance>']) {
|
|
163
169
|
if (!host.includes(marker) || !client.includes(marker)) {
|
|
@@ -200,8 +200,15 @@ export function normalizeEmail(parsed, { address, state } = {}) {
|
|
|
200
200
|
// attachment type under any other key is silently dropped.
|
|
201
201
|
...(attachment.contentType ? { mediaType: String(attachment.contentType) } : {}),
|
|
202
202
|
// The bridge streams files via a loader so large attachments are not
|
|
203
|
-
// held in memory until they are actually needed.
|
|
204
|
-
|
|
203
|
+
// held in memory until they are actually needed. Transports differ in
|
|
204
|
+
// what `content` is: IMAP hands over a Buffer (already fetched with the
|
|
205
|
+
// body), while the Agent mailbox can only fetch bytes on demand and so
|
|
206
|
+
// exposes a function. Returning that function unchanged made the loader
|
|
207
|
+
// resolve to a function, which the inbound-file layer rejects as
|
|
208
|
+
// `inbound-file-data-invalid` — the download never happened.
|
|
209
|
+
load: async () => (typeof attachment.content === 'function'
|
|
210
|
+
? attachment.content()
|
|
211
|
+
: attachment.content),
|
|
205
212
|
})),
|
|
206
213
|
reactionTarget: null,
|
|
207
214
|
replyTarget: {
|
|
@@ -301,10 +301,21 @@ export class AgentMailTransport {
|
|
|
301
301
|
this.#connected = false;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* The newest id, used to seed a cursor.
|
|
306
|
+
*
|
|
307
|
+
* Reads the list summary only. Going through `listMessages` without an
|
|
308
|
+
* allowlist meant "no filter", so seeding a cursor downloaded the body of the
|
|
309
|
+
* newest message — mail the policy may well refuse, fetched before anyone
|
|
310
|
+
* asked for it. The newest id is in the first summary already.
|
|
311
|
+
*/
|
|
305
312
|
async latestUid() {
|
|
306
|
-
const
|
|
307
|
-
|
|
313
|
+
const { document } = await this.#call(
|
|
314
|
+
['message', '+list', '--dir', INBOX, '--limit', '1'],
|
|
315
|
+
{ signal: this.#signal },
|
|
316
|
+
);
|
|
317
|
+
const items = Array.isArray(document?.data?.data) ? document.data.data : [];
|
|
318
|
+
return String(items[0]?.message_id ?? '').trim() || 0;
|
|
308
319
|
}
|
|
309
320
|
|
|
310
321
|
/**
|