@agentmeshhq/agent 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +39 -0
  2. package/dist/__tests__/context-template.test.d.ts +4 -0
  3. package/dist/__tests__/context-template.test.js +233 -0
  4. package/dist/__tests__/context-template.test.js.map +1 -0
  5. package/dist/__tests__/loader.test.js +140 -28
  6. package/dist/__tests__/loader.test.js.map +1 -1
  7. package/dist/__tests__/no-respawn.test.d.ts +1 -0
  8. package/dist/__tests__/no-respawn.test.js +254 -0
  9. package/dist/__tests__/no-respawn.test.js.map +1 -0
  10. package/dist/__tests__/onboard.test.d.ts +5 -0
  11. package/dist/__tests__/onboard.test.js +341 -0
  12. package/dist/__tests__/onboard.test.js.map +1 -0
  13. package/dist/__tests__/orphan-process.test.d.ts +11 -0
  14. package/dist/__tests__/orphan-process.test.js +286 -0
  15. package/dist/__tests__/orphan-process.test.js.map +1 -0
  16. package/dist/__tests__/runner.test.js +16 -0
  17. package/dist/__tests__/runner.test.js.map +1 -1
  18. package/dist/__tests__/shared-resource-guards.test.d.ts +7 -0
  19. package/dist/__tests__/shared-resource-guards.test.js +260 -0
  20. package/dist/__tests__/shared-resource-guards.test.js.map +1 -0
  21. package/dist/__tests__/watchdog.test.js +138 -12
  22. package/dist/__tests__/watchdog.test.js.map +1 -1
  23. package/dist/cli/status.js +11 -0
  24. package/dist/cli/status.js.map +1 -1
  25. package/dist/cli/stop.js +7 -2
  26. package/dist/cli/stop.js.map +1 -1
  27. package/dist/config/loader.d.ts +0 -4
  28. package/dist/config/loader.js +102 -42
  29. package/dist/config/loader.js.map +1 -1
  30. package/dist/config/schema.d.ts +6 -4
  31. package/dist/core/daemon/assignment-message.d.ts +12 -0
  32. package/dist/core/daemon/assignment-message.js +36 -0
  33. package/dist/core/daemon/assignment-message.js.map +1 -0
  34. package/dist/core/daemon/bootstrap.d.ts +35 -0
  35. package/dist/core/daemon/bootstrap.js +52 -0
  36. package/dist/core/daemon/bootstrap.js.map +1 -0
  37. package/dist/core/daemon/context-template.d.ts +11 -0
  38. package/dist/core/daemon/context-template.js +144 -0
  39. package/dist/core/daemon/context-template.js.map +1 -0
  40. package/dist/core/daemon/crash-log.d.ts +14 -0
  41. package/dist/core/daemon/crash-log.js +23 -0
  42. package/dist/core/daemon/crash-log.js.map +1 -0
  43. package/dist/core/daemon/git-auth.d.ts +18 -0
  44. package/dist/core/daemon/git-auth.js +88 -0
  45. package/dist/core/daemon/git-auth.js.map +1 -0
  46. package/dist/core/daemon/health-policy.d.ts +17 -0
  47. package/dist/core/daemon/health-policy.js +24 -0
  48. package/dist/core/daemon/health-policy.js.map +1 -0
  49. package/dist/core/daemon/sandbox-config.d.ts +9 -0
  50. package/dist/core/daemon/sandbox-config.js +17 -0
  51. package/dist/core/daemon/sandbox-config.js.map +1 -0
  52. package/dist/core/daemon/state.d.ts +33 -0
  53. package/dist/core/daemon/state.js +78 -0
  54. package/dist/core/daemon/state.js.map +1 -0
  55. package/dist/core/daemon/tmux-session.d.ts +17 -0
  56. package/dist/core/daemon/tmux-session.js +34 -0
  57. package/dist/core/daemon/tmux-session.js.map +1 -0
  58. package/dist/core/daemon/workspace.d.ts +23 -0
  59. package/dist/core/daemon/workspace.js +90 -0
  60. package/dist/core/daemon/workspace.js.map +1 -0
  61. package/dist/core/daemon.d.ts +9 -12
  62. package/dist/core/daemon.js +293 -393
  63. package/dist/core/daemon.js.map +1 -1
  64. package/dist/core/injector.d.ts +5 -1
  65. package/dist/core/injector.js +83 -0
  66. package/dist/core/injector.js.map +1 -1
  67. package/dist/core/registry.d.ts +62 -0
  68. package/dist/core/registry.js +18 -0
  69. package/dist/core/registry.js.map +1 -1
  70. package/dist/core/runner/build.d.ts +9 -0
  71. package/dist/core/runner/build.js +53 -0
  72. package/dist/core/runner/build.js.map +1 -0
  73. package/dist/core/runner/detect.d.ts +5 -0
  74. package/dist/core/runner/detect.js +14 -0
  75. package/dist/core/runner/detect.js.map +1 -0
  76. package/dist/core/runner/index.d.ts +5 -0
  77. package/dist/core/runner/index.js +5 -0
  78. package/dist/core/runner/index.js.map +1 -0
  79. package/dist/core/runner/model.d.ts +5 -0
  80. package/dist/core/runner/model.js +7 -0
  81. package/dist/core/runner/model.js.map +1 -0
  82. package/dist/core/runner/opencode-models.d.ts +15 -0
  83. package/dist/core/runner/opencode-models.js +70 -0
  84. package/dist/core/runner/opencode-models.js.map +1 -0
  85. package/dist/core/runner/types.d.ts +19 -0
  86. package/dist/core/runner/types.js +8 -0
  87. package/dist/core/runner/types.js.map +1 -0
  88. package/dist/core/runner.d.ts +5 -47
  89. package/dist/core/runner.js +5 -167
  90. package/dist/core/runner.js.map +1 -1
  91. package/dist/core/tmux-runtime.d.ts +13 -0
  92. package/dist/core/tmux-runtime.js +72 -0
  93. package/dist/core/tmux-runtime.js.map +1 -0
  94. package/dist/core/tmux.d.ts +7 -1
  95. package/dist/core/tmux.js +75 -45
  96. package/dist/core/tmux.js.map +1 -1
  97. package/dist/core/watchdog.d.ts +18 -1
  98. package/dist/core/watchdog.js +78 -29
  99. package/dist/core/watchdog.js.map +1 -1
  100. package/package.json +24 -4
  101. package/src/__tests__/context.test.ts +0 -464
  102. package/src/__tests__/injector.test.ts +0 -29
  103. package/src/__tests__/jwt.test.ts +0 -112
  104. package/src/__tests__/loader.test.ts +0 -239
  105. package/src/__tests__/runner.test.ts +0 -104
  106. package/src/__tests__/sandbox.test.ts +0 -435
  107. package/src/__tests__/watchdog.test.ts +0 -368
  108. package/src/cli/attach.ts +0 -22
  109. package/src/cli/build.ts +0 -145
  110. package/src/cli/config.ts +0 -148
  111. package/src/cli/context.ts +0 -231
  112. package/src/cli/deploy.ts +0 -155
  113. package/src/cli/index.ts +0 -376
  114. package/src/cli/init.ts +0 -75
  115. package/src/cli/list.ts +0 -70
  116. package/src/cli/local.ts +0 -183
  117. package/src/cli/logs.ts +0 -64
  118. package/src/cli/migrate.ts +0 -212
  119. package/src/cli/nudge.ts +0 -81
  120. package/src/cli/restart.ts +0 -59
  121. package/src/cli/slack.ts +0 -70
  122. package/src/cli/start.ts +0 -118
  123. package/src/cli/status.ts +0 -91
  124. package/src/cli/stop.ts +0 -48
  125. package/src/cli/test.ts +0 -143
  126. package/src/cli/token.ts +0 -188
  127. package/src/cli/whoami.ts +0 -142
  128. package/src/config/loader.ts +0 -121
  129. package/src/config/schema.ts +0 -68
  130. package/src/context/handoff.ts +0 -122
  131. package/src/context/index.ts +0 -8
  132. package/src/context/schema.ts +0 -111
  133. package/src/context/storage.ts +0 -197
  134. package/src/core/daemon.ts +0 -1317
  135. package/src/core/heartbeat.ts +0 -129
  136. package/src/core/injector.ts +0 -292
  137. package/src/core/registry.ts +0 -159
  138. package/src/core/runner.ts +0 -225
  139. package/src/core/sandbox.ts +0 -547
  140. package/src/core/session-id.ts +0 -111
  141. package/src/core/tmux.ts +0 -405
  142. package/src/core/watchdog.ts +0 -238
  143. package/src/core/websocket.ts +0 -94
  144. package/src/index.ts +0 -10
  145. package/src/utils/jwt.ts +0 -87
  146. package/tsconfig.json +0 -8
  147. package/vitest.config.ts +0 -12
@@ -1,129 +0,0 @@
1
- import { getTokenTimeRemaining, isTokenExpiringSoon } from "../utils/jwt.js";
2
-
3
- // Refresh token when less than 24 hours remain
4
- const TOKEN_REFRESH_THRESHOLD_MS = 24 * 60 * 60 * 1000;
5
-
6
- export interface HeartbeatConfig {
7
- url: string;
8
- token: string;
9
- intervalMs: number;
10
- agentName: string;
11
- agentId: string;
12
- apiKey: string;
13
- workspace: string;
14
- onError?: (error: Error) => void;
15
- onTokenRefresh?: (newToken: string) => void;
16
- /** Called periodically to save agent context (every N heartbeats) */
17
- onContextSave?: () => void;
18
- /** How many heartbeats between context saves (default: 5) */
19
- contextSaveFrequency?: number;
20
- }
21
-
22
- export class Heartbeat {
23
- private config: HeartbeatConfig;
24
- private currentToken: string;
25
- private intervalId: NodeJS.Timeout | null = null;
26
- private heartbeatCount = 0;
27
- private contextSaveFrequency: number;
28
-
29
- constructor(config: HeartbeatConfig) {
30
- this.config = config;
31
- this.currentToken = config.token;
32
- this.contextSaveFrequency = config.contextSaveFrequency ?? 5;
33
- }
34
-
35
- start(): void {
36
- if (this.intervalId) {
37
- return;
38
- }
39
-
40
- // Send initial heartbeat
41
- this.sendHeartbeat();
42
-
43
- // Schedule recurring heartbeats
44
- this.intervalId = setInterval(() => {
45
- this.sendHeartbeat();
46
- }, this.config.intervalMs);
47
- }
48
-
49
- stop(): void {
50
- if (this.intervalId) {
51
- clearInterval(this.intervalId);
52
- this.intervalId = null;
53
- }
54
- }
55
-
56
- getToken(): string {
57
- return this.currentToken;
58
- }
59
-
60
- private async sendHeartbeat(): Promise<void> {
61
- try {
62
- // Check if token needs refresh before sending heartbeat
63
- if (isTokenExpiringSoon(this.currentToken, TOKEN_REFRESH_THRESHOLD_MS)) {
64
- const remaining = getTokenTimeRemaining(this.currentToken);
65
- const hours = Math.floor(remaining / (1000 * 60 * 60));
66
- console.log(`Token expiring in ${hours} hours, refreshing...`);
67
- await this.refreshToken();
68
- }
69
-
70
- const response = await fetch(`${this.config.url}/api/v1/agents/heartbeat`, {
71
- method: "POST",
72
- headers: {
73
- Authorization: `Bearer ${this.currentToken}`,
74
- "Content-Type": "application/json",
75
- },
76
- body: JSON.stringify({}),
77
- });
78
-
79
- if (!response.ok) {
80
- // If unauthorized, try to refresh token
81
- if (response.status === 401) {
82
- console.log("Token rejected, attempting refresh...");
83
- await this.refreshToken();
84
- return;
85
- }
86
- throw new Error(`Heartbeat failed: ${response.status}`);
87
- }
88
-
89
- // Periodically save context
90
- this.heartbeatCount++;
91
- if (this.config.onContextSave && this.heartbeatCount % this.contextSaveFrequency === 0) {
92
- this.config.onContextSave();
93
- }
94
- } catch (error) {
95
- this.config.onError?.(error as Error);
96
- }
97
- }
98
-
99
- private async refreshToken(): Promise<void> {
100
- try {
101
- const response = await fetch(`${this.config.url}/api/v1/agents/register`, {
102
- method: "POST",
103
- headers: {
104
- "Content-Type": "application/json",
105
- "x-agentmesh-secret": this.config.apiKey,
106
- },
107
- body: JSON.stringify({
108
- agent_id: this.config.agentId,
109
- workspace: this.config.workspace,
110
- display_name: this.config.agentName,
111
- model: "claude-sonnet-4",
112
- }),
113
- });
114
-
115
- if (!response.ok) {
116
- throw new Error(`Token refresh failed: ${response.status}`);
117
- }
118
-
119
- const data = (await response.json()) as { token: string };
120
- this.currentToken = data.token;
121
-
122
- console.log("Token refreshed successfully");
123
- this.config.onTokenRefresh?.(data.token);
124
- } catch (error) {
125
- console.error("Failed to refresh token:", (error as Error).message);
126
- this.config.onError?.(error as Error);
127
- }
128
- }
129
- }
@@ -1,292 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { formatHandoffContextSummary, parseHandoffContext } from "../context/handoff.js";
4
- import type { AgentContext } from "../context/schema.js";
5
- import type { InboxItem } from "./registry.js";
6
- import { sendKeys } from "./tmux.js";
7
- import type { WebSocketEvent } from "./websocket.js";
8
-
9
- export function injectStartupMessage(
10
- agentName: string,
11
- pendingCount: number,
12
- inboxItems?: InboxItem[],
13
- ): void {
14
- if (pendingCount === 0) {
15
- const message = `[AgentMesh] Connected and ready. No pending items in inbox.`;
16
- sendKeys(agentName, message);
17
- return;
18
- }
19
-
20
- // Build detailed message with handoff info
21
- let message = `[AgentMesh] Welcome back! You have ${pendingCount} pending handoff${pendingCount === 1 ? "" : "s"}.`;
22
-
23
- if (inboxItems && inboxItems.length > 0) {
24
- message += "\n\n--- PENDING HANDOFFS ---";
25
- for (const item of inboxItems.slice(0, 3)) {
26
- // Show up to 3
27
- const fromName = item.from_agent?.display_name || item.from_agent_id || "Unknown";
28
- message += `\n\nHandoff ID: ${item.id}`;
29
- message += `\nFrom: ${fromName}`;
30
- message += `\nScope: ${item.scope || "Not specified"}`;
31
- message += `\nReason: ${item.reason || "Not specified"}`;
32
- }
33
- if (inboxItems.length > 3) {
34
- message += `\n\n... and ${inboxItems.length - 3} more.`;
35
- }
36
- message += "\n\n--- END HANDOFFS ---";
37
- message += "\n\nReview the handoff(s) above and begin work on the assigned task.";
38
- } else {
39
- message += "\nUse agentmesh_check_inbox to see them.";
40
- }
41
-
42
- sendKeys(agentName, message);
43
- }
44
-
45
- export function injectHandoffReceived(agentName: string, event: WebSocketEvent): void {
46
- const fromName =
47
- (event.from_agent as { display_name?: string })?.display_name ||
48
- (event.from_agent_id as string) ||
49
- "Unknown";
50
- const scope = (event.scope as string) || "No scope provided";
51
- const reason = (event.reason as string) || "No reason provided";
52
- const handoffId = (event.handoff_id as string) || (event.id as string) || "unknown";
53
-
54
- const message = `[AgentMesh] New handoff from ${fromName}
55
-
56
- Scope: ${scope}
57
- Reason: ${reason}
58
- Handoff ID: ${handoffId}
59
-
60
- Accept this handoff and begin work.`;
61
-
62
- sendKeys(agentName, message);
63
- }
64
-
65
- export function injectNudge(agentName: string, event: WebSocketEvent): void {
66
- const fromName =
67
- (event.from as { name?: string })?.name || (event.from_name as string) || "Someone";
68
- const message = (event.message as string) || "Check your inbox";
69
-
70
- const formatted = `[AgentMesh] Nudge from ${fromName}:
71
- ${message}`;
72
-
73
- sendKeys(agentName, formatted);
74
- }
75
-
76
- export function injectBlockerResolved(agentName: string, event: WebSocketEvent): void {
77
- const description = (event.description as string) || "A blocker has been resolved";
78
- const resolvedBy =
79
- (event.resolved_by as { display_name?: string })?.display_name ||
80
- (event.resolved_by_name as string) ||
81
- "Another agent";
82
-
83
- const message = `[AgentMesh] Blocker resolved!
84
-
85
- Blocker: ${description}
86
- Resolved by: ${resolvedBy}
87
-
88
- You can now proceed with your work.`;
89
-
90
- sendKeys(agentName, message);
91
- }
92
-
93
- export interface EventContext {
94
- hubUrl?: string;
95
- token?: string;
96
- }
97
-
98
- interface SlackFile {
99
- name: string;
100
- type: string;
101
- url: string;
102
- permalink: string;
103
- base64?: string;
104
- mediaType?: string;
105
- }
106
-
107
- export function injectSlackMessage(
108
- agentName: string,
109
- event: WebSocketEvent,
110
- _context?: EventContext,
111
- ): void {
112
- const user = (event.user as string) || "unknown";
113
- const channel = (event.channel as string) || "unknown";
114
- const text = (event.text as string) || "";
115
- const files = (event.files as SlackFile[] | undefined) || [];
116
-
117
- // Build message with file info
118
- let message = `[Slack from ${user} in ${channel}] ${text}`;
119
-
120
- if (files.length > 0) {
121
- const savedImages: string[] = [];
122
- const otherFiles: string[] = [];
123
-
124
- // Save images with base64 data to disk
125
- const attachmentsDir = join(process.env.HOME || "/tmp", ".agentmesh", "attachments");
126
- if (!existsSync(attachmentsDir)) {
127
- mkdirSync(attachmentsDir, { recursive: true });
128
- }
129
-
130
- for (const f of files) {
131
- console.log(
132
- `[Injector] File: ${f.name}, type: ${f.type}, mediaType: ${f.mediaType}, has base64: ${!!f.base64}`,
133
- );
134
- if (f.base64 && (f.mediaType?.startsWith("image/") || f.type?.startsWith("image/"))) {
135
- // Save image to disk
136
- const timestamp = Date.now();
137
- const safeName = f.name.replace(/[^a-zA-Z0-9._-]/g, "_");
138
- const filePath = join(attachmentsDir, `${timestamp}-${safeName}`);
139
-
140
- try {
141
- writeFileSync(filePath, Buffer.from(f.base64, "base64"));
142
- savedImages.push(filePath);
143
- console.log(`[Injector] Saved image to: ${filePath}`);
144
- } catch (err) {
145
- console.error(`[Injector] Failed to save image:`, err);
146
- otherFiles.push(`${f.name} (failed to save)`);
147
- }
148
- } else {
149
- // Just reference the URL for non-image files
150
- otherFiles.push(`${f.name}: ${f.permalink}`);
151
- }
152
- }
153
-
154
- // Add instruction to view images - OpenCode needs explicit instruction
155
- if (savedImages.length > 0) {
156
- message += `\n\nUser attached ${savedImages.length} image(s). Use the Read tool to view them:`;
157
- for (const img of savedImages) {
158
- message += `\n- ${img}`;
159
- }
160
- }
161
-
162
- if (otherFiles.length > 0) {
163
- message += ` [Other files: ${otherFiles.join(" | ")}]`;
164
- }
165
- }
166
-
167
- console.log(`[Injector] Sending to tmux: ${message}`);
168
- const result = sendKeys(agentName, message);
169
- console.log(`[Injector] sendKeys result: ${result}`);
170
- }
171
-
172
- export function injectInboxItems(agentName: string, items: InboxItem[]): void {
173
- if (items.length === 0) {
174
- sendKeys(agentName, "[AgentMesh] Your inbox is empty.");
175
- return;
176
- }
177
-
178
- let message = `[AgentMesh] You have ${items.length} pending item${items.length === 1 ? "" : "s"}:\n\n`;
179
-
180
- for (const item of items) {
181
- const fromName = item.from_agent?.display_name || item.from_agent_id;
182
- message += `- From: ${fromName}\n`;
183
- message += ` Scope: ${item.scope}\n`;
184
- message += ` ID: ${item.id}\n\n`;
185
- }
186
-
187
- message += "Use agentmesh_accept_handoff with the ID to start working.";
188
-
189
- sendKeys(agentName, message);
190
- }
191
-
192
- export function handleWebSocketEvent(
193
- agentName: string,
194
- event: WebSocketEvent,
195
- context?: EventContext,
196
- ): void {
197
- switch (event.type) {
198
- case "handoff_received":
199
- case "handoff.received":
200
- injectHandoffReceived(agentName, event);
201
- break;
202
-
203
- case "nudge":
204
- case "agent.nudge":
205
- injectNudge(agentName, event);
206
- break;
207
-
208
- case "blocker_resolved":
209
- case "blocker.resolved":
210
- injectBlockerResolved(agentName, event);
211
- break;
212
-
213
- case "slack.message":
214
- console.log(`[Injector] Handling slack.message for ${agentName}`);
215
- console.log(`[Injector] Event keys: ${Object.keys(event).join(", ")}`);
216
- if (event.files) {
217
- const files = event.files as Array<{ name: string; base64?: string }>;
218
- console.log(
219
- `[Injector] Files count: ${files.length}, has base64: ${files.some((f) => !!f.base64)}`,
220
- );
221
- }
222
- injectSlackMessage(agentName, event, context);
223
- break;
224
-
225
- default:
226
- // Unknown event type, ignore
227
- break;
228
- }
229
- }
230
-
231
- /**
232
- * Injects restored context from a previous session
233
- */
234
- export function injectRestoredContext(agentName: string, context: AgentContext): void {
235
- const parts: string[] = ["[AgentMesh] Restored context from previous session:"];
236
-
237
- // Working state
238
- if (context.workingState.workdir) {
239
- parts.push(`Working directory: ${context.workingState.workdir}`);
240
- }
241
- if (context.workingState.gitBranch) {
242
- parts.push(`Git branch: ${context.workingState.gitBranch}`);
243
- }
244
-
245
- // Tasks
246
- const activeTasks = context.tasks.tasks.filter(
247
- (t) => t.status === "in_progress" || t.status === "pending",
248
- );
249
- if (activeTasks.length > 0) {
250
- parts.push("");
251
- parts.push("Active tasks:");
252
- for (const task of activeTasks.slice(0, 5)) {
253
- const statusIcon = task.status === "in_progress" ? ">" : "-";
254
- parts.push(` ${statusIcon} ${task.content}`);
255
- }
256
- if (activeTasks.length > 5) {
257
- parts.push(` ... and ${activeTasks.length - 5} more`);
258
- }
259
- }
260
-
261
- // Current goal
262
- if (context.tasks.currentGoal) {
263
- parts.push("");
264
- parts.push(`Goal: ${context.tasks.currentGoal}`);
265
- }
266
-
267
- // Accomplishments
268
- if (context.conversation.accomplishments.length > 0) {
269
- parts.push("");
270
- parts.push("Recent accomplishments:");
271
- for (const acc of context.conversation.accomplishments.slice(0, 3)) {
272
- parts.push(` - ${acc}`);
273
- }
274
- }
275
-
276
- const message = parts.join("\n");
277
- sendKeys(agentName, message);
278
- }
279
-
280
- /**
281
- * Injects context received from a handoff
282
- */
283
- export function injectHandoffContext(agentName: string, contextString: string): void {
284
- const context = parseHandoffContext(contextString);
285
- if (!context) {
286
- return;
287
- }
288
-
289
- const summary = formatHandoffContextSummary(context);
290
- const message = `[AgentMesh] Context from previous agent:\n\n${summary}`;
291
- sendKeys(agentName, message);
292
- }
@@ -1,159 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
-
3
- export interface RegisterOptions {
4
- url: string;
5
- apiKey: string;
6
- workspace: string;
7
- agentId?: string;
8
- agentName: string;
9
- model: string;
10
- capabilities?: string[];
11
- restoreContext?: boolean;
12
- }
13
-
14
- export type ServerContext = Record<string, Record<string, unknown>>;
15
-
16
- export interface RegisterResult {
17
- agentId: string;
18
- token: string;
19
- status?: "registered" | "re-registered";
20
- context?: ServerContext;
21
- assignments?: ProjectAssignment[];
22
- }
23
-
24
- export interface InboxItem {
25
- id: string;
26
- from_agent_id: string;
27
- from_agent?: {
28
- display_name?: string;
29
- };
30
- scope: string;
31
- reason: string;
32
- status: string;
33
- created_at: string;
34
- }
35
-
36
- export async function registerAgent(options: RegisterOptions): Promise<RegisterResult> {
37
- const agentId = options.agentId || randomUUID();
38
-
39
- const response = await fetch(`${options.url}/api/v1/agents/register`, {
40
- method: "POST",
41
- headers: {
42
- "x-agentmesh-secret": options.apiKey,
43
- "Content-Type": "application/json",
44
- },
45
- body: JSON.stringify({
46
- agent_id: agentId,
47
- display_name: options.agentName,
48
- model: options.model,
49
- capabilities: options.capabilities || ["coding", "review", "debugging"],
50
- workspace: options.workspace,
51
- restore_context: options.restoreContext ?? false,
52
- }),
53
- });
54
-
55
- if (!response.ok) {
56
- const error = await response.text();
57
- throw new Error(`Failed to register agent: ${error}`);
58
- }
59
-
60
- const data = await response.json();
61
- const token = data.data?.token || data.token;
62
-
63
- if (!token) {
64
- throw new Error("No token in registration response");
65
- }
66
-
67
- return {
68
- agentId,
69
- token,
70
- status: data.status,
71
- context: data.context,
72
- assignments: data.assignments,
73
- };
74
- }
75
-
76
- export async function checkInbox(
77
- url: string,
78
- workspace: string,
79
- token: string,
80
- ): Promise<InboxItem[]> {
81
- const response = await fetch(`${url}/api/v1/workspaces/${workspace}/inbox`, {
82
- headers: {
83
- Authorization: `Bearer ${token}`,
84
- },
85
- });
86
-
87
- if (!response.ok) {
88
- throw new Error(`Failed to check inbox: ${response.status}`);
89
- }
90
-
91
- const data = await response.json();
92
- return (data.data || []).filter((item: InboxItem) => item.status === "pending");
93
- }
94
-
95
- export async function sendNudge(
96
- url: string,
97
- agentId: string,
98
- message: string,
99
- token: string,
100
- ): Promise<boolean> {
101
- const response = await fetch(`${url}/api/v1/agents/${agentId}/nudge`, {
102
- method: "POST",
103
- headers: {
104
- Authorization: `Bearer ${token}`,
105
- "Content-Type": "application/json",
106
- },
107
- body: JSON.stringify({ message }),
108
- });
109
-
110
- return response.ok;
111
- }
112
-
113
- // ============================================================================
114
- // Agent Self-Discovery
115
- // ============================================================================
116
-
117
- export interface ProjectAssignment {
118
- assignment_id: string;
119
- role: string;
120
- status: string;
121
- notes: string | null;
122
- priority: number;
123
- created_at: string;
124
- project: {
125
- project_id: string;
126
- name: string;
127
- code: string;
128
- description: string | null;
129
- workdir: string | null;
130
- };
131
- repo: {
132
- repo_id: string;
133
- provider: string;
134
- full_name: string;
135
- url: string;
136
- default_branch: string;
137
- } | null;
138
- }
139
-
140
- /**
141
- * Fetch the agent's project assignments from HQ
142
- */
143
- export async function fetchAssignments(url: string, token: string): Promise<ProjectAssignment[]> {
144
- const response = await fetch(`${url}/api/v1/agents/me/assignments`, {
145
- headers: {
146
- Authorization: `Bearer ${token}`,
147
- },
148
- });
149
-
150
- if (!response.ok) {
151
- if (response.status === 404) {
152
- return []; // No assignments
153
- }
154
- throw new Error(`Failed to fetch assignments: ${response.status}`);
155
- }
156
-
157
- const data = (await response.json()) as { data: ProjectAssignment[] };
158
- return data.data || [];
159
- }