dsh-working-activity 0.2.6 → 0.3.2

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/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["css"],"sources":["../src/client/WorkingLine.tsx","../src/client/index.ts"],"sourcesContent":["// Working-line dock entry: one dim full-width row above the composer card\n// showing the live working-activity snapshot — phase-colored breathing\n// marker, the host-composed status line, and the turn's tool count badge.\n// Renders for every live phase (waiting/thinking/tool) and the done summary;\n// hides while idle or before the first publish.\n//\n// The 'conversation.input.dock' SlotMap declaration lives in\n// @deepseek-ai/dsh-client-ui-conversation/client (contract/slots.ts); this\n// entry contributes into it without owning it.\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport css from './WorkingLine.module.css'\n\n/** Full props of the dock entry: the input-zone runtime share (session standard kit). */\nexport type WorkingLineProps = PropsRuntime<'conversation.input.dock'>\n\n/** Tool-count badge copy (no locale seat: the line text itself is host-composed). */\nconst TOOLS_LABEL = 'tools this turn'\n\n/**\n * Working-line dock entry: reads the latest activity snapshot off the\n * conversation snapshot and renders the row, or nothing when idle/absent.\n */\nexport function WorkingLine({ useSession }: WorkingLineProps) {\n // Defensive read: the runtime patch that puts `activity` onto the\n // conversation snapshot ships separately — on an unpatched host the field\n // is absent (undefined), not null, and this component must render nothing\n // instead of crashing into the slot error boundary. Same never-throw\n // discipline as the node side's registration.js.\n const activity = useSession(s => s.activity ?? null)\n if (activity === null || activity.phase === 'idle' || activity.line === '') return null\n return (\n <div className={css.line} data-activity-phase={activity.phase}>\n <span className={css.marker} aria-hidden=\"true\" />\n <span className={css.text}>{activity.line}</span>\n {activity.toolCount > 0 && (\n <span className={css.tools} title={`${activity.toolCount} ${TOOLS_LABEL}`}>\n {activity.toolCount}\n </span>\n )}\n </div>\n )\n}\n","/**\n * Working-activity surface plugin, browser half: the working-line entry in\n * the conversation.input.dock strip. This package's node half emits\n * `activity/status` session events; the web runtime patch narrows them into\n * the conversation snapshot's `activity` member, so this dock entry reads\n * the live frame through the standard session kit and owns no store, no\n * refresh chain, and no event listener.\n *\n * Mount contract (see the root README's \"Web UI 集成\" section): the web\n * client's client-modules host scans loader entries for `dsh.client`\n * declarations and serves this package's `./client` bundle at\n * /plugins/dsh-working-activity/client.js. The entry contributes into the\n * input dock — no official-source patch is involved.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the input.dock entry)\n// through the Client assembly boundary.\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { WorkingLine } from './WorkingLine.tsx'\nimport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\n\nexport { WorkingLine, type WorkingLineProps } from './WorkingLine.tsx'\nexport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\n\n/** Required services for the dock registration. */\nexport const inject = ['slots']\n\n/**\n * Client plugin body: the working-line dock entry.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n // The pre-slots patch used the same id in ui-conversation; entries with\n // equal order render in registration order, and goal (10) / queue (20)\n // keep their seats — this row sits between them.\n id: 'activity',\n order: 15,\n registrant: 'dsh-working-activity',\n }, WorkingLine))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBA,MAAM,cAAc;;;;;EAMpB,SAAgB,YAAY,EAAE,cAAgC;GAM5D,MAAM,WAAW,YAAW,MAAK,EAAE,YAAY,IAAI;GACnD,IAAI,aAAa,QAAQ,SAAS,UAAU,UAAU,SAAS,SAAS,IAAI,OAAO;GACnF,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAWA,+BAAI;IAAM,uBAAqB,SAAS;IAAxD,UAAA;KACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAQ,eAAY;KAAQ,CAAA;KACjD,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,UAAA,SAAS;KAAW,CAAA;KAC/C,SAAS,YAAY,KACpB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,OAAO,GAAG,SAAS,UAAU,GAAG;MACzD,UAAA,SAAS;KACN,CAAA;IAEL;;EAET;;;;EChBA,MAAa,SAAS,CAAC,OAAO;;;;;EAM9B,SAAgB,MAAM,KAA0B;GAC9C,IAAI,MAAM,OAAO,iCAAiC,IAAI,MAAM,SAAS;IACnE,MAAM;IAIN,IAAI;IACJ,OAAO;IACP,YAAY;GACd,GAAG,WAAW,CAAC;EACjB"}
1
+ {"version":3,"file":"client.js","names":["css"],"sources":["../src/client/WorkingLine.tsx","../src/client/index.ts"],"sourcesContent":["// Working-line dock entry: one dim full-width row above the composer card\r\n// showing the live working-activity snapshot — phase-colored breathing\r\n// marker, the host-composed status line, and the turn's tool count badge.\r\n// Renders for every live phase (waiting/thinking/tool) and the done summary;\r\n// hides while idle or before the first publish.\r\n//\r\n// The 'conversation.input.dock' SlotMap declaration lives in\r\n// @deepseek-ai/dsh-client-ui-conversation/client (contract/slots.ts); this\r\n// entry contributes into it without owning it.\r\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport css from './WorkingLine.module.css'\r\n\r\n/** Full props of the dock entry: the input-zone runtime share (session standard kit). */\r\nexport type WorkingLineProps = PropsRuntime<'conversation.input.dock'>\r\n\r\n/** Tool-count badge copy (no locale seat: the line text itself is host-composed). */\r\nconst TOOLS_LABEL = 'tools this turn'\r\n\r\n/**\r\n * Working-line dock entry: reads the latest activity snapshot off the\r\n * conversation snapshot and renders the row, or nothing when idle/absent.\r\n */\r\nexport function WorkingLine({ useSession }: WorkingLineProps) {\r\n // Defensive read: the runtime patch that puts `activity` onto the\r\n // conversation snapshot ships separately — on an unpatched host the field\r\n // is absent (undefined), not null, and this component must render nothing\r\n // instead of crashing into the slot error boundary. Same never-throw\r\n // discipline as the node side's registration.js.\r\n const activity = useSession(s => s.activity ?? null)\r\n if (activity === null || activity.phase === 'idle' || activity.line === '') return null\r\n return (\r\n <div className={css.line} data-activity-phase={activity.phase}>\r\n <span className={css.marker} aria-hidden=\"true\" />\r\n <span className={css.text}>{activity.line}</span>\r\n {activity.toolCount > 0 && (\r\n <span className={css.tools} title={`${activity.toolCount} ${TOOLS_LABEL}`}>\r\n {activity.toolCount}\r\n </span>\r\n )}\r\n </div>\r\n )\r\n}\r\n","/**\r\n * Working-activity surface plugin, browser half: the working-line entry in\r\n * the conversation.input.dock strip. This package's node half emits\r\n * `activity/status` session events; the web runtime patch narrows them into\r\n * the conversation snapshot's `activity` member, so this dock entry reads\r\n * the live frame through the standard session kit and owns no store, no\r\n * refresh chain, and no event listener.\r\n *\r\n * Mount contract (see the root README's \"Web UI 集成\" section): the web\r\n * client's client-modules host scans loader entries for `dsh.client`\r\n * declarations and serves this package's `./client` bundle at\r\n * /plugins/dsh-working-activity/client.js. The entry contributes into the\r\n * input dock — no official-source patch is involved.\r\n */\r\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\r\n// Type-only: pulls the ui-conversation SlotMap merge (the input.dock entry)\r\n// through the Client assembly boundary.\r\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\r\nimport { WorkingLine } from './WorkingLine.tsx'\r\nimport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\r\n\r\nexport { WorkingLine, type WorkingLineProps } from './WorkingLine.tsx'\r\nexport type { ActivityPhase, ActivitySnapshot } from './activity.ts'\r\n\r\n/** Required services for the dock registration. */\r\nexport const inject = ['slots']\r\n\r\n/**\r\n * Client plugin body: the working-line dock entry.\r\n * @param ctx - client root context.\r\n */\r\nexport function apply(ctx: ClientContext): void {\r\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\r\n name: 'conversation.input.dock',\r\n // The pre-slots patch used the same id in ui-conversation; entries with\r\n // equal order render in registration order, and goal (10) / queue (20)\r\n // keep their seats — this row sits between them.\r\n id: 'activity',\r\n order: 15,\r\n registrant: 'dsh-working-activity',\r\n }, WorkingLine))\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBA,MAAM,cAAc;;;;;EAMpB,SAAgB,YAAY,EAAE,cAAgC;GAM5D,MAAM,WAAW,YAAW,MAAK,EAAE,YAAY,IAAI;GACnD,IAAI,aAAa,QAAQ,SAAS,UAAU,UAAU,SAAS,SAAS,IAAI,OAAO;GACnF,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAWA,+BAAI;IAAM,uBAAqB,SAAS;IAAxD,UAAA;KACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAQ,eAAY;KAAQ,CAAA;KACjD,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,UAAA,SAAS;KAAW,CAAA;KAC/C,SAAS,YAAY,KACpB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MAAM,WAAWA,+BAAI;MAAO,OAAO,GAAG,SAAS,UAAU,GAAG;MACzD,UAAA,SAAS;KACN,CAAA;IAEL;;EAET;;;;EChBA,MAAa,SAAS,CAAC,OAAO;;;;;EAM9B,SAAgB,MAAM,KAA0B;GAC9C,IAAI,MAAM,OAAO,iCAAiC,IAAI,MAAM,SAAS;IACnE,MAAM;IAIN,IAAI;IACJ,OAAO;IACP,YAAY;GACd,GAAG,WAAW,CAAC;EACjB"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Working-activity user configuration, mirroring the pi extension's
3
+ * `~/.pi/agent/working-activity.json` shape: the UI owns the file (dsh-tui
4
+ * persists `frames` at `~/.dsh-tui/working-activity.json`) and consumers use
5
+ * the pure parser here to honor `mode` / `features` / `customPhrases` /
6
+ * `customActions` without duplicating validation. No I/O — filesystem
7
+ * ownership stays with the host.
8
+ * @module @deepseek-ai/dsh-working-activity/config
9
+ */
10
+ /** Feature flags that can be switched independently (pi extension parity). */
11
+ export declare const FEATURE_FLAGS: readonly ["phrases", "rareEggs", "nightPhrases", "weekend", "holidays", "combo", "failPhrases", "modelQuips", "shimmer", "continuePhrases", "cost"];
12
+ /** Feature-flag name type. */
13
+ export type FeatureFlag = (typeof FEATURE_FLAGS)[number];
14
+ /** Chinese labels for the settings panel / docs. */
15
+ export declare const FEATURE_LABELS: Record<FeatureFlag, string>;
16
+ /** The file-based config, same keys as the pi extension. */
17
+ export type WorkingActivityConfig = {
18
+ /** Frame preset name (`random` picks one per process). */
19
+ frames?: string;
20
+ /** Extra thinking phrases appended to the base pool. */
21
+ customPhrases?: readonly string[];
22
+ /** Exact tool-name → action-copy pools (case-insensitive match). */
23
+ customActions?: Readonly<Record<string, readonly string[]>>;
24
+ /** Inject the `⏵` self-narration contract into the system prompt. */
25
+ narrate?: boolean;
26
+ /** Debug logging to `working-activity-debug.log`. */
27
+ debugLog?: boolean;
28
+ /** Context usage warning threshold in percent. */
29
+ contextWarnAt?: number;
30
+ /** Context usage danger threshold in percent. */
31
+ contextDangerAt?: number;
32
+ /** Show estimated output tokens per second. */
33
+ showTokPerSec?: boolean;
34
+ /** Hourly work-reminder threshold (0–24, 0 = off). */
35
+ workRemindAt?: number;
36
+ /** lively: full flourish (default) / minimal: functional labels only. */
37
+ mode?: 'lively' | 'minimal';
38
+ /** Per-feature switches; explicit values override `mode` defaults. */
39
+ features?: Readonly<Record<string, boolean>>;
40
+ };
41
+ /** The defaults when nothing is configured. */
42
+ export declare const DEFAULT_ACTIVITY_CONFIG: WorkingActivityConfig;
43
+ /** Result of parsing a config file. */
44
+ export type ConfigReadResult = {
45
+ readonly config: WorkingActivityConfig;
46
+ /** Raw parsed object, preserving unknown keys for lossless write-back. */
47
+ readonly raw: Record<string, unknown>;
48
+ /** Parse error message, when the text was not a valid config object. */
49
+ readonly error?: string;
50
+ };
51
+ /** Clamp `dangerAt` to be at least `warnAt` (percent thresholds). */
52
+ export declare function normalizeThresholds(cfg: WorkingActivityConfig): WorkingActivityConfig;
53
+ /**
54
+ * Parse a `working-activity.json` text into a validated config. Unknown keys
55
+ * are preserved in `raw` (for lossless write-back); invalid values are
56
+ * dropped. A non-object root yields the defaults plus an `error`.
57
+ * @param text - Raw file contents.
58
+ * @returns Validated config + raw object (+ optional error).
59
+ */
60
+ export declare function parseWorkingActivityConfig(text: string): ConfigReadResult;
61
+ /**
62
+ * Whether a feature is on. Explicit `features` entries win; otherwise
63
+ * `minimal` mode turns everything off (except `cost`, always on).
64
+ * @param config - Parsed config.
65
+ * @param name - Feature flag name.
66
+ */
67
+ export declare function featureOn(config: WorkingActivityConfig, name: FeatureFlag): boolean;
68
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,8EAA8E;AAC9E,eAAO,MAAM,aAAa,qJAYhB,CAAA;AAEV,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,oDAAoD;AACpD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAYtD,CAAA;AAED,4DAA4D;AAC5D,MAAM,MAAM,qBAAqB,GAAG;IAClC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,wDAAwD;IACxD,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,oEAAoE;IACpE,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;IAC3D,qEAAqE;IACrE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,+CAA+C;IAC/C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yEAAyE;IACzE,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC7C,CAAA;AAED,+CAA+C;AAC/C,eAAO,MAAM,uBAAuB,EAAE,qBAGrC,CAAA;AAED,uCAAuC;AACvC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAA;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAMD,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,qBAAqB,GAAG,qBAAqB,CAIrF;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAwDzE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,qBAAqB,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAKnF"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Working-activity user configuration, mirroring the pi extension's
3
+ * `~/.pi/agent/working-activity.json` shape: the UI owns the file (dsh-tui
4
+ * persists `frames` at `~/.dsh-tui/working-activity.json`) and consumers use
5
+ * the pure parser here to honor `mode` / `features` / `customPhrases` /
6
+ * `customActions` without duplicating validation. No I/O — filesystem
7
+ * ownership stays with the host.
8
+ * @module @deepseek-ai/dsh-working-activity/config
9
+ */
10
+ import { DEFAULT_PRESET, isPresetName } from './frames.js';
11
+ /** Feature flags that can be switched independently (pi extension parity). */
12
+ export const FEATURE_FLAGS = [
13
+ 'phrases', // 俏皮文案池(思考/等待/收尾/工具动词)
14
+ 'rareEggs', // 稀有彩虹彩蛋
15
+ 'nightPhrases', // 深夜文案
16
+ 'weekend', // 周末问候
17
+ 'holidays', // 节假日彩蛋
18
+ 'combo', // 连击火力全开
19
+ 'failPhrases', // 失败文案池
20
+ 'modelQuips', // 模型切换梗
21
+ 'shimmer', // 文案星辉扫过/彩虹流光
22
+ 'continuePhrases', // 打断后接梗
23
+ 'cost', // 成本与 token 核算(结束时展示)
24
+ ];
25
+ /** Chinese labels for the settings panel / docs. */
26
+ export const FEATURE_LABELS = {
27
+ phrases: '俏皮文案',
28
+ rareEggs: '稀有彩蛋',
29
+ nightPhrases: '深夜文案',
30
+ weekend: '周末问候',
31
+ holidays: '节假日彩蛋',
32
+ combo: '工具连击',
33
+ failPhrases: '失败文案',
34
+ modelQuips: '模型切换梗',
35
+ shimmer: '星辉效果',
36
+ continuePhrases: '打断接梗',
37
+ cost: '成本与 token',
38
+ };
39
+ /** The defaults when nothing is configured. */
40
+ export const DEFAULT_ACTIVITY_CONFIG = {
41
+ frames: DEFAULT_PRESET,
42
+ narrate: true,
43
+ };
44
+ function errorText(error) {
45
+ return error instanceof Error ? error.message : String(error);
46
+ }
47
+ /** Clamp `dangerAt` to be at least `warnAt` (percent thresholds). */
48
+ export function normalizeThresholds(cfg) {
49
+ const warnAt = cfg.contextWarnAt ?? 80;
50
+ const dangerAt = cfg.contextDangerAt ?? 95;
51
+ return dangerAt < warnAt ? { ...cfg, contextDangerAt: warnAt } : cfg;
52
+ }
53
+ /**
54
+ * Parse a `working-activity.json` text into a validated config. Unknown keys
55
+ * are preserved in `raw` (for lossless write-back); invalid values are
56
+ * dropped. A non-object root yields the defaults plus an `error`.
57
+ * @param text - Raw file contents.
58
+ * @returns Validated config + raw object (+ optional error).
59
+ */
60
+ export function parseWorkingActivityConfig(text) {
61
+ let parsed;
62
+ try {
63
+ parsed = JSON.parse(text);
64
+ }
65
+ catch (error) {
66
+ return { config: { ...DEFAULT_ACTIVITY_CONFIG }, raw: {}, error: errorText(error) };
67
+ }
68
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
69
+ return {
70
+ config: { ...DEFAULT_ACTIVITY_CONFIG },
71
+ raw: {},
72
+ error: '配置根节点必须是 JSON 对象',
73
+ };
74
+ }
75
+ const raw = parsed;
76
+ const config = { ...DEFAULT_ACTIVITY_CONFIG };
77
+ if (typeof raw.frames === 'string' && isPresetName(raw.frames))
78
+ config.frames = raw.frames;
79
+ if (Array.isArray(raw.customPhrases)) {
80
+ const phrases = raw.customPhrases.filter((s) => typeof s === 'string' && s.trim().length > 0);
81
+ if (phrases.length > 0)
82
+ config.customPhrases = phrases;
83
+ }
84
+ if (raw.customActions !== null && typeof raw.customActions === 'object' && !Array.isArray(raw.customActions)) {
85
+ const customActions = {};
86
+ for (const [key, value] of Object.entries(raw.customActions)) {
87
+ const actions = Array.isArray(value)
88
+ ? value.filter((entry) => typeof entry === 'string' && entry.trim().length > 0)
89
+ .map((entry) => entry.trim())
90
+ : [];
91
+ if (key.trim().length > 0 && actions.length > 0)
92
+ customActions[key.trim()] = actions;
93
+ }
94
+ if (Object.keys(customActions).length > 0)
95
+ config.customActions = customActions;
96
+ }
97
+ if (typeof raw.narrate === 'boolean')
98
+ config.narrate = raw.narrate;
99
+ if (typeof raw.debugLog === 'boolean')
100
+ config.debugLog = raw.debugLog;
101
+ if (typeof raw.contextWarnAt === 'number' && Number.isFinite(raw.contextWarnAt)
102
+ && raw.contextWarnAt >= 0 && raw.contextWarnAt <= 100) {
103
+ config.contextWarnAt = raw.contextWarnAt;
104
+ }
105
+ if (typeof raw.contextDangerAt === 'number' && Number.isFinite(raw.contextDangerAt)
106
+ && raw.contextDangerAt >= 0 && raw.contextDangerAt <= 100) {
107
+ config.contextDangerAt = raw.contextDangerAt;
108
+ }
109
+ if (typeof raw.showTokPerSec === 'boolean')
110
+ config.showTokPerSec = raw.showTokPerSec;
111
+ if (typeof raw.workRemindAt === 'number' && Number.isFinite(raw.workRemindAt)
112
+ && raw.workRemindAt >= 0 && raw.workRemindAt <= 24) {
113
+ config.workRemindAt = raw.workRemindAt;
114
+ }
115
+ if (raw.mode === 'lively' || raw.mode === 'minimal')
116
+ config.mode = raw.mode;
117
+ if (raw.features !== null && typeof raw.features === 'object' && !Array.isArray(raw.features)) {
118
+ const features = {};
119
+ for (const [key, value] of Object.entries(raw.features)) {
120
+ if (typeof value === 'boolean')
121
+ features[key] = value;
122
+ }
123
+ if (Object.keys(features).length > 0)
124
+ config.features = features;
125
+ }
126
+ return { config: normalizeThresholds(config), raw };
127
+ }
128
+ /**
129
+ * Whether a feature is on. Explicit `features` entries win; otherwise
130
+ * `minimal` mode turns everything off (except `cost`, always on).
131
+ * @param config - Parsed config.
132
+ * @param name - Feature flag name.
133
+ */
134
+ export function featureOn(config, name) {
135
+ const explicit = config.features?.[name];
136
+ if (typeof explicit === 'boolean')
137
+ return explicit;
138
+ if (name === 'cost')
139
+ return true;
140
+ return config.mode !== 'minimal';
141
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Working-activity indicator frame presets — the single home for every
3
+ * spinner animation, ported from the pi working-activity extension
4
+ * (`FRAME_PRESETS`) plus the dsh-tui additions (moon8 / rainbow / whale
5
+ * family / clock / traffic lights). Consumers (TUI row, Web slot) render the
6
+ * current frame next to the live working line; the preset name is chosen by
7
+ * the UI and persisted in `working-activity.json` (`frames` key).
8
+ *
9
+ * `\uFE0E` forces text rendering so Windows never paints the glyphs as color
10
+ * emoji (the green-block problem); emoji presets (moon8, clock, whales)
11
+ * deliberately omit it to keep colorful rendering on modern terminals.
12
+ * @module @deepseek-ai/dsh-working-activity/frames
13
+ */
14
+ /** One working-activity preset: the frame sequence and the per-frame interval. */
15
+ export interface FramePreset {
16
+ readonly frames: readonly string[];
17
+ readonly intervalMs: number;
18
+ }
19
+ /** Named working-activity frame presets, keyed by preset name (`claude`, `moon`, `sand`, ...). */
20
+ export declare const FRAME_PRESETS: Record<string, FramePreset>;
21
+ /** The default preset (moon8: the smoothest of the emoji moons). */
22
+ export declare const DEFAULT_PRESET = "moon8";
23
+ /** Every selectable preset name, `random` first (the pi selector order). */
24
+ export declare const PRESET_NAMES: readonly string[];
25
+ /**
26
+ * Whether `name` selects a known preset or `random`.
27
+ * @param name - Candidate preset name.
28
+ * @returns True when the name resolves to a preset.
29
+ */
30
+ export declare function isPresetName(name: string): boolean;
31
+ /**
32
+ * Resolve a preset name (`random` picks one per process).
33
+ * @param name - Preset name, or undefined for the default.
34
+ * @returns The matching preset; unknown or absent names fall back to the default.
35
+ */
36
+ export declare function resolvePreset(name: string | undefined): FramePreset;
37
+ //# sourceMappingURL=frames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../src/frames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,kGAAkG;AAClG,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAkFrD,CAAA;AAED,oEAAoE;AACpE,eAAO,MAAM,cAAc,UAAU,CAAA;AAErC,4EAA4E;AAC5E,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAA8C,CAAA;AAExF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CASnE"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Working-activity indicator frame presets — the single home for every
3
+ * spinner animation, ported from the pi working-activity extension
4
+ * (`FRAME_PRESETS`) plus the dsh-tui additions (moon8 / rainbow / whale
5
+ * family / clock / traffic lights). Consumers (TUI row, Web slot) render the
6
+ * current frame next to the live working line; the preset name is chosen by
7
+ * the UI and persisted in `working-activity.json` (`frames` key).
8
+ *
9
+ * `\uFE0E` forces text rendering so Windows never paints the glyphs as color
10
+ * emoji (the green-block problem); emoji presets (moon8, clock, whales)
11
+ * deliberately omit it to keep colorful rendering on modern terminals.
12
+ * @module @deepseek-ai/dsh-working-activity/frames
13
+ */
14
+ /** Text-variant selector: keep symbols monochrome on Windows. */
15
+ const TE = '\uFE0E';
16
+ /** Named working-activity frame presets, keyed by preset name (`claude`, `moon`, `sand`, ...). */
17
+ export const FRAME_PRESETS = {
18
+ // Claude Code's real sequence: · ✢ * ✶ ✻ ✽ forward + backward.
19
+ claude: {
20
+ frames: ['·', `✢${TE}`, '*', `✶${TE}`, `✻${TE}`, `✽${TE}`, `✻${TE}`, `✶${TE}`, '*', `✢${TE}`],
21
+ intervalMs: 150,
22
+ },
23
+ star2: { frames: [`✶${TE}`, `✸${TE}`, `✹${TE}`, `✺${TE}`, `✹${TE}`, `✷${TE}`], intervalMs: 140 },
24
+ sand: {
25
+ frames: ['⠁', '⠂', '⠄', '⡀', '⡈', '⡐', '⡠', '⣀', '⣁', '⣂', '⣄', '⣌', '⣔', '⣤', '⣥', '⣦', '⣮', '⣶', '⣷', '⣿', '⡿', '⠿', '⢟', '⠟', '⡛', '⠛', '⠫', '⢋', '⠋', '⠍', '⡉', '⠉', '⠑', '⠡', '⢁'],
26
+ intervalMs: 120,
27
+ },
28
+ triangle: { frames: ['◢', '◣', '◤', '◥'], intervalMs: 180 },
29
+ box: { frames: ['▖', '▘', '▝', '▗'], intervalMs: 180 },
30
+ box2: { frames: ['▌', '▀', '▐', '▄'], intervalMs: 180 },
31
+ corners: { frames: ['◰', '◳', '◲', '◱'], intervalMs: 190 },
32
+ point: { frames: ['∙∙∙', '●∙∙', '∙●∙', '∙∙●', '∙∙∙'], intervalMs: 190 },
33
+ layer: { frames: ['-', '=', '≡'], intervalMs: 220 },
34
+ flip: { frames: ['_', '_', '_', '-', '`', '`', "'", '´', '-', '_', '_', '_'], intervalMs: 140 },
35
+ aesthetic: {
36
+ frames: ['▰▱▱▱▱▱▱', '▰▰▱▱▱▱▱', '▰▰▰▱▱▱▱', '▰▰▰▰▱▱▱', '▰▰▰▰▰▱▱', '▰▰▰▰▰▰▱', '▰▰▰▰▰▰▰', '▰▱▱▱▱▱▱'],
37
+ intervalMs: 140,
38
+ },
39
+ hamburger: { frames: ['☱', '☲', '☴'], intervalMs: 220 },
40
+ moon: { frames: ['◐', '◓', '◑', '◒'], intervalMs: 240 },
41
+ // kimi-code MoonLoader 同款:8 帧 emoji 月相,120ms 一帧,比半圆版更丝滑。
42
+ // 不带 \uFE0E:保留彩色 emoji 渲染(Windows Terminal 等现代终端效果最佳)。
43
+ moon8: { frames: ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'], intervalMs: 120 },
44
+ // 鲸鱼喷水:🐳 固定,水柱升起(· → | → ║)再回落,顶珠 ° 模拟水花(对齐 pi 版)。
45
+ 'whale-spout': {
46
+ frames: ['🐳 ', '🐳° ', '🐳|°', '🐳║°', '🐳|°', '🐳° ', '🐳 '],
47
+ intervalMs: 160,
48
+ },
49
+ // 鲸鱼转圈:🐳 + 环绕方向指示(逆时针转圈语义,对齐 pi 版)。
50
+ 'whale-spin': {
51
+ frames: ['🐳→', '🐳↘', '🐳↓', '🐳↙', '🐳←', '🐳↖', '🐳↑', '🐳↗'],
52
+ intervalMs: 150,
53
+ },
54
+ // 鲸鱼吐泡泡:🐳 固定,泡泡从头顶冒出(大泡 ○ 先行、小泡 ∘ 跟上)向右飘走。
55
+ 'whale-bubbles': {
56
+ frames: ['🐳 ', '🐳○ ', '🐳 ○', '🐳 ', '🐳∘ ', '🐳 ∘', '🐳 '],
57
+ intervalMs: 170,
58
+ },
59
+ // 时钟:12 个整点表盘循环(🕛 → 🕚),emoji 彩色渲染(同 moon8,不带 \uFE0E)。
60
+ clock: {
61
+ frames: ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'],
62
+ intervalMs: 300,
63
+ },
64
+ // 红绿灯:🔴 → 🟡 → 🟢 循环,一轮 1.2s,节奏从容。
65
+ traffic_lights: {
66
+ frames: ['🔴', '🟡', '🟢'],
67
+ intervalMs: 400,
68
+ },
69
+ comet: {
70
+ frames: ['● ', ' ● ', ' ● ', ' ● ', ' ●', ' ● ', ' ● ', ' ● '],
71
+ intervalMs: 160,
72
+ },
73
+ breathe: { frames: ['▁', '▃', '▅', '▇', '▅', '▃'], intervalMs: 210 },
74
+ dots: { frames: ['⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽'], intervalMs: 140 },
75
+ arrow: { frames: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'], intervalMs: 160 },
76
+ spark: { frames: ['·', '∘', '°', '✧', '°', '∘'], intervalMs: 240 },
77
+ bar: { frames: ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█', '▉', '▊', '▋', '▌', '▍', '▎'], intervalMs: 120 },
78
+ braille: { frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], intervalMs: 120 },
79
+ arc: { frames: ['◜', '◠', '◝', '◞', '◡', '◟'], intervalMs: 160 },
80
+ circle: { frames: ['◴', '◷', '◶', '◵'], intervalMs: 190 },
81
+ grow: { frames: ['.', 'o', 'O', '0', 'O', 'o'], intervalMs: 210 },
82
+ noise: { frames: ['▓', '▒', '░', '▒'], intervalMs: 160 },
83
+ bounce: { frames: ['⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈'], intervalMs: 140 },
84
+ rainbow: {
85
+ frames: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▂'],
86
+ intervalMs: 120,
87
+ },
88
+ // 双端进度条往返(pi 版独有,补全并集)。
89
+ bar2: {
90
+ frames: [
91
+ '[ ]', '[= ]', '[== ]', '[=== ]', '[ ===]',
92
+ '[ ==]', '[ =]', '[ ]', '[ =]', '[ ==]',
93
+ '[ ===]', '[=== ]', '[== ]', '[= ]',
94
+ ],
95
+ intervalMs: 140,
96
+ },
97
+ dqpb: { frames: ['d', 'q', 'p', 'b'], intervalMs: 210 },
98
+ toggle: { frames: ['⊶', '⊷'], intervalMs: 300 },
99
+ };
100
+ /** The default preset (moon8: the smoothest of the emoji moons). */
101
+ export const DEFAULT_PRESET = 'moon8';
102
+ /** Every selectable preset name, `random` first (the pi selector order). */
103
+ export const PRESET_NAMES = ['random', ...Object.keys(FRAME_PRESETS)];
104
+ /**
105
+ * Whether `name` selects a known preset or `random`.
106
+ * @param name - Candidate preset name.
107
+ * @returns True when the name resolves to a preset.
108
+ */
109
+ export function isPresetName(name) {
110
+ return name === 'random' || Object.hasOwn(FRAME_PRESETS, name);
111
+ }
112
+ /**
113
+ * Resolve a preset name (`random` picks one per process).
114
+ * @param name - Preset name, or undefined for the default.
115
+ * @returns The matching preset; unknown or absent names fall back to the default.
116
+ */
117
+ export function resolvePreset(name) {
118
+ if (name === 'random') {
119
+ const names = Object.keys(FRAME_PRESETS);
120
+ // FRAME_PRESETS is non-empty by construction, so any random index is in
121
+ // range and the lookup always lands on a preset.
122
+ const pick = names[Math.floor(Math.random() * names.length)];
123
+ return FRAME_PRESETS[pick];
124
+ }
125
+ return FRAME_PRESETS[name ?? ''] ?? FRAME_PRESETS[DEFAULT_PRESET];
126
+ }
@@ -39,6 +39,22 @@ export type Config = {
39
39
  customActions?: Record<string, string[]>;
40
40
  /** Inject the `⏵` self-narration contract into the system prompt and surface it. */
41
41
  narrate?: boolean;
42
+ /** UI language: `auto` follows `DSH_TUI_LANG` → `~/.dsh-tui/lang.json` →
43
+ * OS locale → zh; `zh`/`en` pin the copy directly. */
44
+ lang?: 'auto' | 'zh' | 'en';
45
+ /** Default frame preset name (informational for UI consumers; the TUI
46
+ * resolves the persisted `frames` choice itself). */
47
+ frames?: string;
48
+ /** lively: full flourish (default) / minimal: functional labels only. */
49
+ mode?: 'lively' | 'minimal';
50
+ /** Per-feature switches; explicit values override `mode` defaults. */
51
+ features?: Record<string, boolean>;
52
+ /** Extra thinking phrases appended to the base pool. */
53
+ customPhrases?: string[];
54
+ /** Show an estimated tokens/s prefix while streaming. */
55
+ showTokPerSec?: boolean;
56
+ /** Work reminder after this many turn-hours (0 = off). */
57
+ workRemindAt?: number;
42
58
  };
43
59
  export declare const Config: Schemastery<Config>;
44
60
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAalD,mBAAmB,aAAa,CAAA;AAEhC,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,wDAAwD;AACxD,MAAM,MAAM,MAAM,GAAG;IACnB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;mCAM+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAMD,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAQrC,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI,CA+H7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAelD,mBAAmB,aAAa,CAAA;AAEhC,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,wDAAwD;AACxD,MAAM,MAAM,MAAM,GAAG;IACnB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;mCAM+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;2DACuD;IACvD,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC3B;0DACsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yEAAyE;IACzE,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,yDAAyD;IACzD,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAMD,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAerC,CAAA;AA4BF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI,CAqJ7D"}
@@ -17,6 +17,8 @@
17
17
  import z from '@deepseek-ai/schemastery';
18
18
  import { ActivityTracker } from './status.js';
19
19
  import { registerActivityEventType } from './registration.js';
20
+ import { setLangOverride, t } from './lang.js';
21
+ import { DEFAULT_PRESET } from './frames.js';
20
22
  export const name = 'working-activity';
21
23
  // Explicit annotation: the inferred z.dict output references cosmokit's
22
24
  // Dict through a pnpm-virtual path, which is not portable in declaration
@@ -30,9 +32,14 @@ export const Config = z.object({
30
32
  detailLimit: z.number().step(1).min(8).max(120).default(40),
31
33
  customActions: z.dict(z.array(z.string())).default({}),
32
34
  narrate: z.boolean().default(true),
35
+ lang: z.union(['auto', 'zh', 'en']).default('auto'),
36
+ frames: z.string().default(DEFAULT_PRESET),
37
+ mode: z.union(['lively', 'minimal']).default('lively'),
38
+ features: z.dict(z.boolean()).default({}),
39
+ customPhrases: z.array(z.string()).default([]),
40
+ showTokPerSec: z.boolean().default(false),
41
+ workRemindAt: z.number().min(0).max(24).default(0),
33
42
  });
34
- /** The self-narration contract injected into the system prompt (narrate on). */
35
- const NARRATE_INSTRUCTION = '[状态栏] 你有一个状态栏展示给用户。【必须】在每个步骤/子任务开始时(不只是调用工具前),在回复正文的最前面单独写一行:⏵ 你在做的具体事情(不超过20字),然后换行继续正常回复。整轮回复只写一行 ⏵,不要重复。信息为主——让人一眼知道你在干什么,风格自然、可以带点俏皮。例:⏵ 修复登录页样式、⏵ 查一下报错原因、⏵ 给补丁跑个验证。切换任务时必须更新。';
36
43
  /**
37
44
  * Wire the working-activity plugin.
38
45
  * @param ctx - Cordis context (agent loop + session services composed).
@@ -46,14 +53,26 @@ export function apply(ctx, config = {}) {
46
53
  // in processes where publishing itself is off. See registration.ts.
47
54
  registerActivityEventType();
48
55
  const resolved = {
49
- phrases: config.phrases ?? true,
56
+ // `mode: minimal` renders functional labels only (pi extension parity).
57
+ phrases: config.phrases ?? config.mode !== 'minimal',
50
58
  publish: config.publish ?? false,
51
59
  tickMs: config.tickMs ?? 500,
52
60
  publishIntervalMs: config.publishIntervalMs ?? 2000,
53
61
  detailLimit: config.detailLimit ?? 40,
54
62
  narrate: config.narrate ?? true,
63
+ lang: config.lang ?? 'auto',
55
64
  customActions: config.customActions ?? {},
65
+ frames: config.frames ?? DEFAULT_PRESET,
66
+ mode: config.mode ?? 'lively',
67
+ features: config.features ?? {},
68
+ customPhrases: config.customPhrases ?? [],
69
+ showTokPerSec: config.showTokPerSec ?? false,
70
+ workRemindAt: config.workRemindAt ?? 0,
56
71
  };
72
+ // A pinned plugin-level language beats the env/file chain; releasing it on
73
+ // dispose restores `auto` for any other composition in the process.
74
+ setLangOverride(resolved.lang);
75
+ ctx.effect(() => () => setLangOverride('auto'), 'working-activity lang override');
57
76
  const trackers = new Map();
58
77
  let activeSession;
59
78
  let lastPublishedLine;
@@ -65,20 +84,30 @@ export function apply(ctx, config = {}) {
65
84
  const promptHandle = prompt?.register('activity', undefined);
66
85
  // The `⏵` self-narration contract rides the stable system-prompt sections:
67
86
  // injected when the systemPrompt service is composed (agent assemblies
68
- // always mount it), removed with this fiber.
87
+ // always mount it), removed with this fiber. The text is resolved at every
88
+ // assembly in the live language, so a `/lang` switch applies to the next
89
+ // turn without rebuilding the agent.
69
90
  if (resolved.narrate) {
70
91
  ctx.inject(['systemPrompt'], (promptCtx) => {
71
92
  promptCtx.systemPrompt.section({
72
93
  name: 'working-activity:narrate',
73
94
  order: 60,
74
- text: NARRATE_INSTRUCTION,
95
+ text: () => t('narrate-instruction'),
75
96
  });
76
97
  });
77
98
  }
78
99
  const trackerFor = (session) => {
79
100
  let tracker = trackers.get(session);
80
101
  if (tracker === undefined) {
81
- tracker = new ActivityTracker({ phrases: resolved.phrases, detailLimit: resolved.detailLimit, showIdle: false }, Date.now, resolved.customActions);
102
+ tracker = new ActivityTracker({
103
+ phrases: resolved.phrases,
104
+ detailLimit: resolved.detailLimit,
105
+ showIdle: false,
106
+ features: resolved.features,
107
+ customPhrases: resolved.customPhrases,
108
+ showTokPerSec: resolved.showTokPerSec,
109
+ workRemindAt: resolved.workRemindAt,
110
+ }, Date.now, resolved.customActions);
82
111
  trackers.set(session, tracker);
83
112
  }
84
113
  return tracker;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Language resolution + copy dictionary for the working-activity plugin.
3
+ *
4
+ * The plugin follows the dsh-tui UI language without importing it: the same
5
+ * chain dsh-tui resolves (`DSH_TUI_LANG` env → `~/.dsh-tui/lang.json` → OS
6
+ * locale → zh) is read here directly, so a `/lang en|zh` switch (or the
7
+ * /settings language pick) hot-swaps this plugin's narration and status-line
8
+ * copy on the next render tick.
9
+ *
10
+ * Resolution order:
11
+ * 1. `setLangOverride()` — a plugin-level `lang: zh|en` config key
12
+ * (cordis.yml) or an explicit test pin.
13
+ * 2. `DSH_TUI_LANG` env var — pinned at process start.
14
+ * 3. `~/.dsh-tui/lang.json` — the persisted dsh-tui choice, mtime-cached
15
+ * so the per-tick status render never re-reads an unchanged file.
16
+ * 4. OS locale guess (`LC_ALL` / `LC_MESSAGES` / `LANG`); POSIX/C means
17
+ * "no locale selected" and maps to English.
18
+ * 5. `zh` — the original hard-coded language.
19
+ *
20
+ * The dictionary is a flat key → per-language string map; `t(key, params)`
21
+ * substitutes `{{name}}` placeholders. Missing keys render the key itself so
22
+ * a typo is visible instead of silently blank.
23
+ * @module @deepseek-ai/dsh-working-activity/lang
24
+ */
25
+ export type Lang = 'zh' | 'en';
26
+ /** The languages shipped with the plugin, in display order. */
27
+ export declare const LANGS: readonly ["zh", "en"];
28
+ declare const dict: {
29
+ readonly 'narrate-instruction': {
30
+ readonly zh: "[状态栏] 你有一个状态栏展示给用户。【必须】在每个步骤/子任务开始时(不只是调用工具前),在回复正文的最前面单独写一行:⏵ 你在做的具体事情(不超过20字),然后换行继续正常回复。整轮回复只写一行 ⏵,不要重复。信息为主——让人一眼知道你在干什么,风格自然、可以带点俏皮。例:⏵ 修复登录页样式、⏵ 查一下报错原因、⏵ 给补丁跑个验证。切换任务时必须更新。";
31
+ readonly en: "[Status line] You have a status line visible to the user. [Required] At the start of each step or subtask (not only before tool calls), write exactly one standalone line at the very beginning of your response: ⏵ a concrete description of what you are doing (20 words max), then continue with the normal response on the next line. Write only one ⏵ line per response and do not repeat it. Prioritize information so the user can understand the current work at a glance; keep the style natural and optionally playful. Examples: ⏵ Fixing the login page styles, ⏵ Investigating the error, ⏵ Running validation for the patch. Update it when the task changes.";
32
+ };
33
+ /** Plain (non-playful) phase labels. */
34
+ readonly 'waiting-label': {
35
+ readonly zh: "等待模型响应";
36
+ readonly en: "Waiting for model";
37
+ };
38
+ readonly 'thinking-label': {
39
+ readonly zh: "思考中";
40
+ readonly en: "Thinking";
41
+ };
42
+ /** Elapsed suffix: zh glues the char, en keeps a space. */
43
+ readonly 'line-elapsed': {
44
+ readonly zh: "总{{elapsed}}";
45
+ readonly en: "total {{elapsed}}";
46
+ };
47
+ /** Plain-mode completion prefix (playful pools pick their own). */
48
+ readonly 'done-prefix': {
49
+ readonly zh: "搞定 ✓";
50
+ readonly en: "Finished";
51
+ };
52
+ /** Turn-completion summary. */
53
+ readonly 'done-summary': {
54
+ readonly zh: "{{tools}} · 想{{thinking}} 干{{tooling}}";
55
+ readonly en: "{{tools}} · thought {{thinking}} worked {{tooling}}";
56
+ };
57
+ readonly 'tool-count-one': {
58
+ readonly zh: "{{count}} 工具";
59
+ readonly en: "{{count}} tool";
60
+ };
61
+ readonly 'tool-count-many': {
62
+ readonly zh: "{{count}} 工具";
63
+ readonly en: "{{count}} tools";
64
+ };
65
+ /** Subagent count in the done summary. */
66
+ readonly 'subagent-count': {
67
+ readonly zh: "子代理 {{count}} 个";
68
+ readonly en: "{{count}} subagents";
69
+ };
70
+ /** Work-reminder copy after `workRemindAt` turn-hours. */
71
+ readonly 'work-remind': {
72
+ readonly zh: "已连续工作 {{hours}} 小时,歇会儿?";
73
+ readonly en: "Worked {{hours}}h straight — take a break?";
74
+ };
75
+ };
76
+ export type I18nKey = keyof typeof dict;
77
+ export type I18nParams = Record<string, string | number>;
78
+ /** Force (or release) the active language; `auto` re-enables the chain. */
79
+ export declare function setLangOverride(lang: Lang | 'auto'): void;
80
+ /** The currently active language. */
81
+ export declare function langNow(): Lang;
82
+ /** Translate a dictionary key, substituting `{{name}}` placeholders. */
83
+ export declare function t(key: I18nKey, params?: I18nParams): string;
84
+ /** Is a value a valid shipped language code? */
85
+ export declare function isLang(value: unknown): value is Lang;
86
+ /**
87
+ * Read the persisted dsh-tui language choice, mtime-cached so the per-tick
88
+ * status render never re-reads an unchanged file. `undefined` when the file
89
+ * is absent or holds no valid `{ lang }` value.
90
+ */
91
+ export declare function readLangFile(): Lang | undefined;
92
+ /**
93
+ * Guess the language from the OS locale (`LC_ALL`, `LC_MESSAGES`, `LANG`),
94
+ * defaulting to `zh`. POSIX/C means "no locale selected" and conventionally
95
+ * maps to English (what CI runners report). An absent locale variable
96
+ * (typical on Windows) defaults to `zh`.
97
+ */
98
+ export declare function detectLocaleLang(): Lang;
99
+ export {};
100
+ //# sourceMappingURL=lang.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lang.d.ts","sourceRoot":"","sources":["../../src/lang.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE9B,+DAA+D;AAC/D,eAAO,MAAM,KAAK,uBAAwB,CAAA;AAK1C,QAAA,MAAM,IAAI;;;;;IAQR,wCAAwC;;;;;;;;;IAGxC,2DAA2D;;;;;IAE3D,mEAAmE;;;;;IAEnE,+BAA+B;;;;;;;;;;;;;IAO/B,0CAA0C;;;;;IAE1C,0DAA0D;;;;;CAElD,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,IAAI,CAAA;AACvC,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;AAKxD,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAEzD;AAED,qCAAqC;AACrC,wBAAgB,OAAO,IAAI,IAAI,CAK9B;AAED,wEAAwE;AACxE,wBAAgB,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,UAAe,GAAG,MAAM,CAM/D;AAED,gDAAgD;AAChD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,IAAI,GAAG,SAAS,CAmB/C;AAKD;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAWvC"}