@sparkelf/dsh-plus 0.2.1-rc.2.2 → 0.2.2-alpha.2.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.
Files changed (37) hide show
  1. package/README.md +7 -5
  2. package/README.zh.md +7 -5
  3. package/lib/apply-08huHfLG.js +665 -0
  4. package/lib/apply-DmICvIGh.js +653 -0
  5. package/lib/apply.js +1 -1
  6. package/lib/bin.js +133 -37
  7. package/lib/types/apply.d.ts +1 -1
  8. package/lib/types/apply.js +56 -117
  9. package/lib/types/patch-set.d.ts +59 -0
  10. package/lib/types/patch-set.js +132 -0
  11. package/lib/types/standalone-cli.js +56 -2
  12. package/lib/types/standalone-profile.d.ts +8 -1
  13. package/lib/types/standalone-profile.js +84 -38
  14. package/package.json +625 -176
  15. package/patches/approval-allow-policy.patch +108 -0
  16. package/patches/atomic-write-zombie-lock-holder.patch +53 -0
  17. package/patches/bootstrap-exempt-names.patch +32 -0
  18. package/patches/browser-auth-mode.patch +127 -0
  19. package/patches/composer-popover-boundaries.patch +337 -0
  20. package/patches/diagram-preview.patch +562 -0
  21. package/patches/image-offload-hoist-replay.patch +533 -0
  22. package/patches/legacy-code-preset.patch +148 -0
  23. package/patches/llm-pi-ai-transport-truncation.patch +33 -0
  24. package/patches/mobile-journal-generation.patch +76 -0
  25. package/patches/office-preview-stable-load.patch +34 -0
  26. package/patches/officecli-deliverables.patch +42 -0
  27. package/patches/ptc-mcp-schema-types.patch +156 -0
  28. package/patches/responses-reasoning-status.patch +289 -0
  29. package/patches/session-envelope-ignorable.patch +63 -0
  30. package/patches/session-export-chinese.patch +85 -0
  31. package/patches/session-format-legacy-restart.patch +35 -0
  32. package/patches/session-log-trajectory-toolbar.patch +352 -0
  33. package/patches/state-dot-brand-blue.patch +185 -0
  34. package/patches/ui-settings-compact-phone.patch +76 -0
  35. package/patches/web-runtime-compat.patch +83 -0
  36. package/patches/workspace-storage-restore.patch +186 -0
  37. package/patches/wsl-native-open.patch +265 -0
@@ -0,0 +1,63 @@
1
+ diff --git a/packages/core/session/src/index.ts b/packages/core/session/src/index.ts
2
+ index e9baee1b82..fcf26d713a 100644
3
+ --- a/packages/core/session/src/index.ts
4
+ +++ b/packages/core/session/src/index.ts
5
+ @@ -15,7 +15,7 @@ import type { Scoped } from '@deepseek-ai/dsh-scope'
6
+ import type { Message } from '@deepseek-ai/dsh-llm'
7
+ import { SESSION_FORMAT_VERSION, SessionLogOffset, SessionSeq } from './types.ts'
8
+ import type { TypertLookup } from '@deepseek-ai/dsh-typert-protocol'
9
+ -import type { CreateSessionOptions, EpochHeader, PluginRecord, PluginRecordMap, PluginRecordType, PrepareSessionOptions, RequestContext, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SessionId, SessionSeedEventState, SurfaceIntent, SurfaceEventType } from './types.ts'
10
+ +import type { CreateSessionOptions, EpochHeader, PluginRecord, PluginRecordMap, PluginRecordType, PrepareSessionOptions, RequestContext, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SessionId, SessionSeedEventState, SurfaceIntent, EnvelopeIntent, SurfaceEventType } from './types.ts'
11
+ import { SurfaceManager, validateSessionEventData, validateSurfaceMetadata } from './surface.ts'
12
+ import type { SessionSurface, SessionMessageProjection } from './surface.ts'
13
+ import { foldRequestHeader } from './request-header.ts'
14
+ @@ -806,10 +806,13 @@ export class Session {
15
+ append<T extends SessionEventType>(
16
+ type: T,
17
+ data: SessionEventMap[T],
18
+ - ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
19
+ + ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : [opts?: EnvelopeIntent]
20
+ ): SessionEvent<T> {
21
+ this.publicationEntry()
22
+ - const surfaceOpts: SurfaceIntent | undefined = opts[0]
23
+ + // A surface event's single option object is a `SurfaceIntent`; a log-only event's is an
24
+ + // `EnvelopeIntent`. The two never share a field, so one object serves both.
25
+ + const intent: SurfaceIntent & EnvelopeIntent = (opts[0] ?? {}) as SurfaceIntent & EnvelopeIntent
26
+ + const surfaceOpts: SurfaceIntent | undefined = opts[0] as SurfaceIntent | undefined
27
+ const surfaceMetadata = {
28
+ ...surfaceOpts?.sourceEventSeqs === undefined ? {} : { sourceEventSeqs: surfaceOpts.sourceEventSeqs },
29
+ ...surfaceOpts?.surfaceOp === undefined ? {} : { surfaceOp: surfaceOpts.surfaceOp },
30
+ @@ -830,6 +833,10 @@ export class Session {
31
+ time: Date.now(),
32
+ data: dataSnapshot,
33
+ ...(surfaceMetadataSnapshot as { surfaceOp?: unknown; sourceEventSeqs?: unknown }),
34
+ + // A log-only producer declares that a reader without its event type may skip the record.
35
+ + // Only `true` is admitted: the field's absence means required, and an explicit falsy value
36
+ + // would be indistinguishable from a producer that meant the opposite.
37
+ + ...(intent.ignorable === true ? { ignorable: true } : {}),
38
+ } as unknown as SessionEvent<T>)
39
+ validateSessionEventData(event, `session event "${type}" at seq ${event.seq}`)
40
+ this.commit(event as SessionEvent, entry)
41
+ diff --git a/packages/core/session/src/types.ts b/packages/core/session/src/types.ts
42
+ index 7787dd9bd0..af11a41635 100644
43
+ --- a/packages/core/session/src/types.ts
44
+ +++ b/packages/core/session/src/types.ts
45
+ @@ -485,6 +485,18 @@ export type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
46
+ sourceEventSeqs?: SessionSeq[]
47
+ })
48
+
49
+ +/**
50
+ + * Envelope markers a log-only event may declare at {@link Session.append}.
51
+ + *
52
+ + * `ignorable` lets a reader that does not know the event type skip it instead of refusing the
53
+ + * whole log. Only a producer whose record cannot affect reconstruction may set it, so it is
54
+ + * opt-in per append rather than a property of the event type.
55
+ + */
56
+ +export type EnvelopeIntent = {
57
+ + /** Marks an event a reader may skip when it does not recognize the type. */
58
+ + ignorable?: true
59
+ +}
60
+ +
61
+ /**
62
+ * One immutable entry in the session log.
63
+ *
@@ -0,0 +1,85 @@
1
+ diff --git a/packages/session-query/session-log-export/src/client/locales.ts b/packages/session-query/session-log-export/src/client/locales.ts
2
+ index 516950f70c..def3d97fb4 100644
3
+ --- a/packages/session-query/session-log-export/src/client/locales.ts
4
+ +++ b/packages/session-query/session-log-export/src/client/locales.ts
5
+ @@ -4,24 +4,26 @@ export const NS = 'session-log-download'
6
+ /** Simplified-Chinese Session export strings. */
7
+ export const zh = {
8
+ 'header.more': '更多操作',
9
+ + 'header.action': '会话日志',
10
+ 'menu.copyMarkdown': '复制会话为 Markdown',
11
+ - 'menu.download': '下载 Session 日志',
12
+ + 'menu.download': '下载会话日志',
13
+ 'menu.feedback': '反馈',
14
+ 'toast.copied': 'Session 已复制为 Markdown',
15
+ 'toast.preparationFailed': '无法读取 Session 进行复制,请重试',
16
+ 'toast.clipboardFailed': '无法写入剪贴板,请检查剪贴板权限后重试',
17
+ - 'dialog.preparingTitle': '正在导出 Session',
18
+ - 'dialog.preparingDescription': '正在准备包含当前 Session、子 Session 和附件的 ZIP 文件。',
19
+ - 'dialog.successTitle': 'Session 导出已开始下载',
20
+ - 'dialog.successDescription': '浏览器正在下载 Session ZIP 文件。',
21
+ + 'dialog.preparingTitle': '正在导出会话',
22
+ + 'dialog.preparingDescription': '正在准备包含当前会话、子会话和附件的 ZIP 文件。',
23
+ + 'dialog.successTitle': '会话导出已开始下载',
24
+ + 'dialog.successDescription': '浏览器正在下载会话 ZIP 文件。',
25
+ 'dialog.errorTitle': 'Session 导出失败',
26
+ 'dialog.close': '关闭',
27
+ - 'dialog.commandFailed': '无法启动 Session 导出。',
28
+ + 'dialog.commandFailed': '无法启动会话导出。',
29
+ } as const
30
+
31
+ /** English Session export strings. */
32
+ export const en: Record<keyof typeof zh, string> = {
33
+ 'header.more': 'More actions',
34
+ + 'header.action': 'Session log',
35
+ 'menu.copyMarkdown': 'Copy Session as Markdown',
36
+ 'menu.download': 'Download session log',
37
+ 'menu.feedback': 'Feedback',
38
+ @@ -42,6 +44,7 @@ export type SessionLogDownloadKey = keyof typeof zh
39
+
40
+ /** Japanese copy, checked against the English keys. */
41
+ export const ja = {
42
+ + 'header.action': 'セッションログ',
43
+ 'header.more': 'その他の操作',
44
+ 'menu.copyMarkdown': 'セッションを Markdown としてコピー',
45
+ 'menu.download': 'セッションログをダウンロード',
46
+ @@ -60,6 +63,7 @@ export const ja = {
47
+
48
+ /** Spanish dictionary, checked against the English keys. */
49
+ export const es = {
50
+ + 'header.action': 'Registro de sesión',
51
+ 'header.more': 'Más acciones',
52
+ 'menu.copyMarkdown': 'Copiar sesión como Markdown',
53
+ 'menu.download': 'Descargar registro de sesión',
54
+ @@ -78,6 +82,7 @@ export const es = {
55
+
56
+ /** French dictionary, checked against the English keys. */
57
+ export const fr = {
58
+ + 'header.action': 'Journal de session',
59
+ 'header.more': 'Autres actions',
60
+ 'menu.copyMarkdown': 'Copier la session sous Markdown',
61
+ 'menu.download': 'Télécharger le journal de session',
62
+ @@ -96,6 +101,7 @@ export const fr = {
63
+
64
+ /** German dictionary, checked against the English keys. */
65
+ export const de = {
66
+ + 'header.action': 'Sitzungsprotokoll',
67
+ 'header.more': 'Weitere Aktionen',
68
+ 'menu.copyMarkdown': 'Kopieren Sie die Sitzung als Markdown',
69
+ 'menu.download': 'Sitzungsprotokoll herunterladen',
70
+ @@ -114,6 +120,7 @@ export const de = {
71
+
72
+ /** Russian dictionary, checked against the English keys. */
73
+ export const ru = {
74
+ + 'header.action': 'Журнал сеанса',
75
+ 'header.more': 'Другие действия',
76
+ 'menu.copyMarkdown': 'Скопировать сеанс как Markdown',
77
+ 'menu.download': 'Скачать журнал сеанса',
78
+ @@ -132,6 +139,7 @@ export const ru = {
79
+
80
+ /** Portuguese dictionary, checked against the English keys. */
81
+ export const pt = {
82
+ + 'header.action': 'Registro da sessão',
83
+ 'header.more': 'Mais ações',
84
+ 'menu.copyMarkdown': 'Copiar sessão como Markdown',
85
+ 'menu.download': 'Baixar registro da sessão',
@@ -0,0 +1,35 @@
1
+ diff --git a/packages/session/session-format-v0-to-v1/src/relationships.ts b/packages/session/session-format-v0-to-v1/src/relationships.ts
2
+ index ddd1a4b71d..5cf0c7c0c7 100644
3
+ --- a/packages/session/session-format-v0-to-v1/src/relationships.ts
4
+ +++ b/packages/session/session-format-v0-to-v1/src/relationships.ts
5
+ @@ -35,7 +35,7 @@ export interface ReleasedRelationshipExtensions {
6
+ /** Title-request model input was source-validated and preserved across sequence remapping. */
7
+ readonly preservedSourceTitleRequestText?: true
8
+ /** Admit the released resume pattern whose next-turn inbox insert omitted the prior turn/end. */
9
+ - readonly legacyInterruptedTurnRestart?: true
10
+ + readonly legacyInterruptedTurnRestart?: boolean
11
+ }
12
+
13
+ /**
14
+ @@ -78,8 +78,8 @@ export function assertReleasedArtifactRelationships(
15
+
16
+ switch (event.type) {
17
+ case 'turn/start': {
18
+ - const previous = artifact.events[event.seq - 1]
19
+ - if (extensions.legacyInterruptedTurnRestart === true
20
+ + const previous = artifact.events.find(candidate => candidate.seq === event.seq - 1)
21
+ + if (extensions.legacyInterruptedTurnRestart !== false
22
+ && openTurn !== null
23
+ && openStep === null
24
+ && data['turn'] === openTurn + 1
25
+ @@ -437,8 +437,8 @@ function assertTitleSources(
26
+ throw new SessionFormatError(`${event.type} ${event.seq} messageSeqs must cite earlier human user/message events`)
27
+ }
28
+ const sourceData = releasedV0Record(source.data, `${source.type} ${seq} data`)
29
+ - const messageSource = releasedV0Record(sourceData['source'], `${source.type} ${seq} source`)
30
+ - if (messageSource['kind'] !== 'user') {
31
+ + const provenance = releasedV0Record(sourceData['source'], `${source.type} ${seq} source`)
32
+ + if (provenance['kind'] !== 'user') {
33
+ throw new SessionFormatError(`${event.type} ${event.seq} messageSeqs must cite earlier human user/message events`)
34
+ }
35
+ const content = sourceData['content'] as readonly Record<string, SessionFormatJsonValue>[]
@@ -0,0 +1,352 @@
1
+ diff --git a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css
2
+ index 2bbc35663a..b2dde8dd3b 100644
3
+ --- a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css
4
+ +++ b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css
5
+ @@ -174,13 +174,19 @@
6
+ font: 14px/14px var(--ds-font-family-code);
7
+ }
8
+
9
+ +.utilities {
10
+ + display: flex;
11
+ + flex: none;
12
+ + align-items: center;
13
+ + margin-left: auto;
14
+ +}
15
+ +
16
+ .search {
17
+ display: flex;
18
+ flex: 0 1 164px;
19
+ align-items: center;
20
+ min-width: 84px;
21
+ height: 22px;
22
+ - margin-left: auto;
23
+ padding: 0 6px;
24
+ gap: 4px;
25
+ border: 0.5px solid var(--dsw-alias-border-l4);
26
+ diff --git a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx
27
+ index c3861f03a9..a63ac8823b 100644
28
+ --- a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx
29
+ +++ b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx
30
+ @@ -1,5 +1,6 @@
31
+ /** Trajectory toolbar: timeline and ledger fold controls. */
32
+
33
+ +import type { ReactNode } from 'react'
34
+ import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
35
+ import { IconSearchOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'
36
+ import type { NS } from './locales.ts'
37
+ @@ -26,6 +27,8 @@ export interface TrajectoryToolbarProps {
38
+ searchQuery: string
39
+ /** Update the live ledger search query. */
40
+ onSearchQueryChange: (query: string) => void
41
+ + /** Session utilities contributed immediately before search. */
42
+ + utilities: ReactNode
43
+ /** Translate a toolbar dictionary key. */
44
+ t: TranslateNS<typeof NS>
45
+ }
46
+ @@ -46,6 +49,7 @@ export function TrajectoryToolbar({
47
+ onToggleAllAssistants,
48
+ searchQuery,
49
+ onSearchQueryChange,
50
+ + utilities,
51
+ t,
52
+ }: TrajectoryToolbarProps) {
53
+ return (
54
+ @@ -111,7 +115,8 @@ export function TrajectoryToolbar({
55
+ {t('toolbar.calls')}
56
+ </button>
57
+ </div>
58
+ - <div className={css.search}>
59
+ + <div className={css.utilities} data-trajectory-toolbar-utilities="">{utilities}</div>
60
+ + <div className={css.search} data-trajectory-search="">
61
+ <IconSearchOutlineRegular size={11} className={css.searchIcon} />
62
+ <input
63
+ type="search"
64
+ diff --git a/packages/client/ui-trajectory/src/client/TrajectoryView.tsx b/packages/client/ui-trajectory/src/client/TrajectoryView.tsx
65
+ index 6835939198..47db0ef561 100644
66
+ --- a/packages/client/ui-trajectory/src/client/TrajectoryView.tsx
67
+ +++ b/packages/client/ui-trajectory/src/client/TrajectoryView.tsx
68
+ @@ -135,7 +135,9 @@ export function TrajectoryView({
69
+ useSession, useTrajectory, useDuration, watchToolResult, loadOlder, loadImage, setActualDuration,
70
+ viewRequest, completeViewRequest, renderSlot, t, jsonStringWrapping,
71
+ }: ConvViewProps
72
+ - & Pick<PropsRenderSlots<'conversation.trajectory.images'>, 'renderSlot'>
73
+ + & PropsRenderSlots<
74
+ + 'conversation.trajectory.images' | 'conversation.trajectory.toolbar.utilities'
75
+ + >
76
+ & InjectFace<TrajectoryViewInjected>
77
+ & PropsLocale<'trajectory'>) {
78
+ const [collapsedTurns, setCollapsedTurns] = useState<ReadonlySet<number>>(EMPTY_TURN_IDS)
79
+ @@ -530,6 +532,7 @@ export function TrajectoryView({
80
+ onToggleAllAssistants={toggleAllAssistants}
81
+ searchQuery={searchQuery}
82
+ onSearchQueryChange={setSearchQuery}
83
+ + utilities={renderSlot('conversation.trajectory.toolbar.utilities', {})}
84
+ t={t}
85
+ />
86
+ <TrajectoryTimeline
87
+ diff --git a/packages/client/ui-trajectory/src/client/index.ts b/packages/client/ui-trajectory/src/client/index.ts
88
+ index 28bf045ab4..6e44cb96f7 100644
89
+ --- a/packages/client/ui-trajectory/src/client/index.ts
90
+ +++ b/packages/client/ui-trajectory/src/client/index.ts
91
+ @@ -38,6 +38,13 @@ export type {
92
+ UseTrajectory,
93
+ } from './trajectory-contract.ts'
94
+
95
+ +declare module '@deepseek-ai/dsh-client-ui-slots' {
96
+ + interface SlotMap {
97
+ + /** Compact Session utilities immediately before the Trajectory search field. */
98
+ + 'conversation.trajectory.toolbar.utilities': { kind: 'list'; scope: 'session' }
99
+ + }
100
+ +}
101
+ +
102
+ /** Required services: the conversation slot, registries, ordinary Session paging, and the locale service. */
103
+ export const inject = ['slots', 'sessions', 'uiSession', 'uiConversation', 'locale', 'remote', 'remote.toolResults']
104
+
105
+ @@ -83,7 +90,10 @@ export function apply(ctx: Context): void {
106
+ order: 10,
107
+ locale: NS,
108
+ label: () => t('view.trajectory'),
109
+ - children: { 'conversation.trajectory.images': { kind: 'single', scope: 'session' } },
110
+ + children: {
111
+ + 'conversation.trajectory.images': { kind: 'single', scope: 'session' },
112
+ + 'conversation.trajectory.toolbar.utilities': { kind: 'list', scope: 'session' },
113
+ + },
114
+ inject: (sessionId: SessionId): TrajectoryViewInjected => {
115
+ const session = ctx.sessions.binding(sessionId)?.session
116
+ if (session === undefined) {
117
+ diff --git a/packages/client/ui-trajectory/tests/views.client.spec.tsx b/packages/client/ui-trajectory/tests/views.client.spec.tsx
118
+ index c233d3315a..93a0fb5f3f 100644
119
+ --- a/packages/client/ui-trajectory/tests/views.client.spec.tsx
120
+ +++ b/packages/client/ui-trajectory/tests/views.client.spec.tsx
121
+ @@ -249,6 +249,9 @@ function standaloneProps(
122
+ loadImage: () => Promise.reject(new Error('standalone views load no images')),
123
+ // The locale seat the outlet would inject for the declared namespace.
124
+ t: tZh,
125
+ + // The rc.2 slot map widened this view to a session scope, so the
126
+ + // standard kit the outlet would bake now includes the SessionProvider seat.
127
+ + SessionProvider: ({ children }: { children: ReactNode }) => children,
128
+ }
129
+ }
130
+
131
+ diff --git a/packages/session-query/session-log-export/package.json b/packages/session-query/session-log-export/package.json
132
+ index 083b0e2ba7..a63d999978 100644
133
+ --- a/packages/session-query/session-log-export/package.json
134
+ +++ b/packages/session-query/session-log-export/package.json
135
+ @@ -49,8 +49,8 @@
136
+ "@deepseek-ai/dsh-session-persistence": "workspace:*"
137
+ },
138
+ "devDependencies": {
139
+ - "@deepseek-ai/cordis": "workspace:~",
140
+ "@deepseek-ai/cordis-plugin-loader": "workspace:~",
141
+ + "@deepseek-ai/cordis": "workspace:~",
142
+ "@deepseek-ai/dsh-agent": "workspace:*",
143
+ "@deepseek-ai/dsh-attachment": "workspace:*",
144
+ "@deepseek-ai/dsh-client-connection": "workspace:*",
145
+ @@ -64,11 +64,13 @@
146
+ "@deepseek-ai/dsh-client-ui-renderer": "workspace:*",
147
+ "@deepseek-ai/dsh-client-ui-session": "workspace:*",
148
+ "@deepseek-ai/dsh-client-ui-slots": "workspace:*",
149
+ + "@deepseek-ai/dsh-client-ui-trajectory": "workspace:*",
150
+ "@deepseek-ai/dsh-commands": "workspace:*",
151
+ "@deepseek-ai/dsh-session": "workspace:*",
152
+ "@deepseek-ai/dsh-session-persistence": "workspace:*",
153
+ "@deepseek-ai/dsh-session-query": "workspace:*",
154
+ "@deepseek-ai/dsh-session-title": "workspace:*",
155
+ + "@types/react": "~18.3.1",
156
+ "react": "^18.2.0"
157
+ },
158
+ "dsh": {
159
+ @@ -79,7 +81,8 @@
160
+ "@deepseek-ai/dsh-client-ui-conversation",
161
+ "@deepseek-ai/dsh-client-ui-layout",
162
+ "@deepseek-ai/dsh-client-ui-renderer",
163
+ - "@deepseek-ai/dsh-client-ui-session"
164
+ + "@deepseek-ai/dsh-client-ui-session",
165
+ + "@deepseek-ai/dsh-client-ui-trajectory"
166
+ ],
167
+ "platform": "web"
168
+ }
169
+ diff --git a/packages/session-query/session-log-export/src/client/HeaderAction.module.css b/packages/session-query/session-log-export/src/client/HeaderAction.module.css
170
+ index 90ca1eedbc..aa22e45ead 100644
171
+ --- a/packages/session-query/session-log-export/src/client/HeaderAction.module.css
172
+ +++ b/packages/session-query/session-log-export/src/client/HeaderAction.module.css
173
+ @@ -10,3 +10,46 @@
174
+ width: 15px;
175
+ height: 15px;
176
+ }
177
+ +/* The Header action is the shared compact Button; the Trajectory action below
178
+ + replaces it from 768px, where the header has room for a labelled control. */
179
+ +
180
+ +.trajectoryButton {
181
+ + display: none;
182
+ + flex: none;
183
+ + align-items: center;
184
+ + height: 22px;
185
+ + padding: 0 6px;
186
+ + gap: 4px;
187
+ + border: 0;
188
+ + border-radius: 3px;
189
+ + color: var(--dsw-alias-label-tertiary);
190
+ + background: transparent;
191
+ + cursor: pointer;
192
+ + font: var(--dsw-font-xxs-12);
193
+ +}
194
+ +
195
+ +.trajectoryButton:hover:not(:disabled) {
196
+ + color: var(--dsw-alias-label-primary);
197
+ + background: var(--dsw-alias-interactive-bg-hover);
198
+ +}
199
+ +
200
+ +.trajectoryButton:focus-visible {
201
+ + outline: 1px solid var(--dsw-alias-state-business-primary);
202
+ + outline-offset: 1px;
203
+ +}
204
+ +
205
+ +.trajectoryButton:disabled {
206
+ + color: var(--dsw-alias-label-dimmed);
207
+ + cursor: wait;
208
+ +}
209
+ +
210
+ +.trajectoryButton span,
211
+ +.trajectoryButton svg { flex: none; }
212
+ +.trajectoryButton span { white-space: nowrap; }
213
+ +
214
+ +/* Wider layouts replace the Header action with the compact Trajectory one. */
215
+ +@media (min-width: 768px) {
216
+ + .moreButton { display: none; }
217
+ +
218
+ + .trajectoryButton { display: inline-flex; }
219
+ +}
220
+ diff --git a/packages/session-query/session-log-export/src/client/HeaderAction.tsx b/packages/session-query/session-log-export/src/client/HeaderAction.tsx
221
+ index fcf0cfd862..74a8209ffa 100644
222
+ --- a/packages/session-query/session-log-export/src/client/HeaderAction.tsx
223
+ +++ b/packages/session-query/session-log-export/src/client/HeaderAction.tsx
224
+ @@ -1,12 +1,13 @@
225
+ import { useState } from 'react'
226
+ import type { ReactNode } from 'react'
227
+ import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store'
228
+ -import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots'
229
+ +import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
230
+ import type { SessionId } from '@deepseek-ai/dsh-session/types'
231
+ import {
232
+ Button, IconCopyOutlineRegular, IconDownloadOutlineRegular, IconEllipsisOutlineRegular,
233
+ IconPaperPlaneOutlineRegular, Menu,
234
+ } from '@deepseek-ai/dsh-client-ui-primitives'
235
+ +import { NS } from './locales.ts'
236
+ import { SessionLogDownloadDialog, type SessionLogDownloadDialogProps } from './Dialog.tsx'
237
+ import type { SessionLogDownloadDialogInjected } from './Dialog.tsx'
238
+ import type { SessionMarkdownCopyState } from './copy-controller.ts'
239
+ @@ -38,6 +39,12 @@ export interface SessionLogDownloadHeaderInjected
240
+ /** Session download props plus the optional feedback action. */
241
+ export type SessionLogDownloadHeaderProps = SessionLogDownloadDialogProps & InjectFace<SessionLogDownloadHeaderInjected>
242
+
243
+ +/** Trajectory toolbar presentation of the same download controller. */
244
+ +type SessionLogDownloadToolbarActionProps =
245
+ + PropsRuntime<'conversation.trajectory.toolbar.utilities'>
246
+ + & PropsLocale<typeof NS>
247
+ + & InjectFace<SessionLogDownloadDialogInjected>
248
+ +
249
+ /**
250
+ * Render the Session Header menu with Markdown copy, download, and optional
251
+ * feedback actions.
252
+ @@ -77,6 +84,7 @@ export function SessionLogDownloadHeaderAction(props: SessionLogDownloadHeaderPr
253
+ <Button
254
+ size="sm"
255
+ className={css.moreButton}
256
+ + data-session-log-header
257
+ aria-label={t('header.more')}
258
+ aria-haspopup="menu"
259
+ aria-expanded={open}
260
+ @@ -91,3 +99,31 @@ export function SessionLogDownloadHeaderAction(props: SessionLogDownloadHeaderPr
261
+ </>
262
+ )
263
+ }
264
+ +
265
+ +/**
266
+ + * Render the desktop Session export action in the Trajectory toolbar.
267
+ + * @param props - Session runtime, shared download controller, and localized copy.
268
+ + * @returns the compact toolbar action.
269
+ + */
270
+ +export function SessionLogDownloadToolbarAction(
271
+ + props: SessionLogDownloadToolbarActionProps,
272
+ +): ReactNode {
273
+ + const { sessionId, useSessionLogDownload, request, t } = props
274
+ + const entry = useSessionLogDownload(state => state.bySession[String(sessionId)])
275
+ + const busy = entry?.status === 'downloading'
276
+ +
277
+ + return (
278
+ + <button
279
+ + type="button"
280
+ + className={css.trajectoryButton}
281
+ + data-session-log-toolbar
282
+ + disabled={busy}
283
+ + aria-busy={busy}
284
+ + title={t('header.action')}
285
+ + onClick={() => { void request(sessionId) }}
286
+ + >
287
+ + <IconDownloadOutlineRegular size={11} />
288
+ + <span>{t('header.action')}</span>
289
+ + </button>
290
+ + )
291
+ +}
292
+ diff --git a/packages/session-query/session-log-export/src/client/index.ts b/packages/session-query/session-log-export/src/client/index.ts
293
+ index 74808d1751..f3acaa0746 100644
294
+ --- a/packages/session-query/session-log-export/src/client/index.ts
295
+ +++ b/packages/session-query/session-log-export/src/client/index.ts
296
+ @@ -8,11 +8,16 @@ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
297
+ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
298
+ import type {} from '@deepseek-ai/dsh-client-ui-session/client'
299
+ import type {} from '@deepseek-ai/dsh-client-ui-message-feedback/client'
300
+ +import type {} from '@deepseek-ai/dsh-client-ui-trajectory/client'
301
+ import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
302
+ import { SessionLogDownloadController } from './controller.ts'
303
+ import { SessionMarkdownCopyController } from './copy-controller.ts'
304
+ import { SessionMarkdownCopyToast, type SessionMarkdownCopyToastInjected } from './CopyToast.tsx'
305
+ -import { SessionLogDownloadHeaderAction, type SessionLogDownloadHeaderInjected } from './HeaderAction.tsx'
306
+ +import type { SessionLogDownloadDialogInjected } from './Dialog.tsx'
307
+ +import {
308
+ + SessionLogDownloadHeaderAction, SessionLogDownloadToolbarAction,
309
+ + type SessionLogDownloadHeaderInjected,
310
+ +} from './HeaderAction.tsx'
311
+ import { en, ja, es, fr, de, ru, pt, NS, zh, type SessionLogDownloadKey } from './locales.ts'
312
+
313
+ declare module '@deepseek-ai/cordis' {
314
+ @@ -39,6 +44,13 @@ export const inject = ['slots', 'locale']
315
+ export function apply(ctx: ClientContext): void {
316
+ const controller = new SessionLogDownloadController()
317
+ const copyController = new SessionMarkdownCopyController()
318
+ + // The Trajectory presentation is download-only and shares the same controller,
319
+ + // request, and dismiss as the Header one.
320
+ + const injectAction = (): SessionLogDownloadDialogInjected => ({
321
+ + hooks: { sessionLogDownload: controller.store },
322
+ + request: (sessionId: SessionId) => controller.download(sessionId),
323
+ + dismiss: (sessionId: SessionId) => { controller.dismiss(sessionId) },
324
+ + })
325
+ ctx.provide('sessionLogDownload', controller)
326
+ ctx.effect(() => async () => { await controller.dispose() }, 'session-log-download: browser download lifecycle')
327
+ ctx.effect(() => async () => { await copyController.dispose() }, 'session-log-download: browser Markdown copy lifecycle')
328
+ @@ -79,6 +91,12 @@ export function apply(ctx: ClientContext): void {
329
+ openFeedback: (sessionId: SessionId) => { ctx.get('feedbackUi')?.openSession(sessionId) },
330
+ }),
331
+ }, SessionLogDownloadHeaderAction))
332
+ + ctx.slots.inject('conversation.trajectory.toolbar.utilities', () => ctx.slots.register({
333
+ + name: 'conversation.trajectory.toolbar.utilities',
334
+ + id: 'session-log-download',
335
+ + locale: NS,
336
+ + inject: injectAction,
337
+ + }, SessionLogDownloadToolbarAction))
338
+ }
339
+
340
+ export type { SessionLogDownloadDialogInjected, SessionLogDownloadDialogProps } from './Dialog.tsx'
341
+ diff --git a/packages/session-query/session-log-export/tsconfig.client.json b/packages/session-query/session-log-export/tsconfig.client.json
342
+ index c78dbf6893..43a2f3e861 100644
343
+ --- a/packages/session-query/session-log-export/tsconfig.client.json
344
+ +++ b/packages/session-query/session-log-export/tsconfig.client.json
345
+ @@ -28,6 +28,7 @@
346
+ { "path": "../../client/ui-renderer" },
347
+ { "path": "../../client/ui-session" },
348
+ { "path": "../../client/ui-slots" },
349
+ + { "path": "../../client/ui-trajectory/tsconfig.client.json" },
350
+ { "path": "../../core/session" }
351
+ ]
352
+ }