@zengxingyuan/aamp-cli-bridge 0.1.7-dev.3 → 0.1.7-dev.4
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 +33 -4
- package/dist/agent-bridge.d.ts +5 -0
- package/dist/agent-bridge.js +208 -30
- package/dist/agent-bridge.js.map +1 -1
- package/dist/bridge.d.ts +109 -0
- package/dist/bridge.js +35 -1
- package/dist/bridge.js.map +1 -1
- package/dist/cli-agent-client.d.ts +1 -0
- package/dist/cli-agent-client.js +13 -0
- package/dist/cli-agent-client.js.map +1 -1
- package/dist/cli-profiles.js +1 -1
- package/dist/cli-profiles.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/discovery.d.ts +20 -0
- package/dist/discovery.js +172 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.js +152 -7
- package/dist/index.js.map +1 -1
- package/dist/json-init.d.ts +280 -0
- package/dist/json-init.js +177 -0
- package/dist/json-init.js.map +1 -0
- package/dist/sender-policy.d.ts +27 -0
- package/dist/sender-policy.js +161 -0
- package/dist/sender-policy.js.map +1 -0
- package/dist/stream-parser.js +17 -6
- package/dist/stream-parser.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,33 @@ npx aamp-cli-bridge start --config ./production.cli-bridge.json
|
|
|
76
76
|
npx aamp-cli-bridge list --config ./production.cli-bridge.json
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
Desktop and other non-interactive clients can use JSON output:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx aamp-cli-bridge init --json --input -
|
|
83
|
+
npx aamp-cli-bridge discover --json
|
|
84
|
+
npx aamp-cli-bridge list --json
|
|
85
|
+
npx aamp-cli-bridge start --json
|
|
86
|
+
npx aamp-cli-bridge pair --agent codex --json --no-start
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`init --json` is an upsert operation for desktop clients: it writes the bridge config, reuses existing credentials when available, registers missing mailboxes, and does not auto-start the bridge. `discover --json` scans built-in profiles, user profiles, and already configured agents, then reports which commands are available on PATH. `start --json` emits JSONL runtime events on stdout and sends human-readable logs to stderr. `pair --json --no-start` creates a pairing URL without rendering a terminal QR code.
|
|
90
|
+
|
|
91
|
+
Example JSON init input:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"aampHost": "https://meshmail.ai",
|
|
96
|
+
"agents": [
|
|
97
|
+
{
|
|
98
|
+
"name": "codex",
|
|
99
|
+
"cliProfile": "codex",
|
|
100
|
+
"createPairing": true
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
79
106
|
## Profile Model
|
|
80
107
|
|
|
81
108
|
A CLI profile describes how to invoke an agent and how to interpret its output.
|
|
@@ -211,10 +238,10 @@ NDJSON stream:
|
|
|
211
238
|
The parser accepts common event shapes used by CLI agents:
|
|
212
239
|
|
|
213
240
|
- `text`, `delta`, `text.delta`: forwarded to AAMP as `text.delta`
|
|
214
|
-
- `tool_start`, `tool_result`, `tool`: forwarded
|
|
215
|
-
- `usage`: forwarded as a
|
|
241
|
+
- `tool_start`, `tool_result`, `tool`: forwarded to AAMP as `tool_call`
|
|
242
|
+
- `usage`: forwarded as a `todo` update
|
|
216
243
|
- `result`: used as final text when present
|
|
217
|
-
- `done`:
|
|
244
|
+
- `done`: ends parser consumption; terminal state is sent through `task.result`
|
|
218
245
|
|
|
219
246
|
Text deltas are streamed to AAMP and concatenated into the final `task.result`. This lets a mailbox UI show live output while still preserving a complete final answer in the thread.
|
|
220
247
|
|
|
@@ -314,9 +341,11 @@ The CLI agent can use these plain-output conventions:
|
|
|
314
341
|
|
|
315
342
|
```bash
|
|
316
343
|
npx aamp-cli-bridge init [--no-start]
|
|
317
|
-
npx aamp-cli-bridge
|
|
344
|
+
npx aamp-cli-bridge init --json --input -
|
|
345
|
+
npx aamp-cli-bridge start [--config X] [--json]
|
|
318
346
|
npx aamp-cli-bridge pair --agent NAME [--config X] [--no-start]
|
|
319
347
|
npx aamp-cli-bridge list [--config X]
|
|
348
|
+
npx aamp-cli-bridge discover [--config X] [--json]
|
|
320
349
|
npx aamp-cli-bridge status
|
|
321
350
|
npx aamp-cli-bridge profile-list
|
|
322
351
|
npx aamp-cli-bridge profile-maker
|
package/dist/agent-bridge.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentConfig, BridgeConfig } from './config.js';
|
|
2
|
+
import type { BridgeRuntimeEvent } from './bridge.js';
|
|
2
3
|
export interface AgentIdentity {
|
|
3
4
|
email: string;
|
|
4
5
|
mailboxToken: string;
|
|
@@ -6,6 +7,7 @@ export interface AgentIdentity {
|
|
|
6
7
|
}
|
|
7
8
|
export interface AgentBridgeStartOptions {
|
|
8
9
|
quiet?: boolean;
|
|
10
|
+
onEvent?: (event: BridgeRuntimeEvent) => void;
|
|
9
11
|
}
|
|
10
12
|
export declare class AgentBridge {
|
|
11
13
|
private readonly agentConfig;
|
|
@@ -17,16 +19,19 @@ export declare class AgentBridge {
|
|
|
17
19
|
private activeTaskCount;
|
|
18
20
|
private pollingFallback;
|
|
19
21
|
private cancelledTaskIds;
|
|
22
|
+
private activeTaskIds;
|
|
20
23
|
private profileLabel;
|
|
21
24
|
private streamEnabled;
|
|
22
25
|
private senderPolicies;
|
|
23
26
|
private isHistoricalReconcile;
|
|
27
|
+
private onEvent;
|
|
24
28
|
constructor(agentConfig: AgentConfig, aampHost: string, rejectUnauthorized: boolean, customProfiles?: BridgeConfig['profiles']);
|
|
25
29
|
get name(): string;
|
|
26
30
|
get email(): string;
|
|
27
31
|
get isConnected(): boolean;
|
|
28
32
|
get isUsingPollingFallback(): boolean;
|
|
29
33
|
get isBusy(): boolean;
|
|
34
|
+
private emit;
|
|
30
35
|
private getConfiguredCardText;
|
|
31
36
|
private syncDirectoryProfile;
|
|
32
37
|
start(options?: AgentBridgeStartOptions): Promise<void>;
|
package/dist/agent-bridge.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AampClient, } from 'aamp-sdk';
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { basename, dirname } from 'node:path';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
4
|
import { CliAgentClient } from './cli-agent-client.js';
|
|
5
5
|
import { resolveCliProfile } from './cli-profiles.js';
|
|
6
6
|
import { buildPrompt, parseResponse } from './prompt-builder.js';
|
|
7
|
-
import { resolveCredentialsFile } from './storage.js';
|
|
7
|
+
import { getBridgeHomeDir, resolveCredentialsFile } from './storage.js';
|
|
8
8
|
import { addSenderPolicy, consumePairingCode, loadSenderPolicies, resolvePairingFile, resolveSenderPoliciesFile, rulesMatch, validatePairingCode, } from './pairing.js';
|
|
9
9
|
const IDENTITY_AUTH_RETRY_COUNT = 5;
|
|
10
10
|
const IDENTITY_AUTH_RETRY_DELAY_MS = 1_000;
|
|
@@ -12,7 +12,7 @@ function matchSenderPolicy(task, senderPolicies) {
|
|
|
12
12
|
if (!senderPolicies?.length)
|
|
13
13
|
return { allowed: false, reason: 'no configured senderPolicies' };
|
|
14
14
|
const sender = task.from.toLowerCase();
|
|
15
|
-
const policy = senderPolicies.find((item) => item.sender
|
|
15
|
+
const policy = senderPolicies.find((item) => matchesSenderPattern(sender, item.sender));
|
|
16
16
|
if (!policy) {
|
|
17
17
|
return { allowed: false, reason: `sender ${task.from} is not allowed by senderPolicies` };
|
|
18
18
|
}
|
|
@@ -32,6 +32,17 @@ function matchSenderPolicy(task, senderPolicies) {
|
|
|
32
32
|
}
|
|
33
33
|
return { allowed: true };
|
|
34
34
|
}
|
|
35
|
+
function matchesSenderPattern(senderEmail, pattern) {
|
|
36
|
+
const normalizedSender = senderEmail.trim().toLowerCase();
|
|
37
|
+
const normalizedPattern = pattern.trim().toLowerCase();
|
|
38
|
+
if (!normalizedSender || !normalizedPattern)
|
|
39
|
+
return false;
|
|
40
|
+
const canonicalPattern = normalizedPattern.startsWith('@')
|
|
41
|
+
? `*${normalizedPattern}`
|
|
42
|
+
: normalizedPattern;
|
|
43
|
+
const escaped = canonicalPattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
44
|
+
return new RegExp(`^${escaped}$`, 'i').test(normalizedSender);
|
|
45
|
+
}
|
|
35
46
|
function matchPairedSenderPolicy(task, senderPolicies) {
|
|
36
47
|
if (senderPolicies.length === 0)
|
|
37
48
|
return { allowed: false, reason: 'no paired sender policies configured' };
|
|
@@ -132,6 +143,39 @@ function stringifyStreamPayload(payload) {
|
|
|
132
143
|
return payload.chunk;
|
|
133
144
|
return JSON.stringify(payload, null, 2);
|
|
134
145
|
}
|
|
146
|
+
function taskLockName(taskId) {
|
|
147
|
+
return taskId
|
|
148
|
+
.trim()
|
|
149
|
+
.replace(/[^a-zA-Z0-9_.-]+/g, '-')
|
|
150
|
+
.replace(/-+/g, '-')
|
|
151
|
+
.replace(/^-|-$/g, '')
|
|
152
|
+
.slice(0, 128) || 'task';
|
|
153
|
+
}
|
|
154
|
+
function acquireTaskExecutionLock(taskId) {
|
|
155
|
+
const locksDir = join(getBridgeHomeDir(), 'task-locks');
|
|
156
|
+
const lockDir = join(locksDir, `${taskLockName(taskId)}.lock`);
|
|
157
|
+
mkdirSync(locksDir, { recursive: true });
|
|
158
|
+
try {
|
|
159
|
+
mkdirSync(lockDir);
|
|
160
|
+
writeFileSync(join(lockDir, 'owner.json'), `${JSON.stringify({
|
|
161
|
+
pid: process.pid,
|
|
162
|
+
taskId,
|
|
163
|
+
acquiredAt: new Date().toISOString(),
|
|
164
|
+
}, null, 2)}\n`);
|
|
165
|
+
return lockDir;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
const code = error.code;
|
|
169
|
+
if (code === 'EEXIST')
|
|
170
|
+
return null;
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function releaseTaskExecutionLock(lockDir) {
|
|
175
|
+
if (!lockDir)
|
|
176
|
+
return;
|
|
177
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
135
179
|
export class AgentBridge {
|
|
136
180
|
agentConfig;
|
|
137
181
|
aampHost;
|
|
@@ -142,10 +186,12 @@ export class AgentBridge {
|
|
|
142
186
|
activeTaskCount = 0;
|
|
143
187
|
pollingFallback = false;
|
|
144
188
|
cancelledTaskIds = new Set();
|
|
189
|
+
activeTaskIds = new Set();
|
|
145
190
|
profileLabel;
|
|
146
191
|
streamEnabled;
|
|
147
192
|
senderPolicies = [];
|
|
148
193
|
isHistoricalReconcile = false;
|
|
194
|
+
onEvent;
|
|
149
195
|
constructor(agentConfig, aampHost, rejectUnauthorized, customProfiles) {
|
|
150
196
|
this.agentConfig = agentConfig;
|
|
151
197
|
this.aampHost = aampHost;
|
|
@@ -160,6 +206,9 @@ export class AgentBridge {
|
|
|
160
206
|
get isConnected() { return this.client?.isConnected() ?? false; }
|
|
161
207
|
get isUsingPollingFallback() { return this.pollingFallback || (this.client?.isUsingPollingFallback() ?? false); }
|
|
162
208
|
get isBusy() { return this.activeTaskCount > 0; }
|
|
209
|
+
emit(event) {
|
|
210
|
+
this.onEvent?.(event);
|
|
211
|
+
}
|
|
163
212
|
getConfiguredCardText() {
|
|
164
213
|
const inline = this.agentConfig.cardText?.trim();
|
|
165
214
|
if (inline)
|
|
@@ -186,6 +235,7 @@ export class AgentBridge {
|
|
|
186
235
|
}
|
|
187
236
|
}
|
|
188
237
|
async start(options = {}) {
|
|
238
|
+
this.onEvent = options.onEvent;
|
|
189
239
|
let quietStartup = options.quiet === true;
|
|
190
240
|
this.identity = await this.resolveIdentity();
|
|
191
241
|
this.senderPolicies = loadSenderPolicies(resolveSenderPoliciesFile(this.agentConfig.senderPoliciesFile, this.agentConfig.name));
|
|
@@ -193,6 +243,13 @@ export class AgentBridge {
|
|
|
193
243
|
console.log(`[${this.name}] AAMP identity: ${this.identity.email}`);
|
|
194
244
|
console.log(`[${this.name}] CLI profile: ${this.profileLabel}`);
|
|
195
245
|
}
|
|
246
|
+
this.emit({
|
|
247
|
+
type: 'agent.identity',
|
|
248
|
+
bridge: 'cli-bridge',
|
|
249
|
+
agent: this.name,
|
|
250
|
+
email: this.identity.email,
|
|
251
|
+
profile: this.profileLabel,
|
|
252
|
+
});
|
|
196
253
|
this.client = AampClient.fromMailboxIdentity({
|
|
197
254
|
email: this.identity.email,
|
|
198
255
|
smtpPassword: this.identity.smtpPassword,
|
|
@@ -218,12 +275,27 @@ export class AgentBridge {
|
|
|
218
275
|
});
|
|
219
276
|
client.on('connected', () => {
|
|
220
277
|
this.pollingFallback = client.isUsingPollingFallback();
|
|
278
|
+
this.emit({
|
|
279
|
+
type: 'agent.connected',
|
|
280
|
+
bridge: 'cli-bridge',
|
|
281
|
+
agent: this.name,
|
|
282
|
+
email: this.email,
|
|
283
|
+
pollingFallback: this.pollingFallback,
|
|
284
|
+
});
|
|
221
285
|
if (!quietStartup) {
|
|
222
286
|
console.log(`[${this.name}] AAMP connected${this.pollingFallback ? ' (polling fallback)' : ''}`);
|
|
223
287
|
}
|
|
224
288
|
});
|
|
225
289
|
client.on('disconnected', (reason) => {
|
|
226
290
|
this.pollingFallback = client.isUsingPollingFallback();
|
|
291
|
+
this.emit({
|
|
292
|
+
type: 'agent.disconnected',
|
|
293
|
+
bridge: 'cli-bridge',
|
|
294
|
+
agent: this.name,
|
|
295
|
+
email: this.email,
|
|
296
|
+
reason,
|
|
297
|
+
pollingFallback: this.pollingFallback,
|
|
298
|
+
});
|
|
227
299
|
if (!quietStartup) {
|
|
228
300
|
console.warn(`[${this.name}] AAMP disconnected: ${reason}`);
|
|
229
301
|
}
|
|
@@ -231,11 +303,25 @@ export class AgentBridge {
|
|
|
231
303
|
client.on('error', (err) => {
|
|
232
304
|
if (err.message.includes('falling back to polling')) {
|
|
233
305
|
this.pollingFallback = true;
|
|
306
|
+
this.emit({
|
|
307
|
+
type: 'agent.error',
|
|
308
|
+
bridge: 'cli-bridge',
|
|
309
|
+
agent: this.name,
|
|
310
|
+
email: this.email,
|
|
311
|
+
message: err.message,
|
|
312
|
+
});
|
|
234
313
|
if (!quietStartup) {
|
|
235
314
|
console.warn(`[${this.name}] ${err.message}`);
|
|
236
315
|
}
|
|
237
316
|
return;
|
|
238
317
|
}
|
|
318
|
+
this.emit({
|
|
319
|
+
type: 'agent.error',
|
|
320
|
+
bridge: 'cli-bridge',
|
|
321
|
+
agent: this.name,
|
|
322
|
+
email: this.email,
|
|
323
|
+
message: err.message,
|
|
324
|
+
});
|
|
239
325
|
console.error(`[${this.name}] AAMP error: ${err.message}`);
|
|
240
326
|
});
|
|
241
327
|
await client.connect();
|
|
@@ -253,6 +339,13 @@ export class AgentBridge {
|
|
|
253
339
|
if (!quietStartup) {
|
|
254
340
|
console.log(`[${this.name}] Reconciled ${reconciled} recent email(s)`);
|
|
255
341
|
}
|
|
342
|
+
this.emit({
|
|
343
|
+
type: 'agent.reconciled',
|
|
344
|
+
bridge: 'cli-bridge',
|
|
345
|
+
agent: this.name,
|
|
346
|
+
email: this.email,
|
|
347
|
+
count: reconciled,
|
|
348
|
+
});
|
|
256
349
|
await this.syncDirectoryProfile({ quiet: quietStartup }).catch((err) => {
|
|
257
350
|
if (!quietStartup) {
|
|
258
351
|
console.warn(`[${this.name}] Directory profile sync failed: ${err.message}`);
|
|
@@ -270,6 +363,15 @@ export class AgentBridge {
|
|
|
270
363
|
const shouldLogTask = !options.historical;
|
|
271
364
|
if (shouldLogTask) {
|
|
272
365
|
console.log(`[${this.name}] <- task.dispatch ${task.taskId} "${task.title}" from=${task.from}`);
|
|
366
|
+
this.emit({
|
|
367
|
+
type: 'task.received',
|
|
368
|
+
bridge: 'cli-bridge',
|
|
369
|
+
agent: this.name,
|
|
370
|
+
email: this.email,
|
|
371
|
+
taskId: task.taskId,
|
|
372
|
+
title: task.title,
|
|
373
|
+
from: task.from,
|
|
374
|
+
});
|
|
273
375
|
}
|
|
274
376
|
if (task.expiresAt && new Date(task.expiresAt).getTime() <= Date.now()) {
|
|
275
377
|
console.warn(`[${this.name}] Skipping expired task ${task.taskId}`);
|
|
@@ -279,6 +381,10 @@ export class AgentBridge {
|
|
|
279
381
|
console.warn(`[${this.name}] Ignoring cancelled task ${task.taskId}`);
|
|
280
382
|
return;
|
|
281
383
|
}
|
|
384
|
+
if (this.activeTaskIds.has(task.taskId)) {
|
|
385
|
+
console.warn(`[${this.name}] Ignoring duplicate active task ${task.taskId}`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
282
388
|
const hydratedTask = await this.client.hydrateTaskDispatch(task).catch((err) => {
|
|
283
389
|
if (!options.historical) {
|
|
284
390
|
console.warn(`[${this.name}] Failed to load thread history for ${task.taskId}: ${err.message}`);
|
|
@@ -305,6 +411,14 @@ export class AgentBridge {
|
|
|
305
411
|
if (options.historical)
|
|
306
412
|
return;
|
|
307
413
|
console.warn(`[${this.name}] Rejecting task ${task.taskId}: ${senderDecision.reason ?? 'sender policy rejected the task'}`);
|
|
414
|
+
this.emit({
|
|
415
|
+
type: 'task.rejected',
|
|
416
|
+
bridge: 'cli-bridge',
|
|
417
|
+
agent: this.name,
|
|
418
|
+
email: this.email,
|
|
419
|
+
taskId: task.taskId,
|
|
420
|
+
reason: senderDecision.reason ?? 'sender policy rejected the task',
|
|
421
|
+
});
|
|
308
422
|
await this.client.sendResult({
|
|
309
423
|
to: task.from,
|
|
310
424
|
taskId: task.taskId,
|
|
@@ -315,6 +429,12 @@ export class AgentBridge {
|
|
|
315
429
|
});
|
|
316
430
|
return;
|
|
317
431
|
}
|
|
432
|
+
const taskLockDir = acquireTaskExecutionLock(task.taskId);
|
|
433
|
+
if (!taskLockDir) {
|
|
434
|
+
console.warn(`[${this.name}] Ignoring duplicate locked task ${task.taskId}`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
this.activeTaskIds.add(task.taskId);
|
|
318
438
|
this.activeTaskCount += 1;
|
|
319
439
|
let activeStream = null;
|
|
320
440
|
let streamOpenedAt = null;
|
|
@@ -328,7 +448,7 @@ export class AgentBridge {
|
|
|
328
448
|
let write;
|
|
329
449
|
write = this.client.appendStreamEvent({
|
|
330
450
|
streamId,
|
|
331
|
-
type,
|
|
451
|
+
type: type,
|
|
332
452
|
payload,
|
|
333
453
|
})
|
|
334
454
|
.then(() => undefined)
|
|
@@ -351,7 +471,6 @@ export class AgentBridge {
|
|
|
351
471
|
if (update.textDelta) {
|
|
352
472
|
queueStreamAppend('text.delta', {
|
|
353
473
|
text: update.textDelta,
|
|
354
|
-
channel: 'assistant',
|
|
355
474
|
sourceEvent: eventType,
|
|
356
475
|
});
|
|
357
476
|
return;
|
|
@@ -359,16 +478,14 @@ export class AgentBridge {
|
|
|
359
478
|
if (update.finalText) {
|
|
360
479
|
queueStreamAppend('text.delta', {
|
|
361
480
|
text: update.finalText,
|
|
362
|
-
channel: 'assistant',
|
|
363
481
|
sourceEvent: eventType,
|
|
364
482
|
});
|
|
365
483
|
return;
|
|
366
484
|
}
|
|
367
485
|
if (eventType === 'session') {
|
|
368
|
-
queueStreamAppend('
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
data,
|
|
486
|
+
queueStreamAppend('todo', {
|
|
487
|
+
items: [{ id: 'cli-session', content: 'CLI session started', status: 'in_progress' }],
|
|
488
|
+
summary: 'CLI session started',
|
|
372
489
|
});
|
|
373
490
|
return;
|
|
374
491
|
}
|
|
@@ -376,10 +493,13 @@ export class AgentBridge {
|
|
|
376
493
|
const record = data && typeof data === 'object' && !Array.isArray(data)
|
|
377
494
|
? data
|
|
378
495
|
: {};
|
|
379
|
-
|
|
496
|
+
const name = typeof record.name === 'string' ? record.name : 'tool';
|
|
497
|
+
const toolCallId = String(record.toolCallId ?? record.tool_call_id ?? record.call_id ?? record.id ?? name);
|
|
498
|
+
queueStreamAppend('tool_call', {
|
|
499
|
+
toolCallId,
|
|
380
500
|
label: `Tool running: ${typeof record.name === 'string' ? record.name : 'tool'}`,
|
|
381
|
-
status: '
|
|
382
|
-
|
|
501
|
+
status: 'running',
|
|
502
|
+
input: stringifyStreamPayload(record),
|
|
383
503
|
});
|
|
384
504
|
return;
|
|
385
505
|
}
|
|
@@ -388,10 +508,13 @@ export class AgentBridge {
|
|
|
388
508
|
? data
|
|
389
509
|
: {};
|
|
390
510
|
const failed = record.is_error === true || record.status === 'failed';
|
|
391
|
-
|
|
511
|
+
const name = typeof record.name === 'string' ? record.name : 'tool';
|
|
512
|
+
const toolCallId = String(record.toolCallId ?? record.tool_call_id ?? record.call_id ?? record.id ?? name);
|
|
513
|
+
queueStreamAppend('tool_call', {
|
|
514
|
+
toolCallId,
|
|
392
515
|
label: `Tool ${failed ? 'failed' : 'completed'}: ${typeof record.name === 'string' ? record.name : 'tool'}`,
|
|
393
516
|
status: failed ? 'failed' : 'completed',
|
|
394
|
-
|
|
517
|
+
output: stringifyStreamPayload(record),
|
|
395
518
|
});
|
|
396
519
|
return;
|
|
397
520
|
}
|
|
@@ -400,28 +523,27 @@ export class AgentBridge {
|
|
|
400
523
|
? data
|
|
401
524
|
: {};
|
|
402
525
|
const chunk = typeof record.chunk === 'string' ? record.chunk : undefined;
|
|
403
|
-
|
|
526
|
+
const name = typeof record.name === 'string' ? record.name : 'tool';
|
|
527
|
+
const toolCallId = String(record.toolCallId ?? record.tool_call_id ?? record.call_id ?? record.id ?? name);
|
|
528
|
+
queueStreamAppend('tool_call', {
|
|
529
|
+
toolCallId,
|
|
404
530
|
label: 'Tool output',
|
|
405
|
-
status: '
|
|
406
|
-
|
|
407
|
-
...(chunk ? { chunk } : {}),
|
|
531
|
+
status: 'running',
|
|
532
|
+
output: chunk ? chunk : stringifyStreamPayload(record),
|
|
408
533
|
});
|
|
409
534
|
return;
|
|
410
535
|
}
|
|
411
536
|
if (eventType === 'usage') {
|
|
412
|
-
queueStreamAppend('
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
? data
|
|
416
|
-
: { data }),
|
|
537
|
+
queueStreamAppend('todo', {
|
|
538
|
+
items: [{ id: 'token-usage', content: 'Token usage updated', status: 'in_progress' }],
|
|
539
|
+
summary: 'Token usage updated',
|
|
417
540
|
});
|
|
418
541
|
return;
|
|
419
542
|
}
|
|
420
543
|
if (eventType === 'done') {
|
|
421
|
-
queueStreamAppend('
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
data,
|
|
544
|
+
queueStreamAppend('todo', {
|
|
545
|
+
items: [{ id: 'cli-stream', content: 'CLI stream completed', status: 'completed' }],
|
|
546
|
+
summary: 'CLI stream completed',
|
|
425
547
|
});
|
|
426
548
|
}
|
|
427
549
|
};
|
|
@@ -440,7 +562,10 @@ export class AgentBridge {
|
|
|
440
562
|
streamId: activeStream.streamId,
|
|
441
563
|
inReplyTo: task.messageId,
|
|
442
564
|
});
|
|
443
|
-
queueStreamAppend('
|
|
565
|
+
queueStreamAppend('todo', {
|
|
566
|
+
items: [{ id: 'cli-task', content: 'CLI task started', status: 'in_progress' }],
|
|
567
|
+
summary: 'CLI task started',
|
|
568
|
+
});
|
|
444
569
|
}
|
|
445
570
|
catch (err) {
|
|
446
571
|
activeStream = null;
|
|
@@ -482,6 +607,14 @@ export class AgentBridge {
|
|
|
482
607
|
inReplyTo: task.messageId,
|
|
483
608
|
});
|
|
484
609
|
console.log(`[${this.name}] -> task.help_needed ${task.taskId}`);
|
|
610
|
+
this.emit({
|
|
611
|
+
type: 'task.completed',
|
|
612
|
+
bridge: 'cli-bridge',
|
|
613
|
+
agent: this.name,
|
|
614
|
+
email: this.email,
|
|
615
|
+
taskId: task.taskId,
|
|
616
|
+
status: 'help_needed',
|
|
617
|
+
});
|
|
485
618
|
return;
|
|
486
619
|
}
|
|
487
620
|
const attachments = [];
|
|
@@ -522,6 +655,14 @@ export class AgentBridge {
|
|
|
522
655
|
attachments: attachments.length > 0 ? attachments : undefined,
|
|
523
656
|
});
|
|
524
657
|
console.log(`[${this.name}] -> task.result ${task.taskId} completed${structuredResult?.length ? ` (${structuredResult.length} structured field(s))` : ''}${attachments.length ? ` (${attachments.length} attachment(s))` : ''}`);
|
|
658
|
+
this.emit({
|
|
659
|
+
type: 'task.completed',
|
|
660
|
+
bridge: 'cli-bridge',
|
|
661
|
+
agent: this.name,
|
|
662
|
+
email: this.email,
|
|
663
|
+
taskId: task.taskId,
|
|
664
|
+
status: 'completed',
|
|
665
|
+
});
|
|
525
666
|
}
|
|
526
667
|
catch (err) {
|
|
527
668
|
const errorMsg = err.message;
|
|
@@ -546,9 +687,19 @@ export class AgentBridge {
|
|
|
546
687
|
errorMsg: `CLI agent error: ${errorMsg}`,
|
|
547
688
|
inReplyTo: task.messageId,
|
|
548
689
|
}).catch(() => { });
|
|
690
|
+
this.emit({
|
|
691
|
+
type: 'task.completed',
|
|
692
|
+
bridge: 'cli-bridge',
|
|
693
|
+
agent: this.name,
|
|
694
|
+
email: this.email,
|
|
695
|
+
taskId: task.taskId,
|
|
696
|
+
status: 'rejected',
|
|
697
|
+
});
|
|
549
698
|
}
|
|
550
699
|
finally {
|
|
551
700
|
this.activeTaskCount = Math.max(0, this.activeTaskCount - 1);
|
|
701
|
+
this.activeTaskIds.delete(task.taskId);
|
|
702
|
+
releaseTaskExecutionLock(taskLockDir);
|
|
552
703
|
}
|
|
553
704
|
}
|
|
554
705
|
normalizeEmail(email) {
|
|
@@ -585,6 +736,14 @@ export class AgentBridge {
|
|
|
585
736
|
const shouldLogRequest = !options.historical;
|
|
586
737
|
if (shouldLogRequest) {
|
|
587
738
|
console.log(`[${this.name}] <- pair.request ${request.taskId} from=${request.from}`);
|
|
739
|
+
this.emit({
|
|
740
|
+
type: 'pair.request',
|
|
741
|
+
bridge: 'cli-bridge',
|
|
742
|
+
agent: this.name,
|
|
743
|
+
email: this.email,
|
|
744
|
+
taskId: request.taskId,
|
|
745
|
+
from: request.from,
|
|
746
|
+
});
|
|
588
747
|
}
|
|
589
748
|
const requestTo = this.normalizeEmail(request.to);
|
|
590
749
|
if (requestTo && requestTo !== this.normalizeEmail(this.identity.email)) {
|
|
@@ -624,6 +783,16 @@ export class AgentBridge {
|
|
|
624
783
|
}
|
|
625
784
|
console.warn(`[${this.name}] Rejected pair.request from ${request.from}: ${reason}`);
|
|
626
785
|
await this.sendPairResponse(request, false, reason);
|
|
786
|
+
this.emit({
|
|
787
|
+
type: 'pair.completed',
|
|
788
|
+
bridge: 'cli-bridge',
|
|
789
|
+
agent: this.name,
|
|
790
|
+
email: this.email,
|
|
791
|
+
taskId: request.taskId,
|
|
792
|
+
sender: request.from,
|
|
793
|
+
success: false,
|
|
794
|
+
reason,
|
|
795
|
+
});
|
|
627
796
|
return;
|
|
628
797
|
}
|
|
629
798
|
this.senderPolicies = addSenderPolicy(senderPoliciesFile, {
|
|
@@ -633,6 +802,15 @@ export class AgentBridge {
|
|
|
633
802
|
});
|
|
634
803
|
console.log(`[${this.name}] Paired sender ${request.from}; sender policy saved to ${senderPoliciesFile}`);
|
|
635
804
|
if (await this.sendPairResponse(request, true)) {
|
|
805
|
+
this.emit({
|
|
806
|
+
type: 'pair.completed',
|
|
807
|
+
bridge: 'cli-bridge',
|
|
808
|
+
agent: this.name,
|
|
809
|
+
email: this.email,
|
|
810
|
+
taskId: request.taskId,
|
|
811
|
+
sender: request.from,
|
|
812
|
+
success: true,
|
|
813
|
+
});
|
|
636
814
|
consumePairingCode(pairParams);
|
|
637
815
|
}
|
|
638
816
|
else {
|