@peers-app/peers-ui 0.25.21 → 0.26.0

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.
Files changed (36) hide show
  1. package/dist/mention-configs.d.ts +7 -0
  2. package/dist/mention-configs.js +8 -2
  3. package/dist/screens/assistants/assistant-config.d.ts +1 -0
  4. package/dist/screens/assistants/assistant-config.js +20 -3
  5. package/dist/screens/assistants/assistant-config.test.d.ts +1 -0
  6. package/dist/screens/assistants/assistant-config.test.js +57 -0
  7. package/dist/screens/settings/settings-page.js +1 -59
  8. package/dist/screens/settings/settings-page.test.d.ts +1 -0
  9. package/dist/screens/settings/settings-page.test.js +19 -0
  10. package/dist/screens/tools/tool-catalog-ipc.d.ts +118 -0
  11. package/dist/screens/tools/tool-catalog-ipc.js +48 -0
  12. package/dist/screens/tools/tool-catalog-ipc.test.d.ts +1 -0
  13. package/dist/screens/tools/tool-catalog-ipc.test.js +46 -0
  14. package/dist/screens/tools/tool-details.d.ts +3 -4
  15. package/dist/screens/tools/tool-details.js +14 -33
  16. package/dist/screens/tools/tool-info.d.ts +5 -4
  17. package/dist/screens/tools/tool-info.js +10 -30
  18. package/dist/screens/tools/tool-list.d.ts +1 -0
  19. package/dist/screens/tools/tool-list.js +2 -26
  20. package/dist/screens/tools/tool-schema.d.ts +5 -3
  21. package/dist/screens/tools/tool-schema.js +5 -14
  22. package/package.json +3 -3
  23. package/src/mention-configs.ts +9 -5
  24. package/src/screens/assistants/assistant-config.test.tsx +54 -0
  25. package/src/screens/assistants/assistant-config.tsx +42 -11
  26. package/src/screens/settings/settings-page.test.tsx +24 -0
  27. package/src/screens/settings/settings-page.tsx +1 -255
  28. package/src/screens/tools/tool-catalog-ipc.test.ts +46 -0
  29. package/src/screens/tools/tool-catalog-ipc.ts +51 -0
  30. package/src/screens/tools/tool-details.tsx +18 -67
  31. package/src/screens/tools/tool-info.tsx +39 -53
  32. package/src/screens/tools/tool-list.tsx +4 -37
  33. package/src/screens/tools/tool-schema.tsx +40 -45
  34. package/dist/screens/tools/tool-code.d.ts +0 -4
  35. package/dist/screens/tools/tool-code.js +0 -28
  36. package/src/screens/tools/tool-code.tsx +0 -30
@@ -1,4 +1,5 @@
1
1
  import { type IMentionData } from "@peers-app/peers-sdk";
2
+ /** Editor and renderer behavior for one persisted mention kind. */
2
3
  export interface IMentionConfig {
3
4
  iconClass: string;
4
5
  styleClass?: string;
@@ -8,9 +9,15 @@ export interface IMentionConfig {
8
9
  picture?: (data: IMentionData) => JSX.Element;
9
10
  onClick?: (data: IMentionData) => void;
10
11
  }
12
+ /** Discover workflow-invocable tools while keeping historical mention IDs readable. */
11
13
  export declare const toolMentionConfig: IMentionConfig;
14
+ /** Discover users and resolve persisted user mentions. */
12
15
  export declare const userMentionConfig: IMentionConfig;
16
+ /** Discover assistants and resolve persisted assistant mentions. */
13
17
  export declare const assistantMentionConfig: IMentionConfig;
18
+ /** Discover workflows and resolve persisted workflow mentions. */
14
19
  export declare const workflowMentionConfig: IMentionConfig;
20
+ /** Discover data types and navigate to their definitions. */
15
21
  export declare const valueTypeMentionConfig: IMentionConfig;
22
+ /** Shared mention definitions used by editors and rendered messages. */
16
23
  export declare const mentionConfigs: IMentionConfig[];
@@ -2,14 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mentionConfigs = exports.valueTypeMentionConfig = exports.workflowMentionConfig = exports.assistantMentionConfig = exports.userMentionConfig = exports.toolMentionConfig = void 0;
4
4
  const peers_sdk_1 = require("@peers-app/peers-sdk");
5
- // import { MENTIONS_MAX_RESULTS } from "../client/components/markdown-editor/mentions-plugin";
5
+ const tool_catalog_ipc_1 = require("./screens/tools/tool-catalog-ipc");
6
6
  const MENTIONS_MAX_RESULTS = 10;
7
+ /** Discover workflow-invocable tools while keeping historical mention IDs readable. */
7
8
  exports.toolMentionConfig = {
8
9
  kind: "tool",
9
10
  iconClass: "bi bi-tools",
10
11
  styleClass: "mention-tool",
11
12
  query: async (search) => {
12
- const results = await (0, peers_sdk_1.Tools)().list({ name: { $matchWords: search } }, { pageSize: MENTIONS_MAX_RESULTS });
13
+ const results = await (0, tool_catalog_ipc_1.listWorkflowMentionTools)(search, MENTIONS_MAX_RESULTS);
13
14
  return results.map((p) => ({
14
15
  kind: "tool",
15
16
  id: p.toolId,
@@ -24,6 +25,7 @@ exports.toolMentionConfig = {
24
25
  name: item.name,
25
26
  }),
26
27
  };
28
+ /** Discover users and resolve persisted user mentions. */
27
29
  exports.userMentionConfig = {
28
30
  kind: "user",
29
31
  iconClass: "bi-person-fill",
@@ -45,6 +47,7 @@ exports.userMentionConfig = {
45
47
  name: item.name,
46
48
  }),
47
49
  };
50
+ /** Discover assistants and resolve persisted assistant mentions. */
48
51
  exports.assistantMentionConfig = {
49
52
  kind: "assistant",
50
53
  iconClass: "bi bi-person-fill-gear",
@@ -66,6 +69,7 @@ exports.assistantMentionConfig = {
66
69
  name: item.name,
67
70
  }),
68
71
  };
72
+ /** Discover workflows and resolve persisted workflow mentions. */
69
73
  exports.workflowMentionConfig = {
70
74
  kind: "workflow",
71
75
  iconClass: "bi bi-database-fill-gear",
@@ -87,6 +91,7 @@ exports.workflowMentionConfig = {
87
91
  name: item.name,
88
92
  }),
89
93
  };
94
+ /** Discover data types and navigate to their definitions. */
90
95
  exports.valueTypeMentionConfig = {
91
96
  kind: "type",
92
97
  iconClass: "bi bi-code-square",
@@ -116,6 +121,7 @@ exports.valueTypeMentionConfig = {
116
121
  // name: task.title,
117
122
  // }))),
118
123
  // picture = <i className="bi bi-list-task pe-1" />;
124
+ /** Shared mention definitions used by editors and rendered messages. */
119
125
  exports.mentionConfigs = [
120
126
  exports.toolMentionConfig,
121
127
  exports.userMentionConfig,
@@ -1,4 +1,5 @@
1
1
  import { type IAssistant, type IDoc } from "@peers-app/peers-sdk";
2
+ /** Edit assistant runner selection and its configuration. */
2
3
  export declare const AssistantConfig: (props: {
3
4
  assistant: IDoc<IAssistant>;
4
5
  }) => import("react/jsx-runtime").JSX.Element;
@@ -6,6 +6,8 @@ const peers_sdk_1 = require("@peers-app/peers-sdk");
6
6
  const io_schema_values_1 = require("../../components/io-schema-values");
7
7
  const loading_indicator_1 = require("../../components/loading-indicator");
8
8
  const hooks_1 = require("../../hooks");
9
+ const tool_catalog_ipc_1 = require("../tools/tool-catalog-ipc");
10
+ /** Edit assistant runner selection and its configuration. */
9
11
  const AssistantConfig = (props) => {
10
12
  const { assistant } = props;
11
13
  const assistantRunnerTool = (0, hooks_1.usePromise)(async () => {
@@ -28,12 +30,27 @@ const AssistantRunnersDropdown = (props) => {
28
30
  const [selectedToolId] = (0, hooks_1.useObservable)(props.selectedToolId);
29
31
  const modelTools = (0, hooks_1.usePromise)(async () => {
30
32
  const modelTools = await (0, peers_sdk_1.Tools)().list({ isAssistantRunner: true });
31
- return modelTools;
33
+ let availableIds;
34
+ try {
35
+ const catalog = await (0, tool_catalog_ipc_1.getToolCatalogConsumer)();
36
+ const entries = await Promise.all(modelTools.map((tool) => catalog.tools.getTool({ toolIdOrName: tool.toolId, runnableOnly: true })));
37
+ availableIds = new Set(modelTools
38
+ .filter((_tool, index) => entries[index].tool?.workflowInvocable)
39
+ .map((tool) => tool.toolId));
40
+ }
41
+ catch {
42
+ availableIds = new Set(modelTools.filter((tool) => tool.catalogState !== "quarantined").map((tool) => tool.toolId));
43
+ }
44
+ return modelTools.map((tool) => ({ tool, available: availableIds.has(tool.toolId) }));
32
45
  });
33
46
  if (!modelTools) {
34
47
  return (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {});
35
48
  }
36
- return ((0, jsx_runtime_1.jsxs)("div", { className: "dropdown", children: [(0, jsx_runtime_1.jsx)("button", { className: "btn btn-secondary dropdown-toggle", type: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false", children: modelTools.find((tool) => tool.toolId === selectedToolId)?.name ?? "Select Tool" }), (0, jsx_runtime_1.jsx)("ul", { className: "dropdown-menu", children: modelTools.map((tool) => {
37
- return ((0, jsx_runtime_1.jsx)("li", { onClick: () => props.selectedToolId(tool.toolId), children: (0, jsx_runtime_1.jsx)("span", { className: "dropdown-item", children: tool.name }) }, tool.toolId));
49
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dropdown", children: [(0, jsx_runtime_1.jsxs)("button", { className: "btn btn-secondary dropdown-toggle", type: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false", children: [modelTools.find(({ tool }) => tool.toolId === selectedToolId)?.tool.name ?? "Select Tool", modelTools.some(({ tool, available }) => tool.toolId === selectedToolId && !available)
50
+ ? " (unavailable)"
51
+ : ""] }), (0, jsx_runtime_1.jsx)("ul", { className: "dropdown-menu", children: modelTools
52
+ .filter(({ tool, available }) => available || tool.toolId === selectedToolId)
53
+ .map(({ tool, available }) => {
54
+ return ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsxs)("button", { className: "dropdown-item", type: "button", disabled: !available, onClick: () => props.selectedToolId(tool.toolId), children: [tool.name, available ? "" : " (unavailable)"] }) }, tool.toolId));
38
55
  }) })] }));
39
56
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
5
+ const react_1 = require("@testing-library/react");
6
+ const assistant_config_1 = require("./assistant-config");
7
+ const mockRunners = [
8
+ { toolId: "selected", name: "Retired", catalogState: "quarantined" },
9
+ { toolId: "hidden", name: "Other unavailable", catalogState: "quarantined" },
10
+ { toolId: "available", name: "Available" },
11
+ ];
12
+ const mockCatalog = jest.fn();
13
+ jest.mock("@peers-app/peers-sdk", () => ({
14
+ ...jest.requireActual("@peers-app/peers-sdk"),
15
+ Tools: () => ({
16
+ get: async () => mockRunners[0],
17
+ list: async () => mockRunners,
18
+ }),
19
+ }));
20
+ jest.mock("../tools/tool-catalog-ipc", () => ({
21
+ getToolCatalogConsumer: () => mockCatalog(),
22
+ }));
23
+ jest.mock("../../components/io-schema-values", () => ({ IOSchemaValues: () => null }));
24
+ test.each([
25
+ "catalog",
26
+ "browser fallback",
27
+ ])("runner selection retains a disabled current runner in %s", async (mode) => {
28
+ mockCatalog.mockReset();
29
+ if (mode === "catalog")
30
+ mockCatalog.mockResolvedValue({
31
+ tools: {
32
+ getTool: async ({ toolIdOrName }) => ({
33
+ tool: { workflowInvocable: toolIdOrName === "available" },
34
+ }),
35
+ },
36
+ });
37
+ else
38
+ mockCatalog.mockRejectedValue(new Error("No host transport"));
39
+ const selected = (0, peers_sdk_1.observable)("selected");
40
+ const assistant = {
41
+ assistantRunnerToolId: "selected",
42
+ qs: { assistantRunnerToolId: selected },
43
+ };
44
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(assistant_config_1.AssistantConfig, { assistant: assistant }));
45
+ await react_1.screen.findByRole("button", { name: "Available" });
46
+ const retired = container.querySelector(".dropdown-menu button:disabled");
47
+ expect(retired).toHaveTextContent("Retired (unavailable)");
48
+ expect(container.querySelector(".dropdown-toggle")).toHaveTextContent("Retired (unavailable)");
49
+ expect(react_1.screen.queryByText("Other unavailable")).not.toBeInTheDocument();
50
+ if (!retired)
51
+ throw new Error("Missing unavailable runner option");
52
+ react_1.fireEvent.click(retired);
53
+ expect(selected()).toBe("selected");
54
+ react_1.fireEvent.click(react_1.screen.getByRole("button", { name: "Available" }));
55
+ await (0, react_1.waitFor)(() => expect(selected()).toBe("available"));
56
+ expect(react_1.screen.queryByText("Retired (unavailable)")).not.toBeInTheDocument();
57
+ });
@@ -79,7 +79,7 @@ const AppearanceSettingsTab = () => {
79
79
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-3", children: "Appearance" }), (0, jsx_runtime_1.jsx)("div", { className: "mb-3", children: (0, jsx_runtime_1.jsx)(color_mode_dropdown_1.ColorModeDropdown, {}) }), (0, jsx_runtime_1.jsx)("div", { className: "mb-3", children: (0, jsx_runtime_1.jsx)(layout_picker_1.LayoutPicker, {}) })] }));
80
80
  };
81
81
  const AdvancedSettingsTab = () => {
82
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-3", children: "Advanced Settings" }), (0, jsx_runtime_1.jsx)(PackagesRootDirectory, {}), (0, jsx_runtime_1.jsx)(ReloadPackagesOnPageRefresh, {}), (0, jsx_runtime_1.jsx)(AutoUpdatePeersCore, {}), (0, jsx_runtime_1.jsx)(ResetDeviceSyncInfos, {}), (0, jsx_runtime_1.jsx)(DeleteLocalDatabase, {}), (0, jsx_runtime_1.jsx)(CleanupOldDataDir, {}), (0, jsx_runtime_1.jsx)(ImportOldPeersData, {}), (0, jsx_runtime_1.jsx)(ResetChangeTracking, {})] }));
82
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-3", children: "Advanced Settings" }), (0, jsx_runtime_1.jsx)(PackagesRootDirectory, {}), (0, jsx_runtime_1.jsx)(ReloadPackagesOnPageRefresh, {}), (0, jsx_runtime_1.jsx)(AutoUpdatePeersCore, {}), (0, jsx_runtime_1.jsx)(ResetDeviceSyncInfos, {}), (0, jsx_runtime_1.jsx)(DeleteLocalDatabase, {}), (0, jsx_runtime_1.jsx)(CleanupOldDataDir, {}), (0, jsx_runtime_1.jsx)(ResetChangeTracking, {})] }));
83
83
  };
84
84
  const ProfileSection = () => {
85
85
  const me = (0, hooks_1.usePromise)(async () => {
@@ -228,64 +228,6 @@ const ResetChangeTracking = () => {
228
228
  }
229
229
  }, children: busy ? "Resetting…" : "Reset Change Tracking" })] }));
230
230
  };
231
- const IMPORT_TOOL_ID = "00mh0wlipkdbeaw8imptsk001";
232
- const ImportOldPeersData = () => {
233
- const fileInputRef = (0, react_1.useRef)(null);
234
- const [filePath, setFilePath] = (0, react_1.useState)("");
235
- const [_fileName, setFileName] = (0, react_1.useState)("");
236
- const [loading, setLoading] = (0, react_1.useState)(false);
237
- const [result, setResult] = (0, react_1.useState)(null);
238
- const [error, setError] = (0, react_1.useState)("");
239
- const handleFileSelect = (e) => {
240
- const file = e.target.files?.[0];
241
- if (!file)
242
- return;
243
- // Electron exposes .path on File objects
244
- const path = file.path;
245
- if (path) {
246
- setFilePath(path);
247
- setFileName(file.name);
248
- }
249
- else {
250
- setFileName(file.name);
251
- setFilePath("");
252
- setError("File path not available. This feature requires the Electron desktop app.");
253
- }
254
- setResult(null);
255
- setError("");
256
- };
257
- const handleDryRun = async () => {
258
- if (!filePath)
259
- return;
260
- setLoading(true);
261
- setError("");
262
- setResult(null);
263
- try {
264
- const response = await peers_sdk_1.rpcServerCalls.runTool(IMPORT_TOOL_ID, {
265
- filePath,
266
- dryRun: true,
267
- });
268
- if (response?.result) {
269
- setResult(response.result);
270
- }
271
- else {
272
- setError("Unexpected response from import tool");
273
- }
274
- }
275
- catch (err) {
276
- setError(err.message || "Failed to run dry run");
277
- }
278
- finally {
279
- setLoading(false);
280
- }
281
- };
282
- return ((0, jsx_runtime_1.jsxs)("div", { className: "mt-4 pt-3 border-top", children: [(0, jsx_runtime_1.jsx)("h6", { className: "mb-2", children: "Import Old Peers Data" }), (0, jsx_runtime_1.jsx)("small", { className: "text-muted d-block mb-2", children: "Import tasks and log entries from an old peers JSON export file." }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-2 mb-2", children: [(0, jsx_runtime_1.jsx)("input", { ref: fileInputRef, type: "file", accept: ".json", className: "form-control form-control-sm", style: { maxWidth: 350 }, onChange: handleFileSelect }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-outline-primary btn-sm", onClick: handleDryRun, disabled: !filePath || loading, children: loading ? "Analyzing..." : "Dry Run" })] }), error && (0, jsx_runtime_1.jsx)("div", { className: "alert alert-danger py-1 px-2 small mt-2", children: error }), result && (0, jsx_runtime_1.jsx)(DryRunResults, { result: result })] }));
283
- };
284
- const DryRunResults = ({ result }) => {
285
- const sm = result.statusMapping;
286
- const um = result.userMapping;
287
- return ((0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "alert alert-info py-2 px-3 small", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Summary:" }), " ", result.totalTasks.toLocaleString(), " tasks and", " ", result.totalLogEntries.toLocaleString(), " log entries across ", result.groups.length, " groups (", result.totalRecords.toLocaleString(), " total records in file)"] }), result.warnings.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-warning py-2 px-3 small", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Warnings:" }), (0, jsx_runtime_1.jsx)("ul", { className: "mb-0 ps-3", children: result.warnings.map((w, i) => ((0, jsx_runtime_1.jsx)("li", { children: w }, i))) })] })), (0, jsx_runtime_1.jsxs)("details", { className: "mb-2", children: [(0, jsx_runtime_1.jsx)("summary", { className: "small fw-bold", style: { cursor: "pointer" }, children: "Status Mapping" }), (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm small mt-1", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Mapping" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Count" })] }) }), (0, jsx_runtime_1.jsxs)("tbody", { children: [(0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "In-Progress \u2192 Done" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.inProgressToDone.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "In-Progress \u2192 In-Progress" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.inProgressKeep.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "Queued \u2192 Done" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.queuedToDone.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "Queued \u2192 Queued" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.queuedKeep.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "Backlog \u2192 Done" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.backlogToDone.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "Backlog \u2192 Backlog" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.backlogKeep.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "No status \u2192 Done" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.noStatusToDone.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "No status \u2192 Backlog" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: sm.noStatusToBacklog.toLocaleString() })] })] })] })] }), (0, jsx_runtime_1.jsxs)("details", { className: "mb-2", children: [(0, jsx_runtime_1.jsx)("summary", { className: "small fw-bold", style: { cursor: "pointer" }, children: "User Mapping" }), (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm small mt-1", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "User" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Tasks" })] }) }), (0, jsx_runtime_1.jsxs)("tbody", { children: [(0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "Mark" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: um.mark.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "Blair" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: um.blair.toLocaleString() })] }), (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: "Other \u2192 Mark" }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: um.other.toLocaleString() })] })] })] })] }), (0, jsx_runtime_1.jsxs)("details", { open: true, className: "mb-2", children: [(0, jsx_runtime_1.jsxs)("summary", { className: "small fw-bold", style: { cursor: "pointer" }, children: ["Groups (", result.groups.length, ")"] }), (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm small mt-1", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Group" }), (0, jsx_runtime_1.jsx)("th", { children: "Route" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Tasks" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Active" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Done" }), (0, jsx_runtime_1.jsx)("th", { className: "text-end", children: "Logs" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: result.groups.map((g) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: g.groupName }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("span", { className: `badge bg-${g.context === "home" ? "success" : "secondary"}`, children: g.context }) }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: g.taskCount.toLocaleString() }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: g.activeCount.toLocaleString() }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: g.doneCount.toLocaleString() }), (0, jsx_runtime_1.jsx)("td", { className: "text-end", children: g.logEntryCount.toLocaleString() })] }, g.groupId))) })] })] })] }));
288
- };
289
231
  const LogoutSection = () => {
290
232
  if (!isBrowserPwaHost)
291
233
  return null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const server_1 = require("react-dom/server");
5
+ const settings_page_1 = require("./settings-page");
6
+ jest.mock("../../components/tooltip", () => ({ Tooltip: () => null }));
7
+ jest.mock("../../hooks", () => ({ usePromise: () => undefined, useObservable: () => [false] }));
8
+ jest.mock("../../components/tabs", () => ({
9
+ Tabs: ({ tabs }) => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: tabs.map((tab) => ((0, jsx_runtime_1.jsx)("div", { children: tab.content }, tab.name))) })),
10
+ }));
11
+ jest.mock("../../root-layout/layout-picker", () => ({ LayoutPicker: () => (0, jsx_runtime_1.jsx)("div", { children: "Layout" }) }));
12
+ jest.mock("./color-mode-dropdown", () => ({ ColorModeDropdown: () => (0, jsx_runtime_1.jsx)("div", { children: "Theme" }) }));
13
+ jest.mock("../../globals", () => ({ me: {}, isDesktop: () => false }));
14
+ test("Settings renders advanced controls without the retired Tasks importer", () => {
15
+ const html = (0, server_1.renderToStaticMarkup)((0, jsx_runtime_1.jsx)(settings_page_1.SettingsPage, {}));
16
+ expect(html).toContain("Advanced Settings");
17
+ expect(html).not.toContain("Import Old Peers");
18
+ expect(html).not.toContain("Dry Run");
19
+ });
@@ -0,0 +1,118 @@
1
+ import { type IToolCatalogConsumer, type IToolCatalogEntry } from "@peers-app/peers-sdk";
2
+ /** Obtain a catalog consumer for the selected host context; PWA callers can fall back to rows. */
3
+ export declare function getToolCatalogConsumer(dataContextId?: string): Promise<IToolCatalogConsumer>;
4
+ /** Get live tool details, retaining historical row metadata when the host catalog is absent. */
5
+ export declare function getToolDetails(toolId: string): Promise<{
6
+ row: {
7
+ code: string;
8
+ name: string;
9
+ usageDescription: string;
10
+ inputSchema: {
11
+ type: import("@peers-app/peers-sdk").IOSchemaType;
12
+ fields: {
13
+ name: string;
14
+ type: import("@peers-app/peers-sdk").FieldType;
15
+ optional?: boolean | undefined;
16
+ description?: string | undefined;
17
+ defaultValue?: any;
18
+ isArray?: boolean | undefined;
19
+ subType?: string | undefined;
20
+ }[];
21
+ };
22
+ outputSchema: {
23
+ type: import("@peers-app/peers-sdk").IOSchemaType;
24
+ fields: {
25
+ name: string;
26
+ type: import("@peers-app/peers-sdk").FieldType;
27
+ optional?: boolean | undefined;
28
+ description?: string | undefined;
29
+ defaultValue?: any;
30
+ isArray?: boolean | undefined;
31
+ subType?: string | undefined;
32
+ }[];
33
+ };
34
+ toolId: string;
35
+ contractId?: string | undefined;
36
+ contractVersion?: number | undefined;
37
+ toolName?: string | undefined;
38
+ catalogIdentity?: "deterministic" | "legacy-alias" | undefined;
39
+ catalogState?: "resolved" | "unresolved" | "quarantined" | undefined;
40
+ catalogReason?: string | undefined;
41
+ configSchema?: {
42
+ type: import("@peers-app/peers-sdk").IOSchemaType;
43
+ fields: {
44
+ name: string;
45
+ type: import("@peers-app/peers-sdk").FieldType;
46
+ optional?: boolean | undefined;
47
+ description?: string | undefined;
48
+ defaultValue?: any;
49
+ isArray?: boolean | undefined;
50
+ subType?: string | undefined;
51
+ }[];
52
+ } | undefined;
53
+ isAssistantRunner?: boolean | undefined;
54
+ detailedDescription?: string | undefined;
55
+ codeRepository?: string | undefined;
56
+ packageId?: string | undefined;
57
+ authorityPropagation?: "preserve" | "cap" | undefined;
58
+ accessLevel?: number | undefined;
59
+ remoteAccessLevel?: number | undefined;
60
+ };
61
+ entry: IToolCatalogEntry | undefined;
62
+ } | undefined>;
63
+ /** Query workflow mention candidates; unavailable browser hosts retain non-quarantined rows. */
64
+ export declare function listWorkflowMentionTools(query: string, limit: number): Promise<IToolCatalogEntry[] | {
65
+ code: string;
66
+ name: string;
67
+ usageDescription: string;
68
+ inputSchema: {
69
+ type: import("@peers-app/peers-sdk").IOSchemaType;
70
+ fields: {
71
+ name: string;
72
+ type: import("@peers-app/peers-sdk").FieldType;
73
+ optional?: boolean | undefined;
74
+ description?: string | undefined;
75
+ defaultValue?: any;
76
+ isArray?: boolean | undefined;
77
+ subType?: string | undefined;
78
+ }[];
79
+ };
80
+ outputSchema: {
81
+ type: import("@peers-app/peers-sdk").IOSchemaType;
82
+ fields: {
83
+ name: string;
84
+ type: import("@peers-app/peers-sdk").FieldType;
85
+ optional?: boolean | undefined;
86
+ description?: string | undefined;
87
+ defaultValue?: any;
88
+ isArray?: boolean | undefined;
89
+ subType?: string | undefined;
90
+ }[];
91
+ };
92
+ toolId: string;
93
+ contractId?: string | undefined;
94
+ contractVersion?: number | undefined;
95
+ toolName?: string | undefined;
96
+ catalogIdentity?: "deterministic" | "legacy-alias" | undefined;
97
+ catalogState?: "resolved" | "unresolved" | "quarantined" | undefined;
98
+ catalogReason?: string | undefined;
99
+ configSchema?: {
100
+ type: import("@peers-app/peers-sdk").IOSchemaType;
101
+ fields: {
102
+ name: string;
103
+ type: import("@peers-app/peers-sdk").FieldType;
104
+ optional?: boolean | undefined;
105
+ description?: string | undefined;
106
+ defaultValue?: any;
107
+ isArray?: boolean | undefined;
108
+ subType?: string | undefined;
109
+ }[];
110
+ } | undefined;
111
+ isAssistantRunner?: boolean | undefined;
112
+ detailedDescription?: string | undefined;
113
+ codeRepository?: string | undefined;
114
+ packageId?: string | undefined;
115
+ authorityPropagation?: "preserve" | "cap" | undefined;
116
+ accessLevel?: number | undefined;
117
+ remoteAccessLevel?: number | undefined;
118
+ }[]>;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getToolCatalogConsumer = getToolCatalogConsumer;
4
+ exports.getToolDetails = getToolDetails;
5
+ exports.listWorkflowMentionTools = listWorkflowMentionTools;
6
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
7
+ const consumers = new Map();
8
+ /** Obtain a catalog consumer for the selected host context; PWA callers can fall back to rows. */
9
+ async function getToolCatalogConsumer(dataContextId) {
10
+ if (!(0, peers_sdk_1.getHostContractTransport)())
11
+ throw new Error("Tool catalog host transport unavailable");
12
+ dataContextId ??= (await (0, peers_sdk_1.getDefaultDataContext)()).dataContextId;
13
+ let handle = consumers.get(dataContextId);
14
+ if (!handle) {
15
+ const id = dataContextId;
16
+ handle = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getToolCatalogContractDefinition)(), {
17
+ dataContextId: async () => id,
18
+ });
19
+ consumers.set(id, handle);
20
+ }
21
+ return handle.get();
22
+ }
23
+ /** Get live tool details, retaining historical row metadata when the host catalog is absent. */
24
+ async function getToolDetails(toolId) {
25
+ const row = await (0, peers_sdk_1.Tools)().get(toolId);
26
+ if (!row)
27
+ return undefined;
28
+ let entry;
29
+ try {
30
+ entry = (await (await getToolCatalogConsumer()).tools.getTool({ toolIdOrName: toolId })).tool;
31
+ }
32
+ catch {
33
+ /* Browser-only hosts retain catalog rows. */
34
+ }
35
+ return { row, entry };
36
+ }
37
+ /** Query workflow mention candidates; unavailable browser hosts retain non-quarantined rows. */
38
+ async function listWorkflowMentionTools(query, limit) {
39
+ try {
40
+ const { tools } = await (await getToolCatalogConsumer()).tools.listTools({ query, limit: 500 });
41
+ return tools.filter((entry) => entry.workflowInvocable).slice(0, limit);
42
+ }
43
+ catch {
44
+ return (await (0, peers_sdk_1.Tools)().list({ name: { $matchWords: query } }, { pageSize: 500 }))
45
+ .filter((row) => row.catalogState !== "quarantined")
46
+ .slice(0, limit);
47
+ }
48
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const list = jest.fn();
4
+ const get = jest.fn();
5
+ const rows = jest.fn();
6
+ let transport = true;
7
+ jest.mock("@peers-app/peers-sdk", () => ({
8
+ getDefaultDataContext: async () => ({ dataContextId: "group" }),
9
+ getHostContractTransport: () => (transport ? {} : undefined),
10
+ getToolCatalogContractDefinition: () => ({}),
11
+ hostContractConsumer: () => ({ get: async () => ({ tools: { listTools: list, getTool: get } }) }),
12
+ Tools: () => ({ list: rows, get: async (toolId) => ({ toolId, name: "historical" }) }),
13
+ }));
14
+ const mention_configs_1 = require("../../mention-configs");
15
+ const tool_catalog_ipc_1 = require("./tool-catalog-ipc");
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ transport = true;
19
+ });
20
+ test("tool mentions include only workflow-invocable catalog tools", async () => {
21
+ list.mockResolvedValue({
22
+ tools: [
23
+ { toolId: "run", name: "Run", workflowInvocable: true },
24
+ { toolId: "approve", name: "Approve", workflowInvocable: false },
25
+ ],
26
+ });
27
+ expect(await mention_configs_1.toolMentionConfig.query("run")).toEqual([{ kind: "tool", id: "run", name: "Run" }]);
28
+ expect(rows).not.toHaveBeenCalled();
29
+ expect(await mention_configs_1.toolMentionConfig.getById("old")).toEqual({
30
+ kind: "tool",
31
+ id: "old",
32
+ name: "historical",
33
+ });
34
+ });
35
+ test("browser fallback omits quarantined rows and preserves historical detail", async () => {
36
+ transport = false;
37
+ rows.mockResolvedValue([
38
+ { toolId: "old", name: "Old" },
39
+ { toolId: "bad", name: "Bad", catalogState: "quarantined" },
40
+ ]);
41
+ expect(await (0, tool_catalog_ipc_1.listWorkflowMentionTools)("", 10)).toEqual([{ toolId: "old", name: "Old" }]);
42
+ expect(await (0, tool_catalog_ipc_1.getToolDetails)("old")).toEqual({
43
+ row: { toolId: "old", name: "historical" },
44
+ entry: undefined,
45
+ });
46
+ });
@@ -1,5 +1,4 @@
1
- interface IToolDetailsProps {
1
+ /** Read-only permanent tool metadata and live provider details. */
2
+ export declare const ToolDetails: (props: {
2
3
  toolId: string;
3
- }
4
- export declare const ToolDetails: (props: IToolDetailsProps) => import("react/jsx-runtime").JSX.Element;
5
- export {};
4
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -2,54 +2,35 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ToolDetails = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
- const peers_sdk_1 = require("@peers-app/peers-sdk");
6
- const input_1 = require("../../components/input");
7
5
  const loading_indicator_1 = require("../../components/loading-indicator");
8
- const save_button_1 = require("../../components/save-button");
9
6
  const tabs_1 = require("../../components/tabs");
10
7
  const hooks_1 = require("../../hooks");
11
8
  const tabs_state_1 = require("../../tabs-layout/tabs-state");
12
- const tool_code_1 = require("./tool-code");
9
+ const tool_catalog_ipc_1 = require("./tool-catalog-ipc");
13
10
  const tool_info_1 = require("./tool-info");
14
11
  const tool_schema_1 = require("./tool-schema");
12
+ /** Read-only permanent tool metadata and live provider details. */
15
13
  const ToolDetails = (props) => {
16
- const tool = (0, hooks_1.usePromise)(async () => {
17
- const toolData = await (0, peers_sdk_1.Tools)().get(props.toolId);
18
- if (!toolData)
19
- return null;
20
- (0, tabs_state_1.updateActiveTabTitle)(toolData.name || "Tool");
21
- return (0, peers_sdk_1.Tools)().initDoc(toolData);
14
+ const details = (0, hooks_1.usePromise)(async () => {
15
+ const details = await (0, tool_catalog_ipc_1.getToolDetails)(props.toolId);
16
+ if (details)
17
+ (0, tabs_state_1.updateActiveTabTitle)(details.entry?.name || details.row.name || "Tool");
18
+ return details ?? null;
22
19
  }, undefined, [props.toolId]);
23
- if (tool === null) {
20
+ if (details === null)
24
21
  return (0, jsx_runtime_1.jsx)("div", { children: "Tool not found" });
25
- }
26
- if (!tool) {
22
+ if (!details)
27
23
  return (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {});
28
- }
29
- function saveChanges() {
30
- if (!tool)
31
- return;
32
- tool.save();
33
- }
34
- return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-3", style: { overflowY: "hidden" }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex", children: [(0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-tools me-2" }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-grow-1", children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)(input_1.Input, { className: "border border-0", style: { width: "100%", outline: "none", backgroundColor: "transparent" }, value: tool.qs.name }, tool.toolId) }) }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(save_button_1.SaveButton, { onClick: saveChanges, doc: tool }, tool.toolId) })] }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: [
24
+ const { row, entry } = details;
25
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-3", children: [(0, jsx_runtime_1.jsxs)("h4", { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-tools me-2" }), entry?.name ?? row.name] }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: [
35
26
  {
36
27
  name: "Info",
37
- content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(tool_info_1.ToolInfo, { tool: tool }) })),
28
+ content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(tool_info_1.ToolInfo, { tool: row, entry: entry }) })),
38
29
  },
39
30
  {
40
31
  name: "Schema",
41
- content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(tool_schema_1.ToolSchema, { tool: tool }) })),
32
+ content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(tool_schema_1.ToolSchema, { tool: row, entry: entry }) })),
42
33
  },
43
- {
44
- name: "Code",
45
- content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(tool_code_1.ToolCode, { tool: tool }) })),
46
- },
47
- // {
48
- // name: 'Tests', content:
49
- // <ScreenTabBody>
50
- // <ToolTestList toolId={tool.toolId} />
51
- // </ScreenTabBody>
52
- // },
53
- ] }, tool.toolId)] }));
34
+ ] }, row.toolId)] }));
54
35
  };
55
36
  exports.ToolDetails = ToolDetails;
@@ -1,5 +1,6 @@
1
- import { type IDoc, type ITool } from "@peers-app/peers-sdk";
2
- /** Info tab for a tool, including its required access level. */
3
- export declare const ToolInfo: (props: {
4
- tool: IDoc<ITool>;
1
+ import { type ITool, type IToolCatalogEntry } from "@peers-app/peers-sdk";
2
+ /** Display permanent identity and the host’s current dispatch and access projection. */
3
+ export declare const ToolInfo: ({ tool, entry }: {
4
+ tool: ITool;
5
+ entry?: IToolCatalogEntry;
5
6
  }) => import("react/jsx-runtime").JSX.Element;