UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

133 lines 4.73 kB
/** * OrchestratorPTY — Assess loop for Orchestrator-over-PTY * * Drives a PTY session via an LLM assessor using a read→assess→plan→act→wait * loop. The LLM observes the current screen state and decides whether to type, * wait, send a signal, or declare the mission complete. * * The LLMAssessor interface is generic — no Anthropic SDK is imported here — * which keeps this class fully testable with mock assessors. * * @issue #755 * @see src/serve/screen-reader.ts — ScreenReader / ScreenState * @see src/serve/pty-bridge.ts — PtyLike and session interfaces */ // ============================================================ // OrchestratorPTY // ============================================================ const DEFAULT_MAX_CYCLES = 100; const HISTORY_WINDOW = 10; const AWAIT_CHANGE_TIMEOUT_MS = 5000; export class OrchestratorPTY { session; screenReader; llm; context; maxCycles; _history = []; _done = false; _paused = false; constructor(session, screenReader, llm, context) { this.session = session; this.screenReader = screenReader; this.llm = llm; this.context = context; this.maxCycles = context.maxCycles ?? DEFAULT_MAX_CYCLES; // Seed history from context (advanced use-case; does not count against window) if (context.recentActions.length > 0) { this._history.push(...context.recentActions); } } // ---------------------------------------------------------- // Main loop // ---------------------------------------------------------- /** * Run the assess loop until: * - the LLM returns `complete` * - `maxCycles` iterations are exhausted * - `pause()` is called */ async run() { let cycleCount = 0; while (!this._done && !this._paused && cycleCount < this.maxCycles) { // ---- read ---- const screen = await this.screenReader.awaitChange({ timeout: AWAIT_CHANGE_TIMEOUT_MS, }); // Check pause again after the potentially long await if (this._paused) break; // ---- assess ---- const decision = await this.llm.assess({ screen_state: screen.summary, prompt_detected: screen.prompt_detected, prompt_text: screen.prompt_text, context: this.context, history: this._history.slice(-HISTORY_WINDOW), }); // ---- act ---- await this._execute(decision); cycleCount++; } // Auto-complete on cycle exhaustion if (!this._done && !this._paused && cycleCount >= this.maxCycles) { const capAction = { action: 'complete', reasoning: 'Max cycles reached', }; this._history.push(capAction); this._done = true; } } // ---------------------------------------------------------- // Pause / resume // ---------------------------------------------------------- /** Pause the loop at the next iteration boundary. */ pause() { this._paused = true; } /** * Clear the pause flag and re-enter the assess loop. * Resolves when the loop exits again (complete or paused again). */ async resume() { this._paused = false; await this.run(); } // ---------------------------------------------------------- // Accessors // ---------------------------------------------------------- /** Returns a copy of the full action history. */ getHistory() { return [...this._history]; } /** Whether the orchestrator has reached a terminal `complete` state. */ get done() { return this._done; } /** Whether the orchestrator is currently paused. */ get paused() { return this._paused; } // ---------------------------------------------------------- // Private helpers // ---------------------------------------------------------- async _execute(action) { this._history.push(action); switch (action.action) { case 'type': await this.session.write((action.text ?? '') + '\n'); break; case 'wait': // No-op — loop will call awaitChange again next iteration break; case 'signal': await this.session.signal(action.signal ?? ''); break; case 'complete': this._done = true; break; } } } //# sourceMappingURL=orchestrator-pty.js.map