@swarmclawai/swarmclaw 0.7.1 → 0.7.3
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 +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- 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/files/open/route.ts +16 -14
- 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/skills/route.ts +11 -3
- 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 +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- 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 +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- 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 +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- 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/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -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 +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- 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 +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- 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 +948 -112
- 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 +188 -9
- 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/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -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/heartbeat-service.ts +14 -40
- 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 +28 -1103
- 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 +5 -6
- 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 +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- 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 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- 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 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- 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/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
4
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
5
|
+
import { getPluginManager } from '../plugins'
|
|
6
|
+
import { runStructuredExtraction } from '../structured-extract'
|
|
7
|
+
import type { ToolBuildContext } from './context'
|
|
8
|
+
import { safePath } from './context'
|
|
9
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
10
|
+
|
|
11
|
+
function resolveSessionForExtraction(bctx: ToolBuildContext) {
|
|
12
|
+
const session = bctx.resolveCurrentSession?.()
|
|
13
|
+
if (!session) throw new Error('extract requires an active session context.')
|
|
14
|
+
return session
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function executeExtractAction(args: Record<string, unknown>, bctx: ToolBuildContext) {
|
|
18
|
+
const normalized = normalizeToolInputArgs(args)
|
|
19
|
+
const action = String(normalized.action || 'extract_structured').trim().toLowerCase()
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (action === 'status') {
|
|
23
|
+
const session = resolveSessionForExtraction(bctx)
|
|
24
|
+
return JSON.stringify({
|
|
25
|
+
provider: session.provider || null,
|
|
26
|
+
model: session.model || null,
|
|
27
|
+
source: 'session',
|
|
28
|
+
supports: ['extract_structured', 'summarize'],
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const session = resolveSessionForExtraction(bctx)
|
|
33
|
+
const filePath = typeof normalized.filePath === 'string' && normalized.filePath.trim()
|
|
34
|
+
? (path.isAbsolute(normalized.filePath) ? path.resolve(normalized.filePath) : safePath(bctx.cwd, normalized.filePath))
|
|
35
|
+
: typeof normalized.path === 'string' && normalized.path.trim()
|
|
36
|
+
? (path.isAbsolute(normalized.path) ? path.resolve(normalized.path) : safePath(bctx.cwd, normalized.path))
|
|
37
|
+
: null
|
|
38
|
+
const schema = action === 'summarize' ? undefined : normalized.schema
|
|
39
|
+
const instruction = typeof normalized.instruction === 'string'
|
|
40
|
+
? normalized.instruction
|
|
41
|
+
: action === 'summarize'
|
|
42
|
+
? (typeof normalized.prompt === 'string' ? normalized.prompt : 'Summarize the input and extract the main entities and key points.')
|
|
43
|
+
: typeof normalized.prompt === 'string'
|
|
44
|
+
? normalized.prompt
|
|
45
|
+
: 'Extract the requested structured data.'
|
|
46
|
+
const result = await runStructuredExtraction({
|
|
47
|
+
session,
|
|
48
|
+
text: typeof normalized.text === 'string' ? normalized.text : typeof normalized.content === 'string' ? normalized.content : null,
|
|
49
|
+
filePath,
|
|
50
|
+
instruction,
|
|
51
|
+
schema,
|
|
52
|
+
preferOcr: normalized.preferOcr === true,
|
|
53
|
+
maxChars: typeof normalized.maxChars === 'number' ? Math.max(5_000, normalized.maxChars) : undefined,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return JSON.stringify({
|
|
57
|
+
object: result.object,
|
|
58
|
+
validationErrors: result.validationErrors,
|
|
59
|
+
provider: result.provider,
|
|
60
|
+
model: result.model,
|
|
61
|
+
source: {
|
|
62
|
+
kind: result.source.kind,
|
|
63
|
+
filePath: result.source.filePath || null,
|
|
64
|
+
method: result.source.artifact?.method || null,
|
|
65
|
+
fileName: result.source.artifact?.fileName || null,
|
|
66
|
+
},
|
|
67
|
+
raw: normalized.includeRaw === true ? result.raw : undefined,
|
|
68
|
+
})
|
|
69
|
+
} catch (err: unknown) {
|
|
70
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const ExtractPlugin: Plugin = {
|
|
75
|
+
name: 'Extract',
|
|
76
|
+
enabledByDefault: false,
|
|
77
|
+
description: 'Run schema-driven structured extraction over text or local files using the current session model.',
|
|
78
|
+
hooks: {
|
|
79
|
+
getCapabilityDescription: () =>
|
|
80
|
+
'I can turn unstructured text or documents into validated JSON with `extract`, using the current session provider/model and a caller-supplied schema.',
|
|
81
|
+
} as PluginHooks,
|
|
82
|
+
tools: [
|
|
83
|
+
{
|
|
84
|
+
name: 'extract',
|
|
85
|
+
description: 'Structured extraction tool. Actions: extract_structured, summarize, status.',
|
|
86
|
+
parameters: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
action: { type: 'string', enum: ['extract_structured', 'summarize', 'status'] },
|
|
90
|
+
text: { type: 'string' },
|
|
91
|
+
content: { type: 'string' },
|
|
92
|
+
filePath: { type: 'string' },
|
|
93
|
+
schema: {},
|
|
94
|
+
instruction: { type: 'string' },
|
|
95
|
+
prompt: { type: 'string' },
|
|
96
|
+
maxChars: { type: 'number' },
|
|
97
|
+
preferOcr: { type: 'boolean' },
|
|
98
|
+
includeRaw: { type: 'boolean' },
|
|
99
|
+
},
|
|
100
|
+
required: ['action'],
|
|
101
|
+
},
|
|
102
|
+
execute: async (args, context) => {
|
|
103
|
+
const syntheticBuildContext = {
|
|
104
|
+
cwd: context.session.cwd || process.cwd(),
|
|
105
|
+
ctx: { sessionId: context.session.id, agentId: context.session.agentId || null },
|
|
106
|
+
hasPlugin: () => true,
|
|
107
|
+
hasTool: () => true,
|
|
108
|
+
cleanupFns: [],
|
|
109
|
+
commandTimeoutMs: 0,
|
|
110
|
+
claudeTimeoutMs: 0,
|
|
111
|
+
cliProcessTimeoutMs: 0,
|
|
112
|
+
persistDelegateResumeId: () => undefined,
|
|
113
|
+
readStoredDelegateResumeId: () => null,
|
|
114
|
+
resolveCurrentSession: () => context.session,
|
|
115
|
+
activePlugins: context.session.plugins || [],
|
|
116
|
+
} as ToolBuildContext
|
|
117
|
+
return executeExtractAction(args, syntheticBuildContext)
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getPluginManager().registerBuiltin('extract', ExtractPlugin)
|
|
124
|
+
|
|
125
|
+
export function buildExtractTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
126
|
+
if (!bctx.hasPlugin('extract')) return []
|
|
127
|
+
return [
|
|
128
|
+
tool(
|
|
129
|
+
async (args) => executeExtractAction(args, bctx),
|
|
130
|
+
{
|
|
131
|
+
name: 'extract',
|
|
132
|
+
description: ExtractPlugin.tools![0].description,
|
|
133
|
+
schema: z.object({}).passthrough(),
|
|
134
|
+
},
|
|
135
|
+
),
|
|
136
|
+
]
|
|
137
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { executeFileAction, normalizeFileArgs } from './file'
|
|
7
|
+
|
|
8
|
+
describe('normalizeFileArgs', () => {
|
|
9
|
+
it('infers write from top-level filename and text', () => {
|
|
10
|
+
const out = normalizeFileArgs({ filename: 'note.txt', text: 'hello' })
|
|
11
|
+
assert.equal(out.action, 'write')
|
|
12
|
+
assert.equal(out.filePath, 'note.txt')
|
|
13
|
+
assert.equal(out.content, 'hello')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('accepts top-level name and body aliases', () => {
|
|
17
|
+
const out = normalizeFileArgs({ name: 'note.txt', body: 'hello' })
|
|
18
|
+
assert.equal(out.action, 'write')
|
|
19
|
+
assert.equal(out.filePath, 'note.txt')
|
|
20
|
+
assert.equal(out.content, 'hello')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('infers read from filename when action is omitted', () => {
|
|
24
|
+
const out = normalizeFileArgs({ filename: 'note.txt' })
|
|
25
|
+
assert.equal(out.action, 'read')
|
|
26
|
+
assert.equal(out.filePath, 'note.txt')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('infers read from lowercase filepath alias when action is omitted', () => {
|
|
30
|
+
const out = normalizeFileArgs({ filepath: 'note.txt' })
|
|
31
|
+
assert.equal(out.action, 'read')
|
|
32
|
+
assert.equal(out.filePath, 'note.txt')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('infers list from directory aliases', () => {
|
|
36
|
+
const out = normalizeFileArgs({ directory: 'docs' })
|
|
37
|
+
assert.equal(out.action, 'list')
|
|
38
|
+
assert.equal(out.dirPath, 'docs')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('infers write from bulk file entries with text content', () => {
|
|
42
|
+
const out = normalizeFileArgs({
|
|
43
|
+
files: [
|
|
44
|
+
{ filename: 'a.txt', text: 'alpha' },
|
|
45
|
+
],
|
|
46
|
+
})
|
|
47
|
+
assert.equal(out.action, 'write')
|
|
48
|
+
assert.deepEqual(out.files, [{ filename: 'a.txt', text: 'alpha' }])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('normalizes legacy write wrapper payloads', () => {
|
|
52
|
+
const out = normalizeFileArgs({
|
|
53
|
+
input: JSON.stringify({
|
|
54
|
+
write: {
|
|
55
|
+
filename: 'legacy.txt',
|
|
56
|
+
content: 'legacy body',
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
assert.equal(out.action, 'write')
|
|
62
|
+
assert.equal(out.filePath, 'legacy.txt')
|
|
63
|
+
assert.equal(out.content, 'legacy body')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('preserves nested write.files arrays from natural model payloads', () => {
|
|
67
|
+
const out = normalizeFileArgs({
|
|
68
|
+
input: JSON.stringify({
|
|
69
|
+
write: {
|
|
70
|
+
files: [
|
|
71
|
+
{ name: 'report.md', content: '# report' },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
assert.equal(out.action, 'write')
|
|
78
|
+
assert.deepEqual(out.files, [{ name: 'report.md', content: '# report' }])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('treats trailing-slash write targets as directory creation', async () => {
|
|
82
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'file-write-dir-'))
|
|
83
|
+
const out = await executeFileAction({
|
|
84
|
+
action: 'write',
|
|
85
|
+
path: 'weather_update/',
|
|
86
|
+
content: 'placeholder',
|
|
87
|
+
}, { cwd })
|
|
88
|
+
|
|
89
|
+
assert.equal(out, 'Created directory weather_update/')
|
|
90
|
+
assert.equal(fs.statSync(path.join(cwd, 'weather_update')).isDirectory(), true)
|
|
91
|
+
fs.rmSync(cwd, { recursive: true, force: true })
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, it } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { findRecentSendFileFallbackPaths, normalizeSendFilePaths, resolveSendFileSourcePath } from './file'
|
|
4
7
|
|
|
5
8
|
describe('normalizeSendFilePaths', () => {
|
|
6
9
|
it('reads top-level filePath', () => {
|
|
@@ -8,6 +11,11 @@ describe('normalizeSendFilePaths', () => {
|
|
|
8
11
|
assert.deepEqual(out, ['foo.png'])
|
|
9
12
|
})
|
|
10
13
|
|
|
14
|
+
it('reads top-level lowercase filepath alias', () => {
|
|
15
|
+
const out = normalizeSendFilePaths({ filepath: 'foo.png' })
|
|
16
|
+
assert.deepEqual(out, ['foo.png'])
|
|
17
|
+
})
|
|
18
|
+
|
|
11
19
|
it('reads nested input.files string array payload', () => {
|
|
12
20
|
const out = normalizeSendFilePaths({
|
|
13
21
|
input: {
|
|
@@ -26,6 +34,16 @@ describe('normalizeSendFilePaths', () => {
|
|
|
26
34
|
assert.deepEqual(out, ['a.png'])
|
|
27
35
|
})
|
|
28
36
|
|
|
37
|
+
it('accepts filePaths arrays from natural model tool calls', () => {
|
|
38
|
+
const out = normalizeSendFilePaths({
|
|
39
|
+
filePaths: ['a.png', 'b.png'],
|
|
40
|
+
input: JSON.stringify({
|
|
41
|
+
filePaths: ['b.png', 'c.png'],
|
|
42
|
+
}),
|
|
43
|
+
})
|
|
44
|
+
assert.deepEqual(out, ['a.png', 'b.png', 'c.png'])
|
|
45
|
+
})
|
|
46
|
+
|
|
29
47
|
it('reads files object entries with path/filePath and dedupes', () => {
|
|
30
48
|
const out = normalizeSendFilePaths({
|
|
31
49
|
files: [
|
|
@@ -36,4 +54,69 @@ describe('normalizeSendFilePaths', () => {
|
|
|
36
54
|
})
|
|
37
55
|
assert.deepEqual(out, ['a.png', 'b.png'])
|
|
38
56
|
})
|
|
57
|
+
|
|
58
|
+
it('accepts filename/name aliases commonly produced by model tool calls', () => {
|
|
59
|
+
const out = normalizeSendFilePaths({
|
|
60
|
+
filename: 'brief.md',
|
|
61
|
+
input: {
|
|
62
|
+
files: [{ name: 'fallback.md' }],
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
assert.deepEqual(out, ['brief.md', 'fallback.md'])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('accepts fileId aliases commonly produced by model tool calls', () => {
|
|
69
|
+
const out = normalizeSendFilePaths({
|
|
70
|
+
input: {
|
|
71
|
+
fileId: 'brief.md',
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
assert.deepEqual(out, ['brief.md'])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('extracts upload URLs from screenshot markdown tool output', () => {
|
|
78
|
+
const out = normalizeSendFilePaths({
|
|
79
|
+
filePath: '- [Screenshot of viewport](../../../.swarmclaw/browser-profiles/session/mcp-output/page.png)\n',
|
|
80
|
+
})
|
|
81
|
+
assert.deepEqual(out, ['/api/uploads/screenshot-123.png', '../../../.swarmclaw/browser-profiles/session/mcp-output/page.png'])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('falls back to a single recent file in the workspace when the payload is empty', () => {
|
|
85
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'send-file-'))
|
|
86
|
+
const recent = path.join(dir, 'brief.md')
|
|
87
|
+
const stale = path.join(dir, 'notes.txt')
|
|
88
|
+
fs.writeFileSync(recent, '# brief')
|
|
89
|
+
fs.writeFileSync(stale, 'old')
|
|
90
|
+
const oldTime = new Date(Date.now() - 20 * 60 * 1000)
|
|
91
|
+
fs.utimesSync(stale, oldTime, oldTime)
|
|
92
|
+
|
|
93
|
+
const out = findRecentSendFileFallbackPaths(dir)
|
|
94
|
+
assert.deepEqual(out, ['brief.md'])
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('resolves sandbox upload URLs when sending files', async () => {
|
|
98
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'send-file-upload-'))
|
|
99
|
+
const resolved = resolveSendFileSourcePath(cwd, 'sandbox:/api/uploads/artifact.md')
|
|
100
|
+
assert.equal(path.basename(resolved), 'artifact.md')
|
|
101
|
+
assert.match(resolved, /uploads[\/\\]artifact\.md$/)
|
|
102
|
+
fs.rmSync(cwd, { recursive: true, force: true })
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('resolves /workspace aliases against the current session workspace first', () => {
|
|
106
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'send-file-workspace-alias-'))
|
|
107
|
+
const artifact = path.join(cwd, 'spec.md')
|
|
108
|
+
fs.writeFileSync(artifact, '# spec')
|
|
109
|
+
|
|
110
|
+
const resolved = resolveSendFileSourcePath(cwd, '/workspace/spec.md')
|
|
111
|
+
|
|
112
|
+
assert.equal(resolved, artifact)
|
|
113
|
+
fs.rmSync(cwd, { recursive: true, force: true })
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('resolves browser profile screenshot paths back into the agent home directory', () => {
|
|
117
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'send-file-browser-profile-'))
|
|
118
|
+
const resolved = resolveSendFileSourcePath(cwd, '../../../.swarmclaw/browser-profiles/example/mcp-output/page.png')
|
|
119
|
+
assert.match(resolved, new RegExp(`\\.swarmclaw[\\\\/]browser-profiles[\\\\/]example[\\\\/]mcp-output[\\\\/]page\\.png$`))
|
|
120
|
+
fs.rmSync(cwd, { recursive: true, force: true })
|
|
121
|
+
})
|
|
39
122
|
})
|