@miller-tech/uap 1.220.9 → 1.220.11

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.
@@ -553,16 +553,79 @@ export interface ReadCache {
553
553
  * parsed back out of the key — a key is now lossy (it may carry an offset
554
554
  * suffix), and a path recovered by string-slicing a key is how the grounding
555
555
  * builder would silently start reading files that do not exist.
556
+ *
557
+ * `offset` is the 1-based line the model last read this window from. It is
558
+ * carried for the same reason `path` is: the key is lossy, and the grounding
559
+ * builder needs to know WHICH part of a long file the model was working in.
560
+ * Without it the builder can only clip from byte 0 — see
561
+ * buildForcedRoundGrounding.
556
562
  */
557
563
  seen: Map<string, {
558
564
  mtimeMs: number;
559
565
  round: number;
560
566
  path?: string;
567
+ offset?: number;
568
+ seq?: number;
561
569
  }>;
570
+ /**
571
+ * Monotonic counter stamped onto each entry as `seq`.
572
+ *
573
+ * `round` cannot order these on its own: one round issues many tool calls
574
+ * and they all share its number, and an entry updated in place keeps its
575
+ * original Map position, so insertion order stops being chronological the
576
+ * first time a window is re-read. Both cases sent the grounding anchor to
577
+ * the WRONG window — measured, both landed back on the head clip this
578
+ * whole mechanism exists to avoid.
579
+ */
580
+ nextSeq?: number;
562
581
  }
563
582
  /** Bounds for forced-round grounding — see buildForcedRoundGrounding. */
564
583
  export declare const FORCED_GROUNDING_MAX_FILES = 3;
565
584
  export declare const FORCED_GROUNDING_MAX_BYTES = 24000;
585
+ /**
586
+ * Below this, a per-file share buys so few lines that the block is noise —
587
+ * a mid-word fragment carrying a confident line-range label. Skip the file
588
+ * instead; grounding two files well beats grounding three badly.
589
+ */
590
+ export declare const FORCED_GROUNDING_MIN_WINDOW_BYTES = 400;
591
+ /**
592
+ * Clip `body` to `maxBytes`, keeping the region around `anchorLine` rather than
593
+ * the head of the file.
594
+ *
595
+ * A head-anchored clip is what made the forced round useless on any file longer
596
+ * than the budget: the model pages toward the region it needs, the read cap
597
+ * strips its read tools, and the grounding then hands it bytes 0..budget —
598
+ * which is the one part of the file it had already moved past. Measured
599
+ * 2026-08-25 on a 952-line, 38 019-byte Rust file: the 24 000-byte head clip
600
+ * ended at line 615, the region under review was 725-732, and the run reported
601
+ * "the file is truncated before line 725; I cannot verify" ten times over forty
602
+ * minutes before giving up.
603
+ *
604
+ * Grows forward from the anchor FIRST (the model reads downward), then spends
605
+ * whatever is left backward toward the head for lead-in context.
606
+ *
607
+ * The order is load-bearing. An earlier version reserved the lead-in before
608
+ * growing forward, which charged the budget for lines above the anchor before
609
+ * it had paid for the anchor itself: on a tight budget the window stopped
610
+ * short and the anchor line was not in its own window — 88% of clipped results
611
+ * in a fuzz sweep. The block then carried a confident "centred on line N"
612
+ * label for an N it did not contain, which is worse than the bare
613
+ * "(truncated)" it replaced. Growing from the anchor makes its presence an
614
+ * invariant whenever any line fits at all.
615
+ *
616
+ * `anchor` is the CLAMPED line the window is actually built around. Callers
617
+ * must label with this, not with the raw request — a past-EOF offset otherwise
618
+ * renders as "your last read at line 5000" on a 952-line file.
619
+ */
620
+ export declare function clipAroundAnchor(body: string, maxBytes: number, anchorLine?: number): {
621
+ text: string;
622
+ from: number;
623
+ to: number;
624
+ total: number;
625
+ anchor: number;
626
+ clipped: boolean;
627
+ partialLine: boolean;
628
+ };
566
629
  /**
567
630
  * Build the grounding block for a forced-write round.
568
631
  *
@@ -1 +1 @@
1
- {"version":3,"file":"agentic-executor.d.ts","sourceRoot":"","sources":["../../src/delivery/agentic-executor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agentic-executor.d.ts","sourceRoot":"","sources":["../../src/delivery/agentic-executor.ts"],"names":[],"mappings":"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"}
@@ -1101,6 +1101,110 @@ function restoreProtected(snap) {
1101
1101
  /** Bounds for forced-round grounding — see buildForcedRoundGrounding. */
1102
1102
  export const FORCED_GROUNDING_MAX_FILES = 3;
1103
1103
  export const FORCED_GROUNDING_MAX_BYTES = 24_000;
1104
+ /**
1105
+ * Below this, a per-file share buys so few lines that the block is noise —
1106
+ * a mid-word fragment carrying a confident line-range label. Skip the file
1107
+ * instead; grounding two files well beats grounding three badly.
1108
+ */
1109
+ export const FORCED_GROUNDING_MIN_WINDOW_BYTES = 400;
1110
+ /** Recency order for read-cache entries — see ReadCache.nextSeq. */
1111
+ function readEntryRank(m) {
1112
+ return [m.seq ?? 0, m.round];
1113
+ }
1114
+ function isMoreRecent(a, b) {
1115
+ const [as, ar] = readEntryRank(a);
1116
+ const [bs, br] = readEntryRank(b);
1117
+ // `>=` on the tie-break, not `>`: entries hand-built without a seq (tests,
1118
+ // and any caller that fills the map directly) fall back to round alone, and
1119
+ // within one round the LATER call is the one the model made last.
1120
+ return as !== bs ? as > bs : ar >= br;
1121
+ }
1122
+ /**
1123
+ * Clip `body` to `maxBytes`, keeping the region around `anchorLine` rather than
1124
+ * the head of the file.
1125
+ *
1126
+ * A head-anchored clip is what made the forced round useless on any file longer
1127
+ * than the budget: the model pages toward the region it needs, the read cap
1128
+ * strips its read tools, and the grounding then hands it bytes 0..budget —
1129
+ * which is the one part of the file it had already moved past. Measured
1130
+ * 2026-08-25 on a 952-line, 38 019-byte Rust file: the 24 000-byte head clip
1131
+ * ended at line 615, the region under review was 725-732, and the run reported
1132
+ * "the file is truncated before line 725; I cannot verify" ten times over forty
1133
+ * minutes before giving up.
1134
+ *
1135
+ * Grows forward from the anchor FIRST (the model reads downward), then spends
1136
+ * whatever is left backward toward the head for lead-in context.
1137
+ *
1138
+ * The order is load-bearing. An earlier version reserved the lead-in before
1139
+ * growing forward, which charged the budget for lines above the anchor before
1140
+ * it had paid for the anchor itself: on a tight budget the window stopped
1141
+ * short and the anchor line was not in its own window — 88% of clipped results
1142
+ * in a fuzz sweep. The block then carried a confident "centred on line N"
1143
+ * label for an N it did not contain, which is worse than the bare
1144
+ * "(truncated)" it replaced. Growing from the anchor makes its presence an
1145
+ * invariant whenever any line fits at all.
1146
+ *
1147
+ * `anchor` is the CLAMPED line the window is actually built around. Callers
1148
+ * must label with this, not with the raw request — a past-EOF offset otherwise
1149
+ * renders as "your last read at line 5000" on a 952-line file.
1150
+ */
1151
+ export function clipAroundAnchor(body, maxBytes, anchorLine = 1) {
1152
+ const lines = body.split('\n');
1153
+ // A newline-terminated file — which is nearly every source file — splits to a
1154
+ // phantom trailing ''. Counting it inflates `total` and lets `to` name a line
1155
+ // that does not exist, desyncing these numbers from the ones read_file shows
1156
+ // the model. The whole point of the label is that the two agree.
1157
+ if (lines.length > 1 && lines[lines.length - 1] === '')
1158
+ lines.pop();
1159
+ const total = lines.length;
1160
+ const requested = Number.isFinite(anchorLine) ? Math.trunc(anchorLine) : 1;
1161
+ const anchorIdx = Math.max(0, Math.min(total - 1, requested - 1));
1162
+ const anchor = anchorIdx + 1;
1163
+ if (body.length <= maxBytes) {
1164
+ return { text: body, from: 1, to: total, total, anchor, clipped: false, partialLine: false };
1165
+ }
1166
+ let end = anchorIdx; // exclusive
1167
+ let used = 0;
1168
+ while (end < total) {
1169
+ const cost = lines[end].length + 1;
1170
+ if (used + cost > maxBytes)
1171
+ break;
1172
+ used += cost;
1173
+ end += 1;
1174
+ }
1175
+ // The anchor's own line is wider than the entire budget. Emit its head —
1176
+ // that line, not a neighbour, is the one the model was looking at.
1177
+ if (end === anchorIdx) {
1178
+ return {
1179
+ text: lines[anchorIdx].slice(0, maxBytes),
1180
+ from: anchor,
1181
+ to: anchor,
1182
+ total,
1183
+ anchor,
1184
+ clipped: true,
1185
+ partialLine: true,
1186
+ };
1187
+ }
1188
+ let start = anchorIdx;
1189
+ while (start > 0) {
1190
+ const cost = lines[start - 1].length + 1;
1191
+ if (used + cost > maxBytes)
1192
+ break;
1193
+ used += cost;
1194
+ start -= 1;
1195
+ }
1196
+ return {
1197
+ text: lines.slice(start, end).join('\n'),
1198
+ from: start + 1,
1199
+ to: end,
1200
+ total,
1201
+ anchor,
1202
+ // Reaching here means the whole body did not fit, so the window is a clip
1203
+ // by construction — full coverage is unreachable on this path.
1204
+ clipped: true,
1205
+ partialLine: false,
1206
+ };
1207
+ }
1104
1208
  /**
1105
1209
  * Build the grounding block for a forced-write round.
1106
1210
  *
@@ -1129,21 +1233,34 @@ export function buildForcedRoundGrounding(cache, projectRoot, readFile = (p) =>
1129
1233
  if (!key.startsWith('read_file:'))
1130
1234
  continue;
1131
1235
  // Entries carry their path; keys may also carry a window offset, and
1132
- // several windows of one file must ground it ONCE, not three times.
1236
+ // several windows of one file must ground it ONCE, not three times. The
1237
+ // window that wins is the most recent one — that is where the model was
1238
+ // working when its read tools were taken away, so that is where the clip
1239
+ // must be anchored if the file does not fit whole. Recency is `seq`, not
1240
+ // `round`: see ReadCache.nextSeq for why round alone picks the wrong one.
1133
1241
  const rel = meta.path ?? key.slice('read_file:'.length);
1134
1242
  const prior = byPath.get(rel);
1135
- if (!prior || meta.round > prior.round)
1136
- byPath.set(rel, { round: meta.round });
1243
+ if (!prior || isMoreRecent(meta, prior)) {
1244
+ byPath.set(rel, { round: meta.round, offset: meta.offset ?? 1, seq: meta.seq });
1245
+ }
1137
1246
  }
1138
1247
  const recent = [...byPath.entries()]
1139
- .sort((a, b) => b[1].round - a[1].round)
1248
+ .sort((a, b) => (b[1].seq ?? 0) - (a[1].seq ?? 0) || b[1].round - a[1].round)
1140
1249
  .slice(0, FORCED_GROUNDING_MAX_FILES);
1141
1250
  if (recent.length === 0)
1142
1251
  return undefined;
1143
1252
  const blocks = [];
1144
1253
  let budget = FORCED_GROUNDING_MAX_BYTES;
1145
- for (const [rel] of recent) {
1146
- if (budget <= 0)
1254
+ let filesLeft = recent.length;
1255
+ for (const [rel, meta] of recent) {
1256
+ // Even share, with whatever earlier files left unspent rolling forward.
1257
+ // A greedy first-come split let file 1 take the whole budget and handed
1258
+ // file 2 a mid-word fragment under a confident line-range label — worse
1259
+ // than omitting it, because the label asserts the fragment is the region
1260
+ // the model needs.
1261
+ const share = Math.floor(budget / filesLeft);
1262
+ filesLeft -= 1;
1263
+ if (share < FORCED_GROUNDING_MIN_WINDOW_BYTES)
1147
1264
  break;
1148
1265
  let body;
1149
1266
  try {
@@ -1152,9 +1269,25 @@ export function buildForcedRoundGrounding(cache, projectRoot, readFile = (p) =>
1152
1269
  catch {
1153
1270
  continue; // deleted or unreadable since it was read — skip, never throw
1154
1271
  }
1155
- const clipped = body.length > budget ? `${body.slice(0, budget)}\n… (truncated)` : body;
1156
- budget -= clipped.length;
1157
- blocks.push(`--- ${rel} (current content) ---\n${clipped}`);
1272
+ const win = clipAroundAnchor(body, share, meta.offset);
1273
+ budget -= win.text.length;
1274
+ // Naming the exact range is not cosmetic. A block labelled only
1275
+ // "(truncated)" is indistinguishable from a corrupt read, and the model's
1276
+ // response to that was to declare the whole file unverifiable and stop.
1277
+ // Told which lines it holds, it can act on them and name what is missing.
1278
+ // The anchor named is the CLAMPED one the window was actually built on.
1279
+ let label = '(current content)';
1280
+ if (win.partialLine) {
1281
+ label =
1282
+ `(current content, first ${win.text.length} chars of line ${win.anchor} ` +
1283
+ `of ${win.total} — truncated, that line is wider than the budget)`;
1284
+ }
1285
+ else if (win.clipped) {
1286
+ label =
1287
+ `(current content, lines ${win.from}-${win.to} of ${win.total} — truncated, ` +
1288
+ `centred on your last read at line ${win.anchor})`;
1289
+ }
1290
+ blocks.push(`--- ${rel} ${label} ---\n${win.text}`);
1158
1291
  }
1159
1292
  if (blocks.length === 0)
1160
1293
  return undefined;
@@ -1163,7 +1296,10 @@ export function buildForcedRoundGrounding(cache, projectRoot, readFile = (p) =>
1163
1296
  `${blocks.join('\n\n')}\n\n` +
1164
1297
  'Base your edit on exactly this text. For edit_file, copy old_string ' +
1165
1298
  'verbatim from above and make it long enough to be unique. Do not rewrite a ' +
1166
- 'whole file when a targeted edit will do.');
1299
+ 'whole file when a targeted edit will do. Where a block is marked truncated ' +
1300
+ 'it still shows the region you were last reading: act on what is shown and ' +
1301
+ 'state which line range you would need next — do not report the whole file ' +
1302
+ 'as unreadable.');
1167
1303
  }
1168
1304
  /**
1169
1305
  * Read a positive-integer round threshold from the environment.
@@ -1202,7 +1338,7 @@ export function readCountEnv(name, fallback) {
1202
1338
  return n;
1203
1339
  }
1204
1340
  export function newReadCache() {
1205
- return { seen: new Map() };
1341
+ return { seen: new Map(), nextSeq: 1 };
1206
1342
  }
1207
1343
  /** mtime of a path, or null when it does not exist / is unreadable. */
1208
1344
  function mtimeOf(p) {
@@ -1228,20 +1364,32 @@ export function repeatReadNote(cache, projectRoot, name, args, round) {
1228
1364
  // repeat — same path, same window, unchanged on disk — earns the note.
1229
1365
  // Both windowed tools, not just read_file: list_dir pages too now, and a
1230
1366
  // directory's second page is no more a re-read than a file's.
1231
- const offsetKey = Number.isFinite(Number(args.offset))
1232
- ? `@${Math.max(1, Math.trunc(Number(args.offset)))}`
1233
- : '';
1367
+ const offsetLine = Number.isFinite(Number(args.offset))
1368
+ ? Math.max(1, Math.trunc(Number(args.offset)))
1369
+ : 1;
1370
+ const offsetKey = Number.isFinite(Number(args.offset)) ? `@${offsetLine}` : '';
1234
1371
  const key = `${name}:${args.path}${offsetKey}`;
1235
1372
  const abs = resolve(projectRoot, args.path);
1236
1373
  const mtime = mtimeOf(abs);
1237
1374
  const prior = cache.seen.get(key);
1238
- if (prior && mtime !== null && prior.mtimeMs === mtime) {
1239
- return (`[NOTE: unchanged since you read ${args.path} at round ${prior.round}. ` +
1375
+ const seq = cache.nextSeq ?? 1;
1376
+ cache.nextSeq = seq + 1;
1377
+ const repeated = Boolean(prior && mtime !== null && prior.mtimeMs === mtime);
1378
+ // Record the read BEFORE the repeat check returns. The early return used to
1379
+ // skip this, so a window re-read seven times kept the round of its FIRST
1380
+ // read and lost the recency race to a one-off head read — leaving the
1381
+ // grounding anchored on the head, which is the read-forever profile this
1382
+ // whole mechanism exists to break. A re-read is still a read; it is in fact
1383
+ // the strongest signal of where the model is stuck.
1384
+ const priorRound = prior?.round ?? round;
1385
+ if (mtime !== null) {
1386
+ cache.seen.set(key, { mtimeMs: mtime, round, path: args.path, offset: offsetLine, seq });
1387
+ }
1388
+ if (repeated) {
1389
+ return (`[NOTE: unchanged since you read ${args.path} at round ${priorRound}. ` +
1240
1390
  'The content follows anyway — but re-reading it tells you nothing new, so act on it: ' +
1241
1391
  'make the edit, or call finish.]');
1242
1392
  }
1243
- if (mtime !== null)
1244
- cache.seen.set(key, { mtimeMs: mtime, round, path: args.path });
1245
1393
  return null;
1246
1394
  }
1247
1395
  export function runTool(projectRoot, name, args, bashTimeoutMs, protectedFiles, protectGateConfigs, allowBash, contractFiles = EMPTY_SET,