@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 +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +484 -53
- package/lib/index.js +27 -24
- package/lib/types/client/fixture.d.ts +2 -0
- package/lib/types/index.d.ts +3 -3
- package/package.json +13 -13
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:
|
|
6
|
-
README.zh.md:
|
|
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;
|
|
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
|
|
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
|
|
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, "
|
|
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.
|
|
1775
|
-
if (startIdx === -1) throw new Error(`surface replace: start seq ${op.
|
|
1776
|
-
const endIdx = state.nodes.indexOf(op.
|
|
1777
|
-
if (endIdx === -1) throw new Error(`surface replace: end seq ${op.
|
|
1778
|
-
if (startIdx > endIdx) throw new Error(`surface replace: start seq ${op.
|
|
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 =
|
|
1958
|
+
const surfaceOp = validateSurfaceMetadata(event);
|
|
1834
1959
|
if (surfaceOp === void 0) return;
|
|
1835
|
-
if (surfaceOp === "append") {
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
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.
|
|
1849
|
-
end: surfaceOp.
|
|
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/
|
|
2709
|
+
type: "tool/ptc-dispatch-start",
|
|
2576
2710
|
data: {
|
|
2577
2711
|
rootCallId: callId,
|
|
2578
2712
|
parentCallId: callId,
|
|
2579
|
-
subCallId: `${callId}:
|
|
2713
|
+
subCallId: `${callId}:ptc:${n}`,
|
|
2580
2714
|
name,
|
|
2581
2715
|
arguments: dispatchArgs
|
|
2582
2716
|
}
|
|
2583
2717
|
});
|
|
2584
2718
|
push({
|
|
2585
|
-
type: "tool/
|
|
2719
|
+
type: "tool/ptc-dispatch",
|
|
2586
2720
|
data: {
|
|
2587
2721
|
rootCallId: callId,
|
|
2588
2722
|
parentCallId: callId,
|
|
2589
|
-
subCallId: `${callId}:
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
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(
|
|
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:
|
|
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 = ["
|
|
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
|
-
*
|
|
745
|
-
* the
|
|
746
|
-
*
|
|
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.
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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 {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -31,9 +31,9 @@ export interface ConnectionConfig {
|
|
|
31
31
|
}
|
|
32
32
|
export declare const Config: z<ConnectionConfig>;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
* the
|
|
36
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
52
|
-
"@deepseek-ai/dsh-attachment": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-brand": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
55
|
-
"@deepseek-ai/dsh-host-directory-picker": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-tool-todo": "^0.1.
|
|
60
|
-
"@deepseek-ai/dsh-util-values": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
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
|
}
|