dsh-tiddlywiki 0.26.2 → 0.26.5
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 +6 -0
- package/lib/client.js +1 -1
- package/lib/index.js +110 -20
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/settings-page.ts +5 -0
- package/src/host/seed-send-to-agent.ts +2 -2
- package/src/host/seed-starter-docs.ts +5 -5
- package/src/host/tools.ts +34 -3
- package/src/host/write-policy.ts +72 -2
- package/src/index.ts +1 -0
- package/src/sdk.ts +14 -2
package/lib/index.js
CHANGED
|
@@ -3002,12 +3002,12 @@ const STARTER_DOCS_ITEMS = [
|
|
|
3002
3002
|
|
|
3003
3003
|
「汇总页」不是 TW 的特殊功能,而是:**一个普通笔记 + 正文里一段筛选器(filter)**。渲染时 TW 实时从整个 wiki 挑出符合条件的笔记列出来。**新笔记只要打上对应标签,汇总页自动出现,零维护**——不用像手工目录那样每次手动更新。
|
|
3004
3004
|
|
|
3005
|
-
##
|
|
3005
|
+
## 方法一:零代码,点标签自带的下拉列表
|
|
3006
3006
|
|
|
3007
|
-
-
|
|
3008
|
-
-
|
|
3009
|
-
-
|
|
3010
|
-
-
|
|
3007
|
+
- 打开任意笔记,点正文底部的**标签丸**,或侧边栏「标签」(标签云)里点某个标签;
|
|
3008
|
+
- 内核会弹出一个**下拉列表**,列出所有带此标签的笔记(还能拖拽排序、直接跳转)。
|
|
3009
|
+
- 优点:一行代码都不用写;缺点:列表**全量不分页**(标签下笔记很多时会很长)、不能加说明文字、不能自定义排序/样式,也做不了「多主题合一」。
|
|
3010
|
+
- ⚠️ 注意:它只是个弹层,**不是**一个可以打开、可以分享链接的「标签页面」——直接打开以标签命名的条目,看到的只是那个条目本身(不存在则是缺失提示),**不会**自动列出成员笔记。想要「一个页面 + 可翻页」的标签列表,用方法二自己写汇总页(笔记数量多时给它套上分页)。
|
|
3011
3011
|
|
|
3012
3012
|
## 方法二:一页一主题(最常用)
|
|
3013
3013
|
|
|
@@ -3493,7 +3493,7 @@ const SEND_TO_AGENT_PLUGIN_TITLE = "$:/plugins/dsh/send-to-agent";
|
|
|
3493
3493
|
/** One-time marker: presence means "the button was offered once — hands off". */
|
|
3494
3494
|
const SEND_TO_AGENT_MARKER_TITLE = "$:/plugins/dsh-tiddlywiki/seed-send-to-agent";
|
|
3495
3495
|
/** The bundle's JSON text (`{"tiddlers": {...}}`), exactly as TW stores it. */
|
|
3496
|
-
const SEND_TO_AGENT_BUNDLE_TEXT = "{\n \"tiddlers\": {\n \"$:/plugins/dsh/send-to-agent/plugin.info\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/plugin.info\",\n \"type\": \"application/json\",\n \"text\": \"{\\\"title\\\":\\\"$:/plugins/dsh/send-to-agent\\\",\\\"name\\\":\\\"Send to Agent\\\",\\\"description\\\":\\\"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\\\",\\\"author\\\":\\\"dsh-tiddlywiki\\\",\\\"version\\\":\\\"0.3.5\\\",\\\"plugin-type\\\":\\\"plugin\\\"}\"\n },\n \"$:/plugins/dsh/send-to-agent/startup.js\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/startup.js\",\n \"type\": \"application/javascript\",\n \"module-type\": \"startup\",\n \"text\": \"/*\\\\\\ntitle: $:/plugins/dsh/send-to-agent/startup.js\\ntype: application/javascript\\nmodule-type: startup\\n\\n\\\\*/\\n(function(){\\n\\n/*jslint node: true, browser: true */\\n/*global $tw: false */\\n\\\"use strict\\\";\\n\\nexports.name = \\\"dsh-send-to-agent\\\";\\nexports.after = [\\\"story\\\"];\\nexports.platforms = [\\\"browser\\\"];\\n\\nfunction readConfig() {\\n\\tvar config = { enabled: true, endpoint: \\\"\\\", token: \\\"\\\" };\\n\\ttry {\\n\\t\\tvar t = $tw.wiki.getTiddler(\\\"$:/plugins/dsh-tiddlywiki/config\\\");\\n\\t\\tif (t && t.fields && typeof t.fields.text === \\\"string\\\") {\\n\\t\\t\\tvar parsed = JSON.parse(t.fields.text);\\n\\t\\t\\tvar s2a = parsed && parsed.ui && parsed.ui.sendToAgent;\\n\\t\\t\\tif (s2a) {\\n\\t\\t\\t\\tif (typeof s2a.enabled === \\\"boolean\\\") { config.enabled = s2a.enabled; }\\n\\t\\t\\t\\tif (typeof s2a.endpoint === \\\"string\\\" && s2a.endpoint.length > 0) { config.endpoint = s2a.endpoint; }\\n\\t\\t\\t\\tif (typeof s2a.token === \\\"string\\\" && s2a.token.length > 0) { config.token = s2a.token; }\\n\\t\\t\\t}\\n\\t\\t}\\n\\t} catch (e) {}\\n\\treturn config;\\n}\\n\\nfunction baseEndpoint() {\\n\\tvar config = readConfig();\\n\\tif (config.endpoint.length > 0) { return config.endpoint.replace(/\\\\/+$/, \\\"\\\"); }\\n\\tif (typeof location !== \\\"undefined\\\" && location.origin) { return location.origin + \\\"/dsh-tiddlywiki\\\"; }\\n\\treturn \\\"/dsh-tiddlywiki\\\";\\n}\\n\\n/*\\nShow a transient message.\\n\\nTW's notifier renders a TIDDLER by title and does **nothing at all** when that\\ntiddler does not exist (core/modules/utils/dom/notifier.js: \\\"Don't do anything\\nif the tiddler doesn't exist\\\"). Passing free text as the title — which is what\\nthis function used to do — therefore made every success/failure notice of the\\n「发送给 Agent」 button invisible: the user clicked, chose a session, and saw\\nnothing. Store the message in a $:/temp tiddler first (volatile: never synced,\\nnever written to disk) and display THAT title.\\n*/\\nvar NOTICE_TITLE = \\\"$:/temp/dsh/send-to-agent/notice\\\";\\nfunction notify(msg) {\\n\\ttry {\\n\\t\\tif ($tw.wiki && $tw.notifier && typeof $tw.notifier.display === \\\"function\\\" && typeof $tw.wiki.addTiddler === \\\"function\\\") {\\n\\t\\t\\t$tw.wiki.addTiddler({ title: NOTICE_TITLE, text: String(msg), type: \\\"text/vnd.tiddlywiki\\\" });\\n\\t\\t\\t$tw.notifier.display(NOTICE_TITLE);\\n\\t\\t\\treturn;\\n\\t\\t}\\n\\t} catch (e) {}\\n\\tif (typeof alert === \\\"function\\\") { alert(msg); }\\n}\\n\\nfunction doSend(payload, sessionId, note) {\\n\\tvar config = readConfig();\\n\\tvar lines = [];\\n\\tlines.push(\\\"《\\\" + payload.title + \\\"》\\\");\\n\\tlines.push(\\\"标签: \\\" + (payload.tags || []).join(\\\", \\\"));\\n\\tlines.push(\\\"类型: \\\" + (payload.type || \\\"无\\\"));\\n\\tlines.push(\\\"\\\");\\n\\tlines.push(\\\"【待办说明】以下内容是我(用户)提前编辑在 TiddlyWiki 知识库中的待办事项,通过「发送给 Agent」一键发送给你处理。请按内容执行;如有任何不清楚的地方,请主动向我提问,不要臆测或擅自发挥。\\\");\\n\\tlines.push(\\\"\\\");\\n\\tlines.push(payload.text || \\\"\\\");\\n\\t// 附加说明放在消息最后:正文之后、作为我(用户)的最终补充要求,优先遵循。\\n\\tif (note && String(note).trim().length > 0) {\\n\\t\\tlines.push(\\\"\\\");\\n\\t\\tlines.push(\\\"【附加说明】\\\" + String(note).trim());\\n\\t}\\n\\tvar text = lines.join(\\\"\\\\n\\\");\\n\\tvar headers = { \\\"Content-Type\\\": \\\"application/json\\\" };\\n\\tif (config.token.length > 0) { headers[\\\"x-send-to-agent-token\\\"] = config.token; }\\n\\t$tw.utils.httpRequest({\\n\\t\\turl: baseEndpoint() + \\\"/agent/send\\\",\\n\\t\\ttype: \\\"POST\\\",\\n\\t\\theaders: headers,\\n\\t\\tdata: JSON.stringify({ sessionId: sessionId, text: text }),\\n\\t\\tcallback: function(err, data) {\\n\\t\\t\\tvar parsed = null;\\n\\t\\t\\ttry { parsed = JSON.parse(data || \\\"\\\"); } catch (e) {}\\n\\t\\t\\tif (err) { notify(\\\"发送失败:\\\" + err); return; }\\n\\t\\t\\tif (parsed && parsed.ok) {\\n\\t\\t\\t\\tnotify(\\\"已发送 ✓ 会话 \\\" + sessionId.slice(0, 8));\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tnotify(\\\"发送失败:\\\" + ((parsed && parsed.error) || data || \\\"未知错误\\\"));\\n\\t\\t\\t}\\n\\t\\t}\\n\\t});\\n}\\n\\nfunction closeOverlay(overlay, escHandler) {\\n\\tif (escHandler) { document.removeEventListener(\\\"keydown\\\", escHandler); }\\n\\tif (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); }\\n}\\n\\nfunction showPicker(payload, items, modes, defaultId, permissions) {\\n\\tif (typeof document === \\\"undefined\\\") { return; }\\n\\t// remove any previously-open picker\\n\\tvar old = document.getElementById(\\\"dsh-send-picker\\\");\\n\\tif (old && old.parentNode) { old.parentNode.removeChild(old); }\\n\\n\\tvar state = { workspace: null, mode: \\\"\\\", note: \\\"\\\", permission: \\\"\\\" };\\n\\n\\tvar overlay = document.createElement(\\\"div\\\");\\n\\toverlay.id = \\\"dsh-send-picker\\\";\\n\\toverlay.setAttribute(\\\"style\\\", \\\"position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.35);z-index:99999;display:flex;align-items:center;justify-content:center;\\\");\\n\\tvar box = document.createElement(\\\"div\\\");\\n\\tbox.setAttribute(\\\"style\\\", \\\"background:#fff;color:#333;border-radius:8px;padding:14px;min-width:340px;max-width:560px;max-height:82vh;display:flex;flex-direction:column;box-shadow:0 8px 30px rgba(0,0,0,.3);font-family:system-ui,-apple-system,sans-serif;\\\");\\n\\n\\t// header\\n\\tvar header = document.createElement(\\\"div\\\");\\n\\theader.setAttribute(\\\"style\\\", \\\"display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;\\\");\\n\\tvar h = document.createElement(\\\"h3\\\");\\n\\th.setAttribute(\\\"style\\\", \\\"margin:0;font-size:15px;\\\");\\n\\th.textContent = \\\"发送给 Agent · 选择目标\\\";\\n\\tvar closeX = document.createElement(\\\"button\\\");\\n\\tcloseX.type = \\\"button\\\";\\n\\tcloseX.textContent = \\\"✕\\\";\\n\\tcloseX.title = \\\"关闭\\\";\\n\\tcloseX.setAttribute(\\\"style\\\", \\\"border:none;background:transparent;font-size:15px;cursor:pointer;color:#888;padding:2px 8px;border-radius:4px;\\\");\\n\\tcloseX.addEventListener(\\\"click\\\", function() { closeOverlay(overlay, escHandler); });\\n\\theader.appendChild(h);\\n\\theader.appendChild(closeX);\\n\\tbox.appendChild(header);\\n\\n\\t// description\\n\\tvar desc = document.createElement(\\\"p\\\");\\n\\tdesc.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;font-size:12px;color:#888;word-break:break-all;\\\");\\n\\tdesc.textContent = \\\"《\\\" + payload.title + \\\"》将作为消息注入所选会话\\\";\\n\\tbox.appendChild(desc);\\n\\n\\t// 附加说明(可选)— any extra context the user wants to attach to the\\n\\t// message, e.g. what to focus on or how to handle it.\\n\\tvar noteRow = document.createElement(\\\"div\\\");\\n\\tnoteRow.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;\\\");\\n\\tvar noteLbl = document.createElement(\\\"label\\\");\\n\\tnoteLbl.setAttribute(\\\"style\\\", \\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\");\\n\\tnoteLbl.textContent = \\\"附加说明(可选,随笔记一起发给 Agent)\\\";\\n\\tnoteRow.appendChild(noteLbl);\\n\\tvar noteTa = document.createElement(\\\"textarea\\\");\\n\\tnoteTa.placeholder = \\\"例如:请重点看第 3 条;我希望你按 XX 方式处理…\\\";\\n\\tnoteTa.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;min-height:44px;resize:vertical;font-family:inherit;\\\");\\n\\tnoteTa.addEventListener(\\\"input\\\", function() { state.note = noteTa.value; });\\n\\tnoteRow.appendChild(noteTa);\\n\\tbox.appendChild(noteRow);\\n\\n\\t// 工作模式(Agent 预设)selector — applies to newly created sessions; the\\n\\t// modes come from GET /agent/modes (id/name/description + deployment\\n\\t// default). When the modes endpoint is unreachable (e.g. an older host) the\\n\\t// row degrades to a hint and no mode is sent — DSH uses its default.\\n\\tvar modeRow = document.createElement(\\\"div\\\");\\n\\tmodeRow.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;\\\");\\n\\tif (modes && modes.length > 0) {\\n\\t\\tvar modeLbl = document.createElement(\\\"label\\\");\\n\\t\\tmodeLbl.setAttribute(\\\"style\\\", \\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\");\\n\\t\\tmodeLbl.textContent = \\\"工作模式(Agent 预设)— 用于新建会话\\\";\\n\\t\\tmodeRow.appendChild(modeLbl);\\n\\t\\tvar sel = document.createElement(\\\"select\\\");\\n\\t\\tsel.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\");\\n\\t\\tvar optDefault = document.createElement(\\\"option\\\");\\n\\t\\toptDefault.value = \\\"\\\";\\n\\t\\toptDefault.textContent = \\\"(默认模式)\\\";\\n\\t\\tsel.appendChild(optDefault);\\n\\t\\tmodes.forEach(function(m) {\\n\\t\\t\\tvar o = document.createElement(\\\"option\\\");\\n\\t\\t\\to.value = m.id || \\\"\\\";\\n\\t\\t\\to.textContent = (m.name || m.id) + (m.isDefault ? \\\"(默认)\\\" : \\\"\\\");\\n\\t\\t\\tsel.appendChild(o);\\n\\t\\t});\\n\\t\\t// preselect the deployment default when listed\\n\\t\\tif (defaultId) {\\n\\t\\t\\tfor (var i = 0; i < sel.options.length; i++) {\\n\\t\\t\\t\\tif (sel.options[i].value === defaultId) { sel.selectedIndex = i; break; }\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\tstate.mode = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].value : \\\"\\\";\\n\\t\\tsel.addEventListener(\\\"change\\\", function() { state.mode = sel.value; });\\n\\t\\tmodeRow.appendChild(sel);\\n\\t} else {\\n\\t\\tvar modeHint = document.createElement(\\\"p\\\");\\n\\t\\tmodeHint.setAttribute(\\\"style\\\", \\\"margin:0;font-size:12px;color:#999;\\\");\\n\\t\\tmodeHint.textContent = \\\"(未获取到可用工作模式,将使用 DSH 默认模式)\\\";\\n\\t\\tmodeRow.appendChild(modeHint);\\n\\t}\\n\\tbox.appendChild(modeRow);\\n\\n\\t// 权限(权限预设)selector — applies to newly created sessions only; the\\n\\t// options come from GET /agent/modes' `permissions` roster (each bundles a\\n\\t// sandbox mode + approval policy). Existing sessions keep their own\\n\\t// permission, so this only affects \\\"新建会话并发送\\\".\\n\\tvar permRow = document.createElement(\\\"div\\\");\\n\\tpermRow.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;\\\");\\n\\tif (permissions && Array.isArray(permissions.items) && permissions.items.length > 0) {\\n\\t\\tvar permLbl = document.createElement(\\\"label\\\");\\n\\t\\tpermLbl.setAttribute(\\\"style\\\", \\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\");\\n\\t\\tpermLbl.textContent = \\\"权限(权限预设)— 用于新建会话\\\";\\n\\t\\tpermRow.appendChild(permLbl);\\n\\t\\tvar psel = document.createElement(\\\"select\\\");\\n\\t\\tpsel.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\");\\n\\t\\tpermissions.items.forEach(function(p) {\\n\\t\\t\\tvar o = document.createElement(\\\"option\\\");\\n\\t\\t\\to.value = p.value || \\\"\\\";\\n\\t\\t\\to.textContent = p.name || p.value;\\n\\t\\t\\tif (p.description) { o.title = p.description; }\\n\\t\\t\\tpsel.appendChild(o);\\n\\t\\t});\\n\\t\\t// preselect the deployment default when listed\\n\\t\\tif (permissions.defaultId) {\\n\\t\\t\\tfor (var j = 0; j < psel.options.length; j++) {\\n\\t\\t\\t\\tif (psel.options[j].value === permissions.defaultId) { psel.selectedIndex = j; break; }\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\tstate.permission = psel.options[psel.selectedIndex] ? psel.options[psel.selectedIndex].value : \\\"\\\";\\n\\t\\tpsel.addEventListener(\\\"change\\\", function() { state.permission = psel.value; });\\n\\t\\tpermRow.appendChild(psel);\\n\\t} else {\\n\\t\\tvar permHint = document.createElement(\\\"p\\\");\\n\\t\\tpermHint.setAttribute(\\\"style\\\", \\\"margin:0;font-size:12px;color:#999;\\\");\\n\\t\\tpermHint.textContent = \\\"(未获取到权限预设,新建会话将使用 DSH 默认权限)\\\";\\n\\t\\tpermRow.appendChild(permHint);\\n\\t}\\n\\tbox.appendChild(permRow);\\n\\n\\t// scrollable body\\n\\tvar body = document.createElement(\\\"div\\\");\\n\\tbody.setAttribute(\\\"style\\\", \\\"overflow:auto;flex:1;min-height:0;\\\");\\n\\tbox.appendChild(body);\\n\\n\\t// footer with cancel\\n\\tvar footer = document.createElement(\\\"div\\\");\\n\\tfooter.setAttribute(\\\"style\\\", \\\"display:flex;justify-content:flex-end;gap:8px;margin-top:10px;\\\");\\n\\tvar cancel = document.createElement(\\\"button\\\");\\n\\tcancel.type = \\\"button\\\";\\n\\tcancel.textContent = \\\"取消\\\";\\n\\tcancel.setAttribute(\\\"style\\\", \\\"padding:6px 14px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:13px;\\\");\\n\\tcancel.addEventListener(\\\"click\\\", function() { closeOverlay(overlay, escHandler); });\\n\\tfooter.appendChild(cancel);\\n\\tbox.appendChild(footer);\\n\\n\\toverlay.appendChild(box);\\n\\tdocument.body.appendChild(overlay);\\n\\n\\t// backdrop click + Esc close\\n\\toverlay.addEventListener(\\\"click\\\", function(e) {\\n\\t\\tif (e.target === overlay) { closeOverlay(overlay, escHandler); }\\n\\t});\\n\\tvar escHandler = function(e) { if (e.key === \\\"Escape\\\") { closeOverlay(overlay, escHandler); } };\\n\\tdocument.addEventListener(\\\"keydown\\\", escHandler);\\n\\n\\t// group sessions by workspace (cwd)\\n\\tvar groups = {};\\n\\tvar order = [];\\n\\t(items || []).forEach(function(s) {\\n\\t\\tvar key = (s.cwd && String(s.cwd).length > 0) ? s.cwd : \\\"__default__\\\";\\n\\t\\tif (!groups[key]) { groups[key] = { cwd: s.cwd, sessions: [], max: 0 }; order.push(key); }\\n\\t\\tgroups[key].sessions.push(s);\\n\\t\\tif ((s.updatedAt || 0) > groups[key].max) { groups[key].max = s.updatedAt || 0; }\\n\\t});\\n\\torder.sort(function(a, b) { return groups[b].max - groups[a].max; });\\n\\n\\tfunction btnStyle() {\\n\\t\\treturn \\\"display:block;width:100%;text-align:left;padding:8px 10px;margin:4px 0;border:1px solid #ddd;border-radius:6px;background:#f7f7f7;cursor:pointer;font-size:13px;\\\";\\n\\t}\\n\\tfunction primaryBtnStyle() {\\n\\t\\treturn \\\"display:block;width:100%;text-align:center;padding:7px 10px;margin:6px 0 0;border:1px solid #4a90d9;border-radius:6px;background:#4a90d9;color:#fff;cursor:pointer;font-size:13px;\\\";\\n\\t}\\n\\tfunction smallBtnStyle() {\\n\\t\\treturn \\\"margin:0 0 6px;padding:4px 10px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#555;\\\";\\n\\t}\\n\\n\\tfunction shortSessionLabel(s) {\\n\\t\\tvar id = s.sessionId || \\\"\\\";\\n\\t\\tvar parts = id.split(\\\"-\\\");\\n\\t\\tvar short = (parts.length > 1 ? parts[parts.length - 1] : id).slice(-10);\\n\\t\\tvar when = \\\"\\\";\\n\\t\\tif (s.updatedAt) {\\n\\t\\t\\tvar diff = Date.now() - s.updatedAt;\\n\\t\\t\\tif (diff < 60000) { when = \\\"刚刚\\\"; }\\n\\t\\t\\telse if (diff < 3600000) { when = Math.floor(diff / 60000) + \\\" 分钟前\\\"; }\\n\\t\\t\\telse if (diff < 86400000) { when = Math.floor(diff / 3600000) + \\\" 小时前\\\"; }\\n\\t\\t\\telse {\\n\\t\\t\\t\\tvar d = new Date(s.updatedAt);\\n\\t\\t\\t\\twhen = (d.getMonth() + 1) + \\\"-\\\" + d.getDate() + \\\" \\\" + (\\\"0\\\" + d.getHours()).slice(-2) + \\\":\\\" + (\\\"0\\\" + d.getMinutes()).slice(-2);\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\treturn \\\"#\\\" + short + (when ? \\\" · \\\" + when : \\\"\\\") + (s.running ? \\\" · ●运行中\\\" : \\\"\\\") + (s.blank ? \\\" · (空)\\\" : \\\"\\\");\\n\\t}\\n\\n\\tfunction modeName(id) {\\n\\t\\tif (!id) { return \\\"\\\"; }\\n\\t\\tfor (var i = 0; i < (modes || []).length; i++) {\\n\\t\\t\\tif (modes[i].id === id) { return modes[i].name || modes[i].id; }\\n\\t\\t}\\n\\t\\treturn id;\\n\\t}\\n\\n\\tfunction render() {\\n\\t\\tbody.innerHTML = \\\"\\\";\\n\\t\\tif (state.workspace === null) { renderWorkspaces(); } else { renderSessions(state.workspace); }\\n\\t}\\n\\n\\tfunction renderWorkspaces() {\\n\\t\\tif (order.length === 0) {\\n\\t\\t\\tvar none = document.createElement(\\\"p\\\");\\n\\t\\t\\tnone.textContent = \\\"还没有任何会话——在下方新建一个吧\\\";\\n\\t\\t\\tnone.setAttribute(\\\"style\\\", \\\"color:#999;font-size:13px;margin:0 0 8px;\\\");\\n\\t\\t\\tbody.appendChild(none);\\n\\t\\t} else {\\n\\t\\t\\torder.forEach(function(key) {\\n\\t\\t\\t\\tvar g = groups[key];\\n\\t\\t\\t\\tvar label = g.cwd || \\\"(默认工作区)\\\";\\n\\t\\t\\t\\tvar b = document.createElement(\\\"button\\\");\\n\\t\\t\\t\\tb.type = \\\"button\\\";\\n\\t\\t\\t\\tb.setAttribute(\\\"style\\\", btnStyle());\\n\\t\\t\\t\\tb.textContent = \\\"📁 \\\" + label + \\\" · \\\" + g.sessions.length + \\\" 个会话\\\";\\n\\t\\t\\t\\tb.title = g.cwd || \\\"默认工作区\\\";\\n\\t\\t\\t\\tb.addEventListener(\\\"click\\\", function() { state.workspace = key; render(); });\\n\\t\\t\\t\\tbody.appendChild(b);\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t// new workspace / session\\n\\t\\tvar newRow = document.createElement(\\\"div\\\");\\n\\t\\tnewRow.setAttribute(\\\"style\\\", \\\"margin-top:10px;border-top:1px solid #eee;padding-top:8px;\\\");\\n\\t\\tvar lbl = document.createElement(\\\"p\\\");\\n\\t\\tlbl.textContent = \\\"新建工作区 / 会话\\\";\\n\\t\\tlbl.setAttribute(\\\"style\\\", \\\"margin:0 0 6px;font-size:12px;color:#666;\\\");\\n\\t\\tnewRow.appendChild(lbl);\\n\\t\\tvar input = document.createElement(\\\"input\\\");\\n\\t\\tinput.type = \\\"text\\\";\\n\\t\\tinput.placeholder = \\\"输入工作区路径,留空为默认工作区\\\";\\n\\t\\tinput.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;\\\");\\n\\t\\tnewRow.appendChild(input);\\n\\t\\tvar go = document.createElement(\\\"button\\\");\\n\\t\\tgo.type = \\\"button\\\";\\n\\t\\tgo.textContent = \\\"创建并发送\\\";\\n\\t\\tgo.setAttribute(\\\"style\\\", primaryBtnStyle());\\n\\t\\tgo.addEventListener(\\\"click\\\", function() { createAndSend(payload, input.value.trim()); });\\n\\t\\tnewRow.appendChild(go);\\n\\t\\tinput.addEventListener(\\\"keydown\\\", function(e) { if (e.key === \\\"Enter\\\") { createAndSend(payload, input.value.trim()); } });\\n\\t\\tbody.appendChild(newRow);\\n\\t}\\n\\n\\tfunction renderSessions(key) {\\n\\t\\tvar g = groups[key];\\n\\t\\tvar back = document.createElement(\\\"button\\\");\\n\\t\\tback.type = \\\"button\\\";\\n\\t\\tback.textContent = \\\"← 返回工作区列表\\\";\\n\\t\\tback.setAttribute(\\\"style\\\", smallBtnStyle());\\n\\t\\tback.addEventListener(\\\"click\\\", function() { state.workspace = null; render(); });\\n\\t\\tbody.appendChild(back);\\n\\n\\t\\tvar wsName = document.createElement(\\\"p\\\");\\n\\t\\twsName.textContent = g.cwd ? \\\"📁 \\\" + g.cwd : \\\"(默认工作区)\\\";\\n\\t\\twsName.setAttribute(\\\"style\\\", \\\"margin:0 0 6px;font-size:13px;font-weight:600;word-break:break-all;\\\");\\n\\t\\tbody.appendChild(wsName);\\n\\n\\t\\t(g.sessions || []).forEach(function(s) {\\n\\t\\t\\tvar b = document.createElement(\\\"button\\\");\\n\\t\\t\\tb.type = \\\"button\\\";\\n\\t\\t\\tb.setAttribute(\\\"style\\\", btnStyle());\\n\\t\\t\\tvar label = shortSessionLabel(s);\\n\\t\\t\\tif (s.agentPreset) { label += \\\" · 🧭 \\\" + modeName(s.agentPreset); }\\n\\t\\t\\tb.textContent = label;\\n\\t\\t\\tb.title = \\\"会话 \\\" + s.sessionId + (s.agentPreset ? \\\" · 工作模式 \\\" + modeName(s.agentPreset) : \\\"\\\");\\n\\t\\t\\tb.addEventListener(\\\"click\\\", function() { closeOverlay(overlay, escHandler); doSend(payload, s.sessionId, state.note); });\\n\\t\\t\\tbody.appendChild(b);\\n\\t\\t});\\n\\n\\t\\tvar newBtn = document.createElement(\\\"button\\\");\\n\\t\\tnewBtn.type = \\\"button\\\";\\n\\t\\tnewBtn.textContent = \\\"➕ 在此工作区新建会话并发送\\\";\\n\\t\\tnewBtn.setAttribute(\\\"style\\\", primaryBtnStyle());\\n\\t\\tnewBtn.addEventListener(\\\"click\\\", function() { createAndSend(payload, g.cwd); });\\n\\t\\tbody.appendChild(newBtn);\\n\\t}\\n\\n\\tfunction createAndSend(payload2, cwd) {\\n\\t\\tvar config = readConfig();\\n\\t\\tvar headers = { \\\"Content-Type\\\": \\\"application/json\\\" };\\n\\t\\tif (config.token.length > 0) { headers[\\\"x-send-to-agent-token\\\"] = config.token; }\\n\\t\\tvar body = { cwd: cwd || \\\"\\\" };\\n\\t\\tif (state.mode && String(state.mode).length > 0) { body.mode = state.mode; }\\n\\t\\tif (state.permission && String(state.permission).length > 0) { body.permission = state.permission; }\\n\\t\\t$tw.utils.httpRequest({\\n\\t\\t\\turl: baseEndpoint() + \\\"/agent/create\\\",\\n\\t\\t\\ttype: \\\"POST\\\",\\n\\t\\t\\theaders: headers,\\n\\t\\t\\tdata: JSON.stringify(body),\\n\\t\\t\\tcallback: function(err, data) {\\n\\t\\t\\t\\tvar parsed = null;\\n\\t\\t\\t\\ttry { parsed = JSON.parse(data || \\\"\\\"); } catch (e) {}\\n\\t\\t\\t\\tif (err) { notify(\\\"新建会话失败:\\\" + err); return; }\\n\\t\\t\\t\\tif (parsed && parsed.ok && parsed.sessionId) {\\n\\t\\t\\t\\t\\tcloseOverlay(overlay, escHandler);\\n\\t\\t\\t\\t\\tvar modeNote = parsed.mode ? \\\"(模式 \\\" + modeName(parsed.mode) + \\\")\\\" : \\\"\\\";\\n\\t\\t\\t\\t\\tvar permNote = parsed.permissionApplied ? \\\"(权限 \\\" + (parsed.permission || \\\"\\\") + \\\")\\\" : \\\"\\\";\\n\\t\\t\\t\\t\\tnotify(\\\"已创建会话并发送 ✓ \\\" + parsed.sessionId.slice(0, 8) + modeNote + permNote);\\n\\t\\t\\t\\t\\tdoSend(payload2, parsed.sessionId, state.note);\\n\\t\\t\\t\\t} else {\\n\\t\\t\\t\\t\\tnotify(\\\"新建会话失败:\\\" + ((parsed && parsed.error) || data || \\\"未知错误\\\"));\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t});\\n\\t}\\n\\n\\trender();\\n}\\n\\nfunction handleSend(title) {\\n\\tif (!title) { notify(\\\"无法确定当前笔记标题\\\"); return; }\\n\\tvar tiddler = $tw.wiki.getTiddler(title);\\n\\tif (!tiddler) { notify(\\\"找不到笔记:\\\" + title); return; }\\n\\tvar config = readConfig();\\n\\tif (!config.enabled) { notify(\\\"「发送给 Agent」特性未启用(可在 TW 配置中打开)\\\"); return; }\\n\\tvar payload = {\\n\\t\\ttitle: title,\\n\\t\\ttext: tiddler.fields.text || \\\"\\\",\\n\\t\\ttags: tiddler.fields.tags || [],\\n\\t\\ttype: tiddler.fields.type || \\\"\\\"\\n\\t};\\n\\tvar headers = {};\\n\\tif (config.token.length > 0) { headers[\\\"x-send-to-agent-token\\\"] = config.token; }\\n\\t$tw.utils.httpRequest({\\n\\t\\turl: baseEndpoint() + \\\"/agent/sessions\\\",\\n\\t\\ttype: \\\"GET\\\",\\n\\t\\theaders: headers,\\n\\t\\tcallback: function(err, data) {\\n\\t\\t\\tvar parsed = null;\\n\\t\\t\\ttry { parsed = JSON.parse(data || \\\"\\\"); } catch (e) {}\\n\\t\\t\\tif (err) { notify(\\\"获取会话列表失败:\\\" + err); return; }\\n\\t\\t\\tif (!parsed || !parsed.ok || !parsed.items) { notify(\\\"获取会话列表失败:\\\" + ((parsed && parsed.error) || \\\"未知错误\\\")); return; }\\n\\t\\t\\t// fetch the available 工作模式 (Agent presets) alongside; degrade to\\n\\t\\t\\t// an empty roster (default mode) when the endpoint is missing.\\n\\t\\t\\t$tw.utils.httpRequest({\\n\\t\\t\\t\\turl: baseEndpoint() + \\\"/agent/modes\\\",\\n\\t\\t\\t\\ttype: \\\"GET\\\",\\n\\t\\t\\t\\theaders: headers,\\n\\t\\t\\t\\tcallback: function(err2, data2) {\\n\\t\\t\\t\\t\\tvar modes = [];\\n\\t\\t\\t\\t\\tvar defaultId = \\\"\\\";\\n\\t\\t\\t\\t\\tvar permissions = null;\\n\\t\\t\\t\\t\\tvar parsed2 = null;\\n\\t\\t\\t\\t\\ttry { parsed2 = JSON.parse(data2 || \\\"\\\"); } catch (e) {}\\n\\t\\t\\t\\t\\tif (!err2 && parsed2 && parsed2.ok) {\\n\\t\\t\\t\\t\\t\\tif (Array.isArray(parsed2.items)) {\\n\\t\\t\\t\\t\\t\\t\\tmodes = parsed2.items;\\n\\t\\t\\t\\t\\t\\t\\tdefaultId = parsed2.defaultId || \\\"\\\";\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\tif (parsed2.permissions && Array.isArray(parsed2.permissions.items)) {\\n\\t\\t\\t\\t\\t\\t\\tpermissions = parsed2.permissions;\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\tshowPicker(payload, parsed.items, modes, defaultId, permissions);\\n\\t\\t\\t\\t}\\n\\t\\t\\t});\\n\\t\\t}\\n\\t});\\n}\\n\\nexports.startup = function() {\\n\\tconsole.log(\\\"[dsh-send-to-agent] startup ran\\\");\\n\\tif (!$tw.rootWidget || typeof $tw.rootWidget.addEventListener !== \\\"function\\\") { return; }\\n\\t$tw.rootWidget.addEventListener(\\\"dsh-send-to-agent\\\", function(event) {\\n\\t\\thandleSend(event.param);\\n\\t});\\n};\\n\\n})();\\n\"\n },\n \"$:/plugins/dsh/send-to-agent/ui/icon\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/ui/icon\",\n \"tags\": [\n \"$:/tags/Image\"\n ],\n \"text\": \"\\\\parameters (size:\\\"22pt\\\")\\n<svg width=<<size>> height=<<size>> class=\\\"tc-image-send-to-agent tc-image-button\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z\\\"/></svg>\\n\"\n },\n \"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\",\n \"type\": \"text/vnd.tiddlywiki\",\n \"tags\": [\n \"$:/tags/ViewToolbar\"\n ],\n \"icon\": \"$:/plugins/dsh/send-to-agent/ui/icon\",\n \"caption\": \"发送给 Agent\",\n \"description\": \"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\",\n \"text\": \"\\\\whitespace trim\\n<$button message=\\\"dsh-send-to-agent\\\"\\n\\tparam=<<currentTiddler>>\\n\\ttooltip=\\\"把当前笔记一键发送给 DSH Agent\\\"\\n\\taria-label=\\\"把当前笔记一键发送给 DSH Agent\\\"\\n\\tclass=<<tv-config-toolbar-class>>\\n>\\n\\t<%if [<tv-config-toolbar-icons>match[yes]] %>\\n\\t\\t{{$:/plugins/dsh/send-to-agent/ui/icon}}\\n\\t<%endif%>\\n\\t<%if [<tv-config-toolbar-text>match[yes]] %>\\n\\t\\t<span class=\\\"tc-btn-text\\\">\\n\\t\\t<$text text=\\\"发送给 Agent\\\"/>\\n\\t</span>\\n\\t<%endif%>\\n</$button>\\n\"\n },\n \"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\": {\n \"title\": \"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\",\n \"type\": \"text/vnd.tiddlywiki\",\n \"text\": \"\\\\define config-title()\\n$(config-base)$$(currentTiddler)$\\n\\\\end\\n\\\\whitespace trim\\n\\n<$draggable tiddler=<<currentTiddler>>>\\n<$checkbox tiddler=<<config-title>> field=\\\"text\\\" checked=\\\"show\\\" unchecked=\\\"hide\\\" default=\\\"show\\\"/>\\n \\n<span class=\\\"tc-icon-wrapper\\\"><$transclude tiddler={{!!icon}}/></span>\\n \\n<$transclude field=\\\"caption\\\"/>\\n -- \\n<i class=\\\"tc-muted\\\"><$transclude field=\\\"description\\\"/></i>\\n</$draggable>\\n\"\n }\n }\n}";
|
|
3496
|
+
const SEND_TO_AGENT_BUNDLE_TEXT = "{\n \"tiddlers\": {\n \"$:/plugins/dsh/send-to-agent/plugin.info\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/plugin.info\",\n \"type\": \"application/json\",\n \"text\": \"{\\\"title\\\":\\\"$:/plugins/dsh/send-to-agent\\\",\\\"name\\\":\\\"Send to Agent\\\",\\\"description\\\":\\\"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\\\",\\\"author\\\":\\\"dsh-tiddlywiki\\\",\\\"version\\\":\\\"0.3.6\\\",\\\"plugin-type\\\":\\\"plugin\\\"}\"\n },\n \"$:/plugins/dsh/send-to-agent/startup.js\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/startup.js\",\n \"type\": \"application/javascript\",\n \"module-type\": \"startup\",\n \"text\": \"/*\\\\\\ntitle: $:/plugins/dsh/send-to-agent/startup.js\\ntype: application/javascript\\nmodule-type: startup\\n\\n\\\\*/\\n(function(){\\n\\n/*jslint node: true, browser: true */\\n/*global $tw: false */\\n\\\"use strict\\\";\\n\\nexports.name = \\\"dsh-send-to-agent\\\";\\nexports.after = [\\\"story\\\"];\\nexports.platforms = [\\\"browser\\\"];\\n\\nfunction readConfig() {\\n\\tvar config = { enabled: true, endpoint: \\\"\\\", token: \\\"\\\" };\\n\\ttry {\\n\\t\\tvar t = $tw.wiki.getTiddler(\\\"$:/plugins/dsh-tiddlywiki/config\\\");\\n\\t\\tif (t && t.fields && typeof t.fields.text === \\\"string\\\") {\\n\\t\\t\\tvar parsed = JSON.parse(t.fields.text);\\n\\t\\t\\tvar s2a = parsed && parsed.ui && parsed.ui.sendToAgent;\\n\\t\\t\\tif (s2a) {\\n\\t\\t\\t\\tif (typeof s2a.enabled === \\\"boolean\\\") { config.enabled = s2a.enabled; }\\n\\t\\t\\t\\tif (typeof s2a.endpoint === \\\"string\\\" && s2a.endpoint.length > 0) { config.endpoint = s2a.endpoint; }\\n\\t\\t\\t\\tif (typeof s2a.token === \\\"string\\\" && s2a.token.length > 0) { config.token = s2a.token; }\\n\\t\\t\\t}\\n\\t\\t}\\n\\t} catch (e) {}\\n\\treturn config;\\n}\\n\\n/*\\nA configured endpoint is only useful when it points at the DSH server root\\n(/dsh-tiddlywiki). Browser autofill sometimes stuffs a URL into the \\\"TW 端请求\\n基址\\\" box (实测 /dsh-tiddlywiki/tw/root), which would make every request land\\non the TW proxy and 404. Anything that already contains one of the TW proxy /\\nsub-route segments (or isn't an origin or a root-absolute path) is clearly\\nwrong → fall back to the automatic derivation so the button keeps working.\\n*/\\nfunction isLikelyBadEndpoint(e) {\\n\\tif (typeof e !== \\\"string\\\" || e.trim().length === 0) { return true; }\\n\\tif (/\\\\/tw\\\\//.test(e) || /\\\\/api\\\\//.test(e) || /\\\\/recipes\\\\//.test(e) || /\\\\/files\\\\//.test(e)) { return true; }\\n\\tif (/^(https?:)?\\\\/\\\\//.test(e)) { return false; }\\n\\t// Root-absolute like /dsh-tiddlywiki is fine; anything else (relative,\\n\\t// bare \\\"dsh-tiddlywiki\\\", a stray token) is not an endpoint.\\n\\treturn e.charAt(0) !== \\\"/\\\";\\n}\\n\\nfunction baseEndpoint() {\\n\\tvar config = readConfig();\\n\\tif (config.endpoint.length > 0 && !isLikelyBadEndpoint(config.endpoint)) { return config.endpoint.replace(/\\\\/+$/, \\\"\\\"); }\\n\\tif (typeof location !== \\\"undefined\\\" && location.origin) { return location.origin + \\\"/dsh-tiddlywiki\\\"; }\\n\\treturn \\\"/dsh-tiddlywiki\\\";\\n}\\n\\n/*\\nShow a transient message.\\n\\nTW's notifier renders a TIDDLER by title and does **nothing at all** when that\\ntiddler does not exist (core/modules/utils/dom/notifier.js: \\\"Don't do anything\\nif the tiddler doesn't exist\\\"). Passing free text as the title — which is what\\nthis function used to do — therefore made every success/failure notice of the\\n「发送给 Agent」 button invisible: the user clicked, chose a session, and saw\\nnothing. Store the message in a $:/temp tiddler first (volatile: never synced,\\nnever written to disk) and display THAT title.\\n*/\\nvar NOTICE_TITLE = \\\"$:/temp/dsh/send-to-agent/notice\\\";\\nfunction notify(msg) {\\n\\ttry {\\n\\t\\tif ($tw.wiki && $tw.notifier && typeof $tw.notifier.display === \\\"function\\\" && typeof $tw.wiki.addTiddler === \\\"function\\\") {\\n\\t\\t\\t$tw.wiki.addTiddler({ title: NOTICE_TITLE, text: String(msg), type: \\\"text/vnd.tiddlywiki\\\" });\\n\\t\\t\\t$tw.notifier.display(NOTICE_TITLE);\\n\\t\\t\\treturn;\\n\\t\\t}\\n\\t} catch (e) {}\\n\\tif (typeof alert === \\\"function\\\") { alert(msg); }\\n}\\n\\nfunction doSend(payload, sessionId, note) {\\n\\tvar config = readConfig();\\n\\tvar lines = [];\\n\\tlines.push(\\\"《\\\" + payload.title + \\\"》\\\");\\n\\tlines.push(\\\"标签: \\\" + (payload.tags || []).join(\\\", \\\"));\\n\\tlines.push(\\\"类型: \\\" + (payload.type || \\\"无\\\"));\\n\\tlines.push(\\\"\\\");\\n\\tlines.push(\\\"【待办说明】以下内容是我(用户)提前编辑在 TiddlyWiki 知识库中的待办事项,通过「发送给 Agent」一键发送给你处理。请按内容执行;如有任何不清楚的地方,请主动向我提问,不要臆测或擅自发挥。\\\");\\n\\tlines.push(\\\"\\\");\\n\\tlines.push(payload.text || \\\"\\\");\\n\\t// 附加说明放在消息最后:正文之后、作为我(用户)的最终补充要求,优先遵循。\\n\\tif (note && String(note).trim().length > 0) {\\n\\t\\tlines.push(\\\"\\\");\\n\\t\\tlines.push(\\\"【附加说明】\\\" + String(note).trim());\\n\\t}\\n\\tvar text = lines.join(\\\"\\\\n\\\");\\n\\tvar headers = { \\\"Content-Type\\\": \\\"application/json\\\" };\\n\\tif (config.token.length > 0) { headers[\\\"x-send-to-agent-token\\\"] = config.token; }\\n\\t$tw.utils.httpRequest({\\n\\t\\turl: baseEndpoint() + \\\"/agent/send\\\",\\n\\t\\ttype: \\\"POST\\\",\\n\\t\\theaders: headers,\\n\\t\\tdata: JSON.stringify({ sessionId: sessionId, text: text }),\\n\\t\\tcallback: function(err, data) {\\n\\t\\t\\tvar parsed = null;\\n\\t\\t\\ttry { parsed = JSON.parse(data || \\\"\\\"); } catch (e) {}\\n\\t\\t\\tif (err) { notify(\\\"发送失败:\\\" + err); return; }\\n\\t\\t\\tif (parsed && parsed.ok) {\\n\\t\\t\\t\\tnotify(\\\"已发送 ✓ 会话 \\\" + sessionId.slice(0, 8));\\n\\t\\t\\t} else {\\n\\t\\t\\t\\tnotify(\\\"发送失败:\\\" + ((parsed && parsed.error) || data || \\\"未知错误\\\"));\\n\\t\\t\\t}\\n\\t\\t}\\n\\t});\\n}\\n\\nfunction closeOverlay(overlay, escHandler) {\\n\\tif (escHandler) { document.removeEventListener(\\\"keydown\\\", escHandler); }\\n\\tif (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); }\\n}\\n\\nfunction showPicker(payload, items, modes, defaultId, permissions) {\\n\\tif (typeof document === \\\"undefined\\\") { return; }\\n\\t// remove any previously-open picker\\n\\tvar old = document.getElementById(\\\"dsh-send-picker\\\");\\n\\tif (old && old.parentNode) { old.parentNode.removeChild(old); }\\n\\n\\tvar state = { workspace: null, mode: \\\"\\\", note: \\\"\\\", permission: \\\"\\\" };\\n\\n\\tvar overlay = document.createElement(\\\"div\\\");\\n\\toverlay.id = \\\"dsh-send-picker\\\";\\n\\toverlay.setAttribute(\\\"style\\\", \\\"position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.35);z-index:99999;display:flex;align-items:center;justify-content:center;\\\");\\n\\tvar box = document.createElement(\\\"div\\\");\\n\\tbox.setAttribute(\\\"style\\\", \\\"background:#fff;color:#333;border-radius:8px;padding:14px;min-width:340px;max-width:560px;max-height:82vh;display:flex;flex-direction:column;box-shadow:0 8px 30px rgba(0,0,0,.3);font-family:system-ui,-apple-system,sans-serif;\\\");\\n\\n\\t// header\\n\\tvar header = document.createElement(\\\"div\\\");\\n\\theader.setAttribute(\\\"style\\\", \\\"display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;\\\");\\n\\tvar h = document.createElement(\\\"h3\\\");\\n\\th.setAttribute(\\\"style\\\", \\\"margin:0;font-size:15px;\\\");\\n\\th.textContent = \\\"发送给 Agent · 选择目标\\\";\\n\\tvar closeX = document.createElement(\\\"button\\\");\\n\\tcloseX.type = \\\"button\\\";\\n\\tcloseX.textContent = \\\"✕\\\";\\n\\tcloseX.title = \\\"关闭\\\";\\n\\tcloseX.setAttribute(\\\"style\\\", \\\"border:none;background:transparent;font-size:15px;cursor:pointer;color:#888;padding:2px 8px;border-radius:4px;\\\");\\n\\tcloseX.addEventListener(\\\"click\\\", function() { closeOverlay(overlay, escHandler); });\\n\\theader.appendChild(h);\\n\\theader.appendChild(closeX);\\n\\tbox.appendChild(header);\\n\\n\\t// description\\n\\tvar desc = document.createElement(\\\"p\\\");\\n\\tdesc.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;font-size:12px;color:#888;word-break:break-all;\\\");\\n\\tdesc.textContent = \\\"《\\\" + payload.title + \\\"》将作为消息注入所选会话\\\";\\n\\tbox.appendChild(desc);\\n\\n\\t// 附加说明(可选)— any extra context the user wants to attach to the\\n\\t// message, e.g. what to focus on or how to handle it.\\n\\tvar noteRow = document.createElement(\\\"div\\\");\\n\\tnoteRow.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;\\\");\\n\\tvar noteLbl = document.createElement(\\\"label\\\");\\n\\tnoteLbl.setAttribute(\\\"style\\\", \\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\");\\n\\tnoteLbl.textContent = \\\"附加说明(可选,随笔记一起发给 Agent)\\\";\\n\\tnoteRow.appendChild(noteLbl);\\n\\tvar noteTa = document.createElement(\\\"textarea\\\");\\n\\tnoteTa.placeholder = \\\"例如:请重点看第 3 条;我希望你按 XX 方式处理…\\\";\\n\\tnoteTa.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;min-height:44px;resize:vertical;font-family:inherit;\\\");\\n\\tnoteTa.addEventListener(\\\"input\\\", function() { state.note = noteTa.value; });\\n\\tnoteRow.appendChild(noteTa);\\n\\tbox.appendChild(noteRow);\\n\\n\\t// 工作模式(Agent 预设)selector — applies to newly created sessions; the\\n\\t// modes come from GET /agent/modes (id/name/description + deployment\\n\\t// default). When the modes endpoint is unreachable (e.g. an older host) the\\n\\t// row degrades to a hint and no mode is sent — DSH uses its default.\\n\\tvar modeRow = document.createElement(\\\"div\\\");\\n\\tmodeRow.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;\\\");\\n\\tif (modes && modes.length > 0) {\\n\\t\\tvar modeLbl = document.createElement(\\\"label\\\");\\n\\t\\tmodeLbl.setAttribute(\\\"style\\\", \\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\");\\n\\t\\tmodeLbl.textContent = \\\"工作模式(Agent 预设)— 用于新建会话\\\";\\n\\t\\tmodeRow.appendChild(modeLbl);\\n\\t\\tvar sel = document.createElement(\\\"select\\\");\\n\\t\\tsel.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\");\\n\\t\\tvar optDefault = document.createElement(\\\"option\\\");\\n\\t\\toptDefault.value = \\\"\\\";\\n\\t\\toptDefault.textContent = \\\"(默认模式)\\\";\\n\\t\\tsel.appendChild(optDefault);\\n\\t\\tmodes.forEach(function(m) {\\n\\t\\t\\tvar o = document.createElement(\\\"option\\\");\\n\\t\\t\\to.value = m.id || \\\"\\\";\\n\\t\\t\\to.textContent = (m.name || m.id) + (m.isDefault ? \\\"(默认)\\\" : \\\"\\\");\\n\\t\\t\\tsel.appendChild(o);\\n\\t\\t});\\n\\t\\t// preselect the deployment default when listed\\n\\t\\tif (defaultId) {\\n\\t\\t\\tfor (var i = 0; i < sel.options.length; i++) {\\n\\t\\t\\t\\tif (sel.options[i].value === defaultId) { sel.selectedIndex = i; break; }\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\tstate.mode = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].value : \\\"\\\";\\n\\t\\tsel.addEventListener(\\\"change\\\", function() { state.mode = sel.value; });\\n\\t\\tmodeRow.appendChild(sel);\\n\\t} else {\\n\\t\\tvar modeHint = document.createElement(\\\"p\\\");\\n\\t\\tmodeHint.setAttribute(\\\"style\\\", \\\"margin:0;font-size:12px;color:#999;\\\");\\n\\t\\tmodeHint.textContent = \\\"(未获取到可用工作模式,将使用 DSH 默认模式)\\\";\\n\\t\\tmodeRow.appendChild(modeHint);\\n\\t}\\n\\tbox.appendChild(modeRow);\\n\\n\\t// 权限(权限预设)selector — applies to newly created sessions only; the\\n\\t// options come from GET /agent/modes' `permissions` roster (each bundles a\\n\\t// sandbox mode + approval policy). Existing sessions keep their own\\n\\t// permission, so this only affects \\\"新建会话并发送\\\".\\n\\tvar permRow = document.createElement(\\\"div\\\");\\n\\tpermRow.setAttribute(\\\"style\\\", \\\"margin:0 0 8px;\\\");\\n\\tif (permissions && Array.isArray(permissions.items) && permissions.items.length > 0) {\\n\\t\\tvar permLbl = document.createElement(\\\"label\\\");\\n\\t\\tpermLbl.setAttribute(\\\"style\\\", \\\"display:block;font-size:12px;color:#666;margin-bottom:4px;\\\");\\n\\t\\tpermLbl.textContent = \\\"权限(权限预设)— 用于新建会话\\\";\\n\\t\\tpermRow.appendChild(permLbl);\\n\\t\\tvar psel = document.createElement(\\\"select\\\");\\n\\t\\tpsel.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;background:#fff;\\\");\\n\\t\\tpermissions.items.forEach(function(p) {\\n\\t\\t\\tvar o = document.createElement(\\\"option\\\");\\n\\t\\t\\to.value = p.value || \\\"\\\";\\n\\t\\t\\to.textContent = p.name || p.value;\\n\\t\\t\\tif (p.description) { o.title = p.description; }\\n\\t\\t\\tpsel.appendChild(o);\\n\\t\\t});\\n\\t\\t// preselect the deployment default when listed\\n\\t\\tif (permissions.defaultId) {\\n\\t\\t\\tfor (var j = 0; j < psel.options.length; j++) {\\n\\t\\t\\t\\tif (psel.options[j].value === permissions.defaultId) { psel.selectedIndex = j; break; }\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\tstate.permission = psel.options[psel.selectedIndex] ? psel.options[psel.selectedIndex].value : \\\"\\\";\\n\\t\\tpsel.addEventListener(\\\"change\\\", function() { state.permission = psel.value; });\\n\\t\\tpermRow.appendChild(psel);\\n\\t} else {\\n\\t\\tvar permHint = document.createElement(\\\"p\\\");\\n\\t\\tpermHint.setAttribute(\\\"style\\\", \\\"margin:0;font-size:12px;color:#999;\\\");\\n\\t\\tpermHint.textContent = \\\"(未获取到权限预设,新建会话将使用 DSH 默认权限)\\\";\\n\\t\\tpermRow.appendChild(permHint);\\n\\t}\\n\\tbox.appendChild(permRow);\\n\\n\\t// scrollable body\\n\\tvar body = document.createElement(\\\"div\\\");\\n\\tbody.setAttribute(\\\"style\\\", \\\"overflow:auto;flex:1;min-height:0;\\\");\\n\\tbox.appendChild(body);\\n\\n\\t// footer with cancel\\n\\tvar footer = document.createElement(\\\"div\\\");\\n\\tfooter.setAttribute(\\\"style\\\", \\\"display:flex;justify-content:flex-end;gap:8px;margin-top:10px;\\\");\\n\\tvar cancel = document.createElement(\\\"button\\\");\\n\\tcancel.type = \\\"button\\\";\\n\\tcancel.textContent = \\\"取消\\\";\\n\\tcancel.setAttribute(\\\"style\\\", \\\"padding:6px 14px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:13px;\\\");\\n\\tcancel.addEventListener(\\\"click\\\", function() { closeOverlay(overlay, escHandler); });\\n\\tfooter.appendChild(cancel);\\n\\tbox.appendChild(footer);\\n\\n\\toverlay.appendChild(box);\\n\\tdocument.body.appendChild(overlay);\\n\\n\\t// backdrop click + Esc close\\n\\toverlay.addEventListener(\\\"click\\\", function(e) {\\n\\t\\tif (e.target === overlay) { closeOverlay(overlay, escHandler); }\\n\\t});\\n\\tvar escHandler = function(e) { if (e.key === \\\"Escape\\\") { closeOverlay(overlay, escHandler); } };\\n\\tdocument.addEventListener(\\\"keydown\\\", escHandler);\\n\\n\\t// group sessions by workspace (cwd)\\n\\tvar groups = {};\\n\\tvar order = [];\\n\\t(items || []).forEach(function(s) {\\n\\t\\tvar key = (s.cwd && String(s.cwd).length > 0) ? s.cwd : \\\"__default__\\\";\\n\\t\\tif (!groups[key]) { groups[key] = { cwd: s.cwd, sessions: [], max: 0 }; order.push(key); }\\n\\t\\tgroups[key].sessions.push(s);\\n\\t\\tif ((s.updatedAt || 0) > groups[key].max) { groups[key].max = s.updatedAt || 0; }\\n\\t});\\n\\torder.sort(function(a, b) { return groups[b].max - groups[a].max; });\\n\\n\\tfunction btnStyle() {\\n\\t\\treturn \\\"display:block;width:100%;text-align:left;padding:8px 10px;margin:4px 0;border:1px solid #ddd;border-radius:6px;background:#f7f7f7;cursor:pointer;font-size:13px;\\\";\\n\\t}\\n\\tfunction primaryBtnStyle() {\\n\\t\\treturn \\\"display:block;width:100%;text-align:center;padding:7px 10px;margin:6px 0 0;border:1px solid #4a90d9;border-radius:6px;background:#4a90d9;color:#fff;cursor:pointer;font-size:13px;\\\";\\n\\t}\\n\\tfunction smallBtnStyle() {\\n\\t\\treturn \\\"margin:0 0 6px;padding:4px 10px;border:1px solid #ccc;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#555;\\\";\\n\\t}\\n\\n\\tfunction shortSessionLabel(s) {\\n\\t\\tvar id = s.sessionId || \\\"\\\";\\n\\t\\tvar parts = id.split(\\\"-\\\");\\n\\t\\tvar short = (parts.length > 1 ? parts[parts.length - 1] : id).slice(-10);\\n\\t\\tvar when = \\\"\\\";\\n\\t\\tif (s.updatedAt) {\\n\\t\\t\\tvar diff = Date.now() - s.updatedAt;\\n\\t\\t\\tif (diff < 60000) { when = \\\"刚刚\\\"; }\\n\\t\\t\\telse if (diff < 3600000) { when = Math.floor(diff / 60000) + \\\" 分钟前\\\"; }\\n\\t\\t\\telse if (diff < 86400000) { when = Math.floor(diff / 3600000) + \\\" 小时前\\\"; }\\n\\t\\t\\telse {\\n\\t\\t\\t\\tvar d = new Date(s.updatedAt);\\n\\t\\t\\t\\twhen = (d.getMonth() + 1) + \\\"-\\\" + d.getDate() + \\\" \\\" + (\\\"0\\\" + d.getHours()).slice(-2) + \\\":\\\" + (\\\"0\\\" + d.getMinutes()).slice(-2);\\n\\t\\t\\t}\\n\\t\\t}\\n\\t\\treturn \\\"#\\\" + short + (when ? \\\" · \\\" + when : \\\"\\\") + (s.running ? \\\" · ●运行中\\\" : \\\"\\\") + (s.blank ? \\\" · (空)\\\" : \\\"\\\");\\n\\t}\\n\\n\\tfunction modeName(id) {\\n\\t\\tif (!id) { return \\\"\\\"; }\\n\\t\\tfor (var i = 0; i < (modes || []).length; i++) {\\n\\t\\t\\tif (modes[i].id === id) { return modes[i].name || modes[i].id; }\\n\\t\\t}\\n\\t\\treturn id;\\n\\t}\\n\\n\\tfunction render() {\\n\\t\\tbody.innerHTML = \\\"\\\";\\n\\t\\tif (state.workspace === null) { renderWorkspaces(); } else { renderSessions(state.workspace); }\\n\\t}\\n\\n\\tfunction renderWorkspaces() {\\n\\t\\tif (order.length === 0) {\\n\\t\\t\\tvar none = document.createElement(\\\"p\\\");\\n\\t\\t\\tnone.textContent = \\\"还没有任何会话——在下方新建一个吧\\\";\\n\\t\\t\\tnone.setAttribute(\\\"style\\\", \\\"color:#999;font-size:13px;margin:0 0 8px;\\\");\\n\\t\\t\\tbody.appendChild(none);\\n\\t\\t} else {\\n\\t\\t\\torder.forEach(function(key) {\\n\\t\\t\\t\\tvar g = groups[key];\\n\\t\\t\\t\\tvar label = g.cwd || \\\"(默认工作区)\\\";\\n\\t\\t\\t\\tvar b = document.createElement(\\\"button\\\");\\n\\t\\t\\t\\tb.type = \\\"button\\\";\\n\\t\\t\\t\\tb.setAttribute(\\\"style\\\", btnStyle());\\n\\t\\t\\t\\tb.textContent = \\\"📁 \\\" + label + \\\" · \\\" + g.sessions.length + \\\" 个会话\\\";\\n\\t\\t\\t\\tb.title = g.cwd || \\\"默认工作区\\\";\\n\\t\\t\\t\\tb.addEventListener(\\\"click\\\", function() { state.workspace = key; render(); });\\n\\t\\t\\t\\tbody.appendChild(b);\\n\\t\\t\\t});\\n\\t\\t}\\n\\t\\t// new workspace / session\\n\\t\\tvar newRow = document.createElement(\\\"div\\\");\\n\\t\\tnewRow.setAttribute(\\\"style\\\", \\\"margin-top:10px;border-top:1px solid #eee;padding-top:8px;\\\");\\n\\t\\tvar lbl = document.createElement(\\\"p\\\");\\n\\t\\tlbl.textContent = \\\"新建工作区 / 会话\\\";\\n\\t\\tlbl.setAttribute(\\\"style\\\", \\\"margin:0 0 6px;font-size:12px;color:#666;\\\");\\n\\t\\tnewRow.appendChild(lbl);\\n\\t\\tvar input = document.createElement(\\\"input\\\");\\n\\t\\tinput.type = \\\"text\\\";\\n\\t\\tinput.placeholder = \\\"输入工作区路径,留空为默认工作区\\\";\\n\\t\\tinput.setAttribute(\\\"style\\\", \\\"width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:6px;font-size:13px;\\\");\\n\\t\\tnewRow.appendChild(input);\\n\\t\\tvar go = document.createElement(\\\"button\\\");\\n\\t\\tgo.type = \\\"button\\\";\\n\\t\\tgo.textContent = \\\"创建并发送\\\";\\n\\t\\tgo.setAttribute(\\\"style\\\", primaryBtnStyle());\\n\\t\\tgo.addEventListener(\\\"click\\\", function() { createAndSend(payload, input.value.trim()); });\\n\\t\\tnewRow.appendChild(go);\\n\\t\\tinput.addEventListener(\\\"keydown\\\", function(e) { if (e.key === \\\"Enter\\\") { createAndSend(payload, input.value.trim()); } });\\n\\t\\tbody.appendChild(newRow);\\n\\t}\\n\\n\\tfunction renderSessions(key) {\\n\\t\\tvar g = groups[key];\\n\\t\\tvar back = document.createElement(\\\"button\\\");\\n\\t\\tback.type = \\\"button\\\";\\n\\t\\tback.textContent = \\\"← 返回工作区列表\\\";\\n\\t\\tback.setAttribute(\\\"style\\\", smallBtnStyle());\\n\\t\\tback.addEventListener(\\\"click\\\", function() { state.workspace = null; render(); });\\n\\t\\tbody.appendChild(back);\\n\\n\\t\\tvar wsName = document.createElement(\\\"p\\\");\\n\\t\\twsName.textContent = g.cwd ? \\\"📁 \\\" + g.cwd : \\\"(默认工作区)\\\";\\n\\t\\twsName.setAttribute(\\\"style\\\", \\\"margin:0 0 6px;font-size:13px;font-weight:600;word-break:break-all;\\\");\\n\\t\\tbody.appendChild(wsName);\\n\\n\\t\\t(g.sessions || []).forEach(function(s) {\\n\\t\\t\\tvar b = document.createElement(\\\"button\\\");\\n\\t\\t\\tb.type = \\\"button\\\";\\n\\t\\t\\tb.setAttribute(\\\"style\\\", btnStyle());\\n\\t\\t\\tvar label = shortSessionLabel(s);\\n\\t\\t\\tif (s.agentPreset) { label += \\\" · 🧭 \\\" + modeName(s.agentPreset); }\\n\\t\\t\\tb.textContent = label;\\n\\t\\t\\tb.title = \\\"会话 \\\" + s.sessionId + (s.agentPreset ? \\\" · 工作模式 \\\" + modeName(s.agentPreset) : \\\"\\\");\\n\\t\\t\\tb.addEventListener(\\\"click\\\", function() { closeOverlay(overlay, escHandler); doSend(payload, s.sessionId, state.note); });\\n\\t\\t\\tbody.appendChild(b);\\n\\t\\t});\\n\\n\\t\\tvar newBtn = document.createElement(\\\"button\\\");\\n\\t\\tnewBtn.type = \\\"button\\\";\\n\\t\\tnewBtn.textContent = \\\"➕ 在此工作区新建会话并发送\\\";\\n\\t\\tnewBtn.setAttribute(\\\"style\\\", primaryBtnStyle());\\n\\t\\tnewBtn.addEventListener(\\\"click\\\", function() { createAndSend(payload, g.cwd); });\\n\\t\\tbody.appendChild(newBtn);\\n\\t}\\n\\n\\tfunction createAndSend(payload2, cwd) {\\n\\t\\tvar config = readConfig();\\n\\t\\tvar headers = { \\\"Content-Type\\\": \\\"application/json\\\" };\\n\\t\\tif (config.token.length > 0) { headers[\\\"x-send-to-agent-token\\\"] = config.token; }\\n\\t\\tvar body = { cwd: cwd || \\\"\\\" };\\n\\t\\tif (state.mode && String(state.mode).length > 0) { body.mode = state.mode; }\\n\\t\\tif (state.permission && String(state.permission).length > 0) { body.permission = state.permission; }\\n\\t\\t$tw.utils.httpRequest({\\n\\t\\t\\turl: baseEndpoint() + \\\"/agent/create\\\",\\n\\t\\t\\ttype: \\\"POST\\\",\\n\\t\\t\\theaders: headers,\\n\\t\\t\\tdata: JSON.stringify(body),\\n\\t\\t\\tcallback: function(err, data) {\\n\\t\\t\\t\\tvar parsed = null;\\n\\t\\t\\t\\ttry { parsed = JSON.parse(data || \\\"\\\"); } catch (e) {}\\n\\t\\t\\t\\tif (err) { notify(\\\"新建会话失败:\\\" + err); return; }\\n\\t\\t\\t\\tif (parsed && parsed.ok && parsed.sessionId) {\\n\\t\\t\\t\\t\\tcloseOverlay(overlay, escHandler);\\n\\t\\t\\t\\t\\tvar modeNote = parsed.mode ? \\\"(模式 \\\" + modeName(parsed.mode) + \\\")\\\" : \\\"\\\";\\n\\t\\t\\t\\t\\tvar permNote = parsed.permissionApplied ? \\\"(权限 \\\" + (parsed.permission || \\\"\\\") + \\\")\\\" : \\\"\\\";\\n\\t\\t\\t\\t\\tnotify(\\\"已创建会话并发送 ✓ \\\" + parsed.sessionId.slice(0, 8) + modeNote + permNote);\\n\\t\\t\\t\\t\\tdoSend(payload2, parsed.sessionId, state.note);\\n\\t\\t\\t\\t} else {\\n\\t\\t\\t\\t\\tnotify(\\\"新建会话失败:\\\" + ((parsed && parsed.error) || data || \\\"未知错误\\\"));\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t});\\n\\t}\\n\\n\\trender();\\n}\\n\\nfunction handleSend(title) {\\n\\tif (!title) { notify(\\\"无法确定当前笔记标题\\\"); return; }\\n\\tvar tiddler = $tw.wiki.getTiddler(title);\\n\\tif (!tiddler) { notify(\\\"找不到笔记:\\\" + title); return; }\\n\\tvar config = readConfig();\\n\\tif (!config.enabled) { notify(\\\"「发送给 Agent」特性未启用(可在 TW 配置中打开)\\\"); return; }\\n\\tvar payload = {\\n\\t\\ttitle: title,\\n\\t\\ttext: tiddler.fields.text || \\\"\\\",\\n\\t\\ttags: tiddler.fields.tags || [],\\n\\t\\ttype: tiddler.fields.type || \\\"\\\"\\n\\t};\\n\\tvar headers = {};\\n\\tif (config.token.length > 0) { headers[\\\"x-send-to-agent-token\\\"] = config.token; }\\n\\t$tw.utils.httpRequest({\\n\\t\\turl: baseEndpoint() + \\\"/agent/sessions\\\",\\n\\t\\ttype: \\\"GET\\\",\\n\\t\\theaders: headers,\\n\\t\\tcallback: function(err, data) {\\n\\t\\t\\tvar parsed = null;\\n\\t\\t\\ttry { parsed = JSON.parse(data || \\\"\\\"); } catch (e) {}\\n\\t\\t\\tif (err) { notify(\\\"获取会话列表失败:\\\" + err); return; }\\n\\t\\t\\tif (!parsed || !parsed.ok || !parsed.items) { notify(\\\"获取会话列表失败:\\\" + ((parsed && parsed.error) || \\\"未知错误\\\")); return; }\\n\\t\\t\\t// fetch the available 工作模式 (Agent presets) alongside; degrade to\\n\\t\\t\\t// an empty roster (default mode) when the endpoint is missing.\\n\\t\\t\\t$tw.utils.httpRequest({\\n\\t\\t\\t\\turl: baseEndpoint() + \\\"/agent/modes\\\",\\n\\t\\t\\t\\ttype: \\\"GET\\\",\\n\\t\\t\\t\\theaders: headers,\\n\\t\\t\\t\\tcallback: function(err2, data2) {\\n\\t\\t\\t\\t\\tvar modes = [];\\n\\t\\t\\t\\t\\tvar defaultId = \\\"\\\";\\n\\t\\t\\t\\t\\tvar permissions = null;\\n\\t\\t\\t\\t\\tvar parsed2 = null;\\n\\t\\t\\t\\t\\ttry { parsed2 = JSON.parse(data2 || \\\"\\\"); } catch (e) {}\\n\\t\\t\\t\\t\\tif (!err2 && parsed2 && parsed2.ok) {\\n\\t\\t\\t\\t\\t\\tif (Array.isArray(parsed2.items)) {\\n\\t\\t\\t\\t\\t\\t\\tmodes = parsed2.items;\\n\\t\\t\\t\\t\\t\\t\\tdefaultId = parsed2.defaultId || \\\"\\\";\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\tif (parsed2.permissions && Array.isArray(parsed2.permissions.items)) {\\n\\t\\t\\t\\t\\t\\t\\tpermissions = parsed2.permissions;\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\tshowPicker(payload, parsed.items, modes, defaultId, permissions);\\n\\t\\t\\t\\t}\\n\\t\\t\\t});\\n\\t\\t}\\n\\t});\\n}\\n\\nexports.startup = function() {\\n\\tconsole.log(\\\"[dsh-send-to-agent] startup ran\\\");\\n\\tif (!$tw.rootWidget || typeof $tw.rootWidget.addEventListener !== \\\"function\\\") { return; }\\n\\t$tw.rootWidget.addEventListener(\\\"dsh-send-to-agent\\\", function(event) {\\n\\t\\thandleSend(event.param);\\n\\t});\\n};\\n\\n})();\\n\"\n },\n \"$:/plugins/dsh/send-to-agent/ui/icon\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/ui/icon\",\n \"tags\": [\n \"$:/tags/Image\"\n ],\n \"text\": \"\\\\parameters (size:\\\"22pt\\\")\\n<svg width=<<size>> height=<<size>> class=\\\"tc-image-send-to-agent tc-image-button\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z\\\"/></svg>\\n\"\n },\n \"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\": {\n \"title\": \"$:/plugins/dsh/send-to-agent/ui/ViewToolbar/SendToAgent\",\n \"type\": \"text/vnd.tiddlywiki\",\n \"tags\": [\n \"$:/tags/ViewToolbar\"\n ],\n \"icon\": \"$:/plugins/dsh/send-to-agent/ui/icon\",\n \"caption\": \"发送给 Agent\",\n \"description\": \"把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)\",\n \"text\": \"\\\\whitespace trim\\n<$button message=\\\"dsh-send-to-agent\\\"\\n\\tparam=<<currentTiddler>>\\n\\ttooltip=\\\"把当前笔记一键发送给 DSH Agent\\\"\\n\\taria-label=\\\"把当前笔记一键发送给 DSH Agent\\\"\\n\\tclass=<<tv-config-toolbar-class>>\\n>\\n\\t<%if [<tv-config-toolbar-icons>match[yes]] %>\\n\\t\\t{{$:/plugins/dsh/send-to-agent/ui/icon}}\\n\\t<%endif%>\\n\\t<%if [<tv-config-toolbar-text>match[yes]] %>\\n\\t\\t<span class=\\\"tc-btn-text\\\">\\n\\t\\t<$text text=\\\"发送给 Agent\\\"/>\\n\\t</span>\\n\\t<%endif%>\\n</$button>\\n\"\n },\n \"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\": {\n \"title\": \"$:/core/ui/ControlPanel/Toolbars/ItemTemplate\",\n \"type\": \"text/vnd.tiddlywiki\",\n \"text\": \"\\\\define config-title()\\n$(config-base)$$(currentTiddler)$\\n\\\\end\\n\\\\whitespace trim\\n\\n<$draggable tiddler=<<currentTiddler>>>\\n<$checkbox tiddler=<<config-title>> field=\\\"text\\\" checked=\\\"show\\\" unchecked=\\\"hide\\\" default=\\\"show\\\"/>\\n \\n<span class=\\\"tc-icon-wrapper\\\"><$transclude tiddler={{!!icon}}/></span>\\n \\n<$transclude field=\\\"caption\\\"/>\\n -- \\n<i class=\\\"tc-muted\\\"><$transclude field=\\\"description\\\"/></i>\\n</$draggable>\\n\"\n }\n }\n}";
|
|
3497
3497
|
/**
|
|
3498
3498
|
* Seed the "发送给 Agent" TW button exactly once per wiki (mirrors the doc-note
|
|
3499
3499
|
* one-shot policy). The marker records the offer; afterwards the bundle is
|
|
@@ -3519,7 +3519,7 @@ async function seedSendToAgent(client, opts) {
|
|
|
3519
3519
|
"plugin-type": "plugin",
|
|
3520
3520
|
name: "Send to Agent",
|
|
3521
3521
|
author: "dsh-tiddlywiki",
|
|
3522
|
-
version: "0.3.
|
|
3522
|
+
version: "0.3.6",
|
|
3523
3523
|
description: "把当前笔记一键发送给 DSH Agent(TiddlyWiki → DSH 会话注入)"
|
|
3524
3524
|
});
|
|
3525
3525
|
wrote = true;
|
|
@@ -4345,7 +4345,7 @@ const TW_WEB_HOST_DEFAULT = "$protocol$//$host$/";
|
|
|
4345
4345
|
* (mirrored in src/client/theme-sync.ts — the two bundles cannot share code).
|
|
4346
4346
|
*/
|
|
4347
4347
|
const DARK_PALETTE_DEFAULT = "$:/palettes/CupertinoDark";
|
|
4348
|
-
function isPlainObject(value) {
|
|
4348
|
+
function isPlainObject$1(value) {
|
|
4349
4349
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4350
4350
|
}
|
|
4351
4351
|
/** Deep-merge: `over` wins; nested plain objects merge recursively. */
|
|
@@ -4353,7 +4353,7 @@ function deepMerge(base, over) {
|
|
|
4353
4353
|
const out = { ...base };
|
|
4354
4354
|
for (const [key, value] of Object.entries(over)) {
|
|
4355
4355
|
if (value === void 0) continue;
|
|
4356
|
-
if (isPlainObject(value) && isPlainObject(out[key])) out[key] = deepMerge(out[key], value);
|
|
4356
|
+
if (isPlainObject$1(value) && isPlainObject$1(out[key])) out[key] = deepMerge(out[key], value);
|
|
4357
4357
|
else out[key] = value;
|
|
4358
4358
|
}
|
|
4359
4359
|
return out;
|
|
@@ -4468,13 +4468,13 @@ const FORBIDDEN_PATCH_KEYS = /* @__PURE__ */ new Set([
|
|
|
4468
4468
|
* - `__proto__` / `constructor` / `prototype` anywhere → rejected.
|
|
4469
4469
|
*/
|
|
4470
4470
|
function normalizeConfigPatch(input) {
|
|
4471
|
-
if (!isPlainObject(input)) throw new ConfigPatchError("配置补丁必须是一个 JSON 对象(例如 {\"note\":{\"tag\":\"inbox\"}})");
|
|
4471
|
+
if (!isPlainObject$1(input)) throw new ConfigPatchError("配置补丁必须是一个 JSON 对象(例如 {\"note\":{\"tag\":\"inbox\"}})");
|
|
4472
4472
|
const out = {};
|
|
4473
4473
|
const walk = (source, target, prefix) => {
|
|
4474
4474
|
for (const [key, value] of Object.entries(source)) {
|
|
4475
4475
|
if (FORBIDDEN_PATCH_KEYS.has(key)) throw new ConfigPatchError(`配置补丁里不允许出现键「${key}」`);
|
|
4476
4476
|
const path = prefix.length > 0 ? `${prefix}.${key}` : key;
|
|
4477
|
-
if (isPlainObject(value)) {
|
|
4477
|
+
if (isPlainObject$1(value)) {
|
|
4478
4478
|
const child = {};
|
|
4479
4479
|
target[key] = child;
|
|
4480
4480
|
walk(value, child, path);
|
|
@@ -4561,7 +4561,7 @@ var ConfigStore = class {
|
|
|
4561
4561
|
const raw = typeof tiddler.text === "string" ? tiddler.text : "";
|
|
4562
4562
|
try {
|
|
4563
4563
|
const parsed = JSON.parse(raw);
|
|
4564
|
-
if (!isPlainObject(parsed)) throw new Error("config tiddler is not a JSON object");
|
|
4564
|
+
if (!isPlainObject$1(parsed)) throw new Error("config tiddler is not a JSON object");
|
|
4565
4565
|
this.overrides = parsed;
|
|
4566
4566
|
this.lastParseError = void 0;
|
|
4567
4567
|
} catch {
|
|
@@ -4588,7 +4588,7 @@ var ConfigStore = class {
|
|
|
4588
4588
|
if (tiddler !== void 0 && typeof tiddler.text === "string") {
|
|
4589
4589
|
storedText = tiddler.text;
|
|
4590
4590
|
const parsed = JSON.parse(tiddler.text);
|
|
4591
|
-
if (!isPlainObject(parsed)) throw new Error("config tiddler is not a JSON object");
|
|
4591
|
+
if (!isPlainObject$1(parsed)) throw new Error("config tiddler is not a JSON object");
|
|
4592
4592
|
stored = parsed;
|
|
4593
4593
|
}
|
|
4594
4594
|
if (tiddler !== void 0 && typeof tiddler.created === "string" && tiddler.created.trim().length > 0) existingCreated = tiddler.created;
|
|
@@ -5406,15 +5406,71 @@ function cleanTiddler(t) {
|
|
|
5406
5406
|
}
|
|
5407
5407
|
return out;
|
|
5408
5408
|
}
|
|
5409
|
+
/** 是不是一个「平铺对象」(`fields` 唯一合法的形状;数组不算)。 */
|
|
5410
|
+
function isPlainObject(value) {
|
|
5411
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5412
|
+
}
|
|
5413
|
+
/** 错误信息里的短样本(不把整段正文塞进回执)。 */
|
|
5414
|
+
function excerpt(value, max = 60) {
|
|
5415
|
+
const flat = value.replace(/\s+/g, " ").trim();
|
|
5416
|
+
return flat.length > max ? `${flat.slice(0, max)}…` : flat;
|
|
5417
|
+
}
|
|
5418
|
+
/**
|
|
5419
|
+
* 归一化调用方给的 `fields`(v0.26.5)。
|
|
5420
|
+
*
|
|
5421
|
+
* 背景(真实事故):`tiddlywiki_put`/`batch_put`/`append` 的参数 schema 曾把它声明为
|
|
5422
|
+
* `{ type: 'json' }`,而 DSH 的 schema 里 `type:'json'` 是**纯注解**——编译出来的
|
|
5423
|
+
* 节点连 `type` 都没有(见 `src/sdk.ts` 的 `compileValue`)。模型于是不知道它是对象,
|
|
5424
|
+
* 把 `{"review-after":"2026-12-22"}` 当**字符串**发来;字符串走到
|
|
5425
|
+
* `withWorkspaceMark` 的 `{ ...(fields ?? {}) }` 被按字符展开成
|
|
5426
|
+
* `{0:'{', 1:'"', 2:'r', …}`,再被当成几十个单字符字段写进条目,真正的自定义字段
|
|
5427
|
+
* 一个也没落(用户 2026-09-23 实测复现两次)。
|
|
5428
|
+
*
|
|
5429
|
+
* 这条修复同时钉两头:工具 schema 改成真正的 object(`additionalProperties: true`),
|
|
5430
|
+
* 以及在任何展开/遍历之前先在这里归一化。语义:
|
|
5431
|
+
*
|
|
5432
|
+
* | 输入 | 结果 |
|
|
5433
|
+
* |---|---|
|
|
5434
|
+
* | `undefined` / `null` / 空串 | `undefined`(保留基底,不动任何字段) |
|
|
5435
|
+
* | 平铺对象 | 原样返回 |
|
|
5436
|
+
* | JSON 字符串(对象) | 解析后使用——兼容仍按字符串发参的模型 |
|
|
5437
|
+
* | 数组 / 数字 / 布尔 / 解析不出对象的字符串 | **抛错** |
|
|
5438
|
+
*
|
|
5439
|
+
* 最后一条是刻意的:宁可给模型一条可读的报错让它重发,也**绝不**静默丢字段或把
|
|
5440
|
+
* 字符串按字符拆成垃圾字段(那正是这次事故的形态)。
|
|
5441
|
+
*/
|
|
5442
|
+
function normalizeFieldsArg(fields) {
|
|
5443
|
+
if (fields === void 0 || fields === null) return void 0;
|
|
5444
|
+
if (typeof fields === "string") {
|
|
5445
|
+
const trimmed = fields.trim();
|
|
5446
|
+
if (trimmed.length === 0) return void 0;
|
|
5447
|
+
let parsed;
|
|
5448
|
+
try {
|
|
5449
|
+
parsed = JSON.parse(trimmed);
|
|
5450
|
+
} catch {
|
|
5451
|
+
parsed = void 0;
|
|
5452
|
+
}
|
|
5453
|
+
if (isPlainObject(parsed)) return parsed;
|
|
5454
|
+
throw new Error(`fields 必须是一个对象(如 {"review-after":"2026-12-22"}),收到无法解析成对象的字符串:${excerpt(fields)}`);
|
|
5455
|
+
}
|
|
5456
|
+
if (isPlainObject(fields)) return fields;
|
|
5457
|
+
throw new Error(`fields 必须是一个对象(如 {"review-after":"2026-12-22"}),收到 ${Array.isArray(fields) ? "数组" : typeof fields}`);
|
|
5458
|
+
}
|
|
5409
5459
|
/**
|
|
5410
5460
|
* 合并调用方显式提供的自定义字段(跳过保留字段与 undefined)。
|
|
5411
5461
|
*
|
|
5412
5462
|
* 模块私有(v0.22.8):它只被 buildWriteTiddler 调用,导出会让「写策略只有一个
|
|
5413
5463
|
* 入口」这条约定出现第二个可绕过的门。`verify-write-policy.mjs` 断的是
|
|
5414
5464
|
* `buildWriteTiddler`/`cleanTiddler` 的对外行为,不需要这个内部步骤。
|
|
5465
|
+
*
|
|
5466
|
+
* v0.26.5:非对象一律**抛错**,不再静默 return。旧实现的静默 return 让模型看到
|
|
5467
|
+
* 「字段没写进去」却收不到任何错误(事故的第二个症状);配合 `withWorkspaceMark`
|
|
5468
|
+
* 的字符串展开,才有了「几十个单字符字段」那一步。现在任何绕过
|
|
5469
|
+
* `normalizeFieldsArg` 的调用方都会在这里被拦住,而不是往人类笔记上写垃圾。
|
|
5415
5470
|
*/
|
|
5416
5471
|
function applyCustomFields(tiddler, fields) {
|
|
5417
|
-
if (fields === void 0 || fields === null
|
|
5472
|
+
if (fields === void 0 || fields === null) return;
|
|
5473
|
+
if (!isPlainObject(fields)) throw new Error(`内部错误:fields 必须是对象,收到 ${Array.isArray(fields) ? "数组" : typeof fields}`);
|
|
5418
5474
|
for (const [key, value] of Object.entries(fields)) {
|
|
5419
5475
|
if (RESERVED_TIDDLER_FIELDS.has(key)) continue;
|
|
5420
5476
|
if (value === void 0) continue;
|
|
@@ -5700,9 +5756,10 @@ function defineTool(options) {
|
|
|
5700
5756
|
}
|
|
5701
5757
|
},
|
|
5702
5758
|
async execute(args, exec) {
|
|
5703
|
-
const
|
|
5759
|
+
const normalized = options.normalizeArgs !== void 0 ? options.normalizeArgs(args) : args;
|
|
5760
|
+
const violations = validateValue(parameters, normalized, "arguments");
|
|
5704
5761
|
if (violations.length > 0) throw new Error(`Error: invalid arguments: ${violations.join("; ")}`);
|
|
5705
|
-
return userExecute(
|
|
5762
|
+
return userExecute(normalized, exec);
|
|
5706
5763
|
}
|
|
5707
5764
|
};
|
|
5708
5765
|
}
|
|
@@ -9949,11 +10006,35 @@ function workspaceMarkFor(deps, exec) {
|
|
|
9949
10006
|
return workspaceMarkFromCwd(deps.workspaceName(sessionId));
|
|
9950
10007
|
}
|
|
9951
10008
|
/**
|
|
10009
|
+
* 工具参数的 `fields` 归一化(v0.26.5,见 `normalizeFieldsArg`)。
|
|
10010
|
+
*
|
|
10011
|
+
* 历史:`fields` 的参数 schema 曾是 `{ type: 'json' }`——编译后是**纯注解**,
|
|
10012
|
+
* 模型收不到「这是个对象」的信息,于是把 JSON 当字符串发来,被
|
|
10013
|
+
* `withWorkspaceMark` 的 `{ ...fields }` 拆成单字符字段。schema 已改成 object;
|
|
10014
|
+
* 这里再兜一层,字符串能解析成对象就直接用,不必让模型重发一次。
|
|
10015
|
+
* 解析不出对象 → 抛错(绝不静默丢字段,也不按字符展开)。
|
|
10016
|
+
*/
|
|
10017
|
+
function normalizeFieldsInArgs(args) {
|
|
10018
|
+
if (!("fields" in args)) return args;
|
|
10019
|
+
return {
|
|
10020
|
+
...args,
|
|
10021
|
+
fields: normalizeFieldsArg(args.fields)
|
|
10022
|
+
};
|
|
10023
|
+
}
|
|
10024
|
+
/** batch_put 专用:逐条目归一化 `fields`(非对象条目原样留给逐条校验报错)。 */
|
|
10025
|
+
function normalizeBatchItemsFields(items) {
|
|
10026
|
+
if (!Array.isArray(items)) return items;
|
|
10027
|
+
return items.map((item) => item !== null && typeof item === "object" && !Array.isArray(item) ? normalizeFieldsInArgs(item) : item);
|
|
10028
|
+
}
|
|
10029
|
+
/**
|
|
9952
10030
|
* Merge the workspace marker into a NEW note's explicit tags and fields.
|
|
9953
10031
|
*
|
|
9954
10032
|
* ADDITIVE, never replacing: the caller's tags are kept in front and the caller's
|
|
9955
10033
|
* own `workspace` field (if it set one) wins, so an explicit value is never
|
|
9956
10034
|
* silently overwritten. Returns the inputs untouched when there is no marker.
|
|
10035
|
+
*
|
|
10036
|
+
* ⚠️ `fields` 必须已经过 `normalizeFieldsArg()`(v0.26.5):下面这行 `{ ...fields }`
|
|
10037
|
+
* 对字符串会按字符展开成 `{0:'{',1:'"',…}`——那正是 fields bug 的现场。
|
|
9957
10038
|
*/
|
|
9958
10039
|
function withWorkspaceMark(mark, tags, fields) {
|
|
9959
10040
|
if (mark === void 0) return {
|
|
@@ -10538,7 +10619,8 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
10538
10619
|
description: "标签数组(可选)。不传 = 保留既有条目的原标签;传空数组 [] = 清空全部标签;有内容 = 整体替换"
|
|
10539
10620
|
},
|
|
10540
10621
|
fields: {
|
|
10541
|
-
type: "
|
|
10622
|
+
type: "object",
|
|
10623
|
+
additionalProperties: true,
|
|
10542
10624
|
description: "附加自定义字段,如 {\"date\":\"2026-09-02\"}(可选)。fields.type 是**改内容类型的正规入口**(如 {\"type\":\"text/css\"}):新建条目未指定时默认 text/markdown,覆盖既有条目时保留原类型。注意不要把业务分类值(如 \"meeting\")写进 type——业务分类请放 tags"
|
|
10543
10625
|
},
|
|
10544
10626
|
expectedModified: {
|
|
@@ -10554,6 +10636,7 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
10554
10636
|
description: "可选:true 时忽略 expectedModified/expectedRevision 强制覆盖(默认 false)"
|
|
10555
10637
|
}
|
|
10556
10638
|
},
|
|
10639
|
+
normalizeArgs: normalizeFieldsInArgs,
|
|
10557
10640
|
output: { render: (_args, value) => {
|
|
10558
10641
|
const lines = [`已写入 tiddler「${value.title}」`];
|
|
10559
10642
|
if (value.workspace !== void 0) lines.push(`已自动标记工作区: ws/${value.workspace}(字段 ${WORKSPACE_FIELD})`);
|
|
@@ -10631,7 +10714,8 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
10631
10714
|
description: "标签数组(可选)。不传 = 保留原标签;传空数组 [] = 清空全部标签"
|
|
10632
10715
|
},
|
|
10633
10716
|
fields: {
|
|
10634
|
-
type: "
|
|
10717
|
+
type: "object",
|
|
10718
|
+
additionalProperties: true,
|
|
10635
10719
|
description: "附加自定义字段(可选)。注意:fields.type 是 TW 内容类型(保留字段,默认已自动补 text/markdown),不要写业务分类值"
|
|
10636
10720
|
},
|
|
10637
10721
|
expectedModified: {
|
|
@@ -10646,6 +10730,10 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
10646
10730
|
description: "可选:true=覆盖同名(默认),false=跳过已存在的标题"
|
|
10647
10731
|
}
|
|
10648
10732
|
},
|
|
10733
|
+
normalizeArgs: (args) => ({
|
|
10734
|
+
...args,
|
|
10735
|
+
items: normalizeBatchItemsFields(args.items)
|
|
10736
|
+
}),
|
|
10649
10737
|
output: { render: (_args, value) => {
|
|
10650
10738
|
const lines = [`批量写入完成:成功 ${value.written},跳过 ${value.skipped},失败 ${value.failed},共 ${value.items.length} 条。`];
|
|
10651
10739
|
for (const r of value.items) {
|
|
@@ -11053,7 +11141,8 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
11053
11141
|
description: "可选:标签(不传则保留既有条目的原标签,传空数组 [] 表示清空全部标签;新建条目会额外自动补 agent-written)"
|
|
11054
11142
|
},
|
|
11055
11143
|
fields: {
|
|
11056
|
-
type: "
|
|
11144
|
+
type: "object",
|
|
11145
|
+
additionalProperties: true,
|
|
11057
11146
|
description: "可选:显式覆盖的自定义字段(如 {\"type\":\"text/css\"})。不传则保留既有条目的原字段与内容类型"
|
|
11058
11147
|
},
|
|
11059
11148
|
expectedModified: {
|
|
@@ -11069,6 +11158,7 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
11069
11158
|
description: "可选:true 时忽略 expectedModified/expectedRevision 强制写入(默认 false)"
|
|
11070
11159
|
}
|
|
11071
11160
|
},
|
|
11161
|
+
normalizeArgs: normalizeFieldsInArgs,
|
|
11072
11162
|
output: { render: (_args, value) => {
|
|
11073
11163
|
const lines = [`${value.created ? "已新建并写入" : "已增量写入"} tiddler「${value.title}」(${value.mode}${value.heading !== null ? ` · 段落「${value.heading}」` : ""}):新增 ${value.added} 字符,现共 ${value.total} 字符。`];
|
|
11074
11164
|
if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? "(新建且未指定,已默认 markdown)" : ""}`);
|
|
@@ -12698,6 +12788,6 @@ function apply(ctx, rawConfig = {}) {
|
|
|
12698
12788
|
}, "dsh-tiddlywiki: host teardown");
|
|
12699
12789
|
}
|
|
12700
12790
|
//#endregion
|
|
12701
|
-
export { AGENT_WRITTEN_TAG, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT, ALL_ARTICLES_TITLE, ANON_USERNAME, AutoCommitter, CLIP_BRIDGE_BOOKMARKLET, CLIP_BRIDGE_DEFAULT_PORT, CLIP_BRIDGE_DOC_TEXT, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_DRAG_HREF, CLIP_BRIDGE_MARKER_TITLE, ClipBridge, ConfigPatchError, ConfigStore, ConfigUnreadableError, DEFAULT_NOTE_TYPE, DEFAULT_PROMPT_MODE, DEFAULT_WECHAT_ADAPTER, DEFAULT_WECHAT_COMMAND, DOC_NOTE_TAG, DOC_NOTE_TEXT, DOC_NOTE_TITLE, DSH_DOCS_TAG, GitConflictStateError, GitFace, HOME_DEFAULT_TIDDLERS, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HUMAN_EDITED_TAG, LOCATION_STATE_VERSION, MARKDOWN_PLUGIN, MASKED_SECRET, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT, MENUBAR_THEME_TIDDLER, MISSING_TYPE_FILTER, PATH_PREFIX, PROMPT_GOVERNANCE_BLOCKS, PROMPT_MODES, PROMPT_SECTION_NAME, PROMPT_SECTION_ORDER, READY_HARD_FACTOR, READY_POLL_MS, READY_SLOW_POLL_MS, READY_TIMEOUT_DEFAULT_MS, READY_TIMEOUT_MAX_MS, READY_TIMEOUT_MIN_MS, RENDER_BUNDLE_TEXT, RENDER_MARKER_TITLE, RENDER_PLUGIN_FILE, RENDER_PLUGIN_TITLE, SEED_DEFS, SEED_MARKER_VERSION, SEND_TO_AGENT_BUNDLE_TEXT, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_PLUGIN_TITLE, SESSION_SUMMARY_PREFIX, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE, TEXT_LIST_FILTER, TRASH_INDEX_TITLE, TRASH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TW_WEB_HOST_DEFAULT, TW_WEB_HOST_TIDDLER, TiddlyWebClient, TrashIndexUnavailableError, UI_STYLES_MARKER_TITLE, UI_STYLE_ITEMS, WECHAT_ADAPTERS, WECHAT_ADAPTER_FILES, WECHAT_ADAPTER_MARKER, WECHAT_DOCS_MARKER_TITLE, WECHAT_DOCS_TEXT, WECHAT_DOCS_TITLE, WECHAT_PUBLISH_BUNDLE_TEXT, WECHAT_PUBLISH_MARKER_TITLE, WECHAT_PUBLISH_PLUGIN_TITLE, WORKSPACE_FIELD, WORKSPACE_TAG_PREFIX, WechatPublishRunner, WikiServer, WriteConflictError, apply, assertNoConflict, assertPublicImageUrl, awaitReady, buildBinaryTiddler, buildClipTiddler, buildImageNoteTiddler, buildPromptText, buildPublishInvocation, buildVersionInvocation, buildWriteTiddler, bundledCatalog, capOutput, checkAllSeeds, checkWechatReady, cleanTiddler, clearLocationState, deepMerge, defaultAdaptersDir, defaultLocationStateFile, defaultWechatJobDir, defineTool, describeConflict, describePrompt, describeUnreadableConfig, docNoteText, downloadClipImage, drainThenStop, dshHomePath, ensureLanguage, ensurePlugin, ensureTiddlerTimestamps, escapeInline, escapePromptBraces, expandEnvPath, flattenTiddlerFields, flushPendingWrites, formatTiddlerDate, hashText, hostAllowed, imageExtensionForMime, inject, interpretPublishOutcome, isBinaryType, isJunkTag, isPrivateAddress, isSafeCliValue, isSafeDsn, isSafeUrl, listWikiCandidates, locationPath, maskConfigSecrets, name, needsRestartAfterSeeds, normalizeConfigPatch, normalizeLocation, normalizePromptMode, normalizePromptPreview, normalizeReadyTimeoutMs, normalizeThemes, normalizeWechatConfig, normalizeWorkspaceName, openInTwEditor, parseClipPayload, parseSeedMarker, parseTiddlerDate, pickImageMime, pinLanguageTiddler, readActiveThemeName, readLocationState, readSeedMarker, readWikiInfo, readyHardTimeoutMs, registerAdminRoutes, registerRoutes, registerTiddlywikiTools, removeSeedById, resolveClipTitle, resolveTwRoot, runAllSeeds, runSeedById, sanitizeTwFragment, scanAdapterDir, seedAllArticles, seedClipBridge, seedDocNote, seedHomeIndex, seedMenubarTheme, seedRenderRoute, seedSendToAgent, seedStarterDocs, seedUiStyles, seedWechatDocs, seedWechatPublish, stripMaskedSecrets, switchWiki, tailOf, tiddlywikiToolSummary, toIsoDateString, toolSignatureLines, unseedClipBridge, unseedWechatDocs, unseedWechatPublish, waitForFileWrite, workspaceMarkFromCwd, workspaceNameFromCwd, workspaceTagName, writeLocationState, writeSeedMarker, writeSessionSummary, writeWikiInfo };
|
|
12791
|
+
export { AGENT_WRITTEN_TAG, ALL_ARTICLES_MARKER_TITLE, ALL_ARTICLES_TEXT, ALL_ARTICLES_TITLE, ANON_USERNAME, AutoCommitter, CLIP_BRIDGE_BOOKMARKLET, CLIP_BRIDGE_DEFAULT_PORT, CLIP_BRIDGE_DOC_TEXT, CLIP_BRIDGE_DOC_TITLE, CLIP_BRIDGE_DRAG_HREF, CLIP_BRIDGE_MARKER_TITLE, ClipBridge, ConfigPatchError, ConfigStore, ConfigUnreadableError, DEFAULT_NOTE_TYPE, DEFAULT_PROMPT_MODE, DEFAULT_WECHAT_ADAPTER, DEFAULT_WECHAT_COMMAND, DOC_NOTE_TAG, DOC_NOTE_TEXT, DOC_NOTE_TITLE, DSH_DOCS_TAG, GitConflictStateError, GitFace, HOME_DEFAULT_TIDDLERS, HOME_INDEX_ITEMS, HOME_INDEX_MARKER_TITLE, HUMAN_EDITED_TAG, LOCATION_STATE_VERSION, MARKDOWN_PLUGIN, MASKED_SECRET, MENUBAR_THEME_MARKER_TITLE, MENUBAR_THEME_TEXT, MENUBAR_THEME_TIDDLER, MISSING_TYPE_FILTER, PATH_PREFIX, PROMPT_GOVERNANCE_BLOCKS, PROMPT_MODES, PROMPT_SECTION_NAME, PROMPT_SECTION_ORDER, READY_HARD_FACTOR, READY_POLL_MS, READY_SLOW_POLL_MS, READY_TIMEOUT_DEFAULT_MS, READY_TIMEOUT_MAX_MS, READY_TIMEOUT_MIN_MS, RENDER_BUNDLE_TEXT, RENDER_MARKER_TITLE, RENDER_PLUGIN_FILE, RENDER_PLUGIN_TITLE, SEED_DEFS, SEED_MARKER_VERSION, SEND_TO_AGENT_BUNDLE_TEXT, SEND_TO_AGENT_MARKER_TITLE, SEND_TO_AGENT_PLUGIN_TITLE, SESSION_SUMMARY_PREFIX, STARTER_DOCS_ITEMS, STARTER_DOCS_MARKER_TITLE, TEXT_LIST_FILTER, TRASH_INDEX_TITLE, TRASH_PREFIX, TW_PROXY_PATH, TW_PROXY_PREFIX, TW_WEB_HOST_DEFAULT, TW_WEB_HOST_TIDDLER, TiddlyWebClient, TrashIndexUnavailableError, UI_STYLES_MARKER_TITLE, UI_STYLE_ITEMS, WECHAT_ADAPTERS, WECHAT_ADAPTER_FILES, WECHAT_ADAPTER_MARKER, WECHAT_DOCS_MARKER_TITLE, WECHAT_DOCS_TEXT, WECHAT_DOCS_TITLE, WECHAT_PUBLISH_BUNDLE_TEXT, WECHAT_PUBLISH_MARKER_TITLE, WECHAT_PUBLISH_PLUGIN_TITLE, WORKSPACE_FIELD, WORKSPACE_TAG_PREFIX, WechatPublishRunner, WikiServer, WriteConflictError, apply, assertNoConflict, assertPublicImageUrl, awaitReady, buildBinaryTiddler, buildClipTiddler, buildImageNoteTiddler, buildPromptText, buildPublishInvocation, buildVersionInvocation, buildWriteTiddler, bundledCatalog, capOutput, checkAllSeeds, checkWechatReady, cleanTiddler, clearLocationState, deepMerge, defaultAdaptersDir, defaultLocationStateFile, defaultWechatJobDir, defineTool, describeConflict, describePrompt, describeUnreadableConfig, docNoteText, downloadClipImage, drainThenStop, dshHomePath, ensureLanguage, ensurePlugin, ensureTiddlerTimestamps, escapeInline, escapePromptBraces, expandEnvPath, flattenTiddlerFields, flushPendingWrites, formatTiddlerDate, hashText, hostAllowed, imageExtensionForMime, inject, interpretPublishOutcome, isBinaryType, isJunkTag, isPrivateAddress, isSafeCliValue, isSafeDsn, isSafeUrl, listWikiCandidates, locationPath, maskConfigSecrets, name, needsRestartAfterSeeds, normalizeConfigPatch, normalizeFieldsArg, normalizeLocation, normalizePromptMode, normalizePromptPreview, normalizeReadyTimeoutMs, normalizeThemes, normalizeWechatConfig, normalizeWorkspaceName, openInTwEditor, parseClipPayload, parseSeedMarker, parseTiddlerDate, pickImageMime, pinLanguageTiddler, readActiveThemeName, readLocationState, readSeedMarker, readWikiInfo, readyHardTimeoutMs, registerAdminRoutes, registerRoutes, registerTiddlywikiTools, removeSeedById, resolveClipTitle, resolveTwRoot, runAllSeeds, runSeedById, sanitizeTwFragment, scanAdapterDir, seedAllArticles, seedClipBridge, seedDocNote, seedHomeIndex, seedMenubarTheme, seedRenderRoute, seedSendToAgent, seedStarterDocs, seedUiStyles, seedWechatDocs, seedWechatPublish, stripMaskedSecrets, switchWiki, tailOf, tiddlywikiToolSummary, toIsoDateString, toolSignatureLines, unseedClipBridge, unseedWechatDocs, unseedWechatPublish, waitForFileWrite, workspaceMarkFromCwd, workspaceNameFromCwd, workspaceTagName, writeLocationState, writeSeedMarker, writeSessionSummary, writeWikiInfo };
|
|
12702
12792
|
|
|
12703
12793
|
//# sourceMappingURL=index.js.map
|