mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Align permission runtime resolution
This commit is contained in:
parent
6575ad7bf3
commit
070fd261fc
40 changed files with 587 additions and 203 deletions
|
|
@ -225,6 +225,9 @@ export function threadActivationSnapshotFromAppServerResponse(response: ThreadAc
|
|||
return {
|
||||
thread: threadFromThreadRecord(response.thread),
|
||||
cwd: response.cwd,
|
||||
approvalPolicyKnown: "approvalPolicy" in response,
|
||||
sandboxPolicyKnown: "sandbox" in response || "sandboxPolicy" in response,
|
||||
permissionProfileKnown: "activePermissionProfile" in response,
|
||||
model: response.model,
|
||||
reasoningEffort: normalizeReasoningEffort(response.reasoningEffort),
|
||||
serviceTier: parseServiceTier(response.serviceTier),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ export interface RuntimePermissionState {
|
|||
readonly activePermissionProfile: RuntimeActivePermissionProfile | null;
|
||||
}
|
||||
|
||||
export interface RuntimePermissionKnownState {
|
||||
readonly approvalPolicyKnown: boolean;
|
||||
readonly sandboxPolicyKnown: boolean;
|
||||
readonly permissionProfileKnown: boolean;
|
||||
}
|
||||
|
||||
export function initialRuntimePermissionState(): RuntimePermissionState {
|
||||
return {
|
||||
approvalPolicy: null,
|
||||
|
|
@ -46,6 +52,14 @@ export function initialRuntimePermissionState(): RuntimePermissionState {
|
|||
};
|
||||
}
|
||||
|
||||
export function initialRuntimePermissionKnownState(): RuntimePermissionKnownState {
|
||||
return {
|
||||
approvalPolicyKnown: false,
|
||||
sandboxPolicyKnown: false,
|
||||
permissionProfileKnown: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function runtimePermissionStateOrDefault(value: Partial<RuntimePermissionState> | null | undefined): RuntimePermissionState {
|
||||
return {
|
||||
approvalPolicy: value?.approvalPolicy ? cloneRuntimeApprovalPolicy(value.approvalPolicy) : null,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { ReasoningEffort } from "../catalog/metadata";
|
||||
import type { RuntimePermissionState } from "../runtime/permissions";
|
||||
import type { RuntimePermissionKnownState, RuntimePermissionState } from "../runtime/permissions";
|
||||
import type { ApprovalsReviewer, ServiceTier } from "../runtime/policy";
|
||||
import type { Thread } from "./model";
|
||||
|
||||
export interface ThreadActivationSnapshot extends RuntimePermissionState {
|
||||
export interface ThreadActivationSnapshot extends RuntimePermissionState, RuntimePermissionKnownState {
|
||||
thread: Thread;
|
||||
cwd: string;
|
||||
model: string | null;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@ export function implementPlanTargetFromState(state: {
|
|||
runtime: { pending: Pick<ChatRuntimeState["pending"], "collaborationMode"> };
|
||||
messageStream: Pick<ChatMessageStreamState, "stableItems" | "activeSegment">;
|
||||
}): PlanImplementationTarget | null {
|
||||
if (!state.activeThread.id || chatTurnBusy(state) || state.runtime.pending.collaborationMode !== "plan") {
|
||||
if (
|
||||
!state.activeThread.id ||
|
||||
chatTurnBusy(state) ||
|
||||
state.runtime.pending.collaborationMode.kind !== "set" ||
|
||||
state.runtime.pending.collaborationMode.value !== "plan"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return latestImplementablePlanTargetFromItems(messageStreamItems(state.messageStream));
|
||||
|
|
|
|||
|
|
@ -153,8 +153,8 @@ async function setFastMode(host: RuntimeSettingsActionsHost, mode: FastModeState
|
|||
}
|
||||
|
||||
async function toggleCollaborationMode(host: RuntimeSettingsActionsHost): Promise<void> {
|
||||
const current = state(host);
|
||||
const next = nextCollaborationMode(current.runtime.pending.collaborationMode);
|
||||
const { snapshot, config } = runtimeProjection(host);
|
||||
const next = nextCollaborationMode(resolveRuntimeControls(snapshot, config).collaborationMode.effective);
|
||||
await setCollaborationMode(host, next);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { normalizeReasoningEffort, type ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import { type RuntimePermissionState, runtimePermissionStateOrDefault } from "../../../../domain/runtime/permissions";
|
||||
import {
|
||||
type RuntimePermissionKnownState,
|
||||
type RuntimePermissionState,
|
||||
runtimePermissionStateOrDefault,
|
||||
} from "../../../../domain/runtime/permissions";
|
||||
import { parseServiceTier, type ServiceTier } from "../../../../domain/runtime/policy";
|
||||
import type { ServerInitialization } from "../../../../domain/server/initialization";
|
||||
import type { ThreadActivationSnapshot } from "../../../../domain/threads/activation";
|
||||
|
|
@ -27,6 +31,9 @@ interface ResumedThreadFromActiveRuntimeParams {
|
|||
| "serviceTier"
|
||||
| "serviceTierKnown"
|
||||
| "approvalsReviewer"
|
||||
| "approvalPolicyKnown"
|
||||
| "sandboxPolicyKnown"
|
||||
| "permissionProfileKnown"
|
||||
| "approvalPolicy"
|
||||
| "sandboxPolicy"
|
||||
| "activePermissionProfile"
|
||||
|
|
@ -35,7 +42,7 @@ interface ResumedThreadFromActiveRuntimeParams {
|
|||
items?: readonly MessageStreamItem[];
|
||||
}
|
||||
|
||||
export interface ActiveThreadResumedAction extends RuntimePermissionState {
|
||||
export interface ActiveThreadResumedAction extends RuntimePermissionState, RuntimePermissionKnownState {
|
||||
type: "active-thread/resumed";
|
||||
thread: Thread;
|
||||
cwd: string;
|
||||
|
|
@ -50,7 +57,7 @@ export interface ActiveThreadResumedAction extends RuntimePermissionState {
|
|||
preserveRequestedRuntimeSettings?: boolean;
|
||||
}
|
||||
|
||||
export interface ActiveThreadSettingsAppliedAction extends RuntimePermissionState {
|
||||
export interface ActiveThreadSettingsAppliedAction extends RuntimePermissionState, RuntimePermissionKnownState {
|
||||
type: "active-thread/settings-applied";
|
||||
cwd: string;
|
||||
model: string | null;
|
||||
|
|
@ -121,6 +128,9 @@ export function resumedThreadActionFromActiveRuntime(params: ResumedThreadFromAc
|
|||
response: {
|
||||
thread: params.thread,
|
||||
cwd: params.cwd,
|
||||
approvalPolicyKnown: params.runtime.approvalPolicyKnown,
|
||||
sandboxPolicyKnown: params.runtime.sandboxPolicyKnown,
|
||||
permissionProfileKnown: params.runtime.permissionProfileKnown,
|
||||
model: params.runtime.model,
|
||||
reasoningEffort: params.runtime.reasoningEffort,
|
||||
serviceTier: params.runtime.serviceTier,
|
||||
|
|
@ -147,6 +157,9 @@ export function resumedThreadAction(params: ResumedThreadActionParams): ActiveTh
|
|||
serviceTier: response.serviceTier,
|
||||
serviceTierKnown: params.serviceTierKnown ?? true,
|
||||
approvalsReviewer: response.approvalsReviewer,
|
||||
approvalPolicyKnown: response.approvalPolicyKnown,
|
||||
sandboxPolicyKnown: response.sandboxPolicyKnown,
|
||||
permissionProfileKnown: response.permissionProfileKnown,
|
||||
...permissions,
|
||||
...(params.items ? { items: params.items } : {}),
|
||||
...(params.listedThreads ? { listedThreads: upsertThread(params.listedThreads, response.thread) } : {}),
|
||||
|
|
@ -164,6 +177,9 @@ export function activeThreadSettingsAppliedAction(settings: ActiveThreadSettings
|
|||
collaborationMode: settings.collaborationMode.mode,
|
||||
serviceTier: parseServiceTier(settings.serviceTier),
|
||||
approvalsReviewer: settings.approvalsReviewer,
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
...permissions,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,6 +328,9 @@ function reduceActiveThreadResumedTransition(state: ChatState, action: ActiveThr
|
|||
collaborationMode: initialActiveChatRuntimeState().collaborationMode,
|
||||
serviceTier: action.serviceTier,
|
||||
approvalsReviewer: action.approvalsReviewer,
|
||||
approvalPolicyKnown: action.approvalPolicyKnown,
|
||||
sandboxPolicyKnown: action.sandboxPolicyKnown,
|
||||
permissionProfileKnown: action.permissionProfileKnown,
|
||||
approvalPolicy: action.approvalPolicy,
|
||||
sandboxPolicy: action.sandboxPolicy,
|
||||
activePermissionProfile: action.activePermissionProfile,
|
||||
|
|
@ -356,13 +359,16 @@ function reduceActiveThreadSettingsAppliedTransition(state: ChatState, action: A
|
|||
collaborationMode: action.collaborationMode,
|
||||
serviceTier: action.serviceTier,
|
||||
approvalsReviewer: action.approvalsReviewer,
|
||||
approvalPolicyKnown: action.approvalPolicyKnown,
|
||||
sandboxPolicyKnown: action.sandboxPolicyKnown,
|
||||
permissionProfileKnown: action.permissionProfileKnown,
|
||||
approvalPolicy: action.approvalPolicy,
|
||||
sandboxPolicy: action.sandboxPolicy,
|
||||
activePermissionProfile: action.activePermissionProfile,
|
||||
},
|
||||
pending: {
|
||||
...state.runtime.pending,
|
||||
collaborationMode: action.collaborationMode,
|
||||
collaborationMode: { kind: "unchanged" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { ModeKind } from "../../../../domain/runtime/thread-settings";
|
|||
|
||||
export type CollaborationModeSelection = ModeKind;
|
||||
export type ActiveCollaborationMode = CollaborationModeSelection | null;
|
||||
export type CollaborationModeIntent = { readonly kind: "unchanged" } | { readonly kind: "set"; readonly value: CollaborationModeSelection };
|
||||
|
||||
// Pending runtime intents are panel-side user requests, not app-server protocol values.
|
||||
// They are projected for display and reduced into panel-owned runtime settings patches before app-server protocol adaptation.
|
||||
|
|
@ -23,6 +24,14 @@ export function resetRuntimeIntentToConfig<T>(): PendingRuntimeIntent<T> {
|
|||
return { kind: "resetToConfig" };
|
||||
}
|
||||
|
||||
export function unchangedCollaborationModeIntent(): CollaborationModeIntent {
|
||||
return { kind: "unchanged" };
|
||||
}
|
||||
|
||||
export function setCollaborationModeIntent(value: CollaborationModeSelection): CollaborationModeIntent {
|
||||
return { kind: "set", value };
|
||||
}
|
||||
|
||||
export function nextCollaborationMode(mode: CollaborationModeSelection): CollaborationModeSelection {
|
||||
return mode === "plan" ? "default" : "plan";
|
||||
}
|
||||
|
|
@ -30,3 +39,8 @@ export function nextCollaborationMode(mode: CollaborationModeSelection): Collabo
|
|||
export function effectiveCollaborationMode(mode: ActiveCollaborationMode): CollaborationModeSelection {
|
||||
return mode ?? "default";
|
||||
}
|
||||
|
||||
export function collaborationModeIntentValue(intent: CollaborationModeIntent, active: ActiveCollaborationMode): CollaborationModeSelection {
|
||||
if (intent.kind === "set") return intent.value;
|
||||
return effectiveCollaborationMode(active);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import { findModelMetadataByIdOrName, type ModelMetadata, supportedEffortsForModelMetadata } from "../../../../domain/catalog/metadata";
|
||||
import type { RuntimeConfigSnapshot } from "../../../../domain/runtime/config";
|
||||
import { cloneRuntimePermissionState, type RuntimePermissionState } from "../../../../domain/runtime/permissions";
|
||||
import { cloneRuntimePermissionState, type RuntimeApprovalPolicy, type RuntimeSandboxPolicy } from "../../../../domain/runtime/permissions";
|
||||
import type { ApprovalsReviewer, ServiceTier } from "../../../../domain/runtime/policy";
|
||||
import { effectiveCollaborationMode, type PendingRuntimeIntent, type RequestedFastMode } from "./intent";
|
||||
import {
|
||||
effectiveCollaborationMode,
|
||||
type PendingRuntimeIntent,
|
||||
type RequestedFastMode,
|
||||
resetRuntimeIntentToConfig,
|
||||
setRuntimeIntentValue,
|
||||
unchangedRuntimeIntent,
|
||||
} from "./intent";
|
||||
import type { RuntimeSnapshot } from "./snapshot";
|
||||
|
||||
type RuntimeValueSource = "pending" | "active-thread" | "config" | "none";
|
||||
|
|
@ -12,6 +19,18 @@ interface RuntimeLayeredValue<T> {
|
|||
readonly configured: T | null;
|
||||
readonly active: T | null;
|
||||
readonly pending: PendingRuntimeIntent<T>;
|
||||
readonly confirmed: T | null;
|
||||
readonly confirmedSource: RuntimeValueSource;
|
||||
readonly effective: T | null;
|
||||
readonly source: RuntimeValueSource;
|
||||
}
|
||||
|
||||
interface RuntimeNullableLayeredValue<T> {
|
||||
readonly configured: T | null;
|
||||
readonly active: T | null;
|
||||
readonly pending: PendingRuntimeIntent<T | null>;
|
||||
readonly confirmed: T | null;
|
||||
readonly confirmedSource: RuntimeValueSource;
|
||||
readonly effective: T | null;
|
||||
readonly source: RuntimeValueSource;
|
||||
}
|
||||
|
|
@ -19,32 +38,30 @@ interface RuntimeLayeredValue<T> {
|
|||
interface FastModeResolution {
|
||||
readonly requested: PendingRuntimeIntent<RequestedFastMode>;
|
||||
readonly active: boolean;
|
||||
readonly confirmedActive: boolean;
|
||||
readonly source: RuntimeValueSource;
|
||||
readonly confirmedSource: RuntimeValueSource;
|
||||
readonly effectiveServiceTier: ServiceTier | null;
|
||||
readonly confirmedServiceTier: ServiceTier | null;
|
||||
readonly serviceTierRequestValue: string;
|
||||
}
|
||||
|
||||
interface AutoReviewResolution {
|
||||
readonly active: boolean;
|
||||
readonly confirmedActive: boolean;
|
||||
readonly source: RuntimeValueSource;
|
||||
readonly confirmedSource: RuntimeValueSource;
|
||||
}
|
||||
|
||||
interface CollaborationModeResolution {
|
||||
readonly active: RuntimeSnapshot["active"]["collaborationMode"];
|
||||
readonly selected: RuntimeSnapshot["pending"]["collaborationMode"];
|
||||
readonly effective: RuntimeSnapshot["pending"]["collaborationMode"];
|
||||
readonly pending: RuntimeSnapshot["pending"]["collaborationMode"];
|
||||
readonly confirmed: NonNullable<RuntimeSnapshot["active"]["collaborationMode"]>;
|
||||
readonly effective: NonNullable<RuntimeSnapshot["active"]["collaborationMode"]>;
|
||||
readonly dirty: boolean;
|
||||
readonly blockedReason: "missing-model" | null;
|
||||
}
|
||||
|
||||
interface RuntimePermissionsResolution {
|
||||
readonly scope: "new-thread" | "current-thread";
|
||||
readonly configured: RuntimePermissionState;
|
||||
readonly active: RuntimePermissionState | null;
|
||||
readonly effective: RuntimePermissionState;
|
||||
readonly source: RuntimeValueSource;
|
||||
}
|
||||
|
||||
export interface RuntimeControlsResolution {
|
||||
readonly model: RuntimeLayeredValue<string>;
|
||||
readonly reasoningEffort: RuntimeLayeredValue<ReasoningEffort>;
|
||||
|
|
@ -52,7 +69,9 @@ export interface RuntimeControlsResolution {
|
|||
readonly serviceTier: RuntimeLayeredValue<ServiceTier>;
|
||||
readonly fastMode: FastModeResolution;
|
||||
readonly collaborationMode: CollaborationModeResolution;
|
||||
readonly permissions: RuntimePermissionsResolution;
|
||||
readonly permissionProfile: RuntimeLayeredValue<string>;
|
||||
readonly sandboxPolicy: RuntimeNullableLayeredValue<RuntimeSandboxPolicy>;
|
||||
readonly approvalPolicy: RuntimeLayeredValue<RuntimeApprovalPolicy>;
|
||||
readonly approvalsReviewer: RuntimeLayeredValue<ApprovalsReviewer>;
|
||||
readonly supportedReasoningEfforts: readonly ReasoningEffort[];
|
||||
}
|
||||
|
|
@ -77,7 +96,19 @@ export function resolveRuntimeControls(snapshot: RuntimeSnapshot, config: Runtim
|
|||
const serviceTier = resolveServiceTier(snapshot, config);
|
||||
const fastMode = resolveFastMode(snapshot.pending.fastMode, serviceTier, serviceTiers);
|
||||
const collaborationMode = resolveCollaborationMode(snapshot, model.effective);
|
||||
const permissions = resolveRuntimePermissions(snapshot, config);
|
||||
const permissionProfile = resolveRuntimePermissionValue({
|
||||
configured: config.startupPermissions.activePermissionProfile?.id ?? null,
|
||||
active: snapshot.active.activePermissionProfile?.id ?? null,
|
||||
pending: snapshot.pending.permissionProfile,
|
||||
activeKnown: snapshot.activeThreadId !== null && snapshot.active.permissionProfileKnown,
|
||||
});
|
||||
const approvalPolicy = resolveRuntimePermissionValue({
|
||||
configured: config.startupPermissions.approvalPolicy,
|
||||
active: snapshot.active.approvalPolicy,
|
||||
pending: snapshot.pending.approvalPolicy,
|
||||
activeKnown: snapshot.activeThreadId !== null && snapshot.active.approvalPolicyKnown,
|
||||
});
|
||||
const sandboxPolicy = resolveRuntimeSandboxPolicy(snapshot, config);
|
||||
const autoReview = resolveAutoReview(reviewer);
|
||||
|
||||
return {
|
||||
|
|
@ -87,7 +118,9 @@ export function resolveRuntimeControls(snapshot: RuntimeSnapshot, config: Runtim
|
|||
serviceTier,
|
||||
fastMode,
|
||||
collaborationMode,
|
||||
permissions,
|
||||
permissionProfile,
|
||||
sandboxPolicy,
|
||||
approvalPolicy,
|
||||
approvalsReviewer: reviewer,
|
||||
supportedReasoningEfforts: supportedEffortsForModelMetadata(findModelMetadataByIdOrName(snapshot.availableModels, model.effective)),
|
||||
};
|
||||
|
|
@ -96,78 +129,86 @@ export function resolveRuntimeControls(snapshot: RuntimeSnapshot, config: Runtim
|
|||
function resolveAutoReview(reviewer: RuntimeLayeredValue<ApprovalsReviewer>): AutoReviewResolution {
|
||||
return {
|
||||
active: reviewer.effective === "auto_review" || reviewer.effective === "guardian_subagent",
|
||||
confirmedActive: reviewer.confirmed === "auto_review" || reviewer.confirmed === "guardian_subagent",
|
||||
source: reviewer.source,
|
||||
confirmedSource: reviewer.confirmedSource,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveRuntimePermissions(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): RuntimePermissionsResolution {
|
||||
const configured = cloneRuntimePermissionState(config.startupPermissions);
|
||||
const scope = snapshot.activeThreadId ? "current-thread" : "new-thread";
|
||||
const baseSource = snapshot.activeThreadId ? "active-thread" : "config";
|
||||
if (snapshot.activeThreadId) {
|
||||
const active = cloneRuntimePermissionState(snapshot.active);
|
||||
const { effective, source } = resolveRuntimePermissionState(
|
||||
active,
|
||||
configured,
|
||||
snapshot.pending.approvalPolicy,
|
||||
snapshot.pending.permissionProfile,
|
||||
baseSource,
|
||||
);
|
||||
return {
|
||||
scope,
|
||||
configured,
|
||||
active,
|
||||
effective,
|
||||
source,
|
||||
};
|
||||
}
|
||||
const { effective, source } = resolveRuntimePermissionState(
|
||||
configured,
|
||||
configured,
|
||||
snapshot.pending.approvalPolicy,
|
||||
snapshot.pending.permissionProfile,
|
||||
baseSource,
|
||||
);
|
||||
return {
|
||||
scope,
|
||||
configured,
|
||||
active: null,
|
||||
effective,
|
||||
source,
|
||||
};
|
||||
function resolveRuntimeSandboxPolicy(
|
||||
snapshot: RuntimeSnapshot,
|
||||
config: RuntimeConfigSnapshot,
|
||||
): RuntimeNullableLayeredValue<RuntimeSandboxPolicy> {
|
||||
const pending = sandboxPolicyIntentFromPermissionProfile(snapshot.pending.permissionProfile, config);
|
||||
return resolveNullableRuntimePermissionValue({
|
||||
configured: cloneRuntimeSandboxPolicy(config.startupPermissions.sandboxPolicy),
|
||||
active: cloneRuntimeSandboxPolicy(snapshot.active.sandboxPolicy),
|
||||
pending,
|
||||
activeKnown: snapshot.activeThreadId !== null && snapshot.active.sandboxPolicyKnown,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveRuntimePermissionState(
|
||||
base: RuntimePermissionState,
|
||||
configured: RuntimePermissionState,
|
||||
approvalPolicyIntent: RuntimeSnapshot["pending"]["approvalPolicy"],
|
||||
permissionProfileIntent: RuntimeSnapshot["pending"]["permissionProfile"],
|
||||
baseSource: RuntimeValueSource,
|
||||
): Pick<RuntimePermissionsResolution, "effective" | "source"> {
|
||||
let effective = cloneRuntimePermissionState(base);
|
||||
let source = baseSource;
|
||||
const approvalPolicy = runtimePermissionIntentValue(approvalPolicyIntent, configured.approvalPolicy);
|
||||
if (approvalPolicy !== undefined) {
|
||||
effective = { ...effective, approvalPolicy };
|
||||
source = "pending";
|
||||
function sandboxPolicyIntentFromPermissionProfile(
|
||||
intent: PendingRuntimeIntent<string>,
|
||||
config: RuntimeConfigSnapshot,
|
||||
): PendingRuntimeIntent<RuntimeSandboxPolicy | null> {
|
||||
if (intent.kind === "set") {
|
||||
return setRuntimeIntentValue(sandboxPolicyForPermissionProfile(intent.value, config));
|
||||
}
|
||||
const configuredPermissionProfile = configured.activePermissionProfile?.id ?? null;
|
||||
const permissionProfile = runtimePermissionIntentValue(permissionProfileIntent, configuredPermissionProfile);
|
||||
if (permissionProfile !== undefined) {
|
||||
effective = {
|
||||
...effective,
|
||||
sandboxPolicy: permissionProfile === configuredPermissionProfile ? configured.sandboxPolicy : null,
|
||||
activePermissionProfile: permissionProfile ? { id: permissionProfile, extends: null } : null,
|
||||
};
|
||||
source = "pending";
|
||||
}
|
||||
return { effective: cloneRuntimePermissionState(effective), source };
|
||||
if (intent.kind === "resetToConfig") return resetRuntimeIntentToConfig();
|
||||
return unchangedRuntimeIntent();
|
||||
}
|
||||
|
||||
function runtimePermissionIntentValue<T>(intent: PendingRuntimeIntent<T>, configured: T | null): T | null | undefined {
|
||||
if (intent.kind === "set") return intent.value;
|
||||
if (intent.kind === "resetToConfig") return configured;
|
||||
return undefined;
|
||||
function sandboxPolicyForPermissionProfile(profile: string, config: RuntimeConfigSnapshot): RuntimeSandboxPolicy | null {
|
||||
return profile === config.startupPermissions.activePermissionProfile?.id
|
||||
? cloneRuntimeSandboxPolicy(config.startupPermissions.sandboxPolicy)
|
||||
: null;
|
||||
}
|
||||
|
||||
function cloneRuntimeSandboxPolicy(value: RuntimeSandboxPolicy | null): RuntimeSandboxPolicy | null {
|
||||
return cloneRuntimePermissionState({ approvalPolicy: null, activePermissionProfile: null, sandboxPolicy: value }).sandboxPolicy;
|
||||
}
|
||||
|
||||
function resolveRuntimePermissionValue<T>(input: {
|
||||
configured: T | null | undefined;
|
||||
active: T | null | undefined;
|
||||
pending: PendingRuntimeIntent<T>;
|
||||
activeKnown: boolean;
|
||||
}): RuntimeLayeredValue<T> {
|
||||
if (input.pending.kind !== "unchanged") {
|
||||
const value = resolveRuntimeValue({
|
||||
configured: input.configured,
|
||||
active: input.active,
|
||||
pending: input.pending,
|
||||
});
|
||||
return { ...value, ...confirmedRuntimeValue(input.configured, input.active, input.activeKnown) };
|
||||
}
|
||||
const configured = input.configured ?? null;
|
||||
const active = input.active ?? null;
|
||||
const pending = input.pending;
|
||||
const { confirmed, confirmedSource } = confirmedRuntimeValue(input.configured, input.active, input.activeKnown);
|
||||
return { configured, active, pending, confirmed, confirmedSource, effective: confirmed, source: confirmedSource };
|
||||
}
|
||||
|
||||
function resolveNullableRuntimePermissionValue<T>(input: {
|
||||
configured: T | null | undefined;
|
||||
active: T | null | undefined;
|
||||
pending: PendingRuntimeIntent<T | null>;
|
||||
activeKnown: boolean;
|
||||
}): RuntimeNullableLayeredValue<T> {
|
||||
if (input.pending.kind !== "unchanged") {
|
||||
const value = resolveRuntimeValue({
|
||||
configured: input.configured,
|
||||
active: input.active,
|
||||
pending: input.pending,
|
||||
});
|
||||
return { ...value, ...confirmedRuntimeValue(input.configured, input.active, input.activeKnown) };
|
||||
}
|
||||
const configured = input.configured ?? null;
|
||||
const active = input.active ?? null;
|
||||
const pending = input.pending;
|
||||
const { confirmed, confirmedSource } = confirmedRuntimeValue(input.configured, input.active, input.activeKnown);
|
||||
return { configured, active, pending, confirmed, confirmedSource, effective: confirmed, source: confirmedSource };
|
||||
}
|
||||
|
||||
function resolveRuntimeValue<T>(input: {
|
||||
|
|
@ -178,19 +219,26 @@ function resolveRuntimeValue<T>(input: {
|
|||
const configured = input.configured ?? null;
|
||||
const active = input.active ?? null;
|
||||
const pending = input.pending ?? ({ kind: "unchanged" } satisfies PendingRuntimeIntent<T>);
|
||||
const { confirmed, confirmedSource } = confirmedRuntimeValue(configured, active, active !== null);
|
||||
if (pending.kind === "set") {
|
||||
return { configured, active, pending, effective: pending.value, source: "pending" };
|
||||
return { configured, active, pending, confirmed, confirmedSource, effective: pending.value, source: "pending" };
|
||||
}
|
||||
if (pending.kind === "resetToConfig") {
|
||||
return { configured, active, pending, effective: configured, source: "config" };
|
||||
return { configured, active, pending, confirmed, confirmedSource, effective: configured, source: "config" };
|
||||
}
|
||||
if (active !== null) {
|
||||
return { configured, active, pending, effective: active, source: "active-thread" };
|
||||
}
|
||||
if (configured !== null) {
|
||||
return { configured, active, pending, effective: configured, source: "config" };
|
||||
}
|
||||
return { configured, active, pending, effective: null, source: "none" };
|
||||
return { configured, active, pending, confirmed, confirmedSource, effective: confirmed, source: confirmedSource };
|
||||
}
|
||||
|
||||
function confirmedRuntimeValue<T>(
|
||||
configured: T | null | undefined,
|
||||
active: T | null | undefined,
|
||||
activeKnown: boolean,
|
||||
): Pick<RuntimeLayeredValue<T>, "confirmed" | "confirmedSource"> {
|
||||
const configuredValue = configured ?? null;
|
||||
const activeValue = active ?? null;
|
||||
if (activeKnown) return { confirmed: activeValue, confirmedSource: "active-thread" };
|
||||
if (configuredValue !== null) return { confirmed: configuredValue, confirmedSource: "config" };
|
||||
return { confirmed: null, confirmedSource: "none" };
|
||||
}
|
||||
|
||||
function resolveServiceTier(snapshot: RuntimeSnapshot, config: RuntimeConfigSnapshot): RuntimeLayeredValue<ServiceTier> {
|
||||
|
|
@ -220,10 +268,17 @@ function serviceTierValue(
|
|||
effective: ServiceTier | null,
|
||||
source: RuntimeValueSource,
|
||||
): RuntimeLayeredValue<ServiceTier> {
|
||||
const { confirmed, confirmedSource } = confirmedRuntimeValue(
|
||||
config.serviceTier,
|
||||
snapshot.active.serviceTier,
|
||||
snapshot.activeThreadId !== null && snapshot.active.serviceTierKnown,
|
||||
);
|
||||
return {
|
||||
configured: config.serviceTier,
|
||||
active: snapshot.active.serviceTier,
|
||||
pending: { kind: "unchanged" },
|
||||
confirmed,
|
||||
confirmedSource,
|
||||
effective,
|
||||
source,
|
||||
};
|
||||
|
|
@ -237,20 +292,25 @@ function resolveFastMode(
|
|||
return {
|
||||
requested,
|
||||
active: isFastServiceTier(serviceTier.effective, serviceTiers),
|
||||
confirmedActive: isFastServiceTier(serviceTier.confirmed, serviceTiers),
|
||||
source: serviceTier.source,
|
||||
confirmedSource: serviceTier.confirmedSource,
|
||||
effectiveServiceTier: serviceTier.effective,
|
||||
confirmedServiceTier: serviceTier.confirmed,
|
||||
serviceTierRequestValue: serviceTiers.find((tier) => tier.name.trim().toLowerCase() === "fast")?.id ?? "fast",
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCollaborationMode(snapshot: RuntimeSnapshot, model: string | null): CollaborationModeResolution {
|
||||
const active = snapshot.active.collaborationMode;
|
||||
const selected = snapshot.pending.collaborationMode;
|
||||
const effective = effectiveCollaborationMode(active);
|
||||
const dirty = selected !== effective;
|
||||
const pending = snapshot.pending.collaborationMode;
|
||||
const confirmed = effectiveCollaborationMode(active);
|
||||
const effective = pending.kind === "set" ? pending.value : confirmed;
|
||||
const dirty = pending.kind === "set";
|
||||
return {
|
||||
active,
|
||||
selected,
|
||||
pending,
|
||||
confirmed,
|
||||
effective,
|
||||
dirty,
|
||||
blockedReason: dirty && !model ? "missing-model" : null,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
import { normalizeReasoningEffort, type ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import {
|
||||
initialRuntimePermissionKnownState,
|
||||
initialRuntimePermissionState,
|
||||
type RuntimeApprovalPolicy,
|
||||
type RuntimePermissionKnownState,
|
||||
type RuntimePermissionState,
|
||||
} from "../../../../domain/runtime/permissions";
|
||||
import { type ApprovalsReviewer, parseServiceTier, type ServiceTier } from "../../../../domain/runtime/policy";
|
||||
import type { RuntimeSettingsPatch } from "../../../../domain/runtime/thread-settings";
|
||||
import {
|
||||
type ActiveCollaborationMode,
|
||||
type CollaborationModeIntent,
|
||||
type CollaborationModeSelection,
|
||||
type PendingRuntimeIntent,
|
||||
type RequestedFastMode,
|
||||
resetRuntimeIntentToConfig,
|
||||
setCollaborationModeIntent,
|
||||
setRuntimeIntentValue,
|
||||
unchangedCollaborationModeIntent,
|
||||
unchangedRuntimeIntent,
|
||||
} from "./intent";
|
||||
|
||||
|
|
@ -21,7 +26,7 @@ export interface ChatRuntimeState {
|
|||
readonly pending: PendingRuntimeIntentState;
|
||||
}
|
||||
|
||||
export interface ActiveThreadRuntimeState extends RuntimePermissionState {
|
||||
export interface ActiveThreadRuntimeState extends RuntimePermissionState, RuntimePermissionKnownState {
|
||||
readonly serviceTierKnown: boolean;
|
||||
readonly model: string | null;
|
||||
readonly reasoningEffort: ReasoningEffort | null;
|
||||
|
|
@ -36,13 +41,14 @@ export interface PendingRuntimeIntentState {
|
|||
readonly permissionProfile: PendingRuntimeIntent<string>;
|
||||
readonly approvalPolicy: PendingRuntimeIntent<RuntimeApprovalPolicy>;
|
||||
readonly approvalsReviewer: PendingRuntimeIntent<ApprovalsReviewer>;
|
||||
readonly collaborationMode: CollaborationModeSelection;
|
||||
readonly collaborationMode: CollaborationModeIntent;
|
||||
readonly fastMode: PendingRuntimeIntent<RequestedFastMode>;
|
||||
}
|
||||
|
||||
export function initialActiveChatRuntimeState(): ActiveThreadRuntimeState {
|
||||
return {
|
||||
...initialRuntimePermissionState(),
|
||||
...initialRuntimePermissionKnownState(),
|
||||
serviceTierKnown: false,
|
||||
model: null,
|
||||
reasoningEffort: null,
|
||||
|
|
@ -67,7 +73,7 @@ function initialPendingRuntimeIntentState(): PendingRuntimeIntentState {
|
|||
permissionProfile: unchangedRuntimeIntent(),
|
||||
approvalPolicy: unchangedRuntimeIntent(),
|
||||
approvalsReviewer: unchangedRuntimeIntent(),
|
||||
collaborationMode: "default",
|
||||
collaborationMode: unchangedCollaborationModeIntent(),
|
||||
fastMode: unchangedRuntimeIntent(),
|
||||
};
|
||||
}
|
||||
|
|
@ -141,7 +147,7 @@ export function setSelectedCollaborationModeRuntimeState(
|
|||
): ChatRuntimeState {
|
||||
return {
|
||||
...state,
|
||||
pending: { ...state.pending, collaborationMode },
|
||||
pending: { ...state.pending, collaborationMode: setCollaborationModeIntent(collaborationMode) },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +160,17 @@ export function commitAppliedRuntimeSettingsPatchState(state: ChatRuntimeState,
|
|||
...("effort" in update ? { reasoningEffort: normalizeReasoningEffort(update.effort) } : {}),
|
||||
...("serviceTier" in update ? { serviceTier: parseServiceTier(update.serviceTier), serviceTierKnown: true } : {}),
|
||||
...("approvalsReviewer" in update ? { approvalsReviewer: update.approvalsReviewer ?? null } : {}),
|
||||
...("approvalPolicy" in update ? { approvalPolicy: update.approvalPolicy ?? null } : {}),
|
||||
...("approvalPolicy" in update
|
||||
? { approvalPolicy: update.approvalPolicy ?? null, approvalPolicyKnown: update.approvalPolicy !== null }
|
||||
: {}),
|
||||
...("permissions" in update
|
||||
? {
|
||||
activePermissionProfile: update.permissions ? { id: update.permissions, extends: null } : null,
|
||||
sandboxPolicy: null,
|
||||
permissionProfileKnown: update.permissions !== null,
|
||||
sandboxPolicyKnown: update.permissions !== null,
|
||||
}
|
||||
: {}),
|
||||
...(update.collaborationMode ? { collaborationMode: update.collaborationMode.mode } : {}),
|
||||
},
|
||||
pending: {
|
||||
|
|
@ -165,6 +181,7 @@ export function commitAppliedRuntimeSettingsPatchState(state: ChatRuntimeState,
|
|||
...("approvalPolicy" in update ? { approvalPolicy: unchangedRuntimeIntent<RuntimeApprovalPolicy>() } : {}),
|
||||
...("approvalsReviewer" in update ? { approvalsReviewer: unchangedRuntimeIntent<ApprovalsReviewer>() } : {}),
|
||||
...("permissions" in update ? { permissionProfile: unchangedRuntimeIntent<string>() } : {}),
|
||||
...("collaborationMode" in update ? { collaborationMode: unchangedCollaborationModeIntent() } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ function requestedTurnCollaborationModeSettings(resolution: RuntimeControlsResol
|
|||
const effort = resolution.reasoningEffort.effective;
|
||||
if (!model) return { collaborationMode: null, warning: "missing-model" };
|
||||
return {
|
||||
collaborationMode: runtimeCollaborationModeSettings(resolution.collaborationMode.selected, model, effort),
|
||||
collaborationMode: runtimeCollaborationModeSettings(resolution.collaborationMode.effective, model, effort),
|
||||
warning: null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { ChatAppServerGateway } from "../../app-server/session-gateway";
|
|||
import { type ChatRuntimeSettingsActions, createChatRuntimeSettingsActions } from "../../application/runtime/settings-actions";
|
||||
import { runtimeSnapshotForChatState } from "../../application/runtime/snapshot";
|
||||
import type { ChatStateStore } from "../../application/state/store";
|
||||
import { collaborationModeIntentValue } from "../../domain/runtime/intent";
|
||||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../../domain/runtime/labels";
|
||||
import { type ChatPanelRuntimeProjection, createChatPanelRuntimeProjection } from "../../panel/runtime-status-projection";
|
||||
import type { ChatPanelEnvironment } from "../contracts";
|
||||
|
|
@ -52,5 +53,6 @@ export function createRuntimeBundle(
|
|||
}
|
||||
|
||||
function collaborationModeLabel(stateStore: ChatStateStore): string {
|
||||
return formatCollaborationModeLabel(stateStore.getState().runtime.pending.collaborationMode);
|
||||
const runtime = stateStore.getState().runtime;
|
||||
return formatCollaborationModeLabel(collaborationModeIntentValue(runtime.pending.collaborationMode, runtime.active.collaborationMode));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { runtimeConfigOrDefault } from "../../../domain/runtime/config";
|
||||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
import type { ChatState } from "../application/state/root-reducer";
|
||||
import type { MessageStreamNoticeSection } from "../domain/message-stream/items";
|
||||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../domain/runtime/labels";
|
||||
import { resolveRuntimeControls } from "../domain/runtime/resolution";
|
||||
import type { RuntimeSnapshot } from "../domain/runtime/snapshot";
|
||||
import { appServerDiagnosticSections } from "../presentation/runtime/diagnostic-sections";
|
||||
import { runtimePermissionSections } from "../presentation/runtime/permission-sections";
|
||||
|
|
@ -103,7 +105,10 @@ function noticeSectionsFromDiagnostics(
|
|||
}
|
||||
|
||||
function collaborationModeLabel(state: ChatState): string {
|
||||
return formatCollaborationModeLabel(state.runtime.pending.collaborationMode);
|
||||
const snapshot = runtimeSnapshot(state);
|
||||
return formatCollaborationModeLabel(
|
||||
resolveRuntimeControls(snapshot, runtimeConfigOrDefault(snapshot.runtimeConfig)).collaborationMode.effective,
|
||||
);
|
||||
}
|
||||
|
||||
function runtimeSnapshot(state: ChatState): RuntimeSnapshot {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ function composerMetaViewModel(
|
|||
const effort = resolution.reasoningEffort.effective;
|
||||
const composerContext = contextComposerMeter(context?.percent ?? null);
|
||||
const compactEffort = effort ? compactReasoningEffortLabel(effort) : null;
|
||||
const planActive = resolution.collaborationMode.selected === "plan";
|
||||
const planActive = resolution.collaborationMode.effective === "plan";
|
||||
const reviewActive = resolution.autoReview.active;
|
||||
const fastActive = resolution.fastMode.active;
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { runtimeConfigOrDefault } from "../../../../domain/runtime/config";
|
||||
import type { RuntimePermissionState, RuntimeSandboxPolicy } from "../../../../domain/runtime/permissions";
|
||||
import type { RuntimeApprovalPolicy, RuntimeSandboxPolicy } from "../../../../domain/runtime/permissions";
|
||||
import { resolveRuntimeControls } from "../../domain/runtime/resolution";
|
||||
import type { RuntimeSnapshot } from "../../domain/runtime/snapshot";
|
||||
import type { DiagnosticRow, DiagnosticSection } from "./diagnostic-sections";
|
||||
|
|
@ -15,37 +15,37 @@ export function runtimePermissionSections(input: RuntimePermissionSectionsInput)
|
|||
return [
|
||||
{
|
||||
title: "Permissions",
|
||||
rows: accessRows(resolution.permissions.effective, input.vaultPath),
|
||||
rows: accessRows(resolution.permissionProfile.confirmed, resolution.sandboxPolicy.confirmed, input.vaultPath),
|
||||
},
|
||||
{
|
||||
title: "Approvals",
|
||||
rows: approvalRows(resolution.permissions.effective, resolution.approvalsReviewer.effective),
|
||||
rows: approvalRows(resolution.approvalPolicy.confirmed, resolution.autoReview.confirmedActive),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function accessRows(permissions: RuntimePermissionState, vaultPath: string): DiagnosticRow[] {
|
||||
function accessRows(profile: string | null, sandbox: RuntimeSandboxPolicy | null, vaultPath: string): DiagnosticRow[] {
|
||||
return [
|
||||
{ label: "Profile", value: profileLabel(permissions) },
|
||||
{ label: "Sandbox", value: sandboxLabel(permissions.sandboxPolicy) },
|
||||
{ label: "Network", value: networkLabel(permissions.sandboxPolicy) },
|
||||
{ label: "Extra writable roots", value: writableRootsLabel(permissions.sandboxPolicy, vaultPath) },
|
||||
{ label: "Profile", value: profileLabel(profile, sandbox) },
|
||||
{ label: "Sandbox", value: sandboxLabel(sandbox) },
|
||||
{ label: "Network", value: networkLabel(sandbox) },
|
||||
{ label: "Extra writable roots", value: writableRootsLabel(sandbox, vaultPath) },
|
||||
];
|
||||
}
|
||||
|
||||
function approvalRows(permissions: RuntimePermissionState, reviewer: string | null): DiagnosticRow[] {
|
||||
function approvalRows(policy: RuntimeApprovalPolicy | null, autoReview: boolean): DiagnosticRow[] {
|
||||
return [
|
||||
{ label: "Approval policy", value: approvalPolicyLabel(permissions.approvalPolicy) },
|
||||
{ label: "Approval policy", value: approvalPolicyLabel(policy) },
|
||||
{
|
||||
label: "Reviewer",
|
||||
value: reviewer ?? "(Codex default)",
|
||||
label: "Auto review",
|
||||
value: autoReview ? "on" : "off",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function profileLabel(permissions: RuntimePermissionState): string {
|
||||
if (permissions.activePermissionProfile) return permissions.activePermissionProfile.id;
|
||||
if (permissions.sandboxPolicy) return "(legacy sandbox)";
|
||||
function profileLabel(profile: string | null, sandbox: RuntimeSandboxPolicy | null): string {
|
||||
if (profile) return profile;
|
||||
if (sandbox) return "(legacy sandbox)";
|
||||
return "(not reported)";
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ function displayRoot(root: string, vaultPath: string): string {
|
|||
return root;
|
||||
}
|
||||
|
||||
function approvalPolicyLabel(policy: RuntimePermissionState["approvalPolicy"]): string {
|
||||
function approvalPolicyLabel(policy: RuntimeApprovalPolicy | null): string {
|
||||
if (!policy) return "(not reported)";
|
||||
if (typeof policy === "string") return policy;
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ describe("chat app-server actions", () => {
|
|||
expect(stateStore.getState().runtime.pending.reasoningEffort).toEqual({ kind: "set", value: "high" });
|
||||
expect(stateStore.getState().runtime.pending.fastMode).toEqual({ kind: "set", value: "enabled" });
|
||||
expect(stateStore.getState().runtime.pending.approvalsReviewer).toEqual({ kind: "set", value: "auto_review" });
|
||||
expect(stateStore.getState().runtime.pending.collaborationMode).toBe("plan");
|
||||
expect(stateStore.getState().runtime.pending.collaborationMode).toEqual({ kind: "set", value: "plan" });
|
||||
});
|
||||
|
||||
it("can skip newly started thread goal sync when the caller sets the first goal", async () => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ function createHost(overrides: Partial<ChatReconnectActionsHost> = {}) {
|
|||
stateStore.dispatch({ type: "ui/panel-set", panel: "history" });
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -151,6 +151,9 @@ describe("submitComposer", () => {
|
|||
const { host, interruptTurn, showLatest, stateStore } = createHost("");
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ const streamingPlanItem = (id: string): MessageStreamItem => ({
|
|||
function resumeThread(stateStore: ChatStateStore, items: readonly MessageStreamItem[]): void {
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -99,7 +102,7 @@ describe("implementPlan", () => {
|
|||
|
||||
expect(ensureConnected).toHaveBeenCalledOnce();
|
||||
expect(requestDefaultCollaborationModeForNextTurn).toHaveBeenCalledOnce();
|
||||
expect(stateStore.getState().runtime.pending.collaborationMode).toBe("default");
|
||||
expect(stateStore.getState().runtime.pending.collaborationMode).toEqual({ kind: "set", value: "default" });
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
expect(sendTurnText).toHaveBeenCalledWith("Please implement this plan.");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -563,7 +563,7 @@ describe("slash commands", () => {
|
|||
it("shows permissions and approvals as shared structured sections", async () => {
|
||||
const details = [
|
||||
{ title: "Permissions", auditFacts: [{ key: "Profile", value: "workspace-write" }] },
|
||||
{ title: "Approvals", auditFacts: [{ key: "Reviewer", value: "auto_review" }] },
|
||||
{ title: "Approvals", auditFacts: [{ key: "Auto review", value: "on" }] },
|
||||
];
|
||||
const ctx = context({ permissionDetails: () => details });
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ describe("executeSlashCommandWithState", () => {
|
|||
});
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -110,6 +113,9 @@ describe("executeSlashCommandWithState", () => {
|
|||
const { compactThread, host, stateStore } = createHost({ connectionAvailable: () => false });
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ function createHost(overrides: TurnSubmissionHostOverrides = {}) {
|
|||
function resumeThread(stateStore: ReturnType<typeof createChatStateStore>) {
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { runtimeSnapshotForChatState } from "../../../../../src/features/chat/ap
|
|||
import type { ActiveThreadSettingsAppliedAction } from "../../../../../src/features/chat/application/state/actions";
|
||||
import type { ChatState } from "../../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore } from "../../../../../src/features/chat/application/state/store";
|
||||
import { setRuntimeIntentValue } from "../../../../../src/features/chat/domain/runtime/intent";
|
||||
import { chatStateFixture, chatStateWith } from "../../support/state";
|
||||
|
||||
describe("createChatRuntimeSettingsActions", () => {
|
||||
|
|
@ -35,6 +36,36 @@ describe("createChatRuntimeSettingsActions", () => {
|
|||
expect(messages).toEqual([]);
|
||||
});
|
||||
|
||||
it("commits pending permission profile updates into active permission state", async () => {
|
||||
let state = chatStateFixture();
|
||||
state = chatStateWith(state, { activeThread: { id: "thread" } });
|
||||
state = chatStateWith(state, {
|
||||
runtime: {
|
||||
active: {
|
||||
activePermissionProfile: { id: ":read-only", extends: null },
|
||||
sandboxPolicy: { type: "readOnly", networkAccess: false },
|
||||
},
|
||||
pending: {
|
||||
permissionProfile: setRuntimeIntentValue(":workspace"),
|
||||
},
|
||||
},
|
||||
});
|
||||
const store = createChatStateStore(state);
|
||||
const transport = settingsTransportFixture();
|
||||
const messages: string[] = [];
|
||||
const controller = runtimeControllerFixture(store, transport, messages);
|
||||
|
||||
await expect(controller.applyPendingThreadSettings()).resolves.toBe(true);
|
||||
|
||||
expect(transport.updateThreadSettings).toHaveBeenCalledWith("thread", { permissions: ":workspace" });
|
||||
expect(store.getState().runtime.pending.permissionProfile).toEqual({ kind: "unchanged" });
|
||||
expect(store.getState().runtime.active.activePermissionProfile).toEqual({ id: ":workspace", extends: null });
|
||||
expect(store.getState().runtime.active.permissionProfileKnown).toBe(true);
|
||||
expect(store.getState().runtime.active.sandboxPolicy).toBeNull();
|
||||
expect(store.getState().runtime.active.sandboxPolicyKnown).toBe(true);
|
||||
expect(messages).toEqual([]);
|
||||
});
|
||||
|
||||
it("reserves thread runtime settings when no thread is active", async () => {
|
||||
const store = createChatStateStore(chatStateFixture());
|
||||
const transport = settingsTransportFixture();
|
||||
|
|
@ -53,7 +84,7 @@ describe("createChatRuntimeSettingsActions", () => {
|
|||
expect(store.getState().runtime.pending.reasoningEffort).toEqual({ kind: "set", value: "high" });
|
||||
expect(store.getState().runtime.pending.fastMode).toEqual({ kind: "set", value: "enabled" });
|
||||
expect(store.getState().runtime.pending.approvalsReviewer).toEqual({ kind: "set", value: "auto_review" });
|
||||
expect(store.getState().runtime.pending.collaborationMode).toBe("default");
|
||||
expect(store.getState().runtime.pending.collaborationMode).toEqual({ kind: "set", value: "default" });
|
||||
expect(messages).toEqual([
|
||||
"Fast mode on for subsequent turns.",
|
||||
"Auto-review on for subsequent turns.",
|
||||
|
|
@ -154,7 +185,7 @@ describe("createChatRuntimeSettingsActions", () => {
|
|||
await expect(controller.setCollaborationMode("plan")).resolves.toBe(true);
|
||||
|
||||
expect(transport.updateThreadSettings).not.toHaveBeenCalled();
|
||||
expect(store.getState().runtime.pending.collaborationMode).toBe("plan");
|
||||
expect(store.getState().runtime.pending.collaborationMode).toEqual({ kind: "set", value: "plan" });
|
||||
expect(store.getState().runtime.active.collaborationMode).toBeNull();
|
||||
expect(messages).toEqual(["Plan mode is selected, but No effective model is available. Sending without a mode override."]);
|
||||
});
|
||||
|
|
@ -163,7 +194,7 @@ describe("createChatRuntimeSettingsActions", () => {
|
|||
let state = chatStateFixture();
|
||||
state = chatStateWith(state, { activeThread: { id: "thread" } });
|
||||
state = chatStateWith(state, { runtime: { active: { collaborationMode: "plan" } } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: "plan" } } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: { kind: "set", value: "plan" } } } });
|
||||
const store = createChatStateStore(state);
|
||||
const transport = settingsTransportFixture();
|
||||
const messages: string[] = [];
|
||||
|
|
@ -172,7 +203,7 @@ describe("createChatRuntimeSettingsActions", () => {
|
|||
controller.requestDefaultCollaborationModeForNextTurn();
|
||||
|
||||
expect(transport.updateThreadSettings).not.toHaveBeenCalled();
|
||||
expect(store.getState().runtime.pending.collaborationMode).toBe("default");
|
||||
expect(store.getState().runtime.pending.collaborationMode).toEqual({ kind: "set", value: "default" });
|
||||
expect(store.getState().runtime.active.collaborationMode).toBe("plan");
|
||||
expect(messages).toEqual([]);
|
||||
});
|
||||
|
|
@ -425,6 +456,9 @@ function threadSettings(
|
|||
collaborationMode: "default",
|
||||
serviceTier,
|
||||
approvalsReviewer,
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ describe("chat thread resume helpers", () => {
|
|||
reasoningEffort: "high",
|
||||
serviceTier: "fast",
|
||||
serviceTierKnown: true,
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalsReviewer: "user",
|
||||
approvalPolicy: "on-request",
|
||||
sandboxPolicy: {
|
||||
|
|
@ -34,6 +37,9 @@ describe("chat thread resume helpers", () => {
|
|||
|
||||
expect(action).toMatchObject({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
thread: resumed,
|
||||
cwd: "/vault",
|
||||
model: "gpt-5.5",
|
||||
|
|
@ -65,6 +71,9 @@ describe("chat thread resume helpers", () => {
|
|||
|
||||
expect(action).toMatchObject({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: "on-request",
|
||||
sandboxPolicy: {
|
||||
type: "workspaceWrite",
|
||||
|
|
@ -87,6 +96,9 @@ function responseFixture(thread: Thread): ThreadActivationSnapshot {
|
|||
model: "gpt-5.5",
|
||||
serviceTier: "fast",
|
||||
cwd: "/vault",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalsReviewer: "user",
|
||||
reasoningEffort: "high",
|
||||
approvalPolicy: "on-request",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe("chatReducer", () => {
|
|||
state = chatStateWith(state, { runtime: { active: { serviceTier: "fast" } } });
|
||||
state = chatStateWith(state, { runtime: { active: { approvalsReviewer: "auto_review" } } });
|
||||
state = chatStateWith(state, { runtime: { active: { collaborationMode: "plan" } } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: "plan" } } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: { kind: "set", value: "plan" } } } });
|
||||
state = chatStateWith(state, { activeThread: { goal: goal("thread") } });
|
||||
state = chatStateWith(state, { messageStream: { historyCursor: "cursor" } });
|
||||
state = chatStateWith(state, { messageStream: { loadingHistory: true } });
|
||||
|
|
@ -56,7 +56,7 @@ describe("chatReducer", () => {
|
|||
expect(next.runtime.pending.reasoningEffort).toEqual({ kind: "unchanged" });
|
||||
expect(next.runtime.pending.fastMode).toEqual({ kind: "unchanged" });
|
||||
expect(next.runtime.pending.approvalsReviewer).toEqual({ kind: "unchanged" });
|
||||
expect(next.runtime.pending.collaborationMode).toBe("default");
|
||||
expect(next.runtime.pending.collaborationMode).toEqual({ kind: "unchanged" });
|
||||
expect(activeTurnId(next)).toBeNull();
|
||||
expect(chatStateMessageStreamItems(next)).toEqual([]);
|
||||
expect(next.messageStream.turnDiffs.size).toBe(0);
|
||||
|
|
@ -91,7 +91,7 @@ describe("chatReducer", () => {
|
|||
state = chatStateWith(state, { composer: { suggestions: [suggestion("/plan")] } });
|
||||
state = chatStateWith(state, { composer: { suggestionsDismissedSignature: "dismissed" } });
|
||||
state = chatStateWith(state, { runtime: { active: { collaborationMode: "plan" } } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: "plan" } } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: { kind: "set", value: "plan" } } } });
|
||||
state = chatStateWith(state, { ui: { disclosures: { approvalDetails: new Set(["1:details"]) } } });
|
||||
state = chatStateWith(state, { ui: { disclosures: { textDetails: new Set(["previous:details"]) } } });
|
||||
state = chatStateWith(state, { ui: { messageActionMenu: { forkMenuItemId: "previous" } } });
|
||||
|
|
@ -102,6 +102,9 @@ describe("chatReducer", () => {
|
|||
|
||||
const next = chatReducer(state, {
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -129,7 +132,7 @@ describe("chatReducer", () => {
|
|||
expect(next.composer.suggestions).toEqual([]);
|
||||
expect(next.composer.suggestionsDismissedSignature).toBeNull();
|
||||
expect(next.runtime.active.collaborationMode).toBeNull();
|
||||
expect(next.runtime.pending.collaborationMode).toBe("default");
|
||||
expect(next.runtime.pending.collaborationMode).toEqual({ kind: "unchanged" });
|
||||
expect(uiDisclosureCount(next)).toBe(0);
|
||||
expect(next.ui.messageActionMenu.forkMenuItemId).toBeNull();
|
||||
expect(next.ui.goalEditor.kind).toBe("closed");
|
||||
|
|
@ -145,6 +148,9 @@ describe("chatReducer", () => {
|
|||
|
||||
const next = chatReducer(state, {
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -166,7 +172,7 @@ describe("chatReducer", () => {
|
|||
expect(next.runtime.pending.reasoningEffort).toEqual({ kind: "set", value: "high" });
|
||||
expect(next.runtime.pending.fastMode).toEqual({ kind: "set", value: "enabled" });
|
||||
expect(next.runtime.pending.approvalsReviewer).toEqual({ kind: "set", value: "auto_review" });
|
||||
expect(next.runtime.pending.collaborationMode).toBe("plan");
|
||||
expect(next.runtime.pending.collaborationMode).toEqual({ kind: "set", value: "plan" });
|
||||
expect(next.runtime.active.collaborationMode).toBeNull();
|
||||
});
|
||||
|
||||
|
|
@ -176,6 +182,9 @@ describe("chatReducer", () => {
|
|||
|
||||
const next = chatReducer(state, {
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -650,6 +659,9 @@ describe("chatReducer", () => {
|
|||
it("preserves unknown service tier state when resuming from active runtime", () => {
|
||||
const state = chatReducer(chatStateFixture(), {
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -715,6 +727,9 @@ describe("chatReducer", () => {
|
|||
|
||||
panelA.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -731,6 +746,9 @@ describe("chatReducer", () => {
|
|||
|
||||
panelB.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ describe("createActiveThreadIdentitySync", () => {
|
|||
const { controller, host, restoredClear, stateStore } = createController();
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -69,6 +72,9 @@ describe("createActiveThreadIdentitySync", () => {
|
|||
const { controller, host, restoredClear, stateStore } = createController();
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -107,6 +113,9 @@ describe("createActiveThreadIdentitySync", () => {
|
|||
const { controller, host, restoredRename, stateStore } = createController();
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -187,6 +187,9 @@ describe("createGoalActions", () => {
|
|||
const startThread = vi.fn().mockImplementation(async () => {
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ function activation(threadId: string, overrides: Partial<ThreadResumeSnapshot> =
|
|||
cwd: "/vault",
|
||||
model: "gpt-test",
|
||||
serviceTier: null,
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalsReviewer: "user",
|
||||
reasoningEffort: null,
|
||||
approvalPolicy: null,
|
||||
|
|
@ -120,6 +123,9 @@ describe("ResumeActions", () => {
|
|||
const { actions, host, resumeThread, stateStore } = createActions();
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -165,6 +171,9 @@ describe("ResumeActions", () => {
|
|||
await actions.resumeThread("thread");
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ describe("thread management actions", () => {
|
|||
host.threadTransport.compactThread.mockReturnValue(compact.promise);
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -100,6 +103,9 @@ describe("thread management actions", () => {
|
|||
});
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -241,6 +247,9 @@ describe("thread management actions", () => {
|
|||
host.threadTransport.forkThread.mockReturnValue(fork.promise);
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -264,6 +273,9 @@ describe("thread management actions", () => {
|
|||
});
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -325,6 +337,9 @@ describe("thread management actions", () => {
|
|||
const host = hostMock({ items: turnItems() });
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -360,6 +375,9 @@ describe("thread management actions", () => {
|
|||
host.threadTransport.rollbackThread.mockReturnValue(rollback.promise);
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -384,6 +402,9 @@ describe("thread management actions", () => {
|
|||
});
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -412,6 +433,9 @@ describe("thread management actions", () => {
|
|||
});
|
||||
host.stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import {
|
|||
function resumeThreadState(stateStore: ChatStateStore, threadId: string): void {
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ describe("createReconnectAction", () => {
|
|||
const stateStore = createChatStateStore();
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -158,6 +158,9 @@ describe("createChatPanelSessionGraph actions", () => {
|
|||
});
|
||||
stateStore.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,50 @@ describe("createChatPanelRuntimeProjection", () => {
|
|||
title: "Approvals",
|
||||
auditFacts: [
|
||||
{ key: "Approval policy", value: "on-request" },
|
||||
{ key: "Reviewer", value: "auto_review" },
|
||||
{ key: "Auto review", value: "on" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps pending approval reviewer out of diagnostic permission details", () => {
|
||||
const state = chatStateWith(chatStateFixture(), {
|
||||
activeThread: { id: "thread-1" },
|
||||
connection: {
|
||||
runtimeConfig: runtimeConfigFixture({
|
||||
approvals_reviewer: "user",
|
||||
approval_policy: "on-request",
|
||||
}),
|
||||
},
|
||||
runtime: {
|
||||
pending: {
|
||||
approvalsReviewer: { kind: "set", value: "auto_review" },
|
||||
},
|
||||
},
|
||||
});
|
||||
const projection = createChatPanelRuntimeProjection({
|
||||
state: () => state,
|
||||
connected: () => true,
|
||||
configuredCommand: () => "codex",
|
||||
vaultPath: () => "/vault",
|
||||
nowMs: () => 0,
|
||||
});
|
||||
|
||||
expect(projection.permissionDetails()).toEqual([
|
||||
{
|
||||
title: "Permissions",
|
||||
auditFacts: [
|
||||
{ key: "Profile", value: "(not reported)" },
|
||||
{ key: "Sandbox", value: "(not reported)" },
|
||||
{ key: "Network", value: "(not reported)" },
|
||||
{ key: "Extra writable roots", value: "(not reported)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Approvals",
|
||||
auditFacts: [
|
||||
{ key: "Approval policy", value: "on-request" },
|
||||
{ key: "Auto review", value: "off" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ describe("MessageStreamPresenter scroll pinning", () => {
|
|||
const store = createChatStateStore(chatStateFixture());
|
||||
store.dispatch({
|
||||
type: "active-thread/resumed",
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
activePermissionProfile: null,
|
||||
|
|
|
|||
|
|
@ -149,7 +149,8 @@ describe("chat panel surface projections", () => {
|
|||
);
|
||||
expect(parent.textContent).toContain(":workspace");
|
||||
expect(parent.textContent).toContain("on-request");
|
||||
expect(parent.textContent).toContain("auto_review");
|
||||
expect(parent.textContent).toContain("off");
|
||||
expect(parent.textContent).not.toContain("auto_review");
|
||||
expect(parent.textContent).not.toContain("user -> auto_review");
|
||||
unmountUiRoot(parent);
|
||||
});
|
||||
|
|
@ -157,7 +158,7 @@ describe("chat panel surface projections", () => {
|
|||
it("builds composer meta from context and runtime state", () => {
|
||||
let state = chatStateFixture();
|
||||
state = chatStateWith(state, { activeThread: { id: "thread-1" } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: "plan" } } });
|
||||
state = chatStateWith(state, { runtime: { pending: { collaborationMode: { kind: "set", value: "plan" } } } });
|
||||
state = chatStateWith(state, {
|
||||
connection: {
|
||||
runtimeConfig: runtimeConfigFixture({
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ function runtimeWithPatch(runtime: ChatState["runtime"], patch: RuntimePatch): C
|
|||
active: {
|
||||
...runtime.active,
|
||||
...(patch.active && "serviceTier" in patch.active ? { serviceTierKnown: true } : {}),
|
||||
...(patch.active && "approvalPolicy" in patch.active ? { approvalPolicyKnown: true } : {}),
|
||||
...(patch.active && "sandboxPolicy" in patch.active ? { sandboxPolicyKnown: true } : {}),
|
||||
...(patch.active && "activePermissionProfile" in patch.active ? { permissionProfileKnown: true } : {}),
|
||||
...patch.active,
|
||||
},
|
||||
pending: {
|
||||
|
|
|
|||
|
|
@ -799,7 +799,7 @@ describe("message stream rendering and message action menu", () => {
|
|||
const baseState = {
|
||||
activeThread: { id: "thread" },
|
||||
turn: { lifecycle: { kind: "idle" as const } },
|
||||
runtime: { pending: { collaborationMode: "plan" as const } },
|
||||
runtime: { pending: { collaborationMode: { kind: "set", value: "plan" } as const } },
|
||||
messageStream: {
|
||||
stableItems: [
|
||||
firstPlan,
|
||||
|
|
@ -818,7 +818,9 @@ describe("message stream rendering and message action menu", () => {
|
|||
};
|
||||
|
||||
expect(implementPlanTargetFromState(baseState)).toEqual({ itemId: secondPlan.id });
|
||||
expect(implementPlanTargetFromState({ ...baseState, runtime: { pending: { collaborationMode: "default" } } })).toBeNull();
|
||||
expect(
|
||||
implementPlanTargetFromState({ ...baseState, runtime: { pending: { collaborationMode: { kind: "set", value: "default" } } } }),
|
||||
).toBeNull();
|
||||
expect(implementPlanTargetFromState({ ...baseState, turn: { lifecycle: { kind: "running", turnId: "turn-2" } } })).toBeNull();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ describe("Toolbar decisions", () => {
|
|||
title: "Approvals",
|
||||
rows: [
|
||||
{ label: "Approval policy", value: "on-request" },
|
||||
{ label: "Reviewer", value: "auto_review" },
|
||||
{ label: "Auto review", value: "on" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -229,7 +229,7 @@ describe("Toolbar decisions", () => {
|
|||
);
|
||||
expect(parent.textContent).toContain(":workspace");
|
||||
expect(parent.textContent).toContain("workspace-write");
|
||||
expect(parent.textContent).toContain("auto_review");
|
||||
expect(parent.textContent).toContain("on");
|
||||
expect(parent.querySelector(".codex-panel__status-diagnostics-row--warning")).toBeNull();
|
||||
expect(parent.querySelector(".codex-panel__status-diagnostics-row--error")).toBeNull();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -99,22 +99,17 @@ describe("runtime settings", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("treats active thread permissions as authoritative over startup config", () => {
|
||||
it("falls back to startup permissions until active thread permissions are reported", () => {
|
||||
const configured = runtimeSnapshot({
|
||||
runtimeConfig: runtimeConfigFixture({
|
||||
default_permissions: ":workspace",
|
||||
approval_policy: "on-request",
|
||||
}),
|
||||
});
|
||||
expect(resolveRuntimeControls(configured, snapshotConfig(configured)).permissions).toMatchObject({
|
||||
scope: "new-thread",
|
||||
source: "config",
|
||||
active: null,
|
||||
effective: {
|
||||
activePermissionProfile: { id: ":workspace", extends: null },
|
||||
approvalPolicy: "on-request",
|
||||
sandboxPolicy: null,
|
||||
},
|
||||
expect(resolveRuntimeControls(configured, snapshotConfig(configured))).toMatchObject({
|
||||
permissionProfile: { effective: ":workspace", source: "config" },
|
||||
sandboxPolicy: { effective: null, source: "none" },
|
||||
approvalPolicy: { effective: "on-request", source: "config" },
|
||||
});
|
||||
|
||||
const activeUnreported = runtimeSnapshot({
|
||||
|
|
@ -124,19 +119,18 @@ describe("runtime settings", () => {
|
|||
approval_policy: "on-request",
|
||||
}),
|
||||
});
|
||||
expect(resolveRuntimeControls(activeUnreported, snapshotConfig(activeUnreported)).permissions).toMatchObject({
|
||||
scope: "current-thread",
|
||||
source: "active-thread",
|
||||
effective: {
|
||||
activePermissionProfile: null,
|
||||
approvalPolicy: null,
|
||||
sandboxPolicy: null,
|
||||
},
|
||||
expect(resolveRuntimeControls(activeUnreported, snapshotConfig(activeUnreported))).toMatchObject({
|
||||
permissionProfile: { configured: ":workspace", active: null, effective: ":workspace", source: "config" },
|
||||
sandboxPolicy: { configured: null, active: null, effective: null, source: "none" },
|
||||
approvalPolicy: { configured: "on-request", active: null, effective: "on-request", source: "config" },
|
||||
});
|
||||
|
||||
const activeReported = runtimeSnapshot({
|
||||
activeThreadId: "thread",
|
||||
active: {
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: "never",
|
||||
sandboxPolicy: { type: "readOnly", networkAccess: false },
|
||||
activePermissionProfile: { id: ":read-only", extends: null },
|
||||
|
|
@ -146,22 +140,36 @@ describe("runtime settings", () => {
|
|||
});
|
||||
expect(resolveRuntimeControls(activeReported, snapshotConfig(activeReported))).toMatchObject({
|
||||
approvalsReviewer: { effective: "auto_review", source: "pending" },
|
||||
permissions: {
|
||||
scope: "current-thread",
|
||||
source: "active-thread",
|
||||
effective: {
|
||||
activePermissionProfile: { id: ":read-only", extends: null },
|
||||
approvalPolicy: "never",
|
||||
sandboxPolicy: { type: "readOnly", networkAccess: false },
|
||||
},
|
||||
permissionProfile: { effective: ":read-only", source: "active-thread" },
|
||||
sandboxPolicy: { effective: { type: "readOnly", networkAccess: false }, source: "active-thread" },
|
||||
approvalPolicy: { effective: "never", source: "active-thread" },
|
||||
});
|
||||
|
||||
const approvalOnlyReported = runtimeSnapshot({
|
||||
activeThreadId: "thread",
|
||||
runtimeConfig: runtimeConfigFixture({
|
||||
default_permissions: ":workspace",
|
||||
approval_policy: "on-request",
|
||||
}),
|
||||
active: {
|
||||
approvalPolicyKnown: true,
|
||||
approvalPolicy: "never",
|
||||
},
|
||||
});
|
||||
expect(resolveRuntimeControls(approvalOnlyReported, snapshotConfig(approvalOnlyReported))).toMatchObject({
|
||||
permissionProfile: { effective: ":workspace", source: "config" },
|
||||
sandboxPolicy: { effective: null, source: "none" },
|
||||
approvalPolicy: { effective: "never", source: "active-thread" },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps permission display scope separate from pending permission source", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
activeThreadId: "thread",
|
||||
active: {
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
approvalPolicy: "on-request",
|
||||
sandboxPolicy: { type: "readOnly", networkAccess: false },
|
||||
activePermissionProfile: null,
|
||||
|
|
@ -172,14 +180,10 @@ describe("runtime settings", () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(resolveRuntimeControls(snapshot, snapshotConfig(snapshot)).permissions).toMatchObject({
|
||||
scope: "current-thread",
|
||||
source: "pending",
|
||||
effective: {
|
||||
activePermissionProfile: { id: ":workspace", extends: null },
|
||||
approvalPolicy: "never",
|
||||
sandboxPolicy: null,
|
||||
},
|
||||
expect(resolveRuntimeControls(snapshot, snapshotConfig(snapshot))).toMatchObject({
|
||||
permissionProfile: { effective: ":workspace", source: "pending" },
|
||||
sandboxPolicy: { effective: null, source: "pending" },
|
||||
approvalPolicy: { effective: "never", source: "pending" },
|
||||
});
|
||||
expect(pendingRuntimeSettingsPatch(snapshot, snapshotConfig(snapshot))).toMatchObject({
|
||||
update: {
|
||||
|
|
@ -202,22 +206,23 @@ describe("runtime settings", () => {
|
|||
},
|
||||
}),
|
||||
active: {
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
activePermissionProfile: { id: ":workspace", extends: null },
|
||||
sandboxPolicy: null,
|
||||
},
|
||||
pending: { permissionProfile: resetRuntimeIntentToConfig() },
|
||||
});
|
||||
|
||||
expect(resolveRuntimeControls(snapshot, snapshotConfig(snapshot)).permissions).toMatchObject({
|
||||
scope: "current-thread",
|
||||
source: "pending",
|
||||
effective: {
|
||||
activePermissionProfile: null,
|
||||
sandboxPolicy: {
|
||||
expect(resolveRuntimeControls(snapshot, snapshotConfig(snapshot))).toMatchObject({
|
||||
permissionProfile: { effective: null, source: "config" },
|
||||
sandboxPolicy: {
|
||||
effective: {
|
||||
type: "workspaceWrite",
|
||||
writableRoots: ["/vault"],
|
||||
networkAccess: false,
|
||||
},
|
||||
source: "config",
|
||||
},
|
||||
});
|
||||
expect(pendingRuntimeSettingsPatch(snapshot, snapshotConfig(snapshot))).toMatchObject({
|
||||
|
|
@ -261,7 +266,9 @@ describe("runtime settings", () => {
|
|||
});
|
||||
|
||||
it("treats unreported thread collaboration mode as default without losing the unknown state", () => {
|
||||
const snapshot = runtimeSnapshot({ active: { collaborationMode: null }, pending: { collaborationMode: "default" } });
|
||||
const snapshot = runtimeSnapshot({
|
||||
active: { collaborationMode: null },
|
||||
});
|
||||
|
||||
expect(snapshot.active.collaborationMode).toBeNull();
|
||||
expect(pendingRuntimeSettingsPatch(snapshot, snapshotConfig(snapshot))).toEqual({
|
||||
|
|
@ -269,7 +276,7 @@ describe("runtime settings", () => {
|
|||
collaborationModeWarning: null,
|
||||
});
|
||||
|
||||
expect(snapshot.pending.collaborationMode).toBe("default");
|
||||
expect(snapshot.pending.collaborationMode).toEqual({ kind: "unchanged" });
|
||||
});
|
||||
|
||||
it("keeps model reset tied to config when active thread model differs", () => {
|
||||
|
|
@ -293,7 +300,7 @@ describe("runtime settings", () => {
|
|||
it("builds the Plan collaboration mode payload from selected runtime settings", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
pending: {
|
||||
collaborationMode: "plan",
|
||||
collaborationMode: { kind: "set", value: "plan" },
|
||||
model: setRuntimeIntentValue("gpt-5.5"),
|
||||
reasoningEffort: setRuntimeIntentValue("high"),
|
||||
},
|
||||
|
|
@ -316,7 +323,7 @@ describe("runtime settings", () => {
|
|||
|
||||
it("uses the explicit config for collaboration mode thread settings", () => {
|
||||
const snapshot = runtimeSnapshot({
|
||||
pending: { collaborationMode: "plan" },
|
||||
pending: { collaborationMode: { kind: "set", value: "plan" } },
|
||||
runtimeConfig: runtimeConfigFixture({
|
||||
model: "snapshot-model",
|
||||
model_reasoning_effort: "low",
|
||||
|
|
@ -345,12 +352,12 @@ describe("runtime settings", () => {
|
|||
it("keeps collaboration mode settings separate from reviewer and direct runtime intents", () => {
|
||||
const reviewerSnapshot = runtimeSnapshot({
|
||||
pending: {
|
||||
collaborationMode: "plan",
|
||||
collaborationMode: { kind: "set", value: "plan" },
|
||||
approvalsReviewer: setRuntimeIntentValue("auto_review"),
|
||||
},
|
||||
});
|
||||
const activeRuntimeSnapshot = runtimeSnapshot({
|
||||
pending: { collaborationMode: "plan" },
|
||||
pending: { collaborationMode: { kind: "set", value: "plan" } },
|
||||
active: { model: "gpt-5-active", serviceTier: "fast" },
|
||||
runtimeConfig: runtimeConfigFixture({}),
|
||||
});
|
||||
|
|
@ -541,6 +548,15 @@ describe("runtime settings", () => {
|
|||
runtimeConfig: runtimeConfigFixture({
|
||||
model: "gpt-config",
|
||||
model_reasoning_effort: "medium",
|
||||
default_permissions: ":workspace",
|
||||
approval_policy: "on-request",
|
||||
sandbox_mode: "workspace-write",
|
||||
sandbox_workspace_write: {
|
||||
writable_roots: ["/vault"],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
},
|
||||
approvals_reviewer: "auto_review",
|
||||
service_tier: "fast",
|
||||
}),
|
||||
|
|
@ -549,8 +565,14 @@ describe("runtime settings", () => {
|
|||
...configured,
|
||||
activeThreadId: "thread",
|
||||
active: {
|
||||
approvalPolicyKnown: true,
|
||||
sandboxPolicyKnown: true,
|
||||
permissionProfileKnown: true,
|
||||
model: "gpt-active",
|
||||
reasoningEffort: "high",
|
||||
activePermissionProfile: { id: ":read-only", extends: null },
|
||||
sandboxPolicy: { type: "readOnly", networkAccess: false },
|
||||
approvalPolicy: "never",
|
||||
approvalsReviewer: "user",
|
||||
serviceTier: "flex",
|
||||
},
|
||||
|
|
@ -561,6 +583,8 @@ describe("runtime settings", () => {
|
|||
...active.pending,
|
||||
model: setRuntimeIntentValue("gpt-pending"),
|
||||
reasoningEffort: setRuntimeIntentValue("low"),
|
||||
permissionProfile: setRuntimeIntentValue(":workspace"),
|
||||
approvalPolicy: setRuntimeIntentValue("on-failure"),
|
||||
approvalsReviewer: setRuntimeIntentValue("guardian_subagent"),
|
||||
fastMode: setRuntimeIntentValue("enabled"),
|
||||
},
|
||||
|
|
@ -569,45 +593,67 @@ describe("runtime settings", () => {
|
|||
expect(resolveRuntimeControls(configured, snapshotConfig(configured))).toMatchObject({
|
||||
model: { effective: "gpt-config", source: "config" },
|
||||
reasoningEffort: { effective: "medium", source: "config" },
|
||||
permissionProfile: { effective: null, source: "none" },
|
||||
sandboxPolicy: { effective: { type: "workspaceWrite", writableRoots: ["/vault"], networkAccess: false }, source: "config" },
|
||||
approvalPolicy: { effective: "on-request", source: "config" },
|
||||
approvalsReviewer: { effective: "auto_review", source: "config" },
|
||||
serviceTier: { effective: "fast", source: "config" },
|
||||
});
|
||||
expect(resolveRuntimeControls(active, snapshotConfig(active))).toMatchObject({
|
||||
model: { effective: "gpt-active", source: "active-thread" },
|
||||
reasoningEffort: { effective: "high", source: "active-thread" },
|
||||
permissionProfile: { effective: ":read-only", source: "active-thread" },
|
||||
sandboxPolicy: { effective: { type: "readOnly", networkAccess: false }, source: "active-thread" },
|
||||
approvalPolicy: { effective: "never", source: "active-thread" },
|
||||
approvalsReviewer: { effective: "user", source: "active-thread" },
|
||||
serviceTier: { effective: "flex", source: "active-thread" },
|
||||
});
|
||||
expect(resolveRuntimeControls(pending, snapshotConfig(pending))).toMatchObject({
|
||||
model: { effective: "gpt-pending", source: "pending" },
|
||||
reasoningEffort: { effective: "low", source: "pending" },
|
||||
approvalsReviewer: { effective: "guardian_subagent", source: "pending" },
|
||||
serviceTier: { effective: "fast", source: "pending" },
|
||||
fastMode: { active: true, source: "pending", serviceTierRequestValue: "fast" },
|
||||
model: { confirmed: "gpt-active", confirmedSource: "active-thread", effective: "gpt-pending", source: "pending" },
|
||||
reasoningEffort: { confirmed: "high", confirmedSource: "active-thread", effective: "low", source: "pending" },
|
||||
permissionProfile: { confirmed: ":read-only", confirmedSource: "active-thread", effective: ":workspace", source: "pending" },
|
||||
sandboxPolicy: {
|
||||
confirmed: { type: "readOnly", networkAccess: false },
|
||||
confirmedSource: "active-thread",
|
||||
effective: null,
|
||||
source: "pending",
|
||||
},
|
||||
approvalPolicy: { confirmed: "never", confirmedSource: "active-thread", effective: "on-failure", source: "pending" },
|
||||
approvalsReviewer: { confirmed: "user", confirmedSource: "active-thread", effective: "guardian_subagent", source: "pending" },
|
||||
serviceTier: { confirmed: "flex", confirmedSource: "active-thread", effective: "fast", source: "pending" },
|
||||
fastMode: {
|
||||
active: true,
|
||||
confirmedActive: false,
|
||||
source: "pending",
|
||||
confirmedSource: "active-thread",
|
||||
serviceTierRequestValue: "fast",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("reports collaboration mode dirtiness and missing model blockers from the resolved runtime", () => {
|
||||
const blocked = runtimeSnapshot({
|
||||
pending: { collaborationMode: "plan" },
|
||||
pending: { collaborationMode: { kind: "set", value: "plan" } },
|
||||
runtimeConfig: runtimeConfigFixture({}),
|
||||
});
|
||||
const ready = runtimeSnapshot({
|
||||
pending: {
|
||||
collaborationMode: "plan",
|
||||
collaborationMode: { kind: "set", value: "plan" },
|
||||
model: setRuntimeIntentValue("gpt-5.5"),
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolveRuntimeControls(blocked, snapshotConfig(blocked)).collaborationMode).toMatchObject({
|
||||
selected: "plan",
|
||||
effective: "default",
|
||||
pending: { kind: "set", value: "plan" },
|
||||
confirmed: "default",
|
||||
effective: "plan",
|
||||
dirty: true,
|
||||
blockedReason: "missing-model",
|
||||
});
|
||||
expect(resolveRuntimeControls(ready, snapshotConfig(ready)).collaborationMode).toMatchObject({
|
||||
selected: "plan",
|
||||
effective: "default",
|
||||
pending: { kind: "set", value: "plan" },
|
||||
confirmed: "default",
|
||||
effective: "plan",
|
||||
dirty: true,
|
||||
blockedReason: null,
|
||||
});
|
||||
|
|
@ -615,8 +661,10 @@ describe("runtime settings", () => {
|
|||
|
||||
it("resolves requested approval reviewer without adding it to turn runtime settings", () => {
|
||||
const snapshot = runtimeSnapshot({ pending: { approvalsReviewer: setRuntimeIntentValue("auto_review") } });
|
||||
const resolution = resolveRuntimeControls(snapshot, snapshotConfig(snapshot));
|
||||
|
||||
expect(autoReviewActive(snapshot, snapshotConfig(snapshot))).toBe(true);
|
||||
expect(resolution.autoReview).toMatchObject({ active: true, confirmedActive: false, source: "pending", confirmedSource: "none" });
|
||||
expect(pendingRuntimeSettingsPatch(snapshot, snapshotConfig(snapshot))).toMatchObject({
|
||||
update: { approvalsReviewer: "auto_review" },
|
||||
});
|
||||
|
|
@ -871,6 +919,9 @@ function runtimeSnapshot(overrides: RuntimeSnapshotPatch = {}): RuntimeSnapshot
|
|||
}),
|
||||
activeThreadId: null,
|
||||
active: {
|
||||
approvalPolicyKnown: false,
|
||||
sandboxPolicyKnown: false,
|
||||
permissionProfileKnown: false,
|
||||
serviceTierKnown: false,
|
||||
model: null,
|
||||
reasoningEffort: null,
|
||||
|
|
@ -887,7 +938,7 @@ function runtimeSnapshot(overrides: RuntimeSnapshotPatch = {}): RuntimeSnapshot
|
|||
permissionProfile: { kind: "unchanged" },
|
||||
approvalPolicy: { kind: "unchanged" },
|
||||
approvalsReviewer: { kind: "unchanged" },
|
||||
collaborationMode: "default",
|
||||
collaborationMode: { kind: "unchanged" },
|
||||
fastMode: { kind: "unchanged" },
|
||||
},
|
||||
tokenUsage: null,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ function fastMode(serviceTier: string | null, serviceTiers: ModelMetadata["servi
|
|||
runtimeConfig: config,
|
||||
activeThreadId: "thread",
|
||||
active: {
|
||||
approvalPolicyKnown: false,
|
||||
sandboxPolicyKnown: false,
|
||||
permissionProfileKnown: false,
|
||||
serviceTierKnown: true,
|
||||
model: "gpt-5.5",
|
||||
reasoningEffort: null,
|
||||
|
|
@ -39,7 +42,7 @@ function fastMode(serviceTier: string | null, serviceTiers: ModelMetadata["servi
|
|||
permissionProfile: { kind: "unchanged" },
|
||||
approvalPolicy: { kind: "unchanged" },
|
||||
approvalsReviewer: { kind: "unchanged" },
|
||||
collaborationMode: "default",
|
||||
collaborationMode: { kind: "unchanged" },
|
||||
fastMode: { kind: "unchanged" },
|
||||
},
|
||||
tokenUsage: null,
|
||||
|
|
|
|||
Loading…
Reference in a new issue