cbe9e5b294
* chore(desktop): literal comments in the guide script and runbooks Comment-only change to onboarding-script.ts and setup-profile.ts. The module headers now state the purpose and the constraints that shaped each file. The notes beside the runbook strings keep one fact per sentence, or are deleted when the string beside them says the same thing. The runbook text, the persona, the option pills and the SOUL text are unchanged. Both versions transpile to identical output with comments removed. * chore(desktop): literal comments in the guide chat cards and stores Comment-only change to the guided chat's cards, directive dispatcher, option catalog, assembly module and chip. Metaphor and personification are replaced by the name of the atom, effect or CSS property they stood for. Comments that restate the code are deleted. Two stale facts are corrected in place: the mini layout trees point at app/contrib/layout-presets.ts, and the skip button sets the onboarding phase to skipped rather than done. One comment line in cards/frame.tsx from bb/connector-ui-e2e-v2 loses a metaphor and an em dash; its fact is unchanged. * chore(desktop): literal comments in the handoff and first build Comment-only change to the handoff wiring, the kickoff, the receipt store, the first-build check-ins, the handoff tour, the connector rows and the machine profile store. Every kept comment names the caller, the constraint or the defect it prevents. The claim that the tour never throws is removed: the function can reject and its caller does not catch. Five comment blocks in connector-tool.tsx written on bb/connector-ui-e2e-v2 lose personification, dramatic capitals and em dashes. Every fact in them stays, and no block moves. * chore(desktop): literal comments in the intro reveal Comment-only change to the intro reveal's clock, timeline, cube renderer, sound, scenes, store and README. Animation comments now name the actual ramp, easing or offset with its number. Four comments that contradicted the code are corrected: the first texture slot opens at 3700 ms, the tear settles from 1 to 0 over 460 ms, the typing weight delays the character it sits on, and INTRO_EXIT_MS is wall time in index.tsx but score time in the overlay. * chore(desktop): literal comments in the Electron onboarding windows Comment-only change to the window growth geometry and the two onboarding windows. The 768 px floor keeps its one fact: the floor uses Math.ceil where the deltas round, because rounding 906.24 DIP down leaves the media query false. The comment that placed the CSS-pixel to DIP conversion at getBounds now points at growWindowBounds, where it happens. * chore(gateway): literal docstrings in the onboarding RPCs and the tour tool Docstring and comment-only change. The module summaries state what each module does and where authorization comes from, without contrast pairs. The tool descriptions the model reads are unchanged. Two words in the tour tool's module docstring lose personification; the rest of that docstring is as it was. ast.dump of both versions, with docstrings stripped, is identical for all three files. * chore(desktop): literal punctuation in the relaunch and film-end notes Comment-only change to four lines that bb/connector-ui-e2e-v2 added to the boot gate, the gate store and the intro gate. Each em dash becomes a colon, a full stop or a pair of parentheses; one emphasis capital is lowercased. The facts in the notes are unchanged.
200 lines
6.9 KiB
TypeScript
200 lines
6.9 KiB
TypeScript
import { useCallback } from 'react'
|
|
|
|
import type { useSessionActions } from '@/app/session/hooks/use-session-actions'
|
|
import type { SessionCreateOverrides } from '@/app/session/hooks/use-session-actions/create-overrides'
|
|
import {
|
|
$chatOnboardingThreadIds,
|
|
endChatOnboardingSolo,
|
|
pickOnboardingGreeting,
|
|
takeGuideShape
|
|
} from '@/components/onboarding-chat/assembly'
|
|
import {
|
|
$setupSession,
|
|
ensureSetupProfile,
|
|
guideSourceConnectionId,
|
|
SETUP_CHAT_TITLE,
|
|
SETUP_PROFILE
|
|
} from '@/components/onboarding-chat/setup-profile'
|
|
import { isOnboardingEnabled } from '@/lib/onboarding-enabled'
|
|
import { activeGatewayConnectionId, requestGatewayForProfile } from '@/store/gateway'
|
|
import { loadMachineProfile } from '@/store/machine'
|
|
import { notify } from '@/store/notifications'
|
|
import { skipGuide } from '@/store/onboarding-gate'
|
|
import { buildChatOnboardingSeedMessages } from '@/store/onboarding-script'
|
|
import {
|
|
$activeGatewayProfile,
|
|
$newChatProfile,
|
|
$newChatRoute,
|
|
ensureGatewayAgent,
|
|
ensureGatewayProfile
|
|
} from '@/store/profile'
|
|
import { $activeSessionId, $selectedStoredSessionId } from '@/store/session'
|
|
|
|
import type { AmbientGatewayRequest } from './session-rpc-dispatcher'
|
|
|
|
export interface OnboardingKickoffOptions extends Pick<
|
|
ReturnType<typeof useSessionActions>,
|
|
'createBackendSessionForSend' | 'resumeSession'
|
|
> {
|
|
requestGateway: AmbientGatewayRequest
|
|
/** The caller's own requestGateway reads the pin. */
|
|
runCreatePinnedTo: <T>(profile: string, create: () => Promise<T>) => Promise<T>
|
|
}
|
|
|
|
interface SetupStatus {
|
|
ready?: boolean
|
|
provider_configured?: boolean
|
|
free_tier?: boolean
|
|
}
|
|
|
|
interface GuideSession {
|
|
id: string
|
|
resolved_id?: string
|
|
}
|
|
|
|
async function adoptGuideSession(
|
|
canonical: GuideSession,
|
|
freeTier: SetupStatus['free_tier'],
|
|
resumeSession: OnboardingKickoffOptions['resumeSession'],
|
|
guideRequest: AmbientGatewayRequest
|
|
): Promise<void> {
|
|
await resumeSession(canonical.resolved_id ?? canonical.id, true)
|
|
const adoptedRuntimeId = $activeSessionId.get()
|
|
$chatOnboardingThreadIds.set(adoptedRuntimeId ? [canonical.id, adoptedRuntimeId] : [canonical.id])
|
|
$setupSession.set({
|
|
connectionId: guideSourceConnectionId(canonical.id),
|
|
profile: SETUP_PROFILE,
|
|
runtimeId: adoptedRuntimeId ?? canonical.id,
|
|
storedId: canonical.id
|
|
})
|
|
|
|
if (freeTier) {
|
|
await guideRequest('config.set', {
|
|
session_id: adoptedRuntimeId ?? canonical.id,
|
|
key: 'reasoning',
|
|
value: 'minimal'
|
|
})
|
|
}
|
|
}
|
|
|
|
/** Seeds the runbook and a pre-written greeting on hermes-setup before the phase advances.
|
|
* The seeded assistant row shows the chat's first message without a model turn. */
|
|
export function useOnboardingKickoff({
|
|
createBackendSessionForSend,
|
|
requestGateway,
|
|
resumeSession,
|
|
runCreatePinnedTo
|
|
}: OnboardingKickoffOptions) {
|
|
return useCallback(async (): Promise<boolean> => {
|
|
if (!isOnboardingEnabled()) {
|
|
return false
|
|
}
|
|
|
|
const previousNewChatProfile = $newChatProfile.get()
|
|
const previousNewChatRoute = $newChatRoute.get()
|
|
const previousProfile = $activeGatewayProfile.get()
|
|
const previousConnectionId = activeGatewayConnectionId()
|
|
const previousSetupSession = $setupSession.get()
|
|
const previousThreadIds = $chatOnboardingThreadIds.get()
|
|
let swapped = false
|
|
|
|
try {
|
|
await ensureSetupProfile(requestGateway)
|
|
|
|
// Probe the guide's own socket before switching profiles so a refusal
|
|
// leaves classic onboarding on the user's current backend.
|
|
const record = await requestGatewayForProfile<SetupStatus>(SETUP_PROFILE, 'setup.status', {})
|
|
|
|
if (record.ready !== true || record.provider_configured !== true) {
|
|
return false
|
|
}
|
|
|
|
swapped = true
|
|
$newChatRoute.set(null)
|
|
$newChatProfile.set(SETUP_PROFILE)
|
|
await ensureGatewayProfile(SETUP_PROFILE)
|
|
|
|
// Idempotent: the gate already took the shape on the tick the guide was
|
|
// owed, so no full-size shell painted during the profile round trips.
|
|
takeGuideShape()
|
|
await loadMachineProfile()
|
|
|
|
const seedMessages = buildChatOnboardingSeedMessages(pickOnboardingGreeting(), record.free_tier !== true)
|
|
|
|
const guideRequest: AmbientGatewayRequest = (method, params, timeout) =>
|
|
requestGatewayForProfile(SETUP_PROFILE, method, params, timeout)
|
|
|
|
// Look the guide up by its exact title: a relaunch adopts the existing guide session before creating
|
|
// one, so the backend's UNIQUE(title) constraint cannot leave an untitled duplicate behind.
|
|
const registryHit = await guideRequest<{ sessions?: GuideSession[] }>('session.list', {
|
|
include_hidden: true,
|
|
title: SETUP_CHAT_TITLE
|
|
})
|
|
|
|
const canonical = registryHit?.sessions?.[0]
|
|
|
|
if (canonical?.id) {
|
|
await adoptGuideSession(canonical, record.free_tier, resumeSession, guideRequest)
|
|
|
|
// runGuideKickoff records the guided phase only after adoption.
|
|
return true
|
|
}
|
|
|
|
const createOverrides: SessionCreateOverrides = { title: SETUP_CHAT_TITLE }
|
|
|
|
if (record.free_tier) {
|
|
createOverrides.reasoningEffort = 'minimal'
|
|
}
|
|
|
|
const runtimeId = await runCreatePinnedTo(SETUP_PROFILE, () =>
|
|
createBackendSessionForSend(null, seedMessages, createOverrides)
|
|
)
|
|
|
|
if (!runtimeId) {
|
|
throw new Error('The welcome chat could not be created. Please try again.')
|
|
}
|
|
|
|
const storedId = $selectedStoredSessionId.get()
|
|
$chatOnboardingThreadIds.set(storedId ? [storedId, runtimeId] : [runtimeId])
|
|
$setupSession.set({
|
|
connectionId: guideSourceConnectionId(storedId),
|
|
profile: SETUP_PROFILE,
|
|
runtimeId,
|
|
storedId
|
|
})
|
|
|
|
// Set the title explicitly so the backend does not name the session after the hidden runbook message.
|
|
await guideRequest('session.title', { session_id: runtimeId, title: SETUP_CHAT_TITLE }).catch(() => undefined)
|
|
|
|
// session.create has already persisted both seed rows, so the caller may advance the phase.
|
|
return true
|
|
} catch (error) {
|
|
$newChatProfile.set(previousNewChatProfile)
|
|
$newChatRoute.set(previousNewChatRoute)
|
|
$setupSession.set(previousSetupSession)
|
|
$chatOnboardingThreadIds.set(previousThreadIds)
|
|
endChatOnboardingSolo()
|
|
skipGuide()
|
|
|
|
if (swapped) {
|
|
await (
|
|
previousConnectionId
|
|
? ensureGatewayAgent(previousConnectionId, previousProfile)
|
|
: ensureGatewayProfile(previousProfile)
|
|
).catch(restoreError => {
|
|
notify({ kind: 'error', title: 'Could not restore your profile', message: String(restoreError) })
|
|
})
|
|
}
|
|
|
|
console.error('[setup] welcome chat could not start', error)
|
|
notify({
|
|
kind: 'error',
|
|
title: 'Welcome chat needs attention',
|
|
message: error instanceof Error ? error.message : 'The welcome chat could not start.'
|
|
})
|
|
|
|
return false
|
|
}
|
|
}, [createBackendSessionForSend, requestGateway, resumeSession, runCreatePinnedTo])
|
|
}
|