@kodocagent/cli 0.4.5 → 0.5.0

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/dist/index.js CHANGED
@@ -9739,9 +9739,9 @@ var require_load = __commonJS({
9739
9739
  var require_lib3 = __commonJS({
9740
9740
  "../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/index.js"(exports, module) {
9741
9741
  "use strict";
9742
- function JSZip5() {
9743
- if (!(this instanceof JSZip5)) {
9744
- return new JSZip5();
9742
+ function JSZip7() {
9743
+ if (!(this instanceof JSZip7)) {
9744
+ return new JSZip7();
9745
9745
  }
9746
9746
  if (arguments.length) {
9747
9747
  throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
@@ -9750,7 +9750,7 @@ var require_lib3 = __commonJS({
9750
9750
  this.comment = null;
9751
9751
  this.root = "";
9752
9752
  this.clone = function() {
9753
- var newObj = new JSZip5();
9753
+ var newObj = new JSZip7();
9754
9754
  for (var i in this) {
9755
9755
  if (typeof this[i] !== "function") {
9756
9756
  newObj[i] = this[i];
@@ -9759,16 +9759,16 @@ var require_lib3 = __commonJS({
9759
9759
  return newObj;
9760
9760
  };
9761
9761
  }
9762
- JSZip5.prototype = require_object();
9763
- JSZip5.prototype.loadAsync = require_load();
9764
- JSZip5.support = require_support();
9765
- JSZip5.defaults = require_defaults();
9766
- JSZip5.version = "3.10.1";
9767
- JSZip5.loadAsync = function(content, options) {
9768
- return new JSZip5().loadAsync(content, options);
9762
+ JSZip7.prototype = require_object();
9763
+ JSZip7.prototype.loadAsync = require_load();
9764
+ JSZip7.support = require_support();
9765
+ JSZip7.defaults = require_defaults();
9766
+ JSZip7.version = "3.10.1";
9767
+ JSZip7.loadAsync = function(content, options) {
9768
+ return new JSZip7().loadAsync(content, options);
9769
9769
  };
9770
- JSZip5.external = require_external();
9771
- module.exports = JSZip5;
9770
+ JSZip7.external = require_external();
9771
+ module.exports = JSZip7;
9772
9772
  }
9773
9773
  });
9774
9774
 
@@ -9894,6 +9894,15 @@ function detectPii(text3) {
9894
9894
  function summarizePii(findings) {
9895
9895
  return findings.map((f) => `${f.type} ${f.count}\uAC74`).join(", ");
9896
9896
  }
9897
+ function redactText(text3) {
9898
+ if (!text3) return { text: text3 ?? "", findings: [] };
9899
+ let result = text3;
9900
+ const findings = detectPii(text3);
9901
+ for (const { re, mask } of PATTERNS) {
9902
+ result = result.replace(new RegExp(re.source, re.flags), (m) => mask(m));
9903
+ }
9904
+ return { text: result, findings };
9905
+ }
9897
9906
 
9898
9907
  // ../core/dist/index.js
9899
9908
  import { stepCountIs, streamText } from "ai";
@@ -9948,7 +9957,7 @@ var EDIT_SAFETY_SECTION = `## \uD3B8\uC9D1 \uC548\uC804 \uADDC\uCE59
9948
9957
  4. \uC804\uBB38 \uC6A9\uC5B4\uB294 \uC784\uC758\uB85C \uB2E4\uB978 \uD45C\uD604\uC73C\uB85C \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uBB38\uC11C \uC804\uCCB4\uC5D0\uC11C \uC77C\uAD00\uB418\uAC8C \uC720\uC9C0\uD569\uB2C8\uB2E4.
9949
9958
  5. \uACC4\uC57D\uC11C\xB7\uC57D\uAD00\xB7\uACF5\uC2DC\xB7\uB17C\uBB38 \uB4F1 \uBC95\uC801\xB7\uACF5\uC2DD \uBB38\uC11C\uB294 \uB2E8\uC5B4 \uD558\uB098\uAC00 \uC758\uBBF8\uB97C \uBC14\uAFC0 \uC218 \uC788\uC73C\uBBC0\uB85C, \uD45C\uD604\uC744 \uC790\uB3D9\uC73C\uB85C \uB2E4\uB4EC\uC9C0 \uB9D0\uACE0 \uC0AC\uC6A9\uC790\uAC00 \uC9C0\uC815\uD55C \uBCC0\uACBD\uB9CC \uC218\uD589\uD569\uB2C8\uB2E4.
9950
9959
  6. \uC758\uBBF8\uAC00 \uBC14\uB014 \uC218 \uC788\uB294 \uC218\uC815\uC740 \uC2E4\uD589 \uC804\uC5D0 \uADF8 \uC0AC\uC2E4\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC54C\uB9AC\uACE0, \uC218\uC815 \uC81C\uC548\uC758 \uC694\uC57D(summary)\uC5D0\uB294 \uBB34\uC5C7\uC744\xB7\uC65C \uBC14\uAFB8\uB294\uC9C0 \uD568\uAED8 \uC801\uC2B5\uB2C8\uB2E4.
9951
- 7. \uAC1C\uC778\uC815\uBCF4(\uC8FC\uBBFC\uB4F1\uB85D\uBC88\uD638\xB7\uC804\uD654\uBC88\uD638\xB7\uC774\uBA54\uC77C\xB7\uACC4\uC88C\xB7\uCE74\uB4DC\uBC88\uD638 \uB4F1)\uAC00 \uC788\uC744 \uC218 \uC788\uB294 \uBB38\uC11C\uB97C \uB2E4\uB8F0 \uB54C \uC8FC\uC758\uD558\uACE0, \uC0AC\uC6A9\uC790\uAC00 \uAC1C\uC778\uC815\uBCF4 \uC810\uAC80\uC744 \uC694\uCCAD\uD558\uBA74 \`scan_pii\` \uB3C4\uAD6C\uB85C \uD655\uC778\uD569\uB2C8\uB2E4.`;
9960
+ 7. \uAC1C\uC778\uC815\uBCF4(\uC8FC\uBBFC\uB4F1\uB85D\uBC88\uD638\xB7\uC804\uD654\uBC88\uD638\xB7\uC774\uBA54\uC77C\xB7\uACC4\uC88C\xB7\uCE74\uB4DC\uBC88\uD638 \uB4F1) \uCC98\uB9AC: \uC0AC\uC6A9\uC790\uAC00 **\uD655\uC778\xB7\uC810\uAC80\uB9CC** \uC6D0\uD558\uBA74 \`scan_pii\`(\uC77D\uAE30 \uC804\uC6A9)\uB85C \uBCF4\uC5EC\uC8FC\uACE0, **\uAC00\uB9AC\uAE30\xB7\uB9C8\uC2A4\uD0B9\xB7\uBE44\uC2DD\uBCC4\xB7\uAC1C\uC778\uC815\uBCF4 \uC0AD\uC81C**\uB97C \uC6D0\uD558\uBA74 \`scan_pii\`\uC5D0\uC11C \uBA48\uCD94\uC9C0 \uB9D0\uACE0 \`propose_redact_pii\`\uB85C \uBC14\uB85C \uC218\uC815\uC548\uC744 \uC81C\uC548\uD558\uC138\uC694(\uC774 \uB3C4\uAD6C\uB3C4 \uC2B9\uC778\uC744 \uAC70\uCE58\uBBC0\uB85C \uC548\uC804\uD569\uB2C8\uB2E4).`;
9952
9961
  var LAW_RULES_SECTION = `## \uBC95\uB839 \uADDC\uCE59
9953
9962
 
9954
9963
  1. \uBC95\uB839 \uC778\uC6A9 \uD615\uC2DD: \u300C\uBC95\uB839\uBA85\u300D \uC81CN\uC870 \uC81CN\uD56D \uC81CN\uD638
@@ -10919,23 +10928,27 @@ import { basename as basename3, extname as extname2, join as join32 } from "path
10919
10928
  var import_jszip = __toESM(require_lib3(), 1);
10920
10929
  var import_jszip2 = __toESM(require_lib3(), 1);
10921
10930
  var import_jszip3 = __toESM(require_lib3(), 1);
10931
+ var import_jszip4 = __toESM(require_lib3(), 1);
10922
10932
  import { z as z3 } from "zod";
10923
10933
  import { readFile as readFile32 } from "fs/promises";
10924
10934
  import { blocksToMarkdown, compare } from "@clazic/kordoc";
10925
10935
  import { z as z22 } from "zod";
10936
+ import { readFile as readFile5 } from "fs/promises";
10937
+ import { extname as extname4 } from "path";
10938
+ import { z as z4 } from "zod";
10926
10939
  import { readFile as readFile4 } from "fs/promises";
10927
10940
  import { extname as extname3 } from "path";
10928
10941
  import { z as z32 } from "zod";
10929
- import { readdir as readdir3, stat as stat3 } from "fs/promises";
10930
- import { extname as extname4, join as join4, relative as relative2 } from "path";
10931
- import { z as z4 } from "zod";
10932
- import { readFile as readFile5 } from "fs/promises";
10942
+ import { readFile as readFile6 } from "fs/promises";
10933
10943
  import { extname as extname5 } from "path";
10934
10944
  import { z as z5 } from "zod";
10935
- import { readFile as readFile6 } from "fs/promises";
10936
- import { extname as extname6 } from "path";
10937
- import { compare as compare2, markdownToHwpx, parse } from "@clazic/kordoc";
10945
+ import { readdir as readdir3, stat as stat3 } from "fs/promises";
10946
+ import { extname as extname6, join as join4, relative as relative2 } from "path";
10938
10947
  import { z as z6 } from "zod";
10948
+ import { readFile as readFile7 } from "fs/promises";
10949
+ import { extname as extname7 } from "path";
10950
+ import { compare as compare2, markdownToHwpx, parse } from "@clazic/kordoc";
10951
+ import { z as z7 } from "zod";
10939
10952
  import {
10940
10953
  Document,
10941
10954
  HeadingLevel,
@@ -10947,41 +10960,45 @@ import {
10947
10960
  TextRun,
10948
10961
  WidthType
10949
10962
  } from "docx";
10950
- import { readFile as readFile7 } from "fs/promises";
10951
- import { extname as extname7 } from "path";
10952
- import { parse as parse2 } from "@clazic/kordoc";
10953
- import { z as z7 } from "zod";
10954
10963
  import { readFile as readFile8 } from "fs/promises";
10955
10964
  import { extname as extname8 } from "path";
10956
- import { extractFormFields, markdownToHwpx as markdownToHwpx2, parse as parse3 } from "@clazic/kordoc";
10957
- var import_jszip4 = __toESM(require_lib3(), 1);
10965
+ import { parse as parse2 } from "@clazic/kordoc";
10958
10966
  import { z as z8 } from "zod";
10959
10967
  import { readFile as readFile9 } from "fs/promises";
10960
10968
  import { extname as extname9 } from "path";
10961
- import ExcelJS from "exceljs";
10969
+ import { extractFormFields, markdownToHwpx as markdownToHwpx2, parse as parse3 } from "@clazic/kordoc";
10962
10970
  import { z as z9 } from "zod";
10963
10971
  import { readFile as readFile10 } from "fs/promises";
10964
- import { extname as extname10 } from "path";
10965
- import { parse as parse4 } from "@clazic/kordoc";
10972
+ import { basename as basename4, extname as extname10 } from "path";
10973
+ var import_jszip5 = __toESM(require_lib3(), 1);
10974
+ var import_jszip6 = __toESM(require_lib3(), 1);
10966
10975
  import { z as z10 } from "zod";
10967
- import { readFile as readFile11, stat as stat4 } from "fs/promises";
10976
+ import { readFile as readFile11 } from "fs/promises";
10968
10977
  import { extname as extname11 } from "path";
10969
- import { parse as parse5 } from "@clazic/kordoc";
10978
+ import ExcelJS from "exceljs";
10970
10979
  import { z as z11 } from "zod";
10971
- import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
10972
- import { z as z12 } from "zod";
10973
10980
  import { readFile as readFile12 } from "fs/promises";
10974
10981
  import { extname as extname12 } from "path";
10975
- import { parse as parse6 } from "@clazic/kordoc";
10982
+ import { parse as parse4 } from "@clazic/kordoc";
10983
+ import { z as z12 } from "zod";
10984
+ import { readFile as readFile13, stat as stat4 } from "fs/promises";
10985
+ import { extname as extname13 } from "path";
10986
+ import { parse as parse5 } from "@clazic/kordoc";
10976
10987
  import { z as z13 } from "zod";
10988
+ import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
10989
+ import { z as z14 } from "zod";
10990
+ import { readFile as readFile14 } from "fs/promises";
10991
+ import { extname as extname14 } from "path";
10992
+ import { parse as parse6 } from "@clazic/kordoc";
10993
+ import { z as z15 } from "zod";
10977
10994
  import { stat as stat6 } from "fs/promises";
10978
- import { extname as extname13 } from "path";
10995
+ import { extname as extname15 } from "path";
10979
10996
  import { markdownToHwpx as markdownToHwpx3 } from "@clazic/kordoc";
10980
- import { z as z14 } from "zod";
10997
+ import { z as z16 } from "zod";
10981
10998
  import { stat as stat7 } from "fs/promises";
10982
- import { extname as extname14 } from "path";
10999
+ import { extname as extname16 } from "path";
10983
11000
  import ExcelJS2 from "exceljs";
10984
- import { z as z15 } from "zod";
11001
+ import { z as z17 } from "zod";
10985
11002
  async function resolveSafePath(cwd, p) {
10986
11003
  const normalizedCwd = normalize(cwd).normalize("NFC");
10987
11004
  const normalizedP = p.normalize("NFC");
@@ -11481,458 +11498,357 @@ var compareDocumentsTool = {
11481
11498
  return output;
11482
11499
  }
11483
11500
  };
11484
- function escapeXml(text3) {
11485
- return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
11501
+ var cellEditItemSchema = z32.object({
11502
+ tableIndex: z32.number().int().nonnegative().optional().describe(
11503
+ "\uC88C\uD45C \uBAA8\uB4DC: 0-based \uD45C \uC778\uB371\uC2A4(read_document\uC758 kordoc \uBE14\uB85D \uC21C\uC11C, \uC911\uCCA9\uD45C \uC81C\uC678). \uB808\uC774\uBE14 \uBAA8\uB4DC\uC5D0\uC120 \uD0D0\uC0C9 \uBC94\uC704 \uC81C\uD55C\uC6A9(\uC120\uD0DD)."
11504
+ ),
11505
+ row: z32.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 rowAddr (0-based)"),
11506
+ col: z32.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 colAddr (0-based)"),
11507
+ label: z32.string().optional().describe(
11508
+ "\uB808\uC774\uBE14 \uBAA8\uB4DC: \uAE30\uC900 \uC140 \uD14D\uC2A4\uD2B8(\uD2B8\uB9BC \uBE44\uAD50). \uC774 \uC140\uC758 direction \uBC29\uD5A5 \uC778\uC811 \uC140\uC5D0 newText\uB97C \uAE30\uB85D. \uC88C\uD45C \uBAA8\uB4DC\uBA74 \uC0DD\uB7B5."
11509
+ ),
11510
+ direction: z32.enum(["right", "below"]).optional().describe("\uB808\uC774\uBE14 \uBAA8\uB4DC \uBC29\uD5A5. \uAE30\uBCF8 right(\uC624\uB978\uCABD \uC140), below(\uC544\uB798 \uC140). \uBCD1\uD569 span \uACE0\uB824."),
11511
+ newText: z32.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
11512
+ expectedText: z32.string().optional().describe(
11513
+ "\uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8(\uC548\uC804 \uAC80\uC99D\uC6A9). \uBD88\uC77C\uCE58 \uC2DC \uC218\uC815\uD558\uC9C0 \uC54A\uC74C. \uC798\uBABB\uB41C \uC140 \uC218\uC815 \uBC29\uC9C0\uB97C \uC704\uD574 \uAD8C\uC7A5."
11514
+ )
11515
+ }).describe(
11516
+ "\uD3B8\uC9D1 \uD56D\uBAA9. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label[+direction]) \uC911 \uD558\uB098\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. \uB458 \uB2E4 \uC9C0\uC815\uD558\uAC70\uB098 \uB458 \uB2E4 \uC0DD\uB7B5\uD558\uBA74 \uC624\uB958\uC785\uB2C8\uB2E4."
11517
+ );
11518
+ var proposeCellEditSchema = z32.object({
11519
+ path: z32.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
11520
+ edits: z32.array(cellEditItemSchema).min(1).describe(
11521
+ "\uD3B8\uC9D1 \uBAA9\uB85D. \uAC01 \uD56D\uBAA9\uC740 \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label+direction) \uC911 \uD558\uB098"
11522
+ ),
11523
+ summary: z32.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
11524
+ });
11525
+ function tokenizeHwpxXml(xml) {
11526
+ const tokens = [];
11527
+ const re = /<hp:tbl[\s>]|<\/hp:tbl>|<hp:tc[\s>]|<\/hp:tc>|<hp:t\/>|<hp:t>|<hp:cellAddr[^/>]*|<hp:cellSpan[^/>]*/g;
11528
+ let startPos = 0;
11529
+ let m = re.exec(xml);
11530
+ while (m !== null) {
11531
+ const raw = m[0];
11532
+ const pos = m.index;
11533
+ if (raw.startsWith("<hp:tbl")) {
11534
+ tokens.push({ kind: "tbl_open", pos, end: pos + raw.length });
11535
+ } else if (raw === "</hp:tbl>") {
11536
+ tokens.push({ kind: "tbl_close", pos, end: pos + raw.length });
11537
+ } else if (raw.startsWith("<hp:tc")) {
11538
+ tokens.push({ kind: "tc_open", pos, end: pos + raw.length });
11539
+ } else if (raw === "</hp:tc>") {
11540
+ tokens.push({ kind: "tc_close", pos, end: pos + raw.length });
11541
+ } else if (raw === "<hp:t/>") {
11542
+ tokens.push({ kind: "t_empty", pos, end: pos + raw.length });
11543
+ } else if (raw === "<hp:t>") {
11544
+ tokens.push({ kind: "t_open", pos, end: pos + raw.length });
11545
+ } else if (raw.startsWith("<hp:cellAddr")) {
11546
+ const colM = raw.match(/colAddr="(\d+)"/);
11547
+ const rowM = raw.match(/rowAddr="(\d+)"/);
11548
+ if (colM && rowM) {
11549
+ const selfClose = xml.indexOf("/>", pos);
11550
+ const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
11551
+ tokens.push({
11552
+ kind: "cell_addr",
11553
+ pos,
11554
+ end,
11555
+ colAddr: Number(colM[1]),
11556
+ rowAddr: Number(rowM[1])
11557
+ });
11558
+ }
11559
+ } else if (raw.startsWith("<hp:cellSpan")) {
11560
+ const colM = raw.match(/colSpan="(\d+)"/);
11561
+ const rowM = raw.match(/rowSpan="(\d+)"/);
11562
+ const selfClose = xml.indexOf("/>", pos);
11563
+ const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
11564
+ tokens.push({
11565
+ kind: "cell_span",
11566
+ pos,
11567
+ end,
11568
+ colSpan: colM ? Number(colM[1]) : 1,
11569
+ rowSpan: rowM ? Number(rowM[1]) : 1
11570
+ });
11571
+ }
11572
+ startPos = re.lastIndex;
11573
+ m = re.exec(xml);
11574
+ }
11575
+ void startPos;
11576
+ return tokens;
11486
11577
  }
11487
- function decodeXml(text3) {
11488
- return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
11578
+ function escapeXml(text3) {
11579
+ return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
11489
11580
  }
11490
- function getAttr(openTag, attr) {
11491
- const re = new RegExp(`\\b${attr}="([^"]*)"`, "");
11492
- const m = re.exec(openTag);
11493
- return m ? decodeXml(m[1] ?? "") : "";
11581
+ function collectDirectTcRanges(tokens, tblStart, tblEnd) {
11582
+ const tblTokens = tokens.filter((t) => t.pos > tblStart && t.pos < tblEnd);
11583
+ const tcRanges = [];
11584
+ const tcStack = [];
11585
+ let innerDepth = 0;
11586
+ for (const tok of tblTokens) {
11587
+ if (tok.kind === "tbl_open") {
11588
+ innerDepth++;
11589
+ } else if (tok.kind === "tbl_close") {
11590
+ innerDepth--;
11591
+ } else if (tok.kind === "tc_open") {
11592
+ tcStack.push({ pos: tok.pos, depth: innerDepth });
11593
+ } else if (tok.kind === "tc_close") {
11594
+ const entry = tcStack.pop();
11595
+ if (entry !== void 0) {
11596
+ tcRanges.push({ start: entry.pos, end: tok.end, depth: entry.depth });
11597
+ }
11598
+ }
11599
+ }
11600
+ return tcRanges.filter((tc) => tc.depth === 0);
11494
11601
  }
11495
- function parseFormObjects(xml, sectionFile, startIndex = 0) {
11496
- const results = [];
11497
- let idx = startIndex;
11498
- const tagSpecs = [
11499
- { xmlTag: "hp:btn", type: "button" },
11500
- { xmlTag: "hp:checkBtn", type: "checkBox" },
11501
- { xmlTag: "hp:radioBtn", type: "radioButton" },
11502
- { xmlTag: "hp:comboBox", type: "comboBox" },
11503
- { xmlTag: "hp:edit", type: "edit" }
11504
- ];
11505
- const hits = [];
11506
- for (const spec of tagSpecs) {
11507
- const re = new RegExp(`<${spec.xmlTag}\\b`, "g");
11508
- for (let m = re.exec(xml); m !== null; m = re.exec(xml)) {
11509
- hits.push({ pos: m.index, xmlTag: spec.xmlTag, type: spec.type });
11602
+ function readOwnTextFromTc(xml, tokens, tcStart, tcEnd) {
11603
+ const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
11604
+ let innerDepth = 0;
11605
+ let text3 = "";
11606
+ for (const t of tcTokens) {
11607
+ if (t.kind === "tbl_open") innerDepth++;
11608
+ else if (t.kind === "tbl_close") innerDepth--;
11609
+ else if (t.kind === "t_empty" && innerDepth === 0) {
11610
+ } else if (t.kind === "t_open" && innerDepth === 0) {
11611
+ const closePos = xml.indexOf("</hp:t>", t.end);
11612
+ if (closePos >= 0) {
11613
+ text3 += xml.substring(t.end, closePos);
11614
+ }
11510
11615
  }
11511
11616
  }
11512
- hits.sort((a, b) => a.pos - b.pos);
11513
- for (const hit of hits) {
11514
- const { pos, xmlTag, type } = hit;
11515
- const openTagEnd = xml.indexOf(">", pos);
11516
- if (openTagEnd < 0) continue;
11517
- const openTag = xml.slice(pos, openTagEnd + 1);
11518
- const closeTag = `</${xmlTag}>`;
11519
- const closeIdx = xml.indexOf(closeTag, openTagEnd);
11520
- if (closeIdx < 0) continue;
11521
- const elementEnd = closeIdx + closeTag.length;
11522
- const elementXml = xml.slice(pos, elementEnd);
11523
- const name = getAttr(openTag, "name");
11524
- let currentValue;
11525
- let comboItems;
11526
- switch (type) {
11527
- case "button":
11528
- currentValue = getAttr(openTag, "caption");
11529
- break;
11530
- case "checkBox":
11531
- case "radioButton":
11532
- currentValue = getAttr(openTag, "value") === "CHECKED";
11533
- break;
11534
- case "comboBox": {
11535
- currentValue = getAttr(openTag, "selectedValue");
11536
- const listItemRe = /<hp:listItem\b[^>]*/g;
11537
- const items = [];
11538
- for (let lm = listItemRe.exec(elementXml); lm !== null; lm = listItemRe.exec(elementXml)) {
11539
- const itemValue = getAttr(lm[0] ?? "", "value");
11540
- items.push(itemValue);
11617
+ return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
11618
+ }
11619
+ function findTopLevelTableRange(tokens, tableIndex) {
11620
+ let topLevelCount = 0;
11621
+ let depth = 0;
11622
+ let targetStart = -1;
11623
+ for (const tok of tokens) {
11624
+ if (tok.kind === "tbl_open") {
11625
+ if (depth === 0) {
11626
+ if (topLevelCount === tableIndex) {
11627
+ targetStart = tok.pos;
11541
11628
  }
11542
- comboItems = items;
11543
- break;
11629
+ topLevelCount++;
11544
11630
  }
11545
- case "edit": {
11546
- const textSelfClose = /<hp:text\s*\/>/;
11547
- const textOpen = /<hp:text>/;
11548
- const textClose = "</hp:text>";
11549
- if (textSelfClose.test(elementXml)) {
11550
- currentValue = "";
11551
- } else {
11552
- const tOpenIdx = elementXml.search(textOpen);
11553
- if (tOpenIdx >= 0) {
11554
- const afterOpen = elementXml.indexOf(">", tOpenIdx) + 1;
11555
- const closePos = elementXml.indexOf(textClose, afterOpen);
11556
- currentValue = closePos >= 0 ? decodeXml(elementXml.slice(afterOpen, closePos)) : "";
11557
- } else {
11558
- currentValue = "";
11559
- }
11560
- }
11561
- break;
11631
+ depth++;
11632
+ } else if (tok.kind === "tbl_close") {
11633
+ depth--;
11634
+ if (depth === 0 && topLevelCount === tableIndex + 1 && targetStart >= 0) {
11635
+ return { start: targetStart, end: tok.end };
11562
11636
  }
11563
11637
  }
11564
- results.push({
11565
- index: idx++,
11566
- name,
11567
- type,
11568
- currentValue,
11569
- comboItems,
11570
- sectionFile,
11571
- posInSection: pos
11572
- });
11573
11638
  }
11574
- return results;
11639
+ return null;
11575
11640
  }
11576
- function applyFormObjectEdits(xml, edits) {
11577
- const results = edits.map(() => ({ success: false }));
11578
- const patches = [];
11579
- for (let ei = 0; ei < edits.length; ei++) {
11580
- const edit = edits[ei];
11581
- const { target, set, expected } = edit;
11582
- const pos = target.posInSection;
11583
- const xmlTag = typeToXmlTag(target.type);
11584
- const openTagEnd = xml.indexOf(">", pos);
11585
- if (openTagEnd < 0) {
11586
- results[ei] = {
11587
- success: false,
11588
- error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}" \uC5EC\uB294 \uD0DC\uADF8 \uB05D\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
11589
- };
11590
- continue;
11641
+ function readCellAddrSpan(tokens, tcStart, tcEnd) {
11642
+ const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
11643
+ let addr = null;
11644
+ let span = { colSpan: 1, rowSpan: 1 };
11645
+ for (const t of tcTokens) {
11646
+ if (t.kind === "cell_addr" && t.colAddr !== void 0 && t.rowAddr !== void 0) {
11647
+ addr = { colAddr: t.colAddr, rowAddr: t.rowAddr };
11648
+ } else if (t.kind === "cell_span" && t.colSpan !== void 0 && t.rowSpan !== void 0) {
11649
+ span = { colSpan: t.colSpan, rowSpan: t.rowSpan };
11591
11650
  }
11592
- const _openTag = xml.slice(pos, openTagEnd + 1);
11593
- const closeTag = `</${xmlTag}>`;
11594
- const closeIdx = xml.indexOf(closeTag, openTagEnd);
11595
- if (closeIdx < 0) {
11651
+ }
11652
+ if (!addr) return null;
11653
+ return { ...addr, ...span };
11654
+ }
11655
+ function applyCellEditsToSectionXml(xml, edits) {
11656
+ const tokens = tokenizeHwpxXml(xml);
11657
+ const results = edits.map(() => ({ success: false }));
11658
+ let totalTopLevelTbls = 0;
11659
+ {
11660
+ let d = 0;
11661
+ for (const tok of tokens) {
11662
+ if (tok.kind === "tbl_open") {
11663
+ if (d === 0) totalTopLevelTbls++;
11664
+ d++;
11665
+ } else if (tok.kind === "tbl_close") {
11666
+ d--;
11667
+ }
11668
+ }
11669
+ }
11670
+ const replacements = [];
11671
+ for (let ei = 0; ei < edits.length; ei++) {
11672
+ const edit = edits[ei];
11673
+ const tblRange = findTopLevelTableRange(tokens, edit.tableIndex);
11674
+ if (!tblRange) {
11596
11675
  results[ei] = {
11597
11676
  success: false,
11598
- error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}" \uB2EB\uB294 \uD0DC\uADF8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
11677
+ error: `\uD45C ${edit.tableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774 \uC139\uC158\uC5D0 \uCD1D ${totalTopLevelTbls}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
11599
11678
  };
11600
11679
  continue;
11601
11680
  }
11602
- const elementEnd = closeIdx + closeTag.length;
11603
- const typeError = validateSetForType(target.type, set);
11604
- if (typeError) {
11605
- results[ei] = { success: false, error: typeError };
11606
- continue;
11607
- }
11608
- const currentRaw = readCurrentValue(
11609
- xml,
11610
- pos,
11611
- openTagEnd,
11612
- openTagEnd + 1,
11613
- elementEnd,
11614
- target.type
11615
- );
11616
- if (expected !== void 0) {
11617
- const mismatch = checkExpected(expected, currentRaw, target);
11618
- if (mismatch) {
11619
- results[ei] = { success: false, error: mismatch };
11620
- continue;
11621
- }
11622
- }
11623
- if (target.type === "comboBox" && set.selected !== void 0) {
11624
- const items = target.comboItems ?? [];
11625
- if (!items.includes(set.selected)) {
11626
- const validList = items.map((v) => `"${v}"`).join(", ");
11627
- results[ei] = {
11628
- success: false,
11629
- error: `comboBox "${target.name}"\uC758 selected \uAC12 "${set.selected}"\uC774 \uC720\uD6A8\uD55C \uD56D\uBAA9\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uC720\uD6A8\uD55C \uD56D\uBAA9: ${validList || "(\uC5C6\uC74C)"}`
11630
- };
11631
- continue;
11681
+ const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
11682
+ const tblTokens = tokens.filter((t) => t.pos >= tblRange.start && t.pos < tblRange.end);
11683
+ let found = false;
11684
+ for (const tc of directTcs) {
11685
+ const tcTokens = tblTokens.filter((t) => t.pos >= tc.start && t.pos < tc.end);
11686
+ const hasAddr = tcTokens.some(
11687
+ (t) => t.kind === "cell_addr" && t.colAddr === edit.col && t.rowAddr === edit.row
11688
+ );
11689
+ if (!hasAddr) continue;
11690
+ let innerDepth = 0;
11691
+ const ownTRuns = [];
11692
+ for (const t of tokens.filter((x) => x.pos >= tc.start && x.pos < tc.end)) {
11693
+ if (t.kind === "tbl_open") innerDepth++;
11694
+ else if (t.kind === "tbl_close") innerDepth--;
11695
+ else if (t.kind === "t_empty" && innerDepth === 0) {
11696
+ ownTRuns.push({ isEmpty: true, tagPos: t.pos, tagEnd: t.end });
11697
+ } else if (t.kind === "t_open" && innerDepth === 0) {
11698
+ const closePos = xml.indexOf("</hp:t>", t.end);
11699
+ if (closePos >= 0) {
11700
+ ownTRuns.push({ isEmpty: false, openEnd: t.end, closePos });
11701
+ }
11702
+ }
11632
11703
  }
11633
- }
11634
- let patchCreated = false;
11635
- if (set.caption !== void 0) {
11636
- const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "caption", escapeXml(set.caption));
11637
- if (!patch) {
11704
+ const currentText = ownTRuns.map((r) => r.isEmpty ? "" : xml.substring(r.openEnd, r.closePos)).join("").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
11705
+ if (edit.expectedText !== void 0 && edit.expectedText !== currentText) {
11638
11706
  results[ei] = {
11639
11707
  success: false,
11640
- error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 caption \uC18D\uC131\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
11708
+ oldText: currentText,
11709
+ error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC758 \uD604\uC7AC \uD14D\uC2A4\uD2B8\uAC00 \uC608\uC0C1\uAC12\uACFC \uB2E4\uB985\uB2C8\uB2E4. \uC608\uC0C1: "${edit.expectedText}", \uC2E4\uC81C: "${currentText}". \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`
11641
11710
  };
11642
- continue;
11711
+ found = true;
11712
+ break;
11643
11713
  }
11644
- patches.push(patch);
11645
- patchCreated = true;
11646
- } else if (set.checked !== void 0) {
11647
- const newValue = set.checked ? "CHECKED" : "UNCHECKED";
11648
- const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "value", newValue);
11649
- if (!patch) {
11714
+ if (ownTRuns.length === 0) {
11650
11715
  results[ei] = {
11651
11716
  success: false,
11652
- error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 value \uC18D\uC131\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
11717
+ oldText: currentText,
11718
+ error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC5D0 \uD14D\uC2A4\uD2B8 \uB7F0\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. <hp:t> \uB610\uB294 <hp:t/> \uB7F0\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC140 \uAD6C\uC870\uB97C \uD655\uC778\uD558\uC138\uC694.`
11653
11719
  };
11654
- continue;
11720
+ found = true;
11721
+ break;
11655
11722
  }
11656
- patches.push(patch);
11657
- patchCreated = true;
11658
- } else if (set.selected !== void 0) {
11659
- const patch = replaceAttrInOpenTag(
11660
- xml,
11661
- pos,
11662
- openTagEnd,
11663
- "selectedValue",
11664
- escapeXml(set.selected)
11665
- );
11666
- if (!patch) {
11667
- results[ei] = {
11668
- success: false,
11669
- error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 selectedValue \uC18D\uC131\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
11670
- };
11671
- continue;
11723
+ const escapedNew = escapeXml(edit.newText);
11724
+ const patches = [];
11725
+ const firstRun = ownTRuns[0];
11726
+ if (firstRun.isEmpty) {
11727
+ patches.push({
11728
+ from: firstRun.tagPos,
11729
+ to: firstRun.tagEnd,
11730
+ text: `<hp:t>${escapedNew}</hp:t>`
11731
+ });
11732
+ } else {
11733
+ patches.push({ from: firstRun.openEnd, to: firstRun.closePos, text: escapedNew });
11672
11734
  }
11673
- patches.push(patch);
11674
- patchCreated = true;
11675
- } else if (set.text !== void 0) {
11676
- const elementContent = xml.slice(pos, elementEnd);
11677
- const textPatch = replaceEditText(xml, pos, elementContent, set.text);
11678
- if (!textPatch) {
11679
- results[ei] = {
11680
- success: false,
11681
- error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 <hp:text> \uC694\uC18C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
11682
- };
11683
- continue;
11735
+ for (let ri = 1; ri < ownTRuns.length; ri++) {
11736
+ const run = ownTRuns[ri];
11737
+ if (!run.isEmpty) {
11738
+ patches.push({ from: run.openEnd, to: run.closePos, text: "" });
11739
+ }
11684
11740
  }
11685
- patches.push(textPatch);
11686
- patchCreated = true;
11741
+ replacements.push({ editIdx: ei, patches });
11742
+ results[ei] = { success: true, oldText: currentText };
11743
+ found = true;
11744
+ break;
11687
11745
  }
11688
- if (!patchCreated) {
11689
- results[ei] = { success: false, error: `\uD3B8\uC9D1 #${ei + 1}: set \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.` };
11690
- continue;
11746
+ if (!found) {
11747
+ results[ei] = {
11748
+ success: false,
11749
+ error: `\uD45C ${edit.tableIndex}\uC5D0\uC11C \uC140 (\uD589 ${edit.row}, \uC5F4 ${edit.col})\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. cellAddr colAddr="${edit.col}" rowAddr="${edit.row}"\uC5D0 \uD574\uB2F9\uD558\uB294 \uC140\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
11750
+ };
11691
11751
  }
11692
- results[ei] = { success: true, oldValue: currentRaw };
11693
11752
  }
11694
11753
  if (results.some((r) => !r.success)) {
11695
11754
  return { newXml: xml, results };
11696
11755
  }
11697
- const sortedPatches = [...patches].sort((a, b) => b.from - a.from);
11756
+ const allPatches = replacements.flatMap((r) => r.patches).sort((a, b) => b.from - a.from);
11698
11757
  let result = xml;
11699
- for (const patch of sortedPatches) {
11700
- result = result.slice(0, patch.from) + patch.text + result.slice(patch.to);
11758
+ for (const patch of allPatches) {
11759
+ result = result.substring(0, patch.from) + patch.text + result.substring(patch.to);
11701
11760
  }
11702
11761
  return { newXml: result, results };
11703
11762
  }
11704
- function typeToXmlTag(type) {
11705
- switch (type) {
11706
- case "button":
11707
- return "hp:btn";
11708
- case "checkBox":
11709
- return "hp:checkBtn";
11710
- case "radioButton":
11711
- return "hp:radioBtn";
11712
- case "comboBox":
11713
- return "hp:comboBox";
11714
- case "edit":
11715
- return "hp:edit";
11716
- }
11717
- }
11718
- function validateSetForType(type, set) {
11719
- const key = Object.keys(set).find((k) => set[k] !== void 0);
11720
- if (!key) return "set \uD544\uB4DC\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. caption/checked/selected/text \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD558\uC138\uC694.";
11721
- const allowed = {
11722
- button: "caption",
11723
- checkBox: "checked",
11724
- radioButton: "checked",
11725
- comboBox: "selected",
11726
- edit: "text"
11727
- };
11728
- if (key !== allowed[type]) {
11729
- const typeKo = {
11730
- button: "PushButton",
11731
- checkBox: "CheckBox",
11732
- radioButton: "RadioButton",
11733
- comboBox: "ComboBox",
11734
- edit: "Edit"
11735
- };
11736
- return `\uD0C0\uC785 \uBD88\uC77C\uCE58: ${typeKo[type]} \uC591\uC2DD \uAC1C\uCCB4\uC5D0\uB294 "${allowed[type]}" \uD544\uB4DC\uB9CC \uC0AC\uC6A9 \uAC00\uB2A5\uD558\uC9C0\uB9CC "${key}"\uC774(\uAC00) \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`;
11737
- }
11738
- return null;
11739
- }
11740
- function readCurrentValue(xml, pos, openTagEnd, _afterOpen, elementEnd, type) {
11741
- const openTag = xml.slice(pos, openTagEnd + 1);
11742
- switch (type) {
11743
- case "button":
11744
- return decodeXml(getAttr(openTag, "caption"));
11745
- case "checkBox":
11746
- case "radioButton":
11747
- return getAttr(openTag, "value") === "CHECKED";
11748
- case "comboBox":
11749
- return decodeXml(getAttr(openTag, "selectedValue"));
11750
- case "edit": {
11751
- const elementContent = xml.slice(pos, elementEnd);
11752
- if (/<hp:text\s*\/>/.test(elementContent)) return "";
11753
- const tOpenIdx = elementContent.search(/<hp:text>/);
11754
- if (tOpenIdx >= 0) {
11755
- const afterOpen = elementContent.indexOf(">", tOpenIdx) + 1;
11756
- const closePos = elementContent.indexOf("</hp:text>", afterOpen);
11757
- return closePos >= 0 ? decodeXml(elementContent.slice(afterOpen, closePos)) : "";
11763
+ async function applyEditsToHwpx(hwpxBuffer, edits) {
11764
+ const zip = await import_jszip2.default.loadAsync(hwpxBuffer);
11765
+ const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
11766
+ const sectionXmls = [];
11767
+ const sectionTblCounts = [];
11768
+ for (const sf of sectionFiles) {
11769
+ const entry = zip.file(sf);
11770
+ const xml = entry ? await entry.async("string") : "";
11771
+ sectionXmls.push(xml);
11772
+ const tokens = tokenizeHwpxXml(xml);
11773
+ let count = 0;
11774
+ let depth = 0;
11775
+ for (const tok of tokens) {
11776
+ if (tok.kind === "tbl_open") {
11777
+ if (depth === 0) count++;
11778
+ depth++;
11779
+ } else if (tok.kind === "tbl_close") {
11780
+ depth--;
11758
11781
  }
11759
- return "";
11760
11782
  }
11783
+ sectionTblCounts.push(count);
11761
11784
  }
11762
- }
11763
- function checkExpected(expected, currentRaw, target) {
11764
- let expectedVal;
11765
- if (expected.caption !== void 0) expectedVal = expected.caption;
11766
- else if (expected.checked !== void 0) expectedVal = expected.checked;
11767
- else if (expected.selected !== void 0) expectedVal = expected.selected;
11768
- else if (expected.text !== void 0) expectedVal = expected.text;
11769
- if (expectedVal === void 0) return null;
11770
- const matches = typeof expectedVal === "boolean" ? currentRaw === expectedVal : String(currentRaw) === String(expectedVal);
11771
- if (!matches) {
11772
- return `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}" \uD604\uC7AC \uAC12\uC774 \uC608\uC0C1\uAC12\uACFC \uB2E4\uB985\uB2C8\uB2E4. \uC608\uC0C1: ${JSON.stringify(expectedVal)}, \uC2E4\uC81C: ${JSON.stringify(currentRaw)}. \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`;
11773
- }
11774
- return null;
11775
- }
11776
- function replaceAttrInOpenTag(xml, tagStart, tagEnd, attr, newValue) {
11777
- const tagStr = xml.slice(tagStart, tagEnd + 1);
11778
- const re = new RegExp(`\\b(${attr}=")([^"]*)(")`, "");
11779
- const m = re.exec(tagStr);
11780
- if (!m) return null;
11781
- const relFrom = m.index + (m[1]?.length ?? 0);
11782
- const relTo = relFrom + (m[2]?.length ?? 0);
11783
- return {
11784
- from: tagStart + relFrom,
11785
- to: tagStart + relTo,
11786
- text: newValue
11787
- };
11788
- }
11789
- function replaceEditText(_xml, pos, elementContent, newText) {
11790
- const escaped = escapeXml(newText);
11791
- const selfCloseRe = /<hp:text\s*\/>/;
11792
- const scm = selfCloseRe.exec(elementContent);
11793
- if (scm) {
11794
- return {
11795
- from: pos + scm.index,
11796
- to: pos + scm.index + scm[0].length,
11797
- text: `<hp:text>${escaped}</hp:text>`
11798
- };
11799
- }
11800
- const openRe = /<hp:text>/;
11801
- const om = openRe.exec(elementContent);
11802
- if (om) {
11803
- const afterOpen = om.index + om[0].length;
11804
- const closePos = elementContent.indexOf("</hp:text>", afterOpen);
11805
- if (closePos < 0) return null;
11806
- return {
11807
- from: pos + afterOpen,
11808
- to: pos + closePos,
11809
- text: escaped
11810
- };
11811
- }
11812
- return null;
11813
- }
11814
- async function listFormObjectsFromZip(zip) {
11815
- const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
11816
- const sectionXmls = [];
11817
- const objects = [];
11818
- let globalIndex = 0;
11819
- for (const sf of sectionFiles) {
11820
- const entry = zip.file(sf);
11821
- const xml = entry ? await entry.async("string") : "";
11822
- sectionXmls.push(xml);
11823
- const parsed = parseFormObjects(xml, sf, globalIndex);
11824
- objects.push(...parsed);
11825
- globalIndex += parsed.length;
11785
+ const sectionEdits = sectionFiles.map(() => []);
11786
+ let offset = 0;
11787
+ for (let si = 0; si < sectionFiles.length; si++) {
11788
+ const count = sectionTblCounts[si] ?? 0;
11789
+ for (let ei = 0; ei < edits.length; ei++) {
11790
+ const edit = edits[ei];
11791
+ if (edit.tableIndex >= offset && edit.tableIndex < offset + count) {
11792
+ const secEdits = sectionEdits[si];
11793
+ if (secEdits) {
11794
+ secEdits.push({
11795
+ tableIndex: edit.tableIndex - offset,
11796
+ // 섹션 내 상대 인덱스
11797
+ row: edit.row,
11798
+ col: edit.col,
11799
+ newText: edit.newText,
11800
+ expectedText: edit.expectedText,
11801
+ originalEditIdx: ei
11802
+ });
11803
+ }
11804
+ }
11805
+ }
11806
+ offset += count;
11826
11807
  }
11827
- return { objects, sectionFiles, sectionXmls };
11828
- }
11829
- function validateHwpxBuffer(ext, buffer) {
11830
- if (ext === ".hwp") {
11831
- return "\uC624\uB958: \uC774 \uD234\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
11808
+ const totalTables = sectionTblCounts.reduce((a, b) => a + b, 0);
11809
+ const allResults = edits.map((edit, ei) => ({
11810
+ success: false,
11811
+ error: `\uD45C ${edit?.tableIndex ?? ei}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uC5D0 \uCD1D ${totalTables}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
11812
+ }));
11813
+ const newSectionXmls = [...sectionXmls];
11814
+ for (let si = 0; si < sectionFiles.length; si++) {
11815
+ const sEdits = sectionEdits[si] ?? [];
11816
+ if (sEdits.length === 0) continue;
11817
+ const srcXml = sectionXmls[si] ?? "";
11818
+ const { newXml, results } = applyCellEditsToSectionXml(srcXml, sEdits);
11819
+ newSectionXmls[si] = newXml;
11820
+ for (let i = 0; i < sEdits.length; i++) {
11821
+ const sEdit = sEdits[i];
11822
+ const res = results[i];
11823
+ if (sEdit && res) {
11824
+ allResults[sEdit.originalEditIdx] = res;
11825
+ }
11826
+ }
11832
11827
  }
11833
- if (ext !== ".hwpx") {
11834
- return `\uC624\uB958: \uC774 \uD234\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}`;
11828
+ if (allResults.some((r) => !r.success)) {
11829
+ return { buffer: hwpxBuffer, results: allResults };
11835
11830
  }
11836
- if (buffer[0] !== 80 || buffer[1] !== 75) {
11837
- return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4.";
11831
+ const out = new import_jszip2.default();
11832
+ const mimetypeEntry = zip.file("mimetype");
11833
+ if (mimetypeEntry) {
11834
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
11838
11835
  }
11839
- return null;
11840
- }
11841
- var listFormObjectsSchema = z32.object({
11842
- path: z32.string().describe("\uC77D\uC744 .hwpx \uD30C\uC77C \uACBD\uB85C")
11843
- });
11844
- var formEditSetSchema = z32.object({
11845
- caption: z32.string().optional().describe("PushButton \uCEA1\uC158 \uD14D\uC2A4\uD2B8"),
11846
- checked: z32.boolean().optional().describe("CheckBox/RadioButton \uCCB4\uD06C \uC0C1\uD0DC (true=CHECKED)"),
11847
- selected: z32.string().optional().describe("ComboBox \uC120\uD0DD \uAC12 (listItem \uC911 \uD558\uB098\uC5EC\uC57C \uD568)"),
11848
- text: z32.string().optional().describe("Edit \uD14D\uC2A4\uD2B8 \uB0B4\uC6A9")
11849
- }).refine(
11850
- (v) => {
11851
- const keys = ["caption", "checked", "selected", "text"].filter(
11852
- (k) => v[k] !== void 0
11853
- );
11854
- return keys.length === 1;
11855
- },
11856
- { message: "set \uD544\uB4DC\uB294 caption/checked/selected/text \uC911 \uC815\uD655\uD788 \uD558\uB098\uB9CC \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4." }
11857
- );
11858
- var formEditExpectedSchema = z32.object({
11859
- caption: z32.string().optional(),
11860
- checked: z32.boolean().optional(),
11861
- selected: z32.string().optional(),
11862
- text: z32.string().optional()
11863
- }).optional();
11864
- var formEditItemSchema = z32.object({
11865
- name: z32.string().describe("\uC591\uC2DD \uAC1C\uCCB4\uC758 name \uC18D\uC131 \uAC12"),
11866
- index: z32.number().int().nonnegative().optional().describe("\uB3D9\uC77C name\uC774 \uC5EC\uB7FF\uC778 \uACBD\uC6B0 \uBB38\uC11C \uC804\uCCB4 0-based \uC778\uB371\uC2A4\uB85C \uAD6C\uBD84"),
11867
- set: formEditSetSchema.describe("\uBCC0\uACBD\uD560 \uAC12 (caption/checked/selected/text \uC911 \uD558\uB098)"),
11868
- expected: formEditExpectedSchema.describe(
11869
- "\uD604\uC7AC \uAC12 \uC0AC\uC804 \uAC80\uC99D (\uC548\uC804 \uC635\uC158). \uC2E4\uC81C \uAC12\uC774 \uB2E4\uB974\uBA74 \uC774 \uD3B8\uC9D1\uC744 \uCDE8\uC18C\uD558\uACE0 \uC624\uB958 \uBC18\uD658."
11870
- )
11871
- });
11872
- var proposeFormObjectSchema = z32.object({
11873
- path: z32.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C"),
11874
- edits: z32.array(formEditItemSchema).min(1).describe("\uC591\uC2DD \uAC1C\uCCB4 \uD3B8\uC9D1 \uBAA9\uB85D"),
11875
- summary: z32.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
11876
- });
11877
- var listFormObjectsTool = {
11878
- name: "list_form_objects",
11879
- description: "HWPX \uBB38\uC11C\uC758 \uC591\uC2DD \uAC1C\uCCB4(form object) \uBAA9\uB85D\uC744 \uC5F4\uAC70\uD569\uB2C8\uB2E4. PushButton, CheckBox, RadioButton, ComboBox, Edit \uB2E4\uC12F \uAC00\uC9C0 \uD0C0\uC785\uC744 \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uAC01 \uC591\uC2DD \uAC1C\uCCB4\uC758 \uC774\uB984(name), \uD0C0\uC785, \uD604\uC7AC \uAC12, ComboBox\uC758 \uACBD\uC6B0 \uC120\uD0DD \uAC00\uB2A5\uD55C \uD56D\uBAA9 \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. propose_form_object\uB85C \uAC12\uC744 \uC218\uC815\uD558\uAE30 \uC804\uC5D0 \uC774 \uD234\uB85C \uBA3C\uC800 \uD604\uC7AC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4.",
11880
- inputSchema: listFormObjectsSchema,
11881
- requiresApproval: false,
11882
- execute: async ({
11883
- input,
11884
- ctx
11885
- }) => {
11886
- const safePath = await resolveSafePath(ctx.cwd, input.path);
11887
- const ext = extname3(safePath).toLowerCase();
11888
- let buffer;
11889
- try {
11890
- buffer = await readFile4(safePath);
11891
- } catch {
11892
- return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
11893
- }
11894
- const validationError = validateHwpxBuffer(ext, new Uint8Array(buffer.buffer));
11895
- if (validationError) return validationError;
11896
- let zip;
11897
- try {
11898
- zip = await import_jszip.default.loadAsync(new Uint8Array(buffer.buffer));
11899
- } catch {
11900
- return "\uC624\uB958: .hwpx(ZIP) \uD30C\uC77C\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
11901
- }
11902
- const { objects } = await listFormObjectsFromZip(zip);
11903
- if (objects.length === 0) {
11904
- return "\uC774 \uBB38\uC11C\uC5D0\uB294 \uC591\uC2DD \uAC1C\uCCB4(form object)\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.";
11905
- }
11906
- const lines = [`\uCD1D ${objects.length}\uAC1C\uC758 \uC591\uC2DD \uAC1C\uCCB4\uAC00 \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
11907
- `];
11908
- const typeKo = {
11909
- button: "PushButton",
11910
- checkBox: "CheckBox",
11911
- radioButton: "RadioButton",
11912
- comboBox: "ComboBox",
11913
- edit: "Edit"
11914
- };
11915
- for (const obj of objects) {
11916
- let valueStr;
11917
- if (typeof obj.currentValue === "boolean") {
11918
- valueStr = obj.currentValue ? "\uCCB4\uD06C\uB428 (CHECKED)" : "\uCCB4\uD06C \uD574\uC81C\uB428 (UNCHECKED)";
11919
- } else {
11920
- valueStr = `"${obj.currentValue}"`;
11921
- }
11922
- let line = `[${obj.index}] name="${obj.name}" | \uD0C0\uC785: ${typeKo[obj.type]} | \uD604\uC7AC \uAC12: ${valueStr}`;
11923
- if (obj.type === "comboBox" && obj.comboItems) {
11924
- const itemList = obj.comboItems.map((v) => `"${v}"`).join(", ");
11925
- line += ` | \uD56D\uBAA9: [${itemList}]`;
11926
- }
11927
- lines.push(line);
11836
+ for (const [name, entry] of Object.entries(zip.files)) {
11837
+ if (name === "mimetype" || entry.dir) continue;
11838
+ const sectionIdx = sectionFiles.indexOf(name);
11839
+ if (sectionIdx >= 0) {
11840
+ out.file(name, newSectionXmls[sectionIdx] ?? "");
11841
+ } else {
11842
+ out.file(name, await entry.async("uint8array"));
11928
11843
  }
11929
- return lines.join("\n");
11930
11844
  }
11931
- };
11932
- var proposeFormObjectTool = {
11933
- name: "propose_form_object",
11934
- description: "HWPX \uBB38\uC11C\uC758 \uC591\uC2DD \uAC1C\uCCB4(form object) \uAC12\uC744 XML \uC9C1\uC811 \uD328\uCE58 \uBC29\uC2DD\uC73C\uB85C \uC218\uC815\uD569\uB2C8\uB2E4. PushButton(caption), CheckBox/RadioButton(checked), ComboBox(selected), Edit(text)\uB97C \uC218\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. kordoc IR\uC5D0 \uC591\uC2DD \uAC1C\uCCB4 \uD0C0\uC785\uC774 \uC5C6\uC73C\uBBC0\uB85C section XML\uC744 \uC9C1\uC811 \uD328\uCE58\uD569\uB2C8\uB2E4. \uC218\uC815 \uC804\uC5D0 list_form_objects\uB85C \uD604\uC7AC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. name\uC774 \uC911\uBCF5\uB41C \uACBD\uC6B0 index\uB85C \uB300\uC0C1\uC744 \uC9C0\uC815\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 \uC0AC\uC6A9\uC790 \uC2B9\uC778 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4.",
11935
- inputSchema: proposeFormObjectSchema,
11845
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
11846
+ return { buffer: new Uint8Array(buf), results: allResults };
11847
+ }
11848
+ var proposeCellEditTool = {
11849
+ name: "propose_cell_edit",
11850
+ description: "HWPX \uBB38\uC11C\uC758 \uD45C \uC140 \uB0B4\uC6A9\uC744 XML \uC9C1\uC811 \uD328\uCE58 \uBC29\uC2DD\uC73C\uB85C \uC218\uC815\uD569\uB2C8\uB2E4. \uBCD1\uD569 \uC140(cellSpan/rowSpan)\uC774 \uC788\uB294 \uD45C\uC5D0\uC11C\uB3C4 \uBCD1\uD569 \uAD6C\uC870\uB97C \uC644\uC804\uD788 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uBE48 \uC140(<hp:t/> self-closing \uB7F0)\uB3C4 \uCC44\uC6B8 \uC218 \uC788\uC5B4 \uC591\uC2DD(form) \uD3B8\uC9D1\uC5D0 \uC801\uD569\uD569\uB2C8\uB2E4. \uC140 \uC8FC\uC18C \uC9C0\uC815 \uBC29\uBC95: (1) \uC88C\uD45C \uBAA8\uB4DC \u2014 tableIndex + row + col \uC9C1\uC811 \uC9C0\uC815. (2) \uB808\uC774\uBE14 \uBAA8\uB4DC \u2014 label(\uC778\uC811 \uB808\uC774\uBE14 \uC140 \uD14D\uC2A4\uD2B8) + direction(right/below, \uAE30\uBCF8 right) + \uC120\uD0DD\uC801 tableIndex\uB85C \uB808\uC774\uBE14 \uC606/\uC544\uB798 \uC140\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uD3B8\uC9D1. \uBCD1\uD569 \uB808\uC774\uBE14 \uC140\uB3C4 colSpan/rowSpan\uC744 \uBC18\uC601\uD558\uC5EC \uB300\uC0C1 \uC140\uC744 \uACC4\uC0B0\uD569\uB2C8\uB2E4. propose_edit/propose_form_fill\uC740 \uB9C8\uD06C\uB2E4\uC6B4 \uB77C\uC6B4\uB4DC\uD2B8\uB9BD\uC73C\uB85C \uBCD1\uD569 \uC140\uC744 \uC18C\uC2E4\uC2DC\uD0A4\uBBC0\uB85C, \uBCD1\uD569 \uC140\uC774 \uC788\uB294 \uD45C\uB97C \uC218\uC815\uD560 \uB54C\uB294 \uC774 \uD234\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4. .hwp\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBA70 Hancom\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694. \uC218\uC815 \uC804\uC5D0 \uBC18\uB4DC\uC2DC read_document\uB85C \uC6D0\uBCF8\uC744 \uC77D\uACE0, expectedText\uB97C \uC9C0\uC815\uD558\uC5EC \uC548\uC804\uD558\uAC8C \uC218\uC815\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 diff \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
11851
+ inputSchema: proposeCellEditSchema,
11936
11852
  requiresApproval: true,
11937
11853
  propose: async ({
11938
11854
  input,
@@ -11940,145 +11856,196 @@ var proposeFormObjectTool = {
11940
11856
  }) => {
11941
11857
  const safePath = await resolveSafePath(ctx.cwd, input.path);
11942
11858
  const ext = extname3(safePath).toLowerCase();
11859
+ if (ext === ".hwp") {
11860
+ return "\uC624\uB958: propose_cell_edit\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. \uB610\uB294 propose_edit\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC73C\uB098, \uBCD1\uD569 \uC140\uC774 \uC18C\uC2E4\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
11861
+ }
11862
+ if (ext !== ".hwpx") {
11863
+ return `\uC624\uB958: propose_cell_edit\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}. \uD45C \uC140 \uC9C1\uC811 \uD3B8\uC9D1\uC740 .hwpx \uD3EC\uB9F7\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.`;
11864
+ }
11943
11865
  let originalBuffer;
11944
11866
  try {
11945
11867
  originalBuffer = await readFile4(safePath);
11946
11868
  } catch {
11947
- return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
11869
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uAC70\uB098 read_document\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
11948
11870
  }
11949
- const bufArray = new Uint8Array(originalBuffer.buffer);
11950
- const validationError = validateHwpxBuffer(ext, bufArray);
11951
- if (validationError) return validationError;
11952
- let zip;
11953
- try {
11954
- zip = await import_jszip.default.loadAsync(bufArray);
11955
- } catch {
11956
- return "\uC624\uB958: .hwpx(ZIP) \uD30C\uC77C\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
11871
+ if (originalBuffer[0] !== 80 || originalBuffer[1] !== 75) {
11872
+ return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
11957
11873
  }
11958
- const { objects, sectionFiles, sectionXmls } = await listFormObjectsFromZip(zip);
11959
- const resolvedEdits = [];
11960
- const resolveErrors = [];
11961
- for (let ei = 0; ei < input.edits.length; ei++) {
11962
- const edit = input.edits[ei];
11963
- if (!edit) continue;
11964
- const candidates = objects.filter((o) => o.name === edit.name);
11965
- if (candidates.length === 0) {
11966
- resolveErrors.push(
11967
- `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}"\uC778 \uC591\uC2DD \uAC1C\uCCB4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. list_form_objects\uB85C \uBB38\uC11C\uC758 \uC591\uC2DD \uAC1C\uCCB4 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.`
11968
- );
11969
- continue;
11874
+ const zipForLabel = await import_jszip2.default.loadAsync(new Uint8Array(originalBuffer.buffer));
11875
+ const sectionFilesForLabel = Object.keys(zipForLabel.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
11876
+ const sectionInfos = [];
11877
+ let globalTblOffset = 0;
11878
+ for (const sf of sectionFilesForLabel) {
11879
+ const entry = zipForLabel.file(sf);
11880
+ const xml = entry ? await entry.async("string") : "";
11881
+ const tokens = tokenizeHwpxXml(xml);
11882
+ let count = 0;
11883
+ let d = 0;
11884
+ for (const tok of tokens) {
11885
+ if (tok.kind === "tbl_open") {
11886
+ if (d === 0) count++;
11887
+ d++;
11888
+ } else if (tok.kind === "tbl_close") {
11889
+ d--;
11890
+ }
11970
11891
  }
11971
- let target;
11972
- if (candidates.length > 1) {
11973
- if (edit.index === void 0) {
11974
- const indices = candidates.map((c) => c.index).join(", ");
11975
- resolveErrors.push(
11976
- `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}"\uC778 \uC591\uC2DD \uAC1C\uCCB4\uAC00 ${candidates.length}\uAC1C \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uBB38\uC11C \uC778\uB371\uC2A4: ${indices}). index \uD544\uB4DC\uB85C \uB300\uC0C1\uC744 \uC9C0\uC815\uD558\uC138\uC694.`
11977
- );
11978
- continue;
11892
+ sectionInfos.push({ xml, tblCount: count, globalOffset: globalTblOffset });
11893
+ globalTblOffset += count;
11894
+ }
11895
+ function resolveLabelAcrossSections(label, direction, scopedTableIndex) {
11896
+ const trimmedLabel = label.trim();
11897
+ const allMatches = [];
11898
+ for (const si of sectionInfos) {
11899
+ const tokens = tokenizeHwpxXml(si.xml);
11900
+ let localStart = 0;
11901
+ let localEnd = si.tblCount - 1;
11902
+ if (scopedTableIndex !== void 0) {
11903
+ const localIdx2 = scopedTableIndex - si.globalOffset;
11904
+ if (localIdx2 < 0 || localIdx2 >= si.tblCount) continue;
11905
+ localStart = localIdx2;
11906
+ localEnd = localIdx2;
11979
11907
  }
11980
- const byIndex = candidates.find((c) => c.index === edit.index);
11981
- if (!byIndex) {
11982
- resolveErrors.push(
11983
- `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}", index=${edit.index}\uC778 \uC591\uC2DD \uAC1C\uCCB4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
11984
- );
11985
- continue;
11908
+ for (let li = localStart; li <= localEnd; li++) {
11909
+ const tblRange = findTopLevelTableRange(tokens, li);
11910
+ if (!tblRange) continue;
11911
+ const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
11912
+ for (const tc of directTcs) {
11913
+ const cellText = readOwnTextFromTc(si.xml, tokens, tc.start, tc.end).trim();
11914
+ if (cellText === trimmedLabel) {
11915
+ const addrSpan2 = readCellAddrSpan(tokens, tc.start, tc.end);
11916
+ if (addrSpan2) {
11917
+ allMatches.push({
11918
+ globalTableIndex: si.globalOffset + li,
11919
+ addrSpan: addrSpan2,
11920
+ sectionXml: si.xml,
11921
+ sectionOffset: si.globalOffset
11922
+ });
11923
+ }
11924
+ }
11925
+ }
11986
11926
  }
11987
- target = byIndex;
11927
+ }
11928
+ if (allMatches.length === 0) {
11929
+ const scope = scopedTableIndex !== void 0 ? `\uD45C ${scopedTableIndex}` : "\uBB38\uC11C \uB0B4 \uBAA8\uB4E0 \uD45C";
11930
+ return {
11931
+ error: `\uB808\uC774\uBE14 "${label}"\uC744(\uB97C) ${scope}\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.`
11932
+ };
11933
+ }
11934
+ if (allMatches.length > 1) {
11935
+ const locs = allMatches.map(
11936
+ (m) => `\uD45C ${m.globalTableIndex} (\uD589 ${m.addrSpan.rowAddr}, \uC5F4 ${m.addrSpan.colAddr})`
11937
+ ).join(", ");
11938
+ return {
11939
+ error: `\uB808\uC774\uBE14 "${label}"\uC774(\uAC00) \uC5EC\uB7EC \uC140\uC5D0\uC11C \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${locs}. tableIndex\uB85C \uD0D0\uC0C9 \uBC94\uC704\uB97C \uC881\uD788\uAC70\uB098 \uC88C\uD45C(row/col)\uB97C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694.`
11940
+ };
11941
+ }
11942
+ const match = allMatches[0];
11943
+ const { addrSpan, globalTableIndex, sectionXml, sectionOffset } = match;
11944
+ let targetRow;
11945
+ let targetCol;
11946
+ if (direction === "right") {
11947
+ targetRow = addrSpan.rowAddr;
11948
+ targetCol = addrSpan.colAddr + addrSpan.colSpan;
11988
11949
  } else {
11989
- target = candidates[0];
11990
- if (edit.index !== void 0 && edit.index !== target.index) {
11991
- resolveErrors.push(
11992
- `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}"\uC758 \uBB38\uC11C \uC778\uB371\uC2A4\uB294 ${target.index}\uC774\uC9C0\uB9CC index=${edit.index}\uAC00 \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`
11993
- );
11994
- continue;
11950
+ targetRow = addrSpan.rowAddr + addrSpan.rowSpan;
11951
+ targetCol = addrSpan.colAddr;
11952
+ }
11953
+ const localIdx = globalTableIndex - sectionOffset;
11954
+ const tokens2 = tokenizeHwpxXml(sectionXml);
11955
+ const tblRange2 = findTopLevelTableRange(tokens2, localIdx);
11956
+ if (!tblRange2) {
11957
+ return { error: `\uD45C ${globalTableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958).` };
11958
+ }
11959
+ const directTcs2 = collectDirectTcRanges(tokens2, tblRange2.start, tblRange2.end);
11960
+ const tblTokens2 = tokens2.filter((t) => t.pos >= tblRange2.start && t.pos < tblRange2.end);
11961
+ let targetExists = false;
11962
+ for (const tc of directTcs2) {
11963
+ const tcTokens = tblTokens2.filter((t) => t.pos >= tc.start && t.pos < tc.end);
11964
+ if (tcTokens.some(
11965
+ (t) => t.kind === "cell_addr" && t.colAddr === targetCol && t.rowAddr === targetRow
11966
+ )) {
11967
+ targetExists = true;
11968
+ break;
11995
11969
  }
11996
11970
  }
11997
- resolvedEdits.push({
11998
- target,
11999
- set: edit.set,
12000
- expected: edit.expected,
12001
- editIdx: ei
12002
- });
11971
+ if (!targetExists) {
11972
+ const dirLabel = direction === "right" ? "\uC624\uB978\uCABD" : "\uC544\uB798";
11973
+ return {
11974
+ error: `\uB808\uC774\uBE14 "${label}" (\uD45C ${globalTableIndex}, \uD589 ${addrSpan.rowAddr}, \uC5F4 ${addrSpan.colAddr})\uC758 ${dirLabel} \uC140 (\uD589 ${targetRow}, \uC5F4 ${targetCol})\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. direction \uB610\uB294 \uC88C\uD45C\uB97C \uD655\uC778\uD558\uC138\uC694.`
11975
+ };
11976
+ }
11977
+ return { tableIndex: globalTableIndex, row: targetRow, col: targetCol };
11978
+ }
11979
+ const resolvedEdits = [];
11980
+ const resolveErrors = [];
11981
+ for (let i = 0; i < input.edits.length; i++) {
11982
+ const e = input.edits[i];
11983
+ if (!e) continue;
11984
+ if (e.label !== void 0 && (e.row !== void 0 || e.col !== void 0)) {
11985
+ resolveErrors.push(
11986
+ `\uD3B8\uC9D1 #${i + 1}: label\uACFC row/col\uC744 \uB3D9\uC2DC\uC5D0 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694.`
11987
+ );
11988
+ } else if (e.label !== void 0) {
11989
+ const direction = e.direction ?? "right";
11990
+ const resolved = resolveLabelAcrossSections(e.label, direction, e.tableIndex);
11991
+ if ("error" in resolved) {
11992
+ resolveErrors.push(`\uD3B8\uC9D1 #${i + 1} (\uB808\uC774\uBE14 "${e.label}"): ${resolved.error}`);
11993
+ } else {
11994
+ resolvedEdits.push({
11995
+ tableIndex: resolved.tableIndex,
11996
+ row: resolved.row,
11997
+ col: resolved.col,
11998
+ newText: e.newText,
11999
+ expectedText: e.expectedText,
12000
+ label: e.label
12001
+ });
12002
+ }
12003
+ } else if (e.tableIndex !== void 0 && e.row !== void 0 && e.col !== void 0) {
12004
+ resolvedEdits.push({
12005
+ tableIndex: e.tableIndex,
12006
+ row: e.row,
12007
+ col: e.col,
12008
+ newText: e.newText,
12009
+ expectedText: e.expectedText
12010
+ });
12011
+ } else {
12012
+ resolveErrors.push(
12013
+ `\uD3B8\uC9D1 #${i + 1}: \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB97C \uC644\uC804\uD788 \uC9C0\uC815\uD558\uC138\uC694. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`
12014
+ );
12015
+ }
12003
12016
  }
12004
12017
  if (resolveErrors.length > 0) {
12005
- return `\uC624\uB958: \uB2E4\uC74C \uD3B8\uC9D1 \uB300\uC0C1\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12018
+ return `\uC624\uB958: \uB2E4\uC74C \uB808\uC774\uBE14\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12006
12019
  ${resolveErrors.join("\n")}`;
12007
12020
  }
12008
- const sectionEditMap = /* @__PURE__ */ new Map();
12009
- for (const re of resolvedEdits) {
12010
- const si = sectionFiles.indexOf(re.target.sectionFile);
12011
- if (si < 0) continue;
12012
- if (!sectionEditMap.has(si)) sectionEditMap.set(si, []);
12013
- sectionEditMap.get(si).push({
12014
- target: re.target,
12015
- set: re.set,
12016
- expected: re.expected
12021
+ const editRequests = resolvedEdits.map((e) => ({
12022
+ tableIndex: e.tableIndex,
12023
+ row: e.row,
12024
+ col: e.col,
12025
+ newText: e.newText,
12026
+ expectedText: e.expectedText
12027
+ }));
12028
+ const { buffer: newBuffer, results } = await applyEditsToHwpx(
12029
+ new Uint8Array(originalBuffer.buffer),
12030
+ editRequests
12031
+ );
12032
+ const failedResults = results.map((r, i) => ({ r, i })).filter(({ r }) => !r.success);
12033
+ if (failedResults.length > 0) {
12034
+ const messages = failedResults.map(({ r, i }) => {
12035
+ const e = resolvedEdits[i];
12036
+ const label = e?.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 ` : "";
12037
+ return `\uD3B8\uC9D1 #${i + 1} (${label}\uD45C ${e?.tableIndex ?? "?"}, \uD589 ${e?.row ?? "?"}, \uC5F4 ${e?.col ?? "?"}): ${r.error}`;
12017
12038
  });
12018
- }
12019
- const sectionResults = [];
12020
- const newSectionXmls = [...sectionXmls];
12021
- for (const [si, edits] of sectionEditMap) {
12022
- const xml = sectionXmls[si] ?? "";
12023
- const { newXml, results } = applyFormObjectEdits(xml, edits);
12024
- newSectionXmls[si] = newXml;
12025
- sectionResults.push({ si, results, edits });
12026
- }
12027
- const failMessages = [];
12028
- const successMap = /* @__PURE__ */ new Map();
12029
- for (const sr of sectionResults) {
12030
- for (let i = 0; i < sr.edits.length; i++) {
12031
- const editReq = sr.edits[i];
12032
- const result = sr.results[i];
12033
- const resolved = resolvedEdits.find((r) => r.target === editReq.target);
12034
- if (resolved) {
12035
- successMap.set(resolved.editIdx, result);
12036
- if (!result.success) {
12037
- failMessages.push(
12038
- `\uD3B8\uC9D1 #${resolved.editIdx + 1} (name="${editReq.target.name}"): ${result.error}`
12039
- );
12040
- }
12041
- }
12042
- }
12043
- }
12044
- if (failMessages.length > 0) {
12045
12039
  return `\uC624\uB958: \uB2E4\uC74C \uD3B8\uC9D1\uC744 \uC801\uC6A9\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12046
- ${failMessages.join("\n")}`;
12047
- }
12048
- const out = new import_jszip.default();
12049
- const mimetypeEntry = zip.file("mimetype");
12050
- if (mimetypeEntry) {
12051
- out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
12052
- }
12053
- for (const [name, entry] of Object.entries(zip.files)) {
12054
- if (name === "mimetype" || entry.dir) continue;
12055
- const si = sectionFiles.indexOf(name);
12056
- if (si >= 0) {
12057
- out.file(name, newSectionXmls[si] ?? "");
12058
- } else {
12059
- out.file(name, await entry.async("uint8array"));
12060
- }
12040
+ ${messages.join("\n")}`;
12061
12041
  }
12062
- const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12063
- const newBuffer = new Uint8Array(buf);
12064
- const typeKo = {
12065
- button: "PushButton",
12066
- checkBox: "CheckBox",
12067
- radioButton: "RadioButton",
12068
- comboBox: "ComboBox",
12069
- edit: "Edit"
12070
- };
12071
- const diffLines = ["| \uC591\uC2DD \uAC1C\uCCB4 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12072
- for (const re of resolvedEdits) {
12073
- const result = successMap.get(re.editIdx);
12074
- const oldVal = result?.oldValue ?? "";
12075
- const oldStr = typeof oldVal === "boolean" ? oldVal ? "CHECKED" : "UNCHECKED" : String(oldVal);
12076
- let newStr;
12077
- if (re.set.caption !== void 0) newStr = re.set.caption;
12078
- else if (re.set.checked !== void 0) newStr = re.set.checked ? "CHECKED" : "UNCHECKED";
12079
- else if (re.set.selected !== void 0) newStr = re.set.selected;
12080
- else newStr = re.set.text ?? "";
12081
- diffLines.push(`| ${re.target.name}(${typeKo[re.target.type]}) | ${oldStr} | ${newStr} |`);
12042
+ const diffLines = ["| \uD45C\xB7\uC140 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12043
+ for (let i = 0; i < resolvedEdits.length; i++) {
12044
+ const e = resolvedEdits[i];
12045
+ if (!e) continue;
12046
+ const oldText = results[i]?.oldText ?? "";
12047
+ const addr = e.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 #${e.tableIndex} (${e.row},${e.col})` : `#${e.tableIndex} (${e.row},${e.col})`;
12048
+ diffLines.push(`| ${addr} | ${oldText} | ${e.newText} |`);
12082
12049
  }
12083
12050
  const diff = diffLines.join("\n");
12084
12051
  const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
@@ -12087,7 +12054,7 @@ ${failMessages.join("\n")}`;
12087
12054
  return {
12088
12055
  proposal: {
12089
12056
  id: proposalId,
12090
- kind: "form-object",
12057
+ kind: "cell-edit",
12091
12058
  targetPath: outputPath,
12092
12059
  stagedPath,
12093
12060
  summary: input.summary,
@@ -12104,443 +12071,644 @@ ${failMessages.join("\n")}`;
12104
12071
  };
12105
12072
  }
12106
12073
  };
12107
- var DOC_EXTENSIONS = /* @__PURE__ */ new Set([
12108
- ".hwp",
12109
- ".hwpx",
12110
- ".hwpml",
12111
- ".docx",
12112
- ".doc",
12113
- ".xlsx",
12114
- ".xls",
12115
- ".pdf",
12116
- ".pptx",
12117
- ".ppt",
12118
- ".md",
12119
- ".txt"
12120
- ]);
12121
- var SKIP_DIRS = /* @__PURE__ */ new Set([
12122
- "node_modules",
12123
- ".git",
12124
- ".DS_Store",
12125
- "__pycache__",
12126
- "dist",
12127
- "build",
12128
- ".next"
12129
- ]);
12130
- var MAX_DEPTH = 4;
12131
- var MAX_FILES = 500;
12132
- var listFilesSchema = z4.object({
12133
- dir: z4.string().optional().describe("\uBAA9\uB85D\uC744 \uC870\uD68C\uD560 \uB514\uB809\uD130\uB9AC (\uBBF8\uC9C0\uC815 \uC2DC cwd \uC804\uCCB4)")
12074
+ var findInDocumentSchema = z4.object({
12075
+ path: z4.string().describe("\uAC80\uC0C9\uD560 .hwpx \uBB38\uC11C \uACBD\uB85C"),
12076
+ query: z4.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
12077
+ caseSensitive: z4.boolean().optional().describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8 false)")
12134
12078
  });
12135
- async function collectFiles(dir, cwd, depth, entries) {
12136
- if (depth > MAX_DEPTH || entries.length >= MAX_FILES) return;
12137
- let items;
12138
- try {
12139
- items = await readdir3(dir);
12140
- } catch {
12141
- return;
12142
- }
12143
- for (const item of items) {
12144
- if (entries.length >= MAX_FILES) break;
12145
- if (item.startsWith(".")) continue;
12146
- if (SKIP_DIRS.has(item)) continue;
12147
- const fullPath = join4(dir, item);
12148
- let info;
12149
- try {
12150
- info = await stat3(fullPath);
12151
- } catch {
12152
- continue;
12079
+ function unescapeXml(text3) {
12080
+ return text3.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
12081
+ }
12082
+ function windowText(text3, query, caseSensitive, maxLen = 60) {
12083
+ const compare3 = caseSensitive ? text3 : text3.toLowerCase();
12084
+ const compareQuery = caseSensitive ? query : query.toLowerCase();
12085
+ const idx = compare3.indexOf(compareQuery);
12086
+ if (idx < 0) return text3.slice(0, maxLen);
12087
+ const half = Math.floor((maxLen - query.length) / 2);
12088
+ const start = Math.max(0, idx - half);
12089
+ const end = Math.min(text3.length, idx + query.length + half);
12090
+ const prefix = start > 0 ? "\u2026" : "";
12091
+ const suffix = end < text3.length ? "\u2026" : "";
12092
+ return prefix + text3.slice(start, end) + suffix;
12093
+ }
12094
+ function findInSectionXmls(xmls, query, caseSensitive) {
12095
+ const hits = [];
12096
+ let globalOffset = 0;
12097
+ for (let si = 0; si < xmls.length; si++) {
12098
+ const xml = xmls[si] ?? "";
12099
+ const tokens = tokenizeHwpxXml(xml);
12100
+ let localCount = 0;
12101
+ {
12102
+ let depth = 0;
12103
+ for (const tok of tokens) {
12104
+ if (tok.kind === "tbl_open") {
12105
+ if (depth === 0) localCount++;
12106
+ depth++;
12107
+ } else if (tok.kind === "tbl_close") {
12108
+ depth--;
12109
+ }
12110
+ }
12111
+ }
12112
+ for (let ti = 0; ti < localCount; ti++) {
12113
+ const range = findTopLevelTableRange(tokens, ti);
12114
+ if (!range) continue;
12115
+ const cells = collectDirectTcRanges(tokens, range.start, range.end);
12116
+ const tblTokens = tokens.filter((t) => t.pos >= range.start && t.pos < range.end);
12117
+ for (const cell of cells) {
12118
+ const cellText = readOwnTextFromTc(xml, tokens, cell.start, cell.end);
12119
+ const compare3 = caseSensitive ? cellText : cellText.toLowerCase();
12120
+ const compareQuery = caseSensitive ? query : query.toLowerCase();
12121
+ if (!compare3.includes(compareQuery)) continue;
12122
+ const cellTokens = tblTokens.filter((t) => t.pos >= cell.start && t.pos < cell.end);
12123
+ const addrTok = cellTokens.find((t) => t.kind === "cell_addr");
12124
+ if (addrTok === void 0) continue;
12125
+ const row = addrTok.rowAddr ?? 0;
12126
+ const col = addrTok.colAddr ?? 0;
12127
+ const truncated = cellText.length > 60 ? `${cellText.slice(0, 57)}\u2026` : cellText;
12128
+ hits.push({
12129
+ kind: "\uD45C",
12130
+ tableIndex: globalOffset + ti,
12131
+ row,
12132
+ col,
12133
+ text: truncated
12134
+ });
12135
+ }
12153
12136
  }
12154
- const relPath = relative2(cwd, fullPath);
12155
- const ext = extname4(item).toLowerCase();
12156
- const isDoc = DOC_EXTENSIONS.has(ext);
12157
- if (info.isDirectory()) {
12158
- entries.push({ path: relPath + "/", isDir: true, isDoc: false });
12159
- await collectFiles(fullPath, cwd, depth + 1, entries);
12160
- } else if (info.isFile()) {
12161
- entries.push({ path: relPath, isDir: false, isDoc });
12137
+ {
12138
+ const bodySegments = [];
12139
+ let tblDepth = 0;
12140
+ let segStart = 0;
12141
+ for (const tok of tokens) {
12142
+ if (tok.kind === "tbl_open") {
12143
+ if (tblDepth === 0) {
12144
+ if (tok.pos > segStart) {
12145
+ bodySegments.push({ start: segStart, end: tok.pos });
12146
+ }
12147
+ }
12148
+ tblDepth++;
12149
+ } else if (tok.kind === "tbl_close") {
12150
+ tblDepth--;
12151
+ if (tblDepth === 0) {
12152
+ segStart = tok.end;
12153
+ }
12154
+ }
12155
+ }
12156
+ if (segStart < xml.length) {
12157
+ bodySegments.push({ start: segStart, end: xml.length });
12158
+ }
12159
+ const tRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
12160
+ for (const seg of bodySegments) {
12161
+ const slice = xml.slice(seg.start, seg.end);
12162
+ tRe.lastIndex = 0;
12163
+ let m = tRe.exec(slice);
12164
+ while (m !== null) {
12165
+ const rawText = m[1] ?? "";
12166
+ const decodedText = unescapeXml(rawText);
12167
+ const compare3 = caseSensitive ? decodedText : decodedText.toLowerCase();
12168
+ const compareQuery = caseSensitive ? query : query.toLowerCase();
12169
+ if (compare3.includes(compareQuery)) {
12170
+ hits.push({
12171
+ kind: "\uBCF8\uBB38",
12172
+ section: si,
12173
+ text: windowText(decodedText, query, caseSensitive)
12174
+ });
12175
+ }
12176
+ m = tRe.exec(slice);
12177
+ }
12178
+ }
12162
12179
  }
12180
+ globalOffset += localCount;
12163
12181
  }
12182
+ return hits;
12164
12183
  }
12165
- var listFilesTool = {
12166
- name: "list_files",
12167
- description: "\uD604\uC7AC \uC791\uC5C5 \uB514\uB809\uD130\uB9AC(cwd) \uC774\uD558\uC758 \uD30C\uC77C \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. \uBB38\uC11C \uD30C\uC77C(.hwp/.hwpx/.docx/.xlsx/.pdf \uB4F1)\uC774 \uCD5C\uC0C1\uB2E8\uC5D0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uAE4A\uC774\uB294 \uCD5C\uB300 4\uB2E8\uACC4\uAE4C\uC9C0\uC785\uB2C8\uB2E4.",
12168
- inputSchema: listFilesSchema,
12184
+ var MAX_HITS = 50;
12185
+ function formatHits(hits, query) {
12186
+ const shown = hits.slice(0, MAX_HITS);
12187
+ const lines = shown.map((h) => {
12188
+ if (h.kind === "\uD45C") {
12189
+ return `[\uD45C] tableIndex=${h.tableIndex}, row=${h.row}, col=${h.col} \u2014 "${h.text}" (propose_cell_edit\uC73C\uB85C \uC218\uC815 \uAC00\uB2A5)`;
12190
+ }
12191
+ return `[\uBCF8\uBB38] \uC139\uC158 ${h.section} \u2014 "\u2026${h.text}\u2026" (propose_find_replace\uB85C \uC218\uC815 \uAC00\uB2A5)`;
12192
+ });
12193
+ const notice = hits.length > MAX_HITS ? `
12194
+ (\uCD1D ${hits.length}\uAC1C \uB9E4\uCE6D \uC911 ${MAX_HITS}\uAC1C\uB9CC \uD45C\uC2DC\uB429\uB2C8\uB2E4.)` : "";
12195
+ return `"${query}" \uAC80\uC0C9 \uACB0\uACFC: ${hits.length}\uAC1C
12196
+
12197
+ ${lines.join("\n")}${notice}`;
12198
+ }
12199
+ var findInDocumentTool = {
12200
+ name: "find_in_document",
12201
+ description: "\uBB38\uC11C\uC5D0\uC11C \uD2B9\uC815 \uD14D\uC2A4\uD2B8\uAC00 **\uC5B4\uB514\uC5D0 \uC788\uB294\uC9C0** \uC704\uCE58\uB97C \uCC3E\uC544 \uC90D\uB2C8\uB2E4. \uD45C \uC548\uC758 \uC140\uC774\uBA74 propose_cell_edit\uC5D0\uC11C \uBC14\uB85C \uC4F8 \uC218 \uC788\uB294 tableIndex\xB7row\xB7col \uC88C\uD45C\uB97C, \uBCF8\uBB38\uC774\uBA74 \uC8FC\uBCC0 \uB9E5\uB77D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. \uD070 \uBB38\uC11C\uC5D0\uC11C \uC804\uCCB4\uB97C \uC77D\uC9C0 \uC54A\uACE0 \uC218\uC815 \uB300\uC0C1\uC744 \uC815\uD655\uD788 \uC9C0\uC815\uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. .hwpx \uC804\uC6A9.",
12202
+ inputSchema: findInDocumentSchema,
12169
12203
  requiresApproval: false,
12170
12204
  execute: async ({
12171
12205
  input,
12172
12206
  ctx
12173
12207
  }) => {
12174
- const targetDir = input.dir ? await resolveSafePath(ctx.cwd, input.dir) : ctx.cwd;
12175
- const entries = [];
12176
- await collectFiles(targetDir, ctx.cwd, 0, entries);
12177
- if (entries.length === 0) {
12178
- return "\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
12208
+ let safePath;
12209
+ try {
12210
+ safePath = await resolveSafePath(ctx.cwd, input.path);
12211
+ } catch (err) {
12212
+ return `\uC624\uB958: \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. ${String(err)}`;
12179
12213
  }
12180
- const docs = entries.filter((e) => e.isDoc);
12181
- const dirs = entries.filter((e) => e.isDir);
12182
- const others = entries.filter((e) => !e.isDoc && !e.isDir);
12183
- const allSorted = [...docs, ...dirs, ...others];
12184
- const lines = allSorted.map((e) => {
12185
- const icon = e.isDir ? "\u{1F4C1}" : e.isDoc ? "\u{1F4C4}" : " ";
12186
- return `${icon} ${e.path}`;
12187
- });
12188
- const truncateNotice = entries.length >= MAX_FILES ? `
12189
- (\uCD5C\uB300 ${MAX_FILES}\uAC1C\uAE4C\uC9C0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uB354 \uC881\uC740 \uBC94\uC704\uB97C \uC9C0\uC815\uD558\uC138\uC694.)` : "";
12190
- return lines.join("\n") + truncateNotice;
12191
- }
12192
- };
12193
- var cellEditItemSchema = z5.object({
12194
- tableIndex: z5.number().int().nonnegative().optional().describe(
12195
- "\uC88C\uD45C \uBAA8\uB4DC: 0-based \uD45C \uC778\uB371\uC2A4(read_document\uC758 kordoc \uBE14\uB85D \uC21C\uC11C, \uC911\uCCA9\uD45C \uC81C\uC678). \uB808\uC774\uBE14 \uBAA8\uB4DC\uC5D0\uC120 \uD0D0\uC0C9 \uBC94\uC704 \uC81C\uD55C\uC6A9(\uC120\uD0DD)."
12196
- ),
12197
- row: z5.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 rowAddr (0-based)"),
12198
- col: z5.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 colAddr (0-based)"),
12199
- label: z5.string().optional().describe(
12200
- "\uB808\uC774\uBE14 \uBAA8\uB4DC: \uAE30\uC900 \uC140 \uD14D\uC2A4\uD2B8(\uD2B8\uB9BC \uBE44\uAD50). \uC774 \uC140\uC758 direction \uBC29\uD5A5 \uC778\uC811 \uC140\uC5D0 newText\uB97C \uAE30\uB85D. \uC88C\uD45C \uBAA8\uB4DC\uBA74 \uC0DD\uB7B5."
12201
- ),
12202
- direction: z5.enum(["right", "below"]).optional().describe("\uB808\uC774\uBE14 \uBAA8\uB4DC \uBC29\uD5A5. \uAE30\uBCF8 right(\uC624\uB978\uCABD \uC140), below(\uC544\uB798 \uC140). \uBCD1\uD569 span \uACE0\uB824."),
12203
- newText: z5.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
12204
- expectedText: z5.string().optional().describe(
12205
- "\uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8(\uC548\uC804 \uAC80\uC99D\uC6A9). \uBD88\uC77C\uCE58 \uC2DC \uC218\uC815\uD558\uC9C0 \uC54A\uC74C. \uC798\uBABB\uB41C \uC140 \uC218\uC815 \uBC29\uC9C0\uB97C \uC704\uD574 \uAD8C\uC7A5."
12206
- )
12207
- }).describe(
12208
- "\uD3B8\uC9D1 \uD56D\uBAA9. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label[+direction]) \uC911 \uD558\uB098\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. \uB458 \uB2E4 \uC9C0\uC815\uD558\uAC70\uB098 \uB458 \uB2E4 \uC0DD\uB7B5\uD558\uBA74 \uC624\uB958\uC785\uB2C8\uB2E4."
12209
- );
12210
- var proposeCellEditSchema = z5.object({
12211
- path: z5.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12212
- edits: z5.array(cellEditItemSchema).min(1).describe(
12213
- "\uD3B8\uC9D1 \uBAA9\uB85D. \uAC01 \uD56D\uBAA9\uC740 \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label+direction) \uC911 \uD558\uB098"
12214
- ),
12215
- summary: z5.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12216
- });
12217
- function tokenizeHwpxXml(xml) {
12218
- const tokens = [];
12219
- const re = /<hp:tbl[\s>]|<\/hp:tbl>|<hp:tc[\s>]|<\/hp:tc>|<hp:t\/>|<hp:t>|<hp:cellAddr[^/>]*|<hp:cellSpan[^/>]*/g;
12220
- let startPos = 0;
12221
- let m = re.exec(xml);
12222
- while (m !== null) {
12223
- const raw = m[0];
12224
- const pos = m.index;
12225
- if (raw.startsWith("<hp:tbl")) {
12226
- tokens.push({ kind: "tbl_open", pos, end: pos + raw.length });
12227
- } else if (raw === "</hp:tbl>") {
12228
- tokens.push({ kind: "tbl_close", pos, end: pos + raw.length });
12229
- } else if (raw.startsWith("<hp:tc")) {
12230
- tokens.push({ kind: "tc_open", pos, end: pos + raw.length });
12231
- } else if (raw === "</hp:tc>") {
12232
- tokens.push({ kind: "tc_close", pos, end: pos + raw.length });
12233
- } else if (raw === "<hp:t/>") {
12234
- tokens.push({ kind: "t_empty", pos, end: pos + raw.length });
12235
- } else if (raw === "<hp:t>") {
12236
- tokens.push({ kind: "t_open", pos, end: pos + raw.length });
12237
- } else if (raw.startsWith("<hp:cellAddr")) {
12238
- const colM = raw.match(/colAddr="(\d+)"/);
12239
- const rowM = raw.match(/rowAddr="(\d+)"/);
12240
- if (colM && rowM) {
12241
- const selfClose = xml.indexOf("/>", pos);
12242
- const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
12243
- tokens.push({
12244
- kind: "cell_addr",
12245
- pos,
12246
- end,
12247
- colAddr: Number(colM[1]),
12248
- rowAddr: Number(rowM[1])
12249
- });
12214
+ const ext = extname4(safePath).toLowerCase();
12215
+ if (ext !== ".hwpx") {
12216
+ return "find_in_document\uB294 .hwpx \uC804\uC6A9\uC785\uB2C8\uB2E4. \uBCF8\uBB38 \uD14D\uC2A4\uD2B8 \uAC80\uC0C9\uC740 read_document\uC758 search \uBAA8\uB4DC\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. (.hwp \uD30C\uC77C\uC740 \uD55C\uAE00\uC5D0\uC11C \uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx\uB85C \uBCC0\uD658 \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694.)";
12217
+ }
12218
+ let bytes;
12219
+ try {
12220
+ bytes = await readFile5(safePath);
12221
+ } catch {
12222
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
12223
+ }
12224
+ if (bytes[0] !== 80 || bytes[1] !== 75) {
12225
+ return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
12226
+ }
12227
+ let zip;
12228
+ try {
12229
+ zip = await import_jszip.default.loadAsync(bytes);
12230
+ } catch (err) {
12231
+ return `\uC624\uB958: .hwpx ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${String(err)}`;
12232
+ }
12233
+ const sectionNames = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
12234
+ const xmls = [];
12235
+ for (const name of sectionNames) {
12236
+ try {
12237
+ const entry = zip.file(name);
12238
+ const xml = entry ? await entry.async("string") : "";
12239
+ xmls.push(xml);
12240
+ } catch {
12241
+ xmls.push("");
12250
12242
  }
12251
- } else if (raw.startsWith("<hp:cellSpan")) {
12252
- const colM = raw.match(/colSpan="(\d+)"/);
12253
- const rowM = raw.match(/rowSpan="(\d+)"/);
12254
- const selfClose = xml.indexOf("/>", pos);
12255
- const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
12256
- tokens.push({
12257
- kind: "cell_span",
12258
- pos,
12259
- end,
12260
- colSpan: colM ? Number(colM[1]) : 1,
12261
- rowSpan: rowM ? Number(rowM[1]) : 1
12262
- });
12263
12243
  }
12264
- startPos = re.lastIndex;
12265
- m = re.exec(xml);
12244
+ if (xmls.length === 0) {
12245
+ return `\uC624\uB958: .hwpx \uD30C\uC77C\uC5D0\uC11C \uC139\uC158 XML\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
12246
+ }
12247
+ const caseSensitive = input.caseSensitive ?? false;
12248
+ let hits;
12249
+ try {
12250
+ hits = findInSectionXmls(xmls, input.query, caseSensitive);
12251
+ } catch (err) {
12252
+ return `\uC624\uB958: \uBB38\uC11C \uD0D0\uC0C9 \uC911 \uC608\uC678\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4: ${String(err)}`;
12253
+ }
12254
+ if (hits.length === 0) {
12255
+ return `\uBB38\uC11C\uC5D0\uC11C "${input.query}"\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${input.path}`;
12256
+ }
12257
+ return formatHits(hits, input.query);
12266
12258
  }
12267
- void startPos;
12268
- return tokens;
12269
- }
12259
+ };
12270
12260
  function escapeXml2(text3) {
12271
- return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12261
+ return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
12272
12262
  }
12273
- function collectDirectTcRanges(tokens, tblStart, tblEnd) {
12274
- const tblTokens = tokens.filter((t) => t.pos > tblStart && t.pos < tblEnd);
12275
- const tcRanges = [];
12276
- const tcStack = [];
12277
- let innerDepth = 0;
12278
- for (const tok of tblTokens) {
12279
- if (tok.kind === "tbl_open") {
12280
- innerDepth++;
12281
- } else if (tok.kind === "tbl_close") {
12282
- innerDepth--;
12283
- } else if (tok.kind === "tc_open") {
12284
- tcStack.push({ pos: tok.pos, depth: innerDepth });
12285
- } else if (tok.kind === "tc_close") {
12286
- const entry = tcStack.pop();
12287
- if (entry !== void 0) {
12288
- tcRanges.push({ start: entry.pos, end: tok.end, depth: entry.depth });
12289
- }
12290
- }
12291
- }
12292
- return tcRanges.filter((tc) => tc.depth === 0);
12263
+ function decodeXml(text3) {
12264
+ return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
12293
12265
  }
12294
- function readOwnTextFromTc(xml, tokens, tcStart, tcEnd) {
12295
- const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
12296
- let innerDepth = 0;
12297
- let text3 = "";
12298
- for (const t of tcTokens) {
12299
- if (t.kind === "tbl_open") innerDepth++;
12300
- else if (t.kind === "tbl_close") innerDepth--;
12301
- else if (t.kind === "t_empty" && innerDepth === 0) {
12302
- } else if (t.kind === "t_open" && innerDepth === 0) {
12303
- const closePos = xml.indexOf("</hp:t>", t.end);
12304
- if (closePos >= 0) {
12305
- text3 += xml.substring(t.end, closePos);
12306
- }
12307
- }
12308
- }
12309
- return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
12266
+ function getAttr(openTag, attr) {
12267
+ const re = new RegExp(`\\b${attr}="([^"]*)"`, "");
12268
+ const m = re.exec(openTag);
12269
+ return m ? decodeXml(m[1] ?? "") : "";
12310
12270
  }
12311
- function findTopLevelTableRange(tokens, tableIndex) {
12312
- let topLevelCount = 0;
12313
- let depth = 0;
12314
- let targetStart = -1;
12315
- for (const tok of tokens) {
12316
- if (tok.kind === "tbl_open") {
12317
- if (depth === 0) {
12318
- if (topLevelCount === tableIndex) {
12319
- targetStart = tok.pos;
12320
- }
12321
- topLevelCount++;
12322
- }
12323
- depth++;
12324
- } else if (tok.kind === "tbl_close") {
12325
- depth--;
12326
- if (depth === 0 && topLevelCount === tableIndex + 1 && targetStart >= 0) {
12327
- return { start: targetStart, end: tok.end };
12328
- }
12271
+ function parseFormObjects(xml, sectionFile, startIndex = 0) {
12272
+ const results = [];
12273
+ let idx = startIndex;
12274
+ const tagSpecs = [
12275
+ { xmlTag: "hp:btn", type: "button" },
12276
+ { xmlTag: "hp:checkBtn", type: "checkBox" },
12277
+ { xmlTag: "hp:radioBtn", type: "radioButton" },
12278
+ { xmlTag: "hp:comboBox", type: "comboBox" },
12279
+ { xmlTag: "hp:edit", type: "edit" }
12280
+ ];
12281
+ const hits = [];
12282
+ for (const spec of tagSpecs) {
12283
+ const re = new RegExp(`<${spec.xmlTag}\\b`, "g");
12284
+ for (let m = re.exec(xml); m !== null; m = re.exec(xml)) {
12285
+ hits.push({ pos: m.index, xmlTag: spec.xmlTag, type: spec.type });
12329
12286
  }
12330
12287
  }
12331
- return null;
12332
- }
12333
- function readCellAddrSpan(tokens, tcStart, tcEnd) {
12334
- const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
12335
- let addr = null;
12336
- let span = { colSpan: 1, rowSpan: 1 };
12337
- for (const t of tcTokens) {
12338
- if (t.kind === "cell_addr" && t.colAddr !== void 0 && t.rowAddr !== void 0) {
12339
- addr = { colAddr: t.colAddr, rowAddr: t.rowAddr };
12340
- } else if (t.kind === "cell_span" && t.colSpan !== void 0 && t.rowSpan !== void 0) {
12341
- span = { colSpan: t.colSpan, rowSpan: t.rowSpan };
12288
+ hits.sort((a, b) => a.pos - b.pos);
12289
+ for (const hit of hits) {
12290
+ const { pos, xmlTag, type } = hit;
12291
+ const openTagEnd = xml.indexOf(">", pos);
12292
+ if (openTagEnd < 0) continue;
12293
+ const openTag = xml.slice(pos, openTagEnd + 1);
12294
+ const closeTag = `</${xmlTag}>`;
12295
+ const closeIdx = xml.indexOf(closeTag, openTagEnd);
12296
+ if (closeIdx < 0) continue;
12297
+ const elementEnd = closeIdx + closeTag.length;
12298
+ const elementXml = xml.slice(pos, elementEnd);
12299
+ const name = getAttr(openTag, "name");
12300
+ let currentValue;
12301
+ let comboItems;
12302
+ switch (type) {
12303
+ case "button":
12304
+ currentValue = getAttr(openTag, "caption");
12305
+ break;
12306
+ case "checkBox":
12307
+ case "radioButton":
12308
+ currentValue = getAttr(openTag, "value") === "CHECKED";
12309
+ break;
12310
+ case "comboBox": {
12311
+ currentValue = getAttr(openTag, "selectedValue");
12312
+ const listItemRe = /<hp:listItem\b[^>]*/g;
12313
+ const items = [];
12314
+ for (let lm = listItemRe.exec(elementXml); lm !== null; lm = listItemRe.exec(elementXml)) {
12315
+ const itemValue = getAttr(lm[0] ?? "", "value");
12316
+ items.push(itemValue);
12317
+ }
12318
+ comboItems = items;
12319
+ break;
12320
+ }
12321
+ case "edit": {
12322
+ const textSelfClose = /<hp:text\s*\/>/;
12323
+ const textOpen = /<hp:text>/;
12324
+ const textClose = "</hp:text>";
12325
+ if (textSelfClose.test(elementXml)) {
12326
+ currentValue = "";
12327
+ } else {
12328
+ const tOpenIdx = elementXml.search(textOpen);
12329
+ if (tOpenIdx >= 0) {
12330
+ const afterOpen = elementXml.indexOf(">", tOpenIdx) + 1;
12331
+ const closePos = elementXml.indexOf(textClose, afterOpen);
12332
+ currentValue = closePos >= 0 ? decodeXml(elementXml.slice(afterOpen, closePos)) : "";
12333
+ } else {
12334
+ currentValue = "";
12335
+ }
12336
+ }
12337
+ break;
12338
+ }
12342
12339
  }
12340
+ results.push({
12341
+ index: idx++,
12342
+ name,
12343
+ type,
12344
+ currentValue,
12345
+ comboItems,
12346
+ sectionFile,
12347
+ posInSection: pos
12348
+ });
12343
12349
  }
12344
- if (!addr) return null;
12345
- return { ...addr, ...span };
12350
+ return results;
12346
12351
  }
12347
- function applyCellEditsToSectionXml(xml, edits) {
12348
- const tokens = tokenizeHwpxXml(xml);
12352
+ function applyFormObjectEdits(xml, edits) {
12349
12353
  const results = edits.map(() => ({ success: false }));
12350
- let totalTopLevelTbls = 0;
12351
- {
12352
- let d = 0;
12353
- for (const tok of tokens) {
12354
- if (tok.kind === "tbl_open") {
12355
- if (d === 0) totalTopLevelTbls++;
12356
- d++;
12357
- } else if (tok.kind === "tbl_close") {
12358
- d--;
12359
- }
12360
- }
12361
- }
12362
- const replacements = [];
12354
+ const patches = [];
12363
12355
  for (let ei = 0; ei < edits.length; ei++) {
12364
12356
  const edit = edits[ei];
12365
- const tblRange = findTopLevelTableRange(tokens, edit.tableIndex);
12366
- if (!tblRange) {
12357
+ const { target, set, expected } = edit;
12358
+ const pos = target.posInSection;
12359
+ const xmlTag = typeToXmlTag(target.type);
12360
+ const openTagEnd = xml.indexOf(">", pos);
12361
+ if (openTagEnd < 0) {
12367
12362
  results[ei] = {
12368
12363
  success: false,
12369
- error: `\uD45C ${edit.tableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774 \uC139\uC158\uC5D0 \uCD1D ${totalTopLevelTbls}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
12364
+ error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}" \uC5EC\uB294 \uD0DC\uADF8 \uB05D\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
12370
12365
  };
12371
12366
  continue;
12372
12367
  }
12373
- const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
12374
- const tblTokens = tokens.filter((t) => t.pos >= tblRange.start && t.pos < tblRange.end);
12375
- let found = false;
12376
- for (const tc of directTcs) {
12377
- const tcTokens = tblTokens.filter((t) => t.pos >= tc.start && t.pos < tc.end);
12378
- const hasAddr = tcTokens.some(
12379
- (t) => t.kind === "cell_addr" && t.colAddr === edit.col && t.rowAddr === edit.row
12380
- );
12381
- if (!hasAddr) continue;
12382
- let innerDepth = 0;
12383
- const ownTRuns = [];
12384
- for (const t of tokens.filter((x) => x.pos >= tc.start && x.pos < tc.end)) {
12385
- if (t.kind === "tbl_open") innerDepth++;
12386
- else if (t.kind === "tbl_close") innerDepth--;
12387
- else if (t.kind === "t_empty" && innerDepth === 0) {
12388
- ownTRuns.push({ isEmpty: true, tagPos: t.pos, tagEnd: t.end });
12389
- } else if (t.kind === "t_open" && innerDepth === 0) {
12390
- const closePos = xml.indexOf("</hp:t>", t.end);
12391
- if (closePos >= 0) {
12392
- ownTRuns.push({ isEmpty: false, openEnd: t.end, closePos });
12393
- }
12394
- }
12368
+ const _openTag = xml.slice(pos, openTagEnd + 1);
12369
+ const closeTag = `</${xmlTag}>`;
12370
+ const closeIdx = xml.indexOf(closeTag, openTagEnd);
12371
+ if (closeIdx < 0) {
12372
+ results[ei] = {
12373
+ success: false,
12374
+ error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}" \uB2EB\uB294 \uD0DC\uADF8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
12375
+ };
12376
+ continue;
12377
+ }
12378
+ const elementEnd = closeIdx + closeTag.length;
12379
+ const typeError = validateSetForType(target.type, set);
12380
+ if (typeError) {
12381
+ results[ei] = { success: false, error: typeError };
12382
+ continue;
12383
+ }
12384
+ const currentRaw = readCurrentValue(
12385
+ xml,
12386
+ pos,
12387
+ openTagEnd,
12388
+ openTagEnd + 1,
12389
+ elementEnd,
12390
+ target.type
12391
+ );
12392
+ if (expected !== void 0) {
12393
+ const mismatch = checkExpected(expected, currentRaw, target);
12394
+ if (mismatch) {
12395
+ results[ei] = { success: false, error: mismatch };
12396
+ continue;
12395
12397
  }
12396
- const currentText = ownTRuns.map((r) => r.isEmpty ? "" : xml.substring(r.openEnd, r.closePos)).join("").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
12397
- if (edit.expectedText !== void 0 && edit.expectedText !== currentText) {
12398
+ }
12399
+ if (target.type === "comboBox" && set.selected !== void 0) {
12400
+ const items = target.comboItems ?? [];
12401
+ if (!items.includes(set.selected)) {
12402
+ const validList = items.map((v) => `"${v}"`).join(", ");
12398
12403
  results[ei] = {
12399
12404
  success: false,
12400
- oldText: currentText,
12401
- error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC758 \uD604\uC7AC \uD14D\uC2A4\uD2B8\uAC00 \uC608\uC0C1\uAC12\uACFC \uB2E4\uB985\uB2C8\uB2E4. \uC608\uC0C1: "${edit.expectedText}", \uC2E4\uC81C: "${currentText}". \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`
12405
+ error: `comboBox "${target.name}"\uC758 selected \uAC12 "${set.selected}"\uC774 \uC720\uD6A8\uD55C \uD56D\uBAA9\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uC720\uD6A8\uD55C \uD56D\uBAA9: ${validList || "(\uC5C6\uC74C)"}`
12402
12406
  };
12403
- found = true;
12404
- break;
12407
+ continue;
12405
12408
  }
12406
- if (ownTRuns.length === 0) {
12409
+ }
12410
+ let patchCreated = false;
12411
+ if (set.caption !== void 0) {
12412
+ const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "caption", escapeXml2(set.caption));
12413
+ if (!patch) {
12407
12414
  results[ei] = {
12408
12415
  success: false,
12409
- oldText: currentText,
12410
- error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC5D0 \uD14D\uC2A4\uD2B8 \uB7F0\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. <hp:t> \uB610\uB294 <hp:t/> \uB7F0\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC140 \uAD6C\uC870\uB97C \uD655\uC778\uD558\uC138\uC694.`
12416
+ error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 caption \uC18D\uC131\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
12411
12417
  };
12412
- found = true;
12413
- break;
12418
+ continue;
12414
12419
  }
12415
- const escapedNew = escapeXml2(edit.newText);
12416
- const patches = [];
12417
- const firstRun = ownTRuns[0];
12418
- if (firstRun.isEmpty) {
12419
- patches.push({
12420
- from: firstRun.tagPos,
12421
- to: firstRun.tagEnd,
12422
- text: `<hp:t>${escapedNew}</hp:t>`
12423
- });
12424
- } else {
12425
- patches.push({ from: firstRun.openEnd, to: firstRun.closePos, text: escapedNew });
12420
+ patches.push(patch);
12421
+ patchCreated = true;
12422
+ } else if (set.checked !== void 0) {
12423
+ const newValue = set.checked ? "CHECKED" : "UNCHECKED";
12424
+ const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "value", newValue);
12425
+ if (!patch) {
12426
+ results[ei] = {
12427
+ success: false,
12428
+ error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 value \uC18D\uC131\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
12429
+ };
12430
+ continue;
12426
12431
  }
12427
- for (let ri = 1; ri < ownTRuns.length; ri++) {
12428
- const run = ownTRuns[ri];
12429
- if (!run.isEmpty) {
12430
- patches.push({ from: run.openEnd, to: run.closePos, text: "" });
12431
- }
12432
+ patches.push(patch);
12433
+ patchCreated = true;
12434
+ } else if (set.selected !== void 0) {
12435
+ const patch = replaceAttrInOpenTag(
12436
+ xml,
12437
+ pos,
12438
+ openTagEnd,
12439
+ "selectedValue",
12440
+ escapeXml2(set.selected)
12441
+ );
12442
+ if (!patch) {
12443
+ results[ei] = {
12444
+ success: false,
12445
+ error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 selectedValue \uC18D\uC131\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
12446
+ };
12447
+ continue;
12432
12448
  }
12433
- replacements.push({ editIdx: ei, patches });
12434
- results[ei] = { success: true, oldText: currentText };
12435
- found = true;
12436
- break;
12449
+ patches.push(patch);
12450
+ patchCreated = true;
12451
+ } else if (set.text !== void 0) {
12452
+ const elementContent = xml.slice(pos, elementEnd);
12453
+ const textPatch = replaceEditText(xml, pos, elementContent, set.text);
12454
+ if (!textPatch) {
12455
+ results[ei] = {
12456
+ success: false,
12457
+ error: `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}"\uC758 <hp:text> \uC694\uC18C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
12458
+ };
12459
+ continue;
12460
+ }
12461
+ patches.push(textPatch);
12462
+ patchCreated = true;
12437
12463
  }
12438
- if (!found) {
12439
- results[ei] = {
12440
- success: false,
12441
- error: `\uD45C ${edit.tableIndex}\uC5D0\uC11C \uC140 (\uD589 ${edit.row}, \uC5F4 ${edit.col})\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. cellAddr colAddr="${edit.col}" rowAddr="${edit.row}"\uC5D0 \uD574\uB2F9\uD558\uB294 \uC140\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
12442
- };
12464
+ if (!patchCreated) {
12465
+ results[ei] = { success: false, error: `\uD3B8\uC9D1 #${ei + 1}: set \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.` };
12466
+ continue;
12443
12467
  }
12468
+ results[ei] = { success: true, oldValue: currentRaw };
12444
12469
  }
12445
12470
  if (results.some((r) => !r.success)) {
12446
12471
  return { newXml: xml, results };
12447
12472
  }
12448
- const allPatches = replacements.flatMap((r) => r.patches).sort((a, b) => b.from - a.from);
12473
+ const sortedPatches = [...patches].sort((a, b) => b.from - a.from);
12449
12474
  let result = xml;
12450
- for (const patch of allPatches) {
12451
- result = result.substring(0, patch.from) + patch.text + result.substring(patch.to);
12475
+ for (const patch of sortedPatches) {
12476
+ result = result.slice(0, patch.from) + patch.text + result.slice(patch.to);
12452
12477
  }
12453
12478
  return { newXml: result, results };
12454
12479
  }
12455
- async function applyEditsToHwpx(hwpxBuffer, edits) {
12456
- const zip = await import_jszip2.default.loadAsync(hwpxBuffer);
12480
+ function typeToXmlTag(type) {
12481
+ switch (type) {
12482
+ case "button":
12483
+ return "hp:btn";
12484
+ case "checkBox":
12485
+ return "hp:checkBtn";
12486
+ case "radioButton":
12487
+ return "hp:radioBtn";
12488
+ case "comboBox":
12489
+ return "hp:comboBox";
12490
+ case "edit":
12491
+ return "hp:edit";
12492
+ }
12493
+ }
12494
+ function validateSetForType(type, set) {
12495
+ const key = Object.keys(set).find((k) => set[k] !== void 0);
12496
+ if (!key) return "set \uD544\uB4DC\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. caption/checked/selected/text \uC911 \uD558\uB098\uB97C \uC9C0\uC815\uD558\uC138\uC694.";
12497
+ const allowed = {
12498
+ button: "caption",
12499
+ checkBox: "checked",
12500
+ radioButton: "checked",
12501
+ comboBox: "selected",
12502
+ edit: "text"
12503
+ };
12504
+ if (key !== allowed[type]) {
12505
+ const typeKo = {
12506
+ button: "PushButton",
12507
+ checkBox: "CheckBox",
12508
+ radioButton: "RadioButton",
12509
+ comboBox: "ComboBox",
12510
+ edit: "Edit"
12511
+ };
12512
+ return `\uD0C0\uC785 \uBD88\uC77C\uCE58: ${typeKo[type]} \uC591\uC2DD \uAC1C\uCCB4\uC5D0\uB294 "${allowed[type]}" \uD544\uB4DC\uB9CC \uC0AC\uC6A9 \uAC00\uB2A5\uD558\uC9C0\uB9CC "${key}"\uC774(\uAC00) \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`;
12513
+ }
12514
+ return null;
12515
+ }
12516
+ function readCurrentValue(xml, pos, openTagEnd, _afterOpen, elementEnd, type) {
12517
+ const openTag = xml.slice(pos, openTagEnd + 1);
12518
+ switch (type) {
12519
+ case "button":
12520
+ return decodeXml(getAttr(openTag, "caption"));
12521
+ case "checkBox":
12522
+ case "radioButton":
12523
+ return getAttr(openTag, "value") === "CHECKED";
12524
+ case "comboBox":
12525
+ return decodeXml(getAttr(openTag, "selectedValue"));
12526
+ case "edit": {
12527
+ const elementContent = xml.slice(pos, elementEnd);
12528
+ if (/<hp:text\s*\/>/.test(elementContent)) return "";
12529
+ const tOpenIdx = elementContent.search(/<hp:text>/);
12530
+ if (tOpenIdx >= 0) {
12531
+ const afterOpen = elementContent.indexOf(">", tOpenIdx) + 1;
12532
+ const closePos = elementContent.indexOf("</hp:text>", afterOpen);
12533
+ return closePos >= 0 ? decodeXml(elementContent.slice(afterOpen, closePos)) : "";
12534
+ }
12535
+ return "";
12536
+ }
12537
+ }
12538
+ }
12539
+ function checkExpected(expected, currentRaw, target) {
12540
+ let expectedVal;
12541
+ if (expected.caption !== void 0) expectedVal = expected.caption;
12542
+ else if (expected.checked !== void 0) expectedVal = expected.checked;
12543
+ else if (expected.selected !== void 0) expectedVal = expected.selected;
12544
+ else if (expected.text !== void 0) expectedVal = expected.text;
12545
+ if (expectedVal === void 0) return null;
12546
+ const matches = typeof expectedVal === "boolean" ? currentRaw === expectedVal : String(currentRaw) === String(expectedVal);
12547
+ if (!matches) {
12548
+ return `\uC591\uC2DD \uAC1C\uCCB4 "${target.name}" \uD604\uC7AC \uAC12\uC774 \uC608\uC0C1\uAC12\uACFC \uB2E4\uB985\uB2C8\uB2E4. \uC608\uC0C1: ${JSON.stringify(expectedVal)}, \uC2E4\uC81C: ${JSON.stringify(currentRaw)}. \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`;
12549
+ }
12550
+ return null;
12551
+ }
12552
+ function replaceAttrInOpenTag(xml, tagStart, tagEnd, attr, newValue) {
12553
+ const tagStr = xml.slice(tagStart, tagEnd + 1);
12554
+ const re = new RegExp(`\\b(${attr}=")([^"]*)(")`, "");
12555
+ const m = re.exec(tagStr);
12556
+ if (!m) return null;
12557
+ const relFrom = m.index + (m[1]?.length ?? 0);
12558
+ const relTo = relFrom + (m[2]?.length ?? 0);
12559
+ return {
12560
+ from: tagStart + relFrom,
12561
+ to: tagStart + relTo,
12562
+ text: newValue
12563
+ };
12564
+ }
12565
+ function replaceEditText(_xml, pos, elementContent, newText) {
12566
+ const escaped = escapeXml2(newText);
12567
+ const selfCloseRe = /<hp:text\s*\/>/;
12568
+ const scm = selfCloseRe.exec(elementContent);
12569
+ if (scm) {
12570
+ return {
12571
+ from: pos + scm.index,
12572
+ to: pos + scm.index + scm[0].length,
12573
+ text: `<hp:text>${escaped}</hp:text>`
12574
+ };
12575
+ }
12576
+ const openRe = /<hp:text>/;
12577
+ const om = openRe.exec(elementContent);
12578
+ if (om) {
12579
+ const afterOpen = om.index + om[0].length;
12580
+ const closePos = elementContent.indexOf("</hp:text>", afterOpen);
12581
+ if (closePos < 0) return null;
12582
+ return {
12583
+ from: pos + afterOpen,
12584
+ to: pos + closePos,
12585
+ text: escaped
12586
+ };
12587
+ }
12588
+ return null;
12589
+ }
12590
+ async function listFormObjectsFromZip(zip) {
12457
12591
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
12458
12592
  const sectionXmls = [];
12459
- const sectionTblCounts = [];
12593
+ const objects = [];
12594
+ let globalIndex = 0;
12460
12595
  for (const sf of sectionFiles) {
12461
12596
  const entry = zip.file(sf);
12462
12597
  const xml = entry ? await entry.async("string") : "";
12463
12598
  sectionXmls.push(xml);
12464
- const tokens = tokenizeHwpxXml(xml);
12465
- let count = 0;
12466
- let depth = 0;
12467
- for (const tok of tokens) {
12468
- if (tok.kind === "tbl_open") {
12469
- if (depth === 0) count++;
12470
- depth++;
12471
- } else if (tok.kind === "tbl_close") {
12472
- depth--;
12473
- }
12474
- }
12475
- sectionTblCounts.push(count);
12476
- }
12477
- const sectionEdits = sectionFiles.map(() => []);
12478
- let offset = 0;
12479
- for (let si = 0; si < sectionFiles.length; si++) {
12480
- const count = sectionTblCounts[si] ?? 0;
12481
- for (let ei = 0; ei < edits.length; ei++) {
12482
- const edit = edits[ei];
12483
- if (edit.tableIndex >= offset && edit.tableIndex < offset + count) {
12484
- const secEdits = sectionEdits[si];
12485
- if (secEdits) {
12486
- secEdits.push({
12487
- tableIndex: edit.tableIndex - offset,
12488
- // 섹션 내 상대 인덱스
12489
- row: edit.row,
12490
- col: edit.col,
12491
- newText: edit.newText,
12492
- expectedText: edit.expectedText,
12493
- originalEditIdx: ei
12494
- });
12495
- }
12496
- }
12497
- }
12498
- offset += count;
12599
+ const parsed = parseFormObjects(xml, sf, globalIndex);
12600
+ objects.push(...parsed);
12601
+ globalIndex += parsed.length;
12499
12602
  }
12500
- const totalTables = sectionTblCounts.reduce((a, b) => a + b, 0);
12501
- const allResults = edits.map((edit, ei) => ({
12502
- success: false,
12503
- error: `\uD45C ${edit?.tableIndex ?? ei}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uC5D0 \uCD1D ${totalTables}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
12504
- }));
12505
- const newSectionXmls = [...sectionXmls];
12506
- for (let si = 0; si < sectionFiles.length; si++) {
12507
- const sEdits = sectionEdits[si] ?? [];
12508
- if (sEdits.length === 0) continue;
12509
- const srcXml = sectionXmls[si] ?? "";
12510
- const { newXml, results } = applyCellEditsToSectionXml(srcXml, sEdits);
12511
- newSectionXmls[si] = newXml;
12512
- for (let i = 0; i < sEdits.length; i++) {
12513
- const sEdit = sEdits[i];
12514
- const res = results[i];
12515
- if (sEdit && res) {
12516
- allResults[sEdit.originalEditIdx] = res;
12517
- }
12518
- }
12603
+ return { objects, sectionFiles, sectionXmls };
12604
+ }
12605
+ function validateHwpxBuffer(ext, buffer) {
12606
+ if (ext === ".hwp") {
12607
+ return "\uC624\uB958: \uC774 \uD234\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
12519
12608
  }
12520
- if (allResults.some((r) => !r.success)) {
12521
- return { buffer: hwpxBuffer, results: allResults };
12609
+ if (ext !== ".hwpx") {
12610
+ return `\uC624\uB958: \uC774 \uD234\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}`;
12522
12611
  }
12523
- const out = new import_jszip2.default();
12524
- const mimetypeEntry = zip.file("mimetype");
12525
- if (mimetypeEntry) {
12526
- out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
12612
+ if (buffer[0] !== 80 || buffer[1] !== 75) {
12613
+ return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4.";
12527
12614
  }
12528
- for (const [name, entry] of Object.entries(zip.files)) {
12529
- if (name === "mimetype" || entry.dir) continue;
12530
- const sectionIdx = sectionFiles.indexOf(name);
12531
- if (sectionIdx >= 0) {
12532
- out.file(name, newSectionXmls[sectionIdx] ?? "");
12533
- } else {
12534
- out.file(name, await entry.async("uint8array"));
12615
+ return null;
12616
+ }
12617
+ var listFormObjectsSchema = z5.object({
12618
+ path: z5.string().describe("\uC77D\uC744 .hwpx \uD30C\uC77C \uACBD\uB85C")
12619
+ });
12620
+ var formEditSetSchema = z5.object({
12621
+ caption: z5.string().optional().describe("PushButton \uCEA1\uC158 \uD14D\uC2A4\uD2B8"),
12622
+ checked: z5.boolean().optional().describe("CheckBox/RadioButton \uCCB4\uD06C \uC0C1\uD0DC (true=CHECKED)"),
12623
+ selected: z5.string().optional().describe("ComboBox \uC120\uD0DD \uAC12 (listItem \uC911 \uD558\uB098\uC5EC\uC57C \uD568)"),
12624
+ text: z5.string().optional().describe("Edit \uD14D\uC2A4\uD2B8 \uB0B4\uC6A9")
12625
+ }).refine(
12626
+ (v) => {
12627
+ const keys = ["caption", "checked", "selected", "text"].filter(
12628
+ (k) => v[k] !== void 0
12629
+ );
12630
+ return keys.length === 1;
12631
+ },
12632
+ { message: "set \uD544\uB4DC\uB294 caption/checked/selected/text \uC911 \uC815\uD655\uD788 \uD558\uB098\uB9CC \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4." }
12633
+ );
12634
+ var formEditExpectedSchema = z5.object({
12635
+ caption: z5.string().optional(),
12636
+ checked: z5.boolean().optional(),
12637
+ selected: z5.string().optional(),
12638
+ text: z5.string().optional()
12639
+ }).optional();
12640
+ var formEditItemSchema = z5.object({
12641
+ name: z5.string().describe("\uC591\uC2DD \uAC1C\uCCB4\uC758 name \uC18D\uC131 \uAC12"),
12642
+ index: z5.number().int().nonnegative().optional().describe("\uB3D9\uC77C name\uC774 \uC5EC\uB7FF\uC778 \uACBD\uC6B0 \uBB38\uC11C \uC804\uCCB4 0-based \uC778\uB371\uC2A4\uB85C \uAD6C\uBD84"),
12643
+ set: formEditSetSchema.describe("\uBCC0\uACBD\uD560 \uAC12 (caption/checked/selected/text \uC911 \uD558\uB098)"),
12644
+ expected: formEditExpectedSchema.describe(
12645
+ "\uD604\uC7AC \uAC12 \uC0AC\uC804 \uAC80\uC99D (\uC548\uC804 \uC635\uC158). \uC2E4\uC81C \uAC12\uC774 \uB2E4\uB974\uBA74 \uC774 \uD3B8\uC9D1\uC744 \uCDE8\uC18C\uD558\uACE0 \uC624\uB958 \uBC18\uD658."
12646
+ )
12647
+ });
12648
+ var proposeFormObjectSchema = z5.object({
12649
+ path: z5.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C"),
12650
+ edits: z5.array(formEditItemSchema).min(1).describe("\uC591\uC2DD \uAC1C\uCCB4 \uD3B8\uC9D1 \uBAA9\uB85D"),
12651
+ summary: z5.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12652
+ });
12653
+ var listFormObjectsTool = {
12654
+ name: "list_form_objects",
12655
+ description: "HWPX \uBB38\uC11C\uC758 \uC591\uC2DD \uAC1C\uCCB4(form object) \uBAA9\uB85D\uC744 \uC5F4\uAC70\uD569\uB2C8\uB2E4. PushButton, CheckBox, RadioButton, ComboBox, Edit \uB2E4\uC12F \uAC00\uC9C0 \uD0C0\uC785\uC744 \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uAC01 \uC591\uC2DD \uAC1C\uCCB4\uC758 \uC774\uB984(name), \uD0C0\uC785, \uD604\uC7AC \uAC12, ComboBox\uC758 \uACBD\uC6B0 \uC120\uD0DD \uAC00\uB2A5\uD55C \uD56D\uBAA9 \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. propose_form_object\uB85C \uAC12\uC744 \uC218\uC815\uD558\uAE30 \uC804\uC5D0 \uC774 \uD234\uB85C \uBA3C\uC800 \uD604\uC7AC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4.",
12656
+ inputSchema: listFormObjectsSchema,
12657
+ requiresApproval: false,
12658
+ execute: async ({
12659
+ input,
12660
+ ctx
12661
+ }) => {
12662
+ const safePath = await resolveSafePath(ctx.cwd, input.path);
12663
+ const ext = extname5(safePath).toLowerCase();
12664
+ let buffer;
12665
+ try {
12666
+ buffer = await readFile6(safePath);
12667
+ } catch {
12668
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
12669
+ }
12670
+ const validationError = validateHwpxBuffer(ext, new Uint8Array(buffer.buffer));
12671
+ if (validationError) return validationError;
12672
+ let zip;
12673
+ try {
12674
+ zip = await import_jszip3.default.loadAsync(new Uint8Array(buffer.buffer));
12675
+ } catch {
12676
+ return "\uC624\uB958: .hwpx(ZIP) \uD30C\uC77C\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
12677
+ }
12678
+ const { objects } = await listFormObjectsFromZip(zip);
12679
+ if (objects.length === 0) {
12680
+ return "\uC774 \uBB38\uC11C\uC5D0\uB294 \uC591\uC2DD \uAC1C\uCCB4(form object)\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.";
12681
+ }
12682
+ const lines = [`\uCD1D ${objects.length}\uAC1C\uC758 \uC591\uC2DD \uAC1C\uCCB4\uAC00 \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
12683
+ `];
12684
+ const typeKo = {
12685
+ button: "PushButton",
12686
+ checkBox: "CheckBox",
12687
+ radioButton: "RadioButton",
12688
+ comboBox: "ComboBox",
12689
+ edit: "Edit"
12690
+ };
12691
+ for (const obj of objects) {
12692
+ let valueStr;
12693
+ if (typeof obj.currentValue === "boolean") {
12694
+ valueStr = obj.currentValue ? "\uCCB4\uD06C\uB428 (CHECKED)" : "\uCCB4\uD06C \uD574\uC81C\uB428 (UNCHECKED)";
12695
+ } else {
12696
+ valueStr = `"${obj.currentValue}"`;
12697
+ }
12698
+ let line = `[${obj.index}] name="${obj.name}" | \uD0C0\uC785: ${typeKo[obj.type]} | \uD604\uC7AC \uAC12: ${valueStr}`;
12699
+ if (obj.type === "comboBox" && obj.comboItems) {
12700
+ const itemList = obj.comboItems.map((v) => `"${v}"`).join(", ");
12701
+ line += ` | \uD56D\uBAA9: [${itemList}]`;
12702
+ }
12703
+ lines.push(line);
12535
12704
  }
12705
+ return lines.join("\n");
12536
12706
  }
12537
- const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12538
- return { buffer: new Uint8Array(buf), results: allResults };
12539
- }
12540
- var proposeCellEditTool = {
12541
- name: "propose_cell_edit",
12542
- description: "HWPX \uBB38\uC11C\uC758 \uD45C \uC140 \uB0B4\uC6A9\uC744 XML \uC9C1\uC811 \uD328\uCE58 \uBC29\uC2DD\uC73C\uB85C \uC218\uC815\uD569\uB2C8\uB2E4. \uBCD1\uD569 \uC140(cellSpan/rowSpan)\uC774 \uC788\uB294 \uD45C\uC5D0\uC11C\uB3C4 \uBCD1\uD569 \uAD6C\uC870\uB97C \uC644\uC804\uD788 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uBE48 \uC140(<hp:t/> self-closing \uB7F0)\uB3C4 \uCC44\uC6B8 \uC218 \uC788\uC5B4 \uC591\uC2DD(form) \uD3B8\uC9D1\uC5D0 \uC801\uD569\uD569\uB2C8\uB2E4. \uC140 \uC8FC\uC18C \uC9C0\uC815 \uBC29\uBC95: (1) \uC88C\uD45C \uBAA8\uB4DC \u2014 tableIndex + row + col \uC9C1\uC811 \uC9C0\uC815. (2) \uB808\uC774\uBE14 \uBAA8\uB4DC \u2014 label(\uC778\uC811 \uB808\uC774\uBE14 \uC140 \uD14D\uC2A4\uD2B8) + direction(right/below, \uAE30\uBCF8 right) + \uC120\uD0DD\uC801 tableIndex\uB85C \uB808\uC774\uBE14 \uC606/\uC544\uB798 \uC140\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uD3B8\uC9D1. \uBCD1\uD569 \uB808\uC774\uBE14 \uC140\uB3C4 colSpan/rowSpan\uC744 \uBC18\uC601\uD558\uC5EC \uB300\uC0C1 \uC140\uC744 \uACC4\uC0B0\uD569\uB2C8\uB2E4. propose_edit/propose_form_fill\uC740 \uB9C8\uD06C\uB2E4\uC6B4 \uB77C\uC6B4\uB4DC\uD2B8\uB9BD\uC73C\uB85C \uBCD1\uD569 \uC140\uC744 \uC18C\uC2E4\uC2DC\uD0A4\uBBC0\uB85C, \uBCD1\uD569 \uC140\uC774 \uC788\uB294 \uD45C\uB97C \uC218\uC815\uD560 \uB54C\uB294 \uC774 \uD234\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4. .hwp\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBA70 Hancom\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694. \uC218\uC815 \uC804\uC5D0 \uBC18\uB4DC\uC2DC read_document\uB85C \uC6D0\uBCF8\uC744 \uC77D\uACE0, expectedText\uB97C \uC9C0\uC815\uD558\uC5EC \uC548\uC804\uD558\uAC8C \uC218\uC815\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 diff \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
12543
- inputSchema: proposeCellEditSchema,
12707
+ };
12708
+ var proposeFormObjectTool = {
12709
+ name: "propose_form_object",
12710
+ description: "HWPX \uBB38\uC11C\uC758 \uC591\uC2DD \uAC1C\uCCB4(form object) \uAC12\uC744 XML \uC9C1\uC811 \uD328\uCE58 \uBC29\uC2DD\uC73C\uB85C \uC218\uC815\uD569\uB2C8\uB2E4. PushButton(caption), CheckBox/RadioButton(checked), ComboBox(selected), Edit(text)\uB97C \uC218\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. kordoc IR\uC5D0 \uC591\uC2DD \uAC1C\uCCB4 \uD0C0\uC785\uC774 \uC5C6\uC73C\uBBC0\uB85C section XML\uC744 \uC9C1\uC811 \uD328\uCE58\uD569\uB2C8\uB2E4. \uC218\uC815 \uC804\uC5D0 list_form_objects\uB85C \uD604\uC7AC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. name\uC774 \uC911\uBCF5\uB41C \uACBD\uC6B0 index\uB85C \uB300\uC0C1\uC744 \uC9C0\uC815\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 \uC0AC\uC6A9\uC790 \uC2B9\uC778 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4.",
12711
+ inputSchema: proposeFormObjectSchema,
12544
12712
  requiresApproval: true,
12545
12713
  propose: async ({
12546
12714
  input,
@@ -12548,196 +12716,145 @@ var proposeCellEditTool = {
12548
12716
  }) => {
12549
12717
  const safePath = await resolveSafePath(ctx.cwd, input.path);
12550
12718
  const ext = extname5(safePath).toLowerCase();
12551
- if (ext === ".hwp") {
12552
- return "\uC624\uB958: propose_cell_edit\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. \uB610\uB294 propose_edit\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC73C\uB098, \uBCD1\uD569 \uC140\uC774 \uC18C\uC2E4\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
12553
- }
12554
- if (ext !== ".hwpx") {
12555
- return `\uC624\uB958: propose_cell_edit\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}. \uD45C \uC140 \uC9C1\uC811 \uD3B8\uC9D1\uC740 .hwpx \uD3EC\uB9F7\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.`;
12556
- }
12557
12719
  let originalBuffer;
12558
12720
  try {
12559
- originalBuffer = await readFile5(safePath);
12721
+ originalBuffer = await readFile6(safePath);
12560
12722
  } catch {
12561
- return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uAC70\uB098 read_document\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
12562
- }
12563
- if (originalBuffer[0] !== 80 || originalBuffer[1] !== 75) {
12564
- return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
12565
- }
12566
- const zipForLabel = await import_jszip2.default.loadAsync(new Uint8Array(originalBuffer.buffer));
12567
- const sectionFilesForLabel = Object.keys(zipForLabel.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
12568
- const sectionInfos = [];
12569
- let globalTblOffset = 0;
12570
- for (const sf of sectionFilesForLabel) {
12571
- const entry = zipForLabel.file(sf);
12572
- const xml = entry ? await entry.async("string") : "";
12573
- const tokens = tokenizeHwpxXml(xml);
12574
- let count = 0;
12575
- let d = 0;
12576
- for (const tok of tokens) {
12577
- if (tok.kind === "tbl_open") {
12578
- if (d === 0) count++;
12579
- d++;
12580
- } else if (tok.kind === "tbl_close") {
12581
- d--;
12582
- }
12583
- }
12584
- sectionInfos.push({ xml, tblCount: count, globalOffset: globalTblOffset });
12585
- globalTblOffset += count;
12723
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
12586
12724
  }
12587
- function resolveLabelAcrossSections(label, direction, scopedTableIndex) {
12588
- const trimmedLabel = label.trim();
12589
- const allMatches = [];
12590
- for (const si of sectionInfos) {
12591
- const tokens = tokenizeHwpxXml(si.xml);
12592
- let localStart = 0;
12593
- let localEnd = si.tblCount - 1;
12594
- if (scopedTableIndex !== void 0) {
12595
- const localIdx2 = scopedTableIndex - si.globalOffset;
12596
- if (localIdx2 < 0 || localIdx2 >= si.tblCount) continue;
12597
- localStart = localIdx2;
12598
- localEnd = localIdx2;
12599
- }
12600
- for (let li = localStart; li <= localEnd; li++) {
12601
- const tblRange = findTopLevelTableRange(tokens, li);
12602
- if (!tblRange) continue;
12603
- const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
12604
- for (const tc of directTcs) {
12605
- const cellText = readOwnTextFromTc(si.xml, tokens, tc.start, tc.end).trim();
12606
- if (cellText === trimmedLabel) {
12607
- const addrSpan2 = readCellAddrSpan(tokens, tc.start, tc.end);
12608
- if (addrSpan2) {
12609
- allMatches.push({
12610
- globalTableIndex: si.globalOffset + li,
12611
- addrSpan: addrSpan2,
12612
- sectionXml: si.xml,
12613
- sectionOffset: si.globalOffset
12614
- });
12615
- }
12616
- }
12617
- }
12618
- }
12619
- }
12620
- if (allMatches.length === 0) {
12621
- const scope = scopedTableIndex !== void 0 ? `\uD45C ${scopedTableIndex}` : "\uBB38\uC11C \uB0B4 \uBAA8\uB4E0 \uD45C";
12622
- return {
12623
- error: `\uB808\uC774\uBE14 "${label}"\uC744(\uB97C) ${scope}\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.`
12624
- };
12625
- }
12626
- if (allMatches.length > 1) {
12627
- const locs = allMatches.map(
12628
- (m) => `\uD45C ${m.globalTableIndex} (\uD589 ${m.addrSpan.rowAddr}, \uC5F4 ${m.addrSpan.colAddr})`
12629
- ).join(", ");
12630
- return {
12631
- error: `\uB808\uC774\uBE14 "${label}"\uC774(\uAC00) \uC5EC\uB7EC \uC140\uC5D0\uC11C \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${locs}. tableIndex\uB85C \uD0D0\uC0C9 \uBC94\uC704\uB97C \uC881\uD788\uAC70\uB098 \uC88C\uD45C(row/col)\uB97C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694.`
12632
- };
12633
- }
12634
- const match = allMatches[0];
12635
- const { addrSpan, globalTableIndex, sectionXml, sectionOffset } = match;
12636
- let targetRow;
12637
- let targetCol;
12638
- if (direction === "right") {
12639
- targetRow = addrSpan.rowAddr;
12640
- targetCol = addrSpan.colAddr + addrSpan.colSpan;
12641
- } else {
12642
- targetRow = addrSpan.rowAddr + addrSpan.rowSpan;
12643
- targetCol = addrSpan.colAddr;
12644
- }
12645
- const localIdx = globalTableIndex - sectionOffset;
12646
- const tokens2 = tokenizeHwpxXml(sectionXml);
12647
- const tblRange2 = findTopLevelTableRange(tokens2, localIdx);
12648
- if (!tblRange2) {
12649
- return { error: `\uD45C ${globalTableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958).` };
12650
- }
12651
- const directTcs2 = collectDirectTcRanges(tokens2, tblRange2.start, tblRange2.end);
12652
- const tblTokens2 = tokens2.filter((t) => t.pos >= tblRange2.start && t.pos < tblRange2.end);
12653
- let targetExists = false;
12654
- for (const tc of directTcs2) {
12655
- const tcTokens = tblTokens2.filter((t) => t.pos >= tc.start && t.pos < tc.end);
12656
- if (tcTokens.some(
12657
- (t) => t.kind === "cell_addr" && t.colAddr === targetCol && t.rowAddr === targetRow
12658
- )) {
12659
- targetExists = true;
12660
- break;
12661
- }
12662
- }
12663
- if (!targetExists) {
12664
- const dirLabel = direction === "right" ? "\uC624\uB978\uCABD" : "\uC544\uB798";
12665
- return {
12666
- error: `\uB808\uC774\uBE14 "${label}" (\uD45C ${globalTableIndex}, \uD589 ${addrSpan.rowAddr}, \uC5F4 ${addrSpan.colAddr})\uC758 ${dirLabel} \uC140 (\uD589 ${targetRow}, \uC5F4 ${targetCol})\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. direction \uB610\uB294 \uC88C\uD45C\uB97C \uD655\uC778\uD558\uC138\uC694.`
12667
- };
12668
- }
12669
- return { tableIndex: globalTableIndex, row: targetRow, col: targetCol };
12725
+ const bufArray = new Uint8Array(originalBuffer.buffer);
12726
+ const validationError = validateHwpxBuffer(ext, bufArray);
12727
+ if (validationError) return validationError;
12728
+ let zip;
12729
+ try {
12730
+ zip = await import_jszip3.default.loadAsync(bufArray);
12731
+ } catch {
12732
+ return "\uC624\uB958: .hwpx(ZIP) \uD30C\uC77C\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
12670
12733
  }
12734
+ const { objects, sectionFiles, sectionXmls } = await listFormObjectsFromZip(zip);
12671
12735
  const resolvedEdits = [];
12672
12736
  const resolveErrors = [];
12673
- for (let i = 0; i < input.edits.length; i++) {
12674
- const e = input.edits[i];
12675
- if (!e) continue;
12676
- if (e.label !== void 0 && (e.row !== void 0 || e.col !== void 0)) {
12737
+ for (let ei = 0; ei < input.edits.length; ei++) {
12738
+ const edit = input.edits[ei];
12739
+ if (!edit) continue;
12740
+ const candidates = objects.filter((o) => o.name === edit.name);
12741
+ if (candidates.length === 0) {
12677
12742
  resolveErrors.push(
12678
- `\uD3B8\uC9D1 #${i + 1}: label\uACFC row/col\uC744 \uB3D9\uC2DC\uC5D0 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694.`
12743
+ `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}"\uC778 \uC591\uC2DD \uAC1C\uCCB4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. list_form_objects\uB85C \uBB38\uC11C\uC758 \uC591\uC2DD \uAC1C\uCCB4 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.`
12679
12744
  );
12680
- } else if (e.label !== void 0) {
12681
- const direction = e.direction ?? "right";
12682
- const resolved = resolveLabelAcrossSections(e.label, direction, e.tableIndex);
12683
- if ("error" in resolved) {
12684
- resolveErrors.push(`\uD3B8\uC9D1 #${i + 1} (\uB808\uC774\uBE14 "${e.label}"): ${resolved.error}`);
12685
- } else {
12686
- resolvedEdits.push({
12687
- tableIndex: resolved.tableIndex,
12688
- row: resolved.row,
12689
- col: resolved.col,
12690
- newText: e.newText,
12691
- expectedText: e.expectedText,
12692
- label: e.label
12693
- });
12745
+ continue;
12746
+ }
12747
+ let target;
12748
+ if (candidates.length > 1) {
12749
+ if (edit.index === void 0) {
12750
+ const indices = candidates.map((c) => c.index).join(", ");
12751
+ resolveErrors.push(
12752
+ `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}"\uC778 \uC591\uC2DD \uAC1C\uCCB4\uAC00 ${candidates.length}\uAC1C \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uBB38\uC11C \uC778\uB371\uC2A4: ${indices}). index \uD544\uB4DC\uB85C \uB300\uC0C1\uC744 \uC9C0\uC815\uD558\uC138\uC694.`
12753
+ );
12754
+ continue;
12694
12755
  }
12695
- } else if (e.tableIndex !== void 0 && e.row !== void 0 && e.col !== void 0) {
12696
- resolvedEdits.push({
12697
- tableIndex: e.tableIndex,
12698
- row: e.row,
12699
- col: e.col,
12700
- newText: e.newText,
12701
- expectedText: e.expectedText
12702
- });
12756
+ const byIndex = candidates.find((c) => c.index === edit.index);
12757
+ if (!byIndex) {
12758
+ resolveErrors.push(
12759
+ `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}", index=${edit.index}\uC778 \uC591\uC2DD \uAC1C\uCCB4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
12760
+ );
12761
+ continue;
12762
+ }
12763
+ target = byIndex;
12703
12764
  } else {
12704
- resolveErrors.push(
12705
- `\uD3B8\uC9D1 #${i + 1}: \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB97C \uC644\uC804\uD788 \uC9C0\uC815\uD558\uC138\uC694. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`
12706
- );
12765
+ target = candidates[0];
12766
+ if (edit.index !== void 0 && edit.index !== target.index) {
12767
+ resolveErrors.push(
12768
+ `\uD3B8\uC9D1 #${ei + 1}: name="${edit.name}"\uC758 \uBB38\uC11C \uC778\uB371\uC2A4\uB294 ${target.index}\uC774\uC9C0\uB9CC index=${edit.index}\uAC00 \uC9C0\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`
12769
+ );
12770
+ continue;
12771
+ }
12707
12772
  }
12773
+ resolvedEdits.push({
12774
+ target,
12775
+ set: edit.set,
12776
+ expected: edit.expected,
12777
+ editIdx: ei
12778
+ });
12708
12779
  }
12709
12780
  if (resolveErrors.length > 0) {
12710
- return `\uC624\uB958: \uB2E4\uC74C \uB808\uC774\uBE14\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12781
+ return `\uC624\uB958: \uB2E4\uC74C \uD3B8\uC9D1 \uB300\uC0C1\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12711
12782
  ${resolveErrors.join("\n")}`;
12712
12783
  }
12713
- const editRequests = resolvedEdits.map((e) => ({
12714
- tableIndex: e.tableIndex,
12715
- row: e.row,
12716
- col: e.col,
12717
- newText: e.newText,
12718
- expectedText: e.expectedText
12719
- }));
12720
- const { buffer: newBuffer, results } = await applyEditsToHwpx(
12721
- new Uint8Array(originalBuffer.buffer),
12722
- editRequests
12723
- );
12724
- const failedResults = results.map((r, i) => ({ r, i })).filter(({ r }) => !r.success);
12725
- if (failedResults.length > 0) {
12726
- const messages = failedResults.map(({ r, i }) => {
12727
- const e = resolvedEdits[i];
12728
- const label = e?.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 ` : "";
12729
- return `\uD3B8\uC9D1 #${i + 1} (${label}\uD45C ${e?.tableIndex ?? "?"}, \uD589 ${e?.row ?? "?"}, \uC5F4 ${e?.col ?? "?"}): ${r.error}`;
12784
+ const sectionEditMap = /* @__PURE__ */ new Map();
12785
+ for (const re of resolvedEdits) {
12786
+ const si = sectionFiles.indexOf(re.target.sectionFile);
12787
+ if (si < 0) continue;
12788
+ if (!sectionEditMap.has(si)) sectionEditMap.set(si, []);
12789
+ sectionEditMap.get(si).push({
12790
+ target: re.target,
12791
+ set: re.set,
12792
+ expected: re.expected
12730
12793
  });
12794
+ }
12795
+ const sectionResults = [];
12796
+ const newSectionXmls = [...sectionXmls];
12797
+ for (const [si, edits] of sectionEditMap) {
12798
+ const xml = sectionXmls[si] ?? "";
12799
+ const { newXml, results } = applyFormObjectEdits(xml, edits);
12800
+ newSectionXmls[si] = newXml;
12801
+ sectionResults.push({ si, results, edits });
12802
+ }
12803
+ const failMessages = [];
12804
+ const successMap = /* @__PURE__ */ new Map();
12805
+ for (const sr of sectionResults) {
12806
+ for (let i = 0; i < sr.edits.length; i++) {
12807
+ const editReq = sr.edits[i];
12808
+ const result = sr.results[i];
12809
+ const resolved = resolvedEdits.find((r) => r.target === editReq.target);
12810
+ if (resolved) {
12811
+ successMap.set(resolved.editIdx, result);
12812
+ if (!result.success) {
12813
+ failMessages.push(
12814
+ `\uD3B8\uC9D1 #${resolved.editIdx + 1} (name="${editReq.target.name}"): ${result.error}`
12815
+ );
12816
+ }
12817
+ }
12818
+ }
12819
+ }
12820
+ if (failMessages.length > 0) {
12731
12821
  return `\uC624\uB958: \uB2E4\uC74C \uD3B8\uC9D1\uC744 \uC801\uC6A9\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12732
- ${messages.join("\n")}`;
12822
+ ${failMessages.join("\n")}`;
12733
12823
  }
12734
- const diffLines = ["| \uD45C\xB7\uC140 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12735
- for (let i = 0; i < resolvedEdits.length; i++) {
12736
- const e = resolvedEdits[i];
12737
- if (!e) continue;
12738
- const oldText = results[i]?.oldText ?? "";
12739
- const addr = e.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 #${e.tableIndex} (${e.row},${e.col})` : `#${e.tableIndex} (${e.row},${e.col})`;
12740
- diffLines.push(`| ${addr} | ${oldText} | ${e.newText} |`);
12824
+ const out = new import_jszip3.default();
12825
+ const mimetypeEntry = zip.file("mimetype");
12826
+ if (mimetypeEntry) {
12827
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
12828
+ }
12829
+ for (const [name, entry] of Object.entries(zip.files)) {
12830
+ if (name === "mimetype" || entry.dir) continue;
12831
+ const si = sectionFiles.indexOf(name);
12832
+ if (si >= 0) {
12833
+ out.file(name, newSectionXmls[si] ?? "");
12834
+ } else {
12835
+ out.file(name, await entry.async("uint8array"));
12836
+ }
12837
+ }
12838
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12839
+ const newBuffer = new Uint8Array(buf);
12840
+ const typeKo = {
12841
+ button: "PushButton",
12842
+ checkBox: "CheckBox",
12843
+ radioButton: "RadioButton",
12844
+ comboBox: "ComboBox",
12845
+ edit: "Edit"
12846
+ };
12847
+ const diffLines = ["| \uC591\uC2DD \uAC1C\uCCB4 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12848
+ for (const re of resolvedEdits) {
12849
+ const result = successMap.get(re.editIdx);
12850
+ const oldVal = result?.oldValue ?? "";
12851
+ const oldStr = typeof oldVal === "boolean" ? oldVal ? "CHECKED" : "UNCHECKED" : String(oldVal);
12852
+ let newStr;
12853
+ if (re.set.caption !== void 0) newStr = re.set.caption;
12854
+ else if (re.set.checked !== void 0) newStr = re.set.checked ? "CHECKED" : "UNCHECKED";
12855
+ else if (re.set.selected !== void 0) newStr = re.set.selected;
12856
+ else newStr = re.set.text ?? "";
12857
+ diffLines.push(`| ${re.target.name}(${typeKo[re.target.type]}) | ${oldStr} | ${newStr} |`);
12741
12858
  }
12742
12859
  const diff = diffLines.join("\n");
12743
12860
  const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
@@ -12746,7 +12863,7 @@ ${messages.join("\n")}`;
12746
12863
  return {
12747
12864
  proposal: {
12748
12865
  id: proposalId,
12749
- kind: "cell-edit",
12866
+ kind: "form-object",
12750
12867
  targetPath: outputPath,
12751
12868
  stagedPath,
12752
12869
  summary: input.summary,
@@ -12763,6 +12880,92 @@ ${messages.join("\n")}`;
12763
12880
  };
12764
12881
  }
12765
12882
  };
12883
+ var DOC_EXTENSIONS = /* @__PURE__ */ new Set([
12884
+ ".hwp",
12885
+ ".hwpx",
12886
+ ".hwpml",
12887
+ ".docx",
12888
+ ".doc",
12889
+ ".xlsx",
12890
+ ".xls",
12891
+ ".pdf",
12892
+ ".pptx",
12893
+ ".ppt",
12894
+ ".md",
12895
+ ".txt"
12896
+ ]);
12897
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
12898
+ "node_modules",
12899
+ ".git",
12900
+ ".DS_Store",
12901
+ "__pycache__",
12902
+ "dist",
12903
+ "build",
12904
+ ".next"
12905
+ ]);
12906
+ var MAX_DEPTH = 4;
12907
+ var MAX_FILES = 500;
12908
+ var listFilesSchema = z6.object({
12909
+ dir: z6.string().optional().describe("\uBAA9\uB85D\uC744 \uC870\uD68C\uD560 \uB514\uB809\uD130\uB9AC (\uBBF8\uC9C0\uC815 \uC2DC cwd \uC804\uCCB4)")
12910
+ });
12911
+ async function collectFiles(dir, cwd, depth, entries) {
12912
+ if (depth > MAX_DEPTH || entries.length >= MAX_FILES) return;
12913
+ let items;
12914
+ try {
12915
+ items = await readdir3(dir);
12916
+ } catch {
12917
+ return;
12918
+ }
12919
+ for (const item of items) {
12920
+ if (entries.length >= MAX_FILES) break;
12921
+ if (item.startsWith(".")) continue;
12922
+ if (SKIP_DIRS.has(item)) continue;
12923
+ const fullPath = join4(dir, item);
12924
+ let info;
12925
+ try {
12926
+ info = await stat3(fullPath);
12927
+ } catch {
12928
+ continue;
12929
+ }
12930
+ const relPath = relative2(cwd, fullPath);
12931
+ const ext = extname6(item).toLowerCase();
12932
+ const isDoc = DOC_EXTENSIONS.has(ext);
12933
+ if (info.isDirectory()) {
12934
+ entries.push({ path: relPath + "/", isDir: true, isDoc: false });
12935
+ await collectFiles(fullPath, cwd, depth + 1, entries);
12936
+ } else if (info.isFile()) {
12937
+ entries.push({ path: relPath, isDir: false, isDoc });
12938
+ }
12939
+ }
12940
+ }
12941
+ var listFilesTool = {
12942
+ name: "list_files",
12943
+ description: "\uD604\uC7AC \uC791\uC5C5 \uB514\uB809\uD130\uB9AC(cwd) \uC774\uD558\uC758 \uD30C\uC77C \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. \uBB38\uC11C \uD30C\uC77C(.hwp/.hwpx/.docx/.xlsx/.pdf \uB4F1)\uC774 \uCD5C\uC0C1\uB2E8\uC5D0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uAE4A\uC774\uB294 \uCD5C\uB300 4\uB2E8\uACC4\uAE4C\uC9C0\uC785\uB2C8\uB2E4.",
12944
+ inputSchema: listFilesSchema,
12945
+ requiresApproval: false,
12946
+ execute: async ({
12947
+ input,
12948
+ ctx
12949
+ }) => {
12950
+ const targetDir = input.dir ? await resolveSafePath(ctx.cwd, input.dir) : ctx.cwd;
12951
+ const entries = [];
12952
+ await collectFiles(targetDir, ctx.cwd, 0, entries);
12953
+ if (entries.length === 0) {
12954
+ return "\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
12955
+ }
12956
+ const docs = entries.filter((e) => e.isDoc);
12957
+ const dirs = entries.filter((e) => e.isDir);
12958
+ const others = entries.filter((e) => !e.isDoc && !e.isDir);
12959
+ const allSorted = [...docs, ...dirs, ...others];
12960
+ const lines = allSorted.map((e) => {
12961
+ const icon = e.isDir ? "\u{1F4C1}" : e.isDoc ? "\u{1F4C4}" : " ";
12962
+ return `${icon} ${e.path}`;
12963
+ });
12964
+ const truncateNotice = entries.length >= MAX_FILES ? `
12965
+ (\uCD5C\uB300 ${MAX_FILES}\uAC1C\uAE4C\uC9C0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uB354 \uC881\uC740 \uBC94\uC704\uB97C \uC9C0\uC815\uD558\uC138\uC694.)` : "";
12966
+ return lines.join("\n") + truncateNotice;
12967
+ }
12968
+ };
12766
12969
  var HEADING_LEVELS = {
12767
12970
  1: HeadingLevel.HEADING_1,
12768
12971
  2: HeadingLevel.HEADING_2,
@@ -12885,10 +13088,10 @@ async function markdownToDocx(markdown) {
12885
13088
  });
12886
13089
  return Packer.toBuffer(doc);
12887
13090
  }
12888
- var proposeEditSchema = z6.object({
12889
- path: z6.string().describe("\uC218\uC815\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12890
- newMarkdown: z6.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD). read_document\uB85C \uC6D0\uBCF8\uC744 \uBA3C\uC800 \uC77D\uC5B4\uC57C \uD568"),
12891
- summary: z6.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13091
+ var proposeEditSchema = z7.object({
13092
+ path: z7.string().describe("\uC218\uC815\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13093
+ newMarkdown: z7.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD). read_document\uB85C \uC6D0\uBCF8\uC744 \uBA3C\uC800 \uC77D\uC5B4\uC57C \uD568"),
13094
+ summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12892
13095
  });
12893
13096
  var proposeEditTool = {
12894
13097
  name: "propose_edit",
@@ -12900,10 +13103,10 @@ var proposeEditTool = {
12900
13103
  ctx
12901
13104
  }) => {
12902
13105
  const safePath = await resolveSafePath(ctx.cwd, input.path);
12903
- const ext = extname6(safePath).toLowerCase();
13106
+ const ext = extname7(safePath).toLowerCase();
12904
13107
  let originalBuffer;
12905
13108
  try {
12906
- originalBuffer = await readFile6(safePath);
13109
+ originalBuffer = await readFile7(safePath);
12907
13110
  } catch {
12908
13111
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uAC70\uB098 read_document\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
12909
13112
  }
@@ -12974,19 +13177,19 @@ ${diff}`;
12974
13177
  };
12975
13178
  }
12976
13179
  };
12977
- var proposeFindReplaceSchema = z7.object({
12978
- path: z7.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12979
- find: z7.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
12980
- replace: z7.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
12981
- caseSensitive: z7.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
12982
- all: z7.boolean().optional().default(true).describe("\uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uAD50\uCCB4\uD560\uC9C0 \uC5EC\uBD80 (\uAE30\uBCF8\uAC12: true). false\uC774\uBA74 \uCCAB \uBC88\uC9F8 \uB9E4\uCE58\uB9CC \uAD50\uCCB4"),
12983
- summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13180
+ var proposeFindReplaceSchema = z8.object({
13181
+ path: z8.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13182
+ find: z8.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
13183
+ replace: z8.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
13184
+ caseSensitive: z8.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
13185
+ all: z8.boolean().optional().default(true).describe("\uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uAD50\uCCB4\uD560\uC9C0 \uC5EC\uBD80 (\uAE30\uBCF8\uAC12: true). false\uC774\uBA74 \uCCAB \uBC88\uC9F8 \uB9E4\uCE58\uB9CC \uAD50\uCCB4"),
13186
+ summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12984
13187
  });
12985
13188
  var MAX_DIFF_SAMPLES = 20;
12986
13189
  function escapeXml3(text3) {
12987
13190
  return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12988
13191
  }
12989
- function unescapeXml(text3) {
13192
+ function unescapeXml2(text3) {
12990
13193
  return text3.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
12991
13194
  }
12992
13195
  function makeChangeSnippet(before, after, ctx = 24) {
@@ -13010,7 +13213,7 @@ function collectChangedSnippets(beforeXml, afterXml, maxSamples) {
13010
13213
  const n = Math.min(beforeNodes.length, afterNodes.length);
13011
13214
  for (let i = 0; i < n && out.length < maxSamples; i++) {
13012
13215
  if (beforeNodes[i] !== afterNodes[i]) {
13013
- const snip = makeChangeSnippet(unescapeXml(beforeNodes[i]), unescapeXml(afterNodes[i]));
13216
+ const snip = makeChangeSnippet(unescapeXml2(beforeNodes[i]), unescapeXml2(afterNodes[i]));
13014
13217
  out.push(snip);
13015
13218
  }
13016
13219
  }
@@ -13093,7 +13296,7 @@ function countOccurrences(str, sub) {
13093
13296
  return count;
13094
13297
  }
13095
13298
  async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive, replaceAll) {
13096
- const zip = await import_jszip3.default.loadAsync(hwpxBuffer);
13299
+ const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
13097
13300
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13098
13301
  const sectionXmls = [];
13099
13302
  for (const sf of sectionFiles) {
@@ -13137,7 +13340,7 @@ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive,
13137
13340
  samples.push(snip);
13138
13341
  }
13139
13342
  }
13140
- const out = new import_jszip3.default();
13343
+ const out = new import_jszip4.default();
13141
13344
  const mimetypeEntry = zip.file("mimetype");
13142
13345
  if (mimetypeEntry) {
13143
13346
  out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
@@ -13168,7 +13371,7 @@ var proposeFindReplaceTool = {
13168
13371
  ctx
13169
13372
  }) => {
13170
13373
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13171
- const ext = extname7(safePath).toLowerCase();
13374
+ const ext = extname8(safePath).toLowerCase();
13172
13375
  if (ext === ".hwp") {
13173
13376
  return "\uC624\uB958: propose_find_replace\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 XML \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
13174
13377
  }
@@ -13177,7 +13380,7 @@ var proposeFindReplaceTool = {
13177
13380
  }
13178
13381
  let originalBuf;
13179
13382
  try {
13180
- originalBuf = await readFile7(safePath);
13383
+ originalBuf = await readFile8(safePath);
13181
13384
  } catch {
13182
13385
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13183
13386
  }
@@ -13270,10 +13473,10 @@ var proposeFindReplaceTool = {
13270
13473
  };
13271
13474
  }
13272
13475
  };
13273
- var proposeFormFillSchema = z8.object({
13274
- path: z8.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13275
- fields: z8.record(z8.string(), z8.string()).describe("\uCC44\uC6B8 \uD544\uB4DC \uB9E4\uD551: { \uB77C\uBCA8: \uAC12 }. read_document\uB85C \uBA3C\uC800 \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694"),
13276
- summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13476
+ var proposeFormFillSchema = z9.object({
13477
+ path: z9.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13478
+ fields: z9.record(z9.string(), z9.string()).describe("\uCC44\uC6B8 \uD544\uB4DC \uB9E4\uD551: { \uB77C\uBCA8: \uAC12 }. read_document\uB85C \uBA3C\uC800 \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694"),
13479
+ summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13277
13480
  });
13278
13481
  var proposeFormFillTool = {
13279
13482
  name: "propose_form_fill",
@@ -13285,13 +13488,13 @@ var proposeFormFillTool = {
13285
13488
  ctx
13286
13489
  }) => {
13287
13490
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13288
- const ext = extname8(safePath).toLowerCase();
13491
+ const ext = extname9(safePath).toLowerCase();
13289
13492
  if (ext !== ".hwpx" && ext !== ".hwp") {
13290
13493
  return `\uC624\uB958: propose_form_fill\uC740 .hwp/.hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}. .docx \uD30C\uC77C\uC740 propose_edit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
13291
13494
  }
13292
13495
  let originalBuffer;
13293
13496
  try {
13294
- originalBuffer = await readFile8(safePath);
13497
+ originalBuffer = await readFile9(safePath);
13295
13498
  } catch {
13296
13499
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13297
13500
  }
@@ -13359,16 +13562,216 @@ var proposeFormFillTool = {
13359
13562
  };
13360
13563
  }
13361
13564
  };
13362
- var proposeSheetEditSchema = z9.object({
13363
- path: z9.string().describe("\uC218\uC815\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13364
- updates: z9.array(
13365
- z9.object({
13366
- sheet: z9.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13367
- cell: z9.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
13368
- value: z9.union([z9.string(), z9.number()]).describe("\uC0C8 \uAC12")
13565
+ var proposeRedactPiiSchema = z10.object({
13566
+ path: z10.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD560 \uBB38\uC11C \uACBD\uB85C"),
13567
+ summary: z10.string().optional().describe("\uBCC0\uACBD \uC694\uC57D")
13568
+ });
13569
+ function mergeFindings(allFindings) {
13570
+ const map = /* @__PURE__ */ new Map();
13571
+ for (const findings of allFindings) {
13572
+ for (const f of findings) {
13573
+ const existing = map.get(f.type);
13574
+ if (existing) {
13575
+ existing.count += f.count;
13576
+ for (const m of f.masked) {
13577
+ if (!existing.masked.includes(m) && existing.masked.length < 5) {
13578
+ existing.masked.push(m);
13579
+ }
13580
+ }
13581
+ } else {
13582
+ map.set(f.type, { type: f.type, count: f.count, masked: [...f.masked] });
13583
+ }
13584
+ }
13585
+ }
13586
+ return [...map.values()];
13587
+ }
13588
+ async function applyRedactToHwpx(hwpxBuffer) {
13589
+ const zip = await import_jszip5.default.loadAsync(hwpxBuffer);
13590
+ const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13591
+ const sectionXmls = [];
13592
+ for (const sf of sectionFiles) {
13593
+ const entry = zip.file(sf);
13594
+ const xml = entry ? await entry.async("string") : "";
13595
+ sectionXmls.push(xml);
13596
+ }
13597
+ const newSectionXmls = [];
13598
+ const allFindings = [];
13599
+ let anyChanged = false;
13600
+ for (const srcXml of sectionXmls) {
13601
+ const tNodeRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
13602
+ const sectionFindings = [];
13603
+ let offset = 0;
13604
+ let result = srcXml;
13605
+ let m = tNodeRe.exec(srcXml);
13606
+ while (m !== null) {
13607
+ const content = m[1];
13608
+ if (content.length === 0) {
13609
+ m = tNodeRe.exec(srcXml);
13610
+ continue;
13611
+ }
13612
+ const { text: redacted, findings } = redactText(content);
13613
+ if (redacted !== content) {
13614
+ const openTagLen = "<hp:t>".length;
13615
+ const contentStart = m.index + offset + openTagLen;
13616
+ const contentEnd = contentStart + content.length;
13617
+ result = result.substring(0, contentStart) + redacted + result.substring(contentEnd);
13618
+ offset += redacted.length - content.length;
13619
+ anyChanged = true;
13620
+ }
13621
+ if (findings.length > 0) {
13622
+ sectionFindings.push(findings);
13623
+ }
13624
+ m = tNodeRe.exec(srcXml);
13625
+ }
13626
+ newSectionXmls.push(result);
13627
+ allFindings.push(...sectionFindings);
13628
+ }
13629
+ if (!anyChanged) {
13630
+ return { buffer: hwpxBuffer, findings: mergeFindings(allFindings), changed: false };
13631
+ }
13632
+ const out = new import_jszip5.default();
13633
+ const mimetypeEntry = zip.file("mimetype");
13634
+ if (mimetypeEntry) {
13635
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
13636
+ }
13637
+ for (const [name, entry] of Object.entries(zip.files)) {
13638
+ if (name === "mimetype" || entry.dir) continue;
13639
+ const sectionIdx = sectionFiles.indexOf(name);
13640
+ if (sectionIdx >= 0) {
13641
+ out.file(name, newSectionXmls[sectionIdx] ?? "");
13642
+ } else {
13643
+ out.file(name, await entry.async("uint8array"));
13644
+ }
13645
+ }
13646
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
13647
+ return {
13648
+ buffer: new Uint8Array(buf),
13649
+ findings: mergeFindings(allFindings),
13650
+ changed: true
13651
+ };
13652
+ }
13653
+ function buildDiff(findings) {
13654
+ const total = findings.reduce((s, f) => s + f.count, 0);
13655
+ const lines = [`\uAC1C\uC778\uC815\uBCF4 ${total}\uAC74 \uBE44\uC2DD\uBCC4 \uCC98\uB9AC`];
13656
+ for (const f of findings) {
13657
+ const examples = f.masked.slice(0, 3).join(", ");
13658
+ lines.push(`- ${f.type}: ${f.count}\uAC74 \u2192 ${examples}`);
13659
+ }
13660
+ return lines.join("\n");
13661
+ }
13662
+ var proposeRedactPiiTool = {
13663
+ name: "propose_redact_pii",
13664
+ description: "\uBB38\uC11C\uC758 \uAC1C\uC778\uC815\uBCF4(\uC8FC\uBBFC\uB4F1\uB85D\uBC88\uD638\xB7\uC804\uD654\uBC88\uD638\xB7\uC774\uBA54\uC77C\xB7\uC2E0\uC6A9\uCE74\uB4DC\uBC88\uD638)\uB97C \uAC00\uB9AC\uAE30/\uB9C8\uC2A4\uD0B9/\uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD569\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 '\uAC1C\uC778\uC815\uBCF4 \uAC00\uB824\uC918/\uC9C0\uC6CC\uC918/\uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD574\uC918/\uB9C8\uC2A4\uD0B9\uD574\uC918'\uB77C\uACE0 \uD558\uBA74 \uC774 \uB3C4\uAD6C\uB85C \uC218\uC815\uC548\uC744 \uC81C\uC548\uD558\uC138\uC694(scan_pii\uB294 \uD655\uC778\uC6A9\uC77C \uBFD0 \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). .hwpx\uB294 XML \uC9C1\uC811 \uD328\uCE58\uB85C \uAD6C\uC870(\uD45C\xB7\uC774\uBBF8\uC9C0\xB7\uC11C\uC2DD)\uB97C \uBCF4\uC874\uD558\uBA70, .md/.txt\uB3C4 \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uBCC0\uACBD\uC740 \uC2B9\uC778 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4. \uC6D0\uBB38 \uAC12\uC740 \uD45C\uC2DC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
13665
+ inputSchema: proposeRedactPiiSchema,
13666
+ requiresApproval: true,
13667
+ propose: async ({
13668
+ input,
13669
+ ctx
13670
+ }) => {
13671
+ const safePath = await resolveSafePath(ctx.cwd, input.path);
13672
+ const ext = extname10(safePath).toLowerCase();
13673
+ if (ext === ".hwp") {
13674
+ return "\uC624\uB958: propose_redact_pii\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 XML \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
13675
+ }
13676
+ if (ext !== ".hwpx" && ext !== ".md" && ext !== ".txt") {
13677
+ const hint = ext === ".docx" || ext === ".xlsx" ? " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4." : " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.";
13678
+ return `\uC624\uB958: propose_redact_pii\uB294 .hwpx/.md/.txt \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}.${hint}`;
13679
+ }
13680
+ const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13681
+ if (ext === ".hwpx") {
13682
+ let originalBuf;
13683
+ try {
13684
+ originalBuf = await readFile10(safePath);
13685
+ } catch {
13686
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13687
+ }
13688
+ if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
13689
+ return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
13690
+ }
13691
+ const originalBytes = new Uint8Array(
13692
+ originalBuf.buffer,
13693
+ originalBuf.byteOffset,
13694
+ originalBuf.byteLength
13695
+ );
13696
+ let patchResult;
13697
+ try {
13698
+ patchResult = await applyRedactToHwpx(originalBytes);
13699
+ } catch (e) {
13700
+ return `\uC624\uB958: \uBE44\uC2DD\uBCC4 \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. ${String(e)}`;
13701
+ }
13702
+ const { buffer: newBytes, findings: findings2, changed } = patchResult;
13703
+ const totalCount2 = findings2.reduce((s, f) => s + f.count, 0);
13704
+ if (!changed || totalCount2 === 0) {
13705
+ return `\uAC1C\uC778\uC815\uBCF4\uAC00 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC544 \uBCC0\uACBD\uD560 \uB0B4\uC6A9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13706
+ }
13707
+ const stagedPath2 = await stageFile(ctx.sessionId, outputPath, newBytes);
13708
+ const proposalId2 = crypto.randomUUID();
13709
+ const diff2 = buildDiff(findings2);
13710
+ const summaryText2 = input.summary ?? `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uCC98\uB9AC(\uB9C8\uC2A4\uD0B9): ${basename4(safePath)}`;
13711
+ return {
13712
+ proposal: {
13713
+ id: proposalId2,
13714
+ kind: "redact-pii",
13715
+ targetPath: outputPath,
13716
+ stagedPath: stagedPath2,
13717
+ summary: summaryText2,
13718
+ diff: diff2,
13719
+ warnings: [],
13720
+ willConvertFormat
13721
+ },
13722
+ commit: async () => {
13723
+ const backupPath = await backupFile(outputPath);
13724
+ await commitStaged(stagedPath2, outputPath);
13725
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
13726
+ return `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uC644\uB8CC: ${outputPath}${backupInfo}`;
13727
+ }
13728
+ };
13729
+ }
13730
+ let originalText;
13731
+ try {
13732
+ originalText = await readFile10(safePath, "utf-8");
13733
+ } catch {
13734
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13735
+ }
13736
+ const { text: redacted, findings } = redactText(originalText);
13737
+ const totalCount = findings.reduce((s, f) => s + f.count, 0);
13738
+ if (totalCount === 0) {
13739
+ return `\uAC1C\uC778\uC815\uBCF4\uAC00 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC544 \uBCC0\uACBD\uD560 \uB0B4\uC6A9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13740
+ }
13741
+ const stagedPath = await stageFile(ctx.sessionId, outputPath, redacted);
13742
+ const proposalId = crypto.randomUUID();
13743
+ const diff = buildDiff(findings);
13744
+ const summaryText = input.summary ?? `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uCC98\uB9AC(\uB9C8\uC2A4\uD0B9): ${basename4(safePath)}`;
13745
+ return {
13746
+ proposal: {
13747
+ id: proposalId,
13748
+ kind: "redact-pii",
13749
+ targetPath: outputPath,
13750
+ stagedPath,
13751
+ summary: summaryText,
13752
+ diff,
13753
+ warnings: [],
13754
+ willConvertFormat
13755
+ },
13756
+ commit: async () => {
13757
+ const backupPath = await backupFile(outputPath);
13758
+ await commitStaged(stagedPath, outputPath);
13759
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
13760
+ return `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uC644\uB8CC: ${outputPath}${backupInfo}`;
13761
+ }
13762
+ };
13763
+ }
13764
+ };
13765
+ var proposeSheetEditSchema = z11.object({
13766
+ path: z11.string().describe("\uC218\uC815\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13767
+ updates: z11.array(
13768
+ z11.object({
13769
+ sheet: z11.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13770
+ cell: z11.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
13771
+ value: z11.union([z11.string(), z11.number()]).describe("\uC0C8 \uAC12")
13369
13772
  })
13370
13773
  ).min(1).describe("\uC218\uC815\uD560 \uC140 \uBAA9\uB85D. read_document\uB85C \uC6D0\uBCF8 \uC2DC\uD2B8 \uAD6C\uC870\uB97C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694"),
13371
- summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13774
+ summary: z11.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13372
13775
  });
13373
13776
  var proposeSheetEditTool = {
13374
13777
  name: "propose_sheet_edit",
@@ -13380,13 +13783,13 @@ var proposeSheetEditTool = {
13380
13783
  ctx
13381
13784
  }) => {
13382
13785
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13383
- const ext = extname9(safePath).toLowerCase();
13786
+ const ext = extname11(safePath).toLowerCase();
13384
13787
  if (ext !== ".xlsx" && ext !== ".xls") {
13385
13788
  return `\uC624\uB958: propose_sheet_edit\uC740 .xlsx/.xls \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}.`;
13386
13789
  }
13387
13790
  let originalBuffer;
13388
13791
  try {
13389
- originalBuffer = await readFile9(safePath);
13792
+ originalBuffer = await readFile11(safePath);
13390
13793
  } catch {
13391
13794
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13392
13795
  }
@@ -13479,47 +13882,47 @@ function detectStructuralLoss(beforeBlocks, afterBlocks) {
13479
13882
  }
13480
13883
  return { lost: false, detail: "" };
13481
13884
  }
13482
- var insertRowOpSchema = z10.object({
13483
- type: z10.literal("insertRow"),
13484
- row: z10.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
13485
- position: z10.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
13885
+ var insertRowOpSchema = z12.object({
13886
+ type: z12.literal("insertRow"),
13887
+ row: z12.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
13888
+ position: z12.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
13486
13889
  });
13487
- var deleteRowOpSchema = z10.object({
13488
- type: z10.literal("deleteRow"),
13489
- row: z10.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
13890
+ var deleteRowOpSchema = z12.object({
13891
+ type: z12.literal("deleteRow"),
13892
+ row: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
13490
13893
  });
13491
- var insertColumnOpSchema = z10.object({
13492
- type: z10.literal("insertColumn"),
13493
- col: z10.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
13494
- position: z10.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
13894
+ var insertColumnOpSchema = z12.object({
13895
+ type: z12.literal("insertColumn"),
13896
+ col: z12.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
13897
+ position: z12.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
13495
13898
  });
13496
- var deleteColumnOpSchema = z10.object({
13497
- type: z10.literal("deleteColumn"),
13498
- col: z10.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
13899
+ var deleteColumnOpSchema = z12.object({
13900
+ type: z12.literal("deleteColumn"),
13901
+ col: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
13499
13902
  });
13500
- var mergeCellsOpSchema = z10.object({
13501
- type: z10.literal("mergeCells"),
13502
- startRow: z10.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
13503
- startCol: z10.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
13504
- endRow: z10.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
13505
- endCol: z10.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
13903
+ var mergeCellsOpSchema = z12.object({
13904
+ type: z12.literal("mergeCells"),
13905
+ startRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
13906
+ startCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
13907
+ endRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
13908
+ endCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
13506
13909
  });
13507
- var operationSchema = z10.discriminatedUnion("type", [
13910
+ var operationSchema = z12.discriminatedUnion("type", [
13508
13911
  insertRowOpSchema,
13509
13912
  deleteRowOpSchema,
13510
13913
  insertColumnOpSchema,
13511
13914
  deleteColumnOpSchema,
13512
13915
  mergeCellsOpSchema
13513
13916
  ]).describe("\uD45C \uAD6C\uC870 \uC5F0\uC0B0. \uB098\uC911 \uC5F0\uC0B0\uC758 row/col \uC778\uB371\uC2A4\uB294 \uC55E \uC5F0\uC0B0 \uC801\uC6A9 \uD6C4 \uC0C1\uD0DC\uB97C \uAE30\uC900\uC73C\uB85C \uD55C\uB2E4.");
13514
- var proposeTableStructureSchema = z10.object({
13515
- path: z10.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13516
- anchor: z10.string().min(1).describe(
13917
+ var proposeTableStructureSchema = z12.object({
13918
+ path: z12.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13919
+ anchor: z12.string().min(1).describe(
13517
13920
  "\uB300\uC0C1 \uD45C\uB97C \uC2DD\uBCC4\uD558\uB294 \uC575\uCEE4 \uD14D\uC2A4\uD2B8. \uD45C \uC548\uC5D0\uB9CC \uC788\uB294 \uB3C5\uD2B9\uD55C \uC140 \uD14D\uC2A4\uD2B8\uB97C \uC9C0\uC815\uD558\uC138\uC694. (\uBD80\uBD84 \uC77C\uCE58, \uACF5\uBC31 \uD2B8\uB9BC) \u2014 read_document\uB85C \uD655\uC778 \uD6C4 \uC0AC\uC6A9 \uAD8C\uC7A5."
13518
13921
  ),
13519
- operations: z10.array(operationSchema).min(1).describe(
13922
+ operations: z12.array(operationSchema).min(1).describe(
13520
13923
  "\uC801\uC6A9\uD560 \uD45C \uAD6C\uC870 \uC5F0\uC0B0 \uBAA9\uB85D (\uC21C\uC11C\uB300\uB85C \uC2E4\uD589). \uAC01 \uC5F0\uC0B0\uC740 \uC774\uC804 \uC5F0\uC0B0\uC774 \uC801\uC6A9\uB41C \uD6C4\uC758 \uD45C \uC0C1\uD0DC \uAE30\uC900\uC73C\uB85C row/col\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
13521
13924
  ),
13522
- summary: z10.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13925
+ summary: z12.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13523
13926
  });
13524
13927
  function tokenizeHwpxXml2(xml) {
13525
13928
  const tokens = [];
@@ -14076,7 +14479,7 @@ function getTblDims(tblXml) {
14076
14479
  return parseTblDimensions(tblXml, 0);
14077
14480
  }
14078
14481
  async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14079
- const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
14482
+ const zip = await import_jszip6.default.loadAsync(hwpxBuffer);
14080
14483
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
14081
14484
  let targetSectionIdx = -1;
14082
14485
  let targetRange = null;
@@ -14135,7 +14538,7 @@ async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14135
14538
  const afterDims = getTblDims(tblBlock);
14136
14539
  const newSectionXml = sectionXml.substring(0, targetRange.start) + tblBlock + sectionXml.substring(targetRange.end);
14137
14540
  sectionXmls[targetSectionIdx] = newSectionXml;
14138
- const out = new import_jszip4.default();
14541
+ const out = new import_jszip6.default();
14139
14542
  const mimetypeEntry = zip.file("mimetype");
14140
14543
  if (mimetypeEntry) {
14141
14544
  out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
@@ -14159,7 +14562,7 @@ async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14159
14562
  };
14160
14563
  }
14161
14564
  async function verifyOutputDims(newBytes, anchor, expectedRowCnt, expectedColCnt) {
14162
- const zip = await import_jszip4.default.loadAsync(newBytes);
14565
+ const zip = await import_jszip6.default.loadAsync(newBytes);
14163
14566
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
14164
14567
  for (const sf of sectionFiles) {
14165
14568
  const entry = zip.file(sf);
@@ -14194,7 +14597,7 @@ var proposeTableStructureTool = {
14194
14597
  ctx
14195
14598
  }) => {
14196
14599
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14197
- const ext = extname10(safePath).toLowerCase();
14600
+ const ext = extname12(safePath).toLowerCase();
14198
14601
  if (ext === ".hwp") {
14199
14602
  return "\uC624\uB958: propose_table_structure\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. .hwp(\uAD6C\uD615 OLE \uBC14\uC774\uB108\uB9AC)\uB294 XML \uC9C1\uC811 \uD3B8\uC9D1\uC774 \uBD88\uAC00\uD569\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 .hwpx'\uB85C \uC800\uC7A5\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
14200
14603
  }
@@ -14203,7 +14606,7 @@ var proposeTableStructureTool = {
14203
14606
  }
14204
14607
  let originalBuf;
14205
14608
  try {
14206
- originalBuf = await readFile10(safePath);
14609
+ originalBuf = await readFile12(safePath);
14207
14610
  } catch {
14208
14611
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
14209
14612
  }
@@ -14399,11 +14802,11 @@ function searchExcerpts(markdown, query) {
14399
14802
  }
14400
14803
  return result;
14401
14804
  }
14402
- var readDocumentSchema = z11.object({
14403
- path: z11.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14404
- pages: z11.string().optional().describe('\uC77D\uC744 \uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: "1-3", "1,3,5") \u2014 \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4'),
14405
- outline: z11.boolean().optional().describe("\uD5E4\uB529(\uC81C\uBAA9) \uAD6C\uC870\uB9CC \uBC18\uD658\uD574 \uBB38\uC11C \uAC1C\uC694 \uD30C\uC545 (\uB300\uD615 \uBB38\uC11C \uD0D0\uC0C9\uC6A9)"),
14406
- search: z11.string().optional().describe("\uD0A4\uC6CC\uB4DC\uAC00 \uD3EC\uD568\uB41C \uBD80\uBD84\uACFC \uC8FC\uBCC0 \uB9E5\uB77D\uB9CC \uBC18\uD658 (\uB300\uD615 \uBB38\uC11C\uC5D0\uC11C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uAE30)")
14805
+ var readDocumentSchema = z13.object({
14806
+ path: z13.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14807
+ pages: z13.string().optional().describe('\uC77D\uC744 \uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: "1-3", "1,3,5") \u2014 \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4'),
14808
+ outline: z13.boolean().optional().describe("\uD5E4\uB529(\uC81C\uBAA9) \uAD6C\uC870\uB9CC \uBC18\uD658\uD574 \uBB38\uC11C \uAC1C\uC694 \uD30C\uC545 (\uB300\uD615 \uBB38\uC11C \uD0D0\uC0C9\uC6A9)"),
14809
+ search: z13.string().optional().describe("\uD0A4\uC6CC\uB4DC\uAC00 \uD3EC\uD568\uB41C \uBD80\uBD84\uACFC \uC8FC\uBCC0 \uB9E5\uB77D\uB9CC \uBC18\uD658 (\uB300\uD615 \uBB38\uC11C\uC5D0\uC11C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uAE30)")
14407
14810
  });
14408
14811
  function applyReadMode(body, outline, search) {
14409
14812
  if (outline === true) {
@@ -14432,11 +14835,11 @@ var readDocumentTool = {
14432
14835
  const msg = e instanceof Error ? e.message : String(e);
14433
14836
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14434
14837
  }
14435
- const ext = extname11(safePath).toLowerCase();
14838
+ const ext = extname13(safePath).toLowerCase();
14436
14839
  if (PLAIN_TEXT_EXTS.has(ext)) {
14437
14840
  let raw;
14438
14841
  try {
14439
- raw = await readFile11(safePath, "utf-8");
14842
+ raw = await readFile13(safePath, "utf-8");
14440
14843
  } catch (e) {
14441
14844
  const msg = e instanceof Error ? e.message : String(e);
14442
14845
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -14536,8 +14939,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
14536
14939
  ".csv",
14537
14940
  ".log"
14538
14941
  ]);
14539
- var readFileSchema = z12.object({
14540
- path: z12.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
14942
+ var readFileSchema = z14.object({
14943
+ path: z14.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
14541
14944
  });
14542
14945
  var readFileTool = {
14543
14946
  name: "read_file",
@@ -14561,8 +14964,8 @@ var readFileTool = {
14561
14964
  if (info.size > MAX_SIZE) {
14562
14965
  return `\uC624\uB958: \uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${Math.round(info.size / 1024)}KB). \uCD5C\uB300 256KB\uAE4C\uC9C0 \uC77D\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`;
14563
14966
  }
14564
- const { extname: extname15 } = await import("path");
14565
- const ext = extname15(safePath).toLowerCase();
14967
+ const { extname: extname17 } = await import("path");
14968
+ const ext = extname17(safePath).toLowerCase();
14566
14969
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
14567
14970
  return `\uC624\uB958: '${ext}' \uD615\uC2DD\uC740 \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uB85C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C \uD30C\uC77C\uC774\uB77C\uBA74 read_document \uD234\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
14568
14971
  }
@@ -14577,8 +14980,8 @@ var readFileTool = {
14577
14980
  }
14578
14981
  };
14579
14982
  var PLAIN_TEXT_EXTS2 = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text"]);
14580
- var scanPiiSchema = z13.object({
14581
- path: z13.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
14983
+ var scanPiiSchema = z15.object({
14984
+ path: z15.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
14582
14985
  });
14583
14986
  var scanPiiTool = {
14584
14987
  name: "scan_pii",
@@ -14596,11 +14999,11 @@ var scanPiiTool = {
14596
14999
  const msg = e instanceof Error ? e.message : String(e);
14597
15000
  return `\uC624\uB958: \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14598
15001
  }
14599
- const ext = extname12(safePath).toLowerCase();
15002
+ const ext = extname14(safePath).toLowerCase();
14600
15003
  let text3;
14601
15004
  if (PLAIN_TEXT_EXTS2.has(ext)) {
14602
15005
  try {
14603
- text3 = await readFile12(safePath, "utf-8");
15006
+ text3 = await readFile14(safePath, "utf-8");
14604
15007
  } catch (e) {
14605
15008
  const msg = e instanceof Error ? e.message : String(e);
14606
15009
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -14631,9 +15034,9 @@ var scanPiiTool = {
14631
15034
  }
14632
15035
  };
14633
15036
  var MAX_PREVIEW_CHARS = 1e4;
14634
- var writeNewDocumentSchema = z14.object({
14635
- path: z14.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14636
- markdown: z14.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
15037
+ var writeNewDocumentSchema = z16.object({
15038
+ path: z16.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15039
+ markdown: z16.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
14637
15040
  });
14638
15041
  var writeNewDocumentTool = {
14639
15042
  name: "write_new_document",
@@ -14645,7 +15048,7 @@ var writeNewDocumentTool = {
14645
15048
  ctx
14646
15049
  }) => {
14647
15050
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14648
- const ext = extname13(safePath).toLowerCase();
15051
+ const ext = extname15(safePath).toLowerCase();
14649
15052
  try {
14650
15053
  await stat6(safePath);
14651
15054
  return `\uC624\uB958: \uD30C\uC77C\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${input.path}. \uAE30\uC874 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uB824\uBA74 propose_edit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
@@ -14695,12 +15098,12 @@ ${preview}`,
14695
15098
  };
14696
15099
  }
14697
15100
  };
14698
- var writeNewSpreadsheetSchema = z15.object({
14699
- path: z15.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14700
- sheets: z15.array(
14701
- z15.object({
14702
- name: z15.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
14703
- rows: z15.array(z15.array(z15.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
15101
+ var writeNewSpreadsheetSchema = z17.object({
15102
+ path: z17.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15103
+ sheets: z17.array(
15104
+ z17.object({
15105
+ name: z17.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
15106
+ rows: z17.array(z17.array(z17.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
14704
15107
  })
14705
15108
  ).min(1).describe("\uC0DD\uC131\uD560 \uC2DC\uD2B8 \uBAA9\uB85D")
14706
15109
  });
@@ -14714,7 +15117,7 @@ var writeNewSpreadsheetTool = {
14714
15117
  ctx
14715
15118
  }) => {
14716
15119
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14717
- const ext = extname14(safePath).toLowerCase();
15120
+ const ext = extname16(safePath).toLowerCase();
14718
15121
  if (ext !== ".xlsx") {
14719
15122
  return `\uC624\uB958: write_new_spreadsheet\uC740 .xlsx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD655\uC7A5\uC790: ${ext}.`;
14720
15123
  }
@@ -14775,10 +15178,12 @@ function createDocTools(_ctx) {
14775
15178
  listBackupsTool,
14776
15179
  readFileTool,
14777
15180
  scanPiiTool,
15181
+ findInDocumentTool,
14778
15182
  proposeEditTool,
14779
15183
  proposeFormFillTool,
14780
15184
  proposeCellEditTool,
14781
15185
  proposeFindReplaceTool,
15186
+ proposeRedactPiiTool,
14782
15187
  proposeSheetEditTool,
14783
15188
  proposeTableStructureTool,
14784
15189
  listFormObjectsTool,
@@ -15602,7 +16007,7 @@ async function runOnboarding() {
15602
16007
 
15603
16008
  // src/update.ts
15604
16009
  import { spawn } from "child_process";
15605
- import { mkdir as mkdir4, readFile as readFile13, writeFile as writeFile3 } from "fs/promises";
16010
+ import { mkdir as mkdir4, readFile as readFile15, writeFile as writeFile3 } from "fs/promises";
15606
16011
  import { dirname as dirname3 } from "path";
15607
16012
  function compareSemver(a, b) {
15608
16013
  const parse7 = (v) => {
@@ -15623,7 +16028,7 @@ function compareSemver(a, b) {
15623
16028
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
15624
16029
  async function readCache(cachePath) {
15625
16030
  try {
15626
- const raw = await readFile13(cachePath, "utf-8");
16031
+ const raw = await readFile15(cachePath, "utf-8");
15627
16032
  const parsed = JSON.parse(raw);
15628
16033
  if (parsed !== null && typeof parsed === "object" && "checkedAt" in parsed && "latest" in parsed && typeof parsed.checkedAt === "string" && typeof parsed.latest === "string") {
15629
16034
  return parsed;