@coda-console/grok-bot 0.1.1 → 0.1.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/README.md CHANGED
@@ -29,7 +29,7 @@
29
29
  | `grok-bot.telemetry` | 看板 HTTP(S) 基地址,无默认值;只允许 GET `/api/` 路径,不跟随重定向 |
30
30
  | `grok-bot.turnTimeout` | 默认 `PT30M`;从消息开始或自主轮次开始计时,包含 Webhook、审批与工具等待 |
31
31
 
32
- 会话绑定与完整会话指令存于 `CODA_PLUGIN_STATE`。初始化声明 `loadSession`、`sessionCapabilities.systemPrompt`、`_meta.coda.waits` 和 `_meta.coda.remote`。
32
+ 仅在绑定或选择、切换 Bot 时,将 Bot 选择与绑定状态写入 `CODA_PLUGIN_STATE`;创建和恢复会话不写文件,读取模型和配置项的探测不会留下状态文件。恢复没有状态文件的会话时按未绑定处理。会话指令只保留在内存,由 Coda 在 session/new、session/load 时提供,不落盘。初始化声明 `loadSession`、`sessionCapabilities.systemPrompt`、`_meta.coda.waits` 和 `_meta.coda.remote`。
33
33
  会话指令可经 `hello` 重复获取,且随每次 Webhook 的正文一起发送;响应丢失、重新绑定或切换 Bot 都不会清空指令。
34
34
  消息以 `{source:"coda", sessionId, turn, text}` POST 到 Routine,Bearer key 只送到配置的地址,禁止重定向。HTTP 200 只确认触发,`say` 输出、`done` 完成本轮。失败不自动重发,避免重复操作。
35
35
  取消会立即回 `cancelled`;超时说明原因后结束本轮。两者都不保证云端停止。旧 turn 被拒绝;Coda 发起的轮次必须带 turn,省略它不会混入新一轮。
package/agent/session.mjs CHANGED
@@ -32,11 +32,11 @@ export class Adapter {
32
32
  }
33
33
  file(id) {
34
34
  if (!this.env.CODA_PLUGIN_STATE) throw new Error('缺少 CODA_PLUGIN_STATE,无法保存 Bot 绑定');
35
- mkdirSync(this.env.CODA_PLUGIN_STATE, { recursive: true, mode: 0o700 });
36
35
  return join(this.env.CODA_PLUGIN_STATE, `${createHash('sha256').update(id).digest('hex')}.json`);
37
36
  }
38
37
  save(s) {
39
38
  const file = this.file(s.id), temp = `${file}.${randomUUID()}.tmp`;
39
+ mkdirSync(this.env.CODA_PLUGIN_STATE, { recursive: true, mode: 0o700 });
40
40
  writeFileSync(temp, JSON.stringify(s.saved), { mode: 0o600 });
41
41
  renameSync(temp, file);
42
42
  }
@@ -87,13 +87,16 @@ export class Adapter {
87
87
  const id = load ? params.sessionId : randomUUID();
88
88
  if (typeof id !== 'string' || !id) fail('缺少 sessionId');
89
89
  if (this.sessions.has(id)) fail('会话已经加载');
90
- let saved = { bot: '', bound: false, instructions: '' };
90
+ let saved = { bot: '', bound: false };
91
91
  if (load) {
92
- try { saved = JSON.parse(readFileSync(this.file(id), 'utf8')); }
92
+ try {
93
+ const { bot, bound } = JSON.parse(readFileSync(this.file(id), 'utf8'));
94
+ saved = { bot, bound };
95
+ }
93
96
  catch (error) { if (error.code !== 'ENOENT') throw error; }
94
97
  }
95
- if (systemPrompt) saved.instructions = systemPrompt.content.map(b => b.text).join('\n');
96
- const s = { id, saved, diagnosticsEnabled: mcpServers.some(server => server.name === 'diagnostics'), clients: [], routes: new Map(), unavailable: new Map(), turn: null };
98
+ const instructions = systemPrompt?.content.map(b => b.text).join('\n') ?? '';
99
+ const s = { id, saved, instructions, diagnosticsEnabled: mcpServers.some(server => server.name === 'diagnostics'), clients: [], routes: new Map(), unavailable: new Map(), turn: null };
97
100
  // Coda and the optional built-in diagnostics plugin are separate stdio MCP servers. Discover
98
101
  // both at load time; never treat a tool name returned by a server as authority
99
102
  // to expand the whitelist, or fall back to coda for absent diagnostics tools.
@@ -119,7 +122,6 @@ export class Adapter {
119
122
  if (s.routes.has(tool.name)) { s.routes.set(tool.name, null); continue; }
120
123
  s.routes.set(tool.name, catalog.client);
121
124
  }
122
- this.save(s);
123
125
  this.sessions.set(id, s);
124
126
  this.wait(s, 'waiting');
125
127
  return { ...(load ? {} : { sessionId: id }), configOptions: this.configs(s) };
@@ -143,7 +145,7 @@ export class Adapter {
143
145
  const key = readFileSync(keyPath, 'utf8').trim();
144
146
  if (!key) throw new Error('这个 Bot 的 Webhook key 为空');
145
147
  const text = prompt.map(b => b.text).join('\n');
146
- const instructions = s.saved.instructions;
148
+ const instructions = s.instructions;
147
149
  const response = await this.fetcher(url(bot.webhook), { method: 'POST', redirect: 'error', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
148
150
  body: JSON.stringify({ source: 'coda', sessionId: s.id, turn: turn.id,
149
151
  text: instructions ? `<coda-session-instructions>\n${instructions}\n</coda-session-instructions>\n\n${text}` : text }), signal: turn.controller.signal });
@@ -249,7 +251,7 @@ export class Adapter {
249
251
  this.save(s);
250
252
  // The HTTP response can be lost after this request succeeds. Keep the
251
253
  // instructions available to every bind (including a different Bot).
252
- return { sessionId: s.id, bot: s.saved.bot, instructions: s.saved.instructions };
254
+ return { sessionId: s.id, bot: s.saved.bot, instructions: s.instructions };
253
255
  }
254
256
  if (!s.saved.bound) fail('Bot 尚未完成绑定,请先运行 bind');
255
257
  if (p.method === 'say') {
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.1",
6
+ "version": "0.1.2",
7
7
  "type": "module",
8
8
  "engines": { "node": ">=24" },
9
9
  "categories": ["Agent"],