dsh-brief-sidebar 0.9.2 → 0.9.4
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/dsh.plugin.json +1 -1
- package/lib/client.js +1025 -441
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +303 -11
- package/lib/index.mjs.map +1 -1
- package/lib/types/client/locales.d.ts +13 -0
- package/lib/types/client/summary/ReferenceSection.d.ts +24 -0
- package/lib/types/client/summary/SearchRefSection.d.ts +21 -0
- package/lib/types/client/summary/SkillUsageSection.d.ts +6 -0
- package/lib/types/client/summary/search-refs.d.ts +38 -0
- package/lib/types/projection/keys.d.ts +42 -0
- package/lib/types/projection/register.d.ts +6 -0
- package/lib/types/projection/search-ref-fold.d.ts +116 -0
- package/package.json +1 -1
- package/src/client/SummaryTab.tsx +12 -18
- package/src/client/locales.ts +117 -0
- package/src/client/summary/ReferenceSection.tsx +211 -0
- package/src/client/summary/SearchRefSection.tsx +241 -0
- package/src/client/summary/SkillUsageSection.tsx +8 -2
- package/src/client/summary/search-refs.ts +116 -0
- package/src/index.ts +4 -1
- package/src/projection/keys.ts +43 -0
- package/src/projection/register.ts +82 -2
- package/src/projection/search-ref-fold.ts +258 -0
package/lib/index.mjs
CHANGED
|
@@ -5375,7 +5375,7 @@ function superRefine(fn, params) {
|
|
|
5375
5375
|
* lists are capped; the uncapped `sessionTotal` keeps the summary truthful
|
|
5376
5376
|
* beyond the cap.
|
|
5377
5377
|
*/
|
|
5378
|
-
const CAPS$
|
|
5378
|
+
const CAPS$2 = {
|
|
5379
5379
|
/** Latest-turn paths kept in the wire view. */
|
|
5380
5380
|
latest: 50,
|
|
5381
5381
|
/** Session-wide paths kept in the wire view (most recent win). */
|
|
@@ -5442,10 +5442,10 @@ function applyDeliverablesEvent(state, event) {
|
|
|
5442
5442
|
const latest = state.latest;
|
|
5443
5443
|
const nextLatest = latest !== null && latest.turn === recorded.turn && !latest.paths.includes(path) ? {
|
|
5444
5444
|
turn: latest.turn,
|
|
5445
|
-
paths: [...latest.paths, path].slice(-CAPS$
|
|
5445
|
+
paths: [...latest.paths, path].slice(-CAPS$2.latest)
|
|
5446
5446
|
} : latest;
|
|
5447
5447
|
const withoutDuplicate = state.sessionPaths.filter((existing) => existing !== path);
|
|
5448
|
-
const sessionPaths = [path, ...withoutDuplicate].slice(0, CAPS$
|
|
5448
|
+
const sessionPaths = [path, ...withoutDuplicate].slice(0, CAPS$2.session);
|
|
5449
5449
|
const sessionTotal = withoutDuplicate.length === state.sessionPaths.length ? state.sessionTotal + 1 : state.sessionTotal;
|
|
5450
5450
|
return {
|
|
5451
5451
|
...state,
|
|
@@ -5509,7 +5509,7 @@ function isRecord(value) {
|
|
|
5509
5509
|
* Capacity bounds. The unit rides every committed event, so the lists are
|
|
5510
5510
|
* capped; `sessionTotal`/`callTotal` keep the summary truthful beyond the cap.
|
|
5511
5511
|
*/
|
|
5512
|
-
const CAPS = {
|
|
5512
|
+
const CAPS$1 = {
|
|
5513
5513
|
/** Distinct skills kept in state (most recent win). */
|
|
5514
5514
|
session: 50,
|
|
5515
5515
|
/** Skills kept in the latest-turn bucket of the wire view. */
|
|
@@ -5570,7 +5570,7 @@ function tally(state, skill, turn) {
|
|
|
5570
5570
|
name: skill,
|
|
5571
5571
|
count: (existing?.count ?? 0) + 1,
|
|
5572
5572
|
lastTurn: turn
|
|
5573
|
-
}, ...withoutSkill].slice(0, CAPS.session);
|
|
5573
|
+
}, ...withoutSkill].slice(0, CAPS$1.session);
|
|
5574
5574
|
const sessionTotal = existing === void 0 ? state.sessionTotal + 1 : state.sessionTotal;
|
|
5575
5575
|
const latest = state.latest;
|
|
5576
5576
|
const nextLatest = latest !== null && latest.turn === turn && !latest.names.includes(skill) ? {
|
|
@@ -5647,7 +5647,7 @@ function applySkillUsageEvent(state, event) {
|
|
|
5647
5647
|
}
|
|
5648
5648
|
return {
|
|
5649
5649
|
...state,
|
|
5650
|
-
catalog: names.slice(0, CAPS.available),
|
|
5650
|
+
catalog: names.slice(0, CAPS$1.available),
|
|
5651
5651
|
catalogCount: names.length
|
|
5652
5652
|
};
|
|
5653
5653
|
}
|
|
@@ -5663,6 +5663,199 @@ function applySkillUsageEvent(state, event) {
|
|
|
5663
5663
|
return tally(state, recorded.skill, recorded.turn);
|
|
5664
5664
|
}
|
|
5665
5665
|
//#endregion
|
|
5666
|
+
//#region src/projection/search-ref-fold.ts
|
|
5667
|
+
/**
|
|
5668
|
+
* Capacity bounds. `snippet` is capped because it is the only long text in the
|
|
5669
|
+
* state and the frame is republished per event; the provider already caps it
|
|
5670
|
+
* once in its own `presentationMeta`, so this is a second, tighter belt.
|
|
5671
|
+
*/
|
|
5672
|
+
const CAPS = {
|
|
5673
|
+
/** Distinct sources kept in state. */
|
|
5674
|
+
items: 20,
|
|
5675
|
+
/** Distinct queries kept in state. */
|
|
5676
|
+
queries: 20,
|
|
5677
|
+
/** URLs kept in the latest-turn bucket. */
|
|
5678
|
+
latest: 20,
|
|
5679
|
+
/** Snippet characters kept per source. */
|
|
5680
|
+
snippet: 160
|
|
5681
|
+
};
|
|
5682
|
+
/** The fold's initial state: no search has settled yet. */
|
|
5683
|
+
function initSearchRefState() {
|
|
5684
|
+
return {
|
|
5685
|
+
turn: 0,
|
|
5686
|
+
calls: {},
|
|
5687
|
+
latest: null,
|
|
5688
|
+
items: [],
|
|
5689
|
+
queries: [],
|
|
5690
|
+
sourceTotal: 0,
|
|
5691
|
+
queryTotal: 0
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5694
|
+
/** The tool this fold owns. Nothing else counts, even with a `query` argument. */
|
|
5695
|
+
const SEARCH_TOOL = "advanced_search";
|
|
5696
|
+
/**
|
|
5697
|
+
* Extract the query string from one committed `tool/call`.
|
|
5698
|
+
*
|
|
5699
|
+
* @param name - wire tool name.
|
|
5700
|
+
* @param argsRaw - model-produced JSON arguments (a string by the session contract).
|
|
5701
|
+
* @returns the non-blank query, or null when this call is not a search.
|
|
5702
|
+
*/
|
|
5703
|
+
function queryFromArgs(name, argsRaw) {
|
|
5704
|
+
if (name !== SEARCH_TOOL) return null;
|
|
5705
|
+
let args;
|
|
5706
|
+
try {
|
|
5707
|
+
args = JSON.parse(argsRaw);
|
|
5708
|
+
} catch {
|
|
5709
|
+
return null;
|
|
5710
|
+
}
|
|
5711
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) return null;
|
|
5712
|
+
const query = args["query"];
|
|
5713
|
+
return typeof query === "string" && query.trim().length > 0 ? query : null;
|
|
5714
|
+
}
|
|
5715
|
+
/**
|
|
5716
|
+
* The dedupe key. Host case and a trailing slash are not meaningful for
|
|
5717
|
+
* identity; path case IS, so it is left alone. Exported because the client
|
|
5718
|
+
* matches a row against the latest-turn bucket with the same algorithm.
|
|
5719
|
+
*/
|
|
5720
|
+
function urlKey(url) {
|
|
5721
|
+
const trimmed = url.trim();
|
|
5722
|
+
return (trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed).replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, (match) => match.toLowerCase());
|
|
5723
|
+
}
|
|
5724
|
+
/** The host label of a URL, or the raw string when it does not parse. */
|
|
5725
|
+
function hostOf(url) {
|
|
5726
|
+
try {
|
|
5727
|
+
return new URL(url).hostname.replace(/^www\./i, "");
|
|
5728
|
+
} catch {
|
|
5729
|
+
return url.slice(0, 40);
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5732
|
+
/** Cap one snippet so one long provider blurb cannot dominate every frame. */
|
|
5733
|
+
function capSnippet(text) {
|
|
5734
|
+
const one = text.replace(/\s+/g, " ").trim();
|
|
5735
|
+
return one.length > CAPS.snippet ? `${one.slice(0, CAPS.snippet)}…` : one;
|
|
5736
|
+
}
|
|
5737
|
+
/** The sources of one result meta, or `[]` when the meta is absent or drifted. */
|
|
5738
|
+
function readSources(meta) {
|
|
5739
|
+
if (typeof meta !== "object" || meta === null) return [];
|
|
5740
|
+
const raw = meta["sources"];
|
|
5741
|
+
if (!Array.isArray(raw)) return [];
|
|
5742
|
+
const out = [];
|
|
5743
|
+
for (const entry of raw) {
|
|
5744
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
5745
|
+
const record = entry;
|
|
5746
|
+
if (typeof record["url"] !== "string" || record["url"].trim().length === 0) continue;
|
|
5747
|
+
const source = { url: record["url"] };
|
|
5748
|
+
if (typeof record["title"] === "string" && record["title"].length > 0) source.title = record["title"];
|
|
5749
|
+
if (typeof record["snippet"] === "string" && record["snippet"].length > 0) source.snippet = capSnippet(record["snippet"]);
|
|
5750
|
+
out.push(source);
|
|
5751
|
+
}
|
|
5752
|
+
return out;
|
|
5753
|
+
}
|
|
5754
|
+
/**
|
|
5755
|
+
* The next state after one committed event. Events this fold does not own
|
|
5756
|
+
* return the SAME reference, which is what keeps the registry's `Object.is`
|
|
5757
|
+
* drive from republishing the key on unrelated frames.
|
|
5758
|
+
*
|
|
5759
|
+
* @param state - the state covering all prior events.
|
|
5760
|
+
* @param event - the next committed session event.
|
|
5761
|
+
* @returns the next state (same reference when nothing the fold tracks moved).
|
|
5762
|
+
*/
|
|
5763
|
+
function applySearchRefEvent(state, event) {
|
|
5764
|
+
if (event.type === "turn/start") {
|
|
5765
|
+
const turn = event.data;
|
|
5766
|
+
if (typeof turn?.turn !== "number") return state;
|
|
5767
|
+
const calls = {};
|
|
5768
|
+
for (const [id, entry] of Object.entries(state.calls)) if (entry.turn >= turn.turn - 1) calls[id] = entry;
|
|
5769
|
+
return {
|
|
5770
|
+
...state,
|
|
5771
|
+
turn: turn.turn,
|
|
5772
|
+
calls,
|
|
5773
|
+
latest: {
|
|
5774
|
+
turn: turn.turn,
|
|
5775
|
+
urls: []
|
|
5776
|
+
}
|
|
5777
|
+
};
|
|
5778
|
+
}
|
|
5779
|
+
if (event.type === "tool/call") {
|
|
5780
|
+
const call = event.data;
|
|
5781
|
+
if (typeof call?.callId !== "string") return state;
|
|
5782
|
+
if (typeof call.name !== "string" || typeof call.arguments !== "string") return state;
|
|
5783
|
+
const query = queryFromArgs(call.name, call.arguments);
|
|
5784
|
+
if (query === null) return state;
|
|
5785
|
+
return {
|
|
5786
|
+
...state,
|
|
5787
|
+
calls: {
|
|
5788
|
+
...state.calls,
|
|
5789
|
+
[call.callId]: {
|
|
5790
|
+
turn: state.turn,
|
|
5791
|
+
query
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
};
|
|
5795
|
+
}
|
|
5796
|
+
if (event.type !== "tool/result") return state;
|
|
5797
|
+
const result = event.data;
|
|
5798
|
+
const callId = result?.message?.source?.callId;
|
|
5799
|
+
if (typeof callId !== "string") return state;
|
|
5800
|
+
if (result?.message?.content?.[0]?.isError === true) return state;
|
|
5801
|
+
const recorded = state.calls[callId];
|
|
5802
|
+
if (recorded === void 0) return state;
|
|
5803
|
+
const { query, turn } = recorded;
|
|
5804
|
+
const sources = readSources(result.meta);
|
|
5805
|
+
const items = [...state.items];
|
|
5806
|
+
let sourceTotal = state.sourceTotal;
|
|
5807
|
+
const urls = [];
|
|
5808
|
+
for (const source of sources) {
|
|
5809
|
+
const key = urlKey(source.url);
|
|
5810
|
+
urls.push(key);
|
|
5811
|
+
const at = items.findIndex((item) => urlKey(item.url) === key);
|
|
5812
|
+
const row = {
|
|
5813
|
+
url: source.url,
|
|
5814
|
+
host: hostOf(source.url),
|
|
5815
|
+
...source.title !== void 0 ? { title: source.title } : {},
|
|
5816
|
+
...source.snippet !== void 0 ? { snippet: source.snippet } : {},
|
|
5817
|
+
query,
|
|
5818
|
+
count: at < 0 ? 1 : (items[at]?.count ?? 0) + 1,
|
|
5819
|
+
lastTurn: turn
|
|
5820
|
+
};
|
|
5821
|
+
if (at >= 0) items.splice(at, 1);
|
|
5822
|
+
else sourceTotal += 1;
|
|
5823
|
+
items.unshift(row);
|
|
5824
|
+
}
|
|
5825
|
+
const queries = [...state.queries];
|
|
5826
|
+
const qAt = queries.findIndex((row) => row.query === query);
|
|
5827
|
+
let queryTotal = state.queryTotal;
|
|
5828
|
+
if (qAt >= 0) {
|
|
5829
|
+
const previous = queries[qAt];
|
|
5830
|
+
queries.splice(qAt, 1);
|
|
5831
|
+
queries.unshift({
|
|
5832
|
+
query,
|
|
5833
|
+
turn,
|
|
5834
|
+
hits: (previous?.hits ?? 0) + sources.length
|
|
5835
|
+
});
|
|
5836
|
+
} else {
|
|
5837
|
+
queries.unshift({
|
|
5838
|
+
query,
|
|
5839
|
+
turn,
|
|
5840
|
+
hits: sources.length
|
|
5841
|
+
});
|
|
5842
|
+
queryTotal += 1;
|
|
5843
|
+
}
|
|
5844
|
+
const latest = state.latest;
|
|
5845
|
+
const nextLatest = latest !== null && latest.turn === turn ? {
|
|
5846
|
+
turn: latest.turn,
|
|
5847
|
+
urls: [...urls.reverse(), ...latest.urls].slice(0, CAPS.latest)
|
|
5848
|
+
} : latest;
|
|
5849
|
+
return {
|
|
5850
|
+
...state,
|
|
5851
|
+
items: items.slice(0, CAPS.items),
|
|
5852
|
+
queries: queries.slice(0, CAPS.queries),
|
|
5853
|
+
sourceTotal,
|
|
5854
|
+
queryTotal,
|
|
5855
|
+
latest: nextLatest
|
|
5856
|
+
};
|
|
5857
|
+
}
|
|
5858
|
+
//#endregion
|
|
5666
5859
|
//#region src/projection/keys.ts
|
|
5667
5860
|
/**
|
|
5668
5861
|
* The projection keys this plugin contributes to the session-projection
|
|
@@ -5685,6 +5878,12 @@ const DELIVERABLES_KEY = "dshSummaryDeliverables";
|
|
|
5685
5878
|
* to collide silently.
|
|
5686
5879
|
*/
|
|
5687
5880
|
const SKILL_USAGE_KEY = "dshSummarySkillUsage";
|
|
5881
|
+
/**
|
|
5882
|
+
* The search-reference key (queries and returned sources of this session's
|
|
5883
|
+
* `advanced_search` calls). Same rule as above: the name is ours, so no other
|
|
5884
|
+
* host-side unit can answer for it by accident.
|
|
5885
|
+
*/
|
|
5886
|
+
const SEARCH_REFS_KEY = "dshSummarySearchRefs";
|
|
5688
5887
|
//#endregion
|
|
5689
5888
|
//#region src/projection/register.ts
|
|
5690
5889
|
/**
|
|
@@ -5740,9 +5939,9 @@ function view(state) {
|
|
|
5740
5939
|
const next = {
|
|
5741
5940
|
latest: state.latest === null ? null : {
|
|
5742
5941
|
turn: state.latest.turn,
|
|
5743
|
-
paths: state.latest.paths.slice(-CAPS$
|
|
5942
|
+
paths: state.latest.paths.slice(-CAPS$2.latest)
|
|
5744
5943
|
},
|
|
5745
|
-
sessionPaths: state.sessionPaths.slice(0, CAPS$
|
|
5944
|
+
sessionPaths: state.sessionPaths.slice(0, CAPS$2.session),
|
|
5746
5945
|
sessionTotal: state.sessionTotal
|
|
5747
5946
|
};
|
|
5748
5947
|
viewMemo.set(state, next);
|
|
@@ -5818,12 +6017,12 @@ function skillView(state) {
|
|
|
5818
6017
|
const next = {
|
|
5819
6018
|
latest: state.latest === null ? null : {
|
|
5820
6019
|
turn: state.latest.turn,
|
|
5821
|
-
names: state.latest.names.slice(0, CAPS.latest)
|
|
6020
|
+
names: state.latest.names.slice(0, CAPS$1.latest)
|
|
5822
6021
|
},
|
|
5823
|
-
session: state.items.slice(0, CAPS.view),
|
|
6022
|
+
session: state.items.slice(0, CAPS$1.view),
|
|
5824
6023
|
sessionTotal: state.sessionTotal,
|
|
5825
6024
|
callTotal: state.callTotal,
|
|
5826
|
-
available: state.catalog.slice(0, CAPS.available),
|
|
6025
|
+
available: state.catalog.slice(0, CAPS$1.available),
|
|
5827
6026
|
availableCount: state.catalogCount
|
|
5828
6027
|
};
|
|
5829
6028
|
skillViewMemo.set(state, next);
|
|
@@ -5851,6 +6050,98 @@ function registerSkillUsageProjection(ctx) {
|
|
|
5851
6050
|
});
|
|
5852
6051
|
});
|
|
5853
6052
|
}
|
|
6053
|
+
/** Zod schema of the search-reference fold state. */
|
|
6054
|
+
const searchStateSchema = object({
|
|
6055
|
+
turn: number(),
|
|
6056
|
+
calls: record(string(), object({
|
|
6057
|
+
turn: number(),
|
|
6058
|
+
query: string()
|
|
6059
|
+
})),
|
|
6060
|
+
latest: object({
|
|
6061
|
+
turn: number(),
|
|
6062
|
+
urls: array(string())
|
|
6063
|
+
}).nullable(),
|
|
6064
|
+
items: array(object({
|
|
6065
|
+
url: string(),
|
|
6066
|
+
host: string(),
|
|
6067
|
+
title: string().optional(),
|
|
6068
|
+
snippet: string().optional(),
|
|
6069
|
+
query: string(),
|
|
6070
|
+
count: number(),
|
|
6071
|
+
lastTurn: number()
|
|
6072
|
+
})),
|
|
6073
|
+
queries: array(object({
|
|
6074
|
+
query: string(),
|
|
6075
|
+
turn: number(),
|
|
6076
|
+
hits: number()
|
|
6077
|
+
})),
|
|
6078
|
+
sourceTotal: number(),
|
|
6079
|
+
queryTotal: number()
|
|
6080
|
+
});
|
|
6081
|
+
/** Zod schema of the search-reference wire view. */
|
|
6082
|
+
const searchViewSchema = object({
|
|
6083
|
+
latest: object({
|
|
6084
|
+
turn: number(),
|
|
6085
|
+
urls: array(string())
|
|
6086
|
+
}).nullable(),
|
|
6087
|
+
queries: array(object({
|
|
6088
|
+
query: string(),
|
|
6089
|
+
turn: number(),
|
|
6090
|
+
hits: number()
|
|
6091
|
+
})),
|
|
6092
|
+
sources: array(object({
|
|
6093
|
+
url: string(),
|
|
6094
|
+
host: string(),
|
|
6095
|
+
title: string().optional(),
|
|
6096
|
+
snippet: string().optional(),
|
|
6097
|
+
query: string(),
|
|
6098
|
+
count: number(),
|
|
6099
|
+
lastTurn: number()
|
|
6100
|
+
})),
|
|
6101
|
+
queryTotal: number(),
|
|
6102
|
+
sourceTotal: number()
|
|
6103
|
+
});
|
|
6104
|
+
/** Same reference-stability memo, for the search-reference unit. */
|
|
6105
|
+
const searchViewMemo = /* @__PURE__ */ new WeakMap();
|
|
6106
|
+
/** State → wire view, reference-stable across unchanged states. */
|
|
6107
|
+
function searchView(state) {
|
|
6108
|
+
const memoized = searchViewMemo.get(state);
|
|
6109
|
+
if (memoized !== void 0) return memoized;
|
|
6110
|
+
const next = {
|
|
6111
|
+
latest: state.latest === null ? null : {
|
|
6112
|
+
turn: state.latest.turn,
|
|
6113
|
+
urls: state.latest.urls.slice(0, CAPS.latest)
|
|
6114
|
+
},
|
|
6115
|
+
queries: state.queries.slice(0, CAPS.queries),
|
|
6116
|
+
sources: state.items.slice(0, CAPS.items),
|
|
6117
|
+
queryTotal: state.queryTotal,
|
|
6118
|
+
sourceTotal: state.sourceTotal
|
|
6119
|
+
};
|
|
6120
|
+
searchViewMemo.set(state, next);
|
|
6121
|
+
return next;
|
|
6122
|
+
}
|
|
6123
|
+
/**
|
|
6124
|
+
* Register the search-reference projection as an optional contribution.
|
|
6125
|
+
*
|
|
6126
|
+
* @param ctx - the host context the plugin's `apply` receives.
|
|
6127
|
+
*/
|
|
6128
|
+
function registerSearchRefProjection(ctx) {
|
|
6129
|
+
ctx.inject(["sessionProjections"], (injected) => {
|
|
6130
|
+
const registry = injected.sessionProjections;
|
|
6131
|
+
if (registry === void 0 || typeof registry.register !== "function") return;
|
|
6132
|
+
registry.register({
|
|
6133
|
+
key: SEARCH_REFS_KEY,
|
|
6134
|
+
stateSchema: searchStateSchema,
|
|
6135
|
+
init: () => initSearchRefState(),
|
|
6136
|
+
apply: applySearchRefEvent,
|
|
6137
|
+
wire: {
|
|
6138
|
+
viewSchema: searchViewSchema,
|
|
6139
|
+
view: searchView
|
|
6140
|
+
},
|
|
6141
|
+
stateVersion: 0
|
|
6142
|
+
});
|
|
6143
|
+
});
|
|
6144
|
+
}
|
|
5854
6145
|
//#endregion
|
|
5855
6146
|
//#region src/index.ts
|
|
5856
6147
|
/** Profile row identity. */
|
|
@@ -5880,6 +6171,7 @@ const Config = z.object({
|
|
|
5880
6171
|
function apply(ctx) {
|
|
5881
6172
|
registerDeliverablesProjection(ctx);
|
|
5882
6173
|
registerSkillUsageProjection(ctx);
|
|
6174
|
+
registerSearchRefProjection(ctx);
|
|
5883
6175
|
}
|
|
5884
6176
|
//#endregion
|
|
5885
6177
|
export { Config, apply, name };
|