@akagilnc/pi-workflow-roles 0.1.3783 → 0.1.3789
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +487 -236
- package/dist/atomic-write.js +23 -0
- package/dist/collector-handbook.js +142 -0
- package/dist/collector-ledger.js +39 -47
- package/dist/collector-role.js +103 -14
- package/dist/collector-tool-schemas.js +25 -1
- package/dist/headless-host/production-host.js +488 -237
- package/dist/public-cli/main.js +2 -72
- package/dist/public-cli/option-definitions.js +2 -2
- package/dist/role-runtime.js +3 -0
- package/extensions/role-runtime.ts +2 -0
- package/package.json +1 -1
- package/resources/collector-bot-handbook.md +33 -0
- package/src/acp-host/production-host.ts +4 -0
- package/src/collector-handbook.ts +194 -0
- package/src/collector-ledger.ts +42 -62
- package/src/collector-role.ts +117 -11
- package/src/collector-tool-schemas.ts +25 -1
- package/src/public-cli/option-definitions.ts +2 -2
- package/src/role-runtime.ts +5 -0
package/dist/public-cli/main.js
CHANGED
|
@@ -19741,8 +19741,8 @@ var init_option_definitions = __esm({
|
|
|
19741
19741
|
repeatable: false,
|
|
19742
19742
|
form: "option",
|
|
19743
19743
|
description: {
|
|
19744
|
-
en: "Optional request manifest JSON path ({requests:[{id,body}]}).",
|
|
19745
|
-
zh: "\u53EF\u9009 request manifest JSON \u8DEF\u5F84\uFF08{requests:[{id,body}]}\uFF09\u3002"
|
|
19744
|
+
en: "Optional request manifest JSON path ({requests:[{id,body}]}). Role may also decide request bodies from handbook/field activity without this file.",
|
|
19745
|
+
zh: "\u53EF\u9009 request manifest JSON \u8DEF\u5F84\uFF08{requests:[{id,body}]}\uFF09\u3002\u89D2\u8272\u4EA6\u53EF\u4F9D\u624B\u518C/\u73B0\u573A\u81EA\u884C\u5224\u5B9A\u8BF7\u6C42\u6B63\u6587\uFF0C\u4E0D\u5FC5\u4F9D\u8D56\u672C\u6587\u4EF6\u3002"
|
|
19746
19746
|
}
|
|
19747
19747
|
}
|
|
19748
19748
|
];
|
|
@@ -23613,84 +23613,14 @@ var init_collector_identity = __esm({
|
|
|
23613
23613
|
}
|
|
23614
23614
|
});
|
|
23615
23615
|
|
|
23616
|
-
// src/collector-tool-schemas.ts
|
|
23617
|
-
var collectorObserveArgsSchema, collectorRequestArgsSchema, collectorReadArgsSchema, collectorWaitArgsSchema, collectorBindTargetArgsSchema, collectorFindingItemDeclaration, collectorOutputBaseSchema, collectorOutputArgsSchema;
|
|
23618
|
-
var init_collector_tool_schemas = __esm({
|
|
23619
|
-
"src/collector-tool-schemas.ts"() {
|
|
23620
|
-
"use strict";
|
|
23621
|
-
init_build();
|
|
23622
|
-
init_collector_evidence();
|
|
23623
|
-
init_open_tool_schema();
|
|
23624
|
-
init_terminating_infrastructure();
|
|
23625
|
-
collectorObserveArgsSchema = typebox_exports.Object({}, { additionalProperties: false });
|
|
23626
|
-
collectorRequestArgsSchema = typebox_exports.Object({
|
|
23627
|
-
requestId: typebox_exports.String({ minLength: 1, description: "\u914D\u7F6E\u8BF7\u6C42\u8EAB\u4EFD" }),
|
|
23628
|
-
snapshotId: typebox_exports.String({ minLength: 1, description: "\u6700\u65B0\u7559\u5B58\u89C2\u5BDF\u5FEB\u7167" })
|
|
23629
|
-
}, { additionalProperties: false });
|
|
23630
|
-
collectorReadArgsSchema = typebox_exports.Object({
|
|
23631
|
-
evidenceId: typebox_exports.String({ minLength: 1, description: "observe \u8FD4\u56DE\u7684\u6750\u6599\u8BC1\u636E id\uFF08evidenceId\uFF09" })
|
|
23632
|
-
}, { additionalProperties: false });
|
|
23633
|
-
collectorWaitArgsSchema = typebox_exports.Object({
|
|
23634
|
-
durationMs: typebox_exports.Integer({ minimum: 1, maximum: COLLECTOR_ELIGIBILITY_MS, description: "\u7B49\u5F85\u6BEB\u79D2\uFF1B\u5355\u6B21\u4E0A\u9650\u4E94\u5206\u949F\u4E14\u4E0D\u8D85\u5269\u4F59\u8D44\u683C" })
|
|
23635
|
-
}, { additionalProperties: false });
|
|
23636
|
-
collectorBindTargetArgsSchema = typebox_exports.Object({
|
|
23637
|
-
prNumber: typebox_exports.Optional(typebox_exports.Unknown({
|
|
23638
|
-
description: "\u89D2\u8272\u5224\u5B9A\u7684\u672C\u4ED3 PR \u53F7\uFF08\u6B63\u6574\u6570\uFF09\u3002\u4E0E issueNumber \u4E8C\u9009\u4E00\u6216\u540C\u6307\u552F\u4E00\u76EE\u6807\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
|
|
23639
|
-
})),
|
|
23640
|
-
issueNumber: typebox_exports.Optional(typebox_exports.Unknown({
|
|
23641
|
-
description: "\u89D2\u8272\u5224\u5B9A\u7684\u672C\u4ED3 issue \u53F7\uFF08\u6B63\u6574\u6570\uFF09\uFF1Bruntime \u7ECF\u7EBF\u4E0A\u5173\u8054\u89E3\u6790\u552F\u4E00 PR\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
|
|
23642
|
-
}))
|
|
23643
|
-
}, { additionalProperties: true });
|
|
23644
|
-
collectorFindingItemDeclaration = (() => {
|
|
23645
|
-
const item = typebox_exports.Object(
|
|
23646
|
-
{
|
|
23647
|
-
evidenceId: typebox_exports.Unknown({
|
|
23648
|
-
description: "observe \u8FD4\u56DE\u7684\u6750\u6599\u6307\u9488\uFF08\u5FC5\u586B\u8BED\u4E49\uFF09"
|
|
23649
|
-
}),
|
|
23650
|
-
category: typebox_exports.Unknown({
|
|
23651
|
-
description: "\u7B80\u77ED\u5F52\u7C7B\u6807\u7B7E\uFF0C\u4E0D\u662F\u6458\u8981"
|
|
23652
|
-
}),
|
|
23653
|
-
summary: typebox_exports.Unknown({
|
|
23654
|
-
description: "\u54EA\u4E2A bot\u3001\u4EC0\u4E48\u95EE\u9898\u7684\u6458\u8981\uFF1B\u4E0D\u8A8A\u6284\u6B63\u6587"
|
|
23655
|
-
})
|
|
23656
|
-
},
|
|
23657
|
-
{
|
|
23658
|
-
additionalProperties: true,
|
|
23659
|
-
description: "\u5355\u6761 finding \u6307\u9488\uFF1AevidenceId + \u53EF\u9009 category/summary\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
|
|
23660
|
-
}
|
|
23661
|
-
);
|
|
23662
|
-
item.required = [];
|
|
23663
|
-
return item;
|
|
23664
|
-
})();
|
|
23665
|
-
collectorOutputBaseSchema = openToolObject(
|
|
23666
|
-
typebox_exports.Object({
|
|
23667
|
-
// No root type:array — host must not shape-reject non-array findings (#676 C).
|
|
23668
|
-
// Nested item declarations ride `items` for registration preservation (ADR 0057).
|
|
23669
|
-
findings: typebox_exports.Unsafe({
|
|
23670
|
-
description: "\u672C\u6B21\u6536\u96C6\u5230\u7684\u9010\u6761 findings\uFF08\u6307\u9488\u6570\u7EC4\u4E3A\u89C4\u8303\u5F62\uFF09\u3002\u96F6 finding \u7684\u6A21\u677F\u901A\u77E5\u4E0D\u5F97\u8FDB\u5165\uFF1B\u6B63\u5E38\u5B8C\u5DE5\u65E0 finding \u65F6\u7701\u7565\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002",
|
|
23671
|
-
items: collectorFindingItemDeclaration
|
|
23672
|
-
}),
|
|
23673
|
-
unfinishedReasons: typebox_exports.Unknown({
|
|
23674
|
-
description: "\u672A\u5B8C\u6210\u539F\u56E0\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u989D\u5EA6/\u6545\u969C/\u7B49\u5F85\u5C4A\u6EE1\u7B49\u73B0\u573A\u4F9D\u636E\uFF09\uFF1B\u4E0D\u5F97\u628A\u672A\u5B8C\u6210\u8868\u8FF0\u4E3A\u65E0\u95EE\u9898\u3002\u65E0\u53EF\u62A5\u544A\u65F6\u7701\u7565\u3002\u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8\u3002"
|
|
23675
|
-
})
|
|
23676
|
-
})
|
|
23677
|
-
);
|
|
23678
|
-
collectorOutputArgsSchema = withInfrastructureFailureDeclaration(
|
|
23679
|
-
collectorOutputBaseSchema
|
|
23680
|
-
);
|
|
23681
|
-
}
|
|
23682
|
-
});
|
|
23683
|
-
|
|
23684
23616
|
// src/collector-ledger.ts
|
|
23685
23617
|
var COLLECTOR_OBSERVE_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, COLLECTOR_BIND_TARGET_TOOL;
|
|
23686
23618
|
var init_collector_ledger = __esm({
|
|
23687
23619
|
"src/collector-ledger.ts"() {
|
|
23688
23620
|
"use strict";
|
|
23689
|
-
init_value2();
|
|
23690
23621
|
init_collector_evidence();
|
|
23691
23622
|
init_collector_github();
|
|
23692
23623
|
init_collector_identity();
|
|
23693
|
-
init_collector_tool_schemas();
|
|
23694
23624
|
init_collector_output();
|
|
23695
23625
|
COLLECTOR_OBSERVE_TOOL = "ak_collector_observe";
|
|
23696
23626
|
COLLECTOR_READ_TOOL = "ak_collector_read";
|
|
@@ -483,8 +483,8 @@ const COLLECTOR_OPTIONS = [
|
|
|
483
483
|
repeatable: false,
|
|
484
484
|
form: "option",
|
|
485
485
|
description: {
|
|
486
|
-
en: "Optional request manifest JSON path ({requests:[{id,body}]}).",
|
|
487
|
-
zh: "可选 request manifest JSON 路径({requests:[{id,body}]}
|
|
486
|
+
en: "Optional request manifest JSON path ({requests:[{id,body}]}). Role may also decide request bodies from handbook/field activity without this file.",
|
|
487
|
+
zh: "可选 request manifest JSON 路径({requests:[{id,body}]})。角色亦可依手册/现场自行判定请求正文,不必依赖本文件。",
|
|
488
488
|
},
|
|
489
489
|
},
|
|
490
490
|
];
|
package/dist/role-runtime.js
CHANGED
|
@@ -1223,6 +1223,9 @@ export function createRoleRuntimeExtension(dependencies) {
|
|
|
1223
1223
|
...(dependencies.createCollectorClock === undefined
|
|
1224
1224
|
? {}
|
|
1225
1225
|
: { createClock: dependencies.createCollectorClock }),
|
|
1226
|
+
...(dependencies.loadCollectorHandbookSeed === undefined
|
|
1227
|
+
? {}
|
|
1228
|
+
: { loadHandbookSeed: dependencies.loadCollectorHandbookSeed }),
|
|
1226
1229
|
}, hostActions);
|
|
1227
1230
|
// #676 J1: business tools + tool_call gate register only behind admission/activation.
|
|
1228
1231
|
// before_agent_start / tool_result collector branches live on the shared envelope hooks above.
|
|
@@ -40,6 +40,7 @@ import { loadGatekeeperSessionMaterials, loadMainRoleSessionMaterials } from "..
|
|
|
40
40
|
const extensionPath = fileURLToPath(import.meta.url);
|
|
41
41
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
42
42
|
const navigatorRoutePlaybookPath = fileURLToPath(new URL("../resources/navigator-route-playbook.md", import.meta.url));
|
|
43
|
+
const collectorHandbookSeedPath = fileURLToPath(new URL("../resources/collector-bot-handbook.md", import.meta.url));
|
|
43
44
|
// #675: nested public summons (gate/auditor) resolve root via env under jiti.
|
|
44
45
|
if (process.env.AK_ROLE_PACKAGE_ROOT === undefined || process.env.AK_ROLE_PACKAGE_ROOT.trim() === "") {
|
|
45
46
|
process.env.AK_ROLE_PACKAGE_ROOT = packageRoot;
|
|
@@ -122,6 +123,7 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
122
123
|
createReviewerPinnedGitReader: () => createReviewerPinnedGitReader(),
|
|
123
124
|
createReviewerIssueFetcher: () => createGhIssueSoftFetcher(),
|
|
124
125
|
loadCollectorSoul: () => loadMainRoleSessionMaterials("collector"),
|
|
126
|
+
loadCollectorHandbookSeed: () => readFile(collectorHandbookSeedPath, "utf8"),
|
|
125
127
|
createCollectorTransport: () => createGhCollectorGitHubTransport(),
|
|
126
128
|
loadDoctorSoul: () => loadMainRoleSessionMaterials("doctor"),
|
|
127
129
|
loadDoctorCase,
|
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 通进司 bot 工作记忆(通用)
|
|
2
|
+
|
|
3
|
+
薄手册:触发方式、自动/手动条件、完成与不可继续的识别线索、官方出处。
|
|
4
|
+
内容是角色阅读与修正的工作记忆,不是代码状态规则;临时额度/故障不得固化为永久行为。
|
|
5
|
+
参与者依本仓使用与现场活动辨认,不把下列名称当作全仓固定名单。
|
|
6
|
+
|
|
7
|
+
## Codex(hosted GitHub connector)
|
|
8
|
+
|
|
9
|
+
- 官方:https://learn.chatgpt.com/docs/third-party/github
|
|
10
|
+
- 常见主动请求:`@codex review`
|
|
11
|
+
- 新建 PR 常有自动评审;是否已在审当前提交以现场 reviews/comments 为准
|
|
12
|
+
|
|
13
|
+
## CodeRabbit
|
|
14
|
+
|
|
15
|
+
- 官方:https://docs.coderabbit.ai/configuration/auto-review
|
|
16
|
+
- 命令:https://docs.coderabbit.ai/guides/commands
|
|
17
|
+
- 常见主动请求:`@coderabbitai review`;全量:`@coderabbitai full review`
|
|
18
|
+
- 默认常为增量;本仓实际自动/暂停以现场与仓库差异条为准
|
|
19
|
+
|
|
20
|
+
## Sourcery
|
|
21
|
+
|
|
22
|
+
- 官方:https://docs.sourcery.ai/reviews/ 与 https://docs.sourcery.ai/reviews/commands/
|
|
23
|
+
- 新建/更新与主动请求依文档与本仓现场
|
|
24
|
+
|
|
25
|
+
## Cursor Bugbot / 自动化
|
|
26
|
+
|
|
27
|
+
- 官方:https://prod.cursor.com/help/ai-features/bugbot 与 automations 文档
|
|
28
|
+
- 内置 Bugbot 与同账号自定义任务须区分;某任务额度耗尽不推断其他任务失效
|
|
29
|
+
|
|
30
|
+
## 修正规则
|
|
31
|
+
|
|
32
|
+
- 优先引用官方文档;现场差异记入仓库差异条并附 PR/证据指针
|
|
33
|
+
- 未知或暂未响应 ≠ 通过或确定死亡
|
|
@@ -39,6 +39,9 @@ export type ProductionAcpHostOptions = Readonly<{
|
|
|
39
39
|
const navigatorRoutePlaybookPath = fileURLToPath(
|
|
40
40
|
new URL("../../resources/navigator-route-playbook.md", import.meta.url),
|
|
41
41
|
);
|
|
42
|
+
const collectorHandbookSeedPath = fileURLToPath(
|
|
43
|
+
new URL("../../resources/collector-bot-handbook.md", import.meta.url),
|
|
44
|
+
);
|
|
42
45
|
|
|
43
46
|
/** Host-neutral packaged role runtime deps for the ACP parent-process envelope. */
|
|
44
47
|
export function createAcpRoleRuntimeDependencies(packageRoot: string): RoleRuntimeDependencies {
|
|
@@ -55,6 +58,7 @@ export function createAcpRoleRuntimeDependencies(packageRoot: string): RoleRunti
|
|
|
55
58
|
createReviewerPinnedGitReader: () => createReviewerPinnedGitReader(),
|
|
56
59
|
createReviewerIssueFetcher: () => createGhIssueSoftFetcher(),
|
|
57
60
|
loadCollectorSoul: () => loadMainRoleSessionMaterials("collector"),
|
|
61
|
+
loadCollectorHandbookSeed: () => readFile(collectorHandbookSeedPath, "utf8"),
|
|
58
62
|
createCollectorTransport: () => createGhCollectorGitHubTransport(),
|
|
59
63
|
loadDoctorSoul: () => loadMainRoleSessionMaterials("doctor"),
|
|
60
64
|
loadDoctorCase,
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector bot handbook — opaque working memory under the book topology.
|
|
3
|
+
* Runtime stores and delivers UTF-8 text only; never parses free text into
|
|
4
|
+
* code state rules (parent #673 D3 / #677).
|
|
5
|
+
*
|
|
6
|
+
* Placement reuses the machine ledger home (ADR 0048) when the admitted
|
|
7
|
+
* session already sits under books/<bookKey>/ — no parallel persistence frame.
|
|
8
|
+
*/
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { join, sep } from "node:path";
|
|
11
|
+
|
|
12
|
+
import { writeFileAtomically } from "./atomic-write.ts";
|
|
13
|
+
import {
|
|
14
|
+
activationBookDirectory,
|
|
15
|
+
assertLedgerFileInsideHome,
|
|
16
|
+
ensureRealDirectoryTree,
|
|
17
|
+
resolveActivationLedgerHomeForPath,
|
|
18
|
+
} from "./activation-ledger-topology.ts";
|
|
19
|
+
import { COLLECTOR_HANDBOOK_MAX_BYTES } from "./collector-tool-schemas.ts";
|
|
20
|
+
|
|
21
|
+
export type CollectorHandbookScope = "general" | "repo";
|
|
22
|
+
|
|
23
|
+
export type CollectorHandbookRead = {
|
|
24
|
+
readonly general: string;
|
|
25
|
+
readonly repo: string;
|
|
26
|
+
readonly generalSource: "book" | "seed" | "empty";
|
|
27
|
+
readonly repoSource: "book" | "empty";
|
|
28
|
+
readonly generalPath: string;
|
|
29
|
+
readonly repoPath: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type CollectorHandbookWriteResult = {
|
|
33
|
+
readonly scope: CollectorHandbookScope;
|
|
34
|
+
readonly path: string;
|
|
35
|
+
readonly byteLength: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type CollectorHandbookStore = {
|
|
39
|
+
readonly root: string;
|
|
40
|
+
readonly repositoryCanonical: string;
|
|
41
|
+
read(): Promise<CollectorHandbookRead>;
|
|
42
|
+
write(scope: CollectorHandbookScope, body: string): Promise<CollectorHandbookWriteResult>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type CollectorHandbookPlacement = {
|
|
46
|
+
readonly ledgerHome: string;
|
|
47
|
+
readonly bookKey: string;
|
|
48
|
+
readonly root: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Derive handbook root from an admitted session/run path under
|
|
53
|
+
* `.ak-roles/books/<bookKey>/...`. Fails closed when topology is absent.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveCollectorHandbookRoot(sessionPath: string): CollectorHandbookPlacement {
|
|
56
|
+
if (typeof sessionPath !== "string" || sessionPath.trim().length === 0) {
|
|
57
|
+
throw new Error("通进司手册要求非空 session 路径,且位于 books/<bookKey>/");
|
|
58
|
+
}
|
|
59
|
+
// Platform path segments only — never rewrite non-separator characters (POSIX `\\` is literal).
|
|
60
|
+
const segments = sessionPath.split(sep);
|
|
61
|
+
let bookKey: string | undefined;
|
|
62
|
+
for (let i = 0; i + 3 < segments.length; i += 1) {
|
|
63
|
+
if (segments[i] === ".ak-roles" && segments[i + 1] === "books") {
|
|
64
|
+
const candidate = segments[i + 2]!;
|
|
65
|
+
// Require a following segment so bookKey is a true path component under books/.
|
|
66
|
+
if (candidate.length > 0) {
|
|
67
|
+
bookKey = candidate;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (bookKey === undefined) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`通进司手册要求 session 位于 books/<bookKey>/;收到 ${sessionPath}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (bookKey === "." || bookKey === "..") {
|
|
78
|
+
throw new Error(`通进司手册拒绝不安全 bookKey ${JSON.stringify(bookKey)}`);
|
|
79
|
+
}
|
|
80
|
+
const ledgerHome = resolveActivationLedgerHomeForPath(sessionPath);
|
|
81
|
+
const root = join(activationBookDirectory(ledgerHome, bookKey), "collector-handbook");
|
|
82
|
+
return { ledgerHome, bookKey, root };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Flat repo file name under handbook/repos/ — avoids nested owner/repo dirs. */
|
|
86
|
+
function collectorHandbookRepoFileName(repositoryCanonical: string): string {
|
|
87
|
+
if (!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/.test(repositoryCanonical)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`通进司手册仓库文件要求规范 owner/repo,收到 ${JSON.stringify(repositoryCanonical)}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return `${repositoryCanonical.replaceAll("/", "__")}.md`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function createCollectorHandbookStore(input: {
|
|
96
|
+
readonly ledgerHome: string;
|
|
97
|
+
readonly handbookRoot: string;
|
|
98
|
+
readonly repositoryCanonical: string;
|
|
99
|
+
readonly seedGeneral?: string;
|
|
100
|
+
}): CollectorHandbookStore {
|
|
101
|
+
const generalPath = join(input.handbookRoot, "general.md");
|
|
102
|
+
const repoDir = join(input.handbookRoot, "repos");
|
|
103
|
+
const repoPath = join(repoDir, collectorHandbookRepoFileName(input.repositoryCanonical));
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* ADR 0038: confine at the real read seam. Parent dirs must be physical under
|
|
107
|
+
* ledger home; the leaf must not be a pre-existing symlink (even in-home).
|
|
108
|
+
*/
|
|
109
|
+
/** Sole UTF-8 budget seam — write and read share COLLECTOR_HANDBOOK_MAX_BYTES. */
|
|
110
|
+
const assertHandbookBudget = (body: string, label: string): number => {
|
|
111
|
+
const byteLength = Buffer.byteLength(body, "utf8");
|
|
112
|
+
if (byteLength > COLLECTOR_HANDBOOK_MAX_BYTES) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`通进司手册${label} UTF-8 至多 ${COLLECTOR_HANDBOOK_MAX_BYTES} 字节,收到 ${byteLength}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return byteLength;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const readOptional = async (path: string, parentDir: string): Promise<string | undefined> => {
|
|
121
|
+
ensureRealDirectoryTree(input.ledgerHome, parentDir);
|
|
122
|
+
assertLedgerFileInsideHome(path, input.ledgerHome);
|
|
123
|
+
try {
|
|
124
|
+
const body = await readFile(path, "utf8");
|
|
125
|
+
assertHandbookBudget(body, "正文");
|
|
126
|
+
return body;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (isNotFound(error)) return undefined;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
root: input.handbookRoot,
|
|
135
|
+
repositoryCanonical: input.repositoryCanonical,
|
|
136
|
+
async read() {
|
|
137
|
+
const bookGeneral = await readOptional(generalPath, input.handbookRoot);
|
|
138
|
+
const bookRepo = await readOptional(repoPath, repoDir);
|
|
139
|
+
if (bookGeneral !== undefined) {
|
|
140
|
+
return {
|
|
141
|
+
general: bookGeneral,
|
|
142
|
+
repo: bookRepo ?? "",
|
|
143
|
+
generalSource: "book",
|
|
144
|
+
repoSource: bookRepo === undefined ? "empty" : "book",
|
|
145
|
+
generalPath,
|
|
146
|
+
repoPath,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const seed = input.seedGeneral;
|
|
150
|
+
if (typeof seed === "string" && seed.length > 0) {
|
|
151
|
+
assertHandbookBudget(seed, "正文");
|
|
152
|
+
return {
|
|
153
|
+
general: seed,
|
|
154
|
+
repo: bookRepo ?? "",
|
|
155
|
+
generalSource: "seed",
|
|
156
|
+
repoSource: bookRepo === undefined ? "empty" : "book",
|
|
157
|
+
generalPath,
|
|
158
|
+
repoPath,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
general: "",
|
|
163
|
+
repo: bookRepo ?? "",
|
|
164
|
+
generalSource: "empty",
|
|
165
|
+
repoSource: bookRepo === undefined ? "empty" : "book",
|
|
166
|
+
generalPath,
|
|
167
|
+
repoPath,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
async write(scope, body) {
|
|
171
|
+
// body string shape = collectorHandbookWriteArgsSchema; byte budget is business (UTF-8).
|
|
172
|
+
const byteLength = assertHandbookBudget(body, "正文");
|
|
173
|
+
ensureRealDirectoryTree(input.ledgerHome, input.handbookRoot);
|
|
174
|
+
const path = scope === "general" ? generalPath : repoPath;
|
|
175
|
+
if (scope === "repo") {
|
|
176
|
+
ensureRealDirectoryTree(input.ledgerHome, repoDir);
|
|
177
|
+
}
|
|
178
|
+
assertLedgerFileInsideHome(path, input.ledgerHome);
|
|
179
|
+
await writeFileAtomically(path, body);
|
|
180
|
+
return {
|
|
181
|
+
scope,
|
|
182
|
+
path,
|
|
183
|
+
byteLength,
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isNotFound(error: unknown): boolean {
|
|
190
|
+
return typeof error === "object"
|
|
191
|
+
&& error !== null
|
|
192
|
+
&& "code" in error
|
|
193
|
+
&& (error as { code?: unknown }).code === "ENOENT";
|
|
194
|
+
}
|
package/src/collector-ledger.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Value from "typebox/value";
|
|
2
|
-
|
|
3
1
|
import type { CollectorManifest, CollectorRepository } from "./collector-config.ts";
|
|
4
2
|
import {
|
|
5
3
|
applyEvidenceVersionHistory,
|
|
@@ -26,13 +24,6 @@ import {
|
|
|
26
24
|
type GitHubPullRequest,
|
|
27
25
|
} from "./collector-github.ts";
|
|
28
26
|
import { CollectorNonOpenRequestError } from "./collector-identity.ts";
|
|
29
|
-
import {
|
|
30
|
-
collectorObserveArgsSchema,
|
|
31
|
-
collectorOutputArgsSchema,
|
|
32
|
-
collectorReadArgsSchema,
|
|
33
|
-
collectorRequestArgsSchema,
|
|
34
|
-
collectorWaitArgsSchema,
|
|
35
|
-
} from "./collector-tool-schemas.ts";
|
|
36
27
|
import { COLLECTOR_OUTPUT_TOOL } from "./package-contracts/collector-output.ts";
|
|
37
28
|
|
|
38
29
|
export const COLLECTOR_OBSERVE_TOOL = "ak_collector_observe";
|
|
@@ -41,6 +32,8 @@ export const COLLECTOR_REQUEST_TOOL = "ak_collector_request";
|
|
|
41
32
|
export const COLLECTOR_WAIT_TOOL = "ak_collector_wait";
|
|
42
33
|
/** #676 A: role-decided target bind — business tool, ledger-booked. */
|
|
43
34
|
export const COLLECTOR_BIND_TARGET_TOOL = "ak_collector_bind_target";
|
|
35
|
+
/** #677: opaque handbook write — business tool, ledger-booked. */
|
|
36
|
+
export const COLLECTOR_HANDBOOK_WRITE_TOOL = "ak_collector_handbook_write";
|
|
44
37
|
export { COLLECTOR_OUTPUT_TOOL };
|
|
45
38
|
|
|
46
39
|
export const COLLECTOR_OPERATIONAL_TOOLS = [
|
|
@@ -49,6 +42,7 @@ export const COLLECTOR_OPERATIONAL_TOOLS = [
|
|
|
49
42
|
COLLECTOR_READ_TOOL,
|
|
50
43
|
COLLECTOR_REQUEST_TOOL,
|
|
51
44
|
COLLECTOR_WAIT_TOOL,
|
|
45
|
+
COLLECTOR_HANDBOOK_WRITE_TOOL,
|
|
52
46
|
] as const;
|
|
53
47
|
|
|
54
48
|
/**
|
|
@@ -57,7 +51,7 @@ export const COLLECTOR_OPERATIONAL_TOOLS = [
|
|
|
57
51
|
* (evidenceId + htmlUrl) and is preserved in the volume, never unconditionally
|
|
58
52
|
* transcribed into model context or receipt.
|
|
59
53
|
*/
|
|
60
|
-
|
|
54
|
+
const COLLECTOR_OBSERVE_BODY_HEAD_BYTES = 512;
|
|
61
55
|
|
|
62
56
|
function utf8SafePrefix(text: string, maxBytes: number): string {
|
|
63
57
|
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
@@ -108,7 +102,7 @@ export type ObserveModelView = {
|
|
|
108
102
|
* (collector-side; provider-visible observe details carry only this bounded
|
|
109
103
|
* projection), never unconditionally transcribed into model context.
|
|
110
104
|
*/
|
|
111
|
-
|
|
105
|
+
function projectObserveContextView(modelView: ObserveModelView): ObserveModelView {
|
|
112
106
|
return {
|
|
113
107
|
...modelView,
|
|
114
108
|
evidence: modelView.evidence.map((entry) =>
|
|
@@ -121,9 +115,9 @@ export function projectObserveContextView(modelView: ObserveModelView): ObserveM
|
|
|
121
115
|
export type CollectorOperationalTool = (typeof COLLECTOR_OPERATIONAL_TOOLS)[number];
|
|
122
116
|
|
|
123
117
|
export const COLLECTOR_ACTIVATION_ENTRY_TYPE = "ak-collector-activation" as const;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
const COLLECTOR_SNAPSHOT_ENTRY_TYPE = "ak-collector-snapshot" as const;
|
|
119
|
+
const COLLECTOR_REQUEST_ENTRY_TYPE = "ak-collector-request" as const;
|
|
120
|
+
const COLLECTOR_WAIT_ENTRY_TYPE = "ak-collector-wait" as const;
|
|
127
121
|
|
|
128
122
|
export type CollectorRequestAttempt = {
|
|
129
123
|
attemptId: string;
|
|
@@ -217,7 +211,7 @@ export type CollectorLedger = {
|
|
|
217
211
|
}>;
|
|
218
212
|
|
|
219
213
|
request(
|
|
220
|
-
input: { requestId: string; snapshotId: string },
|
|
214
|
+
input: { requestId: string; snapshotId: string; body?: string },
|
|
221
215
|
transport: CollectorGitHubTransport,
|
|
222
216
|
clock: CollectorClock,
|
|
223
217
|
signal?: AbortSignal,
|
|
@@ -243,29 +237,6 @@ function isOperationalTool(name: string): name is CollectorOperationalTool {
|
|
|
243
237
|
return (COLLECTOR_OPERATIONAL_TOOLS as readonly string[]).includes(name);
|
|
244
238
|
}
|
|
245
239
|
|
|
246
|
-
/** Shared Check owner for Collector tool argument shapes (schema seam, not batch law). */
|
|
247
|
-
export function collectorToolArgumentsValid(
|
|
248
|
-
name: string,
|
|
249
|
-
args: unknown,
|
|
250
|
-
): boolean {
|
|
251
|
-
// Residual envelope: reject missing/null args before schema check.
|
|
252
|
-
if (args === undefined || args === null) return false;
|
|
253
|
-
switch (name) {
|
|
254
|
-
case COLLECTOR_OBSERVE_TOOL:
|
|
255
|
-
return Value.Check(collectorObserveArgsSchema, args);
|
|
256
|
-
case COLLECTOR_READ_TOOL:
|
|
257
|
-
return Value.Check(collectorReadArgsSchema, args);
|
|
258
|
-
case COLLECTOR_REQUEST_TOOL:
|
|
259
|
-
return Value.Check(collectorRequestArgsSchema, args);
|
|
260
|
-
case COLLECTOR_WAIT_TOOL:
|
|
261
|
-
return Value.Check(collectorWaitArgsSchema, args);
|
|
262
|
-
case COLLECTOR_OUTPUT_TOOL:
|
|
263
|
-
return Value.Check(collectorOutputArgsSchema, args);
|
|
264
|
-
default:
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
240
|
export function createCollectorLedger(
|
|
270
241
|
config: CollectorConfigState,
|
|
271
242
|
options?: CollectorLedgerOptions,
|
|
@@ -335,9 +306,7 @@ export function createCollectorLedger(
|
|
|
335
306
|
|
|
336
307
|
const requireBoundPr = (): number => {
|
|
337
308
|
if (config.prNumber === undefined) {
|
|
338
|
-
throw new Error(
|
|
339
|
-
"Collector PR target is unbound; call ak_collector_bind_target with the role-decided issue/PR or pass --pr",
|
|
340
|
-
);
|
|
309
|
+
throw new Error("通进司 PR 目标未绑定");
|
|
341
310
|
}
|
|
342
311
|
return config.prNumber;
|
|
343
312
|
};
|
|
@@ -675,11 +644,11 @@ export function createCollectorLedger(
|
|
|
675
644
|
throw new Error("通进司已产出输出候选,本局不再受理目标绑定");
|
|
676
645
|
}
|
|
677
646
|
if (!Number.isSafeInteger(prNumber) || prNumber < 1) {
|
|
678
|
-
throw new Error("
|
|
647
|
+
throw new Error("通进司绑定目标要求正安全整数 PR 号");
|
|
679
648
|
}
|
|
680
649
|
if (config.prNumber !== undefined && config.prNumber !== prNumber) {
|
|
681
650
|
throw new Error(
|
|
682
|
-
|
|
651
|
+
`通进司目标已绑定 PR ${config.prNumber},不可改绑为 ${prNumber}`,
|
|
683
652
|
);
|
|
684
653
|
}
|
|
685
654
|
config.prNumber = prNumber;
|
|
@@ -915,9 +884,25 @@ export function createCollectorLedger(
|
|
|
915
884
|
throw latchFatal("通进司请求时存在未恢复的传输失败");
|
|
916
885
|
}
|
|
917
886
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
887
|
+
// Sole request identity seam: trim once, then use everywhere (lookup/marker/attemptKey).
|
|
888
|
+
// Schema pattern rejects leading/trailing whitespace at host; trim still collapses
|
|
889
|
+
// direct ledger callers and any residual padding into one stable id (#677 D2).
|
|
890
|
+
if (typeof input.requestId !== "string") {
|
|
891
|
+
throw new Error("通进司 requestId 须为字符串");
|
|
892
|
+
}
|
|
893
|
+
const requestId = input.requestId.trim();
|
|
894
|
+
if (requestId.length === 0) {
|
|
895
|
+
throw new Error("通进司 requestId 须为非空(首尾空白不构成身份)");
|
|
896
|
+
}
|
|
897
|
+
const configured = config.manifest.requests.find((item) => item.id === requestId);
|
|
898
|
+
const roleBody = typeof input.body === "string" ? input.body : undefined;
|
|
899
|
+
if (configured === undefined) {
|
|
900
|
+
// #677: role-decided trigger from handbook/field activity — body required when not in manifest.
|
|
901
|
+
if (roleBody === undefined || roleBody.trim().length === 0) {
|
|
902
|
+
throw new Error(
|
|
903
|
+
`未知通进司 requestId "${requestId}" 且未提供 body;请提交角色判定的请求正文或使用 request-manifest`,
|
|
904
|
+
);
|
|
905
|
+
}
|
|
921
906
|
}
|
|
922
907
|
|
|
923
908
|
const snapshot = snapshots.find((item) => item.snapshotId === input.snapshotId);
|
|
@@ -936,10 +921,12 @@ export function createCollectorLedger(
|
|
|
936
921
|
throw latchFatal("通进司请求不在资格截止前");
|
|
937
922
|
}
|
|
938
923
|
|
|
924
|
+
// Caller manifest body wins when requestId is configured; otherwise role body.
|
|
925
|
+
const configuredBody = configured?.requestBody ?? roleBody!;
|
|
939
926
|
const { body, marker } = buildCollectorRequestBody({
|
|
940
|
-
configuredBody
|
|
927
|
+
configuredBody,
|
|
941
928
|
manifestDigest: config.manifest.digest,
|
|
942
|
-
requestId
|
|
929
|
+
requestId,
|
|
943
930
|
headOid: snapshot.headOid,
|
|
944
931
|
});
|
|
945
932
|
const existingMarker = snapshot.evidenceIds.some((id) => {
|
|
@@ -951,7 +938,7 @@ export function createCollectorLedger(
|
|
|
951
938
|
});
|
|
952
939
|
if (existingMarker) {
|
|
953
940
|
throw new Error(
|
|
954
|
-
`通进司在此 HEAD 已有同 marker 的已认证请求 "${
|
|
941
|
+
`通进司在此 HEAD 已有同 marker 的已认证请求 "${requestId}"`,
|
|
955
942
|
);
|
|
956
943
|
}
|
|
957
944
|
|
|
@@ -960,11 +947,11 @@ export function createCollectorLedger(
|
|
|
960
947
|
config.repository.canonical,
|
|
961
948
|
String(boundPr),
|
|
962
949
|
snapshot.headOid,
|
|
963
|
-
|
|
950
|
+
requestId,
|
|
964
951
|
].join("|");
|
|
965
952
|
if (attemptKeys.has(attemptKey)) {
|
|
966
953
|
throw new Error(
|
|
967
|
-
`通进司进程内请求 "${
|
|
954
|
+
`通进司进程内请求 "${requestId}" 在 HEAD ${snapshot.headOid} 的 attempt 已用`,
|
|
968
955
|
);
|
|
969
956
|
}
|
|
970
957
|
|
|
@@ -972,7 +959,7 @@ export function createCollectorLedger(
|
|
|
972
959
|
const attemptId = sha256Text(`${attemptKey}:${startedAt}`).slice(0, 16);
|
|
973
960
|
const attempt: CollectorRequestAttempt = {
|
|
974
961
|
attemptId,
|
|
975
|
-
requestId
|
|
962
|
+
requestId,
|
|
976
963
|
observedHead: snapshot.headOid,
|
|
977
964
|
snapshotId: snapshot.snapshotId,
|
|
978
965
|
marker,
|
|
@@ -1017,7 +1004,7 @@ export function createCollectorLedger(
|
|
|
1017
1004
|
return {
|
|
1018
1005
|
status: "succeeded",
|
|
1019
1006
|
attemptId,
|
|
1020
|
-
requestId
|
|
1007
|
+
requestId,
|
|
1021
1008
|
observedHead: snapshot.headOid,
|
|
1022
1009
|
marker,
|
|
1023
1010
|
commentEvidenceId: record.evidenceId,
|
|
@@ -1032,7 +1019,7 @@ export function createCollectorLedger(
|
|
|
1032
1019
|
return {
|
|
1033
1020
|
status: "ambiguous_loss",
|
|
1034
1021
|
attemptId,
|
|
1035
|
-
requestId
|
|
1022
|
+
requestId,
|
|
1036
1023
|
observedHead: snapshot.headOid,
|
|
1037
1024
|
marker,
|
|
1038
1025
|
diagnostics: result.diagnostics,
|
|
@@ -1051,14 +1038,7 @@ export function createCollectorLedger(
|
|
|
1051
1038
|
if (activationTime === undefined) {
|
|
1052
1039
|
throw latchFatal("通进司等待需要激活");
|
|
1053
1040
|
}
|
|
1054
|
-
|
|
1055
|
-
throw new Error("通进司等待 durationMs 须为正安全整数");
|
|
1056
|
-
}
|
|
1057
|
-
if (input.durationMs > COLLECTOR_ELIGIBILITY_MS) {
|
|
1058
|
-
throw new Error(
|
|
1059
|
-
`通进司等待 durationMs 至多为 ${COLLECTOR_ELIGIBILITY_MS}`,
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1041
|
+
// durationMs shape authority = collectorWaitArgsSchema (host parameters).
|
|
1062
1042
|
if (pastCutoff(clock)) {
|
|
1063
1043
|
finalObservationRequired = true;
|
|
1064
1044
|
throw latchFatal("通进司等待不在资格截止前");
|