chainlesschain 0.166.135 → 0.166.138

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.
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { randomUUID } from "node:crypto";
4
+ import { renameWithRetry } from "../config-manager.js";
4
5
  import { redactSecretsInText } from "../ide-context-redaction.js";
5
6
  import {
6
7
  diagnosticExcerpt,
@@ -437,6 +438,11 @@ export class TaskWorklog {
437
438
  function writeProjection(cwd, sessionId, markdown) {
438
439
  const file = worklogPath(cwd, sessionId, true);
439
440
  const temporary = file + "." + randomUUID() + ".tmp";
441
+ let writeError;
442
+ const validatePath = () => {
443
+ if (worklogPath(cwd, sessionId) !== file)
444
+ throw new Error("Worklog workspace path changed while saving");
445
+ };
440
446
  try {
441
447
  const fd = fs.openSync(temporary, "wx", 0o600);
442
448
  try {
@@ -445,9 +451,26 @@ function writeProjection(cwd, sessionId, markdown) {
445
451
  } finally {
446
452
  fs.closeSync(fd);
447
453
  }
448
- worklogPath(cwd, sessionId);
449
- fs.renameSync(temporary, file);
454
+ // Windows readers can briefly lock the existing projection. Retry only
455
+ // the atomic replacement, rechecking directory/link boundaries each time.
456
+ renameWithRetry(temporary, file, {
457
+ attempts: process.platform === "win32" ? 8 : 1,
458
+ _rename: (source, target) => {
459
+ validatePath();
460
+ fs.renameSync(source, target);
461
+ },
462
+ });
463
+ } catch (error) {
464
+ writeError = error;
465
+ throw error;
450
466
  } finally {
451
- if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
467
+ try {
468
+ validatePath();
469
+ if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
470
+ } catch (error) {
471
+ // Never follow a redirected directory to clean up, or hide the original
472
+ // write failure behind a second transient lock on its temporary file.
473
+ if (!writeError) throw error;
474
+ }
452
475
  }
453
476
  }
@@ -1,3 +1,5 @@
1
+ import { parse as parseSource, parseExpressionAt, tokenizer } from "acorn";
2
+
1
3
  // Decode data only: no evaluation, revivers, prototype setters or permissive
2
4
  // JSON repair. Budgets belong to the entire projection, including nested text.
3
5
  export const EVIDENCE_JSON_POLICY = Object.freeze({
@@ -177,6 +179,7 @@ export function createEvidenceJsonTextBoundary(
177
179
  policy = EVIDENCE_JSON_POLICY,
178
180
  ) {
179
181
  let nodes = 0;
182
+ let sourceTokens = 0;
180
183
  let bytes = 0;
181
184
  const PARSE_FAILURE = Symbol("evidence-json-parse-failure");
182
185
  const reject = (
@@ -303,7 +306,11 @@ export function createEvidenceJsonTextBoundary(
303
306
  const parser = (
304
307
  text,
305
308
  start,
306
- { allowSingleQuotedStrings = false, allowSourceExpressions = false } = {},
309
+ {
310
+ allowSingleQuotedStrings = false,
311
+ allowSourceExpressions = false,
312
+ allowSourceLiteralSyntax = false,
313
+ } = {},
307
314
  ) => {
308
315
  let index = start;
309
316
  const whitespace = () => {
@@ -405,9 +412,36 @@ export function createEvidenceJsonTextBoundary(
405
412
  }
406
413
  index++;
407
414
  whitespace();
415
+ if (text[index] === end) {
416
+ if (!allowSourceLiteralSyntax) parserReject("trailing-comma");
417
+ index++;
418
+ return output;
419
+ }
408
420
  }
409
421
  return parserReject("syntax");
410
422
  }
423
+ // JavaScript numeric separators/radices are never JSON syntax. Admit
424
+ // them only in a container independently proven by the source parser.
425
+ const sourceNumber =
426
+ /^[+-]?(?:0[xX][0-9a-fA-F_]+|0[bB][01_]+|0[oO][0-7_]+|[0-9][0-9_]*(?:\.[0-9_]*)?(?:[eE][+-]?[0-9_]+)?)/u.exec(
427
+ text.slice(index),
428
+ )?.[0];
429
+ if (
430
+ sourceNumber &&
431
+ (sourceNumber.includes("_") || /^[+-]?0[xXbBoO]/u.test(sourceNumber))
432
+ ) {
433
+ if (!allowSourceLiteralSyntax) parserReject("source-number");
434
+ const unsigned = sourceNumber.replace(/^[+-]/u, "").replaceAll("_", "");
435
+ const number =
436
+ (sourceNumber.startsWith("-") ? -1 : 1) * Number(unsigned);
437
+ if (
438
+ !Number.isFinite(number) ||
439
+ (Number.isInteger(number) && !Number.isSafeInteger(number))
440
+ )
441
+ parserReject("budget");
442
+ index += sourceNumber.length;
443
+ return number;
444
+ }
411
445
  const token =
412
446
  /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/u.exec(
413
447
  text.slice(index),
@@ -460,6 +494,7 @@ export function createEvidenceJsonTextBoundary(
460
494
  layer = 0,
461
495
  objectOnly = false,
462
496
  codeText = false,
497
+ sourceText = false,
463
498
  pythonFString = false,
464
499
  fencedCodeLanguages = null,
465
500
  visit,
@@ -478,6 +513,7 @@ export function createEvidenceJsonTextBoundary(
478
513
  output += scanText(text.slice(start, end), {
479
514
  ...options,
480
515
  codeText: true,
516
+ sourceText: layer === 0,
481
517
  });
482
518
  offset = end;
483
519
  }
@@ -516,6 +552,214 @@ export function createEvidenceJsonTextBoundary(
516
552
  "Agent model code expression under an opaque source context cannot be safely projected",
517
553
  ),
518
554
  );
555
+ // Source syntax is a separate, explicit boundary. A paired tool's decoded
556
+ // string values do not inherit it. JSON-shaped standalone expressions still
557
+ // require strict JSON, even when they happen to parse as JavaScript arrays.
558
+ let sourceRanges;
559
+ let sourceBindings;
560
+ let tokenStarts;
561
+ let containerEnds;
562
+ let interpolationStarts;
563
+ const sourceOptions = {
564
+ ecmaVersion: "latest",
565
+ sourceType: "module",
566
+ allowReturnOutsideFunction: true,
567
+ };
568
+ const sourceContainer = (start) => {
569
+ if (
570
+ !sourceText ||
571
+ !codeText ||
572
+ policy.sourceLiteralPolicy !== "explicit-source-containers-v1"
573
+ )
574
+ return null;
575
+ const explicit = new Set([
576
+ "VariableDeclarator",
577
+ "FunctionDeclaration",
578
+ "FunctionExpression",
579
+ "ArrowFunctionExpression",
580
+ "CallExpression",
581
+ "NewExpression",
582
+ "MemberExpression",
583
+ "AssignmentExpression",
584
+ "ReturnStatement",
585
+ ]);
586
+ const bindingNames = (root) => {
587
+ const names = [];
588
+ const pending = [root];
589
+ while (pending.length) {
590
+ const node = pending.pop();
591
+ if (!node || typeof node.type !== "string") continue;
592
+ if (
593
+ (node.type === "MemberExpression" || node.type === "Property") &&
594
+ node.computed
595
+ )
596
+ return null;
597
+ if (["Identifier", "PrivateIdentifier"].includes(node.type))
598
+ names.push(node.name);
599
+ if (node.type === "Literal" && typeof node.value === "string")
600
+ names.push(node.value);
601
+ for (const child of Object.values(node)) {
602
+ if (Array.isArray(child)) pending.push(...child);
603
+ else if (child && typeof child === "object") pending.push(child);
604
+ }
605
+ }
606
+ return names;
607
+ };
608
+ const collect = (root, admitted = false, bindings = null) => {
609
+ const provenAncestry = bindings !== null;
610
+ const stack = [[root, admitted, bindings]];
611
+ while (stack.length) {
612
+ const [node, parentSource, parentBindings] = stack.pop();
613
+ if (!node || typeof node.type !== "string") continue;
614
+ const isSource = parentSource || explicit.has(node.type);
615
+ let ownBindings = [];
616
+ if (node.type === "VariableDeclarator")
617
+ ownBindings = bindingNames(node.id);
618
+ if (["AssignmentExpression", "AssignmentPattern"].includes(node.type))
619
+ ownBindings = bindingNames(node.left);
620
+ if (
621
+ ["Property", "PropertyDefinition", "MethodDefinition"].includes(
622
+ node.type,
623
+ )
624
+ )
625
+ ownBindings = node.computed ? null : bindingNames(node.key);
626
+ const activeBindings =
627
+ parentBindings && ownBindings
628
+ ? [...parentBindings, ...ownBindings]
629
+ : null;
630
+ if (
631
+ isSource &&
632
+ ["ArrayExpression", "ObjectExpression"].includes(node.type)
633
+ ) {
634
+ sourceRanges.set(node.start, node.end);
635
+ if (provenAncestry) sourceBindings.set(node.start, activeBindings);
636
+ }
637
+ if (node.type === "TemplateLiteral" && provenAncestry) {
638
+ for (const quasi of node.quasis.slice(0, -1))
639
+ sourceBindings.set(quasi.end + 1, activeBindings);
640
+ }
641
+ for (const value of Object.values(node)) {
642
+ if (Array.isArray(value))
643
+ for (const child of value)
644
+ stack.push([child, isSource, activeBindings]);
645
+ else if (value && typeof value === "object")
646
+ stack.push([value, isSource, activeBindings]);
647
+ }
648
+ }
649
+ };
650
+ if (!sourceRanges) {
651
+ sourceRanges = new Map();
652
+ sourceBindings = new Map();
653
+ tokenStarts = new Set();
654
+ containerEnds = new Map();
655
+ interpolationStarts = new Set();
656
+ const templates = [];
657
+ const delimiters = [];
658
+ let completePrefixEnd = 0;
659
+ try {
660
+ for (const token of tokenizer(text, sourceOptions)) {
661
+ if (++sourceTokens > (policy.maxSourceTokens ?? policy.maxNodes))
662
+ parserReject("budget");
663
+ tokenStarts.add(token.start);
664
+ const label = token.type.label;
665
+ if (label === "`") {
666
+ if (templates.at(-1)?.inText) templates.pop();
667
+ else templates.push({ start: token.start, inText: true });
668
+ }
669
+ if (label === "${" && templates.length) {
670
+ templates.at(-1).inText = false;
671
+ }
672
+ if (["[", "{", "${", "("].includes(label)) {
673
+ delimiters.push(token);
674
+ if (label === "${") interpolationStarts.add(token.start);
675
+ } else if (["]", "}", ")"].includes(label)) {
676
+ const opening = delimiters.pop();
677
+ if (
678
+ opening?.type.label === "${" &&
679
+ label === "}" &&
680
+ templates.length
681
+ )
682
+ templates.at(-1).inText = true;
683
+ if (
684
+ opening &&
685
+ ((opening.type.label === "[" && label === "]") ||
686
+ (opening.type.label === "(" && label === ")") ||
687
+ (["{", "${"].includes(opening.type.label) && label === "}"))
688
+ )
689
+ containerEnds.set(opening.start, token.end);
690
+ }
691
+ if (
692
+ delimiters.length === 0 &&
693
+ templates.length === 0 &&
694
+ [";", "}"].includes(label)
695
+ )
696
+ completePrefixEnd = token.end;
697
+ }
698
+ } catch (error) {
699
+ if (error?.[PARSE_FAILURE]) throw error;
700
+ tokenStarts.clear();
701
+ interpolationStarts.clear();
702
+ return null;
703
+ }
704
+ try {
705
+ collect(parseSource(text, sourceOptions), false, []);
706
+ } catch {
707
+ // A page may end inside a later statement. A fully parsed top-level
708
+ // prefix still proves the ancestors of earlier complete constructs.
709
+ // Try only one tokenizer-bounded prefix; partial/unproven constructs
710
+ // retain the conservative source-prefix checks.
711
+ if (completePrefixEnd > 0 && completePrefixEnd < text.length) {
712
+ try {
713
+ collect(
714
+ parseSource(text.slice(0, completePrefixEnd), sourceOptions),
715
+ false,
716
+ [],
717
+ );
718
+ } catch {
719
+ /* No complete program prefix was proven. */
720
+ }
721
+ }
722
+ }
723
+ }
724
+ if (!sourceRanges.has(start) && tokenStarts.has(start)) {
725
+ try {
726
+ const end = containerEnds.get(start);
727
+ if (!end) return null;
728
+ const expression = parseExpressionAt(
729
+ text.slice(0, end),
730
+ start,
731
+ sourceOptions,
732
+ );
733
+ // A complete member/call expression proves source syntax itself.
734
+ // A declaration prefix can also prove a complete literal on a page
735
+ // whose later statements are incomplete. Comments/strings cannot
736
+ // nominate starts: only tokenizer-confirmed source tokens qualify.
737
+ const declared =
738
+ /(?:^|[;\n])\s*(?:const|let|var)\s+[$_A-Za-z][$_A-Za-z0-9]*\s*=\s*$/u.test(
739
+ text.slice(0, start),
740
+ );
741
+ const iteration =
742
+ /(?:^|[;\n])\s*for\s*\(\s*(?:const|let|var)\s+[$_A-Za-z][$_A-Za-z0-9]*\s+of\s*$/u.test(
743
+ text.slice(0, start),
744
+ );
745
+ const member = /^\s*\.\s*[$_A-Za-z][$_A-Za-z0-9]*/u.exec(
746
+ text.slice(end),
747
+ );
748
+ if (member) {
749
+ const memberExpression = parseExpressionAt(
750
+ text.slice(0, end + member[0].length),
751
+ start,
752
+ sourceOptions,
753
+ );
754
+ collect(memberExpression);
755
+ }
756
+ collect(expression, declared || iteration);
757
+ } catch {
758
+ /* No complete, explicit source expression. */
759
+ }
760
+ }
761
+ return sourceRanges.get(start) ?? null;
762
+ };
519
763
  if (objectOnly) {
520
764
  const reader = parser(text, 0);
521
765
  let parsed;
@@ -543,6 +787,43 @@ export function createEvidenceJsonTextBoundary(
543
787
  let plainStart = 0;
544
788
  for (let index = 0; index < text.length; ) {
545
789
  const char = text[index];
790
+ // A tokenizer-proven template interpolation is source punctuation,
791
+ // not a JSON object. Its complete expression must parse independently;
792
+ // the normal scanner still visits all literals inside it.
793
+ if (
794
+ sourceText &&
795
+ codeText &&
796
+ char === "{" &&
797
+ text[index - 1] === "$" &&
798
+ ['"', "'"].includes(text.slice(index + 1).trimStart()[0]) &&
799
+ policy.sourceLiteralPolicy === "explicit-source-containers-v1"
800
+ ) {
801
+ sourceContainer(index);
802
+ const end = containerEnds?.get(index - 1);
803
+ if (interpolationStarts?.has(index - 1) && end) {
804
+ try {
805
+ const expression = parseExpressionAt(
806
+ text.slice(0, end - 1),
807
+ index + 1,
808
+ sourceOptions,
809
+ );
810
+ if (text.slice(expression.end, end - 1).trim() === "") {
811
+ const bindings = sourceBindings.get(index);
812
+ if (!bindings)
813
+ reject(
814
+ "Agent model code expression under an opaque source context cannot be safely projected",
815
+ );
816
+ bindings.forEach(requireSafeSourceKey);
817
+ index++;
818
+ continue;
819
+ }
820
+ } catch (error) {
821
+ // Projection failures must propagate; only Acorn syntax failures
822
+ // may fall back to the strict scanner.
823
+ if (!(error instanceof SyntaxError)) throw error;
824
+ }
825
+ }
826
+ }
546
827
  const next =
547
828
  char === "{" || char === "["
548
829
  ? text.slice(index + 1).match(/^\s*(.)/u)?.[1]
@@ -652,7 +933,7 @@ export function createEvidenceJsonTextBoundary(
652
933
  index++;
653
934
  continue;
654
935
  }
655
- const reader = parser(text, index, {
936
+ let reader = parser(text, index, {
656
937
  // A recognized container is always tried as strict JSON first, even
657
938
  // inside code. Only an explicit non-JSON token allows literal scanning.
658
939
  allowSingleQuotedStrings: codeText && !container,
@@ -667,21 +948,50 @@ export function createEvidenceJsonTextBoundary(
667
948
  valueEnd = reader.end();
668
949
  } catch (error) {
669
950
  const kind = error?.[PARSE_FAILURE];
670
- if (
671
- !codeText ||
672
- !["code-expression", "source-expression"].includes(kind)
673
- )
674
- rejectParseFailure(error);
675
- if (kind === "source-expression") {
676
- requireSafeSourceAssignments(index);
951
+ let sourceEnd = null;
952
+ if (["trailing-comma", "source-number"].includes(kind)) {
953
+ try {
954
+ sourceEnd = sourceContainer(index);
955
+ } catch (sourceError) {
956
+ rejectParseFailure(sourceError);
957
+ }
958
+ }
959
+ if (sourceEnd !== null) {
960
+ const bindings = sourceBindings.get(index);
961
+ if (sourceBindings.has(index)) {
962
+ if (!bindings)
963
+ reject(
964
+ "Agent model code expression under an opaque source context cannot be safely projected",
965
+ );
966
+ bindings.forEach(requireSafeSourceKey);
967
+ } else requireSafeSourceAssignments(index);
677
968
  const key = assignmentKeyBefore(text, plainStart, index);
678
969
  if (key) requireSafeSourceKey(key);
970
+ reader = parser(text, index, { allowSourceLiteralSyntax: true });
971
+ try {
972
+ parsed = reader.value();
973
+ valueEnd = reader.end();
974
+ } catch (sourceError) {
975
+ rejectParseFailure(sourceError);
976
+ }
977
+ if (valueEnd !== sourceEnd) reject();
978
+ } else {
979
+ if (
980
+ !codeText ||
981
+ !["code-expression", "source-expression"].includes(kind)
982
+ )
983
+ rejectParseFailure(error);
984
+ if (kind === "source-expression") {
985
+ requireSafeSourceAssignments(index);
986
+ const key = assignmentKeyBefore(text, plainStart, index);
987
+ if (key) requireSafeSourceKey(key);
988
+ }
989
+ // Python/JavaScript containers may contain single quotes or expressions.
990
+ // Scan their literals individually, preserving nested strict JSON scans.
991
+ // Keep consumed nodes charged so repeated fallback cannot reset budgets.
992
+ index++;
993
+ continue;
679
994
  }
680
- // Python/JavaScript containers may contain single quotes or expressions.
681
- // Scan their literals individually, preserving nested strict JSON scans.
682
- // Keep consumed nodes charged so repeated fallback cannot reset budgets.
683
- index++;
684
- continue;
685
995
  }
686
996
  reader.whitespace();
687
997
  let pair = quoted && text[reader.end()] === ":";
@@ -614,7 +614,7 @@ export const EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_V7_DIGEST = `sha256:${crea
614
614
  "utf8",
615
615
  )
616
616
  .digest("hex")}`;
617
- const AGENT_MODEL_RULESET = Object.freeze({
617
+ const AGENT_MODEL_RULESET_V8 = Object.freeze({
618
618
  ...AGENT_MODEL_RULESET_V7,
619
619
  schema: "chainlesschain.evolution-agent-model-projection-rules/v8",
620
620
  version: 8,
@@ -624,6 +624,25 @@ const AGENT_MODEL_RULESET = Object.freeze({
624
624
  sourceExpressionPolicy: "bounded-source-tokens-v1",
625
625
  }),
626
626
  });
627
+ export const EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_V8_DIGEST = `sha256:${createHash(
628
+ "sha256",
629
+ )
630
+ .update(
631
+ `${AGENT_MODEL_RULESET_V8.schema}\0${JSON.stringify(AGENT_MODEL_RULESET_V8)}`,
632
+ "utf8",
633
+ )
634
+ .digest("hex")}`;
635
+ const AGENT_MODEL_RULESET = Object.freeze({
636
+ ...AGENT_MODEL_RULESET_V8,
637
+ schema: "chainlesschain.evolution-agent-model-projection-rules/v9",
638
+ version: 9,
639
+ jsonTextPolicy: Object.freeze({
640
+ ...AGENT_MODEL_RULESET_V8.jsonTextPolicy,
641
+ version: 6,
642
+ sourceLiteralPolicy: "explicit-source-containers-v1",
643
+ maxSourceTokens: 32768,
644
+ }),
645
+ });
627
646
  export const EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_DIGEST = `sha256:${createHash(
628
647
  "sha256",
629
648
  )
@@ -659,6 +678,7 @@ const SUPPORTED_RULESETS = new Map([
659
678
  [EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_V5_DIGEST, AGENT_MODEL_RULESET_V5],
660
679
  [EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_V6_DIGEST, AGENT_MODEL_RULESET_V6],
661
680
  [EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_V7_DIGEST, AGENT_MODEL_RULESET_V7],
681
+ [EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_V8_DIGEST, AGENT_MODEL_RULESET_V8],
662
682
  [EVOLUTION_AGENT_MODEL_PROJECTION_RULESET_DIGEST, AGENT_MODEL_RULESET],
663
683
  ]);
664
684
 
@@ -734,6 +754,7 @@ const AGENT_VALUE_PATTERNS = Object.freeze([
734
754
  function valuePatterns(ruleset) {
735
755
  return ruleset === SKILL_DECISION_RULESET ||
736
756
  ruleset === AGENT_MODEL_RULESET ||
757
+ ruleset === AGENT_MODEL_RULESET_V8 ||
737
758
  ruleset === AGENT_MODEL_RULESET_V7 ||
738
759
  ruleset === AGENT_MODEL_RULESET_V6 ||
739
760
  ruleset === AGENT_MODEL_RULESET_V5 ||
@@ -1459,6 +1480,43 @@ function isAgentCodeTextPath(payload, path, resultToolNames) {
1459
1480
  return false;
1460
1481
  }
1461
1482
 
1483
+ // Unlike historical codeText literal scanning, source grammar is confined to
1484
+ // exact paired-tool source fields. A second $json descent is ordinary data.
1485
+ function isAgentSourceTextPath(payload, path, resultToolNames) {
1486
+ if (!isAgentCodeTextPath(payload, path, resultToolNames)) return false;
1487
+ const message = payload.messages[Number(path[1])];
1488
+ if (path[2] === "tool_calls") {
1489
+ const fields = path.slice(6);
1490
+ if (fields[0] === "$json") fields.shift();
1491
+ return fields.length === 1 && fields[0] === "code";
1492
+ }
1493
+ const calls = payload.messages.flatMap((candidate) =>
1494
+ candidate?.role === "assistant" && Array.isArray(candidate.tool_calls)
1495
+ ? candidate.tool_calls.filter((call) => call?.id === message.tool_call_id)
1496
+ : [],
1497
+ );
1498
+ if (calls.length !== 1) return false;
1499
+ const tool = calls[0].function?.name;
1500
+ const fields = path.slice(3);
1501
+ if (fields[0] === "$json") fields.shift();
1502
+ if (fields.includes("$json")) return false;
1503
+ if (tool === "search_files")
1504
+ return (
1505
+ fields.length === 3 &&
1506
+ fields[0] === "matches" &&
1507
+ /^\d+$/u.test(fields[1]) &&
1508
+ ["context", "text"].includes(fields[2])
1509
+ );
1510
+ const sourceFields = {
1511
+ read_file: ["content"],
1512
+ run_code: ["output"],
1513
+ run_shell: ["stdout", "stderr"],
1514
+ check_shell: ["stdout", "stderr"],
1515
+ git: ["stdout", "stderr"],
1516
+ };
1517
+ return fields.length === 1 && (sourceFields[tool] || []).includes(fields[0]);
1518
+ }
1519
+
1462
1520
  function sanitizePayload(
1463
1521
  payload,
1464
1522
  ruleset = RULESET,
@@ -1489,6 +1547,7 @@ function sanitizePayload(
1489
1547
  const toolArguments = jsonText && isAgentToolArgumentsPath(path);
1490
1548
  const opaqueTransportBlock =
1491
1549
  ruleset === AGENT_MODEL_RULESET ||
1550
+ ruleset === AGENT_MODEL_RULESET_V8 ||
1492
1551
  ruleset === AGENT_MODEL_RULESET_V7 ||
1493
1552
  ruleset === AGENT_MODEL_RULESET_V6 ||
1494
1553
  ruleset === AGENT_MODEL_RULESET_V5 ||
@@ -1515,6 +1574,11 @@ function sanitizePayload(
1515
1574
  layer,
1516
1575
  objectOnly: toolArguments,
1517
1576
  codeText: codeText || textContext?.pythonFString === true,
1577
+ sourceText: isAgentSourceTextPath(
1578
+ payload,
1579
+ path,
1580
+ ruleset.codeTextResultTools,
1581
+ ),
1518
1582
  pythonFString: textContext?.pythonFString === true,
1519
1583
  // Only direct message text can contain Markdown code examples. Tool
1520
1584
  // arguments and recursively decoded strings keep their existing rules.
@@ -3385,6 +3449,7 @@ function validateBundleIntegrity(bundle) {
3385
3449
  }
3386
3450
  if (
3387
3451
  ruleset === AGENT_MODEL_RULESET ||
3452
+ ruleset === AGENT_MODEL_RULESET_V8 ||
3388
3453
  ruleset === AGENT_MODEL_RULESET_V7 ||
3389
3454
  ruleset === AGENT_MODEL_RULESET_V6 ||
3390
3455
  ruleset === AGENT_MODEL_RULESET_V5 ||
@@ -3397,6 +3462,7 @@ function validateBundleIntegrity(bundle) {
3397
3462
  allowStructuredArgumentText: ruleset !== AGENT_MODEL_RULESET_V1,
3398
3463
  allowOpaqueTransportBlocks:
3399
3464
  ruleset === AGENT_MODEL_RULESET ||
3465
+ ruleset === AGENT_MODEL_RULESET_V8 ||
3400
3466
  ruleset === AGENT_MODEL_RULESET_V7 ||
3401
3467
  ruleset === AGENT_MODEL_RULESET_V6 ||
3402
3468
  ruleset === AGENT_MODEL_RULESET_V5 ||