@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.
- package/README.md +39 -0
- package/dist/__tests__/context-template.test.d.ts +4 -0
- package/dist/__tests__/context-template.test.js +233 -0
- package/dist/__tests__/context-template.test.js.map +1 -0
- package/dist/__tests__/loader.test.js +140 -28
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/no-respawn.test.d.ts +1 -0
- package/dist/__tests__/no-respawn.test.js +254 -0
- package/dist/__tests__/no-respawn.test.js.map +1 -0
- package/dist/__tests__/onboard.test.d.ts +5 -0
- package/dist/__tests__/onboard.test.js +341 -0
- package/dist/__tests__/onboard.test.js.map +1 -0
- package/dist/__tests__/orphan-process.test.d.ts +11 -0
- package/dist/__tests__/orphan-process.test.js +286 -0
- package/dist/__tests__/orphan-process.test.js.map +1 -0
- package/dist/__tests__/runner.test.js +16 -0
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/__tests__/shared-resource-guards.test.d.ts +7 -0
- package/dist/__tests__/shared-resource-guards.test.js +260 -0
- package/dist/__tests__/shared-resource-guards.test.js.map +1 -0
- package/dist/__tests__/watchdog.test.js +138 -12
- package/dist/__tests__/watchdog.test.js.map +1 -1
- package/dist/cli/status.js +11 -0
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/stop.js +7 -2
- package/dist/cli/stop.js.map +1 -1
- package/dist/config/loader.d.ts +0 -4
- package/dist/config/loader.js +102 -42
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +6 -4
- package/dist/core/daemon/assignment-message.d.ts +12 -0
- package/dist/core/daemon/assignment-message.js +36 -0
- package/dist/core/daemon/assignment-message.js.map +1 -0
- package/dist/core/daemon/bootstrap.d.ts +35 -0
- package/dist/core/daemon/bootstrap.js +52 -0
- package/dist/core/daemon/bootstrap.js.map +1 -0
- package/dist/core/daemon/context-template.d.ts +11 -0
- package/dist/core/daemon/context-template.js +144 -0
- package/dist/core/daemon/context-template.js.map +1 -0
- package/dist/core/daemon/crash-log.d.ts +14 -0
- package/dist/core/daemon/crash-log.js +23 -0
- package/dist/core/daemon/crash-log.js.map +1 -0
- package/dist/core/daemon/git-auth.d.ts +18 -0
- package/dist/core/daemon/git-auth.js +88 -0
- package/dist/core/daemon/git-auth.js.map +1 -0
- package/dist/core/daemon/health-policy.d.ts +17 -0
- package/dist/core/daemon/health-policy.js +24 -0
- package/dist/core/daemon/health-policy.js.map +1 -0
- package/dist/core/daemon/sandbox-config.d.ts +9 -0
- package/dist/core/daemon/sandbox-config.js +17 -0
- package/dist/core/daemon/sandbox-config.js.map +1 -0
- package/dist/core/daemon/state.d.ts +33 -0
- package/dist/core/daemon/state.js +78 -0
- package/dist/core/daemon/state.js.map +1 -0
- package/dist/core/daemon/tmux-session.d.ts +17 -0
- package/dist/core/daemon/tmux-session.js +34 -0
- package/dist/core/daemon/tmux-session.js.map +1 -0
- package/dist/core/daemon/workspace.d.ts +23 -0
- package/dist/core/daemon/workspace.js +90 -0
- package/dist/core/daemon/workspace.js.map +1 -0
- package/dist/core/daemon.d.ts +9 -12
- package/dist/core/daemon.js +293 -393
- package/dist/core/daemon.js.map +1 -1
- package/dist/core/injector.d.ts +5 -1
- package/dist/core/injector.js +83 -0
- package/dist/core/injector.js.map +1 -1
- package/dist/core/registry.d.ts +62 -0
- package/dist/core/registry.js +18 -0
- package/dist/core/registry.js.map +1 -1
- package/dist/core/runner/build.d.ts +9 -0
- package/dist/core/runner/build.js +53 -0
- package/dist/core/runner/build.js.map +1 -0
- package/dist/core/runner/detect.d.ts +5 -0
- package/dist/core/runner/detect.js +14 -0
- package/dist/core/runner/detect.js.map +1 -0
- package/dist/core/runner/index.d.ts +5 -0
- package/dist/core/runner/index.js +5 -0
- package/dist/core/runner/index.js.map +1 -0
- package/dist/core/runner/model.d.ts +5 -0
- package/dist/core/runner/model.js +7 -0
- package/dist/core/runner/model.js.map +1 -0
- package/dist/core/runner/opencode-models.d.ts +15 -0
- package/dist/core/runner/opencode-models.js +70 -0
- package/dist/core/runner/opencode-models.js.map +1 -0
- package/dist/core/runner/types.d.ts +19 -0
- package/dist/core/runner/types.js +8 -0
- package/dist/core/runner/types.js.map +1 -0
- package/dist/core/runner.d.ts +5 -47
- package/dist/core/runner.js +5 -167
- package/dist/core/runner.js.map +1 -1
- package/dist/core/tmux-runtime.d.ts +13 -0
- package/dist/core/tmux-runtime.js +72 -0
- package/dist/core/tmux-runtime.js.map +1 -0
- package/dist/core/tmux.d.ts +7 -1
- package/dist/core/tmux.js +75 -45
- package/dist/core/tmux.js.map +1 -1
- package/dist/core/watchdog.d.ts +18 -1
- package/dist/core/watchdog.js +78 -29
- package/dist/core/watchdog.js.map +1 -1
- package/package.json +24 -4
- package/src/__tests__/context.test.ts +0 -464
- package/src/__tests__/injector.test.ts +0 -29
- package/src/__tests__/jwt.test.ts +0 -112
- package/src/__tests__/loader.test.ts +0 -239
- package/src/__tests__/runner.test.ts +0 -104
- package/src/__tests__/sandbox.test.ts +0 -435
- package/src/__tests__/watchdog.test.ts +0 -368
- package/src/cli/attach.ts +0 -22
- package/src/cli/build.ts +0 -145
- package/src/cli/config.ts +0 -148
- package/src/cli/context.ts +0 -231
- package/src/cli/deploy.ts +0 -155
- package/src/cli/index.ts +0 -376
- package/src/cli/init.ts +0 -75
- package/src/cli/list.ts +0 -70
- package/src/cli/local.ts +0 -183
- package/src/cli/logs.ts +0 -64
- package/src/cli/migrate.ts +0 -212
- package/src/cli/nudge.ts +0 -81
- package/src/cli/restart.ts +0 -59
- package/src/cli/slack.ts +0 -70
- package/src/cli/start.ts +0 -118
- package/src/cli/status.ts +0 -91
- package/src/cli/stop.ts +0 -48
- package/src/cli/test.ts +0 -143
- package/src/cli/token.ts +0 -188
- package/src/cli/whoami.ts +0 -142
- package/src/config/loader.ts +0 -121
- package/src/config/schema.ts +0 -68
- package/src/context/handoff.ts +0 -122
- package/src/context/index.ts +0 -8
- package/src/context/schema.ts +0 -111
- package/src/context/storage.ts +0 -197
- package/src/core/daemon.ts +0 -1317
- package/src/core/heartbeat.ts +0 -129
- package/src/core/injector.ts +0 -292
- package/src/core/registry.ts +0 -159
- package/src/core/runner.ts +0 -225
- package/src/core/sandbox.ts +0 -547
- package/src/core/session-id.ts +0 -111
- package/src/core/tmux.ts +0 -405
- package/src/core/watchdog.ts +0 -238
- package/src/core/websocket.ts +0 -94
- package/src/index.ts +0 -10
- package/src/utils/jwt.ts +0 -87
- package/tsconfig.json +0 -8
- package/vitest.config.ts +0 -12
package/src/core/heartbeat.ts
DELETED
|
@@ -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
|
-
}
|
package/src/core/injector.ts
DELETED
|
@@ -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
|
-
}
|
package/src/core/registry.ts
DELETED
|
@@ -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
|
-
}
|