@kodocagent/cli 0.4.5 → 0.6.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";
@@ -9935,7 +9944,7 @@ var DOCUMENT_RULES_SECTION = `## \uBB38\uC11C \uADDC\uCE59
9935
9944
  1. \uBB38\uC11C\uB97C \uC218\uC815\uD558\uAE30 \uC804\uC5D0 \uBC18\uB4DC\uC2DC \`read_document\` \uD234\uB85C \uB0B4\uC6A9\uC744 \uBA3C\uC800 \uC77D\uC73C\uC138\uC694.
9936
9945
  2. \uBAA8\uB4E0 \uD30C\uC77C \uC800\uC7A5\uC740 \`propose_*\` \uD234\uC744 \uD1B5\uD55C \uC2A4\uD14C\uC774\uC9D5\uACFC \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uAC70\uCCD0\uC57C \uD569\uB2C8\uB2E4.
9937
9946
  3. \uC2B9\uC778\uC744 \uBC1B\uAE30 \uC804\uC5D0\uB294 \uC808\uB300 "\uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4", "\uC644\uB8CC\uD588\uC2B5\uB2C8\uB2E4"\uB77C\uACE0 \uB9D0\uD558\uC9C0 \uB9C8\uC138\uC694.
9938
- 4. \`.hwp\` \uD30C\uC77C\uC744 \uD3B8\uC9D1\uD55C \uACB0\uACFC\uB294 \`.hwpx\` \uD615\uC2DD\uC73C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4. \uC774 \uBCC0\uD658 \uC0AC\uC2E4\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBBF8\uB9AC \uC548\uB0B4\uD558\uC138\uC694.
9947
+ 4. \`propose_edit\`\uB85C \`.hwp\`/\`.hwpx\`\uB97C \uD3B8\uC9D1\uD558\uBA74 \uC6D0\uBCF8 \uD615\uC2DD \uADF8\uB300\uB85C \uBB34\uC190\uC2E4 \uC800\uC7A5\uB429\uB2C8\uB2E4(.hwp\uB294 .hwp\uB85C \uC720\uC9C0). \uB2E8, \uD45C\xB7\uC140\xB7\uCC3E\uC544\uBC14\uAFB8\uAE30 \uB4F1 \uC77C\uBD80 \uAD6C\uC870 \uD3B8\uC9D1 \uD234\uC740 \`.hwpx\` \uAE30\uBC18\uC774\uBBC0\uB85C \`.hwp\` \uC785\uB825\uC774 \`.hwpx\`\uB85C \uC800\uC7A5\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD615\uC2DD \uBCC0\uD658\uC774 \uC548\uB0B4\uB418\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBBF8\uB9AC \uC54C\uB9AC\uC138\uC694.
9939
9948
  5. \uACBD\uB85C\uB294 \uD604\uC7AC \uC791\uC5C5 \uB514\uB809\uD130\uB9AC\uB97C \uAE30\uC900\uC73C\uB85C \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.
9940
9949
  6. \uC0AC\uC6A9\uC790\uAC00 \uC218\uC815\uC548\uC744 \uAC70\uC808\uD558\uBA74 \uAC19\uC740 \uC81C\uC548\uC744 \uC790\uB3D9\uC73C\uB85C \uBC18\uBCF5\uD558\uC9C0 \uB9D0\uACE0, \uC0AC\uC6A9\uC790\uC758 \uB2E4\uC74C \uC9C0\uC2DC\uB97C \uAE30\uB2E4\uB9AC\uC138\uC694.
9941
9950
  7. \uD070 \uBB38\uC11C\uB294 \uBA3C\uC800 \`outline\`\uC73C\uB85C \uAD6C\uC870\uB97C \uD30C\uC545\uD558\uACE0, \`search\`\uB098 \`pages\`\uB85C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uC5B4 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC544\uB07C\uC138\uC694.
@@ -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
- import { blocksToMarkdown, compare } from "@clazic/kordoc";
10934
+ import { blocksToMarkdown, compare } from "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, parse, patchHwp, patchHwpx } from "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 "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, parse as parse3, patchHwpx as patchHwpx2 } from "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";
10976
- import { z as z13 } from "zod";
10977
- import { stat as stat6 } from "fs/promises";
10982
+ import { parse as parse4 } from "kordoc";
10983
+ import { z as z12 } from "zod";
10984
+ import { readFile as readFile13, stat as stat4 } from "fs/promises";
10978
10985
  import { extname as extname13 } from "path";
10979
- import { markdownToHwpx as markdownToHwpx3 } from "@clazic/kordoc";
10986
+ import { parse as parse5 } from "kordoc";
10987
+ import { z as z13 } from "zod";
10988
+ import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
10980
10989
  import { z as z14 } from "zod";
10981
- import { stat as stat7 } from "fs/promises";
10990
+ import { readFile as readFile14 } from "fs/promises";
10982
10991
  import { extname as extname14 } from "path";
10983
- import ExcelJS2 from "exceljs";
10992
+ import { parse as parse6 } from "kordoc";
10984
10993
  import { z as z15 } from "zod";
10994
+ import { stat as stat6 } from "fs/promises";
10995
+ import { extname as extname15 } from "path";
10996
+ import { markdownToHwpx } from "kordoc";
10997
+ import { z as z16 } from "zod";
10998
+ import { stat as stat7 } from "fs/promises";
10999
+ import { extname as extname16 } from "path";
11000
+ import ExcelJS2 from "exceljs";
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;
11970
- }
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;
11979
- }
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;
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--;
11986
11890
  }
11987
- target = byIndex;
11891
+ }
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;
11907
+ }
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
+ }
11926
+ }
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
- });
12250
- }
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
- });
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.)";
12263
12217
  }
12264
- startPos = re.lastIndex;
12265
- m = re.exec(xml);
12266
- }
12267
- void startPos;
12268
- return tokens;
12269
- }
12270
- function escapeXml2(text3) {
12271
- return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12272
- }
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
- }
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.`;
12290
12223
  }
12291
- }
12292
- return tcRanges.filter((tc) => tc.depth === 0);
12293
- }
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
- }
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.";
12307
12226
  }
12308
- }
12309
- return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
12310
- }
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 };
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("");
12328
12242
  }
12329
12243
  }
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);
12330
12258
  }
12331
- return null;
12259
+ };
12260
+ function escapeXml2(text3) {
12261
+ return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
12332
12262
  }
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 };
12263
+ function decodeXml(text3) {
12264
+ return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
12265
+ }
12266
+ function getAttr(openTag, attr) {
12267
+ const re = new RegExp(`\\b${attr}="([^"]*)"`, "");
12268
+ const m = re.exec(openTag);
12269
+ return m ? decodeXml(m[1] ?? "") : "";
12270
+ }
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 });
12342
12286
  }
12343
12287
  }
12344
- if (!addr) return null;
12345
- return { ...addr, ...span };
12346
- }
12347
- function applyCellEditsToSectionXml(xml, edits) {
12348
- const tokens = tokenizeHwpxXml(xml);
12349
- 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--;
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;
12359
12338
  }
12360
12339
  }
12340
+ results.push({
12341
+ index: idx++,
12342
+ name,
12343
+ type,
12344
+ currentValue,
12345
+ comboItems,
12346
+ sectionFile,
12347
+ posInSection: pos
12348
+ });
12361
12349
  }
12362
- const replacements = [];
12350
+ return results;
12351
+ }
12352
+ function applyFormObjectEdits(xml, edits) {
12353
+ const results = edits.map(() => ({ success: false }));
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.";
12535
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);
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")}`;
12823
+ }
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" });
12733
12828
  }
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} |`);
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
  }
@@ -12915,15 +13118,21 @@ var proposeEditTool = {
12915
13118
  originalMarkdown = originalResult.markdown;
12916
13119
  }
12917
13120
  if (ext === ".hwpx" || ext === ".hwp") {
12918
- const kordocWarnings = [];
12919
- const hwpxBuffer = await markdownToHwpx(input.newMarkdown, {
12920
- templateArrayBuffer: originalBuffer.buffer,
12921
- warnings: kordocWarnings
12922
- });
12923
- if (kordocWarnings.length > 0) {
12924
- warnings.push(...kordocWarnings.map((w) => `kordoc \uACBD\uACE0: ${w}`));
13121
+ const origU8 = new Uint8Array(
13122
+ originalBuffer.buffer,
13123
+ originalBuffer.byteOffset,
13124
+ originalBuffer.byteLength
13125
+ );
13126
+ const patchResult = ext === ".hwp" ? await patchHwp(origU8, input.newMarkdown) : await patchHwpx(origU8, input.newMarkdown);
13127
+ if (!patchResult.success || !patchResult.data) {
13128
+ return `\uC624\uB958: \uD3B8\uC9D1\uC744 \uC801\uC6A9\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${patchResult.error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}. read_document\uB85C \uC6D0\uBCF8\uC744 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.`;
13129
+ }
13130
+ stagedData = patchResult.data;
13131
+ for (const s of patchResult.skipped) {
13132
+ warnings.push(
13133
+ `\uC77C\uBD80 \uBCC0\uACBD\uC774 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4(${s.reason ?? "\uC0AC\uC720 \uBBF8\uC0C1"}). \uD45C \uAD6C\uC870 \uBCC0\uACBD\uC740 propose_table_structure, \uC140 \uAC12\uC740 propose_cell_edit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`
13134
+ );
12925
13135
  }
12926
- stagedData = new Uint8Array(hwpxBuffer);
12927
13136
  } else if (ext === ".docx") {
12928
13137
  warnings.push("DOCX \uC7AC\uC0DD\uC131: \uBCF5\uC7A1\uD55C \uC11C\uC2DD(\uBA38\uB9AC\uAE00/\uAC01\uC8FC/\uC2A4\uD0C0\uC77C)\uC740 \uC190\uC2E4\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
12929
13138
  const docxBuffer = await markdownToDocx(input.newMarkdown);
@@ -12933,7 +13142,8 @@ var proposeEditTool = {
12933
13142
  } else {
12934
13143
  return `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4: ${ext}. .hwp, .hwpx, .docx, .md, .txt\uB9CC \uC218\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`;
12935
13144
  }
12936
- const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13145
+ const outputPath = ext === ".hwpx" || ext === ".hwp" ? safePath : resolveOutputPath(safePath).outputPath;
13146
+ const willConvertFormat = ext === ".hwpx" || ext === ".hwp" ? void 0 : resolveOutputPath(safePath).willConvertFormat;
12937
13147
  const stagedPath = await stageFile(ctx.sessionId, safePath, stagedData);
12938
13148
  let diff = markdownDiff(originalMarkdown, input.newMarkdown, safePath);
12939
13149
  if (ext === ".hwpx" || ext === ".hwp") {
@@ -12974,19 +13184,19 @@ ${diff}`;
12974
13184
  };
12975
13185
  }
12976
13186
  };
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)")
13187
+ var proposeFindReplaceSchema = z8.object({
13188
+ 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)"),
13189
+ find: z8.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
13190
+ replace: z8.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
13191
+ caseSensitive: z8.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
13192
+ 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"),
13193
+ summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12984
13194
  });
12985
13195
  var MAX_DIFF_SAMPLES = 20;
12986
13196
  function escapeXml3(text3) {
12987
13197
  return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12988
13198
  }
12989
- function unescapeXml(text3) {
13199
+ function unescapeXml2(text3) {
12990
13200
  return text3.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
12991
13201
  }
12992
13202
  function makeChangeSnippet(before, after, ctx = 24) {
@@ -13010,7 +13220,7 @@ function collectChangedSnippets(beforeXml, afterXml, maxSamples) {
13010
13220
  const n = Math.min(beforeNodes.length, afterNodes.length);
13011
13221
  for (let i = 0; i < n && out.length < maxSamples; i++) {
13012
13222
  if (beforeNodes[i] !== afterNodes[i]) {
13013
- const snip = makeChangeSnippet(unescapeXml(beforeNodes[i]), unescapeXml(afterNodes[i]));
13223
+ const snip = makeChangeSnippet(unescapeXml2(beforeNodes[i]), unescapeXml2(afterNodes[i]));
13014
13224
  out.push(snip);
13015
13225
  }
13016
13226
  }
@@ -13093,7 +13303,7 @@ function countOccurrences(str, sub) {
13093
13303
  return count;
13094
13304
  }
13095
13305
  async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive, replaceAll) {
13096
- const zip = await import_jszip3.default.loadAsync(hwpxBuffer);
13306
+ const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
13097
13307
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13098
13308
  const sectionXmls = [];
13099
13309
  for (const sf of sectionFiles) {
@@ -13137,7 +13347,7 @@ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive,
13137
13347
  samples.push(snip);
13138
13348
  }
13139
13349
  }
13140
- const out = new import_jszip3.default();
13350
+ const out = new import_jszip4.default();
13141
13351
  const mimetypeEntry = zip.file("mimetype");
13142
13352
  if (mimetypeEntry) {
13143
13353
  out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
@@ -13168,7 +13378,7 @@ var proposeFindReplaceTool = {
13168
13378
  ctx
13169
13379
  }) => {
13170
13380
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13171
- const ext = extname7(safePath).toLowerCase();
13381
+ const ext = extname8(safePath).toLowerCase();
13172
13382
  if (ext === ".hwp") {
13173
13383
  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
13384
  }
@@ -13177,7 +13387,7 @@ var proposeFindReplaceTool = {
13177
13387
  }
13178
13388
  let originalBuf;
13179
13389
  try {
13180
- originalBuf = await readFile7(safePath);
13390
+ originalBuf = await readFile8(safePath);
13181
13391
  } catch {
13182
13392
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13183
13393
  }
@@ -13270,10 +13480,10 @@ var proposeFindReplaceTool = {
13270
13480
  };
13271
13481
  }
13272
13482
  };
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)")
13483
+ var proposeFormFillSchema = z9.object({
13484
+ path: z9.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13485
+ 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"),
13486
+ summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13277
13487
  });
13278
13488
  var proposeFormFillTool = {
13279
13489
  name: "propose_form_fill",
@@ -13285,13 +13495,13 @@ var proposeFormFillTool = {
13285
13495
  ctx
13286
13496
  }) => {
13287
13497
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13288
- const ext = extname8(safePath).toLowerCase();
13498
+ const ext = extname9(safePath).toLowerCase();
13289
13499
  if (ext !== ".hwpx" && ext !== ".hwp") {
13290
13500
  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
13501
  }
13292
13502
  let originalBuffer;
13293
13503
  try {
13294
- originalBuffer = await readFile8(safePath);
13504
+ originalBuffer = await readFile9(safePath);
13295
13505
  } catch {
13296
13506
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13297
13507
  }
@@ -13327,15 +13537,19 @@ var proposeFormFillTool = {
13327
13537
  }
13328
13538
  }
13329
13539
  const diff = diffLines.join("\n");
13330
- const kordocWarnings = [];
13331
- const hwpxBuffer = await markdownToHwpx2(newMarkdown, {
13332
- templateArrayBuffer: originalBuffer.buffer,
13333
- warnings: kordocWarnings
13334
- });
13335
- if (kordocWarnings.length > 0) {
13336
- warnings.push(...kordocWarnings.map((w) => `kordoc \uACBD\uACE0: ${w}`));
13540
+ const origU8 = new Uint8Array(
13541
+ originalBuffer.buffer,
13542
+ originalBuffer.byteOffset,
13543
+ originalBuffer.byteLength
13544
+ );
13545
+ const patchResult = await patchHwpx2(origU8, newMarkdown);
13546
+ if (!patchResult.success || !patchResult.data) {
13547
+ return `\uC624\uB958: \uC591\uC2DD \uCC44\uC6B0\uAE30\uB97C \uC801\uC6A9\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${patchResult.error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}.`;
13548
+ }
13549
+ const stagedData = patchResult.data;
13550
+ for (const s of patchResult.skipped) {
13551
+ warnings.push(`\uC77C\uBD80 \uD56D\uBAA9\uC774 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4(${s.reason ?? "\uC0AC\uC720 \uBBF8\uC0C1"}).`);
13337
13552
  }
13338
- const stagedData = new Uint8Array(hwpxBuffer);
13339
13553
  const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13340
13554
  const stagedPath = await stageFile(ctx.sessionId, safePath, stagedData);
13341
13555
  const proposalId = crypto.randomUUID();
@@ -13359,16 +13573,216 @@ var proposeFormFillTool = {
13359
13573
  };
13360
13574
  }
13361
13575
  };
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")
13576
+ var proposeRedactPiiSchema = z10.object({
13577
+ path: z10.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD560 \uBB38\uC11C \uACBD\uB85C"),
13578
+ summary: z10.string().optional().describe("\uBCC0\uACBD \uC694\uC57D")
13579
+ });
13580
+ function mergeFindings(allFindings) {
13581
+ const map = /* @__PURE__ */ new Map();
13582
+ for (const findings of allFindings) {
13583
+ for (const f of findings) {
13584
+ const existing = map.get(f.type);
13585
+ if (existing) {
13586
+ existing.count += f.count;
13587
+ for (const m of f.masked) {
13588
+ if (!existing.masked.includes(m) && existing.masked.length < 5) {
13589
+ existing.masked.push(m);
13590
+ }
13591
+ }
13592
+ } else {
13593
+ map.set(f.type, { type: f.type, count: f.count, masked: [...f.masked] });
13594
+ }
13595
+ }
13596
+ }
13597
+ return [...map.values()];
13598
+ }
13599
+ async function applyRedactToHwpx(hwpxBuffer) {
13600
+ const zip = await import_jszip5.default.loadAsync(hwpxBuffer);
13601
+ const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13602
+ const sectionXmls = [];
13603
+ for (const sf of sectionFiles) {
13604
+ const entry = zip.file(sf);
13605
+ const xml = entry ? await entry.async("string") : "";
13606
+ sectionXmls.push(xml);
13607
+ }
13608
+ const newSectionXmls = [];
13609
+ const allFindings = [];
13610
+ let anyChanged = false;
13611
+ for (const srcXml of sectionXmls) {
13612
+ const tNodeRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
13613
+ const sectionFindings = [];
13614
+ let offset = 0;
13615
+ let result = srcXml;
13616
+ let m = tNodeRe.exec(srcXml);
13617
+ while (m !== null) {
13618
+ const content = m[1];
13619
+ if (content.length === 0) {
13620
+ m = tNodeRe.exec(srcXml);
13621
+ continue;
13622
+ }
13623
+ const { text: redacted, findings } = redactText(content);
13624
+ if (redacted !== content) {
13625
+ const openTagLen = "<hp:t>".length;
13626
+ const contentStart = m.index + offset + openTagLen;
13627
+ const contentEnd = contentStart + content.length;
13628
+ result = result.substring(0, contentStart) + redacted + result.substring(contentEnd);
13629
+ offset += redacted.length - content.length;
13630
+ anyChanged = true;
13631
+ }
13632
+ if (findings.length > 0) {
13633
+ sectionFindings.push(findings);
13634
+ }
13635
+ m = tNodeRe.exec(srcXml);
13636
+ }
13637
+ newSectionXmls.push(result);
13638
+ allFindings.push(...sectionFindings);
13639
+ }
13640
+ if (!anyChanged) {
13641
+ return { buffer: hwpxBuffer, findings: mergeFindings(allFindings), changed: false };
13642
+ }
13643
+ const out = new import_jszip5.default();
13644
+ const mimetypeEntry = zip.file("mimetype");
13645
+ if (mimetypeEntry) {
13646
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
13647
+ }
13648
+ for (const [name, entry] of Object.entries(zip.files)) {
13649
+ if (name === "mimetype" || entry.dir) continue;
13650
+ const sectionIdx = sectionFiles.indexOf(name);
13651
+ if (sectionIdx >= 0) {
13652
+ out.file(name, newSectionXmls[sectionIdx] ?? "");
13653
+ } else {
13654
+ out.file(name, await entry.async("uint8array"));
13655
+ }
13656
+ }
13657
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
13658
+ return {
13659
+ buffer: new Uint8Array(buf),
13660
+ findings: mergeFindings(allFindings),
13661
+ changed: true
13662
+ };
13663
+ }
13664
+ function buildDiff(findings) {
13665
+ const total = findings.reduce((s, f) => s + f.count, 0);
13666
+ const lines = [`\uAC1C\uC778\uC815\uBCF4 ${total}\uAC74 \uBE44\uC2DD\uBCC4 \uCC98\uB9AC`];
13667
+ for (const f of findings) {
13668
+ const examples = f.masked.slice(0, 3).join(", ");
13669
+ lines.push(`- ${f.type}: ${f.count}\uAC74 \u2192 ${examples}`);
13670
+ }
13671
+ return lines.join("\n");
13672
+ }
13673
+ var proposeRedactPiiTool = {
13674
+ name: "propose_redact_pii",
13675
+ 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.",
13676
+ inputSchema: proposeRedactPiiSchema,
13677
+ requiresApproval: true,
13678
+ propose: async ({
13679
+ input,
13680
+ ctx
13681
+ }) => {
13682
+ const safePath = await resolveSafePath(ctx.cwd, input.path);
13683
+ const ext = extname10(safePath).toLowerCase();
13684
+ if (ext === ".hwp") {
13685
+ 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.";
13686
+ }
13687
+ if (ext !== ".hwpx" && ext !== ".md" && ext !== ".txt") {
13688
+ const hint = ext === ".docx" || ext === ".xlsx" ? " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4." : " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.";
13689
+ 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}`;
13690
+ }
13691
+ const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13692
+ if (ext === ".hwpx") {
13693
+ let originalBuf;
13694
+ try {
13695
+ originalBuf = await readFile10(safePath);
13696
+ } catch {
13697
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13698
+ }
13699
+ if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
13700
+ 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.";
13701
+ }
13702
+ const originalBytes = new Uint8Array(
13703
+ originalBuf.buffer,
13704
+ originalBuf.byteOffset,
13705
+ originalBuf.byteLength
13706
+ );
13707
+ let patchResult;
13708
+ try {
13709
+ patchResult = await applyRedactToHwpx(originalBytes);
13710
+ } catch (e) {
13711
+ return `\uC624\uB958: \uBE44\uC2DD\uBCC4 \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. ${String(e)}`;
13712
+ }
13713
+ const { buffer: newBytes, findings: findings2, changed } = patchResult;
13714
+ const totalCount2 = findings2.reduce((s, f) => s + f.count, 0);
13715
+ if (!changed || totalCount2 === 0) {
13716
+ return `\uAC1C\uC778\uC815\uBCF4\uAC00 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC544 \uBCC0\uACBD\uD560 \uB0B4\uC6A9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13717
+ }
13718
+ const stagedPath2 = await stageFile(ctx.sessionId, outputPath, newBytes);
13719
+ const proposalId2 = crypto.randomUUID();
13720
+ const diff2 = buildDiff(findings2);
13721
+ const summaryText2 = input.summary ?? `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uCC98\uB9AC(\uB9C8\uC2A4\uD0B9): ${basename4(safePath)}`;
13722
+ return {
13723
+ proposal: {
13724
+ id: proposalId2,
13725
+ kind: "redact-pii",
13726
+ targetPath: outputPath,
13727
+ stagedPath: stagedPath2,
13728
+ summary: summaryText2,
13729
+ diff: diff2,
13730
+ warnings: [],
13731
+ willConvertFormat
13732
+ },
13733
+ commit: async () => {
13734
+ const backupPath = await backupFile(outputPath);
13735
+ await commitStaged(stagedPath2, outputPath);
13736
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
13737
+ return `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uC644\uB8CC: ${outputPath}${backupInfo}`;
13738
+ }
13739
+ };
13740
+ }
13741
+ let originalText;
13742
+ try {
13743
+ originalText = await readFile10(safePath, "utf-8");
13744
+ } catch {
13745
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13746
+ }
13747
+ const { text: redacted, findings } = redactText(originalText);
13748
+ const totalCount = findings.reduce((s, f) => s + f.count, 0);
13749
+ if (totalCount === 0) {
13750
+ return `\uAC1C\uC778\uC815\uBCF4\uAC00 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC544 \uBCC0\uACBD\uD560 \uB0B4\uC6A9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13751
+ }
13752
+ const stagedPath = await stageFile(ctx.sessionId, outputPath, redacted);
13753
+ const proposalId = crypto.randomUUID();
13754
+ const diff = buildDiff(findings);
13755
+ const summaryText = input.summary ?? `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uCC98\uB9AC(\uB9C8\uC2A4\uD0B9): ${basename4(safePath)}`;
13756
+ return {
13757
+ proposal: {
13758
+ id: proposalId,
13759
+ kind: "redact-pii",
13760
+ targetPath: outputPath,
13761
+ stagedPath,
13762
+ summary: summaryText,
13763
+ diff,
13764
+ warnings: [],
13765
+ willConvertFormat
13766
+ },
13767
+ commit: async () => {
13768
+ const backupPath = await backupFile(outputPath);
13769
+ await commitStaged(stagedPath, outputPath);
13770
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
13771
+ return `\uAC1C\uC778\uC815\uBCF4 \uBE44\uC2DD\uBCC4 \uC644\uB8CC: ${outputPath}${backupInfo}`;
13772
+ }
13773
+ };
13774
+ }
13775
+ };
13776
+ var proposeSheetEditSchema = z11.object({
13777
+ 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)"),
13778
+ updates: z11.array(
13779
+ z11.object({
13780
+ sheet: z11.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13781
+ cell: z11.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
13782
+ value: z11.union([z11.string(), z11.number()]).describe("\uC0C8 \uAC12")
13369
13783
  })
13370
13784
  ).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)")
13785
+ summary: z11.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13372
13786
  });
13373
13787
  var proposeSheetEditTool = {
13374
13788
  name: "propose_sheet_edit",
@@ -13380,13 +13794,13 @@ var proposeSheetEditTool = {
13380
13794
  ctx
13381
13795
  }) => {
13382
13796
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13383
- const ext = extname9(safePath).toLowerCase();
13797
+ const ext = extname11(safePath).toLowerCase();
13384
13798
  if (ext !== ".xlsx" && ext !== ".xls") {
13385
13799
  return `\uC624\uB958: propose_sheet_edit\uC740 .xlsx/.xls \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}.`;
13386
13800
  }
13387
13801
  let originalBuffer;
13388
13802
  try {
13389
- originalBuffer = await readFile9(safePath);
13803
+ originalBuffer = await readFile11(safePath);
13390
13804
  } catch {
13391
13805
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13392
13806
  }
@@ -13479,47 +13893,47 @@ function detectStructuralLoss(beforeBlocks, afterBlocks) {
13479
13893
  }
13480
13894
  return { lost: false, detail: "" };
13481
13895
  }
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")
13896
+ var insertRowOpSchema = z12.object({
13897
+ type: z12.literal("insertRow"),
13898
+ row: z12.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
13899
+ position: z12.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
13486
13900
  });
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)")
13901
+ var deleteRowOpSchema = z12.object({
13902
+ type: z12.literal("deleteRow"),
13903
+ row: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
13490
13904
  });
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")
13905
+ var insertColumnOpSchema = z12.object({
13906
+ type: z12.literal("insertColumn"),
13907
+ col: z12.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
13908
+ position: z12.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
13495
13909
  });
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)")
13910
+ var deleteColumnOpSchema = z12.object({
13911
+ type: z12.literal("deleteColumn"),
13912
+ col: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
13499
13913
  });
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)")
13914
+ var mergeCellsOpSchema = z12.object({
13915
+ type: z12.literal("mergeCells"),
13916
+ startRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
13917
+ startCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
13918
+ endRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
13919
+ endCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
13506
13920
  });
13507
- var operationSchema = z10.discriminatedUnion("type", [
13921
+ var operationSchema = z12.discriminatedUnion("type", [
13508
13922
  insertRowOpSchema,
13509
13923
  deleteRowOpSchema,
13510
13924
  insertColumnOpSchema,
13511
13925
  deleteColumnOpSchema,
13512
13926
  mergeCellsOpSchema
13513
13927
  ]).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(
13928
+ var proposeTableStructureSchema = z12.object({
13929
+ 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)"),
13930
+ anchor: z12.string().min(1).describe(
13517
13931
  "\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
13932
  ),
13519
- operations: z10.array(operationSchema).min(1).describe(
13933
+ operations: z12.array(operationSchema).min(1).describe(
13520
13934
  "\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
13935
  ),
13522
- summary: z10.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13936
+ summary: z12.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13523
13937
  });
13524
13938
  function tokenizeHwpxXml2(xml) {
13525
13939
  const tokens = [];
@@ -14076,7 +14490,7 @@ function getTblDims(tblXml) {
14076
14490
  return parseTblDimensions(tblXml, 0);
14077
14491
  }
14078
14492
  async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14079
- const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
14493
+ const zip = await import_jszip6.default.loadAsync(hwpxBuffer);
14080
14494
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
14081
14495
  let targetSectionIdx = -1;
14082
14496
  let targetRange = null;
@@ -14135,7 +14549,7 @@ async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14135
14549
  const afterDims = getTblDims(tblBlock);
14136
14550
  const newSectionXml = sectionXml.substring(0, targetRange.start) + tblBlock + sectionXml.substring(targetRange.end);
14137
14551
  sectionXmls[targetSectionIdx] = newSectionXml;
14138
- const out = new import_jszip4.default();
14552
+ const out = new import_jszip6.default();
14139
14553
  const mimetypeEntry = zip.file("mimetype");
14140
14554
  if (mimetypeEntry) {
14141
14555
  out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
@@ -14159,7 +14573,7 @@ async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14159
14573
  };
14160
14574
  }
14161
14575
  async function verifyOutputDims(newBytes, anchor, expectedRowCnt, expectedColCnt) {
14162
- const zip = await import_jszip4.default.loadAsync(newBytes);
14576
+ const zip = await import_jszip6.default.loadAsync(newBytes);
14163
14577
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
14164
14578
  for (const sf of sectionFiles) {
14165
14579
  const entry = zip.file(sf);
@@ -14194,7 +14608,7 @@ var proposeTableStructureTool = {
14194
14608
  ctx
14195
14609
  }) => {
14196
14610
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14197
- const ext = extname10(safePath).toLowerCase();
14611
+ const ext = extname12(safePath).toLowerCase();
14198
14612
  if (ext === ".hwp") {
14199
14613
  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
14614
  }
@@ -14203,7 +14617,7 @@ var proposeTableStructureTool = {
14203
14617
  }
14204
14618
  let originalBuf;
14205
14619
  try {
14206
- originalBuf = await readFile10(safePath);
14620
+ originalBuf = await readFile12(safePath);
14207
14621
  } catch {
14208
14622
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
14209
14623
  }
@@ -14399,11 +14813,11 @@ function searchExcerpts(markdown, query) {
14399
14813
  }
14400
14814
  return result;
14401
14815
  }
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)")
14816
+ var readDocumentSchema = z13.object({
14817
+ path: z13.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14818
+ 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'),
14819
+ 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)"),
14820
+ 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
14821
  });
14408
14822
  function applyReadMode(body, outline, search) {
14409
14823
  if (outline === true) {
@@ -14432,11 +14846,11 @@ var readDocumentTool = {
14432
14846
  const msg = e instanceof Error ? e.message : String(e);
14433
14847
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14434
14848
  }
14435
- const ext = extname11(safePath).toLowerCase();
14849
+ const ext = extname13(safePath).toLowerCase();
14436
14850
  if (PLAIN_TEXT_EXTS.has(ext)) {
14437
14851
  let raw;
14438
14852
  try {
14439
- raw = await readFile11(safePath, "utf-8");
14853
+ raw = await readFile13(safePath, "utf-8");
14440
14854
  } catch (e) {
14441
14855
  const msg = e instanceof Error ? e.message : String(e);
14442
14856
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -14536,8 +14950,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
14536
14950
  ".csv",
14537
14951
  ".log"
14538
14952
  ]);
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)")
14953
+ var readFileSchema = z14.object({
14954
+ path: z14.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
14541
14955
  });
14542
14956
  var readFileTool = {
14543
14957
  name: "read_file",
@@ -14561,8 +14975,8 @@ var readFileTool = {
14561
14975
  if (info.size > MAX_SIZE) {
14562
14976
  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
14977
  }
14564
- const { extname: extname15 } = await import("path");
14565
- const ext = extname15(safePath).toLowerCase();
14978
+ const { extname: extname17 } = await import("path");
14979
+ const ext = extname17(safePath).toLowerCase();
14566
14980
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
14567
14981
  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
14982
  }
@@ -14577,8 +14991,8 @@ var readFileTool = {
14577
14991
  }
14578
14992
  };
14579
14993
  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")
14994
+ var scanPiiSchema = z15.object({
14995
+ path: z15.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
14582
14996
  });
14583
14997
  var scanPiiTool = {
14584
14998
  name: "scan_pii",
@@ -14596,11 +15010,11 @@ var scanPiiTool = {
14596
15010
  const msg = e instanceof Error ? e.message : String(e);
14597
15011
  return `\uC624\uB958: \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14598
15012
  }
14599
- const ext = extname12(safePath).toLowerCase();
15013
+ const ext = extname14(safePath).toLowerCase();
14600
15014
  let text3;
14601
15015
  if (PLAIN_TEXT_EXTS2.has(ext)) {
14602
15016
  try {
14603
- text3 = await readFile12(safePath, "utf-8");
15017
+ text3 = await readFile14(safePath, "utf-8");
14604
15018
  } catch (e) {
14605
15019
  const msg = e instanceof Error ? e.message : String(e);
14606
15020
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -14631,9 +15045,9 @@ var scanPiiTool = {
14631
15045
  }
14632
15046
  };
14633
15047
  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)")
15048
+ var writeNewDocumentSchema = z16.object({
15049
+ path: z16.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15050
+ markdown: z16.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
14637
15051
  });
14638
15052
  var writeNewDocumentTool = {
14639
15053
  name: "write_new_document",
@@ -14645,7 +15059,7 @@ var writeNewDocumentTool = {
14645
15059
  ctx
14646
15060
  }) => {
14647
15061
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14648
- const ext = extname13(safePath).toLowerCase();
15062
+ const ext = extname15(safePath).toLowerCase();
14649
15063
  try {
14650
15064
  await stat6(safePath);
14651
15065
  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.`;
@@ -14654,13 +15068,7 @@ var writeNewDocumentTool = {
14654
15068
  const warnings = [];
14655
15069
  let stagedData;
14656
15070
  if (ext === ".hwpx") {
14657
- const kordocWarnings = [];
14658
- const hwpxBuffer = await markdownToHwpx3(input.markdown, {
14659
- warnings: kordocWarnings
14660
- });
14661
- if (kordocWarnings.length > 0) {
14662
- warnings.push(...kordocWarnings.map((w) => `kordoc \uACBD\uACE0: ${w}`));
14663
- }
15071
+ const hwpxBuffer = await markdownToHwpx(input.markdown);
14664
15072
  stagedData = new Uint8Array(hwpxBuffer);
14665
15073
  } else if (ext === ".docx") {
14666
15074
  warnings.push("DOCX \uC0DD\uC131: \uBCF5\uC7A1\uD55C \uC11C\uC2DD(\uBA38\uB9AC\uAE00/\uAC01\uC8FC/\uC2A4\uD0C0\uC77C)\uC740 \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
@@ -14695,12 +15103,12 @@ ${preview}`,
14695
15103
  };
14696
15104
  }
14697
15105
  };
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)")
15106
+ var writeNewSpreadsheetSchema = z17.object({
15107
+ 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)"),
15108
+ sheets: z17.array(
15109
+ z17.object({
15110
+ name: z17.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
15111
+ rows: z17.array(z17.array(z17.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
14704
15112
  })
14705
15113
  ).min(1).describe("\uC0DD\uC131\uD560 \uC2DC\uD2B8 \uBAA9\uB85D")
14706
15114
  });
@@ -14714,7 +15122,7 @@ var writeNewSpreadsheetTool = {
14714
15122
  ctx
14715
15123
  }) => {
14716
15124
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14717
- const ext = extname14(safePath).toLowerCase();
15125
+ const ext = extname16(safePath).toLowerCase();
14718
15126
  if (ext !== ".xlsx") {
14719
15127
  return `\uC624\uB958: write_new_spreadsheet\uC740 .xlsx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD655\uC7A5\uC790: ${ext}.`;
14720
15128
  }
@@ -14775,10 +15183,12 @@ function createDocTools(_ctx) {
14775
15183
  listBackupsTool,
14776
15184
  readFileTool,
14777
15185
  scanPiiTool,
15186
+ findInDocumentTool,
14778
15187
  proposeEditTool,
14779
15188
  proposeFormFillTool,
14780
15189
  proposeCellEditTool,
14781
15190
  proposeFindReplaceTool,
15191
+ proposeRedactPiiTool,
14782
15192
  proposeSheetEditTool,
14783
15193
  proposeTableStructureTool,
14784
15194
  listFormObjectsTool,
@@ -15602,7 +16012,7 @@ async function runOnboarding() {
15602
16012
 
15603
16013
  // src/update.ts
15604
16014
  import { spawn } from "child_process";
15605
- import { mkdir as mkdir4, readFile as readFile13, writeFile as writeFile3 } from "fs/promises";
16015
+ import { mkdir as mkdir4, readFile as readFile15, writeFile as writeFile3 } from "fs/promises";
15606
16016
  import { dirname as dirname3 } from "path";
15607
16017
  function compareSemver(a, b) {
15608
16018
  const parse7 = (v) => {
@@ -15623,7 +16033,7 @@ function compareSemver(a, b) {
15623
16033
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
15624
16034
  async function readCache(cachePath) {
15625
16035
  try {
15626
- const raw = await readFile13(cachePath, "utf-8");
16036
+ const raw = await readFile15(cachePath, "utf-8");
15627
16037
  const parsed = JSON.parse(raw);
15628
16038
  if (parsed !== null && typeof parsed === "object" && "checkedAt" in parsed && "latest" in parsed && typeof parsed.checkedAt === "string" && typeof parsed.latest === "string") {
15629
16039
  return parsed;