@klarkxy/dsh-current-title 0.1.4 → 0.1.5
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.md +14 -2
- package/docs/README.zh-CN.md +30 -4
- package/icon.svg +15 -0
- package/lib/client.inner.cjs +2 -2
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +2 -2
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +26 -19
package/README.md
CHANGED
|
@@ -10,7 +10,19 @@ The bundle insert is enabled after installation. It does not permanently turn of
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
-
Requires Node.js ≥22 and DSH `0.1.7-rc.2`. The
|
|
13
|
+
Requires Node.js ≥22 and DSH `0.1.7-rc.2`. The **自动标题** entry starts enabled and can be disabled in plugin settings. Native title storage and scheduling come from host `sessionTitle`; generation needs `@klarkxy/dsh-ai-services`.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @klarkxy/dsh-current-title
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Host RPC
|
|
20
|
+
|
|
21
|
+
Channel `/dsh-current-title` requires the host authorization policy.
|
|
22
|
+
|
|
23
|
+
- `status` — current settings, slot support and ownership, and per-session title state (title, source kind, generating, pinned) when `sessionId` is given.
|
|
24
|
+
- `settings` — update the stored locale mode with compare-and-swap (`{ locale, expectedRevision }`). The stored mode is `auto`, so type labels follow the host locale preference.
|
|
25
|
+
- `regenerate` — regenerate one session's title (`{ sessionId }`); fails with `not-found` for an unknown session and `unavailable` while the title provider is inactive.
|
|
14
26
|
|
|
15
27
|
## Title shape
|
|
16
28
|
|
|
@@ -24,4 +36,4 @@ Only recent real human `user/message` events are used. Plugin auxiliary messages
|
|
|
24
36
|
|
|
25
37
|
[SATA License 2.1](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-current-title/LICENSE), including attribution for the original `dsh-plugins` sources.
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
Hosts that bundle this feature usually enable it by default; where the host supports live plugin switching, toggling needs no restart. Standalone DSH must load `@klarkxy/dsh-ai-services` before this package; installation or removal may require a restart when the host asks for one.
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -4,10 +4,36 @@
|
|
|
4
4
|
|
|
5
5
|
[English](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-current-title/README.md)
|
|
6
6
|
|
|
7
|
-
从 [`dsh-plugins/dsh-current-title`](https://github.com/klarkxy/dsh-plugins/tree/main/plugins/dsh-current-title)
|
|
7
|
+
从 [`dsh-plugins/dsh-current-title`](https://github.com/klarkxy/dsh-plugins/tree/main/plugins/dsh-current-title) 迁入。类型标签保持中英双语(`功能` / `Feature` 及其余固定词表)。生成走 `@klarkxy/dsh-ai-services` 的快速档用途 `current-title.generate`。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
安装后默认启用,不会永久停用宿主自带的标题插件。启用期间占用原生 `sessionTitle` 槽位;关闭时若槽位仍由本包持有,且被让出的加载项身份未变,则交还原所有者。类型标签语言跟随宿主偏好,没有单独设置页。
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## 安装
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
需要 Node.js ≥22 与 DSH `0.1.7-rc.2`。「当前标题」默认启用,可在插件设置中关闭。标题写入宿主 `sessionTitle`,由宿主调度;生成依赖 `@klarkxy/dsh-ai-services`。
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @klarkxy/dsh-current-title
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 宿主 RPC
|
|
20
|
+
|
|
21
|
+
频道 `/dsh-current-title`,走宿主授权策略。
|
|
22
|
+
|
|
23
|
+
- `status` — 当前设置、槽位支持与归属;传入 `sessionId` 时附带该会话的标题状态(标题、来源类型、是否生成中、是否被手动固定)。
|
|
24
|
+
- `settings` — 按比较并交换修订号更新存储的语言模式(`{ locale, expectedRevision }`)。存储的模式为 `auto`,类型标签跟随宿主语言偏好。
|
|
25
|
+
- `regenerate` — 重新生成单个会话的标题(`{ sessionId }`);会话不存在报 `not-found`,标题服务未就绪报 `unavailable`。
|
|
26
|
+
|
|
27
|
+
## 标题形状
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
0903 | 修复 | 登录回调失败
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
只使用最近真实的人工 `user/message` 事件,插件辅助消息不参与。手动改过的标题由原生标题存储保留。关闭插件会取消在途生成并丢弃迟到结果。
|
|
34
|
+
|
|
35
|
+
## 许可证
|
|
36
|
+
|
|
37
|
+
[SATA License 2.1](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-current-title/LICENSE),保留原 `dsh-plugins` 出处。
|
|
38
|
+
|
|
39
|
+
部分宿主会预装并默认启用本功能;宿主支持热切换时,通过「设置 → 插件」开关即时生效。独立 DSH 需先加载 `@klarkxy/dsh-ai-services` 再加载本包;宿主提示需要重启时,安装或移除后重启。
|
package/icon.svg
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="tag" x1="6" y1="9" x2="26" y2="24" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop stop-color="#4D75F5"/>
|
|
5
|
+
<stop offset="1" stop-color="#8EB6FF"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<g transform="translate(3.6 3.6) scale(.8)">
|
|
9
|
+
<path d="M6 10C6 8.9 6.9 8 8 8H18L26 16L19 23L6 12V10Z" fill="url(#tag)"/>
|
|
10
|
+
<circle cx="11" cy="12.5" r="2" fill="#E0EBFF"/>
|
|
11
|
+
<path d="M13 16.5H20M15 19.8H18.5" stroke="#B8D7FF" stroke-width="2" stroke-linecap="round"/>
|
|
12
|
+
<path d="M21.5 29L22.7 25.7L29.3 19.1C30.1 18.3 30.1 17 29.3 16.2C28.5 15.4 27.2 15.4 26.4 16.2L19.8 22.8L18.5 29H21.5Z" fill="#39ABA9"/>
|
|
13
|
+
<path d="M25.5 17.5L28 20" stroke="#65DED0" stroke-width="1.8" stroke-linecap="round"/>
|
|
14
|
+
</g>
|
|
15
|
+
</svg>
|
package/lib/client.inner.cjs
CHANGED
|
@@ -81,7 +81,7 @@ const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
|
81
81
|
//#region src/client-work.ts
|
|
82
82
|
const titleCopy = {
|
|
83
83
|
zh: {
|
|
84
|
-
label: "
|
|
84
|
+
label: "自动标题",
|
|
85
85
|
pinned: "已手动命名",
|
|
86
86
|
generating: "正在生成标题…",
|
|
87
87
|
regenerate: "重新生成",
|
|
@@ -91,7 +91,7 @@ const titleCopy = {
|
|
|
91
91
|
failed: "无法读取标题设置。"
|
|
92
92
|
},
|
|
93
93
|
en: {
|
|
94
|
-
label: "
|
|
94
|
+
label: "Auto Title",
|
|
95
95
|
pinned: "Renamed manually",
|
|
96
96
|
generating: "Generating title…",
|
|
97
97
|
regenerate: "Regenerate",
|
package/lib/client.inner.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.inner.cjs","names":["useSyncExternalStore","useCallback","useRef","useState","useRef","useCallback"],"sources":["../../dsh-ai-services/src/client-utils.ts","../src/contracts.ts","../src/client-work.ts","../src/marker.ts","../src/client.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\n\nexport interface NativeSurfaceClient {\n sessions: {\n binding?(sessionId: string): { eventSource: { subscribe(listener: () => void): () => void } } | undefined\n }\n uiWorkspace: { current?: { getSnapshot(): { sessionId: string } | undefined; subscribe(listener: () => void): () => void } }\n uiSession: { adapter: { current: { getSnapshot(): { key?: string }; subscribe(listener: () => void): () => void } } }\n locale: { getSnapshot(): { active: string }; subscribe(listener: () => void): () => void }\n connection?: { generation?: { subscribe(listener: () => void): () => void } }\n}\n\n/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */\nexport function selectedSessionId(client: Pick<NativeSurfaceClient, 'uiWorkspace' | 'uiSession'>): string {\n return client.uiWorkspace.current\n ? client.uiWorkspace.current.getSnapshot()?.sessionId ?? ''\n : client.uiSession.adapter.current.getSnapshot().key ?? ''\n}\n\n/** Owner props win; native settings only supplies close, so resolve its selected session. */\nexport function useNativeSeat(client: NativeSurfaceClient, props: unknown) {\n const current = useSyncExternalStore(\n useCallback((fn: () => void) => (client.uiWorkspace.current ?? client.uiSession.adapter.current).subscribe(fn), [client]),\n useCallback(() => selectedSessionId(client), [client]),\n () => '',\n )\n const language = useSyncExternalStore(\n useCallback((fn: () => void) => client.locale.subscribe(fn), [client]),\n useCallback(() => client.locale.getSnapshot().active, [client]),\n () => 'en',\n )\n const row = props && typeof props === 'object' ? props as Record<string, unknown> : {}\n const owner = row.owner && typeof row.owner === 'object' ? row.owner as Record<string, unknown> : {}\n const sessionId = typeof row.sessionId === 'string' ? row.sessionId\n : typeof owner.sessionId === 'string' ? owner.sessionId : current\n const requestedLocale = row.locale ?? owner.locale ?? language\n return { sessionId, locale: (String(requestedLocale).startsWith('zh') ? 'zh' : 'en') as 'zh' | 'en',\n hidden: row.hidden === true || owner.hidden === true }\n}\n\n/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */\nexport function useFeatureRefresh(\n client: NativeSurfaceClient, sessionId: string, refresh: () => void,\n working = false, enabled = true,\n): void {\n const refreshRef = useRef(refresh)\n refreshRef.current = refresh\n useEffect(() => {\n if (!enabled) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const queue = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n clearTimeout(timer)\n timer = setTimeout(() => refreshRef.current(), 150)\n }\n const source = sessionId ? client.sessions.binding?.(sessionId)?.eventSource : undefined\n const stopEvents = source?.subscribe(queue)\n const stopConnection = client.connection?.generation?.subscribe(queue)\n const visibility = () => { if (document.visibilityState === 'visible') queue() }\n window.addEventListener('focus', queue)\n document.addEventListener('visibilitychange', visibility)\n // Details are collapsed by default; opening an existing panel must get current state.\n document.addEventListener('toggle', queue, true)\n return () => {\n clearTimeout(timer)\n stopEvents?.(); stopConnection?.()\n window.removeEventListener('focus', queue)\n document.removeEventListener('visibilitychange', visibility)\n document.removeEventListener('toggle', queue, true)\n }\n }, [client, sessionId, enabled])\n useEffect(() => {\n if (!enabled || !working) return\n let delay = 250\n const until = Date.now() + 300_000\n let timer: ReturnType<typeof setTimeout>\n const tick = () => {\n if (Date.now() >= until) return\n if (document.visibilityState !== 'hidden') refreshRef.current()\n delay = Math.min(delay * 2, 2000)\n timer = setTimeout(tick, delay)\n }\n timer = setTimeout(tick, delay)\n return () => clearTimeout(timer)\n }, [client, sessionId, working, enabled])\n}\n","/** Browser-safe contracts. Shared RPC/result types stay on @klarkxy/dsh-ai-services. */\nexport type { RpcResult } from '@klarkxy/dsh-ai-services/contracts'\n\nexport const PLUGIN_NAME = '@klarkxy/dsh-current-title'\nexport const FEATURE_ID = 'current-title'\nexport const PROVIDER_ID = '@klarkxy/dsh-current-title'\nexport const PURPOSE_ID = 'current-title.generate'\nexport const RPC_CHANNEL = '/dsh-current-title'\nexport const TITLE_CLIENT_SERVICE = 'dshCurrentTitleClient'\nexport const PROMPT_VERSION = 'current-title.v1'\nexport const SCHEMA_VERSION = 'title-json.v1'\nexport const CONTRACT_VERSION = 1\nexport const SETTINGS_KEY = 'current'\n\nexport const DEFAULT_MAX_RECENT_MESSAGES = 8\nexport const DEFAULT_TARGET_WORDS = 5\nexport const DEFAULT_TARGET_CJK = 10\nexport const DEFAULT_MAX_INPUT_BYTES = 4096\nexport const DEFAULT_MAX_OUTPUT_TOKENS = 64\nexport const DEFAULT_TIMEOUT_MS = 60_000\nexport const DEFAULT_MAX_TITLE_BYTES = 80\n\nexport type TitleLocaleMode = 'auto' | 'zh' | 'en'\nexport type TitleSourceKind = 'fallback' | 'provider' | 'user'\n\nexport interface TitleMessage {\n readonly seq: number\n readonly text: string\n}\n\nexport interface TitleSnapshot {\n readonly title: string\n readonly messageSeqs: readonly number[]\n readonly source: { readonly kind: TitleSourceKind; readonly provider?: string }\n readonly eventSeq: number\n readonly updatedAt: number\n}\n\nexport interface TitleSettings {\n revision: number\n locale: TitleLocaleMode\n}\n\nexport interface TitleSupport {\n nativeOwner: string | null\n weOwn: boolean\n limitation?: string\n}\n\nexport interface TitleStatus {\n settings: TitleSettings\n support: TitleSupport\n session?: {\n sessionId: string\n title?: string\n sourceKind?: TitleSourceKind\n generating: boolean\n pinned: boolean\n }\n}\n\nexport interface TitleClientMarker {\n active: boolean\n}\n\nexport interface GenerateConfig {\n readonly maxRecentMessages: number\n readonly locale: TitleLocaleMode\n readonly targetWords: number\n readonly targetCjkCharacters: number\n readonly maxInputBytes: number\n readonly maxOutputTokens: number\n readonly timeoutMs: number\n readonly maxTitleBytes: number\n}\n\nexport function defaultSettings(locale: TitleLocaleMode = 'auto'): TitleSettings {\n return { revision: 0, locale }\n}\n\nexport function defaultGenerateConfig(locale: TitleLocaleMode = 'auto'): GenerateConfig {\n return {\n maxRecentMessages: DEFAULT_MAX_RECENT_MESSAGES,\n locale,\n targetWords: DEFAULT_TARGET_WORDS,\n targetCjkCharacters: DEFAULT_TARGET_CJK,\n maxInputBytes: DEFAULT_MAX_INPUT_BYTES,\n maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n maxTitleBytes: DEFAULT_MAX_TITLE_BYTES,\n }\n}\n\nexport function isTitleLocaleMode(value: unknown): value is TitleLocaleMode {\n return value === 'auto' || value === 'zh' || value === 'en'\n}\n","import type { RpcResult, TitleLocaleMode, TitleStatus } from './contracts.ts'\n\nexport const titleCopy = {\n zh: {\n label: '当前标题',\n pinned: '已手动命名',\n generating: '正在生成标题…',\n regenerate: '重新生成',\n locale: '类型标签语言',\n auto: '自动',\n saved: '已保存。',\n failed: '无法读取标题设置。',\n },\n en: {\n label: 'Current title',\n pinned: 'Renamed manually',\n generating: 'Generating title…',\n regenerate: 'Regenerate',\n locale: 'Type-label language',\n auto: 'Auto',\n saved: 'Saved.',\n failed: 'Could not read title settings.',\n },\n} as const\n\nexport function settingsCopy(locale: 'zh' | 'en') {\n return titleCopy[locale] ?? titleCopy.zh\n}\n\nexport function shouldSkipTitleRefresh(input: { busy: boolean }): boolean {\n return input.busy\n}\n\nexport function createTitleClientWork() {\n let generation = 0\n let request = 0\n let disposed = false\n return {\n get disposed() { return disposed },\n beginLoad() {\n generation += 1\n request += 1\n return generation\n },\n captureLoad() {\n return generation\n },\n beginRequest() {\n request += 1\n return { generation, request }\n },\n isLoad(generationToken: number) {\n return !disposed && generation === generationToken\n },\n isRequest(token: { generation: number; request: number }) {\n return !disposed && generation === token.generation && request === token.request\n },\n dispose() {\n disposed = true\n generation += 1\n request += 1\n },\n }\n}\n\nexport type TitleClientWork = ReturnType<typeof createTitleClientWork>\nexport type TitleClientCall = (endpoint: string, payload?: unknown) => Promise<unknown>\n\nfunction unwrap<T>(result: RpcResult<T>): T {\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n}\n\nasync function invoke<T>(call: TitleClientCall, endpoint: string, payload?: unknown): Promise<T> {\n return unwrap(await call(endpoint, payload) as RpcResult<T>)\n}\n\nexport async function runTitleStatusLoad(input: {\n call: TitleClientCall\n sessionId?: string\n isCurrent: () => boolean\n failedMessage: string\n onStatus: (status: TitleStatus) => void\n onError: (message: string) => void\n onMarker: (status: TitleStatus | undefined) => void\n}): Promise<void> {\n try {\n const status = await invoke<TitleStatus>(\n input.call,\n 'status',\n input.sessionId ? { sessionId: input.sessionId } : {},\n )\n if (!input.isCurrent()) return\n input.onStatus(status)\n input.onMarker(status)\n } catch {\n if (!input.isCurrent()) return\n input.onMarker(undefined)\n input.onError(input.failedMessage)\n }\n}\n\nexport async function runTitleRegenerateFlow(input: {\n call: TitleClientCall\n sessionId: string\n isCurrent: () => boolean\n failedMessage: string\n onBusy: (busy: boolean) => void\n onStatus: (status: TitleStatus) => void\n onError: (message: string) => void\n onMarker: (status: TitleStatus | undefined) => void\n}): Promise<void> {\n if (!input.sessionId || !input.isCurrent()) return\n input.onBusy(true)\n try {\n await invoke(input.call, 'regenerate', { sessionId: input.sessionId })\n if (!input.isCurrent()) return\n const status = await invoke<TitleStatus>(input.call, 'status', { sessionId: input.sessionId })\n if (!input.isCurrent()) return\n input.onStatus(status)\n input.onMarker(status)\n } catch (cause) {\n if (!input.isCurrent()) return\n input.onError(cause instanceof Error ? cause.message : input.failedMessage)\n } finally {\n if (!input.isCurrent()) return\n input.onBusy(false)\n }\n}\n\nexport async function runTitleSettingsSave(input: {\n call: TitleClientCall\n locale: TitleLocaleMode\n expectedRevision: number\n isCurrent: () => boolean\n savedMessage: string\n failedMessage: string\n onBusy: (busy: boolean) => void\n onSettings: (settings: TitleStatus['settings']) => void\n onNote: (note: string) => void\n onError: (message: string) => void\n}): Promise<void> {\n input.onBusy(true)\n try {\n const settings = await invoke<TitleStatus['settings']>(input.call, 'settings', {\n locale: input.locale,\n expectedRevision: input.expectedRevision,\n })\n if (!input.isCurrent()) return\n input.onSettings(settings)\n input.onNote(input.savedMessage)\n } catch (cause) {\n if (!input.isCurrent()) return\n input.onError(cause instanceof Error ? cause.message : input.failedMessage)\n } finally {\n if (!input.isCurrent()) return\n input.onBusy(false)\n }\n}\n","import type { TitleClientMarker, TitleStatus } from './contracts.ts'\n\nexport function createTitleClientMarker(): TitleClientMarker {\n return { active: false }\n}\n\nexport function applyMarkerFromStatus(marker: TitleClientMarker, status: Pick<TitleStatus, 'support'> | undefined, live: boolean): void {\n if (!live) return\n marker.active = status?.support.weOwn === true\n}\n\nexport function disposeTitleClientMarker(marker: TitleClientMarker): void {\n marker.active = false\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport type { NativeSurfaceClient } from '@klarkxy/dsh-ai-services/client-utils'\nimport { useFeatureRefresh, useNativeSeat } from '@klarkxy/dsh-ai-services/client-utils'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { RPC_CHANNEL, TITLE_CLIENT_SERVICE, type RpcResult, type TitleLocaleMode, type TitleStatus } from './contracts.ts'\nimport {\n createTitleClientWork, runTitleRegenerateFlow, runTitleSettingsSave, runTitleStatusLoad,\n shouldSkipTitleRefresh, titleCopy, type TitleClientCall, type TitleClientWork,\n} from './client-work.ts'\nimport { applyMarkerFromStatus, createTitleClientMarker, disposeTitleClientMarker } from './marker.ts'\n\nexport const name = 'dsh-current-title-client'\nexport const inject = ['slots', 'connection', 'sessions', 'locale', 'uiWorkspace', 'uiSession'] as const\nexport { createTitleClientMarker, disposeTitleClientMarker, applyMarkerFromStatus }\n\ntype Client = NativeSurfaceClient & {\n connection: NonNullable<NativeSurfaceClient['connection']> & {\n rpc: { call(channel: string, endpoint: string, payload: unknown): Promise<unknown> }\n }\n slots: {\n inject(key: string, callback: () => unknown): () => void\n register(spec: { name: string; id: string; label: string; order: number }, render: unknown): () => void\n }\n}\n\nexport function TitleSettings(props: {\n client: Client\n marker: { active: boolean }\n owner?: unknown\n}) {\n const seat = useNativeSeat(props.client, props.owner)\n const sessionId = seat.sessionId\n const text = titleCopy[seat.locale]\n const [status, setStatus] = useState<TitleStatus>()\n const [busy, setBusy] = useState(false)\n const [note, setNote] = useState('')\n const [error, setError] = useState('')\n const workRef = useRef<TitleClientWork>(undefined)\n if (!workRef.current) workRef.current = createTitleClientWork()\n const work = workRef.current\n const busyRef = useRef(busy)\n busyRef.current = busy\n\n const call: TitleClientCall = (endpoint, payload = {}) => (\n props.client.connection.rpc.call(RPC_CHANNEL, endpoint, payload)\n )\n\n function commitMarker(next: TitleStatus | undefined) {\n applyMarkerFromStatus(props.marker, next, true)\n }\n\n useEffect(() => () => { work.dispose() }, [work])\n\n useEffect(() => {\n const generation = work.beginLoad()\n const isCurrent = () => work.isLoad(generation)\n setBusy(false)\n setError('')\n setNote('')\n void runTitleStatusLoad({\n call,\n sessionId: sessionId || undefined,\n isCurrent,\n failedMessage: text.failed,\n onStatus: setStatus,\n onError: setError,\n onMarker: commitMarker,\n })\n return () => { if (work.isLoad(generation)) work.beginLoad() }\n }, [props.client, props.marker, sessionId, text.failed, work])\n\n const refreshStatus = useCallback(() => {\n if (shouldSkipTitleRefresh({ busy: busyRef.current }) || work.disposed) return\n const generation = work.captureLoad()\n void runTitleStatusLoad({\n call,\n sessionId: sessionId || undefined,\n isCurrent: () => work.isLoad(generation) && !shouldSkipTitleRefresh({ busy: busyRef.current }),\n failedMessage: text.failed,\n onStatus: next => {\n if (shouldSkipTitleRefresh({ busy: busyRef.current })) return\n setStatus(next)\n },\n onError: () => {},\n onMarker: next => {\n if (shouldSkipTitleRefresh({ busy: busyRef.current })) return\n commitMarker(next)\n },\n })\n }, [props.client, props.marker, sessionId, text.failed, work])\n\n useFeatureRefresh(\n props.client,\n sessionId,\n refreshStatus,\n status?.session?.generating === true && !busy,\n Boolean(sessionId),\n )\n\n const locale = status?.settings.locale ?? 'auto'\n const modes: Array<{ id: TitleLocaleMode; label: string }> = [\n { id: 'auto', label: text.auto },\n { id: 'zh', label: '中文' },\n { id: 'en', label: 'English' },\n ]\n const title = status?.session?.title\n const pinned = status?.session?.pinned === true\n const weOwn = status?.support.weOwn === true\n const canRegenerate = Boolean(sessionId) && weOwn && !busy\n\n return <section className=\"current-title-settings\" data-testid=\"current-title-settings\">\n {!status && !error ? <p role=\"status\">{seat.locale === 'en' ? 'Loading title settings…' : '正在读取标题设置…'}</p> : null}\n {title ? <p translate=\"no\">{title}</p> : null}\n {pinned ? <p className=\"current-title-meta\">{text.pinned}</p> : null}\n {status?.session?.generating ? <p role=\"status\">{text.generating}</p> : null}\n {error ? <p role=\"alert\">{error}</p> : null}\n {note ? <p role=\"status\">{note}</p> : null}\n {sessionId ? <button type=\"button\" disabled={!canRegenerate} aria-label={text.regenerate}\n onClick={() => {\n if (!sessionId) return\n const token = work.beginRequest()\n setError('')\n setNote('')\n void runTitleRegenerateFlow({\n call,\n sessionId,\n isCurrent: () => work.isRequest(token),\n failedMessage: text.failed,\n onBusy: setBusy,\n onStatus: setStatus,\n onError: setError,\n onMarker: commitMarker,\n })\n }}>{text.regenerate}</button> : null}\n <fieldset disabled={busy || !status} aria-label={text.locale}>\n <legend>{text.locale}</legend>\n {modes.map(mode => (\n <label key={mode.id}>\n <input type=\"radio\" name=\"current-title-locale\" value={mode.id} checked={locale === mode.id}\n onChange={() => {\n if (!status) return\n const token = work.beginRequest()\n setError('')\n setNote('')\n void runTitleSettingsSave({\n call,\n locale: mode.id,\n expectedRevision: status.settings.revision,\n isCurrent: () => work.isRequest(token),\n savedMessage: text.saved,\n failedMessage: text.failed,\n onBusy: setBusy,\n onSettings: settings => setStatus({ ...status, settings }),\n onNote: setNote,\n onError: setError,\n })\n }} />\n {mode.label}\n </label>\n ))}\n </fieldset>\n </section>\n}\n\nconst styles = `\n.current-title-settings{display:grid;gap:8px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}\n.current-title-settings p{margin:0}\n.current-title-settings p[translate=\"no\"]{overflow-wrap:anywhere}\n.current-title-meta{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}\n.current-title-settings button{min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}\n.current-title-settings button:hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}\n.current-title-settings button:active:not(:disabled){transform:scale(.97)}\n.current-title-settings button:disabled{opacity:.45;cursor:not-allowed}\n.current-title-settings fieldset{margin:0;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:10px;padding:8px 12px;display:grid;gap:8px}\n.current-title-settings label{display:flex;gap:8px;align-items:center}\n.current-title-settings :focus-visible{outline:2px solid currentColor;outline-offset:3px}\n@media(prefers-reduced-motion:reduce){.current-title-settings button{transition:none}}\n`\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context { dshCurrentTitleClient: { active: boolean } }\n}\n\nexport function apply(ctx: Context): void {\n const client = ctx as unknown as Client\n const marker = createTitleClientMarker()\n ctx.provide(TITLE_CLIENT_SERVICE, marker)\n ctx.effect(() => {\n let live = true\n void client.connection.rpc.call(RPC_CHANNEL, 'status', {}).then(result => {\n const row = result as RpcResult<TitleStatus>\n if (!live) return\n applyMarkerFromStatus(marker, row?.ok ? row.value : undefined, live)\n }).catch(() => { if (live) applyMarkerFromStatus(marker, undefined, live) })\n return () => { live = false; disposeTitleClientMarker(marker) }\n }, 'current-title.marker')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.inner.cjs","names":["useSyncExternalStore","useCallback","useRef","useState","useRef","useCallback"],"sources":["../../dsh-ai-services/src/client-utils.ts","../src/contracts.ts","../src/client-work.ts","../src/marker.ts","../src/client.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\n\nexport interface NativeSurfaceClient {\n sessions: {\n binding?(sessionId: string): { eventSource: { subscribe(listener: () => void): () => void } } | undefined\n }\n uiWorkspace: { current?: { getSnapshot(): { sessionId: string } | undefined; subscribe(listener: () => void): () => void } }\n uiSession: { adapter: { current: { getSnapshot(): { key?: string }; subscribe(listener: () => void): () => void } } }\n locale: { getSnapshot(): { active: string }; subscribe(listener: () => void): () => void }\n connection?: { generation?: { subscribe(listener: () => void): () => void } }\n}\n\n/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */\nexport function selectedSessionId(client: Pick<NativeSurfaceClient, 'uiWorkspace' | 'uiSession'>): string {\n return client.uiWorkspace.current\n ? client.uiWorkspace.current.getSnapshot()?.sessionId ?? ''\n : client.uiSession.adapter.current.getSnapshot().key ?? ''\n}\n\n/** Owner props win; native settings only supplies close, so resolve its selected session. */\nexport function useNativeSeat(client: NativeSurfaceClient, props: unknown) {\n const current = useSyncExternalStore(\n useCallback((fn: () => void) => (client.uiWorkspace.current ?? client.uiSession.adapter.current).subscribe(fn), [client]),\n useCallback(() => selectedSessionId(client), [client]),\n () => '',\n )\n const language = useSyncExternalStore(\n useCallback((fn: () => void) => client.locale.subscribe(fn), [client]),\n useCallback(() => client.locale.getSnapshot().active, [client]),\n () => 'en',\n )\n const row = props && typeof props === 'object' ? props as Record<string, unknown> : {}\n const owner = row.owner && typeof row.owner === 'object' ? row.owner as Record<string, unknown> : {}\n const sessionId = typeof row.sessionId === 'string' ? row.sessionId\n : typeof owner.sessionId === 'string' ? owner.sessionId : current\n const requestedLocale = row.locale ?? owner.locale ?? language\n return { sessionId, locale: (String(requestedLocale).startsWith('zh') ? 'zh' : 'en') as 'zh' | 'en',\n hidden: row.hidden === true || owner.hidden === true }\n}\n\n/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */\nexport function useFeatureRefresh(\n client: NativeSurfaceClient, sessionId: string, refresh: () => void,\n working = false, enabled = true,\n): void {\n const refreshRef = useRef(refresh)\n refreshRef.current = refresh\n useEffect(() => {\n if (!enabled) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const queue = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n clearTimeout(timer)\n timer = setTimeout(() => refreshRef.current(), 150)\n }\n const source = sessionId ? client.sessions.binding?.(sessionId)?.eventSource : undefined\n const stopEvents = source?.subscribe(queue)\n const stopConnection = client.connection?.generation?.subscribe(queue)\n const visibility = () => { if (document.visibilityState === 'visible') queue() }\n window.addEventListener('focus', queue)\n document.addEventListener('visibilitychange', visibility)\n // Details are collapsed by default; opening an existing panel must get current state.\n document.addEventListener('toggle', queue, true)\n return () => {\n clearTimeout(timer)\n stopEvents?.(); stopConnection?.()\n window.removeEventListener('focus', queue)\n document.removeEventListener('visibilitychange', visibility)\n document.removeEventListener('toggle', queue, true)\n }\n }, [client, sessionId, enabled])\n useEffect(() => {\n if (!enabled || !working) return\n let delay = 250\n const until = Date.now() + 300_000\n let timer: ReturnType<typeof setTimeout>\n const tick = () => {\n if (Date.now() >= until) return\n if (document.visibilityState !== 'hidden') refreshRef.current()\n delay = Math.min(delay * 2, 2000)\n timer = setTimeout(tick, delay)\n }\n timer = setTimeout(tick, delay)\n return () => clearTimeout(timer)\n }, [client, sessionId, working, enabled])\n}\n","/** Browser-safe contracts. Shared RPC/result types stay on @klarkxy/dsh-ai-services. */\nexport type { RpcResult } from '@klarkxy/dsh-ai-services/contracts'\n\nexport const PLUGIN_NAME = '@klarkxy/dsh-current-title'\nexport const FEATURE_ID = 'current-title'\nexport const PROVIDER_ID = '@klarkxy/dsh-current-title'\nexport const PURPOSE_ID = 'current-title.generate'\nexport const RPC_CHANNEL = '/dsh-current-title'\nexport const TITLE_CLIENT_SERVICE = 'dshCurrentTitleClient'\nexport const PROMPT_VERSION = 'current-title.v1'\nexport const SCHEMA_VERSION = 'title-json.v1'\nexport const CONTRACT_VERSION = 1\nexport const SETTINGS_KEY = 'current'\n\nexport const DEFAULT_MAX_RECENT_MESSAGES = 8\nexport const DEFAULT_TARGET_WORDS = 5\nexport const DEFAULT_TARGET_CJK = 10\nexport const DEFAULT_MAX_INPUT_BYTES = 4096\nexport const DEFAULT_MAX_OUTPUT_TOKENS = 64\nexport const DEFAULT_TIMEOUT_MS = 60_000\nexport const DEFAULT_MAX_TITLE_BYTES = 80\n\nexport type TitleLocaleMode = 'auto' | 'zh' | 'en'\nexport type TitleSourceKind = 'fallback' | 'provider' | 'user'\n\nexport interface TitleMessage {\n readonly seq: number\n readonly text: string\n}\n\nexport interface TitleSnapshot {\n readonly title: string\n readonly messageSeqs: readonly number[]\n readonly source: { readonly kind: TitleSourceKind; readonly provider?: string }\n readonly eventSeq: number\n readonly updatedAt: number\n}\n\nexport interface TitleSettings {\n revision: number\n locale: TitleLocaleMode\n}\n\nexport interface TitleSupport {\n nativeOwner: string | null\n weOwn: boolean\n limitation?: string\n}\n\nexport interface TitleStatus {\n settings: TitleSettings\n support: TitleSupport\n session?: {\n sessionId: string\n title?: string\n sourceKind?: TitleSourceKind\n generating: boolean\n pinned: boolean\n }\n}\n\nexport interface TitleClientMarker {\n active: boolean\n}\n\nexport interface GenerateConfig {\n readonly maxRecentMessages: number\n readonly locale: TitleLocaleMode\n readonly targetWords: number\n readonly targetCjkCharacters: number\n readonly maxInputBytes: number\n readonly maxOutputTokens: number\n readonly timeoutMs: number\n readonly maxTitleBytes: number\n}\n\nexport function defaultSettings(locale: TitleLocaleMode = 'auto'): TitleSettings {\n return { revision: 0, locale }\n}\n\nexport function defaultGenerateConfig(locale: TitleLocaleMode = 'auto'): GenerateConfig {\n return {\n maxRecentMessages: DEFAULT_MAX_RECENT_MESSAGES,\n locale,\n targetWords: DEFAULT_TARGET_WORDS,\n targetCjkCharacters: DEFAULT_TARGET_CJK,\n maxInputBytes: DEFAULT_MAX_INPUT_BYTES,\n maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n maxTitleBytes: DEFAULT_MAX_TITLE_BYTES,\n }\n}\n\nexport function isTitleLocaleMode(value: unknown): value is TitleLocaleMode {\n return value === 'auto' || value === 'zh' || value === 'en'\n}\n","import type { RpcResult, TitleLocaleMode, TitleStatus } from './contracts.ts'\n\nexport const titleCopy = {\n zh: {\n label: '自动标题',\n pinned: '已手动命名',\n generating: '正在生成标题…',\n regenerate: '重新生成',\n locale: '类型标签语言',\n auto: '自动',\n saved: '已保存。',\n failed: '无法读取标题设置。',\n },\n en: {\n label: 'Auto Title',\n pinned: 'Renamed manually',\n generating: 'Generating title…',\n regenerate: 'Regenerate',\n locale: 'Type-label language',\n auto: 'Auto',\n saved: 'Saved.',\n failed: 'Could not read title settings.',\n },\n} as const\n\nexport function settingsCopy(locale: 'zh' | 'en') {\n return titleCopy[locale] ?? titleCopy.zh\n}\n\nexport function shouldSkipTitleRefresh(input: { busy: boolean }): boolean {\n return input.busy\n}\n\nexport function createTitleClientWork() {\n let generation = 0\n let request = 0\n let disposed = false\n return {\n get disposed() { return disposed },\n beginLoad() {\n generation += 1\n request += 1\n return generation\n },\n captureLoad() {\n return generation\n },\n beginRequest() {\n request += 1\n return { generation, request }\n },\n isLoad(generationToken: number) {\n return !disposed && generation === generationToken\n },\n isRequest(token: { generation: number; request: number }) {\n return !disposed && generation === token.generation && request === token.request\n },\n dispose() {\n disposed = true\n generation += 1\n request += 1\n },\n }\n}\n\nexport type TitleClientWork = ReturnType<typeof createTitleClientWork>\nexport type TitleClientCall = (endpoint: string, payload?: unknown) => Promise<unknown>\n\nfunction unwrap<T>(result: RpcResult<T>): T {\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n}\n\nasync function invoke<T>(call: TitleClientCall, endpoint: string, payload?: unknown): Promise<T> {\n return unwrap(await call(endpoint, payload) as RpcResult<T>)\n}\n\nexport async function runTitleStatusLoad(input: {\n call: TitleClientCall\n sessionId?: string\n isCurrent: () => boolean\n failedMessage: string\n onStatus: (status: TitleStatus) => void\n onError: (message: string) => void\n onMarker: (status: TitleStatus | undefined) => void\n}): Promise<void> {\n try {\n const status = await invoke<TitleStatus>(\n input.call,\n 'status',\n input.sessionId ? { sessionId: input.sessionId } : {},\n )\n if (!input.isCurrent()) return\n input.onStatus(status)\n input.onMarker(status)\n } catch {\n if (!input.isCurrent()) return\n input.onMarker(undefined)\n input.onError(input.failedMessage)\n }\n}\n\nexport async function runTitleRegenerateFlow(input: {\n call: TitleClientCall\n sessionId: string\n isCurrent: () => boolean\n failedMessage: string\n onBusy: (busy: boolean) => void\n onStatus: (status: TitleStatus) => void\n onError: (message: string) => void\n onMarker: (status: TitleStatus | undefined) => void\n}): Promise<void> {\n if (!input.sessionId || !input.isCurrent()) return\n input.onBusy(true)\n try {\n await invoke(input.call, 'regenerate', { sessionId: input.sessionId })\n if (!input.isCurrent()) return\n const status = await invoke<TitleStatus>(input.call, 'status', { sessionId: input.sessionId })\n if (!input.isCurrent()) return\n input.onStatus(status)\n input.onMarker(status)\n } catch (cause) {\n if (!input.isCurrent()) return\n input.onError(cause instanceof Error ? cause.message : input.failedMessage)\n } finally {\n if (!input.isCurrent()) return\n input.onBusy(false)\n }\n}\n\nexport async function runTitleSettingsSave(input: {\n call: TitleClientCall\n locale: TitleLocaleMode\n expectedRevision: number\n isCurrent: () => boolean\n savedMessage: string\n failedMessage: string\n onBusy: (busy: boolean) => void\n onSettings: (settings: TitleStatus['settings']) => void\n onNote: (note: string) => void\n onError: (message: string) => void\n}): Promise<void> {\n input.onBusy(true)\n try {\n const settings = await invoke<TitleStatus['settings']>(input.call, 'settings', {\n locale: input.locale,\n expectedRevision: input.expectedRevision,\n })\n if (!input.isCurrent()) return\n input.onSettings(settings)\n input.onNote(input.savedMessage)\n } catch (cause) {\n if (!input.isCurrent()) return\n input.onError(cause instanceof Error ? cause.message : input.failedMessage)\n } finally {\n if (!input.isCurrent()) return\n input.onBusy(false)\n }\n}\n","import type { TitleClientMarker, TitleStatus } from './contracts.ts'\n\nexport function createTitleClientMarker(): TitleClientMarker {\n return { active: false }\n}\n\nexport function applyMarkerFromStatus(marker: TitleClientMarker, status: Pick<TitleStatus, 'support'> | undefined, live: boolean): void {\n if (!live) return\n marker.active = status?.support.weOwn === true\n}\n\nexport function disposeTitleClientMarker(marker: TitleClientMarker): void {\n marker.active = false\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport type { NativeSurfaceClient } from '@klarkxy/dsh-ai-services/client-utils'\nimport { useFeatureRefresh, useNativeSeat } from '@klarkxy/dsh-ai-services/client-utils'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { RPC_CHANNEL, TITLE_CLIENT_SERVICE, type RpcResult, type TitleLocaleMode, type TitleStatus } from './contracts.ts'\nimport {\n createTitleClientWork, runTitleRegenerateFlow, runTitleSettingsSave, runTitleStatusLoad,\n shouldSkipTitleRefresh, titleCopy, type TitleClientCall, type TitleClientWork,\n} from './client-work.ts'\nimport { applyMarkerFromStatus, createTitleClientMarker, disposeTitleClientMarker } from './marker.ts'\n\nexport const name = 'dsh-current-title-client'\nexport const inject = ['slots', 'connection', 'sessions', 'locale', 'uiWorkspace', 'uiSession'] as const\nexport { createTitleClientMarker, disposeTitleClientMarker, applyMarkerFromStatus }\n\ntype Client = NativeSurfaceClient & {\n connection: NonNullable<NativeSurfaceClient['connection']> & {\n rpc: { call(channel: string, endpoint: string, payload: unknown): Promise<unknown> }\n }\n slots: {\n inject(key: string, callback: () => unknown): () => void\n register(spec: { name: string; id: string; label: string; order: number }, render: unknown): () => void\n }\n}\n\nexport function TitleSettings(props: {\n client: Client\n marker: { active: boolean }\n owner?: unknown\n}) {\n const seat = useNativeSeat(props.client, props.owner)\n const sessionId = seat.sessionId\n const text = titleCopy[seat.locale]\n const [status, setStatus] = useState<TitleStatus>()\n const [busy, setBusy] = useState(false)\n const [note, setNote] = useState('')\n const [error, setError] = useState('')\n const workRef = useRef<TitleClientWork>(undefined)\n if (!workRef.current) workRef.current = createTitleClientWork()\n const work = workRef.current\n const busyRef = useRef(busy)\n busyRef.current = busy\n\n const call: TitleClientCall = (endpoint, payload = {}) => (\n props.client.connection.rpc.call(RPC_CHANNEL, endpoint, payload)\n )\n\n function commitMarker(next: TitleStatus | undefined) {\n applyMarkerFromStatus(props.marker, next, true)\n }\n\n useEffect(() => () => { work.dispose() }, [work])\n\n useEffect(() => {\n const generation = work.beginLoad()\n const isCurrent = () => work.isLoad(generation)\n setBusy(false)\n setError('')\n setNote('')\n void runTitleStatusLoad({\n call,\n sessionId: sessionId || undefined,\n isCurrent,\n failedMessage: text.failed,\n onStatus: setStatus,\n onError: setError,\n onMarker: commitMarker,\n })\n return () => { if (work.isLoad(generation)) work.beginLoad() }\n }, [props.client, props.marker, sessionId, text.failed, work])\n\n const refreshStatus = useCallback(() => {\n if (shouldSkipTitleRefresh({ busy: busyRef.current }) || work.disposed) return\n const generation = work.captureLoad()\n void runTitleStatusLoad({\n call,\n sessionId: sessionId || undefined,\n isCurrent: () => work.isLoad(generation) && !shouldSkipTitleRefresh({ busy: busyRef.current }),\n failedMessage: text.failed,\n onStatus: next => {\n if (shouldSkipTitleRefresh({ busy: busyRef.current })) return\n setStatus(next)\n },\n onError: () => {},\n onMarker: next => {\n if (shouldSkipTitleRefresh({ busy: busyRef.current })) return\n commitMarker(next)\n },\n })\n }, [props.client, props.marker, sessionId, text.failed, work])\n\n useFeatureRefresh(\n props.client,\n sessionId,\n refreshStatus,\n status?.session?.generating === true && !busy,\n Boolean(sessionId),\n )\n\n const locale = status?.settings.locale ?? 'auto'\n const modes: Array<{ id: TitleLocaleMode; label: string }> = [\n { id: 'auto', label: text.auto },\n { id: 'zh', label: '中文' },\n { id: 'en', label: 'English' },\n ]\n const title = status?.session?.title\n const pinned = status?.session?.pinned === true\n const weOwn = status?.support.weOwn === true\n const canRegenerate = Boolean(sessionId) && weOwn && !busy\n\n return <section className=\"current-title-settings\" data-testid=\"current-title-settings\">\n {!status && !error ? <p role=\"status\">{seat.locale === 'en' ? 'Loading title settings…' : '正在读取标题设置…'}</p> : null}\n {title ? <p translate=\"no\">{title}</p> : null}\n {pinned ? <p className=\"current-title-meta\">{text.pinned}</p> : null}\n {status?.session?.generating ? <p role=\"status\">{text.generating}</p> : null}\n {error ? <p role=\"alert\">{error}</p> : null}\n {note ? <p role=\"status\">{note}</p> : null}\n {sessionId ? <button type=\"button\" disabled={!canRegenerate} aria-label={text.regenerate}\n onClick={() => {\n if (!sessionId) return\n const token = work.beginRequest()\n setError('')\n setNote('')\n void runTitleRegenerateFlow({\n call,\n sessionId,\n isCurrent: () => work.isRequest(token),\n failedMessage: text.failed,\n onBusy: setBusy,\n onStatus: setStatus,\n onError: setError,\n onMarker: commitMarker,\n })\n }}>{text.regenerate}</button> : null}\n <fieldset disabled={busy || !status} aria-label={text.locale}>\n <legend>{text.locale}</legend>\n {modes.map(mode => (\n <label key={mode.id}>\n <input type=\"radio\" name=\"current-title-locale\" value={mode.id} checked={locale === mode.id}\n onChange={() => {\n if (!status) return\n const token = work.beginRequest()\n setError('')\n setNote('')\n void runTitleSettingsSave({\n call,\n locale: mode.id,\n expectedRevision: status.settings.revision,\n isCurrent: () => work.isRequest(token),\n savedMessage: text.saved,\n failedMessage: text.failed,\n onBusy: setBusy,\n onSettings: settings => setStatus({ ...status, settings }),\n onNote: setNote,\n onError: setError,\n })\n }} />\n {mode.label}\n </label>\n ))}\n </fieldset>\n </section>\n}\n\nconst styles = `\n.current-title-settings{display:grid;gap:8px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}\n.current-title-settings p{margin:0}\n.current-title-settings p[translate=\"no\"]{overflow-wrap:anywhere}\n.current-title-meta{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}\n.current-title-settings button{min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}\n.current-title-settings button:hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}\n.current-title-settings button:active:not(:disabled){transform:scale(.97)}\n.current-title-settings button:disabled{opacity:.45;cursor:not-allowed}\n.current-title-settings fieldset{margin:0;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:10px;padding:8px 12px;display:grid;gap:8px}\n.current-title-settings label{display:flex;gap:8px;align-items:center}\n.current-title-settings :focus-visible{outline:2px solid currentColor;outline-offset:3px}\n@media(prefers-reduced-motion:reduce){.current-title-settings button{transition:none}}\n`\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context { dshCurrentTitleClient: { active: boolean } }\n}\n\nexport function apply(ctx: Context): void {\n const client = ctx as unknown as Client\n const marker = createTitleClientMarker()\n ctx.provide(TITLE_CLIENT_SERVICE, marker)\n ctx.effect(() => {\n let live = true\n void client.connection.rpc.call(RPC_CHANNEL, 'status', {}).then(result => {\n const row = result as RpcResult<TitleStatus>\n if (!live) return\n applyMarkerFromStatus(marker, row?.ok ? row.value : undefined, live)\n }).catch(() => { if (live) applyMarkerFromStatus(marker, undefined, live) })\n return () => { live = false; disposeTitleClientMarker(marker) }\n }, 'current-title.marker')\n}\n"],"mappings":"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"}
|
package/lib/client.js
CHANGED
|
@@ -248,7 +248,7 @@ const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
|
248
248
|
//#region src/client-work.ts
|
|
249
249
|
const titleCopy = {
|
|
250
250
|
zh: {
|
|
251
|
-
label: "
|
|
251
|
+
label: "自动标题",
|
|
252
252
|
pinned: "已手动命名",
|
|
253
253
|
generating: "正在生成标题…",
|
|
254
254
|
regenerate: "重新生成",
|
|
@@ -258,7 +258,7 @@ const titleCopy = {
|
|
|
258
258
|
failed: "无法读取标题设置。"
|
|
259
259
|
},
|
|
260
260
|
en: {
|
|
261
|
-
label: "
|
|
261
|
+
label: "Auto Title",
|
|
262
262
|
pinned: "Renamed manually",
|
|
263
263
|
generating: "Generating title…",
|
|
264
264
|
regenerate: "Regenerate",
|
package/lib/index.js
CHANGED
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/input.ts","../src/output.ts","../src/messages.ts","../src/generate.ts","../src/ownership.ts","../src/native-slot.ts","../src/storage.ts","../src/service.ts","../src/index.ts"],"sourcesContent":["import type { TitleMessage } from './contracts.ts'\n\nexport const FRAME_PREFIX =\n 'Choose the current task from these recent human messages, ordered oldest to newest:\\n'\n\nexport function frameMessages(messages: readonly TitleMessage[]): string {\n return `${FRAME_PREFIX}${JSON.stringify(messages)}`\n}\n\nexport function utf8Bytes(value: string): number {\n return Buffer.byteLength(value, 'utf8')\n}\n\nfunction middleSlice(codePoints: readonly string[], kept: number): string {\n if (kept >= codePoints.length) return codePoints.join('')\n if (kept <= 0) return ''\n if (kept === 1) return '…'\n\n const contentPoints = kept - 1\n const headCount = Math.ceil(contentPoints / 2)\n const tailCount = Math.floor(contentPoints / 2)\n return `${codePoints.slice(0, headCount).join('')}…${\n tailCount === 0 ? '' : codePoints.slice(-tailCount).join('')\n }`\n}\n\nexport function fitSingleMessage(message: TitleMessage, maxInputBytes: number): TitleMessage {\n const empty = { ...message, text: '' }\n if (utf8Bytes(frameMessages([empty])) > maxInputBytes) {\n throw new Error('dsh-current-title: maxInputBytes is too small for the input envelope')\n }\n if (utf8Bytes(frameMessages([message])) <= maxInputBytes) return message\n\n const points = Array.from(message.text)\n let low = 0\n let high = points.length\n let best = ''\n\n while (low <= high) {\n const kept = Math.floor((low + high) / 2)\n const text = middleSlice(points, kept)\n if (utf8Bytes(frameMessages([{ ...message, text }])) <= maxInputBytes) {\n best = text\n low = kept + 1\n } else {\n high = kept - 1\n }\n }\n\n return { ...message, text: best }\n}\n\nexport function selectRecentMessages(\n messages: readonly TitleMessage[],\n maxRecentMessages: number,\n maxInputBytes: number,\n): TitleMessage[] {\n if (messages.length === 0) return []\n\n const selected = messages.slice(-maxRecentMessages).map(message => ({ ...message }))\n while (selected.length > 1 && utf8Bytes(frameMessages(selected)) > maxInputBytes) {\n selected.shift()\n }\n\n if (utf8Bytes(frameMessages(selected)) > maxInputBytes) {\n selected[0] = fitSingleMessage(selected[0]!, maxInputBytes)\n }\n\n return selected\n}\n","export const TITLE_TYPE_KEYS = [\n 'feature',\n 'fix',\n 'optimize',\n 'refactor',\n 'test',\n 'docs',\n 'release',\n 'config',\n 'explore',\n 'discuss',\n] as const\n\nexport const TITLE_LOCALES = ['zh', 'en'] as const\nexport const TITLE_LOCALE_MODES = ['auto', ...TITLE_LOCALES] as const\n\nexport type TitleType = (typeof TITLE_TYPE_KEYS)[number]\nexport type TitleLocale = (typeof TITLE_LOCALES)[number]\nexport type TitleLocaleMode = (typeof TITLE_LOCALE_MODES)[number]\n\nconst TITLE_TYPE_LABELS: Record<TitleLocale, Record<TitleType, string>> = {\n zh: {\n feature: '功能',\n fix: '修复',\n optimize: '优化',\n refactor: '重构',\n test: '测试',\n docs: '文档',\n release: '发布',\n config: '配置',\n explore: '探索',\n discuss: '讨论',\n },\n en: {\n feature: 'Feature',\n fix: 'Fix',\n optimize: 'Optimize',\n refactor: 'Refactor',\n test: 'Test',\n docs: 'Docs',\n release: 'Release',\n config: 'Config',\n explore: 'Explore',\n discuss: 'Discussion',\n },\n}\n\nexport interface ParsedTitle {\n type: TitleType\n summary: string\n}\n\nconst OSC_SEQUENCE = /(?:\\u001B\\]|\\u009D)(?:(?!\\u0007|\\u001B\\\\)[\\s\\S])*(?:\\u0007|\\u001B\\\\|$)/gu\nconst CSI_SEQUENCE = /(?:\\u001B\\[|\\u009B)[0-?]*[ -/]*[@-~]/gu\nconst ESC_SEQUENCE = /\\u001B[@-_]/gu\nconst CONTROL_CHARACTER = /[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u009F]/gu\nconst DIRECTIONAL_CONTROL = /[\\u200B\\u200E\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u206F\\uFEFF]/gu\n\nfunction cleanTitleText(input: string): string {\n return input\n .replace(OSC_SEQUENCE, '')\n .replace(CSI_SEQUENCE, '')\n .replace(ESC_SEQUENCE, '')\n .replace(CONTROL_CHARACTER, '')\n .replace(DIRECTIONAL_CONTROL, '')\n .replace(/\\s+/gu, ' ')\n .trim()\n}\n\nexport function truncateTitleUtf8(input: string, maxBytes: number): string {\n if (!Number.isInteger(maxBytes) || maxBytes <= 0) throw new Error('maxBytes must be a positive integer')\n if (Buffer.byteLength(input, 'utf8') <= maxBytes) return input\n let used = 0\n let output = ''\n for (const character of input) {\n const bytes = Buffer.byteLength(character, 'utf8')\n if (used + bytes > maxBytes) break\n output += character\n used += bytes\n }\n return output\n}\n\n/** Same sanitizing rules as DSH 0.1.7-rc.2 `normalizeSessionTitle`. */\nexport function normalizeSessionTitle(input: string, maxBytes: number): string {\n return truncateTitleUtf8(cleanTitleText(input), maxBytes).trimEnd()\n}\n\nfunction isTitleType(value: string): value is TitleType {\n return (TITLE_TYPE_KEYS as readonly string[]).includes(value)\n}\n\nexport function resolveTitleLocale(\n mode: TitleLocaleMode,\n messages: readonly string[],\n preference?: string,\n): TitleLocale {\n if (mode !== 'auto') return mode\n\n const preferred = preference?.trim().toLowerCase().split('-')[0]\n if (preferred === 'zh' || preferred === 'en') return preferred\n\n const text = messages.join('\\n')\n if (/[\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]/u.test(text)) return 'en'\n return /\\p{Script=Han}/u.test(text) ? 'zh' : 'en'\n}\n\nfunction removeTitleSyntax(value: string): string {\n return value.replace(/[|`*_~#\\[\\]\"“”]/gu, ' ')\n}\n\nfunction truncateSummary(value: string, targetWords: number, targetCjkCharacters: number): string {\n const containsCjk = /[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]/u.test(value)\n if (containsCjk) {\n return Array.from(value.replace(/\\s+/gu, '')).slice(0, targetCjkCharacters).join('')\n }\n return value.split(/\\s+/u).filter(Boolean).slice(0, targetWords).join(' ')\n}\n\nexport function parseModelTitle(\n raw: string,\n targetWords: number,\n targetCjkCharacters: number,\n): ParsedTitle {\n let value: unknown\n try {\n value = JSON.parse(raw.trim())\n } catch (cause) {\n throw new Error('dsh-current-title: title model returned invalid JSON', { cause })\n }\n\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('dsh-current-title: title model response must be an object')\n }\n\n const candidate = value as Record<string, unknown>\n if (Object.keys(candidate).sort().join(',') !== 'summary,type') {\n throw new Error('dsh-current-title: title model response must contain only type and summary')\n }\n if (typeof candidate.type !== 'string' || !isTitleType(candidate.type)) {\n throw new Error('dsh-current-title: title model returned an unsupported type')\n }\n if (typeof candidate.summary !== 'string') {\n throw new Error('dsh-current-title: title model summary must be a string')\n }\n\n const normalized = normalizeSessionTitle(removeTitleSyntax(candidate.summary), Number.MAX_SAFE_INTEGER)\n const summary = truncateSummary(normalized, targetWords, targetCjkCharacters)\n if (summary.length === 0) {\n throw new Error('dsh-current-title: title model returned an empty summary')\n }\n\n return { type: candidate.type, summary }\n}\n\nexport function localMonthDay(now: Date): string {\n const month = String(now.getMonth() + 1).padStart(2, '0')\n const day = String(now.getDate()).padStart(2, '0')\n return `${month}${day}`\n}\n\nexport function formatTitle(parsed: ParsedTitle, locale: TitleLocale, now = new Date()): string {\n return `${localMonthDay(now)} | ${TITLE_TYPE_LABELS[locale][parsed.type]} | ${parsed.summary}`\n}\n","import { normalizeSessionTitle } from './output.ts'\nimport type { TitleMessage, TitleSnapshot, TitleSourceKind } from './contracts.ts'\n\nexport interface SessionEventLike {\n readonly type: string\n readonly seq: number\n readonly time?: number\n readonly data?: {\n readonly source?: { readonly kind?: string; readonly plugin?: string; readonly provider?: string }\n readonly content?: ReadonlyArray<{ readonly type?: string; readonly text?: string }>\n readonly title?: string\n readonly messageSeqs?: readonly number[]\n }\n}\n\nexport interface SessionLike {\n readonly id: string\n readonly seq?: number\n snapshotEvents(): readonly SessionEventLike[]\n append?(type: string, data: unknown): unknown\n}\n\nfunction textFromContent(content: ReadonlyArray<{ readonly type?: string; readonly text?: string }> | undefined): string {\n if (!Array.isArray(content)) return ''\n return content\n .filter(block => block && block.type === 'text' && typeof block.text === 'string')\n .map(block => block.text as string)\n .join('\\n')\n}\n\n/** Eligible human prompts only. Plugin/tool/model user-role rows are excluded. */\nexport function collectHumanMessages(events: readonly SessionEventLike[], throughSeq?: number): TitleMessage[] {\n const messages: TitleMessage[] = []\n for (const event of events) {\n if (throughSeq !== undefined && event.seq > throughSeq) break\n if (event.type !== 'user/message') continue\n if (event.data?.source?.kind !== 'user') continue\n const text = textFromContent(event.data.content)\n if (normalizeSessionTitle(text, Number.MAX_SAFE_INTEGER).length === 0) continue\n messages.push({ seq: event.seq, text })\n }\n return messages\n}\n\nexport function foldTitle(events: readonly SessionEventLike[]): TitleSnapshot | undefined {\n let latest: SessionEventLike | undefined\n for (const event of events) {\n if (event.type === 'session/title') latest = event\n }\n if (!latest?.data || typeof latest.data.title !== 'string') return undefined\n const kind = latest.data.source?.kind\n const sourceKind: TitleSourceKind = kind === 'user' || kind === 'fallback' || kind === 'provider' ? kind : 'provider'\n return {\n title: latest.data.title,\n messageSeqs: latest.data.messageSeqs ? [...latest.data.messageSeqs] : [],\n source: { kind: sourceKind, ...(typeof latest.data.source?.provider === 'string' ? { provider: latest.data.source.provider } : {}) },\n eventSeq: latest.seq,\n updatedAt: latest.time ?? 0,\n }\n}\n\nexport function userRenameNewerThan(events: readonly SessionEventLike[], watermarkSeq: number): boolean {\n const current = foldTitle(events)\n return Boolean(current && current.source.kind === 'user' && current.eventSeq > watermarkSeq)\n}\n\nexport function sourceVersionOf(sessionId: string, messages: readonly TitleMessage[]): string {\n const last = messages.at(-1)\n return `${sessionId}:${last?.seq ?? -1}:${messages.length}`\n}\n","import type { AiFeatureScope } from '@klarkxy/dsh-ai-services/contracts'\nimport type { GenerateConfig, TitleMessage } from './contracts.ts'\nimport { CONTRACT_VERSION, DEFAULT_MAX_OUTPUT_TOKENS, PROMPT_VERSION, PURPOSE_ID, SCHEMA_VERSION } from './contracts.ts'\nimport { frameMessages, selectRecentMessages } from './input.ts'\nimport { formatTitle, normalizeSessionTitle, parseModelTitle, resolveTitleLocale, type TitleLocaleMode } from './output.ts'\nimport { sourceVersionOf } from './messages.ts'\n\nexport interface GeneratedTitle {\n readonly title: string\n readonly messageSeqs: readonly number[]\n readonly model?: { readonly provider: string; readonly model: string }\n readonly sourceVersion: string\n}\n\nexport function systemPrompt(config: GenerateConfig): string {\n return [\n 'Name the current task in an AI coding-assistant session.',\n 'Prioritize the newest human messages. Older messages are context only and must not keep a completed task in the title.',\n 'Allowed type values: feature, fix, optimize, refactor, test, docs, release, config, explore, discuss.',\n 'Return exactly one JSON object: {\"type\":\"<type>\",\"summary\":\"<summary>\"}.',\n 'Return no Markdown, code fence, explanation, date, or extra key.',\n 'Keep the summary in the language of the recent messages.',\n `Aim for at most ${config.targetWords} non-CJK words or ${config.targetCjkCharacters} CJK characters.`,\n ].join('\\n')\n}\n\nexport function registerTitlePurpose(scope: AiFeatureScope): () => void {\n return scope.registerPurpose({\n id: PURPOSE_ID,\n label: '当前标题',\n defaultTarget: { kind: 'role', role: 'weak' },\n maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,\n })\n}\n\nexport async function generateCurrentTitle(options: {\n scope: AiFeatureScope\n config: GenerateConfig\n sessionId: string\n messages: readonly TitleMessage[]\n localePreference?: string\n localeMode?: TitleLocaleMode\n signal: AbortSignal\n isCurrent: () => boolean\n now?: Date\n}): Promise<GeneratedTitle> {\n options.signal.throwIfAborted()\n if (!options.scope.active || !options.isCurrent()) {\n throw Object.assign(new Error('dsh-current-title: generation is no longer current'), { code: 'superseded' })\n }\n const selected = selectRecentMessages(\n options.messages,\n options.config.maxRecentMessages,\n options.config.maxInputBytes,\n )\n if (selected.length === 0) throw new Error('dsh-current-title: at least one source message is required')\n\n const framedInput = frameMessages(selected)\n const locale = resolveTitleLocale(\n options.localeMode ?? options.config.locale,\n selected.map(message => message.text),\n options.localePreference,\n )\n const sourceVersion = sourceVersionOf(options.sessionId, selected)\n let settled = false\n const aborted = new Promise<never>((_, reject) => {\n const fail = () => {\n if (settled) return\n reject(options.signal.reason ?? new Error('dsh-current-title: generation cancelled'))\n }\n if (options.signal.aborted) { fail(); return }\n options.signal.addEventListener('abort', fail, { once: true })\n })\n let result: Awaited<ReturnType<AiFeatureScope['run']>>\n try {\n result = await Promise.race([\n options.scope.run({\n purpose: PURPOSE_ID,\n sessionId: options.sessionId,\n input: framedInput,\n system: systemPrompt(options.config),\n sourceVersion,\n contractVersion: CONTRACT_VERSION,\n promptVersion: PROMPT_VERSION,\n schemaVersion: SCHEMA_VERSION,\n signal: options.signal,\n isCurrent: () => options.scope.active && options.isCurrent(),\n priority: 'background',\n }),\n aborted,\n ])\n } finally {\n settled = true\n }\n options.signal.throwIfAborted()\n if (!options.scope.active || !options.isCurrent()) {\n throw Object.assign(new Error('dsh-current-title: generation is no longer current'), { code: 'superseded' })\n }\n if (result.receipt.status === 'cancelled' || result.receipt.status === 'superseded') {\n throw Object.assign(new Error('dsh-current-title: generation did not complete'), { code: result.receipt.status })\n }\n const parsed = parseModelTitle(result.text, options.config.targetWords, options.config.targetCjkCharacters)\n const title = normalizeSessionTitle(formatTitle(parsed, locale, options.now ?? new Date()), options.config.maxTitleBytes)\n if (!title) throw new Error('dsh-current-title: title model returned an empty summary')\n const route = result.receipt.route\n return {\n title,\n messageSeqs: selected.map(message => message.seq),\n ...(route?.provider && route.model ? { model: { provider: route.provider, model: route.model } } : {}),\n sourceVersion,\n }\n}\n","export interface OwnershipClaim {\n readonly id: string\n readonly dispose: () => void | Promise<void>\n}\n\nexport interface TitleSlot {\n owner(): string | undefined\n occupy(id: string): Promise<() => void | Promise<void>>\n}\n\n/** Exclusive title-provider ownership. Enable claims the slot; disable releases only while this id still holds it. */\nexport async function claimTitleSlot(slot: TitleSlot, id: string): Promise<OwnershipClaim> {\n const dispose = await slot.occupy(id)\n return { id, dispose }\n}\n\nexport async function releaseTitleSlot(\n slot: TitleSlot,\n claim: OwnershipClaim | undefined,\n): Promise<'released' | 'left-other-owner' | 'idle'> {\n if (!claim) return 'idle'\n if (slot.owner() !== claim.id) return 'left-other-owner'\n await claim.dispose()\n if (slot.owner() && slot.owner() !== claim.id) return 'left-other-owner'\n return 'released'\n}\n\nexport function occupiedProviderId(error: unknown): string | undefined {\n const message = error instanceof Error ? error.message : String(error)\n return /session-title provider \"([^\"]+)\" is already registered/.exec(message)?.[1]\n}\n","import { PROVIDER_ID } from './contracts.ts'\nimport { occupiedProviderId, type TitleSlot } from './ownership.ts'\n\nexport interface SessionTitleProvider {\n readonly id: string\n readonly automatic: 'first-prompt' | 'all-prompts'\n generate(request: {\n session: { id: string; snapshotEvents(): readonly unknown[] }\n messages: ReadonlyArray<{ seq: number; text: string }>\n route?: { provider: string; model: string }\n signal: AbortSignal\n }): Promise<{ title: string; messageSeqs: readonly number[]; model?: { provider: string; model: string } }>\n}\n\nexport interface SessionTitleServiceLike {\n register(provider: SessionTitleProvider): () => void | Promise<void>\n get(session: unknown): { title: string; source?: { kind?: string; provider?: string }; eventSeq?: number; updatedAt?: number; messageSeqs?: readonly number[] } | undefined\n refresh(session: unknown, signal?: AbortSignal): Promise<unknown>\n}\n\nexport interface LoaderEntryOptions {\n id?: string\n name?: string\n disabled?: boolean | null\n config?: unknown\n inject?: unknown\n group?: boolean | null\n}\n\nexport interface LoaderEntry {\n id: string\n options?: LoaderEntryOptions\n}\n\nexport interface LoaderFace {\n entries(): Iterable<LoaderEntry>\n update(id: string, options: { disabled?: boolean | null }): Promise<unknown> | unknown\n}\n\nexport interface Displacement {\n readonly id: string\n readonly name: string\n readonly identity: string\n}\n\nfunction catalogLookupId(entryId: string): string {\n const colon = entryId.indexOf(':')\n return colon === -1 ? entryId : entryId.slice(colon + 1)\n}\n\nfunction entryName(entry: LoaderEntry): string {\n return String(entry.options?.name ?? '')\n}\n\nexport function entryDisabled(entry: LoaderEntry): boolean {\n return entry.options?.disabled === true\n}\n\nexport function displacementIdentity(entry: LoaderEntry): string {\n const options = entry.options ?? {}\n const { disabled: _disabled, ...rest } = options\n return JSON.stringify({ id: entry.id, name: entryName(entry), rest })\n}\n\nexport function captureDisplacement(entry: LoaderEntry): Displacement {\n return { id: entry.id, name: entryName(entry), identity: displacementIdentity(entry) }\n}\n\nexport function displacementUnchanged(snapshot: Displacement, entry: LoaderEntry): boolean {\n return entry.id === snapshot.id\n && entryName(entry) === snapshot.name\n && displacementIdentity(entry) === snapshot.identity\n}\n\nexport function findEntryById(loader: LoaderFace, id: string): LoaderEntry | undefined {\n for (const entry of loader.entries()) {\n if (entry.id === id) return entry\n }\n return undefined\n}\n\nexport function findLoaderEntry(loader: LoaderFace, providerId: string): LoaderEntry | undefined {\n for (const entry of loader.entries()) {\n const name = entryName(entry)\n const ids = [entry.id, entry.options?.id, name, catalogLookupId(entry.id)]\n if (ids.some(value => value === providerId)) return entry\n if (name.endsWith(`/${providerId}`) || name.endsWith(providerId)) return entry\n if (providerId === 'session-title-first-prompt-llm' && catalogLookupId(entry.id) === 'session-title-llm') return entry\n }\n return undefined\n}\n\nexport async function restoreOwnDisplacement(loader: LoaderFace | undefined, snapshot: Displacement | undefined): Promise<'restored' | 'skipped'> {\n if (!loader || !snapshot) return 'skipped'\n const current = findEntryById(loader, snapshot.id)\n if (!current || !entryDisabled(current) || !displacementUnchanged(snapshot, current)) return 'skipped'\n await loader.update(snapshot.id, { disabled: false })\n return 'restored'\n}\n\nexport function createNativeTitleSlot(options: {\n sessionTitle: SessionTitleServiceLike\n loader: LoaderFace\n generate: SessionTitleProvider['generate']\n}): TitleSlot & { displacement?: Displacement } {\n let owner: string | undefined\n const slot: TitleSlot & { displacement?: Displacement } = {\n owner: () => owner,\n async occupy(id: string) {\n const provider: SessionTitleProvider = {\n id: PROVIDER_ID,\n automatic: 'all-prompts',\n generate: options.generate,\n }\n try {\n const dispose = options.sessionTitle.register(provider)\n owner = id\n return async () => {\n await dispose()\n if (owner === id) owner = undefined\n }\n } catch (error) {\n const occupied = occupiedProviderId(error)\n if (!occupied) throw error\n const entry = findLoaderEntry(options.loader, occupied)\n if (!entry || entryDisabled(entry)) throw error\n const snapshot = captureDisplacement(entry)\n await options.loader.update(entry.id, { disabled: true })\n const afterDisable = findEntryById(options.loader, entry.id)\n if (!afterDisable || !displacementUnchanged(snapshot, afterDisable)) throw error\n try {\n const dispose = options.sessionTitle.register(provider)\n slot.displacement = snapshot\n owner = id\n return async () => {\n await dispose()\n if (owner === id) owner = undefined\n }\n } catch (second) {\n if (findEntryById(options.loader, snapshot.id) && displacementUnchanged(snapshot, findEntryById(options.loader, snapshot.id)!)) {\n await options.loader.update(snapshot.id, { disabled: false })\n }\n throw second\n }\n }\n },\n }\n return slot\n}\n","import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'\nimport { z } from 'zod'\nimport { SETTINGS_KEY, isTitleLocaleMode, type TitleSettings } from './contracts.ts'\n\nexport { SETTINGS_KEY }\n\nexport const titleSettingsSchema = z.object({\n revision: z.number().int().nonnegative(),\n locale: z.enum(['auto', 'zh', 'en']),\n}).strict()\n\nexport const titleDomain = defineDomain({\n name: 'dsh_editor_current_title',\n version: 1,\n tables: {\n settings: domainTable<string, TitleSettings>(titleSettingsSchema),\n },\n})\n\nexport function parseTitleSettings(value: unknown): TitleSettings | undefined {\n const parsed = titleSettingsSchema.safeParse(value)\n if (!parsed.success) return undefined\n if (!isTitleLocaleMode(parsed.data.locale)) return undefined\n return parsed.data\n}\n\nexport function storedSettings(value: unknown, _fallbackLocale: TitleSettings['locale'] = 'auto'): TitleSettings {\n const parsed = parseTitleSettings(value)\n return { revision: parsed?.revision ?? 0, locale: 'auto' }\n}\n","import type { AiFeatureScope, AiServices, RpcResult } from '@klarkxy/dsh-ai-services/contracts'\nimport type { GenerateConfig, TitleSettings, TitleSnapshot, TitleStatus, TitleSupport } from './contracts.ts'\nimport { PROVIDER_ID, defaultGenerateConfig, isTitleLocaleMode } from './contracts.ts'\nimport { generateCurrentTitle, registerTitlePurpose } from './generate.ts'\nimport { collectHumanMessages, type SessionLike } from './messages.ts'\nimport {\n createNativeTitleSlot, restoreOwnDisplacement, type LoaderFace, type SessionTitleServiceLike,\n} from './native-slot.ts'\nimport { claimTitleSlot, releaseTitleSlot, type OwnershipClaim } from './ownership.ts'\nimport { storedSettings } from './storage.ts'\nimport type { TitleLocaleMode } from './output.ts'\n\nexport interface TitleSettingsStore {\n load(): TitleSettings | undefined\n save(settings: TitleSettings): Promise<void>\n}\n\nexport interface CurrentTitleHost {\n get(name: string): unknown\n}\n\nexport class CurrentTitleService {\n private settings: TitleSettings\n private config: GenerateConfig\n private scope?: AiFeatureScope\n private unregisterPurpose?: () => void\n private claim?: OwnershipClaim\n private nativeSlot?: ReturnType<typeof createNativeTitleSlot>\n private generation = 0\n private readonly inFlight = new Map<string, AbortController>()\n readonly support: TitleSupport\n\n constructor(private readonly options: {\n plugin: string\n ai?: AiServices\n sessionTitle?: SessionTitleServiceLike\n loader?: LoaderFace\n sessions?: { get(id: string): SessionLike | undefined }\n store?: TitleSettingsStore\n localePreference?: () => string | undefined\n initialLocale?: TitleLocaleMode\n now?: () => Date\n }) {\n const stored = storedSettings(options.store?.load(), options.initialLocale ?? 'auto')\n this.settings = stored\n this.config = defaultGenerateConfig(this.settings.locale)\n this.support = { nativeOwner: null, weOwn: false }\n }\n\n async start(): Promise<void> {\n const { ai, sessionTitle, loader, sessions } = this.options\n if (!ai || !sessionTitle?.register || !sessions || !loader) {\n this.support.weOwn = false\n this.support.limitation = 'required host services are missing'\n return\n }\n this.scope = ai.activate(this.options.plugin)\n this.unregisterPurpose = registerTitlePurpose(this.scope)\n this.nativeSlot = createNativeTitleSlot({\n sessionTitle,\n loader,\n generate: request => this.nativeGenerate(request),\n })\n try {\n this.claim = await claimTitleSlot(this.nativeSlot, PROVIDER_ID)\n this.support.weOwn = true\n this.support.nativeOwner = PROVIDER_ID\n delete this.support.limitation\n } catch (error) {\n this.support.weOwn = false\n this.support.nativeOwner = this.nativeSlot.owner() ?? null\n this.support.limitation = error instanceof Error ? error.message : 'native title provider slot is occupied'\n }\n }\n\n async dispose(): Promise<void> {\n this.generation += 1\n for (const controller of this.inFlight.values()) controller.abort(new Error('current-title disabled'))\n this.inFlight.clear()\n const snapshot = this.nativeSlot?.displacement\n const outcome = await releaseTitleSlot(this.nativeSlot ?? { owner: () => undefined, occupy: async () => () => {} }, this.claim)\n if (outcome === 'released') await restoreOwnDisplacement(this.options.loader, snapshot)\n this.claim = undefined\n this.support.weOwn = false\n this.support.nativeOwner = this.nativeSlot?.owner() ?? null\n if (outcome === 'left-other-owner') {\n this.support.limitation = 'another title provider owns the slot; previous owner was not restored'\n }\n this.unregisterPurpose?.()\n this.unregisterPurpose = undefined\n this.scope?.dispose()\n this.scope = undefined\n }\n\n status(sessionId?: string): TitleStatus {\n const session = sessionId && this.options.sessions ? this.options.sessions.get(sessionId) : undefined\n const snapshot = session ? this.read(session) : undefined\n return {\n settings: { ...this.settings },\n support: { ...this.support, nativeOwner: this.nativeSlot?.owner() ?? this.support.nativeOwner },\n ...(sessionId ? {\n session: {\n sessionId,\n title: snapshot?.title,\n sourceKind: snapshot?.source.kind,\n generating: this.inFlight.has(sessionId),\n pinned: snapshot?.source.kind === 'user',\n },\n } : {}),\n }\n }\n\n async updateLocale(_locale: TitleLocaleMode, expectedRevision: number): Promise<TitleSettings> {\n if (this.settings.revision !== expectedRevision) {\n throw Object.assign(new Error('current-title settings changed'), { code: 'revision' })\n }\n const next = { revision: this.settings.revision + 1, locale: 'auto' as const }\n if (this.options.store) await this.options.store.save(next)\n this.settings = next\n this.config = { ...this.config, locale: 'auto' }\n return { ...this.settings }\n }\n\n async regenerate(sessionId: string, signal: AbortSignal): Promise<TitleSnapshot | undefined> {\n if (!this.support.weOwn) throw Object.assign(new Error('title provider is inactive'), { code: 'unavailable' })\n const session = this.options.sessions?.get(sessionId)\n if (!session) throw Object.assign(new Error('session not found'), { code: 'not-found' })\n await this.options.sessionTitle?.refresh(session, signal)\n return this.read(session)\n }\n\n read(session: SessionLike): TitleSnapshot | undefined {\n const snapshot = this.options.sessionTitle?.get(session)\n if (!snapshot?.title) return undefined\n const kind = snapshot.source?.kind\n return {\n title: snapshot.title,\n messageSeqs: snapshot.messageSeqs ? [...snapshot.messageSeqs] : [],\n source: {\n kind: kind === 'user' || kind === 'fallback' || kind === 'provider' ? kind : 'provider',\n ...(typeof snapshot.source?.provider === 'string' ? { provider: snapshot.source.provider } : {}),\n },\n eventSeq: Number(snapshot.eventSeq ?? 0),\n updatedAt: snapshot.updatedAt ?? 0,\n }\n }\n\n private async nativeGenerate(request: {\n session: { id: string; snapshotEvents(): readonly unknown[] }\n messages: ReadonlyArray<{ seq: number; text: string }>\n signal: AbortSignal\n }) {\n const scope = this.scope\n if (!scope) throw new Error('dsh-current-title: aiServices is not available')\n const generation = this.generation\n const controller = new AbortController()\n this.inFlight.get(request.session.id)?.abort(new Error('newer title generation superseded older work'))\n this.inFlight.set(request.session.id, controller)\n try {\n const combined = AbortSignal.any([request.signal, controller.signal, scope.signal])\n const session = this.options.sessions?.get(request.session.id)\n const messages = collectHumanMessages(\n request.messages.map(message => ({\n type: 'user/message',\n seq: message.seq,\n data: { source: { kind: 'user' }, content: [{ type: 'text', text: message.text }] },\n })),\n )\n const generated = await generateCurrentTitle({\n scope,\n config: this.config,\n sessionId: request.session.id,\n messages: messages.length > 0 ? messages : [...request.messages],\n localePreference: this.options.localePreference?.(),\n signal: combined,\n isCurrent: () => this.generation === generation && scope.active && this.support.weOwn\n && (!session || this.options.sessions?.get(request.session.id) === session),\n now: this.options.now?.(),\n })\n return { title: generated.title, messageSeqs: generated.messageSeqs, model: generated.model }\n } finally {\n if (this.inFlight.get(request.session.id) === controller) this.inFlight.delete(request.session.id)\n }\n }\n}\n\nexport function hostLocalePreference(host: CurrentTitleHost): string | undefined {\n try {\n const settings = host.get('settings') as { get?(namespace: string): unknown } | undefined\n const value = settings?.get?.('locale')\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined\n const preference = (value as Record<string, unknown>).preference\n return typeof preference === 'string' ? preference : undefined\n } catch {\n return undefined\n }\n}\n\nexport async function handleRpc(service: CurrentTitleService, endpoint: string, payload: unknown, signal: AbortSignal): Promise<RpcResult> {\n signal.throwIfAborted()\n const body = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : {}\n if (endpoint === 'status') {\n return { ok: true, value: service.status(typeof body.sessionId === 'string' ? body.sessionId : undefined) }\n }\n if (endpoint === 'settings') {\n if (!isTitleLocaleMode(body.locale) || typeof body.expectedRevision !== 'number') {\n return { ok: false, error: { code: 'bad-request', message: '标题语言设置无效。' } }\n }\n try {\n return { ok: true, value: await service.updateLocale(body.locale, body.expectedRevision) }\n } catch (error) {\n const code = error instanceof Error && 'code' in error ? String((error as { code?: string }).code) : 'failed'\n if (code === 'revision') return { ok: false, error: { code, message: '设置已更新,请刷新后重试。' } }\n return { ok: false, error: { code: 'failed', message: '无法保存标题设置。' } }\n }\n }\n if (endpoint === 'regenerate') {\n if (typeof body.sessionId !== 'string' || !body.sessionId) {\n return { ok: false, error: { code: 'bad-request', message: '缺少会话。' } }\n }\n try {\n const snapshot = await service.regenerate(body.sessionId, signal)\n return { ok: true, value: snapshot ?? null }\n } catch (error) {\n const code = error instanceof Error && 'code' in error ? String((error as { code?: string }).code) : 'failed'\n if (code === 'not-found') return { ok: false, error: { code, message: '找不到该会话。' } }\n if (code === 'unavailable') return { ok: false, error: { code, message: '标题服务未就绪。' } }\n if (signal.aborted) return { ok: false, error: { code: 'cancelled', message: '已取消。' } }\n return { ok: false, error: { code: 'failed', message: '标题生成失败。' } }\n }\n }\n return { ok: false, error: { code: 'bad-request', message: '未知操作。' } }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport type { AiServices } from '@klarkxy/dsh-ai-services/contracts'\nimport { registerHostRpc, type HostRpcContext } from '@klarkxy/dsh-ai-services/host-rpc'\nimport { isTitleLocaleMode, PLUGIN_NAME, RPC_CHANNEL, SETTINGS_KEY, type TitleLocaleMode } from './contracts.ts'\nimport { CurrentTitleService, handleRpc, hostLocalePreference } from './service.ts'\nimport type { LoaderFace, SessionTitleServiceLike } from './native-slot.ts'\nimport type { SessionLike } from './messages.ts'\nimport { parseTitleSettings, titleDomain } from './storage.ts'\n\nexport const name = PLUGIN_NAME\nexport const inject = ['sessionTitle', 'aiServices', 'sessions', 'loader', 'storageDomain', 'connection', 'webServer'] as const\nexport { CurrentTitleService }\nexport { selectRecentMessages, frameMessages } from './input.ts'\nexport { parseModelTitle, formatTitle, resolveTitleLocale } from './output.ts'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context { currentTitle: CurrentTitleService }\n}\n\ntype DomainHandle = {\n table(name: 'settings'): {\n get(key: string): unknown\n put(key: string, value: unknown): Promise<void>\n }\n close(): Promise<void>\n}\n\ntype Host = Context & HostRpcContext & {\n sessionTitle: SessionTitleServiceLike\n aiServices: AiServices\n sessions: { get(id: string): SessionLike | undefined }\n loader: LoaderFace\n storageDomain: { open: (spec: typeof titleDomain) => Promise<DomainHandle> }\n}\n\nexport async function apply(ctx: Context, config: { locale?: TitleLocaleMode } = {}): Promise<void> {\n const host = ctx as Host\n const domain = await host.storageDomain.open(titleDomain)\n const table = domain.table('settings')\n const locale = isTitleLocaleMode(config.locale) ? config.locale : 'auto'\n const service = new CurrentTitleService({\n plugin: PLUGIN_NAME,\n ai: host.aiServices,\n sessionTitle: host.sessionTitle,\n loader: host.loader,\n sessions: host.sessions,\n store: {\n load: () => parseTitleSettings(table.get(SETTINGS_KEY)),\n save: settings => table.put(SETTINGS_KEY, settings),\n },\n initialLocale: locale,\n localePreference: () => hostLocalePreference(host),\n })\n await service.start()\n ctx.provide('currentTitle', service)\n ctx.effect(() => async () => { await service.dispose(); await domain.close() }, 'current-title.lifecycle')\n ctx.effect(() => registerHostRpc(host, RPC_CHANNEL, (endpoint, payload, signal) => handleRpc(service, endpoint, payload, signal)), 'current-title.rpc')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/input.ts","../src/output.ts","../src/messages.ts","../src/generate.ts","../src/ownership.ts","../src/native-slot.ts","../src/storage.ts","../src/service.ts","../src/index.ts"],"sourcesContent":["import type { TitleMessage } from './contracts.ts'\n\nexport const FRAME_PREFIX =\n 'Choose the current task from these recent human messages, ordered oldest to newest:\\n'\n\nexport function frameMessages(messages: readonly TitleMessage[]): string {\n return `${FRAME_PREFIX}${JSON.stringify(messages)}`\n}\n\nexport function utf8Bytes(value: string): number {\n return Buffer.byteLength(value, 'utf8')\n}\n\nfunction middleSlice(codePoints: readonly string[], kept: number): string {\n if (kept >= codePoints.length) return codePoints.join('')\n if (kept <= 0) return ''\n if (kept === 1) return '…'\n\n const contentPoints = kept - 1\n const headCount = Math.ceil(contentPoints / 2)\n const tailCount = Math.floor(contentPoints / 2)\n return `${codePoints.slice(0, headCount).join('')}…${\n tailCount === 0 ? '' : codePoints.slice(-tailCount).join('')\n }`\n}\n\nexport function fitSingleMessage(message: TitleMessage, maxInputBytes: number): TitleMessage {\n const empty = { ...message, text: '' }\n if (utf8Bytes(frameMessages([empty])) > maxInputBytes) {\n throw new Error('dsh-current-title: maxInputBytes is too small for the input envelope')\n }\n if (utf8Bytes(frameMessages([message])) <= maxInputBytes) return message\n\n const points = Array.from(message.text)\n let low = 0\n let high = points.length\n let best = ''\n\n while (low <= high) {\n const kept = Math.floor((low + high) / 2)\n const text = middleSlice(points, kept)\n if (utf8Bytes(frameMessages([{ ...message, text }])) <= maxInputBytes) {\n best = text\n low = kept + 1\n } else {\n high = kept - 1\n }\n }\n\n return { ...message, text: best }\n}\n\nexport function selectRecentMessages(\n messages: readonly TitleMessage[],\n maxRecentMessages: number,\n maxInputBytes: number,\n): TitleMessage[] {\n if (messages.length === 0) return []\n\n const selected = messages.slice(-maxRecentMessages).map(message => ({ ...message }))\n while (selected.length > 1 && utf8Bytes(frameMessages(selected)) > maxInputBytes) {\n selected.shift()\n }\n\n if (utf8Bytes(frameMessages(selected)) > maxInputBytes) {\n selected[0] = fitSingleMessage(selected[0]!, maxInputBytes)\n }\n\n return selected\n}\n","export const TITLE_TYPE_KEYS = [\n 'feature',\n 'fix',\n 'optimize',\n 'refactor',\n 'test',\n 'docs',\n 'release',\n 'config',\n 'explore',\n 'discuss',\n] as const\n\nexport const TITLE_LOCALES = ['zh', 'en'] as const\nexport const TITLE_LOCALE_MODES = ['auto', ...TITLE_LOCALES] as const\n\nexport type TitleType = (typeof TITLE_TYPE_KEYS)[number]\nexport type TitleLocale = (typeof TITLE_LOCALES)[number]\nexport type TitleLocaleMode = (typeof TITLE_LOCALE_MODES)[number]\n\nconst TITLE_TYPE_LABELS: Record<TitleLocale, Record<TitleType, string>> = {\n zh: {\n feature: '功能',\n fix: '修复',\n optimize: '优化',\n refactor: '重构',\n test: '测试',\n docs: '文档',\n release: '发布',\n config: '配置',\n explore: '探索',\n discuss: '讨论',\n },\n en: {\n feature: 'Feature',\n fix: 'Fix',\n optimize: 'Optimize',\n refactor: 'Refactor',\n test: 'Test',\n docs: 'Docs',\n release: 'Release',\n config: 'Config',\n explore: 'Explore',\n discuss: 'Discussion',\n },\n}\n\nexport interface ParsedTitle {\n type: TitleType\n summary: string\n}\n\nconst OSC_SEQUENCE = /(?:\\u001B\\]|\\u009D)(?:(?!\\u0007|\\u001B\\\\)[\\s\\S])*(?:\\u0007|\\u001B\\\\|$)/gu\nconst CSI_SEQUENCE = /(?:\\u001B\\[|\\u009B)[0-?]*[ -/]*[@-~]/gu\nconst ESC_SEQUENCE = /\\u001B[@-_]/gu\nconst CONTROL_CHARACTER = /[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u009F]/gu\nconst DIRECTIONAL_CONTROL = /[\\u200B\\u200E\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u206F\\uFEFF]/gu\n\nfunction cleanTitleText(input: string): string {\n return input\n .replace(OSC_SEQUENCE, '')\n .replace(CSI_SEQUENCE, '')\n .replace(ESC_SEQUENCE, '')\n .replace(CONTROL_CHARACTER, '')\n .replace(DIRECTIONAL_CONTROL, '')\n .replace(/\\s+/gu, ' ')\n .trim()\n}\n\nexport function truncateTitleUtf8(input: string, maxBytes: number): string {\n if (!Number.isInteger(maxBytes) || maxBytes <= 0) throw new Error('maxBytes must be a positive integer')\n if (Buffer.byteLength(input, 'utf8') <= maxBytes) return input\n let used = 0\n let output = ''\n for (const character of input) {\n const bytes = Buffer.byteLength(character, 'utf8')\n if (used + bytes > maxBytes) break\n output += character\n used += bytes\n }\n return output\n}\n\n/** Same sanitizing rules as DSH 0.1.7-rc.2 `normalizeSessionTitle`. */\nexport function normalizeSessionTitle(input: string, maxBytes: number): string {\n return truncateTitleUtf8(cleanTitleText(input), maxBytes).trimEnd()\n}\n\nfunction isTitleType(value: string): value is TitleType {\n return (TITLE_TYPE_KEYS as readonly string[]).includes(value)\n}\n\nexport function resolveTitleLocale(\n mode: TitleLocaleMode,\n messages: readonly string[],\n preference?: string,\n): TitleLocale {\n if (mode !== 'auto') return mode\n\n const preferred = preference?.trim().toLowerCase().split('-')[0]\n if (preferred === 'zh' || preferred === 'en') return preferred\n\n const text = messages.join('\\n')\n if (/[\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]/u.test(text)) return 'en'\n return /\\p{Script=Han}/u.test(text) ? 'zh' : 'en'\n}\n\nfunction removeTitleSyntax(value: string): string {\n return value.replace(/[|`*_~#\\[\\]\"“”]/gu, ' ')\n}\n\nfunction truncateSummary(value: string, targetWords: number, targetCjkCharacters: number): string {\n const containsCjk = /[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]/u.test(value)\n if (containsCjk) {\n return Array.from(value.replace(/\\s+/gu, '')).slice(0, targetCjkCharacters).join('')\n }\n return value.split(/\\s+/u).filter(Boolean).slice(0, targetWords).join(' ')\n}\n\nexport function parseModelTitle(\n raw: string,\n targetWords: number,\n targetCjkCharacters: number,\n): ParsedTitle {\n let value: unknown\n try {\n value = JSON.parse(raw.trim())\n } catch (cause) {\n throw new Error('dsh-current-title: title model returned invalid JSON', { cause })\n }\n\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('dsh-current-title: title model response must be an object')\n }\n\n const candidate = value as Record<string, unknown>\n if (Object.keys(candidate).sort().join(',') !== 'summary,type') {\n throw new Error('dsh-current-title: title model response must contain only type and summary')\n }\n if (typeof candidate.type !== 'string' || !isTitleType(candidate.type)) {\n throw new Error('dsh-current-title: title model returned an unsupported type')\n }\n if (typeof candidate.summary !== 'string') {\n throw new Error('dsh-current-title: title model summary must be a string')\n }\n\n const normalized = normalizeSessionTitle(removeTitleSyntax(candidate.summary), Number.MAX_SAFE_INTEGER)\n const summary = truncateSummary(normalized, targetWords, targetCjkCharacters)\n if (summary.length === 0) {\n throw new Error('dsh-current-title: title model returned an empty summary')\n }\n\n return { type: candidate.type, summary }\n}\n\nexport function localMonthDay(now: Date): string {\n const month = String(now.getMonth() + 1).padStart(2, '0')\n const day = String(now.getDate()).padStart(2, '0')\n return `${month}${day}`\n}\n\nexport function formatTitle(parsed: ParsedTitle, locale: TitleLocale, now = new Date()): string {\n return `${localMonthDay(now)} | ${TITLE_TYPE_LABELS[locale][parsed.type]} | ${parsed.summary}`\n}\n","import { normalizeSessionTitle } from './output.ts'\nimport type { TitleMessage, TitleSnapshot, TitleSourceKind } from './contracts.ts'\n\nexport interface SessionEventLike {\n readonly type: string\n readonly seq: number\n readonly time?: number\n readonly data?: {\n readonly source?: { readonly kind?: string; readonly plugin?: string; readonly provider?: string }\n readonly content?: ReadonlyArray<{ readonly type?: string; readonly text?: string }>\n readonly title?: string\n readonly messageSeqs?: readonly number[]\n }\n}\n\nexport interface SessionLike {\n readonly id: string\n readonly seq?: number\n snapshotEvents(): readonly SessionEventLike[]\n append?(type: string, data: unknown): unknown\n}\n\nfunction textFromContent(content: ReadonlyArray<{ readonly type?: string; readonly text?: string }> | undefined): string {\n if (!Array.isArray(content)) return ''\n return content\n .filter(block => block && block.type === 'text' && typeof block.text === 'string')\n .map(block => block.text as string)\n .join('\\n')\n}\n\n/** Eligible human prompts only. Plugin/tool/model user-role rows are excluded. */\nexport function collectHumanMessages(events: readonly SessionEventLike[], throughSeq?: number): TitleMessage[] {\n const messages: TitleMessage[] = []\n for (const event of events) {\n if (throughSeq !== undefined && event.seq > throughSeq) break\n if (event.type !== 'user/message') continue\n if (event.data?.source?.kind !== 'user') continue\n const text = textFromContent(event.data.content)\n if (normalizeSessionTitle(text, Number.MAX_SAFE_INTEGER).length === 0) continue\n messages.push({ seq: event.seq, text })\n }\n return messages\n}\n\nexport function foldTitle(events: readonly SessionEventLike[]): TitleSnapshot | undefined {\n let latest: SessionEventLike | undefined\n for (const event of events) {\n if (event.type === 'session/title') latest = event\n }\n if (!latest?.data || typeof latest.data.title !== 'string') return undefined\n const kind = latest.data.source?.kind\n const sourceKind: TitleSourceKind = kind === 'user' || kind === 'fallback' || kind === 'provider' ? kind : 'provider'\n return {\n title: latest.data.title,\n messageSeqs: latest.data.messageSeqs ? [...latest.data.messageSeqs] : [],\n source: { kind: sourceKind, ...(typeof latest.data.source?.provider === 'string' ? { provider: latest.data.source.provider } : {}) },\n eventSeq: latest.seq,\n updatedAt: latest.time ?? 0,\n }\n}\n\nexport function userRenameNewerThan(events: readonly SessionEventLike[], watermarkSeq: number): boolean {\n const current = foldTitle(events)\n return Boolean(current && current.source.kind === 'user' && current.eventSeq > watermarkSeq)\n}\n\nexport function sourceVersionOf(sessionId: string, messages: readonly TitleMessage[]): string {\n const last = messages.at(-1)\n return `${sessionId}:${last?.seq ?? -1}:${messages.length}`\n}\n","import type { AiFeatureScope } from '@klarkxy/dsh-ai-services/contracts'\nimport type { GenerateConfig, TitleMessage } from './contracts.ts'\nimport { CONTRACT_VERSION, DEFAULT_MAX_OUTPUT_TOKENS, PROMPT_VERSION, PURPOSE_ID, SCHEMA_VERSION } from './contracts.ts'\nimport { frameMessages, selectRecentMessages } from './input.ts'\nimport { formatTitle, normalizeSessionTitle, parseModelTitle, resolveTitleLocale, type TitleLocaleMode } from './output.ts'\nimport { sourceVersionOf } from './messages.ts'\n\nexport interface GeneratedTitle {\n readonly title: string\n readonly messageSeqs: readonly number[]\n readonly model?: { readonly provider: string; readonly model: string }\n readonly sourceVersion: string\n}\n\nexport function systemPrompt(config: GenerateConfig): string {\n return [\n 'Name the current task in an AI coding-assistant session.',\n 'Prioritize the newest human messages. Older messages are context only and must not keep a completed task in the title.',\n 'Allowed type values: feature, fix, optimize, refactor, test, docs, release, config, explore, discuss.',\n 'Return exactly one JSON object: {\"type\":\"<type>\",\"summary\":\"<summary>\"}.',\n 'Return no Markdown, code fence, explanation, date, or extra key.',\n 'Keep the summary in the language of the recent messages.',\n `Aim for at most ${config.targetWords} non-CJK words or ${config.targetCjkCharacters} CJK characters.`,\n ].join('\\n')\n}\n\nexport function registerTitlePurpose(scope: AiFeatureScope): () => void {\n return scope.registerPurpose({\n id: PURPOSE_ID,\n label: '自动标题',\n defaultTarget: { kind: 'role', role: 'weak' },\n maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,\n })\n}\n\nexport async function generateCurrentTitle(options: {\n scope: AiFeatureScope\n config: GenerateConfig\n sessionId: string\n messages: readonly TitleMessage[]\n localePreference?: string\n localeMode?: TitleLocaleMode\n signal: AbortSignal\n isCurrent: () => boolean\n now?: Date\n}): Promise<GeneratedTitle> {\n options.signal.throwIfAborted()\n if (!options.scope.active || !options.isCurrent()) {\n throw Object.assign(new Error('dsh-current-title: generation is no longer current'), { code: 'superseded' })\n }\n const selected = selectRecentMessages(\n options.messages,\n options.config.maxRecentMessages,\n options.config.maxInputBytes,\n )\n if (selected.length === 0) throw new Error('dsh-current-title: at least one source message is required')\n\n const framedInput = frameMessages(selected)\n const locale = resolveTitleLocale(\n options.localeMode ?? options.config.locale,\n selected.map(message => message.text),\n options.localePreference,\n )\n const sourceVersion = sourceVersionOf(options.sessionId, selected)\n let settled = false\n const aborted = new Promise<never>((_, reject) => {\n const fail = () => {\n if (settled) return\n reject(options.signal.reason ?? new Error('dsh-current-title: generation cancelled'))\n }\n if (options.signal.aborted) { fail(); return }\n options.signal.addEventListener('abort', fail, { once: true })\n })\n let result: Awaited<ReturnType<AiFeatureScope['run']>>\n try {\n result = await Promise.race([\n options.scope.run({\n purpose: PURPOSE_ID,\n sessionId: options.sessionId,\n input: framedInput,\n system: systemPrompt(options.config),\n sourceVersion,\n contractVersion: CONTRACT_VERSION,\n promptVersion: PROMPT_VERSION,\n schemaVersion: SCHEMA_VERSION,\n signal: options.signal,\n isCurrent: () => options.scope.active && options.isCurrent(),\n priority: 'background',\n }),\n aborted,\n ])\n } finally {\n settled = true\n }\n options.signal.throwIfAborted()\n if (!options.scope.active || !options.isCurrent()) {\n throw Object.assign(new Error('dsh-current-title: generation is no longer current'), { code: 'superseded' })\n }\n if (result.receipt.status === 'cancelled' || result.receipt.status === 'superseded') {\n throw Object.assign(new Error('dsh-current-title: generation did not complete'), { code: result.receipt.status })\n }\n const parsed = parseModelTitle(result.text, options.config.targetWords, options.config.targetCjkCharacters)\n const title = normalizeSessionTitle(formatTitle(parsed, locale, options.now ?? new Date()), options.config.maxTitleBytes)\n if (!title) throw new Error('dsh-current-title: title model returned an empty summary')\n const route = result.receipt.route\n return {\n title,\n messageSeqs: selected.map(message => message.seq),\n ...(route?.provider && route.model ? { model: { provider: route.provider, model: route.model } } : {}),\n sourceVersion,\n }\n}\n","export interface OwnershipClaim {\n readonly id: string\n readonly dispose: () => void | Promise<void>\n}\n\nexport interface TitleSlot {\n owner(): string | undefined\n occupy(id: string): Promise<() => void | Promise<void>>\n}\n\n/** Exclusive title-provider ownership. Enable claims the slot; disable releases only while this id still holds it. */\nexport async function claimTitleSlot(slot: TitleSlot, id: string): Promise<OwnershipClaim> {\n const dispose = await slot.occupy(id)\n return { id, dispose }\n}\n\nexport async function releaseTitleSlot(\n slot: TitleSlot,\n claim: OwnershipClaim | undefined,\n): Promise<'released' | 'left-other-owner' | 'idle'> {\n if (!claim) return 'idle'\n if (slot.owner() !== claim.id) return 'left-other-owner'\n await claim.dispose()\n if (slot.owner() && slot.owner() !== claim.id) return 'left-other-owner'\n return 'released'\n}\n\nexport function occupiedProviderId(error: unknown): string | undefined {\n const message = error instanceof Error ? error.message : String(error)\n return /session-title provider \"([^\"]+)\" is already registered/.exec(message)?.[1]\n}\n","import { PROVIDER_ID } from './contracts.ts'\nimport { occupiedProviderId, type TitleSlot } from './ownership.ts'\n\nexport interface SessionTitleProvider {\n readonly id: string\n readonly automatic: 'first-prompt' | 'all-prompts'\n generate(request: {\n session: { id: string; snapshotEvents(): readonly unknown[] }\n messages: ReadonlyArray<{ seq: number; text: string }>\n route?: { provider: string; model: string }\n signal: AbortSignal\n }): Promise<{ title: string; messageSeqs: readonly number[]; model?: { provider: string; model: string } }>\n}\n\nexport interface SessionTitleServiceLike {\n register(provider: SessionTitleProvider): () => void | Promise<void>\n get(session: unknown): { title: string; source?: { kind?: string; provider?: string }; eventSeq?: number; updatedAt?: number; messageSeqs?: readonly number[] } | undefined\n refresh(session: unknown, signal?: AbortSignal): Promise<unknown>\n}\n\nexport interface LoaderEntryOptions {\n id?: string\n name?: string\n disabled?: boolean | null\n config?: unknown\n inject?: unknown\n group?: boolean | null\n}\n\nexport interface LoaderEntry {\n id: string\n options?: LoaderEntryOptions\n}\n\nexport interface LoaderFace {\n entries(): Iterable<LoaderEntry>\n update(id: string, options: { disabled?: boolean | null }): Promise<unknown> | unknown\n}\n\nexport interface Displacement {\n readonly id: string\n readonly name: string\n readonly identity: string\n}\n\nfunction catalogLookupId(entryId: string): string {\n const colon = entryId.indexOf(':')\n return colon === -1 ? entryId : entryId.slice(colon + 1)\n}\n\nfunction entryName(entry: LoaderEntry): string {\n return String(entry.options?.name ?? '')\n}\n\nexport function entryDisabled(entry: LoaderEntry): boolean {\n return entry.options?.disabled === true\n}\n\nexport function displacementIdentity(entry: LoaderEntry): string {\n const options = entry.options ?? {}\n const { disabled: _disabled, ...rest } = options\n return JSON.stringify({ id: entry.id, name: entryName(entry), rest })\n}\n\nexport function captureDisplacement(entry: LoaderEntry): Displacement {\n return { id: entry.id, name: entryName(entry), identity: displacementIdentity(entry) }\n}\n\nexport function displacementUnchanged(snapshot: Displacement, entry: LoaderEntry): boolean {\n return entry.id === snapshot.id\n && entryName(entry) === snapshot.name\n && displacementIdentity(entry) === snapshot.identity\n}\n\nexport function findEntryById(loader: LoaderFace, id: string): LoaderEntry | undefined {\n for (const entry of loader.entries()) {\n if (entry.id === id) return entry\n }\n return undefined\n}\n\nexport function findLoaderEntry(loader: LoaderFace, providerId: string): LoaderEntry | undefined {\n for (const entry of loader.entries()) {\n const name = entryName(entry)\n const ids = [entry.id, entry.options?.id, name, catalogLookupId(entry.id)]\n if (ids.some(value => value === providerId)) return entry\n if (name.endsWith(`/${providerId}`) || name.endsWith(providerId)) return entry\n if (providerId === 'session-title-first-prompt-llm' && catalogLookupId(entry.id) === 'session-title-llm') return entry\n }\n return undefined\n}\n\nexport async function restoreOwnDisplacement(loader: LoaderFace | undefined, snapshot: Displacement | undefined): Promise<'restored' | 'skipped'> {\n if (!loader || !snapshot) return 'skipped'\n const current = findEntryById(loader, snapshot.id)\n if (!current || !entryDisabled(current) || !displacementUnchanged(snapshot, current)) return 'skipped'\n await loader.update(snapshot.id, { disabled: false })\n return 'restored'\n}\n\nexport function createNativeTitleSlot(options: {\n sessionTitle: SessionTitleServiceLike\n loader: LoaderFace\n generate: SessionTitleProvider['generate']\n}): TitleSlot & { displacement?: Displacement } {\n let owner: string | undefined\n const slot: TitleSlot & { displacement?: Displacement } = {\n owner: () => owner,\n async occupy(id: string) {\n const provider: SessionTitleProvider = {\n id: PROVIDER_ID,\n automatic: 'all-prompts',\n generate: options.generate,\n }\n try {\n const dispose = options.sessionTitle.register(provider)\n owner = id\n return async () => {\n await dispose()\n if (owner === id) owner = undefined\n }\n } catch (error) {\n const occupied = occupiedProviderId(error)\n if (!occupied) throw error\n const entry = findLoaderEntry(options.loader, occupied)\n if (!entry || entryDisabled(entry)) throw error\n const snapshot = captureDisplacement(entry)\n await options.loader.update(entry.id, { disabled: true })\n const afterDisable = findEntryById(options.loader, entry.id)\n if (!afterDisable || !displacementUnchanged(snapshot, afterDisable)) throw error\n try {\n const dispose = options.sessionTitle.register(provider)\n slot.displacement = snapshot\n owner = id\n return async () => {\n await dispose()\n if (owner === id) owner = undefined\n }\n } catch (second) {\n if (findEntryById(options.loader, snapshot.id) && displacementUnchanged(snapshot, findEntryById(options.loader, snapshot.id)!)) {\n await options.loader.update(snapshot.id, { disabled: false })\n }\n throw second\n }\n }\n },\n }\n return slot\n}\n","import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'\nimport { z } from 'zod'\nimport { SETTINGS_KEY, isTitleLocaleMode, type TitleSettings } from './contracts.ts'\n\nexport { SETTINGS_KEY }\n\nexport const titleSettingsSchema = z.object({\n revision: z.number().int().nonnegative(),\n locale: z.enum(['auto', 'zh', 'en']),\n}).strict()\n\nexport const titleDomain = defineDomain({\n name: 'dsh_editor_current_title',\n version: 1,\n tables: {\n settings: domainTable<string, TitleSettings>(titleSettingsSchema),\n },\n})\n\nexport function parseTitleSettings(value: unknown): TitleSettings | undefined {\n const parsed = titleSettingsSchema.safeParse(value)\n if (!parsed.success) return undefined\n if (!isTitleLocaleMode(parsed.data.locale)) return undefined\n return parsed.data\n}\n\nexport function storedSettings(value: unknown, _fallbackLocale: TitleSettings['locale'] = 'auto'): TitleSettings {\n const parsed = parseTitleSettings(value)\n return { revision: parsed?.revision ?? 0, locale: 'auto' }\n}\n","import type { AiFeatureScope, AiServices, RpcResult } from '@klarkxy/dsh-ai-services/contracts'\nimport type { GenerateConfig, TitleSettings, TitleSnapshot, TitleStatus, TitleSupport } from './contracts.ts'\nimport { PROVIDER_ID, defaultGenerateConfig, isTitleLocaleMode } from './contracts.ts'\nimport { generateCurrentTitle, registerTitlePurpose } from './generate.ts'\nimport { collectHumanMessages, type SessionLike } from './messages.ts'\nimport {\n createNativeTitleSlot, restoreOwnDisplacement, type LoaderFace, type SessionTitleServiceLike,\n} from './native-slot.ts'\nimport { claimTitleSlot, releaseTitleSlot, type OwnershipClaim } from './ownership.ts'\nimport { storedSettings } from './storage.ts'\nimport type { TitleLocaleMode } from './output.ts'\n\nexport interface TitleSettingsStore {\n load(): TitleSettings | undefined\n save(settings: TitleSettings): Promise<void>\n}\n\nexport interface CurrentTitleHost {\n get(name: string): unknown\n}\n\nexport class CurrentTitleService {\n private settings: TitleSettings\n private config: GenerateConfig\n private scope?: AiFeatureScope\n private unregisterPurpose?: () => void\n private claim?: OwnershipClaim\n private nativeSlot?: ReturnType<typeof createNativeTitleSlot>\n private generation = 0\n private readonly inFlight = new Map<string, AbortController>()\n readonly support: TitleSupport\n\n constructor(private readonly options: {\n plugin: string\n ai?: AiServices\n sessionTitle?: SessionTitleServiceLike\n loader?: LoaderFace\n sessions?: { get(id: string): SessionLike | undefined }\n store?: TitleSettingsStore\n localePreference?: () => string | undefined\n initialLocale?: TitleLocaleMode\n now?: () => Date\n }) {\n const stored = storedSettings(options.store?.load(), options.initialLocale ?? 'auto')\n this.settings = stored\n this.config = defaultGenerateConfig(this.settings.locale)\n this.support = { nativeOwner: null, weOwn: false }\n }\n\n async start(): Promise<void> {\n const { ai, sessionTitle, loader, sessions } = this.options\n if (!ai || !sessionTitle?.register || !sessions || !loader) {\n this.support.weOwn = false\n this.support.limitation = 'required host services are missing'\n return\n }\n this.scope = ai.activate(this.options.plugin)\n this.unregisterPurpose = registerTitlePurpose(this.scope)\n this.nativeSlot = createNativeTitleSlot({\n sessionTitle,\n loader,\n generate: request => this.nativeGenerate(request),\n })\n try {\n this.claim = await claimTitleSlot(this.nativeSlot, PROVIDER_ID)\n this.support.weOwn = true\n this.support.nativeOwner = PROVIDER_ID\n delete this.support.limitation\n } catch (error) {\n this.support.weOwn = false\n this.support.nativeOwner = this.nativeSlot.owner() ?? null\n this.support.limitation = error instanceof Error ? error.message : 'native title provider slot is occupied'\n }\n }\n\n async dispose(): Promise<void> {\n this.generation += 1\n for (const controller of this.inFlight.values()) controller.abort(new Error('current-title disabled'))\n this.inFlight.clear()\n const snapshot = this.nativeSlot?.displacement\n const outcome = await releaseTitleSlot(this.nativeSlot ?? { owner: () => undefined, occupy: async () => () => {} }, this.claim)\n if (outcome === 'released') await restoreOwnDisplacement(this.options.loader, snapshot)\n this.claim = undefined\n this.support.weOwn = false\n this.support.nativeOwner = this.nativeSlot?.owner() ?? null\n if (outcome === 'left-other-owner') {\n this.support.limitation = 'another title provider owns the slot; previous owner was not restored'\n }\n this.unregisterPurpose?.()\n this.unregisterPurpose = undefined\n this.scope?.dispose()\n this.scope = undefined\n }\n\n status(sessionId?: string): TitleStatus {\n const session = sessionId && this.options.sessions ? this.options.sessions.get(sessionId) : undefined\n const snapshot = session ? this.read(session) : undefined\n return {\n settings: { ...this.settings },\n support: { ...this.support, nativeOwner: this.nativeSlot?.owner() ?? this.support.nativeOwner },\n ...(sessionId ? {\n session: {\n sessionId,\n title: snapshot?.title,\n sourceKind: snapshot?.source.kind,\n generating: this.inFlight.has(sessionId),\n pinned: snapshot?.source.kind === 'user',\n },\n } : {}),\n }\n }\n\n async updateLocale(_locale: TitleLocaleMode, expectedRevision: number): Promise<TitleSettings> {\n if (this.settings.revision !== expectedRevision) {\n throw Object.assign(new Error('current-title settings changed'), { code: 'revision' })\n }\n const next = { revision: this.settings.revision + 1, locale: 'auto' as const }\n if (this.options.store) await this.options.store.save(next)\n this.settings = next\n this.config = { ...this.config, locale: 'auto' }\n return { ...this.settings }\n }\n\n async regenerate(sessionId: string, signal: AbortSignal): Promise<TitleSnapshot | undefined> {\n if (!this.support.weOwn) throw Object.assign(new Error('title provider is inactive'), { code: 'unavailable' })\n const session = this.options.sessions?.get(sessionId)\n if (!session) throw Object.assign(new Error('session not found'), { code: 'not-found' })\n await this.options.sessionTitle?.refresh(session, signal)\n return this.read(session)\n }\n\n read(session: SessionLike): TitleSnapshot | undefined {\n const snapshot = this.options.sessionTitle?.get(session)\n if (!snapshot?.title) return undefined\n const kind = snapshot.source?.kind\n return {\n title: snapshot.title,\n messageSeqs: snapshot.messageSeqs ? [...snapshot.messageSeqs] : [],\n source: {\n kind: kind === 'user' || kind === 'fallback' || kind === 'provider' ? kind : 'provider',\n ...(typeof snapshot.source?.provider === 'string' ? { provider: snapshot.source.provider } : {}),\n },\n eventSeq: Number(snapshot.eventSeq ?? 0),\n updatedAt: snapshot.updatedAt ?? 0,\n }\n }\n\n private async nativeGenerate(request: {\n session: { id: string; snapshotEvents(): readonly unknown[] }\n messages: ReadonlyArray<{ seq: number; text: string }>\n signal: AbortSignal\n }) {\n const scope = this.scope\n if (!scope) throw new Error('dsh-current-title: aiServices is not available')\n const generation = this.generation\n const controller = new AbortController()\n this.inFlight.get(request.session.id)?.abort(new Error('newer title generation superseded older work'))\n this.inFlight.set(request.session.id, controller)\n try {\n const combined = AbortSignal.any([request.signal, controller.signal, scope.signal])\n const session = this.options.sessions?.get(request.session.id)\n const messages = collectHumanMessages(\n request.messages.map(message => ({\n type: 'user/message',\n seq: message.seq,\n data: { source: { kind: 'user' }, content: [{ type: 'text', text: message.text }] },\n })),\n )\n const generated = await generateCurrentTitle({\n scope,\n config: this.config,\n sessionId: request.session.id,\n messages: messages.length > 0 ? messages : [...request.messages],\n localePreference: this.options.localePreference?.(),\n signal: combined,\n isCurrent: () => this.generation === generation && scope.active && this.support.weOwn\n && (!session || this.options.sessions?.get(request.session.id) === session),\n now: this.options.now?.(),\n })\n return { title: generated.title, messageSeqs: generated.messageSeqs, model: generated.model }\n } finally {\n if (this.inFlight.get(request.session.id) === controller) this.inFlight.delete(request.session.id)\n }\n }\n}\n\nexport function hostLocalePreference(host: CurrentTitleHost): string | undefined {\n try {\n const settings = host.get('settings') as { get?(namespace: string): unknown } | undefined\n const value = settings?.get?.('locale')\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined\n const preference = (value as Record<string, unknown>).preference\n return typeof preference === 'string' ? preference : undefined\n } catch {\n return undefined\n }\n}\n\nexport async function handleRpc(service: CurrentTitleService, endpoint: string, payload: unknown, signal: AbortSignal): Promise<RpcResult> {\n signal.throwIfAborted()\n const body = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : {}\n if (endpoint === 'status') {\n return { ok: true, value: service.status(typeof body.sessionId === 'string' ? body.sessionId : undefined) }\n }\n if (endpoint === 'settings') {\n if (!isTitleLocaleMode(body.locale) || typeof body.expectedRevision !== 'number') {\n return { ok: false, error: { code: 'bad-request', message: '标题语言设置无效。' } }\n }\n try {\n return { ok: true, value: await service.updateLocale(body.locale, body.expectedRevision) }\n } catch (error) {\n const code = error instanceof Error && 'code' in error ? String((error as { code?: string }).code) : 'failed'\n if (code === 'revision') return { ok: false, error: { code, message: '设置已更新,请刷新后重试。' } }\n return { ok: false, error: { code: 'failed', message: '无法保存标题设置。' } }\n }\n }\n if (endpoint === 'regenerate') {\n if (typeof body.sessionId !== 'string' || !body.sessionId) {\n return { ok: false, error: { code: 'bad-request', message: '缺少会话。' } }\n }\n try {\n const snapshot = await service.regenerate(body.sessionId, signal)\n return { ok: true, value: snapshot ?? null }\n } catch (error) {\n const code = error instanceof Error && 'code' in error ? String((error as { code?: string }).code) : 'failed'\n if (code === 'not-found') return { ok: false, error: { code, message: '找不到该会话。' } }\n if (code === 'unavailable') return { ok: false, error: { code, message: '标题服务未就绪。' } }\n if (signal.aborted) return { ok: false, error: { code: 'cancelled', message: '已取消。' } }\n return { ok: false, error: { code: 'failed', message: '标题生成失败。' } }\n }\n }\n return { ok: false, error: { code: 'bad-request', message: '未知操作。' } }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport type { AiServices } from '@klarkxy/dsh-ai-services/contracts'\nimport { registerHostRpc, type HostRpcContext } from '@klarkxy/dsh-ai-services/host-rpc'\nimport { isTitleLocaleMode, PLUGIN_NAME, RPC_CHANNEL, SETTINGS_KEY, type TitleLocaleMode } from './contracts.ts'\nimport { CurrentTitleService, handleRpc, hostLocalePreference } from './service.ts'\nimport type { LoaderFace, SessionTitleServiceLike } from './native-slot.ts'\nimport type { SessionLike } from './messages.ts'\nimport { parseTitleSettings, titleDomain } from './storage.ts'\n\nexport const name = PLUGIN_NAME\nexport const inject = ['sessionTitle', 'aiServices', 'sessions', 'loader', 'storageDomain', 'connection', 'webServer'] as const\nexport { CurrentTitleService }\nexport { selectRecentMessages, frameMessages } from './input.ts'\nexport { parseModelTitle, formatTitle, resolveTitleLocale } from './output.ts'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context { currentTitle: CurrentTitleService }\n}\n\ntype DomainHandle = {\n table(name: 'settings'): {\n get(key: string): unknown\n put(key: string, value: unknown): Promise<void>\n }\n close(): Promise<void>\n}\n\ntype Host = Context & HostRpcContext & {\n sessionTitle: SessionTitleServiceLike\n aiServices: AiServices\n sessions: { get(id: string): SessionLike | undefined }\n loader: LoaderFace\n storageDomain: { open: (spec: typeof titleDomain) => Promise<DomainHandle> }\n}\n\nexport async function apply(ctx: Context, config: { locale?: TitleLocaleMode } = {}): Promise<void> {\n const host = ctx as Host\n const domain = await host.storageDomain.open(titleDomain)\n const table = domain.table('settings')\n const locale = isTitleLocaleMode(config.locale) ? config.locale : 'auto'\n const service = new CurrentTitleService({\n plugin: PLUGIN_NAME,\n ai: host.aiServices,\n sessionTitle: host.sessionTitle,\n loader: host.loader,\n sessions: host.sessions,\n store: {\n load: () => parseTitleSettings(table.get(SETTINGS_KEY)),\n save: settings => table.put(SETTINGS_KEY, settings),\n },\n initialLocale: locale,\n localePreference: () => hostLocalePreference(host),\n })\n await service.start()\n ctx.provide('currentTitle', service)\n ctx.effect(() => async () => { await service.dispose(); await domain.close() }, 'current-title.lifecycle')\n ctx.effect(() => registerHostRpc(host, RPC_CHANNEL, (endpoint, payload, signal) => handleRpc(service, endpoint, payload, signal)), 'current-title.rpc')\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klarkxy/dsh-current-title",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Auto Title: keep session titles following the latest task; names you set by hand stay put.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"./package.json": "./package.json"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
+
"icon.svg",
|
|
29
30
|
"lib",
|
|
30
31
|
"cordis.patch.yml",
|
|
31
32
|
"README.md",
|
|
@@ -53,23 +54,34 @@
|
|
|
53
54
|
"entries": [
|
|
54
55
|
{
|
|
55
56
|
"id": "current-title",
|
|
56
|
-
"title": "
|
|
57
|
-
"description": "
|
|
57
|
+
"title": "自动标题",
|
|
58
|
+
"description": "标题跟随最新任务更新,手动命名不被覆盖",
|
|
58
59
|
"feature": "current-title",
|
|
59
60
|
"service": "currentTitle"
|
|
60
61
|
}
|
|
61
62
|
]
|
|
62
63
|
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsdown && node ../../scripts/editor-plugins/wrap-client.mjs @klarkxy/dsh-current-title",
|
|
66
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
67
|
+
"test": "vitest run --root ../.. --config vitest.editor-plugins.config.ts --maxWorkers=2 plugins/dsh-current-title/src"
|
|
68
|
+
},
|
|
63
69
|
"dependencies": {
|
|
64
|
-
"@
|
|
65
|
-
"zod": "^4.1.5"
|
|
66
|
-
"@klarkxy/dsh-ai-services": "0.1.3"
|
|
70
|
+
"@klarkxy/dsh-ai-services": "0.1.4",
|
|
71
|
+
"zod": "^4.1.5"
|
|
67
72
|
},
|
|
68
73
|
"peerDependencies": {
|
|
69
|
-
"@deepseek-ai/cordis": "^4.0.4"
|
|
74
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
75
|
+
"@deepseek-ai/dsh-storage-domain": ">=0.1.7-rc.2"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@deepseek-ai/dsh-storage-domain": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
70
81
|
},
|
|
71
82
|
"devDependencies": {
|
|
72
83
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
84
|
+
"@deepseek-ai/dsh-storage-domain": "0.1.7-rc.2",
|
|
73
85
|
"react": "^18.2.0",
|
|
74
86
|
"@types/react": "18.3.31"
|
|
75
87
|
},
|
|
@@ -83,23 +95,18 @@
|
|
|
83
95
|
],
|
|
84
96
|
"repository": {
|
|
85
97
|
"type": "git",
|
|
86
|
-
"url": "git+https://github.com/klarkxy/dsh-
|
|
87
|
-
"directory": "
|
|
98
|
+
"url": "git+https://github.com/klarkxy/dsh-plugins.git",
|
|
99
|
+
"directory": "plugins/dsh-current-title"
|
|
88
100
|
},
|
|
89
|
-
"homepage": "https://github.
|
|
101
|
+
"homepage": "https://klarkxy.github.io/dsh-plugins/plugins/current-title/",
|
|
90
102
|
"bugs": {
|
|
91
|
-
"url": "https://github.com/klarkxy/dsh-
|
|
103
|
+
"url": "https://github.com/klarkxy/dsh-plugins/issues"
|
|
92
104
|
},
|
|
93
105
|
"publishConfig": {
|
|
94
106
|
"access": "public",
|
|
95
107
|
"registry": "https://registry.npmjs.org/"
|
|
96
108
|
},
|
|
97
109
|
"dshRelease": {
|
|
98
|
-
"contentHash": "
|
|
99
|
-
},
|
|
100
|
-
"scripts": {
|
|
101
|
-
"build": "tsdown && node ../../scripts/wrap-client.mjs @klarkxy/dsh-current-title",
|
|
102
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
103
|
-
"test": "vitest run --root ../.. packages/dsh-current-title/src"
|
|
110
|
+
"contentHash": "a5170feef2f881e060265ede40e9744d1497bbabff47a1fab1945c1820b62f9f"
|
|
104
111
|
}
|
|
105
|
-
}
|
|
112
|
+
}
|