immune-brain 3.6.3 → 3.6.5
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/package.json +6 -3
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +117 -47
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +6 -4
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +17 -44
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +213 -81
- package/plugins/immune-brain/dist/imm-loop.md +8 -2
- package/plugins/immune-brain/dist/imm-planner.md +9 -4
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +35 -2
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +23 -13
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +133 -45
- package/plugins/immune-brain/runtime/claude/mcp_server.ts +14 -1
- package/plugins/immune-brain/runtime/claude/review_host.ts +0 -10
- package/plugins/immune-brain/runtime/commands/kernel.ts +15 -13
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +107 -11
- package/plugins/immune-brain/runtime/kernel/application.ts +6 -0
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +407 -0
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +17 -8
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +76 -13
- package/plugins/immune-brain/runtime/kernel/index.ts +2 -0
- package/plugins/immune-brain/runtime/kernel/intent.ts +67 -23
- package/plugins/immune-brain/runtime/kernel/observation.ts +2 -0
- package/plugins/immune-brain/runtime/kernel/reducer.ts +34 -8
- package/plugins/immune-brain/runtime/kernel/storage.ts +17 -2
- package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +1 -1
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +0 -4
- package/plugins/immune-brain/runtime/kernel/types.ts +10 -0
- package/plugins/immune-brain/runtime/kernel/validation.ts +10 -6
- package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +2 -2
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "immune-brain",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.5",
|
|
4
4
|
"description": "Immune-Brain agent skill system",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -30,13 +30,16 @@
|
|
|
30
30
|
"scripts": {
|
|
31
31
|
"changeset": "changeset",
|
|
32
32
|
"changeset:version": "changeset version && bun scripts/build-claude-plugin.ts && bun scripts/plugin_versioning.ts validate",
|
|
33
|
-
"verify:release": "bun scripts/plugin_versioning.ts validate && bun scripts/build-claude-plugin.ts --check && bun scripts/sync-dist-docs.ts --check && bun test && npm pack --dry-run --ignore-scripts",
|
|
33
|
+
"verify:release": "bun scripts/plugin_versioning.ts validate && bun scripts/build-claude-plugin.ts --check && bun scripts/sync-dist-docs.ts --check && bun run typecheck && bun test && npm pack --dry-run --ignore-scripts",
|
|
34
34
|
"changeset:publish": "bun run verify:release && changeset publish",
|
|
35
|
-
"release": "bun run changeset:publish"
|
|
35
|
+
"release": "bun run changeset:publish",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@changesets/changelog-github": "^0.5.2",
|
|
39
40
|
"@changesets/cli": "^2.29.8",
|
|
41
|
+
"@types/bun": "^1.4.1",
|
|
42
|
+
"typescript": "5.7",
|
|
40
43
|
"zod": "^4.1.8"
|
|
41
44
|
},
|
|
42
45
|
"peerDependencies": {
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
clearTerminalTaskRailOnInput,
|
|
57
57
|
loopResultDetails,
|
|
58
58
|
notifyOnce,
|
|
59
|
+
type UiContext,
|
|
59
60
|
presentTaskOverviewOverlay,
|
|
60
61
|
presentTaskRail,
|
|
61
62
|
presentTaskRailResult,
|
|
@@ -68,7 +69,7 @@ import {
|
|
|
68
69
|
type UserAttentionEventV1,
|
|
69
70
|
type UserAttentionReason,
|
|
70
71
|
} from "./pi-canary-interaction";
|
|
71
|
-
import { isToolFailureState, throwToolFailure } from "./pi-canary-tool-failure";
|
|
72
|
+
import { isToolFailureState, throwToolFailure, type ToolFailureV1 } from "./pi-canary-tool-failure";
|
|
72
73
|
import { taskDiffIdentity, taskRevisionIdentity, captureGitTaskSnapshot } from "../runtime/workspace_scope";
|
|
73
74
|
import {
|
|
74
75
|
AssuranceProgression,
|
|
@@ -87,6 +88,7 @@ import {
|
|
|
87
88
|
type AssuranceProgressionPorts,
|
|
88
89
|
type AssuranceSubmitReviewResult,
|
|
89
90
|
type AssuranceVerdict,
|
|
91
|
+
type HostContext,
|
|
90
92
|
type QaVerificationProgress,
|
|
91
93
|
type SnapshotDescriptor,
|
|
92
94
|
} from "./pi-canary-assurance-progression";
|
|
@@ -175,6 +177,7 @@ export type { AssuranceRole } from "./pi-canary-assurance";
|
|
|
175
177
|
export type AuthorizeOperation =
|
|
176
178
|
| "approve-breaking-intent-revision"
|
|
177
179
|
| "resolve-user-decision"
|
|
180
|
+
| "authorize-rework"
|
|
178
181
|
| "stop";
|
|
179
182
|
|
|
180
183
|
export interface SnapshotDescriptorInput {
|
|
@@ -262,11 +265,18 @@ type LoopToolAction =
|
|
|
262
265
|
context: Record<string, unknown>;
|
|
263
266
|
};
|
|
264
267
|
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
/**
|
|
269
|
+
* The exact ports object the Pi Assurance progression runs on.
|
|
270
|
+
*
|
|
271
|
+
* This lived as an inline literal inside the anonymous default export, so no
|
|
272
|
+
* test could ever obtain what production wires; every host adapter defect that
|
|
273
|
+
* reached a published plugin lived in this object. Exporting the factory lets
|
|
274
|
+
* the dual-host conformance suite drive the real thing.
|
|
275
|
+
*/
|
|
276
|
+
export function createPiAssuranceProgressionPorts(
|
|
267
277
|
dependencies: CanaryWorkExtensionDependencies = {},
|
|
268
|
-
) {
|
|
269
|
-
|
|
278
|
+
): AssuranceProgressionPorts {
|
|
279
|
+
return {
|
|
270
280
|
projectTask: (root, taskId) => projectAssuranceState(root, taskId),
|
|
271
281
|
readTaskRecord: (root, taskId) => readTaskRecord(root, taskId),
|
|
272
282
|
readTaskIntent: (root, taskId) => readTaskIntent(root, taskId),
|
|
@@ -294,7 +304,14 @@ export default function (
|
|
|
294
304
|
qaOnAuthorityCommit: dependencies.qaOnAuthorityCommit,
|
|
295
305
|
qaAfterAuthorityCommit: dependencies.qaAfterAuthorityCommit,
|
|
296
306
|
qaJobTimeoutMs: dependencies.qaJobTimeoutMs,
|
|
297
|
-
} satisfies AssuranceProgressionPorts
|
|
307
|
+
} satisfies AssuranceProgressionPorts;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export default function (
|
|
311
|
+
pi: ExtensionAPI,
|
|
312
|
+
dependencies: CanaryWorkExtensionDependencies = {},
|
|
313
|
+
) {
|
|
314
|
+
const progression = new AssuranceProgression(createPiAssuranceProgressionPorts(dependencies));
|
|
298
315
|
|
|
299
316
|
let railContext: ExtensionContext | undefined;
|
|
300
317
|
const refreshTaskRail = async (ctx: ExtensionContext) => {
|
|
@@ -419,6 +436,7 @@ export default function (
|
|
|
419
436
|
Type.Object({ op: Type.Literal("advance_assurance") }),
|
|
420
437
|
Type.Object({ op: Type.Literal("submit_review"), verdict: Type.Unknown() }),
|
|
421
438
|
Type.Object({ op: Type.Literal("request_authorization") }),
|
|
439
|
+
Type.Object({ op: Type.Literal("request_stop") }),
|
|
422
440
|
Type.Object({ op: Type.Literal("repair_authority_state") }),
|
|
423
441
|
Type.Object({ op: Type.Literal("freeze_artifacts") }),
|
|
424
442
|
Type.Object({
|
|
@@ -514,8 +532,8 @@ export default function (
|
|
|
514
532
|
return failCanaryTool(taskId, action.op, "authority_conflict", "authority_repair_failed", message, blocked.next_action);
|
|
515
533
|
}
|
|
516
534
|
}
|
|
517
|
-
if (action.op === "advance_assurance" || action.op === "request_authorization" || action.op === "submit_review" || action.op === "approve_breaking_intent_revision") {
|
|
518
|
-
if ((action.op === "request_authorization" || action.op === "approve_breaking_intent_revision") && ctx.mode !== "tui")
|
|
535
|
+
if (action.op === "advance_assurance" || action.op === "request_stop" || action.op === "request_authorization" || action.op === "submit_review" || action.op === "approve_breaking_intent_revision") {
|
|
536
|
+
if ((action.op === "request_stop" || action.op === "request_authorization" || action.op === "approve_breaking_intent_revision") && ctx.mode !== "tui")
|
|
519
537
|
return failCanaryTool(taskId, action.op, "blocked", "tui_required", "literal-user authorization is TUI-only", "invoke the TUI Tool");
|
|
520
538
|
const result = action.op === "advance_assurance"
|
|
521
539
|
? await progression.advance(taskId, ctx, signal, (update) => {
|
|
@@ -531,7 +549,9 @@ export default function (
|
|
|
531
549
|
ctx,
|
|
532
550
|
(action as { next_intent?: unknown }).next_intent,
|
|
533
551
|
)
|
|
534
|
-
:
|
|
552
|
+
: action.op === "request_stop"
|
|
553
|
+
? await authorizeExactOperation(taskId, "stop", { ...ctx, signal: signal && ctx.signal ? AbortSignal.any([signal, ctx.signal]) : signal ?? ctx.signal })
|
|
554
|
+
: await requestAuthorization(taskId, ctx);
|
|
535
555
|
const enriched = await enrichAssuranceResult(ctx, taskId, result as unknown as Record<string, unknown>);
|
|
536
556
|
presentTaskRailResult(ctx, taskId, enriched);
|
|
537
557
|
throwIfCanaryToolFailure(taskId, action.op, enriched);
|
|
@@ -680,7 +700,7 @@ export default function (
|
|
|
680
700
|
});
|
|
681
701
|
|
|
682
702
|
type AuthorizationOutcome =
|
|
683
|
-
| { state: "applied"; operation: AuthorizeOperation; lifecycle?: string }
|
|
703
|
+
| { state: "applied"; operation: AuthorizeOperation; lifecycle?: string; delivery_error?: string }
|
|
684
704
|
| { state: "cancelled"; operation: AuthorizeOperation; reason: string }
|
|
685
705
|
| { state: "blocked"; reason: string };
|
|
686
706
|
|
|
@@ -710,12 +730,15 @@ export default function (
|
|
|
710
730
|
let invocation: InvocationToken;
|
|
711
731
|
const authorizationGeneration = progression.sessionGenerationValue();
|
|
712
732
|
try {
|
|
733
|
+
if (operation === "stop" && progression.active(taskId)?.state === "running")
|
|
734
|
+
throw new Error("assurance operation is already running");
|
|
713
735
|
invocation = progression.openInvocation(taskId);
|
|
714
736
|
} catch (error) {
|
|
715
737
|
const reason = error instanceof Error ? error.message : String(error);
|
|
716
738
|
notifyOnce(ctx, `authorization-open:${taskId}:${reason}`, `cannot authorize ${taskId}: ${reason}`, "error");
|
|
717
739
|
return { state: "blocked", reason };
|
|
718
740
|
}
|
|
741
|
+
try {
|
|
719
742
|
const projection = await projectAssuranceState(ctx.cwd, taskId);
|
|
720
743
|
if (projection.error || !projection.claim) {
|
|
721
744
|
const reason = projection.error ?? "no active backend claim";
|
|
@@ -825,6 +848,9 @@ export default function (
|
|
|
825
848
|
`Resolution: ${userDecisionOperation.resolution}`,
|
|
826
849
|
]
|
|
827
850
|
: []),
|
|
851
|
+
...(operation === "authorize-rework"
|
|
852
|
+
? [`Replan boundaries: ${projection.projection.replan_required_ids.join(", ")}`]
|
|
853
|
+
: []),
|
|
828
854
|
...(nextIntent
|
|
829
855
|
? [
|
|
830
856
|
`Next Intent: rev ${nextIntent.revision} (${nextIntentHash})`,
|
|
@@ -872,16 +898,27 @@ export default function (
|
|
|
872
898
|
if (nextIntent) {
|
|
873
899
|
restoreStagedIntent();
|
|
874
900
|
}
|
|
875
|
-
if (
|
|
901
|
+
if (
|
|
902
|
+
operation !== "stop" &&
|
|
903
|
+
operation !== "approve-breaking-intent-revision" &&
|
|
904
|
+
operation !== "authorize-rework"
|
|
905
|
+
)
|
|
876
906
|
await recordCancelledUserDecision(ctx, taskId, operation, snapshotDigestRef).catch(() => undefined);
|
|
877
907
|
progression.closeInvocation(invocation);
|
|
908
|
+
if (operation === "stop" && !ctx.signal?.aborted && !(error instanceof Error && error.name === "AbortError")) {
|
|
909
|
+
return { state: "blocked", reason: "native stop confirmation failed; retry request_stop in this Host" };
|
|
910
|
+
}
|
|
878
911
|
return { state: "cancelled", operation, reason: "confirmation aborted" };
|
|
879
912
|
}
|
|
880
|
-
if (!confirmed) {
|
|
913
|
+
if (!confirmed || ctx.signal?.aborted) {
|
|
881
914
|
if (nextIntent) {
|
|
882
915
|
restoreStagedIntent();
|
|
883
916
|
}
|
|
884
|
-
if (
|
|
917
|
+
if (
|
|
918
|
+
operation !== "stop" &&
|
|
919
|
+
operation !== "approve-breaking-intent-revision" &&
|
|
920
|
+
operation !== "authorize-rework"
|
|
921
|
+
)
|
|
885
922
|
await recordCancelledUserDecision(ctx, taskId, operation, snapshotDigestRef).catch(() => undefined);
|
|
886
923
|
progression.closeInvocation(invocation);
|
|
887
924
|
return { state: "cancelled", operation, reason: "cancelled" };
|
|
@@ -894,7 +931,6 @@ export default function (
|
|
|
894
931
|
progression.closeInvocation(invocation);
|
|
895
932
|
return { state: "blocked", reason: "session changed; confirmation discarded" };
|
|
896
933
|
}
|
|
897
|
-
try {
|
|
898
934
|
// Linearization point: only this fresh affirmative continuation
|
|
899
935
|
// may mint/apply; timeout/cancel already won open -> cancelled.
|
|
900
936
|
try {
|
|
@@ -916,14 +952,16 @@ export default function (
|
|
|
916
952
|
};
|
|
917
953
|
}
|
|
918
954
|
const exactOperation = operation === "stop"
|
|
919
|
-
? { op: "stop" as const, reason: "literal user
|
|
955
|
+
? { op: "stop" as const, reason: "literal user requested task stop" }
|
|
920
956
|
: operation === "approve-breaking-intent-revision"
|
|
921
957
|
? {
|
|
922
958
|
op: "approve_breaking_intent_revision" as const,
|
|
923
959
|
next_intent: nextIntent!,
|
|
924
960
|
next_intent_ref: nextIntentRef!,
|
|
925
961
|
}
|
|
926
|
-
:
|
|
962
|
+
: operation === "authorize-rework"
|
|
963
|
+
? { op: "authorize_rework" as const }
|
|
964
|
+
: userDecisionOperation!;
|
|
927
965
|
// The exact host-built operation is shared by capability digest and
|
|
928
966
|
// application payload; command arguments cannot inject authority fields.
|
|
929
967
|
try {
|
|
@@ -964,9 +1002,11 @@ export default function (
|
|
|
964
1002
|
diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
|
|
965
1003
|
now,
|
|
966
1004
|
})) as unknown as { record: { lifecycle: string; artifact_state: string; intent_ref: { path: string }; intent_snapshot: { scope_hint: string[] } } };
|
|
1005
|
+
if (exactOperation.op === "stop") progression.releaseStoppedReview(taskId);
|
|
967
1006
|
if (
|
|
968
|
-
exactOperation.op === "stop"
|
|
969
|
-
|
|
1007
|
+
exactOperation.op === "stop" ||
|
|
1008
|
+
exactOperation.op === "authorize_rework" ||
|
|
1009
|
+
exactOperation.op === "approve_breaking_intent_revision"
|
|
970
1010
|
) stagePlanningArtifactTransition(ctx.cwd, result.record);
|
|
971
1011
|
return { state: "applied", operation, lifecycle: result.record.lifecycle };
|
|
972
1012
|
} catch (error) {
|
|
@@ -978,6 +1018,13 @@ export default function (
|
|
|
978
1018
|
}
|
|
979
1019
|
} catch (error) {
|
|
980
1020
|
const reason = error instanceof Error ? error.message : String(error);
|
|
1021
|
+
if (operation === "stop") {
|
|
1022
|
+
const terminal = await projectAssuranceState(ctx.cwd, taskId).catch(() => null);
|
|
1023
|
+
if (terminal && !terminal.error && terminal.projection.lifecycle === "stopped") {
|
|
1024
|
+
progression.releaseStoppedReview(taskId);
|
|
1025
|
+
return { state: "applied", operation, lifecycle: "stopped", delivery_error: reason };
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
981
1028
|
notifyOnce(ctx, `authorization-apply:${taskId}:${operation}:${reason}`, `authorize failed: ${reason}`, "error");
|
|
982
1029
|
return { state: "blocked", reason };
|
|
983
1030
|
} finally {
|
|
@@ -999,9 +1046,6 @@ export default function (
|
|
|
999
1046
|
return { state: "blocked", reason: "TaskRecord changed while deriving authorization operation" };
|
|
1000
1047
|
const derived = deriveAuthorizationOperation({
|
|
1001
1048
|
readiness: projection.projection.authorization,
|
|
1002
|
-
hasOpenReplanRequired: read.record.findings.some(
|
|
1003
|
-
(finding) => finding.kind === "replan_required" && finding.status === "open",
|
|
1004
|
-
),
|
|
1005
1049
|
});
|
|
1006
1050
|
if ("blocked" in derived) return { state: "blocked", reason: derived.blocked };
|
|
1007
1051
|
return authorizeExactOperation(taskId, derived.operation, ctx);
|
|
@@ -1014,15 +1058,14 @@ export default function (
|
|
|
1014
1058
|
|
|
1015
1059
|
export type DerivedAuthorizationOperation =
|
|
1016
1060
|
| "resolve-user-decision"
|
|
1017
|
-
| "
|
|
1061
|
+
| "authorize-rework";
|
|
1018
1062
|
|
|
1019
1063
|
// Kernel projection is the sole source of authorization readiness.
|
|
1020
1064
|
export function deriveAuthorizationOperation(input: {
|
|
1021
1065
|
readiness: AssuranceAuthorizationReadiness;
|
|
1022
|
-
hasOpenReplanRequired?: boolean;
|
|
1023
1066
|
}): { operation: DerivedAuthorizationOperation } | { blocked: string } {
|
|
1024
|
-
if (input.hasOpenReplanRequired) return { operation: "stop" };
|
|
1025
1067
|
if (input.readiness.state === "resolve_user_decision") return { operation: "resolve-user-decision" };
|
|
1068
|
+
if (input.readiness.state === "authorize_rework") return { operation: "authorize-rework" };
|
|
1026
1069
|
if (input.readiness.blocked) return { blocked: input.readiness.blocked };
|
|
1027
1070
|
return { blocked: "no unique host-derived authorization operation" };
|
|
1028
1071
|
}
|
|
@@ -1196,7 +1239,7 @@ async function projectAssuranceState(root: string, taskId: string): Promise<Assu
|
|
|
1196
1239
|
* Publish and prove the task-scoped synthetic revision for a v4 record. v3
|
|
1197
1240
|
* records keep the legacy full-source bundle and return null here.
|
|
1198
1241
|
*/
|
|
1199
|
-
async function ensureTaskReviewRevision(
|
|
1242
|
+
export async function ensureTaskReviewRevision(
|
|
1200
1243
|
root: string,
|
|
1201
1244
|
taskId: string,
|
|
1202
1245
|
projection: AssuranceProjectionResult,
|
|
@@ -1221,7 +1264,11 @@ async function ensureTaskReviewRevision(
|
|
|
1221
1264
|
lifecycle: projection.projection.lifecycle,
|
|
1222
1265
|
artifactState: projection.projection.artifact_state,
|
|
1223
1266
|
risk: record.intent_snapshot.risk,
|
|
1224
|
-
outcomes
|
|
1267
|
+
// The same outcomes the Review snapshot is built from. A preflight stand-in
|
|
1268
|
+
// only matched the settled QA attestation because deterministic QA happens to
|
|
1269
|
+
// write that exact summary, so the submit-time digest comparison held by
|
|
1270
|
+
// coincidence rather than by construction.
|
|
1271
|
+
outcomes: qaOutcomes(record),
|
|
1225
1272
|
});
|
|
1226
1273
|
return {
|
|
1227
1274
|
contract: "assurance_kernel/review_revision/v1",
|
|
@@ -1266,8 +1313,22 @@ async function reconcileReviewRevisionRefs(root: string): Promise<{ removed: str
|
|
|
1266
1313
|
return reconcileReviewRefs(root, live);
|
|
1267
1314
|
}
|
|
1268
1315
|
|
|
1316
|
+
/**
|
|
1317
|
+
* The coordinator port supplies a `HostContext`, which carries no UI. Pi hands
|
|
1318
|
+
* its full `ExtensionContext` through at runtime, so the notice still reaches
|
|
1319
|
+
* the user; a host that does not is left un-notified rather than throwing from
|
|
1320
|
+
* inside an authority commit, where a notification has no authority anyway.
|
|
1321
|
+
*/
|
|
1322
|
+
function notifyHost(ctx: HostContext, key: string, message: string, level: "warning" | "error"): void {
|
|
1323
|
+
const ui = (ctx as Partial<UiContext>).ui;
|
|
1324
|
+
if (ui) notifyOnce({ ui }, key, message, level);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1269
1327
|
async function applyAssuranceVerdict(
|
|
1270
|
-
|
|
1328
|
+
// The coordinator port hands these a `HostContext`, not the Pi
|
|
1329
|
+
// `ExtensionContext`. Both functions only ever read `cwd`; declaring the
|
|
1330
|
+
// wider host type made the port assignment unsound.
|
|
1331
|
+
ctx: HostContext,
|
|
1271
1332
|
snapshot: SnapshotDescriptor,
|
|
1272
1333
|
verdict: AssuranceVerdict,
|
|
1273
1334
|
invocation: InvocationToken,
|
|
@@ -1352,7 +1413,7 @@ async function applyAssuranceVerdict(
|
|
|
1352
1413
|
const parked = (result.record as { findings?: Array<{ kind: string; status: string }> }).findings?.some(
|
|
1353
1414
|
(finding) => finding.kind === "replan_required" && finding.status === "open",
|
|
1354
1415
|
);
|
|
1355
|
-
if (parked)
|
|
1416
|
+
if (parked) notifyHost(
|
|
1356
1417
|
ctx,
|
|
1357
1418
|
`rework-parked:${snapshot.task_id}`,
|
|
1358
1419
|
`rework applied: review parked for replan with ${findings.length} finding(s)`,
|
|
@@ -1431,10 +1492,17 @@ async function buildAssuranceSnapshot(
|
|
|
1431
1492
|
assertRunnerCompatible(descriptor, runner);
|
|
1432
1493
|
descriptors.set(item.id, descriptor);
|
|
1433
1494
|
}
|
|
1434
|
-
|
|
1495
|
+
// `git_base_head` is optional on the read shape because v3 records carry
|
|
1496
|
+
// none, so the contract test alone does not prove it is present.
|
|
1497
|
+
const baseHead = record.record.contract === "assurance_kernel/task_record/v4"
|
|
1498
|
+
? record.record.git_base_head
|
|
1499
|
+
: undefined;
|
|
1500
|
+
if (record.record.contract === "assurance_kernel/task_record/v4" && !baseHead)
|
|
1501
|
+
throw new Error("TaskRecord v4 is missing its Enrollment git_base_head");
|
|
1502
|
+
const reviewRevision = baseHead
|
|
1435
1503
|
? {
|
|
1436
1504
|
contract: "assurance_kernel/review_revision_identity/v1" as const,
|
|
1437
|
-
base_head:
|
|
1505
|
+
base_head: baseHead,
|
|
1438
1506
|
review_commit: "",
|
|
1439
1507
|
review_tree: "",
|
|
1440
1508
|
manifest_digest: "",
|
|
@@ -1507,15 +1575,6 @@ async function buildAssuranceSnapshot(
|
|
|
1507
1575
|
};
|
|
1508
1576
|
}
|
|
1509
1577
|
|
|
1510
|
-
function reviewPreflightOutcomes(
|
|
1511
|
-
acceptance: Array<{ id: string }>,
|
|
1512
|
-
): Record<string, { status: "passed"; summary: string }> {
|
|
1513
|
-
const summary = `host-attested QA: all ${acceptance.length} fixed verification descriptor(s) passed`;
|
|
1514
|
-
return Object.fromEntries(
|
|
1515
|
-
acceptance.map((item) => [item.id, { status: "passed" as const, summary }]),
|
|
1516
|
-
);
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
1578
|
function qaOutcomes(
|
|
1520
1579
|
record: NonNullable<TaskRecordRead["record"]>,
|
|
1521
1580
|
): Record<string, { status: "passed" | "failed" | "blocked"; summary: string }> {
|
|
@@ -1617,7 +1676,7 @@ function authorityPair(): Promise<{ registry: MutationAuthorityRegistry; app: Ca
|
|
|
1617
1676
|
}
|
|
1618
1677
|
|
|
1619
1678
|
async function executeOrdinaryOperation(
|
|
1620
|
-
ctx:
|
|
1679
|
+
ctx: HostContext,
|
|
1621
1680
|
input: { taskId: string; operation: { op: string; actor_id: string; next_intent?: unknown } },
|
|
1622
1681
|
): Promise<unknown> {
|
|
1623
1682
|
const { app } = await authorityPair();
|
|
@@ -1638,7 +1697,10 @@ async function executeOrdinaryOperation(
|
|
|
1638
1697
|
now: new Date().toISOString(),
|
|
1639
1698
|
});
|
|
1640
1699
|
if (operation.op === "freeze_artifacts" || operation.op === "stop")
|
|
1641
|
-
stagePlanningArtifactTransition(
|
|
1700
|
+
stagePlanningArtifactTransition(
|
|
1701
|
+
ctx.cwd,
|
|
1702
|
+
(result as { record: Parameters<typeof stagePlanningArtifactTransition>[1] }).record,
|
|
1703
|
+
);
|
|
1642
1704
|
return result;
|
|
1643
1705
|
} catch (error) {
|
|
1644
1706
|
if (priorBytes) {
|
|
@@ -1741,18 +1803,24 @@ function toolResult(text: string, details?: Record<string, unknown>) {
|
|
|
1741
1803
|
* Pi or Hyper adapter upgrade cycles pass a live nested-object Tool-call
|
|
1742
1804
|
* probe at least 30 days apart.
|
|
1743
1805
|
*/
|
|
1744
|
-
|
|
1745
|
-
|
|
1806
|
+
/**
|
|
1807
|
+
* Pre-schema normalizer: some hosts deliver `action` as a JSON string. The Pi
|
|
1808
|
+
* runtime validates the returned value against the Tool schema immediately
|
|
1809
|
+
* after this shim, so the parameter type is the schema's, not a claim this
|
|
1810
|
+
* function makes about unvalidated input.
|
|
1811
|
+
*/
|
|
1812
|
+
function prepareActionArgs<Params>(args: unknown): Params {
|
|
1813
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return args as Params;
|
|
1746
1814
|
const input = args as Record<string, unknown>;
|
|
1747
|
-
if (typeof input.action !== "string") return input;
|
|
1815
|
+
if (typeof input.action !== "string") return input as Params;
|
|
1748
1816
|
try {
|
|
1749
1817
|
const parsed: unknown = JSON.parse(input.action);
|
|
1750
1818
|
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
|
|
1751
|
-
return { ...input, action: parsed };
|
|
1819
|
+
return { ...input, action: parsed } as Params;
|
|
1752
1820
|
} catch {
|
|
1753
1821
|
// Unchanged input keeps the normal host schema error authoritative.
|
|
1754
1822
|
}
|
|
1755
|
-
return input;
|
|
1823
|
+
return input as Params;
|
|
1756
1824
|
}
|
|
1757
1825
|
|
|
1758
1826
|
function stagePlanningArtifactTransition(root: string, record: {
|
|
@@ -1785,7 +1853,9 @@ function stagePlanningArtifactTransition(root: string, record: {
|
|
|
1785
1853
|
function failCanaryTool(
|
|
1786
1854
|
taskId: string,
|
|
1787
1855
|
operation: string,
|
|
1788
|
-
|
|
1856
|
+
// `review_preparation_failed` is a declared ToolFailureV1 state and a
|
|
1857
|
+
// documented Loop recovery path; omitting it here made it unreportable.
|
|
1858
|
+
state: ToolFailureV1["state"],
|
|
1789
1859
|
code: string,
|
|
1790
1860
|
message: string,
|
|
1791
1861
|
nextAction: string,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DynamicBorder, type ExtensionAPI, type ExtensionContext, type Theme } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { DynamicBorder, type ExtensionAPI, type ExtensionContext, type Theme, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Container, SelectList, Text, type Component, type SelectItem } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
export const USER_ATTENTION_EVENT = "immune-brain:user-attention.v1" as const;
|
|
@@ -75,7 +75,7 @@ export interface AuthorityDialogOptions<T extends string> {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
type EventPublisher = Pick<ExtensionAPI, "events">;
|
|
78
|
-
type UiContext = Pick<ExtensionContext, "ui">;
|
|
78
|
+
export type UiContext = Pick<ExtensionContext, "ui">;
|
|
79
79
|
|
|
80
80
|
const terminalRailUis = new WeakSet<object>();
|
|
81
81
|
const deliveredNotifications = new WeakMap<object, Set<string>>();
|
|
@@ -373,7 +373,9 @@ function emitAttention(pi: EventPublisher, event: UserAttentionEventV1): void {
|
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
function formatTaskRailState(state: TaskRailState, theme?: Theme): string {
|
|
376
|
-
|
|
376
|
+
// Typed as the theme's own colour union: a plain `string` here silently
|
|
377
|
+
// accepted a name the theme cannot resolve.
|
|
378
|
+
const symbolAndColor: Record<TaskRailState, { symbol: string; color: ThemeColor }> = {
|
|
377
379
|
Planning: { symbol: "●", color: "muted" },
|
|
378
380
|
"Approval required": { symbol: "▲", color: "accent" },
|
|
379
381
|
Working: { symbol: "●", color: "accent" },
|
|
@@ -383,7 +385,7 @@ function formatTaskRailState(state: TaskRailState, theme?: Theme): string {
|
|
|
383
385
|
Completed: { symbol: "✓", color: "success" },
|
|
384
386
|
Stopped: { symbol: "■", color: "muted" },
|
|
385
387
|
};
|
|
386
|
-
const cfg = symbolAndColor[state] ?? { symbol: "●", color: "dim" };
|
|
388
|
+
const cfg: { symbol: string; color: ThemeColor } = symbolAndColor[state] ?? { symbol: "●", color: "dim" };
|
|
387
389
|
if (!theme) return `${cfg.symbol} ${state}`;
|
|
388
390
|
return `${theme.fg(cfg.color, cfg.symbol)} ${theme.fg(cfg.color, state)}`;
|
|
389
391
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Extension-local runtime adapter: type-isolated, executable stub.
|
|
2
2
|
// Extensions import this file directly (relative path, resolvable by the Pi
|
|
3
3
|
// extension loader); it forwards to the real Kernel modules via dynamic
|
|
4
|
-
// import.
|
|
5
|
-
// type
|
|
4
|
+
// import. Shared contracts are re-exported as types from the real Kernel
|
|
5
|
+
// modules: `export type` is erased at compile time, so the extension still
|
|
6
|
+
// carries no static runtime import, but a Kernel contract change now breaks
|
|
7
|
+
// the extension's build instead of silently drifting from it.
|
|
6
8
|
|
|
7
9
|
// --- Types (structural contracts, no runtime import) ---
|
|
8
10
|
export interface EnrollmentCapabilityBinding {
|
|
@@ -178,40 +180,15 @@ export interface TaskRecordRead {
|
|
|
178
180
|
|
|
179
181
|
// --- Assurance projection (host-neutral Kernel facts, not exported from the
|
|
180
182
|
// public Kernel index) ---
|
|
181
|
-
export
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
diff_hash: string;
|
|
191
|
-
lifecycle: "active" | "done" | "stopped" | "";
|
|
192
|
-
artifact_state: "active" | "frozen" | "";
|
|
193
|
-
risk: "routine" | "material" | "critical" | "";
|
|
194
|
-
next_obligation: "resolve_findings" | "resolve_user_decision" | "revise_intent" | "submit_assurance" | "run_qa" | "run_review" | "complete" | "none";
|
|
195
|
-
fresh_acceptance_ids: string[];
|
|
196
|
-
missing_acceptance_ids: string[];
|
|
197
|
-
stale_attestation_ids: string[];
|
|
198
|
-
fresh_approval_kinds: string[];
|
|
199
|
-
missing_approval_kinds: string[];
|
|
200
|
-
blocking_finding_ids: string[];
|
|
201
|
-
unresolved_user_decision_ids: string[];
|
|
202
|
-
replan_required_ids: string[];
|
|
203
|
-
independence_violations: string[];
|
|
204
|
-
open_user_decision_count: number;
|
|
205
|
-
completion_ready: boolean;
|
|
206
|
-
authorization: AssuranceAuthorizationReadiness;
|
|
207
|
-
}
|
|
208
|
-
export interface AssuranceProjectionResult {
|
|
209
|
-
contract: "assurance_kernel/assurance_projection/v1";
|
|
210
|
-
task_id: string;
|
|
211
|
-
error: string | null;
|
|
212
|
-
claim: { task_id: string; lifecycle_status: string } | null;
|
|
213
|
-
projection: AssuranceProjection;
|
|
214
|
-
}
|
|
183
|
+
export type {
|
|
184
|
+
AssuranceAuthorizationReadiness,
|
|
185
|
+
AssuranceProjection,
|
|
186
|
+
AssuranceProjectionResult,
|
|
187
|
+
} from "../runtime/kernel/assurance_projection";
|
|
188
|
+
import type {
|
|
189
|
+
AssuranceAuthorizationReadiness,
|
|
190
|
+
AssuranceProjectionResult,
|
|
191
|
+
} from "../runtime/kernel/assurance_projection";
|
|
215
192
|
|
|
216
193
|
// --- Runtime forwarding (dynamic import keeps the graph out of tsc) ---
|
|
217
194
|
function kernelPath(module: string): string {
|
|
@@ -349,12 +326,8 @@ export interface TaskIntentV1 {
|
|
|
349
326
|
acceptance: Array<{ id: string; assertion: string; verification: string }>;
|
|
350
327
|
scope_hint: string[];
|
|
351
328
|
}
|
|
352
|
-
export
|
|
353
|
-
|
|
354
|
-
content_hash: string;
|
|
355
|
-
intent: TaskIntentV1;
|
|
356
|
-
intent_ref: { path: string; revision: number; content_hash: string };
|
|
357
|
-
}
|
|
329
|
+
export type { ReadTaskIntentResult as TaskIntentRead } from "../runtime/kernel/intent";
|
|
330
|
+
import type { ReadTaskIntentResult as TaskIntentRead } from "../runtime/kernel/intent";
|
|
358
331
|
export interface WorkspaceRead {
|
|
359
332
|
revision: string;
|
|
360
333
|
state: { contract: string; current_working: string | null };
|
|
@@ -420,7 +393,7 @@ export async function projectAssurance(
|
|
|
420
393
|
},
|
|
421
394
|
): Promise<AssuranceProjectionResult> {
|
|
422
395
|
const mod = await import(/* @vite-ignore */ kernelPath("assurance_projection"));
|
|
423
|
-
return mod.projectAssurance(root, taskId, diffProvider) as
|
|
396
|
+
return mod.projectAssurance(root, taskId, diffProvider) as AssuranceProjectionResult;
|
|
424
397
|
}
|
|
425
398
|
|
|
426
399
|
export async function deriveAssuranceAuthorization(input: {
|
|
@@ -428,5 +401,5 @@ export async function deriveAssuranceAuthorization(input: {
|
|
|
428
401
|
open_user_decision_count: number;
|
|
429
402
|
}): Promise<AssuranceAuthorizationReadiness> {
|
|
430
403
|
const mod = await import(/* @vite-ignore */ kernelPath("assurance_projection"));
|
|
431
|
-
return mod.deriveAssuranceAuthorization(input) as
|
|
404
|
+
return mod.deriveAssuranceAuthorization(input) as AssuranceAuthorizationReadiness;
|
|
432
405
|
}
|