@coda-console/grok-bot 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -42,7 +42,7 @@
42
42
  副作用调用由适配器向 coda MCP 的 status 取 turn 票据并生成 requestId,授权与通知复用 Coda 会话机制。`spawn` 必须指定目标 `project`,缺失直接拒绝(Bot 的空目录没有代码);开 Agent、给别的会话发消息由 Coda 的授权卡片批准。给自己开出的子会话补充是已批准任务的延续,不另问;`cancel` 只能停自己开出的子会话,由 Coda 校验归属,不另问。适配器不另发审批卡片,工具范围之外全部拒绝。
43
43
 
44
44
  `spawn`、`send`、`cancel` 的 requestId 由本轮 ID、工具名和规范化参数的 SHA-256 生成 UUID;对象键递归排序,数组顺序保留,参数顶层的 turn/requestId 不参与计算。同一轮内参数等价的重试复用同一 ID,由 Coda 恢复原调用;改参数或换轮次会得到新 ID。
45
- `spawn`、`send` 提交给 Coda 后,超过 20 秒仍未返回就先回复「已提交,正在等用户在 Coda 卡片上批准;结果会作为新消息送来;不要重试或轮询」。阈值为适配器的 `SUBMISSION_WAIT_MS` 常量。提前回执前以原 toolCallId 登记独立后台活动,原调用继续执行;实际返回或 MCP 断连时,活动报 handling,补交结果、终结原工具记录并报 done/failed。原轮次已结束时由 Coda 开自主轮次交回,失败会明确显示给用户,不自动重试。没有 waits 能力的客户端会继续等实际结果,不提前回执。收到回执后可 coda_done,等待批准的子会话不在 status.children 中不代表失败,不要改写同一任务重复派出。
45
+ `spawn`、`send` 提交给 Coda 后,超过 20 秒仍未返回就先回复「已提交,正在等用户在 Coda 卡片上批准;结果会作为新消息送来;不要重试或轮询」。阈值为适配器的 `SUBMISSION_WAIT_MS` 常量。提交调用时就在原轮次内以 toolCallId 登记独立后台活动;20 秒只控制提前回执。回执前取消或超时也不把仍在运行的工具标为失败,原调用继续执行;实际返回或 MCP 断连时,活动报 handling,补交结果、终结原工具记录并报 done/failed。原轮次已结束时由 Coda 开自主轮次交回,失败会明确显示给用户,不自动重试。没有 waits 能力的客户端会继续等实际结果,不提前回执。收到回执后可 coda_done,等待批准的子会话不在 status.children 中不代表失败,不要改写同一任务重复派出。
46
46
 
47
47
  ## 远端协议与调试
48
48
 
package/agent/session.mjs CHANGED
@@ -90,7 +90,8 @@ export class Adapter {
90
90
  finish(s, stopReason) {
91
91
  const turn = s.turn;
92
92
  if (!turn) return;
93
- for (const [toolCallId] of turn.tools) this.update(s, { sessionUpdate: 'tool_call_update', toolCallId, status: 'failed', rawOutput: '本轮已结束,远端操作结果未确认' });
93
+ for (const [toolCallId, call] of turn.tools) if (!call.background)
94
+ this.update(s, { sessionUpdate: 'tool_call_update', toolCallId, status: 'failed', rawOutput: '本轮已结束,远端操作结果未确认' });
94
95
  this.clock.clearTimeout(turn.timer);
95
96
  s.turn = null;
96
97
  turn.controller.abort();
@@ -185,10 +186,14 @@ export class Adapter {
185
186
  }
186
187
  async trace(s, name, args, operation, replyAfter) {
187
188
  const turn = s.turn, toolCallId = randomUUID();
189
+ const background = replyAfter !== undefined && !!this.waits;
188
190
  const toolCall = { toolCallId, title: `Coda · ${name}`, kind: 'other', status: 'pending', rawInput: args };
189
- if (turn) { turn.tools.set(toolCallId, true); this.update(s, { sessionUpdate: 'tool_call', ...toolCall }); }
191
+ if (turn) { turn.tools.set(toolCallId, { background }); this.update(s, { sessionUpdate: 'tool_call', ...toolCall }); }
190
192
  else this.log(JSON.stringify({ sessionId: s.id, toolCallId, name, arguments: args, state: 'started' }));
191
- let background = false;
193
+ // Register while the initiating turn is still active, before dispatch. The
194
+ // request survives cancellation even before its early acknowledgement.
195
+ if (background) this.rpc.notify('_coda/wait', { sessionId: s.id, waitId: toolCallId, kind: 'task', state: 'waiting',
196
+ title: `Coda · ${name} 等待结果`, toolCallId });
192
197
  const activity = state => this.rpc.notify('_coda/wait', { sessionId: s.id, waitId: toolCallId, state });
193
198
  const report = (status, rawOutput) => {
194
199
  if (background) {
@@ -196,7 +201,7 @@ export class Adapter {
196
201
  // activity's original cause. Do not reopen the Bot's expired turn.
197
202
  activity('handling');
198
203
  this.update(s, { sessionUpdate: 'tool_call_update', toolCallId, status, rawOutput });
199
- this.say(s, status === 'failed'
204
+ if (s.turn !== turn || !turn.tools.has(toolCallId)) this.say(s, status === 'failed'
200
205
  ? `Coda · ${name} 后台调用失败:${typeof rawOutput === 'string' ? rawOutput : JSON.stringify(rawOutput)}。请核实执行结果,不要自动重发。`
201
206
  : `Coda · ${name} 后台调用已返回,结果见该工具记录。`);
202
207
  activity(status === 'failed' ? 'failed' : 'done');
@@ -214,13 +219,10 @@ export class Adapter {
214
219
  } finally { turn?.tools.delete(toolCallId); }
215
220
  })();
216
221
  // Without activity support we cannot safely hand off a pending call.
217
- if (replyAfter === undefined || !this.waits) return completed;
222
+ if (!background) return completed;
218
223
  let timer;
219
224
  const submitted = new Promise(resolve => {
220
225
  timer = this.clock.setTimeout(() => {
221
- background = true;
222
- this.rpc.notify('_coda/wait', { sessionId: s.id, waitId: toolCallId, kind: 'task', state: 'waiting',
223
- title: `Coda · ${name} 等待结果`, toolCallId });
224
226
  turn.tools.delete(toolCallId);
225
227
  resolve({ content: [{ type: 'text', text: '已提交,正在等用户在 Coda 卡片上批准;结果会作为新消息送来;不要重试或轮询。没有其他工作就结束本轮。' }] });
226
228
  }, replyAfter);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "id": "grok-bot",
4
4
  "displayName": "Grok Bot",
5
5
  "description": "把 Grok 云端 Bot 接到 Coda 的长期会话:Webhook 消息、定时巡检与用户审批。",
6
- "version": "0.1.4",
6
+ "version": "0.1.6",
7
7
  "type": "module",
8
8
  "engines": { "node": ">=24" },
9
9
  "categories": ["Agent"],