dsh-harmonyos-hiboard 0.8.0 → 0.9.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/package.json +1 -1
- package/src/lib/client.js +91 -23
- package/src/lib/index.js +44 -3
- package/src/lib/push-guard.js +48 -17
package/package.json
CHANGED
package/src/lib/client.js
CHANGED
|
@@ -95,12 +95,19 @@ window.__ModuleLoader__.load({
|
|
|
95
95
|
guardBudget: function (g) {
|
|
96
96
|
return '重试预算:确定未送达 ' + g.retryCount + ' 次 / 可能已送达 ' + g.ambiguousRetryCount + ' 次 · schedule_id 兜底:' + g.defaultScheduleId;
|
|
97
97
|
},
|
|
98
|
-
ruleTitle: '
|
|
98
|
+
ruleTitle: '每轮对话推送',
|
|
99
99
|
ruleOn: '已启用',
|
|
100
100
|
ruleOff: '未启用',
|
|
101
101
|
ruleHint: '保存授权码时,会自动把「每轮都要推送」写进全局指令文件 AGENTS.md(带标记、可随时移除),新会话即生效。',
|
|
102
102
|
ruleCustom: '检测到你已手写过推送规则 —— 插件不会改动你的 AGENTS.md。',
|
|
103
103
|
ruleBlockPresent: 'AGENTS.md 里已有本插件维护的规则段(带标记;关掉即可移除)。',
|
|
104
|
+
sourceTitle: '推送源名称',
|
|
105
|
+
sourceCurrent: function (v) {
|
|
106
|
+
return '当前生效:' + v;
|
|
107
|
+
},
|
|
108
|
+
sourcePlaceholder: 'DeepSeek Harness',
|
|
109
|
+
sourceHint: '负一屏卡片上显示的「来源 / AI 工具名」。改这里,本机所有推送(含启动卡片)会一起改用同一个名称。输入框留空并保存即恢复默认。',
|
|
110
|
+
sourceReset: '恢复默认',
|
|
104
111
|
debugTitle: '兜底战绩',
|
|
105
112
|
debugSummary: function (c) {
|
|
106
113
|
return '已救回 ' + (c.retryOk + c.rescueOk) + ' 次(重试 ' + c.retryOk + ' · 补推 ' + c.rescueOk + ')';
|
|
@@ -144,12 +151,19 @@ window.__ModuleLoader__.load({
|
|
|
144
151
|
guardBudget: function (g) {
|
|
145
152
|
return 'Retry budget: definitely-undelivered ' + g.retryCount + ' / possibly-delivered ' + g.ambiguousRetryCount + ' · schedule_id fallback: ' + g.defaultScheduleId;
|
|
146
153
|
},
|
|
147
|
-
ruleTitle: '
|
|
154
|
+
ruleTitle: 'Push every turn',
|
|
148
155
|
ruleOn: 'enabled',
|
|
149
156
|
ruleOff: 'disabled',
|
|
150
157
|
ruleHint: 'Saving the authCode also writes an "every turn must push" rule into your global instruction file AGENTS.md (marker-delimited, removable); new sessions pick it up.',
|
|
151
158
|
ruleCustom: 'You already wrote a push rule by hand, so this plugin leaves AGENTS.md untouched.',
|
|
152
159
|
ruleBlockPresent: "AGENTS.md already carries this plugin's rule block (marker-delimited; switch this off to remove it).",
|
|
160
|
+
sourceTitle: 'Push source name',
|
|
161
|
+
sourceCurrent: function (v) {
|
|
162
|
+
return 'Currently: ' + v;
|
|
163
|
+
},
|
|
164
|
+
sourcePlaceholder: 'DeepSeek Harness',
|
|
165
|
+
sourceHint: 'The brand / AI tool name shown on the card. Changing it moves every push on this machine (including the startup card) to the same name. Save an empty value to restore the default.',
|
|
166
|
+
sourceReset: 'Reset',
|
|
153
167
|
debugTitle: 'Rescues so far',
|
|
154
168
|
debugSummary: function (c) {
|
|
155
169
|
return 'Saved ' + (c.retryOk + c.rescueOk) + ' (retries ' + c.retryOk + ', pushes ' + c.rescueOk + ')';
|
|
@@ -300,6 +314,26 @@ window.__ModuleLoader__.load({
|
|
|
300
314
|
setNote((data.notes && data.notes[0]) || '');
|
|
301
315
|
});
|
|
302
316
|
};
|
|
317
|
+
var sourceState = useState('');
|
|
318
|
+
var sourceDraft = sourceState[0];
|
|
319
|
+
var setSourceDraft = sourceState[1];
|
|
320
|
+
var onSaveSource = function () {
|
|
321
|
+
setBusy('source');
|
|
322
|
+
setNote('');
|
|
323
|
+
post({ source: sourceDraft }).then(function (data) {
|
|
324
|
+
setBusy('');
|
|
325
|
+
if (data && data.notes && data.notes[0]) setNote(data.notes[0]);
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
var onResetSource = function () {
|
|
329
|
+
setSourceDraft('');
|
|
330
|
+
setBusy('source');
|
|
331
|
+
setNote('');
|
|
332
|
+
post({ source: '' }).then(function (data) {
|
|
333
|
+
setBusy('');
|
|
334
|
+
if (data && data.notes && data.notes[0]) setNote(data.notes[0]);
|
|
335
|
+
});
|
|
336
|
+
};
|
|
303
337
|
var onToggleRule = function (next) {
|
|
304
338
|
setBusy('rule');
|
|
305
339
|
setNote('');
|
|
@@ -344,7 +378,7 @@ window.__ModuleLoader__.load({
|
|
|
344
378
|
disabled: busy === 'auth',
|
|
345
379
|
onChange: function (event) { setDraft(event.target.value); },
|
|
346
380
|
onKeyDown: function (event) { if (event.key === 'Enter') onSave(); },
|
|
347
|
-
style: { flex: '1 1
|
|
381
|
+
style: { flex: '1 1 300px', minWidth: '300px' }
|
|
348
382
|
});
|
|
349
383
|
|
|
350
384
|
var preview = null;
|
|
@@ -446,6 +480,60 @@ window.__ModuleLoader__.load({
|
|
|
446
480
|
)
|
|
447
481
|
: null,
|
|
448
482
|
|
|
483
|
+
// ---- 每轮对话推送(保存授权码时自动写进全局指令文件)----
|
|
484
|
+
h(
|
|
485
|
+
'div',
|
|
486
|
+
{ style: { marginTop: '14px', paddingTop: '12px', borderTop: '1px solid ' + V.border } },
|
|
487
|
+
row([
|
|
488
|
+
h('span', { style: { fontWeight: 600 } }, t.ruleTitle),
|
|
489
|
+
h('span', { style: { color: V.sub } }, rule && rule.enabled ? t.ruleOn : t.ruleOff),
|
|
490
|
+
h(Switch, {
|
|
491
|
+
checked: Boolean(rule && rule.enabled),
|
|
492
|
+
onChange: onToggleRule,
|
|
493
|
+
label: t.ruleTitle,
|
|
494
|
+
disabled: busy === 'rule' || !rule
|
|
495
|
+
})
|
|
496
|
+
]),
|
|
497
|
+
muted(
|
|
498
|
+
rule && rule.customRule ? t.ruleCustom : rule && rule.block ? t.ruleBlockPresent : t.ruleHint,
|
|
499
|
+
{ marginTop: '6px' }
|
|
500
|
+
)
|
|
501
|
+
),
|
|
502
|
+
|
|
503
|
+
// ---- 推送源名称:改一处,本机所有推送一起改 ----
|
|
504
|
+
h(
|
|
505
|
+
'div',
|
|
506
|
+
{ style: { marginTop: '14px', paddingTop: '12px', borderTop: '1px solid ' + V.border } },
|
|
507
|
+
row([
|
|
508
|
+
h('span', { style: { fontWeight: 600 } }, t.sourceTitle),
|
|
509
|
+
h(
|
|
510
|
+
'span',
|
|
511
|
+
{ style: { color: V.sub } },
|
|
512
|
+
t.sourceCurrent(String((summary.data && summary.data.source) || t.sourcePlaceholder))
|
|
513
|
+
)
|
|
514
|
+
]),
|
|
515
|
+
h(
|
|
516
|
+
'div',
|
|
517
|
+
{ style: { display: 'flex', gap: '8px', flexWrap: 'wrap', alignItems: 'center', marginTop: '6px' } },
|
|
518
|
+
[
|
|
519
|
+
h(Input, {
|
|
520
|
+
type: 'text',
|
|
521
|
+
value: sourceDraft,
|
|
522
|
+
placeholder: t.sourcePlaceholder,
|
|
523
|
+
spellCheck: false,
|
|
524
|
+
autoComplete: 'off',
|
|
525
|
+
disabled: busy === 'source',
|
|
526
|
+
onChange: function (event) { setSourceDraft(event.target.value); },
|
|
527
|
+
onKeyDown: function (event) { if (event.key === 'Enter') onSaveSource(); },
|
|
528
|
+
style: { flex: '1 1 300px', minWidth: '300px' }
|
|
529
|
+
}),
|
|
530
|
+
h(Button, { variant: 'primary', size: 'sm', onClick: onSaveSource, disabled: busy === 'source' }, t.save),
|
|
531
|
+
h(Button, { variant: 'ghost', size: 'sm', onClick: onResetSource, disabled: busy === 'source' }, t.sourceReset)
|
|
532
|
+
]
|
|
533
|
+
),
|
|
534
|
+
muted(t.sourceHint, { marginTop: '6px' })
|
|
535
|
+
),
|
|
536
|
+
|
|
449
537
|
// ---- 推送兜底 + 调试图 ----
|
|
450
538
|
h(
|
|
451
539
|
'div',
|
|
@@ -483,26 +571,6 @@ window.__ModuleLoader__.load({
|
|
|
483
571
|
showDebug ? h('div', { style: { marginTop: '4px' } }, debugBody) : null
|
|
484
572
|
)
|
|
485
573
|
),
|
|
486
|
-
|
|
487
|
-
// ---- 常驻推送规则:保存授权码时自动写进全局指令文件 ----
|
|
488
|
-
h(
|
|
489
|
-
'div',
|
|
490
|
-
{ style: { marginTop: '14px', paddingTop: '12px', borderTop: '1px solid ' + V.border } },
|
|
491
|
-
row([
|
|
492
|
-
h('span', { style: { fontWeight: 600 } }, t.ruleTitle),
|
|
493
|
-
h('span', { style: { color: V.sub } }, rule && rule.enabled ? t.ruleOn : t.ruleOff),
|
|
494
|
-
h(Switch, {
|
|
495
|
-
checked: Boolean(rule && rule.enabled),
|
|
496
|
-
onChange: onToggleRule,
|
|
497
|
-
label: t.ruleTitle,
|
|
498
|
-
disabled: busy === 'rule' || !rule
|
|
499
|
-
})
|
|
500
|
-
]),
|
|
501
|
-
muted(
|
|
502
|
-
rule && rule.customRule ? t.ruleCustom : rule && rule.block ? t.ruleBlockPresent : t.ruleHint,
|
|
503
|
-
{ marginTop: '6px' }
|
|
504
|
-
)
|
|
505
|
-
)
|
|
506
574
|
);
|
|
507
575
|
};
|
|
508
576
|
}
|
package/src/lib/index.js
CHANGED
|
@@ -61,7 +61,19 @@ const DEFAULT_RESULT = "任务已完成";
|
|
|
61
61
|
* name); the platform accepts other values, but rendering is platform-defined,
|
|
62
62
|
* so check a real card after changing it.
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
/**
|
|
65
|
+
* The HIBoard contract brands cards by this value — the phone renders it as the
|
|
66
|
+
* card's brand / tool name. It defaults to `"DeepSeek Harness"` (this project's
|
|
67
|
+
* branding; the official today-task skill and hwpush send `"OpenClaw"` for the
|
|
68
|
+
* same field, and the platform accepts either — rendering of a custom value is
|
|
69
|
+
* platform-defined, so look at a real card after changing it).
|
|
70
|
+
*
|
|
71
|
+
* Overridable via the `source` config option, which the settings card exposes as
|
|
72
|
+
* 「推送源名称」. Other local pushers (e.g. the startup/token card in
|
|
73
|
+
* `dsh-token-broadcast`) read this same value from the plugin's config file, so
|
|
74
|
+
* one edit moves the whole machine to one brand.
|
|
75
|
+
*/
|
|
76
|
+
const WIRE_SOURCE = "DeepSeek Harness";
|
|
65
77
|
|
|
66
78
|
/** Environment fallback for the authCode. */
|
|
67
79
|
const AUTH_CODE_ENV = "DSH_HIBOARD_AUTH_CODE";
|
|
@@ -454,7 +466,7 @@ function hiboardVerifyTool({ resolveAuthCode, settings }) {
|
|
|
454
466
|
* 不继承 dsh Connection 的登录认证,本插件也不自己做 Host / Origin 策略。
|
|
455
467
|
* 部署时鉴权必须同时覆盖本路由与 dsh 本身;不想暴露就把 settingsCard 设为 false。
|
|
456
468
|
*/
|
|
457
|
-
function buildConfigRoute({ authCodeInfo, settings, storeFile }) {
|
|
469
|
+
function buildConfigRoute({ authCodeInfo, settings, storeFile, selfcheckReport }) {
|
|
458
470
|
const SOURCE_LABEL = {
|
|
459
471
|
settings: "设置页(本插件配置)",
|
|
460
472
|
entry: "profile 补丁层 hiboard-push.config.authCode",
|
|
@@ -557,6 +569,10 @@ function buildConfigRoute({ authCodeInfo, settings, storeFile }) {
|
|
|
557
569
|
defaultScheduleId: s.defaultScheduleId
|
|
558
570
|
},
|
|
559
571
|
endpoint: s.pushServiceUrl,
|
|
572
|
+
// 推送源名称:卡片上显示的"来源 / AI 工具名"
|
|
573
|
+
source: s.source,
|
|
574
|
+
// 自检工具是否真的注册上了(失败会被回显,不再静默)
|
|
575
|
+
selfcheck: selfcheckReport(),
|
|
560
576
|
storeFile,
|
|
561
577
|
// 调试图:兜底到底把多少次「原本失败的推送」救了回来
|
|
562
578
|
audit: auditSummary(),
|
|
@@ -639,6 +655,13 @@ function buildConfigRoute({ authCodeInfo, settings, storeFile }) {
|
|
|
639
655
|
const text = String(body.defaultScheduleId ?? "").trim();
|
|
640
656
|
patch.defaultScheduleId = text || null;
|
|
641
657
|
}
|
|
658
|
+
// ---- 推送源名称(source):卡片上显示的"来源 / AI 工具名" ----
|
|
659
|
+
// 空串 → 清除覆盖,回退到默认 "DeepSeek Harness"
|
|
660
|
+
if (Object.prototype.hasOwnProperty.call(body, "source")) {
|
|
661
|
+
const text = String(body.source ?? "").trim();
|
|
662
|
+
patch.source = text || null;
|
|
663
|
+
notes.push(text ? `推送源名称已改为「${text}」,本机所有推送都会用它。` : "已恢复默认推送源名称(DeepSeek Harness)。");
|
|
664
|
+
}
|
|
642
665
|
|
|
643
666
|
if (Object.keys(patch).length === 0) {
|
|
644
667
|
send(400, { error: "没有可保存的字段" });
|
|
@@ -730,8 +753,13 @@ function apply(ctx, rawConfig) {
|
|
|
730
753
|
ctx.tools.register(hiboardPushTool({ resolveAuthCode, settings }));
|
|
731
754
|
ctx.tools.register(hiboardVerifyTool({ resolveAuthCode, settings }));
|
|
732
755
|
|
|
756
|
+
/** 自检工具的注册结果,由兜底回调写入(见下面的 selfcheckReport)。 */
|
|
757
|
+
let selfcheckStatus = { ok: null, error: "" };
|
|
758
|
+
|
|
733
759
|
// ---- 推送兜底(原 dsh-push-guard,现并入本包):默认开启,可实时关闭 ----
|
|
734
760
|
applyPushGuard(ctx, {
|
|
761
|
+
// 注册成败都要回显:失败原本只写了一条**看不见的** warn
|
|
762
|
+
onSelfcheckRegister: (result) => { selfcheckStatus = result; },
|
|
735
763
|
// 静态值:只有把 guardEnabled:false 写死在配置里,才会整个不加载
|
|
736
764
|
enabled: entry.guardEnabled !== false,
|
|
737
765
|
// 实时值:每次判定都重新读,设置页一关就立即停
|
|
@@ -745,6 +773,8 @@ function apply(ctx, rawConfig) {
|
|
|
745
773
|
// 让兜底走"实时解析"而不是把挂载时的值钉死,
|
|
746
774
|
// 否则设置页里新存的授权码要等重启才对兜底生效。
|
|
747
775
|
resolveAuthCode,
|
|
776
|
+
// 同理实时解析:设置页改了「推送源名称」,兜底补推的卡片也要立刻跟着变
|
|
777
|
+
resolveSource: () => settings().source,
|
|
748
778
|
pushServiceUrl: settings().pushServiceUrl,
|
|
749
779
|
maxContentLength: settings().maxContentLength,
|
|
750
780
|
defaultResult: settings().defaultResult,
|
|
@@ -754,12 +784,23 @@ function apply(ctx, rawConfig) {
|
|
|
754
784
|
source: settings().source
|
|
755
785
|
});
|
|
756
786
|
|
|
787
|
+
/**
|
|
788
|
+
* 自检工具的注册状态(**回显给设置页**)。
|
|
789
|
+
* `registered` 取实时查询 —— 即便注册被延迟或撤下也能如实反映;
|
|
790
|
+
* `error` 是注册时捕获到的原因。没有它,注册失败就只剩一条无处可见的 warn:
|
|
791
|
+
* 本机 `~/.dsh/logs` 下没有该进程的日志文件(2026-10-13 验收报告第 5 节)。
|
|
792
|
+
*/
|
|
793
|
+
const selfcheckReport = () => ({
|
|
794
|
+
registered: Boolean(ctx.tools?.get?.("hiboard_push_selfcheck")),
|
|
795
|
+
error: selfcheckStatus.error || ""
|
|
796
|
+
});
|
|
797
|
+
|
|
757
798
|
// ---- 设置页路由 ----
|
|
758
799
|
// webServer 只在 web profile 存在,而 cordis 没有"可选注入"的写法,
|
|
759
800
|
// 所以走一次 scoped inject:服务不在时闭包根本不执行,插件照常加载。
|
|
760
801
|
if (settings().settingsCard && typeof ctx.inject === "function") {
|
|
761
802
|
ctx.inject(["webServer"], (scope) => {
|
|
762
|
-
const route = buildConfigRoute({ authCodeInfo, settings, storeFile });
|
|
803
|
+
const route = buildConfigRoute({ authCodeInfo, settings, storeFile, selfcheckReport });
|
|
763
804
|
// 挂在 effect 上:插件被关掉时路由一起撤下,否则它会继续为一个
|
|
764
805
|
// 已关闭的插件读写配置(modsearch 踩过这个坑,见其 dsh/index.js 注释)。
|
|
765
806
|
scope.effect(() => scope.webServer.register(route), "hiboard-push: settings route");
|
package/src/lib/push-guard.js
CHANGED
|
@@ -83,7 +83,7 @@ const DEFAULTS = {
|
|
|
83
83
|
retryCount: 3,
|
|
84
84
|
ambiguousRetryCount: 1,
|
|
85
85
|
defaultResult: "任务已完成",
|
|
86
|
-
source: "
|
|
86
|
+
source: "DeepSeek Harness",
|
|
87
87
|
defaultScheduleId: "dsh_worklog",
|
|
88
88
|
stateDir: "",
|
|
89
89
|
minPromiseLength: 120,
|
|
@@ -123,7 +123,8 @@ export const Config = z.object({
|
|
|
123
123
|
defaultResult: z.string().default(DEFAULTS.defaultResult),
|
|
124
124
|
/**
|
|
125
125
|
* 卡片 `source` 字段 —— 就是负一屏上那个**来源 / AI 工具名**的品牌化渲染值。
|
|
126
|
-
* 默认 `"
|
|
126
|
+
* 默认 `"DeepSeek Harness"`(本项目品牌)。官方 `today-task` skill 与 `hwpush` 在同一
|
|
127
|
+
* 字段上发的是 `"OpenClaw"`,平台两者都接受。可在设置卡片「推送源名称」里改。
|
|
127
128
|
* 实测平台**接受**任意值(自定义值同样返回 `0000000000`),但渲染样式由平台决定,
|
|
128
129
|
* 换值后请先在真机上看一眼卡片再长期使用。
|
|
129
130
|
*/
|
|
@@ -488,7 +489,13 @@ export function apply(ctx, config) {
|
|
|
488
489
|
).trim();
|
|
489
490
|
const pushUrl = String(settings.pushServiceUrl || DEFAULTS.pushServiceUrl);
|
|
490
491
|
const maxLen = Number(settings.maxContentLength) > 0 ? Number(settings.maxContentLength) : DEFAULTS.maxContentLength;
|
|
491
|
-
|
|
492
|
+
/**
|
|
493
|
+
* 卡片来源(负一屏上的"来源 / AI 工具名")**实时解析**:设置页改一次,
|
|
494
|
+
* 之后**所有**卡片(含兜底补推的那张)都用新值,而不是钉在挂载那一刻。
|
|
495
|
+
*/
|
|
496
|
+
const resolveCardSource = () =>
|
|
497
|
+
String((typeof settings.resolveSource === "function" ? settings.resolveSource() : settings.source) ?? "").trim() ||
|
|
498
|
+
DEFAULTS.source;
|
|
492
499
|
const retryBudget = Math.max(0, Number(settings.retryCount) >= 0 ? Number(settings.retryCount) : DEFAULTS.retryCount);
|
|
493
500
|
const ambiguousBudget = Math.max(0, Number(settings.ambiguousRetryCount) >= 0 ? Number(settings.ambiguousRetryCount) : DEFAULTS.ambiguousRetryCount);
|
|
494
501
|
const stateDir = String(settings.stateDir || "").trim() || join(homedir(), ".dsh", "lan", "push-guard");
|
|
@@ -514,7 +521,7 @@ export function apply(ctx, config) {
|
|
|
514
521
|
retryEnabled: settings.retryEnabled !== false,
|
|
515
522
|
retryCount: retryBudget,
|
|
516
523
|
ambiguousRetryCount: ambiguousBudget,
|
|
517
|
-
source:
|
|
524
|
+
source: resolveCardSource(),
|
|
518
525
|
defaultScheduleId: String(settings.defaultScheduleId || DEFAULTS.defaultScheduleId),
|
|
519
526
|
stateDir
|
|
520
527
|
};
|
|
@@ -522,7 +529,7 @@ export function apply(ctx, config) {
|
|
|
522
529
|
ctx.logger?.info?.(
|
|
523
530
|
"[push-guard] 启动自检:authCode=%s;L1 抢救=%s,L2 承诺兜底=%s,L3 重试=%s(确定未送达 %d 次 / 可能已送达 %d 次);卡片来源=%s;schedule_id 兜底=%s;状态目录=%s",
|
|
524
531
|
startup.authCode, startup.rescueEnabled, startup.promiseRescueEnabled, startup.retryEnabled,
|
|
525
|
-
retryBudget, ambiguousBudget,
|
|
532
|
+
retryBudget, ambiguousBudget, resolveCardSource(), startup.defaultScheduleId, stateDir
|
|
526
533
|
);
|
|
527
534
|
if (startup.authCode === "MISSING") {
|
|
528
535
|
ctx.logger?.warn?.("[push-guard] ⚠️ 未配置 authCode,任何兜底推送都会失败(请检查 hiboard-push / push-guard 配置)");
|
|
@@ -618,7 +625,7 @@ export function apply(ctx, config) {
|
|
|
618
625
|
// 兜底卡片同样必须是"能点开"的:schedule_id 为空时用默认分组
|
|
619
626
|
defaultScheduleId: String(settings.defaultScheduleId || DEFAULTS.defaultScheduleId),
|
|
620
627
|
// 兜底卡片与正常卡片用同一个来源名(负一屏上显示的 AI 工具名)
|
|
621
|
-
source:
|
|
628
|
+
source: resolveCardSource(),
|
|
622
629
|
stateDir,
|
|
623
630
|
audit
|
|
624
631
|
});
|
|
@@ -776,16 +783,26 @@ export function apply(ctx, config) {
|
|
|
776
783
|
schema: {
|
|
777
784
|
type: "object",
|
|
778
785
|
additionalProperties: false,
|
|
786
|
+
// ⚠️ `required` 必须写在**对象层**,不能写进每个属性节点。
|
|
787
|
+
// 本工具没有走 defineTool()(那个会把 DSL 编译掉),而是把裸对象直接
|
|
788
|
+
// 交给 ctx.tools.register(),于是要过 dsh-tools 的
|
|
789
|
+
// assertSupportedJsonSchema —— 它只允许 required 出现在 type:"object" 上。
|
|
790
|
+
// 写成属性级 `required: true` 会抛 JsonSchemaError,而被下面的 catch 吞掉,
|
|
791
|
+
// 表现为"自检工具根本不存在"且毫无提示(2026-10-13 验收实测)。
|
|
792
|
+
required: [
|
|
793
|
+
"guardActive", "hooked", "authCode", "source", "retryBudget",
|
|
794
|
+
"endpoint", "endpointReachable", "lastState", "auditTail"
|
|
795
|
+
],
|
|
779
796
|
properties: {
|
|
780
|
-
guardActive: { type: "boolean"
|
|
781
|
-
hooked: { type: "boolean"
|
|
782
|
-
authCode: { type: "string"
|
|
783
|
-
source: { type: "string"
|
|
784
|
-
retryBudget: { type: "string"
|
|
785
|
-
endpoint: { type: "string"
|
|
786
|
-
endpointReachable: { type: "boolean"
|
|
787
|
-
lastState: { type: "string"
|
|
788
|
-
auditTail: { type: "string"
|
|
797
|
+
guardActive: { type: "boolean" },
|
|
798
|
+
hooked: { type: "boolean" },
|
|
799
|
+
authCode: { type: "string" },
|
|
800
|
+
source: { type: "string" },
|
|
801
|
+
retryBudget: { type: "string" },
|
|
802
|
+
endpoint: { type: "string" },
|
|
803
|
+
endpointReachable: { type: "boolean" },
|
|
804
|
+
lastState: { type: "string" },
|
|
805
|
+
auditTail: { type: "string" }
|
|
789
806
|
}
|
|
790
807
|
},
|
|
791
808
|
render(_args, value) {
|
|
@@ -832,7 +849,7 @@ export function apply(ctx, config) {
|
|
|
832
849
|
guardActive: !guardOff(),
|
|
833
850
|
hooked,
|
|
834
851
|
authCode: resolveAuthCode() ? "已配置" : "未配置(推送会失败)",
|
|
835
|
-
source:
|
|
852
|
+
source: resolveCardSource(),
|
|
836
853
|
retryBudget: `确定未送达 ${retryBudget} 次 / 可能已送达 ${ambiguousBudget} 次`,
|
|
837
854
|
endpoint: pushUrl,
|
|
838
855
|
endpointReachable: reachable,
|
|
@@ -841,10 +858,24 @@ export function apply(ctx, config) {
|
|
|
841
858
|
};
|
|
842
859
|
}
|
|
843
860
|
});
|
|
861
|
+
try {
|
|
862
|
+
settings.onSelfcheckRegister?.({ ok: true, error: "" });
|
|
863
|
+
} catch {
|
|
864
|
+
/* 上报失败不影响工具本身 */
|
|
865
|
+
}
|
|
844
866
|
return () => off();
|
|
845
867
|
}, "push-guard: self-check tool");
|
|
846
868
|
} catch (error) {
|
|
847
|
-
|
|
869
|
+
const message = String(error?.message ?? error);
|
|
870
|
+
ctx.logger?.warn?.("[push-guard] 注册自检工具失败:%s", message);
|
|
871
|
+
// **不再静默**:把失败回显给宿主,设置页会在 /hiboard-push/config 里报
|
|
872
|
+
// selfcheck.registered=false。本机 ~/.dsh/logs 下没有该进程的日志文件,warn
|
|
873
|
+
// 是无处可见的 —— 2026-10-13 的验收就是因为这一点完全无感(报告第 5 节)。
|
|
874
|
+
try {
|
|
875
|
+
settings.onSelfcheckRegister?.({ ok: false, error: message });
|
|
876
|
+
} catch {
|
|
877
|
+
/* 上报失败不该影响其它功能 */
|
|
878
|
+
}
|
|
848
879
|
}
|
|
849
880
|
}
|
|
850
881
|
|