@akagilnc/pi-workflow-roles 0.1.3565 → 0.1.3621
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 +4 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +18477 -8099
- package/dist/analyst-gate-cycles-read.js +387 -0
- package/dist/archivist-record-entry.js +22 -1
- package/dist/audit-escalation.js +6 -0
- package/dist/auditor-soul.js +74 -0
- package/dist/collector-config.js +86 -0
- package/dist/collector-evidence.js +316 -0
- package/dist/collector-github.js +527 -0
- package/dist/collector-ledger.js +853 -0
- package/dist/collector-tool-schemas.js +33 -0
- package/dist/compliance-transport.js +104 -52
- package/dist/diarist-contracts.js +72 -0
- package/dist/diarist-mechanical.js +411 -0
- package/dist/diarist-ticket-resolution.js +177 -0
- package/dist/diarist.js +311 -0
- package/dist/doctor-auditor.js +25 -0
- package/dist/doctor-contracts.js +2 -0
- package/dist/doctor-evidence.js +142 -0
- package/dist/gatekeeper-role.js +104 -81
- package/dist/host-transition-prior-native.js +78 -0
- package/dist/institutional-resolution.js +1 -95
- package/dist/judge-auditor.js +26 -0
- package/dist/ledger-session-read.js +227 -0
- package/dist/merger-git-state.js +78 -0
- package/dist/navigator-attendance.js +15 -7
- package/dist/navigator-public-session.js +170 -0
- package/dist/navigator-session-contracts.js +36 -40
- package/dist/notary-source-run.js +122 -0
- package/dist/package-contracts/auditor-output.js +66 -0
- package/dist/package-contracts/evidence-child-output.js +34 -0
- package/dist/package-contracts/judge-output.js +2 -0
- package/dist/package-contracts/terminating-tools.js +44 -3
- package/dist/package-resources/method-skill.js +254 -0
- package/dist/packaged-role-registry.js +56 -0
- package/dist/pi/durable-principal.js +61 -0
- package/dist/pi/in-process-session.js +25 -6
- package/dist/pi/known-failure.js +52 -0
- package/dist/pi/role-turn-host.js +430 -0
- package/dist/public-cli/auto-resume.js +414 -0
- package/dist/public-cli/cli-errors.js +8 -0
- package/dist/public-cli/cli-io.js +1 -0
- package/dist/public-cli/command-renderer.js +5 -0
- package/dist/public-cli/doctor-run.js +84 -0
- package/dist/public-cli/inspector-run.js +136 -0
- package/dist/public-cli/instruction-seat-run.js +256 -0
- package/dist/public-cli/invocation.js +2225 -0
- package/dist/public-cli/judge-run.js +118 -0
- package/dist/public-cli/load-production-acp-host.js +40 -0
- package/dist/public-cli/main.js +1511 -1124
- package/dist/public-cli/notary-run.js +161 -0
- package/dist/public-cli/option-definitions.js +1192 -0
- package/dist/public-cli/post-admission.js +667 -0
- package/dist/public-cli/public-run-credentials.js +49 -0
- package/dist/public-cli/registry.js +8 -2
- package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
- package/dist/public-cli/run-lifecycle.js +1344 -0
- package/dist/public-cli/seat-ticket-binding.js +113 -0
- package/dist/public-cli/settlement.js +3441 -0
- package/dist/public-cli/terminal.js +135 -0
- package/dist/public-cli/turn-request.js +25 -0
- package/dist/public-role-summons.js +301 -0
- package/dist/receipt-delivery-policy.js +10 -0
- package/dist/reviewer-child-executor.js +80 -8
- package/dist/reviewer-execution-ledger.js +2 -1
- package/dist/run-terminal-artifacts.js +195 -0
- package/dist/run-ticket-number.js +40 -0
- package/dist/session-assistant-usage.js +107 -0
- package/dist/shape-unreadable-failure.js +34 -0
- package/dist/submission-errors.js +3 -0
- package/dist/submission-ledger.js +388 -0
- package/dist/ticket-provenance-contracts.js +115 -0
- package/dist/ticket-provenance.js +340 -0
- package/extensions/role-runtime.ts +8 -0
- package/package.json +1 -1
- package/resources/diarist-collect.md +6 -10
- package/scripts/build-package.mjs +2 -1
- package/souls/diarist.md +9 -0
- package/souls/doctor-auditor.md +1 -0
- package/souls/evidence-child.md +1 -0
- package/src/acp-host/production-host.ts +4 -0
- package/src/acp-host/role-envelope.ts +4 -3
- package/src/acp-host/role-turn-host.ts +9 -1
- package/src/analyst-gate-cycles-read.ts +37 -2
- package/src/archivist-record-entry.ts +45 -1
- package/src/audit-escalation.ts +17 -0
- package/src/auditor-role.ts +22 -0
- package/src/auditor-soul.ts +42 -0
- package/src/compliance-transport.ts +177 -66
- package/src/diarist-contracts.ts +88 -0
- package/src/diarist-role.ts +60 -0
- package/src/diarist.ts +253 -179
- package/src/doctor-auditor.ts +10 -14
- package/src/doctor-contracts.ts +1 -0
- package/src/doctor-role.ts +2 -2
- package/src/evidence-child-role.ts +22 -0
- package/src/gatekeeper-pass-envelope.ts +109 -0
- package/src/gatekeeper-role.ts +145 -104
- package/src/host-contracts.ts +15 -2
- package/src/institutional-resolution.ts +8 -163
- package/src/judge-auditor.ts +10 -15
- package/src/judge-role.ts +8 -0
- package/src/navigator-attendance.ts +25 -9
- package/src/navigator-public-session.ts +217 -0
- package/src/navigator-session-contracts.ts +61 -54
- package/src/notary-source-run.ts +3 -1
- package/src/package-contracts/auditor-output.ts +82 -0
- package/src/package-contracts/evidence-child-output.ts +51 -0
- package/src/package-contracts/judge-output.ts +1 -0
- package/src/package-contracts/reviewer-output.ts +2 -2
- package/src/package-contracts/terminating-tools.ts +53 -3
- package/src/packaged-role-registry.ts +63 -0
- package/src/pi/adapter.ts +2 -1
- package/src/pi/in-process-session.ts +38 -12
- package/src/pi/role-turn-host.ts +42 -0
- package/src/public-cli/cli.ts +43 -6
- package/src/public-cli/countersign-run.ts +9 -129
- package/src/public-cli/diarist-run.ts +312 -0
- package/src/public-cli/instruction-seat-run.ts +289 -50
- package/src/public-cli/invocation.ts +87 -25
- package/src/public-cli/option-definitions.ts +79 -0
- package/src/public-cli/post-admission.ts +9 -3
- package/src/public-cli/registry.ts +7 -1
- package/src/public-cli/run-lifecycle.ts +53 -6
- package/src/public-cli/settlement.ts +170 -7
- package/src/public-cli/terminal.ts +4 -1
- package/src/public-role-summons.ts +431 -0
- package/src/receipt-delivery-policy.ts +10 -0
- package/src/reviewer-agent.ts +1 -1
- package/src/reviewer-child-executor.ts +114 -12
- package/src/reviewer-execution-ledger.ts +3 -2
- package/src/role-runtime.ts +275 -23
- package/src/session-assistant-usage.ts +119 -0
- package/src/session-opening-materials.ts +1 -1
- package/src/shape-unreadable-failure.ts +49 -0
- package/src/submission-errors.ts +3 -0
- package/src/ticket-provenance-contracts.ts +1 -0
- package/src/ticket-provenance.ts +0 -23
- package/dist/evidence-child-executor.js +0 -829
- package/src/diarist-llm-collector.ts +0 -324
- package/src/evidence-child-executor.ts +0 -1140
package/src/diarist.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 起居郎
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 起居郎 mechanical halves — ADR 0075 `diarist-is-role` / `diarist-collector-is-own-turn`.
|
|
3
|
+
* Semantic collection is the diarist role's own LLM turn; this module keeps the
|
|
4
|
+
* mechanical safeguard band only: source enumeration into a frozen catalog, and
|
|
5
|
+
* the verbatim reverse-verify → idempotent sitian append → watermark commit.
|
|
6
|
+
* No lifecycle here (ADR 0018): the seat prepares, the role envelope commits.
|
|
5
7
|
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
|
|
6
10
|
import { extractReferencedAdrPaths } from "./adr-path-refs.ts";
|
|
7
11
|
import {
|
|
8
12
|
createGhApiRunner,
|
|
@@ -22,13 +26,9 @@ import {
|
|
|
22
26
|
type DiaristIssueFace,
|
|
23
27
|
type DiaristSourceBlock,
|
|
24
28
|
} from "./diarist-mechanical.ts";
|
|
25
|
-
import {
|
|
26
|
-
createHermesDiaristCollector,
|
|
27
|
-
type DiaristLlmCollectResult,
|
|
28
|
-
} from "./diarist-llm-collector.ts";
|
|
29
|
+
import type { DiaristSelection } from "./diarist-contracts.ts";
|
|
29
30
|
import { parseGitHubOriginRemote } from "./reviewer-pinned-git.ts";
|
|
30
31
|
import {
|
|
31
|
-
appendCollectorFailureDiagnostic,
|
|
32
32
|
appendIssueSourceFailureDiagnostic,
|
|
33
33
|
appendQuoteVerifyFailureDiagnostic,
|
|
34
34
|
appendTicketProvenanceEntry,
|
|
@@ -40,8 +40,7 @@ import {
|
|
|
40
40
|
ticketProvenanceEntryIdentity,
|
|
41
41
|
writeTicketProvenanceHumanView,
|
|
42
42
|
} from "./ticket-provenance.ts";
|
|
43
|
-
import
|
|
44
|
-
import { readSitianRecords, type RecordPointer } from "./sitian-facade.ts";
|
|
43
|
+
import { readSitianRecords } from "./sitian-facade.ts";
|
|
45
44
|
import { execFileSync } from "node:child_process";
|
|
46
45
|
|
|
47
46
|
export type { DiaristIssueFace } from "./diarist-mechanical.ts";
|
|
@@ -72,55 +71,15 @@ export class DiaristIssueSourceError extends Error {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
75
|
-
* Issue-face fetch for the diarist
|
|
76
|
-
*
|
|
77
|
-
* Test injectors may return undefined to simulate unavailability — station converts
|
|
78
|
-
* that into a typed DiaristIssueSourceError + durable diagnostic.
|
|
74
|
+
* Issue-face fetch for the diarist seat. Unavailability is a typed
|
|
75
|
+
* DiaristIssueSourceError — there is no soft-undefined face.
|
|
79
76
|
*/
|
|
80
77
|
export type DiaristIssueFaceFetcher = (input: {
|
|
81
78
|
readonly owner: string;
|
|
82
79
|
readonly repo: string;
|
|
83
80
|
readonly ticketNumber: number;
|
|
84
81
|
readonly signal?: AbortSignal;
|
|
85
|
-
}) => Promise<DiaristIssueFace
|
|
86
|
-
|
|
87
|
-
export type DiaristRunInput = {
|
|
88
|
-
readonly ticketNumber: number;
|
|
89
|
-
readonly cwd: string;
|
|
90
|
-
/** Explicit package home (admitted run / tests); never process.env.HOME (#604). */
|
|
91
|
-
readonly home?: string;
|
|
92
|
-
/**
|
|
93
|
-
* Frozen GitHub issue face (body + comments). Production loads via shared gh seam.
|
|
94
|
-
* Soft-unavailable → omit (no fake face from attachments).
|
|
95
|
-
*/
|
|
96
|
-
readonly issueFace?: DiaristIssueFace;
|
|
97
|
-
/** Extra cwd roots whose cc project folders are scanned. */
|
|
98
|
-
readonly sessionCwds?: readonly string[];
|
|
99
|
-
readonly signal?: AbortSignal;
|
|
100
|
-
/** Package root for hermes collector method material resolution. */
|
|
101
|
-
readonly packageRoot?: string;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export type DiaristRunResult = {
|
|
105
|
-
readonly ticketNumber: number;
|
|
106
|
-
/** Safeguard-cleaned source count before incremental filter. */
|
|
107
|
-
readonly candidateCount: number;
|
|
108
|
-
/** Blocks not yet on the volume — sole set sent to the collector this court. */
|
|
109
|
-
readonly freshCount: number;
|
|
110
|
-
readonly appended: number;
|
|
111
|
-
readonly rejectedQuotes: number;
|
|
112
|
-
readonly pointers: readonly RecordPointer[];
|
|
113
|
-
readonly entries: readonly TicketProvenanceEntry[];
|
|
114
|
-
readonly humanViewFile: string;
|
|
115
|
-
readonly volumeRecordFile: string;
|
|
116
|
-
readonly collectorStatus:
|
|
117
|
-
| "ok"
|
|
118
|
-
| "skipped-no-fresh"
|
|
119
|
-
| "failed"
|
|
120
|
-
| "empty-selection";
|
|
121
|
-
readonly collectorError?: string;
|
|
122
|
-
readonly llmRawStdout?: string;
|
|
123
|
-
};
|
|
82
|
+
}) => Promise<DiaristIssueFace>;
|
|
124
83
|
|
|
125
84
|
/**
|
|
126
85
|
* Production issue-face capability over shared gh execution seams.
|
|
@@ -216,10 +175,78 @@ export function resolveDiaristGithubOrigin(
|
|
|
216
175
|
return parseGitHubOriginRemote(remoteUrl);
|
|
217
176
|
}
|
|
218
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Acquire the issue face for a bound ticket. Failures are typed and durable on
|
|
180
|
+
* the ticket-provenance volume, then propagated — never washed into empty face.
|
|
181
|
+
*/
|
|
182
|
+
export async function loadDiaristIssueFace(input: {
|
|
183
|
+
readonly ticketNumber: number;
|
|
184
|
+
readonly projectRoot: string;
|
|
185
|
+
readonly home?: string;
|
|
186
|
+
readonly fetcher?: DiaristIssueFaceFetcher;
|
|
187
|
+
}): Promise<DiaristIssueFace> {
|
|
188
|
+
const persistAndThrow = (error: DiaristIssueSourceError): DiaristIssueSourceError => {
|
|
189
|
+
appendIssueSourceFailureDiagnostic({
|
|
190
|
+
ticketNumber: input.ticketNumber,
|
|
191
|
+
cwd: input.projectRoot,
|
|
192
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
193
|
+
cause: error.message,
|
|
194
|
+
reason: error.reason,
|
|
195
|
+
});
|
|
196
|
+
return error;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const origin = resolveDiaristGithubOrigin(input.projectRoot);
|
|
200
|
+
if (origin === undefined) {
|
|
201
|
+
throw persistAndThrow(
|
|
202
|
+
new DiaristIssueSourceError(
|
|
203
|
+
"origin-unresolved",
|
|
204
|
+
`bound ticket #${input.ticketNumber} issue face requires a resolvable github.com origin remote`,
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const fetcher = input.fetcher ?? createDiaristIssueFaceFetcher();
|
|
210
|
+
try {
|
|
211
|
+
return await fetcher({
|
|
212
|
+
owner: origin.owner,
|
|
213
|
+
repo: origin.repo,
|
|
214
|
+
ticketNumber: input.ticketNumber,
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
throw persistAndThrow(
|
|
218
|
+
error instanceof DiaristIssueSourceError
|
|
219
|
+
? error
|
|
220
|
+
: new DiaristIssueSourceError(
|
|
221
|
+
"issue-unavailable",
|
|
222
|
+
`issue face fetch failed for ${origin.owner}/${origin.repo}#${input.ticketNumber}`,
|
|
223
|
+
{ cause: error },
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** One frozen candidate the diarist turn may select by index. */
|
|
230
|
+
export type DiaristSourceCandidate = DiaristSourceBlock & {
|
|
231
|
+
readonly candidateIndex: number;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Frozen per-ticket catalog handed to the diarist turn and re-read by the
|
|
236
|
+
* envelope at accept time. Carries its own volume coordinates so neither side
|
|
237
|
+
* re-derives them from ambient state.
|
|
238
|
+
*/
|
|
239
|
+
export type DiaristSourceCatalog = {
|
|
240
|
+
readonly ticketNumber: number;
|
|
241
|
+
readonly cwd: string;
|
|
242
|
+
readonly home?: string;
|
|
243
|
+
readonly candidates: readonly DiaristSourceCandidate[];
|
|
244
|
+
};
|
|
245
|
+
|
|
219
246
|
/**
|
|
220
247
|
* Identities already processed for this ticket:
|
|
221
248
|
* - volume record identities (selected / verify-fail residue)
|
|
222
|
-
* - offered watermark (blocks shown to
|
|
249
|
+
* - offered watermark (blocks shown to the diarist, selected or not)
|
|
223
250
|
*/
|
|
224
251
|
async function loadSeenEntryIdentities(
|
|
225
252
|
ticketNumber: number,
|
|
@@ -257,7 +284,11 @@ function blockEntryIdentity(
|
|
|
257
284
|
* Mechanical layer does not prose-filter for relevance (锚定宪法).
|
|
258
285
|
* Attachments are never merged in as fake issue-body-comment.
|
|
259
286
|
*/
|
|
260
|
-
|
|
287
|
+
function loadSourceBlocks(input: {
|
|
288
|
+
readonly cwd: string;
|
|
289
|
+
readonly issueFace?: DiaristIssueFace;
|
|
290
|
+
readonly sessionCwds?: readonly string[];
|
|
291
|
+
}): DiaristSourceBlock[] {
|
|
261
292
|
const cwds = input.sessionCwds ?? [input.cwd];
|
|
262
293
|
const blocks: DiaristSourceBlock[] = [...readCcSessionBlocks({ cwds })];
|
|
263
294
|
if (input.issueFace !== undefined) {
|
|
@@ -279,162 +310,205 @@ async function loadSourceBlocks(input: DiaristRunInput): Promise<DiaristSourceBl
|
|
|
279
310
|
return blocks;
|
|
280
311
|
}
|
|
281
312
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
313
|
+
export type PrepareDiaristSourceCatalogInput = {
|
|
314
|
+
readonly ticketNumber: number;
|
|
315
|
+
readonly cwd: string;
|
|
316
|
+
/** Explicit package home (admitted run / tests); never process.env.HOME (#604). */
|
|
317
|
+
readonly home?: string;
|
|
318
|
+
/** Frozen GitHub issue face (body + comments) from the shared gh seam. */
|
|
319
|
+
readonly issueFace?: DiaristIssueFace;
|
|
320
|
+
/** Extra cwd roots whose cc project folders are scanned. */
|
|
321
|
+
readonly sessionCwds?: readonly string[];
|
|
322
|
+
};
|
|
290
323
|
|
|
291
324
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
325
|
+
* Mechanical half A — source enumeration into a frozen catalog.
|
|
326
|
+
* Establishes the per-ticket volume + human view for every bound run (ADR 0075
|
|
327
|
+
* `ticket-provenance-file` 每票一份起居录), then offers only blocks whose entry
|
|
328
|
+
* identity is not already on the volume or the offered watermark (增量幂等).
|
|
295
329
|
*/
|
|
296
|
-
export async function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const anchors: DiaristAnchorSet = buildDiaristAnchors({
|
|
330
|
+
export async function prepareDiaristSourceCatalog(
|
|
331
|
+
input: PrepareDiaristSourceCatalogInput,
|
|
332
|
+
): Promise<DiaristSourceCatalog> {
|
|
333
|
+
ensureTicketProvenanceVolume(input.ticketNumber, input.cwd, input.home);
|
|
334
|
+
const volume = await readTicketProvenance(input.ticketNumber, input.cwd, input.home);
|
|
335
|
+
writeTicketProvenanceHumanView({
|
|
303
336
|
ticketNumber: input.ticketNumber,
|
|
304
|
-
|
|
337
|
+
cwd: input.cwd,
|
|
338
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
339
|
+
entries: volume.entries,
|
|
305
340
|
});
|
|
306
|
-
|
|
341
|
+
|
|
342
|
+
const rawBlocks = loadSourceBlocks(input);
|
|
307
343
|
// Safeguard only (notify filter + dedupe) — never prose-based exclusion.
|
|
308
344
|
const safeguarded = mechanicalSafeguardPipeline(rawBlocks);
|
|
309
|
-
// Incremental: only blocks whose entry identity is not yet on the volume
|
|
310
|
-
// are offered to the collector (ADR 0075 refresh-every-court = 增量幂等).
|
|
311
345
|
const seen = await loadSeenEntryIdentities(input.ticketNumber, input.cwd, input.home);
|
|
312
346
|
const fresh = safeguarded.filter(
|
|
313
347
|
(block) => !seen.has(blockEntryIdentity(input.ticketNumber, block)),
|
|
314
348
|
);
|
|
315
349
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
let llmRawStdout: string | undefined;
|
|
319
|
-
let collect: DiaristLlmCollectResult | undefined;
|
|
320
|
-
|
|
321
|
-
// Production composition always runs the hermes collector (ADR 0075).
|
|
322
|
-
// No injectable skip / alternate collector on this seam.
|
|
323
|
-
const collector = createHermesDiaristCollector({
|
|
350
|
+
return {
|
|
351
|
+
ticketNumber: input.ticketNumber,
|
|
324
352
|
cwd: input.cwd,
|
|
325
|
-
...(input.
|
|
326
|
-
|
|
353
|
+
...(input.home === undefined ? {} : { home: input.home }),
|
|
354
|
+
candidates: fresh.map((block, candidateIndex) => ({ ...block, candidateIndex })),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
327
357
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// before selected entries / quote diagnostics are committed.
|
|
342
|
-
} catch (error) {
|
|
343
|
-
collectorStatus = "failed";
|
|
344
|
-
collectorError =
|
|
345
|
-
error instanceof Error ? error.message : String(error);
|
|
346
|
-
// Durable true-cause on the ticket volume (append-only history).
|
|
347
|
-
appendCollectorFailureDiagnostic({
|
|
348
|
-
ticketNumber: input.ticketNumber,
|
|
349
|
-
cwd: input.cwd,
|
|
350
|
-
...homeOpt,
|
|
351
|
-
collectorError,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
358
|
+
export function serializeDiaristSourceCatalog(catalog: DiaristSourceCatalog): string {
|
|
359
|
+
return JSON.stringify(catalog);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Read a frozen catalog written by the seat. Unreadable/malformed fails loudly. */
|
|
363
|
+
export function loadDiaristSourceCatalog(path: string): DiaristSourceCatalog {
|
|
364
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
365
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
366
|
+
throw new Error(`diarist source catalog is not an object (${path})`);
|
|
367
|
+
}
|
|
368
|
+
const record = parsed as Record<string, unknown>;
|
|
369
|
+
if (typeof record.ticketNumber !== "number" || typeof record.cwd !== "string") {
|
|
370
|
+
throw new Error(`diarist source catalog is missing ticket coordinates (${path})`);
|
|
354
371
|
}
|
|
372
|
+
if (!Array.isArray(record.candidates)) {
|
|
373
|
+
throw new Error(`diarist source catalog is missing candidates (${path})`);
|
|
374
|
+
}
|
|
375
|
+
return parsed as DiaristSourceCatalog;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 失败真因 — typed causes for submitted rows that never reached the volume.
|
|
380
|
+
* Names the cause only; the per-row detail stays on the volume diagnostic.
|
|
381
|
+
*/
|
|
382
|
+
export type DiaristFailureCause =
|
|
383
|
+
/** Row pointed at no candidate in this turn's frozen catalog. */
|
|
384
|
+
| "unknown-candidate"
|
|
385
|
+
/** Row's quotes failed verbatim reverse-verify. */
|
|
386
|
+
| "quote-verify-rejected";
|
|
387
|
+
|
|
388
|
+
/** Honest machine facts about what this turn actually committed to the volume. */
|
|
389
|
+
export type DiaristCommitFacts = {
|
|
390
|
+
readonly ticketNumber: number;
|
|
391
|
+
/** Candidates offered to the diarist this turn. */
|
|
392
|
+
readonly offered: number;
|
|
393
|
+
/** New entries this turn actually put on the volume (entry-count delta). */
|
|
394
|
+
readonly appended: number;
|
|
395
|
+
/** Selections rejected by verbatim reverse-verify. */
|
|
396
|
+
readonly rejectedQuotes: number;
|
|
397
|
+
/** Offered identities newly written to the watermark this turn. */
|
|
398
|
+
readonly watermarked: number;
|
|
399
|
+
readonly volumeRecordFile: string;
|
|
400
|
+
readonly humanViewFile: string;
|
|
401
|
+
/**
|
|
402
|
+
* What this turn's collection amounted to. A turn whose every row was
|
|
403
|
+
* dropped is `nothing-appended`, never `empty-selection` — the diarist
|
|
404
|
+
* selecting nothing and the safeguard band rejecting everything are
|
|
405
|
+
* different events. Why rows were dropped is `failureCauses`.
|
|
406
|
+
*/
|
|
407
|
+
readonly collectorStatus:
|
|
408
|
+
| "ok"
|
|
409
|
+
| "empty-selection"
|
|
410
|
+
| "nothing-appended"
|
|
411
|
+
| "skipped-no-fresh";
|
|
412
|
+
/**
|
|
413
|
+
* 失败真因: empty when every submitted row landed. Populated whenever rows
|
|
414
|
+
* were dropped — including a partial turn whose collectorStatus is `ok`.
|
|
415
|
+
*/
|
|
416
|
+
readonly failureCauses: readonly DiaristFailureCause[];
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Mechanical half B — commit the diarist turn's selections.
|
|
421
|
+
* Verbatim reverse-verify → idempotent sitian append → watermark → human view.
|
|
422
|
+
* Verify failure records a single typed diagnostic and drops that selection; it
|
|
423
|
+
* never bounces the receipt (第 0 条) and never enters the volume as an entry.
|
|
424
|
+
*/
|
|
425
|
+
export async function commitDiaristSelections(input: {
|
|
426
|
+
readonly catalog: DiaristSourceCatalog;
|
|
427
|
+
readonly selections: readonly DiaristSelection[];
|
|
428
|
+
}): Promise<DiaristCommitFacts> {
|
|
429
|
+
const { ticketNumber, cwd } = input.catalog;
|
|
430
|
+
const homeOpt = input.catalog.home === undefined ? {} : { home: input.catalog.home };
|
|
431
|
+
const volumePaths = ensureTicketProvenanceVolume(ticketNumber, cwd, input.catalog.home);
|
|
355
432
|
|
|
356
|
-
const
|
|
357
|
-
|
|
433
|
+
const anchors: DiaristAnchorSet = buildDiaristAnchors({ ticketNumber });
|
|
434
|
+
// Entry-count baseline: sitian entry identity already makes a repeat of an
|
|
435
|
+
// already-recorded block a no-op append, so the honest `appended` is what the
|
|
436
|
+
// volume gained — not how many rows survived verify.
|
|
437
|
+
const before = await readTicketProvenance(ticketNumber, cwd, input.catalog.home);
|
|
438
|
+
let acceptedRows = 0;
|
|
358
439
|
let rejectedQuotes = 0;
|
|
440
|
+
let unknownCandidate = false;
|
|
359
441
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
ticketNumber: input.ticketNumber,
|
|
378
|
-
cwd: input.cwd,
|
|
379
|
-
...homeOpt,
|
|
380
|
-
cause: projected.cause,
|
|
381
|
-
});
|
|
382
|
-
pointers.push(ptr);
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
const ptr = appendTicketProvenanceEntry({
|
|
386
|
-
ticketNumber: input.ticketNumber,
|
|
387
|
-
cwd: input.cwd,
|
|
442
|
+
for (const selection of input.selections) {
|
|
443
|
+
const block = input.catalog.candidates[selection.candidateIndex];
|
|
444
|
+
if (block === undefined) {
|
|
445
|
+
unknownCandidate = true;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const projected = blockToLlmEntry(block, {
|
|
449
|
+
anchors,
|
|
450
|
+
quotes: selection.quotes,
|
|
451
|
+
...(selection.note === undefined ? {} : { note: selection.note }),
|
|
452
|
+
});
|
|
453
|
+
if (!projected.ok) {
|
|
454
|
+
rejectedQuotes += 1;
|
|
455
|
+
// Single diagnostic expression — never a disguised diary entry.
|
|
456
|
+
appendQuoteVerifyFailureDiagnostic({
|
|
457
|
+
ticketNumber,
|
|
458
|
+
cwd,
|
|
388
459
|
...homeOpt,
|
|
389
|
-
|
|
390
|
-
source: "diarist",
|
|
460
|
+
cause: projected.cause,
|
|
391
461
|
});
|
|
392
|
-
|
|
393
|
-
accepted.push(projected.entry);
|
|
462
|
+
continue;
|
|
394
463
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// identities only after the volume writes above, so a crash mid-commit
|
|
399
|
-
// still retries the batch next court. Entry identity and quote-verify
|
|
400
|
-
// diagnostic identity are stable — retry does not duplicate either.
|
|
401
|
-
// Failure does not advance the watermark (retry honestly).
|
|
402
|
-
if (
|
|
403
|
-
collect !== undefined &&
|
|
404
|
-
(collectorStatus === "ok" || collectorStatus === "empty-selection")
|
|
405
|
-
) {
|
|
406
|
-
recordOfferedIdentities({
|
|
407
|
-
ticketNumber: input.ticketNumber,
|
|
408
|
-
cwd: input.cwd,
|
|
464
|
+
appendTicketProvenanceEntry({
|
|
465
|
+
ticketNumber,
|
|
466
|
+
cwd,
|
|
409
467
|
...homeOpt,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
),
|
|
468
|
+
entry: projected.entry,
|
|
469
|
+
source: "diarist",
|
|
413
470
|
});
|
|
471
|
+
acceptedRows += 1;
|
|
414
472
|
}
|
|
415
473
|
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
474
|
+
// Watermark advances only after the durable volume writes above, so a crash
|
|
475
|
+
// mid-commit retries the batch on the next summons. Entry identity and
|
|
476
|
+
// quote-verify diagnostic identity are stable — retry duplicates neither.
|
|
477
|
+
const identities = input.catalog.candidates.map((block) =>
|
|
478
|
+
blockEntryIdentity(ticketNumber, block),
|
|
479
|
+
);
|
|
480
|
+
const alreadyWatermarked = readOfferedIdentities(ticketNumber, cwd, input.catalog.home);
|
|
481
|
+
const watermarked = identities.filter((identity) => !alreadyWatermarked.has(identity)).length;
|
|
482
|
+
recordOfferedIdentities({ ticketNumber, cwd, ...homeOpt, identities });
|
|
483
|
+
|
|
484
|
+
// Refresh the human view from the full volume (includes prior summons).
|
|
485
|
+
const volume = await readTicketProvenance(ticketNumber, cwd, input.catalog.home);
|
|
419
486
|
const humanViewFile = writeTicketProvenanceHumanView({
|
|
420
|
-
ticketNumber
|
|
421
|
-
cwd
|
|
487
|
+
ticketNumber,
|
|
488
|
+
cwd,
|
|
422
489
|
...homeOpt,
|
|
423
490
|
entries: volume.entries,
|
|
424
491
|
});
|
|
425
492
|
|
|
426
493
|
return {
|
|
427
|
-
ticketNumber
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
appended: accepted.length,
|
|
494
|
+
ticketNumber,
|
|
495
|
+
offered: input.catalog.candidates.length,
|
|
496
|
+
appended: volume.entries.length - before.entries.length,
|
|
431
497
|
rejectedQuotes,
|
|
432
|
-
|
|
433
|
-
entries: volume.entries,
|
|
434
|
-
humanViewFile,
|
|
498
|
+
watermarked,
|
|
435
499
|
volumeRecordFile: volumePaths.recordFile,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
500
|
+
humanViewFile,
|
|
501
|
+
collectorStatus:
|
|
502
|
+
input.catalog.candidates.length === 0
|
|
503
|
+
? "skipped-no-fresh"
|
|
504
|
+
: acceptedRows > 0
|
|
505
|
+
? "ok"
|
|
506
|
+
: input.selections.length === 0
|
|
507
|
+
? "empty-selection"
|
|
508
|
+
: "nothing-appended",
|
|
509
|
+
failureCauses: [
|
|
510
|
+
...(unknownCandidate ? (["unknown-candidate"] as const) : []),
|
|
511
|
+
...(rejectedQuotes > 0 ? (["quote-verify-rejected"] as const) : []),
|
|
512
|
+
],
|
|
439
513
|
};
|
|
440
514
|
}
|
package/src/doctor-auditor.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
2
|
-
import { loadAuditorSoul } from "./auditor-soul.ts";
|
|
3
2
|
import {
|
|
4
|
-
createComplianceDecisionTool,
|
|
5
3
|
runComplianceAudit,
|
|
4
|
+
type AuditorSummon,
|
|
6
5
|
type ComplianceDecision,
|
|
7
6
|
} from "./compliance-transport.ts";
|
|
8
7
|
import {
|
|
@@ -17,16 +16,13 @@ export const DOCTOR_AUDIT_TOOL_NAME = "ak_doctor_audit_decision";
|
|
|
17
16
|
export type DoctorAuditOptions = {
|
|
18
17
|
context: HostContext;
|
|
19
18
|
signal?: AbortSignal;
|
|
19
|
+
/** Same seam as runComplianceAudit options — offline tracers only. */
|
|
20
|
+
summonAuditor?: AuditorSummon;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
const tool = createComplianceDecisionTool(
|
|
23
|
-
DOCTOR_AUDIT_TOOL_NAME,
|
|
24
|
-
"提交 typed pass/revise/escalate 决议(太医署审刑)。",
|
|
25
|
-
);
|
|
26
|
-
|
|
27
23
|
/**
|
|
28
|
-
* Doctor auditor
|
|
29
|
-
*
|
|
24
|
+
* Doctor compliance via public auditor activation (#675 / ADR 0062 / owner r11).
|
|
25
|
+
* 审刑院 is an independent role; subject=doctor selects souls/doctor-auditor.md.
|
|
30
26
|
*/
|
|
31
27
|
export function createPiDoctorAuditor(): (options: DoctorAuditOptions) => Promise<ComplianceDecision> {
|
|
32
28
|
return async (options) => {
|
|
@@ -36,13 +32,13 @@ export function createPiDoctorAuditor(): (options: DoctorAuditOptions) => Promis
|
|
|
36
32
|
requireAuditMaterials(subjects);
|
|
37
33
|
|
|
38
34
|
return runComplianceAudit({
|
|
39
|
-
|
|
40
|
-
systemPrompt: await loadAuditorSoul("doctor"),
|
|
41
|
-
roleLabel: "Doctor Soul compliance audit",
|
|
42
|
-
invalidDecisionLabel: "invalid Doctor audit decision",
|
|
35
|
+
subject: "doctor",
|
|
43
36
|
context: options.context,
|
|
44
|
-
...(auditorRunDirectory(options.context) === undefined
|
|
37
|
+
...(auditorRunDirectory(options.context) === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { runDirectory: auditorRunDirectory(options.context) }),
|
|
45
40
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
41
|
+
...(options.summonAuditor === undefined ? {} : { summonAuditor: options.summonAuditor }),
|
|
46
42
|
});
|
|
47
43
|
};
|
|
48
44
|
}
|
package/src/doctor-contracts.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
|
|
|
7
7
|
export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
|
|
8
8
|
export const DOCTOR_ACCEPTED_TEXT = "太医署回执已接受";
|
|
9
9
|
export const DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT = "太医署回执已接受;审计无回执";
|
|
10
|
+
export const DOCTOR_ACCEPTED_AUDIT_UNREADABLE_TEXT = "太医署回执已接受;审计形状不可读";
|
|
10
11
|
export const DOCTOR_OUTPUT_TOOL_DESCRIPTION = "提交唯一终局单案证词;completed 允许空 findings;runtime 补记派生成本入回执。";
|
|
11
12
|
export const DOCTOR_TARGET_KINDS = ["law", "gate", "template", "station", "seat"] as const;
|
|
12
13
|
export type DoctorTargetKind = typeof DOCTOR_TARGET_KINDS[number];
|
package/src/doctor-role.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts"
|
|
|
2
2
|
import { disposeComplianceDecision } from "./audit-escalation.ts";
|
|
3
3
|
import { ComplianceResponseRetentionError, type ComplianceDecision } from "./compliance-transport.ts";
|
|
4
4
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
|
|
5
|
-
import { DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, DOCTOR_ACCEPTED_TEXT, DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
|
|
5
|
+
import { DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, DOCTOR_ACCEPTED_AUDIT_UNREADABLE_TEXT, DOCTOR_ACCEPTED_TEXT, DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
|
|
6
6
|
|
|
7
7
|
import { sitianReport } from "./sitian-facade.ts";
|
|
8
8
|
|
|
@@ -35,7 +35,7 @@ export function createDoctorRoleRuntime(pi: RoleHost, dependencies: DoctorRoleDe
|
|
|
35
35
|
let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
|
|
36
36
|
return { async activate() { const path = pi.getFlag(DOCTOR_CASE_FLAG.name); if (typeof path !== "string" || !path.trim()) throw new Error("Doctor requires --ak-doctor-case"); const soul = (await dependencies.loadSoul()).trim(); if (!soul) throw new Error("Doctor soul is empty"); const patient = await dependencies.loadCase(path); activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
|
|
37
37
|
if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "太医署证据", description: "分页读取留存的 Pi session 字节。", parameters: doctorEvidenceReadSchema, async execute(_id: string, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("太医署未激活"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
|
|
38
|
-
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id: string, params: unknown, signal: AbortSignal | undefined, _update: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendCandidate(ctx, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision<HostToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true as const, ...usageProjection }), revise: (violations) => { throw new Error(`太医署回执违 soul:${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
|
|
38
|
+
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id: string, params: unknown, signal: AbortSignal | undefined, _update: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); const testimony = validateDoctorOutput(params, activation.patient, activation.store); try { appendCandidate(ctx, { version: 1, testimony, readRecord: activation.store.readRecord(), patientIdentity: activation.patient.identity }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx } : { context: ctx, signal }); } catch (error) { host.failInfrastructure(error, ctx, id); } const details = testimony.status === "completed" ? { ...testimony, cost: activation.patient.cost } : testimony; const acceptedDetails = details; return disposeComplianceDecision<HostToolResult<unknown>>(audit, { pass: (usage) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }], details: { ...acceptedDetails, auditNoReceipt }, terminate: true as const, ...usageProjection }), unreadable: (auditUnreadable, usageProjection) => ({ content: [{ type: "text" as const, text: DOCTOR_ACCEPTED_AUDIT_UNREADABLE_TEXT }], details: { ...acceptedDetails, auditUnreadable }, terminate: true as const, ...usageProjection }), revise: (violations) => { throw new Error(`太医署回执违 soul:${violations.join("; ")}`); }, escalate: (result) => result }, acceptedDetails); } });
|
|
39
39
|
pi.on("before_agent_start", (event) => { if (!activation) throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { provenance: "由留存 session 字节推导,封入受理回执。", cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; }); }
|
|
40
40
|
const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME]; const names = pi.getAllTools().map((tool) => tool.name); for (const name of required) if (names.filter((item) => item === name).length !== 1) throw new Error(`Doctor required tool collision or missing: ${name}`); pi.setActiveTools(required); const active = pi.getActiveTools?.() ?? required; if (active.length !== 2 || !required.every((name) => active.includes(name))) throw new Error("Doctor active tool narrowing failed"); } };
|
|
41
41
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Evidence-Child role — filed-officer envelope (#675).
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
EVIDENCE_CHILD_ACCEPTED_TEXT,
|
|
6
|
+
EVIDENCE_CHILD_OUTPUT_TOOL_NAME,
|
|
7
|
+
evidenceChildOutputSchema,
|
|
8
|
+
} from "./package-contracts/evidence-child-output.ts";
|
|
9
|
+
|
|
10
|
+
export { EVIDENCE_CHILD_ACCEPTED_TEXT, EVIDENCE_CHILD_OUTPUT_TOOL_NAME };
|
|
11
|
+
|
|
12
|
+
export type EvidenceChildRuntimeDependencies = {
|
|
13
|
+
loadSoul(): Promise<string>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const EVIDENCE_CHILD_TOOL_SPEC = {
|
|
17
|
+
name: EVIDENCE_CHILD_OUTPUT_TOOL_NAME,
|
|
18
|
+
label: "取证输出",
|
|
19
|
+
description: "提交取证报告。",
|
|
20
|
+
promptSnippet: "提交取证报告",
|
|
21
|
+
parameters: evidenceChildOutputSchema,
|
|
22
|
+
} as const;
|