@cowwo/dsh-icon-custom 0.19.1 → 0.20.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/client/client.js +843 -37
- package/lib/index.js +27 -8
- package/lib/running.js +26 -5
- package/lib/typert.host.js +19 -10
- package/lib/unread.js +66 -13
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -371,7 +371,11 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
371
371
|
projectionCtx.sessionProjections.register({
|
|
372
372
|
key: LAST_TURN_END_KEY,
|
|
373
373
|
stateSchema: lastTurnEndStateSchema,
|
|
374
|
-
|
|
374
|
+
// Bumped when the fold learned to attribute a turn to the sub-agent
|
|
375
|
+
// settlement that woke it (`user/message` could no longer be ignored).
|
|
376
|
+
// A cached row is keyed by this number, so an old one is re-folded
|
|
377
|
+
// instead of being read as "nobody caused this turn".
|
|
378
|
+
stateVersion: 2,
|
|
375
379
|
init: () => initLastTurnEnd(),
|
|
376
380
|
apply: applyLastTurnEnd,
|
|
377
381
|
wire: { viewSchema: lastTurnEndViewSchema, view: lastTurnEndView }
|
|
@@ -517,7 +521,7 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
517
521
|
* icon RPCs (`getStatus` / `setIcon` / `setPwa` / `setLogo` / `resetIcon`)
|
|
518
522
|
* keep their EXACT pre-badge response shape, so a problem in this feature
|
|
519
523
|
* can never break the icon itself.
|
|
520
|
-
* @returns `{ reasons, pending, workspaceDot, appBadge, clearDelaySec, hostNow }`.
|
|
524
|
+
* @returns `{ reasons, pending, workspaceDot, appBadge, clearDelaySec, revealOnOpen, unreadIncludeSubagents, hostNow }`.
|
|
521
525
|
*/
|
|
522
526
|
async getUnreadRule() {
|
|
523
527
|
return this._unreadRuleView()
|
|
@@ -527,7 +531,7 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
527
531
|
* typert endpoint: replace the unread-badge rule — which turn-end reasons
|
|
528
532
|
* light the badge. The rule is applied on the client, so this takes effect
|
|
529
533
|
* without re-folding any session log.
|
|
530
|
-
* @param request - `{ reasons, pending?, workspaceDot?, appBadge?, clearDelaySec? }`; unknown or missing keys fall back to defaults.
|
|
534
|
+
* @param request - `{ reasons, pending?, workspaceDot?, appBadge?, clearDelaySec?, revealOnOpen?, unreadIncludeSubagents? }`; unknown or missing keys fall back to defaults.
|
|
531
535
|
* @returns the stored rule.
|
|
532
536
|
*/
|
|
533
537
|
async setUnreadRule(request) {
|
|
@@ -548,22 +552,35 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
548
552
|
* drag the other's storage or response shape along. There is deliberately no
|
|
549
553
|
* `running` field here — who is running is the official host's answer and
|
|
550
554
|
* travels in the ordinary session list.
|
|
551
|
-
*
|
|
555
|
+
*
|
|
556
|
+
* Both switches are returned whole from the normalized copy — `enabled` (draw
|
|
557
|
+
* the marks at all), `includeSubagents` (credit a running sub-agent to the
|
|
558
|
+
* topmost non-sub-agent ancestor session) and `headerRing` (the yellow ring on
|
|
559
|
+
* the conversation header's sub-agent count) — so a caller can never drop one by
|
|
560
|
+
* copying only the field it happened to know about.
|
|
561
|
+
* @returns `{ enabled, includeSubagents, headerRing }`.
|
|
552
562
|
*/
|
|
553
563
|
async getRunningRule() {
|
|
554
564
|
return { ...this.running }
|
|
555
565
|
}
|
|
556
566
|
|
|
557
567
|
/**
|
|
558
|
-
* typert endpoint: flip
|
|
559
|
-
*
|
|
560
|
-
*
|
|
568
|
+
* typert endpoint: flip a running-mark switch.
|
|
569
|
+
*
|
|
570
|
+
* A patch, not a replacement: `normalizeRunningConfig` fills whichever field is
|
|
571
|
+
* absent from the current defaults, so `{ includeSubagents: false }` alone never
|
|
572
|
+
* resets `enabled` and vice versa.
|
|
573
|
+
* @param request - `{ enabled?: boolean, includeSubagents?: boolean }`; anything else falls back to the defaults.
|
|
574
|
+
* @returns the stored switches.
|
|
561
575
|
*/
|
|
562
576
|
async setRunningRule(request) {
|
|
563
577
|
if (request === null || typeof request !== 'object') {
|
|
564
578
|
throw new Error('缺少 enabled 参数')
|
|
565
579
|
}
|
|
566
|
-
|
|
580
|
+
// Merged onto the stored copy, not swapped in wholesale: the two switches live
|
|
581
|
+
// in one record, so a caller that only knows about one of them must not reset
|
|
582
|
+
// the other to its default.
|
|
583
|
+
this.running = normalizeRunningConfig({ ...this.running, ...request })
|
|
567
584
|
await this._saveRunning().catch(() => {}) // switch file is advisory
|
|
568
585
|
return { ...this.running }
|
|
569
586
|
}
|
|
@@ -613,6 +630,8 @@ export class FaviconCustomService extends TypertRemoteService {
|
|
|
613
630
|
workspaceDot: this.unread.workspaceDot,
|
|
614
631
|
appBadge: this.unread.appBadge,
|
|
615
632
|
clearDelaySec: this.unread.clearDelaySec,
|
|
633
|
+
revealOnOpen: this.unread.revealOnOpen,
|
|
634
|
+
unreadIncludeSubagents: this.unread.unreadIncludeSubagents,
|
|
616
635
|
hostNow: Date.now(),
|
|
617
636
|
seen: { ...this.seen }
|
|
618
637
|
}
|
package/lib/running.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 事件实时推到浏览器,客户端会话列表里本来就带着每个会话的 `running`。
|
|
7
7
|
* 这个插件只把那件事**多投影几次**(黄点/黄数字/黄 favicon 角标)。
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* 所以这个文件只有几个布尔开关,和一条纪律:
|
|
10
10
|
* **不落盘"进行中",只落盘"要不要显示"。** 进程重启后冷会话按官方口径
|
|
11
11
|
* 永远是 `running:false`,不会留下一个永远亮着的假黄点;而开关是偏好,
|
|
12
12
|
* 应该跨设备、跨重启跟着你,所以它归宿主,和红点规则并列但互不牵连
|
|
@@ -17,16 +17,37 @@
|
|
|
17
17
|
export const RUNNING_MARK_DEFAULT = true
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* 默认:把子代理的「进行中」算到它所属的会话上。
|
|
21
|
+
*
|
|
22
|
+
* 和上面那个开关是**两个问题**:「要不要画黄点」与「子代理在跑,算不算它上面那个
|
|
23
|
+
* 会话在跑」。子代理自己没有会话行(官方侧栏根本不渲染),所以它跑得再欢,真正属于
|
|
24
|
+
* 用户的那个会话看上去也是空闲的;默认聚合上去,黄点才反映「有活正在干」。
|
|
25
|
+
* 聚合只影响计数归属:子代理自己的行照旧不画,等待你(琥珀色)的祖先也照旧不算。
|
|
26
|
+
*/
|
|
27
|
+
export const RUNNING_INCLUDE_SUBAGENTS_DEFAULT = true
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 默认:在会话头部那颗「N 个子智能体」计数上叠一个黄圈。
|
|
31
|
+
*
|
|
32
|
+
* 第三个问题,同样独立:前两个开关决定「黄点画不画」「子代理算不算到祖先」,
|
|
33
|
+
* 这一个只决定「会话头部那颗计数上要不要再叠一个运行数」。它不参与任何既有
|
|
34
|
+
* 计数(侧栏红黄点、左上角数字、favicon、系统角标),关掉它一个数字都不会动。
|
|
35
|
+
*/
|
|
36
|
+
export const RUNNING_HEADER_RING_DEFAULT = true
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 把任意输入夹成 `{ enabled, includeSubagents, headerRing }`。
|
|
21
40
|
*
|
|
22
41
|
* 只认严格的 `true`/`false`;缺项、脏值、null 一律回落默认——一个读不懂的
|
|
23
42
|
* 开关值不该把一整套标记的可见性变成猜谜。
|
|
24
|
-
* @param input - `{ enabled? }`,可以是脏的、缺项的、null
|
|
25
|
-
* @returns `{ enabled }` —— 永远完整
|
|
43
|
+
* @param input - `{ enabled?, includeSubagents?, headerRing? }`,可以是脏的、缺项的、null
|
|
44
|
+
* @returns `{ enabled, includeSubagents, headerRing }` —— 永远完整
|
|
26
45
|
*/
|
|
27
46
|
export function normalizeRunningConfig(input) {
|
|
28
47
|
const source = input !== null && typeof input === 'object' ? input : {}
|
|
29
48
|
return {
|
|
30
|
-
enabled: typeof source.enabled === 'boolean' ? source.enabled : RUNNING_MARK_DEFAULT
|
|
49
|
+
enabled: typeof source.enabled === 'boolean' ? source.enabled : RUNNING_MARK_DEFAULT,
|
|
50
|
+
includeSubagents: typeof source.includeSubagents === 'boolean' ? source.includeSubagents : RUNNING_INCLUDE_SUBAGENTS_DEFAULT,
|
|
51
|
+
headerRing: typeof source.headerRing === 'boolean' ? source.headerRing : RUNNING_HEADER_RING_DEFAULT
|
|
31
52
|
}
|
|
32
53
|
}
|
package/lib/typert.host.js
CHANGED
|
@@ -59,6 +59,8 @@ const unreadRuleSchema = z.object({
|
|
|
59
59
|
clearDelaySec: z.number().int().nonnegative().max(600),
|
|
60
60
|
/** 实验项:点清单条目时在侧栏定位到该会话(展开工作区 + 滚动 + 闪一下;贴产品 DOM,可一键关闭)。 */
|
|
61
61
|
revealOnOpen: z.boolean(),
|
|
62
|
+
/** 子代理结算唤醒父会话跑出来的那一轮结束,算不算未读。默认 true(= 现状)。 */
|
|
63
|
+
unreadIncludeSubagents: z.boolean().optional(),
|
|
62
64
|
/**
|
|
63
65
|
* 宿主此刻的 `Date.now()`。
|
|
64
66
|
* 客户端用它把"看过"水位换算进宿主时钟域(`turn/end.time` 也是宿主时钟):
|
|
@@ -80,7 +82,8 @@ const setUnreadRuleRequestSchema = z.object({
|
|
|
80
82
|
workspaceDot: z.boolean().optional(),
|
|
81
83
|
appBadge: z.boolean().optional(),
|
|
82
84
|
clearDelaySec: z.number().int().nonnegative().max(600).optional(),
|
|
83
|
-
revealOnOpen: z.boolean().optional()
|
|
85
|
+
revealOnOpen: z.boolean().optional(),
|
|
86
|
+
unreadIncludeSubagents: z.boolean().optional()
|
|
84
87
|
})
|
|
85
88
|
|
|
86
89
|
/** 一次水位线同步:浏览器把自己那份交上来,宿主逐会话取 max 后回权威表。 */
|
|
@@ -96,15 +99,21 @@ const unreadSeenSchema = z.object({
|
|
|
96
99
|
* 进行中黄点的开关。
|
|
97
100
|
*
|
|
98
101
|
* 这里**没有**"谁在跑"的字段,一条都没有:那是官方的 `SessionSummary.running`,
|
|
99
|
-
*
|
|
100
|
-
*
|
|
102
|
+
* 由官方宿主实时推给浏览器,插件只读不存。宿主这一侧只有两个偏好——要不要显示、
|
|
103
|
+
* 子代理的进行中是否算到它所属的祖先会话上——和红点规则并列、互不牵连
|
|
104
|
+
* (自己的端点对、自己的文件)。
|
|
101
105
|
*/
|
|
102
106
|
const runningRuleSchema = z.object({
|
|
103
|
-
enabled: z.boolean()
|
|
107
|
+
enabled: z.boolean(),
|
|
108
|
+
includeSubagents: z.boolean().optional(),
|
|
109
|
+
/** 会话头部「N 个子智能体」计数上那颗黄圈(独立开关,不并入任何既有计数)。 */
|
|
110
|
+
headerRing: z.boolean().optional()
|
|
104
111
|
})
|
|
105
112
|
|
|
106
113
|
const setRunningRuleRequestSchema = z.object({
|
|
107
|
-
enabled: z.boolean().optional()
|
|
114
|
+
enabled: z.boolean().optional(),
|
|
115
|
+
includeSubagents: z.boolean().optional(),
|
|
116
|
+
headerRing: z.boolean().optional()
|
|
108
117
|
})
|
|
109
118
|
|
|
110
119
|
const setIconResultSchema = statusSchema
|
|
@@ -379,7 +388,7 @@ export const TYPERT = {
|
|
|
379
388
|
{
|
|
380
389
|
kind: 'method',
|
|
381
390
|
name: 'getUnreadRule',
|
|
382
|
-
signature: 'async getUnreadRule(): Promise<{ reasons: Record<string, boolean>; pending: boolean; workspaceDot: boolean; appBadge: boolean; clearDelaySec: number; revealOnOpen: boolean; hostNow: number; seen: Record<string, number> }>',
|
|
391
|
+
signature: 'async getUnreadRule(): Promise<{ reasons: Record<string, boolean>; pending: boolean; workspaceDot: boolean; appBadge: boolean; clearDelaySec: number; revealOnOpen: boolean; unreadIncludeSubagents?: boolean; hostNow: number; seen: Record<string, number> }>',
|
|
383
392
|
description: "Read the unread-badge rule, the Host clock and the seen watermark.",
|
|
384
393
|
summary: "Read the unread-badge rule.",
|
|
385
394
|
jsDoc: "/** Read the unread-badge rule. */",
|
|
@@ -388,7 +397,7 @@ export const TYPERT = {
|
|
|
388
397
|
{
|
|
389
398
|
kind: 'method',
|
|
390
399
|
name: 'setUnreadRule',
|
|
391
|
-
signature: 'async setUnreadRule(request: { reasons: Record<string, boolean>; pending?: boolean; workspaceDot?: boolean; appBadge?: boolean; clearDelaySec?: number; revealOnOpen?: boolean }): Promise<{ reasons: Record<string, boolean>; pending: boolean; workspaceDot: boolean; appBadge: boolean; clearDelaySec: number; revealOnOpen: boolean; hostNow: number; seen: Record<string, number> }>',
|
|
400
|
+
signature: 'async setUnreadRule(request: { reasons: Record<string, boolean>; pending?: boolean; workspaceDot?: boolean; appBadge?: boolean; clearDelaySec?: number; revealOnOpen?: boolean; unreadIncludeSubagents?: boolean }): Promise<{ reasons: Record<string, boolean>; pending: boolean; workspaceDot: boolean; appBadge: boolean; clearDelaySec: number; revealOnOpen: boolean; unreadIncludeSubagents?: boolean; hostNow: number; seen: Record<string, number> }>',
|
|
392
401
|
description: "Replace the unread-badge rule: which turn-end reasons light the red dot.",
|
|
393
402
|
summary: "Set the unread-badge rule.",
|
|
394
403
|
jsDoc: "/** Set the unread-badge rule. */",
|
|
@@ -406,8 +415,8 @@ export const TYPERT = {
|
|
|
406
415
|
{
|
|
407
416
|
kind: 'method',
|
|
408
417
|
name: 'getRunningRule',
|
|
409
|
-
signature: 'async getRunningRule(): Promise<{ enabled: boolean }>',
|
|
410
|
-
description: "Read the running-mark
|
|
418
|
+
signature: 'async getRunningRule(): Promise<{ enabled: boolean, includeSubagents?: boolean, headerRing?: boolean }>',
|
|
419
|
+
description: "Read the running-mark switches (show the yellow mark; roll a running sub-agent up to its ancestor session; draw the header count ring).",
|
|
411
420
|
summary: "Read the running-mark switch.",
|
|
412
421
|
jsDoc: "/** Read the running-mark switch. */",
|
|
413
422
|
tags: []
|
|
@@ -415,7 +424,7 @@ export const TYPERT = {
|
|
|
415
424
|
{
|
|
416
425
|
kind: 'method',
|
|
417
426
|
name: 'setRunningRule',
|
|
418
|
-
signature: 'async setRunningRule(request: { enabled?: boolean }): Promise<{ enabled: boolean }>',
|
|
427
|
+
signature: 'async setRunningRule(request: { enabled?: boolean, includeSubagents?: boolean, headerRing?: boolean }): Promise<{ enabled: boolean, includeSubagents?: boolean, headerRing?: boolean }>',
|
|
419
428
|
description: "Flip the running-mark switch. The running fact itself is official and is never stored here.",
|
|
420
429
|
summary: "Set the running-mark switch.",
|
|
421
430
|
jsDoc: "/** Set the running-mark switch. */",
|
package/lib/unread.js
CHANGED
|
@@ -75,6 +75,31 @@ export const UNREAD_REVEAL_DEFAULT = true
|
|
|
75
75
|
*/
|
|
76
76
|
export const UNREAD_APP_BADGE_DEFAULT = true
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* 默认:子代理引起的结束也算未读。
|
|
80
|
+
*
|
|
81
|
+
* 一个后台子代理结算时会**唤醒父会话跑一个回合**(官方把一条
|
|
82
|
+
* `source.kind === 'subagent-settled'` 的 user 消息投进父会话的日志),
|
|
83
|
+
* 父会话因此亮红点。这一项就是那个"我不关心子代理替我跑出来的结果"的开关:
|
|
84
|
+
* 关掉它,只屏蔽**由子代理唤醒**的那一轮结束,人自己发起的一轮照旧。
|
|
85
|
+
*
|
|
86
|
+
* 开关只改客户端的判定,折叠出来的**事实**里两样都在(见下面的
|
|
87
|
+
* `subagentCaused`),所以来回拨开关永远不需要重折日志。
|
|
88
|
+
*/
|
|
89
|
+
export const UNREAD_INCLUDE_SUBAGENTS_DEFAULT = true
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 一条 `user/message` 的 `source.kind` 里,哪些明确是"子代理在说话":
|
|
93
|
+
* * `subagent-settled` —— 运行时替子代理交代它结束了(唤醒父会话的那一条);
|
|
94
|
+
* * `agent-message` —— 子代理自己用 `send_message` 发过来的话。
|
|
95
|
+
* 人在输入框里说话是 `user`,所以这两条之外都不算。
|
|
96
|
+
*/
|
|
97
|
+
export function isSubagentWakeSource(message) {
|
|
98
|
+
const source = message !== null && typeof message === 'object' && message.source !== null && typeof message.source === 'object' ? message.source : null
|
|
99
|
+
if (source === null) return false
|
|
100
|
+
return source.kind === 'subagent-settled' || source.kind === 'agent-message'
|
|
101
|
+
}
|
|
102
|
+
|
|
78
103
|
/**
|
|
79
104
|
* 默认:进入会话即视为已读(0 秒)。
|
|
80
105
|
* 大于 0 时,你必须在那个会话里**连续**待满这么多秒它才算"看过";中途切走会
|
|
@@ -110,8 +135,8 @@ export function normalizeReasons(input) {
|
|
|
110
135
|
|
|
111
136
|
/**
|
|
112
137
|
* 把任意输入夹成完整设置。
|
|
113
|
-
* @param input - `{ reasons?, pending?, workspaceDot?, appBadge?, clearDelaySec?, revealOnOpen? }`,可以是脏的、缺项的、null
|
|
114
|
-
* @returns `{ reasons, pending, workspaceDot, appBadge, clearDelaySec, revealOnOpen }` —— 每一项都完整
|
|
138
|
+
* @param input - `{ reasons?, pending?, workspaceDot?, appBadge?, clearDelaySec?, revealOnOpen?, unreadIncludeSubagents? }`,可以是脏的、缺项的、null
|
|
139
|
+
* @returns `{ reasons, pending, workspaceDot, appBadge, clearDelaySec, revealOnOpen, unreadIncludeSubagents }` —— 每一项都完整
|
|
115
140
|
*/
|
|
116
141
|
export function normalizeUnreadConfig(input) {
|
|
117
142
|
const source = input !== null && typeof input === 'object' ? input : {}
|
|
@@ -121,7 +146,10 @@ export function normalizeUnreadConfig(input) {
|
|
|
121
146
|
workspaceDot: typeof source.workspaceDot === 'boolean' ? source.workspaceDot : UNREAD_WORKSPACE_DOT_DEFAULT,
|
|
122
147
|
appBadge: typeof source.appBadge === 'boolean' ? source.appBadge : UNREAD_APP_BADGE_DEFAULT,
|
|
123
148
|
clearDelaySec: normalizeClearDelay(source.clearDelaySec),
|
|
124
|
-
revealOnOpen: typeof source.revealOnOpen === 'boolean' ? source.revealOnOpen : UNREAD_REVEAL_DEFAULT
|
|
149
|
+
revealOnOpen: typeof source.revealOnOpen === 'boolean' ? source.revealOnOpen : UNREAD_REVEAL_DEFAULT,
|
|
150
|
+
// 缺项或非 boolean 一律回落 true(等于现状):一个没听懂这一项的旧客户端
|
|
151
|
+
// 不该因为宿主多了一个字段就少看见红点。
|
|
152
|
+
unreadIncludeSubagents: typeof source.unreadIncludeSubagents === 'boolean' ? source.unreadIncludeSubagents : UNREAD_INCLUDE_SUBAGENTS_DEFAULT
|
|
125
153
|
}
|
|
126
154
|
}
|
|
127
155
|
|
|
@@ -140,43 +168,68 @@ export function reasonKey(reason) {
|
|
|
140
168
|
|
|
141
169
|
/** 折叠初始态:还没有任何回合结束过。 */
|
|
142
170
|
export function initLastTurnEnd() {
|
|
143
|
-
return { endAt: 0, reason: null }
|
|
171
|
+
return { endAt: 0, reason: null, subagentCaused: false, wakeSubagent: null }
|
|
144
172
|
}
|
|
145
173
|
|
|
146
174
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
175
|
+
* 纯转移。
|
|
176
|
+
*
|
|
177
|
+
* 除了 `turn/end`,另有两类事件会改动它,因为"这一轮是谁叫醒的"必须先被记下来:
|
|
178
|
+
* * `user/message` —— 观察到的**第一条** user 消息就是这一轮的起因
|
|
179
|
+
* (`subagent-settled` / `agent-message` = 子代理,其余 = 人)。之后 steer
|
|
180
|
+
* 进来的消息不算:第一票定终身,人自己发起的一轮不会因为子代理在中途
|
|
181
|
+
* 插了句话就变成"子代理引起";
|
|
182
|
+
* * `turn/start` —— 清掉上一轮的起因,新的一轮重新投票。
|
|
183
|
+
* 与折叠单元无关的事件仍原样返回同一个引用(契约要求 `Object.is` 不变以产生
|
|
184
|
+
* 零下游工作)。
|
|
149
185
|
* @param state - 覆盖此前全部事件的状态
|
|
150
186
|
* @param event - 下一个已提交的会话事件
|
|
151
187
|
* @returns 下一个状态(事件与本单元无关时返回同一个引用)
|
|
152
188
|
*/
|
|
153
189
|
export function applyLastTurnEnd(state, event) {
|
|
154
|
-
if (event === null || typeof event !== 'object'
|
|
190
|
+
if (event === null || typeof event !== 'object') return state
|
|
191
|
+
const current = state !== null && typeof state === 'object' ? state : initLastTurnEnd()
|
|
192
|
+
const wake = typeof current.wakeSubagent === 'boolean' ? current.wakeSubagent : null
|
|
193
|
+
if (event.type === 'user/message') {
|
|
194
|
+
if (wake !== null) return state
|
|
195
|
+
return { ...current, wakeSubagent: isSubagentWakeSource(event.data) }
|
|
196
|
+
}
|
|
197
|
+
if (event.type === 'turn/start') {
|
|
198
|
+
if (wake === null) return state
|
|
199
|
+
return { ...current, wakeSubagent: null }
|
|
200
|
+
}
|
|
201
|
+
if (event.type !== 'turn/end') return state
|
|
155
202
|
const data = event.data !== null && typeof event.data === 'object' ? event.data : {}
|
|
156
203
|
const reason = reasonKey(data.reason)
|
|
157
204
|
const endAt = typeof event.time === 'number' ? event.time : 0
|
|
158
|
-
|
|
159
|
-
|
|
205
|
+
const subagentCaused = wake === true
|
|
206
|
+
if (current.endAt === endAt && current.reason === reason && (current.subagentCaused === true) === subagentCaused && wake === null) return state
|
|
207
|
+
return { endAt, reason, subagentCaused, wakeSubagent: null }
|
|
160
208
|
}
|
|
161
209
|
|
|
162
210
|
/** 状态 → 上线载荷。 */
|
|
163
211
|
export function lastTurnEndView(state) {
|
|
164
212
|
return {
|
|
165
213
|
endAt: typeof state.endAt === 'number' ? state.endAt : 0,
|
|
166
|
-
reason: typeof state.reason === 'string' && state.reason !== '' ? state.reason : null
|
|
214
|
+
reason: typeof state.reason === 'string' && state.reason !== '' ? state.reason : null,
|
|
215
|
+
// 这一轮是不是被"子代理结束"叫醒的。老检查点里没有这一项 → false(=现状)。
|
|
216
|
+
subagentCaused: state !== null && typeof state === 'object' && state.subagentCaused === true
|
|
167
217
|
}
|
|
168
218
|
}
|
|
169
219
|
|
|
170
|
-
/** 持久化折叠状态(纯 JSON,供投影缓存检查点)
|
|
220
|
+
/** 持久化折叠状态(纯 JSON,供投影缓存检查点)。新增的字段可选:老检查点照样能读。 */
|
|
171
221
|
export const lastTurnEndStateSchema = z.object({
|
|
172
222
|
endAt: z.number(),
|
|
173
|
-
reason: z.string().nullable()
|
|
223
|
+
reason: z.string().nullable(),
|
|
224
|
+
subagentCaused: z.boolean().optional(),
|
|
225
|
+
wakeSubagent: z.boolean().nullable().optional()
|
|
174
226
|
})
|
|
175
227
|
|
|
176
228
|
/** 上线载荷。 */
|
|
177
229
|
export const lastTurnEndViewSchema = z.object({
|
|
178
230
|
endAt: z.number(),
|
|
179
|
-
reason: z.string().nullable()
|
|
231
|
+
reason: z.string().nullable(),
|
|
232
|
+
subagentCaused: z.boolean()
|
|
180
233
|
})
|
|
181
234
|
|
|
182
235
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cowwo/dsh-icon-custom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "自定义浏览器标签页图标:在设置页上传 SVG/PNG/ICO,立即生效并持久保存,自动适配安卓/iOS/平板 PWA 图标,显示未读红点与进行中黄点(工作区数字/会话行/左上角/标签页,数字可点开清单、点条目定位到侧栏),并可把未读数显示为系统应用图标角标(Windows 任务栏 / macOS Dock,含通知权限申请)。· Custom browser tab favicon: upload SVG/PNG/ICO from the settings page, applies instantly and persists, auto-sizes PWA icons for Android/iOS/tablets, shows unread red dots and running yellow marks (workspace number / session row / top-left / tab icon, both counts open a list whose rows locate the session in the sidebar), and projects the unread count onto the system app-icon badge (Windows taskbar / macOS Dock, notifications permission included).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "node test/app-badge.mjs && node test/notification-permission.mjs && node test/unread-panel.mjs && node test/unread-clear.mjs && node test/unread-rows.mjs && node test/unread-seen.mjs && node test/unread-host-sync.mjs && node test/running-mark.mjs && node test/running-panel.mjs && node test/reveal.mjs"
|
|
9
|
+
"test": "node test/app-badge.mjs && node test/notification-permission.mjs && node test/unread-panel.mjs && node test/unread-clear.mjs && node test/unread-rows.mjs && node test/unread-seen.mjs && node test/unread-host-sync.mjs && node test/running-mark.mjs && node test/running-panel.mjs && node test/running-subagents.mjs && node test/header-ring.mjs && node test/unread-subagents.mjs && node test/reveal.mjs && node test/reveal-persist.mjs && node test/hint-disclosure.mjs"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"jimp": "^1.6.1",
|