rcs-context 0.0.0-stage → 0.1.40
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/LICENSE.txt +21 -0
- package/README.md +204 -2
- package/condense/chunk.mjs +258 -0
- package/condense/ivr.mjs +104 -0
- package/contextinjector-webui/README.md +43 -0
- package/contextinjector-webui/lib/client.js +3034 -0
- package/contextinjector-webui/lib/index.mjs +1565 -0
- package/contextinjector-webui/lib/trace-read.mjs +180 -0
- package/contextinjector-webui/package.json +21 -0
- package/cordis.patch.yml +25 -0
- package/package.json +67 -4
- package/toolfold/toolfold.mjs +513 -0
- package/tools/agg-cost.mjs +78 -0
- package/tools/measure-prompt-surface.mjs +236 -0
- package/tools/session-econ.mjs +133 -0
- package/web/contextinjector.mjs +3139 -0
- package/web/extreason.mjs +1107 -0
- package/web/ledger-online.mjs +229 -0
- package/web/logcompiler.mjs +369 -0
- package/web/memory-durability.mjs +431 -0
- package/web/memory-janitor.mjs +551 -0
- package/web/memory-ledger-core.mjs +1331 -0
- package/web/memory-ledger.mjs +836 -0
- package/web/memory-observations.mjs +430 -0
- package/web/memory-online-bridge.mjs +688 -0
- package/web/memory-proposal-store.mjs +1054 -0
- package/web/memory-proposals.mjs +2306 -0
- package/web/memory-trace.mjs +533 -0
- package/web/memory-vec.mjs +241 -0
- package/web/memory.mjs +5801 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Copyright (c) 2026 ScreamingMaggot. This source code is licensed under the MIT License.
|
|
2
|
+
// trace-read.mjs — read-only access to Phase 4 retrieval traces for the WebUI host half.
|
|
3
|
+
// Kept inside the package so the WebUI can be loaded without reaching into ../../web.
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import { existsSync, statSync } from 'node:fs'
|
|
6
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
7
|
+
|
|
8
|
+
const sha1 = (s) => createHash('sha1').update(String(s ?? '')).digest('hex')
|
|
9
|
+
const hash8 = (s) => '#' + sha1(s).slice(0, 8)
|
|
10
|
+
const clip = (s, n) => {
|
|
11
|
+
const t = String(s ?? '')
|
|
12
|
+
return t.length > n ? t.slice(0, Math.max(1, n - 1)) + '…' : t
|
|
13
|
+
}
|
|
14
|
+
const TRIGGERS = new Set(['tool', 'ls', 'recall', 'inject', 'handover'])
|
|
15
|
+
|
|
16
|
+
function open(path) {
|
|
17
|
+
if (!path || !existsSync(path)) return null
|
|
18
|
+
try {
|
|
19
|
+
const db = new DatabaseSync(path, { readOnly: true })
|
|
20
|
+
for (const p of ['busy_timeout=5000', 'cache_size=-65536']) {
|
|
21
|
+
try { db.exec('PRAGMA ' + p) } catch { /* ignore */ }
|
|
22
|
+
}
|
|
23
|
+
return db
|
|
24
|
+
} catch { return null }
|
|
25
|
+
}
|
|
26
|
+
function hasTable(db) {
|
|
27
|
+
try { return !!db.prepare("SELECT 1 x FROM sqlite_master WHERE type='table' AND name='retrieval_traces'").get() } catch { return false }
|
|
28
|
+
}
|
|
29
|
+
const parse = (s, d) => { try { const v = JSON.parse(s); return v ?? d } catch { return d } }
|
|
30
|
+
|
|
31
|
+
function mapRow(r) {
|
|
32
|
+
return {
|
|
33
|
+
id: Number(r.id), ts: Number(r.ts), sid: r.sid, workspace: r.workspace, trigger: r.trigger,
|
|
34
|
+
uri: r.uri, queryHash: r.query_hash, preview: clip(r.query_text, 80), queryLen: Number(r.query_len),
|
|
35
|
+
mode: r.mode, candTotal: Number(r.cand_total), selTotal: Number(r.sel_total),
|
|
36
|
+
ms: Number(r.ms), error: r.error, truncated: Number(r.truncated),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function listTraces(dbFile, { sid, since = 0, until = 0, limit = 50, cursor = null, trigger = '' } = {}) {
|
|
41
|
+
const db = open(dbFile)
|
|
42
|
+
if (!db) return { available: false, traces: [], nextCursor: null, hasMore: false }
|
|
43
|
+
try {
|
|
44
|
+
if (!hasTable(db)) return { available: false, traces: [], nextCursor: null, hasMore: false }
|
|
45
|
+
const lim = Math.max(1, Math.min(200, Number(limit) || 50))
|
|
46
|
+
const where = ['sid=?']
|
|
47
|
+
const args = [String(sid)]
|
|
48
|
+
if (since > 0) { where.push('ts>=?'); args.push(Number(since)) }
|
|
49
|
+
if (until > 0) { where.push('ts<=?'); args.push(Number(until)) }
|
|
50
|
+
if (trigger && TRIGGERS.has(trigger)) { where.push('trigger=?'); args.push(trigger) }
|
|
51
|
+
if (cursor && Number.isFinite(cursor.ts) && Number.isFinite(cursor.id)) {
|
|
52
|
+
where.push('(ts<? OR (ts=? AND id<?))')
|
|
53
|
+
args.push(cursor.ts, cursor.ts, cursor.id)
|
|
54
|
+
}
|
|
55
|
+
const rows = db.prepare(`SELECT id, ts, sid, workspace, trigger, uri, query_hash, query_text, query_len,
|
|
56
|
+
mode, cand_total, sel_total, ms, error, truncated
|
|
57
|
+
FROM retrieval_traces WHERE ${where.join(' AND ')} ORDER BY ts DESC, id DESC LIMIT ?`)
|
|
58
|
+
.all(...args, lim + 1)
|
|
59
|
+
const hasMore = rows.length > lim
|
|
60
|
+
const page = rows.slice(0, lim)
|
|
61
|
+
const last = page[page.length - 1]
|
|
62
|
+
return {
|
|
63
|
+
available: true, traces: page.map(mapRow),
|
|
64
|
+
nextCursor: hasMore && last ? `${last.ts}:${last.id}` : null, hasMore, limit: lim,
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
return { available: false, traces: [], nextCursor: null, hasMore: false }
|
|
68
|
+
} finally {
|
|
69
|
+
try { db.close() } catch { /* ignore */ }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getTrace(dbFile, id) {
|
|
74
|
+
const db = open(dbFile)
|
|
75
|
+
if (!db) return { available: false, trace: null }
|
|
76
|
+
try {
|
|
77
|
+
if (!hasTable(db)) return { available: false, trace: null }
|
|
78
|
+
const r = db.prepare('SELECT * FROM retrieval_traces WHERE id=?').get(Number(id))
|
|
79
|
+
if (!r) return { available: true, trace: null }
|
|
80
|
+
return {
|
|
81
|
+
available: true,
|
|
82
|
+
trace: {
|
|
83
|
+
id: Number(r.id), ts: Number(r.ts), sid: r.sid, workspace: r.workspace, trigger: r.trigger, uri: r.uri,
|
|
84
|
+
query: { hash: r.query_hash, text: r.query_text, len: Number(r.query_len) },
|
|
85
|
+
mode: r.mode, widened: parse(r.widened, []), steps: parse(r.steps, []),
|
|
86
|
+
candidates: parse(r.candidates, []), selected: parse(r.selected, []),
|
|
87
|
+
candTotal: Number(r.cand_total), selTotal: Number(r.sel_total), ms: Number(r.ms),
|
|
88
|
+
model: r.model, error: r.error, truncated: Number(r.truncated), bytes: Number(r.bytes),
|
|
89
|
+
ref: `rcs-trace://${r.sid}/${r.id}`,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
return { available: false, trace: null }
|
|
94
|
+
} finally {
|
|
95
|
+
try { db.close() } catch { /* ignore */ }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function redactExport(text) {
|
|
100
|
+
let s = String(text ?? '')
|
|
101
|
+
s = s.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]')
|
|
102
|
+
s = s.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, 'sk-[REDACTED]')
|
|
103
|
+
s = s.replace(/((?:password|passwd|pwd|token|api[_-]?key|secret)\s*[=:]\s*)([^\s,;]+)/gi, (_m, p) => p + '[REDACTED]')
|
|
104
|
+
s = s.replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, (m) => `<email:${hash8(m)}>`)
|
|
105
|
+
return s
|
|
106
|
+
}
|
|
107
|
+
function hashUri(uri) {
|
|
108
|
+
const s = String(uri ?? '')
|
|
109
|
+
const i = s.lastIndexOf('/')
|
|
110
|
+
if (i < 0 || i === s.length - 1) return s
|
|
111
|
+
return s.slice(0, i + 1) + '<key:' + hash8(s.slice(i + 1)) + '>'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function exportTraces(dbFile, { sid, since = 0, until = 0, limit = 5000 } = {}) {
|
|
115
|
+
const db = open(dbFile)
|
|
116
|
+
if (!db) return { available: false, rows: [] }
|
|
117
|
+
try {
|
|
118
|
+
if (!hasTable(db)) return { available: false, rows: [] }
|
|
119
|
+
const lim = Math.max(1, Math.min(5000, Number(limit) || 5000))
|
|
120
|
+
const where = ['sid=?']
|
|
121
|
+
const args = [String(sid)]
|
|
122
|
+
if (since > 0) { where.push('ts>=?'); args.push(Number(since)) }
|
|
123
|
+
if (until > 0) { where.push('ts<=?'); args.push(Number(until)) }
|
|
124
|
+
const rows = db.prepare(`SELECT * FROM retrieval_traces WHERE ${where.join(' AND ')} ORDER BY ts ASC, id ASC LIMIT ?`)
|
|
125
|
+
.all(...args, lim)
|
|
126
|
+
return {
|
|
127
|
+
available: true,
|
|
128
|
+
rows: rows.map((r) => ({
|
|
129
|
+
ts: Number(r.ts), sid: `<sid:${hash8(r.sid)}>`, workspace: `<ws:${hash8(r.workspace)}>`,
|
|
130
|
+
trigger: r.trigger, uri: hashUri(r.uri),
|
|
131
|
+
query: { hash: r.query_hash, text: redactExport(r.query_text), len: Number(r.query_len) },
|
|
132
|
+
mode: r.mode, widened: parse(r.widened, []),
|
|
133
|
+
steps: parse(r.steps, []).map((s) => ({ ...s, uri: s.uri ? hashUri(s.uri) : undefined })),
|
|
134
|
+
candidates: parse(r.candidates, []).map((c) => ({ ...c, uri: hashUri(c.uri) })),
|
|
135
|
+
selected: parse(r.selected, []).map((c) => ({ ...c, uri: hashUri(c.uri) })),
|
|
136
|
+
candTotal: Number(r.cand_total), selTotal: Number(r.sel_total), ms: Number(r.ms),
|
|
137
|
+
model: r.model, error: r.error, truncated: Number(r.truncated),
|
|
138
|
+
})),
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
return { available: false, rows: [] }
|
|
142
|
+
} finally {
|
|
143
|
+
try { db.close() } catch { /* ignore */ }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function clearTraces(dbFile, sid) {
|
|
148
|
+
if (!dbFile || !existsSync(dbFile)) return { ok: true, deleted: 0 }
|
|
149
|
+
let db = null
|
|
150
|
+
try {
|
|
151
|
+
db = new DatabaseSync(dbFile)
|
|
152
|
+
db.exec('PRAGMA busy_timeout=5000')
|
|
153
|
+
try { db.exec('PRAGMA cache_size=-65536') } catch { /* ignore */ }
|
|
154
|
+
if (!hasTable(db)) return { ok: true, deleted: 0 }
|
|
155
|
+
const r = db.prepare('DELETE FROM retrieval_traces WHERE sid=?').run(String(sid))
|
|
156
|
+
return { ok: true, deleted: Number(r.changes) || 0 }
|
|
157
|
+
} catch {
|
|
158
|
+
return { ok: false, deleted: 0 }
|
|
159
|
+
} finally {
|
|
160
|
+
try { db?.close() } catch { /* ignore */ }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function traceStats(dbFile) {
|
|
165
|
+
if (!dbFile || !existsSync(dbFile)) return { available: false, rows: 0, bytes: 0 }
|
|
166
|
+
let bytes = 0
|
|
167
|
+
try { bytes = statSync(dbFile).size } catch { /* ignore */ }
|
|
168
|
+
const db = open(dbFile)
|
|
169
|
+
if (!db) return { available: false, rows: 0, bytes }
|
|
170
|
+
try {
|
|
171
|
+
if (!hasTable(db)) return { available: false, rows: 0, bytes }
|
|
172
|
+
return { available: true, rows: Number(db.prepare('SELECT COUNT(*) c FROM retrieval_traces').get()?.c ?? 0), bytes }
|
|
173
|
+
} catch {
|
|
174
|
+
return { available: false, rows: 0, bytes }
|
|
175
|
+
} finally {
|
|
176
|
+
try { db.close() } catch { /* ignore */ }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const TRACE_TRIGGERS = TRIGGERS
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contextinjector-webui",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.mjs",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"client": {
|
|
14
|
+
"inject": [
|
|
15
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
16
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
17
|
+
],
|
|
18
|
+
"platform": "web"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# RCS bundle layer — 随包分发,`dsh plugin add` 装好后由 profile 的 dsh.profile.bundles 选中即生效。
|
|
2
|
+
#
|
|
3
|
+
# 入口一律用**包内相对路径**:本包的目录布局(web/ + condense/ + toolfold/)与安装进
|
|
4
|
+
# DSH_HOME 后的布局同构,插件源码里的 `../condense/*`、`../toolfold/*` 相对导入因此
|
|
5
|
+
# 在两种装法(本 bundle / install.ps1)下都成立,源码无需分叉。
|
|
6
|
+
- insert:
|
|
7
|
+
- id: contextinjector
|
|
8
|
+
name: './web/contextinjector.mjs'
|
|
9
|
+
- insert:
|
|
10
|
+
- id: logcompiler
|
|
11
|
+
name: './web/logcompiler.mjs'
|
|
12
|
+
- insert:
|
|
13
|
+
- id: extreason
|
|
14
|
+
name: './web/extreason.mjs'
|
|
15
|
+
- insert:
|
|
16
|
+
- id: memory
|
|
17
|
+
name: './web/memory.mjs'
|
|
18
|
+
- insert:
|
|
19
|
+
- id: contextinjector-webui
|
|
20
|
+
name: './contextinjector-webui/lib/index.mjs'
|
|
21
|
+
|
|
22
|
+
# 注:0.1.7-rc.2 一代宿主上,折叠与宿主自带的 `plugin-package-inventory-deepseek`
|
|
23
|
+
# 请求扩展互斥(折叠会触发该扩展抛 REQUEST_EXTENSION)。本 bundle **刻意不在此处关它**——
|
|
24
|
+
# bundle 补丁无法探测该扩展是否存在,对不存在的 id 做 config 覆盖不安全。
|
|
25
|
+
# 那一代为唯一会踩到的宿主,处理方式见 README「兼容性」一节(install.ps1 会按探测结果处理)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rcs-context",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.40",
|
|
4
|
+
"description": "RCS — fold repeated tool output without losing the original, give the model a read-only verifier, and externalize session facts into auditable memory. DeepSeek Harness plugin bundle: context folding, external reasoning, append-only transcript, and external memory with rcs:// addressing, L0/L1/L2 layers, directory-aware search, retrieval traces, a deterministic durability gate, host-verified semantic proposals, and a file-state janitor. Compatible with DSH 0.1.5-rc.2 through 0.2.2-rc.2. MIT.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./web/contextinjector.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./web/contextinjector.mjs",
|
|
9
|
+
"./web/*": "./web/*",
|
|
10
|
+
"./condense/*": "./condense/*",
|
|
11
|
+
"./toolfold/*": "./toolfold/*",
|
|
12
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"web",
|
|
17
|
+
"condense",
|
|
18
|
+
"toolfold",
|
|
19
|
+
"contextinjector-webui",
|
|
20
|
+
"tools",
|
|
21
|
+
"cordis.patch.yml",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE.txt"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"dsh",
|
|
27
|
+
"dsh-plugin",
|
|
28
|
+
"deepseek-harness",
|
|
29
|
+
"context-management",
|
|
30
|
+
"context-compression",
|
|
31
|
+
"context-engineering",
|
|
32
|
+
"llm-agent",
|
|
33
|
+
"external-memory",
|
|
34
|
+
"agent-memory",
|
|
35
|
+
"semantic-memory",
|
|
36
|
+
"ledger",
|
|
37
|
+
"retrieval",
|
|
38
|
+
"provenance",
|
|
39
|
+
"auditability",
|
|
40
|
+
"sqlite",
|
|
41
|
+
"observability"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"author": "ScreamingMaggot",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/ScreamingMaggot/rcs-context.git"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/ScreamingMaggot/rcs-context#readme",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/ScreamingMaggot/rcs-context/issues"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"dsh": ">=0.1.5-rc.2 <0.1.6-0 || >=0.1.7-rc.2 <0.2.0-0 || >=0.2.0-rc.2 <0.2.1-0 || >=0.2.1-rc.2 <0.2.2-0 || >=0.2.2-rc.1 <0.3.0-0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@deepseek-ai/dsh": ">=0.1.5-rc.2 <0.1.6-0 || >=0.1.7-rc.2 <0.2.0-0 || >=0.2.0-rc.2 <0.2.1-0 || >=0.2.1-rc.2 <0.2.2-0 || >=0.2.2-rc.1 <0.3.0-0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@deepseek-ai/dsh": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"dsh": {
|
|
65
|
+
"bundle": {
|
|
66
|
+
"patch": "./cordis.patch.yml"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|