diff --git a/config/scripts/regenerate-xterm-patches.test.mjs b/config/scripts/regenerate-xterm-patches.test.mjs
index 1bc1c3387ea..c39eeadc710 100644
--- a/config/scripts/regenerate-xterm-patches.test.mjs
+++ b/config/scripts/regenerate-xterm-patches.test.mjs
@@ -260,20 +260,26 @@ describe('source derivation agreement', () => {
).toThrow(/disagree on a source file/)
})
- it('diffs the checkout with pnpm formatting so the two halves stay comparable', () => {
- expect(CHECKOUT_DIFF_FLAGS.filter((flag) => flag !== '--relative')).toEqual(
- PNPM_DIFF_FLAGS.filter((flag) => flag !== '--no-index')
- )
- expect(CHECKOUT_DIFF_FLAGS).toContain('--full-index')
- expect(CHECKOUT_DIFF_FLAGS).not.toContain('--no-index')
- })
+ it('diffs a nested checkout with package-relative paths and full object IDs', async () => {
+ const root = await createDirectory()
+ const checkout = path.join(root, 'checkout')
+ const packageRoot = path.join(checkout, 'addons', 'widget')
+ const pristine = path.join(root, 'pristine')
+ await writeTree(packageRoot, PRISTINE)
+ await writeTree(pristine, PRISTINE)
+ execFileSync('git', ['init', '--quiet', checkout], { env: pnpmDiffEnvironment() })
+ execFileSync('git', ['add', '.'], { cwd: checkout, env: pnpmDiffEnvironment() })
+ await writeTree(packageRoot, PATCHED)
- it('passes --relative as a flag, not as a pathspec', () => {
- // After `--` git reads it as a path, silently leaving addon diffs rooted at the
- // repo instead of the package, which drops every source hunk from the patch.
- expect(CHECKOUT_DIFF_FLAGS.indexOf('--relative')).toBeLessThan(
- CHECKOUT_DIFF_FLAGS.indexOf('--')
- )
+ const patch = execFileSync('git', [...CHECKOUT_DIFF_FLAGS, 'src/'], {
+ cwd: packageRoot,
+ encoding: 'utf8',
+ env: pnpmDiffEnvironment()
+ })
+
+ expect(patch).toBe(sourceHunks(diffFolders(pristine, packageRoot)))
+ expect(patch).toContain('diff --git a/src/Widget.ts b/src/Widget.ts')
+ expect(patch).toMatch(/^index [0-9a-f]{40}\.\.[0-9a-f]{40} 100644$/m)
})
})
@@ -455,7 +461,7 @@ describe('committed xterm patch artifacts', () => {
}
})
- it('pins a full upstream commit and a buildable package entry', async () => {
+ it('requires a full upstream commit, a package, and allowed build steps', async () => {
const manifest = JSON.parse(await readFile(MANIFEST_PATH, 'utf8'))
expect(manifest.upstream.commit).toMatch(/^[0-9a-f]{40}$/)
expect(manifest.packages.length).toBeGreaterThan(0)
diff --git a/config/scripts/relay-era-ref-refusal-workflow.test.mjs b/config/scripts/relay-era-ref-refusal-workflow.test.mjs
index 30df8a74908..47ed8f35cda 100644
--- a/config/scripts/relay-era-ref-refusal-workflow.test.mjs
+++ b/config/scripts/relay-era-ref-refusal-workflow.test.mjs
@@ -47,6 +47,9 @@ describe.each(refusalTargets)(
({ jobSteps, index, laterStep }) => {
it('checks the ref before it builds or tags anything', () => {
expect(index).toBeGreaterThan(0)
+ expect(jobSteps[index].if).toBeUndefined()
+ expect(jobSteps[index]['continue-on-error'] ?? false).toBe(false)
+ expect(jobSteps[index].shell).toBe('bash')
expect(jobSteps[index - 1].uses).toMatch(/^actions\/checkout@/)
const sparse = jobSteps[index - 1].with['sparse-checkout']
expect(sparse?.split('\n').includes('/config/tsconfig.relay.json') ?? true).toBe(true)
diff --git a/config/scripts/release-cut-token-permissions.test.mjs b/config/scripts/release-cut-token-permissions.test.mjs
index 1f5b410fba4..494c50a2e29 100644
--- a/config/scripts/release-cut-token-permissions.test.mjs
+++ b/config/scripts/release-cut-token-permissions.test.mjs
@@ -140,7 +140,9 @@ function normalizePermissions(permissions) {
)
}
-function intersectPermissions(granted, requested) {
+const permissionEscalations = []
+
+function intersectPermissions(granted, requested, identity) {
const rank = { none: 0, read: 1, write: 2 }
const scopes = new Set([...Object.keys(granted), ...Object.keys(requested)])
return Object.fromEntries(
@@ -148,6 +150,14 @@ function intersectPermissions(granted, requested) {
.map((scope) => {
const grantedLevel = granted[scope] ?? 'none'
const requestedLevel = requested[scope] ?? 'none'
+ if (rank[requestedLevel] > rank[grantedLevel]) {
+ permissionEscalations.push({
+ identity,
+ scope,
+ granted: grantedLevel,
+ requested: requestedLevel
+ })
+ }
return [scope, rank[grantedLevel] < rank[requestedLevel] ? grantedLevel : requestedLevel]
})
.filter(([, level]) => level !== 'none')
@@ -165,12 +175,12 @@ function resolveWorkflowMatrix(workflow, workflowPath, inheritedPermissions, pre
if (!requested) {
throw new Error(`${workflowPath}#${jobName} has no resolvable permissions`)
}
- const effective = inheritedPermissions
- ? intersectPermissions(inheritedPermissions, requested)
- : requested
const identity = prefix
? `${prefix} -> ${workflowPath}#${jobName}`
: `${workflowPath}#${jobName}`
+ const effective = inheritedPermissions
+ ? intersectPermissions(inheritedPermissions, requested, identity)
+ : requested
matrix[identity] = effective
if (job.uses?.startsWith('./.github/workflows/')) {
@@ -249,6 +259,7 @@ describe('release-cut token permissions', () => {
)
it('keeps the complete effective job and reusable-workflow matrix least-privileged', () => {
+ expect(permissionEscalations).toEqual([])
expect(matrix).toEqual(EXPECTED_MATRIX)
})
diff --git a/mobile/src/navigation/route-handoff-host-area.web.test.tsx b/mobile/src/navigation/route-handoff-host-area.web.test.tsx
index 6e3bb452f01..6084a109e73 100644
--- a/mobile/src/navigation/route-handoff-host-area.web.test.tsx
+++ b/mobile/src/navigation/route-handoff-host-area.web.test.tsx
@@ -3,11 +3,13 @@
* the session screen inside the same document, decided by the page's real handoff rule against the
* `init` the shell's real policy builds.
*/
-import { act, create } from 'react-test-renderer'
-import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { act, create, type ReactTestRenderer } from 'react-test-renderer'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createShellPageClient } from '../mobile-web-shell/bridge/page-bootstrap'
import { createBridgeInitFrame } from '../mobile-web-shell/bridge/bridge-init-frame'
import { routeViewOf, type MobileWebPageRoute } from '../mobile-web-shell/page-route-policy'
+import { BRIDGE_PROTOCOL_VERSION } from '../mobile-web-shell/bridge/bridge-envelope'
+import type { BridgeRpcClient } from '../mobile-web-shell/bridge/bridge-rpc-client'
import type { RouteHandoff } from './route-handoff'
const router = vi.hoisted(() => ({
@@ -65,6 +67,9 @@ const ROUTES: MobileWebPageRoute[] = [
}
]
+const trees: ReactTestRenderer[] = []
+const clients: BridgeRpcClient[] = []
+
const held: { handoff: RouteHandoff | null } = { handoff: null }
function Screen(): null {
@@ -76,7 +81,7 @@ function Screen(): null {
function mountPage(
pathname: string,
wide: boolean
-): { handoff: RouteHandoff; navigations: () => number } {
+): { handoff: RouteHandoff; navigations: () => unknown[] } {
const posted: string[] = []
const channel: {
postMessage: (json: string) => void
@@ -87,6 +92,7 @@ function mountPage(
if (client === null) {
throw new Error('no channel installed')
}
+ clients.push(client)
const view = routeViewOf(ROUTES, pathname, wide)
const init = createBridgeInitFrame({
sessionId: 'session-a',
@@ -108,10 +114,12 @@ function mountPage(
})
channel.onmessage?.({ data: JSON.stringify(init) })
act(() => {
- create(
-
-
-
+ trees.push(
+ create(
+
+
+
+ )
)
})
if (held.handoff === null) {
@@ -121,7 +129,7 @@ function mountPage(
return {
handoff,
navigations: () =>
- posted.map((json) => JSON.parse(json)).filter((frame) => frame.name === 'navigate').length
+ posted.map((json) => JSON.parse(json)).filter((frame) => frame.name === 'navigate')
}
}
@@ -130,26 +138,45 @@ beforeEach(() => {
vi.clearAllMocks()
})
+afterEach(() => {
+ act(() => {
+ for (const tree of trees.splice(0)) {
+ tree.unmount()
+ }
+ })
+ for (const client of clients.splice(0)) {
+ client.close()
+ }
+ Reflect.deleteProperty(globalThis, 'orcaBridge')
+})
+
describe('a worktree row in the host-area page', () => {
it('opens the session inside this document, without a native push', () => {
const page = mountPage('/h/host-1', true)
page.handoff.push('/h/host-1/session/wt-1?name=main')
expect(router.push).toHaveBeenCalledWith('/h/host-1/session/wt-1?name=main', undefined)
- expect(page.navigations()).toBe(0)
+ expect(page.navigations()).toEqual([])
})
it('switches workspaces in place too, which the embedded sidebar does with a replace', () => {
const page = mountPage('/h/host-1', true)
page.handoff.replace('/h/host-1/session/wt-2')
expect(router.replace).toHaveBeenCalledWith('/h/host-1/session/wt-2', undefined)
- expect(page.navigations()).toBe(0)
+ expect(page.navigations()).toEqual([])
})
it('is still handed to the shell from a phone host page, whose grants do not cover it', () => {
const page = mountPage('/h/host-1', false)
page.handoff.push('/h/host-1/session/wt-1')
expect(router.push).not.toHaveBeenCalled()
- expect(page.navigations()).toBe(1)
+ expect(page.navigations()).toEqual([
+ {
+ v: BRIDGE_PROTOCOL_VERSION,
+ type: 'notify',
+ name: 'navigate',
+ href: '/h/host-1/session/wt-1'
+ }
+ ])
})
})
@@ -161,7 +188,9 @@ describe('a session page opened on its own, leaving for the host route', () => {
const page = mountPage('/h/host-1/session/wt-1', true)
page.handoff.replace(hop)
expect(router.replace).not.toHaveBeenCalled()
- expect(page.navigations()).toBe(1)
+ expect(page.navigations()).toEqual([
+ { v: BRIDGE_PROTOCOL_VERSION, type: 'notify', name: 'navigate', href: hop }
+ ])
expect(routeViewOf(ROUTES, '/h/host-1', true).ownsHostArea).toBe(true)
})
@@ -169,6 +198,6 @@ describe('a session page opened on its own, leaving for the host route', () => {
const page = mountPage('/h/host-1/session/wt-1', false)
page.handoff.replace(hop)
expect(router.replace).toHaveBeenCalledWith(hop, undefined)
- expect(page.navigations()).toBe(0)
+ expect(page.navigations()).toEqual([])
})
})
diff --git a/mobile/src/navigation/route-handoff.web.test.tsx b/mobile/src/navigation/route-handoff.web.test.tsx
index dc777e07fb0..c8e8fc3892b 100644
--- a/mobile/src/navigation/route-handoff.web.test.tsx
+++ b/mobile/src/navigation/route-handoff.web.test.tsx
@@ -1,5 +1,5 @@
import type { ReactElement } from 'react'
-import { act, create } from 'react-test-renderer'
+import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { BRIDGE_MAX_ROUTE_HREF_CHARS } from '../mobile-web-shell/bridge/bridge-caps'
import {
@@ -36,7 +36,7 @@ vi.mock('../transport/host-client-hooks', () => ({
}))
import { RpcClientProvider } from '../transport/client-context.web'
-import { useRouteHandoff, WRAPPED_HREF_MEMBERS } from './route-handoff.web'
+import { useRouteHandoff } from './route-handoff.web'
const INIT = {
v: BRIDGE_PROTOCOL_VERSION,
@@ -55,6 +55,9 @@ const INIT = {
pageRoutes: ['/h/[hostId]']
}
+const trees: ReactTestRenderer[] = []
+const clients: BridgeRpcClient[] = []
+
const held: { handoff: RouteHandoff | null } = { handoff: null }
function Screen(): null {
@@ -62,13 +65,21 @@ function Screen(): null {
return null
}
-function installChannel(): { posted: string[]; deliver: (frame: unknown) => void } {
+function installChannel(): {
+ posted: string[]
+ deliver: (frame: unknown) => void
+ failPosts: () => void
+} {
+ let failed = false
const posted: string[] = []
const channel: {
postMessage: (json: string) => void
onmessage: ((event: { data: string }) => void) | null
} = {
postMessage: (json) => {
+ if (failed) {
+ throw new Error('page port unavailable')
+ }
posted.push(json)
},
onmessage: null
@@ -76,6 +87,9 @@ function installChannel(): { posted: string[]; deliver: (frame: unknown) => void
Object.defineProperty(globalThis, 'orcaBridge', { value: channel, configurable: true })
return {
posted,
+ failPosts: () => {
+ failed = true
+ },
deliver: (frame) => {
channel.onmessage?.({ data: JSON.stringify(frame) })
}
@@ -83,21 +97,22 @@ function installChannel(): { posted: string[]; deliver: (frame: unknown) => void
}
/** The page as the entry leaves it: one client, already holding a session. */
-function mount(init: unknown): { posted: string[]; handoff: RouteHandoff } {
+function mount(init: unknown): { posted: string[]; handoff: RouteHandoff; failPosts: () => void } {
const channel = installChannel()
const client = createShellPageClient()
if (client === null) {
throw new Error('no channel installed')
}
+ clients.push(client)
channel.deliver(init)
act(() => {
- create(render(client))
+ trees.push(create(render(client)))
})
const handoff = held.handoff
if (handoff === null) {
throw new Error('no screen mounted')
}
- return { posted: channel.posted, handoff }
+ return { posted: channel.posted, handoff, failPosts: channel.failPosts }
}
function render(client: BridgeRpcClient): ReactElement {
@@ -147,6 +162,14 @@ beforeEach(() => {
})
afterEach(() => {
+ act(() => {
+ for (const tree of trees.splice(0)) {
+ tree.unmount()
+ }
+ })
+ for (const client of clients.splice(0)) {
+ client.close()
+ }
restoreWarn?.()
restoreWarn = null
vi.useRealTimers()
@@ -346,8 +369,12 @@ describe('a back this document cannot serve', () => {
})
it('does not throw out of a tap handler when there is nowhere to post', () => {
- const { handoff } = mount({ ...INIT, grants: { ...INIT.grants, native: [] } })
+ const { posted, handoff, failPosts } = mount(INIT)
+ failPosts()
expect(() => handoff.back()).not.toThrow()
+ expect(backs(posted)).toEqual([])
+ expect(router.back).toHaveBeenCalledTimes(1)
+ expect(JSON.stringify(warned)).toContain('send-failed')
})
})
@@ -360,10 +387,25 @@ describe('a back this document cannot serve', () => {
*/
describe('every member that takes a target', () => {
it('is replaced rather than handed through, all of them', () => {
- const { handoff } = mount(INIT)
- for (const member of WRAPPED_HREF_MEMBERS) {
- expect(typeof handoff[member], member).toBe('function')
- expect(handoff[member], member).not.toBe(router[member])
+ const options = { withAnchor: false }
+ for (const member of ['push', 'replace', 'navigate', 'dismissTo'] as const) {
+ router[member].mockClear()
+ const { posted, handoff } = mount(INIT)
+ handoff[member]('/h/host-b', options)
+ expect(router[member], member).toHaveBeenCalledExactlyOnceWith('/h/host-b', options)
+ router[member].mockClear()
+ handoff[member]('/h/host-a/session/wt-1')
+ expect(navigations(posted), member).toEqual([
+ {
+ v: BRIDGE_PROTOCOL_VERSION,
+ type: 'notify',
+ name: 'navigate',
+ href: '/h/host-a/session/wt-1'
+ }
+ ])
+ handoff[member]('/h/host-a/tasks#top')
+ expect(navigations(posted), member).toHaveLength(1)
+ expect(router[member], member).not.toHaveBeenCalled()
}
})
diff --git a/mobile/src/navigation/use-back-claim.web.test.tsx b/mobile/src/navigation/use-back-claim.web.test.tsx
index ab63b2366a0..6fc66048dc4 100644
--- a/mobile/src/navigation/use-back-claim.web.test.tsx
+++ b/mobile/src/navigation/use-back-claim.web.test.tsx
@@ -1,6 +1,6 @@
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
-import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest'
import {
createPageBackConsumers,
type PageBackConsumers
@@ -38,11 +38,14 @@ function Screen({ claim }: { claim: (() => boolean) | null }): null {
return null
}
+const trees: ReactTestRenderer[] = []
+
function render(claim: (() => boolean) | null): ReactTestRenderer {
let tree!: ReactTestRenderer
act(() => {
tree = create(createElement(Screen, { claim }))
})
+ trees.push(tree)
return tree
}
@@ -55,6 +58,15 @@ beforeEach(() => {
})
})
+afterEach(() => {
+ act(() => {
+ for (const tree of trees.splice(0)) {
+ tree.unmount()
+ }
+ })
+ page.consumers = null
+})
+
/**
* The page half of the seam every sheet takes. There is no hardware key inside a WebView:
* react-native-web answers `BackHandler.addEventListener` with a console warning and an inert
diff --git a/mobile/src/navigation/workspace-route-handoff.test.tsx b/mobile/src/navigation/workspace-route-handoff.test.tsx
index 47669eb1727..2a30994865e 100644
--- a/mobile/src/navigation/workspace-route-handoff.test.tsx
+++ b/mobile/src/navigation/workspace-route-handoff.test.tsx
@@ -1,13 +1,13 @@
import { createElement } from 'react'
-import { act, create } from 'react-test-renderer'
-import { describe, expect, it, vi } from 'vitest'
+import { act, create, type ReactTestRenderer } from 'react-test-renderer'
+import { afterEach, describe, expect, it, vi } from 'vitest'
const route = vi.hoisted(() => {
const opened: unknown[] = []
const router = {
- push: (href: unknown) => opened.push(['push', href]),
- replace: (href: unknown) => opened.push(['replace', href]),
- navigate: (href: unknown) => opened.push(['navigate', href]),
+ push: (href: unknown, options?: unknown) => opened.push(['push', href, options]),
+ replace: (href: unknown, options?: unknown) => opened.push(['replace', href, options]),
+ navigate: (href: unknown, options?: unknown) => opened.push(['navigate', href, options]),
back: () => opened.push(['back'])
}
const state: { opened: unknown[]; router: typeof router } = {
@@ -23,6 +23,16 @@ import { useWorkspaceRouteHandoff } from './workspace-route-handoff'
import { normalizeExecutionHostId } from '../../../src/shared/execution-host'
import { WorkspaceExecutionHostContext } from './workspace-execution-host'
+const trees: ReactTestRenderer[] = []
+
+afterEach(() => {
+ act(() => {
+ for (const tree of trees.splice(0)) {
+ tree.unmount()
+ }
+ })
+})
+
async function workspaceRouterFor(
params: Record
): Promise> {
@@ -33,11 +43,13 @@ async function workspaceRouterFor(
return null
}
await act(async () => {
- create(
- createElement(
- WorkspaceExecutionHostContext.Provider,
- { value: normalizeExecutionHostId(params.executionHost) ?? undefined },
- createElement(Probe)
+ trees.push(
+ create(
+ createElement(
+ WorkspaceExecutionHostContext.Provider,
+ { value: normalizeExecutionHostId(params.executionHost) ?? undefined },
+ createElement(Probe)
+ )
)
)
})
@@ -50,19 +62,24 @@ async function workspaceRouterFor(
describe('a workspace screen’s router', () => {
it('keeps every screen it opens on the workspace’s server', async () => {
const router = await workspaceRouterFor({ executionHost: 'runtime:vm' })
- router.push('/h/host-1/review/wt?name=api')
- router.replace('/h/host-1/session/wt')
- router.navigate({ pathname: '/h/[hostId]/files/[worktreeId]', params: { hostId: 'host-1' } })
+ const options = { withAnchor: false }
+ router.push('/h/host-1/review/wt?name=api', options)
+ router.replace('/h/host-1/session/wt', options)
+ router.navigate(
+ { pathname: '/h/[hostId]/files/[worktreeId]', params: { hostId: 'host-1' } },
+ options
+ )
router.back()
expect(route.opened).toEqual([
- ['push', '/h/host-1/review/wt?name=api&executionHost=runtime%3Avm'],
- ['replace', '/h/host-1/session/wt?executionHost=runtime%3Avm'],
+ ['push', '/h/host-1/review/wt?name=api&executionHost=runtime%3Avm', options],
+ ['replace', '/h/host-1/session/wt?executionHost=runtime%3Avm', options],
[
'navigate',
{
pathname: '/h/[hostId]/files/[worktreeId]',
params: { hostId: 'host-1', executionHost: 'runtime:vm' }
- }
+ },
+ options
],
['back']
])
diff --git a/mobile/src/navigation/workspace-route.test.tsx b/mobile/src/navigation/workspace-route.test.tsx
index b29cc2c2bf4..4fe7718840a 100644
--- a/mobile/src/navigation/workspace-route.test.tsx
+++ b/mobile/src/navigation/workspace-route.test.tsx
@@ -2,7 +2,7 @@ import { readdirSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { createElement, type ReactElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
-import { describe, expect, it, vi } from 'vitest'
+import { afterEach, describe, expect, it, vi } from 'vitest'
const route = vi.hoisted(() => {
const state: {
@@ -155,6 +155,22 @@ const WORKSPACE_ROUTE_FILES = readdirSync(HOST_ROUTES, { recursive: true, encodi
.filter((entry) => /(^|\/)\[worktreeId\](\.web)?\.tsx$/.test(entry))
.sort()
+const trees: ReactTestRenderer[] = []
+const pairs: ReturnType[] = []
+
+afterEach(async () => {
+ await act(async () => {
+ for (const tree of trees.splice(0)) {
+ tree.unmount()
+ }
+ })
+ for (const pair of pairs.splice(0)) {
+ pair.client.close()
+ await pair.flush()
+ pair.host.dispose()
+ }
+})
+
async function render(element: ReactElement, gates: HostStatusGates): Promise {
route.seen.length = 0
route.shellRoutes.length = 0
@@ -170,6 +186,7 @@ async function render(element: ReactElement, gates: HostStatusGates): Promise {
it('is found on disk', () => {
- expect(WORKSPACE_ROUTE_FILES.length).toBeGreaterThanOrEqual(14)
+ expect(WORKSPACE_ROUTE_FILES.length).toBeGreaterThan(0)
})
it.each(WORKSPACE_ROUTE_FILES)('keeps %s on the server its route names', async (file) => {
@@ -225,9 +242,10 @@ describe('a server workspace', () => {
it('gates its features on that server’s own status, not the desktop’s', async () => {
route.params = { hostId: 'host-1', executionHost: 'runtime:vm' }
- const session = desktop([])
+ const serverCapability = 'server-feature.v1'
+ const session = desktop([serverCapability])
await render(screen, desktopGates([...RELAYS, VAULT]))
- await vi.waitFor(() => expect(below.capabilities).toEqual([]))
+ await vi.waitFor(() => expect(below.capabilities).toEqual([serverCapability]))
expect(session.sendRequest).toHaveBeenCalledWith('status.get', undefined, {
envelope: { executionHost: 'runtime:vm' }
})
@@ -273,8 +291,10 @@ describe('a server workspace', () => {
desktop()
serverReport.current = FAILED
await act(async () => {
- create(
- createElement(HostStatusGatesContext.Provider, { value: desktopGates(RELAYS) }, screen)
+ trees.push(
+ create(
+ createElement(HostStatusGatesContext.Provider, { value: desktopGates(RELAYS) }, screen)
+ )
)
})
expect(below.state).toBe('disconnected')
@@ -299,6 +319,7 @@ describe('a server workspace', () => {
rpc: shell,
rewriteToPage: (json) => json.replace(',"rpcEnvelope"', '')
})
+ pairs.push(pair)
await pair.flush()
expect(pair.client.carriesRpcEnvelope?.()).toBe(false)
route.client = pair.client
diff --git a/src/shared/agent-hook-listener-grok-completion.test.ts b/src/shared/agent-hook-listener-grok-completion.test.ts
index 263c9e01a03..af37cb05685 100644
--- a/src/shared/agent-hook-listener-grok-completion.test.ts
+++ b/src/shared/agent-hook-listener-grok-completion.test.ts
@@ -237,7 +237,7 @@ describe('Grok completion observations', () => {
).toBe('done')
})
- it('keeps a starting finite task working until a later terminal observation', () => {
+ it('keeps a starting finite task working', () => {
expect(
normalize({
hookEventName: 'Stop',
diff --git a/src/shared/agent-hook-listener-grok.test.ts b/src/shared/agent-hook-listener-grok.test.ts
index 9fa6f844114..a3ea8080d98 100644
--- a/src/shared/agent-hook-listener-grok.test.ts
+++ b/src/shared/agent-hook-listener-grok.test.ts
@@ -289,9 +289,8 @@ describe('shared agent-hook-listener', () => {
expect(event?.payload.prompt).toBe('Find recent PR')
})
- it('strips newline-heavy Grok user_query wrappers without regex matching', () => {
- const matchSpy = vi.spyOn(String.prototype, 'match')
- const promptText = 'Find recent PR\n'.repeat(300)
+ it('preserves both ends of newline-heavy Grok user_query prompts', () => {
+ const promptText = `Find recent PR${'\n'.repeat(300)}Keep the final requirement`
const event = normalizeHookPayload(
state,
'grok',
@@ -305,16 +304,18 @@ describe('shared agent-hook-listener', () => {
'production'
)
- expect(event?.payload.prompt).toContain('Find recent PR')
- expect(event?.payload.prompt).not.toContain('')
- expect(event?.payload.prompt).not.toContain('')
- const usedGrokWrapperMatch = matchSpy.mock.calls.some(
- ([pattern]) =>
- pattern instanceof RegExp &&
- pattern.source.startsWith('^') &&
- pattern.source.includes('[\\s\\S]')
+ expect(event?.payload.prompt).toBe('Find recent PR Keep the final requirement')
+
+ const tool = normalizeHookPayload(
+ state,
+ 'grok',
+ {
+ paneKey: PANE_KEY,
+ payload: { hookEventName: 'pre_tool_use', toolName: 'web_search' }
+ },
+ 'production'
)
- expect(usedGrokWrapperMatch).toBe(false)
+ expect(tool?.payload.prompt).toBe('Find recent PR Keep the final requirement')
})
it('maps Grok feedback notifications to waiting without overwriting the prompt', () => {
diff --git a/src/shared/agent-hook-listener-opencode-session-correlation.test.ts b/src/shared/agent-hook-listener-opencode-session-correlation.test.ts
index c0bc1d0ef37..2a1914dc669 100644
--- a/src/shared/agent-hook-listener-opencode-session-correlation.test.ts
+++ b/src/shared/agent-hook-listener-opencode-session-correlation.test.ts
@@ -138,6 +138,13 @@ describe('correlateOpenCodeSessionOwners', () => {
knownOwners: new Map()
})
expect(results).toEqual([])
+ const expired = correlateOpenCodeSessionOwners({
+ sessions: [session('ses_1', NOW - 60_000)],
+ panes: [{ paneKey: 'pane-a', directory: DIR }],
+ clients: [client('pane-a', NOW - 120_000, NOW - 90_000)],
+ knownOwners: new Map()
+ })
+ expect(expired).toEqual([])
})
it('breaks a same-directory tie by client evidence', () => {
diff --git a/src/shared/agent-hook-listener-opencode-tui-identity.test.ts b/src/shared/agent-hook-listener-opencode-tui-identity.test.ts
index 8195dde52f8..8e31013b96f 100644
--- a/src/shared/agent-hook-listener-opencode-tui-identity.test.ts
+++ b/src/shared/agent-hook-listener-opencode-tui-identity.test.ts
@@ -74,7 +74,7 @@ describe('legacy structural TUI identity at the execution-host boundary', () =>
bindOpenCodeSession(state, 'ses_b', { paneKey: PANE_A, boundAt: 1, basis: 'argv' })
trackOpenCodePaneLaunchToken(state, PANE_A, 'creator-live')
const tokens = [...state.lastLaunchTokenByPaneKey]
- const owner = lookupOpenCodeSessionPane(state, 'ses_b')
+ const owner = { ...lookupOpenCodeSessionPane(state, 'ses_b') }
const admitOpenCodeTui = vi.fn(() => false)
expect(normalizeHookPayload(state, 'opencode', body(), 'test', { admitOpenCodeTui })).toBeNull()
expect(admitOpenCodeTui).toHaveBeenCalledExactlyOnceWith({
@@ -84,7 +84,7 @@ describe('legacy structural TUI identity at the execution-host boundary', () =>
hasExplicitPrompt: false
})
expect([...state.lastLaunchTokenByPaneKey]).toEqual(tokens)
- expect(lookupOpenCodeSessionPane(state, 'ses_b')).toBe(owner)
+ expect(lookupOpenCodeSessionPane(state, 'ses_b')).toEqual(owner)
expect(state.lastStatusByPaneKey.size).toBe(0)
expect(state.lastPromptByPaneKey.size).toBe(0)
})
diff --git a/src/shared/agent-hook-relay.test.ts b/src/shared/agent-hook-relay.test.ts
index 8587bc9020a..41d75f6f029 100644
--- a/src/shared/agent-hook-relay.test.ts
+++ b/src/shared/agent-hook-relay.test.ts
@@ -1,9 +1,5 @@
import { describe, expect, it } from 'vitest'
import {
- AGENT_HOOK_INSTALL_PLUGINS_METHOD,
- AGENT_HOOK_NOTIFICATION_METHOD,
- AGENT_HOOK_REQUEST_REPLAY_METHOD,
- AGENT_HOOK_SHED_FIELDS_KEY,
ORCA_FEATURE_REMOTE_AGENT_HOOKS_ENV,
createShedSubagentsField,
isAgentHookSource,
@@ -13,12 +9,6 @@ import {
import type { ParsedAgentStatusPayload } from './agent-status-types'
describe('agent-hook-relay wire shape', () => {
- it('exposes stable JSON-RPC method names', () => {
- expect(AGENT_HOOK_NOTIFICATION_METHOD).toBe('agent.hook')
- expect(AGENT_HOOK_REQUEST_REPLAY_METHOD).toBe('agent_hook.requestReplay')
- expect(AGENT_HOOK_INSTALL_PLUGINS_METHOD).toBe('agent_hook.installPlugins')
- })
-
it('validates hook sources crossing persisted and relay trust boundaries', () => {
expect(isAgentHookSource('claude')).toBe(true)
expect(isAgentHookSource('kimi')).toBe(true)
@@ -69,7 +59,6 @@ describe('restoreShedStatusFields', () => {
})
it('does not restore a shed roster across a prompt identity change', () => {
- expect(AGENT_HOOK_SHED_FIELDS_KEY).toBe('shedFields')
const nextTurn = { ...shed, prompt: 'next prompt' }
const restored = restoreShedStatusFields(
nextTurn,
@@ -110,11 +99,13 @@ describe('restoreShedStatusFields', () => {
expect(restoreShedStatusFields(shed, [{ toString: () => 'subagents' }], cached)).toBe(shed)
})
- it('does not overwrite a value the envelope still carries', () => {
- const fresh: ParsedAgentStatusPayload = { ...shed, lastAssistantMessage: 'fresh' }
- expect(
- restoreShedStatusFields(fresh, ['lastAssistantMessage'], cached).lastAssistantMessage
- ).toBe('fresh')
+ it('does not overwrite a roster the envelope still carries', () => {
+ const freshRoster = [
+ { id: 'child-fresh', agentType: 'reviewer', state: 'working' as const, startedAt: 2 }
+ ]
+ const fresh: ParsedAgentStatusPayload = { ...shed, subagents: freshRoster }
+ const restored = restoreShedStatusFields(fresh, [createShedSubagentsField(roster)], cached)
+ expect(restored.subagents).toEqual(freshRoster)
})
it('is a no-op with no cached payload for the pane', () => {
diff --git a/src/shared/agent-hook-transport-interference.test.ts b/src/shared/agent-hook-transport-interference.test.ts
index 95b4c97c055..4234a39882c 100644
--- a/src/shared/agent-hook-transport-interference.test.ts
+++ b/src/shared/agent-hook-transport-interference.test.ts
@@ -30,9 +30,9 @@ describe('classifyTruncatedHookRequest', () => {
})
it('reads the first value when the header arrives duplicated', () => {
- expect(classifyTruncatedHookRequest(['4096', '4096'], 12)).toBeInstanceOf(
- HookRequestTruncatedError
- )
+ const error = classifyTruncatedHookRequest(['4096', '8'], 12)
+ expect(error).toBeInstanceOf(HookRequestTruncatedError)
+ expect(error).toMatchObject({ bytesRead: 12, contentLength: 4096 })
})
})
diff --git a/src/shared/agent-notification-id.test.ts b/src/shared/agent-notification-id.test.ts
index c167750094d..bd32a616cdb 100644
--- a/src/shared/agent-notification-id.test.ts
+++ b/src/shared/agent-notification-id.test.ts
@@ -2,14 +2,18 @@ import { describe, expect, it } from 'vitest'
import { buildAgentNotificationId } from './agent-notification-id'
describe('buildAgentNotificationId', () => {
- it('builds a stable id for the same agent event metadata', () => {
+ it('builds a stable escaped id for the same agent event metadata', () => {
const args = {
worktreeId: 'repo::/Users/me/orca/workspaces/feature',
paneKey: 'tab-1:11111111-1111-4111-8111-111111111111',
stateStartedAt: 1780000000123
}
- expect(buildAgentNotificationId(args)).toBe(buildAgentNotificationId(args))
+ const id = buildAgentNotificationId(args)
+ expect(id).toBe(
+ 'agent:repo%3A%3A%2FUsers%2Fme%2Forca%2Fworkspaces%2Ffeature:tab-1%3A11111111-1111-4111-8111-111111111111:1780000000123'
+ )
+ expect(buildAgentNotificationId(args)).toBe(id)
})
it('changes when the agent state start time changes', () => {