@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.
- package/README.md +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- 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
|
+
})
|