dsh-side-chat-plus 0.3.3 → 0.4.0-alpha.1
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/README.md +10 -1
- package/README.zh.md +8 -1
- package/dsh.plugin.json +3 -3
- package/lib/client-registry.js +416 -170
- package/lib/client-registry.js.map +1 -1
- package/lib/client.js +414 -168
- package/lib/client.js.map +1 -1
- package/lib/index.js +66 -21
- package/lib/types/client/api.d.ts +1 -0
- package/lib/types/client/locales.d.ts +20 -0
- package/lib/types/context-types.d.ts +45 -14
- package/lib/types/settings-shared.d.ts +4 -0
- package/package.json +39 -38
- package/src/client/api.ts +1 -1
- package/src/client/client.module.css +19 -0
- package/src/client/index.tsx +336 -62
- package/src/client/locales.ts +20 -0
- package/src/context-types.ts +42 -16
- package/src/index.ts +63 -13
- package/src/settings-shared.ts +6 -0
package/src/context-types.ts
CHANGED
|
@@ -43,11 +43,14 @@ export interface SideSessionEvent {
|
|
|
43
43
|
sourceEventSeqs?: number[]
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* The live session face the host reads. Event reads go through
|
|
48
|
+
* `sessionQuery.readSession` instead: DSH 0.1.6 deprecated the Session's
|
|
49
|
+
* synchronous event getters (`events` before 0.1.5, `snapshotEvents` after).
|
|
50
|
+
*/
|
|
47
51
|
export interface SideSession {
|
|
48
52
|
id: string
|
|
49
53
|
header: SideSessionHeader
|
|
50
|
-
events?: readonly SideSessionEvent[]
|
|
51
54
|
requestHeader?: () => { config?: { provider?: string; model?: string; reasoningEffort?: string; maxTokens?: number } } | undefined
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -206,14 +209,6 @@ export interface SideSessionQuery {
|
|
|
206
209
|
readSession(sessionId: string): Promise<SideSessionLogSnapshot>
|
|
207
210
|
}
|
|
208
211
|
|
|
209
|
-
/** Sandbox mode vocabulary (read from the session override). */
|
|
210
|
-
export type SideSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'
|
|
211
|
-
|
|
212
|
-
/** The sandbox policy face. */
|
|
213
|
-
export interface SideSandboxPolicy {
|
|
214
|
-
overrideOf(session: SideSession): SideSandboxMode | undefined
|
|
215
|
-
}
|
|
216
|
-
|
|
217
212
|
/** One permission preset option. */
|
|
218
213
|
export interface SidePresetOption {
|
|
219
214
|
value: string
|
|
@@ -221,11 +216,16 @@ export interface SidePresetOption {
|
|
|
221
216
|
description?: string
|
|
222
217
|
}
|
|
223
218
|
|
|
224
|
-
/** The permission presets face. */
|
|
219
|
+
/** The permission presets face (`current` reads the SESSION, not event rows). */
|
|
225
220
|
export interface SidePermissionPresets {
|
|
226
|
-
current(
|
|
221
|
+
current(session: SideSession): string
|
|
227
222
|
set(session: SideSession, name: string): void
|
|
228
|
-
|
|
223
|
+
/** 0.1.6+: process-level catalog of selectable presets. */
|
|
224
|
+
catalog?(): { options: SidePresetOption[] }
|
|
225
|
+
/** 0.1.6+: the preset selected as the default for future sessions. */
|
|
226
|
+
defaultPreset?: string
|
|
227
|
+
/** 0.1.5 and earlier: combined catalog plus current value. Removed in 0.1.6. */
|
|
228
|
+
selectFor?(state: unknown): { options: SidePresetOption[]; currentValue: string }
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/** The agent presets face (composeFrom joins a child to the parent's composition). */
|
|
@@ -304,10 +304,35 @@ export interface SideSettingsService {
|
|
|
304
304
|
|
|
305
305
|
/** The client slots service face (settings.section registration). */
|
|
306
306
|
export interface SideSlotsService {
|
|
307
|
-
inject(name: string, factory: () => () => void): void
|
|
307
|
+
inject(name: string, factory: () => () => void): () => void
|
|
308
308
|
register(options: Record<string, unknown>, component: unknown): () => void
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
/** One right-sidebar tab type definition (dsh-client-ui-sidebar-right contract, mirror). */
|
|
312
|
+
export interface SideTabDefinition {
|
|
313
|
+
/** Stable unique key; the body seat entry registers under the same key. */
|
|
314
|
+
id: string
|
|
315
|
+
/** Discriminator used by `openTab(kind)` and the pane dispatch. */
|
|
316
|
+
kind: string
|
|
317
|
+
/** Registry band ('builtin' | 'extension' | 'fallback'); defaults to the extension band. */
|
|
318
|
+
priority?: string
|
|
319
|
+
/** Fresh title for the tab chip captured at open time. */
|
|
320
|
+
title(address: string): string
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** The right-sidebar tab-type registry face (`ctx.sidebarRight.tabs`). */
|
|
324
|
+
export interface SideSidebarRightTabs {
|
|
325
|
+
/** Register a tab type; returns a disposer (hold it in the caller's ctx.effect). */
|
|
326
|
+
register(definition: SideTabDefinition): () => void
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The cross-plugin right-sidebar face (`ctx.sidebarRight`, ui-sidebar-right). */
|
|
330
|
+
export interface SideSidebarRight {
|
|
331
|
+
tabs: SideSidebarRightTabs
|
|
332
|
+
/** Open a page type by kind in the mounted session; reveals the column. */
|
|
333
|
+
openTab(kind: string, options?: { paneId?: string; replaceTab?: boolean; revealIfOpened?: boolean }): void
|
|
334
|
+
}
|
|
335
|
+
|
|
311
336
|
/** One durable image reference (mirror of ImageAttachmentRef). */
|
|
312
337
|
export interface SideImageAttachmentRef {
|
|
313
338
|
attachmentId: string
|
|
@@ -325,6 +350,8 @@ export interface SideAttachmentStore {
|
|
|
325
350
|
maxImagesPerMessage: number
|
|
326
351
|
maxMessageImageBytes: number
|
|
327
352
|
maxImagePixels: number
|
|
353
|
+
/** 0.1.6+ per-image intrinsic width/height ceiling; absent on earlier lines. */
|
|
354
|
+
maxImageDimension?: number
|
|
328
355
|
mediaTypes: readonly string[]
|
|
329
356
|
}
|
|
330
357
|
validateImage(input: { data: Uint8Array; mediaType: string; name?: string }): Promise<void>
|
|
@@ -335,7 +362,7 @@ export interface SideAttachmentStore {
|
|
|
335
362
|
/** The host command registry face (`ctx.commands`). */
|
|
336
363
|
export interface SideCommandsService {
|
|
337
364
|
list(agent: SideAgent): Array<{ name: string; description: string }>
|
|
338
|
-
execute(agent: SideAgent, line: string, signal: AbortSignal): Promise<unknown>
|
|
365
|
+
execute(agent: SideAgent, line: string, submittedAttachments: readonly unknown[], signal: AbortSignal): Promise<unknown>
|
|
339
366
|
}
|
|
340
367
|
|
|
341
368
|
/** The per-session composer input face this plugin writes to (draft-only). */
|
|
@@ -363,7 +390,6 @@ declare module 'cordis' {
|
|
|
363
390
|
agents: SideAgentsService
|
|
364
391
|
workspaceRegistry: SideWorkspaceRegistry
|
|
365
392
|
sessionQuery: SideSessionQuery
|
|
366
|
-
sandboxPolicy: SideSandboxPolicy
|
|
367
393
|
permissionPresets: SidePermissionPresets
|
|
368
394
|
agentPresets: SideAgentPresets
|
|
369
395
|
llm: SideLlm
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
SideImageAttachmentRef,
|
|
24
24
|
SideInstallModelSelection,
|
|
25
25
|
SideModelSelectionRef,
|
|
26
|
+
SidePresetOption,
|
|
26
27
|
SideSession,
|
|
27
28
|
SideSessionEvent,
|
|
28
29
|
} from './context-types.ts'
|
|
@@ -40,7 +41,6 @@ export const inject = [
|
|
|
40
41
|
'agents',
|
|
41
42
|
'workspaceRegistry',
|
|
42
43
|
'sessionQuery',
|
|
43
|
-
'sandboxPolicy',
|
|
44
44
|
'permissionPresets',
|
|
45
45
|
'agentPresets',
|
|
46
46
|
'llm',
|
|
@@ -187,6 +187,7 @@ const PrefsSchema: z<SubchatPrefs> = z.object({
|
|
|
187
187
|
sendImmediately: z.boolean().default(SUBCHAT_PREFS_DEFAULTS.sendImmediately),
|
|
188
188
|
defaultPrompt: z.string().default(SUBCHAT_PREFS_DEFAULTS.defaultPrompt),
|
|
189
189
|
bringMode: z.union(['draft', 'context']).default(SUBCHAT_PREFS_DEFAULTS.bringMode),
|
|
190
|
+
panelHome: z.union(['floating', 'sidebar-right']).default(SUBCHAT_PREFS_DEFAULTS.panelHome),
|
|
190
191
|
})
|
|
191
192
|
|
|
192
193
|
/** Live settings face (bound when the settings service is mounted). */
|
|
@@ -311,15 +312,24 @@ function buildApi(ctx: Context, sideChats: Map<string, SidechatRecord>, getSetti
|
|
|
311
312
|
const childId = requireString(payload, 'childId')
|
|
312
313
|
const line = requireString(payload, 'line')
|
|
313
314
|
const child = childOf(childId)
|
|
314
|
-
const result = await ctx.commands.execute(child, line, new AbortController().signal)
|
|
315
|
+
const result = await ctx.commands.execute(child, line, [], new AbortController().signal)
|
|
315
316
|
return { executed: result !== undefined }
|
|
316
317
|
}
|
|
317
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Read a side chat's event log through the session-query service. DSH 0.1.6
|
|
321
|
+
* deprecated the Session's synchronous event getters (`snapshotEvents`,
|
|
322
|
+
* `eventAt`, `ownEvents`); `readSession` is the live-preferred asynchronous
|
|
323
|
+
* read that supersedes them, and the transcript routes already use it.
|
|
324
|
+
*/
|
|
325
|
+
const sessionEvents = async (sessionId: string): Promise<readonly SideSessionEvent[]> =>
|
|
326
|
+
(await ctx.sessionQuery.readSession(sessionId)).events
|
|
327
|
+
|
|
318
328
|
/** Fold one side-chat agent's plan/goal state for its composer chrome. */
|
|
319
|
-
const state = (payload: unknown): { plan: { active: boolean; pending: boolean }; goal: { id: string; objective: string } | null } => {
|
|
329
|
+
const state = async (payload: unknown): Promise<{ plan: { active: boolean; pending: boolean }; goal: { id: string; objective: string } | null }> => {
|
|
320
330
|
const childId = requireString(payload, 'childId')
|
|
321
|
-
|
|
322
|
-
const events =
|
|
331
|
+
childOf(childId)
|
|
332
|
+
const events = await sessionEvents(childId)
|
|
323
333
|
// Plan fold mirrors dsh-plan-mode's `plan` projection.
|
|
324
334
|
let planActive = false
|
|
325
335
|
let planWanted: boolean | null = null
|
|
@@ -431,7 +441,7 @@ function buildApi(ctx: Context, sideChats: Map<string, SidechatRecord>, getSetti
|
|
|
431
441
|
// Client-supplied preset wins; otherwise inherit the launching
|
|
432
442
|
// conversation's permission preset (skip custom).
|
|
433
443
|
const explicitPreset = typeof record.preset === 'string' && record.preset !== '' ? record.preset : undefined
|
|
434
|
-
const parentPreset = ctx.permissionPresets.current(parent.session
|
|
444
|
+
const parentPreset = ctx.permissionPresets.current(parent.session)
|
|
435
445
|
const preset = explicitPreset ?? parentPreset
|
|
436
446
|
if (preset !== 'custom') {
|
|
437
447
|
ctx.permissionPresets.set(handle.agent.session, preset)
|
|
@@ -467,7 +477,7 @@ function buildApi(ctx: Context, sideChats: Map<string, SidechatRecord>, getSetti
|
|
|
467
477
|
}
|
|
468
478
|
|
|
469
479
|
/** List the side chats launched by one parent conversation. */
|
|
470
|
-
const list = (payload: unknown): { items: Array<{ childId: string; running: boolean; runningSince?: number }> } => {
|
|
480
|
+
const list = async (payload: unknown): Promise<{ items: Array<{ childId: string; running: boolean; runningSince?: number }> }> => {
|
|
471
481
|
const parentSessionId = requireString(payload, 'parentSessionId')
|
|
472
482
|
const items: Array<{ childId: string; running: boolean; runningSince?: number }> = []
|
|
473
483
|
for (const record of sideChats.values()) {
|
|
@@ -482,7 +492,8 @@ function buildApi(ctx: Context, sideChats: Map<string, SidechatRecord>, getSetti
|
|
|
482
492
|
// back to the open-turn event scan.
|
|
483
493
|
const status = (agent as { status?: string }).status
|
|
484
494
|
const statusRunning = status === 'running'
|
|
485
|
-
|
|
495
|
+
// Only the status-less fallback pays for a log read.
|
|
496
|
+
const eventRunningSince = status === undefined ? openTurnStart(await sessionEvents(record.childId)) : undefined
|
|
486
497
|
const running = statusRunning || (status === undefined && eventRunningSince !== undefined)
|
|
487
498
|
const runningSince = running ? (eventRunningSince ?? Date.now()) : undefined
|
|
488
499
|
items.push({
|
|
@@ -510,7 +521,7 @@ function buildApi(ctx: Context, sideChats: Map<string, SidechatRecord>, getSetti
|
|
|
510
521
|
}
|
|
511
522
|
|
|
512
523
|
/** The deployment-resolved image policy (for client-side fast-path checks). */
|
|
513
|
-
const limits = (): { mediaTypes: string[]; maxImageBytes: number; maxImagesPerMessage: number; maxMessageImageBytes: number; maxImagePixels: number } => {
|
|
524
|
+
const limits = (): { mediaTypes: string[]; maxImageBytes: number; maxImagesPerMessage: number; maxMessageImageBytes: number; maxImagePixels: number; maxImageDimension?: number } => {
|
|
514
525
|
const l = ctx.attachments.imageLimits
|
|
515
526
|
return {
|
|
516
527
|
mediaTypes: [...l.mediaTypes],
|
|
@@ -518,6 +529,8 @@ function buildApi(ctx: Context, sideChats: Map<string, SidechatRecord>, getSetti
|
|
|
518
529
|
maxImagesPerMessage: l.maxImagesPerMessage,
|
|
519
530
|
maxMessageImageBytes: l.maxMessageImageBytes,
|
|
520
531
|
maxImagePixels: l.maxImagePixels,
|
|
532
|
+
// Added in 0.1.6; earlier lines leave it absent so the client skips the check.
|
|
533
|
+
...(l.maxImageDimension === undefined ? {} : { maxImageDimension: l.maxImageDimension }),
|
|
521
534
|
}
|
|
522
535
|
}
|
|
523
536
|
|
|
@@ -706,11 +719,31 @@ function buildApi(ctx: Context, sideChats: Map<string, SidechatRecord>, getSetti
|
|
|
706
719
|
|
|
707
720
|
/** Permission-preset options for the side-chat selector. */
|
|
708
721
|
const permissions = (): { options: Array<{ value: string; name: string; description?: string }>; current: string } => {
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
722
|
+
const presets = ctx.permissionPresets
|
|
723
|
+
const toOption = (o: SidePresetOption): { value: string; name: string; description?: string } => ({
|
|
724
|
+
value: o.value,
|
|
725
|
+
name: o.name,
|
|
726
|
+
...(o.description === undefined ? {} : { description: o.description }),
|
|
727
|
+
})
|
|
728
|
+
// 0.1.6 split the combined `selectFor(knobState)` into a process-level
|
|
729
|
+
// `catalog()` plus a separate current value. Prefer the new face and fall
|
|
730
|
+
// back to the old one so both release lines serve this route.
|
|
731
|
+
const catalog = presets.catalog
|
|
732
|
+
if (typeof catalog === 'function') {
|
|
733
|
+
return {
|
|
734
|
+
options: catalog.call(presets).options.map(toOption),
|
|
735
|
+
current: presets.defaultPreset ?? 'custom',
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const selectFor = presets.selectFor
|
|
739
|
+
if (typeof selectFor === 'function') {
|
|
740
|
+
const select = selectFor.call(presets, {})
|
|
741
|
+
return {
|
|
742
|
+
options: select.options.map(toOption),
|
|
743
|
+
current: select.currentValue,
|
|
744
|
+
}
|
|
713
745
|
}
|
|
746
|
+
throw new SidechatError('permissions-unavailable', 'the permission-presets service exposes neither catalog() nor selectFor()', 503)
|
|
714
747
|
}
|
|
715
748
|
|
|
716
749
|
return {
|
|
@@ -814,6 +847,23 @@ export function apply(ctx: Context): void {
|
|
|
814
847
|
}
|
|
815
848
|
}, 'dsh-side-chat: dispose side chats')
|
|
816
849
|
|
|
850
|
+
// DSH 0.1.6 lists archived sessions in Settings and lets the user restore
|
|
851
|
+
// one. A side chat is archived precisely so it stays out of the session
|
|
852
|
+
// list, so at activation re-archive every recorded side chat that a restore
|
|
853
|
+
// (or an older run) left visible again. Best-effort: records whose session
|
|
854
|
+
// no longer exists are simply skipped.
|
|
855
|
+
ctx.effect(() => {
|
|
856
|
+
void (async () => {
|
|
857
|
+
for (const record of await readRecords()) {
|
|
858
|
+
try {
|
|
859
|
+
await ctx.workspaceRegistry.archiveSession(record.childId)
|
|
860
|
+
} catch {
|
|
861
|
+
// Unknown/expired ids are expected as the record list ages out.
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
})()
|
|
865
|
+
}, 'dsh-side-chat: re-archive recorded side chats')
|
|
866
|
+
|
|
817
867
|
ctx.effect(() => ctx.webServer.register({
|
|
818
868
|
kind: 'prefix',
|
|
819
869
|
path: '/sidechat/api',
|
package/src/settings-shared.ts
CHANGED
|
@@ -12,6 +12,9 @@ export const SUBCHAT_PREFS_NS = 'dsh-side-chat'
|
|
|
12
12
|
/** How a brought-back reply lands in the main conversation. */
|
|
13
13
|
export type BringMode = 'draft' | 'context'
|
|
14
14
|
|
|
15
|
+
/** Where the side-chat panel lives when a side chat is open. */
|
|
16
|
+
export type PanelHome = 'floating' | 'sidebar-right'
|
|
17
|
+
|
|
15
18
|
/** User-facing side-chat preferences. */
|
|
16
19
|
export interface SubchatPrefs {
|
|
17
20
|
/** Whether the "look up workspace / parent when needed" switch defaults on. */
|
|
@@ -22,6 +25,8 @@ export interface SubchatPrefs {
|
|
|
22
25
|
defaultPrompt: string
|
|
23
26
|
/** How brought-back content lands: into the composer draft, or as a collapsed context row. */
|
|
24
27
|
bringMode: BringMode
|
|
28
|
+
/** Which container hosts the panel: the classic floating right-edge panel, or the new built-in right sidebar. */
|
|
29
|
+
panelHome: PanelHome
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
/** Fallback prefs used whenever the settings document is unreachable or malformed. */
|
|
@@ -30,4 +35,5 @@ export const SUBCHAT_PREFS_DEFAULTS: SubchatPrefs = {
|
|
|
30
35
|
sendImmediately: true,
|
|
31
36
|
defaultPrompt: '',
|
|
32
37
|
bringMode: 'draft',
|
|
38
|
+
panelHome: 'sidebar-right',
|
|
33
39
|
}
|