@cxxl/dsh-sqlite 0.2.0 → 0.3.0

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 CHANGED
@@ -45,6 +45,20 @@ dsh plugin --profile web add @cxxl/dsh-sqlite
45
45
  - `to` / `from` 建议使用绝对路径
46
46
  - 部分导出时注意表间外键引用关系
47
47
 
48
+ ## 稳定触发(v1.2)
49
+
50
+ 插件向每个会话注入一段常驻提示词规则(约 40 token/回合):用户要求"记住/记录/跟踪/保存"或表达"以后查/对比/统计"意图时,使用 sqlite 工具而非文本文件。规则不覆盖模型判断,最终决策权在模型。
51
+
52
+ ## 跨会话协作(v2)
53
+
54
+ 多会话并行同一任务时的对齐机制(设计见 DESIGN.md 第 15 节):
55
+
56
+ - 协作数据放**命名库**(任务名,kebab-case),同一任务的会话共用同一个命名库
57
+ - 会话开工时自动收到**协作库清单**(含各库 meta 描述与表说明)
58
+ - 其他会话对命名库的写入会**自动提醒**相关会话(直到你查询确认);**新库出现会广播**
59
+ - 库表带描述:新建协作库后先建 meta 表(label / description / table:<名>)
60
+ - 默认库 agent.db 是个人数据区,不参与协作提醒
61
+
48
62
  ## 引擎
49
63
 
50
64
  Node 内置 `node:sqlite`(实验性,Node ≥ 22.5),零原生依赖;所有引擎调用隔离在 `lib/engine.js`,可整体替换为 better-sqlite3 而不动其余代码。
package/lib/engine.js CHANGED
@@ -382,3 +382,19 @@ export function tablePreview(dbName, table, limit, signal) {
382
382
  })
383
383
  return { table: name, columns, rows: clean, truncated: rows.length >= cap }
384
384
  }
385
+
386
+ // 读取库内 meta 约定表(label/description/table:<名> 键值对);无表/读失败返回空对象。
387
+ export function readDbMeta(dbName, signal) {
388
+ assertNotAborted(signal)
389
+ const { file } = dbFile(dbName)
390
+ if (!existsSync(file)) return {}
391
+ const { db } = openDb(dbName)
392
+ const out = {}
393
+ try {
394
+ const rows = db.prepare('SELECT key, value FROM meta').all()
395
+ for (const r of rows) {
396
+ if (r.key !== undefined && r.value !== undefined) out[String(r.key)] = String(r.value)
397
+ }
398
+ } catch { /* 无 meta 表:返回空 */ }
399
+ return out
400
+ }
package/lib/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  // index.js — 插件入口:注册五个工具 + 可选挂载自测。
2
2
  import { defineTool } from '@deepseek-ai/dsh-tools'
3
- import { CallId } from '@deepseek-ai/dsh-llm'
3
+ import { CallId, createUserMessage } from '@deepseek-ai/dsh-llm'
4
4
  import { mkdtempSync, rmSync } from 'node:fs'
5
5
  import { tmpdir } from 'node:os'
6
6
  import { join } from 'node:path'
7
7
  import { toolDefs } from './tools.js'
8
8
  import * as engine from './engine.js'
9
+ import { COLLAB_RULE, createCoordination, frame, onPostExecute, preStepTexts } from './coordination.js'
9
10
 
10
11
  export const name = '@cxxl/dsh-sqlite'
11
- export const inject = ['tools', 'webServer']
12
+ export const inject = ['tools', 'webServer', 'systemPrompt']
12
13
 
13
14
  function sendJson(res, code, payload) {
14
15
  const body = JSON.stringify(payload)
@@ -67,12 +68,59 @@ function registerPanelRoutes(ctx) {
67
68
  }, 'dsh-sqlite panel routes')
68
69
  }
69
70
 
71
+ // v1.2:常驻短规则注入——把"持久化场景 → sqlite 工具"的触发率推向接近确定。
72
+ // 机制依据:官方 dsh-plan-mode 的 systemPrompt.section 先例(DESIGN.md 第 14 节)。
73
+ function registerPersistenceRule(ctx) {
74
+ const dispose = ctx.systemPrompt.section({
75
+ name: 'dsh-sqlite:persistence-rule',
76
+ order: 1000,
77
+ text: '持久化规则:当用户要求记住、记录、跟踪、保存结构化数据,或表达"以后还要查/对比/统计"的意图时,使用 sqlite_exec(写)与 sqlite_query(读)工具,不要用普通文本文件替代数据库;不确定库里有什么时先调 sqlite_tables。' + COLLAB_RULE,
78
+ })
79
+ console.log('[dsh-sqlite] persistence rule section registered (order 1000)')
80
+ ctx.effect(() => dispose, 'dsh-sqlite persistence rule')
81
+ }
82
+
83
+ // v2:跨会话协作感知(DESIGN.md 第 15 节)。观察者语义:绝不修改执行链路。
84
+ function registerCoordination(ctx) {
85
+ const coord = createCoordination()
86
+ const names = new Set()
87
+ try {
88
+ for (const f of engine.listDbFiles()) {
89
+ if (f.name !== 'agent.db') names.add(f.name.replace(/\.db$/, ''))
90
+ }
91
+ } catch { /* 启动快照失败则按空处理 */ }
92
+ coord.knownDbs = names
93
+
94
+ ctx.on('tools/post-execute', (exec, result, next) => {
95
+ try { onPostExecute(coord, exec, result) } catch { /* 观察者绝不抛 */ }
96
+ return next()
97
+ })
98
+
99
+ ctx.on('agent/pre-step', async (payload, next) => {
100
+ const decision = await next()
101
+ if (decision === undefined || decision.kind === 'reject') return decision
102
+ try {
103
+ const texts = preStepTexts(coord, payload.agent, engine)
104
+ if (texts.length === 0) return decision
105
+ const msgs = texts.map((text) => createUserMessage({
106
+ content: [{ type: 'text', text: frame(text) }],
107
+ source: { kind: 'plugin', plugin: 'dsh-sqlite' },
108
+ }))
109
+ return { ...decision, messages: [...msgs, ...decision.messages] }
110
+ } catch {
111
+ return decision
112
+ }
113
+ })
114
+ }
115
+
70
116
  export function apply(ctx) {
71
117
  for (const key of Object.keys(toolDefs)) {
72
118
  ctx.tools.register(defineTool(toolDefs[key]))
73
119
  }
74
120
  ctx.effect(() => () => engine.closeAll())
75
121
  registerPanelRoutes(ctx)
122
+ registerPersistenceRule(ctx)
123
+ registerCoordination(ctx)
76
124
 
77
125
  // 挂载自测:DSH_PLUGIN_SELFTEST=1 时在临时数据目录跑一遍真实执行管线。
78
126
  if (process.env.DSH_PLUGIN_SELFTEST === '1') void selfTest(ctx)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cxxl/dsh-sqlite",
3
- "version": "0.2.0",
4
- "description": "DSH plugin: persistent SQLite tools for the agent (sqlite_query / sqlite_exec / sqlite_tables / sqlite_export / sqlite_import) plus a read-only table browser settings page — zero runtime deps beyond Node 22 node:sqlite.",
3
+ "version": "0.3.0",
4
+ "description": "DSH plugin: persistent SQLite tools for the agent (sqlite_query / sqlite_exec / sqlite_tables / sqlite_export / sqlite_import), a read-only table browser settings page, and a persistence-usage prompt rule — zero runtime deps beyond Node 22 node:sqlite.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {