@tita-agent/dsh-data-table 0.0.0-stage → 0.2.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.
Files changed (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -2
  3. package/cordis.patch.yml +7 -0
  4. package/lib/client.js +16766 -0
  5. package/lib/contract-envelope.d.ts +91 -0
  6. package/lib/contract-envelope.js +106 -0
  7. package/lib/contract-layout.d.ts +130 -0
  8. package/lib/contract-layout.js +138 -0
  9. package/lib/contract-refs.d.ts +39 -0
  10. package/lib/contract-refs.js +9 -0
  11. package/lib/contract-version.d.ts +311 -0
  12. package/lib/contract-version.js +270 -0
  13. package/lib/contract-wire.d.ts +147 -0
  14. package/lib/contract-wire.js +99 -0
  15. package/lib/contracts.d.ts +12 -0
  16. package/lib/contracts.js +12 -0
  17. package/lib/types/audit.d.ts +42 -0
  18. package/lib/types/audit.js +39 -0
  19. package/lib/types/client/ProjectImportMenu.d.ts +30 -0
  20. package/lib/types/client/ProjectImportMenu.js +70 -0
  21. package/lib/types/client/SheetPanel.d.ts +33 -0
  22. package/lib/types/client/SheetPanel.js +1766 -0
  23. package/lib/types/client/SheetToggle.d.ts +18 -0
  24. package/lib/types/client/SheetToggle.js +26 -0
  25. package/lib/types/client/index.d.ts +15 -0
  26. package/lib/types/client/index.js +110 -0
  27. package/lib/types/client/persistence/changes.d.ts +15 -0
  28. package/lib/types/client/persistence/changes.js +51 -0
  29. package/lib/types/client/persistence/ids.d.ts +34 -0
  30. package/lib/types/client/persistence/ids.js +48 -0
  31. package/lib/types/client/persistence/merge.d.ts +35 -0
  32. package/lib/types/client/persistence/merge.js +362 -0
  33. package/lib/types/client/store.d.ts +61 -0
  34. package/lib/types/client/store.js +152 -0
  35. package/lib/types/client/surface-context.d.ts +27 -0
  36. package/lib/types/client/surface-context.js +104 -0
  37. package/lib/types/client/surface-control.d.ts +2 -0
  38. package/lib/types/client/surface-control.js +12 -0
  39. package/lib/types/client/transport/workbookApi.d.ts +159 -0
  40. package/lib/types/client/transport/workbookApi.js +246 -0
  41. package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
  42. package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
  43. package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
  44. package/lib/types/client/univer/WorkbookCanvas.js +87 -0
  45. package/lib/types/client/univer/WorkbookView.d.ts +3 -0
  46. package/lib/types/client/univer/WorkbookView.js +2 -0
  47. package/lib/types/client/univer/loader.d.ts +18 -0
  48. package/lib/types/client/univer/loader.js +81 -0
  49. package/lib/types/client/univer/standalone-entry.d.ts +1 -0
  50. package/lib/types/client/univer/standalone-entry.js +19 -0
  51. package/lib/types/client/versions/VersionChrome.d.ts +33 -0
  52. package/lib/types/client/versions/VersionChrome.js +21 -0
  53. package/lib/types/client/versions/inspect.d.ts +70 -0
  54. package/lib/types/client/versions/inspect.js +88 -0
  55. package/lib/types/client/versions/preview-decoration.d.ts +11 -0
  56. package/lib/types/client/versions/preview-decoration.js +54 -0
  57. package/lib/types/client/versions/records.d.ts +19 -0
  58. package/lib/types/client/versions/records.js +70 -0
  59. package/lib/types/client/workspace-host.d.ts +61 -0
  60. package/lib/types/client/workspace-host.js +124 -0
  61. package/lib/types/collaboration-hub.d.ts +31 -0
  62. package/lib/types/collaboration-hub.js +69 -0
  63. package/lib/types/contract/ids.d.ts +24 -0
  64. package/lib/types/contract/ids.js +46 -0
  65. package/lib/types/contract/mutations.d.ts +25 -0
  66. package/lib/types/contract/mutations.js +782 -0
  67. package/lib/types/contract/operations.d.ts +54 -0
  68. package/lib/types/contract/operations.js +203 -0
  69. package/lib/types/contract/snapshots.d.ts +176 -0
  70. package/lib/types/contract/snapshots.js +351 -0
  71. package/lib/types/contract/univer-api-catalog.d.ts +29 -0
  72. package/lib/types/contract/univer-api-catalog.js +264 -0
  73. package/lib/types/errors.d.ts +8 -0
  74. package/lib/types/errors.js +12 -0
  75. package/lib/types/http/assets-routes.d.ts +3 -0
  76. package/lib/types/http/assets-routes.js +169 -0
  77. package/lib/types/http/collaboration.d.ts +10 -0
  78. package/lib/types/http/collaboration.js +154 -0
  79. package/lib/types/http/history.d.ts +15 -0
  80. package/lib/types/http/history.js +134 -0
  81. package/lib/types/http/project-files.d.ts +79 -0
  82. package/lib/types/http/project-files.js +206 -0
  83. package/lib/types/http/project-scope.d.ts +50 -0
  84. package/lib/types/http/project-scope.js +52 -0
  85. package/lib/types/http/routes.d.ts +98 -0
  86. package/lib/types/http/routes.js +323 -0
  87. package/lib/types/http/standalone.d.ts +9 -0
  88. package/lib/types/http/standalone.js +31 -0
  89. package/lib/types/http/trunk.d.ts +10 -0
  90. package/lib/types/http/trunk.js +322 -0
  91. package/lib/types/http/worktree.d.ts +9 -0
  92. package/lib/types/http/worktree.js +145 -0
  93. package/lib/types/index.d.ts +18 -0
  94. package/lib/types/index.js +156 -0
  95. package/lib/types/invariant.d.ts +15 -0
  96. package/lib/types/invariant.js +15 -0
  97. package/lib/types/request-params.d.ts +21 -0
  98. package/lib/types/request-params.js +76 -0
  99. package/lib/types/store/paths.d.ts +68 -0
  100. package/lib/types/store/paths.js +148 -0
  101. package/lib/types/store/project.d.ts +88 -0
  102. package/lib/types/store/project.js +120 -0
  103. package/lib/types/store/read.d.ts +110 -0
  104. package/lib/types/store/read.js +404 -0
  105. package/lib/types/store/retention.d.ts +27 -0
  106. package/lib/types/store/retention.js +111 -0
  107. package/lib/types/store/write.d.ts +22 -0
  108. package/lib/types/store/write.js +210 -0
  109. package/lib/types/tools/arguments.d.ts +40 -0
  110. package/lib/types/tools/arguments.js +130 -0
  111. package/lib/types/tools/cells.d.ts +50 -0
  112. package/lib/types/tools/cells.js +298 -0
  113. package/lib/types/tools/index.d.ts +2 -0
  114. package/lib/types/tools/index.js +250 -0
  115. package/lib/types/tools/render.d.ts +47 -0
  116. package/lib/types/tools/render.js +290 -0
  117. package/lib/types/tools/univer-api.d.ts +18 -0
  118. package/lib/types/tools/univer-api.js +198 -0
  119. package/lib/types/tools/workbook-catalog.d.ts +3 -0
  120. package/lib/types/tools/workbook-catalog.js +83 -0
  121. package/lib/types/tools/workbook-edit.d.ts +192 -0
  122. package/lib/types/tools/workbook-edit.js +912 -0
  123. package/lib/types/tools/workbook-find.d.ts +11 -0
  124. package/lib/types/tools/workbook-find.js +89 -0
  125. package/lib/types/tools/workbook-history.d.ts +2 -0
  126. package/lib/types/tools/workbook-history.js +59 -0
  127. package/lib/types/tools/workbook-read.d.ts +21 -0
  128. package/lib/types/tools/workbook-read.js +204 -0
  129. package/lib/types/types.d.ts +377 -0
  130. package/lib/types/types.js +34 -0
  131. package/lib/types/univer/apply-operation.d.ts +14 -0
  132. package/lib/types/univer/apply-operation.js +398 -0
  133. package/lib/types/univer/chunk.d.ts +68 -0
  134. package/lib/types/univer/chunk.js +16 -0
  135. package/lib/types/univer/decoration.d.ts +34 -0
  136. package/lib/types/univer/decoration.js +88 -0
  137. package/lib/types/univer/diagnostics.d.ts +31 -0
  138. package/lib/types/univer/diagnostics.js +141 -0
  139. package/lib/types/univer/engine.d.ts +33 -0
  140. package/lib/types/univer/engine.js +338 -0
  141. package/lib/types/univer/entry.d.ts +8 -0
  142. package/lib/types/univer/entry.js +11 -0
  143. package/lib/types/univer/facade.d.ts +107 -0
  144. package/lib/types/univer/facade.js +1 -0
  145. package/lib/types/univer/fonts.d.ts +202 -0
  146. package/lib/types/univer/fonts.js +140 -0
  147. package/lib/types/univer/plugins/collaboration.d.ts +23 -0
  148. package/lib/types/univer/plugins/collaboration.js +47 -0
  149. package/lib/types/univer/plugins/export.d.ts +39 -0
  150. package/lib/types/univer/plugins/export.js +246 -0
  151. package/lib/types/univer/plugins/history.d.ts +24 -0
  152. package/lib/types/univer/plugins/history.js +50 -0
  153. package/lib/types/univer/plugins/version.d.ts +13 -0
  154. package/lib/types/univer/plugins/version.js +13 -0
  155. package/lib/types/version/diff.d.ts +36 -0
  156. package/lib/types/version/diff.js +156 -0
  157. package/lib/types/version/grouping.d.ts +7 -0
  158. package/lib/types/version/grouping.js +94 -0
  159. package/lib/types/version/records.d.ts +17 -0
  160. package/lib/types/version/records.js +102 -0
  161. package/lib/types/version/session.d.ts +36 -0
  162. package/lib/types/version/session.js +89 -0
  163. package/lib/types/version/summary.d.ts +24 -0
  164. package/lib/types/version/summary.js +111 -0
  165. package/lib/types/workbook-manager.d.ts +191 -0
  166. package/lib/types/workbook-manager.js +935 -0
  167. package/lib/types/xlsx-export-worker.d.ts +5 -0
  168. package/lib/types/xlsx-export-worker.js +24 -0
  169. package/lib/types/xlsx-export.d.ts +21 -0
  170. package/lib/types/xlsx-export.js +371 -0
  171. package/lib/types/xlsx-import-worker.d.ts +1 -0
  172. package/lib/types/xlsx-import-worker.js +19 -0
  173. package/lib/univer-client.js +690 -0
  174. package/lib/workbook-page.js +23826 -0
  175. package/lib/xlsx-export-worker.js +104 -0
  176. package/lib/xlsx-import-worker.js +46 -0
  177. package/package.json +106 -3
  178. package/src/audit.ts +58 -0
  179. package/src/client/ProjectImportMenu.tsx +100 -0
  180. package/src/client/SheetPanel.module.css +1578 -0
  181. package/src/client/SheetPanel.tsx +1934 -0
  182. package/src/client/SheetToggle.module.css +73 -0
  183. package/src/client/SheetToggle.tsx +65 -0
  184. package/src/client/contract.d.ts +19 -0
  185. package/src/client/index.ts +131 -0
  186. package/src/client/persistence/changes.ts +58 -0
  187. package/src/client/persistence/ids.ts +60 -0
  188. package/src/client/persistence/merge.ts +392 -0
  189. package/src/client/store.ts +188 -0
  190. package/src/client/surface-context.ts +123 -0
  191. package/src/client/surface-control.ts +13 -0
  192. package/src/client/transport/workbookApi.ts +355 -0
  193. package/src/client/univer/SheetView.module.css +110 -0
  194. package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
  195. package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
  196. package/src/client/univer/WorkbookCanvas.tsx +128 -0
  197. package/src/client/univer/WorkbookView.module.css +30 -0
  198. package/src/client/univer/WorkbookView.tsx +3 -0
  199. package/src/client/univer/loader.ts +90 -0
  200. package/src/client/univer/react-dom-client.d.ts +10 -0
  201. package/src/client/univer/standalone-entry.tsx +19 -0
  202. package/src/client/univer/standalone.css +12 -0
  203. package/src/client/univer/univer-locales.d.ts +89 -0
  204. package/src/client/univer/univer-modules.d.ts +26 -0
  205. package/src/client/versions/VersionChrome.tsx +114 -0
  206. package/src/client/versions/inspect.ts +138 -0
  207. package/src/client/versions/preview-decoration.ts +54 -0
  208. package/src/client/versions/records.ts +78 -0
  209. package/src/client/workspace-host.ts +147 -0
  210. package/src/collaboration-hub.ts +83 -0
  211. package/src/contract/ids.ts +50 -0
  212. package/src/contract/mutations.ts +791 -0
  213. package/src/contract/operations.ts +221 -0
  214. package/src/contract/snapshots.ts +383 -0
  215. package/src/contract/univer-api-catalog.ts +289 -0
  216. package/src/css-modules.d.ts +6 -0
  217. package/src/errors.ts +25 -0
  218. package/src/http/assets-routes.ts +177 -0
  219. package/src/http/collaboration.ts +144 -0
  220. package/src/http/history.ts +151 -0
  221. package/src/http/project-files.ts +227 -0
  222. package/src/http/project-scope.ts +77 -0
  223. package/src/http/routes.ts +364 -0
  224. package/src/http/standalone.ts +32 -0
  225. package/src/http/trunk.ts +322 -0
  226. package/src/http/worktree.ts +119 -0
  227. package/src/index.ts +165 -0
  228. package/src/invariant.ts +17 -0
  229. package/src/request-params.ts +92 -0
  230. package/src/store/paths.ts +165 -0
  231. package/src/store/project.ts +137 -0
  232. package/src/store/read.ts +454 -0
  233. package/src/store/retention.ts +106 -0
  234. package/src/store/write.ts +230 -0
  235. package/src/tools/arguments.ts +128 -0
  236. package/src/tools/cells.ts +298 -0
  237. package/src/tools/index.ts +274 -0
  238. package/src/tools/render.ts +301 -0
  239. package/src/tools/univer-api.ts +243 -0
  240. package/src/tools/workbook-catalog.ts +94 -0
  241. package/src/tools/workbook-edit.ts +987 -0
  242. package/src/tools/workbook-find.ts +80 -0
  243. package/src/tools/workbook-history.ts +63 -0
  244. package/src/tools/workbook-read.ts +210 -0
  245. package/src/types.ts +432 -0
  246. package/src/univer/apply-operation.ts +369 -0
  247. package/src/univer/chunk.ts +77 -0
  248. package/src/univer/decoration.ts +111 -0
  249. package/src/univer/diagnostics.ts +136 -0
  250. package/src/univer/engine.ts +370 -0
  251. package/src/univer/entry.ts +14 -0
  252. package/src/univer/facade.ts +91 -0
  253. package/src/univer/fonts.ts +148 -0
  254. package/src/univer/overrides.css +33 -0
  255. package/src/univer/plugins/collaboration.ts +47 -0
  256. package/src/univer/plugins/export.ts +236 -0
  257. package/src/univer/plugins/history.ts +49 -0
  258. package/src/univer/plugins/version.ts +13 -0
  259. package/src/version/diff.ts +169 -0
  260. package/src/version/grouping.ts +96 -0
  261. package/src/version/records.ts +113 -0
  262. package/src/version/session.ts +90 -0
  263. package/src/version/summary.ts +117 -0
  264. package/src/workbook-manager.ts +1111 -0
  265. package/src/xlsx-export-worker.ts +34 -0
  266. package/src/xlsx-export.ts +373 -0
  267. package/src/xlsx-import-worker.ts +30 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `workbook_find`: locate a value across sheets, cells or formulas
3
+ * (docs/issue/0022 P3b).
4
+ */
5
+ import type { WorkbookRange } from '../types.ts'
6
+ import { readWorkbook } from '../workbook-manager.ts'
7
+
8
+ import { parseRange } from '../request-params.ts'
9
+ import { asToolArguments, parseCount, parseNonEmptyString, sheetRefFromArguments, type ToolArguments } from './arguments.ts'
10
+
11
+ export function parseFindScope(value: unknown): 'sheets' | 'cells' | 'formulas' {
12
+ if (value === undefined || value === null) return 'cells'
13
+ if (value === 'sheets' || value === 'cells' || value === 'formulas') return value
14
+ throw new Error('scope 必须是 sheets、cells 或 formulas')
15
+ }
16
+
17
+ export function parseFindMatch(value: unknown): 'contains' | 'exact' | 'regex' {
18
+ if (value === undefined || value === null) return 'contains'
19
+ if (value === 'contains' || value === 'exact' || value === 'regex') return value
20
+ throw new Error('match 必须是 contains、exact 或 regex')
21
+ }
22
+
23
+ export function textMatcher(query: string, match: 'contains' | 'exact' | 'regex'): (value: unknown) => boolean {
24
+ if (match === 'regex') {
25
+ let expression: RegExp
26
+ try { expression = new RegExp(query, 'i') } catch { throw new Error('query 不是有效的正则表达式') }
27
+ return value => expression.test(String(value ?? ''))
28
+ }
29
+ const needle = query.toLocaleLowerCase()
30
+ return value => {
31
+ const text = String(value ?? '').toLocaleLowerCase()
32
+ return match === 'exact' ? text === needle : text.includes(needle)
33
+ }
34
+ }
35
+
36
+ export function inRange(row: number, column: number, range: WorkbookRange | undefined): boolean {
37
+ return range === undefined || row >= range.startRow && row <= range.endRow && column >= range.startColumn && column <= range.endColumn
38
+ }
39
+
40
+ export async function findWorkbookContent(workbookId: string, args: ToolArguments): Promise<unknown> {
41
+ const meta = await readWorkbook(workbookId)
42
+ if (meta === null) throw new Error('工作簿不存在:' + workbookId)
43
+ const query = parseNonEmptyString(args.query, 'query')
44
+ const scope = parseFindScope(args.scope)
45
+ const match = parseFindMatch(args.match)
46
+ const matches = textMatcher(query, match)
47
+ const limit = args.limit === undefined ? 100 : Math.min(500, parseCount(args.limit))
48
+ const selectedSheet = sheetRefFromArguments(args, 'sheet')
49
+ const range = args.range === undefined ? undefined : parseRange(args.range)
50
+ const root = asToolArguments(meta.snapshot, 'workbook snapshot')
51
+ const sheets = asToolArguments(root.sheets, 'workbook snapshot.sheets') as Record<string, Record<string, unknown>>
52
+ const results: Array<Record<string, unknown>> = []
53
+ for (const [sheetId, sheet] of Object.entries(sheets)) {
54
+ const sheetName = typeof sheet.name === 'string' ? sheet.name : sheetId
55
+ if (selectedSheet !== undefined && selectedSheet !== sheetId && selectedSheet !== sheetName) continue
56
+ if (scope === 'sheets') {
57
+ if (matches(sheetName)) results.push({ sheetId, sheetName })
58
+ if (results.length >= limit) break
59
+ continue
60
+ }
61
+ const cellData = sheet.cellData
62
+ if (typeof cellData !== 'object' || cellData === null || Array.isArray(cellData)) continue
63
+ for (const [rowKey, line] of Object.entries(cellData as Record<string, unknown>)) {
64
+ const row = Number(rowKey)
65
+ if (!Number.isInteger(row) || typeof line !== 'object' || line === null || Array.isArray(line)) continue
66
+ for (const [columnKey, rawCell] of Object.entries(line as Record<string, unknown>)) {
67
+ const column = Number(columnKey)
68
+ if (!Number.isInteger(column) || !inRange(row, column, range)) continue
69
+ const cell = typeof rawCell === 'object' && rawCell !== null && !Array.isArray(rawCell) ? rawCell as Record<string, unknown> : {}
70
+ const candidate = scope === 'formulas' ? cell.f : cell.v
71
+ if (!matches(candidate)) continue
72
+ results.push({ sheetId, sheetName, row, column, value: cell.v ?? null, formula: typeof cell.f === 'string' ? cell.f : null })
73
+ if (results.length >= limit) break
74
+ }
75
+ if (results.length >= limit) break
76
+ }
77
+ if (results.length >= limit) break
78
+ }
79
+ return { ok: true, workbookId, revision: meta.revision ?? 0, query, scope, match, results, truncated: results.length >= limit }
80
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `workbook_history`: the version list, one version, one Step, and a diff
3
+ * (docs/issue/0022 P3b). It answers with the same record shape the HTTP history
4
+ * endpoint returns, which is why `version/records.ts` owns that shape.
5
+ */
6
+ import type { WorkbookOperation } from '../types.ts'
7
+ import { readWorkbook, readWorkbookAtRevision, readWorkbookAtStep } from '../workbook-manager.ts'
8
+ import { diffWorkbookSnapshots } from '../version/diff.ts'
9
+ import { historyVersionsFromOperations } from '../version/grouping.ts'
10
+
11
+ import { activeHistoryRevisions, canReplayHistoryRevision, checkpointHasher, toVersionRecords } from '../version/records.ts'
12
+ import { parseIndex } from '../request-params.ts'
13
+ import { parseCount, type ToolArguments } from './arguments.ts'
14
+
15
+
16
+ export async function executeHistoryQuery(workbookId: string, args: ToolArguments): Promise<unknown> {
17
+ const action = args.action === undefined || args.action === null ? 'list' : args.action
18
+ if (action !== 'list' && action !== 'diff' && action !== 'replay') throw new Error('action 必须是 list、diff 或 replay')
19
+ if (action === 'diff') {
20
+ const fromRevision = parseIndex(args.fromRevision, 'fromRevision')
21
+ const toRevision = parseIndex(args.toRevision, 'toRevision')
22
+ if (fromRevision > toRevision) throw new Error('fromRevision 不得大于 toRevision')
23
+ const left = await readWorkbookAtRevision(workbookId, fromRevision)
24
+ const right = await readWorkbookAtRevision(workbookId, toRevision)
25
+ if (left === null || right === null) throw new Error('指定 revision 已被压缩或无法重放')
26
+ return { ok: true, action, workbookId, fromRevision, toRevision, changes: diffWorkbookSnapshots(left.snapshot, right.snapshot) }
27
+ }
28
+ if (action === 'replay') {
29
+ const rawRevision = args.revision ?? args.toRevision
30
+ const revision = parseIndex(rawRevision, 'revision')
31
+ if (args.step !== undefined && args.step !== null) {
32
+ const step = parseIndex(args.step, 'step')
33
+ const replay = await readWorkbookAtStep(workbookId, revision, step)
34
+ if (replay === null) throw new Error('指定 revision/step 已被压缩或无法重放')
35
+ return { ok: true, action, workbookId, ...replay }
36
+ }
37
+ const replay = await readWorkbookAtRevision(workbookId, revision)
38
+ if (replay === null) throw new Error('指定 revision 已被压缩或无法重放')
39
+ return { ok: true, action, workbookId, ...replay }
40
+ }
41
+
42
+ const meta = await readWorkbook(workbookId, { includeCheckpoints: true })
43
+ if (meta === null) throw new Error('工作簿不存在:' + workbookId)
44
+ const limit = args.limit === undefined ? 20 : Math.min(100, Math.max(1, parseCount(args.limit)))
45
+ const allOperations = (meta.operations ?? []).slice().sort((left, right) => left.revision - right.revision)
46
+ const currentRevision = meta.revision ?? 0
47
+ const byRevision = new Map(allOperations.map(operation => [operation.revision, operation]))
48
+ const active = activeHistoryRevisions(currentRevision, byRevision)
49
+ const checkpoints = (meta.checkpoints ?? []).map(checkpoint => checkpoint.revision).sort((left, right) => left - right)
50
+ const canReplay = (revision: number): boolean => canReplayHistoryRevision(revision, currentRevision, checkpoints, byRevision)
51
+ const fullVersions = historyVersionsFromOperations(allOperations, canReplay, active).slice(-limit)
52
+ const versions = fullVersions.map(({ steps: _steps, ...version }) => version)
53
+ return {
54
+ ok: true,
55
+ action,
56
+ workbookId,
57
+ revision: currentRevision,
58
+ versions,
59
+ // Unified cross-plugin model (PRD §7.1). `versions` stays for compatibility.
60
+ versionRecords: toVersionRecords(fullVersions, workbookId, checkpointHasher(meta.checkpoints ?? [])),
61
+ checkpoints,
62
+ }
63
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * `workbook_read`: one bounded look at a sheet, a range or a single cell
3
+ * (docs/issue/0022 P3b).
4
+ */
5
+ import type { WorkbookRange } from '../types.ts'
6
+ import type { SheetRef } from '#contracts'
7
+
8
+ import { readWorkbook, readWorkbookRange, summarize } from '../workbook-manager.ts'
9
+ import { parseIndex, parseRange } from '../request-params.ts'
10
+ import { asToolArguments, sheetRefForSnapshot } from './arguments.ts'
11
+ import { sheetOverviews } from './render.ts'
12
+
13
+ export function parseReadIncludes(value: unknown): Set<string> {
14
+ if (value === undefined || value === null) return new Set()
15
+ if (!Array.isArray(value)) throw new Error('include 必须是字符串数组')
16
+ const allowed = new Set(['values', 'formulas', 'cells', 'styles', 'snapshot', 'backfill'])
17
+ const result = new Set<string>()
18
+ for (const item of value) {
19
+ if (typeof item !== 'string' || !allowed.has(item)) throw new Error('include 仅支持 values、formulas、cells、styles、snapshot、backfill')
20
+ result.add(item)
21
+ }
22
+ return result
23
+ }
24
+
25
+ export function snapshotSheet(snapshot: unknown, sheetRef: string): Record<string, unknown> {
26
+ const root = asToolArguments(snapshot, 'workbook snapshot')
27
+ const sheets = asToolArguments(root.sheets, 'workbook snapshot.sheets') as Record<string, Record<string, unknown>>
28
+ const direct = sheets[sheetRef]
29
+ if (direct !== undefined) return direct
30
+ const byName = Object.values(sheets).find(sheet => sheet.name === sheetRef)
31
+ if (byName === undefined) throw new Error('sheet 不存在:' + sheetRef)
32
+ return byName
33
+ }
34
+
35
+ export function snapshotCell(snapshot: unknown, sheetRef: string, row: number, column: number): Record<string, unknown> | undefined {
36
+ const sheet = snapshotSheet(snapshot, sheetRef)
37
+ const cellData = sheet.cellData
38
+ if (typeof cellData !== 'object' || cellData === null || Array.isArray(cellData)) return undefined
39
+ const line = (cellData as Record<string, unknown>)[String(row)]
40
+ if (typeof line !== 'object' || line === null || Array.isArray(line)) return undefined
41
+ const cell = (line as Record<string, unknown>)[String(column)]
42
+ return typeof cell === 'object' && cell !== null && !Array.isArray(cell) ? cell as Record<string, unknown> : undefined
43
+ }
44
+
45
+ export function styleMatrix(snapshot: unknown, sheetRef: string, range: WorkbookRange): unknown[][] {
46
+ const result: unknown[][] = []
47
+ for (let row = range.startRow; row <= range.endRow; row += 1) {
48
+ const line: unknown[] = []
49
+ for (let column = range.startColumn; column <= range.endColumn; column += 1) {
50
+ line.push(snapshotCell(snapshot, sheetRef, row, column)?.s ?? null)
51
+ }
52
+ result.push(line)
53
+ }
54
+ return result
55
+ }
56
+
57
+ /**
58
+ * Per-sheet backfill status for `workbook_read(kind: 'workbook')`
59
+ * (docs/issue/0011 I5 / T7).
60
+ *
61
+ * The agent kept a prose table of "which sheet still needs what" because the
62
+ * read surface could only show scale. This reports the four things that table
63
+ * tracked — data area, run provenance, run time and a conclusion block — so a
64
+ * workbook's completeness is visible without opening every sheet. `complete` is
65
+ * deliberately conservative: it is a hint, not a gate.
66
+ */
67
+ export function sheetBackfillStatus(sheet: Record<string, unknown>): Record<string, unknown> {
68
+ const asRecord = (value: unknown): Record<string, unknown> | undefined =>
69
+ typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : undefined
70
+ const stats = asRecord(sheet.dataSheetStats)
71
+ const meta = asRecord(sheet.dataSheetMeta)
72
+ const schema = asRecord(sheet.dataSheetSchema)
73
+ const dataRows = typeof stats?.rowCount === 'number' && stats.rowCount >= 0 ? stats.rowCount : 0
74
+ const cellData = asRecord(sheet.cellData) ?? {}
75
+ // A conclusion block is not tracked anywhere in the snapshot: any write
76
+ // recomputes the row counts, so the counter cannot separate data from prose.
77
+ // What it can do is spot rows that do not span the data schema, which is the
78
+ // shape a conclusion row takes in these report sheets (row 0 is the header).
79
+ const schemaColumns = Array.isArray(schema?.columns) ? schema.columns.length : 0
80
+ let conclusionRows = 0
81
+ for (const [key, line] of Object.entries(cellData)) {
82
+ const row = Number(key)
83
+ if (!Number.isInteger(row) || row <= 0) continue
84
+ const cells = asRecord(line)
85
+ if (cells === undefined) continue
86
+ const filled = Object.values(cells).filter(cell => {
87
+ const value = asRecord(cell)?.v
88
+ return value !== undefined && value !== null && String(value).length > 0
89
+ }).length
90
+ if (filled === 0) continue
91
+ if (schemaColumns > 0 && filled >= schemaColumns) continue
92
+ conclusionRows += 1
93
+ }
94
+ const provenance = meta === undefined
95
+ ? null
96
+ : { source: meta.source ?? null, query: meta.query ?? null, notes: meta.notes ?? null }
97
+ return {
98
+ /** Highest row index holding content. Every write recomputes it, so it
99
+ * covers a conclusion block as well as the data — it is a bound, not a
100
+ * count of data rows. */
101
+ lastOccupiedRow: typeof stats?.rowCount === 'number' && stats.rowCount >= 0 ? stats.rowCount : 0,
102
+ columnCount: typeof stats?.columnCount === 'number' ? stats.columnCount : 0,
103
+ conclusionRows,
104
+ runAt: typeof meta?.generatedAt === 'string' ? meta.generatedAt : null,
105
+ provenance,
106
+ schemaUpdatedAt: typeof schema?.updatedAt === 'string' ? schema.updatedAt : null,
107
+ complete: (typeof stats?.rowCount === 'number' ? stats.rowCount : 0) > 0 && provenance !== null,
108
+ }
109
+ }
110
+
111
+ export async function readWorkbookTarget(workbookId: string, rawTarget: unknown, rawInclude: unknown): Promise<unknown> {
112
+ const meta = await readWorkbook(workbookId)
113
+ if (meta === null) throw new Error('工作簿不存在:' + workbookId)
114
+ const target = asToolArguments(rawTarget, 'target')
115
+ const kind = target.kind
116
+ if (kind !== 'workbook' && kind !== 'sheet' && kind !== 'range' && kind !== 'cells') {
117
+ throw new Error('target.kind 必须是 workbook、sheet、range 或 cells')
118
+ }
119
+ const include = parseReadIncludes(rawInclude)
120
+ const summary = summarize(meta)
121
+ const base = { ok: true, workbookId, revision: meta.revision ?? 0 }
122
+
123
+ if (kind === 'workbook') {
124
+ const sheets = sheetOverviews(meta.snapshot)
125
+ return {
126
+ ...base,
127
+ workbook: { id: summary.id, name: summary.name, sheets, sheetCount: sheets.length, updatedAt: summary.updatedAt },
128
+ ...(include.has('snapshot') ? { snapshot: meta.snapshot } : {}),
129
+ ...(include.has('backfill')
130
+ ? {
131
+ backfill: sheets.map(overview => ({
132
+ sheetId: overview.id,
133
+ name: overview.name,
134
+ ...sheetBackfillStatus(snapshotSheet(meta.snapshot, overview.id)),
135
+ })),
136
+ }
137
+ : {}),
138
+ }
139
+ }
140
+
141
+ const sheetRef = sheetRefForSnapshot(meta.snapshot, target, 'target.sheet')
142
+ const sheet = snapshotSheet(meta.snapshot, sheetRef)
143
+ const sheetSummary = summary.sheets.find(candidate => candidate.id === sheetRef || candidate.name === sheetRef)
144
+ const resolvedSheetRef: SheetRef = {
145
+ workbookId,
146
+ sheetId: sheetSummary?.id ?? String(sheet.id ?? sheetRef),
147
+ sheetName: sheetSummary?.name ?? String(sheet.name ?? sheetRef),
148
+ }
149
+ if (kind === 'sheet') {
150
+ const overview = sheetOverviews(meta.snapshot).find(candidate => candidate.id === resolvedSheetRef.sheetId)
151
+ return {
152
+ ...base,
153
+ sheetRef: resolvedSheetRef,
154
+ sheet: {
155
+ id: resolvedSheetRef.sheetId,
156
+ name: resolvedSheetRef.sheetName,
157
+ rowCount: overview?.rowCount ?? sheet.rowCount ?? 0,
158
+ columnCount: overview?.columnCount ?? sheet.columnCount ?? 0,
159
+ maxRow: overview?.maxRow ?? null,
160
+ maxColumn: overview?.maxColumn ?? null,
161
+ nonEmptyCells: overview?.nonEmptyCells ?? 0,
162
+ },
163
+ ...(include.has('snapshot') ? { snapshot: sheet } : {}),
164
+ }
165
+ }
166
+
167
+ if (kind === 'range') {
168
+ const range = parseRange(target.range)
169
+ const result = readWorkbookRange(meta.snapshot, sheetRef, range)
170
+ // `readWorkbookRange` materializes values, formulas and cells together, but
171
+ // returning all three inline spent the result budget three times over
172
+ // (docs/issue/0011 I1 / T5). Only `values` is unconditional; the heavier views
173
+ // have to be asked for.
174
+ return {
175
+ ...base,
176
+ sheetRef: { ...resolvedSheetRef, range: `${range.startRow}:${range.startColumn}-${range.endRow}:${range.endColumn}` },
177
+ sheetId: result.sheetId,
178
+ sheetName: result.sheetName,
179
+ range: result.range,
180
+ values: result.values,
181
+ ...(include.has('formulas') ? { formulas: result.formulas } : {}),
182
+ ...(include.has('cells') ? { cells: result.cells } : {}),
183
+ ...(include.has('styles') ? { styles: styleMatrix(meta.snapshot, result.sheetId, range) } : {}),
184
+ }
185
+ }
186
+
187
+ if (!Array.isArray(target.cells) || target.cells.length === 0 || target.cells.length > 500) {
188
+ throw new Error('target.cells 必须是 1 到 500 项的单元格坐标数组')
189
+ }
190
+ const cells = target.cells.map((rawCell, index) => {
191
+ const coordinate = asToolArguments(rawCell, 'target.cells[' + index + ']')
192
+ const row = parseIndex(coordinate.row, 'target.cells[' + index + '].row')
193
+ const column = parseIndex(coordinate.column, 'target.cells[' + index + '].column')
194
+ const cell = snapshotCell(meta.snapshot, sheetRef, row, column)
195
+ const value = cell?.v
196
+ return {
197
+ row,
198
+ column,
199
+ value: value === undefined ? null : value,
200
+ formula: typeof cell?.f === 'string' ? cell.f : null,
201
+ ...(include.has('styles') ? { style: cell?.s ?? null } : {}),
202
+ }
203
+ })
204
+ return {
205
+ ...base,
206
+ sheetRef: resolvedSheetRef,
207
+ sheet: { id: resolvedSheetRef.sheetId, name: resolvedSheetRef.sheetName },
208
+ cells,
209
+ }
210
+ }