@agentsquared/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +420 -0
- package/a2_cli.mjs +1576 -0
- package/adapters/index.mjs +79 -0
- package/adapters/openclaw/adapter.mjs +1020 -0
- package/adapters/openclaw/cli.mjs +89 -0
- package/adapters/openclaw/detect.mjs +259 -0
- package/adapters/openclaw/helpers.mjs +827 -0
- package/adapters/openclaw/ws_client.mjs +740 -0
- package/bin/a2-cli.js +8 -0
- package/lib/conversation/policy.mjs +122 -0
- package/lib/conversation/store.mjs +223 -0
- package/lib/conversation/templates.mjs +419 -0
- package/lib/gateway/api.mjs +28 -0
- package/lib/gateway/inbox.mjs +344 -0
- package/lib/gateway/lifecycle.mjs +602 -0
- package/lib/gateway/runtime_state.mjs +388 -0
- package/lib/gateway/server.mjs +883 -0
- package/lib/gateway/state.mjs +175 -0
- package/lib/routing/agent_router.mjs +511 -0
- package/lib/runtime/executor.mjs +380 -0
- package/lib/runtime/keys.mjs +85 -0
- package/lib/runtime/report.mjs +302 -0
- package/lib/runtime/safety.mjs +72 -0
- package/lib/shared/paths.mjs +155 -0
- package/lib/shared/primitives.mjs +43 -0
- package/lib/transport/http_json.mjs +96 -0
- package/lib/transport/libp2p.mjs +397 -0
- package/lib/transport/peer_session.mjs +857 -0
- package/lib/transport/relay_http.mjs +110 -0
- package/package.json +53 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
function clean(value) {
|
|
4
|
+
return `${value ?? ''}`.trim()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function parseOpenClawJson(text) {
|
|
8
|
+
const trimmed = clean(text)
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(trimmed)
|
|
14
|
+
} catch {
|
|
15
|
+
const lines = trimmed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
16
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
17
|
+
const candidate = lines.slice(index).join('\n')
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(candidate)
|
|
20
|
+
} catch {
|
|
21
|
+
// keep scanning upward for the first trailing valid JSON payload
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function openClawCliEnv(baseEnv = process.env) {
|
|
29
|
+
return {
|
|
30
|
+
...baseEnv,
|
|
31
|
+
OPENCLAW_LOG_LEVEL: 'error'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function runOpenClawCli(command, args, {
|
|
36
|
+
cwd = '',
|
|
37
|
+
timeoutMs = 10000,
|
|
38
|
+
env = process.env
|
|
39
|
+
} = {}) {
|
|
40
|
+
const normalizedCommand = clean(command) || 'openclaw'
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const child = spawn(normalizedCommand, args, {
|
|
43
|
+
cwd: clean(cwd) || undefined,
|
|
44
|
+
env: openClawCliEnv(env),
|
|
45
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
46
|
+
})
|
|
47
|
+
let stdout = ''
|
|
48
|
+
let stderr = ''
|
|
49
|
+
let settled = false
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
if (settled) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
settled = true
|
|
55
|
+
child.kill('SIGTERM')
|
|
56
|
+
reject(new Error(`${normalizedCommand} ${args.join(' ')} timed out after ${timeoutMs}ms`))
|
|
57
|
+
}, Math.max(500, timeoutMs))
|
|
58
|
+
|
|
59
|
+
child.stdout.on('data', (chunk) => {
|
|
60
|
+
stdout += chunk.toString()
|
|
61
|
+
})
|
|
62
|
+
child.stderr.on('data', (chunk) => {
|
|
63
|
+
stderr += chunk.toString()
|
|
64
|
+
})
|
|
65
|
+
child.on('error', (error) => {
|
|
66
|
+
if (settled) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
settled = true
|
|
70
|
+
clearTimeout(timer)
|
|
71
|
+
reject(error)
|
|
72
|
+
})
|
|
73
|
+
child.on('close', (code) => {
|
|
74
|
+
if (settled) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
settled = true
|
|
78
|
+
clearTimeout(timer)
|
|
79
|
+
if (code !== 0) {
|
|
80
|
+
reject(new Error(clean(stderr) || `${normalizedCommand} ${args.join(' ')} exited with status ${code}`))
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
resolve({
|
|
84
|
+
stdout: stdout.trim(),
|
|
85
|
+
stderr: stderr.trim()
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { resolveOpenClawGatewayBootstrap, withOpenClawGatewayClient } from './ws_client.mjs'
|
|
6
|
+
|
|
7
|
+
function clean(value) {
|
|
8
|
+
return `${value ?? ''}`.trim()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readJson(filePath = '') {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
14
|
+
} catch {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function asArray(value) {
|
|
20
|
+
return Array.isArray(value) ? value : []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeAgentEntries(value) {
|
|
24
|
+
return asArray(value)
|
|
25
|
+
.filter((item) => item && typeof item === 'object')
|
|
26
|
+
.map((item) => ({
|
|
27
|
+
...item,
|
|
28
|
+
resolvedId: clean(item?.id || item?.agentId || item?.name),
|
|
29
|
+
resolvedWorkspaceDir: clean(item?.workspaceDir || item?.workspace || item?.agentDir),
|
|
30
|
+
isDefault: Boolean(item?.isDefault || item?.default)
|
|
31
|
+
}))
|
|
32
|
+
.filter((item) => item.resolvedId || item.resolvedWorkspaceDir)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function defaultOpenClawConfigPath() {
|
|
36
|
+
return path.join(os.homedir(), '.openclaw', 'openclaw.json')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function summarizeConfig(configPath = '') {
|
|
40
|
+
const resolvedPath = clean(configPath) || defaultOpenClawConfigPath()
|
|
41
|
+
const config = readJson(resolvedPath)
|
|
42
|
+
if (!config || typeof config !== 'object') {
|
|
43
|
+
return {
|
|
44
|
+
exists: false,
|
|
45
|
+
path: resolvedPath,
|
|
46
|
+
defaultAgentId: '',
|
|
47
|
+
workspaceDir: '',
|
|
48
|
+
agents: []
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const agents = normalizeAgentEntries(config?.agents?.list)
|
|
52
|
+
const defaultAgent = agents.find((entry) => entry.isDefault) ?? agents[0] ?? null
|
|
53
|
+
return {
|
|
54
|
+
exists: true,
|
|
55
|
+
path: resolvedPath,
|
|
56
|
+
defaultAgentId: clean(defaultAgent?.resolvedId) || 'main',
|
|
57
|
+
workspaceDir: clean(defaultAgent?.resolvedWorkspaceDir || config?.agents?.defaults?.workspace),
|
|
58
|
+
agents
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function extractOpenClawAgentInfo(payload = null) {
|
|
63
|
+
const root = payload && typeof payload === 'object' ? payload : {}
|
|
64
|
+
const container = root?.agents && typeof root.agents === 'object' ? root.agents : root
|
|
65
|
+
const nestedAgents = normalizeAgentEntries(container?.agents)
|
|
66
|
+
const directAgents = normalizeAgentEntries(container)
|
|
67
|
+
const agents = nestedAgents.length > 0 ? nestedAgents : directAgents
|
|
68
|
+
const defaultId = clean(
|
|
69
|
+
container?.defaultId
|
|
70
|
+
|| container?.defaultAgentId
|
|
71
|
+
|| root?.defaultId
|
|
72
|
+
|| root?.defaultAgentId
|
|
73
|
+
)
|
|
74
|
+
const defaultAgent = agents.find((entry) => entry.resolvedId === defaultId)
|
|
75
|
+
?? agents.find((entry) => entry.isDefault)
|
|
76
|
+
?? agents[0]
|
|
77
|
+
?? null
|
|
78
|
+
return {
|
|
79
|
+
defaultAgentId: defaultId || clean(defaultAgent?.resolvedId),
|
|
80
|
+
workspaceDir: clean(defaultAgent?.resolvedWorkspaceDir),
|
|
81
|
+
agents
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function resolveOpenClawAgentSelection(detectedHostRuntime = null) {
|
|
86
|
+
const agentsList = extractOpenClawAgentInfo(detectedHostRuntime?.agentsList)
|
|
87
|
+
const overview = extractOpenClawAgentInfo(detectedHostRuntime?.overviewStatus)
|
|
88
|
+
const gatewayHealth = extractOpenClawAgentInfo(detectedHostRuntime?.gatewayHealth)
|
|
89
|
+
const configSummary = detectedHostRuntime?.configSummary && typeof detectedHostRuntime.configSummary === 'object'
|
|
90
|
+
? detectedHostRuntime.configSummary
|
|
91
|
+
: summarizeConfig(clean(detectedHostRuntime?.configPath))
|
|
92
|
+
const defaultAgentId = clean(
|
|
93
|
+
agentsList.defaultAgentId
|
|
94
|
+
|| overview.defaultAgentId
|
|
95
|
+
|| gatewayHealth.defaultAgentId
|
|
96
|
+
|| configSummary.defaultAgentId
|
|
97
|
+
)
|
|
98
|
+
const workspaceDir = clean(
|
|
99
|
+
agentsList.workspaceDir
|
|
100
|
+
|| overview.workspaceDir
|
|
101
|
+
|| gatewayHealth.workspaceDir
|
|
102
|
+
|| configSummary.workspaceDir
|
|
103
|
+
)
|
|
104
|
+
return {
|
|
105
|
+
defaultAgentId,
|
|
106
|
+
workspaceDir,
|
|
107
|
+
configSummary
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function requestProbe(client, method, params = {}, timeoutMs = 10000) {
|
|
112
|
+
try {
|
|
113
|
+
const payload = await client.request(method, params, timeoutMs)
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
reason: 'ok',
|
|
117
|
+
payload
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const message = clean(error?.message)
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
reason: message.includes('timed out after') ? 'timeout' : (message || 'request-error'),
|
|
124
|
+
error: message
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function probeOpenClawGatewayWs(options = {}) {
|
|
130
|
+
try {
|
|
131
|
+
return await withOpenClawGatewayClient({
|
|
132
|
+
...options,
|
|
133
|
+
pairingStrategy: 'none',
|
|
134
|
+
connectTimeoutMs: 10000,
|
|
135
|
+
requestTimeoutMs: 10000
|
|
136
|
+
}, async (client, bootstrap) => {
|
|
137
|
+
const [health, agentsList, status] = await Promise.all([
|
|
138
|
+
requestProbe(client, 'health'),
|
|
139
|
+
requestProbe(client, 'agents.list'),
|
|
140
|
+
requestProbe(client, 'status')
|
|
141
|
+
])
|
|
142
|
+
return {
|
|
143
|
+
ok: health.ok || agentsList.ok || status.ok,
|
|
144
|
+
bootstrap,
|
|
145
|
+
health,
|
|
146
|
+
agentsList,
|
|
147
|
+
status
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
error: clean(error?.message) || 'gateway-connect-error'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function detectOpenClawHostEnvironment({
|
|
159
|
+
configPath = '',
|
|
160
|
+
gatewayUrl = '',
|
|
161
|
+
gatewayToken = '',
|
|
162
|
+
gatewayPassword = ''
|
|
163
|
+
} = {}) {
|
|
164
|
+
const bootstrap = await resolveOpenClawGatewayBootstrap({
|
|
165
|
+
configPath,
|
|
166
|
+
gatewayUrl,
|
|
167
|
+
gatewayToken,
|
|
168
|
+
gatewayPassword
|
|
169
|
+
})
|
|
170
|
+
const resolvedConfigPath = clean(bootstrap.configPath) || defaultOpenClawConfigPath()
|
|
171
|
+
const configSummary = summarizeConfig(resolvedConfigPath)
|
|
172
|
+
const wsProbe = await probeOpenClawGatewayWs({
|
|
173
|
+
gatewayUrl: clean(bootstrap.gatewayUrl),
|
|
174
|
+
gatewayToken: clean(bootstrap.gatewayToken),
|
|
175
|
+
gatewayPassword: clean(bootstrap.gatewayPassword)
|
|
176
|
+
})
|
|
177
|
+
const agentsListPayload = wsProbe?.agentsList?.ok ? wsProbe.agentsList.payload : null
|
|
178
|
+
const statusPayload = wsProbe?.status?.ok ? wsProbe.status.payload : null
|
|
179
|
+
const healthPayload = wsProbe?.health?.ok ? wsProbe.health.payload : null
|
|
180
|
+
const selection = resolveOpenClawAgentSelection({
|
|
181
|
+
agentsList: agentsListPayload,
|
|
182
|
+
overviewStatus: statusPayload,
|
|
183
|
+
gatewayHealth: healthPayload,
|
|
184
|
+
configSummary,
|
|
185
|
+
configPath: resolvedConfigPath
|
|
186
|
+
})
|
|
187
|
+
const workspaceDir = clean(selection.workspaceDir)
|
|
188
|
+
if (wsProbe?.agentsList?.ok && agentsListPayload) {
|
|
189
|
+
return {
|
|
190
|
+
id: 'openclaw',
|
|
191
|
+
detected: true,
|
|
192
|
+
confidence: 'high',
|
|
193
|
+
reason: 'openclaw-ws-agents-list',
|
|
194
|
+
agentsList: agentsListPayload,
|
|
195
|
+
overviewStatus: statusPayload,
|
|
196
|
+
gatewayHealth: healthPayload,
|
|
197
|
+
configSummary,
|
|
198
|
+
configPath: resolvedConfigPath,
|
|
199
|
+
gatewayBootstrap: wsProbe.bootstrap || bootstrap,
|
|
200
|
+
workspaceDir,
|
|
201
|
+
rpcHealthy: true
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (wsProbe?.status?.ok && statusPayload) {
|
|
206
|
+
return {
|
|
207
|
+
id: 'openclaw',
|
|
208
|
+
detected: true,
|
|
209
|
+
confidence: 'medium',
|
|
210
|
+
reason: 'openclaw-ws-status',
|
|
211
|
+
overviewStatus: statusPayload,
|
|
212
|
+
gatewayHealth: healthPayload,
|
|
213
|
+
configSummary,
|
|
214
|
+
configPath: resolvedConfigPath,
|
|
215
|
+
gatewayBootstrap: wsProbe.bootstrap || bootstrap,
|
|
216
|
+
workspaceDir
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (wsProbe?.health?.ok && healthPayload) {
|
|
221
|
+
return {
|
|
222
|
+
id: 'openclaw',
|
|
223
|
+
detected: true,
|
|
224
|
+
confidence: 'low',
|
|
225
|
+
reason: 'openclaw-ws-health',
|
|
226
|
+
gatewayHealth: healthPayload,
|
|
227
|
+
overviewStatus: statusPayload,
|
|
228
|
+
configSummary,
|
|
229
|
+
configPath: resolvedConfigPath,
|
|
230
|
+
gatewayBootstrap: wsProbe.bootstrap || bootstrap,
|
|
231
|
+
workspaceDir
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (configSummary.exists) {
|
|
236
|
+
return {
|
|
237
|
+
id: 'openclaw',
|
|
238
|
+
detected: true,
|
|
239
|
+
confidence: 'low',
|
|
240
|
+
reason: 'openclaw-config-present',
|
|
241
|
+
overviewStatus: statusPayload,
|
|
242
|
+
gatewayHealth: healthPayload,
|
|
243
|
+
agentsList: agentsListPayload,
|
|
244
|
+
configSummary,
|
|
245
|
+
configPath: resolvedConfigPath,
|
|
246
|
+
gatewayBootstrap: bootstrap,
|
|
247
|
+
gatewayProbeError: clean(wsProbe?.error || wsProbe?.agentsList?.error || wsProbe?.status?.error || wsProbe?.health?.error),
|
|
248
|
+
workspaceDir
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
id: 'none',
|
|
254
|
+
detected: false,
|
|
255
|
+
confidence: 'low',
|
|
256
|
+
reason: 'no-supported-host-runtime-detected',
|
|
257
|
+
suggested: 'openclaw'
|
|
258
|
+
}
|
|
259
|
+
}
|