dsh-context 0.52.1 → 0.52.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +70 -28
- package/lib/index.d.ts +11 -0
- package/lib/index.js +42 -31
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1905,10 +1905,10 @@ window.__ModuleLoader__.load({
|
|
|
1905
1905
|
* - INLINE (older hosts, channel-less deployments, the baseline-gate
|
|
1906
1906
|
* fallback): the wire value carries the collections in place. It passes
|
|
1907
1907
|
* through untouched — no fetch ever happens.
|
|
1908
|
-
* - SPLIT (current host with the detail
|
|
1908
|
+
* - SPLIT (current host with the detail route live): the wire value is the
|
|
1909
1909
|
* slim head (~1KB — every session.list row, control baseline, and push
|
|
1910
|
-
* frame carries it whole). The collections arrive from the
|
|
1911
|
-
* `/dsh-context
|
|
1910
|
+
* frame carries it whole). The collections arrive from the plugin's
|
|
1911
|
+
* `/api/dsh-context/detail` fetch route (host/detail.ts): one targeted read
|
|
1912
1912
|
* when the tab/modal first opens, then a debounced refetch whenever the
|
|
1913
1913
|
* pushed `detailRev` outruns the served detail. Closed tabs fetch nothing.
|
|
1914
1914
|
*
|
|
@@ -1923,8 +1923,7 @@ window.__ModuleLoader__.load({
|
|
|
1923
1923
|
* failure surfaces as a retryable note on the detail cards instead of an
|
|
1924
1924
|
* empty chart.
|
|
1925
1925
|
*/
|
|
1926
|
-
const
|
|
1927
|
-
const DETAIL_ENDPOINT = "detail";
|
|
1926
|
+
const DETAIL_ROUTE = "/api/dsh-context/detail";
|
|
1928
1927
|
/**
|
|
1929
1928
|
* Narrow the detail endpoint's payload to a render-safe value (the same
|
|
1930
1929
|
* boundary rigor as `timelineOf`): collections re-proved per item, scalars
|
|
@@ -1935,8 +1934,10 @@ window.__ModuleLoader__.load({
|
|
|
1935
1934
|
const data = asRecord(value);
|
|
1936
1935
|
if (data === null) return null;
|
|
1937
1936
|
if (typeof data.rev !== "number" || !Number.isFinite(data.rev) || data.rev < 0) return null;
|
|
1937
|
+
const head = timelineOf(data.head);
|
|
1938
1938
|
return {
|
|
1939
1939
|
rev: data.rev,
|
|
1940
|
+
...head !== null ? { head } : {},
|
|
1940
1941
|
requests: objectsOf(data.requests),
|
|
1941
1942
|
events: objectsOf(data.events),
|
|
1942
1943
|
nodes: objectsOf(data.nodes),
|
|
@@ -1949,24 +1950,26 @@ window.__ModuleLoader__.load({
|
|
|
1949
1950
|
};
|
|
1950
1951
|
}
|
|
1951
1952
|
/**
|
|
1952
|
-
* The detail reader over the
|
|
1953
|
-
*
|
|
1954
|
-
*
|
|
1955
|
-
*
|
|
1956
|
-
*
|
|
1957
|
-
* session is not live anymore, and rejects on transport failure or a
|
|
1958
|
-
* malformed payload (the store turns the two into the retryable state).
|
|
1953
|
+
* The detail reader over the plugin's own `/api` fetch route (host/detail.ts).
|
|
1954
|
+
* Same-origin POST through the harness's authenticated fence; the returned
|
|
1955
|
+
* thunk resolves the session's current detail, `null` when the session is not
|
|
1956
|
+
* live anymore, and rejects on transport failure or a malformed payload (the
|
|
1957
|
+
* store turns the two into the retryable state).
|
|
1959
1958
|
*/
|
|
1960
1959
|
function makeDetailFetcher(ctx, sessionId) {
|
|
1961
1960
|
if (sessionId === "") return void 0;
|
|
1962
|
-
const call = rpcCallOf(ctx);
|
|
1963
|
-
if (call === void 0) return void 0;
|
|
1964
1961
|
return async () => {
|
|
1965
|
-
const
|
|
1966
|
-
|
|
1962
|
+
const response = await fetch(DETAIL_ROUTE, {
|
|
1963
|
+
method: "POST",
|
|
1964
|
+
headers: { "content-type": "application/json" },
|
|
1965
|
+
body: JSON.stringify({ sessionId })
|
|
1966
|
+
});
|
|
1967
|
+
if (!response.ok) throw new Error(`dsh-context: detail route HTTP ${response.status}`);
|
|
1968
|
+
const r = asRecord(await response.json());
|
|
1969
|
+
if (r === null || r.ok !== true) throw new Error("dsh-context: detail read failed");
|
|
1967
1970
|
if (r.value === null) return null;
|
|
1968
1971
|
const detail = detailOf(r.value);
|
|
1969
|
-
if (detail === null) throw new Error("dsh-context: detail
|
|
1972
|
+
if (detail === null) throw new Error("dsh-context: detail read malformed");
|
|
1970
1973
|
return detail;
|
|
1971
1974
|
};
|
|
1972
1975
|
}
|
|
@@ -6371,8 +6374,15 @@ window.__ModuleLoader__.load({
|
|
|
6371
6374
|
* no current session, or a snapshot without a `byId` table (older harness).
|
|
6372
6375
|
* The current session synthesizes a row when the list has not delivered it
|
|
6373
6376
|
* yet, so the card can still show its live self stats.
|
|
6374
|
-
|
|
6375
|
-
|
|
6377
|
+
* @param fetched - detail-channel heads for COLD rows, keyed by session id.
|
|
6378
|
+
* The host's list block serves projection values only from the projection
|
|
6379
|
+
* cache, so a relative that never attached since the timeline unit last
|
|
6380
|
+
* changed lists pressure-only (no composition row) and its ring would
|
|
6381
|
+
* degrade to the occupancy arc; the card fetches those heads on demand.
|
|
6382
|
+
* A row's own value always wins — the injection lands only on rows whose
|
|
6383
|
+
* `contextTimeline` is absent.
|
|
6384
|
+
*/
|
|
6385
|
+
function agentForestOf(snapshot, currentId, self, fetched) {
|
|
6376
6386
|
const byId = asRecord(asRecord(snapshot)?.byId);
|
|
6377
6387
|
if (byId === null || currentId === void 0 || currentId === "") return null;
|
|
6378
6388
|
const rows = /* @__PURE__ */ new Map();
|
|
@@ -6427,7 +6437,12 @@ window.__ModuleLoader__.load({
|
|
|
6427
6437
|
const visit = (id, row, parentId, depth, seen, family) => {
|
|
6428
6438
|
if (seen.has(id) || nodes.length >= 25) return;
|
|
6429
6439
|
seen.add(id);
|
|
6430
|
-
const
|
|
6440
|
+
const values = row.projections;
|
|
6441
|
+
const fetchedHead = fetched !== void 0 ? fetched.get(id) : void 0;
|
|
6442
|
+
const stats = agentStatsOf(fetchedHead !== void 0 && values?.contextTimeline === void 0 ? {
|
|
6443
|
+
...values,
|
|
6444
|
+
contextTimeline: fetchedHead
|
|
6445
|
+
} : values);
|
|
6431
6446
|
const identity = stats.identity;
|
|
6432
6447
|
const node = {
|
|
6433
6448
|
...stats,
|
|
@@ -6686,9 +6701,13 @@ window.__ModuleLoader__.load({
|
|
|
6686
6701
|
*
|
|
6687
6702
|
* Data rides the harness's existing planes end to end — the session-list
|
|
6688
6703
|
* snapshot (`ctx.sessions.list`: lineage rows + per-session projection
|
|
6689
|
-
* values) and the tab's own projections for the current node
|
|
6690
|
-
*
|
|
6691
|
-
*
|
|
6704
|
+
* values) and the tab's own projections for the current node. The list block
|
|
6705
|
+
* serves projection values only from the host's projection cache, so a
|
|
6706
|
+
* relative that never attached since the timeline unit last changed lists
|
|
6707
|
+
* pressure-only (occupancy without composition); those nodes fetch their slim
|
|
6708
|
+
* head from the plugin's `/api` detail route (the one call below) and
|
|
6709
|
+
* re-render composed. A harness without the outward sessions service hides
|
|
6710
|
+
* the card.
|
|
6692
6711
|
*/
|
|
6693
6712
|
/** Caption box height under a node (3 wrapped label lines + the tokens line). */
|
|
6694
6713
|
const CAPTION_H = 60;
|
|
@@ -6701,6 +6720,7 @@ window.__ModuleLoader__.load({
|
|
|
6701
6720
|
}
|
|
6702
6721
|
function makeAgentGraph(ctx, kit) {
|
|
6703
6722
|
const { t, fmt, catLabel } = kit;
|
|
6723
|
+
const heads = /* @__PURE__ */ new Map();
|
|
6704
6724
|
function AgentGraph(props) {
|
|
6705
6725
|
const face = (0, react.useMemo)(() => sessionsFaceOf(ctx), []);
|
|
6706
6726
|
const subscribe = (0, react.useCallback)((fn) => {
|
|
@@ -6746,8 +6766,9 @@ window.__ModuleLoader__.load({
|
|
|
6746
6766
|
if (typeof face.refreshSubagents !== "function") return;
|
|
6747
6767
|
face.refreshSubagents(sessionId).catch(() => {});
|
|
6748
6768
|
}, [face, sessionId]);
|
|
6769
|
+
const [landed, setLanded] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
6749
6770
|
const built = (0, react.useMemo)(() => {
|
|
6750
|
-
const forest = agentForestOf(snapshot, sessionId, props.self);
|
|
6771
|
+
const forest = agentForestOf(snapshot, sessionId, props.self, landed);
|
|
6751
6772
|
return forest !== null ? {
|
|
6752
6773
|
forest,
|
|
6753
6774
|
layout: layoutForest(forest, stageWidth)
|
|
@@ -6756,8 +6777,29 @@ window.__ModuleLoader__.load({
|
|
|
6756
6777
|
snapshot,
|
|
6757
6778
|
sessionId,
|
|
6758
6779
|
props.self,
|
|
6759
|
-
stageWidth
|
|
6780
|
+
stageWidth,
|
|
6781
|
+
landed
|
|
6760
6782
|
]);
|
|
6783
|
+
(0, react.useEffect)(() => {
|
|
6784
|
+
if (built === null) return;
|
|
6785
|
+
const attach = (pending, id) => {
|
|
6786
|
+
pending.then((head) => {
|
|
6787
|
+
if (head !== null) setLanded((prev) => prev.get(id) === head ? prev : new Map(prev).set(id, head));
|
|
6788
|
+
}).catch(() => {});
|
|
6789
|
+
};
|
|
6790
|
+
for (const n of built.forest.nodes) {
|
|
6791
|
+
if (n.isCurrent || n.head !== null && n.head.parts.length > 0) continue;
|
|
6792
|
+
const cached = heads.get(n.id);
|
|
6793
|
+
if (cached !== void 0) {
|
|
6794
|
+
attach(cached, n.id);
|
|
6795
|
+
continue;
|
|
6796
|
+
}
|
|
6797
|
+
const fetcher = makeDetailFetcher(ctx, n.id);
|
|
6798
|
+
const pending = fetcher !== void 0 ? fetcher().then((d) => d?.head ?? null) : Promise.resolve(null);
|
|
6799
|
+
heads.set(n.id, pending);
|
|
6800
|
+
attach(pending, n.id);
|
|
6801
|
+
}
|
|
6802
|
+
}, [built]);
|
|
6761
6803
|
if (built === null) return null;
|
|
6762
6804
|
const { forest, layout } = built;
|
|
6763
6805
|
const byId = new Map(forest.nodes.map((n) => [n.id, n]));
|
|
@@ -7599,7 +7641,7 @@ window.__ModuleLoader__.load({
|
|
|
7599
7641
|
return function PluginInfo() {
|
|
7600
7642
|
const [latest, setLatest] = (0, react.useState)(null);
|
|
7601
7643
|
(0, react.useEffect)(() => {
|
|
7602
|
-
if ("0.52.
|
|
7644
|
+
if ("0.52.2".includes("-dev")) return;
|
|
7603
7645
|
let on = true;
|
|
7604
7646
|
fetchLatestVersion().then((v) => {
|
|
7605
7647
|
if (on && v) setLatest(v);
|
|
@@ -7608,8 +7650,8 @@ window.__ModuleLoader__.load({
|
|
|
7608
7650
|
on = false;
|
|
7609
7651
|
};
|
|
7610
7652
|
}, []);
|
|
7611
|
-
const update = latest !== null && isNewerVersion(latest, "0.52.
|
|
7612
|
-
const nameText = "dsh-context (v0.52.
|
|
7653
|
+
const update = latest !== null && isNewerVersion(latest, "0.52.2") ? latest : null;
|
|
7654
|
+
const nameText = "dsh-context (v0.52.2)";
|
|
7613
7655
|
const nameValue = [nameText];
|
|
7614
7656
|
if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7615
7657
|
className: "lc-pi-update",
|
package/lib/index.d.ts
CHANGED
|
@@ -446,6 +446,17 @@ interface Snapshot {
|
|
|
446
446
|
*/
|
|
447
447
|
interface ContextTimelineDetail {
|
|
448
448
|
rev: number;
|
|
449
|
+
/**
|
|
450
|
+
* The slim wire head at the SAME fold cut as the collections: the
|
|
451
|
+
* composition scalars (`current`), the window/model envelope, and the
|
|
452
|
+
* precomputed counts. Sessions listed cold (never attached since the
|
|
453
|
+
* requesting unit last changed) carry no `contextTimeline` projection row
|
|
454
|
+
* for the browser's list reads, so the Agent network card fetches this
|
|
455
|
+
* head per node to render their composition rings. The host always serves
|
|
456
|
+
* it; optional so a payload missing it still serves the collections (the
|
|
457
|
+
* detail cards) and only the ring composition degrades.
|
|
458
|
+
*/
|
|
459
|
+
head?: ContextTimeline;
|
|
449
460
|
requests: RequestRecord[];
|
|
450
461
|
events: ContextEventRecord[];
|
|
451
462
|
nodes: SurfaceNode[];
|
package/lib/index.js
CHANGED
|
@@ -1933,11 +1933,14 @@ function buildTimelineHead(state) {
|
|
|
1933
1933
|
}
|
|
1934
1934
|
/**
|
|
1935
1935
|
* The on-demand detail payload (host/detail.ts serves it off the live fold
|
|
1936
|
-
* state): the heavy collections
|
|
1936
|
+
* state): the heavy collections, the slim head at the SAME cut (the Agent
|
|
1937
|
+
* network's cold-node ring fetch renders composition off it), and the
|
|
1938
|
+
* revision marker the head carries.
|
|
1937
1939
|
*/
|
|
1938
1940
|
function buildTimelineDetail(state, bounds) {
|
|
1939
1941
|
return {
|
|
1940
1942
|
rev: state.detailRev ?? 0,
|
|
1943
|
+
head: buildTimelineHead(state),
|
|
1941
1944
|
...detailCollectionsOf(state, bounds)
|
|
1942
1945
|
};
|
|
1943
1946
|
}
|
|
@@ -1956,63 +1959,71 @@ function buildTimelineView(state, bounds) {
|
|
|
1956
1959
|
}
|
|
1957
1960
|
//#endregion
|
|
1958
1961
|
//#region src/host/detail.ts
|
|
1959
|
-
/** The plugin's
|
|
1960
|
-
const
|
|
1961
|
-
/**
|
|
1962
|
+
/** The plugin's detail route, under the authenticated `/api` fence. */
|
|
1963
|
+
const DETAIL_ROUTE = "/api/dsh-context/detail";
|
|
1964
|
+
/** One JSON reply of the detail route. */
|
|
1965
|
+
function reply(value) {
|
|
1966
|
+
return Response.json(value, { headers: { "cache-control": "no-store" } });
|
|
1967
|
+
}
|
|
1968
|
+
/** The typed failure envelope (the same shape the Connection RPC carried). */
|
|
1962
1969
|
function failure(code, message) {
|
|
1963
|
-
return {
|
|
1970
|
+
return reply({
|
|
1964
1971
|
ok: false,
|
|
1965
1972
|
error: {
|
|
1966
1973
|
code,
|
|
1967
|
-
message
|
|
1968
|
-
details: {}
|
|
1974
|
+
message
|
|
1969
1975
|
}
|
|
1970
|
-
};
|
|
1976
|
+
});
|
|
1971
1977
|
}
|
|
1972
1978
|
/**
|
|
1973
|
-
*
|
|
1979
|
+
* Serve the detail route whenever the connection and sessions services are
|
|
1974
1980
|
* both composed (see the module header for the load-order contract). The
|
|
1975
1981
|
* registration rides the injected fiber: either service unloading withdraws
|
|
1976
|
-
* the
|
|
1982
|
+
* the route and closes the gate.
|
|
1977
1983
|
*/
|
|
1978
1984
|
function watchDetailChannel(ctx, bounds) {
|
|
1979
1985
|
const gate = { live: false };
|
|
1980
1986
|
ctx.inject(["connection", "sessions"], (c) => {
|
|
1981
1987
|
const connection = c.get("connection");
|
|
1982
1988
|
const sessions = c.get("sessions");
|
|
1983
|
-
const
|
|
1989
|
+
const register = typeof connection?.fetch?.register === "function" ? connection.fetch.register.bind(connection.fetch) : void 0;
|
|
1984
1990
|
const getSession = typeof sessions?.get === "function" ? sessions.get.bind(sessions) : void 0;
|
|
1985
|
-
if (
|
|
1991
|
+
if (register === void 0 || getSession === void 0) return;
|
|
1986
1992
|
const projections = ctx.sessionProjections;
|
|
1987
|
-
const handler = async (
|
|
1988
|
-
|
|
1989
|
-
|
|
1993
|
+
const handler = async (request) => {
|
|
1994
|
+
let sessionId;
|
|
1995
|
+
try {
|
|
1996
|
+
const body = await request.json();
|
|
1997
|
+
sessionId = body !== null && typeof body === "object" ? body.sessionId : void 0;
|
|
1998
|
+
} catch {
|
|
1999
|
+
return failure("dsh-context/bad-request", "body is not JSON");
|
|
2000
|
+
}
|
|
1990
2001
|
if (typeof sessionId !== "string" || sessionId === "") return failure("dsh-context/bad-request", "missing sessionId");
|
|
1991
2002
|
try {
|
|
1992
2003
|
const session = getSession(sessionId);
|
|
1993
2004
|
if (session !== void 0 && session !== null) {
|
|
1994
2005
|
const state = projections.stateOf(session, "contextTimeline");
|
|
1995
|
-
if (state === void 0) return {
|
|
2006
|
+
if (state === void 0) return reply({
|
|
1996
2007
|
ok: true,
|
|
1997
2008
|
value: null
|
|
1998
|
-
};
|
|
1999
|
-
return {
|
|
2009
|
+
});
|
|
2010
|
+
return reply({
|
|
2000
2011
|
ok: true,
|
|
2001
2012
|
value: buildTimelineDetail(state, bounds)
|
|
2002
|
-
};
|
|
2013
|
+
});
|
|
2003
2014
|
}
|
|
2004
2015
|
const query = ctx.get("sessionQuery");
|
|
2005
2016
|
const observe = typeof query?.observeSession === "function" ? query.observeSession.bind(query) : void 0;
|
|
2006
|
-
if (observe === void 0) return {
|
|
2017
|
+
if (observe === void 0) return reply({
|
|
2007
2018
|
ok: true,
|
|
2008
2019
|
value: null
|
|
2009
|
-
};
|
|
2020
|
+
});
|
|
2010
2021
|
const observation = await observe(sessionId, { projectionMode: "none" });
|
|
2011
2022
|
const events = observation?.events;
|
|
2012
|
-
if (!Array.isArray(events)) return {
|
|
2023
|
+
if (!Array.isArray(events)) return reply({
|
|
2013
2024
|
ok: true,
|
|
2014
2025
|
value: null
|
|
2015
|
-
};
|
|
2026
|
+
});
|
|
2016
2027
|
let state = createTimelineState();
|
|
2017
2028
|
try {
|
|
2018
2029
|
for (const ev of events) state = applyTimeline(state, ev, bounds);
|
|
@@ -2020,21 +2031,21 @@ function watchDetailChannel(ctx, bounds) {
|
|
|
2020
2031
|
const dispose = observation?.[Symbol.dispose];
|
|
2021
2032
|
if (typeof dispose === "function") dispose.call(observation);
|
|
2022
2033
|
}
|
|
2023
|
-
return {
|
|
2034
|
+
return reply({
|
|
2024
2035
|
ok: true,
|
|
2025
2036
|
value: buildTimelineDetail(state, bounds)
|
|
2026
|
-
};
|
|
2037
|
+
});
|
|
2027
2038
|
} catch (err) {
|
|
2028
2039
|
return failure("gateway/internal", err instanceof Error ? err.message : String(err));
|
|
2029
2040
|
}
|
|
2030
2041
|
};
|
|
2031
2042
|
try {
|
|
2032
|
-
c.effect(() => {
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
}, "dsh-context: detail
|
|
2043
|
+
c.effect(() => register({
|
|
2044
|
+
path: DETAIL_ROUTE,
|
|
2045
|
+
methods: ["POST"],
|
|
2046
|
+
requestBody: "buffered",
|
|
2047
|
+
fetch: handler
|
|
2048
|
+
}), "dsh-context: detail route");
|
|
2038
2049
|
} catch {
|
|
2039
2050
|
return;
|
|
2040
2051
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.2",
|
|
4
4
|
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|