dsh-code 1.0.6 → 1.2.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/README.en.md +123 -26
- package/README.md +124 -27
- package/bin/deepseek.mjs +283 -35
- package/cordis.patch.yml +97 -0
- package/lib/index.mjs +5008 -881
- package/lib/session-query.mjs +150 -0
- package/lib/startup.mjs +4 -4
- package/lib/{theme-DCT8Y2xf.mjs → theme-7u5Qo3dF.mjs} +657 -20
- package/lib/types/app.d.ts +106 -62
- package/lib/types/authorization-panel.d.ts +3 -3
- package/lib/types/git-workflow.d.ts +91 -2
- package/lib/types/i18n.d.ts +39 -0
- package/lib/types/index.d.ts +100 -1
- package/lib/types/input-split.d.ts +1 -1
- package/lib/types/kernel-panels.d.ts +107 -29
- package/lib/types/language-panel.d.ts +12 -0
- package/lib/types/locales/en.d.ts +450 -0
- package/lib/types/locales/zh.d.ts +9 -0
- package/lib/types/mentions.d.ts +7 -3
- package/lib/types/models.d.ts +14 -0
- package/lib/types/panel-accent.d.ts +28 -0
- package/lib/types/rainbow.d.ts +69 -0
- package/lib/types/render/animations.d.ts +42 -0
- package/lib/types/render/editor.d.ts +4 -3
- package/lib/types/render/ime-cursor.d.ts +60 -0
- package/lib/types/render/inspector.d.ts +26 -0
- package/lib/types/render/lines.d.ts +21 -1
- package/lib/types/render/markdown.d.ts +1 -1
- package/lib/types/render/projection.d.ts +130 -4
- package/lib/types/render/status.d.ts +9 -9
- package/lib/types/render/text.d.ts +6 -0
- package/lib/types/render/usage.d.ts +113 -0
- package/lib/types/session-directory.d.ts +17 -0
- package/lib/types/session-query.d.ts +92 -0
- package/lib/types/startup.d.ts +1 -1
- package/lib/types/terminal-title.d.ts +66 -0
- package/lib/types/theme-panel.d.ts +2 -2
- package/lib/types/theme.d.ts +271 -52
- package/lib/types/update-panel.d.ts +49 -0
- package/lib/types/update.d.ts +75 -0
- package/lib/types/version.d.ts +4 -3
- package/package.json +246 -90
- package/src/app.ts +1369 -509
- package/src/approval.ts +166 -166
- package/src/authorization-panel.ts +19 -16
- package/src/editor-keys.ts +371 -371
- package/src/git-workflow.ts +229 -3
- package/src/i18n.ts +68 -0
- package/src/index.ts +534 -80
- package/src/input-split.ts +3 -3
- package/src/internals.ts +5 -0
- package/src/kernel-panels.ts +554 -86
- package/src/keyboard.ts +5 -4
- package/src/language-panel.ts +53 -0
- package/src/locales/en.ts +489 -0
- package/src/locales/zh.ts +488 -0
- package/src/mentions.ts +8 -4
- package/src/models.ts +264 -212
- package/src/panel-accent.ts +41 -0
- package/src/presets.ts +1 -1
- package/src/provider-settings.ts +1 -1
- package/src/rainbow.ts +208 -0
- package/src/render/animations.ts +104 -6
- package/src/render/editor.ts +25 -24
- package/src/render/export.ts +116 -95
- package/src/render/ime-cursor.ts +147 -0
- package/src/render/inspector.ts +42 -0
- package/src/render/lines.ts +628 -415
- package/src/render/markdown.ts +15 -3
- package/src/render/projection.ts +572 -21
- package/src/render/status.ts +59 -39
- package/src/render/text.ts +14 -0
- package/src/render/tool-preview.ts +77 -77
- package/src/render/usage.ts +430 -0
- package/src/render/width.ts +2 -2
- package/src/session-directory.ts +8 -6
- package/src/session-query.ts +239 -0
- package/src/startup.ts +3 -3
- package/src/subagents.ts +229 -229
- package/src/terminal-title.ts +190 -0
- package/src/theme-panel.ts +17 -21
- package/src/theme.ts +281 -33
- package/src/update-panel.ts +256 -0
- package/src/update.ts +126 -0
- package/src/version.ts +58 -20
- package/src/whale-glyph.ts +23 -23
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skip-tolerant session-query engine for this terminal.
|
|
3
|
+
*
|
|
4
|
+
* The upstream SqliteSessionQueryEngine reconciliation observes EVERY
|
|
5
|
+
* persisted session before each search, and ONE unreadable source (for
|
|
6
|
+
* example a pre-release session artifact the frozen format codecs reject)
|
|
7
|
+
* fails the whole pass with SESSION_QUERY_PERSISTENCE_FAILED — every
|
|
8
|
+
* cross-session search dies because of a single old file nobody opened
|
|
9
|
+
* otherwise. This subclass overrides only the observation loop so an
|
|
10
|
+
* unreadable source is skipped with a warning and the rest of the corpus
|
|
11
|
+
* indexes normally; skipped sessions are retried on later reconciliations
|
|
12
|
+
* and rejoin automatically once a host that can read them is installed.
|
|
13
|
+
*
|
|
14
|
+
* Vendored surface note: `_observeStable` and its module-local helpers are
|
|
15
|
+
* private upstream; this file re-declares the observation loop against the
|
|
16
|
+
* pinned @deepseek-ai line (see package.json peers) and must be re-checked
|
|
17
|
+
* whenever that line moves. The engine class and the tool boundary also
|
|
18
|
+
* share ONE physical parent-package instance through this bundle, which
|
|
19
|
+
* restores instanceof-based typed error messages on the search path.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createHash } from 'node:crypto'
|
|
23
|
+
import SqliteSessionQueryEngine, { type Config } from '@deepseek-ai/dsh-session-query-sqlite'
|
|
24
|
+
import {
|
|
25
|
+
assertSessionHeadersCompatible,
|
|
26
|
+
buildSessionEventSearchDocuments,
|
|
27
|
+
readColdSessionLog,
|
|
28
|
+
SessionQueryError,
|
|
29
|
+
} from '@deepseek-ai/dsh-session-query'
|
|
30
|
+
import type { SessionEvent, SessionHeader, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session'
|
|
31
|
+
import type { SessionPersistenceRevision, SessionPersistenceSnapshot } from '@deepseek-ai/dsh-session-persistence'
|
|
32
|
+
|
|
33
|
+
/** One observed session: detached header plus its derived search documents. */
|
|
34
|
+
interface ObservedSession {
|
|
35
|
+
header: SessionHeader
|
|
36
|
+
inheritedEventCount: SessionLogOffset
|
|
37
|
+
documents: readonly ReturnType<typeof buildSessionEventSearchDocuments>[number][]
|
|
38
|
+
fingerprint: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** One persisted snapshot as the reconciliation sees it (loaded once readable). */
|
|
42
|
+
interface ObservedPersistedSession {
|
|
43
|
+
header: SessionHeader
|
|
44
|
+
revision: SessionPersistenceRevision
|
|
45
|
+
loaded?: ObservedSession
|
|
46
|
+
/** Diagnosis for a cold read that failed; the session stays unindexed. */
|
|
47
|
+
unreadable?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The engine-internal state the observation loop touches. */
|
|
51
|
+
export interface EngineSurface {
|
|
52
|
+
readonly ctx: {
|
|
53
|
+
sessions: {
|
|
54
|
+
list(): readonly { header: SessionHeader; inheritedEventCount: SessionLogOffset; snapshotEvents(): readonly SessionEvent[]; id: SessionId }[]
|
|
55
|
+
get(id: SessionId): unknown
|
|
56
|
+
}
|
|
57
|
+
logger?: { warn(format: string, ...args: readonly unknown[]): void }
|
|
58
|
+
}
|
|
59
|
+
readonly _persistenceBinding: {
|
|
60
|
+
readonly identity: symbol
|
|
61
|
+
readonly service?: {
|
|
62
|
+
list(options?: { readonly signal?: AbortSignal }): Promise<readonly SessionPersistenceSnapshot[]>
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
_lastPersistenceIdentity: symbol | undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type ColdRead = (persistence: NonNullable<EngineSurface['_persistenceBinding']['service']>, id: SessionId, signal: AbortSignal | undefined) => Promise<{ header: SessionHeader; inheritedEventCount: SessionLogOffset; events: readonly SessionEvent[] }>
|
|
69
|
+
|
|
70
|
+
const STABLE_OBSERVATION_ATTEMPTS = 2
|
|
71
|
+
|
|
72
|
+
function assertNotAborted(signal: AbortSignal | undefined): void {
|
|
73
|
+
if (signal?.aborted) {
|
|
74
|
+
throw new SessionQueryError('session-search aborted', 'SESSION_QUERY_ABORTED')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isAbort(error: unknown): boolean {
|
|
79
|
+
return error instanceof SessionQueryError && error.code === 'SESSION_QUERY_ABORTED'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function errorMessage(error: unknown): string {
|
|
83
|
+
return error instanceof Error ? error.message : 'unknown error'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function observeSession(header: SessionHeader, inheritedEventCount: SessionLogOffset, events: readonly SessionEvent[]): ObservedSession {
|
|
87
|
+
const detachedHeader = structuredClone(header)
|
|
88
|
+
const detachedEvents = events.map(event => structuredClone(event))
|
|
89
|
+
return {
|
|
90
|
+
header: detachedHeader,
|
|
91
|
+
inheritedEventCount,
|
|
92
|
+
documents: buildSessionEventSearchDocuments(detachedHeader.id, detachedEvents),
|
|
93
|
+
fingerprint: createHash('sha256')
|
|
94
|
+
.update(JSON.stringify({ header: detachedHeader, inheritedEventCount, events: detachedEvents }))
|
|
95
|
+
.digest('base64url'),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function sameHeader(a: SessionHeader, b: SessionHeader): boolean {
|
|
100
|
+
return a.id === b.id
|
|
101
|
+
&& a.createdAt === b.createdAt
|
|
102
|
+
&& a.cwd === b.cwd
|
|
103
|
+
&& a.parentSession === b.parentSession
|
|
104
|
+
&& a.isSeeded === b.isSeeded
|
|
105
|
+
&& (a.delegationDepth ?? 0) === (b.delegationDepth ?? 0)
|
|
106
|
+
&& a.agentPreset === b.agentPreset
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function materializePersistenceSnapshots(snapshots: readonly SessionPersistenceSnapshot[]): Map<SessionId, ObservedPersistedSession> {
|
|
110
|
+
// `Array.isArray` narrows a declared array type to `any[]`, which would let
|
|
111
|
+
// every later access escape the type system. Step through `unknown` so the
|
|
112
|
+
// guard stays a runtime assertion and the element type stays declared.
|
|
113
|
+
const candidate: unknown = snapshots
|
|
114
|
+
if (!Array.isArray(candidate)) throw new Error('persistence snapshots must be an array')
|
|
115
|
+
const result = new Map<SessionId, ObservedPersistedSession>()
|
|
116
|
+
for (const snapshot of candidate as readonly SessionPersistenceSnapshot[]) {
|
|
117
|
+
if (typeof snapshot.revision !== 'string') {
|
|
118
|
+
throw new Error('persistence snapshot revision must be a string')
|
|
119
|
+
}
|
|
120
|
+
const header = structuredClone(snapshot.header)
|
|
121
|
+
if (result.has(header.id)) {
|
|
122
|
+
throw new Error(`persistence listed duplicate session "${header.id}"`)
|
|
123
|
+
}
|
|
124
|
+
result.set(header.id, { header, revision: snapshot.revision })
|
|
125
|
+
}
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function samePersistenceSnapshots(before: ReadonlyMap<SessionId, ObservedPersistedSession>, after: ReadonlyMap<SessionId, ObservedPersistedSession>): boolean {
|
|
130
|
+
if (before.size !== after.size) return false
|
|
131
|
+
for (const [id, first] of before) {
|
|
132
|
+
const second = after.get(id)
|
|
133
|
+
if (second === undefined || first.revision !== second.revision || !sameHeader(first.header, second.header)) return false
|
|
134
|
+
}
|
|
135
|
+
return true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The skip-tolerant observation pass: structurally the upstream loop, with
|
|
140
|
+
* the per-source cold read wrapped so one unreadable session degrades to a
|
|
141
|
+
* warning instead of failing every search. Exported for unit tests with an
|
|
142
|
+
* injectable cold reader.
|
|
143
|
+
*/
|
|
144
|
+
export async function observeStableWithSkip(
|
|
145
|
+
engine: EngineSurface,
|
|
146
|
+
indexed: ReadonlyMap<SessionId, { revision: SessionPersistenceRevision }>,
|
|
147
|
+
signal: AbortSignal | undefined,
|
|
148
|
+
readCold: ColdRead = readColdSessionLog as unknown as ColdRead,
|
|
149
|
+
): Promise<{ persistenceBinding: EngineSurface['_persistenceBinding']; persisted: Map<SessionId, ObservedPersistedSession>; live: Map<SessionId, ObservedSession> }> {
|
|
150
|
+
for (let attempt = 0; attempt < STABLE_OBSERVATION_ATTEMPTS; attempt += 1) {
|
|
151
|
+
assertNotAborted(signal)
|
|
152
|
+
const persistenceBinding = engine._persistenceBinding
|
|
153
|
+
const persistence = persistenceBinding.service
|
|
154
|
+
const initiallyLive = new Set(engine.ctx.sessions.list().map(session => session.id))
|
|
155
|
+
let persisted = new Map<SessionId, ObservedPersistedSession>()
|
|
156
|
+
if (persistence !== undefined) {
|
|
157
|
+
try {
|
|
158
|
+
const canReuseIndexed = engine._lastPersistenceIdentity === undefined
|
|
159
|
+
|| engine._lastPersistenceIdentity === persistenceBinding.identity
|
|
160
|
+
const listOptions = signal === undefined ? undefined : { signal }
|
|
161
|
+
const before = await persistence.list(listOptions)
|
|
162
|
+
assertNotAborted(signal)
|
|
163
|
+
persisted = materializePersistenceSnapshots(before)
|
|
164
|
+
for (const entry of persisted.values()) {
|
|
165
|
+
if (canReuseIndexed && indexed.get(entry.header.id)?.revision === entry.revision) continue
|
|
166
|
+
if (initiallyLive.has(entry.header.id) || engine.ctx.sessions.get(entry.header.id) !== undefined) continue
|
|
167
|
+
assertNotAborted(signal)
|
|
168
|
+
// The skip: a session whose cold log fails to migrate or decode
|
|
169
|
+
// stays OUT of the index with one warning; the remaining corpus
|
|
170
|
+
// indexes normally. `loaded` stays undefined exactly like a
|
|
171
|
+
// not-yet-read entry, so the stable-snapshot comparison and the
|
|
172
|
+
// live-preferred merge below are unaffected.
|
|
173
|
+
try {
|
|
174
|
+
const loaded = await readCold(persistence, entry.header.id, signal)
|
|
175
|
+
assertNotAborted(signal)
|
|
176
|
+
assertSessionHeadersCompatible(entry.header, loaded.header)
|
|
177
|
+
entry.loaded = observeSession(loaded.header, loaded.inheritedEventCount, loaded.events)
|
|
178
|
+
} catch (error: unknown) {
|
|
179
|
+
if (isAbort(error) || signal?.aborted) throw error
|
|
180
|
+
if (engine._persistenceBinding !== persistenceBinding) break
|
|
181
|
+
entry.unreadable = errorMessage(error)
|
|
182
|
+
engine.ctx.logger?.warn(
|
|
183
|
+
'session-search skipped unreadable session %s: %s',
|
|
184
|
+
entry.header.id,
|
|
185
|
+
entry.unreadable,
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
assertNotAborted(signal)
|
|
190
|
+
const afterSnapshots = await persistence.list(listOptions)
|
|
191
|
+
assertNotAborted(signal)
|
|
192
|
+
const after = materializePersistenceSnapshots(afterSnapshots)
|
|
193
|
+
if (!samePersistenceSnapshots(persisted, after)) continue
|
|
194
|
+
if (engine._persistenceBinding !== persistenceBinding) continue
|
|
195
|
+
} catch (error: unknown) {
|
|
196
|
+
if (isAbort(error) || signal?.aborted) {
|
|
197
|
+
throw new SessionQueryError('session-search aborted', 'SESSION_QUERY_ABORTED', { cause: error })
|
|
198
|
+
}
|
|
199
|
+
if (engine._persistenceBinding !== persistenceBinding) continue
|
|
200
|
+
if (error instanceof SessionQueryError) throw error
|
|
201
|
+
throw new SessionQueryError(
|
|
202
|
+
`session-search persistence observation failed: ${errorMessage(error)}`,
|
|
203
|
+
'SESSION_QUERY_PERSISTENCE_FAILED',
|
|
204
|
+
{ cause: error },
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const live = new Map<SessionId, ObservedSession>()
|
|
209
|
+
for (const session of engine.ctx.sessions.list()) {
|
|
210
|
+
const observed = observeSession(session.header, session.inheritedEventCount, session.snapshotEvents())
|
|
211
|
+
const durable = persisted.get(session.id)
|
|
212
|
+
if (durable !== undefined && durable.loaded === undefined) {
|
|
213
|
+
// A live owner always wins over a skipped durable copy.
|
|
214
|
+
live.set(session.id, observed)
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
if (durable !== undefined) assertSessionHeadersCompatible(observed.header, durable.header)
|
|
218
|
+
live.set(session.id, observed)
|
|
219
|
+
}
|
|
220
|
+
const sameLive = initiallyLive.size === live.size && [...initiallyLive].every(id => live.has(id))
|
|
221
|
+
if (!sameLive) continue
|
|
222
|
+
return { persistenceBinding, persisted, live }
|
|
223
|
+
}
|
|
224
|
+
throw new SessionQueryError('session-search persistence observation did not stabilize after one retry', 'SESSION_QUERY_PERSISTENCE_FAILED')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// The opaque-base cast keeps the private `_observeStable` override legal in
|
|
228
|
+
// TypeScript while inheriting every runtime static (inject, Config, Service
|
|
229
|
+
// metadata) from the real engine class.
|
|
230
|
+
const EngineBase = SqliteSessionQueryEngine as unknown as abstract new (ctx: never, config: Config) => EngineSurface & object
|
|
231
|
+
|
|
232
|
+
/** The engine this bundle mounts in place of the base `session-query-sqlite` row. */
|
|
233
|
+
export class SkipTolerantSessionQueryEngine extends EngineBase {
|
|
234
|
+
async _observeStable(indexed: ReadonlyMap<SessionId, { revision: SessionPersistenceRevision }>, signal: AbortSignal | undefined): Promise<unknown> {
|
|
235
|
+
return await observeStableWithSkip(this, indexed, signal)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default SkipTolerantSessionQueryEngine as unknown as typeof SqliteSessionQueryEngine
|
package/src/startup.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* whose project directory matches the current working directory.
|
|
15
15
|
* - `--session <id>` — create a new session under an explicit identity (the
|
|
16
16
|
* id must not exist yet).
|
|
17
|
-
* - `--theme <dark|light|auto>` — the color palette; auto follows the
|
|
17
|
+
* - `--theme <dark|light|prismatic|rainbow|auto>` — the color palette; auto follows the
|
|
18
18
|
* terminal (dark fallback until OSC-11 detection lands).
|
|
19
19
|
* - no flags — a fresh session with a minted id.
|
|
20
20
|
*
|
|
@@ -69,7 +69,7 @@ export function resolveTuiStartup(options: TuiStartupOptions): TuiStartup {
|
|
|
69
69
|
throw new Error('--mode applies only to a new session; it cannot be combined with --resume or --continue')
|
|
70
70
|
}
|
|
71
71
|
if (options.theme !== undefined && !THEME_NAMES.includes(options.theme)) {
|
|
72
|
-
throw new Error(
|
|
72
|
+
throw new Error(`--theme must be one of: ${THEME_NAMES.join(', ')}`)
|
|
73
73
|
}
|
|
74
74
|
const input = {
|
|
75
75
|
...(options.theme === undefined ? {} : { theme: options.theme }),
|
|
@@ -99,7 +99,7 @@ function tuiCommand(): Command {
|
|
|
99
99
|
.option('-c, --continue', 'resume the most recent persisted session for this working directory')
|
|
100
100
|
.option('--session <id>', 'create a new session under this explicit id')
|
|
101
101
|
.option('--mode <preset>', 'agent preset for a newly created session')
|
|
102
|
-
.option('--theme <name>', 'color theme: dark (default), light, or auto')
|
|
102
|
+
.option('--theme <name>', 'color theme: dark (default), light, prismatic, rainbow, or auto')
|
|
103
103
|
.option('-i, --image <path>', 'attach an image to the initial prompt (repeatable)', (path, paths: string[]) => [...paths, path], [])
|
|
104
104
|
.argument('[prompt...]', 'initial prompt; sends immediately after startup')
|
|
105
105
|
.addHelpText('after', `
|