@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 +1 -1
- package/agent/session.mjs +10 -8
- package/package.json +1 -1
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
|
-
|
|
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
|
|
90
|
+
let saved = { bot: '', bound: false };
|
|
91
91
|
if (load) {
|
|
92
|
-
try {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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