atlarix 15.2.0 → 15.4.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.
@@ -81,11 +81,19 @@ function getRegisteredHandler(channel) {
81
81
  return handlers.get(channel);
82
82
  }
83
83
  function attachCliWindow(send) {
84
+ cliSenders.add(send);
85
+ const detach = () => {
86
+ cliSenders.delete(send);
87
+ if (cliSenders.size === 0) cliWindow = null;
88
+ };
89
+ if (cliWindow) return detach;
84
90
  const webContents = {
85
91
  id: 1,
86
92
  isDestroyed: () => false,
87
93
  isCrashed: () => false,
88
- send
94
+ send: (channel, payload) => {
95
+ for (const s3 of [...cliSenders]) s3(channel, payload);
96
+ }
89
97
  };
90
98
  cliWindow = {
91
99
  id: 1,
@@ -97,11 +105,12 @@ function attachCliWindow(send) {
97
105
  focus: noop,
98
106
  show: noop
99
107
  };
108
+ return detach;
100
109
  }
101
110
  function setSafeStorageImpl(impl) {
102
111
  safeStorageImpl = impl;
103
112
  }
104
- var handlers, ipcMain, userDataBase, noop, app, BrowserWindow, cliWindow, safeStorageImpl, safeStorage, dialog, shell, clipboard, nativeTheme, powerMonitor, powerSaveBlocker, session, Menu, Notification, contextBridge, ipcRenderer, webFrame, WebContentsView, electron_stub_default;
113
+ var handlers, ipcMain, userDataBase, noop, app, BrowserWindow, cliWindow, cliSenders, safeStorageImpl, safeStorage, dialog, shell, clipboard, nativeTheme, powerMonitor, powerSaveBlocker, session, Menu, Notification, contextBridge, ipcRenderer, webFrame, WebContentsView, electron_stub_default;
105
114
  var init_electron_stub = __esm({
106
115
  "src/headless/electron-stub.ts"() {
107
116
  handlers = /* @__PURE__ */ new Map();
@@ -140,6 +149,7 @@ var init_electron_stub = __esm({
140
149
  fromId: () => null
141
150
  };
142
151
  cliWindow = null;
152
+ cliSenders = /* @__PURE__ */ new Set();
143
153
  safeStorageImpl = {
144
154
  isEncryptionAvailable: () => false,
145
155
  encryptString: (s3) => Buffer.from(s3, "utf8"),
@@ -27345,6 +27355,8 @@ var init_schemas3 = __esm({
27345
27355
  voiceTranscriptionProvider: external_exports.string().optional(),
27346
27356
  /** Messages from the user's other Atlarix sessions: delivered, or refused. Unset = accept. */
27347
27357
  peerMessages: external_exports.enum(["accept", "refuse"]).optional(),
27358
+ /** A one-line recap when the user comes back to a session after a few minutes away. Unset = on. */
27359
+ sessionRecap: external_exports.boolean().optional(),
27348
27360
  // `voiceInputMode` REMOVED. Its "live" value streamed a re-transcription of a
27349
27361
  // growing buffer every 1.5s, which was only viable against the local WebGPU
27350
27362
  // pipeline; against a metered API it would bill every 1.5s for the same audio.
@@ -67729,7 +67741,7 @@ var require_cross_spawn = __commonJS({
67729
67741
  var cp = __require("child_process");
67730
67742
  var parse4 = require_parse();
67731
67743
  var enoent = require_enoent();
67732
- function spawn5(command, args2, options) {
67744
+ function spawn6(command, args2, options) {
67733
67745
  const parsed = parse4(command, args2, options);
67734
67746
  const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
67735
67747
  enoent.hookChildProcess(spawned, parsed);
@@ -67741,8 +67753,8 @@ var require_cross_spawn = __commonJS({
67741
67753
  result2.error = result2.error || enoent.verifyENOENTSync(result2.status, parsed);
67742
67754
  return result2;
67743
67755
  }
67744
- module2.exports = spawn5;
67745
- module2.exports.spawn = spawn5;
67756
+ module2.exports = spawn6;
67757
+ module2.exports.spawn = spawn6;
67746
67758
  module2.exports.sync = spawnSync3;
67747
67759
  module2.exports._parse = parse4;
67748
67760
  module2.exports._enoent = enoent;
@@ -147390,12 +147402,12 @@ var require_oauth2client = __commonJS({
147390
147402
  throw new Error("Expiration time too far in future: " + JSON.stringify(payload));
147391
147403
  }
147392
147404
  const earliest = iat - _OAuth2Client.CLOCK_SKEW_SECS_;
147393
- const latest = exp + _OAuth2Client.CLOCK_SKEW_SECS_;
147405
+ const latest2 = exp + _OAuth2Client.CLOCK_SKEW_SECS_;
147394
147406
  if (now2 < earliest) {
147395
147407
  throw new Error("Token used too early, " + now2 + " < " + earliest + ": " + JSON.stringify(payload));
147396
147408
  }
147397
- if (now2 > latest) {
147398
- throw new Error("Token used too late, " + now2 + " > " + latest + ": " + JSON.stringify(payload));
147409
+ if (now2 > latest2) {
147410
+ throw new Error("Token used too late, " + now2 + " > " + latest2 + ": " + JSON.stringify(payload));
147399
147411
  }
147400
147412
  if (issuers && issuers.indexOf(payload.iss) < 0) {
147401
147413
  throw new Error("Invalid issuer, expected one of [" + issuers + "], but got " + payload.iss);
@@ -192238,6 +192250,7 @@ function autoStatusFromHeaders(headers) {
192238
192250
  function broadcastAutoStatus(headers) {
192239
192251
  const status = autoStatusFromHeaders(headers);
192240
192252
  if (!status) return;
192253
+ latest = status;
192241
192254
  void noteAutoBilling(status.billing);
192242
192255
  import_electron_log17.default.scope("atlarix-auto").info(
192243
192256
  `Auto reply: billing=${status.billing ?? "?"} model=${status.servedModel ?? "?"}`
@@ -192246,13 +192259,14 @@ function broadcastAutoStatus(headers) {
192246
192259
  if (!win.isDestroyed()) safeSend(win.webContents, "core:auto-status", status);
192247
192260
  }
192248
192261
  }
192249
- var import_electron_log17;
192262
+ var import_electron_log17, latest;
192250
192263
  var init_auto_status = __esm({
192251
192264
  "src/main/auto-status.ts"() {
192252
192265
  init_electron_stub();
192253
192266
  import_electron_log17 = __toESM(require_src());
192254
192267
  init_safe_sender();
192255
192268
  init_auto_context_window();
192269
+ latest = null;
192256
192270
  }
192257
192271
  });
192258
192272
 
@@ -212134,7 +212148,7 @@ function getPostHog() {
212134
212148
  if (client) return client;
212135
212149
  const apiKey = process.env.POSTHOG_API_KEY;
212136
212150
  if (!apiKey) return null;
212137
- client = new PostHog(apiKey, { host: POSTHOG_HOST });
212151
+ client = new PostHog(apiKey, { host: POSTHOG_HOST, disableGeoip: false });
212138
212152
  return client;
212139
212153
  }
212140
212154
  function resolveDistinctId() {
@@ -237597,7 +237611,7 @@ function isOnlyToolDebris(content) {
237597
237611
  stripToolJsonLinesFromMarkdown(stripOrphanToolTagFragments(content))
237598
237612
  ).trim();
237599
237613
  if (!stripped) return true;
237600
- if (/^[\s\]},":\d._-]+$/.test(stripped)) return true;
237614
+ if (/^[\s\]},":\d._-]+$/.test(stripped) && /[\]}"]/.test(stripped)) return true;
237601
237615
  if (/^"?,?\s*"message"\s*:/.test(stripped)) return true;
237602
237616
  if (/^\{\s*"success"\s*:/.test(stripped)) return true;
237603
237617
  const lines = stripped.split("\n").filter((l3) => l3.trim());
@@ -238276,7 +238290,7 @@ var init_framing = __esm({
238276
238290
 
238277
238291
  // src/lib/peers/peer-message.ts
238278
238292
  function peerSurfaceLabel(surface2) {
238279
- return surface2 === "cli" ? "CLI" : surface2 === "chrome" ? "browser" : "app";
238293
+ return surface2 === "cli" ? "CLI" : surface2 === "chrome" ? "browser" : surface2 === "editor" ? "editor" : "app";
238280
238294
  }
238281
238295
  var init_peer_message = __esm({
238282
238296
  "src/lib/peers/peer-message.ts"() {
@@ -238373,7 +238387,7 @@ function publish() {
238373
238387
  host,
238374
238388
  peerSocketPath(),
238375
238389
  token,
238376
- surface === "app" ? "Atlarix app" : "Atlarix CLI",
238390
+ surface === "app" ? "Atlarix app" : surface === "editor" ? "Atlarix in an editor" : "Atlarix CLI",
238377
238391
  "",
238378
238392
  surface,
238379
238393
  0,
@@ -238409,6 +238423,11 @@ function noteLiveSession(s3) {
238409
238423
  local.set(s3.sessionId, s3);
238410
238424
  if (!prev || prev.name !== s3.name || prev.chatId !== s3.chatId) publish();
238411
238425
  }
238426
+ function sessionsHeldElsewhere() {
238427
+ return new Set(
238428
+ liveRows().filter((r3) => r3.pid !== process.pid && !r3.session_id.startsWith(PROCESS_ROW_PREFIX)).map((r3) => r3.session_id)
238429
+ );
238430
+ }
238412
238431
  function forgetLiveSession(sessionId) {
238413
238432
  if (local.delete(sessionId)) publish();
238414
238433
  }
@@ -238454,7 +238473,7 @@ function listPeerSessions(exclude) {
238454
238473
  sessionId: r3.session_id,
238455
238474
  name: r3.name,
238456
238475
  workspace: r3.workspace,
238457
- surface: r3.surface === "cli" ? "cli" : r3.surface === "chrome" ? "chrome" : "app",
238476
+ surface: r3.surface === "cli" || r3.surface === "chrome" || r3.surface === "editor" ? r3.surface : "app",
238458
238477
  busy: r3.busy === 1,
238459
238478
  pid: r3.pid
238460
238479
  });
@@ -238735,7 +238754,7 @@ async function noteSessionOpen(sessionId, chatId) {
238735
238754
  noteLiveSession({
238736
238755
  sessionId,
238737
238756
  chatId,
238738
- name: (session2.title?.trim() && !isPlaceholderTitle(session2.title) ? session2.title.trim() : "") || `New ${process.env.ATLARIX_SURFACE === "cli" ? "CLI " : ""}session (${(app2?.path ?? "").split(/[\\/]/).pop() || "no folder"})`,
238757
+ name: (session2.title?.trim() && !isPlaceholderTitle(session2.title) ? session2.title.trim() : "") || `New ${process.env.ATLARIX_PEER_SURFACE === "editor" ? "editor " : process.env.ATLARIX_SURFACE === "cli" ? "CLI " : ""}session (${(app2?.path ?? "").split(/[\\/]/).pop() || "no folder"})`,
238739
238758
  workspace: app2?.path ?? ""
238740
238759
  });
238741
238760
  } catch (e3) {
@@ -238880,6 +238899,65 @@ var init_continue_in_terminal = __esm({
238880
238899
  }
238881
238900
  });
238882
238901
 
238902
+ // src/lib/peers/open-in-editor.ts
238903
+ var open_in_editor_exports = {};
238904
+ __export(open_in_editor_exports, {
238905
+ EDITORS: () => EDITORS,
238906
+ editorLink: () => editorLink,
238907
+ guessEditor: () => guessEditor,
238908
+ openCommand: () => openCommand,
238909
+ openInEditor: () => openInEditor
238910
+ });
238911
+ import { spawn as spawn3 } from "node:child_process";
238912
+ function editorLink(scheme, sessionId, folder) {
238913
+ const q3 = new URLSearchParams({ session: sessionId, folder });
238914
+ return `${scheme}://${EXTENSION}/open?${q3.toString()}`;
238915
+ }
238916
+ function guessEditor(env3 = process.env) {
238917
+ const hint = `${env3.VSCODE_GIT_ASKPASS_NODE ?? ""} ${env3.__CFBundleIdentifier ?? ""} ${env3.TERM_PROGRAM ?? ""}`.toLowerCase();
238918
+ for (const key of ["cursor", "kiro", "windsurf", "vscodium", "insiders"]) {
238919
+ if (hint.includes(key)) return key;
238920
+ }
238921
+ return "vscode";
238922
+ }
238923
+ function openCommand(platform, url2) {
238924
+ if (platform === "darwin") return { file: "open", args: [url2] };
238925
+ if (platform === "win32") return { file: "cmd.exe", args: ["/c", "start", "", url2.replace(/&/g, "^&")] };
238926
+ return { file: "xdg-open", args: [url2] };
238927
+ }
238928
+ async function openInEditor(sessionId, folder, editor) {
238929
+ const key = (editor || guessEditor()).toLowerCase();
238930
+ const target = EDITORS[key];
238931
+ if (!target) {
238932
+ return { ok: false, editor: key, error: `Unknown editor "${editor}". Try: ${Object.keys(EDITORS).filter((k2) => k2 !== "code").join(", ")}.` };
238933
+ }
238934
+ if (!/^[\w-]+$/.test(sessionId)) return { ok: false, editor: target.name, error: "This session has no id yet." };
238935
+ const { file: file2, args: args2 } = openCommand(process.platform, editorLink(target.scheme, sessionId, folder));
238936
+ return new Promise((resolve14) => {
238937
+ const child = spawn3(file2, args2, { detached: true, stdio: "ignore", windowsHide: true });
238938
+ child.once("error", (e3) => resolve14({ ok: false, editor: target.name, error: `Couldn't open ${target.name}: ${e3.message}` }));
238939
+ child.once("spawn", () => {
238940
+ child.unref();
238941
+ resolve14({ ok: true, editor: target.name });
238942
+ });
238943
+ });
238944
+ }
238945
+ var EDITORS, EXTENSION;
238946
+ var init_open_in_editor = __esm({
238947
+ "src/lib/peers/open-in-editor.ts"() {
238948
+ EDITORS = {
238949
+ vscode: { scheme: "vscode", name: "VS Code" },
238950
+ code: { scheme: "vscode", name: "VS Code" },
238951
+ insiders: { scheme: "vscode-insiders", name: "VS Code Insiders" },
238952
+ cursor: { scheme: "cursor", name: "Cursor" },
238953
+ kiro: { scheme: "kiro", name: "Kiro" },
238954
+ windsurf: { scheme: "windsurf", name: "Windsurf" },
238955
+ vscodium: { scheme: "vscodium", name: "VSCodium" }
238956
+ };
238957
+ EXTENSION = "atlarix.atlarix";
238958
+ }
238959
+ });
238960
+
238883
238961
  // src/ipc/handlers/workspace_session_handlers.ts
238884
238962
  var workspace_session_handlers_exports = {};
238885
238963
  __export(workspace_session_handlers_exports, {
@@ -239054,18 +239132,18 @@ async function resolveOrBindWorkspaceSessionForStream(input) {
239054
239132
  "workspaceSessionId is required for workspace chat streams"
239055
239133
  );
239056
239134
  }
239057
- const latest = await db.query.chatSessions.findFirst({
239135
+ const latest2 = await db.query.chatSessions.findFirst({
239058
239136
  where: eq(chatSessions.workspaceId, appId),
239059
239137
  orderBy: [desc(chatSessions.updatedAt)]
239060
239138
  });
239061
- if (latest) {
239062
- const stored = latest.streamChatId;
239139
+ if (latest2) {
239140
+ const stored = latest2.streamChatId;
239063
239141
  if (stored == null || isStreamTransportId(stored) && !getStreamTransport(stored)) {
239064
- await persistSessionStreamChatId(latest.id, input.chatId);
239142
+ await persistSessionStreamChatId(latest2.id, input.chatId);
239065
239143
  logger30.warn(
239066
- `Bound transport chat ${input.chatId} to session ${latest.id} (workspace ${appId})`
239144
+ `Bound transport chat ${input.chatId} to session ${latest2.id} (workspace ${appId})`
239067
239145
  );
239068
- return latest.id;
239146
+ return latest2.id;
239069
239147
  }
239070
239148
  }
239071
239149
  const session2 = await insertWorkspaceSession(appId);
@@ -239160,10 +239238,13 @@ async function verifyTrulyEmptySession(sessionId, purpose) {
239160
239238
  }
239161
239239
  return { row, empty: true, reason: "empty" };
239162
239240
  }
239163
- async function startWorkspaceSession(workspaceId) {
239241
+ async function startWorkspaceSession(workspaceId, opts = {}) {
239164
239242
  let session2 = null;
239165
239243
  try {
239166
- const rows = await listSessionsWithMessageCounts(workspaceId);
239244
+ const held = sessionsHeldElsewhere();
239245
+ const rows = (await listSessionsWithMessageCounts(workspaceId)).filter(
239246
+ (r3) => !held.has(r3.id) && !claimedSessions.has(r3.id)
239247
+ );
239167
239248
  const { reuseId, pruneIds } = pickSessionReuse(rows);
239168
239249
  if (rows.length > 0) {
239169
239250
  logger30.log(
@@ -239207,6 +239288,7 @@ async function startWorkspaceSession(workspaceId) {
239207
239288
  if (!session2) {
239208
239289
  session2 = await insertWorkspaceSession(workspaceId);
239209
239290
  }
239291
+ if (opts.claim) claimedSessions.add(session2.id);
239210
239292
  const chatId = createStreamTransport(workspaceId);
239211
239293
  await persistSessionStreamChatId(session2.id, chatId);
239212
239294
  return { sessionId: session2.id, chatId, session: session2 };
@@ -239345,9 +239427,9 @@ function registerWorkspaceSessionHandlers() {
239345
239427
  return out;
239346
239428
  }
239347
239429
  );
239348
- handle("sessions:start", async (_event, workspaceId) => {
239430
+ handle("sessions:start", async (_event, workspaceId, opts) => {
239349
239431
  await requireAuthSession();
239350
- const started = await startWorkspaceSession(workspaceId);
239432
+ const started = await startWorkspaceSession(workspaceId, { claim: opts?.claim === true });
239351
239433
  logger30.info(
239352
239434
  `sessions:start workspace=${workspaceId} session=${started.sessionId} transport=${started.chatId}`
239353
239435
  );
@@ -239378,6 +239460,15 @@ function registerWorkspaceSessionHandlers() {
239378
239460
  const { continueInTerminal: continueInTerminal2 } = await Promise.resolve().then(() => (init_continue_in_terminal(), continue_in_terminal_exports));
239379
239461
  return continueInTerminal2(sessionId, workspace?.path ?? "");
239380
239462
  });
239463
+ handle("sessions:open-in-editor", async (_event, sessionId, editor) => {
239464
+ const session2 = await db.query.chatSessions.findFirst({
239465
+ where: eq(chatSessions.id, sessionId)
239466
+ });
239467
+ if (!session2) return { ok: false, error: "Session not found" };
239468
+ const workspace = session2.workspaceId ? await db.query.apps.findFirst({ where: eq(apps.id, session2.workspaceId) }) : void 0;
239469
+ const { openInEditor: openInEditor2 } = await Promise.resolve().then(() => (init_open_in_editor(), open_in_editor_exports));
239470
+ return openInEditor2(sessionId, workspace?.path ?? "", editor);
239471
+ });
239381
239472
  handle("sessions:create", async (_event, workspaceId) => {
239382
239473
  await requireAuthSession();
239383
239474
  return insertWorkspaceSession(workspaceId);
@@ -239525,7 +239616,7 @@ function registerWorkspaceSessionHandlers() {
239525
239616
  return { title: currentTitle };
239526
239617
  });
239527
239618
  }
239528
- var import_electron_log32, DEFAULT_SESSION_TITLES, logger30, handle;
239619
+ var import_electron_log32, DEFAULT_SESSION_TITLES, logger30, handle, claimedSessions;
239529
239620
  var init_workspace_session_handlers = __esm({
239530
239621
  "src/ipc/handlers/workspace_session_handlers.ts"() {
239531
239622
  import_electron_log32 = __toESM(require_src());
@@ -239549,6 +239640,7 @@ var init_workspace_session_handlers = __esm({
239549
239640
  DEFAULT_SESSION_TITLES = /* @__PURE__ */ new Set(["Untitled", "New session", "New chat"]);
239550
239641
  logger30 = import_electron_log32.default.scope("workspace_session_handlers");
239551
239642
  handle = createLoggedHandler(logger30);
239643
+ claimedSessions = /* @__PURE__ */ new Set();
239552
239644
  }
239553
239645
  });
239554
239646
 
@@ -250381,14 +250473,14 @@ var init_mcp_manager = __esm({
250381
250473
  };
250382
250474
  const envForTransport = Object.keys(env3).length > 0 ? env3 : void 0;
250383
250475
  if (!s3.command) throw new Error("MCP server command is required");
250384
- const spawn5 = await wrapStdioSpawnForFirejail(
250476
+ const spawn6 = await wrapStdioSpawnForFirejail(
250385
250477
  s3.command,
250386
250478
  args2,
250387
250479
  readSettings()
250388
250480
  );
250389
250481
  transport = new StdioClientTransport({
250390
- command: spawn5.command,
250391
- args: spawn5.args,
250482
+ command: spawn6.command,
250483
+ args: spawn6.args,
250392
250484
  env: envForTransport
250393
250485
  });
250394
250486
  } else if (s3.transport === "http") {
@@ -294799,15 +294891,15 @@ var require_generators = __commonJS({
294799
294891
  var stack = new Error().stack;
294800
294892
  return function() {
294801
294893
  var generator = generatorFunction.apply(this, arguments);
294802
- var spawn5 = new PromiseSpawn$(
294894
+ var spawn6 = new PromiseSpawn$(
294803
294895
  void 0,
294804
294896
  void 0,
294805
294897
  yieldHandler,
294806
294898
  stack
294807
294899
  );
294808
- var ret2 = spawn5.promise();
294809
- spawn5._generator = generator;
294810
- spawn5._promiseFulfilled(void 0);
294900
+ var ret2 = spawn6.promise();
294901
+ spawn6._generator = generator;
294902
+ spawn6._promiseFulfilled(void 0);
294811
294903
  return ret2;
294812
294904
  };
294813
294905
  };
@@ -294822,9 +294914,9 @@ var require_generators = __commonJS({
294822
294914
  if (typeof generatorFunction !== "function") {
294823
294915
  return apiRejection("generatorFunction must be a function\n\n See http://goo.gl/MqrFmX\n");
294824
294916
  }
294825
- var spawn5 = new PromiseSpawn(generatorFunction, this);
294826
- var ret2 = spawn5.promise();
294827
- spawn5._run(Promise2.spawn);
294917
+ var spawn6 = new PromiseSpawn(generatorFunction, this);
294918
+ var ret2 = spawn6.promise();
294919
+ spawn6._run(Promise2.spawn);
294828
294920
  return ret2;
294829
294921
  };
294830
294922
  };
@@ -320114,12 +320206,12 @@ async function readPlan(args2) {
320114
320206
  const row = await getLatestPlanArtifact(sessionId, filter4);
320115
320207
  if (!row) {
320116
320208
  if (filter4) {
320117
- const latest = await getLatestPlanArtifact(sessionId);
320118
- if (latest) {
320209
+ const latest2 = await getLatestPlanArtifact(sessionId);
320210
+ if (latest2) {
320119
320211
  return {
320120
320212
  success: true,
320121
320213
  content: null,
320122
- message: `No plan with status "${filter4}" \u2014 this session's plan is "${latest.planStatus}" (planId: ${latest.id}). Call plan({operation:"read"}) without a status to read it.`
320214
+ message: `No plan with status "${filter4}" \u2014 this session's plan is "${latest2.planStatus}" (planId: ${latest2.id}). Call plan({operation:"read"}) without a status to read it.`
320123
320215
  };
320124
320216
  }
320125
320217
  }
@@ -324644,11 +324736,11 @@ async function persistCursor(automationId, cursor) {
324644
324736
  await db.update(automations).set({ cursor: JSON.stringify(cursor), updatedAt: /* @__PURE__ */ new Date() }).where(eq(automations.id, automationId));
324645
324737
  }
324646
324738
  async function recordPollError(automationId, message) {
324647
- const latest = await db.query.automationRuns.findFirst({
324739
+ const latest2 = await db.query.automationRuns.findFirst({
324648
324740
  where: eq(automationRuns.automationId, automationId),
324649
324741
  orderBy: (runs, { desc: desc2 }) => [desc2(runs.startedAt)]
324650
324742
  });
324651
- if (latest?.status === "error" && latest.error === message) return;
324743
+ if (latest2?.status === "error" && latest2.error === message) return;
324652
324744
  const { randomUUID: randomUUID13 } = await import("node:crypto");
324653
324745
  const now2 = /* @__PURE__ */ new Date();
324654
324746
  await db.insert(automationRuns).values({
@@ -328295,7 +328387,7 @@ var init_prompt_budget = __esm({
328295
328387
  });
328296
328388
 
328297
328389
  // src/ipc/utils/system_git_exec.ts
328298
- import { execFile as execFile8, spawn as spawn4 } from "node:child_process";
328390
+ import { execFile as execFile8, spawn as spawn5 } from "node:child_process";
328299
328391
  import path54 from "node:path";
328300
328392
  function gitInstallHint() {
328301
328393
  switch (process.platform) {
@@ -331867,7 +331959,7 @@ function attachEmptyOutputSafeRejectHandlers(streamResult, logWarn, scope) {
331867
331959
  attach("finishReason");
331868
331960
  attach("response");
331869
331961
  }
331870
- var MODEL_EMPTY_RESPONSE_USER_MESSAGE, EmptyModelOutputAfterRetryError;
331962
+ var MODEL_EMPTY_RESPONSE_USER_MESSAGE, EmptyModelOutputAfterRetryError, ProviderRefusedRequestError;
331871
331963
  var init_no_usable_model_output = __esm({
331872
331964
  "src/ipc/utils/no_usable_model_output.ts"() {
331873
331965
  init_dist21();
@@ -331878,6 +331970,14 @@ var init_no_usable_model_output = __esm({
331878
331970
  this.name = "EmptyModelOutputAfterRetryError";
331879
331971
  }
331880
331972
  };
331973
+ ProviderRefusedRequestError = class extends Error {
331974
+ constructor(refusal) {
331975
+ super("ProviderRefusedRequest");
331976
+ this.refusal = refusal;
331977
+ this.name = "ProviderRefusedRequestError";
331978
+ }
331979
+ refusal;
331980
+ };
331881
331981
  }
331882
331982
  });
331883
331983
 
@@ -332583,6 +332683,10 @@ async function runStreamRoundWithEmptyOutputRetry(ctx, deps, params) {
332583
332683
  "[stream] The SDK reported 'no output generated' for a transport failure \u2014 retrying as a network error instead"
332584
332684
  );
332585
332685
  }
332686
+ const refusal = bundle?.getRecordedRefusal?.();
332687
+ if (refusal != null && isEmptyOutputStreamError(err2)) {
332688
+ throw new ProviderRefusedRequestError(refusal);
332689
+ }
332586
332690
  if (isForcedToolChoiceRejection(err2) && !ctx.forcedToolChoiceRejected) {
332587
332691
  ctx.forcedToolChoiceRejected = true;
332588
332692
  logger81.warn(
@@ -333162,6 +333266,7 @@ async function simpleStreamText(ctx, deps, {
333162
333266
  if (msgs && msgs.length) completedStepMessages.push(...msgs);
333163
333267
  };
333164
333268
  let recordedNetworkError = null;
333269
+ let recordedRefusal = null;
333165
333270
  const turnBuckets = {
333166
333271
  filesRead: 0,
333167
333272
  searchResults: 0,
@@ -333474,6 +333579,7 @@ async function simpleStreamText(ctx, deps, {
333474
333579
  } else {
333475
333580
  message = byokLimitUserMessage(message, modelClient.builtinProviderId) ?? modelFailureUserMessage(message);
333476
333581
  }
333582
+ if (isProviderRefusal(error51)) recordedRefusal = error51;
333477
333583
  logger84.error(
333478
333584
  `AI stream text error for request: errorMessage=${errorMessage} error=`,
333479
333585
  error51
@@ -333528,9 +333634,16 @@ async function simpleStreamText(ctx, deps, {
333528
333634
  * SDK's substitute error arrives, so the network branch can arm — see
333529
333635
  * `recordedNetworkError` above for why the substitution happens at all.
333530
333636
  */
333531
- getRecordedNetworkError: () => recordedNetworkError
333637
+ getRecordedNetworkError: () => recordedNetworkError,
333638
+ /** A refusal `onError` already reported; see `recordedRefusal`. */
333639
+ getRecordedRefusal: () => recordedRefusal
333532
333640
  };
333533
333641
  }
333642
+ function isProviderRefusal(error51) {
333643
+ const e3 = error51;
333644
+ const status = [e3?.statusCode, e3?.status, e3?.error?.statusCode, e3?.error?.status].find((x4) => typeof x4 === "number");
333645
+ return status != null && status >= 400 && status < 500 && status !== 408 && status !== 409 && status !== 429;
333646
+ }
333534
333647
  var import_electron_log87, logger84;
333535
333648
  var init_simple_stream_text = __esm({
333536
333649
  "src/ipc/handlers/chat_stream/turn-runner/simple-stream-text.ts"() {
@@ -339469,6 +339582,18 @@ async function handleTurnError(error51, ctx, deps) {
339469
339582
  FileUploadsState.getInstance().clear(req.chatId);
339470
339583
  return;
339471
339584
  }
339585
+ if (error51 instanceof ProviderRefusedRequestError) {
339586
+ safeSend(event.sender, "chat:response:end", {
339587
+ chatId: req.chatId,
339588
+ streamId,
339589
+ updatedFiles: false,
339590
+ hadToolCallsOrPermissions: false
339591
+ });
339592
+ if (activeStreamId.get(req.chatId) === streamId)
339593
+ activeStreams.delete(req.chatId);
339594
+ FileUploadsState.getInstance().clear(req.chatId);
339595
+ return;
339596
+ }
339472
339597
  if (error51 instanceof EmptyModelOutputAfterRetryError) {
339473
339598
  safeSend(event.sender, "chat:response:error", {
339474
339599
  chatId: req.chatId,
@@ -342965,7 +343090,7 @@ ${err2.stack}` : "";
342965
343090
  }
342966
343091
 
342967
343092
  // src/headless/atlarix-headless.mts
342968
- var VERSION15 = false ? "dev" : "15.2.0";
343093
+ var VERSION15 = false ? "dev" : "15.4.0";
342969
343094
  if (process.argv[2] === "run") process.argv.splice(2, 1);
342970
343095
  function arg(name29) {
342971
343096
  const i4 = process.argv.indexOf(`--${name29}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlarix",
3
- "version": "15.2.0",
3
+ "version": "15.4.0",
4
4
  "description": "Atlarix CLI: an AI coding agent in your terminal. Works with any model (Atlarix Auto, your own API key, or a local model) and runs unattended with `atlarix run` for CI.",
5
5
  "license": "PROPRIETARY",
6
6
  "author": {
@@ -65,12 +65,12 @@
65
65
  "provenance": false
66
66
  },
67
67
  "optionalDependencies": {
68
- "atlarix-darwin-arm64": "15.2.0",
69
- "atlarix-darwin-x64": "15.2.0",
70
- "atlarix-linux-x64": "15.2.0",
71
- "atlarix-linux-arm64": "15.2.0",
72
- "atlarix-linux-x64-musl": "15.2.0",
73
- "atlarix-linux-arm64-musl": "15.2.0",
74
- "atlarix-windows-x64": "15.2.0"
68
+ "atlarix-darwin-arm64": "15.4.0",
69
+ "atlarix-darwin-x64": "15.4.0",
70
+ "atlarix-linux-x64": "15.4.0",
71
+ "atlarix-linux-arm64": "15.4.0",
72
+ "atlarix-linux-x64-musl": "15.4.0",
73
+ "atlarix-linux-arm64-musl": "15.4.0",
74
+ "atlarix-windows-x64": "15.4.0"
75
75
  }
76
76
  }