@aiden-ade/sandbox-agent 0.1.38 → 0.1.40

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 (2) hide show
  1. package/dist/index.cjs +228 -35
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -15717,10 +15717,107 @@ function getCodexPermissionArgs(config) {
15717
15717
  const sandbox = shouldUseReadOnlyRuntimePermissions(config) ? "read-only" : "danger-full-access";
15718
15718
  return ["--ask-for-approval", "never", "--sandbox", sandbox];
15719
15719
  }
15720
+ var ERROR_SPECS = {
15721
+ model_mismatch: {
15722
+ message: "The selected model isn't available, or you don't have access to it. Pick a different model, then send your message again.",
15723
+ recoveryClass: "user_fixable"
15724
+ },
15725
+ auth_invalid: {
15726
+ message: "API key invalid or expired. Check your provider settings, then retry.",
15727
+ recoveryClass: "user_fixable"
15728
+ },
15729
+ not_logged_in: {
15730
+ message: "Not logged in to this provider. Sign in from provider settings, then retry.",
15731
+ recoveryClass: "user_fixable"
15732
+ },
15733
+ not_authenticated: {
15734
+ message: "The agent CLI isn't signed in on this runtime. Authenticate it there, then retry.",
15735
+ recoveryClass: "needs_env"
15736
+ },
15737
+ auth_expired: {
15738
+ message: "The provider connection expired. Reconnect it in Cloud settings, then restart or recreate the machine.",
15739
+ recoveryClass: "user_fixable"
15740
+ },
15741
+ subscription_required: {
15742
+ message: "This model requires a paid subscription. Switch to a supported model or upgrade your plan.",
15743
+ recoveryClass: "user_fixable"
15744
+ },
15745
+ quota_exceeded: {
15746
+ message: "API quota exceeded. Wait for it to reset or check your billing, then retry.",
15747
+ recoveryClass: "user_fixable"
15748
+ },
15749
+ usage_limit: {
15750
+ message: "Provider usage limit reached. Switch model or wait for the limit to reset, then retry.",
15751
+ recoveryClass: "user_fixable"
15752
+ },
15753
+ context_length: {
15754
+ message: "Context window exceeded \u2014 the conversation is too long. Start a new session or send a shorter message.",
15755
+ recoveryClass: "user_fixable"
15756
+ },
15757
+ rate_limited: {
15758
+ message: "Rate limited by the provider. Retrying automatically shortly.",
15759
+ recoveryClass: "auto_recovering"
15760
+ },
15761
+ overloaded: {
15762
+ message: "The provider is overloaded. Retrying automatically.",
15763
+ recoveryClass: "auto_recovering"
15764
+ },
15765
+ network: {
15766
+ message: "Network connection failed. Check your internet connection \u2014 retrying automatically.",
15767
+ recoveryClass: "auto_recovering"
15768
+ },
15769
+ provider_timeout: {
15770
+ message: "The provider timed out while continuing the turn. Send your message again; if it repeats, check network/API latency or reduce slow tool calls.",
15771
+ recoveryClass: "retry"
15772
+ },
15773
+ resume_failed: {
15774
+ message: "The previous session could not be resumed \u2014 its stored session was missing or expired. The next message starts a fresh session without earlier context.",
15775
+ recoveryClass: "retry"
15776
+ },
15777
+ permission_denied: {
15778
+ message: "Permission denied. Check file or command permissions on the runtime.",
15779
+ recoveryClass: "needs_env"
15780
+ },
15781
+ out_of_memory: {
15782
+ message: "Out of memory on the runtime. Close other work or use a larger machine, then retry.",
15783
+ recoveryClass: "needs_env"
15784
+ },
15785
+ provider_error: {
15786
+ message: "The provider returned an error. Send your message again to retry.",
15787
+ recoveryClass: "retry"
15788
+ },
15789
+ unknown_cli_error: {
15790
+ message: "The agent stopped unexpectedly. Send your message again to retry.",
15791
+ recoveryClass: "retry"
15792
+ }
15793
+ };
15794
+ function specForErrorKind(errorKind) {
15795
+ const spec = ERROR_SPECS[errorKind];
15796
+ return { message: spec.message, errorKind, recoveryClass: spec.recoveryClass };
15797
+ }
15720
15798
  function isLikelyProviderAuthError(stderr) {
15721
15799
  const lower = stderr.toLowerCase();
15722
15800
  return lower.includes("api key") || lower.includes("api_key") || lower.includes("invalid api key") || lower.includes("api key invalid") || lower.includes("unauthorized") || lower.includes("authentication failed") || lower.includes("authentication error") || lower.includes("provider settings") || lower.includes("invalid token") || lower.includes("token expired");
15723
15801
  }
15802
+ function mapProviderApiError(errorCode, apiStatus) {
15803
+ const code = errorCode?.trim().toLowerCase();
15804
+ if (code) {
15805
+ if (code.includes("model_not_found") || code.includes("model_not_available")) {
15806
+ return "model_mismatch";
15807
+ }
15808
+ if (code.includes("authentication") || code.includes("permission_error")) return "auth_invalid";
15809
+ if (code.includes("rate_limit")) return "rate_limited";
15810
+ if (code.includes("overloaded")) return "overloaded";
15811
+ if (code.includes("insufficient_quota") || code.includes("billing")) return "quota_exceeded";
15812
+ }
15813
+ if (typeof apiStatus === "number") {
15814
+ if (apiStatus === 404) return "model_mismatch";
15815
+ if (apiStatus === 401 || apiStatus === 403) return "auth_invalid";
15816
+ if (apiStatus === 429) return "rate_limited";
15817
+ if (apiStatus >= 500) return "overloaded";
15818
+ }
15819
+ return null;
15820
+ }
15724
15821
  function normalizeCodexCliErrorMessage(message) {
15725
15822
  const lower = message.toLowerCase();
15726
15823
  if (lower.includes("reconnecting") && lower.includes("request timed out")) {
@@ -15731,40 +15828,80 @@ function normalizeCodexCliErrorMessage(message) {
15731
15828
  }
15732
15829
  return message;
15733
15830
  }
15734
- function classifyCliError(stderr, exitCode) {
15831
+ function classifyCliErrorDetailed(stderr, _exitCode, opts) {
15735
15832
  const normalizedCodexError = normalizeCodexCliErrorMessage(stderr);
15736
- if (normalizedCodexError !== stderr) return normalizedCodexError;
15737
- const lower = stderr.toLowerCase();
15738
- if (lower.includes("authentication required") || lower.includes("not authenticated"))
15739
- return "Cursor Agent is not authenticated. Log in with `cursor-agent login` on this machine, then retry.";
15740
- if (lower.includes("out of usage") || lower.includes("increase your limit"))
15741
- return "Provider usage limit reached. Switch model/plan or wait for the limit to reset, then retry.";
15742
- if (lower.includes("no chat found") || lower.includes("chat not found") || lower.includes("session not found") || lower.includes("could not resume"))
15743
- return "The previous session could not be resumed \u2014 the CLI's stored session was missing or expired. The next message starts a fresh session without earlier context.";
15744
- if (lower.includes("rate_limit") || lower.includes("rate limit") || lower.includes("429"))
15745
- return "Rate limited by API. Try again shortly.";
15746
- if (isLikelyProviderAuthError(stderr))
15747
- return "API key invalid or expired. Check your provider settings.";
15748
- if (lower.includes("not logged in") || lower.includes("login required") || lower.includes("please sign in") || lower.includes("please log in") || lower.includes("sign in to") || lower.includes("log in to"))
15749
- return "Not logged in. Please authenticate with this provider first.";
15750
- if (lower.includes("model not supported") || lower.includes("unsupported model") || lower.includes("model not found") || lower.includes("model not available") || lower.includes("model is not available") || lower.includes("invalid model") || lower.includes("unknown model") || lower.includes("no such model") || lower.includes("llm not set"))
15751
- return "Model not supported by this provider. Check your provider's model list.";
15752
- if (lower.includes("subscription") || lower.includes("paid plan") || lower.includes("plan required") || lower.includes("upgrade your plan") || lower.includes("only supports free") || lower.includes("not available on your") || lower.includes("requires a paid"))
15753
- return "This model requires a paid subscription. Switch to a supported model or upgrade your plan.";
15754
- if (lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("daily limit") || lower.includes("monthly limit") || lower.includes("usage limit"))
15755
- return "API quota exceeded. Wait for it to reset or check your billing.";
15756
- if (lower.includes("overloaded") || lower.includes("503") || lower.includes("service unavailable"))
15757
- return "API is overloaded. Will retry automatically.";
15758
- if (lower.includes("context_length") || lower.includes("too long") || lower.includes("max tokens") || lower.includes("context window"))
15759
- return "Context window exceeded. Try a shorter prompt or start a new session.";
15760
- if (lower.includes("econnrefused") || lower.includes("network") || lower.includes("enotfound") || lower.includes("timeout") || lower.includes("etimedout"))
15761
- return "Network connection failed. Check your internet connection.";
15762
- if (lower.includes("permission denied") || lower.includes("eacces"))
15763
- return "Permission denied. Check file or command permissions.";
15764
- if (lower.includes("out of memory") || lower.includes("enomem"))
15765
- return "Out of memory. Close other applications and try again.";
15833
+ if (normalizedCodexError !== stderr) {
15834
+ const kind = /timed out/i.test(normalizedCodexError) ? "provider_timeout" : "auth_expired";
15835
+ return {
15836
+ message: normalizedCodexError,
15837
+ errorKind: kind,
15838
+ recoveryClass: ERROR_SPECS[kind].recoveryClass
15839
+ };
15840
+ }
15841
+ const haystack = [stderr, ...opts?.extraSignals ?? []].join("\n").toLowerCase();
15842
+ const has = (...needles) => needles.some((n) => haystack.includes(n));
15843
+ if (has("codex cli connection expired", "reconnect openai codex"))
15844
+ return specForErrorKind("auth_expired");
15845
+ if (has("codex connection to openai timed out")) return specForErrorKind("provider_timeout");
15846
+ if (has("authentication required", "not authenticated"))
15847
+ return specForErrorKind("not_authenticated");
15848
+ if (has("out of usage", "increase your limit")) return specForErrorKind("usage_limit");
15849
+ if (has("no chat found", "chat not found", "session not found", "could not resume"))
15850
+ return specForErrorKind("resume_failed");
15851
+ if (has("rate_limit", "rate limit", "429")) return specForErrorKind("rate_limited");
15852
+ if (has("authentication_failed", "authentication error", "authentication failed") || isLikelyProviderAuthError(haystack))
15853
+ return specForErrorKind("auth_invalid");
15854
+ if (has(
15855
+ "not logged in",
15856
+ "login required",
15857
+ "please sign in",
15858
+ "please log in",
15859
+ "sign in to",
15860
+ "log in to",
15861
+ // Claude prompts the user to re-auth with the /login slash command.
15862
+ "run /login",
15863
+ "please run /login"
15864
+ ))
15865
+ return specForErrorKind("not_logged_in");
15866
+ if (has(
15867
+ "model not supported",
15868
+ "unsupported model",
15869
+ "model not found",
15870
+ "model not available",
15871
+ "model is not available",
15872
+ "invalid model",
15873
+ "unknown model",
15874
+ "no such model",
15875
+ "llm not set",
15876
+ // Claude Code phrasings (safety net; the structured decoder catches these first).
15877
+ "issue with the selected model",
15878
+ "may not exist or you may not have access"
15879
+ ))
15880
+ return specForErrorKind("model_mismatch");
15881
+ if (has(
15882
+ "subscription",
15883
+ "paid plan",
15884
+ "plan required",
15885
+ "upgrade your plan",
15886
+ "only supports free",
15887
+ "not available on your",
15888
+ "requires a paid"
15889
+ ))
15890
+ return specForErrorKind("subscription_required");
15891
+ if (has("quota exceeded", "quota limit", "daily limit", "monthly limit", "usage limit"))
15892
+ return specForErrorKind("quota_exceeded");
15893
+ if (has("overloaded", "503", "service unavailable")) return specForErrorKind("overloaded");
15894
+ if (has("context_length", "too long", "max tokens", "context window"))
15895
+ return specForErrorKind("context_length");
15896
+ if (has("econnrefused", "network", "enotfound", "timeout", "etimedout"))
15897
+ return specForErrorKind("network");
15898
+ if (has("permission denied", "eacces")) return specForErrorKind("permission_denied");
15899
+ if (has("out of memory", "enomem")) return specForErrorKind("out_of_memory");
15766
15900
  const raw = stderr.trim().slice(0, 200);
15767
- return raw || `Process exited with code ${exitCode}`;
15901
+ if (raw) {
15902
+ return { message: raw, errorKind: "unknown_cli_error", recoveryClass: "unknown" };
15903
+ }
15904
+ return specForErrorKind("unknown_cli_error");
15768
15905
  }
15769
15906
  function isTransientError(errorMessage) {
15770
15907
  const lower = errorMessage.toLowerCase();
@@ -16204,13 +16341,34 @@ function createGenericCliBackend(options) {
16204
16341
  };
16205
16342
  }
16206
16343
  const summary = state.summary.trim() || state.error?.trim() || (failed ? "Task failed" : "Task completed");
16344
+ let classifiedError;
16345
+ let classifiedErrorKind;
16346
+ let classifiedRecoveryClass;
16347
+ if (failed) {
16348
+ if (state.errorKind && state.error?.trim()) {
16349
+ classifiedError = state.error.trim();
16350
+ classifiedErrorKind = state.errorKind;
16351
+ classifiedRecoveryClass = state.recoveryClass;
16352
+ } else {
16353
+ const detailed = classifyCliErrorDetailed(
16354
+ state.error?.trim() || stderrText || "",
16355
+ exitCode,
16356
+ { extraSignals: state.summary ? [state.summary] : void 0 }
16357
+ );
16358
+ classifiedError = detailed.message;
16359
+ classifiedErrorKind = detailed.errorKind;
16360
+ classifiedRecoveryClass = detailed.recoveryClass;
16361
+ }
16362
+ }
16207
16363
  return {
16208
16364
  success: !failed,
16209
16365
  summary,
16210
16366
  filesModified: [],
16211
16367
  planFilesCreated: [],
16212
16368
  iterations: Math.max(state.iterations, 1),
16213
- error: failed ? classifyCliError(state.error?.trim() || stderrText || "", exitCode) : void 0,
16369
+ error: classifiedError,
16370
+ ...classifiedErrorKind ? { errorKind: classifiedErrorKind } : {},
16371
+ ...classifiedRecoveryClass ? { recoveryClass: classifiedRecoveryClass } : {},
16214
16372
  providerSessionId: state.runtimeSessionId,
16215
16373
  runtimeSessionId: state.runtimeSessionId,
16216
16374
  backendKind: options.kind,
@@ -16884,6 +17042,9 @@ function handleClaudeStructuredEvent(parsed, context, state) {
16884
17042
  if (typeof parsed.session_id === "string") {
16885
17043
  state.runtimeSessionId = parsed.session_id;
16886
17044
  }
17045
+ if (typeof parsed.error === "string" && parsed.error.trim()) {
17046
+ state.providerErrorCode = parsed.error.trim();
17047
+ }
16887
17048
  const parentToolUseId = typeof parsed.parent_tool_use_id === "string" ? parsed.parent_tool_use_id : null;
16888
17049
  switch (type) {
16889
17050
  case "system": {
@@ -17096,6 +17257,27 @@ function handleClaudeStructuredEvent(parsed, context, state) {
17096
17257
  })
17097
17258
  );
17098
17259
  }
17260
+ const isApiError = parsed.is_error === true || parsed.terminal_reason === "api_error" || typeof parsed.api_error_status === "number";
17261
+ if (isApiError && !state.error) {
17262
+ const apiStatus = typeof parsed.api_error_status === "number" ? parsed.api_error_status : void 0;
17263
+ state.apiErrorStatus = apiStatus;
17264
+ const mappedKind = mapProviderApiError(state.providerErrorCode, apiStatus);
17265
+ const spec = mappedKind ? specForErrorKind(mappedKind) : specForErrorKind("provider_error");
17266
+ state.error = mappedKind || typeof parsed.result !== "string" || !parsed.result.trim() ? spec.message : parsed.result.trim();
17267
+ state.errorKind = spec.errorKind;
17268
+ state.recoveryClass = spec.recoveryClass;
17269
+ console.error(
17270
+ "[claude_cli] result flagged API error:",
17271
+ JSON.stringify({
17272
+ is_error: parsed.is_error,
17273
+ terminal_reason: parsed.terminal_reason,
17274
+ api_error_status: parsed.api_error_status,
17275
+ providerErrorCode: state.providerErrorCode,
17276
+ mappedKind,
17277
+ result: typeof parsed.result === "string" ? parsed.result.slice(0, 200) : void 0
17278
+ })
17279
+ );
17280
+ }
17099
17281
  debugAskLog(
17100
17282
  `[claude_cli] result event \u2014 pendingAskUserToolIds size=${presenter.pendingAskUserToolIds?.size ?? "undefined"}, hasOwnProp=${Object.hasOwn(presenter, "pendingAskUserToolIds")}`
17101
17283
  );
@@ -21829,6 +22011,10 @@ var ANTIGRAVITY_MODEL_DISCOVERY_TIMEOUT_MS = 12e3;
21829
22011
  var OPENCODE_ZEN_MODEL_IDS = OPENCODE_ZEN_MODELS.map((model) => model.id);
21830
22012
  var CLAUDE_MODEL_ID_PATTERN = /claude-(?:sonnet|opus|haiku|fable)-[a-z0-9][a-z0-9-]*/g;
21831
22013
  var CLAUDE_MODEL_ALIASES = ["sonnet", "opus", "haiku", "fable"];
22014
+ var CLAUDE_MODEL_QUALIFIER_SEGMENTS = /* @__PURE__ */ new Set(["fast", "thinking", "latest", "preview"]);
22015
+ var PROTECTED_CATALOG_MODEL_IDS = new Set(
22016
+ AVAILABLE_MODELS.map((model) => model.id).filter((id) => id.length > 0)
22017
+ );
21832
22018
  var CATALOG_MODEL_IDS_BY_PROVIDER = Object.fromEntries(
21833
22019
  Object.entries(PROVIDER_MODELS).map(([provider, models]) => [
21834
22020
  provider,
@@ -21919,15 +22105,22 @@ function extractModelIdsFromLabeledList(value2) {
21919
22105
  }
21920
22106
  function isValidClaudeModelId(id) {
21921
22107
  if (CLAUDE_MODEL_ALIASES.includes(id)) return true;
21922
- if (!/^claude-(sonnet|opus|haiku|fable)-[a-z0-9][a-z0-9-]*$/.test(id)) return false;
22108
+ const match = id.match(/^claude-(sonnet|opus|haiku|fable)-([a-z0-9][a-z0-9-]*)$/);
22109
+ if (!match) return false;
21923
22110
  if (id.includes(".")) return false;
21924
22111
  if (/-\d{8}$/.test(id)) return false;
21925
22112
  if (/-v\d+$/.test(id)) return false;
22113
+ for (const segment of match[2].split("-")) {
22114
+ if (/^\d+$/.test(segment)) continue;
22115
+ if (CLAUDE_MODEL_QUALIFIER_SEGMENTS.has(segment)) continue;
22116
+ return false;
22117
+ }
21926
22118
  return true;
21927
22119
  }
21928
22120
  function filterClaudePrefixModelIds(ids) {
21929
22121
  const set = new Set(ids);
21930
22122
  return ids.filter((id) => {
22123
+ if (PROTECTED_CATALOG_MODEL_IDS.has(id)) return true;
21931
22124
  for (const other of set) {
21932
22125
  if (other !== id && other.startsWith(`${id}-`)) return false;
21933
22126
  }
@@ -22250,7 +22443,7 @@ var RunStartGate = class {
22250
22443
  };
22251
22444
 
22252
22445
  // src/version.ts
22253
- var AGENT_VERSION = "0.1.38";
22446
+ var AGENT_VERSION = "0.1.40";
22254
22447
 
22255
22448
  // src/workspace-relocation.ts
22256
22449
  var import_node_child_process3 = require("child_process");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiden-ade/sandbox-agent",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "type": "module",
5
5
  "description": "Alan agent runtime — cloud sandbox and local daemon (alan-agent CLI)",
6
6
  "bin": {