agent-afk 5.100.1 → 5.100.3

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.
@@ -2,4 +2,4 @@ import type { ProviderEvent } from '../../../provider.js';
2
2
  import type { RunTurnInput, TurnResult } from '../types.js';
3
3
  import type { TurnAccumulator } from './turn-accumulator.js';
4
4
  export type ToolRoundOutcome = 'continue' | 'terminated';
5
- export declare function runToolRound(turnResult: TurnResult, input: RunTurnInput, turn: TurnAccumulator, maxIterations: number): AsyncGenerator<ProviderEvent, ToolRoundOutcome, void>;
5
+ export declare function runToolRound(turnResult: TurnResult, input: RunTurnInput, turn: TurnAccumulator, maxIterations: number, softDeadlineMs: number): AsyncGenerator<ProviderEvent, ToolRoundOutcome, void>;
@@ -1,9 +1,12 @@
1
1
  import type { ProviderUsage } from '../../../provider.js';
2
+ import type { SOFT_DEADLINE_WIND_DOWN } from '../../shared/soft-deadline.js';
3
+ import type { TOOL_USE_LOOP_CAPPED } from '../../shared/tool-loop-cap.js';
4
+ export type WindDownReason = typeof TOOL_USE_LOOP_CAPPED | typeof SOFT_DEADLINE_WIND_DOWN;
2
5
  export declare class TurnAccumulator {
3
6
  usage: ProviderUsage;
4
7
  iterations: number;
5
8
  toolCallCount: number;
6
- capReached: boolean;
9
+ windDownReason: WindDownReason | null;
7
10
  readonly taskId: string;
8
11
  readonly startedAt: number;
9
12
  elapsedMs(): number;
@@ -18,6 +18,7 @@ export interface TurnRequestInput {
18
18
  thinking?: RunTurnInput['thinking'];
19
19
  effort?: RunTurnInput['effort'];
20
20
  maxToolUseIterations?: number;
21
+ softDeadlineMs?: number;
21
22
  traceWriter?: RunTurnInput['traceWriter'];
22
23
  subagentId?: string;
23
24
  throttleQueue?: RunTurnInput['throttleQueue'];
@@ -23,6 +23,7 @@ export interface AnthropicDirectQueryOptions {
23
23
  effort?: import('../../types/sdk-types.js').EffortLevel;
24
24
  baseUrl?: string;
25
25
  maxToolUseIterations?: number;
26
+ softDeadlineMs?: number;
26
27
  traceWriter?: import('../../trace/index.js').TraceWriter;
27
28
  subagentId?: string;
28
29
  autoResumeOnUsageLimit?: boolean;
@@ -11,6 +11,7 @@ export declare class AnthropicDirectQuery implements ProviderQuery {
11
11
  private readonly effort?;
12
12
  private readonly baseUrl?;
13
13
  private readonly maxToolUseIterations?;
14
+ private readonly softDeadlineMs?;
14
15
  private readonly traceWriter?;
15
16
  private readonly subagentId?;
16
17
  private readonly state;
@@ -17,6 +17,7 @@ export interface TurnDriverContext {
17
17
  readonly effort: import('../../types/sdk-types.js').EffortLevel | undefined;
18
18
  readonly baseUrl: string | undefined;
19
19
  readonly maxToolUseIterations: number | undefined;
20
+ readonly softDeadlineMs: number | undefined;
20
21
  readonly traceWriter: import('../../trace/index.js').TraceWriter | undefined;
21
22
  readonly subagentId: string | undefined;
22
23
  readonly mcpManager: import('../../mcp/index.js').McpManager | undefined;
@@ -13,6 +13,7 @@ export interface RunTurnInput {
13
13
  signal: AbortSignal;
14
14
  ctx: TranslateCtx;
15
15
  maxToolUseIterations?: number;
16
+ softDeadlineMs?: number;
16
17
  thinking?: ThinkingConfigParam;
17
18
  effort?: import('../../types/sdk-types.js').EffortLevel;
18
19
  fastMode?: boolean;
@@ -0,0 +1,8 @@
1
+ export declare const SOFT_DEADLINE_WIND_DOWN = "soft_deadline_wind_down";
2
+ export declare const SOFT_DEADLINE_NOTE: string;
3
+ export declare const SOFT_DEADLINE_RESERVE_FRACTION = 0.15;
4
+ export declare const SOFT_DEADLINE_MIN_RESERVE_MS = 30000;
5
+ export declare const SOFT_DEADLINE_MAX_RESERVE_MS: number;
6
+ export declare const SOFT_DEADLINE_MIN_BUDGET_MS = 120000;
7
+ export declare function resolveSoftDeadlineMs(hardTimeoutMs: number | undefined): number;
8
+ export declare function softDeadlineExpired(startedAt: number, softDeadlineMs: number, now?: number): boolean;
@@ -28,6 +28,7 @@ export interface AgentConfig {
28
28
  forceChatgptOAuth?: boolean;
29
29
  maxTurns?: number;
30
30
  maxToolUseIterations?: number;
31
+ softDeadlineMs?: number;
31
32
  thinking?: ThinkingConfig;
32
33
  effort?: EffortLevel;
33
34
  tools?: ToolConfig;
@@ -46,13 +46,13 @@ Never fabricate intent. When none is available the value is the literal `(none s
46
46
 
47
47
  **Concurrency floor — declared, conditional, and enforced.** *Through synthesis*, a full-regime review peaks at **2 concurrent sub-agent sessions** (Wave 1's two dimension agents) and dispatches **3 in total** (Wave 1 ×2, then Wave 2 ×1, sequential). Wave 1.5 runs inline in the orchestrator and dispatches nothing. **No wave nests a child**: the sub-agents are shell-less by design, and nothing in this skill requires them to run a command, so none of them needs to nest a `git-investigator` to comply. If you add a requirement here that needs a shell, you have silently doubled this floor — put that requirement in Wave 1.5 instead.
48
48
 
49
- **The post-synthesis tail is the conditional half of that budget.** A review that surfaces a `critical`/`high` finding invokes `/shadow-verify` (see **Post-synthesis** below), which dispatches one verifier per claim in parallel so the whole-run budget is **peak 3 concurrent, 5–6 total**, and it lands on exactly the high-stakes reviews most likely to hit a rate ceiling. Bound it: **at most 3 claims in a single round, no repeat rounds**, and hand the verifiers Wave 1.5's manifest so each re-derives the *claim* instead of re-locating evidence Wave 1.5 already pinned at the ref. Wave 1.5 verifies that a citation is real; shadow-verify re-derives whether the inference drawn from it holds — never substitute one for the other.
49
+ **The post-synthesis tail is the conditional half of that budget.** A review that surfaces a `critical`/`high` finding (or one whose `blocking` value departs from the default table — see **Post-synthesis** below) invokes `/shadow-verify`, which dispatches one verifier per claim in parallel, so the whole-run budget is **peak 3 concurrent, 5–6 total**, and it lands on exactly the high-stakes reviews most likely to hit a rate ceiling. Bound it: **at most 3 claims in a single round, no repeat rounds**, and hand the verifiers Wave 1.5's manifest so each re-derives the *claim* instead of re-locating evidence Wave 1.5 already pinned at the ref. Wave 1.5 verifies that a citation is real; shadow-verify re-derives whether the inference drawn from it holds — never substitute one for the other.
50
50
 
51
51
  **Wave 1 — Full review (regime=full, 2 parallel agents, `subagent_type: "research-agent"`).** Dispatch:
52
52
  - **security · api-compat** — contracts, auth, injection, breaking changes, secret exposure.
53
53
  - **correctness · spec-compliance · test-coverage · perf-observability** — logic bugs, regressions, whether the change satisfies its **stated intent** (unmet requirement or unrequested scope creep), missing tests, hot-path perf, logging gaps.
54
54
 
55
- Each agent receives: full diff + file tree + triage header + **reviewed ref (SHA)** + the **stated intent** (what the change is meant to accomplish, or `(none supplied)`), the severity rubric, and the finding schema.
55
+ Each agent receives: full diff + file tree + triage header + **reviewed ref (SHA)** + the **stated intent** (what the change is meant to accomplish, or `(none supplied)`), the severity rubric, **the `blocking` default table plus its overrides and assignment-order invariant**, and the finding schema. The blocking rules are not optional context: the finding schema mandates a `blocking` value per finding, so an agent that receives the schema without the table is being told to emit a field whose assignment rules it was never given.
56
56
 
57
57
  **Citation requirement (enforced per agent).** Wave 1 agents cite from the diff and from file reads available in their own session. They do **not** run git and do **not** re-read at the reviewed ref — that verification is centralized in Wave 1.5 below, which re-reads every `blocking`/`critical`/`high` citation **and every `file-state` citation at any severity** at the ref, then drops the fabricated ones. Each agent must:
58
58
  1. State the reviewed ref it was given in each finding: `ref: <sha>`.
@@ -97,24 +97,59 @@ This is the agent's first-line self-check; **Wave 1.5 Check B** independently re
97
97
 
98
98
  Returns a combined verification manifest: `[{type: citation|absence, claim, status, finding_id, evidence?}]`. Findings classified `fabricated` (citation) or `false-absent` (absence) are excluded from Wave 2 input. `diff-only` citations are passed to Wave 2 with a `⚠ diff-only citation — line absent at the reviewed ref` annotation and auto-downgraded one severity tier. `grep-unavailable` absence claims are passed through with their `[UNVERIFIED]` tag intact.
99
99
 
100
- **Wave 2 — Synthesis (1 agent, `subagent_type: "research-agent"`).** Receives: Wave 1 findings **after** citation-verification filtering + manifest of dropped/downgraded citations. Dedup by `(file, line_range, dimension)` — keep highest severity on exact match. Flag cross-agent conflicts as `CONFLICT` blocks (surface both rationales; do not auto-resolve).
100
+ **Wave 2 — Synthesis (1 agent, `subagent_type: "research-agent"`).** Receives: Wave 1 findings **after** citation-verification filtering + manifest of dropped/downgraded citations + **the merge-decision rule and its counts format below**. Wave 2 emits the verdict, so it needs that rule for exactly the reason Wave 1 needs the blocking table: an agent told to produce an output whose format and threshold it was never given will improvise both. Dedup by `(file, line_range, dimension)` — keep highest severity on exact match. Flag cross-agent conflicts as `CONFLICT` blocks (surface both rationales; do not auto-resolve).
101
101
 
102
102
  **Severity sort order within the blocking list:** findings tagged with semantics matching `invariant violation`, `defeats stated purpose`, `defeats refactor goal`, or `breaks stated contract` sort above all other `high` findings, even those with higher mechanical severity (e.g. test/build hygiene). Within that group, sort by tier (critical → high). Mechanical findings (missing test, build hygiene) sort last within their tier.
103
103
 
104
- Sort overall: critical → high → medium → low → nit; security first within tier; semantic/invariant findings above mechanical findings within tier. Template-fill summary block. Emit merge decision: **DO NOT MERGE**, **MERGE**. This is the terminal step — after emitting the decision, STOP. Do not act on any finding: no edits, commits, pushes, or PR/MR mutations. A blocking bug is a finding to report, not a fix to apply.
104
+ Sort overall: critical → high → medium → low → nit; security first within tier; semantic/invariant findings above mechanical findings within tier. Template-fill summary block.
105
105
 
106
- **Severity rubric:**
106
+ **Merge-decision rule (mandatory — do not improvise a threshold).**
107
+
108
+ Severity and disposition are **separate axes**. `severity` answers "how bad is this defect?" — it is a property of the finding. `blocking` answers "does this prevent merge?" — it is policy. Never let one silently encode the other.
109
+
110
+ **Wave 1 assigns** an explicit `blocking: true|false` to every finding it emits, from this default table. Wave 2 carries each value through unchanged and never re-derives it — synthesis dedups and sorts, it does not re-adjudicate disposition:
111
+
112
+ | severity | default `blocking` |
113
+ |---|---|
114
+ | `critical` | true |
115
+ | `high` | true |
116
+ | `medium` | true |
117
+ | `low` | false |
118
+ | `nit` | false |
119
+
120
+ **Overrides (each requires a one-clause justification appended to the finding):**
121
+ - A `medium` may be marked `blocking: false` when it is a bounded, non-data-affecting defect the author can reasonably land and follow up — e.g. a rare-input formatting error with no downstream consumer.
122
+ - A `medium` in the `security` dimension is **never** overridable to `false`; today's narrow reachability is tomorrow's incident.
123
+ - A `medium` representing a material data-integrity risk or a likely production failure under normal usage is **never** overridable to `false` — a race that intermittently loses user state stays blocking even when its blast radius keeps it out of `high`.
124
+ - A `low` or `nit` may be marked `blocking: true` only for a stated external constraint (release gate, compliance requirement). Do not use this to smuggle a preference.
125
+
126
+ **Invariant — assignment order.** `blocking` is assigned from the **pre-downgrade** severity. A finding later downgraded by **any** downgrade rule in this file — the api-compat reachability rule (which drops straight to `nit`, two tiers in one step), its grep-unavailable fallback, Wave 1's absence-grounding fallback, Wave 1.5's `diff-only` citation rule, Wave 1.5's `grep-unavailable` absence rule, or the confidence rule below — **keeps the `blocking` value its pre-downgrade severity earned**: a downgrade lowers severity, never disposition. Only an explicit, justified override from the list above may flip `blocking`. Without this ordering, a security or data-integrity `medium` would silently become non-blocking by being downgraded rather than waived, defeating the two never-overridable rules above through a path that requires no justification at all.
127
+
128
+ A `blocking: true` that survives a downgrade this way is **not** an override and needs no justification clause: it carries `· blocking preserved from pre-downgrade <severity>` instead, and the `low`/`nit` external-constraint rule above does not apply to it. Without this exemption the invariant and that rule contradict each other — every downgraded `medium` would land as a `low`/`nit` carrying `blocking: true` with no admissible reason to write, forcing the reviewer to either fabricate an external constraint or emit a schema-violating finding.
129
+
130
+ Emit **DO NOT MERGE** when one or more findings carry `blocking: true` after Wave 1.5 filtering. Emit **MERGE** only when every surviving finding is `blocking: false`.
131
+
132
+ State the counts that drove the decision on the same line, **with a dimension breakdown for any blocking medium**, e.g. `Decision: DO NOT MERGE — 1 high, 2 medium blocking (1 security, 1 correctness); 1 medium waived, 3 low.` or `Decision: MERGE — 0 blocking (2 medium waived, 3 low, 1 nit).` If zero findings survived, say `Decision: MERGE — 0 findings.` Never emit a bare verdict with no counts, and never waive a finding silently — a waived medium must appear in the count with its justification.
133
+
134
+ This is the terminal step — after emitting the decision, STOP. Do not act on any finding: no edits, commits, pushes, or PR/MR mutations. A blocking bug is a finding to report, not a fix to apply.
135
+
136
+ **Severity rubric (impact axis only — severity measures blast radius and reachability, never category):**
107
137
  - `critical` — data loss, auth bypass, secret exposure, RCE. If it cannot cause unauthorized access or data loss, it is NOT critical.
108
- - `high` — wrong output under reachable conditions (reachable = called from production code, not tests-only)
109
- - `medium` — missing edge case, perf degraded under load, deprecated API
110
- - `low` — missing test, unclear error message
111
- - `nit` — naming, formatting
138
+ - `high` — produces wrong output or an unsafe state under reachable conditions (reachable = called from production code, not tests-only)
139
+ - `medium` — produces wrong output or an unsafe state, but only under narrow, rare, or hard-to-reach conditions
140
+ - `low` — does not affect production behavior today. **Absent an impact claim**, these land here: missing test, unclear error message, doc/PR-body mismatch, dead code, stale comment, deprecated API with no removal date, perf concern with no load evidence. An instance that *does* carry an impact claim re-homes upward per the rules below — no category pins a finding to this tier, and a `security`-dimension finding is never parked here just because its category appears in this list
141
+ - `nit` — naming, formatting; no behavioral claim at stake
142
+
143
+ **A category is never a tier by itself.** Re-home by impact, not by kind:
144
+ - "missing edge case" → `high` if reachable in production and wrong; `medium` if reachable but rare; `low` if only reachable from tests.
145
+ - "perf degraded under load" → `high` if unbounded or production-breaking; `medium` if bounded but measured; `low` if theoretical with no load evidence. "Measured" does not require running a benchmark — Wave 1 is shell-less by design — so a load number in the **stated intent**, or a committed benchmark or perf test `Read` from the diff or repo, qualifies.
146
+ - "deprecated API" → `low` by default; `high` only when a hard removal date will break a production call path.
112
147
 
113
148
  Confidence `low` → auto-downgrade one tier + append `[low confidence — verify with runtime context]`.
114
149
 
115
150
  **Output per dimension:** if you have read the relevant file(s) and have either real findings or a confirmed clean read, emit findings or `no issues found — read <file>`. If evidence is insufficient — you could not read the file, the tool was unavailable, no production importers were found for the symbol, or no test file exists at the asserted path — emit `unverified — <reason>` naming the missing evidence rather than invent a finding to fill the slot. Banned words from the hedging list (`ensure`, `consider`, `may`, `could`) remain banned **inside findings**; the `unverified` channel is the sanctioned path for uncertainty.
116
151
 
117
- **Finding schema:** `severity · confidence · dimension · file:line_range · ref:<sha> · citation-type:(diff-context|file-state) · finding (one concrete sentence naming the failure mode) · evidence (verbatim code ≤4 lines) · suggestion (one concrete fix)`.
152
+ **Finding schema:** `severity · blocking:(true|false) · confidence · dimension · file:line_range · ref:<sha> · citation-type:(diff-context|file-state) · finding (one concrete sentence naming the failure mode) · evidence (verbatim code ≤4 lines) · suggestion (one concrete fix)`. When `blocking` departs from the default table, append `· waived: <one clause>` (or `· escalated: <one clause>`) naming the reason.
118
153
 
119
154
  **Epistemic scope disclosure (required in synthesis output).** The "What was not checked" section must include:
120
155
  - Which ref citations were verified against in Wave 1.5 (list the SHA or `unknown` if patch-file input). Example: `Citations verified inline against branch HEAD abc1234.`
@@ -122,4 +157,6 @@ Confidence `low` → auto-downgrade one tier + append `[low confidence — verif
122
157
  - Any topical gaps (e.g. 'did not review Telegram surface', 'did not run tests').
123
158
  - Whether a **stated intent** was available and spec-compliance was assessed. Example: `Stated intent: PR #123 title+body — spec-compliance assessed.` or `Stated intent: (none supplied) — spec-compliance not assessed.`
124
159
 
125
- **Post-synthesis:** if any `critical` or `high` finding is present, invoke `/shadow-verify` on those findings before surfacing to the user. Shadow-verify independently re-derives each top-severity claim against source; fabricated or unsupportable findings drop here before they reach the merge decision. `medium` and below go straight through.
160
+ **Post-synthesis:** if any `critical` or `high` finding is present, **or any finding whose `blocking` value departs from the default table** (a waived `medium`, an escalated `low`/`nit`), invoke `/shadow-verify` on those findings before surfacing to the user. Shadow-verify independently re-derives each claim against source; fabricated or unsupportable findings drop here before they reach the merge decision. An overridden finding is routed because the agent that found it also set its disposition and wrote its own justification — the waiver is otherwise the only judgement in this pipeline with no second reader. `medium` and below **at their default disposition** go straight through.
161
+
162
+ The concurrency floor's bound still holds — **at most 3 claims in a single round, no repeat rounds**. When critical/high plus overridden findings exceed 3, verify the **overridden ones first**: an unreviewed waiver silently removes a blocker (fails open), while an unreviewed `critical` still blocks (fails closed). Name any claim that went unverified in the epistemic-scope section.