@coda-console/claude 1.6.2 → 1.6.4
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/agent/errors.mjs +13 -3
- package/agent/session.mjs +2 -2
- package/agent/waits.mjs +14 -19
- package/package.json +1 -1
package/agent/errors.mjs
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { RequestError } from '@agentclientprotocol/sdk';
|
|
2
2
|
|
|
3
3
|
// Only failed turns come here. Claude Code's synthetic assistant error and the subscription's rejected window are
|
|
4
|
-
// evidence, ordinary assistant text and a bare HTTP 429 are not. Keep the provider's
|
|
4
|
+
// evidence, ordinary assistant text and a bare HTTP 429 are not. Keep the provider's recovery advice intact (its reset time
|
|
5
|
+
// said as a time mark: `marked`).
|
|
5
6
|
const exhausted = message => /^(?:You've (?:hit|reached) your (?:session|weekly|5-hour|7-day|usage) limit\b|(?:Your )?[Cc]redit balance is too low\b|You're out of (?:usage credits|extra usage)\b|Your org is out of usage\b)/.test(message);
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
// Claude Code says when the window that limits now resets in the time zone of the machine it runs on ("resets 5:10am
|
|
9
|
+
// (Australia/Sydney)"), not of whoever reads it. With that window's reset known (`window`: the latest rate_limit_info,
|
|
10
|
+
// which the CLI sends only as a window moves), the reason says it as a time mark instead, which each client writes in
|
|
11
|
+
// its own time zone (Coda's docs/agent-protocol.md「文字里的时刻」).
|
|
12
|
+
const windowReset = /^(You've (?:hit|reached) your [^·]+ · resets )[^·()]+ \([^()]+\)/;
|
|
13
|
+
const marked = (message, window) => window?.status === 'rejected' && Number.isSafeInteger(window.resetsAt) && window.resetsAt > 0
|
|
14
|
+
? message.replace(windowReset, (_, said) => `${said}<t:${new Date(window.resetsAt * 1000).toISOString()}>`) : message;
|
|
15
|
+
|
|
16
|
+
export function claudeFailure(message, { error, limits, window } = {}) {
|
|
8
17
|
const quota = (!error || ['rate_limit', 'billing_error'].includes(error)) &&
|
|
9
18
|
(exhausted(message) || (error === 'rate_limit' && limits?.status === 'rejected'));
|
|
10
|
-
|
|
19
|
+
const data = quota ? { _meta: { coda: { failure: { kind: 'quota_exhausted' } } } } : undefined;
|
|
20
|
+
return new RequestError(-32603, quota ? marked(message, window) : message, data);
|
|
11
21
|
}
|
package/agent/session.mjs
CHANGED
|
@@ -365,7 +365,7 @@ export class ClaudeSession {
|
|
|
365
365
|
case 'user': return this.toolResults(m, parent);
|
|
366
366
|
case 'result': return this.result(m);
|
|
367
367
|
case 'rate_limit_event':
|
|
368
|
-
this.turnLimits = m.rate_limit_info;
|
|
368
|
+
this.turnLimits = this.window = m.rate_limit_info;
|
|
369
369
|
if (this.env.CODA_PLUGIN_STATE) new ClaudeSessionLimits({ path: join(this.env.CODA_PLUGIN_STATE, 'limits.json') }).observe(m.rate_limit_info, this.account ?? null);
|
|
370
370
|
}
|
|
371
371
|
}
|
|
@@ -516,7 +516,7 @@ export class ClaudeSession {
|
|
|
516
516
|
const id = ids.find(id => this.prompts.has(id)) ?? (steered ? this.held : undefined);
|
|
517
517
|
const cancelled = interrupted(m), failed = !cancelled && (m.is_error || m.subtype !== 'success');
|
|
518
518
|
const error = failed ? claudeFailure(m.errors?.length || m.result ? errorText(m) : this.turnError?.message || errorText(m),
|
|
519
|
-
{ error: this.turnError?.error, limits: this.turnLimits }) : null;
|
|
519
|
+
{ error: this.turnError?.error, limits: this.turnLimits, window: this.window }) : null;
|
|
520
520
|
this.turnError = null; this.turnLimits = null;
|
|
521
521
|
if (id) {
|
|
522
522
|
const outcome = this.ending ?? (failed ? { failure: error } : { stopReason: cancelled ? 'cancelled' : 'end_turn' });
|
package/agent/waits.mjs
CHANGED
|
@@ -144,24 +144,24 @@ export class ClaudeWaits {
|
|
|
144
144
|
takeUp({ origin, text } = {}) {
|
|
145
145
|
const pending = [...this.tasks.values()].filter(task => task.final !== undefined && task.state === 'waiting');
|
|
146
146
|
const crons = [...this.crons.values()].filter(cron => open(cron.state));
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
: [...this.tasks.values()].filter(task => origin.runId && task.runId === origin.runId
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!
|
|
157
|
-
if (!handled.length) {
|
|
147
|
+
// What its opening message names: the wakeup that fired, the run a notice is of, the subagent a message is from.
|
|
148
|
+
const named = origin?.kind === 'task-notification' ? origin.subkind === 'scheduled-trigger' ? crons.filter(cron => cron.prompt && cron.prompt === text)
|
|
149
|
+
: [...this.tasks.values()].filter(task => origin.runId && task.runId === origin.runId)
|
|
150
|
+
: origin?.kind === 'peer' ? [...this.tasks.values()].filter(task => origin.senderTaskId && task.taskId === origin.senderTaskId) : [];
|
|
151
|
+
// Handled means ended (Coda places its card there and lets it be stopped no more): a subagent's message is the report it
|
|
152
|
+
// handed back only once it has ended; one still running says how it goes (SendMessage).
|
|
153
|
+
let handled = origin?.kind === 'peer' ? named.filter(task => pending.includes(task)) : named.filter(wait => open(wait.state));
|
|
154
|
+
// One that names nothing known is taken for what else ended, else the one wakeup due. One that names what was taken in
|
|
155
|
+
// already (Claude Code's turn for the notice of a subagent whose report an earlier turn read) or what still runs is not.
|
|
156
|
+
if (!named.length) handled = pending.sort((a, b) => a.endedAt - b.endedAt);
|
|
157
|
+
if (!named.length && !handled.length) {
|
|
158
158
|
// Which came due is not told: the one due, or else the only one there is.
|
|
159
159
|
const due = crons.filter(cron => cron.at !== null && cron.at <= this.now() + 60_000);
|
|
160
160
|
if ((due.length || crons.length) === 1) handled = due.length ? due : crons;
|
|
161
161
|
}
|
|
162
162
|
if (handled.length) for (const wait of handled) this.report(wait, 'handling');
|
|
163
|
-
// Nothing
|
|
164
|
-
// as
|
|
163
|
+
// Nothing to take up: told as a wait of its own once it says or asks something. One that says nothing is not told at
|
|
164
|
+
// all, as that turn for a notice, which reaches no model.
|
|
165
165
|
else this.unclaimed = true;
|
|
166
166
|
}
|
|
167
167
|
claim() { if (this.unclaimed) { this.unclaimed = false; this.hold(); } }
|
|
@@ -176,12 +176,7 @@ export class ClaudeWaits {
|
|
|
176
176
|
// outcome, and leave recurring wakeups scheduled. There may already be a turn-owned activity (hold).
|
|
177
177
|
this.unclaimed = false;
|
|
178
178
|
if (error && !this.own.size) this.hold();
|
|
179
|
-
for (const task of this.tasks.values()) {
|
|
180
|
-
if (task.state !== 'handling') continue;
|
|
181
|
-
const messaged = task.messaged; task.messaged = false;
|
|
182
|
-
if (messaged && task.final === undefined) { this.report(task, 'waiting'); continue; }
|
|
183
|
-
this.report(task, task.final ?? 'done'); task.final = undefined;
|
|
184
|
-
}
|
|
179
|
+
for (const task of this.tasks.values()) if (task.state === 'handling') { this.report(task, task.final ?? 'done'); task.final = undefined; }
|
|
185
180
|
for (const cron of this.crons.values()) {
|
|
186
181
|
if (cron.state !== 'handling') continue;
|
|
187
182
|
if (cron.final !== undefined || !cron.recurring) { this.report(cron, 'done'); continue; }
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "claude",
|
|
4
4
|
"displayName": "Claude",
|
|
5
5
|
"description": "Claude SDK Agent:Coda 基于 Claude Agent SDK 写的 ACP 入口,一个进程一个 SDK 会话;资源页的「Claude」面板显示会话报告的额度,可从 Coda 登录。",
|
|
6
|
-
"version": "1.6.
|
|
6
|
+
"version": "1.6.4",
|
|
7
7
|
"categories": ["Agent"],
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@agentclientprotocol/sdk": "1.4.0",
|