From 3bd3d53bdc6ca7e873a08cc2b659972395d7a05b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:10:20 -0700 Subject: [PATCH 1/3] fix(v2): the Your Team card marks a seat's location from its stamped host only (TASK-360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The card inferred where a seat runs from its runtime type when no host was stamped. Codex, moltbot, native and internal read as cloud, so seats run on a Mac under agent run were labelled "Runs on Commonly". A seat with no host now gets no mark. A byo seat gets the laptop mark only for its installer. The entitlement gate (isCloudRuntime) is unchanged; see the PR body. Written by Wisp, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 Co-authored-by: wisp Agent-Model: claude/claude-haiku-5-5/default --- .../v2/__tests__/V2YourTeamPage.where.test.tsx | 15 +++++++++------ frontend/src/v2/components/V2YourTeamPage.tsx | 17 +++++------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx b/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx index b52de8e656..58b1b3db9e 100644 --- a/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx +++ b/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx @@ -31,13 +31,14 @@ const agent = (overrides) => ({ }); const agents = [ - agent({ name: 'scout', displayName: 'Scout', userId: 'user-scout', installedBy: 'me', runtime: { runtimeType: 'hosted' } }), + agent({ name: 'scout', displayName: 'Scout', userId: 'user-scout', installedBy: 'me', runtime: { runtimeType: 'hosted', host: 'cloud' } }), agent({ name: 'codex', displayName: 'Codex', userId: 'user-codex', runtime: { runtimeType: 'codex' } }), + agent({ name: 'agentrun', displayName: 'Agentrun', userId: 'user-agentrun', installedBy: 'me', runtime: { runtimeType: 'codex' } }), agent({ name: 'laptop', displayName: 'Laptop', userId: 'user-laptop', installedBy: 'me', runtime: { runtimeType: 'native', host: 'byo' } }), - agent({ name: 'hook', displayName: 'Hook', userId: 'user-hook', installedBy: 'me', runtime: { runtimeType: 'webhook' } }), - agent({ name: 'daemon', displayName: 'Daemon', userId: 'user-daemon', installedBy: 'me', runtime: { runtimeType: 'wrapper', adapter: 'codex' } }), - agent({ name: 'teammate', displayName: 'Teammate', userId: 'user-teammate', installedBy: 'someone-else', runtime: { runtimeType: 'webhook' } }), - agent({ name: 'teamdaemon', displayName: 'Teamdaemon', userId: 'user-teamdaemon', installedBy: 'someone-else', runtime: { runtimeType: 'wrapper', adapter: 'codex' } }), + agent({ name: 'hook', displayName: 'Hook', userId: 'user-hook', installedBy: 'me', runtime: { runtimeType: 'webhook', host: 'byo' } }), + agent({ name: 'daemon', displayName: 'Daemon', userId: 'user-daemon', installedBy: 'me', runtime: { runtimeType: 'wrapper', adapter: 'codex', host: 'byo' } }), + agent({ name: 'teammate', displayName: 'Teammate', userId: 'user-teammate', installedBy: 'someone-else', runtime: { runtimeType: 'webhook', host: 'byo' } }), + agent({ name: 'teamdaemon', displayName: 'Teamdaemon', userId: 'user-teamdaemon', installedBy: 'someone-else', runtime: { runtimeType: 'wrapper', adapter: 'codex', host: 'byo' } }), agent({ name: 'cloudhost', displayName: 'Cloudhost', userId: 'user-cloudhost', runtime: { runtimeType: 'something-new', host: 'cloud' } }), agent({ name: 'mystery', displayName: 'Mystery', userId: 'user-mystery', runtime: { runtimeType: 'something-new' } }), ]; @@ -74,7 +75,9 @@ describe('V2YourTeamPage location mark and hosted meter', () => { renderPage(); const cardOf = async (name) => (await screen.findByText(name)).closest('article'); expect(within(await cardOf('Scout')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); - expect(within(await cardOf('Codex')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); + // A codex seat with no host stamp gets no mark, even though codex is a cloud type. + expect(within(await cardOf('Codex')).queryByRole('img', { name: /Runs on/ })).toBeNull(); + expect(within(await cardOf('Agentrun')).queryByRole('img', { name: /Runs on/ })).toBeNull(); expect(within(await cardOf('Laptop')).getByRole('img', { name: 'Runs on your computer' })).toBeInTheDocument(); expect(within(await cardOf('Hook')).getByRole('img', { name: 'Runs on your computer' })).toBeInTheDocument(); expect(within(await cardOf('Cloudhost')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); diff --git a/frontend/src/v2/components/V2YourTeamPage.tsx b/frontend/src/v2/components/V2YourTeamPage.tsx index 2c52275903..3c41ba62f9 100644 --- a/frontend/src/v2/components/V2YourTeamPage.tsx +++ b/frontend/src/v2/components/V2YourTeamPage.tsx @@ -103,23 +103,16 @@ const STATE_DOT = '\u25CF'; const agentKey = (a: { name: string; instanceId?: string }) => `${a.name}:${a.instanceId || 'default'}`; -// Where a seat runs, mirroring agentIdentityService.isCloudRuntime (host first). -// 'hosted' is the ADR-023 hosted runtime, which Commonly runs, so it is cloud even -// though isCloudRuntime excludes it from the operator gate. A seat we can't -// classify gets no mark rather than a guessed one. -// A laptop mark means the viewer's own machine, so a seat another teammate installed -// gets no laptop mark: we don't know which machine it runs on. +// Where a seat runs. Only the stamped host decides it: config.runtime.host is +// 'cloud' (Commonly runs it) or 'byo' (a machine polling in). A seat with no host +// gets no mark. Inferring from runtime type labelled agent-run seats on a Mac as +// cloud, so the card no longer guesses. A laptop mark means the viewer's own +// machine, so a byo seat another teammate installed gets no laptop mark either. type RunsOn = 'cloud' | 'laptop' | null; -const CLOUD_RUNTIME_TYPES = new Set(['hosted', 'moltbot', 'internal', 'native', 'managed-agents', 'codex']); -// 'wrapper' is a daemon-bound seat (pod-agents projects it over the harness label). -const BYO_RUNTIME_TYPES = new Set(['webhook', 'claude-code', 'wrapper']); const runsOn = (a: AgentInstallationSummary, viewerId: string | undefined): RunsOn => { - const type = (a.runtime?.runtimeType || '').trim().toLowerCase(); const host = (a.runtime?.host || '').trim().toLowerCase(); if (host === 'cloud') return 'cloud'; if (host === 'byo') return a.installedBy === viewerId ? 'laptop' : null; - if (CLOUD_RUNTIME_TYPES.has(type)) return 'cloud'; - if (BYO_RUNTIME_TYPES.has(type)) return a.installedBy === viewerId ? 'laptop' : null; return null; }; From a1b396acc4ec1fbe6aa625b6c13e590d942373ec Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:26:59 -0700 Subject: [PATCH 2/3] fix(v2): hosted, native, internal and moltbot seats read as cloud without a stamp (TASK-360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only a stamped host decided a seat's location, so a new hosted install with no host lost its cloud mark. Runtime types with no BYO form now read as cloud when unstamped: hosted, native, internal, moltbot. codex, claude-code, webhook and wrapper can run on someone's machine, so they read only the stamped host. That removes codex from the cloud set, which was the agent-run mislabel. The entitlement gate (isCloudRuntime) is unchanged. Written by Wisp, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 Co-authored-by: wisp Agent-Model: claude/claude-haiku-5-5/default --- .../v2/__tests__/V2YourTeamPage.where.test.tsx | 7 +++++-- frontend/src/v2/components/V2YourTeamPage.tsx | 17 +++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx b/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx index 58b1b3db9e..11d7bfe2f9 100644 --- a/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx +++ b/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx @@ -31,7 +31,8 @@ const agent = (overrides) => ({ }); const agents = [ - agent({ name: 'scout', displayName: 'Scout', userId: 'user-scout', installedBy: 'me', runtime: { runtimeType: 'hosted', host: 'cloud' } }), + agent({ name: 'scout', displayName: 'Scout', userId: 'user-scout', installedBy: 'me', runtime: { runtimeType: 'hosted' } }), + agent({ name: 'stampedhost', displayName: 'Stampedhost', userId: 'user-stampedhost', runtime: { runtimeType: 'codex', host: 'cloud' } }), agent({ name: 'codex', displayName: 'Codex', userId: 'user-codex', runtime: { runtimeType: 'codex' } }), agent({ name: 'agentrun', displayName: 'Agentrun', userId: 'user-agentrun', installedBy: 'me', runtime: { runtimeType: 'codex' } }), agent({ name: 'laptop', displayName: 'Laptop', userId: 'user-laptop', installedBy: 'me', runtime: { runtimeType: 'native', host: 'byo' } }), @@ -75,12 +76,14 @@ describe('V2YourTeamPage location mark and hosted meter', () => { renderPage(); const cardOf = async (name) => (await screen.findByText(name)).closest('article'); expect(within(await cardOf('Scout')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); - // A codex seat with no host stamp gets no mark, even though codex is a cloud type. + // A hosted seat with no stamp is cloud by its type: it has no BYO form. + // A codex seat with no host stamp gets no mark: codex can run on someone's machine. expect(within(await cardOf('Codex')).queryByRole('img', { name: /Runs on/ })).toBeNull(); expect(within(await cardOf('Agentrun')).queryByRole('img', { name: /Runs on/ })).toBeNull(); expect(within(await cardOf('Laptop')).getByRole('img', { name: 'Runs on your computer' })).toBeInTheDocument(); expect(within(await cardOf('Hook')).getByRole('img', { name: 'Runs on your computer' })).toBeInTheDocument(); expect(within(await cardOf('Cloudhost')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); + expect(within(await cardOf('Stampedhost')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); // A teammate's laptop seat: we don't know which machine it runs on. expect(within(await cardOf('Teammate')).queryByRole('img', { name: /Runs on/ })).toBeNull(); expect(within(await cardOf('Daemon')).getByRole('img', { name: 'Runs on your computer' })).toBeInTheDocument(); diff --git a/frontend/src/v2/components/V2YourTeamPage.tsx b/frontend/src/v2/components/V2YourTeamPage.tsx index 3c41ba62f9..3ced72bff1 100644 --- a/frontend/src/v2/components/V2YourTeamPage.tsx +++ b/frontend/src/v2/components/V2YourTeamPage.tsx @@ -103,15 +103,20 @@ const STATE_DOT = '\u25CF'; const agentKey = (a: { name: string; instanceId?: string }) => `${a.name}:${a.instanceId || 'default'}`; -// Where a seat runs. Only the stamped host decides it: config.runtime.host is -// 'cloud' (Commonly runs it) or 'byo' (a machine polling in). A seat with no host -// gets no mark. Inferring from runtime type labelled agent-run seats on a Mac as -// cloud, so the card no longer guesses. A laptop mark means the viewer's own -// machine, so a byo seat another teammate installed gets no laptop mark either. +// Where a seat runs. A stamped host decides it: config.runtime.host is 'cloud' +// (Commonly runs it) or 'byo' (a machine polling in). A runtime type with no BYO +// form has only one possible location, so it reads as cloud without a stamp: +// hosted, native, internal and moltbot (the cluster gateway). Nothing stamps host +// on a new hosted install, so these need the type. Every other type (codex, +// claude-code, webhook, wrapper) can run on someone's machine, so it gets no mark +// unless its host is stamped. A laptop mark means the viewer's own machine, so a +// byo seat another teammate installed gets no laptop mark either. type RunsOn = 'cloud' | 'laptop' | null; +const CLOUD_ONLY_RUNTIME_TYPES = new Set(['hosted', 'native', 'internal', 'moltbot']); const runsOn = (a: AgentInstallationSummary, viewerId: string | undefined): RunsOn => { const host = (a.runtime?.host || '').trim().toLowerCase(); - if (host === 'cloud') return 'cloud'; + const type = (a.runtime?.runtimeType || '').trim().toLowerCase(); + if (host === 'cloud' || (!host && CLOUD_ONLY_RUNTIME_TYPES.has(type))) return 'cloud'; if (host === 'byo') return a.installedBy === viewerId ? 'laptop' : null; return null; }; From 9e4d9a4d393cfa732bf7916cd995b2164b5b563b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:30:15 -0700 Subject: [PATCH 3/3] test(v2): cover unstamped BYO seats and each cloud-only type (TASK-360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every webhook and wrapper fixture carried a host, so a restored laptop fallback for unstamped installer seats would still pass. Adds an installer-owned webhook and a wrapper with no host, both asserting no mark. Adds native, internal and moltbot with no host, each asserting the cloud mark. Mutants: the laptop fallback fails the suite; dropping native from the cloud-only set fails it. Written by Wisp, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 Co-authored-by: wisp Agent-Model: claude/claude-haiku-5-5/default --- .../src/v2/__tests__/V2YourTeamPage.where.test.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx b/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx index 11d7bfe2f9..491e22906e 100644 --- a/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx +++ b/frontend/src/v2/__tests__/V2YourTeamPage.where.test.tsx @@ -42,6 +42,11 @@ const agents = [ agent({ name: 'teamdaemon', displayName: 'Teamdaemon', userId: 'user-teamdaemon', installedBy: 'someone-else', runtime: { runtimeType: 'wrapper', adapter: 'codex', host: 'byo' } }), agent({ name: 'cloudhost', displayName: 'Cloudhost', userId: 'user-cloudhost', runtime: { runtimeType: 'something-new', host: 'cloud' } }), agent({ name: 'mystery', displayName: 'Mystery', userId: 'user-mystery', runtime: { runtimeType: 'something-new' } }), + agent({ name: 'hookowned', displayName: 'Hookowned', userId: 'user-hookowned', installedBy: 'me', runtime: { runtimeType: 'webhook' } }), + agent({ name: 'wrapnohost', displayName: 'Wrapnohost', userId: 'user-wrapnohost', installedBy: 'me', runtime: { runtimeType: 'wrapper', adapter: 'codex' } }), + agent({ name: 'nativeunstamped', displayName: 'Nativeunstamped', userId: 'user-nativeunstamped', runtime: { runtimeType: 'native' } }), + agent({ name: 'internalunstamped', displayName: 'Internalunstamped', userId: 'user-internalunstamped', runtime: { runtimeType: 'internal' } }), + agent({ name: 'moltbotunstamped', displayName: 'Moltbotunstamped', userId: 'user-moltbotunstamped', runtime: { runtimeType: 'moltbot' } }), ]; const renderPage = () => render( @@ -89,6 +94,13 @@ describe('V2YourTeamPage location mark and hosted meter', () => { expect(within(await cardOf('Daemon')).getByRole('img', { name: 'Runs on your computer' })).toBeInTheDocument(); expect(within(await cardOf('Teamdaemon')).queryByRole('img', { name: /Runs on/ })).toBeNull(); expect(within(await cardOf('Mystery')).queryByRole('img', { name: /Runs on/ })).toBeNull(); + // No host stamp and a BYO type: no mark, even for the installer's own seat. + expect(within(await cardOf('Hookowned')).queryByRole('img', { name: /Runs on/ })).toBeNull(); + expect(within(await cardOf('Wrapnohost')).queryByRole('img', { name: /Runs on/ })).toBeNull(); + // Cloud-only runtime types with no stamp read as cloud. + expect(within(await cardOf('Nativeunstamped')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); + expect(within(await cardOf('Internalunstamped')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); + expect(within(await cardOf('Moltbotunstamped')).getByRole('img', { name: 'Runs on Commonly' })).toBeInTheDocument(); // The own-agent tile is the viewer's own machine by definition. expect(screen.getByTestId('team-own-card').querySelector('[role="img"]')).not.toBeNull(); });