dsh-tool-stats 0.1.3 → 0.1.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/CHANGELOG.md +9 -0
- package/lib/client/index.d.ts +23 -1
- package/lib/client/index.js +62 -13
- package/lib/stats.web.js +48 -13
- package/lib/stats.web.js.map +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
- **Fix the client half never activating.** The browser half declared package names
|
|
6
|
+
(`@deepseek-ai/dsh-client-ui-settings`) as cordis services, but services are named by
|
|
7
|
+
their runtime identity — the settings shell is `slots`, the host bridge is
|
|
8
|
+
`connection`. The fiber therefore never resolved and the plugin stayed pending, which
|
|
9
|
+
the loader reported as "did not activate". It now injects `slots` and registers into
|
|
10
|
+
the `settings.section` slot the way the official client artifacts do.
|
|
11
|
+
|
|
3
12
|
## 0.1.3
|
|
4
13
|
|
|
5
14
|
- **Fix the browser half never registering.** The client bundle was emitted as plain ESM
|
package/lib/client/index.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half of dsh-tool-stats: the Tool Stats page inside Settings.
|
|
3
|
+
*
|
|
4
|
+
* Services are named by their runtime identity, not by the package that provides them:
|
|
5
|
+
* the settings shell is the `slots` service and the host bridge is `connection`. A
|
|
6
|
+
* fiber that injects package names never resolves and the plugin stays pending, which
|
|
7
|
+
* the loader reports as "did not activate".
|
|
8
|
+
*
|
|
9
|
+
* @module client
|
|
10
|
+
*/
|
|
11
|
+
import type { ComponentType } from 'react';
|
|
12
|
+
interface SlotsService {
|
|
13
|
+
inject(name: string, register: () => void): void;
|
|
14
|
+
register(options: {
|
|
15
|
+
name: string;
|
|
16
|
+
id: string;
|
|
17
|
+
order: number;
|
|
18
|
+
label: () => string;
|
|
19
|
+
}, component: ComponentType): unknown;
|
|
20
|
+
}
|
|
21
|
+
/** The slots service is what the settings shell exposes. */
|
|
1
22
|
export declare const inject: string[];
|
|
2
23
|
export declare function apply(ctx: {
|
|
3
|
-
|
|
24
|
+
slots: SlotsService;
|
|
4
25
|
}): void;
|
|
26
|
+
export {};
|
package/lib/client/index.js
CHANGED
|
@@ -1,16 +1,65 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Browser half of dsh-tool-stats: the Tool Stats page inside Settings.
|
|
4
|
+
*
|
|
5
|
+
* Services are named by their runtime identity, not by the package that provides them:
|
|
6
|
+
* the settings shell is the `slots` service and the host bridge is `connection`. A
|
|
7
|
+
* fiber that injects package names never resolves and the plugin stays pending, which
|
|
8
|
+
* the loader reports as "did not activate".
|
|
9
|
+
*
|
|
10
|
+
* @module client
|
|
11
|
+
*/
|
|
12
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
1
13
|
import { renderPanel } from './view.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/** Path the host half registers on the web connection. */
|
|
15
|
+
const PANEL_PATH = '/api/stats.panel';
|
|
16
|
+
/** Fetch the panel payload; `reload` re-runs the request. */
|
|
17
|
+
function usePanel() {
|
|
18
|
+
const [html, setHtml] = useState(null);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const [tick, setTick] = useState(0);
|
|
21
|
+
const reload = useCallback(() => setTick((t) => t + 1), []);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
setError(null);
|
|
25
|
+
fetch(PANEL_PATH, { signal: controller.signal })
|
|
26
|
+
.then(async (response) => {
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
throw new Error(`panel request failed with ${response.status}`);
|
|
29
|
+
return response.json();
|
|
30
|
+
})
|
|
31
|
+
.then((payload) => {
|
|
32
|
+
if (!controller.signal.aborted)
|
|
33
|
+
setHtml(renderPanel(payload));
|
|
34
|
+
})
|
|
35
|
+
.catch((cause) => {
|
|
36
|
+
if (controller.signal.aborted)
|
|
37
|
+
return;
|
|
38
|
+
// Say what failed rather than rendering an empty panel, which would read as
|
|
39
|
+
// "nothing recorded" — a different and wrong answer.
|
|
40
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
14
41
|
});
|
|
15
|
-
|
|
42
|
+
return () => controller.abort();
|
|
43
|
+
}, [tick]);
|
|
44
|
+
return { html, error, reload };
|
|
45
|
+
}
|
|
46
|
+
function ToolStatsPage() {
|
|
47
|
+
const { html, error, reload } = usePanel();
|
|
48
|
+
if (error !== null) {
|
|
49
|
+
return (_jsxs("section", { children: [_jsxs("p", { role: "alert", children: ["Tool Stats panel failed to load: ", error] }), _jsx("button", { type: "button", onClick: reload, children: "Retry" })] }));
|
|
50
|
+
}
|
|
51
|
+
if (html === null)
|
|
52
|
+
return _jsx("p", { "aria-live": "polite", children: "Loading the Tool Stats panel\u2026" });
|
|
53
|
+
// The markup is produced by `renderPanel`, which escapes every interpolated value.
|
|
54
|
+
return _jsx("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
55
|
+
}
|
|
56
|
+
/** The slots service is what the settings shell exposes. */
|
|
57
|
+
export const inject = ['slots'];
|
|
58
|
+
export function apply(ctx) {
|
|
59
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
60
|
+
name: 'settings.section',
|
|
61
|
+
id: 'tool-stats',
|
|
62
|
+
order: 43,
|
|
63
|
+
label: () => 'Tool Stats',
|
|
64
|
+
}, ToolStatsPage));
|
|
16
65
|
}
|
package/lib/stats.web.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
inject: () => inject
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
var import_react = require("react");
|
|
29
30
|
|
|
30
31
|
// src/client/view.tsx
|
|
31
32
|
function renderPanel(payload) {
|
|
@@ -41,20 +42,54 @@ function renderPanel(payload) {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
// src/client/index.tsx
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
45
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
46
|
+
var PANEL_PATH = "/api/stats.panel";
|
|
47
|
+
function usePanel() {
|
|
48
|
+
const [html, setHtml] = (0, import_react.useState)(null);
|
|
49
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
50
|
+
const [tick, setTick] = (0, import_react.useState)(0);
|
|
51
|
+
const reload = (0, import_react.useCallback)(() => setTick((t) => t + 1), []);
|
|
52
|
+
(0, import_react.useEffect)(() => {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
setError(null);
|
|
55
|
+
fetch(PANEL_PATH, { signal: controller.signal }).then(async (response) => {
|
|
56
|
+
if (!response.ok) throw new Error(`panel request failed with ${response.status}`);
|
|
57
|
+
return response.json();
|
|
58
|
+
}).then((payload) => {
|
|
59
|
+
if (!controller.signal.aborted) setHtml(renderPanel(payload));
|
|
60
|
+
}).catch((cause) => {
|
|
61
|
+
if (controller.signal.aborted) return;
|
|
62
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
56
63
|
});
|
|
57
|
-
|
|
64
|
+
return () => controller.abort();
|
|
65
|
+
}, [tick]);
|
|
66
|
+
return { html, error, reload };
|
|
67
|
+
}
|
|
68
|
+
function ToolStatsPage() {
|
|
69
|
+
const { html, error, reload } = usePanel();
|
|
70
|
+
if (error !== null) {
|
|
71
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
|
|
72
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "alert", children: [
|
|
73
|
+
"Tool Stats panel failed to load: ",
|
|
74
|
+
error
|
|
75
|
+
] }),
|
|
76
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, children: "Retry" })
|
|
77
|
+
] });
|
|
78
|
+
}
|
|
79
|
+
if (html === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "aria-live": "polite", children: "Loading the Tool Stats panel\u2026" });
|
|
80
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
81
|
+
}
|
|
82
|
+
var inject = ["slots"];
|
|
83
|
+
function apply(ctx) {
|
|
84
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register(
|
|
85
|
+
{
|
|
86
|
+
name: "settings.section",
|
|
87
|
+
id: "tool-stats",
|
|
88
|
+
order: 43,
|
|
89
|
+
label: () => "Tool Stats"
|
|
90
|
+
},
|
|
91
|
+
ToolStatsPage
|
|
92
|
+
));
|
|
58
93
|
}
|
|
59
94
|
return module.exports; } });
|
|
60
95
|
//# sourceMappingURL=stats.web.js.map
|
package/lib/stats.web.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client/index.tsx", "../src/client/view.tsx"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;
|
|
4
|
+
"sourcesContent": ["/**\n * Browser half of dsh-tool-stats: the Tool Stats page inside Settings.\n *\n * Services are named by their runtime identity, not by the package that provides them:\n * the settings shell is the `slots` service and the host bridge is `connection`. A\n * fiber that injects package names never resolves and the plugin stays pending, which\n * the loader reports as \"did not activate\".\n *\n * @module client\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { ComponentType } from 'react'\nimport type { PanelPayload } from '../types.js'\nimport { renderPanel } from './view.js'\n\n/** Path the host half registers on the web connection. */\nconst PANEL_PATH = '/api/stats.panel'\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(options: { name: string; id: string; order: number; label: () => string }, component: ComponentType): unknown\n}\n\n/** Fetch the panel payload; `reload` re-runs the request. */\nfunction usePanel() {\n const [html, setHtml] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((t) => t + 1), [])\n\n useEffect(() => {\n const controller = new AbortController()\n setError(null)\n fetch(PANEL_PATH, { signal: controller.signal })\n .then(async (response) => {\n if (!response.ok) throw new Error(`panel request failed with ${response.status}`)\n return response.json() as Promise<PanelPayload>\n })\n .then((payload) => {\n if (!controller.signal.aborted) setHtml(renderPanel(payload))\n })\n .catch((cause: unknown) => {\n if (controller.signal.aborted) return\n // Say what failed rather than rendering an empty panel, which would read as\n // \"nothing recorded\" \u2014 a different and wrong answer.\n setError(cause instanceof Error ? cause.message : String(cause))\n })\n return () => controller.abort()\n }, [tick])\n\n return { html, error, reload }\n}\n\nfunction ToolStatsPage() {\n const { html, error, reload } = usePanel()\n if (error !== null) {\n return (\n <section>\n <p role=\"alert\">Tool Stats panel failed to load: {error}</p>\n <button type=\"button\" onClick={reload}>Retry</button>\n </section>\n )\n }\n if (html === null) return <p aria-live=\"polite\">Loading the Tool Stats panel\u2026</p>\n // The markup is produced by `renderPanel`, which escapes every interpolated value.\n return <div dangerouslySetInnerHTML={{ __html: html }} />\n}\n\n/** The slots service is what the settings shell exposes. */\nexport const inject = ['slots']\n\nexport function apply(ctx: { slots: SlotsService }): void {\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'tool-stats',\n order: 43,\n label: () => 'Tool Stats',\n },\n ToolStatsPage,\n ))\n}\n", "import type { PanelPayload } from '../types.js';\n\nexport function renderPanel(payload: PanelPayload): string {\n const toolRows = payload.tools\n .map((t) => `<tr><td>${t.tool}</td><td>${t.invocations}</td><td>${Math.round(t.failureRate * 100)}%</td><td>${t.p50Latency}ms</td><td>${t.p95Latency}ms</td></tr>`)\n .join('');\n\n const deadSection = payload.deadTools.length > 0\n ? `<h3>Dead tools</h3><ul>${payload.deadTools.map((d) => `<li>${d.tool}</li>`).join('')}</ul>`\n : '';\n\n const failSection = payload.failingTools.length > 0\n ? `<h3>Failing tools</h3><ul>${payload.failingTools.map((f) => `<li>${f.tool}: ${Math.round(f.failureRate * 100)}%</li>`).join('')}</ul>`\n : '';\n\n const recSection = payload.recommendations.length > 0\n ? `<h3>Recommendations</h3><ul>${payload.recommendations.map((r) => `<li>${r.message}</li>`).join('')}</ul>`\n : '';\n\n return `<div class=\"stats-panel\">\n <h2>Tool Stats</h2>\n ${toolRows ? `<table><thead><tr><th>Tool</th><th>Calls</th><th>Fail</th><th>p50</th><th>p95</th></tr></thead><tbody>${toolRows}</tbody></table>` : '<p>No calls recorded.</p>'}\n ${deadSection}${failSection}${recSection}\n </div>`;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tool-stats",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Every tool call is counted, and the ones that never fire are named so you can remove them.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"bundle": { "patch": "./cordis.patch.yml" },
|
|
18
18
|
"client": {
|
|
19
19
|
"platform": "web",
|
|
20
|
-
"inject": ["
|
|
20
|
+
"inject": ["slots", "connection"]
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"license": "MIT",
|