@agentchatham/cli 2.26.0 → 2.28.0

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/CLAUDE.md CHANGED
@@ -26,8 +26,11 @@ register: src/registration.ts — bindOrRegister: idempotent register-or-
26
26
  storage: src/agentConfig.ts — per-agent cli.json (read/writeAgentConfig: harness+mode); NO cli dep,
27
27
  so registration.ts reuses it without depending on cli/
28
28
  slash-cmds: src/commands/ — agent in-chat slash-commands (distinct from cli/commands.ts)
29
+ errors: src/errors/ — ErrorClass/TurnError vocabulary + formatting; no classification
29
30
  abstraction: src/provider.ts — ProviderAgent / ProviderAdapter interfaces
30
31
  adapters: src/providers/*/ — one per provider, implements ProviderAdapter
32
+ <name>/errors.ts classifies that harness's failures
33
+ src/providers/shared/ — prose + errno tables the classifiers fall back on
31
34
  loop: src/dispatcher.ts — buffer, retry, watermarks; private to /cli
32
35
  format: src/prompts.ts — formatters; input is ParsedNotification not raw events
33
36
  infra: src/lifecycle.ts — shutdown choreography
@@ -52,6 +55,83 @@ infra: src/lifecycle.ts — shutdown choreography
52
55
  4. Add case in `src/auth.ts` and `selectAdapter()` in `src/server.ts`
53
56
  5. Add provider SDK dep to `package.json` (exact pin)
54
57
  6. Add `test/providers/<name>/adapter.test.ts` using `fakeAgent`
58
+ 7. Create `src/providers/<name>/errors.ts` — export `create<Name>ErrorTracker()`,
59
+ wire it into the adapter, and add the harness to `test/errors/conformance.test.ts`
60
+
61
+ ## Error classification
62
+
63
+ Each harness classifies its own errors in `src/providers/<name>/errors.ts`, because
64
+ the four share no error types: Claude has typed stream events, Cursor typed error
65
+ subclasses, Codex and Gemini carry their cause as prose inside stream events.
66
+
67
+ `src/errors/` holds what is common and nothing else:
68
+
69
+ - `types.ts` — `ErrorClass` / `TurnError`, the vocabulary every classifier returns.
70
+ - `format.ts` — `makeTurnError` (the only constructor), `safeCode`, `withError`
71
+ (the one place a failure is logged), `formatChannelNotice`,
72
+ `toErrorInfo` (the wire projection).
73
+
74
+ It contains no branch that decides what an error is, and imports nothing from
75
+ `src/providers/`. `providers/shared/textPatterns.ts` holds the last-tier prose
76
+ table — used by Codex, Gemini and Cursor's fallback — and the errno mapping,
77
+ which is Node's vocabulary rather than any harness's and is used by all four.
78
+
79
+ A classifier collects signals across the turn and resolves once; precedence is the
80
+ `??` chain in its `resolve()`, most specific cause first. Rule ids are
81
+ `<harness>/<signal>/<variant>`, so a rule can be found and disabled by name.
82
+ `test/errors/conformance.test.ts` asserts all four harnesses map equivalent
83
+ failures onto the same `ErrorClass`.
84
+
85
+ Rules:
86
+
87
+ - `TurnError.message` is built from `class`, the `code` where it adds anything,
88
+ and the reset time. It reaches channels, and harness stderr carries tokens and
89
+ env vars, so no harness output goes into it. It carries no harness name
90
+ either: the channel has one agent, and log lines are already tagged with it.
91
+ - Prose matching is the last tier and is labelled `source: "text"`. A harness whose
92
+ `code` matches turn into `text` matches has changed a structured shape. Where a
93
+ harness exports its own error strings, import them rather than hardcoding.
94
+ - Warning-level signals are not errors. Claude's approaching-limit and
95
+ overage-transition notices never arrive as API errors.
96
+
97
+ ## Error policy
98
+
99
+ `TurnError.retryable` decides everything:
100
+
101
+ - **`false`** — announce once in the channel, drop the batch (advancing the
102
+ watermark), stay alive, and reset the retry budget rather than spending it.
103
+ Because the batch is dropped, the notice states that the message went
104
+ unanswered.
105
+ - **`true`** — re-enqueue, linear backoff, `onFatal` after `MAX_BATCH_RETRIES`. One
106
+ notice is posted just before exiting.
107
+
108
+ It means "does the dispatcher retry this automatically", not "could a retry ever
109
+ succeed": `usage_limit` is `false` because the notice carries the reset time and the
110
+ human re-sends. A harness may override it per error — Cursor reports its own
111
+ `isRetryable`, which may raise an error to retryable but never reopens
112
+ `usage_limit` or `billing` — the SDK marks every 429 retryable, including the
113
+ ones that mean the plan is exhausted.
114
+
115
+ Notices are deduped per (channel, class) on a cooldown; fatal ones are not. The
116
+ cooldown is a loop breaker, not just noise control: a notice is an ordinary
117
+ channel message, so it wakes every other agent in the channel, and two agents
118
+ failing the same way would answer each other's notices indefinitely.
119
+
120
+ A failure outside a turn is not reported in-channel: a phase that fails parks the
121
+ agent (`state: failed`) and `sendFailed` tells the backend, which is where that
122
+ state belongs. Only a classified turn failure reaches a channel.
123
+
124
+ A notice goes out through `sendAgentError`, which puts the sentence and
125
+ `toErrorInfo(error, {fatal})` on one text body (SDK `TurnErrorInfo`). The
126
+ console renders the structured half as a card; every older consumer — including
127
+ other agents in the channel — reads the sentence and is unaffected. The
128
+ projection drops `ruleId` and `source`: they say how we classified, which
129
+ belongs in logs, not in a channel. Keep the sentence self-sufficient; nothing
130
+ downstream may depend on the structured half being understood.
131
+
132
+ Process-level survival predicates (`isTransientNetworkError` and friends) stay in
133
+ `lifecycle.ts`. They answer "may the daemon survive this?", have no turn context,
134
+ and never map to an `ErrorClass`.
55
135
 
56
136
  ## The agent loop is private
57
137