dsh-task-ledger 0.0.113 → 0.0.114

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 (43) hide show
  1. package/dist/client.js +50739 -1529
  2. package/dist/client.js.map +1 -1
  3. package/dist/host/errors.d.ts +2 -2
  4. package/dist/host/repositories/cache.d.ts +2 -3
  5. package/dist/host/repositories/cache.js +1 -2
  6. package/dist/host/repositories/identity.repository.d.ts +8 -2
  7. package/dist/host/repositories/identity.repository.js +9 -29
  8. package/dist/host/repositories/ledger.repository.d.ts +12 -22
  9. package/dist/host/repositories/ledger.repository.js +39 -58
  10. package/dist/host/repositories/records.d.ts +1 -11
  11. package/dist/host/repositories/records.js +16 -42
  12. package/dist/host/repositories/runner.d.ts +0 -30
  13. package/dist/host/repositories/runner.js +0 -23
  14. package/dist/host/repositories/stats.repository.d.ts +36 -0
  15. package/dist/host/repositories/stats.repository.js +86 -0
  16. package/dist/host/routes.d.ts +4 -25
  17. package/dist/host/routes.js +7 -384
  18. package/dist/host/services/dates.d.ts +3 -24
  19. package/dist/host/services/dates.js +3 -63
  20. package/dist/host/services/stats.service.d.ts +7 -0
  21. package/dist/host/services/stats.service.js +134 -0
  22. package/dist/host/skill/registration.d.ts +0 -3
  23. package/dist/host/skill/registration.js +0 -3
  24. package/dist/host/types.d.ts +70 -126
  25. package/dist/host/workspace.d.ts +3 -4
  26. package/dist/host/workspace.js +5 -5
  27. package/dist/index.js +2 -33
  28. package/package.json +2 -2
  29. package/skill/SKILL.md +1 -0
  30. package/dist/host/board.d.ts +0 -20
  31. package/dist/host/board.js +0 -29
  32. package/dist/host/services/history.service.d.ts +0 -27
  33. package/dist/host/services/history.service.js +0 -72
  34. package/dist/host/services/home.service.d.ts +0 -23
  35. package/dist/host/services/home.service.js +0 -91
  36. package/dist/host/services/ledger-write.service.d.ts +0 -10
  37. package/dist/host/services/ledger-write.service.js +0 -32
  38. package/dist/host/services/session.service.d.ts +0 -110
  39. package/dist/host/services/session.service.js +0 -155
  40. package/dist/host/services/trend.service.d.ts +0 -36
  41. package/dist/host/services/trend.service.js +0 -162
  42. package/dist/host/services/uuid.d.ts +0 -11
  43. package/dist/host/services/uuid.js +0 -15
@@ -1,72 +0,0 @@
1
- /**
2
- * 已结案列表 / 筛选项候选用例(history tab 与筛选控件用;非首屏,按需调用)。
3
- */
4
- import { settleWorkspaces } from "../errors.js";
5
- import { readHistory } from "../repositories/ledger.repository.js";
6
- import { RECENT_MONTHS, readTrendScope } from "./trend.service.js";
7
- /** 已结案列表默认分页大小(「加载更多」= 调用方加大 limit 重取)。 */
8
- export const DEFAULT_HISTORY_LIMIT = 500;
9
- /**
10
- * 已结案列表:默认近 13 个月 + limit 500,跨工作区合并后按创建时间倒序。
11
- * shortcut: 分页用「加大 limit 重取」的笨办法(skill 脚本无 offset);
12
- * 单工作区内 limit 已生效,合并边界的全局精确排序暂不保证(500 条一页无感)。
13
- * 要全量老数据:调用方显式传 months: undefined(不拼 --months,脚本读全部,按秒计)。
14
- */
15
- export async function queryClosed(workspaces, filter = {}) {
16
- const windowed = { months: RECENT_MONTHS, ...filter };
17
- const { data, degraded } = await settleWorkspaces(workspaces, "history", (workspace) => readHistory(workspace, windowed), (rows) => rows
18
- .flatMap((row) => row.records)
19
- .sort((a, b) => String(b.created ?? b.time ?? "").localeCompare(String(a.created ?? a.time ?? "")))
20
- .slice(0, filter.limit ?? DEFAULT_HISTORY_LIMIT));
21
- return { records: data, degraded };
22
- }
23
- /** 候选值收集(纯函数):owner/category/tag 去重 + 排序。 */
24
- export function collectFilterOptions(scopes) {
25
- const owners = new Set();
26
- const categories = new Set();
27
- const tags = new Set();
28
- const addTask = (task) => {
29
- if (task.owner !== undefined && task.owner !== null && task.owner !== "")
30
- owners.add(task.owner);
31
- if (task.creator !== undefined &&
32
- task.creator !== null &&
33
- task.creator !== "")
34
- owners.add(task.creator);
35
- if (task.category !== undefined &&
36
- task.category !== null &&
37
- task.category !== "")
38
- categories.add(task.category);
39
- for (const tag of task.tags ?? [])
40
- tags.add(tag);
41
- };
42
- const addRecord = (record) => {
43
- if (record.owner !== undefined && record.owner !== "")
44
- owners.add(record.owner);
45
- if (record.category !== undefined &&
46
- record.category !== null &&
47
- record.category !== "")
48
- categories.add(record.category);
49
- for (const tag of record.tags ?? [])
50
- tags.add(tag);
51
- };
52
- for (const scope of scopes) {
53
- for (const task of scope.tasks)
54
- addTask(task);
55
- for (const record of scope.history)
56
- addRecord(record);
57
- }
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
- };
64
- }
65
- /**
66
- * 筛选项候选:在办(全量)+ 近 13 个月历史的去重值(owner / category / tag)。
67
- * 按需调用(/filters 路由),非首屏——历史侧在极端量级下是百毫秒级。单工作区坏 ⇒ 进 degraded。
68
- */
69
- export async function getFilterOptions(workspaces) {
70
- const { data, degraded } = await settleWorkspaces(workspaces, "history", readTrendScope, collectFilterOptions);
71
- return { options: data, degraded };
72
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * 首页**快路径**用例:在办快照 + 统计卡。
3
- * 只读 progress.json(list)+ 本机身份,**不碰 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";
9
- /** 停滞阈值:进行中且 updated 至今满这么多天没更新(2026-10-19 拍板,显示级)。 */
10
- export declare const STALE_DAYS = 3;
11
- /** 逾期阈值:进行中且 started 至今满这么多天还没做完——中途更新不影响(2026-10-19 拍板 7 → 14 天)。 */
12
- export declare const OVERDUE_DAYS = 14;
13
- /** 积压阈值:待认领且 created 至今满这么多天还没人接(2026-10-19 拍板)。 */
14
- export declare const BACKLOG_DAYS = 30;
15
- /** 快路径读法:单工作区 readTasks + 本机身份(并行;供 settle 逐工作区调)。 */
16
- export declare function readTaskScope(workspace: LedgerWorkspace): Promise<TaskScope>;
17
- /** 在办行加工 + 六个统计面 + identities(纯函数;`now` 显式传入)。 */
18
- export declare function computeSnapshot(scopes: readonly TaskScope[], now: Date): BoardSnapshot;
19
- /** 快路径端点:在办快照(统计卡 + 身份 + 行加工)。单工作区坏 ⇒ 进 degraded,其余照常。 */
20
- export declare function getSnapshot(workspaces: readonly LedgerWorkspace[]): Promise<{
21
- snapshot: BoardSnapshot;
22
- degraded: readonly DegradedInfo[];
23
- }>;
@@ -1,91 +0,0 @@
1
- /**
2
- * 首页**快路径**用例:在办快照 + 统计卡。
3
- * 只读 progress.json(list)+ 本机身份,**不碰 history**——首屏耗时与历史总量解耦
4
- * (20 万条量级实测:527ms vs 合并端点 4.4s,见 docs/design/host-data-plane.md §四)。
5
- * 慢路径(趋势)在 ./trend.service.ts;组合入口(/board 兼容全量)在 ../board.ts。
6
- */
7
- import { settleWorkspaces } from "../errors.js";
8
- import { sameUuid } from "./uuid.js";
9
- import { getCurrentIdentity } from "../repositories/identity.repository.js";
10
- import { readTasks } from "../repositories/ledger.repository.js";
11
- import { daysSince } from "./dates.js";
12
- /** 停滞阈值:进行中且 updated 至今满这么多天没更新(2026-10-19 拍板,显示级)。 */
13
- export const STALE_DAYS = 3;
14
- /** 逾期阈值:进行中且 started 至今满这么多天还没做完——中途更新不影响(2026-10-19 拍板 7 → 14 天)。 */
15
- export const OVERDUE_DAYS = 14;
16
- /** 积压阈值:待认领且 created 至今满这么多天还没人接(2026-10-19 拍板)。 */
17
- export const BACKLOG_DAYS = 30;
18
- /** 快路径读法:单工作区 readTasks + 本机身份(并行;供 settle 逐工作区调)。 */
19
- export async function readTaskScope(workspace) {
20
- const [tasks, identity] = await Promise.all([
21
- readTasks(workspace),
22
- getCurrentIdentity(workspace),
23
- ]);
24
- return { workspace, tasks, identity };
25
- }
26
- /** 在办行加工 + 六个统计面 + identities(纯函数;`now` 显式传入)。 */
27
- export function computeSnapshot(scopes, now) {
28
- const withMeta = scopes.flatMap((scope) => scope.tasks.map((task) => {
29
- // blocked 只是旧数据(新任务不可能再有);读侧一律当进行中
30
- const working = task.status === "doing" || task.status === "blocked";
31
- const ageDays = working
32
- ? daysSince(task.started ?? task.created, now)
33
- : null;
34
- // 停滞看「最后更新」,逾期看「接任务至今」,积压看「创建至今还没人认领」——三个不同的问题,互不覆盖
35
- const idleDays = working
36
- ? daysSince(task.updated ?? task.started ?? task.created, now)
37
- : null;
38
- const idleOpen = task.status === "open" ? daysSince(task.created, now) : null;
39
- return {
40
- ...task,
41
- ageDays,
42
- // 两个天数只供悬停提示说人话(「已进行 N 天」「已停滞 N 天」「已等待 N 天」),不参与判定
43
- idleDays,
44
- waitingDays: idleOpen,
45
- stale: idleDays !== null && idleDays >= STALE_DAYS,
46
- overdue: ageDays !== null && ageDays >= OVERDUE_DAYS,
47
- backlog: idleOpen !== null && idleOpen >= BACKLOG_DAYS,
48
- };
49
- }));
50
- const openTasks = withMeta.filter((task) => task.status === "open");
51
- const myActive = scopes.reduce((sum, scope) => sum +
52
- scope.tasks.filter((task) =>
53
- // identity 为 null(本机没 init)时不许把 owner 为空的 doing 任务算成「我的」
54
- sameUuid(task.owner, scope.identity) &&
55
- (task.status === "doing" || task.status === "blocked")).length, 0);
56
- const identities = {};
57
- for (const scope of scopes)
58
- identities[scope.workspace.id] = scope.identity;
59
- const mine = (task) => sameUuid(task.owner, identities[task.workspace.id]);
60
- const backlogTasks = withMeta.filter((task) => task.backlog);
61
- const overdueTasks = withMeta.filter((task) => task.overdue);
62
- const staleTasks = withMeta.filter((task) => task.stale);
63
- const stats = {
64
- myActive,
65
- myOverdue: overdueTasks.filter(mine).length,
66
- myStale: staleTasks.filter(mine).length,
67
- open: openTasks.length,
68
- doing: withMeta.filter((task) => task.status === "doing" || task.status === "blocked").length,
69
- overdue: overdueTasks.length,
70
- stale: staleTasks.length,
71
- backlog: backlogTasks.length,
72
- staleDays: STALE_DAYS,
73
- overdueDays: OVERDUE_DAYS,
74
- backlogDays: BACKLOG_DAYS,
75
- oldestOpenDays: openTasks.length === 0
76
- ? null
77
- : openTasks.reduce((max, task) => Math.max(max, daysSince(task.created, now) ?? 0), 0),
78
- };
79
- return {
80
- at: now.toISOString(),
81
- workspaces: scopes.map((scope) => scope.workspace),
82
- tasks: withMeta,
83
- stats,
84
- identities,
85
- };
86
- }
87
- /** 快路径端点:在办快照(统计卡 + 身份 + 行加工)。单工作区坏 ⇒ 进 degraded,其余照常。 */
88
- export async function getSnapshot(workspaces) {
89
- const { data: scopes, degraded } = await settleWorkspaces(workspaces, "list", readTaskScope, (rows) => rows);
90
- return { snapshot: computeSnapshot(scopes, new Date()), degraded };
91
- }
@@ -1,10 +0,0 @@
1
- import type { LedgerWorkspace } from "../types.js";
2
- /** 认领:只有 open 且无人认领才能接(脚本内判定),owner = 本机 identity 文件里的 uuid。 */
3
- export declare function claimTask(workspace: LedgerWorkspace, taskId: string): Promise<string>;
4
- /**
5
- * 释放:写回公共池(owner / owner_name 清空、status 回 open),只许当前 owner 本人(脚本内判定)。
6
- *
7
- * 走 `set --release`。**不能用 `--owner ''`**:脚本不接受「空 owner = 释放」这种隐式语义,
8
- * 空串会被当成非法 uuid 直接退 1(2026-10-25 修,此前这条路必失败)。
9
- */
10
- export declare function releaseTask(workspace: LedgerWorkspace, taskId: string): Promise<string>;
@@ -1,32 +0,0 @@
1
- /**
2
- * 台账写方法(2026-10-18 拍板 B 混合制):认领 / 释放没有判断量,host 直跑 skill 脚本,**不开会话**。
3
- *
4
- * 身份:脚本只读本机 identity 文件里的 uuid 作为 owner(机器级、与工作区和 git 配置无关),
5
- * 宿主进程不传、也不推断身份。
6
- *
7
- * 校验全在脚本里(认领要求 open 且无 owner;释放要求是当前 owner),本层不重复实现。
8
- */
9
- import { runLedgerCommand, WRITE_TIMEOUT_MS } from "../repositories/runner.js";
10
- /** 返回脚本在退出码 0 时打到 stderr 的提示(同步失败等);没有提示就是空串。 */
11
- async function run(workspace, sub, args) {
12
- const result = await runLedgerCommand(sub, [...args, "--repo", workspace.path], {
13
- cwd: workspace.path,
14
- timeoutMs: WRITE_TIMEOUT_MS,
15
- });
16
- if (!result.ok)
17
- throw new Error(`${workspace.title}:${result.error}`);
18
- return result.notice ?? "";
19
- }
20
- /** 认领:只有 open 且无人认领才能接(脚本内判定),owner = 本机 identity 文件里的 uuid。 */
21
- export function claimTask(workspace, taskId) {
22
- return run(workspace, "claim", ["--id", taskId]);
23
- }
24
- /**
25
- * 释放:写回公共池(owner / owner_name 清空、status 回 open),只许当前 owner 本人(脚本内判定)。
26
- *
27
- * 走 `set --release`。**不能用 `--owner ''`**:脚本不接受「空 owner = 释放」这种隐式语义,
28
- * 空串会被当成非法 uuid 直接退 1(2026-10-25 修,此前这条路必失败)。
29
- */
30
- export function releaseTask(workspace, taskId) {
31
- return run(workspace, "set", ["--id", taskId, "--release"]);
32
- }
@@ -1,110 +0,0 @@
1
- import type { LedgerWorkspace } from "../types.js";
2
- /** 模型路由:provider 与 model 必须成对。 */
3
- export interface ModelRoute {
4
- readonly provider: string;
5
- readonly model: string;
6
- }
7
- /** 会话首条 user 消息(`@deepseek-ai/dsh-llm` UserMessage 的最小同形面)。 */
8
- export interface UserMessageLike {
9
- readonly id: string;
10
- readonly role: "user";
11
- readonly content: readonly {
12
- readonly type: "text";
13
- readonly text: string;
14
- }[];
15
- /**
16
- * 生产者自有 kind(宿主 v4 格式要求;`kind: 'plugin'` 已废弃会被拒)。
17
- * form/summary 给官方界面渲染成通知卡用。
18
- */
19
- readonly source: {
20
- readonly kind: string;
21
- readonly form: "notice";
22
- readonly summary: string;
23
- };
24
- }
25
- /** `ctx.agents` 最小面(`CreateAgentOptions` 见 session-handoff 文档 §二)。 */
26
- export interface AgentsLike {
27
- create(options: {
28
- readonly sessionId: string;
29
- readonly meta?: {
30
- readonly cwd?: string;
31
- readonly agentPreset?: string;
32
- };
33
- readonly agentOptions?: {
34
- readonly provider?: string;
35
- readonly model?: string;
36
- };
37
- readonly setup?: (agentCtx: unknown) => void | Promise<void>;
38
- }): Promise<{
39
- readonly agent: {
40
- send(message: UserMessageLike, target: "next-turn" | "next-step", wakeup: boolean): void;
41
- };
42
- }>;
43
- }
44
- /** `ctx.agentPresets` 最小面:resolve 省略 id = 部署默认;mount 只在 setup 里调得到。 */
45
- export interface AgentPresetsLike {
46
- resolve(id?: string): Promise<{
47
- readonly id: string;
48
- }>;
49
- mount(agentCtx: unknown, id?: string): Promise<unknown>;
50
- }
51
- /** `ctx.agentDefaultModel` 最小面(部署默认模型)。 */
52
- export interface DefaultModelLike {
53
- currentSelection(): ModelRoute;
54
- }
55
- /** 交接失败的原因码(路由层据此给状态码,文案在这里写成能直接上界面的)。 */
56
- export type HandoffErrorCode = "no-agents" | "no-preset" | "no-model" | "task-missing" | "create-failed" | "send-failed";
57
- export declare class HandoffError extends Error {
58
- readonly code: HandoffErrorCode;
59
- constructor(code: HandoffErrorCode, message: string);
60
- }
61
- /** 结束任务的预置提示词入参(纯数据,便于冒烟直接断言文案)。 */
62
- export interface ClosePromptInput {
63
- readonly title: string;
64
- readonly taskId: string;
65
- /** 任务所在工作区绝对路径(`--repo` 就是它)。 */
66
- readonly workspacePath: string;
67
- /** 过程文档相对 `ledger/` 的路径;缺失传 null,提示词如实说「没有」。 */
68
- readonly worklogPath: string | null;
69
- /** 用户在弹框里留的话(可为空)。 */
70
- readonly message: string;
71
- }
72
- /**
73
- * 「结束任务」提示词(2026-10-18 用户口述定稿)。
74
- *
75
- * 结构照用户原话:**预置说明在前,用户留言放最末**(越靠后、越只讲一件事的段落遵守率越高 ——
76
- * 参考插件真机结论)。三道硬约束缺一不可:
77
- * ① 不要开新会话(用户:我要结束这个流程了,不能让模型再开一堆框);
78
- * ② 读过程文档后直接收尾(别再回头问,用户已经留言了);
79
- * ③ 写台账只经 skill 脚本(插件不手改文件,close 由模型执行)。
80
- */
81
- export declare function buildClosePrompt(input: ClosePromptInput): string;
82
- /** 交接入参(路由层校验完协议字段后给)。 */
83
- export interface CloseHandoffRequest {
84
- readonly workspace: LedgerWorkspace;
85
- readonly taskId: string;
86
- readonly message: string;
87
- /** 用户选的模型;不给 = 用宿主默认。 */
88
- readonly model?: ModelRoute;
89
- }
90
- export interface CloseHandoffResult {
91
- readonly sessionId: string;
92
- /** 非致命问题(如会话没能挂进工作区列表),如实回给调用方显示,不吞。 */
93
- readonly warning?: string;
94
- }
95
- /** 依赖面(都是惰性服务,缺了给 503 人话,不硬崩)。 */
96
- export interface CloseHandoffDeps {
97
- readonly agents: AgentsLike | undefined;
98
- readonly presets: AgentPresetsLike | undefined;
99
- readonly defaultModel: DefaultModelLike | undefined;
100
- }
101
- /**
102
- * 起一个「结束任务」会话并发出首条提示词。
103
- * 失败抛 {@link HandoffError}(带原因码);成功返回 sessionId 供客户端 `openSession` 跳转。
104
- */
105
- export declare function startCloseSession(deps: CloseHandoffDeps, request: CloseHandoffRequest): Promise<CloseHandoffResult>;
106
- /**
107
- * 模型路由:用户选了就必须给全一对(只有一半 = 配错,直接人话拒绝);
108
- * 没选就走宿主部署默认。**没有兜底猜测**——猜一个模型去结案比报错更糟。
109
- */
110
- export declare function resolveModelRoute(defaultModel: DefaultModelLike | undefined, explicit?: ModelRoute): ModelRoute;
@@ -1,155 +0,0 @@
1
- /**
2
- * 会话交接(按钮侧方法层):把一条任务交给模型去收尾。
3
- *
4
- * 事实([`external/dsh-session-handoff.md`](../../../../docs/design/external/dsh-session-handoff.md)):
5
- * 浏览器半**没有**「往会话里发自由文本」的官方 API,唯一链路 = host `ctx.agents.create()` 自建会话
6
- * + `agent.send()` 发预置提示词,客户端再 `openSession` 跳过去。
7
- *
8
- * 两条硬约束(都是参考插件真机踩出来的,不是猜的):
9
- * ① **必须带 preset**:`create` 里不 mount preset 的 agent 落到「空的全局层」——只剩根作用域的东西,
10
- * fs / bash 全无,模型连 `progress.py` 都跑不起来 ⇒ 这种会话建了等于白建。
11
- * ② **provider + model 必须成对显式给**:缺省**不是**「走宿主默认路由」。用户没选模型时,
12
- * 这里按宿主 `agentDefaultModel.currentSelection()` 取部署默认;取不到就报 503,不建半个会话。
13
- *
14
- * 本文件只管「结束任务」这一条(2026-10-18 用户拍板:认领 / 释放由 host 直写、不开会话;
15
- * 结束要走会话,因为要带用户留言、要模型读过程文档后写摘要)。
16
- */
17
- import { randomUUID } from "node:crypto";
18
- import { getTask } from "../repositories/ledger.repository.js";
19
- import { SKILL_SCRIPT } from "../skill/registration.js";
20
- export class HandoffError extends Error {
21
- code;
22
- constructor(code, message) {
23
- super(message);
24
- this.code = code;
25
- this.name = "HandoffError";
26
- }
27
- }
28
- /**
29
- * 「结束任务」提示词(2026-10-18 用户口述定稿)。
30
- *
31
- * 结构照用户原话:**预置说明在前,用户留言放最末**(越靠后、越只讲一件事的段落遵守率越高 ——
32
- * 参考插件真机结论)。三道硬约束缺一不可:
33
- * ① 不要开新会话(用户:我要结束这个流程了,不能让模型再开一堆框);
34
- * ② 读过程文档后直接收尾(别再回头问,用户已经留言了);
35
- * ③ 写台账只经 skill 脚本(插件不手改文件,close 由模型执行)。
36
- */
37
- export function buildClosePrompt(input) {
38
- const worklog = input.worklogPath === null || input.worklogPath === ""
39
- ? "(这条任务没有登记过程文档)"
40
- : `${input.workspacePath}/ledger/${input.worklogPath}`;
41
- const message = input.message.trim() === ""
42
- ? "(用户没有留具体内容)"
43
- : input.message.trim();
44
- return [
45
- `用户从任务台账看板点了「结束」,要把任务「${input.title}」(任务号 ${input.taskId})结案。`,
46
- "",
47
- `工作区:${input.workspacePath}`,
48
- `过程文档:${worklog}`,
49
- "",
50
- "请按顺序做完这三件,然后停下:",
51
- "1. 读过程文档的「背景 / 待办 / 下一步」,核对这次到底做了什么、还留了什么坑。",
52
- `2. 用 task-ledger skill 的脚本结案(在工作区目录下、原样执行这一条命令):`,
53
- ` ${SKILL_SCRIPT} close --id ${input.taskId} --repo ${input.workspacePath} --summary "<一句话说清做成了什么、留了什么坑>"`,
54
- " `--summary` 是必填参数,不许编套话;命令报错就原样报告错误,不要自己绕过、不要手改台账文件。",
55
- "3. 收尾即结束——这就是本条消息的全部要求。",
56
- "",
57
- "【硬约束】",
58
- "- 不要在会话里继续派活,**不要新建会话或子 agent**:这是收尾动作,不是新任务的开始。",
59
- "- 用户已经明确要结束,不会再回答提问:信息不足就按留言与过程文档如实写进摘要/结论,**不要停下来反问**。",
60
- "- 只动这一个任务,不顺带改别的记录;写台账只经上面那个 skill 脚本。",
61
- "",
62
- "用户现在处理结束,以下是用户的留言:",
63
- message,
64
- ].join("\n");
65
- }
66
- /**
67
- * 起一个「结束任务」会话并发出首条提示词。
68
- * 失败抛 {@link HandoffError}(带原因码);成功返回 sessionId 供客户端 `openSession` 跳转。
69
- */
70
- export async function startCloseSession(deps, request) {
71
- const agents = deps.agents;
72
- if (agents === undefined) {
73
- throw new HandoffError("no-agents", "宿主未暴露 agents 服务,起不了会话(按钮交接暂不可用)");
74
- }
75
- const presets = deps.presets;
76
- if (presets === undefined) {
77
- throw new HandoffError("no-preset", "宿主未暴露 agentPresets 服务:起出来的会话没有 fs / bash 工具,模型跑不了 close");
78
- }
79
- const route = resolveModelRoute(deps.defaultModel, request.model);
80
- // 任务此刻必须还在在办里(刚被别处结案 ⇒ 明确告知,不拿空标题去建会话)。
81
- const task = await getTask(request.workspace, request.taskId);
82
- if (task === null) {
83
- throw new HandoffError("task-missing", `任务 ${request.taskId} 不在「${request.workspace.title}」的在办列表里(可能已被结案)`);
84
- }
85
- let presetId;
86
- try {
87
- presetId = (await presets.resolve()).id;
88
- }
89
- catch (error) {
90
- throw new HandoffError("no-preset", `解析默认 preset 失败:${error instanceof Error ? error.message : String(error)}`);
91
- }
92
- const sessionId = randomUUID();
93
- let handle;
94
- try {
95
- handle = await agents.create({
96
- sessionId,
97
- // cwd 必须是该工作区真实路径(会话 header 校验 + 工作区归组都按它判)。
98
- meta: { cwd: request.workspace.path, agentPreset: presetId },
99
- agentOptions: { provider: route.provider, model: route.model },
100
- // setup 是发布前唯一能挂工具 / prompt sections / skill 的时机;抛错则整个作用域回滚。
101
- setup: async (agentCtx) => {
102
- await presets.mount(agentCtx, presetId);
103
- },
104
- });
105
- }
106
- catch (error) {
107
- throw new HandoffError("create-failed", `会话没建起来(preset=${presetId}):${error instanceof Error ? error.message : String(error)}`);
108
- }
109
- const prompt = buildClosePrompt({
110
- title: task.title ?? request.taskId,
111
- taskId: request.taskId,
112
- workspacePath: request.workspace.path,
113
- worklogPath: task.worklog ?? null,
114
- message: request.message,
115
- });
116
- try {
117
- handle.agent.send({
118
- id: randomUUID(),
119
- role: "user",
120
- content: [{ type: "text", text: prompt }],
121
- source: {
122
- kind: "task-ledger",
123
- form: "notice",
124
- summary: `结束任务 · ${task.title ?? request.taskId}`,
125
- },
126
- }, "next-turn", true);
127
- }
128
- catch (error) {
129
- throw new HandoffError("send-failed", `会话已建(${sessionId})但首条消息没发出去:${error instanceof Error ? error.message : String(error)}`);
130
- }
131
- return { sessionId };
132
- }
133
- /**
134
- * 模型路由:用户选了就必须给全一对(只有一半 = 配错,直接人话拒绝);
135
- * 没选就走宿主部署默认。**没有兜底猜测**——猜一个模型去结案比报错更糟。
136
- */
137
- export function resolveModelRoute(defaultModel, explicit) {
138
- if (explicit !== undefined) {
139
- if (explicit.provider === "" || explicit.model === "") {
140
- throw new HandoffError("no-model", "provider 与 model 必须成对给出(只给一半会被拒,不做猜测)");
141
- }
142
- return explicit;
143
- }
144
- if (defaultModel !== undefined) {
145
- try {
146
- const route = defaultModel.currentSelection();
147
- if (route.provider !== "" && route.model !== "")
148
- return { provider: route.provider, model: route.model };
149
- }
150
- catch {
151
- /* 读不到就往下走,报同一句人话 */
152
- }
153
- }
154
- throw new HandoffError("no-model", "宿主没有可用的默认模型:请在请求里给出 provider + model");
155
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * 首页**慢路径**用例:近 8 周趋势 + 四个自然周期。
3
- * 读 history(months=RECENT_MONTHS)+ 在办 + 本机身份——首屏不等它(异步/按需拉)。
4
- *
5
- * ⚠️ 窗口必须是 13 个月而不是 2:8 周桶在月初会横跨 3 个自然月(如 10 月第 1 周回溯到 8 月),
6
- * `--months` 是**文件级**过滤,缩到 2 会静默漏掉 8 月结案记录、趋势少算(2026-10-18 架构评审结论)。
7
- */
8
- import { type DegradedInfo } from "../errors.js";
9
- import type { ClosedSummary, LedgerFilter, LedgerWorkspace, TrendPeriods, TrendScope } from "../types.js";
10
- /** 历史窗口:只读最近 13 个月的文件(2026-10-17 拍板;脚本默认读全部,窗口由插件显式传)。 */
11
- export declare const RECENT_MONTHS = 13;
12
- /** 趋势图桶数:近 8 周(2026-10-17 定稿)。 */
13
- export declare const WEEKLY_WEEKS = 8;
14
- /** 慢路径读法:单工作区 readTasks + readHistory(months) + 本机身份(并行;供 settle 逐工作区调)。 */
15
- export declare function readTrendScope(workspace: LedgerWorkspace): Promise<TrendScope & {
16
- warnings: readonly string[];
17
- }>;
18
- /**
19
- * 近 N 周分组桶(完成按结案时间、新增按创建时间)× 所有 / 我的(纯函数)。
20
- *
21
- * 新增必须**合并两个来源**:已结案的任务被 close 移出 progress.json,只在 history 里;
22
- * 在办的只在 progress.json 里。两者不相交(close 是移动),合并即全集、不会重复计。
23
- * remaining:某任务在「第 i 周截止时」仍未完成 ⇔ 创建 ≤ weekEnd_i 且(未结案 或 结案 > weekEnd_i)。
24
- */
25
- export declare function computeClosedSummary(scopes: readonly TrendScope[], now: Date): ClosedSummary;
26
- /** 四个自然周期(只算「全部工作区」范围;图表切换不带动卡片)——纯函数。 */
27
- export declare function computeTrendPeriods(scopes: readonly TrendScope[], now: Date): TrendPeriods;
28
- /** 慢路径端点:趋势 + 周期(读 history)。异步加载,首屏不等。单工作区坏 ⇒ 进 degraded。 */
29
- export declare function getTrend(workspaces: readonly LedgerWorkspace[]): Promise<{
30
- closed: ClosedSummary;
31
- trend: TrendPeriods;
32
- degraded: readonly DegradedInfo[];
33
- warnings: readonly string[];
34
- }>;
35
- /** 趋势端点用的历史 filter(窗口由本模块策略决定,repository 零默认值注入)。 */
36
- export declare function trendHistoryFilter(extra?: LedgerFilter): LedgerFilter;