dsh-harmonyos-hiboard 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/package.json +2 -2
- package/src/lib/client.js +48 -16
- package/src/lib/index.js +22 -3
- package/src/lib/push-guard.js +47 -4
package/README.md
CHANGED
|
@@ -223,6 +223,11 @@ L2 抢救"承诺了却没调用"、L3 按错误类别重试并自动补 `schedul
|
|
|
223
223
|
- `retry-ok`:工具真的调用了但失败,按错误类别重试后成功
|
|
224
224
|
- `rescue-ok`:根本没调用工具(写成纯文本 / 只承诺没做),兜底代为补推
|
|
225
225
|
|
|
226
|
+
**关掉兜底后面板不会消失**,而是改显示「**漏掉未推送 N 次**」——
|
|
227
|
+
兜底关闭期间它不再补推,但**继续记账**(记成 `missed`):本该补推的(写成纯文本 /
|
|
228
|
+
只承诺没调用)以及调用失败却没重试的,都会被记下来。
|
|
229
|
+
关掉它的代价应该看得见,而不是等你发现卡片少了几张。
|
|
230
|
+
|
|
226
231
|
这个面板的意义是:以前"保险丝到底有没有干活"只能靠猜,现在直接列给你看。
|
|
227
232
|
|
|
228
233
|
配置写在 `<用户目录>/.dsh/hiboard-push/config.json`(0600,先写临时文件再原子替换)。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-harmonyos-hiboard",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "把DSH
|
|
3
|
+
"version": "0.7.2",
|
|
4
|
+
"description": "把DSH的任务结果,以服务卡片形式推送到华为鸿蒙负一屏 | Push DSH task results to the Huawei HarmonyOS assistant-today screen as service cards.",
|
|
5
5
|
"author": "Jim Chen",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
package/src/lib/client.js
CHANGED
|
@@ -101,8 +101,13 @@ window.__ModuleLoader__.load({
|
|
|
101
101
|
},
|
|
102
102
|
debugEmpty: '还没有需要兜底的记录 —— 每次推送都成功调用了工具。',
|
|
103
103
|
debugNone: '兜底还没有运行记录。',
|
|
104
|
+
debugMissedSummary: function (c) {
|
|
105
|
+
return '兜底已关闭 —— 期间漏掉未推送 ' + c.missed + ' 次';
|
|
106
|
+
},
|
|
107
|
+
debugMissedEmpty: '兜底关闭期间还没有漏推记录。',
|
|
104
108
|
kindRetry: '重试',
|
|
105
109
|
kindRescue: '补推',
|
|
110
|
+
kindMissed: '漏推',
|
|
106
111
|
loadFailed: '读取配置失败',
|
|
107
112
|
saveFailed: '保存失败',
|
|
108
113
|
unavailable: '设置路由不可用(宿主未提供)。'
|
|
@@ -139,8 +144,13 @@ window.__ModuleLoader__.load({
|
|
|
139
144
|
},
|
|
140
145
|
debugEmpty: 'Nothing needed rescuing yet — every push called the tool successfully.',
|
|
141
146
|
debugNone: 'The safety net has no run records yet.',
|
|
147
|
+
debugMissedSummary: function (c) {
|
|
148
|
+
return 'Safety net is off — ' + c.missed + ' pushes have gone missing since';
|
|
149
|
+
},
|
|
150
|
+
debugMissedEmpty: 'Nothing has gone missing while the net was off.',
|
|
142
151
|
kindRetry: 'retry',
|
|
143
152
|
kindRescue: 'rescue',
|
|
153
|
+
kindMissed: 'missed',
|
|
144
154
|
loadFailed: 'Could not load the configuration',
|
|
145
155
|
saveFailed: 'Could not save',
|
|
146
156
|
unavailable: 'The settings route is unavailable (host did not provide it).'
|
|
@@ -323,26 +333,48 @@ window.__ModuleLoader__.load({
|
|
|
323
333
|
preview = h('div', { style: { marginTop: '6px', fontSize: '12px', color: V.danger } }, t.invalid);
|
|
324
334
|
}
|
|
325
335
|
|
|
326
|
-
// ----
|
|
336
|
+
// ---- 战绩面板 ----
|
|
337
|
+
// 兜底**开着**:显示「救回了几次」。
|
|
338
|
+
// 兜底**关掉**:面板不消失,改显示「漏掉了几张」—— 关掉它的代价要看得见,
|
|
339
|
+
// 否则用户只会觉得"关了就关了",直到发现卡片少了几张。
|
|
340
|
+
var guardOn = Boolean(guard && guard.enabled);
|
|
341
|
+
var itemRows = function (items, kindOf) {
|
|
342
|
+
return (items || []).map(function (item, index) {
|
|
343
|
+
return h(
|
|
344
|
+
'div',
|
|
345
|
+
{ key: String(index), style: { fontSize: '12px', lineHeight: '1.7', color: V.sub } },
|
|
346
|
+
'· ' + shortTime(item.at) + ' [' + kindOf(item) + '] ' + (item.label || '-') + ' — ' + item.detail
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
var boldLine = function (text) {
|
|
351
|
+
return h('div', { style: { fontSize: '12px', fontWeight: 600, marginBottom: '4px' } }, text);
|
|
352
|
+
};
|
|
327
353
|
var debugBody;
|
|
328
354
|
if (!audit) {
|
|
329
355
|
debugBody = muted(t.debugNone);
|
|
330
|
-
} else if (
|
|
331
|
-
debugBody =
|
|
356
|
+
} else if (guardOn) {
|
|
357
|
+
debugBody =
|
|
358
|
+
!audit.exists || audit.counts.retryOk + audit.counts.rescueOk === 0
|
|
359
|
+
? muted(t.debugEmpty)
|
|
360
|
+
: h(
|
|
361
|
+
'div',
|
|
362
|
+
null,
|
|
363
|
+
boldLine(t.debugSummary(audit.counts)),
|
|
364
|
+
itemRows(audit.recent, function (item) {
|
|
365
|
+
return item.kind === 'retry-ok' ? t.kindRetry : t.kindRescue;
|
|
366
|
+
})
|
|
367
|
+
);
|
|
332
368
|
} else {
|
|
333
|
-
debugBody =
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
(item.label || '-') + ' — ' + item.detail
|
|
343
|
-
);
|
|
344
|
-
})
|
|
345
|
-
);
|
|
369
|
+
debugBody =
|
|
370
|
+
!audit.exists || audit.counts.missed === 0
|
|
371
|
+
? muted(t.debugMissedEmpty)
|
|
372
|
+
: h(
|
|
373
|
+
'div',
|
|
374
|
+
null,
|
|
375
|
+
boldLine(t.debugMissedSummary(audit.counts)),
|
|
376
|
+
itemRows(audit.missedRecent, function () { return t.kindMissed; })
|
|
377
|
+
);
|
|
346
378
|
}
|
|
347
379
|
|
|
348
380
|
return h(
|
package/src/lib/index.js
CHANGED
|
@@ -446,7 +446,7 @@ function buildConfigRoute({ authCodeInfo, settings, storeFile }) {
|
|
|
446
446
|
*/
|
|
447
447
|
const auditSummary = () => {
|
|
448
448
|
const file = join(GUARD_STATE_DIR, "audit.jsonl");
|
|
449
|
-
const empty = { file, exists: false, counts: { retryOk: 0, rescueOk: 0, failed: 0 }, recent: [] };
|
|
449
|
+
const empty = { file, exists: false, counts: { retryOk: 0, rescueOk: 0, failed: 0, missed: 0 }, recent: [], missedRecent: [] };
|
|
450
450
|
let text;
|
|
451
451
|
try {
|
|
452
452
|
text = readFileSync(file, "utf8");
|
|
@@ -455,8 +455,16 @@ function buildConfigRoute({ authCodeInfo, settings, storeFile }) {
|
|
|
455
455
|
return empty;
|
|
456
456
|
}
|
|
457
457
|
const lines = text.split("\n").filter(Boolean).slice(-AUDIT_MAX_LINES);
|
|
458
|
-
const counts = { retryOk: 0, rescueOk: 0, failed: 0 };
|
|
458
|
+
const counts = { retryOk: 0, rescueOk: 0, failed: 0, missed: 0 };
|
|
459
459
|
const recent = [];
|
|
460
|
+
const missedRecent = [];
|
|
461
|
+
/** missed 的原因码 → 面板上的说法。 */
|
|
462
|
+
const MISSED_REASON = {
|
|
463
|
+
"literal-text-unrescued": "模型把调用写成了纯文本(兜底已关闭,未补推)",
|
|
464
|
+
"broken-promise-unrescued": "模型承诺了推送却没调用工具(兜底已关闭,未补推)",
|
|
465
|
+
"failed-retryable-unretried": "调用失败,本可重试(兜底已关闭,未重试)",
|
|
466
|
+
"failed-permanent": "调用永久失败(兜底不会重试这类错误)"
|
|
467
|
+
};
|
|
460
468
|
for (const line of lines) {
|
|
461
469
|
let row;
|
|
462
470
|
try {
|
|
@@ -484,13 +492,24 @@ function buildConfigRoute({ authCodeInfo, settings, storeFile }) {
|
|
|
484
492
|
label: row.name ?? "",
|
|
485
493
|
detail: row.level === "L1" ? "模型把调用写成了纯文本" : "模型承诺了推送却没调用工具"
|
|
486
494
|
});
|
|
495
|
+
} else if (row?.kind === "missed") {
|
|
496
|
+
// 兜底关闭期间"本该补推 / 本该重试"的记录:这是关掉它的代价,要看得到
|
|
497
|
+
counts.missed += 1;
|
|
498
|
+
missedRecent.push({
|
|
499
|
+
kind: "missed",
|
|
500
|
+
at: row.at ?? "",
|
|
501
|
+
level: row.level ?? "",
|
|
502
|
+
label: row.name ?? "",
|
|
503
|
+
detail: MISSED_REASON[row.reason] ?? String(row.reason ?? "未推送")
|
|
504
|
+
});
|
|
487
505
|
} else if (row?.kind === "push-failed" || row?.kind === "rescue-failed" || row?.kind === "rescue-error") {
|
|
488
506
|
counts.failed += 1;
|
|
489
507
|
}
|
|
490
508
|
}
|
|
491
509
|
// 文件是追加写的,末尾最新;面板要「最近几次」,所以倒序后取前几条
|
|
492
510
|
recent.reverse();
|
|
493
|
-
|
|
511
|
+
missedRecent.reverse();
|
|
512
|
+
return { file, exists: true, counts, recent: recent.slice(0, 5), missedRecent: missedRecent.slice(0, 5) };
|
|
494
513
|
};
|
|
495
514
|
|
|
496
515
|
const summary = () => {
|
package/src/lib/push-guard.js
CHANGED
|
@@ -546,8 +546,9 @@ export function apply(ctx, config) {
|
|
|
546
546
|
ctx.effect(() => {
|
|
547
547
|
const onEvent = (session, event) => {
|
|
548
548
|
try {
|
|
549
|
-
//
|
|
550
|
-
|
|
549
|
+
// 注意:**关掉兜底后这个处理器仍要继续跑**。它不再补推,但仍要记账
|
|
550
|
+
// (见下面的 missed)——「关掉期间漏掉多少张卡片」只有继续观察才数得出来,
|
|
551
|
+
// 否则设置页那一栏永远是空的。
|
|
551
552
|
if (!event || typeof event !== "object") return;
|
|
552
553
|
const sessionId = session?.id ?? "?";
|
|
553
554
|
|
|
@@ -584,6 +585,25 @@ export function apply(ctx, config) {
|
|
|
584
585
|
}
|
|
585
586
|
return;
|
|
586
587
|
}
|
|
588
|
+
|
|
589
|
+
// 兜底被关掉时:**不补推,但照样记账**。
|
|
590
|
+
// 记成 missed 而不是 rescue-ok —— 设置页据此显示「关掉期间漏掉 N 张」。
|
|
591
|
+
// 关掉兜底的代价应该是看得见的,而不是悄悄少几张卡片。
|
|
592
|
+
if (guardOff()) {
|
|
593
|
+
audit({
|
|
594
|
+
kind: "missed",
|
|
595
|
+
level: card.level,
|
|
596
|
+
name: card.name,
|
|
597
|
+
sessionId,
|
|
598
|
+
turn: current.turn,
|
|
599
|
+
reason: card.level === "L1" ? "literal-text-unrescued" : "broken-promise-unrescued"
|
|
600
|
+
});
|
|
601
|
+
ctx.logger?.warn?.(
|
|
602
|
+
"[push-guard] ⚠️ %s 本该补推(会话 %s 第 %s 轮):%s —— 但兜底已关闭,仅记账未推送",
|
|
603
|
+
card.level, sessionId, current.turn ?? "?", card.name
|
|
604
|
+
);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
587
607
|
const fingerprint = `${card.name}\n${card.content.length}\n${card.content.slice(0, 80)}`;
|
|
588
608
|
if (lastRescued.get(sessionId) === fingerprint) return;
|
|
589
609
|
lastRescued.set(sessionId, fingerprint);
|
|
@@ -639,8 +659,31 @@ export function apply(ctx, config) {
|
|
|
639
659
|
const ambiguousAttempts = ambiguousBudget;
|
|
640
660
|
const fallbackScheduleId = String(settings.defaultScheduleId || DEFAULTS.defaultScheduleId).trim() || DEFAULTS.defaultScheduleId;
|
|
641
661
|
const wrapped = async (args, exec) => {
|
|
642
|
-
//
|
|
643
|
-
|
|
662
|
+
// 设置页关掉后:不补参、不重试,按原样调用(关闭必须真的关闭)。
|
|
663
|
+
// 但**照样给失败记账**:调用失败了却没重试,就是"漏掉一张"。
|
|
664
|
+
if (guardOff()) {
|
|
665
|
+
const outcome = await original(args, exec);
|
|
666
|
+
try {
|
|
667
|
+
const verdict = classifyPush(outcome);
|
|
668
|
+
if (!verdict.ok) {
|
|
669
|
+
audit({
|
|
670
|
+
kind: "missed",
|
|
671
|
+
level: "L3",
|
|
672
|
+
name: String(args?.name ?? ""),
|
|
673
|
+
reason: verdict.retryable ? "failed-retryable-unretried" : "failed-permanent",
|
|
674
|
+
code: verdict.code ?? "",
|
|
675
|
+
message: String(verdict.message ?? "").slice(0, 160)
|
|
676
|
+
});
|
|
677
|
+
ctx.logger?.warn?.(
|
|
678
|
+
"[push-guard] ⚠️ hiboard_push 失败(%s,%s)—— 兜底已关闭,未重试,仅记账",
|
|
679
|
+
verdict.code || "-", verdict.retryable ? "本可重试" : "永久错误"
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
} catch {
|
|
683
|
+
/* 记账失败不影响工具本身的返回 */
|
|
684
|
+
}
|
|
685
|
+
return outcome;
|
|
686
|
+
}
|
|
644
687
|
// ---- 先补 schedule_id,再让工具校验/发送 ----
|
|
645
688
|
// 为什么必须补:scheduleTaskId 为空时负一屏**只显示一行标题**,
|
|
646
689
|
// 正文不渲染、详情页点不开(2026-09-27 用户实际遇到)。
|