dsh-tender-workbench 0.2.2-beta.0 → 0.2.2-beta.1

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/README.md CHANGED
@@ -101,7 +101,7 @@ dsh plugin --profile web remove dsh-tender-workbench
101
101
 
102
102
  ## Using the workbench
103
103
 
104
- The plugin exposes a top-left “招投标” launcher, four phase shortcuts below the conversation composer, and a Session Header recovery action. Every entry focuses the same Better Sidebar tab for the addressed Session. When no Session is selected, the sidebar action starts the native New Session flow.
104
+ The plugin exposes a top-left “招投标” launcher and a Session Header recovery action. Every top-left activation creates a distinct native Session in the current Session's workspace, the recent workspace, or the first available workspace, gives that blank Session the “招投标” hero label and goal icon, and opens its Better Sidebar workbench. Other Sessions keep their existing host/provider hero label and the fish mark. The composer-bottom shortcuts are intentionally absent; the Header action only reopens the workbench for its existing Session.
105
105
 
106
106
  Navigation only changes the visible workbench phase. It does not mutate business state or run a later action. Criteria are proposed, edited, previewed, and confirmed as distinct steps. Agent recommendations never become user decisions automatically. Report generation always shows the reviewed and pending scope before creating a delivery snapshot.
107
107
 
package/lib/client.js CHANGED
@@ -5,8 +5,8 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- let react_dom = require("react-dom");
9
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_dom = require("react-dom");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
12
12
  //#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
@@ -4357,94 +4357,46 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4357
4357
  return (0, react.useSyncExternalStore)(subscribe, source.getSnapshot, source.getSnapshot);
4358
4358
  }
4359
4359
  //#endregion
4360
- //#region src/client/workbench/navigation-controller.ts
4361
- /** Single front-stage mapping for the seven non-linear Projection nodes. */
4362
- const TENDER_WORKBENCH_PHASES = [
4363
- {
4364
- id: "opportunity",
4365
- labelKey: "workbench.phase.opportunity",
4366
- icon: "search",
4367
- nodes: ["query", "overview"],
4368
- implemented: true,
4369
- completionNode: "overview"
4370
- },
4371
- {
4372
- id: "screening",
4373
- labelKey: "workbench.phase.screening",
4374
- icon: "screening",
4375
- nodes: [
4376
- "rules",
4377
- "classification",
4378
- "analysis"
4379
- ],
4380
- implemented: true,
4381
- completionNode: "classification"
4382
- },
4383
- {
4384
- id: "decision",
4385
- labelKey: "workbench.phase.decision",
4386
- icon: "decision",
4387
- nodes: ["review"],
4388
- implemented: true,
4389
- completionNode: "review"
4390
- },
4391
- {
4392
- id: "delivery",
4393
- labelKey: "workbench.phase.delivery",
4394
- icon: "delivery",
4395
- nodes: ["report"],
4396
- implemented: true,
4397
- completionNode: "report"
4360
+ //#region src/client/tender-session-entry.ts
4361
+ const TENDER_ENTRY_SESSION_ID_PREFIX = "session-dsh-tender-workbench-";
4362
+ var TenderSessionEntryError = class extends Error {
4363
+ code;
4364
+ constructor(code) {
4365
+ super(code);
4366
+ this.code = code;
4367
+ this.name = "TenderSessionEntryError";
4398
4368
  }
4399
- ];
4400
- TENDER_WORKBENCH_PHASES.map((phase) => phase.id);
4401
- function tenderWorkbenchPhaseProgress(projection, phase) {
4402
- const config = TENDER_WORKBENCH_PHASES.find((candidate) => candidate.id === phase);
4403
- if (config === void 0) return "not-started";
4404
- if (!config.implemented) return "unavailable";
4405
- if (projection === void 0) return "not-started";
4406
- const statuses = config.nodes.map((stage) => projection.stages[stage].status);
4407
- if (statuses.includes("running") || statuses.includes("waiting-agent")) return "running";
4408
- if (statuses.includes("failed")) return "failed";
4409
- if (statuses.includes("blocked")) return "blocked";
4410
- if (config.completionNode !== void 0 && projection.stages[config.completionNode].status === "succeeded") return "completed";
4411
- if (statuses.includes("succeeded")) return "progress";
4412
- if (phase === "decision" && projection.stages.classification.status === "succeeded") return "progress";
4413
- return "not-started";
4414
- }
4415
- function tenderWorkbenchPhaseForStage(stage) {
4416
- if (stage === void 0) return "opportunity";
4417
- return TENDER_WORKBENCH_PHASES.find((phase) => phase.nodes.includes(stage))?.id ?? "opportunity";
4418
- }
4419
- /** Session-scoped transient navigation; it never represents workflow progress. */
4420
- function createTenderWorkbenchNavigationController() {
4421
- const targets = /* @__PURE__ */ new Map();
4422
- const pending = /* @__PURE__ */ new Map();
4423
- return {
4424
- attach(sessionId, select) {
4425
- targets.set(sessionId, select);
4426
- const requested = pending.get(sessionId);
4427
- if (requested !== void 0) {
4428
- pending.delete(sessionId);
4429
- select(requested);
4430
- }
4431
- return () => {
4432
- if (targets.get(sessionId) === select) targets.delete(sessionId);
4433
- };
4434
- },
4435
- request(sessionId, phase) {
4436
- const select = targets.get(sessionId);
4437
- if (select === void 0) pending.set(sessionId, phase);
4438
- else select(phase);
4439
- }
4440
- };
4441
- }
4442
- function useTenderWorkbenchNavigation(controller, sessionId, select) {
4443
- (0, react.useEffect)(() => controller.attach(sessionId, select), [
4444
- controller,
4445
- select,
4446
- sessionId
4447
- ]);
4369
+ };
4370
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
4371
+ function browserUuid() {
4372
+ if (typeof globalThis.crypto?.randomUUID !== "function") throw new TenderSessionEntryError("create-unavailable");
4373
+ return globalThis.crypto.randomUUID();
4374
+ }
4375
+ function isTenderEntrySessionId(sessionId) {
4376
+ return sessionId?.startsWith("session-dsh-tender-workbench-") === true && UUID_PATTERN.test(sessionId.slice(29));
4377
+ }
4378
+ function createTenderEntrySessionId(uuid) {
4379
+ if (!UUID_PATTERN.test(uuid)) throw new TenderSessionEntryError("invalid-session-id");
4380
+ return `${TENDER_ENTRY_SESSION_ID_PREFIX}${uuid}`;
4381
+ }
4382
+ function resolveTenderEntryWorkspace(sessions, workspaces) {
4383
+ const current = sessions.list.getSnapshot().current;
4384
+ const workspaceSnapshot = workspaces.list.getSnapshot();
4385
+ return (current === void 0 ? void 0 : workspaceSnapshot.items.find((workspace) => workspace.sessionIds.includes(current)))?.workspaceId ?? workspaceSnapshot.recentWorkspaceId ?? workspaceSnapshot.items[0]?.workspaceId;
4386
+ }
4387
+ /** Create a distinct native Session without falling back to blank-Session reuse. */
4388
+ async function createTenderEntrySession(sessions, workspaces, uuid = browserUuid) {
4389
+ const workspaceId = resolveTenderEntryWorkspace(sessions, workspaces);
4390
+ if (workspaceId === void 0) throw new TenderSessionEntryError("workspace-unavailable");
4391
+ const capability = sessions;
4392
+ if (typeof capability.create !== "function") throw new TenderSessionEntryError("create-unavailable");
4393
+ const requestedId = createTenderEntrySessionId(uuid());
4394
+ const createdId = await capability.create({
4395
+ workspaceId,
4396
+ sessionId: requestedId
4397
+ });
4398
+ if (createdId !== requestedId) throw new TenderSessionEntryError("invalid-session-id");
4399
+ return createdId;
4448
4400
  }
4449
4401
  //#endregion
4450
4402
  //#region src/client/workbench/workbench-status.ts
@@ -4471,7 +4423,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4471
4423
  }
4472
4424
  //#endregion
4473
4425
  //#region \0dsh-tender-workbench-css:C:\workspace_github\dsh-tender-workbench\src\client\workbench-entry.module.css.mjs
4474
- const css$1 = ".hbWcya_topMount{flex:none;width:100%;min-width:0}.hbWcya_topEntry{box-sizing:border-box;width:100%;padding-right:var(--dsh-sidebar-inline-padding,12px)}.hbWcya_sidebarButton,.hbWcya_headerButton{color:var(--text-secondary,#475467);cursor:pointer;font:inherit;background:0 0;border:0;justify-content:center;align-items:center;gap:6px;display:inline-flex}.hbWcya_sidebarButton:hover,.hbWcya_sidebarButton:focus-visible,.hbWcya_headerButton:hover,.hbWcya_headerButton:focus-visible{color:var(--text-primary,#1f2430);background:var(--background-secondary,#f2f4f7)}.hbWcya_sidebarButton{border-radius:6px;justify-content:flex-start;width:100%;min-height:40px;padding:8px 12px;font-size:13px}.hbWcya_sidebarRail{justify-content:center;width:40px;padding-inline:0}.hbWcya_dockHost{box-sizing:border-box;flex:none;order:120;width:100%;padding:0 0 14px}.hbWcya_dockBar{box-sizing:border-box;width:min(calc(var(--dsh-composer-card-max-width,780px) + 32px), 100%);min-width:0;min-height:34px;padding:0 var(--dsh-composer-side-clearance,16px);color:var(--text-secondary,#475467);font:inherit;letter-spacing:0;justify-content:center;align-items:center;gap:8px;margin:0 auto;display:flex}.hbWcya_dockBrand,.hbWcya_dockActions,.hbWcya_dockButton{align-items:center;display:inline-flex}.hbWcya_dockBrand{color:var(--text-primary,#1f2430);white-space:nowrap;flex:none;gap:6px;padding-right:4px;font-size:13px;font-weight:600}.hbWcya_dockActions{flex-wrap:wrap;justify-content:center;gap:6px;min-width:0}.hbWcya_dockButton{border:1px solid var(--border-color,#e1e5ea);min-height:32px;color:var(--text-secondary,#475467);background:var(--background-primary,#fff);cursor:pointer;font:inherit;letter-spacing:0;white-space:nowrap;border-radius:5px;flex:none;gap:6px;padding:0 12px;font-size:13px}.hbWcya_dockButton:hover,.hbWcya_dockButton:focus-visible{border-color:var(--text-secondary,#475467);color:var(--text-primary,#1f2430);background:var(--background-secondary,#f2f4f7)}.hbWcya_dockButton[data-phase-status=running]{border-color:color-mix(in srgb, var(--color-primary,#4f6ef7) 45%, transparent);color:var(--color-primary,#4f6ef7)}.hbWcya_dockButton[data-phase-status=completed]{color:#217a4b}.hbWcya_dockButton[data-phase-status=failed],.hbWcya_dockButton[data-phase-status=blocked]{color:#b42318}.hbWcya_headerButton{border:1px solid var(--border-color,#e5e7eb);border-radius:999px;min-height:26px;padding:4px 8px;font-size:11px}.hbWcya_headerButton[data-workbench-status=running]{border-color:color-mix(in srgb, var(--color-primary,#4f6ef7) 40%, transparent);color:var(--color-primary,#4f6ef7)}.hbWcya_headerButton[data-workbench-status=failed],.hbWcya_headerButton[data-workbench-status=unavailable]{color:#b42318;border-color:#d92d2066}@media (width<=640px){.hbWcya_dockBar{flex-direction:column;align-items:flex-start}.hbWcya_dockActions{justify-content:flex-start;width:100%}.hbWcya_dockButton{flex:calc(50% - 3px);justify-content:center;min-width:0}}";
4426
+ const css$1 = ".hbWcya_topMount{flex:none;width:100%;min-width:0}.hbWcya_topEntry{box-sizing:border-box;width:100%;padding-right:var(--dsh-sidebar-inline-padding,12px)}.hbWcya_headerButton{color:var(--text-secondary,#475467);cursor:pointer;font:inherit;background:0 0;border:0;justify-content:center;align-items:center;gap:6px;display:inline-flex}.hbWcya_headerButton:hover,.hbWcya_headerButton:focus-visible{color:var(--text-primary,#1f2430);background:var(--background-secondary,#f2f4f7)}.hbWcya_sidebarButton:disabled{cursor:wait;opacity:.62}.hbWcya_sidebarButton{box-sizing:border-box;white-space:nowrap;border-radius:12px;flex:none;justify-content:flex-start;gap:5px;width:100%;min-width:0;height:42px;margin:0 0 8px;padding:0 10px;overflow:hidden}.hbWcya_sidebarRail{border-radius:50%;justify-content:center;width:36px;height:36px;padding:0}.hbWcya_sidebarError{color:#b42318;overflow-wrap:anywhere;padding:2px 12px 7px;font-size:11px;line-height:16px;display:block}.hbWcya_headerButton{border:1px solid var(--border-color,#e5e7eb);border-radius:999px;min-height:26px;padding:4px 8px;font-size:11px}.hbWcya_headerButton[data-workbench-status=running]{border-color:color-mix(in srgb, var(--color-primary,#4f6ef7) 40%, transparent);color:var(--color-primary,#4f6ef7)}.hbWcya_headerButton[data-workbench-status=failed],.hbWcya_headerButton[data-workbench-status=unavailable]{color:#b42318;border-color:#d92d2066}.hbWcya_topEntry[data-wide=false] .hbWcya_sidebarError{clip:rect(0, 0, 0, 0);white-space:nowrap;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}";
4475
4427
  const tagId$1 = "dsh-tender-workbench/workbench-entry.module.css";
4476
4428
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
4477
4429
  const tag = document.createElement("style");
@@ -4481,13 +4433,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4481
4433
  document.head.appendChild(tag);
4482
4434
  }
4483
4435
  var workbench_entry_module_css_default = {
4484
- "dockActions": "hbWcya_dockActions",
4485
- "dockBar": "hbWcya_dockBar",
4486
- "dockBrand": "hbWcya_dockBrand",
4487
- "dockButton": "hbWcya_dockButton",
4488
- "dockHost": "hbWcya_dockHost",
4489
4436
  "headerButton": "hbWcya_headerButton",
4490
4437
  "sidebarButton": "hbWcya_sidebarButton",
4438
+ "sidebarError": "hbWcya_sidebarError",
4491
4439
  "sidebarRail": "hbWcya_sidebarRail",
4492
4440
  "topEntry": "hbWcya_topEntry",
4493
4441
  "topMount": "hbWcya_topMount"
@@ -4495,97 +4443,99 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4495
4443
  //#endregion
4496
4444
  //#region src/client/TenderEntry.tsx
4497
4445
  const SIDEBAR_WORKSPACES_SELECTOR = "[data-slot=\"sidebar.workspaces\"]";
4498
- const DATA_CLEANING_TOP_MOUNT_SELECTOR = "[data-data-cleaning-top-mount=\"true\"]";
4499
4446
  const TENDER_TOP_MOUNT_SELECTOR = "[data-dsh-tender-top-mount=\"true\"]";
4500
- function phaseIcon(icon) {
4501
- if (icon === "search") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 16 });
4502
- if (icon === "screening") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChecklistOutline14, { size: 16 });
4503
- if (icon === "decision") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 16 });
4504
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { size: 16 });
4505
- }
4506
- /** Four existing workbench phases presented as one compact launcher group. */
4507
- function TenderDockEntryView({ openWorkbench, projection, t }) {
4508
- const read = readTenderProjectionSnapshot(projection);
4509
- const workflow = read.status === "ready" ? read.projection : void 0;
4510
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4511
- className: workbench_entry_module_css_default.dockBar,
4512
- role: "group",
4513
- "aria-label": t("workbench.phases"),
4514
- "data-dsh-plugin": "tender-workbench",
4515
- "data-dsh-part": "dock-entry",
4516
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4517
- className: workbench_entry_module_css_default.dockBrand,
4518
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16, { size: 15 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sidebar.label") })]
4519
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4520
- className: workbench_entry_module_css_default.dockActions,
4521
- children: TENDER_WORKBENCH_PHASES.map((phase) => {
4522
- const progress = tenderWorkbenchPhaseProgress(workflow, phase.id);
4523
- const label = t(phase.labelKey);
4524
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4525
- type: "button",
4526
- className: workbench_entry_module_css_default.dockButton,
4527
- "data-phase": phase.id,
4528
- "data-phase-status": progress,
4529
- title: `${label} · ${t(`workbench.phaseStatus.${progress}`)}`,
4530
- onClick: () => {
4531
- openWorkbench(phase.id);
4532
- },
4533
- children: [phaseIcon(phase.icon), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
4534
- }, phase.id);
4535
- })
4536
- })]
4447
+ const KNOWN_HERO_HEADLINES = /* @__PURE__ */ new Set([
4448
+ "探索未至之境",
4449
+ "Into the Unknown",
4450
+ "访前尽调智能体"
4451
+ ]);
4452
+ /** Preserve the native mark everywhere except Sessions created by the tender entry. */
4453
+ function TenderHeroBrandMark({ size, className, useSessions }) {
4454
+ return isTenderEntrySessionId(useSessions((snapshot) => snapshot.current)) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16, {
4455
+ size,
4456
+ className
4457
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.FishLogo, {
4458
+ size,
4459
+ className
4460
+ });
4461
+ }
4462
+ function rewriteTenderHeroHeadline(anchor, enabled, replacement) {
4463
+ if (!enabled) return () => {};
4464
+ const hero = anchor.closest("[data-phase=\"hero\"]");
4465
+ if (hero === null) return () => {};
4466
+ const title = [...hero.querySelectorAll("span")].find((element) => element.dataset.dshTenderHeroHeadline === "true" || KNOWN_HERO_HEADLINES.has(element.textContent?.trim() ?? ""));
4467
+ if (title === void 0) return () => {};
4468
+ let original = title.dataset.dshTenderOriginalHeadline ?? title.textContent ?? "";
4469
+ title.dataset.dshTenderHeroHeadline = "true";
4470
+ title.dataset.dshTenderOriginalHeadline = original;
4471
+ title.textContent = replacement;
4472
+ const Observer = anchor.ownerDocument.defaultView?.MutationObserver;
4473
+ const observer = Observer === void 0 ? void 0 : new Observer(() => {
4474
+ if (title.textContent === replacement) return;
4475
+ original = title.textContent ?? original;
4476
+ title.dataset.dshTenderOriginalHeadline = original;
4477
+ title.textContent = replacement;
4478
+ });
4479
+ observer?.observe(title, {
4480
+ childList: true,
4481
+ characterData: true,
4482
+ subtree: true
4537
4483
  });
4484
+ return () => {
4485
+ observer?.disconnect();
4486
+ if (title.dataset.dshTenderOriginalHeadline !== original) return;
4487
+ if (title.textContent === replacement) title.textContent = original;
4488
+ delete title.dataset.dshTenderOriginalHeadline;
4489
+ delete title.dataset.dshTenderHeroHeadline;
4490
+ };
4538
4491
  }
4539
- /** Session entry hosted after the composer so it remains visible in the hero phase. */
4540
- function TenderDockEntry({ openWorkbench, sessionId, t, useProjection }) {
4492
+ /** Narrow, reversible bridge for the headline that DSH does not expose as a Slot. */
4493
+ function TenderHeroTitleBridge({ sessionId, t, useSession }) {
4541
4494
  const anchorRef = (0, react.useRef)(null);
4542
- const [host, setHost] = (0, react.useState)(null);
4543
- const projection = useProjection("dshTenderWorkflow");
4495
+ const blank = useSession((snapshot) => snapshot.composerPhase === "blank");
4496
+ const replacement = t("sidebar.label");
4544
4497
  (0, react.useEffect)(() => {
4545
4498
  const anchor = anchorRef.current;
4546
4499
  if (anchor === null) return;
4547
- const stack = anchor.closest("[class*=\"composerStack\"]") ?? anchor.parentElement;
4548
- if (stack === null) return;
4549
- const nextHost = document.createElement("div");
4550
- nextHost.className = workbench_entry_module_css_default.dockHost ?? "";
4551
- nextHost.dataset.dshTenderDockHost = "true";
4552
- stack.append(nextHost);
4553
- setHost(nextHost);
4554
- return () => {
4555
- nextHost.remove();
4556
- setHost((current) => current === nextHost ? null : current);
4557
- };
4558
- }, [sessionId]);
4559
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4500
+ return rewriteTenderHeroHeadline(anchor, blank && isTenderEntrySessionId(sessionId), replacement);
4501
+ }, [
4502
+ blank,
4503
+ replacement,
4504
+ sessionId
4505
+ ]);
4506
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4560
4507
  ref: anchorRef,
4561
4508
  hidden: true,
4562
- "data-dsh-tender-dock-anchor": "true"
4563
- }), host !== null && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TenderDockEntryView, {
4564
- openWorkbench,
4565
- projection,
4566
- t
4567
- }), host)] });
4509
+ "data-dsh-tender-hero-anchor": "true"
4510
+ });
4568
4511
  }
4569
4512
  function ensureTenderTopMount() {
4570
4513
  const workspaceSlot = document.querySelector(SIDEBAR_WORKSPACES_SELECTOR);
4571
4514
  if (workspaceSlot === null) return null;
4572
4515
  const parent = workspaceSlot.parentElement;
4573
4516
  if (parent === null) return null;
4574
- let mount = parent.querySelector(TENDER_TOP_MOUNT_SELECTOR);
4517
+ let mount = document.querySelector(TENDER_TOP_MOUNT_SELECTOR);
4575
4518
  if (mount === null) {
4576
4519
  mount = document.createElement("div");
4577
4520
  mount.dataset.dshTenderTopMount = "true";
4578
4521
  mount.className = workbench_entry_module_css_default.topMount ?? "";
4579
4522
  }
4580
- const dataCleaningMount = parent.querySelector(DATA_CLEANING_TOP_MOUNT_SELECTOR);
4581
- if (dataCleaningMount !== null) {
4582
- if (mount.parentElement !== parent || mount.previousSibling !== dataCleaningMount) parent.insertBefore(mount, dataCleaningMount.nextSibling);
4583
- } else if (mount.parentElement !== parent) parent.insertBefore(mount, workspaceSlot);
4523
+ if (mount.parentElement !== parent) parent.prepend(mount);
4584
4524
  return mount;
4585
4525
  }
4586
- /** Global launcher portalled between the Data Cleaning entry and workspace browser. */
4587
- function TenderSidebarEntry({ wide, openCurrentWorkbench, t }) {
4526
+ /** Global launcher portalled at the start of the sidebar's primary action region. */
4527
+ function TenderSidebarEntry({ wide, startTenderSession, t }) {
4588
4528
  const [topMount, setTopMount] = (0, react.useState)(null);
4529
+ const [creating, setCreating] = (0, react.useState)(false);
4530
+ const [error, setError] = (0, react.useState)();
4531
+ const creatingRef = (0, react.useRef)(false);
4532
+ const mountedRef = (0, react.useRef)(true);
4533
+ (0, react.useEffect)(() => {
4534
+ mountedRef.current = true;
4535
+ return () => {
4536
+ mountedRef.current = false;
4537
+ };
4538
+ }, []);
4589
4539
  (0, react.useEffect)(() => {
4590
4540
  if (typeof document === "undefined") return;
4591
4541
  let disposed = false;
@@ -4610,17 +4560,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4610
4560
  };
4611
4561
  }, []);
4612
4562
  if (topMount === null) return null;
4613
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4563
+ const start = () => {
4564
+ if (creatingRef.current) return;
4565
+ creatingRef.current = true;
4566
+ setCreating(true);
4567
+ setError(void 0);
4568
+ startTenderSession().catch((reason) => {
4569
+ if (mountedRef.current) setError(reason instanceof Error ? reason.message : t("sidebar.createFailed"));
4570
+ }).finally(() => {
4571
+ creatingRef.current = false;
4572
+ if (mountedRef.current) setCreating(false);
4573
+ });
4574
+ };
4575
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4614
4576
  className: workbench_entry_module_css_default.topEntry,
4577
+ "data-wide": wide,
4615
4578
  "data-dsh-plugin": "tender-workbench",
4616
4579
  "data-dsh-part": "top-entry",
4617
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4580
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4618
4581
  type: "button",
4582
+ variant: "ghost",
4619
4583
  className: wide ? workbench_entry_module_css_default.sidebarButton : `${workbench_entry_module_css_default.sidebarButton} ${workbench_entry_module_css_default.sidebarRail}`,
4584
+ "data-wide": wide,
4620
4585
  "aria-label": t("sidebar.aria"),
4621
- onClick: openCurrentWorkbench,
4586
+ "aria-busy": creating,
4587
+ disabled: creating,
4588
+ title: error,
4589
+ onClick: start,
4622
4590
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16, { size: wide ? 16 : 18 }), wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sidebar.label") })]
4623
- })
4591
+ }), error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4592
+ className: workbench_entry_module_css_default.sidebarError,
4593
+ role: "alert",
4594
+ children: error
4595
+ })]
4624
4596
  }), topMount);
4625
4597
  }
4626
4598
  function TenderSessionHeaderEntry({ openWorkbench, t, useProjection }) {
@@ -4708,11 +4680,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4708
4680
  if (!features.includes("stateSubscription")) throw new Error("dsh-tender-workbench requires the Better Sidebar stateSubscription capability");
4709
4681
  }
4710
4682
  /** Register the single Session-scoped workbench tab through Better Sidebar's public service. */
4711
- function registerTenderWorkbenchTab(service, component, title = "招投标") {
4683
+ function registerTenderWorkbenchTab(service, component, title = "招投标", icon) {
4712
4684
  assertBetterSidebarContract(service);
4713
4685
  return service.registerTab({
4714
4686
  id: TENDER_WORKBENCH_TAB_ID,
4715
4687
  title,
4688
+ icon,
4716
4689
  order: 40,
4717
4690
  single: true,
4718
4691
  component
@@ -20408,7 +20381,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
20408
20381
  "trigger.label": "搜索招投标",
20409
20382
  "trigger.aria": "打开招投标工作台",
20410
20383
  "sidebar.label": "招投标",
20411
- "sidebar.aria": "打开招投标工作台",
20384
+ "sidebar.aria": "新建招投标会话",
20385
+ "sidebar.createFailed": "未能新建招投标会话,请重试。",
20386
+ "sidebar.createUnavailable": "当前 DSH 不支持新建招投标专属会话。",
20387
+ "sidebar.workspaceRequired": "请先选择或创建一个工作区。",
20412
20388
  "header.reopen": "招投标工作台",
20413
20389
  "workbench.title": "招投标工作台",
20414
20390
  "workbench.subtitle": "与当前 Session 联动 · 所有结论可追溯",
@@ -21467,7 +21443,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
21467
21443
  "trigger.label": "Search tenders",
21468
21444
  "trigger.aria": "Open tender workbench",
21469
21445
  "sidebar.label": "Tenders",
21470
- "sidebar.aria": "Open tender workbench",
21446
+ "sidebar.aria": "Start a tender session",
21447
+ "sidebar.createFailed": "The tender session could not be created. Try again.",
21448
+ "sidebar.createUnavailable": "This DSH runtime cannot create a dedicated tender session.",
21449
+ "sidebar.workspaceRequired": "Select or create a workspace first.",
21471
21450
  "header.reopen": "Tender workbench",
21472
21451
  "workbench.title": "Tender workbench",
21473
21452
  "workbench.subtitle": "Linked to this Session · Every conclusion remains traceable",
@@ -23008,6 +22987,96 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
23008
22987
  return errors;
23009
22988
  }
23010
22989
  //#endregion
22990
+ //#region src/client/workbench/navigation-controller.ts
22991
+ /** Single front-stage mapping for the seven non-linear Projection nodes. */
22992
+ const TENDER_WORKBENCH_PHASES = [
22993
+ {
22994
+ id: "opportunity",
22995
+ labelKey: "workbench.phase.opportunity",
22996
+ icon: "search",
22997
+ nodes: ["query", "overview"],
22998
+ implemented: true,
22999
+ completionNode: "overview"
23000
+ },
23001
+ {
23002
+ id: "screening",
23003
+ labelKey: "workbench.phase.screening",
23004
+ icon: "screening",
23005
+ nodes: [
23006
+ "rules",
23007
+ "classification",
23008
+ "analysis"
23009
+ ],
23010
+ implemented: true,
23011
+ completionNode: "classification"
23012
+ },
23013
+ {
23014
+ id: "decision",
23015
+ labelKey: "workbench.phase.decision",
23016
+ icon: "decision",
23017
+ nodes: ["review"],
23018
+ implemented: true,
23019
+ completionNode: "review"
23020
+ },
23021
+ {
23022
+ id: "delivery",
23023
+ labelKey: "workbench.phase.delivery",
23024
+ icon: "delivery",
23025
+ nodes: ["report"],
23026
+ implemented: true,
23027
+ completionNode: "report"
23028
+ }
23029
+ ];
23030
+ TENDER_WORKBENCH_PHASES.map((phase) => phase.id);
23031
+ function tenderWorkbenchPhaseProgress(projection, phase) {
23032
+ const config = TENDER_WORKBENCH_PHASES.find((candidate) => candidate.id === phase);
23033
+ if (config === void 0) return "not-started";
23034
+ if (!config.implemented) return "unavailable";
23035
+ if (projection === void 0) return "not-started";
23036
+ const statuses = config.nodes.map((stage) => projection.stages[stage].status);
23037
+ if (statuses.includes("running") || statuses.includes("waiting-agent")) return "running";
23038
+ if (statuses.includes("failed")) return "failed";
23039
+ if (statuses.includes("blocked")) return "blocked";
23040
+ if (config.completionNode !== void 0 && projection.stages[config.completionNode].status === "succeeded") return "completed";
23041
+ if (statuses.includes("succeeded")) return "progress";
23042
+ if (phase === "decision" && projection.stages.classification.status === "succeeded") return "progress";
23043
+ return "not-started";
23044
+ }
23045
+ function tenderWorkbenchPhaseForStage(stage) {
23046
+ if (stage === void 0) return "opportunity";
23047
+ return TENDER_WORKBENCH_PHASES.find((phase) => phase.nodes.includes(stage))?.id ?? "opportunity";
23048
+ }
23049
+ /** Session-scoped transient navigation; it never represents workflow progress. */
23050
+ function createTenderWorkbenchNavigationController() {
23051
+ const targets = /* @__PURE__ */ new Map();
23052
+ const pending = /* @__PURE__ */ new Map();
23053
+ return {
23054
+ attach(sessionId, select) {
23055
+ targets.set(sessionId, select);
23056
+ const requested = pending.get(sessionId);
23057
+ if (requested !== void 0) {
23058
+ pending.delete(sessionId);
23059
+ select(requested);
23060
+ }
23061
+ return () => {
23062
+ if (targets.get(sessionId) === select) targets.delete(sessionId);
23063
+ };
23064
+ },
23065
+ request(sessionId, phase) {
23066
+ const select = targets.get(sessionId);
23067
+ if (select === void 0) pending.set(sessionId, phase);
23068
+ else select(phase);
23069
+ }
23070
+ };
23071
+ }
23072
+ function useTenderWorkbenchNavigation(controller, sessionId, select) {
23073
+ (0, react.useEffect)(() => controller.attach(sessionId, select), [
23074
+ controller,
23075
+ select,
23076
+ sessionId
23077
+ ]);
23078
+ }
23079
+ //#endregion
23011
23080
  //#region src/client/workbench/session-write-flight.ts
23012
23081
  const IDLE_PHASES = /* @__PURE__ */ new Set([
23013
23082
  "idle",
@@ -30266,7 +30335,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
30266
30335
  t
30267
30336
  });
30268
30337
  }
30269
- /** Register the workbench Tab, two placement bridges, and the Header recovery action. */
30338
+ /** Register the dedicated Session entry, workbench Tab, Hero brand, and Header recovery action. */
30270
30339
  function apply(ctx) {
30271
30340
  ctx.conversationEvents.register(tenderSearchDefinition);
30272
30341
  ctx.effect(() => ctx.locale.register(NS, {
@@ -30280,6 +30349,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
30280
30349
  const reveal = createTenderWorkbenchRevealController();
30281
30350
  const navigation = createTenderWorkbenchNavigationController();
30282
30351
  const projectionPort = createTenderProjectionPort(sessions);
30352
+ let active = true;
30283
30353
  const sendIntent = (sessionId, intent) => sendSessionTenderWorkbenchIntent(sessions, sessionId, intent);
30284
30354
  const openSession = (sessionId, phase) => {
30285
30355
  const summary = sessions.list.getSnapshot().byId[sessionId];
@@ -30290,13 +30360,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
30290
30360
  if (opened && phase !== void 0) navigation.request(sessionId, phase);
30291
30361
  return opened;
30292
30362
  };
30293
- const openCurrent = () => {
30294
- const current = sessions.list.getSnapshot().current;
30295
- if (current === void 0) {
30296
- ctx.workspaces.startSession();
30297
- return false;
30363
+ const startTenderSession = async () => {
30364
+ try {
30365
+ const sessionId = await createTenderEntrySession(sessions, ctx.workspaces);
30366
+ if (!active) return;
30367
+ sessions.open(sessionId);
30368
+ if (openSession(sessionId)) reveal.request(sessionId);
30369
+ } catch (error) {
30370
+ if (error instanceof TenderSessionEntryError) {
30371
+ const key = error.code === "workspace-unavailable" ? "sidebar.workspaceRequired" : error.code === "create-unavailable" ? "sidebar.createUnavailable" : "sidebar.createFailed";
30372
+ throw new Error(t(key), { cause: error });
30373
+ }
30374
+ throw new Error(t("sidebar.createFailed"), { cause: error });
30298
30375
  }
30299
- return openSession(current);
30300
30376
  };
30301
30377
  ctx.effect(() => registerTenderWorkbenchTab(ctx.betterSidebar, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegisteredTenderWorkbenchTab, {
30302
30378
  ...props,
@@ -30306,21 +30382,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
30306
30382
  projectionPort,
30307
30383
  reveal,
30308
30384
  navigation
30309
- }), () => t("sidebar.label")), "dsh-tender-workbench: Better Sidebar tab");
30385
+ }), () => t("sidebar.label"), (size) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16, { size })), "dsh-tender-workbench: Better Sidebar tab");
30386
+ ctx.slots.inject("conversation.hero.brand.mark", () => ctx.slots.register({
30387
+ name: "conversation.hero.brand.mark",
30388
+ priority: -10
30389
+ }, TenderHeroBrandMark));
30390
+ ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
30391
+ name: "conversation.input.dock",
30392
+ id: "dsh-tender-workbench:hero-title",
30393
+ order: 120,
30394
+ locale: NS
30395
+ }, TenderHeroTitleBridge));
30310
30396
  ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
30311
30397
  name: "sidebar.footer.action",
30312
30398
  id: "dsh-tender-workbench:sidebar",
30313
30399
  order: 40,
30314
30400
  locale: NS,
30315
- inject: () => ({ openCurrentWorkbench: openCurrent })
30401
+ inject: () => ({ startTenderSession })
30316
30402
  }, TenderSidebarEntry));
30317
- ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
30318
- name: "conversation.input.dock",
30319
- id: "dsh-tender-workbench:dock",
30320
- order: 120,
30321
- locale: NS,
30322
- inject: (sessionId) => ({ openWorkbench: (phase) => openSession(sessionId, phase) })
30323
- }, TenderDockEntry));
30324
30403
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
30325
30404
  name: "conversation.session.header.actions",
30326
30405
  id: "dsh-tender-workbench:reopen",
@@ -30328,6 +30407,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
30328
30407
  locale: NS,
30329
30408
  inject: (sessionId) => ({ openWorkbench: () => openSession(sessionId) })
30330
30409
  }, TenderSessionHeaderEntry));
30410
+ ctx.effect(() => () => {
30411
+ active = false;
30412
+ }, "dsh-tender-workbench: Session entry lifetime");
30331
30413
  }
30332
30414
  //#endregion
30333
30415
  exports.apply = apply;