@deepseek-ai/dsh-client-connection 0.1.3-alpha.2 → 0.1.5-alpha.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/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,86 @@ 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
+ "deliverables/presented",
1730
+ "feedback/message-delete",
1731
+ "feedback/message-put",
1732
+ "feedback/record",
1733
+ "goal/change",
1734
+ "hook/invoked",
1735
+ "hook/result",
1736
+ "llm/retry",
1737
+ "llm/retry-started",
1738
+ "model/selection",
1739
+ "permission/preset",
1740
+ "plan/mode",
1741
+ "request/context",
1742
+ "request/header",
1743
+ "sandbox/mode",
1744
+ "schedule/change",
1745
+ "session-log-deepseek/delivery-accepted",
1746
+ "session/end-seed",
1747
+ "session/title",
1748
+ "session/title-llm-request",
1749
+ "step/end",
1750
+ "step/start",
1751
+ "subagent/catalog",
1752
+ "subagent/descriptor",
1753
+ "subagent/model-selection-policy",
1754
+ "system/message",
1755
+ "team/member",
1756
+ "team/message/delivered",
1757
+ "team/message/queued",
1758
+ "team/task",
1759
+ "todo/write",
1760
+ "tool-workflow/agent-end",
1761
+ "tool-workflow/agent-start",
1762
+ "tool-workflow/run-end",
1763
+ "tool-workflow/run-start",
1764
+ "tool/call",
1765
+ "tool/ptc-dispatch",
1766
+ "tool/ptc-dispatch-start",
1767
+ "tool/result",
1768
+ "turn/end",
1769
+ "turn/start",
1770
+ "user/message",
1771
+ "web/deepseek-search-llm-request"
1772
+ ]);
1773
+ //#endregion
1674
1774
  //#region ../../core/session/lib/types/surface.js
1675
1775
  /**
1676
1776
  * Surface layer on top of the session event log: an ordered view of events
@@ -1683,6 +1783,7 @@ window.__ModuleLoader__.load({
1683
1783
  */
1684
1784
  /** Runtime counterpart of the message-producing event union. */
1685
1785
  const SURFACE_EVENT_TYPES = new Set([
1786
+ "system/message",
1686
1787
  "user/message",
1687
1788
  "assistant/message",
1688
1789
  "tool/result"
@@ -1690,7 +1791,7 @@ window.__ModuleLoader__.load({
1690
1791
  /**
1691
1792
  * Whether an event type can join the model-visible surface.
1692
1793
  * @param type - event type to test.
1693
- * @returns true for one of the three message-producing event types.
1794
+ * @returns true for one of the four message-producing event types.
1694
1795
  */
1695
1796
  function isSurfaceEligibleType(type) {
1696
1797
  return SURFACE_EVENT_TYPES.has(type);
@@ -1711,6 +1812,7 @@ window.__ModuleLoader__.load({
1711
1812
  function deriveEventMessage(event) {
1712
1813
  switch (event.type) {
1713
1814
  case "user/message": return event.data;
1815
+ case "system/message":
1714
1816
  case "assistant/message":
1715
1817
  if (event.data.message.content.length === 0) return null;
1716
1818
  return event.data.message;
@@ -1732,12 +1834,13 @@ window.__ModuleLoader__.load({
1732
1834
  /** Whether a runtime value is the exact positional-replacement shape. */
1733
1835
  function isReplaceOp(value) {
1734
1836
  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"]);
1837
+ 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
1838
  }
1737
1839
  /** Validate event-local surface eligibility and return its operation. */
1738
1840
  function surfaceOpOf(event) {
1739
1841
  const raw = event;
1740
1842
  if (!isSurfaceEligibleType(event.type)) {
1843
+ if (!KNOWN_SESSION_EVENT_TYPES.has(event.type) && event.ignorable === true) return;
1741
1844
  if (raw.surfaceOp !== void 0) throw new Error(`session event "${event.type}" is not surface-eligible and cannot carry surfaceOp`);
1742
1845
  if (raw.sourceEventSeqs !== void 0) throw new Error(`session event "${event.type}" is not surface-eligible and cannot carry sourceEventSeqs`);
1743
1846
  return;
@@ -1769,13 +1872,26 @@ window.__ModuleLoader__.load({
1769
1872
  const missing = shadowedSeqs.filter((seq) => !sources.has(seq));
1770
1873
  if (missing.length > 0) throw new Error(`surface replace: sourceEventSeqs must include every shadowed surface node; missing ${missing.join(", ")}`);
1771
1874
  }
1875
+ /**
1876
+ * Validate one event's surface metadata without checking membership in a log or surface.
1877
+ * @param event - event whose marker and source sequence values are inspected.
1878
+ * Unknown ignorable records retain opaque metadata and never change the surface.
1879
+ * @returns the validated operation, or undefined for a log-only or unknown ignorable event.
1880
+ * @throws when metadata violates event-local eligibility, marker, or source-sequence rules.
1881
+ */
1882
+ function validateSurfaceMetadata(event) {
1883
+ const op = surfaceOpOf(event);
1884
+ 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`);
1885
+ if (op !== void 0) assertProvenance(event, []);
1886
+ return op;
1887
+ }
1772
1888
  /** Locate one replacement range without mutating the current fold state. */
1773
1889
  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})`);
1890
+ const startIdx = state.nodes.indexOf(op.startSeq);
1891
+ if (startIdx === -1) throw new Error(`surface replace: start seq ${op.startSeq} not found in surface`);
1892
+ const endIdx = state.nodes.indexOf(op.endSeq);
1893
+ if (endIdx === -1) throw new Error(`surface replace: end seq ${op.endSeq} not found in surface`);
1894
+ if (startIdx > endIdx) throw new Error(`surface replace: start seq ${op.startSeq} (index ${startIdx}) is after end seq ${op.endSeq} (index ${endIdx})`);
1779
1895
  return {
1780
1896
  startIdx,
1781
1897
  endIdx,
@@ -1827,26 +1943,35 @@ window.__ModuleLoader__.load({
1827
1943
  if (!isDeepEqualJson(originalRest, replacementRest)) throw new Error("tool/result surface replacement may change only content");
1828
1944
  }
1829
1945
  }
1946
+ /**
1947
+ * Protect the system prompt at surface node 0. A replacement covering node 0
1948
+ * while that node is a `system/message` must itself be a `system/message` over
1949
+ * exactly that node; later system nodes carry no protection and a compaction
1950
+ * range may shadow them.
1951
+ */
1952
+ function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs, events, baseSeq) {
1953
+ if (startIdx !== 0) return;
1954
+ if (events[state.nodes[0] - baseSeq]?.type !== "system/message") return;
1955
+ 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");
1956
+ }
1830
1957
  /** Validate one event at its replay boundary and prepare its atomic fold transition. */
1831
1958
  function planSurfaceEvent(state, event, expectedSeq, events, baseSeq) {
1832
1959
  if (event.seq !== expectedSeq) throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
1833
- const surfaceOp = surfaceOpOf(event);
1960
+ const surfaceOp = validateSurfaceMetadata(event);
1834
1961
  if (surfaceOp === void 0) return;
1835
- if (surfaceOp === "append") {
1836
- assertProvenance(event, []);
1837
- return {
1838
- kind: "append",
1839
- seq: event.seq
1840
- };
1841
- }
1962
+ if (surfaceOp === "append") return {
1963
+ kind: "append",
1964
+ seq: event.seq
1965
+ };
1842
1966
  const range = replacementRange(state, surfaceOp);
1843
1967
  assertProvenance(event, range.shadowedSeqs);
1844
1968
  assertToolResultRewrite(event, range.shadowedSeqs, events, baseSeq);
1969
+ assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs, events, baseSeq);
1845
1970
  return {
1846
1971
  kind: "replace",
1847
1972
  seq: event.seq,
1848
- start: surfaceOp.start,
1849
- end: surfaceOp.end,
1973
+ start: surfaceOp.startSeq,
1974
+ end: surfaceOp.endSeq,
1850
1975
  ...range
1851
1976
  };
1852
1977
  }
@@ -2263,6 +2388,8 @@ window.__ModuleLoader__.load({
2263
2388
  });
2264
2389
  return stream;
2265
2390
  }
2391
+ /** Rendered system prompt of the fx-alpha history: surface node 0. */
2392
+ const FIXTURE_SYSTEM_PROMPT = "你是 DeepSeek Harness 的 fixture 助手。用简洁的中文回答,并在需要时调用工具。";
2266
2393
  /** fx-alpha history script: 75 turns (~150+ messages -> 4 pages at PAGE_MESSAGES=50),
2267
2394
  * mixing reasoning blocks / tool call+result / context. */
2268
2395
  function buildAlphaLog() {
@@ -2300,6 +2427,15 @@ window.__ModuleLoader__.load({
2300
2427
  type: "turn/start",
2301
2428
  data: { turn }
2302
2429
  });
2430
+ if (turn === 0) push({
2431
+ type: "system/message",
2432
+ surfaceOp: "append",
2433
+ data: {
2434
+ turn,
2435
+ step: 0,
2436
+ message: createSystemMessage(FIXTURE_SYSTEM_PROMPT, "@deepseek-ai/dsh-system-prompt")
2437
+ }
2438
+ });
2303
2439
  const userSeq = push({
2304
2440
  type: "user/message",
2305
2441
  surfaceOp: "append",
@@ -2572,21 +2708,21 @@ window.__ModuleLoader__.load({
2572
2708
  });
2573
2709
  const dispatchPair = (n, name, dispatchArgs, resultText, isError = false) => {
2574
2710
  push({
2575
- type: "tool/code-dispatch-start",
2711
+ type: "tool/ptc-dispatch-start",
2576
2712
  data: {
2577
2713
  rootCallId: callId,
2578
2714
  parentCallId: callId,
2579
- subCallId: `${callId}:code:${n}`,
2715
+ subCallId: `${callId}:ptc:${n}`,
2580
2716
  name,
2581
2717
  arguments: dispatchArgs
2582
2718
  }
2583
2719
  });
2584
2720
  push({
2585
- type: "tool/code-dispatch",
2721
+ type: "tool/ptc-dispatch",
2586
2722
  data: {
2587
2723
  rootCallId: callId,
2588
2724
  parentCallId: callId,
2589
- subCallId: `${callId}:code:${n}`,
2725
+ subCallId: `${callId}:ptc:${n}`,
2590
2726
  name,
2591
2727
  arguments: dispatchArgs,
2592
2728
  isError,
@@ -2981,19 +3117,30 @@ window.__ModuleLoader__.load({
2981
3117
  return tokens + densityPrice(JSON.stringify(block)) + BLOCK_OVERHEAD;
2982
3118
  }, 0);
2983
3119
  }
2984
- /** Fixture parallel of token-meter's heuristic context-composition projection. */
3120
+ /**
3121
+ * Fixture parallel of token-meter's heuristic context-composition projection.
3122
+ * The system prompt is the system-role surface node; it prices as text plus
3123
+ * role framing with no block overhead and stays out of the message figure.
3124
+ */
2985
3125
  function contextBreakdownOf(log) {
2986
3126
  const headerEvent = log.findLast((event) => event.type === "request/header");
2987
3127
  const header = headerEvent === void 0 ? void 0 : headerEvent.data.header;
3128
+ let systemTokens = 0;
2988
3129
  let messageTokens = 0;
2989
3130
  for (const seq of foldSurface(log).nodes) {
2990
3131
  const event = log[seq];
2991
3132
  if (event === void 0) continue;
2992
3133
  const message = deriveEventMessage(event);
2993
- if (message !== null) messageTokens += estimateFixtureContent(message.content) + ROLE_OVERHEAD;
3134
+ if (message === null) continue;
3135
+ if (message.role === "system") {
3136
+ const characters = message.content.reduce((total, block) => total + (block.type === "text" ? block.text.length : JSON.stringify(block).length), 0);
3137
+ systemTokens = Math.ceil(characters / CHARS_PER_TOKEN) + ROLE_OVERHEAD;
3138
+ continue;
3139
+ }
3140
+ messageTokens += estimateFixtureContent(message.content) + ROLE_OVERHEAD;
2994
3141
  }
2995
3142
  return {
2996
- systemTokens: header?.system === void 0 ? 0 : Math.ceil(header.system.length / CHARS_PER_TOKEN) + ROLE_OVERHEAD,
3143
+ systemTokens,
2997
3144
  toolsTokens: header?.tools === void 0 || header.tools.length === 0 ? 0 : Math.ceil(JSON.stringify(header.tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD,
2998
3145
  messageTokens
2999
3146
  };
@@ -3108,7 +3255,7 @@ window.__ModuleLoader__.load({
3108
3255
  value: contextPressureOf(log),
3109
3256
  seq: event.seq
3110
3257
  });
3111
- if (type === "request/header" || type === "user/message" || type === "assistant/message" || type === "tool/result") frames.push({
3258
+ if (type === "request/header" || type === "system/message" || type === "user/message" || type === "assistant/message" || type === "tool/result") frames.push({
3112
3259
  type: "projection",
3113
3260
  sessionId: id,
3114
3261
  key: "contextBreakdown",
@@ -3400,6 +3547,7 @@ window.__ModuleLoader__.load({
3400
3547
  }
3401
3548
  ];
3402
3549
  const logs = new Map([[sid("fx-alpha"), buildAlphaLog()]]);
3550
+ const goalActivations = /* @__PURE__ */ new Map();
3403
3551
  const modelSelections = new Map(sessions.map((session) => [session.sessionId, {
3404
3552
  provider: "deepseek-official",
3405
3553
  model: "deepseek-v4-flash"
@@ -3818,6 +3966,26 @@ window.__ModuleLoader__.load({
3818
3966
  });
3819
3967
  return backscanGoal(log);
3820
3968
  };
3969
+ /** Update process-local goal activation and publish the forwarded edge. */
3970
+ const setGoalActivation = (id, activation) => {
3971
+ const current = backscanGoal(logOf(id));
3972
+ if (current === null) {
3973
+ if (!goalActivations.delete(id)) return;
3974
+ emitRemote("goal/activation-changed", [{ sessionId: id }]);
3975
+ return;
3976
+ }
3977
+ const previous = goalActivations.get(id);
3978
+ goalActivations.set(id, activation);
3979
+ if (previous === activation) return;
3980
+ emitRemote("goal/activation-changed", [{
3981
+ sessionId: id,
3982
+ goal: {
3983
+ id: current.goal.id,
3984
+ revision: current.goal.revision,
3985
+ activation
3986
+ }
3987
+ }]);
3988
+ };
3821
3989
  const goalFailure = (message) => ({
3822
3990
  ok: false,
3823
3991
  error: {
@@ -3991,21 +4159,25 @@ window.__ModuleLoader__.load({
3991
4159
  let text;
3992
4160
  if (objective === "") text = current === null ? "No goal is set. Usage: /goal <objective>" : `Current goal: ${current.goal.objective}`;
3993
4161
  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}`;
4162
+ else {
4163
+ const created = appendGoalChange(id, {
4164
+ kind: "goal/change",
4165
+ version: 1,
4166
+ operation: "create",
4167
+ goal: {
4168
+ id: `fx-goal-${logOf(id).length}`,
4169
+ revision: 1,
4170
+ objective,
4171
+ phase: "active",
4172
+ maxGoalRounds: 256
4173
+ },
4174
+ roundsStarted: 0,
4175
+ createdAt: Date.now(),
4176
+ updatedAt: Date.now()
4177
+ });
4178
+ setGoalActivation(id, "armed");
4179
+ text = `Goal created: ${created.goal.objective}`;
4180
+ }
4009
4181
  const result = {
4010
4182
  kind: "success",
4011
4183
  text
@@ -4073,12 +4245,12 @@ window.__ModuleLoader__.load({
4073
4245
  };
4074
4246
  }
4075
4247
  };
4076
- const goalView = (projection) => ({
4248
+ const goalView = (id, projection) => ({
4077
4249
  ...projection.goal,
4078
4250
  roundsStarted: projection.roundsStarted,
4079
4251
  createdAt: projection.createdAt,
4080
4252
  updatedAt: projection.updatedAt,
4081
- activation: projection.goal.phase === "active" ? "armed" : "disarmed"
4253
+ activation: goalActivations.get(id) ?? (projection.goal.phase === "active" ? "armed" : "disarmed")
4082
4254
  });
4083
4255
  /** Canonical fixture implementation of the generated Goal Remote contract. */
4084
4256
  /** Canonical fixture implementation of the generated reference-discovery Remote contracts. */
@@ -4126,6 +4298,212 @@ window.__ModuleLoader__.load({
4126
4298
  }
4127
4299
  };
4128
4300
  /**
4301
+ * Workspace text reads under `?fixture`.
4302
+ *
4303
+ * The sample content is deliberately more than one shape: the panel's states
4304
+ * (text, oversized, unreadable) are only demonstrable if the fixture can
4305
+ * produce each of them, and a preview that can only ever succeed hides its
4306
+ * own failure rendering.
4307
+ */
4308
+ const WORKSPACE_FILES_ROOT = "/tmp/fixture";
4309
+ const workspaceFileTree = new Map([
4310
+ ["", [
4311
+ {
4312
+ name: ".gitignore",
4313
+ type: "file",
4314
+ size: 24
4315
+ },
4316
+ {
4317
+ name: "dev.sock",
4318
+ type: "other"
4319
+ },
4320
+ {
4321
+ name: "notes",
4322
+ type: "directory"
4323
+ },
4324
+ {
4325
+ name: "package.json",
4326
+ type: "file",
4327
+ size: 512
4328
+ },
4329
+ {
4330
+ name: "README.md",
4331
+ type: "file",
4332
+ size: 640
4333
+ },
4334
+ {
4335
+ name: "src",
4336
+ type: "directory"
4337
+ }
4338
+ ]],
4339
+ ["notes", [{
4340
+ name: "demo.txt",
4341
+ type: "file",
4342
+ size: 14
4343
+ }, {
4344
+ name: "new-demo.txt",
4345
+ type: "file",
4346
+ size: 14
4347
+ }]],
4348
+ ["src", [
4349
+ {
4350
+ name: "config.ts",
4351
+ type: "file",
4352
+ size: 211
4353
+ },
4354
+ {
4355
+ name: "index.ts",
4356
+ type: "file",
4357
+ size: 88
4358
+ },
4359
+ {
4360
+ name: "lib",
4361
+ type: "directory"
4362
+ }
4363
+ ]],
4364
+ ["src/lib", Array.from({ length: 24 }, (_, index) => ({
4365
+ name: `module-${String(index + 1).padStart(2, "0")}.ts`,
4366
+ type: "file",
4367
+ size: 96 + index
4368
+ }))]
4369
+ ]);
4370
+ /** Resolve a `list` argument to its workspace-relative path, or undefined when it leaves the root. */
4371
+ const workspaceFilePath = (path) => {
4372
+ const segments = [];
4373
+ for (const segment of (path.startsWith("/") ? path : `${WORKSPACE_FILES_ROOT}/${path}`).split("/")) {
4374
+ if (segment === "" || segment === ".") continue;
4375
+ if (segment === "..") {
4376
+ segments.pop();
4377
+ continue;
4378
+ }
4379
+ segments.push(segment);
4380
+ }
4381
+ const absolute = `/${segments.join("/")}`;
4382
+ if (absolute !== WORKSPACE_FILES_ROOT && !absolute.startsWith(`${WORKSPACE_FILES_ROOT}/`)) return void 0;
4383
+ return absolute.slice(13);
4384
+ };
4385
+ const WORKSPACE_FILE_PAGE_LINES = 5e3;
4386
+ /**
4387
+ * Sample text for any readable path: a heading plus two lines of copy. The
4388
+ * replayed conversation's `demo` files, and any `huge` path, run past two
4389
+ * default pages so paging can be exercised without a real workspace.
4390
+ */
4391
+ const workspaceFileLines = (path) => {
4392
+ const head = [
4393
+ `# ${path.slice(path.lastIndexOf("/") + 1)}`,
4394
+ "",
4395
+ "fixture 模式下的示例文本,用于验收侧栏的文本预览。",
4396
+ "真实构建从工作区读取同名文件。"
4397
+ ];
4398
+ return path.includes("demo") || path.includes("huge") ? [...head, ...Array.from({ length: 12e3 }, (_, index) => `第 ${index + 5} 行:用于验收分页与滚动的长文本样本。`)] : head;
4399
+ };
4400
+ const workspaceFileRemotes = {
4401
+ list(path) {
4402
+ if (path.length === 0) return {
4403
+ ok: false,
4404
+ error: {
4405
+ code: "gateway/bad-request",
4406
+ message: "path is required",
4407
+ details: {}
4408
+ }
4409
+ };
4410
+ const relative = workspaceFilePath(path);
4411
+ if (relative === void 0) return {
4412
+ ok: false,
4413
+ error: {
4414
+ code: "workspace-file/outside-workspace",
4415
+ message: `${path} is outside the workspace`,
4416
+ details: { path }
4417
+ }
4418
+ };
4419
+ const entries = workspaceFileTree.get(relative);
4420
+ if (entries === void 0) {
4421
+ const cut = relative.lastIndexOf("/");
4422
+ const name = relative.slice(cut + 1);
4423
+ const sibling = workspaceFileTree.get(cut === -1 ? "" : relative.slice(0, cut))?.find((entry) => entry.name === name);
4424
+ if (sibling === void 0) return {
4425
+ ok: false,
4426
+ error: {
4427
+ code: "workspace-file/not-found",
4428
+ message: `no entry at ${path}`,
4429
+ details: { path }
4430
+ }
4431
+ };
4432
+ return {
4433
+ ok: false,
4434
+ error: {
4435
+ code: "workspace-file/not-directory",
4436
+ message: `${path} is a ${sibling.type}`,
4437
+ details: {
4438
+ path,
4439
+ kind: sibling.type === "file" ? "file" : "other"
4440
+ }
4441
+ }
4442
+ };
4443
+ }
4444
+ return {
4445
+ ok: true,
4446
+ value: {
4447
+ path: relative,
4448
+ entries,
4449
+ truncated: relative === "src/lib"
4450
+ }
4451
+ };
4452
+ },
4453
+ read(path, range) {
4454
+ const located = workspaceFileRemotes.stat(path);
4455
+ if (!located.ok) return located;
4456
+ const offset = range.offset ?? 1;
4457
+ const limit = range.limit ?? WORKSPACE_FILE_PAGE_LINES;
4458
+ if (!Number.isInteger(offset) || offset < 1 || !Number.isInteger(limit) || limit < 1 || limit > WORKSPACE_FILE_PAGE_LINES) return {
4459
+ ok: false,
4460
+ error: {
4461
+ code: "gateway/bad-request",
4462
+ message: "offset and limit must be positive integers within the page cap",
4463
+ details: {}
4464
+ }
4465
+ };
4466
+ const lines = workspaceFileLines(path);
4467
+ const page = lines.slice(offset - 1, offset - 1 + limit);
4468
+ return {
4469
+ ok: true,
4470
+ value: {
4471
+ ...located.value,
4472
+ offset,
4473
+ text: page.join("\n"),
4474
+ lines: page.length,
4475
+ eof: offset - 1 + limit >= lines.length
4476
+ }
4477
+ };
4478
+ },
4479
+ stat(path) {
4480
+ if (path.length === 0) return {
4481
+ ok: false,
4482
+ error: {
4483
+ code: "gateway/bad-request",
4484
+ message: "path is required",
4485
+ details: {}
4486
+ }
4487
+ };
4488
+ if (path.endsWith(".png") || path.endsWith(".bin")) return {
4489
+ ok: false,
4490
+ error: {
4491
+ code: "workspace-file/not-text",
4492
+ message: `${path} is not UTF-8 text`,
4493
+ details: { path }
4494
+ }
4495
+ };
4496
+ return {
4497
+ ok: true,
4498
+ value: {
4499
+ absolutePath: path.startsWith("/") ? path : `/${path}`,
4500
+ version: "fx-v1",
4501
+ bytes: new TextEncoder().encode(workspaceFileLines(path).join("\n")).byteLength
4502
+ }
4503
+ };
4504
+ }
4505
+ };
4506
+ /**
4129
4507
  * Canonical fixture implementation of the generated Directory Picker Remote
4130
4508
  * contract. The pick is deterministic — the keyless lanes drive the full
4131
4509
  * pick-then-adopt path without an OS chooser — over the same design-mock
@@ -4192,6 +4570,15 @@ window.__ModuleLoader__.load({
4192
4570
  }
4193
4571
  };
4194
4572
  const goalRemotes = {
4573
+ get(id) {
4574
+ const missing = requireGoalSession(id);
4575
+ if (missing !== void 0) return missing;
4576
+ const current = backscanGoal(logOf(id));
4577
+ return {
4578
+ ok: true,
4579
+ value: current === null ? void 0 : goalView(id, current)
4580
+ };
4581
+ },
4195
4582
  create(id, request) {
4196
4583
  const missing = requireGoalSession(id);
4197
4584
  if (missing !== void 0) return missing;
@@ -4213,6 +4600,7 @@ window.__ModuleLoader__.load({
4213
4600
  createdAt: now,
4214
4601
  updatedAt: now
4215
4602
  });
4603
+ setGoalActivation(id, "armed");
4216
4604
  return {
4217
4605
  ok: true,
4218
4606
  value: { ref: {
@@ -4265,6 +4653,7 @@ window.__ModuleLoader__.load({
4265
4653
  cleared: tombstone,
4266
4654
  clearedAt: Date.now()
4267
4655
  });
4656
+ setGoalActivation(id, "disarmed");
4268
4657
  return {
4269
4658
  ok: true,
4270
4659
  value: tombstone
@@ -4289,17 +4678,21 @@ window.__ModuleLoader__.load({
4289
4678
  const current = resolved.value;
4290
4679
  const goal = next(current);
4291
4680
  if (goal === void 0) return goalFailure(`invalid goal transition from "${current.goal.phase}"`);
4681
+ const currentActivation = goalActivations.get(id) ?? (current.goal.phase === "active" ? "armed" : "disarmed");
4682
+ const activation = goal.phase === "active" ? current.goal.phase === "active" ? currentActivation : "armed" : "disarmed";
4683
+ const projection = appendGoalChange(id, {
4684
+ kind: "goal/change",
4685
+ version: 1,
4686
+ operation: goal.phase === current.goal.phase ? "edit" : goal.phase === "paused" ? "pause" : goal.phase === "active" ? "resume" : "complete",
4687
+ goal,
4688
+ roundsStarted: current.roundsStarted,
4689
+ createdAt: current.createdAt,
4690
+ updatedAt: Date.now()
4691
+ });
4692
+ setGoalActivation(id, activation);
4292
4693
  return {
4293
4694
  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
- }))
4695
+ value: goalView(id, projection)
4303
4696
  };
4304
4697
  }
4305
4698
  /** Canonical fixture implementation of the generated AgentPresets Remote contract. */
@@ -4441,6 +4834,16 @@ window.__ModuleLoader__.load({
4441
4834
  }
4442
4835
  });
4443
4836
  },
4837
+ /** Disarm the single active goal without a durable phase change. */
4838
+ disarmOnlyGoal() {
4839
+ const active = [...logs.entries()].filter(([, log]) => {
4840
+ return backscanGoal(log)?.goal.phase === "active";
4841
+ });
4842
+ if (active.length !== 1) throw new Error(`fixture: expected one active goal, found ${String(active.length)}`);
4843
+ const [session] = active;
4844
+ if (session === void 0) return;
4845
+ setGoalActivation(session[0], "disarmed");
4846
+ },
4444
4847
  /** Start an externally paced reasoning stream for the opt-in browser stress lane. */
4445
4848
  startReasoningChunkStorm(id, chunkCount, chunksPerInterval, intervalMs) {
4446
4849
  if (!Number.isSafeInteger(chunkCount) || chunkCount < 1) throw new Error("fixture: reasoning chunk count must be a positive safe integer");
@@ -5241,6 +5644,30 @@ window.__ModuleLoader__.load({
5241
5644
  workspaceConns.delete(conn);
5242
5645
  }
5243
5646
  }
5647
+ async function* openWorkspaceFileChanges(signal) {
5648
+ signal.throwIfAborted();
5649
+ const conn = new FxInbox();
5650
+ const breakNow = () => {
5651
+ conn.breakNow();
5652
+ };
5653
+ streamBreakers.add(breakNow);
5654
+ const announce = options.fileChanges ? setTimeout(() => {
5655
+ conn.push({
5656
+ kind: "change",
5657
+ change: {
5658
+ absolutePath: `${WORKSPACE_FILES_ROOT}/notes/demo.txt`,
5659
+ version: "fx-demo-v2"
5660
+ }
5661
+ });
5662
+ }, 1e3) : void 0;
5663
+ try {
5664
+ yield { kind: "ready" };
5665
+ yield* conn.drain(signal);
5666
+ } finally {
5667
+ if (announce !== void 0) clearTimeout(announce);
5668
+ streamBreakers.delete(breakNow);
5669
+ }
5670
+ }
5244
5671
  async function* openRemoteEvents(signal) {
5245
5672
  signal.throwIfAborted();
5246
5673
  const clientId = randomUuid();
@@ -5291,7 +5718,7 @@ window.__ModuleLoader__.load({
5291
5718
  yield {
5292
5719
  type: "snapshot",
5293
5720
  header: {
5294
- version: 2,
5721
+ version: 3,
5295
5722
  id: sessionId,
5296
5723
  createdAt: summary.updatedAt,
5297
5724
  ...summary.cwd === void 0 ? {} : { cwd: summary.cwd },
@@ -5525,6 +5952,7 @@ window.__ModuleLoader__.load({
5525
5952
  case "directoryPicker/pick": return Promise.resolve(directoryPickerRemotes.pick());
5526
5953
  case "directoryPicker/list": return Promise.resolve(directoryPickerRemotes.list(args.path));
5527
5954
  case "directoryPicker/createDirectory": return Promise.resolve(directoryPickerRemotes.createDirectory(args.path ?? "", args.name ?? ""));
5955
+ case "goals/get": return Promise.resolve(goalRemotes.get(sessionId));
5528
5956
  case "goals/create": return Promise.resolve(goalRemotes.create(sessionId, {
5529
5957
  objective: request?.objective,
5530
5958
  ...request?.maxGoalRounds === void 0 ? {} : { maxGoalRounds: request.maxGoalRounds }
@@ -5579,6 +6007,9 @@ window.__ModuleLoader__.load({
5579
6007
  }] });
5580
6008
  }
5581
6009
  case "session/openWorkspacePath": return sessionOk({ opened: true });
6010
+ case "workspaceFiles/read": return Promise.resolve(workspaceFileRemotes.read(args.path ?? "", args.range ?? {}));
6011
+ case "workspaceFiles/stat": return Promise.resolve(workspaceFileRemotes.stat(args.path ?? ""));
6012
+ case "workspaceFiles/list": return Promise.resolve(workspaceFileRemotes.list(args.path ?? ""));
5582
6013
  case "session/canOpenWorkspacePath": return Promise.resolve({
5583
6014
  ok: true,
5584
6015
  value: true
@@ -5696,6 +6127,7 @@ window.__ModuleLoader__.load({
5696
6127
  case "session/control": return openControl(signal);
5697
6128
  case "session/follow": return openFollow(args.request, signal);
5698
6129
  case "workspace/follow": return openWorkspace(signal);
6130
+ case "workspaceFiles/changes": return openWorkspaceFileChanges(signal);
5699
6131
  default: throw new Error(`fixture connection stream endpoint ${JSON.stringify(endpoint)} is unavailable`);
5700
6132
  }
5701
6133
  }
@@ -5717,7 +6149,8 @@ window.__ModuleLoader__.load({
5717
6149
  rejectPrompt: query.get("fixturePrompt") === "reject",
5718
6150
  failWorkspaceAttach: query.get("fixtureAttach") === "fail",
5719
6151
  dropSessionCreateResponse: query.get("fixtureSessionCreate") === "drop-response",
5720
- createFrameOrder: query.get("fixtureFrames") === "workspace-first" ? "workspace-first" : "session-first"
6152
+ createFrameOrder: query.get("fixtureFrames") === "workspace-first" ? "workspace-first" : "session-first",
6153
+ fileChanges: query.get("fixtureFileChanges") === "demo"
5721
6154
  };
5722
6155
  }
5723
6156
  //#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.2",
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.2",
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.2",
52
+ "@deepseek-ai/dsh-attachment": "^0.1.5-alpha.2",
53
+ "@deepseek-ai/dsh-brand": "^0.1.5-alpha.2",
54
+ "@deepseek-ai/dsh-host-directory-picker": "^0.1.5-alpha.2",
55
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-alpha.2",
56
+ "@deepseek-ai/dsh-commands": "^0.1.5-alpha.2",
57
+ "@deepseek-ai/dsh-llm": "^0.1.5-alpha.2",
58
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.2",
59
+ "@deepseek-ai/dsh-settings": "^0.1.5-alpha.2",
60
+ "@deepseek-ai/dsh-tool-todo": "^0.1.5-alpha.2",
61
+ "@deepseek-ai/dsh-util-values": "^0.1.5-alpha.2"
62
62
  }
63
63
  }