{"version":3,"file":"controller.mjs","names":[],"sources":["../src/controller.ts"],"sourcesContent":["import type { ExtensionCommandContext, ExtensionContext } from '@earendil-works/pi-coding-agent';\n\nimport {\n\ttype ConfigScope,\n\tCURRENT_PRESET_SENTINEL,\n\tdeleteScopedPrompt,\n\ttype LiveCompactionConfig,\n\ttype LiveCompactionPaths,\n\tloadEditableScopedConfig,\n\tloadEffectiveConfig,\n\ttype PresetConfig,\n\ttype PromptKind,\n\ttype PromptResolution,\n\tresetLiveCompactionScope,\n\tresolveEffectivePrompt,\n\tresolveLiveCompactionPaths,\n\tsaveScopedConfig,\n\tsaveScopedPromptText,\n\tscopeHasLocalOverrides,\n\ttype ThinkingLevel,\n} from '@live-compaction/config';\n\nexport interface RuntimeStatus {\n\tavailable: boolean;\n\tissues: string[];\n\tlastCheckedAt?: number;\n}\n\nexport interface LiveCompactionState {\n\tscope: ConfigScope;\n\tconfig: LiveCompactionConfig;\n\tpaths: LiveCompactionPaths;\n\tcompactionPrompt: PromptResolution;\n\tbranchSummaryPrompt: PromptResolution;\n\tprojectScopeAvailable: boolean;\n\tprojectScopeHasOverrides: boolean;\n\truntimeStatus: RuntimeStatus;\n}\n\nexport interface LiveCompactionController {\n\tloadState(ctx: ExtensionContext, scope: ConfigScope): Promise<LiveCompactionState>;\n\tsetConfig(\n\t\tscope: ConfigScope,\n\t\tnext: LiveCompactionConfig,\n\t\tctx: ExtensionCommandContext,\n\t): Promise<void>;\n\tupsertPreset(\n\t\tscope: ConfigScope,\n\t\tname: string,\n\t\tpreset: PresetConfig,\n\t\tctx: ExtensionCommandContext,\n\t): Promise<void>;\n\tdeletePreset(scope: ConfigScope, name: string, ctx: ExtensionCommandContext): Promise<void>;\n\tsavePrompt(\n\t\tscope: ConfigScope,\n\t\tkind: PromptKind,\n\t\ttext: string,\n\t\tctx: ExtensionCommandContext,\n\t): Promise<void>;\n\tresetPrompt(scope: ConfigScope, kind: PromptKind, ctx: ExtensionCommandContext): Promise<void>;\n\tresetScope(scope: ConfigScope, ctx: ExtensionCommandContext): Promise<void>;\n\trefreshRuntimeStatus(ctx: ExtensionContext): Promise<RuntimeStatus>;\n\tgetPaths(ctx: ExtensionContext): LiveCompactionPaths;\n\tsummarizeState(state: LiveCompactionState): string;\n\tgetUsageText(): string;\n}\n\nconst USAGE_TEXT = 'Usage: /live-compaction [show|verify|path|reset|help]';\n\nfunction createDefaultRuntimeStatus(): RuntimeStatus {\n\treturn { available: true, issues: [] };\n}\n\nfunction cloneConfig(config: LiveCompactionConfig): LiveCompactionConfig {\n\treturn {\n\t\tincludeFilesTouched: {\n\t\t\tinCompactionSummary: config.includeFilesTouched.inCompactionSummary,\n\t\t\tinBranchSummary: config.includeFilesTouched.inBranchSummary,\n\t\t},\n\t\tdefaultPreset: config.defaultPreset,\n\t\tfallbackPreset: config.fallbackPreset,\n\t\tpresets: Object.fromEntries(\n\t\t\tObject.entries(config.presets).map(([name, preset]) => [name, { ...preset }]),\n\t\t),\n\t\tdefaultPanelScope: config.defaultPanelScope,\n\t\tinheritSessionModel: config.inheritSessionModel,\n\t};\n}\n\nfunction describePromptSource(kind: PromptKind, prompt: PromptResolution): string {\n\tif (kind === 'compaction') {\n\t\tif (prompt.source === 'default') return 'compactionPrompt=default';\n\t\tif (prompt.isBlankOverride) return `compactionPrompt=${prompt.source}:blank->default`;\n\t\treturn `compactionPrompt=${prompt.source}:custom`;\n\t}\n\tif (prompt.source === 'default') return 'branchPrompt=default';\n\tif (prompt.isBlankOverride) return `branchPrompt=${prompt.source}:blank->disabled`;\n\treturn `branchPrompt=${prompt.source}:custom`;\n}\n\nfunction validateConfigAgainstModels(\n\tconfig: LiveCompactionConfig,\n\tctx: ExtensionContext,\n): string[] {\n\tconst issues: string[] = [];\n\tconst models = ctx.modelRegistry.getAll();\n\tfor (const [name, preset] of Object.entries(config.presets)) {\n\t\tconst separatorIndex = preset.model.indexOf('/');\n\t\tif (separatorIndex <= 0 || separatorIndex === preset.model.length - 1) {\n\t\t\tissues.push(`Preset '${name}' uses invalid model '${preset.model}'.`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst provider = preset.model.slice(0, separatorIndex);\n\t\tconst modelId = preset.model.slice(separatorIndex + 1);\n\t\tconst model = models.find(\n\t\t\t(candidate) => candidate.provider === provider && candidate.id === modelId,\n\t\t);\n\t\tif (!model) {\n\t\t\tissues.push(`Preset '${name}' model ${preset.model} is not registered.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (preset.thinkingLevel && preset.thinkingLevel !== 'off' && !model.reasoning) {\n\t\t\tissues.push(\n\t\t\t\t`Preset '${name}' requires reasoning level '${preset.thinkingLevel}' but ${preset.model} does not support reasoning.`,\n\t\t\t);\n\t\t}\n\t}\n\treturn issues;\n}\n\nfunction getEffectiveScope(state: LiveCompactionState): ConfigScope | 'default' {\n\tif (state.scope === 'project' || !state.projectScopeAvailable) return state.scope;\n\tif (state.projectScopeHasOverrides) return 'project';\n\treturn 'global';\n}\n\nexport function createLiveCompactionController(): LiveCompactionController {\n\tlet runtimeStatus = createDefaultRuntimeStatus();\n\n\tasync function buildState(\n\t\tctx: ExtensionContext,\n\t\tscope: ConfigScope,\n\t): Promise<LiveCompactionState> {\n\t\tconst paths = resolveLiveCompactionPaths(ctx.cwd);\n\t\tconst config = await loadEditableScopedConfig(scope, ctx.cwd);\n\t\tconst [compactionPrompt, branchSummaryPrompt, projectScopeHasOverrides] = await Promise.all([\n\t\t\tresolveEffectivePrompt('compaction', scope === 'project' ? ctx.cwd : undefined, undefined),\n\t\t\tresolveEffectivePrompt(\n\t\t\t\t'branch-summary',\n\t\t\t\tscope === 'project' ? ctx.cwd : undefined,\n\t\t\t\tundefined,\n\t\t\t),\n\t\t\tpaths.project ? scopeHasLocalOverrides('project', ctx.cwd) : Promise.resolve(false),\n\t\t]);\n\t\treturn {\n\t\t\tscope,\n\t\t\tconfig,\n\t\t\tpaths,\n\t\t\tcompactionPrompt,\n\t\t\tbranchSummaryPrompt,\n\t\t\tprojectScopeAvailable: Boolean(paths.project),\n\t\t\tprojectScopeHasOverrides,\n\t\t\truntimeStatus,\n\t\t};\n\t}\n\n\treturn {\n\t\tasync loadState(ctx, scope) {\n\t\t\treturn buildState(ctx, scope);\n\t\t},\n\n\t\tasync setConfig(scope, next, ctx) {\n\t\t\ttry {\n\t\t\t\tawait saveScopedConfig(scope, next, ctx.cwd);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(message, 'error');\n\t\t\t}\n\t\t},\n\n\t\tasync upsertPreset(scope, name, preset, ctx) {\n\t\t\tconst config = cloneConfig(await loadEditableScopedConfig(scope, ctx.cwd));\n\t\t\tconfig.presets[name] = preset;\n\t\t\tif (\n\t\t\t\tconfig.defaultPreset !== CURRENT_PRESET_SENTINEL &&\n\t\t\t\t!config.presets[config.defaultPreset]\n\t\t\t) {\n\t\t\t\tconfig.defaultPreset = name;\n\t\t\t}\n\t\t\tawait this.setConfig(scope, config, ctx);\n\t\t},\n\n\t\tasync deletePreset(scope, name, ctx) {\n\t\t\tconst config = cloneConfig(await loadEditableScopedConfig(scope, ctx.cwd));\n\t\t\tdelete config.presets[name];\n\t\t\tif (config.defaultPreset === name) config.defaultPreset = CURRENT_PRESET_SENTINEL;\n\t\t\tawait this.setConfig(scope, config, ctx);\n\t\t},\n\n\t\tasync savePrompt(scope, kind, text, ctx) {\n\t\t\ttry {\n\t\t\t\tawait saveScopedPromptText(kind, scope, text, ctx.cwd);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(message, 'error');\n\t\t\t}\n\t\t},\n\n\t\tasync resetPrompt(scope, kind, ctx) {\n\t\t\ttry {\n\t\t\t\tawait deleteScopedPrompt(kind, scope, ctx.cwd);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(message, 'error');\n\t\t\t}\n\t\t},\n\n\t\tasync resetScope(scope, ctx) {\n\t\t\ttry {\n\t\t\t\tawait resetLiveCompactionScope(scope, ctx.cwd);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(message, 'error');\n\t\t\t}\n\t\t},\n\n\t\tasync refreshRuntimeStatus(ctx) {\n\t\t\tconst config = await loadEffectiveConfig(ctx.cwd);\n\t\t\tconst issues = validateConfigAgainstModels(config, ctx);\n\t\t\truntimeStatus = { available: issues.length === 0, issues, lastCheckedAt: Date.now() };\n\t\t\treturn runtimeStatus;\n\t\t},\n\n\t\tgetPaths(ctx) {\n\t\t\treturn resolveLiveCompactionPaths(ctx.cwd);\n\t\t},\n\n\t\tsummarizeState(state) {\n\t\t\tconst presetNames = Object.keys(state.config.presets);\n\t\t\tconst effectiveScope = getEffectiveScope(state);\n\t\t\tconst runtimePart = state.runtimeStatus.available\n\t\t\t\t? 'runtime=ok'\n\t\t\t\t: `runtime=issues:${state.runtimeStatus.issues.length}`;\n\t\t\treturn [\n\t\t\t\t`scope=${state.scope}`,\n\t\t\t\t`effective=${effectiveScope}`,\n\t\t\t\t`filesTouched=compaction:${state.config.includeFilesTouched.inCompactionSummary ? 'on' : 'off'},branch:${state.config.includeFilesTouched.inBranchSummary ? 'on' : 'off'}`,\n\t\t\t\t`defaultPreset=${state.config.defaultPreset}`,\n\t\t\t\t`presets=${presetNames.length === 0 ? 'none' : presetNames.join(',')}`,\n\t\t\t\tdescribePromptSource('compaction', state.compactionPrompt),\n\t\t\t\tdescribePromptSource('branch-summary', state.branchSummaryPrompt),\n\t\t\t\truntimePart,\n\t\t\t].join(' | ');\n\t\t},\n\n\t\tgetUsageText() {\n\t\t\treturn USAGE_TEXT;\n\t\t},\n\t};\n}\n\nexport function getPresetNames(config: LiveCompactionConfig): string[] {\n\treturn Object.keys(config.presets).sort((left, right) => left.localeCompare(right));\n}\n\nexport function buildPresetChoices(config: LiveCompactionConfig): string[] {\n\treturn [CURRENT_PRESET_SENTINEL, ...getPresetNames(config)];\n}\n\nexport function formatThinkingLevel(value?: ThinkingLevel): string {\n\treturn value ?? 'unset';\n}\n\nexport function parseThinkingLevelSelection(value: string): ThinkingLevel | undefined {\n\treturn value === 'unset' ? undefined : (value as ThinkingLevel);\n}\n"],"mappings":"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"}