@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,302 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
import { defaultReceiptFile } from '../shared/paths.mjs'
|
|
7
|
+
import { currentRuntimeRevision } from '../gateway/state.mjs'
|
|
8
|
+
import { loadRuntimeKeyBundle, publicKeyFingerprint } from './keys.mjs'
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
+
const __dirname = path.dirname(__filename)
|
|
12
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '../..')
|
|
13
|
+
const PACKAGE_JSON_PATH = path.join(PACKAGE_ROOT, 'package.json')
|
|
14
|
+
|
|
15
|
+
function clean(value) {
|
|
16
|
+
return `${value ?? ''}`.trim()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readJson(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
22
|
+
} catch {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function runGit(args = []) {
|
|
28
|
+
const result = spawnSync('git', args, {
|
|
29
|
+
cwd: PACKAGE_ROOT,
|
|
30
|
+
encoding: 'utf8'
|
|
31
|
+
})
|
|
32
|
+
if (result.status !== 0) {
|
|
33
|
+
return { ok: false, stdout: clean(result.stdout), stderr: clean(result.stderr) }
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, stdout: clean(result.stdout), stderr: clean(result.stderr) }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseGatewayPort(gatewayBase = '') {
|
|
39
|
+
try {
|
|
40
|
+
return new URL(gatewayBase).port || ''
|
|
41
|
+
} catch {
|
|
42
|
+
return ''
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseHumanName(agentId = '') {
|
|
47
|
+
const cleaned = clean(agentId)
|
|
48
|
+
const at = cleaned.lastIndexOf('@')
|
|
49
|
+
return at >= 0 ? cleaned.slice(at + 1) : ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function latestStatusLabel(status = '') {
|
|
53
|
+
switch (clean(status)) {
|
|
54
|
+
case 'up-to-date':
|
|
55
|
+
return 'up to date'
|
|
56
|
+
case 'ahead':
|
|
57
|
+
return 'ahead of upstream'
|
|
58
|
+
case 'behind':
|
|
59
|
+
return 'behind upstream'
|
|
60
|
+
case 'diverged':
|
|
61
|
+
return 'diverged from upstream'
|
|
62
|
+
default:
|
|
63
|
+
return 'unknown'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function latestStatus() {
|
|
68
|
+
const upstream = runGit(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
|
|
69
|
+
if (!upstream.ok || !clean(upstream.stdout)) {
|
|
70
|
+
return {
|
|
71
|
+
status: 'unknown',
|
|
72
|
+
detail: 'No upstream tracking branch was available from local git metadata.'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const counts = runGit(['rev-list', '--left-right', '--count', `HEAD...${clean(upstream.stdout)}`])
|
|
76
|
+
if (!counts.ok) {
|
|
77
|
+
return {
|
|
78
|
+
status: 'unknown',
|
|
79
|
+
detail: 'Upstream comparison was unavailable from local git metadata.'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const [aheadRaw, behindRaw] = clean(counts.stdout).split(/\s+/)
|
|
83
|
+
const ahead = Number.parseInt(aheadRaw ?? '0', 10) || 0
|
|
84
|
+
const behind = Number.parseInt(behindRaw ?? '0', 10) || 0
|
|
85
|
+
if (ahead === 0 && behind === 0) {
|
|
86
|
+
return { status: 'up-to-date', detail: `Local HEAD matches ${clean(upstream.stdout)}.` }
|
|
87
|
+
}
|
|
88
|
+
if (ahead > 0 && behind === 0) {
|
|
89
|
+
return { status: 'ahead', detail: `Local HEAD is ahead of ${clean(upstream.stdout)} by ${ahead} commit(s).` }
|
|
90
|
+
}
|
|
91
|
+
if (ahead === 0 && behind > 0) {
|
|
92
|
+
return { status: 'behind', detail: `Local HEAD is behind ${clean(upstream.stdout)} by ${behind} commit(s).` }
|
|
93
|
+
}
|
|
94
|
+
return { status: 'diverged', detail: `Local HEAD has diverged from ${clean(upstream.stdout)}.` }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function currentRuntimeMetadata() {
|
|
98
|
+
const pkg = readJson(PACKAGE_JSON_PATH) ?? {}
|
|
99
|
+
const repo = runGit(['remote', 'get-url', 'origin'])
|
|
100
|
+
const commit = runGit(['rev-parse', '--short=12', 'HEAD'])
|
|
101
|
+
return {
|
|
102
|
+
packageName: clean(pkg.name) || '@agentsquared/cli',
|
|
103
|
+
packageVersion: clean(pkg.version) || 'unknown',
|
|
104
|
+
gitCommit: commit.ok ? clean(commit.stdout) : 'unknown',
|
|
105
|
+
repoUrl: repo.ok ? clean(repo.stdout) : 'https://github.com/AgentSquaredNet/agentsquared-cli.git',
|
|
106
|
+
runtimeRevision: currentRuntimeRevision(),
|
|
107
|
+
latest: latestStatus()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function receiptFilePath(keyFile, agentId) {
|
|
112
|
+
return defaultReceiptFile(keyFile, agentId)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function loadReceiptForAgent({ keyFile = '', agentId = '' } = {}) {
|
|
116
|
+
const filePath = receiptFilePath(keyFile, agentId)
|
|
117
|
+
const receipt = readJson(filePath)
|
|
118
|
+
return {
|
|
119
|
+
filePath,
|
|
120
|
+
receipt
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function summarizeUpdateCommits(previousGitCommit = '', currentGitCommit = '') {
|
|
125
|
+
const from = clean(previousGitCommit)
|
|
126
|
+
const to = clean(currentGitCommit)
|
|
127
|
+
if (!from || !to || from === to) {
|
|
128
|
+
return []
|
|
129
|
+
}
|
|
130
|
+
const log = runGit(['log', '--oneline', '--no-merges', `${from}..${to}`])
|
|
131
|
+
if (!log.ok || !clean(log.stdout)) {
|
|
132
|
+
return []
|
|
133
|
+
}
|
|
134
|
+
return clean(log.stdout)
|
|
135
|
+
.split('\n')
|
|
136
|
+
.map((line) => clean(line.replace(/^[0-9a-f]+\s+/, '')))
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
.slice(0, 6)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function buildRuntimeUpdateSection({
|
|
142
|
+
previousState = null,
|
|
143
|
+
currentRuntime = currentRuntimeMetadata()
|
|
144
|
+
} = {}) {
|
|
145
|
+
const previous = {
|
|
146
|
+
packageVersion: clean(previousState?.runtimePackageVersion || previousState?.skillsPackageVersion) || 'unknown',
|
|
147
|
+
gitCommit: clean(previousState?.runtimeGitCommit || previousState?.skillsGitCommit) || 'unknown',
|
|
148
|
+
runtimeRevision: clean(previousState?.runtimeRevision) || 'unknown'
|
|
149
|
+
}
|
|
150
|
+
const current = {
|
|
151
|
+
packageVersion: clean(currentRuntime.packageVersion) || 'unknown',
|
|
152
|
+
gitCommit: clean(currentRuntime.gitCommit) || 'unknown',
|
|
153
|
+
runtimeRevision: clean(currentRuntime.runtimeRevision) || 'unknown'
|
|
154
|
+
}
|
|
155
|
+
const changed = previous.gitCommit !== 'unknown' && current.gitCommit !== 'unknown'
|
|
156
|
+
? previous.gitCommit !== current.gitCommit
|
|
157
|
+
: previous.runtimeRevision !== 'unknown' && current.runtimeRevision !== 'unknown' && previous.runtimeRevision !== current.runtimeRevision
|
|
158
|
+
const initialInstall = previous.gitCommit === 'unknown' && previous.runtimeRevision === 'unknown'
|
|
159
|
+
return {
|
|
160
|
+
changed,
|
|
161
|
+
initialInstall,
|
|
162
|
+
from: previous,
|
|
163
|
+
to: current,
|
|
164
|
+
capabilityNotes: summarizeUpdateCommits(previous.gitCommit, current.gitCommit)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function extractRelayHealth(gatewayHealth = null) {
|
|
169
|
+
const relayControl = gatewayHealth?.relayControl ?? null
|
|
170
|
+
if (typeof relayControl?.ok === 'boolean') {
|
|
171
|
+
return {
|
|
172
|
+
ok: relayControl.ok,
|
|
173
|
+
detail: clean(relayControl.detail)
|
|
174
|
+
|| (relayControl.ok
|
|
175
|
+
? 'Relay control-plane handshake succeeded.'
|
|
176
|
+
: 'Relay control-plane handshake failed.')
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const startupRelay = Boolean(gatewayHealth?.startupChecks?.relay?.ok)
|
|
180
|
+
const relayAddrs = Array.isArray(gatewayHealth?.relayAddrs) ? gatewayHealth.relayAddrs : []
|
|
181
|
+
return {
|
|
182
|
+
ok: startupRelay && relayAddrs.length > 0,
|
|
183
|
+
detail: startupRelay
|
|
184
|
+
? (relayAddrs.length > 0 ? 'Relay startup check passed and relay-backed addresses are present.' : 'Relay startup check passed but no relay-backed addresses were reported yet.')
|
|
185
|
+
: clean(gatewayHealth?.startupChecks?.relay?.error) || 'Relay startup check did not pass.'
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function extractHostRuntimeHealth(gatewayHealth = null, detectedHostRuntime = null) {
|
|
190
|
+
const resolved = clean(detectedHostRuntime?.resolved) || 'none'
|
|
191
|
+
const startupHost = Boolean(gatewayHealth?.startupChecks?.hostRuntime?.ok)
|
|
192
|
+
return {
|
|
193
|
+
ok: resolved === 'none' ? true : startupHost,
|
|
194
|
+
detail: resolved === 'none'
|
|
195
|
+
? 'No host runtime adapter is active.'
|
|
196
|
+
: (startupHost
|
|
197
|
+
? `${resolved} host adapter startup check passed.`
|
|
198
|
+
: clean(gatewayHealth?.startupChecks?.hostRuntime?.error) || `${resolved} host adapter startup check failed.`)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function buildStandardRuntimeReport({
|
|
203
|
+
apiBase = 'https://api.agentsquared.net',
|
|
204
|
+
agentId = '',
|
|
205
|
+
keyFile = '',
|
|
206
|
+
detectedHostRuntime = null,
|
|
207
|
+
registration = null,
|
|
208
|
+
gateway = null,
|
|
209
|
+
gatewayHealth = null,
|
|
210
|
+
previousState = null
|
|
211
|
+
} = {}) {
|
|
212
|
+
const currentRuntime = currentRuntimeMetadata()
|
|
213
|
+
let keyBundle = null
|
|
214
|
+
if (keyFile) {
|
|
215
|
+
try {
|
|
216
|
+
keyBundle = loadRuntimeKeyBundle(keyFile)
|
|
217
|
+
} catch {
|
|
218
|
+
keyBundle = null
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const receiptResult = loadReceiptForAgent({ keyFile, agentId })
|
|
222
|
+
const receipt = receiptResult.receipt ?? {}
|
|
223
|
+
const registrationFacts = registration ?? receipt ?? {}
|
|
224
|
+
const relayStatus = extractRelayHealth(gatewayHealth)
|
|
225
|
+
const hostStatus = extractHostRuntimeHealth(gatewayHealth, detectedHostRuntime)
|
|
226
|
+
const effectiveGatewayHealth = gatewayHealth ?? gateway?.health ?? null
|
|
227
|
+
return {
|
|
228
|
+
overall: {
|
|
229
|
+
humanId: registrationFacts.humanId ?? null,
|
|
230
|
+
humanName: clean(registrationFacts.humanName) || parseHumanName(registrationFacts.fullName || agentId),
|
|
231
|
+
agentId: clean(registrationFacts.fullName || agentId),
|
|
232
|
+
chainAgentId: clean(registrationFacts.chainAgentId),
|
|
233
|
+
publicKey: clean(registrationFacts.publicKey || keyBundle?.publicKey),
|
|
234
|
+
publicKeyFingerprint: keyBundle ? publicKeyFingerprint(keyBundle) : '',
|
|
235
|
+
relayApiBase: clean(apiBase) || 'https://api.agentsquared.net',
|
|
236
|
+
runtimeRepoUrl: clean(currentRuntime.repoUrl),
|
|
237
|
+
runtimePackageVersion: clean(currentRuntime.packageVersion),
|
|
238
|
+
runtimeGitCommit: clean(currentRuntime.gitCommit),
|
|
239
|
+
runtimeRevision: clean(currentRuntime.runtimeRevision),
|
|
240
|
+
latestStatus: currentRuntime.latest,
|
|
241
|
+
hostRuntime: clean(detectedHostRuntime?.resolved) || 'none'
|
|
242
|
+
},
|
|
243
|
+
runtimeUpdate: buildRuntimeUpdateSection({
|
|
244
|
+
previousState,
|
|
245
|
+
currentRuntime
|
|
246
|
+
}),
|
|
247
|
+
gatewayStatus: {
|
|
248
|
+
runtimeRevision: clean(currentRuntime.runtimeRevision),
|
|
249
|
+
runtimeGitCommit: clean(currentRuntime.gitCommit),
|
|
250
|
+
runtimePackageVersion: clean(currentRuntime.packageVersion),
|
|
251
|
+
latestStatus: currentRuntime.latest,
|
|
252
|
+
gatewayBase: clean(gateway?.gatewayBase || effectiveGatewayHealth?.gatewayBase),
|
|
253
|
+
listeningPort: parseGatewayPort(clean(gateway?.gatewayBase || effectiveGatewayHealth?.gatewayBase)),
|
|
254
|
+
peerId: clean(effectiveGatewayHealth?.peerId),
|
|
255
|
+
started: Boolean(gateway?.started ?? effectiveGatewayHealth),
|
|
256
|
+
relay: relayStatus,
|
|
257
|
+
hostRuntime: hostStatus,
|
|
258
|
+
startupChecks: effectiveGatewayHealth?.startupChecks ?? null
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function buildStandardRuntimeOwnerLines(report = {}) {
|
|
264
|
+
const overall = report.overall ?? {}
|
|
265
|
+
const runtimeUpdate = report.runtimeUpdate ?? {}
|
|
266
|
+
const gatewayStatus = report.gatewayStatus ?? {}
|
|
267
|
+
const lines = [
|
|
268
|
+
'AgentSquared standard runtime report:',
|
|
269
|
+
`Overall: human=${clean(overall.humanName) || 'unknown'}${overall.humanId != null ? ` (id ${overall.humanId})` : ''}, agent=${clean(overall.agentId) || 'unknown'}, host mode=${clean(overall.hostRuntime) || 'none'}.`,
|
|
270
|
+
`Overall: public key=${clean(overall.publicKey) || 'unknown'}, fingerprint=${clean(overall.publicKeyFingerprint) || 'unknown'}.`,
|
|
271
|
+
`Overall: official relay=${clean(overall.relayApiBase) || 'unknown'}, runtime repo=${clean(overall.runtimeRepoUrl) || 'unknown'}.`,
|
|
272
|
+
`Overall: current package=${clean(overall.runtimePackageVersion) || 'unknown'}, git=${clean(overall.runtimeGitCommit) || 'unknown'}, runtimeRevision=${clean(overall.runtimeRevision) || 'unknown'}, version status=${latestStatusLabel(overall.latestStatus?.status)}.`
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
if (runtimeUpdate.initialInstall) {
|
|
276
|
+
lines.push('Runtime update: this is the first standard runtime report for the current local AgentSquared setup, so there is no previous version to compare against.')
|
|
277
|
+
} else if (runtimeUpdate.changed) {
|
|
278
|
+
lines.push(
|
|
279
|
+
`Runtime update: upgraded from ${clean(runtimeUpdate.from?.gitCommit) || 'unknown'} (package ${clean(runtimeUpdate.from?.packageVersion) || 'unknown'}) to ${clean(runtimeUpdate.to?.gitCommit) || 'unknown'} (package ${clean(runtimeUpdate.to?.packageVersion) || 'unknown'}).`
|
|
280
|
+
)
|
|
281
|
+
if (Array.isArray(runtimeUpdate.capabilityNotes) && runtimeUpdate.capabilityNotes.length > 0) {
|
|
282
|
+
lines.push(`Runtime update: capability changes include ${runtimeUpdate.capabilityNotes.join('; ')}.`)
|
|
283
|
+
} else {
|
|
284
|
+
lines.push('Runtime update: local git metadata did not include a concise capability summary.')
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
lines.push(
|
|
288
|
+
`Runtime update: no version change was detected. Current package=${clean(runtimeUpdate.to?.packageVersion) || 'unknown'}, git=${clean(runtimeUpdate.to?.gitCommit) || 'unknown'}.`
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
lines.push(
|
|
293
|
+
`A2 gateway status: running on package=${clean(gatewayStatus.runtimePackageVersion) || 'unknown'}, git=${clean(gatewayStatus.runtimeGitCommit) || 'unknown'}, version status=${latestStatusLabel(gatewayStatus.latestStatus?.status)}.`
|
|
294
|
+
)
|
|
295
|
+
lines.push(
|
|
296
|
+
`A2 gateway status: listening port=${clean(gatewayStatus.listeningPort) || 'unknown'}, Peer ID=${clean(gatewayStatus.peerId) || 'unknown'}.`
|
|
297
|
+
)
|
|
298
|
+
lines.push(
|
|
299
|
+
`A2 gateway status: relay communication is ${gatewayStatus.relay?.ok ? 'healthy' : 'unhealthy'} (${clean(gatewayStatus.relay?.detail) || 'no detail'}), host communication is ${gatewayStatus.hostRuntime?.ok ? 'healthy' : 'unhealthy'} (${clean(gatewayStatus.hostRuntime?.detail) || 'no detail'}).`
|
|
300
|
+
)
|
|
301
|
+
return lines
|
|
302
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
function clean(value) {
|
|
2
|
+
return `${value ?? ''}`.trim()
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function stripDelimitedBlock(text = '', begin = '', end = '') {
|
|
6
|
+
const start = text.indexOf(begin)
|
|
7
|
+
const finish = text.indexOf(end)
|
|
8
|
+
if (start < 0 || finish < start) {
|
|
9
|
+
return text
|
|
10
|
+
}
|
|
11
|
+
return `${text.slice(0, start)}${text.slice(finish + end.length)}`.trim()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stripAgentSquaredTransportText(text = '') {
|
|
15
|
+
let value = clean(text)
|
|
16
|
+
if (!value) {
|
|
17
|
+
return value
|
|
18
|
+
}
|
|
19
|
+
value = stripDelimitedBlock(value, 'BEGIN_A2_OWNER_REQUEST', 'END_A2_OWNER_REQUEST')
|
|
20
|
+
const strippedLines = value
|
|
21
|
+
.split('\n')
|
|
22
|
+
.filter((line) => {
|
|
23
|
+
const normalized = clean(line)
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
if (normalized.includes('[AgentSquared]')) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
if (normalized === 'This is an AgentSquared private agent message.') {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
if (normalized === 'Please read the AgentSquared official skill before sending or replying through AgentSquared.') {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
if (/^(From|To|Sent At \(UTC\)|Owner Request|Local Skill Snapshot):/i.test(normalized)) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
if (normalized === 'Please reply to me for my owner.') {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
return true
|
|
43
|
+
})
|
|
44
|
+
value = strippedLines.join('\n').replace(/\n{3,}/g, '\n\n').trim()
|
|
45
|
+
return value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// This module is intentionally narrow.
|
|
49
|
+
// The main safety decision path lives in the host runtime triage prompt.
|
|
50
|
+
// Local code here is only a lightweight outbound redaction layer.
|
|
51
|
+
const SECRET_PATTERNS = [
|
|
52
|
+
/-----begin [a-z0-9 _-]*private key-----/i,
|
|
53
|
+
/\bsk-[a-z0-9]{16,}\b/i,
|
|
54
|
+
/\bghp_[a-z0-9]{20,}\b/i,
|
|
55
|
+
/\bxox[baprs]-[a-z0-9-]{10,}\b/i,
|
|
56
|
+
/\b(?:api|access|refresh|bearer|auth(?:orization)?) token\b/i,
|
|
57
|
+
/\bseed phrase\b/i,
|
|
58
|
+
/\bprivate key\b/i,
|
|
59
|
+
/\bm(nemonic)?\b.{0,20}\bphrase\b/i
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
export function scrubOutboundText(text = '') {
|
|
63
|
+
let value = clean(text)
|
|
64
|
+
if (!value) {
|
|
65
|
+
return value
|
|
66
|
+
}
|
|
67
|
+
value = stripAgentSquaredTransportText(value)
|
|
68
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
69
|
+
value = value.replace(pattern, '[REDACTED]')
|
|
70
|
+
}
|
|
71
|
+
return value
|
|
72
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
function clean(value) {
|
|
4
|
+
return `${value ?? ''}`.trim()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function safeAgentId(value) {
|
|
8
|
+
return clean(value).replace(/[^a-zA-Z0-9_.-]+/g, '_')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveUserPath(inputPath) {
|
|
12
|
+
return path.resolve(`${inputPath ?? ''}`.replace(/^~(?=$|\/|\\)/, process.env.HOME || '~'))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveHostWorkspaceDir(detectedHostRuntime = null) {
|
|
16
|
+
return clean(
|
|
17
|
+
detectedHostRuntime?.workspaceDir
|
|
18
|
+
?? detectedHostRuntime?.overviewStatus?.agents?.agents?.find?.((item) => clean(item?.workspaceDir))?.workspaceDir
|
|
19
|
+
?? detectedHostRuntime?.overviewStatus?.agents?.agents?.[0]?.workspaceDir
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveAgentSquaredDir(args = {}, detectedHostRuntime = null) {
|
|
24
|
+
const explicit = clean(args?.['agentsquared-dir'])
|
|
25
|
+
if (explicit) {
|
|
26
|
+
return resolveUserPath(explicit)
|
|
27
|
+
}
|
|
28
|
+
const workspaceDir = resolveHostWorkspaceDir(detectedHostRuntime)
|
|
29
|
+
if (workspaceDir) {
|
|
30
|
+
return path.join(resolveUserPath(workspaceDir), 'AgentSquared')
|
|
31
|
+
}
|
|
32
|
+
if (process.env.HOME) {
|
|
33
|
+
return path.join(process.env.HOME, '.openclaw', 'workspace', 'AgentSquared')
|
|
34
|
+
}
|
|
35
|
+
return path.join(process.cwd(), 'AgentSquared')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveAgentScopeDir(agentNameOrId, args = {}, detectedHostRuntime = null) {
|
|
39
|
+
const safeId = safeAgentId(agentNameOrId)
|
|
40
|
+
if (!safeId) {
|
|
41
|
+
throw new Error('agentNameOrId is required to derive the AgentSquared agent scope directory')
|
|
42
|
+
}
|
|
43
|
+
return path.join(resolveAgentSquaredDir(args, detectedHostRuntime), safeId)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function inferAgentSquaredScopeFromArtifact(filePath) {
|
|
47
|
+
const resolved = resolveUserPath(filePath)
|
|
48
|
+
const name = path.basename(resolved)
|
|
49
|
+
const parent = path.dirname(resolved)
|
|
50
|
+
const grandparent = path.dirname(parent)
|
|
51
|
+
|
|
52
|
+
if ((name === 'runtime-key.json' || name === 'registration-receipt.json' || name === 'onboarding-summary.json') && path.basename(parent) === 'identity') {
|
|
53
|
+
return grandparent
|
|
54
|
+
}
|
|
55
|
+
if ((name === 'gateway.json' || name === 'gateway-peer.key' || name === 'gateway.log' || name === 'openclaw-device.json' || name === 'openclaw-device-auth.json') && path.basename(parent) === 'runtime') {
|
|
56
|
+
return grandparent
|
|
57
|
+
}
|
|
58
|
+
if ((name === 'index.json' || name === 'inbox.md') && path.basename(parent) === 'inbox') {
|
|
59
|
+
return grandparent
|
|
60
|
+
}
|
|
61
|
+
if (path.basename(parent) === 'entries' && path.basename(grandparent) === 'inbox') {
|
|
62
|
+
return path.dirname(grandparent)
|
|
63
|
+
}
|
|
64
|
+
if (name === 'AGENT_RELATIONSHIPS.md') {
|
|
65
|
+
return parent
|
|
66
|
+
}
|
|
67
|
+
return ''
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveAgentScopeDirFromKeyFile(keyFile) {
|
|
71
|
+
const scopeDir = inferAgentSquaredScopeFromArtifact(keyFile)
|
|
72
|
+
if (!scopeDir || path.basename(resolveUserPath(keyFile)) !== 'runtime-key.json') {
|
|
73
|
+
throw new Error(`keyFile is not inside the AgentSquared multi-agent layout: ${resolveUserPath(keyFile)}`)
|
|
74
|
+
}
|
|
75
|
+
return scopeDir
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function identityDirForAgentScope(scopeDir) {
|
|
79
|
+
return path.join(resolveUserPath(scopeDir), 'identity')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function runtimeDirForAgentScope(scopeDir) {
|
|
83
|
+
return path.join(resolveUserPath(scopeDir), 'runtime')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function inboxDirForAgentScope(scopeDir) {
|
|
87
|
+
return path.join(resolveUserPath(scopeDir), 'inbox')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function defaultRuntimeKeyFile(agentName, args = {}, detectedHostRuntime = null) {
|
|
91
|
+
return path.join(identityDirForAgentScope(resolveAgentScopeDir(agentName, args, detectedHostRuntime)), 'runtime-key.json')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function scopeDirForKeyAndAgent(keyFile, agentId = '') {
|
|
95
|
+
try {
|
|
96
|
+
return resolveAgentScopeDirFromKeyFile(keyFile)
|
|
97
|
+
} catch {
|
|
98
|
+
const safeId = safeAgentId(agentId)
|
|
99
|
+
if (!safeId) {
|
|
100
|
+
throw new Error(`Cannot derive AgentSquared scope directory from keyFile without agentId: ${resolveUserPath(keyFile)}`)
|
|
101
|
+
}
|
|
102
|
+
const keyDir = path.dirname(resolveUserPath(keyFile))
|
|
103
|
+
const identityDir = path.basename(keyDir) === 'identity' ? keyDir : path.join(keyDir, 'identity')
|
|
104
|
+
return path.dirname(identityDir)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function defaultGatewayStateFile(keyFile, agentId) {
|
|
109
|
+
if (!keyFile || !agentId) {
|
|
110
|
+
throw new Error('keyFile and agentId are required to derive the gateway state file')
|
|
111
|
+
}
|
|
112
|
+
return path.join(runtimeDirForAgentScope(scopeDirForKeyAndAgent(keyFile, agentId)), 'gateway.json')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function defaultPeerKeyFile(keyFile, agentId) {
|
|
116
|
+
if (!keyFile || !agentId) {
|
|
117
|
+
throw new Error('keyFile and agentId are required to derive the peer key file')
|
|
118
|
+
}
|
|
119
|
+
return path.join(runtimeDirForAgentScope(scopeDirForKeyAndAgent(keyFile, agentId)), 'gateway-peer.key')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function defaultGatewayLogFile(keyFile, agentId) {
|
|
123
|
+
if (!keyFile || !agentId) {
|
|
124
|
+
throw new Error('keyFile and agentId are required to derive the gateway log file')
|
|
125
|
+
}
|
|
126
|
+
return path.join(runtimeDirForAgentScope(scopeDirForKeyAndAgent(keyFile, agentId)), 'gateway.log')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function defaultOpenClawStateDir(keyFile, agentId) {
|
|
130
|
+
if (!keyFile || !agentId) {
|
|
131
|
+
throw new Error('keyFile and agentId are required to derive the OpenClaw state directory')
|
|
132
|
+
}
|
|
133
|
+
return runtimeDirForAgentScope(scopeDirForKeyAndAgent(keyFile, agentId))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function defaultInboxDir(keyFile, agentId) {
|
|
137
|
+
if (!keyFile || !agentId) {
|
|
138
|
+
throw new Error('keyFile and agentId are required to derive the inbox directory')
|
|
139
|
+
}
|
|
140
|
+
return inboxDirForAgentScope(scopeDirForKeyAndAgent(keyFile, agentId))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function defaultReceiptFile(keyFile, fullName = '') {
|
|
144
|
+
if (!keyFile) {
|
|
145
|
+
throw new Error('keyFile is required to derive the receipt file')
|
|
146
|
+
}
|
|
147
|
+
return path.join(identityDirForAgentScope(scopeDirForKeyAndAgent(keyFile, fullName)), 'registration-receipt.json')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function defaultOnboardingSummaryFile(keyFile, fullName = '') {
|
|
151
|
+
if (!keyFile) {
|
|
152
|
+
throw new Error('keyFile is required to derive the onboarding summary file')
|
|
153
|
+
}
|
|
154
|
+
return path.join(identityDirForAgentScope(scopeDirForKeyAndAgent(keyFile, fullName)), 'onboarding-summary.json')
|
|
155
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
export function parseArgs(argv) {
|
|
4
|
+
const out = { _: [] }
|
|
5
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
6
|
+
const value = argv[index]
|
|
7
|
+
if (value.startsWith('--')) {
|
|
8
|
+
const key = value.slice(2)
|
|
9
|
+
const next = argv[index + 1]
|
|
10
|
+
if (next && !next.startsWith('--')) {
|
|
11
|
+
out[key] = next
|
|
12
|
+
index += 1
|
|
13
|
+
} else {
|
|
14
|
+
out[key] = 'true'
|
|
15
|
+
}
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
out._.push(value)
|
|
19
|
+
}
|
|
20
|
+
return out
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseList(value, fallback = []) {
|
|
24
|
+
const raw = (value ?? '').trim()
|
|
25
|
+
if (!raw) return [...fallback]
|
|
26
|
+
return raw.split(',').map((item) => item.trim()).filter(Boolean)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function randomRequestId(prefix = 'req') {
|
|
30
|
+
return `${prefix}_${crypto.randomBytes(8).toString('hex')}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function utcNow() {
|
|
34
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function requireArg(value, message) {
|
|
38
|
+
const trimmed = (value ?? '').trim()
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
throw new Error(message)
|
|
41
|
+
}
|
|
42
|
+
return trimmed
|
|
43
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import { promisify } from 'node:util'
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile)
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_USER_AGENT = 'curl/8.1.2'
|
|
7
|
+
const STATUS_MARKER = '__AGENTSQUARED_HTTP_STATUS__:'
|
|
8
|
+
|
|
9
|
+
function parseBody(text) {
|
|
10
|
+
const trimmed = text.trim()
|
|
11
|
+
return trimmed ? JSON.parse(trimmed) : {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildStatusError(status, text, statusText = '') {
|
|
15
|
+
let data = {}
|
|
16
|
+
try {
|
|
17
|
+
data = parseBody(text)
|
|
18
|
+
} catch {
|
|
19
|
+
data = {}
|
|
20
|
+
}
|
|
21
|
+
const detail = data?.error?.message ?? (text.trim() || statusText || 'Request failed')
|
|
22
|
+
return new Error(`${status} ${detail}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isStatusError(error) {
|
|
26
|
+
return typeof error?.message === 'string' && /^[1-5][0-9][0-9]\s/.test(error.message)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function fetchJson(url, { method = 'GET', headers = {}, body } = {}) {
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
method,
|
|
32
|
+
headers,
|
|
33
|
+
body
|
|
34
|
+
})
|
|
35
|
+
const text = await response.text()
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw buildStatusError(response.status, text, response.statusText)
|
|
38
|
+
}
|
|
39
|
+
return parseBody(text)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function curlJson(url, { method = 'GET', headers = {}, body } = {}) {
|
|
43
|
+
const args = [
|
|
44
|
+
'--silent',
|
|
45
|
+
'--show-error',
|
|
46
|
+
'--location',
|
|
47
|
+
'--connect-timeout', '10',
|
|
48
|
+
'--max-time', '60',
|
|
49
|
+
'--request', method,
|
|
50
|
+
]
|
|
51
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
52
|
+
args.push('--header', `${name}: ${value}`)
|
|
53
|
+
}
|
|
54
|
+
if (body !== undefined) {
|
|
55
|
+
args.push('--data-binary', body)
|
|
56
|
+
}
|
|
57
|
+
args.push('--write-out', `\n${STATUS_MARKER}%{http_code}`, url)
|
|
58
|
+
const { stdout } = await execFileAsync('curl', args, { maxBuffer: 1024 * 1024 * 4 })
|
|
59
|
+
const markerIndex = stdout.lastIndexOf(`\n${STATUS_MARKER}`)
|
|
60
|
+
if (markerIndex < 0) {
|
|
61
|
+
throw new Error('curl response did not include an HTTP status marker')
|
|
62
|
+
}
|
|
63
|
+
const text = stdout.slice(0, markerIndex)
|
|
64
|
+
const status = Number.parseInt(stdout.slice(markerIndex + 1 + STATUS_MARKER.length).trim(), 10)
|
|
65
|
+
if (!Number.isInteger(status)) {
|
|
66
|
+
throw new Error('curl response did not include a valid HTTP status code')
|
|
67
|
+
}
|
|
68
|
+
if (status < 200 || status >= 300) {
|
|
69
|
+
throw buildStatusError(status, text)
|
|
70
|
+
}
|
|
71
|
+
return parseBody(text)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function requestJson(url, { method = 'GET', headers = {}, payload } = {}) {
|
|
75
|
+
const normalizedHeaders = {
|
|
76
|
+
Accept: 'application/json',
|
|
77
|
+
'User-Agent': DEFAULT_USER_AGENT,
|
|
78
|
+
...headers
|
|
79
|
+
}
|
|
80
|
+
let body
|
|
81
|
+
if (payload !== undefined) {
|
|
82
|
+
body = JSON.stringify(payload)
|
|
83
|
+
if (!normalizedHeaders['Content-Type']) {
|
|
84
|
+
normalizedHeaders['Content-Type'] = 'application/json'
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
return await fetchJson(url, { method, headers: normalizedHeaders, body })
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (isStatusError(error)) {
|
|
92
|
+
throw error
|
|
93
|
+
}
|
|
94
|
+
return curlJson(url, { method, headers: normalizedHeaders, body })
|
|
95
|
+
}
|
|
96
|
+
}
|