Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/controller/agentGuiComposerDefaultsReconciliation.ts
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

269 lines
8.7 KiB
TypeScript

import type { AgentActivityComposerOptions } from "@tutti-os/agent-activity-core";
import type { AgentSessionComposerSettings } from "../../../shared/agentSessionTypes";
import type { AgentGUIComposerTargetData } from "./agentGuiController.composerPresentation";
import {
rememberComposerDefaultsFields,
normalizedComposerDefaultValue,
type AgentGUIComposerDefaults,
type AgentGUIComposerDefaultsField,
type AgentGUIRememberComposerDefaultsResult
} from "./agentGuiController.providerHelpers";
interface AgentGUIComposerDefaultsGeneration {
generation: number;
value: string;
}
interface AgentGUIAcknowledgedComposerDefaultsGeneration extends AgentGUIComposerDefaultsGeneration {
confirmationReadsRemaining: number;
}
const composerDefaultsConfirmationReadLimit = 2;
export interface AgentGUIComposerDefaultsMutation {
draftKey: string;
fields: Partial<
Record<AgentGUIComposerDefaultsField, AgentGUIComposerDefaultsGeneration>
>;
}
export interface AgentGUIComposerDefaultsLedger {
acknowledgedByDraftKey: Record<
string,
Partial<
Record<
AgentGUIComposerDefaultsField,
AgentGUIAcknowledgedComposerDefaultsGeneration
>
>
>;
latestByDraftKey: Record<
string,
Partial<Record<AgentGUIComposerDefaultsField, number>>
>;
nextGeneration: number;
}
export interface AgentGUIRetiredComposerDefault {
field: AgentGUIComposerDefaultsField;
value: string;
}
export interface AgentGUIComposerDefaultsAuthorityRead {
force: boolean;
receipt: AgentGUIComposerDefaultsAuthorityReadReceipt | null;
settings: AgentSessionComposerSettings;
}
export interface AgentGUIComposerDefaultsAuthorityReadReceipt {
draftKey: string;
fields: Partial<
Record<AgentGUIComposerDefaultsField, AgentGUIComposerDefaultsGeneration>
>;
}
export interface AgentGUIComposerDefaultsAuthorityReconciler {
prepareRead(
target: AgentGUIComposerTargetData,
settings: AgentSessionComposerSettings
): AgentGUIComposerDefaultsAuthorityRead;
reconcileHomeDefaults(
target: AgentGUIComposerTargetData,
options: AgentActivityComposerOptions
): void;
reloaded(
receipt: AgentGUIComposerDefaultsAuthorityReadReceipt | null,
options: AgentActivityComposerOptions
): void;
}
export function createAgentGUIComposerDefaultsLedger(): AgentGUIComposerDefaultsLedger {
return {
acknowledgedByDraftKey: {},
latestByDraftKey: {},
nextGeneration: 0
};
}
export function registerAgentGUIComposerDefaultsMutation(
ledger: AgentGUIComposerDefaultsLedger,
draftKey: string,
defaults: AgentGUIComposerDefaults
): AgentGUIComposerDefaultsMutation {
const latest = (ledger.latestByDraftKey[draftKey] ??= {});
const acknowledged = (ledger.acknowledgedByDraftKey[draftKey] ??= {});
const fields: AgentGUIComposerDefaultsMutation["fields"] = {};
for (const field of rememberComposerDefaultsFields) {
const value = normalizedComposerDefaultValue(defaults, field);
if (value === null) continue;
const generation = ++ledger.nextGeneration;
latest[field] = generation;
delete acknowledged[field];
fields[field] = { generation, value };
}
return { draftKey, fields };
}
export function acknowledgeAgentGUIComposerDefaultsMutation(
ledger: AgentGUIComposerDefaultsLedger,
mutation: AgentGUIComposerDefaultsMutation,
result: AgentGUIRememberComposerDefaultsResult
): boolean {
const latest = ledger.latestByDraftKey[mutation.draftKey];
if (!latest) return false;
const acknowledgedFields = new Set(result.acknowledgedFields);
const acknowledged = (ledger.acknowledgedByDraftKey[mutation.draftKey] ??=
{});
let changed = false;
for (const field of rememberComposerDefaultsFields) {
const requested = mutation.fields[field];
if (
!requested ||
!acknowledgedFields.has(field) ||
latest[field] !== requested.generation
) {
continue;
}
acknowledged[field] = {
...requested,
confirmationReadsRemaining: composerDefaultsConfirmationReadLimit
};
changed = true;
}
return changed;
}
export function prepareAcknowledgedComposerDefaultsAuthorityRead(
ledger: AgentGUIComposerDefaultsLedger,
draftKey: string,
settings: AgentSessionComposerSettings
): AgentGUIComposerDefaultsAuthorityRead {
const authoritySettings = { ...settings };
const receipt: AgentGUIComposerDefaultsAuthorityReadReceipt = {
draftKey,
fields: {}
};
const latest = ledger.latestByDraftKey[draftKey];
const acknowledged = ledger.acknowledgedByDraftKey[draftKey];
if (latest && acknowledged) {
for (const field of rememberComposerDefaultsFields) {
const entry = acknowledged[field];
if (
entry &&
latest[field] === entry.generation &&
normalizedComposerDefaultValue(authoritySettings, field) === entry.value
) {
receipt.fields[field] = {
generation: entry.generation,
value: entry.value
};
delete authoritySettings[field];
}
}
}
const hasAcknowledgedFields = Object.keys(receipt.fields).length > 0;
return {
// An acknowledged field must cross the authority boundary again before
// its optimistic draft can retire. Bypass a potentially pre-ack cache.
force: hasAcknowledgedFields,
receipt: hasAcknowledgedFields ? receipt : null,
settings: authoritySettings
};
}
export function retireAcknowledgedComposerDefaultsForRead(
ledger: AgentGUIComposerDefaultsLedger,
receipt: AgentGUIComposerDefaultsAuthorityReadReceipt,
settings: AgentSessionComposerSettings,
authoritativeSettings: AgentSessionComposerSettings
): AgentGUIRetiredComposerDefault[] {
const latest = ledger.latestByDraftKey[receipt.draftKey];
const acknowledged = ledger.acknowledgedByDraftKey[receipt.draftKey];
if (!latest || !acknowledged) return [];
const retired: AgentGUIRetiredComposerDefault[] = [];
for (const field of rememberComposerDefaultsFields) {
const readEntry = receipt.fields[field];
const currentEntry = acknowledged[field];
if (
!readEntry ||
!currentEntry ||
currentEntry.generation !== readEntry.generation ||
latest[field] !== readEntry.generation
) {
continue;
}
const authoritativeValue = normalizedComposerDefaultValue(
authoritativeSettings,
field
);
if (authoritativeValue === null) {
// This provider does not project authority for the field. Stop forcing
// uncached reads, but keep the optimistic draft as the user's intent.
delete acknowledged[field];
continue;
}
if (authoritativeValue !== readEntry.value) {
// A concrete different value can be a stale overlapping discovery.
// Bound retries because some providers normalize or reject defaults and
// never echo the requested value. Protect the optimistic intent during
// confirmation, then release the marker so later settled options can
// apply normal sanitization and reuse the Engine cache.
currentEntry.confirmationReadsRemaining -= 1;
if (currentEntry.confirmationReadsRemaining <= 0) {
delete acknowledged[field];
}
continue;
}
if (normalizedComposerDefaultValue(settings, field) === readEntry.value) {
retired.push({ field, value: readEntry.value });
}
delete acknowledged[field];
}
if (Object.keys(acknowledged).length === 0) {
delete ledger.acknowledgedByDraftKey[receipt.draftKey];
}
return retired;
}
export function removeRetiredComposerDefaults(
settings: AgentSessionComposerSettings,
retired: readonly AgentGUIRetiredComposerDefault[]
): AgentSessionComposerSettings {
const result = { ...settings };
for (const entry of retired) {
if (normalizedComposerDefaultValue(result, entry.field) === entry.value) {
delete result[entry.field];
}
}
return result;
}
export function preserveAcknowledgedComposerDefaultsForReconciliation(
ledger: AgentGUIComposerDefaultsLedger,
draftKey: string,
currentSettings: AgentSessionComposerSettings,
reconciledSettings: AgentSessionComposerSettings
): AgentSessionComposerSettings {
const latest = ledger.latestByDraftKey[draftKey];
const acknowledged = ledger.acknowledgedByDraftKey[draftKey];
if (!latest || !acknowledged) return reconciledSettings;
let result = reconciledSettings;
for (const field of rememberComposerDefaultsFields) {
const entry = acknowledged[field];
if (
!entry ||
latest[field] !== entry.generation ||
normalizedComposerDefaultValue(currentSettings, field) !== entry.value ||
normalizedComposerDefaultValue(result, field) === entry.value
) {
continue;
}
if (result === reconciledSettings) {
result = { ...reconciledSettings };
}
Object.assign(result, { [field]: currentSettings[field] });
}
return result;
}