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/extensions/gentle-ai.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { consumeReviewMutation, pendingReviewMutation, recordReviewMutation } from "../lib/review-reminder-receipt.ts";
|
|
2
2
|
import { resolveSessionWorktree } from "../lib/session-worktree-registry.ts";
|
|
3
|
+
import { OddRuntimeDelegationGate } from "../lib/odd-runtime-delegation-gate.ts";
|
|
3
4
|
import { resolveResearchCapabilities, renderResearchCapabilities } from "../lib/sdd-research-capabilities.ts";
|
|
4
5
|
import { declareReviewRelayHandshake } from "../lib/review-relay-contract.ts";
|
|
5
6
|
import { execFileSync } from "node:child_process";
|
|
@@ -116,6 +117,7 @@ import {
|
|
|
116
117
|
readOrchestratorSettings,
|
|
117
118
|
restoreOrchestratorSettings,
|
|
118
119
|
type OrchestratorSettingsReadResult,
|
|
120
|
+
parseOrchestratorModelRef,
|
|
119
121
|
} from "../lib/profiles-orchestrator.ts";
|
|
120
122
|
import { measureAgentsViewLayout, type AgentsViewLayout } from "../lib/agents-view-layout.ts";
|
|
121
123
|
import { NativeChoiceList } from "../lib/native-choice-list.ts";
|
|
@@ -132,11 +134,11 @@ import {
|
|
|
132
134
|
REVIEW_HOST_RELAY_SUBMISSION_MISSING_MESSAGE,
|
|
133
135
|
REVIEW_HOST_RELAY_UNAVAILABLE_MESSAGE,
|
|
134
136
|
ReviewHostRelayError,
|
|
137
|
+
reviewHostMediatedRoleSlots,
|
|
135
138
|
reviewHostRelaySlots,
|
|
136
139
|
reviewHostRelayUnachievableDetail,
|
|
137
140
|
reviewHostRelayUnachievableReason,
|
|
138
141
|
reviewProviderRoleVectorSlots,
|
|
139
|
-
resolveReviewHostRelayExtensionPaths,
|
|
140
142
|
resolveReviewHostRelaySubmission,
|
|
141
143
|
runReviewHostRelayReviewerGroup,
|
|
142
144
|
runReviewHostRelaySlot,
|
|
@@ -147,6 +149,7 @@ import {
|
|
|
147
149
|
type ReviewHostRelaySlot,
|
|
148
150
|
type ReviewProviderRoleVectorSlot,
|
|
149
151
|
} from "../lib/review-host-relay.ts";
|
|
152
|
+
import type { InProcessReviewerRegistry } from "../lib/inprocess-reviewer.ts";
|
|
150
153
|
import {
|
|
151
154
|
JOURNAL_STATUS,
|
|
152
155
|
REVIEW_OPERATION,
|
|
@@ -4137,6 +4140,39 @@ function reportProfilesDrops(ctx: ExtensionContext, path: string, drops: Profile
|
|
|
4137
4140
|
}
|
|
4138
4141
|
}
|
|
4139
4142
|
|
|
4143
|
+
/** Pi's own live-session controls: the ExtensionAPI's setModel/setThinkingLevel. */
|
|
4144
|
+
type LiveSession = Pick<ExtensionAPI, "setModel" | "setThinkingLevel">;
|
|
4145
|
+
|
|
4146
|
+
/**
|
|
4147
|
+
* Switch the running session to the profile's orchestrator. `settings.json`
|
|
4148
|
+
* is the default for new sessions only; Pi's `setModel`/`setThinkingLevel`
|
|
4149
|
+
* are what move the live one. Failures never undo the persisted default: the
|
|
4150
|
+
* profile is applied for the next session either way, and the note says what
|
|
4151
|
+
* this session did. Nothing here may throw — settings.json is already written
|
|
4152
|
+
* and the apply must finish reporting.
|
|
4153
|
+
*/
|
|
4154
|
+
async function switchLiveOrchestrator(ctx: ExtensionContext, live: LiveSession, entry: AgentRoutingEntry): Promise<string> {
|
|
4155
|
+
const reference = parseOrchestratorModelRef(entry.model);
|
|
4156
|
+
if (reference === undefined) return "";
|
|
4157
|
+
const label = `${reference.provider}/${reference.model}`;
|
|
4158
|
+
const model = ctx.modelRegistry.find(reference.provider, reference.model);
|
|
4159
|
+
if (model === undefined) return `\n${label} is not in the model catalog; this session keeps its current model.`;
|
|
4160
|
+
let switched = false;
|
|
4161
|
+
try {
|
|
4162
|
+
switched = await live.setModel(model);
|
|
4163
|
+
} catch (error) {
|
|
4164
|
+
return `\nThis session could not switch to ${label}: ${sanitizeTerminalText(error instanceof Error ? error.message : String(error))}.`;
|
|
4165
|
+
}
|
|
4166
|
+
if (!switched) return `\nno authentication is configured for ${reference.provider}; this session keeps its current model.`;
|
|
4167
|
+
if (entry.thinking === undefined) return `\nThis session now runs on ${label}.`;
|
|
4168
|
+
try {
|
|
4169
|
+
live.setThinkingLevel(entry.thinking);
|
|
4170
|
+
} catch (error) {
|
|
4171
|
+
return `\nThis session now runs on ${label}, but its thinking level could not be set to ${entry.thinking}: ${sanitizeTerminalText(error instanceof Error ? error.message : String(error))}.`;
|
|
4172
|
+
}
|
|
4173
|
+
return `\nThis session now runs on ${label} · ${entry.thinking}.`;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4140
4176
|
function profileSnapshotFrom(
|
|
4141
4177
|
current: AgentModelConfig,
|
|
4142
4178
|
settings: OrchestratorSettingsReadResult,
|
|
@@ -4150,6 +4186,7 @@ function profileSnapshotFrom(
|
|
|
4150
4186
|
|
|
4151
4187
|
async function runProfilesPanelAction(
|
|
4152
4188
|
ctx: ExtensionContext,
|
|
4189
|
+
live: LiveSession,
|
|
4153
4190
|
path: string,
|
|
4154
4191
|
file: AgentProfilesFile,
|
|
4155
4192
|
result: Exclude<ProfilesPanelResult, { type: "close" }>,
|
|
@@ -4291,6 +4328,10 @@ async function runProfilesPanelAction(
|
|
|
4291
4328
|
orchestratorRollback = () => restoreOrchestratorSettings(settingsPath, previous);
|
|
4292
4329
|
orchestratorNote = `\nOrchestrator set to ${formatOrchestratorSelection(orchestratorEntry)} in ${sanitizeTerminalText(settingsPath)}.`;
|
|
4293
4330
|
}
|
|
4331
|
+
// settings.json only governs future sessions. The session the user is
|
|
4332
|
+
// sitting in keeps its model until told otherwise, which made a profile
|
|
4333
|
+
// look applied while the orchestrator kept answering with the old one.
|
|
4334
|
+
orchestratorNote += await switchLiveOrchestrator(ctx, live, orchestratorEntry);
|
|
4294
4335
|
}
|
|
4295
4336
|
// A pin that does not resolve changes nothing at launch, so the global apply
|
|
4296
4337
|
// above is what governs this repository. Saying so keeps a broken pin from
|
|
@@ -4537,7 +4578,7 @@ async function runProfilesPanelAction(
|
|
|
4537
4578
|
}
|
|
4538
4579
|
}
|
|
4539
4580
|
|
|
4540
|
-
async function handleProfilesCommand(ctx: ExtensionContext): Promise<void> {
|
|
4581
|
+
async function handleProfilesCommand(ctx: ExtensionContext, live: LiveSession): Promise<void> {
|
|
4541
4582
|
const path = profilesFilePath(gentleAiConfigHome());
|
|
4542
4583
|
const read = readProfilesFileResult(path);
|
|
4543
4584
|
if (read.status === "invalid") {
|
|
@@ -4587,7 +4628,7 @@ async function handleProfilesCommand(ctx: ExtensionContext): Promise<void> {
|
|
|
4587
4628
|
);
|
|
4588
4629
|
while (result.type !== "close") {
|
|
4589
4630
|
selectedName = "name" in result ? result.name : undefined;
|
|
4590
|
-
file = await runProfilesPanelAction(ctx, path, file, result);
|
|
4631
|
+
file = await runProfilesPanelAction(ctx, live, path, file, result);
|
|
4591
4632
|
result = await showProfilesPanel(
|
|
4592
4633
|
ctx,
|
|
4593
4634
|
file,
|
|
@@ -6594,18 +6635,22 @@ function reviewHostRelayFailureReport(error: ReviewHostRelayError): Record<strin
|
|
|
6594
6635
|
};
|
|
6595
6636
|
}
|
|
6596
6637
|
|
|
6597
|
-
// gentle-shell#1136 / #1158: the
|
|
6598
|
-
//
|
|
6599
|
-
//
|
|
6600
|
-
//
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6638
|
+
// gentle-pi#311 P2 (superseding gentle-shell#1136 / #1158): the lens's
|
|
6639
|
+
// completion selection comes from its entry in the agent model routing
|
|
6640
|
+
// config, keyed by its routing key (`review-<lens>`). There is no extension
|
|
6641
|
+
// allowlist and no ambient default model: the in-process completion resolves
|
|
6642
|
+
// its provider through the live model registry the caller supplies, or it is
|
|
6643
|
+
// refused before materialize ever runs (validateReviewerSelectionConfiguration
|
|
6644
|
+
// in lib/review-host-relay.ts).
|
|
6645
|
+
function reviewHostRelaySelection(lens: string | undefined, config: AgentModelConfig): { selection?: string; thinking?: string; routingKey: string } {
|
|
6646
|
+
const routingKey = lens === undefined || lens.length === 0 ? "review capture" : lens.startsWith("review-") ? lens : `review-${lens}`;
|
|
6647
|
+
const entry = config[routingKey];
|
|
6604
6648
|
const model = typeof entry === "object" && entry !== null && typeof (entry as AgentRoutingEntry).model === "string" && (entry as AgentRoutingEntry).model!.length > 0 ? (entry as AgentRoutingEntry).model : undefined;
|
|
6605
|
-
const
|
|
6649
|
+
const thinking = typeof entry === "object" && entry !== null && typeof (entry as AgentRoutingEntry).thinking === "string" ? (entry as AgentRoutingEntry).thinking : undefined;
|
|
6606
6650
|
return {
|
|
6607
|
-
...(model === undefined ? {} : {
|
|
6608
|
-
...(
|
|
6651
|
+
...(model === undefined ? {} : { selection: model }),
|
|
6652
|
+
...(thinking === undefined ? {} : { thinking }),
|
|
6653
|
+
routingKey,
|
|
6609
6654
|
};
|
|
6610
6655
|
}
|
|
6611
6656
|
|
|
@@ -6708,6 +6753,7 @@ async function executeReviewHostRelayCapture(
|
|
|
6708
6753
|
selections: Map<string, RetainedNativeStatusSelection>,
|
|
6709
6754
|
route: RetainedNativeCaptureRoute | undefined,
|
|
6710
6755
|
signal?: AbortSignal,
|
|
6756
|
+
modelRegistry?: InProcessReviewerRegistry,
|
|
6711
6757
|
): Promise<Record<string, unknown>> {
|
|
6712
6758
|
try {
|
|
6713
6759
|
if (slot.submission === undefined) {
|
|
@@ -6718,15 +6764,19 @@ async function executeReviewHostRelayCapture(
|
|
|
6718
6764
|
);
|
|
6719
6765
|
}
|
|
6720
6766
|
const result = await activeReviewHostRelayRunner((() => {
|
|
6721
|
-
// gentle-
|
|
6722
|
-
//
|
|
6723
|
-
//
|
|
6724
|
-
|
|
6767
|
+
// gentle-pi#311 P2 / P3: the lens's (or, for a v9 host-mediated role
|
|
6768
|
+
// slot, the fixed review-refuter/review-validator routing key's)
|
|
6769
|
+
// user-owned completion selection rides the request alongside the
|
|
6770
|
+
// live model registry; the relay validates and refuses a missing
|
|
6771
|
+
// registry or a routing entry with no configured model typed before
|
|
6772
|
+
// anything launches, never a fallback to a child.
|
|
6773
|
+
const launch = reviewHostRelaySelection(slot.routingKey ?? slot.lens, readModelConfig(cwd));
|
|
6725
6774
|
return {
|
|
6726
6775
|
captureArgumentTokens: slot.captureArgumentTokens,
|
|
6727
6776
|
targetCwd: cwd,
|
|
6728
6777
|
submission: slot.submission,
|
|
6729
6778
|
...launch,
|
|
6779
|
+
...(modelRegistry === undefined ? {} : { reviewerRegistry: modelRegistry }),
|
|
6730
6780
|
...(signal === undefined ? {} : { signal }),
|
|
6731
6781
|
};
|
|
6732
6782
|
})());
|
|
@@ -6742,6 +6792,7 @@ async function executeReviewHostRelayCapture(
|
|
|
6742
6792
|
...(slot.lens === undefined ? {} : { lens: slot.lens }),
|
|
6743
6793
|
...(slot.order === undefined ? {} : { order: slot.order }),
|
|
6744
6794
|
...(slot.subjectHash === undefined ? {} : { subject_hash: slot.subjectHash }),
|
|
6795
|
+
...(slot.routingKey === undefined ? {} : { role: slot.name }),
|
|
6745
6796
|
prompt_bytes: result.promptByteLength,
|
|
6746
6797
|
result_bytes: result.resultByteLength,
|
|
6747
6798
|
submission: result.submission,
|
|
@@ -7323,6 +7374,11 @@ async function executeReviewCaptureOperation(
|
|
|
7323
7374
|
candidateViews: CandidateViewRegistry | null = new CandidateViewRegistry(),
|
|
7324
7375
|
retainedUntrackedSelections: Map<string, RetainedNativeStatusSelection> = new Map(),
|
|
7325
7376
|
requireRegisteredRoute = false,
|
|
7377
|
+
// gentle-pi#311 P2: the live model registry a lens materialize capture
|
|
7378
|
+
// resolves its in-process completion through. Appended last (rather than
|
|
7379
|
+
// inserted) so every existing positional call site — none of which pass an
|
|
7380
|
+
// eighth argument — keeps compiling unchanged.
|
|
7381
|
+
modelRegistry?: InProcessReviewerRegistry,
|
|
7326
7382
|
): Promise<Record<string, unknown>> {
|
|
7327
7383
|
const parameters = parseReviewCaptureParameters(parametersValue);
|
|
7328
7384
|
if (nativeReviewCli === null || nativeReviewCli.targetStatus === undefined) {
|
|
@@ -7382,7 +7438,33 @@ async function executeReviewCaptureOperation(
|
|
|
7382
7438
|
mutation_outcome: "none",
|
|
7383
7439
|
};
|
|
7384
7440
|
}
|
|
7385
|
-
return withCorrectionTarget(await executeReviewHostRelayCapture(hostRelaySlots[0]!, nativeReviewCli, cwd, selected.binding, retainedUntrackedSelections, route, signal));
|
|
7441
|
+
return withCorrectionTarget(await executeReviewHostRelayCapture(hostRelaySlots[0]!, nativeReviewCli, cwd, selected.binding, retainedUntrackedSelections, route, signal, modelRegistry));
|
|
7442
|
+
}
|
|
7443
|
+
|
|
7444
|
+
// gentle-pi#311 P3: gentle-ai's v9 contract renders the refuter and
|
|
7445
|
+
// targeted-validator role captures host-mediated, exactly like the lens
|
|
7446
|
+
// slot above — same relay machinery, only its fixed review-refuter /
|
|
7447
|
+
// review-validator routing key (carried on the slot) and its own schema
|
|
7448
|
+
// differ. An older gentle-ai's self-contained --execute=true vector still
|
|
7449
|
+
// falls through to reviewProviderRoleVectorSlots below unchanged.
|
|
7450
|
+
const hostMediatedRoleSlots = reviewHostMediatedRoleSlots([selected.input]);
|
|
7451
|
+
if (hostMediatedRoleSlots.length === 1) {
|
|
7452
|
+
if (parameters.correctionLines !== undefined) return captureBindingRejected("correctionLines is valid only for a correction-plan capture");
|
|
7453
|
+
if (parameters.reviewerRunAcknowledged !== true) {
|
|
7454
|
+
return {
|
|
7455
|
+
tool: "gentle_review_capture",
|
|
7456
|
+
status: "blocked",
|
|
7457
|
+
outcome: "reviewer-model-run-forecast",
|
|
7458
|
+
cost_forecast: {
|
|
7459
|
+
transport: "pi_host_relay",
|
|
7460
|
+
model_runs: 1,
|
|
7461
|
+
roles: [hostMediatedRoleSlots[0]!.routingKey],
|
|
7462
|
+
},
|
|
7463
|
+
mutation_performed: false,
|
|
7464
|
+
mutation_outcome: "none",
|
|
7465
|
+
};
|
|
7466
|
+
}
|
|
7467
|
+
return withCorrectionTarget(await executeReviewHostRelayCapture(hostMediatedRoleSlots[0]!, nativeReviewCli, cwd, selected.binding, retainedUntrackedSelections, route, signal, modelRegistry));
|
|
7386
7468
|
}
|
|
7387
7469
|
|
|
7388
7470
|
if (selected.input.captureOperation === "review.capture-correction-plan") {
|
|
@@ -7461,6 +7543,8 @@ async function executeReviewCaptureGroupOperation(
|
|
|
7461
7543
|
candidateViews: CandidateViewRegistry | null = new CandidateViewRegistry(),
|
|
7462
7544
|
retainedUntrackedSelections: Map<string, RetainedNativeStatusSelection> = new Map(),
|
|
7463
7545
|
requireRegisteredRoute = false,
|
|
7546
|
+
// gentle-pi#311 P2: see executeReviewCaptureOperation's matching parameter.
|
|
7547
|
+
modelRegistry?: InProcessReviewerRegistry,
|
|
7464
7548
|
): Promise<Record<string, unknown>> {
|
|
7465
7549
|
const parameters = parseReviewCaptureGroupParameters(parametersValue);
|
|
7466
7550
|
if (nativeReviewCli === null || nativeReviewCli.targetStatus === undefined) return { ...captureGroupRejected("native target STATUS is unavailable"), outcome: "native-status-unsupported" };
|
|
@@ -7501,7 +7585,8 @@ async function executeReviewCaptureGroupOperation(
|
|
|
7501
7585
|
captureArgumentTokens: slot.captureArgumentTokens,
|
|
7502
7586
|
targetCwd: cwd,
|
|
7503
7587
|
submission: slot.submission!,
|
|
7504
|
-
...
|
|
7588
|
+
...reviewHostRelaySelection(slot.lens, readModelConfig(cwd)),
|
|
7589
|
+
...(modelRegistry === undefined ? {} : { reviewerRegistry: modelRegistry }),
|
|
7505
7590
|
...(signal === undefined ? {} : { signal }),
|
|
7506
7591
|
}));
|
|
7507
7592
|
let prepared: readonly ReviewHostRelayPreparedResult[];
|
|
@@ -8648,6 +8733,11 @@ function createGentleAiExtensionForTesting(
|
|
|
8648
8733
|
const candidateViews = dependencies.candidateViews === undefined ? new CandidateViewRegistry() : dependencies.candidateViews;
|
|
8649
8734
|
const herdrLifecycle = createHerdrConfirmationLifecycle(pi.events);
|
|
8650
8735
|
const permissionEnvironment = dependencies.processEnv ?? process.env;
|
|
8736
|
+
const oddDelegationGate = new OddRuntimeDelegationGate();
|
|
8737
|
+
const oddSessionId = (ctx: ExtensionContext): string => {
|
|
8738
|
+
try { return ctx.sessionManager.getSessionId(); }
|
|
8739
|
+
catch { return ""; }
|
|
8740
|
+
};
|
|
8651
8741
|
|
|
8652
8742
|
const setReviewSessionPermissionStatus = (context: ExtensionContext, active: boolean): void => {
|
|
8653
8743
|
try {
|
|
@@ -8769,6 +8859,7 @@ function createGentleAiExtensionForTesting(
|
|
|
8769
8859
|
candidateViews,
|
|
8770
8860
|
((sessionKey: PendingReviewConsentSessionKey) => processRetainedNativeStatusSelections.get(sessionKey) ?? processRetainedNativeStatusSelections.set(sessionKey, new Map()).get(sessionKey)!)(pendingReviewConsentSessionKey(ctx, pendingReviewConsentFallbackKey)),
|
|
8771
8861
|
true,
|
|
8862
|
+
ctx.modelRegistry,
|
|
8772
8863
|
);
|
|
8773
8864
|
return { content: [{ type: "text", text: JSON.stringify(details) }], details };
|
|
8774
8865
|
},
|
|
@@ -8782,7 +8873,7 @@ function createGentleAiExtensionForTesting(
|
|
|
8782
8873
|
promptSnippet: "Use one exact current STATUS collectBinding for one ordinary native capture; call fresh STATUS before every additional capture.",
|
|
8783
8874
|
promptGuidelines: [
|
|
8784
8875
|
"Pass only lineageId, the JSON-serialized exact collectBinding from current STATUS, and the route-specific optional acknowledgement or correctionLines value. Never compose provider argument tokens, prompts, results, verdicts, or lens arrays.",
|
|
8785
|
-
"A materialize reviewer slot first forecasts one model run; re-submit that same exact binding with reviewerRunAcknowledged: true to authorize one host relay. Correction-plan slots require correctionLines inside the provider-issued bounds, counted in diff lines (one replaced source line is one deletion plus one addition) — a different unit from the frozen logical correction budget.
|
|
8876
|
+
"A materialize reviewer slot first forecasts one model run; re-submit that same exact binding with reviewerRunAcknowledged: true to authorize one host relay. Correction-plan slots require correctionLines inside the provider-issued bounds, counted in diff lines (one replaced source line is one deletion plus one addition) — a different unit from the frozen logical correction budget. A refuter or targeted-validator slot forecasts and runs the same way when the provider renders it host-mediated; an older provider's self-contained refuter/validation vector still executes exactly once as provider-rendered.",
|
|
8786
8877
|
"A native terminal closure or nonterminal capture returns directly. Do not expect automatic STATUS, FINALIZE, receipt, delivery, or another capture; call fresh STATUS before any next capture.",
|
|
8787
8878
|
],
|
|
8788
8879
|
parameters: REVIEW_CAPTURE_PARAMETERS,
|
|
@@ -8807,6 +8898,7 @@ function createGentleAiExtensionForTesting(
|
|
|
8807
8898
|
candidateViews,
|
|
8808
8899
|
((sessionKey: PendingReviewConsentSessionKey) => processRetainedNativeStatusSelections.get(sessionKey) ?? processRetainedNativeStatusSelections.set(sessionKey, new Map()).get(sessionKey)!)(pendingReviewConsentSessionKey(ctx, pendingReviewConsentFallbackKey)),
|
|
8809
8900
|
true,
|
|
8901
|
+
ctx.modelRegistry,
|
|
8810
8902
|
);
|
|
8811
8903
|
return {
|
|
8812
8904
|
content: [{ type: "text", text: JSON.stringify(details) }],
|
|
@@ -9043,6 +9135,10 @@ function createGentleAiExtensionForTesting(
|
|
|
9043
9135
|
const retiredSync = readAgentStartNames(event).includes("sdd-sync") || /\bSDD sync executor\b/i.test(event.systemPrompt ?? "");
|
|
9044
9136
|
const isSddAgent = retiredSync || isSddAgentStartEvent(event);
|
|
9045
9137
|
const isNamedAgent = isNamedAgentStartEvent(event);
|
|
9138
|
+
oddDelegationGate.start(
|
|
9139
|
+
oddSessionId(ctx),
|
|
9140
|
+
!isNamedAgent && !isSddAgent && permissionEnvironment.GENTLE_PI_AGENTS_CHILD !== "1",
|
|
9141
|
+
);
|
|
9046
9142
|
const subagentDepthKey = pendingReviewConsentSessionKey(ctx, pendingReviewConsentFallbackKey);
|
|
9047
9143
|
if (isSddAgent || isNamedAgent) {
|
|
9048
9144
|
processAgentEndSubagentDepth.set(subagentDepthKey, (processAgentEndSubagentDepth.get(subagentDepthKey) ?? 0) + 1);
|
|
@@ -9152,6 +9248,7 @@ function createGentleAiExtensionForTesting(
|
|
|
9152
9248
|
// consent, or chooses a partial candidate. Durable own-mutation receipts
|
|
9153
9249
|
// gate STATUS and consume only the generation captured before that await.
|
|
9154
9250
|
pi.on("agent_end", async (_event, ctx) => {
|
|
9251
|
+
oddDelegationGate.endChild(oddSessionId(ctx));
|
|
9155
9252
|
if (nativeReviewCli?.reviewMode === undefined || nativeReviewCli.targetStatus === undefined) return;
|
|
9156
9253
|
if (ctx.hasUI !== true || !reminderSessionActive) return;
|
|
9157
9254
|
const sessionKey = pendingReviewConsentSessionKey(ctx, pendingReviewConsentFallbackKey);
|
|
@@ -9187,6 +9284,7 @@ function createGentleAiExtensionForTesting(
|
|
|
9187
9284
|
pi.on("tool_result", (event, ctx) => {
|
|
9188
9285
|
if (!reminderSessionActive || event.isError !== false || (event.toolName !== "write" && event.toolName !== "edit")) return;
|
|
9189
9286
|
if (!isRecord(event.input) || typeof event.input.path !== "string" || !event.input.path.trim()) return;
|
|
9287
|
+
oddDelegationGate.recordSuccess(oddSessionId(ctx), event.toolName, event.input, ctx.cwd);
|
|
9190
9288
|
try {
|
|
9191
9289
|
const root = resolveSessionWorktree(event.input.path, ctx.cwd)?.root;
|
|
9192
9290
|
if (root) recordReviewMutation(pi, ctx.sessionManager, root, { source: "direct", toolName: event.toolName, toolCallId: event.toolCallId });
|
|
@@ -9200,6 +9298,10 @@ function createGentleAiExtensionForTesting(
|
|
|
9200
9298
|
event.input,
|
|
9201
9299
|
);
|
|
9202
9300
|
if (sensitivePathDenied) return sensitivePathDenied;
|
|
9301
|
+
const oddDelegationDenied = oddDelegationGate.beforeTool(
|
|
9302
|
+
oddSessionId(ctx), event.toolName, event.input, ctx.cwd, readActiveToolNames(pi),
|
|
9303
|
+
);
|
|
9304
|
+
if (oddDelegationDenied) return oddDelegationDenied;
|
|
9203
9305
|
if (event.toolName === "subagent_run") {
|
|
9204
9306
|
const sddAgent = sddDispatchAgentName(event.input);
|
|
9205
9307
|
if (sddAgent === "invalid") {
|
|
@@ -9365,7 +9467,7 @@ function createGentleAiExtensionForTesting(
|
|
|
9365
9467
|
pi.registerCommand("gentle:profiles", {
|
|
9366
9468
|
description: "Create, switch, and manage global agent-model profiles for el Gentleman.",
|
|
9367
9469
|
handler: async (_args, ctx) => {
|
|
9368
|
-
await handleProfilesCommand(ctx);
|
|
9470
|
+
await handleProfilesCommand(ctx, pi);
|
|
9369
9471
|
},
|
|
9370
9472
|
});
|
|
9371
9473
|
|