(w: WorkspaceMeta)
| 919 | let shuttingDown = false; |
| 920 | |
| 921 | const publicMeta = async (w: WorkspaceMeta): Promise<unknown> => { |
| 922 | const metadata = await readWorkspaceMetadata(w.dir); |
| 923 | const live = pool.liveSessionsFor(w.id); |
| 924 | await sessionRegistry.ensureLoaded(w.id).catch(() => undefined); |
| 925 | const liveById = new Map(live.map((l) => [l.id, l])); |
| 926 | const sessions = sessionRegistry.listFor(w.id).map((r) => { |
| 927 | const liveEntry = liveById.get(r.id); |
| 928 | return { |
| 929 | id: r.id, |
| 930 | wsId: r.wsId, |
| 931 | agent: r.agent, |
| 932 | name: r.name, |
| 933 | createdAt: r.createdAt, |
| 934 | lastActiveAt: r.lastActiveAt, |
| 935 | state: r.state === 'running' && liveEntry ? 'running' : 'paused', |
| 936 | agentSessionId: liveEntry?.agentSessionId ?? r.resumeHint?.value ?? null, |
| 937 | pid: liveEntry?.pid ?? null, |
| 938 | startedAt: liveEntry?.startedAt ?? null, |
| 939 | title: r.title ?? null, |
| 940 | }; |
| 941 | }); |
| 942 | // Workspace AI provider override signals — read by the Overview |
| 943 | // dashboard for the "⚙ Workspace override" footer per card. Cheap |
| 944 | // (single statSync each) so it's safe on the regular list poll. |
| 945 | const agentOverride = { |
| 946 | claude: existsSync(join(w.dir, '.claude', 'settings.local.json')), |
| 947 | codex: existsSync(join(w.dir, '.codex')), |
| 948 | opencode: existsSync(join(w.dir, 'opencode.json')), |
| 949 | pi: existsSync(join(w.dir, '.pi-agent')), |
| 950 | }; |
| 951 | // Version lineage + upgrade hint. We read the instance README's |
| 952 | // frontmatter for the "current" version each list call — cheap (one |
| 953 | // file read per workspace) and authoritative: the agent self-upgrades |
| 954 | // by bumping that frontmatter, so reading it live makes the badge |
| 955 | // disappear without any extra plumbing. |
| 956 | let currentVersion: string | undefined; |
| 957 | let upgradeAvailable: { from: string; to: string } | null = null; |
| 958 | if (w.template) { |
| 959 | const tpl = templates.get(w.template); |
| 960 | if (tpl) { |
| 961 | const instanceReadme = join(w.dir, 'README.md'); |
| 962 | const fromInstance = existsSync(instanceReadme) |
| 963 | ? await readReadmeVersion(instanceReadme).catch(() => undefined) |
| 964 | : undefined; |
| 965 | currentVersion = fromInstance ?? w.spawnedFromVersion; |
| 966 | // Surface the badge when the template has moved past whatever |
| 967 | // version the instance self-claims. `compareVersions` returns 1 |
| 968 | // when tpl.version > currentVersion. Missing currentVersion (and |
| 969 | // no spawnedFromVersion) → no signal, don't guess. |
| 970 | if (currentVersion && compareVersions(tpl.version, currentVersion) > 0) { |
| 971 | upgradeAvailable = { from: currentVersion, to: tpl.version }; |
| 972 | } |
| 973 | } |
| 974 | } |
| 975 | return { |
| 976 | ...w, |
| 977 | ...(metadata.ok ? metadata.metadata : {}), |
| 978 | ...(!metadata.ok && metadata.reason === 'invalid' ? { metadataError: metadata.error } : {}), |
nothing calls this directly
no test coverage detected