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
JavaScript
/**
* 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