ai-foreman 1.7.17 → 1.7.19

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.
Files changed (87) hide show
  1. package/README.md +18 -7
  2. package/dist/activity.d.ts +11 -1
  3. package/dist/activity.js +84 -16
  4. package/dist/adapters/claude.d.ts +16 -1
  5. package/dist/adapters/claude.js +196 -29
  6. package/dist/adapters/codex.d.ts +15 -1
  7. package/dist/adapters/codex.js +172 -38
  8. package/dist/adapters/recovering.d.ts +3 -1
  9. package/dist/adapters/recovering.js +12 -4
  10. package/dist/adapters/types.d.ts +34 -1
  11. package/dist/agentRun.js +2 -0
  12. package/dist/branch/currentGuard.d.ts +3 -1
  13. package/dist/branch/currentGuard.js +11 -2
  14. package/dist/branch/resume.js +1 -1
  15. package/dist/branch/runner.d.ts +2 -0
  16. package/dist/branch/runner.js +90 -11
  17. package/dist/buildApproval.d.ts +9 -0
  18. package/dist/buildApproval.js +126 -0
  19. package/dist/buildRuns.js +74 -55
  20. package/dist/cli/events.js +21 -1
  21. package/dist/cli/recovery.js +9 -13
  22. package/dist/cli/runtimeAuthPrompt.d.ts +7 -0
  23. package/dist/cli/runtimeAuthPrompt.js +40 -5
  24. package/dist/cli/start.d.ts +16 -1
  25. package/dist/cli/start.js +161 -81
  26. package/dist/cli/tickets.d.ts +11 -0
  27. package/dist/cli/tickets.js +382 -145
  28. package/dist/continuity.d.ts +7 -1
  29. package/dist/continuity.js +86 -7
  30. package/dist/diagnostics.js +37 -8
  31. package/dist/foreman.d.ts +8 -1
  32. package/dist/foreman.js +144 -29
  33. package/dist/handoffs.js +121 -79
  34. package/dist/humanDecision.d.ts +18 -0
  35. package/dist/humanDecision.js +43 -0
  36. package/dist/index.js +2 -1
  37. package/dist/log.d.ts +1 -1
  38. package/dist/managerPacket.js +1 -0
  39. package/dist/observability.js +3 -7
  40. package/dist/processIdentity.d.ts +8 -0
  41. package/dist/processIdentity.js +40 -0
  42. package/dist/projectDiagnostics.js +3 -16
  43. package/dist/providerQuestions.d.ts +5 -0
  44. package/dist/providerQuestions.js +27 -2
  45. package/dist/qaDeliveryJournal.d.ts +70 -0
  46. package/dist/qaDeliveryJournal.js +1 -0
  47. package/dist/qaFailureDelivery.d.ts +26 -10
  48. package/dist/qaFailureDelivery.js +399 -412
  49. package/dist/qaHandbackHistory.d.ts +4 -0
  50. package/dist/qaHandbackHistory.js +30 -0
  51. package/dist/qaHandbackMigration.d.ts +9 -0
  52. package/dist/qaHandbackMigration.js +147 -0
  53. package/dist/qaPrerequisites.d.ts +26 -0
  54. package/dist/qaPrerequisites.js +61 -0
  55. package/dist/qaProtocolV2.d.ts +8 -0
  56. package/dist/qaProtocolV2.js +3 -3
  57. package/dist/qaReview.d.ts +8 -0
  58. package/dist/qaReview.js +54 -10
  59. package/dist/questionTrace.d.ts +31 -0
  60. package/dist/questionTrace.js +85 -0
  61. package/dist/readOnlyProposal.d.ts +8 -0
  62. package/dist/readOnlyProposal.js +41 -0
  63. package/dist/recoveryPolicy.js +14 -3
  64. package/dist/runtimeAuth.d.ts +2 -1
  65. package/dist/runtimeAuth.js +2 -2
  66. package/dist/runtimeReadiness.d.ts +13 -1
  67. package/dist/runtimeReadiness.js +69 -11
  68. package/dist/sessionLifecycle.d.ts +1 -0
  69. package/dist/sessionLifecycle.js +53 -3
  70. package/dist/sources/sourceRegistry.d.ts +7 -0
  71. package/dist/sources/sourceRegistry.js +14 -0
  72. package/dist/stateTransfer.js +2 -0
  73. package/dist/statusReporter.d.ts +3 -1
  74. package/dist/statusReporter.js +8 -2
  75. package/dist/supervisedStart.d.ts +12 -0
  76. package/dist/supervisedStart.js +276 -0
  77. package/dist/supervisor.js +44 -22
  78. package/dist/ticketPopulation.js +1 -0
  79. package/dist/tickets/setupConfig.d.ts +8 -0
  80. package/dist/tickets/setupConfig.js +38 -4
  81. package/dist/util/deadline.d.ts +13 -0
  82. package/dist/util/deadline.js +60 -0
  83. package/dist/workflowDb.d.ts +55 -11
  84. package/dist/workflowDb.js +284 -70
  85. package/dist/workflowReader.d.ts +10 -2
  86. package/dist/workflowReader.js +35 -1
  87. package/package.json +3 -3
package/README.md CHANGED
@@ -15,9 +15,9 @@ Loop Claude Code or Codex through tickets.
15
15
  - **Rich ticket system** — tickets carry acceptance criteria, required tests, dependencies, priority, size, and risk level. The agent reads the full context before starting each step.
16
16
  - **Future work tracking** — when the builder discovers out-of-scope work during a run, `ai-foreman tickets discover` captures it without derailing the current ticket. Discovered items live in a separate inbox until you promote them.
17
17
  - **QA that actually gates** — QA runs after every completed ticket and checks code quality, test passing, and regression protection. The ticket only closes after QA passes. QA turns do not count against `--steps`.
18
- - **Visible long-running work** — semantic activity rows identify provider, phase, tools, retries, and quiet periods; cursor-capable terminals update changing counters in place, while record-oriented terminals coalesce them.
18
+ - **Visible long-running work** — semantic activity rows identify provider, phase, tools, retries, and quiet periods; safe record output coalesces spinner- and time-only changes.
19
19
 
20
- Provider-managed retries are reported immediately as durable lines. A quiet-provider warning appears after 60 seconds without a signal, but Foreman does not add retries, abort the provider, or change provider retry limits. Non-TTY output receives a heartbeat every 30 seconds while work remains pending. TTY rendering defaults to automatic detection: Codex, CI, and `TERM=dumb` use newline records and suppress spinner- or number-only repeats; other terminals redraw those changes in place and retain a row when the textual status changes. Use `RAFI_ACTIVITY_RENDER_MODE=auto|cursor|records` to override detection.
20
+ Provider-managed retries are reported immediately as durable lines. A quiet-provider warning appears after 60 seconds without a signal, but Foreman does not add retries, abort the provider, or change provider retry limits. Non-TTY output emits the initial and meaningful status changes without ANSI redraws. Automatic TTY output also uses newline records and suppresses spinner-, elapsed-time-, and number-only repeats, making it safe for transcript hosts. Set `RAFI_ACTIVITY_RENDER_MODE=cursor` to explicitly enable animated in-place redraws in a known real terminal; `records` explicitly forces safe records, while `auto` selects the safe default.
21
21
 
22
22
  ## Install
23
23
 
@@ -77,13 +77,14 @@ What each part does:
77
77
 
78
78
  - `./my-project`: the repo Foreman will work on.
79
79
  - `tickets init`: creates the local `.tickets/` tracker.
80
- - `tickets populate`: asks the ticket-maker role to convert project planning material.
80
+ - `tickets populate`: generates tickets from an approved structured Rafi plan, with optional supporting context.
81
81
  - Converted output uses Foreman's schema.
82
82
  - `start`: drives Codex through the next tickets one step at a time.
83
83
 
84
- `tickets populate` can convert:
84
+ In `approved_plan` mode, `tickets populate` requires a validated `<docs.root>/rafi-plan.json`. Supporting documents and URLs add detail but cannot replace its slices. `external_import` is a separate mode for importing configured Linear or Jira tickets.
85
+
86
+ Supporting context can include:
85
87
 
86
- - a managed Rafi plan at `<docs.root>/rafi-plan.md`
87
88
  - plans
88
89
  - TODOs
89
90
  - roadmap docs
@@ -161,6 +162,16 @@ Saved `tickets.build` defaults in `rafi-config.yaml` can enable branch-per-ticke
161
162
 
162
163
  With the saved `current` strategy, Foreman works in the active branch while the user owns Git. It may edit, test, run QA, and update tracker/recovery state, but all branch/worktree, commit, push, merge, rebase, and review lifecycle commands are fenced. An unexpected active ref or worktree change pauses the run. Explicit isolated flags are reported as run overrides.
163
164
 
165
+ Build stall recovery uses current context occupancy, not lifetime token usage. The `compact` session strategy checks occupancy at ordinary boundaries and compacts only when the threshold requires it; `fresh` still creates an intentional new session. Compaction normally has a 120-second deadline, extending to at most 180 seconds only after correlated progress. Late completion is attributed to the original session; unresolved attempts remain quarantined across restart. Unknown completion pauses recovery rather than replaying implementation in a replacement session.
166
+
167
+ Unchanged, source-bound plan and ticket approvals are reused. Material ticket, plan, delivery, or workflow changes require a new decision, including with `--yes`. Legacy approval evidence that cannot prove unchanged scope requires review. `--steps` counts completed tickets or steps; it is neither a wall-clock limit nor a model-turn budget.
168
+
169
+ Questions are persisted before prompting. Redirected or detached runs return a recoverable needs-input outcome (exit 2). Use `rafi build:attach . --run <id>` to see decisions, `rafi build:decide . --run <id> --decision <id> --choice <id>` to answer, and `rafi build:resume . --run <id>` to continue. Custom answers additionally use `--choice custom --answer "text"`. Independent eligible tickets continue around a ticket blocker; dependent and unsafe shared-delivery work stays deferred. Answered ticket questions are reconsidered at safe boundaries.
170
+
171
+ CLI starts run under a durable parent supervisor by default. `--detach` writes output under `.rafi/logs/supervisor-<run>.log`; `--no-supervisor` retains direct execution. A preparation-only worker crash may restart within frozen budgets after proving the worker and registered readiness children are stopped. A worker crash with unresolved provider work pauses for reconciliation: it does not silently repeat a possibly completed action. `build:stop` signals only a verified supervisor process incarnation.
172
+
173
+ Runtime deadlines can be overridden in `autonomy.runtime_deadlines` using positive integer milliseconds: `preparation_ms` (120000), `rpc_ms` (60000), `compaction_ms` (120000), `shutdown_ms` (10000), and `turn_ms` (3600000). They are frozen with the run policy. Transport-idle and hard active-turn deadlines have distinct meanings; neither grants permission to repeat uncertain side effects. Attached-process elapsed time includes waiting and is not provider compute time. Activity warns after five minutes without a completed tool or agent turn; intentional human waits are excluded, and the warning alone does not cancel work. Final paused output lists the durable question and exact run-scoped recovery commands.
174
+
164
175
  The current terminal status includes the role/provider, activity, truthful context state, successful compaction count, and handoff generation. At the Builder threshold, Foreman settles the in-flight action, verifies native compaction plus a fresh provider usage sample, and resumes the frozen action. The configured maximum defaults to ten successful compactions per location-scoped provider session; the next crossing uses a validated fresh handoff instead of exceeding it. Disposable QA snapshots never reuse a prior QA conversation in a new `/tmp/rafi-qa-*` worktree; they transfer cumulative state through an accepted handoff.
165
176
 
166
177
  Failed QA reviews use the versioned `RAFI_QA_FAILURE_REPORT_START` / `RAFI_QA_FAILURE_REPORT_END` JSON contract. Foreman validates the report before Builder remediation, namespaces finding IDs by run, ticket, and review, and durably indexes the exact report, remediation response, and bounded fix summary by digest. A malformed failing report receives two correction-only turns in its original session, then a validated fresh successor performs one complete source-bound review followed by up to two correction-only turns. Any later operator-requested fresh successor performs one complete source-bound review followed by five usable correction-only turns.
@@ -214,11 +225,11 @@ gh repo view <host>/<owner>/<repo>
214
225
  | `-a, --agent <agent>` | `claude` / `codex` | single `rafi-config.yaml` target, otherwise `claude` | Builder agent. Explicit `--agent` always wins. |
215
226
  | `-m, --model <model>` | `gpt-5.5` / any supported agent model | agent default | Overrides the builder model. |
216
227
  | `--effort <level>` | Claude Code: `low` / `medium` / `high` / `xhigh`<br>Codex: `low` / `medium` / `high` / `xhigh` | agent default | Reasoning level. |
217
- | `--sources <paths...>` | `docs/tickets.md docs/plans/**` | Configured Rafi `<docs.root>/rafi-plan.md`, then ticket docs root, then `docs/rafi-plan.md`, otherwise scans | Optional files, folders, or globs for the agent to check first. Any reasonable planning format is OK. |
228
+ | `--sources <paths...>` | `docs/tickets.md docs/plans/**` | saved context, subject to `source_handling` | Supplemental files, folders, URLs, or globs. Passing an approved `rafi-plan.json` remains supported; ordinary documents cannot replace the approved plan. |
218
229
  | `--fast` | flag | off | Lower latency. |
219
230
  | `-y, --yes` | flag | off | Skips confirmation before builder edits ticket files. |
220
231
 
221
- Use `ai-foreman tickets setup:init` or `setup:update` to append local, Linear, or Jira Cloud sources to the project-wide registry in `rafi-config.yaml`. Linear imports use `LINEAR_API_KEY`; Jira Cloud imports use `JIRA_EMAIL` and `JIRA_API_TOKEN`. Credentials are never persisted, and mirrored tickets retain source provenance.
232
+ Use `ai-foreman tickets setup:init` or `setup:update` to manage supporting local/URL context and external Linear/Jira connections in the project-wide registry in `rafi-config.yaml`. Linear imports use `LINEAR_API_KEY`; Jira Cloud imports use `JIRA_EMAIL` and `JIRA_API_TOKEN`. Credentials are never persisted, and mirrored tickets retain source provenance.
222
233
 
223
234
  Use `ai-foreman tickets review` to accept, defer, or dismiss pending split/combine/duplicate recommendations rendered in the progress doc.
224
235
 
@@ -11,7 +11,8 @@ export interface ActivityReporterOptions {
11
11
  tickMs?: number;
12
12
  heartbeatMs?: number;
13
13
  quietWarningMs?: number;
14
- /** Override automatic TTY capability detection for tests or embedding hosts. */
14
+ usefulProgressWarningMs?: number;
15
+ /** Override the safe automatic TTY rendering mode for tests or embedding hosts. */
15
16
  ttyMode?: ActivityTtyMode;
16
17
  }
17
18
  export type ActivityTtyMode = "auto" | "cursor" | "records";
@@ -24,6 +25,10 @@ export declare class ActivityReporter {
24
25
  private readonly tickMs;
25
26
  private readonly heartbeatMs;
26
27
  private readonly quietWarningMs;
28
+ private readonly usefulProgressWarningMs;
29
+ private lastUsefulProgressAt;
30
+ private usefulProgressWarningPrinted;
31
+ private pauseStartedAt;
27
32
  private readonly ttyMode;
28
33
  private readonly commandStartedAt;
29
34
  private readonly phases;
@@ -48,6 +53,8 @@ export declare class ActivityReporter {
48
53
  immediate?: boolean;
49
54
  }): void;
50
55
  pulse(detail?: string): void;
56
+ /** A completed tool or turn is distinct from transport/status activity. */
57
+ completedWork(): void;
51
58
  note(message: string): void;
52
59
  writePersistent(text: string): void;
53
60
  /** Keep role/provider/context state attached to the current activity status. */
@@ -71,5 +78,8 @@ export declare function pauseActivityForInput<T>(operation: () => Promise<T>, _m
71
78
  export declare function reportBuilderEvent(event: BuilderEvent): void;
72
79
  /** Async event queue that also feeds the active CLI reporter without consuming the stream. */
73
80
  export declare class BuilderEventQueue extends AsyncQueue<BuilderEvent> {
81
+ private readonly observers;
82
+ /** Synchronous fan-out; never consumes or competes with the owning iterator. */
83
+ observe(listener: (event: BuilderEvent) => void): () => void;
74
84
  push(event: BuilderEvent): void;
75
85
  }
package/dist/activity.js CHANGED
@@ -10,6 +10,10 @@ export class ActivityReporter {
10
10
  tickMs;
11
11
  heartbeatMs;
12
12
  quietWarningMs;
13
+ usefulProgressWarningMs;
14
+ lastUsefulProgressAt = 0;
15
+ usefulProgressWarningPrinted = false;
16
+ pauseStartedAt = 0;
13
17
  ttyMode;
14
18
  commandStartedAt;
15
19
  phases = new Map();
@@ -37,6 +41,7 @@ export class ActivityReporter {
37
41
  this.tickMs = options.tickMs ?? 1_000;
38
42
  this.heartbeatMs = options.heartbeatMs ?? 30_000;
39
43
  this.quietWarningMs = options.quietWarningMs ?? 60_000;
44
+ this.usefulProgressWarningMs = options.usefulProgressWarningMs ?? 300_000;
40
45
  this.ttyMode = resolveTtyMode(options.ttyMode);
41
46
  this.commandStartedAt = this.now();
42
47
  }
@@ -45,6 +50,8 @@ export class ActivityReporter {
45
50
  this.phases.set(phase.id, phase);
46
51
  if (this.phases.size === 1) {
47
52
  this.lastSignalAt = phase.startedAt;
53
+ this.lastUsefulProgressAt = phase.startedAt;
54
+ this.usefulProgressWarningPrinted = false;
48
55
  this.lastHeartbeatAt = phase.startedAt;
49
56
  this.quietWarningPrinted = false;
50
57
  this.startTimer();
@@ -80,6 +87,11 @@ export class ActivityReporter {
80
87
  this.quietWarningPrinted = false;
81
88
  this.render(this.output.isTTY);
82
89
  }
90
+ /** A completed tool or turn is distinct from transport/status activity. */
91
+ completedWork() {
92
+ this.lastUsefulProgressAt = this.now();
93
+ this.usefulProgressWarningPrinted = false;
94
+ }
83
95
  note(message) {
84
96
  this.clearLine();
85
97
  this.output.write(`${clean(message, 500)}\n`);
@@ -109,6 +121,8 @@ export class ActivityReporter {
109
121
  this.render(true);
110
122
  }
111
123
  pause() {
124
+ if (this.paused === 0)
125
+ this.pauseStartedAt = this.now();
112
126
  this.paused++;
113
127
  this.clearLine();
114
128
  let resumed = false;
@@ -117,6 +131,11 @@ export class ActivityReporter {
117
131
  return;
118
132
  resumed = true;
119
133
  this.paused = Math.max(0, this.paused - 1);
134
+ if (this.paused === 0) {
135
+ const duration = this.now() - this.pauseStartedAt;
136
+ this.lastUsefulProgressAt += duration;
137
+ this.lastSignalAt += duration;
138
+ }
120
139
  this.render(true);
121
140
  };
122
141
  }
@@ -161,9 +180,17 @@ export class ActivityReporter {
161
180
  if (!force && elapsed < this.displayDelayMs)
162
181
  return;
163
182
  const quietFor = now - this.lastSignalAt;
183
+ if (phase && now - this.lastUsefulProgressAt >= this.usefulProgressWarningMs && !this.usefulProgressWarningPrinted) {
184
+ this.usefulProgressWarningPrinted = true;
185
+ this.note(`rafi: no completed tool or agent turn observed for ${formatDuration(now - this.lastUsefulProgressAt)}; current phase: ${phase.label}. The operation remains subject to its configured deadline.`);
186
+ return;
187
+ }
164
188
  if (phase && quietFor >= this.quietWarningMs && !this.quietWarningPrinted) {
165
189
  this.quietWarningPrinted = true;
166
- this.note(`rafi: ${this.provider ?? "provider"} has been quiet for ${formatDuration(quietFor)}; RAFI is still responsive and will keep waiting`);
190
+ const status = phase.label === "answer sent; waiting for Claude"
191
+ ? `Claude has not resumed its stream yet after your answer was sent (${formatDuration(quietFor)})`
192
+ : `${this.provider ?? "provider"} has been quiet for ${formatDuration(quietFor)}`;
193
+ this.note(`rafi: ${status}; RAFI is still responsive and will keep waiting`);
167
194
  return;
168
195
  }
169
196
  const provider = [this.provider, this.model].filter(Boolean).join("/");
@@ -188,9 +215,16 @@ export class ActivityReporter {
188
215
  this.lastSemanticKey = semanticKey;
189
216
  return;
190
217
  }
218
+ const semanticKey = semanticStatusKey(body);
219
+ // Append-only outputs cannot replace an earlier line. Emit the first
220
+ // state and meaningful state changes, but do not turn a changing counter
221
+ // or elapsed time into an endless log stream.
222
+ if (semanticKey === this.lastSemanticKey)
223
+ return;
191
224
  if (force || now - this.lastHeartbeatAt >= this.heartbeatMs) {
192
225
  this.lastHeartbeatAt = now;
193
226
  this.output.write(`[${new Date(now).toISOString()}] rafi working: ${body} (${formatDuration(now - this.commandStartedAt)})\n`);
227
+ this.lastSemanticKey = semanticKey;
194
228
  }
195
229
  }
196
230
  clearLine() {
@@ -246,22 +280,49 @@ export function reportBuilderEvent(event) {
246
280
  reporter.note(`rafi: ${event.provider} ${clean(event.reason, 300)}; retrying${attempt}${delay}`);
247
281
  reporter.update(`retrying ${event.provider}`, event.reason, { provider: event.provider });
248
282
  }
249
- else if (event.kind === "tool")
250
- reporter.update(`running ${event.name}`, briefInput(event.input));
283
+ else if (event.kind === "tool") {
284
+ switch (event.lifecycle) {
285
+ case "progress":
286
+ reporter.pulse();
287
+ break;
288
+ case "completed":
289
+ reporter.completedWork();
290
+ if (isFailedToolCompletion(event))
291
+ reporter.update("tool failed", event.name);
292
+ else
293
+ reporter.pulse();
294
+ break;
295
+ case "started":
296
+ case undefined:
297
+ // Older event producers omitted lifecycle for a tool start.
298
+ reporter.update(`running ${event.name}`, briefInput(event.input));
299
+ break;
300
+ }
301
+ }
251
302
  else if (event.kind === "text")
252
303
  reporter.update("processing agent response");
253
304
  else if (event.kind === "session-transition")
254
305
  reporter.update(`session ${event.transition}`, event.detail);
255
306
  else if (event.kind === "context-usage")
256
307
  reporter.pulse(event.percentage === undefined ? undefined : `context ${event.percentage.toFixed(0)}%`);
257
- else if (event.kind === "turn-complete")
308
+ else if (event.kind === "turn-complete") {
309
+ reporter.completedWork();
258
310
  reporter.update(event.result.isError ? "agent turn failed" : "agent turn complete");
311
+ }
259
312
  else if (event.kind === "error")
260
313
  reporter.update("provider error", event.message);
261
314
  }
262
315
  /** Async event queue that also feeds the active CLI reporter without consuming the stream. */
263
316
  export class BuilderEventQueue extends AsyncQueue {
317
+ observers = new Set();
318
+ /** Synchronous fan-out; never consumes or competes with the owning iterator. */
319
+ observe(listener) {
320
+ this.observers.add(listener);
321
+ return () => { this.observers.delete(listener); };
322
+ }
264
323
  push(event) {
324
+ for (const listener of this.observers)
325
+ listener(event);
265
326
  reportBuilderEvent(event);
266
327
  super.push(event);
267
328
  }
@@ -273,30 +334,37 @@ function briefInput(input) {
273
334
  const detail = value.command ?? value.file_path ?? value.path ?? value.pattern ?? value.query;
274
335
  return detail === undefined ? undefined : clean(String(detail), 100);
275
336
  }
337
+ function isFailedToolCompletion(event) {
338
+ const status = event.status?.toLowerCase();
339
+ return status === "failed"
340
+ || status === "error"
341
+ || (typeof event.exitCode === "number" && Number.isFinite(event.exitCode) && event.exitCode !== 0);
342
+ }
276
343
  function clean(value, maximum) {
277
344
  return value.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maximum);
278
345
  }
279
346
  function semanticStatusKey(body) {
280
- return body.replace(/\p{Number}+(?:[.,]\p{Number}+)*/gu, "#");
347
+ return body
348
+ // Counters, percentages, ticket suffixes, and model version numbers.
349
+ .replace(/\p{Number}+(?:[.,]\p{Number}+)*/gu, "#")
350
+ // Commit IDs, request IDs, and generated hexadecimal identifiers.
351
+ .replace(/\b[\da-f]{7,}\b/giu, "#")
352
+ .replace(/\s+/g, " ")
353
+ .trim();
281
354
  }
282
355
  function resolveTtyMode(requested) {
283
- const configured = requested ?? configuredTtyMode(process.env.RAFI_ACTIVITY_RENDER_MODE);
356
+ const configured = requested === "cursor" || requested === "records"
357
+ ? requested
358
+ : configuredTtyMode(process.env.RAFI_ACTIVITY_RENDER_MODE);
284
359
  if (configured === "cursor" || configured === "records")
285
360
  return configured;
286
- return truthyEnvironmentValue(process.env.CODEX_CI)
287
- || truthyEnvironmentValue(process.env.CI)
288
- || process.env.TERM?.toLowerCase() === "dumb"
289
- ? "records"
290
- : "cursor";
361
+ // A TTY-like output can still be an append-only transcript. Cursor redraws
362
+ // are therefore an explicit opt-in rather than an unsafe capability guess.
363
+ return "records";
291
364
  }
292
365
  function configuredTtyMode(value) {
293
366
  return value === "cursor" || value === "records" || value === "auto" ? value : "auto";
294
367
  }
295
- function truthyEnvironmentValue(value) {
296
- if (value === undefined)
297
- return false;
298
- return !["", "0", "false"].includes(value.trim().toLowerCase());
299
- }
300
368
  function formatDuration(milliseconds) {
301
369
  const seconds = Math.max(0, Math.floor(milliseconds / 1_000));
302
370
  if (seconds < 60)
@@ -58,6 +58,8 @@ export declare class ClaudeAdapter implements BuilderAdapter {
58
58
  private structuredError?;
59
59
  private apiErrorStatus?;
60
60
  private compactResult?;
61
+ private manualCompactionDeadline?;
62
+ private compactionPromise?;
61
63
  private autoCompactionPrepared;
62
64
  private preparedAutoCompactThreshold?;
63
65
  private preparedAutoCompactionPolicy?;
@@ -70,6 +72,8 @@ export declare class ClaudeAdapter implements BuilderAdapter {
70
72
  private activeProviderTurnSpanId?;
71
73
  private readonly toolSpans;
72
74
  private pending?;
75
+ private questionTrace?;
76
+ private responseOnlyTurn;
73
77
  private terminalResult?;
74
78
  private streamEnded;
75
79
  private closed;
@@ -78,7 +82,11 @@ export declare class ClaudeAdapter implements BuilderAdapter {
78
82
  /** Background loop: consume the SDK message stream until it ends. */
79
83
  private pump;
80
84
  private handle;
81
- sendTurn(text: string): Promise<TurnResult>;
85
+ sendTurn(text: string, policy?: {
86
+ handback?: boolean;
87
+ responseOnly?: boolean;
88
+ logicalActionId?: string;
89
+ }): Promise<TurnResult>;
82
90
  private sendTurnInternal;
83
91
  private captureStderr;
84
92
  private observationContext;
@@ -91,16 +99,23 @@ export declare class ClaudeAdapter implements BuilderAdapter {
91
99
  compact(): Promise<CompactResult>;
92
100
  private compactInternal;
93
101
  prepareAutoCompaction(thresholdPercent?: number | undefined): Promise<NativeAutoCompactionPolicy | void>;
102
+ private prepareAutoCompactionInternal;
94
103
  autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
95
104
  drainNativeCompactions(): NativeCompaction[];
96
105
  restoreNativeCompactions(compactions: NativeCompaction[]): void;
97
106
  contextUsage(): Promise<ContextUsage | undefined>;
98
107
  sessionUsage(): Promise<ProviderSessionUsage | undefined>;
99
108
  switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
109
+ observeEvents(listener: (event: BuilderEvent) => void): () => void;
100
110
  events(): AsyncIterable<BuilderEvent>;
101
111
  close(): Promise<void>;
102
112
  private failSessionIdentity;
103
113
  private observeSession;
104
114
  private settlePending;
115
+ private touchPendingTurn;
116
+ private beginProviderQuestionWait;
117
+ private endProviderQuestionWait;
118
+ private armPendingTurn;
119
+ private providerIdleTimeoutMs;
105
120
  private streamFailureResult;
106
121
  }