agent-relay 1.3.2 → 1.3.3
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 +23 -9
- package/dist/bridge/spawner.js +39 -75
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +251 -30
- package/dist/daemon/agent-manager.js +4 -0
- package/dist/daemon/connection.js +17 -9
- package/dist/daemon/router.js +2 -2
- package/dist/dashboard/out/404.html +1 -0
- package/dist/dashboard/out/_next/static/R-uQOUcOLINtsp6ACeZa9/_buildManifest.js +1 -0
- package/dist/dashboard/out/_next/static/R-uQOUcOLINtsp6ACeZa9/_ssgManifest.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +2 -0
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
- package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8553743baca53a00.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/page-7f64824ae7d06707.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-3538dfe0ffe984b8.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/history/page-abb9ab2d329f56e9.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-f829604fb75a831a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/page-814efc4d77b4191d.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-b08ed1c34d14434a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-84322991d7244499.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-05606941a8e2be83.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-68d34f50baa8ab6b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
- package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/main-5a40a5ae29646e1b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
- package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
- package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +1 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
- package/dist/dashboard/out/alt-logos/logo.svg +38 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
- package/dist/dashboard/out/app/onboarding.html +1 -0
- package/dist/dashboard/out/app/onboarding.txt +7 -0
- package/dist/dashboard/out/app.html +1 -0
- package/dist/dashboard/out/app.txt +7 -0
- package/dist/dashboard/out/apple-icon.png +0 -0
- package/dist/dashboard/out/cloud/link.html +1 -0
- package/dist/dashboard/out/cloud/link.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -0
- package/dist/dashboard/out/connect-repos.txt +7 -0
- package/dist/dashboard/out/history.html +1 -0
- package/dist/dashboard/out/history.txt +7 -0
- package/dist/dashboard/out/index.html +1 -0
- package/dist/dashboard/out/index.txt +7 -0
- package/dist/dashboard/out/login.html +5 -0
- package/dist/dashboard/out/login.txt +7 -0
- package/dist/dashboard/out/metrics.html +1 -0
- package/dist/dashboard/out/metrics.txt +7 -0
- package/dist/dashboard/out/pricing.html +13 -0
- package/dist/dashboard/out/pricing.txt +7 -0
- package/dist/dashboard/out/providers/setup/claude.html +1 -0
- package/dist/dashboard/out/providers/setup/claude.txt +8 -0
- package/dist/dashboard/out/providers/setup/codex.html +1 -0
- package/dist/dashboard/out/providers/setup/codex.txt +8 -0
- package/dist/dashboard/out/providers.html +1 -0
- package/dist/dashboard/out/providers.txt +7 -0
- package/dist/dashboard/out/signup.html +6 -0
- package/dist/dashboard/out/signup.txt +7 -0
- package/dist/dashboard-server/metrics.d.ts +105 -0
- package/dist/dashboard-server/metrics.js +193 -0
- package/dist/dashboard-server/needs-attention.d.ts +24 -0
- package/dist/dashboard-server/needs-attention.js +78 -0
- package/dist/dashboard-server/server.d.ts +15 -0
- package/dist/dashboard-server/server.js +3992 -0
- package/dist/dashboard-server/start.d.ts +6 -0
- package/dist/dashboard-server/start.js +13 -0
- package/dist/dashboard-server/user-bridge.d.ts +103 -0
- package/dist/dashboard-server/user-bridge.js +189 -0
- package/dist/wrapper/base-wrapper.d.ts +4 -0
- package/dist/wrapper/base-wrapper.js +12 -4
- package/dist/wrapper/client.js +5 -0
- package/dist/wrapper/parser.js +2 -2
- package/dist/wrapper/pty-wrapper.d.ts +7 -1
- package/dist/wrapper/pty-wrapper.js +81 -5
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +8 -1
- package/dist/wrapper/tmux-wrapper.js +103 -28
- package/package.json +5 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone dashboard starter for local development
|
|
4
|
+
*/
|
|
5
|
+
import { startDashboard } from './server.js';
|
|
6
|
+
import { getProjectPaths } from '../utils/project-namespace.js';
|
|
7
|
+
const port = parseInt(process.env.DASHBOARD_PORT || '3888', 10);
|
|
8
|
+
const paths = getProjectPaths();
|
|
9
|
+
console.log(`Starting dashboard for project: ${paths.projectRoot}`);
|
|
10
|
+
console.log(`Data dir: ${paths.dataDir}`);
|
|
11
|
+
console.log(`Database: ${paths.dbPath}`);
|
|
12
|
+
startDashboard(port, paths.dataDir, paths.teamDir, paths.dbPath).catch(console.error);
|
|
13
|
+
//# sourceMappingURL=start.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Bridge - Bridges dashboard WebSocket users to the relay daemon.
|
|
3
|
+
*
|
|
4
|
+
* This module allows human users connected via WebSocket to:
|
|
5
|
+
* - Register as "user" entities in the relay daemon
|
|
6
|
+
* - Join/leave channels
|
|
7
|
+
* - Send/receive messages through the relay daemon
|
|
8
|
+
* - Communicate with agents and other users
|
|
9
|
+
*/
|
|
10
|
+
import type { WebSocket } from 'ws';
|
|
11
|
+
/**
|
|
12
|
+
* Relay client interface (subset of RelayClient for dependency injection)
|
|
13
|
+
*/
|
|
14
|
+
export interface IRelayClient {
|
|
15
|
+
connect(): Promise<void>;
|
|
16
|
+
disconnect(): void;
|
|
17
|
+
state: string;
|
|
18
|
+
sendMessage(to: string, body: string, kind?: string, data?: unknown, thread?: string): boolean;
|
|
19
|
+
onMessage?: (from: string, payload: any, messageId: string, meta?: any, originalTo?: string) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Factory function type for creating relay clients
|
|
23
|
+
*/
|
|
24
|
+
export type RelayClientFactory = (options: {
|
|
25
|
+
socketPath: string;
|
|
26
|
+
agentName: string;
|
|
27
|
+
entityType: 'user';
|
|
28
|
+
displayName?: string;
|
|
29
|
+
avatarUrl?: string;
|
|
30
|
+
}) => Promise<IRelayClient>;
|
|
31
|
+
/**
|
|
32
|
+
* Options for creating a UserBridge
|
|
33
|
+
*/
|
|
34
|
+
export interface UserBridgeOptions {
|
|
35
|
+
socketPath: string;
|
|
36
|
+
createRelayClient: RelayClientFactory;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Message options for sending
|
|
40
|
+
*/
|
|
41
|
+
export interface SendMessageOptions {
|
|
42
|
+
thread?: string;
|
|
43
|
+
data?: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* UserBridge manages the connection between dashboard WebSocket users
|
|
47
|
+
* and the relay daemon.
|
|
48
|
+
*/
|
|
49
|
+
export declare class UserBridge {
|
|
50
|
+
private readonly socketPath;
|
|
51
|
+
private readonly createRelayClient;
|
|
52
|
+
private readonly users;
|
|
53
|
+
constructor(options: UserBridgeOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Register a user with the relay daemon.
|
|
56
|
+
* Creates a relay client connection for the user.
|
|
57
|
+
*/
|
|
58
|
+
registerUser(username: string, webSocket: WebSocket, options?: {
|
|
59
|
+
avatarUrl?: string;
|
|
60
|
+
displayName?: string;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Unregister a user and disconnect their relay client.
|
|
64
|
+
*/
|
|
65
|
+
unregisterUser(username: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Check if a user is registered.
|
|
68
|
+
*/
|
|
69
|
+
isUserRegistered(username: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Get list of all registered users.
|
|
72
|
+
*/
|
|
73
|
+
getRegisteredUsers(): string[];
|
|
74
|
+
/**
|
|
75
|
+
* Join a channel.
|
|
76
|
+
*/
|
|
77
|
+
joinChannel(username: string, channel: string): Promise<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* Leave a channel.
|
|
80
|
+
*/
|
|
81
|
+
leaveChannel(username: string, channel: string): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Get channels a user has joined.
|
|
84
|
+
*/
|
|
85
|
+
getUserChannels(username: string): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Send a message to a channel.
|
|
88
|
+
*/
|
|
89
|
+
sendChannelMessage(username: string, channel: string, body: string, options?: SendMessageOptions): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* Send a direct message to another user or agent.
|
|
92
|
+
*/
|
|
93
|
+
sendDirectMessage(fromUsername: string, toName: string, body: string, options?: SendMessageOptions): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Handle incoming message from relay daemon.
|
|
96
|
+
*/
|
|
97
|
+
private handleIncomingMessage;
|
|
98
|
+
/**
|
|
99
|
+
* Dispose of all user sessions.
|
|
100
|
+
*/
|
|
101
|
+
dispose(): void;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=user-bridge.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Bridge - Bridges dashboard WebSocket users to the relay daemon.
|
|
3
|
+
*
|
|
4
|
+
* This module allows human users connected via WebSocket to:
|
|
5
|
+
* - Register as "user" entities in the relay daemon
|
|
6
|
+
* - Join/leave channels
|
|
7
|
+
* - Send/receive messages through the relay daemon
|
|
8
|
+
* - Communicate with agents and other users
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* UserBridge manages the connection between dashboard WebSocket users
|
|
12
|
+
* and the relay daemon.
|
|
13
|
+
*/
|
|
14
|
+
export class UserBridge {
|
|
15
|
+
socketPath;
|
|
16
|
+
createRelayClient;
|
|
17
|
+
users = new Map();
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.socketPath = options.socketPath;
|
|
20
|
+
this.createRelayClient = options.createRelayClient;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register a user with the relay daemon.
|
|
24
|
+
* Creates a relay client connection for the user.
|
|
25
|
+
*/
|
|
26
|
+
async registerUser(username, webSocket, options) {
|
|
27
|
+
// If user already registered, unregister first
|
|
28
|
+
if (this.users.has(username)) {
|
|
29
|
+
this.unregisterUser(username);
|
|
30
|
+
}
|
|
31
|
+
// Create relay client for this user
|
|
32
|
+
const relayClient = await this.createRelayClient({
|
|
33
|
+
socketPath: this.socketPath,
|
|
34
|
+
agentName: username,
|
|
35
|
+
entityType: 'user',
|
|
36
|
+
displayName: options?.displayName,
|
|
37
|
+
avatarUrl: options?.avatarUrl,
|
|
38
|
+
});
|
|
39
|
+
// Connect to daemon
|
|
40
|
+
await relayClient.connect();
|
|
41
|
+
// Set up message handler to forward messages to WebSocket
|
|
42
|
+
relayClient.onMessage = (from, payload, _messageId, _meta, _originalTo) => {
|
|
43
|
+
const body = typeof payload === 'object' && payload !== null && 'body' in payload
|
|
44
|
+
? payload.body
|
|
45
|
+
: String(payload);
|
|
46
|
+
this.handleIncomingMessage(username, from, body, payload);
|
|
47
|
+
};
|
|
48
|
+
// Create session
|
|
49
|
+
const session = {
|
|
50
|
+
username,
|
|
51
|
+
relayClient,
|
|
52
|
+
webSocket,
|
|
53
|
+
channels: new Set(),
|
|
54
|
+
avatarUrl: options?.avatarUrl,
|
|
55
|
+
};
|
|
56
|
+
this.users.set(username, session);
|
|
57
|
+
// Set up WebSocket close handler
|
|
58
|
+
webSocket.on('close', () => {
|
|
59
|
+
this.unregisterUser(username);
|
|
60
|
+
});
|
|
61
|
+
console.log(`[user-bridge] User ${username} registered with relay daemon`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Unregister a user and disconnect their relay client.
|
|
65
|
+
*/
|
|
66
|
+
unregisterUser(username) {
|
|
67
|
+
const session = this.users.get(username);
|
|
68
|
+
if (!session)
|
|
69
|
+
return;
|
|
70
|
+
session.relayClient.disconnect();
|
|
71
|
+
this.users.delete(username);
|
|
72
|
+
console.log(`[user-bridge] User ${username} unregistered from relay daemon`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if a user is registered.
|
|
76
|
+
*/
|
|
77
|
+
isUserRegistered(username) {
|
|
78
|
+
return this.users.has(username);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get list of all registered users.
|
|
82
|
+
*/
|
|
83
|
+
getRegisteredUsers() {
|
|
84
|
+
return Array.from(this.users.keys());
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Join a channel.
|
|
88
|
+
*/
|
|
89
|
+
async joinChannel(username, channel) {
|
|
90
|
+
const session = this.users.get(username);
|
|
91
|
+
if (!session) {
|
|
92
|
+
console.warn(`[user-bridge] Cannot join channel - user ${username} not registered`);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
// Send channel join via relay client
|
|
96
|
+
session.relayClient.sendMessage(channel, '', 'channel_join');
|
|
97
|
+
// Track membership
|
|
98
|
+
session.channels.add(channel);
|
|
99
|
+
console.log(`[user-bridge] User ${username} joined channel ${channel}`);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Leave a channel.
|
|
104
|
+
*/
|
|
105
|
+
async leaveChannel(username, channel) {
|
|
106
|
+
const session = this.users.get(username);
|
|
107
|
+
if (!session) {
|
|
108
|
+
console.warn(`[user-bridge] Cannot leave channel - user ${username} not registered`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
// Send channel leave via relay client
|
|
112
|
+
session.relayClient.sendMessage(channel, '', 'channel_leave');
|
|
113
|
+
// Update membership
|
|
114
|
+
session.channels.delete(channel);
|
|
115
|
+
console.log(`[user-bridge] User ${username} left channel ${channel}`);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get channels a user has joined.
|
|
120
|
+
*/
|
|
121
|
+
getUserChannels(username) {
|
|
122
|
+
const session = this.users.get(username);
|
|
123
|
+
return session ? Array.from(session.channels) : [];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Send a message to a channel.
|
|
127
|
+
*/
|
|
128
|
+
async sendChannelMessage(username, channel, body, options) {
|
|
129
|
+
const session = this.users.get(username);
|
|
130
|
+
if (!session) {
|
|
131
|
+
console.warn(`[user-bridge] Cannot send - user ${username} not registered`);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return session.relayClient.sendMessage(channel, body, 'message', options?.data, options?.thread);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Send a direct message to another user or agent.
|
|
138
|
+
*/
|
|
139
|
+
async sendDirectMessage(fromUsername, toName, body, options) {
|
|
140
|
+
const session = this.users.get(fromUsername);
|
|
141
|
+
if (!session) {
|
|
142
|
+
console.warn(`[user-bridge] Cannot send DM - user ${fromUsername} not registered`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return session.relayClient.sendMessage(toName, body, 'message', options?.data, options?.thread);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Handle incoming message from relay daemon.
|
|
149
|
+
*/
|
|
150
|
+
handleIncomingMessage(username, from, body, envelope) {
|
|
151
|
+
const session = this.users.get(username);
|
|
152
|
+
if (!session)
|
|
153
|
+
return;
|
|
154
|
+
const ws = session.webSocket;
|
|
155
|
+
if (ws.readyState !== 1)
|
|
156
|
+
return; // Not OPEN
|
|
157
|
+
// Determine message type from envelope
|
|
158
|
+
const env = envelope;
|
|
159
|
+
if (env.type === 'CHANNEL_MESSAGE') {
|
|
160
|
+
// Channel message
|
|
161
|
+
ws.send(JSON.stringify({
|
|
162
|
+
type: 'channel_message',
|
|
163
|
+
channel: env.payload?.channel,
|
|
164
|
+
from,
|
|
165
|
+
body: env.payload?.body || body,
|
|
166
|
+
timestamp: new Date().toISOString(),
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Direct message (DELIVER)
|
|
171
|
+
ws.send(JSON.stringify({
|
|
172
|
+
type: 'direct_message',
|
|
173
|
+
from,
|
|
174
|
+
body: env.payload?.body || body,
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Dispose of all user sessions.
|
|
181
|
+
*/
|
|
182
|
+
dispose() {
|
|
183
|
+
for (const [username] of this.users) {
|
|
184
|
+
this.unregisterUser(username);
|
|
185
|
+
}
|
|
186
|
+
console.log('[user-bridge] Disposed all user sessions');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=user-bridge.js.map
|
|
@@ -60,6 +60,10 @@ export interface BaseWrapperConfig {
|
|
|
60
60
|
idleBeforeInjectMs?: number;
|
|
61
61
|
/** Confidence threshold for idle detection (0-1, default: 0.7) */
|
|
62
62
|
idleConfidenceThreshold?: number;
|
|
63
|
+
/** Skip initial instruction injection (when using --append-system-prompt) */
|
|
64
|
+
skipInstructions?: boolean;
|
|
65
|
+
/** Skip continuity loading (for spawned agents that don't need session recovery) */
|
|
66
|
+
skipContinuity?: boolean;
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
69
|
* Abstract base class for agent wrappers
|
|
@@ -63,8 +63,10 @@ export class BaseWrapper extends EventEmitter {
|
|
|
63
63
|
workingDirectory: config.cwd,
|
|
64
64
|
quiet: true,
|
|
65
65
|
});
|
|
66
|
-
// Initialize continuity manager
|
|
67
|
-
|
|
66
|
+
// Initialize continuity manager (skip for spawned agents that don't need session recovery)
|
|
67
|
+
if (!config.skipContinuity) {
|
|
68
|
+
this.continuity = getContinuityManager({ defaultCli: this.cliType });
|
|
69
|
+
}
|
|
68
70
|
// Initialize universal idle detector for robust injection timing
|
|
69
71
|
this.idleDetector = new UniversalIdleDetector({
|
|
70
72
|
minSilenceMs: config.idleBeforeInjectMs ?? DEFAULT_IDLE_BEFORE_INJECT_MS,
|
|
@@ -186,9 +188,15 @@ export class BaseWrapper extends EventEmitter {
|
|
|
186
188
|
this.sentMessageHashes.delete(oldest);
|
|
187
189
|
}
|
|
188
190
|
// Only send if client ready
|
|
189
|
-
if (this.client.state !== 'READY')
|
|
191
|
+
if (this.client.state !== 'READY') {
|
|
192
|
+
console.error(`[base-wrapper] Skipped message to ${cmd.to} - client not ready (state: ${this.client.state})`);
|
|
190
193
|
return;
|
|
191
|
-
|
|
194
|
+
}
|
|
195
|
+
console.log(`[base-wrapper] Sending message to ${cmd.to}: "${cmd.body.substring(0, 50)}..."`);
|
|
196
|
+
const sent = this.client.sendMessage(cmd.to, cmd.body, cmd.kind, cmd.data, cmd.thread);
|
|
197
|
+
if (!sent) {
|
|
198
|
+
console.error(`[base-wrapper] Failed to send message to ${cmd.to} - sendMessage returned false`);
|
|
199
|
+
}
|
|
192
200
|
}
|
|
193
201
|
// =========================================================================
|
|
194
202
|
// Spawn/release handling
|
package/dist/wrapper/client.js
CHANGED
|
@@ -417,6 +417,7 @@ export class RelayClient {
|
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
handleDeliver(envelope) {
|
|
420
|
+
console.log(`[relay-client:${this.config.agentName}] Received DELIVER from ${envelope.from}: "${envelope.payload.body?.substring(0, 40)}..."`);
|
|
420
421
|
// Send ACK
|
|
421
422
|
this.send({
|
|
422
423
|
v: PROTOCOL_VERSION,
|
|
@@ -430,6 +431,7 @@ export class RelayClient {
|
|
|
430
431
|
});
|
|
431
432
|
const duplicate = this.markDelivered(envelope.id);
|
|
432
433
|
if (duplicate) {
|
|
434
|
+
console.log(`[relay-client:${this.config.agentName}] Duplicate delivery, skipping`);
|
|
433
435
|
return;
|
|
434
436
|
}
|
|
435
437
|
// Notify handler
|
|
@@ -437,6 +439,9 @@ export class RelayClient {
|
|
|
437
439
|
if (this.onMessage && envelope.from) {
|
|
438
440
|
this.onMessage(envelope.from, envelope.payload, envelope.id, envelope.payload_meta, envelope.delivery.originalTo);
|
|
439
441
|
}
|
|
442
|
+
else {
|
|
443
|
+
console.log(`[relay-client:${this.config.agentName}] No onMessage handler or no from field`);
|
|
444
|
+
}
|
|
440
445
|
}
|
|
441
446
|
handlePing(envelope) {
|
|
442
447
|
this.send({
|
package/dist/wrapper/parser.js
CHANGED
|
@@ -811,12 +811,12 @@ export class OutputParser {
|
|
|
811
811
|
shouldFilterFencedInline(target, body) {
|
|
812
812
|
// Check for placeholder target names
|
|
813
813
|
if (isPlaceholderTarget(target)) {
|
|
814
|
-
|
|
814
|
+
// Silently filter placeholder targets (common in documentation)
|
|
815
815
|
return true;
|
|
816
816
|
}
|
|
817
817
|
// Check for instructional body content
|
|
818
818
|
if (isInstructionalText(body)) {
|
|
819
|
-
|
|
819
|
+
// Silently filter instructional text (common in system prompts)
|
|
820
820
|
return true;
|
|
821
821
|
}
|
|
822
822
|
return false;
|
|
@@ -230,9 +230,15 @@ export declare class PtyWrapper extends BaseWrapper {
|
|
|
230
230
|
protected parseSpawnReleaseCommands(content: string): void;
|
|
231
231
|
/**
|
|
232
232
|
* Execute spawn via API or callback.
|
|
233
|
-
*
|
|
233
|
+
* After spawning, waits for the agent to come online and sends the task via relay.
|
|
234
234
|
*/
|
|
235
235
|
protected executeSpawn(name: string, cli: string, task: string): Promise<void>;
|
|
236
|
+
/**
|
|
237
|
+
* Wait for a spawned agent to come online, then send the task via relay.
|
|
238
|
+
* Uses the wrapper's own relay client so the message comes "from" this agent,
|
|
239
|
+
* not from the dashboard's relay client.
|
|
240
|
+
*/
|
|
241
|
+
private waitAndSendTask;
|
|
236
242
|
/**
|
|
237
243
|
* Execute release via API or callback.
|
|
238
244
|
* Overrides BaseWrapper to add PTY-specific logging and API path.
|
|
@@ -220,6 +220,7 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
220
220
|
this.injectInstructions();
|
|
221
221
|
}
|
|
222
222
|
this.readyForMessages = true;
|
|
223
|
+
console.log(`[pty:${this.config.name}] Agent ready for messages (queueLen=${this.messageQueue.length}, interactive=${this.config.interactive})`);
|
|
223
224
|
// Process any messages that arrived while waiting (skip in interactive mode)
|
|
224
225
|
if (!this.config.interactive) {
|
|
225
226
|
this.processMessageQueue();
|
|
@@ -228,6 +229,7 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
228
229
|
console.error(`[pty:${this.config.name}] Failed to wait for agent ready:`, err);
|
|
229
230
|
// Fall back to marking ready anyway to avoid blocking forever
|
|
230
231
|
this.readyForMessages = true;
|
|
232
|
+
console.log(`[pty:${this.config.name}] Agent ready for messages (fallback, queueLen=${this.messageQueue.length})`);
|
|
231
233
|
});
|
|
232
234
|
}
|
|
233
235
|
/**
|
|
@@ -608,6 +610,14 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
608
610
|
// 500 chars is enough to capture most relay message headers
|
|
609
611
|
const lookbackStart = Math.max(0, this.lastParsedLength - 500);
|
|
610
612
|
const contentToParse = cleanContent.substring(lookbackStart);
|
|
613
|
+
// Debug: Check if content contains relay pattern
|
|
614
|
+
if (contentToParse.includes('->relay:')) {
|
|
615
|
+
const relayLines = contentToParse.split('\n').filter(l => l.includes('->relay:'));
|
|
616
|
+
console.log(`[pty:${this.config.name}] [RELAY-DEBUG] Found ${relayLines.length} lines with ->relay: pattern`);
|
|
617
|
+
relayLines.slice(0, 3).forEach((line, i) => {
|
|
618
|
+
console.log(`[pty:${this.config.name}] [RELAY-DEBUG] Line ${i}: "${line.substring(0, 80)}..."`);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
611
621
|
// First, try to find fenced multi-line messages: ->relay:Target <<<\n...\n>>>
|
|
612
622
|
this.parseFencedMessages(contentToParse);
|
|
613
623
|
// Then parse single-line messages
|
|
@@ -985,22 +995,24 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
985
995
|
}
|
|
986
996
|
/**
|
|
987
997
|
* Execute spawn via API or callback.
|
|
988
|
-
*
|
|
998
|
+
* After spawning, waits for the agent to come online and sends the task via relay.
|
|
989
999
|
*/
|
|
990
1000
|
async executeSpawn(name, cli, task) {
|
|
991
1001
|
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] executeSpawn called: name=${name}, cli=${cli}, task="${task.substring(0, 50)}..."`);
|
|
992
1002
|
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] dashboardPort=${this.config.dashboardPort}, hasOnSpawn=${!!this.config.onSpawn}`);
|
|
1003
|
+
let spawned = false;
|
|
993
1004
|
if (this.config.dashboardPort) {
|
|
994
1005
|
// Use dashboard API for spawning (works from spawned agents)
|
|
995
1006
|
try {
|
|
996
1007
|
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawn`, {
|
|
997
1008
|
method: 'POST',
|
|
998
1009
|
headers: { 'Content-Type': 'application/json' },
|
|
999
|
-
body: JSON.stringify({ name, cli, task
|
|
1010
|
+
body: JSON.stringify({ name, cli }), // No task - we send it after agent is online
|
|
1000
1011
|
});
|
|
1001
1012
|
const result = await response.json();
|
|
1002
1013
|
if (result.success) {
|
|
1003
1014
|
console.log(`[pty:${this.config.name}] Spawned ${name} via API`);
|
|
1015
|
+
spawned = true;
|
|
1004
1016
|
}
|
|
1005
1017
|
else {
|
|
1006
1018
|
console.error(`[pty:${this.config.name}] Spawn failed: ${result.error}`);
|
|
@@ -1014,11 +1026,57 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1014
1026
|
// Fall back to callback
|
|
1015
1027
|
try {
|
|
1016
1028
|
await this.config.onSpawn(name, cli, task);
|
|
1029
|
+
spawned = true;
|
|
1017
1030
|
}
|
|
1018
1031
|
catch (err) {
|
|
1019
1032
|
console.error(`[pty:${this.config.name}] Spawn failed: ${err.message}`);
|
|
1020
1033
|
}
|
|
1021
1034
|
}
|
|
1035
|
+
// If spawn succeeded and we have a task, wait for agent to come online and send it
|
|
1036
|
+
if (spawned && task && task.trim() && this.config.dashboardPort) {
|
|
1037
|
+
await this.waitAndSendTask(name, task);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Wait for a spawned agent to come online, then send the task via relay.
|
|
1042
|
+
* Uses the wrapper's own relay client so the message comes "from" this agent,
|
|
1043
|
+
* not from the dashboard's relay client.
|
|
1044
|
+
*/
|
|
1045
|
+
async waitAndSendTask(agentName, task) {
|
|
1046
|
+
const maxWaitMs = 30000;
|
|
1047
|
+
const pollIntervalMs = 500;
|
|
1048
|
+
const startTime = Date.now();
|
|
1049
|
+
console.log(`[pty:${this.config.name}] Waiting for ${agentName} to come online...`);
|
|
1050
|
+
// Poll for agent to be online using dedicated status endpoint
|
|
1051
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1052
|
+
try {
|
|
1053
|
+
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/agents/${encodeURIComponent(agentName)}/online`);
|
|
1054
|
+
const data = await response.json();
|
|
1055
|
+
if (data.online) {
|
|
1056
|
+
console.log(`[pty:${this.config.name}] ${agentName} is online, sending task...`);
|
|
1057
|
+
// Send task directly via our relay client (not dashboard API)
|
|
1058
|
+
// This ensures the message comes "from" this agent, not from _DashboardUI
|
|
1059
|
+
if (this.client.state === 'READY') {
|
|
1060
|
+
const sent = this.client.sendMessage(agentName, task, 'message');
|
|
1061
|
+
if (sent) {
|
|
1062
|
+
console.log(`[pty:${this.config.name}] Task sent to ${agentName}`);
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
console.error(`[pty:${this.config.name}] Failed to send task to ${agentName}: sendMessage returned false`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
console.error(`[pty:${this.config.name}] Failed to send task to ${agentName}: relay client not ready (state: ${this.client.state})`);
|
|
1070
|
+
}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
catch (err) {
|
|
1075
|
+
// Ignore poll errors, keep trying
|
|
1076
|
+
}
|
|
1077
|
+
await sleep(pollIntervalMs);
|
|
1078
|
+
}
|
|
1079
|
+
console.error(`[pty:${this.config.name}] Timeout waiting for ${agentName} to come online`);
|
|
1022
1080
|
}
|
|
1023
1081
|
/**
|
|
1024
1082
|
* Execute release via API or callback.
|
|
@@ -1058,6 +1116,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1058
1116
|
* Extends BaseWrapper to add PTY-specific behavior.
|
|
1059
1117
|
*/
|
|
1060
1118
|
handleIncomingMessage(from, payload, messageId, meta, originalTo) {
|
|
1119
|
+
const bodyPreview = payload.body.substring(0, 50).replace(/\n/g, '\\n');
|
|
1120
|
+
console.log(`[pty:${this.config.name}] Message received from ${from}: "${bodyPreview}..." (readyForMessages=${this.readyForMessages}, queueLen=${this.messageQueue.length})`);
|
|
1061
1121
|
// Call base class to handle deduplication and queuing
|
|
1062
1122
|
super.handleIncomingMessage(from, payload, messageId, meta, originalTo);
|
|
1063
1123
|
// PTY-specific: Process the message queue immediately
|
|
@@ -1114,6 +1174,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1114
1174
|
this.isInjecting = false;
|
|
1115
1175
|
return;
|
|
1116
1176
|
}
|
|
1177
|
+
const bodyPreview = msg.body.substring(0, 50).replace(/\n/g, '\\n');
|
|
1178
|
+
console.log(`[pty:${this.config.name}] Processing message from ${msg.from}: "${bodyPreview}..." (remaining=${this.messageQueue.length})`);
|
|
1117
1179
|
try {
|
|
1118
1180
|
// Wait for output to stabilize before injecting
|
|
1119
1181
|
await this.waitForOutputStable();
|
|
@@ -1152,9 +1214,21 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1152
1214
|
if (!this.ptyProcess || !this.running) {
|
|
1153
1215
|
throw new Error('PTY process not running');
|
|
1154
1216
|
}
|
|
1155
|
-
//
|
|
1156
|
-
|
|
1157
|
-
|
|
1217
|
+
// Use bracketed paste mode for CLIs that support it (claude, codex, gemini)
|
|
1218
|
+
// This prevents interleaving with CLI output and ensures clean input
|
|
1219
|
+
const useBracketedPaste = this.cliType === 'claude' || this.cliType === 'codex' || this.cliType === 'gemini';
|
|
1220
|
+
if (useBracketedPaste) {
|
|
1221
|
+
// Bracketed paste: \x1b[200~ starts paste, \x1b[201~ ends paste
|
|
1222
|
+
this.ptyProcess.write('\x1b[200~' + inj + '\x1b[201~');
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
this.ptyProcess.write(inj);
|
|
1226
|
+
}
|
|
1227
|
+
// Wait longer for CLI to process the pasted content before sending Enter.
|
|
1228
|
+
// The standard 50ms delay is too short for CLIs like Claude that need time
|
|
1229
|
+
// to process bracketed paste content before accepting Enter.
|
|
1230
|
+
await sleep(200);
|
|
1231
|
+
// Send Enter key - use \r for PTY (carriage return)
|
|
1158
1232
|
this.ptyProcess.write('\r');
|
|
1159
1233
|
},
|
|
1160
1234
|
log: (message) => console.log(`[pty:${this.config.name}] ${message}`),
|
|
@@ -1199,6 +1273,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1199
1273
|
injectInstructions() {
|
|
1200
1274
|
if (!this.running)
|
|
1201
1275
|
return;
|
|
1276
|
+
if (this.config.skipInstructions)
|
|
1277
|
+
return;
|
|
1202
1278
|
// Guard: Only inject once per session
|
|
1203
1279
|
if (this.instructionsInjected) {
|
|
1204
1280
|
console.log(`[pty:${this.config.name}] Init instructions already injected, skipping`);
|
package/dist/wrapper/shared.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare const INJECTION_CONSTANTS: {
|
|
|
53
53
|
/** Timeout for injection verification (ms) */
|
|
54
54
|
readonly VERIFICATION_TIMEOUT_MS: 2000;
|
|
55
55
|
/** Delay between message and Enter key (ms) */
|
|
56
|
-
readonly ENTER_DELAY_MS:
|
|
56
|
+
readonly ENTER_DELAY_MS: 100;
|
|
57
57
|
/** Backoff multiplier for retries (ms per attempt) */
|
|
58
58
|
readonly RETRY_BACKOFF_MS: 300;
|
|
59
59
|
/** Delay between processing queued messages (ms) */
|
package/dist/wrapper/shared.js
CHANGED
|
@@ -19,7 +19,7 @@ export const INJECTION_CONSTANTS = {
|
|
|
19
19
|
/** Timeout for injection verification (ms) */
|
|
20
20
|
VERIFICATION_TIMEOUT_MS: 2000,
|
|
21
21
|
/** Delay between message and Enter key (ms) */
|
|
22
|
-
ENTER_DELAY_MS:
|
|
22
|
+
ENTER_DELAY_MS: 100,
|
|
23
23
|
/** Backoff multiplier for retries (ms per attempt) */
|
|
24
24
|
RETRY_BACKOFF_MS: 300,
|
|
25
25
|
/** Delay between processing queued messages (ms) */
|
|
@@ -232,9 +232,16 @@ export declare class TmuxWrapper extends BaseWrapper {
|
|
|
232
232
|
*/
|
|
233
233
|
private parseSessionEndAndClose;
|
|
234
234
|
/**
|
|
235
|
-
* Execute spawn via API (if dashboardPort set) or callback
|
|
235
|
+
* Execute spawn via API (if dashboardPort set) or callback.
|
|
236
|
+
* After spawning, waits for the agent to come online and sends the task via relay.
|
|
236
237
|
*/
|
|
237
238
|
protected executeSpawn(name: string, cli: string, task: string): Promise<void>;
|
|
239
|
+
/**
|
|
240
|
+
* Wait for a spawned agent to come online, then send the task via relay.
|
|
241
|
+
* Uses the wrapper's own relay client so the message comes "from" this agent,
|
|
242
|
+
* not from the dashboard's relay client.
|
|
243
|
+
*/
|
|
244
|
+
private waitAndSendTask;
|
|
238
245
|
/**
|
|
239
246
|
* Execute release via API (if dashboardPort set) or callback
|
|
240
247
|
*/
|