@swarmclawai/swarmclaw 0.7.2 → 0.7.4

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 (274) hide show
  1. package/README.md +116 -50
  2. package/bin/package-manager.js +157 -0
  3. package/bin/package-manager.test.js +90 -0
  4. package/bin/server-cmd.js +38 -7
  5. package/bin/swarmclaw.js +54 -4
  6. package/bin/update-cmd.js +48 -10
  7. package/bin/update-cmd.test.js +55 -0
  8. package/package.json +8 -3
  9. package/scripts/postinstall.mjs +26 -0
  10. package/src/app/api/agents/[id]/route.ts +43 -0
  11. package/src/app/api/agents/[id]/thread/route.ts +39 -8
  12. package/src/app/api/agents/route.ts +35 -2
  13. package/src/app/api/auth/route.ts +77 -8
  14. package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
  15. package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
  16. package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
  17. package/src/app/api/chatrooms/[id]/route.ts +6 -0
  18. package/src/app/api/chats/[id]/browser/route.ts +5 -1
  19. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  20. package/src/app/api/chats/[id]/messages/route.ts +19 -13
  21. package/src/app/api/chats/[id]/route.ts +30 -0
  22. package/src/app/api/chats/[id]/stop/route.ts +6 -1
  23. package/src/app/api/chats/heartbeat/route.ts +2 -1
  24. package/src/app/api/chats/route.ts +23 -1
  25. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  26. package/src/app/api/connectors/doctor/route.ts +13 -0
  27. package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
  28. package/src/app/api/external-agents/[id]/route.ts +31 -0
  29. package/src/app/api/external-agents/register/route.ts +3 -0
  30. package/src/app/api/external-agents/route.ts +66 -0
  31. package/src/app/api/files/open/route.ts +16 -14
  32. package/src/app/api/gateways/[id]/health/route.ts +28 -0
  33. package/src/app/api/gateways/[id]/route.ts +79 -0
  34. package/src/app/api/gateways/route.ts +57 -0
  35. package/src/app/api/memory/maintenance/route.ts +11 -1
  36. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  37. package/src/app/api/openclaw/gateway/route.ts +10 -7
  38. package/src/app/api/openclaw/skills/route.ts +12 -4
  39. package/src/app/api/plugins/dependencies/route.ts +24 -0
  40. package/src/app/api/plugins/install/route.ts +15 -92
  41. package/src/app/api/plugins/route.ts +3 -26
  42. package/src/app/api/plugins/settings/route.ts +17 -12
  43. package/src/app/api/plugins/ui/route.ts +1 -0
  44. package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
  45. package/src/app/api/schedules/[id]/route.ts +38 -9
  46. package/src/app/api/schedules/route.ts +51 -28
  47. package/src/app/api/settings/route.ts +55 -17
  48. package/src/app/api/setup/doctor/route.ts +6 -4
  49. package/src/app/api/tasks/[id]/route.ts +16 -6
  50. package/src/app/api/tasks/bulk/route.ts +3 -3
  51. package/src/app/api/tasks/route.ts +9 -4
  52. package/src/app/api/webhooks/[id]/route.ts +8 -1
  53. package/src/app/page.tsx +135 -17
  54. package/src/cli/binary.test.js +142 -0
  55. package/src/cli/index.js +38 -11
  56. package/src/cli/index.test.js +195 -0
  57. package/src/cli/index.ts +21 -12
  58. package/src/cli/server-cmd.test.js +59 -0
  59. package/src/cli/spec.js +20 -2
  60. package/src/components/agents/agent-card.tsx +15 -12
  61. package/src/components/agents/agent-chat-list.tsx +101 -1
  62. package/src/components/agents/agent-list.tsx +46 -9
  63. package/src/components/agents/agent-sheet.tsx +456 -23
  64. package/src/components/agents/inspector-panel.tsx +110 -49
  65. package/src/components/agents/sandbox-env-panel.tsx +4 -1
  66. package/src/components/auth/access-key-gate.tsx +36 -97
  67. package/src/components/auth/setup-wizard.tsx +970 -275
  68. package/src/components/chat/chat-area.tsx +70 -27
  69. package/src/components/chat/chat-card.tsx +6 -21
  70. package/src/components/chat/chat-header.tsx +263 -366
  71. package/src/components/chat/chat-list.tsx +62 -26
  72. package/src/components/chat/checkpoint-timeline.tsx +1 -1
  73. package/src/components/chat/message-list.tsx +145 -19
  74. package/src/components/chatrooms/chatroom-input.tsx +96 -33
  75. package/src/components/chatrooms/chatroom-list.tsx +141 -72
  76. package/src/components/chatrooms/chatroom-message.tsx +7 -6
  77. package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
  78. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
  79. package/src/components/chatrooms/chatroom-view.tsx +422 -209
  80. package/src/components/chatrooms/reaction-picker.tsx +38 -33
  81. package/src/components/connectors/connector-list.tsx +265 -127
  82. package/src/components/connectors/connector-sheet.tsx +217 -0
  83. package/src/components/gateways/gateway-sheet.tsx +567 -0
  84. package/src/components/home/home-view.tsx +128 -4
  85. package/src/components/input/chat-input.tsx +135 -86
  86. package/src/components/layout/app-layout.tsx +385 -194
  87. package/src/components/layout/mobile-header.tsx +26 -8
  88. package/src/components/memory/memory-browser.tsx +71 -6
  89. package/src/components/memory/memory-card.tsx +18 -0
  90. package/src/components/memory/memory-detail.tsx +58 -31
  91. package/src/components/memory/memory-sheet.tsx +32 -4
  92. package/src/components/plugins/plugin-list.tsx +15 -3
  93. package/src/components/plugins/plugin-sheet.tsx +118 -9
  94. package/src/components/projects/project-detail.tsx +189 -1
  95. package/src/components/providers/provider-list.tsx +158 -2
  96. package/src/components/providers/provider-sheet.tsx +81 -70
  97. package/src/components/shared/agent-picker-list.tsx +2 -2
  98. package/src/components/shared/bottom-sheet.tsx +31 -15
  99. package/src/components/shared/command-palette.tsx +111 -24
  100. package/src/components/shared/confirm-dialog.tsx +45 -30
  101. package/src/components/shared/model-combobox.tsx +90 -8
  102. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  103. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  104. package/src/components/shared/settings/section-heartbeat.tsx +88 -6
  105. package/src/components/shared/settings/section-orchestrator.tsx +6 -3
  106. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  107. package/src/components/shared/settings/section-secrets.tsx +6 -6
  108. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  109. package/src/components/shared/settings/section-voice.tsx +5 -1
  110. package/src/components/shared/settings/section-web-search.tsx +10 -2
  111. package/src/components/shared/settings/settings-page.tsx +248 -47
  112. package/src/components/tasks/approvals-panel.tsx +211 -18
  113. package/src/components/tasks/task-board.tsx +242 -46
  114. package/src/components/ui/dialog.tsx +2 -2
  115. package/src/components/usage/metrics-dashboard.tsx +74 -1
  116. package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
  117. package/src/components/wallets/wallet-panel.tsx +17 -5
  118. package/src/components/webhooks/webhook-sheet.tsx +7 -7
  119. package/src/lib/auth.ts +17 -0
  120. package/src/lib/chat-streaming-state.test.ts +108 -0
  121. package/src/lib/chat-streaming-state.ts +108 -0
  122. package/src/lib/heartbeat-defaults.ts +48 -0
  123. package/src/lib/memory-presentation.ts +59 -0
  124. package/src/lib/openclaw-agent-id.test.ts +14 -0
  125. package/src/lib/openclaw-agent-id.ts +31 -0
  126. package/src/lib/provider-model-discovery-client.ts +29 -0
  127. package/src/lib/providers/index.ts +12 -5
  128. package/src/lib/runtime-loop.ts +105 -3
  129. package/src/lib/safe-storage.ts +6 -1
  130. package/src/lib/server/agent-assignment.test.ts +112 -0
  131. package/src/lib/server/agent-assignment.ts +169 -0
  132. package/src/lib/server/agent-runtime-config.test.ts +141 -0
  133. package/src/lib/server/agent-runtime-config.ts +277 -0
  134. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  135. package/src/lib/server/approvals-auto-approve.test.ts +264 -0
  136. package/src/lib/server/approvals.ts +483 -75
  137. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  138. package/src/lib/server/browser-state.test.ts +118 -0
  139. package/src/lib/server/browser-state.ts +123 -0
  140. package/src/lib/server/build-llm.test.ts +44 -0
  141. package/src/lib/server/build-llm.ts +11 -4
  142. package/src/lib/server/builtin-plugins.ts +34 -0
  143. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  144. package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
  145. package/src/lib/server/chat-execution.ts +402 -125
  146. package/src/lib/server/chatroom-health.test.ts +26 -0
  147. package/src/lib/server/chatroom-health.ts +2 -3
  148. package/src/lib/server/chatroom-helpers.test.ts +74 -2
  149. package/src/lib/server/chatroom-helpers.ts +144 -11
  150. package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
  151. package/src/lib/server/connectors/discord.ts +175 -11
  152. package/src/lib/server/connectors/doctor.test.ts +80 -0
  153. package/src/lib/server/connectors/doctor.ts +116 -0
  154. package/src/lib/server/connectors/manager.ts +994 -130
  155. package/src/lib/server/connectors/policy.test.ts +222 -0
  156. package/src/lib/server/connectors/policy.ts +452 -0
  157. package/src/lib/server/connectors/slack.ts +189 -10
  158. package/src/lib/server/connectors/telegram.ts +65 -15
  159. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  160. package/src/lib/server/connectors/thread-context.ts +72 -0
  161. package/src/lib/server/connectors/types.ts +41 -11
  162. package/src/lib/server/daemon-state.ts +62 -3
  163. package/src/lib/server/data-dir.ts +13 -0
  164. package/src/lib/server/delegation-jobs.test.ts +140 -0
  165. package/src/lib/server/delegation-jobs.ts +248 -0
  166. package/src/lib/server/document-utils.test.ts +47 -0
  167. package/src/lib/server/document-utils.ts +397 -0
  168. package/src/lib/server/eval/agent-regression.test.ts +47 -0
  169. package/src/lib/server/eval/agent-regression.ts +1742 -0
  170. package/src/lib/server/eval/runner.ts +11 -1
  171. package/src/lib/server/eval/store.ts +2 -1
  172. package/src/lib/server/heartbeat-service.ts +23 -43
  173. package/src/lib/server/heartbeat-source.test.ts +22 -0
  174. package/src/lib/server/heartbeat-source.ts +7 -0
  175. package/src/lib/server/identity-continuity.test.ts +77 -0
  176. package/src/lib/server/identity-continuity.ts +127 -0
  177. package/src/lib/server/mailbox-utils.ts +347 -0
  178. package/src/lib/server/main-agent-loop.ts +31 -964
  179. package/src/lib/server/memory-db.ts +4 -6
  180. package/src/lib/server/memory-tiers.ts +40 -0
  181. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  182. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  183. package/src/lib/server/openclaw-exec-config.ts +6 -5
  184. package/src/lib/server/openclaw-gateway.ts +123 -36
  185. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  186. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  187. package/src/lib/server/openclaw-sync.ts +3 -2
  188. package/src/lib/server/orchestrator-lg.ts +18 -8
  189. package/src/lib/server/orchestrator.ts +5 -4
  190. package/src/lib/server/playwright-proxy.mjs +27 -3
  191. package/src/lib/server/plugins.test.ts +215 -0
  192. package/src/lib/server/plugins.ts +832 -69
  193. package/src/lib/server/provider-health.ts +33 -3
  194. package/src/lib/server/provider-model-discovery.ts +481 -0
  195. package/src/lib/server/queue.ts +4 -21
  196. package/src/lib/server/runtime-settings.test.ts +119 -0
  197. package/src/lib/server/runtime-settings.ts +12 -92
  198. package/src/lib/server/schedule-normalization.ts +187 -0
  199. package/src/lib/server/scheduler.ts +2 -0
  200. package/src/lib/server/session-archive-memory.test.ts +85 -0
  201. package/src/lib/server/session-archive-memory.ts +230 -0
  202. package/src/lib/server/session-mailbox.ts +8 -18
  203. package/src/lib/server/session-reset-policy.test.ts +99 -0
  204. package/src/lib/server/session-reset-policy.ts +311 -0
  205. package/src/lib/server/session-run-manager.ts +33 -80
  206. package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
  207. package/src/lib/server/session-tools/calendar.ts +2 -12
  208. package/src/lib/server/session-tools/connector.ts +109 -8
  209. package/src/lib/server/session-tools/context.ts +14 -2
  210. package/src/lib/server/session-tools/crawl.ts +447 -0
  211. package/src/lib/server/session-tools/crud.ts +96 -34
  212. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  213. package/src/lib/server/session-tools/delegate.ts +406 -20
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
  215. package/src/lib/server/session-tools/discovery.ts +40 -12
  216. package/src/lib/server/session-tools/document.ts +283 -0
  217. package/src/lib/server/session-tools/email.ts +1 -3
  218. package/src/lib/server/session-tools/extract.ts +137 -0
  219. package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
  220. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  221. package/src/lib/server/session-tools/file.ts +243 -24
  222. package/src/lib/server/session-tools/http.ts +9 -3
  223. package/src/lib/server/session-tools/human-loop.ts +227 -0
  224. package/src/lib/server/session-tools/image-gen.ts +1 -3
  225. package/src/lib/server/session-tools/index.ts +87 -2
  226. package/src/lib/server/session-tools/mailbox.ts +276 -0
  227. package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
  228. package/src/lib/server/session-tools/memory.ts +35 -3
  229. package/src/lib/server/session-tools/monitor.ts +162 -12
  230. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  231. package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
  232. package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
  233. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  234. package/src/lib/server/session-tools/platform.ts +142 -4
  235. package/src/lib/server/session-tools/plugin-creator.ts +95 -25
  236. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  237. package/src/lib/server/session-tools/replicate.ts +1 -3
  238. package/src/lib/server/session-tools/sandbox.ts +51 -92
  239. package/src/lib/server/session-tools/schedule.ts +20 -10
  240. package/src/lib/server/session-tools/session-info.ts +58 -4
  241. package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
  242. package/src/lib/server/session-tools/shell.ts +2 -2
  243. package/src/lib/server/session-tools/subagent.ts +195 -27
  244. package/src/lib/server/session-tools/table.ts +587 -0
  245. package/src/lib/server/session-tools/wallet.ts +13 -10
  246. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  247. package/src/lib/server/session-tools/web.ts +947 -108
  248. package/src/lib/server/storage.ts +255 -10
  249. package/src/lib/server/stream-agent-chat.test.ts +61 -0
  250. package/src/lib/server/stream-agent-chat.ts +185 -25
  251. package/src/lib/server/structured-extract.test.ts +72 -0
  252. package/src/lib/server/structured-extract.ts +373 -0
  253. package/src/lib/server/task-mention.test.ts +16 -2
  254. package/src/lib/server/task-mention.ts +61 -11
  255. package/src/lib/server/tool-aliases.ts +80 -12
  256. package/src/lib/server/tool-capability-policy.ts +7 -1
  257. package/src/lib/server/tool-retry.ts +2 -0
  258. package/src/lib/server/watch-jobs.test.ts +173 -0
  259. package/src/lib/server/watch-jobs.ts +532 -0
  260. package/src/lib/server/ws-hub.ts +5 -3
  261. package/src/lib/setup-defaults.ts +352 -11
  262. package/src/lib/tool-definitions.ts +3 -4
  263. package/src/lib/validation/schemas.test.ts +26 -0
  264. package/src/lib/validation/schemas.ts +62 -1
  265. package/src/lib/ws-client.ts +14 -12
  266. package/src/proxy.ts +5 -5
  267. package/src/stores/use-app-store.ts +43 -7
  268. package/src/stores/use-chat-store.ts +31 -2
  269. package/src/stores/use-chatroom-store.ts +153 -26
  270. package/src/types/index.ts +470 -44
  271. package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
  272. package/src/components/chat/new-chat-sheet.tsx +0 -253
  273. package/src/lib/server/main-session.ts +0 -17
  274. package/src/lib/server/session-run-manager.test.ts +0 -26
@@ -0,0 +1,397 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { spawnSync } from 'child_process'
4
+ import * as cheerio from 'cheerio'
5
+ import { findBinaryOnPath } from './session-tools/context'
6
+
7
+ const TEXT_EXTENSIONS = new Set([
8
+ '.txt', '.md', '.markdown', '.json', '.jsonl', '.csv', '.tsv',
9
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs',
10
+ '.java', '.yaml', '.yml', '.sql', '.xml', '.css', '.scss', '.html', '.htm',
11
+ ])
12
+ const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.tif', '.tiff'])
13
+
14
+ export interface StructuredTable {
15
+ name: string
16
+ headers: string[]
17
+ rows: Array<Record<string, unknown>>
18
+ rowCount: number
19
+ }
20
+
21
+ export interface DocumentArtifact {
22
+ filePath: string
23
+ fileName: string
24
+ ext: string
25
+ method: string
26
+ text: string
27
+ metadata: Record<string, unknown>
28
+ tables: StructuredTable[]
29
+ }
30
+
31
+ function trimText(text: string, maxChars = 200_000): string {
32
+ const normalized = text.replace(/\r\n/g, '\n').replace(/\u0000/g, '').trim()
33
+ if (normalized.length <= maxChars) return normalized
34
+ return `${normalized.slice(0, maxChars)}\n... [truncated]`
35
+ }
36
+
37
+ function normalizeScalar(value: unknown): unknown {
38
+ if (value === undefined) return null
39
+ if (value === null) return null
40
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') return value
41
+ if (value instanceof Date) return value.toISOString()
42
+ return String(value)
43
+ }
44
+
45
+ function parseDelimitedText(input: string, delimiter: string): string[][] {
46
+ const rows: string[][] = []
47
+ let row: string[] = []
48
+ let field = ''
49
+ let inQuotes = false
50
+
51
+ for (let index = 0; index < input.length; index += 1) {
52
+ const char = input[index]
53
+ const next = input[index + 1]
54
+
55
+ if (inQuotes) {
56
+ if (char === '"' && next === '"') {
57
+ field += '"'
58
+ index += 1
59
+ continue
60
+ }
61
+ if (char === '"') {
62
+ inQuotes = false
63
+ continue
64
+ }
65
+ field += char
66
+ continue
67
+ }
68
+
69
+ if (char === '"') {
70
+ inQuotes = true
71
+ continue
72
+ }
73
+ if (char === delimiter) {
74
+ row.push(field)
75
+ field = ''
76
+ continue
77
+ }
78
+ if (char === '\n') {
79
+ row.push(field)
80
+ rows.push(row)
81
+ row = []
82
+ field = ''
83
+ continue
84
+ }
85
+ if (char === '\r') continue
86
+ field += char
87
+ }
88
+
89
+ if (field.length > 0 || row.length > 0) {
90
+ row.push(field)
91
+ rows.push(row)
92
+ }
93
+
94
+ return rows.filter((cells) => cells.some((cell) => cell.trim().length > 0))
95
+ }
96
+
97
+ function matrixToTable(name: string, matrix: string[][]): StructuredTable {
98
+ if (matrix.length === 0) return { name, headers: [], rows: [], rowCount: 0 }
99
+ const headerRow = matrix[0].map((cell, index) => cell.trim() || `column_${index + 1}`)
100
+ const rows = matrix.slice(1).map((cells) => {
101
+ const row: Record<string, unknown> = {}
102
+ for (let index = 0; index < headerRow.length; index += 1) {
103
+ row[headerRow[index]] = cells[index] ?? ''
104
+ }
105
+ return row
106
+ })
107
+ return {
108
+ name,
109
+ headers: headerRow,
110
+ rows,
111
+ rowCount: rows.length,
112
+ }
113
+ }
114
+
115
+ function objectsToTable(name: string, rows: Array<Record<string, unknown>>): StructuredTable {
116
+ const headers = Array.from(new Set(rows.flatMap((row) => Object.keys(row))))
117
+ const normalizedRows = rows.map((row) => {
118
+ const out: Record<string, unknown> = {}
119
+ for (const header of headers) out[header] = normalizeScalar(row[header])
120
+ return out
121
+ })
122
+ return {
123
+ name,
124
+ headers,
125
+ rows: normalizedRows,
126
+ rowCount: normalizedRows.length,
127
+ }
128
+ }
129
+
130
+ function tablesToText(tables: StructuredTable[]): string {
131
+ return tables
132
+ .map((table) => {
133
+ const header = table.headers.join('\t')
134
+ const body = table.rows.slice(0, 100).map((row) => table.headers.map((key) => String(row[key] ?? '')).join('\t')).join('\n')
135
+ return `${table.name}\n${header}${body ? `\n${body}` : ''}`
136
+ })
137
+ .join('\n\n')
138
+ }
139
+
140
+ function worksheetRowToArray(values: unknown): unknown[] {
141
+ if (Array.isArray(values)) return values.slice(1)
142
+ if (values && typeof values === 'object') {
143
+ return Object.entries(values as Record<string, unknown>)
144
+ .filter(([key]) => Number.isFinite(Number(key)) && Number(key) >= 1)
145
+ .sort((left, right) => Number(left[0]) - Number(right[0]))
146
+ .map(([, value]) => value)
147
+ }
148
+ return []
149
+ }
150
+
151
+ function listZipEntries(filePath: string): { entries: string[]; method: string } {
152
+ const unzip = findBinaryOnPath('unzip') || findBinaryOnPath('zipinfo')
153
+ if (!unzip) throw new Error('ZIP listing requires `unzip` or `zipinfo` on PATH.')
154
+ const args = path.basename(unzip).includes('zipinfo') ? ['-1', filePath] : ['-Z1', filePath]
155
+ const out = spawnSync(unzip, args, {
156
+ encoding: 'utf-8',
157
+ maxBuffer: 10 * 1024 * 1024,
158
+ timeout: 20_000,
159
+ })
160
+ if ((out.status ?? 1) !== 0) {
161
+ throw new Error(`Failed to inspect ZIP: ${(out.stderr || out.stdout || '').trim() || 'unknown error'}`)
162
+ }
163
+ const entries = (out.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
164
+ return { entries, method: path.basename(unzip) }
165
+ }
166
+
167
+ async function extractPdfText(filePath: string): Promise<{ text: string; method: string }> {
168
+ try {
169
+ const pdfMod = await import(/* webpackIgnore: true */ 'pdf-parse')
170
+ const pdfParse = ((pdfMod as Record<string, unknown>).default ?? pdfMod) as (buf: Buffer) => Promise<{ text: string }>
171
+ const result = await pdfParse(fs.readFileSync(filePath))
172
+ if ((result.text || '').trim()) {
173
+ return { text: result.text, method: 'pdf-parse' }
174
+ }
175
+ } catch {
176
+ // fall through to pdftotext
177
+ }
178
+
179
+ const pdftotext = findBinaryOnPath('pdftotext')
180
+ if (!pdftotext) throw new Error('PDF extraction requires `pdf-parse` or `pdftotext`.')
181
+ const out = spawnSync(pdftotext, ['-layout', '-nopgbrk', '-q', filePath, '-'], {
182
+ encoding: 'utf-8',
183
+ maxBuffer: 25 * 1024 * 1024,
184
+ timeout: 20_000,
185
+ })
186
+ if ((out.status ?? 1) !== 0) {
187
+ throw new Error(`pdftotext failed: ${(out.stderr || out.stdout || '').trim() || 'unknown error'}`)
188
+ }
189
+ return { text: out.stdout || '', method: 'pdftotext' }
190
+ }
191
+
192
+ function extractImageText(filePath: string): { text: string; method: string } {
193
+ const tesseract = findBinaryOnPath('tesseract')
194
+ if (!tesseract) {
195
+ throw new Error('Image OCR requires `tesseract` on PATH.')
196
+ }
197
+ const out = spawnSync(tesseract, [filePath, 'stdout', '--psm', '6'], {
198
+ encoding: 'utf-8',
199
+ maxBuffer: 25 * 1024 * 1024,
200
+ timeout: 30_000,
201
+ })
202
+ if ((out.status ?? 1) !== 0) {
203
+ throw new Error(`tesseract failed: ${(out.stderr || out.stdout || '').trim() || 'unknown error'}`)
204
+ }
205
+ return { text: out.stdout || '', method: 'tesseract' }
206
+ }
207
+
208
+ function extractRichText(filePath: string): { text: string; method: string } {
209
+ const textutil = findBinaryOnPath('textutil')
210
+ if (!textutil) throw new Error('DOC/DOCX/RTF extraction requires `textutil` on PATH.')
211
+ const out = spawnSync(textutil, ['-convert', 'txt', '-stdout', filePath], {
212
+ encoding: 'utf-8',
213
+ maxBuffer: 25 * 1024 * 1024,
214
+ timeout: 20_000,
215
+ })
216
+ if ((out.status ?? 1) !== 0 || !(out.stdout || '').trim()) {
217
+ throw new Error(`textutil failed: ${(out.stderr || out.stdout || '').trim() || 'unknown error'}`)
218
+ }
219
+ return { text: out.stdout || '', method: 'textutil' }
220
+ }
221
+
222
+ export async function extractDocumentArtifact(filePath: string, options?: { maxChars?: number; preferOcr?: boolean }): Promise<DocumentArtifact> {
223
+ const resolved = path.resolve(filePath)
224
+ if (!fs.existsSync(resolved)) throw new Error(`File not found: ${filePath}`)
225
+ const stat = fs.statSync(resolved)
226
+ if (!stat.isFile()) throw new Error(`Expected a file: ${filePath}`)
227
+
228
+ const ext = path.extname(resolved).toLowerCase()
229
+ const metadata: Record<string, unknown> = {
230
+ sizeBytes: stat.size,
231
+ modifiedAt: stat.mtimeMs,
232
+ }
233
+ const maxChars = options?.maxChars || 200_000
234
+ let text = ''
235
+ let method = 'utf8'
236
+ let tables: StructuredTable[] = []
237
+
238
+ if (ext === '.pdf') {
239
+ const pdf = await extractPdfText(resolved)
240
+ text = pdf.text
241
+ method = pdf.method
242
+ } else if (ext === '.csv' || ext === '.tsv') {
243
+ const delimiter = ext === '.tsv' ? '\t' : ','
244
+ const raw = fs.readFileSync(resolved, 'utf-8')
245
+ const table = matrixToTable(path.basename(resolved), parseDelimitedText(raw, delimiter))
246
+ tables = [table]
247
+ text = tablesToText(tables)
248
+ method = ext === '.tsv' ? 'tsv' : 'csv'
249
+ } else if (ext === '.xlsx' || ext === '.xlsm') {
250
+ const ExcelJS = await import('exceljs')
251
+ const workbook = new ExcelJS.Workbook()
252
+ await workbook.xlsx.readFile(resolved)
253
+ tables = workbook.worksheets.map((worksheet) => {
254
+ const matrix: string[][] = []
255
+ worksheet.eachRow((row) => {
256
+ matrix.push(worksheetRowToArray(row.values).map((cell) => String(normalizeScalar(cell) ?? '')))
257
+ })
258
+ return matrixToTable(worksheet.name, matrix)
259
+ }).filter((table) => table.headers.length > 0 || table.rowCount > 0)
260
+ text = tablesToText(tables)
261
+ method = 'exceljs'
262
+ metadata.sheetNames = workbook.worksheets.map((sheet) => sheet.name)
263
+ } else if (ext === '.json') {
264
+ const raw = fs.readFileSync(resolved, 'utf-8')
265
+ text = raw
266
+ method = 'json'
267
+ try {
268
+ const parsed = JSON.parse(raw)
269
+ if (Array.isArray(parsed) && parsed.every((row) => row && typeof row === 'object' && !Array.isArray(row))) {
270
+ tables = [objectsToTable(path.basename(resolved), parsed as Array<Record<string, unknown>>)]
271
+ }
272
+ } catch {
273
+ // keep raw json text only
274
+ }
275
+ } else if (ext === '.html' || ext === '.htm') {
276
+ const html = fs.readFileSync(resolved, 'utf-8')
277
+ const $ = cheerio.load(html)
278
+ $('script, style, noscript').remove()
279
+ text = $('body').text() || $.text()
280
+ method = 'html-strip'
281
+ } else if (ext === '.zip') {
282
+ const zip = listZipEntries(resolved)
283
+ text = zip.entries.join('\n')
284
+ method = zip.method
285
+ metadata.entries = zip.entries
286
+ } else if (ext === '.doc' || ext === '.docx' || ext === '.rtf') {
287
+ const rich = extractRichText(resolved)
288
+ text = rich.text
289
+ method = rich.method
290
+ } else if (IMAGE_EXTENSIONS.has(ext) || options?.preferOcr === true) {
291
+ const image = extractImageText(resolved)
292
+ text = image.text
293
+ method = image.method
294
+ } else if (TEXT_EXTENSIONS.has(ext) || !ext) {
295
+ text = fs.readFileSync(resolved, 'utf-8')
296
+ method = 'utf8'
297
+ } else {
298
+ text = fs.readFileSync(resolved, 'utf-8')
299
+ method = 'utf8-fallback'
300
+ }
301
+
302
+ return {
303
+ filePath: resolved,
304
+ fileName: path.basename(resolved),
305
+ ext,
306
+ method,
307
+ text: trimText(text, maxChars),
308
+ metadata,
309
+ tables,
310
+ }
311
+ }
312
+
313
+ export async function loadTabularFile(filePath: string, options?: { sheetName?: string }): Promise<StructuredTable> {
314
+ const resolved = path.resolve(filePath)
315
+ const ext = path.extname(resolved).toLowerCase()
316
+ if (ext === '.csv' || ext === '.tsv') {
317
+ const delimiter = ext === '.tsv' ? '\t' : ','
318
+ return matrixToTable(path.basename(resolved), parseDelimitedText(fs.readFileSync(resolved, 'utf-8'), delimiter))
319
+ }
320
+ if (ext === '.json') {
321
+ const parsed = JSON.parse(fs.readFileSync(resolved, 'utf-8'))
322
+ if (!Array.isArray(parsed) || !parsed.every((row) => row && typeof row === 'object' && !Array.isArray(row))) {
323
+ throw new Error('JSON table inputs must be an array of objects.')
324
+ }
325
+ return objectsToTable(path.basename(resolved), parsed as Array<Record<string, unknown>>)
326
+ }
327
+ if (ext === '.xlsx' || ext === '.xlsm') {
328
+ const ExcelJS = await import('exceljs')
329
+ const workbook = new ExcelJS.Workbook()
330
+ await workbook.xlsx.readFile(resolved)
331
+ const target = options?.sheetName
332
+ ? workbook.getWorksheet(options.sheetName)
333
+ : workbook.worksheets[0]
334
+ if (!target) throw new Error(`Worksheet not found: ${options?.sheetName || '(first worksheet)'}`)
335
+ const matrix: string[][] = []
336
+ target.eachRow((row) => {
337
+ matrix.push(worksheetRowToArray(row.values).map((cell) => String(normalizeScalar(cell) ?? '')))
338
+ })
339
+ return matrixToTable(target.name, matrix)
340
+ }
341
+ throw new Error(`Unsupported tabular file: ${ext || '(no extension)'}`)
342
+ }
343
+
344
+ export function normalizeInlineRows(value: unknown): StructuredTable {
345
+ if (!Array.isArray(value)) throw new Error('rows must be an array.')
346
+ if (value.length === 0) return { name: 'rows', headers: [], rows: [], rowCount: 0 }
347
+ if (value.every((row) => Array.isArray(row))) {
348
+ return matrixToTable('rows', value.map((row) => (row as unknown[]).map((cell) => String(normalizeScalar(cell) ?? ''))))
349
+ }
350
+ if (value.every((row) => row && typeof row === 'object' && !Array.isArray(row))) {
351
+ return objectsToTable('rows', value as Array<Record<string, unknown>>)
352
+ }
353
+ throw new Error('rows must be an array of objects or arrays.')
354
+ }
355
+
356
+ function escapeDelimitedCell(value: unknown, delimiter: string): string {
357
+ const raw = String(normalizeScalar(value) ?? '')
358
+ if (raw.includes('"') || raw.includes('\n') || raw.includes(delimiter)) {
359
+ return `"${raw.replace(/"/g, '""')}"`
360
+ }
361
+ return raw
362
+ }
363
+
364
+ export function serializeTable(table: StructuredTable, delimiter = ','): string {
365
+ const header = table.headers.map((cell) => escapeDelimitedCell(cell, delimiter)).join(delimiter)
366
+ const rows = table.rows.map((row) => table.headers.map((headerCell) => escapeDelimitedCell(row[headerCell], delimiter)).join(delimiter))
367
+ return [header, ...rows].join('\n')
368
+ }
369
+
370
+ export async function writeStructuredTable(filePath: string, table: StructuredTable): Promise<{ filePath: string; format: string }> {
371
+ const resolved = path.resolve(filePath)
372
+ const ext = path.extname(resolved).toLowerCase()
373
+ fs.mkdirSync(path.dirname(resolved), { recursive: true })
374
+
375
+ if (ext === '.json') {
376
+ fs.writeFileSync(resolved, JSON.stringify(table.rows, null, 2), 'utf-8')
377
+ return { filePath: resolved, format: 'json' }
378
+ }
379
+ if (ext === '.tsv') {
380
+ fs.writeFileSync(resolved, serializeTable(table, '\t'), 'utf-8')
381
+ return { filePath: resolved, format: 'tsv' }
382
+ }
383
+ if (ext === '.xlsx') {
384
+ const ExcelJS = await import('exceljs')
385
+ const workbook = new ExcelJS.Workbook()
386
+ const worksheet = workbook.addWorksheet(table.name || 'Sheet1')
387
+ worksheet.addRow(table.headers)
388
+ for (const row of table.rows) {
389
+ worksheet.addRow(table.headers.map((header) => row[header] ?? null))
390
+ }
391
+ await workbook.xlsx.writeFile(resolved)
392
+ return { filePath: resolved, format: 'xlsx' }
393
+ }
394
+
395
+ fs.writeFileSync(resolved, serializeTable(table, ','), 'utf-8')
396
+ return { filePath: resolved, format: 'csv' }
397
+ }
@@ -0,0 +1,47 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+ import { AGENT_REGRESSION_SCENARIOS, resolveRegressionApprovalSettings, scoreAssertions } from './agent-regression'
4
+
5
+ describe('agent regression helpers', () => {
6
+ it('maps approval modes onto deterministic platform settings', () => {
7
+ assert.deepEqual(resolveRegressionApprovalSettings('manual'), {
8
+ approvalsEnabled: true,
9
+ approvalAutoApproveCategories: [],
10
+ })
11
+ assert.deepEqual(resolveRegressionApprovalSettings('auto'), {
12
+ approvalsEnabled: true,
13
+ approvalAutoApproveCategories: ['tool_access'],
14
+ })
15
+ assert.deepEqual(resolveRegressionApprovalSettings('off'), {
16
+ approvalsEnabled: false,
17
+ approvalAutoApproveCategories: [],
18
+ })
19
+ })
20
+
21
+ it('scores scenarios from assertion weights instead of prose', () => {
22
+ const scored = scoreAssertions([
23
+ { name: 'artifact exists', passed: true, weight: 2 },
24
+ { name: 'exact token preserved', passed: false, weight: 3 },
25
+ { name: 'delegate used', passed: true },
26
+ ])
27
+
28
+ assert.deepEqual(scored, {
29
+ score: 3,
30
+ maxScore: 6,
31
+ status: 'failed',
32
+ })
33
+ })
34
+
35
+ it('includes the extended signup, secrets, email, and human-verification scenarios', () => {
36
+ const ids = AGENT_REGRESSION_SCENARIOS.map((scenario) => scenario.id)
37
+ assert.deepEqual(ids, [
38
+ 'approval-resume',
39
+ 'delegate-literal-artifact',
40
+ 'schedule-script',
41
+ 'open-ended-iteration',
42
+ 'mock-signup-secret-email',
43
+ 'human-verified-signup',
44
+ 'research-build-deploy',
45
+ ])
46
+ })
47
+ })