@roamcode.ai/server 1.0.23 → 1.2.0

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.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as better_sqlite3 from 'better-sqlite3';
2
2
  import { FastifyInstance } from 'fastify';
3
+ import WebSocket from 'ws';
4
+ import { z, ZodType } from 'zod';
3
5
  import { spawn, ChildProcess, SpawnOptions } from 'node:child_process';
4
6
  import { EventEmitter } from 'node:events';
5
- import { ZodType } from 'zod';
6
7
 
7
8
  interface ServerConfig {
8
9
  claudeBin: string;
@@ -138,6 +139,12 @@ declare function assertConfigAllowsStart(cfg: ServerRuntimeConfig): void;
138
139
  declare function extractBearerToken(authorizationHeader: string | undefined): string | undefined;
139
140
  interface AuthGateOptions {
140
141
  token?: string;
142
+ /**
143
+ * Optional verifier for independently revocable credentials (for example per-device tokens). It
144
+ * receives the presented plaintext once; implementations should persist only a digest. A verifier
145
+ * failure is treated as an invalid credential and can never take the server down.
146
+ */
147
+ verifyCredential?: (token: string) => boolean;
141
148
  /** Consecutive failures from one client before it is locked out. Default 10. */
142
149
  maxFailures?: number;
143
150
  /** Lockout duration in ms. Default 60000. */
@@ -181,6 +188,7 @@ declare class AuthGate {
181
188
  private readonly lockoutMs;
182
189
  private readonly graceMs;
183
190
  private readonly now;
191
+ private readonly verifyCredential?;
184
192
  private readonly clients;
185
193
  constructor(opts: AuthGateOptions);
186
194
  check(presentedToken: string | undefined, clientKey: string): AuthCheckResult;
@@ -193,6 +201,10 @@ declare class AuthGate {
193
201
  * slate is correct (and avoids leaving a client locked out against a token that no longer exists).
194
202
  */
195
203
  rotateToken(newToken: string): void;
204
+ /** True only for the current host recovery credential (never a device key or grace token). */
205
+ isCurrentHostToken(presentedToken: string | undefined): boolean;
206
+ /** Security reset: replace the host credential immediately, with no previous-token grace window. */
207
+ resetToken(newToken: string): void;
196
208
  /** Drop entries whose lockout has expired and whose failure count is 0 — keeps the map bounded. */
197
209
  private sweepExpired;
198
210
  /** TEST ONLY: number of tracked clients currently locked (lockedUntil in the future). */
@@ -319,7 +331,58 @@ declare class FsService {
319
331
  pruneOlderThan(target: string, maxAgeMs: number, now?: number, recursive?: boolean): Promise<number>;
320
332
  }
321
333
 
322
- type ProviderId = "claude" | "codex";
334
+ declare const ADAPTER_CONTRACT_VERSION: 1;
335
+ declare const adapterCapabilityNames: readonly ["probe", "launch", "resume", "state", "identity", "metadata", "usage", "login", "attachments", "cleanup"];
336
+ type AdapterCapabilityName = (typeof adapterCapabilityNames)[number];
337
+ type AdapterStateAuthority = "native-events" | "runtime-signals" | "pane-heuristics";
338
+ interface AdapterManifestV1 {
339
+ schemaVersion: typeof ADAPTER_CONTRACT_VERSION;
340
+ id: string;
341
+ version: string;
342
+ displayName: string;
343
+ platforms: Array<"darwin" | "linux">;
344
+ resumeIdentity: "optional" | "required" | "unsupported";
345
+ capabilities: Record<AdapterCapabilityName, boolean>;
346
+ stateAuthority: AdapterStateAuthority[];
347
+ /** JSON Schema for the adapter-owned launch options. */
348
+ optionSchema: Record<string, unknown>;
349
+ }
350
+ declare class AdapterManifestError extends Error {
351
+ constructor(message: string);
352
+ }
353
+ /** The supported, bounded JSON Schema subset shared by the server validator, generated UI, and OpenAPI. */
354
+ declare function validateAdapterOptionSchema(value: unknown): Record<string, unknown>;
355
+ declare function validateAdapterManifest(value: unknown): AdapterManifestV1;
356
+ /** Validate and freeze the public metadata at the adapter boundary before any lifecycle code can run. */
357
+ declare function defineAdapterManifest(value: AdapterManifestV1): Readonly<AdapterManifestV1>;
358
+ declare function publicAdapterDescriptor(manifest: AdapterManifestV1, source: "built-in" | "installed"): {
359
+ id: string;
360
+ displayName: string;
361
+ version: string;
362
+ schemaVersion: 1;
363
+ source: "built-in" | "installed";
364
+ enabled: boolean;
365
+ platforms: ("darwin" | "linux")[];
366
+ resumeIdentity: "optional" | "required" | "unsupported";
367
+ capabilities: {
368
+ state: boolean;
369
+ probe: boolean;
370
+ launch: boolean;
371
+ resume: boolean;
372
+ identity: boolean;
373
+ metadata: boolean;
374
+ usage: boolean;
375
+ login: boolean;
376
+ attachments: boolean;
377
+ cleanup: boolean;
378
+ };
379
+ stateAuthority: AdapterStateAuthority[];
380
+ optionSchema: Record<string, unknown>;
381
+ };
382
+
383
+ /** Public adapter identity. Built-ins are ordinary ids under the same contract; third parties are not
384
+ * forced through a source-code union update. Runtime validation is owned by ProviderRegistry. */
385
+ type ProviderId = string;
323
386
  interface ProviderAvailability {
324
387
  terminalAvailable: boolean;
325
388
  metadataAvailable: boolean;
@@ -350,7 +413,22 @@ type CodexSessionOptions = {
350
413
  dangerouslyBypassApprovalsAndSandbox?: boolean;
351
414
  addDirs?: string[];
352
415
  };
353
- type ProviderSessionOptions = ClaudeSessionOptions | CodexSessionOptions;
416
+ interface ProviderSessionOptions {
417
+ provider: ProviderId;
418
+ model?: string;
419
+ effort?: string;
420
+ reasoningEffort?: string;
421
+ permissionMode?: ClaudeSessionOptions["permissionMode"];
422
+ dangerouslySkip?: boolean;
423
+ sandbox?: CodexSessionOptions["sandbox"];
424
+ approvalPolicy?: CodexSessionOptions["approvalPolicy"];
425
+ profile?: string;
426
+ webSearch?: boolean;
427
+ dangerouslyBypassApprovalsAndSandbox?: boolean;
428
+ addDirs?: string[];
429
+ legacyArgs?: string[];
430
+ [key: string]: unknown;
431
+ }
354
432
  type LaunchIntent = "fresh" | "resume";
355
433
  interface ProcessSpec {
356
434
  executable: string;
@@ -399,9 +477,11 @@ interface ProviderRuntimeMetadata {
399
477
  effort?: string;
400
478
  }
401
479
  interface AgentProvider {
480
+ /** Exact public, versioned adapter contract. Optional only for legacy in-process test doubles. */
481
+ readonly manifest?: Readonly<AdapterManifestV1>;
402
482
  readonly id: ProviderId;
403
483
  readonly displayName: string;
404
- readonly resumeIdentity: "optional" | "required";
484
+ readonly resumeIdentity: "optional" | "required" | "unsupported";
405
485
  probe(): Promise<ProviderAvailability>;
406
486
  buildProcess(context: ProviderProcessContext): Promise<ProcessSpec>;
407
487
  /** Stateful parsers must be created per spawned process; providers are registry singletons. */
@@ -413,6 +493,10 @@ interface AgentProvider {
413
493
  runtimeMetadata?(pane: string): ProviderRuntimeMetadata | undefined;
414
494
  cleanup(paths: readonly string[]): void;
415
495
  }
496
+ /** Installable and built-in adapters must implement this strict public shape. */
497
+ interface ProviderAdapterV1 extends AgentProvider {
498
+ readonly manifest: Readonly<AdapterManifestV1>;
499
+ }
416
500
 
417
501
  interface SessionDefaults {
418
502
  /** The provider used by the most recently created session. Absent only for legacy saved documents. */
@@ -480,6 +564,7 @@ interface StoredSessionBase {
480
564
  }
481
565
  interface StoredClaudeSession extends StoredSessionBase {
482
566
  provider: "claude";
567
+ externalAdapter?: never;
483
568
  dangerouslySkip: boolean;
484
569
  /** The USER-chosen spawn flags the session was created with (`--model`/`--effort`/`--permission-mode`/
485
570
  * `--dangerously-skip-permissions`/`--add-dir …`) — everything EXCEPT the ephemeral per-session
@@ -497,13 +582,24 @@ interface StoredIntegrationStatus {
497
582
  }
498
583
  interface StoredCodexSession extends StoredSessionBase {
499
584
  provider: "codex";
585
+ externalAdapter?: never;
500
586
  launchOptions: CodexSessionOptions;
501
587
  providerSessionId?: string;
502
588
  integrationStatus?: StoredIntegrationStatus;
503
589
  dangerouslySkip?: never;
504
590
  spawnArgs?: never;
505
591
  }
506
- type StoredSession = StoredClaudeSession | StoredCodexSession;
592
+ interface StoredExternalSession extends StoredSessionBase {
593
+ provider: ProviderId;
594
+ /** Discriminator that keeps additive third-party rows separate from rollback-readable built-in tables. */
595
+ externalAdapter: true;
596
+ launchOptions: ProviderSessionOptions;
597
+ providerSessionId?: string;
598
+ integrationStatus?: StoredIntegrationStatus;
599
+ dangerouslySkip?: never;
600
+ spawnArgs?: never;
601
+ }
602
+ type StoredSession = StoredClaudeSession | StoredCodexSession | StoredExternalSession;
507
603
  /** How a store is actually backed: "sqlite" (durable) or "memory-fallback" (the native module failed to
508
604
  * load — NOT durable across restarts). Surfaced so start.ts can warn loudly + /diag can report it. */
509
605
  type StoreMode = "sqlite" | "memory-fallback";
@@ -567,10 +663,10 @@ declare function ensureDataDir(dir: string): void;
567
663
  */
568
664
  declare function generateAccessToken(): string;
569
665
  /**
570
- * Persist a token to `<dataDir>/token` with mode 0600 (overwriting any prior file). Used by both first-run
571
- * generation and POST /token/rotate. `writeFileSync`'s `mode` is honored only when CREATING a file, so we
572
- * chmod unconditionally afterwards a rotated secret can never land in a too-permissive (e.g. world-
573
- * readable) file left behind by an earlier write.
666
+ * Persist a token to `<dataDir>/token` with mode 0600 (atomically replacing any prior regular file). Used by
667
+ * POST /token/rotate and access reset. The secret is fsynced before rename, and the containing directory is
668
+ * fsynced afterwards; a crash can expose either the complete old token or the complete new token, never a
669
+ * truncated intermediate value.
574
670
  */
575
671
  declare function persistAccessToken(dataDir: string, token: string): void;
576
672
  interface ResolveAccessTokenOptions {
@@ -594,6 +690,1548 @@ declare function resolveAccessToken(opts: ResolveAccessTokenOptions): {
594
690
  generated: boolean;
595
691
  };
596
692
 
693
+ /** A pairing link is deliberately short-lived and can be claimed exactly once. */
694
+ declare const PAIRING_TTL_MS: number;
695
+ type DeviceScope = "direct" | "relay";
696
+ interface DeviceInfo {
697
+ id: string;
698
+ name: string;
699
+ createdAt: number;
700
+ lastSeenAt: number;
701
+ scopes: DeviceScope[];
702
+ /** Pinned E2E relay signing identity. Public material only; the private key never leaves the device. */
703
+ relayIdentityFingerprint?: string;
704
+ }
705
+ interface DeviceRelayIdentity {
706
+ publicKey: string;
707
+ fingerprint: string;
708
+ }
709
+ declare class DevicePairingError extends Error {
710
+ readonly code: "INVALID_RELAY_IDENTITY";
711
+ constructor(code: "INVALID_RELAY_IDENTITY", message: string);
712
+ }
713
+ interface PairingTicket {
714
+ /** One-time capability carried by the pairing URL. Never persisted in plaintext. */
715
+ secret: string;
716
+ expiresAt: number;
717
+ /** Exact product surfaces the resulting device credential can reach. */
718
+ scopes: DeviceScope[];
719
+ }
720
+ /**
721
+ * Relay bootstrap pre-allocates both durable ids. The token is still inert until the one-use pairing
722
+ * capability is claimed, but carrying it in the URL fragment makes a dropped final response recoverable:
723
+ * the browser can reconnect with the same credential after the host committed the claim.
724
+ */
725
+ interface RelayPairingTicket extends PairingTicket {
726
+ deviceId: string;
727
+ token: string;
728
+ }
729
+ interface RelayPairingCancellationReservation {
730
+ deviceId: string;
731
+ reservationId: string;
732
+ }
733
+ type RelayPairingCancellationStart = {
734
+ status: "reserved";
735
+ reservation: RelayPairingCancellationReservation;
736
+ } | {
737
+ status: "busy";
738
+ } | {
739
+ status: "missing";
740
+ };
741
+ interface DeviceEnrollment {
742
+ /** The per-device bearer credential. Returned once; only its SHA-256 digest is persisted. */
743
+ token: string;
744
+ device: DeviceInfo;
745
+ }
746
+ interface DeviceStore {
747
+ readonly mode: "sqlite" | "memory-fallback";
748
+ issuePairing(now?: number, scopes?: DeviceScope[]): PairingTicket;
749
+ cancelPairing(secret: string): boolean;
750
+ claimPairing(secret: string, name: string, now?: number, relayIdentityPublicKey?: string): DeviceEnrollment | undefined;
751
+ issueRelayPairing(now?: number): RelayPairingTicket;
752
+ pendingRelayPairing(deviceId: string, now?: number): boolean;
753
+ /** Atomically prevents a relay claim from winning while broker revocation is in flight. */
754
+ beginRelayPairingCancellation(deviceId: string, now?: number): RelayPairingCancellationStart;
755
+ /** Release a failed broker-revocation attempt so the same expiry-bounded link can be retried. */
756
+ releaseRelayPairingCancellation(reservation: RelayPairingCancellationReservation): boolean;
757
+ /** Delete the local bootstrap only after broker revocation is authoritative. */
758
+ finishRelayPairingCancellation(reservation: RelayPairingCancellationReservation): boolean;
759
+ cancelRelayPairing(deviceId: string): boolean;
760
+ claimRelayPairing(secret: string, token: string, name: string, relayIdentityPublicKey: string, now?: number): DeviceEnrollment | undefined;
761
+ /** Resolve a per-device bearer credential and best-effort touch its last-seen time. */
762
+ authenticate(token: string, now?: number, requiredScope?: DeviceScope): DeviceInfo | undefined;
763
+ relayIdentity(id: string): DeviceRelayIdentity | undefined;
764
+ list(): DeviceInfo[];
765
+ rename(id: string, name: string): DeviceInfo | undefined;
766
+ revoke(id: string): boolean;
767
+ revokeAll(): number;
768
+ close(): void;
769
+ }
770
+ interface OpenDeviceStoreOptions {
771
+ /** SQLite file path. ":memory:" uses an in-process DB. */
772
+ dbPath: string;
773
+ generateSecret?: () => string;
774
+ generateToken?: () => string;
775
+ generateId?: () => string;
776
+ }
777
+ declare function normalizeDeviceScopes(value: unknown): DeviceScope[] | undefined;
778
+ /** Keep device labels useful in UI while refusing control characters and unbounded payloads. */
779
+ declare function normalizeDeviceName(value: unknown): string | undefined;
780
+ declare function openDeviceStore(opts: OpenDeviceStoreOptions): DeviceStore;
781
+
782
+ declare const INPUT_LEASE_TTL_MS = 30000;
783
+ type InputLeaseActorType = "device" | "host" | "local" | "relay";
784
+ interface InputLeasePrincipal {
785
+ actorType: InputLeaseActorType;
786
+ actorId: string;
787
+ label: string;
788
+ }
789
+ interface InputLease {
790
+ id: string;
791
+ sessionId: string;
792
+ holderId: string;
793
+ actorType: InputLeaseActorType;
794
+ actorId: string;
795
+ label: string;
796
+ acquiredAt: number;
797
+ renewedAt: number;
798
+ expiresAt: number;
799
+ revision: number;
800
+ }
801
+ type InputLeaseEvent = {
802
+ type: "granted";
803
+ lease: InputLease;
804
+ } | {
805
+ type: "renewed";
806
+ lease: InputLease;
807
+ } | {
808
+ type: "released" | "expired" | "revoked";
809
+ lease: InputLease;
810
+ } | {
811
+ type: "taken-over";
812
+ lease: InputLease;
813
+ previous: InputLease;
814
+ };
815
+ type InputLeaseAcquireResult = {
816
+ status: "granted" | "owned";
817
+ lease: InputLease;
818
+ } | {
819
+ status: "denied";
820
+ current: InputLease;
821
+ };
822
+ interface InputLeaseCoordinatorOptions {
823
+ ttlMs?: number;
824
+ now?: () => number;
825
+ generateId?: () => string;
826
+ scheduleExpiry?: boolean;
827
+ onEvent?: (event: InputLeaseEvent) => void;
828
+ }
829
+ /**
830
+ * Coordinates the one mutable terminal input stream shared by many read-only observers.
831
+ *
832
+ * Leases intentionally live in memory: a host restart invalidates every connection and therefore every holder.
833
+ * Durable audit is supplied by the caller through onEvent. Expiry, release, and confirmed takeover are explicit;
834
+ * merely sending input can never acquire or steal ownership.
835
+ */
836
+ declare class InputLeaseCoordinator {
837
+ private readonly leases;
838
+ private readonly listeners;
839
+ private readonly timers;
840
+ private readonly ttlMs;
841
+ private readonly now;
842
+ private readonly generateId;
843
+ private readonly scheduleExpiry;
844
+ private readonly onEvent?;
845
+ private revision;
846
+ constructor(options?: InputLeaseCoordinatorOptions);
847
+ acquire(sessionId: string, holderId: string, principal: InputLeasePrincipal): InputLeaseAcquireResult;
848
+ takeover(sessionId: string, holderId: string, principal: InputLeasePrincipal, confirmed: boolean, authorized?: boolean): InputLeaseAcquireResult;
849
+ renew(sessionId: string, holderId: string, leaseId: string): InputLease | undefined;
850
+ canWrite(sessionId: string, holderId: string, leaseId?: string): boolean;
851
+ release(sessionId: string, holderId: string, leaseId?: string): boolean;
852
+ releaseHolder(holderId: string): number;
853
+ revoke(sessionId: string): boolean;
854
+ revokeActor(actorType: InputLeaseActorType, actorId: string): number;
855
+ get(sessionId: string): InputLease | undefined;
856
+ subscribe(sessionId: string, listener: (event: InputLeaseEvent) => void): () => void;
857
+ close(): void;
858
+ private validate;
859
+ private current;
860
+ private renewExisting;
861
+ private remove;
862
+ private schedule;
863
+ private emit;
864
+ }
865
+
866
+ type TeamStoreMode = "sqlite" | "memory-fallback";
867
+ type TeamMemberKind = "person" | "service";
868
+ type TeamMemberStatus = "active" | "suspended" | "removed";
869
+ type TeamRole = "viewer" | "operator" | "workspace-manager" | "extension-manager" | "policy-admin" | "organization-admin";
870
+ type TeamScopeType = "team" | "host" | "workspace";
871
+ type TeamPrincipalType = "device" | "host" | "local" | "relay";
872
+ type TeamPermission = "team:read" | "sessions:read" | "sessions:operate" | "attention:read" | "attention:manage" | "presence:read" | "presence:write" | "workspaces:manage" | "extensions:manage" | "policy:manage" | "members:manage" | "audit:read" | "fleet:read";
873
+ interface TeamRecord {
874
+ id: string;
875
+ name: string;
876
+ authorizationEnabled: boolean;
877
+ revision: number;
878
+ createdAt: number;
879
+ updatedAt: number;
880
+ }
881
+ interface TeamMember {
882
+ id: string;
883
+ displayName: string;
884
+ kind: TeamMemberKind;
885
+ status: TeamMemberStatus;
886
+ revision: number;
887
+ createdAt: number;
888
+ updatedAt: number;
889
+ }
890
+ interface TeamRoleBinding {
891
+ id: string;
892
+ memberId: string;
893
+ role: TeamRole;
894
+ scopeType: TeamScopeType;
895
+ scopeId?: string;
896
+ createdAt: number;
897
+ }
898
+ interface TeamPrincipalBinding {
899
+ actorType: TeamPrincipalType;
900
+ actorId: string;
901
+ memberId: string;
902
+ createdAt: number;
903
+ }
904
+ interface TeamAuthorizationResource {
905
+ hostId?: string;
906
+ workspaceId?: string;
907
+ }
908
+ interface TeamAuthorizationDecision {
909
+ allowed: boolean;
910
+ reason: "local-break-glass" | "not-enforced" | "unbound" | "inactive" | "role" | "missing-permission";
911
+ member?: TeamMember;
912
+ roles: TeamRole[];
913
+ }
914
+ interface TeamStore {
915
+ readonly mode: TeamStoreMode;
916
+ getTeam(): TeamRecord | null;
917
+ createTeam(input: {
918
+ name: string;
919
+ ownerName: string;
920
+ ownerPrincipal: {
921
+ actorType: TeamPrincipalType;
922
+ actorId: string;
923
+ };
924
+ }, now?: number): {
925
+ team: TeamRecord;
926
+ owner: TeamMember;
927
+ };
928
+ updateTeam(input: {
929
+ name?: string;
930
+ authorizationEnabled?: boolean;
931
+ }, expectedRevision: number, now?: number): TeamRecord;
932
+ listMembers(opts?: {
933
+ includeRemoved?: boolean;
934
+ }): TeamMember[];
935
+ getMember(id: string): TeamMember | undefined;
936
+ createMember(input: {
937
+ displayName: string;
938
+ kind?: TeamMemberKind;
939
+ }, now?: number): TeamMember;
940
+ updateMember(id: string, input: {
941
+ displayName?: string;
942
+ status?: TeamMemberStatus;
943
+ }, expectedRevision: number, now?: number): TeamMember | undefined;
944
+ listRoleBindings(memberId?: string): TeamRoleBinding[];
945
+ grantRole(input: {
946
+ memberId: string;
947
+ role: TeamRole;
948
+ scopeType?: TeamScopeType;
949
+ scopeId?: string;
950
+ }, now?: number): TeamRoleBinding;
951
+ revokeRole(id: string, now?: number): boolean;
952
+ listPrincipalBindings(memberId?: string): TeamPrincipalBinding[];
953
+ bindPrincipal(input: {
954
+ memberId: string;
955
+ actorType: TeamPrincipalType;
956
+ actorId: string;
957
+ }, now?: number): TeamPrincipalBinding;
958
+ unbindPrincipal(actorType: TeamPrincipalType, actorId: string, now?: number): boolean;
959
+ memberForPrincipal(actorType: TeamPrincipalType, actorId: string): TeamMember | undefined;
960
+ authorize(actorType: TeamPrincipalType, actorId: string, permission: TeamPermission, resource?: TeamAuthorizationResource): TeamAuthorizationDecision;
961
+ close(): void;
962
+ }
963
+ interface OpenTeamStoreOptions {
964
+ dbPath: string;
965
+ generateTeamId?: () => string;
966
+ generateMemberId?: () => string;
967
+ generateRoleId?: () => string;
968
+ loadDatabase?: () => typeof better_sqlite3;
969
+ }
970
+ declare class TeamRevisionConflictError extends Error {
971
+ readonly current: TeamRecord | TeamMember;
972
+ constructor(current: TeamRecord | TeamMember);
973
+ }
974
+ declare function openTeamStore(opts: OpenTeamStoreOptions): TeamStore;
975
+ declare function teamRolePermissions(role: TeamRole): readonly TeamPermission[];
976
+ declare function isTeamRole(value: unknown): value is TeamRole;
977
+ declare function isTeamScopeType(value: unknown): value is TeamScopeType;
978
+
979
+ declare const PRESENCE_TTL_MS = 45000;
980
+ declare const PRESENCE_HEARTBEAT_MS = 15000;
981
+ type PresenceMode = "viewing" | "operating";
982
+ interface PresenceTarget {
983
+ hostId: string;
984
+ workspaceId?: string;
985
+ sessionId?: string;
986
+ agentId?: string;
987
+ }
988
+ interface PresenceRecord extends PresenceTarget {
989
+ id: string;
990
+ memberId?: string;
991
+ label: string;
992
+ mode: PresenceMode;
993
+ connectedAt: number;
994
+ lastSeenAt: number;
995
+ expiresAt: number;
996
+ revision: number;
997
+ }
998
+ type PresenceEvent = {
999
+ type: "joined" | "updated" | "left" | "expired";
1000
+ presence: PresenceRecord;
1001
+ };
1002
+ interface PresenceHeartbeatInput extends PresenceTarget {
1003
+ clientId: string;
1004
+ mode: PresenceMode;
1005
+ memberId?: string;
1006
+ }
1007
+ interface PresenceCoordinatorOptions {
1008
+ ttlMs?: number;
1009
+ now?: () => number;
1010
+ generateId?: () => string;
1011
+ maxRecords?: number;
1012
+ maxPerActor?: number;
1013
+ scheduleExpiry?: boolean;
1014
+ }
1015
+ /**
1016
+ * Ephemeral, privacy-bounded presence. No IP, bearer credential, path, prompt, or terminal data is retained.
1017
+ * Heartbeats expire cleanly after disconnect and are capped both globally and per authenticated actor.
1018
+ */
1019
+ declare class PresenceCoordinator {
1020
+ private readonly records;
1021
+ private readonly listeners;
1022
+ private readonly ttlMs;
1023
+ private readonly now;
1024
+ private readonly generateId;
1025
+ private readonly maxRecords;
1026
+ private readonly maxPerActor;
1027
+ private readonly timer?;
1028
+ private revision;
1029
+ constructor(options?: PresenceCoordinatorOptions);
1030
+ heartbeat(principal: InputLeasePrincipal, input: PresenceHeartbeatInput): PresenceRecord;
1031
+ list(filter?: Partial<PresenceTarget>): PresenceRecord[];
1032
+ release(principal: Pick<InputLeasePrincipal, "actorType" | "actorId">, clientId: string): boolean;
1033
+ releaseActor(principal: Pick<InputLeasePrincipal, "actorType" | "actorId">): number;
1034
+ /**
1035
+ * Input ownership is authoritative for the public "operating" label. When a lease ends or moves, keep the
1036
+ * connected observer visible but immediately downgrade stale operating heartbeats instead of waiting for TTL.
1037
+ */
1038
+ downgradeOperating(principal: Pick<InputLeasePrincipal, "actorType" | "actorId">, sessionId: string): number;
1039
+ subscribe(listener: (event: PresenceEvent) => void): () => void;
1040
+ sweep(): number;
1041
+ close(): void;
1042
+ private enforceBounds;
1043
+ private evict;
1044
+ private publicRecord;
1045
+ private emit;
1046
+ }
1047
+
1048
+ declare const RELAY_PROTOCOL_VERSION: 1;
1049
+ declare const RELAY_HANDSHAKE_MAX_SKEW_MS: number;
1050
+ declare const RELAY_CHANNEL_MAX_AGE_MS: number;
1051
+ declare const RELAY_CHANNEL_MAX_FRAMES = 1000000;
1052
+ declare const RELAY_MAX_PLAINTEXT_BYTES: number;
1053
+ type RelayRole = "device" | "host";
1054
+ type RelayDirection = "device-to-host" | "host-to-device";
1055
+ type RelayFrameKind = "auth" | "rpc-request" | "rpc-response" | "stream-open" | "stream-data" | "stream-control" | "close";
1056
+ type RelayCryptoErrorCode = "INVALID_RELAY_IDENTITY" | "INVALID_RELAY_HELLO" | "RELAY_IDENTITY_MISMATCH" | "RELAY_SIGNATURE_INVALID" | "RELAY_HANDSHAKE_EXPIRED" | "RELAY_HANDSHAKE_MISMATCH" | "RELAY_FRAME_INVALID" | "RELAY_FRAME_OUT_OF_ORDER" | "RELAY_FRAME_AUTH_FAILED" | "RELAY_FRAME_TOO_LARGE" | "RELAY_KEY_ROTATION_REQUIRED" | "RELAY_CHANNEL_CLOSED";
1057
+ declare class RelayCryptoError extends Error {
1058
+ readonly code: RelayCryptoErrorCode;
1059
+ constructor(code: RelayCryptoErrorCode, message: string);
1060
+ }
1061
+ interface RelayIdentity {
1062
+ /** Base64url-encoded SPKI DER public key. Safe to pin and exchange. */
1063
+ publicKey: string;
1064
+ /** Base64url-encoded PKCS#8 DER private key. Host persistence must use mode 0600. */
1065
+ privateKey: string;
1066
+ fingerprint: string;
1067
+ }
1068
+ interface RelayEphemeralKeyPair {
1069
+ publicKey: string;
1070
+ privateKey: string;
1071
+ }
1072
+ interface RelayHandshakeHello {
1073
+ v: typeof RELAY_PROTOCOL_VERSION;
1074
+ role: RelayRole;
1075
+ routeId: string;
1076
+ deviceId: string;
1077
+ sessionId: string;
1078
+ issuedAt: number;
1079
+ nonce: string;
1080
+ ephemeralPublicKey: string;
1081
+ identityFingerprint: string;
1082
+ signature: string;
1083
+ }
1084
+ interface RelayEncryptedFrame {
1085
+ v: typeof RELAY_PROTOCOL_VERSION;
1086
+ sessionId: string;
1087
+ seq: string;
1088
+ kind: RelayFrameKind;
1089
+ ciphertext: string;
1090
+ }
1091
+ interface RelayChannelOptions {
1092
+ role: RelayRole;
1093
+ localEphemeral: RelayEphemeralKeyPair;
1094
+ deviceHello: RelayHandshakeHello;
1095
+ hostHello: RelayHandshakeHello;
1096
+ deviceIdentityPublicKey: string;
1097
+ hostIdentityPublicKey: string;
1098
+ now?: () => number;
1099
+ maxAgeMs?: number;
1100
+ maxFrames?: number;
1101
+ }
1102
+ declare function relayIdentityFingerprint(publicKey: string): string;
1103
+ declare function generateRelayIdentity(): RelayIdentity;
1104
+ /** Validates both key encodings and proves that the persisted private key belongs to the advertised identity. */
1105
+ declare function validateRelayIdentity(value: unknown): RelayIdentity;
1106
+ declare function generateRelayEphemeralKeyPair(): RelayEphemeralKeyPair;
1107
+ declare function createRelayHandshakeHello(input: {
1108
+ role: RelayRole;
1109
+ routeId: string;
1110
+ deviceId: string;
1111
+ sessionId?: string;
1112
+ identity: RelayIdentity;
1113
+ ephemeral?: RelayEphemeralKeyPair;
1114
+ nonce?: Uint8Array;
1115
+ issuedAt?: number;
1116
+ }): {
1117
+ hello: RelayHandshakeHello;
1118
+ ephemeral: RelayEphemeralKeyPair;
1119
+ };
1120
+ declare function verifyRelayHandshakeHello(hello: RelayHandshakeHello, expected: {
1121
+ role: RelayRole;
1122
+ routeId: string;
1123
+ deviceId: string;
1124
+ sessionId: string;
1125
+ identityPublicKey: string;
1126
+ now?: number;
1127
+ maxSkewMs?: number;
1128
+ }): void;
1129
+ /** Small exported primitive used to pin RFC 5869 test vectors independently of the handshake. */
1130
+ declare function hkdfSha256(input: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Buffer;
1131
+ declare class RelayCipherState {
1132
+ readonly sessionId: string;
1133
+ readonly role: RelayRole;
1134
+ private readonly sendKey;
1135
+ private readonly receiveKey;
1136
+ private readonly sendBaseNonce;
1137
+ private readonly receiveBaseNonce;
1138
+ private readonly now;
1139
+ private readonly maxAgeMs;
1140
+ private readonly maxFrames;
1141
+ private sendSequence;
1142
+ private receiveSequence;
1143
+ private closed;
1144
+ private readonly createdAt;
1145
+ private readonly sendDirection;
1146
+ private readonly receiveDirection;
1147
+ constructor(sessionId: string, role: RelayRole, sendKey: Buffer, receiveKey: Buffer, sendBaseNonce: Buffer, receiveBaseNonce: Buffer, now: () => number, maxAgeMs: number, maxFrames: number);
1148
+ encrypt(kind: RelayFrameKind, plaintext: Uint8Array): RelayEncryptedFrame;
1149
+ decrypt(frame: RelayEncryptedFrame): Buffer;
1150
+ needsRotation(): boolean;
1151
+ sequences(): {
1152
+ send: string;
1153
+ receive: string;
1154
+ };
1155
+ close(): void;
1156
+ private assertOpen;
1157
+ private assertKind;
1158
+ }
1159
+ declare function establishRelayChannel(options: RelayChannelOptions): RelayCipherState;
1160
+
1161
+ interface RelayIdentityStoreOptions {
1162
+ dataDir: string;
1163
+ generate?: () => RelayIdentity;
1164
+ now?: () => number;
1165
+ }
1166
+ interface DurableRelayIdentity {
1167
+ identity: RelayIdentity;
1168
+ createdAt: number;
1169
+ path: string;
1170
+ generated: boolean;
1171
+ }
1172
+ /** Loads one stable host identity or creates it once without allowing concurrent processes to overwrite it. */
1173
+ declare function loadOrCreateRelayIdentity(options: RelayIdentityStoreOptions): DurableRelayIdentity;
1174
+
1175
+ type RelayStoreMode = "sqlite" | "memory";
1176
+ interface RelayRouteRecord {
1177
+ id: string;
1178
+ label: string;
1179
+ hostCredentialHash: string;
1180
+ ownerAccountId?: string;
1181
+ createdAt: number;
1182
+ updatedAt: number;
1183
+ }
1184
+ interface RelayDeviceRouteRecord {
1185
+ routeId: string;
1186
+ deviceId: string;
1187
+ credentialHash: string;
1188
+ createdAt: number;
1189
+ updatedAt: number;
1190
+ /** Bootstrap credentials expire unless the host promotes the device after the E2E pairing claim. */
1191
+ expiresAt?: number;
1192
+ }
1193
+ interface PublicRelayRouteRecord {
1194
+ id: string;
1195
+ label: string;
1196
+ deviceCount: number;
1197
+ createdAt: number;
1198
+ updatedAt: number;
1199
+ }
1200
+ interface RelayRouteStore {
1201
+ readonly mode: RelayStoreMode;
1202
+ createRoute(input: {
1203
+ id?: string;
1204
+ label: string;
1205
+ hostCredentialHash: string;
1206
+ ownerAccountId?: string;
1207
+ }, now?: number): RelayRouteRecord;
1208
+ getRoute(id: string): RelayRouteRecord | undefined;
1209
+ listRoutes(now?: number): PublicRelayRouteRecord[];
1210
+ listRoutesByOwner(ownerAccountId: string, now?: number): PublicRelayRouteRecord[];
1211
+ countDevices(routeId: string, now?: number): number;
1212
+ rotateHostCredential(routeId: string, credentialHash: string, now?: number): boolean;
1213
+ deleteRoute(id: string): boolean;
1214
+ authenticateHost(routeId: string, credential: string): boolean;
1215
+ putDevice(input: {
1216
+ routeId: string;
1217
+ deviceId: string;
1218
+ credentialHash: string;
1219
+ expiresAt?: number;
1220
+ }, now?: number): RelayDeviceRouteRecord;
1221
+ getDevice(routeId: string, deviceId: string, now?: number): RelayDeviceRouteRecord | undefined;
1222
+ authenticateDevice(routeId: string, deviceId: string, credential: string, now?: number): boolean;
1223
+ revokeDevice(routeId: string, deviceId: string): boolean;
1224
+ close(): void;
1225
+ }
1226
+ interface OpenRelayRouteStoreOptions {
1227
+ dbPath: string;
1228
+ generateRouteId?: () => string;
1229
+ loadDatabase?: () => typeof better_sqlite3;
1230
+ }
1231
+ declare function relayCredentialHash(credential: string): string;
1232
+ declare function generateRelayCredential(prefix?: "rrh" | "rrd" | "rrp"): string;
1233
+ declare function openRelayRouteStore(options: OpenRelayRouteStoreOptions): RelayRouteStore;
1234
+
1235
+ type RelayAccountStoreMode = "sqlite" | "memory";
1236
+ type RelayAccountStatus = "active" | "suspended" | "deleted";
1237
+ type RelayAccountPlan = "free" | "team" | "enterprise";
1238
+ interface RelayAccountRecord {
1239
+ id: string;
1240
+ label: string;
1241
+ status: RelayAccountStatus;
1242
+ plan: RelayAccountPlan;
1243
+ maxRoutes: number;
1244
+ maxDevicesPerRoute: number;
1245
+ revision: number;
1246
+ createdAt: number;
1247
+ updatedAt: number;
1248
+ }
1249
+ interface RelayAccountFields {
1250
+ label: string;
1251
+ plan?: RelayAccountPlan;
1252
+ maxRoutes?: number;
1253
+ maxDevicesPerRoute?: number;
1254
+ }
1255
+ interface RelayAccountCredentialMaterial {
1256
+ credentialHash: string;
1257
+ credentialLookup: string;
1258
+ }
1259
+ type CreateRelayAccountInput = RelayAccountFields & (({
1260
+ credential: string;
1261
+ } & Partial<Record<keyof RelayAccountCredentialMaterial, never>>) | ({
1262
+ credential?: never;
1263
+ } & RelayAccountCredentialMaterial));
1264
+ type RelayAccountCredentialInput = string | RelayAccountCredentialMaterial;
1265
+ interface UpdateRelayAccountInput {
1266
+ label?: string;
1267
+ status?: RelayAccountStatus;
1268
+ plan?: RelayAccountPlan;
1269
+ maxRoutes?: number;
1270
+ maxDevicesPerRoute?: number;
1271
+ }
1272
+ interface RelayAccountStore {
1273
+ readonly mode: RelayAccountStoreMode;
1274
+ createAccount(input: CreateRelayAccountInput, now?: number): RelayAccountRecord;
1275
+ getAccount(id: string): RelayAccountRecord | undefined;
1276
+ listAccounts(options?: {
1277
+ includeDeleted?: boolean;
1278
+ }): RelayAccountRecord[];
1279
+ /** Verify ownership for recovery without granting a suspended account route access. */
1280
+ verifyCredential(credential: string): RelayAccountRecord | undefined;
1281
+ authenticate(credential: string): RelayAccountRecord | undefined;
1282
+ updateAccount(id: string, input: UpdateRelayAccountInput, expectedRevision: number, now?: number): RelayAccountRecord | undefined;
1283
+ rotateCredential(id: string, credential: RelayAccountCredentialInput, expectedRevision: number, now?: number): RelayAccountRecord | undefined;
1284
+ close(): void;
1285
+ }
1286
+ interface OpenRelayAccountStoreOptions {
1287
+ dbPath: string;
1288
+ generateAccountId?: () => string;
1289
+ loadDatabase?: () => typeof better_sqlite3;
1290
+ }
1291
+ declare class RelayAccountRevisionConflictError extends Error {
1292
+ readonly current: RelayAccountRecord;
1293
+ constructor(current: RelayAccountRecord);
1294
+ }
1295
+ declare function relayAccountCredentialHash(credential: string): string;
1296
+ declare function relayAccountCredentialLookup(credential: string): string;
1297
+ declare function generateRelayAccountCredential(): string;
1298
+ declare function openRelayAccountStore(options: OpenRelayAccountStoreOptions): RelayAccountStore;
1299
+
1300
+ interface RelayDeviceProvisioner {
1301
+ putDevice(deviceId: string, credentialHash: string, expiresAt?: number): Promise<void>;
1302
+ revokeDevice(deviceId: string): Promise<void>;
1303
+ }
1304
+ interface RelayDeviceProvisionerOptions {
1305
+ relayUrl: string;
1306
+ routeId: string;
1307
+ hostCredential: string;
1308
+ request?: typeof globalThis.fetch;
1309
+ timeoutMs?: number;
1310
+ }
1311
+ /** Host-authenticated provisioning uses HTTPS; routing credentials never appear in URLs or logs. */
1312
+ declare function createRelayDeviceProvisioner(options: RelayDeviceProvisionerOptions): RelayDeviceProvisioner;
1313
+
1314
+ interface RelayPairingPackage {
1315
+ v: 1;
1316
+ label: string;
1317
+ relayUrl: string;
1318
+ routeId: string;
1319
+ deviceId: string;
1320
+ deviceCredential: string;
1321
+ deviceToken: string;
1322
+ pairingSecret: string;
1323
+ expiresAt: number;
1324
+ hostIdentityPublicKey: string;
1325
+ hostIdentityFingerprint: string;
1326
+ }
1327
+ interface RelayPairingBootstrap {
1328
+ appUrl: string;
1329
+ label: string;
1330
+ relayUrl: string;
1331
+ routeId: string;
1332
+ hostIdentityPublicKey: string;
1333
+ hostIdentityFingerprint: string;
1334
+ provisioner: RelayDeviceProvisioner;
1335
+ generateDeviceCredential?: () => string;
1336
+ }
1337
+ declare function normalizeRelayAppUrl(value: string): string;
1338
+ declare function buildRelayPairingUrl(appUrl: string, pairing: RelayPairingPackage): string;
1339
+
1340
+ declare const BLIND_RELAY_PROTOCOL_VERSION: 1;
1341
+ declare const BLIND_RELAY_DEFAULT_MAX_FRAME_BYTES = 1500000;
1342
+ declare const BLIND_RELAY_DEFAULT_MAX_QUEUE_BYTES = 4000000;
1343
+ declare const BLIND_RELAY_DEFAULT_MAX_TOTAL_CONNECTIONS = 1024;
1344
+ declare const BLIND_RELAY_DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 64;
1345
+ declare const BLIND_RELAY_DEFAULT_MAX_BYTES_PER_MINUTE: number;
1346
+ declare const BLIND_RELAY_DEFAULT_MAX_MESSAGES_PER_MINUTE = 12000;
1347
+ interface CreateBlindRelayOptions {
1348
+ rootToken: string;
1349
+ /** Previous provisioning capabilities accepted only during an explicit, bounded zero-downtime rotation window. */
1350
+ previousRootTokens?: string[];
1351
+ store?: RelayRouteStore;
1352
+ /** Optional hosted control plane. Self-hosted root-only routing remains available without it. */
1353
+ accountStore?: RelayAccountStore;
1354
+ allowedOrigins?: string[];
1355
+ handshakeTimeoutMs?: number;
1356
+ idleTimeoutMs?: number;
1357
+ maxFrameBytes?: number;
1358
+ maxQueueBytes?: number;
1359
+ maxTotalConnections?: number;
1360
+ maxConnectionsPerRoute?: number;
1361
+ maxBytesPerMinute?: number;
1362
+ maxMessagesPerMinute?: number;
1363
+ now?: () => number;
1364
+ generateChannelId?: () => string;
1365
+ }
1366
+ interface BlindRelayMetrics {
1367
+ activeConnections: number;
1368
+ activeHosts: number;
1369
+ activeDevices: number;
1370
+ acceptedConnections: number;
1371
+ rejectedConnections: number;
1372
+ forwardedFrames: number;
1373
+ forwardedBytes: number;
1374
+ droppedFrames: number;
1375
+ }
1376
+ interface BlindRelayServer {
1377
+ app: FastifyInstance;
1378
+ store: RelayRouteStore;
1379
+ accountStore?: RelayAccountStore;
1380
+ metrics(): BlindRelayMetrics;
1381
+ }
1382
+ declare function createBlindRelayServer(options: CreateBlindRelayOptions): BlindRelayServer;
1383
+
1384
+ interface StartedBlindRelay extends BlindRelayServer {
1385
+ url: string;
1386
+ }
1387
+ declare function startBlindRelay(env?: NodeJS.ProcessEnv): Promise<StartedBlindRelay>;
1388
+ declare function isRelayDirectExecution(moduleUrl: string, argv1: string | undefined, embeddedContainerBuild?: boolean): boolean;
1389
+
1390
+ /** RPC uses JSON inside an encrypted frame inside a base64 relay envelope. Larger data uses chunked streams. */
1391
+ declare const RELAY_RPC_MAX_BODY_BYTES: number;
1392
+ declare const RELAY_RPC_MAX_PATH_BYTES = 4096;
1393
+ type RelayRpcMethod = "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE";
1394
+ interface RelayRpcRequest {
1395
+ id: string;
1396
+ method: RelayRpcMethod;
1397
+ path: string;
1398
+ headers: Record<string, string>;
1399
+ body?: Buffer;
1400
+ }
1401
+ interface RelayRpcResponse {
1402
+ id: string;
1403
+ status: number;
1404
+ headers: Record<string, string>;
1405
+ body?: string;
1406
+ }
1407
+ declare function parseRelayRpcRequest(value: unknown): RelayRpcRequest;
1408
+ declare function relayRpcResponse(input: {
1409
+ id: string;
1410
+ status: number;
1411
+ headers: Record<string, string | string[] | number | undefined>;
1412
+ body?: Uint8Array;
1413
+ }): RelayRpcResponse;
1414
+
1415
+ type RelayHostStatus = "idle" | "connecting" | "online" | "reconnecting" | "stopped";
1416
+ interface RelayHostMetrics {
1417
+ status: RelayHostStatus;
1418
+ activeChannels: number;
1419
+ acceptedChannels: number;
1420
+ rejectedChannels: number;
1421
+ completedRequests: number;
1422
+ failedRequests: number;
1423
+ reconnects: number;
1424
+ activeTransfers: number;
1425
+ completedTransfers: number;
1426
+ failedTransfers: number;
1427
+ streamedRequestBytes: number;
1428
+ streamedResponseBytes: number;
1429
+ }
1430
+ interface RelayHostConnectorOptions {
1431
+ relayUrl: string;
1432
+ routeId: string;
1433
+ hostCredential: string;
1434
+ hostIdentity: RelayIdentity;
1435
+ devices: DeviceStore;
1436
+ dispatchRequest(token: string, request: RelayRpcRequest): Promise<RelayRpcResponse>;
1437
+ openTerminal?: RelayTerminalOpener;
1438
+ openHttp?: RelayHttpOpener;
1439
+ WebSocketClass?: typeof WebSocket;
1440
+ now?: () => number;
1441
+ random?: () => number;
1442
+ handshakeTimeoutMs?: number;
1443
+ requestTimeoutMs?: number;
1444
+ maxStreamRequestBytes?: number;
1445
+ onStatus?: (status: RelayHostStatus) => void;
1446
+ /** Promotes a five-minute broker bootstrap credential after the E2E pairing claim commits. */
1447
+ promoteDevice?: (deviceId: string, credentialHash: string) => Promise<void>;
1448
+ }
1449
+ interface RelayTerminalOpenRequest {
1450
+ streamId: string;
1451
+ sessionId: string;
1452
+ cols?: number;
1453
+ rows?: number;
1454
+ respawn?: "continue" | "fresh";
1455
+ }
1456
+ interface RelayTerminalHandlers {
1457
+ onBinary(data: Uint8Array): void;
1458
+ onControl(data: string): void;
1459
+ onClose(code: number): void;
1460
+ }
1461
+ interface RelayTerminalBridge {
1462
+ send(data: string): void;
1463
+ close(): void;
1464
+ }
1465
+ type RelayTerminalOpener = (token: string, request: RelayTerminalOpenRequest, handlers: RelayTerminalHandlers) => Promise<RelayTerminalBridge>;
1466
+ interface RelayHttpOpenRequest {
1467
+ streamId: string;
1468
+ method: RelayRpcRequest["method"];
1469
+ path: string;
1470
+ headers: Record<string, string>;
1471
+ }
1472
+ interface RelayHttpResponseHead {
1473
+ status: number;
1474
+ headers: Record<string, string>;
1475
+ }
1476
+ interface RelayHttpHandlers {
1477
+ onResponse(response: RelayHttpResponseHead): void | Promise<void>;
1478
+ onData(data: Uint8Array): void | Promise<void>;
1479
+ onEnd(): void | Promise<void>;
1480
+ onError(error: Error): void;
1481
+ }
1482
+ interface RelayHttpBridge {
1483
+ write(data: Uint8Array): Promise<void>;
1484
+ end(): void;
1485
+ close(): void;
1486
+ }
1487
+ type RelayHttpOpener = (token: string, request: RelayHttpOpenRequest, handlers: RelayHttpHandlers) => Promise<RelayHttpBridge>;
1488
+ interface RelayHostConnector {
1489
+ start(): void;
1490
+ stop(): Promise<void>;
1491
+ waitUntilReady(timeoutMs?: number): Promise<void>;
1492
+ closeDevice(deviceId: string): void;
1493
+ metrics(): RelayHostMetrics;
1494
+ }
1495
+ declare function relayConnectUrl(raw: string): string;
1496
+ declare function createRelayHostConnector(options: RelayHostConnectorOptions): RelayHostConnector;
1497
+
1498
+ interface RelayHostRuntimeConfig {
1499
+ relayUrl: string;
1500
+ routeId: string;
1501
+ hostCredential: string;
1502
+ hostIdentity: RelayIdentity;
1503
+ /** Static PWA origin used only for one-use remote pairing links. */
1504
+ appUrl?: string;
1505
+ hostLabel: string;
1506
+ }
1507
+ interface PersistedRelayHostConfig {
1508
+ version: 1;
1509
+ relayUrl: string;
1510
+ routeId: string;
1511
+ hostCredential: string;
1512
+ appUrl?: string;
1513
+ hostLabel: string;
1514
+ }
1515
+ type RelayHostConfigInput = Omit<PersistedRelayHostConfig, "version">;
1516
+ declare function relayHostConfigPath(dataDir: string): string;
1517
+ declare function readRelayHostConfig(dataDir: string): PersistedRelayHostConfig | undefined;
1518
+ declare function writeRelayHostConfig(dataDir: string, input: RelayHostConfigInput): PersistedRelayHostConfig;
1519
+ declare function removeRelayHostConfig(dataDir: string): boolean;
1520
+ declare function resolveRelayHostConfig(env: NodeJS.ProcessEnv, dataDir: string): RelayHostRuntimeConfig | undefined;
1521
+
1522
+ interface LoopbackRelayTerminalOptions {
1523
+ baseUrl(): string | undefined;
1524
+ issueTicket(token: string): Promise<string>;
1525
+ WebSocketClass?: typeof WebSocket;
1526
+ openTimeoutMs?: number;
1527
+ }
1528
+ declare function createLoopbackRelayTerminalOpener(options: LoopbackRelayTerminalOptions): RelayTerminalOpener;
1529
+
1530
+ interface LoopbackRelayHttpOptions {
1531
+ baseUrl(): string | undefined;
1532
+ headers(token: string, requestHeaders: Record<string, string>): Record<string, string>;
1533
+ idleTimeoutMs?: number;
1534
+ }
1535
+ /**
1536
+ * Streams an authenticated relay request through the real loopback HTTP server. This deliberately traverses
1537
+ * the normal auth, RBAC, rate-limit, idempotency, audit, multipart, and route hooks instead of calling file
1538
+ * services out of band.
1539
+ */
1540
+ declare function createLoopbackRelayHttpOpener(options: LoopbackRelayHttpOptions): RelayHttpOpener;
1541
+
1542
+ declare const RELAY_WIRE_PROTOCOL_VERSION: 1;
1543
+ declare const RELAY_WIRE_MAX_ENVELOPE_BYTES = 1400000;
1544
+ type RelayWireEnvelope = {
1545
+ v: 1;
1546
+ t: "device-hello";
1547
+ hello: RelayHandshakeHello;
1548
+ identityPublicKey?: string;
1549
+ } | {
1550
+ v: 1;
1551
+ t: "host-hello";
1552
+ hello: RelayHandshakeHello;
1553
+ } | {
1554
+ v: 1;
1555
+ t: "cipher";
1556
+ frame: RelayEncryptedFrame;
1557
+ };
1558
+ declare function encodeRelayWireEnvelope(envelope: RelayWireEnvelope): string;
1559
+ declare function decodeRelayWireEnvelope(payload: unknown): RelayWireEnvelope;
1560
+
1561
+ type PolicyStoreMode = "sqlite" | "memory-fallback";
1562
+ type ExtensionPolicyMode = "allow-integrity" | "signed-only" | "deny";
1563
+ type UpdatePolicyMode = "stable-only" | "deny";
1564
+ interface EnterprisePolicy {
1565
+ enforcementEnabled: boolean;
1566
+ allowedHostIds: string[] | null;
1567
+ allowedWorkspaceIds: string[] | null;
1568
+ allowedProviderIds: string[] | null;
1569
+ allowDangerousProviderModes: boolean;
1570
+ allowFileTransfer: boolean;
1571
+ extensionMode: ExtensionPolicyMode;
1572
+ allowRelay: boolean;
1573
+ updateMode: UpdatePolicyMode;
1574
+ revision: number;
1575
+ createdAt: number;
1576
+ updatedAt: number;
1577
+ }
1578
+ interface EnterprisePolicyUpdate {
1579
+ enforcementEnabled?: boolean;
1580
+ allowedHostIds?: string[] | null;
1581
+ allowedWorkspaceIds?: string[] | null;
1582
+ allowedProviderIds?: string[] | null;
1583
+ allowDangerousProviderModes?: boolean;
1584
+ allowFileTransfer?: boolean;
1585
+ extensionMode?: ExtensionPolicyMode;
1586
+ allowRelay?: boolean;
1587
+ updateMode?: UpdatePolicyMode;
1588
+ }
1589
+ type EnterprisePolicyAction = "access" | "session.launch" | "file.transfer" | "extension.mutate" | "relay.access" | "update.mutate";
1590
+ interface EnterprisePolicyContext {
1591
+ hostId: string;
1592
+ workspaceId?: string;
1593
+ providerId?: string;
1594
+ dangerousProviderMode?: boolean;
1595
+ extensionTrust?: "signed" | "integrity";
1596
+ updateChannel?: "stable" | "beta" | "nightly";
1597
+ }
1598
+ interface EnterprisePolicyDecision {
1599
+ allowed: boolean;
1600
+ reason: "not-enforced" | "allowed" | "host-denied" | "workspace-denied" | "provider-denied" | "dangerous-mode-denied" | "file-transfer-denied" | "extension-denied" | "extension-signature-required" | "relay-denied" | "updates-denied" | "update-channel-denied";
1601
+ }
1602
+ interface PolicyStore {
1603
+ readonly mode: PolicyStoreMode;
1604
+ get(): EnterprisePolicy;
1605
+ update(input: EnterprisePolicyUpdate, expectedRevision: number, now?: number): EnterprisePolicy;
1606
+ close(): void;
1607
+ }
1608
+ interface OpenPolicyStoreOptions {
1609
+ dbPath: string;
1610
+ loadDatabase?: () => typeof better_sqlite3;
1611
+ now?: number;
1612
+ }
1613
+ declare class EnterprisePolicyRevisionConflictError extends Error {
1614
+ readonly current: EnterprisePolicy;
1615
+ constructor(current: EnterprisePolicy);
1616
+ }
1617
+ declare function evaluateEnterprisePolicy(policy: EnterprisePolicy, action: EnterprisePolicyAction, context: EnterprisePolicyContext): EnterprisePolicyDecision;
1618
+ declare function openPolicyStore(options: OpenPolicyStoreOptions): PolicyStore;
1619
+
1620
+ type PeerStoreMode = "sqlite" | "memory-fallback";
1621
+ type PeerAction = "read" | "send" | "wait" | "start" | "focus";
1622
+ type PeerStatus = "active" | "suspended";
1623
+ interface PeerRecord {
1624
+ id: string;
1625
+ label: string;
1626
+ remoteHostId: string;
1627
+ remoteVersion: string;
1628
+ actions: PeerAction[];
1629
+ allowedWorkspaceIds: string[] | null;
1630
+ status: PeerStatus;
1631
+ revision: number;
1632
+ createdAt: number;
1633
+ updatedAt: number;
1634
+ lastVerifiedAt: number;
1635
+ }
1636
+ interface PeerConnection extends PeerRecord {
1637
+ baseUrl: string;
1638
+ credential: string;
1639
+ }
1640
+ interface CreatePeerInput {
1641
+ label: string;
1642
+ baseUrl: string;
1643
+ credential: string;
1644
+ remoteHostId: string;
1645
+ remoteVersion: string;
1646
+ actions?: PeerAction[];
1647
+ allowedWorkspaceIds?: string[] | null;
1648
+ }
1649
+ interface UpdatePeerInput {
1650
+ label?: string;
1651
+ actions?: PeerAction[];
1652
+ allowedWorkspaceIds?: string[] | null;
1653
+ status?: PeerStatus;
1654
+ remoteVersion?: string;
1655
+ lastVerifiedAt?: number;
1656
+ }
1657
+ interface PeerStore {
1658
+ readonly mode: PeerStoreMode;
1659
+ list(): PeerRecord[];
1660
+ get(id: string): PeerRecord | undefined;
1661
+ connection(id: string): PeerConnection | undefined;
1662
+ create(input: CreatePeerInput, now?: number): PeerRecord;
1663
+ update(id: string, input: UpdatePeerInput, expectedRevision: number, now?: number): PeerRecord | undefined;
1664
+ rotateCredential(id: string, input: {
1665
+ credential: string;
1666
+ remoteVersion: string;
1667
+ lastVerifiedAt?: number;
1668
+ }, expectedRevision: number, now?: number): PeerRecord | undefined;
1669
+ remove(id: string): boolean;
1670
+ close(): void;
1671
+ }
1672
+ interface OpenPeerStoreOptions {
1673
+ dbPath: string;
1674
+ generatePeerId?: () => string;
1675
+ loadDatabase?: () => typeof better_sqlite3;
1676
+ }
1677
+ declare class PeerRevisionConflictError extends Error {
1678
+ readonly current: PeerRecord;
1679
+ constructor(current: PeerRecord);
1680
+ }
1681
+ declare function normalizePeerBaseUrl(value: unknown): string;
1682
+ declare function openPeerStore(options: OpenPeerStoreOptions): PeerStore;
1683
+
1684
+ interface VerifiedPeerIdentity {
1685
+ remoteHostId: string;
1686
+ remoteVersion: string;
1687
+ remoteLabel: string;
1688
+ protocolVersion: number;
1689
+ }
1690
+ interface PeerJsonResponse {
1691
+ status: number;
1692
+ body: unknown;
1693
+ }
1694
+ declare class PeerRequestError extends Error {
1695
+ readonly status?: number | undefined;
1696
+ readonly remoteCode?: string | undefined;
1697
+ constructor(message: string, status?: number | undefined, remoteCode?: string | undefined);
1698
+ }
1699
+ declare function requestPeerJson(connection: Pick<PeerConnection, "baseUrl" | "credential">, path: string, init?: {
1700
+ method?: "GET" | "POST";
1701
+ body?: unknown;
1702
+ idempotencyKey?: string;
1703
+ fetch?: typeof globalThis.fetch;
1704
+ timeoutMs?: number;
1705
+ }): Promise<PeerJsonResponse>;
1706
+ declare function verifyPeerConnection(input: {
1707
+ baseUrl: string;
1708
+ credential: string;
1709
+ localHostId: string;
1710
+ fetch?: typeof globalThis.fetch;
1711
+ }): Promise<VerifiedPeerIdentity>;
1712
+
1713
+ type CommandCenterStoreMode = "sqlite" | "memory-fallback";
1714
+ type WorkspaceKind = "directory" | "worktree";
1715
+ type AgentActivity = "blocked" | "working" | "done" | "idle" | "ended" | "unknown";
1716
+ type AttentionKind = "blocked" | "done" | "error" | "file" | "policy";
1717
+ type AttentionState = "open" | "acknowledged" | "snoozed" | "resolved";
1718
+ interface HostRecord {
1719
+ id: string;
1720
+ label: string;
1721
+ createdAt: number;
1722
+ updatedAt: number;
1723
+ }
1724
+ interface WorkspaceRecord {
1725
+ id: string;
1726
+ label: string;
1727
+ cwd: string;
1728
+ kind: WorkspaceKind;
1729
+ sortOrder: number;
1730
+ createdAt: number;
1731
+ updatedAt: number;
1732
+ archivedAt?: number;
1733
+ }
1734
+ interface SessionPlacement {
1735
+ sessionId: string;
1736
+ workspaceId: string;
1737
+ agentId: string;
1738
+ createdAt: number;
1739
+ }
1740
+ interface AgentRecord {
1741
+ id: string;
1742
+ sessionId: string;
1743
+ workspaceId: string;
1744
+ provider: string;
1745
+ activity: AgentActivity;
1746
+ createdAt: number;
1747
+ updatedAt: number;
1748
+ }
1749
+ interface AttentionItem {
1750
+ id: string;
1751
+ workspaceId: string;
1752
+ sessionId: string;
1753
+ agentId: string;
1754
+ kind: AttentionKind;
1755
+ state: AttentionState;
1756
+ title: string;
1757
+ detail?: string;
1758
+ urgency: number;
1759
+ occurrenceCount: number;
1760
+ createdAt: number;
1761
+ updatedAt: number;
1762
+ acknowledgedAt?: number;
1763
+ snoozedUntil?: number;
1764
+ resolvedAt?: number;
1765
+ }
1766
+ interface CommandEvent {
1767
+ id: number;
1768
+ type: string;
1769
+ resourceType: string;
1770
+ resourceId: string;
1771
+ payload: Record<string, unknown>;
1772
+ createdAt: number;
1773
+ }
1774
+ interface CommandLayoutEnvelope {
1775
+ document: Record<string, unknown> | null;
1776
+ revision: number;
1777
+ updatedAt?: number;
1778
+ }
1779
+ declare class CommandCenterRevisionConflictError extends Error {
1780
+ readonly current: CommandLayoutEnvelope;
1781
+ constructor(current: CommandLayoutEnvelope);
1782
+ }
1783
+ interface CreateWorkspaceInput {
1784
+ cwd: string;
1785
+ label?: string;
1786
+ kind?: WorkspaceKind;
1787
+ }
1788
+ interface UpdateWorkspaceInput {
1789
+ label?: string;
1790
+ sortOrder?: number;
1791
+ archived?: boolean;
1792
+ }
1793
+ interface UpsertAgentInput {
1794
+ sessionId: string;
1795
+ workspaceId: string;
1796
+ provider: string;
1797
+ activity: AgentActivity;
1798
+ createdAt: number;
1799
+ }
1800
+ interface RecordAttentionInput {
1801
+ workspaceId: string;
1802
+ sessionId: string;
1803
+ agentId: string;
1804
+ kind: AttentionKind;
1805
+ title: string;
1806
+ detail?: string;
1807
+ urgency?: number;
1808
+ /** Stable transition identity, e.g. `blocked:<sessionId>`. Only one unresolved item exists per key. */
1809
+ dedupeKey: string;
1810
+ }
1811
+ interface CommandCenterStore {
1812
+ readonly mode: CommandCenterStoreMode;
1813
+ getHost(): HostRecord;
1814
+ renameHost(label: string, now?: number): HostRecord;
1815
+ listWorkspaces(opts?: {
1816
+ includeArchived?: boolean;
1817
+ }): WorkspaceRecord[];
1818
+ getWorkspace(id: string): WorkspaceRecord | undefined;
1819
+ createWorkspace(input: CreateWorkspaceInput, now?: number): WorkspaceRecord;
1820
+ updateWorkspace(id: string, input: UpdateWorkspaceInput, now?: number): WorkspaceRecord | undefined;
1821
+ ensureSession(sessionId: string, cwd: string, now?: number): SessionPlacement;
1822
+ placementForSession(sessionId: string): SessionPlacement | undefined;
1823
+ removeSession(sessionId: string, now?: number): void;
1824
+ upsertAgent(input: UpsertAgentInput, now?: number): AgentRecord;
1825
+ getAgent(id: string): AgentRecord | undefined;
1826
+ listAgents(): AgentRecord[];
1827
+ recordAttention(input: RecordAttentionInput, now?: number): AttentionItem;
1828
+ listAttention(opts?: {
1829
+ includeResolved?: boolean;
1830
+ includeSnoozed?: boolean;
1831
+ now?: number;
1832
+ }): AttentionItem[];
1833
+ acknowledgeAttention(id: string, now?: number): AttentionItem | undefined;
1834
+ snoozeAttention(id: string, until: number, now?: number): AttentionItem | undefined;
1835
+ resolveAttention(id: string, now?: number): AttentionItem | undefined;
1836
+ resolveAttentionByDedupeKey(dedupeKey: string, now?: number): number;
1837
+ markSessionViewed(sessionId: string, now?: number): number;
1838
+ appendEvent(type: string, resourceType: string, resourceId: string, payload?: Record<string, unknown>, now?: number): CommandEvent;
1839
+ listEvents(afterId?: number, limit?: number): CommandEvent[];
1840
+ eventBounds(): {
1841
+ earliest: number;
1842
+ latest: number;
1843
+ };
1844
+ subscribeEvents(listener: (event: CommandEvent) => void): () => void;
1845
+ getLayout(): CommandLayoutEnvelope;
1846
+ putLayout(document: Record<string, unknown>, expectedRevision: number, now?: number): CommandLayoutEnvelope;
1847
+ close(): void;
1848
+ }
1849
+ interface OpenCommandCenterStoreOptions {
1850
+ dbPath: string;
1851
+ hostLabel?: string;
1852
+ generateHostId?: () => string;
1853
+ generateWorkspaceId?: () => string;
1854
+ generateAttentionId?: () => string;
1855
+ /** Test/embedding seam; throwing selects the non-durable in-memory fallback. */
1856
+ loadDatabase?: () => typeof better_sqlite3;
1857
+ }
1858
+ declare function normalizeLabel(value: unknown): string | undefined;
1859
+ declare function agentIdForSession(sessionId: string): string;
1860
+ declare function openCommandCenterStore(opts: OpenCommandCenterStoreOptions): CommandCenterStore;
1861
+ /** Exposed for API adapters and tests; one current RoamCode session owns one current agent. */
1862
+ declare const currentAgentIdForSession: typeof agentIdForSession;
1863
+ /** Re-exported validation keeps transport and stores on one label contract. */
1864
+ declare const normalizeCommandCenterLabel: typeof normalizeLabel;
1865
+
1866
+ type WorktreeErrorCode = "WORKTREE_OUTSIDE_ROOT" | "WORKTREE_INVALID_REF" | "WORKTREE_NOT_FOUND" | "WORKTREE_NOT_REGISTERED" | "WORKTREE_MAIN_PROTECTED" | "WORKTREE_DIRTY" | "WORKTREE_EXISTS" | "WORKTREE_GIT_FAILED" | "WORKTREE_TIMEOUT";
1867
+ declare class WorktreeError extends Error {
1868
+ readonly code: WorktreeErrorCode;
1869
+ readonly statusCode: number;
1870
+ constructor(code: WorktreeErrorCode, message: string, statusCode?: number);
1871
+ }
1872
+ interface WorktreeRecord {
1873
+ path: string;
1874
+ repositoryPath: string;
1875
+ branch?: string;
1876
+ head: string;
1877
+ dirty: boolean;
1878
+ changedFiles: number;
1879
+ isMain: boolean;
1880
+ }
1881
+ interface CreateWorktreeInput {
1882
+ repositoryPath: string;
1883
+ path: string;
1884
+ branch?: string;
1885
+ baseRef?: string;
1886
+ }
1887
+ interface CreateWorktreeResult {
1888
+ worktree: WorktreeRecord;
1889
+ created: boolean;
1890
+ }
1891
+ interface WorktreeService {
1892
+ inspect(path: string): Promise<WorktreeRecord>;
1893
+ create(input: CreateWorktreeInput): Promise<CreateWorktreeResult>;
1894
+ remove(path: string, force?: boolean): Promise<WorktreeRecord>;
1895
+ }
1896
+ interface CreateWorktreeServiceOptions {
1897
+ fsRoot: string;
1898
+ gitBin?: string;
1899
+ timeoutMs?: number;
1900
+ maxOutputBytes?: number;
1901
+ }
1902
+ declare function createWorktreeService(options: CreateWorktreeServiceOptions): WorktreeService;
1903
+
1904
+ type ExtensionKind = "adapter" | "plugin";
1905
+ type ExtensionTrust = "signed" | "integrity";
1906
+ declare class ExtensionError extends Error {
1907
+ readonly code: "EXTENSION_INVALID" | "EXTENSION_OUTSIDE_ROOT" | "EXTENSION_INTEGRITY_MISMATCH" | "EXTENSION_SIGNATURE_INVALID" | "EXTENSION_NOT_FOUND" | "EXTENSION_VERSION_NOT_FOUND" | "EXTENSION_ROLLBACK_UNAVAILABLE" | "EXTENSION_IN_USE" | "EXTENSION_PERMISSION_DENIED";
1908
+ readonly statusCode: number;
1909
+ constructor(code: "EXTENSION_INVALID" | "EXTENSION_OUTSIDE_ROOT" | "EXTENSION_INTEGRITY_MISMATCH" | "EXTENSION_SIGNATURE_INVALID" | "EXTENSION_NOT_FOUND" | "EXTENSION_VERSION_NOT_FOUND" | "EXTENSION_ROLLBACK_UNAVAILABLE" | "EXTENSION_IN_USE" | "EXTENSION_PERMISSION_DENIED", message: string, statusCode?: number);
1910
+ }
1911
+ declare const pluginPermissionSchema: z.ZodEnum<{
1912
+ "notifications:write": "notifications:write";
1913
+ "worktrees:write": "worktrees:write";
1914
+ "ci:read": "ci:read";
1915
+ "releases:read": "releases:read";
1916
+ "events:read": "events:read";
1917
+ "events:write": "events:write";
1918
+ }>;
1919
+ declare const pluginManifestSchema: z.ZodObject<{
1920
+ schemaVersion: z.ZodLiteral<1>;
1921
+ kind: z.ZodLiteral<"plugin">;
1922
+ id: z.ZodString;
1923
+ version: z.ZodString;
1924
+ displayName: z.ZodString;
1925
+ description: z.ZodString;
1926
+ platforms: z.ZodArray<z.ZodEnum<{
1927
+ darwin: "darwin";
1928
+ linux: "linux";
1929
+ }>>;
1930
+ minimumRoamCodeVersion: z.ZodOptional<z.ZodString>;
1931
+ permissions: z.ZodArray<z.ZodEnum<{
1932
+ "notifications:write": "notifications:write";
1933
+ "worktrees:write": "worktrees:write";
1934
+ "ci:read": "ci:read";
1935
+ "releases:read": "releases:read";
1936
+ "events:read": "events:read";
1937
+ "events:write": "events:write";
1938
+ }>>;
1939
+ actions: z.ZodArray<z.ZodObject<{
1940
+ id: z.ZodString;
1941
+ title: z.ZodString;
1942
+ description: z.ZodOptional<z.ZodString>;
1943
+ entrypoint: z.ZodString;
1944
+ args: z.ZodDefault<z.ZodArray<z.ZodString>>;
1945
+ cwd: z.ZodDefault<z.ZodEnum<{
1946
+ workspace: "workspace";
1947
+ host: "host";
1948
+ explicit: "explicit";
1949
+ }>>;
1950
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
1951
+ maxOutputBytes: z.ZodDefault<z.ZodNumber>;
1952
+ permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<{
1953
+ "notifications:write": "notifications:write";
1954
+ "worktrees:write": "worktrees:write";
1955
+ "ci:read": "ci:read";
1956
+ "releases:read": "releases:read";
1957
+ "events:read": "events:read";
1958
+ "events:write": "events:write";
1959
+ }>>>;
1960
+ }, z.core.$strict>>;
1961
+ eventHooks: z.ZodDefault<z.ZodArray<z.ZodObject<{
1962
+ event: z.ZodString;
1963
+ actionId: z.ZodString;
1964
+ }, z.core.$strict>>>;
1965
+ settingsSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1966
+ ui: z.ZodOptional<z.ZodObject<{
1967
+ label: z.ZodString;
1968
+ route: z.ZodString;
1969
+ }, z.core.$strict>>;
1970
+ }, z.core.$strict>;
1971
+ declare const adapterRuntimeSchema: z.ZodObject<{
1972
+ executable: z.ZodString;
1973
+ probeArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
1974
+ probeTimeoutMs: z.ZodDefault<z.ZodNumber>;
1975
+ launchArgs: z.ZodArray<z.ZodString>;
1976
+ resumeArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
1977
+ env: z.ZodDefault<z.ZodArray<z.ZodEnum<{
1978
+ HOME: "HOME";
1979
+ PATH: "PATH";
1980
+ LANG: "LANG";
1981
+ LC_ALL: "LC_ALL";
1982
+ TMPDIR: "TMPDIR";
1983
+ SHELL: "SHELL";
1984
+ }>>>;
1985
+ workingPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
1986
+ blockedPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
1987
+ idlePatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
1988
+ identityPattern: z.ZodOptional<z.ZodString>;
1989
+ }, z.core.$strict>;
1990
+ type PluginPermission = z.infer<typeof pluginPermissionSchema>;
1991
+ type PluginManifestV1 = z.infer<typeof pluginManifestSchema>;
1992
+ type AdapterRuntimeV1 = z.infer<typeof adapterRuntimeSchema>;
1993
+ interface AdapterPackageManifestV1 {
1994
+ schemaVersion: 1;
1995
+ kind: "adapter";
1996
+ adapter: AdapterManifestV1;
1997
+ runtime: AdapterRuntimeV1;
1998
+ }
1999
+ type ExtensionManifestV1 = PluginManifestV1 | AdapterPackageManifestV1;
2000
+ interface ExtensionVersionRecord {
2001
+ kind: ExtensionKind;
2002
+ id: string;
2003
+ version: string;
2004
+ manifest: ExtensionManifestV1;
2005
+ integrity: string;
2006
+ trust: ExtensionTrust;
2007
+ signerFingerprint?: string;
2008
+ source: string;
2009
+ installedAt: number;
2010
+ }
2011
+ interface InstalledExtension {
2012
+ kind: ExtensionKind;
2013
+ id: string;
2014
+ enabled: boolean;
2015
+ currentVersion: string;
2016
+ previousVersion?: string;
2017
+ updatedAt: number;
2018
+ approvedPermissions: string[];
2019
+ current: ExtensionVersionRecord;
2020
+ versions: ExtensionVersionRecord[];
2021
+ }
2022
+ interface InstallExtensionInput {
2023
+ sourceDirectory: string;
2024
+ expectedIntegrity: string;
2025
+ signature?: string;
2026
+ publicKey?: string;
2027
+ source?: string;
2028
+ allowUnsigned?: boolean;
2029
+ }
2030
+ interface ExtensionManager {
2031
+ readonly mode: "sqlite" | "memory-fallback";
2032
+ list(kind?: ExtensionKind): InstalledExtension[];
2033
+ get(kind: ExtensionKind, id: string): InstalledExtension | undefined;
2034
+ packagePath(kind: ExtensionKind, id: string, version?: string): string;
2035
+ verify(kind: ExtensionKind, id: string, version?: string): Promise<boolean>;
2036
+ install(input: InstallExtensionInput): Promise<InstalledExtension>;
2037
+ setEnabled(kind: ExtensionKind, id: string, enabled: boolean, approvedPermissions?: string[]): InstalledExtension;
2038
+ rollback(kind: ExtensionKind, id: string): InstalledExtension;
2039
+ uninstall(kind: ExtensionKind, id: string, options?: {
2040
+ purgeState?: boolean;
2041
+ }): Promise<boolean>;
2042
+ close(): void;
2043
+ }
2044
+ interface OpenExtensionManagerOptions {
2045
+ dbPath: string;
2046
+ packagesDir: string;
2047
+ fsRoot: string;
2048
+ now?: () => number;
2049
+ loadDatabase?: () => typeof better_sqlite3;
2050
+ }
2051
+ declare function inspectExtensionPackage(sourceDirectory: string, fsRoot: string): Promise<{
2052
+ manifest: ExtensionManifestV1;
2053
+ integrity: string;
2054
+ }>;
2055
+ declare function openExtensionManager(options: OpenExtensionManagerOptions): ExtensionManager;
2056
+ interface MarketplaceEntry {
2057
+ kind: ExtensionKind;
2058
+ id: string;
2059
+ version: string;
2060
+ displayName: string;
2061
+ description: string;
2062
+ trust: "verified" | "community" | "local";
2063
+ compatibility: {
2064
+ platforms: Array<"darwin" | "linux">;
2065
+ minimumRoamCodeVersion?: string;
2066
+ };
2067
+ changelog?: string;
2068
+ source: string;
2069
+ integrity: string;
2070
+ signerFingerprint?: string;
2071
+ reportUrl?: string;
2072
+ }
2073
+ declare function parseMarketplaceIndex(value: unknown): MarketplaceEntry[];
2074
+ declare function searchMarketplace(entries: MarketplaceEntry[], query: string, platform?: NodeJS.Platform): MarketplaceEntry[];
2075
+
2076
+ declare class PluginRuntimeError extends Error {
2077
+ readonly code: "PLUGIN_NOT_FOUND" | "PLUGIN_DISABLED" | "PLUGIN_ACTION_NOT_FOUND" | "PLUGIN_PERMISSION_DENIED" | "PLUGIN_PATH_DENIED" | "PLUGIN_OUTPUT_LIMIT" | "PLUGIN_TIMEOUT" | "PLUGIN_FAILED";
2078
+ readonly statusCode: number;
2079
+ constructor(code: "PLUGIN_NOT_FOUND" | "PLUGIN_DISABLED" | "PLUGIN_ACTION_NOT_FOUND" | "PLUGIN_PERMISSION_DENIED" | "PLUGIN_PATH_DENIED" | "PLUGIN_OUTPUT_LIMIT" | "PLUGIN_TIMEOUT" | "PLUGIN_FAILED", message: string, statusCode?: number);
2080
+ }
2081
+ interface PluginRunInput {
2082
+ pluginId: string;
2083
+ actionId: string;
2084
+ workspacePath?: string;
2085
+ explicitCwd?: string;
2086
+ context?: Record<string, unknown>;
2087
+ }
2088
+ interface PluginRunResult {
2089
+ pluginId: string;
2090
+ pluginVersion: string;
2091
+ actionId: string;
2092
+ status: "succeeded" | "failed";
2093
+ exitCode: number;
2094
+ stdout: string;
2095
+ stderr: string;
2096
+ startedAt: number;
2097
+ finishedAt: number;
2098
+ truncated: false;
2099
+ }
2100
+ interface PluginAuditEvent {
2101
+ pluginId: string;
2102
+ pluginVersion: string;
2103
+ actionId: string;
2104
+ phase: "started" | "finished";
2105
+ result?: "succeeded" | "failed";
2106
+ exitCode?: number;
2107
+ durationMs?: number;
2108
+ }
2109
+ interface CreatePluginRuntimeOptions {
2110
+ extensions: ExtensionManager;
2111
+ fsRoot: string;
2112
+ now?: () => number;
2113
+ audit?: (event: PluginAuditEvent) => void;
2114
+ }
2115
+ interface PluginRuntime {
2116
+ run(input: PluginRunInput): Promise<PluginRunResult>;
2117
+ hooksFor(eventType: string): Array<{
2118
+ pluginId: string;
2119
+ actionId: string;
2120
+ }>;
2121
+ }
2122
+ declare function createPluginRuntime(options: CreatePluginRuntimeOptions): PluginRuntime;
2123
+
2124
+ type ControlStoreMode = "sqlite" | "memory-fallback";
2125
+ type AuditActorType = "host" | "device" | "local" | "automation" | "plugin" | "system";
2126
+ type AuditResult = "success" | "denied" | "error";
2127
+ interface IdempotencyRecord {
2128
+ actorId: string;
2129
+ key: string;
2130
+ fingerprint: string;
2131
+ statusCode: number;
2132
+ body: string;
2133
+ createdAt: number;
2134
+ expiresAt: number;
2135
+ }
2136
+ interface AuditRecord {
2137
+ id: number;
2138
+ actorType: AuditActorType;
2139
+ actorId: string;
2140
+ action: string;
2141
+ targetType: string;
2142
+ targetId?: string;
2143
+ result: AuditResult;
2144
+ metadata: Record<string, string | number | boolean | null>;
2145
+ createdAt: number;
2146
+ previousHash: string;
2147
+ hash: string;
2148
+ }
2149
+ type AutomationTrigger = {
2150
+ eventType: string;
2151
+ resourceType?: string;
2152
+ };
2153
+ type AutomationAction = {
2154
+ type: "acknowledge_attention";
2155
+ target: "event-resource" | string;
2156
+ } | {
2157
+ type: "resolve_attention";
2158
+ target: "event-resource" | string;
2159
+ } | {
2160
+ type: "snooze_attention";
2161
+ target: "event-resource" | string;
2162
+ durationMs: number;
2163
+ } | {
2164
+ type: "emit_event";
2165
+ eventType: string;
2166
+ resourceType: string;
2167
+ resourceId: string;
2168
+ };
2169
+ interface AutomationDefinition {
2170
+ id: string;
2171
+ name: string;
2172
+ enabled: boolean;
2173
+ trigger: AutomationTrigger;
2174
+ action: AutomationAction;
2175
+ permissions: string[];
2176
+ createdAt: number;
2177
+ updatedAt: number;
2178
+ }
2179
+ interface AutomationRun {
2180
+ id: number;
2181
+ automationId: string;
2182
+ eventId?: number;
2183
+ status: "succeeded" | "skipped" | "failed";
2184
+ detail?: string;
2185
+ createdAt: number;
2186
+ }
2187
+ interface CreateAutomationInput {
2188
+ name: string;
2189
+ enabled?: boolean;
2190
+ trigger: AutomationTrigger;
2191
+ action: AutomationAction;
2192
+ permissions: string[];
2193
+ }
2194
+ interface UpdateAutomationInput {
2195
+ name?: string;
2196
+ enabled?: boolean;
2197
+ trigger?: AutomationTrigger;
2198
+ action?: AutomationAction;
2199
+ permissions?: string[];
2200
+ }
2201
+ interface ControlStore {
2202
+ readonly mode: ControlStoreMode;
2203
+ getIdempotency(actorId: string, key: string, now?: number): IdempotencyRecord | undefined;
2204
+ putIdempotency(record: IdempotencyRecord): void;
2205
+ appendAudit(input: Omit<AuditRecord, "id" | "previousHash" | "hash">): AuditRecord;
2206
+ listAudit(afterId?: number, limit?: number): AuditRecord[];
2207
+ listAuditLatest(limit?: number): AuditRecord[];
2208
+ verifyAuditChain(): {
2209
+ valid: boolean;
2210
+ count: number;
2211
+ head: string;
2212
+ };
2213
+ listAutomations(): AutomationDefinition[];
2214
+ getAutomation(id: string): AutomationDefinition | undefined;
2215
+ createAutomation(input: CreateAutomationInput, now?: number): AutomationDefinition;
2216
+ updateAutomation(id: string, input: UpdateAutomationInput, now?: number): AutomationDefinition | undefined;
2217
+ removeAutomation(id: string): boolean;
2218
+ recordAutomationRun(input: Omit<AutomationRun, "id">): AutomationRun;
2219
+ listAutomationRuns(automationId?: string, limit?: number): AutomationRun[];
2220
+ close(): void;
2221
+ }
2222
+ interface OpenControlStoreOptions {
2223
+ dbPath: string;
2224
+ generateAutomationId?: () => string;
2225
+ loadDatabase?: () => typeof better_sqlite3;
2226
+ }
2227
+ declare function normalizeAutomationTrigger(value: unknown): AutomationTrigger | undefined;
2228
+ declare function normalizeAutomationAction(value: unknown): AutomationAction | undefined;
2229
+ declare function normalizeAutomationInput(value: unknown): CreateAutomationInput | undefined;
2230
+ /** Retain only coarse, bounded audit metadata; content- or credential-shaped fields are dropped. */
2231
+ declare function privacySafeAuditMetadata(value: Record<string, unknown> | undefined): Record<string, string | number | boolean | null>;
2232
+ declare function openControlStore(opts: OpenControlStoreOptions): ControlStore;
2233
+ declare const CONTROL_IDEMPOTENCY_TTL_MS: number;
2234
+
597
2235
  /**
598
2236
  * Origin / CSWSH (cross-site WebSocket hijacking) guard for the WS upgrade and state-changing HTTP.
599
2237
  *
@@ -724,6 +2362,11 @@ interface WsTicketStoreOptions {
724
2362
  /** Injectable ticket generator for tests. Default: 32 CSPRNG bytes, base64url. */
725
2363
  generate?: () => string;
726
2364
  }
2365
+ interface WsTicketContext {
2366
+ actorType: "device" | "host" | "local" | "relay";
2367
+ actorId: string;
2368
+ label: string;
2369
+ }
727
2370
  /**
728
2371
  * In-memory single-use ticket store. Deliberately NOT persisted: a ticket outliving a server restart
729
2372
  * would defeat its 30s point, and the client just re-POSTs on reconnect. Expired entries are swept
@@ -731,20 +2374,25 @@ interface WsTicketStoreOptions {
731
2374
  * a timer.
732
2375
  */
733
2376
  declare class WsTicketStore {
734
- /** ticket → expiry (epoch ms). */
2377
+ /** ticket → expiry + the authenticated principal that minted it. */
735
2378
  private readonly tickets;
736
2379
  private readonly ttlMs;
737
2380
  private readonly now;
738
2381
  private readonly generate;
739
2382
  constructor(opts?: WsTicketStoreOptions);
740
2383
  /** Mint a fresh single-use ticket. The response shape is exactly what POST /ws-ticket returns. */
741
- issue(): {
2384
+ issue(context?: WsTicketContext): {
742
2385
  ticket: string;
743
2386
  expiresInMs: number;
744
2387
  };
745
2388
  /** Consume a ticket: true exactly ONCE for a live ticket; false for unknown, expired, or already-used
746
2389
  * (the entry is deleted on first consume, so a replayed WS URL is rejected). */
747
2390
  consume(ticket: string): boolean;
2391
+ /** Consume once and recover the authenticated principal bound at issuance. The wrapper object makes a
2392
+ * context-free legacy/test ticket distinguishable from an invalid ticket. */
2393
+ consumeWithContext(ticket: string): {
2394
+ context?: WsTicketContext;
2395
+ } | undefined;
748
2396
  /** Drop expired entries so an unconsumed flood can't grow the map (lazy — no timer to leak). */
749
2397
  private sweep;
750
2398
  /** TEST ONLY: how many un-consumed tickets are currently held (post-sweep count). */
@@ -760,6 +2408,8 @@ interface PushSubscriptionRecord {
760
2408
  auth: string;
761
2409
  /** Optional session scope; undefined = subscribed to ALL sessions. */
762
2410
  sessionId?: string;
2411
+ /** Per-device credential that created this subscription. Revocation removes its push channel too. */
2412
+ deviceId?: string;
763
2413
  createdAt: number;
764
2414
  }
765
2415
  interface PushStore {
@@ -769,6 +2419,7 @@ interface PushStore {
769
2419
  sessionId?: string;
770
2420
  }): PushSubscriptionRecord[];
771
2421
  remove(endpoint: string): void;
2422
+ removeForDevice(deviceId: string): void;
772
2423
  close(): void;
773
2424
  }
774
2425
  interface OpenPushStoreOptions {
@@ -1498,10 +3149,45 @@ declare function capturePane(opts: CaptureOptions): Promise<string>;
1498
3149
 
1499
3150
  declare class ProviderRegistry {
1500
3151
  private readonly byId;
3152
+ private readonly manifests;
3153
+ private readonly sources;
3154
+ private readonly enabled;
1501
3155
  constructor(providers: readonly AgentProvider[]);
3156
+ register(provider: AgentProvider, source?: "built-in" | "installed", enabled?: boolean): void;
3157
+ setEnabled(id: ProviderId, enabled: boolean): void;
3158
+ isEnabled(id: ProviderId): boolean;
3159
+ source(id: ProviderId): "built-in" | "installed" | undefined;
3160
+ unregisterInstalled(id: ProviderId): void;
1502
3161
  get(id: ProviderId): AgentProvider;
1503
3162
  list(): AgentProvider[];
3163
+ listEnabled(): AgentProvider[];
3164
+ manifest(id: ProviderId): Readonly<AdapterManifestV1>;
3165
+ descriptors(): {
3166
+ enabled: boolean;
3167
+ id: string;
3168
+ displayName: string;
3169
+ version: string;
3170
+ schemaVersion: 1;
3171
+ source: "built-in" | "installed";
3172
+ platforms: ("darwin" | "linux")[];
3173
+ resumeIdentity: "optional" | "required" | "unsupported";
3174
+ capabilities: {
3175
+ state: boolean;
3176
+ probe: boolean;
3177
+ launch: boolean;
3178
+ resume: boolean;
3179
+ identity: boolean;
3180
+ metadata: boolean;
3181
+ usage: boolean;
3182
+ login: boolean;
3183
+ attachments: boolean;
3184
+ cleanup: boolean;
3185
+ };
3186
+ stateAuthority: AdapterStateAuthority[];
3187
+ optionSchema: Record<string, unknown>;
3188
+ }[];
1504
3189
  }
3190
+ type ReturnTypeOfDescriptors = ReturnType<ProviderRegistry["descriptors"]>;
1505
3191
 
1506
3192
  interface CodexSpawnLease {
1507
3193
  readonly started: Promise<void>;
@@ -1643,6 +3329,11 @@ interface TerminalManagerDeps {
1643
3329
  * are). Same never-throw contract.
1644
3330
  */
1645
3331
  onFinished?: (id: string, wasAttached: boolean) => void;
3332
+ /** Every semantic state transition, including ones observed while a browser is attached. Used by the
3333
+ * command-center event/attention layer; a failure is isolated from terminal state. */
3334
+ onActivityChanged?: (id: string, previous: PaneStatus, current: PaneStatus, attached: boolean) => void;
3335
+ /** A client focused/attached this terminal. Done-unseen attention can become seen; blocked attention stays. */
3336
+ onViewed?: (id: string) => void;
1646
3337
  /**
1647
3338
  * Capture a tmux session's CURRENT rendered pane as plain text (READ-ONLY). Injected for tests; in
1648
3339
  * production it defaults to a real `capture-pane -p` on {@link TMUX_SOCKET}. Drives {@link refreshActivity},
@@ -1655,15 +3346,8 @@ interface TerminalManagerDeps {
1655
3346
  type CreateTerminalOptions = {
1656
3347
  id: string;
1657
3348
  cwd: string;
1658
- provider: "claude";
1659
- options: ClaudeSessionOptions;
1660
- cols?: number;
1661
- rows?: number;
1662
- } | {
1663
- id: string;
1664
- cwd: string;
1665
- provider: "codex";
1666
- options: CodexSessionOptions;
3349
+ provider: ProviderId;
3350
+ options: ProviderSessionOptions;
1667
3351
  cols?: number;
1668
3352
  rows?: number;
1669
3353
  };
@@ -1680,6 +3364,7 @@ declare class TerminalManager {
1680
3364
  private readonly providers;
1681
3365
  private attachConfig?;
1682
3366
  constructor(deps: TerminalManagerDeps);
3367
+ private notifyActivityChanged;
1683
3368
  /** Late-bound (after listen(), which resolves the loopback port) — same config the chat SessionManager
1684
3369
  * gets. When set, each terminal's claude is spawned with `--mcp-config` so send_image/send_file work. */
1685
3370
  setAttachConfig(attach: AttachSpawnOptions | undefined): void;
@@ -2125,8 +3810,24 @@ declare class CodexLatestService {
2125
3810
 
2126
3811
  interface CreateServerDeps {
2127
3812
  store?: SessionStore;
3813
+ /** Durable per-device credentials + one-time pairing sessions. start.ts supplies a SQLite store. */
3814
+ deviceStore?: DeviceStore;
3815
+ /** Durable host/workspace/agent/attention/event state. start.ts supplies a SQLite store. */
3816
+ commandStore?: CommandCenterStore;
3817
+ /** Durable idempotency, privacy-safe audit, and automation definitions/runs. */
3818
+ controlStore?: ControlStore;
3819
+ /** Guarded git worktree lifecycle, confined to FS_ROOT and injectable for isolated tests. */
3820
+ worktreeService?: WorktreeService;
3821
+ /** Durable verified adapter/plugin package inventory. start.ts supplies a SQLite-backed manager. */
3822
+ extensionManager?: ExtensionManager;
3823
+ /** Bounded, permissioned plugin subprocess boundary. */
3824
+ pluginRuntime?: PluginRuntime;
3825
+ /** Optional immutable marketplace index; local extension installation never depends on it. */
3826
+ marketplaceEntries?: MarketplaceEntry[];
2128
3827
  /** Absolute path to the built PWA (packages/web/dist). When set, the server also serves the UI. */
2129
3828
  webDir?: string;
3829
+ /** Per-process boot identity returned as a response header for the out-of-process managed watchdog. */
3830
+ healthInstanceId?: string;
2130
3831
  pushStore?: PushStore;
2131
3832
  /** VAPID public key exposed at GET /push/vapid for the browser subscription. */
2132
3833
  vapidPublicKey?: string;
@@ -2216,18 +3917,67 @@ interface CreateServerDeps {
2216
3917
  * store is built when omitted.
2217
3918
  */
2218
3919
  wsTickets?: WsTicketStore;
3920
+ /** One-writer/many-observer terminal input coordinator. Injectable for deterministic multi-client tests. */
3921
+ inputLeases?: InputLeaseCoordinator;
3922
+ /** Team policy seam: direct local mode permits confirmed takeover; enterprise policy can deny it here. */
3923
+ authorizeInputTakeover?: (principal: InputLeasePrincipal, sessionId: string) => boolean;
3924
+ /** Optional policy seam for acquiring/writing input. Team RBAC is applied in addition when enabled. */
3925
+ authorizeInputWrite?: (principal: InputLeasePrincipal, sessionId: string) => boolean;
3926
+ /** Durable team membership and role assignments. start.ts supplies SQLite; tests may inject memory. */
3927
+ teamStore?: TeamStore;
3928
+ /** Durable organization policy. Disabled by default; enforced uniformly when explicitly enabled. */
3929
+ policyStore?: PolicyStore;
3930
+ /** Durable, explicitly scoped host-to-host API connections. Raw peer credentials never leave this store. */
3931
+ peerStore?: PeerStore;
3932
+ /** Isolated outbound peer transport; injectable so tests never contact developer or production hosts. */
3933
+ peerFetch?: typeof globalThis.fetch;
3934
+ /** Ephemeral, bounded presence heartbeats. */
3935
+ presence?: PresenceCoordinator;
3936
+ /** Advertises and enforces that this host has an outbound blind-relay transport configured. */
3937
+ relayEnabled?: boolean;
3938
+ /** Privacy-bounded connector health for the authenticated settings UI. */
3939
+ relayStatus?: () => {
3940
+ status: "idle" | "connecting" | "online" | "reconnecting" | "stopped";
3941
+ activeChannels: number;
3942
+ reconnects: number;
3943
+ };
3944
+ /** Optional hosted/self-hosted bootstrap; absent means relay works for already-provisioned devices only. */
3945
+ relayPairing?: RelayPairingBootstrap;
3946
+ /** Late-bound host connector hook so device revocation closes relay channels immediately. */
3947
+ onDeviceRevoked?: (deviceId: string) => void;
2219
3948
  }
2220
3949
  interface CreateServerResult {
2221
3950
  app: FastifyInstance;
2222
3951
  authGate: AuthGate;
3952
+ /** Issue a five-minute, one-use pairing capability without exposing the host's master token. */
3953
+ issuePairing(): PairingTicket;
3954
+ /** Internal E2E-relay bridge. It authenticates relay-scoped devices through the exact normal route hooks. */
3955
+ dispatchRelayRequest(token: string, request: RelayRpcRequest): Promise<RelayRpcResponse>;
3956
+ /** Issues a relay-principal terminal ticket without exposing the bridge's process-local capability. */
3957
+ issueRelayTerminalTicket(token: string): Promise<string>;
3958
+ /** Process-local loopback headers for streamed relay HTTP. Never expose these outside this server process. */
3959
+ relayLoopbackHeaders(token: string, headers?: Record<string, string>): Record<string, string>;
2223
3960
  /** Exposed so startServer can late-bind the MCP attach config (after listen() resolves the port) —
2224
3961
  * this is what gives the terminal's claude send_image/send_file. */
2225
3962
  terminalManager: TerminalManager;
3963
+ /** Exposed for relay/team composition and isolated ownership tests. */
3964
+ inputLeases: InputLeaseCoordinator;
3965
+ teamStore: TeamStore;
3966
+ policyStore: PolicyStore;
3967
+ peerStore: PeerStore;
3968
+ presence: PresenceCoordinator;
2226
3969
  /** False when tmux/node-pty is unavailable → terminal sessions are disabled (startServer warns loudly). */
2227
3970
  terminalAvailable: boolean;
2228
3971
  }
2229
3972
  declare function createServer(config: ServerRuntimeConfig, deps?: CreateServerDeps): CreateServerResult;
2230
3973
 
3974
+ /** Hash of the intentionally inline boot-recovery watchdog in packages/web/index.html. */
3975
+ declare const PWA_BOOT_WATCHDOG_SHA256 = "sha256-tcgQYptaPeNGqJtts8Ft/5H4tf+s+jfSWQSguIhTp8k=";
3976
+ /**
3977
+ * Static-shell policy shared by direct hosts and mirrored by packaging/relay/Caddyfile.
3978
+ * Inline component styles remain necessary; executable inline code is restricted to the reviewed watchdog hash.
3979
+ */
3980
+ declare const PWA_CONTENT_SECURITY_POLICY: string;
2231
3981
  /**
2232
3982
  * Server-side mirror of the web SW's `apiNavigationDenylist` (packages/web/src/pwa/sw-exclusions.ts),
2233
3983
  * EXTENDED with /health, /push and the /ws suffix. A request whose path matches one of these is a
@@ -2351,8 +4101,41 @@ declare function startServer(env?: NodeJS.ProcessEnv): Promise<CreateServerResul
2351
4101
  url: string;
2352
4102
  token?: string;
2353
4103
  tokenGenerated: boolean;
4104
+ relayHost?: RelayHostConnector;
2354
4105
  }>;
2355
4106
 
4107
+ interface ProcessLifecycleTarget {
4108
+ once(event: string, listener: (...args: unknown[]) => void): unknown;
4109
+ off(event: string, listener: (...args: unknown[]) => void): unknown;
4110
+ exit(code: number): never | void;
4111
+ }
4112
+ interface ProcessLifecycleOptions {
4113
+ /** Close only on an intentional signal. Fatal errors exit immediately because process state is unknown. */
4114
+ close: () => void | Promise<unknown>;
4115
+ target?: ProcessLifecycleTarget;
4116
+ log?: (message: string) => void;
4117
+ shutdownTimeoutMs?: number;
4118
+ setTimer?: typeof setTimeout;
4119
+ clearTimer?: typeof clearTimeout;
4120
+ }
4121
+ interface ProcessLifecycleHandle {
4122
+ dispose(): void;
4123
+ }
4124
+ /**
4125
+ * Keep fatal diagnostics useful without copying bearer credentials, pairing links, or a maintainer's absolute
4126
+ * home path into service logs. The summary deliberately uses Error.message rather than the full stack.
4127
+ */
4128
+ declare function safeProcessErrorSummary(reason: unknown, home?: string): string;
4129
+ /**
4130
+ * Own the process boundary for the always-on server.
4131
+ *
4132
+ * Intentional SIGTERM/SIGINT gets a bounded graceful close. An uncaught exception or unhandled rejection is
4133
+ * different: Node cannot promise that application state remains valid, so the process exits non-zero immediately
4134
+ * and lets launchd/systemd restart it. Keeping a possibly-corrupt process alive is how a listener can remain bound
4135
+ * while every HTTP request stalls indefinitely.
4136
+ */
4137
+ declare function installProcessLifecycle(options: ProcessLifecycleOptions): ProcessLifecycleHandle;
4138
+
2356
4139
  /** Terminal mode needs BOTH a tmux binary and a loadable node-pty. Injectable for tests. */
2357
4140
  declare function detectTerminalSupport(deps?: {
2358
4141
  hasTmux?: () => boolean;
@@ -2435,9 +4218,23 @@ declare function enableService(record: ServiceRecord): {
2435
4218
  declare class ProviderOptionsError extends ProviderError {
2436
4219
  constructor(message: string);
2437
4220
  }
2438
- declare function parseProviderOptions(provider: ProviderId, raw: unknown): ProviderSessionOptions;
4221
+ declare function parseProviderOptions(provider: ProviderId, raw: unknown, optionSchema?: Record<string, unknown>): ProviderSessionOptions;
2439
4222
  declare function parseLegacyClaudeArgs(args: readonly string[]): ClaudeSessionOptions;
2440
4223
 
4224
+ interface CreateInstalledAdapterProviderOptions {
4225
+ extensions: ExtensionManager;
4226
+ adapterId: string;
4227
+ env?: NodeJS.ProcessEnv;
4228
+ }
4229
+ declare function createInstalledAdapterProvider(options: CreateInstalledAdapterProviderOptions): ProviderAdapterV1;
4230
+
4231
+ type JsonObject = Record<string, unknown>;
4232
+ interface OpenApiBuildOptions {
4233
+ serverVersion: string;
4234
+ adapters: ReturnTypeOfDescriptors;
4235
+ }
4236
+ declare function buildOpenApiDocument(options: OpenApiBuildOptions): JsonObject;
4237
+
2441
4238
  interface CreateClaudeProviderOptions {
2442
4239
  claudeBin: string;
2443
4240
  env?: NodeJS.ProcessEnv;
@@ -2445,7 +4242,7 @@ interface CreateClaudeProviderOptions {
2445
4242
  getAttach?: () => AttachSpawnOptions | undefined;
2446
4243
  probe?: () => Promise<ProviderAvailability>;
2447
4244
  }
2448
- declare function createClaudeProvider(options: CreateClaudeProviderOptions): AgentProvider;
4245
+ declare function createClaudeProvider(options: CreateClaudeProviderOptions): ProviderAdapterV1;
2449
4246
 
2450
4247
  interface CreateCodexProviderOptions {
2451
4248
  codexBin: string;
@@ -2458,7 +4255,7 @@ interface CreateCodexProviderOptions {
2458
4255
  }
2459
4256
  type CodexAttachContext = Pick<AttachSpawnOptions, "baseUrl" | "token" | "mcpScriptPath">;
2460
4257
  declare function buildCodexArgs(context: ProviderProcessContext, attach?: CodexAttachContext): string[];
2461
- declare function createCodexProvider(options: CreateCodexProviderOptions): AgentProvider;
4258
+ declare function createCodexProvider(options: CreateCodexProviderOptions): ProviderAdapterV1;
2462
4259
 
2463
4260
  declare const CODEX_EXECUTABLE_PROBE_TIMEOUT_MS = 5000;
2464
4261
  declare const OPENAI_CODE_SIGNING_TEAM_ID = "2DC432GLL2";
@@ -2512,4 +4309,4 @@ declare function codexClassifierVersionWarning(codexVersion: string | undefined)
2512
4309
 
2513
4310
  declare const SERVER_PACKAGE = "@roamcode.ai/server";
2514
4311
 
2515
- export { API_PATH_DENYLIST, type AgentProvider, type AttachSpawnOptions, type AuthCheckResult, AuthGate, type AuthGateOptions, type Bar, CHECK_CACHE_MS, CLASSIFIER_TESTED_UP_TO, CLAUDE_LATEST_CACHE_MS, CLAUDE_VERSION_CACHE_MS, CLAUDE_VERSION_TIMEOUT_MS, CODEX_CLASSIFIER_TESTED_UP_TO, CODEX_EXECUTABLE_PROBE_TIMEOUT_MS, CODEX_OSC_MAX_CARRY, type CaptureOptions, type ChangelogEntry, type ClaudeAuthDeps, ClaudeAuthService, type ClaudeAuthStatus, type ClaudeAvailability, type ClaudeLatestDeps, ClaudeLatestService, type ClaudeMetadataRunner, ClaudeMetadataService, type ClaudeModelCatalogItem, type ClaudeSessionOptions, type ClaudeVersionProbe, type CodexAccount, CodexAppServerClient, type CodexAppServerClientOptions, type CodexDeviceLogin, type CodexExecutableDeps, type CodexExecutableProbe, type CodexExecutableResolution, type CodexInstallProvenance, CodexLatestService, type CodexLoginCompletion, type CodexLoginStatus, type CodexMetadataDiagnostics, type CodexMetadataRpc, CodexMetadataService, type CodexMetadataServiceOptions, type CodexModel, type CodexOscParser, type CodexProfileClientLifecycle, type CodexSessionOptions, type CodexSpawnLease, type CodexThreadInventoryEntry, type CodexThreadPersistence, CodexThreadResolver, type CodexThreadResolverOptions, type CodexUsage, type CodexVersionInfo, type CreateClaudeProviderOptions, type CreateCodexProfileClientLifecycleOptions, type CreateCodexProviderOptions, type CreatePushDispatcherDeps, type CreateServerDeps, type CreateServerResult, type CreateWebPushSendOptions, type DirEntry, type DirListing, type DirSearchResult, FETCH_TIMEOUT_MS, type FetchLatest, type FetchManifest, type FetchReleases, FsError, type FsErrorCode, FsService, type FsServiceOptions, type GitHubRelease, type HooksSettingsDocument, type IPty, type InstallServiceContext, type InstallServiceResult, type InstallationKind, type LaunchIntent, type ManagedInstallOptions, type ManagedInstallResult, type ManagedInstallStatus, type ManagedPaths, type McpConfigDocument, type ModelWeekBar, OPENAI_CODE_SIGNING_TEAM_ID, type OpenPushStoreOptions, type OpenSessionStoreOptions, type OriginCheckOptions, type PaneStatus, type ProcessSpec, type ProviderAvailability, ProviderError, type ProviderId, ProviderOptionsError, type ProviderProcessContext, ProviderRegistry, type ProviderRuntimeSignal, type ProviderRuntimeSignalParser, type ProviderSessionOptions, type PtySpawn, type PushDispatcher, type PushEvent, type PushEventKind, type PushPayload, type PushRecipient, type PushSendFn, type PushStore, type PushSubscriptionRecord, RUNNING_BUILD, RUNNING_VERSION, type RateLimitDecision, RateLimiter, type RateLimiterOptions, type RegisterStaticOptions, type ReleaseRecord, type RenderLaunchdOptions, type RenderSystemdOptions, type ResolveAccessTokenOptions, type ResolveCodexExecutableOptions, type ResolveCodexThreadOptions, type ResolveVapidKeysOptions, type RunClaudeVersion, type RunUsage, SEARCH_MAX_DEPTH, SEARCH_MAX_DIRS, SEARCH_MAX_RESULTS, SERVER_PACKAGE, SHELL_PATH_ALLOWLIST, type ServerConfig, type ServerRuntimeConfig, type ServiceRecord, type SessionDefaults, SessionDefaultsConflictError, type SessionFileDirection, type SessionFileKind, type SessionFileStorage, type SessionStore, type StoreMode, type StoredSession, type StoredSessionDefaults, type StoredSessionFile, type StoredStatus, TerminalManager, type TerminalManagerDeps, type TerminalMeta, TerminalProcess, type TerminalProcessOptions, type TerminalSub, USAGE_CACHE_MS, USAGE_TIMEOUT_MS, type UpdateAction, type UpdateState, type UpdateStatus, Updater, type UpdaterDeps, type UpdaterFs, type UsageInfo, UsageService, type UsageServiceDeps, type VapidKeys, type VersionInfo, WS_TICKET_TTL_MS, WsTicketStore, type WsTicketStoreOptions, assertConfigAllowsStart, buildCodexArgs, buildHooksSettingsDocument, buildMcpConfigDocument, buildPushPayload, buildServicePath, capturePane, classifierVersionWarning, classifyCodexPane, classifyPaneStatus, claudePreflightWarning, codexClassifierVersionWarning, compareVersions, computeBuildDrift, computeInstallDrift, createClaudeAuthService, createClaudeLatestService, createClaudeMetadataRunner, createClaudeProvider, createClaudeVersionProbe, createCodexOscParser, createCodexProfileClientLifecycle, createCodexProvider, createCodexThreadInventory, createCodexThreadPersistence, createPushDispatcher, createServer, createUpdater, createUsageRunner, createUsageService, createWebPushSend, defaultFetchManifest, defaultFetchReleases, defaultProbeCodexExecutable, defaultRunClaudeVersion, defaultUpdaterFs, detectTerminalSupport, enableService, ensureDataDir, extractBearerToken, extractLoginUrl, generateAccessToken, hasEncodedSep, hookAuthFileContent, hookAuthPathFor, hooksSettingsPathFor, installManagedRelease, installService, isCodexProfileClientLifecycle, isLoopbackAddress, isNewerMajorMinor, isOriginAllowed, isPublicForRequest, isPublicPath, isShellPath, isStableVersion, listTmuxSessions, loadConfig, loadServerConfig, looksLikeAssetRequest, managedPaths, mcpConfigPathFor, migrateServiceToLauncher, normalizeOrigin, normalizeProviderAvailability, normalizeRelease, normalizeSessionDefaults, openPushStore, openSessionStore, parseAllowedOrigins, parseAuthStatus, parseClaudeVersion, parseCodexOscNotifications, parseCodexVersion, parseLegacyClaudeArgs, parseNpmLatest, parseProviderOptions, parseReleaseNotes, parseUsage, pathForGate, persistAccessToken, providerPreflightWarning, readActiveVersion, readPreviousVersion, readServiceRecord, registerStatic, relativeWhen, renderLaunchdPlist, renderManagedLauncher, renderSystemdUnit, resetCodexThreadResolutionCoordinatorForTests, resolveAccessToken, resolveCodexExecutable, resolveDataDir, resolveInstallRoot, resolveVapidKeys, restartService, runClaudePreflight, runProviderPreflight, stableReleases, startServer, tmuxSessionName, writeManagedLauncher };
4312
+ export { ADAPTER_CONTRACT_VERSION, API_PATH_DENYLIST, type AdapterCapabilityName, AdapterManifestError, type AdapterManifestV1, type AdapterPackageManifestV1, type AdapterRuntimeV1, type AdapterStateAuthority, type AgentActivity, type AgentProvider, type AgentRecord, type AttachSpawnOptions, type AttentionItem, type AttentionKind, type AttentionState, type AuditActorType, type AuditRecord, type AuditResult, type AuthCheckResult, AuthGate, type AuthGateOptions, type AutomationAction, type AutomationDefinition, type AutomationRun, type AutomationTrigger, BLIND_RELAY_DEFAULT_MAX_BYTES_PER_MINUTE, BLIND_RELAY_DEFAULT_MAX_CONNECTIONS_PER_ROUTE, BLIND_RELAY_DEFAULT_MAX_FRAME_BYTES, BLIND_RELAY_DEFAULT_MAX_MESSAGES_PER_MINUTE, BLIND_RELAY_DEFAULT_MAX_QUEUE_BYTES, BLIND_RELAY_DEFAULT_MAX_TOTAL_CONNECTIONS, BLIND_RELAY_PROTOCOL_VERSION, type Bar, type BlindRelayMetrics, type BlindRelayServer, CHECK_CACHE_MS, CLASSIFIER_TESTED_UP_TO, CLAUDE_LATEST_CACHE_MS, CLAUDE_VERSION_CACHE_MS, CLAUDE_VERSION_TIMEOUT_MS, CODEX_CLASSIFIER_TESTED_UP_TO, CODEX_EXECUTABLE_PROBE_TIMEOUT_MS, CODEX_OSC_MAX_CARRY, CONTROL_IDEMPOTENCY_TTL_MS, type CaptureOptions, type ChangelogEntry, type ClaudeAuthDeps, ClaudeAuthService, type ClaudeAuthStatus, type ClaudeAvailability, type ClaudeLatestDeps, ClaudeLatestService, type ClaudeMetadataRunner, ClaudeMetadataService, type ClaudeModelCatalogItem, type ClaudeSessionOptions, type ClaudeVersionProbe, type CodexAccount, CodexAppServerClient, type CodexAppServerClientOptions, type CodexDeviceLogin, type CodexExecutableDeps, type CodexExecutableProbe, type CodexExecutableResolution, type CodexInstallProvenance, CodexLatestService, type CodexLoginCompletion, type CodexLoginStatus, type CodexMetadataDiagnostics, type CodexMetadataRpc, CodexMetadataService, type CodexMetadataServiceOptions, type CodexModel, type CodexOscParser, type CodexProfileClientLifecycle, type CodexSessionOptions, type CodexSpawnLease, type CodexThreadInventoryEntry, type CodexThreadPersistence, CodexThreadResolver, type CodexThreadResolverOptions, type CodexUsage, type CodexVersionInfo, CommandCenterRevisionConflictError, type CommandCenterStore, type CommandCenterStoreMode, type CommandEvent, type CommandLayoutEnvelope, type ControlStore, type ControlStoreMode, type CreateAutomationInput, type CreateBlindRelayOptions, type CreateClaudeProviderOptions, type CreateCodexProfileClientLifecycleOptions, type CreateCodexProviderOptions, type CreateInstalledAdapterProviderOptions, type CreatePeerInput, type CreatePluginRuntimeOptions, type CreatePushDispatcherDeps, type CreateRelayAccountInput, type CreateServerDeps, type CreateServerResult, type CreateWebPushSendOptions, type CreateWorktreeInput, type CreateWorktreeResult, type CreateWorktreeServiceOptions, type DeviceEnrollment, type DeviceInfo, DevicePairingError, type DeviceRelayIdentity, type DeviceScope, type DeviceStore, type DirEntry, type DirListing, type DirSearchResult, type DurableRelayIdentity, type EnterprisePolicy, type EnterprisePolicyAction, type EnterprisePolicyContext, type EnterprisePolicyDecision, EnterprisePolicyRevisionConflictError, type EnterprisePolicyUpdate, ExtensionError, type ExtensionKind, type ExtensionManager, type ExtensionManifestV1, type ExtensionPolicyMode, type ExtensionTrust, type ExtensionVersionRecord, FETCH_TIMEOUT_MS, type FetchLatest, type FetchManifest, type FetchReleases, FsError, type FsErrorCode, FsService, type FsServiceOptions, type GitHubRelease, type HooksSettingsDocument, type HostRecord, INPUT_LEASE_TTL_MS, type IPty, type IdempotencyRecord, type InputLease, type InputLeaseAcquireResult, type InputLeaseActorType, InputLeaseCoordinator, type InputLeaseCoordinatorOptions, type InputLeaseEvent, type InputLeasePrincipal, type InstallExtensionInput, type InstallServiceContext, type InstallServiceResult, type InstallationKind, type InstalledExtension, type LaunchIntent, type LoopbackRelayHttpOptions, type LoopbackRelayTerminalOptions, type ManagedInstallOptions, type ManagedInstallResult, type ManagedInstallStatus, type ManagedPaths, type MarketplaceEntry, type McpConfigDocument, type ModelWeekBar, OPENAI_CODE_SIGNING_TEAM_ID, type OpenApiBuildOptions, type OpenCommandCenterStoreOptions, type OpenControlStoreOptions, type OpenDeviceStoreOptions, type OpenExtensionManagerOptions, type OpenPeerStoreOptions, type OpenPolicyStoreOptions, type OpenPushStoreOptions, type OpenRelayAccountStoreOptions, type OpenRelayRouteStoreOptions, type OpenSessionStoreOptions, type OpenTeamStoreOptions, type OriginCheckOptions, PAIRING_TTL_MS, PRESENCE_HEARTBEAT_MS, PRESENCE_TTL_MS, PWA_BOOT_WATCHDOG_SHA256, PWA_CONTENT_SECURITY_POLICY, type PairingTicket, type PaneStatus, type PeerAction, type PeerConnection, type PeerJsonResponse, type PeerRecord, PeerRequestError, PeerRevisionConflictError, type PeerStatus, type PeerStore, type PeerStoreMode, type PersistedRelayHostConfig, type PluginAuditEvent, type PluginManifestV1, type PluginPermission, type PluginRunInput, type PluginRunResult, type PluginRuntime, PluginRuntimeError, type PolicyStore, type PolicyStoreMode, PresenceCoordinator, type PresenceCoordinatorOptions, type PresenceEvent, type PresenceHeartbeatInput, type PresenceMode, type PresenceRecord, type PresenceTarget, type ProcessLifecycleHandle, type ProcessLifecycleOptions, type ProcessLifecycleTarget, type ProcessSpec, type ProviderAdapterV1, type ProviderAvailability, ProviderError, type ProviderId, ProviderOptionsError, type ProviderProcessContext, ProviderRegistry, type ProviderRuntimeSignal, type ProviderRuntimeSignalParser, type ProviderSessionOptions, type PtySpawn, type PublicRelayRouteRecord, type PushDispatcher, type PushEvent, type PushEventKind, type PushPayload, type PushRecipient, type PushSendFn, type PushStore, type PushSubscriptionRecord, RELAY_CHANNEL_MAX_AGE_MS, RELAY_CHANNEL_MAX_FRAMES, RELAY_HANDSHAKE_MAX_SKEW_MS, RELAY_MAX_PLAINTEXT_BYTES, RELAY_PROTOCOL_VERSION, RELAY_RPC_MAX_BODY_BYTES, RELAY_RPC_MAX_PATH_BYTES, RELAY_WIRE_MAX_ENVELOPE_BYTES, RELAY_WIRE_PROTOCOL_VERSION, RUNNING_BUILD, RUNNING_VERSION, type RateLimitDecision, RateLimiter, type RateLimiterOptions, type RegisterStaticOptions, type RelayAccountCredentialInput, type RelayAccountCredentialMaterial, type RelayAccountPlan, type RelayAccountRecord, RelayAccountRevisionConflictError, type RelayAccountStatus, type RelayAccountStore, type RelayAccountStoreMode, type RelayChannelOptions, RelayCipherState, RelayCryptoError, type RelayCryptoErrorCode, type RelayDeviceProvisioner, type RelayDeviceProvisionerOptions, type RelayDeviceRouteRecord, type RelayDirection, type RelayEncryptedFrame, type RelayEphemeralKeyPair, type RelayFrameKind, type RelayHandshakeHello, type RelayHostConfigInput, type RelayHostConnector, type RelayHostConnectorOptions, type RelayHostMetrics, type RelayHostRuntimeConfig, type RelayHostStatus, type RelayHttpBridge, type RelayHttpHandlers, type RelayHttpOpenRequest, type RelayHttpOpener, type RelayHttpResponseHead, type RelayIdentity, type RelayIdentityStoreOptions, type RelayPairingBootstrap, type RelayPairingPackage, type RelayRole, type RelayRouteRecord, type RelayRouteStore, type RelayRpcMethod, type RelayRpcRequest, type RelayRpcResponse, type RelayStoreMode, type RelayTerminalBridge, type RelayTerminalHandlers, type RelayTerminalOpenRequest, type RelayTerminalOpener, type RelayWireEnvelope, type ReleaseRecord, type RenderLaunchdOptions, type RenderSystemdOptions, type ResolveAccessTokenOptions, type ResolveCodexExecutableOptions, type ResolveCodexThreadOptions, type ResolveVapidKeysOptions, type ReturnTypeOfDescriptors, type RunClaudeVersion, type RunUsage, SEARCH_MAX_DEPTH, SEARCH_MAX_DIRS, SEARCH_MAX_RESULTS, SERVER_PACKAGE, SHELL_PATH_ALLOWLIST, type ServerConfig, type ServerRuntimeConfig, type ServiceRecord, type SessionDefaults, SessionDefaultsConflictError, type SessionFileDirection, type SessionFileKind, type SessionFileStorage, type SessionPlacement, type SessionStore, type StartedBlindRelay, type StoreMode, type StoredExternalSession, type StoredSession, type StoredSessionDefaults, type StoredSessionFile, type StoredStatus, type TeamAuthorizationDecision, type TeamAuthorizationResource, type TeamMember, type TeamMemberKind, type TeamMemberStatus, type TeamPermission, type TeamPrincipalBinding, type TeamPrincipalType, type TeamRecord, TeamRevisionConflictError, type TeamRole, type TeamRoleBinding, type TeamScopeType, type TeamStore, type TeamStoreMode, TerminalManager, type TerminalManagerDeps, type TerminalMeta, TerminalProcess, type TerminalProcessOptions, type TerminalSub, USAGE_CACHE_MS, USAGE_TIMEOUT_MS, type UpdateAction, type UpdateAutomationInput, type UpdatePeerInput, type UpdatePolicyMode, type UpdateRelayAccountInput, type UpdateState, type UpdateStatus, Updater, type UpdaterDeps, type UpdaterFs, type UsageInfo, UsageService, type UsageServiceDeps, type VapidKeys, type VerifiedPeerIdentity, type VersionInfo, WS_TICKET_TTL_MS, type WorkspaceKind, type WorkspaceRecord, WorktreeError, type WorktreeErrorCode, type WorktreeRecord, type WorktreeService, type WsTicketContext, WsTicketStore, type WsTicketStoreOptions, adapterCapabilityNames, assertConfigAllowsStart, buildCodexArgs, buildHooksSettingsDocument, buildMcpConfigDocument, buildOpenApiDocument, buildPushPayload, buildRelayPairingUrl, buildServicePath, capturePane, classifierVersionWarning, classifyCodexPane, classifyPaneStatus, claudePreflightWarning, codexClassifierVersionWarning, compareVersions, computeBuildDrift, computeInstallDrift, createBlindRelayServer, createClaudeAuthService, createClaudeLatestService, createClaudeMetadataRunner, createClaudeProvider, createClaudeVersionProbe, createCodexOscParser, createCodexProfileClientLifecycle, createCodexProvider, createCodexThreadInventory, createCodexThreadPersistence, createInstalledAdapterProvider, createLoopbackRelayHttpOpener, createLoopbackRelayTerminalOpener, createPluginRuntime, createPushDispatcher, createRelayDeviceProvisioner, createRelayHandshakeHello, createRelayHostConnector, createServer, createUpdater, createUsageRunner, createUsageService, createWebPushSend, createWorktreeService, currentAgentIdForSession, decodeRelayWireEnvelope, defaultFetchManifest, defaultFetchReleases, defaultProbeCodexExecutable, defaultRunClaudeVersion, defaultUpdaterFs, defineAdapterManifest, detectTerminalSupport, enableService, encodeRelayWireEnvelope, ensureDataDir, establishRelayChannel, evaluateEnterprisePolicy, extractBearerToken, extractLoginUrl, generateAccessToken, generateRelayAccountCredential, generateRelayCredential, generateRelayEphemeralKeyPair, generateRelayIdentity, hasEncodedSep, hkdfSha256, hookAuthFileContent, hookAuthPathFor, hooksSettingsPathFor, inspectExtensionPackage, installManagedRelease, installProcessLifecycle, installService, isCodexProfileClientLifecycle, isLoopbackAddress, isNewerMajorMinor, isOriginAllowed, isPublicForRequest, isPublicPath, isRelayDirectExecution, isShellPath, isStableVersion, isTeamRole, isTeamScopeType, listTmuxSessions, loadConfig, loadOrCreateRelayIdentity, loadServerConfig, looksLikeAssetRequest, managedPaths, mcpConfigPathFor, migrateServiceToLauncher, normalizeAutomationAction, normalizeAutomationInput, normalizeAutomationTrigger, normalizeCommandCenterLabel, normalizeDeviceName, normalizeDeviceScopes, normalizeOrigin, normalizePeerBaseUrl, normalizeProviderAvailability, normalizeRelayAppUrl, normalizeRelease, normalizeSessionDefaults, openCommandCenterStore, openControlStore, openDeviceStore, openExtensionManager, openPeerStore, openPolicyStore, openPushStore, openRelayAccountStore, openRelayRouteStore, openSessionStore, openTeamStore, parseAllowedOrigins, parseAuthStatus, parseClaudeVersion, parseCodexOscNotifications, parseCodexVersion, parseLegacyClaudeArgs, parseMarketplaceIndex, parseNpmLatest, parseProviderOptions, parseRelayRpcRequest, parseReleaseNotes, parseUsage, pathForGate, persistAccessToken, privacySafeAuditMetadata, providerPreflightWarning, publicAdapterDescriptor, readActiveVersion, readPreviousVersion, readRelayHostConfig, readServiceRecord, registerStatic, relativeWhen, relayAccountCredentialHash, relayAccountCredentialLookup, relayConnectUrl, relayCredentialHash, relayHostConfigPath, relayIdentityFingerprint, relayRpcResponse, removeRelayHostConfig, renderLaunchdPlist, renderManagedLauncher, renderSystemdUnit, requestPeerJson, resetCodexThreadResolutionCoordinatorForTests, resolveAccessToken, resolveCodexExecutable, resolveDataDir, resolveInstallRoot, resolveRelayHostConfig, resolveVapidKeys, restartService, runClaudePreflight, runProviderPreflight, safeProcessErrorSummary, searchMarketplace, stableReleases, startBlindRelay, startServer, teamRolePermissions, tmuxSessionName, validateAdapterManifest, validateAdapterOptionSchema, validateRelayIdentity, verifyPeerConnection, verifyRelayHandshakeHello, writeManagedLauncher, writeRelayHostConfig };