@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,344 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
function clean(value) {
|
|
5
|
+
return `${value ?? ''}`.trim()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function ensureDir(dir) {
|
|
9
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function safeSegment(value, fallback = 'unknown') {
|
|
13
|
+
const cleaned = clean(value).replace(/[^a-zA-Z0-9_.-]+/g, '_')
|
|
14
|
+
return cleaned || fallback
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function nowISO() {
|
|
18
|
+
return new Date().toISOString()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function clone(value) {
|
|
22
|
+
return JSON.parse(JSON.stringify(value))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function jsonRead(filePath, fallback) {
|
|
26
|
+
try {
|
|
27
|
+
if (!fs.existsSync(filePath)) {
|
|
28
|
+
return clone(fallback)
|
|
29
|
+
}
|
|
30
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
31
|
+
} catch {
|
|
32
|
+
return clone(fallback)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function jsonWrite(filePath, payload) {
|
|
37
|
+
ensureDir(path.dirname(filePath))
|
|
38
|
+
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractInboundText(item = null) {
|
|
42
|
+
const parts = item?.request?.params?.message?.parts ?? []
|
|
43
|
+
return parts
|
|
44
|
+
.filter((part) => clean(part?.kind) === 'text')
|
|
45
|
+
.map((part) => clean(part?.text))
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join('\n')
|
|
48
|
+
.trim()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractReplyText(peerResponse = null) {
|
|
52
|
+
const parts = peerResponse?.message?.parts ?? []
|
|
53
|
+
return parts
|
|
54
|
+
.filter((part) => clean(part?.kind) === 'text')
|
|
55
|
+
.map((part) => clean(part?.text))
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join('\n')
|
|
58
|
+
.trim()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function extractConversationKey(item = null, peerResponse = null, ownerReport = null) {
|
|
62
|
+
return clean(
|
|
63
|
+
peerResponse?.metadata?.conversationKey
|
|
64
|
+
?? item?.request?.params?.metadata?.conversationKey
|
|
65
|
+
?? ownerReport?.conversationKey
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function excerpt(text, maxLength = 180) {
|
|
70
|
+
const compact = clean(text).replace(/\s+/g, ' ').trim()
|
|
71
|
+
if (!compact) {
|
|
72
|
+
return ''
|
|
73
|
+
}
|
|
74
|
+
return compact.length > maxLength ? `${compact.slice(0, maxLength - 3)}...` : compact
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function requestIdFromEntry(entry = null) {
|
|
78
|
+
return clean(entry?.request?.id)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isFinalOwnerReport(entry = null) {
|
|
82
|
+
return Boolean(entry?.ownerReport?.finalize)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createInboxStore({
|
|
86
|
+
inboxDir
|
|
87
|
+
} = {}) {
|
|
88
|
+
const resolvedInboxDir = path.resolve(clean(inboxDir) || '.')
|
|
89
|
+
const entriesDir = path.join(resolvedInboxDir, 'entries')
|
|
90
|
+
const indexFile = path.join(resolvedInboxDir, 'index.json')
|
|
91
|
+
const inboxMarkdownFile = path.join(resolvedInboxDir, 'inbox.md')
|
|
92
|
+
|
|
93
|
+
function readIndex() {
|
|
94
|
+
return jsonRead(indexFile, {
|
|
95
|
+
updatedAt: '',
|
|
96
|
+
totalCount: 0,
|
|
97
|
+
lastEntryAt: '',
|
|
98
|
+
lastEntryId: '',
|
|
99
|
+
ownerPushAttemptedCount: 0,
|
|
100
|
+
ownerPushDeliveredCount: 0,
|
|
101
|
+
ownerPushFailedCount: 0,
|
|
102
|
+
recent: []
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function summarizeEntry(entry) {
|
|
107
|
+
return {
|
|
108
|
+
id: entry.id,
|
|
109
|
+
createdAt: entry.createdAt,
|
|
110
|
+
updatedAt: entry.updatedAt,
|
|
111
|
+
remoteAgentId: entry.remoteAgentId,
|
|
112
|
+
conversationKey: entry.conversationKey,
|
|
113
|
+
peerSessionId: entry.peerSessionId,
|
|
114
|
+
selectedSkill: entry.selectedSkill,
|
|
115
|
+
summary: entry.summary,
|
|
116
|
+
messageExcerpt: entry.messageExcerpt,
|
|
117
|
+
replyExcerpt: entry.replyExcerpt,
|
|
118
|
+
ownerDelivery: entry.ownerDelivery ?? null,
|
|
119
|
+
file: entry.file
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderMarkdown(index) {
|
|
124
|
+
const lines = [
|
|
125
|
+
'# Inbox',
|
|
126
|
+
'',
|
|
127
|
+
`Updated: ${clean(index.updatedAt) || 'unknown'}`,
|
|
128
|
+
`Total: ${index.totalCount ?? 0}`,
|
|
129
|
+
`Last Entry: ${clean(index.lastEntryAt) || 'none'}`,
|
|
130
|
+
`Owner Push Attempted: ${index.ownerPushAttemptedCount ?? 0}`,
|
|
131
|
+
`Owner Push Delivered: ${index.ownerPushDeliveredCount ?? 0}`,
|
|
132
|
+
`Owner Push Failed: ${index.ownerPushFailedCount ?? 0}`,
|
|
133
|
+
''
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
if (Array.isArray(index.recent) && index.recent.length > 0) {
|
|
137
|
+
lines.push('## Recent')
|
|
138
|
+
lines.push('')
|
|
139
|
+
for (const item of index.recent) {
|
|
140
|
+
const delivery = item.ownerDelivery?.attempted
|
|
141
|
+
? item.ownerDelivery?.delivered
|
|
142
|
+
? 'owner-delivered'
|
|
143
|
+
: 'owner-push-failed'
|
|
144
|
+
: 'audit-only'
|
|
145
|
+
lines.push(`- [${delivery}] ${item.remoteAgentId}: ${item.summary}`)
|
|
146
|
+
}
|
|
147
|
+
lines.push('')
|
|
148
|
+
} else {
|
|
149
|
+
lines.push('## Recent')
|
|
150
|
+
lines.push('')
|
|
151
|
+
lines.push('- none')
|
|
152
|
+
lines.push('')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fs.writeFileSync(inboxMarkdownFile, `${lines.join('\n')}\n`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function rebuildIndex() {
|
|
159
|
+
ensureDir(entriesDir)
|
|
160
|
+
const files = fs.readdirSync(entriesDir)
|
|
161
|
+
.filter((name) => name.endsWith('.json'))
|
|
162
|
+
.sort()
|
|
163
|
+
|
|
164
|
+
const entries = files.map((name) => {
|
|
165
|
+
const entry = jsonRead(path.join(entriesDir, name), null)
|
|
166
|
+
return entry && typeof entry === 'object' ? entry : null
|
|
167
|
+
}).filter(Boolean)
|
|
168
|
+
|
|
169
|
+
entries.sort((left, right) => {
|
|
170
|
+
return Date.parse(right.createdAt || 0) - Date.parse(left.createdAt || 0)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const recent = entries.slice(0, 50).map(summarizeEntry)
|
|
174
|
+
const ownerPushAttemptedCount = entries.filter((entry) => Boolean(entry?.ownerDelivery?.attempted)).length
|
|
175
|
+
const ownerPushDeliveredCount = entries.filter((entry) => entry?.ownerDelivery?.attempted && entry?.ownerDelivery?.delivered).length
|
|
176
|
+
const ownerPushFailedCount = entries.filter((entry) => entry?.ownerDelivery?.attempted && !entry?.ownerDelivery?.delivered).length
|
|
177
|
+
|
|
178
|
+
const index = {
|
|
179
|
+
updatedAt: nowISO(),
|
|
180
|
+
totalCount: entries.length,
|
|
181
|
+
lastEntryAt: clean(entries[0]?.createdAt),
|
|
182
|
+
lastEntryId: clean(entries[0]?.id),
|
|
183
|
+
ownerPushAttemptedCount,
|
|
184
|
+
ownerPushDeliveredCount,
|
|
185
|
+
ownerPushFailedCount,
|
|
186
|
+
recent
|
|
187
|
+
}
|
|
188
|
+
jsonWrite(indexFile, index)
|
|
189
|
+
renderMarkdown(index)
|
|
190
|
+
return index
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function appendEntry({
|
|
194
|
+
agentId,
|
|
195
|
+
selectedSkill,
|
|
196
|
+
mailboxKey,
|
|
197
|
+
item,
|
|
198
|
+
ownerReport,
|
|
199
|
+
peerResponse,
|
|
200
|
+
ownerDelivery = null
|
|
201
|
+
}) {
|
|
202
|
+
ensureDir(entriesDir)
|
|
203
|
+
const createdAt = nowISO()
|
|
204
|
+
const id = clean(item?.inboundId) || safeSegment(createdAt)
|
|
205
|
+
const remoteAgentId = clean(item?.remoteAgentId)
|
|
206
|
+
const normalizedRequestId = clean(item?.request?.id)
|
|
207
|
+
const normalizedConversationKey = extractConversationKey(item, peerResponse, ownerReport)
|
|
208
|
+
const isFinalReport = isFinalOwnerReport({ ownerReport })
|
|
209
|
+
let existingEntry = null
|
|
210
|
+
let file = ''
|
|
211
|
+
const files = fs.readdirSync(entriesDir)
|
|
212
|
+
.filter((name) => name.endsWith('.json'))
|
|
213
|
+
.sort()
|
|
214
|
+
.reverse()
|
|
215
|
+
if (normalizedRequestId) {
|
|
216
|
+
for (const name of files) {
|
|
217
|
+
const candidatePath = path.join(entriesDir, name)
|
|
218
|
+
const candidate = jsonRead(candidatePath, null)
|
|
219
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
222
|
+
if (requestIdFromEntry(candidate) !== normalizedRequestId) {
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
if (clean(candidate.peerSessionId) && clean(item?.peerSessionId) && clean(candidate.peerSessionId) !== clean(item?.peerSessionId)) {
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
existingEntry = candidate
|
|
229
|
+
file = candidatePath
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!file && isFinalReport && normalizedConversationKey) {
|
|
234
|
+
for (const name of files) {
|
|
235
|
+
const candidatePath = path.join(entriesDir, name)
|
|
236
|
+
const candidate = jsonRead(candidatePath, null)
|
|
237
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
if (clean(candidate.conversationKey) !== normalizedConversationKey) {
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
if (!isFinalOwnerReport(candidate)) {
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
existingEntry = candidate
|
|
247
|
+
file = candidatePath
|
|
248
|
+
break
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!file) {
|
|
252
|
+
const filename = `${createdAt.replace(/[:.]/g, '-')}_${safeSegment(remoteAgentId)}_${safeSegment(id)}.json`
|
|
253
|
+
file = path.join(entriesDir, filename)
|
|
254
|
+
}
|
|
255
|
+
const inboundText = extractInboundText(item)
|
|
256
|
+
const replyText = extractReplyText(peerResponse)
|
|
257
|
+
const summary = clean(ownerReport?.summary) || `${remoteAgentId || 'unknown'} opened an inbound ${selectedSkill} session.`
|
|
258
|
+
|
|
259
|
+
const entry = {
|
|
260
|
+
...(existingEntry ?? {}),
|
|
261
|
+
id: clean(existingEntry?.id) || id,
|
|
262
|
+
createdAt: clean(existingEntry?.createdAt) || createdAt,
|
|
263
|
+
updatedAt: createdAt,
|
|
264
|
+
agentId: clean(agentId),
|
|
265
|
+
mailboxKey: clean(mailboxKey),
|
|
266
|
+
remoteAgentId,
|
|
267
|
+
conversationKey: normalizedConversationKey,
|
|
268
|
+
peerSessionId: clean(item?.peerSessionId),
|
|
269
|
+
selectedSkill: clean(selectedSkill),
|
|
270
|
+
summary,
|
|
271
|
+
messageExcerpt: excerpt(inboundText),
|
|
272
|
+
replyExcerpt: excerpt(replyText),
|
|
273
|
+
ownerReport: ownerReport ?? null,
|
|
274
|
+
ownerDelivery: ownerDelivery ?? null,
|
|
275
|
+
request: item?.request ?? null,
|
|
276
|
+
peerResponse: peerResponse ?? null,
|
|
277
|
+
file: path.relative(resolvedInboxDir, file)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
jsonWrite(file, entry)
|
|
281
|
+
const index = rebuildIndex()
|
|
282
|
+
return {
|
|
283
|
+
entry,
|
|
284
|
+
index
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function findDeliveredFinalConversationReport(conversationKey = '') {
|
|
289
|
+
const targetConversationKey = clean(conversationKey)
|
|
290
|
+
if (!targetConversationKey) {
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
ensureDir(entriesDir)
|
|
294
|
+
const files = fs.readdirSync(entriesDir)
|
|
295
|
+
.filter((name) => name.endsWith('.json'))
|
|
296
|
+
.sort()
|
|
297
|
+
.reverse()
|
|
298
|
+
|
|
299
|
+
for (const name of files) {
|
|
300
|
+
const entry = jsonRead(path.join(entriesDir, name), null)
|
|
301
|
+
if (!entry || typeof entry !== 'object') {
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
if (clean(entry.conversationKey) !== targetConversationKey) {
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
if (!entry.ownerDelivery?.delivered) {
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
if (!entry.ownerReport?.finalize) {
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
313
|
+
return entry
|
|
314
|
+
}
|
|
315
|
+
return null
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function snapshot() {
|
|
319
|
+
const index = readIndex()
|
|
320
|
+
return {
|
|
321
|
+
inboxDir: resolvedInboxDir,
|
|
322
|
+
entriesDir,
|
|
323
|
+
inboxMarkdownFile,
|
|
324
|
+
indexFile,
|
|
325
|
+
totalCount: index.totalCount ?? 0,
|
|
326
|
+
lastEntryAt: index.lastEntryAt ?? '',
|
|
327
|
+
ownerPushAttemptedCount: index.ownerPushAttemptedCount ?? 0,
|
|
328
|
+
ownerPushDeliveredCount: index.ownerPushDeliveredCount ?? 0,
|
|
329
|
+
ownerPushFailedCount: index.ownerPushFailedCount ?? 0
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
inboxDir: resolvedInboxDir,
|
|
335
|
+
entriesDir,
|
|
336
|
+
indexFile,
|
|
337
|
+
inboxMarkdownFile,
|
|
338
|
+
readIndex,
|
|
339
|
+
rebuildIndex,
|
|
340
|
+
appendEntry,
|
|
341
|
+
findDeliveredFinalConversationReport,
|
|
342
|
+
snapshot
|
|
343
|
+
}
|
|
344
|
+
}
|