@akagilnc/pi-workflow-roles 0.1.5289 → 0.1.5301
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 +7 -3
- package/README.zh-CN.md +7 -3
- package/dist/acp-host/production-host.js +97 -462
- package/dist/diarist-contracts.js +20 -96
- package/dist/headless-host/production-host.js +97 -462
- package/dist/migrate-book-topology.js +75 -77
- package/dist/public-cli/countersign-run.js +3 -124
- package/dist/public-cli/instruction-seat-run.js +5 -97
- package/dist/public-cli/invocation.js +0 -33
- package/dist/public-cli/main.js +10 -362
- package/dist/public-cli/post-admission.js +2 -14
- package/dist/public-cli/run-lifecycle.js +0 -85
- package/dist/public-role-summons.js +2 -2
- package/dist/ticket-provenance.js +25 -29
- package/package.json +1 -1
- package/resources/diarist-collect.md +4 -4
- package/src/diarist-contracts.ts +24 -116
- package/src/diarist-role.ts +39 -50
- package/src/diarist.ts +9 -29
- package/src/public-cli/countersign-run.ts +7 -184
- package/src/public-cli/instruction-seat-run.ts +6 -101
- package/src/public-cli/invocation.ts +2 -64
- package/src/public-cli/post-admission.ts +2 -15
- package/src/public-cli/run-lifecycle.ts +0 -117
- package/src/public-role-summons.ts +3 -3
- package/src/role-runtime.ts +13 -7
- package/src/ticket-provenance.ts +31 -44
|
@@ -14,7 +14,6 @@ import { CliUsageError } from "./cli-errors.js";
|
|
|
14
14
|
import { readLatestTypedProviderHttpObservation, } from "../typed-provider-http.js";
|
|
15
15
|
export { clearTypedProviderHttpObservation, recordTypedProviderHttpStatus, readLatestTypedProviderHttpObservation, } from "../typed-provider-http.js";
|
|
16
16
|
import { parseCollectorRepository } from "../collector-config.js";
|
|
17
|
-
import { interpretDurableCourtTicketNumbers, sameCourtTicketNumbers, } from "../diarist-contracts.js";
|
|
18
17
|
import { rewriteAdmittedRoleRunPage, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
|
|
19
18
|
import { appendEngineSessionMaterial, engineSessionMaterialFromOptions, pickEngineAxis, } from "../package-resources/engine-material.js";
|
|
20
19
|
import { packagedResumeSourcePath, packagedRoleMetadata, } from "../packaged-role-registry.js";
|
|
@@ -865,64 +864,10 @@ function parsePersistedTicketIdentity(record) {
|
|
|
865
864
|
...(ticketNumber === undefined ? {} : { ticketNumber }),
|
|
866
865
|
};
|
|
867
866
|
}
|
|
868
|
-
/**
|
|
869
|
-
* #871 durable set from one run page.
|
|
870
|
-
* Absent → absent. Present damage keeps the original reason (no throw here).
|
|
871
|
-
*/
|
|
872
|
-
function readDurableCourtTicketNumbersPage(record, principalTicket, pageLabel) {
|
|
873
|
-
const interpreted = interpretDurableCourtTicketNumbers(record, {
|
|
874
|
-
...(principalTicket === undefined ? {} : { principalTicket }),
|
|
875
|
-
});
|
|
876
|
-
if (interpreted.kind === "damage") {
|
|
877
|
-
return {
|
|
878
|
-
kind: "damage",
|
|
879
|
-
reason: `role run ${pageLabel} courtTicketNumbers is damaged: ${interpreted.reason}`,
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
if (interpreted.kind === "ok") {
|
|
883
|
-
return { kind: "ok", tickets: interpreted.tickets };
|
|
884
|
-
}
|
|
885
|
-
return { kind: "absent" };
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Merge admitted + invocation #871 sets.
|
|
889
|
-
* Both present must match; one present wins; both absent stays legacy.
|
|
890
|
-
* Damage / cross-page mismatch return a diagnostic — countersign resume settles
|
|
891
|
-
* controlled failure; load itself does not structural-reject.
|
|
892
|
-
*/
|
|
893
|
-
function mergeDurableCourtTicketNumbers(input) {
|
|
894
|
-
const fromAdmitted = input.admittedRecord === undefined
|
|
895
|
-
? { kind: "absent" }
|
|
896
|
-
: readDurableCourtTicketNumbersPage(input.admittedRecord, input.principalTicket, "admitted-request");
|
|
897
|
-
const fromInvocation = input.invocationRecord === undefined
|
|
898
|
-
? { kind: "absent" }
|
|
899
|
-
: readDurableCourtTicketNumbersPage(input.invocationRecord, input.principalTicket, "invocation");
|
|
900
|
-
if (fromAdmitted.kind === "damage")
|
|
901
|
-
return fromAdmitted;
|
|
902
|
-
if (fromInvocation.kind === "damage")
|
|
903
|
-
return fromInvocation;
|
|
904
|
-
if (fromAdmitted.kind === "ok" && fromInvocation.kind === "ok") {
|
|
905
|
-
if (!sameCourtTicketNumbers(fromAdmitted.tickets, fromInvocation.tickets)) {
|
|
906
|
-
return {
|
|
907
|
-
kind: "damage",
|
|
908
|
-
reason: "role run courtTicketNumbers differs between admitted-request and invocation",
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
return { kind: "ok", tickets: fromAdmitted.tickets };
|
|
912
|
-
}
|
|
913
|
-
if (fromAdmitted.kind === "ok")
|
|
914
|
-
return { kind: "ok", tickets: fromAdmitted.tickets };
|
|
915
|
-
if (fromInvocation.kind === "ok")
|
|
916
|
-
return { kind: "ok", tickets: fromInvocation.tickets };
|
|
917
|
-
return { kind: "ok" };
|
|
918
|
-
}
|
|
919
867
|
function restoredTicketFields(fields) {
|
|
920
868
|
return {
|
|
921
869
|
...(fields.correlationId === undefined ? {} : { correlationId: fields.correlationId }),
|
|
922
870
|
...(fields.ticketNumber === undefined ? {} : { ticketNumber: fields.ticketNumber }),
|
|
923
|
-
...(fields.courtTicketNumbers === undefined
|
|
924
|
-
? {}
|
|
925
|
-
: { courtTicketNumbers: fields.courtTicketNumbers }),
|
|
926
871
|
};
|
|
927
872
|
}
|
|
928
873
|
async function loadResumableRunRecord(home, runId, authority) {
|
|
@@ -958,10 +903,6 @@ async function loadResumableRunRecord(home, runId, authority) {
|
|
|
958
903
|
let derived;
|
|
959
904
|
let correlationId;
|
|
960
905
|
let ticketNumber;
|
|
961
|
-
let courtTicketNumbers;
|
|
962
|
-
let courtTicketNumbersDamage;
|
|
963
|
-
let admittedIdentityRecord;
|
|
964
|
-
let invocationIdentityRecord;
|
|
965
906
|
let prNumber;
|
|
966
907
|
let repository;
|
|
967
908
|
let repositoryDisplay;
|
|
@@ -1106,7 +1047,6 @@ async function loadResumableRunRecord(home, runId, authority) {
|
|
|
1106
1047
|
};
|
|
1107
1048
|
}
|
|
1108
1049
|
}
|
|
1109
|
-
admittedIdentityRecord = record;
|
|
1110
1050
|
const fromAdmitted = parsePersistedTicketIdentity(record);
|
|
1111
1051
|
correlationId = fromAdmitted.correlationId;
|
|
1112
1052
|
ticketNumber = fromAdmitted.ticketNumber;
|
|
@@ -1125,7 +1065,6 @@ async function loadResumableRunRecord(home, runId, authority) {
|
|
|
1125
1065
|
typeof invocationRaw === "object" &&
|
|
1126
1066
|
!Array.isArray(invocationRaw)) {
|
|
1127
1067
|
const rec = invocationRaw;
|
|
1128
|
-
invocationIdentityRecord = rec;
|
|
1129
1068
|
if (typeof rec.provider === "string" && typeof rec.model === "string") {
|
|
1130
1069
|
model = {
|
|
1131
1070
|
provider: rec.provider,
|
|
@@ -1154,20 +1093,6 @@ async function loadResumableRunRecord(home, runId, authority) {
|
|
|
1154
1093
|
throw new CliUsageError(`role run invocation identity is unreadable: ${runId}`, { cause: error });
|
|
1155
1094
|
}
|
|
1156
1095
|
}
|
|
1157
|
-
// #871: resolve set after main ticket so principal membership can be checked.
|
|
1158
|
-
// Present-but-damaged / cross-page mismatch keep diagnostic for countersign
|
|
1159
|
-
// controlled-failure; both-absent stays legacy; single lawful page restores.
|
|
1160
|
-
const mergedSet = mergeDurableCourtTicketNumbers({
|
|
1161
|
-
admittedRecord: admittedIdentityRecord,
|
|
1162
|
-
invocationRecord: invocationIdentityRecord,
|
|
1163
|
-
principalTicket: ticketNumber,
|
|
1164
|
-
});
|
|
1165
|
-
if (mergedSet.kind === "damage") {
|
|
1166
|
-
courtTicketNumbersDamage = mergedSet.reason;
|
|
1167
|
-
}
|
|
1168
|
-
else if (mergedSet.tickets !== undefined) {
|
|
1169
|
-
courtTicketNumbers = mergedSet.tickets;
|
|
1170
|
-
}
|
|
1171
1096
|
const sourceRunLeaf = basename(sourceRunPath ?? "").split("@");
|
|
1172
1097
|
const referencedRunId = sourceRun?.runId ?? sourceRunLeaf[0];
|
|
1173
1098
|
const referencedRole = sourceRun?.role ?? sourceRunLeaf[1];
|
|
@@ -1200,10 +1125,6 @@ async function loadResumableRunRecord(home, runId, authority) {
|
|
|
1200
1125
|
...(derived === undefined ? {} : { derived }),
|
|
1201
1126
|
...(correlationId === undefined ? {} : { correlationId }),
|
|
1202
1127
|
...(ticketNumber === undefined ? {} : { ticketNumber }),
|
|
1203
|
-
...(courtTicketNumbers === undefined ? {} : { courtTicketNumbers }),
|
|
1204
|
-
...(courtTicketNumbersDamage === undefined
|
|
1205
|
-
? {}
|
|
1206
|
-
: { courtTicketNumbersDamage }),
|
|
1207
1128
|
...(prNumber === undefined ? {} : { prNumber }),
|
|
1208
1129
|
...(repository === undefined ? {} : { repository }),
|
|
1209
1130
|
...(repositoryDisplay === undefined ? {} : { repositoryDisplay }),
|
|
@@ -1296,12 +1217,6 @@ function admitResumedRole(loaded) {
|
|
|
1296
1217
|
const admitted = {
|
|
1297
1218
|
role: "countersign",
|
|
1298
1219
|
...base,
|
|
1299
|
-
...(base.courtTicketNumbers === undefined
|
|
1300
|
-
? {}
|
|
1301
|
-
: { courtTicketNumbers: base.courtTicketNumbers }),
|
|
1302
|
-
...(fields.courtTicketNumbersDamage === undefined
|
|
1303
|
-
? {}
|
|
1304
|
-
: { courtTicketNumbersDamage: fields.courtTicketNumbersDamage }),
|
|
1305
1220
|
};
|
|
1306
1221
|
return admitted;
|
|
1307
1222
|
}
|
|
@@ -198,8 +198,8 @@ async function summonPublicRole(options) {
|
|
|
198
198
|
// Nested court stations keep station-child semantics; dual-lens
|
|
199
199
|
// ordinary Reviewer axes opt out (#946 / ADR 0082).
|
|
200
200
|
...options.stationChild === false ? {} : { stationChild: true },
|
|
201
|
-
// Forward composition-root adapters so nested
|
|
202
|
-
//
|
|
201
|
+
// Forward composition-root adapters so nested stations (e.g.
|
|
202
|
+
// secretariat → diarist) select the same faux/production table (#924).
|
|
203
203
|
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
204
204
|
...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit },
|
|
205
205
|
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
@@ -231,39 +231,35 @@ async function projectSessionRanges(input) {
|
|
|
231
231
|
return { entries };
|
|
232
232
|
}
|
|
233
233
|
/**
|
|
234
|
-
*
|
|
234
|
+
* Project every submitted boundary before appending any ticket's record.
|
|
235
235
|
* #1090: 纯追加——不回读已成录历史查重、不折叠、不刷新;同一区间再次提交也留下新的追加记录。
|
|
236
236
|
* 证不出的 body 原字节原样留卷。
|
|
237
237
|
*/
|
|
238
|
-
export async function
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
238
|
+
export async function reprojectTicketProvenanceBatch(input) {
|
|
239
|
+
const projections = [];
|
|
240
|
+
for (const ticket of input.tickets) {
|
|
241
|
+
const recordInput = ticketProvenanceRecordInput(ticket.ticketNumber, input.cwd, input.home, input.runDirectory);
|
|
242
|
+
const entries = [];
|
|
243
|
+
for (let s = 0; s < ticket.sessions.length; s += 1) {
|
|
244
|
+
const projected = await projectSessionRanges({
|
|
245
|
+
s,
|
|
246
|
+
session: ticket.sessions[s],
|
|
247
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
248
|
+
});
|
|
249
|
+
entries.push(...projected.entries);
|
|
250
|
+
}
|
|
251
|
+
projections.push({ ticket, recordInput, entries });
|
|
246
252
|
}
|
|
247
|
-
const entries
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
for (const { ticket, recordInput, entries } of projections) {
|
|
254
|
+
if (ticket.sessions.length === 0)
|
|
255
|
+
continue;
|
|
256
|
+
appendSitianRecord({
|
|
257
|
+
...recordInput,
|
|
258
|
+
payload: {
|
|
259
|
+
type: "ticket-provenance-append",
|
|
260
|
+
sessions: ticket.sessions,
|
|
261
|
+
lines: entries,
|
|
262
|
+
},
|
|
254
263
|
});
|
|
255
|
-
entries.push(...projected.entries);
|
|
256
264
|
}
|
|
257
|
-
const pointer = appendSitianRecord({
|
|
258
|
-
...recordInput,
|
|
259
|
-
payload: {
|
|
260
|
-
type: "ticket-provenance-append",
|
|
261
|
-
sessions: input.sessions,
|
|
262
|
-
lines: entries,
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
return {
|
|
266
|
-
recordFile: pointer.recordFile,
|
|
267
|
-
lines: entries.filter((entry) => "speaker" in entry),
|
|
268
|
-
};
|
|
269
265
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 任务
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
判断本庭要办哪些票、每票对话起止于何处并交出各自边界;正文与定位由机械按边界从会话卷投影。不整块誊录正文,不自行搜集材料替代机械投影;本文件不新立职掌、不替 soul 立法、不复制 schema 与 CLI 说明。
|
|
8
8
|
|
|
9
9
|
## 如何定边界
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- 本仓相关会话卷(路径里的 `_` 在目录名里常写作 `-`;主 clone 与 worktree 可能共用或分列,自行辨认)
|
|
14
14
|
- 本票票面与评论:用 `gh` 或等价方式读 GitHub issue
|
|
15
15
|
|
|
16
|
-
调用方只给传召文与 `--project`。边界定不下来就如实说明并走既有 reask/escalate,不编造。
|
|
16
|
+
调用方只给传召文与 `--project`。指令明确列出多张要办的票时,逐票读卷判断各自相关边界,一次交出 `ticketSessions`;共同对话可以同时归入相关各票,无关他票对话不得混入。单票沿用 `sessions`。边界定不下来就如实说明并走既有 reask/escalate,不编造。
|
|
17
17
|
|
|
18
18
|
## 何谓本票对话
|
|
19
19
|
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
|
|
30
30
|
## 本票身份
|
|
31
31
|
|
|
32
|
-
由你(本席 LLM
|
|
32
|
+
由你(本席 LLM)判断当前要办的票;多票庭逐票划界,同时照旧申报本庭主票身份,在交卷对象上以 typed 字段交出——代码不做认票裁定(ADR 0075:起居郎 LLM 自行认票;认得出→有录,真无票→无录;机械层不做引文/票号/相关性判定)。
|
|
33
33
|
|
|
34
34
|
- 认得出本庭对象:`status: completed` 且 `ticketNumber: N`,并交边界。
|
|
35
35
|
- 真无票对象(方案/派单/处置案等):`status: completed` 且 `ticketNumber: null`(或省略)。真无票是合法结果,不入录。
|
|
36
36
|
- 本庭明确要新建票、但 issue 尚未建号:不是认票失败。以 `status: completed`、`ticketNumber: null` 交出已辨明的本案对话边界;起居录先随无号 run 暂存,建号后由既有归卷流程补归该号。不要借用旁票号。
|
|
37
37
|
- 认不出本庭对象是哪一张:`status: escalate` 并写明 `reason`。识别不了就上抛,不得省略票号假装无录,也不得猜一个号。
|
|
38
38
|
- 不得从旁及的他票或任何猜测里挑一个号顶替认不出的身份。
|
|
39
|
-
- 传召文自然会提轮次、决定编号、commit sha
|
|
39
|
+
- 传召文自然会提轮次、决定编号、commit sha、邻票号;要办的是指令明确列出的票,不是文中出现的每一个数字。
|
package/src/diarist-contracts.ts
CHANGED
|
@@ -11,10 +11,7 @@ import {
|
|
|
11
11
|
projectTicketProvenanceSessions,
|
|
12
12
|
type TicketProvenanceSession,
|
|
13
13
|
} from "./ticket-provenance-contracts.ts";
|
|
14
|
-
import {
|
|
15
|
-
isSafePositiveTicketNumber,
|
|
16
|
-
requireSafePositiveTicketNumber,
|
|
17
|
-
} from "./run-ticket-number.ts";
|
|
14
|
+
import { isSafePositiveTicketNumber } from "./run-ticket-number.ts";
|
|
18
15
|
|
|
19
16
|
export const DIARIST_OUTPUT_TOOL_NAME = "ak_diarist_output";
|
|
20
17
|
|
|
@@ -27,21 +24,17 @@ export type DiaristOutput =
|
|
|
27
24
|
* LLM owns recognition; mechanical layer does not re-judge the number.
|
|
28
25
|
*/
|
|
29
26
|
readonly ticketNumber?: number | null;
|
|
30
|
-
/**
|
|
31
|
-
* #871 typed co-review set (ADR 0075 `diarist-resolves-ticket-llm-layer` narrow extension).
|
|
32
|
-
* Positive integers = tickets that should each receive a diary this court (includes main).
|
|
33
|
-
* Field absent → no new set this turn (resume keeps stored run fact; first court defaults to [main]).
|
|
34
|
-
* Explicit empty array → single-ticket face [main] whole-set replace.
|
|
35
|
-
* Non-empty with zero lawful members after type projection is not a new set (call site).
|
|
36
|
-
* Mechanical layer only type-projects/dedupes and guarantees principal membership when applying a set.
|
|
37
|
-
*/
|
|
38
|
-
readonly courtTicketNumbers?: readonly number[] | null;
|
|
39
27
|
/**
|
|
40
28
|
* Dialogue bounds: one entry per session volume, each with one or more ranges.
|
|
41
29
|
* Empty / absent = lawful empty selection (no dialogue this turn).
|
|
42
30
|
* Malformed endpoints → accept hook reasks (`reask-not-explode`).
|
|
43
31
|
*/
|
|
44
32
|
readonly sessions?: readonly TicketProvenanceSession[];
|
|
33
|
+
/** Per-ticket dialogue bounds for a single multi-ticket summons (#1107). */
|
|
34
|
+
readonly ticketSessions?: readonly {
|
|
35
|
+
readonly ticketNumber: number;
|
|
36
|
+
readonly sessions: readonly TicketProvenanceSession[];
|
|
37
|
+
}[];
|
|
45
38
|
}
|
|
46
39
|
| {
|
|
47
40
|
/** LLM cannot tell which ticket this summons is about — escalate, never wash into 无录. */
|
|
@@ -49,109 +42,6 @@ export type DiaristOutput =
|
|
|
49
42
|
readonly reason: string;
|
|
50
43
|
};
|
|
51
44
|
|
|
52
|
-
/**
|
|
53
|
-
* Sole shape projection for a typed co-review ticket set (#871).
|
|
54
|
-
* Positive safe integers, first-seen order, duplicates dropped. Not content judgment.
|
|
55
|
-
* Returns null when `raw` is not an array (callers classify absent vs durable damage).
|
|
56
|
-
* Returns a possibly empty list when `raw` is an array (unlawful members skipped).
|
|
57
|
-
* When `principalTicket` is set, that ticket is guaranteed as a member (prepended if missing).
|
|
58
|
-
*/
|
|
59
|
-
export function projectCourtTicketNumbers(
|
|
60
|
-
raw: unknown,
|
|
61
|
-
options?: { readonly principalTicket?: number },
|
|
62
|
-
): readonly number[] | null {
|
|
63
|
-
if (!Array.isArray(raw)) return null;
|
|
64
|
-
const out: number[] = [];
|
|
65
|
-
const seen = new Set<number>();
|
|
66
|
-
for (const item of raw) {
|
|
67
|
-
if (!isSafePositiveTicketNumber(item)) continue;
|
|
68
|
-
if (seen.has(item)) continue;
|
|
69
|
-
seen.add(item);
|
|
70
|
-
out.push(item);
|
|
71
|
-
}
|
|
72
|
-
const principal = options?.principalTicket;
|
|
73
|
-
if (principal === undefined) return out;
|
|
74
|
-
requireSafePositiveTicketNumber(principal, "projectCourtTicketNumbers principalTicket");
|
|
75
|
-
if (seen.has(principal)) return out;
|
|
76
|
-
return [principal, ...out];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Durable-field interpreter for #871 co-review sets.
|
|
81
|
-
* Absent key (old-format run) is not damage. Present but empty / non-array /
|
|
82
|
-
* partially unlawful / missing principal is damage — callers must fail closed,
|
|
83
|
-
* never wash into main-only.
|
|
84
|
-
*/
|
|
85
|
-
export function interpretDurableCourtTicketNumbers(
|
|
86
|
-
record: Record<string, unknown>,
|
|
87
|
-
options?: { readonly principalTicket?: number },
|
|
88
|
-
):
|
|
89
|
-
| { readonly kind: "absent" }
|
|
90
|
-
| { readonly kind: "ok"; readonly tickets: readonly number[] }
|
|
91
|
-
| { readonly kind: "damage"; readonly reason: string } {
|
|
92
|
-
if (!Object.hasOwn(record, "courtTicketNumbers")) {
|
|
93
|
-
return { kind: "absent" };
|
|
94
|
-
}
|
|
95
|
-
const raw = record.courtTicketNumbers;
|
|
96
|
-
if (!Array.isArray(raw)) {
|
|
97
|
-
return {
|
|
98
|
-
kind: "damage",
|
|
99
|
-
reason: "courtTicketNumbers is present but not an array",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
if (raw.length === 0) {
|
|
103
|
-
return {
|
|
104
|
-
kind: "damage",
|
|
105
|
-
reason: "courtTicketNumbers is present but empty",
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
for (let i = 0; i < raw.length; i += 1) {
|
|
109
|
-
const item = raw[i];
|
|
110
|
-
if (!isSafePositiveTicketNumber(item)) {
|
|
111
|
-
return {
|
|
112
|
-
kind: "damage",
|
|
113
|
-
reason: `courtTicketNumbers[${i}] is not a safe positive integer`,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// Members are all lawful numbers; sole projection owns dedupe/order.
|
|
118
|
-
const tickets = projectCourtTicketNumbers(raw);
|
|
119
|
-
if (tickets === null || tickets.length === 0) {
|
|
120
|
-
return {
|
|
121
|
-
kind: "damage",
|
|
122
|
-
reason: "courtTicketNumbers projected empty after lawful members",
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
const principal = options?.principalTicket;
|
|
126
|
-
if (principal !== undefined) {
|
|
127
|
-
if (!isSafePositiveTicketNumber(principal)) {
|
|
128
|
-
return {
|
|
129
|
-
kind: "damage",
|
|
130
|
-
reason: "courtTicketNumbers principal ticket is not a safe positive integer",
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
if (!tickets.includes(principal)) {
|
|
134
|
-
return {
|
|
135
|
-
kind: "damage",
|
|
136
|
-
reason: `courtTicketNumbers is missing principal ticket #${principal}`,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return { kind: "ok", tickets: Object.freeze([...tickets]) };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** True when two durable #871 sets are byte-identical sequences. */
|
|
144
|
-
export function sameCourtTicketNumbers(
|
|
145
|
-
left: readonly number[],
|
|
146
|
-
right: readonly number[],
|
|
147
|
-
): boolean {
|
|
148
|
-
if (left.length !== right.length) return false;
|
|
149
|
-
for (let i = 0; i < left.length; i += 1) {
|
|
150
|
-
if (left[i] !== right[i]) return false;
|
|
151
|
-
}
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
45
|
export function validateRecordedDiaristOutput(value: unknown): DiaristOutput {
|
|
156
46
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
157
47
|
throw new Error("Diarist output has no execution discriminator");
|
|
@@ -179,3 +69,21 @@ export function projectDiaristSessions(
|
|
|
179
69
|
if (raw === undefined) return [];
|
|
180
70
|
return projectTicketProvenanceSessions(raw);
|
|
181
71
|
}
|
|
72
|
+
|
|
73
|
+
/** Present multi-ticket bounds take precedence over the legacy single-ticket sessions. */
|
|
74
|
+
export function projectDiaristTicketSessions(
|
|
75
|
+
value: Record<string, unknown>,
|
|
76
|
+
): readonly { readonly ticketNumber: number; readonly sessions: readonly TicketProvenanceSession[] }[] | undefined {
|
|
77
|
+
const raw = value.ticketSessions;
|
|
78
|
+
if (!Array.isArray(raw)) return undefined;
|
|
79
|
+
const perTicket = new Map<number, TicketProvenanceSession[]>();
|
|
80
|
+
for (const entry of raw) {
|
|
81
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return undefined;
|
|
82
|
+
const { ticketNumber, sessions: rawSessions } = entry as Record<string, unknown>;
|
|
83
|
+
if (!isSafePositiveTicketNumber(ticketNumber)) return undefined;
|
|
84
|
+
const sessions = projectTicketProvenanceSessions(rawSessions);
|
|
85
|
+
if (sessions === undefined) return undefined;
|
|
86
|
+
perTicket.set(ticketNumber, [...(perTicket.get(ticketNumber) ?? []), ...sessions]);
|
|
87
|
+
}
|
|
88
|
+
return Array.from(perTicket, ([ticketNumber, sessions]) => ({ ticketNumber, sessions }));
|
|
89
|
+
}
|
package/src/diarist-role.ts
CHANGED
|
@@ -19,6 +19,38 @@ export { validateRecordedDiaristOutput };
|
|
|
19
19
|
* 起居郎交卷形状。
|
|
20
20
|
* #901:交边界(sessions);正文与不可解析原字节由机械投影。
|
|
21
21
|
*/
|
|
22
|
+
const diaristSessionsSchema = Type.Array(
|
|
23
|
+
Type.Object(
|
|
24
|
+
{
|
|
25
|
+
path: Type.Optional(Type.String({ description: "会话卷绝对或可读路径" })),
|
|
26
|
+
ranges: Type.Optional(
|
|
27
|
+
Type.Array(
|
|
28
|
+
Type.Object(
|
|
29
|
+
{
|
|
30
|
+
from: Type.Optional(
|
|
31
|
+
Type.Object(
|
|
32
|
+
{ id: Type.Optional(Type.String()), line: Type.Optional(Type.Unknown()) },
|
|
33
|
+
{ additionalProperties: true, description: "起点:原生 id 或本轮行号二选一" },
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
to: Type.Optional(
|
|
37
|
+
Type.Object(
|
|
38
|
+
{ id: Type.Optional(Type.String()), line: Type.Optional(Type.Unknown()) },
|
|
39
|
+
{ additionalProperties: true, description: "终点:原生 id 或本轮行号二选一" },
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
{ additionalProperties: true, description: "一段对话区间" },
|
|
44
|
+
),
|
|
45
|
+
{ description: "本卷本轮各区间;至少一段" },
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
{ additionalProperties: true, description: "一卷会话的边界" },
|
|
50
|
+
),
|
|
51
|
+
{ description: "对话边界;空列表=本轮无对话可划。端点无法指名时走 reask,不中止。" },
|
|
52
|
+
);
|
|
53
|
+
|
|
22
54
|
export const diaristOutputSchema = withTerminatingOutputDeclarations(
|
|
23
55
|
openToolObject(
|
|
24
56
|
Type.Object({
|
|
@@ -31,65 +63,22 @@ export const diaristOutputSchema = withTerminatingOutputDeclarations(
|
|
|
31
63
|
"本庭对象票号(正整数)或 null/省略=真无票;下游走 typed 键;认不出用 status=escalate,不洗成无录。机械层不重判。",
|
|
32
64
|
}),
|
|
33
65
|
),
|
|
34
|
-
courtTicketNumbers: Type.Optional(
|
|
35
|
-
Type.Unknown({
|
|
36
|
-
description:
|
|
37
|
-
"#871 本次合审应各自成录的票号集合(正整数数组,含主票);单票可省略。机械层只做类型/去重投影,不从散文猜票。",
|
|
38
|
-
}),
|
|
39
|
-
),
|
|
40
66
|
reason: Type.Optional(
|
|
41
67
|
Type.String({
|
|
42
68
|
description: "status 为 escalate 时:认不出本庭对象的原因",
|
|
43
69
|
}),
|
|
44
70
|
),
|
|
45
|
-
sessions: Type.Optional(
|
|
71
|
+
sessions: Type.Optional(diaristSessionsSchema),
|
|
72
|
+
ticketSessions: Type.Optional(
|
|
46
73
|
Type.Array(
|
|
47
74
|
Type.Object(
|
|
48
75
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
),
|
|
52
|
-
ranges: Type.Optional(
|
|
53
|
-
Type.Array(
|
|
54
|
-
Type.Object(
|
|
55
|
-
{
|
|
56
|
-
from: Type.Optional(
|
|
57
|
-
Type.Object(
|
|
58
|
-
{
|
|
59
|
-
id: Type.Optional(Type.String()),
|
|
60
|
-
line: Type.Optional(Type.Unknown()),
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
additionalProperties: true,
|
|
64
|
-
description: "起点:原生 id 或本轮行号二选一",
|
|
65
|
-
},
|
|
66
|
-
),
|
|
67
|
-
),
|
|
68
|
-
to: Type.Optional(
|
|
69
|
-
Type.Object(
|
|
70
|
-
{
|
|
71
|
-
id: Type.Optional(Type.String()),
|
|
72
|
-
line: Type.Optional(Type.Unknown()),
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
additionalProperties: true,
|
|
76
|
-
description: "终点:原生 id 或本轮行号二选一",
|
|
77
|
-
},
|
|
78
|
-
),
|
|
79
|
-
),
|
|
80
|
-
},
|
|
81
|
-
{ additionalProperties: true, description: "一段对话区间" },
|
|
82
|
-
),
|
|
83
|
-
{ description: "本卷本轮各区间;至少一段" },
|
|
84
|
-
),
|
|
85
|
-
),
|
|
76
|
+
ticketNumber: Type.Unknown({ description: "本条边界所归票号(正整数)" }),
|
|
77
|
+
sessions: diaristSessionsSchema,
|
|
86
78
|
},
|
|
87
|
-
{ additionalProperties: true
|
|
79
|
+
{ additionalProperties: true },
|
|
88
80
|
),
|
|
89
|
-
{
|
|
90
|
-
description:
|
|
91
|
-
"本票对话边界;空列表=本轮无对话可划。端点无法指名时走 reask,不中止。",
|
|
92
|
-
},
|
|
81
|
+
{ description: "多票庭逐票对话边界;一次交卷各票各自分录。与单票 sessions 二选一。" },
|
|
93
82
|
),
|
|
94
83
|
),
|
|
95
84
|
}),
|
|
@@ -109,7 +98,7 @@ export const DIARIST_TOOL_SPEC = {
|
|
|
109
98
|
name: DIARIST_OUTPUT_TOOL_NAME,
|
|
110
99
|
label: "起居郎输出",
|
|
111
100
|
description:
|
|
112
|
-
"
|
|
101
|
+
"起居郎交单票 sessions 或多票逐票 ticketSessions 边界;认不出本庭对象则 escalate。",
|
|
113
102
|
promptSnippet: "起居郎交边界",
|
|
114
103
|
parameters: diaristOutputSchema,
|
|
115
104
|
} as const;
|
package/src/diarist.ts
CHANGED
|
@@ -4,42 +4,22 @@
|
|
|
4
4
|
* session volumes (pure append, no cross-submit dedupe). No human view.
|
|
5
5
|
* No lifecycle here (ADR 0018): the seat prepares, the role envelope commits.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { reprojectTicketProvenanceBatch } from "./ticket-provenance.ts";
|
|
8
8
|
import type { TicketProvenanceSession } from "./ticket-provenance-contracts.ts";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* Populated by the envelope accept hook — never from model self-report.
|
|
13
|
-
*/
|
|
14
|
-
export type DiaristCommitFacts = {
|
|
15
|
-
readonly ticketNumber: number | null;
|
|
16
|
-
readonly volumeRecordFile: string;
|
|
17
|
-
/** Dialogue lines written by this reproject. */
|
|
18
|
-
readonly lineCount: number;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Append a diary projection from LLM-submitted bounds (#1090).
|
|
11
|
+
* Append per-ticket diary projections from LLM-submitted bounds (#1090 / #1107).
|
|
23
12
|
* Shape projection of sessions is the caller's job (accept hook);
|
|
24
|
-
* this seam only copies source facts into
|
|
13
|
+
* this seam only copies source facts into each ticket's volume.
|
|
25
14
|
*/
|
|
26
15
|
export async function commitDiaristProjection(input: {
|
|
27
|
-
readonly ticketNumber: number | null;
|
|
28
16
|
readonly cwd: string;
|
|
29
17
|
readonly home?: string;
|
|
30
18
|
readonly runDirectory?: string;
|
|
31
|
-
readonly
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sessions: input.sessions,
|
|
38
|
-
...(input.runDirectory === undefined ? {} : { runDirectory: input.runDirectory }),
|
|
39
|
-
});
|
|
40
|
-
return {
|
|
41
|
-
ticketNumber: input.ticketNumber,
|
|
42
|
-
volumeRecordFile: result.recordFile,
|
|
43
|
-
lineCount: result.lines.length,
|
|
44
|
-
};
|
|
19
|
+
readonly tickets: readonly {
|
|
20
|
+
readonly ticketNumber: number | null;
|
|
21
|
+
readonly sessions: readonly TicketProvenanceSession[];
|
|
22
|
+
}[];
|
|
23
|
+
}): Promise<void> {
|
|
24
|
+
await reprojectTicketProvenanceBatch(input);
|
|
45
25
|
}
|