grain-tools 0.10.15 → 0.10.17

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/dist/web-app.html CHANGED
@@ -12820,7 +12820,7 @@ const useAppStore = create$3((set2, get2) => ({
12820
12820
  chartsEnabled: false,
12821
12821
  mcpEnabled: false,
12822
12822
  mcpEntitled: false,
12823
- grainServiceInstalled: true,
12823
+ grainServiceSetup: "registered",
12824
12824
  mcpSafeMode: true,
12825
12825
  activeConnectorType: null,
12826
12826
  activeConnectionId: null,
@@ -12910,7 +12910,7 @@ const useAppStore = create$3((set2, get2) => ({
12910
12910
  "filterResultsByActiveFile",
12911
12911
  "mcpEnabled",
12912
12912
  "mcpEntitled",
12913
- "grainServiceInstalled",
12913
+ "grainServiceSetup",
12914
12914
  "mcpSafeMode",
12915
12915
  "activeConnectorType",
12916
12916
  "activeConnectionId",
@@ -13073,6 +13073,7 @@ const ALL_TOOLS = [
13073
13073
  "list_connections",
13074
13074
  "get_demo_dataset",
13075
13075
  "save_connection",
13076
+ "create_connection_draft",
13076
13077
  "request_connection_credentials",
13077
13078
  "test_connection",
13078
13079
  "delete_connection",
@@ -13170,6 +13171,7 @@ const TELEMETRY_CONNECTORS = [
13170
13171
  ];
13171
13172
  const TELEMETRY_TOOL_NAMES = [...ALL_TOOLS, "unknown"];
13172
13173
  const TELEMETRY_COMMAND_IDS = [
13174
+ "grain.setUpService",
13173
13175
  "grain.showDaemonInfo",
13174
13176
  "grain.mcp.restart",
13175
13177
  "grain.mcp.testConnection",
@@ -13510,6 +13512,8 @@ const TELEMETRY_ERROR_IDS = [
13510
13512
  * Not a database error: nothing was ever sent to a connector.
13511
13513
  */
13512
13514
  "credential_required",
13515
+ "setup_required",
13516
+ "connection_draft_rejected",
13513
13517
  /**
13514
13518
  * The session holding this query's tab was evicted (SessionManager's maxSessions
13515
13519
  * cap) while the query was still running, so the result had nowhere to land.
@@ -13774,7 +13778,18 @@ async function buildGuideHubState() {
13774
13778
  return { ...DEFAULT_GUIDE_HUB_STATE, telemetryEnabled };
13775
13779
  }
13776
13780
  }
13777
- const WEB_SESSION_ID = `web-${crypto.randomUUID()}`;
13781
+ const WEB_SESSION_KEY = "grain:web-session-id";
13782
+ function resolveWebSessionId() {
13783
+ const stored = readStoredValue(WEB_SESSION_KEY);
13784
+ if (stored && /^web-[0-9a-f-]{36}$/.test(stored)) {
13785
+ return stored;
13786
+ }
13787
+ return `web-${crypto.randomUUID()}`;
13788
+ }
13789
+ const WEB_SESSION_ID = resolveWebSessionId();
13790
+ function rememberWebSessionId() {
13791
+ writeStoredValue(WEB_SESSION_KEY, WEB_SESSION_ID);
13792
+ }
13778
13793
  function resolveProjectedTarget(tabId, payload) {
13779
13794
  const parsed = tabId ? parseProjectedTabId(tabId) : void 0;
13780
13795
  const tab = tabId ? useAppStore.getState().tabs.find((t) => t.id === tabId) : void 0;
@@ -14030,6 +14045,8 @@ function useWebTabSync(options) {
14030
14045
  remoteId: tab.id,
14031
14046
  sessionDisplayName: session.displayName,
14032
14047
  clientType: session.clientType,
14048
+ connectionId: tab.connectionId,
14049
+ connectionName: tab.connectionName,
14033
14050
  isRemote: true,
14034
14051
  preserveFocus
14035
14052
  });
@@ -14047,6 +14064,8 @@ function useWebTabSync(options) {
14047
14064
  remoteId: tab.id,
14048
14065
  sessionDisplayName: session.displayName,
14049
14066
  clientType: session.clientType,
14067
+ connectionId: tab.connectionId,
14068
+ connectionName: tab.connectionName,
14050
14069
  isRemote: true,
14051
14070
  preserveFocus
14052
14071
  });
@@ -14067,6 +14086,8 @@ function useWebTabSync(options) {
14067
14086
  remoteId: tab.id,
14068
14087
  sessionDisplayName: session.displayName,
14069
14088
  clientType: session.clientType,
14089
+ connectionId: detail.connectionId ?? tab.connectionId,
14090
+ connectionName: detail.connectionName ?? tab.connectionName,
14070
14091
  isRemote: true
14071
14092
  };
14072
14093
  if (tab.status === "success") {
@@ -14143,7 +14164,7 @@ function useWebTabSync(options) {
14143
14164
  dispatchLoading(session, tab, true);
14144
14165
  }
14145
14166
  } else if (tab.status === "success" || tab.status === "error" || !cached || cached.status !== tab.status) {
14146
- const detailIsNeeded = tab.status === "success" && (!cached || !storeHasTab || cached.status !== tab.status || cached.rowCount !== tab.rowCount);
14167
+ const detailIsNeeded = tab.status === "success" && (!cached || !storeHasTab || cached.status !== tab.status || cached.rowCount !== tab.rowCount || cached.connectionId !== tab.connectionId);
14147
14168
  const errorIsNeeded = tab.status === "error" && (!cached || !storeHasTab || cached.status !== "error");
14148
14169
  if (detailIsNeeded || errorIsNeeded) {
14149
14170
  await dispatchTabDetail(session, tab);
@@ -14154,7 +14175,8 @@ function useWebTabSync(options) {
14154
14175
  remoteTabId: tab.id,
14155
14176
  status: tab.status,
14156
14177
  rowCount: tab.rowCount,
14157
- title: tab.title
14178
+ title: tab.title,
14179
+ connectionId: tab.connectionId
14158
14180
  });
14159
14181
  }
14160
14182
  })
@@ -14166,6 +14188,7 @@ function useWebTabSync(options) {
14166
14188
  }
14167
14189
  }
14168
14190
  unreachableStreakRef.current = 0;
14191
+ rememberWebSessionId();
14169
14192
  setDaemonStatus("connected");
14170
14193
  setIsReady(true);
14171
14194
  } catch (err) {
@@ -14214,7 +14237,8 @@ function useWebToolProxy() {
14214
14237
  query: args.sql,
14215
14238
  sessionId,
14216
14239
  connectionId: args.connectionId,
14217
- tabId: args.tabId
14240
+ tabId: args.tabId,
14241
+ title: args.title
14218
14242
  });
14219
14243
  if (!ok) {
14220
14244
  throw new Error(data.error || "Query failed");
@@ -14330,29 +14354,29 @@ function ReauthorizeNotice({
14330
14354
  const [copied, setCopied] = reactExports.useState(false);
14331
14355
  const copyCommand = async () => {
14332
14356
  try {
14333
- await navigator.clipboard.writeText("grain open");
14357
+ await navigator.clipboard.writeText("grain ui");
14334
14358
  setCopied(true);
14335
14359
  } catch {
14336
14360
  setCopied(false);
14337
14361
  }
14338
14362
  };
14339
14363
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-reauth", role: "alert", children: [
14340
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-title", children: "Grain is running. Reconnect this browser tab." }),
14364
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-title", children: reason === "unreachable" ? "This tab lost its connection to Grain." : "Grain is running. Reconnect this browser tab." }),
14341
14365
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Run this command in your terminal to open your workspace again:" }),
14342
14366
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-reauth-action", children: [
14343
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "sp-web-reauth-command", children: "grain open" }),
14367
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "sp-web-reauth-command", children: "grain ui" }),
14344
14368
  /* @__PURE__ */ jsxRuntimeExports.jsx(
14345
14369
  "button",
14346
14370
  {
14347
14371
  type: "button",
14348
14372
  className: "sp-web-secondary-button",
14349
14373
  onClick: () => void copyCommand(),
14350
- "aria-label": "Copy grain open",
14374
+ "aria-label": "Copy grain ui",
14351
14375
  children: copied ? "Copied" : "Copy"
14352
14376
  }
14353
14377
  )
14354
14378
  ] }),
14355
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-detail", children: reason === "expired-link" ? "This link has expired or was already used." : reason === "unreachable" ? "Grain may have restarted since this tab opened." : "This tab needs a fresh workspace link." })
14379
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-reauth-detail", children: reason === "expired-link" ? "This link has expired or was already used." : reason === "unreachable" ? "Grain may have stopped, or restarted on a different port. If it stopped, start it again first." : "This tab needs a fresh workspace link." })
14356
14380
  ] });
14357
14381
  }
14358
14382
  function isUpdateMarkerStatus(value) {
@@ -14472,7 +14496,10 @@ function fieldVisible(field, values) {
14472
14496
  function ConnectionSetupPage({
14473
14497
  onDone,
14474
14498
  onCancel,
14475
- initialConnection
14499
+ initialConnection,
14500
+ actions: actions2,
14501
+ mode = "full",
14502
+ canEditDetails = false
14476
14503
  }) {
14477
14504
  const { callServerTool, isReady } = useHostContext();
14478
14505
  const [connectors, setConnectors] = reactExports.useState([]);
@@ -14489,20 +14516,20 @@ function ConnectionSetupPage({
14489
14516
  initialConnection?.["id"] ?? `conn_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
14490
14517
  )
14491
14518
  );
14519
+ const [displayMode, setDisplayMode] = reactExports.useState(mode);
14492
14520
  reactExports.useEffect(() => {
14493
- if (!isReady) {
14521
+ if (!actions2 && !isReady) {
14494
14522
  return;
14495
14523
  }
14496
14524
  let active = true;
14497
- void callServerTool("list_connectors", {}).then((result) => {
14525
+ void (actions2 ? actions2.listConnectors() : callServerTool("list_connectors", {}).then((result) => {
14526
+ if (result.isError) throw new Error(toolMessage(result));
14527
+ return JSON.parse(toolMessage(result));
14528
+ })).then((result) => {
14498
14529
  if (!active) {
14499
14530
  return;
14500
14531
  }
14501
- if (result.isError) {
14502
- throw new Error(toolMessage(result));
14503
- }
14504
- const text = toolMessage(result);
14505
- const listed = JSON.parse(text);
14532
+ const listed = result;
14506
14533
  if (!Array.isArray(listed)) {
14507
14534
  throw new Error("Connector list is unavailable.");
14508
14535
  }
@@ -14524,7 +14551,7 @@ function ConnectionSetupPage({
14524
14551
  return () => {
14525
14552
  active = false;
14526
14553
  };
14527
- }, [callServerTool, isReady, initialConnection]);
14554
+ }, [callServerTool, isReady, initialConnection, actions2]);
14528
14555
  const selected = connectors.find((connector) => connector.id === type);
14529
14556
  const properties = selected?.schema?.properties ?? {};
14530
14557
  const entries = Object.entries(properties).filter(([key, field]) => {
@@ -14549,6 +14576,11 @@ function ConnectionSetupPage({
14549
14576
  ...Object.fromEntries(entries.map(([key, field]) => [key, values[key] ?? field.default]))
14550
14577
  });
14551
14578
  const test = async () => {
14579
+ if (actions2) {
14580
+ await actions2.test(profile(), password || void 0);
14581
+ setTested(true);
14582
+ return true;
14583
+ }
14552
14584
  const result = await callServerTool(
14553
14585
  "test_connection",
14554
14586
  initialConnection && !password ? { connectionId: draftId } : { type, connectionProfile: { ...profile(), ...password ? { password } : {} } }
@@ -14573,6 +14605,12 @@ function ConnectionSetupPage({
14573
14605
  return;
14574
14606
  }
14575
14607
  const id = draftId;
14608
+ if (actions2) {
14609
+ await actions2.save(profile(), password || void 0);
14610
+ setPassword("");
14611
+ onDone(id);
14612
+ return;
14613
+ }
14576
14614
  const saved = await callServerTool("save_connection", {
14577
14615
  connectionProfile: { ...profile(), id }
14578
14616
  });
@@ -14649,15 +14687,18 @@ function ConnectionSetupPage({
14649
14687
  };
14650
14688
  return /* @__PURE__ */ jsxRuntimeExports.jsx("main", { className: "grain-connection-page", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-content", children: [
14651
14689
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-intro", children: [
14652
- /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: initialConnection ? "Edit connection" : "Connect a database" }),
14653
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Add a connection to start querying your data." })
14690
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: actions2 ? "Finish connection setup" : initialConnection ? "Edit connection" : "Connect a database" }),
14691
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: actions2 ? "Test and save this connection to continue." : "Add a connection to start querying your data." })
14654
14692
  ] }),
14655
14693
  /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, children: [
14656
14694
  error && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-error", role: "alert", children: [
14657
14695
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Connection failed" }),
14658
14696
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: error })
14659
14697
  ] }),
14660
- tested && !error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "grain-connection-success", role: "status", children: "Connection test successful." }),
14698
+ tested && !error && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "grain-connection-success", role: "status", children: [
14699
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: "✓ " }),
14700
+ "Connection test successful."
14701
+ ] }),
14661
14702
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-grid", children: [
14662
14703
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-field", children: [
14663
14704
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "grain-connection-type", children: "Database type" }),
@@ -14668,7 +14709,7 @@ function ConnectionSetupPage({
14668
14709
  value: type,
14669
14710
  onChange: (event) => selectType(event.target.value),
14670
14711
  required: true,
14671
- disabled: !isReady || busy !== null,
14712
+ disabled: (actions2 ? true : !isReady) || busy !== null,
14672
14713
  children: [
14673
14714
  !type && /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Choose a database" }),
14674
14715
  connectors.map((connector) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: connector.id, children: connector.displayName }, connector.id))
@@ -14685,20 +14726,36 @@ function ConnectionSetupPage({
14685
14726
  value: name,
14686
14727
  onChange: (event) => setName(event.target.value),
14687
14728
  placeholder: "e.g. Analytics",
14688
- required: true
14729
+ required: true,
14730
+ readOnly: Boolean(actions2)
14689
14731
  }
14690
14732
  )
14691
14733
  ] })
14692
14734
  ] }),
14735
+ displayMode === "password" && canEditDetails && /* @__PURE__ */ jsxRuntimeExports.jsx(
14736
+ "button",
14737
+ {
14738
+ type: "button",
14739
+ className: "grain-connection-secondary grain-connection-edit",
14740
+ onClick: () => setDisplayMode("full"),
14741
+ children: "Edit details"
14742
+ }
14743
+ ),
14693
14744
  selected && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
14694
- /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14745
+ displayMode === "full" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14695
14746
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Connection details" }),
14696
14747
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grain-connection-grid", children: entries.filter(([, field]) => !field.ui?.advanced).map(renderField) })
14748
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14749
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Connection details" }),
14750
+ /* @__PURE__ */ jsxRuntimeExports.jsx("dl", { className: "grain-connection-readonly", children: entries.map(([key, field]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
14751
+ /* @__PURE__ */ jsxRuntimeExports.jsx("dt", { children: field.title ?? key }),
14752
+ /* @__PURE__ */ jsxRuntimeExports.jsx("dd", { children: String(values[key] ?? field.default ?? "") })
14753
+ ] }, key)) })
14697
14754
  ] }),
14698
- passwordField && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14755
+ (passwordField || actions2 && displayMode === "password") && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "grain-connection-section", children: [
14699
14756
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Authentication" }),
14700
14757
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grain-connection-grid", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-field", children: [
14701
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "grain-connection-password", children: passwordField.title ?? "Password" }),
14758
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "grain-connection-password", children: passwordField?.title ?? "Password" }),
14702
14759
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-password", children: [
14703
14760
  /* @__PURE__ */ jsxRuntimeExports.jsx(
14704
14761
  "input",
@@ -14723,13 +14780,16 @@ function ConnectionSetupPage({
14723
14780
  /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Stored on this computer after connecting." })
14724
14781
  ] }) })
14725
14782
  ] }),
14726
- entries.some(([, field]) => field.ui?.advanced) && /* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "grain-connection-advanced", children: [
14783
+ displayMode === "full" && entries.some(([, field]) => field.ui?.advanced) && /* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "grain-connection-advanced", children: [
14727
14784
  /* @__PURE__ */ jsxRuntimeExports.jsx("summary", { children: "Advanced settings" }),
14728
14785
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grain-connection-grid", children: entries.filter(([, field]) => field.ui?.advanced).map(renderField) })
14729
14786
  ] })
14730
14787
  ] }),
14731
14788
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-footer", children: [
14732
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Grain will test the connection before saving it." }),
14789
+ tested && !error ? /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "grain-connection-success", children: [
14790
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: "✓ " }),
14791
+ "Connection test successful."
14792
+ ] }) : error ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "grain-connection-footer-error", children: error }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Grain will test the connection before saving it." }),
14733
14793
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grain-connection-actions", children: [
14734
14794
  /* @__PURE__ */ jsxRuntimeExports.jsx(
14735
14795
  "button",
@@ -75059,6 +75119,10 @@ const DENSITY_HEIGHTS = {
75059
75119
  normal: { row: 35, header: 42 },
75060
75120
  comfortable: { row: 45, header: 52 }
75061
75121
  };
75122
+ const NUMERIC_TYPE = /^(unsigned\s+|signed\s+)?(u?(tiny|small|medium|big|huge)?int(eger)?\d*|decimal|numeric|number|double|float\d*|real|long|short|byte|money)(\s+(precision|unsigned|signed))?\s*(\(\s*\d+\s*(,\s*\d+\s*)?\))?$/i;
75123
+ function isNumericColumnType(type) {
75124
+ return Boolean(type && NUMERIC_TYPE.test(type.trim()));
75125
+ }
75062
75126
  const DataGrid = reactExports.forwardRef(function DataGrid2({
75063
75127
  theme: theme2,
75064
75128
  data,
@@ -75293,6 +75357,7 @@ const DataGrid = reactExports.forwardRef(function DataGrid2({
75293
75357
  return p.data[col.name];
75294
75358
  return void 0;
75295
75359
  },
75360
+ ...isNumericColumnType(col.type) ? { cellClass: "sp-num-cell", headerClass: "ag-right-aligned-header" } : {},
75296
75361
  cellClassRules: {
75297
75362
  "sp-range-cell": (p) => isInRange(p.rowIndex ?? 0, index + 1)
75298
75363
  },
@@ -149247,6 +149312,7 @@ function getFormInitialData(connection) {
149247
149312
  delete formData["type"];
149248
149313
  delete formData["builtIn"];
149249
149314
  delete formData["hasPassword"];
149315
+ delete formData["draft"];
149250
149316
  return formData;
149251
149317
  }
149252
149318
  function schemaHasPasswordField(schema) {
@@ -149285,9 +149351,10 @@ function schemaForConnectionMode(schema, connectorId, mode) {
149285
149351
  }
149286
149352
  };
149287
149353
  }
149354
+ const UNREACHABLE_MESSAGE = "Can't reach Grain. Start Grain and try again.";
149288
149355
  const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149289
149356
  const { callServerTool, isReady, mode, sendMessage, onMessage } = useHostContext();
149290
- const grainServiceInstalled = useAppStore((state) => state.grainServiceInstalled);
149357
+ const grainServiceSetup = useAppStore((state) => state.grainServiceSetup);
149291
149358
  const supportsActiveConnection = mode === "vscode";
149292
149359
  const trackUi = useTrackUi();
149293
149360
  reactExports.useEffect(() => {
@@ -149519,7 +149586,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149519
149586
  const profileToSave = {
149520
149587
  ...formData,
149521
149588
  type: typeToSave,
149522
- id: connection?.id ?? `conn_${Date.now()}`
149589
+ id: connection?.id ?? `conn_${Date.now()}`,
149590
+ ...connection?.draft ? { draft: false } : {}
149523
149591
  };
149524
149592
  if (isEditing2 && profileToSave["password"] === "") {
149525
149593
  delete profileToSave["password"];
@@ -149795,10 +149863,30 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149795
149863
  }
149796
149864
  return null;
149797
149865
  };
149866
+ const renderUnreachableActions = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginTop: 12, display: "flex", gap: 8 }, children: [
149867
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: fetchData, children: "Retry" }),
149868
+ mode === "vscode" && /* @__PURE__ */ jsxRuntimeExports.jsx(
149869
+ "button",
149870
+ {
149871
+ className: "sp-btn",
149872
+ onClick: () => sendMessage?.(grainServiceSetup === "not-set-up" ? "setUpGrain" : "startGrain"),
149873
+ children: grainServiceSetup === "not-set-up" ? "Set up Grain" : "Start Grain"
149874
+ }
149875
+ )
149876
+ ] });
149877
+ const renderUnreachableState = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-empty", children: [
149878
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: UNREACHABLE_MESSAGE }),
149879
+ renderUnreachableActions()
149880
+ ] });
149798
149881
  const renderConnectionForm = () => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-detail", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-detail-body", children: [
149799
149882
  renderToast(),
149800
- !selectedType && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-conn-form-section", children: "Database Type" }),
149801
- !selectedType && ["local", "server"].map((group) => {
149883
+ !selectedType && connectors.length === 0 && connectionsUnreachable && renderUnreachableState(),
149884
+ !selectedType && connectors.length === 0 && !connectionsUnreachable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-empty", children: [
149885
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "No database types are available." }),
149886
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: fetchData, children: "Retry" }) })
149887
+ ] }),
149888
+ !selectedType && connectors.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-conn-form-section", children: "Database Type" }),
149889
+ !selectedType && connectors.length > 0 && ["local", "server"].map((group) => {
149802
149890
  const groupConnectors = connectors.filter((c) => getConnectorGroup(c.id) === group);
149803
149891
  if (groupConnectors.length === 0) {
149804
149892
  return null;
@@ -149975,11 +150063,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
149975
150063
  gap: "12px"
149976
150064
  },
149977
150065
  children: connections.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-conn-empty", style: { gridColumn: "1 / -1" }, children: [
149978
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: connectionsUnreachable ? "Can't reach Grain. Start Grain and try again." : "No connections configured." }),
149979
- connectionsUnreachable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginTop: 12, display: "flex", gap: 8 }, children: [
149980
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: fetchData, children: "Retry" }),
149981
- mode === "vscode" && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "sp-btn", onClick: () => sendMessage?.("startGrain"), children: grainServiceInstalled ? "Start Grain" : "Set up Grain" })
149982
- ] }),
150066
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: connectionsUnreachable ? UNREACHABLE_MESSAGE : "No connections configured." }),
150067
+ connectionsUnreachable && renderUnreachableActions(),
149983
150068
  mode === "vscode" && !connectionsUnreachable && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
149984
150069
  "button",
149985
150070
  {
@@ -150056,7 +150141,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
150056
150141
  },
150057
150142
  children: [
150058
150143
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `sp-dot sp-dot-${status}` }),
150059
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--sp-muted-fg)" }, children: statusLabel }),
150144
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--sp-muted-fg)" }, children: c.draft ? "Needs setup" : statusLabel }),
150145
+ c.draft && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-pill", children: "Draft" }),
150060
150146
  isActive && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-pill", style: { marginLeft: "auto" }, children: "Active" })
150061
150147
  ]
150062
150148
  }
@@ -150080,7 +150166,8 @@ const ConnectionsManager = ({ initialAdd = false, onSubpageChange }) => {
150080
150166
  meta: selectedConnection && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
150081
150167
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-type-badge", children: selectedConnection.type }),
150082
150168
  supportsActiveConnection && activeConnectionId === selectedConnection.id && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-active-badge", children: "Active" }),
150083
- selectedConnection.builtIn && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-built-in-label", children: "Built-in" })
150169
+ selectedConnection.builtIn && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-built-in-label", children: "Built-in" }),
150170
+ selectedConnection.draft && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-conn-built-in-label", children: "Draft" })
150084
150171
  ] }),
150085
150172
  actions: isFormOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "ghost", onClick: closeForm, "aria-label": "Cancel", children: "Cancel" }) : selectedConnection && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
150086
150173
  supportsActiveConnection && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -150251,6 +150338,10 @@ const TOOL_ENTRIES = {
150251
150338
  list_connections: ["connections", "Show saved connection profiles."],
150252
150339
  get_demo_dataset: ["connections", "Offer the built-in bookstore demo data and sample queries."],
150253
150340
  save_connection: ["connections", "Create or update a connection profile."],
150341
+ create_connection_draft: [
150342
+ "connections",
150343
+ "Create a draft connection and give the user a setup link."
150344
+ ],
150254
150345
  request_connection_credentials: ["connections", "Ask the user to enter a connection password."],
150255
150346
  test_connection: ["connections", "Verify a saved or draft profile."],
150256
150347
  delete_connection: ["connections", "Remove a saved profile."],
@@ -150758,13 +150849,13 @@ function SettingsPanel({ onSave, onClose, onShowWelcome, initialTab = "general"
150758
150849
  title: "This daemon can run standalone or inside VS Code; check the releases page for the latest version.",
150759
150850
  children: "View Releases"
150760
150851
  }
150761
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
150852
+ ) : updateAvailable && /* @__PURE__ */ jsxRuntimeExports.jsx(
150762
150853
  "button",
150763
150854
  {
150764
150855
  type: "button",
150765
150856
  className: "sp-btn",
150766
150857
  onClick: () => sendMessage("openExtensionPage", {}),
150767
- children: updateAvailable ? "Open Update" : "Open Extension"
150858
+ children: "Open Update"
150768
150859
  }
150769
150860
  )
150770
150861
  ] }),
@@ -151423,7 +151514,7 @@ const LandingScreen = ({
151423
151514
  activeConnectionName: storeConnName,
151424
151515
  activeConnectorType: storeConnType,
151425
151516
  openSettingsPanel,
151426
- grainServiceInstalled
151517
+ grainServiceSetup
151427
151518
  } = useAppStore();
151428
151519
  const [fetchedConnName, setFetchedConnName] = reactExports.useState(null);
151429
151520
  const [fetchedConnType, setFetchedConnType] = reactExports.useState(null);
@@ -151498,9 +151589,13 @@ const LandingScreen = ({
151498
151589
  ] }),
151499
151590
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-landing-subtitle", children: "Execute SQL queries directly in your IDE." })
151500
151591
  ] }),
151501
- !hasActiveConnection && grainUnavailable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-landing-active-connection", children: [
151502
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: grainServiceInstalled ? "Grain is stopped. Start Grain to see your connections." : "Grain is not set up yet. Setup installs the Grain service once." }),
151503
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-btn", onClick: () => sendMessage("startGrain"), children: grainServiceInstalled ? "Start Grain" : "Set up Grain" })
151592
+ grainServiceSetup === "not-set-up" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-landing-active-connection", children: [
151593
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Grain's background service isn't set up. Set it up once so your agents and the grain CLI can reach Grain when VS Code is closed." }),
151594
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-btn", onClick: () => sendMessage("setUpGrain"), children: "Set up Grain" })
151595
+ ] }),
151596
+ grainServiceSetup !== "not-set-up" && !hasActiveConnection && grainUnavailable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-landing-active-connection", children: [
151597
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: grainServiceSetup === "registered" ? "Grain is stopped. Start Grain to see your connections." : "Grain starts when needed on this machine." }),
151598
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-btn", onClick: () => sendMessage("startGrain"), children: "Start Grain" })
151504
151599
  ] }),
151505
151600
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
151506
151601
  "div",
@@ -151630,7 +151725,7 @@ function SafeModeIndicator({
151630
151725
  capability
151631
151726
  ] });
151632
151727
  }
151633
- function MultiTabLayout({ emptyState } = {}) {
151728
+ function MultiTabLayout({ emptyState, fillGridWidth = false } = {}) {
151634
151729
  const {
151635
151730
  tabs,
151636
151731
  activeTabId,
@@ -152319,6 +152414,7 @@ ${activeTab.errorDetails}` : activeTab.error;
152319
152414
  data: activeTab.result,
152320
152415
  copyIncludesHeaders,
152321
152416
  density: rowDensity,
152417
+ fillWidth: fillGridWidth,
152322
152418
  onCellDoubleClick: handleCellDoubleClick
152323
152419
  }
152324
152420
  )
@@ -152332,6 +152428,7 @@ ${activeTab.errorDetails}` : activeTab.error;
152332
152428
  data: activeTab.result,
152333
152429
  copyIncludesHeaders,
152334
152430
  density: rowDensity,
152431
+ fillWidth: fillGridWidth,
152335
152432
  onCellDoubleClick: handleCellDoubleClick
152336
152433
  }
152337
152434
  ),
@@ -152393,12 +152490,254 @@ ${activeTab.errorDetails}` : activeTab.error ?? "",
152393
152490
  )
152394
152491
  ] });
152395
152492
  }
152493
+ function SidebarAddButton({ label, title, onClick }) {
152494
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
152495
+ "button",
152496
+ {
152497
+ type: "button",
152498
+ className: "sp-web-section-add",
152499
+ onClick,
152500
+ title,
152501
+ "aria-label": label,
152502
+ children: "+"
152503
+ }
152504
+ );
152505
+ }
152506
+ function SqlEditor({ value, onChange, onRun, readOnly = false }) {
152507
+ const frameRef = reactExports.useRef(null);
152508
+ const gutterRef = reactExports.useRef(null);
152509
+ const lineCount = value.split("\n").length;
152510
+ const digits = Math.max(2, String(lineCount).length);
152511
+ reactExports.useLayoutEffect(() => {
152512
+ frameRef.current?.style.setProperty("--sp-web-gutter-digits", String(digits));
152513
+ }, [digits]);
152514
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: frameRef, className: "sp-web-sql-editor-frame", children: [
152515
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
152516
+ "div",
152517
+ {
152518
+ ref: gutterRef,
152519
+ className: "sp-web-sql-gutter",
152520
+ "aria-hidden": "true",
152521
+ "data-testid": "sql-gutter",
152522
+ children: Array.from({ length: lineCount }, (_, index) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: index + 1 }, index))
152523
+ }
152524
+ ),
152525
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
152526
+ "textarea",
152527
+ {
152528
+ className: "sp-web-sql-editor",
152529
+ "aria-label": "SQL editor",
152530
+ spellCheck: false,
152531
+ wrap: "off",
152532
+ value,
152533
+ readOnly,
152534
+ onChange: (event) => onChange(event.target.value),
152535
+ onScroll: (event) => {
152536
+ if (gutterRef.current) gutterRef.current.scrollTop = event.currentTarget.scrollTop;
152537
+ },
152538
+ onKeyDown: (event) => {
152539
+ if (!readOnly && (event.metaKey || event.ctrlKey) && event.key === "Enter") {
152540
+ event.preventDefault();
152541
+ onRun();
152542
+ }
152543
+ }
152544
+ }
152545
+ )
152546
+ ] });
152547
+ }
152548
+ function displayQueryTitle(title, sql) {
152549
+ if (title.trim() && !/^Result \d+$/.test(title.trim())) return title;
152550
+ const line = sql.replace(/\/\*[\s\S]*?\*\//g, "").split("\n").map((part) => part.trim()).find((part) => part && !part.startsWith("--"))?.replace(/\s+/g, " ");
152551
+ if (!line) return title.trim() || "Query";
152552
+ return line.length > 40 ? `${line.slice(0, 40).trimEnd()}…` : line;
152553
+ }
152554
+ function resultText(result, fallback) {
152555
+ return result.content?.find((item) => item.type === "text")?.text ?? fallback;
152556
+ }
152557
+ function failed(result) {
152558
+ return Boolean(result.isError) || result.structuredContent?.["success"] === false;
152559
+ }
152560
+ function ConnectionPage({
152561
+ connection,
152562
+ onNewQuery,
152563
+ onEdit,
152564
+ onDeleted
152565
+ }) {
152566
+ const { callServerTool } = useHostContext();
152567
+ const [test, setTest] = reactExports.useState(null);
152568
+ const [confirmingDelete, setConfirmingDelete] = reactExports.useState(false);
152569
+ const [deleteError, setDeleteError] = reactExports.useState("");
152570
+ const label = connection.name ?? connection.id;
152571
+ const runTest = async () => {
152572
+ setTest({ kind: "testing" });
152573
+ try {
152574
+ const result = await callServerTool("test_connection", { connectionId: connection.id });
152575
+ setTest(
152576
+ failed(result) ? { kind: "failed", message: resultText(result, "Connection test failed") } : { kind: "passed" }
152577
+ );
152578
+ } catch (error) {
152579
+ setTest({
152580
+ kind: "failed",
152581
+ message: error instanceof Error ? error.message : "Connection test failed"
152582
+ });
152583
+ }
152584
+ };
152585
+ const confirmDelete = async () => {
152586
+ setDeleteError("");
152587
+ try {
152588
+ const result = await callServerTool("delete_connection", { connectionId: connection.id });
152589
+ if (failed(result)) throw new Error(resultText(result, "Could not delete connection"));
152590
+ onDeleted();
152591
+ } catch (error) {
152592
+ setDeleteError(error instanceof Error ? error.message : "Could not delete connection");
152593
+ }
152594
+ };
152595
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-connection-detail", children: [
152596
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: label }),
152597
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
152598
+ connection.type ?? "Database",
152599
+ " ·",
152600
+ " ",
152601
+ connection.engineState === "ready" ? "Ready" : "Saved connection"
152602
+ ] }),
152603
+ test?.kind === "testing" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { role: "status", children: "Testing…" }),
152604
+ test?.kind === "passed" && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "sp-web-connection-test-success", role: "status", children: [
152605
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: "✓ " }),
152606
+ "Connection test successful."
152607
+ ] }),
152608
+ test?.kind === "failed" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-connection-test-error", role: "alert", children: test.message }),
152609
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-connection-actions", children: [
152610
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
152611
+ "button",
152612
+ {
152613
+ type: "button",
152614
+ className: "sp-web-primary-button",
152615
+ "aria-label": `New query for ${label}`,
152616
+ onClick: onNewQuery,
152617
+ children: "New query"
152618
+ }
152619
+ ),
152620
+ !connection.builtIn && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-web-secondary-button", onClick: onEdit, children: "Edit" }),
152621
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-web-secondary-button", onClick: () => void runTest(), children: "Test" }),
152622
+ !connection.builtIn && /* @__PURE__ */ jsxRuntimeExports.jsx(
152623
+ "button",
152624
+ {
152625
+ type: "button",
152626
+ className: "sp-web-secondary-button",
152627
+ onClick: () => {
152628
+ setDeleteError("");
152629
+ setConfirmingDelete(true);
152630
+ },
152631
+ children: "Delete"
152632
+ }
152633
+ )
152634
+ ] }),
152635
+ confirmingDelete && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-connection-delete-confirm", children: [
152636
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
152637
+ "Delete '",
152638
+ label,
152639
+ "'? Its stored password is removed too."
152640
+ ] }),
152641
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
152642
+ "button",
152643
+ {
152644
+ type: "button",
152645
+ className: "sp-web-secondary-button",
152646
+ onClick: () => void confirmDelete(),
152647
+ children: "Delete"
152648
+ }
152649
+ ),
152650
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
152651
+ "button",
152652
+ {
152653
+ type: "button",
152654
+ className: "sp-web-secondary-button",
152655
+ onClick: () => {
152656
+ setConfirmingDelete(false);
152657
+ setDeleteError("");
152658
+ },
152659
+ children: "Cancel"
152660
+ }
152661
+ )
152662
+ ] }),
152663
+ deleteError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-connection-test-error", role: "alert", children: deleteError })
152664
+ ] });
152665
+ }
152666
+ function AgentActivity({
152667
+ tabs,
152668
+ activeTabId,
152669
+ onSelect
152670
+ }) {
152671
+ const [collapsed, setCollapsed] = reactExports.useState([]);
152672
+ const sessions = Array.from(
152673
+ new Set(
152674
+ tabs.flatMap(
152675
+ (tab) => tab.sessionId && tab.sessionId !== WEB_SESSION_ID ? [tab.sessionId] : []
152676
+ )
152677
+ )
152678
+ );
152679
+ if (sessions.length === 0) return null;
152680
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
152681
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-sidebar-section-title", children: "Agent activity" }),
152682
+ sessions.map((sessionId) => {
152683
+ const sessionTabs = tabs.filter((tab) => tab.sessionId === sessionId);
152684
+ const name = sessionTabs[0]?.sessionDisplayName || sessionTabs[0]?.clientType || "Agent";
152685
+ const isCollapsed = collapsed.includes(sessionId);
152686
+ const groupId = `sp-web-agent-${sessionId.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
152687
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
152688
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
152689
+ "button",
152690
+ {
152691
+ type: "button",
152692
+ className: "sp-web-agent-session-toggle",
152693
+ "aria-expanded": !isCollapsed,
152694
+ "aria-controls": groupId,
152695
+ onClick: () => setCollapsed(
152696
+ (current2) => isCollapsed ? current2.filter((id) => id !== sessionId) : [...current2, sessionId]
152697
+ ),
152698
+ children: [
152699
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: isCollapsed ? "▸" : "▾" }),
152700
+ " ",
152701
+ name
152702
+ ]
152703
+ }
152704
+ ),
152705
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { id: groupId, hidden: isCollapsed, children: sessionTabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
152706
+ "button",
152707
+ {
152708
+ type: "button",
152709
+ className: `sp-web-session-item sp-web-session-button${activeTabId === tab.id ? " active" : ""}`,
152710
+ "aria-pressed": activeTabId === tab.id,
152711
+ onClick: () => onSelect(tab.id),
152712
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-session-name", children: displayQueryTitle(tab.title, tab.sql) })
152713
+ },
152714
+ tab.id
152715
+ )) })
152716
+ ] }, sessionId);
152717
+ })
152718
+ ] });
152719
+ }
152720
+ function AgentQueryHeading({
152721
+ tab,
152722
+ connectionName
152723
+ }) {
152724
+ const ranAt = tab.result?.executedAt && new Date(tab.result.executedAt).toLocaleTimeString([], {
152725
+ hour: "2-digit",
152726
+ minute: "2-digit",
152727
+ hour12: false
152728
+ });
152729
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
152730
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: displayQueryTitle(tab.title, tab.sql) }),
152731
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-query-provenance", children: [tab.sessionDisplayName || tab.clientType, connectionName, ranAt].filter(Boolean).join(" · ") })
152732
+ ] });
152733
+ }
152396
152734
  const THEME_KEY = "grain:web-theme";
152397
152735
  const SIDEBAR_DEFAULT_WIDTH = 220;
152398
152736
  const SIDEBAR_MIN_WIDTH = 160;
152399
152737
  const SIDEBAR_MAX_WIDTH = 400;
152400
152738
  const SIDEBAR_AUTO_COLLAPSE_QUERY = "(max-width: 640px)";
152401
152739
  const SIDEBAR_WIDTH_STORAGE_KEY = "sql-preview:web-sidebar-width";
152740
+ const CONNECTIONS_COLLAPSED_KEY = "grain:web-connections-collapsed";
152402
152741
  function WebApp({ viewHint }) {
152403
152742
  const { onMessage, isReady, sendMessage, callServerTool, track } = useHostContext();
152404
152743
  const session = useWebSession();
@@ -152409,6 +152748,7 @@ function WebApp({ viewHint }) {
152409
152748
  setActiveTab,
152410
152749
  setTheme,
152411
152750
  openSettingsPanel,
152751
+ closeSettingsPanel,
152412
152752
  tabs,
152413
152753
  activeTabId,
152414
152754
  settingsPanelTab
@@ -152416,22 +152756,28 @@ function WebApp({ viewHint }) {
152416
152756
  const [connections, setConnections] = reactExports.useState([]);
152417
152757
  const [setupOpen, setSetupOpen] = reactExports.useState(false);
152418
152758
  const [editingConnection, setEditingConnection] = reactExports.useState(null);
152419
- const [openConnectionIds, setOpenConnectionIds] = reactExports.useState([]);
152759
+ const [connectionsCollapsed, setConnectionsCollapsed] = reactExports.useState(
152760
+ () => readStoredValue(CONNECTIONS_COLLAPSED_KEY) === "true"
152761
+ );
152420
152762
  const [activeConnectionId, setActiveConnectionId] = reactExports.useState(null);
152421
- const [connectionStatus, setConnectionStatus] = reactExports.useState({});
152422
152763
  const [descriptions, setDescriptions] = reactExports.useState({});
152423
152764
  const [queryConnections, setQueryConnections] = reactExports.useState({});
152424
152765
  const [closedQueries, setClosedQueries] = reactExports.useState([]);
152425
152766
  const activeTab = tabs.find((tab) => tab.id === activeTabId);
152426
152767
  const localConnection = connections.find((connection) => connection.id === "built-in-duckdb");
152427
- const selectedConnectionId = activeTab ? queryConnections[activeTab.id] ?? localConnection?.id ?? connections[0]?.id ?? "" : "";
152768
+ const isAgentTab = Boolean(activeTab?.sessionId && activeTab.sessionId !== WEB_SESSION_ID);
152769
+ const selectedConnectionId = activeTab ? isAgentTab ? activeTab.connectionId ?? "" : queryConnections[activeTab.id] ?? localConnection?.id ?? connections[0]?.id ?? "" : "";
152770
+ const activeConnectionName = activeTab ? activeTab.connectionName ?? connections.find((connection) => connection.id === activeTab.connectionId)?.name ?? (activeTab.connectionId === "unknown" ? void 0 : activeTab.connectionId) : void 0;
152428
152771
  reactExports.useEffect(() => {
152429
152772
  if (isReady) track?.("ui_state_shown", { state: "workspace_ready" });
152430
152773
  }, [isReady, track]);
152431
- const newQuery = (connectionId = "built-in-duckdb") => {
152774
+ const newQuery = (connectionId = "built-in-duckdb", sql = "SELECT 42;") => {
152775
+ setSetupOpen(false);
152776
+ setEditingConnection(null);
152777
+ closeSettingsPanel();
152432
152778
  const remoteId = crypto.randomUUID();
152433
152779
  const id = addTab(
152434
- "SELECT 42;",
152780
+ sql,
152435
152781
  createProjectedTabId(WEB_SESSION_ID, remoteId),
152436
152782
  tabs.some((tab) => tab.title === "Untitled query") ? `Untitled query ${tabs.filter((tab) => tab.title.startsWith("Untitled query")).length + 1}` : "Untitled query",
152437
152783
  void 0,
@@ -152440,26 +152786,14 @@ function WebApp({ viewHint }) {
152440
152786
  setQueryConnections((current2) => ({ ...current2, [id]: connectionId }));
152441
152787
  setActiveConnectionId(null);
152442
152788
  };
152443
- const openConnection = (id) => {
152444
- setOpenConnectionIds((current2) => current2.includes(id) ? current2 : [...current2, id]);
152445
- setActiveConnectionId(id);
152446
- };
152447
- const testConnection = async (id) => {
152448
- setConnectionStatus((current2) => ({ ...current2, [id]: "Testing…" }));
152449
- try {
152450
- const result = await callServerTool("test_connection", { connectionId: id });
152451
- const message = result.content?.find((item) => item.type === "text")?.text ?? "Connection test failed";
152452
- setConnectionStatus((current2) => ({
152453
- ...current2,
152454
- [id]: result.isError || result.structuredContent?.["success"] === false ? message : "Connected"
152455
- }));
152456
- } catch (error) {
152457
- setConnectionStatus((current2) => ({
152458
- ...current2,
152459
- [id]: error instanceof Error ? error.message : "Connection test failed"
152460
- }));
152461
- }
152789
+ const showQuery = (tabId) => {
152790
+ setSetupOpen(false);
152791
+ setEditingConnection(null);
152792
+ closeSettingsPanel();
152793
+ setActiveConnectionId(null);
152794
+ setActiveTab(tabId);
152462
152795
  };
152796
+ const openConnection = (id) => setActiveConnectionId(id);
152463
152797
  const closeQuery = (id) => {
152464
152798
  const tab = tabs.find((item) => item.id === id);
152465
152799
  if (tab) {
@@ -152469,7 +152803,7 @@ function WebApp({ viewHint }) {
152469
152803
  sql: tab.sql,
152470
152804
  title: tab.title,
152471
152805
  description: descriptions[id] ?? "",
152472
- connectionId: queryConnections[id] ?? "built-in-duckdb"
152806
+ connectionId: tab.sessionId && tab.sessionId !== WEB_SESSION_ID ? tab.connectionId ?? "" : queryConnections[id] ?? "built-in-duckdb"
152473
152807
  }
152474
152808
  ]);
152475
152809
  if (tab.result || tab.isLoading) {
@@ -152495,14 +152829,15 @@ function WebApp({ viewHint }) {
152495
152829
  };
152496
152830
  const runQuery = async () => {
152497
152831
  if (!activeTab || !activeTab.sql.trim() || !selectedConnectionId || activeTab.isLoading) return;
152498
- const { id, sql } = activeTab;
152499
- updateTab(id, { isLoading: true, error: null, cancelled: false });
152832
+ const { id, sql, title } = activeTab;
152833
+ updateTab(id, { isLoading: true, error: null, result: null, cancelled: false });
152500
152834
  try {
152501
152835
  const result = await callServerTool("run_query", {
152502
152836
  sql,
152503
152837
  session: WEB_SESSION_ID,
152504
152838
  tabId: activeTab.remoteId,
152505
- connectionId: selectedConnectionId
152839
+ connectionId: selectedConnectionId,
152840
+ title
152506
152841
  });
152507
152842
  const data = result.structuredContent;
152508
152843
  if (useAppStore.getState().tabs.find((tab) => tab.id === id)?.cancelled) return;
@@ -152661,6 +152996,8 @@ function WebApp({ viewHint }) {
152661
152996
  remoteId: msg.remoteId,
152662
152997
  sessionDisplayName: msg.sessionDisplayName,
152663
152998
  clientType: msg.clientType,
152999
+ connectionId: msg.connectionId,
153000
+ connectionName: msg.connectionName,
152664
153001
  isRemote: msg.isRemote
152665
153002
  },
152666
153003
  !msg.preserveFocus
@@ -152682,6 +153019,8 @@ function WebApp({ viewHint }) {
152682
153019
  remoteId: msg.remoteId,
152683
153020
  sessionDisplayName: msg.sessionDisplayName,
152684
153021
  clientType: msg.clientType,
153022
+ connectionId: msg.connectionId,
153023
+ connectionName: msg.connectionName,
152685
153024
  isRemote: msg.isRemote
152686
153025
  },
152687
153026
  !msg.preserveFocus
@@ -152694,6 +153033,8 @@ function WebApp({ viewHint }) {
152694
153033
  remoteId: msg.remoteId,
152695
153034
  sessionDisplayName: msg.sessionDisplayName,
152696
153035
  clientType: msg.clientType,
153036
+ connectionId: msg.connectionId,
153037
+ connectionName: msg.connectionName,
152697
153038
  isRemote: msg.isRemote
152698
153039
  });
152699
153040
  if (!msg.preserveFocus || !currentActiveId) {
@@ -152712,6 +153053,8 @@ function WebApp({ viewHint }) {
152712
153053
  remoteId: msg.remoteId,
152713
153054
  sessionDisplayName: msg.sessionDisplayName,
152714
153055
  clientType: msg.clientType,
153056
+ connectionId: msg.connectionId,
153057
+ connectionName: msg.connectionName,
152715
153058
  isRemote: msg.isRemote
152716
153059
  },
152717
153060
  false
@@ -152724,6 +153067,8 @@ function WebApp({ viewHint }) {
152724
153067
  remoteId: msg.remoteId,
152725
153068
  sessionDisplayName: msg.sessionDisplayName,
152726
153069
  clientType: msg.clientType,
153070
+ connectionId: msg.connectionId,
153071
+ connectionName: msg.connectionName,
152727
153072
  isRemote: msg.isRemote,
152728
153073
  result: {
152729
153074
  query: msg.data.query,
@@ -152731,7 +153076,7 @@ function WebApp({ viewHint }) {
152731
153076
  rows: msg.data.rows,
152732
153077
  rowCount: msg.data.totalRowsInFirstBatch ?? msg.data.rows.length,
152733
153078
  executionTime: msg.data.executionTimeMs,
152734
- connection: "Web",
153079
+ connection: msg.connectionName ?? msg.connectionId ?? "Connection unknown",
152735
153080
  wasTruncated: msg.data.wasTruncated,
152736
153081
  executedAt: msg.data.executedAt
152737
153082
  }
@@ -152749,6 +153094,8 @@ function WebApp({ viewHint }) {
152749
153094
  remoteId: msg.remoteId,
152750
153095
  sessionDisplayName: msg.sessionDisplayName,
152751
153096
  clientType: msg.clientType,
153097
+ connectionId: msg.connectionId,
153098
+ connectionName: msg.connectionName,
152752
153099
  isRemote: msg.isRemote
152753
153100
  },
152754
153101
  false
@@ -152761,6 +153108,8 @@ function WebApp({ viewHint }) {
152761
153108
  remoteId: msg.remoteId,
152762
153109
  sessionDisplayName: msg.sessionDisplayName,
152763
153110
  clientType: msg.clientType,
153111
+ connectionId: msg.connectionId,
153112
+ connectionName: msg.connectionName,
152764
153113
  isRemote: msg.isRemote
152765
153114
  });
152766
153115
  break;
@@ -152868,7 +153217,33 @@ function WebApp({ viewHint }) {
152868
153217
  ] }),
152869
153218
  monitorOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { ref: sidebarRef, className: "sp-web-sidebar", "aria-label": "Workspace navigation", children: [
152870
153219
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-sidebar-header", children: [
152871
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-sidebar-title", children: "Connections" }),
153220
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
153221
+ "button",
153222
+ {
153223
+ type: "button",
153224
+ className: "sp-web-sidebar-title sp-web-section-toggle",
153225
+ "aria-expanded": !connectionsCollapsed,
153226
+ "aria-controls": "sp-web-connections",
153227
+ onClick: () => {
153228
+ setConnectionsCollapsed((current2) => {
153229
+ writeStoredValue(CONNECTIONS_COLLAPSED_KEY, String(!current2));
153230
+ return !current2;
153231
+ });
153232
+ },
153233
+ children: [
153234
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { "aria-hidden": "true", children: connectionsCollapsed ? "▸" : "▾" }),
153235
+ " Connections"
153236
+ ]
153237
+ }
153238
+ ),
153239
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
153240
+ SidebarAddButton,
153241
+ {
153242
+ label: "Add connection",
153243
+ title: "Add connection",
153244
+ onClick: () => setSetupOpen(true)
153245
+ }
153246
+ ),
152872
153247
  /* @__PURE__ */ jsxRuntimeExports.jsx(
152873
153248
  "button",
152874
153249
  {
@@ -152877,7 +153252,7 @@ function WebApp({ viewHint }) {
152877
153252
  onClick: () => setMonitorOpen(false),
152878
153253
  title: "Hide sidebar",
152879
153254
  "aria-label": "Hide sidebar",
152880
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, { size: 14 })
153255
+ children: "«"
152881
153256
  }
152882
153257
  )
152883
153258
  ] }),
@@ -152894,68 +153269,55 @@ function WebApp({ viewHint }) {
152894
153269
  "aria-valuemax": SIDEBAR_MAX_WIDTH
152895
153270
  }
152896
153271
  ),
152897
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-connection-list", children: [
152898
- connections.length ? connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
152899
- "button",
152900
- {
152901
- type: "button",
152902
- className: "sp-web-connection-item",
152903
- onClick: () => openConnection(connection.id),
152904
- children: [
152905
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-connection-icon", "aria-hidden": "true", children: "▤" }),
152906
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
152907
- connection.name ?? connection.id,
152908
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: connection.type })
152909
- ] })
152910
- ]
152911
- },
152912
- connection.id
152913
- )) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-sidebar-helper", children: "No connections yet." }),
152914
- /* @__PURE__ */ jsxRuntimeExports.jsx(
152915
- "button",
152916
- {
152917
- type: "button",
152918
- className: "sp-web-add-connection",
152919
- onClick: () => setSetupOpen(true),
152920
- children: "+ Add connection"
152921
- }
152922
- )
153272
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
153273
+ "div",
153274
+ {
153275
+ id: "sp-web-connections",
153276
+ className: "sp-web-connection-list",
153277
+ hidden: connectionsCollapsed,
153278
+ children: connections.length ? connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
153279
+ "button",
153280
+ {
153281
+ type: "button",
153282
+ className: "sp-web-connection-item",
153283
+ "aria-current": activeConnectionId === connection.id ? "page" : void 0,
153284
+ onClick: () => openConnection(connection.id),
153285
+ children: [
153286
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-connection-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DatabaseIcon, { size: 15 }) }),
153287
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "sp-web-connection-label", children: [
153288
+ connection.name ?? connection.id,
153289
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: connection.type })
153290
+ ] }),
153291
+ connection.engineState === "ready" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
153292
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-connection-dot", "aria-hidden": "true" }),
153293
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-visually-hidden", children: "Ready" })
153294
+ ] })
153295
+ ]
153296
+ },
153297
+ connection.id
153298
+ )) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "sp-web-sidebar-helper", children: "No connections yet." })
153299
+ }
153300
+ ),
153301
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-sidebar-section-title", children: [
153302
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "My queries" }),
153303
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SidebarAddButton, { label: "Add query", title: "New query", onClick: () => newQuery() })
152923
153304
  ] }),
152924
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-sidebar-section-title", children: "Queries" }),
152925
153305
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-session-list", children: [
152926
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-web-add-connection", onClick: () => newQuery(), children: "+ New query" }),
152927
153306
  closedQueries.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "sp-web-add-connection", onClick: reopenQuery, children: "Reopen closed query" }),
152928
- tabs.filter((tab) => tab.sessionId === WEB_SESSION_ID).map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
153307
+ tabs.filter((tab) => !tab.sessionId || tab.sessionId === WEB_SESSION_ID).map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
152929
153308
  "button",
152930
153309
  {
152931
153310
  type: "button",
152932
153311
  className: `sp-web-session-item sp-web-session-button${activeTabId === tab.id ? " active" : ""}`,
152933
153312
  "aria-pressed": activeTabId === tab.id,
152934
- onClick: () => {
152935
- setActiveTab(tab.id);
152936
- setActiveConnectionId(null);
152937
- },
152938
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-session-name", children: tab.title })
152939
- },
152940
- tab.id
152941
- )),
152942
- tabs.some((tab) => tab.sessionId !== WEB_SESSION_ID) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-session-meta sp-web-agent-heading", children: "Other sessions" }),
152943
- tabs.filter((tab) => tab.sessionId !== WEB_SESSION_ID).map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
152944
- "button",
152945
- {
152946
- type: "button",
152947
- className: `sp-web-session-item sp-web-session-button${activeTabId === tab.id ? " active" : ""}`,
152948
- "aria-pressed": activeTabId === tab.id,
152949
- onClick: () => {
152950
- setActiveTab(tab.id);
152951
- setActiveConnectionId(null);
152952
- },
152953
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-session-name", children: tab.title })
153313
+ onClick: () => showQuery(tab.id),
153314
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-session-name", children: displayQueryTitle(tab.title, tab.sql) })
152954
153315
  },
152955
153316
  tab.id
152956
153317
  )),
152957
153318
  tabs.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-empty-sessions", children: "No queries yet." })
152958
153319
  ] }),
153320
+ /* @__PURE__ */ jsxRuntimeExports.jsx(AgentActivity, { tabs, activeTabId, onSelect: showQuery }),
152959
153321
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-sidebar-footer", children: "⌂   All work stays on this computer." })
152960
153322
  ] }),
152961
153323
  setupOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-main", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -152992,41 +153354,6 @@ function WebApp({ viewHint }) {
152992
153354
  ) }),
152993
153355
  !settingsPanelTab && (activeTab || activeConnectionId) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-query-workspace", children: [
152994
153356
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-query-tabs", role: "tablist", "aria-label": "Open tabs", children: [
152995
- openConnectionIds.map((id) => {
152996
- const connection = connections.find((item) => item.id === id);
152997
- if (!connection) return null;
152998
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
152999
- "div",
153000
- {
153001
- className: `sp-web-query-tab${activeConnectionId === id ? " active" : ""}`,
153002
- children: [
153003
- /* @__PURE__ */ jsxRuntimeExports.jsx(
153004
- "button",
153005
- {
153006
- type: "button",
153007
- role: "tab",
153008
- "aria-selected": activeConnectionId === id,
153009
- onClick: () => setActiveConnectionId(id),
153010
- children: connection.name ?? id
153011
- }
153012
- ),
153013
- /* @__PURE__ */ jsxRuntimeExports.jsx(
153014
- "button",
153015
- {
153016
- type: "button",
153017
- "aria-label": `Close ${connection.name ?? id} tab`,
153018
- onClick: () => {
153019
- setOpenConnectionIds((current2) => current2.filter((item) => item !== id));
153020
- if (activeConnectionId === id) setActiveConnectionId(null);
153021
- },
153022
- children: "×"
153023
- }
153024
- )
153025
- ]
153026
- },
153027
- `connection-${id}`
153028
- );
153029
- }),
153030
153357
  tabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
153031
153358
  "div",
153032
153359
  {
@@ -153038,18 +153365,15 @@ function WebApp({ viewHint }) {
153038
153365
  type: "button",
153039
153366
  role: "tab",
153040
153367
  "aria-selected": tab.id === activeTabId && !activeConnectionId,
153041
- onClick: () => {
153042
- setActiveTab(tab.id);
153043
- setActiveConnectionId(null);
153044
- },
153045
- children: tab.title
153368
+ onClick: () => showQuery(tab.id),
153369
+ children: displayQueryTitle(tab.title, tab.sql)
153046
153370
  }
153047
153371
  ),
153048
153372
  /* @__PURE__ */ jsxRuntimeExports.jsx(
153049
153373
  "button",
153050
153374
  {
153051
153375
  type: "button",
153052
- "aria-label": `Close ${tab.title} tab`,
153376
+ "aria-label": `Close ${displayQueryTitle(tab.title, tab.sql)} tab`,
153053
153377
  onClick: () => closeQuery(tab.id),
153054
153378
  children: "×"
153055
153379
  }
@@ -153073,51 +153397,39 @@ function WebApp({ viewHint }) {
153073
153397
  activeConnectionId ? (() => {
153074
153398
  const connection = connections.find((item) => item.id === activeConnectionId);
153075
153399
  if (!connection) return null;
153076
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-connection-detail", children: [
153077
- /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: connection.name ?? connection.id }),
153078
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
153079
- connection.type ?? "Database",
153080
- " ·",
153081
- " ",
153082
- connectionStatus[connection.id] ?? (connection.engineState === "ready" ? "Ready" : "Saved connection")
153083
- ] }),
153084
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-connection-actions", children: [
153085
- /* @__PURE__ */ jsxRuntimeExports.jsx(
153086
- "button",
153087
- {
153088
- type: "button",
153089
- className: "sp-web-primary-button",
153090
- "aria-label": `New query for ${connection.name ?? connection.id}`,
153091
- onClick: () => newQuery(connection.id),
153092
- children: "New query"
153093
- }
153094
- ),
153095
- !connection.builtIn && /* @__PURE__ */ jsxRuntimeExports.jsx(
153096
- "button",
153097
- {
153098
- type: "button",
153099
- className: "sp-web-secondary-button",
153100
- onClick: () => {
153101
- setEditingConnection(connection);
153102
- setSetupOpen(true);
153103
- },
153104
- children: "Edit"
153105
- }
153106
- ),
153107
- /* @__PURE__ */ jsxRuntimeExports.jsx(
153108
- "button",
153109
- {
153110
- type: "button",
153111
- className: "sp-web-secondary-button",
153112
- onClick: () => void testConnection(connection.id),
153113
- children: "Test"
153114
- }
153115
- )
153116
- ] })
153117
- ] });
153400
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
153401
+ ConnectionPage,
153402
+ {
153403
+ connection,
153404
+ onNewQuery: () => newQuery(connection.id),
153405
+ onEdit: () => {
153406
+ setEditingConnection(connection);
153407
+ setSetupOpen(true);
153408
+ },
153409
+ onDeleted: () => {
153410
+ setActiveConnectionId(null);
153411
+ setQueryConnections(
153412
+ (current2) => Object.fromEntries(
153413
+ Object.entries(current2).map(([tabId, id]) => [
153414
+ tabId,
153415
+ id === connection.id ? "built-in-duckdb" : id
153416
+ ])
153417
+ )
153418
+ );
153419
+ void fetchConnections();
153420
+ }
153421
+ },
153422
+ connection.id
153423
+ );
153118
153424
  })() : activeTab && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
153119
153425
  " ",
153120
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-query-heading", children: [
153426
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-query-heading", children: isAgentTab ? /* @__PURE__ */ jsxRuntimeExports.jsx(
153427
+ AgentQueryHeading,
153428
+ {
153429
+ tab: activeTab,
153430
+ connectionName: activeConnectionName
153431
+ }
153432
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
153121
153433
  /* @__PURE__ */ jsxRuntimeExports.jsx(
153122
153434
  "input",
153123
153435
  {
@@ -153139,24 +153451,35 @@ function WebApp({ viewHint }) {
153139
153451
  rows: 1
153140
153452
  }
153141
153453
  )
153142
- ] }),
153454
+ ] }) }),
153143
153455
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sp-web-query-toolbar", children: [
153144
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { children: [
153145
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connection" }),
153146
- /* @__PURE__ */ jsxRuntimeExports.jsx(
153147
- "select",
153148
- {
153149
- "aria-label": "Query connection",
153150
- value: selectedConnectionId,
153151
- onChange: (event) => setQueryConnections((current2) => ({
153152
- ...current2,
153153
- [activeTab.id]: event.target.value
153154
- })),
153155
- children: connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: connection.id, children: connection.name ?? connection.id }, connection.id))
153156
- }
153157
- )
153158
- ] }),
153159
- activeTab.isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(
153456
+ isAgentTab ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-readonly", children: activeConnectionName ?? "Connection unknown" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("label", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
153457
+ "select",
153458
+ {
153459
+ "aria-label": "Query connection",
153460
+ value: selectedConnectionId,
153461
+ onChange: (event) => setQueryConnections((current2) => ({
153462
+ ...current2,
153463
+ [activeTab.id]: event.target.value
153464
+ })),
153465
+ children: [
153466
+ !selectedConnectionId && /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Choose connection" }),
153467
+ selectedConnectionId && !connections.some(
153468
+ (connection) => connection.id === selectedConnectionId
153469
+ ) && /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: selectedConnectionId, children: selectedConnectionId }),
153470
+ connections.map((connection) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: connection.id, children: connection.name ?? connection.id }, connection.id))
153471
+ ]
153472
+ }
153473
+ ) }),
153474
+ isAgentTab ? /* @__PURE__ */ jsxRuntimeExports.jsx(
153475
+ "button",
153476
+ {
153477
+ type: "button",
153478
+ className: "sp-web-secondary-button",
153479
+ onClick: () => newQuery(activeTab.connectionId ?? "", activeTab.sql),
153480
+ children: "Open as my query"
153481
+ }
153482
+ ) : activeTab.isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(
153160
153483
  "button",
153161
153484
  {
153162
153485
  type: "button",
@@ -153172,6 +153495,7 @@ function WebApp({ viewHint }) {
153172
153495
  onClick: () => void runQuery(),
153173
153496
  disabled: !selectedConnectionId || !activeTab.sql.trim(),
153174
153497
  children: [
153498
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sp-web-run-glyph", "aria-hidden": "true", children: "▶" }),
153175
153499
  "Run ",
153176
153500
  /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { children: "⌘↵" })
153177
153501
  ]
@@ -153179,25 +153503,17 @@ function WebApp({ viewHint }) {
153179
153503
  )
153180
153504
  ] }),
153181
153505
  /* @__PURE__ */ jsxRuntimeExports.jsx(
153182
- "textarea",
153506
+ SqlEditor,
153183
153507
  {
153184
- className: "sp-web-sql-editor",
153185
- "aria-label": "SQL editor",
153186
- spellCheck: false,
153187
153508
  value: activeTab.sql,
153188
- onChange: (event) => updateTab(activeTab.id, { sql: event.target.value }),
153189
- onKeyDown: (event) => {
153190
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
153191
- event.preventDefault();
153192
- void runQuery();
153193
- }
153194
- }
153509
+ onChange: (sql) => updateTab(activeTab.id, { sql }),
153510
+ onRun: () => void runQuery(),
153511
+ readOnly: isAgentTab
153195
153512
  }
153196
153513
  )
153197
- ] }),
153198
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-results-label", children: "Results" })
153514
+ ] })
153199
153515
  ] }),
153200
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-results", hidden: Boolean(activeConnectionId), children: /* @__PURE__ */ jsxRuntimeExports.jsx(MultiTabLayout, { emptyState: resultsEmptyState }) })
153516
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "sp-web-results", hidden: Boolean(activeConnectionId), children: /* @__PURE__ */ jsxRuntimeExports.jsx(MultiTabLayout, { emptyState: resultsEmptyState, fillGridWidth: true }) })
153201
153517
  ] })
153202
153518
  ] })
153203
153519
  ] });
@@ -153370,11 +153686,18 @@ bootstrap();</script>
153370
153686
  margin: 5px 0 0;
153371
153687
  }
153372
153688
  .grain-connection-success {
153373
- color: var(--grain-ink);
153374
- background: var(--grain-surface);
153689
+ color: var(--vscode-testing-iconPassed, var(--sp-success));
153690
+ border: 1px solid var(--vscode-testing-iconPassed, var(--sp-success));
153691
+ background: color-mix(in srgb, var(--sp-success) 8%, var(--grain-surface));
153375
153692
  padding: 10px 14px;
153376
153693
  border-radius: var(--grain-radius-sm);
153377
153694
  }
153695
+ .grain-connection-footer .grain-connection-success {
153696
+ color: var(--vscode-testing-iconPassed, var(--sp-success));
153697
+ }
153698
+ .grain-connection-footer-error {
153699
+ color: var(--vscode-errorForeground, var(--sp-error));
153700
+ }
153378
153701
  .grain-connection-page :is(button, input, select, summary):focus-visible {
153379
153702
  outline: 2px solid var(--grain-accent);
153380
153703
  outline-offset: 2px;
@@ -163150,6 +163473,12 @@ body.vscode-high-contrast .ag-theme-quartz {
163150
163473
  outline: none !important;
163151
163474
  }
163152
163475
 
163476
+ .sp-num-cell {
163477
+ justify-content: flex-end;
163478
+ text-align: right;
163479
+ font-variant-numeric: tabular-nums;
163480
+ }
163481
+
163153
163482
  .sp-null {
163154
163483
  color: var(--sp-muted-fg);
163155
163484
  font-style: italic;
@@ -166216,10 +166545,79 @@ body {
166216
166545
  letter-spacing: 0;
166217
166546
  }
166218
166547
  .sp-web-sidebar-section-title {
166219
- margin: 18px 9px 8px;
166220
- padding-top: 17px;
166548
+ display: flex;
166549
+ align-items: center;
166550
+ justify-content: space-between;
166551
+ margin: 18px 0 6px;
166552
+ padding: 17px 9px 0;
166221
166553
  border-top: 1px solid var(--grain-border-subtle);
166222
166554
  }
166555
+ .sp-web-sidebar-header .sp-web-sidebar-title {
166556
+ flex: 1;
166557
+ }
166558
+ .sp-web-section-toggle,
166559
+ .sp-web-agent-session-toggle {
166560
+ border: 0;
166561
+ background: transparent;
166562
+ color: var(--grain-ink);
166563
+ font: inherit;
166564
+ text-align: left;
166565
+ cursor: pointer;
166566
+ }
166567
+ .sp-web-section-toggle {
166568
+ padding: 0;
166569
+ }
166570
+ .sp-web-agent-session-toggle {
166571
+ width: 100%;
166572
+ padding: 7px 9px;
166573
+ color: var(--grain-ink-muted);
166574
+ font-size: 11px;
166575
+ }
166576
+ .sp-web-section-toggle:focus-visible,
166577
+ .sp-web-agent-session-toggle:focus-visible {
166578
+ outline: 1px solid var(--grain-accent);
166579
+ }
166580
+ [aria-current='page'].sp-web-connection-item {
166581
+ background: var(--grain-surface-raised);
166582
+ }
166583
+ .sp-web-section-add,
166584
+ .sp-web-sidebar-close {
166585
+ display: inline-flex;
166586
+ align-items: center;
166587
+ justify-content: center;
166588
+ width: 22px;
166589
+ height: 22px;
166590
+ padding: 0;
166591
+ border: 0;
166592
+ border-radius: var(--grain-radius-sm);
166593
+ background: transparent;
166594
+ color: var(--grain-ink-muted);
166595
+ font: 400 16px/1 var(--grain-font-body);
166596
+ cursor: pointer;
166597
+ }
166598
+ .sp-web-sidebar-close {
166599
+ font-size: 13px;
166600
+ }
166601
+ .sp-web-section-add:hover,
166602
+ .sp-web-sidebar-close:hover {
166603
+ background: var(--grain-surface-raised);
166604
+ color: var(--grain-ink);
166605
+ }
166606
+ .sp-web-section-add:focus-visible,
166607
+ .sp-web-sidebar-close:focus-visible {
166608
+ outline: 1px solid var(--grain-accent);
166609
+ }
166610
+ .sp-web-connection-label {
166611
+ flex: 1;
166612
+ min-width: 0;
166613
+ }
166614
+ .sp-web-connection-dot {
166615
+ width: 7px;
166616
+ height: 7px;
166617
+ flex: none;
166618
+ border-radius: 50%;
166619
+ background: var(--sp-success);
166620
+ }
166223
166621
  .sp-web-connection-item {
166224
166622
  display: flex;
166225
166623
  align-items: center;
@@ -166338,7 +166736,7 @@ body {
166338
166736
  .sp-web-query-tabs {
166339
166737
  display: flex;
166340
166738
  align-items: stretch;
166341
- min-height: 38px;
166739
+ min-height: 42px;
166342
166740
  overflow-x: auto;
166343
166741
  border-bottom: 1px solid var(--grain-border-subtle);
166344
166742
  background: var(--grain-surface);
@@ -166352,6 +166750,9 @@ body {
166352
166750
  background: var(--grain-ground);
166353
166751
  box-shadow: inset 0 -2px var(--grain-accent);
166354
166752
  }
166753
+ .sp-web-query-tab.active button:first-child {
166754
+ color: var(--grain-accent);
166755
+ }
166355
166756
  .sp-web-query-tab button,
166356
166757
  .sp-web-query-tab-action {
166357
166758
  border: 0;
@@ -166359,7 +166760,7 @@ body {
166359
166760
  color: var(--grain-ink);
166360
166761
  font: inherit;
166361
166762
  cursor: pointer;
166362
- padding: 8px 10px;
166763
+ padding: 8px 14px;
166363
166764
  white-space: nowrap;
166364
166765
  }
166365
166766
  .sp-web-query-tab button:last-child {
@@ -166370,12 +166771,20 @@ body {
166370
166771
  gap: 4px;
166371
166772
  padding: 18px 24px 14px;
166372
166773
  }
166774
+ .sp-web-query-heading h1 {
166775
+ margin: 0;
166776
+ font-size: 22px;
166777
+ }
166778
+ .sp-web-query-provenance {
166779
+ color: var(--grain-ink-muted);
166780
+ font-size: 11px;
166781
+ }
166373
166782
  .sp-web-query-heading input,
166374
166783
  .sp-web-query-heading textarea {
166375
166784
  width: 100%;
166376
166785
  padding: 0;
166377
166786
  border: 0;
166378
- resize: vertical;
166787
+ resize: none;
166379
166788
  background: transparent;
166380
166789
  color: var(--grain-ink);
166381
166790
  font-family: var(--grain-font-body);
@@ -166434,26 +166843,43 @@ body {
166434
166843
  opacity: 0.5;
166435
166844
  cursor: default;
166436
166845
  }
166846
+ .sp-web-sql-editor-frame {
166847
+ --sp-web-gutter-width: calc(var(--sp-web-gutter-digits, 2) * 1ch + 28px);
166848
+ position: relative;
166849
+ border-bottom: 1px solid var(--grain-border-subtle);
166850
+ background: var(--grain-ground);
166851
+ }
166852
+ .sp-web-sql-gutter {
166853
+ position: absolute;
166854
+ top: 0;
166855
+ bottom: 0;
166856
+ left: 0;
166857
+ box-sizing: border-box;
166858
+ width: var(--sp-web-gutter-width);
166859
+ overflow: hidden;
166860
+ padding: 20px 12px 40px 0;
166861
+ border-right: 1px solid var(--grain-border-subtle);
166862
+ color: var(--grain-ink-muted);
166863
+ font: 12px/1.7 var(--grain-font-mono);
166864
+ text-align: right;
166865
+ user-select: none;
166866
+ }
166437
166867
  .sp-web-sql-editor {
166438
166868
  display: block;
166439
166869
  box-sizing: border-box;
166440
- width: 100%;
166870
+ width: calc(100% - var(--sp-web-gutter-width));
166441
166871
  min-height: 190px;
166442
- padding: 20px 24px;
166872
+ margin-left: var(--sp-web-gutter-width);
166873
+ padding: 20px 24px 20px 16px;
166443
166874
  border: 0;
166444
- border-bottom: 1px solid var(--grain-border-subtle);
166445
166875
  resize: vertical;
166876
+ white-space: pre;
166446
166877
  outline-color: var(--grain-accent);
166447
166878
  background: var(--grain-ground);
166448
166879
  color: var(--grain-ink);
166449
166880
  font: 12px/1.7 var(--grain-font-mono);
166450
166881
  tab-size: 2;
166451
166882
  }
166452
- .sp-web-results-label {
166453
- padding: 10px 24px;
166454
- font-size: 12px;
166455
- font-weight: 700;
166456
- }
166457
166883
  .sp-web-results {
166458
166884
  display: flex;
166459
166885
  flex: 1;
@@ -166480,6 +166906,15 @@ body {
166480
166906
  font-weight: 700;
166481
166907
  }
166482
166908
 
166909
+ .sp-web-run-glyph {
166910
+ margin-right: 6px;
166911
+ font-size: 9px;
166912
+ }
166913
+ .sp-web-query-toolbar > .sp-web-primary-button {
166914
+ display: inline-flex;
166915
+ align-items: center;
166916
+ font-weight: 600;
166917
+ }
166483
166918
  .sp-web-query-toolbar kbd {
166484
166919
  margin-left: 6px;
166485
166920
  padding: 2px 5px;
@@ -166545,6 +166980,8 @@ body {
166545
166980
  .sp-web-connection-item:hover,
166546
166981
  .sp-web-connection-item:focus-visible {
166547
166982
  background: var(--grain-surface);
166983
+ }
166984
+ .sp-web-connection-item:focus-visible {
166548
166985
  outline: 1px solid var(--grain-accent);
166549
166986
  }
166550
166987
  .sp-web-connection-detail {
@@ -166557,6 +166994,18 @@ body {
166557
166994
  .sp-web-connection-detail p {
166558
166995
  color: var(--grain-ink-muted);
166559
166996
  }
166997
+ .sp-web-connection-detail .sp-web-connection-test-success {
166998
+ color: var(--vscode-testing-iconPassed, var(--sp-success));
166999
+ }
167000
+ .sp-web-connection-detail .sp-web-connection-test-error {
167001
+ color: var(--vscode-errorForeground, var(--sp-error));
167002
+ }
167003
+ .sp-web-connection-delete-confirm {
167004
+ margin-top: 16px;
167005
+ }
167006
+ .sp-web-connection-delete-confirm p {
167007
+ color: var(--grain-ink);
167008
+ }
166560
167009
  .sp-web-connection-actions {
166561
167010
  display: flex;
166562
167011
  gap: 8px;