dsh-smooth-stream 0.3.4 → 0.4.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/src/plugin.ts CHANGED
@@ -7,8 +7,18 @@ import { DEFAULT_STREAM_CONFIG, type StreamConfig } from './config.ts'
7
7
  import { injectStreamConfig } from './boot-config.ts'
8
8
  import { STREAM_PACKAGE_NAME, STREAM_PACKAGE_VERSION } from './package-meta.ts'
9
9
  import { inspectProfileInstallation, updateNpmProfilePackage } from './profile-installation.ts'
10
- import { STREAM_SETTINGS_RPC, STREAM_SETTINGS_RPC_CHANNEL, type StreamSettingsView } from './settings-api.ts'
11
- import { DEFAULT_STREAM_SETTINGS, STREAM_SETTINGS_NS, type StreamSettings } from './settings.ts'
10
+ import {
11
+ STREAM_SETTINGS_RPC,
12
+ STREAM_SETTINGS_RPC_CHANNEL,
13
+ type StreamDebugSettingsView,
14
+ type StreamSettingsView,
15
+ } from './settings-api.ts'
16
+ import {
17
+ DEFAULT_STREAM_SETTINGS,
18
+ STREAM_SETTINGS_NS,
19
+ type StreamDebugTuning,
20
+ type StreamSettings,
21
+ } from './settings.ts'
12
22
 
13
23
  /** Display name shown by the Host loader while the plugin is mounted. */
14
24
  export const name = 'dsh-smooth-stream'
@@ -42,7 +52,21 @@ export const Config: Schema<Config> = Schema.object({
42
52
  * settings provider while the browser edits it through the plugin RPC below.
43
53
  */
44
54
  export const StreamSettingsSchema: Schema<StreamSettings> = Schema.object({
55
+ enabled: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.enabled),
45
56
  thinkAutoExpand: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.thinkAutoExpand),
57
+ autoCollapse: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.autoCollapse),
58
+ debugEnabled: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.debugEnabled),
59
+ debugTuning: Schema.object({
60
+ revealScale: Schema.number().min(0.25).max(2).default(DEFAULT_STREAM_SETTINGS.debugTuning.revealScale),
61
+ queuePressure: Schema.number().min(0).max(2).default(DEFAULT_STREAM_SETTINGS.debugTuning.queuePressure),
62
+ maxRevealCps: Schema.number().min(120).max(1000).default(DEFAULT_STREAM_SETTINGS.debugTuning.maxRevealCps),
63
+ springStiffness: Schema.number().min(40).max(320).default(DEFAULT_STREAM_SETTINGS.debugTuning.springStiffness),
64
+ springDamping: Schema.number().min(8).max(80).default(DEFAULT_STREAM_SETTINGS.debugTuning.springDamping),
65
+ springMass: Schema.number().min(0.5).max(3).default(DEFAULT_STREAM_SETTINGS.debugTuning.springMass),
66
+ runwayPx: Schema.number().min(0).max(120).default(DEFAULT_STREAM_SETTINGS.debugTuning.runwayPx),
67
+ reserveResponseMs: Schema.number().min(60).max(600).default(DEFAULT_STREAM_SETTINGS.debugTuning.reserveResponseMs),
68
+ backpressureMinScale: Schema.number().min(0.25).max(1).default(DEFAULT_STREAM_SETTINGS.debugTuning.backpressureMinScale),
69
+ }),
46
70
  })
47
71
 
48
72
  /**
@@ -79,25 +103,61 @@ export function apply(ctx: Context, config: Config): void {
79
103
 
80
104
  const view = (): StreamSettingsView => {
81
105
  const installation = inspectProfileInstallation(connectionCtx.baseUrl, STREAM_PACKAGE_NAME)
106
+ const settings = scope.get()
82
107
  return {
83
108
  version: STREAM_PACKAGE_VERSION,
84
109
  installation: installation.kind,
85
110
  writable: connectionCtx.settings.writable,
86
- thinkAutoExpand: scope.get().thinkAutoExpand,
111
+ enabled: settings.enabled,
112
+ thinkAutoExpand: settings.thinkAutoExpand,
113
+ autoCollapse: settings.autoCollapse,
87
114
  canUpgrade: installation.kind === 'npm',
88
115
  }
89
116
  }
90
117
 
118
+ const debugView = (): StreamDebugSettingsView => {
119
+ const settings = scope.get()
120
+ return {
121
+ debugEnabled: settings.debugEnabled,
122
+ tuning: { ...settings.debugTuning },
123
+ }
124
+ }
125
+
126
+ const validDebugTuning = (value: unknown): value is StreamDebugTuning => {
127
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return false
128
+ const tuning = value as Record<string, unknown>
129
+ return typeof tuning.revealScale === 'number'
130
+ && tuning.revealScale >= 0.25 && tuning.revealScale <= 2
131
+ && typeof tuning.queuePressure === 'number'
132
+ && tuning.queuePressure >= 0 && tuning.queuePressure <= 2
133
+ && typeof tuning.maxRevealCps === 'number'
134
+ && tuning.maxRevealCps >= 120 && tuning.maxRevealCps <= 1000
135
+ && typeof tuning.springStiffness === 'number'
136
+ && tuning.springStiffness >= 40 && tuning.springStiffness <= 320
137
+ && typeof tuning.springDamping === 'number'
138
+ && tuning.springDamping >= 8 && tuning.springDamping <= 80
139
+ && typeof tuning.springMass === 'number'
140
+ && tuning.springMass >= 0.5 && tuning.springMass <= 3
141
+ && typeof tuning.runwayPx === 'number'
142
+ && tuning.runwayPx >= 0 && tuning.runwayPx <= 120
143
+ && typeof tuning.reserveResponseMs === 'number'
144
+ && tuning.reserveResponseMs >= 60 && tuning.reserveResponseMs <= 600
145
+ && typeof tuning.backpressureMinScale === 'number'
146
+ && tuning.backpressureMinScale >= 0.25 && tuning.backpressureMinScale <= 1
147
+ }
148
+
91
149
  const handle: ConnectionRpcHandler = async (endpoint, payload) => {
92
150
  if (endpoint === STREAM_SETTINGS_RPC.read) return { ok: true, value: view() }
93
151
  if (endpoint === STREAM_SETTINGS_RPC.write) {
94
- if (typeof payload !== 'object' || payload === null
95
- || typeof (payload as { thinkAutoExpand?: unknown }).thinkAutoExpand !== 'boolean') {
152
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)
153
+ || typeof (payload as { enabled?: unknown }).enabled !== 'boolean'
154
+ || typeof (payload as { thinkAutoExpand?: unknown }).thinkAutoExpand !== 'boolean'
155
+ || typeof (payload as { autoCollapse?: unknown }).autoCollapse !== 'boolean') {
96
156
  return {
97
157
  ok: false,
98
158
  error: {
99
159
  code: 'settings-rejected',
100
- message: 'thinkAutoExpand must be a boolean',
160
+ message: 'enabled, thinkAutoExpand and autoCollapse must be booleans',
101
161
  details: { ns: STREAM_SETTINGS_NS },
102
162
  },
103
163
  }
@@ -113,7 +173,30 @@ export function apply(ctx: Context, config: Config): void {
113
173
  }
114
174
  }
115
175
  try {
116
- await scope.update({ thinkAutoExpand: (payload as { thinkAutoExpand: boolean }).thinkAutoExpand })
176
+ const next = payload as {
177
+ enabled: boolean
178
+ thinkAutoExpand: boolean
179
+ autoCollapse: boolean
180
+ debugEnabled?: unknown
181
+ debugTuning?: unknown
182
+ }
183
+ const hasDebug = next.debugEnabled !== undefined || next.debugTuning !== undefined
184
+ if (hasDebug && (typeof next.debugEnabled !== 'boolean' || !validDebugTuning(next.debugTuning))) {
185
+ return {
186
+ ok: false,
187
+ error: {
188
+ code: 'settings-rejected',
189
+ message: 'debugEnabled and debugTuning must be provided together and be valid',
190
+ details: { ns: STREAM_SETTINGS_NS },
191
+ },
192
+ }
193
+ }
194
+ await scope.update({
195
+ enabled: next.enabled,
196
+ thinkAutoExpand: next.thinkAutoExpand,
197
+ autoCollapse: next.autoCollapse,
198
+ ...(hasDebug ? { debugEnabled: next.debugEnabled, debugTuning: next.debugTuning } : {}),
199
+ })
117
200
  } catch {
118
201
  return {
119
202
  ok: false,
@@ -126,6 +209,53 @@ export function apply(ctx: Context, config: Config): void {
126
209
  }
127
210
  return { ok: true, value: view() }
128
211
  }
212
+ if (endpoint === STREAM_SETTINGS_RPC.debugRead) return { ok: true, value: debugView() }
213
+ if (endpoint === STREAM_SETTINGS_RPC.debugWrite) {
214
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
215
+ return {
216
+ ok: false,
217
+ error: {
218
+ code: 'settings-rejected',
219
+ message: 'debug settings must be an object',
220
+ details: { ns: STREAM_SETTINGS_NS },
221
+ },
222
+ }
223
+ }
224
+ const next = payload as { debugEnabled?: unknown; tuning?: unknown }
225
+ if (typeof next.debugEnabled !== 'boolean' || !validDebugTuning(next.tuning)) {
226
+ return {
227
+ ok: false,
228
+ error: {
229
+ code: 'settings-rejected',
230
+ message: 'debugEnabled and tuning are malformed',
231
+ details: { ns: STREAM_SETTINGS_NS },
232
+ },
233
+ }
234
+ }
235
+ if (!connectionCtx.settings.writable) {
236
+ return {
237
+ ok: false,
238
+ error: {
239
+ code: 'settings-rejected',
240
+ message: 'smooth-stream debug settings are read-only',
241
+ details: { ns: STREAM_SETTINGS_NS },
242
+ },
243
+ }
244
+ }
245
+ try {
246
+ await scope.update({ debugEnabled: next.debugEnabled, debugTuning: next.tuning })
247
+ } catch {
248
+ return {
249
+ ok: false,
250
+ error: {
251
+ code: 'settings-rejected',
252
+ message: 'smooth-stream debug settings update failed',
253
+ details: { ns: STREAM_SETTINGS_NS },
254
+ },
255
+ }
256
+ }
257
+ return { ok: true, value: debugView() }
258
+ }
129
259
  if (endpoint === STREAM_SETTINGS_RPC.upgrade) {
130
260
  const installation = inspectProfileInstallation(connectionCtx.baseUrl, STREAM_PACKAGE_NAME)
131
261
  if (installation.kind !== 'npm') {
@@ -7,6 +7,8 @@ export const STREAM_SETTINGS_RPC_CHANNEL = '/smooth-stream'
7
7
  export const STREAM_SETTINGS_RPC = {
8
8
  read: 'settings.read',
9
9
  write: 'settings.write',
10
+ debugRead: 'debug.read',
11
+ debugWrite: 'debug.write',
10
12
  upgrade: 'plugin.upgrade',
11
13
  } as const
12
14
 
@@ -21,12 +23,22 @@ export interface StreamSettingsView {
21
23
  installation: StreamInstallationKind
22
24
  /** Whether the Host settings provider permits a write. */
23
25
  writable: boolean
26
+ /** Whether smooth-stream currently owns conversation rendering. */
27
+ enabled: boolean
24
28
  /** Current resolved preference. */
25
29
  thinkAutoExpand: boolean
30
+ /** Whether finished turns fold their work process behind a summary row. */
31
+ autoCollapse: boolean
26
32
  /** Whether a fixed npm update command is safe to offer. */
27
33
  canUpgrade: boolean
28
34
  }
29
35
 
36
+ /** Debug-only settings returned by the diagnostics RPC. */
37
+ export interface StreamDebugSettingsView {
38
+ debugEnabled: boolean
39
+ tuning: import('./settings.ts').StreamDebugTuning
40
+ }
41
+
30
42
  /** Successful package update acknowledgement; loading the new code needs a restart. */
31
43
  export interface StreamUpgradeView {
32
44
  restartRequired: true
package/src/settings.ts CHANGED
@@ -10,21 +10,76 @@
10
10
  /** Settings namespace registered by the Host and served through the plugin RPC. */
11
11
  export const STREAM_SETTINGS_NS = 'smooth-stream'
12
12
 
13
+ /** Runtime knobs exposed only while the diagnostics switch is enabled. */
14
+ export interface StreamDebugTuning {
15
+ /** Multiplier applied to the reveal cadence (1 = preset default). */
16
+ revealScale: number
17
+ /** Multiplier for backlog pressure in the adaptive reveal queue. */
18
+ queuePressure: number
19
+ /** Upper bound for reveal cadence, in characters per second. */
20
+ maxRevealCps: number
21
+ /** Spring stiffness used by the conversation follower. */
22
+ springStiffness: number
23
+ /** Spring damping used by the conversation follower. */
24
+ springDamping: number
25
+ /** Spring mass used by the conversation follower. */
26
+ springMass: number
27
+ /** Predictive runway before the fixed conversation chrome, in pixels. */
28
+ runwayPx: number
29
+ /** Response time for opening/closing the predictive runway, in ms. */
30
+ reserveResponseMs: number
31
+ /** Lowest reveal multiplier when visual lag fills the safe paint room. */
32
+ backpressureMinScale: number
33
+ }
34
+
35
+ /** Defaults preserve the production engine exactly. */
36
+ export const DEFAULT_STREAM_DEBUG_TUNING: StreamDebugTuning = {
37
+ revealScale: 1,
38
+ queuePressure: 0.85,
39
+ maxRevealCps: 600,
40
+ springStiffness: 130,
41
+ springDamping: 24,
42
+ springMass: 1,
43
+ runwayPx: 48,
44
+ reserveResponseMs: 180,
45
+ backpressureMinScale: 0.55,
46
+ }
47
+
13
48
  /**
14
49
  * Preferences a user may set. Deliberately separate from {@link StreamConfig}
15
50
  * because the two change at different times: composition-time values go
16
51
  * through the boot global, a live UI edit goes through the protected plugin RPC.
17
52
  */
18
53
  export interface StreamSettings {
54
+ /**
55
+ * Whether this plugin replaces and wraps Harness conversation renderers.
56
+ * Off returns all rendering ownership to the built-in UI.
57
+ */
58
+ enabled: boolean
19
59
  /**
20
60
  * Whether a reasoning ("Think") block auto-expands while it is the
21
61
  * streaming tail. Off keeps the block collapsed — the user can still open
22
62
  * it by hand — and stops the running state from re-owning the disclosure.
23
63
  */
24
64
  thinkAutoExpand: boolean
65
+ /**
66
+ * Whether a finished turn folds its work process (thinking, tool calls,
67
+ * context injection, intermediate replies) behind one "已处理" summary row,
68
+ * leaving only the final answer visible. The summary row can always be
69
+ * toggled by hand, and turns still expand live while they stream.
70
+ */
71
+ autoCollapse: boolean
72
+ /** Whether the live renderer diagnostics panel is enabled. */
73
+ debugEnabled: boolean
74
+ /** Values edited by the diagnostics panel. */
75
+ debugTuning: StreamDebugTuning
25
76
  }
26
77
 
27
78
  /** Defaults shared by the Host schema and the client-side fallback. */
28
79
  export const DEFAULT_STREAM_SETTINGS: StreamSettings = {
80
+ enabled: true,
29
81
  thinkAutoExpand: true,
82
+ autoCollapse: true,
83
+ debugEnabled: false,
84
+ debugTuning: DEFAULT_STREAM_DEBUG_TUNING,
30
85
  }