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.
- package/LICENSE +21 -0
- package/README.i18n.yaml +7 -0
- package/README.md +303 -0
- package/README.zh.md +166 -0
- package/cordis.patch.yml +6 -0
- package/lib/advisor-runtime.d.ts +242 -0
- package/lib/advisor-runtime.js +662 -0
- package/lib/advisor-runtime.js.map +1 -0
- package/lib/client/advisor-card.d.ts +90 -0
- package/lib/client/advisor-store.d.ts +310 -0
- package/lib/client/index.d.ts +39 -0
- package/lib/client/locales.d.ts +40 -0
- package/lib/client.d.ts +1 -0
- package/lib/client.js +840 -0
- package/lib/commands.d.ts +136 -0
- package/lib/commands.js +185 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +74 -0
- package/lib/config.js +93 -0
- package/lib/config.js.map +1 -0
- package/lib/delivery.d.ts +129 -0
- package/lib/delivery.js +169 -0
- package/lib/delivery.js.map +1 -0
- package/lib/emission-guard.d.ts +99 -0
- package/lib/emission-guard.js +155 -0
- package/lib/emission-guard.js.map +1 -0
- package/lib/gateway.d.ts +116 -0
- package/lib/gateway.js +214 -0
- package/lib/gateway.js.map +1 -0
- package/lib/index.d.ts +48 -0
- package/lib/index.js +485 -0
- package/lib/index.js.map +1 -0
- package/lib/kinds.d.ts +38 -0
- package/lib/kinds.js +24 -0
- package/lib/kinds.js.map +1 -0
- package/lib/prompts.d.ts +22 -0
- package/lib/prompts.js +38 -0
- package/lib/prompts.js.map +1 -0
- package/lib/settings.d.ts +96 -0
- package/lib/settings.js +141 -0
- package/lib/settings.js.map +1 -0
- package/lib/transcript.d.ts +257 -0
- package/lib/transcript.js +530 -0
- package/lib/transcript.js.map +1 -0
- package/package.json +90 -0
- package/scripts/build-client.mjs +268 -0
package/lib/delivery.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery routing (spec §2 S3, §4 mapping row, §6 delivery semantics,
|
|
3
|
+
* §8.4 KD-4) — the advice delivery channel into the primary agent.
|
|
4
|
+
*
|
|
5
|
+
* One {@link AdvisorDelivery} exists per plugin load and owns:
|
|
6
|
+
*
|
|
7
|
+
* - **The KD-4 per-session agent map**: keyed by `agent.id` (=== `session.id`),
|
|
8
|
+
* maintained by `index.ts` on `agent/created` / `agent/disposed`, with a
|
|
9
|
+
* registry fallback (`ctx.agents.get(session.id)`, injected as
|
|
10
|
+
* `lookupAgent`) that covers agents published before this plugin loaded.
|
|
11
|
+
* A missing agent at delivery time drops the note with a log — advisory
|
|
12
|
+
* only, never throw, never stall.
|
|
13
|
+
* - **Severity routing (spec §6)**: nit → `agent.inject` (non-waking, consumed
|
|
14
|
+
* at the next pre-step boundary); concern/blocker → `agent.steer` (waking —
|
|
15
|
+
* an idle driver starts a turn, a running driver consumes at its next step
|
|
16
|
+
* boundary).
|
|
17
|
+
* - **The immuneTurns cooldown (spec §6)**: after a concern/blocker is actually
|
|
18
|
+
* steered, the next `immuneTurns` stepped primary turns must complete before
|
|
19
|
+
* another interrupting note may steer; interrupting notes inside the window
|
|
20
|
+
* downgrade to inject. The fence arms only on a real steer delivery; the
|
|
21
|
+
* observer's `onSteppedTurnEnd` / `onRewrite` hooks (T3 wiring) drive the
|
|
22
|
+
* countdown and the KD-5 reset.
|
|
23
|
+
*
|
|
24
|
+
* Message shape (spec §6): a user-role message via `createUserMessage` whose
|
|
25
|
+
* source carries the distinct `kind === 'advisor'` (the plugin's
|
|
26
|
+
* `MessageSourceMap` merge extension, src/kinds.ts) and whose content is
|
|
27
|
+
* self-describing `[advisor:{severity}] {note}` — the only cue the primary
|
|
28
|
+
* model gets about how to treat it ("weigh, don't blindly obey" spirit).
|
|
29
|
+
*
|
|
30
|
+
* Delivery is synchronous and fire-and-forget; the runtime path (T4 F1) is
|
|
31
|
+
* what contains a throwing `inject`/`steer` — this module lets agent-method
|
|
32
|
+
* throws propagate to that containment seam.
|
|
33
|
+
*
|
|
34
|
+
* @module dsh-advisor/delivery
|
|
35
|
+
*/
|
|
36
|
+
import { boundContextSummary, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
37
|
+
import { ADVISOR_SOURCE_KIND } from './kinds.js';
|
|
38
|
+
/**
|
|
39
|
+
* Build the advisor message for one note (spec §6): a user-role message whose
|
|
40
|
+
* source carries the distinct advisor kind and whose content is self-describing
|
|
41
|
+
* `[advisor:{severity}] {note}`.
|
|
42
|
+
*
|
|
43
|
+
* Bounds (qc3 F-2 / qc2 S-1): the note itself is already capped at
|
|
44
|
+
* `ADVISOR_NOTE_MAX_CHARS` by extraction; the collapsed-row summary is
|
|
45
|
+
* additionally bounded via `boundContextSummary` (120 chars — the platform
|
|
46
|
+
* `CONTEXT_SUMMARY_MAX_CHARS` convention), so the durable log and the
|
|
47
|
+
* collapsed context row never carry an unbounded account.
|
|
48
|
+
*/
|
|
49
|
+
export function buildAdvisorMessage(note) {
|
|
50
|
+
// One formatter for content and summary: the summary is derived from the
|
|
51
|
+
// same string as the content via a fixed slice of the `[advisor:` marker
|
|
52
|
+
// (`[` + everything after it), so the severity formatting can never drift
|
|
53
|
+
// between the two — and note text mentioning "advisor:" cannot affect it.
|
|
54
|
+
const text = `[advisor:${note.severity}] ${note.note}`;
|
|
55
|
+
const summary = `[${text.slice('[advisor:'.length)}`;
|
|
56
|
+
return createUserMessage({
|
|
57
|
+
content: [{ type: 'text', text }],
|
|
58
|
+
// n4 (user direction): declare the notice form + a collapsed-row summary so
|
|
59
|
+
// the web shell's ContextInjectionRow shows "… · advisor · [nit] <note>"
|
|
60
|
+
// instead of a bare producer label. The severity tag is part of the summary
|
|
61
|
+
// text; per-severity COLOR needs the shell UI (out of plugin reach).
|
|
62
|
+
source: {
|
|
63
|
+
kind: ADVISOR_SOURCE_KIND,
|
|
64
|
+
form: 'notice',
|
|
65
|
+
summary: boundContextSummary(summary),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** True when a note interrupts the primary (concern/blocker — spec §6). */
|
|
70
|
+
function isInterrupting(severity) {
|
|
71
|
+
return severity === 'concern' || severity === 'blocker';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Per-plugin delivery router: severity → channel, the KD-4 agent map, and the
|
|
75
|
+
* immuneTurns cooldown. Cordis-free, so the routing logic is unit-testable.
|
|
76
|
+
*/
|
|
77
|
+
export class AdvisorDelivery {
|
|
78
|
+
immuneTurns;
|
|
79
|
+
lookupAgent;
|
|
80
|
+
logger;
|
|
81
|
+
/** KD-4 per-session agent map, keyed by `agent.id` (=== session.id). */
|
|
82
|
+
agents = new Map();
|
|
83
|
+
/**
|
|
84
|
+
* immuneTurns latch: remaining stepped primary turns before an interrupting
|
|
85
|
+
* note may steer again (spec §6). A present entry > 0 means armed; the entry
|
|
86
|
+
* is removed when the countdown exhausts.
|
|
87
|
+
*/
|
|
88
|
+
cooldown = new Map();
|
|
89
|
+
constructor(options) {
|
|
90
|
+
this.immuneTurns = options.immuneTurns;
|
|
91
|
+
this.lookupAgent = options.lookupAgent ?? (() => undefined);
|
|
92
|
+
this.logger = options.logger ?? console;
|
|
93
|
+
}
|
|
94
|
+
/** KD-4: register an agent on `agent/created` (keyed by `agent.id`). */
|
|
95
|
+
registerAgent(agent) {
|
|
96
|
+
this.agents.set(agent.id, agent);
|
|
97
|
+
}
|
|
98
|
+
/** KD-4: drop an agent — and its cooldown with the session — on `agent/disposed`. */
|
|
99
|
+
unregisterAgent(sessionId) {
|
|
100
|
+
this.agents.delete(sessionId);
|
|
101
|
+
this.cooldown.delete(sessionId);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Update the immuneTurns cooldown length (live config — settings onChange,
|
|
105
|
+
* plan dsh-advisor-settings-n2 T1). The fence is re-armed with the new
|
|
106
|
+
* length on the next real steer; the per-session cooldown countdown itself
|
|
107
|
+
* is untouched, so the delivery semantics (spec §6) never change mid-window.
|
|
108
|
+
*/
|
|
109
|
+
setImmuneTurns(value) {
|
|
110
|
+
this.immuneTurns = value;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* One completed stepped primary turn (observer `onSteppedTurnEnd`): decrement
|
|
114
|
+
* the immuneTurns countdown. The latch is removed at zero, so the next
|
|
115
|
+
* interrupting note steers again. Total — never throws.
|
|
116
|
+
*/
|
|
117
|
+
onSteppedTurnEnd(sessionId) {
|
|
118
|
+
const remaining = this.cooldown.get(sessionId);
|
|
119
|
+
if (remaining === undefined || remaining <= 0)
|
|
120
|
+
return;
|
|
121
|
+
if (remaining <= 1)
|
|
122
|
+
this.cooldown.delete(sessionId);
|
|
123
|
+
else
|
|
124
|
+
this.cooldown.set(sessionId, remaining - 1);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* KD-5 reset trigger: a compaction / surface rewrite clears the immuneTurns
|
|
128
|
+
* latch — the session state is being rewritten, so the cooldown's turn-count
|
|
129
|
+
* basis no longer applies. Total — never throws.
|
|
130
|
+
*/
|
|
131
|
+
reset(sessionId) {
|
|
132
|
+
this.cooldown.delete(sessionId);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Route one accepted advice note (spec §6, KD-4).
|
|
136
|
+
*
|
|
137
|
+
* Resolves the primary agent via the map, falling back to the registry; a
|
|
138
|
+
* missing agent drops the note with a log (advisory only — never throw,
|
|
139
|
+
* never stall). nit → inject; concern/blocker → steer, unless the
|
|
140
|
+
* immuneTurns fence is armed, in which case they downgrade to inject.
|
|
141
|
+
*
|
|
142
|
+
* @returns the channel delivered on, or `undefined` when dropped (no agent).
|
|
143
|
+
*/
|
|
144
|
+
route(sessionId, note) {
|
|
145
|
+
const agent = this.agents.get(sessionId) ?? this.lookupAgent(sessionId);
|
|
146
|
+
if (agent === undefined) {
|
|
147
|
+
this.logger.warn('advisor: note dropped — no agent for session', {
|
|
148
|
+
sessionId,
|
|
149
|
+
severity: note.severity,
|
|
150
|
+
});
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
const message = buildAdvisorMessage(note);
|
|
154
|
+
const cooldown = this.cooldown.get(sessionId) ?? 0;
|
|
155
|
+
if (isInterrupting(note.severity) && cooldown <= 0) {
|
|
156
|
+
// A real steer delivery arms the fence (spec §6). Armed before the call:
|
|
157
|
+
// a failed steer delivery still counts as an attempted interrupt, keeping
|
|
158
|
+
// a throwing agent out of the noise loop — and a downgraded or dropped
|
|
159
|
+
// note never reaches this branch, so the fence never arms on one.
|
|
160
|
+
if (this.immuneTurns > 0)
|
|
161
|
+
this.cooldown.set(sessionId, this.immuneTurns);
|
|
162
|
+
agent.steer(message);
|
|
163
|
+
return 'steer';
|
|
164
|
+
}
|
|
165
|
+
agent.inject(message);
|
|
166
|
+
return 'inject';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery.js","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emission guard (spec §4 mapping row — omp `advisor/emission-guard.ts`
|
|
3
|
+
* ported 1:1: normalize / dedupe / content-free suppression / one-per-update
|
|
4
|
+
* / escalation; §6 one-note-per-update; §8.2 KD-2 "guard also drops extras").
|
|
5
|
+
*
|
|
6
|
+
* The guard sits between the T4 runtime (note extraction) and delivery (T6):
|
|
7
|
+
* {@link EmissionGuard.accept} returns `true` to pass a note through to the
|
|
8
|
+
* delivery callback and `false` to suppress it **silently** — the caller can
|
|
9
|
+
* never tell an accepted from a suppressed note, and no error is thrown.
|
|
10
|
+
*
|
|
11
|
+
* Rules (in order):
|
|
12
|
+
* 1. **Normalization** — lowercase, NFKC, every run of non-alphanumeric
|
|
13
|
+
* characters collapsed to one space, trimmed. `"Stop."`, `*stop*` and
|
|
14
|
+
* `" STOP "` all key to `stop`.
|
|
15
|
+
* 2. **Content-free phrase filter** — short phrases with no concrete reason
|
|
16
|
+
* (`stop`, `done`, `complete`, `no issue continue`, `lgtm`,
|
|
17
|
+
* `nothing to add`, plus documented equivalents — see
|
|
18
|
+
* {@link CONTENT_FREE_PHRASES}) are suppressed. Matching is exact on the
|
|
19
|
+
* normalized text, so a note that merely contains a phrase survives.
|
|
20
|
+
* 3. **Per-update rate limit** — at most one note per advisor model cycle is
|
|
21
|
+
* accepted. The runtime signals each new cycle with {@link
|
|
22
|
+
* EmissionGuard.beginUpdate} (once per processed delta), resetting the
|
|
23
|
+
* latch. A guard whose `beginUpdate` is never called still accepts the
|
|
24
|
+
* first note of the session (fail-safe toward fewer notes).
|
|
25
|
+
* 4. **Normalized dedupe with escalation** — a normalized note already
|
|
26
|
+
* accepted this session is dropped, with a FIFO-bounded history
|
|
27
|
+
* (default 4096, omp parity; {@link DEFAULT_MAX_HISTORY}); the oldest
|
|
28
|
+
* entry is evicted past the bound. A repeat at equal or lower severity is
|
|
29
|
+
* suppressed, but a real escalation (nit → concern → blocker) of the same
|
|
30
|
+
* note is accepted and updates the remembered severity.
|
|
31
|
+
*
|
|
32
|
+
* Guard state clears per session: a new runtime (and therefore a new guard)
|
|
33
|
+
* is created per session and discarded on dispose; {@link
|
|
34
|
+
* EmissionGuard.reset} is exposed for the KD-5 session-reset triggers
|
|
35
|
+
* (`compact/*`, `user/message` replace) when the reset wiring lands.
|
|
36
|
+
*
|
|
37
|
+
* @module dsh-advisor/emission-guard
|
|
38
|
+
*/
|
|
39
|
+
import type { AdviceNote } from './advisor-runtime.js';
|
|
40
|
+
/**
|
|
41
|
+
* FIFO dedupe history bound (omp parity: 4096). Deliberately capped — a
|
|
42
|
+
* session that accepts more than this many distinct notes keeps only the most
|
|
43
|
+
* recent window, so an old note may legitimately be advised again.
|
|
44
|
+
*/
|
|
45
|
+
export declare const DEFAULT_MAX_HISTORY = 4096;
|
|
46
|
+
/**
|
|
47
|
+
* Content-free phrases suppressed at delivery. The omp base list is
|
|
48
|
+
* `stop` / `done` / `complete` / `no issue continue` / `lgtm` /
|
|
49
|
+
* `nothing to add` (this is also the frame the KD-2 prompt asks the model to
|
|
50
|
+
* emit when there is nothing to advise). Additions beyond the omp base —
|
|
51
|
+
* short acknowledgements with no concrete reason — are listed after them.
|
|
52
|
+
*/
|
|
53
|
+
export declare const CONTENT_FREE_PHRASES: ReadonlySet<string>;
|
|
54
|
+
/** Options for one {@link EmissionGuard}. */
|
|
55
|
+
export interface EmissionGuardOptions {
|
|
56
|
+
/**
|
|
57
|
+
* FIFO dedupe history bound (normalized notes remembered per session);
|
|
58
|
+
* default {@link DEFAULT_MAX_HISTORY} (4096, omp parity).
|
|
59
|
+
*/
|
|
60
|
+
readonly maxHistory?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Per-session emission guard. One instance per advisor runtime; created via
|
|
64
|
+
* {@link createEmissionGuard} (or the constructor directly).
|
|
65
|
+
*/
|
|
66
|
+
export declare class EmissionGuard {
|
|
67
|
+
private readonly maxHistory;
|
|
68
|
+
/**
|
|
69
|
+
* normalized note → last accepted severity. Map insertion order is the
|
|
70
|
+
* FIFO order (updating an existing key does not move it), so the oldest
|
|
71
|
+
* entry is simply the first key.
|
|
72
|
+
*/
|
|
73
|
+
private readonly history;
|
|
74
|
+
/** One-note-per-update latch; reset by {@link beginUpdate} and {@link reset}. */
|
|
75
|
+
private acceptedThisUpdate;
|
|
76
|
+
constructor(options?: EmissionGuardOptions);
|
|
77
|
+
/**
|
|
78
|
+
* Mark the start of one advisor model cycle (one processed transcript
|
|
79
|
+
* delta). The per-update rate limit latch resets here, so each cycle may
|
|
80
|
+
* deliver one note again. The runtime calls this once per delta it drains.
|
|
81
|
+
*/
|
|
82
|
+
beginUpdate(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Accept or suppress one extracted note.
|
|
85
|
+
*
|
|
86
|
+
* @returns `true` = pass through to delivery (T6); `false` = suppressed
|
|
87
|
+
* (normalized-empty, content-free, a repeat at equal/lower severity, or
|
|
88
|
+
* the second note of the same update). Never throws.
|
|
89
|
+
*/
|
|
90
|
+
accept(note: AdviceNote): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Clear all session state (dedupe history + per-update latch). Exposed for
|
|
93
|
+
* the KD-5 session-reset triggers; a new session gets a fresh guard anyway
|
|
94
|
+
* (per-runtime lifetime).
|
|
95
|
+
*/
|
|
96
|
+
reset(): void;
|
|
97
|
+
}
|
|
98
|
+
/** Factory (plan T5: "Export the guard class + a factory"). */
|
|
99
|
+
export declare function createEmissionGuard(options?: EmissionGuardOptions): EmissionGuard;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emission guard (spec §4 mapping row — omp `advisor/emission-guard.ts`
|
|
3
|
+
* ported 1:1: normalize / dedupe / content-free suppression / one-per-update
|
|
4
|
+
* / escalation; §6 one-note-per-update; §8.2 KD-2 "guard also drops extras").
|
|
5
|
+
*
|
|
6
|
+
* The guard sits between the T4 runtime (note extraction) and delivery (T6):
|
|
7
|
+
* {@link EmissionGuard.accept} returns `true` to pass a note through to the
|
|
8
|
+
* delivery callback and `false` to suppress it **silently** — the caller can
|
|
9
|
+
* never tell an accepted from a suppressed note, and no error is thrown.
|
|
10
|
+
*
|
|
11
|
+
* Rules (in order):
|
|
12
|
+
* 1. **Normalization** — lowercase, NFKC, every run of non-alphanumeric
|
|
13
|
+
* characters collapsed to one space, trimmed. `"Stop."`, `*stop*` and
|
|
14
|
+
* `" STOP "` all key to `stop`.
|
|
15
|
+
* 2. **Content-free phrase filter** — short phrases with no concrete reason
|
|
16
|
+
* (`stop`, `done`, `complete`, `no issue continue`, `lgtm`,
|
|
17
|
+
* `nothing to add`, plus documented equivalents — see
|
|
18
|
+
* {@link CONTENT_FREE_PHRASES}) are suppressed. Matching is exact on the
|
|
19
|
+
* normalized text, so a note that merely contains a phrase survives.
|
|
20
|
+
* 3. **Per-update rate limit** — at most one note per advisor model cycle is
|
|
21
|
+
* accepted. The runtime signals each new cycle with {@link
|
|
22
|
+
* EmissionGuard.beginUpdate} (once per processed delta), resetting the
|
|
23
|
+
* latch. A guard whose `beginUpdate` is never called still accepts the
|
|
24
|
+
* first note of the session (fail-safe toward fewer notes).
|
|
25
|
+
* 4. **Normalized dedupe with escalation** — a normalized note already
|
|
26
|
+
* accepted this session is dropped, with a FIFO-bounded history
|
|
27
|
+
* (default 4096, omp parity; {@link DEFAULT_MAX_HISTORY}); the oldest
|
|
28
|
+
* entry is evicted past the bound. A repeat at equal or lower severity is
|
|
29
|
+
* suppressed, but a real escalation (nit → concern → blocker) of the same
|
|
30
|
+
* note is accepted and updates the remembered severity.
|
|
31
|
+
*
|
|
32
|
+
* Guard state clears per session: a new runtime (and therefore a new guard)
|
|
33
|
+
* is created per session and discarded on dispose; {@link
|
|
34
|
+
* EmissionGuard.reset} is exposed for the KD-5 session-reset triggers
|
|
35
|
+
* (`compact/*`, `user/message` replace) when the reset wiring lands.
|
|
36
|
+
*
|
|
37
|
+
* @module dsh-advisor/emission-guard
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* FIFO dedupe history bound (omp parity: 4096). Deliberately capped — a
|
|
41
|
+
* session that accepts more than this many distinct notes keeps only the most
|
|
42
|
+
* recent window, so an old note may legitimately be advised again.
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_MAX_HISTORY = 4096;
|
|
45
|
+
/**
|
|
46
|
+
* Content-free phrases suppressed at delivery. The omp base list is
|
|
47
|
+
* `stop` / `done` / `complete` / `no issue continue` / `lgtm` /
|
|
48
|
+
* `nothing to add` (this is also the frame the KD-2 prompt asks the model to
|
|
49
|
+
* emit when there is nothing to advise). Additions beyond the omp base —
|
|
50
|
+
* short acknowledgements with no concrete reason — are listed after them.
|
|
51
|
+
*/
|
|
52
|
+
export const CONTENT_FREE_PHRASES = new Set([
|
|
53
|
+
// omp base list
|
|
54
|
+
'stop',
|
|
55
|
+
'done',
|
|
56
|
+
'complete',
|
|
57
|
+
'no issue continue',
|
|
58
|
+
'lgtm',
|
|
59
|
+
'nothing to add',
|
|
60
|
+
// additions (documented in the task-5 report): content-free equivalents
|
|
61
|
+
'ok',
|
|
62
|
+
'okay',
|
|
63
|
+
'good',
|
|
64
|
+
'fine',
|
|
65
|
+
'looks good',
|
|
66
|
+
'looks fine',
|
|
67
|
+
'all good',
|
|
68
|
+
'all clear',
|
|
69
|
+
'no issue',
|
|
70
|
+
'no issues',
|
|
71
|
+
'nothing',
|
|
72
|
+
'looks good to me',
|
|
73
|
+
]);
|
|
74
|
+
/** Severity order for the escalation check (spec §6): nit < concern < blocker. */
|
|
75
|
+
const SEVERITY_RANK = { nit: 0, concern: 1, blocker: 2 };
|
|
76
|
+
/**
|
|
77
|
+
* Normalize one note to its identity key (spec §4): lowercase, NFKC, every
|
|
78
|
+
* run of non-alphanumeric characters collapsed to one space, trimmed.
|
|
79
|
+
*/
|
|
80
|
+
function normalizeNote(text) {
|
|
81
|
+
return text
|
|
82
|
+
.normalize('NFKC')
|
|
83
|
+
.toLowerCase()
|
|
84
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
85
|
+
.trim();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Per-session emission guard. One instance per advisor runtime; created via
|
|
89
|
+
* {@link createEmissionGuard} (or the constructor directly).
|
|
90
|
+
*/
|
|
91
|
+
export class EmissionGuard {
|
|
92
|
+
maxHistory;
|
|
93
|
+
/**
|
|
94
|
+
* normalized note → last accepted severity. Map insertion order is the
|
|
95
|
+
* FIFO order (updating an existing key does not move it), so the oldest
|
|
96
|
+
* entry is simply the first key.
|
|
97
|
+
*/
|
|
98
|
+
history = new Map();
|
|
99
|
+
/** One-note-per-update latch; reset by {@link beginUpdate} and {@link reset}. */
|
|
100
|
+
acceptedThisUpdate = false;
|
|
101
|
+
constructor(options) {
|
|
102
|
+
this.maxHistory = options?.maxHistory ?? DEFAULT_MAX_HISTORY;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Mark the start of one advisor model cycle (one processed transcript
|
|
106
|
+
* delta). The per-update rate limit latch resets here, so each cycle may
|
|
107
|
+
* deliver one note again. The runtime calls this once per delta it drains.
|
|
108
|
+
*/
|
|
109
|
+
beginUpdate() {
|
|
110
|
+
this.acceptedThisUpdate = false;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Accept or suppress one extracted note.
|
|
114
|
+
*
|
|
115
|
+
* @returns `true` = pass through to delivery (T6); `false` = suppressed
|
|
116
|
+
* (normalized-empty, content-free, a repeat at equal/lower severity, or
|
|
117
|
+
* the second note of the same update). Never throws.
|
|
118
|
+
*/
|
|
119
|
+
accept(note) {
|
|
120
|
+
const key = normalizeNote(note.note);
|
|
121
|
+
if (key.length === 0)
|
|
122
|
+
return false; // punctuation-only / whitespace-only
|
|
123
|
+
if (CONTENT_FREE_PHRASES.has(key))
|
|
124
|
+
return false;
|
|
125
|
+
if (this.acceptedThisUpdate)
|
|
126
|
+
return false; // one note per update
|
|
127
|
+
const prior = this.history.get(key);
|
|
128
|
+
if (prior !== undefined && SEVERITY_RANK[note.severity] <= SEVERITY_RANK[prior]) {
|
|
129
|
+
return false; // equal/lower-severity repeat suppressed
|
|
130
|
+
}
|
|
131
|
+
this.history.set(key, note.severity); // new note, or an accepted escalation
|
|
132
|
+
if (this.history.size > this.maxHistory) {
|
|
133
|
+
// FIFO eviction: drop the oldest accepted note past the bound.
|
|
134
|
+
const oldest = this.history.keys().next().value;
|
|
135
|
+
if (oldest !== undefined)
|
|
136
|
+
this.history.delete(oldest);
|
|
137
|
+
}
|
|
138
|
+
this.acceptedThisUpdate = true;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Clear all session state (dedupe history + per-update latch). Exposed for
|
|
143
|
+
* the KD-5 session-reset triggers; a new session gets a fresh guard anyway
|
|
144
|
+
* (per-runtime lifetime).
|
|
145
|
+
*/
|
|
146
|
+
reset() {
|
|
147
|
+
this.history.clear();
|
|
148
|
+
this.acceptedThisUpdate = false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Factory (plan T5: "Export the guard class + a factory"). */
|
|
152
|
+
export function createEmissionGuard(options) {
|
|
153
|
+
return new EmissionGuard(options);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=emission-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emission-guard.js","sourceRoot":"","sources":["../src/emission-guard.ts"],"names":[],"mappings":"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"}
|
package/lib/gateway.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T1 (plan dsh-advisor-settings-gateway-n5) — host-side `advisor` config
|
|
3
|
+
* gateway: the `/api/advisor/get` + `/api/advisor/set` Remote endpoints.
|
|
4
|
+
*
|
|
5
|
+
* Transport: the typertGateway `/api` interceptor is the single host-wide RPC
|
|
6
|
+
* slot (a plugin must NOT `connection.rpc.intercept('/api')` again — it would
|
|
7
|
+
* throw). Instead this service declares a typertGateway binding (via the
|
|
8
|
+
* `TypertRemoteService` base) plus `@Remote` method markers; the gateway's SRC
|
|
9
|
+
* discovery (`claimsEndpoint` — `ctx.reflect.props` + `remoteMethods`) claims
|
|
10
|
+
* `/api/advisor/get` and `/api/advisor/set`, and the payload contract is
|
|
11
|
+
* exactly one plain-object `args` field whose keys are the method parameter
|
|
12
|
+
* names (`get()` → `{ args: {} }`; `set(patch)` → `{ args: { patch } }`).
|
|
13
|
+
*
|
|
14
|
+
* Data: `get` reads the `AdvisorSettingsBridge` source — the same live
|
|
15
|
+
* composed config the runtime reads (schema defaults → plugin-row base →
|
|
16
|
+
* settings user layer), resolved through the `resolveAdvisorConfig` hard gate
|
|
17
|
+
* (the SSOT for enabled-without-pair disabled-with-reason). `set` validates
|
|
18
|
+
* the patch against the `Config` schema first (unknown-key rejection
|
|
19
|
+
* unchanged — the settings service itself is non-strict and would accept the
|
|
20
|
+
* unknown key), then writes the USER layer in-process via
|
|
21
|
+
* `ctx.settings.update` (no exposed-namespace gate on the in-process write —
|
|
22
|
+
* the wire-level `exposedNamespaces()` check only guards the apiproxy path),
|
|
23
|
+
* and returns the new composed value.
|
|
24
|
+
*
|
|
25
|
+
* The settings service is OPTIONAL (no settings service → the bridge source
|
|
26
|
+
* stays the entry, `get` still works; `set` fails with a clear error — KD-G5
|
|
27
|
+
* fallback). The gateway captures the service through a conditional
|
|
28
|
+
* `ctx.inject(['settings'], ...)` child (the same activation pattern as
|
|
29
|
+
* `installAdvisorSettings`), because `ctx.settings` is only resolvable from a
|
|
30
|
+
* fiber that declares it.
|
|
31
|
+
*
|
|
32
|
+
* The returned config is normalized to the typertGateway JSON wire boundary:
|
|
33
|
+
* absent keys (provider/model/disabledReason) are OMITTED, never
|
|
34
|
+
* present-as-undefined (the gateway's result validation rejects undefined
|
|
35
|
+
* values).
|
|
36
|
+
*
|
|
37
|
+
* @module dsh-advisor/gateway
|
|
38
|
+
*/
|
|
39
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
40
|
+
import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry';
|
|
41
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
42
|
+
import type { AdvisorSettingsBridge } from './settings.js';
|
|
43
|
+
import type { AdvisorConfig, ResolvedAdvisorConfig } from './config.js';
|
|
44
|
+
/** Patch shape accepted by `advisor.set` — any subset of the config keys. */
|
|
45
|
+
export type AdvisorConfigPatch = Partial<AdvisorConfig>;
|
|
46
|
+
/**
|
|
47
|
+
* The host-side `advisor` config gateway (`/api/advisor/get` +
|
|
48
|
+
* `/api/advisor/set`). Registered as the cordis service key `'advisor'`
|
|
49
|
+
* (namespace defaults to the service key). The `TypertRemoteService` base is
|
|
50
|
+
* kept ONLY for its `typertRemote` binding — the typertGateway's dispatch
|
|
51
|
+
* `validateBinding` requires the visible binding on the live service (a pure
|
|
52
|
+
* instance property, no module-private state). Endpoints are registered
|
|
53
|
+
* EXPLICITLY through `ctx.typert.register(advisorTypertContribution())`
|
|
54
|
+
* (see `apply` in `src/index.ts`) instead of the `@Remote` SRC markers:
|
|
55
|
+
* SRC discovery reads `remoteMethods()` — a module-private WeakMap in
|
|
56
|
+
* `@deepseek-ai/dsh-typert-protocol` — so a locally-linked plugin whose
|
|
57
|
+
* peers resolve outside the host installation never shares that table with
|
|
58
|
+
* the host typertGateway (zero claimed endpoints, `/api/advisor/*` 404).
|
|
59
|
+
* The explicit `TypertRegistry.register` path writes the invocation
|
|
60
|
+
* descriptors into `ctx.typert.local`, which `claimsEndpoint` checks FIRST,
|
|
61
|
+
* so claim + dispatch work regardless of module identity.
|
|
62
|
+
*/
|
|
63
|
+
export declare class AdvisorConfigGateway extends TypertRemoteService {
|
|
64
|
+
private readonly bridge;
|
|
65
|
+
/** The live settings service once the optional inject child activates. */
|
|
66
|
+
private settings;
|
|
67
|
+
/**
|
|
68
|
+
* @param ctx - owning context (the plugin fiber's ctx inside `apply`).
|
|
69
|
+
* @param bridge - the same `AdvisorSettingsBridge` the runtime reads, so
|
|
70
|
+
* get/set always operate on the live composed config.
|
|
71
|
+
*/
|
|
72
|
+
constructor(ctx: Context, bridge: AdvisorSettingsBridge);
|
|
73
|
+
/**
|
|
74
|
+
* Read the current composed config (schema defaults → entry base → settings
|
|
75
|
+
* user layer) through the hard gate.
|
|
76
|
+
* @returns the resolved config (incl. disabledReason when the gate blocks).
|
|
77
|
+
*/
|
|
78
|
+
get(): {
|
|
79
|
+
config: ResolvedAdvisorConfig;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Validate a config patch and write it to the settings USER layer (live —
|
|
83
|
+
* the runtime re-applies through the bridge `onChange`; no restart needed).
|
|
84
|
+
* @param patch - any subset of the config keys; unknown keys are rejected
|
|
85
|
+
* by the `Config` schema before anything is written.
|
|
86
|
+
* @returns the NEW composed config after the write.
|
|
87
|
+
* @throws when the patch fails `Config` validation, or when no settings
|
|
88
|
+
* service is composed (KD-G5: the write channel is unavailable).
|
|
89
|
+
*/
|
|
90
|
+
set(patch: AdvisorConfigPatch): Promise<{
|
|
91
|
+
config: ResolvedAdvisorConfig;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the live composed config through the hard gate. Containment
|
|
95
|
+
* (qc2 W-1): a user layer the resolver rejects (e.g. an unknown key that
|
|
96
|
+
* survived the non-strict settings schema) resolves to disabled-with-reason
|
|
97
|
+
* carrying the message — the gateway never fails the RPC on a bad user
|
|
98
|
+
* layer, and gate semantics hold (no model call can start). S1: when the
|
|
99
|
+
* raw source is still readable, the fallback seeds its scalar latches
|
|
100
|
+
* (systemPrompt / immuneTurns / maxDeltaMessages) instead of hardcoded
|
|
101
|
+
* defaults, so an invalid layer only drops the offending keys.
|
|
102
|
+
*/
|
|
103
|
+
private readConfig;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The explicit typert contribution for the `advisor` gateway endpoints —
|
|
107
|
+
* registered via `ctx.typert.register(...)` (see `apply` in `src/index.ts`).
|
|
108
|
+
* The descriptors mirror exactly what the former SRC discovery derived from
|
|
109
|
+
* the `@Remote` markers (`src:advisor#<endpoint>` identity shape, direct
|
|
110
|
+
* receiver, JSON wire params with `src-json` codec), so the host
|
|
111
|
+
* typertGateway claim + dispatch behavior is byte-for-byte the same — the
|
|
112
|
+
* only difference is the registration does not depend on the module-private
|
|
113
|
+
* `remoteMethods` marker table, which a locally-linked plugin can never
|
|
114
|
+
* share with the host installation.
|
|
115
|
+
*/
|
|
116
|
+
export declare function advisorTypertContribution(): TypertContribution;
|