@coda-console/claude 1.6.1 → 1.6.2

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;
@@ -146,6 +147,12 @@ export class ClaudeWaits {
146
147
  let handled = [];
147
148
  if (origin?.kind === 'task-notification') handled = origin.subkind === 'scheduled-trigger' ? crons.filter(cron => cron.prompt && cron.prompt === text)
148
149
  : [...this.tasks.values()].filter(task => origin.runId && task.runId === origin.runId && open(task.state));
150
+ // A message a subagent sent (SendMessage: its report handed back, or word of how it goes) is that subagent's. Taken up for
151
+ // what it said, not for its end (`messaged`): the subagent ends as its end is told, not with this turn.
152
+ if (origin?.kind === 'peer') {
153
+ handled = [...this.tasks.values()].filter(task => origin.senderTaskId && task.taskId === origin.senderTaskId && open(task.state));
154
+ for (const task of handled) task.messaged = true;
155
+ }
149
156
  if (!handled.length) handled = pending.sort((a, b) => a.endedAt - b.endedAt);
150
157
  if (!handled.length) {
151
158
  // Which came due is not told: the one due, or else the only one there is.
@@ -153,8 +160,11 @@ export class ClaudeWaits {
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 it names: told as a wait of its own once it says or asks something. One that says nothing is not told at all,
164
+ // as Claude Code's turn for the notice of a subagent whose report an earlier turn took in, 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,8 +174,14 @@ 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
- for (const task of this.tasks.values()) if (task.state === 'handling') { this.report(task, task.final ?? 'done'); task.final = undefined; }
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
+ }
169
185
  for (const cron of this.crons.values()) {
170
186
  if (cron.state !== 'handling') continue;
171
187
  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.1",
6
+ "version": "1.6.2",
7
7
  "categories": ["Agent"],
8
8
  "dependencies": {
9
9
  "@agentclientprotocol/sdk": "1.4.0",