dsh-task-ledger 0.0.73 → 0.0.75

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.
Files changed (90) hide show
  1. package/dist/client.js +128 -128
  2. package/dist/client.js.map +1 -1
  3. package/dist/host/board.d.ts +4 -4
  4. package/dist/host/board.js +6 -6
  5. package/dist/host/errors.d.ts +3 -3
  6. package/dist/host/errors.js +13 -7
  7. package/dist/host/repositories/cache.d.ts +1 -1
  8. package/dist/host/repositories/cache.js +4 -2
  9. package/dist/host/repositories/identity.repository.d.ts +1 -1
  10. package/dist/host/repositories/identity.repository.js +14 -7
  11. package/dist/host/repositories/ledger.repository.d.ts +1 -1
  12. package/dist/host/repositories/ledger.repository.js +34 -23
  13. package/dist/host/repositories/records.d.ts +1 -1
  14. package/dist/host/repositories/records.js +3 -3
  15. package/dist/host/repositories/runner.d.ts +5 -4
  16. package/dist/host/repositories/runner.js +41 -19
  17. package/dist/host/routes.d.ts +4 -4
  18. package/dist/host/routes.js +145 -96
  19. package/dist/host/services/dates.js +4 -4
  20. package/dist/host/services/history.service.d.ts +5 -2
  21. package/dist/host/services/history.service.js +24 -13
  22. package/dist/host/services/home.service.d.ts +8 -2
  23. package/dist/host/services/home.service.js +29 -14
  24. package/dist/host/services/ledger-write.service.d.ts +8 -3
  25. package/dist/host/services/ledger-write.service.js +14 -9
  26. package/dist/host/services/session.service.d.ts +6 -6
  27. package/dist/host/services/session.service.js +47 -37
  28. package/dist/host/services/trend.service.d.ts +9 -2
  29. package/dist/host/services/trend.service.js +42 -21
  30. package/dist/host/skill/registration.d.ts +1 -1
  31. package/dist/host/skill/registration.js +16 -11
  32. package/dist/host/workspace.d.ts +1 -1
  33. package/dist/host/workspace.js +6 -2
  34. package/dist/index.js +12 -12
  35. package/package.json +57 -58
  36. package/skill/README.md +29 -245
  37. package/skill/SKILL.md +173 -88
  38. package/skill/package.json +22 -22
  39. package/skill/references/log-note.md +3 -1
  40. package/skill/references/state-model.md +29 -27
  41. package/skill/references/troubleshooting.md +98 -25
  42. package/skill/scripts/ledgerkit/__init__.py +0 -0
  43. package/skill/scripts/ledgerkit/actions/__init__.py +0 -0
  44. package/skill/scripts/ledgerkit/actions/atom.py +154 -0
  45. package/skill/scripts/ledgerkit/actions/bootstrap.py +27 -0
  46. package/skill/scripts/ledgerkit/actions/claim.py +82 -0
  47. package/skill/scripts/ledgerkit/actions/close_task.py +180 -0
  48. package/skill/scripts/ledgerkit/actions/guard.py +140 -0
  49. package/skill/scripts/ledgerkit/actions/handoff.py +74 -0
  50. package/skill/scripts/ledgerkit/actions/list_tasks.py +53 -0
  51. package/skill/scripts/ledgerkit/actions/log_progress.py +60 -0
  52. package/skill/scripts/ledgerkit/actions/new_task.py +120 -0
  53. package/skill/scripts/ledgerkit/actions/query.py +33 -0
  54. package/skill/scripts/ledgerkit/actions/query_history.py +173 -0
  55. package/skill/scripts/ledgerkit/actions/registry.py +82 -0
  56. package/skill/scripts/ledgerkit/actions/release.py +51 -0
  57. package/skill/scripts/ledgerkit/actions/result.py +88 -0
  58. package/skill/scripts/ledgerkit/actions/set_task.py +43 -0
  59. package/skill/scripts/ledgerkit/actions/show_task.py +27 -0
  60. package/skill/scripts/ledgerkit/actions/validate.py +224 -0
  61. package/skill/scripts/ledgerkit/core/__init__.py +0 -0
  62. package/skill/scripts/ledgerkit/core/history_md.py +350 -0
  63. package/skill/scripts/ledgerkit/core/identity.py +43 -0
  64. package/skill/scripts/ledgerkit/core/ids.py +58 -0
  65. package/skill/scripts/ledgerkit/core/labels.py +79 -0
  66. package/skill/scripts/ledgerkit/core/paths.py +93 -0
  67. package/skill/scripts/ledgerkit/core/time.py +102 -0
  68. package/skill/scripts/ledgerkit/core/worklog_md.py +232 -0
  69. package/skill/scripts/ledgerkit/env.py +93 -0
  70. package/skill/scripts/ledgerkit/errors.py +15 -0
  71. package/skill/scripts/ledgerkit/notices.py +28 -0
  72. package/skill/scripts/ledgerkit/shell/__init__.py +0 -0
  73. package/skill/scripts/ledgerkit/shell/cli.py +259 -0
  74. package/skill/scripts/ledgerkit/shell/gitops.py +655 -0
  75. package/skill/scripts/ledgerkit/shell/merge.py +266 -0
  76. package/skill/scripts/ledgerkit/shell/presync.py +23 -0
  77. package/skill/scripts/ledgerkit/shell/render.py +165 -0
  78. package/skill/scripts/ledgerkit/shell/runner.py +225 -0
  79. package/skill/scripts/ledgerkit/store/__init__.py +0 -0
  80. package/skill/scripts/ledgerkit/store/atomic.py +130 -0
  81. package/skill/scripts/ledgerkit/store/dirty.py +108 -0
  82. package/skill/scripts/ledgerkit/store/history_store.py +189 -0
  83. package/skill/scripts/ledgerkit/store/json_store.py +166 -0
  84. package/skill/scripts/ledgerkit/store/locks.py +64 -0
  85. package/skill/scripts/ledgerkit/store/owners_index.py +135 -0
  86. package/skill/scripts/ledgerkit/store/task_store.py +31 -0
  87. package/skill/scripts/ledgerkit/store/worklog_store.py +168 -0
  88. package/skill/scripts/progress.py +8 -3021
  89. package/skill/references/taxonomy.md +0 -26
  90. package/skill/references/worklog.md +0 -39
@@ -1,15 +1,15 @@
1
- import { getHomeAggregate, getFilterOptions, queryClosed } from './board.js';
2
- import { getSnapshot } from './services/home.service.js';
3
- import { getTrend } from './services/trend.service.js';
4
- import { claimTask, releaseTask } from './services/ledger-write.service.js';
5
- import { getTask, readTasks, readWorklog } from './repositories/ledger.repository.js';
6
- import { invalidate } from './repositories/cache.js';
7
- import { HandoffError, startCloseSession, } from './services/session.service.js';
8
- import { resolveWorkspaces } from './workspace.js';
1
+ import { getFilterOptions, getHomeAggregate, queryClosed } from "./board.js";
2
+ import { invalidate } from "./repositories/cache.js";
3
+ import { getTask, readTasks, readWorklog, } from "./repositories/ledger.repository.js";
4
+ import { getSnapshot } from "./services/home.service.js";
5
+ import { claimTask, releaseTask } from "./services/ledger-write.service.js";
6
+ import { HandoffError, startCloseSession, } from "./services/session.service.js";
7
+ import { getTrend } from "./services/trend.service.js";
8
+ import { resolveWorkspaces } from "./workspace.js";
9
9
  /** API 前缀(唯一真源就在这一处)。 */
10
- const API_PREFIX = '/api/task-ledger';
10
+ const API_PREFIX = "/api/task-ledger";
11
11
  function sendJson(res, status, body) {
12
- res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
12
+ res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
13
13
  res.end(JSON.stringify(body));
14
14
  }
15
15
  /** URL query → LedgerFilter(只认白名单键;limit/months 非正数忽略)。 */
@@ -17,7 +17,7 @@ function filterFromQuery(url) {
17
17
  const filter = {};
18
18
  const str = (key) => {
19
19
  const value = url.searchParams.get(key);
20
- return value !== null && value !== '' ? value : null;
20
+ return value !== null && value !== "" ? value : null;
21
21
  };
22
22
  const int = (key) => {
23
23
  const raw = str(key);
@@ -26,28 +26,28 @@ function filterFromQuery(url) {
26
26
  const value = Number.parseInt(raw, 10);
27
27
  return Number.isFinite(value) && value > 0 ? value : undefined;
28
28
  };
29
- const status = str('status');
29
+ const status = str("status");
30
30
  if (status !== null)
31
31
  filter.status = status;
32
- const owner = str('owner');
32
+ const owner = str("owner");
33
33
  if (owner !== null)
34
34
  filter.owner = owner;
35
- const category = str('category');
35
+ const category = str("category");
36
36
  if (category !== null)
37
37
  filter.category = category;
38
- const tag = str('tag');
38
+ const tag = str("tag");
39
39
  if (tag !== null)
40
40
  filter.tag = tag;
41
- const since = str('since');
41
+ const since = str("since");
42
42
  if (since !== null)
43
43
  filter.since = since;
44
- const until = str('until');
44
+ const until = str("until");
45
45
  if (until !== null)
46
46
  filter.until = until;
47
- const limit = int('limit');
47
+ const limit = int("limit");
48
48
  if (limit !== undefined)
49
49
  filter.limit = limit;
50
- const months = int('months');
50
+ const months = int("months");
51
51
  if (months !== undefined)
52
52
  filter.months = months;
53
53
  return filter;
@@ -63,96 +63,124 @@ export function registerRoutes(webServer, deps) {
63
63
  */
64
64
  const registerRead = (path, handler) => {
65
65
  webServer.register({
66
- kind: 'exact',
66
+ kind: "exact",
67
67
  path: `${API_PREFIX}${path}`,
68
68
  handler: async (req, res) => {
69
- if (new URL(req.url ?? '/', 'http://localhost').searchParams.get('fresh') === '1')
69
+ if (new URL(req.url ?? "/", "http://localhost").searchParams.get("fresh") === "1")
70
70
  invalidate();
71
71
  await handler(req, res);
72
72
  },
73
73
  });
74
74
  };
75
- registerRead('/workspaces', (_req, res) => { sendJson(res, 200, { workspaces: workspacesOf(deps) }); });
75
+ registerRead("/workspaces", (_req, res) => {
76
+ sendJson(res, 200, { workspaces: workspacesOf(deps) });
77
+ });
76
78
  // 首页一次拿全:在办快照 + 近 8 周趋势 + 四个自然周期计数(host 每个工作区只读一次)。
77
- registerRead('/board', async (_req, res) => {
79
+ registerRead("/board", async (_req, res) => {
78
80
  try {
79
81
  const home = await getHomeAggregate(workspacesOf(deps));
80
- sendJson(res, 200, { ...home.snapshot, closed: home.closed, trend: home.trend, degraded: home.degraded });
82
+ sendJson(res, 200, {
83
+ ...home.snapshot,
84
+ closed: home.closed,
85
+ trend: home.trend,
86
+ degraded: home.degraded,
87
+ });
81
88
  }
82
89
  catch (error) {
83
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
90
+ sendJson(res, 500, {
91
+ error: error instanceof Error ? error.message : String(error),
92
+ });
84
93
  }
85
94
  });
86
95
  // ── 拆端点(2026-10-18 步骤 4a:纯加法,旧 /board 全量原样保留)──
87
96
  // 快路径:首屏。只读 progress.json + git 身份,**不碰 history** ⇒ 耗时与历史总量解耦
88
97
  // (20 万条量级实测 527ms vs 合并端点 4.4s)。
89
- registerRead('/board/snapshot', async (_req, res) => {
98
+ registerRead("/board/snapshot", async (_req, res) => {
90
99
  try {
91
100
  const result = await getSnapshot(workspacesOf(deps));
92
101
  sendJson(res, 200, { ...result.snapshot, degraded: result.degraded });
93
102
  }
94
103
  catch (error) {
95
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
104
+ sendJson(res, 500, {
105
+ error: error instanceof Error ? error.message : String(error),
106
+ });
96
107
  }
97
108
  });
98
109
  // 慢路径:趋势(近 8 周 + 四个自然周期)。读 history ⇒ 异步/按需拉,首屏不等它。
99
- registerRead('/board/trend', async (_req, res) => {
110
+ registerRead("/board/trend", async (_req, res) => {
100
111
  try {
101
112
  const result = await getTrend(workspacesOf(deps));
102
- sendJson(res, 200, { closed: result.closed, trend: result.trend, degraded: result.degraded });
113
+ sendJson(res, 200, {
114
+ closed: result.closed,
115
+ trend: result.trend,
116
+ degraded: result.degraded,
117
+ });
103
118
  }
104
119
  catch (error) {
105
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
120
+ sendJson(res, 500, {
121
+ error: error instanceof Error ? error.message : String(error),
122
+ });
106
123
  }
107
124
  });
108
- registerRead('/tasks', async (req, res) => {
125
+ registerRead("/tasks", async (req, res) => {
109
126
  try {
110
- const filter = filterFromQuery(new URL(req.url ?? '/', 'http://localhost'));
127
+ const filter = filterFromQuery(new URL(req.url ?? "/", "http://localhost"));
111
128
  const perWorkspace = await Promise.all(workspacesOf(deps).map((workspace) => readTasks(workspace, filter)));
112
129
  sendJson(res, 200, { tasks: perWorkspace.flat() });
113
130
  }
114
131
  catch (error) {
115
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
132
+ sendJson(res, 500, {
133
+ error: error instanceof Error ? error.message : String(error),
134
+ });
116
135
  }
117
136
  });
118
137
  // 任务详情 + 过程文档正文(query 传 ws/id,不做路径段解析——任务号含中文和连字符)。
119
- registerRead('/task', async (req, res) => {
138
+ registerRead("/task", async (req, res) => {
120
139
  try {
121
- const url = new URL(req.url ?? '/', 'http://localhost');
122
- const workspace = workspacesOf(deps).find((item) => item.id === url.searchParams.get('ws'));
123
- const taskId = url.searchParams.get('id');
140
+ const url = new URL(req.url ?? "/", "http://localhost");
141
+ const workspace = workspacesOf(deps).find((item) => item.id === url.searchParams.get("ws"));
142
+ const taskId = url.searchParams.get("id");
124
143
  if (workspace === undefined || taskId === null) {
125
- sendJson(res, 400, { error: '缺少 ws / id 参数' });
144
+ sendJson(res, 400, { error: "缺少 ws / id 参数" });
126
145
  return;
127
146
  }
128
147
  const task = await getTask(workspace, taskId);
129
148
  if (task === null) {
130
- sendJson(res, 404, { error: '任务不存在' });
149
+ sendJson(res, 404, { error: "任务不存在" });
131
150
  return;
132
151
  }
133
152
  sendJson(res, 200, { task, worklog: await readWorklog(workspace, task) });
134
153
  }
135
154
  catch (error) {
136
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
155
+ sendJson(res, 500, {
156
+ error: error instanceof Error ? error.message : String(error),
157
+ });
137
158
  }
138
159
  });
139
- registerRead('/history', async (req, res) => {
160
+ registerRead("/history", async (req, res) => {
140
161
  try {
141
- const filter = filterFromQuery(new URL(req.url ?? '/', 'http://localhost'));
162
+ const filter = filterFromQuery(new URL(req.url ?? "/", "http://localhost"));
142
163
  const result = await queryClosed(workspacesOf(deps), filter);
143
- sendJson(res, 200, { records: result.records, degraded: result.degraded });
164
+ sendJson(res, 200, {
165
+ records: result.records,
166
+ degraded: result.degraded,
167
+ });
144
168
  }
145
169
  catch (error) {
146
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
170
+ sendJson(res, 500, {
171
+ error: error instanceof Error ? error.message : String(error),
172
+ });
147
173
  }
148
174
  });
149
- registerRead('/filters', async (_req, res) => {
175
+ registerRead("/filters", async (_req, res) => {
150
176
  try {
151
177
  const result = await getFilterOptions(workspacesOf(deps));
152
178
  sendJson(res, 200, { ...result.options, degraded: result.degraded });
153
179
  }
154
180
  catch (error) {
155
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
181
+ sendJson(res, 500, {
182
+ error: error instanceof Error ? error.message : String(error),
183
+ });
156
184
  }
157
185
  });
158
186
  }
@@ -166,15 +194,15 @@ export function registerRoutes(webServer, deps) {
166
194
  export function registerWriteRoutes(webServer, deps) {
167
195
  const registerWrite = (path, action) => {
168
196
  webServer.register({
169
- kind: 'exact',
197
+ kind: "exact",
170
198
  path: `${API_PREFIX}${path}`,
171
199
  handler: async (req, res) => {
172
- if (req.method !== 'POST') {
173
- sendJson(res, 405, { error: '只支持 POST' });
200
+ if (req.method !== "POST") {
201
+ sendJson(res, 405, { error: "只支持 POST" });
174
202
  return;
175
203
  }
176
204
  if (!isTrustedRequest(req)) {
177
- sendJson(res, 403, { error: '只接受同源请求' });
205
+ sendJson(res, 403, { error: "只接受同源请求" });
178
206
  return;
179
207
  }
180
208
  let body;
@@ -182,21 +210,25 @@ export function registerWriteRoutes(webServer, deps) {
182
210
  body = await readJsonBody(req);
183
211
  }
184
212
  catch (error) {
185
- const tooLarge = error instanceof Error && error.message === 'body-too-large';
186
- sendJson(res, tooLarge ? 413 : 400, { error: tooLarge ? '请求体过大' : '请求体不是合法 JSON' });
213
+ const tooLarge = error instanceof Error && error.message === "body-too-large";
214
+ sendJson(res, tooLarge ? 413 : 400, {
215
+ error: tooLarge ? "请求体过大" : "请求体不是合法 JSON",
216
+ });
187
217
  return;
188
218
  }
189
- const fields = typeof body === 'object' && body !== null ? body : {};
190
- const str = (key) => (typeof fields[key] === 'string' ? fields[key] : '');
191
- const workspaceId = str('ws');
192
- const taskId = str('id');
193
- if (workspaceId === '' || taskId === '') {
194
- sendJson(res, 400, { error: '缺少 ws / id 参数' });
219
+ const fields = typeof body === "object" && body !== null
220
+ ? body
221
+ : {};
222
+ const str = (key) => typeof fields[key] === "string" ? fields[key] : "";
223
+ const workspaceId = str("ws");
224
+ const taskId = str("id");
225
+ if (workspaceId === "" || taskId === "") {
226
+ sendJson(res, 400, { error: "缺少 ws / id 参数" });
195
227
  return;
196
228
  }
197
229
  const workspace = workspacesOf(deps).find((item) => item.id === workspaceId);
198
230
  if (workspace === undefined) {
199
- sendJson(res, 404, { error: '工作区不存在或不含台账' });
231
+ sendJson(res, 404, { error: "工作区不存在或不含台账" });
200
232
  return;
201
233
  }
202
234
  try {
@@ -206,13 +238,15 @@ export function registerWriteRoutes(webServer, deps) {
206
238
  sendJson(res, 200, notice ? { ok: true, notice } : { ok: true });
207
239
  }
208
240
  catch (error) {
209
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
241
+ sendJson(res, 500, {
242
+ error: error instanceof Error ? error.message : String(error),
243
+ });
210
244
  }
211
245
  },
212
246
  });
213
247
  };
214
- registerWrite('/claim', claimTask);
215
- registerWrite('/release', releaseTask);
248
+ registerWrite("/claim", claimTask);
249
+ registerWrite("/release", releaseTask);
216
250
  }
217
251
  // ───────────────────────── 会话交接(按钮侧 · 写类端点) ─────────────────────────
218
252
  /** 写端点请求体上限:只收一小段用户留言,别让人往这塞文件。 */
@@ -225,11 +259,11 @@ async function readJsonBody(req) {
225
259
  const buffer = chunk;
226
260
  size += buffer.length;
227
261
  if (size > BODY_LIMIT)
228
- throw new Error('body-too-large');
262
+ throw new Error("body-too-large");
229
263
  chunks.push(buffer);
230
264
  }
231
- const text = Buffer.concat(chunks).toString('utf8');
232
- if (text.trim() === '')
265
+ const text = Buffer.concat(chunks).toString("utf8");
266
+ if (text.trim() === "")
233
267
  return {};
234
268
  return JSON.parse(text);
235
269
  }
@@ -239,11 +273,13 @@ async function readJsonBody(req) {
239
273
  * 读端点保持现状(只读无副作用);**会起会话的写端点必须过这道**。
240
274
  */
241
275
  function isTrustedRequest(req) {
242
- const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined;
243
- const host = typeof req.headers.host === 'string' ? req.headers.host : undefined;
276
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : undefined;
277
+ const host = typeof req.headers.host === "string" ? req.headers.host : undefined;
244
278
  if (origin === undefined) {
245
279
  const address = req.socket.remoteAddress;
246
- return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
280
+ return (address === "127.0.0.1" ||
281
+ address === "::1" ||
282
+ address === "::ffff:127.0.0.1");
247
283
  }
248
284
  if (host === undefined)
249
285
  return false;
@@ -264,15 +300,15 @@ function isTrustedRequest(req) {
264
300
  */
265
301
  export function registerSessionRoutes(webServer, deps) {
266
302
  webServer.register({
267
- kind: 'exact',
303
+ kind: "exact",
268
304
  path: `${API_PREFIX}/close`,
269
305
  handler: async (req, res) => {
270
- if (req.method !== 'POST') {
271
- sendJson(res, 405, { error: '「结束任务」只支持 POST' });
306
+ if (req.method !== "POST") {
307
+ sendJson(res, 405, { error: "「结束任务」只支持 POST" });
272
308
  return;
273
309
  }
274
310
  if (!isTrustedRequest(req)) {
275
- sendJson(res, 403, { error: '只接受同源请求' });
311
+ sendJson(res, 403, { error: "只接受同源请求" });
276
312
  return;
277
313
  }
278
314
  let body;
@@ -280,52 +316,65 @@ export function registerSessionRoutes(webServer, deps) {
280
316
  body = await readJsonBody(req);
281
317
  }
282
318
  catch (error) {
283
- const tooLarge = error instanceof Error && error.message === 'body-too-large';
284
- sendJson(res, tooLarge ? 413 : 400, { error: tooLarge ? '请求体过大' : '请求体不是合法 JSON' });
319
+ const tooLarge = error instanceof Error && error.message === "body-too-large";
320
+ sendJson(res, tooLarge ? 413 : 400, {
321
+ error: tooLarge ? "请求体过大" : "请求体不是合法 JSON",
322
+ });
285
323
  return;
286
324
  }
287
- const fields = typeof body === 'object' && body !== null ? body : {};
288
- const str = (key) => (typeof fields[key] === 'string' ? fields[key] : '');
289
- const workspaceId = str('ws');
290
- const taskId = str('id');
291
- if (workspaceId === '' || taskId === '') {
292
- sendJson(res, 400, { error: '缺少 ws / id 参数' });
325
+ const fields = typeof body === "object" && body !== null
326
+ ? body
327
+ : {};
328
+ const str = (key) => typeof fields[key] === "string" ? fields[key] : "";
329
+ const workspaceId = str("ws");
330
+ const taskId = str("id");
331
+ if (workspaceId === "" || taskId === "") {
332
+ sendJson(res, 400, { error: "缺少 ws / id 参数" });
293
333
  return;
294
334
  }
295
- const provider = str('provider');
296
- const model = str('model');
297
- if ((provider === '') !== (model === '')) {
298
- sendJson(res, 400, { error: 'provider 与 model 必须成对给出' });
335
+ const provider = str("provider");
336
+ const model = str("model");
337
+ if ((provider === "") !== (model === "")) {
338
+ sendJson(res, 400, { error: "provider 与 model 必须成对给出" });
299
339
  return;
300
340
  }
301
341
  const workspace = workspacesOf(deps).find((item) => item.id === workspaceId);
302
342
  if (workspace === undefined) {
303
- sendJson(res, 404, { error: '工作区不存在或不含台账' });
343
+ sendJson(res, 404, { error: "工作区不存在或不含台账" });
304
344
  return;
305
345
  }
306
346
  try {
307
347
  const result = await startCloseSession({
308
348
  agents: deps.agents,
309
- presets: deps.getService('agentPresets'),
310
- defaultModel: deps.getService('agentDefaultModel'),
349
+ presets: deps.getService("agentPresets"),
350
+ defaultModel: deps.getService("agentDefaultModel"),
311
351
  }, {
312
352
  workspace,
313
353
  taskId,
314
- message: str('message'),
315
- model: provider === '' ? undefined : { provider, model },
354
+ message: str("message"),
355
+ model: provider === "" ? undefined : { provider, model },
316
356
  });
317
- const warning = await attachSession(deps, workspaceId, result.sessionId) ?? result.warning;
318
- sendJson(res, 200, warning === undefined ? { sessionId: result.sessionId } : { sessionId: result.sessionId, warning });
357
+ const warning = (await attachSession(deps, workspaceId, result.sessionId)) ??
358
+ result.warning;
359
+ sendJson(res, 200, warning === undefined
360
+ ? { sessionId: result.sessionId }
361
+ : { sessionId: result.sessionId, warning });
319
362
  }
320
363
  catch (error) {
321
364
  if (error instanceof HandoffError) {
322
- const status = error.code === 'task-missing'
365
+ const status = error.code === "task-missing"
323
366
  ? 404
324
- : error.code === 'no-agents' || error.code === 'no-preset' || error.code === 'no-model' ? 503 : 500;
367
+ : error.code === "no-agents" ||
368
+ error.code === "no-preset" ||
369
+ error.code === "no-model"
370
+ ? 503
371
+ : 500;
325
372
  sendJson(res, status, { error: error.message, code: error.code });
326
373
  return;
327
374
  }
328
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
375
+ sendJson(res, 500, {
376
+ error: error instanceof Error ? error.message : String(error),
377
+ });
329
378
  }
330
379
  },
331
380
  });
@@ -336,8 +385,8 @@ export function registerSessionRoutes(webServer, deps) {
336
385
  */
337
386
  async function attachSession(deps, workspaceId, sessionId) {
338
387
  const entity = deps.registry.list().find((item) => item.id === workspaceId);
339
- if (entity === undefined || typeof entity.attachSession !== 'function') {
340
- return '会话已建,但宿主该工作区没有 attachSession 面:会话不会出现在工作区列表里';
388
+ if (entity === undefined || typeof entity.attachSession !== "function") {
389
+ return "会话已建,但宿主该工作区没有 attachSession 面:会话不会出现在工作区列表里";
341
390
  }
342
391
  try {
343
392
  await entity.attachSession(sessionId);
@@ -3,7 +3,7 @@
3
3
  * 所有函数显式收 `now`——不自己读时钟,便于单测与跨时区复现。
4
4
  */
5
5
  function pad(value) {
6
- return String(value).padStart(2, '0');
6
+ return String(value).padStart(2, "0");
7
7
  }
8
8
  /** Date → `YYYY-MM-DD`。 */
9
9
  export function ymd(date) {
@@ -11,7 +11,7 @@ export function ymd(date) {
11
11
  }
12
12
  /** 时刻串(`YYYY-MM-DD HH:mm:ss` / `YYYY-MM-DD`)→ 日期部分;解析不了回 null。 */
13
13
  export function dayOf(stamp) {
14
- if (stamp === null || stamp === undefined || stamp === '')
14
+ if (stamp === null || stamp === undefined || stamp === "")
15
15
  return null;
16
16
  const day = String(stamp).slice(0, 10);
17
17
  return /^\d{4}-\d{2}-\d{2}$/.test(day) ? day : null;
@@ -49,9 +49,9 @@ export function inWindow(day, window) {
49
49
  }
50
50
  /** 「YYYY-MM-DD HH:mm:ss」→ 距今天数(解析不了 / 未来都按 0;不编造)。 */
51
51
  export function daysSince(stamp, now) {
52
- if (stamp === null || stamp === undefined || stamp === '')
52
+ if (stamp === null || stamp === undefined || stamp === "")
53
53
  return null;
54
- const parsed = new Date(String(stamp).replace(' ', 'T'));
54
+ const parsed = new Date(String(stamp).replace(" ", "T"));
55
55
  if (Number.isNaN(parsed.getTime()))
56
56
  return null;
57
57
  const days = Math.floor((now.getTime() - parsed.getTime()) / 86_400_000);
@@ -1,5 +1,8 @@
1
- import { type DegradedInfo } from '../errors.js';
2
- import type { FilterOptions, HistoryRecord, LedgerFilter, LedgerWorkspace, TrendScope } from '../types.js';
1
+ /**
2
+ * 已结案列表 / 筛选项候选用例(history tab 与筛选控件用;非首屏,按需调用)。
3
+ */
4
+ import { type DegradedInfo } from "../errors.js";
5
+ import type { FilterOptions, HistoryRecord, LedgerFilter, LedgerWorkspace, TrendScope } from "../types.js";
3
6
  /** 已结案列表默认分页大小(「加载更多」= 调用方加大 limit 重取)。 */
4
7
  export declare const DEFAULT_HISTORY_LIMIT = 500;
5
8
  /**
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * 已结案列表 / 筛选项候选用例(history tab 与筛选控件用;非首屏,按需调用)。
3
3
  */
4
- import { readHistory } from '../repositories/ledger.repository.js';
5
- import { settleWorkspaces } from '../errors.js';
6
- import { RECENT_MONTHS, readTrendScope } from './trend.service.js';
4
+ import { settleWorkspaces } from "../errors.js";
5
+ import { readHistory } from "../repositories/ledger.repository.js";
6
+ import { RECENT_MONTHS, readTrendScope } from "./trend.service.js";
7
7
  /** 已结案列表默认分页大小(「加载更多」= 调用方加大 limit 重取)。 */
8
8
  export const DEFAULT_HISTORY_LIMIT = 500;
9
9
  /**
@@ -14,8 +14,9 @@ export const DEFAULT_HISTORY_LIMIT = 500;
14
14
  */
15
15
  export async function queryClosed(workspaces, filter = {}) {
16
16
  const windowed = { months: RECENT_MONTHS, ...filter };
17
- const { data, degraded } = await settleWorkspaces(workspaces, 'history', (workspace) => readHistory(workspace, windowed), (rows) => rows.flat()
18
- .sort((a, b) => String(b.created ?? b.time ?? '').localeCompare(String(a.created ?? a.time ?? '')))
17
+ const { data, degraded } = await settleWorkspaces(workspaces, "history", (workspace) => readHistory(workspace, windowed), (rows) => rows
18
+ .flat()
19
+ .sort((a, b) => String(b.created ?? b.time ?? "").localeCompare(String(a.created ?? a.time ?? "")))
19
20
  .slice(0, filter.limit ?? DEFAULT_HISTORY_LIMIT));
20
21
  return { records: data, degraded };
21
22
  }
@@ -25,19 +26,25 @@ export function collectFilterOptions(scopes) {
25
26
  const categories = new Set();
26
27
  const tags = new Set();
27
28
  const addTask = (task) => {
28
- if (task.owner !== undefined && task.owner !== null && task.owner !== '')
29
+ if (task.owner !== undefined && task.owner !== null && task.owner !== "")
29
30
  owners.add(task.owner);
30
- if (task.creator !== undefined && task.creator !== null && task.creator !== '')
31
+ if (task.creator !== undefined &&
32
+ task.creator !== null &&
33
+ task.creator !== "")
31
34
  owners.add(task.creator);
32
- if (task.category !== undefined && task.category !== null && task.category !== '')
35
+ if (task.category !== undefined &&
36
+ task.category !== null &&
37
+ task.category !== "")
33
38
  categories.add(task.category);
34
39
  for (const tag of task.tags ?? [])
35
40
  tags.add(tag);
36
41
  };
37
42
  const addRecord = (record) => {
38
- if (record.owner !== undefined && record.owner !== '')
43
+ if (record.owner !== undefined && record.owner !== "")
39
44
  owners.add(record.owner);
40
- if (record.category !== undefined && record.category !== null && record.category !== '')
45
+ if (record.category !== undefined &&
46
+ record.category !== null &&
47
+ record.category !== "")
41
48
  categories.add(record.category);
42
49
  for (const tag of record.tags ?? [])
43
50
  tags.add(tag);
@@ -48,14 +55,18 @@ export function collectFilterOptions(scopes) {
48
55
  for (const record of scope.history)
49
56
  addRecord(record);
50
57
  }
51
- const sorted = (values) => [...values].sort((a, b) => a.localeCompare(b, 'zh-Hans-CN'));
52
- return { owners: sorted(owners), categories: sorted(categories), tags: sorted(tags) };
58
+ const sorted = (values) => [...values].sort((a, b) => a.localeCompare(b, "zh-Hans-CN"));
59
+ return {
60
+ owners: sorted(owners),
61
+ categories: sorted(categories),
62
+ tags: sorted(tags),
63
+ };
53
64
  }
54
65
  /**
55
66
  * 筛选项候选:在办(全量)+ 近 13 个月历史的去重值(owner / category / tag)。
56
67
  * 按需调用(/filters 路由),非首屏——历史侧在极端量级下是百毫秒级。单工作区坏 ⇒ 进 degraded。
57
68
  */
58
69
  export async function getFilterOptions(workspaces) {
59
- const { data, degraded } = await settleWorkspaces(workspaces, 'history', readTrendScope, collectFilterOptions);
70
+ const { data, degraded } = await settleWorkspaces(workspaces, "history", readTrendScope, collectFilterOptions);
60
71
  return { options: data, degraded };
61
72
  }
@@ -1,5 +1,11 @@
1
- import { type DegradedInfo } from '../errors.js';
2
- import type { BoardSnapshot, LedgerWorkspace, TaskScope } from '../types.js';
1
+ /**
2
+ * 首页**快路径**用例:在办快照 + 统计卡。
3
+ * 只读 progress.json(list)+ git 身份,**不碰 history**——首屏耗时与历史总量解耦
4
+ * (20 万条量级实测:527ms vs 合并端点 4.4s,见 docs/design/host-data-plane.md §四)。
5
+ * 慢路径(趋势)在 ./trend.service.ts;组合入口(/board 兼容全量)在 ../board.ts。
6
+ */
7
+ import { type DegradedInfo } from "../errors.js";
8
+ import type { BoardSnapshot, LedgerWorkspace, TaskScope } from "../types.js";
3
9
  /** 停滞阈值:进行中且 updated 至今满这么多天没更新(2026-10-19 拍板,显示级)。 */
4
10
  export declare const STALE_DAYS = 3;
5
11
  /** 逾期阈值:进行中且 started 至今满这么多天还没做完——中途更新不影响(2026-10-19 拍板 7 → 14 天)。 */