@aiwayds/dsh-dcp 0.5.0 → 0.5.1
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/lib/index.js +60 -55
- package/package.json +37 -13
package/lib/index.js
CHANGED
|
@@ -39,6 +39,28 @@ import { registerDcpCommand } from './command.js'
|
|
|
39
39
|
const require = createRequire(import.meta.url)
|
|
40
40
|
const { version: VERSION } = require('../package.json')
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Per-engine state uses module-scoped symbol keys, never `#private` members.
|
|
44
|
+
* Cordis hands services to consuming fibers through derived receivers —
|
|
45
|
+
* `ctx.mixin` binds methods to a withProps proxy and `createTraceable` calls
|
|
46
|
+
* them on Object.create-derived shadows — so `/compact` and `/dcp compact`
|
|
47
|
+
* (both `ctx.compaction.compactNow(...)`) routinely execute engine methods
|
|
48
|
+
* with a `this` this class's constructor never initialized. Private members
|
|
49
|
+
* brand-check against exactly that and throw
|
|
50
|
+
* "Cannot read private member #triggerLabels from an object whose class did
|
|
51
|
+
* not declare it", while symbol lookups traverse those receivers to this
|
|
52
|
+
* instance's own state.
|
|
53
|
+
*/
|
|
54
|
+
const kRounds = Symbol('dsh-dcp.rounds')
|
|
55
|
+
const kTriggerLabels = Symbol('dsh-dcp.triggerLabels')
|
|
56
|
+
const kRoundInFlight = Symbol('dsh-dcp.roundInFlight')
|
|
57
|
+
const kSessionStats = Symbol('dsh-dcp.sessionStats')
|
|
58
|
+
const kRegisterRoundTrigger = Symbol('dsh-dcp.registerRoundTrigger')
|
|
59
|
+
const kMaybeRoundCompact = Symbol('dsh-dcp.maybeRoundCompact')
|
|
60
|
+
const kRecordStats = Symbol('dsh-dcp.recordStats')
|
|
61
|
+
const kRecordSessionStats = Symbol('dsh-dcp.recordSessionStats')
|
|
62
|
+
const kAppendNotice = Symbol('dsh-dcp.appendNotice')
|
|
63
|
+
|
|
42
64
|
/**
|
|
43
65
|
* Deterministic compaction engine: `summarize()` overridden, everything else
|
|
44
66
|
* inherited. Registers the `/dcp` command beside the inherited `/compact`.
|
|
@@ -94,38 +116,20 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
94
116
|
/** Absolute module path, echoed by `/dcp set` for persistence snippets. */
|
|
95
117
|
pluginPath
|
|
96
118
|
|
|
97
|
-
/**
|
|
98
|
-
* Completed-assistant-message counters since the last dsh-dcp compaction,
|
|
99
|
-
* per session — one "round" is one `assistant/message` (one LLM roundtrip).
|
|
100
|
-
* Weak keys: disposed sessions (including one-shot subagents) drop out with
|
|
101
|
-
* the object.
|
|
102
|
-
*/
|
|
103
|
-
#rounds = new WeakMap()
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Sessions whose next `compactNow` is a round-interval trigger rather than
|
|
107
|
-
* a manual command. Only {@link DcpEngine.#maybeRoundCompact} writes;
|
|
108
|
-
* `compactNow` reads and clears its own entry, so a stale marker can only
|
|
109
|
-
* turn a manual compaction into a labeled `'round'` one — never the reverse.
|
|
110
|
-
*/
|
|
111
|
-
#triggerLabels = new WeakMap()
|
|
112
|
-
|
|
113
|
-
/** Sessions with a round-triggered compaction still in flight. */
|
|
114
|
-
#roundInFlight = new WeakSet()
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Per-session compaction records: one entry per session that committed at
|
|
118
|
-
* least one compaction, counting the compactions and the shadowed tokens.
|
|
119
|
-
* Weak keys (mirroring `#rounds`): disposed sessions — including one-shot
|
|
120
|
-
* subagents — drop out with the object. Not enumerated directly, because a
|
|
121
|
-
* WeakMap leaks nothing and lists nothing; `/dcp` walks the sessions
|
|
122
|
-
* service and looks each live session up here.
|
|
123
|
-
*/
|
|
124
|
-
#sessionStats = new WeakMap()
|
|
125
|
-
|
|
126
119
|
constructor(ctx, config = {}) {
|
|
127
120
|
const { basic, dcp } = splitConfig(config)
|
|
128
121
|
super(ctx, basic)
|
|
122
|
+
// Constructor assignments, not class fields: Node 26's V8 silently drops
|
|
123
|
+
// every symbol-keyed class field after the first one in a derived class
|
|
124
|
+
// (plain string-keyed and base-class fields are unaffected).
|
|
125
|
+
/** Per-session assistant-message counters since the last dsh-dcp compaction. */
|
|
126
|
+
this[kRounds] = new WeakMap()
|
|
127
|
+
/** Sessions whose next `compactNow` is a round-interval trigger, not manual. */
|
|
128
|
+
this[kTriggerLabels] = new WeakMap()
|
|
129
|
+
/** Sessions with a round-triggered compaction still in flight. */
|
|
130
|
+
this[kRoundInFlight] = new WeakSet()
|
|
131
|
+
/** Per-session compaction records for `/dcp` (weak so disposed sessions drop out). */
|
|
132
|
+
this[kSessionStats] = new WeakMap()
|
|
129
133
|
this.dcp = { ...resolveDcpConfig(dcp) }
|
|
130
134
|
this.dcpStats = { compactions: 0, shadowedTokens: 0, lastAt: null }
|
|
131
135
|
this.pluginPath = fileURLToPath(import.meta.url)
|
|
@@ -133,7 +137,7 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
133
137
|
ctx.effect(function* () {
|
|
134
138
|
yield registerDcpCommand(ctx, engine, VERSION)
|
|
135
139
|
}, 'dsh-dcp /dcp command lifecycle')
|
|
136
|
-
if (this.config.auto) this
|
|
140
|
+
if (this.config.auto) this[kRegisterRoundTrigger]()
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
/**
|
|
@@ -145,7 +149,7 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
145
149
|
* like the top-level session — and because one-shot subagents emit many
|
|
146
150
|
* assistant messages inside a single turn, they now trigger too.
|
|
147
151
|
*/
|
|
148
|
-
|
|
152
|
+
[kRegisterRoundTrigger]() {
|
|
149
153
|
const { ctx } = this
|
|
150
154
|
ctx.on('session/event', (session, event) => {
|
|
151
155
|
// Counting is skipped while disabled, but the listener stays registered
|
|
@@ -155,10 +159,10 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
155
159
|
// counting it (instead of completed turns) also covers one-shot
|
|
156
160
|
// subagents, whose whole run is a single turn with many model calls.
|
|
157
161
|
if (event.type !== 'assistant/message') return
|
|
158
|
-
this
|
|
162
|
+
this[kRounds].set(session, (this[kRounds].get(session) ?? 0) + 1)
|
|
159
163
|
})
|
|
160
164
|
ctx.on('agent/status', ({ agent, status }) => {
|
|
161
|
-
if (status === 'idle') this
|
|
165
|
+
if (status === 'idle') this[kMaybeRoundCompact](agent)
|
|
162
166
|
})
|
|
163
167
|
}
|
|
164
168
|
|
|
@@ -170,17 +174,17 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
170
174
|
* must not cancel the N assistant messages behind it. Any other failure warns
|
|
171
175
|
* and releases the boundary: the pressure trigger remains the safety net.
|
|
172
176
|
*/
|
|
173
|
-
|
|
177
|
+
[kMaybeRoundCompact](agent) {
|
|
174
178
|
const interval = this.dcp.roundInterval
|
|
175
179
|
if (!interval) return
|
|
176
180
|
const session = agent?.session
|
|
177
|
-
if (session === undefined || this
|
|
178
|
-
if ((this
|
|
179
|
-
this
|
|
180
|
-
this
|
|
181
|
-
const settle = () => this
|
|
181
|
+
if (session === undefined || this[kRoundInFlight].has(session)) return
|
|
182
|
+
if ((this[kRounds].get(session) ?? 0) < interval) return
|
|
183
|
+
this[kTriggerLabels].set(session, 'round')
|
|
184
|
+
this[kRoundInFlight].add(session)
|
|
185
|
+
const settle = () => this[kRoundInFlight].delete(session)
|
|
182
186
|
const consume = () => {
|
|
183
|
-
this
|
|
187
|
+
this[kRounds].delete(session)
|
|
184
188
|
settle()
|
|
185
189
|
}
|
|
186
190
|
void this.compactNow(agent, new AbortController().signal).then(consume, (error) => {
|
|
@@ -217,7 +221,7 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
217
221
|
async compactIfNeeded(agent, trigger, signal) {
|
|
218
222
|
const label = trigger === 'context-overflow' ? 'overflow' : 'auto'
|
|
219
223
|
const result = await super.compactIfNeeded(agent, trigger, signal)
|
|
220
|
-
if (result !== null) this
|
|
224
|
+
if (result !== null) this[kAppendNotice](agent.session, result, label)
|
|
221
225
|
return result
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -229,25 +233,25 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
229
233
|
*/
|
|
230
234
|
async compactNow(agent, signal, sourceCommandId) {
|
|
231
235
|
const session = agent.session
|
|
232
|
-
const trigger = this
|
|
236
|
+
const trigger = this[kTriggerLabels].get(session) === 'round' ? 'round' : 'manual'
|
|
233
237
|
try {
|
|
234
238
|
const result = await super.compactNow(agent, signal, sourceCommandId)
|
|
235
239
|
// `null` means no useful range existed: release the round counter so an
|
|
236
240
|
// early-session interval boundary cannot retry every idle boundary.
|
|
237
241
|
// Manual compactions share the release: a user-driven compact restarts
|
|
238
242
|
// interval counting whether or not it found anything to compact.
|
|
239
|
-
if (result === null) this
|
|
243
|
+
if (result === null) this[kRounds].delete(session)
|
|
240
244
|
else this.recordCompaction(session, result, trigger)
|
|
241
245
|
return result
|
|
242
246
|
} finally {
|
|
243
|
-
this
|
|
247
|
+
this[kTriggerLabels].delete(session)
|
|
244
248
|
}
|
|
245
249
|
}
|
|
246
250
|
|
|
247
251
|
/** Stats and round-counter restart for every committed region. */
|
|
248
252
|
async compactRegion(start, end, agent, signal) {
|
|
249
253
|
const result = await super.compactRegion(start, end, agent, signal)
|
|
250
|
-
this
|
|
254
|
+
this[kRecordStats](agent.session, result)
|
|
251
255
|
return result
|
|
252
256
|
}
|
|
253
257
|
|
|
@@ -255,12 +259,12 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
255
259
|
* Record one committed compaction: bump the `/dcp` counters and restart the
|
|
256
260
|
* round-interval counting.
|
|
257
261
|
*/
|
|
258
|
-
|
|
262
|
+
[kRecordStats](session, result) {
|
|
259
263
|
this.dcpStats.compactions += 1
|
|
260
264
|
this.dcpStats.shadowedTokens += result.shadowedTokenCount
|
|
261
265
|
this.dcpStats.lastAt = Date.now()
|
|
262
|
-
this
|
|
263
|
-
this
|
|
266
|
+
this[kRounds].delete(session)
|
|
267
|
+
this[kRecordSessionStats](session, result.shadowedTokenCount)
|
|
264
268
|
}
|
|
265
269
|
|
|
266
270
|
/**
|
|
@@ -268,12 +272,12 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
268
272
|
* must be objects, so a session-less recording (should not happen) is
|
|
269
273
|
* skipped rather than thrown on.
|
|
270
274
|
*/
|
|
271
|
-
|
|
275
|
+
[kRecordSessionStats](session, shadowedTokenCount) {
|
|
272
276
|
if (session === undefined || session === null || typeof session !== 'object') return
|
|
273
|
-
const entry = this
|
|
277
|
+
const entry = this[kSessionStats].get(session) ?? { compactions: 0, shadowedTokens: 0 }
|
|
274
278
|
entry.compactions += 1
|
|
275
279
|
entry.shadowedTokens += shadowedTokenCount
|
|
276
|
-
this
|
|
280
|
+
this[kSessionStats].set(session, entry)
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
/**
|
|
@@ -281,7 +285,7 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
281
285
|
* is a `notice`-form plugin message, so every dsh frontend renders it as a
|
|
282
286
|
* collapsed transcript row, live and on replay.
|
|
283
287
|
*/
|
|
284
|
-
|
|
288
|
+
[kAppendNotice](session, result, trigger) {
|
|
285
289
|
if (!this.dcp.notice) return
|
|
286
290
|
const summary = boundContextSummary(noticeText(this.dcp.language, result.shadowedSeqs.length, result.shadowedTokenCount, trigger))
|
|
287
291
|
try {
|
|
@@ -303,10 +307,11 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
303
307
|
*
|
|
304
308
|
* Internal seam: `compactNow` calls it after its durable commit; tests
|
|
305
309
|
* drive it directly instead of mocking the upstream region machinery.
|
|
310
|
+
* Public-named but not part of the dsh compaction contract.
|
|
306
311
|
*/
|
|
307
312
|
recordCompaction(session, result, trigger) {
|
|
308
|
-
this
|
|
309
|
-
this
|
|
313
|
+
this[kRecordStats](session, result)
|
|
314
|
+
this[kAppendNotice](session, result, trigger)
|
|
310
315
|
}
|
|
311
316
|
|
|
312
317
|
/**
|
|
@@ -323,7 +328,7 @@ export class DcpEngine extends BasicCompactionEngine {
|
|
|
323
328
|
// Services live on ctx for a cordis plugin instance (`inject` only
|
|
324
329
|
// declares them); this.sessions is undefined in production.
|
|
325
330
|
for (const session of this.ctx.sessions?.list?.() ?? []) {
|
|
326
|
-
const entry = this
|
|
331
|
+
const entry = this[kSessionStats].get(session)
|
|
327
332
|
if (entry === undefined) continue
|
|
328
333
|
overview.push({
|
|
329
334
|
id: session.header?.id ?? '<unknown>',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwayds/dsh-dcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Deterministic context-pruning compaction backend for dsh (DeepSeek Harness) — zero-LLM summaries, /dcp command, works out of the box. Design references Opencode-DCP/opencode-dynamic-context-pruning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -62,18 +62,42 @@
|
|
|
62
62
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
|
-
"@deepseek-ai/cordis": {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"@deepseek-ai/dsh-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
"@deepseek-ai/cordis": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@deepseek-ai/dsh-agent": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@deepseek-ai/dsh-brand": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@deepseek-ai/dsh-commands": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@deepseek-ai/dsh-compaction": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"@deepseek-ai/dsh-compaction-basic": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"@deepseek-ai/dsh-compaction-tool-result-pruner": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"@deepseek-ai/dsh-invariants": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"@deepseek-ai/dsh-llm": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"@deepseek-ai/dsh-session": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"@deepseek-ai/dsh-token-meter": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"@deepseek-ai/schemastery": {
|
|
99
|
+
"optional": true
|
|
100
|
+
}
|
|
77
101
|
},
|
|
78
102
|
"devDependencies": {
|
|
79
103
|
"@deepseek-ai/cordis": "^4.0.1",
|