dsh-tiddlywiki 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/index.js +70 -23
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/host/routes.ts +31 -5
- package/src/host/tools.ts +42 -13
- package/src/index.ts +8 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tiddlywiki",
|
|
3
3
|
"description": "TiddlyWiki 5 as the DSH persistent knowledge base: tiddlywiki_* agent tools (search with filters/recent/tags/batch/rename + git sync & conflict resolve), a full TiddlyWiki editor embedded in the GUI center column (same-origin proxy, works over Tailscale/LAN/domain), a quick-note card with draft auto-save & recent-notes picker behind a single knowledge FAB, and git-based sync with auto-commit. Mounts via the official dsh plugin system — no DSH source changes.",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"dsh",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"homepage": "https://github.com/bbqisbbq/dsh-tiddlywiki#readme",
|
|
49
49
|
"repository": {
|
|
50
50
|
"type": "git",
|
|
51
|
-
"url": "https://github.com/bbqisbbq/dsh-tiddlywiki.git"
|
|
51
|
+
"url": "git+https://github.com/bbqisbbq/dsh-tiddlywiki.git"
|
|
52
52
|
},
|
|
53
53
|
"bugs": {
|
|
54
54
|
"url": "https://github.com/bbqisbbq/dsh-tiddlywiki/issues"
|
package/src/host/routes.ts
CHANGED
|
@@ -73,6 +73,16 @@ export interface SessionControllerFace {
|
|
|
73
73
|
create(request: { cwd?: string; workspaceId?: string }): Promise<{ sessionId: string }>
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Structural face over the DSH `workspaceRegistry` service (a subset of
|
|
78
|
+
* dsh-workspace). Only what the agent-create route needs is declared; the
|
|
79
|
+
* runtime instance is a real Service, never inspected data.
|
|
80
|
+
*/
|
|
81
|
+
export interface WorkspaceRegistryFace {
|
|
82
|
+
/** Resolve or create the workspace owning `path` — idempotent by canonical path. */
|
|
83
|
+
create(path: string, title?: string): Promise<{ id: string; path: string }>
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
/** Effective UI visibility flags returned by /status (mirror index.ts). */
|
|
77
87
|
export interface UiDefaultsPublic {
|
|
78
88
|
showQuickNote: boolean
|
|
@@ -91,6 +101,10 @@ export interface RouteDeps {
|
|
|
91
101
|
/** Optional DSH sessionController service (agent-send routes only); resolved
|
|
92
102
|
* lazily per request because it may register after webServer appears. */
|
|
93
103
|
getSessionController: () => SessionControllerFace | undefined
|
|
104
|
+
/** Optional DSH workspaceRegistry service (agent-create route); resolves a
|
|
105
|
+
* cwd to a real Workspace so new sessions land inside it instead of the
|
|
106
|
+
* ungrouped bucket. */
|
|
107
|
+
getWorkspaceRegistry: () => WorkspaceRegistryFace | undefined
|
|
94
108
|
/** Whether the one-click send-to-agent feature is enabled (config switch). */
|
|
95
109
|
sendToAgentEnabled: () => boolean
|
|
96
110
|
/** Optional shared token that must match `x-send-to-agent-token` when set. */
|
|
@@ -334,10 +348,14 @@ export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDep
|
|
|
334
348
|
}
|
|
335
349
|
|
|
336
350
|
/**
|
|
337
|
-
* POST /dsh-tiddlywiki/agent/create — create (or adopt) one ordinary session
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
351
|
+
* POST /dsh-tiddlywiki/agent/create — create (or adopt) one ordinary session
|
|
352
|
+
* inside a real DSH workspace resolved from the requested path. The picker
|
|
353
|
+
* uses it for "new workspace / new session": the directory is materialised so
|
|
354
|
+
* a brand-new workspace actually exists on disk, the path is resolved to its
|
|
355
|
+
* (idempotent) Workspace, and the session is created with `workspaceId` so it
|
|
356
|
+
* lands under that workspace in the sidebar. Creating with bare `cwd` instead
|
|
357
|
+
* would leave the session in the ungrouped bucket even when its working
|
|
358
|
+
* directory matches an existing workspace path.
|
|
341
359
|
*/
|
|
342
360
|
const handleAgentCreate = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
343
361
|
try {
|
|
@@ -361,10 +379,18 @@ export function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDep
|
|
|
361
379
|
json(res, { ok: false, error: 'session service unavailable' }, 503)
|
|
362
380
|
return
|
|
363
381
|
}
|
|
382
|
+
const ws = deps.getWorkspaceRegistry()
|
|
364
383
|
if (cwd.length > 0) {
|
|
365
384
|
await mkdir(cwd, { recursive: true })
|
|
366
385
|
}
|
|
367
|
-
|
|
386
|
+
let created: { sessionId: string }
|
|
387
|
+
if (cwd.length > 0 && ws !== undefined) {
|
|
388
|
+
const workspace = await ws.create(cwd)
|
|
389
|
+
created = await sc.create({ workspaceId: workspace.id })
|
|
390
|
+
json(res, { ok: true, sessionId: created.sessionId, cwd, workspaceId: workspace.id })
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
created = await sc.create({ cwd: cwd.length > 0 ? cwd : undefined })
|
|
368
394
|
json(res, { ok: true, sessionId: created.sessionId, cwd: cwd || null })
|
|
369
395
|
} catch (err) {
|
|
370
396
|
json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)
|
package/src/host/tools.ts
CHANGED
|
@@ -18,6 +18,19 @@ import { defineTool } from '../sdk.ts'
|
|
|
18
18
|
import type { TiddlyWebClient, Tiddler } from './tw-api.ts'
|
|
19
19
|
import type { GitFace } from './git.ts'
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 约定标签:标记「由 Agent 撰写」的笔记。
|
|
23
|
+
* 新建(title 不存在)时由 tiddlywiki_put / tiddlywiki_batch_put 自动补打;
|
|
24
|
+
* 首页据此把这类笔记单独列在「Agent 区块」并从主标签列表排除。
|
|
25
|
+
*/
|
|
26
|
+
export const AGENT_WRITTEN_TAG = 'agent-written'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 约定标签:标记「Agent 撰写后又经人类编辑」的笔记(双标签分级第二档)。
|
|
30
|
+
* 人类在编辑某篇 Agent 笔记时手动补打,首页把这类笔记归入「Agent + 人工」档。
|
|
31
|
+
*/
|
|
32
|
+
export const HUMAN_EDITED_TAG = 'human-edited'
|
|
33
|
+
|
|
21
34
|
/** Structural tool-registry face (subset of the dsh tools service). */
|
|
22
35
|
export interface ToolsCtx {
|
|
23
36
|
tools: { register(tool: unknown): () => void }
|
|
@@ -83,6 +96,21 @@ function rewriteRefs(text: string, oldTitle: string, newTitle: string): { text:
|
|
|
83
96
|
return { text: out, count }
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Compute the final tags for a write:
|
|
101
|
+
* - NEW tiddler (created by this tool) → auto-append the agent-written tag,
|
|
102
|
+
* unless it is a `$:/` system tiddler (config/language/theme internals).
|
|
103
|
+
* - Existing tiddler → keep the caller's tags as-is (an agent maintaining a
|
|
104
|
+
* human note must NOT silently claim authorship; an already agent-written
|
|
105
|
+
* note keeps its tag because the caller provides the full tag list).
|
|
106
|
+
*/
|
|
107
|
+
function finalTagsForWrite(title: string, existing: Tiddler | undefined, tags: string[]): string[] {
|
|
108
|
+
if (existing !== undefined) return tags
|
|
109
|
+
if (title.startsWith('$:/')) return tags
|
|
110
|
+
if (tags.includes(AGENT_WRITTEN_TAG)) return tags
|
|
111
|
+
return [...tags, AGENT_WRITTEN_TAG]
|
|
112
|
+
}
|
|
113
|
+
|
|
86
114
|
export function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<() => void> {
|
|
87
115
|
const disposers: Array<() => void> = []
|
|
88
116
|
const register = (tool: unknown): void => { disposers.push(ctx.tools.register(tool)) }
|
|
@@ -227,7 +255,7 @@ export function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<(
|
|
|
227
255
|
// ── tiddlywiki_put ───────────────────────────────────────────────────────
|
|
228
256
|
register(defineTool({
|
|
229
257
|
name: 'tiddlywiki_put',
|
|
230
|
-
description: '写入(新建或覆盖)一个 TiddlyWiki tiddler。同名覆盖;tags 为标签数组,fields 为附加自定义字段(json 对象,会写入 tiddler 字段)。写入后触发自动 commit
|
|
258
|
+
description: '写入(新建或覆盖)一个 TiddlyWiki tiddler。同名覆盖;tags 为标签数组,fields 为附加自定义字段(json 对象,会写入 tiddler 字段)。写入后触发自动 commit。新建(title 不存在)时自动补打 agent-written 标签标记「由 Agent 撰写」,无需手动添加。',
|
|
231
259
|
parameters: {
|
|
232
260
|
title: { type: 'string', description: 'tiddler 标题(精确匹配,覆盖同名)', required: true },
|
|
233
261
|
text: { type: 'string', description: 'tiddler 全文(wiki 文本)', required: true },
|
|
@@ -249,19 +277,21 @@ export function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<(
|
|
|
249
277
|
execute: async (args: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }): Promise<PutResult> => {
|
|
250
278
|
const wiki = deps.wiki()
|
|
251
279
|
if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')
|
|
280
|
+
const existing = await wiki.get(args.title).catch(() => undefined)
|
|
281
|
+
const tags = finalTagsForWrite(args.title, existing, Array.isArray(args.tags) ? args.tags.filter((t) => typeof t === 'string' && t.trim().length > 0) : [])
|
|
252
282
|
const tiddler: Tiddler = { title: args.title, text: args.text }
|
|
253
|
-
if (
|
|
283
|
+
if (tags.length > 0) tiddler.tags = tags
|
|
254
284
|
if (args.fields !== undefined && typeof args.fields === 'object' && args.fields !== null) Object.assign(tiddler, args.fields)
|
|
255
285
|
await wiki.put(tiddler)
|
|
256
286
|
deps.autoCommit()
|
|
257
|
-
return { ok: true, title: args.title, tags
|
|
287
|
+
return { ok: true, title: args.title, tags, fields: args.fields ?? null }
|
|
258
288
|
},
|
|
259
289
|
}))
|
|
260
290
|
|
|
261
291
|
// ── tiddlywiki_batch_put ─────────────────────────────────────────────────
|
|
262
292
|
register(defineTool({
|
|
263
293
|
name: 'tiddlywiki_batch_put',
|
|
264
|
-
description: '批量写入/覆盖多个 TiddlyWiki tiddler(一次工具调用)。overwrite=false 时跳过已存在的标题;返回逐条结果。写入后触发自动 commit
|
|
294
|
+
description: '批量写入/覆盖多个 TiddlyWiki tiddler(一次工具调用)。overwrite=false 时跳过已存在的标题;返回逐条结果。写入后触发自动 commit。新建(title 不存在)的条目会自动补打 agent-written 标签,无需手动添加。',
|
|
265
295
|
parameters: {
|
|
266
296
|
items: {
|
|
267
297
|
type: 'array',
|
|
@@ -301,16 +331,15 @@ export function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<(
|
|
|
301
331
|
for (const item of list) {
|
|
302
332
|
if (typeof item.title !== 'string' || item.title.length === 0) throw new Error('batch_put: 每条 items 都需要非空 title')
|
|
303
333
|
if (typeof item.text !== 'string') throw new Error(`batch_put: items「${item.title}」缺少 text`)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
continue
|
|
310
|
-
}
|
|
334
|
+
const existing = await wiki.get(item.title).catch(() => undefined)
|
|
335
|
+
if (!overwrite && existing !== undefined) {
|
|
336
|
+
skipped++
|
|
337
|
+
results.push({ title: item.title, written: false, skipped: true })
|
|
338
|
+
continue
|
|
311
339
|
}
|
|
340
|
+
const tags = finalTagsForWrite(item.title, existing, Array.isArray(item.tags) ? item.tags.filter((t) => typeof t === 'string' && t.trim().length > 0) : [])
|
|
312
341
|
const tiddler: Tiddler = { title: item.title, text: item.text }
|
|
313
|
-
if (
|
|
342
|
+
if (tags.length > 0) tiddler.tags = tags
|
|
314
343
|
if (item.fields !== undefined && typeof item.fields === 'object' && item.fields !== null) Object.assign(tiddler, item.fields)
|
|
315
344
|
await wiki.put(tiddler)
|
|
316
345
|
written++
|
|
@@ -366,7 +395,7 @@ export function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<(
|
|
|
366
395
|
}
|
|
367
396
|
}
|
|
368
397
|
}
|
|
369
|
-
await wiki.put(cleanTiddler(existing))
|
|
398
|
+
await wiki.put({ ...cleanTiddler(existing), title: newTitle })
|
|
370
399
|
await wiki.delete(oldTitle)
|
|
371
400
|
if (refsTiddlers === 0) {
|
|
372
401
|
warning = '未找到任何其他 tiddler 引用旧标题;如确实需要,可手动补充链接。'
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { watch, type FSWatcher } from 'node:fs'
|
|
|
22
22
|
import { writeFile } from 'node:fs/promises'
|
|
23
23
|
import { join } from 'node:path'
|
|
24
24
|
import { AutoCommitter, GitFace } from './host/git.ts'
|
|
25
|
-
import { registerRoutes, type SessionControllerFace, type WebServerFace } from './host/routes.ts'
|
|
25
|
+
import { registerRoutes, type SessionControllerFace, type WebServerFace, type WorkspaceRegistryFace } from './host/routes.ts'
|
|
26
26
|
import { ConfigStore, deepMerge, type PluginConfigShape } from './host/config.ts'
|
|
27
27
|
import { registerAdminRoutes, ensureLanguage, resolveTwRoot, type AdminDeps } from './host/admin.ts'
|
|
28
28
|
import { seedDocNote, DOC_NOTE_TITLE } from './host/seed-notes.ts'
|
|
@@ -43,6 +43,7 @@ export { ConfigStore, deepMerge } from './host/config.ts'
|
|
|
43
43
|
export { openInTwEditor, registerRoutes } from './host/routes.ts'
|
|
44
44
|
export { registerAdminRoutes, resolveTwRoot, readWikiInfo, writeWikiInfo, bundledCatalog, ensureLanguage, normalizeThemes } from './host/admin.ts'
|
|
45
45
|
export { seedDocNote, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT } from './host/seed-notes.ts'
|
|
46
|
+
export { registerTiddlywikiTools } from './host/tools.ts'
|
|
46
47
|
export type { PluginConfigShape } from './host/config.ts'
|
|
47
48
|
export type { GitStatusView } from './host/git.ts'
|
|
48
49
|
export type { Tiddler } from './host/tw-api.ts'
|
|
@@ -182,7 +183,9 @@ pull 冲突后:先 \`tiddlywiki_git_resolve files=[冲突文件] strategy=keep
|
|
|
182
183
|
|
|
183
184
|
把 wiki 当作长期记忆与知识沉淀的地方:会议纪要、决策记录、调研笔记、随手的想法都可存成独立 tiddler(tag 建议用 inbox/meeting/decision 等便于检索)。
|
|
184
185
|
|
|
185
|
-
用本插件自动创建笔记时,除了业务性 tag 外,请把「当前工作区(项目)的名字」也作为标签之一加上去(例如 \`tiddlywiki_put\` 的 tags 里带上当前 workspace
|
|
186
|
+
用本插件自动创建笔记时,除了业务性 tag 外,请把「当前工作区(项目)的名字」也作为标签之一加上去(例如 \`tiddlywiki_put\` 的 tags 里带上当前 workspace 名),这样笔记能按项目归集、检索。
|
|
187
|
+
|
|
188
|
+
**Agent 笔记标签约定**:\`tiddlywiki_put\` / \`tiddlywiki_batch_put\` 新建笔记时,插件会自动补打 \`agent-written\` 标签(标记「由 Agent 撰写」),无需手动添加,也不要手动移除它(除非用户明确要求)。首页会把 Agent 笔记单独列在「Agent 区块」,主标签列表只统计人类笔记。若某篇 Agent 笔记后续被人类编辑过,请在该笔记上补打 \`human-edited\` 标签,首页会把它归入「Agent + 人工」档。覆盖写入已有的(人类)笔记时不会自动加 agent-written,请保持笔记原本的归属。`
|
|
186
189
|
|
|
187
190
|
/**
|
|
188
191
|
* Mount the host half.
|
|
@@ -358,6 +361,8 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
358
361
|
// agent routes must work whenever a request actually arrives.
|
|
359
362
|
const getSessionController = (): SessionControllerFace | undefined =>
|
|
360
363
|
ctx.get('sessionController') as SessionControllerFace | undefined
|
|
364
|
+
const getWorkspaceRegistry = (): WorkspaceRegistryFace | undefined =>
|
|
365
|
+
ctx.get('workspaceRegistry') as WorkspaceRegistryFace | undefined
|
|
361
366
|
const disposeRoutes = registerRoutes({ webServer: ws }, {
|
|
362
367
|
server,
|
|
363
368
|
getClient: client,
|
|
@@ -367,6 +372,7 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
|
|
|
367
372
|
uiDefaults: () => effectiveUi(),
|
|
368
373
|
getWikiPath: () => wikiPath,
|
|
369
374
|
getSessionController,
|
|
375
|
+
getWorkspaceRegistry,
|
|
370
376
|
sendToAgentEnabled: () => eff().ui?.sendToAgent?.enabled !== false,
|
|
371
377
|
sendToAgentToken: () => {
|
|
372
378
|
const token = eff().ui?.sendToAgent?.token
|