codex-overleaf-link 2.1.5 → 2.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.
Files changed (66) hide show
  1. package/README.md +27 -23
  2. package/extension/bootstrap/background.js +14 -1
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/runtime-manifest.json +10 -0
  5. package/extension/src/background.js +507 -8
  6. package/extension/src/backgroundUpdateCoordinator.js +284 -19
  7. package/extension/src/content/activeTurnControl.js +179 -0
  8. package/extension/src/content/composerPanel.js +14 -5
  9. package/extension/src/content/contentRuntime.js +330 -309
  10. package/extension/src/content/diagnosticsPanel.js +132 -1
  11. package/extension/src/content/markdownDomRenderer.js +451 -0
  12. package/extension/src/content/markdownText.js +86 -7
  13. package/extension/src/content/mathText.js +49 -4
  14. package/extension/src/content/modelPicker.js +14 -5
  15. package/extension/src/content/nativeChannel.js +7 -1
  16. package/extension/src/content/panelRenderer.js +112 -12
  17. package/extension/src/content/pendingInputView.js +106 -0
  18. package/extension/src/content/projectProviderSelection.js +68 -0
  19. package/extension/src/content/projectSessionCleanup.js +4 -0
  20. package/extension/src/content/providerSettingsCoordinator.js +170 -22
  21. package/extension/src/content/providerSettingsDialog.js +2 -2
  22. package/extension/src/content/recentProjects.js +7 -2
  23. package/extension/src/content/runGuidanceController.js +70 -0
  24. package/extension/src/content/runGuidanceView.js +48 -0
  25. package/extension/src/content/runQueueScheduler.js +260 -0
  26. package/extension/src/content/runTimelineView.js +33 -80
  27. package/extension/src/content/sessionManager.js +2 -2
  28. package/extension/src/content/sessionPersistence.js +62 -0
  29. package/extension/src/content/settingsPanel.js +1 -1
  30. package/extension/src/content/updateNotice.js +80 -1
  31. package/extension/src/page/writebackRouter.js +3 -9
  32. package/extension/src/shared/compatibility.js +2 -1
  33. package/extension/src/shared/i18n.js +38 -0
  34. package/extension/src/shared/providerProfiles.js +3 -2
  35. package/extension/src/shared/runInputQueue.js +253 -0
  36. package/extension/src/shared/sessionState.js +45 -22
  37. package/extension/src/shared/storageDb.js +39 -44
  38. package/extension/src/shared/storageMigration.js +123 -3
  39. package/extension/src/shared/storageRunActions.js +34 -1
  40. package/extension/src/shared/updateConsent.js +15 -4
  41. package/extension/styles/panel.css +319 -20
  42. package/extension/vendor/katex/katex.min.css +1 -1
  43. package/extension/vendor/markdown-it/LICENSE +22 -0
  44. package/extension/vendor/markdown-it/SOURCE.json +7 -0
  45. package/extension/vendor/markdown-it/VERSION +1 -0
  46. package/extension/vendor/markdown-it/markdown-it.min.js +3 -0
  47. package/native-host/src/anthropicBridgeRequest.js +3 -3
  48. package/native-host/src/anthropicBridgeResponse.js +58 -17
  49. package/native-host/src/anthropicMessagesBridge.js +156 -23
  50. package/native-host/src/chatBridgeRequest.js +1 -1
  51. package/native-host/src/chatBridgeResponse.js +12 -8
  52. package/native-host/src/chatCompletionsBridge.js +22 -40
  53. package/native-host/src/codexPromptAssembly.js +3 -1
  54. package/native-host/src/codexProviderLaunch.js +6 -3
  55. package/native-host/src/codexSessionRunner.js +65 -65
  56. package/native-host/src/codexSessionTiming.js +45 -0
  57. package/native-host/src/index.js +58 -3
  58. package/native-host/src/managedInstall.js +45 -7
  59. package/native-host/src/providerProfile.js +2 -2
  60. package/native-host/src/providerStreamLifecycle.js +100 -0
  61. package/native-host/src/responsesPassthroughBridge.js +636 -0
  62. package/native-host/src/taskRunnerRuntime.js +81 -2
  63. package/native-host/src/updateArchive.js +4 -3
  64. package/native-host/src/updateManager.js +306 -19
  65. package/native-host/src/updateTrust.js +1 -1
  66. package/package.json +3 -1
@@ -22,6 +22,13 @@ const { createSubagentBroker } = require('./subagentBroker');
22
22
  const { resolveCodexCommand, shouldUseShellForCommand } = require('./codexCommand');
23
23
  const { applyProviderEnvironment, buildProviderConfigArgs, prepareProviderLaunch } = require('./codexProviderLaunch');
24
24
  const { buildReadProgressRules, createReadProgressController } = require('./readProgressGuard');
25
+ const {
26
+ createCodexIdleWatchdog,
27
+ createOptionalTimeout,
28
+ getAbortReason,
29
+ parseOptionalPositiveInteger,
30
+ throwIfAborted
31
+ } = require('./codexSessionTiming');
25
32
 
26
33
  const PARALLEL_SUBAGENTS_SKILL_ID = 'parallel-subagents';
27
34
 
@@ -30,7 +37,7 @@ const MAX_TURN_ATTACHMENT_BYTES = 12 * 1024 * 1024;
30
37
  const MAX_TURN_ATTACHMENTS = 8;
31
38
  const MAX_TURN_ATTACHMENT_TOTAL_BYTES = MAX_TURN_ATTACHMENT_BYTES * MAX_TURN_ATTACHMENTS;
32
39
 
33
- async function runCodexSession({ params = {}, env = process.env, emit = () => {}, rootDir, executeCodex, providerLaunch, signal } = {}) {
40
+ async function runCodexSession({ params = {}, env = process.env, emit = () => {}, rootDir, executeCodex, providerLaunch, signal, onControlReady } = {}) {
34
41
  throwIfAborted(signal);
35
42
  const projectId = params.projectId || params.project?.projectId || params.project?.id || params.project?.url || 'overleaf-project';
36
43
  const skillInvocation = normalizeSkillInvocation(params.skillInvocation);
@@ -162,6 +169,7 @@ async function runCodexSession({ params = {}, env = process.env, emit = () => {}
162
169
  // While subagent workers are active, the parent idle watchdog resets
163
170
  // instead of failing the run (spec P2-7 fix).
164
171
  hasExternalActivity: subagentBroker ? () => subagentBroker.hasActiveWorkers() : undefined,
172
+ onControlReady,
165
173
  signal
166
174
  });
167
175
  } catch (error) {
@@ -808,6 +816,7 @@ function runCodexAppServerProcess(input) {
808
816
  let stderr = '';
809
817
  let activeThreadId = '';
810
818
  let activeTurnId = '';
819
+ let controlPublished = false;
811
820
  const assistantMessages = new Map();
812
821
  const assistantMessageOrder = [];
813
822
  const readProgressController = createReadProgressController({
@@ -841,7 +850,15 @@ function runCodexAppServerProcess(input) {
841
850
  fail(new Error(`Codex app-server produced no events for ${ms}ms (idle watchdog); the run was aborted to release the project lock.`));
842
851
  });
843
852
  const onAbort = () => {
844
- fail(getAbortReason(input.signal));
853
+ const reason = getAbortReason(input.signal);
854
+ if (!activeThreadId || !activeTurnId) {
855
+ fail(reason);
856
+ return;
857
+ }
858
+ Promise.race([
859
+ interruptActiveTurn().catch(() => null),
860
+ new Promise(resolveDelay => setTimeout(resolveDelay, 1500))
861
+ ]).finally(() => fail(reason));
845
862
  };
846
863
  input.signal?.addEventListener('abort', onAbort, { once: true });
847
864
 
@@ -910,6 +927,7 @@ function runCodexAppServerProcess(input) {
910
927
 
911
928
  const turnResponse = await startTurnWithSummaryFallback(activeThreadId);
912
929
  activeTurnId = turnResponse?.turn?.id || '';
930
+ publishActiveTurnControl();
913
931
  readProgressController.flush();
914
932
  }
915
933
 
@@ -935,6 +953,7 @@ function runCodexAppServerProcess(input) {
935
953
  }
936
954
 
937
955
  function request(method, params) {
956
+ idleWatchdog.reset();
938
957
  const id = nextId++;
939
958
  const message = { id, method, params };
940
959
  child.stdin.write(`${JSON.stringify(message)}\n`);
@@ -986,6 +1005,11 @@ function runCodexAppServerProcess(input) {
986
1005
  }
987
1006
 
988
1007
  if (message.method) {
1008
+ if (message.method === 'turn/started') {
1009
+ activeThreadId = message.params?.thread?.id || message.params?.threadId || activeThreadId;
1010
+ activeTurnId = message.params?.turn?.id || message.params?.turnId || activeTurnId;
1011
+ publishActiveTurnControl();
1012
+ }
989
1013
  recordAssistantMessage(message);
990
1014
  // For `error` events surface the actual error text as the visible
991
1015
  // title so the run timeline reads "Reconnecting... 2/5" instead of
@@ -1061,11 +1085,50 @@ function runCodexAppServerProcess(input) {
1061
1085
  assistantMessages.set(itemId, text);
1062
1086
  }
1063
1087
 
1088
+ function publishActiveTurnControl() {
1089
+ if (controlPublished || !activeThreadId || !activeTurnId) {
1090
+ return;
1091
+ }
1092
+ controlPublished = true;
1093
+ const control = Object.freeze({
1094
+ threadId: activeThreadId,
1095
+ turnId: activeTurnId,
1096
+ steer: ({ input: steerInput, clientUserMessageId } = {}) => request('turn/steer', {
1097
+ threadId: activeThreadId,
1098
+ expectedTurnId: activeTurnId,
1099
+ input: Array.isArray(steerInput) ? steerInput : [],
1100
+ clientUserMessageId: clientUserMessageId || undefined
1101
+ }),
1102
+ interrupt: () => interruptActiveTurn()
1103
+ });
1104
+ input.onControlReady?.(control);
1105
+ emitCodexEvent(input.emit, 'codex.turn.bound', 'Codex turn is ready for follow-up guidance', {
1106
+ threadId: activeThreadId,
1107
+ turnId: activeTurnId
1108
+ }, 'running');
1109
+ }
1110
+
1111
+ function interruptActiveTurn() {
1112
+ if (!activeThreadId || !activeTurnId || settled) {
1113
+ return Promise.resolve({ interrupted: false });
1114
+ }
1115
+ return request('turn/interrupt', {
1116
+ threadId: activeThreadId,
1117
+ turnId: activeTurnId
1118
+ });
1119
+ }
1120
+
1064
1121
  function succeed() {
1065
1122
  if (settled) {
1066
1123
  return;
1067
1124
  }
1068
1125
  settled = true;
1126
+ const turnEndedError = new Error('Codex turn ended before the pending app-server request completed.');
1127
+ turnEndedError.code = 'codex_turn_ended';
1128
+ for (const pendingRequest of pending.values()) {
1129
+ pendingRequest.reject(turnEndedError);
1130
+ }
1131
+ pending.clear();
1069
1132
  cleanup();
1070
1133
  child.kill('SIGTERM');
1071
1134
  resolve({
@@ -1338,69 +1401,6 @@ function tokenizeShellCommand(command) {
1338
1401
  return tokens;
1339
1402
  }
1340
1403
 
1341
- function createOptionalTimeout(value, onTimeout) {
1342
- const timeoutMs = parseOptionalPositiveInteger(value);
1343
- if (!timeoutMs) {
1344
- return {
1345
- cancel() {}
1346
- };
1347
- }
1348
- const timer = setTimeout(() => onTimeout(timeoutMs), timeoutMs);
1349
- return {
1350
- cancel() {
1351
- clearTimeout(timer);
1352
- }
1353
- };
1354
- }
1355
-
1356
- // Idle-style watchdog: fires only after the app-server has been silent for
1357
- // `idleMs`. Callers must invoke .reset() on every signal of liveness
1358
- // (incoming line, outgoing request). cancel() stops the timer on settle.
1359
- // Returns no-op when idleMs is non-positive (defensive guard against bad env
1360
- // var values).
1361
- function createCodexIdleWatchdog(idleMs, onIdle) {
1362
- if (!(idleMs > 0)) {
1363
- return {
1364
- reset() {},
1365
- cancel() {}
1366
- };
1367
- }
1368
- let timer = setTimeout(() => onIdle(idleMs), idleMs);
1369
- return {
1370
- reset() {
1371
- clearTimeout(timer);
1372
- timer = setTimeout(() => onIdle(idleMs), idleMs);
1373
- },
1374
- cancel() {
1375
- clearTimeout(timer);
1376
- }
1377
- };
1378
- }
1379
-
1380
- function parseOptionalPositiveInteger(value) {
1381
- if (value === undefined || value === null || value === '') {
1382
- return 0;
1383
- }
1384
- const number = Number(value);
1385
- return Number.isFinite(number) && number > 0 ? Math.floor(number) : 0;
1386
- }
1387
-
1388
- function throwIfAborted(signal) {
1389
- if (!signal?.aborted) {
1390
- return;
1391
- }
1392
- throw getAbortReason(signal);
1393
- }
1394
-
1395
- function getAbortReason(signal) {
1396
- if (signal?.reason instanceof Error) {
1397
- return signal.reason;
1398
- }
1399
- const error = new Error('Codex run was cancelled by the user');
1400
- error.code = 'codex_cancelled';
1401
- return error;
1402
- }
1403
-
1404
1404
  function buildFinalAssistantMessage(messages = new Map(), order = []) {
1405
1405
  const values = [];
1406
1406
  const seenIds = new Set();
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ function createOptionalTimeout(value, onTimeout) {
4
+ const timeoutMs = parseOptionalPositiveInteger(value);
5
+ if (!timeoutMs) return { cancel() {} };
6
+ const timer = setTimeout(() => onTimeout(timeoutMs), timeoutMs);
7
+ return { cancel: () => clearTimeout(timer) };
8
+ }
9
+
10
+ function createCodexIdleWatchdog(idleMs, onIdle) {
11
+ if (!(idleMs > 0)) return { reset() {}, cancel() {} };
12
+ let timer = setTimeout(() => onIdle(idleMs), idleMs);
13
+ return {
14
+ reset() {
15
+ clearTimeout(timer);
16
+ timer = setTimeout(() => onIdle(idleMs), idleMs);
17
+ },
18
+ cancel: () => clearTimeout(timer)
19
+ };
20
+ }
21
+
22
+ function parseOptionalPositiveInteger(value) {
23
+ if (value === undefined || value === null || value === '') return 0;
24
+ const number = Number(value);
25
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : 0;
26
+ }
27
+
28
+ function getAbortReason(signal) {
29
+ if (signal?.reason instanceof Error) return signal.reason;
30
+ const error = new Error('Codex run was cancelled by the user');
31
+ error.code = 'codex_cancelled';
32
+ return error;
33
+ }
34
+
35
+ function throwIfAborted(signal) {
36
+ if (signal?.aborted) throw getAbortReason(signal);
37
+ }
38
+
39
+ module.exports = {
40
+ createCodexIdleWatchdog,
41
+ createOptionalTimeout,
42
+ getAbortReason,
43
+ parseOptionalPositiveInteger,
44
+ throwIfAborted
45
+ };
@@ -4,11 +4,13 @@
4
4
  const { decodeFrames, encodeMessage } = require('./nativeMessaging');
5
5
  const { logDebug } = require('./debugLog');
6
6
  const { handleRequest } = require('./taskRunner');
7
- const { getActiveNativeWorkState } = require('./taskRunnerRuntime');
7
+ const { abortAllActiveOperations, getActiveNativeWorkState } = require('./taskRunnerRuntime');
8
8
  const { handleUpdateRequest, isUpdateMethod } = require('./updateManager');
9
9
  const { buildNativeRuntimeEnv, summarizeNativeEnvironment } = require('./nativeEnvironment');
10
10
 
11
11
  let buffered = Buffer.alloc(0);
12
+ let stdoutUnavailable = false;
13
+ let nativeDisconnectHandled = false;
12
14
  const runtimeEnv = buildNativeRuntimeEnv(process.env);
13
15
  Object.assign(process.env, runtimeEnv);
14
16
  logDebug('environment.ready', summarizeNativeEnvironment(runtimeEnv));
@@ -71,10 +73,24 @@ async function handleDecodedMessage(message) {
71
73
 
72
74
  process.stdin.on('error', error => {
73
75
  logDebug('stdin.error', { message: error.message });
74
- console.error(`stdin error: ${error.message}`);
76
+ handleNativeDisconnect('stdin_error', error);
77
+ });
78
+ process.stdin.on('end', () => handleNativeDisconnect('stdin_end'));
79
+ process.stdin.on('close', () => handleNativeDisconnect('stdin_close'));
80
+
81
+ process.stdout.on('error', error => {
82
+ if (isExpectedNativeDisconnect(error)) {
83
+ handleNativeDisconnect('stdout_disconnected', error);
84
+ return;
85
+ }
86
+ logDebug('stdout.error', { code: error.code, message: error.message, stack: error.stack });
75
87
  });
76
88
 
77
89
  process.on('uncaughtException', error => {
90
+ if (isExpectedNativeDisconnect(error)) {
91
+ handleNativeDisconnect('uncaught_native_disconnect', error);
92
+ return;
93
+ }
78
94
  logDebug('process.uncaught_exception', {
79
95
  message: error.message,
80
96
  stack: error.stack
@@ -91,6 +107,10 @@ process.on('unhandledRejection', reason => {
91
107
  });
92
108
 
93
109
  function writeResponse(response) {
110
+ if (stdoutUnavailable || process.stdout.destroyed || process.stdout.writableEnded) {
111
+ logDebug('stdout.write_skipped', { ok: response?.ok, code: response?.error?.code });
112
+ return false;
113
+ }
94
114
  let frame;
95
115
  try {
96
116
  frame = encodeMessage(response);
@@ -110,7 +130,42 @@ function writeResponse(response) {
110
130
  }
111
131
  }
112
132
  logDebug('stdout.write', { bytes: frame.length, ok: response?.ok, code: response?.error?.code });
113
- process.stdout.write(frame);
133
+ try {
134
+ process.stdout.write(frame, error => {
135
+ if (!error) return;
136
+ if (isExpectedNativeDisconnect(error)) {
137
+ handleNativeDisconnect('stdout_write_disconnected', error);
138
+ return;
139
+ }
140
+ logDebug('stdout.write_failed', { code: error.code, message: error.message });
141
+ });
142
+ return true;
143
+ } catch (error) {
144
+ if (isExpectedNativeDisconnect(error)) {
145
+ handleNativeDisconnect('stdout_write_disconnected', error);
146
+ return false;
147
+ }
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ function isExpectedNativeDisconnect(error) {
153
+ return ['EPIPE', 'ERR_STREAM_DESTROYED', 'ERR_STREAM_WRITE_AFTER_END'].includes(String(error?.code || ''));
154
+ }
155
+
156
+ function handleNativeDisconnect(source, error) {
157
+ if (nativeDisconnectHandled) return;
158
+ nativeDisconnectHandled = true;
159
+ stdoutUnavailable = true;
160
+ logDebug('native_transport.disconnected', {
161
+ source,
162
+ code: error?.code,
163
+ message: error?.message
164
+ });
165
+ abortAllActiveOperations('Native messaging transport disconnected.');
166
+ process.exitCode = 0;
167
+ const exitTimer = setTimeout(() => process.exit(0), 1500);
168
+ exitTimer.unref?.();
114
169
  }
115
170
 
116
171
  function buildOversizeResponseFallback(response, error) {
@@ -32,6 +32,8 @@ function installManagedDistribution(options = {}) {
32
32
  if (!parseSemver(version)) {
33
33
  throw updateError('managed_version_invalid', 'Managed installation requires a stable semantic version.');
34
34
  }
35
+ const releaseRef = String(options.releaseRef || process.env.CODEX_OVERLEAF_RELEASE_REF || process.env.CODEX_OVERLEAF_REF || `v${version}`);
36
+ const releaseChannel = options.releaseChannel || (releaseRef === `v${version}` ? 'stable' : 'prerelease');
35
37
  const platform = options.platform || process.platform;
36
38
  const browser = options.browser || 'chrome';
37
39
  const env = options.env || process.env;
@@ -52,7 +54,14 @@ function installManagedDistribution(options = {}) {
52
54
  const unique = process.pid + '-' + Date.now() + '-' + Math.random().toString(16).slice(2);
53
55
  const extensionStage = path.join(parent, '.extension.staging-' + unique);
54
56
  const extensionRollback = path.join(parent, '.extension.rollback-' + unique);
55
- buildManagedExtensionTree({ packageRoot, targetRoot: extensionStage, version, allowedFiles: options.allowedFiles });
57
+ buildManagedExtensionTree({
58
+ packageRoot,
59
+ targetRoot: extensionStage,
60
+ version,
61
+ releaseRef,
62
+ releaseChannel,
63
+ allowedFiles: options.allowedFiles
64
+ });
56
65
 
57
66
  const previousExtensionMarker = readManagedMarker(extensionRoot, EXTENSION_MARKER);
58
67
  if (fs.existsSync(extensionRoot) && !isManagedMarker(previousExtensionMarker, 'extension')) {
@@ -75,7 +84,7 @@ function installManagedDistribution(options = {}) {
75
84
  }
76
85
 
77
86
  try {
78
- installManagedNativeVersion({ packageRoot, nativeRoot, version });
87
+ installManagedNativeVersion({ packageRoot, nativeRoot, version, releaseRef, releaseChannel });
79
88
  installManagedNativeRegistration({
80
89
  browser,
81
90
  env,
@@ -111,6 +120,8 @@ function buildManagedExtensionTree(options = {}) {
111
120
  const packageRoot = path.resolve(options.packageRoot || path.resolve(__dirname, '../..'));
112
121
  const targetRoot = path.resolve(String(options.targetRoot || ''));
113
122
  const version = String(options.version || readJson(path.join(packageRoot, 'package.json')).version || '');
123
+ const releaseRef = String(options.releaseRef || `v${version}`);
124
+ const releaseChannel = options.releaseChannel || (releaseRef === `v${version}` ? 'stable' : 'prerelease');
114
125
  if (!parseSemver(version)) {
115
126
  throw new Error('Managed extension version is invalid.');
116
127
  }
@@ -133,17 +144,20 @@ function buildManagedExtensionTree(options = {}) {
133
144
  copyPackageTree(packageRoot, 'extension/styles', targetRoot, 'runtime/styles', { allowedFiles });
134
145
  copyPackageTree(packageRoot, 'extension/vendor', targetRoot, 'runtime/vendor', { allowedFiles });
135
146
  copyPackageFile(packageRoot, 'extension/runtime-manifest.json', targetRoot, 'runtime/runtime-manifest.json', allowedFiles);
147
+ rewriteManagedRuntimeAssetPaths(targetRoot);
136
148
  fs.writeFileSync(path.join(targetRoot, EXTENSION_MARKER), JSON.stringify({
137
149
  managedBy: MANAGED_BY,
138
150
  kind: 'extension',
139
- bootstrapProtocol: 1,
151
+ bootstrapProtocol: 2,
140
152
  version,
153
+ releaseRef,
154
+ releaseChannel,
141
155
  installedAt: new Date().toISOString()
142
156
  }, null, 2) + '\n', 'utf8');
143
157
  return { targetRoot, version };
144
158
  }
145
159
 
146
- function installManagedNativeVersion({ packageRoot, nativeRoot, version }) {
160
+ function installManagedNativeVersion({ packageRoot, nativeRoot, version, releaseRef = `v${version}`, releaseChannel = 'stable' }) {
147
161
  const marker = readManagedMarker(nativeRoot, NATIVE_MARKER);
148
162
  if (fs.existsSync(nativeRoot) && !isManagedMarker(marker, 'native')) {
149
163
  throw new Error('Refusing to replace an unmarked managed native root: ' + nativeRoot);
@@ -157,7 +171,11 @@ function installManagedNativeVersion({ packageRoot, nativeRoot, version }) {
157
171
  for (const entry of buildRuntimeFileManifest({ packageRoot })) {
158
172
  copyFile(entry.sourcePath, path.join(stage, entry.relativePath));
159
173
  }
160
- fs.writeFileSync(path.join(stage, '.codex-overleaf-version.json'), JSON.stringify({ version }, null, 2) + '\n');
174
+ fs.writeFileSync(path.join(stage, '.codex-overleaf-version.json'), JSON.stringify({
175
+ version,
176
+ releaseRef,
177
+ releaseChannel
178
+ }, null, 2) + '\n');
161
179
  if (fs.existsSync(target)) {
162
180
  safeRemove(target);
163
181
  }
@@ -175,7 +193,7 @@ function installManagedNativeVersion({ packageRoot, nativeRoot, version }) {
175
193
  fs.writeFileSync(path.join(nativeRoot, NATIVE_MARKER), JSON.stringify({
176
194
  managedBy: MANAGED_BY,
177
195
  kind: 'native',
178
- bootstrapProtocol: 1,
196
+ bootstrapProtocol: 2,
179
197
  version,
180
198
  installedAt: new Date().toISOString()
181
199
  }, null, 2) + '\n', 'utf8');
@@ -330,6 +348,24 @@ function copyFile(source, target) {
330
348
  fs.chmodSync(target, fs.statSync(source).mode & 0o777);
331
349
  }
332
350
 
351
+ function rewriteManagedRuntimeAssetPaths(targetRoot) {
352
+ const cssPath = path.join(targetRoot, 'runtime/vendor/katex/katex.min.css');
353
+ if (!fs.existsSync(cssPath)) {
354
+ return;
355
+ }
356
+ const sourcePrefix = 'chrome-extension://__MSG_@@extension_id__/vendor/katex/fonts/';
357
+ const managedPrefix = 'chrome-extension://__MSG_@@extension_id__/runtime/vendor/katex/fonts/';
358
+ const css = fs.readFileSync(cssPath, 'utf8');
359
+ if (!css.includes(sourcePrefix)) {
360
+ throw new Error('Managed KaTeX CSS does not contain the expected source font prefix.');
361
+ }
362
+ const rewritten = css.split(sourcePrefix).join(managedPrefix);
363
+ if (rewritten.includes(sourcePrefix)) {
364
+ throw new Error('Managed KaTeX CSS retained a source-only font path.');
365
+ }
366
+ fs.writeFileSync(cssPath, rewritten, 'utf8');
367
+ }
368
+
333
369
  function normalizeAllowedFiles(value) {
334
370
  if (!value) return null;
335
371
  return value instanceof Set ? value : new Set(value);
@@ -344,7 +380,9 @@ function readManagedMarker(root, markerName) {
344
380
  }
345
381
 
346
382
  function isManagedMarker(marker, kind) {
347
- return marker?.managedBy === MANAGED_BY && marker?.kind === kind && marker?.bootstrapProtocol === 1;
383
+ return marker?.managedBy === MANAGED_BY &&
384
+ marker?.kind === kind &&
385
+ (marker?.bootstrapProtocol === 1 || marker?.bootstrapProtocol === 2);
348
386
  }
349
387
 
350
388
  function readVersionPointer(nativeRoot, name) {
@@ -407,8 +407,8 @@ function normalizeUpstreamResponseMode(value, allowAuto) {
407
407
 
408
408
  function normalizeMaxOutputTokens(value) {
409
409
  const number = Number(value);
410
- if (!Number.isFinite(number) || number === 8192) return 32768;
411
- return Math.min(200000, Math.max(256, Math.floor(number)));
410
+ if (!Number.isFinite(number) || number === 8192 || number === 32768) return 65536;
411
+ return Math.min(65536, Math.max(256, Math.floor(number)));
412
412
  }
413
413
 
414
414
  function normalizeRequiredText(value, code, message, maxLength) {
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_IDLE_TIMEOUT_MS = 120000;
4
+ const DEFAULT_TOTAL_TIMEOUT_MS = 30 * 60 * 1000;
5
+ const MAX_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
6
+ const MAX_TOTAL_TIMEOUT_MS = 60 * 60 * 1000;
7
+
8
+ function createProviderStreamLifecycle({
9
+ controller,
10
+ idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,
11
+ totalTimeoutMs = DEFAULT_TOTAL_TIMEOUT_MS,
12
+ onAbort = () => {}
13
+ } = {}) {
14
+ if (!controller?.signal || typeof controller.abort !== 'function') {
15
+ throw new TypeError('A provider stream lifecycle requires an AbortController.');
16
+ }
17
+ let idleTimeout = null;
18
+ let totalTimeout = null;
19
+ let abortReason = '';
20
+ let started = false;
21
+ let finished = false;
22
+
23
+ const clearTimers = () => {
24
+ clearTimeout(idleTimeout);
25
+ clearTimeout(totalTimeout);
26
+ idleTimeout = null;
27
+ totalTimeout = null;
28
+ };
29
+ const abort = reason => {
30
+ if (finished || controller.signal.aborted) return;
31
+ abortReason = String(reason || 'cancelled');
32
+ onAbort(abortReason);
33
+ clearTimers();
34
+ controller.abort();
35
+ };
36
+ const touch = () => {
37
+ if (finished || controller.signal.aborted) return;
38
+ clearTimeout(idleTimeout);
39
+ idleTimeout = setTimeout(() => abort('idle_timeout'), idleTimeoutMs);
40
+ idleTimeout.unref?.();
41
+ };
42
+ const start = () => {
43
+ if (started || finished || controller.signal.aborted) return;
44
+ started = true;
45
+ touch();
46
+ totalTimeout = setTimeout(() => abort('total_timeout'), totalTimeoutMs);
47
+ totalTimeout.unref?.();
48
+ };
49
+ const finish = () => {
50
+ if (finished) return;
51
+ finished = true;
52
+ clearTimers();
53
+ };
54
+ const recoverInterrupted = error => {
55
+ if (abortReason === 'idle_timeout' || abortReason === 'total_timeout') return abortReason;
56
+ if (abortReason) return '';
57
+ return isTransportInterruption(error) ? 'transport_interrupted' : '';
58
+ };
59
+
60
+ return {
61
+ abort,
62
+ dispose: finish,
63
+ finish,
64
+ recoverInterrupted,
65
+ start,
66
+ touch,
67
+ get abortReason() { return abortReason; }
68
+ };
69
+ }
70
+
71
+ function resolveProviderIdleTimeoutMs(launch = {}) {
72
+ const configured = Number(launch.requestTimeoutMs);
73
+ if (!Number.isFinite(configured) || configured <= 0) return DEFAULT_IDLE_TIMEOUT_MS;
74
+ return Math.min(MAX_IDLE_TIMEOUT_MS, Math.max(DEFAULT_IDLE_TIMEOUT_MS, Math.floor(configured)));
75
+ }
76
+
77
+ function resolveProviderTotalTimeoutMs(launch = {}, idleTimeoutMs = resolveProviderIdleTimeoutMs(launch)) {
78
+ const configured = Number(launch.totalRequestTimeoutMs);
79
+ const requested = Number.isFinite(configured) && configured > 0
80
+ ? Math.floor(configured)
81
+ : DEFAULT_TOTAL_TIMEOUT_MS;
82
+ return Math.min(MAX_TOTAL_TIMEOUT_MS, Math.max(requested, idleTimeoutMs * 2));
83
+ }
84
+
85
+ function isTransportInterruption(error) {
86
+ const code = String(error?.cause?.code || error?.code || '').toUpperCase();
87
+ if (['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'UND_ERR_SOCKET', 'UND_ERR_CONNECT_TIMEOUT'].includes(code)) return true;
88
+ const message = String(error?.message || '').toLowerCase();
89
+ return error?.name === 'AbortError'
90
+ || message.includes('fetch failed')
91
+ || message.includes('terminated')
92
+ || message.includes('socket')
93
+ || message.includes('connection reset');
94
+ }
95
+
96
+ module.exports = {
97
+ createProviderStreamLifecycle,
98
+ resolveProviderIdleTimeoutMs,
99
+ resolveProviderTotalTimeoutMs
100
+ };