mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(agents): an old Windows or unknown-platform host asks to update instead of answering its default list (#27164)
* fix(agents): an old Windows or unknown-platform host asks to update instead of answering its default list A host without preflight.workspace-scoped.v1 answers a workspace's agent request with its own default list. On a Windows host that list misses a WSL workspace's agents, and a host that does not report its platform may be Windows. Only a known non-Windows host's default is still used; the others record an empty list plus an update notice, shown in the quick-launch menu. Co-Authored-By: Claude <noreply@anthropic.com> * fix(agents): decide the old-host fallback from a fresh status Reading the platform from the shared capability cache could see a probe another caller had just started (no status yet) and flag a Linux host. A fresh status is race-free and also catches an in-place upgrade. Co-Authored-By: Claude <noreply@anthropic.com> * test(agents): name the served host status for its role Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -117,7 +117,7 @@ function QuickLaunchAgentMenuItemsInner({
|
||||
// instead of the remote server's. Use the same ssh/runtime/local owner
|
||||
// resolution as the rest of the tab bar.
|
||||
const agentDetectionTarget = useAgentDetectionTargetForWorktree(worktreeId)
|
||||
const { detectedIds } = useDetectedAgents(agentDetectionTarget)
|
||||
const { detectedIds, needsServerUpdate } = useDetectedAgents(agentDetectionTarget)
|
||||
const defaultAgent = useAppStore((s) => s.settings?.defaultTuiAgent)
|
||||
const disabledAgents = useAppStore(
|
||||
(s) => s.settings?.disabledTuiAgents ?? DEFAULT_DISABLED_TUI_AGENTS
|
||||
@@ -230,12 +230,20 @@ function QuickLaunchAgentMenuItemsInner({
|
||||
disabled
|
||||
className="gap-2 rounded-[7px] px-2 py-1.5 text-[12px] leading-5 text-muted-foreground"
|
||||
>
|
||||
{detectedIds && detectedIds.length > 0
|
||||
? translate('auto.components.tab.bar.QuickLaunchButton.8dea9b5cdf', 'No enabled agents')
|
||||
: translate(
|
||||
'auto.components.tab.bar.QuickLaunchButton.e518f544b1',
|
||||
'No agents detected'
|
||||
)}
|
||||
{needsServerUpdate
|
||||
? translate(
|
||||
'auto.components.tab.bar.QuickLaunchButton.needsServerUpdate',
|
||||
'Update Orca on this server to list this workspace’s agents'
|
||||
)
|
||||
: detectedIds && detectedIds.length > 0
|
||||
? translate(
|
||||
'auto.components.tab.bar.QuickLaunchButton.8dea9b5cdf',
|
||||
'No enabled agents'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.tab.bar.QuickLaunchButton.e518f544b1',
|
||||
'No agents detected'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
{agents.map((agent) => {
|
||||
|
||||
@@ -10,6 +10,8 @@ export type UseDetectedAgentsResult = {
|
||||
/** True when the first probe for this mounted remote target finished without a result. */
|
||||
detectionFailed: boolean
|
||||
isRefreshing: boolean
|
||||
/** True when the runtime host is too old to list this workspace's agents. */
|
||||
needsServerUpdate: boolean
|
||||
/** Forces a re-detect on the target host (`preflight.refreshAgents` for
|
||||
* local/runtime targets, a fresh probe for SSH) and updates every
|
||||
* subscribed surface in the same tick. Idempotent while in flight:
|
||||
@@ -124,6 +126,11 @@ export function useDetectedAgents(
|
||||
? (s.isRefreshingLocalAgentsByContext[localContextKey] ?? false)
|
||||
: s.isRefreshingAgents
|
||||
})
|
||||
const needsServerUpdate = useAppStore((s) =>
|
||||
targetKind === 'runtime' && recordKey
|
||||
? s.runtimeAgentDetectionNeedsServerUpdate[recordKey] === true
|
||||
: false
|
||||
)
|
||||
const detectionFailed =
|
||||
detectedIds === null &&
|
||||
!isLoading &&
|
||||
@@ -198,5 +205,5 @@ export function useDetectedAgents(
|
||||
localContextKey
|
||||
])
|
||||
|
||||
return { detectedIds, isLoading, detectionFailed, isRefreshing, refresh }
|
||||
return { detectedIds, isLoading, detectionFailed, isRefreshing, needsServerUpdate, refresh }
|
||||
}
|
||||
|
||||
@@ -3640,7 +3640,8 @@
|
||||
"ec2adf093e": "Launch {{value0}} in a new terminal",
|
||||
"465e432ef1": "Could not build launch command for {{value0}}.",
|
||||
"e518f544b1": "No agents detected",
|
||||
"8dea9b5cdf": "No enabled agents"
|
||||
"8dea9b5cdf": "No enabled agents",
|
||||
"needsServerUpdate": "Update Orca on this server to list this workspace’s agents"
|
||||
},
|
||||
"RecentTabSwitcher": {
|
||||
"329638ff6f": "Switch Tab",
|
||||
|
||||
@@ -3426,7 +3426,8 @@
|
||||
"ec2adf093e": "Iniciar {{value0}} en un nuevo terminal",
|
||||
"465e432ef1": "No se pudo generar el comando de inicio para {{value0}}.",
|
||||
"e518f544b1": "No se detectaron agentes",
|
||||
"8dea9b5cdf": "No hay agentes habilitados"
|
||||
"8dea9b5cdf": "No hay agentes habilitados",
|
||||
"needsServerUpdate": "Actualiza Orca en este servidor para ver los agentes de este espacio de trabajo"
|
||||
},
|
||||
"RecentTabSwitcher": {
|
||||
"329638ff6f": "Cambiar pestaña",
|
||||
|
||||
@@ -3531,7 +3531,8 @@
|
||||
"ec2adf093e": "Lancer {{value0}} dans un nouveau terminal",
|
||||
"465e432ef1": "Impossible de construire la commande de lancement pour {{value0}}.",
|
||||
"e518f544b1": "Aucun agent détecté",
|
||||
"8dea9b5cdf": "Aucun agent activé"
|
||||
"8dea9b5cdf": "Aucun agent activé",
|
||||
"needsServerUpdate": "Mettez à jour Orca sur ce serveur pour lister les agents de cet espace de travail"
|
||||
},
|
||||
"RecentTabSwitcher": {
|
||||
"329638ff6f": "Changer d'onglet",
|
||||
|
||||
@@ -3426,7 +3426,8 @@
|
||||
"ec2adf093e": "新規ターミナルで {{value0}} を起動",
|
||||
"465e432ef1": "{{value0}} の起動コマンドを作成できませんでした。",
|
||||
"e518f544b1": "Agent が検出されませんでした",
|
||||
"8dea9b5cdf": "有効な Agent がありません"
|
||||
"8dea9b5cdf": "有効な Agent がありません",
|
||||
"needsServerUpdate": "このワークスペースの Agent を表示するには、このサーバーの Orca を更新してください"
|
||||
},
|
||||
"RecentTabSwitcher": {
|
||||
"329638ff6f": "タブの切り替え",
|
||||
|
||||
@@ -3426,7 +3426,8 @@
|
||||
"ec2adf093e": "새 terminal에서 {{value0}} 실행",
|
||||
"465e432ef1": "{{value0}}에 대한 실행 명령을 작성할 수 없습니다.",
|
||||
"e518f544b1": "감지된 agents가 없습니다.",
|
||||
"8dea9b5cdf": "활성화된 agents가 없습니다."
|
||||
"8dea9b5cdf": "활성화된 agents가 없습니다.",
|
||||
"needsServerUpdate": "이 워크스페이스의 에이전트를 보려면 이 서버의 Orca를 업데이트하세요"
|
||||
},
|
||||
"RecentTabSwitcher": {
|
||||
"329638ff6f": "탭 전환",
|
||||
|
||||
@@ -3426,7 +3426,8 @@
|
||||
"ec2adf093e": "在新终端中启动 {{value0}}",
|
||||
"465e432ef1": "无法为 {{value0}} 构建启动命令。",
|
||||
"e518f544b1": "未检测到智能体",
|
||||
"8dea9b5cdf": "没有启用智能体"
|
||||
"8dea9b5cdf": "没有启用智能体",
|
||||
"needsServerUpdate": "请更新此服务器上的 Orca 以列出此工作区的智能体"
|
||||
},
|
||||
"RecentTabSwitcher": {
|
||||
"329638ff6f": "切换选项卡",
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createTestStore } from './store-test-helpers'
|
||||
import { getRuntimeAgentInventoryKey } from './runtime-agent-inventory-key'
|
||||
import {
|
||||
MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY,
|
||||
RUNTIME_PROTOCOL_VERSION
|
||||
} from '../../../../shared/protocol-version'
|
||||
import { clearRuntimeCompatibilityCacheForTests } from '@/runtime/runtime-rpc-client'
|
||||
|
||||
const runtimeEnvironmentCall = vi.fn()
|
||||
|
||||
vi.stubGlobal('window', { api: { runtimeEnvironments: { call: runtimeEnvironmentCall } } })
|
||||
|
||||
type ServedHostStatus = { hostPlatform?: NodeJS.Platform; scoped: boolean }
|
||||
|
||||
function serveHost(host: ServedHostStatus): void {
|
||||
runtimeEnvironmentCall.mockImplementation(({ method }: { method: string }) => {
|
||||
const result =
|
||||
method === 'status.get'
|
||||
? {
|
||||
runtimeId: 'remote-runtime',
|
||||
rendererGraphEpoch: 1,
|
||||
graphStatus: 'ready',
|
||||
authoritativeWindowId: null,
|
||||
liveTabCount: 0,
|
||||
liveLeafCount: 0,
|
||||
runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,
|
||||
minCompatibleRuntimeClientVersion: MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
capabilities: host.scoped ? [PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY] : [],
|
||||
...(host.hostPlatform ? { hostPlatform: host.hostPlatform } : {})
|
||||
}
|
||||
: method === 'preflight.refreshAgents'
|
||||
? { agents: ['codex'] }
|
||||
: ['codex']
|
||||
return Promise.resolve({ id: method, ok: true, result, _meta: { runtimeId: 'remote-runtime' } })
|
||||
})
|
||||
}
|
||||
|
||||
function agentCalls(): unknown[] {
|
||||
return runtimeEnvironmentCall.mock.calls
|
||||
.map(([request]) => request)
|
||||
.filter((request: { method: string }) => request.method !== 'status.get')
|
||||
}
|
||||
|
||||
const KEY = getRuntimeAgentInventoryKey('env-1', 'wt-1')
|
||||
|
||||
describe('workspace agent detection on a host without workspace-scoped detection', () => {
|
||||
beforeEach(() => {
|
||||
clearRuntimeCompatibilityCacheForTests()
|
||||
runtimeEnvironmentCall.mockReset()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['a Windows host', { hostPlatform: 'win32' as const, scoped: false }],
|
||||
['a host that does not report its platform', { scoped: false }]
|
||||
])('asks to update %s instead of answering its default list', async (_label, host) => {
|
||||
serveHost(host)
|
||||
const store = createTestStore()
|
||||
|
||||
await expect(store.getState().ensureRuntimeDetectedAgents('env-1', 'wt-1')).resolves.toEqual([])
|
||||
|
||||
expect(agentCalls()).toEqual([])
|
||||
expect(store.getState().runtimeDetectedAgentIds[KEY]).toEqual([])
|
||||
expect(store.getState().runtimeAgentDetectionNeedsServerUpdate[KEY]).toBe(true)
|
||||
expect(store.getState().isDetectingRuntimeAgents[KEY]).toBe(false)
|
||||
})
|
||||
|
||||
it('replaces a cached list with the update notice on refresh', async () => {
|
||||
serveHost({ hostPlatform: 'win32', scoped: false })
|
||||
const store = createTestStore()
|
||||
store.setState({ runtimeDetectedAgentIds: { [KEY]: ['claude'] } })
|
||||
|
||||
await expect(store.getState().refreshRuntimeDetectedAgents('env-1', 'wt-1')).resolves.toEqual(
|
||||
[]
|
||||
)
|
||||
|
||||
expect(agentCalls()).toEqual([])
|
||||
expect(store.getState().runtimeDetectedAgentIds[KEY]).toEqual([])
|
||||
expect(store.getState().runtimeAgentDetectionNeedsServerUpdate[KEY]).toBe(true)
|
||||
expect(store.getState().isRefreshingRuntimeAgents[KEY]).toBe(false)
|
||||
})
|
||||
|
||||
it('still answers with the default list of a known non-Windows host', async () => {
|
||||
serveHost({ hostPlatform: 'linux', scoped: false })
|
||||
const store = createTestStore()
|
||||
|
||||
await expect(store.getState().ensureRuntimeDetectedAgents('env-1', 'wt-1')).resolves.toEqual([
|
||||
'codex'
|
||||
])
|
||||
|
||||
expect(agentCalls()).toEqual([
|
||||
expect.objectContaining({ method: 'preflight.detectAgents', params: undefined })
|
||||
])
|
||||
expect(store.getState().runtimeAgentDetectionNeedsServerUpdate[KEY]).toBeUndefined()
|
||||
})
|
||||
|
||||
it('answers concurrent workspaces of a known non-Windows host with its default list', async () => {
|
||||
serveHost({ hostPlatform: 'linux', scoped: false })
|
||||
const store = createTestStore()
|
||||
|
||||
const lists = await Promise.all([
|
||||
store.getState().ensureRuntimeDetectedAgents('env-1', 'wt-1'),
|
||||
store.getState().ensureRuntimeDetectedAgents('env-1', 'wt-2')
|
||||
])
|
||||
|
||||
expect(lists).toEqual([['codex'], ['codex']])
|
||||
expect(store.getState().runtimeAgentDetectionNeedsServerUpdate).toEqual({})
|
||||
})
|
||||
|
||||
it('still answers the host default list of an old Windows host', async () => {
|
||||
serveHost({ hostPlatform: 'win32', scoped: false })
|
||||
const store = createTestStore()
|
||||
|
||||
await expect(store.getState().ensureRuntimeDetectedAgents('env-1')).resolves.toEqual(['codex'])
|
||||
|
||||
expect(store.getState().runtimeAgentDetectionNeedsServerUpdate).toEqual({})
|
||||
})
|
||||
|
||||
it('clears the notice once the host is updated', async () => {
|
||||
serveHost({ hostPlatform: 'win32', scoped: false })
|
||||
const store = createTestStore()
|
||||
await store.getState().ensureRuntimeDetectedAgents('env-1', 'wt-1')
|
||||
|
||||
serveHost({ hostPlatform: 'win32', scoped: true })
|
||||
await expect(store.getState().ensureRuntimeDetectedAgents('env-1', 'wt-1')).resolves.toEqual([
|
||||
'codex'
|
||||
])
|
||||
|
||||
expect(agentCalls()).toEqual([
|
||||
expect.objectContaining({ method: 'preflight.detectAgents', params: { worktreeId: 'wt-1' } })
|
||||
])
|
||||
expect(store.getState().runtimeDetectedAgentIds[KEY]).toEqual(['codex'])
|
||||
expect(store.getState().runtimeAgentDetectionNeedsServerUpdate[KEY]).toBeUndefined()
|
||||
})
|
||||
|
||||
it('drops the notice with its environment', async () => {
|
||||
serveHost({ hostPlatform: 'win32', scoped: false })
|
||||
const store = createTestStore()
|
||||
await store.getState().ensureRuntimeDetectedAgents('env-1', 'wt-1')
|
||||
|
||||
store.getState().clearRuntimeDetectedAgents('env-1')
|
||||
|
||||
expect(store.getState().runtimeAgentDetectionNeedsServerUpdate).toEqual({})
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import type { AppState } from '../types'
|
||||
import type { TuiAgent } from '../../../../shared/tui-agent'
|
||||
import {
|
||||
callRuntimeRpc,
|
||||
getRuntimeEnvironmentStatus,
|
||||
RuntimeRpcCallError,
|
||||
runtimeEnvironmentSupportsCapability
|
||||
} from '@/runtime/runtime-rpc-client'
|
||||
@@ -21,6 +22,8 @@ export type RuntimeDetectedAgentsSlice = {
|
||||
runtimeDetectedAgentIds: Record<string, TuiAgent[] | null>
|
||||
isDetectingRuntimeAgents: Record<string, boolean>
|
||||
isRefreshingRuntimeAgents: Record<string, boolean>
|
||||
/** Keys whose host is too old to list that workspace's agents; their list is empty. */
|
||||
runtimeAgentDetectionNeedsServerUpdate: Record<string, boolean>
|
||||
ensureRuntimeDetectedAgents: (
|
||||
environmentId: string,
|
||||
worktreeId?: string | null
|
||||
@@ -48,10 +51,17 @@ function isRuntimeMethodNotFoundError(error: unknown): boolean {
|
||||
return error instanceof RuntimeRpcCallError && error.code === 'method_not_found'
|
||||
}
|
||||
|
||||
export class RuntimeAgentDetectionNeedsServerUpdateError extends Error {
|
||||
constructor() {
|
||||
super("Update Orca on this server to list this workspace's agents.")
|
||||
this.name = 'RuntimeAgentDetectionNeedsServerUpdateError'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends `preflight.detectAgents` / `refreshAgents` scoped to a workspace. A host without the
|
||||
* workspace-scoped capability would discard the params and answer with its own default, so it is
|
||||
* asked for exactly that. The host default goes out synchronously, as it always has.
|
||||
* workspace-scoped capability can only answer its own default, which is the workspace's list only
|
||||
* on a known non-Windows host. The host default goes out synchronously, as it always has.
|
||||
*/
|
||||
export function callRuntimeAgentDetection<T>(
|
||||
environmentId: string,
|
||||
@@ -61,10 +71,22 @@ export function callRuntimeAgentDetection<T>(
|
||||
if (!worktreeId) {
|
||||
return call(undefined)
|
||||
}
|
||||
return runtimeEnvironmentSupportsCapability(
|
||||
environmentId,
|
||||
PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY
|
||||
).then((supported) => call(supported ? { worktreeId } : undefined))
|
||||
const capability = PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY
|
||||
return runtimeEnvironmentSupportsCapability(environmentId, capability).then(async (supported) => {
|
||||
if (supported) {
|
||||
return call({ worktreeId })
|
||||
}
|
||||
// Why: a fresh status both reports the platform and catches an in-place upgrade.
|
||||
const status = await getRuntimeEnvironmentStatus(environmentId)
|
||||
if (status.capabilities?.includes(capability)) {
|
||||
return call({ worktreeId })
|
||||
}
|
||||
// Why: an old Windows host's default omits a WSL workspace's agents; an absent platform may be one.
|
||||
if (!status.hostPlatform || status.hostPlatform === 'win32') {
|
||||
throw new RuntimeAgentDetectionNeedsServerUpdateError()
|
||||
}
|
||||
return call(undefined)
|
||||
})
|
||||
}
|
||||
|
||||
export function _getRuntimeDetectPromiseCountForTest(): number {
|
||||
@@ -104,18 +126,41 @@ export const createRuntimeDetectedAgentsSlice: StateCreator<
|
||||
const runtimeDetectedAgentIds = withoutKeys(s.runtimeDetectedAgentIds, dropKey)
|
||||
const isDetectingRuntimeAgents = withoutKeys(s.isDetectingRuntimeAgents, dropKey)
|
||||
const isRefreshingRuntimeAgents = withoutKeys(s.isRefreshingRuntimeAgents, dropKey)
|
||||
const runtimeAgentDetectionNeedsServerUpdate = withoutKeys(
|
||||
s.runtimeAgentDetectionNeedsServerUpdate,
|
||||
dropKey
|
||||
)
|
||||
return runtimeDetectedAgentIds === s.runtimeDetectedAgentIds &&
|
||||
isDetectingRuntimeAgents === s.isDetectingRuntimeAgents &&
|
||||
isRefreshingRuntimeAgents === s.isRefreshingRuntimeAgents
|
||||
isRefreshingRuntimeAgents === s.isRefreshingRuntimeAgents &&
|
||||
runtimeAgentDetectionNeedsServerUpdate === s.runtimeAgentDetectionNeedsServerUpdate
|
||||
? s
|
||||
: { runtimeDetectedAgentIds, isDetectingRuntimeAgents, isRefreshingRuntimeAgents }
|
||||
: {
|
||||
runtimeDetectedAgentIds,
|
||||
isDetectingRuntimeAgents,
|
||||
isRefreshingRuntimeAgents,
|
||||
runtimeAgentDetectionNeedsServerUpdate
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Commits a settled detection; an old host commits an empty list, never its default.
|
||||
const commitDetection = (key: string, agents: TuiAgent[], needsServerUpdate: boolean): void => {
|
||||
set((s) => ({
|
||||
runtimeDetectedAgentIds: { ...s.runtimeDetectedAgentIds, [key]: agents },
|
||||
isDetectingRuntimeAgents: { ...s.isDetectingRuntimeAgents, [key]: false },
|
||||
isRefreshingRuntimeAgents: { ...s.isRefreshingRuntimeAgents, [key]: false },
|
||||
runtimeAgentDetectionNeedsServerUpdate: needsServerUpdate
|
||||
? { ...s.runtimeAgentDetectionNeedsServerUpdate, [key]: true }
|
||||
: withoutKeys(s.runtimeAgentDetectionNeedsServerUpdate, (k) => k === key)
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
runtimeDetectedAgentIds: {},
|
||||
isDetectingRuntimeAgents: {},
|
||||
isRefreshingRuntimeAgents: {},
|
||||
runtimeAgentDetectionNeedsServerUpdate: {},
|
||||
|
||||
ensureRuntimeDetectedAgents: (environmentId: string, worktreeId?: string | null) => {
|
||||
const key = getRuntimeAgentInventoryKey(environmentId, worktreeId)
|
||||
@@ -148,18 +193,20 @@ export const createRuntimeDetectedAgentsSlice: StateCreator<
|
||||
// while the detect was in flight — otherwise it re-adds a stale entry
|
||||
// that retainRuntimeDetectedAgents just pruned.
|
||||
if (runtimeDetectPromises.get(key) === pending) {
|
||||
set((s) => ({
|
||||
runtimeDetectedAgentIds: { ...s.runtimeDetectedAgentIds, [key]: typed },
|
||||
isDetectingRuntimeAgents: { ...s.isDetectingRuntimeAgents, [key]: false }
|
||||
}))
|
||||
commitDetection(key, typed, false)
|
||||
}
|
||||
return typed
|
||||
})
|
||||
.catch((): TuiAgent[] => {
|
||||
.catch((error: unknown): TuiAgent[] => {
|
||||
// Why: a remote runtime may be disconnected or version-incompatible.
|
||||
// Keep the menu retryable instead of pinning a failed probe forever.
|
||||
// Same in-flight guard as the .then() above.
|
||||
if (runtimeDetectPromises.get(key) === pending) {
|
||||
if (runtimeDetectPromises.get(key) !== pending) {
|
||||
return []
|
||||
}
|
||||
if (error instanceof RuntimeAgentDetectionNeedsServerUpdateError) {
|
||||
commitDetection(key, [], true)
|
||||
} else {
|
||||
set((s) => ({
|
||||
isDetectingRuntimeAgents: { ...s.isDetectingRuntimeAgents, [key]: false }
|
||||
}))
|
||||
@@ -207,15 +254,18 @@ export const createRuntimeDetectedAgentsSlice: StateCreator<
|
||||
// Why: same guard as ensureRuntimeDetectedAgents — if the environment
|
||||
// was retained out mid-refresh, don't re-add a pruned entry.
|
||||
if (runtimeRefreshPromises.get(key) === pending) {
|
||||
set((s) => ({
|
||||
runtimeDetectedAgentIds: { ...s.runtimeDetectedAgentIds, [key]: typed },
|
||||
isDetectingRuntimeAgents: { ...s.isDetectingRuntimeAgents, [key]: false },
|
||||
isRefreshingRuntimeAgents: { ...s.isRefreshingRuntimeAgents, [key]: false }
|
||||
}))
|
||||
commitDetection(key, typed, false)
|
||||
}
|
||||
return typed
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((error: unknown) => {
|
||||
if (
|
||||
error instanceof RuntimeAgentDetectionNeedsServerUpdateError &&
|
||||
runtimeRefreshPromises.get(key) === pending
|
||||
) {
|
||||
commitDetection(key, [], true)
|
||||
return []
|
||||
}
|
||||
// Why: a disconnected runtime must keep Refresh retryable and must not
|
||||
// wipe the last known agent list.
|
||||
if (runtimeRefreshPromises.get(key) === pending) {
|
||||
|
||||
Reference in New Issue
Block a user