@rynx-ai/runtime 0.1.11-beta.5 → 0.1.11-beta.51

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.
Files changed (65) hide show
  1. package/dist/claude/executor.d.ts +19 -5
  2. package/dist/claude/executor.js +56 -12
  3. package/dist/claude/models.d.ts +0 -5
  4. package/dist/claude/models.js +1 -7
  5. package/dist/claude/native-bridge.d.ts +103 -1
  6. package/dist/claude/native-bridge.js +460 -30
  7. package/dist/claude/native-hook-main.js +81 -1
  8. package/dist/claude/native-hooks.js +7 -0
  9. package/dist/claude/native-integration.d.ts +178 -26
  10. package/dist/claude/native-integration.js +1571 -170
  11. package/dist/claude/session-status.d.ts +39 -0
  12. package/dist/claude/session-status.js +163 -0
  13. package/dist/claude/transcript-clone.d.ts +18 -0
  14. package/dist/claude/transcript-clone.js +497 -0
  15. package/dist/claude/transcript.d.ts +27 -4
  16. package/dist/claude/transcript.js +183 -46
  17. package/dist/codex-app-server/client.d.ts +10 -6
  18. package/dist/codex-app-server/client.js +67 -15
  19. package/dist/codex-app-server/forwarder.d.ts +96 -3
  20. package/dist/codex-app-server/forwarder.js +542 -57
  21. package/dist/codex-app-server/mapping.d.ts +3 -6
  22. package/dist/codex-app-server/mapping.js +228 -36
  23. package/dist/codex-app-server/mcp-startup.d.ts +13 -0
  24. package/dist/codex-app-server/mcp-startup.js +63 -0
  25. package/dist/codex-app-server/process-registry.d.ts +36 -0
  26. package/dist/codex-app-server/process-registry.js +320 -0
  27. package/dist/codex-app-server/protocol.d.ts +68 -7
  28. package/dist/codex-app-server/transport.d.ts +2 -2
  29. package/dist/codex-app-server/transport.js +4 -0
  30. package/dist/codex-app-server/ws-channel.d.ts +8 -1
  31. package/dist/codex-app-server/ws-channel.js +113 -28
  32. package/dist/codex-child-env.js +3 -32
  33. package/dist/codex-home.d.ts +35 -3
  34. package/dist/codex-home.js +323 -18
  35. package/dist/codex-session-store.d.ts +23 -0
  36. package/dist/codex-session-store.js +21 -0
  37. package/dist/host.d.ts +106 -48
  38. package/dist/host.js +2024 -640
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.js +1 -1
  41. package/dist/input-resources.d.ts +4 -0
  42. package/dist/input-resources.js +21 -5
  43. package/dist/models-catalog.js +12 -3
  44. package/dist/runner/child.d.ts +105 -28
  45. package/dist/runner/child.js +1625 -100
  46. package/dist/runner/manager.d.ts +114 -46
  47. package/dist/runner/manager.js +1523 -446
  48. package/dist/runner/protocol.d.ts +212 -24
  49. package/dist/runner/protocol.js +5 -0
  50. package/dist/runner/startup-policy.d.ts +7 -0
  51. package/dist/runner/startup-policy.js +10 -0
  52. package/dist/runner/transport.d.ts +18 -2
  53. package/dist/runner/transport.js +82 -3
  54. package/dist/runner-main.js +8 -3
  55. package/dist/terminal/codex-tui.d.ts +4 -0
  56. package/dist/terminal/codex-tui.js +5 -0
  57. package/dist/terminal/control-parser.d.ts +39 -0
  58. package/dist/terminal/control-parser.js +172 -0
  59. package/dist/terminal/registry.d.ts +18 -15
  60. package/dist/terminal/registry.js +44 -23
  61. package/dist/terminal/spool.d.ts +47 -0
  62. package/dist/terminal/spool.js +231 -0
  63. package/dist/terminal/tmux.d.ts +126 -74
  64. package/dist/terminal/tmux.js +807 -211
  65. package/package.json +4 -4
@@ -1,26 +1,174 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { copyFileSync, cpSync, existsSync, mkdirSync, renameSync, rmSync, symlinkSync } from "node:fs";
2
+ import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { dirname, join } from "node:path";
5
- import { getRuntimeProfile, resolveRuntimeHome, SAFE_SKILL_NAME, } from "@rynx-ai/core";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { parse, stringify } from "smol-toml";
6
+ import { assertSkillPathComponent, getRuntimeProfile, resolveRuntimeHome, } from "@rynx-ai/core";
6
7
  import { adoptLegacyRuntimeDirectory, legacyRuntimeStateRoot, runtimeSessionDigest, runtimeSessionStateDir, } from "./runtime-state-paths.js";
7
- /** Inherit the user's LIVE login by symlink (stays in sync). */
8
- const SYMLINK_FILES = ["auth.json"];
8
+ /** Codex-owned files that must stay live across every private home. */
9
+ const CODEX_SYMLINK_FILES = [
10
+ "auth.json",
11
+ ".credentials.json",
12
+ "memories_1.sqlite",
13
+ "AGENTS.md",
14
+ "AGENTS.override.md",
15
+ "hooks.json",
16
+ ];
17
+ /** Large/shared Codex state and cross-process coordination directories. */
18
+ const CODEX_SYMLINK_DIRS = [
19
+ join("plugins", "cache"),
20
+ "mcp-oauth-locks",
21
+ "memories",
22
+ "rules",
23
+ ];
9
24
  /** Inherit the user's settings by snapshot copy (not the mutable NUX/update state). */
10
25
  const COPY_FILES = ["config.toml"];
11
26
  const RUNTIME_HOME_FILES = {
12
- codex: { symlink: SYMLINK_FILES, copy: COPY_FILES },
27
+ codex: {
28
+ symlink: CODEX_SYMLINK_FILES,
29
+ symlinkDirs: CODEX_SYMLINK_DIRS,
30
+ copy: COPY_FILES,
31
+ },
13
32
  traex: {
14
33
  // Traex keeps credentials below `cli/`, while its user configuration lives
15
34
  // at the TRAE_HOME root. Keep both the current TOML name and the legacy YAML
16
35
  // name so existing installations remain usable without an implicit migrate.
17
36
  symlink: ["cli/auth.json"],
37
+ symlinkDirs: [],
18
38
  copy: ["traecli.toml", "traecli.yaml"],
19
39
  },
20
40
  };
41
+ const CODEX_BUILTIN_PROVIDERS = new Set([
42
+ "openai",
43
+ "amazon-bedrock",
44
+ "amazon-bedrock-runtime",
45
+ "ollama",
46
+ "lmstudio",
47
+ ]);
48
+ function isTomlTable(value) {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ }
51
+ /** Resolve the effective native Provider, honoring the selected profile first. */
52
+ function effectiveCodexModelProvider(config) {
53
+ let provider = config.model_provider;
54
+ const activeProfile = config.profile;
55
+ if (typeof activeProfile === "string" && activeProfile.trim() && isTomlTable(config.profiles)) {
56
+ const profile = config.profiles[activeProfile];
57
+ if (isTomlTable(profile) && typeof profile.model_provider === "string") {
58
+ provider = profile.model_provider;
59
+ }
60
+ }
61
+ return typeof provider === "string" && provider.trim() ? provider : null;
62
+ }
63
+ /** Read the credential mode from Codex's optional-field auth.json shape. */
64
+ export function codexAuthEffectiveMode(authPath) {
65
+ let data;
66
+ try {
67
+ data = JSON.parse(readFileSync(authPath, "utf8"));
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ if (!data || typeof data !== "object" || Array.isArray(data))
73
+ return null;
74
+ const auth = data;
75
+ const tokens = auth.tokens;
76
+ if (tokens && typeof tokens === "object" && !Array.isArray(tokens)) {
77
+ const tokenRecord = tokens;
78
+ for (const field of ["access_token", "refresh_token"]) {
79
+ const value = tokenRecord[field];
80
+ if (typeof value === "string" && value.trim())
81
+ return "chatgpt";
82
+ }
83
+ }
84
+ if (typeof auth.OPENAI_API_KEY === "string" && auth.OPENAI_API_KEY.trim())
85
+ return "apikey";
86
+ if (typeof auth.personal_access_token === "string" &&
87
+ auth.personal_access_token.trim())
88
+ return "pat";
89
+ return null;
90
+ }
91
+ /** Mirror Codex's provider-specific auth decision without spawning the CLI. */
92
+ export function codexConfigEffectiveAuth(configPath, env = process.env) {
93
+ let config;
94
+ try {
95
+ config = parse(readFileSync(configPath, "utf8"));
96
+ }
97
+ catch {
98
+ return "codex-login";
99
+ }
100
+ const providerId = effectiveCodexModelProvider(config);
101
+ if (!providerId)
102
+ return "codex-login";
103
+ if (CODEX_BUILTIN_PROVIDERS.has(providerId)) {
104
+ return providerId === "openai" ? "codex-login" : "provider-ready";
105
+ }
106
+ const provider = isTomlTable(config.model_providers)
107
+ ? config.model_providers[providerId]
108
+ : undefined;
109
+ if (!isTomlTable(provider))
110
+ return "provider-auth-missing";
111
+ if (provider.requires_openai_auth === true)
112
+ return "codex-login";
113
+ const envKey = provider.env_key;
114
+ if (typeof envKey !== "string" || !envKey.trim())
115
+ return "provider-ready";
116
+ const value = env[envKey.trim()];
117
+ return typeof value === "string" && value.trim()
118
+ ? "provider-ready"
119
+ : "provider-auth-missing";
120
+ }
121
+ /** True only when this exact launch would defer to an absent Codex login. */
122
+ export function codexLaunchRequiresLogin(codexHome, env = process.env) {
123
+ return codexConfigEffectiveAuth(join(codexHome, "config.toml"), env) === "codex-login" &&
124
+ codexAuthEffectiveMode(join(codexHome, "auth.json")) === null;
125
+ }
126
+ function pathExistsOrIsSymlink(path) {
127
+ try {
128
+ lstatSync(path);
129
+ return true;
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ }
135
+ function isPrivateCodexHome(candidate) {
136
+ const normalized = resolve(candidate);
137
+ if (basename(normalized) !== "codex-home")
138
+ return false;
139
+ const ownerDir = dirname(normalized);
140
+ const namespace = dirname(ownerDir);
141
+ const root = dirname(namespace);
142
+ return (basename(namespace) === "sessions" && basename(root) === "runtime") || basename(namespace) === "codex-native";
143
+ }
144
+ function privateCodexHomeSource(candidate) {
145
+ const sourceDirs = new Set();
146
+ for (const name of ["auth.json", ".credentials.json", "memories_1.sqlite"]) {
147
+ const file = join(candidate, name);
148
+ try {
149
+ if (lstatSync(file).isSymbolicLink())
150
+ sourceDirs.add(dirname(realpathSync(file)));
151
+ }
152
+ catch {
153
+ // Missing or unreadable links do not identify a source home.
154
+ }
155
+ }
156
+ return sourceDirs.size === 1 ? [...sourceDirs][0] : null;
157
+ }
158
+ /** Resolve the user's auth/config source without nesting a parent Session's
159
+ * private CODEX_HOME into a child Session. */
160
+ export function resolveCodexHomeConfigSource(candidate, defaultHome = join(homedir(), ".codex")) {
161
+ const source = resolve(candidate);
162
+ const fallback = resolve(defaultHome);
163
+ if (source !== fallback && isPrivateCodexHome(source)) {
164
+ return privateCodexHomeSource(source) ?? fallback;
165
+ }
166
+ return source;
167
+ }
21
168
  /** The user's real CODEX_HOME (env override, else `~/.codex`). */
22
169
  function realCodexHome() {
23
- return process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
170
+ const fallback = join(homedir(), ".codex");
171
+ return resolveCodexHomeConfigSource(process.env.CODEX_HOME?.trim() || fallback, fallback);
24
172
  }
25
173
  /**
26
174
  * The deterministic private CODEX_HOME path for a rynx session. PER-SESSION,
@@ -52,8 +200,8 @@ export function legacyCodexHomePath() {
52
200
  * `_CODEX_HOME_COPY_FILES`.
53
201
  *
54
202
  * PER-SESSION: one private home per rynx session (matching reference implementation), so concurrent
55
- * agents never share a `skills/` dir. Idempotent: the symlink/copy are refreshed each
56
- * call so a re-login/config change propagates. Returns the private home dir.
203
+ * agents never share a `skills/` dir. Idempotent: existing Session-owned entries stay
204
+ * unchanged; live symlinks still observe source credential updates. Returns the private home dir.
57
205
  */
58
206
  export function prepareCodexHome(sessionId, realHome = realCodexHome()) {
59
207
  return prepareRuntimeHome(sessionId, "codex", realHome);
@@ -77,13 +225,9 @@ export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "c
77
225
  for (const name of files.symlink) {
78
226
  const src = join(realHome, name);
79
227
  const dst = join(dir, name);
228
+ if (pathExistsOrIsSymlink(dst))
229
+ continue;
80
230
  mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
81
- try {
82
- rmSync(dst, { recursive: true, force: true });
83
- }
84
- catch {
85
- // absent — fine
86
- }
87
231
  if (existsSync(src)) {
88
232
  try {
89
233
  symlinkSync(src, dst);
@@ -93,11 +237,35 @@ export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "c
93
237
  }
94
238
  }
95
239
  }
240
+ for (const name of files.symlinkDirs) {
241
+ const src = join(realHome, name);
242
+ const dst = join(dir, name);
243
+ let sourceIsDirectory = false;
244
+ try {
245
+ sourceIsDirectory = statSync(src).isDirectory();
246
+ }
247
+ catch {
248
+ // Missing/unreadable shared state is optional.
249
+ }
250
+ if (!sourceIsDirectory)
251
+ continue;
252
+ if (pathExistsOrIsSymlink(dst))
253
+ continue;
254
+ mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
255
+ try {
256
+ symlinkSync(src, dst, "dir");
257
+ }
258
+ catch {
259
+ // Best-effort: Codex recreates absent cache/state in its private home.
260
+ }
261
+ }
96
262
  for (const name of files.copy) {
97
263
  const src = join(realHome, name);
98
264
  if (existsSync(src)) {
99
265
  try {
100
266
  const dst = join(dir, name);
267
+ if (pathExistsOrIsSymlink(dst))
268
+ continue;
101
269
  mkdirSync(dirname(dst), { recursive: true, mode: 0o700 });
102
270
  copyFileSync(src, dst);
103
271
  }
@@ -108,14 +276,151 @@ export function prepareRuntimeHome(sessionId, runtime, realHome = runtime === "c
108
276
  }
109
277
  return dir;
110
278
  }
279
+ /** Trust a runner-selected workspace only in the Session's private config so
280
+ * Codex cannot stop a detached TUI at its project-trust prompt. */
281
+ export function trustCodexProject(codexHome, cwd) {
282
+ const configPath = join(codexHome, "config.toml");
283
+ let config = {};
284
+ if (existsSync(configPath))
285
+ config = parse(readFileSync(configPath, "utf8"));
286
+ const projects = isTomlTable(config.projects) ? config.projects : {};
287
+ config.projects = projects;
288
+ let projectPath;
289
+ try {
290
+ projectPath = realpathSync(cwd);
291
+ }
292
+ catch {
293
+ projectPath = resolve(cwd);
294
+ }
295
+ const existingProject = projects[projectPath];
296
+ const project = isTomlTable(existingProject) ? existingProject : {};
297
+ project.trust_level = "trusted";
298
+ projects[projectPath] = project;
299
+ writeCodexConfig(configPath, config);
300
+ }
301
+ const DEFAULT_CODEX_MAX_RETRIES = 7;
302
+ const DEFAULT_CODEX_REQUEST_TIMEOUT_SECONDS = 120;
303
+ const DEVELOPER_INSTRUCTIONS_BASE_FILE = ".rynx-developer-instructions-base";
304
+ /** Apply the Session retry budget through Codex's supported provider settings.
305
+ * A config with no provider definitions is left untouched. */
306
+ export function applyCodexProviderRetryPolicy(codexHome, retry) {
307
+ const configPath = join(codexHome, "config.toml");
308
+ if (!existsSync(configPath) || lstatSync(configPath).isSymbolicLink())
309
+ return;
310
+ const config = parse(readFileSync(configPath, "utf8"));
311
+ if (!isTomlTable(config.model_providers))
312
+ return;
313
+ const maxRetries = retry?.maxRetries ?? DEFAULT_CODEX_MAX_RETRIES;
314
+ const timeoutSeconds = retry?.timeoutPerRequestSeconds ??
315
+ DEFAULT_CODEX_REQUEST_TIMEOUT_SECONDS;
316
+ let changed = false;
317
+ for (const provider of Object.values(config.model_providers)) {
318
+ if (!isTomlTable(provider))
319
+ continue;
320
+ provider.request_max_retries = maxRetries;
321
+ provider.stream_max_retries = maxRetries;
322
+ provider.stream_idle_timeout_ms = Math.trunc(timeoutSeconds * 1_000);
323
+ changed = true;
324
+ }
325
+ if (changed)
326
+ writeCodexConfig(configPath, config);
327
+ }
328
+ /** Preserve the user's private-home base instructions and append only the raw
329
+ * Agent-authored text. Repeated launches replace the managed suffix instead of
330
+ * accumulating it. Invalid user TOML is intentionally left untouched. */
331
+ export function syncCodexDeveloperInstructions(codexHome, instructions) {
332
+ mkdirSync(codexHome, { recursive: true, mode: 0o700 });
333
+ const configPath = join(codexHome, "config.toml");
334
+ if (pathExistsOrIsSymlink(configPath) && lstatSync(configPath).isSymbolicLink()) {
335
+ try {
336
+ const source = realpathSync(configPath);
337
+ const content = readFileSync(source, "utf8");
338
+ rmSync(configPath, { force: true });
339
+ writeFileSync(configPath, content, { encoding: "utf8", mode: 0o600 });
340
+ }
341
+ catch {
342
+ return;
343
+ }
344
+ }
345
+ let config;
346
+ try {
347
+ config = existsSync(configPath) ? parse(readFileSync(configPath, "utf8")) : {};
348
+ }
349
+ catch {
350
+ return;
351
+ }
352
+ const current = config.developer_instructions;
353
+ if (current !== undefined && typeof current !== "string")
354
+ return;
355
+ const addition = instructions?.trim() ?? "";
356
+ const basePath = join(codexHome, DEVELOPER_INSTRUCTIONS_BASE_FILE);
357
+ let base;
358
+ if (existsSync(basePath)) {
359
+ base = readFileSync(basePath, "utf8");
360
+ }
361
+ else {
362
+ base = typeof current === "string" ? current.trim() : "";
363
+ if (addition && base === addition) {
364
+ base = "";
365
+ }
366
+ else if (addition && base.endsWith(`\n\n${addition}`)) {
367
+ base = base.slice(0, -(`\n\n${addition}`).length).trimEnd();
368
+ }
369
+ writeFileSync(basePath, base, { encoding: "utf8", mode: 0o600 });
370
+ }
371
+ const active = base && addition ? `${base}\n\n${addition}` : base || addition;
372
+ if (active)
373
+ config.developer_instructions = active;
374
+ else
375
+ delete config.developer_instructions;
376
+ writeCodexConfig(configPath, config);
377
+ }
378
+ /** Read the active private-home instructions without conflating a definite
379
+ * absence with malformed or temporarily unreadable config. */
380
+ export function readCodexDeveloperInstructionsState(codexHome) {
381
+ const configPath = join(codexHome, "config.toml");
382
+ let source;
383
+ try {
384
+ source = readFileSync(configPath, "utf8");
385
+ }
386
+ catch (error) {
387
+ const code = error.code;
388
+ return code === "ENOENT" || code === "ENOTDIR"
389
+ ? { state: "absent" }
390
+ : { state: "unreadable" };
391
+ }
392
+ let config;
393
+ try {
394
+ config = parse(source);
395
+ }
396
+ catch {
397
+ return { state: "unreadable" };
398
+ }
399
+ if (!Object.prototype.hasOwnProperty.call(config, "developer_instructions")) {
400
+ return { state: "absent" };
401
+ }
402
+ const instructions = config.developer_instructions;
403
+ return typeof instructions === "string" && instructions.trim()
404
+ ? { state: "present", value: instructions }
405
+ : { state: "unreadable" };
406
+ }
407
+ function writeCodexConfig(configPath, config) {
408
+ mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });
409
+ const staged = join(dirname(configPath), `.config.toml.${process.pid}.${randomUUID()}`);
410
+ try {
411
+ writeFileSync(staged, stringify(config), { encoding: "utf8", mode: 0o600 });
412
+ renameSync(staged, configPath);
413
+ }
414
+ finally {
415
+ rmSync(staged, { force: true });
416
+ }
417
+ }
111
418
  /** Persist immutable skill copies in a native Provider home. Fork descendants
112
419
  * share that home, so entries must outlive every runner's temporary sources. */
113
420
  export function populateCodexSkills(codexHome, skills) {
114
421
  const skillsDir = join(codexHome, "skills");
115
422
  for (const skill of skills) {
116
- if (!SAFE_SKILL_NAME.test(skill.name)) {
117
- throw new Error(`unsafe skill name: ${skill.name}`);
118
- }
423
+ assertSkillPathComponent(skill.name);
119
424
  }
120
425
  if (skills.length === 0)
121
426
  return;
@@ -8,6 +8,23 @@ export interface CodexSessionRecord {
8
8
  * metadata remains self-contained; this only tells a runner where the
9
9
  * Provider persisted its rollout/state. */
10
10
  runtimeHomeOwnerSessionId?: string;
11
+ /** Claude hook/forwarder rendezvous ownership. A rotated target keeps the
12
+ * physical pane's bridge while the retired logical source is re-keyed to an
13
+ * isolated bridge; both may still share one Provider runtime home. */
14
+ bridgeOwnerSessionId?: string;
15
+ /** Durable recovery edge for a Claude-native `/clear`·`/fork`. The target
16
+ * binding is written before IPC publication; `published` is flipped only
17
+ * after the parent durably creates the logical target. */
18
+ nativeRotationSourceSessionId?: string;
19
+ /** Authoritative latest published target when this record is the source.
20
+ * Avoids choosing an arbitrary historical edge after repeated rotations. */
21
+ nativeRotationTargetSessionId?: string;
22
+ nativeRotationKind?: "clear" | "fork";
23
+ nativeRotationPublished?: boolean;
24
+ /** Stable transcript boundary captured by the rotating forwarder at the
25
+ * target SessionStart. Clear starts at zero; fork starts after the copied
26
+ * source prefix. Recovery must not replace this with a later file EOF. */
27
+ nativeRotationInitialTranscriptOffset?: number;
11
28
  updatedAt: string;
12
29
  }
13
30
  export interface ClaudeForkIntent {
@@ -15,6 +32,12 @@ export interface ClaudeForkIntent {
15
32
  sourceSessionId: string;
16
33
  sourceClaudeSessionId: string;
17
34
  targetClaudeSessionId: string;
35
+ /** Target transcript atomically cloned before the target runner starts. Its
36
+ * presence means recovery must resume this exact file, never re-clone a
37
+ * source that may have advanced since the fork boundary. */
38
+ forkTranscriptPath?: string;
39
+ /** Exact byte length of the cloned prefix, measured before Claude starts. */
40
+ forkTranscriptPrefixBytes?: number;
18
41
  updatedAt: string;
19
42
  }
20
43
  export interface CodexSessionStore {
@@ -107,6 +107,27 @@ export class FileCodexSessionStore {
107
107
  ...(record.runtimeHomeOwnerSessionId
108
108
  ? { runtimeHomeOwnerSessionId: record.runtimeHomeOwnerSessionId }
109
109
  : {}),
110
+ ...(record.bridgeOwnerSessionId
111
+ ? { bridgeOwnerSessionId: record.bridgeOwnerSessionId }
112
+ : {}),
113
+ ...(record.nativeRotationSourceSessionId
114
+ ? { nativeRotationSourceSessionId: record.nativeRotationSourceSessionId }
115
+ : {}),
116
+ ...(record.nativeRotationTargetSessionId
117
+ ? { nativeRotationTargetSessionId: record.nativeRotationTargetSessionId }
118
+ : {}),
119
+ ...(record.nativeRotationKind
120
+ ? { nativeRotationKind: record.nativeRotationKind }
121
+ : {}),
122
+ ...(record.nativeRotationPublished
123
+ ? { nativeRotationPublished: true }
124
+ : {}),
125
+ ...(Number.isSafeInteger(record.nativeRotationInitialTranscriptOffset) &&
126
+ record.nativeRotationInitialTranscriptOffset >= 0
127
+ ? {
128
+ nativeRotationInitialTranscriptOffset: record.nativeRotationInitialTranscriptOffset,
129
+ }
130
+ : {}),
110
131
  updatedAt: record.updatedAt,
111
132
  };
112
133
  }