@grackle-ai/web 0.135.1 → 0.135.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.
- package/dist/assets/{KnowledgePage-z0o702EY.js → KnowledgePage-BLc0jzI4.js} +2 -2
- package/dist/assets/{KnowledgePage-z0o702EY.js.map → KnowledgePage-BLc0jzI4.js.map} +1 -1
- package/dist/assets/{SessionsListPage-Cy67ACWh.js → SessionsListPage-DqlXe7kn.js} +2 -2
- package/dist/assets/{SessionsListPage-Cy67ACWh.js.map → SessionsListPage-DqlXe7kn.js.map} +1 -1
- package/dist/assets/{index-aktLJa3d.js → index-NKhd-vDs.js} +4 -4
- package/dist/assets/{index-aktLJa3d.js.map → index-NKhd-vDs.js.map} +1 -1
- package/dist/index.html +1 -1
- package/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{b as m,j as t}from"./dagview-B70jDsRo.js";import{c as A,b as T,S as B,I as v,C as E,d as M,M as $,A as H,m as O,f as D,e as P,g as R,h as F,a as q,u as U,t as W,s as G}from"./index-
|
|
1
|
+
import{b as m,j as t}from"./dagview-B70jDsRo.js";import{c as A,b as T,S as B,I as v,C as E,d as M,M as $,A as H,m as O,f as D,e as P,g as R,h as F,a as q,u as U,t as W,s as G}from"./index-NKhd-vDs.js";import"./grpc-D84KWgEK.js";import"./markdown-CPoGw-1N.js";/**
|
|
2
2
|
* @license lucide-react v0.474.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
5
5
|
* See the LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/const K=[["polyline",{points:"4 17 10 11 4 5",key:"akl6gq"}],["line",{x1:"12",x2:"20",y1:"19",y2:"19",key:"q2wloq"}]],w=A("Terminal",K),Q=new Set(["running","idle","pending"]),V=["running","idle","pending","success","paused","error","neutral"],J={running:"Running",idle:"Idle",pending:"Pending",success:"Completed",paused:"Paused",error:"Failed",neutral:"Other"};function X(e){switch(e){case"completed":return{tone:"success",label:"Completed"};case"killed":return{tone:"error",label:"Killed"};case"interrupted":return{tone:"error",label:"Interrupted"};case"terminated":return{tone:"error",label:"Terminated"};case"budget_exceeded":return{tone:"error",label:"Budget exceeded"};case"error":return{tone:"error",label:"Error"};default:return{tone:"neutral",label:"Stopped"}}}function N(e){switch(e.status){case"running":return{tone:"running",label:"Running"};case"idle":case"waiting_input":return{tone:"idle",label:"Idle"};case"pending":return{tone:"pending",label:"Pending"};case"suspended":return{tone:"paused",label:"Suspended"};case"hibernating":return{tone:"paused",label:"Hibernating"};case"completed":return{tone:"success",label:"Completed"};case"failed":return{tone:"error",label:"Failed"};case"killed":return{tone:"error",label:"Killed"};case"interrupted":return{tone:"error",label:"Interrupted"};case"terminated":return{tone:"error",label:"Terminated"};case"stopped":return X(e.endReason);default:return{tone:"neutral",label:T(e.status)||"Unknown"}}}function Y(e){return Q.has(e)}function C(e){return Y(N(e).tone)}function Z(e){const o=new Map;for(const c of e){const{tone:i}=N(c);o.set(i,(o.get(i)??0)+1)}const l=[{value:"all",label:"All",count:e.length}];for(const c of V){const i=o.get(c);i!==void 0&&i>0&&l.push({value:c,label:J[c],count:i})}return l}function ee(e,o,l){const c=o.trim().toLowerCase();return c.length===0?!0:[e.prompt,e.runtime,e.id,e.taskId??"",l].join(" ").toLowerCase().includes(c)}function te(e,o,l,c){return e.filter(i=>{if(o!=="all"&&N(i).tone!==o)return!1;const d=c.get(i.environmentId)??i.environmentId;return ee(i,l,d)})}function z(e,o){return o.localeCompare(e)}function ne(e,o){var u;const l=new Map(o.map(a=>[a.id,a])),c=new Map;for(const a of e){const s=c.get(a.environmentId);s?s.push(a):c.set(a.environmentId,[a])}const i=[];for(const[a,s]of c){const h=[...s].sort((g,j)=>z(g.startedAt,j.startedAt)),p=h.filter(g=>C(g)).length,_=((u=h[0])==null?void 0:u.startedAt)??"";i.push({environmentId:a,environment:l.get(a),sessions:h,activeCount:p,latestStartedAt:_})}const d=a=>{var s;return((s=a.environment)==null?void 0:s.displayName)??a.environmentId};return i.sort((a,s)=>{const h=a.activeCount>0?1:0,p=s.activeCount>0?1:0;if(h!==p)return p-h;const _=z(a.latestStartedAt,s.latestStartedAt);return _!==0?_:d(a).localeCompare(d(s))})}const se="_container_zh6nn_1",ae="_toolbar_zh6nn_8",oe="_chips_zh6nn_22",re="_chip_zh6nn_22",ie="_chipActive_zh6nn_80",ce="_chipCount_zh6nn_86",le="_search_zh6nn_92",de="_searchIcon_zh6nn_105",ue="_searchInput_zh6nn_112",pe="_scroll_zh6nn_147",me="_group_zh6nn_153",he="_groupHeader_zh6nn_160",_e="_groupIcon_zh6nn_187",ge="_groupName_zh6nn_192",ve="_envStatusDot_zh6nn_201",be="_missingEnv_zh6nn_216",xe="_groupSpacer_zh6nn_226",Ne="_activePill_zh6nn_230",je="_countBadge_zh6nn_241",ye="_rows_zh6nn_249",fe="_row_zh6nn_249",ze="_rowButton_zh6nn_286",we="_association_zh6nn_310",Ce="_statusDot_zh6nn_317",Se="_rowContent_zh6nn_367",ke="_promptLine_zh6nn_372",Ie="_prompt_zh6nn_372",Le="_promptEmpty_zh6nn_386",Ae="_meta_zh6nn_391",Te="_statusLabel_zh6nn_401",Be="_runtimeBadge_zh6nn_427",Ee="_personaBadge_zh6nn_428",Me="_taskChip_zh6nn_440",$e="_taskChipLabel_zh6nn_464",He="_adHocChip_zh6nn_470",Oe="_time_zh6nn_482",De="_tokens_zh6nn_486",Pe="_cost_zh6nn_487",Re="_empty_zh6nn_492",Fe="_emptyTitle_zh6nn_504",qe="_emptyHint_zh6nn_511",n={container:se,toolbar:ae,chips:oe,chip:re,chipActive:ie,chipCount:ce,search:le,searchIcon:de,searchInput:ue,scroll:pe,group:me,groupHeader:he,groupIcon:_e,groupName:ge,envStatusDot:ve,missingEnv:be,groupSpacer:xe,activePill:Ne,countBadge:je,rows:ye,row:fe,rowButton:ze,association:we,statusDot:Ce,rowContent:Se,promptLine:ke,prompt:Ie,promptEmpty:Le,meta:Ae,statusLabel:Te,runtimeBadge:Be,personaBadge:Ee,taskChip:Me,taskChipLabel:$e,adHocChip:He,time:Oe,tokens:De,cost:Pe,empty:Re,emptyTitle:Fe,emptyHint:qe},Ue=.18;function We({session:e,taskTitle:o,personaName:l,onOpenSession:c,onOpenTask:i}){const d=N(e),u=(e.inputTokens??0)+(e.outputTokens??0),a=e.costMillicents??0,s=e.taskId;return t.jsxs("li",{className:n.row,"data-status-tone":d.tone,"data-testid":`session-row-${e.id}`,children:[t.jsxs("button",{type:"button",className:n.rowButton,"aria-label":`Open session: ${e.prompt||e.id}`,onClick:()=>c(e.id),"data-testid":`session-open-${e.id}`,children:[t.jsx("span",{className:n.statusDot,"data-tone":d.tone,"aria-hidden":"true"}),t.jsxs("div",{className:n.rowContent,children:[t.jsx("div",{className:n.promptLine,children:t.jsx("span",{className:n.prompt,children:e.prompt||t.jsx("span",{className:n.promptEmpty,children:"(no prompt)"})})}),t.jsxs("div",{className:n.meta,children:[t.jsx("span",{className:n.statusLabel,"data-tone":d.tone,children:d.label}),t.jsx("span",{className:n.runtimeBadge,children:e.runtime||"unknown"}),l!==void 0&&t.jsx("span",{className:n.personaBadge,"data-testid":`session-persona-${e.id}`,children:l}),t.jsx("span",{className:n.time,children:D(e.startedAt)}),u>0&&t.jsxs("span",{className:n.tokens,children:[P(u)," tok"]}),a>0&&t.jsx("span",{className:n.cost,children:R(a)})]})]})]}),t.jsx("div",{className:n.association,children:s?t.jsxs("button",{type:"button",className:n.taskChip,title:o??s,onClick:()=>i(s),"data-testid":`session-task-${e.id}`,children:[t.jsx(F,{size:v,"aria-hidden":"true"}),t.jsx("span",{className:n.taskChipLabel,children:o??s})]}):t.jsxs("span",{className:n.adHocChip,"data-testid":`session-adhoc-${e.id}`,children:[t.jsx(w,{size:v,"aria-hidden":"true"}),"ad-hoc"]})})]})}function Ge({group:e,collapsed:o,onToggle:l,taskTitleById:c,personaNameById:i,onOpenSession:d,onOpenTask:u}){const{environment:a,environmentId:s}=e,h=(a==null?void 0:a.displayName)??s;return t.jsxs("section",{className:n.group,"data-testid":`session-group-${s}`,children:[t.jsxs("button",{type:"button",className:n.groupHeader,"aria-expanded":!o,onClick:()=>l(s),"data-testid":`session-group-toggle-${s}`,children:[o?t.jsx(E,{size:v,"aria-hidden":"true"}):t.jsx(M,{size:v,"aria-hidden":"true"}),t.jsx($,{size:v,className:n.groupIcon,"aria-hidden":"true"}),t.jsx("span",{className:n.groupName,children:h}),a!==void 0?t.jsx("span",{className:n.envStatusDot,"data-status":a.status,"aria-hidden":"true"}):t.jsx("span",{className:n.missingEnv,children:"missing"}),t.jsx("span",{className:n.groupSpacer}),e.activeCount>0&&t.jsxs("span",{className:n.activePill,"data-testid":`session-group-active-${s}`,children:[e.activeCount," active"]}),t.jsx("span",{className:n.countBadge,children:e.sessions.length})]}),t.jsx(H,{initial:!1,children:!o&&t.jsx(O.ul,{className:n.rows,initial:{height:0,opacity:0},animate:{height:"auto",opacity:1},exit:{height:0,opacity:0},transition:{duration:Ue,ease:[.16,1,.3,1]},children:e.sessions.map(p=>t.jsx(We,{session:p,taskTitle:p.taskId?c.get(p.taskId):void 0,personaName:p.personaId?i.get(p.personaId):void 0,onOpenSession:d,onOpenTask:u},p.id))})})]})}function Ke({sessions:e,environments:o,tasks:l,personas:c,onOpenSession:i,onOpenTask:d}){const[u,a]=m.useState("all"),[s,h]=m.useState(""),[p,_]=m.useState(new Set),g=m.useMemo(()=>new Map(o.map(r=>[r.id,r.displayName])),[o]),j=m.useMemo(()=>new Map((l??[]).map(r=>[r.id,r.title])),[l]),S=m.useMemo(()=>new Map((c??[]).map(r=>[r.id,r.name])),[c]),k=m.useMemo(()=>Z(e),[e]),y=m.useMemo(()=>te(e,u,s,g),[e,u,s,g]),f=m.useMemo(()=>ne(y,o),[y,o]),I=r=>{_(L=>{const x=new Set(L);return x.has(r)?x.delete(r):x.add(r),x})};return t.jsxs("div",{className:n.container,"data-testid":"sessions-table",children:[t.jsxs("div",{className:n.toolbar,children:[t.jsx("div",{className:n.chips,role:"group","aria-label":"Filter by status",children:k.map(r=>t.jsxs("button",{type:"button",className:`${n.chip} ${u===r.value?n.chipActive:""}`,"data-tone":r.value==="all"?void 0:r.value,"aria-pressed":u===r.value,onClick:()=>a(r.value),"data-testid":`session-filter-${r.value}`,children:[r.label,t.jsx("span",{className:n.chipCount,children:r.count})]},r.value))}),t.jsxs("div",{className:n.search,children:[t.jsx(B,{size:v,className:n.searchIcon,"aria-hidden":"true"}),t.jsx("input",{type:"text",className:n.searchInput,placeholder:"Search sessions...",value:s,onChange:r=>h(r.target.value),"aria-label":"Search sessions","data-testid":"sessions-search"})]})]}),f.length===0?t.jsxs("div",{className:n.empty,"data-testid":"sessions-empty",children:[t.jsx(w,{size:32,"aria-hidden":"true"}),t.jsx("p",{className:n.emptyTitle,children:e.length===0?"No sessions yet":"No matching sessions"}),t.jsx("p",{className:n.emptyHint,children:e.length===0?"Spawn an agent or start a task and it will show up here.":"Try a different status filter or search term."})]}):t.jsx("div",{className:n.scroll,children:f.map(r=>t.jsx(Ge,{group:r,collapsed:p.has(r.environmentId),onToggle:I,taskTitleById:j,personaNameById:S,onOpenSession:i,onOpenTask:d},r.environmentId))})]})}function Qe(e,o,l){return e===0?"No sessions yet":`${o} active of ${e} ${e===1?"session":"sessions"} across ${l} ${l===1?"environment":"environments"}`}const Ve="_page_cwhm8_1",Je="_header_cwhm8_8",Xe="_title_cwhm8_17",Ye="_subtitle_cwhm8_24",Ze="_tableWrap_cwhm8_30",b={page:Ve,header:Je,title:Xe,subtitle:Ye,tableWrap:Ze};function at(){const{sessions:{sessions:e},environments:{environments:o},tasks:{tasks:l},personas:{personas:c}}=q(),i=U(),d=m.useMemo(()=>e.filter(s=>s.environmentId!==""),[e]),u=m.useMemo(()=>d.filter(s=>C(s)).length,[d]),a=m.useMemo(()=>new Set(d.map(s=>s.environmentId)).size,[d]);return t.jsxs("div",{className:b.page,"data-testid":"sessions-page",children:[t.jsxs("header",{className:b.header,children:[t.jsx("h1",{className:b.title,children:"Sessions"}),t.jsx("p",{className:b.subtitle,"data-testid":"sessions-summary",children:Qe(d.length,u,a)})]}),t.jsx("div",{className:b.tableWrap,children:t.jsx(Ke,{sessions:d,environments:o,tasks:l,personas:c,onOpenSession:s=>i(G(s)),onOpenTask:s=>i(W(s))})})]})}export{at as SessionsListPage};
|
|
7
|
-
//# sourceMappingURL=SessionsListPage-
|
|
7
|
+
//# sourceMappingURL=SessionsListPage-DqlXe7kn.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionsListPage-Cy67ACWh.js","sources":["../../../../common/temp/node_modules/.pnpm/lucide-react@0.474.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/terminal.js","../../../web-components/src/components/sessions/sessionsView.ts","../../../web-components/src/components/sessions/SessionsTable.tsx","../../src/pages/sessionsSummary.ts","../../src/pages/SessionsListPage.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"polyline\", { points: \"4 17 10 11 4 5\", key: \"akl6gq\" }],\n [\"line\", { x1: \"12\", x2: \"20\", y1: \"19\", y2: \"19\", key: \"q2wloq\" }]\n];\nconst Terminal = createLucideIcon(\"Terminal\", __iconNode);\n\nexport { __iconNode, Terminal as default };\n//# sourceMappingURL=terminal.js.map\n","/**\n * Pure view-model helpers for the Sessions activity monitor.\n *\n * All branchy logic (status normalization, filtering, grouping, sorting, and\n * filter-chip derivation) lives here so it can be unit-tested directly. The\n * SessionsTable component is intentionally kept to straight-line JSX that\n * consumes these helpers.\n *\n * @module\n */\n\nimport type { Environment, Session } from \"../../hooks/types.js\";\nimport { capitalize } from \"../../utils/sessionStatus.js\";\n\n/**\n * A normalized status \"tone\": a small, stable set of buckets that both the raw\n * database session statuses and the live-event mapped statuses collapse into.\n * Drives colour, iconography, and filtering.\n *\n * This is intentionally distinct from {@link ../../utils/sessionStatus.js}'s\n * `sessionStatusStyle`, which the Coordination graph uses: that mapping\n * de-emphasizes finished sessions (completed -> neutral grey) and has no\n * `pending`/`paused`/endReason concepts. The Sessions activity monitor instead\n * wants a richer, success-positive palette (completed -> green) plus the extra\n * buckets that drive its status filter chips, so the two are kept separate by\n * design rather than unified.\n */\nexport type StatusTone =\n | \"running\"\n | \"idle\"\n | \"pending\"\n | \"success\"\n | \"error\"\n | \"paused\"\n | \"neutral\";\n\n/** A human-readable description of a session's status. */\nexport interface SessionStatusDescriptor {\n /** Normalized tone bucket used for colour/icon/filter. */\n tone: StatusTone;\n /** Capitalized display label (e.g. \"Running\", \"Completed\"). */\n label: string;\n}\n\n/** Tones considered \"active\" (the session is live, not finished or paused). */\nconst ACTIVE_TONES: ReadonlySet<StatusTone> = new Set<StatusTone>([\"running\", \"idle\", \"pending\"]);\n\n/** Display order for status filter chips and grouping. */\nexport const TONE_ORDER: readonly StatusTone[] = [\n \"running\",\n \"idle\",\n \"pending\",\n \"success\",\n \"paused\",\n \"error\",\n \"neutral\",\n];\n\n/** Category labels for status filter chips (broader than per-status labels). */\nexport const TONE_LABELS: Readonly<Record<StatusTone, string>> = {\n running: \"Running\",\n idle: \"Idle\",\n pending: \"Pending\",\n success: \"Completed\",\n paused: \"Paused\",\n error: \"Failed\",\n neutral: \"Other\",\n};\n\n/**\n * Describe a terminal (\"stopped\") session by its `endReason`.\n *\n * Sessions reach a generic `stopped` status via live status events; the\n * `endReason` distinguishes a clean completion from an error/kill.\n */\nfunction describeStopped(endReason: string | undefined): SessionStatusDescriptor {\n // Cases mirror grackle.END_REASON. A non-clean stop (killed/interrupted/\n // terminated/budget_exceeded/error) is an error tone so it lands in the\n // \"Failed\" filter bucket rather than the neutral \"Stopped\" fallback.\n switch (endReason) {\n case \"completed\":\n return { tone: \"success\", label: \"Completed\" };\n case \"killed\":\n return { tone: \"error\", label: \"Killed\" };\n case \"interrupted\":\n return { tone: \"error\", label: \"Interrupted\" };\n case \"terminated\":\n return { tone: \"error\", label: \"Terminated\" };\n case \"budget_exceeded\":\n return { tone: \"error\", label: \"Budget exceeded\" };\n case \"error\":\n return { tone: \"error\", label: \"Error\" };\n default:\n return { tone: \"neutral\", label: \"Stopped\" };\n }\n}\n\n/**\n * Map a session's raw status (and `endReason`) to a normalized tone + label.\n *\n * Handles both the raw database statuses returned by `ListSessions`\n * (`running`, `completed`, `failed`, ...) and the collapsed `stopped` status\n * produced by live status events (disambiguated via `endReason`).\n */\nexport function describeSessionStatus(\n session: Pick<Session, \"status\" | \"endReason\">,\n): SessionStatusDescriptor {\n switch (session.status) {\n case \"running\":\n return { tone: \"running\", label: \"Running\" };\n case \"idle\":\n case \"waiting_input\":\n return { tone: \"idle\", label: \"Idle\" };\n case \"pending\":\n return { tone: \"pending\", label: \"Pending\" };\n case \"suspended\":\n return { tone: \"paused\", label: \"Suspended\" };\n case \"hibernating\":\n return { tone: \"paused\", label: \"Hibernating\" };\n case \"completed\":\n return { tone: \"success\", label: \"Completed\" };\n case \"failed\":\n return { tone: \"error\", label: \"Failed\" };\n case \"killed\":\n return { tone: \"error\", label: \"Killed\" };\n case \"interrupted\":\n return { tone: \"error\", label: \"Interrupted\" };\n case \"terminated\":\n return { tone: \"error\", label: \"Terminated\" };\n case \"stopped\":\n return describeStopped(session.endReason);\n default:\n return { tone: \"neutral\", label: capitalize(session.status) || \"Unknown\" };\n }\n}\n\n/** Whether a tone represents a live (active) session. */\nexport function isActiveTone(tone: StatusTone): boolean {\n return ACTIVE_TONES.has(tone);\n}\n\n/** Whether a session is currently active (live), by its normalized tone. */\nexport function isActiveSession(session: Pick<Session, \"status\" | \"endReason\">): boolean {\n return isActiveTone(describeSessionStatus(session).tone);\n}\n\n/** A status filter selection: a specific tone, or `\"all\"` for no filter. */\nexport type StatusFilter = StatusTone | \"all\";\n\n/** A single status filter chip with its display label and matching count. */\nexport interface StatusChip {\n /** Filter value this chip selects. */\n value: StatusFilter;\n /** Display label. */\n label: string;\n /** Number of sessions matching this chip. */\n count: number;\n}\n\n/**\n * Build the ordered list of status filter chips for a set of sessions.\n *\n * Always begins with an \"All\" chip, followed by one chip per tone that is\n * actually present (in {@link TONE_ORDER}), each annotated with its count.\n * Chips for absent tones are omitted so the bar stays uncluttered.\n */\nexport function buildStatusChips(sessions: readonly Session[]): StatusChip[] {\n const counts = new Map<StatusTone, number>();\n for (const session of sessions) {\n const { tone } = describeSessionStatus(session);\n counts.set(tone, (counts.get(tone) ?? 0) + 1);\n }\n const chips: StatusChip[] = [{ value: \"all\", label: \"All\", count: sessions.length }];\n for (const tone of TONE_ORDER) {\n const count = counts.get(tone);\n if (count !== undefined && count > 0) {\n chips.push({ value: tone, label: TONE_LABELS[tone], count });\n }\n }\n return chips;\n}\n\n/**\n * Whether a session matches a free-text query, checked against its prompt,\n * runtime, id, task id, and resolved environment name. An empty query matches\n * everything. Matching is case-insensitive.\n */\nexport function sessionMatchesQuery(\n session: Session,\n query: string,\n environmentName: string,\n): boolean {\n const trimmed = query.trim().toLowerCase();\n if (trimmed.length === 0) {\n return true;\n }\n const haystack = [\n session.prompt,\n session.runtime,\n session.id,\n session.taskId ?? \"\",\n environmentName,\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(trimmed);\n}\n\n/**\n * Apply the status-tone filter and free-text query to a list of sessions.\n *\n * @param sessions - Sessions to filter.\n * @param status - Selected status filter, or `\"all\"`.\n * @param query - Free-text query (empty string matches all).\n * @param environmentNameById - Map of environment id to display name, used for\n * both query matching and so a missing environment falls back to its id.\n */\nexport function filterSessions(\n sessions: readonly Session[],\n status: StatusFilter,\n query: string,\n environmentNameById: ReadonlyMap<string, string>,\n): Session[] {\n return sessions.filter((session) => {\n if (status !== \"all\" && describeSessionStatus(session).tone !== status) {\n return false;\n }\n const environmentName = environmentNameById.get(session.environmentId) ?? session.environmentId;\n return sessionMatchesQuery(session, query, environmentName);\n });\n}\n\n/** A group of sessions belonging to a single environment. */\nexport interface SessionGroup {\n /** Environment id this group is keyed by. */\n environmentId: string;\n /** The environment, if it still exists (may be undefined for a gone env). */\n environment: Environment | undefined;\n /** Sessions in this group, newest first. */\n sessions: Session[];\n /** Number of active (live) sessions in this group. */\n activeCount: number;\n /** ISO timestamp of the most recently started session in this group. */\n latestStartedAt: string;\n}\n\n/** Compare two ISO timestamps for a newest-first sort. */\nfunction byNewest(a: string, b: string): number {\n return b.localeCompare(a);\n}\n\n/**\n * Group sessions by their environment, returning groups ready for display.\n *\n * Within each group, sessions are ordered newest-first. Groups are ordered so\n * that those with active sessions appear first, then by most-recent activity,\n * with a stable alphabetical tie-break on environment name. Environments with\n * no sessions are not represented (this is a session inventory, not an env\n * list); environments that no longer exist still get a group keyed by id.\n */\nexport function groupSessionsByEnvironment(\n sessions: readonly Session[],\n environments: readonly Environment[],\n): SessionGroup[] {\n const environmentById = new Map<string, Environment>(environments.map((e) => [e.id, e]));\n const byEnvironment = new Map<string, Session[]>();\n for (const session of sessions) {\n const existing = byEnvironment.get(session.environmentId);\n if (existing) {\n existing.push(session);\n } else {\n byEnvironment.set(session.environmentId, [session]);\n }\n }\n\n const groups: SessionGroup[] = [];\n for (const [environmentId, groupSessions] of byEnvironment) {\n const sorted = [...groupSessions].sort((a, b) => byNewest(a.startedAt, b.startedAt));\n const activeCount = sorted.filter((s) => isActiveSession(s)).length;\n // `sorted` is newest-first, so the head is the most recently started.\n const latestStartedAt = sorted[0]?.startedAt ?? \"\";\n groups.push({\n environmentId,\n environment: environmentById.get(environmentId),\n sessions: sorted,\n activeCount,\n latestStartedAt,\n });\n }\n\n const nameOf = (group: SessionGroup): string =>\n group.environment?.displayName ?? group.environmentId;\n\n return groups.sort((a, b) => {\n const aActive = a.activeCount > 0 ? 1 : 0;\n const bActive = b.activeCount > 0 ? 1 : 0;\n if (aActive !== bActive) {\n return bActive - aActive;\n }\n const byActivity = byNewest(a.latestStartedAt, b.latestStartedAt);\n if (byActivity !== 0) {\n return byActivity;\n }\n return nameOf(a).localeCompare(nameOf(b));\n });\n}\n","/**\n * Sessions activity monitor — a live, environment-grouped table of every\n * session (task-bound and ad-hoc).\n *\n * This is the discovery surface for sessions that aren't reachable through the\n * Tasks tree: ad-hoc `grackle spawn`s, debug sessions, and superseded task\n * attempts. Sessions are grouped under their host environment (the one field\n * every session always has), with the owning task surfaced as a link when\n * present and an `ad-hoc` marker otherwise.\n *\n * Pure presentational component — no `useGrackle()`. All branchy view logic\n * lives in {@link ./sessionsView.js}.\n */\n\nimport { useMemo, useState, type JSX } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { ChevronDown, ChevronRight, ClipboardList, Monitor, Search, Terminal } from \"lucide-react\";\nimport type { Environment, PersonaData, Session, TaskData } from \"../../hooks/types.js\";\nimport { ICON_SM } from \"../../utils/iconSize.js\";\nimport { formatCost, formatTokens } from \"../../utils/format.js\";\nimport { formatRelativeTime } from \"../../utils/time.js\";\nimport {\n buildStatusChips,\n describeSessionStatus,\n filterSessions,\n groupSessionsByEnvironment,\n type SessionGroup,\n type StatusFilter,\n} from \"./sessionsView.js\";\nimport styles from \"./SessionsTable.module.scss\";\n\n/** Props for {@link SessionsTable}. */\nexport interface SessionsTableProps {\n /** All sessions to display (task-bound and ad-hoc). */\n sessions: Session[];\n /** Environments, for group headers and name resolution. */\n environments: Environment[];\n /** Tasks, for resolving task titles on the task chip (optional). */\n tasks?: TaskData[];\n /** Personas, for resolving persona names (optional). */\n personas?: PersonaData[];\n /** Called when a session row is activated. */\n onOpenSession: (sessionId: string) => void;\n /** Called when a session's task chip is activated. */\n onOpenTask: (taskId: string) => void;\n}\n\n/** Entrance/exit animation timing for collapsing a group (seconds). */\nconst COLLAPSE_DURATION_S: number = 0.18;\n\n/** A single session row. */\nfunction SessionRow({\n session,\n taskTitle,\n personaName,\n onOpenSession,\n onOpenTask,\n}: {\n session: Session;\n taskTitle: string | undefined;\n personaName: string | undefined;\n onOpenSession: (sessionId: string) => void;\n onOpenTask: (taskId: string) => void;\n}): JSX.Element {\n const status = describeSessionStatus(session);\n const totalTokens = (session.inputTokens ?? 0) + (session.outputTokens ?? 0);\n const cost = session.costMillicents ?? 0;\n // Bind to a const so TypeScript narrows it to `string` inside the branch\n // below (a property access would not narrow within the onClick closure).\n const taskId = session.taskId;\n\n // The clickable session area and the task-association control are siblings,\n // not nested, so each is an independent, keyboard-accessible <button> (no\n // invalid nested-interactive ARIA, no key-event double-firing between them).\n return (\n <li\n className={styles.row}\n data-status-tone={status.tone}\n data-testid={`session-row-${session.id}`}\n >\n <button\n type=\"button\"\n className={styles.rowButton}\n aria-label={`Open session: ${session.prompt || session.id}`}\n onClick={() => onOpenSession(session.id)}\n data-testid={`session-open-${session.id}`}\n >\n <span className={styles.statusDot} data-tone={status.tone} aria-hidden=\"true\" />\n <div className={styles.rowContent}>\n <div className={styles.promptLine}>\n <span className={styles.prompt}>\n {session.prompt || <span className={styles.promptEmpty}>(no prompt)</span>}\n </span>\n </div>\n <div className={styles.meta}>\n <span className={styles.statusLabel} data-tone={status.tone}>\n {status.label}\n </span>\n <span className={styles.runtimeBadge}>{session.runtime || \"unknown\"}</span>\n {personaName !== undefined && (\n <span className={styles.personaBadge} data-testid={`session-persona-${session.id}`}>\n {personaName}\n </span>\n )}\n <span className={styles.time}>{formatRelativeTime(session.startedAt)}</span>\n {totalTokens > 0 && (\n <span className={styles.tokens}>{formatTokens(totalTokens)} tok</span>\n )}\n {cost > 0 && <span className={styles.cost}>{formatCost(cost)}</span>}\n </div>\n </div>\n </button>\n <div className={styles.association}>\n {taskId ? (\n <button\n type=\"button\"\n className={styles.taskChip}\n title={taskTitle ?? taskId}\n onClick={() => onOpenTask(taskId)}\n data-testid={`session-task-${session.id}`}\n >\n <ClipboardList size={ICON_SM} aria-hidden=\"true\" />\n <span className={styles.taskChipLabel}>{taskTitle ?? taskId}</span>\n </button>\n ) : (\n <span className={styles.adHocChip} data-testid={`session-adhoc-${session.id}`}>\n <Terminal size={ICON_SM} aria-hidden=\"true\" />\n ad-hoc\n </span>\n )}\n </div>\n </li>\n );\n}\n\n/** A collapsible environment group of sessions. */\nfunction EnvironmentGroup({\n group,\n collapsed,\n onToggle,\n taskTitleById,\n personaNameById,\n onOpenSession,\n onOpenTask,\n}: {\n group: SessionGroup;\n collapsed: boolean;\n onToggle: (environmentId: string) => void;\n taskTitleById: Map<string, string>;\n personaNameById: Map<string, string>;\n onOpenSession: (sessionId: string) => void;\n onOpenTask: (taskId: string) => void;\n}): JSX.Element {\n const { environment, environmentId } = group;\n const name = environment?.displayName ?? environmentId;\n\n return (\n <section className={styles.group} data-testid={`session-group-${environmentId}`}>\n <button\n type=\"button\"\n className={styles.groupHeader}\n aria-expanded={!collapsed}\n onClick={() => onToggle(environmentId)}\n data-testid={`session-group-toggle-${environmentId}`}\n >\n {collapsed ? (\n <ChevronRight size={ICON_SM} aria-hidden=\"true\" />\n ) : (\n <ChevronDown size={ICON_SM} aria-hidden=\"true\" />\n )}\n <Monitor size={ICON_SM} className={styles.groupIcon} aria-hidden=\"true\" />\n <span className={styles.groupName}>{name}</span>\n {environment !== undefined ? (\n <span\n className={styles.envStatusDot}\n data-status={environment.status}\n aria-hidden=\"true\"\n />\n ) : (\n <span className={styles.missingEnv}>missing</span>\n )}\n <span className={styles.groupSpacer} />\n {group.activeCount > 0 && (\n <span className={styles.activePill} data-testid={`session-group-active-${environmentId}`}>\n {group.activeCount} active\n </span>\n )}\n <span className={styles.countBadge}>{group.sessions.length}</span>\n </button>\n <AnimatePresence initial={false}>\n {!collapsed && (\n <motion.ul\n className={styles.rows}\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: \"auto\", opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: COLLAPSE_DURATION_S, ease: [0.16, 1, 0.3, 1] }}\n >\n {group.sessions.map((session) => (\n <SessionRow\n key={session.id}\n session={session}\n taskTitle={session.taskId ? taskTitleById.get(session.taskId) : undefined}\n personaName={session.personaId ? personaNameById.get(session.personaId) : undefined}\n onOpenSession={onOpenSession}\n onOpenTask={onOpenTask}\n />\n ))}\n </motion.ul>\n )}\n </AnimatePresence>\n </section>\n );\n}\n\n/**\n * The Sessions activity monitor: a searchable, status-filterable, environment-\n * grouped table of all sessions. Updates live as the parent's session list\n * changes (statuses flow in through the sessions domain hook).\n */\nexport function SessionsTable({\n sessions,\n environments,\n tasks,\n personas,\n onOpenSession,\n onOpenTask,\n}: SessionsTableProps): JSX.Element {\n const [statusFilter, setStatusFilter] = useState<StatusFilter>(\"all\");\n const [query, setQuery] = useState(\"\");\n const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(new Set());\n\n const environmentNameById = useMemo(\n () => new Map(environments.map((e) => [e.id, e.displayName])),\n [environments],\n );\n const taskTitleById = useMemo(() => new Map((tasks ?? []).map((t) => [t.id, t.title])), [tasks]);\n const personaNameById = useMemo(\n () => new Map((personas ?? []).map((p) => [p.id, p.name])),\n [personas],\n );\n\n // Chips reflect the full set; filtering narrows what's shown below.\n const chips = useMemo(() => buildStatusChips(sessions), [sessions]);\n const filtered = useMemo(\n () => filterSessions(sessions, statusFilter, query, environmentNameById),\n [sessions, statusFilter, query, environmentNameById],\n );\n const groups = useMemo(\n () => groupSessionsByEnvironment(filtered, environments),\n [filtered, environments],\n );\n\n const toggleGroup = (environmentId: string): void => {\n setCollapsed((prev) => {\n const next = new Set(prev);\n if (next.has(environmentId)) {\n next.delete(environmentId);\n } else {\n next.add(environmentId);\n }\n return next;\n });\n };\n\n return (\n <div className={styles.container} data-testid=\"sessions-table\">\n <div className={styles.toolbar}>\n <div className={styles.chips} role=\"group\" aria-label=\"Filter by status\">\n {chips.map((chip) => (\n <button\n key={chip.value}\n type=\"button\"\n className={`${styles.chip} ${statusFilter === chip.value ? styles.chipActive : \"\"}`}\n data-tone={chip.value === \"all\" ? undefined : chip.value}\n aria-pressed={statusFilter === chip.value}\n onClick={() => setStatusFilter(chip.value)}\n data-testid={`session-filter-${chip.value}`}\n >\n {chip.label}\n <span className={styles.chipCount}>{chip.count}</span>\n </button>\n ))}\n </div>\n <div className={styles.search}>\n <Search size={ICON_SM} className={styles.searchIcon} aria-hidden=\"true\" />\n <input\n type=\"text\"\n className={styles.searchInput}\n placeholder=\"Search sessions...\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n aria-label=\"Search sessions\"\n data-testid=\"sessions-search\"\n />\n </div>\n </div>\n\n {groups.length === 0 ? (\n <div className={styles.empty} data-testid=\"sessions-empty\">\n <Terminal size={32} aria-hidden=\"true\" />\n <p className={styles.emptyTitle}>\n {sessions.length === 0 ? \"No sessions yet\" : \"No matching sessions\"}\n </p>\n <p className={styles.emptyHint}>\n {sessions.length === 0\n ? \"Spawn an agent or start a task and it will show up here.\"\n : \"Try a different status filter or search term.\"}\n </p>\n </div>\n ) : (\n <div className={styles.scroll}>\n {groups.map((group) => (\n <EnvironmentGroup\n key={group.environmentId}\n group={group}\n collapsed={collapsed.has(group.environmentId)}\n onToggle={toggleGroup}\n taskTitleById={taskTitleById}\n personaNameById={personaNameById}\n onOpenSession={onOpenSession}\n onOpenTask={onOpenTask}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n","/**\n * Pure helper for the Sessions page header summary.\n *\n * Kept in its own module (free of any `@grackle-ai/web-components` barrel\n * import) so it can be unit-tested under vitest — importing the barrel pulls in\n * the dagre-backed coordination graph, which fails to resolve in vitest.\n *\n * @module\n */\n\n/** Build the one-line summary shown under the Sessions page title. */\nexport function buildSummary(\n sessionCount: number,\n activeCount: number,\n environmentCount: number,\n): string {\n if (sessionCount === 0) {\n return \"No sessions yet\";\n }\n const sessionsLabel = sessionCount === 1 ? \"session\" : \"sessions\";\n const envLabel = environmentCount === 1 ? \"environment\" : \"environments\";\n return `${activeCount} active of ${sessionCount} ${sessionsLabel} across ${environmentCount} ${envLabel}`;\n}\n","/**\n * Sessions activity-monitor page.\n *\n * The discovery surface for every session — task-bound and ad-hoc alike —\n * grouped by environment. Page-level component: owns the `useGrackle()` data\n * fetch and navigation; rendering lives in the presentational\n * {@link SessionsTable}.\n */\n\nimport { useMemo, type JSX } from \"react\";\nimport {\n SessionsTable,\n isActiveSession,\n sessionUrl,\n taskUrl,\n useAppNavigate,\n} from \"@grackle-ai/web-components\";\nimport { useGrackle } from \"../context/GrackleContext.js\";\nimport { buildSummary } from \"./sessionsSummary.js\";\nimport styles from \"./SessionsListPage.module.scss\";\n\n/** The Sessions tab — a live, environment-grouped table of all sessions. */\nexport function SessionsListPage(): JSX.Element {\n const {\n sessions: { sessions },\n environments: { environments },\n tasks: { tasks },\n personas: { personas },\n } = useGrackle();\n const navigate = useAppNavigate();\n\n // The sessions domain hook loads on connect and merges live status events, so\n // the list stays current without a page-level refetch. Drop entries with no\n // environment: a `status` event for a not-yet-loaded session id inserts a\n // placeholder with an empty environmentId, which would otherwise render as a\n // blank, nameless environment group until the next list refresh.\n const displayedSessions = useMemo(\n () => sessions.filter((s) => s.environmentId !== \"\"),\n [sessions],\n );\n const activeCount = useMemo(\n () => displayedSessions.filter((s) => isActiveSession(s)).length,\n [displayedSessions],\n );\n const environmentCount = useMemo(\n () => new Set(displayedSessions.map((s) => s.environmentId)).size,\n [displayedSessions],\n );\n\n return (\n <div className={styles.page} data-testid=\"sessions-page\">\n <header className={styles.header}>\n <h1 className={styles.title}>Sessions</h1>\n <p className={styles.subtitle} data-testid=\"sessions-summary\">\n {buildSummary(displayedSessions.length, activeCount, environmentCount)}\n </p>\n </header>\n <div className={styles.tableWrap}>\n <SessionsTable\n sessions={displayedSessions}\n environments={environments}\n tasks={tasks}\n personas={personas}\n onOpenSession={(id) => navigate(sessionUrl(id))}\n onOpenTask={(taskId) => navigate(taskUrl(taskId))}\n />\n </div>\n </div>\n );\n}\n"],"names":["__iconNode","Terminal","createLucideIcon","ACTIVE_TONES","TONE_ORDER","TONE_LABELS","describeStopped","endReason","describeSessionStatus","session","capitalize","isActiveTone","tone","isActiveSession","buildStatusChips","sessions","counts","chips","count","sessionMatchesQuery","query","environmentName","trimmed","filterSessions","status","environmentNameById","byNewest","a","b","groupSessionsByEnvironment","environments","environmentById","e","byEnvironment","existing","groups","environmentId","groupSessions","sorted","activeCount","s","latestStartedAt","_a","nameOf","group","aActive","bActive","byActivity","COLLAPSE_DURATION_S","SessionRow","taskTitle","personaName","onOpenSession","onOpenTask","totalTokens","cost","taskId","jsxs","styles","jsx","formatRelativeTime","formatTokens","formatCost","ClipboardList","ICON_SM","EnvironmentGroup","collapsed","onToggle","taskTitleById","personaNameById","environment","name","ChevronRight","ChevronDown","Monitor","AnimatePresence","motion","SessionsTable","tasks","personas","statusFilter","setStatusFilter","useState","setQuery","setCollapsed","useMemo","t","p","filtered","toggleGroup","prev","next","chip","Search","buildSummary","sessionCount","environmentCount","SessionsListPage","useGrackle","navigate","useAppNavigate","displayedSessions","id","sessionUrl","taskUrl"],"mappings":"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","x_google_ignoreList":[0]}
|
|
1
|
+
{"version":3,"file":"SessionsListPage-DqlXe7kn.js","sources":["../../../../common/temp/node_modules/.pnpm/lucide-react@0.474.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/terminal.js","../../../web-components/src/components/sessions/sessionsView.ts","../../../web-components/src/components/sessions/SessionsTable.tsx","../../src/pages/sessionsSummary.ts","../../src/pages/SessionsListPage.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.474.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"polyline\", { points: \"4 17 10 11 4 5\", key: \"akl6gq\" }],\n [\"line\", { x1: \"12\", x2: \"20\", y1: \"19\", y2: \"19\", key: \"q2wloq\" }]\n];\nconst Terminal = createLucideIcon(\"Terminal\", __iconNode);\n\nexport { __iconNode, Terminal as default };\n//# sourceMappingURL=terminal.js.map\n","/**\n * Pure view-model helpers for the Sessions activity monitor.\n *\n * All branchy logic (status normalization, filtering, grouping, sorting, and\n * filter-chip derivation) lives here so it can be unit-tested directly. The\n * SessionsTable component is intentionally kept to straight-line JSX that\n * consumes these helpers.\n *\n * @module\n */\n\nimport type { Environment, Session } from \"../../hooks/types.js\";\nimport { capitalize } from \"../../utils/sessionStatus.js\";\n\n/**\n * A normalized status \"tone\": a small, stable set of buckets that both the raw\n * database session statuses and the live-event mapped statuses collapse into.\n * Drives colour, iconography, and filtering.\n *\n * This is intentionally distinct from {@link ../../utils/sessionStatus.js}'s\n * `sessionStatusStyle`, which the Coordination graph uses: that mapping\n * de-emphasizes finished sessions (completed -> neutral grey) and has no\n * `pending`/`paused`/endReason concepts. The Sessions activity monitor instead\n * wants a richer, success-positive palette (completed -> green) plus the extra\n * buckets that drive its status filter chips, so the two are kept separate by\n * design rather than unified.\n */\nexport type StatusTone =\n | \"running\"\n | \"idle\"\n | \"pending\"\n | \"success\"\n | \"error\"\n | \"paused\"\n | \"neutral\";\n\n/** A human-readable description of a session's status. */\nexport interface SessionStatusDescriptor {\n /** Normalized tone bucket used for colour/icon/filter. */\n tone: StatusTone;\n /** Capitalized display label (e.g. \"Running\", \"Completed\"). */\n label: string;\n}\n\n/** Tones considered \"active\" (the session is live, not finished or paused). */\nconst ACTIVE_TONES: ReadonlySet<StatusTone> = new Set<StatusTone>([\"running\", \"idle\", \"pending\"]);\n\n/** Display order for status filter chips and grouping. */\nexport const TONE_ORDER: readonly StatusTone[] = [\n \"running\",\n \"idle\",\n \"pending\",\n \"success\",\n \"paused\",\n \"error\",\n \"neutral\",\n];\n\n/** Category labels for status filter chips (broader than per-status labels). */\nexport const TONE_LABELS: Readonly<Record<StatusTone, string>> = {\n running: \"Running\",\n idle: \"Idle\",\n pending: \"Pending\",\n success: \"Completed\",\n paused: \"Paused\",\n error: \"Failed\",\n neutral: \"Other\",\n};\n\n/**\n * Describe a terminal (\"stopped\") session by its `endReason`.\n *\n * Sessions reach a generic `stopped` status via live status events; the\n * `endReason` distinguishes a clean completion from an error/kill.\n */\nfunction describeStopped(endReason: string | undefined): SessionStatusDescriptor {\n // Cases mirror grackle.END_REASON. A non-clean stop (killed/interrupted/\n // terminated/budget_exceeded/error) is an error tone so it lands in the\n // \"Failed\" filter bucket rather than the neutral \"Stopped\" fallback.\n switch (endReason) {\n case \"completed\":\n return { tone: \"success\", label: \"Completed\" };\n case \"killed\":\n return { tone: \"error\", label: \"Killed\" };\n case \"interrupted\":\n return { tone: \"error\", label: \"Interrupted\" };\n case \"terminated\":\n return { tone: \"error\", label: \"Terminated\" };\n case \"budget_exceeded\":\n return { tone: \"error\", label: \"Budget exceeded\" };\n case \"error\":\n return { tone: \"error\", label: \"Error\" };\n default:\n return { tone: \"neutral\", label: \"Stopped\" };\n }\n}\n\n/**\n * Map a session's raw status (and `endReason`) to a normalized tone + label.\n *\n * Handles both the raw database statuses returned by `ListSessions`\n * (`running`, `completed`, `failed`, ...) and the collapsed `stopped` status\n * produced by live status events (disambiguated via `endReason`).\n */\nexport function describeSessionStatus(\n session: Pick<Session, \"status\" | \"endReason\">,\n): SessionStatusDescriptor {\n switch (session.status) {\n case \"running\":\n return { tone: \"running\", label: \"Running\" };\n case \"idle\":\n case \"waiting_input\":\n return { tone: \"idle\", label: \"Idle\" };\n case \"pending\":\n return { tone: \"pending\", label: \"Pending\" };\n case \"suspended\":\n return { tone: \"paused\", label: \"Suspended\" };\n case \"hibernating\":\n return { tone: \"paused\", label: \"Hibernating\" };\n case \"completed\":\n return { tone: \"success\", label: \"Completed\" };\n case \"failed\":\n return { tone: \"error\", label: \"Failed\" };\n case \"killed\":\n return { tone: \"error\", label: \"Killed\" };\n case \"interrupted\":\n return { tone: \"error\", label: \"Interrupted\" };\n case \"terminated\":\n return { tone: \"error\", label: \"Terminated\" };\n case \"stopped\":\n return describeStopped(session.endReason);\n default:\n return { tone: \"neutral\", label: capitalize(session.status) || \"Unknown\" };\n }\n}\n\n/** Whether a tone represents a live (active) session. */\nexport function isActiveTone(tone: StatusTone): boolean {\n return ACTIVE_TONES.has(tone);\n}\n\n/** Whether a session is currently active (live), by its normalized tone. */\nexport function isActiveSession(session: Pick<Session, \"status\" | \"endReason\">): boolean {\n return isActiveTone(describeSessionStatus(session).tone);\n}\n\n/** A status filter selection: a specific tone, or `\"all\"` for no filter. */\nexport type StatusFilter = StatusTone | \"all\";\n\n/** A single status filter chip with its display label and matching count. */\nexport interface StatusChip {\n /** Filter value this chip selects. */\n value: StatusFilter;\n /** Display label. */\n label: string;\n /** Number of sessions matching this chip. */\n count: number;\n}\n\n/**\n * Build the ordered list of status filter chips for a set of sessions.\n *\n * Always begins with an \"All\" chip, followed by one chip per tone that is\n * actually present (in {@link TONE_ORDER}), each annotated with its count.\n * Chips for absent tones are omitted so the bar stays uncluttered.\n */\nexport function buildStatusChips(sessions: readonly Session[]): StatusChip[] {\n const counts = new Map<StatusTone, number>();\n for (const session of sessions) {\n const { tone } = describeSessionStatus(session);\n counts.set(tone, (counts.get(tone) ?? 0) + 1);\n }\n const chips: StatusChip[] = [{ value: \"all\", label: \"All\", count: sessions.length }];\n for (const tone of TONE_ORDER) {\n const count = counts.get(tone);\n if (count !== undefined && count > 0) {\n chips.push({ value: tone, label: TONE_LABELS[tone], count });\n }\n }\n return chips;\n}\n\n/**\n * Whether a session matches a free-text query, checked against its prompt,\n * runtime, id, task id, and resolved environment name. An empty query matches\n * everything. Matching is case-insensitive.\n */\nexport function sessionMatchesQuery(\n session: Session,\n query: string,\n environmentName: string,\n): boolean {\n const trimmed = query.trim().toLowerCase();\n if (trimmed.length === 0) {\n return true;\n }\n const haystack = [\n session.prompt,\n session.runtime,\n session.id,\n session.taskId ?? \"\",\n environmentName,\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(trimmed);\n}\n\n/**\n * Apply the status-tone filter and free-text query to a list of sessions.\n *\n * @param sessions - Sessions to filter.\n * @param status - Selected status filter, or `\"all\"`.\n * @param query - Free-text query (empty string matches all).\n * @param environmentNameById - Map of environment id to display name, used for\n * both query matching and so a missing environment falls back to its id.\n */\nexport function filterSessions(\n sessions: readonly Session[],\n status: StatusFilter,\n query: string,\n environmentNameById: ReadonlyMap<string, string>,\n): Session[] {\n return sessions.filter((session) => {\n if (status !== \"all\" && describeSessionStatus(session).tone !== status) {\n return false;\n }\n const environmentName = environmentNameById.get(session.environmentId) ?? session.environmentId;\n return sessionMatchesQuery(session, query, environmentName);\n });\n}\n\n/** A group of sessions belonging to a single environment. */\nexport interface SessionGroup {\n /** Environment id this group is keyed by. */\n environmentId: string;\n /** The environment, if it still exists (may be undefined for a gone env). */\n environment: Environment | undefined;\n /** Sessions in this group, newest first. */\n sessions: Session[];\n /** Number of active (live) sessions in this group. */\n activeCount: number;\n /** ISO timestamp of the most recently started session in this group. */\n latestStartedAt: string;\n}\n\n/** Compare two ISO timestamps for a newest-first sort. */\nfunction byNewest(a: string, b: string): number {\n return b.localeCompare(a);\n}\n\n/**\n * Group sessions by their environment, returning groups ready for display.\n *\n * Within each group, sessions are ordered newest-first. Groups are ordered so\n * that those with active sessions appear first, then by most-recent activity,\n * with a stable alphabetical tie-break on environment name. Environments with\n * no sessions are not represented (this is a session inventory, not an env\n * list); environments that no longer exist still get a group keyed by id.\n */\nexport function groupSessionsByEnvironment(\n sessions: readonly Session[],\n environments: readonly Environment[],\n): SessionGroup[] {\n const environmentById = new Map<string, Environment>(environments.map((e) => [e.id, e]));\n const byEnvironment = new Map<string, Session[]>();\n for (const session of sessions) {\n const existing = byEnvironment.get(session.environmentId);\n if (existing) {\n existing.push(session);\n } else {\n byEnvironment.set(session.environmentId, [session]);\n }\n }\n\n const groups: SessionGroup[] = [];\n for (const [environmentId, groupSessions] of byEnvironment) {\n const sorted = [...groupSessions].sort((a, b) => byNewest(a.startedAt, b.startedAt));\n const activeCount = sorted.filter((s) => isActiveSession(s)).length;\n // `sorted` is newest-first, so the head is the most recently started.\n const latestStartedAt = sorted[0]?.startedAt ?? \"\";\n groups.push({\n environmentId,\n environment: environmentById.get(environmentId),\n sessions: sorted,\n activeCount,\n latestStartedAt,\n });\n }\n\n const nameOf = (group: SessionGroup): string =>\n group.environment?.displayName ?? group.environmentId;\n\n return groups.sort((a, b) => {\n const aActive = a.activeCount > 0 ? 1 : 0;\n const bActive = b.activeCount > 0 ? 1 : 0;\n if (aActive !== bActive) {\n return bActive - aActive;\n }\n const byActivity = byNewest(a.latestStartedAt, b.latestStartedAt);\n if (byActivity !== 0) {\n return byActivity;\n }\n return nameOf(a).localeCompare(nameOf(b));\n });\n}\n","/**\n * Sessions activity monitor — a live, environment-grouped table of every\n * session (task-bound and ad-hoc).\n *\n * This is the discovery surface for sessions that aren't reachable through the\n * Tasks tree: ad-hoc `grackle spawn`s, debug sessions, and superseded task\n * attempts. Sessions are grouped under their host environment (the one field\n * every session always has), with the owning task surfaced as a link when\n * present and an `ad-hoc` marker otherwise.\n *\n * Pure presentational component — no `useGrackle()`. All branchy view logic\n * lives in {@link ./sessionsView.js}.\n */\n\nimport { useMemo, useState, type JSX } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { ChevronDown, ChevronRight, ClipboardList, Monitor, Search, Terminal } from \"lucide-react\";\nimport type { Environment, PersonaData, Session, TaskData } from \"../../hooks/types.js\";\nimport { ICON_SM } from \"../../utils/iconSize.js\";\nimport { formatCost, formatTokens } from \"../../utils/format.js\";\nimport { formatRelativeTime } from \"../../utils/time.js\";\nimport {\n buildStatusChips,\n describeSessionStatus,\n filterSessions,\n groupSessionsByEnvironment,\n type SessionGroup,\n type StatusFilter,\n} from \"./sessionsView.js\";\nimport styles from \"./SessionsTable.module.scss\";\n\n/** Props for {@link SessionsTable}. */\nexport interface SessionsTableProps {\n /** All sessions to display (task-bound and ad-hoc). */\n sessions: Session[];\n /** Environments, for group headers and name resolution. */\n environments: Environment[];\n /** Tasks, for resolving task titles on the task chip (optional). */\n tasks?: TaskData[];\n /** Personas, for resolving persona names (optional). */\n personas?: PersonaData[];\n /** Called when a session row is activated. */\n onOpenSession: (sessionId: string) => void;\n /** Called when a session's task chip is activated. */\n onOpenTask: (taskId: string) => void;\n}\n\n/** Entrance/exit animation timing for collapsing a group (seconds). */\nconst COLLAPSE_DURATION_S: number = 0.18;\n\n/** A single session row. */\nfunction SessionRow({\n session,\n taskTitle,\n personaName,\n onOpenSession,\n onOpenTask,\n}: {\n session: Session;\n taskTitle: string | undefined;\n personaName: string | undefined;\n onOpenSession: (sessionId: string) => void;\n onOpenTask: (taskId: string) => void;\n}): JSX.Element {\n const status = describeSessionStatus(session);\n const totalTokens = (session.inputTokens ?? 0) + (session.outputTokens ?? 0);\n const cost = session.costMillicents ?? 0;\n // Bind to a const so TypeScript narrows it to `string` inside the branch\n // below (a property access would not narrow within the onClick closure).\n const taskId = session.taskId;\n\n // The clickable session area and the task-association control are siblings,\n // not nested, so each is an independent, keyboard-accessible <button> (no\n // invalid nested-interactive ARIA, no key-event double-firing between them).\n return (\n <li\n className={styles.row}\n data-status-tone={status.tone}\n data-testid={`session-row-${session.id}`}\n >\n <button\n type=\"button\"\n className={styles.rowButton}\n aria-label={`Open session: ${session.prompt || session.id}`}\n onClick={() => onOpenSession(session.id)}\n data-testid={`session-open-${session.id}`}\n >\n <span className={styles.statusDot} data-tone={status.tone} aria-hidden=\"true\" />\n <div className={styles.rowContent}>\n <div className={styles.promptLine}>\n <span className={styles.prompt}>\n {session.prompt || <span className={styles.promptEmpty}>(no prompt)</span>}\n </span>\n </div>\n <div className={styles.meta}>\n <span className={styles.statusLabel} data-tone={status.tone}>\n {status.label}\n </span>\n <span className={styles.runtimeBadge}>{session.runtime || \"unknown\"}</span>\n {personaName !== undefined && (\n <span className={styles.personaBadge} data-testid={`session-persona-${session.id}`}>\n {personaName}\n </span>\n )}\n <span className={styles.time}>{formatRelativeTime(session.startedAt)}</span>\n {totalTokens > 0 && (\n <span className={styles.tokens}>{formatTokens(totalTokens)} tok</span>\n )}\n {cost > 0 && <span className={styles.cost}>{formatCost(cost)}</span>}\n </div>\n </div>\n </button>\n <div className={styles.association}>\n {taskId ? (\n <button\n type=\"button\"\n className={styles.taskChip}\n title={taskTitle ?? taskId}\n onClick={() => onOpenTask(taskId)}\n data-testid={`session-task-${session.id}`}\n >\n <ClipboardList size={ICON_SM} aria-hidden=\"true\" />\n <span className={styles.taskChipLabel}>{taskTitle ?? taskId}</span>\n </button>\n ) : (\n <span className={styles.adHocChip} data-testid={`session-adhoc-${session.id}`}>\n <Terminal size={ICON_SM} aria-hidden=\"true\" />\n ad-hoc\n </span>\n )}\n </div>\n </li>\n );\n}\n\n/** A collapsible environment group of sessions. */\nfunction EnvironmentGroup({\n group,\n collapsed,\n onToggle,\n taskTitleById,\n personaNameById,\n onOpenSession,\n onOpenTask,\n}: {\n group: SessionGroup;\n collapsed: boolean;\n onToggle: (environmentId: string) => void;\n taskTitleById: Map<string, string>;\n personaNameById: Map<string, string>;\n onOpenSession: (sessionId: string) => void;\n onOpenTask: (taskId: string) => void;\n}): JSX.Element {\n const { environment, environmentId } = group;\n const name = environment?.displayName ?? environmentId;\n\n return (\n <section className={styles.group} data-testid={`session-group-${environmentId}`}>\n <button\n type=\"button\"\n className={styles.groupHeader}\n aria-expanded={!collapsed}\n onClick={() => onToggle(environmentId)}\n data-testid={`session-group-toggle-${environmentId}`}\n >\n {collapsed ? (\n <ChevronRight size={ICON_SM} aria-hidden=\"true\" />\n ) : (\n <ChevronDown size={ICON_SM} aria-hidden=\"true\" />\n )}\n <Monitor size={ICON_SM} className={styles.groupIcon} aria-hidden=\"true\" />\n <span className={styles.groupName}>{name}</span>\n {environment !== undefined ? (\n <span\n className={styles.envStatusDot}\n data-status={environment.status}\n aria-hidden=\"true\"\n />\n ) : (\n <span className={styles.missingEnv}>missing</span>\n )}\n <span className={styles.groupSpacer} />\n {group.activeCount > 0 && (\n <span className={styles.activePill} data-testid={`session-group-active-${environmentId}`}>\n {group.activeCount} active\n </span>\n )}\n <span className={styles.countBadge}>{group.sessions.length}</span>\n </button>\n <AnimatePresence initial={false}>\n {!collapsed && (\n <motion.ul\n className={styles.rows}\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: \"auto\", opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: COLLAPSE_DURATION_S, ease: [0.16, 1, 0.3, 1] }}\n >\n {group.sessions.map((session) => (\n <SessionRow\n key={session.id}\n session={session}\n taskTitle={session.taskId ? taskTitleById.get(session.taskId) : undefined}\n personaName={session.personaId ? personaNameById.get(session.personaId) : undefined}\n onOpenSession={onOpenSession}\n onOpenTask={onOpenTask}\n />\n ))}\n </motion.ul>\n )}\n </AnimatePresence>\n </section>\n );\n}\n\n/**\n * The Sessions activity monitor: a searchable, status-filterable, environment-\n * grouped table of all sessions. Updates live as the parent's session list\n * changes (statuses flow in through the sessions domain hook).\n */\nexport function SessionsTable({\n sessions,\n environments,\n tasks,\n personas,\n onOpenSession,\n onOpenTask,\n}: SessionsTableProps): JSX.Element {\n const [statusFilter, setStatusFilter] = useState<StatusFilter>(\"all\");\n const [query, setQuery] = useState(\"\");\n const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(new Set());\n\n const environmentNameById = useMemo(\n () => new Map(environments.map((e) => [e.id, e.displayName])),\n [environments],\n );\n const taskTitleById = useMemo(() => new Map((tasks ?? []).map((t) => [t.id, t.title])), [tasks]);\n const personaNameById = useMemo(\n () => new Map((personas ?? []).map((p) => [p.id, p.name])),\n [personas],\n );\n\n // Chips reflect the full set; filtering narrows what's shown below.\n const chips = useMemo(() => buildStatusChips(sessions), [sessions]);\n const filtered = useMemo(\n () => filterSessions(sessions, statusFilter, query, environmentNameById),\n [sessions, statusFilter, query, environmentNameById],\n );\n const groups = useMemo(\n () => groupSessionsByEnvironment(filtered, environments),\n [filtered, environments],\n );\n\n const toggleGroup = (environmentId: string): void => {\n setCollapsed((prev) => {\n const next = new Set(prev);\n if (next.has(environmentId)) {\n next.delete(environmentId);\n } else {\n next.add(environmentId);\n }\n return next;\n });\n };\n\n return (\n <div className={styles.container} data-testid=\"sessions-table\">\n <div className={styles.toolbar}>\n <div className={styles.chips} role=\"group\" aria-label=\"Filter by status\">\n {chips.map((chip) => (\n <button\n key={chip.value}\n type=\"button\"\n className={`${styles.chip} ${statusFilter === chip.value ? styles.chipActive : \"\"}`}\n data-tone={chip.value === \"all\" ? undefined : chip.value}\n aria-pressed={statusFilter === chip.value}\n onClick={() => setStatusFilter(chip.value)}\n data-testid={`session-filter-${chip.value}`}\n >\n {chip.label}\n <span className={styles.chipCount}>{chip.count}</span>\n </button>\n ))}\n </div>\n <div className={styles.search}>\n <Search size={ICON_SM} className={styles.searchIcon} aria-hidden=\"true\" />\n <input\n type=\"text\"\n className={styles.searchInput}\n placeholder=\"Search sessions...\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n aria-label=\"Search sessions\"\n data-testid=\"sessions-search\"\n />\n </div>\n </div>\n\n {groups.length === 0 ? (\n <div className={styles.empty} data-testid=\"sessions-empty\">\n <Terminal size={32} aria-hidden=\"true\" />\n <p className={styles.emptyTitle}>\n {sessions.length === 0 ? \"No sessions yet\" : \"No matching sessions\"}\n </p>\n <p className={styles.emptyHint}>\n {sessions.length === 0\n ? \"Spawn an agent or start a task and it will show up here.\"\n : \"Try a different status filter or search term.\"}\n </p>\n </div>\n ) : (\n <div className={styles.scroll}>\n {groups.map((group) => (\n <EnvironmentGroup\n key={group.environmentId}\n group={group}\n collapsed={collapsed.has(group.environmentId)}\n onToggle={toggleGroup}\n taskTitleById={taskTitleById}\n personaNameById={personaNameById}\n onOpenSession={onOpenSession}\n onOpenTask={onOpenTask}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n","/**\n * Pure helper for the Sessions page header summary.\n *\n * Kept in its own module (free of any `@grackle-ai/web-components` barrel\n * import) so it can be unit-tested under vitest — importing the barrel pulls in\n * the dagre-backed coordination graph, which fails to resolve in vitest.\n *\n * @module\n */\n\n/** Build the one-line summary shown under the Sessions page title. */\nexport function buildSummary(\n sessionCount: number,\n activeCount: number,\n environmentCount: number,\n): string {\n if (sessionCount === 0) {\n return \"No sessions yet\";\n }\n const sessionsLabel = sessionCount === 1 ? \"session\" : \"sessions\";\n const envLabel = environmentCount === 1 ? \"environment\" : \"environments\";\n return `${activeCount} active of ${sessionCount} ${sessionsLabel} across ${environmentCount} ${envLabel}`;\n}\n","/**\n * Sessions activity-monitor page.\n *\n * The discovery surface for every session — task-bound and ad-hoc alike —\n * grouped by environment. Page-level component: owns the `useGrackle()` data\n * fetch and navigation; rendering lives in the presentational\n * {@link SessionsTable}.\n */\n\nimport { useMemo, type JSX } from \"react\";\nimport {\n SessionsTable,\n isActiveSession,\n sessionUrl,\n taskUrl,\n useAppNavigate,\n} from \"@grackle-ai/web-components\";\nimport { useGrackle } from \"../context/GrackleContext.js\";\nimport { buildSummary } from \"./sessionsSummary.js\";\nimport styles from \"./SessionsListPage.module.scss\";\n\n/** The Sessions tab — a live, environment-grouped table of all sessions. */\nexport function SessionsListPage(): JSX.Element {\n const {\n sessions: { sessions },\n environments: { environments },\n tasks: { tasks },\n personas: { personas },\n } = useGrackle();\n const navigate = useAppNavigate();\n\n // The sessions domain hook loads on connect and merges live status events, so\n // the list stays current without a page-level refetch. Drop entries with no\n // environment: a `status` event for a not-yet-loaded session id inserts a\n // placeholder with an empty environmentId, which would otherwise render as a\n // blank, nameless environment group until the next list refresh.\n const displayedSessions = useMemo(\n () => sessions.filter((s) => s.environmentId !== \"\"),\n [sessions],\n );\n const activeCount = useMemo(\n () => displayedSessions.filter((s) => isActiveSession(s)).length,\n [displayedSessions],\n );\n const environmentCount = useMemo(\n () => new Set(displayedSessions.map((s) => s.environmentId)).size,\n [displayedSessions],\n );\n\n return (\n <div className={styles.page} data-testid=\"sessions-page\">\n <header className={styles.header}>\n <h1 className={styles.title}>Sessions</h1>\n <p className={styles.subtitle} data-testid=\"sessions-summary\">\n {buildSummary(displayedSessions.length, activeCount, environmentCount)}\n </p>\n </header>\n <div className={styles.tableWrap}>\n <SessionsTable\n sessions={displayedSessions}\n environments={environments}\n tasks={tasks}\n personas={personas}\n onOpenSession={(id) => navigate(sessionUrl(id))}\n onOpenTask={(taskId) => navigate(taskUrl(taskId))}\n />\n </div>\n </div>\n );\n}\n"],"names":["__iconNode","Terminal","createLucideIcon","ACTIVE_TONES","TONE_ORDER","TONE_LABELS","describeStopped","endReason","describeSessionStatus","session","capitalize","isActiveTone","tone","isActiveSession","buildStatusChips","sessions","counts","chips","count","sessionMatchesQuery","query","environmentName","trimmed","filterSessions","status","environmentNameById","byNewest","a","b","groupSessionsByEnvironment","environments","environmentById","e","byEnvironment","existing","groups","environmentId","groupSessions","sorted","activeCount","s","latestStartedAt","_a","nameOf","group","aActive","bActive","byActivity","COLLAPSE_DURATION_S","SessionRow","taskTitle","personaName","onOpenSession","onOpenTask","totalTokens","cost","taskId","jsxs","styles","jsx","formatRelativeTime","formatTokens","formatCost","ClipboardList","ICON_SM","EnvironmentGroup","collapsed","onToggle","taskTitleById","personaNameById","environment","name","ChevronRight","ChevronDown","Monitor","AnimatePresence","motion","SessionsTable","tasks","personas","statusFilter","setStatusFilter","useState","setQuery","setCollapsed","useMemo","t","p","filtered","toggleGroup","prev","next","chip","Search","buildSummary","sessionCount","environmentCount","SessionsListPage","useGrackle","navigate","useAppNavigate","displayedSessions","id","sessionUrl","taskUrl"],"mappings":"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","x_google_ignoreList":[0]}
|