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.
@@ -43,11 +43,14 @@ export interface SideSessionEvent {
43
43
  sourceEventSeqs?: number[]
44
44
  }
45
45
 
46
- /** The live session face the host reads (header + append-only event log). */
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(events: readonly SideSessionEvent[]): string
221
+ current(session: SideSession): string
227
222
  set(session: SideSession, name: string): void
228
- selectFor(state: unknown): { options: SidePresetOption[]; currentValue: string }
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
- const child = childOf(childId)
322
- const events = child.session.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.events ?? [])
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
- const eventRunningSince = openTurnStart(agent.session.events ?? [])
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 select = ctx.permissionPresets.selectFor({})
710
- return {
711
- options: select.options.map((o) => ({ value: o.value, name: o.name, ...(o.description === undefined ? {} : { description: o.description }) })),
712
- current: select.currentValue,
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',
@@ -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
  }