@jsonstudio/rcc 0.90.1 → 0.90.89

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 (193) hide show
  1. package/README.md +1 -1
  2. package/dist/build-info.js +2 -2
  3. package/dist/cli/commands/claude.js +3 -8
  4. package/dist/cli/commands/claude.js.map +1 -1
  5. package/dist/cli/commands/codex.js +5 -3
  6. package/dist/cli/commands/codex.js.map +1 -1
  7. package/dist/cli/commands/launcher/index.d.ts +1 -1
  8. package/dist/cli/commands/launcher/types.d.ts +1 -1
  9. package/dist/cli/commands/launcher/utils.d.ts +4 -1
  10. package/dist/cli/commands/launcher/utils.js +18 -8
  11. package/dist/cli/commands/launcher/utils.js.map +1 -1
  12. package/dist/cli/commands/launcher-kernel.d.ts +1 -1
  13. package/dist/cli/commands/launcher-kernel.js +208 -196
  14. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  15. package/dist/cli/commands/{clock-admin.d.ts → session-admin.d.ts} +2 -2
  16. package/dist/cli/commands/{clock-admin.js → session-admin.js} +17 -17
  17. package/dist/cli/commands/session-admin.js.map +1 -0
  18. package/dist/cli/commands/{tmux-inject.d.ts → session-inject.d.ts} +2 -2
  19. package/dist/cli/commands/{tmux-inject.js → session-inject.js} +12 -12
  20. package/dist/cli/commands/session-inject.js.map +1 -0
  21. package/dist/cli/commands/start-utils.d.ts +1 -0
  22. package/dist/cli/commands/start-utils.js +3 -0
  23. package/dist/cli/commands/start-utils.js.map +1 -1
  24. package/dist/cli/commands/start.js +14 -7
  25. package/dist/cli/commands/start.js.map +1 -1
  26. package/dist/cli/register/session-admin-command.d.ts +3 -0
  27. package/dist/cli/register/session-admin-command.js +5 -0
  28. package/dist/cli/register/session-admin-command.js.map +1 -0
  29. package/dist/cli/register/session-inject-command.d.ts +3 -0
  30. package/dist/cli/register/session-inject-command.js +5 -0
  31. package/dist/cli/register/session-inject-command.js.map +1 -0
  32. package/dist/cli.js +4 -4
  33. package/dist/cli.js.map +1 -1
  34. package/dist/config/provider-v2-loader.js +18 -3
  35. package/dist/config/provider-v2-loader.js.map +1 -1
  36. package/dist/config/routecodex-config-loader.js +136 -26
  37. package/dist/config/routecodex-config-loader.js.map +1 -1
  38. package/dist/config/unified-config-paths.js +22 -0
  39. package/dist/config/unified-config-paths.js.map +1 -1
  40. package/dist/config/virtual-router-builder.js +1 -6
  41. package/dist/config/virtual-router-builder.js.map +1 -1
  42. package/dist/docs/daemon-admin-ui.html +6 -6
  43. package/dist/index.js +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/manager/modules/quota/provider-key-normalization.js +1 -10
  46. package/dist/manager/modules/quota/provider-key-normalization.js.map +1 -1
  47. package/dist/manager/modules/quota/provider-quota-daemon.events.js +40 -49
  48. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  49. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +2 -16
  50. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  51. package/dist/manager/modules/token/index.d.ts +1 -0
  52. package/dist/manager/modules/token/index.js +6 -1
  53. package/dist/manager/modules/token/index.js.map +1 -1
  54. package/dist/manager/types.d.ts +1 -0
  55. package/dist/modules/llmswitch/bridge/state-integrations.js +1 -1
  56. package/dist/modules/llmswitch/bridge/state-integrations.js.map +1 -1
  57. package/dist/providers/auth/oauth-repair-cooldown.js +7 -2
  58. package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
  59. package/dist/providers/core/config/camoufox-actions.js +11 -2
  60. package/dist/providers/core/config/camoufox-actions.js.map +1 -1
  61. package/dist/providers/core/config/camoufox-launcher.js +60 -24
  62. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  63. package/dist/providers/core/config/service-profiles.js +2 -2
  64. package/dist/providers/core/config/service-profiles.js.map +1 -1
  65. package/dist/providers/core/runtime/base-provider-runtime-helpers.js +15 -2
  66. package/dist/providers/core/runtime/base-provider-runtime-helpers.js.map +1 -1
  67. package/dist/providers/core/strategies/oauth-device-flow.js +26 -3
  68. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  69. package/dist/providers/core/utils/provider-error-reporter.js +51 -0
  70. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  71. package/dist/server/handlers/handler-response-utils.js +3 -6
  72. package/dist/server/handlers/handler-response-utils.js.map +1 -1
  73. package/dist/server/handlers/handler-utils.js +14 -8
  74. package/dist/server/handlers/handler-utils.js.map +1 -1
  75. package/dist/server/handlers/responses-handler.js +5 -3
  76. package/dist/server/handlers/responses-handler.js.map +1 -1
  77. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +1 -1
  78. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +5 -5
  79. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -1
  80. package/dist/server/runtime/http-server/daemon-admin/routing-policy.d.ts +1 -1
  81. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js +4 -4
  82. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js.map +1 -1
  83. package/dist/server/runtime/http-server/executor/client-injection-flow.js +20 -10
  84. package/dist/server/runtime/http-server/executor/client-injection-flow.js.map +1 -1
  85. package/dist/server/runtime/http-server/executor/provider-response-converter.js +54 -17
  86. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  87. package/dist/server/runtime/http-server/executor/provider-runtime-resolver.js +8 -6
  88. package/dist/server/runtime/http-server/executor/provider-runtime-resolver.js.map +1 -1
  89. package/dist/server/runtime/http-server/executor/request-retry-helpers.d.ts +1 -1
  90. package/dist/server/runtime/http-server/executor/request-retry-helpers.js +8 -8
  91. package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -1
  92. package/dist/server/runtime/http-server/executor/retry-engine.d.ts +2 -2
  93. package/dist/server/runtime/http-server/executor/retry-engine.js +2 -2
  94. package/dist/server/runtime/http-server/executor/retry-engine.js.map +1 -1
  95. package/dist/server/runtime/http-server/executor/usage-aggregator.js +5 -2
  96. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  97. package/dist/server/runtime/http-server/executor-metadata.js +57 -77
  98. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  99. package/dist/server/runtime/http-server/executor-provider.d.ts +1 -0
  100. package/dist/server/runtime/http-server/executor-provider.js +5 -1
  101. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  102. package/dist/server/runtime/http-server/http-server-lifecycle.js +2 -1
  103. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  104. package/dist/server/runtime/http-server/http-server-runtime-setup.js +1 -1
  105. package/dist/server/runtime/http-server/http-server-runtime-setup.js.map +1 -1
  106. package/dist/server/runtime/http-server/http-server-session-daemon.d.ts +6 -0
  107. package/dist/server/runtime/http-server/{http-server-clock-daemon.js → http-server-session-daemon.js} +80 -73
  108. package/dist/server/runtime/http-server/http-server-session-daemon.js.map +1 -0
  109. package/dist/server/runtime/http-server/index.d.ts +11 -11
  110. package/dist/server/runtime/http-server/index.js +21 -21
  111. package/dist/server/runtime/http-server/index.js.map +1 -1
  112. package/dist/server/runtime/http-server/managed-process-probe.js +1 -1
  113. package/dist/server/runtime/http-server/managed-process-probe.js.map +1 -1
  114. package/dist/server/runtime/http-server/middleware.js +19 -11
  115. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  116. package/dist/server/runtime/http-server/request-executor.js +13 -4
  117. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  118. package/dist/server/runtime/http-server/routes.js +3 -3
  119. package/dist/server/runtime/http-server/routes.js.map +1 -1
  120. package/dist/server/runtime/http-server/{clock-client-reaper.d.ts → session-client-reaper.d.ts} +6 -6
  121. package/dist/server/runtime/http-server/{clock-client-reaper.js → session-client-reaper.js} +23 -23
  122. package/dist/server/runtime/http-server/session-client-reaper.js.map +1 -0
  123. package/dist/server/runtime/http-server/{clock-client-registry-utils.d.ts → session-client-registry-utils.d.ts} +10 -10
  124. package/dist/server/runtime/http-server/{clock-client-registry-utils.js → session-client-registry-utils.js} +3 -3
  125. package/dist/server/runtime/http-server/session-client-registry-utils.js.map +1 -0
  126. package/dist/server/runtime/http-server/{clock-client-registry.d.ts → session-client-registry.d.ts} +12 -12
  127. package/dist/server/runtime/http-server/{clock-client-registry.js → session-client-registry.js} +9 -9
  128. package/dist/server/runtime/http-server/session-client-registry.js.map +1 -0
  129. package/dist/server/runtime/http-server/{clock-client-route-utils.d.ts → session-client-route-utils.d.ts} +1 -1
  130. package/dist/server/runtime/http-server/{clock-client-route-utils.js → session-client-route-utils.js} +4 -4
  131. package/dist/server/runtime/http-server/session-client-route-utils.js.map +1 -0
  132. package/dist/server/runtime/http-server/session-client-routes.d.ts +2 -0
  133. package/dist/server/runtime/http-server/{clock-client-routes.js → session-client-routes.js} +65 -47
  134. package/dist/server/runtime/http-server/session-client-routes.js.map +1 -0
  135. package/dist/server/runtime/http-server/session-daemon-inject-config.d.ts +1 -0
  136. package/dist/server/runtime/http-server/{clock-daemon-inject-config.js → session-daemon-inject-config.js} +2 -2
  137. package/dist/server/runtime/http-server/session-daemon-inject-config.js.map +1 -0
  138. package/dist/server/runtime/http-server/{clock-daemon-log-throttle.d.ts → session-daemon-log-throttle.d.ts} +7 -7
  139. package/dist/server/runtime/http-server/{clock-daemon-log-throttle.js → session-daemon-log-throttle.js} +5 -5
  140. package/dist/server/runtime/http-server/session-daemon-log-throttle.js.map +1 -0
  141. package/dist/server/runtime/http-server/{clock-scope-resolution.d.ts → session-scope-resolution.d.ts} +2 -2
  142. package/dist/server/runtime/http-server/{clock-scope-resolution.js → session-scope-resolution.js} +14 -18
  143. package/dist/server/runtime/http-server/session-scope-resolution.js.map +1 -0
  144. package/dist/server/runtime/http-server/stopmessage-scope-rebind.d.ts +8 -0
  145. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js +29 -0
  146. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js.map +1 -1
  147. package/dist/server/runtime/http-server/tmux-session-probe.js +1 -1
  148. package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -1
  149. package/dist/token-daemon/token-daemon.d.ts +2 -0
  150. package/dist/token-daemon/token-daemon.js +18 -10
  151. package/dist/token-daemon/token-daemon.js.map +1 -1
  152. package/dist/utils/log-helpers.js +46 -0
  153. package/dist/utils/log-helpers.js.map +1 -1
  154. package/dist/utils/session-client-token.d.ts +4 -0
  155. package/dist/utils/{clock-client-token.js → session-client-token.js} +19 -24
  156. package/dist/utils/session-client-token.js.map +1 -0
  157. package/dist/utils/session-scope-trace.d.ts +11 -0
  158. package/dist/utils/{clock-scope-trace.js → session-scope-trace.js} +9 -9
  159. package/dist/utils/session-scope-trace.js.map +1 -0
  160. package/docs/CLOCK.md +0 -1
  161. package/docs/PORTS.md +2 -2
  162. package/docs/ROUTING_POLICY_SCHEMA.md +1 -1
  163. package/docs/antigravity-routing-contract.md +2 -2
  164. package/docs/daemon-admin-ui.html +6 -6
  165. package/docs/{clock-client-daemon-design.md → session-client-daemon-design.md} +34 -34
  166. package/package.json +3 -3
  167. package/scripts/compare-responses-sse.mjs +267 -0
  168. package/scripts/replay-codex-sample.mjs +52 -6
  169. package/scripts/virtual-router-dryrun.mjs +7 -1
  170. package/dist/cli/commands/clock-admin.js.map +0 -1
  171. package/dist/cli/commands/tmux-inject.js.map +0 -1
  172. package/dist/cli/register/clock-admin-command.d.ts +0 -3
  173. package/dist/cli/register/clock-admin-command.js +0 -5
  174. package/dist/cli/register/clock-admin-command.js.map +0 -1
  175. package/dist/cli/register/tmux-inject-command.d.ts +0 -3
  176. package/dist/cli/register/tmux-inject-command.js +0 -5
  177. package/dist/cli/register/tmux-inject-command.js.map +0 -1
  178. package/dist/server/runtime/http-server/clock-client-reaper.js.map +0 -1
  179. package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +0 -1
  180. package/dist/server/runtime/http-server/clock-client-registry.js.map +0 -1
  181. package/dist/server/runtime/http-server/clock-client-route-utils.js.map +0 -1
  182. package/dist/server/runtime/http-server/clock-client-routes.d.ts +0 -2
  183. package/dist/server/runtime/http-server/clock-client-routes.js.map +0 -1
  184. package/dist/server/runtime/http-server/clock-daemon-inject-config.d.ts +0 -1
  185. package/dist/server/runtime/http-server/clock-daemon-inject-config.js.map +0 -1
  186. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +0 -1
  187. package/dist/server/runtime/http-server/clock-scope-resolution.js.map +0 -1
  188. package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +0 -6
  189. package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +0 -1
  190. package/dist/utils/clock-client-token.d.ts +0 -4
  191. package/dist/utils/clock-client-token.js.map +0 -1
  192. package/dist/utils/clock-scope-trace.d.ts +0 -11
  193. package/dist/utils/clock-scope-trace.js.map +0 -1
@@ -4,10 +4,11 @@ import { spawnSync } from 'node:child_process';
4
4
  import { createServer } from 'node:http';
5
5
  import crypto from 'node:crypto';
6
6
  import { LOCAL_HOSTS } from '../../constants/index.js';
7
- import { encodeClockClientApiKey, extractClockClientDaemonIdFromApiKey, extractClockClientTmuxSessionIdFromApiKey } from '../../utils/clock-client-token.js';
8
- import { isClockScopeTraceEnabled, isClockScopeTraceVerbose } from '../../utils/clock-scope-trace.js';
7
+ import { encodeSessionClientApiKey, extractSessionClientDaemonIdFromApiKey, extractSessionClientScopeIdFromApiKey } from '../../utils/session-client-token.js';
8
+ import { isSessionScopeTraceEnabled, isSessionScopeTraceVerbose } from '../../utils/session-scope-trace.js';
9
9
  import { logProcessLifecycle } from '../../utils/process-lifecycle-logger.js';
10
10
  import { resolveBinary, parseServerUrl, resolveBoolFromEnv, resolveTmuxSelfHealPolicy, readConfigApiKey, normalizeConnectHost, toIntegerPort, tryReadConfigHostPort, resolveIntFromEnv } from './launcher/utils.js';
11
+ import { resolveRouteCodexConfigPath } from '../../config/config-paths.js';
11
12
  function shouldStopManagedTmuxOnShutdown(signal, env) {
12
13
  if (signal === 'SIGINT') {
13
14
  return true;
@@ -98,7 +99,10 @@ function canSignalOwnedToolProcess(args) {
98
99
  function resolveServerConnection(ctx, fsImpl, pathImpl, options) {
99
100
  let configPath = typeof options.config === 'string' && options.config.trim() ? options.config.trim() : '';
100
101
  if (!configPath) {
101
- configPath = pathImpl.join(ctx.homedir(), '.routecodex', 'config.json');
102
+ const resolved = resolveRouteCodexConfigPath();
103
+ configPath = resolved && resolved.trim()
104
+ ? resolved
105
+ : pathImpl.join(ctx.homedir(), '.routecodex', 'config.json');
102
106
  }
103
107
  let actualProtocol = 'http';
104
108
  let actualPort = toIntegerPort(options.port);
@@ -136,10 +140,9 @@ function resolveServerConnection(ctx, fsImpl, pathImpl, options) {
136
140
  throw new Error('Invalid or missing port configuration for RouteCodex server');
137
141
  }
138
142
  const configuredApiKey = (typeof options.apikey === 'string' && options.apikey.trim() ? options.apikey.trim() : null) ??
139
- (typeof ctx.env.ROUTECODEX_APIKEY === 'string' && ctx.env.ROUTECODEX_APIKEY.trim()
140
- ? ctx.env.ROUTECODEX_APIKEY.trim()
143
+ (typeof ctx.env.ROUTECODEX_HTTP_APIKEY === 'string' && ctx.env.ROUTECODEX_HTTP_APIKEY.trim()
144
+ ? ctx.env.ROUTECODEX_HTTP_APIKEY.trim()
141
145
  : null) ??
142
- (typeof ctx.env.RCC_APIKEY === 'string' && ctx.env.RCC_APIKEY.trim() ? ctx.env.RCC_APIKEY.trim() : null) ??
143
146
  readConfigApiKey(fsImpl, configPath);
144
147
  const connectHost = normalizeConnectHost(actualHost);
145
148
  const portPart = actualPort ? `:${actualPort}` : '';
@@ -602,64 +605,23 @@ function normalizePathForComparison(candidate) {
602
605
  return raw;
603
606
  }
604
607
  }
605
- function findReusableManagedTmuxSession(spawnSyncImpl, cwd, commandName) {
606
- const expectedCwd = normalizePathForComparison(cwd);
607
- const expectedSessionPrefix = `rcc_${normalizeSessionToken(commandName)}_`;
608
+ function formatHmms(value) {
609
+ const pad = (n) => String(n).padStart(2, '0');
610
+ return `${pad(value.getHours())}${pad(value.getMinutes())}${pad(value.getSeconds())}`;
611
+ }
612
+ function tmuxSessionExists(spawnSyncImpl, sessionName) {
608
613
  try {
609
- const listResult = spawnSyncImpl('tmux', ['list-sessions', '-F', '#S #{session_attached}'], { encoding: 'utf8' });
610
- if (listResult.status !== 0) {
611
- return null;
612
- }
613
- const lines = String(listResult.stdout || '')
614
- .split(/\r?\n/)
615
- .map((line) => line.trim())
616
- .filter(Boolean);
617
- for (const line of lines) {
618
- const [sessionName, attachedFlag] = line.split(' ');
619
- const normalizedName = String(sessionName || '').trim();
620
- if (!normalizedName.startsWith(expectedSessionPrefix)) {
621
- continue;
622
- }
623
- if (String(attachedFlag || '').trim() === '1') {
624
- continue;
625
- }
626
- const panesResult = spawnSyncImpl('tmux', [
627
- 'list-panes',
628
- '-t',
629
- normalizedName,
630
- '-F',
631
- '#{session_name}:#{window_index}.#{pane_index} #{pane_current_command} #{pane_current_path}'
632
- ], { encoding: 'utf8' });
633
- if (panesResult.status !== 0) {
634
- continue;
635
- }
636
- const panes = String(panesResult.stdout || '')
637
- .split(/\r?\n/)
638
- .map((paneLine) => paneLine.trim())
639
- .filter(Boolean);
640
- for (const pane of panes) {
641
- const [target, command, panePath] = pane.split(' ');
642
- const tmuxTarget = String(target || '').trim();
643
- const currentCommand = String(command || '').trim().toLowerCase();
644
- const normalizedPanePath = normalizePathForComparison(String(panePath || '').trim());
645
- if (!tmuxTarget) {
646
- continue;
647
- }
648
- if (!isReusableIdlePaneCommand(currentCommand)) {
649
- continue;
650
- }
651
- if (!normalizedPanePath || normalizedPanePath !== expectedCwd) {
652
- continue;
653
- }
654
- return { sessionName: normalizedName, tmuxTarget };
655
- }
656
- }
657
- return null;
614
+ const result = spawnSyncImpl('tmux', ['has-session', '-t', sessionName], { encoding: 'utf8' });
615
+ return result.status === 0;
658
616
  }
659
617
  catch {
660
- return null;
618
+ return false;
661
619
  }
662
620
  }
621
+ function buildManagedTmuxSessionName(nowMs, attempt) {
622
+ const stamp = formatHmms(new Date(nowMs + attempt * 1000));
623
+ return `rcc-tmux-${stamp}`;
624
+ }
663
625
  function requestManagedTmuxSessionExit(spawnSyncImpl, sessionName) {
664
626
  const target = String(sessionName || '').trim();
665
627
  if (!target) {
@@ -691,29 +653,33 @@ function requestManagedTmuxSessionExit(spawnSyncImpl, sessionName) {
691
653
  }
692
654
  }
693
655
  function createManagedTmuxSession(args) {
694
- const { spawnSyncImpl, cwd, commandName } = args;
695
- const sessionName = (() => {
696
- const token = normalizeSessionToken(commandName);
697
- return `rcc_${token}_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
698
- })();
699
- try {
700
- const result = spawnSyncImpl('tmux', ['new-session', '-d', '-s', sessionName, '-c', cwd], { encoding: 'utf8' });
701
- if (result.status !== 0) {
702
- return null;
656
+ const { spawnSyncImpl, cwd } = args;
657
+ const baseNow = Date.now();
658
+ for (let attempt = 0; attempt < 6; attempt += 1) {
659
+ const sessionName = buildManagedTmuxSessionName(baseNow, attempt);
660
+ if (tmuxSessionExists(spawnSyncImpl, sessionName)) {
661
+ continue;
703
662
  }
704
- }
705
- catch {
706
- return null;
707
- }
708
- const tmuxTarget = `${sessionName}:0.0`;
709
- return {
710
- sessionName,
711
- tmuxTarget,
712
- reused: false,
713
- stop: () => {
714
- requestManagedTmuxSessionExit(spawnSyncImpl, sessionName);
663
+ try {
664
+ const result = spawnSyncImpl('tmux', ['new-session', '-d', '-s', sessionName, '-c', cwd], { encoding: 'utf8' });
665
+ if (result.status !== 0) {
666
+ continue;
667
+ }
715
668
  }
716
- };
669
+ catch {
670
+ continue;
671
+ }
672
+ const tmuxTarget = `${sessionName}:0.0`;
673
+ return {
674
+ sessionName,
675
+ tmuxTarget,
676
+ reused: false,
677
+ stop: () => {
678
+ requestManagedTmuxSessionExit(spawnSyncImpl, sessionName);
679
+ }
680
+ };
681
+ }
682
+ return null;
717
683
  }
718
684
  function launchCommandInTmuxPane(args) {
719
685
  const { spawnSyncImpl, tmuxTarget, cwd, command, commandName, commandArgs, envOverrides, selfHealPolicy } = args;
@@ -822,20 +788,20 @@ function normalizeTmuxInjectedText(raw) {
822
788
  .join(' ')
823
789
  .trim();
824
790
  }
825
- async function startClockClientService(args) {
791
+ async function startSessionClientService(args) {
826
792
  const { ctx, resolved, workdir, tmuxTarget, spawnSyncImpl, clientType, managedTmuxSession, getManagedProcessState } = args;
827
793
  const daemonId = (() => {
828
794
  try {
829
- return `clockd_${crypto.randomUUID()}`;
795
+ return `sessiond_${crypto.randomUUID()}`;
830
796
  }
831
797
  catch {
832
- return `clockd_${Date.now()}_${Math.random().toString(16).slice(2)}`;
798
+ return `sessiond_${Date.now()}_${Math.random().toString(16).slice(2)}`;
833
799
  }
834
800
  })();
835
801
  const normalizedTmuxTarget = String(tmuxTarget || '').trim();
836
802
  if (!normalizedTmuxTarget) {
837
803
  // No tmux target means no reliable stdin injection path.
838
- // Do not register a clock-client daemon with a synthetic session id.
804
+ // Do not register a session-client daemon with a synthetic session id.
839
805
  return null;
840
806
  }
841
807
  const tmuxSessionId = (() => {
@@ -888,7 +854,7 @@ async function startClockClientService(args) {
888
854
  server?.listen(0, '127.0.0.1', () => {
889
855
  const address = server?.address();
890
856
  if (!address || typeof address === 'string') {
891
- reject(new Error('failed to resolve clock daemon callback address'));
857
+ reject(new Error('failed to resolve session daemon callback address'));
892
858
  return;
893
859
  }
894
860
  resolve(address.port);
@@ -906,7 +872,7 @@ async function startClockClientService(args) {
906
872
  callbackUrl = `http://127.0.0.1:${port}/inject`;
907
873
  }
908
874
  const controlUrl = `${resolved.protocol}://127.0.0.1:${resolved.port}${resolved.basePath}`;
909
- const controlRequestTimeoutMs = resolveIntFromEnv(ctx.env.ROUTECODEX_CLOCK_CLIENT_CONTROL_TIMEOUT_MS ?? ctx.env.RCC_CLOCK_CLIENT_CONTROL_TIMEOUT_MS, 1500, 200, 30_000);
875
+ const controlRequestTimeoutMs = resolveIntFromEnv(ctx.env.ROUTECODEX_SESSION_CLIENT_CONTROL_TIMEOUT_MS ?? ctx.env.RCC_SESSION_CLIENT_CONTROL_TIMEOUT_MS, 1500, 200, 30_000);
910
876
  const normalizeManagedProcessPayload = () => {
911
877
  const state = typeof getManagedProcessState === 'function' ? getManagedProcessState() : undefined;
912
878
  const managedClientProcess = state?.managedClientProcess === true;
@@ -955,7 +921,7 @@ async function startClockClientService(args) {
955
921
  }
956
922
  }
957
923
  };
958
- const reRegisterBackoffMs = resolveIntFromEnv(ctx.env.ROUTECODEX_CLOCK_CLIENT_REREGISTER_BACKOFF_MS ?? ctx.env.RCC_CLOCK_CLIENT_REREGISTER_BACKOFF_MS, 1500, 200, 60_000);
924
+ const reRegisterBackoffMs = resolveIntFromEnv(ctx.env.ROUTECODEX_SESSION_CLIENT_REREGISTER_BACKOFF_MS ?? ctx.env.RCC_SESSION_CLIENT_REREGISTER_BACKOFF_MS, 1500, 200, 60_000);
959
925
  let registerInFlight = null;
960
926
  let lastRegisterAttemptAtMs = 0;
961
927
  const registerDaemon = async () => {
@@ -964,7 +930,7 @@ async function startClockClientService(args) {
964
930
  }
965
931
  registerInFlight = (async () => {
966
932
  lastRegisterAttemptAtMs = Date.now();
967
- const result = await post('/daemon/clock-client/register', {
933
+ const result = await post('/daemon/session-client/register', {
968
934
  daemonId,
969
935
  tmuxSessionId,
970
936
  sessionId: tmuxSessionId,
@@ -985,7 +951,7 @@ async function startClockClientService(args) {
985
951
  }
986
952
  };
987
953
  const syncHeartbeat = async () => {
988
- const heartbeat = await post('/daemon/clock-client/heartbeat', {
954
+ const heartbeat = await post('/daemon/session-client/heartbeat', {
989
955
  daemonId,
990
956
  tmuxSessionId,
991
957
  sessionId: tmuxSessionId,
@@ -1032,7 +998,7 @@ async function startClockClientService(args) {
1032
998
  syncHeartbeat,
1033
999
  stop: async () => {
1034
1000
  clearInterval(heartbeat);
1035
- await post('/daemon/clock-client/unregister', { daemonId });
1001
+ await post('/daemon/session-client/unregister', { daemonId });
1036
1002
  if (!server) {
1037
1003
  return;
1038
1004
  }
@@ -1120,14 +1086,24 @@ export function createLauncherCommand(program, ctx, spec) {
1120
1086
  command.action(async (extraArgs = [], options) => {
1121
1087
  const spinner = await ctx.createSpinner(`Preparing ${spec.displayName} with RouteCodex...`);
1122
1088
  try {
1123
- const resolved = resolveServerConnection(ctx, fsImpl, pathImpl, options);
1124
- await ctx.ensureGuardianDaemon?.();
1125
- const ensureResult = await ensureServerReady(ctx, fsImpl, pathImpl, spinner, options, resolved, spec.allowAutoStartServer === true);
1126
- if (!ensureResult.ready) {
1127
- spinner.info('RouteCodex server is not running; launcher will continue and wait for your next requests.');
1089
+ const tmuxOnly = spec.commandName === 'codex';
1090
+ const resolved = tmuxOnly ? undefined : resolveServerConnection(ctx, fsImpl, pathImpl, options);
1091
+ const requireResolved = () => {
1092
+ if (!resolved) {
1093
+ throw new Error('RouteCodex server connection is not available for this launcher');
1094
+ }
1095
+ return resolved;
1096
+ };
1097
+ let ensureResult = null;
1098
+ if (!tmuxOnly) {
1099
+ const server = requireResolved();
1100
+ await ctx.ensureGuardianDaemon?.();
1101
+ ensureResult = await ensureServerReady(ctx, fsImpl, pathImpl, spinner, options, server, spec.allowAutoStartServer === true);
1102
+ if (!ensureResult.ready) {
1103
+ spinner.info('RouteCodex server is not running; launcher will continue and wait for your next requests.');
1104
+ }
1128
1105
  }
1129
1106
  spinner.text = `Launching ${spec.displayName}...`;
1130
- const baseUrl = `${resolved.protocol}://${resolved.connectHost}${resolved.portPart}${resolved.basePath}`;
1131
1107
  const currentCwd = resolveWorkingDirectory(ctx, fsImpl, pathImpl, options.cwd);
1132
1108
  const spawnSyncImpl = ctx.spawnSyncImpl ?? spawnSync;
1133
1109
  const tmuxSelfHealPolicy = resolveTmuxSelfHealPolicy(ctx.env);
@@ -1187,97 +1163,107 @@ export function createLauncherCommand(program, ctx, spec) {
1187
1163
  let managedTmuxSession = null;
1188
1164
  const tmuxEnabled = isTmuxAvailable(spawnSyncImpl);
1189
1165
  if (!tmuxEnabled) {
1190
- ctx.logger.warning('[clock-advanced] tmux not found; advanced clock client service disabled (launcher will continue).');
1166
+ ctx.logger.warning('[session-advanced] tmux not found; advanced session client service disabled (launcher will continue).');
1191
1167
  }
1192
- let tmuxTarget = tmuxEnabled ? resolveCurrentTmuxTarget(ctx.env, spawnSyncImpl) : null;
1193
- if (tmuxTarget && !isReusableTmuxPaneTarget(spawnSyncImpl, tmuxTarget, currentCwd)) {
1194
- tmuxTarget = null;
1168
+ let tmuxTarget = null;
1169
+ if (tmuxEnabled && !tmuxOnly) {
1170
+ tmuxTarget = resolveCurrentTmuxTarget(ctx.env, spawnSyncImpl);
1195
1171
  }
1196
- if (tmuxEnabled && !tmuxTarget) {
1172
+ if (tmuxEnabled && (tmuxOnly || !tmuxTarget)) {
1197
1173
  managedTmuxSession = createManagedTmuxSession({
1198
1174
  spawnSyncImpl,
1199
- cwd: currentCwd,
1200
- commandName: spec.commandName
1175
+ cwd: currentCwd
1201
1176
  });
1202
1177
  if (managedTmuxSession) {
1203
1178
  tmuxTarget = managedTmuxSession.tmuxTarget;
1204
- if (managedTmuxSession.reused) {
1205
- ctx.logger.info('[clock-advanced] reused existing managed tmux session and rebound launcher automatically.');
1206
- }
1207
- else {
1208
- ctx.logger.info('[clock-advanced] started managed tmux session automatically; no manual tmux setup needed.');
1209
- }
1179
+ ctx.logger.info('[session-advanced] started managed tmux session automatically; no manual tmux setup needed.');
1210
1180
  }
1211
1181
  else {
1212
- ctx.logger.warning('[clock-advanced] failed to start managed tmux session; launcher continues without advanced mode.');
1182
+ ctx.logger.warning('[session-advanced] failed to start managed tmux session; launcher continues without advanced mode.');
1213
1183
  }
1214
1184
  }
1215
1185
  const managedClientProcessEnabled = !managedTmuxSession;
1216
1186
  let managedClientPid = null;
1217
1187
  const managedClientCommandHint = managedClientProcessEnabled ? resolvedBinary : undefined;
1218
- const reclaimRequiredRaw = String(ctx.env.ROUTECODEX_CLOCK_RECLAIM_REQUIRED
1219
- ?? ctx.env.RCC_CLOCK_RECLAIM_REQUIRED
1188
+ const reclaimRequiredRaw = String(ctx.env.ROUTECODEX_SESSION_RECLAIM_REQUIRED
1189
+ ?? ctx.env.RCC_SESSION_RECLAIM_REQUIRED
1220
1190
  ?? '1')
1221
1191
  .trim()
1222
1192
  .toLowerCase();
1223
1193
  const reclaimRequired = reclaimRequiredRaw !== '0' && reclaimRequiredRaw !== 'false' && reclaimRequiredRaw !== 'no';
1224
- const clockClientService = await startClockClientService({
1225
- ctx,
1226
- resolved,
1227
- workdir: currentCwd,
1228
- tmuxTarget,
1229
- spawnSyncImpl,
1230
- clientType: spec.commandName,
1231
- managedTmuxSession: Boolean(managedTmuxSession),
1232
- getManagedProcessState: () => ({
1233
- managedClientProcess: managedClientProcessEnabled,
1234
- managedClientPid,
1235
- managedClientCommandHint
1236
- })
1237
- });
1238
- if (managedClientProcessEnabled && reclaimRequired && tmuxTarget && !clockClientService) {
1239
- throw new Error('clock client registration failed for managed child process; aborting launch to avoid orphan process');
1240
- }
1241
- if (tmuxTarget && !clockClientService) {
1242
- ctx.logger.warning('[clock-advanced] failed to start clock client daemon service; launcher continues without advanced mode.');
1243
- }
1244
- const clockAdvancedEnabled = Boolean(clockClientService && tmuxTarget);
1245
- const inferredTmuxSessionId = clockClientService?.tmuxSessionId ||
1246
- inferTmuxSessionIdFromTarget(tmuxTarget) ||
1247
- undefined;
1248
- const inferredDaemonId = clockClientService?.daemonId ||
1249
- (inferredTmuxSessionId ? `clockd_unbound_${process.pid}` : undefined);
1250
- const clockClientApiKey = inferredTmuxSessionId && inferredDaemonId
1251
- ? encodeClockClientApiKey(resolved.configuredApiKey || 'rcc-proxy-key', inferredDaemonId, inferredTmuxSessionId)
1252
- : (resolved.configuredApiKey || 'rcc-proxy-key');
1253
- if (isClockScopeTraceEnabled()) {
1254
- try {
1255
- const parsedDaemonId = extractClockClientDaemonIdFromApiKey(clockClientApiKey) || 'none';
1256
- const parsedTmuxSessionId = extractClockClientTmuxSessionIdFromApiKey(clockClientApiKey) || 'none';
1257
- const verbose = isClockScopeTraceVerbose();
1258
- ctx.logger.info(`[clock-scope][launch] command=${spec.commandName} advanced=${clockAdvancedEnabled ? 'on' : 'off'} ` +
1259
- `daemon=${parsedDaemonId} tmux=${parsedTmuxSessionId} tmuxTarget=${tmuxTarget || 'none'}` +
1260
- (verbose ? ` managedTmux=${managedTmuxSession ? 'yes' : 'no'} serverStarted=${ensureResult.started ? 'yes' : 'no'}` : ''));
1194
+ let sessionClientService = null;
1195
+ let sessionAdvancedEnabled = false;
1196
+ let inferredTmuxSessionId;
1197
+ let inferredDaemonId;
1198
+ let sessionClientApiKey;
1199
+ let tmuxOnlySessionId;
1200
+ if (!tmuxOnly) {
1201
+ const server = requireResolved();
1202
+ sessionClientService = await startSessionClientService({
1203
+ ctx,
1204
+ resolved: server,
1205
+ workdir: currentCwd,
1206
+ tmuxTarget,
1207
+ spawnSyncImpl,
1208
+ clientType: spec.commandName,
1209
+ managedTmuxSession: Boolean(managedTmuxSession),
1210
+ getManagedProcessState: () => ({
1211
+ managedClientProcess: managedClientProcessEnabled,
1212
+ managedClientPid,
1213
+ managedClientCommandHint
1214
+ })
1215
+ });
1216
+ if (managedClientProcessEnabled && reclaimRequired && tmuxTarget && !sessionClientService) {
1217
+ throw new Error('session client registration failed for managed child process; aborting launch to avoid orphan process');
1261
1218
  }
1262
- catch {
1263
- // best-effort diagnostics only
1219
+ if (tmuxTarget && !sessionClientService) {
1220
+ ctx.logger.warning('[session-advanced] failed to start session client daemon service; launcher continues without advanced mode.');
1264
1221
  }
1265
- }
1266
- await ctx.registerGuardianProcess?.({
1267
- source: spec.commandName,
1268
- pid: process.pid,
1269
- ppid: process.ppid,
1270
- port: resolved.port,
1271
- tmuxSessionId: clockClientService?.tmuxSessionId || inferTmuxSessionIdFromTarget(tmuxTarget) || undefined,
1272
- tmuxTarget: tmuxTarget || undefined,
1273
- metadata: {
1274
- workingDirectory: currentCwd,
1275
- binary: resolvedBinary,
1276
- managedTmuxSession: Boolean(managedTmuxSession),
1277
- autoStartedServer: ensureResult.started === true
1222
+ sessionAdvancedEnabled = Boolean(sessionClientService && tmuxTarget);
1223
+ inferredTmuxSessionId =
1224
+ sessionClientService?.tmuxSessionId ||
1225
+ inferTmuxSessionIdFromTarget(tmuxTarget) ||
1226
+ undefined;
1227
+ inferredDaemonId =
1228
+ sessionClientService?.daemonId ||
1229
+ (inferredTmuxSessionId ? `sessiond_unbound_${process.pid}` : undefined);
1230
+ sessionClientApiKey =
1231
+ inferredTmuxSessionId && inferredDaemonId
1232
+ ? encodeSessionClientApiKey(server.configuredApiKey || 'rcc-proxy-key', inferredDaemonId, inferredTmuxSessionId)
1233
+ : (server.configuredApiKey || 'rcc-proxy-key');
1234
+ if (isSessionScopeTraceEnabled()) {
1235
+ try {
1236
+ const parsedDaemonId = extractSessionClientDaemonIdFromApiKey(sessionClientApiKey) || 'none';
1237
+ const parsedTmuxSessionId = extractSessionClientScopeIdFromApiKey(sessionClientApiKey) || 'none';
1238
+ const verbose = isSessionScopeTraceVerbose();
1239
+ ctx.logger.info(`[session-scope][launch] command=${spec.commandName} advanced=${sessionAdvancedEnabled ? 'on' : 'off'} ` +
1240
+ `daemon=${parsedDaemonId} tmux=${parsedTmuxSessionId} tmuxTarget=${tmuxTarget || 'none'}` +
1241
+ (verbose ? ` managedTmux=${managedTmuxSession ? 'yes' : 'no'} serverStarted=${ensureResult?.started ? 'yes' : 'no'}` : ''));
1242
+ }
1243
+ catch {
1244
+ // best-effort diagnostics only
1245
+ }
1278
1246
  }
1279
- });
1247
+ await ctx.registerGuardianProcess?.({
1248
+ source: spec.commandName,
1249
+ pid: process.pid,
1250
+ ppid: process.ppid,
1251
+ port: server.port,
1252
+ tmuxSessionId: sessionClientService?.tmuxSessionId || inferTmuxSessionIdFromTarget(tmuxTarget) || undefined,
1253
+ tmuxTarget: tmuxTarget || undefined,
1254
+ metadata: {
1255
+ workingDirectory: currentCwd,
1256
+ binary: resolvedBinary,
1257
+ managedTmuxSession: Boolean(managedTmuxSession),
1258
+ autoStartedServer: ensureResult?.started === true
1259
+ }
1260
+ });
1261
+ }
1280
1262
  const applyLifecycleOrThrow = async (args) => {
1263
+ if (tmuxOnly || !resolved) {
1264
+ return;
1265
+ }
1266
+ const server = requireResolved();
1281
1267
  const accepted = await ctx.reportGuardianLifecycle?.({
1282
1268
  action: args.action,
1283
1269
  source: `cli.launcher.${spec.commandName}`,
@@ -1285,39 +1271,62 @@ export function createLauncherCommand(program, ctx, spec) {
1285
1271
  targetPid: args.targetPid && args.targetPid > 0 ? args.targetPid : undefined,
1286
1272
  signal: args.signal,
1287
1273
  metadata: {
1288
- port: resolved.port,
1289
- serverUrl: resolved.serverUrl
1274
+ port: server.port,
1275
+ serverUrl: server.serverUrl
1290
1276
  }
1291
1277
  });
1292
1278
  if (ctx.reportGuardianLifecycle && accepted !== true) {
1293
1279
  throw new Error(`guardian lifecycle apply rejected (${args.action})`);
1294
1280
  }
1295
1281
  };
1296
- const toolEnv = spec.buildEnv({
1297
- env: {
1298
- ...ctx.env,
1299
- PWD: currentCwd,
1300
- RCC_WORKDIR: currentCwd,
1301
- ROUTECODEX_WORKDIR: currentCwd,
1302
- OPENAI_BASE_URL: normalizeOpenAiBaseUrl(baseUrl),
1303
- OPENAI_API_BASE: normalizeOpenAiBaseUrl(baseUrl),
1304
- OPENAI_API_BASE_URL: normalizeOpenAiBaseUrl(baseUrl),
1305
- OPENAI_API_KEY: clockClientApiKey,
1306
- RCC_CLOCK_ADVANCED_ENABLED: clockAdvancedEnabled ? '1' : '0',
1307
- ...(inferredTmuxSessionId
1308
- ? {
1309
- RCC_CLOCK_CLIENT_SESSION_ID: inferredTmuxSessionId,
1310
- RCC_CLOCK_CLIENT_TMUX_SESSION_ID: inferredTmuxSessionId
1282
+ if (tmuxOnly) {
1283
+ const tmuxSessionId = inferTmuxSessionIdFromTarget(tmuxTarget) || '';
1284
+ tmuxOnlySessionId = tmuxSessionId || undefined;
1285
+ }
1286
+ const toolEnv = (() => {
1287
+ if (tmuxOnly) {
1288
+ const env = { ...ctx.env };
1289
+ if (tmuxOnlySessionId) {
1290
+ const baseKey = (typeof env.ROUTECODEX_HTTP_APIKEY === 'string' && env.ROUTECODEX_HTTP_APIKEY.trim())
1291
+ || '';
1292
+ if (!baseKey) {
1293
+ ctx.logger.warning('[session-scope] ROUTECODEX_HTTP_APIKEY is empty; tmux scope not injected.');
1294
+ return env;
1311
1295
  }
1312
- : {}),
1313
- ...(inferredDaemonId
1314
- ? { RCC_CLOCK_CLIENT_DAEMON_ID: inferredDaemonId }
1315
- : {})
1316
- },
1317
- baseUrl,
1318
- configuredApiKey: resolved.configuredApiKey,
1319
- cwd: currentCwd
1320
- });
1296
+ const scopedKey = encodeSessionClientApiKey(baseKey, '', tmuxOnlySessionId);
1297
+ env.ROUTECODEX_HTTP_APIKEY = scopedKey;
1298
+ env.OPENAI_API_KEY = scopedKey;
1299
+ env.ANTHROPIC_AUTH_TOKEN = scopedKey;
1300
+ }
1301
+ return env;
1302
+ }
1303
+ const server = requireResolved();
1304
+ return spec.buildEnv({
1305
+ env: {
1306
+ ...ctx.env,
1307
+ PWD: currentCwd,
1308
+ RCC_WORKDIR: currentCwd,
1309
+ ROUTECODEX_WORKDIR: currentCwd,
1310
+ OPENAI_BASE_URL: normalizeOpenAiBaseUrl(`${server.protocol}://${server.connectHost}${server.portPart}${server.basePath}`),
1311
+ OPENAI_API_BASE: normalizeOpenAiBaseUrl(`${server.protocol}://${server.connectHost}${server.portPart}${server.basePath}`),
1312
+ OPENAI_API_BASE_URL: normalizeOpenAiBaseUrl(`${server.protocol}://${server.connectHost}${server.portPart}${server.basePath}`),
1313
+ OPENAI_API_KEY: sessionClientApiKey,
1314
+ RCC_SESSION_ADVANCED_ENABLED: sessionAdvancedEnabled ? '1' : '0',
1315
+ ...(inferredTmuxSessionId
1316
+ ? {
1317
+ RCC_SESSION_CLIENT_SESSION_ID: inferredTmuxSessionId,
1318
+ RCC_SESSION_CLIENT_TMUX_SESSION_ID: inferredTmuxSessionId
1319
+ }
1320
+ : {}),
1321
+ ...(inferredDaemonId
1322
+ ? { RCC_SESSION_CLIENT_DAEMON_ID: inferredDaemonId }
1323
+ : {})
1324
+ },
1325
+ baseUrl: `${server.protocol}://${server.connectHost}${server.portPart}${server.basePath}`,
1326
+ configuredApiKey: server.configuredApiKey,
1327
+ cwd: currentCwd
1328
+ });
1329
+ })();
1321
1330
  const shouldUseShell = ctx.isWindows &&
1322
1331
  !pathImpl.extname(resolvedBinary) &&
1323
1332
  !resolvedBinary.includes('/') &&
@@ -1356,14 +1365,17 @@ export function createLauncherCommand(program, ctx, spec) {
1356
1365
  managedClientPid = typeof toolProcess.pid === 'number' && Number.isFinite(toolProcess.pid)
1357
1366
  ? Math.floor(toolProcess.pid)
1358
1367
  : null;
1359
- if (clockClientService && managedClientProcessEnabled && managedClientPid) {
1360
- void clockClientService.syncHeartbeat();
1368
+ if (sessionClientService && managedClientProcessEnabled && managedClientPid) {
1369
+ void sessionClientService.syncHeartbeat();
1361
1370
  }
1362
1371
  spinner.succeed(`${spec.displayName} launched with RouteCodex proxy`);
1363
1372
  if (!managedTmuxSession) {
1364
- ctx.logger.info(`Using RouteCodex server at: ${baseUrl}`);
1373
+ if (!tmuxOnly) {
1374
+ const server = requireResolved();
1375
+ ctx.logger.info(`Using RouteCodex server at: ${server.protocol}://${server.connectHost}${server.portPart}${server.basePath}`);
1376
+ }
1365
1377
  ctx.logger.info(`${spec.displayName} binary: ${resolvedBinary}`);
1366
- if (ensureResult.started && ensureResult.logPath) {
1378
+ if (!tmuxOnly && ensureResult?.started && ensureResult.logPath) {
1367
1379
  ctx.logger.info(`RouteCodex auto-start logs: ${ensureResult.logPath}`);
1368
1380
  }
1369
1381
  ctx.logger.info(`Working directory for ${spec.displayName}: ${currentCwd}`);
@@ -1393,7 +1405,7 @@ export function createLauncherCommand(program, ctx, spec) {
1393
1405
  toolProcessClosing = true;
1394
1406
  logClientExitSummary();
1395
1407
  try {
1396
- await clockClientService?.stop();
1408
+ await sessionClientService?.stop();
1397
1409
  }
1398
1410
  catch {
1399
1411
  // ignore