@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca
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 +129 -39
- package/README.zh-CN.md +128 -39
- package/dist/activation-ledger-topology.js +1 -1
- package/dist/archivist-record-entry.js +47 -26
- package/dist/auditor-dossier-tool.js +3 -1
- package/dist/compliance-transport.js +17 -26
- package/dist/doctor-contracts.js +1 -2
- package/dist/dossier-resolution.js +4 -8
- package/dist/engine-detour-tool.js +33 -8
- package/dist/engine-detour.js +1 -0
- package/dist/evidence-child-executor.js +277 -424
- package/dist/host-contracts.js +22 -0
- package/dist/institutional-resolution.js +88 -0
- package/dist/merger-contracts.js +1 -2
- package/dist/navigator-attendance.js +16 -0
- package/dist/notary-contracts.js +2 -3
- package/dist/package-contracts/fixer-output.js +1 -2
- package/dist/packaged-role-registry.js +102 -9
- package/dist/pi/in-process-session.js +671 -0
- package/dist/public-cli/config.js +7 -5
- package/dist/public-cli/main.js +3767 -4113
- package/dist/reviewer-child-executor.js +6 -1
- package/dist/session-opening-materials.js +10 -38
- package/dist/sitian-appender.js +146 -0
- package/dist/sitian-contracts.js +21 -0
- package/dist/sitian-facade.js +15 -0
- package/dist/sitian-reader.js +52 -0
- package/extensions/role-runtime.ts +9 -7
- package/package.json +5 -5
- package/resources/engines/cursor.md +12 -26
- package/resources/engines/hermes.md +2 -23
- package/resources/engines/opus.md +1 -16
- package/resources/navigator-route-playbook.md +2 -2
- package/scripts/build-package.mjs +0 -1
- package/souls/quality-law.md +1 -1
- package/src/activation-ledger-topology.ts +1 -1
- package/src/archivist-record-entry.ts +57 -45
- package/src/auditor-dossier-tool.ts +4 -2
- package/src/canonical-skill-binding.ts +37 -27
- package/src/collector-role.ts +11 -20
- package/src/collector-tool-schemas.ts +1 -4
- package/src/compliance-transport.ts +19 -44
- package/src/doctor-auditor.ts +1 -5
- package/src/doctor-contracts.ts +1 -4
- package/src/doctor-role.ts +31 -8
- package/src/dossier-resolution.ts +8 -8
- package/src/engine-detour-tool.ts +48 -19
- package/src/engine-detour.ts +3 -0
- package/src/evidence-child-executor.ts +277 -485
- package/src/factory-board.ts +3 -3
- package/src/gatekeeper-role.ts +21 -10
- package/src/host-contracts.ts +386 -0
- package/src/in-process-session.ts +1 -115
- package/src/institutional-resolution.ts +135 -0
- package/src/judge-auditor.ts +1 -5
- package/src/judge-role.ts +37 -51
- package/src/merger-contracts.ts +1 -4
- package/src/merger-role.ts +7 -11
- package/src/navigator-attendance.ts +20 -1
- package/src/notary-contracts.ts +8 -11
- package/src/notary-role.ts +7 -23
- package/src/notary-source-run.ts +4 -3
- package/src/package-contracts/fixer-output.ts +1 -4
- package/src/package-resources/method-skill-binding.ts +10 -34
- package/src/packaged-role-registry.ts +122 -12
- package/src/pi/adapter.ts +192 -0
- package/src/pi/durable-principal.ts +98 -0
- package/src/pi/in-process-session.ts +819 -0
- package/src/pi/known-failure.ts +77 -0
- package/src/pi/pi-normalization.ts +284 -0
- package/src/pi/role-turn-host.ts +459 -0
- package/src/public-cli/auto-resume.ts +42 -44
- package/src/public-cli/cli.ts +171 -257
- package/src/public-cli/coder-run.ts +115 -433
- package/src/public-cli/collector-run.ts +62 -314
- package/src/public-cli/config.ts +7 -5
- package/src/public-cli/doctor-run.ts +41 -52
- package/src/public-cli/fixer-run.ts +122 -427
- package/src/public-cli/invocation.ts +287 -96
- package/src/public-cli/judge-run.ts +92 -422
- package/src/public-cli/merger-run.ts +167 -429
- package/src/public-cli/notary-run.ts +41 -47
- package/src/public-cli/option-definitions.ts +136 -3
- package/src/public-cli/post-admission.ts +497 -0
- package/src/public-cli/public-run-credentials.ts +4 -4
- package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
- package/src/public-cli/reviewer-run.ts +132 -449
- package/src/public-cli/run-lifecycle.ts +339 -327
- package/src/public-cli/settlement.ts +251 -134
- package/src/public-cli/terminal.ts +3 -9
- package/src/public-cli/turn-request.ts +87 -0
- package/src/reviewer-child-executor.ts +9 -1
- package/src/reviewer-role.ts +11 -20
- package/src/role-runtime.ts +129 -78
- package/src/session-opening-materials.ts +25 -39
- package/src/sitian-appender.ts +171 -0
- package/src/sitian-contracts.ts +106 -0
- package/src/sitian-facade.ts +18 -0
- package/src/sitian-reader.ts +62 -0
- package/src/worker-role.ts +42 -40
- package/src/worker-submission-gates.ts +33 -0
- package/dist/archivist-role-run-coordinates.js +0 -28
- package/dist/package-contracts/terminating-infrastructure.js +0 -90
- package/resources/engines/sonnet.md +0 -27
- package/resources/engines/zcode.md +0 -63
- package/src/archivist-role-run-coordinates.ts +0 -45
- package/src/package-contracts/terminating-infrastructure.ts +0 -132
- package/src/public-cli/explicit-internal.ts +0 -427
- package/src/public-cli/one-shot-dispatch.ts +0 -300
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitian Appender kernel (ADR 0065).
|
|
3
|
+
* Computes destination automatically from ledger topology without destination parameters.
|
|
4
|
+
* Owns volume open, torn-tail recovery, entry-level idempotency, and commit boundary.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
9
|
+
|
|
10
|
+
import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
|
|
11
|
+
import {
|
|
12
|
+
activationBookDirectory,
|
|
13
|
+
ensureRealDirectoryTree,
|
|
14
|
+
errorText,
|
|
15
|
+
physicallyContainedIn,
|
|
16
|
+
resolveActivationLedgerHome,
|
|
17
|
+
} from "./activation-ledger-topology.ts";
|
|
18
|
+
import {
|
|
19
|
+
SitianInfrastructureError,
|
|
20
|
+
type RecordPointer,
|
|
21
|
+
type SitianRecord,
|
|
22
|
+
type SitianRecordInput,
|
|
23
|
+
} from "./sitian-contracts.ts";
|
|
24
|
+
|
|
25
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Authorized S4 submission ledger kinds that share a common run submission volume. */
|
|
30
|
+
export const S4_SUBMISSION_LEDGER_KINDS = new Set([
|
|
31
|
+
"candidate",
|
|
32
|
+
"batchContext",
|
|
33
|
+
"outcome",
|
|
34
|
+
"sealed",
|
|
35
|
+
"post-seal-anomaly",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/** Compute the volume partition key for directory placement. */
|
|
39
|
+
export function resolveSitianVolumeCategory(kind: string): string {
|
|
40
|
+
if (S4_SUBMISSION_LEDGER_KINDS.has(kind)) {
|
|
41
|
+
return "submission-ledger";
|
|
42
|
+
}
|
|
43
|
+
return kind;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safeBookKey(cwd: string): string {
|
|
47
|
+
try {
|
|
48
|
+
return resolveBookKeyFromGit(cwd);
|
|
49
|
+
} catch {
|
|
50
|
+
return basename(resolve(cwd)) || "default";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Compute the destination directory and record file from ledger topology (ADR 0065). */
|
|
55
|
+
export function resolveSitianRecordPath(input: SitianRecordInput): {
|
|
56
|
+
readonly sessionDir: string;
|
|
57
|
+
readonly recordFile: string;
|
|
58
|
+
readonly ledgerHome: string;
|
|
59
|
+
} {
|
|
60
|
+
const cwd = input.cwd ?? process.cwd();
|
|
61
|
+
const ledgerHome = resolveActivationLedgerHome();
|
|
62
|
+
const category = resolveSitianVolumeCategory(input.kind);
|
|
63
|
+
|
|
64
|
+
let sessionDir: string;
|
|
65
|
+
|
|
66
|
+
if (input.sessionParent !== undefined && input.sessionParent.length > 0 && physicallyContainedIn(ledgerHome, input.sessionParent)) {
|
|
67
|
+
sessionDir = join(dirname(input.sessionParent), category);
|
|
68
|
+
} else {
|
|
69
|
+
const bookKey = safeBookKey(cwd);
|
|
70
|
+
const bookDir = activationBookDirectory(ledgerHome, bookKey);
|
|
71
|
+
if (input.subject !== undefined) {
|
|
72
|
+
let subjectStr: string;
|
|
73
|
+
if (typeof input.subject === "string") {
|
|
74
|
+
subjectStr = input.subject;
|
|
75
|
+
} else if (typeof input.subject.runId === "string" && input.subject.runId.length > 0) {
|
|
76
|
+
subjectStr = input.subject.runId;
|
|
77
|
+
} else {
|
|
78
|
+
subjectStr = JSON.stringify(input.subject);
|
|
79
|
+
}
|
|
80
|
+
const digest = createHash("sha256").update(subjectStr).digest("hex").slice(0, 32);
|
|
81
|
+
sessionDir = join(bookDir, category, digest);
|
|
82
|
+
} else {
|
|
83
|
+
sessionDir = join(bookDir, category);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const recordFile = join(sessionDir, "records.jsonl");
|
|
88
|
+
return { sessionDir, recordFile, ledgerHome };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Appends a canonical record to its self-computed volume under the Sitian contract.
|
|
93
|
+
* - Idempotency: checks volume by deterministic canonical identity; returns existing pointer on hit.
|
|
94
|
+
* - Torn-tail recovery: checks file tail; un-terminated trailing bytes are sealed with a newline and re-parsed.
|
|
95
|
+
* Substate a (valid JSON): committed on recovery, returns existing pointer.
|
|
96
|
+
* Substate b (malformed): preserved as bad line, check misses, appends new row.
|
|
97
|
+
* - Commit point: full JSON string ending with newline.
|
|
98
|
+
*/
|
|
99
|
+
export function appendSitianRecord(input: SitianRecordInput): RecordPointer {
|
|
100
|
+
try {
|
|
101
|
+
const { sessionDir, recordFile, ledgerHome } = resolveSitianRecordPath(input);
|
|
102
|
+
ensureRealDirectoryTree(ledgerHome, sessionDir);
|
|
103
|
+
|
|
104
|
+
const identity = input.identity ?? randomUUID();
|
|
105
|
+
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
106
|
+
const host = input.host ?? "pi";
|
|
107
|
+
|
|
108
|
+
const record: SitianRecord = {
|
|
109
|
+
level: input.level,
|
|
110
|
+
kind: input.kind,
|
|
111
|
+
identity,
|
|
112
|
+
...(input.subject === undefined ? {} : { subject: input.subject }),
|
|
113
|
+
...(input.sessionParent === undefined ? {} : { sessionParent: input.sessionParent }),
|
|
114
|
+
...(input.priorEventId === undefined ? {} : { priorEventId: input.priorEventId }),
|
|
115
|
+
timestamp,
|
|
116
|
+
host,
|
|
117
|
+
...(input.source === undefined ? {} : { source: input.source }),
|
|
118
|
+
...(input.payload === undefined ? {} : { payload: input.payload }),
|
|
119
|
+
...(input.raw === undefined ? {} : { raw: input.raw }),
|
|
120
|
+
...(input.usage === undefined ? {} : { usage: input.usage }),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (existsSync(recordFile)) {
|
|
124
|
+
const buffer = readFileSync(recordFile);
|
|
125
|
+
if (buffer.length > 0) {
|
|
126
|
+
// Torn-tail check: if last byte is not newline, seal the fragment with \n
|
|
127
|
+
if (buffer[buffer.length - 1] !== 0x0a) {
|
|
128
|
+
appendFileSync(recordFile, "\n", "utf8");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Self-check volume by canonical identity
|
|
132
|
+
const text = readFileSync(recordFile, "utf8");
|
|
133
|
+
for (const line of text.split("\n")) {
|
|
134
|
+
const trimmed = line.trim();
|
|
135
|
+
if (!trimmed) continue;
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(trimmed);
|
|
138
|
+
if (isRecord(parsed) && parsed.identity === identity) {
|
|
139
|
+
// Existing record found (or substate a recovered record) -> return existing pointer
|
|
140
|
+
return {
|
|
141
|
+
identity,
|
|
142
|
+
recordFile,
|
|
143
|
+
kind: record.kind,
|
|
144
|
+
level: record.level,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// Malformed lines (including substate b preserved bad lines) are ignored during self-check
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Not found -> append new canonical row terminating with newline
|
|
155
|
+
const row = `${JSON.stringify(record)}\n`;
|
|
156
|
+
appendFileSync(recordFile, row, "utf8");
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
identity,
|
|
160
|
+
recordFile,
|
|
161
|
+
kind: record.kind,
|
|
162
|
+
level: record.level,
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof SitianInfrastructureError) throw error;
|
|
166
|
+
throw new SitianInfrastructureError(
|
|
167
|
+
`Sitian appender persistence failure: ${errorText(error)}`,
|
|
168
|
+
{ cause: error },
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitian (司天台) canonical contracts, Layout schema, and pointer definitions.
|
|
3
|
+
* ADR 0065 single record entry + ADR 0068 Taishi analysis.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Closed set of record levels. */
|
|
7
|
+
export type SitianLevel = "run-summary" | "event" | "protocol-snapshot";
|
|
8
|
+
|
|
9
|
+
/** Subject coordinate identifying the work identity or run attempt. */
|
|
10
|
+
export type SitianSubject =
|
|
11
|
+
| string
|
|
12
|
+
| {
|
|
13
|
+
readonly runId: string;
|
|
14
|
+
readonly attemptId?: string | undefined;
|
|
15
|
+
readonly [key: string]: unknown;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Raw reference pointing to original retained bytes/frames without duplication (#513). */
|
|
19
|
+
export type SitianRawReference = {
|
|
20
|
+
readonly sessionFile: string;
|
|
21
|
+
readonly entryId: string | number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Optional usage metrics carried on canonical records. */
|
|
25
|
+
export type SitianUsage = {
|
|
26
|
+
readonly promptTokens?: number | undefined;
|
|
27
|
+
readonly completionTokens?: number | undefined;
|
|
28
|
+
readonly totalTokens?: number | undefined;
|
|
29
|
+
readonly [key: string]: unknown;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Canonical Sitian record layout. */
|
|
33
|
+
export type SitianRecord = {
|
|
34
|
+
readonly level: SitianLevel;
|
|
35
|
+
readonly kind: string;
|
|
36
|
+
readonly identity: string;
|
|
37
|
+
readonly subject?: SitianSubject | undefined;
|
|
38
|
+
/** Parent session principal link / nesting origin. */
|
|
39
|
+
readonly sessionParent?: string | undefined;
|
|
40
|
+
/** Prior event identity in a submission ledger chain (S4; distinct from sessionParent). */
|
|
41
|
+
readonly priorEventId?: string | undefined;
|
|
42
|
+
readonly timestamp: string;
|
|
43
|
+
readonly host: string;
|
|
44
|
+
readonly source?: string | undefined;
|
|
45
|
+
readonly payload?: unknown;
|
|
46
|
+
readonly raw?: SitianRawReference | undefined;
|
|
47
|
+
readonly usage?: SitianUsage | undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Typed pointer returned upon durable acceptance of a record. */
|
|
51
|
+
export type RecordPointer = {
|
|
52
|
+
readonly identity: string;
|
|
53
|
+
readonly recordFile: string;
|
|
54
|
+
readonly kind: string;
|
|
55
|
+
readonly level: SitianLevel;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Input passed to the Sitian facade (sitianReport). */
|
|
59
|
+
export type SitianRecordInput = {
|
|
60
|
+
readonly level: SitianLevel;
|
|
61
|
+
readonly kind: string;
|
|
62
|
+
/** Deterministic or explicit record identity. Minted if omitted. */
|
|
63
|
+
readonly identity?: string | undefined;
|
|
64
|
+
readonly subject?: SitianSubject | undefined;
|
|
65
|
+
readonly sessionParent?: string | undefined;
|
|
66
|
+
readonly priorEventId?: string | undefined;
|
|
67
|
+
readonly timestamp?: string | undefined;
|
|
68
|
+
readonly host?: string | undefined;
|
|
69
|
+
readonly source?: string | undefined;
|
|
70
|
+
readonly payload?: unknown;
|
|
71
|
+
readonly raw?: SitianRawReference | undefined;
|
|
72
|
+
readonly usage?: SitianUsage | undefined;
|
|
73
|
+
readonly cwd?: string | undefined;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Typed malformed diagnostic emitted by the canonical Reader upon encountering damaged lines. */
|
|
77
|
+
export type SitianMalformedDiagnostic = {
|
|
78
|
+
readonly kind: "malformed";
|
|
79
|
+
readonly line: number;
|
|
80
|
+
readonly raw: string;
|
|
81
|
+
readonly error: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** Canonical Reader result surfacing both valid records and non-destructive diagnostics. */
|
|
85
|
+
export type SitianReadResult = {
|
|
86
|
+
readonly records: readonly SitianRecord[];
|
|
87
|
+
readonly diagnostics: readonly SitianMalformedDiagnostic[];
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Lift direct-cause fs errno onto a wrap error (no chain walk). */
|
|
91
|
+
export function attachDirectErrnoCode(error: Error, cause: unknown): void {
|
|
92
|
+
if (cause === null || typeof cause !== "object" || !("code" in cause)) return;
|
|
93
|
+
const code = (cause as { code?: unknown }).code;
|
|
94
|
+
if (typeof code === "string") (error as NodeJS.ErrnoException).code = code;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Typed infrastructure error for real ledger persistence / IO failures. */
|
|
98
|
+
export class SitianInfrastructureError extends Error {
|
|
99
|
+
readonly knownCause = "session" as const;
|
|
100
|
+
|
|
101
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
102
|
+
super(message, options);
|
|
103
|
+
this.name = "SitianInfrastructureError";
|
|
104
|
+
attachDirectErrnoCode(this, options?.cause);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitian Facade (司天门面) — Sole entrypoint for record reporting across all producers.
|
|
3
|
+
* ADR 0065 single record entry, #520 r8.
|
|
4
|
+
*/
|
|
5
|
+
import { appendSitianRecord } from "./sitian-appender.ts";
|
|
6
|
+
import type { RecordPointer, SitianRecordInput } from "./sitian-contracts.ts";
|
|
7
|
+
|
|
8
|
+
export * from "./sitian-contracts.ts";
|
|
9
|
+
export * from "./sitian-appender.ts";
|
|
10
|
+
export * from "./sitian-reader.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sole write API for Sitian canonical records.
|
|
14
|
+
* Returns typed RecordPointer with durable record identity and readable file location.
|
|
15
|
+
*/
|
|
16
|
+
export function sitianReport(input: SitianRecordInput): RecordPointer {
|
|
17
|
+
return appendSitianRecord(input);
|
|
18
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitian Reader kernel (ADR 0068 / #520 r8).
|
|
3
|
+
* Sole authoritative read path for canonical Sitian records.
|
|
4
|
+
* Traversal contract:
|
|
5
|
+
* - Malformed lines are exposed as typed malformed diagnostics and traversal continues.
|
|
6
|
+
* - Zero whitewashing, zero deduplication.
|
|
7
|
+
* - Canonical rows after malformed lines are always reachable.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
SitianMalformedDiagnostic,
|
|
14
|
+
SitianReadResult,
|
|
15
|
+
SitianRecord,
|
|
16
|
+
} from "./sitian-contracts.ts";
|
|
17
|
+
|
|
18
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
19
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Read a Sitian record volume with full traversal and non-destructive diagnostics. */
|
|
23
|
+
export async function readSitianRecords(recordFile: string): Promise<SitianReadResult> {
|
|
24
|
+
if (!existsSync(recordFile)) {
|
|
25
|
+
return { records: [], diagnostics: [] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const text = await readFile(recordFile, "utf8");
|
|
29
|
+
const lines = text.split("\n");
|
|
30
|
+
|
|
31
|
+
const records: SitianRecord[] = [];
|
|
32
|
+
const diagnostics: SitianMalformedDiagnostic[] = [];
|
|
33
|
+
|
|
34
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
35
|
+
const line = lines[index]!;
|
|
36
|
+
if (!line.trim()) continue;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(line);
|
|
40
|
+
if (isRecord(parsed)) {
|
|
41
|
+
records.push(parsed as unknown as SitianRecord);
|
|
42
|
+
} else {
|
|
43
|
+
const typeDesc = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
44
|
+
diagnostics.push({
|
|
45
|
+
kind: "malformed",
|
|
46
|
+
line: index + 1,
|
|
47
|
+
raw: line,
|
|
48
|
+
error: `expected JSON object, got ${typeDesc}`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
diagnostics.push({
|
|
53
|
+
kind: "malformed",
|
|
54
|
+
line: index + 1,
|
|
55
|
+
raw: line,
|
|
56
|
+
error: error instanceof Error ? error.message : String(error),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { records, diagnostics };
|
|
62
|
+
}
|
package/src/worker-role.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
ExtensionAPI,
|
|
4
|
-
ExtensionContext,
|
|
5
|
-
AgentToolResult,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
|
|
2
|
+
import { stringEnum } from "./host-contracts.ts";
|
|
7
3
|
import { Type, type Static } from "typebox";
|
|
8
4
|
import { openToolObjectFromUnion } from "./open-tool-schema.ts";
|
|
9
|
-
import {
|
|
10
|
-
failOnInfrastructureFailureDeclaration,
|
|
11
|
-
withInfrastructureFailureDeclaration,
|
|
12
|
-
} from "./package-contracts/terminating-infrastructure.ts";
|
|
13
5
|
|
|
14
6
|
import type {
|
|
15
7
|
AnyCanonicalSkillBinding,
|
|
@@ -32,7 +24,6 @@ import {
|
|
|
32
24
|
parseFixerPrerequisites,
|
|
33
25
|
type FixerInvocationInput,
|
|
34
26
|
} from "./package-contracts/fixer-packet.ts";
|
|
35
|
-
import { requireGatekeeperPass, type GatekeeperPassHostActions } from "./gatekeeper-role.ts";
|
|
36
27
|
import {
|
|
37
28
|
createWorkerSubmissionGate,
|
|
38
29
|
WorkerCommitReminderError,
|
|
@@ -65,18 +56,18 @@ function matchFixerBashForbiddenLiteral(
|
|
|
65
56
|
|
|
66
57
|
const coderOutputVariants = Type.Union([
|
|
67
58
|
Type.Object({
|
|
68
|
-
status:
|
|
59
|
+
status: stringEnum(["planned"] as const, { description: "planned — 形状指引,非 schema 闸" }),
|
|
69
60
|
report: Type.String({ minLength: 1, description: "如实结果报告" }),
|
|
70
61
|
}, { additionalProperties: false }),
|
|
71
62
|
Type.Object({
|
|
72
|
-
status:
|
|
63
|
+
status: stringEnum(["completed", "refused"] as const, {
|
|
73
64
|
description:
|
|
74
65
|
"completed | refused — 形状指引,非 schema 闸;completed 回执含 TDD、同模式、引入回归、行为事实四项证据",
|
|
75
66
|
}),
|
|
76
67
|
report: Type.String({ minLength: 1, description: "如实结果报告" }),
|
|
77
68
|
}, { additionalProperties: false }),
|
|
78
69
|
Type.Object({
|
|
79
|
-
status:
|
|
70
|
+
status: stringEnum(["unfinished"] as const, {
|
|
80
71
|
description:
|
|
81
72
|
"unfinished — 形状指引,非 schema 闸;缺前置或违宪约束致本局未完成时可用。缺待决 owner 决定或答复属缺前置。",
|
|
82
73
|
}),
|
|
@@ -89,9 +80,7 @@ const coderOutputVariants = Type.Union([
|
|
|
89
80
|
})),
|
|
90
81
|
}, { additionalProperties: false }),
|
|
91
82
|
]);
|
|
92
|
-
|
|
93
|
-
openToolObjectFromUnion(coderOutputVariants),
|
|
94
|
-
);
|
|
83
|
+
const coderOutputSchema = openToolObjectFromUnion(coderOutputVariants);
|
|
95
84
|
export type { FixerOutput, CoderOutput };
|
|
96
85
|
export const FIXER_FLAG_DEFINITIONS = {
|
|
97
86
|
packet: {
|
|
@@ -125,7 +114,26 @@ function isWorkerPhase(value: unknown): value is WorkerPhase {
|
|
|
125
114
|
return typeof value === "string" && (FIXER_PHASES as readonly string[]).includes(value);
|
|
126
115
|
}
|
|
127
116
|
|
|
128
|
-
export type WorkerRoleHostActions =
|
|
117
|
+
export type WorkerRoleHostActions = HostGatekeeperActions;
|
|
118
|
+
|
|
119
|
+
/** Stable code for completed apply without host skill-expansion capability evidence (#525). */
|
|
120
|
+
export const CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE =
|
|
121
|
+
"coder_skill_expansion_evidence_missing" as const;
|
|
122
|
+
|
|
123
|
+
export type CoderSkillExpansionEvidenceMissingResult = {
|
|
124
|
+
readonly code: typeof CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** Correct completed rejection — not infrastructure; projected via submission non-pass bridge. */
|
|
128
|
+
export class CoderSkillExpansionEvidenceMissingError extends Error {
|
|
129
|
+
readonly code = CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE;
|
|
130
|
+
readonly result: CoderSkillExpansionEvidenceMissingResult;
|
|
131
|
+
constructor() {
|
|
132
|
+
super("Coder completed requires host skill-expansion capability evidence");
|
|
133
|
+
this.name = "CoderSkillExpansionEvidenceMissingError";
|
|
134
|
+
this.result = Object.freeze({ code: CODER_SKILL_EXPANSION_EVIDENCE_MISSING_CODE });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
129
137
|
|
|
130
138
|
export type FixerRoleDependencies = {
|
|
131
139
|
loadSoul(): Promise<string>;
|
|
@@ -141,7 +149,7 @@ export type CoderRoleDependencies = {
|
|
|
141
149
|
};
|
|
142
150
|
|
|
143
151
|
export type WorkerRoleRuntime = {
|
|
144
|
-
activate(ctx?:
|
|
152
|
+
activate(ctx?: HostContext): Promise<void>;
|
|
145
153
|
/** Arm gate ① baseline after envelope places the worktree (coder/fixer). Parent feeds archivist durability. */
|
|
146
154
|
armSubmissionGate(cwd: string, parent?: { getSessionFile(): string | undefined }): void;
|
|
147
155
|
};
|
|
@@ -169,14 +177,14 @@ function requireSingletonSubmissionCall(
|
|
|
169
177
|
toolCallId: string,
|
|
170
178
|
expectedToolName: string,
|
|
171
179
|
roleLabel: WorkerRoleLabel,
|
|
172
|
-
ctx:
|
|
180
|
+
ctx: HostContext,
|
|
173
181
|
): void {
|
|
174
182
|
const leaf = ctx.sessionManager.getLeafEntry();
|
|
175
183
|
const seat = roleLabel === "Fixer" ? "修内司" : "将作监";
|
|
176
|
-
if (leaf?.type !== "message" || leaf.message.role !== "assistant") {
|
|
184
|
+
if (leaf?.type !== "message" || leaf.message === undefined || leaf.message.role !== "assistant" || !Array.isArray(leaf.message.content)) {
|
|
177
185
|
throw new Error(`${seat}回执非唯一终局工具调用`);
|
|
178
186
|
}
|
|
179
|
-
const calls = leaf.message.content.filter((part) => part.type === "toolCall");
|
|
187
|
+
const calls = leaf.message.content.filter((part): part is Extract<typeof part, { type: "toolCall" }> => part.type === "toolCall");
|
|
180
188
|
const call = calls[0];
|
|
181
189
|
if (
|
|
182
190
|
calls.length !== 1 || call === undefined || call.id !== toolCallId ||
|
|
@@ -192,7 +200,7 @@ function assertAcceptableThroughHost(
|
|
|
192
200
|
status: string,
|
|
193
201
|
details: unknown,
|
|
194
202
|
hostActions: WorkerRoleHostActions,
|
|
195
|
-
ctx:
|
|
203
|
+
ctx: HostContext,
|
|
196
204
|
toolCallId: string,
|
|
197
205
|
): void {
|
|
198
206
|
try {
|
|
@@ -210,7 +218,7 @@ function assertAcceptableThroughHost(
|
|
|
210
218
|
}
|
|
211
219
|
|
|
212
220
|
export function createFixerRoleRuntime(
|
|
213
|
-
pi:
|
|
221
|
+
pi: RoleHost,
|
|
214
222
|
dependencies: FixerRoleDependencies,
|
|
215
223
|
hostActions: WorkerRoleHostActions,
|
|
216
224
|
): WorkerRoleRuntime {
|
|
@@ -268,16 +276,13 @@ export function createFixerRoleRuntime(
|
|
|
268
276
|
pi.registerTool({
|
|
269
277
|
name: FIXER_OUTPUT_TOOL_NAME,
|
|
270
278
|
label: "修内司输出",
|
|
271
|
-
description: "
|
|
279
|
+
description: "提交修内司终局回执;基础设施失败走 abort,不经本工具。",
|
|
272
280
|
promptSnippet: "提交修内司终局回执",
|
|
273
281
|
parameters: fixerOutputSchema,
|
|
274
|
-
async execute(toolCallId, parameters, _signal, _onUpdate, ctx): Promise<
|
|
282
|
+
async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
|
|
275
283
|
if (packet === undefined || phase === undefined) {
|
|
276
284
|
throw new Error("修内司修理包与阶段未装载");
|
|
277
285
|
}
|
|
278
|
-
// #541: infra declaration fails via the shared host seam before any
|
|
279
|
-
// submission gate + Gatekeeper work.
|
|
280
|
-
failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
|
|
281
286
|
requireSingletonSubmissionCall(
|
|
282
287
|
toolCallId,
|
|
283
288
|
FIXER_OUTPUT_TOOL_NAME,
|
|
@@ -293,7 +298,7 @@ export function createFixerRoleRuntime(
|
|
|
293
298
|
ctx,
|
|
294
299
|
toolCallId,
|
|
295
300
|
);
|
|
296
|
-
await requireGatekeeperPass({
|
|
301
|
+
await pi.requireGatekeeperPass!({
|
|
297
302
|
context: ctx,
|
|
298
303
|
subject: { kind: "worker_completion", material: JSON.stringify(output) },
|
|
299
304
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
@@ -336,7 +341,7 @@ export function createFixerRoleRuntime(
|
|
|
336
341
|
}
|
|
337
342
|
|
|
338
343
|
export function createCoderRoleRuntime(
|
|
339
|
-
pi:
|
|
344
|
+
pi: RoleHost,
|
|
340
345
|
dependencies: CoderRoleDependencies,
|
|
341
346
|
hostActions: WorkerRoleHostActions,
|
|
342
347
|
): WorkerRoleRuntime {
|
|
@@ -405,13 +410,10 @@ export function createCoderRoleRuntime(
|
|
|
405
410
|
description: "提交将作监终局回执;本工具无 escalate 通道。",
|
|
406
411
|
promptSnippet: "提交将作监终局回执",
|
|
407
412
|
parameters: coderOutputSchema,
|
|
408
|
-
async execute(toolCallId, parameters, _signal, _onUpdate, ctx) {
|
|
413
|
+
async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext) {
|
|
409
414
|
if (task === undefined || phase === undefined) {
|
|
410
415
|
throw new Error("将作监任务与阶段未装载");
|
|
411
416
|
}
|
|
412
|
-
// #541: infra declaration fails via the shared host seam before any
|
|
413
|
-
// submission gate + Gatekeeper work.
|
|
414
|
-
failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
|
|
415
417
|
requireSingletonSubmissionCall(
|
|
416
418
|
toolCallId,
|
|
417
419
|
CODER_OUTPUT_TOOL_NAME,
|
|
@@ -423,9 +425,9 @@ export function createCoderRoleRuntime(
|
|
|
423
425
|
phase === "apply" && output.status === "completed" &&
|
|
424
426
|
!expansionCaptured
|
|
425
427
|
) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
428
|
+
const rejection = new CoderSkillExpansionEvidenceMissingError();
|
|
429
|
+
hostActions.bindSubmissionNonPass(toolCallId, rejection.result);
|
|
430
|
+
throw rejection;
|
|
429
431
|
}
|
|
430
432
|
assertAcceptableThroughHost(
|
|
431
433
|
submissionGate,
|
|
@@ -435,7 +437,7 @@ export function createCoderRoleRuntime(
|
|
|
435
437
|
ctx,
|
|
436
438
|
toolCallId,
|
|
437
439
|
);
|
|
438
|
-
await requireGatekeeperPass({
|
|
440
|
+
await pi.requireGatekeeperPass!({
|
|
439
441
|
context: ctx,
|
|
440
442
|
subject: { kind: "worker_completion", material: JSON.stringify(output) },
|
|
441
443
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
@@ -483,7 +485,7 @@ export function createCoderRoleRuntime(
|
|
|
483
485
|
expansionPending = false;
|
|
484
486
|
if (originalRequest !== undefined) {
|
|
485
487
|
expansionCaptured = binding.captureExpansion(
|
|
486
|
-
event.prompt,
|
|
488
|
+
pi.capabilities?.skillExpansion(event.prompt),
|
|
487
489
|
originalRequest,
|
|
488
490
|
) !== undefined;
|
|
489
491
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type RecordSessionParent,
|
|
10
10
|
WORKER_SUBMISSION_GATE_KIND,
|
|
11
11
|
} from "./archivist-record-entry.ts";
|
|
12
|
+
import { sitianReport } from "./sitian-facade.ts";
|
|
12
13
|
|
|
13
14
|
export const WORKER_SUBMISSION_GATE_RECORD_KIND = WORKER_SUBMISSION_GATE_KIND;
|
|
14
15
|
export const WORKER_COMMIT_BASELINE_ENTRY_TYPE = "commit-baseline";
|
|
@@ -275,6 +276,18 @@ export function createWorkerSubmissionGate(): {
|
|
|
275
276
|
version: 1,
|
|
276
277
|
head: baseline,
|
|
277
278
|
});
|
|
279
|
+
sitianReport({
|
|
280
|
+
level: "event",
|
|
281
|
+
kind: "gate",
|
|
282
|
+
cwd,
|
|
283
|
+
...(parent?.getSessionFile() ? { sessionParent: parent.getSessionFile() } : {}),
|
|
284
|
+
payload: {
|
|
285
|
+
type: WORKER_COMMIT_BASELINE_ENTRY_TYPE,
|
|
286
|
+
version: 1,
|
|
287
|
+
head: baseline,
|
|
288
|
+
},
|
|
289
|
+
source: "worker-submission-gates",
|
|
290
|
+
});
|
|
278
291
|
},
|
|
279
292
|
assertAcceptable(status, details) {
|
|
280
293
|
if (status === "unfinished" && !unfinishedReasonPresent(details)) {
|
|
@@ -291,6 +304,16 @@ export function createWorkerSubmissionGate(): {
|
|
|
291
304
|
if (!headMoved && !reminded) {
|
|
292
305
|
reminded = true;
|
|
293
306
|
record?.appendCustomEntry(WORKER_COMMIT_REMINDER_BOUNCE_ENTRY_TYPE, { version: 1 });
|
|
307
|
+
sitianReport({
|
|
308
|
+
level: "event",
|
|
309
|
+
kind: "gate",
|
|
310
|
+
cwd: root,
|
|
311
|
+
payload: {
|
|
312
|
+
type: WORKER_COMMIT_REMINDER_BOUNCE_ENTRY_TYPE,
|
|
313
|
+
version: 1,
|
|
314
|
+
},
|
|
315
|
+
source: "worker-submission-gates",
|
|
316
|
+
});
|
|
294
317
|
throw new WorkerCommitReminderError();
|
|
295
318
|
}
|
|
296
319
|
reminded = true;
|
|
@@ -307,6 +330,16 @@ export function createWorkerSubmissionGate(): {
|
|
|
307
330
|
}
|
|
308
331
|
prefixReminded = true;
|
|
309
332
|
record?.appendCustomEntry(WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE, { version: 1 });
|
|
333
|
+
sitianReport({
|
|
334
|
+
level: "event",
|
|
335
|
+
kind: "gate",
|
|
336
|
+
cwd: root,
|
|
337
|
+
payload: {
|
|
338
|
+
type: WORKER_PREFIX_REMINDER_BOUNCE_ENTRY_TYPE,
|
|
339
|
+
version: 1,
|
|
340
|
+
},
|
|
341
|
+
source: "worker-submission-gates",
|
|
342
|
+
});
|
|
310
343
|
throw new WorkerPrefixReminderError();
|
|
311
344
|
},
|
|
312
345
|
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
|
|
3
|
-
import {
|
|
4
|
-
activationBookDirectory,
|
|
5
|
-
resolveActivationLedgerHome
|
|
6
|
-
} from "./activation-ledger-topology.js";
|
|
7
|
-
function roleRunSessionCoordinates(options) {
|
|
8
|
-
const ledgerHome = resolveActivationLedgerHome(
|
|
9
|
-
options.home === void 0 ? void 0 : () => options.home
|
|
10
|
-
);
|
|
11
|
-
const bookKey = resolveBookKeyFromGit(options.cwd);
|
|
12
|
-
const runDirectory = join(
|
|
13
|
-
activationBookDirectory(ledgerHome, bookKey),
|
|
14
|
-
"runs",
|
|
15
|
-
`${options.runId}@${options.role}`
|
|
16
|
-
);
|
|
17
|
-
const sessionDirectory = join(runDirectory, "session");
|
|
18
|
-
return {
|
|
19
|
-
ledgerHome,
|
|
20
|
-
bookKey,
|
|
21
|
-
runDirectory,
|
|
22
|
-
sessionDirectory,
|
|
23
|
-
sessionFile: join(sessionDirectory, "session.jsonl")
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export {
|
|
27
|
-
roleRunSessionCoordinates
|
|
28
|
-
};
|