import { sdk } from '../sdk' import { configFile } from '../file-models/config' const { InputSpec, Value } = sdk const inputSpec = InputSpec.of({ reviewInstructions: Value.textarea({ name: 'Review Rubric', description: 'What every reviewer should look for and produce. Layered above each ' + 'reviewer\'s persona. Be concrete about the structure you want back.', required: true, default: null, minRows: 5, maxRows: 20, placeholder: 'Review the attached document(s). Produce: a short summary, key findings, ' + 'risks/red flags, open questions, and recommendations. Cite the document and ' + 'section for each point. Never invent facts not present in the documents.', }), networkMode: Value.select({ name: 'Network Mode', description: 'Air-gapped: reviewers reach ONLY the on-Spark model proxy — zero internet, ' + 'documents never leave your hardware (models must be pre-pulled into the ' + 'Spark HF cache, all on the head Spark). Local services: reviewers may also ' + 'reach LAN services like SearXNG and the second Spark (this network has ' + 'egress unless you firewall it).', default: 'airgapped', values: { airgapped: 'Air-gapped (no network, recommended)', local_services: 'Local services (SearXNG / 2nd Spark)', }, }), searxngUrl: Value.text({ name: 'SearXNG URL (local-services only)', description: 'JSON-search endpoint to give reviewers a web_search tool. Ignored in air-gapped mode. Empty = no web search.', required: false, default: null, placeholder: 'https://searxng.local', }), synthesisEnabled: Value.toggle({ name: 'Synthesize a Consolidated Report', description: 'After the panel finishes, run a local "lead reviewer" that reads all the ' + 'individual reports and writes one consolidated report (themes, conflicts, ' + 'consensus, recommendation). No frontier model — stays on the Sparks.', default: true, }), synthesisModel: Value.text({ name: 'Lead Reviewer Model (optional)', description: 'Catalog alias of the model that writes the consolidated report. Empty = use the first model.', required: false, default: null, placeholder: 'reviewer-a', }), synthesisPersona: Value.textarea({ name: 'Lead Reviewer Instructions (optional)', description: 'Override how the consolidated report is written. Empty = a sensible built-in default.', required: false, default: null, minRows: 3, maxRows: 14, }), wipeRemoteDocs: Value.toggle({ name: 'Wipe Documents From Sparks After Review', description: 'Delete the extracted document text from the Sparks when a job finishes. ' + 'Reports are always kept on this StartOS box. Recommended for confidential material.', default: true, }), autoRunOnDrop: Value.toggle({ name: 'Auto-run When Documents Are Dropped', description: 'Start a review automatically (after a short debounce) whenever new files ' + 'land in the inbox. Off by default so you trigger reviews explicitly.', default: false, }), }) export const configureReview = sdk.Action.withInput( 'configure-review', async ({ effects }) => ({ name: 'Configure Review', description: 'Set the rubric, air-gap mode, synthesis, and document retention.', warning: null, allowedStatuses: 'any', group: null, visibility: 'enabled', }), inputSpec, async ({ effects }) => { const cfg = await configFile.read().const(effects) if (!cfg) return {} return { reviewInstructions: cfg.reviewInstructions, networkMode: cfg.networkMode, searxngUrl: cfg.searxngUrl || undefined, synthesisEnabled: cfg.synthesisEnabled, synthesisModel: cfg.synthesisModel || undefined, synthesisPersona: cfg.synthesisPersona || undefined, wipeRemoteDocs: cfg.wipeRemoteDocs, autoRunOnDrop: cfg.autoRunOnDrop, } }, async ({ effects, input }) => { await configFile.merge(effects, { reviewInstructions: input.reviewInstructions, networkMode: input.networkMode, searxngUrl: input.searxngUrl ?? '', synthesisEnabled: input.synthesisEnabled, synthesisModel: input.synthesisModel ?? '', synthesisPersona: input.synthesisPersona ?? '', wipeRemoteDocs: input.wipeRemoteDocs, autoRunOnDrop: input.autoRunOnDrop, }) return { version: '1', title: 'Review Settings Saved', message: input.networkMode === 'airgapped' ? 'Saved. Reviewers will run air-gapped (no internet). Ensure all models are on the head Spark and pre-pulled into its HF cache.' : 'Saved. Reviewers run in local-services mode and may reach the network — make sure that is acceptable for these documents.', result: { type: 'single', value: input.networkMode, copyable: false, qr: false, masked: false }, } }, )