@agentmeshhq/agent 0.1.9 → 0.1.11
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/dist/cli/index.js +3 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/list.js +18 -6
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/restart.d.ts +2 -3
- package/dist/cli/restart.js +11 -5
- package/dist/cli/restart.js.map +1 -1
- package/dist/cli/start.d.ts +1 -0
- package/dist/cli/start.js +3 -0
- package/dist/cli/start.js.map +1 -1
- package/dist/cli/status.js +7 -4
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/whoami.js +18 -10
- package/dist/cli/whoami.js.map +1 -1
- package/dist/config/schema.d.ts +2 -0
- package/dist/core/daemon.d.ts +14 -4
- package/dist/core/daemon.js +163 -56
- package/dist/core/daemon.js.map +1 -1
- package/dist/core/registry.d.ts +26 -0
- package/dist/core/registry.js +18 -0
- package/dist/core/registry.js.map +1 -1
- package/dist/core/tmux.js +3 -3
- package/dist/core/tmux.js.map +1 -1
- package/package.json +11 -11
- package/src/cli/index.ts +3 -2
- package/src/cli/list.ts +18 -6
- package/src/cli/restart.ts +12 -10
- package/src/cli/start.ts +3 -0
- package/src/cli/status.ts +8 -5
- package/src/cli/whoami.ts +24 -13
- package/src/config/schema.ts +2 -0
- package/src/core/daemon.ts +212 -66
- package/src/core/registry.ts +57 -16
- package/src/core/tmux.ts +3 -3
- package/LICENSE +0 -21
package/src/cli/start.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface StartOptions {
|
|
|
13
13
|
model?: string;
|
|
14
14
|
foreground?: boolean;
|
|
15
15
|
noContext?: boolean;
|
|
16
|
+
autoSetup?: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export async function start(options: StartOptions): Promise<void> {
|
|
@@ -44,6 +45,7 @@ export async function start(options: StartOptions): Promise<void> {
|
|
|
44
45
|
const daemon = new AgentDaemon({
|
|
45
46
|
...options,
|
|
46
47
|
restoreContext: !options.noContext,
|
|
48
|
+
autoSetup: options.autoSetup,
|
|
47
49
|
});
|
|
48
50
|
await daemon.start();
|
|
49
51
|
// Keep process alive
|
|
@@ -67,6 +69,7 @@ export async function start(options: StartOptions): Promise<void> {
|
|
|
67
69
|
if (options.workdir) args.push("--workdir", options.workdir);
|
|
68
70
|
if (options.model) args.push("--model", options.model);
|
|
69
71
|
if (options.noContext) args.push("--no-context");
|
|
72
|
+
if (options.autoSetup) args.push("--auto-setup");
|
|
70
73
|
|
|
71
74
|
// Spawn detached background process
|
|
72
75
|
const child = spawn("node", [cliPath, ...args], {
|
package/src/cli/status.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { loadConfig, loadState } from "../config/loader.js";
|
|
3
|
-
import { getRunnerDisplayName } from "../core/runner.js";
|
|
4
3
|
import { getSessionName, sessionExists } from "../core/tmux.js";
|
|
5
4
|
|
|
6
5
|
interface HealthResponse {
|
|
@@ -61,10 +60,14 @@ export async function status(): Promise<void> {
|
|
|
61
60
|
console.log(` Total: ${pc.dim(String(state.agents.length))}`);
|
|
62
61
|
|
|
63
62
|
if (runningAgents.length > 0) {
|
|
64
|
-
console.log(`
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
console.log(` Names: ${pc.dim(runningAgents.map((a) => a.name).join(", "))}`);
|
|
64
|
+
|
|
65
|
+
// Show models for running agents
|
|
66
|
+
const modelsInfo = runningAgents
|
|
67
|
+
.filter((a) => a.runtimeModel)
|
|
68
|
+
.map((a) => `${a.name}:${a.runtimeModel}`);
|
|
69
|
+
if (modelsInfo.length > 0) {
|
|
70
|
+
console.log(` Models: ${pc.dim(modelsInfo.join(", "))}`);
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
package/src/cli/whoami.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { loadConfig, loadState } from "../config/loader.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import type { RunnerType } from "../config/schema.js";
|
|
4
|
+
import { getRunnerDisplayName } from "../core/runner.js";
|
|
5
|
+
import { getTokenExpiry } from "../utils/jwt.js";
|
|
5
6
|
|
|
6
7
|
export async function whoami(agentName?: string): Promise<void> {
|
|
7
8
|
const config = loadConfig();
|
|
@@ -24,20 +25,23 @@ export async function whoami(agentName?: string): Promise<void> {
|
|
|
24
25
|
const expiry = getTokenExpiry(envToken);
|
|
25
26
|
const expiryStr = formatExpiry(expiry);
|
|
26
27
|
|
|
27
|
-
//
|
|
28
|
+
// Try to find agent in state for runtime info
|
|
28
29
|
const agentState = state.agents.find((a) => a.agentId === envAgentId);
|
|
29
30
|
|
|
30
31
|
console.log(pc.bold("Current Agent"));
|
|
31
32
|
console.log(` ID: ${pc.cyan(envAgentId)}`);
|
|
32
33
|
console.log(` Workspace: ${pc.dim(config.workspace)}`);
|
|
34
|
+
console.log(` Token: ${expiryStr}`);
|
|
35
|
+
console.log(` Hub: ${pc.dim(config.hubUrl)}`);
|
|
36
|
+
|
|
37
|
+
// Show runtime model info if available
|
|
33
38
|
if (agentState?.runtimeModel) {
|
|
34
39
|
const runnerName = agentState.runnerType
|
|
35
|
-
? getRunnerDisplayName(agentState.runnerType)
|
|
40
|
+
? getRunnerDisplayName(agentState.runnerType as RunnerType)
|
|
36
41
|
: "Unknown";
|
|
37
|
-
console.log(` Model: ${pc.cyan(agentState.runtimeModel)}
|
|
42
|
+
console.log(` Model: ${pc.cyan(agentState.runtimeModel)}`);
|
|
43
|
+
console.log(` Runner: ${pc.dim(runnerName)}`);
|
|
38
44
|
}
|
|
39
|
-
console.log(` Token: ${expiryStr}`);
|
|
40
|
-
console.log(` Hub: ${pc.dim(config.hubUrl)}`);
|
|
41
45
|
return;
|
|
42
46
|
}
|
|
43
47
|
|
|
@@ -59,11 +63,13 @@ export async function whoami(agentName?: string): Promise<void> {
|
|
|
59
63
|
console.log(` ${pc.cyan(agent.name)}`);
|
|
60
64
|
console.log(` ID: ${pc.dim(agent.agentId)}`);
|
|
61
65
|
console.log(` Status: ${running ? pc.green("running") : pc.yellow("stopped")}`);
|
|
66
|
+
console.log(` Token: ${expiryStr}`);
|
|
62
67
|
if (agent.runtimeModel) {
|
|
63
|
-
const runnerName = agent.runnerType
|
|
68
|
+
const runnerName = agent.runnerType
|
|
69
|
+
? getRunnerDisplayName(agent.runnerType as RunnerType)
|
|
70
|
+
: "Unknown";
|
|
64
71
|
console.log(` Model: ${pc.dim(agent.runtimeModel)} (${runnerName})`);
|
|
65
72
|
}
|
|
66
|
-
console.log(` Token: ${expiryStr}`);
|
|
67
73
|
console.log();
|
|
68
74
|
}
|
|
69
75
|
return;
|
|
@@ -85,14 +91,19 @@ export async function whoami(agentName?: string): Promise<void> {
|
|
|
85
91
|
console.log(` ID: ${pc.cyan(agent.agentId)}`);
|
|
86
92
|
console.log(` Workspace: ${pc.dim(config.workspace)}`);
|
|
87
93
|
console.log(` Status: ${running ? pc.green("running") : pc.yellow("stopped")}`);
|
|
88
|
-
if (agent.runtimeModel) {
|
|
89
|
-
const runnerName = agent.runnerType ? getRunnerDisplayName(agent.runnerType) : "Unknown";
|
|
90
|
-
console.log(` Model: ${pc.cyan(agent.runtimeModel)} (${pc.dim(runnerName)})`);
|
|
91
|
-
}
|
|
92
94
|
console.log(` Token: ${expiryStr}`);
|
|
93
95
|
console.log(` Session: ${pc.dim(agent.tmuxSession || "none")}`);
|
|
94
96
|
console.log(` Started: ${pc.dim(agent.startedAt || "unknown")}`);
|
|
95
97
|
console.log(` Hub: ${pc.dim(config.hubUrl)}`);
|
|
98
|
+
|
|
99
|
+
// Show runtime model info
|
|
100
|
+
if (agent.runtimeModel) {
|
|
101
|
+
const runnerName = agent.runnerType
|
|
102
|
+
? getRunnerDisplayName(agent.runnerType as RunnerType)
|
|
103
|
+
: "Unknown";
|
|
104
|
+
console.log(` Model: ${pc.cyan(agent.runtimeModel)}`);
|
|
105
|
+
console.log(` Runner: ${pc.dim(runnerName)}`);
|
|
106
|
+
}
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
function formatExpiry(expiry: Date | null): string {
|
package/src/config/schema.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface AgentState {
|
|
|
39
39
|
tmuxSession: string;
|
|
40
40
|
startedAt: string;
|
|
41
41
|
token?: string;
|
|
42
|
+
workdir?: string;
|
|
43
|
+
assignedProject?: string;
|
|
42
44
|
/** The effective runtime model (resolved from CLI > agent > defaults) */
|
|
43
45
|
runtimeModel?: string;
|
|
44
46
|
/** The runner type (opencode, claude, custom) */
|
package/src/core/daemon.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
1
5
|
import {
|
|
2
6
|
addAgentToState,
|
|
3
7
|
getAgentState,
|
|
4
8
|
loadConfig,
|
|
5
|
-
loadState,
|
|
6
9
|
removeAgentFromState,
|
|
7
10
|
updateAgentInState,
|
|
8
11
|
} from "../config/loader.js";
|
|
@@ -10,8 +13,13 @@ import type { AgentConfig, Config } from "../config/schema.js";
|
|
|
10
13
|
import { loadContext, loadOrCreateContext, saveContext } from "../context/index.js";
|
|
11
14
|
import { Heartbeat } from "./heartbeat.js";
|
|
12
15
|
import { handleWebSocketEvent, injectRestoredContext, injectStartupMessage } from "./injector.js";
|
|
13
|
-
import { checkInbox, registerAgent } from "./registry.js";
|
|
14
|
-
import {
|
|
16
|
+
import { checkInbox, fetchAssignments, registerAgent } from "./registry.js";
|
|
17
|
+
import {
|
|
18
|
+
buildRunnerConfig,
|
|
19
|
+
detectRunner,
|
|
20
|
+
getRunnerDisplayName,
|
|
21
|
+
type RunnerConfig,
|
|
22
|
+
} from "./runner.js";
|
|
15
23
|
import {
|
|
16
24
|
captureSessionContext,
|
|
17
25
|
createSession,
|
|
@@ -30,6 +38,8 @@ export interface DaemonOptions {
|
|
|
30
38
|
daemonize?: boolean;
|
|
31
39
|
/** Whether to restore context from previous session (default: true) */
|
|
32
40
|
restoreContext?: boolean;
|
|
41
|
+
/** Auto-clone repository for project assignments */
|
|
42
|
+
autoSetup?: boolean;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
export class AgentDaemon {
|
|
@@ -42,7 +52,9 @@ export class AgentDaemon {
|
|
|
42
52
|
private token: string | null = null;
|
|
43
53
|
private agentId: string | null = null;
|
|
44
54
|
private isRunning = false;
|
|
55
|
+
private assignedProject: string | undefined;
|
|
45
56
|
private shouldRestoreContext: boolean;
|
|
57
|
+
private autoSetup: boolean;
|
|
46
58
|
|
|
47
59
|
constructor(options: DaemonOptions) {
|
|
48
60
|
const config = loadConfig();
|
|
@@ -53,6 +65,7 @@ export class AgentDaemon {
|
|
|
53
65
|
this.config = config;
|
|
54
66
|
this.agentName = options.name;
|
|
55
67
|
this.shouldRestoreContext = options.restoreContext !== false;
|
|
68
|
+
this.autoSetup = options.autoSetup === true;
|
|
56
69
|
|
|
57
70
|
// Find or create agent config
|
|
58
71
|
let agentConfig = config.agents.find((a) => a.name === options.name);
|
|
@@ -84,46 +97,6 @@ export class AgentDaemon {
|
|
|
84
97
|
const runnerName = getRunnerDisplayName(this.runnerConfig.type);
|
|
85
98
|
console.log(`Runner: ${runnerName}`);
|
|
86
99
|
console.log(`Effective model: ${this.runnerConfig.model}`);
|
|
87
|
-
|
|
88
|
-
// Check workdir conflicts - prevent multiple agents from using same directory
|
|
89
|
-
this.checkWorkdirConflict(agentConfig.workdir);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Checks if another agent is already using the specified workdir
|
|
94
|
-
*/
|
|
95
|
-
private checkWorkdirConflict(workdir?: string): void {
|
|
96
|
-
if (!workdir) return;
|
|
97
|
-
|
|
98
|
-
const state = loadState();
|
|
99
|
-
const conflictingAgent = state.agents.find((a) => {
|
|
100
|
-
// Skip self
|
|
101
|
-
if (a.name === this.agentName) return false;
|
|
102
|
-
|
|
103
|
-
// Check if agent is actually running
|
|
104
|
-
if (!a.pid) return false;
|
|
105
|
-
try {
|
|
106
|
-
process.kill(a.pid, 0); // Check if process exists
|
|
107
|
-
} catch {
|
|
108
|
-
return false; // Process not running
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Check if session exists and has same workdir
|
|
112
|
-
// We need to check the config for this agent's workdir
|
|
113
|
-
const otherAgentConfig = this.config.agents.find((c) => c.name === a.name);
|
|
114
|
-
if (otherAgentConfig?.workdir === workdir) {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return false;
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (conflictingAgent) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Workdir conflict: Agent "${conflictingAgent.name}" is already using "${workdir}".\n` +
|
|
124
|
-
`Use a different --workdir or stop the other agent first.`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
100
|
}
|
|
128
101
|
|
|
129
102
|
async start(): Promise<void> {
|
|
@@ -134,6 +107,27 @@ export class AgentDaemon {
|
|
|
134
107
|
|
|
135
108
|
console.log(`Starting agent: ${this.agentName}`);
|
|
136
109
|
|
|
110
|
+
// Register with hub first (needed for assignment check)
|
|
111
|
+
console.log("Registering with AgentMesh hub...");
|
|
112
|
+
const existingState = getAgentState(this.agentName);
|
|
113
|
+
|
|
114
|
+
const registration = await registerAgent({
|
|
115
|
+
url: this.config.hubUrl,
|
|
116
|
+
apiKey: this.config.apiKey,
|
|
117
|
+
workspace: this.config.workspace,
|
|
118
|
+
agentId: existingState?.agentId || this.agentConfig.agentId,
|
|
119
|
+
agentName: this.agentName,
|
|
120
|
+
model: this.agentConfig.model || this.config.defaults.model,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.agentId = registration.agentId;
|
|
124
|
+
this.token = registration.token;
|
|
125
|
+
|
|
126
|
+
console.log(`Registered as: ${this.agentId}`);
|
|
127
|
+
|
|
128
|
+
// Check assignments and auto-setup workdir if needed (before creating tmux session)
|
|
129
|
+
await this.checkAssignments();
|
|
130
|
+
|
|
137
131
|
// Check if session already exists
|
|
138
132
|
const sessionName = getSessionName(this.agentName);
|
|
139
133
|
const sessionAlreadyExists = sessionExists(sessionName);
|
|
@@ -141,11 +135,13 @@ export class AgentDaemon {
|
|
|
141
135
|
// Create tmux session if it doesn't exist
|
|
142
136
|
if (!sessionAlreadyExists) {
|
|
143
137
|
console.log(`Creating tmux session: ${sessionName}`);
|
|
138
|
+
|
|
139
|
+
// Include runner env vars (e.g., OPENCODE_MODEL) at session creation
|
|
144
140
|
const created = createSession(
|
|
145
141
|
this.agentName,
|
|
146
142
|
this.agentConfig.command,
|
|
147
143
|
this.agentConfig.workdir,
|
|
148
|
-
this.runnerConfig.env, //
|
|
144
|
+
this.runnerConfig.env, // Apply model env at process start
|
|
149
145
|
);
|
|
150
146
|
|
|
151
147
|
if (!created) {
|
|
@@ -154,29 +150,9 @@ export class AgentDaemon {
|
|
|
154
150
|
} else {
|
|
155
151
|
console.log(`Reconnecting to existing session: ${sessionName}`);
|
|
156
152
|
// Update environment for existing session
|
|
157
|
-
|
|
158
|
-
updateSessionEnvironment(this.agentName, this.runnerConfig.env);
|
|
159
|
-
}
|
|
153
|
+
updateSessionEnvironment(this.agentName, this.runnerConfig.env);
|
|
160
154
|
}
|
|
161
155
|
|
|
162
|
-
// Register with hub
|
|
163
|
-
console.log("Registering with AgentMesh hub...");
|
|
164
|
-
const existingState = getAgentState(this.agentName);
|
|
165
|
-
|
|
166
|
-
const registration = await registerAgent({
|
|
167
|
-
url: this.config.hubUrl,
|
|
168
|
-
apiKey: this.config.apiKey,
|
|
169
|
-
workspace: this.config.workspace,
|
|
170
|
-
agentId: existingState?.agentId || this.agentConfig.agentId,
|
|
171
|
-
agentName: this.agentName,
|
|
172
|
-
model: this.agentConfig.model || this.config.defaults.model,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
this.agentId = registration.agentId;
|
|
176
|
-
this.token = registration.token;
|
|
177
|
-
|
|
178
|
-
console.log(`Registered as: ${this.agentId}`);
|
|
179
|
-
|
|
180
156
|
// Inject environment variables into tmux session
|
|
181
157
|
console.log("Injecting environment variables...");
|
|
182
158
|
updateSessionEnvironment(this.agentName, {
|
|
@@ -184,7 +160,7 @@ export class AgentDaemon {
|
|
|
184
160
|
AGENTMESH_AGENT_ID: this.agentId,
|
|
185
161
|
});
|
|
186
162
|
|
|
187
|
-
// Save state
|
|
163
|
+
// Save state including runtime model info
|
|
188
164
|
addAgentToState({
|
|
189
165
|
name: this.agentName,
|
|
190
166
|
agentId: this.agentId,
|
|
@@ -192,6 +168,8 @@ export class AgentDaemon {
|
|
|
192
168
|
tmuxSession: sessionName,
|
|
193
169
|
startedAt: new Date().toISOString(),
|
|
194
170
|
token: this.token,
|
|
171
|
+
workdir: this.agentConfig.workdir,
|
|
172
|
+
assignedProject: this.assignedProject,
|
|
195
173
|
runtimeModel: this.runnerConfig.model,
|
|
196
174
|
runnerType: this.runnerConfig.type,
|
|
197
175
|
});
|
|
@@ -376,4 +354,172 @@ Nudge agent:
|
|
|
376
354
|
console.error("Failed to save agent context:", error);
|
|
377
355
|
}
|
|
378
356
|
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Fetches assignments from HQ and validates workdir setup
|
|
360
|
+
* Uses project.workdir from HQ as source of truth, falls back to helpful instructions
|
|
361
|
+
*/
|
|
362
|
+
private async checkAssignments(): Promise<void> {
|
|
363
|
+
if (!this.token) return;
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
console.log("Fetching project assignments from HQ...");
|
|
367
|
+
const assignments = await fetchAssignments(this.config.hubUrl, this.token);
|
|
368
|
+
|
|
369
|
+
if (assignments.length === 0) {
|
|
370
|
+
console.log("No project assignments found.");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
console.log(`Found ${assignments.length} assignment(s):`);
|
|
375
|
+
for (const assignment of assignments) {
|
|
376
|
+
const repoInfo = assignment.repo ? ` -> ${assignment.repo.full_name}` : "";
|
|
377
|
+
const workdirInfo = assignment.project.workdir ? ` [${assignment.project.workdir}]` : "";
|
|
378
|
+
console.log(` - ${assignment.project.name} (${assignment.role})${repoInfo}${workdirInfo}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
this.assignedProject = assignments[0]?.project.name;
|
|
382
|
+
|
|
383
|
+
// If no CLI workdir specified, try to use project.workdir from HQ
|
|
384
|
+
if (!this.agentConfig.workdir) {
|
|
385
|
+
const assignmentWithWorkdir = assignments.find((a) => a.project.workdir);
|
|
386
|
+
if (assignmentWithWorkdir?.project.workdir) {
|
|
387
|
+
console.log(
|
|
388
|
+
`Using workdir from project settings: ${assignmentWithWorkdir.project.workdir}`,
|
|
389
|
+
);
|
|
390
|
+
this.agentConfig.workdir = assignmentWithWorkdir.project.workdir;
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// No project.workdir set, check if we have a repo assignment
|
|
395
|
+
const repoAssignment = assignments.find((a) => a.repo !== null);
|
|
396
|
+
if (repoAssignment) {
|
|
397
|
+
const repo = repoAssignment.repo!;
|
|
398
|
+
const expandedPath = path.join(
|
|
399
|
+
os.homedir(),
|
|
400
|
+
".agentmesh",
|
|
401
|
+
"workspaces",
|
|
402
|
+
this.config.workspace,
|
|
403
|
+
repoAssignment.project.code.toLowerCase(),
|
|
404
|
+
this.agentName,
|
|
405
|
+
);
|
|
406
|
+
const suggestedPath = `~/.agentmesh/workspaces/${this.config.workspace}/${repoAssignment.project.code.toLowerCase()}/${this.agentName}`;
|
|
407
|
+
|
|
408
|
+
// If --auto-setup is enabled, automatically clone the repo
|
|
409
|
+
if (this.autoSetup) {
|
|
410
|
+
this.agentConfig.workdir = this.setupWorkspace(
|
|
411
|
+
expandedPath,
|
|
412
|
+
repo.url,
|
|
413
|
+
repo.default_branch,
|
|
414
|
+
repoAssignment.project.name,
|
|
415
|
+
);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
console.error(`
|
|
420
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
421
|
+
⚠️ WORKDIR REQUIRED
|
|
422
|
+
|
|
423
|
+
You have a project assignment with a repository, but no workdir is configured.
|
|
424
|
+
|
|
425
|
+
Project: ${repoAssignment.project.name}
|
|
426
|
+
Repo: ${repo.full_name}
|
|
427
|
+
Branch: ${repo.default_branch}
|
|
428
|
+
|
|
429
|
+
Option 1: Set workdir in project settings (recommended)
|
|
430
|
+
- Go to AgentMesh HQ → Projects → ${repoAssignment.project.name} → Settings
|
|
431
|
+
- Set the workdir field to the local path
|
|
432
|
+
|
|
433
|
+
Option 2: Set up workspace manually and pass --workdir:
|
|
434
|
+
|
|
435
|
+
mkdir -p ${suggestedPath}
|
|
436
|
+
git clone ${repo.url} ${suggestedPath}
|
|
437
|
+
cd ${suggestedPath} && git checkout ${repo.default_branch}
|
|
438
|
+
|
|
439
|
+
Then start the agent with:
|
|
440
|
+
|
|
441
|
+
agentmesh start -n ${this.agentName} --workdir ${suggestedPath}
|
|
442
|
+
|
|
443
|
+
Option 3: Use --auto-setup to automatically clone the repository:
|
|
444
|
+
|
|
445
|
+
agentmesh start -n ${this.agentName} --auto-setup
|
|
446
|
+
|
|
447
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
448
|
+
`);
|
|
449
|
+
// No session to clean up - we haven't created it yet
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} catch (error) {
|
|
454
|
+
// Non-fatal: log and continue (HQ might not have assignments feature yet)
|
|
455
|
+
console.log("Could not fetch assignments:", (error as Error).message);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Sets up workspace by cloning repository or using existing clone
|
|
461
|
+
* Returns the absolute path to the workspace
|
|
462
|
+
*/
|
|
463
|
+
private setupWorkspace(
|
|
464
|
+
workspacePath: string,
|
|
465
|
+
repoUrl: string,
|
|
466
|
+
defaultBranch: string,
|
|
467
|
+
projectName: string,
|
|
468
|
+
): string {
|
|
469
|
+
console.log(
|
|
470
|
+
`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
|
|
471
|
+
);
|
|
472
|
+
console.log(`🔧 AUTO-SETUP: Setting up workspace for ${projectName}`);
|
|
473
|
+
console.log(
|
|
474
|
+
`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`,
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// Check if directory already exists and is a git repo
|
|
478
|
+
const gitDir = path.join(workspacePath, ".git");
|
|
479
|
+
if (fs.existsSync(gitDir)) {
|
|
480
|
+
console.log(`✓ Workspace already exists: ${workspacePath}`);
|
|
481
|
+
console.log(` Updating from remote...`);
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
// Fetch and checkout the branch
|
|
485
|
+
execSync(`git fetch origin`, { cwd: workspacePath, stdio: "inherit" });
|
|
486
|
+
execSync(`git checkout ${defaultBranch}`, { cwd: workspacePath, stdio: "inherit" });
|
|
487
|
+
execSync(`git pull origin ${defaultBranch}`, { cwd: workspacePath, stdio: "inherit" });
|
|
488
|
+
console.log(`✓ Workspace updated successfully\n`);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
console.warn(`⚠ Could not update workspace: ${(error as Error).message}`);
|
|
491
|
+
console.log(` Continuing with existing state...\n`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return workspacePath;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Create parent directories
|
|
498
|
+
const parentDir = path.dirname(workspacePath);
|
|
499
|
+
if (!fs.existsSync(parentDir)) {
|
|
500
|
+
console.log(`Creating directory: ${parentDir}`);
|
|
501
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Clone the repository
|
|
505
|
+
console.log(`Cloning repository...`);
|
|
506
|
+
console.log(` URL: ${repoUrl}`);
|
|
507
|
+
console.log(` Path: ${workspacePath}`);
|
|
508
|
+
console.log(` Branch: ${defaultBranch}\n`);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
execSync(`git clone --branch ${defaultBranch} "${repoUrl}" "${workspacePath}"`, {
|
|
512
|
+
stdio: "inherit",
|
|
513
|
+
});
|
|
514
|
+
console.log(`\n✓ Repository cloned successfully`);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error(`\n✗ Failed to clone repository: ${(error as Error).message}`);
|
|
517
|
+
console.error(`\nMake sure you have access to the repository and SSH keys are configured.`);
|
|
518
|
+
// No session to clean up - we haven't created it yet
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
console.log(`✓ Workspace ready: ${workspacePath}\n`);
|
|
523
|
+
return workspacePath;
|
|
524
|
+
}
|
|
379
525
|
}
|
package/src/core/registry.ts
CHANGED
|
@@ -27,9 +27,7 @@ export interface InboxItem {
|
|
|
27
27
|
created_at: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export async function registerAgent(
|
|
31
|
-
options: RegisterOptions
|
|
32
|
-
): Promise<RegisterResult> {
|
|
30
|
+
export async function registerAgent(options: RegisterOptions): Promise<RegisterResult> {
|
|
33
31
|
const agentId = options.agentId || randomUUID();
|
|
34
32
|
|
|
35
33
|
const response = await fetch(`${options.url}/api/v1/agents/register`, {
|
|
@@ -65,32 +63,27 @@ export async function registerAgent(
|
|
|
65
63
|
export async function checkInbox(
|
|
66
64
|
url: string,
|
|
67
65
|
workspace: string,
|
|
68
|
-
token: string
|
|
66
|
+
token: string,
|
|
69
67
|
): Promise<InboxItem[]> {
|
|
70
|
-
const response = await fetch(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
);
|
|
68
|
+
const response = await fetch(`${url}/api/v1/workspaces/${workspace}/inbox`, {
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: `Bearer ${token}`,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
78
73
|
|
|
79
74
|
if (!response.ok) {
|
|
80
75
|
throw new Error(`Failed to check inbox: ${response.status}`);
|
|
81
76
|
}
|
|
82
77
|
|
|
83
78
|
const data = await response.json();
|
|
84
|
-
return (data.data || []).filter(
|
|
85
|
-
(item: InboxItem) => item.status === "pending"
|
|
86
|
-
);
|
|
79
|
+
return (data.data || []).filter((item: InboxItem) => item.status === "pending");
|
|
87
80
|
}
|
|
88
81
|
|
|
89
82
|
export async function sendNudge(
|
|
90
83
|
url: string,
|
|
91
84
|
agentId: string,
|
|
92
85
|
message: string,
|
|
93
|
-
token: string
|
|
86
|
+
token: string,
|
|
94
87
|
): Promise<boolean> {
|
|
95
88
|
const response = await fetch(`${url}/api/v1/agents/${agentId}/nudge`, {
|
|
96
89
|
method: "POST",
|
|
@@ -103,3 +96,51 @@ export async function sendNudge(
|
|
|
103
96
|
|
|
104
97
|
return response.ok;
|
|
105
98
|
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Agent Self-Discovery
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
export interface ProjectAssignment {
|
|
105
|
+
assignment_id: string;
|
|
106
|
+
role: string;
|
|
107
|
+
status: string;
|
|
108
|
+
notes: string | null;
|
|
109
|
+
priority: number;
|
|
110
|
+
created_at: string;
|
|
111
|
+
project: {
|
|
112
|
+
project_id: string;
|
|
113
|
+
name: string;
|
|
114
|
+
code: string;
|
|
115
|
+
description: string | null;
|
|
116
|
+
workdir: string | null;
|
|
117
|
+
};
|
|
118
|
+
repo: {
|
|
119
|
+
repo_id: string;
|
|
120
|
+
provider: string;
|
|
121
|
+
full_name: string;
|
|
122
|
+
url: string;
|
|
123
|
+
default_branch: string;
|
|
124
|
+
} | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Fetch the agent's project assignments from HQ
|
|
129
|
+
*/
|
|
130
|
+
export async function fetchAssignments(url: string, token: string): Promise<ProjectAssignment[]> {
|
|
131
|
+
const response = await fetch(`${url}/api/v1/agents/me/assignments`, {
|
|
132
|
+
headers: {
|
|
133
|
+
Authorization: `Bearer ${token}`,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
if (response.status === 404) {
|
|
139
|
+
return []; // No assignments
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`Failed to fetch assignments: ${response.status}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const data = (await response.json()) as { data: ProjectAssignment[] };
|
|
145
|
+
return data.data || [];
|
|
146
|
+
}
|
package/src/core/tmux.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ChildProcess, execSync, spawn } from "node:child_process";
|
|
1
|
+
import { type ChildProcess, execFileSync, execSync, spawn } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
const SESSION_PREFIX = "agentmesh-";
|
|
4
4
|
|
|
@@ -64,7 +64,7 @@ export function createSession(
|
|
|
64
64
|
|
|
65
65
|
args.push(fullCommand);
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
execFileSync("tmux", args);
|
|
68
68
|
|
|
69
69
|
// Also set session environment for any subsequent processes/refreshes
|
|
70
70
|
if (env) {
|
|
@@ -82,7 +82,7 @@ export function setSessionEnvironment(sessionName: string, env: SessionEnv): boo
|
|
|
82
82
|
try {
|
|
83
83
|
for (const [key, value] of Object.entries(env)) {
|
|
84
84
|
if (value !== undefined && value !== "") {
|
|
85
|
-
|
|
85
|
+
execFileSync("tmux", ["set-environment", "-t", sessionName, key, value]);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
return true;
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 therajushahi
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|