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.
- package/dist/client.js +128 -128
- package/dist/client.js.map +1 -1
- package/dist/host/board.d.ts +4 -4
- package/dist/host/board.js +6 -6
- package/dist/host/errors.d.ts +3 -3
- package/dist/host/errors.js +13 -7
- package/dist/host/repositories/cache.d.ts +1 -1
- package/dist/host/repositories/cache.js +4 -2
- package/dist/host/repositories/identity.repository.d.ts +1 -1
- package/dist/host/repositories/identity.repository.js +14 -7
- package/dist/host/repositories/ledger.repository.d.ts +1 -1
- package/dist/host/repositories/ledger.repository.js +34 -23
- package/dist/host/repositories/records.d.ts +1 -1
- package/dist/host/repositories/records.js +3 -3
- package/dist/host/repositories/runner.d.ts +5 -4
- package/dist/host/repositories/runner.js +41 -19
- package/dist/host/routes.d.ts +4 -4
- package/dist/host/routes.js +145 -96
- package/dist/host/services/dates.js +4 -4
- package/dist/host/services/history.service.d.ts +5 -2
- package/dist/host/services/history.service.js +24 -13
- package/dist/host/services/home.service.d.ts +8 -2
- package/dist/host/services/home.service.js +29 -14
- package/dist/host/services/ledger-write.service.d.ts +8 -3
- package/dist/host/services/ledger-write.service.js +14 -9
- package/dist/host/services/session.service.d.ts +6 -6
- package/dist/host/services/session.service.js +47 -37
- package/dist/host/services/trend.service.d.ts +9 -2
- package/dist/host/services/trend.service.js +42 -21
- package/dist/host/skill/registration.d.ts +1 -1
- package/dist/host/skill/registration.js +16 -11
- package/dist/host/workspace.d.ts +1 -1
- package/dist/host/workspace.js +6 -2
- package/dist/index.js +12 -12
- package/package.json +57 -58
- package/skill/README.md +29 -245
- package/skill/SKILL.md +173 -88
- package/skill/package.json +22 -22
- package/skill/references/log-note.md +3 -1
- package/skill/references/state-model.md +29 -27
- package/skill/references/troubleshooting.md +98 -25
- package/skill/scripts/ledgerkit/__init__.py +0 -0
- package/skill/scripts/ledgerkit/actions/__init__.py +0 -0
- package/skill/scripts/ledgerkit/actions/atom.py +154 -0
- package/skill/scripts/ledgerkit/actions/bootstrap.py +27 -0
- package/skill/scripts/ledgerkit/actions/claim.py +82 -0
- package/skill/scripts/ledgerkit/actions/close_task.py +180 -0
- package/skill/scripts/ledgerkit/actions/guard.py +140 -0
- package/skill/scripts/ledgerkit/actions/handoff.py +74 -0
- package/skill/scripts/ledgerkit/actions/list_tasks.py +53 -0
- package/skill/scripts/ledgerkit/actions/log_progress.py +60 -0
- package/skill/scripts/ledgerkit/actions/new_task.py +120 -0
- package/skill/scripts/ledgerkit/actions/query.py +33 -0
- package/skill/scripts/ledgerkit/actions/query_history.py +173 -0
- package/skill/scripts/ledgerkit/actions/registry.py +82 -0
- package/skill/scripts/ledgerkit/actions/release.py +51 -0
- package/skill/scripts/ledgerkit/actions/result.py +88 -0
- package/skill/scripts/ledgerkit/actions/set_task.py +43 -0
- package/skill/scripts/ledgerkit/actions/show_task.py +27 -0
- package/skill/scripts/ledgerkit/actions/validate.py +224 -0
- package/skill/scripts/ledgerkit/core/__init__.py +0 -0
- package/skill/scripts/ledgerkit/core/history_md.py +350 -0
- package/skill/scripts/ledgerkit/core/identity.py +43 -0
- package/skill/scripts/ledgerkit/core/ids.py +58 -0
- package/skill/scripts/ledgerkit/core/labels.py +79 -0
- package/skill/scripts/ledgerkit/core/paths.py +93 -0
- package/skill/scripts/ledgerkit/core/time.py +102 -0
- package/skill/scripts/ledgerkit/core/worklog_md.py +232 -0
- package/skill/scripts/ledgerkit/env.py +93 -0
- package/skill/scripts/ledgerkit/errors.py +15 -0
- package/skill/scripts/ledgerkit/notices.py +28 -0
- package/skill/scripts/ledgerkit/shell/__init__.py +0 -0
- package/skill/scripts/ledgerkit/shell/cli.py +259 -0
- package/skill/scripts/ledgerkit/shell/gitops.py +655 -0
- package/skill/scripts/ledgerkit/shell/merge.py +266 -0
- package/skill/scripts/ledgerkit/shell/presync.py +23 -0
- package/skill/scripts/ledgerkit/shell/render.py +165 -0
- package/skill/scripts/ledgerkit/shell/runner.py +225 -0
- package/skill/scripts/ledgerkit/store/__init__.py +0 -0
- package/skill/scripts/ledgerkit/store/atomic.py +130 -0
- package/skill/scripts/ledgerkit/store/dirty.py +108 -0
- package/skill/scripts/ledgerkit/store/history_store.py +189 -0
- package/skill/scripts/ledgerkit/store/json_store.py +166 -0
- package/skill/scripts/ledgerkit/store/locks.py +64 -0
- package/skill/scripts/ledgerkit/store/owners_index.py +135 -0
- package/skill/scripts/ledgerkit/store/task_store.py +31 -0
- package/skill/scripts/ledgerkit/store/worklog_store.py +168 -0
- package/skill/scripts/progress.py +8 -3021
- package/skill/references/taxonomy.md +0 -26
- package/skill/references/worklog.md +0 -39
package/dist/host/board.d.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* 拆端点后(host-data-plane §五 A)首屏走 `services/home.service.ts::getSnapshot()`(不碰 history),
|
|
6
6
|
* 本组合归「兼容期全量 `/board`」;新端点在 routes 层直接调 service,不过本文件。
|
|
7
7
|
*/
|
|
8
|
-
import { type DegradedInfo } from
|
|
9
|
-
import type { HomeAggregate, LedgerWorkspace } from
|
|
8
|
+
import { type DegradedInfo } from "./errors.js";
|
|
9
|
+
import type { HomeAggregate, LedgerWorkspace } from "./types.js";
|
|
10
10
|
/**
|
|
11
11
|
* 首页一次拿全:在办快照(含停滞 / 天数 / 身份)+ 近 8 周趋势(完成 / 新增 × 所有 / 我的)
|
|
12
12
|
* + 四个自然周期计数。**每个工作区只读一次**(list + history + git 身份),
|
|
@@ -16,5 +16,5 @@ import type { HomeAggregate, LedgerWorkspace } from './types.js';
|
|
|
16
16
|
export declare function getHomeAggregate(workspaces: readonly LedgerWorkspace[]): Promise<HomeAggregate & {
|
|
17
17
|
degraded: readonly DegradedInfo[];
|
|
18
18
|
}>;
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
19
|
+
export { getFilterOptions, queryClosed } from "./services/history.service.js";
|
|
20
|
+
export { getSnapshot as getBoardSnapshot } from "./services/home.service.js";
|
package/dist/host/board.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* 拆端点后(host-data-plane §五 A)首屏走 `services/home.service.ts::getSnapshot()`(不碰 history),
|
|
6
6
|
* 本组合归「兼容期全量 `/board`」;新端点在 routes 层直接调 service,不过本文件。
|
|
7
7
|
*/
|
|
8
|
-
import { settleWorkspaces } from
|
|
9
|
-
import { computeSnapshot } from
|
|
10
|
-
import { computeClosedSummary, computeTrendPeriods, readTrendScope } from
|
|
8
|
+
import { settleWorkspaces } from "./errors.js";
|
|
9
|
+
import { computeSnapshot } from "./services/home.service.js";
|
|
10
|
+
import { computeClosedSummary, computeTrendPeriods, readTrendScope, } from "./services/trend.service.js";
|
|
11
11
|
/**
|
|
12
12
|
* 首页一次拿全:在办快照(含停滞 / 天数 / 身份)+ 近 8 周趋势(完成 / 新增 × 所有 / 我的)
|
|
13
13
|
* + 四个自然周期计数。**每个工作区只读一次**(list + history + git 身份),
|
|
@@ -16,7 +16,7 @@ import { computeClosedSummary, computeTrendPeriods, readTrendScope } from './ser
|
|
|
16
16
|
*/
|
|
17
17
|
export async function getHomeAggregate(workspaces) {
|
|
18
18
|
const now = new Date();
|
|
19
|
-
const { data: scopes, degraded } = await settleWorkspaces(workspaces,
|
|
19
|
+
const { data: scopes, degraded } = await settleWorkspaces(workspaces, "history", readTrendScope, (rows) => rows);
|
|
20
20
|
return {
|
|
21
21
|
snapshot: computeSnapshot(scopes, now),
|
|
22
22
|
closed: computeClosedSummary(scopes, now),
|
|
@@ -24,6 +24,6 @@ export async function getHomeAggregate(workspaces) {
|
|
|
24
24
|
degraded,
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
export { getFilterOptions, queryClosed } from "./services/history.service.js";
|
|
27
28
|
// 单路径用例直接透传 service(调用方按快 / 慢自己选,不必经过组合):
|
|
28
|
-
export { getSnapshot as getBoardSnapshot } from
|
|
29
|
-
export { getFilterOptions, queryClosed } from './services/history.service.js';
|
|
29
|
+
export { getSnapshot as getBoardSnapshot } from "./services/home.service.js";
|
package/dist/host/errors.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 防止「python3 根本没装」被集体降级成一片绿色空板——那等于界面撒谎。
|
|
9
9
|
*/
|
|
10
10
|
/** 机器可读错误码(只增不改;`LEDGER_` 前缀避开宿主错误码空间)。 */
|
|
11
|
-
export type LedgerErrorCode =
|
|
11
|
+
export type LedgerErrorCode = "LEDGER_SCRIPT_UNAVAILABLE" | "LEDGER_SCRIPT_TIMEOUT" | "LEDGER_SCRIPT_FAILED" | "LEDGER_SCRIPT_BAD_JSON" | "LEDGER_DATA_INVALID";
|
|
12
12
|
/** 台账数据形状不符(容器级校验失败)。message 带工作区名 + 人话,可直接上界面。 */
|
|
13
13
|
export declare class LedgerDataError extends Error {
|
|
14
14
|
readonly code: LedgerErrorCode;
|
|
@@ -20,7 +20,7 @@ export interface DegradedInfo {
|
|
|
20
20
|
readonly workspaceId: string;
|
|
21
21
|
readonly workspaceTitle: string;
|
|
22
22
|
/** 哪个读挂了——trend 同时读 list + history,必须能区分。 */
|
|
23
|
-
readonly op:
|
|
23
|
+
readonly op: "list" | "history";
|
|
24
24
|
readonly code: LedgerErrorCode;
|
|
25
25
|
/** 人话,可直接上界面(真源:脚本 stderr / records 校验文案)。 */
|
|
26
26
|
readonly message: string;
|
|
@@ -36,7 +36,7 @@ export declare function messageOf(error: unknown): string;
|
|
|
36
36
|
export declare function settleWorkspaces<W extends {
|
|
37
37
|
id: string;
|
|
38
38
|
title: string;
|
|
39
|
-
}, T, R>(workspaces: readonly W[], op:
|
|
39
|
+
}, T, R>(workspaces: readonly W[], op: "list" | "history", read: (workspace: W) => Promise<T>, merge: (rows: readonly T[]) => R): Promise<{
|
|
40
40
|
data: R;
|
|
41
41
|
degraded: readonly DegradedInfo[];
|
|
42
42
|
}>;
|
package/dist/host/errors.js
CHANGED
|
@@ -9,22 +9,22 @@
|
|
|
9
9
|
*/
|
|
10
10
|
/** 台账数据形状不符(容器级校验失败)。message 带工作区名 + 人话,可直接上界面。 */
|
|
11
11
|
export class LedgerDataError extends Error {
|
|
12
|
-
code =
|
|
12
|
+
code = "LEDGER_DATA_INVALID";
|
|
13
13
|
workspaceId;
|
|
14
14
|
constructor(workspaceId, message) {
|
|
15
15
|
super(message);
|
|
16
|
-
this.name =
|
|
16
|
+
this.name = "LedgerDataError";
|
|
17
17
|
this.workspaceId = workspaceId;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/** 从任意抛出物取错误码(无 code 的按 LEDGER_SCRIPT_FAILED 兜底——那正是脚本非零的主路径)。 */
|
|
21
21
|
export function codeOf(error) {
|
|
22
|
-
if (error !== null && typeof error ===
|
|
22
|
+
if (error !== null && typeof error === "object" && "code" in error) {
|
|
23
23
|
const code = error.code;
|
|
24
|
-
if (typeof code ===
|
|
24
|
+
if (typeof code === "string" && code.startsWith("LEDGER_"))
|
|
25
25
|
return code;
|
|
26
26
|
}
|
|
27
|
-
return
|
|
27
|
+
return "LEDGER_SCRIPT_FAILED";
|
|
28
28
|
}
|
|
29
29
|
/** 人话(优先 Error.message;其余 String(),不吞)。 */
|
|
30
30
|
export function messageOf(error) {
|
|
@@ -41,13 +41,19 @@ export async function settleWorkspaces(workspaces, op, read, merge) {
|
|
|
41
41
|
let firstError = null;
|
|
42
42
|
settled.forEach((result, i) => {
|
|
43
43
|
const workspace = workspaces[i];
|
|
44
|
-
if (result.status ===
|
|
44
|
+
if (result.status === "fulfilled") {
|
|
45
45
|
rows.push(result.value);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
if (firstError === null)
|
|
49
49
|
firstError = result.reason;
|
|
50
|
-
degraded.push({
|
|
50
|
+
degraded.push({
|
|
51
|
+
workspaceId: workspace.id,
|
|
52
|
+
workspaceTitle: workspace.title,
|
|
53
|
+
op,
|
|
54
|
+
code: codeOf(result.reason),
|
|
55
|
+
message: messageOf(result.reason),
|
|
56
|
+
});
|
|
51
57
|
});
|
|
52
58
|
if (rows.length === 0 && workspaces.length > 0)
|
|
53
59
|
throw firstError;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
/** 缓存有效期(2026-10-18 拍板 1.5s:够挡同一屏的重复读,短到不会让人看到旧数据)。 */
|
|
13
13
|
export declare const CACHE_TTL_MS = 1500;
|
|
14
14
|
/** 缓存的操作种类(= skill 脚本子命令;worklog 不进缓存故不在此列)。 */
|
|
15
|
-
export type CacheOp =
|
|
15
|
+
export type CacheOp = "list" | "history";
|
|
16
16
|
/** 缓存键:`op:workspace:filter` 三条信息都要,否则不同筛选 / 不同工作区会串味。 */
|
|
17
17
|
export declare function cacheKey(workspaceId: string, op: CacheOp, detail: string): string;
|
|
18
18
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/** 缓存有效期(2026-10-18 拍板 1.5s:够挡同一屏的重复读,短到不会让人看到旧数据)。 */
|
|
13
13
|
export const CACHE_TTL_MS = 1_500;
|
|
14
|
-
const SEP =
|
|
14
|
+
const SEP = "\u0000";
|
|
15
15
|
/** 已完成的缓存:key → 值与时间戳。 */
|
|
16
16
|
const settled = new Map();
|
|
17
17
|
/** 在途单飞:key → 正在跑的 Promise(同 key 并发只跑一次)。 */
|
|
@@ -37,7 +37,9 @@ export function cached(key, load, now = Date.now()) {
|
|
|
37
37
|
settled.set(key, { at: Date.now(), value });
|
|
38
38
|
return value;
|
|
39
39
|
})
|
|
40
|
-
.finally(() => {
|
|
40
|
+
.finally(() => {
|
|
41
|
+
inflight.delete(key);
|
|
42
|
+
});
|
|
41
43
|
inflight.set(key, task);
|
|
42
44
|
return task;
|
|
43
45
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
/**
|
|
3
3
|
* 当前 git 身份(cwd = 该工作区)——「我的待办」判定源。
|
|
4
4
|
* 与 agent 在会话里跑 skill 时的取值环境一致;取不到回 null(非 git 目录 / 没配身份),
|
|
@@ -8,13 +8,20 @@ import { spawn } from 'node:child_process';
|
|
|
8
8
|
*/
|
|
9
9
|
export function getCurrentIdentity(workspace) {
|
|
10
10
|
return new Promise((resolve) => {
|
|
11
|
-
const child = spawn(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const child = spawn("git", ["config", "user.name"], {
|
|
12
|
+
cwd: workspace.path,
|
|
13
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
14
|
+
});
|
|
15
|
+
let stdout = "";
|
|
16
|
+
child.stdout.on("data", (chunk) => {
|
|
17
|
+
stdout += chunk.toString();
|
|
18
|
+
});
|
|
19
|
+
child.on("error", () => {
|
|
20
|
+
resolve(null);
|
|
21
|
+
});
|
|
22
|
+
child.on("close", (code) => {
|
|
16
23
|
const name = stdout.trim();
|
|
17
|
-
resolve(code === 0 && name !==
|
|
24
|
+
resolve(code === 0 && name !== "" ? name : null);
|
|
18
25
|
});
|
|
19
26
|
});
|
|
20
27
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { existsSync } from
|
|
2
|
-
import { readFile } from
|
|
3
|
-
import path from
|
|
4
|
-
import {
|
|
5
|
-
import { parseHistoryRecords, parseTaskList } from
|
|
6
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { cached, cacheKey } from "./cache.js";
|
|
5
|
+
import { parseHistoryRecords, parseTaskList } from "./records.js";
|
|
6
|
+
import { runLedgerScript } from "./runner.js";
|
|
7
7
|
/** 台账目录名(各工作区根下的这一个目录;布局知识只在本文件出现一次)。 */
|
|
8
|
-
const LEDGER_DIR =
|
|
8
|
+
const LEDGER_DIR = "ledger";
|
|
9
9
|
/**
|
|
10
10
|
* LedgerFilter → skill 脚本参数(只拼调用方给了的;undefined 一律不拼,
|
|
11
11
|
* 让脚本走自己的默认——插件不替脚本决定默认值)。
|
|
@@ -13,21 +13,21 @@ const LEDGER_DIR = 'ledger';
|
|
|
13
13
|
export function filterArgs(filter) {
|
|
14
14
|
const args = [];
|
|
15
15
|
if (filter.status !== undefined)
|
|
16
|
-
args.push(
|
|
16
|
+
args.push("--status", filter.status);
|
|
17
17
|
if (filter.owner !== undefined)
|
|
18
|
-
args.push(
|
|
18
|
+
args.push("--owner", filter.owner);
|
|
19
19
|
if (filter.category !== undefined)
|
|
20
|
-
args.push(
|
|
20
|
+
args.push("--category", filter.category);
|
|
21
21
|
if (filter.tag !== undefined)
|
|
22
|
-
args.push(
|
|
22
|
+
args.push("--tag", filter.tag);
|
|
23
23
|
if (filter.since !== undefined)
|
|
24
|
-
args.push(
|
|
24
|
+
args.push("--since", filter.since);
|
|
25
25
|
if (filter.until !== undefined)
|
|
26
|
-
args.push(
|
|
26
|
+
args.push("--until", filter.until);
|
|
27
27
|
if (filter.limit !== undefined)
|
|
28
|
-
args.push(
|
|
28
|
+
args.push("--limit", String(filter.limit));
|
|
29
29
|
if (filter.months !== undefined)
|
|
30
|
-
args.push(
|
|
30
|
+
args.push("--months", String(filter.months));
|
|
31
31
|
return args;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
@@ -35,7 +35,8 @@ export function filterArgs(filter) {
|
|
|
35
35
|
* 2026-10-17 拍板)。全 host 对台账目录的 IO 只在本文件——别处再写 existsSync 即为分层倒退。
|
|
36
36
|
*/
|
|
37
37
|
export function hasLedger(workspacePath) {
|
|
38
|
-
|
|
38
|
+
// 分片布局(2026-10-23):公共池 pool.json 在才算初始化;各人分片 owners/*.json 由脚本维护。
|
|
39
|
+
return existsSync(path.join(workspacePath, LEDGER_DIR, "pool.json"));
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
42
|
* 在办任务(包 `list --json`;结果过 records 入口校验后打上 workspace)。
|
|
@@ -43,8 +44,13 @@ export function hasLedger(workspacePath) {
|
|
|
43
44
|
* 不静默返空(空 = 撒谎)。**过 1.5s 读缓存**(同屏多端点会反复读同一份在办)。
|
|
44
45
|
*/
|
|
45
46
|
export async function readTasks(workspace, filter = {}) {
|
|
46
|
-
return await cached(cacheKey(workspace.id,
|
|
47
|
-
const result = await runLedgerScript(
|
|
47
|
+
return await cached(cacheKey(workspace.id, "list", argsKey(filter)), async () => {
|
|
48
|
+
const result = await runLedgerScript("list", [
|
|
49
|
+
"--repo",
|
|
50
|
+
workspace.path,
|
|
51
|
+
"--json",
|
|
52
|
+
...filterArgs(filter),
|
|
53
|
+
]);
|
|
48
54
|
if (!result.ok)
|
|
49
55
|
throw new Error(`${workspace.title}: ${result.error}`);
|
|
50
56
|
return parseTaskList(result.data, workspace);
|
|
@@ -52,8 +58,13 @@ export async function readTasks(workspace, filter = {}) {
|
|
|
52
58
|
}
|
|
53
59
|
/** 已结案记录(包 `history --json`;`records` 必须存在且为数组,缺了即坏数据)。同样过读缓存。 */
|
|
54
60
|
export async function readHistory(workspace, filter = {}) {
|
|
55
|
-
return await cached(cacheKey(workspace.id,
|
|
56
|
-
const result = await runLedgerScript(
|
|
61
|
+
return await cached(cacheKey(workspace.id, "history", argsKey(filter)), async () => {
|
|
62
|
+
const result = await runLedgerScript("history", [
|
|
63
|
+
"--repo",
|
|
64
|
+
workspace.path,
|
|
65
|
+
"--json",
|
|
66
|
+
...filterArgs(filter),
|
|
67
|
+
]);
|
|
57
68
|
if (!result.ok)
|
|
58
69
|
throw new Error(`${workspace.title}: ${result.error}`);
|
|
59
70
|
return parseHistoryRecords(result.data, workspace);
|
|
@@ -61,7 +72,7 @@ export async function readHistory(workspace, filter = {}) {
|
|
|
61
72
|
}
|
|
62
73
|
/** 缓存键的 filter 段:脚本参数原样序列化(分隔符取不可见字符,值里带空格也不会串)。 */
|
|
63
74
|
function argsKey(filter) {
|
|
64
|
-
return filterArgs(filter).join(
|
|
75
|
+
return filterArgs(filter).join("\u0001");
|
|
65
76
|
}
|
|
66
77
|
/** 单条在办(展开行 / 详情用)。取不到回 null,不抛。 */
|
|
67
78
|
export async function getTask(workspace, taskId) {
|
|
@@ -74,10 +85,10 @@ export async function getTask(workspace, taskId) {
|
|
|
74
85
|
*/
|
|
75
86
|
export async function readWorklog(workspace, task) {
|
|
76
87
|
const relative = task.worklog;
|
|
77
|
-
if (relative === undefined || relative === null || relative ===
|
|
88
|
+
if (relative === undefined || relative === null || relative === "")
|
|
78
89
|
return null;
|
|
79
90
|
try {
|
|
80
|
-
return await readFile(path.join(workspace.path, LEDGER_DIR, relative),
|
|
91
|
+
return await readFile(path.join(workspace.path, LEDGER_DIR, relative), "utf-8");
|
|
81
92
|
}
|
|
82
93
|
catch {
|
|
83
94
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HistoryRecord, LedgerWorkspace, TaskRow } from
|
|
1
|
+
import type { HistoryRecord, LedgerWorkspace, TaskRow } from "../types.js";
|
|
2
2
|
/** `list --json`(任务号为键的对象)→ TaskRow[](打上 id 与 workspace)。 */
|
|
3
3
|
export declare function parseTaskList(raw: unknown, workspace: LedgerWorkspace): TaskRow[];
|
|
4
4
|
/** `history --json`(`{count, filters, records}`)→ HistoryRecord[](打上 workspace)。 */
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* 旧 8 列 history 不因此被误杀)。
|
|
9
9
|
* 失败抛 {@link LedgerDataError}——由 service 层捕获进 degraded,不静默。
|
|
10
10
|
*/
|
|
11
|
-
import { LedgerDataError } from
|
|
11
|
+
import { LedgerDataError } from "../errors.js";
|
|
12
12
|
function isPlainObject(value) {
|
|
13
|
-
return value !== null && typeof value ===
|
|
13
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
14
14
|
}
|
|
15
15
|
/** `list --json`(任务号为键的对象)→ TaskRow[](打上 id 与 workspace)。 */
|
|
16
16
|
export function parseTaskList(raw, workspace) {
|
|
@@ -22,7 +22,7 @@ export function parseTaskList(raw, workspace) {
|
|
|
22
22
|
if (!isPlainObject(value)) {
|
|
23
23
|
throw new LedgerDataError(workspace.id, `${workspace.title}: 任务 ${id} 不是对象,台账格式可能已变`);
|
|
24
24
|
}
|
|
25
|
-
if (id ===
|
|
25
|
+
if (id === "") {
|
|
26
26
|
throw new LedgerDataError(workspace.id, `${workspace.title}: 台账存在空任务号,格式可能已变`);
|
|
27
27
|
}
|
|
28
28
|
rows.push({ ...value, id, workspace });
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 写命令的超时上限(ms
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 写命令的超时上限(ms):脚本每条 git 调用各自 30s 超时,写前(fetch / ff / push /
|
|
3
|
+
* rebase / push)与写后(push / fetch / rebase / push)串起来最坏约 270s。
|
|
4
|
+
* 低到 120s 会在网络挂死时先报「超时失败」——台账其实已写入,用户一重试就是二次写入。
|
|
5
|
+
* 180s 覆盖常见最坏组合,又不至于让真卡死的请求占着界面太久。
|
|
5
6
|
*/
|
|
6
|
-
export declare const WRITE_TIMEOUT_MS =
|
|
7
|
+
export declare const WRITE_TIMEOUT_MS = 180000;
|
|
7
8
|
export type LedgerScriptResult<T> = {
|
|
8
9
|
ok: true;
|
|
9
10
|
data: T;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { spawn } from
|
|
2
|
-
import { fileURLToPath } from
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
3
|
/**
|
|
4
4
|
* 内嵌 skill 脚本的默认路径:dist/host/repositories/runner.js → 包根 skill/scripts/progress.py
|
|
5
5
|
* (比原 host/script.js 深一层 ⇒ 相对路径多一级 `../`)。
|
|
6
6
|
* 构建时 scripts/copy-skill.mjs 把 packages/skill 拷到 packages/plugin/skill/。
|
|
7
7
|
*/
|
|
8
|
-
const DEFAULT_SCRIPT_PATH = fileURLToPath(new URL(
|
|
8
|
+
const DEFAULT_SCRIPT_PATH = fileURLToPath(new URL("../../../skill/scripts/progress.py", import.meta.url));
|
|
9
9
|
/** 单次脚本调用超时上限(ms)。实测单仓 5 万行全量 1.2s,10s 是宽松上限。 */
|
|
10
10
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
11
11
|
/**
|
|
12
|
-
* 写命令的超时上限(ms
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 写命令的超时上限(ms):脚本每条 git 调用各自 30s 超时,写前(fetch / ff / push /
|
|
13
|
+
* rebase / push)与写后(push / fetch / rebase / push)串起来最坏约 270s。
|
|
14
|
+
* 低到 120s 会在网络挂死时先报「超时失败」——台账其实已写入,用户一重试就是二次写入。
|
|
15
|
+
* 180s 覆盖常见最坏组合,又不至于让真卡死的请求占着界面太久。
|
|
15
16
|
*/
|
|
16
|
-
export const WRITE_TIMEOUT_MS =
|
|
17
|
+
export const WRITE_TIMEOUT_MS = 180_000;
|
|
17
18
|
/**
|
|
18
19
|
* 起一次 python3、收 stdout / stderr。出错一律归一成人话(起不了 python3 / 超时 / 非零退出)。
|
|
19
20
|
* 解析留给调用方:读命令要 JSON,写命令要原文。
|
|
@@ -22,13 +23,19 @@ function exec(sub, args, opts) {
|
|
|
22
23
|
const scriptPath = opts?.scriptPath ?? DEFAULT_SCRIPT_PATH;
|
|
23
24
|
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
24
25
|
return new Promise((resolve) => {
|
|
25
|
-
const child = spawn(
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const child = spawn("python3", [scriptPath, sub, ...args], {
|
|
27
|
+
cwd: opts?.cwd,
|
|
28
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
29
|
+
});
|
|
30
|
+
let stdout = "";
|
|
31
|
+
let stderr = "";
|
|
28
32
|
let settled = false;
|
|
29
33
|
const timer = setTimeout(() => {
|
|
30
34
|
child.kill();
|
|
31
|
-
settle({
|
|
35
|
+
settle({
|
|
36
|
+
ok: false,
|
|
37
|
+
error: `skill 脚本超时(>${timeoutMs}ms):${sub} ${args.join(" ")}`,
|
|
38
|
+
});
|
|
32
39
|
}, timeoutMs);
|
|
33
40
|
const settle = (result) => {
|
|
34
41
|
if (settled)
|
|
@@ -37,14 +44,24 @@ function exec(sub, args, opts) {
|
|
|
37
44
|
clearTimeout(timer);
|
|
38
45
|
resolve(result);
|
|
39
46
|
};
|
|
40
|
-
child.stdout.on(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
child.stdout.on("data", (chunk) => {
|
|
48
|
+
stdout += chunk.toString();
|
|
49
|
+
});
|
|
50
|
+
child.stderr.on("data", (chunk) => {
|
|
51
|
+
stderr += chunk.toString();
|
|
44
52
|
});
|
|
45
|
-
child.on(
|
|
53
|
+
child.on("error", (error) => {
|
|
54
|
+
settle({
|
|
55
|
+
ok: false,
|
|
56
|
+
error: `起不了 python3(${error.message})——本插件需要 python3 运行内嵌 skill 脚本`,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
child.on("close", (code) => {
|
|
46
60
|
if (code !== 0) {
|
|
47
|
-
settle({
|
|
61
|
+
settle({
|
|
62
|
+
ok: false,
|
|
63
|
+
error: stderr.trim() || `skill 脚本退出码 ${String(code)}`,
|
|
64
|
+
});
|
|
48
65
|
return;
|
|
49
66
|
}
|
|
50
67
|
settle({ ok: true, stdout, stderr: stderr.trim() });
|
|
@@ -69,7 +86,10 @@ export async function runLedgerScript(sub, args, opts) {
|
|
|
69
86
|
return { ok: true, data: JSON.parse(result.stdout) };
|
|
70
87
|
}
|
|
71
88
|
catch {
|
|
72
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
error: `skill 脚本输出不是 JSON:${result.stdout.slice(0, 200)}`,
|
|
92
|
+
};
|
|
73
93
|
}
|
|
74
94
|
}
|
|
75
95
|
/**
|
|
@@ -82,5 +102,7 @@ export async function runLedgerCommand(sub, args, opts) {
|
|
|
82
102
|
return result;
|
|
83
103
|
const output = result.stdout.trim();
|
|
84
104
|
// 退出码 0 但 stderr 有字 = 写成功、同步失败(progress.py 的既定行为),必须带出去
|
|
85
|
-
return result.stderr
|
|
105
|
+
return result.stderr
|
|
106
|
+
? { ok: true, output, notice: result.stderr }
|
|
107
|
+
: { ok: true, output };
|
|
86
108
|
}
|
package/dist/host/routes.d.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* 宿主事实(handler = 原始 Node req/res、发头后不抛错、重复 path 注册直接 throw)
|
|
5
5
|
* 见 external/host-webserver.md。
|
|
6
6
|
*/
|
|
7
|
-
import type { IncomingMessage, ServerResponse } from
|
|
8
|
-
import { type AgentsLike } from
|
|
9
|
-
import { type RegistryWorkspace } from
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
import { type AgentsLike } from "./services/session.service.js";
|
|
9
|
+
import { type RegistryWorkspace } from "./workspace.js";
|
|
10
10
|
/** 宿主 webServer.register 的最小面(WebRoute 形状见 external/host-webserver.md)。 */
|
|
11
11
|
export interface WebServerLike {
|
|
12
12
|
register(route: {
|
|
13
|
-
kind:
|
|
13
|
+
kind: "exact" | "prefix";
|
|
14
14
|
path: string;
|
|
15
15
|
handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
|
|
16
16
|
}): unknown;
|