@atlasharness/atlascode 0.1.89 → 0.1.90
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 +198 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20659,6 +20659,61 @@ var init_attachmentProjection = __esm(() => {
|
|
|
20659
20659
|
]);
|
|
20660
20660
|
});
|
|
20661
20661
|
|
|
20662
|
+
// src/modelprovider/toolPairing.ts
|
|
20663
|
+
function ensureToolResultPairing(messages) {
|
|
20664
|
+
const knownCallIds = new Set;
|
|
20665
|
+
for (const m of messages) {
|
|
20666
|
+
if (m?.role === "assistant" && Array.isArray(m.tool_calls)) {
|
|
20667
|
+
for (const c of m.tool_calls) {
|
|
20668
|
+
if (typeof c?.id === "string")
|
|
20669
|
+
knownCallIds.add(c.id);
|
|
20670
|
+
}
|
|
20671
|
+
}
|
|
20672
|
+
}
|
|
20673
|
+
const strippedOrphanToolCallIds = [];
|
|
20674
|
+
const kept = [];
|
|
20675
|
+
for (const m of messages) {
|
|
20676
|
+
if (m?.role === "tool") {
|
|
20677
|
+
const id = m.tool_call_id;
|
|
20678
|
+
if (typeof id !== "string" || !knownCallIds.has(id)) {
|
|
20679
|
+
strippedOrphanToolCallIds.push(typeof id === "string" ? id : "");
|
|
20680
|
+
continue;
|
|
20681
|
+
}
|
|
20682
|
+
}
|
|
20683
|
+
kept.push(m);
|
|
20684
|
+
}
|
|
20685
|
+
const syntheticToolCallIds = [];
|
|
20686
|
+
const out = [];
|
|
20687
|
+
for (let i = 0;i < kept.length; i++) {
|
|
20688
|
+
const m = kept[i];
|
|
20689
|
+
out.push(m);
|
|
20690
|
+
if (m?.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
|
|
20691
|
+
const covered = new Set;
|
|
20692
|
+
for (let j = i + 1;j < kept.length; j++) {
|
|
20693
|
+
if (kept[j]?.role === "assistant")
|
|
20694
|
+
break;
|
|
20695
|
+
if (kept[j]?.role === "tool" && typeof kept[j].tool_call_id === "string") {
|
|
20696
|
+
covered.add(kept[j].tool_call_id);
|
|
20697
|
+
}
|
|
20698
|
+
}
|
|
20699
|
+
const missing = m.tool_calls.filter((c) => typeof c?.id === "string" && !covered.has(c.id));
|
|
20700
|
+
for (const c of missing) {
|
|
20701
|
+
out.push({ role: "tool", tool_call_id: c.id, content: SYNTHETIC_TOOL_RESULT_CONTENT });
|
|
20702
|
+
syntheticToolCallIds.push(c.id);
|
|
20703
|
+
}
|
|
20704
|
+
}
|
|
20705
|
+
}
|
|
20706
|
+
if (syntheticToolCallIds.length > 0 || strippedOrphanToolCallIds.length > 0) {
|
|
20707
|
+
logForDebugging(`ensureToolResultPairing repaired wire at projection exit: +${syntheticToolCallIds.length} synthetic result(s) [${syntheticToolCallIds.join(",")}] -${strippedOrphanToolCallIds.length} orphan result(s) [${strippedOrphanToolCallIds.join(",")}]`, { level: "warn" });
|
|
20708
|
+
return { messages: out, syntheticToolCallIds, strippedOrphanToolCallIds };
|
|
20709
|
+
}
|
|
20710
|
+
return { messages, syntheticToolCallIds, strippedOrphanToolCallIds };
|
|
20711
|
+
}
|
|
20712
|
+
var SYNTHETIC_TOOL_RESULT_CONTENT = "[tool result missing from conversation history — synthesized to preserve tool_calls/tool_result pairing. The original result was lost to compaction, resume truncation, or a malformed transcript; treat the tool as having failed and proceed.]";
|
|
20713
|
+
var init_toolPairing = __esm(() => {
|
|
20714
|
+
init_shared();
|
|
20715
|
+
});
|
|
20716
|
+
|
|
20662
20717
|
// node_modules/zod/v4/core/util.js
|
|
20663
20718
|
function getEnumValues(entries) {
|
|
20664
20719
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
@@ -27445,7 +27500,7 @@ function toOpenAIMessages(messages) {
|
|
|
27445
27500
|
}
|
|
27446
27501
|
}
|
|
27447
27502
|
}
|
|
27448
|
-
return out;
|
|
27503
|
+
return ensureToolResultPairing(out).messages;
|
|
27449
27504
|
}
|
|
27450
27505
|
function isZodV4Instance(x) {
|
|
27451
27506
|
return typeof x === "object" && x !== null && typeof x._zod === "object";
|
|
@@ -27566,6 +27621,7 @@ var init_params = __esm(() => {
|
|
|
27566
27621
|
init_roles7();
|
|
27567
27622
|
init_shared();
|
|
27568
27623
|
init_attachmentProjection();
|
|
27624
|
+
init_toolPairing();
|
|
27569
27625
|
init_schema();
|
|
27570
27626
|
init_effort();
|
|
27571
27627
|
});
|
|
@@ -80574,6 +80630,14 @@ async function executeToolUse(tu, assistantMsg, deps) {
|
|
|
80574
80630
|
toolUseResult = hookDenyText;
|
|
80575
80631
|
supplemental = undefined;
|
|
80576
80632
|
}
|
|
80633
|
+
if (deps.repeatToolTracker) {
|
|
80634
|
+
const reminder = deps.repeatToolTracker.observe(tu.name, callInput);
|
|
80635
|
+
if (reminder && block.is_error !== true) {
|
|
80636
|
+
const notice = createUserMessage({ content: reminder, isMeta: true });
|
|
80637
|
+
notice.sourceToolUseId = tu.id;
|
|
80638
|
+
supplemental = [...supplemental ?? [], notice];
|
|
80639
|
+
}
|
|
80640
|
+
}
|
|
80577
80641
|
return {
|
|
80578
80642
|
block,
|
|
80579
80643
|
isError,
|
|
@@ -80629,10 +80693,73 @@ var init_toolOrchestration = __esm(() => {
|
|
|
80629
80693
|
init_toolExecution();
|
|
80630
80694
|
});
|
|
80631
80695
|
|
|
80696
|
+
// src/engine/pipeline/repeatToolReminder.ts
|
|
80697
|
+
function validateConfig(config) {
|
|
80698
|
+
if (!Array.isArray(config.thresholds) || config.thresholds.length === 0) {
|
|
80699
|
+
throw new Error("repeat-tool-reminder config: thresholds must be a non-empty array");
|
|
80700
|
+
}
|
|
80701
|
+
for (const t of config.thresholds) {
|
|
80702
|
+
if (!Number.isInteger(t) || t < 2) {
|
|
80703
|
+
throw new Error(`repeat-tool-reminder config: thresholds must be integers >= 2 (got ${String(t)})`);
|
|
80704
|
+
}
|
|
80705
|
+
}
|
|
80706
|
+
if (new Set(config.thresholds).size !== config.thresholds.length) {
|
|
80707
|
+
throw new Error(`repeat-tool-reminder config: thresholds must not contain duplicates (got ${config.thresholds.join(",")})`);
|
|
80708
|
+
}
|
|
80709
|
+
if (!Number.isInteger(config.argumentsPreviewChars) || config.argumentsPreviewChars <= 0) {
|
|
80710
|
+
throw new Error(`repeat-tool-reminder config: argumentsPreviewChars must be a positive integer (got ${String(config.argumentsPreviewChars)})`);
|
|
80711
|
+
}
|
|
80712
|
+
}
|
|
80713
|
+
function canonicalizeArguments(input) {
|
|
80714
|
+
const sortValue = (v) => {
|
|
80715
|
+
if (Array.isArray(v))
|
|
80716
|
+
return v.map(sortValue);
|
|
80717
|
+
if (typeof v === "object" && v !== null) {
|
|
80718
|
+
const rec = v;
|
|
80719
|
+
const keys = Object.keys(rec).sort();
|
|
80720
|
+
const out = {};
|
|
80721
|
+
for (const k of keys)
|
|
80722
|
+
out[k] = sortValue(rec[k]);
|
|
80723
|
+
return out;
|
|
80724
|
+
}
|
|
80725
|
+
return v;
|
|
80726
|
+
};
|
|
80727
|
+
return JSON.stringify(sortValue(input ?? null));
|
|
80728
|
+
}
|
|
80729
|
+
function createRepeatToolCallTracker(config) {
|
|
80730
|
+
const resolved = {
|
|
80731
|
+
thresholds: config?.thresholds ?? [...REPEAT_TOOL_REMINDER_THRESHOLDS],
|
|
80732
|
+
argumentsPreviewChars: config?.argumentsPreviewChars ?? REPEAT_TOOL_REMINDER_PREVIEW_CHARS
|
|
80733
|
+
};
|
|
80734
|
+
validateConfig(resolved);
|
|
80735
|
+
const chains = new Map;
|
|
80736
|
+
return {
|
|
80737
|
+
observe(toolName, input) {
|
|
80738
|
+
const key = `${toolName}\x00${canonicalizeArguments(input)}`;
|
|
80739
|
+
const count = (chains.get(key) ?? 0) + 1;
|
|
80740
|
+
chains.set(key, count);
|
|
80741
|
+
if (!resolved.thresholds.includes(count))
|
|
80742
|
+
return;
|
|
80743
|
+
if (count === resolved.thresholds[0]) {
|
|
80744
|
+
return REPEAT_TOOL_REMINDER_GENTLE.replaceAll("{tool}", toolName).replaceAll("{count}", String(count));
|
|
80745
|
+
}
|
|
80746
|
+
const preview = canonicalizeArguments(input).slice(0, resolved.argumentsPreviewChars);
|
|
80747
|
+
return REPEAT_TOOL_REMINDER_DETAILED.replaceAll("{tool}", toolName).replaceAll("{count}", String(count)).replaceAll("{preview}", preview);
|
|
80748
|
+
}
|
|
80749
|
+
};
|
|
80750
|
+
}
|
|
80751
|
+
var REPEAT_TOOL_REMINDER_THRESHOLDS, REPEAT_TOOL_REMINDER_PREVIEW_CHARS = 500, REPEAT_TOOL_REMINDER_GENTLE, REPEAT_TOOL_REMINDER_DETAILED;
|
|
80752
|
+
var init_repeatToolReminder = __esm(() => {
|
|
80753
|
+
REPEAT_TOOL_REMINDER_THRESHOLDS = [3, 5, 8];
|
|
80754
|
+
REPEAT_TOOL_REMINDER_GENTLE = "[repeat-tool-notice] You have now called {tool} {count} times in a row with identical arguments. " + "If this repetition is intentional (e.g. polling a changing value), continue; " + "otherwise adjust the arguments or try a different approach.";
|
|
80755
|
+
REPEAT_TOOL_REMINDER_DETAILED = "[repeat-tool-notice] You have now called {tool} {count} times in a row with identical arguments " + "(arguments preview: {preview}). Repeating the same call is unlikely to produce a different result — " + "change the arguments, use a different tool, or explain in your reply why another identical call is needed.";
|
|
80756
|
+
});
|
|
80757
|
+
|
|
80632
80758
|
// src/engine/pipeline/index.ts
|
|
80633
80759
|
var init_pipeline = __esm(() => {
|
|
80634
80760
|
init_toolExecution();
|
|
80635
80761
|
init_toolOrchestration();
|
|
80762
|
+
init_repeatToolReminder();
|
|
80636
80763
|
});
|
|
80637
80764
|
|
|
80638
80765
|
// src/engine/tools/toolsearch/toolSearchTool.ts
|
|
@@ -82747,6 +82874,56 @@ var init_compactPtl = __esm(() => {
|
|
|
82747
82874
|
init_tools();
|
|
82748
82875
|
});
|
|
82749
82876
|
|
|
82877
|
+
// src/engine/context/keepWindow.ts
|
|
82878
|
+
function collectToolUseIds(messages) {
|
|
82879
|
+
const ids = new Set;
|
|
82880
|
+
for (const m of messages) {
|
|
82881
|
+
if (m.type !== "assistant")
|
|
82882
|
+
continue;
|
|
82883
|
+
const content = m.message?.content;
|
|
82884
|
+
if (!Array.isArray(content))
|
|
82885
|
+
continue;
|
|
82886
|
+
for (const b of content) {
|
|
82887
|
+
if (b.type === "tool_use" && typeof b.id === "string")
|
|
82888
|
+
ids.add(b.id);
|
|
82889
|
+
}
|
|
82890
|
+
}
|
|
82891
|
+
return ids;
|
|
82892
|
+
}
|
|
82893
|
+
function hasOrphanToolResult(window2) {
|
|
82894
|
+
const ids = collectToolUseIds(window2);
|
|
82895
|
+
for (const m of window2) {
|
|
82896
|
+
if (m.type !== "user")
|
|
82897
|
+
continue;
|
|
82898
|
+
const content = m.message?.content;
|
|
82899
|
+
if (!Array.isArray(content))
|
|
82900
|
+
continue;
|
|
82901
|
+
for (const b of content) {
|
|
82902
|
+
if (b.type === "tool_result" && typeof b.tool_use_id === "string" && !ids.has(b.tool_use_id)) {
|
|
82903
|
+
return true;
|
|
82904
|
+
}
|
|
82905
|
+
}
|
|
82906
|
+
}
|
|
82907
|
+
return false;
|
|
82908
|
+
}
|
|
82909
|
+
function resolvePairedKeepWindow(messages, keepRecent) {
|
|
82910
|
+
if (keepRecent <= 0 || messages.length === 0) {
|
|
82911
|
+
return { window: undefined, revertedFrom: undefined };
|
|
82912
|
+
}
|
|
82913
|
+
let start = Math.max(0, messages.length - keepRecent);
|
|
82914
|
+
const originalStart = start;
|
|
82915
|
+
while (hasOrphanToolResult(messages.slice(start)) && start > 0) {
|
|
82916
|
+
start--;
|
|
82917
|
+
}
|
|
82918
|
+
if (start === 0 && hasOrphanToolResult(messages)) {
|
|
82919
|
+
return { window: undefined, revertedFrom: undefined, fellBackToFull: true };
|
|
82920
|
+
}
|
|
82921
|
+
return {
|
|
82922
|
+
window: messages.slice(start),
|
|
82923
|
+
revertedFrom: start !== originalStart ? originalStart : undefined
|
|
82924
|
+
};
|
|
82925
|
+
}
|
|
82926
|
+
|
|
82750
82927
|
// src/engine/context/compactPorts.ts
|
|
82751
82928
|
function setCompactPorts(next) {
|
|
82752
82929
|
ports2 = next;
|
|
@@ -82886,7 +83063,14 @@ async function trimmedCompactConversation(messages, deps, customInstructions) {
|
|
|
82886
83063
|
throw new Error("Failed to generate conversation summary - response did not contain valid text content");
|
|
82887
83064
|
}
|
|
82888
83065
|
const keepRecent = deps.keepRecent ?? 0;
|
|
82889
|
-
const
|
|
83066
|
+
const keepWindow = resolvePairedKeepWindow(messages, keepRecent);
|
|
83067
|
+
if (keepWindow.fellBackToFull) {
|
|
83068
|
+
logForDebugging("compaction keepRecent cut fell back to full compaction: whole sequence is malformed (orphan tool_result at sequence head, tool_use unresolvable) — messagesToKeep=undefined per pre-registered §5.5 row F", { level: "warn" });
|
|
83069
|
+
}
|
|
83070
|
+
if (keepWindow.revertedFrom !== undefined) {
|
|
83071
|
+
logForDebugging(`compaction keepRecent cut reverted to assistant round boundary: slice start ${keepWindow.revertedFrom} → ${messages.length - keepWindow.window.length} (kept ${keepWindow.window.length} messages)`, { level: "info" });
|
|
83072
|
+
}
|
|
83073
|
+
const messagesToKeep = keepWindow.window;
|
|
82890
83074
|
const summaryMessages = [
|
|
82891
83075
|
{
|
|
82892
83076
|
type: "user",
|
|
@@ -83018,7 +83202,14 @@ async function richCompactConversation(messages, context, cacheSafeParams, suppr
|
|
|
83018
83202
|
preCompactDiscoveredTools: [...preCompactDiscovered].sort()
|
|
83019
83203
|
};
|
|
83020
83204
|
}
|
|
83021
|
-
const
|
|
83205
|
+
const keepWindow = resolvePairedKeepWindow(messages, keepRecent);
|
|
83206
|
+
if (keepWindow.fellBackToFull) {
|
|
83207
|
+
logForDebugging("compaction keepRecent cut fell back to full compaction: whole sequence is malformed (orphan tool_result at sequence head, tool_use unresolvable) — messagesToKeep=undefined per pre-registered §5.5 row F", { level: "warn" });
|
|
83208
|
+
}
|
|
83209
|
+
if (keepWindow.revertedFrom !== undefined) {
|
|
83210
|
+
logForDebugging(`compaction keepRecent cut reverted to assistant round boundary: slice start ${keepWindow.revertedFrom} → ${messages.length - keepWindow.window.length} (kept ${keepWindow.window.length} messages)`, { level: "info" });
|
|
83211
|
+
}
|
|
83212
|
+
const messagesToKeep = keepWindow.window;
|
|
83022
83213
|
const transcriptPath = getTranscriptPath();
|
|
83023
83214
|
const summaryMessages = [
|
|
83024
83215
|
{
|
|
@@ -84186,7 +84377,8 @@ async function queryOneRound(deps, tools, messages) {
|
|
|
84186
84377
|
hooks: deps.hooks?.toolHooks,
|
|
84187
84378
|
toolContext: deps.toolContext,
|
|
84188
84379
|
onToolHeartbeat: deps.emit ? (info) => deps.emit({ type: "tool_heartbeat", ...info }) : undefined,
|
|
84189
|
-
onMcpToolBackgrounded: deps.onMcpToolBackgrounded
|
|
84380
|
+
onMcpToolBackgrounded: deps.onMcpToolBackgrounded,
|
|
84381
|
+
repeatToolTracker: deps.repeatToolTracker
|
|
84190
84382
|
});
|
|
84191
84383
|
const deferredOutcome = outcomes.find((o) => o.deferred);
|
|
84192
84384
|
const deferredPayload = deferredOutcome ? {
|
|
@@ -84240,6 +84432,7 @@ async function queryAgentLoop(deps, args) {
|
|
|
84240
84432
|
const maxGoalTurns = args.context?.maxGoalTurns ?? DEFAULT_MAX_GOAL_TURNS;
|
|
84241
84433
|
let goalTurns = 0;
|
|
84242
84434
|
let deferredToolUse;
|
|
84435
|
+
deps = { ...deps, repeatToolTracker: createRepeatToolCallTracker() };
|
|
84243
84436
|
if (deps.transcript) {
|
|
84244
84437
|
await deps.transcript.record(messages);
|
|
84245
84438
|
if (isCompactBoundaryMessage(messages[0])) {
|
|
@@ -84472,6 +84665,7 @@ var init_loop = __esm(() => {
|
|
|
84472
84665
|
init_goalState();
|
|
84473
84666
|
init_hooksConfig();
|
|
84474
84667
|
init_pipeline();
|
|
84668
|
+
init_repeatToolReminder();
|
|
84475
84669
|
init_context();
|
|
84476
84670
|
init_reactiveCompactError();
|
|
84477
84671
|
});
|
package/package.json
CHANGED