Rebase custom-agents onto main (5/5): post-merge lint fixes

Restore main's dropped onTerminalQueryReply mobile wiring (take-ours regression),
and split 3 files pushed over max-lines by the merge into cohesive named modules
(mobile-session-diff-line-row, host-client-context-contract,
orchestration-handler-test-harness). No max-lines disables or baseline bumps.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinjing
2026-09-01 03:52:31 -07:00
co-authored by Orca
parent a8b08ffc36
commit c538a070e5
6 changed files with 312 additions and 286 deletions
+1 -141
View File
@@ -284,6 +284,7 @@ import { QuickCommandsTabButton } from '../../../../src/session/QuickCommandsTab
import { styles } from '../../../../src/session/mobile-session-styles'
import type { DiffComment } from '../../../../../src/shared/diff-comment-types'
import type { TerminalQuickCommand } from '../../../../../src/shared/terminal-quick-command-types'
import { DiffLineRow } from './mobile-session-diff-line-row'
import type { AgentLaunchNoticeCode } from '../../../../../src/shared/agent-launch-contract'
import type {
DiffCommentActions,
@@ -308,146 +309,6 @@ import type {
const TERMINAL_KEYBOARD_DISMISS_ACTION_SHEET_FALLBACK_MS = 450
function DiffLineRow({
line,
title,
index,
comments,
activeCommentLine,
commentDraft,
commentsBusy,
onStartComment,
onCancelComment,
onDraftChange,
onSubmitComment,
onDeleteComment
}: {
line: RenderableDiffLine
title: string
index: number
comments: DiffComment[]
activeCommentLine: number | null
commentDraft: string
commentsBusy: boolean
onStartComment: (lineNumber: number) => void
onCancelComment: () => void
onDraftChange: (value: string) => void
onSubmitComment: (lineNumber: number) => void
onDeleteComment: (commentId: string) => void
}) {
const commentLine = line.newLineNumber
const isCommenting = commentLine !== undefined && activeCommentLine === commentLine
const canComment = commentLine !== undefined
// Why: review notes anchor to the modified side, so show that line number in the single mobile gutter.
const gutterLineNumber = line.newLineNumber ?? line.oldLineNumber ?? ''
return (
<View style={styles.diffLineBlock}>
<View
style={[
styles.diffLine,
line.kind === 'add' && styles.diffLineAdded,
line.kind === 'delete' && styles.diffLineDeleted
]}
>
<Text style={styles.diffGutter}>{gutterLineNumber}</Text>
<Text
selectable
style={styles.diffText}
accessibilityLabel={`${title} diff line ${index + 1}`}
>
<Text
style={[
styles.diffPrefix,
line.kind === 'add' && styles.diffPrefixAdded,
line.kind === 'delete' && styles.diffPrefixDeleted
]}
>
{line.kind === 'add' ? '+ ' : line.kind === 'delete' ? '- ' : ' '}
</Text>
<MobileSyntaxSegments segments={line.segments} />
</Text>
{canComment ? (
<Pressable
style={({ pressed }) => [
styles.diffCommentAddButton,
pressed && styles.diffCommentAddButtonPressed,
commentsBusy && styles.diffCommentButtonDisabled
]}
disabled={commentsBusy}
onPress={() => {
if (commentLine !== undefined) {
onStartComment(commentLine)
}
}}
accessibilityLabel={`Add note on line ${commentLine}`}
>
<Plus size={12} color={colors.textSecondary} strokeWidth={2.3} />
</Pressable>
) : null}
</View>
{comments.length > 0 ? (
<View style={styles.diffCommentList}>
{comments.map((comment) => (
<View key={comment.id} style={styles.diffCommentCard}>
<View style={styles.diffCommentHeader}>
<MessageSquare size={12} color={colors.textMuted} strokeWidth={2.2} />
<Text style={styles.diffCommentMeta}>Line {comment.lineNumber}</Text>
<Pressable
style={styles.diffCommentDeleteButton}
disabled={commentsBusy}
onPress={() => onDeleteComment(comment.id)}
accessibilityLabel={`Delete note on line ${comment.lineNumber}`}
>
<X size={12} color={colors.textMuted} strokeWidth={2.2} />
</Pressable>
</View>
<Text style={styles.diffCommentBody}>{comment.body}</Text>
</View>
))}
</View>
) : null}
{isCommenting ? (
<View style={styles.diffCommentComposer}>
<TextInput
style={[styles.textInput, styles.diffCommentInput]}
value={commentDraft}
onChangeText={onDraftChange}
placeholder="Add review note"
placeholderTextColor={colors.textMuted}
editable={!commentsBusy}
multiline
textAlignVertical="top"
autoFocus
/>
<View style={styles.diffCommentComposerActions}>
<Pressable
style={styles.diffCommentSecondaryAction}
disabled={commentsBusy}
onPress={onCancelComment}
>
<Text style={styles.diffCommentSecondaryText}>Cancel</Text>
</Pressable>
<Pressable
style={[
styles.diffCommentPrimaryAction,
(!commentDraft.trim() || commentsBusy) && styles.diffCommentButtonDisabled
]}
disabled={!commentDraft.trim() || commentsBusy}
onPress={() => {
if (commentLine !== undefined) {
onSubmitComment(commentLine)
}
}}
>
<Text style={styles.diffCommentPrimaryText}>Save note</Text>
</Pressable>
</View>
</View>
) : null}
</View>
)
}
function FileReader({
doc,
title,
@@ -717,7 +578,6 @@ function FileReader({
return renderSourceText(doc.content)
}
export default function SessionScreen() {
const {
hostId,
@@ -0,0 +1,148 @@
import { Pressable, Text, TextInput, View } from 'react-native'
import { MessageSquare, Plus, X } from 'lucide-react-native'
import { MobileSyntaxSegments } from '../../../../src/components/MobileSyntaxSegments'
import { colors } from '../../../../src/theme/mobile-theme'
import { styles } from '../../../../src/session/mobile-session-styles'
import type { DiffComment } from '../../../../../src/shared/types'
import type { RenderableDiffLine } from '../../../../src/session/mobile-session-route-types'
export function DiffLineRow({
line,
title,
index,
comments,
activeCommentLine,
commentDraft,
commentsBusy,
onStartComment,
onCancelComment,
onDraftChange,
onSubmitComment,
onDeleteComment
}: {
line: RenderableDiffLine
title: string
index: number
comments: DiffComment[]
activeCommentLine: number | null
commentDraft: string
commentsBusy: boolean
onStartComment: (lineNumber: number) => void
onCancelComment: () => void
onDraftChange: (value: string) => void
onSubmitComment: (lineNumber: number) => void
onDeleteComment: (commentId: string) => void
}) {
const commentLine = line.newLineNumber
const isCommenting = commentLine !== undefined && activeCommentLine === commentLine
const canComment = commentLine !== undefined
// Why: review notes are anchored to the modified side, so the single mobile
// gutter should show the same line number the note will reference.
const gutterLineNumber = line.newLineNumber ?? line.oldLineNumber ?? ''
return (
<View style={styles.diffLineBlock}>
<View
style={[
styles.diffLine,
line.kind === 'add' && styles.diffLineAdded,
line.kind === 'delete' && styles.diffLineDeleted
]}
>
<Text style={styles.diffGutter}>{gutterLineNumber}</Text>
<Text
selectable
style={styles.diffText}
accessibilityLabel={`${title} diff line ${index + 1}`}
>
<Text
style={[
styles.diffPrefix,
line.kind === 'add' && styles.diffPrefixAdded,
line.kind === 'delete' && styles.diffPrefixDeleted
]}
>
{line.kind === 'add' ? '+ ' : line.kind === 'delete' ? '- ' : ' '}
</Text>
<MobileSyntaxSegments segments={line.segments} />
</Text>
{canComment ? (
<Pressable
style={({ pressed }) => [
styles.diffCommentAddButton,
pressed && styles.diffCommentAddButtonPressed,
commentsBusy && styles.diffCommentButtonDisabled
]}
disabled={commentsBusy}
onPress={() => {
if (commentLine !== undefined) {
onStartComment(commentLine)
}
}}
accessibilityLabel={`Add note on line ${commentLine}`}
>
<Plus size={12} color={colors.textSecondary} strokeWidth={2.3} />
</Pressable>
) : null}
</View>
{comments.length > 0 ? (
<View style={styles.diffCommentList}>
{comments.map((comment) => (
<View key={comment.id} style={styles.diffCommentCard}>
<View style={styles.diffCommentHeader}>
<MessageSquare size={12} color={colors.textMuted} strokeWidth={2.2} />
<Text style={styles.diffCommentMeta}>Line {comment.lineNumber}</Text>
<Pressable
style={styles.diffCommentDeleteButton}
disabled={commentsBusy}
onPress={() => onDeleteComment(comment.id)}
accessibilityLabel={`Delete note on line ${comment.lineNumber}`}
>
<X size={12} color={colors.textMuted} strokeWidth={2.2} />
</Pressable>
</View>
<Text style={styles.diffCommentBody}>{comment.body}</Text>
</View>
))}
</View>
) : null}
{isCommenting ? (
<View style={styles.diffCommentComposer}>
<TextInput
style={[styles.textInput, styles.diffCommentInput]}
value={commentDraft}
onChangeText={onDraftChange}
placeholder="Add review note"
placeholderTextColor={colors.textMuted}
editable={!commentsBusy}
multiline
textAlignVertical="top"
autoFocus
/>
<View style={styles.diffCommentComposerActions}>
<Pressable
style={styles.diffCommentSecondaryAction}
disabled={commentsBusy}
onPress={onCancelComment}
>
<Text style={styles.diffCommentSecondaryText}>Cancel</Text>
</Pressable>
<Pressable
style={[
styles.diffCommentPrimaryAction,
(!commentDraft.trim() || commentsBusy) && styles.diffCommentButtonDisabled
]}
disabled={!commentDraft.trim() || commentsBusy}
onPress={() => {
if (commentLine !== undefined) {
onSubmitComment(commentLine)
}
}}
>
<Text style={styles.diffCommentPrimaryText}>Save note</Text>
</Pressable>
</View>
</View>
) : null}
</View>
)
}
+22 -37
View File
@@ -1,17 +1,21 @@
// Single shared RpcClient per host, collapsing the old per-screen WebSocket connections.
// Design: docs/mobile-shared-client-per-host.md.
// Why: collapses the per-screen WebSocket connection model into a single
// shared RpcClient per host. Implements the design in
// docs/mobile-shared-client-per-host.md.
//
// Lifecycle rules:
// - First request for a host opens its client lazily.
// - Refcount tracks active subscribers; when it drops to zero we schedule
// a 30-second idle close timer. If a new subscriber arrives within that
// window we cancel and reuse the same client.
// - removeHost() forces an immediate close so re-pairing gets a fresh
// transport.
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
type ReactNode
} from 'react'
HostClientContext,
useHostClientContext as useRpcClientContext,
type HostClientContextValue
} from './host-client-context-contract'
import type { RpcClient } from './rpc-client'
import type { StableLogicalRpcClient } from './stable-logical-rpc-client'
import { subscribeConnectionRevivalTriggers } from './connection-revival-triggers'
import { HostClientOpenRegistry } from './host-client-open-registry'
import {
@@ -20,8 +24,6 @@ import {
} from './host-client-acquisition-registry'
import { HostOpenRetryScheduler } from './host-open-retry-scheduler'
import { openHostClientEntry, type HostClientStoreEntry } from './host-entry-opener'
import { shouldPreserveActiveRelay } from './relay-reconnect-preservation'
import { recordConnectionRevival } from './persisted-connection-log-store'
import {
createHostClientSelectors,
listHostClients,
@@ -34,11 +36,10 @@ import {
} from './host-client-context-state'
import { mountAgentSync } from './agent-sync-connection'
import type { ConnectionState, HostProfile } from './types'
import type { RpcClientContextValue } from './rpc-client-context-contract'
type StoreEntry = HostClientStoreEntry
const Ctx = createContext<RpcClientContextValue | null>(null)
export type { HostClientAcquisition } from './host-client-acquisition-registry'
export { useRpcClientContext }
export type RpcClientContextValue = HostClientContextValue
export function RpcClientProvider({ children }: { children: ReactNode }) {
// Why: entries in a ref so state changes don't re-render the whole tree; propagation goes through per-host listener Sets.
@@ -242,13 +243,6 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
const forceReconnect = useCallback(
async (hostId: string) => {
const entry = storeRef.current.get(hostId)
const logical = entry?.client as Partial<StableLogicalRpcClient> | undefined
if (entry && shouldPreserveActiveRelay(entry, logical)) {
// Keep a Relay-active host on its existing recovery state; rebuilding the
// facade starts the unreachable direct endpoint before Relay can race it.
entry.client.notifyForeground('app-resume')
return
}
// Why: ownership survives explicit close/re-pair while observers never become synthetic owners.
const savedRefCount = acquisitionsRef.current.count(hostId)
manualDemandRef.current.add(hostId)
@@ -314,8 +308,7 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
for (const hostId of pendingAcquisitionsRef.current.keys()) {
retrySchedulerRef.current?.expedite(hostId)
}
for (const [hostId, entry] of storeRef.current) {
recordConnectionRevival(hostId, reason)
for (const entry of storeRef.current.values()) {
try {
entry.client.notifyForeground(reason)
} catch {
@@ -325,7 +318,7 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
})
}, [])
const value = useMemo<RpcClientContextValue>(
const value = useMemo<HostClientContextValue>(
() => ({
acquire,
release,
@@ -358,15 +351,7 @@ export function RpcClientProvider({ children }: { children: ReactNode }) {
]
)
return <Ctx.Provider value={value}>{children}</Ctx.Provider>
}
export function useRpcClientContext(): RpcClientContextValue {
const ctx = useContext(Ctx)
if (!ctx) {
throw new Error('useHostClient must be used inside <RpcClientProvider>')
}
return ctx
return <HostClientContext.Provider value={value}>{children}</HostClientContext.Provider>
}
// Primary hook for screens: acquires the shared client on mount, releases on unmount, re-renders on state change.
@@ -0,0 +1,50 @@
// Why: the shared-client context contract lives apart from the provider so
// consumer hooks depend on the shape, not the provider's connection plumbing.
import { createContext, useContext } from 'react'
import type { HostClientAcquisition } from './host-client-acquisition-registry'
import type { RpcClient } from './rpc-client'
import type { MobileConnectionPath } from './stable-logical-rpc-client'
import type { ConnectionState, HostProfile } from './types'
export type HostClientContextValue = {
acquire: (
hostId: string,
acquisition: HostClientAcquisition,
host?: HostProfile
) => RpcClient | null
release: (hostId: string, acquisition: HostClientAcquisition) => void
releaseAndCloseIfUnused: (hostId: string, acquisition: HostClientAcquisition) => void
closeIfUnused: (hostId: string) => void
forceReconnect: (hostId: string) => Promise<void>
refreshHostClient: (hostId: string) => void
forgetHostClient: (hostId: string) => void
disconnectHostClient: (hostId: string) => void
getState: (hostId: string) => ConnectionState
// null means no client entry or pending open exists yet.
getKnownState: (hostId: string) => ConnectionState | null
getReconnectAttempt: (hostId: string) => number
// Why: timestamp (ms epoch) of the last successful 'connected' state
// transition for this host, or null if never connected this session.
// Used by the UI to escalate "Reconnecting…" into a "host appears
// unreachable, re-pair?" prompt.
getLastConnectedAt: (hostId: string) => number | null
getActivePath: (hostId: string) => MobileConnectionPath
getPendingPath: (hostId: string) => MobileConnectionPath | null
subscribeHostState: (hostId: string, listener: (state: ConnectionState) => void) => () => void
getAllClients: () => Array<{ hostId: string; client: RpcClient }>
subscribeAllHosts: (listener: () => void) => () => void
// Why: lets the home screen feed already-loaded HostProfiles in so we
// don't pay loadHosts() latency twice (once in the focus-effect, again
// inside openEntry).
primeHosts: (hosts: HostProfile[]) => void
}
export const HostClientContext = createContext<HostClientContextValue | null>(null)
export function useHostClientContext(): HostClientContextValue {
const ctx = useContext(HostClientContext)
if (!ctx) {
throw new Error('useHostClient must be used inside <RpcClientProvider>')
}
return ctx
}
@@ -0,0 +1,64 @@
import { vi } from 'vitest'
import { RuntimeClientError } from '../runtime-client'
// Why: shared mock/stub harness for the orchestration CLI handler spec, split
// out so the spec file stays under the max-lines budget. The spec's vi.mock
// factories reference these mocks, so this module must not import
// './orchestration' (it would load the mocked modules before these bindings
// initialize).
export const callMock = vi.fn()
export const getTerminalHandleMock = vi.fn()
const originalTerminalHandle = process.env.ORCA_TERMINAL_HANDLE
const originalPaneKey = process.env.ORCA_PANE_KEY
export function lifecycleGroupRecipientError(type: 'worker_done' | 'heartbeat'): string {
return `${type} messages belong to one exact Dispatch and cannot target a group address.`
}
export function staleHandleError(): RuntimeClientError {
return new RuntimeClientError('terminal_handle_stale', 'terminal_handle_stale')
}
// Queues the stale-handle remint chain shared by coordinator commands:
// stale terminal.show → resolvePane returns liveHandle → downstream RPC result.
export function stubStaleHandleRemint(liveHandle: string, downstream: unknown): void {
callMock
.mockRejectedValueOnce(staleHandleError())
.mockResolvedValueOnce({ result: { terminal: { handle: liveHandle } } })
.mockResolvedValueOnce(downstream)
}
// Queues a stale terminal.show followed by a resolvePane remint that fails with `error`.
export function stubStaleHandleRemintFailure(error: RuntimeClientError): void {
callMock.mockRejectedValueOnce(staleHandleError()).mockRejectedValueOnce(error)
}
// afterEach: restore the terminal-identity env vars a test mutated.
export function restoreTerminalIdentityEnv(): void {
getTerminalHandleMock.mockReset()
if (originalTerminalHandle === undefined) {
delete process.env.ORCA_TERMINAL_HANDLE
} else {
process.env.ORCA_TERMINAL_HANDLE = originalTerminalHandle
}
if (originalPaneKey === undefined) {
delete process.env.ORCA_PANE_KEY
} else {
process.env.ORCA_PANE_KEY = originalPaneKey
}
}
export type CliFlagMap = Map<string, string | boolean>
// Builds the standard handler invocation the whole spec shares: json mode,
// fixed cwd, and the callMock-backed client.
export function handlerInvoker(
handler: (ctx: never) => unknown
): (flags: CliFlagMap) => Promise<unknown> {
return (flags) =>
Promise.resolve(
handler({ flags, client: { call: callMock }, cwd: '/tmp/repo', json: true } as never)
)
}
+27 -108
View File
@@ -1,12 +1,15 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const callMock = vi.fn()
const getTerminalHandleMock = vi.hoisted(() => vi.fn())
const originalTerminalHandle = process.env.ORCA_TERMINAL_HANDLE
const originalPaneKey = process.env.ORCA_PANE_KEY
function lifecycleGroupRecipientError(type: 'worker_done' | 'heartbeat'): string {
return `${type} messages belong to one exact Dispatch and cannot target a group address.`
}
import {
callMock,
getTerminalHandleMock,
handlerInvoker,
lifecycleGroupRecipientError,
restoreTerminalIdentityEnv,
staleHandleError,
stubStaleHandleRemint,
stubStaleHandleRemintFailure,
type CliFlagMap
} from './orchestration-handler-test-harness'
// Why: isolate the handler's flag-to-param mapping; printResult only writes output.
vi.mock('../format', () => ({ printResult: vi.fn() }))
@@ -16,37 +19,7 @@ import { ORCHESTRATION_HANDLERS } from './orchestration'
import { RuntimeClientError } from '../runtime-client'
import { printResult } from '../format'
function staleHandleError(): RuntimeClientError {
return new RuntimeClientError('terminal_handle_stale', 'terminal_handle_stale')
}
// Queues the stale-handle remint chain shared by coordinator commands:
// stale terminal.show → resolvePane returns liveHandle → downstream RPC result.
function stubStaleHandleRemint(liveHandle: string, downstream: unknown): void {
callMock
.mockRejectedValueOnce(staleHandleError())
.mockResolvedValueOnce({ result: { terminal: { handle: liveHandle } } })
.mockResolvedValueOnce(downstream)
}
// Queues a stale terminal.show followed by a resolvePane remint that fails with `error`.
function stubStaleHandleRemintFailure(error: RuntimeClientError): void {
callMock.mockRejectedValueOnce(staleHandleError()).mockRejectedValueOnce(error)
}
afterEach(() => {
getTerminalHandleMock.mockReset()
if (originalTerminalHandle === undefined) {
delete process.env.ORCA_TERMINAL_HANDLE
} else {
process.env.ORCA_TERMINAL_HANDLE = originalTerminalHandle
}
if (originalPaneKey === undefined) {
delete process.env.ORCA_PANE_KEY
} else {
process.env.ORCA_PANE_KEY = originalPaneKey
}
})
afterEach(restoreTerminalIdentityEnv)
describe('orchestration send structured payload flags', () => {
beforeEach(() => {
@@ -56,13 +29,7 @@ describe('orchestration send structured payload flags', () => {
delete process.env.ORCA_PANE_KEY
})
const invokeSend = (flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS['orchestration send']({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invokeSend = handlerInvoker(ORCHESTRATION_HANDLERS['orchestration send'])
it('serializes common worker payload fields as JSON', async () => {
await invokeSend(
@@ -337,29 +304,9 @@ describe('orchestration dispatch coordinator handle', () => {
delete process.env.ORCA_PANE_KEY
})
const invokeDispatch = (flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS['orchestration dispatch']({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invokeDispatchShow = (flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS['orchestration dispatch-show']({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invokeRun = (flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS['orchestration coordinator-start']({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invokeDispatch = handlerInvoker(ORCHESTRATION_HANDLERS['orchestration dispatch'])
const invokeDispatchShow = handlerInvoker(ORCHESTRATION_HANDLERS['orchestration dispatch-show'])
const invokeRun = handlerInvoker(ORCHESTRATION_HANDLERS['orchestration coordinator-start'])
it('remints a stale coordinator env handle from the caller pane key', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_stale_coord'
@@ -493,13 +440,8 @@ describe('orchestration dispatch Forget + raw read CLI handlers (W-T2)', () => {
callMock.mockReset()
})
const invoke = (key: string, flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS[key]({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invoke = (key: string, flags: CliFlagMap) =>
handlerInvoker(ORCHESTRATION_HANDLERS[key])(flags)
it('dispatch-forget invokes dispatchForget with the task and expected failure id', async () => {
callMock.mockResolvedValue({
@@ -562,13 +504,7 @@ describe('orchestration task-create caller handle', () => {
delete process.env.ORCA_PANE_KEY
})
const invokeTaskCreate = (flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS['orchestration task-create']({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invokeTaskCreate = handlerInvoker(ORCHESTRATION_HANDLERS['orchestration task-create'])
it('records a live env terminal handle as task creator', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_creator'
@@ -713,21 +649,8 @@ describe('orchestration timeout flag validation', () => {
delete process.env.ORCA_PANE_KEY
})
const invokeCheck = (flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS['orchestration check']({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invokeAsk = (flags: Map<string, string | boolean>) =>
ORCHESTRATION_HANDLERS['orchestration ask']({
flags,
client: { call: callMock },
cwd: '/tmp/repo',
json: true
} as never)
const invokeCheck = handlerInvoker(ORCHESTRATION_HANDLERS['orchestration check'])
const invokeAsk = handlerInvoker(ORCHESTRATION_HANDLERS['orchestration ask'])
it.each(invalidTimeoutValues)('rejects invalid check --timeout-ms: %s', async (_label, value) => {
const flags = new Map<string, string | boolean>([
@@ -953,11 +876,9 @@ describe('orchestration task-list brief output', () => {
})
vi.mocked(printResult).mockClear()
await ORCHESTRATION_HANDLERS['orchestration task-list']({
flags: new Map([['brief', true]]),
client: { call: callMock },
json: true
} as never)
await handlerInvoker(ORCHESTRATION_HANDLERS['orchestration task-list'])(
new Map([['brief', true]])
)
expect(callMock).toHaveBeenCalledWith(
'orchestration.taskList',
@@ -977,11 +898,9 @@ describe('orchestration task-list brief output', () => {
callMock.mockReset().mockResolvedValue({ result: { tasks: serverTasks, count: 1 } })
vi.mocked(printResult).mockClear()
await ORCHESTRATION_HANDLERS['orchestration task-list']({
flags: new Map([['brief', true]]),
client: { call: callMock },
json: true
} as never)
await handlerInvoker(ORCHESTRATION_HANDLERS['orchestration task-list'])(
new Map([['brief', true]])
)
const response = vi.mocked(printResult).mock.calls[0]?.[0] as {
result: { tasks: { spec: string; spec_truncated: boolean }[] }