@akagilnc/pi-workflow-roles 0.1.3671 → 0.1.3682
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/acp-host/production-host.js +272 -166
- package/dist/diarist-contracts.js +4 -4
- package/dist/diarist-role.js +10 -4
- package/dist/diarist.js +121 -17
- package/dist/package-contracts/terminating-tools.js +1 -1
- package/dist/public-cli/inspector-run.js +3 -13
- package/dist/public-cli/instruction-seat-run.js +6 -31
- package/dist/public-cli/invocation.js +32 -0
- package/dist/public-cli/judge-run.js +1 -7
- package/dist/public-cli/main.js +721 -754
- package/dist/public-cli/run-lifecycle.js +0 -28
- package/dist/public-cli/seat-ticket-binding.js +16 -113
- package/dist/role-runtime.js +89 -10
- package/package.json +1 -1
- package/resources/diarist-collect.md +10 -0
- package/src/diarist-contracts.ts +21 -9
- package/src/diarist-role.ts +15 -4
- package/src/diarist.ts +192 -22
- package/src/package-contracts/terminating-tools.ts +1 -1
- package/src/public-cli/coder-run.ts +1 -7
- package/src/public-cli/countersign-run.ts +238 -86
- package/src/public-cli/diarist-run.ts +83 -28
- package/src/public-cli/fixer-run.ts +1 -7
- package/src/public-cli/inspector-run.ts +3 -17
- package/src/public-cli/instruction-seat-run.ts +6 -36
- package/src/public-cli/invocation.ts +52 -0
- package/src/public-cli/judge-run.ts +1 -7
- package/src/public-cli/run-lifecycle.ts +0 -31
- package/src/public-cli/seat-ticket-binding.ts +15 -146
- package/src/role-runtime.ts +107 -9
|
@@ -827,34 +827,6 @@ export async function findLatestRunIdForSeatTicket(input) {
|
|
|
827
827
|
}
|
|
828
828
|
return best;
|
|
829
829
|
}
|
|
830
|
-
/**
|
|
831
|
-
* Ticket identities this book's retained runs already record (#709).
|
|
832
|
-
* Read-only reuse surface for seat ticket binding — no ticket number is minted here.
|
|
833
|
-
*/
|
|
834
|
-
export async function collectBookRunTicketNumbers(input) {
|
|
835
|
-
const ledgerHome = resolveActivationLedgerHome(input.home);
|
|
836
|
-
const runsDir = join(activationBookDirectory(ledgerHome, input.bookKey), "runs");
|
|
837
|
-
let entries;
|
|
838
|
-
try {
|
|
839
|
-
entries = await readdir(runsDir);
|
|
840
|
-
}
|
|
841
|
-
catch (error) {
|
|
842
|
-
if (errorCodeOf(error) === "ENOENT")
|
|
843
|
-
return new Set();
|
|
844
|
-
throw error;
|
|
845
|
-
}
|
|
846
|
-
const known = new Set();
|
|
847
|
-
for (const entry of entries) {
|
|
848
|
-
// runs/ also holds non-run files (institutional-resolution.json, Finder
|
|
849
|
-
// .DS_Store); only `<runId>@<role>` directories are runs (#769).
|
|
850
|
-
if (!entry.includes("@"))
|
|
851
|
-
continue;
|
|
852
|
-
const ticketNumber = await readRunTicketNumber(join(runsDir, entry));
|
|
853
|
-
if (ticketNumber !== undefined)
|
|
854
|
-
known.add(ticketNumber);
|
|
855
|
-
}
|
|
856
|
-
return known;
|
|
857
|
-
}
|
|
858
830
|
/** Restore optional correlation + typed ticket identity from a durable admitted page. */
|
|
859
831
|
function parsePersistedTicketIdentity(record) {
|
|
860
832
|
const correlationId = typeof record.correlationId === "string" && record.correlationId.trim() !== ""
|
|
@@ -1,118 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared ticket identity seam for public court seats (#635 / #637 / #709 / #747).
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* Shared ticket identity seam for public court seats (#635 / #637 / #709 / #747 / #771).
|
|
3
|
+
*
|
|
4
|
+
* ADR 0075 `diarist-resolves-ticket-llm-layer`: the seat's LLM names the court
|
|
5
|
+
* target as a typed assertion; the mechanical layer only verifies that assertion
|
|
6
|
+
* (complete decimal of N appears in the summons; ticket exists). Code never
|
|
7
|
+
* judges which ticket the summons is about — no prose harvest, no first-#N
|
|
8
|
+
* position pick, no matching book-known numbers against instruction text
|
|
9
|
+
* (锚定宪法; owner 2026-09-08: 代码不准做判断). Other seats reuse a typed
|
|
10
|
+
* identity already handed over (起居郎 assertion / source-run / already-bound
|
|
11
|
+
* resume) — they do not re-recognize from instruction. No CLI --ticket, no
|
|
12
|
+
* attachment frontmatter. #747: officer same-parent resume also lives here.
|
|
13
|
+
*
|
|
14
|
+
* Mechanical verify helpers live on diarist.ts / invocation.ts (sole owners).
|
|
15
|
+
* This module owns same-ticket / same-parent resume only.
|
|
10
16
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { resolveTicketProvenanceVolume } from "../ticket-provenance.js";
|
|
14
|
-
import { bindAdmittedTicketNumber, } from "./invocation.js";
|
|
15
|
-
import { collectBookRunTicketNumbers, findLatestRunIdForSeatTicket, } from "./run-lifecycle.js";
|
|
16
|
-
/**
|
|
17
|
-
* Complete decimal ticket tokens in an instruction.
|
|
18
|
-
* A digit run is one token: `82` inside `#582` is not 82, and a leading zero
|
|
19
|
-
* makes the run a different literal than the ticket number it would parse to.
|
|
20
|
-
*/
|
|
21
|
-
export function instructionTicketTokens(instruction) {
|
|
22
|
-
const tokens = new Set();
|
|
23
|
-
for (const run of instruction.match(/\d+/g) ?? []) {
|
|
24
|
-
if (run.startsWith("0"))
|
|
25
|
-
continue;
|
|
26
|
-
const parsed = Number(run);
|
|
27
|
-
if (!Number.isSafeInteger(parsed) || parsed < 1)
|
|
28
|
-
continue;
|
|
29
|
-
tokens.add(parsed);
|
|
30
|
-
}
|
|
31
|
-
return [...tokens];
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Ticket identity this summons reuses, or undefined when none is unambiguous.
|
|
35
|
-
* Known identities come only from records this book already holds: 起居录 volume
|
|
36
|
-
* partitions and ticket numbers on retained run pages. Zero or several known
|
|
37
|
-
* tokens in the instruction leave the run unbound — never guess, never fail.
|
|
38
|
-
*/
|
|
39
|
-
export async function resolveKnownTicketNumber(input) {
|
|
40
|
-
const tokens = instructionTicketTokens(input.instruction);
|
|
41
|
-
if (tokens.length === 0)
|
|
42
|
-
return undefined;
|
|
43
|
-
const known = [];
|
|
44
|
-
let runTickets;
|
|
45
|
-
for (const token of tokens) {
|
|
46
|
-
const volume = resolveTicketProvenanceVolume(token, input.projectRoot, input.home);
|
|
47
|
-
if (existsSync(volume.volumeDir)) {
|
|
48
|
-
known.push(token);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (runTickets === undefined) {
|
|
52
|
-
runTickets = await readBookRunTickets(input);
|
|
53
|
-
}
|
|
54
|
-
if (runTickets.has(token))
|
|
55
|
-
known.push(token);
|
|
56
|
-
}
|
|
57
|
-
return known.length === 1 ? known[0] : undefined;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Diarist first-summons identity (ADR 0081 `initial-court-ticket-supplied`).
|
|
61
|
-
* Prefer a book-known reuse; when the book has no record yet, the sole complete
|
|
62
|
-
* decimal token in the caller's dispatch is the supplied ticket — the working
|
|
63
|
-
* 起居郎 round establishes the volume. Several tokens or none stay unbound.
|
|
64
|
-
* Not a second ticket-number source of truth and not an extra recognizer call.
|
|
65
|
-
*/
|
|
66
|
-
export async function resolveDiaristSummonsTicketNumber(input) {
|
|
67
|
-
const known = await resolveKnownTicketNumber(input);
|
|
68
|
-
if (known !== undefined)
|
|
69
|
-
return known;
|
|
70
|
-
const tokens = instructionTicketTokens(input.instruction);
|
|
71
|
-
return tokens.length === 1 ? tokens[0] : undefined;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Ticket numbers on this book's retained runs.
|
|
75
|
-
* A directory with no book (not a git repository) simply holds no run history —
|
|
76
|
-
* admission owns that rejection face, this lookup does not pre-empt it.
|
|
77
|
-
*/
|
|
78
|
-
async function readBookRunTickets(input) {
|
|
79
|
-
let bookKey;
|
|
80
|
-
try {
|
|
81
|
-
bookKey = input.bookKey ?? resolveBookKeyFromGit(input.projectRoot);
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
if (error instanceof ActivationGitRepositoryRequiredError)
|
|
85
|
-
return new Set();
|
|
86
|
-
throw error;
|
|
87
|
-
}
|
|
88
|
-
return await collectBookRunTicketNumbers({ home: input.home, bookKey });
|
|
89
|
-
}
|
|
90
|
-
/** Bind a reused ticket number onto an admission that is still unbound. */
|
|
91
|
-
export async function bindReusedTicketNumber(admitted, ticketNumber) {
|
|
92
|
-
if (ticketNumber === undefined)
|
|
93
|
-
return;
|
|
94
|
-
if (admitted.ticketNumber !== undefined)
|
|
95
|
-
return;
|
|
96
|
-
await bindAdmittedTicketNumber(admitted, ticketNumber);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Sole seat disposition for an unbound admission (#635 / #709).
|
|
100
|
-
* Already-bound admissions short-circuit — resume keeps the identity it has.
|
|
101
|
-
* Used by seats that do not pre-resolve for same-ticket resume; the seats that
|
|
102
|
-
* do (countersign / inspector / diarist) reuse the number they already resolved.
|
|
103
|
-
*/
|
|
104
|
-
export async function resolveSeatTicketBinding(admitted, env) {
|
|
105
|
-
if (admitted.ticketNumber !== undefined)
|
|
106
|
-
return admitted.ticketNumber;
|
|
107
|
-
const ticketNumber = await resolveKnownTicketNumber({
|
|
108
|
-
instruction: admitted.instruction,
|
|
109
|
-
projectRoot: admitted.projectRoot,
|
|
110
|
-
home: env.home,
|
|
111
|
-
bookKey: admitted.bookKey,
|
|
112
|
-
});
|
|
113
|
-
await bindReusedTicketNumber(admitted, ticketNumber);
|
|
114
|
-
return ticketNumber;
|
|
115
|
-
}
|
|
17
|
+
import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
18
|
+
import { findLatestRunIdForSeatTicket, } from "./run-lifecycle.js";
|
|
116
19
|
/**
|
|
117
20
|
* Sole same-seat → resume decision (#637 / #724 / #747).
|
|
118
21
|
* Officer seats (notary/inspector/auditor) look up by parent run path; countersign
|
package/dist/role-runtime.js
CHANGED
|
@@ -25,7 +25,8 @@ import { INSPECTOR_TOOL_SPEC, } from "./inspector-role.js";
|
|
|
25
25
|
import { INSPECTOR_ACCEPTED_TEXT } from "./inspector-contracts.js";
|
|
26
26
|
import { DIARIST_TOOL_SPEC, } from "./diarist-role.js";
|
|
27
27
|
import { DIARIST_ACCEPTED_TEXT, DIARIST_SOURCES_FLAG, projectDiaristSelections, } from "./diarist-contracts.js";
|
|
28
|
-
import { commitDiaristSelections, loadDiaristSourceCatalog, } from "./diarist.js";
|
|
28
|
+
import { commitDiaristSelections, DiaristTicketVerificationError, loadDiaristSourceCatalog, verifyAssertedTicketNumber, } from "./diarist.js";
|
|
29
|
+
import { bindTicketNumberOnRunDirectory } from "./public-cli/invocation.js";
|
|
29
30
|
import { GATEKEEPER_TOOL_SPEC, } from "./gatekeeper-role.js";
|
|
30
31
|
import { NAVIGATOR_TOOL_SPEC, } from "./navigator-role.js";
|
|
31
32
|
import { AUDITOR_TOOL_SPEC, } from "./auditor-role.js";
|
|
@@ -495,12 +496,35 @@ export function createAuditorRoleRuntime(roleHost, dependencies) {
|
|
|
495
496
|
},
|
|
496
497
|
};
|
|
497
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* LLM typed court-target assertion from diarist output (ADR 0075).
|
|
501
|
+
* null/absent = true-unbound; positive safe integer = ticket N.
|
|
502
|
+
* Any other shape fails honestly — never washes into unbound.
|
|
503
|
+
*/
|
|
504
|
+
function readDiaristTicketAssertion(submitted) {
|
|
505
|
+
if (submitted === undefined || !("ticketNumber" in submitted)) {
|
|
506
|
+
return { kind: "true-unbound" };
|
|
507
|
+
}
|
|
508
|
+
const raw = submitted.ticketNumber;
|
|
509
|
+
if (raw === null)
|
|
510
|
+
return { kind: "true-unbound" };
|
|
511
|
+
if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) {
|
|
512
|
+
return { kind: "ticket", ticketNumber: raw };
|
|
513
|
+
}
|
|
514
|
+
if (typeof raw === "string" && /^[1-9]\d*$/.test(raw)) {
|
|
515
|
+
const n = Number(raw);
|
|
516
|
+
if (Number.isSafeInteger(n) && n >= 1) {
|
|
517
|
+
return { kind: "ticket", ticketNumber: n };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
throw new DiaristTicketVerificationError("assertion-uninterpretable", "diarist ticketNumber must be a safe integer >= 1, null, or absent");
|
|
521
|
+
}
|
|
498
522
|
/**
|
|
499
523
|
* #708: 起居郎 public seat on the shared filed-officer envelope.
|
|
500
524
|
* Semantic collection happened in this role's own turn; the accept hook runs the
|
|
501
|
-
* mechanical band (
|
|
502
|
-
* watermark) and records the resulting sitian facts
|
|
503
|
-
* Machine facts never come from model self-report (锚定宪法).
|
|
525
|
+
* mechanical band (ticket verify when first-summons + verbatim reverse-verify →
|
|
526
|
+
* idempotent sitian append → watermark) and records the resulting sitian facts
|
|
527
|
+
* next to the receipt. Machine facts never come from model self-report (锚定宪法).
|
|
504
528
|
*/
|
|
505
529
|
export function createDiaristRoleRuntime(roleHost, dependencies, getSourcesFlag) {
|
|
506
530
|
// This turn's catalog, snapshotted at activate: the candidateIndexes the role
|
|
@@ -516,22 +540,77 @@ export function createDiaristRoleRuntime(roleHost, dependencies, getSourcesFlag)
|
|
|
516
540
|
const submitted = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
|
|
517
541
|
? parameters
|
|
518
542
|
: undefined;
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
|
|
543
|
+
// LLM cannot identify the court target — escalate without machine facts.
|
|
544
|
+
// Strip sitian (锚定宪法) and ticketNumber (unverified — must not leak
|
|
545
|
+
// into caller-visible admitted typed key via decisiveFacts mirror).
|
|
546
|
+
if (submitted?.status === "escalate") {
|
|
547
|
+
if (!("sitian" in submitted) && !("ticketNumber" in submitted)) {
|
|
548
|
+
return submitted;
|
|
549
|
+
}
|
|
550
|
+
const stripped = { ...submitted };
|
|
551
|
+
delete stripped.sitian;
|
|
552
|
+
delete stripped.ticketNumber;
|
|
553
|
+
return stripped;
|
|
554
|
+
}
|
|
555
|
+
const assertion = readDiaristTicketAssertion(submitted);
|
|
556
|
+
// No frozen catalog: only true-unbound is lawful without volume work.
|
|
557
|
+
// A self-reported `sitian` is not machine fact (锚定宪法).
|
|
523
558
|
if (catalog === undefined) {
|
|
559
|
+
if (assertion.kind === "ticket") {
|
|
560
|
+
throw new DiaristTicketVerificationError("assertion-uninterpretable", `diarist asserted ticket #${assertion.ticketNumber} but no source catalog was frozen for this turn`);
|
|
561
|
+
}
|
|
524
562
|
if (submitted === undefined || !("sitian" in submitted))
|
|
525
563
|
return undefined;
|
|
526
564
|
const stripped = { ...submitted };
|
|
527
565
|
delete stripped.sitian;
|
|
528
566
|
return stripped;
|
|
529
567
|
}
|
|
568
|
+
// Already bound before the turn (typed handoff / resume): skip re-recognition
|
|
569
|
+
// (ADR 0075 已绑定 ticket 优先) and commit under that identity.
|
|
570
|
+
if (catalog.ticketNumber !== undefined) {
|
|
571
|
+
const facts = await commitDiaristSelections({
|
|
572
|
+
catalog,
|
|
573
|
+
selections: projectDiaristSelections(parameters),
|
|
574
|
+
});
|
|
575
|
+
return {
|
|
576
|
+
...(submitted ?? { receipt: parameters }),
|
|
577
|
+
ticketNumber: catalog.ticketNumber,
|
|
578
|
+
sitian: facts,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
// First summons: LLM typed assertion → mechanical verify → bind → commit.
|
|
582
|
+
// true-unbound → 无录 (no volume). Verification failure throws (失败诚实).
|
|
583
|
+
// Cannot-identify must arrive as status=escalate above — never as silent unbound.
|
|
584
|
+
if (assertion.kind === "true-unbound") {
|
|
585
|
+
if (submitted === undefined || !("sitian" in submitted)) {
|
|
586
|
+
return { ...(submitted ?? { receipt: parameters }), ticketNumber: null };
|
|
587
|
+
}
|
|
588
|
+
const stripped = {
|
|
589
|
+
...submitted,
|
|
590
|
+
ticketNumber: null,
|
|
591
|
+
};
|
|
592
|
+
delete stripped.sitian;
|
|
593
|
+
return stripped;
|
|
594
|
+
}
|
|
595
|
+
await verifyAssertedTicketNumber({
|
|
596
|
+
ticketNumber: assertion.ticketNumber,
|
|
597
|
+
instruction: catalog.instruction,
|
|
598
|
+
projectRoot: catalog.projectRoot,
|
|
599
|
+
});
|
|
600
|
+
await bindTicketNumberOnRunDirectory(catalog.runDirectory, assertion.ticketNumber);
|
|
601
|
+
const boundCatalog = {
|
|
602
|
+
...catalog,
|
|
603
|
+
ticketNumber: assertion.ticketNumber,
|
|
604
|
+
};
|
|
530
605
|
const facts = await commitDiaristSelections({
|
|
531
|
-
catalog,
|
|
606
|
+
catalog: boundCatalog,
|
|
532
607
|
selections: projectDiaristSelections(parameters),
|
|
533
608
|
});
|
|
534
|
-
return {
|
|
609
|
+
return {
|
|
610
|
+
...(submitted ?? { receipt: parameters }),
|
|
611
|
+
ticketNumber: assertion.ticketNumber,
|
|
612
|
+
sitian: facts,
|
|
613
|
+
};
|
|
535
614
|
},
|
|
536
615
|
}, dependencies);
|
|
537
616
|
return {
|
package/package.json
CHANGED
|
@@ -23,3 +23,13 @@
|
|
|
23
23
|
- 入选即整块保留:指向文件中的完整 transcript,不指针化、不摘要替代正文。
|
|
24
24
|
- 引语必须是该块 transcript 内的**连续原文子串**。拼接、改写、跨段拼贴会在机械反验中失败并拒入录。
|
|
25
25
|
- 宁多勿漏:拿不准是否决策相关时入选,由下游反验与人读面承接。
|
|
26
|
+
|
|
27
|
+
## 本票身份
|
|
28
|
+
|
|
29
|
+
由你(本席 LLM)判断当前要办的是哪张票,在交卷对象上以 typed 字段交出——代码不做认票裁定(ADR 0075 `diarist-resolves-ticket-llm-layer`;owner:代码不准做判断)。机械层只验你断言的号逐字出现于指令且票真实存在;后续衙门沿用 typed 键,不再另找。
|
|
30
|
+
|
|
31
|
+
- 派单已给明确票号(含 issue 链接或等价指称)时:`status: completed` 且 `ticketNumber: N`。
|
|
32
|
+
- 派单是真无票对象(方案/派单/处置案等):`status: completed` 且 `ticketNumber: null`(或省略)。真无票是合法结果,不入录。
|
|
33
|
+
- 派单看起来有票、但你认不出本庭对象是哪一张:`status: escalate` 并写明 `reason`。识别不了就上抛,不得省略票号假装无录,也不得猜一个号。
|
|
34
|
+
- 不得从候选材料、旁及的他票或任何猜测里挑一个号顶替派单未给出的身份。
|
|
35
|
+
- 传召文自然会提轮次、决定编号、commit sha、邻票号;你认的是本庭对象,不是文中出现的每一个数字。
|
package/src/diarist-contracts.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public 起居郎 (diarist) terminating receipt contracts — ADR 0075 `diarist-is-role`.
|
|
3
|
-
* Lawful explicit
|
|
3
|
+
* Lawful explicit releases: completed (入录选择) | escalate (认不出本庭对象上抛).
|
|
4
4
|
* Machine facts about the volume come from the mechanical sitian seam, never
|
|
5
5
|
* from model self-report (锚定宪法); this module owns the receipt shape only.
|
|
6
6
|
*/
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
export const DIARIST_OUTPUT_TOOL_NAME = "ak_diarist_output";
|
|
9
9
|
export const DIARIST_ACCEPTED_TEXT = "起居郎回执已接受";
|
|
10
10
|
|
|
11
|
-
/** Internal transport: frozen candidate-catalog path for this turn
|
|
11
|
+
/** Internal transport: frozen candidate-catalog path for this turn. */
|
|
12
12
|
export const DIARIST_SOURCES_FLAG = {
|
|
13
13
|
name: "ak-diarist-sources",
|
|
14
14
|
definition: {
|
|
15
|
-
description:
|
|
15
|
+
description:
|
|
16
|
+
"Frozen source catalog the diarist selects from (may be unbound until LLM asserts ticketNumber)",
|
|
16
17
|
type: "string" as const,
|
|
17
18
|
},
|
|
18
19
|
} as const;
|
|
@@ -27,10 +28,22 @@ export type DiaristSelection = {
|
|
|
27
28
|
readonly note?: string;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
export type DiaristOutput =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
export type DiaristOutput =
|
|
32
|
+
| {
|
|
33
|
+
readonly status: "completed";
|
|
34
|
+
readonly selections: readonly DiaristSelection[];
|
|
35
|
+
/**
|
|
36
|
+
* Typed court-target assertion (ADR 0075 `diarist-resolves-ticket-llm-layer`).
|
|
37
|
+
* Positive integer = 本庭对象=票N; null/absent = true-unbound (真无票→无录).
|
|
38
|
+
* Mechanical layer verifies N; verification failure is failure, never 无录.
|
|
39
|
+
*/
|
|
40
|
+
readonly ticketNumber?: number | null;
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
/** LLM cannot tell which ticket this summons is about — escalate, never wash into 无录. */
|
|
44
|
+
readonly status: "escalate";
|
|
45
|
+
readonly reason: string;
|
|
46
|
+
};
|
|
34
47
|
|
|
35
48
|
export function validateRecordedDiaristOutput(value: unknown): DiaristOutput {
|
|
36
49
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -42,7 +55,7 @@ export function validateRecordedDiaristOutput(value: unknown): DiaristOutput {
|
|
|
42
55
|
} catch {
|
|
43
56
|
throw new Error("Diarist output has no execution discriminator");
|
|
44
57
|
}
|
|
45
|
-
if (status === "completed") {
|
|
58
|
+
if (status === "completed" || status === "escalate") {
|
|
46
59
|
return value as DiaristOutput;
|
|
47
60
|
}
|
|
48
61
|
throw new Error("Diarist output has no execution discriminator");
|
|
@@ -73,4 +86,3 @@ export function projectDiaristSelections(value: unknown): DiaristSelection[] {
|
|
|
73
86
|
}
|
|
74
87
|
return out;
|
|
75
88
|
}
|
|
76
|
-
|
package/src/diarist-role.ts
CHANGED
|
@@ -21,8 +21,19 @@ export const diaristOutputSchema = withInfrastructureFailureDeclaration(
|
|
|
21
21
|
openToolObject(
|
|
22
22
|
Type.Object({
|
|
23
23
|
status: Type.Unknown({
|
|
24
|
-
description: "completed — 形状指引,非 schema 闸",
|
|
24
|
+
description: "completed | escalate — 形状指引,非 schema 闸",
|
|
25
25
|
}),
|
|
26
|
+
ticketNumber: Type.Optional(
|
|
27
|
+
Type.Unknown({
|
|
28
|
+
description:
|
|
29
|
+
"本庭对象票号(正整数)或 null/省略=真无票;机械验真后下游走 typed 键;认不出用 status=escalate,不洗成无录",
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
reason: Type.Optional(
|
|
33
|
+
Type.String({
|
|
34
|
+
description: "status 为 escalate 时:认不出本庭对象的原因",
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
26
37
|
selections: Type.Array(
|
|
27
38
|
Type.Object(
|
|
28
39
|
{
|
|
@@ -36,7 +47,7 @@ export const diaristOutputSchema = withInfrastructureFailureDeclaration(
|
|
|
36
47
|
},
|
|
37
48
|
{ additionalProperties: true, description: "一条入录选择" },
|
|
38
49
|
),
|
|
39
|
-
{ description: "
|
|
50
|
+
{ description: "入录选择;空列表合法完局;escalate 时可不交" },
|
|
40
51
|
),
|
|
41
52
|
}),
|
|
42
53
|
),
|
|
@@ -54,7 +65,7 @@ export type DiaristRuntimeDependencies = {
|
|
|
54
65
|
export const DIARIST_TOOL_SPEC = {
|
|
55
66
|
name: DIARIST_OUTPUT_TOOL_NAME,
|
|
56
67
|
label: "起居郎输出",
|
|
57
|
-
description: "
|
|
58
|
-
promptSnippet: "
|
|
68
|
+
description: "起居郎入录选择与本票身份断言;认不出本庭对象则 escalate。",
|
|
69
|
+
promptSnippet: "起居郎入录选择与本票身份",
|
|
59
70
|
parameters: diaristOutputSchema,
|
|
60
71
|
} as const;
|