@kodocagent/cli 0.6.3 → 0.7.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
@@ -9903,6 +9903,31 @@ function redactText(text3) {
9903
9903
  }
9904
9904
  return { text: result, findings };
9905
9905
  }
9906
+ function redactRanges(text3) {
9907
+ if (!text3) return [];
9908
+ const ranges = [];
9909
+ for (const { type, re, mask } of PATTERNS) {
9910
+ for (const m of text3.matchAll(new RegExp(re.source, re.flags))) {
9911
+ if (m.index === void 0) continue;
9912
+ ranges.push({
9913
+ start: m.index,
9914
+ end: m.index + m[0].length,
9915
+ replacement: mask(m[0]),
9916
+ type
9917
+ });
9918
+ }
9919
+ }
9920
+ ranges.sort((a, b) => a.start - b.start || b.end - a.end);
9921
+ const out = [];
9922
+ let lastEnd = -1;
9923
+ for (const r of ranges) {
9924
+ if (r.start >= lastEnd) {
9925
+ out.push(r);
9926
+ lastEnd = r.end;
9927
+ }
9928
+ }
9929
+ return out;
9930
+ }
9906
9931
 
9907
9932
  // ../core/dist/index.js
9908
9933
  import { stepCountIs, streamText } from "ai";
@@ -9977,6 +10002,12 @@ var LAW_RULES_SECTION = `## \uBC95\uB839 \uADDC\uCE59
9977
10002
  2. \uBC95\uB839 \uD604\uD589 \uC5EC\uBD80\uB294 MCP \uBC95\uB839 \uD234(\`mcp__korean-law__*\`)\uB85C \uD655\uC778\uD55C \uD6C4 \uC778\uC6A9\uD558\uC138\uC694.
9978
10003
  3. MCP \uBC95\uB839 \uD234\uB85C \uD655\uC778\uD558\uC9C0 \uBABB\uD55C \uBC95\uB839 \uB0B4\uC6A9\uC740 \uBC18\uB4DC\uC2DC "\u203B \uD604\uD589 \uC5EC\uBD80\uB97C \uD655\uC778\uD558\uC9C0 \uC54A\uC740 \uB0B4\uC6A9\uC785\uB2C8\uB2E4"\uB77C\uACE0 \uBA85\uC2DC\uD558\uC138\uC694.
9979
10004
  4. \uBC95\uB839 \uD574\uC11D\uC740 \uCC38\uACE0\uC6A9\uC774\uBA70 \uBC95\uC801 \uD6A8\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC911\uC694\uD55C \uC0AC\uD56D\uC740 \uC804\uBB38\uAC00\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694.`;
10005
+ var SELF_VERIFY_PROMPT = `[\uC790\uB3D9 \uAC80\uC99D \uB2E8\uACC4] \uBC29\uAE08 \uBB38\uC11C\uB97C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4. \uC791\uC5C5\uC744 \uB9C8\uCE58\uAE30 \uC804\uC5D0 \uBC18\uB4DC\uC2DC \uB2E4\uC74C\uC744 \uC218\uD589\uD558\uC138\uC694.
10006
+
10007
+ 1. \`read_document\`\uB85C \uBCC0\uACBD\uB41C \uBB38\uC11C(\uB610\uB294 \uAD00\uB828 \uBD80\uBD84)\uB97C **\uB2E4\uC2DC \uC77D\uC73C\uC138\uC694**.
10008
+ 2. \uC0AC\uC6A9\uC790\uC758 **\uC6D0\uB798 \uC694\uCCAD**\uC744 \uD55C \uD56D\uBAA9\uC529 \uB300\uC870\uD558\uC138\uC694 \u2014 "\uBAA8\uB450/\uC804\uBD80/\uC77C\uAD04"\uC774\uBA74 \uBAA8\uB4E0 \uD56D\uBAA9\uC774 \uCC98\uB9AC\uB410\uB294\uC9C0, \uAC12\xB7\uD615\uC2DD\xB7\uB300\uC0C1 \uC704\uCE58\uAC00 \uC815\uD655\uD55C\uC9C0, \uBE60\uC9C0\uAC70\uB098 \uC798\uBABB \uBC18\uC601\uB41C \uBD80\uBD84\uC774 \uC5C6\uB294\uC9C0 \uD655\uC778\uD569\uB2C8\uB2E4.
10009
+ 3. \uB204\uB77D\xB7\uC624\uB958\uAC00 \uC788\uC73C\uBA74 \uC989\uC2DC \`propose_*\` \uB3C4\uAD6C\uB85C **\uCD94\uAC00 \uC218\uC815\uC548\uC744 \uC81C\uC548**\uD558\uC138\uC694. \uB2E8, \uC0AC\uC6A9\uC790\uAC00 \uC774\uBBF8 \uAC70\uC808\uD55C \uC81C\uC548\uC740 \uB2E4\uC2DC \uC62C\uB9AC\uC9C0 \uB9C8\uC138\uC694.
10010
+ 4. \uBAA8\uB4E0 \uD56D\uBAA9\uC774 \uC815\uD655\uD788 \uBC18\uC601\uB410\uC73C\uBA74 \uCD94\uAC00 \uB3C4\uAD6C \uD638\uCD9C \uC5C6\uC774 \`\uAC80\uC99D \uC644\uB8CC: <\uD55C \uC904 \uC694\uC57D>\` \uD615\uC2DD\uC73C\uB85C\uB9CC \uB2F5\uD558\uC138\uC694.`;
9980
10011
  function buildDynamicContext(ctx) {
9981
10012
  const lines = ["## \uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8"];
9982
10013
  lines.push(`- **\uC791\uC5C5 \uB514\uB809\uD130\uB9AC**: \`${ctx.cwd}\``);
@@ -10077,6 +10108,20 @@ function compactMessages(messages, maxTokens) {
10077
10108
  }
10078
10109
  return result;
10079
10110
  }
10111
+ var EDITING_TOOLS = /* @__PURE__ */ new Set([
10112
+ "propose_edit",
10113
+ "propose_find_replace",
10114
+ "propose_cell_edit",
10115
+ "propose_redact_pii",
10116
+ "propose_form_fill",
10117
+ "propose_table_structure",
10118
+ "propose_form_object",
10119
+ "propose_sheet_edit",
10120
+ "write_new_document",
10121
+ "write_new_spreadsheet",
10122
+ "restore_backup"
10123
+ ]);
10124
+ var MAX_SELF_VERIFY_ROUNDS = 1;
10080
10125
  var AgentSession = class {
10081
10126
  constructor(opts) {
10082
10127
  this.opts = opts;
@@ -10136,110 +10181,129 @@ var AgentSession = class {
10136
10181
  await store.appendUser(userMessage);
10137
10182
  const userMsg = { role: "user", content: userMessage };
10138
10183
  this.messages.push(userMsg);
10139
- const system = buildSystemPrompt({
10140
- cwd: this.opts.cwd,
10141
- mcpServers: this.opts.mcpServers ?? [],
10142
- openDocuments: this.openDocuments,
10143
- toolNames: tools.toolNames
10144
- });
10145
10184
  const aiSdkTools = tools.toAiSdkTools();
10146
- this.messages = compactMessages(this.messages, config.maxContextTokens);
10185
+ const selfVerifyEnabled = process.env.KODOC_SELF_VERIFY !== "0";
10186
+ let verifyRounds = 0;
10187
+ let totalInputTokens = 0;
10188
+ let totalOutputTokens = 0;
10189
+ let sawFinish = false;
10147
10190
  try {
10148
- const result = streamText({
10149
- model,
10150
- system,
10151
- messages: this.messages,
10152
- tools: aiSdkTools,
10153
- // 멀티스텝 정지 조건: maxSteps번 툴콜 후 중단 (AI SDK v6 실제 API)
10154
- stopWhen: stepCountIs(config.maxSteps),
10155
- abortSignal: signal,
10156
- // 샘플링 파라미터 미설정 (SPEC §3, §5 불변 원칙)
10157
- onError: () => {
10158
- }
10159
- });
10160
- for await (const part of result.fullStream) {
10161
- if (signal.aborted) break;
10162
- while (this.pendingApprovalEvents.length > 0) {
10163
- const proposal = this.pendingApprovalEvents.shift();
10164
- yield { type: "approval-required", proposal };
10165
- }
10166
- switch (part.type) {
10167
- case "text-delta": {
10168
- yield { type: "text-delta", text: part.text };
10169
- break;
10191
+ while (true) {
10192
+ const system = buildSystemPrompt({
10193
+ cwd: this.opts.cwd,
10194
+ mcpServers: this.opts.mcpServers ?? [],
10195
+ openDocuments: this.openDocuments,
10196
+ toolNames: tools.toolNames
10197
+ });
10198
+ this.messages = compactMessages(this.messages, config.maxContextTokens);
10199
+ let editedThisRound = false;
10200
+ const result = streamText({
10201
+ model,
10202
+ system,
10203
+ messages: this.messages,
10204
+ tools: aiSdkTools,
10205
+ // 멀티스텝 정지 조건: maxSteps번 툴콜 후 중단 (AI SDK v6 실제 API)
10206
+ stopWhen: stepCountIs(config.maxSteps),
10207
+ abortSignal: signal,
10208
+ // 샘플링 파라미터 미설정 (SPEC §3, §5 불변 원칙)
10209
+ onError: () => {
10170
10210
  }
10171
- case "tool-call": {
10172
- yield {
10173
- type: "tool-call",
10174
- toolName: part.toolName,
10175
- args: part.input,
10176
- callId: part.toolCallId
10177
- };
10178
- try {
10179
- const inp = part.input;
10180
- if (part.toolName === "read_document") {
10181
- this.recordOpenDocument(inp.path);
10182
- } else if (part.toolName === "compare_documents") {
10183
- this.recordOpenDocument(inp.pathA);
10184
- this.recordOpenDocument(inp.pathB);
10185
- } else if (part.toolName === "write_new_document" || part.toolName === "write_new_spreadsheet") {
10186
- this.recordOpenDocument(inp.path);
10211
+ });
10212
+ for await (const part of result.fullStream) {
10213
+ if (signal.aborted) break;
10214
+ while (this.pendingApprovalEvents.length > 0) {
10215
+ const proposal = this.pendingApprovalEvents.shift();
10216
+ yield { type: "approval-required", proposal };
10217
+ }
10218
+ switch (part.type) {
10219
+ case "text-delta": {
10220
+ yield { type: "text-delta", text: part.text };
10221
+ break;
10222
+ }
10223
+ case "tool-call": {
10224
+ yield {
10225
+ type: "tool-call",
10226
+ toolName: part.toolName,
10227
+ args: part.input,
10228
+ callId: part.toolCallId
10229
+ };
10230
+ if (EDITING_TOOLS.has(part.toolName)) editedThisRound = true;
10231
+ try {
10232
+ const inp = part.input;
10233
+ if (part.toolName === "read_document") {
10234
+ this.recordOpenDocument(inp.path);
10235
+ } else if (part.toolName === "compare_documents") {
10236
+ this.recordOpenDocument(inp.pathA);
10237
+ this.recordOpenDocument(inp.pathB);
10238
+ } else if (part.toolName === "write_new_document" || part.toolName === "write_new_spreadsheet") {
10239
+ this.recordOpenDocument(inp.path);
10240
+ }
10241
+ } catch {
10187
10242
  }
10188
- } catch {
10243
+ break;
10189
10244
  }
10190
- break;
10191
- }
10192
- case "tool-result": {
10193
- const isError = false;
10194
- yield {
10195
- type: "tool-result",
10196
- callId: part.toolCallId,
10197
- result: part.output,
10198
- isError
10199
- };
10200
- await store.appendToolResult(part.toolCallId, part.output, isError);
10201
- break;
10202
- }
10203
- case "tool-error": {
10204
- yield {
10205
- type: "tool-result",
10206
- callId: part.toolCallId,
10207
- result: String(part.error),
10208
- isError: true
10209
- };
10210
- await store.appendToolResult(part.toolCallId, String(part.error), true);
10211
- break;
10212
- }
10213
- case "finish": {
10214
- const usage = part.totalUsage;
10215
- yield {
10216
- type: "turn-complete",
10217
- usage: usage ? {
10218
- inputTokens: usage.inputTokens ?? 0,
10219
- outputTokens: usage.outputTokens ?? 0
10220
- } : void 0
10221
- };
10222
- break;
10245
+ case "tool-result": {
10246
+ const isError = false;
10247
+ yield {
10248
+ type: "tool-result",
10249
+ callId: part.toolCallId,
10250
+ result: part.output,
10251
+ isError
10252
+ };
10253
+ await store.appendToolResult(part.toolCallId, part.output, isError);
10254
+ break;
10255
+ }
10256
+ case "tool-error": {
10257
+ yield {
10258
+ type: "tool-result",
10259
+ callId: part.toolCallId,
10260
+ result: String(part.error),
10261
+ isError: true
10262
+ };
10263
+ await store.appendToolResult(part.toolCallId, String(part.error), true);
10264
+ break;
10265
+ }
10266
+ case "finish": {
10267
+ const usage = part.totalUsage;
10268
+ if (usage) {
10269
+ totalInputTokens += usage.inputTokens ?? 0;
10270
+ totalOutputTokens += usage.outputTokens ?? 0;
10271
+ }
10272
+ sawFinish = true;
10273
+ break;
10274
+ }
10275
+ case "error": {
10276
+ const errPart = part;
10277
+ const message = errPart.error instanceof Error ? errPart.error.message : String(errPart.error);
10278
+ yield { type: "error", message, recoverable: false };
10279
+ break;
10280
+ }
10281
+ default:
10282
+ break;
10223
10283
  }
10224
- case "error": {
10225
- const errPart = part;
10226
- const message = errPart.error instanceof Error ? errPart.error.message : String(errPart.error);
10227
- yield { type: "error", message, recoverable: false };
10228
- break;
10284
+ }
10285
+ try {
10286
+ const response = await result.response;
10287
+ for (const msg of response.messages) {
10288
+ const modelMsg = msg;
10289
+ this.messages.push(modelMsg);
10290
+ await store.appendAssistant(modelMsg);
10229
10291
  }
10230
- default:
10231
- break;
10292
+ } catch {
10232
10293
  }
10233
- }
10234
- try {
10235
- const response = await result.response;
10236
- for (const msg of response.messages) {
10237
- const modelMsg = msg;
10238
- this.messages.push(modelMsg);
10239
- await store.appendAssistant(modelMsg);
10294
+ if (selfVerifyEnabled && editedThisRound && verifyRounds < MAX_SELF_VERIFY_ROUNDS && !signal.aborted) {
10295
+ verifyRounds++;
10296
+ const verifyMsg = { role: "user", content: SELF_VERIFY_PROMPT };
10297
+ this.messages.push(verifyMsg);
10298
+ await store.appendUser(SELF_VERIFY_PROMPT);
10299
+ continue;
10240
10300
  }
10241
- } catch {
10301
+ break;
10242
10302
  }
10303
+ yield {
10304
+ type: "turn-complete",
10305
+ usage: sawFinish ? { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } : void 0
10306
+ };
10243
10307
  } catch (err) {
10244
10308
  if (signal.aborted) {
10245
10309
  return;
@@ -10934,38 +10998,44 @@ var ToolRegistry = class {
10934
10998
  // ../doc-tools/dist/index.js
10935
10999
  import { realpath } from "fs/promises";
10936
11000
  import { basename, dirname as dirname2, isAbsolute, join as join3, normalize, relative, resolve } from "path";
11001
+ import { isOldHwpFile, isZipFile } from "kordoc";
10937
11002
  import { copyFile, mkdir as mkdir3, readdir as readdir2, readFile as readFile3, rename, rm, stat as stat2, writeFile as writeFile2 } from "fs/promises";
10938
11003
  import { basename as basename2, dirname as dirname22, extname, join as join22 } from "path";
10939
11004
  import { createTwoFilesPatch } from "diff";
10940
11005
  import { readdir as readdir22, readFile as readFile22, stat as stat22 } from "fs/promises";
10941
11006
  import { basename as basename3, extname as extname2, join as join32 } from "path";
11007
+ import { z as z3 } from "zod";
11008
+ import { readFile as readFile32 } from "fs/promises";
11009
+ import { blocksToMarkdown, compare } from "kordoc";
11010
+ import { z as z22 } from "zod";
11011
+ import { extname as extname3 } from "path";
10942
11012
  var import_jszip = __toESM(require_lib3(), 1);
10943
11013
  var import_jszip2 = __toESM(require_lib3(), 1);
10944
11014
  var import_jszip3 = __toESM(require_lib3(), 1);
10945
11015
  var import_jszip4 = __toESM(require_lib3(), 1);
10946
11016
  var import_jszip5 = __toESM(require_lib3(), 1);
10947
- import { z as z3 } from "zod";
10948
- import { readFile as readFile32 } from "fs/promises";
10949
- import { blocksToMarkdown, compare } from "kordoc";
10950
- import { z as z22 } from "zod";
11017
+ import { markdownToPdf, renderHtml } from "kordoc";
11018
+ import { z as z32 } from "zod";
11019
+ import { readFile as readFile4 } from "fs/promises";
11020
+ import { isZipFile as isZipFile2, parse as kordocParse } from "kordoc";
10951
11021
  import { readFile as readFile5 } from "fs/promises";
10952
11022
  import { extname as extname4 } from "path";
11023
+ import { scanSectionXml } from "kordoc";
10953
11024
  import { z as z4 } from "zod";
10954
- import { readFile as readFile4 } from "fs/promises";
10955
- import { extname as extname3 } from "path";
10956
- import { z as z32 } from "zod";
10957
11025
  import { readFile as readFile6 } from "fs/promises";
10958
11026
  import { extname as extname5 } from "path";
10959
11027
  import { z as z5 } from "zod";
10960
11028
  import { readdir as readdir3, stat as stat3 } from "fs/promises";
10961
11029
  import { extname as extname6, join as join4, relative as relative2 } from "path";
10962
11030
  import { z as z6 } from "zod";
10963
- import { readFile as readFile8 } from "fs/promises";
11031
+ import { readFile as readFile7 } from "fs/promises";
10964
11032
  import { extname as extname7 } from "path";
10965
- import { compare as compare2, patchHwp, patchHwpx } from "kordoc";
11033
+ import { applySplices, buildParagraphSplices, scanSectionXml as scanSectionXml2 } from "kordoc";
10966
11034
  import { z as z7 } from "zod";
10967
- import { readFile as readFile7 } from "fs/promises";
10968
- import { parse as kordocParse } from "kordoc";
11035
+ import { readFile as readFile8 } from "fs/promises";
11036
+ import { extname as extname8 } from "path";
11037
+ import { compare as compare2, patchHwp, patchHwpx } from "kordoc";
11038
+ import { z as z8 } from "zod";
10969
11039
  import {
10970
11040
  Document,
10971
11041
  HeadingLevel,
@@ -10978,52 +11048,59 @@ import {
10978
11048
  WidthType
10979
11049
  } from "docx";
10980
11050
  import { readFile as readFile9 } from "fs/promises";
10981
- import { extname as extname8 } from "path";
10982
- import { z as z8 } from "zod";
10983
- import { readFile as readFile10 } from "fs/promises";
10984
11051
  import { extname as extname9 } from "path";
10985
- import { extractFormFields, fillHwpx } from "kordoc";
11052
+ import { applySplices as applySplices3, buildRangeSplices as buildRangeSplices2, scanSectionXml as scanSectionXml4 } from "kordoc";
10986
11053
  import { z as z9 } from "zod";
11054
+ import { applySplices as applySplices2, buildRangeSplices, scanSectionXml as scanSectionXml3 } from "kordoc";
11055
+ import { readFile as readFile10 } from "fs/promises";
11056
+ import { extname as extname10 } from "path";
11057
+ import { extractFormSchema, fillHwpx } from "kordoc";
11058
+ import { z as z10 } from "zod";
10987
11059
  import { readFile as readFile11 } from "fs/promises";
10988
- import { basename as basename4, extname as extname10 } from "path";
11060
+ import { basename as basename4, extname as extname11 } from "path";
10989
11061
  var import_jszip6 = __toESM(require_lib3(), 1);
10990
11062
  var import_jszip7 = __toESM(require_lib3(), 1);
10991
- import { z as z10 } from "zod";
10992
- import { readFile as readFile12 } from "fs/promises";
10993
- import { extname as extname11 } from "path";
10994
- import ExcelJS from "exceljs";
11063
+ import { scanSectionXml as scanSectionXml5 } from "kordoc";
10995
11064
  import { z as z11 } from "zod";
10996
- import { readFile as readFile13 } from "fs/promises";
11065
+ import { readFile as readFile12 } from "fs/promises";
10997
11066
  import { extname as extname12 } from "path";
11067
+ import ExcelJS from "exceljs";
10998
11068
  import { z as z12 } from "zod";
10999
- import { readFile as readFile14, stat as stat4 } from "fs/promises";
11069
+ import { readFile as readFile13 } from "fs/promises";
11000
11070
  import { extname as extname13 } from "path";
11001
11071
  import { z as z13 } from "zod";
11002
- import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
11003
- import { z as z14 } from "zod";
11004
- import { readFile as readFile15 } from "fs/promises";
11072
+ import { readFile as readFile14, stat as stat4 } from "fs/promises";
11005
11073
  import { extname as extname14 } from "path";
11074
+ import { z as z14 } from "zod";
11075
+ import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
11006
11076
  import { z as z15 } from "zod";
11007
- import { stat as stat6 } from "fs/promises";
11077
+ import { readFile as readFile15 } from "fs/promises";
11008
11078
  import { extname as extname15 } from "path";
11009
- import { markdownToHwpx } from "kordoc";
11010
11079
  import { z as z16 } from "zod";
11011
- import { stat as stat7 } from "fs/promises";
11080
+ import { stat as stat6 } from "fs/promises";
11012
11081
  import { extname as extname16 } from "path";
11013
- import ExcelJS2 from "exceljs";
11082
+ import { markdownToHwpx } from "kordoc";
11014
11083
  import { z as z17 } from "zod";
11015
- var OLE2_MAGIC = [208, 207, 17, 224, 161, 177, 26, 225];
11016
- var ZIP_MAGIC_0 = 80;
11017
- var ZIP_MAGIC_1 = 75;
11084
+ import { stat as stat7 } from "fs/promises";
11085
+ import { extname as extname17 } from "path";
11086
+ import ExcelJS2 from "exceljs";
11087
+ import { z as z18 } from "zod";
11088
+ function headerBuffer(bytes, n = 64) {
11089
+ return new Uint8Array(bytes.subarray(0, Math.min(n, bytes.length))).buffer;
11090
+ }
11018
11091
  function isOle2Binary(bytes) {
11019
- if (bytes.length < OLE2_MAGIC.length) return false;
11020
- return OLE2_MAGIC.every((b, i) => bytes[i] === b);
11092
+ if (bytes.length < 8) return false;
11093
+ return isOldHwpFile(headerBuffer(bytes));
11094
+ }
11095
+ function isZipBinary(bytes) {
11096
+ if (bytes.length < 4) return false;
11097
+ return isZipFile(headerBuffer(bytes));
11021
11098
  }
11022
11099
  function hwpStructuralGuard(ext, bytes) {
11023
11100
  const isHwpExt = ext === ".hwp";
11024
11101
  const isOle2 = isOle2Binary(bytes);
11025
11102
  if (!isHwpExt && !isOle2) return null;
11026
- if (!isHwpExt && bytes[0] === ZIP_MAGIC_0 && bytes[1] === ZIP_MAGIC_1) return null;
11103
+ if (!isHwpExt && isZipBinary(bytes)) return null;
11027
11104
  return "\uC774 \uC791\uC5C5(\uD45C\xB7\uC140\xB7\uC591\uC2DD\xB7\uCC3E\uAE30\uBC14\uAFB8\uAE30 \uB4F1 \uAD6C\uC870 \uD3B8\uC9D1)\uC740 `.hwpx` \uBB38\uC11C\uC5D0\uC11C \uC9C0\uC6D0\uB429\uB2C8\uB2E4. `.hwp`\uB294 OLE \uBC14\uC774\uB108\uB9AC\uB77C \uAD6C\uC870\uB97C \uBB34\uC190\uC2E4\uB85C \uD328\uCE58\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD574\uACB0: (1) \uBCF8\uBB38 \uD14D\uC2A4\uD2B8\uB9CC \uACE0\uCE58\uB824\uBA74 `propose_edit`\uC744 \uC0AC\uC6A9\uD558\uC138\uC694(.hwp \uC81C\uC790\uB9AC \uD3B8\uC9D1). (2) \uD45C\xB7\uC140\xB7\uC591\uC2DD \uD3B8\uC9D1\uC774 \uD544\uC694\uD558\uBA74 \uD55C\uAE00\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 HWPX(.hwpx)'\uB85C \uBCC0\uD658\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
11028
11105
  }
11029
11106
  async function resolveSafePath(cwd, p) {
@@ -11525,577 +11602,272 @@ var compareDocumentsTool = {
11525
11602
  return output;
11526
11603
  }
11527
11604
  };
11528
- var cellEditItemSchema = z32.object({
11529
- tableIndex: z32.number().int().nonnegative().optional().describe(
11530
- "\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)."
11531
- ),
11532
- row: z32.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 rowAddr (0-based)"),
11533
- col: z32.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 colAddr (0-based)"),
11534
- label: z32.string().optional().describe(
11535
- "\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."
11536
- ),
11537
- 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."),
11538
- newText: z32.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
11539
- expectedText: z32.string().optional().describe(
11540
- "\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."
11541
- )
11542
- }).describe(
11543
- "\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."
11605
+ var SHAPE_ALT_KEYWORDS = [
11606
+ "\uC0AC\uAC01\uD615",
11607
+ "\uC9C1\uC0AC\uAC01\uD615",
11608
+ "\uC815\uC0AC\uAC01\uD615",
11609
+ "\uC6D0",
11610
+ "\uD0C0\uC6D0",
11611
+ "\uC0BC\uAC01\uD615",
11612
+ "\uC774\uB4F1\uBCC0 \uC0BC\uAC01\uD615",
11613
+ "\uC9C1\uAC01 \uC0BC\uAC01\uD615",
11614
+ "\uC120",
11615
+ "\uC9C1\uC120",
11616
+ "\uACE1\uC120",
11617
+ "\uD654\uC0B4\uD45C",
11618
+ "\uAD75\uC740 \uD654\uC0B4\uD45C",
11619
+ "\uC774\uC911 \uD654\uC0B4\uD45C",
11620
+ "\uC624\uAC01\uD615",
11621
+ "\uC721\uAC01\uD615",
11622
+ "\uD314\uAC01\uD615",
11623
+ "\uBCC4",
11624
+ "[4-8]\uC810\uBCC4",
11625
+ "\uC2ED\uC790",
11626
+ "\uC2ED\uC790\uD615",
11627
+ "\uAD6C\uB984",
11628
+ "\uAD6C\uB984\uD615",
11629
+ "\uB9C8\uB984\uBAA8",
11630
+ "\uB3C4\uB11B",
11631
+ "\uD3C9\uD589\uC0AC\uBCC0\uD615",
11632
+ "\uC0AC\uB2E4\uB9AC\uAF34",
11633
+ "\uBD80\uCC44\uAF34",
11634
+ "\uD638",
11635
+ "\uBC18\uC6D0",
11636
+ "\uBB3C\uACB0",
11637
+ "\uBC88\uAC1C",
11638
+ "\uD558\uD2B8",
11639
+ "\uBE57\uAE08",
11640
+ "\uBE14\uB85D \uD654\uC0B4\uD45C",
11641
+ "\uC218\uC2DD",
11642
+ "\uD45C",
11643
+ "\uADF8\uB9BC",
11644
+ "\uAC1C\uCCB4",
11645
+ "\uADF8\uB9AC\uAE30\\s?\uAC1C\uCCB4",
11646
+ "\uBB36\uC74C\\s?\uAC1C\uCCB4",
11647
+ "\uAE00\uC0C1\uC790",
11648
+ "\uC218\uC2DD\\s?\uAC1C\uCCB4",
11649
+ "OLE\\s?\uAC1C\uCCB4"
11650
+ ].join("|");
11651
+ var BUGGY_SHAPE_STRIP = new RegExp(
11652
+ `(?:\uBAA8\uC11C\uB9AC\uAC00 \uB465\uADFC |\uB465\uADFC )?(?:${SHAPE_ALT_KEYWORDS})\\s?\uC785\uB2C8\uB2E4\\.?`,
11653
+ "g"
11544
11654
  );
11545
- var proposeCellEditSchema = z32.object({
11546
- 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)"),
11547
- edits: z32.array(cellEditItemSchema).min(1).describe(
11548
- "\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"
11549
- ),
11550
- summary: z32.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
11551
- });
11552
- function tokenizeHwpxXml(xml) {
11553
- const tokens = [];
11554
- const re = /<hp:tbl[\s>]|<\/hp:tbl>|<hp:tc[\s>]|<\/hp:tc>|<hp:t\/>|<hp:t>|<hp:cellAddr[^/>]*|<hp:cellSpan[^/>]*/g;
11555
- let startPos = 0;
11556
- let m = re.exec(xml);
11557
- while (m !== null) {
11558
- const raw = m[0];
11559
- const pos = m.index;
11560
- if (raw.startsWith("<hp:tbl")) {
11561
- tokens.push({ kind: "tbl_open", pos, end: pos + raw.length });
11562
- } else if (raw === "</hp:tbl>") {
11563
- tokens.push({ kind: "tbl_close", pos, end: pos + raw.length });
11564
- } else if (raw.startsWith("<hp:tc")) {
11565
- tokens.push({ kind: "tc_open", pos, end: pos + raw.length });
11566
- } else if (raw === "</hp:tc>") {
11567
- tokens.push({ kind: "tc_close", pos, end: pos + raw.length });
11568
- } else if (raw === "<hp:t/>") {
11569
- tokens.push({ kind: "t_empty", pos, end: pos + raw.length });
11570
- } else if (raw === "<hp:t>") {
11571
- tokens.push({ kind: "t_open", pos, end: pos + raw.length });
11572
- } else if (raw.startsWith("<hp:cellAddr")) {
11573
- const colM = raw.match(/colAddr="(\d+)"/);
11574
- const rowM = raw.match(/rowAddr="(\d+)"/);
11575
- if (colM && rowM) {
11576
- const selfClose = xml.indexOf("/>", pos);
11577
- const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
11578
- tokens.push({
11579
- kind: "cell_addr",
11580
- pos,
11581
- end,
11582
- colAddr: Number(colM[1]),
11583
- rowAddr: Number(rowM[1])
11584
- });
11585
- }
11586
- } else if (raw.startsWith("<hp:cellSpan")) {
11587
- const colM = raw.match(/colSpan="(\d+)"/);
11588
- const rowM = raw.match(/rowSpan="(\d+)"/);
11589
- const selfClose = xml.indexOf("/>", pos);
11590
- const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
11591
- tokens.push({
11592
- kind: "cell_span",
11593
- pos,
11594
- end,
11595
- colSpan: colM ? Number(colM[1]) : 1,
11596
- rowSpan: rowM ? Number(rowM[1]) : 1
11597
- });
11598
- }
11599
- startPos = re.lastIndex;
11600
- m = re.exec(xml);
11601
- }
11602
- void startPos;
11603
- return tokens;
11655
+ var FIXED_SHAPE_STRIP = new RegExp(
11656
+ `(?<![\uAC00-\uD7A3])(?:\uBAA8\uC11C\uB9AC\uAC00 \uB465\uADFC |\uB465\uADFC )?(?:${SHAPE_ALT_KEYWORDS})\\s?\uC785\uB2C8\uB2E4\\.?`,
11657
+ "g"
11658
+ );
11659
+ function applyStrip(raw, re) {
11660
+ const normalized = raw.replace(/[ \t]+/g, " ").trim();
11661
+ return normalized.replace(re, "").trim();
11604
11662
  }
11605
- function escapeXml(text3) {
11606
- return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
11663
+ function decodeXmlEntities(s) {
11664
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(Number.parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number.parseInt(d, 10))).replace(/&amp;/g, "&");
11607
11665
  }
11608
- function collectDirectTcRanges(tokens, tblStart, tblEnd) {
11609
- const tblTokens = tokens.filter((t) => t.pos > tblStart && t.pos < tblEnd);
11610
- const tcRanges = [];
11611
- const tcStack = [];
11612
- let innerDepth = 0;
11613
- for (const tok of tblTokens) {
11614
- if (tok.kind === "tbl_open") {
11615
- innerDepth++;
11616
- } else if (tok.kind === "tbl_close") {
11617
- innerDepth--;
11618
- } else if (tok.kind === "tc_open") {
11619
- tcStack.push({ pos: tok.pos, depth: innerDepth });
11620
- } else if (tok.kind === "tc_close") {
11621
- const entry = tcStack.pop();
11622
- if (entry !== void 0) {
11623
- tcRanges.push({ start: entry.pos, end: tok.end, depth: entry.depth });
11666
+ async function extractHwpxParagraphTexts(bytes) {
11667
+ const zip = await import_jszip.default.loadAsync(bytes);
11668
+ const out = [];
11669
+ for (const name of Object.keys(zip.files)) {
11670
+ if (!/^Contents\/section\d*\.xml$/i.test(name)) continue;
11671
+ const entry = zip.files[name];
11672
+ if (!entry) continue;
11673
+ const xml = await entry.async("string");
11674
+ for (const seg of xml.split(/(?=<hp:p\b)/)) {
11675
+ let para = "";
11676
+ for (const m of seg.matchAll(/<hp:t(?:\s[^>]*)?>([\s\S]*?)<\/hp:t>/g)) {
11677
+ para += decodeXmlEntities((m[1] ?? "").replace(/<[^>]+>/g, ""));
11624
11678
  }
11679
+ if (para.trim()) out.push(para);
11625
11680
  }
11626
11681
  }
11627
- return tcRanges.filter((tc) => tc.depth === 0);
11682
+ return out;
11628
11683
  }
11629
- function readOwnTextFromTc(xml, tokens, tcStart, tcEnd) {
11630
- const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
11631
- let innerDepth = 0;
11632
- let text3 = "";
11633
- for (const t of tcTokens) {
11634
- if (t.kind === "tbl_open") innerDepth++;
11635
- else if (t.kind === "tbl_close") innerDepth--;
11636
- else if (t.kind === "t_empty" && innerDepth === 0) {
11637
- } else if (t.kind === "t_open" && innerDepth === 0) {
11638
- const closePos = xml.indexOf("</hp:t>", t.end);
11639
- if (closePos >= 0) {
11640
- text3 += xml.substring(t.end, closePos);
11641
- }
11642
- }
11643
- }
11644
- return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
11684
+ function splitDecoration(line) {
11685
+ const m = line.match(/^(\s*(?:#{1,6}\s+|[-*+]\s+|\d+\.\s+|>\s+)?)([\s\S]*)$/);
11686
+ if (!m) return { prefix: "", content: line };
11687
+ return { prefix: m[1] ?? "", content: m[2] ?? "" };
11645
11688
  }
11646
- function findTopLevelTableRange(tokens, tableIndex) {
11647
- let topLevelCount = 0;
11648
- let depth = 0;
11649
- let targetStart = -1;
11650
- for (const tok of tokens) {
11651
- if (tok.kind === "tbl_open") {
11652
- if (depth === 0) {
11653
- if (topLevelCount === tableIndex) {
11654
- targetStart = tok.pos;
11655
- }
11656
- topLevelCount++;
11657
- }
11658
- depth++;
11659
- } else if (tok.kind === "tbl_close") {
11660
- depth--;
11661
- if (depth === 0 && topLevelCount === tableIndex + 1 && targetStart >= 0) {
11662
- return { start: targetStart, end: tok.end };
11689
+ function restoreOverStrippedShapeText(markdown, paragraphTexts) {
11690
+ const lines = markdown.split("\n");
11691
+ const used = /* @__PURE__ */ new Set();
11692
+ let changed = false;
11693
+ for (const raw of paragraphTexts) {
11694
+ const buggy = applyStrip(raw, BUGGY_SHAPE_STRIP);
11695
+ const fixed = applyStrip(raw, FIXED_SHAPE_STRIP);
11696
+ if (buggy === fixed) continue;
11697
+ if (!fixed) continue;
11698
+ if (!buggy) continue;
11699
+ for (let i = 0; i < lines.length; i++) {
11700
+ if (used.has(i)) continue;
11701
+ const { prefix, content } = splitDecoration(lines[i] ?? "");
11702
+ if (content.trim() === buggy && content.trim() !== fixed) {
11703
+ lines[i] = prefix + fixed;
11704
+ used.add(i);
11705
+ changed = true;
11706
+ break;
11663
11707
  }
11664
11708
  }
11665
11709
  }
11666
- return null;
11667
- }
11668
- function readCellAddrSpan(tokens, tcStart, tcEnd) {
11669
- const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
11670
- let addr = null;
11671
- let span = { colSpan: 1, rowSpan: 1 };
11672
- for (const t of tcTokens) {
11673
- if (t.kind === "cell_addr" && t.colAddr !== void 0 && t.rowAddr !== void 0) {
11674
- addr = { colAddr: t.colAddr, rowAddr: t.rowAddr };
11675
- } else if (t.kind === "cell_span" && t.colSpan !== void 0 && t.rowSpan !== void 0) {
11676
- span = { colSpan: t.colSpan, rowSpan: t.rowSpan };
11677
- }
11678
- }
11679
- if (!addr) return null;
11680
- return { ...addr, ...span };
11710
+ return changed ? lines.join("\n") : markdown;
11681
11711
  }
11682
- function applyCellEditsToSectionXml(xml, edits) {
11683
- const tokens = tokenizeHwpxXml(xml);
11684
- const results = edits.map(() => ({ success: false }));
11685
- let totalTopLevelTbls = 0;
11686
- {
11687
- let d = 0;
11688
- for (const tok of tokens) {
11689
- if (tok.kind === "tbl_open") {
11690
- if (d === 0) totalTopLevelTbls++;
11691
- d++;
11692
- } else if (tok.kind === "tbl_close") {
11693
- d--;
11694
- }
11695
- }
11696
- }
11697
- const replacements = [];
11698
- for (let ei = 0; ei < edits.length; ei++) {
11699
- const edit = edits[ei];
11700
- const tblRange = findTopLevelTableRange(tokens, edit.tableIndex);
11701
- if (!tblRange) {
11702
- results[ei] = {
11703
- success: false,
11704
- 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.`
11705
- };
11706
- continue;
11707
- }
11708
- const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
11709
- const tblTokens = tokens.filter((t) => t.pos >= tblRange.start && t.pos < tblRange.end);
11710
- let found = false;
11711
- for (const tc of directTcs) {
11712
- const tcTokens = tblTokens.filter((t) => t.pos >= tc.start && t.pos < tc.end);
11713
- const hasAddr = tcTokens.some(
11714
- (t) => t.kind === "cell_addr" && t.colAddr === edit.col && t.rowAddr === edit.row
11715
- );
11716
- if (!hasAddr) continue;
11717
- let innerDepth = 0;
11718
- const ownTRuns = [];
11719
- for (const t of tokens.filter((x) => x.pos >= tc.start && x.pos < tc.end)) {
11720
- if (t.kind === "tbl_open") innerDepth++;
11721
- else if (t.kind === "tbl_close") innerDepth--;
11722
- else if (t.kind === "t_empty" && innerDepth === 0) {
11723
- ownTRuns.push({ isEmpty: true, tagPos: t.pos, tagEnd: t.end });
11724
- } else if (t.kind === "t_open" && innerDepth === 0) {
11725
- const closePos = xml.indexOf("</hp:t>", t.end);
11726
- if (closePos >= 0) {
11727
- ownTRuns.push({ isEmpty: false, openEnd: t.end, closePos });
11712
+ function collectBlockTextSlots(blocks) {
11713
+ const slots = [];
11714
+ const walk = (bs) => {
11715
+ for (const b of bs) {
11716
+ if (typeof b.text === "string") {
11717
+ slots.push({
11718
+ get: () => b.text ?? "",
11719
+ set: (v) => {
11720
+ b.text = v;
11728
11721
  }
11729
- }
11730
- }
11731
- const currentText = ownTRuns.map((r) => r.isEmpty ? "" : xml.substring(r.openEnd, r.closePos)).join("").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
11732
- if (edit.expectedText !== void 0 && edit.expectedText !== currentText) {
11733
- results[ei] = {
11734
- success: false,
11735
- oldText: currentText,
11736
- 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.`
11737
- };
11738
- found = true;
11739
- break;
11740
- }
11741
- if (ownTRuns.length === 0) {
11742
- results[ei] = {
11743
- success: false,
11744
- oldText: currentText,
11745
- 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.`
11746
- };
11747
- found = true;
11748
- break;
11749
- }
11750
- const escapedNew = escapeXml(edit.newText);
11751
- const patches = [];
11752
- const firstRun = ownTRuns[0];
11753
- if (firstRun.isEmpty) {
11754
- patches.push({
11755
- from: firstRun.tagPos,
11756
- to: firstRun.tagEnd,
11757
- text: `<hp:t>${escapedNew}</hp:t>`
11758
11722
  });
11759
- } else {
11760
- patches.push({ from: firstRun.openEnd, to: firstRun.closePos, text: escapedNew });
11761
11723
  }
11762
- for (let ri = 1; ri < ownTRuns.length; ri++) {
11763
- const run = ownTRuns[ri];
11764
- if (!run.isEmpty) {
11765
- patches.push({ from: run.openEnd, to: run.closePos, text: "" });
11724
+ if (b.table) {
11725
+ for (const row of b.table.cells) {
11726
+ for (const cell of row) {
11727
+ slots.push({
11728
+ get: () => cell.text,
11729
+ set: (v) => {
11730
+ cell.text = v;
11731
+ }
11732
+ });
11733
+ if (cell.blocks) walk(cell.blocks);
11734
+ }
11766
11735
  }
11767
11736
  }
11768
- replacements.push({ editIdx: ei, patches });
11769
- results[ei] = { success: true, oldText: currentText };
11770
- found = true;
11771
- break;
11737
+ if (b.children) walk(b.children);
11772
11738
  }
11773
- if (!found) {
11774
- results[ei] = {
11775
- success: false,
11776
- 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.`
11777
- };
11778
- }
11779
- }
11780
- if (results.some((r) => !r.success)) {
11781
- return { newXml: xml, results };
11782
- }
11783
- const allPatches = replacements.flatMap((r) => r.patches).sort((a, b) => b.from - a.from);
11784
- let result = xml;
11785
- for (const patch of allPatches) {
11786
- result = result.substring(0, patch.from) + patch.text + result.substring(patch.to);
11787
- }
11788
- return { newXml: result, results };
11739
+ };
11740
+ walk(blocks);
11741
+ return slots;
11789
11742
  }
11790
- async function applyEditsToHwpx(hwpxBuffer, edits) {
11791
- const zip = await import_jszip2.default.loadAsync(hwpxBuffer);
11792
- const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
11793
- const sectionXmls = [];
11794
- const sectionTblCounts = [];
11795
- for (const sf of sectionFiles) {
11796
- const entry = zip.file(sf);
11797
- const xml = entry ? await entry.async("string") : "";
11798
- sectionXmls.push(xml);
11799
- const tokens = tokenizeHwpxXml(xml);
11800
- let count = 0;
11801
- let depth = 0;
11802
- for (const tok of tokens) {
11803
- if (tok.kind === "tbl_open") {
11804
- if (depth === 0) count++;
11805
- depth++;
11806
- } else if (tok.kind === "tbl_close") {
11807
- depth--;
11808
- }
11809
- }
11810
- sectionTblCounts.push(count);
11811
- }
11812
- const sectionEdits = sectionFiles.map(() => []);
11813
- let offset = 0;
11814
- for (let si = 0; si < sectionFiles.length; si++) {
11815
- const count = sectionTblCounts[si] ?? 0;
11816
- for (let ei = 0; ei < edits.length; ei++) {
11817
- const edit = edits[ei];
11818
- if (edit.tableIndex >= offset && edit.tableIndex < offset + count) {
11819
- const secEdits = sectionEdits[si];
11820
- if (secEdits) {
11821
- secEdits.push({
11822
- tableIndex: edit.tableIndex - offset,
11823
- // 섹션 내 상대 인덱스
11824
- row: edit.row,
11825
- col: edit.col,
11826
- newText: edit.newText,
11827
- expectedText: edit.expectedText,
11828
- originalEditIdx: ei
11829
- });
11830
- }
11743
+ function restoreOverStrippedBlocks(blocks, paragraphTexts) {
11744
+ const slots = collectBlockTextSlots(blocks);
11745
+ const used = /* @__PURE__ */ new Set();
11746
+ let changed = false;
11747
+ for (const raw of paragraphTexts) {
11748
+ const buggy = applyStrip(raw, BUGGY_SHAPE_STRIP);
11749
+ const fixed = applyStrip(raw, FIXED_SHAPE_STRIP);
11750
+ if (buggy === fixed) continue;
11751
+ if (!fixed || !buggy) continue;
11752
+ for (let i = 0; i < slots.length; i++) {
11753
+ if (used.has(i)) continue;
11754
+ const cur = slots[i]?.get().trim() ?? "";
11755
+ if (cur === buggy && cur !== fixed) {
11756
+ slots[i]?.set(fixed);
11757
+ used.add(i);
11758
+ changed = true;
11759
+ break;
11831
11760
  }
11832
11761
  }
11833
- offset += count;
11834
11762
  }
11835
- const totalTables = sectionTblCounts.reduce((a, b) => a + b, 0);
11836
- const allResults = edits.map((edit, ei) => ({
11837
- success: false,
11838
- 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.`
11839
- }));
11840
- const newSectionXmls = [...sectionXmls];
11841
- for (let si = 0; si < sectionFiles.length; si++) {
11842
- const sEdits = sectionEdits[si] ?? [];
11843
- if (sEdits.length === 0) continue;
11844
- const srcXml = sectionXmls[si] ?? "";
11845
- const { newXml, results } = applyCellEditsToSectionXml(srcXml, sEdits);
11846
- newSectionXmls[si] = newXml;
11847
- for (let i = 0; i < sEdits.length; i++) {
11848
- const sEdit = sEdits[i];
11849
- const res = results[i];
11850
- if (sEdit && res) {
11851
- allResults[sEdit.originalEditIdx] = res;
11852
- }
11763
+ return changed;
11764
+ }
11765
+ function isZip(bytes) {
11766
+ if (bytes.length < 4) return false;
11767
+ return isZipFile2(new Uint8Array(bytes.subarray(0, 4)).buffer);
11768
+ }
11769
+ async function resolveHwpxBytes(input) {
11770
+ try {
11771
+ if (typeof input === "string") {
11772
+ if (!/\.hwpx$/i.test(input)) return null;
11773
+ return new Uint8Array(await readFile4(input));
11853
11774
  }
11775
+ const u8 = input instanceof Uint8Array ? input : new Uint8Array(input instanceof ArrayBuffer ? input : input);
11776
+ return isZip(u8) ? u8 : null;
11777
+ } catch {
11778
+ return null;
11854
11779
  }
11855
- if (allResults.some((r) => !r.success)) {
11856
- return { buffer: hwpxBuffer, results: allResults };
11857
- }
11858
- const out = new import_jszip2.default();
11859
- const mimetypeEntry = zip.file("mimetype");
11860
- if (mimetypeEntry) {
11861
- out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
11780
+ }
11781
+ async function parse(input, options) {
11782
+ const result = await kordocParse(input, options);
11783
+ if (!result.success || typeof result.markdown !== "string" || !result.markdown) {
11784
+ return result;
11862
11785
  }
11863
- for (const [name, entry] of Object.entries(zip.files)) {
11864
- if (name === "mimetype" || entry.dir) continue;
11865
- const sectionIdx = sectionFiles.indexOf(name);
11866
- if (sectionIdx >= 0) {
11867
- out.file(name, newSectionXmls[sectionIdx] ?? "");
11868
- } else {
11869
- out.file(name, await entry.async("uint8array"));
11786
+ const bytes = await resolveHwpxBytes(input);
11787
+ if (!bytes) return result;
11788
+ try {
11789
+ const paragraphTexts = await extractHwpxParagraphTexts(bytes);
11790
+ if (paragraphTexts.length === 0) return result;
11791
+ const repaired = restoreOverStrippedShapeText(result.markdown, paragraphTexts);
11792
+ if (Array.isArray(result.blocks)) {
11793
+ restoreOverStrippedBlocks(result.blocks, paragraphTexts);
11794
+ }
11795
+ if (repaired !== result.markdown) {
11796
+ return { ...result, markdown: repaired };
11870
11797
  }
11798
+ } catch {
11871
11799
  }
11872
- const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
11873
- return { buffer: new Uint8Array(buf), results: allResults };
11800
+ return result;
11874
11801
  }
11875
- var proposeCellEditTool = {
11876
- name: "propose_cell_edit",
11877
- 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.",
11878
- inputSchema: proposeCellEditSchema,
11802
+ var MAX_PREVIEW_CHARS = 4e3;
11803
+ var exportDocumentSchema = z32.object({
11804
+ path: z32.string().describe("\uBCC0\uD658\uD560 \uC6D0\uBCF8 \uBB38\uC11C \uACBD\uB85C (.hwp/.hwpx/.docx \uB4F1 \u2014 cwd \uAE30\uC900 \uC0C1\uB300/\uC808\uB300 \uACBD\uB85C)"),
11805
+ outputPath: z32.string().describe("\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C \u2014 \uD655\uC7A5\uC790\uB85C \uD615\uC2DD \uACB0\uC815(.html/.htm \uB610\uB294 .pdf)"),
11806
+ summary: z32.string().optional().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
11807
+ });
11808
+ var exportDocumentTool = {
11809
+ name: "export_document",
11810
+ description: "\uBB38\uC11C(.hwp/.hwpx/.docx \uB4F1)\uB97C HTML \uB610\uB294 PDF\uB85C \uB0B4\uBCF4\uB0C5\uB2C8\uB2E4. \uCD9C\uB825 \uACBD\uB85C \uD655\uC7A5\uC790\uB85C \uD615\uC2DD\uC744 \uACB0\uC815\uD569\uB2C8\uB2E4(.html/.htm \uB610\uB294 .pdf). \uC6D0\uBCF8\uC740 \uBCC0\uACBD\uD558\uC9C0 \uC54A\uACE0 \uC0C8 \uD30C\uC77C\uC744 \uB9CC\uB4ED\uB2C8\uB2E4. HTML\uC740 \uD56D\uC0C1 \uAC00\uB2A5\uD558\uBA70, PDF\uB294 puppeteer-core\uAC00 \uC124\uCE58\uB41C \uD658\uACBD\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4(\uBBF8\uC124\uCE58 \uC2DC \uC548\uB0B4). \uBCC0\uACBD \uC0AC\uD56D\uC740 \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
11811
+ inputSchema: exportDocumentSchema,
11879
11812
  requiresApproval: true,
11880
11813
  propose: async ({
11881
11814
  input,
11882
11815
  ctx
11883
11816
  }) => {
11884
11817
  const safePath = await resolveSafePath(ctx.cwd, input.path);
11885
- const ext = extname3(safePath).toLowerCase();
11886
- if (ext !== ".hwpx" && ext !== ".hwp") {
11887
- 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.`;
11888
- }
11889
- let originalBuffer;
11890
- try {
11891
- originalBuffer = await readFile4(safePath);
11892
- } catch {
11893
- 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.`;
11818
+ const outPath = await resolveSafePath(ctx.cwd, input.outputPath);
11819
+ const outExt = extname3(outPath).toLowerCase();
11820
+ const format = outExt === ".html" || outExt === ".htm" ? "html" : outExt === ".pdf" ? "pdf" : null;
11821
+ if (format === null) {
11822
+ return `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uCD9C\uB825 \uD615\uC2DD\uC785\uB2C8\uB2E4: ${outExt || "(\uD655\uC7A5\uC790 \uC5C6\uC74C)"}. \uCD9C\uB825 \uACBD\uB85C\uB97C .html \uB610\uB294 .pdf \uB85C \uC9C0\uC815\uD558\uC138\uC694.`;
11894
11823
  }
11895
- const originalBytes = new Uint8Array(originalBuffer.buffer);
11896
- const structuralGuard = hwpStructuralGuard(ext, originalBytes);
11897
- if (structuralGuard !== null) {
11898
- return structuralGuard;
11899
- }
11900
- if (originalBuffer[0] !== 80 || originalBuffer[1] !== 75) {
11901
- 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.";
11902
- }
11903
- const zipForLabel = await import_jszip2.default.loadAsync(new Uint8Array(originalBuffer.buffer));
11904
- const sectionFilesForLabel = Object.keys(zipForLabel.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
11905
- const sectionInfos = [];
11906
- let globalTblOffset = 0;
11907
- for (const sf of sectionFilesForLabel) {
11908
- const entry = zipForLabel.file(sf);
11909
- const xml = entry ? await entry.async("string") : "";
11910
- const tokens = tokenizeHwpxXml(xml);
11911
- let count = 0;
11912
- let d = 0;
11913
- for (const tok of tokens) {
11914
- if (tok.kind === "tbl_open") {
11915
- if (d === 0) count++;
11916
- d++;
11917
- } else if (tok.kind === "tbl_close") {
11918
- d--;
11919
- }
11920
- }
11921
- sectionInfos.push({ xml, tblCount: count, globalOffset: globalTblOffset });
11922
- globalTblOffset += count;
11923
- }
11924
- function resolveLabelAcrossSections(label, direction, scopedTableIndex) {
11925
- const trimmedLabel = label.trim();
11926
- const allMatches = [];
11927
- for (const si of sectionInfos) {
11928
- const tokens = tokenizeHwpxXml(si.xml);
11929
- let localStart = 0;
11930
- let localEnd = si.tblCount - 1;
11931
- if (scopedTableIndex !== void 0) {
11932
- const localIdx2 = scopedTableIndex - si.globalOffset;
11933
- if (localIdx2 < 0 || localIdx2 >= si.tblCount) continue;
11934
- localStart = localIdx2;
11935
- localEnd = localIdx2;
11936
- }
11937
- for (let li = localStart; li <= localEnd; li++) {
11938
- const tblRange = findTopLevelTableRange(tokens, li);
11939
- if (!tblRange) continue;
11940
- const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
11941
- for (const tc of directTcs) {
11942
- const cellText = readOwnTextFromTc(si.xml, tokens, tc.start, tc.end).trim();
11943
- if (cellText === trimmedLabel) {
11944
- const addrSpan2 = readCellAddrSpan(tokens, tc.start, tc.end);
11945
- if (addrSpan2) {
11946
- allMatches.push({
11947
- globalTableIndex: si.globalOffset + li,
11948
- addrSpan: addrSpan2,
11949
- sectionXml: si.xml,
11950
- sectionOffset: si.globalOffset
11951
- });
11952
- }
11953
- }
11954
- }
11955
- }
11956
- }
11957
- if (allMatches.length === 0) {
11958
- const scope = scopedTableIndex !== void 0 ? `\uD45C ${scopedTableIndex}` : "\uBB38\uC11C \uB0B4 \uBAA8\uB4E0 \uD45C";
11959
- return {
11960
- 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.`
11961
- };
11962
- }
11963
- if (allMatches.length > 1) {
11964
- const locs = allMatches.map(
11965
- (m) => `\uD45C ${m.globalTableIndex} (\uD589 ${m.addrSpan.rowAddr}, \uC5F4 ${m.addrSpan.colAddr})`
11966
- ).join(", ");
11967
- return {
11968
- 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.`
11969
- };
11970
- }
11971
- const match = allMatches[0];
11972
- const { addrSpan, globalTableIndex, sectionXml, sectionOffset } = match;
11973
- let targetRow;
11974
- let targetCol;
11975
- if (direction === "right") {
11976
- targetRow = addrSpan.rowAddr;
11977
- targetCol = addrSpan.colAddr + addrSpan.colSpan;
11978
- } else {
11979
- targetRow = addrSpan.rowAddr + addrSpan.rowSpan;
11980
- targetCol = addrSpan.colAddr;
11981
- }
11982
- const localIdx = globalTableIndex - sectionOffset;
11983
- const tokens2 = tokenizeHwpxXml(sectionXml);
11984
- const tblRange2 = findTopLevelTableRange(tokens2, localIdx);
11985
- if (!tblRange2) {
11986
- return { error: `\uD45C ${globalTableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958).` };
11987
- }
11988
- const directTcs2 = collectDirectTcRanges(tokens2, tblRange2.start, tblRange2.end);
11989
- const tblTokens2 = tokens2.filter((t) => t.pos >= tblRange2.start && t.pos < tblRange2.end);
11990
- let targetExists = false;
11991
- for (const tc of directTcs2) {
11992
- const tcTokens = tblTokens2.filter((t) => t.pos >= tc.start && t.pos < tc.end);
11993
- if (tcTokens.some(
11994
- (t) => t.kind === "cell_addr" && t.colAddr === targetCol && t.rowAddr === targetRow
11995
- )) {
11996
- targetExists = true;
11997
- break;
11998
- }
11999
- }
12000
- if (!targetExists) {
12001
- const dirLabel = direction === "right" ? "\uC624\uB978\uCABD" : "\uC544\uB798";
12002
- return {
12003
- 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.`
12004
- };
12005
- }
12006
- return { tableIndex: globalTableIndex, row: targetRow, col: targetCol };
11824
+ const parseResult = await parse(safePath);
11825
+ if (!parseResult.success) {
11826
+ const msg = kordocErrorMessage(
11827
+ parseResult.code,
11828
+ `\uC6D0\uBCF8 \uBB38\uC11C\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${parseResult.error}`
11829
+ );
11830
+ return `\uC624\uB958: ${msg}`;
12007
11831
  }
12008
- const resolvedEdits = [];
12009
- const resolveErrors = [];
12010
- for (let i = 0; i < input.edits.length; i++) {
12011
- const e = input.edits[i];
12012
- if (!e) continue;
12013
- if (e.label !== void 0 && (e.row !== void 0 || e.col !== void 0)) {
12014
- resolveErrors.push(
12015
- `\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.`
12016
- );
12017
- } else if (e.label !== void 0) {
12018
- const direction = e.direction ?? "right";
12019
- const resolved = resolveLabelAcrossSections(e.label, direction, e.tableIndex);
12020
- if ("error" in resolved) {
12021
- resolveErrors.push(`\uD3B8\uC9D1 #${i + 1} (\uB808\uC774\uBE14 "${e.label}"): ${resolved.error}`);
12022
- } else {
12023
- resolvedEdits.push({
12024
- tableIndex: resolved.tableIndex,
12025
- row: resolved.row,
12026
- col: resolved.col,
12027
- newText: e.newText,
12028
- expectedText: e.expectedText,
12029
- label: e.label
12030
- });
11832
+ const markdown = parseResult.markdown;
11833
+ let stagedData;
11834
+ const warnings = [];
11835
+ if (format === "html") {
11836
+ stagedData = new TextEncoder().encode(renderHtml(markdown));
11837
+ } else {
11838
+ try {
11839
+ const pdf = await markdownToPdf(markdown);
11840
+ stagedData = new Uint8Array(pdf);
11841
+ } catch (err) {
11842
+ const m = err instanceof Error ? err.message : String(err);
11843
+ if (/puppeteer/i.test(m)) {
11844
+ return "\uC624\uB958: PDF \uB0B4\uBCF4\uB0B4\uAE30\uC5D0\uB294 puppeteer-core\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4(\uC774 \uD658\uACBD\uC5D0 \uBBF8\uC124\uCE58). \uD574\uACB0: (1) \uCD9C\uB825 \uACBD\uB85C\uB97C .html \uB85C \uC9C0\uC815\uD574 HTML\uB85C \uB0B4\uBCF4\uB0B4\uAC70\uB098, (2) `npm install -g puppeteer-core` \uD6C4 Chrome/Chromium \uC2E4\uD589 \uD30C\uC77C \uACBD\uB85C\uB97C \uC124\uC815\uD558\uC138\uC694.";
12031
11845
  }
12032
- } else if (e.tableIndex !== void 0 && e.row !== void 0 && e.col !== void 0) {
12033
- resolvedEdits.push({
12034
- tableIndex: e.tableIndex,
12035
- row: e.row,
12036
- col: e.col,
12037
- newText: e.newText,
12038
- expectedText: e.expectedText
12039
- });
12040
- } else {
12041
- resolveErrors.push(
12042
- `\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.`
12043
- );
11846
+ return `\uC624\uB958: PDF \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${m}`;
12044
11847
  }
12045
11848
  }
12046
- if (resolveErrors.length > 0) {
12047
- 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.
12048
- ${resolveErrors.join("\n")}`;
12049
- }
12050
- const editRequests = resolvedEdits.map((e) => ({
12051
- tableIndex: e.tableIndex,
12052
- row: e.row,
12053
- col: e.col,
12054
- newText: e.newText,
12055
- expectedText: e.expectedText
12056
- }));
12057
- const { buffer: newBuffer, results } = await applyEditsToHwpx(
12058
- new Uint8Array(originalBuffer.buffer),
12059
- editRequests
12060
- );
12061
- const failedResults = results.map((r, i) => ({ r, i })).filter(({ r }) => !r.success);
12062
- if (failedResults.length > 0) {
12063
- const messages = failedResults.map(({ r, i }) => {
12064
- const e = resolvedEdits[i];
12065
- const label = e?.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 ` : "";
12066
- return `\uD3B8\uC9D1 #${i + 1} (${label}\uD45C ${e?.tableIndex ?? "?"}, \uD589 ${e?.row ?? "?"}, \uC5F4 ${e?.col ?? "?"}): ${r.error}`;
12067
- });
12068
- 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.
12069
- ${messages.join("\n")}`;
12070
- }
12071
- const diffLines = ["| \uD45C\xB7\uC140 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12072
- for (let i = 0; i < resolvedEdits.length; i++) {
12073
- const e = resolvedEdits[i];
12074
- if (!e) continue;
12075
- const oldText = results[i]?.oldText ?? "";
12076
- const addr = e.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 #${e.tableIndex} (${e.row},${e.col})` : `#${e.tableIndex} (${e.row},${e.col})`;
12077
- diffLines.push(`| ${addr} | ${oldText} | ${e.newText} |`);
12078
- }
12079
- const diff = diffLines.join("\n");
12080
- const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
12081
- const stagedPath = await stageFile(ctx.sessionId, safePath, newBuffer);
11849
+ const stagedPath = await stageFile(ctx.sessionId, outPath, stagedData);
11850
+ const preview = format === "html" ? `[HTML \uB0B4\uBCF4\uB0B4\uAE30 \uBBF8\uB9AC\uBCF4\uAE30 \u2014 \uBCF8\uBB38 \uD14D\uC2A4\uD2B8]
11851
+
11852
+ ${markdown.length > MAX_PREVIEW_CHARS ? `${markdown.slice(0, MAX_PREVIEW_CHARS)}
11853
+
11854
+ ...\uC774\uD558 \uC0DD\uB7B5` : markdown}` : `[PDF \uB0B4\uBCF4\uB0B4\uAE30] ${input.path} \u2192 ${input.outputPath} (${stagedData.byteLength.toLocaleString()} bytes)`;
12082
11855
  const proposalId = crypto.randomUUID();
12083
11856
  return {
12084
11857
  proposal: {
12085
11858
  id: proposalId,
12086
- kind: "cell-edit",
12087
- targetPath: outputPath,
11859
+ kind: "export",
11860
+ targetPath: outPath,
12088
11861
  stagedPath,
12089
- summary: input.summary,
12090
- diff,
12091
- warnings: [],
12092
- willConvertFormat
11862
+ summary: input.summary ?? `${input.path} \u2192 ${format.toUpperCase()} \uB0B4\uBCF4\uB0B4\uAE30`,
11863
+ diff: preview,
11864
+ warnings
12093
11865
  },
12094
11866
  commit: async () => {
12095
- const backupPath = await backupFile(safePath);
12096
- await commitStaged(stagedPath, outputPath);
12097
- const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
12098
- return `\uC800\uC7A5 \uC644\uB8CC: ${outputPath}${backupInfo}`;
11867
+ const backupPath = await backupFile(outPath);
11868
+ await commitStaged(stagedPath, outPath);
11869
+ const backupInfo = backupPath ? ` (\uAE30\uC874 \uD30C\uC77C \uBC31\uC5C5: ${backupPath})` : "";
11870
+ return `\uB0B4\uBCF4\uB0B4\uAE30 \uC644\uB8CC: ${outPath}${backupInfo}`;
12099
11871
  }
12100
11872
  };
12101
11873
  }
@@ -12105,9 +11877,6 @@ var findInDocumentSchema = z4.object({
12105
11877
  query: z4.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
12106
11878
  caseSensitive: z4.boolean().optional().describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8 false)")
12107
11879
  });
12108
- function unescapeXml(text3) {
12109
- return text3.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
12110
- }
12111
11880
  function windowText(text3, query, caseSensitive, maxLen = 60) {
12112
11881
  const compare3 = caseSensitive ? text3 : text3.toLowerCase();
12113
11882
  const compareQuery = caseSensitive ? query : query.toLowerCase();
@@ -12122,91 +11891,31 @@ function windowText(text3, query, caseSensitive, maxLen = 60) {
12122
11891
  }
12123
11892
  function findInSectionXmls(xmls, query, caseSensitive) {
12124
11893
  const hits = [];
11894
+ const needle = caseSensitive ? query : query.toLowerCase();
11895
+ const includes = (text3) => (caseSensitive ? text3 : text3.toLowerCase()).includes(needle);
12125
11896
  let globalOffset = 0;
12126
11897
  for (let si = 0; si < xmls.length; si++) {
12127
11898
  const xml = xmls[si] ?? "";
12128
- const tokens = tokenizeHwpxXml(xml);
12129
- let localCount = 0;
12130
- {
12131
- let depth = 0;
12132
- for (const tok of tokens) {
12133
- if (tok.kind === "tbl_open") {
12134
- if (depth === 0) localCount++;
12135
- depth++;
12136
- } else if (tok.kind === "tbl_close") {
12137
- depth--;
12138
- }
12139
- }
12140
- }
12141
- for (let ti = 0; ti < localCount; ti++) {
12142
- const range = findTopLevelTableRange(tokens, ti);
12143
- if (!range) continue;
12144
- const cells = collectDirectTcRanges(tokens, range.start, range.end);
12145
- const tblTokens = tokens.filter((t) => t.pos >= range.start && t.pos < range.end);
12146
- for (const cell of cells) {
12147
- const cellText = readOwnTextFromTc(xml, tokens, cell.start, cell.end);
12148
- const compare3 = caseSensitive ? cellText : cellText.toLowerCase();
12149
- const compareQuery = caseSensitive ? query : query.toLowerCase();
12150
- if (!compare3.includes(compareQuery)) continue;
12151
- const cellTokens = tblTokens.filter((t) => t.pos >= cell.start && t.pos < cell.end);
12152
- const addrTok = cellTokens.find((t) => t.kind === "cell_addr");
12153
- if (addrTok === void 0) continue;
12154
- const row = addrTok.rowAddr ?? 0;
12155
- const col = addrTok.colAddr ?? 0;
11899
+ const scan = scanSectionXml(xml, 0);
11900
+ for (let ti = 0; ti < scan.tables.length; ti++) {
11901
+ const table = scan.tables[ti];
11902
+ if (table === void 0) continue;
11903
+ for (const [key, cell] of table.cellByAnchor) {
11904
+ const cellText = cell.paragraphs.map((p) => p.text).join("");
11905
+ if (!includes(cellText)) continue;
11906
+ const parts = key.split(",");
11907
+ const row = Number(parts[0]);
11908
+ const col = Number(parts[1]);
12156
11909
  const truncated = cellText.length > 60 ? `${cellText.slice(0, 57)}\u2026` : cellText;
12157
- hits.push({
12158
- kind: "\uD45C",
12159
- tableIndex: globalOffset + ti,
12160
- row,
12161
- col,
12162
- text: truncated
12163
- });
11910
+ hits.push({ kind: "\uD45C", tableIndex: globalOffset + ti, row, col, text: truncated });
12164
11911
  }
12165
11912
  }
12166
- {
12167
- const bodySegments = [];
12168
- let tblDepth = 0;
12169
- let segStart = 0;
12170
- for (const tok of tokens) {
12171
- if (tok.kind === "tbl_open") {
12172
- if (tblDepth === 0) {
12173
- if (tok.pos > segStart) {
12174
- bodySegments.push({ start: segStart, end: tok.pos });
12175
- }
12176
- }
12177
- tblDepth++;
12178
- } else if (tok.kind === "tbl_close") {
12179
- tblDepth--;
12180
- if (tblDepth === 0) {
12181
- segStart = tok.end;
12182
- }
12183
- }
12184
- }
12185
- if (segStart < xml.length) {
12186
- bodySegments.push({ start: segStart, end: xml.length });
12187
- }
12188
- const tRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
12189
- for (const seg of bodySegments) {
12190
- const slice = xml.slice(seg.start, seg.end);
12191
- tRe.lastIndex = 0;
12192
- let m = tRe.exec(slice);
12193
- while (m !== null) {
12194
- const rawText = m[1] ?? "";
12195
- const decodedText = unescapeXml(rawText);
12196
- const compare3 = caseSensitive ? decodedText : decodedText.toLowerCase();
12197
- const compareQuery = caseSensitive ? query : query.toLowerCase();
12198
- if (compare3.includes(compareQuery)) {
12199
- hits.push({
12200
- kind: "\uBCF8\uBB38",
12201
- section: si,
12202
- text: windowText(decodedText, query, caseSensitive)
12203
- });
12204
- }
12205
- m = tRe.exec(slice);
12206
- }
11913
+ for (const p of [...scan.bodyParagraphs, ...scan.excludedParagraphs]) {
11914
+ if (p.text.length > 0 && includes(p.text)) {
11915
+ hits.push({ kind: "\uBCF8\uBB38", section: si, text: windowText(p.text, query, caseSensitive) });
12207
11916
  }
12208
11917
  }
12209
- globalOffset += localCount;
11918
+ globalOffset += scan.tables.length;
12210
11919
  }
12211
11920
  return hits;
12212
11921
  }
@@ -12250,12 +11959,12 @@ var findInDocumentTool = {
12250
11959
  } catch {
12251
11960
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
12252
11961
  }
12253
- if (bytes[0] !== 80 || bytes[1] !== 75) {
11962
+ if (!isZipBinary(bytes)) {
12254
11963
  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.";
12255
11964
  }
12256
11965
  let zip;
12257
11966
  try {
12258
- zip = await import_jszip.default.loadAsync(bytes);
11967
+ zip = await import_jszip2.default.loadAsync(bytes);
12259
11968
  } catch (err) {
12260
11969
  return `\uC624\uB958: .hwpx ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${String(err)}`;
12261
11970
  }
@@ -12286,7 +11995,7 @@ var findInDocumentTool = {
12286
11995
  return formatHits(hits, input.query);
12287
11996
  }
12288
11997
  };
12289
- function escapeXml2(text3) {
11998
+ function escapeXml(text3) {
12290
11999
  return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
12291
12000
  }
12292
12001
  function decodeXml(text3) {
@@ -12438,7 +12147,7 @@ function applyFormObjectEdits(xml, edits) {
12438
12147
  }
12439
12148
  let patchCreated = false;
12440
12149
  if (set.caption !== void 0) {
12441
- const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "caption", escapeXml2(set.caption));
12150
+ const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "caption", escapeXml(set.caption));
12442
12151
  if (!patch) {
12443
12152
  results[ei] = {
12444
12153
  success: false,
@@ -12466,7 +12175,7 @@ function applyFormObjectEdits(xml, edits) {
12466
12175
  pos,
12467
12176
  openTagEnd,
12468
12177
  "selectedValue",
12469
- escapeXml2(set.selected)
12178
+ escapeXml(set.selected)
12470
12179
  );
12471
12180
  if (!patch) {
12472
12181
  results[ei] = {
@@ -12592,7 +12301,7 @@ function replaceAttrInOpenTag(xml, tagStart, tagEnd, attr, newValue) {
12592
12301
  };
12593
12302
  }
12594
12303
  function replaceEditText(_xml, pos, elementContent, newText) {
12595
- const escaped = escapeXml2(newText);
12304
+ const escaped = escapeXml(newText);
12596
12305
  const selfCloseRe = /<hp:text\s*\/>/;
12597
12306
  const scm = selfCloseRe.exec(elementContent);
12598
12307
  if (scm) {
@@ -12639,7 +12348,7 @@ function validateHwpxBuffer(ext, buffer) {
12639
12348
  if (structuralGuard !== null) {
12640
12349
  return structuralGuard;
12641
12350
  }
12642
- if (buffer[0] !== 80 || buffer[1] !== 75) {
12351
+ if (!isZipBinary(buffer)) {
12643
12352
  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.";
12644
12353
  }
12645
12354
  return null;
@@ -12996,146 +12705,377 @@ var listFilesTool = {
12996
12705
  return lines.join("\n") + truncateNotice;
12997
12706
  }
12998
12707
  };
12999
- var SHAPE_ALT_KEYWORDS = [
13000
- "\uC0AC\uAC01\uD615",
13001
- "\uC9C1\uC0AC\uAC01\uD615",
13002
- "\uC815\uC0AC\uAC01\uD615",
13003
- "\uC6D0",
13004
- "\uD0C0\uC6D0",
13005
- "\uC0BC\uAC01\uD615",
13006
- "\uC774\uB4F1\uBCC0 \uC0BC\uAC01\uD615",
13007
- "\uC9C1\uAC01 \uC0BC\uAC01\uD615",
13008
- "\uC120",
13009
- "\uC9C1\uC120",
13010
- "\uACE1\uC120",
13011
- "\uD654\uC0B4\uD45C",
13012
- "\uAD75\uC740 \uD654\uC0B4\uD45C",
13013
- "\uC774\uC911 \uD654\uC0B4\uD45C",
13014
- "\uC624\uAC01\uD615",
13015
- "\uC721\uAC01\uD615",
13016
- "\uD314\uAC01\uD615",
13017
- "\uBCC4",
13018
- "[4-8]\uC810\uBCC4",
13019
- "\uC2ED\uC790",
13020
- "\uC2ED\uC790\uD615",
13021
- "\uAD6C\uB984",
13022
- "\uAD6C\uB984\uD615",
13023
- "\uB9C8\uB984\uBAA8",
13024
- "\uB3C4\uB11B",
13025
- "\uD3C9\uD589\uC0AC\uBCC0\uD615",
13026
- "\uC0AC\uB2E4\uB9AC\uAF34",
13027
- "\uBD80\uCC44\uAF34",
13028
- "\uD638",
13029
- "\uBC18\uC6D0",
13030
- "\uBB3C\uACB0",
13031
- "\uBC88\uAC1C",
13032
- "\uD558\uD2B8",
13033
- "\uBE57\uAE08",
13034
- "\uBE14\uB85D \uD654\uC0B4\uD45C",
13035
- "\uC218\uC2DD",
13036
- "\uD45C",
13037
- "\uADF8\uB9BC",
13038
- "\uAC1C\uCCB4",
13039
- "\uADF8\uB9AC\uAE30\\s?\uAC1C\uCCB4",
13040
- "\uBB36\uC74C\\s?\uAC1C\uCCB4",
13041
- "\uAE00\uC0C1\uC790",
13042
- "\uC218\uC2DD\\s?\uAC1C\uCCB4",
13043
- "OLE\\s?\uAC1C\uCCB4"
13044
- ].join("|");
13045
- var BUGGY_SHAPE_STRIP = new RegExp(
13046
- `(?:\uBAA8\uC11C\uB9AC\uAC00 \uB465\uADFC |\uB465\uADFC )?(?:${SHAPE_ALT_KEYWORDS})\\s?\uC785\uB2C8\uB2E4\\.?`,
13047
- "g"
13048
- );
13049
- var FIXED_SHAPE_STRIP = new RegExp(
13050
- `(?<![\uAC00-\uD7A3])(?:\uBAA8\uC11C\uB9AC\uAC00 \uB465\uADFC |\uB465\uADFC )?(?:${SHAPE_ALT_KEYWORDS})\\s?\uC785\uB2C8\uB2E4\\.?`,
13051
- "g"
12708
+ var cellEditItemSchema = z7.object({
12709
+ tableIndex: z7.number().int().nonnegative().optional().describe(
12710
+ "\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)."
12711
+ ),
12712
+ row: z7.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 rowAddr (0-based)"),
12713
+ col: z7.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 colAddr (0-based)"),
12714
+ label: z7.string().optional().describe(
12715
+ "\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."
12716
+ ),
12717
+ direction: z7.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."),
12718
+ newText: z7.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
12719
+ expectedText: z7.string().optional().describe(
12720
+ "\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."
12721
+ )
12722
+ }).describe(
12723
+ "\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."
13052
12724
  );
13053
- function applyStrip(raw, re) {
13054
- const normalized = raw.replace(/[ \t]+/g, " ").trim();
13055
- return normalized.replace(re, "").trim();
12725
+ var proposeCellEditSchema = z7.object({
12726
+ 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)"),
12727
+ edits: z7.array(cellEditItemSchema).min(1).describe(
12728
+ "\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"
12729
+ ),
12730
+ summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12731
+ });
12732
+ function cellOwnText(cell) {
12733
+ return cell.paragraphs.map((p) => p.text).join("");
13056
12734
  }
13057
- function decodeXmlEntities(s) {
13058
- return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(Number.parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number.parseInt(d, 10))).replace(/&amp;/g, "&");
12735
+ function cellAt(table, row, col) {
12736
+ return table.cellByAnchor.get(`${row},${col}`);
13059
12737
  }
13060
- async function extractHwpxParagraphTexts(bytes) {
13061
- const zip = await import_jszip4.default.loadAsync(bytes);
12738
+ function buildCellWriteSplices(cell, newText) {
12739
+ const paras = cell.paragraphs;
12740
+ if (paras.length === 0) return null;
12741
+ const firstParagraph = paras[0];
12742
+ if (firstParagraph === void 0) return null;
12743
+ const first = buildParagraphSplices(firstParagraph, newText, void 0);
12744
+ if (first === null) return null;
12745
+ const splices = [...first];
12746
+ for (let i = 1; i < paras.length; i++) {
12747
+ const para = paras[i];
12748
+ if (para === void 0) continue;
12749
+ const clear = buildParagraphSplices(para, "", void 0);
12750
+ if (clear !== null) splices.push(...clear);
12751
+ }
12752
+ return splices;
12753
+ }
12754
+ function applyCellEditsToSectionXml(xml, edits) {
12755
+ const scan = scanSectionXml2(xml, 0);
12756
+ const tables = scan.tables;
12757
+ const results = edits.map(() => ({ success: false }));
12758
+ const allSplices = [];
12759
+ for (let ei = 0; ei < edits.length; ei++) {
12760
+ const edit = edits[ei];
12761
+ if (edit === void 0) continue;
12762
+ const table = tables[edit.tableIndex];
12763
+ if (table === void 0) {
12764
+ results[ei] = {
12765
+ success: false,
12766
+ error: `\uD45C ${edit.tableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774 \uC139\uC158\uC5D0 \uCD1D ${tables.length}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
12767
+ };
12768
+ continue;
12769
+ }
12770
+ const cell = cellAt(table, edit.row, edit.col);
12771
+ if (cell === void 0) {
12772
+ results[ei] = {
12773
+ success: false,
12774
+ 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.`
12775
+ };
12776
+ continue;
12777
+ }
12778
+ const currentText = cellOwnText(cell);
12779
+ if (edit.expectedText !== void 0 && edit.expectedText !== currentText) {
12780
+ results[ei] = {
12781
+ success: false,
12782
+ oldText: currentText,
12783
+ 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.`
12784
+ };
12785
+ continue;
12786
+ }
12787
+ const splices = buildCellWriteSplices(cell, edit.newText);
12788
+ if (splices === null) {
12789
+ results[ei] = {
12790
+ success: false,
12791
+ oldText: currentText,
12792
+ error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC5D0 \uD14D\uC2A4\uD2B8 \uB7F0\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD3B8\uC9D1 \uAC00\uB2A5\uD55C \uBB38\uB2E8/\uB7F0\uC774 \uC5C6\uC5B4 \uAC12\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC140 \uAD6C\uC870\uB97C \uD655\uC778\uD558\uC138\uC694.`
12793
+ };
12794
+ continue;
12795
+ }
12796
+ allSplices.push(...splices);
12797
+ results[ei] = { success: true, oldText: currentText };
12798
+ }
12799
+ if (results.some((r) => !r.success)) {
12800
+ return { newXml: xml, results };
12801
+ }
12802
+ return { newXml: applySplices(xml, allSplices), results };
12803
+ }
12804
+ function parseAnchor(key) {
12805
+ const [r, c] = key.split(",");
12806
+ return { row: Number(r), col: Number(c) };
12807
+ }
12808
+ function findLabelMatchesInScan(tables, trimmedLabel, startIdx, endIdx) {
13062
12809
  const out = [];
13063
- for (const name of Object.keys(zip.files)) {
13064
- if (!/^Contents\/section\d*\.xml$/i.test(name)) continue;
13065
- const entry = zip.files[name];
13066
- if (!entry) continue;
13067
- const xml = await entry.async("string");
13068
- for (const seg of xml.split(/(?=<hp:p\b)/)) {
13069
- let para = "";
13070
- for (const m of seg.matchAll(/<hp:t(?:\s[^>]*)?>([\s\S]*?)<\/hp:t>/g)) {
13071
- para += decodeXmlEntities((m[1] ?? "").replace(/<[^>]+>/g, ""));
12810
+ for (let ti = startIdx; ti <= endIdx; ti++) {
12811
+ const table = tables[ti];
12812
+ if (table === void 0) continue;
12813
+ for (const [key, cell] of table.cellByAnchor) {
12814
+ if (cellOwnText(cell).trim() === trimmedLabel) {
12815
+ const { row, col } = parseAnchor(key);
12816
+ out.push({ tableIndex: ti, row, col, colSpan: cell.colSpan, rowSpan: cell.rowSpan });
13072
12817
  }
13073
- if (para.trim()) out.push(para);
13074
12818
  }
13075
- }
13076
- return out;
13077
- }
13078
- function splitDecoration(line) {
13079
- const m = line.match(/^(\s*(?:#{1,6}\s+|[-*+]\s+|\d+\.\s+|>\s+)?)([\s\S]*)$/);
13080
- if (!m) return { prefix: "", content: line };
13081
- return { prefix: m[1] ?? "", content: m[2] ?? "" };
13082
- }
13083
- function restoreOverStrippedShapeText(markdown, paragraphTexts) {
13084
- const lines = markdown.split("\n");
13085
- const used = /* @__PURE__ */ new Set();
13086
- let changed = false;
13087
- for (const raw of paragraphTexts) {
13088
- const buggy = applyStrip(raw, BUGGY_SHAPE_STRIP);
13089
- const fixed = applyStrip(raw, FIXED_SHAPE_STRIP);
13090
- if (buggy === fixed) continue;
13091
- if (!fixed) continue;
13092
- if (!buggy) continue;
13093
- for (let i = 0; i < lines.length; i++) {
13094
- if (used.has(i)) continue;
13095
- const { prefix, content } = splitDecoration(lines[i] ?? "");
13096
- if (content.trim() === buggy && content.trim() !== fixed) {
13097
- lines[i] = prefix + fixed;
13098
- used.add(i);
13099
- changed = true;
13100
- break;
12819
+ }
12820
+ return out;
12821
+ }
12822
+ function targetFromLabel(m, direction) {
12823
+ return direction === "right" ? { row: m.row, col: m.col + m.colSpan } : { row: m.row + m.rowSpan, col: m.col };
12824
+ }
12825
+ var SECTION_RE = /^Contents\/section\d+\.xml$/;
12826
+ async function readSections(zip) {
12827
+ const sectionFiles = Object.keys(zip.files).filter((name) => SECTION_RE.test(name)).sort();
12828
+ const out = [];
12829
+ let globalOffset = 0;
12830
+ for (const name of sectionFiles) {
12831
+ const entry = zip.file(name);
12832
+ const xml = entry ? await entry.async("string") : "";
12833
+ const tblCount = scanSectionXml2(xml, 0).tables.length;
12834
+ out.push({ name, xml, tblCount, globalOffset });
12835
+ globalOffset += tblCount;
12836
+ }
12837
+ return out;
12838
+ }
12839
+ async function applyEditsToHwpx(hwpxBuffer, edits) {
12840
+ const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
12841
+ const sections = await readSections(zip);
12842
+ const totalTables = sections.reduce((a, s) => a + s.tblCount, 0);
12843
+ const sectionEdits = sections.map(() => []);
12844
+ for (let ei = 0; ei < edits.length; ei++) {
12845
+ const edit = edits[ei];
12846
+ if (edit === void 0) continue;
12847
+ for (let si = 0; si < sections.length; si++) {
12848
+ const s = sections[si];
12849
+ if (s === void 0) continue;
12850
+ if (edit.tableIndex >= s.globalOffset && edit.tableIndex < s.globalOffset + s.tblCount) {
12851
+ sectionEdits[si]?.push({
12852
+ tableIndex: edit.tableIndex - s.globalOffset,
12853
+ row: edit.row,
12854
+ col: edit.col,
12855
+ newText: edit.newText,
12856
+ expectedText: edit.expectedText,
12857
+ originalEditIdx: ei
12858
+ });
12859
+ break;
12860
+ }
12861
+ }
12862
+ }
12863
+ const allResults = edits.map((edit, ei) => ({
12864
+ success: false,
12865
+ 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.`
12866
+ }));
12867
+ const newSectionXmls = sections.map((s) => s.xml);
12868
+ for (let si = 0; si < sections.length; si++) {
12869
+ const sEdits = sectionEdits[si] ?? [];
12870
+ if (sEdits.length === 0) continue;
12871
+ const srcXml = sections[si]?.xml ?? "";
12872
+ const { newXml, results } = applyCellEditsToSectionXml(srcXml, sEdits);
12873
+ newSectionXmls[si] = newXml;
12874
+ for (let i = 0; i < sEdits.length; i++) {
12875
+ const sEdit = sEdits[i];
12876
+ const res = results[i];
12877
+ if (sEdit && res) allResults[sEdit.originalEditIdx] = res;
12878
+ }
12879
+ }
12880
+ if (allResults.some((r) => !r.success)) {
12881
+ return { buffer: hwpxBuffer, results: allResults };
12882
+ }
12883
+ const out = new import_jszip4.default();
12884
+ const mimetypeEntry = zip.file("mimetype");
12885
+ if (mimetypeEntry) {
12886
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
12887
+ }
12888
+ const nameToIdx = new Map(sections.map((s, i) => [s.name, i]));
12889
+ for (const [name, entry] of Object.entries(zip.files)) {
12890
+ if (name === "mimetype" || entry.dir) continue;
12891
+ const idx = nameToIdx.get(name);
12892
+ if (idx !== void 0) {
12893
+ out.file(name, newSectionXmls[idx] ?? "");
12894
+ } else {
12895
+ out.file(name, await entry.async("uint8array"));
12896
+ }
12897
+ }
12898
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12899
+ return { buffer: new Uint8Array(buf), results: allResults };
12900
+ }
12901
+ function resolveLabelAcrossSections(sections, label, direction, scopedGlobalTableIndex) {
12902
+ const trimmedLabel = label.trim();
12903
+ const matches = [];
12904
+ for (const s of sections) {
12905
+ const tables = scanSectionXml2(s.xml, 0).tables;
12906
+ let localStart = 0;
12907
+ let localEnd = s.tblCount - 1;
12908
+ if (scopedGlobalTableIndex !== void 0) {
12909
+ const localIdx2 = scopedGlobalTableIndex - s.globalOffset;
12910
+ if (localIdx2 < 0 || localIdx2 >= s.tblCount) continue;
12911
+ localStart = localIdx2;
12912
+ localEnd = localIdx2;
12913
+ }
12914
+ for (const m of findLabelMatchesInScan(tables, trimmedLabel, localStart, localEnd)) {
12915
+ matches.push({
12916
+ globalTableIndex: s.globalOffset + m.tableIndex,
12917
+ row: m.row,
12918
+ col: m.col,
12919
+ colSpan: m.colSpan,
12920
+ rowSpan: m.rowSpan
12921
+ });
12922
+ }
12923
+ }
12924
+ if (matches.length === 0) {
12925
+ const scope = scopedGlobalTableIndex !== void 0 ? `\uD45C ${scopedGlobalTableIndex}` : "\uBB38\uC11C \uB0B4 \uBAA8\uB4E0 \uD45C";
12926
+ return {
12927
+ 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.`
12928
+ };
12929
+ }
12930
+ if (matches.length > 1) {
12931
+ const locs = matches.map((m) => `\uD45C ${m.globalTableIndex} (\uD589 ${m.row}, \uC5F4 ${m.col})`).join(", ");
12932
+ return {
12933
+ 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.`
12934
+ };
12935
+ }
12936
+ const match = matches[0];
12937
+ const target = targetFromLabel(match, direction);
12938
+ const section = sections.find(
12939
+ (s) => match.globalTableIndex >= s.globalOffset && match.globalTableIndex < s.globalOffset + s.tblCount
12940
+ );
12941
+ if (section === void 0) {
12942
+ return { error: `\uD45C ${match.globalTableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958).` };
12943
+ }
12944
+ const localIdx = match.globalTableIndex - section.globalOffset;
12945
+ const table = scanSectionXml2(section.xml, 0).tables[localIdx];
12946
+ if (table === void 0 || cellAt(table, target.row, target.col) === void 0) {
12947
+ const dirLabel = direction === "right" ? "\uC624\uB978\uCABD" : "\uC544\uB798";
12948
+ return {
12949
+ error: `\uB808\uC774\uBE14 "${label}" (\uD45C ${match.globalTableIndex}, \uD589 ${match.row}, \uC5F4 ${match.col})\uC758 ${dirLabel} \uC140 (\uD589 ${target.row}, \uC5F4 ${target.col})\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. direction \uB610\uB294 \uC88C\uD45C\uB97C \uD655\uC778\uD558\uC138\uC694.`
12950
+ };
12951
+ }
12952
+ return { tableIndex: match.globalTableIndex, row: target.row, col: target.col };
12953
+ }
12954
+ var proposeCellEditTool = {
12955
+ name: "propose_cell_edit",
12956
+ description: "HWPX \uBB38\uC11C\uC758 \uD45C \uC140 \uB0B4\uC6A9\uC744 \uC88C\uD45C/\uB808\uC774\uBE14 \uC9C0\uC815\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\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.",
12957
+ inputSchema: proposeCellEditSchema,
12958
+ requiresApproval: true,
12959
+ propose: async ({
12960
+ input,
12961
+ ctx
12962
+ }) => {
12963
+ const safePath = await resolveSafePath(ctx.cwd, input.path);
12964
+ const ext = extname7(safePath).toLowerCase();
12965
+ if (ext !== ".hwpx" && ext !== ".hwp") {
12966
+ 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.`;
12967
+ }
12968
+ let originalBuffer;
12969
+ try {
12970
+ originalBuffer = await readFile7(safePath);
12971
+ } catch {
12972
+ 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.`;
12973
+ }
12974
+ const originalBytes = new Uint8Array(originalBuffer.buffer);
12975
+ const structuralGuard = hwpStructuralGuard(ext, originalBytes);
12976
+ if (structuralGuard !== null) {
12977
+ return structuralGuard;
12978
+ }
12979
+ if (!isZipBinary(originalBytes)) {
12980
+ 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.";
12981
+ }
12982
+ const zipForLabel = await import_jszip4.default.loadAsync(new Uint8Array(originalBuffer.buffer));
12983
+ const sections = await readSections(zipForLabel);
12984
+ const resolvedEdits = [];
12985
+ const resolveErrors = [];
12986
+ for (let i = 0; i < input.edits.length; i++) {
12987
+ const e = input.edits[i];
12988
+ if (!e) continue;
12989
+ if (e.label !== void 0 && (e.row !== void 0 || e.col !== void 0)) {
12990
+ resolveErrors.push(
12991
+ `\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.`
12992
+ );
12993
+ } else if (e.label !== void 0) {
12994
+ const direction = e.direction ?? "right";
12995
+ const resolved = resolveLabelAcrossSections(sections, e.label, direction, e.tableIndex);
12996
+ if ("error" in resolved) {
12997
+ resolveErrors.push(`\uD3B8\uC9D1 #${i + 1} (\uB808\uC774\uBE14 "${e.label}"): ${resolved.error}`);
12998
+ } else {
12999
+ resolvedEdits.push({
13000
+ tableIndex: resolved.tableIndex,
13001
+ row: resolved.row,
13002
+ col: resolved.col,
13003
+ newText: e.newText,
13004
+ expectedText: e.expectedText,
13005
+ label: e.label
13006
+ });
13007
+ }
13008
+ } else if (e.tableIndex !== void 0 && e.row !== void 0 && e.col !== void 0) {
13009
+ resolvedEdits.push({
13010
+ tableIndex: e.tableIndex,
13011
+ row: e.row,
13012
+ col: e.col,
13013
+ newText: e.newText,
13014
+ expectedText: e.expectedText
13015
+ });
13016
+ } else {
13017
+ resolveErrors.push(
13018
+ `\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.`
13019
+ );
13101
13020
  }
13102
13021
  }
13103
- }
13104
- return changed ? lines.join("\n") : markdown;
13105
- }
13106
- function isZip(bytes) {
13107
- return bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75;
13108
- }
13109
- async function resolveHwpxBytes(input) {
13110
- try {
13111
- if (typeof input === "string") {
13112
- if (!/\.hwpx$/i.test(input)) return null;
13113
- return new Uint8Array(await readFile7(input));
13022
+ if (resolveErrors.length > 0) {
13023
+ 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.
13024
+ ${resolveErrors.join("\n")}`;
13114
13025
  }
13115
- const u8 = input instanceof Uint8Array ? input : new Uint8Array(input instanceof ArrayBuffer ? input : input);
13116
- return isZip(u8) ? u8 : null;
13117
- } catch {
13118
- return null;
13119
- }
13120
- }
13121
- async function parse(input, options) {
13122
- const result = await kordocParse(input, options);
13123
- if (!result.success || typeof result.markdown !== "string" || !result.markdown) {
13124
- return result;
13125
- }
13126
- const bytes = await resolveHwpxBytes(input);
13127
- if (!bytes) return result;
13128
- try {
13129
- const paragraphTexts = await extractHwpxParagraphTexts(bytes);
13130
- if (paragraphTexts.length === 0) return result;
13131
- const repaired = restoreOverStrippedShapeText(result.markdown, paragraphTexts);
13132
- if (repaired !== result.markdown) {
13133
- return { ...result, markdown: repaired };
13026
+ const editRequests = resolvedEdits.map((e) => ({
13027
+ tableIndex: e.tableIndex,
13028
+ row: e.row,
13029
+ col: e.col,
13030
+ newText: e.newText,
13031
+ expectedText: e.expectedText
13032
+ }));
13033
+ const { buffer: newBuffer, results } = await applyEditsToHwpx(
13034
+ new Uint8Array(originalBuffer.buffer),
13035
+ editRequests
13036
+ );
13037
+ const failedResults = results.map((r, i) => ({ r, i })).filter(({ r }) => !r.success);
13038
+ if (failedResults.length > 0) {
13039
+ const messages = failedResults.map(({ r, i }) => {
13040
+ const e = resolvedEdits[i];
13041
+ const label = e?.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 ` : "";
13042
+ return `\uD3B8\uC9D1 #${i + 1} (${label}\uD45C ${e?.tableIndex ?? "?"}, \uD589 ${e?.row ?? "?"}, \uC5F4 ${e?.col ?? "?"}): ${r.error}`;
13043
+ });
13044
+ 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.
13045
+ ${messages.join("\n")}`;
13134
13046
  }
13135
- } catch {
13047
+ const diffLines = ["| \uD45C\xB7\uC140 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
13048
+ for (let i = 0; i < resolvedEdits.length; i++) {
13049
+ const e = resolvedEdits[i];
13050
+ if (!e) continue;
13051
+ const oldText = results[i]?.oldText ?? "";
13052
+ const addr = e.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 #${e.tableIndex} (${e.row},${e.col})` : `#${e.tableIndex} (${e.row},${e.col})`;
13053
+ diffLines.push(`| ${addr} | ${oldText} | ${e.newText} |`);
13054
+ }
13055
+ const diff = diffLines.join("\n");
13056
+ const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13057
+ const stagedPath = await stageFile(ctx.sessionId, safePath, newBuffer);
13058
+ const proposalId = crypto.randomUUID();
13059
+ return {
13060
+ proposal: {
13061
+ id: proposalId,
13062
+ kind: "cell-edit",
13063
+ targetPath: outputPath,
13064
+ stagedPath,
13065
+ summary: input.summary,
13066
+ diff,
13067
+ warnings: [],
13068
+ willConvertFormat
13069
+ },
13070
+ commit: async () => {
13071
+ const backupPath = await backupFile(safePath);
13072
+ await commitStaged(stagedPath, outputPath);
13073
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
13074
+ return `\uC800\uC7A5 \uC644\uB8CC: ${outputPath}${backupInfo}`;
13075
+ }
13076
+ };
13136
13077
  }
13137
- return result;
13138
- }
13078
+ };
13139
13079
  var HEADING_LEVELS = {
13140
13080
  1: HeadingLevel.HEADING_1,
13141
13081
  2: HeadingLevel.HEADING_2,
@@ -13258,10 +13198,10 @@ async function markdownToDocx(markdown) {
13258
13198
  });
13259
13199
  return Packer.toBuffer(doc);
13260
13200
  }
13261
- var proposeEditSchema = z7.object({
13262
- path: z7.string().describe("\uC218\uC815\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13263
- 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"),
13264
- summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13201
+ var proposeEditSchema = z8.object({
13202
+ path: z8.string().describe("\uC218\uC815\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13203
+ newMarkdown: z8.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"),
13204
+ summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13265
13205
  });
13266
13206
  var proposeEditTool = {
13267
13207
  name: "propose_edit",
@@ -13273,7 +13213,7 @@ var proposeEditTool = {
13273
13213
  ctx
13274
13214
  }) => {
13275
13215
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13276
- const ext = extname7(safePath).toLowerCase();
13216
+ const ext = extname8(safePath).toLowerCase();
13277
13217
  let originalBuffer;
13278
13218
  try {
13279
13219
  originalBuffer = await readFile8(safePath);
@@ -13354,19 +13294,52 @@ ${diff}`;
13354
13294
  };
13355
13295
  }
13356
13296
  };
13357
- var proposeFindReplaceSchema = z8.object({
13358
- 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)"),
13359
- find: z8.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
13360
- replace: z8.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
13361
- caseSensitive: z8.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
13362
- 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"),
13363
- summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13297
+ function collectParasInDocOrder(scan) {
13298
+ const out = [...scan.bodyParagraphs];
13299
+ const walkTable = (t) => {
13300
+ for (const row of t.rows) {
13301
+ for (const cell of row) {
13302
+ out.push(...cell.paragraphs);
13303
+ for (const nested of cell.tables) walkTable(nested);
13304
+ }
13305
+ }
13306
+ };
13307
+ for (const t of scan.tables) walkTable(t);
13308
+ out.push(...scan.excludedParagraphs);
13309
+ for (const t of scan.orphanTables) walkTable(t);
13310
+ return out.sort((a, b) => a.start - b.start);
13311
+ }
13312
+ function applyRangeSplicesToSection(xml, rangeFn) {
13313
+ const scan = scanSectionXml3(xml, 0);
13314
+ const paras = collectParasInDocOrder(scan);
13315
+ const splices = [];
13316
+ let count = 0;
13317
+ for (const p of paras) {
13318
+ const ranges = rangeFn(p.text);
13319
+ for (const r of ranges) {
13320
+ if (r.end <= r.start) continue;
13321
+ const s = buildRangeSplices(p, xml, r.start, r.end, r.replacement);
13322
+ if (s === null) return null;
13323
+ splices.push(...s);
13324
+ count++;
13325
+ }
13326
+ }
13327
+ if (count === 0) return { xml, count: 0 };
13328
+ return { xml: applySplices2(xml, splices), count };
13329
+ }
13330
+ var proposeFindReplaceSchema = z9.object({
13331
+ path: z9.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13332
+ find: z9.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
13333
+ replace: z9.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
13334
+ caseSensitive: z9.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
13335
+ all: z9.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"),
13336
+ summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13364
13337
  });
13365
13338
  var MAX_DIFF_SAMPLES = 20;
13366
- function escapeXml3(text3) {
13339
+ function escapeXml2(text3) {
13367
13340
  return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13368
13341
  }
13369
- function unescapeXml2(text3) {
13342
+ function unescapeXml(text3) {
13370
13343
  return text3.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
13371
13344
  }
13372
13345
  function makeChangeSnippet(before, after, ctx = 24) {
@@ -13390,7 +13363,7 @@ function collectChangedSnippets(beforeXml, afterXml, maxSamples) {
13390
13363
  const n = Math.min(beforeNodes.length, afterNodes.length);
13391
13364
  for (let i = 0; i < n && out.length < maxSamples; i++) {
13392
13365
  if (beforeNodes[i] !== afterNodes[i]) {
13393
- const snip = makeChangeSnippet(unescapeXml2(beforeNodes[i]), unescapeXml2(afterNodes[i]));
13366
+ const snip = makeChangeSnippet(unescapeXml(beforeNodes[i]), unescapeXml(afterNodes[i]));
13394
13367
  out.push(snip);
13395
13368
  }
13396
13369
  }
@@ -13400,8 +13373,8 @@ function replaceInSectionXml(xml, find, replace, caseSensitive, replaceAll, alre
13400
13373
  if (!replaceAll && alreadyReplaced > 0) {
13401
13374
  return { xml, count: 0 };
13402
13375
  }
13403
- const escapedFind = escapeXml3(find);
13404
- const escapedReplace = escapeXml3(replace);
13376
+ const escapedFind = escapeXml2(find);
13377
+ const escapedReplace = escapeXml2(replace);
13405
13378
  if (escapedFind.length === 0) {
13406
13379
  return { xml, count: 0 };
13407
13380
  }
@@ -13472,6 +13445,31 @@ function countOccurrences(str, sub) {
13472
13445
  }
13473
13446
  return count;
13474
13447
  }
13448
+ function replaceSectionViaSplices(xml, find, replace, caseSensitive, replaceAll, alreadyReplaced) {
13449
+ if (find.length === 0) return { xml, count: 0 };
13450
+ if (!replaceAll && alreadyReplaced > 0) return { xml, count: 0 };
13451
+ const scan = scanSectionXml4(xml, 0);
13452
+ const paras = collectParasInDocOrder(scan);
13453
+ const needle = caseSensitive ? find : find.toLowerCase();
13454
+ const splices = [];
13455
+ let count = 0;
13456
+ for (const p of paras) {
13457
+ if (!replaceAll && alreadyReplaced + count >= 1) break;
13458
+ const hay = caseSensitive ? p.text : p.text.toLowerCase();
13459
+ let idx = hay.indexOf(needle);
13460
+ while (idx !== -1) {
13461
+ if (!replaceAll && alreadyReplaced + count >= 1) break;
13462
+ const s = buildRangeSplices2(p, xml, idx, idx + find.length, replace);
13463
+ if (s === null) return null;
13464
+ splices.push(...s);
13465
+ count++;
13466
+ if (!replaceAll) break;
13467
+ idx = hay.indexOf(needle, idx + needle.length);
13468
+ }
13469
+ }
13470
+ if (count === 0) return { xml, count: 0 };
13471
+ return { xml: applySplices3(xml, splices), count };
13472
+ }
13475
13473
  async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive, replaceAll) {
13476
13474
  const zip = await import_jszip5.default.loadAsync(hwpxBuffer);
13477
13475
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
@@ -13485,7 +13483,9 @@ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive,
13485
13483
  const newSectionXmls = [];
13486
13484
  for (let si = 0; si < sectionFiles.length; si++) {
13487
13485
  const srcXml = sectionXmls[si] ?? "";
13488
- const { xml: newXml, count } = replaceInSectionXml(
13486
+ let newXml;
13487
+ let count;
13488
+ const spliced = replaceSectionViaSplices(
13489
13489
  srcXml,
13490
13490
  find,
13491
13491
  replace,
@@ -13493,6 +13493,21 @@ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive,
13493
13493
  replaceAll,
13494
13494
  totalCount
13495
13495
  );
13496
+ if (spliced !== null) {
13497
+ newXml = spliced.xml;
13498
+ count = spliced.count;
13499
+ } else {
13500
+ const fallback = replaceInSectionXml(
13501
+ srcXml,
13502
+ find,
13503
+ replace,
13504
+ caseSensitive,
13505
+ replaceAll,
13506
+ totalCount
13507
+ );
13508
+ newXml = fallback.xml;
13509
+ count = fallback.count;
13510
+ }
13496
13511
  newSectionXmls.push(newXml);
13497
13512
  totalCount += count;
13498
13513
  if (!replaceAll && totalCount >= 1) {
@@ -13548,7 +13563,7 @@ var proposeFindReplaceTool = {
13548
13563
  ctx
13549
13564
  }) => {
13550
13565
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13551
- const ext = extname8(safePath).toLowerCase();
13566
+ const ext = extname9(safePath).toLowerCase();
13552
13567
  if (ext !== ".hwpx" && ext !== ".hwp") {
13553
13568
  return `\uC624\uB958: propose_find_replace\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}. .hwpx \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.`;
13554
13569
  }
@@ -13567,7 +13582,7 @@ var proposeFindReplaceTool = {
13567
13582
  if (structuralGuard !== null) {
13568
13583
  return structuralGuard;
13569
13584
  }
13570
- if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
13585
+ if (!isZipBinary(originalBytes)) {
13571
13586
  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. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
13572
13587
  }
13573
13588
  let newBytes;
@@ -13651,10 +13666,10 @@ var proposeFindReplaceTool = {
13651
13666
  };
13652
13667
  }
13653
13668
  };
13654
- var proposeFormFillSchema = z9.object({
13655
- path: z9.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13656
- 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"),
13657
- summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13669
+ var proposeFormFillSchema = z10.object({
13670
+ path: z10.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13671
+ fields: z10.record(z10.string(), z10.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"),
13672
+ summary: z10.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13658
13673
  });
13659
13674
  var proposeFormFillTool = {
13660
13675
  name: "propose_form_fill",
@@ -13666,7 +13681,7 @@ var proposeFormFillTool = {
13666
13681
  ctx
13667
13682
  }) => {
13668
13683
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13669
- const ext = extname9(safePath).toLowerCase();
13684
+ const ext = extname10(safePath).toLowerCase();
13670
13685
  if (ext !== ".hwpx" && ext !== ".hwp") {
13671
13686
  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.`;
13672
13687
  }
@@ -13691,9 +13706,19 @@ var proposeFormFillTool = {
13691
13706
  );
13692
13707
  return `\uC624\uB958: ${msg}`;
13693
13708
  }
13694
- const formResult = extractFormFields(parseResult.blocks);
13695
- const existingFields = new Map(formResult.fields.map((f) => [f.label, f.value]));
13709
+ const formSchema = extractFormSchema(parseResult.blocks);
13710
+ const existingFields = new Map(formSchema.fields.map((f) => [f.label, f.value]));
13696
13711
  const warnings = [];
13712
+ const fieldTypeKo = {
13713
+ text: "\uD14D\uC2A4\uD2B8",
13714
+ date: "\uB0A0\uC9DC",
13715
+ phone: "\uC804\uD654",
13716
+ email: "\uC774\uBA54\uC77C",
13717
+ amount: "\uAE08\uC561",
13718
+ checkbox: "\uCCB4\uD06C\uBC15\uC2A4",
13719
+ idnum: "\uC8FC\uBBFC\uBC88\uD638"
13720
+ };
13721
+ const availableHint = formSchema.fields.length > 0 ? ` \uCC44\uC6B8 \uC218 \uC788\uB294 \uD544\uB4DC: ${formSchema.fields.map((f) => `${f.label}(${fieldTypeKo[f.type] ?? f.type}${f.required ? ", \uD544\uC218" : ""})`).join(", ")}.` : "";
13697
13722
  const diffLines = ["| \uB77C\uBCA8 | \uC774\uC804 \uAC12 | \uC0C8 \uAC12 |", "| --- | --- | --- |"];
13698
13723
  for (const [label, newValue] of Object.entries(input.fields)) {
13699
13724
  const oldValue = existingFields.get(label) ?? "(\uC5C6\uC74C)";
@@ -13709,11 +13734,11 @@ var proposeFormFillTool = {
13709
13734
  const fillResult = await fillHwpx(origAB, input.fields);
13710
13735
  if (fillResult.unmatched.length > 0) {
13711
13736
  warnings.push(
13712
- `\uB2E4\uC74C \uB77C\uBCA8\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${fillResult.unmatched.join(", ")}. read_document\uB85C \uD604\uC7AC \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.`
13737
+ `\uB2E4\uC74C \uB77C\uBCA8\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${fillResult.unmatched.join(", ")}.${availableHint || " read_document\uB85C \uD604\uC7AC \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694."}`
13713
13738
  );
13714
13739
  }
13715
13740
  if (fillResult.filled.length === 0) {
13716
- return `\uC624\uB958: \uCC44\uC6CC\uC9C4 \uC591\uC2DD \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uB77C\uBCA8\uC774 \uBB38\uC11C\uC640 \uC77C\uCE58\uD558\uB294\uC9C0 read_document\uB85C \uD655\uC778\uD558\uC138\uC694${fillResult.unmatched.length > 0 ? ` (\uBBF8\uB9E4\uCE6D: ${fillResult.unmatched.join(", ")})` : ""}.`;
13741
+ return `\uC624\uB958: \uCC44\uC6CC\uC9C4 \uC591\uC2DD \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uB77C\uBCA8\uC774 \uBB38\uC11C\uC640 \uC77C\uCE58\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694${fillResult.unmatched.length > 0 ? ` (\uBBF8\uB9E4\uCE6D: ${fillResult.unmatched.join(", ")})` : ""}.${availableHint || " read_document\uB85C \uD604\uC7AC \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694."}`;
13717
13742
  }
13718
13743
  stagedData = new Uint8Array(fillResult.buffer);
13719
13744
  } catch (err) {
@@ -13742,9 +13767,9 @@ var proposeFormFillTool = {
13742
13767
  };
13743
13768
  }
13744
13769
  };
13745
- var proposeRedactPiiSchema = z10.object({
13746
- path: z10.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD560 \uBB38\uC11C \uACBD\uB85C"),
13747
- summary: z10.string().optional().describe("\uBCC0\uACBD \uC694\uC57D")
13770
+ var proposeRedactPiiSchema = z11.object({
13771
+ path: z11.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD560 \uBB38\uC11C \uACBD\uB85C"),
13772
+ summary: z11.string().optional().describe("\uBCC0\uACBD \uC694\uC57D")
13748
13773
  });
13749
13774
  function mergeFindings(allFindings) {
13750
13775
  const map = /* @__PURE__ */ new Map();
@@ -13765,6 +13790,29 @@ function mergeFindings(allFindings) {
13765
13790
  }
13766
13791
  return [...map.values()];
13767
13792
  }
13793
+ function redactSectionViaNodes(srcXml) {
13794
+ const tNodeRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
13795
+ let offset = 0;
13796
+ let result = srcXml;
13797
+ let changed = false;
13798
+ let m = tNodeRe.exec(srcXml);
13799
+ while (m !== null) {
13800
+ const content = m[1];
13801
+ if (content.length > 0) {
13802
+ const { text: redacted } = redactText(content);
13803
+ if (redacted !== content) {
13804
+ const openTagLen = "<hp:t>".length;
13805
+ const contentStart = m.index + offset + openTagLen;
13806
+ const contentEnd = contentStart + content.length;
13807
+ result = result.substring(0, contentStart) + redacted + result.substring(contentEnd);
13808
+ offset += redacted.length - content.length;
13809
+ changed = true;
13810
+ }
13811
+ }
13812
+ m = tNodeRe.exec(srcXml);
13813
+ }
13814
+ return { xml: result, changed };
13815
+ }
13768
13816
  async function applyRedactToHwpx(hwpxBuffer) {
13769
13817
  const zip = await import_jszip6.default.loadAsync(hwpxBuffer);
13770
13818
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
@@ -13778,33 +13826,23 @@ async function applyRedactToHwpx(hwpxBuffer) {
13778
13826
  const allFindings = [];
13779
13827
  let anyChanged = false;
13780
13828
  for (const srcXml of sectionXmls) {
13781
- const tNodeRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
13782
- const sectionFindings = [];
13783
- let offset = 0;
13784
- let result = srcXml;
13785
- let m = tNodeRe.exec(srcXml);
13786
- while (m !== null) {
13787
- const content = m[1];
13788
- if (content.length === 0) {
13789
- m = tNodeRe.exec(srcXml);
13790
- continue;
13791
- }
13792
- const { text: redacted, findings } = redactText(content);
13793
- if (redacted !== content) {
13794
- const openTagLen = "<hp:t>".length;
13795
- const contentStart = m.index + offset + openTagLen;
13796
- const contentEnd = contentStart + content.length;
13797
- result = result.substring(0, contentStart) + redacted + result.substring(contentEnd);
13798
- offset += redacted.length - content.length;
13799
- anyChanged = true;
13800
- }
13801
- if (findings.length > 0) {
13802
- sectionFindings.push(findings);
13803
- }
13804
- m = tNodeRe.exec(srcXml);
13829
+ const scan = scanSectionXml5(srcXml, 0);
13830
+ for (const p of collectParasInDocOrder(scan)) {
13831
+ const findings = detectPii(p.text);
13832
+ if (findings.length > 0) allFindings.push(findings);
13833
+ }
13834
+ const spliced = applyRangeSplicesToSection(
13835
+ srcXml,
13836
+ (text3) => redactRanges(text3).map((r) => ({ start: r.start, end: r.end, replacement: r.replacement }))
13837
+ );
13838
+ if (spliced !== null) {
13839
+ newSectionXmls.push(spliced.xml);
13840
+ if (spliced.count > 0) anyChanged = true;
13841
+ } else {
13842
+ const fb = redactSectionViaNodes(srcXml);
13843
+ newSectionXmls.push(fb.xml);
13844
+ if (fb.changed) anyChanged = true;
13805
13845
  }
13806
- newSectionXmls.push(result);
13807
- allFindings.push(...sectionFindings);
13808
13846
  }
13809
13847
  if (!anyChanged) {
13810
13848
  return { buffer: hwpxBuffer, findings: mergeFindings(allFindings), changed: false };
@@ -13849,7 +13887,7 @@ var proposeRedactPiiTool = {
13849
13887
  ctx
13850
13888
  }) => {
13851
13889
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13852
- const ext = extname10(safePath).toLowerCase();
13890
+ const ext = extname11(safePath).toLowerCase();
13853
13891
  if (ext !== ".hwpx" && ext !== ".hwp" && ext !== ".md" && ext !== ".txt") {
13854
13892
  const hint = ext === ".docx" || ext === ".xlsx" ? " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4." : " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.";
13855
13893
  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}`;
@@ -13871,7 +13909,7 @@ var proposeRedactPiiTool = {
13871
13909
  if (structuralGuard !== null) {
13872
13910
  return structuralGuard;
13873
13911
  }
13874
- if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
13912
+ if (!isZipBinary(originalBytes)) {
13875
13913
  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.";
13876
13914
  }
13877
13915
  let patchResult;
@@ -13943,16 +13981,16 @@ var proposeRedactPiiTool = {
13943
13981
  };
13944
13982
  }
13945
13983
  };
13946
- var proposeSheetEditSchema = z11.object({
13947
- 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)"),
13948
- updates: z11.array(
13949
- z11.object({
13950
- sheet: z11.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13951
- cell: z11.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
13952
- value: z11.union([z11.string(), z11.number()]).describe("\uC0C8 \uAC12")
13984
+ var proposeSheetEditSchema = z12.object({
13985
+ path: z12.string().describe("\uC218\uC815\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13986
+ updates: z12.array(
13987
+ z12.object({
13988
+ sheet: z12.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13989
+ cell: z12.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
13990
+ value: z12.union([z12.string(), z12.number()]).describe("\uC0C8 \uAC12")
13953
13991
  })
13954
13992
  ).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"),
13955
- summary: z11.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13993
+ summary: z12.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13956
13994
  });
13957
13995
  var proposeSheetEditTool = {
13958
13996
  name: "propose_sheet_edit",
@@ -13964,7 +14002,7 @@ var proposeSheetEditTool = {
13964
14002
  ctx
13965
14003
  }) => {
13966
14004
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13967
- const ext = extname11(safePath).toLowerCase();
14005
+ const ext = extname12(safePath).toLowerCase();
13968
14006
  if (ext !== ".xlsx" && ext !== ".xls") {
13969
14007
  return `\uC624\uB958: propose_sheet_edit\uC740 .xlsx/.xls \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}.`;
13970
14008
  }
@@ -14063,49 +14101,49 @@ function detectStructuralLoss(beforeBlocks, afterBlocks) {
14063
14101
  }
14064
14102
  return { lost: false, detail: "" };
14065
14103
  }
14066
- var insertRowOpSchema = z12.object({
14067
- type: z12.literal("insertRow"),
14068
- row: z12.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
14069
- position: z12.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
14104
+ var insertRowOpSchema = z13.object({
14105
+ type: z13.literal("insertRow"),
14106
+ row: z13.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
14107
+ position: z13.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
14070
14108
  });
14071
- var deleteRowOpSchema = z12.object({
14072
- type: z12.literal("deleteRow"),
14073
- row: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
14109
+ var deleteRowOpSchema = z13.object({
14110
+ type: z13.literal("deleteRow"),
14111
+ row: z13.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
14074
14112
  });
14075
- var insertColumnOpSchema = z12.object({
14076
- type: z12.literal("insertColumn"),
14077
- col: z12.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
14078
- position: z12.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
14113
+ var insertColumnOpSchema = z13.object({
14114
+ type: z13.literal("insertColumn"),
14115
+ col: z13.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
14116
+ position: z13.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
14079
14117
  });
14080
- var deleteColumnOpSchema = z12.object({
14081
- type: z12.literal("deleteColumn"),
14082
- col: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
14118
+ var deleteColumnOpSchema = z13.object({
14119
+ type: z13.literal("deleteColumn"),
14120
+ col: z13.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
14083
14121
  });
14084
- var mergeCellsOpSchema = z12.object({
14085
- type: z12.literal("mergeCells"),
14086
- startRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
14087
- startCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
14088
- endRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
14089
- endCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
14122
+ var mergeCellsOpSchema = z13.object({
14123
+ type: z13.literal("mergeCells"),
14124
+ startRow: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
14125
+ startCol: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
14126
+ endRow: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
14127
+ endCol: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
14090
14128
  });
14091
- var operationSchema = z12.discriminatedUnion("type", [
14129
+ var operationSchema = z13.discriminatedUnion("type", [
14092
14130
  insertRowOpSchema,
14093
14131
  deleteRowOpSchema,
14094
14132
  insertColumnOpSchema,
14095
14133
  deleteColumnOpSchema,
14096
14134
  mergeCellsOpSchema
14097
14135
  ]).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.");
14098
- var proposeTableStructureSchema = z12.object({
14099
- 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)"),
14100
- anchor: z12.string().min(1).describe(
14136
+ var proposeTableStructureSchema = z13.object({
14137
+ path: z13.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14138
+ anchor: z13.string().min(1).describe(
14101
14139
  "\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."
14102
14140
  ),
14103
- operations: z12.array(operationSchema).min(1).describe(
14141
+ operations: z13.array(operationSchema).min(1).describe(
14104
14142
  "\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."
14105
14143
  ),
14106
- summary: z12.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
14144
+ summary: z13.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
14107
14145
  });
14108
- function tokenizeHwpxXml2(xml) {
14146
+ function tokenizeHwpxXml(xml) {
14109
14147
  const tokens = [];
14110
14148
  const re = /<hp:tbl[\s>]|<\/hp:tbl>|<hp:tr[\s>]|<\/hp:tr>|<hp:tc[\s>]|<\/hp:tc>|<hp:t\/>|<hp:t>|<\/hp:t>|<hp:cellAddr[^/>]*|<hp:cellSpan[^/>]*|<hp:cellSz[^/>]*/g;
14111
14149
  let m = re.exec(xml);
@@ -14295,7 +14333,7 @@ function setCellSpan(tcXml, colSpan, rowSpan) {
14295
14333
  return tcXml.replace(/(colSpan=")(\d+)(")/, `$1${colSpan}$3`).replace(/(rowSpan=")(\d+)(")/, `$1${rowSpan}$3`);
14296
14334
  }
14297
14335
  function insertRowInTbl(tblXml, row, below) {
14298
- const tokens = tokenizeHwpxXml2(tblXml);
14336
+ const tokens = tokenizeHwpxXml(tblXml);
14299
14337
  const tblStart = 0;
14300
14338
  const tblEnd = tblXml.length;
14301
14339
  const rows = parseTableRows(tblXml, tokens, tblStart, tblEnd);
@@ -14337,7 +14375,7 @@ function insertRowInTbl(tblXml, row, below) {
14337
14375
  modifiedTr = modifiedTr.substring(0, tcLocalStart) + tcXml + modifiedTr.substring(tcLocalEnd);
14338
14376
  }
14339
14377
  let result = tblXml;
14340
- const allTokens = tokenizeHwpxXml2(result);
14378
+ const allTokens = tokenizeHwpxXml(result);
14341
14379
  const addrPatches = [];
14342
14380
  for (const tok of allTokens) {
14343
14381
  if (tok.kind === "cell_addr" && tok.rowAddr !== void 0 && tok.rowAddr >= newRowAddr) {
@@ -14350,7 +14388,7 @@ function insertRowInTbl(tblXml, row, below) {
14350
14388
  const newTag = oldTag.replace(/(rowAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
14351
14389
  result = result.substring(0, p.pos) + newTag + result.substring(p.end);
14352
14390
  }
14353
- const resultTokens = tokenizeHwpxXml2(result);
14391
+ const resultTokens = tokenizeHwpxXml(result);
14354
14392
  const resultRows = parseTableRows(result, resultTokens, 0, result.length);
14355
14393
  let insertAfterPos;
14356
14394
  let insertBeforePos;
@@ -14382,7 +14420,7 @@ function insertRowInTbl(tblXml, row, below) {
14382
14420
  return { ok: true, xml: result };
14383
14421
  }
14384
14422
  function deleteRowInTbl(tblXml, row) {
14385
- const tokens = tokenizeHwpxXml2(tblXml);
14423
+ const tokens = tokenizeHwpxXml(tblXml);
14386
14424
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14387
14425
  const dims = parseTblDimensions(tblXml, 0);
14388
14426
  if (rows.length === 0) {
@@ -14419,7 +14457,7 @@ function deleteRowInTbl(tblXml, row) {
14419
14457
  }
14420
14458
  let result = tblXml;
14421
14459
  result = result.substring(0, targetRow.trStart) + result.substring(targetRow.trEnd);
14422
- const afterTokens = tokenizeHwpxXml2(result);
14460
+ const afterTokens = tokenizeHwpxXml(result);
14423
14461
  const addrPatches = [];
14424
14462
  for (const tok of afterTokens) {
14425
14463
  if (tok.kind === "cell_addr" && tok.rowAddr !== void 0 && tok.rowAddr > row) {
@@ -14436,7 +14474,7 @@ function deleteRowInTbl(tblXml, row) {
14436
14474
  return { ok: true, xml: result };
14437
14475
  }
14438
14476
  function insertColumnInTbl(tblXml, col, right) {
14439
- const tokens = tokenizeHwpxXml2(tblXml);
14477
+ const tokens = tokenizeHwpxXml(tblXml);
14440
14478
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14441
14479
  const dims = parseTblDimensions(tblXml, 0);
14442
14480
  if (rows.length === 0) {
@@ -14457,7 +14495,7 @@ function insertColumnInTbl(tblXml, col, right) {
14457
14495
  }
14458
14496
  }
14459
14497
  let result = tblXml;
14460
- const allTokens = tokenizeHwpxXml2(result);
14498
+ const allTokens = tokenizeHwpxXml(result);
14461
14499
  const addrPatches = [];
14462
14500
  for (const tok of allTokens) {
14463
14501
  if (tok.kind === "cell_addr" && tok.colAddr !== void 0 && tok.colAddr >= newColAddr) {
@@ -14470,7 +14508,7 @@ function insertColumnInTbl(tblXml, col, right) {
14470
14508
  const newTag = oldTag.replace(/(colAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
14471
14509
  result = result.substring(0, p.pos) + newTag + result.substring(p.end);
14472
14510
  }
14473
- const resultTokens2 = tokenizeHwpxXml2(result);
14511
+ const resultTokens2 = tokenizeHwpxXml(result);
14474
14512
  const resultRows = parseTableRows(result, resultTokens2, 0, result.length);
14475
14513
  const sortedRows = [...resultRows].sort((a, b) => b.trStart - a.trStart);
14476
14514
  for (const r of sortedRows) {
@@ -14497,7 +14535,7 @@ function insertColumnInTbl(tblXml, col, right) {
14497
14535
  return { ok: true, xml: result };
14498
14536
  }
14499
14537
  function deleteColumnInTbl(tblXml, col) {
14500
- const tokens = tokenizeHwpxXml2(tblXml);
14538
+ const tokens = tokenizeHwpxXml(tblXml);
14501
14539
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14502
14540
  const dims = parseTblDimensions(tblXml, 0);
14503
14541
  if (rows.length === 0) {
@@ -14531,7 +14569,7 @@ function deleteColumnInTbl(tblXml, col) {
14531
14569
  result = result.substring(0, cell.tcStart) + result.substring(cell.tcEnd);
14532
14570
  }
14533
14571
  }
14534
- const afterTokens = tokenizeHwpxXml2(result);
14572
+ const afterTokens = tokenizeHwpxXml(result);
14535
14573
  const addrPatches = [];
14536
14574
  for (const tok of afterTokens) {
14537
14575
  if (tok.kind === "cell_addr" && tok.colAddr !== void 0 && tok.colAddr > col) {
@@ -14554,7 +14592,7 @@ function mergeCellsInTbl(tblXml, startRow, startCol, endRow, endCol) {
14554
14592
  if (startRow > endRow || startCol > endCol) {
14555
14593
  return { ok: false, error: "\uBCD1\uD569 \uBC94\uC704\uAC00 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (start > end)." };
14556
14594
  }
14557
- const tokens = tokenizeHwpxXml2(tblXml);
14595
+ const tokens = tokenizeHwpxXml(tblXml);
14558
14596
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14559
14597
  const rangeCells = [];
14560
14598
  for (const r of rows) {
@@ -14610,7 +14648,7 @@ function mergeCellsInTbl(tblXml, startRow, startCol, endRow, endCol) {
14610
14648
  for (const c of sortedRemove) {
14611
14649
  result = result.substring(0, c.tcStart) + result.substring(c.tcEnd);
14612
14650
  }
14613
- const afterTokens = tokenizeHwpxXml2(result);
14651
+ const afterTokens = tokenizeHwpxXml(result);
14614
14652
  const afterRows = parseTableRows(result, afterTokens, 0, result.length);
14615
14653
  const afterTopLeft = afterRows.flatMap((r) => r.cells).find((c) => c.rowAddr === startRow && c.colAddr === startCol);
14616
14654
  if (!afterTopLeft) {
@@ -14622,7 +14660,7 @@ function mergeCellsInTbl(tblXml, startRow, startCol, endRow, endCol) {
14622
14660
  return { ok: true, xml: result };
14623
14661
  }
14624
14662
  function findTableByAnchorInXml(xml, anchor) {
14625
- const tokens = tokenizeHwpxXml2(xml);
14663
+ const tokens = tokenizeHwpxXml(xml);
14626
14664
  const allRanges = findAllTopLevelTableRanges(tokens);
14627
14665
  const trimmedAnchor = anchor.trim();
14628
14666
  const matched = [];
@@ -14748,7 +14786,7 @@ async function verifyOutputDims(newBytes, anchor, expectedRowCnt, expectedColCnt
14748
14786
  for (const sf of sectionFiles) {
14749
14787
  const entry = zip.file(sf);
14750
14788
  const xml = entry ? await entry.async("string") : "";
14751
- const tokens = tokenizeHwpxXml2(xml);
14789
+ const tokens = tokenizeHwpxXml(xml);
14752
14790
  const allRanges = findAllTopLevelTableRanges(tokens);
14753
14791
  const trimmedAnchor = anchor.trim();
14754
14792
  for (const r of allRanges) {
@@ -14778,7 +14816,7 @@ var proposeTableStructureTool = {
14778
14816
  ctx
14779
14817
  }) => {
14780
14818
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14781
- const ext = extname12(safePath).toLowerCase();
14819
+ const ext = extname13(safePath).toLowerCase();
14782
14820
  if (ext !== ".hwpx" && ext !== ".hwp") {
14783
14821
  return `\uC624\uB958: propose_table_structure\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}. .hwpx \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.`;
14784
14822
  }
@@ -14797,7 +14835,7 @@ var proposeTableStructureTool = {
14797
14835
  if (structuralGuard !== null) {
14798
14836
  return structuralGuard;
14799
14837
  }
14800
- if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
14838
+ if (!isZipBinary(originalBytes)) {
14801
14839
  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.";
14802
14840
  }
14803
14841
  let originalBlocks = null;
@@ -14984,11 +15022,11 @@ function searchExcerpts(markdown, query) {
14984
15022
  }
14985
15023
  return result;
14986
15024
  }
14987
- var readDocumentSchema = z13.object({
14988
- path: z13.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14989
- 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'),
14990
- 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)"),
14991
- 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)")
15025
+ var readDocumentSchema = z14.object({
15026
+ path: z14.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15027
+ pages: z14.string().optional().describe('\uC77D\uC744 \uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: "1-3", "1,3,5") \u2014 \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4'),
15028
+ outline: z14.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)"),
15029
+ search: z14.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)")
14992
15030
  });
14993
15031
  function applyReadMode(body, outline, search) {
14994
15032
  if (outline === true) {
@@ -15017,7 +15055,7 @@ var readDocumentTool = {
15017
15055
  const msg = e instanceof Error ? e.message : String(e);
15018
15056
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
15019
15057
  }
15020
- const ext = extname13(safePath).toLowerCase();
15058
+ const ext = extname14(safePath).toLowerCase();
15021
15059
  if (PLAIN_TEXT_EXTS.has(ext)) {
15022
15060
  let raw;
15023
15061
  try {
@@ -15121,8 +15159,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
15121
15159
  ".csv",
15122
15160
  ".log"
15123
15161
  ]);
15124
- var readFileSchema = z14.object({
15125
- path: z14.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
15162
+ var readFileSchema = z15.object({
15163
+ path: z15.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
15126
15164
  });
15127
15165
  var readFileTool = {
15128
15166
  name: "read_file",
@@ -15146,8 +15184,8 @@ var readFileTool = {
15146
15184
  if (info.size > MAX_SIZE) {
15147
15185
  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.`;
15148
15186
  }
15149
- const { extname: extname17 } = await import("path");
15150
- const ext = extname17(safePath).toLowerCase();
15187
+ const { extname: extname18 } = await import("path");
15188
+ const ext = extname18(safePath).toLowerCase();
15151
15189
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
15152
15190
  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.`;
15153
15191
  }
@@ -15162,8 +15200,8 @@ var readFileTool = {
15162
15200
  }
15163
15201
  };
15164
15202
  var PLAIN_TEXT_EXTS2 = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text"]);
15165
- var scanPiiSchema = z15.object({
15166
- path: z15.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
15203
+ var scanPiiSchema = z16.object({
15204
+ path: z16.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
15167
15205
  });
15168
15206
  var scanPiiTool = {
15169
15207
  name: "scan_pii",
@@ -15181,7 +15219,7 @@ var scanPiiTool = {
15181
15219
  const msg = e instanceof Error ? e.message : String(e);
15182
15220
  return `\uC624\uB958: \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
15183
15221
  }
15184
- const ext = extname14(safePath).toLowerCase();
15222
+ const ext = extname15(safePath).toLowerCase();
15185
15223
  let text3;
15186
15224
  if (PLAIN_TEXT_EXTS2.has(ext)) {
15187
15225
  try {
@@ -15215,10 +15253,10 @@ var scanPiiTool = {
15215
15253
  return lines.join("\n");
15216
15254
  }
15217
15255
  };
15218
- var MAX_PREVIEW_CHARS = 1e4;
15219
- var writeNewDocumentSchema = z16.object({
15220
- path: z16.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15221
- markdown: z16.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
15256
+ var MAX_PREVIEW_CHARS2 = 1e4;
15257
+ var writeNewDocumentSchema = z17.object({
15258
+ path: z17.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15259
+ markdown: z17.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
15222
15260
  });
15223
15261
  var writeNewDocumentTool = {
15224
15262
  name: "write_new_document",
@@ -15230,7 +15268,7 @@ var writeNewDocumentTool = {
15230
15268
  ctx
15231
15269
  }) => {
15232
15270
  const safePath = await resolveSafePath(ctx.cwd, input.path);
15233
- const ext = extname15(safePath).toLowerCase();
15271
+ const ext = extname16(safePath).toLowerCase();
15234
15272
  try {
15235
15273
  await stat6(safePath);
15236
15274
  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.`;
@@ -15251,9 +15289,9 @@ var writeNewDocumentTool = {
15251
15289
  return `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4: ${ext}. .hwpx, .docx, .md, .txt \uC911 \uD558\uB098\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.`;
15252
15290
  }
15253
15291
  const stagedPath = await stageFile(ctx.sessionId, safePath, stagedData);
15254
- const preview = input.markdown.length > MAX_PREVIEW_CHARS ? `${input.markdown.slice(0, MAX_PREVIEW_CHARS)}
15292
+ const preview = input.markdown.length > MAX_PREVIEW_CHARS2 ? `${input.markdown.slice(0, MAX_PREVIEW_CHARS2)}
15255
15293
 
15256
- ...\uC774\uD558 \uC0DD\uB7B5 (${input.markdown.length - MAX_PREVIEW_CHARS}\uC790 \uB354 \uC788\uC74C)` : input.markdown;
15294
+ ...\uC774\uD558 \uC0DD\uB7B5 (${input.markdown.length - MAX_PREVIEW_CHARS2}\uC790 \uB354 \uC788\uC74C)` : input.markdown;
15257
15295
  const proposalId = crypto.randomUUID();
15258
15296
  return {
15259
15297
  proposal: {
@@ -15274,12 +15312,12 @@ ${preview}`,
15274
15312
  };
15275
15313
  }
15276
15314
  };
15277
- var writeNewSpreadsheetSchema = z17.object({
15278
- 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)"),
15279
- sheets: z17.array(
15280
- z17.object({
15281
- name: z17.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
15282
- rows: z17.array(z17.array(z17.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
15315
+ var writeNewSpreadsheetSchema = z18.object({
15316
+ path: z18.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15317
+ sheets: z18.array(
15318
+ z18.object({
15319
+ name: z18.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
15320
+ rows: z18.array(z18.array(z18.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
15283
15321
  })
15284
15322
  ).min(1).describe("\uC0DD\uC131\uD560 \uC2DC\uD2B8 \uBAA9\uB85D")
15285
15323
  });
@@ -15293,7 +15331,7 @@ var writeNewSpreadsheetTool = {
15293
15331
  ctx
15294
15332
  }) => {
15295
15333
  const safePath = await resolveSafePath(ctx.cwd, input.path);
15296
- const ext = extname16(safePath).toLowerCase();
15334
+ const ext = extname17(safePath).toLowerCase();
15297
15335
  if (ext !== ".xlsx") {
15298
15336
  return `\uC624\uB958: write_new_spreadsheet\uC740 .xlsx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD655\uC7A5\uC790: ${ext}.`;
15299
15337
  }
@@ -15355,6 +15393,7 @@ function createDocTools(_ctx) {
15355
15393
  readFileTool,
15356
15394
  scanPiiTool,
15357
15395
  findInDocumentTool,
15396
+ exportDocumentTool,
15358
15397
  proposeEditTool,
15359
15398
  proposeFormFillTool,
15360
15399
  proposeCellEditTool,
@@ -15371,13 +15410,13 @@ function createDocTools(_ctx) {
15371
15410
  }
15372
15411
 
15373
15412
  // src/index.ts
15374
- import chalk5 from "chalk";
15413
+ import chalk6 from "chalk";
15375
15414
  import { Command } from "commander";
15376
15415
 
15377
15416
  // src/chat.ts
15378
15417
  import * as readline from "readline/promises";
15379
15418
  import { isCancel as isCancel2, select as select2, spinner, text } from "@clack/prompts";
15380
- import chalk2 from "chalk";
15419
+ import chalk3 from "chalk";
15381
15420
 
15382
15421
  // src/approve.ts
15383
15422
  import { isCancel, select } from "@clack/prompts";
@@ -15453,6 +15492,45 @@ function createCliApprovalHandler() {
15453
15492
  };
15454
15493
  }
15455
15494
 
15495
+ // src/usage.ts
15496
+ import chalk2 from "chalk";
15497
+ var MODEL_PRICING = [
15498
+ // Anthropic Claude (USD per 1M tokens)
15499
+ { prefix: "claude-opus-4", price: { input: 15, output: 75 } },
15500
+ { prefix: "claude-sonnet-4", price: { input: 3, output: 15 } },
15501
+ { prefix: "claude-haiku-4", price: { input: 1, output: 5 } }
15502
+ // OpenAI·Google: 정가 미확정 → 미등록(토큰만 표시)
15503
+ ];
15504
+ var PROVIDER_DEFAULT_MODEL = {
15505
+ anthropic: "claude-opus-4-8"
15506
+ };
15507
+ function effectiveModelId(config) {
15508
+ return config.model ?? PROVIDER_DEFAULT_MODEL[config.provider] ?? "";
15509
+ }
15510
+ function resolveTokenPrice(model) {
15511
+ for (const { prefix, price } of MODEL_PRICING) {
15512
+ if (model.startsWith(prefix)) return price;
15513
+ }
15514
+ return null;
15515
+ }
15516
+ function estimateCostUsd(model, inputTokens, outputTokens) {
15517
+ const p = resolveTokenPrice(model);
15518
+ if (!p) return null;
15519
+ return inputTokens / 1e6 * p.input + outputTokens / 1e6 * p.output;
15520
+ }
15521
+ var fmtTok = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
15522
+ function fmtUsd(cost) {
15523
+ if (cost > 0 && cost < 0.01) return `$${cost.toFixed(5)}`;
15524
+ return `$${cost.toFixed(4)}`;
15525
+ }
15526
+ function formatCumulativeUsage(config, inputTokens, outputTokens) {
15527
+ const cost = estimateCostUsd(effectiveModelId(config), inputTokens, outputTokens);
15528
+ const costStr = cost !== null ? ` \xB7 \uCD94\uC815 \uB204\uC801 \uBE44\uC6A9 ${fmtUsd(cost)}` : " \xB7 (\uB2E8\uAC00 \uBBF8\uB4F1\uB85D \u2014 \uD1A0\uD070\uB9CC)";
15529
+ return chalk2.dim(
15530
+ `\uB204\uC801 API \uC0AC\uC6A9: \uC785\uB825 ${fmtTok(inputTokens)} \xB7 \uCD9C\uB825 ${fmtTok(outputTokens)} \uD1A0\uD070${costStr}`
15531
+ );
15532
+ }
15533
+
15456
15534
  // src/chat.ts
15457
15535
  var HELP_TEXT = `
15458
15536
  \uD560 \uC218 \uC788\uB294 \uC77C:
@@ -15466,6 +15544,7 @@ var HELP_TEXT = `
15466
15544
  \uC2AC\uB798\uC2DC \uBA85\uB839:
15467
15545
  /model \u2014 \uD504\uB85C\uBC14\uC774\uB354/\uBAA8\uB378 \uC804\uD658
15468
15546
  /context \u2014 \uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uC0AC\uC6A9\uB7C9 \uD45C\uC2DC
15547
+ /usage \u2014 \uB204\uC801 API \uC0AC\uC6A9\uB7C9\xB7\uCD94\uC815 \uBE44\uC6A9 \uD45C\uC2DC (/cost)
15469
15548
  /clear \u2014 \uC0C8 \uC138\uC158 \uC2DC\uC791
15470
15549
  /help \u2014 \uC774 \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
15471
15550
  /exit \u2014 \uC885\uB8CC
@@ -15498,44 +15577,44 @@ async function runChat(opts) {
15498
15577
  const okCount = mcpManager.connectedServerNames.length;
15499
15578
  s.stop(okCount > 0 ? `MCP \uC11C\uBC84 ${okCount}\uAC1C \uC5F0\uACB0\uB428` : "MCP \uC5F0\uACB0 \uC644\uB8CC");
15500
15579
  } else {
15501
- process.stdout.write(chalk2.dim(`${connectMsg}
15580
+ process.stdout.write(chalk3.dim(`${connectMsg}
15502
15581
  `));
15503
15582
  await mcpManager.connect(servers);
15504
15583
  const okCount = mcpManager.connectedServerNames.length;
15505
15584
  process.stdout.write(
15506
- chalk2.dim(`${okCount > 0 ? `MCP \uC11C\uBC84 ${okCount}\uAC1C \uC5F0\uACB0\uB428` : "MCP \uC5F0\uACB0 \uC644\uB8CC"}
15585
+ chalk3.dim(`${okCount > 0 ? `MCP \uC11C\uBC84 ${okCount}\uAC1C \uC5F0\uACB0\uB428` : "MCP \uC5F0\uACB0 \uC644\uB8CC"}
15507
15586
  `)
15508
15587
  );
15509
15588
  }
15510
15589
  }
15511
15590
  for (const s of mcpManager.status()) {
15512
15591
  if (s.state === "failed") {
15513
- process.stdout.write(chalk2.dim(`MCP [${s.name}] \uC5F0\uACB0 \uC2E4\uD328: ${s.reason ?? ""}
15592
+ process.stdout.write(chalk3.dim(`MCP [${s.name}] \uC5F0\uACB0 \uC2E4\uD328: ${s.reason ?? ""}
15514
15593
  `));
15515
15594
  } else if (s.state === "skipped") {
15516
- process.stdout.write(chalk2.dim(`MCP [${s.name}] \uC2A4\uD0B5: ${s.reason ?? ""}
15595
+ process.stdout.write(chalk3.dim(`MCP [${s.name}] \uC2A4\uD0B5: ${s.reason ?? ""}
15517
15596
  `));
15518
15597
  }
15519
15598
  }
15520
15599
  for (const w of mcpManager.warnings) {
15521
- process.stdout.write(chalk2.yellow(`\u26A0 ${w}
15600
+ process.stdout.write(chalk3.yellow(`\u26A0 ${w}
15522
15601
  `));
15523
15602
  }
15524
15603
  }
15525
15604
  let store;
15526
15605
  if (opts.resumeId) {
15527
15606
  store = await SessionStore.load(opts.resumeId);
15528
- process.stdout.write(chalk2.dim(`\uC138\uC158 \uC7AC\uAC1C: ${opts.resumeId}
15607
+ process.stdout.write(chalk3.dim(`\uC138\uC158 \uC7AC\uAC1C: ${opts.resumeId}
15529
15608
  `));
15530
15609
  } else if (opts.continueLatest) {
15531
15610
  const sessions = await listSessions();
15532
15611
  const latest = sessions[0];
15533
15612
  if (!latest) {
15534
- process.stdout.write(chalk2.dim("\uC774\uC804 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0C8 \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4.\n"));
15613
+ process.stdout.write(chalk3.dim("\uC774\uC804 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0C8 \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4.\n"));
15535
15614
  store = await createNewStore(config, cwd);
15536
15615
  } else {
15537
15616
  store = await SessionStore.load(latest.id);
15538
- process.stdout.write(chalk2.dim(`\uC774\uC804 \uC138\uC158 \uC7AC\uAC1C: ${latest.id}
15617
+ process.stdout.write(chalk3.dim(`\uC774\uC804 \uC138\uC158 \uC7AC\uAC1C: ${latest.id}
15539
15618
  `));
15540
15619
  }
15541
15620
  } else {
@@ -15549,6 +15628,8 @@ async function runChat(opts) {
15549
15628
  let ctrlCCount = 0;
15550
15629
  let currentController = null;
15551
15630
  let lastContextTokens = 0;
15631
+ let cumulativeInputTokens = 0;
15632
+ let cumulativeOutputTokens = 0;
15552
15633
  let sharedActiveInterval = null;
15553
15634
  function clearSharedSpinner() {
15554
15635
  if (!sharedActiveInterval) return;
@@ -15563,30 +15644,30 @@ async function runChat(opts) {
15563
15644
  clearSharedSpinner();
15564
15645
  currentController.abort();
15565
15646
  currentController = null;
15566
- process.stdout.write(chalk2.yellow("\n\uD604\uC7AC \uC751\uB2F5\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15647
+ process.stdout.write(chalk3.yellow("\n\uD604\uC7AC \uC751\uB2F5\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15567
15648
  ctrlCCount = 0;
15568
15649
  } else {
15569
15650
  ctrlCCount++;
15570
15651
  if (ctrlCCount >= 2) {
15571
- process.stdout.write(chalk2.yellow("\n\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15652
+ process.stdout.write(chalk3.yellow("\n\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15572
15653
  rl.close();
15573
15654
  cleanSessionStaging(store.id).catch(() => {
15574
15655
  });
15575
15656
  mcpManager.disconnect().finally(() => process.exit(0));
15576
15657
  } else {
15577
- process.stdout.write(chalk2.dim("\n(\uD55C \uBC88 \uB354 Ctrl+C\uB97C \uB204\uB974\uBA74 \uC885\uB8CC\uB429\uB2C8\uB2E4)\n"));
15658
+ process.stdout.write(chalk3.dim("\n(\uD55C \uBC88 \uB354 Ctrl+C\uB97C \uB204\uB974\uBA74 \uC885\uB8CC\uB429\uB2C8\uB2E4)\n"));
15578
15659
  }
15579
15660
  }
15580
15661
  });
15581
- process.stdout.write(chalk2.bold(`kodocagent \uCC44\uD305 \uC2DC\uC791 (/help\uB85C \uB3C4\uC6C0\uB9D0)
15662
+ process.stdout.write(chalk3.bold(`kodocagent \uCC44\uD305 \uC2DC\uC791 (/help\uB85C \uB3C4\uC6C0\uB9D0)
15582
15663
  `));
15583
15664
  process.stdout.write(
15584
- chalk2.dim(`\uD504\uB85C\uBC14\uC774\uB354: ${config.provider}, \uBAA8\uB378: ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15665
+ chalk3.dim(`\uD504\uB85C\uBC14\uC774\uB354: ${config.provider}, \uBAA8\uB378: ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15585
15666
 
15586
15667
  `)
15587
15668
  );
15588
15669
  process.stdout.write(
15589
- chalk2.dim(
15670
+ chalk3.dim(
15590
15671
  '\uBB38\uC11C\uB97C \uC77D\uACE0 \uD45C\xB7\uC591\uC2DD \uC218\uC815, \uCC3E\uAE30\xB7\uBC14\uAFB8\uAE30, \uB418\uB3CC\uB9AC\uAE30\uAE4C\uC9C0 \uC790\uC5F0\uC5B4\uB85C \uC694\uCCAD\uD558\uC138\uC694. \uC608: "\uC774 \uD45C\uC758 \uD569\uACC4\uB97C \uB2E4\uC2DC \uACC4\uC0B0\uD574\uC918", "\uBC29\uAE08 \uC218\uC815 \uB418\uB3CC\uB824\uC918". \uC790\uC138\uD788: /help\n'
15591
15672
  )
15592
15673
  );
@@ -15604,7 +15685,7 @@ async function runChat(opts) {
15604
15685
  var clearActiveSpinner = clearActiveSpinner2;
15605
15686
  let userInput;
15606
15687
  try {
15607
- userInput = await rl.question(chalk2.green("You: "));
15688
+ userInput = await rl.question(chalk3.green("You: "));
15608
15689
  } catch {
15609
15690
  break;
15610
15691
  }
@@ -15616,15 +15697,28 @@ async function runChat(opts) {
15616
15697
  printContextUsage(lastContextTokens, config.maxContextTokens);
15617
15698
  continue;
15618
15699
  }
15700
+ if (trimmed.toLowerCase() === "/usage" || trimmed.toLowerCase() === "/cost") {
15701
+ if (cumulativeInputTokens === 0 && cumulativeOutputTokens === 0) {
15702
+ process.stdout.write(
15703
+ chalk3.dim("\uC544\uC9C1 API \uC0AC\uC6A9 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uBA74 \uB204\uC801\uB429\uB2C8\uB2E4.\n")
15704
+ );
15705
+ } else {
15706
+ process.stdout.write(
15707
+ `${formatCumulativeUsage(config, cumulativeInputTokens, cumulativeOutputTokens)}
15708
+ `
15709
+ );
15710
+ }
15711
+ continue;
15712
+ }
15619
15713
  const handled = await handleSlashCommand(trimmed, config, store, cwd, rl);
15620
15714
  if (handled === "exit") break;
15621
15715
  if (handled === "new-session") {
15622
15716
  store = await createNewStore(config, cwd);
15623
- process.stdout.write(chalk2.dim("\uC0C8 \uC138\uC158\uC774 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15717
+ process.stdout.write(chalk3.dim("\uC0C8 \uC138\uC158\uC774 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15624
15718
  } else if (handled && typeof handled === "object" && "config" in handled) {
15625
15719
  config = handled.config;
15626
15720
  process.stdout.write(
15627
- chalk2.dim(`\uBAA8\uB378 \uC804\uD658: ${config.provider} / ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15721
+ chalk3.dim(`\uBAA8\uB378 \uC804\uD658: ${config.provider} / ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15628
15722
  `)
15629
15723
  );
15630
15724
  }
@@ -15636,7 +15730,7 @@ async function runChat(opts) {
15636
15730
  model = createModel(config);
15637
15731
  } catch (err) {
15638
15732
  const msg = err instanceof Error ? err.message : String(err);
15639
- process.stderr.write(chalk2.red(`\uC624\uB958: ${msg}
15733
+ process.stderr.write(chalk3.red(`\uC624\uB958: ${msg}
15640
15734
  `));
15641
15735
  continue;
15642
15736
  }
@@ -15658,7 +15752,7 @@ async function runChat(opts) {
15658
15752
  });
15659
15753
  await session.loadHistory();
15660
15754
  currentController = new AbortController();
15661
- process.stdout.write(chalk2.bold("Assistant: "));
15755
+ process.stdout.write(chalk3.bold("Assistant: "));
15662
15756
  const isTTY = process.stdout.isTTY === true;
15663
15757
  const SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
15664
15758
  let activeToolLabel = "";
@@ -15673,7 +15767,7 @@ async function runChat(opts) {
15673
15767
  process.stdout.write(event.text);
15674
15768
  hasOutput = true;
15675
15769
  } else if (event.type === "tool-call") {
15676
- process.stdout.write(chalk2.dim(`
15770
+ process.stdout.write(chalk3.dim(`
15677
15771
  \u2699 ${formatToolCall(event.toolName, event.args)}
15678
15772
  `));
15679
15773
  if (isTTY) {
@@ -15685,7 +15779,7 @@ async function runChat(opts) {
15685
15779
  const frame = SPINNER_FRAMES[spinnerFrameIdx % SPINNER_FRAMES.length] ?? "\u280B";
15686
15780
  spinnerFrameIdx++;
15687
15781
  const plainText = `${frame} ${activeToolLabel} \uC2E4\uD589 \uC911\u2026`;
15688
- process.stdout.write(`\r${chalk2.dim(plainText)}`);
15782
+ process.stdout.write(`\r${chalk3.dim(plainText)}`);
15689
15783
  lastSpinnerWidth = plainText.length + 1;
15690
15784
  }, 80);
15691
15785
  }
@@ -15695,23 +15789,29 @@ async function runChat(opts) {
15695
15789
  clearActiveSpinner2();
15696
15790
  const statusLabel = event.isError ? "\uC2E4\uD328" : "\uC644\uB8CC";
15697
15791
  process.stdout.write(
15698
- chalk2.dim(` \u2514 ${activeToolLabel} ${statusLabel} (${elapsedSec}s)
15792
+ chalk3.dim(` \u2514 ${activeToolLabel} ${statusLabel} (${elapsedSec}s)
15699
15793
  `)
15700
15794
  );
15701
15795
  } else if (event.type === "approval-required") {
15702
15796
  } else if (event.type === "turn-complete") {
15703
15797
  if (event.usage) {
15704
15798
  lastContextTokens = event.usage.inputTokens;
15799
+ cumulativeInputTokens += event.usage.inputTokens;
15800
+ cumulativeOutputTokens += event.usage.outputTokens;
15705
15801
  process.stdout.write(
15706
15802
  `
15707
- ${formatContextUsage(event.usage.inputTokens, config.maxContextTokens)} ${chalk2.dim(
15708
- `\uCD9C\uB825 ${event.usage.outputTokens} \uD1A0\uD070`
15803
+ ${formatContextUsage(event.usage.inputTokens, config.maxContextTokens)} ${chalk3.dim(
15804
+ `\uC774\uBC88 \uD134 \uCD9C\uB825 ${event.usage.outputTokens} \uD1A0\uD070`
15709
15805
  )}
15806
+ `
15807
+ );
15808
+ process.stdout.write(
15809
+ `${formatCumulativeUsage(config, cumulativeInputTokens, cumulativeOutputTokens)}
15710
15810
  `
15711
15811
  );
15712
15812
  }
15713
15813
  } else if (event.type === "error") {
15714
- process.stderr.write(chalk2.red(`
15814
+ process.stderr.write(chalk3.red(`
15715
15815
  \uC624\uB958: ${event.message}
15716
15816
  `));
15717
15817
  }
@@ -15725,6 +15825,12 @@ ${formatContextUsage(event.usage.inputTokens, config.maxContextTokens)} ${chalk
15725
15825
  }
15726
15826
  }
15727
15827
  rl.close();
15828
+ if (cumulativeInputTokens > 0 || cumulativeOutputTokens > 0) {
15829
+ process.stdout.write(
15830
+ `${formatCumulativeUsage(config, cumulativeInputTokens, cumulativeOutputTokens)}
15831
+ `
15832
+ );
15833
+ }
15728
15834
  await cleanSessionStaging(store.id).catch(() => {
15729
15835
  });
15730
15836
  await mcpManager.disconnect();
@@ -15766,14 +15872,14 @@ function formatContextUsage(used, budget) {
15766
15872
  const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
15767
15873
  const pct = budget > 0 ? Math.round(used / budget * 100) : 0;
15768
15874
  const text3 = `\uCEE8\uD14D\uC2A4\uD2B8: ${fmt(used)} / ${fmt(budget)} \uD1A0\uD070 (${pct}%)`;
15769
- if (pct >= 90) return chalk2.red(text3);
15770
- if (pct >= 70) return chalk2.yellow(text3);
15771
- return chalk2.dim(text3);
15875
+ if (pct >= 90) return chalk3.red(text3);
15876
+ if (pct >= 70) return chalk3.yellow(text3);
15877
+ return chalk3.dim(text3);
15772
15878
  }
15773
15879
  function printContextUsage(used, budget) {
15774
15880
  if (used <= 0) {
15775
15881
  process.stdout.write(
15776
- chalk2.dim("\uC544\uC9C1 \uCE21\uC815\uB41C \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uBA74 \uD45C\uC2DC\uB429\uB2C8\uB2E4.\n")
15882
+ chalk3.dim("\uC544\uC9C1 \uCE21\uC815\uB41C \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uBA74 \uD45C\uC2DC\uB429\uB2C8\uB2E4.\n")
15777
15883
  );
15778
15884
  return;
15779
15885
  }
@@ -15794,7 +15900,7 @@ async function handleSlashCommand(cmd, config, _store, _cwd, rl) {
15794
15900
  switch (command) {
15795
15901
  case "/exit":
15796
15902
  case "/quit":
15797
- process.stdout.write(chalk2.yellow("\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15903
+ process.stdout.write(chalk3.yellow("\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15798
15904
  rl.close();
15799
15905
  return "exit";
15800
15906
  case "/help":
@@ -15805,7 +15911,7 @@ async function handleSlashCommand(cmd, config, _store, _cwd, rl) {
15805
15911
  case "/model":
15806
15912
  return handleModelSwitch(config);
15807
15913
  default:
15808
- process.stdout.write(chalk2.yellow(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}. /help\uB85C \uB3C4\uC6C0\uB9D0\uC744 \uD655\uC778\uD558\uC138\uC694.
15914
+ process.stdout.write(chalk3.yellow(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}. /help\uB85C \uB3C4\uC6C0\uB9D0\uC744 \uD655\uC778\uD558\uC138\uC694.
15809
15915
  `));
15810
15916
  return null;
15811
15917
  }
@@ -15827,7 +15933,7 @@ async function handleModelSwitch(_config) {
15827
15933
  options.push({ value: "__custom__", label: "\uC9C1\uC811 \uC785\uB825..." });
15828
15934
  if (options.length <= 1) {
15829
15935
  process.stdout.write(
15830
- chalk2.yellow("API \uD0A4\uAC00 \uC124\uC815\uB41C \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD0A4\uB97C \uC124\uC815\uD558\uC138\uC694.\n")
15936
+ chalk3.yellow("API \uD0A4\uAC00 \uC124\uC815\uB41C \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD0A4\uB97C \uC124\uC815\uD558\uC138\uC694.\n")
15831
15937
  );
15832
15938
  return null;
15833
15939
  }
@@ -15863,7 +15969,7 @@ async function handleModelSwitch(_config) {
15863
15969
  const isKnown = knownForProvider.includes(modelId2);
15864
15970
  if (!isKnown) {
15865
15971
  process.stdout.write(
15866
- chalk2.dim("\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uBAA8\uB378 ID\uC785\uB2C8\uB2E4 \u2014 \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC9C0\uC6D0\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694\n")
15972
+ chalk3.dim("\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uBAA8\uB378 ID\uC785\uB2C8\uB2E4 \u2014 \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC9C0\uC6D0\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694\n")
15867
15973
  );
15868
15974
  }
15869
15975
  const newConfig2 = { ...loadedConfig };
@@ -15885,7 +15991,7 @@ async function saveConfig2(config) {
15885
15991
  }
15886
15992
 
15887
15993
  // src/clean-cmd.ts
15888
- import chalk3 from "chalk";
15994
+ import chalk4 from "chalk";
15889
15995
  async function runClean(opts) {
15890
15996
  const stagingDeleted = await cleanAllStaging();
15891
15997
  let backupDeleted;
@@ -15901,7 +16007,7 @@ async function runClean(opts) {
15901
16007
  }
15902
16008
  const backupNote = opts.all ? "" : ` (\uBCF4\uC874 ${backupKept}\uAC1C)`;
15903
16009
  process.stdout.write(
15904
- chalk3.green(`\u2713 \uC2A4\uD14C\uC774\uC9D5 ${stagingDeleted}\uAC1C, \uBC31\uC5C5 ${backupDeleted}\uAC1C \uC815\uB9AC${backupNote}
16010
+ chalk4.green(`\u2713 \uC2A4\uD14C\uC774\uC9D5 ${stagingDeleted}\uAC1C, \uBC31\uC5C5 ${backupDeleted}\uAC1C \uC815\uB9AC${backupNote}
15905
16011
  `)
15906
16012
  );
15907
16013
  }
@@ -16004,7 +16110,7 @@ async function configShow() {
16004
16110
  }
16005
16111
 
16006
16112
  // src/mcp-cmd.ts
16007
- import chalk4 from "chalk";
16113
+ import chalk5 from "chalk";
16008
16114
  async function mcpList() {
16009
16115
  const config = await loadConfig();
16010
16116
  const cwd = process.cwd();
@@ -16023,22 +16129,22 @@ async function mcpList() {
16023
16129
  return;
16024
16130
  }
16025
16131
  process.stdout.write(
16026
- chalk4.bold("\uC774\uB984".padEnd(20)) + chalk4.bold("\uC0C1\uD0DC".padEnd(12)) + chalk4.bold("\uD234 \uC218".padEnd(8)) + chalk4.bold("\uC0AC\uC720") + "\n"
16132
+ chalk5.bold("\uC774\uB984".padEnd(20)) + chalk5.bold("\uC0C1\uD0DC".padEnd(12)) + chalk5.bold("\uD234 \uC218".padEnd(8)) + chalk5.bold("\uC0AC\uC720") + "\n"
16027
16133
  );
16028
16134
  process.stdout.write("\u2500".repeat(70) + "\n");
16029
16135
  for (const s of statuses) {
16030
- const stateStr = s.state === "connected" ? chalk4.green("\uC5F0\uACB0\uB428") : s.state === "failed" ? chalk4.red("\uC2E4\uD328") : chalk4.yellow("\uC2A4\uD0B5");
16136
+ const stateStr = s.state === "connected" ? chalk5.green("\uC5F0\uACB0\uB428") : s.state === "failed" ? chalk5.red("\uC2E4\uD328") : chalk5.yellow("\uC2A4\uD0B5");
16031
16137
  const toolCount = s.state === "connected" ? String(s.toolCount) : "\u2014";
16032
16138
  const reason = s.reason ?? "";
16033
16139
  process.stdout.write(
16034
16140
  s.name.padEnd(20) + stateStr.padEnd(20) + // chalk adds escape codes so pad more
16035
- toolCount.padEnd(8) + chalk4.dim(reason.slice(0, 50)) + "\n"
16141
+ toolCount.padEnd(8) + chalk5.dim(reason.slice(0, 50)) + "\n"
16036
16142
  );
16037
16143
  }
16038
16144
  if (manager.warnings.length > 0) {
16039
16145
  process.stdout.write("\n");
16040
16146
  for (const w of manager.warnings) {
16041
- process.stdout.write(chalk4.yellow(`\uACBD\uACE0: ${w}
16147
+ process.stdout.write(chalk5.yellow(`\uACBD\uACE0: ${w}
16042
16148
  `));
16043
16149
  }
16044
16150
  }
@@ -16051,7 +16157,7 @@ async function mcpTest(serverName) {
16051
16157
  const isSkipped = skipped.find((s) => s.name === serverName);
16052
16158
  if (isSkipped) {
16053
16159
  process.stderr.write(
16054
- chalk4.yellow(`\uC11C\uBC84 '${serverName}'\uC774(\uAC00) \uC2A4\uD0B5\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${isSkipped.reason}
16160
+ chalk5.yellow(`\uC11C\uBC84 '${serverName}'\uC774(\uAC00) \uC2A4\uD0B5\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${isSkipped.reason}
16055
16161
  `)
16056
16162
  );
16057
16163
  return;
@@ -16059,7 +16165,7 @@ async function mcpTest(serverName) {
16059
16165
  const serverConfig = servers.find((s) => s.name === serverName);
16060
16166
  if (!serverConfig) {
16061
16167
  process.stderr.write(
16062
- chalk4.red(
16168
+ chalk5.red(
16063
16169
  `\uC11C\uBC84 '${serverName}'\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
16064
16170
  'kodocagent mcp list'\uB85C \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC11C\uBC84\uB97C \uD655\uC778\uD558\uC138\uC694.
16065
16171
  `
@@ -16067,32 +16173,32 @@ async function mcpTest(serverName) {
16067
16173
  );
16068
16174
  return;
16069
16175
  }
16070
- process.stdout.write(chalk4.dim(`\uC11C\uBC84 '${serverName}' \uC5F0\uACB0 \uC911...
16176
+ process.stdout.write(chalk5.dim(`\uC11C\uBC84 '${serverName}' \uC5F0\uACB0 \uC911...
16071
16177
  `));
16072
16178
  if (serverConfig.type === "stdio") {
16073
- process.stdout.write(chalk4.dim(" (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC11C\uBC84 \uB2E4\uC6B4\uB85C\uB4DC\uB85C \uC2DC\uAC04\uC774 \uAC78\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4)\n"));
16179
+ process.stdout.write(chalk5.dim(" (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC11C\uBC84 \uB2E4\uC6B4\uB85C\uB4DC\uB85C \uC2DC\uAC04\uC774 \uAC78\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4)\n"));
16074
16180
  }
16075
16181
  const manager = new McpManager();
16076
16182
  await manager.connect([serverConfig]);
16077
16183
  const statuses = manager.status();
16078
16184
  const status = statuses[0];
16079
16185
  if (!status || status.state !== "connected") {
16080
- process.stderr.write(chalk4.red(`\uC5F0\uACB0 \uC2E4\uD328: ${status?.reason ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}
16186
+ process.stderr.write(chalk5.red(`\uC5F0\uACB0 \uC2E4\uD328: ${status?.reason ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}
16081
16187
  `));
16082
16188
  await manager.disconnect();
16083
16189
  return;
16084
16190
  }
16085
- process.stdout.write(chalk4.green(`\uC5F0\uACB0 \uC131\uACF5! \uD234 ${status.toolCount}\uAC1C
16191
+ process.stdout.write(chalk5.green(`\uC5F0\uACB0 \uC131\uACF5! \uD234 ${status.toolCount}\uAC1C
16086
16192
 
16087
16193
  `));
16088
16194
  const defs = manager.getToolDefinitions();
16089
16195
  if (defs.length === 0) {
16090
16196
  process.stdout.write("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD234\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
16091
16197
  } else {
16092
- process.stdout.write(chalk4.bold("\uD234 \uBAA9\uB85D:\n"));
16198
+ process.stdout.write(chalk5.bold("\uD234 \uBAA9\uB85D:\n"));
16093
16199
  for (const def of defs) {
16094
- process.stdout.write(` ${chalk4.cyan(def.name)}
16095
- ${chalk4.dim(def.description)}
16200
+ process.stdout.write(` ${chalk5.cyan(def.name)}
16201
+ ${chalk5.dim(def.description)}
16096
16202
  `);
16097
16203
  }
16098
16204
  }
@@ -16326,7 +16432,7 @@ program.option("-p, --print <prompt>", "\uB2E8\uBC1C \uC9C8\uC758 (\uBE44\uB300\
16326
16432
  const newVersion = await checkForUpdate(cliVersion()).catch(() => null);
16327
16433
  if (newVersion) {
16328
16434
  process.stdout.write(
16329
- chalk5.yellow(`\uC0C8 \uBC84\uC804 v${newVersion} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 'kodocagent update'\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694
16435
+ chalk6.yellow(`\uC0C8 \uBC84\uC804 v${newVersion} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 'kodocagent update'\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694
16330
16436
  `)
16331
16437
  );
16332
16438
  }
@@ -16356,15 +16462,15 @@ program.command("sessions").description("\uC138\uC158 \uBAA9\uB85D \uD45C\uC2DC"
16356
16462
  for (const s of sessions) {
16357
16463
  const dateStr = s.mtime.toLocaleString("ko-KR");
16358
16464
  process.stdout.write(
16359
- `${chalk5.bold(s.id)} ${chalk5.dim(dateStr)} ${chalk5.cyan(s.meta.provider)}/${s.meta.model ?? "(\uAE30\uBCF8)"}
16465
+ `${chalk6.bold(s.id)} ${chalk6.dim(dateStr)} ${chalk6.cyan(s.meta.provider)}/${s.meta.model ?? "(\uAE30\uBCF8)"}
16360
16466
  `
16361
16467
  );
16362
16468
  if (s.preview) {
16363
- process.stdout.write(` ${chalk5.italic(`"${s.preview}"`)}`);
16364
- process.stdout.write(` ${chalk5.dim(s.meta.cwd)}
16469
+ process.stdout.write(` ${chalk6.italic(`"${s.preview}"`)}`);
16470
+ process.stdout.write(` ${chalk6.dim(s.meta.cwd)}
16365
16471
  `);
16366
16472
  } else {
16367
- process.stdout.write(` ${chalk5.dim(s.meta.cwd)}
16473
+ process.stdout.write(` ${chalk6.dim(s.meta.cwd)}
16368
16474
  `);
16369
16475
  }
16370
16476
  }
@@ -16425,7 +16531,7 @@ async function pickSession() {
16425
16531
  }
16426
16532
  const sessions = await listSessions();
16427
16533
  if (sessions.length === 0) {
16428
- process.stdout.write(chalk5.dim("\uC7AC\uAC1C\uD560 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
16534
+ process.stdout.write(chalk6.dim("\uC7AC\uAC1C\uD560 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
16429
16535
  return void 0;
16430
16536
  }
16431
16537
  const options = sessions.map((s) => {
@@ -16446,17 +16552,17 @@ async function pickSession() {
16446
16552
  }
16447
16553
  function handleError(err) {
16448
16554
  if (err instanceof KodocError) {
16449
- process.stderr.write(chalk5.red(`\uC624\uB958: ${err.message}
16555
+ process.stderr.write(chalk6.red(`\uC624\uB958: ${err.message}
16450
16556
  `));
16451
16557
  if (err.hint) {
16452
- process.stderr.write(chalk5.yellow(` \u2192 ${err.hint}
16558
+ process.stderr.write(chalk6.yellow(` \u2192 ${err.hint}
16453
16559
  `));
16454
16560
  }
16455
16561
  } else if (err instanceof Error) {
16456
- process.stderr.write(chalk5.red(`\uC624\uB958: ${err.message}
16562
+ process.stderr.write(chalk6.red(`\uC624\uB958: ${err.message}
16457
16563
  `));
16458
16564
  } else {
16459
- process.stderr.write(chalk5.red(`\uC54C \uC218 \uC5C6\uB294 \uC624\uB958: ${String(err)}
16565
+ process.stderr.write(chalk6.red(`\uC54C \uC218 \uC5C6\uB294 \uC624\uB958: ${String(err)}
16460
16566
  `));
16461
16567
  }
16462
16568
  process.exit(1);
@@ -16475,7 +16581,7 @@ async function runSingleTurn(prompt) {
16475
16581
  await mcpManager.connect(servers);
16476
16582
  }
16477
16583
  for (const w of mcpManager.warnings) {
16478
- process.stdout.write(chalk5.yellow(`\u26A0 ${w}
16584
+ process.stdout.write(chalk6.yellow(`\u26A0 ${w}
16479
16585
  `));
16480
16586
  }
16481
16587
  }
@@ -16516,7 +16622,7 @@ async function runSingleTurn(prompt) {
16516
16622
  if (event.type === "text-delta") {
16517
16623
  process.stdout.write(event.text);
16518
16624
  } else if (event.type === "error") {
16519
- process.stderr.write(chalk5.red(`
16625
+ process.stderr.write(chalk6.red(`
16520
16626
  \uC624\uB958: ${event.message}
16521
16627
  `));
16522
16628
  }