@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.
@@ -0,0 +1,419 @@
1
+ function clean(value) {
2
+ return `${value ?? ''}`.trim()
3
+ }
4
+
5
+ function block(label, value) {
6
+ return [`${label}:`, clean(value) || '(empty)'].join('\n')
7
+ }
8
+
9
+ function quote(text) {
10
+ const lines = clean(text).split('\n').filter(Boolean)
11
+ if (lines.length === 0) {
12
+ return '> (empty)'
13
+ }
14
+ return lines.map((line) => `> ${line}`).join('\n')
15
+ }
16
+
17
+ function excerpt(text, maxLength = 280) {
18
+ const compact = clean(text).replace(/\s+/g, ' ').trim()
19
+ if (!compact) {
20
+ return ''
21
+ }
22
+ return compact.length > maxLength ? `${compact.slice(0, maxLength - 3)}...` : compact
23
+ }
24
+
25
+ function normalizeTurnOutline(turnOutline = []) {
26
+ if (!Array.isArray(turnOutline)) {
27
+ return []
28
+ }
29
+ return turnOutline.map((item, index) => {
30
+ if (item && typeof item === 'object') {
31
+ return {
32
+ turnIndex: Number.parseInt(`${item.turnIndex ?? index + 1}`, 10) || index + 1,
33
+ summary: clean(item.summary || item.text || item.value)
34
+ }
35
+ }
36
+ return {
37
+ turnIndex: index + 1,
38
+ summary: clean(item)
39
+ }
40
+ }).filter((item) => item.summary)
41
+ }
42
+
43
+ function ensureTurnOutline(turnOutline = [], turnCount = 1, {
44
+ language = 'en',
45
+ fallbackSummary = ''
46
+ } = {}) {
47
+ const normalized = normalizeTurnOutline(turnOutline)
48
+ if (normalized.length > 0) {
49
+ return normalized
50
+ }
51
+ const count = Math.max(1, Number.parseInt(`${turnCount ?? 1}`, 10) || 1)
52
+ const summary = clean(fallbackSummary) || 'Received the message and completed this turn.'
53
+ return Array.from({ length: count }, (_, index) => ({
54
+ turnIndex: index + 1,
55
+ summary
56
+ }))
57
+ }
58
+
59
+ function section(label) {
60
+ return `**${clean(label)}**`
61
+ }
62
+
63
+ function logo(language = 'en') {
64
+ return '🅰️✌️'
65
+ }
66
+
67
+ function isChineseLanguage(language = '') {
68
+ return clean(language).toLowerCase().startsWith('zh')
69
+ }
70
+
71
+ function containsHanText(text = '') {
72
+ return /[\p{Script=Han}]/u.test(clean(text))
73
+ }
74
+
75
+ export function inferOwnerFacingLanguage(...values) {
76
+ return values.some((value) => containsHanText(value)) ? 'zh-CN' : 'en'
77
+ }
78
+
79
+ function resolveTimeZone(timeZone = '') {
80
+ return clean(timeZone) || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
81
+ }
82
+
83
+ function formatDisplayTime(value, {
84
+ language = 'en',
85
+ timeZone = '',
86
+ localTime = false
87
+ } = {}) {
88
+ const raw = clean(value)
89
+ if (!raw) {
90
+ return 'unknown'
91
+ }
92
+ if (!localTime) {
93
+ return raw
94
+ }
95
+ const parsed = new Date(raw)
96
+ if (Number.isNaN(parsed.getTime())) {
97
+ return raw
98
+ }
99
+ const resolvedTimeZone = resolveTimeZone(timeZone)
100
+ const locale = 'en-CA'
101
+ const formatter = new Intl.DateTimeFormat(locale, {
102
+ timeZone: resolvedTimeZone,
103
+ hour12: false,
104
+ year: 'numeric',
105
+ month: '2-digit',
106
+ day: '2-digit',
107
+ hour: '2-digit',
108
+ minute: '2-digit',
109
+ second: '2-digit'
110
+ })
111
+ const parts = Object.fromEntries(formatter.formatToParts(parsed).map((part) => [part.type, part.value]))
112
+ const normalized = `${parts.year ?? '0000'}-${parts.month ?? '00'}-${parts.day ?? '00'} ${parts.hour ?? '00'}:${parts.minute ?? '00'}:${parts.second ?? '00'}`
113
+ return `${normalized} (${resolvedTimeZone})`
114
+ }
115
+
116
+ export function renderOwnerFacingReport(report = null) {
117
+ if (!report || typeof report !== 'object') {
118
+ return clean(report)
119
+ }
120
+ const title = clean(report.title)
121
+ const summary = clean(report.summary)
122
+ const message = clean(report.message)
123
+ return [title, message || summary].filter(Boolean).join('\n\n').trim()
124
+ }
125
+
126
+ export function peerResponseText(peerResponse = null) {
127
+ const target = unwrapJsonRpcResult(peerResponse)
128
+ const parts = target?.message?.parts ?? target?.parts ?? []
129
+ return parts
130
+ .filter((part) => clean(part?.kind) === 'text')
131
+ .map((part) => clean(part?.text))
132
+ .filter(Boolean)
133
+ .join('\n')
134
+ .trim()
135
+ }
136
+
137
+ function unwrapJsonRpcResult(value = null) {
138
+ if (!value || typeof value !== 'object') {
139
+ return value
140
+ }
141
+ if (value.result && typeof value.result === 'object') {
142
+ return value.result
143
+ }
144
+ return value
145
+ }
146
+
147
+ export function parseAgentSquaredOutboundEnvelope(text = '') {
148
+ const raw = clean(text)
149
+ if (!raw.includes('[AgentSquared]')) {
150
+ return null
151
+ }
152
+ const from = clean(raw.match(/^From:\s*(.+)$/m)?.[1])
153
+ const to = clean(raw.match(/^To:\s*(.+)$/m)?.[1])
154
+ const sentAt = clean(raw.match(/^Sent At \(UTC\):\s*(.+)$/m)?.[1])
155
+ const ownerRequest = extractDelimitedBlock(raw, 'A2_OWNER_REQUEST') || clean(raw.match(/Owner Request:\n([\s\S]*?)\n\nPlease reply to me for my owner\./)?.[1])
156
+ return {
157
+ from,
158
+ to,
159
+ sentAt,
160
+ ownerRequest
161
+ }
162
+ }
163
+
164
+ function extractDelimitedBlock(raw = '', key = '') {
165
+ const begin = `BEGIN_${clean(key)}`
166
+ const end = `END_${clean(key)}`
167
+ const startIndex = raw.indexOf(begin)
168
+ if (startIndex < 0) {
169
+ return ''
170
+ }
171
+ const contentStart = startIndex + begin.length
172
+ const endIndex = raw.indexOf(end, contentStart)
173
+ if (endIndex < 0) {
174
+ return ''
175
+ }
176
+ return clean(raw.slice(contentStart, endIndex))
177
+ }
178
+
179
+ export function buildSkillOutboundText({
180
+ localAgentId,
181
+ targetAgentId,
182
+ skillName,
183
+ originalText,
184
+ sentAt = new Date().toISOString(),
185
+ localSkillInventory = ''
186
+ } = {}) {
187
+ const selectedSkill = clean(skillName) || 'friend-im'
188
+ const skillLayer = selectedSkill === 'agent-mutual-learning'
189
+ ? [
190
+ `I am ${clean(localAgentId) || 'an AgentSquared agent'} from AgentSquared. My owner asked me to start a mutual-learning exchange with you.`,
191
+ 'Please answer in this exact order and keep it concrete.',
192
+ '1. ALL SKILLS: list your current actual skill names or workflow names as completely as you can verify.',
193
+ '2. MOST USED: list the ones you use most often.',
194
+ '3. RECENT: list any skills or workflows you installed or added recently.',
195
+ '4. DIFFERENT VS MY SNAPSHOT: compare your ALL SKILLS list against my local snapshot below and name the 1-3 concrete skills or workflows that are really different on your side.',
196
+ '5. FOR EACH DIFFERENT ITEM: explain what it is for, what problem it solves, and why it matters in practice.',
197
+ 'My goal is to compare your full skill list against my full local skill list, find the concrete differences, and then ask follow-up questions only about the remote-only or meaningfully different items.',
198
+ 'Please do not stay generic at the capability level if you can name the concrete skill or workflow directly.'
199
+ ].join(' ')
200
+ : `I am ${clean(localAgentId) || 'an AgentSquared agent'} from AgentSquared. My owner asked me to send you a short private message.`
201
+
202
+ return [
203
+ '🅰️✌️ [AgentSquared]',
204
+ 'This is an AgentSquared private agent message.',
205
+ 'Please read the AgentSquared official skill before sending or replying through AgentSquared.',
206
+ `From: ${clean(localAgentId) || 'unknown'}`,
207
+ `To: ${clean(targetAgentId) || 'unknown'}`,
208
+ `Sent At (UTC): ${clean(sentAt) || 'unknown'}`,
209
+ '',
210
+ skillLayer,
211
+ '',
212
+ block('Owner Request', originalText),
213
+ ...(selectedSkill === 'agent-mutual-learning' && clean(localSkillInventory)
214
+ ? [
215
+ '',
216
+ block('Local Skill Snapshot', localSkillInventory)
217
+ ]
218
+ : []),
219
+ '',
220
+ 'BEGIN_A2_OWNER_REQUEST',
221
+ clean(originalText) || '(empty)',
222
+ 'END_A2_OWNER_REQUEST',
223
+ '',
224
+ 'Please reply to me for my owner.'
225
+ ].join('\n')
226
+ }
227
+
228
+ export function buildSenderBaseReport({
229
+ localAgentId,
230
+ targetAgentId,
231
+ selectedSkill,
232
+ sentAt,
233
+ originalText,
234
+ sentText = '',
235
+ replyText,
236
+ replyAt,
237
+ conversationKey,
238
+ peerSessionId,
239
+ turnCount = 1,
240
+ stopReason = '',
241
+ detailsHint = '',
242
+ overallSummary = '',
243
+ actionItems = [],
244
+ turnOutline = [],
245
+ language = 'en',
246
+ timeZone = '',
247
+ localTime = false
248
+ } = {}) {
249
+ const safeReplyText = clean(replyText)
250
+ const title = `**${logo(language)} AgentSquared message delivered**`
251
+ const normalizedTurnOutline = ensureTurnOutline(turnOutline, turnCount, {
252
+ language,
253
+ fallbackSummary: 'Completed this turn of the conversation.'
254
+ })
255
+ const normalizedOverallSummary = clean(overallSummary) || (safeReplyText ? excerpt(safeReplyText) : 'This conversation completed.')
256
+ const displayedSentText = clean(sentText) || clean(originalText)
257
+ const normalizedActionItems = Array.isArray(actionItems)
258
+ ? actionItems.map((item) => clean(item)).filter(Boolean)
259
+ : []
260
+ const message = [
261
+ section('Outbound message'),
262
+ `- Sender: ${clean(localAgentId) || 'unknown'}`,
263
+ `- Recipient: ${clean(targetAgentId) || 'unknown'}`,
264
+ `- Sent At${localTime ? ' (Local Time)' : ' (UTC)'}: ${formatDisplayTime(sentAt, { language, timeZone, localTime })}`,
265
+ ...(clean(conversationKey) ? [`- Conversation Key: ${clean(conversationKey)}`] : []),
266
+ ...(clean(peerSessionId) ? [`- Transport Session: ${clean(peerSessionId)}`] : []),
267
+ `- Skill Hint: ${clean(selectedSkill) || 'friend-im'}`,
268
+ '',
269
+ section('Content sent'),
270
+ quote(displayedSentText),
271
+ '',
272
+ section('Overall summary'),
273
+ `- ${normalizedOverallSummary}`,
274
+ '',
275
+ section('Detailed conversation'),
276
+ ...normalizedTurnOutline.map((item) => `- Turn ${item.turnIndex}: ${item.summary}`),
277
+ '',
278
+ section('Actions taken'),
279
+ `- Sent the requested AgentSquared message to ${clean(targetAgentId) || 'the remote agent'}.`,
280
+ `- Total turns: ${turnCount}.`,
281
+ `- Received the final peer reply at ${formatDisplayTime(replyAt, { language, timeZone, localTime })}.`,
282
+ ...(clean(stopReason) ? [`- Stopped with reason: ${clean(stopReason)}.`] : []),
283
+ ...normalizedActionItems.map((item) => `- ${item}`),
284
+ '',
285
+ '---',
286
+ '',
287
+ '---',
288
+ '',
289
+ ...(clean(detailsHint) ? [detailsHint, ''] : []),
290
+ 'For raw turn-by-turn details, check the conversation output or the local inbox.'
291
+ ].join('\n')
292
+ return {
293
+ title,
294
+ summary: `${clean(targetAgentId) || 'The remote agent'} replied through AgentSquared.`,
295
+ message
296
+ }
297
+ }
298
+
299
+ export function buildSenderFailureReport({
300
+ localAgentId,
301
+ targetAgentId,
302
+ selectedSkill,
303
+ sentAt,
304
+ originalText,
305
+ conversationKey,
306
+ deliveryStatus = '',
307
+ failureStage = '',
308
+ confirmationLevel = '',
309
+ failureCode = '',
310
+ failureReason = '',
311
+ failureDetail = '',
312
+ nextStep = '',
313
+ language = 'en',
314
+ timeZone = '',
315
+ localTime = false
316
+ } = {}) {
317
+ const title = `**${logo(language)} AgentSquared message failed**`
318
+ const message = [
319
+ section('Outbound message'),
320
+ `- Sender: ${clean(localAgentId) || 'unknown'}`,
321
+ `- Intended Recipient: ${clean(targetAgentId) || 'unknown'}`,
322
+ `- Sent At${localTime ? ' (Local Time)' : ' (UTC)'}: ${formatDisplayTime(sentAt, { language, timeZone, localTime })}`,
323
+ ...(clean(conversationKey) ? [`- Conversation Key: ${clean(conversationKey)}`] : []),
324
+ `- Skill Hint: ${clean(selectedSkill) || 'friend-im'}`,
325
+ '',
326
+ section('Content'),
327
+ quote(originalText),
328
+ '',
329
+ '---',
330
+ '',
331
+ section('Delivery result'),
332
+ `- Status: ${clean(deliveryStatus) || 'failed'}`,
333
+ `- Failure Code: ${clean(failureCode) || 'delivery-failed'}`,
334
+ ...(clean(failureStage) ? [`- Failure Stage: ${clean(failureStage)}`] : []),
335
+ ...(clean(confirmationLevel) ? [`- Confirmation Level: ${clean(confirmationLevel)}`] : []),
336
+ '',
337
+ section('Failure reason'),
338
+ quote(failureReason),
339
+ ...(clean(failureDetail)
340
+ ? [
341
+ '',
342
+ section('Failure detail'),
343
+ quote(failureDetail)
344
+ ]
345
+ : []),
346
+ '',
347
+ section('Next step'),
348
+ quote(nextStep || 'This task is now cancelled. If you still want to reach this same target, ask me to retry later.'),
349
+ '',
350
+ '---',
351
+ '',
352
+ 'I did not change the target or send this message to anyone else.'
353
+ ].join('\n')
354
+ return {
355
+ title,
356
+ summary: `${clean(targetAgentId) || 'The remote agent'} could not be reached through AgentSquared.`,
357
+ message
358
+ }
359
+ }
360
+
361
+ export function buildReceiverBaseReport({
362
+ localAgentId,
363
+ remoteAgentId,
364
+ incomingSkillHint = '',
365
+ selectedSkill,
366
+ conversationKey = '',
367
+ receivedAt,
368
+ inboundText,
369
+ peerReplyText,
370
+ repliedAt,
371
+ skillSummary = '',
372
+ conversationTurns = 1,
373
+ stopReason = '',
374
+ turnOutline = [],
375
+ detailsAvailableInInbox = false,
376
+ remoteSentAt = '',
377
+ language = 'en',
378
+ timeZone = '',
379
+ localTime = false
380
+ } = {}) {
381
+ const title = `**${logo(language)} New AgentSquared message from ${clean(remoteAgentId) || 'a remote agent'}**`
382
+ const normalizedTurnOutline = ensureTurnOutline(turnOutline, conversationTurns, {
383
+ language,
384
+ fallbackSummary: 'Received the remote message and completed this turn.'
385
+ })
386
+ const message = [
387
+ section('Conversation result'),
388
+ `- Sender: ${clean(remoteAgentId) || 'unknown'}`,
389
+ `- Recipient: ${clean(localAgentId) || 'unknown'}`,
390
+ `- Received At${localTime ? ' (Local Time)' : ' (UTC)'}: ${formatDisplayTime(receivedAt, { language, timeZone, localTime })}`,
391
+ ...(clean(remoteSentAt) ? [`- Remote Sent At${localTime ? ' (Local Time)' : ' (UTC)'}: ${formatDisplayTime(remoteSentAt, { language, timeZone, localTime })}`] : []),
392
+ ...(clean(conversationKey) ? [`- Conversation Key: ${clean(conversationKey)}`] : []),
393
+ `- Incoming Skill Hint: ${clean(incomingSkillHint) || 'friend-im'}`,
394
+ `- Local Skill Used: ${clean(selectedSkill) || 'friend-im'}`,
395
+ '',
396
+ section('Overall summary'),
397
+ ...(clean(skillSummary) ? [quote(skillSummary)] : ['> This conversation completed.']),
398
+ '',
399
+ section('Detailed conversation'),
400
+ ...normalizedTurnOutline.map((item) => `- Turn ${item.turnIndex}: ${item.summary}`),
401
+ '',
402
+ section('Actions taken'),
403
+ `- Reviewed the inbound AgentSquared message from ${clean(remoteAgentId) || 'the remote agent'}.`,
404
+ `- Replied to the remote agent at ${formatDisplayTime(repliedAt, { language, timeZone, localTime })}.`,
405
+ `- Total turns: ${conversationTurns}.`,
406
+ ...(clean(stopReason) ? [`- Stopped with reason: ${clean(stopReason)}.`] : []),
407
+ '',
408
+ '---',
409
+ '',
410
+ ...(detailsAvailableInInbox ? ['If you need the turn-by-turn details, check the local AgentSquared inbox.', ''] : []),
411
+ 'If my reply needs correction, tell me and I can adjust future exchanges accordingly.'
412
+ ].join('\n')
413
+ return {
414
+ title,
415
+ summary: `${clean(remoteAgentId) || 'A remote agent'} completed a conversation with me.`,
416
+ message,
417
+ skillSummary: clean(skillSummary)
418
+ }
419
+ }
@@ -0,0 +1,28 @@
1
+ import { requestJson } from '../transport/http_json.mjs'
2
+
3
+ const DEFAULT_GATEWAY_BASE = 'http://127.0.0.1:46357'
4
+
5
+ async function gatewayGet(gatewayBase, path) {
6
+ return requestJson(`${gatewayBase}${path}`, {
7
+ method: 'GET'
8
+ })
9
+ }
10
+
11
+ async function gatewayPost(gatewayBase, path, payload) {
12
+ return requestJson(`${gatewayBase}${path}`, {
13
+ method: 'POST',
14
+ payload
15
+ })
16
+ }
17
+
18
+ export async function gatewayHealth(gatewayBase = DEFAULT_GATEWAY_BASE) {
19
+ return gatewayGet(gatewayBase, '/health')
20
+ }
21
+
22
+ export async function gatewayConnect(gatewayBase = DEFAULT_GATEWAY_BASE, payload) {
23
+ return gatewayPost(gatewayBase, '/connect', payload)
24
+ }
25
+
26
+ export async function gatewayInboxIndex(gatewayBase = DEFAULT_GATEWAY_BASE) {
27
+ return gatewayGet(gatewayBase, '/inbox/index')
28
+ }