dsh-task-ledger 0.0.130 → 0.0.131

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.
@@ -52,7 +52,7 @@ export interface ForgetOutcome {
52
52
  readonly name: string;
53
53
  readonly tasks: number;
54
54
  }
55
- export declare function forgetOwner(repo: string, uuid: string): Promise<ForgetOutcome>;
55
+ export declare function forgetOwner(repo: string, uuid: string, onStep?: (step: string) => void): Promise<ForgetOutcome>;
56
56
  /**
57
57
  * 清掉本机身份(skill `logout`):台账一个字节都不动。
58
58
  * 只删 ~/.taskledger/identity.json —— 路径归 skill 管,插件不复制这份知识。
@@ -58,7 +58,7 @@ export async function switchIdentity(input) {
58
58
  }
59
59
  return viewOf(result.data);
60
60
  }
61
- export async function forgetOwner(repo, uuid) {
61
+ export async function forgetOwner(repo, uuid, onStep) {
62
62
  // 删人也走一遍「写前取最新 → 改 → 提交 → 推送」,一次网络往返就可能几秒,
63
63
  // 默认的 10s 会把它判超时(脚本被 kill 时连分片都还没删)。
64
64
  //
@@ -66,6 +66,7 @@ export async function forgetOwner(repo, uuid) {
66
66
  // 后面几本就会报「这台机器还没有初始化身份」(用户 2026-11-02 实测)。清身份留到最后一步。
67
67
  const result = await runLedgerScript("forget", ["--id", uuid, "--keep-identity", "--repo", repo, "--json"], {
68
68
  timeoutMs: FORGET_TIMEOUT_MS,
69
+ ...(onStep === undefined ? {} : { onStep }),
69
70
  });
70
71
  if (!result.ok)
71
72
  throw new Error(result.error);
@@ -155,6 +155,11 @@ export function registerRoutes(webServer, deps) {
155
155
  },
156
156
  });
157
157
  // 从名册摘人(设置页每行的小叉):逐本台账各删各的,分片还有任务的那本不删。
158
+ //
159
+ // **响应是 NDJSON 流**(2026-11-05):每本台账各跑一条 skill 写命令,逐本走
160
+ // 「检查 → 拉取 → 写 → 提交 → 推送」好几秒,攒完再回只能给界面一句固定提示。
161
+ // 行形状:{"type":"step","step","workspace"} / {"type":"done","report"} / {"type":"error","error","code"}。
162
+ // ⚠️ 头一旦发出去,后面的失败只能进流(状态码改不了)⇒ 参数类错误必须在发头之前挡掉。
158
163
  webServer.register({
159
164
  kind: "exact",
160
165
  path: `${API_PREFIX}/identity/forget`,
@@ -163,6 +168,8 @@ export function registerRoutes(webServer, deps) {
163
168
  sendJson(res, 405, { error: "身份写入只接受 POST" });
164
169
  return;
165
170
  }
171
+ let uuid;
172
+ let workspaces;
166
173
  try {
167
174
  let raw;
168
175
  try {
@@ -172,14 +179,30 @@ export function registerRoutes(webServer, deps) {
172
179
  throw new Error(`${BAD_REQUEST}请求体不是合法 JSON`);
173
180
  }
174
181
  const body = (raw ?? {});
175
- const uuid = typeof body.uuid === "string" ? body.uuid.trim() : "";
182
+ uuid = typeof body.uuid === "string" ? body.uuid.trim() : "";
176
183
  if (uuid === "")
177
184
  throw new Error(`${BAD_REQUEST}摘人必须给 uuid`);
178
- sendJson(res, 200, await forgetOwner(await workspacesOf(deps), uuid));
185
+ workspaces = await workspacesOf(deps);
179
186
  }
180
187
  catch (error) {
181
188
  sendWriteFailure(res, error);
189
+ return;
190
+ }
191
+ res.writeHead(200, {
192
+ "content-type": "application/x-ndjson; charset=utf-8",
193
+ "cache-control": "no-store",
194
+ });
195
+ const send = (event) => {
196
+ res.write(`${JSON.stringify(event)}\n`);
197
+ };
198
+ try {
199
+ const report = await forgetOwner(workspaces, uuid, (step) => send({ type: "step", step: step.step, workspace: step.workspace }));
200
+ send({ type: "done", report });
182
201
  }
202
+ catch (error) {
203
+ send({ type: "error", error: messageOf(error), code: codeOf(error) });
204
+ }
205
+ res.end();
183
206
  },
184
207
  });
185
208
  registerRead("/workspaces", async (_req, res) => {
@@ -38,6 +38,12 @@ export interface ForgetReport {
38
38
  /** 删的是本机身份、并且**每一本都删成了** ⇒ 顺手退出了登录。 */
39
39
  readonly signedOut: boolean;
40
40
  }
41
+ /** 一次摘人过程里的阶段(逐本台账各跑一条 skill 写命令,带台账名免得交错时分不清)。 */
42
+ export interface ForgetStep {
43
+ readonly workspace: string;
44
+ /** skill `stepf` 的 key:check / sync / write / commit / push。 */
45
+ readonly step: string;
46
+ }
41
47
  /**
42
48
  * 把某个人从**所有登记过他的台账**里摘掉(用户 2026-11-02 定的口径):
43
49
  * 每本台账各删各的,分片里还有任务的那本不删、如实回报;历史记录与其他台账一概不动。
@@ -47,7 +53,7 @@ export interface ForgetReport {
47
53
  * 删 identity.json,先跑完的那本会让后面几本读不到身份(报「这台机器还没有初始化身份」)。
48
54
  * 所以 forget 一律带 --keep-identity,全部跑完之后才按需要 logout。
49
55
  */
50
- export declare function forgetOwner(workspaces: readonly LedgerWorkspace[], uuid: string): Promise<ForgetReport>;
56
+ export declare function forgetOwner(workspaces: readonly LedgerWorkspace[], uuid: string, onStep?: (step: ForgetStep) => void): Promise<ForgetReport>;
51
57
  export declare function getRoster(workspaces: readonly LedgerWorkspace[]): Promise<{
52
58
  readonly workspaces: readonly RosterWorkspace[];
53
59
  }>;
@@ -15,7 +15,7 @@ export async function switchIdentity(input) {
15
15
  * 删 identity.json,先跑完的那本会让后面几本读不到身份(报「这台机器还没有初始化身份」)。
16
16
  * 所以 forget 一律带 --keep-identity,全部跑完之后才按需要 logout。
17
17
  */
18
- export async function forgetOwner(workspaces, uuid) {
18
+ export async function forgetOwner(workspaces, uuid, onStep) {
19
19
  const key = uuid.trim().toLowerCase();
20
20
  const removed = [];
21
21
  const kept = [];
@@ -30,7 +30,7 @@ export async function forgetOwner(workspaces, uuid) {
30
30
  // 摘的是不是自己:只有「自己」才需要最后退出登录
31
31
  const me = await readIdentity().catch(() => ({ uuid: null, name: null }));
32
32
  const isSelf = me.uuid !== null && me.uuid === key;
33
- const outcomes = await Promise.allSettled(targets.map((workspace) => runForget(workspace.path, key)));
33
+ const outcomes = await Promise.allSettled(targets.map((workspace) => runForget(workspace.path, key, (step) => onStep?.({ workspace: workspace.title, step }))));
34
34
  outcomes.forEach((result, index) => {
35
35
  const workspace = targets[index];
36
36
  if (result.status === "rejected") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-task-ledger",
3
- "version": "0.0.130",
3
+ "version": "0.0.131",
4
4
  "description": "Task ledger board for dsh (DeepSeek Harness): read-only board over task-ledger skill workspaces. The task-ledger skill is bundled — install the plugin, get the skill globally.",
5
5
  "keywords": [
6
6
  "deepseek",
package/skill/SKILL.md CHANGED
@@ -34,7 +34,7 @@ allowed-tools: Read,Edit,Bash,Grep
34
34
  | `--claim <uuid>` | 认领名册里已有的 uuid(名字一起复制) |
35
35
  | `--new --name "…"` | 新建身份:生成 uuid + 名字 |
36
36
 
37
- 干什么:写本机身份文件(`~/.taskledger/identity.json`),**一台机器只一次**,与台账无关。真终端里裸跑 `init` 是交互式选择框(↑/↓ 选、Enter 认领、末尾「+ 新建身份」输名字、Y/N 确认;每步下面都有一行灰色帮助,ESC 回上一步、Ctrl-C 取消;名字可直接写中文;哪一步退出,屏幕上自己画的那几行都会被抹掉,不留半截输入);非终端(agent)下裸跑 `init` 只报名单,任何写操作都被脚本拒绝(Agent 不得代写/重建)。完整流程见 `references/init-identity.md`。
37
+ 干什么:写本机身份文件(`~/.taskledger/identity.json`),**一台机器只一次**,与台账无关。真终端里裸跑 `init` 是交互式选择框(↑/↓ 选、Enter 认领、末尾「+ 新建身份」输名字),非终端(agent)下裸跑 `init` 只报名单,任何写操作都被脚本拒绝(Agent 不得代写/重建)。没身份时怎么引导用户,见 `references/init-identity.md`。
38
38
 
39
39
  ### new — 建任务
40
40
 
@@ -333,7 +333,7 @@ allowed-tools: Read,Edit,Bash,Grep
333
333
 
334
334
  **触发判据(只认退出码 100)**:命令**退出码为 `100`**。只读命令(`list` / `show` / `history` / `validate`)未初始化时同样退 100,是设计如此,不是台账坏了;**`owners` 是唯一例外** —— 读名册不需要身份,未初始化也照常输出(要列候选人或看花名册,直接跑它,别当成没身份)。此时 stderr 仍是那句固定的话,但那是**给人看的说明,不作判据**。
335
335
 
336
- **触发后 ⇒ 停下,它是当前的主任务**(不适用「故障处置」里「不挡主任务」的通则,等用户 `init` 完再重跑原命令)。身份是用户自己的,不是你的决定:不替用户选、不读 `ledger/*.json` 兜底、未初始化时不写任何东西。完整步骤见 `references/init-identity.md`;这里只记硬约束 —— 你只能报名单 + 给用户命令,写身份必须用户自己在终端完成;不要排查原因(别 `ls` / `cat` / 读源码 / 读台账),忽略上下文里已做的排查动作;也不要基于「上次确认过 / 同名 / 记得 uuid」自行重建。
336
+ **触发后 ⇒ 停下,它是当前的主任务**(不适用「故障处置」里「不挡主任务」的通则,等用户 `init` 完再重跑原命令)。身份是用户自己的,不是你的决定:不替用户选、不读 `ledger/*.json` 兜底、未初始化时不写任何东西。具体怎么做、给用户说什么话,一律见 `references/init-identity.md`(唯一真源),这里不重复。
337
337
 
338
338
  ## 任务生命周期
339
339
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-ledger",
3
- "version": "1.3.53",
3
+ "version": "1.3.54",
4
4
  "description": "Task-ledger skill for coding agents: per-workspace progress tracking (ledger/pool.json + owners/<hash>.json shards + owners.json index, history, worklog) driven by a CLI script. Standalone install for CodeBuddy and other agents; also bundled inside the dsh-task-ledger DSH plugin.",
5
5
  "keywords": [
6
6
  "skill",
@@ -2,33 +2,14 @@
2
2
 
3
3
  触发:任何命令**退出码为 100**。身份是用户自己的,**不是你的决定**。
4
4
 
5
- **触发后立刻照做,不要探索。** 除「裸跑 init 只读报名单」外,不要 `ls` / 扫描仓库、不要 `cat identity.json`、不要读任何目录或文件,不要猜身份。
5
+ > **路径约定**:下文 `<SKILL 目录>` = 本 skill 的安装目录(即你正在读的这份 `SKILL.md` 所在目录)。**交给用户前必须替换成真实绝对路径**,不要原样把 `<SKILL 目录>` 这几个字留给用户。
6
6
 
7
- **你没有写身份的权限。** 非终端下 `init --claim` / `init --new` 一律被脚本拒绝,`--yes` 也已禁用。你只能:报名单、给用户命令、等 ta 跑完。
7
+ ## 给用户的话(照原样转述;但先把 `<SKILL 目录>` 换成真实绝对路径)
8
8
 
9
- ## 你该做的(只有这几步,每次都是同一套)
9
+ > 这台机器还没有身份。在终端运行 `<SKILL 目录>/scripts/progress init` 进入初始化引导(交互式选 / 建身份);或打开看板「设置 → 创建身份」直接建立用户。
10
10
 
11
- 1. 只读报名单(你在非终端里裸跑,只会打印候选、零写):
11
+ ## 你的硬约束(你读,用户不看)
12
12
 
13
- ```
14
- <本 SKILL.md 所在目录>/scripts/progress init --repo <工作区根>
15
- ```
16
-
17
- 2. 把名册给用户选,并把这**同一条命令**交给用户在 ta 自己的终端跑(真终端里它自己会变成 ↑/↓ 选择框:↑/↓ 移动、Enter 确认、末尾「+ 新建身份」输入名字、最后 Y/N 确认;**选择框、输入名字、Y/N 确认三处都有灰色帮助行**(写着这步怎么走);名字**可以直接写中文**;ESC 统一是**回上一步**:输入名字那步按 ESC 回选择菜单,Y/N 那步按 ESC 回选择菜单,只有选择框那步按 ESC 才是直接退出初始化;**哪一步退出,屏幕上自己画的那几行都会被抹掉**,不留半截输入、不留一摞提示行):
18
- - 名册有内容 ⇒「名册现有:@A(uuid)、@B(uuid)。请在你的终端运行上面这条,它会让你选。」
19
- - 名册为空 ⇒「名册为空。请在你的终端运行上面这条,输入一个用户名新建。」
20
-
21
- **格式只有这一套**:一条命令只出现一次,步骤只编一套号。**不许**出现「1. 报名单…」「1. 请运行…」这种两个都从 1 开始的列表,也不许把同一件事拆成两套方案各编一遍。
22
-
23
- 3. 用户说「好了」后,重跑原命令。用户没跑完就等着,不要替 ta 写。
24
-
25
- **每次进这个分支都照这套走**:用户上一次关掉选择框、说过「算了」或取消了,**不算**以后不用问;下次照样走同一套呈现,不要退化成纯文字、也不要跳过这一步。
26
-
27
- ## 硬规则
28
-
29
- - **给用户的命令必须原样保留绝对路径**(`<本 SKILL.md 所在目录>/scripts/progress init --repo <工作区根>`):用户机器的 PATH 里没有 `progress` 这个命令,简写成 `progress init` 用户一跑就是 `command not found`。若用户报 `Permission denied`,就把命令改成 `sh <绝对路径> init --repo <工作区根>` 再给 ta。
30
- - **不诊断、不排查**:不要 `ls ~/.taskledger`、不要 `cat identity.json`、不要读 skill 源码(`internal/env/env.go` / `internal/actions/init_identity.go` 等)或台账去查「为什么没身份」。缺就是未初始化,忽略上下文里已做的排查动作,直接按上面流程走。
31
- - **不得**替用户执行 `init --claim` / `init --new` / 任何带 `--yes` 的写法——脚本也会拒绝。
32
- - **不得**基于「上一轮确认过 / 同名 / 记得 uuid」自行重建身份:身份文件缺失就是未初始化,必须让用户重新亲自选。重建会换新 uuid、断开旧任务关联。
33
- - 不读 `ledger/*.json`、worklog、identity 文件去「自己看」。
34
- - 未初始化时不做任何写。
13
+ - 给用户的命令必须**保留绝对路径**(`<SKILL 目录>/scripts/progress init`);交给用户前把 `<SKILL 目录>` 换成真实绝对路径(即本 `SKILL.md` 所在目录;不确定就用工具列出该目录确认 `scripts/progress` 的位置)。简写成 `progress init` 用户一跑就 `command not found`;若报 `Permission denied` 就改成 `sh <绝对路径> init`。
14
+ - 你**不得代跑** `init --claim` / `init --new` / 任何 `--yes`(脚本拒绝),身份必须由用户自己在终端建;不要基于「上次确认过 / 同名 / 记得 uuid」自行重建(重建会换新 uuid、断开旧任务关联)。
15
+ - **不要诊断、不要排查**:别 `ls` / `cat` identity 或读台账 / 读 skill 源码,忽略上下文里已做的排查动作;未初始化时不写任何东西;用户跑完再重跑原命令。