@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.
- package/dist/cli.js +398 -275
- 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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
203224
|
+
registered2 = true;
|
|
203115
203225
|
logForDebugging3("[cu-esc] registered");
|
|
203116
203226
|
return true;
|
|
203117
203227
|
}
|
|
203118
203228
|
function notifyExpectedEscape() {
|
|
203119
|
-
if (!
|
|
203229
|
+
if (!registered2)
|
|
203120
203230
|
return;
|
|
203121
203231
|
requireComputerUseSwift().hotkey.notifyExpectedEscape();
|
|
203122
203232
|
}
|
|
203123
|
-
var
|
|
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
|
|
272445
|
+
return DEFAULT_MAX_RETRIES2;
|
|
272336
272446
|
}
|
|
272337
|
-
var
|
|
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