codeharbor 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -68,6 +68,10 @@ CLI_COMPAT_TRANSCRIBE_AUDIO=false
68
68
  CLI_COMPAT_AUDIO_TRANSCRIBE_MODEL=gpt-4o-mini-transcribe
69
69
  CLI_COMPAT_AUDIO_TRANSCRIBE_TIMEOUT_MS=120000
70
70
  CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS=6000
71
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES=1
72
+ CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS=800
73
+ # Skip transcription when audio file is larger than this limit (bytes). Default: 25MB.
74
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES=26214400
71
75
  # Optional local whisper command. Use {input} placeholder for the audio file path.
72
76
  # Example:
73
77
  # CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND=codeharbor-whisper-transcribe --input {input} --model small
package/README.md CHANGED
@@ -468,6 +468,12 @@ To make IM behavior closer to local `codex` CLI interaction, enable:
468
468
  - timeout for each audio transcription request
469
469
  - `CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS`
470
470
  - max transcript length appended to prompt for one attachment
471
+ - `CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES`
472
+ - retry count for local/OpenAI transcription failures (default `1`)
473
+ - `CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS`
474
+ - base retry delay between attempts
475
+ - `CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES`
476
+ - skip transcription when attachment is larger than this size
471
477
  - `CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND`
472
478
  - optional local whisper command template (use `{input}` placeholder for audio file path)
473
479
  - helper command shipped by package: `codeharbor-whisper-transcribe --input {input} --model small`
@@ -519,11 +525,13 @@ When image attachments are present and `CLI_COMPAT_FETCH_MEDIA=true`, CodeHarbor
519
525
  When audio attachments are present and both `CLI_COMPAT_FETCH_MEDIA=true` and `CLI_COMPAT_TRANSCRIBE_AUDIO=true`, CodeHarbor will:
520
526
 
521
527
  1. download `m.audio` media to a temp file
522
- 2. if `CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND` is configured, execute local whisper first
523
- 3. if local whisper fails and `OPENAI_API_KEY` is available, fallback to OpenAI transcription API
524
- 4. append transcript to `[audio_transcripts]` prompt block
525
- 5. continue request even if transcription fails (warn log + no transcript)
526
- 6. best-effort cleanup temp files after the request
528
+ 2. skip oversized audio files based on `CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES`
529
+ 3. if `CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND` is configured, execute local whisper first
530
+ 4. if local whisper fails and `OPENAI_API_KEY` is available, fallback to OpenAI transcription API
531
+ 5. retry transient failures using `CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES`
532
+ 6. append transcript to `[audio_transcripts]` prompt block
533
+ 7. continue request even if transcription fails (warn log + no transcript)
534
+ 8. best-effort cleanup temp files after the request
527
535
 
528
536
  `OPENAI_API_KEY` is optional when local whisper command is configured, and required only for OpenAI fallback.
529
537
  For `codeharbor-whisper-transcribe`, install runtime first: `python3 -m pip install faster-whisper`.
package/dist/cli.js CHANGED
@@ -366,6 +366,18 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
366
366
  <span class="field-label">Audio transcript max chars</span>
367
367
  <input id="global-cli-audio-max-chars" type="number" min="1" />
368
368
  </label>
369
+ <label class="field">
370
+ <span class="field-label">Audio transcribe max retries</span>
371
+ <input id="global-cli-audio-max-retries" type="number" min="0" max="10" />
372
+ </label>
373
+ <label class="field">
374
+ <span class="field-label">Audio transcribe retry delay (ms)</span>
375
+ <input id="global-cli-audio-retry-delay" type="number" min="0" />
376
+ </label>
377
+ <label class="field">
378
+ <span class="field-label">Audio max bytes</span>
379
+ <input id="global-cli-audio-max-bytes" type="number" min="1" />
380
+ </label>
369
381
  <label class="field">
370
382
  <span class="field-label">Local whisper command</span>
371
383
  <input id="global-cli-audio-local-command" type="text" placeholder='python3 /opt/whisper/transcribe.py --input {input}' />
@@ -712,6 +724,11 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
712
724
  document.getElementById("global-cli-audio-model").value = cliCompat.audioTranscribeModel || "gpt-4o-mini-transcribe";
713
725
  document.getElementById("global-cli-audio-timeout").value = String(cliCompat.audioTranscribeTimeoutMs || 120000);
714
726
  document.getElementById("global-cli-audio-max-chars").value = String(cliCompat.audioTranscribeMaxChars || 6000);
727
+ document.getElementById("global-cli-audio-max-retries").value = String(
728
+ typeof cliCompat.audioTranscribeMaxRetries === "number" ? cliCompat.audioTranscribeMaxRetries : 1
729
+ );
730
+ document.getElementById("global-cli-audio-retry-delay").value = String(cliCompat.audioTranscribeRetryDelayMs || 800);
731
+ document.getElementById("global-cli-audio-max-bytes").value = String(cliCompat.audioTranscribeMaxBytes || 26214400);
715
732
  document.getElementById("global-cli-audio-local-command").value = cliCompat.audioLocalWhisperCommand || "";
716
733
  document.getElementById("global-cli-audio-local-timeout").value = String(cliCompat.audioLocalWhisperTimeoutMs || 180000);
717
734
  document.getElementById("global-agent-enabled").checked = Boolean(agentWorkflow.enabled);
@@ -760,6 +777,9 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
760
777
  audioTranscribeModel: asText("global-cli-audio-model") || "gpt-4o-mini-transcribe",
761
778
  audioTranscribeTimeoutMs: asNumber("global-cli-audio-timeout", 120000),
762
779
  audioTranscribeMaxChars: asNumber("global-cli-audio-max-chars", 6000),
780
+ audioTranscribeMaxRetries: asNumber("global-cli-audio-max-retries", 1),
781
+ audioTranscribeRetryDelayMs: asNumber("global-cli-audio-retry-delay", 800),
782
+ audioTranscribeMaxBytes: asNumber("global-cli-audio-max-bytes", 26214400),
763
783
  audioLocalWhisperCommand: asText("global-cli-audio-local-command"),
764
784
  audioLocalWhisperTimeoutMs: asNumber("global-cli-audio-local-timeout", 180000)
765
785
  },
@@ -2037,6 +2057,37 @@ var AdminServer = class {
2037
2057
  envUpdates.CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS = String(value);
2038
2058
  updatedKeys.push("cliCompat.audioTranscribeMaxChars");
2039
2059
  }
2060
+ if ("audioTranscribeMaxRetries" in compat) {
2061
+ const value = normalizePositiveInt(
2062
+ compat.audioTranscribeMaxRetries,
2063
+ this.config.cliCompat.audioTranscribeMaxRetries,
2064
+ 0,
2065
+ 10
2066
+ );
2067
+ this.config.cliCompat.audioTranscribeMaxRetries = value;
2068
+ envUpdates.CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES = String(value);
2069
+ updatedKeys.push("cliCompat.audioTranscribeMaxRetries");
2070
+ }
2071
+ if ("audioTranscribeRetryDelayMs" in compat) {
2072
+ const value = normalizeNonNegativeInt(
2073
+ compat.audioTranscribeRetryDelayMs,
2074
+ this.config.cliCompat.audioTranscribeRetryDelayMs
2075
+ );
2076
+ this.config.cliCompat.audioTranscribeRetryDelayMs = value;
2077
+ envUpdates.CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS = String(value);
2078
+ updatedKeys.push("cliCompat.audioTranscribeRetryDelayMs");
2079
+ }
2080
+ if ("audioTranscribeMaxBytes" in compat) {
2081
+ const value = normalizePositiveInt(
2082
+ compat.audioTranscribeMaxBytes,
2083
+ this.config.cliCompat.audioTranscribeMaxBytes,
2084
+ 1,
2085
+ Number.MAX_SAFE_INTEGER
2086
+ );
2087
+ this.config.cliCompat.audioTranscribeMaxBytes = value;
2088
+ envUpdates.CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES = String(value);
2089
+ updatedKeys.push("cliCompat.audioTranscribeMaxBytes");
2090
+ }
2040
2091
  if ("audioLocalWhisperCommand" in compat) {
2041
2092
  const value = normalizeString(
2042
2093
  compat.audioLocalWhisperCommand,
@@ -3753,12 +3804,15 @@ var import_promises3 = __toESM(require("fs/promises"));
3753
3804
  var import_node_path8 = __toESM(require("path"));
3754
3805
  var import_node_util3 = require("util");
3755
3806
  var execAsync = (0, import_node_util3.promisify)(import_node_child_process5.exec);
3807
+ var RETRYABLE_OPENAI_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
3756
3808
  var AudioTranscriber = class {
3757
3809
  enabled;
3758
3810
  apiKey;
3759
3811
  model;
3760
3812
  timeoutMs;
3761
3813
  maxChars;
3814
+ maxRetries;
3815
+ retryDelayMs;
3762
3816
  localWhisperCommand;
3763
3817
  localWhisperTimeoutMs;
3764
3818
  constructor(options) {
@@ -3767,6 +3821,8 @@ var AudioTranscriber = class {
3767
3821
  this.model = options.model;
3768
3822
  this.timeoutMs = options.timeoutMs;
3769
3823
  this.maxChars = options.maxChars;
3824
+ this.maxRetries = options.maxRetries;
3825
+ this.retryDelayMs = options.retryDelayMs;
3770
3826
  this.localWhisperCommand = options.localWhisperCommand;
3771
3827
  this.localWhisperTimeoutMs = options.localWhisperTimeoutMs;
3772
3828
  }
@@ -3808,7 +3864,7 @@ var AudioTranscriber = class {
3808
3864
  let localError = null;
3809
3865
  if (hasLocalWhisper) {
3810
3866
  try {
3811
- const localText = await this.transcribeOneWithLocalWhisper(attachment);
3867
+ const localText = await this.transcribeOneWithLocalWhisperWithRetry(attachment);
3812
3868
  if (localText) {
3813
3869
  return localText;
3814
3870
  }
@@ -3818,7 +3874,7 @@ var AudioTranscriber = class {
3818
3874
  }
3819
3875
  if (hasOpenAi) {
3820
3876
  try {
3821
- return await this.transcribeOneWithOpenAi(attachment);
3877
+ return await this.transcribeOneWithOpenAiWithRetry(attachment);
3822
3878
  } catch (error) {
3823
3879
  if (!localError) {
3824
3880
  throw error;
@@ -3834,6 +3890,34 @@ var AudioTranscriber = class {
3834
3890
  }
3835
3891
  return "";
3836
3892
  }
3893
+ async transcribeOneWithOpenAiWithRetry(attachment) {
3894
+ let attempt = 0;
3895
+ while (true) {
3896
+ try {
3897
+ return await this.transcribeOneWithOpenAi(attachment);
3898
+ } catch (error) {
3899
+ if (!isRetryableOpenAiError(error) || attempt >= this.maxRetries) {
3900
+ throw error;
3901
+ }
3902
+ attempt += 1;
3903
+ await sleep2(this.retryDelayMs * attempt);
3904
+ }
3905
+ }
3906
+ }
3907
+ async transcribeOneWithLocalWhisperWithRetry(attachment) {
3908
+ let attempt = 0;
3909
+ while (true) {
3910
+ try {
3911
+ return await this.transcribeOneWithLocalWhisper(attachment);
3912
+ } catch (error) {
3913
+ if (attempt >= this.maxRetries) {
3914
+ throw error;
3915
+ }
3916
+ attempt += 1;
3917
+ await sleep2(this.retryDelayMs * attempt);
3918
+ }
3919
+ }
3920
+ }
3837
3921
  async transcribeOneWithOpenAi(attachment) {
3838
3922
  if (!this.apiKey) {
3839
3923
  return "";
@@ -3866,7 +3950,7 @@ var AudioTranscriber = class {
3866
3950
  const payload = await response.json().catch(() => ({}));
3867
3951
  if (!response.ok) {
3868
3952
  const message = typeof payload?.error?.message === "string" ? payload.error.message : `HTTP ${response.status} ${response.statusText}`;
3869
- throw new Error(`Audio transcription failed for ${attachment.name}: ${message}`);
3953
+ throw new OpenAiTranscriptionHttpError(response.status, `Audio transcription failed for ${attachment.name}: ${message}`);
3870
3954
  }
3871
3955
  const text = typeof payload.text === "string" ? payload.text.trim() : "";
3872
3956
  return this.normalizeTranscriptText(text);
@@ -3911,12 +3995,38 @@ function buildLocalWhisperCommand(template, inputPath) {
3911
3995
  function shellEscape(value) {
3912
3996
  return `'${value.replace(/'/g, `'"'"'`)}'`;
3913
3997
  }
3998
+ function isRetryableOpenAiError(error) {
3999
+ if (error instanceof OpenAiTranscriptionHttpError) {
4000
+ return RETRYABLE_OPENAI_STATUS.has(error.status);
4001
+ }
4002
+ if (error instanceof Error && error.name === "AbortError") {
4003
+ return true;
4004
+ }
4005
+ return true;
4006
+ }
4007
+ async function sleep2(delayMs) {
4008
+ if (delayMs <= 0) {
4009
+ return;
4010
+ }
4011
+ await new Promise((resolve) => {
4012
+ const timer = setTimeout(resolve, delayMs);
4013
+ timer.unref?.();
4014
+ });
4015
+ }
3914
4016
  function formatError3(error) {
3915
4017
  if (error instanceof Error) {
3916
4018
  return error.message;
3917
4019
  }
3918
4020
  return String(error);
3919
4021
  }
4022
+ var OpenAiTranscriptionHttpError = class extends Error {
4023
+ status;
4024
+ constructor(status, message) {
4025
+ super(message);
4026
+ this.name = "OpenAiTranscriptionHttpError";
4027
+ this.status = status;
4028
+ }
4029
+ };
3920
4030
 
3921
4031
  // src/compat/cli-compat-recorder.ts
3922
4032
  var import_node_fs6 = __toESM(require("fs"));
@@ -4814,6 +4924,9 @@ var Orchestrator = class {
4814
4924
  audioTranscribeModel: "gpt-4o-mini-transcribe",
4815
4925
  audioTranscribeTimeoutMs: 12e4,
4816
4926
  audioTranscribeMaxChars: 6e3,
4927
+ audioTranscribeMaxRetries: 1,
4928
+ audioTranscribeRetryDelayMs: 800,
4929
+ audioTranscribeMaxBytes: 26214400,
4817
4930
  audioLocalWhisperCommand: null,
4818
4931
  audioLocalWhisperTimeoutMs: 18e4,
4819
4932
  recordPath: null
@@ -4825,6 +4938,8 @@ var Orchestrator = class {
4825
4938
  model: this.cliCompat.audioTranscribeModel,
4826
4939
  timeoutMs: this.cliCompat.audioTranscribeTimeoutMs,
4827
4940
  maxChars: this.cliCompat.audioTranscribeMaxChars,
4941
+ maxRetries: this.cliCompat.audioTranscribeMaxRetries,
4942
+ retryDelayMs: this.cliCompat.audioTranscribeRetryDelayMs,
4828
4943
  localWhisperCommand: this.cliCompat.audioLocalWhisperCommand,
4829
4944
  localWhisperTimeoutMs: this.cliCompat.audioLocalWhisperTimeoutMs
4830
4945
  });
@@ -5661,35 +5776,73 @@ var Orchestrator = class {
5661
5776
  if (!this.audioTranscriber.isEnabled()) {
5662
5777
  return [];
5663
5778
  }
5664
- const audioAttachments = message.attachments.filter((attachment) => attachment.kind === "audio" && Boolean(attachment.localPath)).map((attachment) => ({
5665
- name: attachment.name,
5666
- mimeType: attachment.mimeType,
5667
- localPath: attachment.localPath
5668
- }));
5669
- if (audioAttachments.length === 0) {
5779
+ const rawAudioAttachments = message.attachments.filter(
5780
+ (attachment) => attachment.kind === "audio" && Boolean(attachment.localPath)
5781
+ );
5782
+ if (rawAudioAttachments.length === 0) {
5670
5783
  return [];
5671
5784
  }
5672
- try {
5673
- const transcripts = await this.audioTranscriber.transcribeMany(audioAttachments);
5674
- if (transcripts.length > 0) {
5675
- this.logger.info("Audio transcription completed", {
5785
+ const maxBytes = this.cliCompat.audioTranscribeMaxBytes;
5786
+ const audioAttachments = [];
5787
+ let skippedTooLarge = 0;
5788
+ for (const attachment of rawAudioAttachments) {
5789
+ const localPath = attachment.localPath;
5790
+ const sizeBytes = await this.resolveAudioAttachmentSizeBytes(attachment.sizeBytes, localPath);
5791
+ if (sizeBytes !== null && sizeBytes > maxBytes) {
5792
+ skippedTooLarge += 1;
5793
+ this.logger.warn("Skip audio transcription for oversized attachment", {
5676
5794
  requestId,
5677
5795
  sessionKey,
5678
- attachmentCount: audioAttachments.length,
5679
- transcriptCount: transcripts.length
5796
+ name: attachment.name,
5797
+ sizeBytes,
5798
+ maxBytes
5680
5799
  });
5800
+ continue;
5681
5801
  }
5802
+ audioAttachments.push({
5803
+ name: attachment.name,
5804
+ mimeType: attachment.mimeType,
5805
+ localPath
5806
+ });
5807
+ }
5808
+ if (audioAttachments.length === 0) {
5809
+ return [];
5810
+ }
5811
+ const startedAt = Date.now();
5812
+ try {
5813
+ const transcripts = await this.audioTranscriber.transcribeMany(audioAttachments);
5814
+ this.logger.info("Audio transcription completed", {
5815
+ requestId,
5816
+ sessionKey,
5817
+ attachmentCount: audioAttachments.length,
5818
+ transcriptCount: transcripts.length,
5819
+ skippedTooLarge,
5820
+ durationMs: Date.now() - startedAt
5821
+ });
5682
5822
  return transcripts;
5683
5823
  } catch (error) {
5684
5824
  this.logger.warn("Audio transcription failed, continuing without transcripts", {
5685
5825
  requestId,
5686
5826
  sessionKey,
5687
5827
  attachmentCount: audioAttachments.length,
5828
+ skippedTooLarge,
5829
+ durationMs: Date.now() - startedAt,
5688
5830
  error: formatError4(error)
5689
5831
  });
5690
5832
  return [];
5691
5833
  }
5692
5834
  }
5835
+ async resolveAudioAttachmentSizeBytes(sizeBytes, localPath) {
5836
+ if (sizeBytes !== null) {
5837
+ return sizeBytes;
5838
+ }
5839
+ try {
5840
+ const stats = await import_promises5.default.stat(localPath);
5841
+ return stats.size;
5842
+ } catch {
5843
+ return null;
5844
+ }
5845
+ }
5693
5846
  buildExecutionPrompt(prompt, message, audioTranscripts) {
5694
5847
  if (message.attachments.length === 0 && audioTranscripts.length === 0) {
5695
5848
  return prompt;
@@ -6590,6 +6743,9 @@ var configSchema = import_zod.z.object({
6590
6743
  CLI_COMPAT_AUDIO_TRANSCRIBE_MODEL: import_zod.z.string().default("gpt-4o-mini-transcribe"),
6591
6744
  CLI_COMPAT_AUDIO_TRANSCRIBE_TIMEOUT_MS: import_zod.z.string().default("120000").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().positive()),
6592
6745
  CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS: import_zod.z.string().default("6000").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().positive()),
6746
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES: import_zod.z.string().default("1").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().min(0).max(10)),
6747
+ CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS: import_zod.z.string().default("800").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().nonnegative()),
6748
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES: import_zod.z.string().default("26214400").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().positive()),
6593
6749
  CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND: import_zod.z.string().default(""),
6594
6750
  CLI_COMPAT_AUDIO_LOCAL_WHISPER_TIMEOUT_MS: import_zod.z.string().default("180000").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().positive()),
6595
6751
  CLI_COMPAT_RECORD_PATH: import_zod.z.string().default(""),
@@ -6656,6 +6812,9 @@ var configSchema = import_zod.z.object({
6656
6812
  audioTranscribeModel: v.CLI_COMPAT_AUDIO_TRANSCRIBE_MODEL.trim() || "gpt-4o-mini-transcribe",
6657
6813
  audioTranscribeTimeoutMs: v.CLI_COMPAT_AUDIO_TRANSCRIBE_TIMEOUT_MS,
6658
6814
  audioTranscribeMaxChars: v.CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS,
6815
+ audioTranscribeMaxRetries: v.CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES,
6816
+ audioTranscribeRetryDelayMs: v.CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS,
6817
+ audioTranscribeMaxBytes: v.CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES,
6659
6818
  audioLocalWhisperCommand: v.CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND.trim() ? v.CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND.trim() : null,
6660
6819
  audioLocalWhisperTimeoutMs: v.CLI_COMPAT_AUDIO_LOCAL_WHISPER_TIMEOUT_MS,
6661
6820
  recordPath: v.CLI_COMPAT_RECORD_PATH.trim() ? import_node_path12.default.resolve(v.CLI_COMPAT_RECORD_PATH) : null
@@ -6903,6 +7062,9 @@ var CONFIG_SNAPSHOT_ENV_KEYS = [
6903
7062
  "CLI_COMPAT_AUDIO_TRANSCRIBE_MODEL",
6904
7063
  "CLI_COMPAT_AUDIO_TRANSCRIBE_TIMEOUT_MS",
6905
7064
  "CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS",
7065
+ "CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES",
7066
+ "CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS",
7067
+ "CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES",
6906
7068
  "CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND",
6907
7069
  "CLI_COMPAT_AUDIO_LOCAL_WHISPER_TIMEOUT_MS",
6908
7070
  "CLI_COMPAT_RECORD_PATH",
@@ -6979,6 +7141,16 @@ var envSnapshotSchema = import_zod2.z.object({
6979
7141
  CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS: integerStringSchema("CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS", 1).default(
6980
7142
  "6000"
6981
7143
  ),
7144
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES: integerStringSchema("CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES", 0, 10).default(
7145
+ "1"
7146
+ ),
7147
+ CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS: integerStringSchema(
7148
+ "CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS",
7149
+ 0
7150
+ ).default("800"),
7151
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES: integerStringSchema("CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES", 1).default(
7152
+ "26214400"
7153
+ ),
6982
7154
  CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND: import_zod2.z.string().default(""),
6983
7155
  CLI_COMPAT_AUDIO_LOCAL_WHISPER_TIMEOUT_MS: integerStringSchema(
6984
7156
  "CLI_COMPAT_AUDIO_LOCAL_WHISPER_TIMEOUT_MS",
@@ -7178,6 +7350,9 @@ function buildSnapshotEnv(config) {
7178
7350
  CLI_COMPAT_AUDIO_TRANSCRIBE_MODEL: config.cliCompat.audioTranscribeModel,
7179
7351
  CLI_COMPAT_AUDIO_TRANSCRIBE_TIMEOUT_MS: String(config.cliCompat.audioTranscribeTimeoutMs),
7180
7352
  CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_CHARS: String(config.cliCompat.audioTranscribeMaxChars),
7353
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_RETRIES: String(config.cliCompat.audioTranscribeMaxRetries),
7354
+ CLI_COMPAT_AUDIO_TRANSCRIBE_RETRY_DELAY_MS: String(config.cliCompat.audioTranscribeRetryDelayMs),
7355
+ CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES: String(config.cliCompat.audioTranscribeMaxBytes),
7181
7356
  CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND: config.cliCompat.audioLocalWhisperCommand ?? "",
7182
7357
  CLI_COMPAT_AUDIO_LOCAL_WHISPER_TIMEOUT_MS: String(config.cliCompat.audioLocalWhisperTimeoutMs),
7183
7358
  CLI_COMPAT_RECORD_PATH: config.cliCompat.recordPath ?? "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeharbor",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Instant-messaging bridge for Codex CLI sessions",
5
5
  "license": "MIT",
6
6
  "main": "dist/cli.js",