agent-relay 1.2.3 → 1.3.1

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.
Files changed (189) hide show
  1. package/.trajectories/agent-relay-322-324.md +17 -0
  2. package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.json +49 -0
  3. package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.md +31 -0
  4. package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.json +125 -0
  5. package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.md +62 -0
  6. package/.trajectories/completed/2026-01/traj_33iuy72sezbk.json +49 -0
  7. package/.trajectories/completed/2026-01/traj_33iuy72sezbk.md +31 -0
  8. package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.json +77 -0
  9. package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.md +42 -0
  10. package/.trajectories/completed/2026-01/traj_6mieijqyvaag.json +77 -0
  11. package/.trajectories/completed/2026-01/traj_6mieijqyvaag.md +42 -0
  12. package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.json +77 -0
  13. package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.md +42 -0
  14. package/.trajectories/completed/2026-01/traj_94gnp3k30goq.json +66 -0
  15. package/.trajectories/completed/2026-01/traj_94gnp3k30goq.md +36 -0
  16. package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.json +40 -0
  17. package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.md +22 -0
  18. package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.json +121 -0
  19. package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.md +29 -0
  20. package/.trajectories/completed/2026-01/traj_fhx9irlckht6.json +53 -0
  21. package/.trajectories/completed/2026-01/traj_fhx9irlckht6.md +32 -0
  22. package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.json +101 -0
  23. package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.md +52 -0
  24. package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.json +49 -0
  25. package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.md +31 -0
  26. package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.json +65 -0
  27. package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.md +37 -0
  28. package/.trajectories/completed/2026-01/traj_lq450ly148uw.json +49 -0
  29. package/.trajectories/completed/2026-01/traj_lq450ly148uw.md +31 -0
  30. package/.trajectories/completed/2026-01/traj_multi_server_arch.md +101 -0
  31. package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.json +27 -0
  32. package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.md +14 -0
  33. package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.json +53 -0
  34. package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.md +32 -0
  35. package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.json +186 -0
  36. package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.md +86 -0
  37. package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.json +77 -0
  38. package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.md +42 -0
  39. package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.json +89 -0
  40. package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.md +47 -0
  41. package/.trajectories/completed/2026-01/traj_xy9vifpqet80.json +65 -0
  42. package/.trajectories/completed/2026-01/traj_xy9vifpqet80.md +37 -0
  43. package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.json +49 -0
  44. package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.md +31 -0
  45. package/.trajectories/consolidate-settings-panel.md +24 -0
  46. package/.trajectories/gh-cli-user-token.md +26 -0
  47. package/.trajectories/index.json +155 -1
  48. package/deploy/workspace/codex.config.toml +15 -0
  49. package/deploy/workspace/entrypoint.sh +167 -7
  50. package/deploy/workspace/git-credential-relay +17 -2
  51. package/dist/bridge/spawner.d.ts +7 -0
  52. package/dist/bridge/spawner.js +40 -9
  53. package/dist/bridge/types.d.ts +2 -0
  54. package/dist/cli/index.js +210 -168
  55. package/dist/cloud/api/admin.d.ts +8 -0
  56. package/dist/cloud/api/admin.js +212 -0
  57. package/dist/cloud/api/auth.js +8 -0
  58. package/dist/cloud/api/billing.d.ts +0 -10
  59. package/dist/cloud/api/billing.js +248 -58
  60. package/dist/cloud/api/codex-auth-helper.d.ts +10 -4
  61. package/dist/cloud/api/codex-auth-helper.js +215 -8
  62. package/dist/cloud/api/coordinators.js +402 -0
  63. package/dist/cloud/api/daemons.js +15 -11
  64. package/dist/cloud/api/git.js +104 -17
  65. package/dist/cloud/api/github-app.js +42 -8
  66. package/dist/cloud/api/nango-auth.js +297 -16
  67. package/dist/cloud/api/onboarding.js +97 -33
  68. package/dist/cloud/api/providers.js +12 -16
  69. package/dist/cloud/api/repos.js +200 -124
  70. package/dist/cloud/api/test-helpers.js +40 -0
  71. package/dist/cloud/api/usage.js +13 -0
  72. package/dist/cloud/api/webhooks.js +1 -1
  73. package/dist/cloud/api/workspaces.d.ts +18 -0
  74. package/dist/cloud/api/workspaces.js +945 -15
  75. package/dist/cloud/config.d.ts +8 -0
  76. package/dist/cloud/config.js +15 -0
  77. package/dist/cloud/db/drizzle.d.ts +5 -2
  78. package/dist/cloud/db/drizzle.js +27 -20
  79. package/dist/cloud/db/schema.d.ts +19 -51
  80. package/dist/cloud/db/schema.js +5 -4
  81. package/dist/cloud/index.d.ts +0 -1
  82. package/dist/cloud/index.js +0 -1
  83. package/dist/cloud/provisioner/index.d.ts +93 -1
  84. package/dist/cloud/provisioner/index.js +608 -63
  85. package/dist/cloud/server.js +156 -16
  86. package/dist/cloud/services/compute-enforcement.d.ts +57 -0
  87. package/dist/cloud/services/compute-enforcement.js +175 -0
  88. package/dist/cloud/services/index.d.ts +2 -0
  89. package/dist/cloud/services/index.js +4 -0
  90. package/dist/cloud/services/intro-expiration.d.ts +55 -0
  91. package/dist/cloud/services/intro-expiration.js +211 -0
  92. package/dist/cloud/services/nango.d.ts +14 -0
  93. package/dist/cloud/services/nango.js +74 -14
  94. package/dist/cloud/services/ssh-security.d.ts +31 -0
  95. package/dist/cloud/services/ssh-security.js +63 -0
  96. package/dist/continuity/manager.d.ts +5 -0
  97. package/dist/continuity/manager.js +56 -2
  98. package/dist/daemon/api.d.ts +2 -0
  99. package/dist/daemon/api.js +214 -5
  100. package/dist/daemon/cli-auth.d.ts +13 -1
  101. package/dist/daemon/cli-auth.js +166 -47
  102. package/dist/daemon/connection.d.ts +7 -1
  103. package/dist/daemon/connection.js +15 -0
  104. package/dist/daemon/orchestrator.d.ts +2 -0
  105. package/dist/daemon/orchestrator.js +26 -0
  106. package/dist/daemon/repo-manager.d.ts +116 -0
  107. package/dist/daemon/repo-manager.js +384 -0
  108. package/dist/daemon/router.d.ts +60 -1
  109. package/dist/daemon/router.js +281 -20
  110. package/dist/daemon/user-directory.d.ts +111 -0
  111. package/dist/daemon/user-directory.js +233 -0
  112. package/dist/dashboard/out/404.html +1 -1
  113. package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
  114. package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +1 -0
  115. package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +1 -0
  116. package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +1 -0
  117. package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +1 -0
  118. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/{page-3fdfa60e53f2810d.js → page-8553743baca53a00.js} +1 -1
  119. package/dist/dashboard/out/_next/static/chunks/app/app/page-c617745b81344f4f.js +1 -0
  120. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-f829604fb75a831a.js +1 -0
  121. package/dist/dashboard/out/_next/static/chunks/app/{page-77e9c65420a06cfb.js → page-dc786c183425c2ac.js} +1 -1
  122. package/dist/dashboard/out/_next/static/chunks/app/providers/page-84322991d7244499.js +1 -0
  123. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-05606941a8e2be83.js +1 -0
  124. package/dist/dashboard/out/_next/static/chunks/{main-ed4e1fb6f29c34cf.js → main-2ee6beb2ae96d210.js} +1 -1
  125. package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +1 -0
  126. package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +1 -0
  127. package/dist/dashboard/out/_next/static/sDcbGRTYLcpPvyTs_rsNb/_ssgManifest.js +1 -0
  128. package/dist/dashboard/out/app/onboarding.html +1 -1
  129. package/dist/dashboard/out/app/onboarding.txt +3 -3
  130. package/dist/dashboard/out/app.html +1 -1
  131. package/dist/dashboard/out/app.txt +3 -3
  132. package/dist/dashboard/out/apple-icon.png +0 -0
  133. package/dist/dashboard/out/connect-repos.html +1 -1
  134. package/dist/dashboard/out/connect-repos.txt +2 -2
  135. package/dist/dashboard/out/history.html +1 -1
  136. package/dist/dashboard/out/history.txt +2 -2
  137. package/dist/dashboard/out/index.html +1 -1
  138. package/dist/dashboard/out/index.txt +3 -3
  139. package/dist/dashboard/out/login.html +2 -2
  140. package/dist/dashboard/out/login.txt +2 -2
  141. package/dist/dashboard/out/metrics.html +1 -1
  142. package/dist/dashboard/out/metrics.txt +3 -3
  143. package/dist/dashboard/out/pricing.html +2 -2
  144. package/dist/dashboard/out/pricing.txt +3 -3
  145. package/dist/dashboard/out/providers/setup/claude.html +1 -0
  146. package/dist/dashboard/out/providers/setup/claude.txt +8 -0
  147. package/dist/dashboard/out/providers/setup/codex.html +1 -0
  148. package/dist/dashboard/out/providers/setup/codex.txt +8 -0
  149. package/dist/dashboard/out/providers.html +1 -1
  150. package/dist/dashboard/out/providers.txt +3 -3
  151. package/dist/dashboard/out/signup.html +2 -2
  152. package/dist/dashboard/out/signup.txt +2 -2
  153. package/dist/dashboard-server/server.js +316 -12
  154. package/dist/dashboard-server/user-bridge.d.ts +103 -0
  155. package/dist/dashboard-server/user-bridge.js +189 -0
  156. package/dist/protocol/channels.d.ts +205 -0
  157. package/dist/protocol/channels.js +154 -0
  158. package/dist/protocol/types.d.ts +13 -1
  159. package/dist/resiliency/provider-context.js +2 -0
  160. package/dist/shared/cli-auth-config.d.ts +19 -0
  161. package/dist/shared/cli-auth-config.js +58 -2
  162. package/dist/utils/agent-config.js +1 -1
  163. package/dist/wrapper/auth-detection.d.ts +49 -0
  164. package/dist/wrapper/auth-detection.js +192 -0
  165. package/dist/wrapper/base-wrapper.d.ts +153 -0
  166. package/dist/wrapper/base-wrapper.js +393 -0
  167. package/dist/wrapper/client.d.ts +7 -1
  168. package/dist/wrapper/client.js +3 -0
  169. package/dist/wrapper/index.d.ts +1 -0
  170. package/dist/wrapper/index.js +4 -3
  171. package/dist/wrapper/pty-wrapper.d.ts +62 -84
  172. package/dist/wrapper/pty-wrapper.js +154 -180
  173. package/dist/wrapper/tmux-wrapper.d.ts +41 -66
  174. package/dist/wrapper/tmux-wrapper.js +90 -134
  175. package/package.json +4 -2
  176. package/scripts/postinstall.js +11 -155
  177. package/scripts/test-interactive-terminal.sh +248 -0
  178. package/dist/cloud/vault/index.d.ts +0 -76
  179. package/dist/cloud/vault/index.js +0 -219
  180. package/dist/dashboard/out/_next/static/chunks/699-3b1cd6618a45d259.js +0 -1
  181. package/dist/dashboard/out/_next/static/chunks/724-2dae7627550ab88f.js +0 -9
  182. package/dist/dashboard/out/_next/static/chunks/766-1f2dd8cb7f766b0b.js +0 -1
  183. package/dist/dashboard/out/_next/static/chunks/app/app/page-e6381e5a6e1fbcfd.js +0 -1
  184. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-67a3e98d9a43a6ed.js +0 -1
  185. package/dist/dashboard/out/_next/static/chunks/app/providers/page-e88bc117ef7671c3.js +0 -1
  186. package/dist/dashboard/out/_next/static/css/29852f26181969a0.css +0 -1
  187. package/dist/dashboard/out/_next/static/css/7c3ae9e8617d42a5.css +0 -1
  188. package/dist/dashboard/out/_next/static/wPgKJtcOmTFLpUncDg16A/_ssgManifest.js +0 -1
  189. /package/dist/dashboard/out/_next/static/{wPgKJtcOmTFLpUncDg16A → sDcbGRTYLcpPvyTs_rsNb}/_buildManifest.js +0 -0
@@ -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
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Channel and User Protocol Types
3
+ *
4
+ * Extends the Agent Relay Protocol to support:
5
+ * - First-class user entities (humans, not just AI agents)
6
+ * - Channels for group communication
7
+ * - Direct messaging between any combination of users and agents
8
+ */
9
+ import { PROTOCOL_VERSION, type Envelope } from './types.js';
10
+ export { PROTOCOL_VERSION };
11
+ /**
12
+ * Entity types in the relay system.
13
+ * - 'agent': AI agent (Claude, GPT, etc.)
14
+ * - 'user': Human user (via dashboard)
15
+ */
16
+ export type EntityType = 'agent' | 'user';
17
+ /**
18
+ * Extended message types for channels.
19
+ */
20
+ export type ChannelMessageType = 'CHANNEL_JOIN' | 'CHANNEL_LEAVE' | 'CHANNEL_MESSAGE' | 'CHANNEL_INFO' | 'CHANNEL_MEMBERS' | 'CHANNEL_TYPING';
21
+ /**
22
+ * Check if an entity type represents a user.
23
+ */
24
+ export declare function isUserEntity(entityType: EntityType | undefined): boolean;
25
+ /**
26
+ * Check if an entity type represents an agent.
27
+ * Undefined defaults to agent for backwards compatibility.
28
+ */
29
+ export declare function isAgentEntity(entityType: EntityType | undefined): boolean;
30
+ /**
31
+ * Attachment metadata for messages.
32
+ */
33
+ export interface MessageAttachment {
34
+ id: string;
35
+ filename: string;
36
+ mimeType: string;
37
+ size: number;
38
+ url?: string;
39
+ data?: string;
40
+ }
41
+ /**
42
+ * Payload for CHANNEL_JOIN message.
43
+ * Sent when a user or agent joins a channel.
44
+ */
45
+ export interface ChannelJoinPayload {
46
+ /** The channel to join (e.g., '#general', 'dm:alice:bob') */
47
+ channel: string;
48
+ /** Optional display name for the channel member list */
49
+ displayName?: string;
50
+ /** Optional avatar URL */
51
+ avatarUrl?: string;
52
+ }
53
+ /**
54
+ * Payload for CHANNEL_LEAVE message.
55
+ * Sent when a user or agent leaves a channel.
56
+ */
57
+ export interface ChannelLeavePayload {
58
+ /** The channel to leave */
59
+ channel: string;
60
+ /** Optional reason for leaving */
61
+ reason?: string;
62
+ }
63
+ /**
64
+ * Payload for CHANNEL_MESSAGE.
65
+ * Sent when posting a message to a channel.
66
+ */
67
+ export interface ChannelMessagePayload {
68
+ /** The target channel */
69
+ channel: string;
70
+ /** Message content */
71
+ body: string;
72
+ /** Optional thread ID for threaded replies */
73
+ thread?: string;
74
+ /** Optional attachments */
75
+ attachments?: MessageAttachment[];
76
+ /** Optional metadata */
77
+ data?: Record<string, unknown>;
78
+ /** Optional list of mentioned users/agents */
79
+ mentions?: string[];
80
+ }
81
+ /**
82
+ * Channel member info.
83
+ */
84
+ export interface ChannelMember {
85
+ name: string;
86
+ entityType: EntityType;
87
+ displayName?: string;
88
+ avatarUrl?: string;
89
+ status?: 'online' | 'away' | 'offline';
90
+ }
91
+ /**
92
+ * Payload for CHANNEL_INFO.
93
+ * Contains metadata about a channel.
94
+ */
95
+ export interface ChannelInfoPayload {
96
+ /** Channel identifier */
97
+ channel: string;
98
+ /** Human-readable channel name */
99
+ name: string;
100
+ /** Optional description */
101
+ description?: string;
102
+ /** Optional current topic */
103
+ topic?: string;
104
+ /** List of members */
105
+ members: ChannelMember[];
106
+ /** When the channel was created */
107
+ createdAt: string;
108
+ /** Whether the channel is private */
109
+ isPrivate?: boolean;
110
+ }
111
+ /**
112
+ * Payload for CHANNEL_MEMBERS.
113
+ * List of members in a channel.
114
+ */
115
+ export interface ChannelMembersPayload {
116
+ channel: string;
117
+ members: ChannelMember[];
118
+ }
119
+ /**
120
+ * Payload for CHANNEL_TYPING.
121
+ * Indicates someone is typing in a channel.
122
+ */
123
+ export interface ChannelTypingPayload {
124
+ channel: string;
125
+ isTyping: boolean;
126
+ }
127
+ export type ChannelJoinEnvelope = Envelope<ChannelJoinPayload> & {
128
+ type: 'CHANNEL_JOIN';
129
+ };
130
+ export type ChannelLeaveEnvelope = Envelope<ChannelLeavePayload> & {
131
+ type: 'CHANNEL_LEAVE';
132
+ };
133
+ export type ChannelMessageEnvelope = Envelope<ChannelMessagePayload> & {
134
+ type: 'CHANNEL_MESSAGE';
135
+ };
136
+ export type ChannelInfoEnvelope = Envelope<ChannelInfoPayload> & {
137
+ type: 'CHANNEL_INFO';
138
+ };
139
+ /**
140
+ * Type guard to check if an envelope is a channel message.
141
+ */
142
+ export declare function isChannelMessage(envelope: {
143
+ type: string;
144
+ }): envelope is ChannelMessageEnvelope;
145
+ /**
146
+ * Type guard to check if an envelope is a channel join.
147
+ */
148
+ export declare function isChannelJoin(envelope: {
149
+ type: string;
150
+ }): envelope is ChannelJoinEnvelope;
151
+ /**
152
+ * Type guard to check if an envelope is a channel leave.
153
+ */
154
+ export declare function isChannelLeave(envelope: {
155
+ type: string;
156
+ }): envelope is ChannelLeaveEnvelope;
157
+ /**
158
+ * Create a CHANNEL_JOIN envelope.
159
+ */
160
+ export declare function createChannelJoinEnvelope(from: string, channel: string, options?: {
161
+ displayName?: string;
162
+ avatarUrl?: string;
163
+ }): ChannelJoinEnvelope;
164
+ /**
165
+ * Create a CHANNEL_LEAVE envelope.
166
+ */
167
+ export declare function createChannelLeaveEnvelope(from: string, channel: string, reason?: string): ChannelLeaveEnvelope;
168
+ /**
169
+ * Create a CHANNEL_MESSAGE envelope.
170
+ */
171
+ export declare function createChannelMessageEnvelope(from: string, channel: string, body: string, options?: {
172
+ thread?: string;
173
+ mentions?: string[];
174
+ attachments?: MessageAttachment[];
175
+ data?: Record<string, unknown>;
176
+ }): ChannelMessageEnvelope;
177
+ /**
178
+ * Parse a DM channel name to extract participants.
179
+ * DM channels follow the format: dm:<participant1>:<participant2>:...
180
+ */
181
+ export declare function parseDmChannel(channel: string): string[] | null;
182
+ /**
183
+ * Create a DM channel name from participants.
184
+ * Participants are sorted alphabetically for consistency.
185
+ */
186
+ export declare function createDmChannelName(...participants: string[]): string;
187
+ /**
188
+ * Check if a channel is a DM (direct message).
189
+ */
190
+ export declare function isDmChannel(channel: string): boolean;
191
+ /**
192
+ * Check if a channel is private.
193
+ */
194
+ export declare function isPrivateChannel(channel: string): boolean;
195
+ /**
196
+ * Check if a channel is a public channel (starts with #).
197
+ */
198
+ export declare function isPublicChannel(channel: string): boolean;
199
+ /**
200
+ * Normalize channel name for storage/lookup.
201
+ * - Removes leading # from public channels
202
+ * - Sorts participants in DM channels
203
+ */
204
+ export declare function normalizeChannelName(channel: string): string;
205
+ //# sourceMappingURL=channels.d.ts.map
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Channel and User Protocol Types
3
+ *
4
+ * Extends the Agent Relay Protocol to support:
5
+ * - First-class user entities (humans, not just AI agents)
6
+ * - Channels for group communication
7
+ * - Direct messaging between any combination of users and agents
8
+ */
9
+ import { v4 as uuid } from 'uuid';
10
+ import { PROTOCOL_VERSION } from './types.js';
11
+ // Re-export PROTOCOL_VERSION for convenience
12
+ export { PROTOCOL_VERSION };
13
+ /**
14
+ * Check if an entity type represents a user.
15
+ */
16
+ export function isUserEntity(entityType) {
17
+ return entityType === 'user';
18
+ }
19
+ /**
20
+ * Check if an entity type represents an agent.
21
+ * Undefined defaults to agent for backwards compatibility.
22
+ */
23
+ export function isAgentEntity(entityType) {
24
+ return entityType === 'agent' || entityType === undefined;
25
+ }
26
+ /**
27
+ * Type guard to check if an envelope is a channel message.
28
+ */
29
+ export function isChannelMessage(envelope) {
30
+ return envelope.type === 'CHANNEL_MESSAGE';
31
+ }
32
+ /**
33
+ * Type guard to check if an envelope is a channel join.
34
+ */
35
+ export function isChannelJoin(envelope) {
36
+ return envelope.type === 'CHANNEL_JOIN';
37
+ }
38
+ /**
39
+ * Type guard to check if an envelope is a channel leave.
40
+ */
41
+ export function isChannelLeave(envelope) {
42
+ return envelope.type === 'CHANNEL_LEAVE';
43
+ }
44
+ /**
45
+ * Create a CHANNEL_JOIN envelope.
46
+ */
47
+ export function createChannelJoinEnvelope(from, channel, options) {
48
+ return {
49
+ v: PROTOCOL_VERSION,
50
+ type: 'CHANNEL_JOIN',
51
+ id: uuid(),
52
+ ts: Date.now(),
53
+ from,
54
+ payload: {
55
+ channel,
56
+ displayName: options?.displayName,
57
+ avatarUrl: options?.avatarUrl,
58
+ },
59
+ };
60
+ }
61
+ /**
62
+ * Create a CHANNEL_LEAVE envelope.
63
+ */
64
+ export function createChannelLeaveEnvelope(from, channel, reason) {
65
+ return {
66
+ v: PROTOCOL_VERSION,
67
+ type: 'CHANNEL_LEAVE',
68
+ id: uuid(),
69
+ ts: Date.now(),
70
+ from,
71
+ payload: {
72
+ channel,
73
+ reason,
74
+ },
75
+ };
76
+ }
77
+ /**
78
+ * Create a CHANNEL_MESSAGE envelope.
79
+ */
80
+ export function createChannelMessageEnvelope(from, channel, body, options) {
81
+ return {
82
+ v: PROTOCOL_VERSION,
83
+ type: 'CHANNEL_MESSAGE',
84
+ id: uuid(),
85
+ ts: Date.now(),
86
+ from,
87
+ payload: {
88
+ channel,
89
+ body,
90
+ thread: options?.thread,
91
+ mentions: options?.mentions,
92
+ attachments: options?.attachments,
93
+ data: options?.data,
94
+ },
95
+ };
96
+ }
97
+ /**
98
+ * Parse a DM channel name to extract participants.
99
+ * DM channels follow the format: dm:<participant1>:<participant2>:...
100
+ */
101
+ export function parseDmChannel(channel) {
102
+ if (!channel.startsWith('dm:')) {
103
+ return null;
104
+ }
105
+ const parts = channel.split(':').slice(1);
106
+ return parts.length >= 2 ? parts : null;
107
+ }
108
+ /**
109
+ * Create a DM channel name from participants.
110
+ * Participants are sorted alphabetically for consistency.
111
+ */
112
+ export function createDmChannelName(...participants) {
113
+ if (participants.length < 2) {
114
+ throw new Error('DM requires at least 2 participants');
115
+ }
116
+ const sorted = [...participants].sort();
117
+ return `dm:${sorted.join(':')}`;
118
+ }
119
+ /**
120
+ * Check if a channel is a DM (direct message).
121
+ */
122
+ export function isDmChannel(channel) {
123
+ return channel.startsWith('dm:');
124
+ }
125
+ /**
126
+ * Check if a channel is private.
127
+ */
128
+ export function isPrivateChannel(channel) {
129
+ return channel.startsWith('private:');
130
+ }
131
+ /**
132
+ * Check if a channel is a public channel (starts with #).
133
+ */
134
+ export function isPublicChannel(channel) {
135
+ return channel.startsWith('#');
136
+ }
137
+ /**
138
+ * Normalize channel name for storage/lookup.
139
+ * - Removes leading # from public channels
140
+ * - Sorts participants in DM channels
141
+ */
142
+ export function normalizeChannelName(channel) {
143
+ if (channel.startsWith('#')) {
144
+ return channel.slice(1);
145
+ }
146
+ if (channel.startsWith('dm:')) {
147
+ const participants = parseDmChannel(channel);
148
+ if (participants) {
149
+ return createDmChannelName(...participants);
150
+ }
151
+ }
152
+ return channel;
153
+ }
154
+ //# sourceMappingURL=channels.js.map
@@ -3,7 +3,7 @@
3
3
  * Version 1.0
4
4
  */
5
5
  export declare const PROTOCOL_VERSION = 1;
6
- export type MessageType = 'HELLO' | 'WELCOME' | 'SEND' | 'DELIVER' | 'ACK' | 'NACK' | 'PING' | 'PONG' | 'ERROR' | 'BUSY' | 'RESUME' | 'BYE' | 'STATE' | 'SYNC' | 'SYNC_SNAPSHOT' | 'SYNC_DELTA' | 'SUBSCRIBE' | 'UNSUBSCRIBE' | 'SHADOW_BIND' | 'SHADOW_UNBIND' | 'LOG';
6
+ export type MessageType = 'HELLO' | 'WELCOME' | 'SEND' | 'DELIVER' | 'ACK' | 'NACK' | 'PING' | 'PONG' | 'ERROR' | 'BUSY' | 'RESUME' | 'BYE' | 'STATE' | 'SYNC' | 'SYNC_SNAPSHOT' | 'SYNC_DELTA' | 'SUBSCRIBE' | 'UNSUBSCRIBE' | 'SHADOW_BIND' | 'SHADOW_UNBIND' | 'LOG' | 'CHANNEL_JOIN' | 'CHANNEL_LEAVE' | 'CHANNEL_MESSAGE' | 'CHANNEL_INFO' | 'CHANNEL_MEMBERS' | 'CHANNEL_TYPING';
7
7
  export type PayloadKind = 'message' | 'action' | 'state' | 'thinking';
8
8
  export interface Envelope<T = unknown> {
9
9
  v: number;
@@ -15,6 +15,12 @@ export interface Envelope<T = unknown> {
15
15
  topic?: string;
16
16
  payload: T;
17
17
  }
18
+ /**
19
+ * Entity type distinguishes between AI agents and human users.
20
+ * - 'agent': AI agent (Claude, GPT, custom agents)
21
+ * - 'user': Human user (via dashboard WebSocket)
22
+ */
23
+ export type EntityType = 'agent' | 'user';
18
24
  export interface HelloPayload {
19
25
  agent: string;
20
26
  capabilities: {
@@ -23,6 +29,8 @@ export interface HelloPayload {
23
29
  max_inflight: number;
24
30
  supports_topics: boolean;
25
31
  };
32
+ /** Entity type: 'agent' (default) or 'user' for human users */
33
+ entityType?: EntityType;
26
34
  /** Optional hint about which CLI the agent is using (claude, codex, gemini, etc.) */
27
35
  cli?: string;
28
36
  /** Optional program identifier (e.g., 'claude', 'gpt-4o') */
@@ -33,6 +41,10 @@ export interface HelloPayload {
33
41
  task?: string;
34
42
  /** Optional working directory hint for registry/dashboard */
35
43
  workingDirectory?: string;
44
+ /** Display name for human users */
45
+ displayName?: string;
46
+ /** Avatar URL for human users */
47
+ avatarUrl?: string;
36
48
  session?: {
37
49
  resume_token?: string;
38
50
  };
@@ -197,6 +197,8 @@ export class CodexContextHandler extends ProviderContextHandler {
197
197
  config.history = config.history || {};
198
198
  config.history.save_history = true;
199
199
  config.history.max_history_size = 1000;
200
+ // Disable Codex auto-update checks to keep container-stable version
201
+ config.check_for_updates = false;
200
202
  // Point to our system prompt if using custom context
201
203
  if (this.codexConfig.systemPromptPath) {
202
204
  config.system_prompt_file = this.codexConfig.systemPromptPath;