@oxy-hq/sdk 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
 
2
2
  import * as React from "react";
3
- //#region src/customer-app/manifest.d.ts
3
+ //#region src/custom-app/manifest.d.ts
4
4
  /**
5
5
  * Declaration of a single Oxy Function shipped in the bundle's
6
6
  * `functions/` dir. See `internal-docs/2026-06-12-customer-apps-functions-design.md`.
@@ -136,7 +136,7 @@ interface OxyAppManifest {
136
136
  * should treat this as the only source of truth for "which org/app
137
137
  * does this bundle belong to."
138
138
  */
139
- interface ResolvedCustomerAppManifest {
139
+ interface ResolvedCustomAppManifest {
140
140
  manifest: OxyAppManifest;
141
141
  /**
142
142
  * Always an empty array for v2 manifests. Kept for API compatibility;
@@ -179,13 +179,13 @@ interface LoadManifestOptions {
179
179
  * Load + validate the manifest. Cached after the first call so callers
180
180
  * can invoke this from every component without coordinating.
181
181
  */
182
- declare function loadCustomerAppManifest(options?: LoadManifestOptions): Promise<ResolvedCustomerAppManifest>;
182
+ declare function loadCustomAppManifest(options?: LoadManifestOptions): Promise<ResolvedCustomAppManifest>;
183
183
  /** For tests: reset the cache between runs. */
184
- declare function _resetCustomerAppManifestCacheForTest(): void;
184
+ declare function _resetCustomAppManifestCacheForTest(): void;
185
185
  //#endregion
186
- //#region src/customer-app/errors.d.ts
186
+ //#region src/custom-app/errors.d.ts
187
187
  /**
188
- * Error thrown by all customer-app hooks when an API call returns a
188
+ * Error thrown by all custom-app hooks when an API call returns a
189
189
  * non-2xx response. Carries the structured `code` + `hint` the server
190
190
  * emits so bundle UIs can render an actionable message instead of
191
191
  * "404: { ...json... }".
@@ -208,23 +208,23 @@ declare class OxyApiError extends Error {
208
208
  * dominate the bundle UI).
209
209
  */
210
210
  declare function apiErrorFromResponse(resp: Response): Promise<OxyApiError>;
211
- interface CustomerAppErrorReport {
211
+ interface CustomAppErrorReport {
212
212
  title: string;
213
213
  message: string;
214
214
  hint: string;
215
215
  docs?: string;
216
216
  }
217
217
  /** Interpret a thrown error as a structured report for UI display. */
218
- declare function interpretCustomerAppError(err: unknown): CustomerAppErrorReport;
218
+ declare function interpretCustomAppError(err: unknown): CustomAppErrorReport;
219
219
  //#endregion
220
- //#region src/customer-app/function-sse.d.ts
220
+ //#region src/custom-app/function-sse.d.ts
221
221
  /** A captured `console.*` / `ctx.log` line from a function run. */
222
222
  interface FunctionLog {
223
223
  level: string;
224
224
  message: string;
225
225
  }
226
226
  //#endregion
227
- //#region src/customer-app/react.d.ts
227
+ //#region src/custom-app/react.d.ts
228
228
  /**
229
229
  * Credentialed fetch wrapper stored in context so `useQuery` can share
230
230
  * the same request mechanism without coupling it to the global `fetch`.
@@ -237,7 +237,7 @@ interface FunctionLog {
237
237
  */
238
238
  type AppFetcher = typeof fetch;
239
239
  interface OxyAppProviderProps {
240
- /** Optional manifest load options. Same shape as `loadCustomerAppManifest`. */
240
+ /** Optional manifest load options. Same shape as `loadCustomAppManifest`. */
241
241
  manifestOptions?: LoadManifestOptions;
242
242
  /**
243
243
  * Rendered while the manifest is loading. Defaults to nothing; pass a
@@ -249,13 +249,23 @@ interface OxyAppProviderProps {
249
249
  * report so the bundle can show its own branded error card. Defaults
250
250
  * to a minimal text-only fallback (better than a blank page).
251
251
  */
252
- errorFallback?: (err: CustomerAppErrorReport) => React.ReactNode;
252
+ errorFallback?: (err: CustomAppErrorReport) => React.ReactNode;
253
253
  /**
254
254
  * Override the fetch implementation used by all hooks (`useQuery`).
255
255
  * Useful for test environments or proxy setups. Defaults to a wrapper
256
256
  * that sets `credentials: "include"` on every request.
257
257
  */
258
258
  fetcher?: AppFetcher;
259
+ /**
260
+ * Origin of the oxy backend to call (e.g. `https://oxy.example.com` or
261
+ * `http://localhost:3000`). When set, the SDK resolves its relative `/api/…`
262
+ * requests — `shell-context`, Ask Oxygen, events — against this origin
263
+ * instead of the app's own, so a standalone / cross-origin dev app can drive
264
+ * the wired shell without a same-origin proxy. The backend must allow the
265
+ * app's origin (see oxy's dev-origin CORS list). Leave unset when the app is
266
+ * served same-origin by oxy.
267
+ */
268
+ backendUrl?: string;
259
269
  children: React.ReactNode;
260
270
  }
261
271
  /**
@@ -268,7 +278,7 @@ declare function OxyAppProvider(props: OxyAppProviderProps): React.JSX.Element;
268
278
  * `<OxyAppProvider>` — that's a programmer error worth surfacing
269
279
  * loudly, not silently swallowing.
270
280
  */
271
- declare function useResolvedManifest(): ResolvedCustomerAppManifest;
281
+ declare function useResolvedManifest(): ResolvedCustomAppManifest;
272
282
  /**
273
283
  * Low-level hook that returns the raw context value (including the
274
284
  * fetcher). Prefer `useResolvedManifest` for manifest access; use
@@ -300,7 +310,7 @@ interface UseQueryResult<Row = Record<string, unknown>> {
300
310
  }
301
311
  /**
302
312
  * Execute an ad-hoc SQL query against the project linked to this
303
- * customer app. The query is specified inline by the caller; no
313
+ * custom app. The query is specified inline by the caller; no
304
314
  * manifest declaration is involved.
305
315
  *
306
316
  * Re-runs whenever `input` or enabled `params` change. Use the
@@ -642,5 +652,5 @@ interface OxyChatProps {
642
652
  */
643
653
  declare function OxyChat(props: OxyChatProps): React.JSX.Element;
644
654
  //#endregion
645
- export { UseSemanticQueryOpts as A, CustomerAppErrorReport as B, UseProcedureRunInput as C, UseQueryOpts as D, UseQueryInput as E, useProcedureRun as F, OxyAppFunctionManifest as G, apiErrorFromResponse as H, useQuery as I, _resetCustomerAppManifestCacheForTest as J, OxyAppManifest as K, useResolvedManifest as L, useAgentRun as M, useFunction as N, UseQueryResult as O, useOxyApp as P, useSemanticQuery as R, UseFunctionResult as S, UseProcedureRunResult as T, interpretCustomerAppError as U, OxyApiError as V, LoadManifestOptions as W, loadCustomerAppManifest as Y, SemanticFilter as _, AppFetcher as a, UseAgentRunInput as b, OxyAppProvider as c, OxyChatProps as d, ProcedureProgress as f, SemanticDateRangeOp as g, SemanticArrayOp as h, AgentSqlArtifact as i, UseSemanticQueryResult as j, UseSemanticQueryInput as k, OxyAppProviderProps as l, ProcedureRunState as m, AgentRunEvent as n, OxyAnswer as o, ProcedureResult as p, ResolvedCustomerAppManifest as q, AgentRunState as r, OxyAnswerProps as s, AgentArtifact as t, OxyChat as u, SemanticScalarOp as v, UseProcedureRunOpts as w, UseAgentRunResult as x, SemanticTimeDimension as y, useTrackEvent as z };
646
- //# sourceMappingURL=react-BLsczFL4.d.cts.map
655
+ export { UseSemanticQueryOpts as A, CustomAppErrorReport as B, UseProcedureRunInput as C, UseQueryOpts as D, UseQueryInput as E, useProcedureRun as F, OxyAppFunctionManifest as G, apiErrorFromResponse as H, useQuery as I, _resetCustomAppManifestCacheForTest as J, OxyAppManifest as K, useResolvedManifest as L, useAgentRun as M, useFunction as N, UseQueryResult as O, useOxyApp as P, useSemanticQuery as R, UseFunctionResult as S, UseProcedureRunResult as T, interpretCustomAppError as U, OxyApiError as V, LoadManifestOptions as W, loadCustomAppManifest as Y, SemanticFilter as _, AppFetcher as a, UseAgentRunInput as b, OxyAppProvider as c, OxyChatProps as d, ProcedureProgress as f, SemanticDateRangeOp as g, SemanticArrayOp as h, AgentSqlArtifact as i, UseSemanticQueryResult as j, UseSemanticQueryInput as k, OxyAppProviderProps as l, ProcedureRunState as m, AgentRunEvent as n, OxyAnswer as o, ProcedureResult as p, ResolvedCustomAppManifest as q, AgentRunState as r, OxyAnswerProps as s, AgentArtifact as t, OxyChat as u, SemanticScalarOp as v, UseProcedureRunOpts as w, UseAgentRunResult as x, SemanticTimeDimension as y, useTrackEvent as z };
656
+ //# sourceMappingURL=react-DnBdQ8dG.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-DnBdQ8dG.d.mts","names":[],"sources":["../src/custom-app/manifest.ts","../src/custom-app/errors.ts","../src/custom-app/function-sse.ts","../src/custom-app/react.tsx"],"mappings":"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"}
package/dist/shell.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // @oxy/sdk - TypeScript SDK for Oxy data platform
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3
- const require_react = require('./react-B7Blpmt7.cjs');
3
+ const require_react = require('./react-BAiiXftp.cjs');
4
4
  let react = require("react");
5
5
  react = require_react.__toESM(react, 1);
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -1189,6 +1189,38 @@ function HistoryIcon() {
1189
1189
  ]
1190
1190
  });
1191
1191
  }
1192
+ function SearchIcon() {
1193
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1194
+ width: "14",
1195
+ height: "14",
1196
+ viewBox: "0 0 24 24",
1197
+ fill: "none",
1198
+ stroke: "currentColor",
1199
+ strokeWidth: "2",
1200
+ strokeLinecap: "round",
1201
+ strokeLinejoin: "round",
1202
+ "aria-hidden": "true",
1203
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1204
+ cx: "11",
1205
+ cy: "11",
1206
+ r: "8"
1207
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m21 21-4.3-4.3" })]
1208
+ });
1209
+ }
1210
+ function ThreadIcon() {
1211
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1212
+ width: "15",
1213
+ height: "15",
1214
+ viewBox: "0 0 24 24",
1215
+ fill: "none",
1216
+ stroke: "currentColor",
1217
+ strokeWidth: "2",
1218
+ strokeLinecap: "round",
1219
+ strokeLinejoin: "round",
1220
+ "aria-hidden": "true",
1221
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" })]
1222
+ });
1223
+ }
1192
1224
  function ExternalIcon() {
1193
1225
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1194
1226
  width: "14",
@@ -1240,6 +1272,26 @@ function StopIcon() {
1240
1272
  function chartsFrom(events) {
1241
1273
  return events.filter((ev) => ev.type === "chart_rendered").map((ev) => ev.data).filter((b) => b?.config && Array.isArray(b.columns) && Array.isArray(b.rows));
1242
1274
  }
1275
+ /** History label: a relative "N minutes/hours/days ago" for when the
1276
+ * conversation was started. */
1277
+ function formatHistoryTime(iso) {
1278
+ const d = new Date(iso);
1279
+ if (Number.isNaN(d.getTime())) return "";
1280
+ const s = Math.max(0, Math.round((Date.now() - d.getTime()) / 1e3));
1281
+ if (s < 45) return "just now";
1282
+ const min = Math.round(s / 60);
1283
+ if (min < 60) return `${min} minute${min === 1 ? "" : "s"} ago`;
1284
+ const hr = Math.round(s / 3600);
1285
+ if (hr < 24) return `${hr} hour${hr === 1 ? "" : "s"} ago`;
1286
+ const day = Math.round(s / 86400);
1287
+ if (day < 30) return `${day} day${day === 1 ? "" : "s"} ago`;
1288
+ const mo = Math.round(day / 30);
1289
+ if (mo < 12) return `${mo} month${mo === 1 ? "" : "s"} ago`;
1290
+ const yr = Math.round(day / 365);
1291
+ return `${yr} year${yr === 1 ? "" : "s"} ago`;
1292
+ }
1293
+ /** How many history rows to show before the "Show more" button. */
1294
+ const HISTORY_PAGE_SIZE = 12;
1243
1295
  /** Rebuild a completed turn from a transcript turn's processed events —
1244
1296
  * identical to what the live dock accumulates during a run. */
1245
1297
  function turnFromEvents(question, events) {
@@ -1265,9 +1317,12 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1265
1317
  const [threadId, setThreadId] = react.useState(null);
1266
1318
  const [history, setHistory] = react.useState([]);
1267
1319
  const [historyOpen, setHistoryOpen] = react.useState(false);
1320
+ const [historyQuery, setHistoryQuery] = react.useState("");
1321
+ const [historyShown, setHistoryShown] = react.useState(HISTORY_PAGE_SIZE);
1268
1322
  const [restoring, setRestoring] = react.useState(false);
1269
1323
  const scrollRef = react.useRef(null);
1270
1324
  const restoreGenRef = react.useRef(0);
1325
+ const startedAtRef = react.useRef(null);
1271
1326
  const busy = run.state === "running";
1272
1327
  const liveSteps = react.useMemo(() => buildTraceSteps(run.events), [run.events]);
1273
1328
  const liveLlm = react.useMemo(() => aggregateLlmStats(run.events), [run.events]);
@@ -1291,7 +1346,8 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1291
1346
  id: threadId ?? all[0].question,
1292
1347
  title: all[0].question,
1293
1348
  turns: all,
1294
- threadId
1349
+ threadId,
1350
+ createdAt: startedAtRef.current ?? (/* @__PURE__ */ new Date()).toISOString()
1295
1351
  };
1296
1352
  };
1297
1353
  const resetToEmpty = () => {
@@ -1301,6 +1357,7 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1301
1357
  setPending(null);
1302
1358
  setDraft("");
1303
1359
  setThreadId(null);
1360
+ startedAtRef.current = null;
1304
1361
  };
1305
1362
  const newChat = () => {
1306
1363
  const snap = snapshotCurrent();
@@ -1334,6 +1391,7 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1334
1391
  setPending(null);
1335
1392
  setDraft("");
1336
1393
  setThreadId(id);
1394
+ startedAtRef.current = serverThreads.find((t) => t.id === id)?.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
1337
1395
  setHistoryOpen(false);
1338
1396
  setRestoring(true);
1339
1397
  try {
@@ -1348,6 +1406,7 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1348
1406
  const question = raw.trim();
1349
1407
  if (!question || busy) return;
1350
1408
  restoreGenRef.current += 1;
1409
+ if (startedAtRef.current === null) startedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
1351
1410
  if (pending !== null) {
1352
1411
  const finalized = finalizedPendingTurn();
1353
1412
  if (finalized) setTurns((t) => [...t, finalized]);
@@ -1363,16 +1422,31 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1363
1422
  });
1364
1423
  const empty = pending === null && turns.length === 0;
1365
1424
  const canNewChat = !empty;
1366
- const historyEntries = [...history.filter((c) => c.id !== threadId).map((c) => ({
1425
+ const currentId = threadId ?? turns[0]?.question ?? pending ?? null;
1426
+ const historyEntries = [...history.map((c) => ({
1367
1427
  id: c.id,
1368
1428
  title: c.title,
1369
- meta: `${c.turns.length} message${c.turns.length === 1 ? "" : "s"}`,
1370
- onOpen: () => openConversation(c)
1371
- })), ...serverThreads.filter((t) => t.id !== threadId && !history.some((c) => c.id === t.id)).map((t) => ({
1429
+ createdAt: c.createdAt,
1430
+ active: c.id === currentId,
1431
+ onOpen: c.id === currentId ? () => setHistoryOpen(false) : () => openConversation(c)
1432
+ })), ...serverThreads.filter((t) => !history.some((c) => c.id === t.id)).map((t) => ({
1372
1433
  id: t.id,
1373
1434
  title: t.title,
1374
- onOpen: () => void openServerThread(t.id)
1435
+ createdAt: t.created_at,
1436
+ active: t.id === currentId,
1437
+ onOpen: t.id === currentId ? () => setHistoryOpen(false) : () => void openServerThread(t.id)
1375
1438
  }))];
1439
+ if (!empty && currentId != null && !historyEntries.some((e) => e.id === currentId)) historyEntries.push({
1440
+ id: currentId,
1441
+ title: turns[0]?.question ?? pending ?? currentId,
1442
+ createdAt: startedAtRef.current ?? (/* @__PURE__ */ new Date()).toISOString(),
1443
+ active: true,
1444
+ onOpen: () => setHistoryOpen(false)
1445
+ });
1446
+ historyEntries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1447
+ const q = historyQuery.trim().toLowerCase();
1448
+ const filteredEntries = q ? historyEntries.filter((e) => e.title.toLowerCase().includes(q)) : historyEntries;
1449
+ const visibleEntries = filteredEntries.slice(0, historyShown);
1376
1450
  const placeholder = run.state === "needs_clarification" ? "Reply…" : pending !== null ? "Ask a follow-up…" : `Ask Oxygen anything about ${workspaceName ?? "your workspace"}…`;
1377
1451
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
1378
1452
  className: cx("oxy-shell-scope oxy-askdock", !open && "oxy-askdock--closed", className),
@@ -1405,7 +1479,11 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1405
1479
  onClick: () => {
1406
1480
  const next = !historyOpen;
1407
1481
  setHistoryOpen(next);
1408
- if (next) refetchHistory();
1482
+ if (next) {
1483
+ refetchHistory();
1484
+ setHistoryQuery("");
1485
+ setHistoryShown(HISTORY_PAGE_SIZE);
1486
+ }
1409
1487
  },
1410
1488
  "data-testid": "askdock-history",
1411
1489
  "aria-label": "Chat history",
@@ -1430,24 +1508,51 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1430
1508
  ]
1431
1509
  })
1432
1510
  ]
1433
- }), historyOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1511
+ }), historyOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1434
1512
  className: "oxy-askdock__history",
1435
1513
  "data-testid": "askdock-history-panel",
1436
- children: historyEntries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1514
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1515
+ className: "oxy-askdock__history-search",
1516
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1517
+ type: "text",
1518
+ className: "oxy-askdock__history-searchinput",
1519
+ placeholder: "Search threads…",
1520
+ value: historyQuery,
1521
+ onChange: (e) => {
1522
+ setHistoryQuery(e.target.value);
1523
+ setHistoryShown(HISTORY_PAGE_SIZE);
1524
+ },
1525
+ "aria-label": "Search threads"
1526
+ })]
1527
+ }), visibleEntries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1437
1528
  className: "oxy-askdock__history-empty",
1438
- children: "No previous chats yet."
1439
- }) : historyEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1529
+ children: historyQuery ? "No matching chats." : "No previous chats yet."
1530
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [visibleEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1440
1531
  type: "button",
1441
- className: "oxy-askdock__history-item",
1532
+ className: cx("oxy-askdock__history-item", entry.active && "oxy-askdock__history-item--active"),
1442
1533
  onClick: entry.onOpen,
1443
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1444
- className: "oxy-askdock__history-title",
1445
- children: entry.title
1446
- }), entry.meta && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1447
- className: "oxy-askdock__history-meta",
1448
- children: entry.meta
1449
- })]
1450
- }, entry.id))
1534
+ title: entry.title,
1535
+ "aria-current": entry.active ? "true" : void 0,
1536
+ children: [
1537
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1538
+ className: "oxy-askdock__history-icon",
1539
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThreadIcon, {})
1540
+ }),
1541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1542
+ className: "oxy-askdock__history-title",
1543
+ children: entry.title
1544
+ }),
1545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1546
+ className: "oxy-askdock__history-time",
1547
+ children: formatHistoryTime(entry.createdAt)
1548
+ })
1549
+ ]
1550
+ }, entry.id)), filteredEntries.length > visibleEntries.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1551
+ type: "button",
1552
+ className: "oxy-askdock__history-more",
1553
+ onClick: () => setHistoryShown((n) => n + HISTORY_PAGE_SIZE),
1554
+ children: "Show more"
1555
+ })] })]
1451
1556
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1452
1557
  className: "oxy-askdock__scroll",
1453
1558
  ref: scrollRef,
@@ -1679,7 +1784,7 @@ function ShellRail({ top, groups, footerItems, bottom, className }) {
1679
1784
  //#endregion
1680
1785
  //#region src/shell/shellContext.ts
1681
1786
  /**
1682
- * Fetch the shell bootstrap payload for the current customer app. Must be
1787
+ * Fetch the shell bootstrap payload for the current custom app. Must be
1683
1788
  * called inside `<OxyAppProvider>`. Failure is non-fatal by design: the
1684
1789
  * shell degrades to chrome-less rendering (older servers don't have the
1685
1790
  * endpoint), so errors are surfaced on the result, never thrown.
@@ -1907,25 +2012,6 @@ function MessagesIcon() {
1907
2012
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M16 10a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M20 9a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" })]
1908
2013
  });
1909
2014
  }
1910
- /** Settings glyph (lucide "settings" outline, inlined). */
1911
- function GearIcon() {
1912
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1913
- width: "16",
1914
- height: "16",
1915
- viewBox: "0 0 24 24",
1916
- fill: "none",
1917
- stroke: "currentColor",
1918
- strokeWidth: "2",
1919
- strokeLinecap: "round",
1920
- strokeLinejoin: "round",
1921
- "aria-hidden": "true",
1922
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1923
- cx: "12",
1924
- cy: "12",
1925
- r: "3"
1926
- })]
1927
- });
1928
- }
1929
2015
  /** Rewrite a shell-context relative product path to an absolute URL against
1930
2016
  * the oxy origin, when one is configured. `/api/*` paths are left relative
1931
2017
  * — they ride the host's same-origin fetch / dev proxy (which attaches
@@ -1965,11 +2051,18 @@ function buildRailGroups(data, currentAppSlug, abs) {
1965
2051
  return appItems.length ? [[hq, chat], appItems] : [[hq, chat]];
1966
2052
  }
1967
2053
  /**
1968
- * The workspace chrome around a customer app: icon rail + universal top bar
2054
+ * The workspace chrome around a custom app: icon rail + universal top bar
1969
2055
  * + content column — visually identical to the main web-app shell.
1970
2056
  */
1971
- function OxyShell({ children, pageLabel, topBarLeft, topBarExtra, railBottom, hideTopBar, askHotkey = true, productBaseUrl, className }) {
2057
+ function OxyShell({ children, pageLabel, topBarLeft, topBarExtra, railBottom, hideTopBar, askHotkey = true, productBaseUrl, className, chromeBackground, chromeForeground }) {
1972
2058
  const abs = makeAbs(productBaseUrl);
2059
+ const chromeStyle = {
2060
+ ...chromeBackground ? { "--sidebar-background": chromeBackground } : {},
2061
+ ...chromeForeground ? {
2062
+ "--foreground": chromeForeground,
2063
+ "--muted-foreground": `color-mix(in srgb, ${chromeForeground} 65%, transparent)`
2064
+ } : {}
2065
+ };
1973
2066
  const { appSlug } = require_react.useOxyApp();
1974
2067
  const { manifest } = require_react.useResolvedManifest();
1975
2068
  const { data, loading } = useShellContext();
@@ -1992,22 +2085,13 @@ function OxyShell({ children, pageLabel, topBarLeft, topBarExtra, railBottom, hi
1992
2085
  window.addEventListener("keydown", onKey);
1993
2086
  return () => window.removeEventListener("keydown", onKey);
1994
2087
  }, [askAgent, askHotkey]);
1995
- const settingsEntry = data?.links.settings ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellTooltip, {
1996
- content: "Settings",
1997
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1998
- href: abs(data.links.settings),
1999
- "aria-label": "Settings",
2000
- "data-testid": "rail-settings",
2001
- className: "oxy-rail__item",
2002
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {})
2003
- })
2004
- }) : null;
2005
- const bottom = railBottom || settingsEntry ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [railBottom, settingsEntry] }) : void 0;
2088
+ const bottom = railBottom;
2006
2089
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellPortalContext.Provider, {
2007
2090
  value: container,
2008
2091
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2009
2092
  ref: setContainer,
2010
2093
  className: cx("oxy-shell-scope oxy-shell", degraded && "oxy-shell--degraded", className),
2094
+ style: chromeStyle,
2011
2095
  children: [
2012
2096
  !degraded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellRail, {
2013
2097
  top: data ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceTile, {