/** * The auto-continue settings card: edits the `auto-continue` namespace fields * from the plugin-configuration section (the `settings.plugin.item` seat). * * Self-contained card chrome (disclosure header, staged fields, save/discard * footer) following the plugin-card store pattern of the DSH plugin * configuration section; styles live in `styles.ts` and use the DSH design * tokens so the card follows the active theme. */ import { useEffect, useState, type ReactNode } from 'react'; import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'; import { type AutoContinueSettings } from './engine.ts'; import { createSnapshotStore, type SettingsScope, type SnapshotStore } from './dsh-store-compat.ts'; import { pausedSessions, readTodayStats, resetTodayStats, subscribeBridge, unpauseSession, } from './bridge.ts'; import type { SettingsCardKey } from './locales.ts'; import { booleanField, CardForm, numberField, textField, type CardActions, type CardFieldState, type CardShell, } from './settings-form.ts'; import { injectStyles } from './styles.ts'; // Styles must land during factory materialization so the module system's // style bookkeeping (HMR) owns them. injectStyles(); /** What the auto-continue card renders. */ export interface AutoContinueSettingsCardState extends CardShell { paused: CardFieldState; continueText: CardFieldState; continueTextMaxTokens: CardFieldState; guardTools: CardFieldState; guardPendingText: CardFieldState; guardDoneText: CardFieldState; graceMs: CardFieldState; cooldownMs: CardFieldState; maxConsecutive: CardFieldState; scanOnBoot: CardFieldState; scanLimit: CardFieldState; freshMs: CardFieldState; verbose: CardFieldState; classify: CardFieldState; retryableErrorPatterns: CardFieldState; backoffFactor: CardFieldState; backoffMaxMs: CardFieldState; notify: CardFieldState; loopGuard: CardFieldState; loopShortChars: CardFieldState; loopWindowMs: CardFieldState; loopShortCount: CardFieldState; loopRepeatText: CardFieldState; loopToolRepeat: CardFieldState; loopText: CardFieldState; } /** The registration-side face the card's slot entry injects. */ export interface AutoContinueSettingsCardFace extends CardActions { hooks: { /** Card snapshot bound by the renderer as useAutoContinueSettingsCard. */ autoContinueSettingsCard: SnapshotStore; }; } /** Bridges the `auto-continue` scope onto the card's staged form. */ export class AutoContinueSettingsCardController { private readonly form: CardForm; private readonly store: SnapshotStore; /** * @param scope - the bound settings scope for the `auto-continue` namespace. */ constructor(scope: SettingsScope) { this.form = new CardForm(scope, [ booleanField('paused'), textField('continueText'), textField('continueTextMaxTokens'), booleanField('guardTools'), textField('guardPendingText'), textField('guardDoneText'), numberField('graceMs', 0), numberField('cooldownMs', 0), numberField('maxConsecutive', 1), booleanField('scanOnBoot'), numberField('scanLimit', 1), numberField('freshMs', 0), booleanField('verbose'), booleanField('classify'), textField('retryableErrorPatterns'), numberField('backoffFactor', 1), numberField('backoffMaxMs', 0), booleanField('notify'), booleanField('loopGuard'), numberField('loopShortChars', 1), numberField('loopWindowMs', 1000), numberField('loopShortCount', 2), numberField('loopRepeatText', 2), numberField('loopToolRepeat', 2), textField('loopText'), ]); this.store = this.form.bind(() => this.projection(), createSnapshotStore); } private projection(): AutoContinueSettingsCardState { return { ...this.form.shell(), paused: this.form.field('paused'), continueText: this.form.field('continueText'), continueTextMaxTokens: this.form.field('continueTextMaxTokens'), guardTools: this.form.field('guardTools'), guardPendingText: this.form.field('guardPendingText'), guardDoneText: this.form.field('guardDoneText'), graceMs: this.form.field('graceMs'), cooldownMs: this.form.field('cooldownMs'), maxConsecutive: this.form.field('maxConsecutive'), scanOnBoot: this.form.field('scanOnBoot'), scanLimit: this.form.field('scanLimit'), freshMs: this.form.field('freshMs'), verbose: this.form.field('verbose'), classify: this.form.field('classify'), retryableErrorPatterns: this.form.field('retryableErrorPatterns'), backoffFactor: this.form.field('backoffFactor'), backoffMaxMs: this.form.field('backoffMaxMs'), notify: this.form.field('notify'), loopGuard: this.form.field('loopGuard'), loopShortChars: this.form.field('loopShortChars'), loopWindowMs: this.form.field('loopWindowMs'), loopShortCount: this.form.field('loopShortCount'), loopRepeatText: this.form.field('loopRepeatText'), loopToolRepeat: this.form.field('loopToolRepeat'), loopText: this.form.field('loopText'), }; } /** * Build the face the card's slot registration injects. * @returns the card's snapshot and its form actions. */ inject(): AutoContinueSettingsCardFace { return { hooks: { autoContinueSettingsCard: this.store }, ...this.form.actions() }; } } /** Props the renderer binds for the auto-continue plugin-configuration card. */ export type AutoContinueSettingsCardProps = PropsRuntime<'settings.plugin.item'> & PropsLocale<'auto-continue'> & InjectFace; const REPOSITORY_URL = 'https://github.com/HsiangNianian/dsh-auto-continue'; const REPOSITORY_SLUG = 'HsiangNianian/dsh-auto-continue'; function RelayMark() { return ( ); } function ChevronMark() { return ( ); } function GitHubMark() { return ( ); } function ExternalMark() { return ( ); } function RelayJourney(props: { t: (key: SettingsCardKey) => string }) { return (