@agentmeshhq/agent 0.2.0 → 0.2.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 (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/dist/__tests__/orphan-process.test.d.ts +11 -0
  4. package/dist/__tests__/orphan-process.test.js +286 -0
  5. package/dist/__tests__/orphan-process.test.js.map +1 -0
  6. package/dist/__tests__/runner.test.js +16 -0
  7. package/dist/__tests__/runner.test.js.map +1 -1
  8. package/dist/__tests__/watchdog.test.js +138 -12
  9. package/dist/__tests__/watchdog.test.js.map +1 -1
  10. package/dist/cli/index.js +0 -0
  11. package/dist/cli/status.js +11 -0
  12. package/dist/cli/status.js.map +1 -1
  13. package/dist/cli/stop.js +7 -2
  14. package/dist/cli/stop.js.map +1 -1
  15. package/dist/config/schema.d.ts +4 -2
  16. package/dist/core/daemon/assignment-message.d.ts +12 -0
  17. package/dist/core/daemon/assignment-message.js +36 -0
  18. package/dist/core/daemon/assignment-message.js.map +1 -0
  19. package/dist/core/daemon/bootstrap.d.ts +35 -0
  20. package/dist/core/daemon/bootstrap.js +52 -0
  21. package/dist/core/daemon/bootstrap.js.map +1 -0
  22. package/dist/core/daemon/crash-log.d.ts +16 -0
  23. package/dist/core/daemon/crash-log.js +24 -0
  24. package/dist/core/daemon/crash-log.js.map +1 -0
  25. package/dist/core/daemon/health-policy.d.ts +21 -0
  26. package/dist/core/daemon/health-policy.js +32 -0
  27. package/dist/core/daemon/health-policy.js.map +1 -0
  28. package/dist/core/daemon/sandbox-config.d.ts +9 -0
  29. package/dist/core/daemon/sandbox-config.js +17 -0
  30. package/dist/core/daemon/sandbox-config.js.map +1 -0
  31. package/dist/core/daemon/state.d.ts +33 -0
  32. package/dist/core/daemon/state.js +77 -0
  33. package/dist/core/daemon/state.js.map +1 -0
  34. package/dist/core/daemon/tmux-session.d.ts +17 -0
  35. package/dist/core/daemon/tmux-session.js +34 -0
  36. package/dist/core/daemon/tmux-session.js.map +1 -0
  37. package/dist/core/daemon/workspace.d.ts +10 -0
  38. package/dist/core/daemon/workspace.js +51 -0
  39. package/dist/core/daemon/workspace.js.map +1 -0
  40. package/dist/core/daemon.d.ts +0 -6
  41. package/dist/core/daemon.js +123 -244
  42. package/dist/core/daemon.js.map +1 -1
  43. package/dist/core/injector.js +6 -0
  44. package/dist/core/injector.js.map +1 -1
  45. package/dist/core/runner/build.d.ts +9 -0
  46. package/dist/core/runner/build.js +53 -0
  47. package/dist/core/runner/build.js.map +1 -0
  48. package/dist/core/runner/detect.d.ts +5 -0
  49. package/dist/core/runner/detect.js +14 -0
  50. package/dist/core/runner/detect.js.map +1 -0
  51. package/dist/core/runner/index.d.ts +5 -0
  52. package/dist/core/runner/index.js +5 -0
  53. package/dist/core/runner/index.js.map +1 -0
  54. package/dist/core/runner/model.d.ts +5 -0
  55. package/dist/core/runner/model.js +7 -0
  56. package/dist/core/runner/model.js.map +1 -0
  57. package/dist/core/runner/opencode-models.d.ts +15 -0
  58. package/dist/core/runner/opencode-models.js +70 -0
  59. package/dist/core/runner/opencode-models.js.map +1 -0
  60. package/dist/core/runner/types.d.ts +19 -0
  61. package/dist/core/runner/types.js +8 -0
  62. package/dist/core/runner/types.js.map +1 -0
  63. package/dist/core/runner.d.ts +5 -47
  64. package/dist/core/runner.js +5 -167
  65. package/dist/core/runner.js.map +1 -1
  66. package/dist/core/tmux-runtime.d.ts +13 -0
  67. package/dist/core/tmux-runtime.js +72 -0
  68. package/dist/core/tmux-runtime.js.map +1 -0
  69. package/dist/core/tmux.d.ts +7 -1
  70. package/dist/core/tmux.js +75 -45
  71. package/dist/core/tmux.js.map +1 -1
  72. package/dist/core/watchdog.d.ts +18 -1
  73. package/dist/core/watchdog.js +78 -29
  74. package/dist/core/watchdog.js.map +1 -1
  75. package/package.json +30 -11
  76. package/dist/cli/inbox.d.ts +0 -5
  77. package/dist/cli/inbox.js +0 -123
  78. package/dist/cli/inbox.js.map +0 -1
  79. package/dist/cli/issue.d.ts +0 -42
  80. package/dist/cli/issue.js +0 -297
  81. package/dist/cli/issue.js.map +0 -1
  82. package/dist/cli/ready.d.ts +0 -5
  83. package/dist/cli/ready.js +0 -131
  84. package/dist/cli/ready.js.map +0 -1
  85. package/dist/cli/sync.d.ts +0 -8
  86. package/dist/cli/sync.js +0 -154
  87. package/dist/cli/sync.js.map +0 -1
  88. package/dist/core/issue-cache.d.ts +0 -44
  89. package/dist/core/issue-cache.js +0 -75
  90. package/dist/core/issue-cache.js.map +0 -1
  91. package/src/__tests__/context.test.ts +0 -464
  92. package/src/__tests__/injector.test.ts +0 -29
  93. package/src/__tests__/jwt.test.ts +0 -112
  94. package/src/__tests__/loader.test.ts +0 -239
  95. package/src/__tests__/runner.test.ts +0 -104
  96. package/src/__tests__/sandbox.test.ts +0 -435
  97. package/src/__tests__/watchdog.test.ts +0 -368
  98. package/src/cli/attach.ts +0 -22
  99. package/src/cli/build.ts +0 -145
  100. package/src/cli/config.ts +0 -148
  101. package/src/cli/context.ts +0 -231
  102. package/src/cli/deploy.ts +0 -155
  103. package/src/cli/index.ts +0 -376
  104. package/src/cli/init.ts +0 -75
  105. package/src/cli/list.ts +0 -70
  106. package/src/cli/local.ts +0 -183
  107. package/src/cli/logs.ts +0 -64
  108. package/src/cli/migrate.ts +0 -212
  109. package/src/cli/nudge.ts +0 -81
  110. package/src/cli/restart.ts +0 -59
  111. package/src/cli/slack.ts +0 -70
  112. package/src/cli/start.ts +0 -118
  113. package/src/cli/status.ts +0 -91
  114. package/src/cli/stop.ts +0 -48
  115. package/src/cli/test.ts +0 -143
  116. package/src/cli/token.ts +0 -188
  117. package/src/cli/whoami.ts +0 -142
  118. package/src/config/loader.ts +0 -121
  119. package/src/config/schema.ts +0 -68
  120. package/src/context/handoff.ts +0 -122
  121. package/src/context/index.ts +0 -8
  122. package/src/context/schema.ts +0 -111
  123. package/src/context/storage.ts +0 -197
  124. package/src/core/daemon.ts +0 -1317
  125. package/src/core/heartbeat.ts +0 -129
  126. package/src/core/injector.ts +0 -292
  127. package/src/core/registry.ts +0 -159
  128. package/src/core/runner.ts +0 -225
  129. package/src/core/sandbox.ts +0 -547
  130. package/src/core/session-id.ts +0 -111
  131. package/src/core/tmux.ts +0 -405
  132. package/src/core/watchdog.ts +0 -238
  133. package/src/core/websocket.ts +0 -94
  134. package/src/index.ts +0 -10
  135. package/src/utils/jwt.ts +0 -87
  136. package/tsconfig.json +0 -8
  137. 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
- }