@oharness/server 0.9.0 → 0.9.2

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.
@@ -1453,6 +1453,49 @@ details.setting>summary:focus-visible{outline:none;background:var(--hover)}
1453
1453
  .status-dot.ok{background:var(--ok);opacity:1}
1454
1454
  /* The presets and the calendar on one line, on the same baseline. */
1455
1455
  .usage-window{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
1456
+ .usage-kind-title{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
1457
+ .usage-head-actions{display:inline-flex;align-items:center;gap:8px;margin-left:auto}
1458
+ .usage-head-actions .seg{margin:0}
1459
+ .usage-head-actions .seg-item{padding:3px 10px;font-size:12px}
1460
+ /*
1461
+ * The kinds, as figures that are also the filter. Filled, never outlined: the
1462
+ * chosen one takes the accent fill the model chips use for "this one".
1463
+ */
1464
+ .usage-kinds{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px;margin:4px 0 10px}
1465
+ .usage-kind{display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;padding:12px 14px;border:0;border-radius:10px;background:var(--panel);color:var(--fg);font:inherit;text-align:left;cursor:pointer}
1466
+ .usage-kind:hover{box-shadow:inset 0 0 0 999px var(--hover)}
1467
+ .usage-kind:focus-visible{outline:none;box-shadow:inset 0 0 0 999px var(--sel)}
1468
+ .usage-kind-label{font-size:11.5px;color:var(--dim)}
1469
+ .usage-kind-value{font-size:20px;font-weight:600;letter-spacing:-.02em;font-variant-numeric:tabular-nums;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1470
+ .usage-kind-sub{font-size:12px;color:var(--dim)}
1471
+ .usage-kind.empty .usage-kind-value{color:var(--dim)}
1472
+ .usage-kind.on{background:var(--accent);color:var(--bg);box-shadow:none}
1473
+ .usage-kind.on .usage-kind-label,.usage-kind.on .usage-kind-sub,.usage-kind.on.empty .usage-kind-value{color:var(--bg);opacity:.72}
1474
+ .usage-counted{margin:0 0 10px}
1475
+ /* A model list: its own height, its own scroll, no frame drawn round it. */
1476
+ .usage-list{max-height:440px;overflow-y:auto;overscroll-behavior:contain}
1477
+ .usage-list>.row-card:last-of-type{margin-bottom:0}
1478
+ /* The share bar: a filled track, no outline, thick enough to carry its facts. */
1479
+ .usage-model{padding:12px 14px}
1480
+ .usage-model .row-head{margin-bottom:8px}
1481
+ .usage-share{position:relative;height:28px;border-radius:6px;background:var(--field);overflow:hidden}
1482
+ .usage-share-fill{position:absolute;inset:0 auto 0 0;border-radius:6px;background:color-mix(in srgb,var(--brand) 34%,transparent)}
1483
+ .usage-share-label{position:relative;display:flex;align-items:center;gap:12px;height:100%;padding:0 10px;min-width:0;font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}
1484
+ .usage-model-pct{flex:none;color:var(--fg);font-weight:600}
1485
+ .usage-share-facts{min-width:0;overflow:hidden;text-overflow:ellipsis;color:var(--fg);opacity:.72}
1486
+ /* Amounts in one column at the right edge, so a list is compared by glancing down it. */
1487
+ .usage .row-head .usage-amount{margin-left:auto;flex:none;font-variant-numeric:tabular-nums}
1488
+ .usage .row-head .row-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1489
+ @media (max-width:640px){.usage-kinds{grid-template-columns:repeat(2,minmax(0,1fr))}.usage-kind:first-child{grid-column:1/-1}}
1490
+ .usage-section-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:22px 0 8px}
1491
+ .usage-section-head .section-title{margin:0}
1492
+ .usage-session{cursor:pointer}
1493
+ .usage-session:hover{background:var(--hover)}
1494
+ .usage-session:focus-visible{outline:none;background:var(--hover)}
1495
+ .usage-sessions-count{margin:10px 0 8px}
1496
+ .usage-sessions-foot{padding:10px 0 24px;text-align:center}
1497
+ .usage-sessions-sentinel{height:1px}
1498
+ .usage-kind-title .muted{font-weight:400;text-transform:none;letter-spacing:0}
1456
1499
  .perms-card{display:flex;flex-direction:column;gap:10px}
1457
1500
  .perms-card .setting-list{margin:0}
1458
1501
  /* The mode, as the one fact the rest of the page qualifies. */
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/page/style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;EAC7B,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAm8FV,CAAC"}
1
+ {"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/page/style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;EAC7B,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8+FV,CAAC"}
package/dist/page.js CHANGED
@@ -82,6 +82,7 @@ ${ANALYTICS_STUB}
82
82
  configuration: this row answers "where did that chart go", which is a
83
83
  question asked mid-task, while the three below it are settings. -->
84
84
  <button class="nav-item" id="nav-artifacts" type="button"><svg class="ico" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="2.67" width="8" height="6" rx="1"/><rect x="6" y="7.33" width="8" height="6" rx="1"/></svg><span data-t="nav.artifacts"></span><span class="count" id="artifact-count"></span></button>
85
+ <button class="nav-item" id="nav-sessions" type="button"><svg class="ico" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.33 10a1.33 1.33 0 0 1-1.33 1.33H4.67L2 14V3.33A1.33 1.33 0 0 1 3.33 2H12a1.33 1.33 0 0 1 1.33 1.33Z"/><path d="M5.33 5.33h5.34"/><path d="M5.33 8h3.34"/></svg><span data-t="nav.sessions"></span></button>
85
86
  <button class="nav-item" id="nav-models" type="button"><svg class="ico" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 5.33a1.33 1.33 0 0 0-.67-1.15L8.67 1.51a1.33 1.33 0 0 0-1.33 0L2.67 4.18A1.33 1.33 0 0 0 2 5.33v5.33a1.33 1.33 0 0 0 .67 1.15l4.67 2.67a1.33 1.33 0 0 0 1.33 0l4.67-2.67A1.33 1.33 0 0 0 14 10.67Z"/><path d="m2.2 4.67 5.8 3.33 5.8-3.33"/><path d="M8 14.67V8"/></svg><span data-t="nav.models"></span><span class="count" id="model-count"></span></button>
86
87
  <button class="nav-item" id="nav-routines" type="button"><svg class="ico" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="6.67"/><path d="M8 4v4l2.67 1.33"/></svg><span data-t="nav.routines"></span><span class="count" id="routine-count"></span></button>
87
88
  <button class="nav-item" id="nav-settings" type="button"><svg class="ico" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.67 4.67h-6"/><path d="M9.33 11.33H3.33"/><circle cx="4.67" cy="4.67" r="2"/><circle cx="11.33" cy="11.33" r="2"/></svg><span data-t="nav.settings"></span></button>
package/dist/page.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AAGH;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG;;;;;yCAKyB,CAAC;AAE1C,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;4CAOwB,kBAAkB,CAAC,OAAO,CAAC;SAC9D,KAAK;EACZ,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA0YK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;qBACvB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;EAC1C,aAAa;EACb,UAAU;EACV,gBAAgB;;;QAGV,CAAC"}
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AAGH;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG;;;;;yCAKyB,CAAC;AAE1C,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;4CAOwB,kBAAkB,CAAC,OAAO,CAAC;SAC9D,KAAK;EACZ,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2YK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;qBACvB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;EAC1C,aAAa;EACb,UAAU;EACV,gBAAgB;;;QAGV,CAAC"}
package/dist/server.js CHANGED
@@ -18,7 +18,7 @@ import { visibleText } from "@oharness/engine/core/types.ts";
18
18
  import { errorMessage } from "@oharness/engine/core/errors.ts";
19
19
  import { officialBaseUrl, officialTeam } from "@oharness/engine/auth/providers/official.ts";
20
20
  import { loadUserConfig, updateUserConfig } from "@oharness/engine/config.ts";
21
- import { EXTENSION_ID, EXTENSION_INSTALL_URL } from "@oharness/engine/browser/host-manifest.ts";
21
+ import { EXTENSION_IDS, EXTENSION_INSTALL_URL } from "@oharness/engine/browser/host-manifest.ts";
22
22
  import { announcePresence } from "@oharness/engine/browser/presence.ts";
23
23
  import { extensionHostReachable } from "@oharness/engine/browser/socket.ts";
24
24
  import { RoutineRunner } from "@oharness/engine/schedule/runner.ts";
@@ -38,6 +38,7 @@ import { artifactDocument, sendArtifact } from "./artifacts.js";
38
38
  import { sendProjectExport } from "./project-export.js";
39
39
  import { ArtifactStore, getArtifactStore, isCanvas, isValidArtifactId, restoreArtifacts, } from "@oharness/engine/artifacts/store.ts";
40
40
  import { accountTeams, publishArtifact, unpublishArtifact } from "@oharness/engine/artifacts/publish.ts";
41
+ import { accountUsage } from "./account-usage.js";
41
42
  /**
42
43
  * How many stored sessions the usage page counts.
43
44
  *
@@ -47,6 +48,15 @@ import { accountTeams, publishArtifact, unpublishArtifact } from "@oharness/engi
47
48
  * stops short says so rather than reading as the whole history.
48
49
  */
49
50
  const USAGE_SESSION_SCAN = 200;
51
+ /**
52
+ * How far back the sessions page reads, and how many rows it sends at a time.
53
+ *
54
+ * The page is "every session that spent something", so it reads further back
55
+ * than the usage summary does — but still a bounded number, and it says when
56
+ * it stopped. A page at a time because the list is drawn as it is scrolled.
57
+ */
58
+ const USAGE_SESSIONS_SCAN = 5000;
59
+ const USAGE_SESSIONS_PAGE = 50;
50
60
  /**
51
61
  * How many trail entries one read of every session may return.
52
62
  *
@@ -452,7 +462,7 @@ export async function startWebServer(opts) {
452
462
  "content-type": "text/html; charset=utf-8",
453
463
  // Framed by itself and by the OHarness extension's side panel, and by
454
464
  // nothing else: a page that drives a shell is not one to embed.
455
- "content-security-policy": `frame-ancestors 'self' chrome-extension://${EXTENSION_ID}`,
465
+ "content-security-policy": `frame-ancestors 'self' ${EXTENSION_IDS.map((id) => `chrome-extension://${id}`).join(" ")}`,
456
466
  });
457
467
  res.end(PAGE);
458
468
  return;
@@ -2124,6 +2134,22 @@ export async function startWebServer(opts) {
2124
2134
  })),
2125
2135
  });
2126
2136
  }
2137
+ /**
2138
+ * The account's spend, from the site's ledger rather than this machine.
2139
+ *
2140
+ * What `GET /usage` cannot see: chat from other devices, and every image,
2141
+ * video and clip of audio, which are billed by the gateway and settled
2142
+ * after the call that started them. Answers `signedIn: false` for a
2143
+ * machine with no account, so the page falls back to its own figures.
2144
+ */
2145
+ case "GET /usage/account": {
2146
+ return send(res, 200, await accountUsage({
2147
+ token: await current.harness.accountToken(),
2148
+ team: officialTeam(),
2149
+ site: siteUrl(),
2150
+ query: url.searchParams,
2151
+ }));
2152
+ }
2127
2153
  /**
2128
2154
  * What has been spent on this machine, and on what.
2129
2155
  *
@@ -2137,6 +2163,33 @@ export async function startWebServer(opts) {
2137
2163
  * mid-turn has already spent money its metadata will not know about
2138
2164
  * until the turn lands, and the page asking is the page that spent it.
2139
2165
  */
2166
+ /**
2167
+ * Every session that spent something, newest first, a page at a time.
2168
+ *
2169
+ * The usage page lists the latest few; this is the rest of them, with a
2170
+ * search over the title and the model. Filtered here rather than in the
2171
+ * page so a search reaches sessions the page has not loaded yet.
2172
+ */
2173
+ case "GET /usage/sessions": {
2174
+ const { stored, rows } = await usageRows(USAGE_SESSIONS_SCAN);
2175
+ const query = (url.searchParams.get("q") || "").trim().toLowerCase();
2176
+ const offset = Math.max(0, Number(url.searchParams.get("offset")) || 0);
2177
+ const limit = Math.min(200, Math.max(1, Number(url.searchParams.get("limit")) || USAGE_SESSIONS_PAGE));
2178
+ const list = [...rows.values()]
2179
+ .filter((row) => row.costUsd > 0 || row.input || row.output || row.cacheRead || row.cacheWrite)
2180
+ .filter((row) => !query || (row.title || "").toLowerCase().includes(query) || row.model.toLowerCase().includes(query))
2181
+ .sort((a, b) => b.updatedAt - a.updatedAt);
2182
+ return send(res, 200, {
2183
+ sessions: list.slice(offset, offset + limit).map((row) => ({
2184
+ ...row,
2185
+ current: row.id === current.session.id,
2186
+ })),
2187
+ total: list.length,
2188
+ offset,
2189
+ more: offset + limit < list.length,
2190
+ truncated: stored.length >= USAGE_SESSIONS_SCAN,
2191
+ });
2192
+ }
2140
2193
  case "GET /usage": {
2141
2194
  // How far back the page is asking about. Everything below — the
2142
2195
  // totals, the models, the sessions and the curve — is that window and
@@ -2144,33 +2197,7 @@ export async function startWebServer(opts) {
2144
2197
  // from the chart under it is worse than no figure.
2145
2198
  const window = usageWindow(url.searchParams.get("range"), url.searchParams.get("from"), url.searchParams.get("to"));
2146
2199
  const { since, until } = window;
2147
- const stored = await current.harness.listSessions(USAGE_SESSION_SCAN);
2148
- const rows = new Map();
2149
- for (const meta of stored) {
2150
- rows.set(meta.id, {
2151
- id: meta.id,
2152
- title: meta.title,
2153
- model: meta.model,
2154
- updatedAt: meta.updatedAt,
2155
- turns: meta.turns,
2156
- costUsd: meta.costUsd || 0,
2157
- ...tokensOf(meta.usage),
2158
- });
2159
- }
2160
- for (const runner of runners.values()) {
2161
- const sess = runner.session;
2162
- rows.set(sess.id, {
2163
- id: sess.id,
2164
- title: sess.title,
2165
- model: sess.model.id,
2166
- // A session opened but not yet spoken to has no stored copy and no
2167
- // last-updated time of its own; it is here, so it is now.
2168
- updatedAt: rows.get(sess.id)?.updatedAt ?? Date.now(),
2169
- turns: sess.turns,
2170
- costUsd: sess.costUsd,
2171
- ...tokensOf(sess.usage),
2172
- });
2173
- }
2200
+ const { stored, rows } = await usageRows(USAGE_SESSION_SCAN);
2174
2201
  // Sessions that never sent anything are dropped: they contribute zero
2175
2202
  // to every total here, and a page about what was spent that opens with
2176
2203
  // a screen of "$0.0000 · 0 tokens" buries the ones that cost money.
@@ -2214,12 +2241,6 @@ export async function startWebServer(opts) {
2214
2241
  // Sorted by what was spent, not by name: the row worth seeing first
2215
2242
  // is the expensive one.
2216
2243
  models: [...byModel.values()].sort((a, b) => b.costUsd - a.costUsd),
2217
- // A page of them is a page nobody reads. The rest are counted in the
2218
- // totals above, which is where a number that large belongs.
2219
- sessions: list.slice(0, 20).map((row) => ({
2220
- ...row,
2221
- current: row.id === current.session.id,
2222
- })),
2223
2244
  scanned: rows.size,
2224
2245
  // Said out loud so a total that stops short is not read as final.
2225
2246
  truncated: stored.length >= USAGE_SESSION_SCAN,
@@ -3571,6 +3592,44 @@ export async function startWebServer(opts) {
3571
3592
  * log carries. Everything else is folded from the revision logs, which is
3572
3593
  * what makes the panel's answer independent of which tabs are open.
3573
3594
  */
3595
+ /**
3596
+ * One row per session: the stored ones, with the open ones laid over their
3597
+ * stored copy. A session mid-turn has already spent money its metadata will
3598
+ * not know about until the turn lands, and the page asking is the page that
3599
+ * spent it.
3600
+ */
3601
+ async function usageRows(scan) {
3602
+ const stored = await current.harness.listSessions(scan);
3603
+ const rows = new Map();
3604
+ for (const meta of stored) {
3605
+ rows.set(meta.id, {
3606
+ id: meta.id,
3607
+ cwd: meta.cwd,
3608
+ title: meta.title,
3609
+ model: meta.model,
3610
+ updatedAt: meta.updatedAt,
3611
+ turns: meta.turns,
3612
+ costUsd: meta.costUsd || 0,
3613
+ ...tokensOf(meta.usage),
3614
+ });
3615
+ }
3616
+ for (const runner of runners.values()) {
3617
+ const sess = runner.session;
3618
+ rows.set(sess.id, {
3619
+ id: sess.id,
3620
+ cwd: rows.get(sess.id)?.cwd ?? runner.harness.cwd,
3621
+ title: sess.title,
3622
+ model: sess.model.id,
3623
+ // A session opened but not yet spoken to has no stored copy and no
3624
+ // last-updated time of its own; it is here, so it is now.
3625
+ updatedAt: rows.get(sess.id)?.updatedAt ?? Date.now(),
3626
+ turns: sess.turns,
3627
+ costUsd: sess.costUsd,
3628
+ ...tokensOf(sess.usage),
3629
+ });
3630
+ }
3631
+ return { stored, rows };
3632
+ }
3574
3633
  async function artifactCatalogue() {
3575
3634
  const groups = [];
3576
3635
  for (const runner of runners.values()) {