Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ on:
paths:
- "website/**"
- ".github/workflows/pages.yml"
# the plugins page serves these as downloads (assembled at build time below)
- "server/assets/workbench-plugins/**"
- "docs/arch/examples/**"
workflow_dispatch:

permissions:
Expand All @@ -34,6 +37,17 @@ jobs:
- name: Configure Pages
uses: actions/configure-pages@v6

# plugins.html links to ./downloads/plugins/* — assembled here from the
# sources of truth (official set + docs examples), never duplicated in git.
- name: Assemble plugin downloads
run: |
mkdir -p website/downloads/plugins
cp server/assets/workbench-plugins/*.plugin.html website/downloads/plugins/
cp docs/arch/examples/*.plugin.html \
docs/arch/examples/*.template.json \
docs/arch/examples/cheers-plugin-sdk.js \
website/downloads/plugins/

- name: Upload site artifact
uses: actions/upload-pages-artifact@v3
with:
Expand Down
18 changes: 15 additions & 3 deletions docs/arch/CODEMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Devin 的 codemap 是 **agent 边探索边维护的代码库认知地图**,不
| 实时刷新(R4) | `filesTick` 只热刷内置 lens 和 Raw 编辑器;**沙箱插件不会重收 `cheers:render`**(协议 1 合并后复核过 `SandboxRenderer`,仍未接) | ⚠️ 缺口 G1 |
| 点节点跳代码(R5) | 三个半成品:① context chip 的 `{verb,params}` → `jumpToContextSource` 跳 Workbench/工作区;② 消息里反引号路径 → `resolveAndOpenRef` + 服务端 `resolveRef` 按来源 bot 跨 inbox/desk/workspace **启发式**解析(带存在性探测与清晰报错);③ `RemoteWorkspaceDialog` 的 `initialBotId/initialPath` 深链 | ⚠️ 缺口 G2:无**确定性文本定位符**、无**行号锚点**、未暴露给插件 |
| 大 repo(>256KB) | 单文件 ≤256KB;插件只能读被指派的那一个文件 | ⚠️ 缺口 G3(v2) |
| 节点发起提问(R6) | 插件无任何触达聊天输入的通道 | ⚠️ 缺口 G4(v2) |
| 节点发起提问(R6) | `cheers:compose` 预填输入框(**已实现**):空草稿填入/有草稿换行追加、`@名字` 注册为可路由提及、绝不代发 | ✅ G4 已落地 |

> G1 的定性随协议 1 变了:英文规范(PLUGIN_DEVELOPMENT.md §5.2 与 SDK 注释)
> 现在**明文规定** render 只有两个触发点(回应 `cheers:ready`、冲突保存之后),
Expand Down Expand Up @@ -317,6 +317,11 @@ pin 的 `codemap/conventions.md` 只剩发现层(一行):
# '@deng please ...'). NEVER guess it and never copy a
# handle from an example - a wrong handle breaks every
# jump. Unsure? Omit loc rather than invent one.
# The path part is relative to YOUR workspace root:
# VERIFY it exists (e.g. `ls <path>`) before writing it -
# a guessed path is a broken jump. Prefer a representative
# FILE plus #L<line> even for modules; a bare directory
# loc only opens the folder view.
# summary: what it does / what matters; <=200 chars; facts only
# status: explored | partial | stale
# tags: [optional, short]
Expand Down Expand Up @@ -350,6 +355,10 @@ pin 的 `codemap/conventions.md` 只剩发现层(一行):
prompt 并不告诉 bot 自己的 mention 名,它唯一可靠的来源是**触发消息里别人
@ 它的那个名字**——提示词明说这一点,并禁止猜测/照抄示例,不确定就不写 `loc`。
解析侧配套兜底:频道只有一个 bot 时,错误 handle 直接落到它(存在性探测照旧)。
- **verify-before-write**(实战教训二:目录 loc + 根基准漂移双杀):要求 bot 写
`loc` 前实际 `ls` 验证路径、优先"代表性文件 + 行号"而非裸目录——bot 有工具,
就该让它自证,这比任何解析侧纠偏都便宜。解析侧同时补齐:目录 locator 合法
(跳文件夹视图),文件接口 miss 后用目录列表二次确认(两种 connector 行为都兜住)。
- 全文 ~2.4KB,不到 256KB 预算的 1%,一次性成本。

## 6. 缺口与补丁(分阶段)
Expand Down Expand Up @@ -391,8 +400,11 @@ pin 的 `codemap/conventions.md` 只剩发现层(一行):
`codemap/map.yaml`(索引)+ `codemap/modules/<id>.yaml`,突破 256KB。
协议 1 明确「host 忽略未知 manifest 键」,`reads` 可以在协议 1 内增补而不 bump 版本。
host 在代理层按 glob 白名单放行 `fs.read`,仍然只读、仍然本频道。
4. **G4(对话入口)**:`cheers:compose { text }` —— 只**预填**聊天输入框,
绝不代发。点节点 →「问 bot:解释 gateway.resource」→ 用户自己按发送。
4. **G4(对话入口)——已实现**:`cheers:compose { text }` 只**预填**聊天输入框,
绝不代发:空草稿填入、已有草稿换行追加(用户文字永不丢)、文本里匹配成员的
`@名字` 注册进 picked(否则光有文本路由不到 bot——提及的真源是 picker 状态);
host 侧 ≤4000 字符 + 剥控制符。codemap 详情抽屉的「让 bot 讲讲这个模块」用它,
提及从节点 `loc` 的 handle 推导(写 loc 的 bot = 懂这块代码的 bot)。
发消息的主体始终是人,避开沙箱插件代用户说话的信任问题。
5. (可选)**行号漂移兜底**:locator 增加 `q=<pct-encoded 片段>`,查看器行号
未命中时按内容搜索定位。
Expand Down
1 change: 1 addition & 0 deletions docs/arch/RENDERER_PLUGIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
| plugin → host | `cheers:resource` | `{ reqId, resource, params }` | **host API**:读频道信息(见下白名单),`channel_id` 由 host 强制为当前频道 |
| host → plugin | `cheers:resource:result` | `{ reqId, ok, data\|error }` | 读取结果 |
| plugin → host | `cheers:open` | `{ uri }` | 请求把**用户的视图**导航到一个 `cheers:` 定位符:`cheers:ws/<bot>/<路径>#L<行>` 打开远程工作区并定位到行(先做存在性探测),`cheers:desk/<路径>` 聚焦工作台文件,`cheers:inbox/<file_id>` 打开频道文件。发出即忘、无回执;纯 UI 路由——host 严格解析,跳转背后的每次读取照常鉴权,解析不了给用户看清晰报错。不支持的 host 直接忽略(协议 1 的"忽略未知"生长规则),可以无条件发。 |
| plugin → host | `cheers:compose` | `{ text }` | **预填**聊天输入框——**绝不代发**。空草稿直接填入;已有草稿则换行追加(用户敲的字永不丢失);文本里匹配频道成员的 `@名字` 会注册为可路由的提及。人审阅、可改、亲手按发送——那一下按键才让插件的建议变成频道动作,副作用保持人在环、全程可审计。host 侧形状把关(≤4000 字符、剥控制符)。发出即忘;不支持的 host 忽略。 |

### 3.1 Host API:读频道信息

Expand Down
5 changes: 5 additions & 0 deletions docs/arch/examples/cheers-plugin-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ function cheersPlugin(opts) {
// desk file, attachment). Fire-and-forget; hosts without support ignore it.
parent.postMessage({ type: "cheers:open", uri: uri }, "*");
},
compose: function (text) {
// PREFILL the channel composer with a suggested message — never sends; the
// human reviews and presses send. Fire-and-forget; unsupported hosts ignore it.
parent.postMessage({ type: "cheers:compose", text: text }, "*");
},
};
parent.postMessage({ type: "cheers:ready" }, "*");
return api;
Expand Down
22 changes: 21 additions & 1 deletion docs/arch/examples/codemap.plugin.html
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@
"# '@deng please ...'). NEVER guess it and never copy a",
"# handle from an example - a wrong handle breaks every",
"# jump. Unsure? Omit loc rather than invent one.",
"# The path part is relative to YOUR workspace root:",
"# VERIFY it exists (e.g. `ls <path>`) before writing it -",
"# a guessed path is a broken jump. Prefer a representative",
"# FILE plus #L<line> even for modules; a bare directory",
"# loc only opens the folder view.",
"# summary: what it does / what matters; <=200 chars; facts only",
"# status: explored | partial | stale",
"# tags: [optional, short]",
Expand Down Expand Up @@ -612,6 +617,7 @@
function matches(n, s) {
if (!s) return true;
if (n.label.toLowerCase().includes(s) || (n.summary || "").toLowerCase().includes(s)) return true;
if (n.loc && String(n.loc).toLowerCase().includes(s)) return true; // 按源码路径搜模块
return (model.satsOf[n.id] || []).some(function (cid) { return model.nodes[cid].label.toLowerCase().includes(s); });
}
function render() {
Expand Down Expand Up @@ -937,7 +943,10 @@
return '<div class="edge-item" data-goto="' + esc(other.id) + '"><span class="dir">' + (out ? "→" : "←") + '</span>' +
'<span class="dot ' + dotCls(other.status) + '"></span><span>' + esc(other.label) + "</span>" +
'<span class="ek">' + (x.e.kind === "data" ? "数据" : "调用") + (x.e.label ? " · " + esc(x.e.label) : "") + "</span></div>";
}).join("") || '<div class="hint">无</div>') + "</div>";
}).join("") || '<div class="hint">无</div>') + "</div>" +

'<div class="d-sec"><button class="btn-soft" id="askBot" style="width:100%">💬 让 bot 讲讲这个模块</button>' +
'<div class="hint">预填聊天输入框(cheers:compose),不代发——检查后自己按发送</div></div>';
drawer.classList.add("open");

drawer.querySelector("#dClose").addEventListener("click", function () { drawer.classList.remove("open"); selected = null; render(); });
Expand All @@ -953,6 +962,17 @@
});
var copyBtn = drawer.querySelector("#copyLoc");
if (copyBtn) copyBtn.addEventListener("click", function () { copyText(String(n.loc)); });
drawer.querySelector("#askBot").addEventListener("click", function () {
// 提及从节点 loc 的 handle 推导(写 loc 的 bot = 懂这块代码的 bot);没有 loc 就
// 不带提及,让用户自己 @。预填不代发——发送权始终在人。
var hm = n.loc ? /^cheers:ws\/@([^/]+)\//.exec(String(n.loc)) : null;
var text = (hm ? "@" + hm[1] + " " : "") +
"请讲讲 " + n.label + "(codemap 节点 " + n.id + "):它负责什么、最近的改动、" +
"有什么要注意的?先读 codemap/map.yaml 里它的条目和注释再回答。";
if (DEMO) { toast("demo:将预填「" + esc(text.slice(0, 48)) + "…」"); return; }
parent.postMessage({ type: "cheers:compose", text: text }, "*");
toast("已预填到输入框——检查后按发送(需 host 支持 cheers:compose)");
});
drawer.querySelector("#saveSum").addEventListener("click", function () {
saveField(id, "summary", drawer.querySelector("#sumEdit").value.trim());
});
Expand Down
2 changes: 1 addition & 1 deletion docs/arch/examples/codemap.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"codemap/conventions.md"
],
"seed": {
"codemap/map.yaml": "# =================================================================\n# CODEMAP - an agent-maintained map of this repository.\n# Humans see this file rendered as an interactive graph in the\n# Workbench; you (the agent) are its maintainer. Keep it truthful.\n#\n# WHEN TO UPDATE (in the same turn as the work, not batched):\n# - explored new code -> add or refine the nodes you touched\n# - changed code -> update those summaries, or set\n# status: stale if you didn't re-verify\n# - starting work somewhere -> put those node ids in `focus:`,\n# and clear them when you move on\n#\n# SCHEMA (structure is the contract; comments are yours to use):\n# nodes: # a MAP keyed by node id - never a list\n# <id>: # dotted path: a.b.c is a child of a.b;\n# # segment chars [a-z0-9_-] only, so file\n# # names need '_' (fs.rs -> fs_rs), and the\n# # real name goes in `label`\n# kind: area | module | file | symbol\n# label: short human-readable name\n# loc: cheers:ws/@<your-handle>/<repo-relative-path>#L<n>[-L<n>]\n# <your-handle> = the EXACT name this channel @-mentions\n# you by (you see it in messages addressed to you, e.g.\n# '@deng please ...'). NEVER guess it and never copy a\n# handle from an example - a wrong handle breaks every\n# jump. Unsure? Omit loc rather than invent one.\n# summary: what it does / what matters; <=200 chars; facts only\n# status: explored | partial | stale\n# tags: [optional, short]\n# edges:\n# - { from: <id>, to: <id>, kind: calls|data, label: short }\n#\n# HOW TO EDIT:\n# - append NEW nodes at the end of the file (desk_append) -\n# `nodes:` is intentionally the last top-level key\n# - change EXISTING nodes with desk_edit, replacing single lines;\n# each node's unique ' <id>:' line is your anchor - never\n# rewrite the whole file\n# - comments survive every write path; leave margin notes freely\n# (e.g. '# TODO: auth branch unverified')\n# - granularity: areas + modules always; files only when they\n# matter; symbols only for hot paths. Keep the file under 256 KB\n# - unsure? prefer `status: partial` over invented detail\n# =================================================================\ncodemap: 1\nrepo: \"\" # owner/name - fill in on your first write\nupdated: \"\" # ISO-8601 - refresh on every write\nfocus: []\nedges: []\nnodes:\n # (empty - append your first node below, e.g.\n # gateway:\n # kind: area\n # label: Gateway\n # summary: ...\n # status: partial )\n\n",
"codemap/map.yaml": "# =================================================================\n# CODEMAP - an agent-maintained map of this repository.\n# Humans see this file rendered as an interactive graph in the\n# Workbench; you (the agent) are its maintainer. Keep it truthful.\n#\n# WHEN TO UPDATE (in the same turn as the work, not batched):\n# - explored new code -> add or refine the nodes you touched\n# - changed code -> update those summaries, or set\n# status: stale if you didn't re-verify\n# - starting work somewhere -> put those node ids in `focus:`,\n# and clear them when you move on\n#\n# SCHEMA (structure is the contract; comments are yours to use):\n# nodes: # a MAP keyed by node id - never a list\n# <id>: # dotted path: a.b.c is a child of a.b;\n# # segment chars [a-z0-9_-] only, so file\n# # names need '_' (fs.rs -> fs_rs), and the\n# # real name goes in `label`\n# kind: area | module | file | symbol\n# label: short human-readable name\n# loc: cheers:ws/@<your-handle>/<repo-relative-path>#L<n>[-L<n>]\n# <your-handle> = the EXACT name this channel @-mentions\n# you by (you see it in messages addressed to you, e.g.\n# '@deng please ...'). NEVER guess it and never copy a\n# handle from an example - a wrong handle breaks every\n# jump. Unsure? Omit loc rather than invent one.\n# The path part is relative to YOUR workspace root:\n# VERIFY it exists (e.g. `ls <path>`) before writing it -\n# a guessed path is a broken jump. Prefer a representative\n# FILE plus #L<line> even for modules; a bare directory\n# loc only opens the folder view.\n# summary: what it does / what matters; <=200 chars; facts only\n# status: explored | partial | stale\n# tags: [optional, short]\n# edges:\n# - { from: <id>, to: <id>, kind: calls|data, label: short }\n#\n# HOW TO EDIT:\n# - append NEW nodes at the end of the file (desk_append) -\n# `nodes:` is intentionally the last top-level key\n# - change EXISTING nodes with desk_edit, replacing single lines;\n# each node's unique ' <id>:' line is your anchor - never\n# rewrite the whole file\n# - comments survive every write path; leave margin notes freely\n# (e.g. '# TODO: auth branch unverified')\n# - granularity: areas + modules always; files only when they\n# matter; symbols only for hot paths. Keep the file under 256 KB\n# - unsure? prefer `status: partial` over invented detail\n# =================================================================\ncodemap: 1\nrepo: \"\" # owner/name - fill in on your first write\nupdated: \"\" # ISO-8601 - refresh on every write\nfocus: []\nedges: []\nnodes:\n # (empty - append your first node below, e.g.\n # gateway:\n # kind: area\n # label: Gateway\n # summary: ...\n # status: partial )\n\n",
"codemap/conventions.md": "Maintain codemap/map.yaml — read that file first; its header comments are the full instructions.\n"
}
}
Loading
Loading