dsh-rewind-plugin 0.4.1 → 0.5.0

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/docs/format.md ADDED
@@ -0,0 +1,150 @@
1
+ # Durable format
2
+
3
+ The on-disk format of the dsh-rewind checkpoint store, pinned as a spec. The
4
+ implementation is `src/snapshot.ts`; this document is the reference for what
5
+ readers may rely on and what a future incompatible change must do. If the code
6
+ and this spec disagree, the code wins and this spec is a bug.
7
+
8
+ ## State root
9
+
10
+ The store root defaults to `~/.dsh/rewind-snapshots/` (the dsh data
11
+ directory), overridable via the `DSH_REWIND_SNAPSHOT_DIR` environment
12
+ variable. It is a sibling of the workspace, never a subtree of it. Deleting
13
+ the root only removes file backups; the store rebuilds from scratch.
14
+
15
+ ```
16
+ <root>/
17
+ └── <sessionId>/ # safeSessionId(sessionId)
18
+ ├── <anchorSeq>/ # decimal seq of the anchoring user/message
19
+ │ └── <callId>.json # one committed before-backup
20
+ └── restore-journal-<opId>.json # one restore-op journal
21
+ ```
22
+
23
+ - `sessionId` is sanitized to `[a-zA-Z0-9._-]`; the bare values `.` and `..`
24
+ are replaced (`safeSessionId`) so a hostile id cannot traverse out of the
25
+ root.
26
+ - `callId` is sanitized to `[a-zA-Z0-9._-]` (`safeFileId`).
27
+ - `<anchorSeq>` is a decimal integer; directories with non-integer names are
28
+ ignored by readers.
29
+ - Journal files are recognized by the `restore-journal-` prefix; everything
30
+ else ending in `.json` under the session dir is treated as a checkpoint
31
+ entry.
32
+
33
+ ## Checkpoint entry
34
+
35
+ One JSON file per before-backup, named `<callId>.json`:
36
+
37
+ ```ts
38
+ interface CheckpointEntry {
39
+ callId: string // the tool call that mutated the file
40
+ anchorSeq: number // seq of the user message anchoring the turn of the change
41
+ path: string // resolved display path (absolute)
42
+ before: string | null // full content before the change; null = file was created
43
+ time: number // epoch ms, strictly increasing within a store instance
44
+ }
45
+ ```
46
+
47
+ Semantics:
48
+
49
+ - **`before` is the pre-edit state**: `null` means the call created the file.
50
+ - **`anchorSeq` ties the backup to a user message**: rewinding to message N
51
+ applies every entry anchored at or after N (the boundary is inclusive).
52
+ - **`time` is the ordering key within an anchor group**: it is monotonic per
53
+ store instance (bumped past the previous commit), so same-millisecond
54
+ commits stay capture-ordered and a re-read always picks the same "earliest"
55
+ entry per path.
56
+ - Synthetic re-check entries (external edits/deletions seen at a user-message
57
+ boundary) use `callId = recheck-<anchorSeq>-<sha256(path) first 8 hex>`.
58
+
59
+ ### Dedup link entry
60
+
61
+ A tracked file that records the same `before` content as its immediately-prior
62
+ entry for that path is stored as a **link** instead of a full copy: the entry
63
+ carries a `ref` (the `<anchorSeq>/<callId>.json` of that prior entry) and omits
64
+ `before`, so identical content is never duplicated across entries. A reader
65
+ resolves the `ref` back to the terminal real snapshot; `before: null` still
66
+ means "the file was created". A `ref` is validated as a single-level,
67
+ `<digits>/<callId>.json` relative reference (no traversal) so a corrupt or
68
+ hostile ref cannot escape the store root when followed. Because links reference
69
+ prior entries, `prune` materializes a surviving link whose `ref` lands on a
70
+ group it is about to drop before deleting that group, so no kept link is left
71
+ dangling.
72
+
73
+ Real entries (with `before`) are unchanged and read identically before and
74
+ after this addition; a link entry is a distinct kind that lacks `before`.
75
+
76
+ ## Restore journal
77
+
78
+ One JSON file per restore operation, written **before any mutation** and
79
+ updated as the pass applies:
80
+
81
+ ```ts
82
+ interface RestoreJournal {
83
+ version: 1
84
+ id: string // `op-<base36 ms>-<random>`; file name suffix
85
+ sessionId: string
86
+ targetSeq: number // rewind target the restore belongs to
87
+ startedAt: number // epoch ms
88
+ finishedAt?: number // set on a terminal state
89
+ state: 'running' | 'rollback-running' | 'completed' | 'rolled-back' | 'recovery-required'
90
+ actions: RestoreJournalAction[]
91
+ rollbackError?: string // set when a rollback pass failed partway
92
+ }
93
+
94
+ interface RestoreJournalAction {
95
+ path: string
96
+ action: 'restore' | 'delete' // restore = write `before` back; delete = unlink
97
+ before: string | null // target content for restore; null for delete
98
+ rescue: string | null // pre-restore disk state; null = file was absent
99
+ rescueError?: string // set when the rescue capture failed (rollback skips it)
100
+ done: boolean // true once the action's fs op completed and was marked
101
+ failed?: string // per-action failure message (the pass never aborts)
102
+ }
103
+ ```
104
+
105
+ States: `running` and `rollback-running` are non-terminal; a host restart turns
106
+ them into `interrupted` (or `recovery-required` when the journal is corrupt or
107
+ a rollback could not complete). `completed` / `rolled-back` are terminal.
108
+
109
+ ## Write guarantees
110
+
111
+ - **Atomicity**: every JSON write serializes to a sibling `<target>.tmp` and
112
+ renames over the target. A crash between the two steps leaves only the temp
113
+ file — never a readable half-written target — and readers ignore temp files
114
+ (they do not end in `.json`). The next write of the same target overwrites
115
+ a leftover temp.
116
+ - **Journal before mutation**: the rescue state of every planned path is
117
+ captured and the intent journal persisted atomically BEFORE the first fs
118
+ mutation; each action is marked `done` as it is applied.
119
+ - **Disk is truth**: after a restart, reconciliation compares the real disk
120
+ against each action's goal (the restore target for `running` journals, the
121
+ rescue state for `rollback-running` / `recovery-required` ones). A path
122
+ whose disk already matches is marked done without being touched.
123
+ - **Bounded storage**: `prune` keeps the newest 100 anchor groups per session
124
+ (`MAX_ANCHOR_GROUPS`), materializing any surviving dedup link that references
125
+ a group being dropped before deleting whole anchor directories; it also
126
+ recycles terminal journals (`completed` / `rolled-back`). Non-terminal and
127
+ corrupt journals are always kept. Across sessions, `pruneStale` removes whole
128
+ long-inactive session directories whose newest member stamp is older than a
129
+ configurable idle cutoff (default off), so the store root does not grow
130
+ without bound either.
131
+
132
+ ## Validation and failure policy
133
+
134
+ - **Entries**: a missing or malformed entry is read as `undefined` (silently
135
+ skipped) — losing one backup, never the recovery path.
136
+ - **Journals**: a corrupt or schema-invalid journal **fails loud** —
137
+ `reconcileRestores` reports it as `recovery-required` and never drops it,
138
+ because dropping it would silently erase the interrupted restore's recovery
139
+ record.
140
+ - **Journal IO**: best-effort by design — if a journal cannot be written, the
141
+ restore proceeds with pre-journal semantics (crash safety degrades,
142
+ behavior does not).
143
+
144
+ ## Versioning policy
145
+
146
+ The journal schema is `version: 1`. Checkpoint entries currently carry no
147
+ version field. A future incompatible format must either bump the journal
148
+ `version` (readers reject unknown values — there is no best-effort fallback or
149
+ legacy coercion) or move the state root (e.g. `rewind-snapshots/v2`) and ship
150
+ an explicit migration tool. Old-format data is never silently re-interpreted.
@@ -21,7 +21,7 @@
21
21
 
22
22
  Compatibility probes against these subsystems (test-driven investigation, the
23
23
  `compat-invariants` / `compat-interop` vitest suites + the `verify-host` real
24
- `/compact` chain): [compat-audit.md](compat-audit.md).
24
+ `/compact` chain): [compat/audit.md](compat/audit.md).
25
25
 
26
26
  Also under `docs/` at the repo root: `persistence-catalog.md` (full
27
27
  `SessionEventMap`), `tool-catalog.md` (tool inventory), `config-catalog.md`
@@ -45,6 +45,10 @@ Also under `docs/` at the repo root: `persistence-catalog.md` (full
45
45
 
46
46
  ## Plugin source layout
47
47
 
48
+ The module map below duplicates `AGENTS.md` (`Layout at a glance`), which is
49
+ the canonical source; this block only adds the finer-grained client-side files
50
+ and packaging entries.
51
+
48
52
  ```
49
53
  src/index.ts host plugin: /rewind command + checkpoint pipeline (tools/execute|post-execute)
50
54
  src/rewind.ts pure planning: target resolution, surface range, candidate listing
@@ -56,9 +60,9 @@ src/client/hidden.ts withdrawn-span computation (hiddenSeqsOf), pure
56
60
  src/client/locales.ts zh / en copy (LocaleNamespaceMap)
57
61
  src/client/styles.ts injected styles (dsh design tokens)
58
62
  scripts/build.mjs esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
59
- scripts/verify-host.mjs end-to-end host verification (18 checks)
63
+ scripts/verify-host.mjs end-to-end host verification (full check suite)
60
64
  tests/ vitest suites (rewind / snapshot / hidden / session-cwd / integration)
61
- docs/ maintainer docs: harness reference, troubleshooting, release steps
65
+ docs/ maintainer docs: contract/, compat/, release/ subdirectories
62
66
  assets/screenshots/ UI screenshots
63
67
  cordis.patch.yml bundle patch (mounts the dual-face plugin row)
64
68
  package.json dsh.bundle + dsh.client manifests, optional peerDependencies
@@ -0,0 +1,69 @@
1
+ # Release
2
+
3
+ [简体中文](release.zh.md)
4
+
5
+ ## First release (manual, one-time)
6
+
7
+ Trusted Publisher can only be configured once the package exists, so the first
8
+ version is published locally:
9
+
10
+ ```sh
11
+ npm login
12
+ npm publish --access public
13
+ ```
14
+
15
+ - If prompted for `EOTP`: complete the browser auth link the CLI prints, or
16
+ retry with a 6-digit code — `npm publish --otp=<code>`.
17
+ - The first version carries no provenance (local path) — acceptable; every CI
18
+ release after that publishes with Sigstore/SLSA provenance automatically.
19
+
20
+ ## Configure Trusted Publisher (npmjs.com, one-time)
21
+
22
+ Open `https://www.npmjs.com/package/dsh-rewind-plugin` → package **settings** →
23
+ **Trusted Publisher**:
24
+
25
+ | Field | Value |
26
+ | --- | --- |
27
+ | Provider | GitHub Actions |
28
+ | Organization or user | `SiriLee` |
29
+ | Repository | `dsh-rewind` (the GitHub repo, not the npm name) |
30
+ | Workflow filename | `publish.yml` |
31
+ | Environment | empty |
32
+ | Allowed actions | `npm publish` |
33
+
34
+ ## Subsequent releases (CI, automatic)
35
+
36
+ ```sh
37
+ npm version patch
38
+ git push origin main --tags # triggers .github/workflows/publish.yml
39
+ ```
40
+
41
+ - The workflow verifies the tag matches `package.json`, runs typecheck + tests +
42
+ a full build + artifact verification, publishes with `--provenance`
43
+ (Sigstore), and creates a GitHub Release. It is **idempotent** — an already
44
+ published version is skipped.
45
+ - CI (`.github/workflows/ci.yml`) runs `npm run check` — typecheck + tests +
46
+ build + artifact verification + a `npm pack --dry-run` — on every push / PR
47
+ across both Node engines boundary versions; the tarball layout is guarded by
48
+ `tests/package-layout.test.ts`.
49
+
50
+ ## DSH version alignment (peer range maintenance)
51
+
52
+ DSH is still in rc; npm's prerelease matching rules require a peer range to
53
+ share the host version's `[major, minor, patch]` tuple. So `peerDependencies`
54
+ uses an OR-union covering every published rc tuple series
55
+ (e.g. `^0.1.0-rc.6 || ^0.1.1-rc.2`), extended as DSH releases new tuples.
56
+
57
+ - **When to update**: only when DSH releases a new tuple
58
+ (`0.1.1 → 0.1.2 → 0.2.x`); rc rolling within a tuple (`0.1.1-rc.2 → rc.3`)
59
+ needs nothing. All `@deepseek-ai/*` packages release together;
60
+ `npm view @deepseek-ai/dsh version` is the authoritative signal.
61
+ - **Automatic detection**: `node scripts/check-dsh-version.mjs` compares the
62
+ latest npm version against the tuples the peers cover and reports whether an
63
+ extension is needed (exit 0 = nothing to do, exit 1 = update).
64
+ - **Update steps**: append `|| ^<new-tuple>-rc.<n>` to every
65
+ `@deepseek-ai/dsh-*` peer → bump devDependencies to the latest → `npm
66
+ install` → `npm run check` → release.
67
+ - **After DSH goes final**: final releases are not bound by the prerelease
68
+ tuple rule, so the peers can converge to a single stable range (e.g.
69
+ `^0.1.x`); this section can then be deleted.
@@ -1,53 +1,7 @@
1
- # Release
2
-
3
- ## First release (manual, one-time)
4
-
5
- Trusted Publisher can only be configured once the package exists, so the first
6
- version is published locally:
7
-
8
- ```sh
9
- npm login
10
- npm publish --access public
11
- ```
12
-
13
- - If prompted for `EOTP`: complete the browser auth link the CLI prints, or
14
- retry with a 6-digit code — `npm publish --otp=<code>`.
15
- - The first version carries no provenance (local path) — acceptable; every CI
16
- release after that publishes with Sigstore/SLSA provenance automatically.
17
-
18
- ## Configure Trusted Publisher (npmjs.com, one-time)
19
-
20
- Open `https://www.npmjs.com/package/dsh-rewind-plugin` → package **settings** →
21
- **Trusted Publisher**:
22
-
23
- | Field | Value |
24
- | --- | --- |
25
- | Provider | GitHub Actions |
26
- | Organization or user | `SiriLee` |
27
- | Repository | `dsh-rewind` (the GitHub repo, not the npm name) |
28
- | Workflow filename | `publish.yml` |
29
- | Environment | empty |
30
- | Allowed actions | `npm publish` |
31
-
32
- ## Subsequent releases (CI, automatic)
33
-
34
- ```sh
35
- npm version patch
36
- git push origin main --tags # triggers .github/workflows/publish.yml
37
- ```
38
-
39
- - The workflow verifies the tag matches `package.json`, runs typecheck + tests +
40
- a full build + artifact verification, publishes with `--provenance`
41
- (Sigstore), and creates a GitHub Release. It is **idempotent** — an already
42
- published version is skipped.
43
- - CI (`.github/workflows/ci.yml`) runs the same checks on every push / PR, plus
44
- a `npm pack --dry-run` sanity check that the tarball carries `lib/` and
45
- `LICENSE`.
46
-
47
- ---
48
-
49
1
  # 发布流程
50
2
 
3
+ [English](release.md)
4
+
51
5
  ## 首次发布(手动,一次性)
52
6
 
53
7
  Trusted Publisher 要求**包已存在**才能配置,因此首个版本走本地发布:
@@ -86,8 +40,9 @@ git push origin main --tags # 触发 .github/workflows/publish.yml
86
40
  - workflow 校验 tag 与 `package.json` 版本一致,跑 typecheck + 测试 + 完整
87
41
  构建 + 产物验证,以 `--provenance`(Sigstore)发布并创建 GitHub Release。
88
42
  **幂等**——已发布的版本会跳过。
89
- - CI(`.github/workflows/ci.yml`)在每次 push / PR 跑相同检查,外加
90
- `npm pack --dry-run` 校验 tarball 含 `lib/` 与 `LICENSE`。
43
+ - CI(`.github/workflows/ci.yml`)在每次 push / PR 跑 `npm run check`——
44
+ typecheck + 测试 + 构建 + 产物验证 + `npm pack --dry-run`,且覆盖
45
+ engines 两个边界版本;tarball 布局由 `tests/package-layout.test.ts` 守护。
91
46
 
92
47
  ## DSH 版本适配(peer 范围维护)
93
48
 
@@ -102,7 +57,6 @@ OR 并集覆盖 DSH 已发布的每个 rc 元组系列(如 `^0.1.0-rc.6 || ^0.
102
57
  - **自动检测**:`node scripts/check-dsh-version.mjs` 对比 npm 最新版本与
103
58
  peer 覆盖的元组,输出是否需追加(exit 0 无需动作,exit 1 需要)。
104
59
  - **更新步骤**:给每个 `@deepseek-ai/dsh-*` peer 追加 `|| ^<新元组>-rc.<n>`
105
- → devDependencies 同步升到最新 → `npm install` → `npm run typecheck` /
106
- `npm test` / `npm run verify:host` → 发版。
60
+ → devDependencies 同步升到最新 → `npm install` → `npm run check` → 发版。
107
61
  - **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
108
62
  限制,peer 可收敛为稳定的 `^0.1.x` 单范围,此节即可删除。
@@ -0,0 +1,71 @@
1
+ # Snapshot auto-cleanup
2
+
3
+ The rewind store writes one on-disk `before` backup per tracked file change,
4
+ grouped by its anchor message. Snapshots are deduped within a session and capped
5
+ at the newest 100 anchor groups, but across sessions that are no longer active
6
+ the store can
7
+ still grow without bound. `snapshot-auto-cleanup` is an OPTIONAL global policy
8
+ (off by default) that removes the whole snapshot directory of a session that has
9
+ been **long-inactive** — untouched past a configurable idle cutoff.
10
+
11
+ It only ever removes the whole snapshot **directory** of a long-inactive session.
12
+ It never touches the active session's snapshots, never touches the conversation
13
+ log, and leaves snapshot data within the idle cutoff alone.
14
+
15
+ ## Commands
16
+
17
+ ```
18
+ /snapshot-auto-cleanup show status (enabled, max-age, config path)
19
+ /snapshot-auto-cleanup on|off enable/disable the automatic sweep
20
+ /snapshot-auto-cleanup max-age <days> set the idle cutoff (positive integer)
21
+ /snapshot-auto-cleanup run dry-run: list what would be removed
22
+ /snapshot-auto-cleanup run --apply actually remove those sessions
23
+ ```
24
+
25
+ `run` is a manual escape hatch and works whether or not the automatic cleanup is
26
+ `on`. `run` defaults to a dry-run; add `--apply` to execute.
27
+
28
+ ## Config file
29
+
30
+ The policy is persisted to `~/.dsh/snapshot-cleanup.json`:
31
+
32
+ ```json
33
+ { "enabled": false, "maxAgeDays": 30 }
34
+ ```
35
+
36
+ - `enabled` — whether the automatic sweeps run (default `false`).
37
+ - `maxAgeDays` — how many idle days before a long-inactive session's snapshot dir is
38
+ removed (default `30`; `0`/negative are rejected, so a broken config can never
39
+ delete everything).
40
+
41
+ Override the path with the `DSH_SNAPSHOT_CLEANUP_CONFIG` environment variable.
42
+ The file is written only by the `/snapshot-auto-cleanup` command. A missing file
43
+ reads as the safe default (off); a missing or corrupt file makes a sweep
44
+ **fail-closed** (delete nothing) and log a warning, and is surfaced when you run
45
+ the command again.
46
+
47
+ ## When it runs
48
+
49
+ The 24h window is anchored on a **persisted** last-sweep time
50
+ (`~/.dsh/snapshot-cleanup-last-sweep.json`), so a host restart does not reset
51
+ it: the auto-sweep checks **once per process run**, on the first session
52
+ activity of a window (a user message or a completed tool call), and cleans only
53
+ when enabled **and** >=24h since the last sweep. It runs in the background and
54
+ never blocks the activity that triggered it. Because the check happens once per
55
+ run, a change that takes effect immediately is best applied with
56
+ `/snapshot-auto-cleanup run`; editing the config file by hand (or enabling after
57
+ the run's first activity) takes effect on the next run.
58
+
59
+ ## Safety and boundaries
60
+
61
+ - Removes only whole **long-inactive** session dirs; the active session and the
62
+ conversation log are never touched.
63
+ - "Inactive" is judged by mtime: a session still being written to keeps scrolling
64
+ its newest member stamp forward, so it is never old enough to be pruned.
65
+ - Dedup `ref` links are session-relative, so removing a whole dir cannot dangle a
66
+ link elsewhere.
67
+ - Trade-off: enabling auto-cleanup means a session resumed after a long idle gap
68
+ will rewind only from its remaining (newest 100) anchors; its old snapshots are
69
+ gone. The conversation log is never affected.
70
+ - Deleting the whole store dir manually stays safe (it is recreated on the next
71
+ capture); auto-cleanup just scopes that removal to long-inactive sessions.
@@ -0,0 +1,42 @@
1
+ # 快照自动清理
2
+
3
+ 回退插件为每次被跟踪的文件修改写入一份磁盘 `before` 备份,并按其锚点消息分组。快照在**同一会话**内去重、且保留最近 100 组锚点,但跨**已不再活跃的会话**时存储仍会无界增长。`snapshot-auto-cleanup` 是**可选**的全局策略(默认关闭):把**长期不活跃**(失活超过可配置阈值)的会话快照**整目录**移除。
4
+
5
+ 它只会移除**长期不活跃**会话的整个快照目录,绝不触碰活动会话的快照、绝不触碰对话日志,也保留仍在阈值内的快照数据。
6
+
7
+ ## 命令
8
+
9
+ ```
10
+ /snapshot-auto-cleanup 查看状态(是否开启、max-age、配置路径)
11
+ /snapshot-auto-cleanup on|off 开启/关闭自动清理
12
+ /snapshot-auto-cleanup max-age <天数> 设置失活阈值(正整数)
13
+ /snapshot-auto-cleanup run 预演:列出将移除哪些会话
14
+ /snapshot-auto-cleanup run --apply 真正移除这些会话
15
+ ```
16
+
17
+ `run` 是手动逃生舱,无论自动清理是否为 `on` 都能用;默认只预演,加 `--apply` 才执行。
18
+
19
+ ## 配置文件
20
+
21
+ 策略持久化到 `~/.dsh/snapshot-cleanup.json`:
22
+
23
+ ```json
24
+ { "enabled": false, "maxAgeDays": 30 }
25
+ ```
26
+
27
+ - `enabled` — 是否运行自动清理(默认 `false`)。
28
+ - `maxAgeDays` — 失活多少天后移除某**长期不活跃**会话的快照目录(默认 `30`;`0`/负数会被拒绝,因此损坏的配置不可能导致"删除一切")。
29
+
30
+ 可用环境变量 `DSH_SNAPSHOT_CLEANUP_CONFIG` 覆盖路径。该文件只由 `/snapshot-auto-cleanup` 命令写入。文件缺失按安全默认(关闭)处理;缺失或损坏会令一次清扫**fail-closed**(不删任何东西)并写入警告,再次运行命令时会有所提示。
31
+
32
+ ## 运行时机
33
+
34
+ 24 小时窗口锚定在**持久化**的上次清扫时间(`~/.dsh/snapshot-cleanup-last-sweep.json`)上,因此宿主重启不会重置:自动清扫在**每次运行只检查一次**,于某个窗口的首次会话活动(一条用户消息或一次工具调用完成)时执行,且仅在**已启用**且距上次清扫 **>=24 小时**时才清理。它后台运行,从不阻塞触发它的活动。由于每次运行只检查一次,想让改动立即生效最好用 `/snapshot-auto-cleanup run`;手动编辑配置文件(或在本次运行的首次活动之后才开启)会到下次运行才生效。
35
+
36
+ ## 安全与边界
37
+
38
+ - 只移除**长期不活跃**会话的整目录;活动会话与对话日志永不触碰。
39
+ - "不活跃"以 mtime 判定:仍在写入的会话会不断把"最新成员"时间戳往后推,因此永远到不了可被清理的失活阈值。
40
+ - 去重 `ref` 链接是会话内相对的,所以移除整目录不会让别处产生悬空链接。
41
+ - 权衡:开启自动清理后,某个长时间闲置才被重新打开的会话,只能从剩余(最近 100 组)锚点回退,旧快照已不存在;对话日志永不受影响。
42
+ - 手动删除整个存储目录依然安全(下次捕获时自动重建);自动清理只是把这种删除限定到长期不活跃的会话。
package/lib/client.js CHANGED
@@ -92,7 +92,7 @@ function hiddenSeqsOf(snap) {
92
92
 
93
93
  // src/client/candidates.ts
94
94
  var PREVIEW_CHARS = 80;
95
- var DEFAULT_CANDIDATE_LIMIT = 50;
95
+ var DEFAULT_CANDIDATE_LIMIT = 100;
96
96
  function messagePreviewOf(message) {
97
97
  const text = message.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("").replace(/\s+/g, " ").trim();
98
98
  return text.length <= PREVIEW_CHARS ? text : `${text.slice(0, PREVIEW_CHARS - 1)}\u2026`;