diff --git a/assets/zcode-context/icon.png b/assets/zcode-context/icon.png new file mode 100644 index 00000000..f4eafe21 Binary files /dev/null and b/assets/zcode-context/icon.png differ diff --git a/external_plugins/zcode-context/.zcode-plugin/plugin.json b/external_plugins/zcode-context/.zcode-plugin/plugin.json index c659b92d..57b032d5 100644 --- a/external_plugins/zcode-context/.zcode-plugin/plugin.json +++ b/external_plugins/zcode-context/.zcode-plugin/plugin.json @@ -1,10 +1,10 @@ { "name": "zcode-context", - "version": "0.3.9", - "description": "只读查看会话用量、模型输入组成、请求差异和压缩事件。", + "version": "0.3.10", + "description": "只读查看会话用量、模型输入组成、请求差异和压缩事件。装好后点右上角「切换面板」,在会话侧边栏打开「上下文」面板;建议同时在「设置 → 通用」打开「完整保留模型 I/O」——默认记录会丢弃更早的请求,且无法事后恢复。", "description_i18n": { - "en": "Read-only session usage, model input composition, request differences and compaction events.", - "zh-CN": "只读查看会话用量、模型输入组成、请求差异和压缩事件。" + "en": "Read-only session usage, model input composition, request differences and compaction events. After installing, open the Context panel from the session sidebar (top-right panel switcher); also turn on full Model I/O retention in Settings → General — the default recording drops older requests and cannot recover them later.", + "zh-CN": "只读查看会话用量、模型输入组成、请求差异和压缩事件。装好后点右上角「切换面板」,在会话侧边栏打开「上下文」面板;建议同时在「设置 → 通用」打开「完整保留模型 I/O」——默认记录会丢弃更早的请求,且无法事后恢复。" }, "author": { "name": "Kevinzzz6" @@ -52,5 +52,6 @@ "availability": "session" } ] - } + }, + "homepage": "https://github.com/Kevinzzz6/zcode-context#默认记录与完整保留" } diff --git a/external_plugins/zcode-context/README.md b/external_plugins/zcode-context/README.md index 167f91e0..75b77c48 100644 --- a/external_plugins/zcode-context/README.md +++ b/external_plugins/zcode-context/README.md @@ -27,7 +27,15 @@ Clicking an event opens the exact item it brought into the context; a compaction ## Install -Install from the ZCode plugin marketplace, then enable **zcode-context**. A **Context** side pane becomes available for sessions in local workspaces. +Install from the ZCode plugin marketplace, then enable **zcode-context**. In a local workspace session, use the top-right panel switcher to open **Context** in the session sidebar. + +## Recording and retention + +For complete future request inputs, enable **full Model I/O retention** in **Settings → General**. Default recording keeps only a trailing portion of each request's messages plus later deltas, and older session record files are periodically pruned. Missing historical content cannot be recovered by enabling the setting later. + +The overview shows a reminder about this setting. Closing it saves the dismissal in local storage; the reminder does not detect whether full retention is already enabled. + +Full retention does not remove the plugin's own reading and cache limits. When first loading a record file, the plugin reads only its last 24 MiB and keeps a bounded request cache. A history-window notice can therefore remain even when newer requests are fully recorded; check each request's completeness indicator to distinguish incomplete input from unloaded history. ## Components @@ -55,6 +63,10 @@ The plugin reads ZCode CLI's local records only — the session database (`db/db Source lives in [`ui-plugins/zcode-context/`](../../ui-plugins/zcode-context). Build with `pnpm install` and `pnpm build` from the repository root; tests run with `node --test test/*.test.mjs`. Upstream project: . +## Acknowledgements + +UI and interaction design draws on [dsh-context](https://github.com/bowenliang123/dsh-context), reimplemented for ZCode. Thanks to its author for sharing the project. + ## License [MIT](./LICENSE) diff --git a/external_plugins/zcode-context/README_CN.md b/external_plugins/zcode-context/README_CN.md index 681de82b..c1973566 100644 --- a/external_plugins/zcode-context/README_CN.md +++ b/external_plugins/zcode-context/README_CN.md @@ -27,7 +27,15 @@ ## 安装 -在 ZCode 插件市场安装并启用 **zcode-context**,本地工作区的会话即可使用「上下文」侧栏面板。 +在 ZCode 插件市场安装并启用 **zcode-context**。进入本地工作区的会话后,点击右上角「切换面板」,在会话侧边栏打开「上下文」。 + +## 记录与保留 + +建议在「设置 → 通用」开启「完整保留模型 I/O」,让之后的请求完整记录输入。默认记录只保留每个请求末尾的一段消息及之后的增量,更早会话的记录文件也会定期清理。开启设置无法补回历史上没有记录或已经清理的内容。 + +概览上方会显示相关提醒。点击关闭后,关闭状态保存在本地存储中;这条提醒不会检测你是否已经开启完整保留。 + +完整保留不会解除插件自身的读取和缓存限制。首次加载记录文件时,插件只读取末尾 24 MiB,并仅缓存一定范围的请求。因此,即使新的请求已经完整记录,历史读取范围的提示仍可能存在;请结合每条请求的完整性标记,区分「请求输入没有记录全」和「更早的历史没有加载」。 ## 组件 @@ -55,6 +63,10 @@ 源码位于 [`ui-plugins/zcode-context/`](../../ui-plugins/zcode-context)。在仓库根目录执行 `pnpm install` 和 `pnpm build` 构建;测试用 `node --test test/*.test.mjs` 运行。上游项目:。 +## 致谢 + +界面与交互设计参考了 [dsh-context](https://github.com/bowenliang123/dsh-context),并针对 ZCode 重新实现。感谢原作者开源分享。 + ## 许可证 [MIT](./LICENSE) diff --git a/marketplace.json b/marketplace.json index 1ed4e2d7..0b896f66 100644 --- a/marketplace.json +++ b/marketplace.json @@ -1142,34 +1142,26 @@ { "name": "zcode-context", "source": "./external_plugins/zcode-context", - "description": "Read-only session usage, model input composition, request differences and compaction events.", + "description": "Read-only session usage, model input composition, request differences and compaction events. After installing, open the Context panel from the session sidebar (top-right panel switcher); also turn on full Model I/O retention in Settings → General — the default recording drops older requests and cannot recover them later.", "description_i18n": { - "en": "Read-only session usage, model input composition, request differences and compaction events.", - "zh-CN": "只读查看会话用量、模型输入组成、请求差异和压缩事件。" + "en": "Read-only session usage, model input composition, request differences and compaction events. After installing, open the Context panel from the session sidebar (top-right panel switcher); also turn on full Model I/O retention in Settings → General — the default recording drops older requests and cannot recover them later.", + "zh-CN": "只读查看会话用量、模型输入组成、请求差异和压缩事件。装好后点右上角「切换面板」,在会话侧边栏打开「上下文」面板;建议同时在「设置 → 通用」打开「完整保留模型 I/O」——默认记录会丢弃更早的请求,且无法事后恢复。" }, - "version": "0.3.9", + "homepage": "https://github.com/Kevinzzz6/zcode-context#默认记录与完整保留", + "version": "0.3.10", "author": { "name": "Kevinzzz6", "url": "https://github.com/Kevinzzz6" }, "category": "developer-tools", + "icon": "https://cdn-zcode.z.ai/zcode/official-plugin/assets/zcode-context/icon.png", "keywords": [ "context", "tokens", "usage", "session", "mcp" - ], - "examplePrompts": [ - "Show the context composition of this session and which category takes the most room", - "What did the last compaction summarize away?" - ], - "examplePrompts_i18n": { - "zh-CN": [ - "看看这个会话的上下文组成,哪一类内容占得最多", - "上一次压缩摘要掉了哪些内容?" - ] - } + ] }, { "name": "jupyter-notebook", diff --git a/ui-plugins/zcode-context/package.json b/ui-plugins/zcode-context/package.json index 9b0834f2..973e345c 100644 --- a/ui-plugins/zcode-context/package.json +++ b/ui-plugins/zcode-context/package.json @@ -1,6 +1,6 @@ { "name": "zcode-context", - "version": "0.3.9", + "version": "0.3.10", "private": true, "type": "module", "license": "MIT", diff --git a/ui-plugins/zcode-context/src/server.mjs b/ui-plugins/zcode-context/src/server.mjs index 320eb6ff..0b35a5aa 100644 --- a/ui-plugins/zcode-context/src/server.mjs +++ b/ui-plugins/zcode-context/src/server.mjs @@ -9,7 +9,7 @@ import { createReader } from "./data/reader.mjs"; const HTML_URI = "ui://zcode-context/panel.html"; const reader = createReader(() => new Worker(new URL("./worker.mjs", import.meta.url), { workerData: { paths: resolveDataPaths() }, resourceLimits: { maxOldGenerationSizeMb: 256 } })); export function createContextServer() { - const server = new McpServer({name:"zcode-context",version:"0.3.9"}); + const server = new McpServer({name:"zcode-context",version:"0.3.10"}); server.registerResource("context_panel", HTML_URI, { mimeType:"text/html;profile=mcp-app", _meta:{ui:{csp:{connectDomains:[],resourceDomains:[]}}} }, async () => ({contents:[{uri:HTML_URI,mimeType:"text/html;profile=mcp-app",text:await readFile(new URL("./context.html",import.meta.url),"utf8")}]}) diff --git a/ui-plugins/zcode-context/ui/app.mjs b/ui-plugins/zcode-context/ui/app.mjs index d149775b..37bbca13 100644 --- a/ui-plugins/zcode-context/ui/app.mjs +++ b/ui-plugins/zcode-context/ui/app.mjs @@ -4,7 +4,9 @@ import { translator, ORDER } from "./i18n.mjs"; import { renderApp, isAgent, CHUNK, layoutTrend, highlight, anchors, olderUnloaded, BOUNDARY, eventId, expandedRoles } from "./components.mjs"; import { demoCall } from "./demo.mjs"; const root=document.getElementById("root"); -const INFO={name:"zcode-context",version:"0.3.9"}; +const INFO={name:"zcode-context",version:"0.3.10"}; +const RETENTION_KEY="zcode-context:retention-dismissed"; +let retentionDismissed=false;try{retentionDismissed=localStorage.getItem(RETENTION_KEY)==="1";}catch{} const demo=new URLSearchParams(location.search).get("demo")==="1"; let app=null,t=translator(),view=initialView(),boundSession=null,overview=null,items=[],hasOlder=false,historyPage=0; let detail=null,content=null,more={},notice=null,busy=false,activeLoads=0; @@ -47,7 +49,7 @@ function render(background=false) { const y=window.scrollY,agents=items.filter(isAgent),index=agents.findIndex(x=>x.key===view.selectedKey); const selected=items.find(x=>x.key===view.selectedKey)??(detail?.summary?.key===view.selectedKey?detail.summary:null); const newerCount=index>=0?agents.length-1-index:selected?agents.filter(x=>x.startedAt>selected.startedAt).length:0; - root.replaceChildren(renderApp({t,view,lens:lens(view),version:INFO.version,overview,items,selected,detail,content,more,eventDetails,hasOlder,notice,busy,demo,latestKey:latestKey(),newerCount,session:boundSession,actions})); + root.replaceChildren(renderApp({t,view,lens:lens(view),version:INFO.version,overview,items,selected,detail,content,more,eventDetails,hasOlder,notice,busy,demo,latestKey:latestKey(),newerCount,session:boundSession,actions,retentionDismissed})); for(const el of root.querySelectorAll("[data-scroll]")) { const saved=scrolls.get(el.dataset.scroll);if(!saved)continue; el.scrollTop=saved[1];el.scrollLeft=keepRightEdge&&el.dataset.scroll==="trend"?el.scrollWidth-keepRightEdge:saved[0]; @@ -352,6 +354,7 @@ const actions={ view={...view,eventKinds:next.length&&next.lengtha.dismissRetention(),{class:"icon-btn",id:"retention-dismiss",title:t("dismiss"),ariaLabel:t("dismiss")}))); +} function toolbar(ctx) { const {t,view,items,actions:a}=ctx,bar=node("div",{class:"toolbar"}); @@ -187,7 +195,7 @@ function noticeBar(ctx) { } function summaryCard(ctx) { - const {t,view,selected:s,detail:d,actions:a,latestKey}=ctx; + const {t,view,selected:s,actions:a,latestKey}=ctx; if(!s)return null; const buckets=bucketsOf(s),sum=buckets.reduce((n,x)=>n+x.estimatedTokens,0)||1; const live=view.follow&&view.selectedKey===latestKey; @@ -603,9 +611,9 @@ function bodyText(ctx,c) { return el; } function bodyFoot(ctx,c) { - const {t,view,actions:a}=ctx,p=view.open,parts=Math.ceil(c.totalChars/CHUNK),part=Math.floor(c.offset/CHUNK)+1; + const {t,view,actions:a}=ctx,p=view.open,parts=Math.ceil(c.totalChars/CHUNK); const keys=e=>{ - if(e.key==="Enter"){e.preventDefault();e.shiftKey?a.prevMatch():a.nextMatch();} + if(e.key==="Enter"){e.preventDefault();if(e.shiftKey)a.prevMatch();else a.nextMatch();} else if(e.key==="Escape"&&e.target.value){e.preventDefault();e.target.value="";a.bodySearch("");} }; const position=p.matchIndex>=0&&c.matchCount?t("matchOf",{i:p.matchIndex+1,n:full(c.matchCount)}):t("matches",{n:full(c.matchCount)}); diff --git a/ui-plugins/zcode-context/ui/i18n.mjs b/ui-plugins/zcode-context/ui/i18n.mjs index 10778e02..def859fe 100644 --- a/ui-plugins/zcode-context/ui/i18n.mjs +++ b/ui-plugins/zcode-context/ui/i18n.mjs @@ -13,7 +13,9 @@ const messages={ unrecorded:["更早消息(未记录)","Earlier messages (not recorded)"], unrecordedTip:["实际输入减去已记录部分的估算;这部分内容没有被记录,无法查看或按类别拆分","Actual input minus the recorded estimate; this content was not recorded and cannot be viewed or split by category"], actualOnly:["记录不完整,只能显示实际输入的变化,无法按类别拆分","Partial record: only the change in actual input is known, not per category"], - partialHint:["这条记录只保留了最近 {n} 条消息(共 {total} 条),更早的内容无法查看,变化也无法按类别对比。在 ZCode「设置 → 通用」打开「完整保留模型 I/O」后,之后的请求会记录完整内容。","This record keeps only the latest {n} of {total} messages, so earlier content cannot be viewed or compared by category. Turn on complete Model I/O retention in ZCode Settings → General to record full input for later requests."],vsPrevious:["较上一步","vs previous"],notComparable:["无法对比","Not comparable"], + partialHint:["这条记录只保留了最近 {n} 条消息(共 {total} 条),更早的内容无法查看,变化也无法按类别对比。在 ZCode「设置 → 通用」打开「完整保留模型 I/O」后,之后的请求会记录完整内容。","This record keeps only the latest {n} of {total} messages, so earlier content cannot be viewed or compared by category. Turn on complete Model I/O retention in ZCode Settings → General to record full input for later requests."], + retentionBanner:["ZCode 默认只记录每个请求末尾的一段消息和之后的增量,更早的部分不会被记录;更早会话的记录文件也会定期清理,丢失的内容无法事后恢复。建议在「设置 → 通用」打开「完整保留模型 I/O」,之后的请求才会完整记录。","By default ZCode records only a trailing window of each request's messages plus later deltas, so earlier content is never recorded; older sessions' record files are also pruned and what is dropped cannot be recovered later. Consider turning on full Model I/O retention in Settings → General so later requests are recorded in full."], + dismiss:["知道了","Dismiss"],vsPrevious:["较上一步","vs previous"],notComparable:["无法对比","Not comparable"], actualIn:["实际输入","Actual in"],output:["输出","Output"],cache:["缓存","Cache"], briefTurn:["本轮","Turn"],briefInput:["输入","In"],briefReply:["回复","Reply"],noInputs:["(无新增)","(nothing new)"],moreItems:["还有 {n} 项","+{n} more"], briefTurnTip:["这一轮开场时用户发送的消息;同一轮的每一步都显示这一条","The message that opened this turn; every step of the turn shows it"], diff --git a/ui-plugins/zcode-context/ui/styles.css b/ui-plugins/zcode-context/ui/styles.css index c3c70e02..8218e7c6 100644 --- a/ui-plugins/zcode-context/ui/styles.css +++ b/ui-plugins/zcode-context/ui/styles.css @@ -29,7 +29,7 @@ h1,h2,h3{margin:0} .btn:hover:not(:disabled){background:var(--bg2)}.btn:disabled{opacity:.4} .btn.small{height:24px;padding:0 8px;font-size:11.5px} .btn.ghost{border-color:transparent;background:transparent;color:var(--muted)}.btn.ghost:hover:not(:disabled){color:var(--fg)} -.btn.accent{border-color:transparent;background:var(--accent);color:#fff;font-weight:600}.btn.accent:hover{background:var(--accent);filter:brightness(1.05)} +.btn.accent{border-color:transparent;background:var(--accent);color:var(--bg);font-weight:600}.btn.accent:hover{background:var(--accent);filter:brightness(1.05)} .btn.on{border-color:var(--accent);color:var(--accent)} .pinned,.notice{display:flex;align-items:center;gap:8px;padding:6px 6px 6px 10px;border-radius:9px;font-size:12px} @@ -127,6 +127,7 @@ html[lang^=zh] .brief-label{width:28px} .mini-select{height:24px;border:1px solid var(--border);border-radius:6px;background:var(--bg);font-size:11.5px;padding:0 4px;max-width:160px} .note{margin:0 0 8px;padding:6px 9px;border-radius:7px;background:var(--bg2);color:var(--muted);font-size:11.5px;display:flex;align-items:center;gap:8px;flex-wrap:wrap} .el-body .note{margin:0} +.retention>.note{margin:0;align-items:flex-start} .pad{margin:6px 10px;font-size:12px} .categories{display:flex;flex-direction:column;gap:6px} .category{border:1px solid var(--border);border-radius:9px;overflow:hidden}