@coda-console/claude 1.6.1 → 1.6.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.
package/agent/session.mjs CHANGED
@@ -42,6 +42,8 @@ const denied = { behavior: 'deny', message: 'Coda 没有在请求结束前给出
42
42
  // A child session's output that comes before its task, kept until the task names that session: all of it, since what streamed
43
43
  // counts as said, a message's streamed pieces as one.
44
44
  const CHUNKS = new Set(['agent_message_chunk', 'agent_thought_chunk']);
45
+ // What a turn says or does, as against what the session is (its settings, commands, title, usage).
46
+ const SAYING = new Set([...CHUNKS, 'tool_call', 'tool_call_update', 'plan']);
45
47
  // How many messages sent into a prompt (steer) may be waiting to be taken at once.
46
48
  const STEERING_LIMIT = 32;
47
49
 
@@ -109,6 +111,8 @@ export class ClaudeSession {
109
111
  else early.push(update);
110
112
  return;
111
113
  }
114
+ // What a turn of Claude's own says comes after what it handles (`claim`).
115
+ if (sessionId === this.sessionId && SAYING.has(update.sessionUpdate)) this.waits.claim();
112
116
  this.notify('session/update', { sessionId, update });
113
117
  }
114
118
  registered(sessionId) {
@@ -306,13 +310,14 @@ export class ClaudeSession {
306
310
  this.begin();
307
311
  // A subagent's request is in its session.
308
312
  const sessionId = this.waits.childSession(context.agentID) ?? this.sessionId;
313
+ if (sessionId === this.sessionId) this.waits.claim();
309
314
  return toolName === 'AskUserQuestion'
310
315
  ? this.ask('elicitation/create', questionForm({ sessionId, toolCallId: context.toolUseID, input }), signal, { behavior: 'deny', message: '问答已失效' })
311
316
  : this.ask('session/request_permission', permissionRequest({ sessionId, toolName, input, context }), signal, denied);
312
317
  }
313
318
  elicitation(request, { signal }) {
314
319
  if (!['starting', 'running'].includes(this.phase)) return Promise.resolve({ action: 'cancel' });
315
- this.begin();
320
+ this.begin(); this.waits.claim();
316
321
  return this.ask('elicitation/create', elicitationForm({ sessionId: this.sessionId, request }), signal, { action: 'cancel' });
317
322
  }
318
323
  // Asked of Coda until it answers; withdrawn (`$/cancel_request`) once the SDK no longer waits for the answer.
@@ -517,7 +522,7 @@ export class ClaudeSession {
517
522
  const outcome = this.ending ?? (failed ? { failure: error } : { stopReason: cancelled ? 'cancelled' : 'end_turn' });
518
523
  if (this.ending || !this.steering.size) this.settle(id, outcome);
519
524
  else { this.held = id; if (cancelled || failed) this.dropRest(outcome); }
520
- } else if (this.waits.handling) {
525
+ } else if (this.waits.handling || this.waits.unclaimed) {
521
526
  // A turn of Claude's own has no RPC reply to fail. End its own activity as failed with the reason, rather than
522
527
  // saying the error as an answer and marking the work complete. The task that woke it retains its own outcome.
523
528
  this.waits.finished(error ? { error: error.message, title: error.data?._meta?.coda?.failure?.kind === 'quota_exhausted' ? '额度不足' : undefined } : {});
package/agent/waits.mjs CHANGED
@@ -4,7 +4,8 @@
4
4
  // What a subagent launches (`parent_task_id`) is told in that subagent's session, once a wait has told Coda that session.
5
5
  // A task that ends while no turn goes will wake Claude for its result: until a turn takes it up, or the session says idle,
6
6
  // it is still waited on. A turn Claude begins on its own handles what woke it (`takeUp`): what its opening message names,
7
- // else the tasks that ended, else the one wakeup due, else a wait of the turn's own; what it handles ends with that turn.
7
+ // else the tasks that ended, else the one wakeup due, else a wait of the turn's own, told only once the turn says or asks
8
+ // something (`claim`); what it handles ends with that turn.
8
9
  const FINAL = { completed: 'done', failed: 'failed', killed: 'cancelled', stopped: 'cancelled', ended: 'done' };
9
10
  const open = state => state === 'waiting' || state === 'handling';
10
11
  const cut = (value, limit) => typeof value === 'string' && value ? value.slice(0, limit) : undefined;
@@ -143,18 +144,27 @@ export class ClaudeWaits {
143
144
  takeUp({ origin, text } = {}) {
144
145
  const pending = [...this.tasks.values()].filter(task => task.final !== undefined && task.state === 'waiting');
145
146
  const crons = [...this.crons.values()].filter(cron => open(cron.state));
146
- let handled = [];
147
- if (origin?.kind === 'task-notification') handled = origin.subkind === 'scheduled-trigger' ? crons.filter(cron => cron.prompt && cron.prompt === text)
148
- : [...this.tasks.values()].filter(task => origin.runId && task.runId === origin.runId && open(task.state));
149
- if (!handled.length) handled = pending.sort((a, b) => a.endedAt - b.endedAt);
150
- 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) {
151
158
  // Which came due is not told: the one due, or else the only one there is.
152
159
  const due = crons.filter(cron => cron.at !== null && cron.at <= this.now() + 60_000);
153
160
  if ((due.length || crons.length) === 1) handled = due.length ? due : crons;
154
161
  }
155
162
  if (handled.length) for (const wait of handled) this.report(wait, 'handling');
156
- else this.hold();
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
+ else this.unclaimed = true;
157
166
  }
167
+ claim() { if (this.unclaimed) { this.unclaimed = false; this.hold(); } }
158
168
  hold() {
159
169
  const own = { waitId: `turn-${++this.turns}` };
160
170
  this.own.add(own);
@@ -164,6 +174,7 @@ export class ClaudeWaits {
164
174
  finished({ error, title } = {}) {
165
175
  // Processing a successful task's result can fail. Give that failure its own activity instead of changing the task's
166
176
  // outcome, and leave recurring wakeups scheduled. There may already be a turn-owned activity (hold).
177
+ this.unclaimed = false;
167
178
  if (error && !this.own.size) this.hold();
168
179
  for (const task of this.tasks.values()) if (task.state === 'handling') { this.report(task, task.final ?? 'done'); task.final = undefined; }
169
180
  for (const cron of this.crons.values()) {
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.1",
6
+ "version": "1.6.3",
7
7
  "categories": ["Agent"],
8
8
  "dependencies": {
9
9
  "@agentclientprotocol/sdk": "1.4.0",