dsh-code 0.6.0 → 0.6.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.en.md CHANGED
@@ -37,11 +37,13 @@ The interface follows familiar terminal conventions, while runtime behavior cont
37
37
  Requires Node `^22.19 || >=24`, the preview `dsh` CLI, and `DEEPSEEK_API_KEY`.
38
38
 
39
39
  ```sh
40
- npm install -g @deepseek-ai/dsh@next dsh-code
40
+ npm install -g @deepseek-ai/dsh dsh-code
41
41
  npm install -g pnpm
42
- dsh plugin --profile cli add dsh-code
42
+ dsh plugin --profile cli add dsh-code@0.6.1
43
43
  ```
44
44
 
45
+ > Note: pnpm ignores packages published less than 24 hours ago, so pin `dsh-code@0.6.1` on the release day; the version can be omitted afterwards. npm installs are not affected.
46
+
45
47
  Available launch commands:
46
48
 
47
49
  ```sh
package/README.md CHANGED
@@ -37,11 +37,13 @@ DSH-Code 保留这套结构,并补充适合编码任务的终端工作流。
37
37
  需要 Node `^22.19 || >=24`、预览版 `dsh` CLI,以及 `DEEPSEEK_API_KEY`。
38
38
 
39
39
  ```sh
40
- npm install -g @deepseek-ai/dsh@next dsh-code
40
+ npm install -g @deepseek-ai/dsh dsh-code
41
41
  npm install -g pnpm
42
- dsh plugin --profile cli add dsh-code
42
+ dsh plugin --profile cli add dsh-code@0.6.1
43
43
  ```
44
44
 
45
+ > 提示:pnpm 会忽略发布不足 24 小时的包,因此发布首日请使用精确版本 `dsh-code@0.6.1`;24 小时后可省略版本号。npm 安装不受此限制。
46
+
45
47
  可用的启动指令:
46
48
  ```sh
47
49
  deepseek
package/bin/deepseek.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn } from 'node:child_process'
4
- import { existsSync, realpathSync } from 'node:fs'
4
+ import { existsSync, readFileSync, realpathSync } from 'node:fs'
5
5
  import { createRequire } from 'node:module'
6
+ import { homedir } from 'node:os'
6
7
  import { join, resolve } from 'node:path'
7
8
  import { fileURLToPath, pathToFileURL } from 'node:url'
8
9
 
@@ -11,6 +12,30 @@ const packageRequire = createRequire(import.meta.url)
11
12
  /** Arguments required to boot DSH-Code's conventional profile. */
12
13
  export const profileArgs = (args = []) => ['--profile', 'cli', ...args]
13
14
 
15
+ /** The conventional cli profile directory under the DSH home. */
16
+ export function cliProfileDir(home = homedir()) {
17
+ return join(home, '.dsh', 'profiles', 'cli')
18
+ }
19
+
20
+ /**
21
+ * Whether the cli profile already mounts dsh-code. A bare profile composes
22
+ * dsh-base alone and keeps the process alive with no TUI runner — the alias
23
+ * must fail loudly with the missing step instead of hanging.
24
+ */
25
+ export function profileHasDshCode(profileDir = cliProfileDir(), fileExists = existsSync, readFile = readFileSync) {
26
+ const manifest = join(profileDir, 'package.json')
27
+ if (!fileExists(manifest)) return false
28
+ try {
29
+ const raw = JSON.parse(readFile(manifest, 'utf8'))
30
+ const bundles = raw?.dsh?.profile?.bundles
31
+ const dependencies = raw?.dependencies
32
+ return (Array.isArray(bundles) && bundles.includes('dsh-code'))
33
+ || (typeof dependencies === 'object' && dependencies !== null && 'dsh-code' in dependencies)
34
+ } catch {
35
+ return false
36
+ }
37
+ }
38
+
14
39
  /** Npm global-prefix roots that may contain DSH when this launcher is globally linked to a checkout. */
15
40
  export function globalDshRoots() {
16
41
  return [...new Set([
@@ -43,10 +68,20 @@ export function dshCommand(args, {
43
68
  }
44
69
 
45
70
  /** Launch the installed DSH CLI while preserving its exit status and stdio. */
46
- export function launchDsh(args = process.argv.slice(2), spawnProcess = spawn, resolveCommand = dshCommand) {
71
+ export function launchDsh(
72
+ args = process.argv.slice(2),
73
+ spawnProcess = spawn,
74
+ resolveCommand = dshCommand,
75
+ isProfileReady = profileHasDshCode,
76
+ ) {
77
+ if (!isProfileReady()) {
78
+ console.error('dsh-code: the cli profile does not mount dsh-code yet. Run: dsh plugin --profile cli add dsh-code')
79
+ process.exitCode = 1
80
+ return undefined
81
+ }
47
82
  const command = resolveCommand(args)
48
83
  if (command === undefined) {
49
- console.error('dsh-code: @deepseek-ai/dsh must be installed globally beside dsh-code; run: npm install -g @deepseek-ai/dsh@next dsh-code')
84
+ console.error('dsh-code: @deepseek-ai/dsh must be installed globally beside dsh-code; run: npm install -g @deepseek-ai/dsh dsh-code')
50
85
  process.exitCode = 1
51
86
  return undefined
52
87
  }
package/lib/index.mjs CHANGED
@@ -25430,19 +25430,23 @@ function projectEvents(events) {
25430
25430
  return events.reduce(projectEvent, createTranscriptView());
25431
25431
  }
25432
25432
  /**
25433
- * How many leading transcript entries can never change again: only a
25434
- * `running` tool or retry can still mutate in place everything before the
25435
- * first one (including a completed tail: later events only APPEND new rows)
25436
- * is final. The renderer currently draws the whole transcript dynamically
25437
- * (a `<Static>` flush proved unstable with CJK wrapping on real terminals);
25438
- * this boundary stays as the append-only contract for when flushing is
25439
- * reintroduced.
25433
+ * The append-only flush boundary for a transcript view: the count of entries
25434
+ * no later event can remove. Entries at or beyond this index are mutable and
25435
+ * must stay in the live tree.
25436
+ *
25437
+ * `pending` rows are excluded even though they are not a running tool/retry:
25438
+ * the inbox claims or cancels them durably (`agent/inbox/spliced` removals,
25439
+ * `user/message` retirement), and an append-only `<Static>` flush cannot
25440
+ * erase a row that vanishes from the view — the retired row would ghost on
25441
+ * screen until the next source-backed replay. Everything else (including a
25442
+ * completed tail) is final: later events only APPEND new rows.
25440
25443
  * @param entries - the view's transcript entries in order.
25441
25444
  * @returns the count of entries safe to flush (0 for an empty transcript).
25442
25445
  */
25443
25446
  function settledEntryCount(entries) {
25444
25447
  for (let index = 0; index < entries.length; index++) {
25445
25448
  const entry = entries[index];
25449
+ if (entry.kind === "pending") return index;
25446
25450
  if (entry.kind === "tool" && entry.state === "running") return index;
25447
25451
  if (entry.kind === "retry" && entry.state === "running") return index;
25448
25452
  }
@@ -30276,6 +30280,7 @@ async function run(ctx, startup, io) {
30276
30280
  mentions = next.mentions;
30277
30281
  commands.setAgent(agent);
30278
30282
  skills.setAgent(agent);
30283
+ process.stdout.write("\x1B[r\x1B[0m\x1B[H\x1B[2J\x1B[3J\x1B[H");
30279
30284
  renderCurrent();
30280
30285
  const queued = pendingInputs.splice(0);
30281
30286
  for (const item of queued) deliverLine(item.text, item.mode);
@@ -235,13 +235,16 @@ export declare function projectEvent(view: TranscriptView, event: SessionEvent):
235
235
  */
236
236
  export declare function projectEvents(events: readonly SessionEvent[]): TranscriptView;
237
237
  /**
238
- * How many leading transcript entries can never change again: only a
239
- * `running` tool or retry can still mutate in place everything before the
240
- * first one (including a completed tail: later events only APPEND new rows)
241
- * is final. The renderer currently draws the whole transcript dynamically
242
- * (a `<Static>` flush proved unstable with CJK wrapping on real terminals);
243
- * this boundary stays as the append-only contract for when flushing is
244
- * reintroduced.
238
+ * The append-only flush boundary for a transcript view: the count of entries
239
+ * no later event can remove. Entries at or beyond this index are mutable and
240
+ * must stay in the live tree.
241
+ *
242
+ * `pending` rows are excluded even though they are not a running tool/retry:
243
+ * the inbox claims or cancels them durably (`agent/inbox/spliced` removals,
244
+ * `user/message` retirement), and an append-only `<Static>` flush cannot
245
+ * erase a row that vanishes from the view — the retired row would ghost on
246
+ * screen until the next source-backed replay. Everything else (including a
247
+ * completed tail) is final: later events only APPEND new rows.
245
248
  * @param entries - the view's transcript entries in order.
246
249
  * @returns the count of entries safe to flush (0 for an empty transcript).
247
250
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-code",
3
3
  "description": "Claude-Code-style interactive TUI bundle for DeepSeek Harness (dsh): DeepSeek-blue whale banner, live session transcript, and a blended status line",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "deepseek": "./bin/deepseek.mjs",
package/src/index.ts CHANGED
@@ -523,6 +523,11 @@ async function run(ctx: Context, startup: TuiStartup, io: TuiIo): Promise<void>
523
523
  mentions = next.mentions
524
524
  commands.setAgent(agent)
525
525
  skills.setAgent(agent)
526
+ // The App mounts with a placeholder key until the first input; the
527
+ // key-change remount below must start from a clean screen or the ghost
528
+ // static header stays visible above the new one (same source-backed
529
+ // clear the session-switch path performs).
530
+ process.stdout.write('\x1b[r\x1b[0m\x1b[H\x1b[2J\x1b[3J\x1b[H')
526
531
  renderCurrent()
527
532
  const queued = pendingInputs.splice(0)
528
533
  for (const item of queued) deliverLine(item.text, item.mode)
@@ -635,19 +635,23 @@ export function projectEvents(events: readonly SessionEvent[]): TranscriptView {
635
635
  }
636
636
 
637
637
  /**
638
- * How many leading transcript entries can never change again: only a
639
- * `running` tool or retry can still mutate in place everything before the
640
- * first one (including a completed tail: later events only APPEND new rows)
641
- * is final. The renderer currently draws the whole transcript dynamically
642
- * (a `<Static>` flush proved unstable with CJK wrapping on real terminals);
643
- * this boundary stays as the append-only contract for when flushing is
644
- * reintroduced.
638
+ * The append-only flush boundary for a transcript view: the count of entries
639
+ * no later event can remove. Entries at or beyond this index are mutable and
640
+ * must stay in the live tree.
641
+ *
642
+ * `pending` rows are excluded even though they are not a running tool/retry:
643
+ * the inbox claims or cancels them durably (`agent/inbox/spliced` removals,
644
+ * `user/message` retirement), and an append-only `<Static>` flush cannot
645
+ * erase a row that vanishes from the view — the retired row would ghost on
646
+ * screen until the next source-backed replay. Everything else (including a
647
+ * completed tail) is final: later events only APPEND new rows.
645
648
  * @param entries - the view's transcript entries in order.
646
649
  * @returns the count of entries safe to flush (0 for an empty transcript).
647
650
  */
648
651
  export function settledEntryCount(entries: readonly TranscriptEntry[]): number {
649
652
  for (let index = 0; index < entries.length; index++) {
650
653
  const entry = entries[index]
654
+ if (entry.kind === 'pending') return index
651
655
  if (entry.kind === 'tool' && entry.state === 'running') return index
652
656
  if (entry.kind === 'retry' && entry.state === 'running') return index
653
657
  }