@atlasharness/atlascode 0.1.89 → 0.1.91

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 +218 -10
  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
  });
@@ -30141,7 +30197,7 @@ var init_hooksSchema = __esm(() => {
30141
30197
  });
30142
30198
 
30143
30199
  // src/engine/config/types.ts
30144
- var SettingsSchema = () => object({
30200
+ var autoCompactWindowSettingSchema, SettingsSchema = () => object({
30145
30201
  model: string2().optional(),
30146
30202
  modelRoles: record(string2(), any()).optional(),
30147
30203
  defaultRole: string2().optional(),
@@ -30174,6 +30230,10 @@ var SettingsSchema = () => object({
30174
30230
  search: object({ tavilyApiKey: string2().optional() }).optional(),
30175
30231
  tui: object({ fullscreen: boolean2().optional() }).optional(),
30176
30232
  idleCompaction: boolean2().optional(),
30233
+ autoCompactWindow: union([
30234
+ autoCompactWindowSettingSchema,
30235
+ record(string2(), autoCompactWindowSettingSchema)
30236
+ ]).optional(),
30177
30237
  skillOverrides: union([
30178
30238
  literal("off"),
30179
30239
  literal("user-invocable-only"),
@@ -30183,6 +30243,12 @@ var SettingsSchema = () => object({
30183
30243
  var init_types3 = __esm(() => {
30184
30244
  init_zod();
30185
30245
  init_hooksSchema();
30246
+ autoCompactWindowSettingSchema = discriminatedUnion("kind", [
30247
+ object({ kind: literal("auto") }),
30248
+ object({ kind: literal("off") }),
30249
+ object({ kind: literal("window"), tokens: number2().int().min(1000) }),
30250
+ object({ kind: literal("pct"), pct: number2().min(1).max(100) })
30251
+ ]);
30186
30252
  });
30187
30253
 
30188
30254
  // src/engine/config/restrictedMode.ts
@@ -80574,6 +80640,14 @@ async function executeToolUse(tu, assistantMsg, deps) {
80574
80640
  toolUseResult = hookDenyText;
80575
80641
  supplemental = undefined;
80576
80642
  }
80643
+ if (deps.repeatToolTracker) {
80644
+ const reminder = deps.repeatToolTracker.observe(tu.name, callInput);
80645
+ if (reminder && block.is_error !== true) {
80646
+ const notice = createUserMessage({ content: reminder, isMeta: true });
80647
+ notice.sourceToolUseId = tu.id;
80648
+ supplemental = [...supplemental ?? [], notice];
80649
+ }
80650
+ }
80577
80651
  return {
80578
80652
  block,
80579
80653
  isError,
@@ -80629,10 +80703,73 @@ var init_toolOrchestration = __esm(() => {
80629
80703
  init_toolExecution();
80630
80704
  });
80631
80705
 
80706
+ // src/engine/pipeline/repeatToolReminder.ts
80707
+ function validateConfig(config) {
80708
+ if (!Array.isArray(config.thresholds) || config.thresholds.length === 0) {
80709
+ throw new Error("repeat-tool-reminder config: thresholds must be a non-empty array");
80710
+ }
80711
+ for (const t of config.thresholds) {
80712
+ if (!Number.isInteger(t) || t < 2) {
80713
+ throw new Error(`repeat-tool-reminder config: thresholds must be integers >= 2 (got ${String(t)})`);
80714
+ }
80715
+ }
80716
+ if (new Set(config.thresholds).size !== config.thresholds.length) {
80717
+ throw new Error(`repeat-tool-reminder config: thresholds must not contain duplicates (got ${config.thresholds.join(",")})`);
80718
+ }
80719
+ if (!Number.isInteger(config.argumentsPreviewChars) || config.argumentsPreviewChars <= 0) {
80720
+ throw new Error(`repeat-tool-reminder config: argumentsPreviewChars must be a positive integer (got ${String(config.argumentsPreviewChars)})`);
80721
+ }
80722
+ }
80723
+ function canonicalizeArguments(input) {
80724
+ const sortValue = (v) => {
80725
+ if (Array.isArray(v))
80726
+ return v.map(sortValue);
80727
+ if (typeof v === "object" && v !== null) {
80728
+ const rec = v;
80729
+ const keys = Object.keys(rec).sort();
80730
+ const out = {};
80731
+ for (const k of keys)
80732
+ out[k] = sortValue(rec[k]);
80733
+ return out;
80734
+ }
80735
+ return v;
80736
+ };
80737
+ return JSON.stringify(sortValue(input ?? null));
80738
+ }
80739
+ function createRepeatToolCallTracker(config) {
80740
+ const resolved = {
80741
+ thresholds: config?.thresholds ?? [...REPEAT_TOOL_REMINDER_THRESHOLDS],
80742
+ argumentsPreviewChars: config?.argumentsPreviewChars ?? REPEAT_TOOL_REMINDER_PREVIEW_CHARS
80743
+ };
80744
+ validateConfig(resolved);
80745
+ const chains = new Map;
80746
+ return {
80747
+ observe(toolName, input) {
80748
+ const key = `${toolName}\x00${canonicalizeArguments(input)}`;
80749
+ const count = (chains.get(key) ?? 0) + 1;
80750
+ chains.set(key, count);
80751
+ if (!resolved.thresholds.includes(count))
80752
+ return;
80753
+ if (count === resolved.thresholds[0]) {
80754
+ return REPEAT_TOOL_REMINDER_GENTLE.replaceAll("{tool}", toolName).replaceAll("{count}", String(count));
80755
+ }
80756
+ const preview = canonicalizeArguments(input).slice(0, resolved.argumentsPreviewChars);
80757
+ return REPEAT_TOOL_REMINDER_DETAILED.replaceAll("{tool}", toolName).replaceAll("{count}", String(count)).replaceAll("{preview}", preview);
80758
+ }
80759
+ };
80760
+ }
80761
+ var REPEAT_TOOL_REMINDER_THRESHOLDS, REPEAT_TOOL_REMINDER_PREVIEW_CHARS = 500, REPEAT_TOOL_REMINDER_GENTLE, REPEAT_TOOL_REMINDER_DETAILED;
80762
+ var init_repeatToolReminder = __esm(() => {
80763
+ REPEAT_TOOL_REMINDER_THRESHOLDS = [3, 5, 8];
80764
+ 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.";
80765
+ 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.";
80766
+ });
80767
+
80632
80768
  // src/engine/pipeline/index.ts
80633
80769
  var init_pipeline = __esm(() => {
80634
80770
  init_toolExecution();
80635
80771
  init_toolOrchestration();
80772
+ init_repeatToolReminder();
80636
80773
  });
80637
80774
 
80638
80775
  // src/engine/tools/toolsearch/toolSearchTool.ts
@@ -82747,6 +82884,56 @@ var init_compactPtl = __esm(() => {
82747
82884
  init_tools();
82748
82885
  });
82749
82886
 
82887
+ // src/engine/context/keepWindow.ts
82888
+ function collectToolUseIds(messages) {
82889
+ const ids = new Set;
82890
+ for (const m of messages) {
82891
+ if (m.type !== "assistant")
82892
+ continue;
82893
+ const content = m.message?.content;
82894
+ if (!Array.isArray(content))
82895
+ continue;
82896
+ for (const b of content) {
82897
+ if (b.type === "tool_use" && typeof b.id === "string")
82898
+ ids.add(b.id);
82899
+ }
82900
+ }
82901
+ return ids;
82902
+ }
82903
+ function hasOrphanToolResult(window2) {
82904
+ const ids = collectToolUseIds(window2);
82905
+ for (const m of window2) {
82906
+ if (m.type !== "user")
82907
+ continue;
82908
+ const content = m.message?.content;
82909
+ if (!Array.isArray(content))
82910
+ continue;
82911
+ for (const b of content) {
82912
+ if (b.type === "tool_result" && typeof b.tool_use_id === "string" && !ids.has(b.tool_use_id)) {
82913
+ return true;
82914
+ }
82915
+ }
82916
+ }
82917
+ return false;
82918
+ }
82919
+ function resolvePairedKeepWindow(messages, keepRecent) {
82920
+ if (keepRecent <= 0 || messages.length === 0) {
82921
+ return { window: undefined, revertedFrom: undefined };
82922
+ }
82923
+ let start = Math.max(0, messages.length - keepRecent);
82924
+ const originalStart = start;
82925
+ while (hasOrphanToolResult(messages.slice(start)) && start > 0) {
82926
+ start--;
82927
+ }
82928
+ if (start === 0 && hasOrphanToolResult(messages)) {
82929
+ return { window: undefined, revertedFrom: undefined, fellBackToFull: true };
82930
+ }
82931
+ return {
82932
+ window: messages.slice(start),
82933
+ revertedFrom: start !== originalStart ? originalStart : undefined
82934
+ };
82935
+ }
82936
+
82750
82937
  // src/engine/context/compactPorts.ts
82751
82938
  function setCompactPorts(next) {
82752
82939
  ports2 = next;
@@ -82886,7 +83073,14 @@ async function trimmedCompactConversation(messages, deps, customInstructions) {
82886
83073
  throw new Error("Failed to generate conversation summary - response did not contain valid text content");
82887
83074
  }
82888
83075
  const keepRecent = deps.keepRecent ?? 0;
82889
- const messagesToKeep = keepRecent > 0 ? messages.slice(-keepRecent) : undefined;
83076
+ const keepWindow = resolvePairedKeepWindow(messages, keepRecent);
83077
+ if (keepWindow.fellBackToFull) {
83078
+ 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" });
83079
+ }
83080
+ if (keepWindow.revertedFrom !== undefined) {
83081
+ 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" });
83082
+ }
83083
+ const messagesToKeep = keepWindow.window;
82890
83084
  const summaryMessages = [
82891
83085
  {
82892
83086
  type: "user",
@@ -83018,7 +83212,14 @@ async function richCompactConversation(messages, context, cacheSafeParams, suppr
83018
83212
  preCompactDiscoveredTools: [...preCompactDiscovered].sort()
83019
83213
  };
83020
83214
  }
83021
- const messagesToKeep = keepRecent > 0 ? messages.slice(-keepRecent) : undefined;
83215
+ const keepWindow = resolvePairedKeepWindow(messages, keepRecent);
83216
+ if (keepWindow.fellBackToFull) {
83217
+ 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" });
83218
+ }
83219
+ if (keepWindow.revertedFrom !== undefined) {
83220
+ 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" });
83221
+ }
83222
+ const messagesToKeep = keepWindow.window;
83022
83223
  const transcriptPath = getTranscriptPath();
83023
83224
  const summaryMessages = [
83024
83225
  {
@@ -84186,7 +84387,8 @@ async function queryOneRound(deps, tools, messages) {
84186
84387
  hooks: deps.hooks?.toolHooks,
84187
84388
  toolContext: deps.toolContext,
84188
84389
  onToolHeartbeat: deps.emit ? (info) => deps.emit({ type: "tool_heartbeat", ...info }) : undefined,
84189
- onMcpToolBackgrounded: deps.onMcpToolBackgrounded
84390
+ onMcpToolBackgrounded: deps.onMcpToolBackgrounded,
84391
+ repeatToolTracker: deps.repeatToolTracker
84190
84392
  });
84191
84393
  const deferredOutcome = outcomes.find((o) => o.deferred);
84192
84394
  const deferredPayload = deferredOutcome ? {
@@ -84240,6 +84442,7 @@ async function queryAgentLoop(deps, args) {
84240
84442
  const maxGoalTurns = args.context?.maxGoalTurns ?? DEFAULT_MAX_GOAL_TURNS;
84241
84443
  let goalTurns = 0;
84242
84444
  let deferredToolUse;
84445
+ deps = { ...deps, repeatToolTracker: createRepeatToolCallTracker() };
84243
84446
  if (deps.transcript) {
84244
84447
  await deps.transcript.record(messages);
84245
84448
  if (isCompactBoundaryMessage(messages[0])) {
@@ -84472,6 +84675,7 @@ var init_loop = __esm(() => {
84472
84675
  init_goalState();
84473
84676
  init_hooksConfig();
84474
84677
  init_pipeline();
84678
+ init_repeatToolReminder();
84475
84679
  init_context();
84476
84680
  init_reactiveCompactError();
84477
84681
  });
@@ -88852,7 +89056,8 @@ async function runHeadless(inputPrompt, options) {
88852
89056
  });
88853
89057
  return resp.message.content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("");
88854
89058
  },
88855
- countTokens: (msgs) => modelProvider.countTokens("small", undefined, msgs)
89059
+ countTokens: (msgs) => modelProvider.countTokens("small", undefined, msgs),
89060
+ keepRecent: AUTOCOMPACT_KEEP_RECENT
88856
89061
  };
88857
89062
  const loopDeps = {
88858
89063
  ...bundle.deps,
@@ -88860,8 +89065,11 @@ async function runHeadless(inputPrompt, options) {
88860
89065
  checkPermission,
88861
89066
  toolContext: makeCtx()
88862
89067
  };
89068
+ const mergedAutoCompact = mergeAutoCompactOverrides(getAutoCompactEnvOverrides(), getInitialSettings().autoCompactWindow, roleModel);
88863
89069
  const autoCompact = {
88864
89070
  contextWindow: (roleModel ? getProviderContextWindow(roleModel) : undefined) ?? 200000,
89071
+ countTokens: (msgs) => modelProvider.countTokens("small", undefined, msgs),
89072
+ enabled: mergedAutoCompact.autoCompactDisabled ? false : undefined,
88865
89073
  compact: (msgs) => compactConversation(msgs, compactDeps)
88866
89074
  };
88867
89075
  let turnRunning = false;
@@ -126042,7 +126250,7 @@ var init_internalWrites = __esm(() => {
126042
126250
  timestamps = new Map;
126043
126251
  });
126044
126252
  // src/tui/utils/settings/types.ts
126045
- var isMcpServerCommandEntry = () => ({}), isMcpServerNameEntry = () => ({}), isMcpServerUrlEntry = () => ({}), autoCompactWindowSettingSchema, SettingsSchema2 = () => object({
126253
+ var isMcpServerCommandEntry = () => ({}), isMcpServerNameEntry = () => ({}), isMcpServerUrlEntry = () => ({}), autoCompactWindowSettingSchema2, SettingsSchema2 = () => object({
126046
126254
  model: string2().optional(),
126047
126255
  modelRoles: record(string2(), any()).optional(),
126048
126256
  defaultRole: string2().optional(),
@@ -126086,8 +126294,8 @@ var isMcpServerCommandEntry = () => ({}), isMcpServerNameEntry = () => ({}), isM
126086
126294
  theme: string2().optional(),
126087
126295
  tui: object({ fullscreen: boolean2().optional() }).optional(),
126088
126296
  autoCompactWindow: union([
126089
- autoCompactWindowSettingSchema,
126090
- record(string2(), autoCompactWindowSettingSchema)
126297
+ autoCompactWindowSettingSchema2,
126298
+ record(string2(), autoCompactWindowSettingSchema2)
126091
126299
  ]).optional(),
126092
126300
  llmTimeoutMs: number2().int().positive().optional(),
126093
126301
  prefersReducedMotion: boolean2().optional(),
@@ -126132,7 +126340,7 @@ var isMcpServerCommandEntry = () => ({}), isMcpServerNameEntry = () => ({}), isM
126132
126340
  }).passthrough(), CUSTOMIZATION_SURFACES;
126133
126341
  var init_types7 = __esm(() => {
126134
126342
  init_zod();
126135
- autoCompactWindowSettingSchema = discriminatedUnion("kind", [
126343
+ autoCompactWindowSettingSchema2 = discriminatedUnion("kind", [
126136
126344
  object({ kind: literal("auto") }),
126137
126345
  object({ kind: literal("off") }),
126138
126346
  object({ kind: literal("window"), tokens: number2().int().min(1000) }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlasharness/atlascode",
3
- "version": "0.1.89",
3
+ "version": "0.1.91",
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": {