@atlasharness/atlascode 0.1.16 → 0.1.17

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/cli.js +398 -275
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -129,6 +129,29 @@ function cliOk(msg) {
129
129
  return;
130
130
  }
131
131
 
132
+ // src/cli/crashBackstop.ts
133
+ function registerGlobalCrashBackstop() {
134
+ if (registered) {
135
+ return;
136
+ }
137
+ registered = true;
138
+ process.on("uncaughtException", (error) => {
139
+ writeCrashDiag("uncaughtException", error);
140
+ });
141
+ process.on("unhandledRejection", (reason) => {
142
+ writeCrashDiag("unhandledRejection", reason);
143
+ });
144
+ }
145
+ function writeCrashDiag(kind, value) {
146
+ const formatted = value instanceof Error ? `${value.name}: ${value.message}
147
+ ${value.stack ?? ""}`.trim() : String(value);
148
+ try {
149
+ process.stderr.write(`[atlas][${kind}] ${formatted}
150
+ `);
151
+ } catch {}
152
+ }
153
+ var registered = false;
154
+
132
155
  // src/shared/feature.ts
133
156
  function feature(name) {
134
157
  const key = "FEATURE_" + name;
@@ -668,266 +691,6 @@ var init_shared = __esm(() => {
668
691
  init_types();
669
692
  });
670
693
 
671
- // src/engine/pipeline/errorClassification.ts
672
- function classifyToolError(error) {
673
- if (error instanceof Error) {
674
- const errnoCode = error.code;
675
- if (typeof errnoCode === "string") {
676
- return `Error:${errnoCode}`;
677
- }
678
- if (error.name && error.name !== "Error" && error.name.length > 3) {
679
- return error.name.slice(0, 60);
680
- }
681
- return "Error";
682
- }
683
- return "UnknownError";
684
- }
685
-
686
- // src/engine/pipeline/schemaValidation.ts
687
- function typeName(v) {
688
- if (v === null)
689
- return "null";
690
- if (Array.isArray(v))
691
- return "array";
692
- return typeof v;
693
- }
694
- function checkBasicType(value, type) {
695
- switch (type) {
696
- case "string":
697
- return typeof value === "string" ? null : `expected string, got ${typeName(value)}`;
698
- case "number":
699
- if (typeof value === "number") {
700
- return Number.isFinite(value) ? null : "expected number, got non-finite number";
701
- }
702
- return `expected number, got ${typeName(value)}`;
703
- case "integer":
704
- if (typeof value === "number") {
705
- return Number.isInteger(value) ? null : "expected integer, got non-integer number";
706
- }
707
- return `expected integer, got ${typeName(value)}`;
708
- case "boolean":
709
- return typeof value === "boolean" ? null : `expected boolean, got ${typeName(value)}`;
710
- case "array":
711
- return Array.isArray(value) ? null : `expected array, got ${typeName(value)}`;
712
- case "object":
713
- return typeof value === "object" && value !== null && !Array.isArray(value) ? null : `expected object, got ${typeName(value)}`;
714
- case "null":
715
- return value === null ? null : `expected null, got ${typeName(value)}`;
716
- default:
717
- return null;
718
- }
719
- }
720
- function validateInputBySchema(input, schema) {
721
- if (!schema) {
722
- return { valid: true };
723
- }
724
- if (input === null || typeof input !== "object" || Array.isArray(input)) {
725
- return { valid: false, message: "expected object input" };
726
- }
727
- const record = input;
728
- const errors = [];
729
- const required = schema.required;
730
- if (Array.isArray(required)) {
731
- for (const rawKey of required) {
732
- const key = String(rawKey);
733
- if (!(key in record) || record[key] === undefined) {
734
- errors.push(`missing required field "${key}"`);
735
- }
736
- }
737
- }
738
- const properties = schema.properties;
739
- if (properties) {
740
- for (const [key, value] of Object.entries(record)) {
741
- if (value === undefined)
742
- continue;
743
- const propSchema = properties[key];
744
- if (!propSchema || typeof propSchema !== "object")
745
- continue;
746
- const type = propSchema.type;
747
- if (typeof type !== "string")
748
- continue;
749
- const err = checkBasicType(value, type);
750
- if (err)
751
- errors.push(`field "${key}": ${err}`);
752
- }
753
- }
754
- return errors.length > 0 ? { valid: false, message: errors.join("; ") } : { valid: true };
755
- }
756
- function buildSchemaNotSentHint(tool, discoveredToolNames) {
757
- if (!tool.shouldDefer)
758
- return null;
759
- if (discoveredToolNames.has(tool.name))
760
- return null;
761
- return `
762
-
763
- This tool's schema was not sent to the API — it was not in the discovered-tool set derived from message history. ` + `Without the schema in your prompt, typed parameters (arrays, numbers, booleans) get emitted as strings and the client-side parser rejects them. ` + `Re-issue the call with correctly-typed parameters (arrays/numbers/booleans, not their string forms).`;
764
- }
765
-
766
- // src/engine/pipeline/toolExecution.ts
767
- function mergeHookPermission(hookBehavior, gate) {
768
- if (hookBehavior === "deny") {
769
- return { allowed: false, reason: "blocked by hook" };
770
- }
771
- if (!gate.allowed)
772
- return gate;
773
- if (hookBehavior === "ask") {
774
- return { allowed: false, ask: true, reason: "hook requested confirmation" };
775
- }
776
- return gate;
777
- }
778
- function findTool(tools, name) {
779
- return tools.find((t) => t.name === name || (t.aliases?.includes(name) ?? false));
780
- }
781
- async function executeToolUse(tu, assistantMsg, deps) {
782
- const tool = findTool(deps.tools, tu.name);
783
- if (!tool) {
784
- return {
785
- block: {
786
- type: "tool_result",
787
- tool_use_id: tu.id,
788
- content: `unknown tool: ${tu.name}`,
789
- is_error: true
790
- },
791
- isError: true
792
- };
793
- }
794
- const toolCallContext = deps.toolContext !== undefined ? { ...deps.toolContext, signal: deps.signal, checkPermission: deps.checkPermission } : { signal: deps.signal, checkPermission: deps.checkPermission };
795
- const schemaResult = validateInputBySchema(tu.input, tool.inputSchema);
796
- if (schemaResult.valid === false) {
797
- const discovered = deps.discoveredToolNames ?? new Set(deps.tools.map((t) => t.name));
798
- const schemaHint = buildSchemaNotSentHint(tool, discovered);
799
- const errorContent = schemaHint ? `${schemaResult.message}${schemaHint}` : schemaResult.message;
800
- return {
801
- block: {
802
- type: "tool_result",
803
- tool_use_id: tu.id,
804
- content: `<tool_use_error>InputValidationError: ${errorContent}</tool_use_error>`,
805
- is_error: true
806
- },
807
- isError: true
808
- };
809
- }
810
- const validation = await tool.validateInput?.(tu.input, toolCallContext);
811
- if (validation && validation.result === false) {
812
- return {
813
- block: {
814
- type: "tool_result",
815
- tool_use_id: tu.id,
816
- content: `<tool_use_error>InputValidationError: ${validation.message}</tool_use_error>`,
817
- is_error: true
818
- },
819
- isError: true
820
- };
821
- }
822
- const preOutcome = await deps.hooks?.preToolUse?.(tool, tu.input, tu.id);
823
- if (preOutcome?.blockingError) {
824
- return {
825
- block: {
826
- type: "tool_result",
827
- tool_use_id: tu.id,
828
- content: `<tool_use_error>hook blocked: ${preOutcome.blockingError}</tool_use_error>`,
829
- is_error: true
830
- },
831
- isError: true
832
- };
833
- }
834
- if (preOutcome?.preventContinuation) {
835
- return {
836
- block: {
837
- type: "tool_result",
838
- tool_use_id: tu.id,
839
- content: `<tool_use_error>Execution stopped by PreToolUse hook${preOutcome.stopReason ? `: ${preOutcome.stopReason}` : ""}</tool_use_error>`,
840
- is_error: true
841
- },
842
- isError: true
843
- };
844
- }
845
- const effectiveInput = preOutcome?.updatedInput ?? tu.input;
846
- const gateVerdict = deps.checkPermission ? await deps.checkPermission(tool, effectiveInput, {
847
- toolUseId: tu.id,
848
- assistantMessage: assistantMsg
849
- }) : { allowed: true };
850
- const verdict = mergeHookPermission(preOutcome?.hookBehavior, gateVerdict);
851
- if (!verdict.allowed) {
852
- const content = verdict.ask ? `<tool_use_error>permission confirmation required (prompt 面残留守): ${verdict.reason ?? tu.name}</tool_use_error>` : `<tool_use_error>permission denied: ${verdict.reason ?? tu.name}</tool_use_error>`;
853
- return {
854
- block: {
855
- type: "tool_result",
856
- tool_use_id: tu.id,
857
- content,
858
- is_error: true
859
- },
860
- isError: true
861
- };
862
- }
863
- const callInput = verdict.updatedInput ?? effectiveInput;
864
- let block;
865
- let isError = false;
866
- let toolUseResult;
867
- try {
868
- const res = await tool.call(callInput, toolCallContext, undefined, assistantMsg);
869
- block = tool.mapToolResultToToolResultBlockParam(res.data, tu.id);
870
- toolUseResult = res.data;
871
- } catch (error) {
872
- const errorText = `tool error [${classifyToolError(error)}]: ${String(error?.message ?? error)}`;
873
- block = {
874
- type: "tool_result",
875
- tool_use_id: tu.id,
876
- content: `<tool_use_error>${errorText}</tool_use_error>`,
877
- is_error: true
878
- };
879
- isError = true;
880
- toolUseResult = errorText;
881
- }
882
- const postOutcome = await deps.hooks?.postToolUse?.(tool, callInput, block, tu.id);
883
- return { block, isError, toolUseResult };
884
- }
885
- var init_toolExecution = () => {};
886
-
887
- // src/engine/pipeline/toolOrchestration.ts
888
- function partitionToolCalls(toolUses, tools) {
889
- return toolUses.reduce((acc, tu) => {
890
- const tool = findTool(tools, tu.name);
891
- let safe = false;
892
- try {
893
- safe = tool ? tool.isConcurrencySafe(tu.input) : false;
894
- } catch {
895
- safe = false;
896
- }
897
- if (safe && acc[acc.length - 1]?.isConcurrencySafe) {
898
- acc[acc.length - 1].blocks.push(tu);
899
- } else {
900
- acc.push({ isConcurrencySafe: safe, blocks: [tu] });
901
- }
902
- return acc;
903
- }, []);
904
- }
905
- async function runToolBatch(toolUses, assistantMsg, deps) {
906
- const outcomes = [];
907
- for (const batch of partitionToolCalls(toolUses, deps.tools)) {
908
- for (const tu of batch.blocks) {
909
- const r = await executeToolUse(tu, assistantMsg, deps);
910
- outcomes.push({
911
- toolUseId: tu.id,
912
- name: tu.name,
913
- block: r.block,
914
- isError: r.isError,
915
- toolUseResult: r.toolUseResult
916
- });
917
- }
918
- }
919
- return outcomes;
920
- }
921
- var init_toolOrchestration = __esm(() => {
922
- init_toolExecution();
923
- });
924
-
925
- // src/engine/pipeline/index.ts
926
- var init_pipeline = __esm(() => {
927
- init_toolExecution();
928
- init_toolOrchestration();
929
- });
930
-
931
694
  // node_modules/openai/internal/tslib.mjs
932
695
  function __classPrivateFieldSet(receiver, state, value, kind, f) {
933
696
  if (kind === "m")
@@ -27094,12 +26857,14 @@ function startLlmWaitHeartbeat(modelId, attempt, totalRetries) {
27094
26857
  class OpenAIProvider {
27095
26858
  maxRetriesPerModel;
27096
26859
  timeoutMs;
27097
- constructor(maxRetriesPerModel = 3, timeoutMs = LLM_TIMEOUT_DEFAULT_MS) {
26860
+ timeoutResolver;
26861
+ constructor(maxRetriesPerModel = 3, timeoutMs = LLM_TIMEOUT_DEFAULT_MS, timeoutResolver) {
27098
26862
  this.maxRetriesPerModel = maxRetriesPerModel;
27099
26863
  this.timeoutMs = timeoutMs;
26864
+ this.timeoutResolver = timeoutResolver;
27100
26865
  }
27101
26866
  getTimeoutMs() {
27102
- return this.timeoutMs;
26867
+ return this.timeoutResolver ? this.timeoutResolver() : this.timeoutMs;
27103
26868
  }
27104
26869
  async chat(args) {
27105
26870
  const refs = getRoleModels(args.role, args.sessionModel, args.fallbackModel);
@@ -27139,7 +26904,7 @@ class OpenAIProvider {
27139
26904
  const stopWait = startLlmWaitHeartbeat(entry.modelId, attempt, this.maxRetriesPerModel);
27140
26905
  let res;
27141
26906
  try {
27142
- res = await client.chat.completions.create(params, { signal: args.signal, timeout: this.timeoutMs });
26907
+ res = await client.chat.completions.create(params, { signal: args.signal, timeout: this.getTimeoutMs() });
27143
26908
  } finally {
27144
26909
  stopWait();
27145
26910
  }
@@ -27256,7 +27021,7 @@ class OpenAIProvider {
27256
27021
  const stopWait = startLlmWaitHeartbeat(entry.modelId, attempt, totalRetries);
27257
27022
  let stream;
27258
27023
  try {
27259
- stream = await client.chat.completions.create({ ...params, stream: true, stream_options: { include_usage: true } }, { signal, timeout: this.timeoutMs });
27024
+ stream = await client.chat.completions.create({ ...params, stream: true, stream_options: { include_usage: true } }, { signal, timeout: this.getTimeoutMs() });
27260
27025
  } finally {
27261
27026
  stopWait();
27262
27027
  }
@@ -27444,7 +27209,7 @@ class OpenAIProvider {
27444
27209
  const wav = Buffer.concat([header, pcm]);
27445
27210
  const file = new File([wav], "audio.wav", { type: "audio/wav" });
27446
27211
  const client = getClientForEntry(entry);
27447
- const resp = await client.audio.transcriptions.create({ file, model, ...language ? { language } : {} }, { timeout: this.timeoutMs });
27212
+ const resp = await client.audio.transcriptions.create({ file, model, ...language ? { language } : {} }, { timeout: this.getTimeoutMs() });
27448
27213
  return resp?.text ?? "";
27449
27214
  }
27450
27215
  async synthesizeSpeech(text, model, voice) {
@@ -27458,7 +27223,7 @@ class OpenAIProvider {
27458
27223
  voice: voice || "alloy",
27459
27224
  input: text,
27460
27225
  response_format: "mp3"
27461
- }, { timeout: this.timeoutMs });
27226
+ }, { timeout: this.getTimeoutMs() });
27462
27227
  const buf = await resp.arrayBuffer();
27463
27228
  return Buffer.from(buf);
27464
27229
  }
@@ -27779,7 +27544,7 @@ var init_queryWithRoleFallback = __esm(() => {
27779
27544
 
27780
27545
  // src/modelprovider/index.ts
27781
27546
  function getModelProvider() {
27782
- return _provider ??= new OpenAIProvider(3, resolveLlmTimeoutMs(process.env.ATLAS_LLM_TIMEOUT, llmTimeoutSettingsSource?.()));
27547
+ return _provider ??= new OpenAIProvider(3, LLM_TIMEOUT_DEFAULT_MS, () => resolveLlmTimeoutMs(process.env.ATLAS_LLM_TIMEOUT, llmTimeoutSettingsSource?.()));
27783
27548
  }
27784
27549
  function setLlmTimeoutSettingsSource(next) {
27785
27550
  llmTimeoutSettingsSource = next;
@@ -27829,6 +27594,345 @@ var init_modelprovider2 = __esm(() => {
27829
27594
  modelProvider = lazyProxy(getModelProvider);
27830
27595
  });
27831
27596
 
27597
+ // src/engine/query/turnRecovery.ts
27598
+ function parseNonNegativeInt(value, fallback) {
27599
+ if (value === undefined || value === "")
27600
+ return fallback;
27601
+ const n = Number.parseInt(value, 10);
27602
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
27603
+ }
27604
+ function resolveTurnRecoveryConfig(env = process.env) {
27605
+ return {
27606
+ enabled: env.ATLAS_TURN_RECOVER_ENABLED !== "0",
27607
+ maxRetries: parseNonNegativeInt(env.ATLAS_TURN_RECOVER_MAX, DEFAULT_MAX_RETRIES),
27608
+ backoffBaseMs: parseNonNegativeInt(env.ATLAS_TURN_RECOVER_BACKOFF_MS, DEFAULT_BACKOFF_BASE_MS),
27609
+ backoffCapMs: parseNonNegativeInt(env.ATLAS_TURN_RECOVER_BACKOFF_CAP_MS, DEFAULT_BACKOFF_CAP_MS)
27610
+ };
27611
+ }
27612
+ function turnRecoveryBackoffMs(attempt, baseMs, capMs) {
27613
+ const base = Math.max(0, baseMs);
27614
+ const cap = Math.max(base, capMs);
27615
+ if (attempt <= 0)
27616
+ return Math.min(base, cap);
27617
+ return Math.min(base * 2 ** attempt, cap);
27618
+ }
27619
+ function sleepSignalAware(ms, signal) {
27620
+ if (ms <= 0)
27621
+ return Promise.resolve();
27622
+ return new Promise((resolve) => {
27623
+ if (signal?.aborted) {
27624
+ resolve();
27625
+ return;
27626
+ }
27627
+ let settled = false;
27628
+ const timer = setTimeout(() => {
27629
+ settled = true;
27630
+ if (signal)
27631
+ signal.removeEventListener("abort", onAbort);
27632
+ resolve();
27633
+ }, ms);
27634
+ function onAbort() {
27635
+ if (settled)
27636
+ return;
27637
+ settled = true;
27638
+ clearTimeout(timer);
27639
+ resolve();
27640
+ }
27641
+ if (signal)
27642
+ signal.addEventListener("abort", onAbort, { once: true });
27643
+ });
27644
+ }
27645
+ async function withTurnRecovery(attempt, options = {}) {
27646
+ const cfg = options.config ?? resolveTurnRecoveryConfig();
27647
+ if (!cfg.enabled) {
27648
+ return attempt();
27649
+ }
27650
+ let lastErr;
27651
+ for (let i = 0;i <= cfg.maxRetries; i++) {
27652
+ if (i > 0 && options.signal?.aborted) {
27653
+ throw options.signal.reason ?? new Error("aborted");
27654
+ }
27655
+ try {
27656
+ return await attempt();
27657
+ } catch (err) {
27658
+ lastErr = err;
27659
+ if (i < cfg.maxRetries && shouldRetryModelError(err, options.signal)) {
27660
+ const backoff = turnRecoveryBackoffMs(i, cfg.backoffBaseMs, cfg.backoffCapMs);
27661
+ if (backoff > 0) {
27662
+ await sleepSignalAware(backoff, options.signal);
27663
+ }
27664
+ continue;
27665
+ }
27666
+ throw err;
27667
+ }
27668
+ }
27669
+ throw lastErr;
27670
+ }
27671
+ var DEFAULT_MAX_RETRIES = 13, DEFAULT_BACKOFF_BASE_MS = 100, DEFAULT_BACKOFF_CAP_MS = 1000;
27672
+ var init_turnRecovery = __esm(() => {
27673
+ init_modelprovider2();
27674
+ });
27675
+
27676
+ // src/engine/pipeline/errorClassification.ts
27677
+ function classifyToolError(error) {
27678
+ if (error instanceof Error) {
27679
+ const errnoCode = error.code;
27680
+ if (typeof errnoCode === "string") {
27681
+ return `Error:${errnoCode}`;
27682
+ }
27683
+ if (error.name && error.name !== "Error" && error.name.length > 3) {
27684
+ return error.name.slice(0, 60);
27685
+ }
27686
+ return "Error";
27687
+ }
27688
+ return "UnknownError";
27689
+ }
27690
+
27691
+ // src/engine/pipeline/schemaValidation.ts
27692
+ function typeName(v) {
27693
+ if (v === null)
27694
+ return "null";
27695
+ if (Array.isArray(v))
27696
+ return "array";
27697
+ return typeof v;
27698
+ }
27699
+ function checkBasicType(value, type) {
27700
+ switch (type) {
27701
+ case "string":
27702
+ return typeof value === "string" ? null : `expected string, got ${typeName(value)}`;
27703
+ case "number":
27704
+ if (typeof value === "number") {
27705
+ return Number.isFinite(value) ? null : "expected number, got non-finite number";
27706
+ }
27707
+ return `expected number, got ${typeName(value)}`;
27708
+ case "integer":
27709
+ if (typeof value === "number") {
27710
+ return Number.isInteger(value) ? null : "expected integer, got non-integer number";
27711
+ }
27712
+ return `expected integer, got ${typeName(value)}`;
27713
+ case "boolean":
27714
+ return typeof value === "boolean" ? null : `expected boolean, got ${typeName(value)}`;
27715
+ case "array":
27716
+ return Array.isArray(value) ? null : `expected array, got ${typeName(value)}`;
27717
+ case "object":
27718
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? null : `expected object, got ${typeName(value)}`;
27719
+ case "null":
27720
+ return value === null ? null : `expected null, got ${typeName(value)}`;
27721
+ default:
27722
+ return null;
27723
+ }
27724
+ }
27725
+ function validateInputBySchema(input, schema) {
27726
+ if (!schema) {
27727
+ return { valid: true };
27728
+ }
27729
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
27730
+ return { valid: false, message: "expected object input" };
27731
+ }
27732
+ const record = input;
27733
+ const errors = [];
27734
+ const required = schema.required;
27735
+ if (Array.isArray(required)) {
27736
+ for (const rawKey of required) {
27737
+ const key = String(rawKey);
27738
+ if (!(key in record) || record[key] === undefined) {
27739
+ errors.push(`missing required field "${key}"`);
27740
+ }
27741
+ }
27742
+ }
27743
+ const properties = schema.properties;
27744
+ if (properties) {
27745
+ for (const [key, value] of Object.entries(record)) {
27746
+ if (value === undefined)
27747
+ continue;
27748
+ const propSchema = properties[key];
27749
+ if (!propSchema || typeof propSchema !== "object")
27750
+ continue;
27751
+ const type = propSchema.type;
27752
+ if (typeof type !== "string")
27753
+ continue;
27754
+ const err = checkBasicType(value, type);
27755
+ if (err)
27756
+ errors.push(`field "${key}": ${err}`);
27757
+ }
27758
+ }
27759
+ return errors.length > 0 ? { valid: false, message: errors.join("; ") } : { valid: true };
27760
+ }
27761
+ function buildSchemaNotSentHint(tool, discoveredToolNames) {
27762
+ if (!tool.shouldDefer)
27763
+ return null;
27764
+ if (discoveredToolNames.has(tool.name))
27765
+ return null;
27766
+ return `
27767
+
27768
+ This tool's schema was not sent to the API — it was not in the discovered-tool set derived from message history. ` + `Without the schema in your prompt, typed parameters (arrays, numbers, booleans) get emitted as strings and the client-side parser rejects them. ` + `Re-issue the call with correctly-typed parameters (arrays/numbers/booleans, not their string forms).`;
27769
+ }
27770
+
27771
+ // src/engine/pipeline/toolExecution.ts
27772
+ function mergeHookPermission(hookBehavior, gate) {
27773
+ if (hookBehavior === "deny") {
27774
+ return { allowed: false, reason: "blocked by hook" };
27775
+ }
27776
+ if (!gate.allowed)
27777
+ return gate;
27778
+ if (hookBehavior === "ask") {
27779
+ return { allowed: false, ask: true, reason: "hook requested confirmation" };
27780
+ }
27781
+ return gate;
27782
+ }
27783
+ function findTool(tools, name) {
27784
+ return tools.find((t) => t.name === name || (t.aliases?.includes(name) ?? false));
27785
+ }
27786
+ async function executeToolUse(tu, assistantMsg, deps) {
27787
+ const tool = findTool(deps.tools, tu.name);
27788
+ if (!tool) {
27789
+ return {
27790
+ block: {
27791
+ type: "tool_result",
27792
+ tool_use_id: tu.id,
27793
+ content: `unknown tool: ${tu.name}`,
27794
+ is_error: true
27795
+ },
27796
+ isError: true
27797
+ };
27798
+ }
27799
+ const toolCallContext = deps.toolContext !== undefined ? { ...deps.toolContext, signal: deps.signal, checkPermission: deps.checkPermission } : { signal: deps.signal, checkPermission: deps.checkPermission };
27800
+ const schemaResult = validateInputBySchema(tu.input, tool.inputSchema);
27801
+ if (schemaResult.valid === false) {
27802
+ const discovered = deps.discoveredToolNames ?? new Set(deps.tools.map((t) => t.name));
27803
+ const schemaHint = buildSchemaNotSentHint(tool, discovered);
27804
+ const errorContent = schemaHint ? `${schemaResult.message}${schemaHint}` : schemaResult.message;
27805
+ return {
27806
+ block: {
27807
+ type: "tool_result",
27808
+ tool_use_id: tu.id,
27809
+ content: `<tool_use_error>InputValidationError: ${errorContent}</tool_use_error>`,
27810
+ is_error: true
27811
+ },
27812
+ isError: true
27813
+ };
27814
+ }
27815
+ const validation = await tool.validateInput?.(tu.input, toolCallContext);
27816
+ if (validation && validation.result === false) {
27817
+ return {
27818
+ block: {
27819
+ type: "tool_result",
27820
+ tool_use_id: tu.id,
27821
+ content: `<tool_use_error>InputValidationError: ${validation.message}</tool_use_error>`,
27822
+ is_error: true
27823
+ },
27824
+ isError: true
27825
+ };
27826
+ }
27827
+ const preOutcome = await deps.hooks?.preToolUse?.(tool, tu.input, tu.id);
27828
+ if (preOutcome?.blockingError) {
27829
+ return {
27830
+ block: {
27831
+ type: "tool_result",
27832
+ tool_use_id: tu.id,
27833
+ content: `<tool_use_error>hook blocked: ${preOutcome.blockingError}</tool_use_error>`,
27834
+ is_error: true
27835
+ },
27836
+ isError: true
27837
+ };
27838
+ }
27839
+ if (preOutcome?.preventContinuation) {
27840
+ return {
27841
+ block: {
27842
+ type: "tool_result",
27843
+ tool_use_id: tu.id,
27844
+ content: `<tool_use_error>Execution stopped by PreToolUse hook${preOutcome.stopReason ? `: ${preOutcome.stopReason}` : ""}</tool_use_error>`,
27845
+ is_error: true
27846
+ },
27847
+ isError: true
27848
+ };
27849
+ }
27850
+ const effectiveInput = preOutcome?.updatedInput ?? tu.input;
27851
+ const gateVerdict = deps.checkPermission ? await deps.checkPermission(tool, effectiveInput, {
27852
+ toolUseId: tu.id,
27853
+ assistantMessage: assistantMsg
27854
+ }) : { allowed: true };
27855
+ const verdict = mergeHookPermission(preOutcome?.hookBehavior, gateVerdict);
27856
+ if (!verdict.allowed) {
27857
+ const content = verdict.ask ? `<tool_use_error>permission confirmation required (prompt 面残留守): ${verdict.reason ?? tu.name}</tool_use_error>` : `<tool_use_error>permission denied: ${verdict.reason ?? tu.name}</tool_use_error>`;
27858
+ return {
27859
+ block: {
27860
+ type: "tool_result",
27861
+ tool_use_id: tu.id,
27862
+ content,
27863
+ is_error: true
27864
+ },
27865
+ isError: true
27866
+ };
27867
+ }
27868
+ const callInput = verdict.updatedInput ?? effectiveInput;
27869
+ let block;
27870
+ let isError = false;
27871
+ let toolUseResult;
27872
+ try {
27873
+ const res = await tool.call(callInput, toolCallContext, undefined, assistantMsg);
27874
+ block = tool.mapToolResultToToolResultBlockParam(res.data, tu.id);
27875
+ toolUseResult = res.data;
27876
+ } catch (error) {
27877
+ const errorText = `tool error [${classifyToolError(error)}]: ${String(error?.message ?? error)}`;
27878
+ block = {
27879
+ type: "tool_result",
27880
+ tool_use_id: tu.id,
27881
+ content: `<tool_use_error>${errorText}</tool_use_error>`,
27882
+ is_error: true
27883
+ };
27884
+ isError = true;
27885
+ toolUseResult = errorText;
27886
+ }
27887
+ const postOutcome = await deps.hooks?.postToolUse?.(tool, callInput, block, tu.id);
27888
+ return { block, isError, toolUseResult };
27889
+ }
27890
+ var init_toolExecution = () => {};
27891
+
27892
+ // src/engine/pipeline/toolOrchestration.ts
27893
+ function partitionToolCalls(toolUses, tools) {
27894
+ return toolUses.reduce((acc, tu) => {
27895
+ const tool = findTool(tools, tu.name);
27896
+ let safe = false;
27897
+ try {
27898
+ safe = tool ? tool.isConcurrencySafe(tu.input) : false;
27899
+ } catch {
27900
+ safe = false;
27901
+ }
27902
+ if (safe && acc[acc.length - 1]?.isConcurrencySafe) {
27903
+ acc[acc.length - 1].blocks.push(tu);
27904
+ } else {
27905
+ acc.push({ isConcurrencySafe: safe, blocks: [tu] });
27906
+ }
27907
+ return acc;
27908
+ }, []);
27909
+ }
27910
+ async function runToolBatch(toolUses, assistantMsg, deps) {
27911
+ const outcomes = [];
27912
+ for (const batch of partitionToolCalls(toolUses, deps.tools)) {
27913
+ for (const tu of batch.blocks) {
27914
+ const r = await executeToolUse(tu, assistantMsg, deps);
27915
+ outcomes.push({
27916
+ toolUseId: tu.id,
27917
+ name: tu.name,
27918
+ block: r.block,
27919
+ isError: r.isError,
27920
+ toolUseResult: r.toolUseResult
27921
+ });
27922
+ }
27923
+ }
27924
+ return outcomes;
27925
+ }
27926
+ var init_toolOrchestration = __esm(() => {
27927
+ init_toolExecution();
27928
+ });
27929
+
27930
+ // src/engine/pipeline/index.ts
27931
+ var init_pipeline = __esm(() => {
27932
+ init_toolExecution();
27933
+ init_toolOrchestration();
27934
+ });
27935
+
27832
27936
  // node_modules/zod/index.js
27833
27937
  var init_zod = __esm(() => {
27834
27938
  init_external();
@@ -80612,7 +80716,9 @@ async function queryOneRound(deps, tools, messages) {
80612
80716
  fallbackModel: deps.fallbackModel,
80613
80717
  options: deps.effortValue !== undefined ? { effortValue: deps.effortValue } : undefined
80614
80718
  };
80615
- let resp = await deps.modelProvider.chat(chatArgs);
80719
+ let resp = await withTurnRecovery(() => deps.modelProvider.chat(chatArgs), {
80720
+ signal: deps.signal
80721
+ });
80616
80722
  let assistantContent = resp.message.content ?? [];
80617
80723
  const isEmptyContent = (content) => {
80618
80724
  if (!Array.isArray(content))
@@ -80627,7 +80733,9 @@ async function queryOneRound(deps, tools, messages) {
80627
80733
  });
80628
80734
  };
80629
80735
  if (isEmptyContent(assistantContent) && !deps.signal?.aborted) {
80630
- const retried = await deps.modelProvider.chat(chatArgs);
80736
+ const retried = await withTurnRecovery(() => deps.modelProvider.chat(chatArgs), {
80737
+ signal: deps.signal
80738
+ });
80631
80739
  const retriedContent = retried.message.content ?? [];
80632
80740
  if (!isEmptyContent(retriedContent)) {
80633
80741
  resp = retried;
@@ -80759,6 +80867,7 @@ async function queryAgentLoop(deps, args) {
80759
80867
  var DEFAULT_AGENT_LOOP_MAX_TURNS = 20;
80760
80868
  var init_loop = __esm(() => {
80761
80869
  init_shared();
80870
+ init_turnRecovery();
80762
80871
  init_pipeline();
80763
80872
  init_context();
80764
80873
  });
@@ -80779,6 +80888,7 @@ var init_QueryEngine = __esm(() => {
80779
80888
  // src/engine/query/index.ts
80780
80889
  var init_query2 = __esm(() => {
80781
80890
  init_loop();
80891
+ init_turnRecovery();
80782
80892
  init_QueryEngine();
80783
80893
  });
80784
80894
 
@@ -203103,7 +203213,7 @@ var init_drainRunLoop = __esm(() => {
203103
203213
 
203104
203214
  // src/tui/utils/computerUse/escHotkey.ts
203105
203215
  function registerEscHotkey(onEscape) {
203106
- if (registered)
203216
+ if (registered2)
203107
203217
  return true;
203108
203218
  const cu = requireComputerUseSwift();
203109
203219
  if (!cu.hotkey.registerEscape(onEscape)) {
@@ -203111,16 +203221,16 @@ function registerEscHotkey(onEscape) {
203111
203221
  return false;
203112
203222
  }
203113
203223
  retainPump();
203114
- registered = true;
203224
+ registered2 = true;
203115
203225
  logForDebugging3("[cu-esc] registered");
203116
203226
  return true;
203117
203227
  }
203118
203228
  function notifyExpectedEscape() {
203119
- if (!registered)
203229
+ if (!registered2)
203120
203230
  return;
203121
203231
  requireComputerUseSwift().hotkey.notifyExpectedEscape();
203122
203232
  }
203123
- var registered = false;
203233
+ var registered2 = false;
203124
203234
  var init_escHotkey = __esm(() => {
203125
203235
  init_debug();
203126
203236
  init_drainRunLoop();
@@ -272332,9 +272442,9 @@ function getDefaultMaxRetries() {
272332
272442
  if (process.env.ATLAS_MAX_RETRIES) {
272333
272443
  return parseInt(process.env.ATLAS_MAX_RETRIES || "", 10);
272334
272444
  }
272335
- return DEFAULT_MAX_RETRIES;
272445
+ return DEFAULT_MAX_RETRIES2;
272336
272446
  }
272337
- var DEFAULT_MAX_RETRIES = 10, BASE_DELAY_MS = 500, FOREGROUND_529_RETRY_SOURCES, PERSISTENT_MAX_BACKOFF_MS, PERSISTENT_RESET_CAP_MS;
272447
+ var DEFAULT_MAX_RETRIES2 = 10, BASE_DELAY_MS = 500, FOREGROUND_529_RETRY_SOURCES, PERSISTENT_MAX_BACKOFF_MS, PERSISTENT_RESET_CAP_MS;
272338
272448
  var init_withRetry = __esm(() => {
272339
272449
  init_shared();
272340
272450
  init_atlas();
@@ -490890,6 +491000,14 @@ function createCoreDependencies() {
490890
491000
  return;
490891
491001
  }
490892
491002
  });
491003
+ setLlmTimeoutSettingsSource(() => {
491004
+ try {
491005
+ const v = getInitialSettings().llmTimeoutMs;
491006
+ return typeof v === "number" ? v : undefined;
491007
+ } catch {
491008
+ return;
491009
+ }
491010
+ });
490893
491011
  return {
490894
491012
  sandboxManager,
490895
491013
  modelProvider: getModelProvider(),
@@ -493978,6 +494096,11 @@ async function binMain() {
493978
494096
  await main2();
493979
494097
  return;
493980
494098
  }
494099
+ registerGlobalCrashBackstop();
494100
+ if (process.env.ATLAS_TEST_CRASH_BACKSTOP === "1") {
494101
+ process.emit("uncaughtException", new Error("atlas crash-backstop live probe"));
494102
+ process.exit(0);
494103
+ }
493981
494104
  getCoreDependencies();
493982
494105
  await main();
493983
494106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlasharness/atlascode",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "AtlasCode \u2014 Coding Agent for the domestic computing ecosystem (Ascend NPU + domestic LLM backends)",
5
5
  "bin": {
6
6
  "atlas": "dist/cli.js",