@khorsheed/dsh-local-agent-dsh 0.1.0-rc.6

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 (45) hide show
  1. package/README.en.md +150 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +151 -0
  4. package/cordis.patch.yml +24 -0
  5. package/lib/client.js +1115 -0
  6. package/lib/dsh-cli-provider-UhWvivte.js +1701 -0
  7. package/lib/dsh-cli-provider.js +3 -0
  8. package/lib/index.js +624 -0
  9. package/lib/invariant.js +33 -0
  10. package/lib/provision.js +602 -0
  11. package/lib/records.js +109 -0
  12. package/lib/session-log-DOsSz56-.js +88 -0
  13. package/lib/types/client/SettingsCard.d.ts +78 -0
  14. package/lib/types/client/SettingsCard.js +200 -0
  15. package/lib/types/client/icons.d.ts +18 -0
  16. package/lib/types/client/icons.js +2 -0
  17. package/lib/types/client/index.d.ts +45 -0
  18. package/lib/types/client/index.js +88 -0
  19. package/lib/types/client/locales.d.ts +76 -0
  20. package/lib/types/client/locales.js +67 -0
  21. package/lib/types/dsh-cli-provider.d.ts +213 -0
  22. package/lib/types/dsh-cli-provider.js +733 -0
  23. package/lib/types/index.d.ts +124 -0
  24. package/lib/types/index.js +347 -0
  25. package/lib/types/invariant.d.ts +16 -0
  26. package/lib/types/invariant.js +37 -0
  27. package/lib/types/live-driver.d.ts +180 -0
  28. package/lib/types/live-driver.js +887 -0
  29. package/lib/types/live-switch.d.ts +76 -0
  30. package/lib/types/live-switch.js +100 -0
  31. package/lib/types/model-broker.d.ts +86 -0
  32. package/lib/types/model-broker.js +149 -0
  33. package/lib/types/model-catalog.d.ts +27 -0
  34. package/lib/types/model-catalog.js +71 -0
  35. package/lib/types/provision.d.ts +382 -0
  36. package/lib/types/provision.js +656 -0
  37. package/lib/types/records.d.ts +41 -0
  38. package/lib/types/records.js +125 -0
  39. package/lib/types/session-log.d.ts +58 -0
  40. package/lib/types/session-log.js +83 -0
  41. package/lib/types/session-mirror.d.ts +155 -0
  42. package/lib/types/session-mirror.js +451 -0
  43. package/locale/en.json +6 -0
  44. package/locale/zh.json +6 -0
  45. package/package.json +121 -0
@@ -0,0 +1,451 @@
1
+ /**
2
+ * Mirror a sub-dsh session's events into the parent-side dsh subagent session,
3
+ * so a dsh delegation shows its full conversation (task, replies, thinking)
4
+ * and real token usage in the standard 子代理 surface — the same fidelity the
5
+ * other family harnesses get from their CLI transcript files. The sub-dsh
6
+ * writes a real dsh session into the harness scoped home with the SAME id as
7
+ * the parent-side child session and the same SessionEvent format, so the
8
+ * mirror copies finalized `user/message`/`assistant/message` events verbatim
9
+ * (no parsing, no re-encoding): reasoning blocks render natively and usage
10
+ * rides the assistant events' own `usage` field into the tokenUsage
11
+ * projection.
12
+ *
13
+ * Round scoping: the parent provider appends one `turn/start` per delegation
14
+ * round, and the sub-dsh session numbers its turns identically (fresh = 1,
15
+ * each resume +1), so a round's span is the sub-dsh events from its
16
+ * `turn/start` with `turn === round` to the next `turn/start`. The sub-dsh's
17
+ * own scaffolding user messages (agent-instructions, plugin, skill-catalog
18
+ * sources) are filtered — only the caller task (`source.kind === 'user'`)
19
+ * crosses. The sub-dsh's own `step/start`/`step/end` pairs cross verbatim
20
+ * (each carries its (turn, step) coordinates): the real-time conversation
21
+ * assembler only registers a step — and materializes its assistant message —
22
+ * at a boundary, so without them the live subsession view renders the prompt
23
+ * and tool cards but no replies. Turn boundaries are NOT copied: the parent's
24
+ * real-time spawn→settle boundaries stay the authoritative timing.
25
+ *
26
+ * `todo/write` passthrough: the sub-dsh's task list is a standing whole-list
27
+ * snapshot (last-wins). Each pass appends the round's LATEST snapshot only
28
+ * when it differs from the child session's last mirrored one, so repeated
29
+ * passes never duplicate an identical snapshot and the child's `todos`
30
+ * projection (and the official TodoPanel in non-takeover views) reads the
31
+ * member's task list for free.
32
+ * @module @khorsheed/dsh-local-agent-dsh/session-mirror
33
+ */
34
+ import { readFile, readdir } from 'node:fs/promises';
35
+ import { resolveDshSessionLog } from './session-log.js';
36
+ import { join } from 'node:path';
37
+ import { promisify } from 'node:util';
38
+ import { zstdDecompress } from 'node:zlib';
39
+ import { persistChildSession } from '@khorsheed/dsh-local-agent';
40
+ /** Decompress one zstd session log (Node ≥22.15 built-in; engines require ^22.19). */
41
+ const decompressZstd = promisify(zstdDecompress);
42
+ /** Every zstd frame's little-endian magic number, marking a frame start in the stream. */
43
+ const ZSTD_FRAME_MAGIC = 0xfd2fb528;
44
+ /**
45
+ * Decompress a multi-frame zstd session log. The persistence layer appends
46
+ * one frame per flush batch, and Node's single-shot `zstdDecompress` stops at
47
+ * the first frame's end (a streaming decoder does the same), so each frame
48
+ * start is scanned and decompressed independently. A magic-looking byte
49
+ * sequence inside compressed data fails decompression and is skipped.
50
+ * @param content - the raw `.zstd` file bytes.
51
+ * @returns the concatenated plaintext of every frame.
52
+ */
53
+ async function decompressZstdFrames(content) {
54
+ const parts = [];
55
+ for (let offset = 0; offset + 4 <= content.length; offset += 1) {
56
+ if (content.readUInt32LE(offset) !== ZSTD_FRAME_MAGIC)
57
+ continue;
58
+ try {
59
+ parts.push((await decompressZstd(content.subarray(offset))).toString('utf8'));
60
+ }
61
+ catch {
62
+ // Not a real frame start; keep scanning.
63
+ }
64
+ }
65
+ return parts.join('');
66
+ }
67
+ /**
68
+ * Read a sub-dsh session's event log from the scoped home. The session lives
69
+ * at `<homeDir>/sessions/<workspace>/<id>/` (the runner passes the bare uuid);
70
+ * WHICH file inside that directory holds the history is the host's choice —
71
+ * `session.jsonl` on the original generation, `session.v3.jsonl.zstd` on host
72
+ * 0.1.5 — so the name is resolved, never assumed (see
73
+ * {@link resolveDshSessionLog}). The first line is the header, the rest are
74
+ * events.
75
+ * @param homeDir - the `dsh` harness's scoped home.
76
+ * @param sessionId - the sub-dsh session id (same uuid as the child session).
77
+ * @returns the events and their source file, or undefined when the session is
78
+ * absent or unreadable.
79
+ */
80
+ export async function readSubDshEvents(homeDir, sessionId) {
81
+ let workspaces;
82
+ try {
83
+ workspaces = await readdir(join(homeDir, 'sessions'));
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ for (const workspace of workspaces) {
89
+ for (const name of [sessionId, `session-${sessionId}`]) {
90
+ const log = await resolveDshSessionLog(join(homeDir, 'sessions', workspace, name));
91
+ if (log === undefined)
92
+ continue;
93
+ let text;
94
+ try {
95
+ const content = await readFile(log.path);
96
+ text = log.compressed ? await decompressZstdFrames(content) : content.toString('utf8');
97
+ }
98
+ catch {
99
+ continue;
100
+ }
101
+ const events = [];
102
+ for (const line of text.split('\n')) {
103
+ if (line === '')
104
+ continue;
105
+ try {
106
+ const parsed = JSON.parse(line);
107
+ if (parsed.type === 'session')
108
+ continue;
109
+ events.push(parsed);
110
+ }
111
+ catch {
112
+ // A torn tail line (killed mid-flush) is skipped, not fatal.
113
+ }
114
+ }
115
+ if (events.length > 0)
116
+ return { events, log };
117
+ }
118
+ }
119
+ return undefined;
120
+ }
121
+ /**
122
+ * Sum one more assistant event's usage into a running round total. Each
123
+ * sub-dsh assistant event carries ONE LLM call's accounting; the round's
124
+ * windows (live polls, settle pass) recompute it from the round's span, so
125
+ * the total is span-derived and never double counts across passes.
126
+ */
127
+ function addEventUsage(total, usage) {
128
+ const raw = usage;
129
+ if (raw === undefined || typeof raw !== 'object')
130
+ return total;
131
+ const num = (value) => {
132
+ const n = Number(value);
133
+ return Number.isFinite(n) && n > 0 ? n : 0;
134
+ };
135
+ const merged = {
136
+ inputTokens: (total?.inputTokens ?? 0) + num(raw.inputTokens),
137
+ outputTokens: (total?.outputTokens ?? 0) + num(raw.outputTokens),
138
+ };
139
+ const cacheRead = (total?.cacheReadTokens ?? 0) + num(raw.cacheReadTokens);
140
+ const cacheWrite = (total?.cacheWriteTokens ?? 0) + num(raw.cacheWriteTokens);
141
+ if (cacheRead > 0)
142
+ merged.cacheReadTokens = cacheRead;
143
+ if (cacheWrite > 0)
144
+ merged.cacheWriteTokens = cacheWrite;
145
+ return merged;
146
+ }
147
+ /**
148
+ * Extract the round's observed model and token usage from its event span:
149
+ the LAST assistant event's `message.source` names the model that ran, and
150
+ * the assistant events' `usage` fields sum to the round's accounting.
151
+ */
152
+ function roundObservation(span) {
153
+ let observedModel;
154
+ let usage;
155
+ // Tool calls counted over the same round window, keyed by the name the
156
+ // sub-dsh's own `tool/call` event carries — the tool's dsh name, verbatim.
157
+ const byName = {};
158
+ let toolCount = 0;
159
+ for (const event of span) {
160
+ if (event.type === 'tool/call') {
161
+ const name = event.data.name;
162
+ toolCount += 1;
163
+ const key = typeof name === 'string' && name !== '' ? name : 'tool';
164
+ byName[key] = (byName[key] ?? 0) + 1;
165
+ continue;
166
+ }
167
+ if (event.type !== 'assistant/message')
168
+ continue;
169
+ const data = event.data;
170
+ const source = data.message?.source;
171
+ if (source !== undefined && typeof source === 'object') {
172
+ const model = typeof source.model === 'string' && source.model !== '' ? source.model : undefined;
173
+ if (model !== undefined) {
174
+ const provider = typeof source.provider === 'string' && source.provider !== '' ? source.provider : undefined;
175
+ observedModel = provider === undefined ? model : `${provider}/${model}`;
176
+ }
177
+ }
178
+ usage = addEventUsage(usage, data.usage);
179
+ }
180
+ return {
181
+ ...observedModel === undefined ? {} : { observedModel },
182
+ ...usage === undefined ? {} : { usage },
183
+ // Zero is not reported: a round that made no tool call and a round whose
184
+ // events were never read are different facts, and only absence can say
185
+ // the second one honestly.
186
+ ...toolCount === 0 ? {} : { toolCalls: { count: toolCount, byName } },
187
+ };
188
+ }
189
+ /** Visible answer text for progress/results; reasoning stays in the verbatim transcript. */
190
+ function mirroredEventText(event) {
191
+ // user/message carries content directly; assistant/message wraps it in `message`.
192
+ const data = event.data;
193
+ const content = data.message?.content ?? data.content ?? [];
194
+ return content
195
+ .filter(block => block.type === 'text' && typeof block.text === 'string')
196
+ .map(block => block.text)
197
+ .join('');
198
+ }
199
+ /** The delta-progress text for one mirrored tool event. */
200
+ function mirroredToolText(event) {
201
+ if (event.type === 'tool/call') {
202
+ return `[工具 ${event.data.name ?? 'tool'}]`;
203
+ }
204
+ const data = event.data;
205
+ const result = data.message?.content?.[0]?.content?.map(block => block.text ?? '').join('') ?? '';
206
+ return result === '' ? '[工具结果]' : `[工具结果] ${result}`;
207
+ }
208
+ /**
209
+ * Find the child-session seq of the mirrored `tool/call` carrying `callId`,
210
+ * so a mirrored `tool/result`'s `sourceEventSeqs` points at the CHILD's call
211
+ * event — the source event's own seqs reference the sub-dsh session's
212
+ * numbering and are meaningless here.
213
+ */
214
+ function findMirroredCallSeq(childSession, callId) {
215
+ for (let index = childSession.snapshotEvents().length - 1; index >= 0; index -= 1) {
216
+ const event = childSession.snapshotEvents()[index];
217
+ if (event?.type !== 'tool/call')
218
+ continue;
219
+ if (event.data.callId === callId)
220
+ return event.seq;
221
+ }
222
+ return undefined;
223
+ }
224
+ /**
225
+ * Append one mirrored `tool/call` or `tool/result` event — the native tool
226
+ * card pair, so the child session renders the sub-dsh's tool activity with
227
+ * the standard conversation's tool rows instead of not at all. Shared by the
228
+ * file mirror's span loop and the live driver's per-event mirror.
229
+ */
230
+ function appendMirroredToolEvent(childSession, event) {
231
+ if (event.type === 'tool/call') {
232
+ childSession.append('tool/call', event.data);
233
+ }
234
+ else if (event.type === 'tool/result') {
235
+ const data = event.data;
236
+ const callSeq = findMirroredCallSeq(childSession, String(data.message.source.callId));
237
+ childSession.append('tool/result', event.data, {
238
+ surfaceOp: 'append',
239
+ ...callSeq === undefined ? {} : { sourceEventSeqs: [callSeq] },
240
+ });
241
+ }
242
+ return mirroredToolText(event);
243
+ }
244
+ /**
245
+ * Append one mirrored `step/start` or `step/end` boundary verbatim — the event
246
+ * carries its own (turn, step) coordinates, so the child session's location
247
+ * index registers the same step the sub-dsh wrote. Shared by the file
248
+ * mirror's span loop and the live driver's per-event mirror. An interrupted
249
+ * step crosses as an OPEN pair (start without end): the location resolves to
250
+ * the step either way, and no boundary is ever synthesized.
251
+ */
252
+ function appendMirroredStepBoundary(childSession, event) {
253
+ if (event.type === 'step/start') {
254
+ childSession.append('step/start', event.data);
255
+ }
256
+ else if (event.type === 'step/end') {
257
+ childSession.append('step/end', event.data);
258
+ }
259
+ }
260
+ /**
261
+ * Append one appendable message event verbatim and report its delta text —
262
+ * the single append path shared by the file mirror's span loop and the live
263
+ * driver's per-event mirror, so both transports produce identical child
264
+ * sessions.
265
+ */
266
+ function appendMirroredMessageEvent(childSession, event) {
267
+ if (event.type === 'user/message') {
268
+ childSession.append('user/message', event.data, { surfaceOp: 'append' });
269
+ }
270
+ else if (event.type === 'assistant/message') {
271
+ // Verbatim copy: content blocks (text/reasoning) and usage ride the
272
+ // event's own fields, so the tokenUsage projection counts the round.
273
+ childSession.append('assistant/message', event.data, { surfaceOp: 'append' });
274
+ }
275
+ return mirroredEventText(event);
276
+ }
277
+ /**
278
+ * Mirror ONE live-pushed sub-dsh session event (the serve mode's
279
+ * `session/event` wire notification) into the child session. This is the
280
+ * live driver's transport-side entry into the SAME fold the file mirror
281
+ * owns: the filter (only the caller task's `user/message`, every
282
+ * `assistant/message`, the tool events, and the `step/start`/`step/end`
283
+ * boundaries cross — verbatim; turn boundaries stay the parent's; scaffolding
284
+ * stays behind) and the verbatim append are exactly `mirrorDshSession`'s
285
+ * span-loop rules. The 0.1.5 sub-dsh emits no per-chunk events (the
286
+ * `assistant/chunk` type is retired — an interrupted attempt settles as
287
+ * `assistant/attempt`, which carries no surface content and never crosses),
288
+ * so the `token` granularity no longer streams deltas into the child log;
289
+ * live text rides the run-progress channel only.
290
+ * The caller owns offset/dedupe (the live runtime pushes
291
+ * each event once) and persistence batching.
292
+ * @param childSession - the parent-side dsh subagent session.
293
+ * @param event - the live event from the resident sub-dsh.
294
+ * @param _options - granularity; retained for caller compatibility — the
295
+ * retirement of per-chunk events leaves the mirror granularity-invariant.
296
+ * @returns the mirrored text for delta progress, or undefined when the event
297
+ * was filtered out or carries no progress text (a step boundary crosses but
298
+ * is structure, not content).
299
+ */
300
+ export function mirrorDshLiveEvent(childSession, event, _options) {
301
+ if (event.type === 'user/message' && event.data.source.kind === 'user') {
302
+ return appendMirroredMessageEvent(childSession, event);
303
+ }
304
+ if (event.type === 'assistant/message') {
305
+ return appendMirroredMessageEvent(childSession, event);
306
+ }
307
+ if (event.type === 'tool/call' || event.type === 'tool/result') {
308
+ return appendMirroredToolEvent(childSession, event);
309
+ }
310
+ if (event.type === 'step/start' || event.type === 'step/end') {
311
+ appendMirroredStepBoundary(childSession, event);
312
+ return undefined;
313
+ }
314
+ return undefined;
315
+ }
316
+ /**
317
+ * Mirror the current round's events from the sub-dsh session into the child
318
+ * session, then persist (a live session syncs through the core's cached write
319
+ * handle, a standalone one through a one-shot handle; see
320
+ * {@link persistIfStandalone}). Runs both
321
+ * from the provider's live poll (while the
322
+ * sub-dsh writes its log in batches — a torn final zstd frame is skipped
323
+ * until the next pass) and after the child process exits (the settle pass);
324
+ * the round's already-mirrored prefix in the child session is the offset, so
325
+ * the settle pass is a no-op when polling kept up. An aborted round still
326
+ * preserves its partial work and real usage. All failures degrade to a warn:
327
+ * the delegation result is already settled.
328
+ * @param ctx - host context carrying session persistence.
329
+ * @param childSession - the parent-side dsh subagent session.
330
+ * @param homeDir - the `dsh` harness's scoped home.
331
+ * @param subSessionId - the sub-dsh session id (same uuid as the child session).
332
+ * @returns the newly mirrored events' texts and the round's mirrored total.
333
+ */
334
+ export async function mirrorDshSession(ctx, childSession, homeDir, subSessionId) {
335
+ const empty = { texts: [], total: 0 };
336
+ try {
337
+ const read = await readSubDshEvents(homeDir, subSessionId);
338
+ if (read === undefined)
339
+ return empty;
340
+ const { events, log } = read;
341
+ // The round mirrors the sub-dsh turn with the same number: the parent's
342
+ // turn/start count IS this round's number (appended before spawn), and the
343
+ // sub-dsh numbers its turns identically across fresh and resume rounds.
344
+ const round = childSession.snapshotEvents().filter(event => event.type === 'turn/start').length;
345
+ if (round === 0)
346
+ return empty;
347
+ let start = -1;
348
+ let end = events.length;
349
+ for (let index = 0; index < events.length; index += 1) {
350
+ const event = events[index];
351
+ if (event?.type !== 'turn/start')
352
+ continue;
353
+ if (start === -1 && event.data.turn === round) {
354
+ start = index;
355
+ continue;
356
+ }
357
+ if (start !== -1) {
358
+ end = index;
359
+ break;
360
+ }
361
+ }
362
+ if (start === -1)
363
+ return empty;
364
+ // The child session's messages after its last turn/start are this round's
365
+ // already-mirrored prefix (a live poll may have appended them); skip it so
366
+ // the settle pass never duplicates what polling mirrored. The span is
367
+ // filtered to APPENDABLE events first — scaffolding user messages
368
+ // (non-'user' sources) never cross, so they must not occupy skip
369
+ // positions either. The two filter sets must stay IDENTICAL — step
370
+ // boundaries included on both sides — or a repeated pass misaligns the
371
+ // prefix and re-appends (a second step/start for the same (turn, step)
372
+ // makes the live assembler throw).
373
+ let lastTurnStart = -1;
374
+ for (let index = 0; index < childSession.snapshotEvents().length; index += 1) {
375
+ if (childSession.snapshotEvents()[index]?.type === 'turn/start')
376
+ lastTurnStart = index;
377
+ }
378
+ const mirrored = childSession.snapshotEvents().slice(lastTurnStart + 1)
379
+ .filter(event => event.type === 'user/message' || event.type === 'assistant/message'
380
+ || event.type === 'tool/call' || event.type === 'tool/result'
381
+ || event.type === 'step/start' || event.type === 'step/end')
382
+ .length;
383
+ const roundSpan = events.slice(start, end)
384
+ .filter(event => (event.type === 'user/message' && event.data.source.kind === 'user')
385
+ || event.type === 'assistant/message'
386
+ || event.type === 'tool/call'
387
+ || event.type === 'tool/result'
388
+ || event.type === 'step/start'
389
+ || event.type === 'step/end');
390
+ const span = roundSpan.slice(mirrored);
391
+ const texts = [];
392
+ let appended = 0;
393
+ for (const event of span) {
394
+ appended += 1;
395
+ if (event.type === 'step/start' || event.type === 'step/end') {
396
+ // A boundary is structure, not content: it crosses verbatim (its own
397
+ // (turn, step) coordinates) but contributes no delta text.
398
+ appendMirroredStepBoundary(childSession, event);
399
+ continue;
400
+ }
401
+ texts.push(event.type === 'tool/call' || event.type === 'tool/result'
402
+ ? appendMirroredToolEvent(childSession, event)
403
+ : appendMirroredMessageEvent(childSession, event));
404
+ }
405
+ // The round's observation comes from the round's OWN window (the
406
+ // pre-skip roundSpan, not the delta): the last assistant event names the
407
+ // model that ran, and the assistant events' usage sums to the round's
408
+ // accounting, whatever the live-poll / settle split was.
409
+ const observation = roundObservation(roundSpan);
410
+ // todo/write passthrough, counted independently of the message prefix
411
+ // skip: the snapshot is a standing whole list (last-wins), so a pass
412
+ // appends the round's latest snapshot only when it differs from the
413
+ // child's last mirrored one — repeated passes never duplicate an
414
+ // identical snapshot, and intermediate snapshots stay out of the log.
415
+ let todosAppended = 0;
416
+ const latestTodos = events.slice(start, end).filter(event => event.type === 'todo/write').at(-1);
417
+ const mirroredTodos = childSession.snapshotEvents().slice(lastTurnStart + 1)
418
+ .filter(event => event.type === 'todo/write');
419
+ if (latestTodos !== undefined && latestTodos.type === 'todo/write') {
420
+ if (JSON.stringify(mirroredTodos.at(-1)?.data) !== JSON.stringify(latestTodos.data)) {
421
+ // todo/write's append takes no surface options (log-only UI state).
422
+ childSession.append('todo/write', latestTodos.data);
423
+ todosAppended = 1;
424
+ }
425
+ }
426
+ if (appended > 0 || todosAppended > 0) {
427
+ await persistChildSession(ctx, childSession);
428
+ }
429
+ return {
430
+ texts,
431
+ total: mirrored + mirroredTodos.length + appended + todosAppended,
432
+ sessionLogFile: log.filename,
433
+ ...observation,
434
+ };
435
+ }
436
+ catch (error) {
437
+ ctx.logger.warn(`subagent-dsh: session mirror failed: ${error instanceof Error ? error.message : String(error)}`);
438
+ return empty;
439
+ }
440
+ }
441
+ /**
442
+ * Persist the child session's mirrored events. Thin wrapper over the core's
443
+ * {@link persistChildSession}: a live session syncs through the core's cached
444
+ * per-child write handle (host 0.1.5's live write-behind never checkpoints a
445
+ * mirror session — it has no agent loop — so production child logs stayed
446
+ * header-only until the explicit sync), a standalone one through a one-shot
447
+ * handle flow. Kept under the historical name for the live driver.
448
+ */
449
+ export async function persistIfStandalone(ctx, childSession) {
450
+ await persistChildSession(ctx, childSession);
451
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DSH Delegation",
4
+ "description": "Delegate to dsh itself as a scoped, resumable local CLI process."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "DSH 委派",
4
+ "description": "把任务委派给独立的本地子 dsh 进程,会话可跨轮续接。"
5
+ }
6
+ }
package/package.json ADDED
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "@khorsheed/dsh-local-agent-dsh",
3
+ "description": "dsh harness for dsh-local-agent: delegate to dsh itself as a local CLI — scoped DSH_HOME under the shared homes root, one-shot sub-dsh runs visible in the 子代理 surface, caller-supplied session ids, resume across rounds, DeepSeek toggle in settings",
4
+ "keywords": [
5
+ "dsh",
6
+ "dsh-plugin",
7
+ "cordis",
8
+ "local-agent",
9
+ "subagent"
10
+ ],
11
+ "version": "0.1.0-rc.6",
12
+ "type": "module",
13
+ "main": "lib/index.js",
14
+ "types": "lib/types/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/types/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./invariant": {
21
+ "types": "./lib/types/invariant.d.ts",
22
+ "default": "./lib/invariant.js"
23
+ },
24
+ "./records": {
25
+ "types": "./lib/types/records.d.ts",
26
+ "default": "./lib/records.js"
27
+ },
28
+ "./provision": {
29
+ "types": "./lib/types/provision.d.ts",
30
+ "default": "./lib/provision.js"
31
+ },
32
+ "./dsh-cli-provider": {
33
+ "types": "./lib/types/dsh-cli-provider.d.ts",
34
+ "default": "./lib/dsh-cli-provider.js"
35
+ },
36
+ "./client": {
37
+ "types": "./lib/types/client/index.d.ts",
38
+ "default": "./lib/client.js"
39
+ },
40
+ "./src/*": "./src/*",
41
+ "./cordis.patch.yml": "./cordis.patch.yml",
42
+ "./package.json": "./package.json",
43
+ "./locale/*.json": "./locale/*.json"
44
+ },
45
+ "dsh": {
46
+ "references": [
47
+ "@khorsheed/dsh-bundle-local-agent"
48
+ ],
49
+ "client": {
50
+ "inject": [
51
+ "@deepseek-ai/dsh-api-remotes",
52
+ "@deepseek-ai/dsh-client-locale",
53
+ "@deepseek-ai/dsh-client-ui-primitives",
54
+ "@deepseek-ai/dsh-client-ui-settings",
55
+ "@deepseek-ai/dsh-client-ui-slots"
56
+ ],
57
+ "platform": "web"
58
+ },
59
+ "bundle": {
60
+ "patch": "./cordis.patch.yml"
61
+ },
62
+ "compat": {
63
+ "minHost": "0.1.5-rc.1",
64
+ "notes": "liveMirrorGranularity token: host 0.1.5 retired the per-chunk session event, so token-mode deltas no longer land in the child session log — they ride the run-progress channel and the round settles as one combined assistant/message (identical final text); a late-arriving usage whose carrier message already mirrored is dropped with a warn (no usage-backfill event exists)",
65
+ "verifiedHost": "0.1.5-rc.1"
66
+ }
67
+ },
68
+ "files": [
69
+ "lib",
70
+ "cordis.patch.yml",
71
+ "locale/*.json"
72
+ ],
73
+ "license": "MIT",
74
+ "dependencies": {
75
+ "@khorsheed/dsh-local-agent": "^0.1.0-rc.6",
76
+ "@khorsheed/dsh-local-agent-dsh-headless": "^0.1.0-rc.6",
77
+ "@khorsheed/dsh-local-agent-tool-subagent": "^0.1.0-rc.6"
78
+ },
79
+ "peerDependencies": {
80
+ "@deepseek-ai/cordis": "^4.0.1",
81
+ "@deepseek-ai/dsh-agent-default-model": "^0.1.0-rc.6",
82
+ "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6",
83
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
84
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
85
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
86
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
87
+ "@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
88
+ "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
89
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
90
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
91
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
92
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
93
+ "@deepseek-ai/dsh-subagent": "^0.1.0-rc.6",
94
+ "@deepseek-ai/dsh-subprocess": "^0.1.0-rc.6",
95
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
96
+ "react": "^18.2.0"
97
+ },
98
+ "peerDependenciesMeta": {
99
+ "@deepseek-ai/dsh-agent-default-model": {
100
+ "optional": true
101
+ },
102
+ "@deepseek-ai/dsh-api-remotes": {
103
+ "optional": true
104
+ },
105
+ "@deepseek-ai/dsh-client-locale": {
106
+ "optional": true
107
+ },
108
+ "@deepseek-ai/dsh-client-ui-primitives": {
109
+ "optional": true
110
+ },
111
+ "@deepseek-ai/dsh-client-ui-settings": {
112
+ "optional": true
113
+ },
114
+ "@deepseek-ai/dsh-client-ui-slots": {
115
+ "optional": true
116
+ },
117
+ "react": {
118
+ "optional": true
119
+ }
120
+ }
121
+ }