@xp266/dshtui 0.1.0 → 0.1.2

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/bin/dshtui.js CHANGED
@@ -29,7 +29,11 @@ function fail(message) {
29
29
  process.exit(1)
30
30
  }
31
31
 
32
- const dshProbe = spawnSync('dsh', ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' })
32
+ // Windows resolves `dsh` through a .cmd shim, which only spawns with a
33
+ // shell; the command line is built as one quoted string there because
34
+ // passing an args array alongside shell:true is deprecated (DEP0190).
35
+ const windows = process.platform === 'win32'
36
+ const dshProbe = spawnSync('dsh', ['--version'], { stdio: 'ignore', shell: windows })
33
37
  if (dshProbe.error !== undefined) {
34
38
  fail('the dsh CLI was not found on PATH; install it with: npm install -g @deepseek-ai/dsh')
35
39
  }
@@ -47,11 +51,11 @@ if (!existsSync(profileDir)) {
47
51
  }
48
52
 
49
53
  const argv = process.argv.slice(2)
50
- const child = spawn('dsh', ['--profile', profile, ...argv], {
54
+ const quote = arg => /^[A-Za-z0-9_@%+=:,./-]+$/.test(arg) ? arg : `"${arg.replaceAll('"', '\\"')}"`
55
+ const command = ['dsh', '--profile', profile, ...argv].map(quote).join(' ')
56
+ const child = spawn(windows ? command : 'dsh', windows ? [] : ['--profile', profile, ...argv], {
51
57
  stdio: 'inherit',
52
- ...(process.platform === 'win32'
53
- ? { shell: true, windowsVerbatimArguments: true }
54
- : {}),
58
+ ...(windows ? { shell: true } : {}),
55
59
  })
56
60
  child.on('error', () => {
57
61
  fail('the dsh CLI was not found on PATH; install it with: npm install -g @deepseek-ai/dsh')
package/cordis.patch.yml CHANGED
@@ -1,29 +1,19 @@
1
1
  - insert:
2
- - id: storage
3
- name: '@deepseek-ai/dsh-storage'
4
- - id: storage-json
5
- name: '@deepseek-ai/dsh-storage-json'
6
- config:
7
- root: !!js dshHomePath('storages')
8
- - id: storage-domain
9
- name: '@deepseek-ai/dsh-storage-domain'
10
- config:
11
- backend: json
12
- # Persisted title/blank hints so the session picker lists cold sessions
13
- # without reading their logs.
14
- - id: session-projection-cache
15
- name: '@deepseek-ai/dsh-session-projection-cache'
16
- config:
17
- writeEveryEvents: 200
18
- writeIntervalMs: 5000
2
+ # The durable storage stack (storage, storage-json, storage-domain,
3
+ # session-projection-cache) is inserted by the dsh-base bundle since
4
+ # 0.1.2-rc.1 with the same configuration this file used to carry —
5
+ # re-inserting it here would collide with duplicate loader entry ids.
6
+ # The standard preset's subagent tool enables modelSelectionSettings,
7
+ # which requires this settings service in the host scope (the web bundle
8
+ # mounts the same row for its own roster).
9
+ - id: subagent-model-selection-settings
10
+ name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
19
11
  - id: workspace
20
12
  name: '@deepseek-ai/dsh-workspace'
21
13
  - id: agent-presets
22
14
  name: '@deepseek-ai/dsh-agent-presets'
23
15
  config:
24
16
  default: standard
25
- - id: cordis-host-runner
26
- name: '@deepseek-ai/dsh-cordis-host-runner'
27
17
  # Durable image storage comes from the dsh base bundle's attachment-local
28
18
  # row; the TUI resolves ctx.attachments at send time and degrades to a
29
19
  # plain text message when the service is absent.
package/lib/index.mjs CHANGED
@@ -14,7 +14,6 @@ import Prism from "prismjs";
14
14
  import { marked } from "marked";
15
15
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
16
16
  import { fileURLToPath, pathToFileURL } from "node:url";
17
- import { resolveSessionPreset } from "@deepseek-ai/dsh-agent-presets";
18
17
  import { installModelSelection } from "@deepseek-ai/dsh-agent";
19
18
  import { ReasoningEffortId, createUserMessage } from "@deepseek-ai/dsh-llm";
20
19
  import { SessionId } from "@deepseek-ai/dsh-session";
@@ -6201,7 +6200,7 @@ function MessageList({ messages, height, width, scrollTop, onScroll, interactive
6201
6200
  }
6202
6201
  //#endregion
6203
6202
  //#region src/contract/upstream.ts
6204
- const UPSTREAM_FLOOR_VERSION = "0.1.0-rc.6";
6203
+ const UPSTREAM_FLOOR_VERSION = "0.1.2-rc.1";
6205
6204
  const UPSTREAM_CEILING_VERSION = "0.2.0";
6206
6205
  const UPSTREAM_FRAMEWORK_MAJORS = {
6207
6206
  "@deepseek-ai/cordis": 4,
@@ -7204,9 +7203,9 @@ async function computeSessionList(ctx) {
7204
7203
  attached.add(id);
7205
7204
  if (session.header.origin === "subagent") continue;
7206
7205
  if (archived.has(id)) continue;
7207
- if (isBlankSession(session.events)) continue;
7208
- const title = titleService?.get?.(session)?.title ?? firstUserText(session.events);
7209
- summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.events), workspacePaths));
7206
+ if (isBlankSession(session.snapshotEvents())) continue;
7207
+ const title = titleService?.get?.(session)?.title ?? firstUserText(session.snapshotEvents());
7208
+ summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.snapshotEvents()), workspacePaths));
7210
7209
  }
7211
7210
  const persistence = ctx.get("sessionPersistence");
7212
7211
  await mergeColdSummaries(ctx, persistence, await listColdHeaders(ctx, persistence, attached), archived, workspacePaths, summaries);
@@ -9650,12 +9649,12 @@ function registerInteractionChannels(ctx, store) {
9650
9649
  const offApproval = ctx.on("approval/request", async (req) => {
9651
9650
  return store.pushApproval(req.toolName, req.callId, req.reason, req.signal);
9652
9651
  });
9653
- const unregisterProvider = ctx.get("userQuestions")?.registerProvider({ ask(request) {
9652
+ const offQuestions = ctx.on("user-questions/request", async (request) => {
9654
9653
  return store.pushQuestion(request, request.signal);
9655
- } });
9654
+ });
9656
9655
  return () => {
9657
9656
  offApproval();
9658
- unregisterProvider?.();
9657
+ offQuestions();
9659
9658
  };
9660
9659
  }
9661
9660
  //#endregion
@@ -10173,6 +10172,17 @@ async function createChatBridge(ctx) {
10173
10172
  image: info.inputModalities?.includes("image") ?? false
10174
10173
  };
10175
10174
  }
10175
+ function resolveSessionPreset(session) {
10176
+ const events = session.snapshotEvents();
10177
+ for (let index = events.length - 1; index >= 0; index -= 1) {
10178
+ const event = events[index];
10179
+ if (event?.type === "agent-preset/selected") {
10180
+ const data = event.data;
10181
+ if (data?.agentPreset !== void 0) return data.agentPreset;
10182
+ }
10183
+ }
10184
+ return session.header?.agentPreset;
10185
+ }
10176
10186
  async function createAgent(cwd, presetId) {
10177
10187
  const resolved = presetId ?? (await presets?.resolve())?.id;
10178
10188
  const meta = {
@@ -10245,21 +10255,21 @@ async function createChatBridge(ctx) {
10245
10255
  async function openSession(id) {
10246
10256
  const sessionId = SessionId(id);
10247
10257
  if (activeAgent.id === sessionId) {
10248
- for (const event of activeAgent.session.events) emit(event);
10258
+ for (const event of activeAgent.session.snapshotEvents()) emit(event);
10249
10259
  return;
10250
10260
  }
10251
10261
  const existing = ctx.agents.get(sessionId);
10252
10262
  if (existing !== void 0) {
10253
10263
  await activateAgent(void 0, existing);
10254
10264
  syncCwd();
10255
- for (const event of activeAgent.session.events) emit(event);
10265
+ for (const event of activeAgent.session.snapshotEvents()) emit(event);
10256
10266
  refreshAgentState();
10257
10267
  return;
10258
10268
  }
10259
10269
  const handle = await resumeAgent(sessionId);
10260
10270
  await activateAgent(handle, handle.agent);
10261
10271
  syncCwd();
10262
- for (const event of activeAgent.session.events) emit(event);
10272
+ for (const event of activeAgent.session.snapshotEvents()) emit(event);
10263
10273
  refreshAgentState();
10264
10274
  }
10265
10275
  function syncCwd() {
@@ -10312,7 +10322,7 @@ async function createChatBridge(ctx) {
10312
10322
  if (presets === void 0) throw new Error("agent presets are not configured");
10313
10323
  if (currentPreset() === id) return;
10314
10324
  const session = activeAgent.session;
10315
- if (!isBlankSession(session.events)) throw new Error("the preset is fixed once the session has started; use /new to start a new session");
10325
+ if (!isBlankSession(session.snapshotEvents())) throw new Error("the preset is fixed once the session has started; use /new to start a new session");
10316
10326
  const preset = await presets.recompose(activeAgent.ctx, id);
10317
10327
  session.append("agent-preset/selected", { agentPreset: preset.id });
10318
10328
  }
@@ -10487,12 +10497,12 @@ async function createChatBridge(ctx) {
10487
10497
  currentEffort,
10488
10498
  effortName,
10489
10499
  selectEffort,
10490
- permissionMode: () => permission()?.current(activeAgent.session.events) ?? PERMISSION_PRESETS[0],
10500
+ permissionMode: () => permission()?.current(activeAgent.session.snapshotEvents()) ?? PERMISSION_PRESETS[0],
10491
10501
  cyclePermission: () => {
10492
10502
  const service = permission();
10493
10503
  if (service === void 0) return;
10494
10504
  try {
10495
- const current = service.current(activeAgent.session.events);
10505
+ const current = service.current(activeAgent.session.snapshotEvents());
10496
10506
  const names = service.names.length > 0 ? service.names : PERMISSION_PRESETS;
10497
10507
  const next = names[(names.indexOf(current) + 1) % names.length];
10498
10508
  service.set(activeAgent.session, next);
@@ -11294,7 +11304,11 @@ function apply(ctx, config = Config(DEFAULT_CONFIG)) {
11294
11304
  const restorePerformance = startPerformanceGuard();
11295
11305
  const disposeCrashHandlers = installCrashHandlers({ exit: env.crashExit });
11296
11306
  const drift = upstreamDriftSummary();
11297
- if (drift !== void 0) warn("upstream", `upstream version drift (supported: ${UPSTREAM_SUPPORTED_RANGE}): ${drift.kind} ${drift.versions.join(", ")}`);
11307
+ if (drift !== void 0) {
11308
+ const message = `dshtui requires dsh harness packages in ${UPSTREAM_SUPPORTED_RANGE} (found ${drift.kind}: ${drift.versions.join(", ")}); upgrade the dsh CLI with: npm install -g @deepseek-ai/dsh@latest`;
11309
+ error("boot", message);
11310
+ throw new Error(message);
11311
+ }
11298
11312
  const capture = createScreenCapture();
11299
11313
  let bridge;
11300
11314
  let app;
@@ -11362,7 +11376,6 @@ function apply(ctx, config = Config(DEFAULT_CONFIG)) {
11362
11376
  };
11363
11377
  const themeScope = registerThemeSettings(ctx);
11364
11378
  openBootLog();
11365
- if (drift !== void 0) emitBootLine(`warning: upstream version drift (${drift.kind})`);
11366
11379
  emitBootLine("terminal: probing color support");
11367
11380
  (async () => {
11368
11381
  const probed = await probeColorLevel();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xp266/dshtui",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "ink-based terminal UI plugin for DeepSeek Harness",
6
6
  "engines": {
@@ -38,17 +38,17 @@
38
38
  ],
39
39
  "license": "MIT",
40
40
  "peerDependencies": {
41
- "@deepseek-ai/cordis": "^4.0.1",
42
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
43
- "@deepseek-ai/dsh-agent-default-model": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
44
- "@deepseek-ai/dsh-agent-loop": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
45
- "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
46
- "@deepseek-ai/dsh-attachment": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
47
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
48
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
49
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
50
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.2",
51
- "@deepseek-ai/schemastery": "^3.18.1"
41
+ "@deepseek-ai/cordis": "^4.0.2",
42
+ "@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
43
+ "@deepseek-ai/dsh-agent-default-model": "^0.1.2-rc.1",
44
+ "@deepseek-ai/dsh-agent-loop": "^0.1.2-rc.1",
45
+ "@deepseek-ai/dsh-agent-presets": "^0.1.2-rc.1",
46
+ "@deepseek-ai/dsh-attachment": "^0.1.2-rc.1",
47
+ "@deepseek-ai/dsh-credentials": "^0.1.2-rc.1",
48
+ "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
49
+ "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
50
+ "@deepseek-ai/dsh-settings": "^0.1.2-rc.1",
51
+ "@deepseek-ai/schemastery": "^3.18.2"
52
52
  },
53
53
  "peerDependenciesMeta": {
54
54
  "@deepseek-ai/cordis": {
@@ -94,23 +94,24 @@
94
94
  "string-width": "^8.2.2"
95
95
  },
96
96
  "devDependencies": {
97
- "@deepseek-ai/cordis": "^4.0.1",
98
- "@deepseek-ai/dsh-agent": "0.1.0-rc.6",
99
- "@deepseek-ai/dsh-agent-default-model": "0.1.0-rc.6",
100
- "@deepseek-ai/dsh-agent-loop": "0.1.0-rc.6",
101
- "@deepseek-ai/dsh-agent-presets": "0.1.0-rc.6",
102
- "@deepseek-ai/dsh-attachment": "0.1.0-rc.6",
103
- "@deepseek-ai/dsh-credentials": "0.1.0-rc.6",
104
- "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
105
- "@deepseek-ai/dsh-session": "0.1.0-rc.6",
106
- "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
107
- "@deepseek-ai/schemastery": "^3.18.1",
97
+ "@deepseek-ai/cordis": "^4.0.2",
98
+ "@deepseek-ai/dsh-agent": "0.1.2-rc.1",
99
+ "@deepseek-ai/dsh-agent-default-model": "0.1.2-rc.1",
100
+ "@deepseek-ai/dsh-agent-loop": "0.1.2-rc.1",
101
+ "@deepseek-ai/dsh-agent-presets": "0.1.2-rc.1",
102
+ "@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
103
+ "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
104
+ "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
105
+ "@deepseek-ai/dsh-session": "0.1.2-rc.1",
106
+ "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
107
+ "@deepseek-ai/schemastery": "^3.18.2",
108
108
  "@types/node": "^24.0.0",
109
109
  "@types/prismjs": "^1.26.6",
110
110
  "@types/react": "^19.2.0",
111
111
  "tsdown": "^0.22.14",
112
112
  "tsx": "^4.0.0",
113
- "typescript": "^5.9.0"
113
+ "typescript": "^5.9.0",
114
+ "@deepseek-ai/dsh-tool-todo": "0.1.2-rc.1"
114
115
  },
115
116
  "dsh": {
116
117
  "bundle": {
@@ -5,7 +5,6 @@ import type {} from '@deepseek-ai/dsh-credentials'
5
5
  import type {} from '@deepseek-ai/dsh-settings'
6
6
  import type {} from '@deepseek-ai/dsh-agent-default-model'
7
7
  import type {} from '@deepseek-ai/dsh-agent-presets'
8
- import { resolveSessionPreset } from '@deepseek-ai/dsh-agent-presets'
9
8
  import { installModelSelection } from '@deepseek-ai/dsh-agent'
10
9
  import type { Agent, AgentHandle, AgentOptions, ModelSelection, ModelSelectionRef } from '@deepseek-ai/dsh-agent'
11
10
  import type { ContentBlock, LlmDiscoveredModel } from '@deepseek-ai/dsh-llm'
@@ -136,6 +135,11 @@ async function sendContent(
136
135
  agent.followup(createUserMessage({ content, source: { kind: 'user' } }))
137
136
  }
138
137
 
138
+ interface PresetSessionLike {
139
+ snapshotEvents(): ReadonlyArray<{ type?: string; data?: unknown }>
140
+ header?: { agentPreset?: string }
141
+ }
142
+
139
143
  export const PERMISSION_PRESETS = ['workspace-write', 'danger-full-access', 'read-only'] as const
140
144
 
141
145
  interface PermissionPresetsLike {
@@ -663,6 +667,22 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
663
667
  }
664
668
  }
665
669
 
670
+ // The 0.1.2 presets package dropped its resolveSessionPreset helper, so the
671
+ // session log is read directly: the newest selection event wins over the
672
+ // creation-time header value, matching the upstream semantics on every
673
+ // host version the peer range admits.
674
+ function resolveSessionPreset(session: PresetSessionLike): string | undefined {
675
+ const events = session.snapshotEvents()
676
+ for (let index = events.length - 1; index >= 0; index -= 1) {
677
+ const event = events[index]
678
+ if (event?.type === 'agent-preset/selected') {
679
+ const data = event.data as { agentPreset?: string } | undefined
680
+ if (data?.agentPreset !== undefined) return data.agentPreset
681
+ }
682
+ }
683
+ return session.header?.agentPreset
684
+ }
685
+
666
686
  async function createAgent(cwd: string, presetId?: string): Promise<AgentHandle> {
667
687
  const resolved = presetId ?? (await presets?.resolve())?.id
668
688
  const meta = { cwd, ...(resolved === undefined ? {} : { agentPreset: resolved }) }
@@ -742,21 +762,21 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
742
762
  async function openSession(id: string): Promise<void> {
743
763
  const sessionId = SessionId(id)
744
764
  if (activeAgent.id === sessionId) {
745
- for (const event of activeAgent.session.events) emit(event)
765
+ for (const event of activeAgent.session.snapshotEvents()) emit(event)
746
766
  return
747
767
  }
748
768
  const existing = ctx.agents.get(sessionId)
749
769
  if (existing !== undefined) {
750
770
  await activateAgent(undefined, existing)
751
771
  syncCwd()
752
- for (const event of activeAgent.session.events) emit(event)
772
+ for (const event of activeAgent.session.snapshotEvents()) emit(event)
753
773
  refreshAgentState()
754
774
  return
755
775
  }
756
776
  const handle = await resumeAgent(sessionId)
757
777
  await activateAgent(handle, handle.agent)
758
778
  syncCwd()
759
- for (const event of activeAgent.session.events) emit(event)
779
+ for (const event of activeAgent.session.snapshotEvents()) emit(event)
760
780
  refreshAgentState()
761
781
  }
762
782
 
@@ -817,7 +837,7 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
817
837
  if (presets === undefined) throw new Error('agent presets are not configured')
818
838
  if (currentPreset() === id) return
819
839
  const session = activeAgent.session
820
- if (!isBlankSession(session.events)) {
840
+ if (!isBlankSession(session.snapshotEvents())) {
821
841
  throw new Error('the preset is fixed once the session has started; use /new to start a new session')
822
842
  }
823
843
  const preset = await presets.recompose(activeAgent.ctx, id)
@@ -1020,12 +1040,12 @@ export async function createChatBridge(ctx: Context): Promise<ChatBridge> {
1020
1040
  currentEffort,
1021
1041
  effortName,
1022
1042
  selectEffort,
1023
- permissionMode: () => permission()?.current(activeAgent.session.events) ?? PERMISSION_PRESETS[0],
1043
+ permissionMode: () => permission()?.current(activeAgent.session.snapshotEvents()) ?? PERMISSION_PRESETS[0],
1024
1044
  cyclePermission: () => {
1025
1045
  const service = permission()
1026
1046
  if (service === undefined) return
1027
1047
  try {
1028
- const current = service.current(activeAgent.session.events)
1048
+ const current = service.current(activeAgent.session.snapshotEvents())
1029
1049
  const names = service.names.length > 0 ? service.names : PERMISSION_PRESETS
1030
1050
  const index = names.indexOf(current)
1031
1051
  const next = names[(index + 1) % names.length]
@@ -308,22 +308,21 @@ type ApprovalRequestLike = {
308
308
 
309
309
  type CtxLike = {
310
310
  on(event: 'approval/request', handler: (req: ApprovalRequestLike) => Promise<ApprovalOutcome>): () => void
311
- get(service: 'userQuestions'): {
312
- registerProvider(provider: { ask(request: AskUserQuestionRequestLike): Promise<AskUserQuestionAnswerLike> }): () => void
313
- } | undefined
311
+ on(event: 'user-questions/request', handler: (req: AskUserQuestionRequestLike) => Promise<AskUserQuestionAnswerLike>): () => void
314
312
  }
315
313
 
316
314
  export function registerInteractionChannels(ctx: CtxLike, store: InteractionStore): () => void {
317
315
  const offApproval = ctx.on('approval/request', async req => {
318
316
  return store.pushApproval(req.toolName, req.callId, req.reason, req.signal)
319
317
  })
320
- const unregisterProvider = ctx.get('userQuestions')?.registerProvider({
321
- ask(request) {
322
- return store.pushQuestion(request, request.signal)
323
- },
318
+ // The 0.1.2 user-questions service routes asks through the scoped
319
+ // `user-questions/request` waterfall; answering without calling `next`
320
+ // claims the request for this terminal.
321
+ const offQuestions = ctx.on('user-questions/request', async request => {
322
+ return store.pushQuestion(request, request.signal)
324
323
  })
325
324
  return () => {
326
325
  offApproval()
327
- unregisterProvider?.()
326
+ offQuestions()
328
327
  }
329
328
  }
@@ -32,16 +32,16 @@ export async function computeSessionList(ctx: Context): Promise<SessionSummary[]
32
32
  const summaries: SessionSummary[] = []
33
33
  const attached = new Set<string>()
34
34
  const titleService = ctx.get('sessionTitle') as
35
- | { get?(session: { id: string; events: readonly SessionEvent[] }): { title?: string } | undefined }
35
+ | { get?(session: { id: string; snapshotEvents(): readonly SessionEvent[] }): { title?: string } | undefined }
36
36
  | undefined
37
37
  for (const session of ctx.sessions.list()) {
38
38
  const id = String(session.id)
39
39
  attached.add(id)
40
40
  if (session.header.origin === 'subagent') continue
41
41
  if (archived.has(id)) continue
42
- if (isBlankSession(session.events)) continue
43
- const title = titleService?.get?.(session)?.title ?? firstUserText(session.events)
44
- summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.events), workspacePaths))
42
+ if (isBlankSession(session.snapshotEvents())) continue
43
+ const title = titleService?.get?.(session)?.title ?? firstUserText(session.snapshotEvents())
44
+ summaries.push(toSummary(id, title, session.header.cwd, session.header.createdAt, lastPromptAt(session.snapshotEvents()), workspacePaths))
45
45
  }
46
46
  const persistence = ctx.get('sessionPersistence') as PersistenceLike | undefined
47
47
  const cold = await listColdHeaders(ctx, persistence, attached)
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from 'node:fs'
2
2
  import { fileURLToPath } from 'node:url'
3
3
 
4
- export const UPSTREAM_FLOOR_VERSION = '0.1.0-rc.6'
4
+ export const UPSTREAM_FLOOR_VERSION = '0.1.2-rc.1'
5
5
 
6
6
  export const UPSTREAM_CEILING_VERSION = '0.2.0'
7
7
 
package/src/index.tsx CHANGED
@@ -23,7 +23,7 @@ import { warmRenderPipeline } from './ui/message/warmup.ts'
23
23
  import { createTuiExtensionPoint, exposeRuntimeFaces } from './ui/extension-point.ts'
24
24
  import { registerBuiltinToolViews } from './chat/builtin-tool-views.ts'
25
25
  import { closeBootLog, emitBootLine, openBootLog } from './boot-log.ts'
26
- import { configureLogs, error as logError, installCrashHandlers, warn } from './log.ts'
26
+ import { configureLogs, error, error as logError, installCrashHandlers } from './log.ts'
27
27
  import { env } from './env.ts'
28
28
  import { upstreamDriftSummary, UPSTREAM_SUPPORTED_RANGE } from './contract/upstream.ts'
29
29
 
@@ -67,9 +67,13 @@ export function apply(ctx: Context, config: Config = Config(DEFAULT_CONFIG)) {
67
67
  configureLogs({ stderr: env.debug, file: env.logFile, dir: env.logDir, level: env.logLevel, maxFileBytes: env.logMaxBytes })
68
68
  const restorePerformance = startPerformanceGuard()
69
69
  const disposeCrashHandlers = installCrashHandlers({ exit: env.crashExit })
70
+ // Fail before any surface mounts: an out-of-range host breaks the bridge
71
+ // in ways that render as a dead interface instead of an actionable error.
70
72
  const drift = upstreamDriftSummary()
71
73
  if (drift !== undefined) {
72
- warn('upstream', `upstream version drift (supported: ${UPSTREAM_SUPPORTED_RANGE}): ${drift.kind} ${drift.versions.join(', ')}`)
74
+ const message = `dshtui requires dsh harness packages in ${UPSTREAM_SUPPORTED_RANGE} (found ${drift.kind}: ${drift.versions.join(', ')}); upgrade the dsh CLI with: npm install -g @deepseek-ai/dsh@latest`
75
+ error('boot', message)
76
+ throw new Error(message)
73
77
  }
74
78
  const capture = createScreenCapture()
75
79
  let bridge: ChatBridge | undefined
@@ -138,9 +142,6 @@ export function apply(ctx: Context, config: Config = Config(DEFAULT_CONFIG)) {
138
142
  }
139
143
  const themeScope = registerThemeSettings(ctx)
140
144
  openBootLog()
141
- if (drift !== undefined) {
142
- emitBootLine(`warning: upstream version drift (${drift.kind})`)
143
- }
144
145
  emitBootLine('terminal: probing color support')
145
146
  void (async () => {
146
147
  const probed = await probeColorLevel()
@@ -1,3 +1,4 @@
1
+ import type {} from '@deepseek-ai/dsh-tool-todo'
1
2
  import { useEffect, useRef, useState } from 'react'
2
3
  import type { SessionEvent } from '@deepseek-ai/dsh-session'
3
4
  import type { ChatBridge, ChatToolPresenter, RegistryCommand } from '../../chat/bridge.ts'