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.
- package/README.md +18 -7
- package/dist/activity.d.ts +11 -1
- package/dist/activity.js +84 -16
- package/dist/adapters/claude.d.ts +16 -1
- package/dist/adapters/claude.js +196 -29
- package/dist/adapters/codex.d.ts +15 -1
- package/dist/adapters/codex.js +172 -38
- package/dist/adapters/recovering.d.ts +3 -1
- package/dist/adapters/recovering.js +12 -4
- package/dist/adapters/types.d.ts +34 -1
- package/dist/agentRun.js +2 -0
- package/dist/branch/currentGuard.d.ts +3 -1
- package/dist/branch/currentGuard.js +11 -2
- package/dist/branch/resume.js +1 -1
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +90 -11
- package/dist/buildApproval.d.ts +9 -0
- package/dist/buildApproval.js +126 -0
- package/dist/buildRuns.js +74 -55
- package/dist/cli/events.js +21 -1
- package/dist/cli/recovery.js +9 -13
- package/dist/cli/runtimeAuthPrompt.d.ts +7 -0
- package/dist/cli/runtimeAuthPrompt.js +40 -5
- package/dist/cli/start.d.ts +16 -1
- package/dist/cli/start.js +161 -81
- package/dist/cli/tickets.d.ts +11 -0
- package/dist/cli/tickets.js +382 -145
- package/dist/continuity.d.ts +7 -1
- package/dist/continuity.js +86 -7
- package/dist/diagnostics.js +37 -8
- package/dist/foreman.d.ts +8 -1
- package/dist/foreman.js +144 -29
- package/dist/handoffs.js +121 -79
- package/dist/humanDecision.d.ts +18 -0
- package/dist/humanDecision.js +43 -0
- package/dist/index.js +2 -1
- package/dist/log.d.ts +1 -1
- package/dist/managerPacket.js +1 -0
- package/dist/observability.js +3 -7
- package/dist/processIdentity.d.ts +8 -0
- package/dist/processIdentity.js +40 -0
- package/dist/projectDiagnostics.js +3 -16
- package/dist/providerQuestions.d.ts +5 -0
- package/dist/providerQuestions.js +27 -2
- package/dist/qaDeliveryJournal.d.ts +70 -0
- package/dist/qaDeliveryJournal.js +1 -0
- package/dist/qaFailureDelivery.d.ts +26 -10
- package/dist/qaFailureDelivery.js +399 -412
- package/dist/qaHandbackHistory.d.ts +4 -0
- package/dist/qaHandbackHistory.js +30 -0
- package/dist/qaHandbackMigration.d.ts +9 -0
- package/dist/qaHandbackMigration.js +147 -0
- package/dist/qaPrerequisites.d.ts +26 -0
- package/dist/qaPrerequisites.js +61 -0
- package/dist/qaProtocolV2.d.ts +8 -0
- package/dist/qaProtocolV2.js +3 -3
- package/dist/qaReview.d.ts +8 -0
- package/dist/qaReview.js +54 -10
- package/dist/questionTrace.d.ts +31 -0
- package/dist/questionTrace.js +85 -0
- package/dist/readOnlyProposal.d.ts +8 -0
- package/dist/readOnlyProposal.js +41 -0
- package/dist/recoveryPolicy.js +14 -3
- package/dist/runtimeAuth.d.ts +2 -1
- package/dist/runtimeAuth.js +2 -2
- package/dist/runtimeReadiness.d.ts +13 -1
- package/dist/runtimeReadiness.js +69 -11
- package/dist/sessionLifecycle.d.ts +1 -0
- package/dist/sessionLifecycle.js +53 -3
- package/dist/sources/sourceRegistry.d.ts +7 -0
- package/dist/sources/sourceRegistry.js +14 -0
- package/dist/stateTransfer.js +2 -0
- package/dist/statusReporter.d.ts +3 -1
- package/dist/statusReporter.js +8 -2
- package/dist/supervisedStart.d.ts +12 -0
- package/dist/supervisedStart.js +276 -0
- package/dist/supervisor.js +44 -22
- package/dist/ticketPopulation.js +1 -0
- package/dist/tickets/setupConfig.d.ts +8 -0
- package/dist/tickets/setupConfig.js +38 -4
- package/dist/util/deadline.d.ts +13 -0
- package/dist/util/deadline.js +60 -0
- package/dist/workflowDb.d.ts +55 -11
- package/dist/workflowDb.js +284 -70
- package/dist/workflowReader.d.ts +10 -2
- package/dist/workflowReader.js +35 -1
- 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;
|
|
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
|
|
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`:
|
|
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`
|
|
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/**` |
|
|
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
|
|
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
|
|
package/dist/activity.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ export interface ActivityReporterOptions {
|
|
|
11
11
|
tickMs?: number;
|
|
12
12
|
heartbeatMs?: number;
|
|
13
13
|
quietWarningMs?: number;
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
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
|
}
|