@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 +80 -0
- package/dist/server.js +1 -1
- package/package.json +2 -2
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
|
|