agentxchain 2.155.38 → 2.155.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.155.38",
3
+ "version": "2.155.40",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,8 +8,11 @@ import {
8
8
  formatGovernanceReportText,
9
9
  } from '../lib/report.js';
10
10
 
11
+ // BUG-88: apply bounding to prevent Invalid string length on large accumulated state
12
+ const defaultExportOpts = { maxJsonlEntries: 1000, maxBase64Bytes: 1024 * 1024, maxExportFiles: 500, maxTextDataBytes: 131072 };
13
+
11
14
  function detectAuditKind(cwd) {
12
- const runResult = buildRunExport(cwd);
15
+ const runResult = buildRunExport(cwd, defaultExportOpts);
13
16
  if (runResult.ok) {
14
17
  return {
15
18
  ok: true,
@@ -381,7 +381,8 @@ function assertExpectedWorkloadSignals(workload, metrics) {
381
381
 
382
382
  async function buildAndVerifyRunExport(root) {
383
383
  const { buildRunExport } = await import('../lib/export.js');
384
- const exportResult = buildRunExport(root);
384
+ // BUG-88: apply bounding to prevent Invalid string length on large accumulated state
385
+ const exportResult = buildRunExport(root, { maxJsonlEntries: 1000, maxBase64Bytes: 1024 * 1024, maxExportFiles: 500, maxTextDataBytes: 131072 });
385
386
  if (!exportResult.ok) {
386
387
  return {
387
388
  ok: false,
@@ -28,10 +28,13 @@ export async function exportCommand(options) {
28
28
  const cwd = process.cwd();
29
29
  const kind = detectExportKind(cwd);
30
30
 
31
+ // BUG-88: apply bounding to prevent Invalid string length on large accumulated state
32
+ const defaultExportOpts = { maxJsonlEntries: 1000, maxBase64Bytes: 1024 * 1024, maxExportFiles: 500, maxTextDataBytes: 131072 };
33
+
31
34
  let result;
32
35
  try {
33
36
  if (kind === 'governed') {
34
- result = buildRunExport(cwd);
37
+ result = buildRunExport(cwd, defaultExportOpts);
35
38
  } else if (kind === 'coordinator') {
36
39
  result = buildCoordinatorExport(cwd);
37
40
  } else {
@@ -59,5 +62,22 @@ export async function exportCommand(options) {
59
62
  return;
60
63
  }
61
64
 
62
- console.log(JSON.stringify(result.export, null, 2));
65
+ // BUG-88: compact JSON to avoid string-length overflow on large exports
66
+ try {
67
+ console.log(JSON.stringify(result.export));
68
+ } catch (serializeErr) {
69
+ if (/Invalid string length/i.test(serializeErr.message)) {
70
+ // Retry with tighter bounds
71
+ const tightOpts = { maxJsonlEntries: 500, maxBase64Bytes: 65536, maxExportFiles: 200, maxTextDataBytes: 32768 };
72
+ const tightResult = buildRunExport(cwd, tightOpts);
73
+ if (tightResult.ok) {
74
+ console.log(JSON.stringify(tightResult.export));
75
+ } else {
76
+ console.error(tightResult.error || serializeErr.message);
77
+ process.exitCode = 1;
78
+ }
79
+ } else {
80
+ throw serializeErr;
81
+ }
82
+ }
63
83
  }
@@ -673,26 +673,52 @@ export async function executeGovernedRun(context, opts = {}) {
673
673
  const reportsDir = join(root, '.agentxchain', 'reports');
674
674
  mkdirSync(reportsDir, { recursive: true });
675
675
 
676
- const exportResult = buildRunExport(root, { maxJsonlEntries: 1000, maxBase64Bytes: 1024 * 1024 });
676
+ // BUG-88: two-attempt export with fallback to tighter bounds on string-length overflow
677
+ const defaultExportOpts = { maxJsonlEntries: 1000, maxBase64Bytes: 1024 * 1024, maxExportFiles: 500, maxTextDataBytes: 131072 };
678
+ const tightExportOpts = { maxJsonlEntries: 500, maxBase64Bytes: 65536, maxExportFiles: 200, maxTextDataBytes: 32768 };
679
+
680
+ let exportResult = buildRunExport(root, defaultExportOpts);
677
681
  if (exportResult.ok) {
678
682
  const runId = result.state.run_id || 'unknown';
679
683
  const exportPath = join(reportsDir, `export-${runId}.json`);
684
+ let exportWriteOk = false;
685
+ let usedExport = exportResult.export;
680
686
 
681
- // Write export JSON — compact format to avoid string-length overflow (BUG-84)
687
+ // Write export JSON — compact format to avoid string-length overflow (BUG-84/88)
682
688
  try {
683
689
  writeFileSync(exportPath, JSON.stringify(exportResult.export));
690
+ exportWriteOk = true;
684
691
  } catch (exportWriteErr) {
685
- log(chalk.dim(` Governance export write failed: ${exportWriteErr.message}`));
692
+ // BUG-88: retry with tighter bounds on Invalid string length
693
+ if (/Invalid string length/i.test(exportWriteErr.message)) {
694
+ log(chalk.dim(` Export write exceeded string limit; retrying with tighter bounds...`));
695
+ const tightResult = buildRunExport(root, tightExportOpts);
696
+ if (tightResult.ok) {
697
+ try {
698
+ writeFileSync(exportPath, JSON.stringify(tightResult.export));
699
+ exportWriteOk = true;
700
+ usedExport = tightResult.export;
701
+ } catch (retryErr) {
702
+ log(chalk.dim(` Governance export write failed (tight bounds): ${retryErr.message}`));
703
+ }
704
+ }
705
+ } else {
706
+ log(chalk.dim(` Governance export write failed: ${exportWriteErr.message}`));
707
+ }
686
708
  }
687
709
 
688
- // Generate markdown report separately so export-write failure doesn't block it
710
+ // Generate markdown report from whichever export succeeded
689
711
  try {
690
- const reportResult = buildGovernanceReport(exportResult.export, { input: exportPath });
712
+ const reportInput = exportWriteOk ? exportPath : '(in-memory)';
713
+ const reportResult = buildGovernanceReport(usedExport, { input: reportInput });
691
714
  const reportPath = join(reportsDir, `report-${runId}.md`);
692
715
  writeFileSync(reportPath, formatGovernanceReportMarkdown(reportResult.report));
693
716
 
694
717
  log('');
695
718
  log(chalk.dim(` Governance report: .agentxchain/reports/report-${runId}.md`));
719
+ if (!exportWriteOk) {
720
+ log(chalk.dim(` Note: report generated from in-memory bounded export (disk write failed).`));
721
+ }
696
722
  } catch (reportErr) {
697
723
  log(chalk.dim(` Governance report failed: ${reportErr.message}`));
698
724
  }
@@ -106,27 +106,35 @@ function verifyFileEntry(relPath, entry, errors) {
106
106
  return;
107
107
  }
108
108
  if (isTruncated) {
109
- if (entry.format !== 'jsonl') {
110
- addError(errors, path, 'truncated file entries must use jsonl format');
111
- }
112
- if (!Array.isArray(entry.data)) {
113
- addError(errors, path, 'truncated JSONL data must be an array');
114
- }
115
- if (!Number.isInteger(entry.total_entries) || entry.total_entries < 0) {
116
- addError(errors, path, 'total_entries must be a non-negative integer when truncated');
117
- }
118
- if (!Number.isInteger(entry.retained_entries) || entry.retained_entries < 0) {
119
- addError(errors, path, 'retained_entries must be a non-negative integer when truncated');
120
- }
121
- if (Array.isArray(entry.data) && entry.retained_entries !== entry.data.length) {
122
- addError(errors, path, 'retained_entries must match truncated data length');
123
- }
124
- if (
125
- Number.isInteger(entry.total_entries)
126
- && Number.isInteger(entry.retained_entries)
127
- && entry.retained_entries > entry.total_entries
128
- ) {
129
- addError(errors, path, 'retained_entries must not exceed total_entries');
109
+ // BUG-88: truncated entries may be JSONL (windowed) or text (size-capped)
110
+ if (entry.format === 'jsonl') {
111
+ if (!Array.isArray(entry.data)) {
112
+ addError(errors, path, 'truncated JSONL data must be an array');
113
+ }
114
+ if (!Number.isInteger(entry.total_entries) || entry.total_entries < 0) {
115
+ addError(errors, path, 'total_entries must be a non-negative integer when truncated');
116
+ }
117
+ if (!Number.isInteger(entry.retained_entries) || entry.retained_entries < 0) {
118
+ addError(errors, path, 'retained_entries must be a non-negative integer when truncated');
119
+ }
120
+ if (Array.isArray(entry.data) && entry.retained_entries !== entry.data.length) {
121
+ addError(errors, path, 'retained_entries must match truncated data length');
122
+ }
123
+ if (
124
+ Number.isInteger(entry.total_entries)
125
+ && Number.isInteger(entry.retained_entries)
126
+ && entry.retained_entries > entry.total_entries
127
+ ) {
128
+ addError(errors, path, 'retained_entries must not exceed total_entries');
129
+ }
130
+ } else if (entry.format === 'text') {
131
+ if (typeof entry.data !== 'string') {
132
+ addError(errors, path, 'truncated text data must be a string');
133
+ }
134
+ } else if (entry.format === 'json') {
135
+ // Truncated JSON: data may be partial or null
136
+ } else {
137
+ addError(errors, path, `truncated file entries must use jsonl, text, or json format, got: ${entry.format}`);
130
138
  }
131
139
  return;
132
140
  }
package/src/lib/export.js CHANGED
@@ -182,6 +182,10 @@ function parseFile(root, relPath, opts = {}) {
182
182
  totalEntries = parsed.totalEntries;
183
183
  } else {
184
184
  data = buffer.toString('utf8');
185
+ if (opts.maxTextDataBytes && typeof data === 'string' && data.length > opts.maxTextDataBytes) {
186
+ data = data.slice(0, opts.maxTextDataBytes);
187
+ truncated = true;
188
+ }
185
189
  }
186
190
 
187
191
  const skipBase64 = truncated || (opts.maxBase64Bytes && buffer.byteLength > opts.maxBase64Bytes);
@@ -464,9 +468,42 @@ export function buildRunExport(startDir = process.cwd(), exportOpts = {}) {
464
468
  if (exportOpts.maxBase64Bytes) {
465
469
  parseOpts.maxBase64Bytes = exportOpts.maxBase64Bytes;
466
470
  }
471
+ if (exportOpts.maxTextDataBytes) {
472
+ parseOpts.maxTextDataBytes = exportOpts.maxTextDataBytes;
473
+ }
474
+
475
+ // BUG-88: apply maxExportFiles cap with priority ordering.
476
+ // Core governance files first, then dispatch/staging, then .planning/ last.
477
+ let boundedPaths = collectedPaths;
478
+ let exportFilesTruncated = false;
479
+ if (exportOpts.maxExportFiles && collectedPaths.length > exportOpts.maxExportFiles) {
480
+ const corePaths = [];
481
+ const runtimePaths = [];
482
+ const planningPaths = [];
483
+ for (const p of collectedPaths) {
484
+ if (p.startsWith('.planning/')) {
485
+ planningPaths.push(p);
486
+ } else if (p.startsWith('.agentxchain/dispatch/') || p.startsWith('.agentxchain/staging/') || p.startsWith('.agentxchain/transactions/')) {
487
+ runtimePaths.push(p);
488
+ } else {
489
+ corePaths.push(p);
490
+ }
491
+ }
492
+ const budget = exportOpts.maxExportFiles;
493
+ boundedPaths = corePaths.slice(0, budget);
494
+ const remaining = budget - boundedPaths.length;
495
+ if (remaining > 0) {
496
+ boundedPaths.push(...runtimePaths.slice(0, remaining));
497
+ const remaining2 = remaining - Math.min(runtimePaths.length, remaining);
498
+ if (remaining2 > 0) {
499
+ boundedPaths.push(...planningPaths.slice(0, remaining2));
500
+ }
501
+ }
502
+ exportFilesTruncated = true;
503
+ }
467
504
 
468
505
  const files = {};
469
- for (const relPath of collectedPaths) {
506
+ for (const relPath of boundedPaths) {
470
507
  files[relPath] = parseFile(root, relPath, parseOpts);
471
508
  }
472
509
 
@@ -511,6 +548,9 @@ export function buildRunExport(startDir = process.cwd(), exportOpts = {}) {
511
548
  dashboard_session: buildDashboardSessionSummary(root),
512
549
  delegation_summary: buildDelegationSummary(files),
513
550
  repo_decisions: buildRepoDecisionsSummary(root, rawConfig),
551
+ export_files_truncated: exportFilesTruncated || false,
552
+ total_collected_files: collectedPaths.length,
553
+ included_files: Object.keys(files).length,
514
554
  },
515
555
  workspace: buildRunWorkspaceMetadata(root),
516
556
  files,
@@ -576,7 +616,7 @@ function buildAggregatedEventsSummary(workspaceRoot, repoEntries) {
576
616
  };
577
617
  }
578
618
 
579
- export function buildCoordinatorExport(startDir = process.cwd()) {
619
+ export function buildCoordinatorExport(startDir = process.cwd(), exportOpts = {}) {
580
620
  const workspaceRoot = resolve(startDir);
581
621
  const configPath = join(workspaceRoot, COORDINATOR_CONFIG_FILE);
582
622
 
@@ -639,7 +679,8 @@ export function buildCoordinatorExport(startDir = process.cwd()) {
639
679
  const resolvedPath = resolve(workspaceRoot, repoPath);
640
680
 
641
681
  try {
642
- const childExport = buildRunExport(resolvedPath);
682
+ // BUG-88: apply bounding to child exports in coordinator workspaces
683
+ const childExport = buildRunExport(resolvedPath, exportOpts);
643
684
  if (childExport.ok) {
644
685
  repos[repoId] = {
645
686
  ok: true,