@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.
Files changed (2) hide show
  1. package/dist/cli.js +198 -4
  2. 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 messagesToKeep = keepRecent > 0 ? messages.slice(-keepRecent) : undefined;
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 messagesToKeep = keepRecent > 0 ? messages.slice(-keepRecent) : undefined;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlasharness/atlascode",
3
- "version": "0.1.89",
3
+ "version": "0.1.90",
4
4
  "description": "AtlasCode \u2014 Coding Agent for the domestic computing ecosystem (Ascend NPU + domestic LLM backends)",
5
5
  "homepage": "https://www.atlaswork.cn/",
6
6
  "bin": {