@agimon-ai/po-web-dashboard 0.0.0-alpha.63 → 0.0.0-alpha.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -175,6 +175,7 @@ exports.builtinCommandFrame = require_commands.builtinCommandFrame;
175
175
  exports.canSaveSettings = require_settingsDraft.canSaveSettings;
176
176
  exports.cancelDialog = require_sessionStore.cancelDialog;
177
177
  exports.cancelLogin = require_authApi.cancelLogin;
178
+ exports.choosePairTarget = require_remoteAccessStore.choosePairTarget;
178
179
  exports.claimDialogMenu = require_menuStore.claimDialogMenu;
179
180
  exports.clearComposerState = require_composerStore.clearComposerState;
180
181
  exports.clearDialog = require_sessionModel.clearDialog;
package/dist/index.d.mts CHANGED
@@ -69,7 +69,7 @@ import { DevProxyState, createDevProxyTarget, deleteDevProxyTarget, devProxyStor
69
69
  import { EMPTY_NEW_SESSION_DRAFT, NewSessionDraft, NewSessionState, closeNewSession, createPlainSession, newSessionDraftStore, newSessionStore, openNewSession, resetNewSessionStore, setNewSessionName } from "./web/stores/newSessionStore.mjs";
70
70
  import { PaletteState, closePalette, openPalette, paletteStore, setPalettePath, togglePalette } from "./web/stores/paletteStore.mjs";
71
71
  import { RailDialogState, ResumeDraft, ResumeTarget, ResumeView, closeResumeDialog, openResumeDialog, railDialogStore, railRestartStore, resetRailDialogStore, restartDashboardSession, restoreRemovedThread, resumeDashboardSession, resumeDraftStore, setResumeView, updateResumeDraft } from "./web/stores/railDialogStore.mjs";
72
- import { PasskeySupportView, RemoteAccessState, RemoteStep, addPasskey, approveDevice, closeRemoteDialog, denyDevice, newPairingCode, openRemoteDialog, refreshPasskeys, refreshRemoteState, remoteAccessStore, revokePairedDevice, revokeTrustedNativeDevice, showRemoteOptions, turnRemoteAccessOff, turnRemoteAccessOn, updateRemoteSettings } from "./web/stores/remoteAccessStore.mjs";
72
+ import { PairTarget, PasskeySupportView, RemoteAccessState, RemoteStep, addPasskey, approveDevice, choosePairTarget, closeRemoteDialog, denyDevice, newPairingCode, openRemoteDialog, refreshPasskeys, refreshRemoteState, remoteAccessStore, revokePairedDevice, revokeTrustedNativeDevice, showRemoteOptions, turnRemoteAccessOff, turnRemoteAccessOn, updateRemoteSettings } from "./web/stores/remoteAccessStore.mjs";
73
73
  import { TemplateConfiguration, configuredTemplate, ensureTemplateConfiguration, refreshTemplateConfiguration, saveTemplateDefault, templateStore, useTemplateConfiguration } from "./web/stores/templateStore.mjs";
74
74
  import { ThemeState, applyStoredTheme, availableThemes, selectTheme, themeStore } from "./web/stores/themeStore.mjs";
75
75
  import { ToolPromptState, markToolPromptFailed, resetToolPromptStore, toolPromptStore } from "./web/stores/toolPromptStore.mjs";
@@ -82,4 +82,4 @@ import { WorkspaceAdmissionDraft, admitDashboardWorkspace, admitDeclaredDashboar
82
82
  import { EMPTY_SETTINGS_DRAFT, SettingsDraftState, SettingsDraftTarget, discardSettingsDraft, loadSettingsDraft, saveSettingsDraft, setSettingsDraft, settingsDraftKey, settingsDraftStore } from "./web/stores/settingsDraftStore.mjs";
83
83
  import { dashboardStateFor, useDashboardState } from "./web/stores/dashboardStateStore.mjs";
84
84
  import { DashboardRendererProvider, DashboardRenderers } from "./web/lib/dashboardRenderers.mjs";
85
- export { ActivityGroup, AgentInfo, AssistantEntry, AttachPhase, BUILTIN_COMMANDS, BrowserNotificationDeliveryStatus, BrowserNotificationPermissionStatus, BrowserPerformanceEvent, BrowserPerformanceEventName, CommandInfo, CompletionItem, CompletionKind, CompletionTrigger, ComposerAttachment, ComposerContextAttachment, ComposerFileAttachment, ComposerImageAttachment, ComposerPromptClaim, ComposerSessionState, ComposerTextAttachment, ContextGroup, ContextGroupKind, ContextItem, ContextItemSource, ContextOwner, CreateSessionResult, DEFAULT_REPOSITORY_SETTINGS_SECTION, DEFAULT_SETTINGS_SECTION, DashboardRendererProvider, type DashboardRenderers, DashboardRoute, DevProxyState, DialogDraft, DialogMethod, DialogPromptClaim, DialogRequest, DockTab, EMPTY_NEW_SESSION_DRAFT, EMPTY_SETTINGS_DRAFT, EditorTextRequest, EntryKind, FetchItemDetailResult, FileLinkResolver, FileMention, GroupUnit, HOST_SLOTS, HistoryPage, ImageSettingsResult, InputSender, InstallDiagnostic, InstallDiagnosticKind, InstalledContextAction, InstalledHomeContribution, InstalledNativeTemplate, InstalledRepositorySettingsPanel, InstalledSettingsPanel, InstalledUserMessageAction, InstalledWebTemplate, IntercomEntry, LEGACY_WORKSPACE_ID, LeaderConflict, LeaderGroup, LeaderOption, LivePushStatus, LoginFlowResult, LogoutResult, MAX_COMPLETION_ITEMS, MAX_COMPOSER_ATTACHMENTS, MAX_COMPOSER_IMAGE_BYTES, MAX_COMPOSER_TEXT_BYTES, MAX_COMPOSER_TOTAL_IMAGE_BYTES, MAX_COMPOSER_TOTAL_TEXT_BYTES, MAX_COMPOSER_UPLOAD_BYTES, MenuClaim, MenuKind, MenuState, MinorMode, MinorModeAvailability, MinorModeSource, ModelChoice, NativeTemplateCatalog, NewSessionDraft, NewSessionState, NoticeEntry, PACKAGED_MINOR_MODES, PACKAGED_SELECTION_AXES, PairingResult, PaletteState, PasskeyOutcome, PasskeySupportView, PlannedWritesInput, ProfileIdentity, ProvidersResult, QueuedEntry, RailDialogState, RailRestartState, RailSessionInput, ReadConfigResult, ReadRepositorySettingsResult, ReduceSessionOptions, RemoteAccessState, RemoteStep, RemoveWorkspaceResult, RemovedSessionsResult, ResolvedFill, RestartSessionResult, RestoreRemovedSessionResult, ResumeDraft, ResumeSessionResult, ResumeTarget, ResumeView, ReviveSessionResult, RpcImage, STEP_UP_HEADER_NAME, Selection, SelectionAxis, SessionAsset, SessionGroupsResult, SessionHistoryResult, SessionMcpResult, SessionMcpViewState, SessionMeta, SessionRailModelInput, SessionSocket, SessionSocketHandlers, SessionState, SessionStats, SessionsState, SettingsDraftState, SettingsDraftTarget, SettingsSection, SettingsWorkspace, SettledEntry, SingleUnit, StateResult, StatusClock, StatusLineInput, StopSessionResult, StoredSessionAttachment, TRIGGER_CHARS, TargetResult, TargetsResult, TemplateConfiguration, ThemeState, ThreadTranscriptReader, TimelineEntry, TimelineUnit, ToolEntry, ToolPromptClaim, ToolPromptInput, ToolPromptState, TransientTabActions, UPDATE_REQUIRED_REASON, UiState, UiTraceRecord, UiTraceSink, UserEntry, UserImage, WebPluginCompositionPhase, WebPluginCompositionState, WebPluginMountState, WebPluginRegistry, WebTemplateCatalog, WebTemplateDecision, WebTemplateDiagnostic, WorkspaceAdmissionDraft, WorkspaceResult, WorkspaceSandboxRequest, WorkspacesResult, WorkspacesState, WriteConfigResult, abbreviateCwd, abortCommand, abortRun, acquireModelContext, activateWebPluginHost, activateWebPluginSession, activateWebPluginWorkspace, activeSessionId, activityGroupSlot, activityGroups, addDevProxyTarget, addPasskey, admitDashboardWorkspace, admitDeclaredDashboardWorkspaces, admitWorkspace, ansiSegments, answerDialogConfirm, answerDialogValue, answerLogin, appendComposerDraft, appendComposerQuote, appendQueued, appendUserPrompt, applyCaptureFrame, applyHistoryPage, applyProtocolQueue, applyProtocolTranscript, applySelectionAnswer, applySessionBacklog, applySessionFrame, applySessionLifecycle, applySessionRemoved, applySessionUpsert, applySessionsSnapshot, applyStoredTheme, applyThreadFrame, applyThreadTranscriptFrame, applyWorkspaceRemoved, applyWorkspaceUpsert, applyWorkspacesSnapshot, approveDevice, approvePairing, asWorkspace, assertionFor, attachComposerCapture, attachComposerContext, attachmentPrompt, availableThemes, beginSessionReplay, beginSessionTransfer, bindBundledNativeTemplates, bindBundledWebTemplates, bindDashboardTranscriptWatcher, bindFileLinkModes, bindHistoryReader, bindHostTransport, bindSessionActivityRenderer, bindSessionFileLinkModes, bindSessionProtocol, bindSessionTranscriptRenderer, bindSessionWebWorkspace, bindThreadReader, bindThreadRenderer, bindTransport, bindWebPluginCompositions, browserNotificationPermission, browserReadyDuration, buildRailWorkspaces, builtinCommandFrame, canSaveSettings, cancelDialog, cancelLogin, claimDialogMenu, clearComposerState, clearDialog, clearPendingMenu, clearQueueCommand, clearQueuedEntries, clearQueuedMessages, closeNewSession, closePalette, closeRemoteDialog, closeResumeDialog, closeTransientTab, collectNativeTemplates, collectWebTemplates, commandCompletions, compactCommand, completeSessionTransfer, composerStore, composerSubmissionStore, configuredTemplate, contextGroups, createDashboardTransport, createDevProxyTarget, createPagedTranscript, createPlainSession, createProtocolHubSocket, createProtocolLink, createSession, createSessionMcpClient, createToolCompletionTabs, createWebPluginRegistry, createWorkspaceSession, dashboardRouteFrom, dashboardStateFor, deleteDevProxyTarget, deleteQueuedMessage, deleteSessionGroup, deliverBrowserNotification, denyDevice, denyPairing, devProxyStore, dialogCancelled, dialogConfirmed, dialogDraftStore, dialogValue, disableLivePush, disableRemoteAccess, discardSettingsDraft, disconnectCaptures, discoverWorkspaceAdmission, discoverWorkspaces, dispatchChannelFrame, dispatchHeldSessionChannel, disposeModelContextAdapter, dockTabFor, dropComposerState, dropPluginSessionData, dropSessionStore, dropThreads, dropTransientTabs, emptySelection, enableLivePush, enableRemoteAccess, endSessionReplay, enqueueAutomaticCommand, ensureTemplateConfiguration, fetchContextItemDetail, fetchDevProxyTargets, fetchRemoteState, fetchWithStepUp, fileCompletions, fileLinkFor, fileLinkMentions, fileTabForPath, findTransientTab, flushBrowserTelemetry, flushUiTrace, focusPrompt, focusWorkspaceWebPlugins, followUpCommand, getAvailableModelsCommand, getAvailableThinkingLevelsCommand, getCommandsCommand, getSessionStatsCommand, getStateCommand, getWorkspace, globalSlotFills, groupSummary, groupTone, hasSessionProtocol, heldSessionChannels, heldThreads, heldVoiceSessions, holdSessionChannels, holdVoiceSession, imagesFromContent, inactiveTotal, initialSessionState, inlineTextBytes, installBrowserErrorReporting, installGlobalWebPlugins, installSessionWebPlugins, installWebPlugins, installWorkspaceWebPlugins, installedWebPlugins, invokeServerMethod, isDesktopClient, isSessionMcpActivity, isSupportedImageMimeType, leaderConflicts, leaderGroup, listDirectory, listPoFolders, listProviders, listRemovedSessions, listSessionHistory, listSessionMcpClients, listSessions, listSettingsModels, listSettingsRepositories, listWorkspaceHistory, listWorkspaces, livePushStatus, loadModelChoices, loadSessionAsset, loadSessionAvatar, loadSettingsDraft, localHostId, logoutProvider, markSocketClosed, markToolPromptFailed, markUpdateRequired, mediaKindFor, menuStore, mergeScopedPlugins, minorModes, mintPairingCode, nativeTemplateCatalog, newPairingCode, newSessionDraftStore, newSessionStore, noSessions, noteRender, noteStoreUpdate, notifyHubConnected, onCaptureStatus, onComposerSubmitted, onHubConnected, onSealedSignInRequired, openNewSession, openPalette, openRemoteDialog, openResumeDialog, openTransientTab, ownerLabel, ownersOf, paletteCommands, paletteStore, parseFileMentions, parseHostKey, parseSelection, passkeysAvailable, pendingCaptureSessions, pendingMenuFor, pendingSetupView, personaInitials, plannedSettingsWrites, pluginActivityGroups, pluginContextActions, pluginDockFaces, pluginFileLinks, pluginHomeContributions, pluginLeaderBindings, pluginMinorModes, pluginRepositorySettingsPanels, pluginSelectionAxes, pluginSessionContext, pluginSettingsPanels, pluginSettingsSections, pluginSlotProps, pluginToolRenderer, pluginUserMessageActions, pluginsAtScope, prependHistory, projectedGroups, promoteQueuedCommand, promoteQueuedMessage, promptCommand, protocolSocketUrl, publishComposerSubmission, qrMatrix, qualifyHostId, qualifyHostKey, queueFollowUp, queueFollowUpWithAck, railDialogStore, railRestartStore, railSessionStatus, readDormantTranscriptPage, readImageSettings, readLogin, readRepositorySettings, readSessionMcpActivity, readSessionMcpConfig, readSettingsConfig, recordBrowserError, recordBrowserPerformance, recordBrowserUiTrace, reduceSession, refreshDevProxyTargets, refreshPasskeys, refreshRemoteState, refreshSessionFacts, refreshSessionSelection, refreshSessionStats, refreshTemplateConfiguration, refreshWebPluginCompositions, registerPasskey, registerPromptInput, releaseDialogMenu, releaseProtocolTranscript, releaseSessionActivityRenderer, releaseSessionTranscriptRenderer, releaseThreadRenderer, releaseTransport, reloadWebTemplateDecision, rememberHostChannelKey, rememberedHostChannelKey, remoteAccessStore, removeDevProxyTarget, removeHostWebPlugins, removeQueuedCommand, removeQueuedEntry, removeSessionMcpSetup, removeSessionWebPlugins, removeWorkspace, removeWorkspaceWebPlugins, renameSession, renameSessionGroup, renderSessionActivity, renderSessionTranscript, renderThread, replaceQueuedEntries, requestBrowserNotificationPermission, requestLatestHistory, requestNewerHistory, requestOlderHistory, requestSessionProtocolFrame, resetComposerStore, resetDevProxyStore, resetMenuStore, resetNewSessionStore, resetRailDialogStore, resetSealedSessions, resetSessionStore, resetSessionStores, resetSessions, resetThreads, resetToolPromptStore, resetTransientTabs, resetWebPlugins, resetWorkspaceAdmission, resetWorkspaces, resolveNativeTemplate, resolveParentId, resolveWebTemplate, restartDashboardSession, restartSession, restoreComposerDrafts, restoreLivePushRegistration, restoreRemovedSession, restoreRemovedThread, restoreSealedSession, resubscribeThreads, resumeDashboardSession, resumeDraftStore, resumeQueueCommand, resumeQueuedMessages, resumeSession, resumeWorkspaceSession, retryWebPluginCompositions, reuseRailWorkspaces, reviveSession, revokeDevice, revokePairedDevice, revokeSessionMcpClient, revokeTrustedDevice, revokeTrustedNativeDevice, rewindCommand, rewindToMessage, runCommand, runningCount, sameRailEntries, sameRailEntry, saveComposerDrafts, saveRemoteSettings, saveSettingsDraft, saveTemplateDefault, sealedHttpSession, sealedProtocolSession, sealedSession, searchDirectories, searchSessionFiles, seedHistoryCursor, selectFastMode, selectModel, selectTheme, selectThinkingLevel, selectWorkspace, selectionAxes, selectionStatuses, sendFrame, sendFrameWithAck, sendHubFrame, sendSessionProtocolFrame, sessionMinorModes, sessionStatusClock, sessionStatusLine, sessionStoreFor, sessionToolRenderer, sessionWebPluginsInstalled, sessionsStore, setActiveSession, setAgentLock, setDockOpen, setDockTab, setHasNewerHistory, setModelCommand, setNewSessionName, setPalettePath, setPendingMenu, setResumeView, setSessionGroups, setSessionNameCommand, setSessionStreamed, setSettingsDraft, setThinkingLevelCommand, setTransientTabSession, setUiTraceSink, setWebPluginLoadDiagnostics, setWorkspaceInstructions, setWorkspaceSandbox, settingsDraftKey, settingsDraftStore, settingsKeyOf, settingsLockedReason, settingsSection, settingsSections, showRemoteOptions, signInWithPasskey, slotFills, splitHostId, startLogin, startPluginDefinitions, startWebPlugins, steerCommand, stopSession, streamedSessions, stripAnsi, submitCapture, submitComposerDraft, submitMessage, submitMessageWithAck, subscribeActivitySources, subscribeThread, subscribeWebPluginRegistry, summariseArgs, summaryValueEqual, tabComposerContext, templateStore, textFromContent, themeStore, threadStoreKey, timelineUnits, toQueuedEntries, toTimelineEntries, togglePalette, toolMessageProps, toolPromptClaim, toolPromptStore, totalTokens, traceProfilerRender, traceStore, transientTabsOf, transientTabsStore, transportForHost, triggerTokenAt, turnRemoteAccessOff, turnRemoteAccessOn, uiStore, unsubscribeThread, updateComposerState, updateDialogDraft, updateRemoteSettings, updateResumeDraft, updateWorkspaceAdmission, uploadSessionAttachment, useActiveSession, useActiveSessionMeta, useActivityGroups, useComposerState, useDashboardRoute, useDashboardState, useDialogDraft, useDockFaceBadges, useDockFaces, useFileLinks, useHasOlderHistory, useNoSessions, useOpenTab, usePluginSlotProps, useRailEntries, useRailEntry, useRenderTrace, useSessionMcp, useTemplateConfiguration, useToolPrompt, useTransientTabs, useWebPluginRegistry, validateComposerCapture, waitForSession, watchDashboardTranscript, watchHubThread, webPluginCompositionStore, webPluginDiagnostics, webPluginMountState, webPluginRegistryForHost, webPluginRegistryRevision, webPluginsInstalled, webTabs, webTemplateCatalog, workspaceAdmissionStore, workspaceName, workspaceSlotFills, workspacesStore, writeImageSettings, writeRepositorySelection, writeSettingsValue };
85
+ export { ActivityGroup, AgentInfo, AssistantEntry, AttachPhase, BUILTIN_COMMANDS, BrowserNotificationDeliveryStatus, BrowserNotificationPermissionStatus, BrowserPerformanceEvent, BrowserPerformanceEventName, CommandInfo, CompletionItem, CompletionKind, CompletionTrigger, ComposerAttachment, ComposerContextAttachment, ComposerFileAttachment, ComposerImageAttachment, ComposerPromptClaim, ComposerSessionState, ComposerTextAttachment, ContextGroup, ContextGroupKind, ContextItem, ContextItemSource, ContextOwner, CreateSessionResult, DEFAULT_REPOSITORY_SETTINGS_SECTION, DEFAULT_SETTINGS_SECTION, DashboardRendererProvider, type DashboardRenderers, DashboardRoute, DevProxyState, DialogDraft, DialogMethod, DialogPromptClaim, DialogRequest, DockTab, EMPTY_NEW_SESSION_DRAFT, EMPTY_SETTINGS_DRAFT, EditorTextRequest, EntryKind, FetchItemDetailResult, FileLinkResolver, FileMention, GroupUnit, HOST_SLOTS, HistoryPage, ImageSettingsResult, InputSender, InstallDiagnostic, InstallDiagnosticKind, InstalledContextAction, InstalledHomeContribution, InstalledNativeTemplate, InstalledRepositorySettingsPanel, InstalledSettingsPanel, InstalledUserMessageAction, InstalledWebTemplate, IntercomEntry, LEGACY_WORKSPACE_ID, LeaderConflict, LeaderGroup, LeaderOption, LivePushStatus, LoginFlowResult, LogoutResult, MAX_COMPLETION_ITEMS, MAX_COMPOSER_ATTACHMENTS, MAX_COMPOSER_IMAGE_BYTES, MAX_COMPOSER_TEXT_BYTES, MAX_COMPOSER_TOTAL_IMAGE_BYTES, MAX_COMPOSER_TOTAL_TEXT_BYTES, MAX_COMPOSER_UPLOAD_BYTES, MenuClaim, MenuKind, MenuState, MinorMode, MinorModeAvailability, MinorModeSource, ModelChoice, NativeTemplateCatalog, NewSessionDraft, NewSessionState, NoticeEntry, PACKAGED_MINOR_MODES, PACKAGED_SELECTION_AXES, PairTarget, PairingResult, PaletteState, PasskeyOutcome, PasskeySupportView, PlannedWritesInput, ProfileIdentity, ProvidersResult, QueuedEntry, RailDialogState, RailRestartState, RailSessionInput, ReadConfigResult, ReadRepositorySettingsResult, ReduceSessionOptions, RemoteAccessState, RemoteStep, RemoveWorkspaceResult, RemovedSessionsResult, ResolvedFill, RestartSessionResult, RestoreRemovedSessionResult, ResumeDraft, ResumeSessionResult, ResumeTarget, ResumeView, ReviveSessionResult, RpcImage, STEP_UP_HEADER_NAME, Selection, SelectionAxis, SessionAsset, SessionGroupsResult, SessionHistoryResult, SessionMcpResult, SessionMcpViewState, SessionMeta, SessionRailModelInput, SessionSocket, SessionSocketHandlers, SessionState, SessionStats, SessionsState, SettingsDraftState, SettingsDraftTarget, SettingsSection, SettingsWorkspace, SettledEntry, SingleUnit, StateResult, StatusClock, StatusLineInput, StopSessionResult, StoredSessionAttachment, TRIGGER_CHARS, TargetResult, TargetsResult, TemplateConfiguration, ThemeState, ThreadTranscriptReader, TimelineEntry, TimelineUnit, ToolEntry, ToolPromptClaim, ToolPromptInput, ToolPromptState, TransientTabActions, UPDATE_REQUIRED_REASON, UiState, UiTraceRecord, UiTraceSink, UserEntry, UserImage, WebPluginCompositionPhase, WebPluginCompositionState, WebPluginMountState, WebPluginRegistry, WebTemplateCatalog, WebTemplateDecision, WebTemplateDiagnostic, WorkspaceAdmissionDraft, WorkspaceResult, WorkspaceSandboxRequest, WorkspacesResult, WorkspacesState, WriteConfigResult, abbreviateCwd, abortCommand, abortRun, acquireModelContext, activateWebPluginHost, activateWebPluginSession, activateWebPluginWorkspace, activeSessionId, activityGroupSlot, activityGroups, addDevProxyTarget, addPasskey, admitDashboardWorkspace, admitDeclaredDashboardWorkspaces, admitWorkspace, ansiSegments, answerDialogConfirm, answerDialogValue, answerLogin, appendComposerDraft, appendComposerQuote, appendQueued, appendUserPrompt, applyCaptureFrame, applyHistoryPage, applyProtocolQueue, applyProtocolTranscript, applySelectionAnswer, applySessionBacklog, applySessionFrame, applySessionLifecycle, applySessionRemoved, applySessionUpsert, applySessionsSnapshot, applyStoredTheme, applyThreadFrame, applyThreadTranscriptFrame, applyWorkspaceRemoved, applyWorkspaceUpsert, applyWorkspacesSnapshot, approveDevice, approvePairing, asWorkspace, assertionFor, attachComposerCapture, attachComposerContext, attachmentPrompt, availableThemes, beginSessionReplay, beginSessionTransfer, bindBundledNativeTemplates, bindBundledWebTemplates, bindDashboardTranscriptWatcher, bindFileLinkModes, bindHistoryReader, bindHostTransport, bindSessionActivityRenderer, bindSessionFileLinkModes, bindSessionProtocol, bindSessionTranscriptRenderer, bindSessionWebWorkspace, bindThreadReader, bindThreadRenderer, bindTransport, bindWebPluginCompositions, browserNotificationPermission, browserReadyDuration, buildRailWorkspaces, builtinCommandFrame, canSaveSettings, cancelDialog, cancelLogin, choosePairTarget, claimDialogMenu, clearComposerState, clearDialog, clearPendingMenu, clearQueueCommand, clearQueuedEntries, clearQueuedMessages, closeNewSession, closePalette, closeRemoteDialog, closeResumeDialog, closeTransientTab, collectNativeTemplates, collectWebTemplates, commandCompletions, compactCommand, completeSessionTransfer, composerStore, composerSubmissionStore, configuredTemplate, contextGroups, createDashboardTransport, createDevProxyTarget, createPagedTranscript, createPlainSession, createProtocolHubSocket, createProtocolLink, createSession, createSessionMcpClient, createToolCompletionTabs, createWebPluginRegistry, createWorkspaceSession, dashboardRouteFrom, dashboardStateFor, deleteDevProxyTarget, deleteQueuedMessage, deleteSessionGroup, deliverBrowserNotification, denyDevice, denyPairing, devProxyStore, dialogCancelled, dialogConfirmed, dialogDraftStore, dialogValue, disableLivePush, disableRemoteAccess, discardSettingsDraft, disconnectCaptures, discoverWorkspaceAdmission, discoverWorkspaces, dispatchChannelFrame, dispatchHeldSessionChannel, disposeModelContextAdapter, dockTabFor, dropComposerState, dropPluginSessionData, dropSessionStore, dropThreads, dropTransientTabs, emptySelection, enableLivePush, enableRemoteAccess, endSessionReplay, enqueueAutomaticCommand, ensureTemplateConfiguration, fetchContextItemDetail, fetchDevProxyTargets, fetchRemoteState, fetchWithStepUp, fileCompletions, fileLinkFor, fileLinkMentions, fileTabForPath, findTransientTab, flushBrowserTelemetry, flushUiTrace, focusPrompt, focusWorkspaceWebPlugins, followUpCommand, getAvailableModelsCommand, getAvailableThinkingLevelsCommand, getCommandsCommand, getSessionStatsCommand, getStateCommand, getWorkspace, globalSlotFills, groupSummary, groupTone, hasSessionProtocol, heldSessionChannels, heldThreads, heldVoiceSessions, holdSessionChannels, holdVoiceSession, imagesFromContent, inactiveTotal, initialSessionState, inlineTextBytes, installBrowserErrorReporting, installGlobalWebPlugins, installSessionWebPlugins, installWebPlugins, installWorkspaceWebPlugins, installedWebPlugins, invokeServerMethod, isDesktopClient, isSessionMcpActivity, isSupportedImageMimeType, leaderConflicts, leaderGroup, listDirectory, listPoFolders, listProviders, listRemovedSessions, listSessionHistory, listSessionMcpClients, listSessions, listSettingsModels, listSettingsRepositories, listWorkspaceHistory, listWorkspaces, livePushStatus, loadModelChoices, loadSessionAsset, loadSessionAvatar, loadSettingsDraft, localHostId, logoutProvider, markSocketClosed, markToolPromptFailed, markUpdateRequired, mediaKindFor, menuStore, mergeScopedPlugins, minorModes, mintPairingCode, nativeTemplateCatalog, newPairingCode, newSessionDraftStore, newSessionStore, noSessions, noteRender, noteStoreUpdate, notifyHubConnected, onCaptureStatus, onComposerSubmitted, onHubConnected, onSealedSignInRequired, openNewSession, openPalette, openRemoteDialog, openResumeDialog, openTransientTab, ownerLabel, ownersOf, paletteCommands, paletteStore, parseFileMentions, parseHostKey, parseSelection, passkeysAvailable, pendingCaptureSessions, pendingMenuFor, pendingSetupView, personaInitials, plannedSettingsWrites, pluginActivityGroups, pluginContextActions, pluginDockFaces, pluginFileLinks, pluginHomeContributions, pluginLeaderBindings, pluginMinorModes, pluginRepositorySettingsPanels, pluginSelectionAxes, pluginSessionContext, pluginSettingsPanels, pluginSettingsSections, pluginSlotProps, pluginToolRenderer, pluginUserMessageActions, pluginsAtScope, prependHistory, projectedGroups, promoteQueuedCommand, promoteQueuedMessage, promptCommand, protocolSocketUrl, publishComposerSubmission, qrMatrix, qualifyHostId, qualifyHostKey, queueFollowUp, queueFollowUpWithAck, railDialogStore, railRestartStore, railSessionStatus, readDormantTranscriptPage, readImageSettings, readLogin, readRepositorySettings, readSessionMcpActivity, readSessionMcpConfig, readSettingsConfig, recordBrowserError, recordBrowserPerformance, recordBrowserUiTrace, reduceSession, refreshDevProxyTargets, refreshPasskeys, refreshRemoteState, refreshSessionFacts, refreshSessionSelection, refreshSessionStats, refreshTemplateConfiguration, refreshWebPluginCompositions, registerPasskey, registerPromptInput, releaseDialogMenu, releaseProtocolTranscript, releaseSessionActivityRenderer, releaseSessionTranscriptRenderer, releaseThreadRenderer, releaseTransport, reloadWebTemplateDecision, rememberHostChannelKey, rememberedHostChannelKey, remoteAccessStore, removeDevProxyTarget, removeHostWebPlugins, removeQueuedCommand, removeQueuedEntry, removeSessionMcpSetup, removeSessionWebPlugins, removeWorkspace, removeWorkspaceWebPlugins, renameSession, renameSessionGroup, renderSessionActivity, renderSessionTranscript, renderThread, replaceQueuedEntries, requestBrowserNotificationPermission, requestLatestHistory, requestNewerHistory, requestOlderHistory, requestSessionProtocolFrame, resetComposerStore, resetDevProxyStore, resetMenuStore, resetNewSessionStore, resetRailDialogStore, resetSealedSessions, resetSessionStore, resetSessionStores, resetSessions, resetThreads, resetToolPromptStore, resetTransientTabs, resetWebPlugins, resetWorkspaceAdmission, resetWorkspaces, resolveNativeTemplate, resolveParentId, resolveWebTemplate, restartDashboardSession, restartSession, restoreComposerDrafts, restoreLivePushRegistration, restoreRemovedSession, restoreRemovedThread, restoreSealedSession, resubscribeThreads, resumeDashboardSession, resumeDraftStore, resumeQueueCommand, resumeQueuedMessages, resumeSession, resumeWorkspaceSession, retryWebPluginCompositions, reuseRailWorkspaces, reviveSession, revokeDevice, revokePairedDevice, revokeSessionMcpClient, revokeTrustedDevice, revokeTrustedNativeDevice, rewindCommand, rewindToMessage, runCommand, runningCount, sameRailEntries, sameRailEntry, saveComposerDrafts, saveRemoteSettings, saveSettingsDraft, saveTemplateDefault, sealedHttpSession, sealedProtocolSession, sealedSession, searchDirectories, searchSessionFiles, seedHistoryCursor, selectFastMode, selectModel, selectTheme, selectThinkingLevel, selectWorkspace, selectionAxes, selectionStatuses, sendFrame, sendFrameWithAck, sendHubFrame, sendSessionProtocolFrame, sessionMinorModes, sessionStatusClock, sessionStatusLine, sessionStoreFor, sessionToolRenderer, sessionWebPluginsInstalled, sessionsStore, setActiveSession, setAgentLock, setDockOpen, setDockTab, setHasNewerHistory, setModelCommand, setNewSessionName, setPalettePath, setPendingMenu, setResumeView, setSessionGroups, setSessionNameCommand, setSessionStreamed, setSettingsDraft, setThinkingLevelCommand, setTransientTabSession, setUiTraceSink, setWebPluginLoadDiagnostics, setWorkspaceInstructions, setWorkspaceSandbox, settingsDraftKey, settingsDraftStore, settingsKeyOf, settingsLockedReason, settingsSection, settingsSections, showRemoteOptions, signInWithPasskey, slotFills, splitHostId, startLogin, startPluginDefinitions, startWebPlugins, steerCommand, stopSession, streamedSessions, stripAnsi, submitCapture, submitComposerDraft, submitMessage, submitMessageWithAck, subscribeActivitySources, subscribeThread, subscribeWebPluginRegistry, summariseArgs, summaryValueEqual, tabComposerContext, templateStore, textFromContent, themeStore, threadStoreKey, timelineUnits, toQueuedEntries, toTimelineEntries, togglePalette, toolMessageProps, toolPromptClaim, toolPromptStore, totalTokens, traceProfilerRender, traceStore, transientTabsOf, transientTabsStore, transportForHost, triggerTokenAt, turnRemoteAccessOff, turnRemoteAccessOn, uiStore, unsubscribeThread, updateComposerState, updateDialogDraft, updateRemoteSettings, updateResumeDraft, updateWorkspaceAdmission, uploadSessionAttachment, useActiveSession, useActiveSessionMeta, useActivityGroups, useComposerState, useDashboardRoute, useDashboardState, useDialogDraft, useDockFaceBadges, useDockFaces, useFileLinks, useHasOlderHistory, useNoSessions, useOpenTab, usePluginSlotProps, useRailEntries, useRailEntry, useRenderTrace, useSessionMcp, useTemplateConfiguration, useToolPrompt, useTransientTabs, useWebPluginRegistry, validateComposerCapture, waitForSession, watchDashboardTranscript, watchHubThread, webPluginCompositionStore, webPluginDiagnostics, webPluginMountState, webPluginRegistryForHost, webPluginRegistryRevision, webPluginsInstalled, webTabs, webTemplateCatalog, workspaceAdmissionStore, workspaceName, workspaceSlotFills, workspacesStore, writeImageSettings, writeRepositorySelection, writeSettingsValue };
package/dist/index.mjs CHANGED
@@ -64,7 +64,7 @@ import { EMPTY_NEW_SESSION_DRAFT, closeNewSession, createPlainSession, newSessio
64
64
  import { createPagedTranscript } from "./web/stores/pagedTranscriptStore.mjs";
65
65
  import { closePalette, openPalette, paletteStore, setPalettePath, togglePalette } from "./web/stores/paletteStore.mjs";
66
66
  import { closeResumeDialog, openResumeDialog, railDialogStore, railRestartStore, resetRailDialogStore, restartDashboardSession, restoreRemovedThread, resumeDashboardSession, resumeDraftStore, setResumeView, updateResumeDraft } from "./web/stores/railDialogStore.mjs";
67
- import { addPasskey, approveDevice, closeRemoteDialog, denyDevice, newPairingCode, openRemoteDialog, refreshPasskeys, refreshRemoteState, remoteAccessStore, revokePairedDevice, revokeTrustedNativeDevice, showRemoteOptions, turnRemoteAccessOff, turnRemoteAccessOn, updateRemoteSettings } from "./web/stores/remoteAccessStore.mjs";
67
+ import { addPasskey, approveDevice, choosePairTarget, closeRemoteDialog, denyDevice, newPairingCode, openRemoteDialog, refreshPasskeys, refreshRemoteState, remoteAccessStore, revokePairedDevice, revokeTrustedNativeDevice, showRemoteOptions, turnRemoteAccessOff, turnRemoteAccessOn, updateRemoteSettings } from "./web/stores/remoteAccessStore.mjs";
68
68
  import { dispatchHeldSessionChannel, heldSessionChannels, heldVoiceSessions, holdSessionChannels, holdVoiceSession } from "./web/stores/sessionChannelHoldsStore.mjs";
69
69
  import { configuredTemplate, ensureTemplateConfiguration, refreshTemplateConfiguration, saveTemplateDefault, templateStore, useTemplateConfiguration } from "./web/stores/templateStore.mjs";
70
70
  import { applyStoredTheme, availableThemes, selectTheme, themeStore } from "./web/stores/themeStore.mjs";
@@ -82,4 +82,4 @@ import { admitDashboardWorkspace, admitDeclaredDashboardWorkspaces, discoverWork
82
82
  import { dialogDraftStore, updateDialogDraft, useDialogDraft } from "./web/stores/dialogDraftStore.mjs";
83
83
  import { EMPTY_SETTINGS_DRAFT, discardSettingsDraft, loadSettingsDraft, saveSettingsDraft, setSettingsDraft, settingsDraftKey, settingsDraftStore } from "./web/stores/settingsDraftStore.mjs";
84
84
  import { dashboardStateFor, useDashboardState } from "./web/stores/dashboardStateStore.mjs";
85
- export { BUILTIN_COMMANDS, DEFAULT_REPOSITORY_SETTINGS_SECTION, DEFAULT_SETTINGS_SECTION, DashboardRendererProvider, EMPTY_NEW_SESSION_DRAFT, EMPTY_SETTINGS_DRAFT, HOST_SLOTS, LEGACY_WORKSPACE_ID, MAX_COMPLETION_ITEMS, MAX_COMPOSER_ATTACHMENTS, MAX_COMPOSER_IMAGE_BYTES, MAX_COMPOSER_TEXT_BYTES, MAX_COMPOSER_TOTAL_IMAGE_BYTES, MAX_COMPOSER_TOTAL_TEXT_BYTES, MAX_COMPOSER_UPLOAD_BYTES, PACKAGED_MINOR_MODES, PACKAGED_SELECTION_AXES, STEP_UP_HEADER_NAME, TRIGGER_CHARS, UPDATE_REQUIRED_REASON, abbreviateCwd, abortCommand, abortRun, acquireModelContext, activateWebPluginHost, activateWebPluginSession, activateWebPluginWorkspace, activeSessionId, activityGroupSlot, activityGroups, addDevProxyTarget, addPasskey, admitDashboardWorkspace, admitDeclaredDashboardWorkspaces, admitWorkspace, ansiSegments, answerDialogConfirm, answerDialogValue, answerLogin, appendComposerDraft, appendComposerQuote, appendQueued, appendUserPrompt, applyCaptureFrame, applyHistoryPage, applyProtocolQueue, applyProtocolTranscript, applySelectionAnswer, applySessionBacklog, applySessionFrame, applySessionLifecycle, applySessionRemoved, applySessionUpsert, applySessionsSnapshot, applyStoredTheme, applyThreadFrame, applyThreadTranscriptFrame, applyWorkspaceRemoved, applyWorkspaceUpsert, applyWorkspacesSnapshot, approveDevice, approvePairing, asWorkspace, assertionFor, attachComposerCapture, attachComposerContext, attachmentPrompt, availableThemes, beginSessionReplay, beginSessionTransfer, bindBundledNativeTemplates, bindBundledWebTemplates, bindDashboardTranscriptWatcher, bindFileLinkModes, bindHistoryReader, bindHostTransport, bindSessionActivityRenderer, bindSessionFileLinkModes, bindSessionProtocol, bindSessionTranscriptRenderer, bindSessionWebWorkspace, bindThreadReader, bindThreadRenderer, bindTransport, bindWebPluginCompositions, browserNotificationPermission, browserReadyDuration, buildRailWorkspaces, builtinCommandFrame, canSaveSettings, cancelDialog, cancelLogin, claimDialogMenu, clearComposerState, clearDialog, clearPendingMenu, clearQueueCommand, clearQueuedEntries, clearQueuedMessages, closeNewSession, closePalette, closeRemoteDialog, closeResumeDialog, closeTransientTab, collectNativeTemplates, collectWebTemplates, commandCompletions, compactCommand, completeSessionTransfer, composerStore, composerSubmissionStore, configuredTemplate, contextGroups, createDashboardTransport, createDevProxyTarget, createPagedTranscript, createPlainSession, createProtocolHubSocket, createProtocolLink, createSession, createSessionMcpClient, createToolCompletionTabs, createWebPluginRegistry, createWorkspaceSession, dashboardRouteFrom, dashboardStateFor, deleteDevProxyTarget, deleteQueuedMessage, deleteSessionGroup, deliverBrowserNotification, denyDevice, denyPairing, devProxyStore, dialogCancelled, dialogConfirmed, dialogDraftStore, dialogValue, disableLivePush, disableRemoteAccess, discardSettingsDraft, disconnectCaptures, discoverWorkspaceAdmission, discoverWorkspaces, dispatchChannelFrame, dispatchHeldSessionChannel, disposeModelContextAdapter, dockTabFor, dropComposerState, dropPluginSessionData, dropSessionStore, dropThreads, dropTransientTabs, emptySelection, enableLivePush, enableRemoteAccess, endSessionReplay, enqueueAutomaticCommand, ensureTemplateConfiguration, fetchContextItemDetail, fetchDevProxyTargets, fetchRemoteState, fetchWithStepUp, fileCompletions, fileLinkFor, fileLinkMentions, fileTabForPath, findTransientTab, flushBrowserTelemetry, flushUiTrace, focusPrompt, focusWorkspaceWebPlugins, followUpCommand, getAvailableModelsCommand, getAvailableThinkingLevelsCommand, getCommandsCommand, getSessionStatsCommand, getStateCommand, getWorkspace, globalSlotFills, groupSummary, groupTone, hasSessionProtocol, heldSessionChannels, heldThreads, heldVoiceSessions, holdSessionChannels, holdVoiceSession, imagesFromContent, inactiveTotal, initialSessionState, inlineTextBytes, installBrowserErrorReporting, installGlobalWebPlugins, installSessionWebPlugins, installWebPlugins, installWorkspaceWebPlugins, installedWebPlugins, invokeServerMethod, isDesktopClient, isSessionMcpActivity, isSupportedImageMimeType, leaderConflicts, leaderGroup, listDirectory, listPoFolders, listProviders, listRemovedSessions, listSessionHistory, listSessionMcpClients, listSessions, listSettingsModels, listSettingsRepositories, listWorkspaceHistory, listWorkspaces, livePushStatus, loadModelChoices, loadSessionAsset, loadSessionAvatar, loadSettingsDraft, localHostId, logoutProvider, markSocketClosed, markToolPromptFailed, markUpdateRequired, mediaKindFor, menuStore, mergeScopedPlugins, minorModes, mintPairingCode, nativeTemplateCatalog, newPairingCode, newSessionDraftStore, newSessionStore, noSessions, noteRender, noteStoreUpdate, notifyHubConnected, onCaptureStatus, onComposerSubmitted, onHubConnected, onSealedSignInRequired, openNewSession, openPalette, openRemoteDialog, openResumeDialog, openTransientTab, ownerLabel, ownersOf, paletteCommands, paletteStore, parseFileMentions, parseHostKey, parseSelection, passkeysAvailable, pendingCaptureSessions, pendingMenuFor, pendingSetupView, personaInitials, plannedSettingsWrites, pluginActivityGroups, pluginContextActions, pluginDockFaces, pluginFileLinks, pluginHomeContributions, pluginLeaderBindings, pluginMinorModes, pluginRepositorySettingsPanels, pluginSelectionAxes, pluginSessionContext, pluginSettingsPanels, pluginSettingsSections, pluginSlotProps, pluginToolRenderer, pluginUserMessageActions, pluginsAtScope, prependHistory, projectedGroups, promoteQueuedCommand, promoteQueuedMessage, promptCommand, protocolSocketUrl, publishComposerSubmission, qrMatrix, qualifyHostId, qualifyHostKey, queueFollowUp, queueFollowUpWithAck, railDialogStore, railRestartStore, railSessionStatus, readDormantTranscriptPage, readImageSettings, readLogin, readRepositorySettings, readSessionMcpActivity, readSessionMcpConfig, readSettingsConfig, recordBrowserError, recordBrowserPerformance, recordBrowserUiTrace, reduceSession, refreshDevProxyTargets, refreshPasskeys, refreshRemoteState, refreshSessionFacts, refreshSessionSelection, refreshSessionStats, refreshTemplateConfiguration, refreshWebPluginCompositions, registerPasskey, registerPromptInput, releaseDialogMenu, releaseProtocolTranscript, releaseSessionActivityRenderer, releaseSessionTranscriptRenderer, releaseThreadRenderer, releaseTransport, reloadWebTemplateDecision, rememberHostChannelKey, rememberedHostChannelKey, remoteAccessStore, removeDevProxyTarget, removeHostWebPlugins, removeQueuedCommand, removeQueuedEntry, removeSessionMcpSetup, removeSessionWebPlugins, removeWorkspace, removeWorkspaceWebPlugins, renameSession, renameSessionGroup, renderSessionActivity, renderSessionTranscript, renderThread, replaceQueuedEntries, requestBrowserNotificationPermission, requestLatestHistory, requestNewerHistory, requestOlderHistory, requestSessionProtocolFrame, resetComposerStore, resetDevProxyStore, resetMenuStore, resetNewSessionStore, resetRailDialogStore, resetSealedSessions, resetSessionStore, resetSessionStores, resetSessions, resetThreads, resetToolPromptStore, resetTransientTabs, resetWebPlugins, resetWorkspaceAdmission, resetWorkspaces, resolveNativeTemplate, resolveParentId, resolveWebTemplate, restartDashboardSession, restartSession, restoreComposerDrafts, restoreLivePushRegistration, restoreRemovedSession, restoreRemovedThread, restoreSealedSession, resubscribeThreads, resumeDashboardSession, resumeDraftStore, resumeQueueCommand, resumeQueuedMessages, resumeSession, resumeWorkspaceSession, retryWebPluginCompositions, reuseRailWorkspaces, reviveSession, revokeDevice, revokePairedDevice, revokeSessionMcpClient, revokeTrustedDevice, revokeTrustedNativeDevice, rewindCommand, rewindToMessage, runCommand, runningCount, sameRailEntries, sameRailEntry, saveComposerDrafts, saveRemoteSettings, saveSettingsDraft, saveTemplateDefault, sealedHttpSession, sealedProtocolSession, sealedSession, searchDirectories, searchSessionFiles, seedHistoryCursor, selectFastMode, selectModel, selectTheme, selectThinkingLevel, selectWorkspace, selectionAxes, selectionStatuses, sendFrame, sendFrameWithAck, sendHubFrame, sendSessionProtocolFrame, sessionMinorModes, sessionStatusClock, sessionStatusLine, sessionStoreFor, sessionToolRenderer, sessionWebPluginsInstalled, sessionsStore, setActiveSession, setAgentLock, setDockOpen, setDockTab, setHasNewerHistory, setModelCommand, setNewSessionName, setPalettePath, setPendingMenu, setResumeView, setSessionGroups, setSessionNameCommand, setSessionStreamed, setSettingsDraft, setThinkingLevelCommand, setTransientTabSession, setUiTraceSink, setWebPluginLoadDiagnostics, setWorkspaceInstructions, setWorkspaceSandbox, settingsDraftKey, settingsDraftStore, settingsKeyOf, settingsLockedReason, settingsSection, settingsSections, showRemoteOptions, signInWithPasskey, slotFills, splitHostId, startLogin, startPluginDefinitions, startWebPlugins, steerCommand, stopSession, streamedSessions, stripAnsi, submitCapture, submitComposerDraft, submitMessage, submitMessageWithAck, subscribeActivitySources, subscribeThread, subscribeWebPluginRegistry, summariseArgs, summaryValueEqual, tabComposerContext, templateStore, textFromContent, themeStore, threadStoreKey, timelineUnits, toQueuedEntries, toTimelineEntries, togglePalette, toolMessageProps, toolPromptClaim, toolPromptStore, totalTokens, traceProfilerRender, traceStore, transientTabsOf, transientTabsStore, transportForHost, triggerTokenAt, turnRemoteAccessOff, turnRemoteAccessOn, uiStore, unsubscribeThread, updateComposerState, updateDialogDraft, updateRemoteSettings, updateResumeDraft, updateWorkspaceAdmission, uploadSessionAttachment, useActiveSession, useActiveSessionMeta, useActivityGroups, useComposerState, useDashboardRoute, useDashboardState, useDialogDraft, useDockFaceBadges, useDockFaces, useFileLinks, useHasOlderHistory, useNoSessions, useOpenTab, usePluginSlotProps, useRailEntries, useRailEntry, useRenderTrace, useSessionMcp, useTemplateConfiguration, useToolPrompt, useTransientTabs, useWebPluginRegistry, validateComposerCapture, waitForSession, watchDashboardTranscript, watchHubThread, webPluginCompositionStore, webPluginDiagnostics, webPluginMountState, webPluginRegistryForHost, webPluginRegistryRevision, webPluginsInstalled, webTabs, webTemplateCatalog, workspaceAdmissionStore, workspaceName, workspaceSlotFills, workspacesStore, writeImageSettings, writeRepositorySelection, writeSettingsValue };
85
+ export { BUILTIN_COMMANDS, DEFAULT_REPOSITORY_SETTINGS_SECTION, DEFAULT_SETTINGS_SECTION, DashboardRendererProvider, EMPTY_NEW_SESSION_DRAFT, EMPTY_SETTINGS_DRAFT, HOST_SLOTS, LEGACY_WORKSPACE_ID, MAX_COMPLETION_ITEMS, MAX_COMPOSER_ATTACHMENTS, MAX_COMPOSER_IMAGE_BYTES, MAX_COMPOSER_TEXT_BYTES, MAX_COMPOSER_TOTAL_IMAGE_BYTES, MAX_COMPOSER_TOTAL_TEXT_BYTES, MAX_COMPOSER_UPLOAD_BYTES, PACKAGED_MINOR_MODES, PACKAGED_SELECTION_AXES, STEP_UP_HEADER_NAME, TRIGGER_CHARS, UPDATE_REQUIRED_REASON, abbreviateCwd, abortCommand, abortRun, acquireModelContext, activateWebPluginHost, activateWebPluginSession, activateWebPluginWorkspace, activeSessionId, activityGroupSlot, activityGroups, addDevProxyTarget, addPasskey, admitDashboardWorkspace, admitDeclaredDashboardWorkspaces, admitWorkspace, ansiSegments, answerDialogConfirm, answerDialogValue, answerLogin, appendComposerDraft, appendComposerQuote, appendQueued, appendUserPrompt, applyCaptureFrame, applyHistoryPage, applyProtocolQueue, applyProtocolTranscript, applySelectionAnswer, applySessionBacklog, applySessionFrame, applySessionLifecycle, applySessionRemoved, applySessionUpsert, applySessionsSnapshot, applyStoredTheme, applyThreadFrame, applyThreadTranscriptFrame, applyWorkspaceRemoved, applyWorkspaceUpsert, applyWorkspacesSnapshot, approveDevice, approvePairing, asWorkspace, assertionFor, attachComposerCapture, attachComposerContext, attachmentPrompt, availableThemes, beginSessionReplay, beginSessionTransfer, bindBundledNativeTemplates, bindBundledWebTemplates, bindDashboardTranscriptWatcher, bindFileLinkModes, bindHistoryReader, bindHostTransport, bindSessionActivityRenderer, bindSessionFileLinkModes, bindSessionProtocol, bindSessionTranscriptRenderer, bindSessionWebWorkspace, bindThreadReader, bindThreadRenderer, bindTransport, bindWebPluginCompositions, browserNotificationPermission, browserReadyDuration, buildRailWorkspaces, builtinCommandFrame, canSaveSettings, cancelDialog, cancelLogin, choosePairTarget, claimDialogMenu, clearComposerState, clearDialog, clearPendingMenu, clearQueueCommand, clearQueuedEntries, clearQueuedMessages, closeNewSession, closePalette, closeRemoteDialog, closeResumeDialog, closeTransientTab, collectNativeTemplates, collectWebTemplates, commandCompletions, compactCommand, completeSessionTransfer, composerStore, composerSubmissionStore, configuredTemplate, contextGroups, createDashboardTransport, createDevProxyTarget, createPagedTranscript, createPlainSession, createProtocolHubSocket, createProtocolLink, createSession, createSessionMcpClient, createToolCompletionTabs, createWebPluginRegistry, createWorkspaceSession, dashboardRouteFrom, dashboardStateFor, deleteDevProxyTarget, deleteQueuedMessage, deleteSessionGroup, deliverBrowserNotification, denyDevice, denyPairing, devProxyStore, dialogCancelled, dialogConfirmed, dialogDraftStore, dialogValue, disableLivePush, disableRemoteAccess, discardSettingsDraft, disconnectCaptures, discoverWorkspaceAdmission, discoverWorkspaces, dispatchChannelFrame, dispatchHeldSessionChannel, disposeModelContextAdapter, dockTabFor, dropComposerState, dropPluginSessionData, dropSessionStore, dropThreads, dropTransientTabs, emptySelection, enableLivePush, enableRemoteAccess, endSessionReplay, enqueueAutomaticCommand, ensureTemplateConfiguration, fetchContextItemDetail, fetchDevProxyTargets, fetchRemoteState, fetchWithStepUp, fileCompletions, fileLinkFor, fileLinkMentions, fileTabForPath, findTransientTab, flushBrowserTelemetry, flushUiTrace, focusPrompt, focusWorkspaceWebPlugins, followUpCommand, getAvailableModelsCommand, getAvailableThinkingLevelsCommand, getCommandsCommand, getSessionStatsCommand, getStateCommand, getWorkspace, globalSlotFills, groupSummary, groupTone, hasSessionProtocol, heldSessionChannels, heldThreads, heldVoiceSessions, holdSessionChannels, holdVoiceSession, imagesFromContent, inactiveTotal, initialSessionState, inlineTextBytes, installBrowserErrorReporting, installGlobalWebPlugins, installSessionWebPlugins, installWebPlugins, installWorkspaceWebPlugins, installedWebPlugins, invokeServerMethod, isDesktopClient, isSessionMcpActivity, isSupportedImageMimeType, leaderConflicts, leaderGroup, listDirectory, listPoFolders, listProviders, listRemovedSessions, listSessionHistory, listSessionMcpClients, listSessions, listSettingsModels, listSettingsRepositories, listWorkspaceHistory, listWorkspaces, livePushStatus, loadModelChoices, loadSessionAsset, loadSessionAvatar, loadSettingsDraft, localHostId, logoutProvider, markSocketClosed, markToolPromptFailed, markUpdateRequired, mediaKindFor, menuStore, mergeScopedPlugins, minorModes, mintPairingCode, nativeTemplateCatalog, newPairingCode, newSessionDraftStore, newSessionStore, noSessions, noteRender, noteStoreUpdate, notifyHubConnected, onCaptureStatus, onComposerSubmitted, onHubConnected, onSealedSignInRequired, openNewSession, openPalette, openRemoteDialog, openResumeDialog, openTransientTab, ownerLabel, ownersOf, paletteCommands, paletteStore, parseFileMentions, parseHostKey, parseSelection, passkeysAvailable, pendingCaptureSessions, pendingMenuFor, pendingSetupView, personaInitials, plannedSettingsWrites, pluginActivityGroups, pluginContextActions, pluginDockFaces, pluginFileLinks, pluginHomeContributions, pluginLeaderBindings, pluginMinorModes, pluginRepositorySettingsPanels, pluginSelectionAxes, pluginSessionContext, pluginSettingsPanels, pluginSettingsSections, pluginSlotProps, pluginToolRenderer, pluginUserMessageActions, pluginsAtScope, prependHistory, projectedGroups, promoteQueuedCommand, promoteQueuedMessage, promptCommand, protocolSocketUrl, publishComposerSubmission, qrMatrix, qualifyHostId, qualifyHostKey, queueFollowUp, queueFollowUpWithAck, railDialogStore, railRestartStore, railSessionStatus, readDormantTranscriptPage, readImageSettings, readLogin, readRepositorySettings, readSessionMcpActivity, readSessionMcpConfig, readSettingsConfig, recordBrowserError, recordBrowserPerformance, recordBrowserUiTrace, reduceSession, refreshDevProxyTargets, refreshPasskeys, refreshRemoteState, refreshSessionFacts, refreshSessionSelection, refreshSessionStats, refreshTemplateConfiguration, refreshWebPluginCompositions, registerPasskey, registerPromptInput, releaseDialogMenu, releaseProtocolTranscript, releaseSessionActivityRenderer, releaseSessionTranscriptRenderer, releaseThreadRenderer, releaseTransport, reloadWebTemplateDecision, rememberHostChannelKey, rememberedHostChannelKey, remoteAccessStore, removeDevProxyTarget, removeHostWebPlugins, removeQueuedCommand, removeQueuedEntry, removeSessionMcpSetup, removeSessionWebPlugins, removeWorkspace, removeWorkspaceWebPlugins, renameSession, renameSessionGroup, renderSessionActivity, renderSessionTranscript, renderThread, replaceQueuedEntries, requestBrowserNotificationPermission, requestLatestHistory, requestNewerHistory, requestOlderHistory, requestSessionProtocolFrame, resetComposerStore, resetDevProxyStore, resetMenuStore, resetNewSessionStore, resetRailDialogStore, resetSealedSessions, resetSessionStore, resetSessionStores, resetSessions, resetThreads, resetToolPromptStore, resetTransientTabs, resetWebPlugins, resetWorkspaceAdmission, resetWorkspaces, resolveNativeTemplate, resolveParentId, resolveWebTemplate, restartDashboardSession, restartSession, restoreComposerDrafts, restoreLivePushRegistration, restoreRemovedSession, restoreRemovedThread, restoreSealedSession, resubscribeThreads, resumeDashboardSession, resumeDraftStore, resumeQueueCommand, resumeQueuedMessages, resumeSession, resumeWorkspaceSession, retryWebPluginCompositions, reuseRailWorkspaces, reviveSession, revokeDevice, revokePairedDevice, revokeSessionMcpClient, revokeTrustedDevice, revokeTrustedNativeDevice, rewindCommand, rewindToMessage, runCommand, runningCount, sameRailEntries, sameRailEntry, saveComposerDrafts, saveRemoteSettings, saveSettingsDraft, saveTemplateDefault, sealedHttpSession, sealedProtocolSession, sealedSession, searchDirectories, searchSessionFiles, seedHistoryCursor, selectFastMode, selectModel, selectTheme, selectThinkingLevel, selectWorkspace, selectionAxes, selectionStatuses, sendFrame, sendFrameWithAck, sendHubFrame, sendSessionProtocolFrame, sessionMinorModes, sessionStatusClock, sessionStatusLine, sessionStoreFor, sessionToolRenderer, sessionWebPluginsInstalled, sessionsStore, setActiveSession, setAgentLock, setDockOpen, setDockTab, setHasNewerHistory, setModelCommand, setNewSessionName, setPalettePath, setPendingMenu, setResumeView, setSessionGroups, setSessionNameCommand, setSessionStreamed, setSettingsDraft, setThinkingLevelCommand, setTransientTabSession, setUiTraceSink, setWebPluginLoadDiagnostics, setWorkspaceInstructions, setWorkspaceSandbox, settingsDraftKey, settingsDraftStore, settingsKeyOf, settingsLockedReason, settingsSection, settingsSections, showRemoteOptions, signInWithPasskey, slotFills, splitHostId, startLogin, startPluginDefinitions, startWebPlugins, steerCommand, stopSession, streamedSessions, stripAnsi, submitCapture, submitComposerDraft, submitMessage, submitMessageWithAck, subscribeActivitySources, subscribeThread, subscribeWebPluginRegistry, summariseArgs, summaryValueEqual, tabComposerContext, templateStore, textFromContent, themeStore, threadStoreKey, timelineUnits, toQueuedEntries, toTimelineEntries, togglePalette, toolMessageProps, toolPromptClaim, toolPromptStore, totalTokens, traceProfilerRender, traceStore, transientTabsOf, transientTabsStore, transportForHost, triggerTokenAt, turnRemoteAccessOff, turnRemoteAccessOn, uiStore, unsubscribeThread, updateComposerState, updateDialogDraft, updateRemoteSettings, updateResumeDraft, updateWorkspaceAdmission, uploadSessionAttachment, useActiveSession, useActiveSessionMeta, useActivityGroups, useComposerState, useDashboardRoute, useDashboardState, useDialogDraft, useDockFaceBadges, useDockFaces, useFileLinks, useHasOlderHistory, useNoSessions, useOpenTab, usePluginSlotProps, useRailEntries, useRailEntry, useRenderTrace, useSessionMcp, useTemplateConfiguration, useToolPrompt, useTransientTabs, useWebPluginRegistry, validateComposerCapture, waitForSession, watchDashboardTranscript, watchHubThread, webPluginCompositionStore, webPluginDiagnostics, webPluginMountState, webPluginRegistryForHost, webPluginRegistryRevision, webPluginsInstalled, webTabs, webTemplateCatalog, workspaceAdmissionStore, workspaceName, workspaceSlotFills, workspacesStore, writeImageSettings, writeRepositorySelection, writeSettingsValue };
@@ -22,6 +22,11 @@ const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;
22
22
  const PROTOCOL_SOCKET_ROUTE = "/api/ws";
23
23
  /** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */
24
24
  const PAIRING_PAGE_ROUTE = "/pair";
25
+ /**
26
+ * The phone app's form of the same QR: its `po` scheme wrapping the encoded HTTPS pairing URL, so a
27
+ * phone camera opens Po instead of the browser. The app unwraps it and checks the inner URL as usual.
28
+ */
29
+ const NATIVE_PAIRING_LINK_PREFIX = "po://pair?link=";
25
30
  /** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */
26
31
  const PAIRING_CLAIM_ROUTE = "/api/remote/pair";
27
32
  /** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */
@@ -67,6 +72,7 @@ exports.BUNDLE_MINIMUM_REVISION_PARAM = BUNDLE_MINIMUM_REVISION_PARAM;
67
72
  exports.BUNDLE_SIGNING_KEY_PARAM = BUNDLE_SIGNING_KEY_PARAM;
68
73
  exports.COOKIE_CEILING_SECONDS = COOKIE_CEILING_SECONDS;
69
74
  exports.DEVICE_COOKIE = DEVICE_COOKIE;
75
+ exports.NATIVE_PAIRING_LINK_PREFIX = NATIVE_PAIRING_LINK_PREFIX;
70
76
  exports.PAIRING_CLAIM_ROUTE = PAIRING_CLAIM_ROUTE;
71
77
  exports.PAIRING_CODE_TTL_MS = PAIRING_CODE_TTL_MS;
72
78
  exports.PAIRING_PAGE_ROUTE = PAIRING_PAGE_ROUTE;
@@ -1 +1 @@
1
- {"version":3,"file":"remoteAccess.cjs","names":[],"sources":["../../src/types/remoteAccess.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\n/** Trusted native devices; `DELETE ${TRUSTED_DEVICES_ROUTE}/:id` revokes one. Mirrors po-core's constant. */\nexport const TRUSTED_DEVICES_ROUTE = `${REMOTE_API_ROUTE}/trusted-devices`;\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\nexport const PROTOCOL_SOCKET_ROUTE = '/api/ws';\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-po_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'po_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-po-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7534;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 30 * 24 * 60 * 60;\n\nexport type TunnelKind = 'quick' | 'named';\nexport type RemoteChannelScope = 'session' | 'protocol' | 'http';\n\n/**\n * How the tunnel is established.\n *\n * `named` carries a hostname rather than discovering one, which is why it is\n * the mode that supports passkeys, a service worker, and durable pairing: all\n * three are bound to the origin, and a quick tunnel's hostname rotates on every\n * start. The token is referenced by path, never stored inline, because it is a\n * Cloudflare account credential and an inlined one ends up in a bug report.\n */\nexport type TunnelConfig =\n | { kind: 'quick' }\n | { kind: 'named'; hostname: string; name?: string; tokenFile?: string; configFile?: string };\n\n/** Nullable numbers are not used: a disabled toggle keeps its value so flipping it back restores it. */\nexport interface RemoteAccessSettings {\n /** Close the tunnel on a timer. */\n autoCloseEnabled: boolean;\n autoCloseMinutes: number;\n /** Expire paired sessions on idle and on total age. */\n sessionExpiryEnabled: boolean;\n idleMinutes: number;\n absoluteHours: number;\n tunnel: TunnelConfig;\n}\n\nexport interface BundlePairingTrust {\n publicKey: string;\n fingerprint: string;\n revision: number;\n}\n\nexport type RemoteAccessStatus = 'off' | 'starting' | 'on' | 'failed';\n\nexport interface PairedDeviceView {\n id: string;\n label: string;\n userAgent: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True for the device making the request, so the UI can say \"this device\". */\n self: boolean;\n}\n\nexport type TrustedDevicePlatform = 'ios' | 'android';\n\n/** What local and remote callers see of a trusted native device: no keys. Mirrors po-core's view. */\nexport interface TrustedDeviceView {\n id: string;\n label: string;\n platform: TrustedDevicePlatform;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True when the calling session signed in with this device's key. */\n self: boolean;\n}\n\nexport interface PairingRequestView {\n id: string;\n userAgent: string;\n /**\n * The address Cloudflare's edge reported. Display only: any local process can\n * reach the tunnel listener directly and set this header to anything, so it\n * never gates a decision. The UI labels it \"reported by the edge\".\n */\n edgeIp: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n expiresAt: string;\n}\n\nexport interface RemoteAccessStateView {\n status: RemoteAccessStatus;\n /** Present once the tunnel reports its hostname. */\n publicUrl?: string;\n /** ISO 8601, present while the tunnel is up. */\n startedAt?: string;\n /** ISO 8601, absent when auto-close is switched off. */\n closesAt?: string;\n /** Why the tunnel failed, once it has. */\n error?: string;\n devices: PairedDeviceView[];\n /** Populated for local callers only; a paired phone must not see approval prompts. */\n pending: PairingRequestView[];\n /** Shown to local and remote callers, so a paired device can revoke a lost one. */\n trustedDevices: TrustedDeviceView[];\n settings: RemoteAccessSettings;\n}\n\nexport type PairingStatus = 'pending' | 'approved' | 'denied' | 'expired' | 'consumed';\n\nexport type TunnelFailure = 'not_installed' | 'spawn_failed' | 'timeout' | 'self_test_failed' | 'exited';\n\nexport interface TunnelStartInput {\n /** The loopback port the tunnel should publish. */\n port: number;\n config: TunnelConfig;\n /** Arms the exact public origin before the launcher probes through it. */\n acceptOrigin?: (origin: string) => void;\n /** Cancels startup and asks the launcher to stop any process it has spawned. */\n signal?: AbortSignal;\n}\n\nexport type TunnelStartResult =\n | { ok: true; publicOrigin: string; stop: () => Promise<void> }\n | { ok: false; failure: TunnelFailure; message: string };\n\n/**\n * Starting a tunnel, as a port rather than a concrete process.\n *\n * Declared here so a test can inject a fake without pulling in the cloudflared\n * adapter, and so `WebServerOptions` can name the seam without reaching across\n * a layer to do it.\n */\nexport type TunnelLauncher = (input: TunnelStartInput) => Promise<TunnelStartResult>;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAEhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAExD,MAAa,oBAAoB,GAAG,iBAAiB;;;AAGrD,MAAa,wBAAwB,GAAG,iBAAiB;AACzD,MAAa,oBAAoB,GAAG,iBAAiB;AACrD,MAAa,wBAAwB,GAAG,kBAAkB;AAC1D,MAAa,wBAAwB;;AAErC,MAAa,qBAAqB;;AAElC,MAAa,sBAAsB;;AAEnC,MAAa,uBAAuB;;AAEpC,MAAa,uBAAuB;;AAEpC,MAAa,2BAA2B;AACxC,MAAa,gCAAgC;;;;;;AAO7C,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AACxC,MAAa,4BAA4B;AACzC,MAAa,+BAA+B;AAC5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAEnC,MAAa,yBAAyB;;AAEtC,MAAa,yBAAyB;;AA8HtC,MAAa,oBAAoB;;AAEjC,MAAa,8BAA8B"}
1
+ {"version":3,"file":"remoteAccess.cjs","names":[],"sources":["../../src/types/remoteAccess.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\n/** Trusted native devices; `DELETE ${TRUSTED_DEVICES_ROUTE}/:id` revokes one. Mirrors po-core's constant. */\nexport const TRUSTED_DEVICES_ROUTE = `${REMOTE_API_ROUTE}/trusted-devices`;\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\nexport const PROTOCOL_SOCKET_ROUTE = '/api/ws';\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n/**\n * The phone app's form of the same QR: its `po` scheme wrapping the encoded HTTPS pairing URL, so a\n * phone camera opens Po instead of the browser. The app unwraps it and checks the inner URL as usual.\n */\nexport const NATIVE_PAIRING_LINK_PREFIX = 'po://pair?link=';\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-po_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'po_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-po-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7534;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 30 * 24 * 60 * 60;\n\nexport type TunnelKind = 'quick' | 'named';\nexport type RemoteChannelScope = 'session' | 'protocol' | 'http';\n\n/**\n * How the tunnel is established.\n *\n * `named` carries a hostname rather than discovering one, which is why it is\n * the mode that supports passkeys, a service worker, and durable pairing: all\n * three are bound to the origin, and a quick tunnel's hostname rotates on every\n * start. The token is referenced by path, never stored inline, because it is a\n * Cloudflare account credential and an inlined one ends up in a bug report.\n */\nexport type TunnelConfig =\n | { kind: 'quick' }\n | { kind: 'named'; hostname: string; name?: string; tokenFile?: string; configFile?: string };\n\n/** Nullable numbers are not used: a disabled toggle keeps its value so flipping it back restores it. */\nexport interface RemoteAccessSettings {\n /** Close the tunnel on a timer. */\n autoCloseEnabled: boolean;\n autoCloseMinutes: number;\n /** Expire paired sessions on idle and on total age. */\n sessionExpiryEnabled: boolean;\n idleMinutes: number;\n absoluteHours: number;\n tunnel: TunnelConfig;\n}\n\nexport interface BundlePairingTrust {\n publicKey: string;\n fingerprint: string;\n revision: number;\n}\n\nexport type RemoteAccessStatus = 'off' | 'starting' | 'on' | 'failed';\n\nexport interface PairedDeviceView {\n id: string;\n label: string;\n userAgent: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True for the device making the request, so the UI can say \"this device\". */\n self: boolean;\n}\n\nexport type TrustedDevicePlatform = 'ios' | 'android';\n\n/** What local and remote callers see of a trusted native device: no keys. Mirrors po-core's view. */\nexport interface TrustedDeviceView {\n id: string;\n label: string;\n platform: TrustedDevicePlatform;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True when the calling session signed in with this device's key. */\n self: boolean;\n}\n\nexport interface PairingRequestView {\n id: string;\n userAgent: string;\n /**\n * The address Cloudflare's edge reported. Display only: any local process can\n * reach the tunnel listener directly and set this header to anything, so it\n * never gates a decision. The UI labels it \"reported by the edge\".\n */\n edgeIp: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n expiresAt: string;\n}\n\nexport interface RemoteAccessStateView {\n status: RemoteAccessStatus;\n /** Present once the tunnel reports its hostname. */\n publicUrl?: string;\n /** ISO 8601, present while the tunnel is up. */\n startedAt?: string;\n /** ISO 8601, absent when auto-close is switched off. */\n closesAt?: string;\n /** Why the tunnel failed, once it has. */\n error?: string;\n devices: PairedDeviceView[];\n /** Populated for local callers only; a paired phone must not see approval prompts. */\n pending: PairingRequestView[];\n /** Shown to local and remote callers, so a paired device can revoke a lost one. */\n trustedDevices: TrustedDeviceView[];\n settings: RemoteAccessSettings;\n}\n\nexport type PairingStatus = 'pending' | 'approved' | 'denied' | 'expired' | 'consumed';\n\nexport type TunnelFailure = 'not_installed' | 'spawn_failed' | 'timeout' | 'self_test_failed' | 'exited';\n\nexport interface TunnelStartInput {\n /** The loopback port the tunnel should publish. */\n port: number;\n config: TunnelConfig;\n /** Arms the exact public origin before the launcher probes through it. */\n acceptOrigin?: (origin: string) => void;\n /** Cancels startup and asks the launcher to stop any process it has spawned. */\n signal?: AbortSignal;\n}\n\nexport type TunnelStartResult =\n | { ok: true; publicOrigin: string; stop: () => Promise<void> }\n | { ok: false; failure: TunnelFailure; message: string };\n\n/**\n * Starting a tunnel, as a port rather than a concrete process.\n *\n * Declared here so a test can inject a fake without pulling in the cloudflared\n * adapter, and so `WebServerOptions` can name the seam without reaching across\n * a layer to do it.\n */\nexport type TunnelLauncher = (input: TunnelStartInput) => Promise<TunnelStartResult>;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAEhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAExD,MAAa,oBAAoB,GAAG,iBAAiB;;;AAGrD,MAAa,wBAAwB,GAAG,iBAAiB;AACzD,MAAa,oBAAoB,GAAG,iBAAiB;AACrD,MAAa,wBAAwB,GAAG,kBAAkB;AAC1D,MAAa,wBAAwB;;AAErC,MAAa,qBAAqB;;;;;AAKlC,MAAa,6BAA6B;;AAE1C,MAAa,sBAAsB;;AAEnC,MAAa,uBAAuB;;AAEpC,MAAa,uBAAuB;;AAEpC,MAAa,2BAA2B;AACxC,MAAa,gCAAgC;;;;;;AAO7C,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AACxC,MAAa,4BAA4B;AACzC,MAAa,+BAA+B;AAC5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAEnC,MAAa,yBAAyB;;AAEtC,MAAa,yBAAyB;;AA8HtC,MAAa,oBAAoB;;AAEjC,MAAa,8BAA8B"}
@@ -22,6 +22,11 @@ export declare const REMOTE_PUSH_KEY_ROUTE = "/api/remote/push/key";
22
22
  export declare const PROTOCOL_SOCKET_ROUTE = "/api/ws";
23
23
  /** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */
24
24
  export declare const PAIRING_PAGE_ROUTE = "/pair";
25
+ /**
26
+ * The phone app's form of the same QR: its `po` scheme wrapping the encoded HTTPS pairing URL, so a
27
+ * phone camera opens Po instead of the browser. The app unwraps it and checks the inner URL as usual.
28
+ */
29
+ export declare const NATIVE_PAIRING_LINK_PREFIX = "po://pair?link=";
25
30
  /** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */
26
31
  export declare const PAIRING_CLAIM_ROUTE = "/api/remote/pair";
27
32
  /** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */
@@ -1 +1 @@
1
- {"version":3,"file":"remoteAccess.d.mts","names":[],"sources":["../../src/types/remoteAccess.ts"],"mappings":";;;;;;;;;;;qBAWa;;qBAEA;;qBAEA;;;qBAGA;qBACA;qBACA;qBACA;;qBAEA;;qBAEA;;qBAEA;;qBAEA;;qBAEA;qBACA;;;;;;qBAOA;;;;;;;;qBASA;qBACA;qBACA;qBACA;;qBAGA;;qBAGA;;qBAGA;;qBAEA;;qBAEA;YAED;YACA;;;;;;;;;;YAWA;EACN;;EACA;EAAe;EAAkB;EAAe;EAAoB;;;iBAGzD;;EAEf;EACA;;EAEA;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA;;YAGU;iBAEK;EACf;EACA;EACA;;EAEA;;EAEA;;EAEA;;YAGU;;iBAGK;EACf;EACA;EACA,UAAU;;EAEV;;EAEA;;EAEA;;iBAGe;EACf;EACA;;;;;;EAMA;;EAEA;;EAEA;;iBAGe;EACf,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;EACA,SAAS;;EAET,SAAS;;EAET,gBAAgB;EAChB,UAAU;;YAGA;YAEA;iBAEK;;EAEf;EACA,QAAQ;;EAER,gBAAgB;;EAEhB,SAAS;;YAGC;EACN;EAAU;EAAsB,YAAY;;EAC5C;EAAW,SAAS;EAAe;;;;;;;;;YAS7B,kBAAkB,OAAO,qBAAqB,QAAQ;;qBAGrD;;qBAEA"}
1
+ {"version":3,"file":"remoteAccess.d.mts","names":[],"sources":["../../src/types/remoteAccess.ts"],"mappings":";;;;;;;;;;;qBAWa;;qBAEA;;qBAEA;;;qBAGA;qBACA;qBACA;qBACA;;qBAEA;;;;;qBAKA;;qBAEA;;qBAEA;;qBAEA;;qBAEA;qBACA;;;;;;qBAOA;;;;;;;;qBASA;qBACA;qBACA;qBACA;;qBAGA;;qBAGA;;qBAGA;;qBAEA;;qBAEA;YAED;YACA;;;;;;;;;;YAWA;EACN;;EACA;EAAe;EAAkB;EAAe;EAAoB;;;iBAGzD;;EAEf;EACA;;EAEA;EACA;EACA;EACA,QAAQ;;iBAGO;EACf;EACA;EACA;;YAGU;iBAEK;EACf;EACA;EACA;;EAEA;;EAEA;;EAEA;;YAGU;;iBAGK;EACf;EACA;EACA,UAAU;;EAEV;;EAEA;;EAEA;;iBAGe;EACf;EACA;;;;;;EAMA;;EAEA;;EAEA;;iBAGe;EACf,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;EACA,SAAS;;EAET,SAAS;;EAET,gBAAgB;EAChB,UAAU;;YAGA;YAEA;iBAEK;;EAEf;EACA,QAAQ;;EAER,gBAAgB;;EAEhB,SAAS;;YAGC;EACN;EAAU;EAAsB,YAAY;;EAC5C;EAAW,SAAS;EAAe;;;;;;;;;YAS7B,kBAAkB,OAAO,qBAAqB,QAAQ;;qBAGrD;;qBAEA"}
@@ -22,6 +22,11 @@ const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;
22
22
  const PROTOCOL_SOCKET_ROUTE = "/api/ws";
23
23
  /** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */
24
24
  const PAIRING_PAGE_ROUTE = "/pair";
25
+ /**
26
+ * The phone app's form of the same QR: its `po` scheme wrapping the encoded HTTPS pairing URL, so a
27
+ * phone camera opens Po instead of the browser. The app unwraps it and checks the inner URL as usual.
28
+ */
29
+ const NATIVE_PAIRING_LINK_PREFIX = "po://pair?link=";
25
30
  /** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */
26
31
  const PAIRING_CLAIM_ROUTE = "/api/remote/pair";
27
32
  /** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */
@@ -63,6 +68,6 @@ const REMOTE_STATE_TYPE = "remote_state";
63
68
  /** Hub frame announcing a device waiting for approval; sent to local pages only. */
64
69
  const REMOTE_PAIRING_REQUEST_TYPE = "remote_pairing_request";
65
70
  //#endregion
66
- export { BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PROTOCOL_SOCKET_ROUTE, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, WEB_DEV_SERVER_PORT };
71
+ export { BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, NATIVE_PAIRING_LINK_PREFIX, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PROTOCOL_SOCKET_ROUTE, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, WEB_DEV_SERVER_PORT };
67
72
 
68
73
  //# sourceMappingURL=remoteAccess.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"remoteAccess.mjs","names":[],"sources":["../../src/types/remoteAccess.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\n/** Trusted native devices; `DELETE ${TRUSTED_DEVICES_ROUTE}/:id` revokes one. Mirrors po-core's constant. */\nexport const TRUSTED_DEVICES_ROUTE = `${REMOTE_API_ROUTE}/trusted-devices`;\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\nexport const PROTOCOL_SOCKET_ROUTE = '/api/ws';\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-po_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'po_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-po-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7534;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 30 * 24 * 60 * 60;\n\nexport type TunnelKind = 'quick' | 'named';\nexport type RemoteChannelScope = 'session' | 'protocol' | 'http';\n\n/**\n * How the tunnel is established.\n *\n * `named` carries a hostname rather than discovering one, which is why it is\n * the mode that supports passkeys, a service worker, and durable pairing: all\n * three are bound to the origin, and a quick tunnel's hostname rotates on every\n * start. The token is referenced by path, never stored inline, because it is a\n * Cloudflare account credential and an inlined one ends up in a bug report.\n */\nexport type TunnelConfig =\n | { kind: 'quick' }\n | { kind: 'named'; hostname: string; name?: string; tokenFile?: string; configFile?: string };\n\n/** Nullable numbers are not used: a disabled toggle keeps its value so flipping it back restores it. */\nexport interface RemoteAccessSettings {\n /** Close the tunnel on a timer. */\n autoCloseEnabled: boolean;\n autoCloseMinutes: number;\n /** Expire paired sessions on idle and on total age. */\n sessionExpiryEnabled: boolean;\n idleMinutes: number;\n absoluteHours: number;\n tunnel: TunnelConfig;\n}\n\nexport interface BundlePairingTrust {\n publicKey: string;\n fingerprint: string;\n revision: number;\n}\n\nexport type RemoteAccessStatus = 'off' | 'starting' | 'on' | 'failed';\n\nexport interface PairedDeviceView {\n id: string;\n label: string;\n userAgent: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True for the device making the request, so the UI can say \"this device\". */\n self: boolean;\n}\n\nexport type TrustedDevicePlatform = 'ios' | 'android';\n\n/** What local and remote callers see of a trusted native device: no keys. Mirrors po-core's view. */\nexport interface TrustedDeviceView {\n id: string;\n label: string;\n platform: TrustedDevicePlatform;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True when the calling session signed in with this device's key. */\n self: boolean;\n}\n\nexport interface PairingRequestView {\n id: string;\n userAgent: string;\n /**\n * The address Cloudflare's edge reported. Display only: any local process can\n * reach the tunnel listener directly and set this header to anything, so it\n * never gates a decision. The UI labels it \"reported by the edge\".\n */\n edgeIp: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n expiresAt: string;\n}\n\nexport interface RemoteAccessStateView {\n status: RemoteAccessStatus;\n /** Present once the tunnel reports its hostname. */\n publicUrl?: string;\n /** ISO 8601, present while the tunnel is up. */\n startedAt?: string;\n /** ISO 8601, absent when auto-close is switched off. */\n closesAt?: string;\n /** Why the tunnel failed, once it has. */\n error?: string;\n devices: PairedDeviceView[];\n /** Populated for local callers only; a paired phone must not see approval prompts. */\n pending: PairingRequestView[];\n /** Shown to local and remote callers, so a paired device can revoke a lost one. */\n trustedDevices: TrustedDeviceView[];\n settings: RemoteAccessSettings;\n}\n\nexport type PairingStatus = 'pending' | 'approved' | 'denied' | 'expired' | 'consumed';\n\nexport type TunnelFailure = 'not_installed' | 'spawn_failed' | 'timeout' | 'self_test_failed' | 'exited';\n\nexport interface TunnelStartInput {\n /** The loopback port the tunnel should publish. */\n port: number;\n config: TunnelConfig;\n /** Arms the exact public origin before the launcher probes through it. */\n acceptOrigin?: (origin: string) => void;\n /** Cancels startup and asks the launcher to stop any process it has spawned. */\n signal?: AbortSignal;\n}\n\nexport type TunnelStartResult =\n | { ok: true; publicOrigin: string; stop: () => Promise<void> }\n | { ok: false; failure: TunnelFailure; message: string };\n\n/**\n * Starting a tunnel, as a port rather than a concrete process.\n *\n * Declared here so a test can inject a fake without pulling in the cloudflared\n * adapter, and so `WebServerOptions` can name the seam without reaching across\n * a layer to do it.\n */\nexport type TunnelLauncher = (input: TunnelStartInput) => Promise<TunnelStartResult>;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAEhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAExD,MAAa,oBAAoB,GAAG,iBAAiB;;;AAGrD,MAAa,wBAAwB,GAAG,iBAAiB;AACzD,MAAa,oBAAoB,GAAG,iBAAiB;AACrD,MAAa,wBAAwB,GAAG,kBAAkB;AAC1D,MAAa,wBAAwB;;AAErC,MAAa,qBAAqB;;AAElC,MAAa,sBAAsB;;AAEnC,MAAa,uBAAuB;;AAEpC,MAAa,uBAAuB;;AAEpC,MAAa,2BAA2B;AACxC,MAAa,gCAAgC;;;;;;AAO7C,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AACxC,MAAa,4BAA4B;AACzC,MAAa,+BAA+B;AAC5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAEnC,MAAa,yBAAyB;;AAEtC,MAAa,yBAAyB;;AA8HtC,MAAa,oBAAoB;;AAEjC,MAAa,8BAA8B"}
1
+ {"version":3,"file":"remoteAccess.mjs","names":[],"sources":["../../src/types/remoteAccess.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\n/** Trusted native devices; `DELETE ${TRUSTED_DEVICES_ROUTE}/:id` revokes one. Mirrors po-core's constant. */\nexport const TRUSTED_DEVICES_ROUTE = `${REMOTE_API_ROUTE}/trusted-devices`;\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\nexport const PROTOCOL_SOCKET_ROUTE = '/api/ws';\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n/**\n * The phone app's form of the same QR: its `po` scheme wrapping the encoded HTTPS pairing URL, so a\n * phone camera opens Po instead of the browser. The app unwraps it and checks the inner URL as usual.\n */\nexport const NATIVE_PAIRING_LINK_PREFIX = 'po://pair?link=';\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-po_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'po_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-po-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7534;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 30 * 24 * 60 * 60;\n\nexport type TunnelKind = 'quick' | 'named';\nexport type RemoteChannelScope = 'session' | 'protocol' | 'http';\n\n/**\n * How the tunnel is established.\n *\n * `named` carries a hostname rather than discovering one, which is why it is\n * the mode that supports passkeys, a service worker, and durable pairing: all\n * three are bound to the origin, and a quick tunnel's hostname rotates on every\n * start. The token is referenced by path, never stored inline, because it is a\n * Cloudflare account credential and an inlined one ends up in a bug report.\n */\nexport type TunnelConfig =\n | { kind: 'quick' }\n | { kind: 'named'; hostname: string; name?: string; tokenFile?: string; configFile?: string };\n\n/** Nullable numbers are not used: a disabled toggle keeps its value so flipping it back restores it. */\nexport interface RemoteAccessSettings {\n /** Close the tunnel on a timer. */\n autoCloseEnabled: boolean;\n autoCloseMinutes: number;\n /** Expire paired sessions on idle and on total age. */\n sessionExpiryEnabled: boolean;\n idleMinutes: number;\n absoluteHours: number;\n tunnel: TunnelConfig;\n}\n\nexport interface BundlePairingTrust {\n publicKey: string;\n fingerprint: string;\n revision: number;\n}\n\nexport type RemoteAccessStatus = 'off' | 'starting' | 'on' | 'failed';\n\nexport interface PairedDeviceView {\n id: string;\n label: string;\n userAgent: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True for the device making the request, so the UI can say \"this device\". */\n self: boolean;\n}\n\nexport type TrustedDevicePlatform = 'ios' | 'android';\n\n/** What local and remote callers see of a trusted native device: no keys. Mirrors po-core's view. */\nexport interface TrustedDeviceView {\n id: string;\n label: string;\n platform: TrustedDevicePlatform;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n lastSeenAt: string;\n /** True when the calling session signed in with this device's key. */\n self: boolean;\n}\n\nexport interface PairingRequestView {\n id: string;\n userAgent: string;\n /**\n * The address Cloudflare's edge reported. Display only: any local process can\n * reach the tunnel listener directly and set this header to anything, so it\n * never gates a decision. The UI labels it \"reported by the edge\".\n */\n edgeIp: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n expiresAt: string;\n}\n\nexport interface RemoteAccessStateView {\n status: RemoteAccessStatus;\n /** Present once the tunnel reports its hostname. */\n publicUrl?: string;\n /** ISO 8601, present while the tunnel is up. */\n startedAt?: string;\n /** ISO 8601, absent when auto-close is switched off. */\n closesAt?: string;\n /** Why the tunnel failed, once it has. */\n error?: string;\n devices: PairedDeviceView[];\n /** Populated for local callers only; a paired phone must not see approval prompts. */\n pending: PairingRequestView[];\n /** Shown to local and remote callers, so a paired device can revoke a lost one. */\n trustedDevices: TrustedDeviceView[];\n settings: RemoteAccessSettings;\n}\n\nexport type PairingStatus = 'pending' | 'approved' | 'denied' | 'expired' | 'consumed';\n\nexport type TunnelFailure = 'not_installed' | 'spawn_failed' | 'timeout' | 'self_test_failed' | 'exited';\n\nexport interface TunnelStartInput {\n /** The loopback port the tunnel should publish. */\n port: number;\n config: TunnelConfig;\n /** Arms the exact public origin before the launcher probes through it. */\n acceptOrigin?: (origin: string) => void;\n /** Cancels startup and asks the launcher to stop any process it has spawned. */\n signal?: AbortSignal;\n}\n\nexport type TunnelStartResult =\n | { ok: true; publicOrigin: string; stop: () => Promise<void> }\n | { ok: false; failure: TunnelFailure; message: string };\n\n/**\n * Starting a tunnel, as a port rather than a concrete process.\n *\n * Declared here so a test can inject a fake without pulling in the cloudflared\n * adapter, and so `WebServerOptions` can name the seam without reaching across\n * a layer to do it.\n */\nexport type TunnelLauncher = (input: TunnelStartInput) => Promise<TunnelStartResult>;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAEhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAExD,MAAa,oBAAoB,GAAG,iBAAiB;;;AAGrD,MAAa,wBAAwB,GAAG,iBAAiB;AACzD,MAAa,oBAAoB,GAAG,iBAAiB;AACrD,MAAa,wBAAwB,GAAG,kBAAkB;AAC1D,MAAa,wBAAwB;;AAErC,MAAa,qBAAqB;;;;;AAKlC,MAAa,6BAA6B;;AAE1C,MAAa,sBAAsB;;AAEnC,MAAa,uBAAuB;;AAEpC,MAAa,uBAAuB;;AAEpC,MAAa,2BAA2B;AACxC,MAAa,gCAAgC;;;;;;AAO7C,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AACxC,MAAa,4BAA4B;AACzC,MAAa,+BAA+B;AAC5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAEnC,MAAa,yBAAyB;;AAEtC,MAAa,yBAAyB;;AA8HtC,MAAa,oBAAoB;;AAEjC,MAAa,8BAA8B"}
package/dist/types.cjs CHANGED
@@ -35,6 +35,7 @@ exports.HUB_ROLE = require_hub.HUB_ROLE;
35
35
  exports.MAX_SESSION_FILE_BYTES = require_media.MAX_SESSION_FILE_BYTES;
36
36
  exports.MEDIA_TYPES = require_media.MEDIA_TYPES;
37
37
  exports.MINOR_MODE_ENTRY_TYPE = require_hub.MINOR_MODE_ENTRY_TYPE;
38
+ exports.NATIVE_PAIRING_LINK_PREFIX = require_remoteAccess.NATIVE_PAIRING_LINK_PREFIX;
38
39
  exports.PAIRING_CLAIM_ROUTE = require_remoteAccess.PAIRING_CLAIM_ROUTE;
39
40
  exports.PAIRING_CODE_TTL_MS = require_remoteAccess.PAIRING_CODE_TTL_MS;
40
41
  exports.PAIRING_PAGE_ROUTE = require_remoteAccess.PAIRING_PAGE_ROUTE;
package/dist/types.d.mts CHANGED
@@ -3,9 +3,9 @@ import { AGENT_MODEL_ENTRY_TYPE, API_SESSION_QUERY_PARAM, CONTEXT_ENTRY_TYPE, Co
3
3
  import { AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE, AuthMethodType, AuthRuntime, AuthRuntimeProvider, LoginEvent, LoginFlowSnapshot, LoginFlowStatus, LoginInteraction, LoginPrompt, LoginPromptView, LoginSelectOption, ProviderAuthMethod, ProviderAuthSummary } from "./types/auth.mjs";
4
4
  import { DEV_PROXY_API_ROUTE, DEV_PROXY_PREFIX, DEV_PROXY_TARGETS_ROUTE, DevProxyStateView, DevProxyTarget } from "./types/devProxy.mjs";
5
5
  import { MAX_SESSION_FILE_BYTES, MEDIA_TYPES, MediaKind, MediaType, SESSION_FILE_EXPECTED_SHA256_HEADER, SESSION_FILE_ROUTE, SESSION_FILE_SHA256_HEADER, sessionFileUrl } from "./types/media.mjs";
6
- import { BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, BundlePairingTrust, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PROTOCOL_SOCKET_ROUTE, PairedDeviceView, PairingRequestView, PairingStatus, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, RemoteAccessSettings, RemoteAccessStateView, RemoteAccessStatus, RemoteChannelScope, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, TrustedDevicePlatform, TrustedDeviceView, TunnelConfig, TunnelFailure, TunnelKind, TunnelLauncher, TunnelStartInput, TunnelStartResult, WEB_DEV_SERVER_PORT } from "./types/remoteAccess.mjs";
6
+ import { BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, BundlePairingTrust, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, NATIVE_PAIRING_LINK_PREFIX, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PROTOCOL_SOCKET_ROUTE, PairedDeviceView, PairingRequestView, PairingStatus, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, RemoteAccessSettings, RemoteAccessStateView, RemoteAccessStatus, RemoteChannelScope, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, TrustedDevicePlatform, TrustedDeviceView, TunnelConfig, TunnelFailure, TunnelKind, TunnelLauncher, TunnelStartInput, TunnelStartResult, WEB_DEV_SERVER_PORT } from "./types/remoteAccess.mjs";
7
7
  import { CreateSessionMcpClientInput, CreatedSessionMcpClient, SessionMcpClient, SessionMcpConfig, SessionMcpRouting, SessionMcpScope, SessionMcpSkill, SessionMcpTool } from "./types/sessionMcp.mjs";
8
8
  import { RepositoryCatalogOption, RepositorySelectionChanges, RepositorySelectionValue, RepositorySelectionWriteRequest, RepositorySettingsView, SETTINGS_CONFIG_API_ROUTE, SETTINGS_IMAGES_API_ROUTE, SETTINGS_MODELS_API_ROUTE, SETTINGS_REPOSITORIES_API_ROUTE, SETTINGS_REPOSITORY_API_ROUTE, SETTINGS_REPOSITORY_SELECTION_API_ROUTE, SETTINGS_VALUE_API_ROUTE, SettingsBatchWriteRequest, SettingsConfigView, SettingsErrorView, SettingsImagesView, SettingsImagesWriteRequest, SettingsKeyScope, SettingsModel, SettingsOrigin, SettingsRepository, SettingsScope, SettingsValueView, SettingsWriteRequest } from "./types/settings.mjs";
9
9
  import { WebServer, WebServerOptions } from "./types/bridge.mjs";
10
10
  import { PoNotificationEntry, parsePoNotificationEntry } from "./types/notification.mjs";
11
- export { AGENT_MODEL_ENTRY_TYPE, API_SESSION_QUERY_PARAM, ATTACHED_TYPE, ATTACH_ERROR_TYPE, ATTACH_TYPE, AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE, AuthMethodType, AuthRuntime, AuthRuntimeProvider, BRIDGE_STATUS_TYPE, BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, BridgeState, BridgeStatusFrame, BundlePairingTrust, CONTEXT_ENTRY_TYPE, COOKIE_CEILING_SECONDS, ContextGroupKind, ContextGroupProjection, ContextItemProjection, ContextItemSource, ContextProjection, CreateSessionMcpClientInput, CreatedSessionMcpClient, DEVICE_COOKIE, DEV_PROXY_API_ROUTE, DEV_PROXY_PREFIX, DEV_PROXY_TARGETS_ROUTE, DIALOG_ANSWERED_TYPE, DIRECTORIES_API_ROUTE, DIRECTORY_CHILDREN_API_ROUTE, DevProxyStateView, DevProxyTarget, DirectoryListing, HISTORY_PAGE_SIZE, HISTORY_PAGE_TYPE, HISTORY_REQUEST_TYPE, HUB_HELLO_TYPE, HUB_PROTOCOL_VERSION, HUB_ROLE, HistoryPageFrame, HistoryRequestFrame, HubHelloFrame, LoginEvent, LoginFlowSnapshot, LoginFlowStatus, LoginInteraction, LoginPrompt, LoginPromptView, LoginSelectOption, MAX_SESSION_FILE_BYTES, MEDIA_TYPES, MINOR_MODE_ENTRY_TYPE, MediaKind, MediaType, MinorModeActionProjection, MinorModeProjection, MinorModeRecordProjection, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PO_FOLDERS_API_ROUTE, PROFILE_IDENTITY_ENTRY_TYPE, PROTOCOL_SOCKET_ROUTE, PairedDeviceView, PairingRequestView, PairingStatus, PiSessionHistoryItem, PoNotificationEntry, ProviderAuthMethod, ProviderAuthSummary, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, REPLAY_TYPE, RESOURCE_CATALOG_ENTRY_TYPE, RemoteAccessSettings, RemoteAccessStateView, RemoteAccessStatus, RemoteChannelScope, RemovedSessionItem, RepositoryCatalogOption, RepositorySelectionChanges, RepositorySelectionValue, RepositorySelectionWriteRequest, RepositorySettingsView, SESSIONS_API_ROUTE, SESSION_COMMAND_TYPE, SESSION_FILE_EXPECTED_SHA256_HEADER, SESSION_FILE_ROUTE, SESSION_FILE_SHA256_HEADER, SETTINGS_CONFIG_API_ROUTE, SETTINGS_IMAGES_API_ROUTE, SETTINGS_MODELS_API_ROUTE, SETTINGS_REPOSITORIES_API_ROUTE, SETTINGS_REPOSITORY_API_ROUTE, SETTINGS_REPOSITORY_SELECTION_API_ROUTE, SETTINGS_VALUE_API_ROUTE, STEP_UP_HEADER, SessionCommandFrame, SessionFrame, SessionGitStatus, SessionMcpClient, SessionMcpConfig, SessionMcpRouting, SessionMcpScope, SessionMcpSkill, SessionMcpTool, SessionNativeComposition, SessionPhase, SessionProfile, SessionSummary, SessionWebComposition, SettingsBatchWriteRequest, SettingsConfigView, SettingsErrorView, SettingsImagesView, SettingsImagesWriteRequest, SettingsKeyScope, SettingsModel, SettingsOrigin, SettingsRepository, SettingsScope, SettingsValueView, SettingsWriteRequest, TRUSTED_DEVICES_ROUTE, TrustedDevicePlatform, TrustedDeviceView, TunnelConfig, TunnelFailure, TunnelKind, TunnelLauncher, TunnelStartInput, TunnelStartResult, WEB_DEV_SERVER_PORT, WORKSPACES_API_ROUTE, WORKSPACE_SANDBOX_STATES, WebServer, WebServerOptions, WorkspaceSandboxState, WorkspaceSummary, bridgeStatus, hubHello, parsePoNotificationEntry, sessionCommand, sessionFileUrl };
11
+ export { AGENT_MODEL_ENTRY_TYPE, API_SESSION_QUERY_PARAM, ATTACHED_TYPE, ATTACH_ERROR_TYPE, ATTACH_TYPE, AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE, AuthMethodType, AuthRuntime, AuthRuntimeProvider, BRIDGE_STATUS_TYPE, BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, BridgeState, BridgeStatusFrame, BundlePairingTrust, CONTEXT_ENTRY_TYPE, COOKIE_CEILING_SECONDS, ContextGroupKind, ContextGroupProjection, ContextItemProjection, ContextItemSource, ContextProjection, CreateSessionMcpClientInput, CreatedSessionMcpClient, DEVICE_COOKIE, DEV_PROXY_API_ROUTE, DEV_PROXY_PREFIX, DEV_PROXY_TARGETS_ROUTE, DIALOG_ANSWERED_TYPE, DIRECTORIES_API_ROUTE, DIRECTORY_CHILDREN_API_ROUTE, DevProxyStateView, DevProxyTarget, DirectoryListing, HISTORY_PAGE_SIZE, HISTORY_PAGE_TYPE, HISTORY_REQUEST_TYPE, HUB_HELLO_TYPE, HUB_PROTOCOL_VERSION, HUB_ROLE, HistoryPageFrame, HistoryRequestFrame, HubHelloFrame, LoginEvent, LoginFlowSnapshot, LoginFlowStatus, LoginInteraction, LoginPrompt, LoginPromptView, LoginSelectOption, MAX_SESSION_FILE_BYTES, MEDIA_TYPES, MINOR_MODE_ENTRY_TYPE, MediaKind, MediaType, MinorModeActionProjection, MinorModeProjection, MinorModeRecordProjection, NATIVE_PAIRING_LINK_PREFIX, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PO_FOLDERS_API_ROUTE, PROFILE_IDENTITY_ENTRY_TYPE, PROTOCOL_SOCKET_ROUTE, PairedDeviceView, PairingRequestView, PairingStatus, PiSessionHistoryItem, PoNotificationEntry, ProviderAuthMethod, ProviderAuthSummary, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, REPLAY_TYPE, RESOURCE_CATALOG_ENTRY_TYPE, RemoteAccessSettings, RemoteAccessStateView, RemoteAccessStatus, RemoteChannelScope, RemovedSessionItem, RepositoryCatalogOption, RepositorySelectionChanges, RepositorySelectionValue, RepositorySelectionWriteRequest, RepositorySettingsView, SESSIONS_API_ROUTE, SESSION_COMMAND_TYPE, SESSION_FILE_EXPECTED_SHA256_HEADER, SESSION_FILE_ROUTE, SESSION_FILE_SHA256_HEADER, SETTINGS_CONFIG_API_ROUTE, SETTINGS_IMAGES_API_ROUTE, SETTINGS_MODELS_API_ROUTE, SETTINGS_REPOSITORIES_API_ROUTE, SETTINGS_REPOSITORY_API_ROUTE, SETTINGS_REPOSITORY_SELECTION_API_ROUTE, SETTINGS_VALUE_API_ROUTE, STEP_UP_HEADER, SessionCommandFrame, SessionFrame, SessionGitStatus, SessionMcpClient, SessionMcpConfig, SessionMcpRouting, SessionMcpScope, SessionMcpSkill, SessionMcpTool, SessionNativeComposition, SessionPhase, SessionProfile, SessionSummary, SessionWebComposition, SettingsBatchWriteRequest, SettingsConfigView, SettingsErrorView, SettingsImagesView, SettingsImagesWriteRequest, SettingsKeyScope, SettingsModel, SettingsOrigin, SettingsRepository, SettingsScope, SettingsValueView, SettingsWriteRequest, TRUSTED_DEVICES_ROUTE, TrustedDevicePlatform, TrustedDeviceView, TunnelConfig, TunnelFailure, TunnelKind, TunnelLauncher, TunnelStartInput, TunnelStartResult, WEB_DEV_SERVER_PORT, WORKSPACES_API_ROUTE, WORKSPACE_SANDBOX_STATES, WebServer, WebServerOptions, WorkspaceSandboxState, WorkspaceSummary, bridgeStatus, hubHello, parsePoNotificationEntry, sessionCommand, sessionFileUrl };
package/dist/types.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE } from "./types/auth.mjs";
2
- import { BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PROTOCOL_SOCKET_ROUTE, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, WEB_DEV_SERVER_PORT } from "./types/remoteAccess.mjs";
2
+ import { BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, NATIVE_PAIRING_LINK_PREFIX, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PROTOCOL_SOCKET_ROUTE, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, WEB_DEV_SERVER_PORT } from "./types/remoteAccess.mjs";
3
3
  import { DEV_PROXY_API_ROUTE, DEV_PROXY_PREFIX, DEV_PROXY_TARGETS_ROUTE } from "./types/devProxy.mjs";
4
4
  import { MAX_SESSION_FILE_BYTES, MEDIA_TYPES, SESSION_FILE_EXPECTED_SHA256_HEADER, SESSION_FILE_ROUTE, SESSION_FILE_SHA256_HEADER, sessionFileUrl } from "./types/media.mjs";
5
5
  import { AGENT_MODEL_ENTRY_TYPE, API_SESSION_QUERY_PARAM, CONTEXT_ENTRY_TYPE, DIALOG_ANSWERED_TYPE, DIRECTORIES_API_ROUTE, DIRECTORY_CHILDREN_API_ROUTE, HISTORY_PAGE_SIZE, HISTORY_PAGE_TYPE, HISTORY_REQUEST_TYPE, HUB_HELLO_TYPE, HUB_PROTOCOL_VERSION, HUB_ROLE, MINOR_MODE_ENTRY_TYPE, PO_FOLDERS_API_ROUTE, PROFILE_IDENTITY_ENTRY_TYPE, RESOURCE_CATALOG_ENTRY_TYPE, SESSIONS_API_ROUTE, SESSION_COMMAND_TYPE, WORKSPACES_API_ROUTE, WORKSPACE_SANDBOX_STATES, hubHello, sessionCommand } from "./types/hub.mjs";
6
6
  import { parsePoNotificationEntry } from "./types/notification.mjs";
7
7
  import { SETTINGS_CONFIG_API_ROUTE, SETTINGS_IMAGES_API_ROUTE, SETTINGS_MODELS_API_ROUTE, SETTINGS_REPOSITORIES_API_ROUTE, SETTINGS_REPOSITORY_API_ROUTE, SETTINGS_REPOSITORY_SELECTION_API_ROUTE, SETTINGS_VALUE_API_ROUTE } from "./types/settings.mjs";
8
8
  import { ATTACHED_TYPE, ATTACH_ERROR_TYPE, ATTACH_TYPE, BRIDGE_STATUS_TYPE, REPLAY_TYPE, bridgeStatus } from "./types/session.mjs";
9
- export { AGENT_MODEL_ENTRY_TYPE, API_SESSION_QUERY_PARAM, ATTACHED_TYPE, ATTACH_ERROR_TYPE, ATTACH_TYPE, AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE, BRIDGE_STATUS_TYPE, BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, CONTEXT_ENTRY_TYPE, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, DEV_PROXY_API_ROUTE, DEV_PROXY_PREFIX, DEV_PROXY_TARGETS_ROUTE, DIALOG_ANSWERED_TYPE, DIRECTORIES_API_ROUTE, DIRECTORY_CHILDREN_API_ROUTE, HISTORY_PAGE_SIZE, HISTORY_PAGE_TYPE, HISTORY_REQUEST_TYPE, HUB_HELLO_TYPE, HUB_PROTOCOL_VERSION, HUB_ROLE, MAX_SESSION_FILE_BYTES, MEDIA_TYPES, MINOR_MODE_ENTRY_TYPE, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PO_FOLDERS_API_ROUTE, PROFILE_IDENTITY_ENTRY_TYPE, PROTOCOL_SOCKET_ROUTE, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, REPLAY_TYPE, RESOURCE_CATALOG_ENTRY_TYPE, SESSIONS_API_ROUTE, SESSION_COMMAND_TYPE, SESSION_FILE_EXPECTED_SHA256_HEADER, SESSION_FILE_ROUTE, SESSION_FILE_SHA256_HEADER, SETTINGS_CONFIG_API_ROUTE, SETTINGS_IMAGES_API_ROUTE, SETTINGS_MODELS_API_ROUTE, SETTINGS_REPOSITORIES_API_ROUTE, SETTINGS_REPOSITORY_API_ROUTE, SETTINGS_REPOSITORY_SELECTION_API_ROUTE, SETTINGS_VALUE_API_ROUTE, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, WEB_DEV_SERVER_PORT, WORKSPACES_API_ROUTE, WORKSPACE_SANDBOX_STATES, bridgeStatus, hubHello, parsePoNotificationEntry, sessionCommand, sessionFileUrl };
9
+ export { AGENT_MODEL_ENTRY_TYPE, API_SESSION_QUERY_PARAM, ATTACHED_TYPE, ATTACH_ERROR_TYPE, ATTACH_TYPE, AUTH_LOGINS_API_ROUTE, AUTH_PROVIDERS_API_ROUTE, BRIDGE_STATUS_TYPE, BUNDLE_MINIMUM_REVISION_PARAM, BUNDLE_SIGNING_KEY_PARAM, CONTEXT_ENTRY_TYPE, COOKIE_CEILING_SECONDS, DEVICE_COOKIE, DEV_PROXY_API_ROUTE, DEV_PROXY_PREFIX, DEV_PROXY_TARGETS_ROUTE, DIALOG_ANSWERED_TYPE, DIRECTORIES_API_ROUTE, DIRECTORY_CHILDREN_API_ROUTE, HISTORY_PAGE_SIZE, HISTORY_PAGE_TYPE, HISTORY_REQUEST_TYPE, HUB_HELLO_TYPE, HUB_PROTOCOL_VERSION, HUB_ROLE, MAX_SESSION_FILE_BYTES, MEDIA_TYPES, MINOR_MODE_ENTRY_TYPE, NATIVE_PAIRING_LINK_PREFIX, PAIRING_CLAIM_ROUTE, PAIRING_CODE_TTL_MS, PAIRING_PAGE_ROUTE, PAIRING_REQUEST_TTL_MS, PAIRING_STATUS_QUERY, PAIRING_STATUS_ROUTE, PASSKEY_AUTH_BEGIN_ROUTE, PASSKEY_AUTH_FINISH_ROUTE, PASSKEY_REGISTER_BEGIN_ROUTE, PASSKEY_REGISTER_FINISH_ROUTE, PO_FOLDERS_API_ROUTE, PROFILE_IDENTITY_ENTRY_TYPE, PROTOCOL_SOCKET_ROUTE, REMOTE_API_ROUTE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, REMOTE_PAIRING_REQUEST_TYPE, REMOTE_PUSH_KEY_ROUTE, REMOTE_PUSH_ROUTE, REMOTE_STATE_TYPE, REPLAY_TYPE, RESOURCE_CATALOG_ENTRY_TYPE, SESSIONS_API_ROUTE, SESSION_COMMAND_TYPE, SESSION_FILE_EXPECTED_SHA256_HEADER, SESSION_FILE_ROUTE, SESSION_FILE_SHA256_HEADER, SETTINGS_CONFIG_API_ROUTE, SETTINGS_IMAGES_API_ROUTE, SETTINGS_MODELS_API_ROUTE, SETTINGS_REPOSITORIES_API_ROUTE, SETTINGS_REPOSITORY_API_ROUTE, SETTINGS_REPOSITORY_SELECTION_API_ROUTE, SETTINGS_VALUE_API_ROUTE, STEP_UP_HEADER, TRUSTED_DEVICES_ROUTE, WEB_DEV_SERVER_PORT, WORKSPACES_API_ROUTE, WORKSPACE_SANDBOX_STATES, bridgeStatus, hubHello, parsePoNotificationEntry, sessionCommand, sessionFileUrl };
@@ -1,3 +1,4 @@
1
+ const require_remoteAccess = require("../../../types/remoteAccess.cjs");
1
2
  const require_remoteAccessStore = require("../../stores/remoteAccessStore.cjs");
2
3
  const require_QrCode = require("../../components/QrCode.cjs");
3
4
  const require_PairedDeviceList = require("./PairedDeviceList.cjs");
@@ -9,13 +10,35 @@ let _agimon_ai_po_web_components = require("@agimon-ai/po-web-components");
9
10
  function RemoteAccessPairing() {
10
11
  const state = (0, _tanstack_react_store.useStore)(require_remoteAccessStore.remoteAccessStore);
11
12
  if (state.pairUrl === void 0 || state.pairCode === void 0 || state.pairFingerprint === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.Spinner, {});
13
+ const target = state.pairTarget ?? "web";
14
+ const qrValue = target === "native" ? `${require_remoteAccess.NATIVE_PAIRING_LINK_PREFIX}${encodeURIComponent(state.pairUrl)}` : state.pairUrl;
12
15
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
13
16
  className: "flex flex-col items-center gap-4",
14
17
  children: [
18
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.Tabs, {
19
+ value: target,
20
+ onValueChange: (value) => require_remoteAccessStore.choosePairTarget(value),
21
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_agimon_ai_po_web_components.TabsList, {
22
+ "aria-label": "Pair with",
23
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.TabsTrigger, {
24
+ value: "web",
25
+ "data-testid": "remote-pair-target-web",
26
+ children: "Browser"
27
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.TabsTrigger, {
28
+ value: "native",
29
+ "data-testid": "remote-pair-target-native",
30
+ children: "Po app"
31
+ })]
32
+ })
33
+ }),
34
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
35
+ className: "text-center text-sm text-po-dim",
36
+ children: target === "native" ? "Scan with the phone camera to open the Po app, or scan it from inside the app." : "Scan with the phone camera to open Po in the phone browser."
37
+ }),
15
38
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
16
39
  className: "rounded-lg bg-po-hi p-3 text-po-deep",
17
40
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_QrCode.QrCode, {
18
- value: state.pairUrl,
41
+ value: qrValue,
19
42
  size: 288,
20
43
  label: "Scan to pair this device"
21
44
  })
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteAccessPairing.cjs","names":["useStore","remoteAccessStore","Spinner","QrCode","SectionLabel","Button","newPairingCode","addPasskey","PairedDeviceList"],"sources":["../../../../src/web/features/remote/RemoteAccessPairing.tsx"],"sourcesContent":["import { Button, SectionLabel, Spinner } from '@agimon-ai/po-web-components';\nimport { useStore } from '@tanstack/react-store';\n\nimport { QrCode } from '../../components/QrCode';\nimport { addPasskey, newPairingCode, remoteAccessStore } from '../../stores/remoteAccessStore';\nimport { PairedDeviceList } from './PairedDeviceList';\n\n/** The QR credential, a short manual code, and the direct address. */\nexport function RemoteAccessPairing() {\n const state = useStore(remoteAccessStore);\n if (state.pairUrl === undefined || state.pairCode === undefined || state.pairFingerprint === undefined)\n return <Spinner />;\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n {/* A light plate under the code: scanners want dark modules on a bright\n field, which is the one thing a dark theme cannot supply. */}\n <div className=\"rounded-lg bg-po-hi p-3 text-po-deep\">\n <QrCode value={state.pairUrl} size={288} label=\"Scan to pair this device\" />\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or enter this pairing code</SectionLabel>\n <code\n data-testid=\"remote-pair-code\"\n className=\"rounded bg-po-deep p-2 text-center font-mono text-lg tracking-widest text-po-text\"\n >\n {state.pairCode}\n </code>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>signing-key fingerprint</SectionLabel>\n <code\n data-testid=\"remote-signing-fingerprint\"\n className=\"break-all rounded bg-po-deep p-2 text-center font-mono text-sm text-po-text\"\n >\n {state.pairFingerprint.match(/.{1,8}/gu)?.join(' ') ?? state.pairFingerprint}\n </code>\n <p className=\"text-sm text-po-dim\">\n Compare this on the phone when entering the manual code. Bundle revision {String(state.pairRevision)}.\n </p>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or open this address</SectionLabel>\n <code data-testid=\"remote-pair-url\" className=\"break-all rounded bg-po-deep p-2 text-sm text-po-dim\">\n {state.pairUrl}\n </code>\n </div>\n <Button variant=\"ghost\" size=\"sm\" data-testid=\"remote-new-code\" onClick={() => void newPairingCode()}>\n Show a new code\n </Button>\n\n {/* Enrolling a passkey turns every later visit into one Face ID instead\n of another scan, and it is what puts a second factor in front of the\n two actions that escalate past driving the agent. */}\n {state.passkeys?.supported === true ? (\n <div className=\"flex w-full flex-col gap-1.5 border-t border-po-border pt-3\">\n <SectionLabel>passkeys</SectionLabel>\n <p className=\"text-sm text-po-dim\">\n {state.passkeys.count === 0\n ? 'Add one on the device you just paired and it will not need the code again.'\n : `${String(state.passkeys.count)} registered. A paired device signs in with a gesture instead of a code.`}\n </p>\n <Button variant=\"outline\" size=\"sm\" data-testid=\"remote-add-passkey\" onClick={() => void addPasskey()}>\n Add a passkey for this device\n </Button>\n </div>\n ) : state.passkeys?.reason === undefined ? null : (\n <p data-testid=\"remote-passkey-unavailable\" className=\"w-full text-sm text-po-dim\">\n {state.passkeys.reason}\n </p>\n )}\n\n <PairedDeviceList />\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,sBAAsB;CACpC,MAAM,SAAA,GAAQA,sBAAAA,SAAAA,CAASC,0BAAAA,iBAAiB;CACxC,IAAI,MAAM,YAAY,KAAA,KAAa,MAAM,aAAa,KAAA,KAAa,MAAM,oBAAoB,KAAA,GAC3F,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAACC,6BAAAA,SAAD,CAAU,CAAA;CAEnB,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GAGE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;IAAK,WAAU;IACb,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAACC,eAAAA,QAAD;KAAQ,OAAO,MAAM;KAAS,MAAM;KAAK,OAAM;IAA4B,CAAA;GACxE,CAAA;GACL,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,6BAAAA,cAAD,EAAA,UAAc,6BAAwC,CAAA,GACtD,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;KACE,eAAY;KACZ,WAAU;KAET,UAAA,MAAM;IACH,CAAA,CACH;;GACL,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,iBAAA,GAAA,kBAAA,IAAA,CAACA,6BAAAA,cAAD,EAAA,UAAc,0BAAqC,CAAA;KACnD,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;MACE,eAAY;MACZ,WAAU;MAET,UAAA,MAAM,gBAAgB,MAAM,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,MAAM;KACzD,CAAA;KACN,iBAAA,GAAA,kBAAA,KAAA,CAAC,KAAD;MAAG,WAAU;MAAb,UAAA;OAAmC;OACyC,OAAO,MAAM,YAAY;OAAE;MACpG;;IACA;;GACL,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACA,6BAAAA,cAAD,EAAA,UAAc,uBAAkC,CAAA,GAChD,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;KAAM,eAAY;KAAkB,WAAU;KAC3C,UAAA,MAAM;IACH,CAAA,CACH;;GACL,iBAAA,GAAA,kBAAA,IAAA,CAACC,6BAAAA,QAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAK,eAAY;IAAkB,eAAe,KAAKC,0BAAAA,eAAe;IAAG,UAAA;GAE9F,CAAA;GAKP,MAAM,UAAU,cAAc,OAC7B,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,iBAAA,GAAA,kBAAA,IAAA,CAACF,6BAAAA,cAAD,EAAA,UAAc,WAAsB,CAAA;KACpC,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;MAAG,WAAU;MACV,UAAA,MAAM,SAAS,UAAU,IACtB,+EACA,GAAG,OAAO,MAAM,SAAS,KAAK,EAAE;KACnC,CAAA;KACH,iBAAA,GAAA,kBAAA,IAAA,CAACC,6BAAAA,QAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAY;MAAqB,eAAe,KAAKE,0BAAAA,WAAW;MAAG,UAAA;KAE/F,CAAA;IACL;GACH,CAAA,IAAA,MAAM,UAAU,WAAW,KAAA,IAAY,OACzC,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;IAAG,eAAY;IAA6B,WAAU;IACnD,UAAA,MAAM,SAAS;GACf,CAAA;GAGL,iBAAA,GAAA,kBAAA,IAAA,CAACC,yBAAAA,kBAAD,CAAmB,CAAA;EAChB;;AAET"}
1
+ {"version":3,"file":"RemoteAccessPairing.cjs","names":["useStore","remoteAccessStore","Spinner","NATIVE_PAIRING_LINK_PREFIX","Tabs","choosePairTarget","TabsList","TabsTrigger","QrCode","SectionLabel","Button","newPairingCode","addPasskey","PairedDeviceList"],"sources":["../../../../src/web/features/remote/RemoteAccessPairing.tsx"],"sourcesContent":["import { Button, SectionLabel, Spinner, Tabs, TabsList, TabsTrigger } from '@agimon-ai/po-web-components';\nimport { useStore } from '@tanstack/react-store';\n\nimport { NATIVE_PAIRING_LINK_PREFIX } from '../../../types/remoteAccess';\nimport { QrCode } from '../../components/QrCode';\nimport {\n addPasskey,\n choosePairTarget,\n newPairingCode,\n type PairTarget,\n remoteAccessStore,\n} from '../../stores/remoteAccessStore';\nimport { PairedDeviceList } from './PairedDeviceList';\n\n/** The QR credential, a short manual code, and the direct address. */\nexport function RemoteAccessPairing() {\n const state = useStore(remoteAccessStore);\n if (state.pairUrl === undefined || state.pairCode === undefined || state.pairFingerprint === undefined)\n return <Spinner />;\n const target = state.pairTarget ?? 'web';\n // The app link wraps the same credential, so one code serves either; only its opener differs.\n const qrValue =\n target === 'native' ? `${NATIVE_PAIRING_LINK_PREFIX}${encodeURIComponent(state.pairUrl)}` : state.pairUrl;\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n <Tabs value={target} onValueChange={(value) => choosePairTarget(value as PairTarget)}>\n <TabsList aria-label=\"Pair with\">\n <TabsTrigger value=\"web\" data-testid=\"remote-pair-target-web\">\n Browser\n </TabsTrigger>\n <TabsTrigger value=\"native\" data-testid=\"remote-pair-target-native\">\n Po app\n </TabsTrigger>\n </TabsList>\n </Tabs>\n <p className=\"text-center text-sm text-po-dim\">\n {target === 'native'\n ? 'Scan with the phone camera to open the Po app, or scan it from inside the app.'\n : 'Scan with the phone camera to open Po in the phone browser.'}\n </p>\n {/* A light plate under the code: scanners want dark modules on a bright\n field, which is the one thing a dark theme cannot supply. */}\n <div className=\"rounded-lg bg-po-hi p-3 text-po-deep\">\n <QrCode value={qrValue} size={288} label=\"Scan to pair this device\" />\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or enter this pairing code</SectionLabel>\n <code\n data-testid=\"remote-pair-code\"\n className=\"rounded bg-po-deep p-2 text-center font-mono text-lg tracking-widest text-po-text\"\n >\n {state.pairCode}\n </code>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>signing-key fingerprint</SectionLabel>\n <code\n data-testid=\"remote-signing-fingerprint\"\n className=\"break-all rounded bg-po-deep p-2 text-center font-mono text-sm text-po-text\"\n >\n {state.pairFingerprint.match(/.{1,8}/gu)?.join(' ') ?? state.pairFingerprint}\n </code>\n <p className=\"text-sm text-po-dim\">\n Compare this on the phone when entering the manual code. Bundle revision {String(state.pairRevision)}.\n </p>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or open this address</SectionLabel>\n <code data-testid=\"remote-pair-url\" className=\"break-all rounded bg-po-deep p-2 text-sm text-po-dim\">\n {state.pairUrl}\n </code>\n </div>\n <Button variant=\"ghost\" size=\"sm\" data-testid=\"remote-new-code\" onClick={() => void newPairingCode()}>\n Show a new code\n </Button>\n\n {/* Enrolling a passkey turns every later visit into one Face ID instead\n of another scan, and it is what puts a second factor in front of the\n two actions that escalate past driving the agent. */}\n {state.passkeys?.supported === true ? (\n <div className=\"flex w-full flex-col gap-1.5 border-t border-po-border pt-3\">\n <SectionLabel>passkeys</SectionLabel>\n <p className=\"text-sm text-po-dim\">\n {state.passkeys.count === 0\n ? 'Add one on the device you just paired and it will not need the code again.'\n : `${String(state.passkeys.count)} registered. A paired device signs in with a gesture instead of a code.`}\n </p>\n <Button variant=\"outline\" size=\"sm\" data-testid=\"remote-add-passkey\" onClick={() => void addPasskey()}>\n Add a passkey for this device\n </Button>\n </div>\n ) : state.passkeys?.reason === undefined ? null : (\n <p data-testid=\"remote-passkey-unavailable\" className=\"w-full text-sm text-po-dim\">\n {state.passkeys.reason}\n </p>\n )}\n\n <PairedDeviceList />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteAccessPairing.d.mts","names":[],"sources":["../../../../src/web/features/remote/RemoteAccessPairing.tsx"],"mappings":";;wBAQgB,uCAAmB,IAAA"}
1
+ {"version":3,"file":"RemoteAccessPairing.d.mts","names":[],"sources":["../../../../src/web/features/remote/RemoteAccessPairing.tsx"],"mappings":";;wBAegB,uCAAmB,IAAA"}
@@ -1,21 +1,44 @@
1
- import { addPasskey, newPairingCode, remoteAccessStore } from "../../stores/remoteAccessStore.mjs";
1
+ import { NATIVE_PAIRING_LINK_PREFIX } from "../../../types/remoteAccess.mjs";
2
+ import { addPasskey, choosePairTarget, newPairingCode, remoteAccessStore } from "../../stores/remoteAccessStore.mjs";
2
3
  import { QrCode } from "../../components/QrCode.mjs";
3
4
  import { PairedDeviceList } from "./PairedDeviceList.mjs";
4
5
  import { useStore } from "@tanstack/react-store";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
- import { Button, SectionLabel, Spinner } from "@agimon-ai/po-web-components";
7
+ import { Button, SectionLabel, Spinner, Tabs, TabsList, TabsTrigger } from "@agimon-ai/po-web-components";
7
8
  //#region src/web/features/remote/RemoteAccessPairing.tsx
8
9
  /** The QR credential, a short manual code, and the direct address. */
9
10
  function RemoteAccessPairing() {
10
11
  const state = useStore(remoteAccessStore);
11
12
  if (state.pairUrl === void 0 || state.pairCode === void 0 || state.pairFingerprint === void 0) return /* @__PURE__ */ jsx(Spinner, {});
13
+ const target = state.pairTarget ?? "web";
14
+ const qrValue = target === "native" ? `${NATIVE_PAIRING_LINK_PREFIX}${encodeURIComponent(state.pairUrl)}` : state.pairUrl;
12
15
  return /* @__PURE__ */ jsxs("div", {
13
16
  className: "flex flex-col items-center gap-4",
14
17
  children: [
18
+ /* @__PURE__ */ jsx(Tabs, {
19
+ value: target,
20
+ onValueChange: (value) => choosePairTarget(value),
21
+ children: /* @__PURE__ */ jsxs(TabsList, {
22
+ "aria-label": "Pair with",
23
+ children: [/* @__PURE__ */ jsx(TabsTrigger, {
24
+ value: "web",
25
+ "data-testid": "remote-pair-target-web",
26
+ children: "Browser"
27
+ }), /* @__PURE__ */ jsx(TabsTrigger, {
28
+ value: "native",
29
+ "data-testid": "remote-pair-target-native",
30
+ children: "Po app"
31
+ })]
32
+ })
33
+ }),
34
+ /* @__PURE__ */ jsx("p", {
35
+ className: "text-center text-sm text-po-dim",
36
+ children: target === "native" ? "Scan with the phone camera to open the Po app, or scan it from inside the app." : "Scan with the phone camera to open Po in the phone browser."
37
+ }),
15
38
  /* @__PURE__ */ jsx("div", {
16
39
  className: "rounded-lg bg-po-hi p-3 text-po-deep",
17
40
  children: /* @__PURE__ */ jsx(QrCode, {
18
- value: state.pairUrl,
41
+ value: qrValue,
19
42
  size: 288,
20
43
  label: "Scan to pair this device"
21
44
  })
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteAccessPairing.mjs","names":[],"sources":["../../../../src/web/features/remote/RemoteAccessPairing.tsx"],"sourcesContent":["import { Button, SectionLabel, Spinner } from '@agimon-ai/po-web-components';\nimport { useStore } from '@tanstack/react-store';\n\nimport { QrCode } from '../../components/QrCode';\nimport { addPasskey, newPairingCode, remoteAccessStore } from '../../stores/remoteAccessStore';\nimport { PairedDeviceList } from './PairedDeviceList';\n\n/** The QR credential, a short manual code, and the direct address. */\nexport function RemoteAccessPairing() {\n const state = useStore(remoteAccessStore);\n if (state.pairUrl === undefined || state.pairCode === undefined || state.pairFingerprint === undefined)\n return <Spinner />;\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n {/* A light plate under the code: scanners want dark modules on a bright\n field, which is the one thing a dark theme cannot supply. */}\n <div className=\"rounded-lg bg-po-hi p-3 text-po-deep\">\n <QrCode value={state.pairUrl} size={288} label=\"Scan to pair this device\" />\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or enter this pairing code</SectionLabel>\n <code\n data-testid=\"remote-pair-code\"\n className=\"rounded bg-po-deep p-2 text-center font-mono text-lg tracking-widest text-po-text\"\n >\n {state.pairCode}\n </code>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>signing-key fingerprint</SectionLabel>\n <code\n data-testid=\"remote-signing-fingerprint\"\n className=\"break-all rounded bg-po-deep p-2 text-center font-mono text-sm text-po-text\"\n >\n {state.pairFingerprint.match(/.{1,8}/gu)?.join(' ') ?? state.pairFingerprint}\n </code>\n <p className=\"text-sm text-po-dim\">\n Compare this on the phone when entering the manual code. Bundle revision {String(state.pairRevision)}.\n </p>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or open this address</SectionLabel>\n <code data-testid=\"remote-pair-url\" className=\"break-all rounded bg-po-deep p-2 text-sm text-po-dim\">\n {state.pairUrl}\n </code>\n </div>\n <Button variant=\"ghost\" size=\"sm\" data-testid=\"remote-new-code\" onClick={() => void newPairingCode()}>\n Show a new code\n </Button>\n\n {/* Enrolling a passkey turns every later visit into one Face ID instead\n of another scan, and it is what puts a second factor in front of the\n two actions that escalate past driving the agent. */}\n {state.passkeys?.supported === true ? (\n <div className=\"flex w-full flex-col gap-1.5 border-t border-po-border pt-3\">\n <SectionLabel>passkeys</SectionLabel>\n <p className=\"text-sm text-po-dim\">\n {state.passkeys.count === 0\n ? 'Add one on the device you just paired and it will not need the code again.'\n : `${String(state.passkeys.count)} registered. A paired device signs in with a gesture instead of a code.`}\n </p>\n <Button variant=\"outline\" size=\"sm\" data-testid=\"remote-add-passkey\" onClick={() => void addPasskey()}>\n Add a passkey for this device\n </Button>\n </div>\n ) : state.passkeys?.reason === undefined ? null : (\n <p data-testid=\"remote-passkey-unavailable\" className=\"w-full text-sm text-po-dim\">\n {state.passkeys.reason}\n </p>\n )}\n\n <PairedDeviceList />\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,sBAAsB;CACpC,MAAM,QAAQ,SAAS,iBAAiB;CACxC,IAAI,MAAM,YAAY,KAAA,KAAa,MAAM,aAAa,KAAA,KAAa,MAAM,oBAAoB,KAAA,GAC3F,OAAO,oBAAC,SAAD,CAAU,CAAA;CAEnB,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GAGE,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,QAAD;KAAQ,OAAO,MAAM;KAAS,MAAM;KAAK,OAAM;IAA4B,CAAA;GACxE,CAAA;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,cAAD,EAAA,UAAc,6BAAwC,CAAA,GACtD,oBAAC,QAAD;KACE,eAAY;KACZ,WAAU;KAET,UAAA,MAAM;IACH,CAAA,CACH;;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,cAAD,EAAA,UAAc,0BAAqC,CAAA;KACnD,oBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MAET,UAAA,MAAM,gBAAgB,MAAM,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,MAAM;KACzD,CAAA;KACN,qBAAC,KAAD;MAAG,WAAU;MAAb,UAAA;OAAmC;OACyC,OAAO,MAAM,YAAY;OAAE;MACpG;;IACA;;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,cAAD,EAAA,UAAc,uBAAkC,CAAA,GAChD,oBAAC,QAAD;KAAM,eAAY;KAAkB,WAAU;KAC3C,UAAA,MAAM;IACH,CAAA,CACH;;GACL,oBAAC,QAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAK,eAAY;IAAkB,eAAe,KAAK,eAAe;IAAG,UAAA;GAE9F,CAAA;GAKP,MAAM,UAAU,cAAc,OAC7B,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,cAAD,EAAA,UAAc,WAAsB,CAAA;KACpC,oBAAC,KAAD;MAAG,WAAU;MACV,UAAA,MAAM,SAAS,UAAU,IACtB,+EACA,GAAG,OAAO,MAAM,SAAS,KAAK,EAAE;KACnC,CAAA;KACH,oBAAC,QAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAY;MAAqB,eAAe,KAAK,WAAW;MAAG,UAAA;KAE/F,CAAA;IACL;GACH,CAAA,IAAA,MAAM,UAAU,WAAW,KAAA,IAAY,OACzC,oBAAC,KAAD;IAAG,eAAY;IAA6B,WAAU;IACnD,UAAA,MAAM,SAAS;GACf,CAAA;GAGL,oBAAC,kBAAD,CAAmB,CAAA;EAChB;;AAET"}
1
+ {"version":3,"file":"RemoteAccessPairing.mjs","names":[],"sources":["../../../../src/web/features/remote/RemoteAccessPairing.tsx"],"sourcesContent":["import { Button, SectionLabel, Spinner, Tabs, TabsList, TabsTrigger } from '@agimon-ai/po-web-components';\nimport { useStore } from '@tanstack/react-store';\n\nimport { NATIVE_PAIRING_LINK_PREFIX } from '../../../types/remoteAccess';\nimport { QrCode } from '../../components/QrCode';\nimport {\n addPasskey,\n choosePairTarget,\n newPairingCode,\n type PairTarget,\n remoteAccessStore,\n} from '../../stores/remoteAccessStore';\nimport { PairedDeviceList } from './PairedDeviceList';\n\n/** The QR credential, a short manual code, and the direct address. */\nexport function RemoteAccessPairing() {\n const state = useStore(remoteAccessStore);\n if (state.pairUrl === undefined || state.pairCode === undefined || state.pairFingerprint === undefined)\n return <Spinner />;\n const target = state.pairTarget ?? 'web';\n // The app link wraps the same credential, so one code serves either; only its opener differs.\n const qrValue =\n target === 'native' ? `${NATIVE_PAIRING_LINK_PREFIX}${encodeURIComponent(state.pairUrl)}` : state.pairUrl;\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n <Tabs value={target} onValueChange={(value) => choosePairTarget(value as PairTarget)}>\n <TabsList aria-label=\"Pair with\">\n <TabsTrigger value=\"web\" data-testid=\"remote-pair-target-web\">\n Browser\n </TabsTrigger>\n <TabsTrigger value=\"native\" data-testid=\"remote-pair-target-native\">\n Po app\n </TabsTrigger>\n </TabsList>\n </Tabs>\n <p className=\"text-center text-sm text-po-dim\">\n {target === 'native'\n ? 'Scan with the phone camera to open the Po app, or scan it from inside the app.'\n : 'Scan with the phone camera to open Po in the phone browser.'}\n </p>\n {/* A light plate under the code: scanners want dark modules on a bright\n field, which is the one thing a dark theme cannot supply. */}\n <div className=\"rounded-lg bg-po-hi p-3 text-po-deep\">\n <QrCode value={qrValue} size={288} label=\"Scan to pair this device\" />\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or enter this pairing code</SectionLabel>\n <code\n data-testid=\"remote-pair-code\"\n className=\"rounded bg-po-deep p-2 text-center font-mono text-lg tracking-widest text-po-text\"\n >\n {state.pairCode}\n </code>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>signing-key fingerprint</SectionLabel>\n <code\n data-testid=\"remote-signing-fingerprint\"\n className=\"break-all rounded bg-po-deep p-2 text-center font-mono text-sm text-po-text\"\n >\n {state.pairFingerprint.match(/.{1,8}/gu)?.join(' ') ?? state.pairFingerprint}\n </code>\n <p className=\"text-sm text-po-dim\">\n Compare this on the phone when entering the manual code. Bundle revision {String(state.pairRevision)}.\n </p>\n </div>\n <div className=\"flex w-full flex-col gap-1\">\n <SectionLabel>or open this address</SectionLabel>\n <code data-testid=\"remote-pair-url\" className=\"break-all rounded bg-po-deep p-2 text-sm text-po-dim\">\n {state.pairUrl}\n </code>\n </div>\n <Button variant=\"ghost\" size=\"sm\" data-testid=\"remote-new-code\" onClick={() => void newPairingCode()}>\n Show a new code\n </Button>\n\n {/* Enrolling a passkey turns every later visit into one Face ID instead\n of another scan, and it is what puts a second factor in front of the\n two actions that escalate past driving the agent. */}\n {state.passkeys?.supported === true ? (\n <div className=\"flex w-full flex-col gap-1.5 border-t border-po-border pt-3\">\n <SectionLabel>passkeys</SectionLabel>\n <p className=\"text-sm text-po-dim\">\n {state.passkeys.count === 0\n ? 'Add one on the device you just paired and it will not need the code again.'\n : `${String(state.passkeys.count)} registered. A paired device signs in with a gesture instead of a code.`}\n </p>\n <Button variant=\"outline\" size=\"sm\" data-testid=\"remote-add-passkey\" onClick={() => void addPasskey()}>\n Add a passkey for this device\n </Button>\n </div>\n ) : state.passkeys?.reason === undefined ? null : (\n <p data-testid=\"remote-passkey-unavailable\" className=\"w-full text-sm text-po-dim\">\n {state.passkeys.reason}\n </p>\n )}\n\n <PairedDeviceList />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,sBAAsB;CACpC,MAAM,QAAQ,SAAS,iBAAiB;CACxC,IAAI,MAAM,YAAY,KAAA,KAAa,MAAM,aAAa,KAAA,KAAa,MAAM,oBAAoB,KAAA,GAC3F,OAAO,oBAAC,SAAD,CAAU,CAAA;CACnB,MAAM,SAAS,MAAM,cAAc;CAEnC,MAAM,UACJ,WAAW,WAAW,GAAG,6BAA6B,mBAAmB,MAAM,OAAO,MAAM,MAAM;CAEpG,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,oBAAC,MAAD;IAAM,OAAO;IAAQ,gBAAgB,UAAU,iBAAiB,KAAmB;IACjF,UAAA,qBAAC,UAAD;KAAU,cAAW;KAArB,UAAA,CACE,oBAAC,aAAD;MAAa,OAAM;MAAM,eAAY;MAAyB,UAAA;KAEjD,CAAA,GACb,oBAAC,aAAD;MAAa,OAAM;MAAS,eAAY;MAA4B,UAAA;KAEvD,CAAA,CACL;;GACN,CAAA;GACN,oBAAC,KAAD;IAAG,WAAU;IACV,UAAA,WAAW,WACR,mFACA;GACH,CAAA;GAGH,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,QAAD;KAAQ,OAAO;KAAS,MAAM;KAAK,OAAM;IAA4B,CAAA;GAClE,CAAA;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,cAAD,EAAA,UAAc,6BAAwC,CAAA,GACtD,oBAAC,QAAD;KACE,eAAY;KACZ,WAAU;KAET,UAAA,MAAM;IACH,CAAA,CACH;;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,cAAD,EAAA,UAAc,0BAAqC,CAAA;KACnD,oBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MAET,UAAA,MAAM,gBAAgB,MAAM,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,MAAM;KACzD,CAAA;KACN,qBAAC,KAAD;MAAG,WAAU;MAAb,UAAA;OAAmC;OACyC,OAAO,MAAM,YAAY;OAAE;MACpG;;IACA;;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,cAAD,EAAA,UAAc,uBAAkC,CAAA,GAChD,oBAAC,QAAD;KAAM,eAAY;KAAkB,WAAU;KAC3C,UAAA,MAAM;IACH,CAAA,CACH;;GACL,oBAAC,QAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAK,eAAY;IAAkB,eAAe,KAAK,eAAe;IAAG,UAAA;GAE9F,CAAA;GAKP,MAAM,UAAU,cAAc,OAC7B,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,cAAD,EAAA,UAAc,WAAsB,CAAA;KACpC,oBAAC,KAAD;MAAG,WAAU;MACV,UAAA,MAAM,SAAS,UAAU,IACtB,+EACA,GAAG,OAAO,MAAM,SAAS,KAAK,EAAE;KACnC,CAAA;KACH,oBAAC,QAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAY;MAAqB,eAAe,KAAK,WAAW;MAAG,UAAA;KAE/F,CAAA;IACL;GACH,CAAA,IAAA,MAAM,UAAU,WAAW,KAAA,IAAY,OACzC,oBAAC,KAAD;IAAG,eAAY;IAA6B,WAAU;IACnD,UAAA,MAAM,SAAS;GACf,CAAA;GAGL,oBAAC,kBAAD,CAAmB,CAAA;EAChB;;AAET"}
@@ -137,6 +137,16 @@ async function newPairingCode() {
137
137
  pairExpiresAt: minted.expiresAt
138
138
  });
139
139
  }
140
+ /**
141
+ * Switches the code between the browser link and the Po app link. Choosing the app asks the host
142
+ * for its native build, which it defers until a phone needs it, so it is ready by the time the
143
+ * app connects.
144
+ */
145
+ function choosePairTarget(target) {
146
+ set({ pairTarget: target });
147
+ if (target !== "native") return;
148
+ require_sealedSession.sealedHttpSession.fetch("/api/compositions?client=native", { credentials: "same-origin" }).catch((error) => console.warn("The Po app build could not be requested", error));
149
+ }
140
150
  async function approveDevice(requestId) {
141
151
  settle(await require_remoteApi.approvePairing(requestId));
142
152
  }
@@ -189,6 +199,7 @@ async function updateRemoteSettings(patch) {
189
199
  //#endregion
190
200
  exports.addPasskey = addPasskey;
191
201
  exports.approveDevice = approveDevice;
202
+ exports.choosePairTarget = choosePairTarget;
192
203
  exports.closeRemoteDialog = closeRemoteDialog;
193
204
  exports.denyDevice = denyDevice;
194
205
  exports.newPairingCode = newPairingCode;
@@ -1 +1 @@
1
- {"version":3,"file":"remoteAccessStore.cjs","names":["Store","sealedHttpSession","passkeysAvailable","registerPasskey","fetchRemoteState","enableRemoteAccess","disableRemoteAccess","mintPairingCode","approvePairing","denyPairing","revokeDevice","revokeTrustedDevice","saveRemoteSettings"],"sources":["../../../src/web/stores/remoteAccessStore.ts"],"sourcesContent":["import { Store } from '@tanstack/store';\n\nimport type { RemoteAccessSettings, RemoteAccessStateView } from '../../types/remoteAccess';\nimport {\n approvePairing,\n denyPairing,\n disableRemoteAccess,\n enableRemoteAccess,\n fetchRemoteState,\n mintPairingCode,\n revokeDevice,\n revokeTrustedDevice,\n saveRemoteSettings,\n} from '../lib/remoteApi';\nimport { sealedHttpSession } from '../lib/sealedSession';\nimport { passkeysAvailable, registerPasskey } from '../lib/webauthnClient';\n\n/** Which panel of the dialog is showing. Options first, then the code to scan. */\nexport type RemoteStep = 'closed' | 'options' | 'pairing';\n\nexport interface PasskeySupportView {\n supported: boolean;\n reason?: string;\n count: number;\n}\n\nexport interface RemoteAccessState {\n step: RemoteStep;\n /** Undefined until the passkey surface has been asked about. */\n passkeys?: PasskeySupportView;\n /** Undefined until the first load answers. */\n view?: RemoteAccessStateView;\n /** The URL the QR encodes; present only while a code is live. */\n pairUrl?: string;\n /** Short code accepted by the same pairing claim for manual entry. */\n pairCode?: string;\n /** Signing-key fingerprint the user compares during manual pairing. */\n pairFingerprint?: string;\n pairRevision?: number;\n pairExpiresAt?: string;\n busy: boolean;\n error?: string;\n}\n\nexport const remoteAccessStore = new Store<RemoteAccessState>({ step: 'closed', busy: false });\nconst pendingSettings = new Set<Partial<RemoteAccessSettings>>();\n\nfunction set(patch: Partial<RemoteAccessState>): void {\n const view = patch.view;\n if (view !== undefined && pendingSettings.size > 0)\n patch = {\n ...patch,\n view: {\n ...view,\n settings: [...pendingSettings].reduce<RemoteAccessSettings>(\n (settings, draft) => ({ ...settings, ...draft }),\n view.settings,\n ),\n },\n };\n remoteAccessStore.setState((state) => ({ ...state, ...patch }));\n}\n\n/** Clears the previous error before an action, so a stale one never outlives its cause. */\nfunction begin(): void {\n set({ busy: true, error: undefined });\n}\n\nfunction settle(outcome: { state: RemoteAccessStateView } | { error: string }): boolean {\n if ('error' in outcome) {\n set({ busy: false, error: outcome.error });\n return false;\n }\n set({ busy: false, view: outcome.state });\n return true;\n}\n\nexport function openRemoteDialog(): void {\n set({ step: 'options', error: undefined });\n void refreshRemoteState();\n}\n\nexport function closeRemoteDialog(): void {\n // The code is left to expire on its own rather than cancelled here: closing\n // the dialog while a phone is mid-scan should not strand it.\n set({ step: 'closed', error: undefined });\n}\n\nexport function showRemoteOptions(): void {\n set({ step: 'options', error: undefined });\n}\n\n/** Whether this tunnel and this browser can carry a passkey at all. */\nexport async function refreshPasskeys(): Promise<void> {\n try {\n const response = await sealedHttpSession.fetch('/api/remote/passkeys', { credentials: 'same-origin' });\n if (!response.ok) return;\n const body = (await response.json()) as {\n support: { supported: boolean; reason?: string };\n credentials: unknown[];\n };\n set({\n passkeys: {\n supported: body.support.supported && passkeysAvailable(),\n ...(body.support.reason === undefined ? {} : { reason: body.support.reason }),\n count: body.credentials.length,\n },\n });\n } catch (error) {\n // The hub is unreachable; the state refresh reports that to the person already.\n console.warn('Passkey support could not be read', error);\n }\n}\n\nexport async function addPasskey(): Promise<void> {\n begin();\n const outcome = await registerPasskey();\n set({ busy: false, ...(outcome.ok ? {} : { error: outcome.error }) });\n await refreshPasskeys();\n}\n\nexport async function refreshRemoteState(): Promise<void> {\n const outcome = await fetchRemoteState();\n if ('error' in outcome) {\n set({ error: outcome.error });\n return;\n }\n // An unchanged answer keeps the same view, so redundant refreshes do not re-render consumers.\n if (pendingSettings.size === 0 && JSON.stringify(remoteAccessStore.state.view) === JSON.stringify(outcome.state))\n return;\n set({ view: outcome.state });\n}\n\n/** Turns the tunnel on and moves straight to the code, which is what the user came for. */\nexport async function turnRemoteAccessOn(): Promise<void> {\n begin();\n const outcome = await enableRemoteAccess();\n if (!settle(outcome)) return;\n await Promise.all([newPairingCode(), refreshPasskeys()]);\n set({ step: 'pairing' });\n}\n\nexport async function turnRemoteAccessOff(): Promise<void> {\n begin();\n if (!settle(await disableRemoteAccess())) return;\n set({\n step: 'options',\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n pairExpiresAt: undefined,\n });\n}\n\nexport async function newPairingCode(): Promise<void> {\n const minted = await mintPairingCode();\n if ('error' in minted) {\n set({\n error: minted.error,\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n });\n return;\n }\n set({\n step: 'pairing',\n pairUrl: minted.pairUrl,\n pairCode: minted.manualCode,\n pairFingerprint: minted.fingerprint,\n pairRevision: minted.revision,\n pairExpiresAt: minted.expiresAt,\n });\n}\n\nexport async function approveDevice(requestId: string): Promise<void> {\n settle(await approvePairing(requestId));\n}\n\nexport async function denyDevice(requestId: string): Promise<void> {\n settle(await denyPairing(requestId));\n}\n\nexport async function revokePairedDevice(id: string): Promise<void> {\n begin();\n settle(await revokeDevice(id));\n}\n\nexport async function revokeTrustedNativeDevice(id: string): Promise<void> {\n begin();\n settle(await revokeTrustedDevice(id));\n}\n\nlet settingsWrites: Promise<unknown> = Promise.resolve();\n\nexport async function updateRemoteSettings(patch: Partial<RemoteAccessSettings>): Promise<boolean> {\n // Keep autosave edits responsive, but serialize writes across app replacements.\n const draft = { ...patch };\n pendingSettings.add(draft);\n const view = remoteAccessStore.state.view;\n if (view !== undefined) set({ view: { ...view, settings: { ...view.settings, ...draft } }, error: undefined });\n const write = () => saveRemoteSettings(draft);\n const pending = settingsWrites.then(write, write);\n settingsWrites = pending;\n const outcome = await pending;\n pendingSettings.delete(draft);\n if ('error' in outcome) {\n set({ error: outcome.error });\n if (settingsWrites === pending) void refreshRemoteState();\n return false;\n }\n const current = remoteAccessStore.state.view;\n if (current !== undefined && settingsWrites === pending)\n set({ view: { ...current, settings: outcome.settings }, error: undefined });\n return true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"remoteAccessStore.cjs","names":["Store","sealedHttpSession","passkeysAvailable","registerPasskey","fetchRemoteState","enableRemoteAccess","disableRemoteAccess","mintPairingCode","approvePairing","denyPairing","revokeDevice","revokeTrustedDevice","saveRemoteSettings"],"sources":["../../../src/web/stores/remoteAccessStore.ts"],"sourcesContent":["import { Store } from '@tanstack/store';\n\nimport type { RemoteAccessSettings, RemoteAccessStateView } from '../../types/remoteAccess';\nimport {\n approvePairing,\n denyPairing,\n disableRemoteAccess,\n enableRemoteAccess,\n fetchRemoteState,\n mintPairingCode,\n revokeDevice,\n revokeTrustedDevice,\n saveRemoteSettings,\n} from '../lib/remoteApi';\nimport { sealedHttpSession } from '../lib/sealedSession';\nimport { passkeysAvailable, registerPasskey } from '../lib/webauthnClient';\n\n/** Which panel of the dialog is showing. Options first, then the code to scan. */\nexport type RemoteStep = 'closed' | 'options' | 'pairing';\n/** What the code pairs: a phone browser, or the Po app through its app link. */\nexport type PairTarget = 'web' | 'native';\n\nexport interface PasskeySupportView {\n supported: boolean;\n reason?: string;\n count: number;\n}\n\nexport interface RemoteAccessState {\n step: RemoteStep;\n /** Undefined until the passkey surface has been asked about. */\n passkeys?: PasskeySupportView;\n /** Undefined until the first load answers. */\n view?: RemoteAccessStateView;\n /** The URL the QR encodes; present only while a code is live. */\n pairUrl?: string;\n /** Short code accepted by the same pairing claim for manual entry. */\n pairCode?: string;\n /** Signing-key fingerprint the user compares during manual pairing. */\n pairFingerprint?: string;\n pairRevision?: number;\n pairExpiresAt?: string;\n /** Undefined until the person picks one; the code shows the browser link meanwhile. */\n pairTarget?: PairTarget;\n busy: boolean;\n error?: string;\n}\n\nexport const remoteAccessStore = new Store<RemoteAccessState>({ step: 'closed', busy: false });\nconst pendingSettings = new Set<Partial<RemoteAccessSettings>>();\n\nfunction set(patch: Partial<RemoteAccessState>): void {\n const view = patch.view;\n if (view !== undefined && pendingSettings.size > 0)\n patch = {\n ...patch,\n view: {\n ...view,\n settings: [...pendingSettings].reduce<RemoteAccessSettings>(\n (settings, draft) => ({ ...settings, ...draft }),\n view.settings,\n ),\n },\n };\n remoteAccessStore.setState((state) => ({ ...state, ...patch }));\n}\n\n/** Clears the previous error before an action, so a stale one never outlives its cause. */\nfunction begin(): void {\n set({ busy: true, error: undefined });\n}\n\nfunction settle(outcome: { state: RemoteAccessStateView } | { error: string }): boolean {\n if ('error' in outcome) {\n set({ busy: false, error: outcome.error });\n return false;\n }\n set({ busy: false, view: outcome.state });\n return true;\n}\n\nexport function openRemoteDialog(): void {\n set({ step: 'options', error: undefined });\n void refreshRemoteState();\n}\n\nexport function closeRemoteDialog(): void {\n // The code is left to expire on its own rather than cancelled here: closing\n // the dialog while a phone is mid-scan should not strand it.\n set({ step: 'closed', error: undefined });\n}\n\nexport function showRemoteOptions(): void {\n set({ step: 'options', error: undefined });\n}\n\n/** Whether this tunnel and this browser can carry a passkey at all. */\nexport async function refreshPasskeys(): Promise<void> {\n try {\n const response = await sealedHttpSession.fetch('/api/remote/passkeys', { credentials: 'same-origin' });\n if (!response.ok) return;\n const body = (await response.json()) as {\n support: { supported: boolean; reason?: string };\n credentials: unknown[];\n };\n set({\n passkeys: {\n supported: body.support.supported && passkeysAvailable(),\n ...(body.support.reason === undefined ? {} : { reason: body.support.reason }),\n count: body.credentials.length,\n },\n });\n } catch (error) {\n // The hub is unreachable; the state refresh reports that to the person already.\n console.warn('Passkey support could not be read', error);\n }\n}\n\nexport async function addPasskey(): Promise<void> {\n begin();\n const outcome = await registerPasskey();\n set({ busy: false, ...(outcome.ok ? {} : { error: outcome.error }) });\n await refreshPasskeys();\n}\n\nexport async function refreshRemoteState(): Promise<void> {\n const outcome = await fetchRemoteState();\n if ('error' in outcome) {\n set({ error: outcome.error });\n return;\n }\n // An unchanged answer keeps the same view, so redundant refreshes do not re-render consumers.\n if (pendingSettings.size === 0 && JSON.stringify(remoteAccessStore.state.view) === JSON.stringify(outcome.state))\n return;\n set({ view: outcome.state });\n}\n\n/** Turns the tunnel on and moves straight to the code, which is what the user came for. */\nexport async function turnRemoteAccessOn(): Promise<void> {\n begin();\n const outcome = await enableRemoteAccess();\n if (!settle(outcome)) return;\n await Promise.all([newPairingCode(), refreshPasskeys()]);\n set({ step: 'pairing' });\n}\n\nexport async function turnRemoteAccessOff(): Promise<void> {\n begin();\n if (!settle(await disableRemoteAccess())) return;\n set({\n step: 'options',\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n pairExpiresAt: undefined,\n });\n}\n\nexport async function newPairingCode(): Promise<void> {\n const minted = await mintPairingCode();\n if ('error' in minted) {\n set({\n error: minted.error,\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n });\n return;\n }\n set({\n step: 'pairing',\n pairUrl: minted.pairUrl,\n pairCode: minted.manualCode,\n pairFingerprint: minted.fingerprint,\n pairRevision: minted.revision,\n pairExpiresAt: minted.expiresAt,\n });\n}\n\n/**\n * Switches the code between the browser link and the Po app link. Choosing the app asks the host\n * for its native build, which it defers until a phone needs it, so it is ready by the time the\n * app connects.\n */\nexport function choosePairTarget(target: PairTarget): void {\n set({ pairTarget: target });\n if (target !== 'native') return;\n void sealedHttpSession\n .fetch('/api/compositions?client=native', { credentials: 'same-origin' })\n .catch((error: unknown) => console.warn('The Po app build could not be requested', error));\n}\n\nexport async function approveDevice(requestId: string): Promise<void> {\n settle(await approvePairing(requestId));\n}\n\nexport async function denyDevice(requestId: string): Promise<void> {\n settle(await denyPairing(requestId));\n}\n\nexport async function revokePairedDevice(id: string): Promise<void> {\n begin();\n settle(await revokeDevice(id));\n}\n\nexport async function revokeTrustedNativeDevice(id: string): Promise<void> {\n begin();\n settle(await revokeTrustedDevice(id));\n}\n\nlet settingsWrites: Promise<unknown> = Promise.resolve();\n\nexport async function updateRemoteSettings(patch: Partial<RemoteAccessSettings>): Promise<boolean> {\n // Keep autosave edits responsive, but serialize writes across app replacements.\n const draft = { ...patch };\n pendingSettings.add(draft);\n const view = remoteAccessStore.state.view;\n if (view !== undefined) set({ view: { ...view, settings: { ...view.settings, ...draft } }, error: undefined });\n const write = () => saveRemoteSettings(draft);\n const pending = settingsWrites.then(write, write);\n settingsWrites = pending;\n const outcome = await pending;\n pendingSettings.delete(draft);\n if ('error' in outcome) {\n set({ error: outcome.error });\n if (settingsWrites === pending) void refreshRemoteState();\n return false;\n }\n const current = remoteAccessStore.state.view;\n if (current !== undefined && settingsWrites === pending)\n set({ view: { ...current, settings: outcome.settings }, error: undefined });\n return true;\n}\n"],"mappings":";;;;AAgDA,MAAa,oBAAoB,8BAAIA,EAAAA,CAAAA,MAAyB;CAAE,MAAM;CAAU,MAAM;AAAM,CAAC;AAC7F,MAAM,kCAAkB,IAAI,IAAmC;AAE/D,SAAS,IAAI,OAAyC;CACpD,MAAM,OAAO,MAAM;CACnB,IAAI,SAAS,KAAA,KAAa,gBAAgB,OAAO,GAC/C,QAAQ;EACN,GAAG;EACH,MAAM;GACJ,GAAG;GACH,UAAU,CAAC,GAAG,eAAe,CAAC,CAAC,QAC5B,UAAU,WAAW;IAAE,GAAG;IAAU,GAAG;GAAM,IAC9C,KAAK,QACP;EACF;CACF;CACF,kBAAkB,UAAU,WAAW;EAAE,GAAG;EAAO,GAAG;CAAM,EAAE;AAChE;;AAGA,SAAS,QAAc;CACrB,IAAI;EAAE,MAAM;EAAM,OAAO,KAAA;CAAU,CAAC;AACtC;AAEA,SAAS,OAAO,SAAwE;CACtF,IAAI,WAAW,SAAS;EACtB,IAAI;GAAE,MAAM;GAAO,OAAO,QAAQ;EAAM,CAAC;EACzC,OAAO;CACT;CACA,IAAI;EAAE,MAAM;EAAO,MAAM,QAAQ;CAAM,CAAC;CACxC,OAAO;AACT;AAEA,SAAgB,mBAAyB;CACvC,IAAI;EAAE,MAAM;EAAW,OAAO,KAAA;CAAU,CAAC;CACzC,mBAAwB;AAC1B;AAEA,SAAgB,oBAA0B;CAGxC,IAAI;EAAE,MAAM;EAAU,OAAO,KAAA;CAAU,CAAC;AAC1C;AAEA,SAAgB,oBAA0B;CACxC,IAAI;EAAE,MAAM;EAAW,OAAO,KAAA;CAAU,CAAC;AAC3C;;AAGA,eAAsB,kBAAiC;CACrD,IAAI;EACF,MAAM,WAAW,MAAMC,sBAAAA,kBAAkB,MAAM,wBAAwB,EAAE,aAAa,cAAc,CAAC;EACrG,IAAI,CAAC,SAAS,IAAI;EAClB,MAAM,OAAQ,MAAM,SAAS,KAAK;EAIlC,IAAI,EACF,UAAU;GACR,WAAW,KAAK,QAAQ,aAAaC,uBAAAA,kBAAkB;GACvD,GAAI,KAAK,QAAQ,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,OAAO;GAC3E,OAAO,KAAK,YAAY;EAC1B,EACF,CAAC;CACH,SAAS,OAAO;EAEd,QAAQ,KAAK,qCAAqC,KAAK;CACzD;AACF;AAEA,eAAsB,aAA4B;CAChD,MAAM;CACN,MAAM,UAAU,MAAMC,uBAAAA,gBAAgB;CACtC,IAAI;EAAE,MAAM;EAAO,GAAI,QAAQ,KAAK,CAAC,IAAI,EAAE,OAAO,QAAQ,MAAM;CAAG,CAAC;CACpE,MAAM,gBAAgB;AACxB;AAEA,eAAsB,qBAAoC;CACxD,MAAM,UAAU,MAAMC,kBAAAA,iBAAiB;CACvC,IAAI,WAAW,SAAS;EACtB,IAAI,EAAE,OAAO,QAAQ,MAAM,CAAC;EAC5B;CACF;CAEA,IAAI,gBAAgB,SAAS,KAAK,KAAK,UAAU,kBAAkB,MAAM,IAAI,MAAM,KAAK,UAAU,QAAQ,KAAK,GAC7G;CACF,IAAI,EAAE,MAAM,QAAQ,MAAM,CAAC;AAC7B;;AAGA,eAAsB,qBAAoC;CACxD,MAAM;CAEN,IAAI,CAAC,OAAO,MADUC,kBAAAA,mBAAmB,CACtB,GAAG;CACtB,MAAM,QAAQ,IAAI,CAAC,eAAe,GAAG,gBAAgB,CAAC,CAAC;CACvD,IAAI,EAAE,MAAM,UAAU,CAAC;AACzB;AAEA,eAAsB,sBAAqC;CACzD,MAAM;CACN,IAAI,CAAC,OAAO,MAAMC,kBAAAA,oBAAoB,CAAC,GAAG;CAC1C,IAAI;EACF,MAAM;EACN,SAAS,KAAA;EACT,UAAU,KAAA;EACV,iBAAiB,KAAA;EACjB,cAAc,KAAA;EACd,eAAe,KAAA;CACjB,CAAC;AACH;AAEA,eAAsB,iBAAgC;CACpD,MAAM,SAAS,MAAMC,kBAAAA,gBAAgB;CACrC,IAAI,WAAW,QAAQ;EACrB,IAAI;GACF,OAAO,OAAO;GACd,SAAS,KAAA;GACT,UAAU,KAAA;GACV,iBAAiB,KAAA;GACjB,cAAc,KAAA;EAChB,CAAC;EACD;CACF;CACA,IAAI;EACF,MAAM;EACN,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB,iBAAiB,OAAO;EACxB,cAAc,OAAO;EACrB,eAAe,OAAO;CACxB,CAAC;AACH;;;;;;AAOA,SAAgB,iBAAiB,QAA0B;CACzD,IAAI,EAAE,YAAY,OAAO,CAAC;CAC1B,IAAI,WAAW,UAAU;CACzB,sBAAKN,kBACF,MAAM,mCAAmC,EAAE,aAAa,cAAc,CAAC,CAAC,CACxE,OAAO,UAAmB,QAAQ,KAAK,2CAA2C,KAAK,CAAC;AAC7F;AAEA,eAAsB,cAAc,WAAkC;CACpE,OAAO,MAAMO,kBAAAA,eAAe,SAAS,CAAC;AACxC;AAEA,eAAsB,WAAW,WAAkC;CACjE,OAAO,MAAMC,kBAAAA,YAAY,SAAS,CAAC;AACrC;AAEA,eAAsB,mBAAmB,IAA2B;CAClE,MAAM;CACN,OAAO,MAAMC,kBAAAA,aAAa,EAAE,CAAC;AAC/B;AAEA,eAAsB,0BAA0B,IAA2B;CACzE,MAAM;CACN,OAAO,MAAMC,kBAAAA,oBAAoB,EAAE,CAAC;AACtC;AAEA,IAAI,iBAAmC,QAAQ,QAAQ;AAEvD,eAAsB,qBAAqB,OAAwD;CAEjG,MAAM,QAAQ,EAAE,GAAG,MAAM;CACzB,gBAAgB,IAAI,KAAK;CACzB,MAAM,OAAO,kBAAkB,MAAM;CACrC,IAAI,SAAS,KAAA,GAAW,IAAI;EAAE,MAAM;GAAE,GAAG;GAAM,UAAU;IAAE,GAAG,KAAK;IAAU,GAAG;GAAM;EAAE;EAAG,OAAO,KAAA;CAAU,CAAC;CAC7G,MAAM,cAAcC,kBAAAA,mBAAmB,KAAK;CAC5C,MAAM,UAAU,eAAe,KAAK,OAAO,KAAK;CAChD,iBAAiB;CACjB,MAAM,UAAU,MAAM;CACtB,gBAAgB,OAAO,KAAK;CAC5B,IAAI,WAAW,SAAS;EACtB,IAAI,EAAE,OAAO,QAAQ,MAAM,CAAC;EAC5B,IAAI,mBAAmB,SAAS,mBAAwB;EACxD,OAAO;CACT;CACA,MAAM,UAAU,kBAAkB,MAAM;CACxC,IAAI,YAAY,KAAA,KAAa,mBAAmB,SAC9C,IAAI;EAAE,MAAM;GAAE,GAAG;GAAS,UAAU,QAAQ;EAAS;EAAG,OAAO,KAAA;CAAU,CAAC;CAC5E,OAAO;AACT"}
@@ -3,6 +3,8 @@ import { Store } from "@tanstack/store";
3
3
  //#region src/web/stores/remoteAccessStore.d.ts
4
4
  /** Which panel of the dialog is showing. Options first, then the code to scan. */
5
5
  export type RemoteStep = 'closed' | 'options' | 'pairing';
6
+ /** What the code pairs: a phone browser, or the Po app through its app link. */
7
+ export type PairTarget = 'web' | 'native';
6
8
  export interface PasskeySupportView {
7
9
  supported: boolean;
8
10
  reason?: string;
@@ -22,6 +24,8 @@ export interface RemoteAccessState {
22
24
  pairFingerprint?: string;
23
25
  pairRevision?: number;
24
26
  pairExpiresAt?: string;
27
+ /** Undefined until the person picks one; the code shows the browser link meanwhile. */
28
+ pairTarget?: PairTarget;
25
29
  busy: boolean;
26
30
  error?: string;
27
31
  }
@@ -37,6 +41,12 @@ export declare function refreshRemoteState(): Promise<void>;
37
41
  export declare function turnRemoteAccessOn(): Promise<void>;
38
42
  export declare function turnRemoteAccessOff(): Promise<void>;
39
43
  export declare function newPairingCode(): Promise<void>;
44
+ /**
45
+ * Switches the code between the browser link and the Po app link. Choosing the app asks the host
46
+ * for its native build, which it defers until a phone needs it, so it is ready by the time the
47
+ * app connects.
48
+ */
49
+ export declare function choosePairTarget(target: PairTarget): void;
40
50
  export declare function approveDevice(requestId: string): Promise<void>;
41
51
  export declare function denyDevice(requestId: string): Promise<void>;
42
52
  export declare function revokePairedDevice(id: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"remoteAccessStore.d.mts","names":[],"sources":["../../../src/web/stores/remoteAccessStore.ts"],"mappings":";;;;YAkBY;iBAEK;EACf;EACA;EACA;;iBAGe;EACf,MAAM;;EAEN,WAAW;;EAEX,OAAO;;EAEP;;EAEA;;EAEA;EACA;EACA;EACA;EACA;;qBAGW,mBAAiB,MAAA;wBAiCd;wBAKA;wBAMA;;wBAKM,mBAAmB;wBAqBnB,cAAc;wBAOd,sBAAsB;;wBAatB,sBAAsB;wBAQtB,uBAAuB;wBAavB,kBAAkB;wBAsBlB,cAAc,oBAAoB;wBAIlC,WAAW,oBAAoB;wBAI/B,mBAAmB,aAAa;wBAKhC,0BAA0B,aAAa;wBAOvC,qBAAqB,OAAO,QAAQ,wBAAwB"}
1
+ {"version":3,"file":"remoteAccessStore.d.mts","names":[],"sources":["../../../src/web/stores/remoteAccessStore.ts"],"mappings":";;;;YAkBY;;YAEA;iBAEK;EACf;EACA;EACA;;iBAGe;EACf,MAAM;;EAEN,WAAW;;EAEX,OAAO;;EAEP;;EAEA;;EAEA;EACA;EACA;;EAEA,aAAa;EACb;EACA;;qBAGW,mBAAiB,MAAA;wBAiCd;wBAKA;wBAMA;;wBAKM,mBAAmB;wBAqBnB,cAAc;wBAOd,sBAAsB;;wBAatB,sBAAsB;wBAQtB,uBAAuB;wBAavB,kBAAkB;;;;;;wBA2BxB,iBAAiB,QAAQ;wBAQnB,cAAc,oBAAoB;wBAIlC,WAAW,oBAAoB;wBAI/B,mBAAmB,aAAa;wBAKhC,0BAA0B,aAAa;wBAOvC,qBAAqB,OAAO,QAAQ,wBAAwB"}
@@ -138,6 +138,16 @@ async function newPairingCode() {
138
138
  pairExpiresAt: minted.expiresAt
139
139
  });
140
140
  }
141
+ /**
142
+ * Switches the code between the browser link and the Po app link. Choosing the app asks the host
143
+ * for its native build, which it defers until a phone needs it, so it is ready by the time the
144
+ * app connects.
145
+ */
146
+ function choosePairTarget(target) {
147
+ set({ pairTarget: target });
148
+ if (target !== "native") return;
149
+ sealedHttpSession.fetch("/api/compositions?client=native", { credentials: "same-origin" }).catch((error) => console.warn("The Po app build could not be requested", error));
150
+ }
141
151
  async function approveDevice(requestId) {
142
152
  settle(await approvePairing(requestId));
143
153
  }
@@ -188,6 +198,6 @@ async function updateRemoteSettings(patch) {
188
198
  return true;
189
199
  }
190
200
  //#endregion
191
- export { addPasskey, approveDevice, closeRemoteDialog, denyDevice, newPairingCode, openRemoteDialog, refreshPasskeys, refreshRemoteState, remoteAccessStore, revokePairedDevice, revokeTrustedNativeDevice, showRemoteOptions, turnRemoteAccessOff, turnRemoteAccessOn, updateRemoteSettings };
201
+ export { addPasskey, approveDevice, choosePairTarget, closeRemoteDialog, denyDevice, newPairingCode, openRemoteDialog, refreshPasskeys, refreshRemoteState, remoteAccessStore, revokePairedDevice, revokeTrustedNativeDevice, showRemoteOptions, turnRemoteAccessOff, turnRemoteAccessOn, updateRemoteSettings };
192
202
 
193
203
  //# sourceMappingURL=remoteAccessStore.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"remoteAccessStore.mjs","names":[],"sources":["../../../src/web/stores/remoteAccessStore.ts"],"sourcesContent":["import { Store } from '@tanstack/store';\n\nimport type { RemoteAccessSettings, RemoteAccessStateView } from '../../types/remoteAccess';\nimport {\n approvePairing,\n denyPairing,\n disableRemoteAccess,\n enableRemoteAccess,\n fetchRemoteState,\n mintPairingCode,\n revokeDevice,\n revokeTrustedDevice,\n saveRemoteSettings,\n} from '../lib/remoteApi';\nimport { sealedHttpSession } from '../lib/sealedSession';\nimport { passkeysAvailable, registerPasskey } from '../lib/webauthnClient';\n\n/** Which panel of the dialog is showing. Options first, then the code to scan. */\nexport type RemoteStep = 'closed' | 'options' | 'pairing';\n\nexport interface PasskeySupportView {\n supported: boolean;\n reason?: string;\n count: number;\n}\n\nexport interface RemoteAccessState {\n step: RemoteStep;\n /** Undefined until the passkey surface has been asked about. */\n passkeys?: PasskeySupportView;\n /** Undefined until the first load answers. */\n view?: RemoteAccessStateView;\n /** The URL the QR encodes; present only while a code is live. */\n pairUrl?: string;\n /** Short code accepted by the same pairing claim for manual entry. */\n pairCode?: string;\n /** Signing-key fingerprint the user compares during manual pairing. */\n pairFingerprint?: string;\n pairRevision?: number;\n pairExpiresAt?: string;\n busy: boolean;\n error?: string;\n}\n\nexport const remoteAccessStore = new Store<RemoteAccessState>({ step: 'closed', busy: false });\nconst pendingSettings = new Set<Partial<RemoteAccessSettings>>();\n\nfunction set(patch: Partial<RemoteAccessState>): void {\n const view = patch.view;\n if (view !== undefined && pendingSettings.size > 0)\n patch = {\n ...patch,\n view: {\n ...view,\n settings: [...pendingSettings].reduce<RemoteAccessSettings>(\n (settings, draft) => ({ ...settings, ...draft }),\n view.settings,\n ),\n },\n };\n remoteAccessStore.setState((state) => ({ ...state, ...patch }));\n}\n\n/** Clears the previous error before an action, so a stale one never outlives its cause. */\nfunction begin(): void {\n set({ busy: true, error: undefined });\n}\n\nfunction settle(outcome: { state: RemoteAccessStateView } | { error: string }): boolean {\n if ('error' in outcome) {\n set({ busy: false, error: outcome.error });\n return false;\n }\n set({ busy: false, view: outcome.state });\n return true;\n}\n\nexport function openRemoteDialog(): void {\n set({ step: 'options', error: undefined });\n void refreshRemoteState();\n}\n\nexport function closeRemoteDialog(): void {\n // The code is left to expire on its own rather than cancelled here: closing\n // the dialog while a phone is mid-scan should not strand it.\n set({ step: 'closed', error: undefined });\n}\n\nexport function showRemoteOptions(): void {\n set({ step: 'options', error: undefined });\n}\n\n/** Whether this tunnel and this browser can carry a passkey at all. */\nexport async function refreshPasskeys(): Promise<void> {\n try {\n const response = await sealedHttpSession.fetch('/api/remote/passkeys', { credentials: 'same-origin' });\n if (!response.ok) return;\n const body = (await response.json()) as {\n support: { supported: boolean; reason?: string };\n credentials: unknown[];\n };\n set({\n passkeys: {\n supported: body.support.supported && passkeysAvailable(),\n ...(body.support.reason === undefined ? {} : { reason: body.support.reason }),\n count: body.credentials.length,\n },\n });\n } catch (error) {\n // The hub is unreachable; the state refresh reports that to the person already.\n console.warn('Passkey support could not be read', error);\n }\n}\n\nexport async function addPasskey(): Promise<void> {\n begin();\n const outcome = await registerPasskey();\n set({ busy: false, ...(outcome.ok ? {} : { error: outcome.error }) });\n await refreshPasskeys();\n}\n\nexport async function refreshRemoteState(): Promise<void> {\n const outcome = await fetchRemoteState();\n if ('error' in outcome) {\n set({ error: outcome.error });\n return;\n }\n // An unchanged answer keeps the same view, so redundant refreshes do not re-render consumers.\n if (pendingSettings.size === 0 && JSON.stringify(remoteAccessStore.state.view) === JSON.stringify(outcome.state))\n return;\n set({ view: outcome.state });\n}\n\n/** Turns the tunnel on and moves straight to the code, which is what the user came for. */\nexport async function turnRemoteAccessOn(): Promise<void> {\n begin();\n const outcome = await enableRemoteAccess();\n if (!settle(outcome)) return;\n await Promise.all([newPairingCode(), refreshPasskeys()]);\n set({ step: 'pairing' });\n}\n\nexport async function turnRemoteAccessOff(): Promise<void> {\n begin();\n if (!settle(await disableRemoteAccess())) return;\n set({\n step: 'options',\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n pairExpiresAt: undefined,\n });\n}\n\nexport async function newPairingCode(): Promise<void> {\n const minted = await mintPairingCode();\n if ('error' in minted) {\n set({\n error: minted.error,\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n });\n return;\n }\n set({\n step: 'pairing',\n pairUrl: minted.pairUrl,\n pairCode: minted.manualCode,\n pairFingerprint: minted.fingerprint,\n pairRevision: minted.revision,\n pairExpiresAt: minted.expiresAt,\n });\n}\n\nexport async function approveDevice(requestId: string): Promise<void> {\n settle(await approvePairing(requestId));\n}\n\nexport async function denyDevice(requestId: string): Promise<void> {\n settle(await denyPairing(requestId));\n}\n\nexport async function revokePairedDevice(id: string): Promise<void> {\n begin();\n settle(await revokeDevice(id));\n}\n\nexport async function revokeTrustedNativeDevice(id: string): Promise<void> {\n begin();\n settle(await revokeTrustedDevice(id));\n}\n\nlet settingsWrites: Promise<unknown> = Promise.resolve();\n\nexport async function updateRemoteSettings(patch: Partial<RemoteAccessSettings>): Promise<boolean> {\n // Keep autosave edits responsive, but serialize writes across app replacements.\n const draft = { ...patch };\n pendingSettings.add(draft);\n const view = remoteAccessStore.state.view;\n if (view !== undefined) set({ view: { ...view, settings: { ...view.settings, ...draft } }, error: undefined });\n const write = () => saveRemoteSettings(draft);\n const pending = settingsWrites.then(write, write);\n settingsWrites = pending;\n const outcome = await pending;\n pendingSettings.delete(draft);\n if ('error' in outcome) {\n set({ error: outcome.error });\n if (settingsWrites === pending) void refreshRemoteState();\n return false;\n }\n const current = remoteAccessStore.state.view;\n if (current !== undefined && settingsWrites === pending)\n set({ view: { ...current, settings: outcome.settings }, error: undefined });\n return true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"remoteAccessStore.mjs","names":[],"sources":["../../../src/web/stores/remoteAccessStore.ts"],"sourcesContent":["import { Store } from '@tanstack/store';\n\nimport type { RemoteAccessSettings, RemoteAccessStateView } from '../../types/remoteAccess';\nimport {\n approvePairing,\n denyPairing,\n disableRemoteAccess,\n enableRemoteAccess,\n fetchRemoteState,\n mintPairingCode,\n revokeDevice,\n revokeTrustedDevice,\n saveRemoteSettings,\n} from '../lib/remoteApi';\nimport { sealedHttpSession } from '../lib/sealedSession';\nimport { passkeysAvailable, registerPasskey } from '../lib/webauthnClient';\n\n/** Which panel of the dialog is showing. Options first, then the code to scan. */\nexport type RemoteStep = 'closed' | 'options' | 'pairing';\n/** What the code pairs: a phone browser, or the Po app through its app link. */\nexport type PairTarget = 'web' | 'native';\n\nexport interface PasskeySupportView {\n supported: boolean;\n reason?: string;\n count: number;\n}\n\nexport interface RemoteAccessState {\n step: RemoteStep;\n /** Undefined until the passkey surface has been asked about. */\n passkeys?: PasskeySupportView;\n /** Undefined until the first load answers. */\n view?: RemoteAccessStateView;\n /** The URL the QR encodes; present only while a code is live. */\n pairUrl?: string;\n /** Short code accepted by the same pairing claim for manual entry. */\n pairCode?: string;\n /** Signing-key fingerprint the user compares during manual pairing. */\n pairFingerprint?: string;\n pairRevision?: number;\n pairExpiresAt?: string;\n /** Undefined until the person picks one; the code shows the browser link meanwhile. */\n pairTarget?: PairTarget;\n busy: boolean;\n error?: string;\n}\n\nexport const remoteAccessStore = new Store<RemoteAccessState>({ step: 'closed', busy: false });\nconst pendingSettings = new Set<Partial<RemoteAccessSettings>>();\n\nfunction set(patch: Partial<RemoteAccessState>): void {\n const view = patch.view;\n if (view !== undefined && pendingSettings.size > 0)\n patch = {\n ...patch,\n view: {\n ...view,\n settings: [...pendingSettings].reduce<RemoteAccessSettings>(\n (settings, draft) => ({ ...settings, ...draft }),\n view.settings,\n ),\n },\n };\n remoteAccessStore.setState((state) => ({ ...state, ...patch }));\n}\n\n/** Clears the previous error before an action, so a stale one never outlives its cause. */\nfunction begin(): void {\n set({ busy: true, error: undefined });\n}\n\nfunction settle(outcome: { state: RemoteAccessStateView } | { error: string }): boolean {\n if ('error' in outcome) {\n set({ busy: false, error: outcome.error });\n return false;\n }\n set({ busy: false, view: outcome.state });\n return true;\n}\n\nexport function openRemoteDialog(): void {\n set({ step: 'options', error: undefined });\n void refreshRemoteState();\n}\n\nexport function closeRemoteDialog(): void {\n // The code is left to expire on its own rather than cancelled here: closing\n // the dialog while a phone is mid-scan should not strand it.\n set({ step: 'closed', error: undefined });\n}\n\nexport function showRemoteOptions(): void {\n set({ step: 'options', error: undefined });\n}\n\n/** Whether this tunnel and this browser can carry a passkey at all. */\nexport async function refreshPasskeys(): Promise<void> {\n try {\n const response = await sealedHttpSession.fetch('/api/remote/passkeys', { credentials: 'same-origin' });\n if (!response.ok) return;\n const body = (await response.json()) as {\n support: { supported: boolean; reason?: string };\n credentials: unknown[];\n };\n set({\n passkeys: {\n supported: body.support.supported && passkeysAvailable(),\n ...(body.support.reason === undefined ? {} : { reason: body.support.reason }),\n count: body.credentials.length,\n },\n });\n } catch (error) {\n // The hub is unreachable; the state refresh reports that to the person already.\n console.warn('Passkey support could not be read', error);\n }\n}\n\nexport async function addPasskey(): Promise<void> {\n begin();\n const outcome = await registerPasskey();\n set({ busy: false, ...(outcome.ok ? {} : { error: outcome.error }) });\n await refreshPasskeys();\n}\n\nexport async function refreshRemoteState(): Promise<void> {\n const outcome = await fetchRemoteState();\n if ('error' in outcome) {\n set({ error: outcome.error });\n return;\n }\n // An unchanged answer keeps the same view, so redundant refreshes do not re-render consumers.\n if (pendingSettings.size === 0 && JSON.stringify(remoteAccessStore.state.view) === JSON.stringify(outcome.state))\n return;\n set({ view: outcome.state });\n}\n\n/** Turns the tunnel on and moves straight to the code, which is what the user came for. */\nexport async function turnRemoteAccessOn(): Promise<void> {\n begin();\n const outcome = await enableRemoteAccess();\n if (!settle(outcome)) return;\n await Promise.all([newPairingCode(), refreshPasskeys()]);\n set({ step: 'pairing' });\n}\n\nexport async function turnRemoteAccessOff(): Promise<void> {\n begin();\n if (!settle(await disableRemoteAccess())) return;\n set({\n step: 'options',\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n pairExpiresAt: undefined,\n });\n}\n\nexport async function newPairingCode(): Promise<void> {\n const minted = await mintPairingCode();\n if ('error' in minted) {\n set({\n error: minted.error,\n pairUrl: undefined,\n pairCode: undefined,\n pairFingerprint: undefined,\n pairRevision: undefined,\n });\n return;\n }\n set({\n step: 'pairing',\n pairUrl: minted.pairUrl,\n pairCode: minted.manualCode,\n pairFingerprint: minted.fingerprint,\n pairRevision: minted.revision,\n pairExpiresAt: minted.expiresAt,\n });\n}\n\n/**\n * Switches the code between the browser link and the Po app link. Choosing the app asks the host\n * for its native build, which it defers until a phone needs it, so it is ready by the time the\n * app connects.\n */\nexport function choosePairTarget(target: PairTarget): void {\n set({ pairTarget: target });\n if (target !== 'native') return;\n void sealedHttpSession\n .fetch('/api/compositions?client=native', { credentials: 'same-origin' })\n .catch((error: unknown) => console.warn('The Po app build could not be requested', error));\n}\n\nexport async function approveDevice(requestId: string): Promise<void> {\n settle(await approvePairing(requestId));\n}\n\nexport async function denyDevice(requestId: string): Promise<void> {\n settle(await denyPairing(requestId));\n}\n\nexport async function revokePairedDevice(id: string): Promise<void> {\n begin();\n settle(await revokeDevice(id));\n}\n\nexport async function revokeTrustedNativeDevice(id: string): Promise<void> {\n begin();\n settle(await revokeTrustedDevice(id));\n}\n\nlet settingsWrites: Promise<unknown> = Promise.resolve();\n\nexport async function updateRemoteSettings(patch: Partial<RemoteAccessSettings>): Promise<boolean> {\n // Keep autosave edits responsive, but serialize writes across app replacements.\n const draft = { ...patch };\n pendingSettings.add(draft);\n const view = remoteAccessStore.state.view;\n if (view !== undefined) set({ view: { ...view, settings: { ...view.settings, ...draft } }, error: undefined });\n const write = () => saveRemoteSettings(draft);\n const pending = settingsWrites.then(write, write);\n settingsWrites = pending;\n const outcome = await pending;\n pendingSettings.delete(draft);\n if ('error' in outcome) {\n set({ error: outcome.error });\n if (settingsWrites === pending) void refreshRemoteState();\n return false;\n }\n const current = remoteAccessStore.state.view;\n if (current !== undefined && settingsWrites === pending)\n set({ view: { ...current, settings: outcome.settings }, error: undefined });\n return true;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-web-dashboard",
3
- "version": "0.0.0-alpha.63",
3
+ "version": "0.0.0-alpha.64",
4
4
  "description": "Shared browser state, actions and optional views for Po dashboard templates.",
5
5
  "keywords": [
6
6
  "ai",
@@ -61,9 +61,9 @@
61
61
  "access": "public"
62
62
  },
63
63
  "dependencies": {
64
- "@agimon-ai/po-core": "0.0.0-alpha.63",
65
- "@agimon-ai/po-web-components": "0.0.0-alpha.63",
66
- "@agimon-ai/po-web-security": "0.0.0-alpha.63",
64
+ "@agimon-ai/po-core": "0.0.0-alpha.64",
65
+ "@agimon-ai/po-web-components": "0.0.0-alpha.64",
66
+ "@agimon-ai/po-web-security": "0.0.0-alpha.64",
67
67
  "@earendil-works/chord": "1.4.0",
68
68
  "@simplewebauthn/browser": "^14.0.0",
69
69
  "@tanstack/react-router": "1.170.45",
@@ -73,7 +73,7 @@
73
73
  "qrcode-generator": "^2.0.4"
74
74
  },
75
75
  "devDependencies": {
76
- "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.63",
76
+ "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.64",
77
77
  "@types/node": "26.6.5",
78
78
  "@types/react": "19.3.0",
79
79
  "@types/react-dom": "19.3.0",