@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.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +8062 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +396 -0
- package/lib/index.js +4166 -0
- package/package.json +101 -0
- package/src/analysis.ts +782 -0
- package/src/bridge.ts +841 -0
- package/src/client/analysis/AnalysisPanel.tsx +191 -0
- package/src/client/analysis/analysis.module.css +183 -0
- package/src/client/analysis-glue.ts +331 -0
- package/src/client/api.ts +380 -0
- package/src/client/board/Board.tsx +214 -0
- package/src/client/board/board.module.css +302 -0
- package/src/client/board/logic.ts +556 -0
- package/src/client/board/project-view-logic.ts +361 -0
- package/src/client/board/project-view.module.css +307 -0
- package/src/client/board/project-view.tsx +189 -0
- package/src/client/board/strings.ts +112 -0
- package/src/client/commands.ts +484 -0
- package/src/client/controller.ts +360 -0
- package/src/client/css-modules.d.ts +11 -0
- package/src/client/detail/SessionDetail.tsx +270 -0
- package/src/client/detail/detail.module.css +433 -0
- package/src/client/detail/logic.ts +779 -0
- package/src/client/detail/strings.ts +98 -0
- package/src/client/detail/transport.ts +175 -0
- package/src/client/detail-glue.ts +397 -0
- package/src/client/detail-view.module.css +79 -0
- package/src/client/detail-view.tsx +233 -0
- package/src/client/dsh-tools/LineageTree.tsx +210 -0
- package/src/client/dsh-tools/SearchPanel.tsx +169 -0
- package/src/client/dsh-tools/dsh-tools.module.css +374 -0
- package/src/client/dsh-tools/logic.ts +596 -0
- package/src/client/dsh-tools/strings.ts +90 -0
- package/src/client/index.ts +315 -0
- package/src/client/inject/InjectPanel.tsx +482 -0
- package/src/client/inject/inject.module.css +446 -0
- package/src/client/inject/logic.ts +516 -0
- package/src/client/inject/overlay.module.css +22 -0
- package/src/client/inject-glue.ts +171 -0
- package/src/client/locales/command.ts +48 -0
- package/src/client/locales/en.ts +385 -0
- package/src/client/locales/index.ts +123 -0
- package/src/client/locales/zh.ts +402 -0
- package/src/client/m3-transport.ts +151 -0
- package/src/client/mount.tsx +307 -0
- package/src/client/project-glue.ts +134 -0
- package/src/client/search-glue.ts +143 -0
- package/src/client/settings-card.module.css +359 -0
- package/src/client/settings-card.tsx +565 -0
- package/src/client/settings-glue.ts +130 -0
- package/src/client/sidebar-tab.tsx +494 -0
- package/src/client/sse.ts +366 -0
- package/src/client/widget.tsx +80 -0
- package/src/config.ts +193 -0
- package/src/dsh-inject.ts +240 -0
- package/src/fusion.ts +988 -0
- package/src/guard.ts +274 -0
- package/src/index.ts +950 -0
- package/src/inject-gateway.ts +574 -0
- package/src/routes.ts +1133 -0
- package/src/send-cli.ts +340 -0
- package/src/session-store.ts +184 -0
- package/src/skills-provider.ts +293 -0
- package/src/supervisor.ts +463 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh in-process injection executor — injection path one of design §4.d
|
|
3
|
+
* (.local/tasks/make_dsh_mode/design/dsh_plugin_design.md): native dsh
|
|
4
|
+
* sessions are reached through the `ctx.agents` registry, closing the
|
|
5
|
+
* sidecar send `unsupported_dsh` gap.
|
|
6
|
+
*
|
|
7
|
+
* API facts verified against the installed SDK
|
|
8
|
+
* (`@deepseek-ai/dsh-agent@0.1.1-rc.2` d.ts, authoritative over the design
|
|
9
|
+
* sketch):
|
|
10
|
+
*
|
|
11
|
+
* - `ctx.agents` is `AgentRegistry` (lib/types/index.d.ts:28):
|
|
12
|
+
* `get(id): Agent | undefined` (:349) and
|
|
13
|
+
* `resume({resumeSessionId}): Promise<AgentHandle>` (:296) with
|
|
14
|
+
* `AgentHandle = { agent; dispose() }` (:155-158).
|
|
15
|
+
* - `Agent.followup(message: UserMessage): void` and
|
|
16
|
+
* `Agent.steer(message: UserMessage): void` are SYNCHRONOUS inbox splices
|
|
17
|
+
* (runtime-types.d.ts:115/:123) — the design sketch's `await` is a
|
|
18
|
+
* deviation. A sync return means the message entered the live inbox
|
|
19
|
+
* (delivered); a sync throw is the only call-site failure face.
|
|
20
|
+
* - `UserMessage` = `{ id, role: 'user', content: ContentBlock[], source }`
|
|
21
|
+
* (dsh-llm message.d.ts:120-133); text content is `[{type:'text',text}]`
|
|
22
|
+
* (types.d.ts:39-42, F11) and plugin attribution is
|
|
23
|
+
* `source: { kind: 'plugin', plugin: <name> }` (message.d.ts:98-101).
|
|
24
|
+
* - resume is NOT idempotent: resuming a live session throws
|
|
25
|
+
* `cannot prepare session "<id>" while it is live`
|
|
26
|
+
* (session-persistence coordinator), and resume rejects when no
|
|
27
|
+
* persistence backend is configured — so the executor MUST `get` first
|
|
28
|
+
* and only resume a miss. persistence prepare may also retry unboundedly
|
|
29
|
+
* under concurrent writers, hence the bounded resume wait here.
|
|
30
|
+
*
|
|
31
|
+
* queue → `followup` (own next turn), steer → `steer` (nearest step),
|
|
32
|
+
* matching dsh's own `session.prompt` mode vocabulary.
|
|
33
|
+
*
|
|
34
|
+
* Error vocabulary is normalized with path two on the shared subset
|
|
35
|
+
* `session_not_found | executor_error | timeout`; dsh-native error text
|
|
36
|
+
* only travels in `detail`. Log lines never carry the message body — only
|
|
37
|
+
* byte size and a sha256 prefix (S8).
|
|
38
|
+
*
|
|
39
|
+
* Pure DI: no cordis/dsh imports. {@link AgentsServiceFace} is a minimal
|
|
40
|
+
* structural face extracted from the d.ts; the real `AgentRegistry`
|
|
41
|
+
* satisfies it directly (method-syntax members keep parameter checks
|
|
42
|
+
* bivariant, so the SDK's branded `SessionId` / wider `UserMessage`
|
|
43
|
+
* signatures remain assignable).
|
|
44
|
+
*
|
|
45
|
+
* @module
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { createHash } from 'node:crypto'
|
|
49
|
+
|
|
50
|
+
import type { InjectExecutionRequest, InjectExecutor, InjectResult } from './inject-gateway.ts'
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Minimal structural faces over the dsh-agent SDK.
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/** The exact message this executor constructs (F11: content-block array). */
|
|
57
|
+
export interface DshInjectMessage {
|
|
58
|
+
/** Plain-string form of the SDK's branded `MessageId`; unique per request. */
|
|
59
|
+
readonly id: string
|
|
60
|
+
readonly role: 'user'
|
|
61
|
+
readonly content: readonly [{ readonly type: 'text'; readonly text: string }]
|
|
62
|
+
/** Plugin attribution (message.d.ts:98-101). */
|
|
63
|
+
readonly source: { readonly kind: 'plugin'; readonly plugin: string }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Widened message parameter face: the SDK's `UserMessage` is assignable to
|
|
68
|
+
* this shape, which keeps `Agent`'s method signatures structurally
|
|
69
|
+
* compatible with {@link DshAgentFace} without importing SDK types.
|
|
70
|
+
*/
|
|
71
|
+
export interface DshUserMessageFace {
|
|
72
|
+
readonly id: string
|
|
73
|
+
readonly role: 'user'
|
|
74
|
+
readonly content: ReadonlyArray<{ readonly type: string }>
|
|
75
|
+
readonly source: { readonly kind: string }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Live-agent face: the two injection entry points (runtime-types.d.ts:115/:123). */
|
|
79
|
+
export interface DshAgentFace {
|
|
80
|
+
followup(message: DshUserMessageFace): void
|
|
81
|
+
steer(message: DshUserMessageFace): void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `AgentHandle` face (index.d.ts:155-158). `dispose` is deliberately absent:
|
|
85
|
+
* the executor never tears down an agent it resumed — disposal would unload
|
|
86
|
+
* the session and cancel the just-queued work. */
|
|
87
|
+
export interface DshAgentHandleFace {
|
|
88
|
+
readonly agent: DshAgentFace
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Minimal `ctx.agents` (`AgentRegistry`) face: locate + resume. */
|
|
92
|
+
export interface AgentsServiceFace {
|
|
93
|
+
/** Live lookup (index.d.ts:349); undefined = session not loaded. */
|
|
94
|
+
get(sessionId: string): DshAgentFace | undefined
|
|
95
|
+
/** Load a persisted session and start an agent on it (index.d.ts:296). */
|
|
96
|
+
resume(options: { readonly resumeSessionId: string }): Promise<DshAgentHandleFace>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Logging seam (same shape as send-cli's; never receives the message body).
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
export type DshInjectLogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
104
|
+
|
|
105
|
+
export type DshInjectLog = (
|
|
106
|
+
level: DshInjectLogLevel,
|
|
107
|
+
msg: string,
|
|
108
|
+
meta?: Record<string, unknown>,
|
|
109
|
+
) => void
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Tunables.
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/** Default `source.plugin` attribution value. */
|
|
116
|
+
export const DEFAULT_PLUGIN_NAME = 'agent-sidecar'
|
|
117
|
+
/**
|
|
118
|
+
* Bounded wait for `agents.resume`: persistence prepare retries unboundedly
|
|
119
|
+
* under concurrent external writers, so an unloaded-session resume is the
|
|
120
|
+
* one async face of this otherwise in-process path.
|
|
121
|
+
*/
|
|
122
|
+
export const DEFAULT_RESUME_TIMEOUT_MS = 30_000
|
|
123
|
+
|
|
124
|
+
/** Sha256 hex prefix length recorded in logs (matches inject-gateway). */
|
|
125
|
+
const SHA_LOG_CHARS = 12
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Internals.
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
function describeError(error: unknown): string {
|
|
132
|
+
return error instanceof Error ? error.message : String(error)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Race a resume against the bounded wait; null = timed out (still pending). */
|
|
136
|
+
async function resumeWithin(
|
|
137
|
+
resume: Promise<DshAgentHandleFace>,
|
|
138
|
+
timeoutMs: number,
|
|
139
|
+
): Promise<DshAgentHandleFace | null> {
|
|
140
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
141
|
+
try {
|
|
142
|
+
return await Promise.race([
|
|
143
|
+
resume,
|
|
144
|
+
new Promise<null>((resolve) => {
|
|
145
|
+
timer = setTimeout(() => resolve(null), timeoutMs)
|
|
146
|
+
}),
|
|
147
|
+
])
|
|
148
|
+
} finally {
|
|
149
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Executor.
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
export function createDshInjectExecutor(deps: {
|
|
158
|
+
agents: AgentsServiceFace
|
|
159
|
+
log?: DshInjectLog
|
|
160
|
+
pluginName?: string
|
|
161
|
+
/** Bounded resume wait override; default {@link DEFAULT_RESUME_TIMEOUT_MS}. */
|
|
162
|
+
resumeTimeoutMs?: number
|
|
163
|
+
}): InjectExecutor {
|
|
164
|
+
const pluginName = deps.pluginName ?? DEFAULT_PLUGIN_NAME
|
|
165
|
+
const log: DshInjectLog = deps.log ?? (() => {})
|
|
166
|
+
const resumeTimeoutMs = deps.resumeTimeoutMs ?? DEFAULT_RESUME_TIMEOUT_MS
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
kind: 'dsh',
|
|
170
|
+
async execute(req: InjectExecutionRequest): Promise<InjectResult> {
|
|
171
|
+
const sessionId = req.target.sessionId
|
|
172
|
+
const messageBytes = Buffer.byteLength(req.message, 'utf8')
|
|
173
|
+
const messageSha12 = createHash('sha256')
|
|
174
|
+
.update(req.message, 'utf8')
|
|
175
|
+
.digest('hex')
|
|
176
|
+
.slice(0, SHA_LOG_CHARS)
|
|
177
|
+
const baseMeta = {
|
|
178
|
+
requestId: req.requestId,
|
|
179
|
+
sessionId,
|
|
180
|
+
mode: req.mode,
|
|
181
|
+
messageBytes,
|
|
182
|
+
messageSha12,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Locate: loaded → inject directly; resume throws on a live session
|
|
186
|
+
// ("cannot prepare ... while it is live"), so `get` must win first.
|
|
187
|
+
let agent = deps.agents.get(sessionId)
|
|
188
|
+
const resumed = agent === undefined
|
|
189
|
+
|
|
190
|
+
if (agent === undefined) {
|
|
191
|
+
log('debug', 'dsh session not loaded; resuming', baseMeta)
|
|
192
|
+
let handle: DshAgentHandleFace | null
|
|
193
|
+
try {
|
|
194
|
+
handle = await resumeWithin(
|
|
195
|
+
deps.agents.resume({ resumeSessionId: sessionId }),
|
|
196
|
+
resumeTimeoutMs,
|
|
197
|
+
)
|
|
198
|
+
} catch (error) {
|
|
199
|
+
const detail = describeError(error)
|
|
200
|
+
log('warn', 'dsh resume failed', { ...baseMeta, error: detail })
|
|
201
|
+
return { outcome: 'failed', errorCode: 'session_not_found', detail }
|
|
202
|
+
}
|
|
203
|
+
if (handle === null) {
|
|
204
|
+
// The resume is still pending; nothing was injected, so 'failed'
|
|
205
|
+
// is honest (unlike path two's post-dispatch 'unknown').
|
|
206
|
+
log('warn', 'dsh resume timed out', { ...baseMeta, resumeTimeoutMs })
|
|
207
|
+
return {
|
|
208
|
+
outcome: 'failed',
|
|
209
|
+
errorCode: 'timeout',
|
|
210
|
+
detail: `resume did not settle within ${resumeTimeoutMs}ms`,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// The handle's dispose() capability is intentionally dropped: the
|
|
214
|
+
// resumed agent must stay live to run the queued/steered turn.
|
|
215
|
+
agent = handle.agent
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const message: DshInjectMessage = {
|
|
219
|
+
id: `${pluginName}-${req.requestId}`,
|
|
220
|
+
role: 'user',
|
|
221
|
+
content: [{ type: 'text', text: req.message }],
|
|
222
|
+
source: { kind: 'plugin', plugin: pluginName },
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// queue → followup (own next turn), steer → steer (nearest step).
|
|
226
|
+
// Both are synchronous inbox splices: sync return = delivered.
|
|
227
|
+
try {
|
|
228
|
+
if (req.mode === 'steer') agent.steer(message)
|
|
229
|
+
else agent.followup(message)
|
|
230
|
+
} catch (error) {
|
|
231
|
+
const detail = describeError(error)
|
|
232
|
+
log('warn', 'dsh injection call threw', { ...baseMeta, resumed, error: detail })
|
|
233
|
+
return { outcome: 'failed', errorCode: 'executor_error', detail }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
log('info', 'dsh injection delivered', { ...baseMeta, resumed })
|
|
237
|
+
return { outcome: 'delivered' }
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
}
|