@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2535
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 +5 -2
- package/dist/inspector-contracts.js +1 -0
- package/dist/package-contracts/terminating-tools.js +9 -1
- package/dist/packaged-role-registry.js +3 -1
- package/dist/public-cli/config.js +9 -9
- package/dist/public-cli/main.js +247 -109
- package/dist/public-cli/registry.js +2 -4
- package/extensions/role-runtime.ts +2 -1
- package/package.json +1 -1
- package/src/gatekeeper-role.ts +2 -1
- package/src/inspector-contracts.ts +1 -0
- package/src/inspector-role.ts +20 -0
- package/src/package-contracts/terminating-tools.ts +11 -2
- package/src/packaged-role-registry.ts +2 -0
- package/src/public-cli/cli.ts +20 -0
- package/src/public-cli/config.ts +11 -11
- package/src/public-cli/inspector-run.ts +61 -0
- package/src/public-cli/invocation.ts +64 -86
- package/src/public-cli/option-definitions.ts +14 -0
- package/src/public-cli/registry.ts +2 -4
- package/src/public-cli/run-lifecycle.ts +27 -11
- package/src/public-cli/settlement.ts +94 -35
- package/src/public-cli/terminal.ts +2 -1
- package/src/role-runtime.ts +44 -1
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
} from "../navigator-invocation-identity.ts";
|
|
94
94
|
import type { NavigatorPhase } from "../navigator-attendance.ts";
|
|
95
95
|
import { NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, parseNoReceiptLifecycleFacts, type NoReceiptLifecycleFacts } from "../receipt-delivery-policy.ts";
|
|
96
|
+
import { INSPECTOR_OUTPUT_TOOL_NAME } from "../inspector-contracts.ts";
|
|
96
97
|
import {
|
|
97
98
|
ensureRunArtifactsDir,
|
|
98
99
|
type AdmittedCoderInvocation,
|
|
@@ -100,6 +101,7 @@ import {
|
|
|
100
101
|
type AdmittedDoctorInvocation,
|
|
101
102
|
type AdmittedFixerInvocation,
|
|
102
103
|
type AdmittedJudgeInvocation,
|
|
104
|
+
type AdmittedInspectorInvocation,
|
|
103
105
|
type AdmittedMergerInvocation,
|
|
104
106
|
type AdmittedNotaryInvocation,
|
|
105
107
|
type AdmittedReviewerInvocation,
|
|
@@ -3057,6 +3059,83 @@ export async function trySettleDoctorTerminalResult(
|
|
|
3057
3059
|
return settleLawfulDoctorTerminalResult(admitted);
|
|
3058
3060
|
}
|
|
3059
3061
|
|
|
3062
|
+
export function extractInspectorRoleOutcome(entries: readonly SessionEntry[]): TerminalResult["roleOutcome"] | undefined {
|
|
3063
|
+
if (!isReceiptSettlementBindingClear(entries)) return undefined;
|
|
3064
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
3065
|
+
const message = entries[index]?.message;
|
|
3066
|
+
if (message?.role !== "toolResult" || message.toolName !== INSPECTOR_OUTPUT_TOOL_NAME) continue;
|
|
3067
|
+
if (!isAcceptedPackagedRoleTerminalResult(message)) continue;
|
|
3068
|
+
const details = message.details;
|
|
3069
|
+
if (typeof details !== "object" || details === null) continue;
|
|
3070
|
+
const status = Reflect.get(details, "status");
|
|
3071
|
+
if (status !== "pass" && status !== "bounce") continue;
|
|
3072
|
+
const findings = Reflect.get(details, "findings");
|
|
3073
|
+
return { kind: "accepted", role: "inspector", status, decisiveFacts: { findings } };
|
|
3074
|
+
}
|
|
3075
|
+
return undefined;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
type UnusableOutputReceipt = Readonly<{
|
|
3079
|
+
candidate: unknown;
|
|
3080
|
+
diagnostic: string;
|
|
3081
|
+
}>;
|
|
3082
|
+
|
|
3083
|
+
function findUnusableOutputReceipt(
|
|
3084
|
+
entries: readonly SessionEntry[],
|
|
3085
|
+
toolName: string,
|
|
3086
|
+
isUsable: (candidate: unknown) => boolean,
|
|
3087
|
+
diagnostic: string,
|
|
3088
|
+
): UnusableOutputReceipt | undefined {
|
|
3089
|
+
let acceptedNonUsable: unknown | undefined;
|
|
3090
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
3091
|
+
const message = entries[index]?.message;
|
|
3092
|
+
if (message?.role !== "toolResult") continue;
|
|
3093
|
+
const residual = boundErroredToolCandidate(entries, index, message, toolName);
|
|
3094
|
+
if (residual !== undefined) {
|
|
3095
|
+
return { candidate: residual.candidate, diagnostic: residual.diagnostic };
|
|
3096
|
+
}
|
|
3097
|
+
if (
|
|
3098
|
+
acceptedNonUsable === undefined &&
|
|
3099
|
+
message.toolName === toolName &&
|
|
3100
|
+
isAcceptedPackagedRoleTerminalResult(message) &&
|
|
3101
|
+
!isUsable(message.details)
|
|
3102
|
+
) {
|
|
3103
|
+
acceptedNonUsable = message.details;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
return acceptedNonUsable === undefined
|
|
3107
|
+
? undefined
|
|
3108
|
+
: { candidate: acceptedNonUsable, diagnostic };
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
export async function trySettleInspectorTerminalResult(
|
|
3112
|
+
admitted: AdmittedInspectorInvocation,
|
|
3113
|
+
): Promise<TerminalResult | undefined> {
|
|
3114
|
+
const entries = await readLawfulSettlementEntries(admitted);
|
|
3115
|
+
if (entries === undefined) return undefined;
|
|
3116
|
+
const outcome = extractInspectorRoleOutcome(entries);
|
|
3117
|
+
if (outcome !== undefined) {
|
|
3118
|
+
return withOptionalGateProjection({
|
|
3119
|
+
roleOutcome: outcome,
|
|
3120
|
+
navigator: extractNavigatorFact(entries), artifacts: [], runId: admitted.runId,
|
|
3121
|
+
}, admitted.sessionDirectory);
|
|
3122
|
+
}
|
|
3123
|
+
const unusable = findUnusableOutputReceipt(
|
|
3124
|
+
entries,
|
|
3125
|
+
INSPECTOR_OUTPUT_TOOL_NAME,
|
|
3126
|
+
(candidate) => isRecord(candidate) &&
|
|
3127
|
+
(candidate.status === "pass" || candidate.status === "bounce"),
|
|
3128
|
+
"给事中回执无显式 pass/bounce",
|
|
3129
|
+
);
|
|
3130
|
+
return unusable === undefined
|
|
3131
|
+
? undefined
|
|
3132
|
+
: settleFailureTerminalResult(admitted, {
|
|
3133
|
+
cause: "output",
|
|
3134
|
+
diagnostic: unusable.diagnostic,
|
|
3135
|
+
details: { candidate: unusable.candidate, acceptedReceipt: false },
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3060
3139
|
/** Lawful Notary accepted outcome (pass/bounce). */
|
|
3061
3140
|
export type LawfulNotaryRoleOutcome = {
|
|
3062
3141
|
kind: "accepted";
|
|
@@ -3099,46 +3178,26 @@ async function settleLawfulNotaryTerminalResult(
|
|
|
3099
3178
|
if (entries === undefined) return undefined;
|
|
3100
3179
|
const extracted = extractNotaryRoleOutcome(entries);
|
|
3101
3180
|
if (extracted === undefined) {
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
const message = entries[index]?.message;
|
|
3107
|
-
if (message?.role !== "toolResult") continue;
|
|
3108
|
-
const residual = boundErroredToolCandidate(
|
|
3109
|
-
entries,
|
|
3110
|
-
index,
|
|
3111
|
-
message,
|
|
3112
|
-
NOTARY_OUTPUT_TOOL_NAME,
|
|
3113
|
-
);
|
|
3114
|
-
if (residual !== undefined) {
|
|
3115
|
-
return settleFailureTerminalResult(admitted, {
|
|
3116
|
-
cause: "output",
|
|
3117
|
-
diagnostic: residual.diagnostic,
|
|
3118
|
-
details: { candidate: residual.candidate, acceptedReceipt: false },
|
|
3119
|
-
});
|
|
3120
|
-
}
|
|
3121
|
-
if (
|
|
3122
|
-
acceptedNonUsable === undefined &&
|
|
3123
|
-
message.toolName === NOTARY_OUTPUT_TOOL_NAME &&
|
|
3124
|
-
isAcceptedPackagedRoleTerminalResult(message)
|
|
3125
|
-
) {
|
|
3126
|
-
// Accepted once but not a lawful pass/bounce — hold as fallback.
|
|
3181
|
+
const unusable = findUnusableOutputReceipt(
|
|
3182
|
+
entries,
|
|
3183
|
+
NOTARY_OUTPUT_TOOL_NAME,
|
|
3184
|
+
(candidate) => {
|
|
3127
3185
|
try {
|
|
3128
|
-
validateRecordedNotaryOutput(
|
|
3186
|
+
validateRecordedNotaryOutput(candidate);
|
|
3187
|
+
return true;
|
|
3129
3188
|
} catch {
|
|
3130
|
-
|
|
3189
|
+
return false;
|
|
3131
3190
|
}
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3191
|
+
},
|
|
3192
|
+
"符宝郎回执无显式 pass/bounce",
|
|
3193
|
+
);
|
|
3194
|
+
return unusable === undefined
|
|
3195
|
+
? undefined
|
|
3196
|
+
: settleFailureTerminalResult(admitted, {
|
|
3136
3197
|
cause: "output",
|
|
3137
|
-
diagnostic:
|
|
3138
|
-
details: { candidate:
|
|
3198
|
+
diagnostic: unusable.diagnostic,
|
|
3199
|
+
details: { candidate: unusable.candidate, acceptedReceipt: false },
|
|
3139
3200
|
});
|
|
3140
|
-
}
|
|
3141
|
-
return undefined;
|
|
3142
3201
|
}
|
|
3143
3202
|
const navigator = extractNavigatorFact(entries);
|
|
3144
3203
|
return withOptionalGateProjection(
|
|
@@ -38,7 +38,8 @@ export type TerminalRoleName =
|
|
|
38
38
|
| "doctor"
|
|
39
39
|
| "reviewer"
|
|
40
40
|
| "merger"
|
|
41
|
-
| "notary"
|
|
41
|
+
| "notary"
|
|
42
|
+
| "inspector";
|
|
42
43
|
|
|
43
44
|
/** Merger/Collector residual only — Notary/audit residual abolished (#475). */
|
|
44
45
|
export type ResidualIncompleteTerminalOutcome = {
|
package/src/role-runtime.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeSync } from "node:fs";
|
|
2
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Value } from "typebox/value";
|
|
4
4
|
|
|
5
5
|
import { activationTraceRecordSchema, namedActivationCause, type ActivationTraceRecord, type ActivationTraceWriter } from "./activation-trace.ts";
|
|
@@ -39,6 +39,9 @@ import {
|
|
|
39
39
|
import type { ComplianceDecision } from "./compliance-transport.ts";
|
|
40
40
|
import { createDoctorRoleRuntime } from "./doctor-role.ts";
|
|
41
41
|
import { createNotaryRoleRuntime } from "./notary-role.ts";
|
|
42
|
+
import { inspectorOutputSchema, projectInspectorReceipt } from "./inspector-role.ts";
|
|
43
|
+
import { failOnInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
|
|
44
|
+
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
|
|
42
45
|
import { decorateSettlementWithNavigation, formatNavigatorReport, NAVIGATOR_EVENT_TYPE, navigatorSubjectKey, navigatorUnavailableError, subjectPath, type NavigatorAttendance, type NavigatorEvent, type NavigatorPhase, type NavigatorReport, type NavigatorSettlement, type NavigatorSubjectProvenance, type NavigatorWorkContext } from "./navigator-attendance.ts";
|
|
43
46
|
import {
|
|
44
47
|
buildNavigatorInfrastructureFailureFact,
|
|
@@ -367,6 +370,7 @@ type ActivationRuntime = {
|
|
|
367
370
|
collector: { activate(context: ExtensionContext, event: { reason: string }): Promise<void> };
|
|
368
371
|
doctor: { activate(): Promise<void> };
|
|
369
372
|
notary: { activate(): Promise<void> };
|
|
373
|
+
inspector: { activate(): Promise<void> };
|
|
370
374
|
merger(): Promise<void>;
|
|
371
375
|
};
|
|
372
376
|
|
|
@@ -404,6 +408,7 @@ function activationStage(role: PackagedRole, runtime: ActivationRuntime): { id:
|
|
|
404
408
|
case "collector": return { id: "load-and-install", run: async () => runtime.collector.activate(runtime.context, runtime.event) };
|
|
405
409
|
case "doctor": return { id: "load-and-install", run: async () => runtime.doctor.activate() };
|
|
406
410
|
case "notary": return { id: "load-and-install", run: async () => runtime.notary.activate() };
|
|
411
|
+
case "inspector": return { id: "load-and-install", run: async () => runtime.inspector.activate() };
|
|
407
412
|
case "merger": return { id: "prepare-git-and-install", run: async () => runtime.merger() };
|
|
408
413
|
}
|
|
409
414
|
}
|
|
@@ -486,6 +491,7 @@ export type RoleRuntimeDependencies = {
|
|
|
486
491
|
createCollectorTransport?(): CollectorGitHubTransport;
|
|
487
492
|
loadDoctorSoul?(): Promise<string>;
|
|
488
493
|
loadNotarySoul?(): Promise<string>;
|
|
494
|
+
loadInspectorSoul?(): Promise<string>;
|
|
489
495
|
loadNotarySourceRun?(path: string): Promise<import("./notary-contracts.ts").NotarySourceRunLocator>;
|
|
490
496
|
loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
|
|
491
497
|
loadMergerSoul?(): Promise<string>;
|
|
@@ -989,6 +995,42 @@ export function createRoleRuntimeExtension(
|
|
|
989
995
|
async loadCase(path) { if (!dependencies.loadDoctorCase) throw new Error("Doctor runtime dependencies are not configured"); return dependencies.loadDoctorCase(path); },
|
|
990
996
|
async auditCompliance(options) { if (!dependencies.auditDoctorCompliance) throw new Error("Doctor runtime dependencies are not configured"); return dependencies.auditDoctorCompliance(options); },
|
|
991
997
|
}, hostActions);
|
|
998
|
+
let inspectorSoul: string | undefined;
|
|
999
|
+
let inspectorRegistered = false;
|
|
1000
|
+
const inspector = {
|
|
1001
|
+
async activate(): Promise<void> {
|
|
1002
|
+
if (!dependencies.loadInspectorSoul) throw new Error("Inspector runtime dependencies are not configured");
|
|
1003
|
+
inspectorSoul = (await dependencies.loadInspectorSoul()).trim();
|
|
1004
|
+
if (inspectorSoul.length === 0) throw new Error("Inspector soul is empty");
|
|
1005
|
+
if (!inspectorRegistered) {
|
|
1006
|
+
inspectorRegistered = true;
|
|
1007
|
+
pi.registerTool({
|
|
1008
|
+
name: INSPECTOR_OUTPUT_TOOL_NAME,
|
|
1009
|
+
label: "给事中输出",
|
|
1010
|
+
description: "给事中终局回执,状态为 pass 或 bounce。",
|
|
1011
|
+
promptSnippet: "给事中终局回执",
|
|
1012
|
+
parameters: inspectorOutputSchema,
|
|
1013
|
+
async execute(toolCallId, parameters, _signal, _onUpdate, ctx): Promise<AgentToolResult<unknown>> {
|
|
1014
|
+
failOnInfrastructureFailureDeclaration(parameters, hostActions, ctx, toolCallId);
|
|
1015
|
+
const leaf = ctx.sessionManager.getLeafEntry();
|
|
1016
|
+
const calls = leaf?.type === "message" && leaf.message.role === "assistant"
|
|
1017
|
+
? leaf.message.content.filter((part) => part.type === "toolCall")
|
|
1018
|
+
: [];
|
|
1019
|
+
if (calls.length !== 1 || calls[0]?.id !== toolCallId || calls[0]?.name !== INSPECTOR_OUTPUT_TOOL_NAME) {
|
|
1020
|
+
throw new Error("给事中回执非唯一终局工具调用");
|
|
1021
|
+
}
|
|
1022
|
+
return projectInspectorReceipt(parameters);
|
|
1023
|
+
},
|
|
1024
|
+
});
|
|
1025
|
+
pi.on("before_agent_start", (event) => {
|
|
1026
|
+
if (inspectorSoul === undefined) throw new Error("给事中未激活");
|
|
1027
|
+
return { systemPrompt: `${event.systemPrompt}\n\n<inspector_soul>\n${inspectorSoul}\n</inspector_soul>` };
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
const tools = pi.getAllTools().filter((tool) => tool.name === INSPECTOR_OUTPUT_TOOL_NAME);
|
|
1031
|
+
if (tools.length !== 1) throw new Error(`Inspector required tool collision or missing: ${INSPECTOR_OUTPUT_TOOL_NAME}`);
|
|
1032
|
+
},
|
|
1033
|
+
};
|
|
992
1034
|
const notary = createNotaryRoleRuntime(pi, {
|
|
993
1035
|
async loadSoul() {
|
|
994
1036
|
if (!dependencies.loadNotarySoul) throw new Error("Notary runtime dependencies are not configured");
|
|
@@ -1138,6 +1180,7 @@ export function createRoleRuntimeExtension(
|
|
|
1138
1180
|
collector,
|
|
1139
1181
|
doctor,
|
|
1140
1182
|
notary,
|
|
1183
|
+
inspector,
|
|
1141
1184
|
merger: async () => {
|
|
1142
1185
|
if (dependencies.mergerGitState === undefined) {
|
|
1143
1186
|
sessionMergerGitState = dependencies.createMergerGitState?.(ctx.cwd);
|