iriai-build 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iriai-build",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Iriai Build tool — AI agent orchestration CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -192,12 +192,18 @@ export class AgentSupervisor extends EventEmitter {
192
192
  console.log(`[supervisor] Scheduling retry ${updatedAgent.retry_count}/${agent.max_retries} for ${agent.agent_key} in ${backoffS}s (${isFastExit ? "fast-exit" : "normal"})`);
193
193
 
194
194
  const entry = this._processes[agent.agent_key] || {};
195
- entry.retryTimer = setTimeout(() => {
196
- const result = promptFn(updatedAgent);
197
- // promptFn can return a string (prompt only) or { prompt, continue } object
198
- const prompt = typeof result === "string" ? result : result.prompt;
199
- const spawnOpts = typeof result === "string" ? {} : { continue: !!result.continue };
200
- this.spawn(agentId, prompt, spawnOpts);
195
+ entry.retryTimer = setTimeout(async () => {
196
+ try {
197
+ const result = await promptFn(updatedAgent);
198
+ // promptFn can return null (already handled spawn), a string, or { prompt, continue }
199
+ if (result == null) return;
200
+ const prompt = typeof result === "string" ? result : result.prompt;
201
+ const spawnOpts = typeof result === "string" ? {} : { continue: !!result.continue };
202
+ this.spawn(agentId, prompt, spawnOpts);
203
+ } catch (err) {
204
+ console.error(`[supervisor] Retry callback error for ${agent.agent_key}:`, err.message);
205
+ queries.updateAgentStatus(agentId, "crashed");
206
+ }
201
207
  }, backoffS * 1000);
202
208
  this._processes[agent.agent_key] = entry;
203
209
 
package/v3/operator.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  * @param {AgentSupervisor} opts.supervisor - Agent supervisor instance
26
26
  * @param {number} opts.agentId - Operator agent DB id
27
27
  */
28
- export async function invokeOperator({ feature, operatorDir, flDir, featureDir, userMessage, supervisor, agentId, planDir, activePlanningRole }) {
28
+ export async function invokeOperator({ feature, operatorDir, flDir, featureDir, userMessage, supervisor, agentId, planDir, activePlanningRole, continue: useContinue }) {
29
29
  // 1. Assemble context from SQLite (async — Haiku summarization is non-blocking)
30
30
  const history = await assembleHistory(feature.id);
31
31
  const activeAgents = assembleActiveAgents(feature.id);
@@ -49,10 +49,9 @@ export async function invokeOperator({ feature, operatorDir, flDir, featureDir,
49
49
  directoryMap,
50
50
  });
51
51
 
52
- // 3. Spawn claude via supervisor with --continue for session context
53
- // The supervisor handles PID tracking, exit detection, .runner.log
54
- // Response flows through .agent-response fileIO slack adapter
55
- supervisor.spawn(agentId, prompt, { continue: true });
52
+ // 3. Spawn claude via supervisor default to --continue for session context,
53
+ // but allow caller to force a fresh session (e.g. after "Prompt is too long")
54
+ supervisor.spawn(agentId, prompt, { continue: useContinue !== undefined ? useContinue : true });
56
55
 
57
56
  // Record the event
58
57
  queries.insertEvent(feature.id, "user-message", "bridge", `Operator invoked for: ${userMessage.slice(0, 100)}`);
@@ -1863,9 +1863,13 @@ export class Orchestrator {
1863
1863
  // Parse structured blocks from Operator output
1864
1864
  const parsed = parseOperatorResponse(content);
1865
1865
 
1866
- // Post the text content (with [DECISION] blocks stripped via parsed.plainText)
1867
- const textContent = parsed.plainText || content;
1868
- await this.adapter.postAgentResponse(feature.id, "Operator", textContent);
1866
+ // Post the text content (with [DECISION]/[ROUTE] blocks stripped via parsed.plainText).
1867
+ // If plainText is empty (response was only routes/decisions), skip posting to avoid
1868
+ // leaking raw [ROUTE:...] markup to the user.
1869
+ const textContent = parsed.plainText;
1870
+ if (textContent) {
1871
+ await this.adapter.postAgentResponse(feature.id, "Operator", textContent);
1872
+ }
1869
1873
 
1870
1874
  // Handle [DECISION] blocks — render as separate decision prompts.
1871
1875
  // Skip any that duplicate a deferred decision (already queued by _requestPhaseReview).
@@ -2232,6 +2236,11 @@ export class Orchestrator {
2232
2236
 
2233
2237
  // Invoke ephemeral operator with planning-phase context
2234
2238
  const planDir = tree.plansDir || path.join(tree.featureDir, "plans");
2239
+
2240
+ // Stash context for retry if "Prompt is too long" — readSignal already deleted the file
2241
+ if (!this._lastOperatorContext) this._lastOperatorContext = {};
2242
+ this._lastOperatorContext[feature.slug] = { userMessage, planDir, tree, feature };
2243
+
2235
2244
  try {
2236
2245
  await invokeOperator({
2237
2246
  feature,
@@ -2263,6 +2272,8 @@ export class Orchestrator {
2263
2272
  if (exitCode === 0) {
2264
2273
  queries.updateAgentStatus(agentId, "idle");
2265
2274
  queries.resetAgentRetry(agentId);
2275
+ // Clean up stashed context
2276
+ if (this._lastOperatorContext) delete this._lastOperatorContext[feature.slug];
2266
2277
  return;
2267
2278
  }
2268
2279
 
@@ -2275,8 +2286,29 @@ export class Orchestrator {
2275
2286
 
2276
2287
  console.log(`[orchestrator] Operator exited with code ${exitCode} after ${elapsed}ms — scheduling retry`);
2277
2288
 
2278
- const retried = this.supervisor.scheduleRetry(agentId, () => {
2279
- // On retry, assembleHistory will re-summarize with a tighter budget
2289
+ // Capture stashed context for the retry closure
2290
+ const stashedCtx = this._lastOperatorContext?.[feature.slug];
2291
+
2292
+ const retried = this.supervisor.scheduleRetry(agentId, async () => {
2293
+ // Retry with continue:false to start a fresh session (old session hit context limit).
2294
+ // Re-invoke the full Operator with the original user message so it doesn't lose context.
2295
+ if (stashedCtx) {
2296
+ console.log(`[orchestrator] Operator retry: re-invoking with original user message (continue:false)`);
2297
+ const freshFeature = queries.getFeatureById(feature.id) || feature;
2298
+ await invokeOperator({
2299
+ feature: freshFeature,
2300
+ operatorDir: tree.operator,
2301
+ flDir: tree.featureLead,
2302
+ featureDir: tree.featureDir,
2303
+ userMessage: stashedCtx.userMessage,
2304
+ supervisor: this.supervisor,
2305
+ agentId,
2306
+ planDir: stashedCtx.planDir,
2307
+ activePlanningRole: freshFeature.active_planning_role,
2308
+ continue: false,
2309
+ });
2310
+ return null; // invokeOperator already called supervisor.spawn
2311
+ }
2280
2312
  return { prompt: "Re-read your CLAUDE.md and check for pending relay queue items or user messages. Write a status update to .agent-response if there's nothing to relay.", continue: false };
2281
2313
  });
2282
2314