@swarmclawai/swarmclaw 1.2.6 → 1.2.8
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 +24 -17
- package/next.config.ts +1 -0
- package/package.json +3 -2
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/inspector-panel.tsx +165 -48
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/builtin-extensions.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +79 -42
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.ts +11 -1
- package/src/lib/server/chat-execution/prompt-builder.test.ts +28 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -1
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +6 -4
- package/src/lib/server/chat-execution/stream-agent-chat.ts +45 -16
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/provider-health.ts +1 -1
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -0
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -0
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -4
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +2 -0
- package/src/lib/server/tool-aliases.ts +2 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +9 -2
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -33
- package/src/lib/server/tool-planning.ts +11 -0
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +16 -0
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +348 -0
- package/src/types/app-settings.ts +175 -0
- package/src/types/approval.ts +27 -0
- package/src/types/connector.ts +187 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +57 -0
- package/src/types/misc.ts +739 -0
- package/src/types/mission.ts +185 -0
- package/src/types/protocol.ts +422 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +183 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +265 -0
- package/src/types/skill.ts +157 -0
- package/src/types/task.ts +140 -0
- package/src/types/working-state.ts +211 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/lib/server/session-tools/sandbox.ts +0 -281
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { MissionSummary } from './mission'
|
|
2
|
+
import type { GoalContract } from './app-settings'
|
|
3
|
+
|
|
4
|
+
// --- Task Board ---
|
|
5
|
+
|
|
6
|
+
export type BoardTaskStatus = 'backlog' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled' | 'archived' | 'deferred'
|
|
7
|
+
|
|
8
|
+
export interface TaskComment {
|
|
9
|
+
id: string
|
|
10
|
+
author: string // agent name or 'user'
|
|
11
|
+
agentId?: string // if from an agent
|
|
12
|
+
text: string
|
|
13
|
+
createdAt: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TaskQualityGateConfig {
|
|
17
|
+
enabled?: boolean
|
|
18
|
+
minResultChars?: number
|
|
19
|
+
minEvidenceItems?: number
|
|
20
|
+
requireVerification?: boolean
|
|
21
|
+
requireArtifact?: boolean
|
|
22
|
+
requireReport?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface BoardTask {
|
|
26
|
+
id: string
|
|
27
|
+
title: string
|
|
28
|
+
description: string
|
|
29
|
+
status: BoardTaskStatus
|
|
30
|
+
agentId: string
|
|
31
|
+
missionId?: string | null
|
|
32
|
+
protocolRunId?: string | null
|
|
33
|
+
missionSummary?: MissionSummary | null
|
|
34
|
+
projectId?: string
|
|
35
|
+
goalContract?: GoalContract | null
|
|
36
|
+
cwd?: string | null
|
|
37
|
+
file?: string | null
|
|
38
|
+
sessionId?: string | null
|
|
39
|
+
completionReportPath?: string | null
|
|
40
|
+
result?: string | null
|
|
41
|
+
error?: string | null
|
|
42
|
+
outputFiles?: string[]
|
|
43
|
+
artifacts?: Array<{
|
|
44
|
+
url: string
|
|
45
|
+
type: 'image' | 'video' | 'pdf' | 'file'
|
|
46
|
+
filename: string
|
|
47
|
+
}>
|
|
48
|
+
comments?: TaskComment[]
|
|
49
|
+
images?: string[]
|
|
50
|
+
createdByAgentId?: string | null
|
|
51
|
+
createdInSessionId?: string | null
|
|
52
|
+
followupConnectorId?: string | null
|
|
53
|
+
followupChannelId?: string | null
|
|
54
|
+
followupThreadId?: string | null
|
|
55
|
+
followupSenderId?: string | null
|
|
56
|
+
followupSenderName?: string | null
|
|
57
|
+
delegatedByAgentId?: string | null
|
|
58
|
+
delegatedFromTaskId?: string | null
|
|
59
|
+
delegationDepth?: number | null
|
|
60
|
+
createdAt: number
|
|
61
|
+
updatedAt: number
|
|
62
|
+
queuedAt?: number | null
|
|
63
|
+
startedAt?: number | null
|
|
64
|
+
completedAt?: number | null
|
|
65
|
+
archivedAt?: number | null
|
|
66
|
+
attempts?: number
|
|
67
|
+
maxAttempts?: number
|
|
68
|
+
retryBackoffSec?: number
|
|
69
|
+
retryScheduledAt?: number | null
|
|
70
|
+
runNumber?: number
|
|
71
|
+
totalRuns?: number
|
|
72
|
+
totalCompleted?: number
|
|
73
|
+
totalFailed?: number
|
|
74
|
+
sourceType?: 'schedule' | 'delegation' | 'manual' | 'import'
|
|
75
|
+
sourceScheduleId?: string | null
|
|
76
|
+
sourceScheduleName?: string | null
|
|
77
|
+
sourceScheduleKey?: string | null
|
|
78
|
+
externalSource?: {
|
|
79
|
+
source: string
|
|
80
|
+
id?: string | null
|
|
81
|
+
repo?: string | null
|
|
82
|
+
number?: number | null
|
|
83
|
+
state?: string | null
|
|
84
|
+
labels?: string[]
|
|
85
|
+
assignee?: string | null
|
|
86
|
+
url?: string | null
|
|
87
|
+
} | null
|
|
88
|
+
lastActivityAt?: number | null
|
|
89
|
+
deferredReason?: string | null
|
|
90
|
+
deadLetteredAt?: number | null
|
|
91
|
+
cliResumeId?: string | null
|
|
92
|
+
cliProvider?: string | null
|
|
93
|
+
claudeResumeId?: string | null
|
|
94
|
+
codexResumeId?: string | null
|
|
95
|
+
opencodeResumeId?: string | null
|
|
96
|
+
geminiResumeId?: string | null
|
|
97
|
+
checkpoint?: {
|
|
98
|
+
lastRunId?: string | null
|
|
99
|
+
lastSessionId?: string | null
|
|
100
|
+
note?: string | null
|
|
101
|
+
updatedAt: number
|
|
102
|
+
} | null
|
|
103
|
+
validation?: {
|
|
104
|
+
ok: boolean
|
|
105
|
+
reasons: string[]
|
|
106
|
+
checkedAt: number
|
|
107
|
+
} | null
|
|
108
|
+
// Parent/child task hierarchy (user-created subtasks)
|
|
109
|
+
parentTaskId?: string | null
|
|
110
|
+
subtaskIds?: string[]
|
|
111
|
+
// Task dependencies (DAG)
|
|
112
|
+
blockedBy?: string[]
|
|
113
|
+
blocks?: string[]
|
|
114
|
+
// Task tags
|
|
115
|
+
tags?: string[]
|
|
116
|
+
// Due date
|
|
117
|
+
dueAt?: number | null
|
|
118
|
+
// Custom fields
|
|
119
|
+
customFields?: Record<string, string | number | boolean>
|
|
120
|
+
// Priority
|
|
121
|
+
priority?: 'low' | 'medium' | 'high' | 'critical'
|
|
122
|
+
// Dedup fingerprint
|
|
123
|
+
fingerprint?: string
|
|
124
|
+
qualityGate?: TaskQualityGateConfig | null
|
|
125
|
+
// Competitive task claiming (pool mode)
|
|
126
|
+
assignmentMode?: 'direct' | 'pool'
|
|
127
|
+
poolCandidateAgentIds?: string[]
|
|
128
|
+
claimedByAgentId?: string | null
|
|
129
|
+
claimedAt?: number | null
|
|
130
|
+
requiredCapabilities?: string[]
|
|
131
|
+
// Upstream task results (populated by cascadeUnblock when dependencies complete)
|
|
132
|
+
upstreamResults?: Array<{
|
|
133
|
+
taskId: string
|
|
134
|
+
taskTitle: string
|
|
135
|
+
agentId: string | null
|
|
136
|
+
resultPreview: string | null
|
|
137
|
+
}>
|
|
138
|
+
repairRunId?: string | null
|
|
139
|
+
lastRepairAttemptAt?: number | null
|
|
140
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { MissionStatus, MissionPhase, MissionWaitState } from './mission'
|
|
2
|
+
|
|
3
|
+
export type WorkingStateStatus = 'idle' | 'progress' | 'blocked' | 'waiting' | 'completed'
|
|
4
|
+
export type WorkingStateItemStatus = 'active' | 'resolved' | 'superseded'
|
|
5
|
+
|
|
6
|
+
export interface EvidenceRef {
|
|
7
|
+
id: string
|
|
8
|
+
type: 'tool' | 'message' | 'mission' | 'task' | 'artifact' | 'error' | 'approval'
|
|
9
|
+
summary: string
|
|
10
|
+
value?: string | null
|
|
11
|
+
toolName?: string | null
|
|
12
|
+
toolCallId?: string | null
|
|
13
|
+
runId?: string | null
|
|
14
|
+
sessionId?: string | null
|
|
15
|
+
missionId?: string | null
|
|
16
|
+
taskId?: string | null
|
|
17
|
+
createdAt: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WorkingPlanStep {
|
|
21
|
+
id: string
|
|
22
|
+
text: string
|
|
23
|
+
status: WorkingStateItemStatus
|
|
24
|
+
createdAt: number
|
|
25
|
+
updatedAt: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WorkingFact {
|
|
29
|
+
id: string
|
|
30
|
+
statement: string
|
|
31
|
+
source: 'user' | 'tool' | 'assistant' | 'mission' | 'system'
|
|
32
|
+
status: WorkingStateItemStatus
|
|
33
|
+
evidenceIds?: string[]
|
|
34
|
+
createdAt: number
|
|
35
|
+
updatedAt: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface WorkingArtifact {
|
|
39
|
+
id: string
|
|
40
|
+
label: string
|
|
41
|
+
kind: 'file' | 'url' | 'approval' | 'message' | 'other'
|
|
42
|
+
path?: string | null
|
|
43
|
+
url?: string | null
|
|
44
|
+
sourceTool?: string | null
|
|
45
|
+
status: WorkingStateItemStatus
|
|
46
|
+
evidenceIds?: string[]
|
|
47
|
+
createdAt: number
|
|
48
|
+
updatedAt: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WorkingDecision {
|
|
52
|
+
id: string
|
|
53
|
+
summary: string
|
|
54
|
+
rationale?: string | null
|
|
55
|
+
status: WorkingStateItemStatus
|
|
56
|
+
evidenceIds?: string[]
|
|
57
|
+
createdAt: number
|
|
58
|
+
updatedAt: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WorkingBlocker {
|
|
62
|
+
id: string
|
|
63
|
+
summary: string
|
|
64
|
+
kind?: 'approval' | 'credential' | 'human_input' | 'external_dependency' | 'error' | 'other' | null
|
|
65
|
+
nextAction?: string | null
|
|
66
|
+
status: WorkingStateItemStatus
|
|
67
|
+
evidenceIds?: string[]
|
|
68
|
+
createdAt: number
|
|
69
|
+
updatedAt: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface WorkingQuestion {
|
|
73
|
+
id: string
|
|
74
|
+
question: string
|
|
75
|
+
status: WorkingStateItemStatus
|
|
76
|
+
evidenceIds?: string[]
|
|
77
|
+
createdAt: number
|
|
78
|
+
updatedAt: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface WorkingHypothesis {
|
|
82
|
+
id: string
|
|
83
|
+
statement: string
|
|
84
|
+
confidence?: 'low' | 'medium' | 'high' | null
|
|
85
|
+
status: WorkingStateItemStatus
|
|
86
|
+
evidenceIds?: string[]
|
|
87
|
+
createdAt: number
|
|
88
|
+
updatedAt: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface WorkingPlanStepPatch {
|
|
92
|
+
id?: string | null
|
|
93
|
+
text: string
|
|
94
|
+
status?: WorkingStateItemStatus | null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface WorkingFactPatch {
|
|
98
|
+
id?: string | null
|
|
99
|
+
statement: string
|
|
100
|
+
source?: WorkingFact['source'] | null
|
|
101
|
+
status?: WorkingStateItemStatus | null
|
|
102
|
+
evidenceIds?: string[]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface WorkingArtifactPatch {
|
|
106
|
+
id?: string | null
|
|
107
|
+
label: string
|
|
108
|
+
kind?: WorkingArtifact['kind'] | null
|
|
109
|
+
path?: string | null
|
|
110
|
+
url?: string | null
|
|
111
|
+
sourceTool?: string | null
|
|
112
|
+
status?: WorkingStateItemStatus | null
|
|
113
|
+
evidenceIds?: string[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface WorkingDecisionPatch {
|
|
117
|
+
id?: string | null
|
|
118
|
+
summary: string
|
|
119
|
+
rationale?: string | null
|
|
120
|
+
status?: WorkingStateItemStatus | null
|
|
121
|
+
evidenceIds?: string[]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface WorkingBlockerPatch {
|
|
125
|
+
id?: string | null
|
|
126
|
+
summary: string
|
|
127
|
+
kind?: WorkingBlocker['kind']
|
|
128
|
+
nextAction?: string | null
|
|
129
|
+
status?: WorkingStateItemStatus | null
|
|
130
|
+
evidenceIds?: string[]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface WorkingQuestionPatch {
|
|
134
|
+
id?: string | null
|
|
135
|
+
question: string
|
|
136
|
+
status?: WorkingStateItemStatus | null
|
|
137
|
+
evidenceIds?: string[]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface WorkingHypothesisPatch {
|
|
141
|
+
id?: string | null
|
|
142
|
+
statement: string
|
|
143
|
+
confidence?: WorkingHypothesis['confidence']
|
|
144
|
+
status?: WorkingStateItemStatus | null
|
|
145
|
+
evidenceIds?: string[]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface WorkingStatePatch {
|
|
149
|
+
objective?: string | null
|
|
150
|
+
summary?: string | null
|
|
151
|
+
constraints?: string[]
|
|
152
|
+
successCriteria?: string[]
|
|
153
|
+
status?: WorkingStateStatus | null
|
|
154
|
+
nextAction?: string | null
|
|
155
|
+
planSteps?: WorkingPlanStepPatch[]
|
|
156
|
+
factsUpsert?: WorkingFactPatch[]
|
|
157
|
+
artifactsUpsert?: WorkingArtifactPatch[]
|
|
158
|
+
decisionsAppend?: WorkingDecisionPatch[]
|
|
159
|
+
blockersUpsert?: WorkingBlockerPatch[]
|
|
160
|
+
questionsUpsert?: WorkingQuestionPatch[]
|
|
161
|
+
hypothesesUpsert?: WorkingHypothesisPatch[]
|
|
162
|
+
evidenceAppend?: EvidenceRef[]
|
|
163
|
+
supersedeIds?: string[]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface SessionWorkingState {
|
|
167
|
+
sessionId: string
|
|
168
|
+
missionId?: string | null
|
|
169
|
+
objective?: string | null
|
|
170
|
+
summary?: string | null
|
|
171
|
+
constraints: string[]
|
|
172
|
+
successCriteria: string[]
|
|
173
|
+
status: WorkingStateStatus
|
|
174
|
+
nextAction?: string | null
|
|
175
|
+
planSteps: WorkingPlanStep[]
|
|
176
|
+
confirmedFacts: WorkingFact[]
|
|
177
|
+
artifacts: WorkingArtifact[]
|
|
178
|
+
decisions: WorkingDecision[]
|
|
179
|
+
blockers: WorkingBlocker[]
|
|
180
|
+
openQuestions: WorkingQuestion[]
|
|
181
|
+
hypotheses: WorkingHypothesis[]
|
|
182
|
+
evidenceRefs: EvidenceRef[]
|
|
183
|
+
createdAt: number
|
|
184
|
+
updatedAt: number
|
|
185
|
+
lastCompactedAt?: number | null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface ExecutionBriefPlanStep {
|
|
189
|
+
text: string
|
|
190
|
+
status: WorkingStateItemStatus
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface ExecutionBrief {
|
|
194
|
+
sessionId?: string | null
|
|
195
|
+
missionId?: string | null
|
|
196
|
+
objective: string | null
|
|
197
|
+
summary: string | null
|
|
198
|
+
status: WorkingStateStatus
|
|
199
|
+
nextAction: string | null
|
|
200
|
+
plan: ExecutionBriefPlanStep[]
|
|
201
|
+
blockers: string[]
|
|
202
|
+
facts: string[]
|
|
203
|
+
artifacts: string[]
|
|
204
|
+
constraints: string[]
|
|
205
|
+
successCriteria: string[]
|
|
206
|
+
missionStatus?: MissionStatus | null
|
|
207
|
+
missionPhase?: MissionPhase | null
|
|
208
|
+
waitState?: MissionWaitState | null
|
|
209
|
+
evidenceRefs: EvidenceRef[]
|
|
210
|
+
parentContext: string | null
|
|
211
|
+
}
|
|
@@ -35,8 +35,8 @@ export function HeartbeatSection({ appSettings, patchSettings, inputClass }: Set
|
|
|
35
35
|
setHeartbeatBulkNotice(
|
|
36
36
|
`Stopped heartbeat on ${result.updatedSessions} session(s); cancelled ${result.cancelledQueued} queued run(s), aborted ${result.abortedRunning} running run(s).`,
|
|
37
37
|
)
|
|
38
|
-
} catch (err:
|
|
39
|
-
setHeartbeatBulkNotice(err
|
|
38
|
+
} catch (err: unknown) {
|
|
39
|
+
setHeartbeatBulkNotice(err instanceof Error ? err.message : 'Failed to disable heartbeat for all agents.')
|
|
40
40
|
} finally {
|
|
41
41
|
setDisablingHeartbeats(false)
|
|
42
42
|
}
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { spawnSync } from 'child_process'
|
|
4
|
-
import { UPLOAD_DIR } from '../storage'
|
|
5
|
-
import { truncate, MAX_OUTPUT } from './context'
|
|
6
|
-
import type { Session } from '@/types'
|
|
7
|
-
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
8
|
-
import { detectDocker } from '@/lib/server/sandbox/docker-detect'
|
|
9
|
-
import {
|
|
10
|
-
ensureSessionSandbox,
|
|
11
|
-
resolveSandboxRuntimeStatus,
|
|
12
|
-
resolveSandboxWorkdir,
|
|
13
|
-
type AgentSandboxConfig,
|
|
14
|
-
} from '@/lib/server/sandbox/session-runtime'
|
|
15
|
-
import { buildDockerExecArgs } from '@/lib/server/runtime/process-manager'
|
|
16
|
-
|
|
17
|
-
export type SandboxContext = {
|
|
18
|
-
sessionId?: string
|
|
19
|
-
cwd?: string
|
|
20
|
-
agentId?: string | null
|
|
21
|
-
config?: AgentSandboxConfig | null
|
|
22
|
-
resolveCurrentSession?: () => Session | null
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const EXT_MAP: Record<string, string> = {
|
|
26
|
-
javascript: 'js',
|
|
27
|
-
typescript: 'ts',
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function sandboxUnavailableError(reason: string): string {
|
|
31
|
-
return JSON.stringify({
|
|
32
|
-
error: reason,
|
|
33
|
-
guidance: [
|
|
34
|
-
'Install Docker Desktop to keep sandbox_exec inside a container.',
|
|
35
|
-
'Use http_request for straightforward API calls.',
|
|
36
|
-
'Use extension_creator plus manage_schedules for recurring automations.',
|
|
37
|
-
],
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function quoteShell(value: string): string {
|
|
42
|
-
return `'${value.replace(/'/g, `'\\''`)}'`
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function createSandboxDir(baseCwd: string, sessionId: string): string {
|
|
46
|
-
const root = path.join(baseCwd, '.swarmclaw-sandbox')
|
|
47
|
-
fs.mkdirSync(root, { recursive: true })
|
|
48
|
-
return fs.mkdtempSync(path.join(root, `${sessionId}-`))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function collectArtifacts(params: {
|
|
52
|
-
sandboxDir: string
|
|
53
|
-
ignoredFiles: Set<string>
|
|
54
|
-
}): Array<{ name: string; url: string }> {
|
|
55
|
-
const artifacts: { name: string; url: string }[] = []
|
|
56
|
-
try {
|
|
57
|
-
const files = fs.readdirSync(params.sandboxDir)
|
|
58
|
-
for (const file of files) {
|
|
59
|
-
if (params.ignoredFiles.has(file)) continue
|
|
60
|
-
const src = path.join(params.sandboxDir, file)
|
|
61
|
-
if (!fs.statSync(src).isFile()) continue
|
|
62
|
-
fs.mkdirSync(UPLOAD_DIR, { recursive: true })
|
|
63
|
-
const destName = `sandbox-${Date.now()}-${file}`
|
|
64
|
-
const dest = path.join(UPLOAD_DIR, destName)
|
|
65
|
-
fs.copyFileSync(src, dest)
|
|
66
|
-
artifacts.push({ name: file, url: `/api/uploads/${encodeURIComponent(destName)}` })
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// ignore artifact collection failures
|
|
70
|
-
}
|
|
71
|
-
return artifacts
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function executeHostNode(params: {
|
|
75
|
-
sandboxDir: string
|
|
76
|
-
language: string
|
|
77
|
-
scriptFile: string
|
|
78
|
-
timeout: number
|
|
79
|
-
}): {
|
|
80
|
-
runtime: 'host'
|
|
81
|
-
stdout: string
|
|
82
|
-
stderr: string
|
|
83
|
-
exitCode: number
|
|
84
|
-
timedOut: boolean
|
|
85
|
-
} {
|
|
86
|
-
const tmpDir = path.join(params.sandboxDir, '.tmp')
|
|
87
|
-
fs.mkdirSync(tmpDir, { recursive: true })
|
|
88
|
-
const args = params.language === 'typescript'
|
|
89
|
-
? ['--no-warnings=ExperimentalWarning', '--experimental-strip-types', params.scriptFile]
|
|
90
|
-
: [params.scriptFile]
|
|
91
|
-
const result = spawnSync(process.execPath, args, {
|
|
92
|
-
cwd: params.sandboxDir,
|
|
93
|
-
encoding: 'utf-8',
|
|
94
|
-
timeout: params.timeout,
|
|
95
|
-
maxBuffer: MAX_OUTPUT,
|
|
96
|
-
env: {
|
|
97
|
-
...process.env,
|
|
98
|
-
HOME: params.sandboxDir,
|
|
99
|
-
TMPDIR: tmpDir,
|
|
100
|
-
WORKSPACE: params.sandboxDir,
|
|
101
|
-
SESSION_CWD: params.sandboxDir,
|
|
102
|
-
SWARMCLAW_SANDBOX_MODE: 'host',
|
|
103
|
-
},
|
|
104
|
-
})
|
|
105
|
-
return {
|
|
106
|
-
runtime: 'host',
|
|
107
|
-
stdout: truncate((result.stdout || '').toString(), MAX_OUTPUT),
|
|
108
|
-
stderr: truncate((result.stderr || '').toString(), MAX_OUTPUT),
|
|
109
|
-
exitCode: result.status ?? (result.error ? 1 : 0),
|
|
110
|
-
timedOut: !!(result.error?.message?.includes('ETIMEDOUT') || result.signal === 'SIGTERM'),
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function executeContainerNode(params: {
|
|
115
|
-
sandboxDir: string
|
|
116
|
-
language: string
|
|
117
|
-
scriptFile: string
|
|
118
|
-
timeout: number
|
|
119
|
-
context: SandboxContext
|
|
120
|
-
}): Promise<{
|
|
121
|
-
runtime: 'container'
|
|
122
|
-
stdout: string
|
|
123
|
-
stderr: string
|
|
124
|
-
exitCode: number
|
|
125
|
-
timedOut: boolean
|
|
126
|
-
}> {
|
|
127
|
-
const session = params.context.resolveCurrentSession?.() ?? null
|
|
128
|
-
const sandbox = await ensureSessionSandbox({
|
|
129
|
-
config: params.context.config,
|
|
130
|
-
session,
|
|
131
|
-
agentId: params.context.agentId ?? session?.agentId ?? null,
|
|
132
|
-
sessionId: params.context.sessionId ?? session?.id ?? null,
|
|
133
|
-
workspaceDir: params.context.cwd || process.cwd(),
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
if (!sandbox) {
|
|
137
|
-
throw new Error('Container sandbox is not active for this session.')
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const tmpDir = path.join(params.sandboxDir, '.tmp')
|
|
141
|
-
fs.mkdirSync(tmpDir, { recursive: true })
|
|
142
|
-
const resolved = resolveSandboxWorkdir({
|
|
143
|
-
workspaceDir: sandbox.workspaceDir,
|
|
144
|
-
hostWorkdir: params.sandboxDir,
|
|
145
|
-
containerWorkdir: sandbox.containerWorkdir,
|
|
146
|
-
})
|
|
147
|
-
const containerCommand = params.language === 'typescript'
|
|
148
|
-
? `node --no-warnings=ExperimentalWarning --experimental-strip-types ${quoteShell(params.scriptFile)}`
|
|
149
|
-
: `node ${quoteShell(params.scriptFile)}`
|
|
150
|
-
const result = spawnSync('docker', buildDockerExecArgs({
|
|
151
|
-
containerName: sandbox.containerName,
|
|
152
|
-
command: containerCommand,
|
|
153
|
-
workdir: resolved.containerWorkdir,
|
|
154
|
-
env: {
|
|
155
|
-
HOME: resolved.containerWorkdir,
|
|
156
|
-
TMPDIR: path.posix.join(resolved.containerWorkdir, '.tmp'),
|
|
157
|
-
WORKSPACE: sandbox.containerWorkdir,
|
|
158
|
-
SESSION_CWD: resolved.containerWorkdir,
|
|
159
|
-
SWARMCLAW_SANDBOX_MODE: 'container',
|
|
160
|
-
},
|
|
161
|
-
}), {
|
|
162
|
-
encoding: 'utf-8',
|
|
163
|
-
timeout: params.timeout,
|
|
164
|
-
maxBuffer: MAX_OUTPUT,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
runtime: 'container',
|
|
169
|
-
stdout: truncate((result.stdout || '').toString(), MAX_OUTPUT),
|
|
170
|
-
stderr: truncate((result.stderr || '').toString(), MAX_OUTPUT),
|
|
171
|
-
exitCode: result.status ?? (result.error ? 1 : 0),
|
|
172
|
-
timedOut: !!(result.error?.message?.includes('ETIMEDOUT') || result.signal === 'SIGTERM'),
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export async function executeSandboxExec(args: unknown, context: SandboxContext) {
|
|
177
|
-
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
178
|
-
const language = normalized.language as string
|
|
179
|
-
const code = normalized.code as string
|
|
180
|
-
const timeoutSec = normalized.timeoutSec as number | undefined
|
|
181
|
-
const timeout = Math.min(Math.max(timeoutSec ?? 60, 5), 300) * 1000
|
|
182
|
-
const ext = EXT_MAP[language]
|
|
183
|
-
const sessionId = context.sessionId ?? 'unknown'
|
|
184
|
-
const cwd = context.cwd || process.cwd()
|
|
185
|
-
|
|
186
|
-
if (language !== 'javascript' && language !== 'typescript') {
|
|
187
|
-
return sandboxUnavailableError('sandbox_exec currently supports only JavaScript and TypeScript via Node.js.')
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let sandboxDir: string | null = null
|
|
191
|
-
try {
|
|
192
|
-
sandboxDir = createSandboxDir(cwd, sessionId)
|
|
193
|
-
const sandboxRoot = sandboxDir
|
|
194
|
-
const scriptFile = `script.${ext}`
|
|
195
|
-
fs.writeFileSync(path.join(sandboxRoot, 'package.json'), JSON.stringify({ type: 'module' }), 'utf-8')
|
|
196
|
-
fs.writeFileSync(path.join(sandboxRoot, scriptFile), code, 'utf-8')
|
|
197
|
-
|
|
198
|
-
const warnings: string[] = []
|
|
199
|
-
const docker = detectDocker()
|
|
200
|
-
const runtimeResult = docker.available
|
|
201
|
-
? await executeContainerNode({
|
|
202
|
-
sandboxDir: sandboxRoot,
|
|
203
|
-
language,
|
|
204
|
-
scriptFile,
|
|
205
|
-
timeout,
|
|
206
|
-
context,
|
|
207
|
-
}).catch((err: unknown) => {
|
|
208
|
-
warnings.push(err instanceof Error ? err.message : 'Container sandbox unavailable; used host Node fallback.')
|
|
209
|
-
return executeHostNode({
|
|
210
|
-
sandboxDir: sandboxRoot,
|
|
211
|
-
language,
|
|
212
|
-
scriptFile,
|
|
213
|
-
timeout,
|
|
214
|
-
})
|
|
215
|
-
})
|
|
216
|
-
: (() => {
|
|
217
|
-
warnings.push('Docker is not available; used host Node fallback.')
|
|
218
|
-
return executeHostNode({
|
|
219
|
-
sandboxDir: sandboxRoot,
|
|
220
|
-
language,
|
|
221
|
-
scriptFile,
|
|
222
|
-
timeout,
|
|
223
|
-
})
|
|
224
|
-
})()
|
|
225
|
-
|
|
226
|
-
const artifacts = collectArtifacts({
|
|
227
|
-
sandboxDir: sandboxRoot,
|
|
228
|
-
ignoredFiles: new Set([scriptFile, 'package.json']),
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
return JSON.stringify({
|
|
232
|
-
runtime: runtimeResult.runtime,
|
|
233
|
-
exitCode: runtimeResult.exitCode,
|
|
234
|
-
timedOut: runtimeResult.timedOut,
|
|
235
|
-
stdout: runtimeResult.stdout,
|
|
236
|
-
stderr: runtimeResult.stderr,
|
|
237
|
-
artifacts,
|
|
238
|
-
...(warnings.length ? { warnings } : {}),
|
|
239
|
-
})
|
|
240
|
-
} catch (err: unknown) {
|
|
241
|
-
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
|
|
242
|
-
} finally {
|
|
243
|
-
if (sandboxDir) {
|
|
244
|
-
try { fs.rmSync(sandboxDir, { recursive: true, force: true }) } catch { /* ignore */ }
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export async function executeListRuntimes(context: SandboxContext) {
|
|
250
|
-
const docker = detectDocker()
|
|
251
|
-
const session = context.resolveCurrentSession?.() ?? null
|
|
252
|
-
const status = resolveSandboxRuntimeStatus({
|
|
253
|
-
config: context.config,
|
|
254
|
-
session,
|
|
255
|
-
agentId: context.agentId ?? session?.agentId ?? null,
|
|
256
|
-
sessionId: context.sessionId ?? session?.id ?? null,
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
return JSON.stringify({
|
|
260
|
-
node: {
|
|
261
|
-
available: true,
|
|
262
|
-
version: process.version,
|
|
263
|
-
supportsTypeScript: true,
|
|
264
|
-
},
|
|
265
|
-
docker,
|
|
266
|
-
sandbox: {
|
|
267
|
-
enabledByConfig: Boolean(context.config?.enabled),
|
|
268
|
-
sandboxedForSession: status.sandboxed,
|
|
269
|
-
mode: status.mode,
|
|
270
|
-
scope: status.scope,
|
|
271
|
-
scopeKey: status.scopeKey,
|
|
272
|
-
executionMode: docker.available && status.sandboxed ? 'container' : 'host',
|
|
273
|
-
browserEnabledByConfig: context.config?.browser?.enabled === true,
|
|
274
|
-
},
|
|
275
|
-
guidance: docker.available
|
|
276
|
-
? []
|
|
277
|
-
: ['Install Docker Desktop to keep shell, browser, and sandbox_exec inside containers.'],
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Execution functions (executeSandboxExec, executeListRuntimes) are kept for shell.ts to import.
|