dsh-advisor 0.4.0 → 0.5.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.
@@ -1,13 +1,16 @@
1
1
  /**
2
- * Advisor settings plugin, browser half. Registers the `advisor` card into
3
- * the shell-declared `settings.plugin.item` keyed slot (the "插件配置"
4
- * settings page — key `advisor`, the settings namespace the card edits,
5
- * appearing after the upstream bash / agent-loop / web-search cards in
6
- * registration order). The card's store joins the settings namespaces and the
7
- * provider directory through the connection wire, and keeps fresh on pushed
8
- * invalidations. Export discipline: the client half value-imports ONLY the
9
- * frozen platform module table (CLIENT_EXTERNALS: react /
10
- * `@deepseek-ai/cordis` / ui-slots / ui-primitives / the documented
2
+ * Advisor settings plugin, browser half. Registers the Advisor card into the
3
+ * shell-declared `plugins.bundle.config` keyed slot (the Plugins page's
4
+ * per-bundle configuration seat — key `dsh-advisor`, the bundle's package name
5
+ * the page dispatches, rendered on the bundle's own page between its
6
+ * description and its rows) and, since B2, an Advisor action into the
7
+ * shell-declared `conversation.session.header.actions` list slot (the
8
+ * per-session reviewer-model control, bound to the slot parent's SessionId —
9
+ * the global card stays global-only). The card's store joins the settings
10
+ * namespaces and the provider directory through the connection wire, and
11
+ * keeps fresh on pushed invalidations. Export discipline: the client half
12
+ * value-imports ONLY the frozen platform module table (CLIENT_EXTERNALS:
13
+ * react / `@deepseek-ai/cordis` / ui-slots / ui-primitives / the documented
11
14
  * `@deepseek-ai/dsh-client-store` exemption); every other
12
15
  * `@deepseek-ai/*` import is type-only (erased at build) — values arrive via
13
16
  * cordis injection (`ctx.get('connection')`, slot inject faces, the
@@ -16,8 +19,9 @@
16
19
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
17
20
  import { type AdvisorKey } from './locales.ts';
18
21
  export type { AdvisorCardInjected, AdvisorCardProps } from './advisor-card.tsx';
22
+ export type { AdvisorSessionActionInjected, AdvisorSessionActionProps } from './advisor-session.tsx';
19
23
  export type { AdvisorKey } from './locales.ts';
20
- export type { AdvisorDraft, AdvisorSettingsState, AdvisorSettingsStore, ApplyFailure, ApplyState, ModelOption, ModelsEmptyReason, ProviderOption, } from './advisor-store.ts';
24
+ export type { AdvisorDraft, AdvisorSessionMenuState, AdvisorSessionModelController, AdvisorSessionRpcPayload, AdvisorSessionSelection, AdvisorSessionSnapshotView, AdvisorSettingsState, AdvisorSettingsStore, ApplyFailure, ApplyState, ModelOption, ModelsEmptyReason, ProviderOption, } from './advisor-store.ts';
21
25
  declare module '@deepseek-ai/dsh-client-ui-slots' {
22
26
  interface LocaleNamespaceMap {
23
27
  /** The Advisor settings card copy. */
@@ -27,8 +31,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
27
31
  export { refreshIfLoaded } from './advisor-store.ts';
28
32
  /**
29
33
  * Required services (cordis fiber inject). The target slot is declared by
30
- * ui-plugin-config's apply, whose activation order relative to this one is
31
- * NOT constrained; registration depends on the slot through `slots.inject()`.
34
+ * ui-plugin-manager's apply (its `plugins` main-panel entry), whose activation
35
+ * order relative to this one is NOT constrained; registration depends on the
36
+ * slot through `slots.inject()`.
32
37
  *
33
38
  * rc.1 dotted-namespace contract: each client Remote namespace is a
34
39
  * child-fiber service named `remote.<ns>` (upstream `remoteServiceKey`), and
@@ -41,7 +46,7 @@ export { refreshIfLoaded } from './advisor-store.ts';
41
46
  */
42
47
  export declare const inject: string[];
43
48
  /**
44
- * Register the Advisor card once the `settings.plugin.item` declaration is on
49
+ * Register the Advisor card once the `plugins.bundle.config` declaration is on
45
50
  * the ledger, wire its store to the connection, and keep it fresh on every
46
51
  * pushed invalidation (settings or provider topology).
47
52
  * @param ctx - client root context.
@@ -33,6 +33,21 @@ export declare const en: {
33
33
  saved: string;
34
34
  readOnly: string;
35
35
  namespaceUnavailable: string;
36
+ sessionTrigger: string;
37
+ sessionExpand: string;
38
+ sessionCollapse: string;
39
+ sessionLoading: string;
40
+ sessionModelHeading: string;
41
+ sessionSourceSession: string;
42
+ sessionSourceGlobal: string;
43
+ sessionNoPair: string;
44
+ sessionLifetime: string;
45
+ sessionAdvisorOff: string;
46
+ sessionGateBlocked: string;
47
+ sessionPinAction: string;
48
+ sessionPending: string;
49
+ sessionReset: string;
50
+ sessionUnavailable: string;
36
51
  };
37
52
  /** The settings.advisor namespace key union. */
38
53
  export type AdvisorKey = keyof typeof en;
package/lib/client.js CHANGED
@@ -34,7 +34,7 @@ module.exports = __toCommonJS(index_exports);
34
34
  var import_react = require("react");
35
35
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
36
36
 
37
- var css = `.kPHV8a_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.kPHV8a_card:hover{border-color:var(--dsw-alias-label-dimmed)}.kPHV8a_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.kPHV8a_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.kPHV8a_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.kPHV8a_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.kPHV8a_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.kPHV8a_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.kPHV8a_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kPHV8a_chevronOpen{transform:rotate(180deg)}.kPHV8a_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.kPHV8a_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.kPHV8a_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kPHV8a_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.kPHV8a_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.kPHV8a_discard,.kPHV8a_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.kPHV8a_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.kPHV8a_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.kPHV8a_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.kPHV8a_discard:disabled,.kPHV8a_save:disabled{opacity:.4;cursor:default}.kPHV8a_discard:focus-visible,.kPHV8a_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.kPHV8a_notice{color:var(--dsw-alias-state-warn-label);margin:12px 0 0;font-size:12px;line-height:18px}.kPHV8a_savedNotice{color:var(--dsw-alias-state-success-primary);margin:12px 0 0;font-size:12px;line-height:18px}.kPHV8a_error{color:var(--dsw-alias-state-error-primary);margin:12px 0 0;font-size:12px;line-height:18px}.kPHV8a_form{flex-direction:column;gap:12px;padding:12px 0 0;display:flex}.kPHV8a_checkboxRow{align-items:center;gap:8px;display:flex}.kPHV8a_checkLabel{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.kPHV8a_checkbox{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer;margin:0}.kPHV8a_checkbox:disabled{opacity:.4;cursor:default}.kPHV8a_checkbox:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.kPHV8a_fieldset{border:none;flex-direction:column;gap:12px;margin:0;padding:0;display:flex}.kPHV8a_field{flex-direction:column;gap:6px;display:flex}.kPHV8a_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.kPHV8a_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}select.kPHV8a_input{cursor:pointer;max-width:240px}.kPHV8a_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.kPHV8a_input::placeholder{color:var(--dsw-alias-label-dimmed)}.kPHV8a_input:disabled{opacity:.6;cursor:default}.kPHV8a_selectInput{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.kPHV8a_textarea{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;min-height:96px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);resize:vertical;border-radius:8px;padding:8px 10px;font-size:14px;line-height:22px}.kPHV8a_textarea:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.kPHV8a_textarea:disabled{opacity:.6;cursor:default}.kPHV8a_numberFields{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.kPHV8a_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.kPHV8a_warnHint{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}`;
37
+ var css = `.kPHV8a_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}.kPHV8a_card:hover{border-color:var(--dsw-alias-label-dimmed)}.kPHV8a_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.kPHV8a_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.kPHV8a_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.kPHV8a_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.kPHV8a_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.kPHV8a_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.kPHV8a_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kPHV8a_chevronOpen{transform:rotate(180deg)}.kPHV8a_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.kPHV8a_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.kPHV8a_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kPHV8a_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.kPHV8a_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.kPHV8a_discard,.kPHV8a_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.kPHV8a_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.kPHV8a_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.kPHV8a_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.kPHV8a_discard:disabled,.kPHV8a_save:disabled{opacity:.4;cursor:default}.kPHV8a_discard:focus-visible,.kPHV8a_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.kPHV8a_notice{color:var(--dsw-alias-state-warn-label);margin:12px 0 0;font-size:12px;line-height:18px}.kPHV8a_savedNotice{color:var(--dsw-alias-state-success-primary);margin:12px 0 0;font-size:12px;line-height:18px}.kPHV8a_error{color:var(--dsw-alias-state-error-primary);margin:12px 0 0;font-size:12px;line-height:18px}.kPHV8a_form{flex-direction:column;gap:12px;padding:12px 0 0;display:flex}.kPHV8a_checkboxRow{align-items:center;gap:8px;display:flex}.kPHV8a_checkLabel{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.kPHV8a_checkbox{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer;margin:0}.kPHV8a_checkbox:disabled{opacity:.4;cursor:default}.kPHV8a_checkbox:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.kPHV8a_fieldset{border:none;flex-direction:column;gap:12px;margin:0;padding:0;display:flex}.kPHV8a_field{flex-direction:column;gap:6px;display:flex}.kPHV8a_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.kPHV8a_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}select.kPHV8a_input{cursor:pointer;max-width:240px}.kPHV8a_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.kPHV8a_input::placeholder{color:var(--dsw-alias-label-dimmed)}.kPHV8a_input:disabled{opacity:.6;cursor:default}.kPHV8a_selectInput{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.kPHV8a_textarea{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;min-height:96px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);resize:vertical;border-radius:8px;padding:8px 10px;font-size:14px;line-height:22px}.kPHV8a_textarea:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.kPHV8a_textarea:disabled{opacity:.6;cursor:default}.kPHV8a_numberFields{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.kPHV8a_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.kPHV8a_warnHint{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}`;
38
38
  var tagId = "dsh-advisor/advisor-card.module.css";
39
39
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
40
40
  const tag = document.createElement("style");
@@ -80,7 +80,7 @@ function AdvisorCard(props) {
80
80
  ] }),
81
81
  state.dirty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: advisor_card_module_css_default["pending"], children: t("unsaved") }) : null,
82
82
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
83
- import_dsh_client_ui_primitives.IconChevronDownOutline14,
83
+ import_dsh_client_ui_primitives.IconChevronDownOutlineRegular,
84
84
  {
85
85
  className: open ? `${advisor_card_module_css_default["chevron"]} ${advisor_card_module_css_default["chevronOpen"]}` : advisor_card_module_css_default["chevron"]
86
86
  }
@@ -276,12 +276,154 @@ function AdvisorCard(props) {
276
276
  } else {
277
277
  body = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: advisor_card_module_css_default["body"] });
278
278
  }
279
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: open ? `${advisor_card_module_css_default["card"]} ${advisor_card_module_css_default["cardOpen"]}` : advisor_card_module_css_default["card"], children: [
279
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: open ? `${advisor_card_module_css_default["card"]} ${advisor_card_module_css_default["cardOpen"]}` : advisor_card_module_css_default["card"], children: [
280
280
  header,
281
281
  open ? body : null
282
282
  ] });
283
283
  }
284
284
 
285
+ // src/client/advisor-session.tsx
286
+ var import_react2 = require("react");
287
+
288
+ var css2 = ".C5gNya_trigger{appearance:none;border:1px solid var(--dsw-alias-border-l2);font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:999px;padding:3px 12px;font-size:13px;line-height:20px}.C5gNya_trigger:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.C5gNya_trigger[aria-expanded=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-dimmed)}.C5gNya_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.C5gNya_anchor{display:inline-flex;position:relative}.C5gNya_panel{z-index:30;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:10px;width:300px;padding:12px 14px;display:flex;position:absolute;top:calc(100% + 6px);right:0;box-shadow:0 8px 24px #0000002e}.C5gNya_panelHeading{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600;line-height:20px}.C5gNya_routeLine{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0;font-size:13px;line-height:20px}.C5gNya_sourceBadge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;margin-left:6px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.C5gNya_mutedLine{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.C5gNya_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.C5gNya_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.C5gNya_controls{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:8px;padding-top:8px;display:flex}.C5gNya_selectRow{gap:8px;display:flex}.C5gNya_select{appearance:auto;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;flex:1;padding:4px 6px;font-size:12px;line-height:18px}.C5gNya_select:disabled{opacity:.4;cursor:default}.C5gNya_select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.C5gNya_actions{justify-content:flex-end;align-items:center;gap:8px;display:flex}.C5gNya_reset,.C5gNya_pin{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:4px 12px;font-size:12px;line-height:18px}.C5gNya_reset{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.C5gNya_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.C5gNya_pin{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.C5gNya_reset:disabled,.C5gNya_pin:disabled{opacity:.4;cursor:default}.C5gNya_reset:focus-visible,.C5gNya_pin:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
289
+ var tagId2 = "dsh-advisor/advisor-session.module.css";
290
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId2) + "]") === null) {
291
+ const tag = document.createElement("style");
292
+ tag.dataset.plugin = "dsh-advisor";
293
+ tag.dataset.pluginCss = tagId2;
294
+ tag.textContent = css2;
295
+ document.head.appendChild(tag);
296
+ }
297
+ var advisor_session_module_css_default = { "actions": "C5gNya_actions", "anchor": "C5gNya_anchor", "controls": "C5gNya_controls", "error": "C5gNya_error", "mutedLine": "C5gNya_mutedLine", "notice": "C5gNya_notice", "panel": "C5gNya_panel", "panelHeading": "C5gNya_panelHeading", "pin": "C5gNya_pin", "reset": "C5gNya_reset", "routeLine": "C5gNya_routeLine", "select": "C5gNya_select", "selectRow": "C5gNya_selectRow", "sourceBadge": "C5gNya_sourceBadge", "trigger": "C5gNya_trigger" };
298
+
299
+ // src/client/advisor-session.tsx
300
+ var import_jsx_runtime2 = require("react/jsx-runtime");
301
+ function AdvisorSessionAction(props) {
302
+ const { controller, directory, useSnapshot, useRefreshSignal, useDirectory, t } = props;
303
+ const state = useSnapshot((s) => s);
304
+ const epoch = useRefreshSignal((s) => s.epoch);
305
+ const directoryState = useDirectory((s) => s);
306
+ const [open, setOpen] = (0, import_react2.useState)(false);
307
+ const [provider, setProvider] = (0, import_react2.useState)("");
308
+ const [model, setModel] = (0, import_react2.useState)("");
309
+ const openAdvisor = () => {
310
+ setOpen(true);
311
+ controller.takeSignal(epoch);
312
+ void controller.refresh();
313
+ if (directoryState.status === "idle") void directory.load();
314
+ };
315
+ if (open && controller.takeSignal(epoch)) void controller.refresh();
316
+ const snapshot = state.snapshot;
317
+ const busy = state.pending;
318
+ const pinned = snapshot?.modelSource === "session";
319
+ let body;
320
+ if (state.unavailable) {
321
+ body = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.notice, role: "status", children: t("sessionUnavailable") });
322
+ } else if (snapshot === null) {
323
+ body = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.mutedLine, role: "status", children: t("sessionLoading") });
324
+ } else {
325
+ const pair = snapshot.effectiveModel !== void 0 ? `${snapshot.effectiveModel.provider}/${snapshot.effectiveModel.model}` : void 0;
326
+ body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
327
+ snapshot.enabled ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.notice, children: t("sessionAdvisorOff") }),
328
+ snapshot.disabledReason !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.notice, children: t("sessionGateBlocked", { reason: snapshot.disabledReason }) }) : null,
329
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: advisor_session_module_css_default.routeLine, children: [
330
+ pair === void 0 ? t("sessionNoPair") : pair,
331
+ pair !== void 0 && snapshot.modelSource !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: advisor_session_module_css_default.sourceBadge, children: t(snapshot.modelSource === "session" ? "sessionSourceSession" : "sessionSourceGlobal") }) : null
332
+ ] }),
333
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.mutedLine, children: t("sessionLifetime") }),
334
+ state.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.error, role: "status", children: state.error }) : null,
335
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: advisor_session_module_css_default.controls, children: [
336
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: advisor_session_module_css_default.selectRow, children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
338
+ "select",
339
+ {
340
+ "aria-label": t("provider"),
341
+ className: advisor_session_module_css_default.select,
342
+ value: provider,
343
+ disabled: busy,
344
+ onChange: (event) => {
345
+ const next = event.target.value;
346
+ setProvider(next);
347
+ setModel("");
348
+ if (next !== "") void directory.ensureModels(next);
349
+ },
350
+ children: [
351
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: t("providerPlaceholder") }),
352
+ directoryState.providers.map((option) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: option.provider, children: option.displayName }, option.provider))
353
+ ]
354
+ }
355
+ ),
356
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
357
+ "select",
358
+ {
359
+ "aria-label": t("model"),
360
+ className: advisor_session_module_css_default.select,
361
+ value: model,
362
+ disabled: busy || provider === "" || (directoryState.modelsByProvider[provider]?.length ?? 0) === 0,
363
+ onChange: (event) => {
364
+ setModel(event.target.value);
365
+ },
366
+ children: [
367
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: t("modelPlaceholder") }),
368
+ (directoryState.modelsByProvider[provider] ?? []).map((option) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: option.id, children: option.name }, option.id))
369
+ ]
370
+ }
371
+ )
372
+ ] }),
373
+ provider !== "" && (directoryState.modelsByProvider[provider]?.length ?? 0) === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.mutedLine, children: t("noModels") }) : null,
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: advisor_session_module_css_default.actions, children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
376
+ "button",
377
+ {
378
+ type: "button",
379
+ className: advisor_session_module_css_default.reset,
380
+ disabled: busy || !pinned,
381
+ onClick: () => {
382
+ void controller.resetSessionModel();
383
+ },
384
+ children: t("sessionReset")
385
+ }
386
+ ),
387
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
388
+ "button",
389
+ {
390
+ type: "button",
391
+ className: advisor_session_module_css_default.pin,
392
+ disabled: busy || provider === "" || model === "",
393
+ onClick: () => {
394
+ void controller.setSessionModel(provider, model);
395
+ },
396
+ children: busy ? t("sessionPending") : t("sessionPinAction")
397
+ }
398
+ )
399
+ ] })
400
+ ] })
401
+ ] });
402
+ }
403
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: advisor_session_module_css_default.anchor, children: [
404
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
405
+ "button",
406
+ {
407
+ type: "button",
408
+ className: advisor_session_module_css_default.trigger,
409
+ "aria-expanded": open,
410
+ "aria-haspopup": "dialog",
411
+ "aria-label": t(open ? "sessionCollapse" : "sessionExpand"),
412
+ onClick: () => {
413
+ if (open) setOpen(false);
414
+ else openAdvisor();
415
+ },
416
+ children: t("sessionTrigger")
417
+ }
418
+ ),
419
+ open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: advisor_session_module_css_default.panel, role: "dialog", "aria-label": t("sessionModelHeading"), children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.panelHeading, children: t("sessionModelHeading") }),
421
+ directoryState.providers.length === 0 && directoryState.status === "ready" && !state.unavailable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: advisor_session_module_css_default.mutedLine, children: t("noProviders") }) : null,
422
+ body
423
+ ] }) : null
424
+ ] });
425
+ }
426
+
285
427
  // src/client/advisor-store.ts
286
428
  var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
287
429
  function defaultDraft() {
@@ -741,6 +883,162 @@ function refreshIfLoaded(controller) {
741
883
  if (controller.store.getSnapshot().status === "idle") return;
742
884
  void controller.load();
743
885
  }
886
+ var AdvisorSessionModelController = class {
887
+ constructor(sessionId, rpc) {
888
+ __publicField(this, "rpc", rpc);
889
+ __publicField(this, "sessionId");
890
+ /** The snapshot the menu renders from (uSES-safe store). */
891
+ __publicField(this, "store", (0, import_dsh_client_store.createSnapshotStore)({
892
+ phase: "idle",
893
+ snapshot: null,
894
+ error: null,
895
+ unavailable: false,
896
+ pending: false
897
+ }));
898
+ /** Fence token: a newer op makes every older settle stale. */
899
+ __publicField(this, "requestSeq", 0);
900
+ /** Last consumed refresh-signal epoch (reconnect / focus bumps). */
901
+ __publicField(this, "lastSignal");
902
+ this.sessionId = sessionId;
903
+ }
904
+ /**
905
+ * Record the refresh-signal epoch and report whether it is NEW. The open
906
+ * handler always refetches and merely marks the signal current; the render
907
+ * watcher calls this while open and refetches only on a true return (a
908
+ * reconnect/focus bump). Idempotent per epoch, so a re-render never
909
+ * double-fetches.
910
+ */
911
+ takeSignal(epoch) {
912
+ if (this.lastSignal === epoch) return false;
913
+ this.lastSignal = epoch;
914
+ return true;
915
+ }
916
+ /**
917
+ * Fetch the authoritative snapshot (`/api/advisor/getSession`). Called on
918
+ * menu open and on every refresh signal while open. A stale settle (a newer
919
+ * op superseded this fetch) touches nothing. An `advisor/unavailable` /
920
+ * `advisor/session-unknown` tag or a transport failure latches
921
+ * `unavailable` (writes stay off until a later successful refresh); other
922
+ * tagged outcomes leave the surface usable with the error shown.
923
+ */
924
+ async refresh() {
925
+ const seq = ++this.requestSeq;
926
+ this.store.update((s) => {
927
+ s.phase = "loading";
928
+ s.pending = false;
929
+ s.error = null;
930
+ });
931
+ let payload;
932
+ let transport;
933
+ try {
934
+ const result = await this.rpc.call("/api", "advisor/getSession", { args: { sessionId: this.sessionId } });
935
+ if (seq !== this.requestSeq) return;
936
+ if (result.ok) payload = result.value;
937
+ else transport = result.error.message;
938
+ } catch (error) {
939
+ if (seq !== this.requestSeq) return;
940
+ transport = error instanceof Error ? error.message : String(error);
941
+ }
942
+ this.applyOutcome(payload, transport, seq);
943
+ }
944
+ /**
945
+ * Pin the atomic `{provider, model}` pair for THIS session
946
+ * (`/api/advisor/setSessionModel`, selection = the pair). Refuses outright
947
+ * while `unavailable` (store-side defense-in-depth — an unavailable menu
948
+ * must never issue a write, and must never fall back to the global
949
+ * `advisor/set` channel). The host validates the pair through the same
950
+ * rules as the `/advisor model set` command face.
951
+ */
952
+ async setSessionModel(provider, model) {
953
+ await this.write({ provider, model });
954
+ }
955
+ /**
956
+ * Drop the session pin and re-inherit the CURRENT global defaults
957
+ * (`selection: null`). Never touches the enable override; reset to a
958
+ * missing global pair still succeeds — the returned snapshot reports the
959
+ * gate-blocked/no-call state, which the menu renders truthfully.
960
+ */
961
+ async resetSessionModel() {
962
+ await this.write(null);
963
+ }
964
+ /** Shared write path (both arms ride the same endpoint + fence + outcome mapping). */
965
+ async write(selection) {
966
+ if (this.store.getSnapshot().unavailable) {
967
+ this.store.update((s) => {
968
+ s.error = "advisor: the session control surface is unavailable \u2014 writes are not offered";
969
+ });
970
+ return;
971
+ }
972
+ const seq = ++this.requestSeq;
973
+ this.store.update((s) => {
974
+ s.pending = true;
975
+ s.error = null;
976
+ });
977
+ let payload;
978
+ let transport;
979
+ try {
980
+ const result = await this.rpc.call("/api", "advisor/setSessionModel", {
981
+ args: { sessionId: this.sessionId, selection }
982
+ });
983
+ if (seq !== this.requestSeq) return;
984
+ if (result.ok) payload = result.value;
985
+ else transport = result.error.message;
986
+ } catch (error) {
987
+ if (seq !== this.requestSeq) return;
988
+ transport = error instanceof Error ? error.message : String(error);
989
+ }
990
+ this.applyOutcome(payload, transport, seq);
991
+ }
992
+ /**
993
+ * Commit one settled outcome. Guards (seq checked by the callers before
994
+ * reaching here): tagged `advisor/unavailable` / `advisor/session-unknown`
995
+ * and transport failures latch `unavailable` (no writes offered, last good
996
+ * snapshot kept for context); other tagged outcomes keep the surface usable
997
+ * and surface the message (the host left the previous selection untouched);
998
+ * a snapshot commits as the new authoritative state.
999
+ */
1000
+ applyOutcome(payload, transport, seq) {
1001
+ if (seq !== this.requestSeq) return;
1002
+ const tagged = payload?.error;
1003
+ if (transport !== void 0 || tagged?.tag === "advisor/unavailable" || tagged?.tag === "advisor/session-unknown") {
1004
+ this.store.update((s) => {
1005
+ s.phase = "ready";
1006
+ s.unavailable = true;
1007
+ s.pending = false;
1008
+ s.error = transport ?? tagged?.message ?? "advisor: the session control surface is unavailable";
1009
+ });
1010
+ return;
1011
+ }
1012
+ if (tagged !== void 0) {
1013
+ this.store.update((s) => {
1014
+ s.phase = "ready";
1015
+ s.pending = false;
1016
+ s.error = tagged.message;
1017
+ });
1018
+ return;
1019
+ }
1020
+ const snapshot = payload?.snapshot;
1021
+ if (snapshot === void 0) {
1022
+ this.store.update((s) => {
1023
+ s.phase = "ready";
1024
+ s.unavailable = true;
1025
+ s.pending = false;
1026
+ s.error = "advisor: the session endpoint returned no snapshot";
1027
+ });
1028
+ return;
1029
+ }
1030
+ this.store.update((s) => {
1031
+ s.phase = "ready";
1032
+ s.snapshot = snapshot;
1033
+ s.unavailable = false;
1034
+ s.pending = false;
1035
+ s.error = null;
1036
+ });
1037
+ }
1038
+ };
1039
+
1040
+ // src/client/index.ts
1041
+ var import_dsh_client_store2 = require("@deepseek-ai/dsh-client-store");
744
1042
 
745
1043
  // src/client/locales.ts
746
1044
  var en = {
@@ -771,7 +1069,24 @@ var en = {
771
1069
  discard: "Discard",
772
1070
  saved: "Advisor settings saved. New sessions pick them up immediately.",
773
1071
  readOnly: "Settings are read-only in this environment.",
774
- namespaceUnavailable: "The advisor configuration channel is not available yet \u2014 the settings gateway is not ready on this host. Configure the advisor via the plugin config row \u2014 `- id: advisor` whose `config:` map sets `enabled`/`provider`/`model` (e.g. `config: { enabled: true, provider: \u2026, model: \u2026 }`) in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The advisor plugin runs fully from its plugin config row; the web settings form becomes available again on the next load once the gateway is reachable. Note: `/advisor` only toggles the advisor per session; it cannot supply provider/model."
1072
+ namespaceUnavailable: "The advisor configuration channel is not available yet \u2014 the settings gateway is not ready on this host. Configure the advisor via the plugin config row \u2014 `- id: advisor` whose `config:` map sets `enabled`/`provider`/`model` (e.g. `config: { enabled: true, provider: \u2026, model: \u2026 }`) in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The advisor plugin runs fully from its plugin config row; the web settings form becomes available again on the next load once the gateway is reachable. Note: `/advisor` only toggles the advisor per session; it cannot supply provider/model.",
1073
+ // Session header action (B2): the control lives on the session header and
1074
+ // drives the live-session reviewer route only.
1075
+ sessionTrigger: "Advisor",
1076
+ sessionExpand: "Open the advisor controls for this session",
1077
+ sessionCollapse: "Close the advisor controls for this session",
1078
+ sessionLoading: "Loading\u2026",
1079
+ sessionModelHeading: "Reviewer model for this session",
1080
+ sessionSourceSession: "session override",
1081
+ sessionSourceGlobal: "global default",
1082
+ sessionNoPair: "No reviewer model \u2014 the global default has no provider/model.",
1083
+ sessionLifetime: "Lives for this session only \u2014 never persisted; cleared on dispose or restart. A new or forked session inherits the global defaults.",
1084
+ sessionAdvisorOff: "The advisor is off for this session.",
1085
+ sessionGateBlocked: "No model call can start: {reason}",
1086
+ sessionPinAction: "Pin this model",
1087
+ sessionPending: "Applying\u2026",
1088
+ sessionReset: "Use global default",
1089
+ sessionUnavailable: "The advisor session control surface is not available on this host. Use /advisor model in the session, or configure the global defaults on the Plugins page."
775
1090
  };
776
1091
  var zh = {
777
1092
  title: "\u987E\u95EE",
@@ -801,16 +1116,33 @@ var zh = {
801
1116
  discard: "\u653E\u5F03\u4FEE\u6539",
802
1117
  saved: "\u987E\u95EE\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002\u65B0\u4F1A\u8BDD\u7ACB\u5373\u751F\u6548\u3002",
803
1118
  readOnly: "\u5F53\u524D\u73AF\u5883\u4E2D\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002",
804
- namespaceUnavailable: "\u987E\u95EE\u914D\u7F6E\u901A\u9053\u6682\u4E0D\u53EF\u7528\u2014\u2014\u672C\u5BBF\u4E3B\u4E0A\u7684\u8BBE\u7F6E\u7F51\u5173\u5C1A\u672A\u5C31\u7EEA\u3002\u8BF7\u901A\u8FC7\u63D2\u4EF6\u914D\u7F6E\u884C\u914D\u7F6E\u987E\u95EE\u2014\u2014\u5728 `$DSH_HOME/profiles/<name>/cordis.patch.yml` \u4E2D\u5199 `- id: advisor`\uFF0C\u5176 `config:` \u6620\u5C04\u4E2D\u8BBE\u7F6E `enabled`/`provider`/`model`\uFF08\u4F8B\u5982 `config: { enabled: true, provider: \u2026, model: \u2026 }`\uFF09\u3002\u987E\u95EE\u63D2\u4EF6\u5B8C\u5168\u901A\u8FC7\u63D2\u4EF6\u914D\u7F6E\u884C\u8FD0\u884C\uFF1B\u7F51\u5173\u53EF\u7528\u540E\uFF0Cweb \u8BBE\u7F6E\u8868\u5355\u5C06\u5728\u4E0B\u6B21\u52A0\u8F7D\u65F6\u91CD\u65B0\u663E\u793A\u3002\u6CE8\u610F\uFF1A`/advisor` \u4EC5\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u7684\u987E\u95EE\u5F00\u5173\uFF0C\u65E0\u6CD5\u63D0\u4F9B provider/model\u3002"
1119
+ namespaceUnavailable: "\u987E\u95EE\u914D\u7F6E\u901A\u9053\u6682\u4E0D\u53EF\u7528\u2014\u2014\u672C\u5BBF\u4E3B\u4E0A\u7684\u8BBE\u7F6E\u7F51\u5173\u5C1A\u672A\u5C31\u7EEA\u3002\u8BF7\u901A\u8FC7\u63D2\u4EF6\u914D\u7F6E\u884C\u914D\u7F6E\u987E\u95EE\u2014\u2014\u5728 `$DSH_HOME/profiles/<name>/cordis.patch.yml` \u4E2D\u5199 `- id: advisor`\uFF0C\u5176 `config:` \u6620\u5C04\u4E2D\u8BBE\u7F6E `enabled`/`provider`/`model`\uFF08\u4F8B\u5982 `config: { enabled: true, provider: \u2026, model: \u2026 }`\uFF09\u3002\u987E\u95EE\u63D2\u4EF6\u5B8C\u5168\u901A\u8FC7\u63D2\u4EF6\u914D\u7F6E\u884C\u8FD0\u884C\uFF1B\u7F51\u5173\u53EF\u7528\u540E\uFF0Cweb \u8BBE\u7F6E\u8868\u5355\u5C06\u5728\u4E0B\u6B21\u52A0\u8F7D\u65F6\u91CD\u65B0\u663E\u793A\u3002\u6CE8\u610F\uFF1A`/advisor` \u4EC5\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u7684\u987E\u95EE\u5F00\u5173\uFF0C\u65E0\u6CD5\u63D0\u4F9B provider/model\u3002",
1120
+ sessionTrigger: "\u987E\u95EE",
1121
+ sessionExpand: "\u6253\u5F00\u672C\u4F1A\u8BDD\u7684\u987E\u95EE\u63A7\u5236",
1122
+ sessionCollapse: "\u5173\u95ED\u672C\u4F1A\u8BDD\u7684\u987E\u95EE\u63A7\u5236",
1123
+ sessionLoading: "\u52A0\u8F7D\u4E2D\u2026",
1124
+ sessionModelHeading: "\u672C\u4F1A\u8BDD\u7684\u5BA1\u9605\u6A21\u578B",
1125
+ sessionSourceSession: "\u4F1A\u8BDD\u8986\u76D6",
1126
+ sessionSourceGlobal: "\u5168\u5C40\u9ED8\u8BA4",
1127
+ sessionNoPair: "\u6682\u65E0\u5BA1\u9605\u6A21\u578B\u2014\u2014\u5168\u5C40\u9ED8\u8BA4\u672A\u914D\u7F6E provider/model\u3002",
1128
+ sessionLifetime: "\u4EC5\u5728\u672C\u4F1A\u8BDD\u5185\u6709\u6548\u2014\u2014\u4ECE\u4E0D\u6301\u4E45\u5316\uFF1B\u4F1A\u8BDD\u9500\u6BC1\u6216\u91CD\u542F\u540E\u6E05\u9664\u3002\u65B0\u5EFA\u6216\u5206\u53C9\u7684\u4F1A\u8BDD\u7EE7\u627F\u5168\u5C40\u9ED8\u8BA4\u503C\u3002",
1129
+ sessionAdvisorOff: "\u672C\u4F1A\u8BDD\u7684\u987E\u95EE\u5DF2\u5173\u95ED\u3002",
1130
+ sessionGateBlocked: "\u65E0\u6CD5\u53D1\u8D77\u6A21\u578B\u8C03\u7528\uFF1A{reason}",
1131
+ sessionPinAction: "\u56FA\u5B9A\u6B64\u6A21\u578B",
1132
+ sessionPending: "\u5E94\u7528\u4E2D\u2026",
1133
+ sessionReset: "\u4F7F\u7528\u5168\u5C40\u9ED8\u8BA4",
1134
+ sessionUnavailable: "\u672C\u5BBF\u4E3B\u4E0A\u7684\u987E\u95EE\u4F1A\u8BDD\u63A7\u5236\u9762\u4E0D\u53EF\u7528\u3002\u8BF7\u5728\u4F1A\u8BDD\u4E2D\u4F7F\u7528 /advisor model\uFF0C\u6216\u5728 Plugins \u9875\u914D\u7F6E\u5168\u5C40\u9ED8\u8BA4\u503C\u3002"
805
1135
  };
806
1136
 
807
1137
  // src/client/index.ts
808
1138
  var NS = "settings.advisor";
1139
+ var BUNDLE_NAME = "dsh-advisor";
809
1140
  var inject = ["slots", "locale", "connection", "settingsSchema", "remote", "remote.llm", "remote.settings", "remote.session"];
810
1141
  function apply(ctx) {
811
1142
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "advisor: copy dictionaries");
812
1143
  const connection = ctx.get("connection");
813
1144
  const controller = new AdvisorSettingsStore(ctx.remote, connection.rpc, ctx.settingsSchema);
1145
+ const refreshSignal = (0, import_dsh_client_store2.createSnapshotStore)({ epoch: 0 });
814
1146
  ctx.effect(() => {
815
1147
  let pending = false;
816
1148
  const refresh = () => {
@@ -819,25 +1151,30 @@ function apply(ctx) {
819
1151
  queueMicrotask(() => {
820
1152
  pending = false;
821
1153
  refreshIfLoaded(controller);
1154
+ refreshSignal.update((signal) => {
1155
+ signal.epoch += 1;
1156
+ });
822
1157
  });
823
1158
  };
824
1159
  const disposers = [ctx.on("connection/reset", refresh)];
825
1160
  disposers.push(ctx.remote.$on("settings/document-updated", refresh));
826
1161
  disposers.push(ctx.remote.$on("llm/adapters-updated", refresh));
1162
+ const onFocus = () => {
1163
+ refresh();
1164
+ };
1165
+ window.addEventListener("focus", onFocus);
827
1166
  return () => {
1167
+ window.removeEventListener("focus", onFocus);
828
1168
  for (const dispose of disposers) dispose();
829
1169
  };
830
1170
  }, "advisor: pushed invalidations");
831
- ctx.slots.inject("settings.plugin.item", function* () {
1171
+ ctx.slots.inject("plugins.bundle.config", function* () {
832
1172
  yield ctx.slots.register({
833
- name: "settings.plugin.item",
834
- // Keyed slot: the key is the settings namespace the card edits —
835
- // the already-installed `advisor` namespace (ADVISOR_SETTINGS_NAMESPACE
836
- // in src/settings.ts; spelled as a literal here because the host-half
837
- // settings module is outside the client bundle's externals). Keyed
838
- // entries declare no `id`/`order` — appearance order is registration
839
- // order, which still places this card after the upstream three.
840
- key: "advisor",
1173
+ name: "plugins.bundle.config",
1174
+ // Keyed slot: the key is the bundle package name the page dispatches
1175
+ // (`entryKey = pkg.name` in ui-plugin-manager's PackageDetail). Keyed
1176
+ // entries declare no `id`/`order` — there is one entry per bundle page.
1177
+ key: BUNDLE_NAME,
841
1178
  locale: NS,
842
1179
  // Hooks compartment: the renderer binds `hooks.snapshot` to the
843
1180
  // component's `useSnapshot` selector hook (the old web-react
@@ -845,5 +1182,29 @@ function apply(ctx) {
845
1182
  inject: () => ({ controller, hooks: { snapshot: controller.store } })
846
1183
  }, AdvisorCard);
847
1184
  });
1185
+ ctx.slots.inject("conversation.session.header.actions", function* () {
1186
+ yield ctx.slots.register({
1187
+ name: "conversation.session.header.actions",
1188
+ // List slot: one cell per `id` — this bundle contributes exactly one
1189
+ // action; default order keeps registration sequencing.
1190
+ id: BUNDLE_NAME,
1191
+ locale: NS,
1192
+ inject: (sessionId) => {
1193
+ const sessionController = new AdvisorSessionModelController(sessionId, connection.rpc);
1194
+ return {
1195
+ sessionId,
1196
+ controller: sessionController,
1197
+ // READ-only directory reuse: provider/model options come from the
1198
+ // global card's store; the session surface never writes through it.
1199
+ directory: controller,
1200
+ hooks: {
1201
+ snapshot: sessionController.store,
1202
+ refreshSignal,
1203
+ directory: controller.store
1204
+ }
1205
+ };
1206
+ }
1207
+ }, AdvisorSessionAction);
1208
+ });
848
1209
  }
849
1210
  return module.exports; } });