fix(queue): keep remote host identity stable
This commit is contained in:
@@ -64,7 +64,7 @@ These stores coordinate persistent project/session metadata across multiple view
|
||||
|
||||
`useProjectContextStore.ts` caches server-owned project notes, todos, and plan links, keyed by the path-derived project id. It replaced a pair of `window` CustomEvents that made every mounted notes panel re-read the whole project config. Writes are optimistic and roll back on failure; they are serialized per project, because the server's own store does a read-modify-write and two concurrent saves would otherwise race it. A load that resolves while a write is in flight keeps the local value for that field group only, so a slow snapshot cannot undo newer typing while still delivering the plan list it fetched. A failed load sets `error` and preserves the cached snapshot — an unreachable server must never render as "this project has no notes". Note and plan creation are deliberately not optimistic, since ids and timestamps are assigned by the server. Notes, todos, and plans are written through separate routes and tracked by separate in-flight flags, so a todo toggle cannot clobber a note edit in the same window. Pinned notes and plans are assembled into a synthetic context part by `lib/projectContextPinning.ts` at send time; that module tracks per-session what it already sent so an unchanged pinned set is not re-sent every turn.
|
||||
|
||||
`messageQueueStore.ts` keeps a queued message until its own send resolves, so between dispatch and resolution the entry is still visible to every reader. Dispatchers must therefore mark the send (`markSending`/`clearSending`) and read `getSendableQueue()` — or filter `sendingIds` themselves — instead of dispatching straight from `queuedMessages`; otherwise a composer submit merges a message the auto-send hook is already delivering and it is sent twice (the window is seconds over a relay). `clearQueue()` retains in-flight entries for the same reason. `sendingIds` is deliberately not persisted: a restart has no in-flight sends, and a stale flag would strand a queued message.
|
||||
`messageQueueStore.ts` keeps a queued message until its own send resolves, so between dispatch and resolution the entry is still visible to every reader. Dispatchers must therefore mark the send (`markSending`/`clearSending`) and read `getSendableQueue()` — or filter `sendingIds` themselves — instead of dispatching straight from `queuedMessages`; otherwise a composer submit merges a message the auto-send hook is already delivering and it is sent twice (the window is seconds over a relay). `clearQueue()` retains in-flight entries for the same reason. `sendingIds` is deliberately not persisted: a restart has no in-flight sends, and a stale flag would strand a queued message. Desktop queues use the configured host id as runtime identity, not the current API URL, because an SSH reconnect allocates a new local forwarding port while the remote host remains the same.
|
||||
|
||||
`useGlobalSessionsStore.ts` owns cold/global active and archived session coverage, including `sessionsByDirectory`. It is complementary to directory child stores: it is not the source of live busy/retry status or session messages.
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('createConfiguredWebAPIs', () => {
|
||||
|
||||
expect(initializeRuntimeEndpoint).toHaveBeenCalledWith({
|
||||
apiBaseUrl: bootstrap.apiBaseUrl,
|
||||
runtimeKey: null,
|
||||
runtimeKey: 'host:host-1',
|
||||
});
|
||||
expect(setRuntimeBearerToken).toHaveBeenCalledWith(bootstrap.clientToken);
|
||||
expect(setRuntimeExtraHeaders).toHaveBeenCalledWith(bootstrap.runtimeHeaders);
|
||||
@@ -116,6 +116,27 @@ describe('createConfiguredWebAPIs', () => {
|
||||
expect(opencodeClient.reconnectToRuntimeBaseUrl).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('uses the configured desktop host id across changing SSH tunnel URLs', () => {
|
||||
const current = makeWindow();
|
||||
current.__OPENCHAMBER_DESKTOP_BOOT_OUTCOME__ = {
|
||||
target: 'remote',
|
||||
status: 'ok',
|
||||
hostId: 'ssh-castle',
|
||||
url: 'http://127.0.0.1:62545',
|
||||
localAvailable: true,
|
||||
};
|
||||
current.__OPENCHAMBER_API_BASE_URL__ = 'http://127.0.0.1:62545';
|
||||
current.__OPENCHAMBER_LOCAL_ORIGIN__ = 'http://127.0.0.1:3901';
|
||||
installWindow(current);
|
||||
|
||||
createConfiguredWebAPIs();
|
||||
|
||||
expect(initializeRuntimeEndpoint).toHaveBeenCalledWith({
|
||||
apiBaseUrl: 'http://127.0.0.1:62545',
|
||||
runtimeKey: 'host:ssh-castle',
|
||||
});
|
||||
});
|
||||
|
||||
test('activates an embedded relay without relying on Electron preload IPC', () => {
|
||||
const relay = {
|
||||
relayUrl: 'wss://relay.example.com',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getRuntimeExtraHeadersSync, refreshLocalRuntimeUrlAuthToken, refreshRun
|
||||
import { installRuntimeFetchBridge } from '@openchamber/ui/lib/runtime-fetch';
|
||||
import { initializeRuntimeEndpoint, switchRuntimeEndpoint } from '@openchamber/ui/lib/runtime-switch';
|
||||
import { restoreDesktopRelayRuntime } from '@openchamber/ui/lib/desktopRelayRestore';
|
||||
import { getInjectedBootOutcome } from '@openchamber/ui/lib/desktopBoot';
|
||||
import { configureRuntimeUrlResolver } from '@openchamber/ui/lib/runtime-url';
|
||||
import type { EmbeddedSessionRuntimeBootstrap } from '@openchamber/ui/components/layout/contextPanelEmbeddedChat';
|
||||
import { opencodeClient } from '@openchamber/ui/lib/opencode/client';
|
||||
@@ -45,6 +46,8 @@ export const getDesktopRelayRestoreReady = (): Promise<void> => desktopRelayRest
|
||||
|
||||
export const createConfiguredWebAPIs = (bootstrap?: EmbeddedSessionRuntimeBootstrap | null) => {
|
||||
const { apiBaseUrl, clientToken, localOrigin, runtimeHeaders, relayHostId, relay } = bootstrap ?? readRuntimeBootstrapConfig();
|
||||
const bootOutcome = bootstrap ? null : getInjectedBootOutcome();
|
||||
const desktopHostId = relayHostId || (bootOutcome?.target === 'remote' ? bootOutcome.hostId : '');
|
||||
|
||||
const urls = configureRuntimeUrlResolver({
|
||||
apiBaseUrl: apiBaseUrl || undefined,
|
||||
@@ -52,7 +55,7 @@ export const createConfiguredWebAPIs = (bootstrap?: EmbeddedSessionRuntimeBootst
|
||||
});
|
||||
initializeRuntimeEndpoint({
|
||||
apiBaseUrl,
|
||||
runtimeKey: sameOrigin(apiBaseUrl, localOrigin) ? 'local' : null,
|
||||
runtimeKey: sameOrigin(apiBaseUrl, localOrigin) ? 'local' : (desktopHostId ? `host:${desktopHostId}` : null),
|
||||
});
|
||||
setRuntimeBearerToken(clientToken || null);
|
||||
setRuntimeExtraHeaders(runtimeHeaders || null);
|
||||
|
||||
Reference in New Issue
Block a user