dsh-coding-sidebar 1.0.2 → 1.0.3

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/lib/index.js CHANGED
@@ -2962,7 +2962,7 @@ const MIRROR_MAX_ENTRIES = 200;
2962
2962
  * The live job_output mirror: subscribes to the session append feed and
2963
2963
  * caches the job_output traces the session store's own log can lag behind
2964
2964
  * (after a host restart the store session stays frozen at its rehydration
2965
- * boundary, so `session.events` misses everything appended since — the very
2965
+ * boundary, so a stale snapshot misses everything appended since — the very
2966
2966
  * reads the pane exists to show). Zero DSH writes: the api-proxy pushes the
2967
2967
  * same feed to browsers.
2968
2968
  */
@@ -3025,7 +3025,8 @@ function buildJobsApi(ctx, outputLimit) {
3025
3025
  const sessionId = requireString(payload, "sessionId");
3026
3026
  const id = requireString(payload, "id");
3027
3027
  const bySeq = /* @__PURE__ */ new Map();
3028
- for (const event of ctx.sessions.get(sessionId)?.events ?? []) {
3028
+ const store = ctx.sessions.get(sessionId);
3029
+ for (const event of (store?.snapshotEvents !== void 0 ? store.snapshotEvents() : []) ?? []) {
3029
3030
  const trace = traceOf(event);
3030
3031
  if (trace !== void 0) bySeq.set(trace.seq, trace);
3031
3032
  }
@@ -3420,7 +3421,8 @@ function buildSubagentLiveApi(ctx) {
3420
3421
  if (entry.kind !== "child" || entry.activity !== "running") continue;
3421
3422
  if (entry.label?.startsWith("Side: ") ?? false) continue;
3422
3423
  try {
3423
- const activity = lastActivity(ctx.sessions.get(entry.id)?.events ?? [], 12);
3424
+ const stored = ctx.sessions.get(entry.id);
3425
+ const activity = lastActivity(stored?.snapshotEvents !== void 0 ? stored.snapshotEvents() : [], 12);
3424
3426
  if (activity.text !== void 0 || activity.tool !== void 0) live[entry.id] = activity;
3425
3427
  } catch {}
3426
3428
  }
@@ -3480,8 +3482,10 @@ async function composeChildSetup(ctx, presetId) {
3480
3482
  async function composePersistedSetup(ctx, childId) {
3481
3483
  const persistence = ctx.get("sessionPersistence");
3482
3484
  if (persistence === void 0) return () => Promise.resolve();
3483
- const inspected = await persistence.inspect(childId);
3484
- const presetId = resolvePresetId(inspected.meta, inspected.events);
3485
+ const handle = await persistence.open(childId, "read");
3486
+ const { events } = await handle.read();
3487
+ const presetId = resolvePresetId(handle.header, events);
3488
+ await handle.close();
3485
3489
  const presets = ctx.get("agentPresets");
3486
3490
  if (presets === void 0 || presetId === void 0) return () => Promise.resolve();
3487
3491
  const resolved = await presets.resolve(presetId);
@@ -3542,8 +3546,8 @@ function buildSidechatApi(ctx) {
3542
3546
  const parent = liveThreadAgent(ctx, sessionId);
3543
3547
  if (parent === void 0) throw new SidebarError("sidechat-error", `parent session "${sessionId}" is not running`, 409);
3544
3548
  const parentSession = parent.session;
3545
- const inheritance = buildSidechatInheritance(parentSession.events);
3546
- const { agentPreset, setup } = await composeChildSetup(ctx, resolvePresetId(parentSession.header, parentSession.events));
3549
+ const inheritance = buildSidechatInheritance(parentSession.snapshotEvents());
3550
+ const { agentPreset, setup } = await composeChildSetup(ctx, resolvePresetId(parentSession.header, parentSession.snapshotEvents()));
3547
3551
  const childId = `session-${randomUUID()}`;
3548
3552
  const label = question === "" ? SIDE_NEW_THREAD_TITLE : sideLabel(question);
3549
3553
  const descriptor = snapshotSubagentDescriptor({
@@ -3565,7 +3569,7 @@ function buildSidechatApi(ctx) {
3565
3569
  meta: {
3566
3570
  ...parentSession.header.cwd === void 0 ? {} : { cwd: parentSession.header.cwd },
3567
3571
  parentSession: parentSession.id,
3568
- seedLength: seed.length,
3572
+ isSeeded: seed.length > 0,
3569
3573
  origin: "subagent",
3570
3574
  delegationDepth: (parentSession.header.delegationDepth ?? 0) + 1,
3571
3575
  ...agentPreset === void 0 ? {} : { agentPreset }
@@ -3622,7 +3626,7 @@ function buildSidechatApi(ctx) {
3622
3626
  throw new SidebarError("sidechat-error", `thread resume failed: ${error instanceof Error ? error.message : String(error)}`, 500);
3623
3627
  }
3624
3628
  }
3625
- if (boundaryDelivered(agent.session.events)) admitFollowup(agent, textPrompt(text));
3629
+ if (boundaryDelivered(agent.session.snapshotEvents())) admitFollowup(agent, textPrompt(text));
3626
3630
  else {
3627
3631
  const parts = [SIDE_BOUNDARY_PROMPT];
3628
3632
  const snapshot = pendingSnapshots.get(childId);
@@ -3668,8 +3672,10 @@ function buildSidechatApi(ctx) {
3668
3672
  }
3669
3673
  const persistence = ctx.get("sessionPersistence");
3670
3674
  if (persistence !== void 0) try {
3671
- const inspected = await persistence.inspect(childId);
3672
- const preset = resolvePresetId(inspected.meta, inspected.events);
3675
+ const handle = await persistence.open(childId, "read");
3676
+ const { events } = await handle.read();
3677
+ const preset = resolvePresetId(handle.header, events);
3678
+ await handle.close();
3673
3679
  return {
3674
3680
  live: false,
3675
3681
  ...preset === void 0 ? {} : { preset }
@@ -3749,7 +3755,9 @@ async function sessionCwdOf(ctx, sessionId, clientCwd) {
3749
3755
  }
3750
3756
  const persistence = ctx.get("sessionPersistence");
3751
3757
  if (persistence !== void 0) {
3752
- const metaCwd = (await persistence.inspect(sessionId)).meta.cwd;
3758
+ const handle = await persistence.open(sessionId, "read");
3759
+ const metaCwd = handle.header.cwd;
3760
+ await handle.close();
3753
3761
  if (metaCwd !== void 0 && metaCwd !== "") try {
3754
3762
  return requireAbsolute(metaCwd);
3755
3763
  } catch {
@@ -78,11 +78,11 @@ export interface SidebarSessionStore {
78
78
  get(id: string): {
79
79
  header: SidebarSessionHeader;
80
80
  /**
81
- * The live session's append-only event log (immutable snapshot; absent
81
+ * The live session's immutable event snapshot (0.1.5 Session API; absent
82
82
  * on sessions the runtime has not hydrated). Read-only access — the
83
83
  * jobs.output route replays `job_output` tool/result rows from it.
84
84
  */
85
- events?: readonly SidebarSessionEvent[];
85
+ snapshotEvents?(fromSeq?: number, toSeqExclusive?: number): readonly SidebarSessionEvent[];
86
86
  } | undefined;
87
87
  }
88
88
  /**
@@ -276,16 +276,25 @@ export interface SidebarSessionTitleService {
276
276
  };
277
277
  }
278
278
  /** The host session-persistence face (mirror of the sessionPersistence
279
- * service): detached inspection of a persisted session, used to compose the
280
- * recorded preset when a Side Chat thread cold-resumes. */
281
- export interface SidebarSessionPersistenceService {
282
- inspect(sessionId: string): Promise<{
283
- meta: {
284
- cwd?: string;
285
- agentPreset?: string;
286
- };
279
+ * service, 0.1.5 handle-seam form): a short-lived read handle over one
280
+ * persisted session, used to compose the recorded preset when a Side Chat
281
+ * thread cold-resumes. */
282
+ export interface SidebarSessionPersistenceHandle {
283
+ readonly header: {
284
+ cwd?: string;
285
+ agentPreset?: string;
286
+ };
287
+ read(fromSeq?: number, toSeqExclusive?: number, options?: {
288
+ signal?: AbortSignal;
289
+ }): Promise<{
287
290
  events: readonly SidebarSessionEvent[];
288
291
  }>;
292
+ close(): Promise<void>;
293
+ }
294
+ export interface SidebarSessionPersistenceService {
295
+ open(sessionId: string, access: 'read', options?: {
296
+ signal?: AbortSignal;
297
+ }): Promise<SidebarSessionPersistenceHandle>;
289
298
  }
290
299
  /** RPC result slot mirror (`RpcResult<T>` on the wire). */
291
300
  export type SidebarRpcResult<T> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-coding-sidebar",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "DSH web plugin: a VSCode-like right sidebar (explorer / editor / terminal / git / browser), isolated per conversation session. Exposes the betterSidebar service for other plugins to register sidebar tabs and file viewers. KCoder-maintained fork of DSH-better-sidebar 0.17.2 (bottom panel removed, upstream-decoupled release line).",
5
5
  "type": "module",
6
6
  "repository": {
@@ -85,19 +85,18 @@
85
85
  "license": "MIT",
86
86
  "peerDependencies": {
87
87
  "@deepseek-ai/cordis": "^4.0.1",
88
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.8",
89
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.8",
90
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.8",
91
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8",
92
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.8",
93
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.8",
94
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.8",
95
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
96
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
97
- "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
98
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
99
- "@deepseek-ai/dsh-subagent": "^0.1.0-rc.8",
100
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
88
+ "@deepseek-ai/dsh-agent": "^0.1.5-rc.1",
89
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
90
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.1",
91
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.1",
92
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.1",
93
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
94
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.1",
95
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
96
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.1",
97
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
98
+ "@deepseek-ai/dsh-subagent": "^0.1.5-rc.1",
99
+ "@deepseek-ai/dsh-tools": "^0.1.5-rc.1",
101
100
  "@huanlin/dsh-plugin-better-locale": "^0.1.0",
102
101
  "react": "^18.2.0",
103
102
  "react-dom": "^18.2.0"
@@ -140,20 +139,20 @@
140
139
  "ws": "^8.18.0"
141
140
  },
142
141
  "devDependencies": {
142
+ "@deepseek-ai/dsh-invariants": "0.1.5-rc.1",
143
143
  "@cordisjs/plugin-loader": "^1.0.0-rc.5",
144
- "@deepseek-ai/dsh-agent": "0.1.2-alpha.2",
145
- "@deepseek-ai/dsh-client-locale": "0.1.2-alpha.2",
146
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.2",
147
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.2",
148
- "@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.2",
149
- "@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.2",
150
- "@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.2",
151
- "@deepseek-ai/dsh-invariants": "0.1.2-alpha.2",
152
- "@deepseek-ai/dsh-llm": "0.1.2-alpha.2",
153
- "@deepseek-ai/dsh-session": "0.1.2-alpha.2",
154
- "@deepseek-ai/dsh-settings": "0.1.2-alpha.2",
155
- "@deepseek-ai/dsh-subagent": "0.1.2-alpha.2",
156
- "@deepseek-ai/dsh-tools": "0.1.2-alpha.2",
144
+ "@deepseek-ai/dsh-agent": "0.1.5-rc.1",
145
+ "@deepseek-ai/dsh-client-locale": "0.1.5-rc.1",
146
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.1",
147
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.1",
148
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.1",
149
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
150
+ "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.1",
151
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.1",
152
+ "@deepseek-ai/dsh-session": "0.1.5-rc.1",
153
+ "@deepseek-ai/dsh-settings": "0.1.5-rc.1",
154
+ "@deepseek-ai/dsh-subagent": "0.1.5-rc.1",
155
+ "@deepseek-ai/dsh-tools": "0.1.5-rc.1",
157
156
  "@huanlin/dsh-plugin-better-locale": "^0.1.0",
158
157
  "@types/node": "^24.0.0",
159
158
  "@types/react": "~18.3.1",
@@ -76,7 +76,7 @@
76
76
  .count {
77
77
  padding: 1px 8px;
78
78
  border-radius: 999px;
79
- background: var(--dsw-alias-accent-soft, var(--dsw-alias-bg-layer-2));
79
+ background: var(--dsw-alias-bg-layer-2);
80
80
  font-size: 11px;
81
81
  line-height: 16px;
82
82
  font-weight: 500;
@@ -836,7 +836,7 @@
836
836
  .versionBadgeTag {
837
837
  padding: 1px 8px;
838
838
  border-radius: 999px;
839
- background: var(--dsw-alias-accent-soft, var(--dsw-alias-border-l2));
839
+ background: var(--dsw-alias-border-l2);
840
840
  color: var(--dsw-alias-label-secondary);
841
841
  font-variant-numeric: tabular-nums;
842
842
  }
@@ -23,7 +23,7 @@
23
23
  gap: 4px;
24
24
  min-height: 36px;
25
25
  padding: 4px 8px 4px 12px;
26
- border-bottom: 1px solid var(--dsw-alias-hairline);
26
+ border-bottom: 1px solid var(--dsw-alias-border-l);
27
27
  }
28
28
 
29
29
  .sidechatHeaderDot {
@@ -39,7 +39,7 @@
39
39
  flex: none;
40
40
  max-width: 55%;
41
41
  padding: 1px 8px;
42
- border: 1px solid var(--dsw-alias-hairline);
42
+ border: 1px solid var(--dsw-alias-border-l);
43
43
  border-radius: 999px;
44
44
  font: var(--dsw-font-xxs-12);
45
45
  color: var(--dsw-alias-label-tertiary);
@@ -108,9 +108,9 @@
108
108
  padding: 6px 14px;
109
109
  border: none;
110
110
  border-radius: 999px;
111
- background: var(--dsw-alias-button-info-fill, var(--dsw-alias-accent));
112
- color: var(--dsw-alias-button-info-label, var(--dsw-alias-accent-ink, #fff));
113
- font: var(--dsw-font-s-13);
111
+ background: var(--dsw-alias-button-info-fill, var(--dsw-alias-interactive-bg-hover-solid));
112
+ color: #fff;
113
+ font: var(--dsw-font-s-14);
114
114
  cursor: pointer;
115
115
  transition: opacity 100ms ease-out;
116
116
  }
@@ -135,7 +135,7 @@
135
135
  flex: none;
136
136
  padding: 4px 12px;
137
137
  font: var(--dsw-font-xxs-12);
138
- color: var(--dsw-alias-danger);
138
+ color: #d5304d;
139
139
  }
140
140
 
141
141
  /* ── transcript ─────────────────────────────────────────────────────── */
@@ -246,13 +246,13 @@
246
246
 
247
247
  .sidechatRowFailed .sidechatRowLabel,
248
248
  .sidechatRowFailed .sidechatRowMeta {
249
- color: var(--dsw-alias-danger);
249
+ color: #d5304d;
250
250
  }
251
251
 
252
252
  .sidechatRowBody {
253
253
  margin: 2px 0 4px 7px;
254
254
  padding: 2px 0 2px 10px;
255
- border-left: 1px solid var(--dsw-alias-hairline);
255
+ border-left: 1px solid var(--dsw-alias-border-l);
256
256
  }
257
257
 
258
258
  .sidechatRowProse {
@@ -277,7 +277,7 @@
277
277
  }
278
278
 
279
279
  .sidechatRowCode + .sidechatRowCode {
280
- border-top: 1px solid var(--dsw-alias-hairline);
280
+ border-top: 1px solid var(--dsw-alias-border-l);
281
281
  }
282
282
 
283
283
  /* Shimmer sweep text: generating (streaming row labels, creating hero). */
@@ -328,7 +328,7 @@
328
328
  gap: 4px;
329
329
  margin: 0 8px 8px;
330
330
  padding: 8px 8px 6px 14px;
331
- border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-hairline));
331
+ border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l));
332
332
  border-radius: 16px;
333
333
  background: var(--dsw-specific-input-major, var(--dsw-alias-bg-base));
334
334
  box-shadow: var(--dsw-shadow-lv2, none);
@@ -380,8 +380,8 @@
380
380
  padding: 0;
381
381
  border: none;
382
382
  border-radius: 50%;
383
- background: var(--dsw-alias-button-info-fill, var(--dsw-alias-accent));
384
- color: var(--dsw-alias-button-info-label, var(--dsw-alias-accent-ink, #fff));
383
+ background: var(--dsw-alias-button-info-fill, var(--dsw-alias-interactive-bg-hover-solid));
384
+ color: #fff;
385
385
  cursor: pointer;
386
386
  /* send ⇄ stop swap: keyed remount plays this, hover only fades. */
387
387
  animation: sidechatBtnIn 120ms ease-out;
@@ -87,11 +87,11 @@ export interface SidebarSessionStore {
87
87
  get(id: string): {
88
88
  header: SidebarSessionHeader
89
89
  /**
90
- * The live session's append-only event log (immutable snapshot; absent
90
+ * The live session's immutable event snapshot (0.1.5 Session API; absent
91
91
  * on sessions the runtime has not hydrated). Read-only access — the
92
92
  * jobs.output route replays `job_output` tool/result rows from it.
93
93
  */
94
- events?: readonly SidebarSessionEvent[]
94
+ snapshotEvents?(fromSeq?: number, toSeqExclusive?: number): readonly SidebarSessionEvent[]
95
95
  } | undefined
96
96
  }
97
97
 
@@ -295,13 +295,18 @@ export interface SidebarSessionTitleService {
295
295
  }
296
296
 
297
297
  /** The host session-persistence face (mirror of the sessionPersistence
298
- * service): detached inspection of a persisted session, used to compose the
299
- * recorded preset when a Side Chat thread cold-resumes. */
300
- export interface SidebarSessionPersistenceService {
301
- inspect(sessionId: string): Promise<{
302
- meta: { cwd?: string; agentPreset?: string }
298
+ * service, 0.1.5 handle-seam form): a short-lived read handle over one
299
+ * persisted session, used to compose the recorded preset when a Side Chat
300
+ * thread cold-resumes. */
301
+ export interface SidebarSessionPersistenceHandle {
302
+ readonly header: { cwd?: string; agentPreset?: string }
303
+ read(fromSeq?: number, toSeqExclusive?: number, options?: { signal?: AbortSignal }): Promise<{
303
304
  events: readonly SidebarSessionEvent[]
304
305
  }>
306
+ close(): Promise<void>
307
+ }
308
+ export interface SidebarSessionPersistenceService {
309
+ open(sessionId: string, access: 'read', options?: { signal?: AbortSignal }): Promise<SidebarSessionPersistenceHandle>
305
310
  }
306
311
 
307
312
  /** RPC result slot mirror (`RpcResult<T>` on the wire). */
package/src/index.ts CHANGED
@@ -18,7 +18,7 @@ import { basename, dirname, extname, isAbsolute, join } from 'node:path'
18
18
  import type { IncomingMessage } from 'node:http'
19
19
  import type { Duplex } from 'node:stream'
20
20
  import { WebSocket, WebSocketServer } from 'ws'
21
- import type { Context, SidebarHttpRequest } from './context-types.ts'
21
+ import type { Context, SidebarHttpRequest, SidebarSessionPersistenceService } from './context-types.ts'
22
22
  import {
23
23
  Config,
24
24
  PrefsSchema,
@@ -126,10 +126,11 @@ async function sessionCwdOf(ctx: Context, sessionId: string, clientCwd?: string)
126
126
  throw new SidebarError('bad-request', `invalid working directory "${clientCwd}"`)
127
127
  }
128
128
  }
129
- const persistence = ctx.get('sessionPersistence')
129
+ const persistence = ctx.get('sessionPersistence') as SidebarSessionPersistenceService | undefined
130
130
  if (persistence !== undefined) {
131
- const inspected = await persistence.inspect(sessionId)
132
- const metaCwd = inspected.meta.cwd
131
+ const handle = await persistence.open(sessionId, 'read')
132
+ const metaCwd = handle.header.cwd
133
+ await handle.close()
133
134
  if (metaCwd !== undefined && metaCwd !== '') {
134
135
  try {
135
136
  return requireAbsolute(metaCwd)
@@ -135,7 +135,7 @@ const MIRROR_MAX_ENTRIES = 200
135
135
  * The live job_output mirror: subscribes to the session append feed and
136
136
  * caches the job_output traces the session store's own log can lag behind
137
137
  * (after a host restart the store session stays frozen at its rehydration
138
- * boundary, so `session.events` misses everything appended since — the very
138
+ * boundary, so a stale snapshot misses everything appended since — the very
139
139
  * reads the pane exists to show). Zero DSH writes: the api-proxy pushes the
140
140
  * same feed to browsers.
141
141
  */
@@ -211,7 +211,8 @@ export function buildJobsApi(ctx: Context, outputLimit: number): SidebarJobsRout
211
211
  // Merge the store's event log (durable seed + whatever it received)
212
212
  // with the live mirror, deduped by seq — a trace never double-counts.
213
213
  const bySeq = new Map<number, JobOutputTrace>()
214
- for (const event of ctx.sessions.get(sessionId)?.events ?? []) {
214
+ const store = ctx.sessions.get(sessionId)
215
+ for (const event of (store?.snapshotEvents !== undefined ? store.snapshotEvents() : []) ?? []) {
215
216
  const trace = traceOf(event)
216
217
  if (trace !== undefined) bySeq.set(trace.seq, trace)
217
218
  }
@@ -26,6 +26,7 @@ import type { Agent, AgentSetup, CreateAgentOptions, ResumeAgentOptions } from '
26
26
  import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
27
27
  import type { Context as CordisContext } from '@deepseek-ai/cordis'
28
28
  import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
29
+ import type { SidebarSessionEvent } from './context-types.ts'
29
30
  import type {
30
31
  Context,
31
32
  SidebarAgentPresetsService,
@@ -104,8 +105,10 @@ async function composePersistedSetup(
104
105
  if (persistence === undefined) {
105
106
  return () => Promise.resolve()
106
107
  }
107
- const inspected = await persistence.inspect(childId)
108
- const presetId = resolvePresetId(inspected.meta, inspected.events)
108
+ const handle = await persistence.open(childId, 'read')
109
+ const { events } = await handle.read()
110
+ const presetId = resolvePresetId(handle.header as never, events as unknown as readonly SidebarSessionEvent[])
111
+ await handle.close()
109
112
  const presets = ctx.get('agentPresets') as SidebarAgentPresetsService | undefined
110
113
  if (presets === undefined || presetId === undefined) {
111
114
  return () => Promise.resolve()
@@ -166,11 +169,11 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
166
169
  }
167
170
  const parentSession = parent.session
168
171
  const inheritance = buildSidechatInheritance(
169
- parentSession.events as unknown as readonly SidechatLogEvent[],
172
+ parentSession.snapshotEvents() as unknown as readonly SidechatLogEvent[],
170
173
  )
171
174
  const { agentPreset, setup } = await composeChildSetup(
172
175
  ctx,
173
- resolvePresetId(parentSession.header, parentSession.events),
176
+ resolvePresetId(parentSession.header, parentSession.snapshotEvents()),
174
177
  )
175
178
  const childId = `session-${randomUUID()}` as SessionId
176
179
  const label = question === '' ? SIDE_NEW_THREAD_TITLE : sideLabel(question)
@@ -198,7 +201,7 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
198
201
  meta: {
199
202
  ...(parentSession.header.cwd === undefined ? {} : { cwd: parentSession.header.cwd }),
200
203
  parentSession: parentSession.id,
201
- seedLength: seed.length,
204
+ isSeeded: seed.length > 0,
202
205
  origin: 'subagent',
203
206
  delegationDepth: (parentSession.header.delegationDepth ?? 0) + 1,
204
207
  ...(agentPreset === undefined ? {} : { agentPreset }),
@@ -267,7 +270,7 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
267
270
  throw new SidebarError('sidechat-error', `thread resume failed: ${error instanceof Error ? error.message : String(error)}`, 500)
268
271
  }
269
272
  }
270
- if (boundaryDelivered(agent.session.events as unknown as readonly SidechatLogEvent[])) {
273
+ if (boundaryDelivered(agent.session.snapshotEvents() as unknown as readonly SidechatLogEvent[])) {
271
274
  admitFollowup(agent, textPrompt(text))
272
275
  } else {
273
276
  // First message of an immediately-created thread: it carries the
@@ -331,8 +334,10 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
331
334
  const persistence = ctx.get('sessionPersistence') as SidebarSessionPersistenceService | undefined
332
335
  if (persistence !== undefined) {
333
336
  try {
334
- const inspected = await persistence.inspect(childId)
335
- const preset = resolvePresetId(inspected.meta, inspected.events)
337
+ const handle = await persistence.open(childId, 'read')
338
+ const { events } = await handle.read()
339
+ const preset = resolvePresetId(handle.header as never, events as unknown as readonly SidebarSessionEvent[])
340
+ await handle.close()
336
341
  return { live: false, ...(preset === undefined ? {} : { preset }) }
337
342
  } catch {
338
343
  // Unknown/gone session: report a bare cold info.
@@ -76,8 +76,9 @@ export function buildSubagentLiveApi(ctx: Context): SidebarSubagentLiveRoutes {
76
76
  // never topology — keep them out of the live map too.
77
77
  if (entry.label?.startsWith(SIDE_LABEL_PREFIX) ?? false) continue
78
78
  try {
79
+ const stored = ctx.sessions.get(entry.id)
79
80
  const activity = lastActivity(
80
- ctx.sessions.get(entry.id)?.events ?? [],
81
+ stored?.snapshotEvents !== undefined ? stored.snapshotEvents() : [],
81
82
  LIVE_WINDOW_MESSAGES,
82
83
  )
83
84
  if (activity.text !== undefined || activity.tool !== undefined) {