@shendeguize/dsh-agent-sidecar 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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/cordis.patch.yml +10 -0
  4. package/lib/client.js +8062 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.d.ts +396 -0
  7. package/lib/index.js +4166 -0
  8. package/package.json +101 -0
  9. package/src/analysis.ts +782 -0
  10. package/src/bridge.ts +841 -0
  11. package/src/client/analysis/AnalysisPanel.tsx +191 -0
  12. package/src/client/analysis/analysis.module.css +183 -0
  13. package/src/client/analysis-glue.ts +331 -0
  14. package/src/client/api.ts +380 -0
  15. package/src/client/board/Board.tsx +214 -0
  16. package/src/client/board/board.module.css +302 -0
  17. package/src/client/board/logic.ts +556 -0
  18. package/src/client/board/project-view-logic.ts +361 -0
  19. package/src/client/board/project-view.module.css +307 -0
  20. package/src/client/board/project-view.tsx +189 -0
  21. package/src/client/board/strings.ts +112 -0
  22. package/src/client/commands.ts +484 -0
  23. package/src/client/controller.ts +360 -0
  24. package/src/client/css-modules.d.ts +11 -0
  25. package/src/client/detail/SessionDetail.tsx +270 -0
  26. package/src/client/detail/detail.module.css +433 -0
  27. package/src/client/detail/logic.ts +779 -0
  28. package/src/client/detail/strings.ts +98 -0
  29. package/src/client/detail/transport.ts +175 -0
  30. package/src/client/detail-glue.ts +397 -0
  31. package/src/client/detail-view.module.css +79 -0
  32. package/src/client/detail-view.tsx +233 -0
  33. package/src/client/dsh-tools/LineageTree.tsx +210 -0
  34. package/src/client/dsh-tools/SearchPanel.tsx +169 -0
  35. package/src/client/dsh-tools/dsh-tools.module.css +374 -0
  36. package/src/client/dsh-tools/logic.ts +596 -0
  37. package/src/client/dsh-tools/strings.ts +90 -0
  38. package/src/client/index.ts +315 -0
  39. package/src/client/inject/InjectPanel.tsx +482 -0
  40. package/src/client/inject/inject.module.css +446 -0
  41. package/src/client/inject/logic.ts +516 -0
  42. package/src/client/inject/overlay.module.css +22 -0
  43. package/src/client/inject-glue.ts +171 -0
  44. package/src/client/locales/command.ts +48 -0
  45. package/src/client/locales/en.ts +385 -0
  46. package/src/client/locales/index.ts +123 -0
  47. package/src/client/locales/zh.ts +402 -0
  48. package/src/client/m3-transport.ts +151 -0
  49. package/src/client/mount.tsx +307 -0
  50. package/src/client/project-glue.ts +134 -0
  51. package/src/client/search-glue.ts +143 -0
  52. package/src/client/settings-card.module.css +359 -0
  53. package/src/client/settings-card.tsx +565 -0
  54. package/src/client/settings-glue.ts +130 -0
  55. package/src/client/sidebar-tab.tsx +494 -0
  56. package/src/client/sse.ts +366 -0
  57. package/src/client/widget.tsx +80 -0
  58. package/src/config.ts +193 -0
  59. package/src/dsh-inject.ts +240 -0
  60. package/src/fusion.ts +988 -0
  61. package/src/guard.ts +274 -0
  62. package/src/index.ts +950 -0
  63. package/src/inject-gateway.ts +574 -0
  64. package/src/routes.ts +1133 -0
  65. package/src/send-cli.ts +340 -0
  66. package/src/session-store.ts +184 -0
  67. package/src/skills-provider.ts +293 -0
  68. package/src/supervisor.ts +463 -0
package/lib/index.d.ts ADDED
@@ -0,0 +1,396 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { IncomingMessage, ServerResponse } from "node:http";
3
+ import { Readable, Writable } from "node:stream";
4
+ import { Context } from "@deepseek-ai/cordis";
5
+
6
+ //#region src/analysis.d.ts
7
+ /**
8
+ * AI bypass-analysis engine — design §5 pillar 3 / §4.e.3 (M3, T5.6).
9
+ * Creates a DEDICATED dsh analysis session per request via `ctx.agents.create`,
10
+ * feeds it a bounded summary of the observed session/project, supports
11
+ * incremental follow-up questions, and can be cancelled from the UI at any
12
+ * time. Pure engine: routing/index wiring is the integration layer's job, and
13
+ * the analysis INPUT (summary text assembled from fusion timelines/overviews)
14
+ * arrives as a structured parameter — this module never touches fusion.
15
+ *
16
+ * API facts verified against the installed SDK
17
+ * (`@deepseek-ai/dsh-agent@0.1.1-rc.2` d.ts, authoritative over the design
18
+ * sketch):
19
+ *
20
+ * - `ctx.agents.create(options): Promise<AgentHandle>` (lib/types/index.d.ts:288)
21
+ * with `CreateAgentOptions` requiring a CALLER-SUPPLIED `sessionId`
22
+ * (index.d.ts:65-118) — the engine mints one per analysis — plus an optional
23
+ * creation-only `signal` (index.d.ts:98). `AgentHandle = { agent; dispose():
24
+ * Promise<void> }` (index.d.ts:155-158); `dispose()` stops the loop and
25
+ * removes the session, which is exactly the UI "stop" semantics.
26
+ * - There is NO system-prompt field on `CreateAgentOptions` (`agentOptions` is
27
+ * only provider/model/maxTokens, runtime-types.d.ts:21-28; `setup` composes
28
+ * cordis scopes and would break pure DI), so the read-only-analyst guidance
29
+ * rides the FIRST user message instead.
30
+ * - `Agent.followup(message: UserMessage): void` is a SYNCHRONOUS inbox splice
31
+ * (runtime-types.d.ts:115) — same finding as T4.4. There is no per-message
32
+ * response promise; the result must be read back from the session.
33
+ * - "Getting the analysis result": `Agent.whenIdle(): Promise<void>` resolves
34
+ * after whole-agent quiescence (runtime-types.d.ts:87), and a waking
35
+ * followup flips status to running synchronously (runtime-types.d.ts:161-172),
36
+ * so `followup → whenIdle → read log` observes the completed turn. The text
37
+ * is read from `agent.session.deriveMessages()` (dsh-session
38
+ * index.d.ts:259, the cached surface projection); token accounting rides
39
+ * `assistant/message` events as `data.usage?: TokenUsage`
40
+ * (dsh-session types.d.ts:279-285, dsh-llm types.d.ts:123-129) — that feeds
41
+ * `tokensHint`.
42
+ * - `Agent.cancel(cause: AgentCancelCause): void` aborts the active turn
43
+ * (runtime-types.d.ts:80); `{ kind: 'user' }` is the honest cause for a
44
+ * user-facing stop/timeout (dsh-session types.d.ts:118-127).
45
+ *
46
+ * Bounds (§7-B token-cost touchpoint, design risk 12): gated by live
47
+ * `analysis.enabled` (default false), input truncated to `maxInputChars`,
48
+ * first response and every follow-up bounded by `analysisTimeoutMs` (timeout
49
+ * cancels the in-flight turn), and at most `maxActiveSessions` concurrent
50
+ * analysis sessions. No periodic/automatic analysis exists here by design.
51
+ *
52
+ * Error vocabulary (contractual):
53
+ * `analysis_disabled | create_failed | timeout | too_many_active | cancelled`.
54
+ * `create_failed` covers the whole establishment phase of `request()`
55
+ * (create + priming followup + first-turn wait); `cancelled` covers follow-ups
56
+ * against a session that is unknown, already stopped, or died mid-turn.
57
+ *
58
+ * Honesty: results are AI-generated inference; every {@link AnalysisResult}
59
+ * carries {@link ANALYSIS_DISCLAIMER} for the UI to display.
60
+ *
61
+ * Log lines NEVER carry the analyzed content: no `summaryText`, no follow-up
62
+ * question, no model reply — only kind/title/analysisSessionId/outcome/token
63
+ * hints and sizes (S8).
64
+ *
65
+ * Pure DI: no cordis/dsh imports; `ctx.agents.create` is injected through the
66
+ * minimal structural face {@link AnalysisAgentFace} (method-syntax members
67
+ * keep parameter checks bivariant, so the SDK's branded `SessionId` / wider
68
+ * `UserMessage` / union `AgentCancelCause` signatures remain assignable).
69
+ *
70
+ * @module
71
+ */
72
+ /** Widened user-message face (same shape family as dsh-inject's, F11). */
73
+ interface AnalysisUserMessageFace {
74
+ readonly id: string;
75
+ readonly role: 'user';
76
+ readonly content: ReadonlyArray<{
77
+ readonly type: string;
78
+ readonly text?: string;
79
+ }>;
80
+ readonly source: {
81
+ readonly kind: string;
82
+ readonly plugin?: string;
83
+ };
84
+ }
85
+ /** Widened derived-message face over dsh-llm `Message` (message.d.ts:120-129). */
86
+ interface AnalysisDerivedMessageFace {
87
+ readonly role: string;
88
+ readonly content: ReadonlyArray<{
89
+ readonly type: string;
90
+ readonly text?: string;
91
+ }>;
92
+ }
93
+ /** Widened session-event envelope face (dsh-session types.d.ts:425-443). */
94
+ interface AnalysisSessionEventFace {
95
+ readonly type: string;
96
+ /** `assistant/message` events carry `{ usage?: TokenUsage }` here. */
97
+ readonly data?: unknown;
98
+ }
99
+ /** Read face over the live session log (dsh-session index.d.ts:106-267). */
100
+ interface AnalysisSessionLogFace {
101
+ /** Cached surface projection of the derived LLM history (index.d.ts:259). */
102
+ deriveMessages(): ReadonlyArray<AnalysisDerivedMessageFace>;
103
+ /** Immutable append-only event snapshot (index.d.ts:174). */
104
+ readonly events: ReadonlyArray<AnalysisSessionEventFace>;
105
+ }
106
+ /** Cancellation cause face; `{kind:'user'}` ∈ `AgentCancelCause` (dsh-session types.d.ts:118-127). */
107
+ interface AnalysisCancelCauseFace {
108
+ readonly kind: 'user';
109
+ }
110
+ /** Live-agent face: prompt in, quiescence + log read back (runtime-types.d.ts:60-133). */
111
+ interface AnalysisAgentDriverFace {
112
+ readonly session: AnalysisSessionLogFace;
113
+ followup(message: AnalysisUserMessageFace): void;
114
+ cancel(cause: AnalysisCancelCauseFace): void;
115
+ whenIdle(): Promise<void>;
116
+ }
117
+ /**
118
+ * The dedicated analysis session handle — `AgentHandle` face
119
+ * (index.d.ts:155-158): send messages via `agent.followup`, read responses via
120
+ * `agent.whenIdle` + `agent.session`, stop via `dispose()`.
121
+ */
122
+ interface AnalysisSession {
123
+ readonly agent: AnalysisAgentDriverFace;
124
+ dispose(): Promise<void>;
125
+ }
126
+ /** Per-agent options face over SDK `AgentOptions` (runtime-types.d.ts:21-28). */
127
+ interface AnalysisAgentOptionsFace {
128
+ /** Provider route (must have a registered adapter at call time). */
129
+ readonly provider?: string;
130
+ /** Model id interpreted by the selected provider adapter. */
131
+ readonly model?: string;
132
+ /** Maximum output tokens for each conversation-model request. */
133
+ readonly maxTokens?: number;
134
+ }
135
+ /** `CreateAgentOptions` face (index.d.ts:65-118): engine-minted id + creation abort. */
136
+ interface AnalysisCreateOptions {
137
+ readonly sessionId: string;
138
+ readonly signal?: AbortSignal;
139
+ /**
140
+ * Provider/model routing for the analysis agent (`CreateAgentOptions.
141
+ * agentOptions`). The engine never sets this — the integration layer's
142
+ * create adapter resolves and attaches it (A-1: an agent created without
143
+ * provider/model fails prompt assembly on the `{{model}}` variable and
144
+ * `buildRequest`, completing with an empty summary and zero tokens).
145
+ */
146
+ readonly agentOptions?: AnalysisAgentOptionsFace;
147
+ /**
148
+ * Session creation metadata (`CreateAgentOptions.meta` subset). Also
149
+ * attached by the create adapter, never the engine: the deployment
150
+ * persona's `{{cwd}}` prompt variable reads `session.header.cwd`, which
151
+ * only `meta.cwd` populates (same reason dsh-headless passes
152
+ * `meta: { cwd: process.cwd() }` on its own create call).
153
+ */
154
+ readonly meta?: {
155
+ readonly cwd?: string;
156
+ };
157
+ }
158
+ /** Minimal `ctx.agents` face: the one factory entry point this engine uses. */
159
+ interface AnalysisAgentFace {
160
+ /** `AgentRegistry.create` (index.d.ts:288). */
161
+ create(options: AnalysisCreateOptions): Promise<AnalysisSession>;
162
+ }
163
+ //#endregion
164
+ //#region src/supervisor.d.ts
165
+ type SupervisorPolicy = 'adopt-or-host' | 'adopt-only' | 'off';
166
+ //#endregion
167
+ //#region src/config.d.ts
168
+ /** Daemon lifecycle governance (design §4.a). */
169
+ interface DaemonConfig {
170
+ /** adopt-or-host: probe→adopt→else spawn; adopt-only: never spawn; off: no lifecycle management (read-only reconcile still runs). */
171
+ policy: SupervisorPolicy;
172
+ /** Consecutive hosting failures before the supervisor trips FAILED. */
173
+ backoffLimit: number;
174
+ }
175
+ /** How to reach/launch the sidecar itself. */
176
+ interface SidecarInvocationConfig {
177
+ /** argv prefix of the sidecar executable (PATH name, absolute path, or e.g. python3+zipapp as multiple entries). */
178
+ command: string[];
179
+ /** Empty = default `~/.agent_sidecar` (honoring AGENT_SIDECAR_RUNTIME_DIR); non-empty redirects via env for spawned daemons. */
180
+ runtimeDir: string;
181
+ }
182
+ /** Reconciler snapshot cadences (design §4.b / ADR-2). */
183
+ interface StreamConfig {
184
+ /** `status` snapshot cadence while any session is working (ms). */
185
+ reconcileActiveMs: number;
186
+ /** `status` snapshot cadence otherwise (ms). */
187
+ reconcileIdleMs: number;
188
+ }
189
+ /** Write-path master switch and defaults (M2 consumes defaultMode). */
190
+ interface InjectConfig {
191
+ /** Master gate: false hides all inject affordances and 403s write actions server-side. */
192
+ enabled: boolean;
193
+ /** Default injection mode offered by the inject panel. */
194
+ defaultMode: 'queue' | 'steer';
195
+ }
196
+ /** AI bypass-analysis switch and model routing (M3). */
197
+ interface AnalysisConfig {
198
+ enabled: boolean;
199
+ /**
200
+ * Explicit provider route for the dedicated analysis agents. Empty (the
201
+ * default) reuses the host's default model selection (`agentDefaultModel`
202
+ * service, the same source dsh's own entry points read). Takes effect
203
+ * only together with a non-empty `model`.
204
+ */
205
+ provider: string;
206
+ /** Explicit model id for the analysis agents; see {@link provider}. */
207
+ model: string;
208
+ }
209
+ /** Board rendering knobs (client half). */
210
+ interface UiConfig {
211
+ /** Session recency window shown on the board (hours). */
212
+ timeWindowHours: number;
213
+ /** Whether dead sessions are listed. */
214
+ showDead: boolean;
215
+ }
216
+ /** Skill provider switch (design §6/§7 path two; wired in M4/T6.2). */
217
+ interface SkillConfig {
218
+ /** Register the embedded agent-sidecar skill on ctx.skills (read at apply; restart semantics). */
219
+ provide: boolean;
220
+ }
221
+ /** Validated composition config (all defaults filled by the schema). */
222
+ interface Config {
223
+ daemon: DaemonConfig;
224
+ sidecar: SidecarInvocationConfig;
225
+ stream: StreamConfig;
226
+ inject: InjectConfig;
227
+ analysis: AnalysisConfig;
228
+ ui: UiConfig;
229
+ skill: SkillConfig;
230
+ }
231
+ declare const Config: z<Config>;
232
+ //#endregion
233
+ //#region src/dsh-inject.d.ts
234
+ /**
235
+ * Widened message parameter face: the SDK's `UserMessage` is assignable to
236
+ * this shape, which keeps `Agent`'s method signatures structurally
237
+ * compatible with {@link DshAgentFace} without importing SDK types.
238
+ */
239
+ interface DshUserMessageFace {
240
+ readonly id: string;
241
+ readonly role: 'user';
242
+ readonly content: ReadonlyArray<{
243
+ readonly type: string;
244
+ }>;
245
+ readonly source: {
246
+ readonly kind: string;
247
+ };
248
+ }
249
+ /** Live-agent face: the two injection entry points (runtime-types.d.ts:115/:123). */
250
+ interface DshAgentFace {
251
+ followup(message: DshUserMessageFace): void;
252
+ steer(message: DshUserMessageFace): void;
253
+ }
254
+ /** `AgentHandle` face (index.d.ts:155-158). `dispose` is deliberately absent:
255
+ * the executor never tears down an agent it resumed — disposal would unload
256
+ * the session and cancel the just-queued work. */
257
+ interface DshAgentHandleFace {
258
+ readonly agent: DshAgentFace;
259
+ }
260
+ /** Minimal `ctx.agents` (`AgentRegistry`) face: locate + resume. */
261
+ interface AgentsServiceFace {
262
+ /** Live lookup (index.d.ts:349); undefined = session not loaded. */
263
+ get(sessionId: string): DshAgentFace | undefined;
264
+ /** Load a persisted session and start an agent on it (index.d.ts:296). */
265
+ resume(options: {
266
+ readonly resumeSessionId: string;
267
+ }): Promise<DshAgentHandleFace>;
268
+ }
269
+ //#endregion
270
+ //#region src/index.d.ts
271
+ declare const name = "agent-sidecar";
272
+ /** Required services; see the module doc for why `agents` is lazy instead. */
273
+ declare const inject: string[];
274
+ /** `ctx.webServer` face (route registration only). */
275
+ interface WebServerService {
276
+ register(route: {
277
+ kind: 'exact' | 'prefix';
278
+ path: string;
279
+ handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
280
+ }): () => void;
281
+ }
282
+ /** Bounded in-memory collection for one child output stream. */
283
+ interface SubprocessCollectSpec {
284
+ maxBytes: number;
285
+ spill?: {
286
+ maxBytes: number;
287
+ };
288
+ }
289
+ /** Fully-specified spawn request (`ctx.subprocess` applies no defaults). */
290
+ interface SubprocessSpawnSpec {
291
+ argv: readonly string[];
292
+ cwd: string;
293
+ stdio: {
294
+ stdin: 'ignore' | 'pipe' | {
295
+ readonly data: string;
296
+ };
297
+ stdout: 'pipe' | 'inherit' | SubprocessCollectSpec;
298
+ stderr: 'pipe' | 'inherit' | SubprocessCollectSpec;
299
+ };
300
+ graceMs: number;
301
+ signal?: AbortSignal;
302
+ env?: NodeJS.ProcessEnv;
303
+ }
304
+ /** Exit facts of one closed process (Node `close`-event vocabulary). */
305
+ interface SubprocessOutcome {
306
+ exitCode: number | null;
307
+ signal: NodeJS.Signals | null;
308
+ }
309
+ /** Offset-based, non-consuming reader over one collect-mode stream. */
310
+ interface SubprocessOutputReader {
311
+ readFrom(fromByte: number): {
312
+ text: string;
313
+ nextOffset: number;
314
+ lossy: boolean;
315
+ };
316
+ }
317
+ /** Live child-process handle rooted in its own process tree. */
318
+ interface SubprocessHandle {
319
+ readonly pid: number;
320
+ /** Present iff spawned with `stdin: 'pipe'` (dsh-subprocess types.d.ts:158). */
321
+ readonly stdin: Writable | undefined;
322
+ readonly stdout: Readable | undefined;
323
+ readonly stderr: Readable | undefined;
324
+ readonly collected: {
325
+ readonly stdout?: SubprocessOutputReader;
326
+ readonly stderr?: SubprocessOutputReader;
327
+ };
328
+ readonly done: Promise<SubprocessOutcome>;
329
+ terminate(): void;
330
+ waitForExit(signal?: AbortSignal): Promise<boolean>;
331
+ }
332
+ /** `ctx.subprocess` face (managed pipe-process primitive only). */
333
+ interface SubprocessService {
334
+ spawn(spec: SubprocessSpawnSpec): SubprocessHandle;
335
+ }
336
+ /** Owner scope returned by `ctx.settings.register` (read/observe subset). */
337
+ interface SettingsScopeFace<T> {
338
+ get(): T;
339
+ watch(callback: (next: T, prev: T) => void): () => void;
340
+ }
341
+ /**
342
+ * `ctx.settings` face (namespace registration only). Source:
343
+ * dsh-settings SettingsProvider.register — `register(ns, schema, {base,
344
+ * applies})` → owner scope; the namespace brand is compile-time only, so a
345
+ * plain string is structurally sound. Registration rides the CALLER's
346
+ * fiber (service proxy binds this.ctx), so disposal is automatic.
347
+ */
348
+ interface SettingsServiceFace {
349
+ register<T>(ns: string, schema: unknown, options?: {
350
+ base?: Partial<T>;
351
+ applies?: 'live' | 'restart';
352
+ }): SettingsScopeFace<T>;
353
+ }
354
+ /**
355
+ * The lazily-bound `ctx.agents` registry surface: M2 injection consumes
356
+ * `get`/`resume` (dsh-inject's {@link AgentsServiceFace}), M3 analysis
357
+ * consumes `create` (analysis.ts's {@link AnalysisAgentFace}). One lazy
358
+ * binding serves both paths — and gates both degradations.
359
+ */
360
+ type AgentsRegistryFace = AgentsServiceFace & Pick<AnalysisAgentFace, 'create'>;
361
+ /**
362
+ * `ctx.agentDefaultModel` face (dsh-agent-default-model index.d.ts:40-56):
363
+ * the host's default model selection — the SAME source dsh's own entry
364
+ * points read when creating agents (dsh-headless `run()` passes
365
+ * `agentOptions: {provider, model}` from `currentSelection()`;
366
+ * dsh-host-apiproxy exposes it as `defaultModelSelection`). Resolved per
367
+ * call via reflect `get` (never a hard inject): the service is core in
368
+ * dsh-base compositions but the plugin must degrade honestly without it.
369
+ */
370
+ interface AgentDefaultModelFace {
371
+ currentSelection(): {
372
+ provider: string;
373
+ model: string;
374
+ };
375
+ }
376
+ /** The plugin context with the two hard-injected services visible. */
377
+ type HostContext = Context & {
378
+ webServer: WebServerService;
379
+ subprocess: SubprocessService;
380
+ };
381
+ /**
382
+ * Assemble the M1 host half.
383
+ *
384
+ * Teardown is order-sensitive, so the whole assembly lives in ONE
385
+ * `ctx.effect` disposer (design §4.a: "顺序敏感拆除放同一 disposer"):
386
+ * supervisor first (terminates a self-hosted daemon, never an adopted one),
387
+ * then the reconciler (closes the subscribe stream and timers), then
388
+ * `routes.dispose()` (ends SSE clients, unsubscribes), and the webServer
389
+ * route disposer last.
390
+ *
391
+ * @param ctx - plugin context handed by the cordis loader.
392
+ * @param config - schema-validated composition config (defaults filled).
393
+ */
394
+ declare function apply(ctx: HostContext, config: Config): void;
395
+ //#endregion
396
+ export { AgentDefaultModelFace, AgentsRegistryFace, Config, HostContext, SettingsScopeFace, SettingsServiceFace, SubprocessCollectSpec, SubprocessHandle, SubprocessOutcome, SubprocessOutputReader, SubprocessService, SubprocessSpawnSpec, WebServerService, apply, inject, name };