ai-foreman 1.7.18 → 1.7.20
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 +33 -8
- package/dist/activity.d.ts +11 -1
- package/dist/activity.js +70 -15
- package/dist/adapters/claude.d.ts +11 -1
- package/dist/adapters/claude.js +122 -18
- package/dist/adapters/codex.d.ts +13 -1
- package/dist/adapters/codex.js +148 -31
- package/dist/adapters/recovering.d.ts +3 -1
- package/dist/adapters/recovering.js +12 -4
- package/dist/adapters/types.d.ts +27 -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 +92 -12
- package/dist/buildAdmission.d.ts +61 -0
- package/dist/buildAdmission.js +284 -0
- package/dist/buildApproval.d.ts +11 -0
- package/dist/buildApproval.js +143 -0
- package/dist/buildLaunchGate.d.ts +1 -0
- package/dist/buildLaunchGate.js +36 -0
- package/dist/buildRuns.d.ts +18 -1
- package/dist/buildRuns.js +261 -83
- package/dist/cli/recovery.js +9 -13
- package/dist/cli/runtimeAuthPrompt.d.ts +12 -0
- package/dist/cli/runtimeAuthPrompt.js +55 -6
- package/dist/cli/start.d.ts +24 -1
- package/dist/cli/start.js +2026 -1808
- package/dist/cli/tickets.d.ts +11 -0
- package/dist/cli/tickets.js +382 -145
- package/dist/continuity.d.ts +12 -2
- package/dist/continuity.js +131 -12
- package/dist/diagnostics.js +35 -1
- package/dist/foreman.d.ts +10 -3
- package/dist/foreman.js +207 -46
- package/dist/handoffs.js +121 -79
- package/dist/humanDecision.d.ts +18 -0
- package/dist/humanDecision.js +44 -0
- package/dist/index.js +4 -2
- package/dist/log.d.ts +1 -1
- package/dist/managerPacket.js +1 -0
- package/dist/observability.js +2 -1
- package/dist/processIdentity.d.ts +21 -1
- package/dist/processIdentity.js +80 -5
- package/dist/providerQuestions.d.ts +5 -0
- package/dist/providerQuestions.js +24 -3
- 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 +113 -27
- 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/readinessCleanup.d.ts +19 -0
- package/dist/readinessCleanup.js +115 -0
- package/dist/readinessGate.d.ts +1 -0
- package/dist/readinessGate.js +82 -0
- package/dist/recoveryGuidance.d.ts +8 -0
- package/dist/recoveryGuidance.js +25 -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 +25 -1
- package/dist/runtimeReadiness.js +256 -14
- 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 +37 -7
- package/dist/statusReporter.d.ts +3 -1
- package/dist/statusReporter.js +8 -2
- package/dist/supervisedStart.d.ts +18 -0
- package/dist/supervisedStart.js +382 -0
- package/dist/supervisor.d.ts +2 -0
- package/dist/supervisor.js +70 -24
- 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/windowsProbeJob.d.ts +12 -0
- package/dist/windowsProbeJob.js +144 -0
- package/dist/workflowDb.d.ts +125 -12
- package/dist/workflowDb.js +810 -72
- package/dist/workflowReader.d.ts +17 -2
- package/dist/workflowReader.js +118 -1
- package/package.json +8 -4
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,11 +162,23 @@ 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 resume` to select a build and 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, its answer command, and the short `rafi resume` recovery command.
|
|
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.
|
|
167
178
|
|
|
168
|
-
|
|
179
|
+
A completed QA turn with missing/invalid continuity metadata or a malformed response can trigger one automatic complete fresh review per ticket. That allowance is durable across restarts and shared across protocol failures. The old session and snapshot close before the new review starts. Exhausted same-session report correction and malformed resumed report/acknowledgement responses use this route, rather than requiring an immediate manual resume. Earlier invalid responses cannot authorize Builder work or a QA pass. Provider execution failures, uncertain dispatch, invalid acceptance receipts, and modified recovery evidence still pause. Repeated failures exhaust the allowance and preserve recoverable state; independent eligible tickets can continue.
|
|
180
|
+
|
|
181
|
+
Owner-only recovery packets under `.foreman/qa-report-recovery/` bind the review to a full frozen source digest: commit, porcelain status, staged and unstaged bytes, and untracked content. Every fresh attempt captures and compares that complete state. Drift appends the new state and forces a complete review; correction-only reconstruction is never applied to changed source. Packet revisions and the authoritative WorkflowDb recovery head advance together. A successor must have a location-scoped identity, accept a handoff containing the complete resource inventory (purpose, byte count, digest, and recovery requirement), and acknowledge the copied packet before reporting. Paused recovery is available through `rafi resume` or `rafi build:resume` in the project directory. The picker resolves the ticket, current QA revision, and validated QA handoff internally; explicit recovery flags still reject stale revisions.
|
|
169
182
|
|
|
170
183
|
### `ai-foreman manager`
|
|
171
184
|
|
|
@@ -214,11 +227,11 @@ gh repo view <host>/<owner>/<repo>
|
|
|
214
227
|
| `-a, --agent <agent>` | `claude` / `codex` | single `rafi-config.yaml` target, otherwise `claude` | Builder agent. Explicit `--agent` always wins. |
|
|
215
228
|
| `-m, --model <model>` | `gpt-5.5` / any supported agent model | agent default | Overrides the builder model. |
|
|
216
229
|
| `--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/**` |
|
|
230
|
+
| `--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
231
|
| `--fast` | flag | off | Lower latency. |
|
|
219
232
|
| `-y, --yes` | flag | off | Skips confirmation before builder edits ticket files. |
|
|
220
233
|
|
|
221
|
-
Use `ai-foreman tickets setup:init` or `setup:update` to
|
|
234
|
+
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
235
|
|
|
223
236
|
Use `ai-foreman tickets review` to accept, defer, or dismiss pending split/combine/duplicate recommendations rendered in the progress doc.
|
|
224
237
|
|
|
@@ -670,3 +683,15 @@ Package output:
|
|
|
670
683
|
- **`special-agents`** — library (rules + skills + agents + composition)
|
|
671
684
|
- **`ai-foreman`** — this runtime
|
|
672
685
|
- **`@rafi-ai/cli`** — CLI for `rafi create` and `rafi compile`
|
|
686
|
+
|
|
687
|
+
### Recovery ownership upgrades
|
|
688
|
+
|
|
689
|
+
Stop existing workers before upgrading. The recovery database now records preparation admission, single-use launches, retry lineage, owned readiness processes and monotonic lease generations. Older binaries must not write an upgraded database; preserve these records when troubleshooting rather than deleting locks. Unknown process identity, incomplete child-launch evidence or an unresolved provider dispatch requires reconciliation. A stale heartbeat alone does not authorize another worker. Read-only resume inspection supports older storage without migrating it.
|
|
690
|
+
|
|
691
|
+
Ownership schema version 2 adds bounded child registration/acknowledgement, explicit child-workflow ownership, and separate readiness/cleanup outcomes. Build readiness requires the original admission and owning project even when the command executes in an external worktree. Both the current build and competing starts wait for verified probe cleanup. Runtime snapshot, dispatch, continuity and session writes retain original authority; releasing a lease does not make those records writable by an arbitrary caller. Completed, cancelled and superseded builds cannot be reactivated by saving a snapshot.
|
|
692
|
+
|
|
693
|
+
Ownership schema version 3 gates provider readiness behind registered helper authorization and records cleanup separately. Cleanup is reachable through both short resume commands even when its owning build is terminal or has no valid build snapshot. Restricted cleanup connections can update only their selected readiness records. Migration waits for local legacy execution to become safely quiescent; persistent compatibility guards then reject already-open older writers as well as new older opens. Uncertain remote provider records survive the upgrade unchanged so their owning recovery flow remains reachable. Ordinary starts remain blocked until those operations are reconciled. A lost acknowledgement cannot revoke an already-authorized helper.
|
|
694
|
+
|
|
695
|
+
Standalone readiness also rejects unverified cleanup. The current process retains its cleanup evidence and checks it before another probe starts; restoring process visibility permits a retry. Standalone commands do not create a build recovery database, so this evidence does not survive process exit.
|
|
696
|
+
|
|
697
|
+
On macOS/Linux, readiness cleanup checks the dedicated process group and inherited probe tags, including tagged descendants that escape the group. This is not a sandbox for descendants that deliberately remove their tag and detach. Unavailable inventory and ambiguous legacy evidence remain unresolved. The Windows readiness implementation targets Windows 10 / Server 2016 APIs, but those minimum versions have not been verified. Native Windows verification remains a release gate. Readiness providers start inside a non-breakaway Job Object with kill-on-close behavior, using built-in Windows PowerShell and `Add-Type`. Standard npm `.cmd`/`.bat` provider shims resolve to direct Node arguments; arbitrary shell shims are rejected. The Windows Job Object applies to readiness probes; it does not add containment for arbitrary Builder commands. Native regression tests are in `.github/workflows/build-recovery.yml`; the implementation plan records actual platform verification.
|
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("/");
|
|
@@ -253,22 +280,49 @@ export function reportBuilderEvent(event) {
|
|
|
253
280
|
reporter.note(`rafi: ${event.provider} ${clean(event.reason, 300)}; retrying${attempt}${delay}`);
|
|
254
281
|
reporter.update(`retrying ${event.provider}`, event.reason, { provider: event.provider });
|
|
255
282
|
}
|
|
256
|
-
else if (event.kind === "tool")
|
|
257
|
-
|
|
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
|
+
}
|
|
258
302
|
else if (event.kind === "text")
|
|
259
303
|
reporter.update("processing agent response");
|
|
260
304
|
else if (event.kind === "session-transition")
|
|
261
305
|
reporter.update(`session ${event.transition}`, event.detail);
|
|
262
306
|
else if (event.kind === "context-usage")
|
|
263
307
|
reporter.pulse(event.percentage === undefined ? undefined : `context ${event.percentage.toFixed(0)}%`);
|
|
264
|
-
else if (event.kind === "turn-complete")
|
|
308
|
+
else if (event.kind === "turn-complete") {
|
|
309
|
+
reporter.completedWork();
|
|
265
310
|
reporter.update(event.result.isError ? "agent turn failed" : "agent turn complete");
|
|
311
|
+
}
|
|
266
312
|
else if (event.kind === "error")
|
|
267
313
|
reporter.update("provider error", event.message);
|
|
268
314
|
}
|
|
269
315
|
/** Async event queue that also feeds the active CLI reporter without consuming the stream. */
|
|
270
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
|
+
}
|
|
271
323
|
push(event) {
|
|
324
|
+
for (const listener of this.observers)
|
|
325
|
+
listener(event);
|
|
272
326
|
reportBuilderEvent(event);
|
|
273
327
|
super.push(event);
|
|
274
328
|
}
|
|
@@ -280,6 +334,12 @@ function briefInput(input) {
|
|
|
280
334
|
const detail = value.command ?? value.file_path ?? value.path ?? value.pattern ?? value.query;
|
|
281
335
|
return detail === undefined ? undefined : clean(String(detail), 100);
|
|
282
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
|
+
}
|
|
283
343
|
function clean(value, maximum) {
|
|
284
344
|
return value.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maximum);
|
|
285
345
|
}
|
|
@@ -293,23 +353,18 @@ function semanticStatusKey(body) {
|
|
|
293
353
|
.trim();
|
|
294
354
|
}
|
|
295
355
|
function resolveTtyMode(requested) {
|
|
296
|
-
const configured = requested
|
|
356
|
+
const configured = requested === "cursor" || requested === "records"
|
|
357
|
+
? requested
|
|
358
|
+
: configuredTtyMode(process.env.RAFI_ACTIVITY_RENDER_MODE);
|
|
297
359
|
if (configured === "cursor" || configured === "records")
|
|
298
360
|
return configured;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
? "records"
|
|
303
|
-
: "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";
|
|
304
364
|
}
|
|
305
365
|
function configuredTtyMode(value) {
|
|
306
366
|
return value === "cursor" || value === "records" || value === "auto" ? value : "auto";
|
|
307
367
|
}
|
|
308
|
-
function truthyEnvironmentValue(value) {
|
|
309
|
-
if (value === undefined)
|
|
310
|
-
return false;
|
|
311
|
-
return !["", "0", "false"].includes(value.trim().toLowerCase());
|
|
312
|
-
}
|
|
313
368
|
function formatDuration(milliseconds) {
|
|
314
369
|
const seconds = Math.max(0, Math.floor(milliseconds / 1_000));
|
|
315
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,12 +99,14 @@ 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;
|