gentle-pi 3.2.1 → 3.3.0
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/docs/gentle-shell.md +27 -14
- package/docs/readme-reference.md +41 -7
- package/docs/review-integration.md +22 -17
- package/extensions/gentle-agents.ts +85 -17
- package/extensions/gentle-ai.ts +123 -21
- package/extensions/gentle-shell.ts +379 -31
- package/extensions/gentle-todo.ts +19 -1
- package/lib/agents-view.ts +41 -14
- package/lib/agents-widget.ts +84 -13
- package/lib/command-palette-catalog.ts +1 -0
- package/lib/double-esc-cancel-policy.ts +138 -0
- package/lib/inprocess-reviewer.ts +260 -0
- package/lib/native-review-cli.ts +14 -0
- package/lib/odd-runtime-delegation-gate.ts +88 -0
- package/lib/review-host-relay.ts +246 -171
- package/lib/review-integration-v2.ts +110 -26
- package/lib/shell-bar.ts +150 -75
- package/lib/shell-card.ts +19 -9
- package/lib/shell-changes-view.ts +43 -5
- package/lib/shell-changes.ts +92 -5
- package/lib/shell-hover.ts +39 -0
- package/lib/shell-prompt.ts +10 -1
- package/lib/shell-sidebar-layout.ts +111 -15
- package/lib/shell-sidebar.ts +16 -0
- package/lib/shell-todo.ts +7 -1
- package/lib/shell-usage-view.ts +98 -10
- package/package.json +1 -1
- package/runtime/native-review-cli.mjs +14 -0
- package/runtime/review-integration-v2.mjs +110 -26
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/maintainer/provider-relay-matrix.mjs +118 -47
- package/scripts/verify-package-files.mjs +3 -3
- package/tests/agents-grouping.test.ts +75 -18
- package/tests/agents-view.test.ts +28 -18
- package/tests/agents-widget.test.ts +100 -12
- package/tests/command-palette.test.ts +1 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +176 -138
- package/tests/double-esc-cancel-policy.test.ts +194 -0
- package/tests/gentle-agents.test.ts +528 -5
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai.test.ts +69 -5
- package/tests/gentle-shell.test.ts +795 -23
- package/tests/gentle-todo.test.ts +17 -4
- package/tests/inprocess-reviewer.test.ts +368 -0
- package/tests/maintainer/provider-relay.maintest.ts +101 -143
- package/tests/native-review-capability-contract.test.ts +19 -1
- package/tests/odd-runtime-delegation-gate.test.ts +212 -0
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/package-manifest.test.ts +6 -6
- package/tests/review-host-relay-routing.test.ts +77 -0
- package/tests/review-host-relay.test.ts +277 -300
- package/tests/review-integration-v2-forward.test.ts +61 -0
- package/tests/review-integration-v2.test.ts +116 -1
- package/tests/review-relay-transport-agent.test.ts +22 -24
- package/tests/runtime-harness.mjs +11 -0
- package/tests/session-changes-shell.test.ts +27 -0
- package/tests/session-worktree-registry.test.ts +41 -0
- package/tests/shell-bar.test.ts +192 -124
- package/tests/shell-card.test.ts +5 -3
- package/tests/shell-changes-view.test.ts +47 -0
- package/tests/shell-changes.test.ts +177 -0
- package/tests/shell-hover.test.ts +19 -0
- package/tests/shell-prompt.test.ts +20 -0
- package/tests/shell-sidebar-fullscreen.test.ts +59 -0
- package/tests/shell-sidebar-layout.test.ts +243 -5
- package/tests/shell-sidebar.test.ts +25 -1
- package/tests/shell-todo.test.ts +36 -0
- package/tests/shell-usage-view.test.ts +120 -1
- package/lib/opaque-pi-reviewer-adapter.ts +0 -404
- package/tests/opaque-pi-reviewer-adapter.test.ts +0 -410
package/lib/shell-usage-view.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
1
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
2
2
|
import { renderUsagePanel, type ActiveProvider, type UsageStore, type UsageTheme } from "./shell-usage.ts";
|
|
3
|
+
import { paintHoverable } from "./shell-hover.ts";
|
|
3
4
|
|
|
4
5
|
// Gentle Shell subscriptions overlay: a framed panel over the usage store.
|
|
5
6
|
// It reads the store on every render, so a refresh only needs to record.
|
|
@@ -33,10 +34,32 @@ function fit(text: string, width: number): string {
|
|
|
33
34
|
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
// Column offset of the footer's hint text within the rendered line: the
|
|
38
|
+
// frame draws "│ " before the fitted content starts.
|
|
39
|
+
const HINT_CONTENT_OFFSET = 2;
|
|
40
|
+
const HINT_GAP = " ";
|
|
41
|
+
|
|
42
|
+
type HintAction = "refresh" | "close";
|
|
43
|
+
|
|
44
|
+
interface HintSpan {
|
|
45
|
+
start: number;
|
|
46
|
+
end: number;
|
|
47
|
+
action: HintAction;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface PointerLayout {
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
row: number;
|
|
54
|
+
spans: HintSpan[];
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
export class UsageView {
|
|
37
58
|
private readonly store: UsageStore;
|
|
38
59
|
private readonly deps: UsageViewDeps;
|
|
39
60
|
private refreshing = false;
|
|
61
|
+
private pointer: PointerLayout | undefined;
|
|
62
|
+
private hoveredHint: HintAction | undefined;
|
|
40
63
|
|
|
41
64
|
constructor(store: UsageStore, deps: UsageViewDeps) {
|
|
42
65
|
this.store = store;
|
|
@@ -48,13 +71,25 @@ export class UsageView {
|
|
|
48
71
|
this.deps.onClose();
|
|
49
72
|
return;
|
|
50
73
|
}
|
|
51
|
-
if (data === "r"
|
|
52
|
-
|
|
74
|
+
if (data === "r") this.refresh();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A failing usage fetch is the store's problem to report (its rows already
|
|
78
|
+
// carry the last error); the panel only clears its "refreshing" state. The
|
|
79
|
+
// rejection must never leave this method: an unhandled rejection is fatal
|
|
80
|
+
// to the whole shell on current Node.
|
|
81
|
+
private refresh(): void {
|
|
82
|
+
if (this.refreshing) return;
|
|
83
|
+
this.refreshing = true;
|
|
84
|
+
this.deps.requestRender();
|
|
85
|
+
const settle = () => {
|
|
86
|
+
this.refreshing = false;
|
|
53
87
|
this.deps.requestRender();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
this.deps.onRefresh().then(settle, settle);
|
|
91
|
+
} catch {
|
|
92
|
+
settle();
|
|
58
93
|
}
|
|
59
94
|
}
|
|
60
95
|
|
|
@@ -66,11 +101,64 @@ export class UsageView {
|
|
|
66
101
|
const body = renderUsagePanel(this.store.all(), theme, inner - 2, this.deps.now(), this.deps.active()).map(
|
|
67
102
|
(line) => `${theme.fg(FRAME_ROLE, "│")} ${fit(line, inner - 2)} ${theme.fg(FRAME_ROLE, "│")}`,
|
|
68
103
|
);
|
|
69
|
-
const
|
|
104
|
+
const hints = KEYS.map(([key, label]) => ({ key, label, text: `${key} ${label}`, action: (key === "r" ? "refresh" : "close") as HintAction }));
|
|
105
|
+
// The hovered hint paints entirely in the shared hover role (key and
|
|
106
|
+
// label together, one color) instead of its ordinary two-role split --
|
|
107
|
+
// the same treatment every other clickable surface uses.
|
|
108
|
+
const keys = hints
|
|
109
|
+
.map(({ key, label, action }) =>
|
|
110
|
+
this.hoveredHint === action ? paintHoverable(theme, `${key} ${label}`, true) : `${theme.fg(KEY_ROLE, key)} ${theme.fg(KEY_TEXT_ROLE, label)}`,
|
|
111
|
+
)
|
|
112
|
+
.join(HINT_GAP);
|
|
70
113
|
const keysLine = `${theme.fg(FRAME_ROLE, "│")} ${fit(keys, inner - 2)} ${theme.fg(FRAME_ROLE, "│")}`;
|
|
71
114
|
const bottom = theme.fg(FRAME_ROLE, `╰${rule(inner)}╯`);
|
|
72
|
-
|
|
115
|
+
const lines = [top, ...body, keysLine, bottom];
|
|
116
|
+
this.pointer = this.hintLayout(width, lines.length, body.length + 1, hints, inner - 2);
|
|
117
|
+
return lines;
|
|
73
118
|
}
|
|
74
119
|
|
|
75
|
-
|
|
120
|
+
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
121
|
+
if (event.type === "move" && event.button === "none") {
|
|
122
|
+
const layout = this.pointer;
|
|
123
|
+
const action = layout && event.width === layout.width && event.height === layout.height && event.y === layout.row
|
|
124
|
+
? layout.spans.find((candidate) => event.x >= candidate.start && event.x < candidate.end)?.action
|
|
125
|
+
: undefined;
|
|
126
|
+
if (action === this.hoveredHint) return action ? { handled: true } : undefined;
|
|
127
|
+
this.hoveredHint = action;
|
|
128
|
+
return { handled: true, render: true };
|
|
129
|
+
}
|
|
130
|
+
if (event.type !== "click" || event.button !== "left") return undefined;
|
|
131
|
+
const layout = this.pointer;
|
|
132
|
+
if (!layout || event.width !== layout.width || event.height !== layout.height || event.y !== layout.row) return undefined;
|
|
133
|
+
const span = layout.spans.find((candidate) => event.x >= candidate.start && event.x < candidate.end);
|
|
134
|
+
if (!span) return undefined;
|
|
135
|
+
if (span.action === "close") {
|
|
136
|
+
this.deps.onClose();
|
|
137
|
+
return { handled: true, render: true };
|
|
138
|
+
}
|
|
139
|
+
this.refresh();
|
|
140
|
+
return { handled: true, render: true };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
invalidate(): void {
|
|
144
|
+
this.pointer = undefined;
|
|
145
|
+
this.hoveredHint = undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Spans are only registered when the hints text fits without truncation:
|
|
149
|
+
// past that point `fit` clips it with an ellipsis and per-hint columns no
|
|
150
|
+
// longer line up with the plain "key label" text used here.
|
|
151
|
+
private hintLayout(width: number, height: number, row: number, hints: Array<{ text: string; action: HintAction }>, contentWidth: number): PointerLayout | undefined {
|
|
152
|
+
const plainWidth = hints.reduce((total, hint) => total + hint.text.length, 0) + HINT_GAP.length * Math.max(0, hints.length - 1);
|
|
153
|
+
if (plainWidth > contentWidth) return undefined;
|
|
154
|
+
const spans: HintSpan[] = [];
|
|
155
|
+
let cursor = HINT_CONTENT_OFFSET;
|
|
156
|
+
for (const hint of hints) {
|
|
157
|
+
const start = cursor;
|
|
158
|
+
const end = start + hint.text.length;
|
|
159
|
+
spans.push({ start, end, action: hint.action });
|
|
160
|
+
cursor = end + HINT_GAP.length;
|
|
161
|
+
}
|
|
162
|
+
return { width, height, row, spans };
|
|
163
|
+
}
|
|
76
164
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -1005,6 +1005,20 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
1005
1005
|
// remain dark because neither is proven to reach the negotiated START
|
|
1006
1006
|
// path Pi consumes.
|
|
1007
1007
|
"3.2.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
1008
|
+
// v3.4.0 (gentle-pi never pinned the intervening v3.3.0 tag, so it gets no
|
|
1009
|
+
// row here) added capabilities/v2.6 and status/v8-v9, and extended
|
|
1010
|
+
// `review assess` with review_due/review_due_reason/next_transition
|
|
1011
|
+
// (gentle-ai #4714 follow-up). Ground-truthed by diffing
|
|
1012
|
+
// contracts/review-integration/v2 and contracts/review-provider-contract
|
|
1013
|
+
// between the v3.2.1 and v3.4.0 tags in the gentle-ai source tree: the
|
|
1014
|
+
// provider contract stays byte-identical at 1.2.0, and every
|
|
1015
|
+
// review-integration/v2 change is an additive superset (new optional
|
|
1016
|
+
// schema/fields) that decodeReviewStatusV3 and the capabilities
|
|
1017
|
+
// negotiator already accept without touching the closed START/STATUS
|
|
1018
|
+
// fields this row negotiates, so it repeats 3.2.1 exactly. riskEvidence
|
|
1019
|
+
// and hint remain dark because neither is proven to reach the negotiated
|
|
1020
|
+
// START path Pi consumes.
|
|
1021
|
+
"3.4.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
1008
1022
|
});
|
|
1009
1023
|
|
|
1010
1024
|
|
|
@@ -200,6 +200,22 @@ const CAPABILITIES_SCHEMA_IDENTITIES
|
|
|
200
200
|
requiredMandatoryFeatures: REQUIRED_MANDATORY_FEATURES_V23,
|
|
201
201
|
optionalFeatureFloor: 14,
|
|
202
202
|
}),
|
|
203
|
+
// Ground-truthed against the published v3.4.0 binary: capabilities/v2.6
|
|
204
|
+
// advertises the same required surface as v2.5 (status/v6 is still
|
|
205
|
+
// advertised for compatibility) plus the status/v7 and status/v8 schemas,
|
|
206
|
+
// which are superset-checked additions, not requirements --
|
|
207
|
+
// decodeReviewStatusV3 already accepts v7/v8/v9 as additive extensions of
|
|
208
|
+
// v6, so the required-schema floor stays unchanged. `review assess` also
|
|
209
|
+
// gained review_due/review_due_reason/next_transition, which is unrelated
|
|
210
|
+
// to this negotiated capabilities surface. The v3.4.0 binary advertised
|
|
211
|
+
// 15 optional features, same as v2.5's binary (floor stays at 14, its
|
|
212
|
+
// established minimum).
|
|
213
|
+
"gentle-ai.review-integration.capabilities/v2.6": Object.freeze({
|
|
214
|
+
protocolMinor: 6,
|
|
215
|
+
requiredSchemas: Object.freeze([...REQUIRED_SCHEMAS_COMMON_V23, "gentle-ai.review-integration.capabilities/v2.6", "gentle-ai.review-integration.consent/v3", "gentle-ai.review-integration.start/v4", "gentle-ai.review-integration.status/v6", "gentle-ai.review-intended-untracked-selection/v1"]),
|
|
216
|
+
requiredMandatoryFeatures: REQUIRED_MANDATORY_FEATURES_V23,
|
|
217
|
+
optionalFeatureFloor: 14,
|
|
218
|
+
}),
|
|
203
219
|
});
|
|
204
220
|
const OPTIONAL_FEATURE_NAMES = Object.freeze([
|
|
205
221
|
"base_ref_workspace_overlay",
|
|
@@ -439,11 +455,21 @@ const REPOSITORY_CONTEXT_OUTCOMES = ["applied", "pending", "blocked_conflict", "
|
|
|
439
455
|
|
|
440
456
|
|
|
441
457
|
|
|
442
|
-
// The two
|
|
443
|
-
//
|
|
444
|
-
// self-contained authority-advancing
|
|
445
|
-
// `--agent=pi --execute=true`, with NO submission descriptor
|
|
446
|
-
//
|
|
458
|
+
// The two non-lens provider role capture operations (gentle-pi#311 P4-roles;
|
|
459
|
+
// provider side gentle-ai#3264). An older gentle-ai still renders each one as
|
|
460
|
+
// a self-contained authority-advancing vector: binding tokens plus
|
|
461
|
+
// `--agent=pi --execute=true`, with NO submission descriptor — executing the
|
|
462
|
+
// exact rendered tokens makes Go materialize the role prompt, run its own
|
|
463
|
+
// locked-down pi subprocess, and admit the verdict itself.
|
|
464
|
+
//
|
|
465
|
+
// gentle-ai's v9 contract makes both roles host-mediated exactly like a lens
|
|
466
|
+
// materialize slot instead (gentle-pi#311 P3): the collect input carries
|
|
467
|
+
// `--materialize=true` (never alongside `--execute`) plus a provider-owned
|
|
468
|
+
// `submission` descriptor, and the host completes the frozen prompt
|
|
469
|
+
// in-process and submits the result through that exact descriptor. The two
|
|
470
|
+
// wire forms are mutually exclusive on one input; see the gating in
|
|
471
|
+
// decodeCollectInput below. The known operation set is closed — an unknown
|
|
472
|
+
// role capture operation is never executed either way.
|
|
447
473
|
export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION = {
|
|
448
474
|
CAPTURE_REFUTER: "review.capture-refuter",
|
|
449
475
|
CAPTURE_VALIDATION: "review.capture-validation",
|
|
@@ -1452,11 +1478,18 @@ function decodeCaptureSubmission(value , label , v5 , v6
|
|
|
1452
1478
|
// one-entry values array the host relay already consumes. A payload
|
|
1453
1479
|
// carrying both wire forms at once matches no captured shape and falls
|
|
1454
1480
|
// through to the legacy decoder, which rejects the unknown `value` key.
|
|
1481
|
+
//
|
|
1482
|
+
// gentle-ai's v9 contract renders the same singular-value shape for the
|
|
1483
|
+
// two host-mediated provider role operations (capture-refuter,
|
|
1484
|
+
// capture-validation): they carry a `schema` key exactly like
|
|
1485
|
+
// capture-result, since both bind one artifact-path-or-stdin value, never
|
|
1486
|
+
// the correction-plan's numeric bounds (gentle-pi#311 P3).
|
|
1455
1487
|
if (v5 && typeof value === "object" && value !== null && "value" in value && !("values" in value)) {
|
|
1456
1488
|
const submission = exactRecord(value, label, ["operation_token", "argument_tokens", "value"]);
|
|
1457
|
-
const operationToken = enumeration(submission.operation_token, ["capture-result", "capture-correction-plan"] , `${label}.operation_token`);
|
|
1489
|
+
const operationToken = enumeration(submission.operation_token, ["capture-result", "capture-correction-plan", "capture-refuter", "capture-validation"] , `${label}.operation_token`);
|
|
1458
1490
|
const argumentTokens = stringArray(submission.argument_tokens, `${label}.argument_tokens`, { minimum: 1 });
|
|
1459
|
-
const
|
|
1491
|
+
const bindsArtifactSchema = operationToken === "capture-result" || operationToken === "capture-refuter" || operationToken === "capture-validation";
|
|
1492
|
+
const row = bindsArtifactSchema
|
|
1460
1493
|
? exactRecord(submission.value, `${label}.value`, ["slot", "domain", "schema", "substitution_location"])
|
|
1461
1494
|
: exactRecord(submission.value, `${label}.value`, ["slot", "domain", "minimum", "maximum", "substitution_location"]);
|
|
1462
1495
|
return {
|
|
@@ -1465,7 +1498,11 @@ function decodeCaptureSubmission(value , label , v5 , v6
|
|
|
1465
1498
|
values: [{
|
|
1466
1499
|
slot: operationToken === "capture-result"
|
|
1467
1500
|
? enumeration(row.slot, ["reviewer_result"] , `${label}.value.slot`)
|
|
1468
|
-
:
|
|
1501
|
+
: operationToken === "capture-correction-plan"
|
|
1502
|
+
? enumeration(row.slot, ["correction_lines"] , `${label}.value.slot`)
|
|
1503
|
+
: operationToken === "capture-refuter"
|
|
1504
|
+
? enumeration(row.slot, ["provider_refuter"] , `${label}.value.slot`)
|
|
1505
|
+
: enumeration(row.slot, ["provider_targeted_validator"] , `${label}.value.slot`),
|
|
1469
1506
|
domain: nonempty(row.domain, `${label}.value.domain`),
|
|
1470
1507
|
...(row.schema === undefined ? {} : { schema: nonempty(row.schema, `${label}.value.schema`) }),
|
|
1471
1508
|
...(row.minimum === undefined ? {} : { minimum: integer(row.minimum, `${label}.value.minimum`, 1, 200) }),
|
|
@@ -1606,7 +1643,7 @@ const CORRECTION_REQUEST_REASON_CODES = Object.freeze(["correction_plan_required
|
|
|
1606
1643
|
// v5 capture operations that must carry a submission descriptor.
|
|
1607
1644
|
const V5_SUBMISSION_CAPTURE_OPERATIONS = Object.freeze(["review.capture-correction-plan"] );
|
|
1608
1645
|
|
|
1609
|
-
function decodeCollectInput(value , label , v5 , v6 ) {
|
|
1646
|
+
function decodeCollectInput(value , label , v5 , v6 , v9 ) {
|
|
1610
1647
|
const input = exactRecord(value, label, ["name", "schema", "capture_operation", "arguments"], ["artifact_subject", "base_tree", "candidate_tree", "changed_path_manifest", "submission", ...(v5 ? ["provider_task", "validation_request"] : [])]);
|
|
1611
1648
|
const name = text(input.name, `${label}.name`, { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
1612
1649
|
const schema = nonempty(input.schema, `${label}.schema`);
|
|
@@ -1657,13 +1694,18 @@ function decodeCollectInput(value , label , v5 , v6
|
|
|
1657
1694
|
sha256(argumentsList[5] .value, `${label}.arguments[5].value`);
|
|
1658
1695
|
}
|
|
1659
1696
|
|
|
1660
|
-
// gentle-pi#311 P4-roles: the two
|
|
1661
|
-
// operations render
|
|
1697
|
+
// gentle-pi#311 P4-roles / P3: the two non-lens provider role capture
|
|
1698
|
+
// operations render either wire form on one input, never both at once.
|
|
1699
|
+
// An older gentle-ai renders a SELF-CONTAINED authority-advancing vector
|
|
1700
|
+
// (binding tokens + --agent=pi --execute=true, no submission): executing
|
|
1662
1701
|
// the exact rendered tokens makes Go materialize the role prompt, run its
|
|
1663
|
-
// own locked-down pi subprocess, and admit the raw verdict
|
|
1664
|
-
//
|
|
1665
|
-
//
|
|
1666
|
-
//
|
|
1702
|
+
// own locked-down pi subprocess, and admit the raw verdict. gentle-ai's
|
|
1703
|
+
// v9 contract instead renders a HOST-MEDIATED form: the same binding
|
|
1704
|
+
// tokens plus --materialize=true (never --execute) and a provider-owned
|
|
1705
|
+
// submission descriptor, exactly like a lens capture-result materialize
|
|
1706
|
+
// slot — the host completes the frozen prompt in-process and submits
|
|
1707
|
+
// through that descriptor.
|
|
1708
|
+
const isRoleCaptureOperation = (REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS ).includes(captureOperation);
|
|
1667
1709
|
if (captureOperation === REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION.CAPTURE_REFUTER && schema !== "https://gentle-ai.dev/schema/review/refuter/v1") {
|
|
1668
1710
|
throw new TypeError(`${label}.schema must be https://gentle-ai.dev/schema/review/refuter/v1`);
|
|
1669
1711
|
}
|
|
@@ -1694,8 +1736,30 @@ function decodeCollectInput(value , label , v5 , v6
|
|
|
1694
1736
|
if (providerArgument("target") !== validationRequest.correctionTargetIdentity) throw new TypeError(`${label}.arguments target must bind validation_request.correction_target_identity`);
|
|
1695
1737
|
if (providerArgument("request-hash") !== validationRequest.requestHash) throw new TypeError(`${label}.arguments request-hash must bind validation_request.request_hash`);
|
|
1696
1738
|
}
|
|
1697
|
-
if (input.submission !== undefined
|
|
1698
|
-
|
|
1739
|
+
if (isRoleCaptureOperation && input.submission !== undefined) {
|
|
1740
|
+
// A submission descriptor only ever rides the v9 host-mediated form
|
|
1741
|
+
// (gentle-ai's v9 provider contract; the sibling branch ahead of the
|
|
1742
|
+
// currently-released v8). A v8-or-earlier provider (status/v3..v8) never
|
|
1743
|
+
// renders one, even if it happened to carry a submission-shaped payload,
|
|
1744
|
+
// so this is gated on v9 specifically — not on v5, which v8 already
|
|
1745
|
+
// satisfies. Within a v9 payload the discriminator is exactly the
|
|
1746
|
+
// materialize slot's --materialize=true with no --execute, the same one
|
|
1747
|
+
// a lens capture-result materialize slot already uses. Mixing a
|
|
1748
|
+
// submission with the old self-contained --execute=true vector, or with
|
|
1749
|
+
// neither flag, is a malformed vector.
|
|
1750
|
+
if (!v9) {
|
|
1751
|
+
throw new TypeError(`${label}.submission requires the v9 provider contract for the host-mediated ${captureOperation} form`);
|
|
1752
|
+
}
|
|
1753
|
+
const argumentNamed = (argumentName ) => {
|
|
1754
|
+
const matches = argumentsList.filter((argument) => argument.name === argumentName);
|
|
1755
|
+
return matches.length === 1 ? matches[0] .value : undefined;
|
|
1756
|
+
};
|
|
1757
|
+
if (argumentNamed("materialize") !== "true") {
|
|
1758
|
+
throw new TypeError(`${label}.submission requires --materialize=true on the host-mediated ${captureOperation} form`);
|
|
1759
|
+
}
|
|
1760
|
+
if (argumentNamed("execute") !== undefined) {
|
|
1761
|
+
throw new TypeError(`${label} must not carry --execute alongside a submission descriptor on ${captureOperation}`);
|
|
1762
|
+
}
|
|
1699
1763
|
}
|
|
1700
1764
|
|
|
1701
1765
|
if (captureOperation === "review.capture-result") {
|
|
@@ -1712,7 +1776,12 @@ function decodeCollectInput(value , label , v5 , v6
|
|
|
1712
1776
|
const submissionOperations = v5
|
|
1713
1777
|
? ["review.capture-result", "review.capture-correction-plan", ...(v6 ? ["external.select_intended_untracked"] : [])]
|
|
1714
1778
|
: ["review.capture-result"];
|
|
1715
|
-
|
|
1779
|
+
// Role operations are gated above by the materialize/execute discriminator
|
|
1780
|
+
// instead of this allowlist: an older gentle-ai's self-contained vector
|
|
1781
|
+
// legitimately carries no submission regardless of v5/v6, so they cannot
|
|
1782
|
+
// simply join the list the way every other submission-carrying operation
|
|
1783
|
+
// does.
|
|
1784
|
+
if (input.submission !== undefined && !isRoleCaptureOperation && !submissionOperations.includes(captureOperation)) {
|
|
1716
1785
|
throw new TypeError(v5 ? `${label}.submission is only valid for ${submissionOperations.join(", ")}` : `${label}.submission is only valid for review.capture-result`);
|
|
1717
1786
|
}
|
|
1718
1787
|
if (intendedUntracked && input.submission === undefined) throw new TypeError(`${label}.submission is required for external.select_intended_untracked`);
|
|
@@ -1750,8 +1819,14 @@ export function decodeReviewManagedAssetsContinuationV1(value , label
|
|
|
1750
1819
|
return { operation, command, ...(agent === undefined ? {} : { agent }), ...(staleAssets === undefined ? {} : { staleAssets }) };
|
|
1751
1820
|
}
|
|
1752
1821
|
|
|
1753
|
-
export function decodeReviewNextTransitionV3(value , options
|
|
1754
|
-
|
|
1822
|
+
export function decodeReviewNextTransitionV3(value , options = {}) {
|
|
1823
|
+
// v9 (gentle-ai's next contract, ahead of the currently-released v8) is
|
|
1824
|
+
// the only rung this sub-decoder distinguishes beyond v6: v7 and v8 add
|
|
1825
|
+
// nothing to next_transition/collect-input decoding (v8 only extended the
|
|
1826
|
+
// reviewer-result transition for OpenCode provider tasks at the top
|
|
1827
|
+
// status level), so they decode on the exact v6 surface here.
|
|
1828
|
+
const v9 = options.v9 === true;
|
|
1829
|
+
const v6 = options.v6 === true || v9;
|
|
1755
1830
|
const v5 = options.v5 === true || v6;
|
|
1756
1831
|
const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : []), "continuation", "unachievable_lens_slots"]);
|
|
1757
1832
|
const kind = enumeration(transition.kind, ["execute", "collect", "stop"] , "next_transition.kind");
|
|
@@ -1813,7 +1888,7 @@ export function decodeReviewNextTransitionV3(value , options
|
|
|
1813
1888
|
}
|
|
1814
1889
|
if (kind === "collect") {
|
|
1815
1890
|
const collect = exactRecord(transition.collect, "next_transition.collect", ["inputs"]);
|
|
1816
|
-
const inputs = array(collect.inputs, "next_transition.collect.inputs", (entry, label) => decodeCollectInput(entry, label, v5, v6), { minimum: 1 });
|
|
1891
|
+
const inputs = array(collect.inputs, "next_transition.collect.inputs", (entry, label) => decodeCollectInput(entry, label, v5, v6, v9), { minimum: 1 });
|
|
1817
1892
|
if (transition.execute !== undefined) throw new TypeError("next_transition.execute is incompatible with collect");
|
|
1818
1893
|
return { kind, reasonCode, collect: { inputs }, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
|
|
1819
1894
|
}
|
|
@@ -1926,16 +2001,25 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1926
2001
|
// surfaces. status/v6 adds the intended-untracked selection; status/v7
|
|
1927
2002
|
// (gentle-ai v2.6.0, advertised through capabilities/v2.5 alongside v6)
|
|
1928
2003
|
// adds optional `eligible_untracked_inventory` and escalation metadata,
|
|
1929
|
-
// so it is decoded on the v6 surface.
|
|
1930
|
-
//
|
|
2004
|
+
// so it is decoded on the v6 surface. status/v8 (gentle-ai main, PR #4765;
|
|
2005
|
+
// the current released contract) only extended the reviewer-result
|
|
2006
|
+
// transition for OpenCode provider tasks -- no new top-level key -- so it
|
|
2007
|
+
// decodes on the exact v7 surface too. status/v9 (the sibling gentle-ai
|
|
2008
|
+
// branch's contract, ahead of the released v8) adds nothing at this top
|
|
2009
|
+
// level either: its one addition is the host-mediated role `submission`
|
|
2010
|
+
// on a next_transition.collect input (gentle-pi#311 P3), gated on v9 in
|
|
2011
|
+
// decodeCollectInput, not here. v3 keeps rejecting every
|
|
2012
|
+
// v5/v6/v7/v8/v9-only field.
|
|
1931
2013
|
const schema = typeof value === "object" && value !== null ? (value ).schema : undefined;
|
|
1932
|
-
const
|
|
2014
|
+
const v9 = schema === "gentle-ai.review-integration.status/v9";
|
|
2015
|
+
const v8 = v9 || schema === "gentle-ai.review-integration.status/v8";
|
|
2016
|
+
const v7 = v8 || schema === "gentle-ai.review-integration.status/v7";
|
|
1933
2017
|
const v6 = v7 || schema === "gentle-ai.review-integration.status/v6";
|
|
1934
2018
|
const v5 = v6 || schema === "gentle-ai.review-integration.status/v5";
|
|
1935
2019
|
const body = exactRecord(value, "status", [
|
|
1936
2020
|
"schema", "contract", "operation", "applicability", "action", "replayability", "target_identity", "projection", "repair", "candidates",
|
|
1937
2021
|
], ["authority", "frozen", "action_disposition", "eligibility", "next_transition", "authority_target_identity", ...(v5 ? ["receipt", "forecast", "repository_context", "validation_request"] : []), ...(v7 ? ["eligible_untracked_inventory", "escalation"] : [])]);
|
|
1938
|
-
requireIdentity(body, v7 ? "gentle-ai.review-integration.status/v7" : v6 ? "gentle-ai.review-integration.status/v6" : v5 ? "gentle-ai.review-integration.status/v5" : "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
2022
|
+
requireIdentity(body, v9 ? "gentle-ai.review-integration.status/v9" : v8 ? "gentle-ai.review-integration.status/v8" : v7 ? "gentle-ai.review-integration.status/v7" : v6 ? "gentle-ai.review-integration.status/v6" : v5 ? "gentle-ai.review-integration.status/v5" : "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
1939
2023
|
|
|
1940
2024
|
const applicability = enumeration(body.applicability, ["current_target", "unrelated", "ambiguous", "corrupted"] , "status.applicability");
|
|
1941
2025
|
let receipt ;
|
|
@@ -1983,7 +2067,7 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1983
2067
|
if (action === "recover" && actionDisposition === undefined) throw new TypeError("recover status requires action_disposition");
|
|
1984
2068
|
if (action !== "recover" && actionDisposition !== undefined) throw new TypeError("status.action_disposition is only valid for the recover action");
|
|
1985
2069
|
if (body.eligibility !== undefined) decodeEligibility(body.eligibility, "status.eligibility");
|
|
1986
|
-
const nextTransition = body.next_transition === undefined ? undefined : decodeReviewNextTransitionV3(body.next_transition, { v5, v6 });
|
|
2070
|
+
const nextTransition = body.next_transition === undefined ? undefined : decodeReviewNextTransitionV3(body.next_transition, { v5, v6, v9 });
|
|
1987
2071
|
const validationRequest = v5 && body.validation_request !== undefined
|
|
1988
2072
|
? decodeReviewTargetedValidationRequestV1(body.validation_request, "status.validation_request")
|
|
1989
2073
|
: undefined;
|
|
@@ -36,7 +36,7 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
|
|
|
36
36
|
// version check below) derives from this constant instead of repeating the
|
|
37
37
|
// literal, so a pin bump cannot leave a stale copy behind. See
|
|
38
38
|
// scripts/install-gentle-ai.mjs for the incident that motivated this.
|
|
39
|
-
export const INSTALLER_VERSION = "3.
|
|
39
|
+
export const INSTALLER_VERSION = "3.4.0";
|
|
40
40
|
export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
|
|
41
41
|
export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
42
42
|
SIGNED_RELEASE_ASSET: "signed-release-asset",
|
|
@@ -45,10 +45,10 @@ export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
|
45
45
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai";
|
|
46
46
|
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v3";
|
|
47
47
|
export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
|
|
48
|
-
// `go mod download -json github.com/gentleman-programming/gentle-ai/v3@v3.
|
|
48
|
+
// `go mod download -json github.com/gentleman-programming/gentle-ai/v3@v3.4.0`
|
|
49
49
|
// with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
|
|
50
|
-
// tag resolves to commit
|
|
51
|
-
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
50
|
+
// tag resolves to commit 82a6de96, the published v3.4.0 release head.
|
|
51
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:bQOM+Xa3WN5+idTagEk15rgEEQQWVhZvq8BPF/FHtSs=";
|
|
52
52
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
|
|
53
53
|
export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
|
|
54
54
|
export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
|
|
@@ -67,7 +67,7 @@ export class GentleAiInstallerError extends Error {
|
|
|
67
67
|
// Sentinel used while a re-pinned gentle-ai release is not yet published. A
|
|
68
68
|
// sentinel digest can never match a real SHA-256, so installation fails closed,
|
|
69
69
|
// and verify-package-files.mjs refuses to pack/publish while any digest below
|
|
70
|
-
// still holds it. The v3.
|
|
70
|
+
// still holds it. The v3.4.0 digests are pinned from the published release:
|
|
71
71
|
// archive sha256 values verified against the minisign-signed checksums.txt and
|
|
72
72
|
// freshly computed hashes; binary sha256 values computed from the extracted
|
|
73
73
|
// executables.
|
|
@@ -109,15 +109,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Windows is absent from signed release archives on purpose. gentle-ai stopped
|
|
112
|
-
// distributing unsigned Windows builds in c4b764d0, so v3.
|
|
112
|
+
// distributing unsigned Windows builds in c4b764d0, so v3.4.0 publishes signed
|
|
113
113
|
// Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
|
|
114
114
|
// exact-tag Go SumDB source-build path below; restore archive rows only when
|
|
115
115
|
// upstream ships signed Windows assets.
|
|
116
116
|
export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
|
|
117
|
-
"darwin/amd64": asset("gentle-ai_3.
|
|
118
|
-
"darwin/arm64": asset("gentle-ai_3.
|
|
119
|
-
"linux/amd64": asset("gentle-ai_3.
|
|
120
|
-
"linux/arm64": asset("gentle-ai_3.
|
|
117
|
+
"darwin/amd64": asset("gentle-ai_3.4.0_darwin_amd64.tar.gz", "7d13fa45489098ff9e82dee2232204f72bf1c04ef998ceadfe1ea972697f2e56", "d9096b4bb13e56aae1251dfacd2487821a15c40b7c9397555ad615ede47e658a", "gentle-ai"),
|
|
118
|
+
"darwin/arm64": asset("gentle-ai_3.4.0_darwin_arm64.tar.gz", "b9052d8dc02923663d4451a4b031c4ef7328cd866c0cb6729e8ff2b9853603f0", "0aca1239cafd87ce63b194ab69da90e762801d82a96418fd6b7b12ea3806dc07", "gentle-ai"),
|
|
119
|
+
"linux/amd64": asset("gentle-ai_3.4.0_linux_amd64.tar.gz", "c287289a514420381e890991bb3fbea4a2c36d7b4b1774fcf6bc4deb83378915", "309d9aafb48de5ef90ba0a212e8a98e8fdbb82d0852e24015e36a5ce0fe04c06", "gentle-ai"),
|
|
120
|
+
"linux/arm64": asset("gentle-ai_3.4.0_linux_arm64.tar.gz", "dfd1fe70acfe577d2a36e3a658895f481467518a2923b7200403dbcca0181399", "83ba7d27d250019115690be51664bd356e09085da8858761e362e6053c6290d3", "gentle-ai"),
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
// A pinned asset is either a signed archive or, for a prerelease pin only,
|