@kodocagent/cli 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9851,6 +9851,49 @@ var KODOC_PATHS = {
9851
9851
  function projectMcpConfigPath(cwd) {
9852
9852
  return join(cwd, ".kodocagent", "mcp.json");
9853
9853
  }
9854
+ var PATTERNS = [
9855
+ {
9856
+ type: "\uC8FC\uBBFC\uB4F1\uB85D\uBC88\uD638",
9857
+ re: /\b\d{6}-[1-4]\d{6}\b/g,
9858
+ mask: (m) => `${m.slice(0, 8)}******`
9859
+ },
9860
+ {
9861
+ type: "\uC2E0\uC6A9\uCE74\uB4DC\uBC88\uD638",
9862
+ re: /\b\d{4}-\d{4}-\d{4}-\d{4}\b/g,
9863
+ mask: (m) => `${m.slice(0, 4)}-****-****-${m.slice(-4)}`
9864
+ },
9865
+ {
9866
+ type: "\uC804\uD654\uBC88\uD638",
9867
+ re: /\b0\d{1,2}-\d{3,4}-\d{4}\b/g,
9868
+ mask: (m) => {
9869
+ const p = m.split("-");
9870
+ return `${p[0]}-${"*".repeat((p[1] ?? "").length)}-${p[2]}`;
9871
+ }
9872
+ },
9873
+ {
9874
+ type: "\uC774\uBA54\uC77C",
9875
+ re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
9876
+ mask: (m) => {
9877
+ const [u, d] = m.split("@");
9878
+ return `${u?.[0] ?? ""}***@${d}`;
9879
+ }
9880
+ }
9881
+ ];
9882
+ function detectPii(text3) {
9883
+ if (!text3) return [];
9884
+ const out = [];
9885
+ for (const { type, re, mask } of PATTERNS) {
9886
+ const matches = text3.match(re);
9887
+ if (matches && matches.length > 0) {
9888
+ const uniq = [...new Set(matches)];
9889
+ out.push({ type, count: matches.length, masked: uniq.slice(0, 5).map(mask) });
9890
+ }
9891
+ }
9892
+ return out;
9893
+ }
9894
+ function summarizePii(findings) {
9895
+ return findings.map((f) => `${f.type} ${f.count}\uAC74`).join(", ");
9896
+ }
9854
9897
 
9855
9898
  // ../core/dist/index.js
9856
9899
  import { stepCountIs, streamText } from "ai";
@@ -9869,7 +9912,12 @@ import { appendFile, mkdir as mkdir2, readdir, readFile as readFile2, stat } fro
9869
9912
  import { join as join2 } from "path";
9870
9913
  import { tool } from "ai";
9871
9914
  function buildSystemPrompt(ctx) {
9872
- const stable = [ROLE_SECTION, DOCUMENT_RULES_SECTION, LAW_RULES_SECTION].join("\n\n");
9915
+ const stable = [
9916
+ ROLE_SECTION,
9917
+ DOCUMENT_RULES_SECTION,
9918
+ EDIT_SAFETY_SECTION,
9919
+ LAW_RULES_SECTION
9920
+ ].join("\n\n");
9873
9921
  const dynamic = buildDynamicContext(ctx);
9874
9922
  return `${stable}
9875
9923
 
@@ -9892,6 +9940,15 @@ var DOCUMENT_RULES_SECTION = `## \uBB38\uC11C \uADDC\uCE59
9892
9940
  6. \uC0AC\uC6A9\uC790\uAC00 \uC218\uC815\uC548\uC744 \uAC70\uC808\uD558\uBA74 \uAC19\uC740 \uC81C\uC548\uC744 \uC790\uB3D9\uC73C\uB85C \uBC18\uBCF5\uD558\uC9C0 \uB9D0\uACE0, \uC0AC\uC6A9\uC790\uC758 \uB2E4\uC74C \uC9C0\uC2DC\uB97C \uAE30\uB2E4\uB9AC\uC138\uC694.
9893
9941
  7. \uD070 \uBB38\uC11C\uB294 \uBA3C\uC800 \`outline\`\uC73C\uB85C \uAD6C\uC870\uB97C \uD30C\uC545\uD558\uACE0, \`search\`\uB098 \`pages\`\uB85C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uC5B4 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC544\uB07C\uC138\uC694.
9894
9942
  8. \uC0AC\uC6A9\uC790\uAC00 \uC9C1\uC804 \uBCC0\uACBD\uC744 \uB418\uB3CC\uB9AC\uAE38 \uC6D0\uD558\uBA74 \`list_backups\`\uB85C \uBC31\uC5C5\uC744 \uD655\uC778\uD558\uACE0 \`restore_backup\`\uC73C\uB85C \uBCF5\uC6D0\uD558\uC138\uC694. \uBCF5\uC6D0\uB3C4 \uC2B9\uC778\uC744 \uAC70\uCE58\uBA70, \uBCF5\uC6D0 \uC804 \uD604\uC7AC \uC0C1\uD0DC\uAC00 \uC790\uB3D9 \uBC31\uC5C5\uB429\uB2C8\uB2E4.`;
9943
+ var EDIT_SAFETY_SECTION = `## \uD3B8\uC9D1 \uC548\uC804 \uADDC\uCE59
9944
+
9945
+ 1. \uC694\uCCAD\uBC1B\uC740 \uBD80\uBD84\uB9CC \uC218\uC815\uD558\uACE0, \uC694\uCCAD\uD558\uC9C0 \uC54A\uC740 \uBB38\uC7A5\xB7\uC11C\uC2DD\xB7\uAD6C\uC870\xB7\uD45C\uD604\uC740 \uADF8\uB300\uB85C \uB461\uB2C8\uB2E4. \uBB38\uC11C \uC804\uCCB4\uB97C \uC784\uC758\uB85C \uC7AC\uC791\uC131\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
9946
+ 2. \uBB38\uC11C\uC5D0 \uC5C6\uB294 \uC815\uBCF4\uB97C \uB9CC\uB4E4\uC5B4\uB0B4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC218\uCE58\xB7\uAE08\uC561\xB7\uB0A0\uC9DC\xB7\uC778\uBA85\xB7\uAE30\uAD00\uBA85\xB7\uACC4\uC57D \uC870\uD56D \uB4F1\uC744 \uCD94\uCE21\uD574 \uC0C8\uB85C \uC4F0\uAC70\uB098 \uBC14\uAFB8\uC9C0 \uC54A\uC73C\uBA70, \uBD88\uD655\uC2E4\uD558\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uD569\uB2C8\uB2E4.
9947
+ 3. \uC0AC\uC6A9\uC790\uAC00 \uBA85\uC2DC\uC801\uC73C\uB85C \uBC14\uAFB8\uB77C\uACE0 \uD558\uC9C0 \uC54A\uC740 \uD55C \uC22B\uC790\xB7\uAE08\uC561\xB7\uB0A0\uC9DC\xB7\uB2E8\uC704\xB7\uACE0\uC720\uBA85\uC0AC\xB7\uBC95\uB839 \uC870\uBB38 \uBC88\uD638\uC640 \uC778\uC6A9\uBD80\uD638(" ", ' ', \u300C \u300D) \uB0B4\uBD80 \uB0B4\uC6A9\uC740 \uBCF4\uC874\uD569\uB2C8\uB2E4.
9948
+ 4. \uC804\uBB38 \uC6A9\uC5B4\uB294 \uC784\uC758\uB85C \uB2E4\uB978 \uD45C\uD604\uC73C\uB85C \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uBB38\uC11C \uC804\uCCB4\uC5D0\uC11C \uC77C\uAD00\uB418\uAC8C \uC720\uC9C0\uD569\uB2C8\uB2E4.
9949
+ 5. \uACC4\uC57D\uC11C\xB7\uC57D\uAD00\xB7\uACF5\uC2DC\xB7\uB17C\uBB38 \uB4F1 \uBC95\uC801\xB7\uACF5\uC2DD \uBB38\uC11C\uB294 \uB2E8\uC5B4 \uD558\uB098\uAC00 \uC758\uBBF8\uB97C \uBC14\uAFC0 \uC218 \uC788\uC73C\uBBC0\uB85C, \uD45C\uD604\uC744 \uC790\uB3D9\uC73C\uB85C \uB2E4\uB4EC\uC9C0 \uB9D0\uACE0 \uC0AC\uC6A9\uC790\uAC00 \uC9C0\uC815\uD55C \uBCC0\uACBD\uB9CC \uC218\uD589\uD569\uB2C8\uB2E4.
9950
+ 6. \uC758\uBBF8\uAC00 \uBC14\uB014 \uC218 \uC788\uB294 \uC218\uC815\uC740 \uC2E4\uD589 \uC804\uC5D0 \uADF8 \uC0AC\uC2E4\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC54C\uB9AC\uACE0, \uC218\uC815 \uC81C\uC548\uC758 \uC694\uC57D(summary)\uC5D0\uB294 \uBB34\uC5C7\uC744\xB7\uC65C \uBC14\uAFB8\uB294\uC9C0 \uD568\uAED8 \uC801\uC2B5\uB2C8\uB2E4.
9951
+ 7. \uAC1C\uC778\uC815\uBCF4(\uC8FC\uBBFC\uB4F1\uB85D\uBC88\uD638\xB7\uC804\uD654\uBC88\uD638\xB7\uC774\uBA54\uC77C\xB7\uACC4\uC88C\xB7\uCE74\uB4DC\uBC88\uD638 \uB4F1)\uAC00 \uC788\uC744 \uC218 \uC788\uB294 \uBB38\uC11C\uB97C \uB2E4\uB8F0 \uB54C \uC8FC\uC758\uD558\uACE0, \uC0AC\uC6A9\uC790\uAC00 \uAC1C\uC778\uC815\uBCF4 \uC810\uAC80\uC744 \uC694\uCCAD\uD558\uBA74 \`scan_pii\` \uB3C4\uAD6C\uB85C \uD655\uC778\uD569\uB2C8\uB2E4.`;
9895
9952
  var LAW_RULES_SECTION = `## \uBC95\uB839 \uADDC\uCE59
9896
9953
 
9897
9954
  1. \uBC95\uB839 \uC778\uC6A9 \uD615\uC2DD: \u300C\uBC95\uB839\uBA85\u300D \uC81CN\uC870 \uC81CN\uD56D \uC81CN\uD638
@@ -10017,6 +10074,26 @@ var AgentSession = class {
10017
10074
  this.openDocuments.push(p);
10018
10075
  }
10019
10076
  }
10077
+ /**
10078
+ * 저장된 메시지(어시스턴트 tool-call 파트)에서 열람·작성한 문서 경로를 도출해 기록한다.
10079
+ * 세션이 턴마다 재생성되므로, 시스템 프롬프트("열람한 문서")가 이전 턴의 열람 기록을
10080
+ * 반영하려면 히스토리에서 다시 복원해야 한다.
10081
+ */
10082
+ recordOpenDocumentsFromMessage(msg) {
10083
+ const content = msg.content;
10084
+ if (!Array.isArray(content)) return;
10085
+ for (const part of content) {
10086
+ if (!part || typeof part !== "object") continue;
10087
+ const p = part;
10088
+ if (p.type !== "tool-call" || !p.input) continue;
10089
+ if (p.toolName === "read_document" || p.toolName === "write_new_document" || p.toolName === "write_new_spreadsheet") {
10090
+ this.recordOpenDocument(p.input.path);
10091
+ } else if (p.toolName === "compare_documents") {
10092
+ this.recordOpenDocument(p.input.pathA);
10093
+ this.recordOpenDocument(p.input.pathB);
10094
+ }
10095
+ }
10096
+ }
10020
10097
  /** approval-required 이벤트를 run() 스트림에 전달하기 위한 큐 */
10021
10098
  pendingApprovalEvents = [];
10022
10099
  /**
@@ -10025,6 +10102,9 @@ var AgentSession = class {
10025
10102
  async loadHistory() {
10026
10103
  const msgs = await this.opts.store.loadMessages();
10027
10104
  this.messages.push(...msgs);
10105
+ for (const msg of msgs) {
10106
+ this.recordOpenDocumentsFromMessage(msg);
10107
+ }
10028
10108
  }
10029
10109
  /**
10030
10110
  * 사용자 메시지를 처리하고 에이전트 이벤트를 스트리밍한다.
@@ -10079,7 +10159,7 @@ var AgentSession = class {
10079
10159
  } else if (part.toolName === "compare_documents") {
10080
10160
  this.recordOpenDocument(inp.pathA);
10081
10161
  this.recordOpenDocument(inp.pathB);
10082
- } else if (part.toolName === "write_new_document") {
10162
+ } else if (part.toolName === "write_new_document" || part.toolName === "write_new_spreadsheet") {
10083
10163
  this.recordOpenDocument(inp.path);
10084
10164
  }
10085
10165
  } catch {
@@ -10788,6 +10868,13 @@ var ToolRegistry = class {
10788
10868
  return outcome;
10789
10869
  }
10790
10870
  const { proposal, commit } = outcome;
10871
+ const piiFindings = detectPii(proposal.diff ?? "");
10872
+ if (piiFindings.length > 0) {
10873
+ proposal.warnings = [
10874
+ ...proposal.warnings ?? [],
10875
+ `\uAC1C\uC778\uC815\uBCF4 \uD3EC\uD568: \uC774\uBC88 \uBCC0\uACBD \uC601\uC5ED\uC5D0 ${summarizePii(piiFindings)}\uC774(\uAC00) \uC788\uC2B5\uB2C8\uB2E4. \uC678\uBD80 \uACF5\uC720 \uC2DC \uC8FC\uC758\uD558\uC138\uC694.`
10876
+ ];
10877
+ }
10791
10878
  getEventEmitter()?.(proposal);
10792
10879
  const approvalResult = await approvalHandler(proposal);
10793
10880
  if (!approvalResult.approved) {
@@ -10883,14 +10970,18 @@ import { parse as parse5 } from "@clazic/kordoc";
10883
10970
  import { z as z11 } from "zod";
10884
10971
  import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
10885
10972
  import { z as z12 } from "zod";
10886
- import { stat as stat6 } from "fs/promises";
10973
+ import { readFile as readFile12 } from "fs/promises";
10887
10974
  import { extname as extname12 } from "path";
10888
- import { markdownToHwpx as markdownToHwpx3 } from "@clazic/kordoc";
10975
+ import { parse as parse6 } from "@clazic/kordoc";
10889
10976
  import { z as z13 } from "zod";
10890
- import { stat as stat7 } from "fs/promises";
10977
+ import { stat as stat6 } from "fs/promises";
10891
10978
  import { extname as extname13 } from "path";
10892
- import ExcelJS2 from "exceljs";
10979
+ import { markdownToHwpx as markdownToHwpx3 } from "@clazic/kordoc";
10893
10980
  import { z as z14 } from "zod";
10981
+ import { stat as stat7 } from "fs/promises";
10982
+ import { extname as extname14 } from "path";
10983
+ import ExcelJS2 from "exceljs";
10984
+ import { z as z15 } from "zod";
10894
10985
  async function resolveSafePath(cwd, p) {
10895
10986
  const normalizedCwd = normalize(cwd).normalize("NFC");
10896
10987
  const normalizedP = p.normalize("NFC");
@@ -11022,8 +11113,10 @@ async function cleanOldBackups(maxAgeDays = 30, baseDir) {
11022
11113
  function resolveOutputPath(targetPath) {
11023
11114
  const ext = extname(targetPath).toLowerCase();
11024
11115
  if (ext === ".hwp") {
11025
- const outputPath = targetPath.slice(0, -4) + ".hwpx";
11026
- return { outputPath, willConvertFormat: ".hwp \u2192 .hwpx" };
11116
+ return { outputPath: targetPath.slice(0, -4) + ".hwpx", willConvertFormat: ".hwp \u2192 .hwpx" };
11117
+ }
11118
+ if (ext === ".xls") {
11119
+ return { outputPath: targetPath.slice(0, -4) + ".xlsx", willConvertFormat: ".xls \u2192 .xlsx" };
11027
11120
  }
11028
11121
  return { outputPath: targetPath, willConvertFormat: void 0 };
11029
11122
  }
@@ -11153,19 +11246,29 @@ var restoreBackupTool = {
11153
11246
  if (candidates.length === 0) {
11154
11247
  return `\uBC31\uC5C5\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${targetBase}. list_backups\uB85C \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBC31\uC5C5\uC744 \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
11155
11248
  }
11249
+ const byNewest = (a, b) => b.mtimeMs !== a.mtimeMs ? b.mtimeMs - a.mtimeMs : b.tsToken.localeCompare(a.tsToken);
11156
11250
  let chosen;
11157
- if (input.backup) {
11158
- const found = candidates.find(
11159
- (c) => c.filename === input.backup || c.fullPath.endsWith(input.backup)
11160
- );
11161
- if (!found) {
11162
- return `\uC9C0\uC815\uD55C \uBC31\uC5C5\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.backup}. list_backups\uB85C \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBC31\uC5C5\uC744 \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
11251
+ let ambiguityNote = null;
11252
+ const requested = input.backup?.trim();
11253
+ if (requested) {
11254
+ const exact = candidates.find((c) => c.filename === requested);
11255
+ if (exact) {
11256
+ chosen = exact;
11257
+ } else {
11258
+ const matches = candidates.filter(
11259
+ (c) => c.filename.endsWith(requested) || c.fullPath.endsWith(requested)
11260
+ );
11261
+ if (matches.length === 0) {
11262
+ return `\uC9C0\uC815\uD55C \uBC31\uC5C5\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.backup}. list_backups\uB85C \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBC31\uC5C5\uC758 \uC815\uD655\uD55C \uD30C\uC77C\uBA85\uC744 \uD655\uC778\uD558\uC138\uC694.`;
11263
+ }
11264
+ matches.sort(byNewest);
11265
+ chosen = matches[0];
11266
+ if (matches.length > 1) {
11267
+ ambiguityNote = `'${input.backup}'\uC640 \uC77C\uCE58\uD558\uB294 \uBC31\uC5C5\uC774 ${matches.length}\uAC1C\uC5EC\uC11C \uAC00\uC7A5 \uCD5C\uADFC(${formatTimestamp(chosen.tsToken)}) \uAC83\uC744 \uC120\uD0DD\uD588\uC2B5\uB2C8\uB2E4. \uD2B9\uC815 \uBC31\uC5C5\uC744 \uC6D0\uD558\uBA74 list_backups\uC758 \uC804\uCCB4 \uD30C\uC77C\uBA85\uC744 \uC9C0\uC815\uD558\uC138\uC694.`;
11268
+ }
11163
11269
  }
11164
- chosen = found;
11165
11270
  } else {
11166
- candidates.sort(
11167
- (a, b) => b.mtimeMs !== a.mtimeMs ? b.mtimeMs - a.mtimeMs : b.tsToken.localeCompare(a.tsToken)
11168
- );
11271
+ candidates.sort(byNewest);
11169
11272
  chosen = candidates[0];
11170
11273
  }
11171
11274
  let backupBytes;
@@ -11206,12 +11309,15 @@ var restoreBackupTool = {
11206
11309
  ].join("\n");
11207
11310
  }
11208
11311
  const warnings = [];
11209
- const autoSelected = !input.backup && candidates.length > 1;
11312
+ const autoSelected = !requested && candidates.length > 1;
11210
11313
  if (autoSelected) {
11211
11314
  warnings.push(
11212
11315
  `\uBC31\uC5C5\uC744 \uC9C0\uC815\uD558\uC9C0 \uC54A\uC544 \uAC00\uC7A5 \uCD5C\uADFC \uBC31\uC5C5(${formatTimestamp(chosen.tsToken)})\uC744 \uC790\uB3D9\uC73C\uB85C \uC120\uD0DD\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uB978 \uBC31\uC5C5\uC744 \uC6D0\uD558\uBA74 list_backups\uB85C \uD655\uC778 \uD6C4 backup \uD30C\uB77C\uBBF8\uD130\uB97C \uC9C0\uC815\uD558\uC138\uC694.`
11213
11316
  );
11214
11317
  }
11318
+ if (ambiguityNote) {
11319
+ warnings.push(ambiguityNote);
11320
+ }
11215
11321
  warnings.push(
11216
11322
  "\uBCF5\uC6D0\uC744 \uC2E4\uD589\uD558\uBA74 \uD604\uC7AC \uD30C\uC77C\uB3C4 \uBC31\uC5C5\uB41C \uB4A4 \uB36E\uC5B4\uC4F0\uC5EC\uC9D1\uB2C8\uB2E4(\uBCF5\uC6D0 \uC790\uCCB4\uB3C4 \uB418\uB3CC\uB9B4 \uC218 \uC788\uC74C)."
11217
11323
  );
@@ -13116,7 +13222,7 @@ var proposeFindReplaceTool = {
13116
13222
  const remaining = countOccurrences(normAfter, normFind);
13117
13223
  if (remaining > 0) {
13118
13224
  warnings.push(
13119
- `\uC77C\uBD80 "${input.find}"(${remaining}\uACF3)\uC774 \uAD50\uCCB4\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uAC00 \uC5EC\uB7EC \uC11C\uC2DD \uB7F0\uC5D0 \uB098\uB258\uC5B4 \uC788\uC5B4 \uACBD\uACC4\uB97C \uAC00\uB85C\uC9C0\uB974\uB294 \uD328\uD134\uC740 \uAD50\uCCB4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC11C\uC2DD\uC774 \uB098\uB25C \uD14D\uC2A4\uD2B8). \uC774\uBBF8 \uAD50\uCCB4\uB41C ${replacedCount}\uACF3\uC740 \uC815\uC0C1 \uBC18\uC601\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`
13225
+ `\uC77C\uBD80 "${input.find}"(${remaining}\uACF3)\uC774 \uAD50\uCCB4\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uAC00 \uC5EC\uB7EC \uC11C\uC2DD \uB7F0\uC5D0 \uB098\uB258\uC5B4 \uC788\uC5B4 \uACBD\uACC4\uB97C \uAC00\uB85C\uC9C0\uB974\uB294 \uD328\uD134\uC740 \uAD50\uCCB4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC11C\uC2DD\uC774 \uB098\uB25C \uD14D\uC2A4\uD2B8). \uC774\uBBF8 \uAD50\uCCB4\uB41C ${replacedCount}\uACF3\uC740 \uC815\uC0C1 \uBC18\uC601\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB0A8\uC740 ${remaining}\uACF3\uC740 \uD45C \uC548\uC758 \uC140\uC774\uBA74 propose_cell_edit\uC73C\uB85C \uD55C \uACF3\uC529 \uC218\uC815\uD558\uAC70\uB098, \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C \uC9C1\uC811 \uCC3E\uAE30\xB7\uBC14\uAFB8\uAE30\uB85C \uCC98\uB9AC\uD558\uC138\uC694.`
13120
13226
  );
13121
13227
  }
13122
13228
  }
@@ -13319,23 +13425,25 @@ var proposeSheetEditTool = {
13319
13425
  return `\uC624\uB958: \uC6CC\uD06C\uBD81 \uC800\uC7A5 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4: ${msg}`;
13320
13426
  }
13321
13427
  const stagedData = modifiedBuffer;
13322
- const stagedPath = await stageFile(ctx.sessionId, safePath, stagedData);
13428
+ const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13429
+ const stagedPath = await stageFile(ctx.sessionId, outputPath, stagedData);
13323
13430
  const proposalId = crypto.randomUUID();
13324
13431
  return {
13325
13432
  proposal: {
13326
13433
  id: proposalId,
13327
13434
  kind: "sheet-edit",
13328
- targetPath: safePath,
13435
+ targetPath: outputPath,
13329
13436
  stagedPath,
13330
13437
  summary: input.summary,
13331
13438
  diff,
13332
- warnings: []
13439
+ warnings: [],
13440
+ willConvertFormat
13333
13441
  },
13334
13442
  commit: async () => {
13335
- const backupPath = await backupFile(safePath);
13336
- await commitStaged(stagedPath, safePath);
13443
+ const backupPath = await backupFile(outputPath);
13444
+ await commitStaged(stagedPath, outputPath);
13337
13445
  const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
13338
- return `\uC800\uC7A5 \uC644\uB8CC: ${safePath}${backupInfo}`;
13446
+ return `\uC800\uC7A5 \uC644\uB8CC: ${outputPath}${backupInfo}`;
13339
13447
  }
13340
13448
  };
13341
13449
  }
@@ -14453,8 +14561,8 @@ var readFileTool = {
14453
14561
  if (info.size > MAX_SIZE) {
14454
14562
  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.`;
14455
14563
  }
14456
- const { extname: extname14 } = await import("path");
14457
- const ext = extname14(safePath).toLowerCase();
14564
+ const { extname: extname15 } = await import("path");
14565
+ const ext = extname15(safePath).toLowerCase();
14458
14566
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
14459
14567
  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.`;
14460
14568
  }
@@ -14468,10 +14576,64 @@ var readFileTool = {
14468
14576
  return content;
14469
14577
  }
14470
14578
  };
14579
+ var PLAIN_TEXT_EXTS2 = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text"]);
14580
+ var scanPiiSchema = z13.object({
14581
+ path: z13.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
14582
+ });
14583
+ var scanPiiTool = {
14584
+ name: "scan_pii",
14585
+ description: "\uBB38\uC11C\uC5D0\uC11C \uD55C\uAD6D \uAC1C\uC778\uC815\uBCF4(\uC8FC\uBBFC\uB4F1\uB85D\uBC88\uD638\xB7\uC2E0\uC6A9\uCE74\uB4DC\uBC88\uD638\xB7\uC804\uD654\uBC88\uD638\xB7\uC774\uBA54\uC77C)\uB97C \uD0D0\uC9C0\uD569\uB2C8\uB2E4. \uC6D0\uBB38 \uAC12\uC740 \uBC18\uD658\uD558\uC9C0 \uC54A\uACE0 \uB9C8\uC2A4\uD0B9\uB41C \uC608\uC2DC\uB9CC \uD45C\uC2DC\uD569\uB2C8\uB2E4. \uC678\uBD80 \uACF5\uC720 \uC804 \uAC80\uD1A0\uC5D0 \uD65C\uC6A9\uD558\uC138\uC694.",
14586
+ inputSchema: scanPiiSchema,
14587
+ requiresApproval: false,
14588
+ execute: async ({
14589
+ input,
14590
+ ctx
14591
+ }) => {
14592
+ let safePath;
14593
+ try {
14594
+ safePath = await resolveSafePath(ctx.cwd, input.path);
14595
+ } catch (e) {
14596
+ const msg = e instanceof Error ? e.message : String(e);
14597
+ return `\uC624\uB958: \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14598
+ }
14599
+ const ext = extname12(safePath).toLowerCase();
14600
+ let text3;
14601
+ if (PLAIN_TEXT_EXTS2.has(ext)) {
14602
+ try {
14603
+ text3 = await readFile12(safePath, "utf-8");
14604
+ } catch (e) {
14605
+ const msg = e instanceof Error ? e.message : String(e);
14606
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14607
+ }
14608
+ } else {
14609
+ let parseResult;
14610
+ try {
14611
+ parseResult = await parse6(safePath);
14612
+ } catch (e) {
14613
+ const msg = e instanceof Error ? e.message : String(e);
14614
+ return `\uC624\uB958: \uBB38\uC11C\uB97C \uD30C\uC2F1\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14615
+ }
14616
+ if (!parseResult.success) {
14617
+ return `\uC624\uB958: \uBB38\uC11C\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${parseResult.error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`;
14618
+ }
14619
+ text3 = parseResult.markdown;
14620
+ }
14621
+ const findings = detectPii(text3);
14622
+ if (findings.length === 0) {
14623
+ return `\uAC1C\uC778\uC815\uBCF4\uAC00 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: ${input.path}`;
14624
+ }
14625
+ const lines = [`\uBC1C\uACAC\uB41C \uAC1C\uC778\uC815\uBCF4 (${input.path}):`];
14626
+ for (const f of findings) {
14627
+ lines.push(`- ${f.type}: ${f.count}\uAC74 (\uC608: ${f.masked.join(", ")})`);
14628
+ }
14629
+ lines.push("\u203B \uB9C8\uC2A4\uD0B9\uB41C \uC608\uC2DC\uC774\uBA70 \uC6D0\uBB38 \uAC12\uC740 \uD45C\uC2DC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC678\uBD80 \uACF5\uC720 \uC804 \uD655\uC778\uD558\uC138\uC694.");
14630
+ return lines.join("\n");
14631
+ }
14632
+ };
14471
14633
  var MAX_PREVIEW_CHARS = 1e4;
14472
- var writeNewDocumentSchema = z13.object({
14473
- path: z13.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14474
- markdown: z13.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
14634
+ var writeNewDocumentSchema = z14.object({
14635
+ path: z14.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14636
+ markdown: z14.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
14475
14637
  });
14476
14638
  var writeNewDocumentTool = {
14477
14639
  name: "write_new_document",
@@ -14483,7 +14645,7 @@ var writeNewDocumentTool = {
14483
14645
  ctx
14484
14646
  }) => {
14485
14647
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14486
- const ext = extname12(safePath).toLowerCase();
14648
+ const ext = extname13(safePath).toLowerCase();
14487
14649
  try {
14488
14650
  await stat6(safePath);
14489
14651
  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.`;
@@ -14533,12 +14695,12 @@ ${preview}`,
14533
14695
  };
14534
14696
  }
14535
14697
  };
14536
- var writeNewSpreadsheetSchema = z14.object({
14537
- path: z14.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14538
- sheets: z14.array(
14539
- z14.object({
14540
- name: z14.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
14541
- rows: z14.array(z14.array(z14.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
14698
+ var writeNewSpreadsheetSchema = z15.object({
14699
+ path: z15.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14700
+ sheets: z15.array(
14701
+ z15.object({
14702
+ name: z15.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
14703
+ rows: z15.array(z15.array(z15.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
14542
14704
  })
14543
14705
  ).min(1).describe("\uC0DD\uC131\uD560 \uC2DC\uD2B8 \uBAA9\uB85D")
14544
14706
  });
@@ -14552,7 +14714,7 @@ var writeNewSpreadsheetTool = {
14552
14714
  ctx
14553
14715
  }) => {
14554
14716
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14555
- const ext = extname13(safePath).toLowerCase();
14717
+ const ext = extname14(safePath).toLowerCase();
14556
14718
  if (ext !== ".xlsx") {
14557
14719
  return `\uC624\uB958: write_new_spreadsheet\uC740 .xlsx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD655\uC7A5\uC790: ${ext}.`;
14558
14720
  }
@@ -14612,6 +14774,7 @@ function createDocTools(_ctx) {
14612
14774
  listFilesTool,
14613
14775
  listBackupsTool,
14614
14776
  readFileTool,
14777
+ scanPiiTool,
14615
14778
  proposeEditTool,
14616
14779
  proposeFormFillTool,
14617
14780
  proposeCellEditTool,
@@ -14711,6 +14874,14 @@ function createCliApprovalHandler() {
14711
14874
 
14712
14875
  // src/chat.ts
14713
14876
  var HELP_TEXT = `
14877
+ \uD560 \uC218 \uC788\uB294 \uC77C:
14878
+ \u2022 \uBB38\uC11C \uC77D\uAE30\xB7\uC694\uC57D\xB7\uAC80\uD1A0 \u2014 .hwp/.hwpx/.docx/.xlsx/.pdf (\uC608: "\uC774 \uBCF4\uB3C4\uC790\uB8CC \uC694\uC57D\uD574\uC918")
14879
+ \u2022 \uD45C\xB7\uC591\uC2DD \uC218\uC815 \u2014 \uC140 \uAC12, \uC591\uC2DD \uBE48\uCE78, \uD589\xB7\uC5F4 \uCD94\uAC00/\uC0AD\uC81C (\uC608: "\uC774 \uD45C\uC758 \uAE08\uC561\uC744 30000\uC73C\uB85C \uACE0\uCCD0\uC918")
14880
+ \u2022 \uBB38\uC11C \uC804\uCCB4 \uCC3E\uAE30\xB7\uBC14\uAFB8\uAE30 (\uC608: "'\uAD6D\uBBFC\uC8FC\uAD8C'\uC744 '\uAD6D\uBBFC\uC911\uC2EC'\uC73C\uB85C \uB2E4 \uBC14\uAFD4\uC918")
14881
+ \u2022 \uB418\uB3CC\uB9AC\uAE30 \u2014 \uC9C1\uC804 \uBCC0\uACBD\uC744 \uBC31\uC5C5\uC73C\uB85C \uBCF5\uC6D0 (\uC608: "\uBC29\uAE08 \uC218\uC815 \uB418\uB3CC\uB824\uC918")
14882
+ \u2022 \uD55C\uAD6D \uBC95\uB839 \uAE30\uBC18 \uAC80\uD1A0 (\uC608: "\uC774 \uCDE8\uC5C5\uADDC\uCE59\uC774 \uADFC\uB85C\uAE30\uC900\uBC95\uC5D0 \uB9DE\uB294\uC9C0 \uBD10\uC918")
14883
+ \u203B .hwp\uB294 \uD55C\uAE00\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5\uD55C \uD6C4 \uC218\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.
14884
+
14714
14885
  \uC2AC\uB798\uC2DC \uBA85\uB839:
14715
14886
  /model \u2014 \uD504\uB85C\uBC14\uC774\uB354/\uBAA8\uB378 \uC804\uD658
14716
14887
  /context \u2014 \uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uC0AC\uC6A9\uB7C9 \uD45C\uC2DC
@@ -14833,6 +15004,11 @@ async function runChat(opts) {
14833
15004
 
14834
15005
  `)
14835
15006
  );
15007
+ process.stdout.write(
15008
+ chalk2.dim(
15009
+ '\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'
15010
+ )
15011
+ );
14836
15012
  while (true) {
14837
15013
  let clearActiveSpinner2 = function() {
14838
15014
  if (!sharedActiveInterval) return;
@@ -15426,10 +15602,10 @@ async function runOnboarding() {
15426
15602
 
15427
15603
  // src/update.ts
15428
15604
  import { spawn } from "child_process";
15429
- import { mkdir as mkdir4, readFile as readFile12, writeFile as writeFile3 } from "fs/promises";
15605
+ import { mkdir as mkdir4, readFile as readFile13, writeFile as writeFile3 } from "fs/promises";
15430
15606
  import { dirname as dirname3 } from "path";
15431
15607
  function compareSemver(a, b) {
15432
- const parse6 = (v) => {
15608
+ const parse7 = (v) => {
15433
15609
  const clean = v.replace(/^v/, "").split("-")[0] ?? "";
15434
15610
  const parts = clean.split(".").map((p) => {
15435
15611
  const n = parseInt(p, 10);
@@ -15437,8 +15613,8 @@ function compareSemver(a, b) {
15437
15613
  });
15438
15614
  return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
15439
15615
  };
15440
- const [aMaj, aMin, aPat] = parse6(a);
15441
- const [bMaj, bMin, bPat] = parse6(b);
15616
+ const [aMaj, aMin, aPat] = parse7(a);
15617
+ const [bMaj, bMin, bPat] = parse7(b);
15442
15618
  if (aMaj !== bMaj) return aMaj < bMaj ? -1 : 1;
15443
15619
  if (aMin !== bMin) return aMin < bMin ? -1 : 1;
15444
15620
  if (aPat !== bPat) return aPat < bPat ? -1 : 1;
@@ -15447,7 +15623,7 @@ function compareSemver(a, b) {
15447
15623
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
15448
15624
  async function readCache(cachePath) {
15449
15625
  try {
15450
- const raw = await readFile12(cachePath, "utf-8");
15626
+ const raw = await readFile13(cachePath, "utf-8");
15451
15627
  const parsed = JSON.parse(raw);
15452
15628
  if (parsed !== null && typeof parsed === "object" && "checkedAt" in parsed && "latest" in parsed && typeof parsed.checkedAt === "string" && typeof parsed.latest === "string") {
15453
15629
  return parsed;
@@ -15730,7 +15906,9 @@ async function runSingleTurn(prompt) {
15730
15906
  });
15731
15907
  const tools = new ToolRegistry();
15732
15908
  for (const tool2 of createDocTools({ cwd })) {
15733
- tools.register(tool2);
15909
+ const t = tool2;
15910
+ if (t.requiresApproval) continue;
15911
+ tools.register(t);
15734
15912
  }
15735
15913
  for (const mcpTool of mcpManager.getToolDefinitions()) {
15736
15914
  tools.register(mcpTool);