@qvac/harness 0.7.3 → 0.8.5

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 (146) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/NOTICE.md +16 -0
  3. package/dist-lib/lib/capabilities/completion.d.ts +1 -0
  4. package/dist-lib/lib/capabilities/completion.js +28 -8
  5. package/dist-lib/lib/capabilities/tool-turns.d.ts +3 -0
  6. package/dist-lib/lib/capabilities/tool-turns.js +7 -0
  7. package/dist-lib/lib/context-window.js +2 -2
  8. package/dist-lib/lib/docx/index.d.ts +1 -1
  9. package/dist-lib/lib/docx/index.js +16 -24
  10. package/dist-lib/lib/helpers/run-outcome.d.ts +4 -1
  11. package/dist-lib/lib/helpers/run-outcome.js +14 -8
  12. package/dist-lib/lib/helpers/tool-call-markup.js +4 -4
  13. package/dist-lib/lib/hooks/built-in/arg-repair.js +7 -0
  14. package/dist-lib/lib/office/engine/codes.d.ts +62 -0
  15. package/dist-lib/lib/office/engine/codes.js +61 -0
  16. package/dist-lib/lib/office/engine/docx/document.d.ts +216 -0
  17. package/dist-lib/lib/office/engine/docx/document.js +1152 -0
  18. package/dist-lib/lib/office/engine/docx/index.d.ts +7 -0
  19. package/dist-lib/lib/office/engine/docx/index.js +8 -0
  20. package/dist-lib/lib/office/engine/docx/styles.d.ts +31 -0
  21. package/dist-lib/lib/office/engine/docx/styles.js +173 -0
  22. package/dist-lib/lib/office/engine/docx/template.d.ts +18 -0
  23. package/dist-lib/lib/office/engine/docx/template.js +133 -0
  24. package/dist-lib/lib/office/engine/docx/text.d.ts +55 -0
  25. package/dist-lib/lib/office/engine/docx/text.js +538 -0
  26. package/dist-lib/lib/office/engine/drawingml/chart.d.ts +63 -0
  27. package/dist-lib/lib/office/engine/drawingml/chart.js +539 -0
  28. package/dist-lib/lib/office/engine/drawingml/text.d.ts +35 -0
  29. package/dist-lib/lib/office/engine/drawingml/text.js +352 -0
  30. package/dist-lib/lib/office/engine/errors.d.ts +6 -0
  31. package/dist-lib/lib/office/engine/errors.js +12 -0
  32. package/dist-lib/lib/office/engine/image.d.ts +8 -0
  33. package/dist-lib/lib/office/engine/image.js +48 -0
  34. package/dist-lib/lib/office/engine/index.d.ts +12 -0
  35. package/dist-lib/lib/office/engine/index.js +7 -0
  36. package/dist-lib/lib/office/engine/opc/content-types.d.ts +17 -0
  37. package/dist-lib/lib/office/engine/opc/content-types.js +74 -0
  38. package/dist-lib/lib/office/engine/opc/media.d.ts +13 -0
  39. package/dist-lib/lib/office/engine/opc/media.js +11 -0
  40. package/dist-lib/lib/office/engine/opc/package.d.ts +51 -0
  41. package/dist-lib/lib/office/engine/opc/package.js +324 -0
  42. package/dist-lib/lib/office/engine/opc/paths.d.ts +7 -0
  43. package/dist-lib/lib/office/engine/opc/paths.js +46 -0
  44. package/dist-lib/lib/office/engine/opc/properties.d.ts +7 -0
  45. package/dist-lib/lib/office/engine/opc/properties.js +75 -0
  46. package/dist-lib/lib/office/engine/opc/relationships.d.ts +23 -0
  47. package/dist-lib/lib/office/engine/opc/relationships.js +65 -0
  48. package/dist-lib/lib/office/engine/opc/zip.d.ts +15 -0
  49. package/dist-lib/lib/office/engine/opc/zip.js +264 -0
  50. package/dist-lib/lib/office/engine/pptx/fit.d.ts +12 -0
  51. package/dist-lib/lib/office/engine/pptx/fit.js +37 -0
  52. package/dist-lib/lib/office/engine/pptx/index.d.ts +8 -0
  53. package/dist-lib/lib/office/engine/pptx/index.js +8 -0
  54. package/dist-lib/lib/office/engine/pptx/inherit.d.ts +29 -0
  55. package/dist-lib/lib/office/engine/pptx/inherit.js +155 -0
  56. package/dist-lib/lib/office/engine/pptx/notes.d.ts +5 -0
  57. package/dist-lib/lib/office/engine/pptx/notes.js +85 -0
  58. package/dist-lib/lib/office/engine/pptx/presentation.d.ts +54 -0
  59. package/dist-lib/lib/office/engine/pptx/presentation.js +749 -0
  60. package/dist-lib/lib/office/engine/pptx/read.d.ts +9 -0
  61. package/dist-lib/lib/office/engine/pptx/read.js +66 -0
  62. package/dist-lib/lib/office/engine/pptx/shapes.d.ts +52 -0
  63. package/dist-lib/lib/office/engine/pptx/shapes.js +276 -0
  64. package/dist-lib/lib/office/engine/pptx/slides.d.ts +27 -0
  65. package/dist-lib/lib/office/engine/pptx/slides.js +123 -0
  66. package/dist-lib/lib/office/engine/pptx/template.d.ts +20 -0
  67. package/dist-lib/lib/office/engine/pptx/template.js +391 -0
  68. package/dist-lib/lib/office/engine/pptx/types.d.ts +120 -0
  69. package/dist-lib/lib/office/engine/pptx/types.js +1 -0
  70. package/dist-lib/lib/office/engine/units.d.ts +3 -0
  71. package/dist-lib/lib/office/engine/units.js +12 -0
  72. package/dist-lib/lib/office/engine/utf8.d.ts +2 -0
  73. package/dist-lib/lib/office/engine/utf8.js +103 -0
  74. package/dist-lib/lib/office/engine/xlsx/formula.d.ts +49 -0
  75. package/dist-lib/lib/office/engine/xlsx/formula.js +381 -0
  76. package/dist-lib/lib/office/engine/xlsx/index.d.ts +8 -0
  77. package/dist-lib/lib/office/engine/xlsx/index.js +8 -0
  78. package/dist-lib/lib/office/engine/xlsx/refs.d.ts +22 -0
  79. package/dist-lib/lib/office/engine/xlsx/refs.js +82 -0
  80. package/dist-lib/lib/office/engine/xlsx/reshape.d.ts +21 -0
  81. package/dist-lib/lib/office/engine/xlsx/reshape.js +424 -0
  82. package/dist-lib/lib/office/engine/xlsx/sheet.d.ts +108 -0
  83. package/dist-lib/lib/office/engine/xlsx/sheet.js +662 -0
  84. package/dist-lib/lib/office/engine/xlsx/strings.d.ts +15 -0
  85. package/dist-lib/lib/office/engine/xlsx/strings.js +93 -0
  86. package/dist-lib/lib/office/engine/xlsx/styles.d.ts +38 -0
  87. package/dist-lib/lib/office/engine/xlsx/styles.js +369 -0
  88. package/dist-lib/lib/office/engine/xlsx/template.d.ts +7 -0
  89. package/dist-lib/lib/office/engine/xlsx/template.js +54 -0
  90. package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +161 -0
  91. package/dist-lib/lib/office/engine/xlsx/workbook.js +850 -0
  92. package/dist-lib/lib/office/engine/xlsx/write.d.ts +2 -0
  93. package/dist-lib/lib/office/engine/xlsx/write.js +42 -0
  94. package/dist-lib/lib/office/engine/xml.d.ts +45 -0
  95. package/dist-lib/lib/office/engine/xml.js +258 -0
  96. package/dist-lib/lib/office/extract.d.ts +9 -0
  97. package/dist-lib/lib/office/extract.js +19 -0
  98. package/dist-lib/lib/office/index.d.ts +1 -1
  99. package/dist-lib/lib/office/index.js +74 -19
  100. package/dist-lib/lib/tools/exec/adapters/python/module-distributions.js +0 -3
  101. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +7 -50
  102. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +38 -695
  103. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +1 -85
  104. package/dist-lib/lib/tools/exec/scope.d.ts +2 -0
  105. package/dist-lib/lib/tools/exec/scope.js +33 -2
  106. package/dist-lib/lib/tools/exec.js +9 -20
  107. package/dist-lib/lib/tools/office/ops.d.ts +13 -0
  108. package/dist-lib/lib/tools/office/ops.js +100 -0
  109. package/dist-lib/lib/tools/office.d.ts +12 -0
  110. package/dist-lib/lib/tools/office.js +486 -0
  111. package/dist-lib/lib/tools/pdf/build.d.ts +43 -0
  112. package/dist-lib/lib/tools/pdf/build.js +150 -0
  113. package/dist-lib/lib/tools/pdf/images.d.ts +7 -0
  114. package/dist-lib/lib/tools/pdf/images.js +82 -0
  115. package/dist-lib/lib/tools/pdf/request.d.ts +63 -0
  116. package/dist-lib/lib/tools/pdf/request.js +335 -0
  117. package/dist-lib/lib/tools/pdf.d.ts +6 -0
  118. package/dist-lib/lib/tools/pdf.js +302 -0
  119. package/dist-lib/lib/tools/skill.js +14 -0
  120. package/package.json +34 -6
  121. package/wheels/manifest.json +1 -160
  122. package/worker/harness-sidecar.mjs +5 -1
  123. package/dist-lib/lib/office/pptx.d.ts +0 -1
  124. package/dist-lib/lib/office/pptx.js +0 -26
  125. package/dist-lib/lib/office/python.d.ts +0 -8
  126. package/dist-lib/lib/office/python.js +0 -32
  127. package/dist-lib/lib/office/xlsx.d.ts +0 -1
  128. package/dist-lib/lib/office/xlsx.js +0 -26
  129. package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +0 -7
  130. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +0 -318
  131. package/wheels/cffi-2.0.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  132. package/wheels/cryptography-47.0.0-cp314-abi3-pyemscripten_2026_0_wasm32.whl +0 -0
  133. package/wheels/defusedxml-0.7.1-py2.py3-none-any.whl +0 -0
  134. package/wheels/et_xmlfile-2.0.0-py3-none-any.whl +0 -0
  135. package/wheels/fonttools-4.62.1-py3-none-any.whl +0 -0
  136. package/wheels/fpdf2-2.8.8-py3-none-any.whl +0 -0
  137. package/wheels/lxml-6.0.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  138. package/wheels/openpyxl-3.1.5-py2.py3-none-any.whl +0 -0
  139. package/wheels/pillow-12.2.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  140. package/wheels/pycparser-3.0-py3-none-any.whl +0 -0
  141. package/wheels/pypdf-6.15.0-py3-none-any.whl +0 -0
  142. package/wheels/python_docx-1.2.0-py3-none-any.whl +0 -0
  143. package/wheels/python_pptx-1.0.2-py3-none-any.whl +0 -0
  144. package/wheels/six-1.17.0-py2.py3-none-any.whl +0 -0
  145. package/wheels/typing_extensions-4.15.0-py3-none-any.whl +0 -0
  146. package/wheels/xlsxwriter-3.2.9-py3-none-any.whl +0 -0
@@ -308,7 +308,7 @@ export function stringLiteralProgramHint(source) {
308
308
  }
309
309
  return ('command is wrapped in a string literal, so Python evaluates it as text and runs ' +
310
310
  'nothing: remove the wrapping quotes (and any escaped \\" quotes) so the first line ' +
311
- 'of command is the first line of the program, e.g. from fpdf import FPDF.');
311
+ 'of command is the first line of the program, e.g. import json.');
312
312
  }
313
313
  const COLLAPSED_COMPOUND = /;\s*(?:async\s+)?(?:for|if|while|with|def|class|try|match)\b/;
314
314
  export function checkCollapsedCompound(source) {
@@ -320,159 +320,6 @@ export function checkCollapsedCompound(source) {
320
320
  'cannot follow a semicolon. Pass multi-line Python source with newline characters, ' +
321
321
  'not a single-line semicolon chain.');
322
322
  }
323
- const MANGLED_PPTX = [
324
- ['textframe', 'text_frame'],
325
- ['addslide', 'add_slide'],
326
- ['slidelayouts', 'slide_layouts'],
327
- ['slidewidth', 'slide_width'],
328
- ['slideheight', 'slide_height'],
329
- ['wordwrap', 'word_wrap'],
330
- ['addparagraph', 'add_paragraph'],
331
- ['addtextbox', 'add_textbox'],
332
- ['addpicture', 'add_picture'],
333
- ['ppalign', 'PP_ALIGN']
334
- ];
335
- export function mangledPptxApiHint(source) {
336
- if (!/\bpptx\b|Presentation\s*\(/.test(source))
337
- return null;
338
- const found = [];
339
- for (const [bad, good] of MANGLED_PPTX) {
340
- if (new RegExp(`\\b${bad}\\b`, 'i').test(source) && !source.includes(good)) {
341
- found.push(`${bad} → ${good}`);
342
- }
343
- }
344
- if (found.length === 0) {
345
- return null;
346
- }
347
- return (`python-pptx names keep their underscores — found stripped forms (${found.join(', ')}). ` +
348
- 'Restore them (text_frame not textframe, add_slide not addslide) and keep the skill ' +
349
- 'sample multi-line with real newlines.');
350
- }
351
- export function wrongRgbColorHint(source) {
352
- if (!/\bpptx\b|Presentation\s*\(/.test(source))
353
- return null;
354
- if (/\bRGBColor\b/.test(source))
355
- return null;
356
- const code = source.replace(/#[^\n]*/g, '').replace(/'[^'\n]*'|"[^"\n]*"/g, '');
357
- if (!/\bRgbColor\b|\brgbColor\b|\brgbcolor\b|\bRGB_Color\b|\brgb_color\b/.test(code)) {
358
- return null;
359
- }
360
- return ('python-pptx color type is RGBColor (RGB in all caps): ' +
361
- 'from pptx.dml.color import RGBColor — not RgbColor / rgbColor. ' +
362
- 'Do not catch that ImportError and continue; fix the name and rerun.');
363
- }
364
- const FPDF_SIZE_KEYWORD = /\b(cell|multi_cell)\s*\([^)]*\b(width|height)\s*=/;
365
- export function fpdfSizeKeywordHint(source) {
366
- if (!/\bfpdf\b|\bFPDF\b/.test(source))
367
- return null;
368
- const match = FPDF_SIZE_KEYWORD.exec(source);
369
- if (!match) {
370
- return null;
371
- }
372
- return (`fpdf2 ${match[1]}() has no "${match[2]}" keyword — the size parameters are the positional ` +
373
- 'w and h: write every line (headings and body) with pdf.multi_cell(0, 6, text, ' +
374
- 'new_x="LMARGIN", new_y="NEXT"). Rename the arguments and keep new_x/new_y; ' +
375
- 'deleting keywords to silence the error prints later lines on top of each other.');
376
- }
377
- export function fpdfKeywordErrorHint(stderr) {
378
- const match = /TypeError: FPDF\.(\w+)\(\) got an unexpected keyword argument '(\w+)'/.exec(stderr);
379
- if (!match) {
380
- return null;
381
- }
382
- const sized = match[2] === 'width' || match[2] === 'height';
383
- const fix = sized
384
- ? 'the size parameters are the positional w and h'
385
- : 'copy the exact call shape from the pdf skill sample';
386
- return (`fpdf2 ${match[1]}() has no "${match[2]}" keyword — ${fix}: ` +
387
- 'write every line (headings and body) with pdf.multi_cell(0, 6, text, ' +
388
- 'new_x="LMARGIN", new_y="NEXT"). Rename the arguments and keep new_x/new_y; ' +
389
- 'deleting keywords to silence the error prints later lines on top of each other.');
390
- }
391
- const LATIN1_SUBSTITUTES = [
392
- [/[\u2010-\u2015\u2212]/gu, '-'],
393
- [/[\u2022\u25e6\u25aa\u25cf]/gu, '-'],
394
- [/\u2026/gu, '...'],
395
- [/\u2192/gu, '->'],
396
- [/\u2190/gu, '<-'],
397
- [/\u2248/gu, '~'],
398
- [/\u2264/gu, '<='],
399
- [/\u2265/gu, '>='],
400
- [/\u2122/gu, '(TM)'],
401
- [/\u20ac/gu, 'EUR'],
402
- [/[\u2000-\u200a]/gu, ' '],
403
- [/[\u200b-\u200d\ufeff]/gu, '']
404
- ];
405
- export function normalizeFpdfSource(source) {
406
- if (!/\bfpdf\b|\bFPDF\b/.test(source))
407
- return source;
408
- let out = source;
409
- for (const [from, to] of LATIN1_SUBSTITUTES) {
410
- out = out.replace(from, to);
411
- }
412
- return out;
413
- }
414
- const CURLY_QUOTE = /[\u2018\u2019\u201a\u2032\u201c\u201d\u201e\u2033]/u;
415
- const TEXT_WITHOUT_COORDS = /\.text\s*\(\s*['"]/;
416
- const TEXT_IS_STAMP_ONLY = 'fpdf2 text(x, y, string) paints at a fixed point with no wrapping \u2014 it is only for the ' +
417
- 'watermark stamp. Write document text (headings, paragraphs, bullets) with ' +
418
- 'pdf.multi_cell(0, 6, text, new_x="LMARGIN", new_y="NEXT").';
419
- export function fpdfTextCallHint(source) {
420
- if (!/\bfpdf\b|\bFPDF\b/.test(source))
421
- return null;
422
- if (!TEXT_WITHOUT_COORDS.test(source))
423
- return null;
424
- return TEXT_IS_STAMP_ONLY;
425
- }
426
- export function fpdfTextErrorHint(stderr) {
427
- if (!/TypeError: FPDF\.text\(\)/.test(stderr))
428
- return null;
429
- return TEXT_IS_STAMP_ONLY;
430
- }
431
- export function fpdfNonLatin1Hint(source) {
432
- if (!/\bfpdf\b|\bFPDF\b/.test(source))
433
- return null;
434
- if (source.includes('.replace('))
435
- return null;
436
- const bad = /[^\u0000-\u00ff]/u.exec(normalizeFpdfSource(source))?.[0];
437
- if (!bad) {
438
- return null;
439
- }
440
- if (CURLY_QUOTE.test(bad)) {
441
- return (`fpdf2 core fonts are latin-1 only and the curly quote ${bad} is outside it — rewrite ` +
442
- 'the strings with straight quotes \' and " and rerun.');
443
- }
444
- return (`fpdf2 core fonts are latin-1 only and "${bad}" cannot be rendered — there are no Unicode ` +
445
- 'fonts in this runtime. Drop it or use a latin transliteration, and tell the user CJK and ' +
446
- 'emoji cannot appear in the PDF.');
447
- }
448
- export function fpdfUnicodeErrorHint(stderr) {
449
- const encoding = /FPDFUnicodeEncodingException/.test(stderr) ||
450
- (/UnicodeEncodeError: 'latin-1' codec/.test(stderr) && /fpdf/.test(stderr));
451
- if (!encoding) {
452
- return null;
453
- }
454
- return ('a character outside latin-1 reached an fpdf2 core font: replace it with ASCII ' +
455
- '(bullets "- ", straight quotes, plain hyphens). CJK and emoji cannot be rendered — ' +
456
- 'tell the user instead of retrying with the same string.');
457
- }
458
- export function fpdfHorizontalSpaceHint(stderr) {
459
- if (!/FPDFException: Not enough horizontal space/.test(stderr))
460
- return null;
461
- return ('an earlier cell/multi_cell left the cursor at the right margin: add ' +
462
- 'new_x="LMARGIN", new_y="NEXT" to every cell and multi_cell call so each ' +
463
- 'one starts on its own line.');
464
- }
465
- export function rgbColorImportHint(stderr) {
466
- const name = /cannot import name '([^']+)' from 'pptx\.dml\.color'/.exec(stderr)?.[1];
467
- if (!name || name === 'RGBColor') {
468
- return null;
469
- }
470
- if (!/^rgb_?color$/i.test(name)) {
471
- return null;
472
- }
473
- return (`pptx.dml.color exports RGBColor (all-caps RGB), not ${name}: ` +
474
- 'from pptx.dml.color import RGBColor');
475
- }
476
323
  export function checkPythonSource(command) {
477
324
  const source = unwrapPythonSource(command) ?? command;
478
325
  const literal = stringLiteralProgramHint(source);
@@ -483,26 +330,6 @@ export function checkPythonSource(command) {
483
330
  if (collapsed) {
484
331
  return collapsed;
485
332
  }
486
- const mangled = mangledPptxApiHint(source);
487
- if (mangled) {
488
- return mangled;
489
- }
490
- const rgb = wrongRgbColorHint(source);
491
- if (rgb) {
492
- return rgb;
493
- }
494
- const fpdfKeyword = fpdfSizeKeywordHint(source);
495
- if (fpdfKeyword) {
496
- return fpdfKeyword;
497
- }
498
- const fpdfLatin1 = fpdfNonLatin1Hint(source);
499
- if (fpdfLatin1) {
500
- return fpdfLatin1;
501
- }
502
- const fpdfText = fpdfTextCallHint(source);
503
- if (fpdfText) {
504
- return fpdfText;
505
- }
506
333
  const first = command.trimStart().split('\n')[0] ?? '';
507
334
  if (!SHELL_WRAPPER.test(first)) {
508
335
  return null;
@@ -511,7 +338,7 @@ export function checkPythonSource(command) {
511
338
  return null;
512
339
  }
513
340
  if (/^pip3?\b/.test(first) || /^(python3?|py)\s+-m\s+pip\b/.test(first)) {
514
- return 'language "python" does not run shell commands: install packages via the packages argument (e.g. packages: ["python-pptx==1.0.2"]), not pip';
341
+ return 'language "python" does not run shell commands: install packages via the packages argument (e.g. packages: ["requests"]), not pip';
515
342
  }
516
343
  if (unwrapPythonSource(command)) {
517
344
  return null;
@@ -522,295 +349,8 @@ export function syntaxErrorHint(stderr, source) {
522
349
  if (!/SyntaxError/.test(stderr))
523
350
  return null;
524
351
  return (checkCollapsedCompound(source) ??
525
- mangledPptxApiHint(source) ??
526
352
  'SyntaxError means the Python source is invalid as written — keep real newlines for for/if/with/def bodies; do not collapse a multi-line program into one semicolon-separated line.');
527
353
  }
528
- export function checkPptxBuildSource(source, outputs, packages) {
529
- if (!outputs?.some((name) => name.toLowerCase().endsWith('.pptx'))) {
530
- return null;
531
- }
532
- const pkgPptx = (packages ?? []).some((p) => {
533
- const name = (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase();
534
- return name === 'python-pptx' || name === 'pptx';
535
- });
536
- const srcPptx = /\bpptx\b|\bPresentation\s*\(/.test(source);
537
- if (!pkgPptx && !srcPptx) {
538
- return null;
539
- }
540
- const builds = /\bPresentation\s*\(/.test(source) && /\.save\s*\(/.test(source);
541
- if (builds) {
542
- return null;
543
- }
544
- return ('outputs declare a .pptx but command does not build one: pass multi-line python-pptx ' +
545
- 'source that constructs Presentation(...) and calls save("deck.pptx") matching an ' +
546
- 'outputs entry. Do not use python -c, os.listdir, pip, or a different package pin — ' +
547
- 'language="python" already runs bare source, and packages must stay ["python-pptx==1.0.2"].');
548
- }
549
- export function tripleQuoteAt(source, index) {
550
- if (source.startsWith('"""', index))
551
- return '"""';
552
- return source.startsWith("'''", index) ? "'''" : null;
553
- }
554
- export function isEscaped(source, index) {
555
- let slashes = 0;
556
- while (index - slashes > 0 && source[index - slashes - 1] === '\\') {
557
- slashes++;
558
- }
559
- return slashes % 2 === 1;
560
- }
561
- function splitCallArgs(source, open) {
562
- const args = [];
563
- let arg = '';
564
- let depth = 0;
565
- let quote = null;
566
- for (let i = open; i < source.length; i++) {
567
- const ch = source.charAt(i);
568
- if (quote) {
569
- arg += ch;
570
- if (ch === quote && !isEscaped(source, i)) {
571
- quote = null;
572
- }
573
- continue;
574
- }
575
- if (ch === '"' || ch === "'") {
576
- quote = ch;
577
- arg += ch;
578
- continue;
579
- }
580
- if (ch === '(' || ch === '[' || ch === '{') {
581
- depth++;
582
- if (depth > 1) {
583
- arg += ch;
584
- }
585
- continue;
586
- }
587
- if (ch === ')' || ch === ']' || ch === '}') {
588
- depth--;
589
- if (depth === 0) {
590
- args.push(arg);
591
- return args.map((a) => a.trim());
592
- }
593
- arg += ch;
594
- continue;
595
- }
596
- if (ch === ',' && depth === 1) {
597
- args.push(arg);
598
- arg = '';
599
- continue;
600
- }
601
- arg += ch;
602
- }
603
- return null;
604
- }
605
- const MIN_MEANT_EMU = 12700;
606
- function bareEmuMistake(arg) {
607
- if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(arg))
608
- return false;
609
- const value = Math.abs(Number(arg));
610
- return value !== 0 && value < MIN_MEANT_EMU;
611
- }
612
- function stripCommentsAndStrings(source) {
613
- let out = '';
614
- let i = 0;
615
- while (i < source.length) {
616
- const ch = source.charAt(i);
617
- const triple = tripleQuoteAt(source, i);
618
- const quote = triple ?? (ch === '"' || ch === "'" ? ch : null);
619
- if (quote) {
620
- i += quote.length;
621
- while (i < source.length && !(source.startsWith(quote, i) && !isEscaped(source, i))) {
622
- i++;
623
- }
624
- out += '""';
625
- i += quote.length;
626
- continue;
627
- }
628
- if (ch === '#') {
629
- while (i < source.length && source[i] !== '\n') {
630
- i++;
631
- }
632
- continue;
633
- }
634
- out += ch;
635
- i++;
636
- }
637
- return out;
638
- }
639
- const LENGTH_LIBRARY = {
640
- pptx: {
641
- module: 'pptx.util',
642
- effect: 'the shape collapses to an invisible box in the top-left corner and the deck is delivered broken',
643
- example: 'add_textbox(Inches(1), Inches(1), Inches(8), Inches(1.5))'
644
- },
645
- docx: {
646
- module: 'docx.shared',
647
- effect: 'the image collapses to nothing and the document is delivered broken',
648
- example: 'add_picture("chart.png", width=Inches(5.5))'
649
- }
650
- };
651
- function lengthLibrary(source, packages) {
652
- const names = (packages ?? []).map((p) => (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase());
653
- if (names.includes('python-pptx') || names.includes('pptx')) {
654
- return 'pptx';
655
- }
656
- if (names.includes('python-docx') || names.includes('docx')) {
657
- return 'docx';
658
- }
659
- if (/\bpptx\b|\bPresentation\s*\(/.test(source)) {
660
- return 'pptx';
661
- }
662
- if (/\bdocx\b|\bDocument\s*\(/.test(source)) {
663
- return 'docx';
664
- }
665
- return null;
666
- }
667
- const LENGTH_KEYWORDS = ['left', 'top', 'width', 'height'];
668
- const FIRST_LENGTH_ARG = { add_textbox: 0, add_picture: 1 };
669
- function callLibrary(method, before, fallback) {
670
- if (method === 'add_textbox')
671
- return 'pptx';
672
- if (/\bshapes\s*\.\s*$/.test(before))
673
- return 'pptx';
674
- if (/\b(?:doc|document|run|paragraph|cell)\s*\.\s*$/.test(before)) {
675
- return 'docx';
676
- }
677
- return fallback;
678
- }
679
- export function checkBareLengthArgs(source, packages) {
680
- const clean = stripCommentsAndStrings(source);
681
- const library = lengthLibrary(clean, packages);
682
- if (!library) {
683
- return null;
684
- }
685
- for (const method of ['add_textbox', 'add_picture']) {
686
- const calls = new RegExp(`\\b${method}\\s*\\(`, 'g');
687
- let call;
688
- while ((call = calls.exec(clean)) !== null) {
689
- const args = splitCallArgs(clean, calls.lastIndex - 1);
690
- if (!args) {
691
- continue;
692
- }
693
- const offender = args.find((arg, index) => {
694
- const eq = arg.indexOf('=');
695
- const name = eq === -1 ? '' : arg.slice(0, eq).trim();
696
- if (eq !== -1 && /^[A-Za-z_]\w*$/.test(name)) {
697
- return LENGTH_KEYWORDS.includes(name) && bareEmuMistake(arg.slice(eq + 1).trim());
698
- }
699
- return index >= FIRST_LENGTH_ARG[method] && bareEmuMistake(arg);
700
- });
701
- if (offender !== undefined) {
702
- const before = clean.slice(Math.max(0, call.index - 40), call.index);
703
- return bareLengthError(`${method}(...)`, offender, callLibrary(method, before, library));
704
- }
705
- }
706
- }
707
- if (library === 'pptx') {
708
- const assignments = /\bslide_(?:width|height)\s*=\s*([^\n]+)/g;
709
- let assigned;
710
- while ((assigned = assignments.exec(clean)) !== null) {
711
- const value = (assigned[1] ?? '').trim();
712
- if (bareEmuMistake(value)) {
713
- return bareLengthError('slide_width / slide_height', value, library);
714
- }
715
- }
716
- }
717
- return null;
718
- }
719
- function bareLengthError(where, value, library) {
720
- const fix = LENGTH_LIBRARY[library];
721
- return (`${where} was given the bare number ${value} where a typed length is required: ` +
722
- `python-${library} reads a bare number as EMU (914400 to the inch), so ${fix.effect}. ` +
723
- 'Nothing raises, so this never reaches you as an error. Wrap every position and size ' +
724
- `with Inches(...), Pt(...) or Emu(...) from ${fix.module}, e.g. ${fix.example}. ` +
725
- 'Do not rescale the raw numbers.');
726
- }
727
- export function checkPdfBuildSource(source, outputs, packages) {
728
- if (!outputs?.some((name) => name.toLowerCase().endsWith('.pdf')))
729
- return null;
730
- const pkgPdf = (packages ?? []).some((p) => {
731
- const name = (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase();
732
- return name === 'fpdf2' || name === 'pypdf';
733
- });
734
- const srcPdf = /\b(fpdf|FPDF|pypdf|PdfWriter|PdfReader)\b/.test(source);
735
- if (!pkgPdf && !srcPdf) {
736
- return null;
737
- }
738
- const builds = /\.output\s*\(|\.write\s*\(/.test(source);
739
- if (builds) {
740
- return null;
741
- }
742
- return ('outputs declare a .pdf but command never writes one: pass multi-line Python that ' +
743
- 'builds the document and saves it under the declared name — fpdf2 ends with ' +
744
- 'pdf.output("name.pdf"), pypdf with writer.write("name.pdf"). Do not send diagnostics ' +
745
- 'like os.listdir or print-only scripts while outputs still lists the file.');
746
- }
747
- export function stripCommentsAndDocstrings(source) {
748
- let out = '';
749
- let i = 0;
750
- while (i < source.length) {
751
- const ch = source.charAt(i);
752
- const triple = tripleQuoteAt(source, i);
753
- if (triple) {
754
- i += triple.length;
755
- while (i < source.length && !(source.startsWith(triple, i) && !isEscaped(source, i))) {
756
- i++;
757
- }
758
- out += '""';
759
- i += triple.length;
760
- continue;
761
- }
762
- if (ch === '"' || ch === "'") {
763
- const start = i;
764
- i++;
765
- while (i < source.length && !(source[i] === ch && !isEscaped(source, i))) {
766
- i++;
767
- }
768
- i++;
769
- out += source.slice(start, Math.min(i, source.length));
770
- continue;
771
- }
772
- if (ch === '#') {
773
- while (i < source.length && source[i] !== '\n') {
774
- i++;
775
- }
776
- continue;
777
- }
778
- out += ch;
779
- i++;
780
- }
781
- return out;
782
- }
783
- export const PDF_READ = /(?:PdfReader|append)\s*\(\s*['"]([\w.-]+\.pdf)['"]|clone_from\s*=\s*['"]([\w.-]+\.pdf)['"]|open\s*\(\s*['"]([\w.-]+\.pdf)['"]\s*(?:,\s*(?:mode\s*=\s*)?['"](?<mode>[rwaxb+]+)['"])?/g;
784
- export const OFFICE_READ = /(?:load_workbook|Document|Presentation)\s*\(\s*['"]([\w.-]+\.(?:xlsx|xlsm|docx|pptx))['"]/g;
785
- export function checkUnstagedRead(source, inputs) {
786
- const staged = new Set((inputs ?? []).map((i) => i.path));
787
- const clean = stripCommentsAndDocstrings(source);
788
- for (const pattern of [PDF_READ, OFFICE_READ]) {
789
- const reads = new RegExp(pattern.source, 'g');
790
- let match;
791
- while ((match = reads.exec(clean)) !== null) {
792
- const name = match.slice(1).find((group) => group !== undefined) ?? '';
793
- const mode = match.groups?.mode;
794
- if (mode !== undefined && /[wax]/.test(mode)) {
795
- continue;
796
- }
797
- if (staged.has(name)) {
798
- continue;
799
- }
800
- const before = clean.slice(0, match.index);
801
- if (before.includes(`"${name}"`) || before.includes(`'${name}'`)) {
802
- continue;
803
- }
804
- return (`${name} does not exist in the working directory — it starts empty on every run, so ` +
805
- 'files from earlier calls are gone. Stage the attachment via inputs: ' +
806
- `[{ "attachmentId": "<id from the tool result that produced it, or from the ` +
807
- `[Attached file …] line if the user uploaded it>", "path": "${name}" }] ` +
808
- 'and open it by that bare name; if no id for it appears anywhere in the chat, ' +
809
- 'ask the user to attach the file.');
810
- }
811
- }
812
- return null;
813
- }
814
354
  const ATTACHMENT_ID = /^[0-9a-f]{32}$/i;
815
355
  export function missingFileHint(stderr) {
816
356
  const name = /FileNotFoundError: \[Errno \d+\] No such file or directory: '([^']+)'/.exec(stderr)?.[1];
@@ -935,6 +475,39 @@ function prepare(args, cwd) {
935
475
  }
936
476
  return { packages, inputs, outputs, workdir };
937
477
  }
478
+ const OFFICE_MODULES = {
479
+ pptx: { tool: 'pptx', skill: 'presentations' },
480
+ docx: { tool: 'docx', skill: 'word' },
481
+ openpyxl: { tool: 'xlsx', skill: 'excel' }
482
+ };
483
+ const OFFICE_PACKAGES = {
484
+ 'python-pptx': 'pptx',
485
+ 'python-docx': 'docx',
486
+ openpyxl: 'openpyxl'
487
+ };
488
+ const OFFICE_IMPORT = /^\s*(?:from\s+(pptx|docx|openpyxl)\b|import\s+(?:[\w.]+\s*(?:as\s+\w+\s*)?,\s*)*(pptx|docx|openpyxl)\b)/m;
489
+ function officeAnswer(module, toolNames) {
490
+ const office = OFFICE_MODULES[module];
491
+ if (!office) {
492
+ return null;
493
+ }
494
+ const call = toolNames?.includes(office.tool)
495
+ ? `call the ${office.tool} tool`
496
+ : `load the "${office.skill}" skill and call the ${office.tool} tool it grants`;
497
+ return `module "${module}" is not installed: Office files are read and edited without Python here. Do not retry in Python; ${call} with the same content.`;
498
+ }
499
+ export function officeModuleHint(stderr, toolNames) {
500
+ const module = /ModuleNotFoundError: No module named '([^'.]+)/.exec(stderr)?.[1];
501
+ return module ? officeAnswer(module, toolNames) : null;
502
+ }
503
+ export function officeImportError(source, { packages, toolNames }) {
504
+ const imported = OFFICE_IMPORT.exec(source);
505
+ const declared = (packages ?? [])
506
+ .map((spec) => OFFICE_PACKAGES[spec.split(/[=<>!~[ ]/)[0]?.toLowerCase() ?? ''])
507
+ .find(Boolean);
508
+ const module = imported?.[1] ?? imported?.[2] ?? declared;
509
+ return module ? officeAnswer(module, toolNames) : null;
510
+ }
938
511
  export function missingModuleHint(stderr, { packages, sandbox = false, network = false } = {}) {
939
512
  const module = /ModuleNotFoundError: No module named '([^']+)'/.exec(stderr)?.[1];
940
513
  if (!module) {
@@ -968,53 +541,6 @@ export function missingModuleHint(stderr, { packages, sandbox = false, network =
968
541
  }
969
542
  return `module "${root}" is not installed: add "${spec}" to packages (packages: ["${spec}"]) and run the same source again. There is no pip.`;
970
543
  }
971
- export function nonePlaceholderHint(stderr) {
972
- if (!/AttributeError: 'NoneType' object has no attribute '(text|text_frame)'/.test(stderr)) {
973
- return null;
974
- }
975
- return 'a placeholder the layout does not have reads as None: the blank layout has no title and no body, so shapes.title is None there. Use a layout that owns the placeholder (title+content is commonly index 1), or keep the blank layout and add every text box explicitly with shapes.add_textbox(left, top, width, height).';
976
- }
977
- export function slideShapesHint(stderr) {
978
- const attribute = /AttributeError: 'Slide' object has no attribute '(add_picture|add_textbox|addpicture|addtextbox)'/.exec(stderr)?.[1];
979
- if (!attribute) {
980
- return null;
981
- }
982
- const method = attribute.startsWith('addp') ? 'add_picture' : 'add_textbox';
983
- return `a Slide has no add_* methods of its own — they live on its shapes collection: slide.shapes.${method}(...), not slide.${method}(...).`;
984
- }
985
- export function shapeFontHint(stderr) {
986
- const attribute = /AttributeError: '\w*(?:Placeholder|Shape|Slide)\w*' object has no attribute '(font|alignment|text_align|word_wrap|paragraphs|add_paragraph)'/.exec(stderr)?.[1];
987
- if (!attribute) {
988
- return null;
989
- }
990
- return `"${attribute}" belongs to the text frame or a paragraph inside it, not to the shape: go through shape.text_frame first (shape.text_frame.word_wrap, shape.text_frame.paragraphs[0].font.size, shape.text_frame.paragraphs[0].alignment). shape.text is the one shortcut that reads through to the frame.`;
991
- }
992
- export function shapesBackgroundHint(stderr) {
993
- if (!/AttributeError: '\w*(?:Shapes|Shape|Placeholder)\w*' object has no attribute 'background'/.test(stderr)) {
994
- return null;
995
- }
996
- return "'background' lives on the slide, not on its shapes: fill = slide.background.fill; fill.solid(); fill.fore_color.rgb = RGBColor(...). A shape is tinted through its own shape.fill the same way.";
997
- }
998
- export function paragraphFillHint(stderr) {
999
- if (!/AttributeError: '(?:_Paragraph|_Run|TextFrame)' object has no attribute 'fill'/.test(stderr)) {
1000
- return null;
1001
- }
1002
- return "text has no 'fill': color it through the font — paragraph.font.color.rgb = RGBColor(...). A fill belongs to a shape (shape.fill) or to slide.background.fill, never to a paragraph or text frame.";
1003
- }
1004
- export function readOnlyPropertyHint(stderr) {
1005
- if (!/AttributeError: can't set attribute/.test(stderr))
1006
- return null;
1007
- if (!/pptx[/\\]util\.py/.test(stderr))
1008
- return null;
1009
- return 'a python-pptx container is read-only: font, text_frame, paragraphs, runs and shapes cannot be assigned, only reached through. Set the leaf property instead — paragraph.font.size = Pt(20) and paragraph.font.bold = True, not paragraph.font = …; and write text with text_frame.text or paragraph.text, never text_frame = …';
1010
- }
1011
- export function paragraphIndexHint(stderr, outputs) {
1012
- if (!/IndexError: tuple index out of range/.test(stderr))
1013
- return null;
1014
- if (!outputs.some((name) => name.toLowerCase().endsWith('.pptx')))
1015
- return null;
1016
- return 'a new text frame holds exactly one paragraph, so text_frame.paragraphs[1] does not exist yet: write the first through paragraphs[0] (or text_frame.text) and create each one after it with text_frame.add_paragraph(), which returns the new paragraph. A paragraph has .text, .font and .alignment — it has no .paragraphs and no .add_paragraph of its own.';
1017
- }
1018
544
  export function shellNameHint(stderr) {
1019
545
  const name = /NameError: name '(python3?|py|pip3?)' is not defined/.exec(stderr)?.[1];
1020
546
  if (!name) {
@@ -1060,110 +586,6 @@ export function packageFetchFailedHint(stderr) {
1060
586
  function fail(stderr) {
1061
587
  return { stdout: '', stderr, exitCode: 1 };
1062
588
  }
1063
- export const IMAGE_INPUT = /\.(png|jpe?g|gif|webp|bmp|heic|heif|avif|tiff?)$/i;
1064
- const PPT_MEDIA = b4a.from('ppt/media/');
1065
- const deckAttachedThisTurn = new WeakMap();
1066
- export function pptxHasEmbeddedMedia(bytes) {
1067
- outer: for (let i = 0; i <= bytes.length - PPT_MEDIA.length; i++) {
1068
- for (let j = 0; j < PPT_MEDIA.length; j++) {
1069
- if (bytes[i + j] !== PPT_MEDIA[j])
1070
- continue outer;
1071
- }
1072
- return true;
1073
- }
1074
- return false;
1075
- }
1076
- export function softFailedDeckHint(stdout, exitCode, source) {
1077
- if (exitCode !== 0)
1078
- return null;
1079
- if (!/\bPresentation\s*\(/.test(source))
1080
- return null;
1081
- if (!/Image loading error|expected if file not found|cannot import name ['"]RgbColor['"]/i.test(stdout)) {
1082
- return null;
1083
- }
1084
- return ('the deck soft-failed (caught an image/import error and continued): remove try/except ' +
1085
- 'around imports and add_picture, fix the error, and rerun once — a partial deck must not be saved');
1086
- }
1087
- export function stagedImagesMissingFromDeck(result, { inputs, outputs, source }) {
1088
- if (result.exitCode !== 0)
1089
- return null;
1090
- if (!/\bPresentation\s*\(/.test(source))
1091
- return null;
1092
- const images = (inputs ?? []).filter((i) => IMAGE_INPUT.test(i.path));
1093
- const [first] = images;
1094
- if (!first) {
1095
- return null;
1096
- }
1097
- for (const name of outputs ?? []) {
1098
- if (!name.toLowerCase().endsWith('.pptx'))
1099
- continue;
1100
- const bytes = result.files?.get(name);
1101
- if (!bytes) {
1102
- continue;
1103
- }
1104
- if (!pptxHasEmbeddedMedia(bytes)) {
1105
- return (`staged image(s) ${images.map((i) => i.path).join(', ')} were not embedded: ` +
1106
- `call slide.shapes.add_picture("${first.path}", …) without try/except, then save`);
1107
- }
1108
- }
1109
- return null;
1110
- }
1111
- export function checkStagedImagesUsed(source, inputs, outputs) {
1112
- if (!inputs?.length)
1113
- return null;
1114
- if (!outputs?.some((n) => n.toLowerCase().endsWith('.pptx')))
1115
- return null;
1116
- if (!/\bPresentation\s*\(/.test(source)) {
1117
- return null;
1118
- }
1119
- const images = inputs.filter((i) => IMAGE_INPUT.test(i.path));
1120
- if (!images.length) {
1121
- return null;
1122
- }
1123
- if (!/\.add_picture\s*\(/.test(source)) {
1124
- return ('inputs stage image(s) but command never calls slide.shapes.add_picture(...): ' +
1125
- 'embed each staged path or drop it from inputs');
1126
- }
1127
- const missing = images.filter((i) => !source.includes(`"${i.path}"`) && !source.includes(`'${i.path}'`));
1128
- if (missing.length) {
1129
- return (`inputs stage ${missing.map((m) => m.path).join(', ')} but command never opens those files — ` +
1130
- `pass the bare path to slide.shapes.add_picture("…")`);
1131
- }
1132
- if (/\btry\s*:(?:(?!\bexcept\b|\bfinally\b)[\s\S]){0,1200}?\badd_picture\b/.test(source)) {
1133
- return 'do not wrap add_picture in try/except: if the image fails the run must fail so you can fix it';
1134
- }
1135
- return null;
1136
- }
1137
- export function checkTurnImagesStaged(source, { inputs, outputs, turnImages }) {
1138
- if (!turnImages?.length)
1139
- return null;
1140
- if (!outputs?.some((n) => n.toLowerCase().endsWith('.pptx')))
1141
- return null;
1142
- if (!/\bPresentation\s*\(/.test(source)) {
1143
- return null;
1144
- }
1145
- if ((inputs ?? []).some((i) => i.attachmentId === undefined)) {
1146
- return null;
1147
- }
1148
- return (`the user's message has ${turnImages.length} uploaded image(s) but inputs stage none of them — ` +
1149
- 'add one id-less { "path": "…" } entry per image and embed each with slide.shapes.add_picture(...)');
1150
- }
1151
- export function deckDeliveredStop(attachments, exitCode, source) {
1152
- if (exitCode !== 0 || !attachments?.length)
1153
- return null;
1154
- if (source !== undefined && !/\bpptx\b|\bPresentation\s*\(/.test(source)) {
1155
- return null;
1156
- }
1157
- const decks = attachments.filter((a) => a.fileName.toLowerCase().endsWith('.pptx'));
1158
- if (decks.length === 0) {
1159
- return null;
1160
- }
1161
- const names = decks.map((a) => a.fileName).join(', ');
1162
- const verb = decks.length === 1 ? 'is' : 'are';
1163
- return (`${names} ${verb} attached to the chat. STOP: do not call exec again for this deck. ` +
1164
- 'A second exec that rebuilds the same .pptx will be rejected. ' +
1165
- 'Reply to the user with one line naming the file and slide count from stdout — nothing else.');
1166
- }
1167
589
  async function stage(inputs, fetchAttachment, opts = {}) {
1168
590
  if (!inputs?.length)
1169
591
  return undefined;
@@ -1181,37 +603,10 @@ async function collect(result, { declared, attach, runBudget, source }) {
1181
603
  if (!declared?.length || !attach) {
1182
604
  return base;
1183
605
  }
1184
- const pptxBuild = source !== undefined && /\bpptx\b|\bPresentation\s*\(/.test(source);
1185
- const pptxNames = declared.filter((n) => n.toLowerCase().endsWith('.pptx') && result.files?.has(n));
1186
- const wouldAttachPptx = result.exitCode === 0 && pptxBuild && pptxNames.length > 0;
1187
- if (wouldAttachPptx) {
1188
- const attached = deckAttachedThisTurn.get(attach);
1189
- if (attached) {
1190
- const collision = pptxNames.find((n) => attached.has(n.toLowerCase()));
1191
- if (collision) {
1192
- return fail(`"${collision}" was already attached this turn — do not rebuild it; reply to the user with the file name and slide count`);
1193
- }
1194
- }
1195
- }
1196
606
  const { attachments, missingOutputs, rejectedOutputs } = await attachOutputs(attach, (result.exitCode === 0 && result.files) || new Map(), { declared, runBudget });
1197
607
  const rejected = [...(result.rejected ?? []), ...rejectedOutputs];
1198
- const stop = deckDeliveredStop(attachments, result.exitCode, source);
1199
- if (stop && result.exitCode === 0) {
1200
- let attached = deckAttachedThisTurn.get(attach);
1201
- if (!attached) {
1202
- attached = new Set();
1203
- deckAttachedThisTurn.set(attach, attached);
1204
- }
1205
- for (const n of pptxNames) {
1206
- attached.add(n.toLowerCase());
1207
- }
1208
- }
1209
- const stdout = stop && result.exitCode === 0
1210
- ? `${base.stdout}${base.stdout && !base.stdout.endsWith('\n') ? '\n' : ''}${stop}`
1211
- : base.stdout;
1212
608
  return {
1213
609
  ...base,
1214
- stdout,
1215
610
  attachments: attachments.length > 0 ? attachments : undefined,
1216
611
  missingOutputs: missingOutputs.length > 0 ? missingOutputs : undefined,
1217
612
  rejectedOutputs: rejected.length > 0 ? rejected : undefined
@@ -1242,39 +637,11 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
1242
637
  if (sourceError) {
1243
638
  return fail(sourceError);
1244
639
  }
1245
- const source = normalizeFpdfSource(unwrapPythonSource(command) ?? command);
640
+ const source = unwrapPythonSource(command) ?? command;
1246
641
  const prepared = prepare(args, options.cwd);
1247
642
  if ('error' in prepared) {
1248
643
  return fail(prepared.error);
1249
644
  }
1250
- const pptxError = checkPptxBuildSource(source, prepared.outputs, prepared.packages);
1251
- if (pptxError) {
1252
- return fail(pptxError);
1253
- }
1254
- const pdfError = checkPdfBuildSource(source, prepared.outputs, prepared.packages);
1255
- if (pdfError) {
1256
- return fail(pdfError);
1257
- }
1258
- const bareLength = checkBareLengthArgs(source, prepared.packages);
1259
- if (bareLength) {
1260
- return fail(bareLength);
1261
- }
1262
- const unstagedRead = checkUnstagedRead(source, prepared.inputs);
1263
- if (unstagedRead) {
1264
- return fail(unstagedRead);
1265
- }
1266
- const stagedUse = checkStagedImagesUsed(source, prepared.inputs, prepared.outputs);
1267
- if (stagedUse) {
1268
- return fail(stagedUse);
1269
- }
1270
- const turnStage = checkTurnImagesStaged(source, {
1271
- inputs: prepared.inputs,
1272
- outputs: prepared.outputs,
1273
- turnImages
1274
- });
1275
- if (turnStage) {
1276
- return fail(turnStage);
1277
- }
1278
645
  if (prepared.outputs?.length && !attach) {
1279
646
  return fail('this device cannot persist produced files');
1280
647
  }
@@ -1306,40 +673,16 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
1306
673
  hosts: gated ? namedHosts(source) : undefined,
1307
674
  askConnect: gated ? confirmConnect : undefined
1308
675
  });
1309
- const soft = softFailedDeckHint(result.stdout, result.exitCode, source);
1310
- if (soft) {
1311
- return fail(soft);
1312
- }
1313
- const missingPic = stagedImagesMissingFromDeck(result, {
1314
- inputs: prepared.inputs,
1315
- outputs: prepared.outputs,
1316
- source
1317
- });
1318
- if (missingPic) {
1319
- return fail(missingPic);
1320
- }
1321
676
  const hint = packageFetchFailedHint(result.stderr) ??
1322
677
  nativeWheelHint(result.stderr) ??
678
+ officeModuleHint(result.stderr, toolNames) ??
1323
679
  missingModuleHint(result.stderr, { packages, sandbox, network: reach }) ??
1324
680
  pipFromSourceHint(result.stderr, source, { packages, sandbox }) ??
1325
681
  noProcessHint(result.stderr, sandbox) ??
1326
682
  noNetworkHint(result.stderr, toolNames, reach) ??
1327
683
  missingFileHint(result.stderr) ??
1328
- rgbColorImportHint(result.stderr) ??
1329
- nonePlaceholderHint(result.stderr) ??
1330
- slideShapesHint(result.stderr) ??
1331
- shapeFontHint(result.stderr) ??
1332
- shapesBackgroundHint(result.stderr) ??
1333
- paragraphFillHint(result.stderr) ??
1334
- readOnlyPropertyHint(result.stderr) ??
1335
- paragraphIndexHint(result.stderr, prepared.outputs ?? []) ??
1336
- fpdfTextErrorHint(result.stderr) ??
1337
- fpdfKeywordErrorHint(result.stderr) ??
1338
- fpdfHorizontalSpaceHint(result.stderr) ??
1339
- fpdfUnicodeErrorHint(result.stderr) ??
1340
684
  shellNameHint(result.stderr) ??
1341
- syntaxErrorHint(result.stderr, source) ??
1342
- mangledPptxApiHint(source);
685
+ syntaxErrorHint(result.stderr, source);
1343
686
  const hinted = hint ? { ...result, stderr: `${result.stderr}\n${hint}` } : result;
1344
687
  const installed = install && result.exitCode === 0
1345
688
  ? {