@coda-console/grok-bot 0.2.0 → 0.3.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/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  ## 安装与登录
6
6
 
7
+ 此插件仍在内部调试,清单限定 `coda.channels: ["preview"]`,只供预览版 Coda 后端安装和更新。以目标后端的通道为准,不是 Mac / iPhone 客户端的版本;正式版后端会显示限制原因。该约束需要支持通道校验的 Coda 后端,公开 npm 包及未声明限制的旧版本仍可被直接下载。
8
+
7
9
  1. 在 Coda 安装 Grok Bot,在空目录(例如 Bot)新建 Grok Bot 会话。
8
10
  2. 发送 `/login`,打开会话中的官方登录链接完成账号登录。手机可能停在 **Completing sign-in**,请再点 **Continue**。适配器会轮询并更新 Bot 列表。
9
11
  3. 在「这是哪个 Bot」按名称或 ID 选择。一个 Bot 对应一个 Coda 会话,避免重复镜像。
@@ -26,7 +28,9 @@ Coda 的 `systemPrompt` 接受格式校验,但不发送给 Grok、不保存,
26
28
 
27
29
  订阅正常到期、游标重置后静默重连;暂时失败只写日志并退避。连续 3 次失败才在会话提示一次,认证失效立即提示,恢复后提示一次。Routine 刷新采用相同策略。
28
30
 
29
- 主会话订阅用 `_coda/wait` 保持活动;自主运行用 handling/waiting 切换轮次。Coda 的未结束等待项会阻止 Agent 空闲回收;退出、断网或归档时不镜像,重新加载后按游标补齐。Grok 的 Routine 执行不依赖 Coda 在线。
31
+ 插件声明 `lifecycle: "external"`,主会话订阅和 Routine 用 `_coda/wait kind: "observation"` 保持观测,Coda 显示呼吸状态;自主运行用 handling/waiting 切换观测到的轮次。Coda 的未结束等待项会阻止 Agent 空闲回收;退出、断网或归档时不镜像,重新加载后按游标补齐。Grok 的 Routine 执行不依赖 Coda 在线。Coda 不根据这些轮次建立自动回复权,也不把巡检文字自动交给委派方。
32
+
33
+ 其他会话通过 `send` 委派时,消息带原请求编号和回传地址。Bot 按任务约定调用 `coda_send`,用 `replyTo` 指定原请求编号并发送结果;`final` 默认 `true`,`false` 用于约定的进展或需要对方处理的问题。回传会唤醒原会话,不再建立反向等待。升级插件后需同步远端的 `coda-bot.mjs` 与本技能,让 Bot 的工具声明包含回传参数。
30
34
 
31
35
  ## Coda 数据与授权工具(可选)
32
36
 
@@ -35,7 +35,7 @@ export class RoutineWatch {
35
35
  return this.flight;
36
36
  }
37
37
  activity(run, state, title) {
38
- this.notify({ waitId: `grok-routine:${this.bot}:${run.id}`, kind: 'task', state, title });
38
+ this.notify({ waitId: `grok-routine:${this.bot}:${run.id}`, kind: 'observation', state, title });
39
39
  }
40
40
  async refresh() {
41
41
  const signal = AbortSignal.any([this.controller.signal, AbortSignal.timeout(30_000)]);
package/agent/session.mjs CHANGED
@@ -67,7 +67,7 @@ export class Adapter {
67
67
  options: [{ value: '', name: '请选择 Bot(先发送 /login 登录)' }, ...this.bots.map(bot => ({ value: bot.agentId, name: `${bot.name || bot.agentId} (${bot.agentId})` })),
68
68
  ...(!this.bots.some(b => b.agentId === s.saved.bot) && s.saved.bot ? [{ value: s.saved.bot, name: s.saved.bot }] : [])] }];
69
69
  }
70
- wait(s, state) { if (this.waits) this.rpc.notify('_coda/wait', { sessionId: s.id, waitId: s.subscriptionId ?? 'grok-native', kind: 'task', title: 'Grok 主会话订阅', state }); }
70
+ wait(s, state) { if (this.waits) this.rpc.notify('_coda/wait', { sessionId: s.id, waitId: s.subscriptionId ?? 'grok-native', kind: 'observation', title: 'Grok 主会话观测', state }); }
71
71
  async refreshBots() {
72
72
  if (!this.auth.client) return;
73
73
  const result = await this.auth.client.request('ListGrokBotAgents', {});
@@ -466,7 +466,7 @@ export class Adapter {
466
466
  if (typeof ticket !== 'string' || !ticket || s.turn !== turn) fail('没有有效的 Coda turn 票据');
467
467
  forwarded = { ...args, turn: ticket, requestId: requestId(turn, name, args) };
468
468
  }
469
- const mayWaitForApproval = name === 'send';
469
+ const mayWaitForApproval = name === 'send' && args.replyTo === undefined;
470
470
  // Approval calls outlive a Coda turn and must not inherit its abort signal
471
471
  // or the MCP client's default 60-second timeout (null disables both).
472
472
  return this.trace(s, name, args, () => this.provider(s, name).call(name, forwarded, mayWaitForApproval ? null : signal),
package/package.json CHANGED
@@ -3,7 +3,10 @@
3
3
  "id": "grok-bot",
4
4
  "displayName": "Grok Bot",
5
5
  "description": "通过 Grok Bot 原生主会话接入 Coda,镜像回复与 Routine 运行记录。",
6
- "version": "0.2.0",
6
+ "version": "0.3.0",
7
+ "coda": {
8
+ "channels": ["preview"]
9
+ },
7
10
  "type": "module",
8
11
  "engines": {
9
12
  "node": ">=24"
@@ -29,6 +32,7 @@
29
32
  "agents": [
30
33
  {
31
34
  "id": "grok-bot",
35
+ "lifecycle": "external",
32
36
  "displayName": "Grok Bot",
33
37
  "icon": {
34
38
  "light": "icons/grok.pdf",
package/remote/SKILL.md CHANGED
@@ -43,3 +43,12 @@ coda_new_session 每次都在 Bot 会话里出允许/拒绝卡片,工具立
43
43
 
44
44
  把已报告问题的指纹、证据范围、相关会话/修复任务、最后状态按 sessionId 保存在 `/workspace/.coda-bot/memory/<sessionId>.json`。
45
45
  同一问题无新证据不重复开任务;先读已记录会话,核实修复、拒绝或失败状态后再决定下一步。
46
+
47
+ ## 接收委派与明确回传
48
+
49
+ 你的生命周期由 Grok 管理。Coda 观测到的巡检、定时运行和自主发言不会自动回复其他会话。
50
+ 收到其他会话的委派时,保留消息里的原请求编号与回传地址。仅在任务约定需要汇报,或需要对方处理问题时,调用 `coda_send`:
51
+ `childId` 是原发起会话地址,`replyTo` 是原请求编号,`prompt` 是回传正文。
52
+ `final` 默认 `true`,表示这次委派已完成;进展或待处理问题用 `final: false`。最终回传后不能继续向这次委派回传。
53
+ 回传已获原委派授权,不再等待反向审批,也不会让你等待对方的回复;它会主动唤醒对方,不用轮询。
54
+ 没有 `replyTo` 的 `coda_send` 仍是普通消息,按原来的审批规则处理。例行巡检没有需要回报的事情时,不调用回传工具。
@@ -35,7 +35,11 @@ export const TOOLS = [
35
35
  proxy('workflow_list', '列出项目的工作流程。', { project: string('项目名称或 ID') }),
36
36
  proxy('new_session', '像用户一样在目标目录开一个独立会话,每次要用户在 Coda 卡片上批准;不是你的子会话。立即返回请用户批准的回执,只会收到已开出/被拒绝/未能开出通知,工作结果不会推给你;记下会话引用,之后用 coda_read_thread 跟进,不要重复开同一任务。任务写清证据、重点代码路径、验收方式、按仓库惯例开 PR 并走完 PR 流程直到评审通过(含检查、交叉评审和自动评审评论)。GitHub 只读限制仅约束 Bot 自己,不要转述进任务或限制会话走 PR 流程。',
37
37
  { project: string('必填:目标目录名字或 ID;别的机器上的目录按 ID'), agent: string('Agent ID,默认选择 codex'), prompt: string('证据、重点代码路径、验收方式;按仓库惯例提交、开 PR 并走 PR 流程直到评审通过'), title: string('会话标题'), model: string('模型'), effort: string('推理强度'), mode: string('独立会话的审批档位,codex 默认选择 agent;用户在卡片上批准'), options: anyObject }, ['project', 'agent', 'prompt']),
38
- proxy('send', '给已有会话发消息,每次由 Coda 授权卡片批准。可能先返回「已提交」;不要重试或轮询。', { childId: string('目标会话 ID 或引用(沿用 Coda 参数名,不表示子会话)'), prompt: string('消息正文') }, ['childId', 'prompt']),
38
+ proxy('send', '给已有会话发消息,由 Coda 授权。明确回传委派结果时带 replyTo,直接唤醒原请求方,不建立反向等待。可能先返回「已提交」;不要轮询。', {
39
+ childId: string('目标会话 ID 或引用(沿用 Coda 参数名,不表示子会话);回传时用原委派附带的地址'), prompt: string('消息或回传正文'),
40
+ replyTo: string('原委派附带的请求编号,只能回传给该请求的发起方'),
41
+ final: { type: 'boolean', description: '仅回传时使用,默认 true 表示委派完成;约定的进展或需要对方处理的问题用 false' },
42
+ }, ['childId', 'prompt']),
39
43
  ];
40
44
 
41
45
  async function jsonPost(address, path, body, token, fetcher = fetch) {