@phnx-labs/agents-cli 1.22.45 → 1.22.47

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 (177) hide show
  1. package/CHANGELOG.md +303 -0
  2. package/LICENSE +102 -182
  3. package/README.md +54 -17
  4. package/dist/cli/command-registry.d.ts +2 -1
  5. package/dist/cli/command-registry.js +6 -2
  6. package/dist/commands/accounts.js +56 -12
  7. package/dist/commands/auth.d.ts +2 -0
  8. package/dist/commands/auth.js +229 -0
  9. package/dist/commands/browser-sessions-picker.js +9 -7
  10. package/dist/commands/browser.js +266 -15
  11. package/dist/commands/config.js +26 -4
  12. package/dist/commands/cost.d.ts +1 -1
  13. package/dist/commands/detach.d.ts +13 -0
  14. package/dist/commands/detach.js +11 -8
  15. package/dist/commands/exec.js +55 -38
  16. package/dist/commands/feed-watch.d.ts +2 -0
  17. package/dist/commands/feed-watch.js +28 -0
  18. package/dist/commands/feed.js +27 -0
  19. package/dist/commands/feedback.js +3 -19
  20. package/dist/commands/insights.d.ts +1 -1
  21. package/dist/commands/insights.js +2 -2
  22. package/dist/commands/lease.js +3 -3
  23. package/dist/commands/routines.js +1 -1
  24. package/dist/commands/sessions-picker-factory.d.ts +2 -1
  25. package/dist/commands/sessions-picker-factory.js +1 -1
  26. package/dist/commands/sessions-stop.d.ts +24 -0
  27. package/dist/commands/sessions-stop.js +66 -0
  28. package/dist/commands/sessions-trace.d.ts +82 -0
  29. package/dist/commands/sessions-trace.js +387 -0
  30. package/dist/commands/sessions.js +4 -0
  31. package/dist/commands/ssh.d.ts +15 -0
  32. package/dist/commands/ssh.js +194 -16
  33. package/dist/commands/teams.js +34 -2
  34. package/dist/commands/view.d.ts +2 -0
  35. package/dist/commands/view.js +19 -32
  36. package/dist/commands/watchdog.js +9 -14
  37. package/dist/lib/account-capabilities.d.ts +4 -0
  38. package/dist/lib/account-capabilities.js +13 -9
  39. package/dist/lib/account-registry.d.ts +2 -0
  40. package/dist/lib/account-registry.js +30 -2
  41. package/dist/lib/accounting/rotate.d.ts +9 -0
  42. package/dist/lib/accounting/rotate.js +12 -2
  43. package/dist/lib/accounting/usage.d.ts +92 -6
  44. package/dist/lib/accounting/usage.js +329 -75
  45. package/dist/lib/agent-modes.js +29 -1
  46. package/dist/lib/agent-spec/agents.d.ts +4 -3
  47. package/dist/lib/agent-spec/agents.js +20 -7
  48. package/dist/lib/analytics/mix-commands.d.ts +1 -1
  49. package/dist/lib/analytics/mix-commands.js +2 -2
  50. package/dist/lib/auth-health.d.ts +13 -3
  51. package/dist/lib/auth-health.js +50 -8
  52. package/dist/lib/browser/devices.d.ts +19 -0
  53. package/dist/lib/browser/devices.js +23 -0
  54. package/dist/lib/browser/ipc.d.ts +1 -1
  55. package/dist/lib/browser/ipc.js +33 -7
  56. package/dist/lib/browser/profiles.d.ts +106 -1
  57. package/dist/lib/browser/profiles.js +300 -17
  58. package/dist/lib/browser/remote-control.d.ts +26 -2
  59. package/dist/lib/browser/remote-control.js +33 -3
  60. package/dist/lib/browser/runtime-state.d.ts +9 -0
  61. package/dist/lib/browser/runtime-state.js +24 -8
  62. package/dist/lib/browser/service.d.ts +37 -0
  63. package/dist/lib/browser/service.js +78 -20
  64. package/dist/lib/browser/sessions-list.d.ts +1 -3
  65. package/dist/lib/browser/sessions-list.js +3 -16
  66. package/dist/lib/browser/types.d.ts +12 -1
  67. package/dist/lib/cli-resources.js +17 -15
  68. package/dist/lib/codex-policy.d.ts +11 -1
  69. package/dist/lib/codex-policy.js +20 -5
  70. package/dist/lib/config-keys.d.ts +4 -3
  71. package/dist/lib/config-keys.js +17 -7
  72. package/dist/lib/daemon/runner.js +15 -2
  73. package/dist/lib/daemon-ticks.js +2 -2
  74. package/dist/lib/device-config.d.ts +25 -8
  75. package/dist/lib/device-config.js +90 -17
  76. package/dist/lib/devices/config-migration.d.ts +7 -0
  77. package/dist/lib/devices/config-migration.js +67 -3
  78. package/dist/lib/devices/harness-inventory.js +20 -3
  79. package/dist/lib/devices/health.d.ts +27 -2
  80. package/dist/lib/devices/health.js +39 -9
  81. package/dist/lib/devices/interactive-host.d.ts +15 -0
  82. package/dist/lib/devices/interactive-host.js +53 -0
  83. package/dist/lib/devices/registry.d.ts +61 -4
  84. package/dist/lib/devices/registry.js +94 -38
  85. package/dist/lib/devices/stats-cache.d.ts +6 -0
  86. package/dist/lib/devices/stats-cache.js +10 -2
  87. package/dist/lib/exec.d.ts +25 -3
  88. package/dist/lib/exec.js +75 -22
  89. package/dist/lib/feed/answer.d.ts +29 -0
  90. package/dist/lib/feed/answer.js +145 -0
  91. package/dist/lib/feed/attention.d.ts +83 -0
  92. package/dist/lib/feed/attention.js +0 -0
  93. package/dist/lib/feed/feed.d.ts +109 -0
  94. package/dist/lib/feed/feed.js +154 -3
  95. package/dist/lib/feed/pr-status.d.ts +14 -0
  96. package/dist/lib/feed/pr-status.js +47 -0
  97. package/dist/lib/feed/watch.d.ts +59 -0
  98. package/dist/lib/feed/watch.js +180 -0
  99. package/dist/lib/fleet/capture.js +6 -0
  100. package/dist/lib/fleet/remote-login.js +8 -3
  101. package/dist/lib/fleet/types.d.ts +17 -0
  102. package/dist/lib/harness/adapter.d.ts +11 -0
  103. package/dist/lib/harness/adapters/claude.js +38 -24
  104. package/dist/lib/harness/adapters/codex.js +2 -4
  105. package/dist/lib/hooks/cache.js +8 -0
  106. package/dist/lib/hooks/match.d.ts +3 -0
  107. package/dist/lib/hooks/match.js +25 -0
  108. package/dist/lib/hosts/passthrough.js +16 -0
  109. package/dist/lib/hosts/registry.js +7 -0
  110. package/dist/lib/identity/client.d.ts +53 -0
  111. package/dist/lib/identity/client.js +106 -0
  112. package/dist/lib/identity/index.d.ts +115 -0
  113. package/dist/lib/identity/index.js +82 -0
  114. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  115. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  116. package/dist/lib/menubar/install-menubar.d.ts +13 -0
  117. package/dist/lib/menubar/install-menubar.js +26 -2
  118. package/dist/lib/open-url.d.ts +60 -2
  119. package/dist/lib/open-url.js +167 -12
  120. package/dist/lib/probe.d.ts +8 -0
  121. package/dist/lib/probe.js +105 -0
  122. package/dist/lib/refresh-coordinator.d.ts +1 -1
  123. package/dist/lib/refresh-coordinator.js +1 -1
  124. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  125. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  126. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  127. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  128. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  129. package/dist/lib/session/active.d.ts +67 -0
  130. package/dist/lib/session/active.js +69 -1
  131. package/dist/lib/session/db.js +4 -2
  132. package/dist/lib/session/discover.d.ts +2 -0
  133. package/dist/lib/session/discover.js +12 -6
  134. package/dist/lib/session/prompt.d.ts +35 -0
  135. package/dist/lib/session/prompt.js +56 -0
  136. package/dist/lib/session/remote/watch.d.ts +1 -1
  137. package/dist/lib/session/remote/watch.js +6 -1
  138. package/dist/lib/session/render.js +7 -3
  139. package/dist/lib/session/session-cache.js +5 -1
  140. package/dist/lib/session/trajectory-compare.d.ts +47 -0
  141. package/dist/lib/session/trajectory-compare.js +116 -0
  142. package/dist/lib/session/trajectory-html.d.ts +15 -0
  143. package/dist/lib/session/trajectory-html.js +750 -0
  144. package/dist/lib/session/trajectory-lineage.d.ts +92 -0
  145. package/dist/lib/session/trajectory-lineage.js +195 -0
  146. package/dist/lib/session/trajectory-text.d.ts +37 -0
  147. package/dist/lib/session/trajectory-text.js +277 -0
  148. package/dist/lib/session/trajectory.d.ts +102 -0
  149. package/dist/lib/session/trajectory.js +335 -0
  150. package/dist/lib/startup/command-registry.d.ts +5 -2
  151. package/dist/lib/startup/command-registry.js +8 -5
  152. package/dist/lib/state.d.ts +1 -1
  153. package/dist/lib/state.js +4 -2
  154. package/dist/lib/teams/agents.js +9 -0
  155. package/dist/lib/terminal/inject.d.ts +19 -0
  156. package/dist/lib/terminal/inject.js +19 -9
  157. package/dist/lib/tmux/session.d.ts +46 -13
  158. package/dist/lib/tmux/session.js +173 -27
  159. package/dist/lib/types.d.ts +16 -0
  160. package/dist/lib/usage-backoff.d.ts +2 -1
  161. package/dist/lib/usage-backoff.js +60 -19
  162. package/dist/lib/usage-refresh.d.ts +17 -3
  163. package/dist/lib/usage-refresh.js +57 -11
  164. package/dist/lib/view-types.d.ts +2 -2
  165. package/dist/lib/watchdog/log.d.ts +1 -1
  166. package/dist/lib/watchdog/log.js +1 -1
  167. package/dist/lib/watchdog/runner.d.ts +36 -45
  168. package/dist/lib/watchdog/runner.js +150 -119
  169. package/dist/lib/watchdog/service.d.ts +0 -1
  170. package/dist/lib/watchdog/service.js +0 -1
  171. package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
  172. package/dist/lib/watchdog/watchdog-agent.js +59 -0
  173. package/dist/lib/watchdog/watchdog.d.ts +11 -2
  174. package/dist/lib/watchdog/watchdog.js +37 -104
  175. package/package.json +2 -2
  176. package/dist/commands/usage.d.ts +0 -27
  177. package/dist/commands/usage.js +0 -96
@@ -19,8 +19,8 @@
19
19
  * floor and ceiling are both 5 minutes.
20
20
  * - **Hard hourly cap** (`HOURLY_CALL_CAP`) so a stuck "due" loop cannot
21
21
  * hammer an endpoint past ~12 calls/account/hour.
22
- * - **429 backoff.** A provider under `usageRateLimitedUntil` is skipped
23
- * entirely — no live fetch, no Touch ID, no re-armed penalty.
22
+ * - **429 backoff.** An account (or provider-wide scope) under
23
+ * `usageRateLimitedUntil` is skipped — no live fetch or re-armed penalty.
24
24
  * - **File-only credentials on the daemon path.** Refresh never opens the
25
25
  * ACL-bound macOS keychain item (Touch ID storm). It uses the no-ACL
26
26
  * access-token cache / setup-token / `.credentials.json` only
@@ -37,8 +37,8 @@
37
37
  * serializes read-modify-write; no lost updates.
38
38
  * 3. **macOS keychain ACL / Touch ID** — fileOnly refresh never calls
39
39
  * `security find-generic-password` on Claude's ACL item.
40
- * 4. **Provider 429** — whole provider skipped until Retry-After; cache
41
- * freezes (visible as aged `capturedAt`) rather than hammering.
40
+ * 4. **Account 429** — that account is skipped until Retry-After while its
41
+ * siblings continue; a provider-wide penalty still skips all accounts.
42
42
  * 5. **Expired access token (no refresh)** — usage path never rotates
43
43
  * single-use refresh tokens; counts as `failed`, reschedules 5m later.
44
44
  * 6. **No file credential on this host** — account skipped / failed; no
@@ -71,6 +71,12 @@ export const REFRESH_BURN_DIVISOR = 4;
71
71
  export const HOURLY_CALL_CAP = 12;
72
72
  /** How often the daemon wakes to *consider* a refresh pass (due accounts only). */
73
73
  export const USAGE_REFRESH_TICK_MS = 60 * 1000;
74
+ /** Consecutive failed live reads before one broken account is quarantined. */
75
+ export const FAILURE_QUARANTINE_THRESHOLD = 3;
76
+ /** A chronic offender waits this long while healthy siblings keep their cadence. */
77
+ export const FAILURE_QUARANTINE_MS = 30 * 60 * 1000;
78
+ const SKIP_JITTER_MIN_MS = 2_000;
79
+ const SKIP_JITTER_RANGE_MS = 3_001;
74
80
  const HOUR_MS = 60 * 60 * 1000;
75
81
  /** Test seam for the headroom cache path (see usage.ts `setClaudeUsageCachePathForTest`). */
76
82
  let headroomCachePathOverride = null;
@@ -169,8 +175,47 @@ export function nextHeadroomEntry(prev, snapshot, now) {
169
175
  nextRefreshAt: now + computeNextRefreshDelayMs(headroom.minutesToLimit),
170
176
  callTimestamps,
171
177
  computedAt: now,
178
+ consecutiveFailures: snapshot ? 0 : (prev?.consecutiveFailures ?? 0) + 1,
172
179
  };
173
180
  }
181
+ /** Stable per-account delay in [2s, 5s], used to spread skipped accounts. */
182
+ function skipJitterMs(usageKey) {
183
+ let hash = 0;
184
+ for (const char of usageKey)
185
+ hash = (hash * 31 + char.charCodeAt(0)) >>> 0;
186
+ return SKIP_JITTER_MIN_MS + (hash % SKIP_JITTER_RANGE_MS);
187
+ }
188
+ function skippedHeadroomEntry(prev, usageKey, now, index) {
189
+ return {
190
+ status: prev?.status ?? null,
191
+ minutesToLimit: prev?.minutesToLimit ?? null,
192
+ sessionUsedPercent: prev?.sessionUsedPercent ?? null,
193
+ capturedAt: prev?.capturedAt ?? null,
194
+ nextRefreshAt: now + (index + 1) * skipJitterMs(usageKey),
195
+ callTimestamps: pruneCallTimestamps(prev?.callTimestamps ?? [], now),
196
+ computedAt: now,
197
+ consecutiveFailures: prev?.consecutiveFailures ?? 0,
198
+ };
199
+ }
200
+ function failedHeadroomEntry(prev, now) {
201
+ const next = nextHeadroomEntry(prev, null, now);
202
+ if ((next.consecutiveFailures ?? 0) >= FAILURE_QUARANTINE_THRESHOLD) {
203
+ next.nextRefreshAt = now + FAILURE_QUARANTINE_MS;
204
+ }
205
+ return next;
206
+ }
207
+ /** Cold accounts lead each pass; both cold and cached groups rotate every tick. */
208
+ export function orderUsageAccounts(accounts, cache, tick) {
209
+ const rotate = (group) => {
210
+ if (group.length < 2)
211
+ return group;
212
+ const start = tick % group.length;
213
+ return [...group.slice(start), ...group.slice(0, start)];
214
+ };
215
+ const cold = accounts.filter((account) => cache[account.usageKey] == null);
216
+ const cached = accounts.filter((account) => cache[account.usageKey] != null);
217
+ return [...rotate(cold), ...rotate(cached)];
218
+ }
174
219
  /**
175
220
  * Enumerate the usage accounts whose credentials live on THIS host — one
176
221
  * per unique usage key, deduped to the most-recently-active version (the same
@@ -217,7 +262,7 @@ export async function buildLocalUsageAccounts() {
217
262
  }
218
263
  /**
219
264
  * One refresher tick: for each local account that is due, under its hourly cap,
220
- * and not provider-backed-off, live-fetch its usage, update the cache, and
265
+ * and not backed off, live-fetch its usage, update the cache, and
221
266
  * reschedule from the new burn projection. Never throws — a single account's
222
267
  * failed fetch leaves its cache untouched and counts as `failed`.
223
268
  */
@@ -230,14 +275,15 @@ export async function runUsageRefresh(deps) {
230
275
  skippedCap: 0,
231
276
  failed: 0,
232
277
  };
233
- const accounts = await deps.listAccounts();
234
278
  const cache = readHeadroomCache();
279
+ const accounts = orderUsageAccounts(await deps.listAccounts(), cache, Math.floor(now / USAGE_REFRESH_TICK_MS));
235
280
  const updates = {};
236
- for (const account of accounts) {
281
+ for (const [index, account] of accounts.entries()) {
237
282
  const entry = cache[account.usageKey] ?? null;
238
- // A provider under a 429 penalty is off-limits — poking it re-arms the
283
+ // A penalized account/provider is off-limits — poking it re-arms the
239
284
  // penalty (the whole reason usage-backoff exists).
240
- if ((deps.backoffUntil(account.agentId) ?? 0) > now) {
285
+ if ((deps.backoffUntil(account.agentId, account.usageKey) ?? 0) > now) {
286
+ updates[account.usageKey] = skippedHeadroomEntry(entry, account.usageKey, now, index);
241
287
  result.skippedBackoff += 1;
242
288
  continue;
243
289
  }
@@ -259,12 +305,12 @@ export async function runUsageRefresh(deps) {
259
305
  // No live snapshot (expired token / fetch miss): don't rewrite the usage
260
306
  // cache, but still record the call + reschedule so a broken account
261
307
  // isn't retried every tick.
262
- updates[account.usageKey] = nextHeadroomEntry(entry, null, now);
308
+ updates[account.usageKey] = failedHeadroomEntry(entry, now);
263
309
  result.failed += 1;
264
310
  }
265
311
  }
266
312
  catch {
267
- updates[account.usageKey] = nextHeadroomEntry(entry, null, now);
313
+ updates[account.usageKey] = failedHeadroomEntry(entry, now);
268
314
  result.failed += 1;
269
315
  }
270
316
  }
@@ -28,8 +28,8 @@ export interface ViewJsonVersion {
28
28
  resetsAt: string | null;
29
29
  }>;
30
30
  unavailable?: {
31
- reason: 'session_limit';
32
- resetsAt: string;
31
+ reason: 'session_limit' | 'out_of_credits';
32
+ resetsAt?: string;
33
33
  };
34
34
  lastActive: string | null;
35
35
  path: string;
@@ -1,7 +1,7 @@
1
1
  /** Same path the Fleet reader pulls (apps/ext/src/core/watchdogLog.ts). */
2
2
  export declare const WATCHDOG_LOG_PATH: string;
3
3
  /** Kinds the Factory reader accepts. Keep in lockstep with watchdogLog.ts. */
4
- export type WatchdogEventKind = 'tick' | 'decision' | 'nudge' | 'rotate' | 'error';
4
+ export type WatchdogEventKind = 'tick' | 'decision' | 'nudge' | 'undelivered' | 'rotate' | 'error';
5
5
  /** JSONL row shape — a verbatim replica of the Factory reader's WatchdogEvent. */
6
6
  export interface WatchdogEvent {
7
7
  ts: number;
@@ -18,7 +18,7 @@ import * as path from 'path';
18
18
  import { withFileLock, atomicWriteFileSync, ensureLockTarget } from '../fs-atomic.js';
19
19
  /** Same path the Fleet reader pulls (apps/ext/src/core/watchdogLog.ts). */
20
20
  export const WATCHDOG_LOG_PATH = path.join(os.homedir(), '.agents', '.cache', 'logs', 'watchdog.log');
21
- const WATCHDOG_EVENT_KINDS = new Set(['tick', 'decision', 'nudge', 'rotate', 'error']);
21
+ const WATCHDOG_EVENT_KINDS = new Set(['tick', 'decision', 'nudge', 'undelivered', 'rotate', 'error']);
22
22
  /** Parse the audit log without letting a partial final append hide valid rows. */
23
23
  export function parseWatchdogEvents(text) {
24
24
  const events = [];
@@ -3,19 +3,23 @@
3
3
  * working auto-nudge (RUSH-1415). The `agents watchdog` command drives it, so
4
4
  * the whole loop runs WITHOUT the Swift menu-bar.
5
5
  *
6
- * One tick, per session:
6
+ * One tick:
7
7
  *
8
8
  * getActiveSessions() (session/active.ts)
9
- * -> classifyTerminal(...) (watchdog/watchdog.ts) — stalled?
10
- * -> readWatchdogTail(...) (watchdog/read.ts) — the transcript tail
11
- * -> isLikelyTrulyBlocked(...) (watchdog/watchdog.ts) — promise-without-toolcall, NOT waiting-on-user
9
+ * -> classifyTerminal(...) per session (watchdog/watchdog.ts) — which are idle?
10
+ * -> readWatchdogTail(...) for the idle ones (watchdog/read.ts) — task + transcript tail
11
+ * -> the watchdog AGENT judges them ALL at once (watchdog/watchdog-agent.ts)
12
+ * — idle-but-unfinished -> nudge; idle-and-done / needs-human -> skip
12
13
  * -> resolveInjectTargetForSession(...) (terminal/resolve.ts) — THE safety gate: addressable or an honest refusal
13
- * -> injectIntoTerminal(target,text) (terminal/inject.ts) — deliver "Continue." into the EXACT split
14
+ * -> injectIntoTerminal(target,text) (terminal/inject.ts) — deliver into the EXACT split, then CONFIRM
14
15
  *
15
- * The safety gate is absolute: a nudge is delivered ONLY when the resolver
16
- * returns `addressable: true`. On `addressable: false` the reason is recorded to
17
- * a state file the menu-bar can surface later and the session is SKIPPED never
18
- * a guessed / frontmost target.
16
+ * There is no heuristic decider no regex over the tail guessing done-vs-stuck.
17
+ * The agent is the whole decider, given every idle session's task + tail in ONE
18
+ * `agents run --mode plan` call per tick. The safety gate is absolute: a nudge is
19
+ * delivered ONLY when the resolver returns `addressable: true`, and it is booked in
20
+ * the cooldown ledger ONLY when delivery is CONFIRMED. On `addressable: false` the
21
+ * reason is recorded to a state file the menu-bar can surface and the session is
22
+ * SKIPPED — never a guessed / frontmost target.
19
23
  *
20
24
  * Persistence (all under ~/.agents/.cache/state/watchdog/, tray-readable):
21
25
  * - nudges.json — { [sessionId]: lastNudgeMs } — enforces the cooldown.
@@ -25,15 +29,16 @@
25
29
  * - rotate/<sessionId>.json — the in-place rotate state machine (rotate.ts).
26
30
  * - rotate-skips.json — zero-healthy skip suppression: { [sessionId]: suppressUntilMs }.
27
31
  *
28
- * The pure logic (classifyTerminal / isLikelyTrulyBlocked) is imported and never
29
- * re-implemented; the runner only supplies its I/O (sessions, tails, clock,
30
- * policy, injection) — each an injectable seam so runner.test.ts drives real
31
- * synthetic sessions without a live terminal.
32
+ * The pure logic (classifyTerminal) is imported and never re-implemented; the
33
+ * runner only supplies its I/O (sessions, tails, clock, policy, the agent decider,
34
+ * injection) — each an injectable seam so runner.test.ts drives real synthetic
35
+ * sessions without a live terminal or a real `agents run`.
32
36
  */
33
37
  import type { ActiveSession } from '../session/active.js';
34
38
  import { type PresenceTransition } from '../session/presence.js';
35
39
  import { type InjectRail, type InjectResult, type InjectTarget } from '../terminal/index.js';
36
40
  import { type StallStatus, type WatchdogCandidate } from './watchdog.js';
41
+ import { type WatchdogAgentDecider } from './watchdog-agent.js';
37
42
  import { type OpenBlock } from '../feed/feed.js';
38
43
  import { type RotateGateResult, type RotatePhase, type RotateState } from './rotate.js';
39
44
  /** Per-session policy sentinel. `keep` is the default (watchdog may nudge). */
@@ -52,16 +57,7 @@ export interface WatchdogTickOptions {
52
57
  nudge?: boolean;
53
58
  /** Nudge text delivered into the terminal. Default "Continue." */
54
59
  nudgeText?: string;
55
- /**
56
- * Force the LLM decider (`agents run`) on EVERY stalled candidate instead of the
57
- * hybrid path. Default false. Even when false the tick still ESCALATES the
58
- * judgment-heavy cases (parked-on-question, ambiguous stalls) to the smart brain
59
- * — `smart: true` additionally routes the obvious promise-without-toolcall
60
- * nudges through it. Non-reproducible; the deterministic pre-filter is the
61
- * reproducible default.
62
- */
63
- smart?: boolean;
64
- /** Agent the smart decider runs as (and the built-in fallback prompt). Default 'claude'. */
60
+ /** Agent the watchdog decider runs as (and the built-in prompt). Default 'claude'. */
65
61
  smartAgent?: string;
66
62
  /** Threshold overrides. Missing fields fall back to DEFAULT_THRESHOLDS. */
67
63
  thresholds?: Partial<WatchdogThresholds>;
@@ -82,13 +78,20 @@ export interface WatchdogTickOptions {
82
78
  /** Per-session policy. Default = the on-disk sentinel. */
83
79
  policyFor?: (s: ActiveSession) => WatchdogPolicy;
84
80
  /**
85
- * The smart brain: given a stalled candidate, decide drive-forward vs
86
- * leave-for-human and craft the message. Default resolves a `watchdog` workflow
87
- * (repo/user override + `model:` frontmatter) and, failing that, the improved
88
- * built-in prompt both via `agents run --mode plan`. Tests inject a
89
- * synthetic decider so escalation is exercised without shelling out.
81
+ * The decider seam: given an idle candidate, decide nudge vs skip and craft the
82
+ * message. Production leaves this unset — the tick judges every idle session in
83
+ * ONE batched `agents run … --mode plan` call (watchdog-agent.ts). Tests inject a
84
+ * synthetic per-candidate decider so the decision path is exercised without
85
+ * shelling out.
90
86
  */
91
87
  smartDecider?: SmartDecider;
88
+ /**
89
+ * The batched agent decider used in production (all idle candidates → one call).
90
+ * Default `makeWatchdogAgentDecider`. Tests inject one (e.g. returning an empty
91
+ * map) to exercise the batched path and the no-verdict fallback without shelling
92
+ * out. Ignored when `smartDecider` (the per-candidate test seam) is set.
93
+ */
94
+ agentDecider?: WatchdogAgentDecider;
92
95
  /** Open feed block for a session (parked-on-question detection). Default reads the feed. */
93
96
  openBlockFor?: (s: ActiveSession) => OpenBlock | null;
94
97
  /** Inject primitive. Default injectIntoTerminal — tests capture the resolved target. */
@@ -206,31 +209,19 @@ export interface WatchdogTickResult {
206
209
  export declare function readPolicySentinel(dir: string, sessionId: string): WatchdogPolicy;
207
210
  /** Write a per-session policy sentinel (used by the CLI `agents watchdog policy`). */
208
211
  export declare function writePolicySentinel(dir: string, sessionId: string, policy: WatchdogPolicy): void;
209
- /** A decide result shared by the deterministic and smart paths. `text` overrides the default nudge text. */
212
+ /** A decision for one idle session. `text` overrides the default nudge text. */
210
213
  export interface NudgeDecision {
211
214
  nudge: boolean;
212
215
  reason: string;
213
216
  text?: string;
214
217
  /**
215
- * Set to `true` when the brain (smart decider) explicitly concluded the session
216
- * needs the human as opposed to a cheap deterministic skip (session is done,
217
- * not stalled, etc.). Only `true` when the brain escalation path ran and returned
218
- * `nudge: false`. The watchdog uses this to gate the self-file reminder: a
219
- * "session is done" skip does NOT trigger a reminder.
218
+ * On a skip, `true` = the agent judged the session genuinely needs the human
219
+ * (surface itself-file reminder / owner page); `false` / absent = idle-and-
220
+ * done, leave it alone. Gates the self-file reminder so a finished session is
221
+ * never poked.
220
222
  */
221
223
  needsHuman?: boolean;
222
224
  }
223
- /**
224
- * The smart brain. Resolves a `watchdog` workflow for the session's cwd
225
- * (project > user > system precedence, via resolveWorkflowRef) so a repo/user
226
- * override AND `model:` frontmatter come for free; when a workflow resolves it
227
- * runs `agents run watchdog --mode plan <prompt>`, else it falls back to the
228
- * improved built-in prompt via `agents run <agent> --mode plan <prompt>`. Plan
229
- * mode keeps the decider read-only. Best-effort and NON-deterministic: any
230
- * failure (decider unavailable, no verdict) returns a SAFE skip — a parked
231
- * question we cannot judge is left for the human, never blindly nudged.
232
- */
233
- export declare function makeDefaultSmartDecider(agent: string): SmartDecider;
234
225
  /**
235
226
  * Run ONE watchdog pass. Returns a structured outcome per live session; injects
236
227
  * only when `opts.nudge` is set AND the safety gate says addressable AND policy