dsh-advisor 0.1.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +7 -0
  3. package/README.md +303 -0
  4. package/README.zh.md +166 -0
  5. package/cordis.patch.yml +6 -0
  6. package/lib/advisor-runtime.d.ts +242 -0
  7. package/lib/advisor-runtime.js +662 -0
  8. package/lib/advisor-runtime.js.map +1 -0
  9. package/lib/client/advisor-card.d.ts +90 -0
  10. package/lib/client/advisor-store.d.ts +310 -0
  11. package/lib/client/index.d.ts +39 -0
  12. package/lib/client/locales.d.ts +40 -0
  13. package/lib/client.d.ts +1 -0
  14. package/lib/client.js +840 -0
  15. package/lib/commands.d.ts +136 -0
  16. package/lib/commands.js +185 -0
  17. package/lib/commands.js.map +1 -0
  18. package/lib/config.d.ts +74 -0
  19. package/lib/config.js +93 -0
  20. package/lib/config.js.map +1 -0
  21. package/lib/delivery.d.ts +129 -0
  22. package/lib/delivery.js +169 -0
  23. package/lib/delivery.js.map +1 -0
  24. package/lib/emission-guard.d.ts +99 -0
  25. package/lib/emission-guard.js +155 -0
  26. package/lib/emission-guard.js.map +1 -0
  27. package/lib/gateway.d.ts +116 -0
  28. package/lib/gateway.js +214 -0
  29. package/lib/gateway.js.map +1 -0
  30. package/lib/index.d.ts +48 -0
  31. package/lib/index.js +485 -0
  32. package/lib/index.js.map +1 -0
  33. package/lib/kinds.d.ts +38 -0
  34. package/lib/kinds.js +24 -0
  35. package/lib/kinds.js.map +1 -0
  36. package/lib/prompts.d.ts +22 -0
  37. package/lib/prompts.js +38 -0
  38. package/lib/prompts.js.map +1 -0
  39. package/lib/settings.d.ts +96 -0
  40. package/lib/settings.js +141 -0
  41. package/lib/settings.js.map +1 -0
  42. package/lib/transcript.d.ts +257 -0
  43. package/lib/transcript.js +530 -0
  44. package/lib/transcript.js.map +1 -0
  45. package/package.json +90 -0
  46. package/scripts/build-client.mjs +268 -0
package/lib/index.js ADDED
@@ -0,0 +1,485 @@
1
+ /**
2
+ * dsh advisor plugin — a per-session reviewer model (port of the omp
3
+ * "advisor" subsystem). Observes the primary transcript, reviews each stepped
4
+ * turn with an explicitly configured model, and injects severity-ranked
5
+ * advice (nit/concern/blocker) without polluting or recursively reviewing
6
+ * itself.
7
+ *
8
+ * T1 scaffold: declares the Cordis plugin entry (`name`/`inject`/`apply`).
9
+ * T2: config contract — the Loader schema (`Config`) plus the explicit
10
+ * provider/model gate via `resolveAdvisorConfig` (spec §5 / S4).
11
+ * T3: session observation — subscribe `session/event`, detect stepped
12
+ * `turn/end` (reason.kind ∈ {completed, 'max-tokens', error}, spec §4), drive
13
+ * a per-session bounded delta renderer, and dispose per-session state on
14
+ * `session/disposed` / `agent/disposed` (KD-5).
15
+ * T4: the per-session advisor runtime — queue each rendered delta, drain
16
+ * asynchronously via `ctx.llm.stream` (system prompt + delta, `purpose` left
17
+ * unset, KD-5), extract `{note, severity}` (KD-2), and apply the failure
18
+ * policy (retry-light → drop, 3-drop backlog flush, quota pause, permanent
19
+ * halt — never park the primary). The emission guard (T5) gates extracted
20
+ * notes before delivery; the delivery router (T6) routes accepted notes into
21
+ * the primary agent (nit → inject, concern/blocker → steer, immuneTurns
22
+ * cooldown, KD-4 agent map). T7: `/advisor` toggle/on/off/status commands
23
+ * (registered through the conditional `ctx.inject(['commands'], ...)` child)
24
+ * drive a per-session override consulted by the runtime gate — the commands
25
+ * start/stop per-session runtimes without touching the persisted config.
26
+ * Settings (plan dsh-advisor-settings-n2): the plugin-row config is the
27
+ * composition base of the `advisor` settings namespace (`src/settings.ts`),
28
+ * read live through the bridge source; committed settings edits re-apply
29
+ * derived state (immuneTurns / maxDeltaMessages / per-session runtimes)
30
+ * without a restart.
31
+ * Config gateway (plan dsh-advisor-settings-gateway-n5): `apply` also
32
+ * registers the host-side `AdvisorConfigGateway` (`src/gateway.ts`) — the
33
+ * `/api/advisor/get` + `/api/advisor/set` endpoints (explicit
34
+ * `ctx.typert.register` contribution — the host typertGateway claims
35
+ * `ctx.typert.local` first, so link-plugin module identity never matters)
36
+ * that make the Settings card truly readable/writable from the web client.
37
+ *
38
+ * @module dsh-advisor
39
+ */
40
+ import { resolveAdvisorConfig } from './config.js';
41
+ import { installAdvisorSettings } from './settings.js';
42
+ import { AdvisorConfigGateway, advisorTypertContribution } from './gateway.js';
43
+ import { SessionTranscriptObserver } from './transcript.js';
44
+ import { AdvisorRuntime } from './advisor-runtime.js';
45
+ import { AdvisorDelivery } from './delivery.js';
46
+ import { DEFAULT_ADVISOR_SYSTEM_PROMPT } from './prompts.js';
47
+ import { AdvisorSessionOverrides, registerAdvisorCommands } from './commands.js';
48
+ export const name = 'dsh-advisor';
49
+ /** Services the advisor consumes; the row loads once all are available. */
50
+ export const inject = ['sessions', 'agents', 'llm'];
51
+ /** Loader schema (schemastery, strict) — validated by the cordis Loader. */
52
+ export { Config } from './config.js';
53
+ /** n4 QC F-6: single-reviewer guard. The host composes multiple dsh-advisor
54
+ * fibers (observed: 3 active); with the global session/event subscription every
55
+ * instance would observe every session and N×-review/N×-call per round. The
56
+ * FIRST apply to claim the reviewer role wires the observer/runtime/delivery
57
+ * and the /advisor commands; later instances attempt the settings registration
58
+ * only (qc1 W-5: it is NOT idempotent — dsh-settings register throws on a
59
+ * duplicate; installAdvisorSettings dedupes it and falls back to the entry
60
+ * source, so the first registration owns the live namespace) and stay inert
61
+ * otherwise. The claim is taken only AFTER the construction-time config gate
62
+ * (qc1 W-4: a rejected first row never leaves the flag claimed) and is
63
+ * released when the claiming fiber is disposed, so a later re-apply/re-mount
64
+ * can take over. The flag rides globalThis so it survives even module-copy
65
+ * divergence. */
66
+ const REVIEWER_KEY = '__dshAdvisorReviewer__';
67
+ function claimReviewer() {
68
+ const g = globalThis;
69
+ if (g[REVIEWER_KEY] === true)
70
+ return false;
71
+ g[REVIEWER_KEY] = true;
72
+ return true;
73
+ }
74
+ export function apply(ctx, config) {
75
+ // T2: the explicit provider/model gate — no model call without both
76
+ // (spec §5.2). Unknown keys / malformed config throw here, rejecting the
77
+ // plugin row at load; the gate resolves to disabled-with-reason instead.
78
+ //
79
+ // T1-settings (plan dsh-advisor-settings-n2): the plugin-row config is the
80
+ // composition BASE of the `advisor` settings namespace. The runtime reads
81
+ // the LIVE composed value through the bridge source (schema defaults →
82
+ // base → settings user layer); with no settings service the source is
83
+ // exactly `config` — behavior identical to today. The hard gate is applied
84
+ // to every read: `resolveAdvisorConfig` stays the SSOT for the
85
+ // disabled-with-reason resolution.
86
+ const bridge = installAdvisorSettings(ctx, config);
87
+ // n5 (plan dsh-advisor-settings-gateway-n5): the host-side `advisor` config
88
+ // gateway — the `/api/advisor/get` + `/api/advisor/set` endpoints. The
89
+ // endpoints are registered EXPLICITLY through `ctx.typert.register(...)`
90
+ // (NOT the @Remote SRC markers): the host typertGateway checks
91
+ // `ctx.typert.local` FIRST for claim + dispatch, while SRC discovery reads
92
+ // a module-private marker table that a locally-linked plugin can never
93
+ // share with the host installation (link plugins resolve their peers from
94
+ // their real directory, physically separate from the dlx host tree — the
95
+ // observed failure was zero claimed endpoints → `/api/advisor/*` 404).
96
+ // It reads the SAME bridge the runtime reads, so get/set always operate on
97
+ // the live composed config. Multi-fiber dedupe mirrors the settings
98
+ // registration (qc1 W-5): the cordis Service registration fails loud on a
99
+ // duplicate key, so the catch lets the FIRST fiber own the `advisor` service
100
+ // key while later fibers fall back (no gateway) — a later fiber's typert
101
+ // re-registration fails the same way (`already registered`). The
102
+ // registrations are fiber effects (unregistered when this fiber disposes),
103
+ // so a re-apply/re-mount can take over. The instance needs no handle here:
104
+ // the typertGateway dispatches through `ctx.get('advisor')`.
105
+ try {
106
+ new AdvisorConfigGateway(ctx, bridge);
107
+ }
108
+ catch (error) {
109
+ if (!(error instanceof Error) || !error.message.includes('has been registered'))
110
+ throw error;
111
+ ctx.logger('advisor').debug('advisor gateway already registered — no gateway on this fiber (multi-fiber dedupe)');
112
+ }
113
+ // The typert endpoint registration is OPTIONAL, like the settings service:
114
+ // it activates through a conditional inject child, so compositions without
115
+ // a typert registry (headless/standalone/integration harnesses) keep the
116
+ // advisor runtime working and simply omit the /api endpoints. The child
117
+ // disposer is the registration's own effect disposer, so the endpoints
118
+ // withdraw when this fiber (or the typert service) goes away.
119
+ ctx.inject(['typert'], (tctx) => {
120
+ try {
121
+ return tctx.typert.register(advisorTypertContribution());
122
+ }
123
+ catch (error) {
124
+ if (!(error instanceof Error) || !error.message.includes('already registered'))
125
+ throw error;
126
+ tctx.logger('advisor').debug('advisor typert endpoints already registered — no endpoints on this fiber (multi-fiber dedupe)');
127
+ return () => { };
128
+ }
129
+ });
130
+ const sourceConfig = () => bridge.source();
131
+ const resolved = () => resolveAdvisorConfig(sourceConfig());
132
+ // qc2 W-1 containment: a settings user layer the resolver rejects (e.g. an
133
+ // unknown key survives the non-strict settings schema) must never wedge the
134
+ // live hot path — every read that can run inside an event handler goes
135
+ // through the safe wrappers, which catch resolver throws and return
136
+ // disabled-with-reason carrying the message, so gate semantics hold (no
137
+ // model call can start) and handlers stay functional. The LOAD-TIME
138
+ // plugin-row throw contract is unchanged: construction-time reads below
139
+ // (delivery/observer latches) still use the throwing `resolved()`, so a bad
140
+ // entry rejects the plugin row at load (config.test.ts ⑤).
141
+ const safeFallback = (reason) => ({
142
+ enabled: false,
143
+ systemPrompt: '',
144
+ immuneTurns: 3,
145
+ maxDeltaMessages: 60,
146
+ disabledReason: reason,
147
+ });
148
+ const safeResolved = () => {
149
+ try {
150
+ return resolveAdvisorConfig(sourceConfig());
151
+ }
152
+ catch (error) {
153
+ return safeFallback(error instanceof Error ? error.message : String(error));
154
+ }
155
+ };
156
+ const safeEffective = (sessionId) => {
157
+ try {
158
+ return resolveAdvisorConfig({ ...sourceConfig(), enabled: effectiveEnabled(sessionId) });
159
+ }
160
+ catch (error) {
161
+ return safeFallback(error instanceof Error ? error.message : String(error));
162
+ }
163
+ };
164
+ ctx.logger('advisor').debug('dsh-advisor loaded', {
165
+ enabled: safeResolved().enabled,
166
+ disabledReason: safeResolved().disabledReason,
167
+ });
168
+ // T7: the per-session override mechanism — `/advisor on|off|toggle` write
169
+ // here and the runtime gate consults `override ?? config.enabled`, so the
170
+ // commands start/stop per-session runtimes WITHOUT touching the persisted
171
+ // config (spec §4 mapping — omp `/advisor` semantics). Ephemeral: entries
172
+ // are cleared on `agent/disposed` / `session/disposed` below. Seeded with
173
+ // the RAW config switch (not the post-gate `resolved.enabled`): a config-
174
+ // enabled-but-gate-blocked session (enabled without provider/model) then
175
+ // re-derives the disabled-with-reason through the resolver, so `/advisor
176
+ // status` shows the reason (spec §5.2; qc3 I-1) — the gate itself still
177
+ // blocks every runtime (the resolver is the SSOT for the gate).
178
+ const overrides = new AdvisorSessionOverrides(config.enabled);
179
+ const effectiveEnabled = (sessionId) => overrides.effective(sessionId);
180
+ // Live-path alias: every consumer reads the effective config through the
181
+ // safe wrapper (qc2 W-1 — a throwing resolver must not break the
182
+ // session/event handler or `/advisor status`).
183
+ const effectiveConfig = (sessionId) => safeEffective(sessionId);
184
+ // T3+T4: per-session transcript observation wired into one advisor runtime
185
+ // per session. On each stepped reviewable turn/end a bounded markdown delta
186
+ // is rendered and queued on the session's runtime; the runtime drains it
187
+ // asynchronously through `ctx.llm.stream`, gates the extracted `AdviceNote`
188
+ // through the T5 emission guard (inside the runtime, between extraction and
189
+ // delivery), and hands accepted notes to `onNote` (T6 routes them).
190
+ // T6: the delivery router. Owns the KD-4 per-session agent map (keyed by
191
+ // agent.id === session.id, maintained on agent/created / agent/disposed
192
+ // below), the severity → channel mapping (nit → inject; concern/blocker →
193
+ // steer), and the immuneTurns cooldown (spec §6). Accepted notes from the
194
+ // runtime's onNote are routed here; missing agent → drop + log (KD-4).
195
+ const delivery = new AdvisorDelivery({
196
+ immuneTurns: resolved().immuneTurns,
197
+ // Registry fallback (KD-4): covers agents published before this plugin
198
+ // loaded, whose `agent/created` was never observed. The delivery module is
199
+ // session-id-string-typed; the registry key is the branded SessionId.
200
+ lookupAgent: (sessionId) => ctx.agents.get(sessionId),
201
+ logger: ctx.logger('advisor'),
202
+ });
203
+ const runtimes = new Map();
204
+ /**
205
+ * Runtime-affecting signature per session — the values that pin one
206
+ * {@link AdvisorRuntime}: the effective switch, the post-gate enable (S4),
207
+ * and the {provider, model, systemPrompt} triple. Recorded at runtime
208
+ * creation and compared on every settings change (qc3 W-1 / qc1 W-2): only
209
+ * a signature change tears the runtime down — an immuneTurns/
210
+ * maxDeltaMessages-only edit updates the latches in place and keeps every
211
+ * in-flight call and backlog.
212
+ */
213
+ const runtimeSignatures = new Map();
214
+ const runtimeSignature = (sessionId) => {
215
+ const effective = safeEffective(sessionId);
216
+ return [
217
+ effectiveEnabled(sessionId) ? 1 : 0,
218
+ effective.enabled ? 1 : 0,
219
+ effective.provider ?? '',
220
+ effective.model ?? '',
221
+ effective.systemPrompt,
222
+ ].join('\u0000');
223
+ };
224
+ /**
225
+ * Create (or return) the runtime for one session, gated on the effective
226
+ * switch: `undefined` when the session is disabled or the S4 explicit gate
227
+ * blocks model calls (effective enabled without provider/model — spec
228
+ * §5.2). T7's `/advisor on` turns a session on without a config change by
229
+ * flipping the override, which this gate reads.
230
+ */
231
+ const ensureRuntime = (sessionId) => {
232
+ if (!effectiveEnabled(sessionId))
233
+ return undefined;
234
+ let runtime = runtimes.get(sessionId);
235
+ if (runtime !== undefined)
236
+ return runtime;
237
+ const effective = effectiveConfig(sessionId);
238
+ // The re-resolved config guarantees provider + model when enabled — the
239
+ // runtime is only constructed behind the gate.
240
+ if (!effective.enabled)
241
+ return undefined;
242
+ runtime = new AdvisorRuntime({
243
+ provider: effective.provider,
244
+ model: effective.model,
245
+ systemPrompt: safeResolved().systemPrompt || DEFAULT_ADVISOR_SYSTEM_PROMPT,
246
+ // n4 root-cause (host-observed NO_ADAPTER): this plugin's ctx may live in
247
+ // an isolated scope whose local llm service lacks the provider adapters
248
+ // (adapter registrations live on the application root's LlmRuntime). Resolve
249
+ // the llm service from the APPLICATION ROOT so the advisor's model calls
250
+ // reach the registered deepseek-official adapter.
251
+ llm: ctx.root?.get?.('llm') ?? ctx.llm,
252
+ onNote: (note) => {
253
+ // Accepted notes only — the runtime's emission guard (T5) already
254
+ // filtered suppressed ones. T6 routes the accepted note to the primary
255
+ // agent (inject/steer); delivery throws stay contained in the runtime
256
+ // path (T4 F1), so a failing agent can only drop its own advice.
257
+ const channel = delivery.route(sessionId, note);
258
+ ctx.logger('advisor').debug('advice note delivered', {
259
+ sessionId,
260
+ severity: note.severity,
261
+ channel,
262
+ });
263
+ },
264
+ });
265
+ runtimes.set(sessionId, runtime);
266
+ runtimeSignatures.set(sessionId, runtimeSignature(sessionId));
267
+ return runtime;
268
+ };
269
+ const disposeRuntime = (sessionId) => {
270
+ const runtime = runtimes.get(sessionId);
271
+ if (runtime === undefined)
272
+ return;
273
+ runtimes.delete(sessionId);
274
+ runtimeSignatures.delete(sessionId);
275
+ runtime.dispose();
276
+ };
277
+ // n4 QC F-6: claim the single-reviewer role HERE — after every
278
+ // construction-time throwing read (the delivery latch above resolves the
279
+ // config and can throw on a rejected row), so a first fiber whose config
280
+ // fails the gate never leaves the flag claimed (qc1 W-4). Non-reviewer
281
+ // instances stop here — observer/runtime/delivery and the /advisor commands
282
+ // are wired only by the single claimed reviewer. The settings registration
283
+ // (installAdvisorSettings above) already ran deduped (qc1 W-5): the first
284
+ // registration owns the live namespace on every composition.
285
+ const reviewer = claimReviewer();
286
+ if (!reviewer) {
287
+ ctx.logger('advisor').debug('non-reviewer instance — observer/runtime/commands skipped (single-reviewer guard)');
288
+ return;
289
+ }
290
+ // qc1 W-4: release the claim when THIS (reviewer) fiber is disposed, so a
291
+ // later re-apply/re-mount (plugin-row removal, composition reload, host hot
292
+ // reload) can take over instead of leaving the advisor silently inert for
293
+ // the process lifetime. Registered only on the claiming fiber.
294
+ ctx.effect(() => () => {
295
+ delete globalThis[REVIEWER_KEY];
296
+ }, 'advisor: release reviewer claim');
297
+ const observer = new SessionTranscriptObserver({
298
+ maxDeltaMessages: resolved().maxDeltaMessages,
299
+ onSteppedTurnEnd: (sessionId) => {
300
+ // One completed stepped primary turn — decrement the immuneTurns
301
+ // cooldown (T6, spec §6). Fires before the delta render, so the note
302
+ // this very turn produces is routed with the decremented cooldown.
303
+ delivery.onSteppedTurnEnd(sessionId);
304
+ },
305
+ onRewrite: (sessionId) => {
306
+ // KD-5: a compaction / surface rewrite resets the immuneTurns latch
307
+ // (delivery) AND the emission-guard dedupe history (runtime) — session
308
+ // state is being rewritten, so both latches' basis no longer applies.
309
+ // T8: guard reset wired through the runtime (T5 ⚠️ follow-through).
310
+ delivery.reset(sessionId);
311
+ runtimes.get(sessionId)?.resetGuard();
312
+ },
313
+ onDelta: (sessionId, delta) => {
314
+ // Lazy creation fallback covers agents that existed before this plugin
315
+ // loaded (their `agent/created` was never observed); `agent/created`
316
+ // below creates eagerly for the common path. The runtime gate drops the
317
+ // delta for sessions that are disabled or S4-gate-blocked (T7).
318
+ ensureRuntime(sessionId)?.enqueue(delta);
319
+ },
320
+ });
321
+ // `session/event` is scope-filtered: the dsh scope carrier sets a
322
+ // `[Context.filter]` on emitted events — untagged listeners pass, but a
323
+ // tagged listener only receives events whose carrier key is on its own
324
+ // key's ancestor chain (see `packages/core/scope/src/index.ts` scopeTarget).
325
+ // Cordis dispatch skips the filter for global hooks
326
+ // (`hook.global || !filter || filter.call(...)` — `cordis/src/events.ts`
327
+ // dispatch), and the plugin's instances may be composed in isolated scopes
328
+ // (dsh-advisor appears as multiple active fibers, e.g. 3), so `{ global:
329
+ // true }` is required for the observer to receive every session's events
330
+ // regardless of scope placement. Q2=grill-me-locked fix; verified by host
331
+ // test (PM operator step, evidence in iteration guides).
332
+ //
333
+ // The three lifecycle listeners below dispatch through the SAME
334
+ // scope-filtered carrier as `session/event` (dsh-session/dsh-agent
335
+ // `announce`/`emitDisposed`), so they must be `{ global: true }` too (qc3
336
+ // F4): a scoped fiber that observes out-of-scope sessions via the global
337
+ // session/event listener would otherwise create per-session state —
338
+ // renderers, runtimes, cooldowns, overrides — whose dispose events are
339
+ // filtered out, a per-session leak for the host's lifetime. Create/dispose
340
+ // symmetry restored; both dispose listeners are documented idempotent
341
+ // (a runtime is disposed at most once, whichever signal lands first).
342
+ ctx.on('session/event', (session, event) => {
343
+ observer.handleEvent(session.id, session.events, event);
344
+ }, { global: true });
345
+ // Per-session runtime lifecycle. Spec KD-5(c) pins `agent/disposed`;
346
+ // `session/disposed` is the store-level pair (both are idempotent — a
347
+ // runtime is disposed at most once, whichever signal lands first). `agent/created`
348
+ // creates the runtime eagerly (plan T4) when the session is enabled and
349
+ // registers the agent in the KD-4 delivery map (T6); the observer fallback
350
+ // covers pre-existing agents (KD-4-style robustness).
351
+ ctx.on('agent/created', ({ agent }) => {
352
+ ensureRuntime(agent.id);
353
+ delivery.registerAgent(agent);
354
+ }, { global: true });
355
+ ctx.on('agent/disposed', ({ agent }) => {
356
+ observer.disposeSession(agent.id);
357
+ disposeRuntime(agent.id);
358
+ delivery.unregisterAgent(agent.id);
359
+ overrides.clear(agent.id);
360
+ }, { global: true });
361
+ ctx.on('session/disposed', (session) => {
362
+ observer.disposeSession(session.id);
363
+ disposeRuntime(session.id);
364
+ delivery.unregisterAgent(session.id);
365
+ overrides.clear(session.id);
366
+ }, { global: true });
367
+ // T1-settings live re-apply: construction-time latches (immuneTurns on the
368
+ // delivery, maxDeltaMessages on the observer, systemPrompt + provider/model
369
+ // on each per-session runtime) are re-derived from the NEW source on every
370
+ // committed settings change and re-applied — delivery/observer update in
371
+ // place, per-session runtimes rebuild only when their runtime-affecting
372
+ // signature actually changed (qc3 W-1 / qc1 W-2: an immuneTurns/
373
+ // maxDeltaMessages-only edit must not abort in-flight advisor calls or drop
374
+ // backlogs). The S4 gate is re-applied by the resolver on every read, so a
375
+ // settings edit can never start a gated model call (SSOT unchanged); the
376
+ // config-level fallback switch follows the live source so new sessions pick
377
+ // up a Settings-page `enabled` edit immediately. A settings user layer the
378
+ // resolver rejects (qc2 W-1 — unknown key) stops the advisor without
379
+ // wedging the re-apply path, and the last-good latches stay until the
380
+ // config is repaired.
381
+ bridge.onChange(() => {
382
+ let next;
383
+ try {
384
+ next = resolveAdvisorConfig(sourceConfig());
385
+ }
386
+ catch (error) {
387
+ // The raw source is still readable for the switch even when the
388
+ // resolver rejects the composed value; if even that fails, keep the
389
+ // current config-level switch (the gate below still blocks runtimes).
390
+ try {
391
+ overrides.setConfigEnabled(sourceConfig().enabled);
392
+ }
393
+ catch {
394
+ // unreadable source — the effective switch stays as-is
395
+ }
396
+ for (const sessionId of [...runtimes.keys()])
397
+ disposeRuntime(sessionId);
398
+ ctx.logger('advisor').warn('settings change: invalid advisor config — advisor stopped', {
399
+ disabledReason: error instanceof Error ? error.message : String(error),
400
+ });
401
+ return;
402
+ }
403
+ delivery.setImmuneTurns(next.immuneTurns);
404
+ observer.setMaxDeltaMessages(next.maxDeltaMessages);
405
+ overrides.setConfigEnabled(sourceConfig().enabled);
406
+ let rebuilt = 0;
407
+ for (const sessionId of [...runtimes.keys()]) {
408
+ if (runtimeSignatures.get(sessionId) === runtimeSignature(sessionId))
409
+ continue;
410
+ disposeRuntime(sessionId);
411
+ ensureRuntime(sessionId);
412
+ rebuilt += 1;
413
+ }
414
+ if (rebuilt > 0) {
415
+ ctx.logger('advisor').debug('settings change rebuilt session runtimes', {
416
+ rebuilt,
417
+ provider: next.provider,
418
+ model: next.model,
419
+ });
420
+ }
421
+ });
422
+ // T7: the `/advisor` command controller — the commands' session-scoped
423
+ // operations against the observer/runtimes above. `/advisor on` seeds the
424
+ // observer cursor to the current transcript length (KD-5 seed-on-enable —
425
+ // no full-history replay) and creates/resumes/recoveries the session runtime;
426
+ // `/advisor off` disposes it (abort in-flight, drop backlog). The S4 gate
427
+ // reason is re-derived through the config resolver, the SSOT for the
428
+ // disabled-with-reason text (spec §5.2).
429
+ const controller = {
430
+ setEnabled(sessionId, enabled, sessionLength) {
431
+ // Recovery, not just a switch flip: `/advisor on` (and toggle-to-on)
432
+ // must restart a session advisor that is `quota_exhausted` (KD-5 —
433
+ // manual resume, no auto-resume timer) or `halted` (permanent model
434
+ // error — terminal in place, rebuilt fresh here) (qc1/qc2/qc3 W-1/I-4).
435
+ // The override is written only when the effective switch actually
436
+ // changes; enabling an already-effectively-enabled session still routes
437
+ // through the recovery path below instead of early-returning.
438
+ const already = effectiveEnabled(sessionId) === enabled;
439
+ if (!already)
440
+ overrides.set(sessionId, enabled);
441
+ if (!enabled) {
442
+ if (!already)
443
+ disposeRuntime(sessionId);
444
+ return;
445
+ }
446
+ // enabled (newly or already): KD-5 seed — no full-history replay of
447
+ // deltas that predate (or occurred while paused/halted under) the enable.
448
+ if (sessionLength !== undefined)
449
+ observer.seedTo(sessionId, sessionLength);
450
+ const runtime = ensureRuntime(sessionId);
451
+ if (runtime === undefined)
452
+ return; // S4 explicit gate blocks model calls
453
+ if (runtime.status() === 'halted') {
454
+ // KD-5 halting is terminal in place; `/advisor on` is the manual
455
+ // recovery path — rebuild a fresh runtime (the S4 gate is re-applied
456
+ // by ensureRuntime, so this can never start a gated model call).
457
+ disposeRuntime(sessionId);
458
+ ensureRuntime(sessionId);
459
+ }
460
+ else {
461
+ runtime.resume(); // no-op on 'running'; resumes 'quota_exhausted' (KD-5)
462
+ }
463
+ },
464
+ getStatus(sessionId) {
465
+ const runtime = runtimes.get(sessionId);
466
+ const effective = effectiveConfig(sessionId);
467
+ return {
468
+ enabled: effective.enabled,
469
+ ...(effective.disabledReason === undefined ? {} : { disabledReason: effective.disabledReason }),
470
+ provider: safeResolved().provider,
471
+ model: safeResolved().model,
472
+ runtimeStatus: runtime?.status() ?? 'disabled',
473
+ pendingCount: runtime?.pendingCount ?? 0,
474
+ lastActivityAt: runtime?.lastActivity,
475
+ };
476
+ },
477
+ };
478
+ // T7: the command child activates ONLY when a command registry is composed
479
+ // (conditional child activation — `commands` must NOT join the top-level
480
+ // `inject` list, T1 fix). `/advisor` toggle/on/off/status (spec §2 S5).
481
+ ctx.inject(['commands'], (commandCtx) => {
482
+ registerAdvisorCommands(commandCtx.commands, controller);
483
+ });
484
+ }
485
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/lib/kinds.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Advisor source kind + the `MessageSourceMap` merge extension (spec §6).
3
+ *
4
+ * Advisor-injected messages enter the session as user-role messages carrying
5
+ * `source.kind === 'advisor'` (via the plugin's `MessageSourceMap` merge
6
+ * declaration, `declare module '@deepseek-ai/dsh-llm'`). The delta renderer
7
+ * (T3) and the delivery router (T6) both key off this kind:
8
+ *
9
+ * - **Self-review exclusion (spec §6):** every advisor-source message is
10
+ * excluded from subsequent advisor deltas, so the advisor never reads its
11
+ * own injected advice back.
12
+ * - **Delivery tagging (T6):** `createUserMessage({ ..., source: { kind:
13
+ * ADVISOR_SOURCE_KIND } })` marks injected advice so it is visible in the
14
+ * session stream yet excluded from later deltas.
15
+ *
16
+ * @module dsh-advisor/kinds
17
+ */
18
+ import type { ContextFormed, Message } from '@deepseek-ai/dsh-llm';
19
+ declare module '@deepseek-ai/dsh-llm' {
20
+ interface MessageSourceMap {
21
+ /**
22
+ * An advisor-injected message (user-role, self-describing
23
+ * `[advisor:{severity}] {note}` content). Never derived into advisor
24
+ * deltas (self-review guard, spec §6). Extends {@link ContextFormed} so
25
+ * the plugin can declare the `notice` form + one-line `summary` the web
26
+ * shell renders on a collapsed context row.
27
+ */
28
+ advisor: {
29
+ readonly kind: 'advisor';
30
+ } & ContextFormed;
31
+ }
32
+ }
33
+ /** The `source.kind` value carried by every advisor-injected message. */
34
+ export declare const ADVISOR_SOURCE_KIND: "advisor";
35
+ /** Type of a message source carrying the advisor kind. */
36
+ export type AdvisorSourceKind = typeof ADVISOR_SOURCE_KIND;
37
+ /** True when a message was injected by the advisor itself. */
38
+ export declare function isAdvisorMessage(message: Message): boolean;
package/lib/kinds.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Advisor source kind + the `MessageSourceMap` merge extension (spec §6).
3
+ *
4
+ * Advisor-injected messages enter the session as user-role messages carrying
5
+ * `source.kind === 'advisor'` (via the plugin's `MessageSourceMap` merge
6
+ * declaration, `declare module '@deepseek-ai/dsh-llm'`). The delta renderer
7
+ * (T3) and the delivery router (T6) both key off this kind:
8
+ *
9
+ * - **Self-review exclusion (spec §6):** every advisor-source message is
10
+ * excluded from subsequent advisor deltas, so the advisor never reads its
11
+ * own injected advice back.
12
+ * - **Delivery tagging (T6):** `createUserMessage({ ..., source: { kind:
13
+ * ADVISOR_SOURCE_KIND } })` marks injected advice so it is visible in the
14
+ * session stream yet excluded from later deltas.
15
+ *
16
+ * @module dsh-advisor/kinds
17
+ */
18
+ /** The `source.kind` value carried by every advisor-injected message. */
19
+ export const ADVISOR_SOURCE_KIND = 'advisor';
20
+ /** True when a message was injected by the advisor itself. */
21
+ export function isAdvisorMessage(message) {
22
+ return message.source.kind === ADVISOR_SOURCE_KIND;
23
+ }
24
+ //# sourceMappingURL=kinds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kinds.js","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAiBH,yEAAyE;AACzE,MAAM,CAAC,MAAM,mBAAmB,GAAG,SAAkB,CAAA;AAKrD,8DAA8D;AAC9D,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,mBAAmB,CAAA;AACpD,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * dsh-advisor reviewer system prompt (spec §6, §8.2 KD-2).
3
+ *
4
+ * The advisor is an independent reviewer attached to a coding session: it
5
+ * receives an incremental markdown delta prefixed with `### Session update`
6
+ * (the T3 `DeltaRenderer` output) after each stepped primary turn and replies
7
+ * with exactly one JSON object `{"note", "severity"}`.
8
+ *
9
+ * The prompt pins the delivery contract KD-2 relies on:
10
+ * - the reviewer framing (independent, advisory-only — never approves actions);
11
+ * - the severity definitions from spec §6 (nit / concern / blocker);
12
+ * - the JSON-frame output contract: exactly one object, `severity` optional
13
+ * (omitted = nit), `note` non-empty, one note per update;
14
+ * - a valid "nothing to add" frame, which the T5 emission guard's content-free
15
+ * suppression filters out at delivery time.
16
+ *
17
+ * The extraction code tolerates prose/fences defensively, but the prompt asks
18
+ * for a bare frame so the happy path is a clean parse.
19
+ *
20
+ * @module dsh-advisor/prompts
21
+ */
22
+ export declare const DEFAULT_ADVISOR_SYSTEM_PROMPT = "You are an independent reviewer attached to a coding-agent session. You observe the primary agent's work and surface concise, severity-ranked advice. You are advisory only: you never approve or reject the agent's actions, and you never issue commands as if you were the primary agent.\n\nAfter each completed primary turn you receive an incremental transcript update prefixed with \"### Session update\" (a user message). Review only what changed in this update.\n\nSeverity levels:\n- nit: a minor style, clarity, or quality suggestion; no course change is needed.\n- concern: a material risk or a clearly better direction that the primary should weigh before continuing.\n- blocker: continuing clearly wastes work \u2014 the primary contradicts an explicit user instruction, is going in circles, or the approach is fundamentally unsound.\n\nReply with EXACTLY ONE JSON object and nothing else:\n{\"note\": \"<your note>\", \"severity\": \"nit\"|\"concern\"|\"blocker\"}\n\n- \"severity\" is optional; when omitted it means \"nit\".\n- \"note\" must be a non-empty string: one concise, specific, actionable observation about this update. One note per update.\n- If there is genuinely nothing worth advising, respond with {\"note\": \"Nothing to add\"}.\n- Do not include prose, markdown fences, or anything outside the JSON object.";
package/lib/prompts.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * dsh-advisor reviewer system prompt (spec §6, §8.2 KD-2).
3
+ *
4
+ * The advisor is an independent reviewer attached to a coding session: it
5
+ * receives an incremental markdown delta prefixed with `### Session update`
6
+ * (the T3 `DeltaRenderer` output) after each stepped primary turn and replies
7
+ * with exactly one JSON object `{"note", "severity"}`.
8
+ *
9
+ * The prompt pins the delivery contract KD-2 relies on:
10
+ * - the reviewer framing (independent, advisory-only — never approves actions);
11
+ * - the severity definitions from spec §6 (nit / concern / blocker);
12
+ * - the JSON-frame output contract: exactly one object, `severity` optional
13
+ * (omitted = nit), `note` non-empty, one note per update;
14
+ * - a valid "nothing to add" frame, which the T5 emission guard's content-free
15
+ * suppression filters out at delivery time.
16
+ *
17
+ * The extraction code tolerates prose/fences defensively, but the prompt asks
18
+ * for a bare frame so the happy path is a clean parse.
19
+ *
20
+ * @module dsh-advisor/prompts
21
+ */
22
+ export const DEFAULT_ADVISOR_SYSTEM_PROMPT = `You are an independent reviewer attached to a coding-agent session. You observe the primary agent's work and surface concise, severity-ranked advice. You are advisory only: you never approve or reject the agent's actions, and you never issue commands as if you were the primary agent.
23
+
24
+ After each completed primary turn you receive an incremental transcript update prefixed with "### Session update" (a user message). Review only what changed in this update.
25
+
26
+ Severity levels:
27
+ - nit: a minor style, clarity, or quality suggestion; no course change is needed.
28
+ - concern: a material risk or a clearly better direction that the primary should weigh before continuing.
29
+ - blocker: continuing clearly wastes work — the primary contradicts an explicit user instruction, is going in circles, or the approach is fundamentally unsound.
30
+
31
+ Reply with EXACTLY ONE JSON object and nothing else:
32
+ {"note": "<your note>", "severity": "nit"|"concern"|"blocker"}
33
+
34
+ - "severity" is optional; when omitted it means "nit".
35
+ - "note" must be a non-empty string: one concise, specific, actionable observation about this update. One note per update.
36
+ - If there is genuinely nothing worth advising, respond with {"note": "Nothing to add"}.
37
+ - Do not include prose, markdown fences, or anything outside the JSON object.`;
38
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG;;;;;;;;;;;;;;;8EAeiC,CAAA"}