@fgbg/mdocs 1.3.22 → 1.3.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +1 -1
  2. package/agent-skills/changelog/SKILL.md +13 -0
  3. package/agent-skills/getting-started-installation/SKILL.md +8 -2
  4. package/dist/server/agent/Agent/run-pi-agent.js +7 -0
  5. package/dist/server/agent/Agent/run-pi-agent.js.map +1 -1
  6. package/dist/server/agent/Agent/session-manager.js +354 -144
  7. package/dist/server/agent/Agent/session-manager.js.map +1 -1
  8. package/dist/server/agent/Agent/session-manager.queue.test.js +65 -0
  9. package/dist/server/agent/Agent/session-manager.queue.test.js.map +1 -0
  10. package/dist/server/agent/Agent/session-meta-lock.js +73 -0
  11. package/dist/server/agent/Agent/session-meta-lock.js.map +1 -0
  12. package/dist/server/agent/Agent/session-meta-lock.test.js +44 -0
  13. package/dist/server/agent/Agent/session-meta-lock.test.js.map +1 -0
  14. package/dist/server/agent/Agent/session-repair.test.js +40 -0
  15. package/dist/server/agent/Agent/session-repair.test.js.map +1 -0
  16. package/dist/server/agent/Agent/system-prompt.js +3 -3
  17. package/dist/server/agent/Agent/tool-ui-effects.js +41 -0
  18. package/dist/server/agent/Agent/tool-ui-effects.js.map +1 -1
  19. package/dist/server/agent/Agent/tools-documents.js +62 -12
  20. package/dist/server/agent/Agent/tools-documents.js.map +1 -1
  21. package/dist/server/agent/Agent/tools-overwrite.js +1 -1
  22. package/dist/server/agent/Agent/tools-overwrite.js.map +1 -1
  23. package/dist/server/agent/Agent/tools-registry.test.js +16 -0
  24. package/dist/server/agent/Agent/tools-registry.test.js.map +1 -1
  25. package/dist/server/agent/Agent/tools-sheet.js +918 -0
  26. package/dist/server/agent/Agent/tools-sheet.js.map +1 -0
  27. package/dist/server/agent/Agent/tools-sheet.test.js +202 -0
  28. package/dist/server/agent/Agent/tools-sheet.test.js.map +1 -0
  29. package/dist/server/agent/Agent/tools-template.js +4 -1
  30. package/dist/server/agent/Agent/tools-template.js.map +1 -1
  31. package/dist/server/cli/main.js +81 -4
  32. package/dist/server/cli/main.js.map +1 -1
  33. package/dist/server/cli/self-update.js +49 -20
  34. package/dist/server/cli/self-update.js.map +1 -1
  35. package/dist/server/cli/self-update.test.js +21 -2
  36. package/dist/server/cli/self-update.test.js.map +1 -1
  37. package/dist/server/config/index.js +2 -1
  38. package/dist/server/config/index.js.map +1 -1
  39. package/dist/server/documents/document.service.integration.test.js +67 -1
  40. package/dist/server/documents/document.service.integration.test.js.map +1 -1
  41. package/dist/server/documents/document.service.js +80 -5
  42. package/dist/server/documents/document.service.js.map +1 -1
  43. package/dist/server/main.js +6 -0
  44. package/dist/server/main.js.map +1 -1
  45. package/dist/server/routes/documents.routes.js +6 -2
  46. package/dist/server/routes/documents.routes.js.map +1 -1
  47. package/dist/server/search/embedding-model.js +33 -11
  48. package/dist/server/search/embedding-model.js.map +1 -1
  49. package/dist/shared/docPath.js +5 -2
  50. package/dist/shared/docPath.js.map +1 -1
  51. package/dist/shared/file-type-policy.js +15 -0
  52. package/dist/shared/file-type-policy.js.map +1 -1
  53. package/dist/shared/file-type-policy.test.js +17 -1
  54. package/dist/shared/file-type-policy.test.js.map +1 -1
  55. package/dist/shared/file-types.js +2 -0
  56. package/dist/shared/file-types.js.map +1 -1
  57. package/dist/shared/sheetDocument.js +781 -0
  58. package/dist/shared/sheetDocument.js.map +1 -0
  59. package/dist/shared/sheetDocument.test.js +179 -0
  60. package/dist/shared/sheetDocument.test.js.map +1 -0
  61. package/dist/shared/storagePath.js +15 -8
  62. package/dist/shared/storagePath.js.map +1 -1
  63. package/dist/web/assets/AgentChatPanel-NcsMAhWM.js +22 -0
  64. package/dist/web/assets/{AgentSkillFormCard-CzJG8FK4.js → AgentSkillFormCard-B99CncJO.js} +3 -3
  65. package/dist/web/assets/{AiWriteWorkbench-DiSDDAee.js → AiWriteWorkbench-DomEbwzU.js} +6 -6
  66. package/dist/web/assets/{DiagramEditor-NtNY3sZL-DfkpQX3P.js → DiagramEditor-NtNY3sZL-BhsjOCtK.js} +1 -1
  67. package/dist/web/assets/DocChrome-wfHrPEB7.js +11 -0
  68. package/dist/web/assets/DocumentEditor-CCuqHlmF.js +1 -0
  69. package/dist/web/assets/HtmlEditor-g2NNYmv2.js +1 -0
  70. package/dist/web/assets/{MergeView-CbFSN3Bi.js → MergeView-GL7xdFM4.js} +4 -4
  71. package/dist/web/assets/PlaygroundPage-Bb4X1bvp.js +8 -0
  72. package/dist/web/assets/SettingsPage-B4INgMK-.js +1 -0
  73. package/dist/web/assets/SheetEditor-Cka57s62.js +16 -0
  74. package/dist/web/assets/SheetEditor-gXD-sRv2.css +1 -0
  75. package/dist/web/assets/{Toolbar-D9FcRwg3.js → Toolbar-Bk3LKZfg.js} +19 -29
  76. package/dist/web/assets/VisitorPickerModal-zafJ6beK.js +1 -0
  77. package/dist/web/assets/{check-Bf8YbzH7.js → check-BMeXlsHC.js} +1 -1
  78. package/dist/web/assets/{copy-SBghRY2T.js → copy-CCsiKd_j.js} +1 -1
  79. package/dist/web/assets/{diagram-CgEd0hrk.js → diagram-BF8g9yg4.js} +3 -3
  80. package/dist/web/assets/docPlainCharCount-BjBZaoHy.js +11 -0
  81. package/dist/web/assets/{editor-CL03se2B.js → editor-DBWWidi3.js} +1 -1
  82. package/dist/web/assets/file-type-policy-C6Nun42Q.js +1 -0
  83. package/dist/web/assets/index-3B7_9QOQ.js +299 -0
  84. package/dist/web/assets/index-BF754bDB.css +1 -0
  85. package/dist/web/assets/italic-B6bGjo1_.js +11 -0
  86. package/dist/web/assets/{meta2d-1-g23JOU.js → meta2d-DP4Vt_ZE.js} +1 -1
  87. package/dist/web/assets/{meta2dEngine-BD2XbzaV-ulfRZEvk.js → meta2dEngine-BD2XbzaV-9HAADQ7Y.js} +1 -1
  88. package/dist/web/assets/{motion-Dyy8pB0Q.js → motion-BAXVQJfN.js} +1 -1
  89. package/dist/web/assets/{plus-CEapsZwl.js → plus-BD44y7Zf.js} +1 -1
  90. package/dist/web/assets/{react-BkX5Koku.js → react-2y9P8xJn.js} +171 -168
  91. package/dist/web/assets/{react-vendor-BuABiQkA.js → react-vendor-BVS1XoFi.js} +1 -1
  92. package/dist/web/assets/{ui-ZPt_BNBg.js → ui-CEFkTVnG.js} +200 -181
  93. package/dist/web/index.html +6 -6
  94. package/package.json +30 -21
  95. package/dist/web/assets/AgentChatPanel-BZlAgyrV.js +0 -22
  96. package/dist/web/assets/DocChrome-QTGER_Af.js +0 -21
  97. package/dist/web/assets/DocumentEditor-DYSRZnJa.js +0 -1
  98. package/dist/web/assets/HtmlEditor-oDPrhq3o.js +0 -1
  99. package/dist/web/assets/PlaygroundPage-DuMH_V-7.js +0 -8
  100. package/dist/web/assets/SettingsPage-C_RMO-5M.js +0 -1
  101. package/dist/web/assets/VisitorPickerModal-CM13Y6jQ.js +0 -1
  102. package/dist/web/assets/file-type-policy-GUk904tm.js +0 -1
  103. package/dist/web/assets/index-CIgmAX2Q.css +0 -1
  104. package/dist/web/assets/index-Cwy87sZC.js +0 -293
@@ -0,0 +1,918 @@
1
+ /**
2
+ * `.sheet` 工作簿 Agent tools(多工作表)。
3
+ * 写操作可选 sheet(表名或 id),默认当前 active 表。
4
+ */
5
+ import { Type } from "@earendil-works/pi-ai";
6
+ import { assertDocumentAccess, DocumentError } from "../../access/access-control.js";
7
+ import { getDocument, updateDocument } from "../../documents/document.service.js";
8
+ import { FILE_TYPE } from "../../../shared/file-types.js";
9
+ import { addWorkbookSheet, insertSheetRows, mergeSheetCells, normalizeStyle, overwriteSheetTab, parseSheetDocument, removeSheetColumns, removeSheetRows, removeWorkbookSheet, renameWorkbookSheet, replaceSheetTab, resolveSheetColumn, resolveSheetTab, serializeSheetDocument, setSheetCellStyles, setSheetCells, setSheetColumnName, sheetRowAsNamedCells, unmergeSheetCells, upsertSheetRow, } from "../../../shared/sheetDocument.js";
10
+ import { asToolResult } from "./tool-deps.js";
11
+ const LIST_DEFAULT_LIMIT = 50;
12
+ const LIST_MAX_LIMIT = 100;
13
+ const SET_CELLS_MAX = 200;
14
+ const INSERT_ROWS_MAX = 100;
15
+ const sheetParam = Type.Optional(Type.String({
16
+ description: "工作表名称或 id;省略则用当前 active 表",
17
+ }));
18
+ function asAgentError(err) {
19
+ if (err instanceof DocumentError) {
20
+ throw new Error(`${err.code}: ${err.message}`);
21
+ }
22
+ throw err;
23
+ }
24
+ function loadWorkbookForRead(documentId, visitorId) {
25
+ assertDocumentAccess(documentId, visitorId, "read");
26
+ const doc = getDocument(documentId, visitorId, "json");
27
+ if (doc.fileType !== FILE_TYPE.SHEET) {
28
+ throw new Error(`NOT_A_SHEET: document fileType is '${doc.fileType}', expected sheet`);
29
+ }
30
+ const workbook = parseSheetDocument(doc.content ?? "");
31
+ return { doc, workbook };
32
+ }
33
+ function loadWorkbookForEdit(documentId, visitorId) {
34
+ assertDocumentAccess(documentId, visitorId, "edit");
35
+ const doc = getDocument(documentId, visitorId, "json");
36
+ if (doc.fileType !== FILE_TYPE.SHEET) {
37
+ throw new Error(`NOT_A_SHEET: document fileType is '${doc.fileType}', expected sheet`);
38
+ }
39
+ const headCommitId = doc.headCommitId;
40
+ if (!headCommitId) {
41
+ throw new Error("DOCUMENT_HAS_NO_HEAD: 文档缺少 headCommitId,无法安全写入");
42
+ }
43
+ const workbook = parseSheetDocument(doc.content ?? "");
44
+ return { doc, workbook, headCommitId };
45
+ }
46
+ function requireTab(workbook, sheet) {
47
+ const tab = resolveSheetTab(workbook, sheet);
48
+ if (!tab) {
49
+ throw new Error(sheet?.trim()
50
+ ? `unknown sheet: ${sheet}`
51
+ : "workbook has no sheets");
52
+ }
53
+ return tab;
54
+ }
55
+ function saveWorkbook(params) {
56
+ return updateDocument({
57
+ actorVisitorId: params.visitorId,
58
+ documentId: params.documentId,
59
+ content: serializeSheetDocument(params.workbook),
60
+ version: { localBaseCommitId: params.headCommitId },
61
+ });
62
+ }
63
+ function sheetsSummary(workbook) {
64
+ return workbook.sheets.map((s) => ({
65
+ id: s.id,
66
+ name: s.name,
67
+ rowCount: s.rows.length,
68
+ columnCount: s.columns.length,
69
+ active: s.id === workbook.activeSheetId,
70
+ }));
71
+ }
72
+ export function sheetGetSchemaTool({ visitorId }) {
73
+ return {
74
+ name: "sheet_get_schema",
75
+ label: "读取表格结构",
76
+ description: "读取 .sheet 工作簿:全部工作表列表,以及指定表(默认 active)的列定义。改表前先调用。",
77
+ parameters: Type.Object({
78
+ documentId: Type.String({ description: "表格文档 documentId" }),
79
+ sheet: sheetParam,
80
+ }),
81
+ execute: async (_id, params) => {
82
+ const p = params;
83
+ const documentId = String(p.documentId ?? "").trim();
84
+ if (!documentId)
85
+ throw new Error("documentId is required");
86
+ try {
87
+ const { doc, workbook } = loadWorkbookForRead(documentId, visitorId);
88
+ const tab = requireTab(workbook, p.sheet);
89
+ return asToolResult({
90
+ documentId: doc.documentId,
91
+ displayName: doc.displayName,
92
+ fileType: doc.fileType,
93
+ headCommitId: doc.headCommitId ?? null,
94
+ sheets: sheetsSummary(workbook),
95
+ sheet: { id: tab.id, name: tab.name },
96
+ rowCount: tab.rows.length,
97
+ columns: tab.columns.map((c) => ({ id: c.id, name: c.name })),
98
+ });
99
+ }
100
+ catch (err) {
101
+ asAgentError(err);
102
+ }
103
+ },
104
+ };
105
+ }
106
+ export function sheetListRowsTool({ visitorId }) {
107
+ return {
108
+ name: "sheet_list_rows",
109
+ label: "列出表格行",
110
+ description: "分页列出指定工作表的行。每行含 rowId 与按列名展开的 cells。默认 limit=50,最大 100。",
111
+ parameters: Type.Object({
112
+ documentId: Type.String({ description: "表格文档 documentId" }),
113
+ sheet: sheetParam,
114
+ offset: Type.Optional(Type.Number({ description: "起始偏移,默认 0" })),
115
+ limit: Type.Optional(Type.Number({ description: "条数,默认 50,最大 100" })),
116
+ }),
117
+ execute: async (_id, params) => {
118
+ const p = params;
119
+ const documentId = String(p.documentId ?? "").trim();
120
+ if (!documentId)
121
+ throw new Error("documentId is required");
122
+ const offset = Math.max(0, Math.floor(Number(p.offset) || 0));
123
+ let limit = Math.floor(Number(p.limit) || LIST_DEFAULT_LIMIT);
124
+ if (!Number.isFinite(limit) || limit < 1)
125
+ limit = LIST_DEFAULT_LIMIT;
126
+ limit = Math.min(limit, LIST_MAX_LIMIT);
127
+ try {
128
+ const { doc, workbook } = loadWorkbookForRead(documentId, visitorId);
129
+ const tab = requireTab(workbook, p.sheet);
130
+ const slice = tab.rows.slice(offset, offset + limit);
131
+ return asToolResult({
132
+ documentId: doc.documentId,
133
+ displayName: doc.displayName,
134
+ sheet: { id: tab.id, name: tab.name },
135
+ total: tab.rows.length,
136
+ offset,
137
+ limit,
138
+ hasMore: offset + slice.length < tab.rows.length,
139
+ columns: tab.columns.map((c) => ({ id: c.id, name: c.name })),
140
+ rows: slice.map((r) => ({
141
+ rowId: r.id,
142
+ cells: sheetRowAsNamedCells(tab, r),
143
+ })),
144
+ });
145
+ }
146
+ catch (err) {
147
+ asAgentError(err);
148
+ }
149
+ },
150
+ };
151
+ }
152
+ export function sheetSetCellsTool({ visitorId }) {
153
+ return {
154
+ name: "sheet_set_cells",
155
+ label: "修改表格单元格",
156
+ description: "批量修改指定工作表若干单元格。column 可为列名或 columnId。单次最多 200 条。写后直接发布。",
157
+ parameters: Type.Object({
158
+ documentId: Type.String({ description: "表格文档 documentId" }),
159
+ sheet: sheetParam,
160
+ updates: Type.Array(Type.Object({
161
+ rowId: Type.String({ description: "行 id" }),
162
+ column: Type.String({ description: "列名或 columnId" }),
163
+ value: Type.String({ description: "单元格文本" }),
164
+ }), { description: "要写入的格子列表" }),
165
+ }),
166
+ execute: async (_id, params) => {
167
+ const p = params;
168
+ const documentId = String(p.documentId ?? "").trim();
169
+ if (!documentId)
170
+ throw new Error("documentId is required");
171
+ if (!Array.isArray(p.updates) || p.updates.length === 0) {
172
+ throw new Error("updates is required");
173
+ }
174
+ if (p.updates.length > SET_CELLS_MAX) {
175
+ throw new Error(`updates exceeds max ${SET_CELLS_MAX}`);
176
+ }
177
+ const updates = p.updates.map((u) => ({
178
+ rowId: String(u.rowId ?? ""),
179
+ column: String(u.column ?? ""),
180
+ value: u.value == null ? "" : String(u.value),
181
+ }));
182
+ try {
183
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
184
+ const tab = requireTab(workbook, p.sheet);
185
+ const { tab: nextTab, applied, errors } = setSheetCells(tab, updates);
186
+ if (applied === 0) {
187
+ return asToolResult({
188
+ status: "noop",
189
+ documentId,
190
+ sheet: { id: tab.id, name: tab.name },
191
+ applied: 0,
192
+ errors,
193
+ message: "没有成功写入任何单元格",
194
+ });
195
+ }
196
+ const nextWb = replaceSheetTab(workbook, nextTab);
197
+ const updated = saveWorkbook({
198
+ visitorId,
199
+ documentId,
200
+ workbook: nextWb,
201
+ headCommitId,
202
+ });
203
+ return asToolResult({
204
+ status: "updated",
205
+ documentId: updated.documentId,
206
+ displayName: updated.displayName,
207
+ headCommitId: updated.headCommitId ?? null,
208
+ sheet: { id: nextTab.id, name: nextTab.name },
209
+ applied,
210
+ errors,
211
+ rowCount: nextTab.rows.length,
212
+ });
213
+ }
214
+ catch (err) {
215
+ asAgentError(err);
216
+ }
217
+ },
218
+ };
219
+ }
220
+ export function sheetUpsertRowTool({ visitorId }) {
221
+ return {
222
+ name: "sheet_upsert_row",
223
+ label: "写入表格行",
224
+ description: "在指定工作表新建或更新一行。省略 rowId 则追加;提供已有 rowId 则 patch。写后直接发布。",
225
+ parameters: Type.Object({
226
+ documentId: Type.String({ description: "表格文档 documentId" }),
227
+ sheet: sheetParam,
228
+ rowId: Type.Optional(Type.String({ description: "已有行 id;省略则新建" })),
229
+ cells: Type.Array(Type.Object({
230
+ column: Type.String({ description: "列名或 columnId" }),
231
+ value: Type.String({ description: "单元格文本" }),
232
+ }), { description: "本行要写入的字段" }),
233
+ }),
234
+ execute: async (_id, params) => {
235
+ const p = params;
236
+ const documentId = String(p.documentId ?? "").trim();
237
+ if (!documentId)
238
+ throw new Error("documentId is required");
239
+ if (!Array.isArray(p.cells) || p.cells.length === 0) {
240
+ throw new Error("cells is required");
241
+ }
242
+ const cells = {};
243
+ for (const item of p.cells) {
244
+ const column = String(item.column ?? "").trim();
245
+ if (!column)
246
+ throw new Error("cells[].column is required");
247
+ cells[column] = item.value == null ? "" : String(item.value);
248
+ }
249
+ try {
250
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
251
+ const tab = requireTab(workbook, p.sheet);
252
+ const { tab: nextTab, row, created } = upsertSheetRow(tab, cells, p.rowId);
253
+ const nextWb = replaceSheetTab(workbook, nextTab);
254
+ const updated = saveWorkbook({
255
+ visitorId,
256
+ documentId,
257
+ workbook: nextWb,
258
+ headCommitId,
259
+ });
260
+ return asToolResult({
261
+ status: "updated",
262
+ documentId: updated.documentId,
263
+ displayName: updated.displayName,
264
+ headCommitId: updated.headCommitId ?? null,
265
+ sheet: { id: nextTab.id, name: nextTab.name },
266
+ created,
267
+ rowId: row.id,
268
+ cells: sheetRowAsNamedCells(nextTab, row),
269
+ rowCount: nextTab.rows.length,
270
+ });
271
+ }
272
+ catch (err) {
273
+ asAgentError(err);
274
+ }
275
+ },
276
+ };
277
+ }
278
+ export function sheetInsertRowsTool({ visitorId }) {
279
+ return {
280
+ name: "sheet_insert_rows",
281
+ label: "批量插入表格行",
282
+ description: "向指定工作表末尾一次追加多行(Excel 式 .sheet)。不改已有行;改已有行用 sheet_upsert_row / sheet_set_cells。单次最多 100 行。列名或 columnId。写后直接发布。",
283
+ parameters: Type.Object({
284
+ documentId: Type.String({ description: "表格文档 documentId" }),
285
+ sheet: sheetParam,
286
+ rows: Type.Array(Type.Object({
287
+ cells: Type.Array(Type.Object({
288
+ column: Type.String({ description: "列名或 columnId" }),
289
+ value: Type.String({ description: "单元格文本" }),
290
+ }), { description: "本行要写入的字段" }),
291
+ }), { description: "要追加的行列表" }),
292
+ }),
293
+ execute: async (_id, params) => {
294
+ const p = params;
295
+ const documentId = String(p.documentId ?? "").trim();
296
+ if (!documentId)
297
+ throw new Error("documentId is required");
298
+ if (!Array.isArray(p.rows) || p.rows.length === 0) {
299
+ throw new Error("rows is required");
300
+ }
301
+ if (p.rows.length > INSERT_ROWS_MAX) {
302
+ throw new Error(`rows exceeds max ${INSERT_ROWS_MAX}`);
303
+ }
304
+ const rows = [];
305
+ for (const row of p.rows) {
306
+ if (!Array.isArray(row.cells) || row.cells.length === 0) {
307
+ throw new Error("rows[].cells is required");
308
+ }
309
+ const cells = {};
310
+ for (const item of row.cells) {
311
+ const column = String(item.column ?? "").trim();
312
+ if (!column)
313
+ throw new Error("rows[].cells[].column is required");
314
+ cells[column] = item.value == null ? "" : String(item.value);
315
+ }
316
+ rows.push(cells);
317
+ }
318
+ try {
319
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
320
+ const tab = requireTab(workbook, p.sheet);
321
+ const { tab: nextTab, rowIds, inserted } = insertSheetRows(tab, rows);
322
+ const nextWb = replaceSheetTab(workbook, nextTab);
323
+ const updated = saveWorkbook({
324
+ visitorId,
325
+ documentId,
326
+ workbook: nextWb,
327
+ headCommitId,
328
+ });
329
+ return asToolResult({
330
+ status: "updated",
331
+ documentId: updated.documentId,
332
+ displayName: updated.displayName,
333
+ headCommitId: updated.headCommitId ?? null,
334
+ sheet: { id: nextTab.id, name: nextTab.name },
335
+ inserted,
336
+ rowIds,
337
+ rowCount: nextTab.rows.length,
338
+ });
339
+ }
340
+ catch (err) {
341
+ asAgentError(err);
342
+ }
343
+ },
344
+ };
345
+ }
346
+ export function sheetOverwriteTool({ visitorId }) {
347
+ return {
348
+ name: "sheet_overwrite",
349
+ label: "覆写表格",
350
+ description: "用完整的列名列表 + 二维行数据,替换 .sheet 中某一个工作表的全部内容(旧行列作废)。不改其它工作表。局部改格/行请用 sheet_set_cells / sheet_upsert_row / sheet_insert_rows。md/html 正文请用 overwrite_document。写后直接发布。",
351
+ parameters: Type.Object({
352
+ documentId: Type.String({ description: "表格文档 documentId" }),
353
+ sheet: sheetParam,
354
+ name: Type.Optional(Type.String({ description: "可选:同时改工作表名称" })),
355
+ columns: Type.Array(Type.String({ description: "列名" }), {
356
+ description: "新列名列表(顺序即列顺序),至少 1 个",
357
+ }),
358
+ rows: Type.Array(Type.Array(Type.String({ description: "单元格文本" })), {
359
+ description: "二维行数据;每行 values[i] 对应 columns[i];可为 []",
360
+ }),
361
+ }),
362
+ execute: async (_id, params) => {
363
+ const p = params;
364
+ const documentId = String(p.documentId ?? "").trim();
365
+ if (!documentId)
366
+ throw new Error("documentId is required");
367
+ if (!Array.isArray(p.columns) || p.columns.length === 0) {
368
+ throw new Error("columns is required");
369
+ }
370
+ if (!Array.isArray(p.rows)) {
371
+ throw new Error("rows is required");
372
+ }
373
+ const columns = p.columns.map((c) => String(c ?? ""));
374
+ const rows = p.rows.map((row) => {
375
+ if (!Array.isArray(row)) {
376
+ throw new Error("rows[] must be an array of cell values");
377
+ }
378
+ return row.map((v) => (v == null ? "" : String(v)));
379
+ });
380
+ try {
381
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
382
+ const tab = requireTab(workbook, p.sheet);
383
+ const nextTab = overwriteSheetTab(tab, {
384
+ name: p.name,
385
+ columns,
386
+ rows,
387
+ });
388
+ const nextWb = replaceSheetTab(workbook, nextTab);
389
+ const updated = saveWorkbook({
390
+ visitorId,
391
+ documentId,
392
+ workbook: nextWb,
393
+ headCommitId,
394
+ });
395
+ return asToolResult({
396
+ status: "updated",
397
+ documentId: updated.documentId,
398
+ displayName: updated.displayName,
399
+ headCommitId: updated.headCommitId ?? null,
400
+ sheet: { id: nextTab.id, name: nextTab.name },
401
+ columnCount: nextTab.columns.length,
402
+ rowCount: nextTab.rows.length,
403
+ });
404
+ }
405
+ catch (err) {
406
+ asAgentError(err);
407
+ }
408
+ },
409
+ };
410
+ }
411
+ export function sheetRenameColumnTool({ visitorId }) {
412
+ return {
413
+ name: "sheet_rename_column",
414
+ label: "重命名表格列",
415
+ description: "将指定工作表某一列改名。写后直接发布。",
416
+ parameters: Type.Object({
417
+ documentId: Type.String({ description: "表格文档 documentId" }),
418
+ sheet: sheetParam,
419
+ column: Type.String({ description: "当前列名或 columnId" }),
420
+ name: Type.String({ description: "新列名" }),
421
+ }),
422
+ execute: async (_id, params) => {
423
+ const p = params;
424
+ const documentId = String(p.documentId ?? "").trim();
425
+ const column = String(p.column ?? "").trim();
426
+ const name = String(p.name ?? "").trim();
427
+ if (!documentId)
428
+ throw new Error("documentId is required");
429
+ if (!column)
430
+ throw new Error("column is required");
431
+ if (!name)
432
+ throw new Error("name is required");
433
+ try {
434
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
435
+ const tab = requireTab(workbook, p.sheet);
436
+ const col = resolveSheetColumn(tab, column);
437
+ if (!col)
438
+ throw new Error(`unknown column: ${column}`);
439
+ const clash = tab.columns.find((c) => c.id !== col.id && c.name === name);
440
+ if (clash)
441
+ throw new Error(`column name already exists: ${name}`);
442
+ if (col.name === name) {
443
+ return asToolResult({
444
+ status: "noop",
445
+ documentId,
446
+ sheet: { id: tab.id, name: tab.name },
447
+ columnId: col.id,
448
+ name,
449
+ message: "列名未变化",
450
+ });
451
+ }
452
+ const nextTab = setSheetColumnName(tab, col.id, name);
453
+ const updated = saveWorkbook({
454
+ visitorId,
455
+ documentId,
456
+ workbook: replaceSheetTab(workbook, nextTab),
457
+ headCommitId,
458
+ });
459
+ return asToolResult({
460
+ status: "updated",
461
+ documentId: updated.documentId,
462
+ displayName: updated.displayName,
463
+ headCommitId: updated.headCommitId ?? null,
464
+ sheet: { id: nextTab.id, name: nextTab.name },
465
+ columnId: col.id,
466
+ previousName: col.name,
467
+ name,
468
+ columns: nextTab.columns.map((c) => ({ id: c.id, name: c.name })),
469
+ });
470
+ }
471
+ catch (err) {
472
+ asAgentError(err);
473
+ }
474
+ },
475
+ };
476
+ }
477
+ export function sheetDeleteRowsTool({ visitorId }) {
478
+ return {
479
+ name: "sheet_delete_rows",
480
+ label: "删除表格行",
481
+ description: "按 rowId 批量删除指定工作表的行。写后直接发布。",
482
+ parameters: Type.Object({
483
+ documentId: Type.String({ description: "表格文档 documentId" }),
484
+ sheet: sheetParam,
485
+ rowIds: Type.Array(Type.String(), { description: "要删除的行 id 列表" }),
486
+ }),
487
+ execute: async (_id, params) => {
488
+ const p = params;
489
+ const documentId = String(p.documentId ?? "").trim();
490
+ if (!documentId)
491
+ throw new Error("documentId is required");
492
+ if (!Array.isArray(p.rowIds) || p.rowIds.length === 0) {
493
+ throw new Error("rowIds is required");
494
+ }
495
+ try {
496
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
497
+ const tab = requireTab(workbook, p.sheet);
498
+ const { tab: nextTab, removed, missing } = removeSheetRows(tab, p.rowIds);
499
+ if (removed.length === 0) {
500
+ return asToolResult({
501
+ status: "noop",
502
+ documentId,
503
+ sheet: { id: tab.id, name: tab.name },
504
+ removed: [],
505
+ missing,
506
+ message: "没有删除任何行",
507
+ });
508
+ }
509
+ const updated = saveWorkbook({
510
+ visitorId,
511
+ documentId,
512
+ workbook: replaceSheetTab(workbook, nextTab),
513
+ headCommitId,
514
+ });
515
+ return asToolResult({
516
+ status: "updated",
517
+ documentId: updated.documentId,
518
+ displayName: updated.displayName,
519
+ headCommitId: updated.headCommitId ?? null,
520
+ sheet: { id: nextTab.id, name: nextTab.name },
521
+ removed,
522
+ missing,
523
+ rowCount: nextTab.rows.length,
524
+ });
525
+ }
526
+ catch (err) {
527
+ asAgentError(err);
528
+ }
529
+ },
530
+ };
531
+ }
532
+ export function sheetDeleteColumnsTool({ visitorId }) {
533
+ return {
534
+ name: "sheet_delete_columns",
535
+ label: "删除表格列",
536
+ description: "按列名或 columnId 批量删除指定工作表的列(至少保留一列)。写后直接发布。",
537
+ parameters: Type.Object({
538
+ documentId: Type.String({ description: "表格文档 documentId" }),
539
+ sheet: sheetParam,
540
+ columns: Type.Array(Type.String(), {
541
+ description: "列名或 columnId 列表",
542
+ }),
543
+ }),
544
+ execute: async (_id, params) => {
545
+ const p = params;
546
+ const documentId = String(p.documentId ?? "").trim();
547
+ if (!documentId)
548
+ throw new Error("documentId is required");
549
+ if (!Array.isArray(p.columns) || p.columns.length === 0) {
550
+ throw new Error("columns is required");
551
+ }
552
+ try {
553
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
554
+ const tab = requireTab(workbook, p.sheet);
555
+ const { tab: nextTab, removed, errors } = removeSheetColumns(tab, p.columns);
556
+ if (removed.length === 0) {
557
+ return asToolResult({
558
+ status: "noop",
559
+ documentId,
560
+ sheet: { id: tab.id, name: tab.name },
561
+ removed: [],
562
+ errors,
563
+ message: "没有删除任何列",
564
+ });
565
+ }
566
+ const updated = saveWorkbook({
567
+ visitorId,
568
+ documentId,
569
+ workbook: replaceSheetTab(workbook, nextTab),
570
+ headCommitId,
571
+ });
572
+ return asToolResult({
573
+ status: "updated",
574
+ documentId: updated.documentId,
575
+ displayName: updated.displayName,
576
+ headCommitId: updated.headCommitId ?? null,
577
+ sheet: { id: nextTab.id, name: nextTab.name },
578
+ removed,
579
+ errors,
580
+ columnCount: nextTab.columns.length,
581
+ columns: nextTab.columns.map((c) => ({ id: c.id, name: c.name })),
582
+ });
583
+ }
584
+ catch (err) {
585
+ asAgentError(err);
586
+ }
587
+ },
588
+ };
589
+ }
590
+ export function sheetAddSheetTool({ visitorId }) {
591
+ return {
592
+ name: "sheet_add_sheet",
593
+ label: "新建工作表",
594
+ description: "在同一 .sheet 工作簿内新增一个工作表标签。写后直接发布。",
595
+ parameters: Type.Object({
596
+ documentId: Type.String({ description: "表格文档 documentId" }),
597
+ name: Type.Optional(Type.String({ description: "工作表名称,可选" })),
598
+ }),
599
+ execute: async (_id, params) => {
600
+ const p = params;
601
+ const documentId = String(p.documentId ?? "").trim();
602
+ if (!documentId)
603
+ throw new Error("documentId is required");
604
+ try {
605
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
606
+ const { doc: nextWb, tab } = addWorkbookSheet(workbook, p.name);
607
+ const updated = saveWorkbook({
608
+ visitorId,
609
+ documentId,
610
+ workbook: nextWb,
611
+ headCommitId,
612
+ });
613
+ return asToolResult({
614
+ status: "updated",
615
+ documentId: updated.documentId,
616
+ displayName: updated.displayName,
617
+ headCommitId: updated.headCommitId ?? null,
618
+ sheet: { id: tab.id, name: tab.name },
619
+ sheets: sheetsSummary(nextWb),
620
+ });
621
+ }
622
+ catch (err) {
623
+ asAgentError(err);
624
+ }
625
+ },
626
+ };
627
+ }
628
+ export function sheetRenameSheetTool({ visitorId }) {
629
+ return {
630
+ name: "sheet_rename_sheet",
631
+ label: "重命名工作表",
632
+ description: "重命名工作簿内某个工作表标签(不是列名)。写后直接发布。",
633
+ parameters: Type.Object({
634
+ documentId: Type.String({ description: "表格文档 documentId" }),
635
+ sheet: Type.String({ description: "当前工作表名称或 id" }),
636
+ name: Type.String({ description: "新工作表名称" }),
637
+ }),
638
+ execute: async (_id, params) => {
639
+ const p = params;
640
+ const documentId = String(p.documentId ?? "").trim();
641
+ const sheet = String(p.sheet ?? "").trim();
642
+ const name = String(p.name ?? "").trim();
643
+ if (!documentId)
644
+ throw new Error("documentId is required");
645
+ if (!sheet)
646
+ throw new Error("sheet is required");
647
+ if (!name)
648
+ throw new Error("name is required");
649
+ try {
650
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
651
+ const before = requireTab(workbook, sheet);
652
+ const nextWb = renameWorkbookSheet(workbook, sheet, name);
653
+ const updated = saveWorkbook({
654
+ visitorId,
655
+ documentId,
656
+ workbook: nextWb,
657
+ headCommitId,
658
+ });
659
+ return asToolResult({
660
+ status: "updated",
661
+ documentId: updated.documentId,
662
+ displayName: updated.displayName,
663
+ headCommitId: updated.headCommitId ?? null,
664
+ sheet: { id: before.id, name },
665
+ previousName: before.name,
666
+ sheets: sheetsSummary(nextWb),
667
+ });
668
+ }
669
+ catch (err) {
670
+ asAgentError(err);
671
+ }
672
+ },
673
+ };
674
+ }
675
+ export function sheetDeleteSheetTool({ visitorId }) {
676
+ return {
677
+ name: "sheet_delete_sheet",
678
+ label: "删除工作表",
679
+ description: "删除工作簿内某个工作表标签(至少保留一个)。写后直接发布。",
680
+ parameters: Type.Object({
681
+ documentId: Type.String({ description: "表格文档 documentId" }),
682
+ sheet: Type.String({ description: "工作表名称或 id" }),
683
+ }),
684
+ execute: async (_id, params) => {
685
+ const p = params;
686
+ const documentId = String(p.documentId ?? "").trim();
687
+ const sheet = String(p.sheet ?? "").trim();
688
+ if (!documentId)
689
+ throw new Error("documentId is required");
690
+ if (!sheet)
691
+ throw new Error("sheet is required");
692
+ try {
693
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
694
+ const tab = requireTab(workbook, sheet);
695
+ const nextWb = removeWorkbookSheet(workbook, sheet);
696
+ const updated = saveWorkbook({
697
+ visitorId,
698
+ documentId,
699
+ workbook: nextWb,
700
+ headCommitId,
701
+ });
702
+ return asToolResult({
703
+ status: "updated",
704
+ documentId: updated.documentId,
705
+ displayName: updated.displayName,
706
+ headCommitId: updated.headCommitId ?? null,
707
+ removed: { id: tab.id, name: tab.name },
708
+ sheets: sheetsSummary(nextWb),
709
+ });
710
+ }
711
+ catch (err) {
712
+ asAgentError(err);
713
+ }
714
+ },
715
+ };
716
+ }
717
+ export function sheetSetCellStyleTool({ visitorId }) {
718
+ return {
719
+ name: "sheet_set_cell_style",
720
+ label: "设置单元格样式",
721
+ description: "批量设置 .sheet 单元格样式(底色 bg、字体色 color、粗体 bold、斜体 italic、字号 fontSize 12–24)。颜色须为 #RGB/#RRGGBB。style 传 null 清除样式。写后直接发布。",
722
+ parameters: Type.Object({
723
+ documentId: Type.String({ description: "表格文档 documentId" }),
724
+ sheet: sheetParam,
725
+ cells: Type.Array(Type.Object({
726
+ rowId: Type.String(),
727
+ column: Type.String({ description: "列名或 columnId" }),
728
+ style: Type.Union([
729
+ Type.Null(),
730
+ Type.Object({
731
+ bg: Type.Optional(Type.String()),
732
+ color: Type.Optional(Type.String()),
733
+ bold: Type.Optional(Type.Boolean()),
734
+ italic: Type.Optional(Type.Boolean()),
735
+ fontSize: Type.Optional(Type.Number()),
736
+ }),
737
+ ]),
738
+ })),
739
+ }),
740
+ execute: async (_id, params) => {
741
+ const p = params;
742
+ const documentId = String(p.documentId ?? "").trim();
743
+ if (!documentId)
744
+ throw new Error("documentId is required");
745
+ if (!Array.isArray(p.cells) || p.cells.length === 0) {
746
+ throw new Error("cells is required");
747
+ }
748
+ if (p.cells.length > SET_CELLS_MAX) {
749
+ throw new Error(`cells exceeds max ${SET_CELLS_MAX}`);
750
+ }
751
+ const updates = p.cells.map((c) => {
752
+ const styleRaw = c.style;
753
+ let style;
754
+ if (styleRaw === null)
755
+ style = null;
756
+ else
757
+ style = normalizeStyle(styleRaw) ?? {};
758
+ return {
759
+ rowId: String(c.rowId ?? ""),
760
+ column: String(c.column ?? ""),
761
+ style,
762
+ };
763
+ });
764
+ try {
765
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
766
+ const tab = requireTab(workbook, p.sheet);
767
+ const { tab: nextTab, applied, errors, warnings } = setSheetCellStyles(tab, updates);
768
+ const nextWb = replaceSheetTab(workbook, nextTab);
769
+ const updated = saveWorkbook({
770
+ visitorId,
771
+ documentId,
772
+ workbook: nextWb,
773
+ headCommitId,
774
+ });
775
+ return asToolResult({
776
+ status: "updated",
777
+ documentId,
778
+ displayName: updated.displayName,
779
+ headCommitId: updated.headCommitId ?? null,
780
+ sheet: { id: nextTab.id, name: nextTab.name },
781
+ applied,
782
+ errors,
783
+ warnings,
784
+ });
785
+ }
786
+ catch (err) {
787
+ asAgentError(err);
788
+ }
789
+ },
790
+ };
791
+ }
792
+ export function sheetMergeCellsTool({ visitorId }) {
793
+ return {
794
+ name: "sheet_merge_cells",
795
+ label: "合并单元格",
796
+ description: "合并 .sheet 矩形区域为合并单元格(Excel 式 colspan/rowspan)。须至少 2 格:" +
797
+ "传对角 startRowId+startColumn 与 endRowId+endColumn(列名或 columnId)。" +
798
+ "先 sheet_list_rows 取 rowId。与已有合并重叠则失败。拆分用 sheet_unmerge_cells。写后直接发布。",
799
+ parameters: Type.Object({
800
+ documentId: Type.String({ description: "表格文档 documentId" }),
801
+ sheet: sheetParam,
802
+ startRowId: Type.String({ description: "合并区起始行 id" }),
803
+ startColumn: Type.String({ description: "合并区起始列名或 columnId" }),
804
+ endRowId: Type.String({ description: "合并区结束行 id" }),
805
+ endColumn: Type.String({ description: "合并区结束列名或 columnId" }),
806
+ }),
807
+ execute: async (_id, params) => {
808
+ const p = params;
809
+ const documentId = String(p.documentId ?? "").trim();
810
+ if (!documentId)
811
+ throw new Error("documentId is required");
812
+ try {
813
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
814
+ const tab = requireTab(workbook, p.sheet);
815
+ const startCol = resolveSheetColumn(tab, String(p.startColumn ?? ""));
816
+ const endCol = resolveSheetColumn(tab, String(p.endColumn ?? ""));
817
+ if (!startCol || !endCol)
818
+ throw new Error("unknown column");
819
+ const nextTab = mergeSheetCells(tab, {
820
+ startRowId: String(p.startRowId ?? ""),
821
+ startColId: startCol.id,
822
+ endRowId: String(p.endRowId ?? ""),
823
+ endColId: endCol.id,
824
+ });
825
+ const nextWb = replaceSheetTab(workbook, nextTab);
826
+ const updated = saveWorkbook({
827
+ visitorId,
828
+ documentId,
829
+ workbook: nextWb,
830
+ headCommitId,
831
+ });
832
+ return asToolResult({
833
+ status: "updated",
834
+ documentId,
835
+ displayName: updated.displayName,
836
+ headCommitId: updated.headCommitId ?? null,
837
+ sheet: { id: nextTab.id, name: nextTab.name },
838
+ merge: nextTab.merges?.[nextTab.merges.length - 1] ?? null,
839
+ });
840
+ }
841
+ catch (err) {
842
+ asAgentError(err);
843
+ }
844
+ },
845
+ };
846
+ }
847
+ export function sheetUnmergeCellsTool({ visitorId }) {
848
+ return {
849
+ name: "sheet_unmerge_cells",
850
+ label: "拆分合并单元格",
851
+ description: "拆分 .sheet 中包含指定格子的合并区。列可用列名或 id。写后直接发布。",
852
+ parameters: Type.Object({
853
+ documentId: Type.String(),
854
+ sheet: sheetParam,
855
+ rowId: Type.String(),
856
+ column: Type.String({ description: "列名或 columnId" }),
857
+ }),
858
+ execute: async (_id, params) => {
859
+ const p = params;
860
+ const documentId = String(p.documentId ?? "").trim();
861
+ if (!documentId)
862
+ throw new Error("documentId is required");
863
+ try {
864
+ const { workbook, headCommitId } = loadWorkbookForEdit(documentId, visitorId);
865
+ const tab = requireTab(workbook, p.sheet);
866
+ const col = resolveSheetColumn(tab, String(p.column ?? ""));
867
+ if (!col)
868
+ throw new Error(`unknown column: ${p.column}`);
869
+ const { tab: nextTab, removed } = unmergeSheetCells(tab, String(p.rowId ?? ""), col.id);
870
+ if (!removed) {
871
+ return asToolResult({
872
+ status: "noop",
873
+ documentId,
874
+ message: "cell is not in a merge",
875
+ });
876
+ }
877
+ const nextWb = replaceSheetTab(workbook, nextTab);
878
+ const updated = saveWorkbook({
879
+ visitorId,
880
+ documentId,
881
+ workbook: nextWb,
882
+ headCommitId,
883
+ });
884
+ return asToolResult({
885
+ status: "updated",
886
+ documentId,
887
+ displayName: updated.displayName,
888
+ headCommitId: updated.headCommitId ?? null,
889
+ sheet: { id: nextTab.id, name: nextTab.name },
890
+ removed,
891
+ });
892
+ }
893
+ catch (err) {
894
+ asAgentError(err);
895
+ }
896
+ },
897
+ };
898
+ }
899
+ export function sheetTools(deps) {
900
+ return [
901
+ sheetGetSchemaTool(deps),
902
+ sheetListRowsTool(deps),
903
+ sheetSetCellsTool(deps),
904
+ sheetUpsertRowTool(deps),
905
+ sheetInsertRowsTool(deps),
906
+ sheetOverwriteTool(deps),
907
+ sheetRenameColumnTool(deps),
908
+ sheetDeleteRowsTool(deps),
909
+ sheetDeleteColumnsTool(deps),
910
+ sheetSetCellStyleTool(deps),
911
+ sheetMergeCellsTool(deps),
912
+ sheetUnmergeCellsTool(deps),
913
+ sheetAddSheetTool(deps),
914
+ sheetRenameSheetTool(deps),
915
+ sheetDeleteSheetTool(deps),
916
+ ];
917
+ }
918
+ //# sourceMappingURL=tools-sheet.js.map