@klarkxy/dsh-safe-auto 0.2.2 → 0.3.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.
package/lib/client.js CHANGED
@@ -33,57 +33,69 @@ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-
33
33
  let react_dom = require("react-dom");
34
34
  let react_jsx_runtime = require("react/jsx-runtime");
35
35
  //#region ../dsh-model-route/lib/menu.js
36
- /** Localize standard effort ids only when the provider has not supplied a display name. */
37
- function modelMenuEffortLabel(effort, locale) {
38
- if (effort.name !== effort.id) return effort.name;
39
- return {
40
- off: ["关闭", "Off"],
41
- none: ["无", "None"],
42
- minimal: ["最低", "Minimal"],
43
- low: ["低", "Low"],
44
- medium: ["中", "Medium"],
45
- high: ["高", "High"],
46
- xhigh: ["极高", "Extra high"],
47
- max: ["最高", "Maximum"],
48
- ultra: ["超高", "Ultra"]
49
- }[effort.id]?.[locale?.toLowerCase().startsWith("zh") ? 0 : 1] ?? effort.name;
50
- }
51
- /** The short model label shown inside a provider group. */
52
- function modelMenuShortName(choice) {
53
- const sep = choice.label.indexOf(" / ");
54
- return sep > 0 ? choice.label.slice(sep + 3) : choice.model;
55
- }
56
- /** Group catalog choices by provider and filter them by a case-insensitive query. */
57
- function groupModelMenuChoices(choices, query) {
58
- const needle = query.trim().toLowerCase();
59
- const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
60
- const groups = [];
61
- for (const choice of matched) {
62
- const sep = choice.label.indexOf(" / ");
63
- const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
64
- const last = groups.at(-1);
65
- if (last && last.provider === choice.provider) last.items.push(choice);
66
- else groups.push({
67
- provider: choice.provider,
68
- name,
69
- items: [choice]
70
- });
71
- }
72
- return groups;
73
- }
74
- //#endregion
75
- //#region ../dsh-model-route/lib/index.js
76
36
  /**
77
37
  * Host model-catalog parsing shared by plugin settings UIs and model hubs.
78
38
  * Pure and browser-safe: no React, no host imports. The catalog envelope is
79
39
  * structural so hosts may add fields without breaking consumers.
80
40
  */
41
+ /** Both services are required: Cordis checks the nested namespace separately. */
42
+ const MODEL_CATALOG_INJECT = ["remote", "remote.session"];
81
43
  function asRecord(value) {
82
44
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
83
45
  }
84
46
  function asText(value) {
85
47
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
86
48
  }
49
+ /** A useful diagnostic even when a transport rejects with a plain object. */
50
+ function modelCatalogErrorMessage(value) {
51
+ if (value instanceof Error && value.message) return value.message;
52
+ return asText(asRecord(value)?.message) ?? asText(value) ?? "Unable to load the model catalog.";
53
+ }
54
+ /**
55
+ * Validate and unwrap a successful catalog without discarding provider failures
56
+ * or newer host metadata. Names and metadata remain optional for older hosts.
57
+ * A broken response is never presented as a successfully loaded empty menu.
58
+ */
59
+ function checkedModelCatalog(value) {
60
+ const envelope = asRecord(value);
61
+ if (envelope?.ok === false) throw new Error(modelCatalogErrorMessage(envelope.error ?? envelope));
62
+ const root = envelope?.ok === true ? asRecord(envelope.value) : envelope;
63
+ const malformed = (field) => {
64
+ throw new Error(`Invalid model catalog: ${field}.`);
65
+ };
66
+ if (!root || !Array.isArray(root.groups)) return malformed("groups must be an array");
67
+ for (const value of root.groups) {
68
+ const group = asRecord(value);
69
+ if (!group || !asText(group.id) || !Array.isArray(group.models)) return malformed("each group needs an id and models array");
70
+ for (const value of group.models) {
71
+ const model = asRecord(value);
72
+ if (!model || !asText(model.id)) return malformed("each model needs an id");
73
+ if (model.reasoning !== void 0) {
74
+ const reasoning = asRecord(model.reasoning);
75
+ if (!reasoning || !Array.isArray(reasoning.efforts) || reasoning.efforts.some((value) => !parseEffort(value))) return malformed("reasoning efforts must be an array of identified efforts");
76
+ }
77
+ }
78
+ }
79
+ if (root.failures !== void 0 && (!Array.isArray(root.failures) || root.failures.some((value) => {
80
+ const failure = asRecord(value);
81
+ return !failure || !asText(failure.id) || typeof failure.name !== "string" || typeof failure.message !== "string";
82
+ }))) return malformed("failures must be an array of provider failures");
83
+ return root;
84
+ }
85
+ /** Read through the injected session namespace; absence and rejection are errors. */
86
+ async function readModelCatalog(client) {
87
+ const session = client.remote?.session;
88
+ if (typeof session?.modelCatalog !== "function") throw new Error("Model catalog unavailable: inject both remote and remote.session.");
89
+ return checkedModelCatalog(await session.modelCatalog());
90
+ }
91
+ /** Provider failures are partial results, not a transport failure. */
92
+ function modelCatalogFailures(value) {
93
+ try {
94
+ return checkedModelCatalog(value).failures ?? [];
95
+ } catch {
96
+ return [];
97
+ }
98
+ }
87
99
  function parseEffort(value) {
88
100
  const row = asRecord(value);
89
101
  const id = row ? asText(row.id) : void 0;
@@ -139,9 +151,201 @@ function modelMenuEffortOptions(choice, current) {
139
151
  });
140
152
  return listed;
141
153
  }
154
+ /** Localize standard effort ids only when the provider has not supplied a display name. */
155
+ function modelMenuEffortLabel(effort, locale) {
156
+ if (effort.name !== effort.id) return effort.name;
157
+ return {
158
+ off: ["关闭", "Off"],
159
+ none: ["无", "None"],
160
+ minimal: ["最低", "Minimal"],
161
+ low: ["低", "Low"],
162
+ medium: ["中", "Medium"],
163
+ high: ["高", "High"],
164
+ xhigh: ["极高", "Extra high"],
165
+ max: ["最高", "Maximum"],
166
+ ultra: ["超高", "Ultra"]
167
+ }[effort.id]?.[locale?.toLowerCase().startsWith("zh") ? 0 : 1] ?? effort.name;
168
+ }
169
+ /** The short model label shown inside a provider group. */
170
+ function modelMenuShortName(choice) {
171
+ const sep = choice.label.indexOf(" / ");
172
+ return sep > 0 ? choice.label.slice(sep + 3) : choice.model;
173
+ }
174
+ /** Group catalog choices by provider and filter them by a case-insensitive query. */
175
+ function groupModelMenuChoices(choices, query) {
176
+ const needle = query.trim().toLowerCase();
177
+ const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
178
+ const groups = [];
179
+ for (const choice of matched) {
180
+ const sep = choice.label.indexOf(" / ");
181
+ const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
182
+ const last = groups.at(-1);
183
+ if (last && last.provider === choice.provider) last.items.push(choice);
184
+ else groups.push({
185
+ provider: choice.provider,
186
+ name,
187
+ items: [choice]
188
+ });
189
+ }
190
+ return groups;
191
+ }
142
192
  //#endregion
143
193
  //#region ../dsh-model-route/lib/ui.js
144
194
  /**
195
+ * Latest refresh wins, including retries while a previous request is pending.
196
+ * Keep load stable (for example with useCallback); changing it invalidates the
197
+ * previous source and fetches again, so switching clients cannot publish stale data.
198
+ */
199
+ function useModelCatalog(load, options = {}) {
200
+ const enabled = options.enabled !== false;
201
+ const enabledRef = (0, react.useRef)(enabled);
202
+ enabledRef.current = enabled;
203
+ const live = (0, react.useRef)(false);
204
+ const source = (0, react.useRef)(load);
205
+ const revision = (0, react.useRef)(0);
206
+ const [state, setState] = (0, react.useState)(() => {
207
+ try {
208
+ return {
209
+ catalog: options.initialCatalog === void 0 ? void 0 : checkedModelCatalog(options.initialCatalog),
210
+ loading: enabled,
211
+ error: ""
212
+ };
213
+ } catch (error) {
214
+ return {
215
+ catalog: void 0,
216
+ loading: enabled,
217
+ error: modelCatalogErrorMessage(error)
218
+ };
219
+ }
220
+ });
221
+ const refresh = (0, react.useCallback)(async () => {
222
+ if (!live.current || !enabledRef.current) return;
223
+ const request = ++revision.current;
224
+ setState((previous) => ({
225
+ ...previous,
226
+ loading: true,
227
+ error: ""
228
+ }));
229
+ try {
230
+ const catalog = checkedModelCatalog(await load());
231
+ if (live.current && request === revision.current) setState({
232
+ catalog,
233
+ loading: false,
234
+ error: ""
235
+ });
236
+ } catch (error) {
237
+ if (live.current && request === revision.current) setState((previous) => ({
238
+ ...previous,
239
+ loading: false,
240
+ error: modelCatalogErrorMessage(error)
241
+ }));
242
+ }
243
+ }, [load, enabled]);
244
+ (0, react.useEffect)(() => {
245
+ live.current = true;
246
+ if (source.current !== load) {
247
+ source.current = load;
248
+ setState({
249
+ catalog: void 0,
250
+ loading: enabled,
251
+ error: ""
252
+ });
253
+ }
254
+ if (enabled) refresh();
255
+ else setState((previous) => ({
256
+ ...previous,
257
+ loading: false
258
+ }));
259
+ return () => {
260
+ live.current = false;
261
+ ++revision.current;
262
+ };
263
+ }, [enabled, refresh]);
264
+ (0, react.useEffect)(() => {
265
+ if (!enabled) return;
266
+ const reload = () => {
267
+ refresh();
268
+ };
269
+ if (typeof window !== "undefined") window.addEventListener("focus", reload);
270
+ let unsubscribe;
271
+ try {
272
+ unsubscribe = options.subscribe?.(reload);
273
+ } catch (error) {
274
+ setState((previous) => ({
275
+ ...previous,
276
+ error: modelCatalogErrorMessage(error)
277
+ }));
278
+ }
279
+ return () => {
280
+ if (typeof window !== "undefined") window.removeEventListener("focus", reload);
281
+ unsubscribe?.();
282
+ };
283
+ }, [
284
+ enabled,
285
+ refresh,
286
+ options.subscribe
287
+ ]);
288
+ const choices = (0, react.useMemo)(() => parseModelMenuChoices(state.catalog), [state.catalog]);
289
+ const failures = (0, react.useMemo)(() => state.catalog === void 0 ? [] : modelCatalogFailures(state.catalog), [state.catalog]);
290
+ return {
291
+ ...state,
292
+ choices,
293
+ failures,
294
+ refresh
295
+ };
296
+ }
297
+ /** Also accepts a mapped state from a caller that owns broader async orchestration. */
298
+ function ModelCatalogStatus({ state, locale }) {
299
+ const zh = locale?.toLowerCase().startsWith("zh") === true;
300
+ const copy = zh ? {
301
+ loading: "正在加载模型…",
302
+ error: "模型列表加载失败",
303
+ partial: "部分提供商的模型未能加载",
304
+ retry: "重试"
305
+ } : {
306
+ loading: "Loading models…",
307
+ error: "Unable to load models",
308
+ partial: "Some providers could not load their models",
309
+ retry: "Retry"
310
+ };
311
+ if (!state.loading && !state.error && state.failures.length === 0) return null;
312
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
313
+ className: "dsh-model-catalog-status",
314
+ lang: zh ? "zh" : "en",
315
+ children: [
316
+ state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
317
+ role: "status",
318
+ children: copy.loading
319
+ }) : null,
320
+ state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
321
+ role: "alert",
322
+ children: [
323
+ copy.error,
324
+ ": ",
325
+ state.error
326
+ ]
327
+ }) : null,
328
+ state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
329
+ role: "status",
330
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
331
+ failure.name || failure.id,
332
+ ": ",
333
+ failure.message
334
+ ] }, `${failure.id}:${index}`)) })]
335
+ }) : null,
336
+ state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
337
+ type: "button",
338
+ size: "sm",
339
+ disabled: state.loading,
340
+ onClick: () => {
341
+ state.refresh();
342
+ },
343
+ children: copy.retry
344
+ }) : null
345
+ ]
346
+ });
347
+ }
348
+ /**
145
349
  * ModelMenu: the standard editor of one model-route field — the visible half
146
350
  * of the x-model-route contract. A feature plugin's settings page renders it
147
351
  * for its own marked field; the model hub renders one per detected field.
@@ -1149,6 +1353,29 @@ ${scope} {
1149
1353
  & .dsh-ui-select:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
1150
1354
  & .dsh-ui-select[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
1151
1355
 
1356
+ /* --- Native textarea. The primitives ship no textarea either, so a settings
1357
+ * prompt keeps the platform element and the same field shell as the select:
1358
+ * 0.5px l4, radius-md, layer-3, 13/1.5 type. It grows vertically instead of
1359
+ * locking to the select's 34px row. --- */
1360
+ & .dsh-ui-textarea {
1361
+ box-sizing: border-box;
1362
+ width: 100%;
1363
+ min-width: 0;
1364
+ resize: vertical;
1365
+ padding: 8px 12px;
1366
+ border: 0.5px solid var(--dsw-alias-border-l4);
1367
+ border-radius: var(--dsw-radius-md);
1368
+ background: var(--dsw-alias-bg-layer-3);
1369
+ font: inherit;
1370
+ font-size: 13px;
1371
+ line-height: 1.5;
1372
+ color: var(--dsw-alias-label-primary);
1373
+ }
1374
+ & .dsh-ui-textarea:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
1375
+ & .dsh-ui-textarea:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
1376
+ & .dsh-ui-textarea::placeholder { color: var(--dsw-alias-label-dimmed); }
1377
+ & .dsh-ui-textarea[aria-invalid='true'] { border-color: var(--dsw-alias-state-error-primary); }
1378
+
1152
1379
  /* Long panels scroll inside themselves instead of growing the host page. */
1153
1380
  & .dsh-ui-panel { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
1154
1381
  & .dsh-ui-panel-wide { max-width: 100%; }
@@ -1348,8 +1575,7 @@ const name = "dsh-safe-auto-client";
1348
1575
  const inject = [
1349
1576
  "slots",
1350
1577
  "connection",
1351
- "remote",
1352
- "remote.session",
1578
+ ...MODEL_CATALOG_INJECT,
1353
1579
  "locale"
1354
1580
  ];
1355
1581
  const CHANNEL = "/dsh-safe-auto";
@@ -1357,11 +1583,7 @@ const BUNDLE = "@klarkxy/dsh-safe-auto";
1357
1583
  const TEXT = {
1358
1584
  zh: {
1359
1585
  requestFailed: "请求失败",
1360
- confirmationUnavailable: "确认已不可用",
1361
- approvalInvalid: "确认请求无效",
1362
- approvalExpiredError: "确认已超时或不再等待处理",
1363
1586
  invalidSession: "会话无效",
1364
- sessionInactive: "会话未激活,请打开后重试",
1365
1587
  childSession: "子会话不能启用安全自动",
1366
1588
  invalidRequest: "请求无效",
1367
1589
  settingsChanged: "配置已更新,请刷新后重试",
@@ -1371,7 +1593,6 @@ const TEXT = {
1371
1593
  workspaceUnavailable: "当前无法使用需原生审批的工作区写入权限",
1372
1594
  unknownPreset: "权限预设无效",
1373
1595
  controlUnloaded: "安全自动已停用",
1374
- browserRequired: "请在浏览器中确认这次调用",
1375
1596
  unknownSetting: "审核模型设置无效",
1376
1597
  invalidModel: "审核模型或提供方标识无效",
1377
1598
  modelPair: "提供方和模型必须一起配置",
@@ -1408,37 +1629,22 @@ const TEXT = {
1408
1629
  tabReviewer: "审核配置",
1409
1630
  details: "安全说明",
1410
1631
  tabLimits: "预算边界",
1411
- safetyDetail: "只处理原生 write/edit/bash/pwsh 的单次沙箱提权。审核会保留真人任务授权及后续限制,并按需读取工作区内的包配置和脚本;模型通过时自动放行。其余有效请求请你在 60 秒内确认,超时拒绝。确认只适用于这一次调用。",
1412
- unableLabel: "无法自动判断时:",
1413
- unableValue: "请你在 60 秒内确认;超时拒绝",
1414
- approvalTitle: "确认这一次调用",
1415
- approvalPending: (count) => `${count} 个请求待确认`,
1416
- approvalCountdown: (seconds) => `剩余 ${seconds} 秒 · 超时拒绝`,
1417
- approvalExpired: "确认已超时,已拒绝。",
1418
- approvalAllow: "仅允许这一次",
1419
- approvalDeny: "拒绝",
1420
- approvalReview: "模型审核意见",
1421
- approvalRefresh: (message) => `${message}。正在刷新请求;不会自动重试决定。`,
1422
- approvalEffect: "完整调用内容",
1632
+ safetyDetail: "普通调用照常在工作区沙箱内运行,插件不拦截。需要审批的请求(沙箱提权、少量危险命令,以及其他工具的审批)交给审核模型:通过就只放行这一次,拒绝或审核失败就把原因告诉智能体。",
1633
+ reviewInput: "发给审核模型的内容:对话记录(你的消息标为可信,其余只作参考,超长时截断)、工作目录和这次操作的完整参数。",
1634
+ breakerDetail: "同一轮对话里连续拒绝 3 次,或最近 50 次审核拒绝 10 次,本轮暂停自动审批,等你发新消息。",
1423
1635
  prompt: "额外审核提示词",
1424
1636
  promptHint: (count) => `${count}/4096 · 只可补充审核约束,不可扩大授权或覆盖安全规则。`,
1425
- limitsLine: (l) => `单次审查超时 ${l.timeoutMs} ms · 输入上限 ${l.maxInputBytes} bytes · 输出上限 ${l.outputTokens} tokens`,
1426
- limitsFuses: (l) => `每个任务最多审查 ${l.maxReviewsPerTask} 次 · 连续拒绝 ${l.consecutiveDenials} 次后熔断`,
1427
- limitsHelp: "预算在 profile 中配置,保存设置或重新启用不会补充;动作、真人指令和有界的本地脚本证据会发送给审核模型。",
1637
+ limitsLine: (l) => `单次审核超时 ${Math.round(l.timeoutMs / 1e3)} 秒 · 输出上限 ${l.outputTokens} tokens · 对话记录上限 ${l.maxTranscriptBytes} bytes`,
1638
+ limitsHelp: "这些上限在 profile 中配置。",
1428
1639
  save: "保存",
1429
1640
  refreshDiscard: "刷新(丢弃草稿)",
1430
1641
  saveFailed: (message) => `${message}。草稿已保留,刷新后重新编辑,不会自动重试。`,
1431
- catalogFailed: (message) => `模型目录读取失败,已保存值保持不变:${message}`,
1432
1642
  saved: "已保存",
1433
- summary: "安全自动:沙箱提权时由独立审核模型把关,通过才自动放行。"
1643
+ summary: "安全自动:需要审批的请求交给审核模型判断,通过才放行这一次。"
1434
1644
  },
1435
1645
  en: {
1436
1646
  requestFailed: "Request failed",
1437
- confirmationUnavailable: "Confirmation is no longer available",
1438
- approvalInvalid: "Invalid approval request",
1439
- approvalExpiredError: "Approval expired or is no longer pending",
1440
1647
  invalidSession: "Invalid session",
1441
- sessionInactive: "Session is not active; open it and retry",
1442
1648
  childSession: "Child sessions cannot select Safe Auto",
1443
1649
  invalidRequest: "Invalid request",
1444
1650
  settingsChanged: "Settings changed; reload and retry",
@@ -1448,7 +1654,6 @@ const TEXT = {
1448
1654
  workspaceUnavailable: "Workspace Write with native approval is unavailable",
1449
1655
  unknownPreset: "Unknown permission preset",
1450
1656
  controlUnloaded: "Safe Auto control unloaded",
1451
- browserRequired: "Browser confirmation required",
1452
1657
  unknownSetting: "Invalid reviewer model setting",
1453
1658
  invalidModel: "Invalid reviewer provider/model identifier",
1454
1659
  modelPair: "Provider and model must be configured together",
@@ -1485,29 +1690,18 @@ const TEXT = {
1485
1690
  tabReviewer: "Reviewer",
1486
1691
  details: "Safety notes",
1487
1692
  tabLimits: "Limits",
1488
- safetyDetail: "Reviews one native write/edit/bash/pwsh sandbox escalation at a time. Earlier human task authorization and later restrictions remain visible; workspace package definitions and scripts may supply evidence. A model pass auto-approves. Other valid requests ask you to confirm within 60 seconds and are denied on timeout. Confirmation grants this call only.",
1489
- unableLabel: "When it cannot judge automatically: ",
1490
- unableValue: "Ask you to confirm within 60 seconds; deny on timeout",
1491
- approvalTitle: "Confirm this call",
1492
- approvalPending: (count) => `${count} pending confirmation${count === 1 ? "" : "s"}`,
1493
- approvalCountdown: (seconds) => `${seconds} seconds left · Deny on timeout`,
1494
- approvalExpired: "Confirmation expired and was denied.",
1495
- approvalAllow: "Allow this call only",
1496
- approvalDeny: "Reject",
1497
- approvalReview: "Model review opinion",
1498
- approvalRefresh: (message) => `${message}. Refreshing requests; your decision will not be retried automatically.`,
1499
- approvalEffect: "Complete call content",
1693
+ safetyDetail: "Ordinary calls run inside the workspace sandbox as usual; the plugin does not intercept them. Requests that need approval (sandbox escalations, a short list of dangerous commands, and approval prompts from other tools) go to the reviewer model: a pass approves that one call; a denial or review failure is explained to the agent.",
1694
+ reviewInput: "Sent to the reviewer: the conversation (your messages marked trusted, everything else as evidence, truncated when long), the working directory, and the complete arguments of the action.",
1695
+ breakerDetail: "After 3 consecutive denials, or 10 denials among the last 50 reviews in one turn, automatic approval pauses until your next message.",
1500
1696
  prompt: "Additional reviewer prompt",
1501
1697
  promptHint: (count) => `${count}/4096 · May add review constraints, never expand authorization or override safety rules.`,
1502
- limitsLine: (l) => `Review timeout ${l.timeoutMs} ms · Input cap ${l.maxInputBytes} bytes · Output cap ${l.outputTokens} tokens`,
1503
- limitsFuses: (l) => `At most ${l.maxReviewsPerTask} reviews per task · Breaker opens after ${l.consecutiveDenials} consecutive denials`,
1504
- limitsHelp: "Budgets live in the profile and are not refilled by saving or re-enabling. Actions, human instructions and bounded local script evidence are sent to the reviewer model.",
1698
+ limitsLine: (l) => `Review timeout ${Math.round(l.timeoutMs / 1e3)} s · Output cap ${l.outputTokens} tokens · Transcript cap ${l.maxTranscriptBytes} bytes`,
1699
+ limitsHelp: "These limits are configured in the profile.",
1505
1700
  save: "Save",
1506
1701
  refreshDiscard: "Refresh (discard draft)",
1507
1702
  saveFailed: (message) => `${message}. Draft retained; refresh before editing again. No automatic retry.`,
1508
- catalogFailed: (message) => `Model catalog failed; saved values retained: ${message}`,
1509
1703
  saved: "Saved",
1510
- summary: "Safe Auto: an independent reviewer vets each sandbox escalation and only a pass auto-approves."
1704
+ summary: "Safe Auto: a reviewer model answers approval requests and approves only the calls that pass."
1511
1705
  }
1512
1706
  };
1513
1707
  /** The host locale may arrive as the injected service or as a plain tag. */
@@ -1529,13 +1723,7 @@ function useText(locale) {
1529
1723
  const LOCAL_ERRORS = {
1530
1724
  "Request failed": "requestFailed",
1531
1725
  "Request rejected": "requestFailed",
1532
- "Confirmation is no longer available": "confirmationUnavailable",
1533
- "Invalid approval response": "approvalInvalid",
1534
- "Invalid approval payload": "approvalInvalid",
1535
- "Approval expired or is no longer pending": "approvalExpiredError",
1536
- "Approval expired or invalidated": "approvalExpiredError",
1537
1726
  "Invalid session": "invalidSession",
1538
- "Session is not active; open it and retry": "sessionInactive",
1539
1727
  "Child sessions cannot select Safe Auto": "childSession",
1540
1728
  "Invalid request": "invalidRequest",
1541
1729
  "Settings changed; reload and retry": "settingsChanged",
@@ -1546,7 +1734,6 @@ const LOCAL_ERRORS = {
1546
1734
  "Workspace Write with native approval is unavailable": "workspaceUnavailable",
1547
1735
  "Unknown permission preset": "unknownPreset",
1548
1736
  "Safe Auto control unloaded": "controlUnloaded",
1549
- "Browser confirmation required": "browserRequired",
1550
1737
  "Unknown Safe Auto model setting": "unknownSetting",
1551
1738
  "invalid reviewer provider/model identifier": "invalidModel",
1552
1739
  "provider and model must be configured together": "modelPair",
@@ -1555,11 +1742,16 @@ const LOCAL_ERRORS = {
1555
1742
  function errorMessage(error) {
1556
1743
  return (typeof error === "string" ? error : error?.message) || TEXT.en.requestFailed;
1557
1744
  }
1745
+ const INACTIVE_SESSION = "Session is not active; open it and retry";
1746
+ function shownPermissionError(error) {
1747
+ const message = errorMessage(error);
1748
+ return message === INACTIVE_SESSION ? "" : message;
1749
+ }
1558
1750
  function errorText(t, error) {
1559
1751
  const message = typeof error === "string" ? error : error?.message;
1560
1752
  const key = Object.hasOwn(LOCAL_ERRORS, message) ? LOCAL_ERRORS[message] : void 0;
1561
1753
  const unknownField = /^unknown config field: (.+)$/.exec(message || "");
1562
- const invalidField = /^(?:(enabled|staticReadonly) must be a boolean|(provider|model|reasoningEffort|reviewerPrompt) must be a string|invalid (timeoutMs|maxInputBytes|outputTokens|maxReviewsPerTask|consecutiveDenials|humanApprovalTimeoutMs))$/.exec(message || "");
1754
+ const invalidField = /^(?:(enabled) must be a boolean|(provider|model|reasoningEffort|reviewerPrompt) must be a string|invalid (timeoutMs|outputTokens|maxTranscriptBytes))$/.exec(message || "");
1563
1755
  const text = key ? t(key) : unknownField ? t("unknownConfigField", unknownField[1]) : invalidField ? t("invalidConfigField", invalidField[1] || invalidField[2] || invalidField[3]) : ["config must be an object", "invalid configuration"].includes(message) ? t("invalidConfig") : message || t("requestFailed");
1564
1756
  return error?.context ? t(error.context, text) : text;
1565
1757
  }
@@ -1603,120 +1795,6 @@ function createRequestScope() {
1603
1795
  }
1604
1796
  };
1605
1797
  }
1606
- const monotonicNow = () => globalThis.performance?.now?.() ?? Date.now();
1607
- function approvalRemaining(request, now = monotonicNow()) {
1608
- return Math.max(0, request.deadline - now);
1609
- }
1610
- /** Per-session serial reads and one-shot writes. A response's server remainder
1611
- * is anchored before the round trip so network delay cannot extend the prompt. */
1612
- function createApprovalController({ api, sessionId, onState, now = monotonicNow, schedule = setTimeout, cancel = clearTimeout, intervalMs = 1e3, readGate = createPluginReadGate() }) {
1613
- let active = true, reading = false, writing = false, generation = 0, readSequence = 0, timer;
1614
- let state = {
1615
- requests: [],
1616
- busy: false,
1617
- error: ""
1618
- };
1619
- const publish = (next) => {
1620
- state = {
1621
- ...state,
1622
- ...next
1623
- };
1624
- if (active) onState(state);
1625
- };
1626
- const arm = () => {
1627
- if (active) {
1628
- cancel(timer);
1629
- timer = schedule(() => {
1630
- refresh();
1631
- }, intervalMs);
1632
- }
1633
- };
1634
- async function refresh(manual = false) {
1635
- if (!active || reading || writing) return false;
1636
- if (manual) {
1637
- generation++;
1638
- readGate.reset();
1639
- }
1640
- if (!readGate.canRead()) {
1641
- arm();
1642
- return false;
1643
- }
1644
- cancel(timer);
1645
- reading = true;
1646
- const ticket = generation, readId = ++readSequence, started = now();
1647
- try {
1648
- const value = await readGate.run(() => api.getApprovals(sessionId));
1649
- if (active && ticket === generation) {
1650
- const previous = new Map(state.requests.map((request) => [request.id, request.deadline]));
1651
- const requests = (value?.requests || []).map((request) => ({
1652
- ...request,
1653
- deadline: Math.min(previous.get(request.id) ?? Infinity, started + Math.max(0, Number(request.remainingMs) || 0))
1654
- })).filter((request) => approvalRemaining(request, now()) > 0);
1655
- publish({
1656
- requests,
1657
- error: ""
1658
- });
1659
- }
1660
- } catch (error) {
1661
- if (active && ticket === generation) publish({ error: errorMessage(error) });
1662
- } finally {
1663
- if (readId === readSequence) {
1664
- reading = false;
1665
- arm();
1666
- }
1667
- }
1668
- return true;
1669
- }
1670
- return {
1671
- refresh,
1672
- reconnect() {
1673
- generation++;
1674
- readSequence++;
1675
- readGate.reset();
1676
- reading = false;
1677
- if (!writing) refresh();
1678
- },
1679
- dispose() {
1680
- active = false;
1681
- generation++;
1682
- readGate.reset();
1683
- cancel(timer);
1684
- },
1685
- async answer(requestId, outcome) {
1686
- const request = state.requests.find((item) => item.id === requestId);
1687
- if (!active || writing || !request || approvalRemaining(request, now()) <= 0) return false;
1688
- writing = true;
1689
- generation++;
1690
- cancel(timer);
1691
- publish({
1692
- busy: true,
1693
- error: ""
1694
- });
1695
- readGate.reset();
1696
- try {
1697
- if ((await api.answerApproval({
1698
- sessionId,
1699
- requestId,
1700
- outcome
1701
- }))?.accepted !== true) throw new Error("Confirmation is no longer available");
1702
- if (active) publish({ requests: state.requests.filter((item) => item.id !== requestId) });
1703
- } catch (error) {
1704
- if (active) publish({
1705
- requests: state.requests.filter((item) => item.id !== requestId),
1706
- error: errorMessage(error)
1707
- });
1708
- } finally {
1709
- writing = false;
1710
- if (active) {
1711
- publish({ busy: false });
1712
- if (!reading) refresh();
1713
- else arm();
1714
- }
1715
- }
1716
- return true;
1717
- }
1718
- };
1719
- }
1720
1798
  function reviewerMenuState(values, catalog) {
1721
1799
  const provider = values.provider || "", model = values.model || "";
1722
1800
  const follow = !provider && !model;
@@ -1765,52 +1843,6 @@ const css = `${officialUiCss("dsh-safe-auto")}${modelMenuCss}
1765
1843
  /* A fieldset is the settings group and takes the contract's card surface; its
1766
1844
  * legend keeps the UA inline padding from indenting the group title. */
1767
1845
  .dsh-safe-auto legend { padding: 0; }
1768
- /* The primitives ship no textarea, so it keeps the platform element at the
1769
- * official field geometry, exactly as dsh-ui-select does for a native select. */
1770
- .dsh-safe-auto-input {
1771
- box-sizing: border-box;
1772
- width: 100%;
1773
- min-width: 0;
1774
- padding: 8px 12px;
1775
- border: 0.5px solid var(--dsw-alias-border-l4);
1776
- border-radius: var(--dsw-radius-md);
1777
- background: var(--dsw-alias-bg-layer-3);
1778
- font: inherit;
1779
- font-size: 13px;
1780
- line-height: 20px;
1781
- color: var(--dsw-alias-label-primary);
1782
- resize: vertical;
1783
- }
1784
- .dsh-safe-auto-input:disabled { opacity: 0.4; cursor: not-allowed; }
1785
- .dsh-safe-auto-approval-dialog {
1786
- display: flex;
1787
- flex-direction: column;
1788
- max-height: 100%;
1789
- max-width: 100%;
1790
- }
1791
- /* The host card sizes every modal to a 380px column; an approval shows whole
1792
- * commands and file contents, so it claims a wider one. The doubled class
1793
- * keeps this rule ahead of the card's own width regardless of style order. */
1794
- .dsh-safe-auto.dsh-safe-auto-approval-dialog { width: min(640px, 100%); }
1795
- .dsh-safe-auto-approval-content {
1796
- flex: 1 1 auto;
1797
- min-height: 0;
1798
- min-width: 0;
1799
- overflow: auto;
1800
- }
1801
- .dsh-safe-auto-approval-pre {
1802
- max-height: 35vh;
1803
- overflow: auto;
1804
- white-space: pre-wrap;
1805
- overflow-wrap: anywhere;
1806
- margin: 0;
1807
- padding: 8px 12px;
1808
- border: 0.5px solid var(--dsw-alias-border-l4);
1809
- border-radius: var(--dsw-radius-md);
1810
- background: var(--dsw-alias-bg-layer-3);
1811
- color: var(--dsw-alias-label-primary);
1812
- font-size: 13px;
1813
- }
1814
1846
  `;
1815
1847
  function Style() {
1816
1848
  return h("style", null, css);
@@ -1821,47 +1853,6 @@ function ErrorText({ error, t }) {
1821
1853
  className: "dsh-ui-error dsh-ui-wrap"
1822
1854
  }, errorText(t, error)) : null;
1823
1855
  }
1824
- /** Values are rendered as plain text, with no Markdown or HTML interpretation. */
1825
- function ApprovalDialog({ request, pendingCount = 1, remainingMs, busy = false, error = "", onAnswer, locale }) {
1826
- const t = useText(locale);
1827
- if (!request) return null;
1828
- const expired = remainingMs <= 0;
1829
- const action = request.action || {};
1830
- const fields = Object.entries({
1831
- tool: action.tool,
1832
- cwd: action.cwd,
1833
- ...action.arguments
1834
- });
1835
- return h(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1836
- open: true,
1837
- title: t("approvalTitle"),
1838
- closeLabel: t("approvalDeny"),
1839
- onClose: () => {
1840
- if (!busy && !expired) onAnswer(request.id, "deny");
1841
- },
1842
- className: "dsh-safe-auto dsh-safe-auto-approval-dialog",
1843
- contentClassName: "dsh-safe-auto-approval-content",
1844
- footer: h("div", { className: "dsh-safe-auto dsh-ui-actions" }, h(Style), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
1845
- variant: "ghost",
1846
- disabled: busy || expired,
1847
- "data-modal-autofocus": true,
1848
- onClick: () => onAnswer(request.id, "deny")
1849
- }, t("approvalDeny")), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
1850
- variant: "primary",
1851
- disabled: busy || expired,
1852
- onClick: () => onAnswer(request.id, "allow")
1853
- }, t("approvalAllow")))
1854
- }, h("div", { className: "dsh-safe-auto dsh-ui-stack" }, h(Style), h("p", {
1855
- className: "dsh-ui-meta",
1856
- role: "status"
1857
- }, t("approvalPending", pendingCount), " · ", expired ? t("approvalExpired") : t("approvalCountdown", Math.ceil(remainingMs / 1e3))), h("div", { className: "dsh-ui-field" }, h("span", { className: "dsh-ui-label" }, t("approvalEffect")), fields.map(([field, value]) => h("div", {
1858
- key: field,
1859
- className: "dsh-ui-field"
1860
- }, h("span", { className: "dsh-ui-meta" }, field), h("pre", { className: "dsh-safe-auto-approval-pre" }, typeof value === "string" ? value : JSON.stringify(value, null, 2))))), h("div", { className: "dsh-ui-field" }, h("span", { className: "dsh-ui-label" }, t("approvalReview")), h("pre", { className: "dsh-safe-auto-approval-pre" }, JSON.stringify(request.review || {}, null, 2))), h(ErrorText, {
1861
- error,
1862
- t
1863
- })));
1864
- }
1865
1856
  /** Composer permission control via the replacement slot: native presets plus Safe Auto. */
1866
1857
  function PermissionMenu({ sessionId, locked, api, locale, initialSnapshot = null }) {
1867
1858
  const t = useText(locale);
@@ -1871,20 +1862,15 @@ function PermissionMenu({ sessionId, locked, api, locale, initialSnapshot = null
1871
1862
  const [busy, setBusy] = (0, react.useState)(false);
1872
1863
  const scopeRef = (0, react.useRef)(null);
1873
1864
  const refreshRef = (0, react.useRef)(null);
1874
- const approvalsRef = (0, react.useRef)(null);
1875
- const [approvals, setApprovals] = (0, react.useState)({
1876
- sessionId,
1877
- requests: [],
1878
- busy: false,
1879
- error: ""
1880
- });
1881
- const [approvalNow, setApprovalNow] = (0, react.useState)(monotonicNow);
1882
1865
  const refresh = (automatic = false) => scopeRef.current?.run(() => api.getSession(sessionId), {
1883
1866
  value: (value) => {
1884
1867
  setSnapshot(value);
1885
1868
  setError("");
1886
1869
  },
1887
- error: (e) => setError(errorMessage(e))
1870
+ error: (e) => {
1871
+ const message = shownPermissionError(e);
1872
+ if (message) setError(message);
1873
+ }
1888
1874
  }, false, automatic);
1889
1875
  refreshRef.current = refresh;
1890
1876
  (0, react.useEffect)(() => {
@@ -1905,50 +1891,6 @@ function PermissionMenu({ sessionId, locked, api, locale, initialSnapshot = null
1905
1891
  globalThis.removeEventListener?.("focus", focus);
1906
1892
  };
1907
1893
  }, [api, sessionId]);
1908
- const approvalsEnabled = Boolean(snapshot?.safeAuto);
1909
- (0, react.useEffect)(() => {
1910
- setApprovals({
1911
- sessionId,
1912
- requests: [],
1913
- busy: false,
1914
- error: ""
1915
- });
1916
- if (!approvalsEnabled) return void 0;
1917
- const controller = createApprovalController({
1918
- api,
1919
- sessionId,
1920
- onState: (value) => {
1921
- setApprovalNow(monotonicNow());
1922
- setApprovals({
1923
- ...value,
1924
- sessionId
1925
- });
1926
- }
1927
- });
1928
- approvalsRef.current = controller;
1929
- controller.refresh();
1930
- const stopConnection = api.subscribeGeneration?.(() => controller.reconnect());
1931
- const focus = () => {
1932
- controller.refresh();
1933
- };
1934
- globalThis.addEventListener?.("focus", focus);
1935
- return () => {
1936
- controller.dispose();
1937
- stopConnection?.();
1938
- approvalsRef.current = null;
1939
- globalThis.removeEventListener?.("focus", focus);
1940
- };
1941
- }, [
1942
- api,
1943
- sessionId,
1944
- approvalsEnabled
1945
- ]);
1946
- const pending = approvalsEnabled && approvals.sessionId === sessionId ? approvals.requests.filter((request) => approvalRemaining(request, approvalNow) > 0) : [];
1947
- (0, react.useEffect)(() => {
1948
- if (!pending.length) return void 0;
1949
- const timer = setInterval(() => setApprovalNow(monotonicNow()), 250);
1950
- return () => clearInterval(timer);
1951
- }, [pending.length]);
1952
1894
  const choose = (value) => {
1953
1895
  const scope = scopeRef.current;
1954
1896
  if (!snapshot || scope?.writing) return;
@@ -1969,8 +1911,9 @@ function PermissionMenu({ sessionId, locked, api, locale, initialSnapshot = null
1969
1911
  setOpen(false);
1970
1912
  },
1971
1913
  error: (e) => {
1972
- setError({
1973
- message: errorMessage(e),
1914
+ const message = shownPermissionError(e);
1915
+ if (message) setError({
1916
+ message,
1974
1917
  context: "refreshRetry"
1975
1918
  });
1976
1919
  refreshRef.current();
@@ -2021,23 +1964,7 @@ function PermissionMenu({ sessionId, locked, api, locale, initialSnapshot = null
2021
1964
  }), h(ErrorText, {
2022
1965
  error,
2023
1966
  t
2024
- }), h(ErrorText, {
2025
- error: approvals.error && {
2026
- message: approvals.error,
2027
- context: "approvalRefresh"
2028
- },
2029
- t
2030
- }), pending.length ? h(ApprovalDialog, {
2031
- key: pending[0].id,
2032
- request: pending[0],
2033
- pendingCount: pending.length,
2034
- remainingMs: approvalRemaining(pending[0], approvalNow),
2035
- busy: approvals.busy,
2036
- onAnswer: (id, outcome) => {
2037
- approvalsRef.current?.answer(id, outcome);
2038
- },
2039
- locale
2040
- }) : null);
1967
+ }));
2041
1968
  }
2042
1969
  function ReviewerFields({ values, catalog, disabled, onChange, locale }) {
2043
1970
  const t = useText(locale);
@@ -2098,9 +2025,12 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
2098
2025
  const t = useText(locale);
2099
2026
  const [settings, setSettings] = (0, react.useState)(initialSettings);
2100
2027
  const [values, setValues] = (0, react.useState)(initialSettings?.values || null);
2101
- const [catalog, setCatalog] = (0, react.useState)(initialCatalog);
2028
+ const catalogState = useModelCatalog((0, react.useCallback)(() => api.modelCatalog(), [api]), {
2029
+ initialCatalog: initialCatalog ?? void 0,
2030
+ subscribe: api.subscribeGeneration
2031
+ });
2032
+ const { catalog, refresh: refreshCatalog } = catalogState;
2102
2033
  const [error, setError] = (0, react.useState)("");
2103
- const [catalogError, setCatalogError] = (0, react.useState)("");
2104
2034
  const [busy, setBusy] = (0, react.useState)(false);
2105
2035
  const [saved, setSaved] = (0, react.useState)(false);
2106
2036
  const [tab, setTab] = (0, react.useState)(initialTab);
@@ -2169,11 +2099,6 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
2169
2099
  setSyncError("");
2170
2100
  refreshRef.current(false, true);
2171
2101
  let alive = true;
2172
- Promise.resolve().then(() => api.modelCatalog()).then((value) => {
2173
- if (alive) setCatalog(value);
2174
- }, (e) => {
2175
- if (alive) setCatalogError(errorMessage(e));
2176
- });
2177
2102
  const focus = () => {
2178
2103
  if (!dirty.current) refreshRef.current(false, true);
2179
2104
  };
@@ -2186,6 +2111,7 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
2186
2111
  if (!alive) return;
2187
2112
  setSyncError("");
2188
2113
  external.current = true;
2114
+ refreshCatalog();
2189
2115
  if (dirty.current && !scope.writing) setExternalChange(true);
2190
2116
  refreshRef.current(false, true);
2191
2117
  }, (message) => {
@@ -2199,7 +2125,7 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
2199
2125
  stopSettings?.();
2200
2126
  globalThis.removeEventListener?.("focus", focus);
2201
2127
  };
2202
- }, [api]);
2128
+ }, [api, refreshCatalog]);
2203
2129
  const change = (next) => {
2204
2130
  dirty.current = true;
2205
2131
  setSaved(false);
@@ -2275,7 +2201,7 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
2275
2201
  key: "prompt",
2276
2202
  className: "dsh-ui-field"
2277
2203
  }, h("span", { className: "dsh-ui-label" }, t("prompt")), h("textarea", {
2278
- className: "dsh-safe-auto-input",
2204
+ className: "dsh-ui-textarea",
2279
2205
  rows: 6,
2280
2206
  maxLength: 4096,
2281
2207
  disabled: busy,
@@ -2291,20 +2217,13 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
2291
2217
  id: "sa-prompt-hint",
2292
2218
  className: "dsh-ui-help"
2293
2219
  }, t("promptHint", (values.reviewerPrompt || "").length))
2294
- ]), tabPanel("details", "dsh-ui-stack", h("div", { className: "dsh-ui-help" }, h("p", null, t("safetyDetail")), h("p", null, h("span", { className: "dsh-ui-label" }, t("unableLabel")), t("unableValue")))), settings?.policyLimits && tabPanel("limits", "dsh-ui-stack", [
2295
- h("p", {
2296
- key: "line",
2297
- className: "dsh-ui-meta dsh-ui-wrap"
2298
- }, t("limitsLine", settings.policyLimits)),
2299
- h("p", {
2300
- key: "fuses",
2301
- className: "dsh-ui-meta dsh-ui-wrap"
2302
- }, t("limitsFuses", settings.policyLimits)),
2303
- h("p", {
2304
- key: "help",
2305
- className: "dsh-ui-help"
2306
- }, t("limitsHelp"))
2307
- ]), h("div", { className: "dsh-ui-actions" }, h(_deepseek_ai_dsh_client_ui_primitives.Button, {
2220
+ ]), tabPanel("details", "dsh-ui-stack", h("div", { className: "dsh-ui-help" }, h("p", null, t("safetyDetail")), h("p", null, t("reviewInput")), h("p", null, t("breakerDetail")))), settings?.policyLimits && tabPanel("limits", "dsh-ui-stack", [h("p", {
2221
+ key: "line",
2222
+ className: "dsh-ui-meta dsh-ui-wrap"
2223
+ }, t("limitsLine", settings.policyLimits)), h("p", {
2224
+ key: "help",
2225
+ className: "dsh-ui-help"
2226
+ }, t("limitsHelp"))]), h("div", { className: "dsh-ui-actions" }, h(_deepseek_ai_dsh_client_ui_primitives.Button, {
2308
2227
  variant: "primary",
2309
2228
  disabled: busy || externalChange || !dirty.current || (values.reviewerPrompt || "").length > 4096,
2310
2229
  onClick: save
@@ -2315,12 +2234,9 @@ function SettingsPanel({ view, api, locale, initialSettings = null, initialCatal
2315
2234
  }, t("refreshDiscard")))), h(ErrorText, {
2316
2235
  error,
2317
2236
  t
2318
- }), h(ErrorText, {
2319
- error: catalogError && {
2320
- message: catalogError,
2321
- context: "catalogFailed"
2322
- },
2323
- t
2237
+ }), h(ModelCatalogStatus, {
2238
+ state: catalogState,
2239
+ locale: localeLanguage(locale)
2324
2240
  }), externalChange && h("p", {
2325
2241
  role: "alert",
2326
2242
  className: "dsh-ui-error"
@@ -2340,11 +2256,9 @@ function apply(ctx) {
2340
2256
  subscribeGeneration: (listener) => ctx.connection.generation?.subscribe(listener) ?? (() => {}),
2341
2257
  getSettings: () => call("settings.get", {}),
2342
2258
  saveSettings: (payload) => call("settings.save", payload),
2343
- modelCatalog: () => ctx.remote.session.modelCatalog(),
2259
+ modelCatalog: () => readModelCatalog(ctx),
2344
2260
  getSession: (sessionId) => call("session.get", { sessionId }),
2345
- selectSession: (payload) => call("session.select", payload),
2346
- getApprovals: (sessionId) => call("approval.list", { sessionId }),
2347
- answerApproval: (payload) => call("approval.answer", payload)
2261
+ selectSession: (payload) => call("session.select", payload)
2348
2262
  });
2349
2263
  ctx.slots.inject("conversation.input.permission", () => ctx.slots.register({
2350
2264
  name: "conversation.input.permission",
@@ -2364,15 +2278,12 @@ function apply(ctx) {
2364
2278
  }, SettingsPanel));
2365
2279
  }
2366
2280
  //#endregion
2367
- exports.ApprovalDialog = ApprovalDialog;
2368
2281
  exports.BUNDLE = BUNDLE;
2369
2282
  exports.CHANNEL = CHANNEL;
2370
2283
  exports.PermissionMenu = PermissionMenu;
2371
2284
  exports.ReviewerFields = ReviewerFields;
2372
2285
  exports.SettingsPanel = SettingsPanel;
2373
2286
  exports.apply = apply;
2374
- exports.approvalRemaining = approvalRemaining;
2375
- exports.createApprovalController = createApprovalController;
2376
2287
  exports.createRequestScope = createRequestScope;
2377
2288
  exports.errorText = errorText;
2378
2289
  exports.inject = inject;