@cowwo/dsh-lookout 0.1.2 → 0.5.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.
@@ -116,11 +116,41 @@ const setRunningRuleRequestSchema = z.object({
116
116
  headerRing: z.boolean().optional()
117
117
  })
118
118
 
119
+ /**
120
+ * 工作区偏好(标签 / 颜色 / 置顶 / 隐藏)。
121
+ *
122
+ * 与图标 RPC 分开的一对端点:偏好文件坏了最多丢标签,不能拖累 favicon、红点
123
+ * 和进行中黄点。文档整份读写(客户端每次提交完整文档,宿主整份覆盖)。
124
+ */
125
+ const workspacePrefsEntrySchema = z.object({
126
+ // 按 Unicode 字符数(不是 UTF-16 长度)限 6:6 个 emoji 是合法标签。
127
+ tag: z.string().refine((value) => [...value].length <= 6, { message: 'tag 最多 6 个字符' }),
128
+ color: z.enum(['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'gray']),
129
+ pinned: z.boolean(),
130
+ hidden: z.boolean(),
131
+ pinAnchor: z.string().nullable()
132
+ })
133
+
134
+ const workspacePrefsSchema = z.object({
135
+ version: z.literal(1),
136
+ workspaces: z.record(z.string().min(1).max(200), workspacePrefsEntrySchema)
137
+ })
138
+
139
+ /**
140
+ * set 的请求 schema 故意放宽到"只验形状":端点自己会用 normalize 把非法 tag /
141
+ * 颜色 / 布尔值降级成默认值,所以旧客户端或手改的文档不会在网关层被整份拒绝,
142
+ * 从而丢掉本来还能用的那些工作区。
143
+ */
144
+ const setWorkspacePrefsRequestSchema = z.object({
145
+ version: z.number().int().optional(),
146
+ workspaces: z.record(z.string(), z.unknown())
147
+ })
148
+
119
149
  const setIconResultSchema = statusSchema
120
150
 
121
151
 
122
152
 
123
- /** 一个通道这一次的成败(多通道扇出后,账本里要看得出是哪个通道)。 */
153
+ /** 一个通道这一次的成败(多通道扇出后,要看得出是哪个通道)。 */
124
154
  let channelSchema$value;
125
155
  const channelSchema = () => (channelSchema$value ??= z.object({
126
156
  transport: z.string(),
@@ -142,13 +172,6 @@ const deliverySchema = () => (deliverySchema$value ??= z.object({
142
172
  channels: z.array(channelSchema()),
143
173
  }));
144
174
 
145
- /** 账本快照。 */
146
- let stateSchema$value;
147
- const stateSchema = () => (stateSchema$value ??= z.object({
148
- counts: z.object({ sent: z.number(), failed: z.number() }),
149
- recent: z.array(deliverySchema()),
150
- }));
151
-
152
175
 
153
176
 
154
177
  /** 合并后的 service 模型:A 的 iconCustom + B 的 mailNotify(B 的 model.services 为空)。 */
@@ -250,13 +273,31 @@ const services = [
250
273
  summary: "Set the running-mark switch.",
251
274
  jsDoc: "/** Set the running-mark switch. */",
252
275
  tags: []
276
+ },
277
+ {
278
+ kind: 'method',
279
+ name: 'getWorkspacePrefs',
280
+ signature: 'async getWorkspacePrefs(): Promise<{ version: 1; workspaces: Record<string, { tag: string; color: string; pinned: boolean; hidden: boolean; pinAnchor: string | null }> }>',
281
+ description: "Read the per-workspace preferences (tag, color, pinned, hidden).",
282
+ summary: "Read workspace preferences.",
283
+ jsDoc: "/** Read workspace preferences. */",
284
+ tags: []
285
+ },
286
+ {
287
+ kind: 'method',
288
+ name: 'setWorkspacePrefs',
289
+ signature: 'async setWorkspacePrefs(request: { version?: number; workspaces: Record<string, unknown> }): Promise<{ version: 1; workspaces: Record<string, { tag: string; color: string; pinned: boolean; hidden: boolean; pinAnchor: string | null }> }>',
290
+ description: "Replace the whole workspace-preferences document (dirty fields degrade to defaults).",
291
+ summary: "Set workspace preferences.",
292
+ jsDoc: "/** Set workspace preferences. */",
293
+ tags: []
253
294
  }
254
295
  ],
255
296
  types: []
256
297
  }
257
298
  ]
258
299
 
259
- /** 合并后的宿主端点:A 的 9 条 iconCustom/* + B 的 2 条 mailNotify/*。 */
300
+ /** 合并后的宿主端点:A 的 12 条 iconCustom/* + B 的 1 条 mailNotify/*。 */
260
301
  const invocations = [
261
302
  {
262
303
  id: 'dsh-lookout#iconCustom/getStatus',
@@ -439,6 +480,45 @@ const invocations = [
439
480
  },
440
481
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
441
482
  },
483
+ {
484
+ id: 'dsh-lookout#iconCustom/getWorkspacePrefs',
485
+ service: 'iconCustom',
486
+ namespace: 'iconCustom',
487
+ method: 'getWorkspacePrefs',
488
+ invocation: { kind: 'direct' },
489
+ parameters: [],
490
+ result: {
491
+ mode: 'strict',
492
+ typeSymbol: 'dsh-lookout#iconCustom/getWorkspacePrefs:result',
493
+ create: () => workspacePrefsSchema
494
+ },
495
+ sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
496
+ },
497
+ {
498
+ id: 'dsh-lookout#iconCustom/setWorkspacePrefs',
499
+ service: 'iconCustom',
500
+ namespace: 'iconCustom',
501
+ method: 'setWorkspacePrefs',
502
+ invocation: { kind: 'direct' },
503
+ parameters: [
504
+ {
505
+ name: 'request',
506
+ wire: 'request',
507
+ source: 'json',
508
+ codec: {
509
+ mode: 'strict',
510
+ typeSymbol: 'dsh-lookout#iconCustom/setWorkspacePrefs:request',
511
+ create: () => setWorkspacePrefsRequestSchema
512
+ }
513
+ }
514
+ ],
515
+ result: {
516
+ mode: 'strict',
517
+ typeSymbol: 'dsh-lookout#iconCustom/setWorkspacePrefs:result',
518
+ create: () => workspacePrefsSchema
519
+ },
520
+ sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
521
+ },
442
522
  {
443
523
  id: 'dsh-lookout#iconCustom/setUnreadSeen',
444
524
  service: 'iconCustom',
@@ -478,20 +558,6 @@ const invocations = [
478
558
  },
479
559
  sourceLocation: { file: 'lib/service.js', line: 1, column: 1 },
480
560
  },
481
- {
482
- id: 'dsh-lookout#mailNotify/state',
483
- service: 'mailNotify',
484
- namespace: 'mailNotify',
485
- method: 'state',
486
- invocation: { kind: 'direct' },
487
- parameters: [],
488
- result: {
489
- mode: 'strict',
490
- typeSymbol: 'dsh-lookout#mailNotify/state:result',
491
- create: stateSchema,
492
- },
493
- sourceLocation: { file: 'lib/service.js', line: 1, column: 1 },
494
- },
495
561
  ]
496
562
 
497
563
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cowwo/dsh-lookout",
3
3
  "description": "瞭望哨:盯着你的每个会话——谁跑完了、谁出错了、谁卡住了、谁在等你审批,用浏览器图标角标、红点/黄点和邮件通知你;顺带显示当前会话 ID。· Lookout: watches every session — who finished, who errored, who is stuck, and who is waiting for your approval — and notifies you through the browser favicon badge, red/yellow dots, and email, while also showing the current session ID.",
4
- "version": "0.1.2",
4
+ "version": "0.5.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
@@ -1,102 +0,0 @@
1
- // dsh-notify-p · 投递账本(落盘,用于"到底发没发出去"的可验证性)
2
- //
3
- // 为什么需要它:cordis 的控制台 exporter 默认只输出 error 级,
4
- // 插件的 info/warn 日志**不会**出现在 dsh 的 stdout 里——
5
- // 所以"看日志确认"是不可行的。这里把每次投递结果落成一个 JSON 文件,
6
- // 出问题时 `cat $DSH_HOME/dsh-notify-p/state.json` 一眼就能看到。
7
- //
8
- // 只写真值,不写密钥、不写正文。
9
-
10
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
11
- import { homedir } from 'node:os';
12
- import { dirname, join } from 'node:path';
13
-
14
- /** 账本保留的最近投递条数。 */
15
- const RECENT_LIMIT = 50;
16
-
17
- /**
18
- * 解析账本路径:配置优先,其次 $DSH_HOME/dsh-notify-p/state.json,
19
- * 最后回退 ~/.dsh/dsh-notify-p/state.json。
20
- * @param {string} [configured] 配置里的 statePath
21
- * @returns {string} 绝对路径
22
- */
23
- export function resolveStatePath(configured) {
24
- if (typeof configured === 'string' && configured.trim() !== '') return configured.trim();
25
- const home = typeof process.env.DSH_HOME === 'string' && process.env.DSH_HOME !== ''
26
- ? process.env.DSH_HOME
27
- : join(homedir(), '.dsh');
28
- return join(home, 'dsh-notify-p', 'state.json');
29
- }
30
-
31
- /**
32
- * 建一个投递账本。
33
- * @param {object} [options] 参数
34
- * @param {string} [options.path] 账本路径
35
- * @param {object} [options.logger] 日志器
36
- * @returns {{ record: (entry: object) => void, snapshot: () => object, flush: () => void, path: string }}
37
- */
38
- export function createLedger(options = {}) {
39
- const logger = options.logger ?? {};
40
- const path = resolveStatePath(options.path);
41
- const startedAt = new Date().toISOString();
42
- /** @type {object[]} */
43
- const recent = [];
44
- const counts = { sent: 0, failed: 0 };
45
-
46
- const persist = () => {
47
- const payload = {
48
- version: 1,
49
- startedAt,
50
- updatedAt: new Date().toISOString(),
51
- counts,
52
- recent,
53
- };
54
- try {
55
- mkdirSync(dirname(path), { recursive: true });
56
- writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
57
- } catch (error) {
58
- // 账本写不进去不能影响发信本身
59
- logger.debug?.(`dsh-notify-p: 账本写入失败 / ledger write failed: ${String(error)}`);
60
- }
61
- };
62
-
63
- // 进程一起来就先落一个文件,避免"文件不存在"被误读成"插件没加载"
64
- persist();
65
-
66
- return {
67
- path,
68
- /**
69
- * 记一条投递结果。
70
- * @param {object} entry 结果(at/ok/outcome/sessionId/subject/error/attempts)
71
- * @returns {void}
72
- */
73
- record(entry) {
74
- if (entry && entry.ok === true) counts.sent += 1;
75
- else counts.failed += 1;
76
- recent.push(entry);
77
- while (recent.length > RECENT_LIMIT) recent.shift();
78
- persist();
79
- },
80
- /**
81
- * 当前账本内容。
82
- * @returns {object}
83
- */
84
- snapshot() {
85
- return { version: 1, startedAt, updatedAt: new Date().toISOString(), counts, recent: [...recent] };
86
- },
87
- flush: persist,
88
- };
89
- }
90
-
91
- /**
92
- * 读一个账本文件(排错用)。
93
- * @param {string} path 账本路径
94
- * @returns {object|undefined}
95
- */
96
- export function readLedger(path) {
97
- try {
98
- return JSON.parse(readFileSync(path, 'utf8'));
99
- } catch {
100
- return undefined;
101
- }
102
- }