dsh-working-activity 0.2.4 → 0.2.6

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 ADDED
@@ -0,0 +1,80 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-working-activity",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ //#region \0dsh-css:src/client/WorkingLine.module.css.mjs
9
+ const css = ".KySvIG_line{box-sizing:border-box;width:100%;max-width:var(--dsh-composer-card-max-width);font-family:var(--dsw-font-family);color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;margin:0 auto;padding:4px 16px 0;font-size:13px;line-height:18px;display:flex}.KySvIG_marker{background:var(--dsw-alias-label-tertiary);border-radius:50%;flex:none;width:7px;height:7px;animation:1.6s ease-in-out infinite KySvIG_working-pulse}.KySvIG_line[data-activity-phase=done] .KySvIG_marker{background:var(--dsw-alias-state-business-primary);animation:none}.KySvIG_line[data-activity-phase=tool] .KySvIG_marker{background:var(--dsw-alias-state-business-primary)}.KySvIG_line[data-activity-phase=waiting] .KySvIG_marker{background:var(--dsw-alias-label-tertiary);opacity:.6}.KySvIG_text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.KySvIG_tools{background:var(--dsw-alias-surface-tertiary);min-width:18px;color:var(--dsw-alias-label-secondary);text-align:center;border-radius:9px;flex:none;padding:0 5px;font-size:11px;line-height:18px}@keyframes KySvIG_working-pulse{0%,to{opacity:1}50%{opacity:.35}}";
10
+ const tagId = "dsh-working-activity/WorkingLine.module.css";
11
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
12
+ const tag = document.createElement("style");
13
+ tag.dataset.plugin = "dsh-working-activity";
14
+ tag.dataset.pluginCss = tagId;
15
+ tag.textContent = css;
16
+ document.head.appendChild(tag);
17
+ }
18
+ var WorkingLine_module_css_default = {
19
+ "working-pulse": "KySvIG_working-pulse",
20
+ "text": "KySvIG_text",
21
+ "marker": "KySvIG_marker",
22
+ "tools": "KySvIG_tools",
23
+ "line": "KySvIG_line"
24
+ };
25
+ //#endregion
26
+ //#region src/client/WorkingLine.tsx
27
+ /** Tool-count badge copy (no locale seat: the line text itself is host-composed). */
28
+ const TOOLS_LABEL = "tools this turn";
29
+ /**
30
+ * Working-line dock entry: reads the latest activity snapshot off the
31
+ * conversation snapshot and renders the row, or nothing when idle/absent.
32
+ */
33
+ function WorkingLine({ useSession }) {
34
+ const activity = useSession((s) => s.activity ?? null);
35
+ if (activity === null || activity.phase === "idle" || activity.line === "") return null;
36
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
37
+ className: WorkingLine_module_css_default.line,
38
+ "data-activity-phase": activity.phase,
39
+ children: [
40
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
41
+ className: WorkingLine_module_css_default.marker,
42
+ "aria-hidden": "true"
43
+ }),
44
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45
+ className: WorkingLine_module_css_default.text,
46
+ children: activity.line
47
+ }),
48
+ activity.toolCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
49
+ className: WorkingLine_module_css_default.tools,
50
+ title: `${activity.toolCount} ${TOOLS_LABEL}`,
51
+ children: activity.toolCount
52
+ })
53
+ ]
54
+ });
55
+ }
56
+ //#endregion
57
+ //#region src/client/index.ts
58
+ /** Required services for the dock registration. */
59
+ const inject = ["slots"];
60
+ /**
61
+ * Client plugin body: the working-line dock entry.
62
+ * @param ctx - client root context.
63
+ */
64
+ function apply(ctx) {
65
+ ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
66
+ name: "conversation.input.dock",
67
+ id: "activity",
68
+ order: 15,
69
+ registrant: "dsh-working-activity"
70
+ }, WorkingLine));
71
+ }
72
+ //#endregion
73
+ exports.WorkingLine = WorkingLine;
74
+ exports.apply = apply;
75
+ exports.inject = inject;
76
+ return module.exports;
77
+ }
78
+ });
79
+
80
+ //# sourceMappingURL=client.js.map
@@ -0,0 +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 +1 @@
1
- {"version":3,"file":"WorkingLine.d.ts","sourceRoot":"","sources":["../../../src/client/WorkingLine.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAGpE,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAA;AAKtE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,gBAAgB,sCAc3D"}
1
+ {"version":3,"file":"WorkingLine.d.ts","sourceRoot":"","sources":["../../../src/client/WorkingLine.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAGpE,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAA;AAKtE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,gBAAgB,sCAmB3D"}
@@ -7,7 +7,12 @@ const TOOLS_LABEL = 'tools this turn';
7
7
  * conversation snapshot and renders the row, or nothing when idle/absent.
8
8
  */
9
9
  export function WorkingLine({ useSession }) {
10
- const activity = useSession(s => s.activity);
10
+ // Defensive read: the runtime patch that puts `activity` onto the
11
+ // conversation snapshot ships separately — on an unpatched host the field
12
+ // is absent (undefined), not null, and this component must render nothing
13
+ // instead of crashing into the slot error boundary. Same never-throw
14
+ // discipline as the node side's registration.js.
15
+ const activity = useSession(s => s.activity ?? null);
11
16
  if (activity === null || activity.phase === 'idle' || activity.line === '')
12
17
  return null;
13
18
  return (_jsxs("div", { className: css.line, "data-activity-phase": activity.phase, children: [_jsx("span", { className: css.marker, "aria-hidden": "true" }), _jsx("span", { className: css.text, children: activity.line }), activity.toolCount > 0 && (_jsx("span", { className: css.tools, title: `${activity.toolCount} ${TOOLS_LABEL}`, children: activity.toolCount }))] }));
@@ -1 +1 @@
1
- {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../src/registration.ts"],"names":[],"mappings":"AAmCA;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAahD"}
1
+ {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../src/registration.ts"],"names":[],"mappings":"AA8CA;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAyBhD"}
@@ -16,17 +16,27 @@
16
16
  * strict validators consult only THEIR copy's Set. Registering through the
17
17
  * plugin's own import alone would leave the validator's copy untouched.
18
18
  * Anchors: this module (plugin/profile tree) and the process entry point
19
- * (the CLI tree the persistence backend resolves from). A copy that cannot
20
- * be resolved from an anchor simply is not there; registration never throws.
19
+ * (the CLI tree the persistence backend resolves from). Each anchor covers
20
+ * its own tree's top-level copy, then walks one edge further through
21
+ * `@deepseek-ai/dsh-session-persistence`'s own resolution — because the
22
+ * strict validators (persistence load / resume seed checks) consult the
23
+ * copy THEY resolve, a third physical copy under nested or split-tree
24
+ * layouts that neither anchor's top-level resolution can reach. Copies are
25
+ * deduped by realpath; a copy that cannot be resolved from an anchor simply
26
+ * is not there; registration never throws.
21
27
  *
22
28
  * Self-adjusting per the compat house rules: when upstream's generated
23
29
  * catalog adopts `activity/status` (or a real registration API ships), the
24
30
  * add() calls are no-ops and this module can be deleted.
25
31
  * @module dsh-working-activity/registration
26
32
  */
33
+ import { realpathSync } from 'node:fs';
27
34
  import { createRequire } from 'node:module';
28
35
  /** The session-event type this plugin publishes. */
29
36
  const ACTIVITY_EVENT_TYPE = 'activity/status';
37
+ /** The package whose own resolution chain leads to the validators' dsh-session copy. */
38
+ const PERSISTENCE_PACKAGE = '@deepseek-ai/dsh-session-persistence';
39
+ const SESSION_PACKAGE = '@deepseek-ai/dsh-session';
30
40
  /**
31
41
  * Register `activity/status` as a known session-event type in every
32
42
  * reachable dsh-session copy. Idempotent; silently skips anchors whose
@@ -34,14 +44,51 @@ const ACTIVITY_EVENT_TYPE = 'activity/status';
34
44
  */
35
45
  export function registerActivityEventType() {
36
46
  const anchors = [import.meta.url, process.argv[1]].filter((anchor) => typeof anchor === 'string' && anchor.length > 0);
47
+ /** Realpaths already registered — split trees symlink heavily, and require caches by realpath. */
48
+ const registered = new Set();
37
49
  for (const anchor of anchors) {
50
+ let req;
38
51
  try {
39
- const req = createRequire(anchor);
40
- const mod = req('@deepseek-ai/dsh-session');
41
- mod.KNOWN_SESSION_EVENT_TYPES?.add(ACTIVITY_EVENT_TYPE);
52
+ req = createRequire(anchor);
42
53
  }
43
54
  catch {
44
- // No resolvable dsh-session copy from this anchor nothing to register into.
55
+ continue; // Anchor not on disk (e.g. no argv[1] under some runners) skip.
45
56
  }
57
+ registerSessionCopy(req, registered);
58
+ // The validator edge: persistence's load()/resume seed checks consult
59
+ // THEIR OWN resolved dsh-session copy — a third physical copy under
60
+ // nested/split-tree layouts (CLI/profile split, rc.5↔rc.6 upgrade
61
+ // windows, pnpm nesting) that the anchor's top-level resolution misses.
62
+ try {
63
+ const persistenceReq = createRequire(req.resolve(PERSISTENCE_PACKAGE));
64
+ registerSessionCopy(persistenceReq, registered);
65
+ }
66
+ catch {
67
+ // No persistence package reachable from this anchor — nothing more to cover.
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Register into the dsh-session copy resolved by `req`, once per realpath.
73
+ * @param req - require anchored at the tree (or nested package) to probe.
74
+ * @param registered - realpaths already handled in this registration pass.
75
+ */
76
+ function registerSessionCopy(req, registered) {
77
+ try {
78
+ const resolved = req.resolve(SESSION_PACKAGE);
79
+ let key = resolved;
80
+ try {
81
+ key = realpathSync(resolved);
82
+ }
83
+ catch {
84
+ // Vanished between resolve and realpath — dedupe by the resolved path.
85
+ }
86
+ if (registered.has(key))
87
+ return;
88
+ registered.add(key);
89
+ req(resolved).KNOWN_SESSION_EVENT_TYPES?.add(ACTIVITY_EVENT_TYPE);
90
+ }
91
+ catch {
92
+ // No resolvable dsh-session copy from this anchor — nothing to register into.
46
93
  }
47
94
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-working-activity",
3
3
  "description": "Live model working-status line: playful copy, running tool, turn elapsed — for TUI prompt and Web UI",
4
- "version": "0.2.4",
4
+ "version": "0.2.6",
5
5
  "type": "module",
6
6
  "main": "lib/types/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -40,9 +40,20 @@
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
43
- "build:client": "tsdown -c tsdown.config.ts"
43
+ "build:client": "tsdown -c tsdown.config.ts",
44
+ "verify:deps": "node scripts/verify-manifest-deps.mjs",
45
+ "verify:lockfiles": "node scripts/verify-lockfiles.mjs",
46
+ "prepublishOnly": "npm run verify:deps && npm run verify:lockfiles && npm run build && npm run build:client"
44
47
  },
45
48
  "license": "BSD-3-Clause",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/ccch1mneyyy/working-activity.git"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/ccch1mneyyy/working-activity/issues"
55
+ },
56
+ "homepage": "https://github.com/ccch1mneyyy/working-activity#readme",
46
57
  "dsh": {
47
58
  "bundle": {
48
59
  "patch": "./cordis.patch.yml"
@@ -56,9 +67,6 @@
56
67
  ]
57
68
  }
58
69
  },
59
- "dependencies": {
60
- "@deepseek-ai/schemastery": "^3.18.1"
61
- },
62
70
  "peerDependencies": {
63
71
  "@deepseek-ai/cordis": "^4.0.1",
64
72
  "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
@@ -68,6 +76,7 @@
68
76
  "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
69
77
  "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
70
78
  "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
79
+ "@deepseek-ai/schemastery": "^3.18.1",
71
80
  "react": "^18.2.0"
72
81
  },
73
82
  "devDependencies": {
@@ -83,6 +92,7 @@
83
92
  "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
84
93
  "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
85
94
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
95
+ "@deepseek-ai/schemastery": "^3.18.1",
86
96
  "@types/node": "^22.0.0",
87
97
  "@types/react": "~18.3.1",
88
98
  "lightningcss": "^1.32.0",
@@ -1,74 +1,74 @@
1
- /* Working line row: one dim line with a phase-colored breathing marker and
2
- the turn's tool-count badge, sized to the composer dock inset like the
3
- queue strip. Mirrors the pre-slots patch's WorkingLine visuals, re-targeted
4
- at the rc.6 design tokens. */
5
-
6
- .line {
7
- box-sizing: border-box;
8
- display: flex;
9
- align-items: center;
10
- gap: 8px;
11
- width: 100%;
12
- max-width: var(--dsh-composer-card-max-width);
13
- margin: 0 auto;
14
- padding: 4px 16px 0;
15
- font-family: var(--dsw-font-family);
16
- font-size: 13px;
17
- line-height: 18px;
18
- color: var(--dsw-alias-label-secondary);
19
- }
20
-
21
- .marker {
22
- flex: none;
23
- width: 7px;
24
- height: 7px;
25
- border-radius: 50%;
26
- background: var(--dsw-alias-label-tertiary);
27
- animation: working-pulse 1.6s ease-in-out infinite;
28
- }
29
-
30
- /* The model's turn is done: steady marker in the brand tone, no pulse. */
31
- .line[data-activity-phase='done'] .marker {
32
- background: var(--dsw-alias-state-business-primary);
33
- animation: none;
34
- }
35
-
36
- /* A tool is actually executing: brand-tone pulse. */
37
- .line[data-activity-phase='tool'] .marker {
38
- background: var(--dsw-alias-state-business-primary);
39
- }
40
-
41
- /* Waiting for the first token: muted pulse. */
42
- .line[data-activity-phase='waiting'] .marker {
43
- background: var(--dsw-alias-label-tertiary);
44
- opacity: 0.6;
45
- }
46
-
47
- .text {
48
- overflow: hidden;
49
- text-overflow: ellipsis;
50
- white-space: nowrap;
51
- }
52
-
53
- .tools {
54
- flex: none;
55
- min-width: 18px;
56
- padding: 0 5px;
57
- border-radius: 9px;
58
- background: var(--dsw-alias-surface-tertiary);
59
- color: var(--dsw-alias-label-secondary);
60
- font-size: 11px;
61
- line-height: 18px;
62
- text-align: center;
63
- }
64
-
65
- @keyframes working-pulse {
66
- 0%,
67
- 100% {
68
- opacity: 1;
69
- }
70
-
71
- 50% {
72
- opacity: 0.35;
73
- }
74
- }
1
+ /* Working line row: one dim line with a phase-colored breathing marker and
2
+ the turn's tool-count badge, sized to the composer dock inset like the
3
+ queue strip. Mirrors the pre-slots patch's WorkingLine visuals, re-targeted
4
+ at the rc.6 design tokens. */
5
+
6
+ .line {
7
+ box-sizing: border-box;
8
+ display: flex;
9
+ align-items: center;
10
+ gap: 8px;
11
+ width: 100%;
12
+ max-width: var(--dsh-composer-card-max-width);
13
+ margin: 0 auto;
14
+ padding: 4px 16px 0;
15
+ font-family: var(--dsw-font-family);
16
+ font-size: 13px;
17
+ line-height: 18px;
18
+ color: var(--dsw-alias-label-secondary);
19
+ }
20
+
21
+ .marker {
22
+ flex: none;
23
+ width: 7px;
24
+ height: 7px;
25
+ border-radius: 50%;
26
+ background: var(--dsw-alias-label-tertiary);
27
+ animation: working-pulse 1.6s ease-in-out infinite;
28
+ }
29
+
30
+ /* The model's turn is done: steady marker in the brand tone, no pulse. */
31
+ .line[data-activity-phase='done'] .marker {
32
+ background: var(--dsw-alias-state-business-primary);
33
+ animation: none;
34
+ }
35
+
36
+ /* A tool is actually executing: brand-tone pulse. */
37
+ .line[data-activity-phase='tool'] .marker {
38
+ background: var(--dsw-alias-state-business-primary);
39
+ }
40
+
41
+ /* Waiting for the first token: muted pulse. */
42
+ .line[data-activity-phase='waiting'] .marker {
43
+ background: var(--dsw-alias-label-tertiary);
44
+ opacity: 0.6;
45
+ }
46
+
47
+ .text {
48
+ overflow: hidden;
49
+ text-overflow: ellipsis;
50
+ white-space: nowrap;
51
+ }
52
+
53
+ .tools {
54
+ flex: none;
55
+ min-width: 18px;
56
+ padding: 0 5px;
57
+ border-radius: 9px;
58
+ background: var(--dsw-alias-surface-tertiary);
59
+ color: var(--dsw-alias-label-secondary);
60
+ font-size: 11px;
61
+ line-height: 18px;
62
+ text-align: center;
63
+ }
64
+
65
+ @keyframes working-pulse {
66
+ 0%,
67
+ 100% {
68
+ opacity: 1;
69
+ }
70
+
71
+ 50% {
72
+ opacity: 0.35;
73
+ }
74
+ }
@@ -1,37 +1,42 @@
1
- // Working-line dock entry: one dim full-width row above the composer card
2
- // showing the live working-activity snapshot — phase-colored breathing
3
- // marker, the host-composed status line, and the turn's tool count badge.
4
- // Renders for every live phase (waiting/thinking/tool) and the done summary;
5
- // hides while idle or before the first publish.
6
- //
7
- // The 'conversation.input.dock' SlotMap declaration lives in
8
- // @deepseek-ai/dsh-client-ui-conversation/client (contract/slots.ts); this
9
- // entry contributes into it without owning it.
10
- import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
11
- import css from './WorkingLine.module.css'
12
-
13
- /** Full props of the dock entry: the input-zone runtime share (session standard kit). */
14
- export type WorkingLineProps = PropsRuntime<'conversation.input.dock'>
15
-
16
- /** Tool-count badge copy (no locale seat: the line text itself is host-composed). */
17
- const TOOLS_LABEL = 'tools this turn'
18
-
19
- /**
20
- * Working-line dock entry: reads the latest activity snapshot off the
21
- * conversation snapshot and renders the row, or nothing when idle/absent.
22
- */
23
- export function WorkingLine({ useSession }: WorkingLineProps) {
24
- const activity = useSession(s => s.activity)
25
- if (activity === null || activity.phase === 'idle' || activity.line === '') return null
26
- return (
27
- <div className={css.line} data-activity-phase={activity.phase}>
28
- <span className={css.marker} aria-hidden="true" />
29
- <span className={css.text}>{activity.line}</span>
30
- {activity.toolCount > 0 && (
31
- <span className={css.tools} title={`${activity.toolCount} ${TOOLS_LABEL}`}>
32
- {activity.toolCount}
33
- </span>
34
- )}
35
- </div>
36
- )
37
- }
1
+ // Working-line dock entry: one dim full-width row above the composer card
2
+ // showing the live working-activity snapshot — phase-colored breathing
3
+ // marker, the host-composed status line, and the turn's tool count badge.
4
+ // Renders for every live phase (waiting/thinking/tool) and the done summary;
5
+ // hides while idle or before the first publish.
6
+ //
7
+ // The 'conversation.input.dock' SlotMap declaration lives in
8
+ // @deepseek-ai/dsh-client-ui-conversation/client (contract/slots.ts); this
9
+ // entry contributes into it without owning it.
10
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
11
+ import css from './WorkingLine.module.css'
12
+
13
+ /** Full props of the dock entry: the input-zone runtime share (session standard kit). */
14
+ export type WorkingLineProps = PropsRuntime<'conversation.input.dock'>
15
+
16
+ /** Tool-count badge copy (no locale seat: the line text itself is host-composed). */
17
+ const TOOLS_LABEL = 'tools this turn'
18
+
19
+ /**
20
+ * Working-line dock entry: reads the latest activity snapshot off the
21
+ * conversation snapshot and renders the row, or nothing when idle/absent.
22
+ */
23
+ export function WorkingLine({ useSession }: WorkingLineProps) {
24
+ // Defensive read: the runtime patch that puts `activity` onto the
25
+ // conversation snapshot ships separately on an unpatched host the field
26
+ // is absent (undefined), not null, and this component must render nothing
27
+ // instead of crashing into the slot error boundary. Same never-throw
28
+ // discipline as the node side's registration.js.
29
+ const activity = useSession(s => s.activity ?? null)
30
+ if (activity === null || activity.phase === 'idle' || activity.line === '') return null
31
+ return (
32
+ <div className={css.line} data-activity-phase={activity.phase}>
33
+ <span className={css.marker} aria-hidden="true" />
34
+ <span className={css.text}>{activity.line}</span>
35
+ {activity.toolCount > 0 && (
36
+ <span className={css.tools} title={`${activity.toolCount} ${TOOLS_LABEL}`}>
37
+ {activity.toolCount}
38
+ </span>
39
+ )}
40
+ </div>
41
+ )
42
+ }
@@ -1,50 +1,50 @@
1
- /**
2
- * Client-side activity snapshot type + the ConversationSnapshot merge that
3
- * carries it.
4
- *
5
- * The host half (@deepseek-ai/dsh-working-activity's node side) publishes
6
- * `activity/status` session events; the web runtime patch (dsh-client-runtime
7
- * activity field, shipping separately) narrows those frames into the
8
- * conversation snapshot's `activity` member. Until that runtime release lands
9
- * on npm, this module re-declares the field via declaration merging so the
10
- * dock component type-checks against the published rc.6 types.
11
- *
12
- * REMOVE the `declare module` block below once
13
- * `@deepseek-ai/dsh-client-runtime/client` ships `ConversationSnapshot.activity`
14
- * natively (the runtime's own `ActivityStatusView` is structurally identical
15
- * to {@link ActivitySnapshot}; the two then agree by construction).
16
- * @module @deepseek-ai/dsh-working-activity/client/activity
17
- */
18
-
19
- /** The `activity/status` phase vocabulary, mirroring the host's ActivityPhase. */
20
- export type ActivityPhase = 'idle' | 'waiting' | 'thinking' | 'tool' | 'done'
21
-
22
- /** One `activity/status` snapshot as rendered by the dock line. */
23
- export interface ActivitySnapshot {
24
- /** Which activity phase the model is in right now. */
25
- readonly phase: ActivityPhase
26
- /** Full human-readable status line (plain text, no ANSI). */
27
- readonly line: string
28
- /** Short label of the current work (tool action or stage), when any. */
29
- readonly label?: string
30
- /** Detail fragment (path / command / search pattern), when any. */
31
- readonly detail?: string
32
- /** The playful phrase currently shown, when any. */
33
- readonly phrase?: string
34
- /** Tools completed in the current turn. */
35
- readonly toolCount: number
36
- /** Wall-clock milliseconds since the current turn started (0 when idle). */
37
- readonly turnElapsedMs: number
38
- /** Wall-clock time the current phase started, for animations. */
39
- readonly phaseStartedAt: number
40
- }
41
-
42
- declare module '@deepseek-ai/dsh-client-runtime/client' {
43
- interface ConversationSnapshot {
44
- /**
45
- * Latest validated `activity/status` snapshot (the working-activity
46
- * plugin's live working line), or null before the first publish.
47
- */
48
- activity: ActivitySnapshot | null
49
- }
50
- }
1
+ /**
2
+ * Client-side activity snapshot type + the ConversationSnapshot merge that
3
+ * carries it.
4
+ *
5
+ * The host half (@deepseek-ai/dsh-working-activity's node side) publishes
6
+ * `activity/status` session events; the web runtime patch (dsh-client-runtime
7
+ * activity field, shipping separately) narrows those frames into the
8
+ * conversation snapshot's `activity` member. Until that runtime release lands
9
+ * on npm, this module re-declares the field via declaration merging so the
10
+ * dock component type-checks against the published rc.6 types.
11
+ *
12
+ * REMOVE the `declare module` block below once
13
+ * `@deepseek-ai/dsh-client-runtime/client` ships `ConversationSnapshot.activity`
14
+ * natively (the runtime's own `ActivityStatusView` is structurally identical
15
+ * to {@link ActivitySnapshot}; the two then agree by construction).
16
+ * @module @deepseek-ai/dsh-working-activity/client/activity
17
+ */
18
+
19
+ /** The `activity/status` phase vocabulary, mirroring the host's ActivityPhase. */
20
+ export type ActivityPhase = 'idle' | 'waiting' | 'thinking' | 'tool' | 'done'
21
+
22
+ /** One `activity/status` snapshot as rendered by the dock line. */
23
+ export interface ActivitySnapshot {
24
+ /** Which activity phase the model is in right now. */
25
+ readonly phase: ActivityPhase
26
+ /** Full human-readable status line (plain text, no ANSI). */
27
+ readonly line: string
28
+ /** Short label of the current work (tool action or stage), when any. */
29
+ readonly label?: string
30
+ /** Detail fragment (path / command / search pattern), when any. */
31
+ readonly detail?: string
32
+ /** The playful phrase currently shown, when any. */
33
+ readonly phrase?: string
34
+ /** Tools completed in the current turn. */
35
+ readonly toolCount: number
36
+ /** Wall-clock milliseconds since the current turn started (0 when idle). */
37
+ readonly turnElapsedMs: number
38
+ /** Wall-clock time the current phase started, for animations. */
39
+ readonly phaseStartedAt: number
40
+ }
41
+
42
+ declare module '@deepseek-ai/dsh-client-runtime/client' {
43
+ interface ConversationSnapshot {
44
+ /**
45
+ * Latest validated `activity/status` snapshot (the working-activity
46
+ * plugin's live working line), or null before the first publish.
47
+ */
48
+ activity: ActivitySnapshot | null
49
+ }
50
+ }
@@ -1,6 +1,6 @@
1
- /* CSS Modules ambient declaration for the client half (tsc only; the tsdown
2
- bundle inlines compiled class maps). */
3
- declare module '*.module.css' {
4
- const classes: Readonly<Record<string, string>>
5
- export default classes
6
- }
1
+ /* CSS Modules ambient declaration for the client half (tsc only; the tsdown
2
+ bundle inlines compiled class maps). */
3
+ declare module '*.module.css' {
4
+ const classes: Readonly<Record<string, string>>
5
+ export default classes
6
+ }