{"version":3,"file":"voiceReloadHandoff.cjs","names":["Type","Errors","Check"],"sources":["../../src/schemas/voiceReloadHandoff.ts"],"sourcesContent":["import { type Static, type TSchema, Type } from 'typebox';\nimport { Check, Errors } from 'typebox/value';\nimport {\n  VOICE_TOOL_MAX_DOMAIN_COUNT,\n  VOICE_TOOL_MAX_IDENTIFIER_LENGTH,\n  type VoiceToolSessionHandle,\n} from './voiceTools.ts';\n\n/** The sole exact global allowed for Voice continuity across module reload. */\nexport const VOICE_RELOAD_HANDOFF_REGISTRY_KEY =\n  '@agimon-ai/doompi-extension-contracts.voice-reload-handoff.v1' as const;\nexport const VOICE_RELOAD_HANDOFF_TTL_MS = 30_000;\n\nconst REGISTRY_SYMBOL = Symbol.for(VOICE_RELOAD_HANDOFF_REGISTRY_KEY);\nconst MAX_SESSION_ID_LENGTH = 256;\nconst MAX_TOKEN_LENGTH = 512;\n\nexport const VoiceReloadHandoffKindSchema = Type.Union([\n  Type.Literal('domain-switch'),\n  Type.Literal('major-mode-switch'),\n  Type.Literal('profile-switch'),\n]);\nexport type VoiceReloadHandoffKind = Static<typeof VoiceReloadHandoffKindSchema>;\n\nexport const VoiceReloadHandoffIdentitySchema = Type.Object(\n  {\n    sessionId: Type.String({ minLength: 1, maxLength: MAX_SESSION_ID_LENGTH }),\n    hostGeneration: Type.String({ minLength: 1, maxLength: MAX_SESSION_ID_LENGTH }),\n  },\n  { additionalProperties: false },\n);\nexport type VoiceReloadHandoffIdentity = Static<typeof VoiceReloadHandoffIdentitySchema>;\n\nexport const VoiceReloadHandoffRequestSchema = Type.Object(\n  {\n    operationId: Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH }),\n    kind: Type.Optional(VoiceReloadHandoffKindSchema),\n    domains: Type.Optional(\n      Type.Array(Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH }), {\n        maxItems: VOICE_TOOL_MAX_DOMAIN_COUNT,\n      }),\n    ),\n    majorMode: Type.Optional(Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH })),\n    profile: Type.Optional(Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH })),\n  },\n  { additionalProperties: false },\n);\nexport interface VoiceReloadHandoffRequest {\n  readonly operationId: string;\n  readonly kind?: VoiceReloadHandoffKind;\n  readonly domains?: readonly string[];\n  readonly majorMode?: string;\n  readonly profile?: string;\n}\n\nexport const VoiceReloadHandoffSchema = Type.Object(\n  {\n    token: Type.String({ minLength: 1, maxLength: MAX_TOKEN_LENGTH }),\n    sessionId: Type.String({ minLength: 1, maxLength: MAX_SESSION_ID_LENGTH }),\n    hostGeneration: Type.String({ minLength: 1, maxLength: MAX_SESSION_ID_LENGTH }),\n    operationId: Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH }),\n    kind: VoiceReloadHandoffKindSchema,\n    domains: Type.Array(Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH }), {\n      maxItems: VOICE_TOOL_MAX_DOMAIN_COUNT,\n    }),\n    majorMode: Type.Optional(Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH })),\n    profile: Type.Optional(Type.String({ minLength: 1, maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH })),\n    createdAt: Type.Integer({ minimum: 0 }),\n    expiresAt: Type.Integer({ minimum: 1 }),\n  },\n  { additionalProperties: false },\n);\nexport type VoiceReloadHandoff = Static<typeof VoiceReloadHandoffSchema>;\n\nexport interface VoiceReloadHandoffHandle {\n  readonly token: string;\n  commit(): boolean;\n  discard(): boolean;\n}\n\nexport interface VoiceReloadHandoffRuntime {\n  now(): number;\n  createToken(): string;\n}\n\nexport interface VoiceReloadHandoffStore {\n  prepare(\n    session: Pick<VoiceToolSessionHandle, 'active' | 'hostGeneration' | 'sessionId'>,\n    request: VoiceReloadHandoffRequest,\n  ): VoiceReloadHandoffHandle;\n  accept(token: string, identity: VoiceReloadHandoffIdentity): VoiceReloadHandoff | undefined;\n  commit(token: string, identity: VoiceReloadHandoffIdentity): boolean;\n  discard(token: string, identity: VoiceReloadHandoffIdentity): boolean;\n  /** The replacement generation consumes a committed record by stable session id. */\n  consume(sessionId: string, token?: string): VoiceReloadHandoff | undefined;\n}\n\nexport class VoiceReloadHandoffError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'VoiceReloadHandoffError';\n  }\n}\n\ninterface PendingHandoff {\n  readonly record: VoiceReloadHandoff;\n  state: 'pending' | 'committed';\n}\n\ninterface VoiceReloadHandoffRegistry {\n  readonly records: Map<string, PendingHandoff>;\n}\n\ninterface VoiceReloadHandoffGlobal {\n  [REGISTRY_SYMBOL]?: VoiceReloadHandoffRegistry;\n}\n\nfunction registry(): VoiceReloadHandoffRegistry {\n  const globalRegistry = globalThis as VoiceReloadHandoffGlobal;\n  const existing = globalRegistry[REGISTRY_SYMBOL];\n  if (existing) return existing;\n  const created = { records: new Map<string, PendingHandoff>() };\n  globalRegistry[REGISTRY_SYMBOL] = created;\n  return created;\n}\n\nfunction validationError(label: string, schema: TSchema, value: unknown): VoiceReloadHandoffError {\n  const [first] = Errors(schema, value);\n  return new VoiceReloadHandoffError(\n    `Invalid ${label}${first ? ` at ${first.instancePath || '/'}: ${first.message}` : ''}.`,\n  );\n}\n\nfunction validateIdentity(identity: VoiceReloadHandoffIdentity): VoiceReloadHandoffIdentity {\n  if (!Check(VoiceReloadHandoffIdentitySchema, identity)) {\n    throw validationError('voice reload handoff identity', VoiceReloadHandoffIdentitySchema, identity);\n  }\n  return { sessionId: identity.sessionId, hostGeneration: identity.hostGeneration };\n}\n\nfunction validNow(runtime: VoiceReloadHandoffRuntime): number {\n  const now = runtime.now();\n  if (!Number.isInteger(now) || now < 0) throw new VoiceReloadHandoffError('Voice reload handoff clock is invalid.');\n  return now;\n}\n\nfunction cloneRecord(record: VoiceReloadHandoff): VoiceReloadHandoff {\n  return {\n    ...record,\n    domains: [...record.domains],\n  };\n}\n\nfunction pruneExpired(now: number): void {\n  for (const [token, pending] of registry().records) {\n    if (pending.record.expiresAt <= now) registry().records.delete(token);\n  }\n}\n\nfunction matchesIdentity(record: VoiceReloadHandoff, identity: VoiceReloadHandoffIdentity): boolean {\n  return record.sessionId === identity.sessionId && record.hostGeneration === identity.hostGeneration;\n}\n\n/** Creates an accessor over the exact cross-reload registry with explicit time and token dependencies. */\nexport function createVoiceReloadHandoffStore(runtime: VoiceReloadHandoffRuntime): VoiceReloadHandoffStore {\n  const store: VoiceReloadHandoffStore = {\n    prepare(session, request) {\n      const now = validNow(runtime);\n      pruneExpired(now);\n      if (!session.active) throw new VoiceReloadHandoffError('The voice session is no longer active.');\n      const identity = validateIdentity({ sessionId: session.sessionId, hostGeneration: session.hostGeneration });\n      if (!Check(VoiceReloadHandoffRequestSchema, request)) {\n        throw validationError('voice reload handoff request', VoiceReloadHandoffRequestSchema, request);\n      }\n      const kind = request.kind ?? 'domain-switch';\n      const domains = [...(request.domains ?? [])];\n      if (kind === 'major-mode-switch' && request.majorMode === undefined) {\n        throw new VoiceReloadHandoffError('Major-mode reload handoffs require a major mode.');\n      }\n      if (kind === 'profile-switch' && request.profile === undefined) {\n        throw new VoiceReloadHandoffError('Profile reload handoffs require a profile.');\n      }\n      if (kind === 'domain-switch' && request.majorMode !== undefined) {\n        throw new VoiceReloadHandoffError('Domain reload handoffs cannot include a major mode.');\n      }\n      if (kind === 'major-mode-switch' && domains.length > 0) {\n        throw new VoiceReloadHandoffError('Major-mode reload handoffs cannot include domains.');\n      }\n      if (kind !== 'profile-switch' && request.profile !== undefined) {\n        throw new VoiceReloadHandoffError('Only profile reload handoffs can include a profile.');\n      }\n      if (kind === 'profile-switch' && request.majorMode !== undefined) {\n        throw new VoiceReloadHandoffError('Profile reload handoffs cannot include a major mode.');\n      }\n      if (kind === 'profile-switch' && domains.length > 0) {\n        throw new VoiceReloadHandoffError('Profile reload handoffs cannot include domains.');\n      }\n      const token = `voice-reload:${runtime.createToken()}`;\n      if (token.length > MAX_TOKEN_LENGTH || registry().records.has(token)) {\n        throw new VoiceReloadHandoffError('Voice reload handoff token is invalid or already active.');\n      }\n      const record: VoiceReloadHandoff = {\n        token,\n        ...identity,\n        operationId: request.operationId,\n        kind,\n        domains,\n        ...(request.majorMode === undefined ? {} : { majorMode: request.majorMode }),\n        ...(request.profile === undefined ? {} : { profile: request.profile }),\n        createdAt: now,\n        expiresAt: now + VOICE_RELOAD_HANDOFF_TTL_MS,\n      };\n      if (!Check(VoiceReloadHandoffSchema, record)) {\n        throw validationError('voice reload handoff', VoiceReloadHandoffSchema, record);\n      }\n      registry().records.set(token, { record, state: 'pending' });\n      let closed = false;\n      return Object.freeze({\n        token,\n        commit() {\n          if (closed) return false;\n          closed = true;\n          return store.commit(token, identity);\n        },\n        discard() {\n          if (closed) return false;\n          closed = true;\n          return store.discard(token, identity);\n        },\n      });\n    },\n    accept(token, identity) {\n      const now = validNow(runtime);\n      pruneExpired(now);\n      const validatedIdentity = validateIdentity(identity);\n      const pending = registry().records.get(token);\n      if (!pending || pending.state !== 'pending' || !matchesIdentity(pending.record, validatedIdentity)) {\n        return undefined;\n      }\n      pending.state = 'committed';\n      return cloneRecord(pending.record);\n    },\n    commit(token, identity) {\n      return store.accept(token, identity) !== undefined;\n    },\n    discard(token, identity) {\n      const now = validNow(runtime);\n      pruneExpired(now);\n      const validatedIdentity = validateIdentity(identity);\n      const pending = registry().records.get(token);\n      if (!pending || !matchesIdentity(pending.record, validatedIdentity)) return false;\n      return registry().records.delete(token);\n    },\n    consume(sessionId, token) {\n      const now = validNow(runtime);\n      pruneExpired(now);\n      if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_LENGTH) {\n        throw new VoiceReloadHandoffError('Voice reload handoff session id is invalid.');\n      }\n      const candidates = [...registry().records.entries()]\n        .filter(\n          ([candidateToken, pending]) =>\n            pending.state === 'committed' &&\n            pending.record.sessionId === sessionId &&\n            (token === undefined || candidateToken === token),\n        )\n        .sort((left, right) => right[1].record.createdAt - left[1].record.createdAt);\n      const [candidate] = candidates;\n      if (!candidate) return undefined;\n      registry().records.delete(candidate[0]);\n      return cloneRecord(candidate[1].record);\n    },\n  };\n  return Object.freeze(store);\n}\n"],"mappings":"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"}