@deepseek-ai/dsh-client-connection 0.1.3-alpha.2 → 0.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/connection/README.md
5
- README.md: 4273c523039ddbc7b0644250800425c555c4c067
6
- README.zh.md: b416659b38a9f63d25842f7926a19eb78842e505
5
+ README.md: 865c894403d2177e15085d0616289cf8950b555d
6
+ README.zh.md: 3d299a4be7b3490298554982eed6d99fbba9718c
package/README.md CHANGED
@@ -25,7 +25,7 @@ The package carries browser-to-Host Remote calls, exact Fetch responses, and con
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
- The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; in-process compositions provide equivalent Remote streams through `connection.rpc.open` without opening a WebSocket. The Host half owns the sole `/api` route, Fetch bridge, browser authentication, Host/Origin checks, and exact `GET`/`HEAD`/`POST` route registry. Each exact route declares buffered or streaming request-body handling before the bridge reads any bytes. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads and raw file uploads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state. Browser raw-body transfer is provided by [`dsh-client-file-upload`](../file-upload/README.md).
28
+ The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; shell-owned compositions provide equivalent Remote streams through `connection.rpc.open` without opening a WebSocket. The Host half always provides the carrier-neutral RPC and exact `GET`/`HEAD`/`POST` route registries. When a Web carrier is present it also owns the sole `/api` route, Fetch bridge, browser authentication, and Host/Origin checks; a shell-owned carrier dispatches the shared Fetch handler directly. Each exact route declares buffered or streaming request-body handling before the bridge reads any bytes. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads and raw file uploads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state. Browser raw-body transfer is provided by [`dsh-client-file-upload`](../file-upload/README.md).
29
29
 
30
30
  -----
31
31
 
package/README.zh.md CHANGED
@@ -25,7 +25,7 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 浏览器通过 HTTP POST 执行 Remote 一元调用。API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。进程内组合通过 `connection.rpc.open` 提供等价的 Remote 流,不打开 WebSocket。Host half 拥有唯一 `/api` route、Fetch bridge、浏览器认证、Host/Origin 校验与精确 `GET`/`HEAD`/`POST` 路由注册表。每条精确路由会在 bridge 读取任何字节前声明缓冲或流式请求体处理方式。Typert Gateway 认领生成的 Remote endpoint,功能包注册 Session 日志下载、原始文件上传等非 JSON 响应,未认领的请求返回 404。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。浏览器原始请求体传输由 [`dsh-client-file-upload`](../file-upload/README.zh.md) 提供。
28
+ 浏览器通过 HTTP POST 执行 Remote 一元调用;API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。由 shell 持有的组合通过 `connection.rpc.open` 提供等价的 Remote 流,不打开 WebSocket。Host half 始终提供与载体无关的 RPC 注册表和精确 `GET`/`HEAD`/`POST` 路由注册表。存在 Web 载体时,它还持有唯一 `/api` route、Fetch bridge、浏览器认证与 Host/Origin 校验;由 shell 持有的载体则直接分派共享 Fetch handler。每条精确路由会在 bridge 读取任何字节前声明缓冲或流式请求体处理方式。Typert Gateway 认领生成的 Remote endpoint,功能包注册 Session 日志下载、原始文件上传等非 JSON 响应,未认领的请求返回 404。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。浏览器原始请求体传输由 [`dsh-client-file-upload`](../file-upload/README.zh.md) 提供。
29
29
 
30
30
  -----
31
31
 
package/lib/client.js CHANGED
@@ -1354,6 +1354,26 @@ window.__ModuleLoader__.load({
1354
1354
  });
1355
1355
  }
1356
1356
  /**
1357
+ * Create and freeze one identified system-role message holding a rendered
1358
+ * system prompt.
1359
+ * @param text - the complete rendered prompt; `''` records "no system prompt".
1360
+ * @param plugin - the plugin that assembled the prompt.
1361
+ * @returns an immutable system message with a fresh stable identity.
1362
+ */
1363
+ function createSystemMessage(text, plugin) {
1364
+ return createMessage({
1365
+ role: "system",
1366
+ content: text.length === 0 ? [] : [{
1367
+ type: "text",
1368
+ text
1369
+ }],
1370
+ source: {
1371
+ kind: "plugin",
1372
+ plugin
1373
+ }
1374
+ });
1375
+ }
1376
+ /**
1357
1377
  * Create and freeze one identified tool-result message.
1358
1378
  * @param input - call identity, raw result blocks, and outcome.
1359
1379
  * @returns an immutable user-role tool-result message.
@@ -1671,6 +1691,84 @@ window.__ModuleLoader__.load({
1671
1691
  return brandNumber(value);
1672
1692
  }
1673
1693
  //#endregion
1694
+ //#region ../../core/session/lib/types/known-event-types.js
1695
+ /**
1696
+ * GENERATED by `scripts/gen-persistence-catalog.ts` — do not edit by hand; run
1697
+ * `pnpm run gen-persistence-catalog` to regenerate (verified fresh by
1698
+ * `pnpm run verify-persistence-catalog`, part of `doc-sync`).
1699
+ * @module @deepseek-ai/dsh-session/known-event-types
1700
+ */
1701
+ /**
1702
+ * Every `SessionEventMap` member declared in this repository — the event
1703
+ * vocabulary this build understands. The persistence read path refuses to
1704
+ * interpret a log containing a type outside this set unless the event
1705
+ * carries the envelope's `ignorable` marker (see `SessionEvent.ignorable`
1706
+ * in `./types.ts`): such a log was likely written by a newer harness, and
1707
+ * silently skipping a required event would reconstruct a wrong session.
1708
+ * Downstream (out-of-repo) plugin events are outside this list by
1709
+ * construction. The persisted `SessionEvent.ignorable` marker is the
1710
+ * compatibility mechanism; event-name registration was rejected because
1711
+ * it does not classify omission safety and would make reads
1712
+ * composition-dependent. The rationale is in
1713
+ * `.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md`.
1714
+ */
1715
+ const KNOWN_SESSION_EVENT_TYPES = new Set([
1716
+ "agent-preset/selected",
1717
+ "agent/inbox/spliced",
1718
+ "approval/asked",
1719
+ "approval/decided",
1720
+ "approval/policy",
1721
+ "assistant/attempt",
1722
+ "assistant/message",
1723
+ "command/done",
1724
+ "command/run",
1725
+ "compaction/end",
1726
+ "compaction/prune",
1727
+ "compaction/start",
1728
+ "compaction/summary",
1729
+ "feedback/message-delete",
1730
+ "feedback/message-put",
1731
+ "feedback/record",
1732
+ "goal/change",
1733
+ "hook/invoked",
1734
+ "hook/result",
1735
+ "llm/retry",
1736
+ "llm/retry-started",
1737
+ "model/selection",
1738
+ "permission/preset",
1739
+ "plan/mode",
1740
+ "request/context",
1741
+ "request/header",
1742
+ "sandbox/mode",
1743
+ "schedule/change",
1744
+ "session-log-deepseek/delivery-accepted",
1745
+ "session/end-seed",
1746
+ "session/title",
1747
+ "session/title-llm-request",
1748
+ "step/end",
1749
+ "step/start",
1750
+ "subagent/descriptor",
1751
+ "subagent/model-selection-policy",
1752
+ "system/message",
1753
+ "team/member",
1754
+ "team/message/delivered",
1755
+ "team/message/queued",
1756
+ "team/task",
1757
+ "todo/write",
1758
+ "tool-workflow/agent-end",
1759
+ "tool-workflow/agent-start",
1760
+ "tool-workflow/run-end",
1761
+ "tool-workflow/run-start",
1762
+ "tool/call",
1763
+ "tool/ptc-dispatch",
1764
+ "tool/ptc-dispatch-start",
1765
+ "tool/result",
1766
+ "turn/end",
1767
+ "turn/start",
1768
+ "user/message",
1769
+ "web/deepseek-search-llm-request"
1770
+ ]);
1771
+ //#endregion
1674
1772
  //#region ../../core/session/lib/types/surface.js
1675
1773
  /**
1676
1774
  * Surface layer on top of the session event log: an ordered view of events
@@ -1683,6 +1781,7 @@ window.__ModuleLoader__.load({
1683
1781
  */
1684
1782
  /** Runtime counterpart of the message-producing event union. */
1685
1783
  const SURFACE_EVENT_TYPES = new Set([
1784
+ "system/message",
1686
1785
  "user/message",
1687
1786
  "assistant/message",
1688
1787
  "tool/result"
@@ -1690,7 +1789,7 @@ window.__ModuleLoader__.load({
1690
1789
  /**
1691
1790
  * Whether an event type can join the model-visible surface.
1692
1791
  * @param type - event type to test.
1693
- * @returns true for one of the three message-producing event types.
1792
+ * @returns true for one of the four message-producing event types.
1694
1793
  */
1695
1794
  function isSurfaceEligibleType(type) {
1696
1795
  return SURFACE_EVENT_TYPES.has(type);
@@ -1711,6 +1810,7 @@ window.__ModuleLoader__.load({
1711
1810
  function deriveEventMessage(event) {
1712
1811
  switch (event.type) {
1713
1812
  case "user/message": return event.data;
1813
+ case "system/message":
1714
1814
  case "assistant/message":
1715
1815
  if (event.data.message.content.length === 0) return null;
1716
1816
  return event.data.message;
@@ -1732,12 +1832,13 @@ window.__ModuleLoader__.load({
1732
1832
  /** Whether a runtime value is the exact positional-replacement shape. */
1733
1833
  function isReplaceOp(value) {
1734
1834
  const op = value;
1735
- return Object.keys(op).length === 3 && Object.hasOwn(op, "op") && Object.hasOwn(op, "start") && Object.hasOwn(op, "end") && op["op"] === "replace" && isEventSeq(op["start"]) && isEventSeq(op["end"]);
1835
+ return Object.keys(op).length === 3 && Object.hasOwn(op, "op") && Object.hasOwn(op, "startSeq") && Object.hasOwn(op, "endSeq") && op["op"] === "replace" && isEventSeq(op["startSeq"]) && isEventSeq(op["endSeq"]);
1736
1836
  }
1737
1837
  /** Validate event-local surface eligibility and return its operation. */
1738
1838
  function surfaceOpOf(event) {
1739
1839
  const raw = event;
1740
1840
  if (!isSurfaceEligibleType(event.type)) {
1841
+ if (!KNOWN_SESSION_EVENT_TYPES.has(event.type) && event.ignorable === true) return;
1741
1842
  if (raw.surfaceOp !== void 0) throw new Error(`session event "${event.type}" is not surface-eligible and cannot carry surfaceOp`);
1742
1843
  if (raw.sourceEventSeqs !== void 0) throw new Error(`session event "${event.type}" is not surface-eligible and cannot carry sourceEventSeqs`);
1743
1844
  return;
@@ -1769,13 +1870,26 @@ window.__ModuleLoader__.load({
1769
1870
  const missing = shadowedSeqs.filter((seq) => !sources.has(seq));
1770
1871
  if (missing.length > 0) throw new Error(`surface replace: sourceEventSeqs must include every shadowed surface node; missing ${missing.join(", ")}`);
1771
1872
  }
1873
+ /**
1874
+ * Validate one event's surface metadata without checking membership in a log or surface.
1875
+ * @param event - event whose marker and source sequence values are inspected.
1876
+ * Unknown ignorable records retain opaque metadata and never change the surface.
1877
+ * @returns the validated operation, or undefined for a log-only or unknown ignorable event.
1878
+ * @throws when metadata violates event-local eligibility, marker, or source-sequence rules.
1879
+ */
1880
+ function validateSurfaceMetadata(event) {
1881
+ const op = surfaceOpOf(event);
1882
+ if (op !== void 0 && op !== "append" && (op.startSeq >= event.seq || op.endSeq >= event.seq)) throw new Error(`surface replace at seq ${event.seq}: startSeq and endSeq must reference earlier events`);
1883
+ if (op !== void 0) assertProvenance(event, []);
1884
+ return op;
1885
+ }
1772
1886
  /** Locate one replacement range without mutating the current fold state. */
1773
1887
  function replacementRange(state, op) {
1774
- const startIdx = state.nodes.indexOf(op.start);
1775
- if (startIdx === -1) throw new Error(`surface replace: start seq ${op.start} not found in surface`);
1776
- const endIdx = state.nodes.indexOf(op.end);
1777
- if (endIdx === -1) throw new Error(`surface replace: end seq ${op.end} not found in surface`);
1778
- if (startIdx > endIdx) throw new Error(`surface replace: start seq ${op.start} (index ${startIdx}) is after end seq ${op.end} (index ${endIdx})`);
1888
+ const startIdx = state.nodes.indexOf(op.startSeq);
1889
+ if (startIdx === -1) throw new Error(`surface replace: start seq ${op.startSeq} not found in surface`);
1890
+ const endIdx = state.nodes.indexOf(op.endSeq);
1891
+ if (endIdx === -1) throw new Error(`surface replace: end seq ${op.endSeq} not found in surface`);
1892
+ if (startIdx > endIdx) throw new Error(`surface replace: start seq ${op.startSeq} (index ${startIdx}) is after end seq ${op.endSeq} (index ${endIdx})`);
1779
1893
  return {
1780
1894
  startIdx,
1781
1895
  endIdx,
@@ -1827,26 +1941,35 @@ window.__ModuleLoader__.load({
1827
1941
  if (!isDeepEqualJson(originalRest, replacementRest)) throw new Error("tool/result surface replacement may change only content");
1828
1942
  }
1829
1943
  }
1944
+ /**
1945
+ * Protect the system prompt at surface node 0. A replacement covering node 0
1946
+ * while that node is a `system/message` must itself be a `system/message` over
1947
+ * exactly that node; later system nodes carry no protection and a compaction
1948
+ * range may shadow them.
1949
+ */
1950
+ function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs, events, baseSeq) {
1951
+ if (startIdx !== 0) return;
1952
+ if (events[state.nodes[0] - baseSeq]?.type !== "system/message") return;
1953
+ if (event.type !== "system/message" || shadowedSeqs.length !== 1) throw new Error("surface replace: node 0 holds the system prompt and may be rewritten only by a system/message over exactly that node");
1954
+ }
1830
1955
  /** Validate one event at its replay boundary and prepare its atomic fold transition. */
1831
1956
  function planSurfaceEvent(state, event, expectedSeq, events, baseSeq) {
1832
1957
  if (event.seq !== expectedSeq) throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
1833
- const surfaceOp = surfaceOpOf(event);
1958
+ const surfaceOp = validateSurfaceMetadata(event);
1834
1959
  if (surfaceOp === void 0) return;
1835
- if (surfaceOp === "append") {
1836
- assertProvenance(event, []);
1837
- return {
1838
- kind: "append",
1839
- seq: event.seq
1840
- };
1841
- }
1960
+ if (surfaceOp === "append") return {
1961
+ kind: "append",
1962
+ seq: event.seq
1963
+ };
1842
1964
  const range = replacementRange(state, surfaceOp);
1843
1965
  assertProvenance(event, range.shadowedSeqs);
1844
1966
  assertToolResultRewrite(event, range.shadowedSeqs, events, baseSeq);
1967
+ assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs, events, baseSeq);
1845
1968
  return {
1846
1969
  kind: "replace",
1847
1970
  seq: event.seq,
1848
- start: surfaceOp.start,
1849
- end: surfaceOp.end,
1971
+ start: surfaceOp.startSeq,
1972
+ end: surfaceOp.endSeq,
1850
1973
  ...range
1851
1974
  };
1852
1975
  }
@@ -2263,6 +2386,8 @@ window.__ModuleLoader__.load({
2263
2386
  });
2264
2387
  return stream;
2265
2388
  }
2389
+ /** Rendered system prompt of the fx-alpha history: surface node 0. */
2390
+ const FIXTURE_SYSTEM_PROMPT = "你是 DeepSeek Harness 的 fixture 助手。用简洁的中文回答,并在需要时调用工具。";
2266
2391
  /** fx-alpha history script: 75 turns (~150+ messages -> 4 pages at PAGE_MESSAGES=50),
2267
2392
  * mixing reasoning blocks / tool call+result / context. */
2268
2393
  function buildAlphaLog() {
@@ -2300,6 +2425,15 @@ window.__ModuleLoader__.load({
2300
2425
  type: "turn/start",
2301
2426
  data: { turn }
2302
2427
  });
2428
+ if (turn === 0) push({
2429
+ type: "system/message",
2430
+ surfaceOp: "append",
2431
+ data: {
2432
+ turn,
2433
+ step: 0,
2434
+ message: createSystemMessage(FIXTURE_SYSTEM_PROMPT, "@deepseek-ai/dsh-system-prompt")
2435
+ }
2436
+ });
2303
2437
  const userSeq = push({
2304
2438
  type: "user/message",
2305
2439
  surfaceOp: "append",
@@ -2572,21 +2706,21 @@ window.__ModuleLoader__.load({
2572
2706
  });
2573
2707
  const dispatchPair = (n, name, dispatchArgs, resultText, isError = false) => {
2574
2708
  push({
2575
- type: "tool/code-dispatch-start",
2709
+ type: "tool/ptc-dispatch-start",
2576
2710
  data: {
2577
2711
  rootCallId: callId,
2578
2712
  parentCallId: callId,
2579
- subCallId: `${callId}:code:${n}`,
2713
+ subCallId: `${callId}:ptc:${n}`,
2580
2714
  name,
2581
2715
  arguments: dispatchArgs
2582
2716
  }
2583
2717
  });
2584
2718
  push({
2585
- type: "tool/code-dispatch",
2719
+ type: "tool/ptc-dispatch",
2586
2720
  data: {
2587
2721
  rootCallId: callId,
2588
2722
  parentCallId: callId,
2589
- subCallId: `${callId}:code:${n}`,
2723
+ subCallId: `${callId}:ptc:${n}`,
2590
2724
  name,
2591
2725
  arguments: dispatchArgs,
2592
2726
  isError,
@@ -2981,19 +3115,30 @@ window.__ModuleLoader__.load({
2981
3115
  return tokens + densityPrice(JSON.stringify(block)) + BLOCK_OVERHEAD;
2982
3116
  }, 0);
2983
3117
  }
2984
- /** Fixture parallel of token-meter's heuristic context-composition projection. */
3118
+ /**
3119
+ * Fixture parallel of token-meter's heuristic context-composition projection.
3120
+ * The system prompt is the system-role surface node; it prices as text plus
3121
+ * role framing with no block overhead and stays out of the message figure.
3122
+ */
2985
3123
  function contextBreakdownOf(log) {
2986
3124
  const headerEvent = log.findLast((event) => event.type === "request/header");
2987
3125
  const header = headerEvent === void 0 ? void 0 : headerEvent.data.header;
3126
+ let systemTokens = 0;
2988
3127
  let messageTokens = 0;
2989
3128
  for (const seq of foldSurface(log).nodes) {
2990
3129
  const event = log[seq];
2991
3130
  if (event === void 0) continue;
2992
3131
  const message = deriveEventMessage(event);
2993
- if (message !== null) messageTokens += estimateFixtureContent(message.content) + ROLE_OVERHEAD;
3132
+ if (message === null) continue;
3133
+ if (message.role === "system") {
3134
+ const characters = message.content.reduce((total, block) => total + (block.type === "text" ? block.text.length : JSON.stringify(block).length), 0);
3135
+ systemTokens = Math.ceil(characters / CHARS_PER_TOKEN) + ROLE_OVERHEAD;
3136
+ continue;
3137
+ }
3138
+ messageTokens += estimateFixtureContent(message.content) + ROLE_OVERHEAD;
2994
3139
  }
2995
3140
  return {
2996
- systemTokens: header?.system === void 0 ? 0 : Math.ceil(header.system.length / CHARS_PER_TOKEN) + ROLE_OVERHEAD,
3141
+ systemTokens,
2997
3142
  toolsTokens: header?.tools === void 0 || header.tools.length === 0 ? 0 : Math.ceil(JSON.stringify(header.tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD,
2998
3143
  messageTokens
2999
3144
  };
@@ -3108,7 +3253,7 @@ window.__ModuleLoader__.load({
3108
3253
  value: contextPressureOf(log),
3109
3254
  seq: event.seq
3110
3255
  });
3111
- if (type === "request/header" || type === "user/message" || type === "assistant/message" || type === "tool/result") frames.push({
3256
+ if (type === "request/header" || type === "system/message" || type === "user/message" || type === "assistant/message" || type === "tool/result") frames.push({
3112
3257
  type: "projection",
3113
3258
  sessionId: id,
3114
3259
  key: "contextBreakdown",
@@ -3400,6 +3545,7 @@ window.__ModuleLoader__.load({
3400
3545
  }
3401
3546
  ];
3402
3547
  const logs = new Map([[sid("fx-alpha"), buildAlphaLog()]]);
3548
+ const goalActivations = /* @__PURE__ */ new Map();
3403
3549
  const modelSelections = new Map(sessions.map((session) => [session.sessionId, {
3404
3550
  provider: "deepseek-official",
3405
3551
  model: "deepseek-v4-flash"
@@ -3818,6 +3964,26 @@ window.__ModuleLoader__.load({
3818
3964
  });
3819
3965
  return backscanGoal(log);
3820
3966
  };
3967
+ /** Update process-local goal activation and publish the forwarded edge. */
3968
+ const setGoalActivation = (id, activation) => {
3969
+ const current = backscanGoal(logOf(id));
3970
+ if (current === null) {
3971
+ if (!goalActivations.delete(id)) return;
3972
+ emitRemote("goal/activation-changed", [{ sessionId: id }]);
3973
+ return;
3974
+ }
3975
+ const previous = goalActivations.get(id);
3976
+ goalActivations.set(id, activation);
3977
+ if (previous === activation) return;
3978
+ emitRemote("goal/activation-changed", [{
3979
+ sessionId: id,
3980
+ goal: {
3981
+ id: current.goal.id,
3982
+ revision: current.goal.revision,
3983
+ activation
3984
+ }
3985
+ }]);
3986
+ };
3821
3987
  const goalFailure = (message) => ({
3822
3988
  ok: false,
3823
3989
  error: {
@@ -3991,21 +4157,25 @@ window.__ModuleLoader__.load({
3991
4157
  let text;
3992
4158
  if (objective === "") text = current === null ? "No goal is set. Usage: /goal <objective>" : `Current goal: ${current.goal.objective}`;
3993
4159
  else if (current !== null && current.goal.phase !== "complete") text = `A goal already exists (${current.goal.objective}). Clear it first.`;
3994
- else text = `Goal created: ${appendGoalChange(id, {
3995
- kind: "goal/change",
3996
- version: 1,
3997
- operation: "create",
3998
- goal: {
3999
- id: `fx-goal-${logOf(id).length}`,
4000
- revision: 1,
4001
- objective,
4002
- phase: "active",
4003
- maxGoalRounds: 256
4004
- },
4005
- roundsStarted: 0,
4006
- createdAt: Date.now(),
4007
- updatedAt: Date.now()
4008
- }).goal.objective}`;
4160
+ else {
4161
+ const created = appendGoalChange(id, {
4162
+ kind: "goal/change",
4163
+ version: 1,
4164
+ operation: "create",
4165
+ goal: {
4166
+ id: `fx-goal-${logOf(id).length}`,
4167
+ revision: 1,
4168
+ objective,
4169
+ phase: "active",
4170
+ maxGoalRounds: 256
4171
+ },
4172
+ roundsStarted: 0,
4173
+ createdAt: Date.now(),
4174
+ updatedAt: Date.now()
4175
+ });
4176
+ setGoalActivation(id, "armed");
4177
+ text = `Goal created: ${created.goal.objective}`;
4178
+ }
4009
4179
  const result = {
4010
4180
  kind: "success",
4011
4181
  text
@@ -4073,12 +4243,12 @@ window.__ModuleLoader__.load({
4073
4243
  };
4074
4244
  }
4075
4245
  };
4076
- const goalView = (projection) => ({
4246
+ const goalView = (id, projection) => ({
4077
4247
  ...projection.goal,
4078
4248
  roundsStarted: projection.roundsStarted,
4079
4249
  createdAt: projection.createdAt,
4080
4250
  updatedAt: projection.updatedAt,
4081
- activation: projection.goal.phase === "active" ? "armed" : "disarmed"
4251
+ activation: goalActivations.get(id) ?? (projection.goal.phase === "active" ? "armed" : "disarmed")
4082
4252
  });
4083
4253
  /** Canonical fixture implementation of the generated Goal Remote contract. */
4084
4254
  /** Canonical fixture implementation of the generated reference-discovery Remote contracts. */
@@ -4126,6 +4296,212 @@ window.__ModuleLoader__.load({
4126
4296
  }
4127
4297
  };
4128
4298
  /**
4299
+ * Workspace text reads under `?fixture`.
4300
+ *
4301
+ * The sample content is deliberately more than one shape: the panel's states
4302
+ * (text, oversized, unreadable) are only demonstrable if the fixture can
4303
+ * produce each of them, and a preview that can only ever succeed hides its
4304
+ * own failure rendering.
4305
+ */
4306
+ const WORKSPACE_FILES_ROOT = "/tmp/fixture";
4307
+ const workspaceFileTree = new Map([
4308
+ ["", [
4309
+ {
4310
+ name: ".gitignore",
4311
+ type: "file",
4312
+ size: 24
4313
+ },
4314
+ {
4315
+ name: "dev.sock",
4316
+ type: "other"
4317
+ },
4318
+ {
4319
+ name: "notes",
4320
+ type: "directory"
4321
+ },
4322
+ {
4323
+ name: "package.json",
4324
+ type: "file",
4325
+ size: 512
4326
+ },
4327
+ {
4328
+ name: "README.md",
4329
+ type: "file",
4330
+ size: 640
4331
+ },
4332
+ {
4333
+ name: "src",
4334
+ type: "directory"
4335
+ }
4336
+ ]],
4337
+ ["notes", [{
4338
+ name: "demo.txt",
4339
+ type: "file",
4340
+ size: 14
4341
+ }, {
4342
+ name: "new-demo.txt",
4343
+ type: "file",
4344
+ size: 14
4345
+ }]],
4346
+ ["src", [
4347
+ {
4348
+ name: "config.ts",
4349
+ type: "file",
4350
+ size: 211
4351
+ },
4352
+ {
4353
+ name: "index.ts",
4354
+ type: "file",
4355
+ size: 88
4356
+ },
4357
+ {
4358
+ name: "lib",
4359
+ type: "directory"
4360
+ }
4361
+ ]],
4362
+ ["src/lib", Array.from({ length: 24 }, (_, index) => ({
4363
+ name: `module-${String(index + 1).padStart(2, "0")}.ts`,
4364
+ type: "file",
4365
+ size: 96 + index
4366
+ }))]
4367
+ ]);
4368
+ /** Resolve a `list` argument to its workspace-relative path, or undefined when it leaves the root. */
4369
+ const workspaceFilePath = (path) => {
4370
+ const segments = [];
4371
+ for (const segment of (path.startsWith("/") ? path : `${WORKSPACE_FILES_ROOT}/${path}`).split("/")) {
4372
+ if (segment === "" || segment === ".") continue;
4373
+ if (segment === "..") {
4374
+ segments.pop();
4375
+ continue;
4376
+ }
4377
+ segments.push(segment);
4378
+ }
4379
+ const absolute = `/${segments.join("/")}`;
4380
+ if (absolute !== WORKSPACE_FILES_ROOT && !absolute.startsWith(`${WORKSPACE_FILES_ROOT}/`)) return void 0;
4381
+ return absolute.slice(13);
4382
+ };
4383
+ const WORKSPACE_FILE_PAGE_LINES = 5e3;
4384
+ /**
4385
+ * Sample text for any readable path: a heading plus two lines of copy. The
4386
+ * replayed conversation's `demo` files, and any `huge` path, run past two
4387
+ * default pages so paging can be exercised without a real workspace.
4388
+ */
4389
+ const workspaceFileLines = (path) => {
4390
+ const head = [
4391
+ `# ${path.slice(path.lastIndexOf("/") + 1)}`,
4392
+ "",
4393
+ "fixture 模式下的示例文本,用于验收侧栏的文本预览。",
4394
+ "真实构建从工作区读取同名文件。"
4395
+ ];
4396
+ return path.includes("demo") || path.includes("huge") ? [...head, ...Array.from({ length: 12e3 }, (_, index) => `第 ${index + 5} 行:用于验收分页与滚动的长文本样本。`)] : head;
4397
+ };
4398
+ const workspaceFileRemotes = {
4399
+ list(path) {
4400
+ if (path.length === 0) return {
4401
+ ok: false,
4402
+ error: {
4403
+ code: "gateway/bad-request",
4404
+ message: "path is required",
4405
+ details: {}
4406
+ }
4407
+ };
4408
+ const relative = workspaceFilePath(path);
4409
+ if (relative === void 0) return {
4410
+ ok: false,
4411
+ error: {
4412
+ code: "workspace-file/outside-workspace",
4413
+ message: `${path} is outside the workspace`,
4414
+ details: { path }
4415
+ }
4416
+ };
4417
+ const entries = workspaceFileTree.get(relative);
4418
+ if (entries === void 0) {
4419
+ const cut = relative.lastIndexOf("/");
4420
+ const name = relative.slice(cut + 1);
4421
+ const sibling = workspaceFileTree.get(cut === -1 ? "" : relative.slice(0, cut))?.find((entry) => entry.name === name);
4422
+ if (sibling === void 0) return {
4423
+ ok: false,
4424
+ error: {
4425
+ code: "workspace-file/not-found",
4426
+ message: `no entry at ${path}`,
4427
+ details: { path }
4428
+ }
4429
+ };
4430
+ return {
4431
+ ok: false,
4432
+ error: {
4433
+ code: "workspace-file/not-directory",
4434
+ message: `${path} is a ${sibling.type}`,
4435
+ details: {
4436
+ path,
4437
+ kind: sibling.type === "file" ? "file" : "other"
4438
+ }
4439
+ }
4440
+ };
4441
+ }
4442
+ return {
4443
+ ok: true,
4444
+ value: {
4445
+ path: relative,
4446
+ entries,
4447
+ truncated: relative === "src/lib"
4448
+ }
4449
+ };
4450
+ },
4451
+ read(path, range) {
4452
+ const located = workspaceFileRemotes.stat(path);
4453
+ if (!located.ok) return located;
4454
+ const offset = range.offset ?? 1;
4455
+ const limit = range.limit ?? WORKSPACE_FILE_PAGE_LINES;
4456
+ if (!Number.isInteger(offset) || offset < 1 || !Number.isInteger(limit) || limit < 1 || limit > WORKSPACE_FILE_PAGE_LINES) return {
4457
+ ok: false,
4458
+ error: {
4459
+ code: "gateway/bad-request",
4460
+ message: "offset and limit must be positive integers within the page cap",
4461
+ details: {}
4462
+ }
4463
+ };
4464
+ const lines = workspaceFileLines(path);
4465
+ const page = lines.slice(offset - 1, offset - 1 + limit);
4466
+ return {
4467
+ ok: true,
4468
+ value: {
4469
+ ...located.value,
4470
+ offset,
4471
+ text: page.join("\n"),
4472
+ lines: page.length,
4473
+ eof: offset - 1 + limit >= lines.length
4474
+ }
4475
+ };
4476
+ },
4477
+ stat(path) {
4478
+ if (path.length === 0) return {
4479
+ ok: false,
4480
+ error: {
4481
+ code: "gateway/bad-request",
4482
+ message: "path is required",
4483
+ details: {}
4484
+ }
4485
+ };
4486
+ if (path.endsWith(".png") || path.endsWith(".bin")) return {
4487
+ ok: false,
4488
+ error: {
4489
+ code: "workspace-file/not-text",
4490
+ message: `${path} is not UTF-8 text`,
4491
+ details: { path }
4492
+ }
4493
+ };
4494
+ return {
4495
+ ok: true,
4496
+ value: {
4497
+ absolutePath: path.startsWith("/") ? path : `/${path}`,
4498
+ version: "fx-v1",
4499
+ bytes: new TextEncoder().encode(workspaceFileLines(path).join("\n")).byteLength
4500
+ }
4501
+ };
4502
+ }
4503
+ };
4504
+ /**
4129
4505
  * Canonical fixture implementation of the generated Directory Picker Remote
4130
4506
  * contract. The pick is deterministic — the keyless lanes drive the full
4131
4507
  * pick-then-adopt path without an OS chooser — over the same design-mock
@@ -4192,6 +4568,15 @@ window.__ModuleLoader__.load({
4192
4568
  }
4193
4569
  };
4194
4570
  const goalRemotes = {
4571
+ get(id) {
4572
+ const missing = requireGoalSession(id);
4573
+ if (missing !== void 0) return missing;
4574
+ const current = backscanGoal(logOf(id));
4575
+ return {
4576
+ ok: true,
4577
+ value: current === null ? void 0 : goalView(id, current)
4578
+ };
4579
+ },
4195
4580
  create(id, request) {
4196
4581
  const missing = requireGoalSession(id);
4197
4582
  if (missing !== void 0) return missing;
@@ -4213,6 +4598,7 @@ window.__ModuleLoader__.load({
4213
4598
  createdAt: now,
4214
4599
  updatedAt: now
4215
4600
  });
4601
+ setGoalActivation(id, "armed");
4216
4602
  return {
4217
4603
  ok: true,
4218
4604
  value: { ref: {
@@ -4265,6 +4651,7 @@ window.__ModuleLoader__.load({
4265
4651
  cleared: tombstone,
4266
4652
  clearedAt: Date.now()
4267
4653
  });
4654
+ setGoalActivation(id, "disarmed");
4268
4655
  return {
4269
4656
  ok: true,
4270
4657
  value: tombstone
@@ -4289,17 +4676,21 @@ window.__ModuleLoader__.load({
4289
4676
  const current = resolved.value;
4290
4677
  const goal = next(current);
4291
4678
  if (goal === void 0) return goalFailure(`invalid goal transition from "${current.goal.phase}"`);
4679
+ const currentActivation = goalActivations.get(id) ?? (current.goal.phase === "active" ? "armed" : "disarmed");
4680
+ const activation = goal.phase === "active" ? current.goal.phase === "active" ? currentActivation : "armed" : "disarmed";
4681
+ const projection = appendGoalChange(id, {
4682
+ kind: "goal/change",
4683
+ version: 1,
4684
+ operation: goal.phase === current.goal.phase ? "edit" : goal.phase === "paused" ? "pause" : goal.phase === "active" ? "resume" : "complete",
4685
+ goal,
4686
+ roundsStarted: current.roundsStarted,
4687
+ createdAt: current.createdAt,
4688
+ updatedAt: Date.now()
4689
+ });
4690
+ setGoalActivation(id, activation);
4292
4691
  return {
4293
4692
  ok: true,
4294
- value: goalView(appendGoalChange(id, {
4295
- kind: "goal/change",
4296
- version: 1,
4297
- operation: goal.phase === current.goal.phase ? "edit" : goal.phase === "paused" ? "pause" : goal.phase === "active" ? "resume" : "complete",
4298
- goal,
4299
- roundsStarted: current.roundsStarted,
4300
- createdAt: current.createdAt,
4301
- updatedAt: Date.now()
4302
- }))
4693
+ value: goalView(id, projection)
4303
4694
  };
4304
4695
  }
4305
4696
  /** Canonical fixture implementation of the generated AgentPresets Remote contract. */
@@ -4441,6 +4832,16 @@ window.__ModuleLoader__.load({
4441
4832
  }
4442
4833
  });
4443
4834
  },
4835
+ /** Disarm the single active goal without a durable phase change. */
4836
+ disarmOnlyGoal() {
4837
+ const active = [...logs.entries()].filter(([, log]) => {
4838
+ return backscanGoal(log)?.goal.phase === "active";
4839
+ });
4840
+ if (active.length !== 1) throw new Error(`fixture: expected one active goal, found ${String(active.length)}`);
4841
+ const [session] = active;
4842
+ if (session === void 0) return;
4843
+ setGoalActivation(session[0], "disarmed");
4844
+ },
4444
4845
  /** Start an externally paced reasoning stream for the opt-in browser stress lane. */
4445
4846
  startReasoningChunkStorm(id, chunkCount, chunksPerInterval, intervalMs) {
4446
4847
  if (!Number.isSafeInteger(chunkCount) || chunkCount < 1) throw new Error("fixture: reasoning chunk count must be a positive safe integer");
@@ -5241,6 +5642,30 @@ window.__ModuleLoader__.load({
5241
5642
  workspaceConns.delete(conn);
5242
5643
  }
5243
5644
  }
5645
+ async function* openWorkspaceFileChanges(signal) {
5646
+ signal.throwIfAborted();
5647
+ const conn = new FxInbox();
5648
+ const breakNow = () => {
5649
+ conn.breakNow();
5650
+ };
5651
+ streamBreakers.add(breakNow);
5652
+ const announce = options.fileChanges ? setTimeout(() => {
5653
+ conn.push({
5654
+ kind: "change",
5655
+ change: {
5656
+ absolutePath: `${WORKSPACE_FILES_ROOT}/notes/demo.txt`,
5657
+ version: "fx-demo-v2"
5658
+ }
5659
+ });
5660
+ }, 1e3) : void 0;
5661
+ try {
5662
+ yield { kind: "ready" };
5663
+ yield* conn.drain(signal);
5664
+ } finally {
5665
+ if (announce !== void 0) clearTimeout(announce);
5666
+ streamBreakers.delete(breakNow);
5667
+ }
5668
+ }
5244
5669
  async function* openRemoteEvents(signal) {
5245
5670
  signal.throwIfAborted();
5246
5671
  const clientId = randomUuid();
@@ -5291,7 +5716,7 @@ window.__ModuleLoader__.load({
5291
5716
  yield {
5292
5717
  type: "snapshot",
5293
5718
  header: {
5294
- version: 2,
5719
+ version: 3,
5295
5720
  id: sessionId,
5296
5721
  createdAt: summary.updatedAt,
5297
5722
  ...summary.cwd === void 0 ? {} : { cwd: summary.cwd },
@@ -5525,6 +5950,7 @@ window.__ModuleLoader__.load({
5525
5950
  case "directoryPicker/pick": return Promise.resolve(directoryPickerRemotes.pick());
5526
5951
  case "directoryPicker/list": return Promise.resolve(directoryPickerRemotes.list(args.path));
5527
5952
  case "directoryPicker/createDirectory": return Promise.resolve(directoryPickerRemotes.createDirectory(args.path ?? "", args.name ?? ""));
5953
+ case "goals/get": return Promise.resolve(goalRemotes.get(sessionId));
5528
5954
  case "goals/create": return Promise.resolve(goalRemotes.create(sessionId, {
5529
5955
  objective: request?.objective,
5530
5956
  ...request?.maxGoalRounds === void 0 ? {} : { maxGoalRounds: request.maxGoalRounds }
@@ -5579,6 +6005,9 @@ window.__ModuleLoader__.load({
5579
6005
  }] });
5580
6006
  }
5581
6007
  case "session/openWorkspacePath": return sessionOk({ opened: true });
6008
+ case "workspaceFiles/read": return Promise.resolve(workspaceFileRemotes.read(args.path ?? "", args.range ?? {}));
6009
+ case "workspaceFiles/stat": return Promise.resolve(workspaceFileRemotes.stat(args.path ?? ""));
6010
+ case "workspaceFiles/list": return Promise.resolve(workspaceFileRemotes.list(args.path ?? ""));
5582
6011
  case "session/canOpenWorkspacePath": return Promise.resolve({
5583
6012
  ok: true,
5584
6013
  value: true
@@ -5696,6 +6125,7 @@ window.__ModuleLoader__.load({
5696
6125
  case "session/control": return openControl(signal);
5697
6126
  case "session/follow": return openFollow(args.request, signal);
5698
6127
  case "workspace/follow": return openWorkspace(signal);
6128
+ case "workspaceFiles/changes": return openWorkspaceFileChanges(signal);
5699
6129
  default: throw new Error(`fixture connection stream endpoint ${JSON.stringify(endpoint)} is unavailable`);
5700
6130
  }
5701
6131
  }
@@ -5717,7 +6147,8 @@ window.__ModuleLoader__.load({
5717
6147
  rejectPrompt: query.get("fixturePrompt") === "reject",
5718
6148
  failWorkspaceAttach: query.get("fixtureAttach") === "fail",
5719
6149
  dropSessionCreateResponse: query.get("fixtureSessionCreate") === "drop-response",
5720
- createFrameOrder: query.get("fixtureFrames") === "workspace-first" ? "workspace-first" : "session-first"
6150
+ createFrameOrder: query.get("fixtureFrames") === "workspace-first" ? "workspace-first" : "session-first",
6151
+ fileChanges: query.get("fixtureFileChanges") === "demo"
5721
6152
  };
5722
6153
  }
5723
6154
  //#endregion
package/lib/index.js CHANGED
@@ -733,7 +733,7 @@ function assertImageBodyCapacity(ctx, maxRequestBodyBytes) {
733
733
  if (maxRequestBodyBytes < requiredImageBodyBytes) throw new Error(`client-connection maxRequestBodyBytes (${String(maxRequestBodyBytes)}) must be at least ${String(requiredImageBodyBytes)} for the configured aggregate image limit`);
734
734
  }
735
735
  /** Services required before providing Connection. */
736
- const inject = ["webServer", "credentials"];
736
+ const inject = ["credentials"];
737
737
  const Config = z.object({
738
738
  recovery: ConnectionRecoveryConfigSchema.default({}),
739
739
  trustedHosts: z.array(String).default([]),
@@ -741,9 +741,9 @@ const Config = z.object({
741
741
  maxRequestBodyBytes: z.natural().min(1).default(DEFAULT_MAX_REQUEST_BODY_BYTES)
742
742
  });
743
743
  /**
744
- * Mounts the API gateway under the browser transport prefix. Every request on
745
- * the prefix passes the Host/Origin browser-trust fence and persistent browser
746
- * authentication before dispatch.
744
+ * Provides carrier-neutral RPC and Fetch registries. When `webServer` is
745
+ * present, the plugin also mounts the `/api` browser transport with Host/Origin
746
+ * checks and persistent browser authentication.
747
747
  * @param ctx - Host plugin context.
748
748
  * @param config - resolved plugin config (schema defaults applied).
749
749
  */
@@ -755,28 +755,31 @@ async function apply(ctx, config) {
755
755
  for (const entry of trustedHosts) assertTrustedAuthority(entry);
756
756
  assertImageBodyCapacity(ctx, maxRequestBodyBytes);
757
757
  const connection = new HostConnectionService(ctx, trustedHosts, await BrowserAuth.create(ctx.root, ctx.credentials, cookieMaxAgeDays));
758
- ctx.on("webserver/index-inject", (table) => {
759
- table.push({
760
- kind: "global",
761
- name: "__DSH_CONNECTION_RECOVERY__",
762
- value: recovery
758
+ ctx.inject(["webServer"], (webCtx) => {
759
+ assertImageBodyCapacity(webCtx, maxRequestBodyBytes);
760
+ webCtx.on("webserver/index-inject", (table) => {
761
+ table.push({
762
+ kind: "global",
763
+ name: "__DSH_CONNECTION_RECOVERY__",
764
+ value: recovery
765
+ });
763
766
  });
764
- });
765
- const fetchHandler = connection.createSharedFetchHandler(API_PATH);
766
- const route = {
767
- kind: "prefix",
768
- path: API_PATH,
769
- handler: async (req, res) => {
770
- const rejection = connection.requestRejection(req);
771
- if (rejection !== void 0) {
772
- res.writeHead(rejection);
773
- res.end(rejection === 401 ? "unauthorized" : "forbidden");
774
- return;
767
+ const fetchHandler = connection.createSharedFetchHandler(API_PATH);
768
+ const route = {
769
+ kind: "prefix",
770
+ path: API_PATH,
771
+ handler: async (req, res) => {
772
+ const rejection = connection.requestRejection(req);
773
+ if (rejection !== void 0) {
774
+ res.writeHead(rejection);
775
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
776
+ return;
777
+ }
778
+ await bridge(req, res, fetchHandler, maxRequestBodyBytes);
775
779
  }
776
- await bridge(req, res, fetchHandler, maxRequestBodyBytes);
777
- }
778
- };
779
- ctx.effect(() => ctx.webServer.register(route), "client-connection: /api route");
780
+ };
781
+ webCtx.effect(() => webCtx.webServer.register(route), "client-connection: /api route");
782
+ });
780
783
  ctx.inject(["attachments"], (attachmentCtx) => {
781
784
  assertImageBodyCapacity(attachmentCtx, maxRequestBodyBytes);
782
785
  });
@@ -42,6 +42,8 @@ export interface FixtureOptions {
42
42
  dropSessionCreateResponse?: boolean;
43
43
  /** Order of the two successful create frames. */
44
44
  createFrameOrder?: 'session-first' | 'workspace-first';
45
+ /** Announce one Agent write to `notes/demo.txt` shortly after a `workspaceFiles/changes` stream opens. */
46
+ fileChanges?: boolean;
45
47
  }
46
48
  /** Fixture RPC face over one in-memory state graph. */
47
49
  export interface FixtureWorld {
@@ -31,9 +31,9 @@ export interface ConnectionConfig {
31
31
  }
32
32
  export declare const Config: z<ConnectionConfig>;
33
33
  /**
34
- * Mounts the API gateway under the browser transport prefix. Every request on
35
- * the prefix passes the Host/Origin browser-trust fence and persistent browser
36
- * authentication before dispatch.
34
+ * Provides carrier-neutral RPC and Fetch registries. When `webServer` is
35
+ * present, the plugin also mounts the `/api` browser transport with Host/Origin
36
+ * checks and persistent browser authentication.
37
37
  * @param ctx - Host plugin context.
38
38
  * @param config - resolved plugin config (schema defaults applied).
39
39
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-connection",
3
3
  "description": "Authenticated RPC transport, generation lifecycle, and browser fixture",
4
- "version": "0.1.3-alpha.2",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -35,7 +35,7 @@
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
37
  "zod": "^4.4.3",
38
- "@deepseek-ai/dsh-credentials": "^0.1.3-alpha.2",
38
+ "@deepseek-ai/dsh-credentials": "^0.1.5-alpha.1",
39
39
  "@deepseek-ai/schemastery": "^3.18.2"
40
40
  },
41
41
  "files": [
@@ -48,16 +48,16 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@deepseek-ai/cordis": "^4.0.2",
51
- "@deepseek-ai/dsh-api-session-controller": "^0.1.3-alpha.2",
52
- "@deepseek-ai/dsh-attachment": "^0.1.3-alpha.2",
53
- "@deepseek-ai/dsh-brand": "^0.1.3-alpha.2",
54
- "@deepseek-ai/dsh-commands": "^0.1.3-alpha.2",
55
- "@deepseek-ai/dsh-host-directory-picker": "^0.1.3-alpha.2",
56
- "@deepseek-ai/dsh-host-webserver": "^0.1.3-alpha.2",
57
- "@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
58
- "@deepseek-ai/dsh-settings": "^0.1.3-alpha.2",
59
- "@deepseek-ai/dsh-tool-todo": "^0.1.3-alpha.2",
60
- "@deepseek-ai/dsh-util-values": "^0.1.3-alpha.2",
61
- "@deepseek-ai/dsh-session": "^0.1.3-alpha.2"
51
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-alpha.1",
52
+ "@deepseek-ai/dsh-attachment": "^0.1.5-alpha.1",
53
+ "@deepseek-ai/dsh-brand": "^0.1.5-alpha.1",
54
+ "@deepseek-ai/dsh-commands": "^0.1.5-alpha.1",
55
+ "@deepseek-ai/dsh-host-directory-picker": "^0.1.5-alpha.1",
56
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-alpha.1",
57
+ "@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
58
+ "@deepseek-ai/dsh-settings": "^0.1.5-alpha.1",
59
+ "@deepseek-ai/dsh-tool-todo": "^0.1.5-alpha.1",
60
+ "@deepseek-ai/dsh-util-values": "^0.1.5-alpha.1",
61
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1"
62
62
  }
63
63
  }