fmt(js): npm run fix on merge (#106039)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
f03ed94a34
commit
e9bb6e86fb
@@ -162,8 +162,7 @@ export type CommandDispatchResponse =
|
||||
| SendCommandDispatchResponse
|
||||
| PrefillCommandDispatchResponse
|
||||
|
||||
export type SidebarNavId =
|
||||
'artifacts' | 'command-center' | 'cron' | 'messaging' | 'new-session' | 'settings' | 'skills'
|
||||
export type SidebarNavId = 'artifacts' | 'command-center' | 'cron' | 'messaging' | 'new-session' | 'settings' | 'skills'
|
||||
|
||||
export interface SidebarNavItem {
|
||||
/** Built-in view id, or a contributed row's namespaced contribution id. */
|
||||
|
||||
@@ -62,7 +62,15 @@ import { displayName, stripPreviewMarkdown } from './labels'
|
||||
import { duplicateBot } from './profile-ops'
|
||||
import { openRosterBot } from './roster-actions'
|
||||
import { botRosterMeta, botWorkspaceOwnerKey, setBotsWorkspaceOwner } from './routing'
|
||||
import { A2A_PREFIX_RE, botCanonicalSessionId, botRowOwnsWorkspace, botWorkingMood, previewKind, useTurnBusy, workerActiveAt } from './row-helpers'
|
||||
import {
|
||||
A2A_PREFIX_RE,
|
||||
botCanonicalSessionId,
|
||||
botRowOwnsWorkspace,
|
||||
botWorkingMood,
|
||||
previewKind,
|
||||
useTurnBusy,
|
||||
workerActiveAt
|
||||
} from './row-helpers'
|
||||
import type { GroupMember, RosterRow, SidebarRowLabels } from './types'
|
||||
import { $botSections, $draggingBot, BOT_DRAG_MIME, botSectionId, moveBotsToSection } from './user-sections'
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ export function followGroupChat(group: string, onRename: (name: string) => void)
|
||||
return
|
||||
}
|
||||
|
||||
const moved = Object.entries(rooms).find(([, room]) =>
|
||||
!room.tombstone && (prior.roomId ? room.roomId === prior.roomId : room === prior)
|
||||
const moved = Object.entries(rooms).find(
|
||||
([, room]) => !room.tombstone && (prior.roomId ? room.roomId === prior.roomId : room === prior)
|
||||
)
|
||||
|
||||
if (!moved) {
|
||||
|
||||
@@ -14,16 +14,31 @@ describe('room display order', () => {
|
||||
expect(sortGroupRosterRows(rows, {}).map(row => row.name)).toEqual(['Pinned', 'Newer', 'Bot', 'Older'])
|
||||
const rooms = { Older: { rosterOrder: 0 }, Newer: { rosterOrder: 1 } }
|
||||
expect(sortGroupRosterRows(rows, rooms).map(row => row.name)).toEqual(['Pinned', 'Older', 'Bot', 'Newer'])
|
||||
expect(sortGroupRosterRows(rows.map(row => ({ ...row, activity: row.name === 'Newer' ? 999 : row.activity })), rooms).filter(row => row.kind === 'group').map(row => row.name)).toEqual(['Pinned', 'Older', 'Newer'])
|
||||
expect(
|
||||
sortGroupRosterRows(
|
||||
rows.map(row => ({ ...row, activity: row.name === 'Newer' ? 999 : row.activity })),
|
||||
rooms
|
||||
)
|
||||
.filter(row => row.kind === 'group')
|
||||
.map(row => row.name)
|
||||
).toEqual(['Pinned', 'Older', 'Newer'])
|
||||
expect(rows[0].name).toBe('Older')
|
||||
})
|
||||
|
||||
it('moves only visible same-band rooms while retaining hidden slots and ignoring stale targets', () => {
|
||||
const ordered = sortGroupRosterRows(rows.filter(row => row.kind === 'group'), {})
|
||||
const ordered = sortGroupRosterRows(
|
||||
rows.filter(row => row.kind === 'group'),
|
||||
{}
|
||||
)
|
||||
expect(reorderGroupRows(ordered, 'Older', -1)).toEqual(['Pinned', 'Older', 'Newer'])
|
||||
expect(reorderGroupRows(ordered, 'Newer', -1)).toBeNull()
|
||||
expect(reorderGroupRows(ordered, 'deleted', 1)).toBeNull()
|
||||
const hidden = { kind: 'group' as const, name: 'Hidden', activity: 2, pinned: false }
|
||||
expect(reorderGroupRows([ordered[0], ordered[1], hidden, ordered[2]], 'Older', -1, ['Newer', 'Older'])).toEqual(['Pinned', 'Older', 'Hidden', 'Newer'])
|
||||
expect(reorderGroupRows([ordered[0], ordered[1], hidden, ordered[2]], 'Older', -1, ['Newer', 'Older'])).toEqual([
|
||||
'Pinned',
|
||||
'Older',
|
||||
'Hidden',
|
||||
'Newer'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,23 +6,32 @@ interface OrderRow {
|
||||
}
|
||||
|
||||
/** Reorder room slots, not bots or folders. Pinning remains the outer band. */
|
||||
export function sortGroupRosterRows<T extends OrderRow>(rows: T[], rooms: Record<string, { rosterOrder?: number }>): T[] {
|
||||
export function sortGroupRosterRows<T extends OrderRow>(
|
||||
rows: T[],
|
||||
rooms: Record<string, { rosterOrder?: number }>
|
||||
): T[] {
|
||||
const legacy = rows.slice().sort((a, b) => Number(b.pinned) - Number(a.pinned) || b.activity - a.activity)
|
||||
|
||||
const groups = legacy.filter(row => row.kind === 'group').sort((a, b) =>
|
||||
Number(b.pinned) - Number(a.pinned) ||
|
||||
(rooms[a.name!]?.rosterOrder ?? Infinity) - (rooms[b.name!]?.rosterOrder ?? Infinity)
|
||||
)
|
||||
const groups = legacy
|
||||
.filter(row => row.kind === 'group')
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Number(b.pinned) - Number(a.pinned) ||
|
||||
(rooms[a.name!]?.rosterOrder ?? Infinity) - (rooms[b.name!]?.rosterOrder ?? Infinity)
|
||||
)
|
||||
|
||||
let index = 0
|
||||
|
||||
return legacy.map(row => row.kind === 'group' ? groups[index++] : row)
|
||||
return legacy.map(row => (row.kind === 'group' ? groups[index++] : row))
|
||||
}
|
||||
|
||||
/** Swap visible neighbours without dropping filtered-out rooms from the order. */
|
||||
export function reorderGroupRows(rows: OrderRow[], name: string, delta: -1 | 1, visible?: string[]): string[] | null {
|
||||
const row = rows.find(row => row.name === name)
|
||||
const band = rows.filter(candidate => candidate.kind === 'group' && candidate.pinned === row?.pinned && (!visible || visible.includes(candidate.name!)))
|
||||
const band = rows.filter(
|
||||
candidate =>
|
||||
candidate.kind === 'group' && candidate.pinned === row?.pinned && (!visible || visible.includes(candidate.name!))
|
||||
)
|
||||
const index = band.findIndex(candidate => candidate.name === name)
|
||||
const neighbour = index >= 0 ? band[index + delta] : undefined
|
||||
|
||||
@@ -30,5 +39,7 @@ export function reorderGroupRows(rows: OrderRow[], name: string, delta: -1 | 1,
|
||||
return null
|
||||
}
|
||||
|
||||
return rows.filter(row => row.kind === 'group').map(row => row.name === name ? neighbour.name! : row.name === neighbour.name ? name : row.name!)
|
||||
return rows
|
||||
.filter(row => row.kind === 'group')
|
||||
.map(row => (row.name === name ? neighbour.name! : row.name === neighbour.name ? name : row.name!))
|
||||
}
|
||||
|
||||
@@ -369,7 +369,8 @@ describe('clarify and approvals (#90694)', () => {
|
||||
// on $groupNeedsYou/$groupClarify AFTER the turn lands proves nothing:
|
||||
// the clarify has already resolved and its mirror is gone by then.
|
||||
onResumePoll: () => {
|
||||
sawPendingAttention = sawPendingAttention || live!.turns.groupHasPendingClarify(live!.chat.$groupClarify.get(), 'Core')
|
||||
sawPendingAttention =
|
||||
sawPendingAttention || live!.turns.groupHasPendingClarify(live!.chat.$groupClarify.get(), 'Core')
|
||||
},
|
||||
turn: () => 'targeting staging'
|
||||
})
|
||||
@@ -506,17 +507,24 @@ describe('clarify and approvals (#90694)', () => {
|
||||
it('keeps late prompt snapshots on the live room and never revives a disbanded room', async ({ onTestFinished }) => {
|
||||
for (const roomId of ['stable-room', undefined]) {
|
||||
for (const disband of [false, true]) {
|
||||
const room = await loadRoom({ turn: ({ n }) => n === 1 ? 'Completed reply' : '(pass)' })
|
||||
const room = await loadRoom({ turn: ({ n }) => (n === 1 ? 'Completed reply' : '(pass)') })
|
||||
const view = await import('./group-chat-view')
|
||||
const member = { name: 'research', title: '' }
|
||||
room.chat.updateGroupChat('Core', current => ({
|
||||
...current, roomId, running: true, epoch: 1,
|
||||
...current,
|
||||
roomId,
|
||||
running: true,
|
||||
epoch: 1,
|
||||
log: [{ id: 'input', at: 1, from: { kind: 'user', name: 'You' }, text: '@research check', thread: 'thread' }]
|
||||
}))
|
||||
let entered!: () => void
|
||||
let release!: () => void
|
||||
const polled = new Promise<void>(resolve => { entered = resolve })
|
||||
const held = new Promise<void>(resolve => { release = resolve })
|
||||
const polled = new Promise<void>(resolve => {
|
||||
entered = resolve
|
||||
})
|
||||
const held = new Promise<void>(resolve => {
|
||||
release = resolve
|
||||
})
|
||||
const original = host.request as (method: string, params: Record<string, unknown>) => Promise<any>
|
||||
let submitted = false
|
||||
let answered = false
|
||||
@@ -525,12 +533,19 @@ describe('clarify and approvals (#90694)', () => {
|
||||
host.request = async (method: string, params: Record<string, unknown>) => {
|
||||
const result = await original(method, params)
|
||||
|
||||
if (method === 'prompt.submit') {submitted = true}
|
||||
if (method === 'prompt.submit') {
|
||||
submitted = true
|
||||
}
|
||||
|
||||
if (method === 'clarify.respond') {answered = true}
|
||||
if (method === 'clarify.respond') {
|
||||
answered = true
|
||||
}
|
||||
|
||||
if (method === 'session.resume' && submitted && !answered) {
|
||||
if (++polls === 1) { entered(); await held }
|
||||
if (++polls === 1) {
|
||||
entered()
|
||||
await held
|
||||
}
|
||||
|
||||
return { ...result, pending_clarify: CLARIFY }
|
||||
}
|
||||
@@ -546,14 +561,19 @@ describe('clarify and approvals (#90694)', () => {
|
||||
release()
|
||||
await drive
|
||||
expect(Object.values(room.chat.$groupClarify.get())).toHaveLength(0)
|
||||
expect(room.chat.$groupChats.get().Core === undefined || room.chat.$groupChats.get().Core.tombstone).toBe(true)
|
||||
expect(room.chat.$groupChats.get().Core === undefined || room.chat.$groupChats.get().Core.tombstone).toBe(
|
||||
true
|
||||
)
|
||||
expect(room.chat.$groupChats.get().Core?.log || []).toHaveLength(0)
|
||||
} else {
|
||||
await view.renameGroupChat('Core', 'Renamed', [])
|
||||
|
||||
const mirrored = new Promise<void>(resolve => {
|
||||
const stop = room.chat.$groupClarify.listen(entries => {
|
||||
if (Object.keys(entries).length) { stop(); resolve() }
|
||||
if (Object.keys(entries).length) {
|
||||
stop()
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -566,7 +586,12 @@ describe('clarify and approvals (#90694)', () => {
|
||||
expect(correctRoom).toBe('Renamed')
|
||||
expect(Object.keys(room.chat.$groupChats.get())).toEqual(['Renamed'])
|
||||
expect(room.chat.$groupChats.get().Renamed.running).toBe(false)
|
||||
expect(room.chat.$groupChats.get().Renamed.log.filter(entry => entry.from.kind === 'member').map(entry => entry.text)).toEqual(['Completed reply'])
|
||||
expect(
|
||||
room.chat.$groupChats
|
||||
.get()
|
||||
.Renamed.log.filter(entry => entry.from.kind === 'member')
|
||||
.map(entry => entry.text)
|
||||
).toEqual(['Completed reply'])
|
||||
expect(Object.values(room.chat.$groupClarify.get())).toHaveLength(0)
|
||||
}
|
||||
}
|
||||
@@ -581,10 +606,20 @@ describe('clarify and approvals (#90694)', () => {
|
||||
const data = await import('./data')
|
||||
const members = [{ name: 'research' }, { name: 'ops' }]
|
||||
room.chat.updateGroupChat('Core', current => ({
|
||||
...current, roomId: 'old-rejection-room', running: true,
|
||||
...current,
|
||||
roomId: 'old-rejection-room',
|
||||
running: true,
|
||||
log: continuation
|
||||
? [{ id: 'handoff', at: 1, from: { kind: 'member', name: 'research' }, text: '@ops check', thread: 'thread' },
|
||||
{ id: 'input', at: 2, from: { kind: 'user', name: 'You' }, text: '@research check', thread: 'thread' }]
|
||||
? [
|
||||
{
|
||||
id: 'handoff',
|
||||
at: 1,
|
||||
from: { kind: 'member', name: 'research' },
|
||||
text: '@ops check',
|
||||
thread: 'thread'
|
||||
},
|
||||
{ id: 'input', at: 2, from: { kind: 'user', name: 'You' }, text: '@research check', thread: 'thread' }
|
||||
]
|
||||
: [{ id: 'input', at: 1, from: { kind: 'user', name: 'You' }, text: 'check', thread: 'thread' }],
|
||||
watermarks: { 'thread::research': continuation ? 2 : 0 }
|
||||
}))
|
||||
@@ -592,23 +627,47 @@ describe('clarify and approvals (#90694)', () => {
|
||||
// handoff is driven by the continuation phase.
|
||||
let phaseEntered!: () => void
|
||||
let release!: () => void
|
||||
const entered = new Promise<void>(resolve => { phaseEntered = resolve })
|
||||
const held = new Promise<void>(resolve => { release = resolve })
|
||||
const entered = new Promise<void>(resolve => {
|
||||
phaseEntered = resolve
|
||||
})
|
||||
const held = new Promise<void>(resolve => {
|
||||
release = resolve
|
||||
})
|
||||
const original = host.request as (method: string, params: Record<string, unknown>) => Promise<any>
|
||||
|
||||
host.request = async (method: string, params: Record<string, unknown>) => {
|
||||
if (method === rejectedMethod) { phaseEntered(); await held; throw new Error('401 unauthorized late rejection') }
|
||||
if (method === rejectedMethod) {
|
||||
phaseEntered()
|
||||
await held
|
||||
throw new Error('401 unauthorized late rejection')
|
||||
}
|
||||
|
||||
return original(method, params)
|
||||
}
|
||||
|
||||
const drive = room.rounds.runGroupChatRounds('Core', members, 'thread')
|
||||
await entered
|
||||
await view.disbandGroupChat('Core', [])
|
||||
room.chat.updateGroupChat('Core', current => ({ ...current, roomId: 'replacement-rejection-room', tombstone: false }))
|
||||
room.chat.updateGroupChat('Core', current => ({
|
||||
...current,
|
||||
roomId: 'replacement-rejection-room',
|
||||
tombstone: false
|
||||
}))
|
||||
room.chat.appendGroupChatEntry('Core', { kind: 'member', name: 'research' }, '@user replacement needs you')
|
||||
const before = structuredClone({ rooms: room.chat.$groupChats.get(), activity: activity.$groupActivity.get(), attention: data.$botAttention.get(), needsYou: room.chat.$groupNeedsYou.get() })
|
||||
const before = structuredClone({
|
||||
rooms: room.chat.$groupChats.get(),
|
||||
activity: activity.$groupActivity.get(),
|
||||
attention: data.$botAttention.get(),
|
||||
needsYou: room.chat.$groupNeedsYou.get()
|
||||
})
|
||||
release()
|
||||
await drive
|
||||
expect({ rooms: room.chat.$groupChats.get(), activity: activity.$groupActivity.get(), attention: data.$botAttention.get(), needsYou: room.chat.$groupNeedsYou.get() }).toEqual(before)
|
||||
expect({
|
||||
rooms: room.chat.$groupChats.get(),
|
||||
activity: activity.$groupActivity.get(),
|
||||
attention: data.$botAttention.get(),
|
||||
needsYou: room.chat.$groupNeedsYou.get()
|
||||
}).toEqual(before)
|
||||
expect(room.gateway.rpcFor('prompt.submit')).toHaveLength(0)
|
||||
}
|
||||
}
|
||||
@@ -624,8 +683,12 @@ describe('clarify and approvals (#90694)', () => {
|
||||
room.chat.updateGroupChat('Core', current => ({ ...current, roomId: 'retired-room' }))
|
||||
let entered!: () => void
|
||||
let release!: () => void
|
||||
const polled = new Promise<void>(resolve => { entered = resolve })
|
||||
const held = new Promise<void>(resolve => { release = resolve })
|
||||
const polled = new Promise<void>(resolve => {
|
||||
entered = resolve
|
||||
})
|
||||
const held = new Promise<void>(resolve => {
|
||||
release = resolve
|
||||
})
|
||||
const original = host.request as (method: string, params: Record<string, unknown>) => Promise<any>
|
||||
let submitted = false
|
||||
|
||||
@@ -638,7 +701,9 @@ describe('clarify and approvals (#90694)', () => {
|
||||
|
||||
const result = await original(method, params)
|
||||
|
||||
if (method === 'prompt.submit') { submitted = true }
|
||||
if (method === 'prompt.submit') {
|
||||
submitted = true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -667,30 +732,47 @@ describe('clarify and approvals (#90694)', () => {
|
||||
const view = await import('./group-chat-view')
|
||||
const members = [{ name: 'research' }, { name: 'ops' }]
|
||||
room.chat.updateGroupChat('Core', current => ({
|
||||
...current, roomId: 'old-harvest-room', running: true,
|
||||
...current,
|
||||
roomId: 'old-harvest-room',
|
||||
running: true,
|
||||
stranded: { research: 0, ops: 0 }
|
||||
}))
|
||||
let tick!: () => void
|
||||
const previousWindow = globalThis.window
|
||||
vi.stubGlobal('window', { setTimeout: (callback: () => void) => { tick = callback;
|
||||
vi.stubGlobal('window', {
|
||||
setTimeout: (callback: () => void) => {
|
||||
tick = callback
|
||||
|
||||
return 0 } })
|
||||
onTestFinished(() => { vi.stubGlobal('window', previousWindow) })
|
||||
return 0
|
||||
}
|
||||
})
|
||||
onTestFinished(() => {
|
||||
vi.stubGlobal('window', previousWindow)
|
||||
})
|
||||
let entered!: () => void
|
||||
let release!: () => void
|
||||
const polled = new Promise<void>(resolve => { entered = resolve })
|
||||
const held = new Promise<void>(resolve => { release = resolve })
|
||||
const polled = new Promise<void>(resolve => {
|
||||
entered = resolve
|
||||
})
|
||||
const held = new Promise<void>(resolve => {
|
||||
release = resolve
|
||||
})
|
||||
let background = false
|
||||
const backgroundProfiles: unknown[] = []
|
||||
const original = host.request as (method: string, params: Record<string, unknown>) => Promise<any>
|
||||
|
||||
host.request = async (method: string, params: Record<string, unknown>) => {
|
||||
if (method !== 'session.resume') { return original(method, params) }
|
||||
if (method !== 'session.resume') {
|
||||
return original(method, params)
|
||||
}
|
||||
|
||||
if (background) {
|
||||
backgroundProfiles.push(params.profile)
|
||||
|
||||
if (params.profile === 'research') { entered(); await held }
|
||||
if (params.profile === 'research') {
|
||||
entered()
|
||||
await held
|
||||
}
|
||||
}
|
||||
|
||||
return { running: true, pending_clarify: CLARIFY }
|
||||
@@ -704,7 +786,9 @@ describe('clarify and approvals (#90694)', () => {
|
||||
const { setImmediate } = await import('node:timers/promises')
|
||||
await setImmediate()
|
||||
room.chat.updateGroupChat('Core', current => ({
|
||||
...current, roomId: 'new-harvest-room', stranded: { research: 0, ops: 0 }
|
||||
...current,
|
||||
roomId: 'new-harvest-room',
|
||||
stranded: { research: 0, ops: 0 }
|
||||
}))
|
||||
const roomsBefore = structuredClone(room.chat.$groupChats.get())
|
||||
const promptsBefore = structuredClone(room.chat.$groupClarify.get())
|
||||
@@ -723,7 +807,8 @@ describe('clarify and approvals (#90694)', () => {
|
||||
const room = await loadRoom({
|
||||
approvalUntil: { research: { payload: APPROVAL, until: 3 } },
|
||||
onResumePoll: () => {
|
||||
sawPendingAttention = sawPendingAttention || live!.turns.groupHasPendingClarify(live!.chat.$groupClarify.get(), 'Core')
|
||||
sawPendingAttention =
|
||||
sawPendingAttention || live!.turns.groupHasPendingClarify(live!.chat.$groupClarify.get(), 'Core')
|
||||
},
|
||||
turn: () => 'build cleaned'
|
||||
})
|
||||
|
||||
@@ -103,9 +103,15 @@ describe('the pet gallery', () => {
|
||||
})
|
||||
|
||||
it('keeps selection while scrolling for more and resets the search window', async () => {
|
||||
useQueryMock.mockReturnValue({ data: { pets: Array.from({ length: 60 }, (_, i) => ({
|
||||
displayName: `Pet ${i}`, slug: `pet-${i}`, spritesheetUrl: SHEET
|
||||
})) } })
|
||||
useQueryMock.mockReturnValue({
|
||||
data: {
|
||||
pets: Array.from({ length: 60 }, (_, i) => ({
|
||||
displayName: `Pet ${i}`,
|
||||
slug: `pet-${i}`,
|
||||
spritesheetUrl: SHEET
|
||||
}))
|
||||
}
|
||||
})
|
||||
stubFetch(async () => ({ blob: async () => new Blob() }))
|
||||
const PetTab = await loadPetTab()
|
||||
const onImage = vi.fn()
|
||||
@@ -115,7 +121,9 @@ describe('the pet gallery', () => {
|
||||
await waitFor(() => expect(onImage).toHaveBeenCalledWith('data:image/png;base64,ok'))
|
||||
const scroller = first.parentElement!.parentElement!
|
||||
Object.defineProperties(scroller, {
|
||||
clientHeight: { value: 220 }, scrollHeight: { value: 600 }, scrollTop: { value: 400 }
|
||||
clientHeight: { value: 220 },
|
||||
scrollHeight: { value: 600 },
|
||||
scrollTop: { value: 400 }
|
||||
})
|
||||
fireEvent.scroll(scroller)
|
||||
expect(view.getByText('Pet 47')).toBeTruthy()
|
||||
|
||||
@@ -43,7 +43,12 @@ export function RosterGroupRowView({
|
||||
activity: groupLastActivity(current[name])
|
||||
}))
|
||||
|
||||
const order = reorderGroupRows(sortGroupRosterRows(rows, current), name, delta, sortedGroupRows.map(row => row.name))
|
||||
const order = reorderGroupRows(
|
||||
sortGroupRosterRows(rows, current),
|
||||
name,
|
||||
delta,
|
||||
sortedGroupRows.map(row => row.name)
|
||||
)
|
||||
|
||||
order?.forEach((name, rosterOrder) => {
|
||||
updateGroupChat(name, room => ({ ...room, rosterOrder }), { sync: false })
|
||||
|
||||
@@ -296,7 +296,9 @@ export function BotsPane() {
|
||||
// and the persisted connection registry hydrate. Keep that transition in a
|
||||
// neutral loading state instead of flashing the first-run "No bots" copy.
|
||||
const initialRosterLoading = !data && !error && roster.length === 0
|
||||
const activeRosterKeys = new Set(activeBots(roster, workingOwner, turnBusy, Date.now(), activeConnectionId).map(botRosterKey))
|
||||
const activeRosterKeys = new Set(
|
||||
activeBots(roster, workingOwner, turnBusy, Date.now(), activeConnectionId).map(botRosterKey)
|
||||
)
|
||||
const gatewayOptions = rosterGatewayOptions(sourceSnapshot, roster)
|
||||
const selectedGateway = gatewayOptions.find(option => option.connectionId === gatewayFilter)
|
||||
const gatewayFilterExists = gatewayFilter === 'all' || Boolean(selectedGateway)
|
||||
|
||||
@@ -105,7 +105,9 @@ describe('which bots are working right now', () => {
|
||||
expect(activeBots([local, remote], owner, false, NOW)).toEqual([])
|
||||
expect(activeBots([local, remote], null, true, NOW)).toEqual([])
|
||||
expect(activeBots([local, remote], { ...owner, authoritative: false }, true, NOW)).toEqual([])
|
||||
expect(activeBots([row({ name: 'analyst', remoteSource: true })], { ...owner, connectionId: '' }, true, NOW)).toEqual([])
|
||||
expect(
|
||||
activeBots([row({ name: 'analyst', remoteSource: true })], { ...owner, connectionId: '' }, true, NOW)
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('includes activity inside the liveness window and excludes activity outside it', () => {
|
||||
|
||||
@@ -108,7 +108,13 @@ export function botWorkingMood(
|
||||
): 'idle' | 'think' | 'work' {
|
||||
const botConnectionId = bot.connectionId || (bot.remoteSource ? '' : activeConnectionId)
|
||||
|
||||
if (turnBusy && owner?.authoritative && owner.connectionId && owner.name === bot.name && owner.connectionId === botConnectionId) {
|
||||
if (
|
||||
turnBusy &&
|
||||
owner?.authoritative &&
|
||||
owner.connectionId &&
|
||||
owner.name === bot.name &&
|
||||
owner.connectionId === botConnectionId
|
||||
) {
|
||||
return 'think'
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,13 @@ import { describe, expect, it } from 'vitest'
|
||||
|
||||
const { matchKnownFailure, rules } = createRequire(import.meta.url)('../tests/install/e2e-assets/known-failures.cjs')
|
||||
const classifier = path.resolve(import.meta.dirname, '../tests/install/e2e-assets/known-failures.cjs')
|
||||
|
||||
const lockedLog = [
|
||||
'error: failed to remove file `C:/install/venv/Lib/site-packages/../../Scripts/hermes.exe`: Access is denied. (os error 5)',
|
||||
'File "C:/install/venv/Scripts/hermes.exe/__main__.py", line 10, in <module>',
|
||||
"subprocess.CalledProcessError: Command '['uv', 'pip', 'install', '-e', '.', '--quiet']' returned non-zero exit status 2.",
|
||||
].join('\n')
|
||||
|
||||
const base = {
|
||||
platform: 'windows', phase: 'update', commit: 'a370ab8391ca5f8de7ebbc449f05cb0df36ade7c',
|
||||
installMethod: 'installer-script', updateMethod: 'hermes-update',
|
||||
@@ -42,6 +44,7 @@ describe('known install failures', () => {
|
||||
error: 'E2E ASSERTION FAILED: app driven via captured hermes desktop spec; update completed',
|
||||
logs: { desktop: '[hermes] [updates] no staged updater; surfacing manual `hermes update` for CLI install at C:/install\n[hermes] [updates] manual: hermes update\n' },
|
||||
}
|
||||
|
||||
expect(matchKnownFailure(sample)?.id).toBe('windows-july-manual-app-update')
|
||||
expect(matchKnownFailure({ ...sample, installMethod: 'desktop-installer@latest' })).toBeNull()
|
||||
expect(matchKnownFailure({ ...sample, error: 'onboarding timed out' })).toBeNull()
|
||||
@@ -50,6 +53,7 @@ describe('known install failures', () => {
|
||||
|
||||
it('CLI writes a receipt and exits zero only on a confirmed match', () => {
|
||||
const root = mkdtempSync(path.join(os.tmpdir(), 'known-install-'))
|
||||
|
||||
try {
|
||||
mkdirSync(path.join(root, 'logs'))
|
||||
writeFileSync(path.join(root, 'shas.json'), '\uFEFF' + JSON.stringify({ old: base.commit, current: 'f'.repeat(40), old_ref: 'v2026.3.12' }))
|
||||
|
||||
@@ -24,6 +24,7 @@ it('waits for native close, not exit, and retains a close observed before hand-o
|
||||
|
||||
it('fails if the launched process never closes', async () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
try {
|
||||
const waitForClose = observeProcessClose(Object.assign(new EventEmitter(), { stdio: [], exitCode: null, signalCode: null }))
|
||||
const completion = expect(waitForClose(2_000)).rejects.toThrow('Electron process did not close')
|
||||
|
||||
@@ -505,6 +505,7 @@ export function startMockServer(options: MockServerOptions = {}): Promise<MockSe
|
||||
const isSidebarCrossTrigger = userText.includes('E2E_SIDEBAR_CROSS')
|
||||
const isQueueStopTrigger = userText.includes('E2E_QUEUE_STOP_TRIGGER')
|
||||
const isTaskPanelResumeTrigger = userText.includes(TASK_PANEL_RESUME_TRIGGER)
|
||||
|
||||
const isVerificationStopTrigger = messages.some(
|
||||
message => typeof message?.content === 'string' && message.content.includes(VERIFICATION_STOP_TRIGGER),
|
||||
)
|
||||
@@ -517,7 +518,9 @@ export function startMockServer(options: MockServerOptions = {}): Promise<MockSe
|
||||
const turn =
|
||||
TASK_PANEL_RESUME_SCRIPT[_taskPanelResumeIndex] ??
|
||||
TASK_PANEL_RESUME_SCRIPT[TASK_PANEL_RESUME_SCRIPT.length - 1]
|
||||
|
||||
_taskPanelResumeIndex++
|
||||
|
||||
const respond = () => {
|
||||
if (stream) {
|
||||
streamScriptedTurn(res, model, turn)
|
||||
@@ -533,6 +536,7 @@ export function startMockServer(options: MockServerOptions = {}): Promise<MockSe
|
||||
} else {
|
||||
respond()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -550,6 +554,7 @@ export function startMockServer(options: MockServerOptions = {}): Promise<MockSe
|
||||
} else {
|
||||
nonStreamingScriptedTurn(res, model, BATCH_CLARIFY_TURN)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user