atlarix 15.3.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.
@@ -27355,6 +27355,8 @@ var init_schemas3 = __esm({
27355
27355
  voiceTranscriptionProvider: external_exports.string().optional(),
27356
27356
  /** Messages from the user's other Atlarix sessions: delivered, or refused. Unset = accept. */
27357
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(),
27358
27360
  // `voiceInputMode` REMOVED. Its "live" value streamed a re-transcription of a
27359
27361
  // growing buffer every 1.5s, which was only viable against the local WebGPU
27360
27362
  // pipeline; against a metered API it would bill every 1.5s for the same audio.
@@ -67739,7 +67741,7 @@ var require_cross_spawn = __commonJS({
67739
67741
  var cp = __require("child_process");
67740
67742
  var parse4 = require_parse();
67741
67743
  var enoent = require_enoent();
67742
- function spawn5(command, args2, options) {
67744
+ function spawn6(command, args2, options) {
67743
67745
  const parsed = parse4(command, args2, options);
67744
67746
  const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
67745
67747
  enoent.hookChildProcess(spawned, parsed);
@@ -67751,8 +67753,8 @@ var require_cross_spawn = __commonJS({
67751
67753
  result2.error = result2.error || enoent.verifyENOENTSync(result2.status, parsed);
67752
67754
  return result2;
67753
67755
  }
67754
- module2.exports = spawn5;
67755
- module2.exports.spawn = spawn5;
67756
+ module2.exports = spawn6;
67757
+ module2.exports.spawn = spawn6;
67756
67758
  module2.exports.sync = spawnSync3;
67757
67759
  module2.exports._parse = parse4;
67758
67760
  module2.exports._enoent = enoent;
@@ -147400,12 +147402,12 @@ var require_oauth2client = __commonJS({
147400
147402
  throw new Error("Expiration time too far in future: " + JSON.stringify(payload));
147401
147403
  }
147402
147404
  const earliest = iat - _OAuth2Client.CLOCK_SKEW_SECS_;
147403
- const latest = exp + _OAuth2Client.CLOCK_SKEW_SECS_;
147405
+ const latest2 = exp + _OAuth2Client.CLOCK_SKEW_SECS_;
147404
147406
  if (now2 < earliest) {
147405
147407
  throw new Error("Token used too early, " + now2 + " < " + earliest + ": " + JSON.stringify(payload));
147406
147408
  }
147407
- if (now2 > latest) {
147408
- 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));
147409
147411
  }
147410
147412
  if (issuers && issuers.indexOf(payload.iss) < 0) {
147411
147413
  throw new Error("Invalid issuer, expected one of [" + issuers + "], but got " + payload.iss);
@@ -192248,6 +192250,7 @@ function autoStatusFromHeaders(headers) {
192248
192250
  function broadcastAutoStatus(headers) {
192249
192251
  const status = autoStatusFromHeaders(headers);
192250
192252
  if (!status) return;
192253
+ latest = status;
192251
192254
  void noteAutoBilling(status.billing);
192252
192255
  import_electron_log17.default.scope("atlarix-auto").info(
192253
192256
  `Auto reply: billing=${status.billing ?? "?"} model=${status.servedModel ?? "?"}`
@@ -192256,13 +192259,14 @@ function broadcastAutoStatus(headers) {
192256
192259
  if (!win.isDestroyed()) safeSend(win.webContents, "core:auto-status", status);
192257
192260
  }
192258
192261
  }
192259
- var import_electron_log17;
192262
+ var import_electron_log17, latest;
192260
192263
  var init_auto_status = __esm({
192261
192264
  "src/main/auto-status.ts"() {
192262
192265
  init_electron_stub();
192263
192266
  import_electron_log17 = __toESM(require_src());
192264
192267
  init_safe_sender();
192265
192268
  init_auto_context_window();
192269
+ latest = null;
192266
192270
  }
192267
192271
  });
192268
192272
 
@@ -212144,7 +212148,7 @@ function getPostHog() {
212144
212148
  if (client) return client;
212145
212149
  const apiKey = process.env.POSTHOG_API_KEY;
212146
212150
  if (!apiKey) return null;
212147
- client = new PostHog(apiKey, { host: POSTHOG_HOST });
212151
+ client = new PostHog(apiKey, { host: POSTHOG_HOST, disableGeoip: false });
212148
212152
  return client;
212149
212153
  }
212150
212154
  function resolveDistinctId() {
@@ -237607,7 +237611,7 @@ function isOnlyToolDebris(content) {
237607
237611
  stripToolJsonLinesFromMarkdown(stripOrphanToolTagFragments(content))
237608
237612
  ).trim();
237609
237613
  if (!stripped) return true;
237610
- if (/^[\s\]},":\d._-]+$/.test(stripped)) return true;
237614
+ if (/^[\s\]},":\d._-]+$/.test(stripped) && /[\]}"]/.test(stripped)) return true;
237611
237615
  if (/^"?,?\s*"message"\s*:/.test(stripped)) return true;
237612
237616
  if (/^\{\s*"success"\s*:/.test(stripped)) return true;
237613
237617
  const lines = stripped.split("\n").filter((l3) => l3.trim());
@@ -238895,6 +238899,65 @@ var init_continue_in_terminal = __esm({
238895
238899
  }
238896
238900
  });
238897
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
+
238898
238961
  // src/ipc/handlers/workspace_session_handlers.ts
238899
238962
  var workspace_session_handlers_exports = {};
238900
238963
  __export(workspace_session_handlers_exports, {
@@ -239069,18 +239132,18 @@ async function resolveOrBindWorkspaceSessionForStream(input) {
239069
239132
  "workspaceSessionId is required for workspace chat streams"
239070
239133
  );
239071
239134
  }
239072
- const latest = await db.query.chatSessions.findFirst({
239135
+ const latest2 = await db.query.chatSessions.findFirst({
239073
239136
  where: eq(chatSessions.workspaceId, appId),
239074
239137
  orderBy: [desc(chatSessions.updatedAt)]
239075
239138
  });
239076
- if (latest) {
239077
- const stored = latest.streamChatId;
239139
+ if (latest2) {
239140
+ const stored = latest2.streamChatId;
239078
239141
  if (stored == null || isStreamTransportId(stored) && !getStreamTransport(stored)) {
239079
- await persistSessionStreamChatId(latest.id, input.chatId);
239142
+ await persistSessionStreamChatId(latest2.id, input.chatId);
239080
239143
  logger30.warn(
239081
- `Bound transport chat ${input.chatId} to session ${latest.id} (workspace ${appId})`
239144
+ `Bound transport chat ${input.chatId} to session ${latest2.id} (workspace ${appId})`
239082
239145
  );
239083
- return latest.id;
239146
+ return latest2.id;
239084
239147
  }
239085
239148
  }
239086
239149
  const session2 = await insertWorkspaceSession(appId);
@@ -239397,6 +239460,15 @@ function registerWorkspaceSessionHandlers() {
239397
239460
  const { continueInTerminal: continueInTerminal2 } = await Promise.resolve().then(() => (init_continue_in_terminal(), continue_in_terminal_exports));
239398
239461
  return continueInTerminal2(sessionId, workspace?.path ?? "");
239399
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
+ });
239400
239472
  handle("sessions:create", async (_event, workspaceId) => {
239401
239473
  await requireAuthSession();
239402
239474
  return insertWorkspaceSession(workspaceId);
@@ -250401,14 +250473,14 @@ var init_mcp_manager = __esm({
250401
250473
  };
250402
250474
  const envForTransport = Object.keys(env3).length > 0 ? env3 : void 0;
250403
250475
  if (!s3.command) throw new Error("MCP server command is required");
250404
- const spawn5 = await wrapStdioSpawnForFirejail(
250476
+ const spawn6 = await wrapStdioSpawnForFirejail(
250405
250477
  s3.command,
250406
250478
  args2,
250407
250479
  readSettings()
250408
250480
  );
250409
250481
  transport = new StdioClientTransport({
250410
- command: spawn5.command,
250411
- args: spawn5.args,
250482
+ command: spawn6.command,
250483
+ args: spawn6.args,
250412
250484
  env: envForTransport
250413
250485
  });
250414
250486
  } else if (s3.transport === "http") {
@@ -294819,15 +294891,15 @@ var require_generators = __commonJS({
294819
294891
  var stack = new Error().stack;
294820
294892
  return function() {
294821
294893
  var generator = generatorFunction.apply(this, arguments);
294822
- var spawn5 = new PromiseSpawn$(
294894
+ var spawn6 = new PromiseSpawn$(
294823
294895
  void 0,
294824
294896
  void 0,
294825
294897
  yieldHandler,
294826
294898
  stack
294827
294899
  );
294828
- var ret2 = spawn5.promise();
294829
- spawn5._generator = generator;
294830
- spawn5._promiseFulfilled(void 0);
294900
+ var ret2 = spawn6.promise();
294901
+ spawn6._generator = generator;
294902
+ spawn6._promiseFulfilled(void 0);
294831
294903
  return ret2;
294832
294904
  };
294833
294905
  };
@@ -294842,9 +294914,9 @@ var require_generators = __commonJS({
294842
294914
  if (typeof generatorFunction !== "function") {
294843
294915
  return apiRejection("generatorFunction must be a function\n\n See http://goo.gl/MqrFmX\n");
294844
294916
  }
294845
- var spawn5 = new PromiseSpawn(generatorFunction, this);
294846
- var ret2 = spawn5.promise();
294847
- spawn5._run(Promise2.spawn);
294917
+ var spawn6 = new PromiseSpawn(generatorFunction, this);
294918
+ var ret2 = spawn6.promise();
294919
+ spawn6._run(Promise2.spawn);
294848
294920
  return ret2;
294849
294921
  };
294850
294922
  };
@@ -320134,12 +320206,12 @@ async function readPlan(args2) {
320134
320206
  const row = await getLatestPlanArtifact(sessionId, filter4);
320135
320207
  if (!row) {
320136
320208
  if (filter4) {
320137
- const latest = await getLatestPlanArtifact(sessionId);
320138
- if (latest) {
320209
+ const latest2 = await getLatestPlanArtifact(sessionId);
320210
+ if (latest2) {
320139
320211
  return {
320140
320212
  success: true,
320141
320213
  content: null,
320142
- 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.`
320143
320215
  };
320144
320216
  }
320145
320217
  }
@@ -324664,11 +324736,11 @@ async function persistCursor(automationId, cursor) {
324664
324736
  await db.update(automations).set({ cursor: JSON.stringify(cursor), updatedAt: /* @__PURE__ */ new Date() }).where(eq(automations.id, automationId));
324665
324737
  }
324666
324738
  async function recordPollError(automationId, message) {
324667
- const latest = await db.query.automationRuns.findFirst({
324739
+ const latest2 = await db.query.automationRuns.findFirst({
324668
324740
  where: eq(automationRuns.automationId, automationId),
324669
324741
  orderBy: (runs, { desc: desc2 }) => [desc2(runs.startedAt)]
324670
324742
  });
324671
- if (latest?.status === "error" && latest.error === message) return;
324743
+ if (latest2?.status === "error" && latest2.error === message) return;
324672
324744
  const { randomUUID: randomUUID13 } = await import("node:crypto");
324673
324745
  const now2 = /* @__PURE__ */ new Date();
324674
324746
  await db.insert(automationRuns).values({
@@ -328315,7 +328387,7 @@ var init_prompt_budget = __esm({
328315
328387
  });
328316
328388
 
328317
328389
  // src/ipc/utils/system_git_exec.ts
328318
- import { execFile as execFile8, spawn as spawn4 } from "node:child_process";
328390
+ import { execFile as execFile8, spawn as spawn5 } from "node:child_process";
328319
328391
  import path54 from "node:path";
328320
328392
  function gitInstallHint() {
328321
328393
  switch (process.platform) {
@@ -331887,7 +331959,7 @@ function attachEmptyOutputSafeRejectHandlers(streamResult, logWarn, scope) {
331887
331959
  attach("finishReason");
331888
331960
  attach("response");
331889
331961
  }
331890
- var MODEL_EMPTY_RESPONSE_USER_MESSAGE, EmptyModelOutputAfterRetryError;
331962
+ var MODEL_EMPTY_RESPONSE_USER_MESSAGE, EmptyModelOutputAfterRetryError, ProviderRefusedRequestError;
331891
331963
  var init_no_usable_model_output = __esm({
331892
331964
  "src/ipc/utils/no_usable_model_output.ts"() {
331893
331965
  init_dist21();
@@ -331898,6 +331970,14 @@ var init_no_usable_model_output = __esm({
331898
331970
  this.name = "EmptyModelOutputAfterRetryError";
331899
331971
  }
331900
331972
  };
331973
+ ProviderRefusedRequestError = class extends Error {
331974
+ constructor(refusal) {
331975
+ super("ProviderRefusedRequest");
331976
+ this.refusal = refusal;
331977
+ this.name = "ProviderRefusedRequestError";
331978
+ }
331979
+ refusal;
331980
+ };
331901
331981
  }
331902
331982
  });
331903
331983
 
@@ -332603,6 +332683,10 @@ async function runStreamRoundWithEmptyOutputRetry(ctx, deps, params) {
332603
332683
  "[stream] The SDK reported 'no output generated' for a transport failure \u2014 retrying as a network error instead"
332604
332684
  );
332605
332685
  }
332686
+ const refusal = bundle?.getRecordedRefusal?.();
332687
+ if (refusal != null && isEmptyOutputStreamError(err2)) {
332688
+ throw new ProviderRefusedRequestError(refusal);
332689
+ }
332606
332690
  if (isForcedToolChoiceRejection(err2) && !ctx.forcedToolChoiceRejected) {
332607
332691
  ctx.forcedToolChoiceRejected = true;
332608
332692
  logger81.warn(
@@ -333182,6 +333266,7 @@ async function simpleStreamText(ctx, deps, {
333182
333266
  if (msgs && msgs.length) completedStepMessages.push(...msgs);
333183
333267
  };
333184
333268
  let recordedNetworkError = null;
333269
+ let recordedRefusal = null;
333185
333270
  const turnBuckets = {
333186
333271
  filesRead: 0,
333187
333272
  searchResults: 0,
@@ -333494,6 +333579,7 @@ async function simpleStreamText(ctx, deps, {
333494
333579
  } else {
333495
333580
  message = byokLimitUserMessage(message, modelClient.builtinProviderId) ?? modelFailureUserMessage(message);
333496
333581
  }
333582
+ if (isProviderRefusal(error51)) recordedRefusal = error51;
333497
333583
  logger84.error(
333498
333584
  `AI stream text error for request: errorMessage=${errorMessage} error=`,
333499
333585
  error51
@@ -333548,9 +333634,16 @@ async function simpleStreamText(ctx, deps, {
333548
333634
  * SDK's substitute error arrives, so the network branch can arm — see
333549
333635
  * `recordedNetworkError` above for why the substitution happens at all.
333550
333636
  */
333551
- getRecordedNetworkError: () => recordedNetworkError
333637
+ getRecordedNetworkError: () => recordedNetworkError,
333638
+ /** A refusal `onError` already reported; see `recordedRefusal`. */
333639
+ getRecordedRefusal: () => recordedRefusal
333552
333640
  };
333553
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
+ }
333554
333647
  var import_electron_log87, logger84;
333555
333648
  var init_simple_stream_text = __esm({
333556
333649
  "src/ipc/handlers/chat_stream/turn-runner/simple-stream-text.ts"() {
@@ -339489,6 +339582,18 @@ async function handleTurnError(error51, ctx, deps) {
339489
339582
  FileUploadsState.getInstance().clear(req.chatId);
339490
339583
  return;
339491
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
+ }
339492
339597
  if (error51 instanceof EmptyModelOutputAfterRetryError) {
339493
339598
  safeSend(event.sender, "chat:response:error", {
339494
339599
  chatId: req.chatId,
@@ -342985,7 +343090,7 @@ ${err2.stack}` : "";
342985
343090
  }
342986
343091
 
342987
343092
  // src/headless/atlarix-headless.mts
342988
- var VERSION15 = false ? "dev" : "15.3.0";
343093
+ var VERSION15 = false ? "dev" : "15.4.0";
342989
343094
  if (process.argv[2] === "run") process.argv.splice(2, 1);
342990
343095
  function arg(name29) {
342991
343096
  const i4 = process.argv.indexOf(`--${name29}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlarix",
3
- "version": "15.3.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.3.0",
69
- "atlarix-darwin-x64": "15.3.0",
70
- "atlarix-linux-x64": "15.3.0",
71
- "atlarix-linux-arm64": "15.3.0",
72
- "atlarix-linux-x64-musl": "15.3.0",
73
- "atlarix-linux-arm64-musl": "15.3.0",
74
- "atlarix-windows-x64": "15.3.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
  }