@agentmeshhq/agent 0.1.8 → 0.1.9
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/__tests__/runner.test.d.ts +1 -0
- package/dist/__tests__/runner.test.js +87 -0
- package/dist/__tests__/runner.test.js.map +1 -0
- package/dist/cli/index.js +3 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/restart.d.ts +4 -1
- package/dist/cli/restart.js +4 -2
- package/dist/cli/restart.js.map +1 -1
- package/dist/cli/status.js +5 -1
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/whoami.js +17 -0
- package/dist/cli/whoami.js.map +1 -1
- package/dist/config/schema.d.ts +5 -0
- package/dist/core/daemon.d.ts +5 -0
- package/dist/core/daemon.js +55 -2
- package/dist/core/daemon.js.map +1 -1
- package/dist/core/runner.d.ts +49 -0
- package/dist/core/runner.js +148 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/core/tmux.d.ts +3 -0
- package/dist/core/tmux.js +23 -7
- package/dist/core/tmux.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/runner.test.ts +105 -0
- package/src/cli/index.ts +3 -2
- package/src/cli/restart.ts +9 -2
- package/src/cli/status.ts +6 -1
- package/src/cli/whoami.ts +18 -0
- package/src/config/schema.ts +6 -0
- package/src/core/daemon.ts +62 -0
- package/src/core/runner.ts +200 -0
- package/src/core/tmux.ts +28 -9
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runner Module
|
|
3
|
+
* Handles model resolution, validation, and runner-specific configuration
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Runner Detection
|
|
8
|
+
// ============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* Detects the runner type from the command
|
|
11
|
+
*/
|
|
12
|
+
export function detectRunner(command) {
|
|
13
|
+
const cmd = command.toLowerCase().trim();
|
|
14
|
+
if (cmd === "opencode" || cmd.startsWith("opencode ")) {
|
|
15
|
+
return "opencode";
|
|
16
|
+
}
|
|
17
|
+
if (cmd === "claude" || cmd.startsWith("claude ")) {
|
|
18
|
+
return "claude";
|
|
19
|
+
}
|
|
20
|
+
return "custom";
|
|
21
|
+
}
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Model Resolution
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the effective model from CLI > agent config > defaults
|
|
27
|
+
*/
|
|
28
|
+
export function resolveModel(input) {
|
|
29
|
+
// Priority: CLI flag > agent config > default
|
|
30
|
+
return input.cliModel || input.agentModel || input.defaultModel;
|
|
31
|
+
}
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// OpenCode Integration
|
|
34
|
+
// ============================================================================
|
|
35
|
+
let cachedOpenCodeModels = null;
|
|
36
|
+
/**
|
|
37
|
+
* Gets available OpenCode models (cached)
|
|
38
|
+
*/
|
|
39
|
+
export function getOpenCodeModels() {
|
|
40
|
+
if (cachedOpenCodeModels) {
|
|
41
|
+
return cachedOpenCodeModels;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const output = execSync("opencode models 2>/dev/null", {
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
timeout: 10000,
|
|
47
|
+
});
|
|
48
|
+
cachedOpenCodeModels = output
|
|
49
|
+
.split("\n")
|
|
50
|
+
.map((line) => line.trim())
|
|
51
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
52
|
+
return cachedOpenCodeModels;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// OpenCode not available or failed
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validates that a model is available in OpenCode
|
|
61
|
+
*/
|
|
62
|
+
export function validateOpenCodeModel(model) {
|
|
63
|
+
const models = getOpenCodeModels();
|
|
64
|
+
// If we couldn't get models list, allow any (graceful degradation)
|
|
65
|
+
if (models.length === 0) {
|
|
66
|
+
return { valid: true };
|
|
67
|
+
}
|
|
68
|
+
if (models.includes(model)) {
|
|
69
|
+
return { valid: true };
|
|
70
|
+
}
|
|
71
|
+
// Check for partial match (e.g., "claude-sonnet-4" matches "anthropic/claude-sonnet-4")
|
|
72
|
+
const partialMatch = models.find((m) => m.endsWith(`/${model}`) || m === model || m.split("/").pop() === model);
|
|
73
|
+
if (partialMatch) {
|
|
74
|
+
return { valid: true };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
valid: false,
|
|
78
|
+
error: `Model "${model}" not found in OpenCode. Run 'opencode models' to see available models.`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Normalizes a model name for OpenCode
|
|
83
|
+
* e.g., "claude-sonnet-4" -> "anthropic/claude-sonnet-4" if that's what OpenCode expects
|
|
84
|
+
*/
|
|
85
|
+
export function normalizeOpenCodeModel(model) {
|
|
86
|
+
const models = getOpenCodeModels();
|
|
87
|
+
// Direct match
|
|
88
|
+
if (models.includes(model)) {
|
|
89
|
+
return model;
|
|
90
|
+
}
|
|
91
|
+
// Try to find full path version
|
|
92
|
+
const fullPath = models.find((m) => m.endsWith(`/${model}`) || m.split("/").pop() === model);
|
|
93
|
+
return fullPath || model;
|
|
94
|
+
}
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Runner Configuration
|
|
97
|
+
// ============================================================================
|
|
98
|
+
/**
|
|
99
|
+
* Builds the complete runner configuration including environment variables
|
|
100
|
+
*/
|
|
101
|
+
export function buildRunnerConfig(input) {
|
|
102
|
+
const runnerType = detectRunner(input.command);
|
|
103
|
+
const model = resolveModel(input);
|
|
104
|
+
const env = {};
|
|
105
|
+
switch (runnerType) {
|
|
106
|
+
case "opencode": {
|
|
107
|
+
// Validate model for OpenCode
|
|
108
|
+
const validation = validateOpenCodeModel(model);
|
|
109
|
+
if (!validation.valid) {
|
|
110
|
+
console.warn(`Warning: ${validation.error}`);
|
|
111
|
+
}
|
|
112
|
+
// Normalize and set OPENCODE_MODEL
|
|
113
|
+
const normalizedModel = normalizeOpenCodeModel(model);
|
|
114
|
+
env.OPENCODE_MODEL = normalizedModel;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "claude": {
|
|
118
|
+
// Claude CLI uses ANTHROPIC_MODEL or similar
|
|
119
|
+
// For now, just pass the model - Claude CLI will handle it
|
|
120
|
+
env.CLAUDE_MODEL = model;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "custom":
|
|
124
|
+
// Custom runners don't get automatic model env
|
|
125
|
+
// User is responsible for configuring their tool
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
type: runnerType,
|
|
130
|
+
command: input.command,
|
|
131
|
+
model,
|
|
132
|
+
env,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Gets a human-readable runner name
|
|
137
|
+
*/
|
|
138
|
+
export function getRunnerDisplayName(runnerType) {
|
|
139
|
+
switch (runnerType) {
|
|
140
|
+
case "opencode":
|
|
141
|
+
return "OpenCode";
|
|
142
|
+
case "claude":
|
|
143
|
+
return "Claude CLI";
|
|
144
|
+
case "custom":
|
|
145
|
+
return "Custom";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAsB9C,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAEzC,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QACtD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAA2B;IACtD,8CAA8C;IAC9C,OAAO,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,YAAY,CAAC;AAClE,CAAC;AAED,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E,IAAI,oBAAoB,GAAoB,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,oBAAoB,EAAE,CAAC;QACzB,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,6BAA6B,EAAE;YACrD,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QAEH,oBAAoB,GAAG,MAAM;aAC1B,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAE9D,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;QACnC,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IAEnC,mEAAmE;IACnE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,wFAAwF;IACxF,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAC9B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,CAC9E,CAAC;IAEF,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,UAAU,KAAK,yEAAyE;KAChG,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IAEnC,eAAe;IACf,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gCAAgC;IAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,CAAC,CAAC;IAE7F,OAAO,QAAQ,IAAI,KAAK,CAAC;AAC3B,CAAC;AAED,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAA2B;IAC3D,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,GAAG,GAA2B,EAAE,CAAC;IAEvC,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,8BAA8B;YAC9B,MAAM,UAAU,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CAAC,YAAY,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;YAC/C,CAAC;YAED,mCAAmC;YACnC,MAAM,eAAe,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;YACtD,GAAG,CAAC,cAAc,GAAG,eAAe,CAAC;YACrC,MAAM;QACR,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,6CAA6C;YAC7C,2DAA2D;YAC3D,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;YACzB,MAAM;QACR,CAAC;QAED,KAAK,QAAQ;YACX,+CAA+C;YAC/C,iDAAiD;YACjD,MAAM;IACV,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK;QACL,GAAG;KACJ,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAsB;IACzD,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,YAAY,CAAC;QACtB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;IACpB,CAAC;AACH,CAAC"}
|
package/dist/core/tmux.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export declare function sessionExists(sessionName: string): boolean;
|
|
|
3
3
|
export interface SessionEnv {
|
|
4
4
|
AGENT_TOKEN?: string;
|
|
5
5
|
AGENTMESH_AGENT_ID?: string;
|
|
6
|
+
OPENCODE_MODEL?: string;
|
|
7
|
+
CLAUDE_MODEL?: string;
|
|
8
|
+
[key: string]: string | undefined;
|
|
6
9
|
}
|
|
7
10
|
export declare function createSession(agentName: string, command: string, workdir?: string, env?: SessionEnv): boolean;
|
|
8
11
|
export declare function setSessionEnvironment(sessionName: string, env: SessionEnv): boolean;
|
package/dist/core/tmux.js
CHANGED
|
@@ -19,13 +19,30 @@ export function createSession(agentName, command, workdir, env) {
|
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
try {
|
|
22
|
+
// Build environment prefix for the command
|
|
23
|
+
// This ensures env vars are set BEFORE the process starts
|
|
24
|
+
let envPrefix = "";
|
|
25
|
+
if (env) {
|
|
26
|
+
const envParts = [];
|
|
27
|
+
for (const [key, value] of Object.entries(env)) {
|
|
28
|
+
if (value !== undefined && value !== "") {
|
|
29
|
+
// Escape special characters in value
|
|
30
|
+
const escapedValue = value.replace(/"/g, '\\"');
|
|
31
|
+
envParts.push(`${key}="${escapedValue}"`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (envParts.length > 0) {
|
|
35
|
+
envPrefix = envParts.join(" ") + " ";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const fullCommand = `${envPrefix}${command}`;
|
|
22
39
|
const args = ["new-session", "-d", "-s", sessionName];
|
|
23
40
|
if (workdir) {
|
|
24
41
|
args.push("-c", workdir);
|
|
25
42
|
}
|
|
26
|
-
args.push(
|
|
43
|
+
args.push(fullCommand);
|
|
27
44
|
execSync(`tmux ${args.join(" ")}`);
|
|
28
|
-
//
|
|
45
|
+
// Also set session environment for any subsequent processes/refreshes
|
|
29
46
|
if (env) {
|
|
30
47
|
setSessionEnvironment(sessionName, env);
|
|
31
48
|
}
|
|
@@ -38,11 +55,10 @@ export function createSession(agentName, command, workdir, env) {
|
|
|
38
55
|
}
|
|
39
56
|
export function setSessionEnvironment(sessionName, env) {
|
|
40
57
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
execSync(`tmux set-environment -t "${sessionName}" AGENTMESH_AGENT_ID "${env.AGENTMESH_AGENT_ID}"`);
|
|
58
|
+
for (const [key, value] of Object.entries(env)) {
|
|
59
|
+
if (value !== undefined && value !== "") {
|
|
60
|
+
execSync(`tmux set-environment -t "${sessionName}" ${key} "${value}"`);
|
|
61
|
+
}
|
|
46
62
|
}
|
|
47
63
|
return true;
|
|
48
64
|
}
|
package/dist/core/tmux.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmux.js","sourceRoot":"","sources":["../../src/core/tmux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,QAAQ,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,cAAc,GAAG,YAAY,CAAC;AAEpC,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OAAO,GAAG,cAAc,GAAG,SAAS,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,IAAI,CAAC;QACH,QAAQ,CAAC,wBAAwB,WAAW,eAAe,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;
|
|
1
|
+
{"version":3,"file":"tmux.js","sourceRoot":"","sources":["../../src/core/tmux.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildRunnerConfig,
|
|
4
|
+
detectRunner,
|
|
5
|
+
getRunnerDisplayName,
|
|
6
|
+
type RunnerType,
|
|
7
|
+
resolveModel,
|
|
8
|
+
} from "../core/runner.js";
|
|
9
|
+
|
|
10
|
+
describe("Runner Module", () => {
|
|
11
|
+
describe("detectRunner", () => {
|
|
12
|
+
it("should detect opencode runner", () => {
|
|
13
|
+
expect(detectRunner("opencode")).toBe("opencode");
|
|
14
|
+
expect(detectRunner("opencode --some-flag")).toBe("opencode");
|
|
15
|
+
expect(detectRunner("OPENCODE")).toBe("opencode");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should detect claude runner", () => {
|
|
19
|
+
expect(detectRunner("claude")).toBe("claude");
|
|
20
|
+
expect(detectRunner("claude --some-flag")).toBe("claude");
|
|
21
|
+
expect(detectRunner("CLAUDE")).toBe("claude");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should detect custom runner for unknown commands", () => {
|
|
25
|
+
expect(detectRunner("aider")).toBe("custom");
|
|
26
|
+
expect(detectRunner("cursor")).toBe("custom");
|
|
27
|
+
expect(detectRunner("my-custom-tool")).toBe("custom");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("resolveModel", () => {
|
|
32
|
+
it("should prioritize CLI model over all others", () => {
|
|
33
|
+
const result = resolveModel({
|
|
34
|
+
cliModel: "cli-model",
|
|
35
|
+
agentModel: "agent-model",
|
|
36
|
+
defaultModel: "default-model",
|
|
37
|
+
command: "opencode",
|
|
38
|
+
});
|
|
39
|
+
expect(result).toBe("cli-model");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should use agent model when CLI not provided", () => {
|
|
43
|
+
const result = resolveModel({
|
|
44
|
+
agentModel: "agent-model",
|
|
45
|
+
defaultModel: "default-model",
|
|
46
|
+
command: "opencode",
|
|
47
|
+
});
|
|
48
|
+
expect(result).toBe("agent-model");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should fall back to default model", () => {
|
|
52
|
+
const result = resolveModel({
|
|
53
|
+
defaultModel: "default-model",
|
|
54
|
+
command: "opencode",
|
|
55
|
+
});
|
|
56
|
+
expect(result).toBe("default-model");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("buildRunnerConfig", () => {
|
|
61
|
+
it("should build config for opencode with OPENCODE_MODEL env", () => {
|
|
62
|
+
const config = buildRunnerConfig({
|
|
63
|
+
cliModel: "claude-sonnet-4",
|
|
64
|
+
defaultModel: "claude-sonnet-4",
|
|
65
|
+
command: "opencode",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(config.type).toBe("opencode");
|
|
69
|
+
expect(config.model).toBe("claude-sonnet-4");
|
|
70
|
+
expect(config.env.OPENCODE_MODEL).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should build config for claude with CLAUDE_MODEL env", () => {
|
|
74
|
+
const config = buildRunnerConfig({
|
|
75
|
+
cliModel: "claude-sonnet-4",
|
|
76
|
+
defaultModel: "claude-sonnet-4",
|
|
77
|
+
command: "claude",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(config.type).toBe("claude");
|
|
81
|
+
expect(config.model).toBe("claude-sonnet-4");
|
|
82
|
+
expect(config.env.CLAUDE_MODEL).toBe("claude-sonnet-4");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should build config for custom runner without env vars", () => {
|
|
86
|
+
const config = buildRunnerConfig({
|
|
87
|
+
cliModel: "gpt-4",
|
|
88
|
+
defaultModel: "claude-sonnet-4",
|
|
89
|
+
command: "aider",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(config.type).toBe("custom");
|
|
93
|
+
expect(config.model).toBe("gpt-4");
|
|
94
|
+
expect(Object.keys(config.env)).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("getRunnerDisplayName", () => {
|
|
99
|
+
it("should return correct display names", () => {
|
|
100
|
+
expect(getRunnerDisplayName("opencode")).toBe("OpenCode");
|
|
101
|
+
expect(getRunnerDisplayName("claude")).toBe("Claude CLI");
|
|
102
|
+
expect(getRunnerDisplayName("custom")).toBe("Custom");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
package/src/cli/index.ts
CHANGED
|
@@ -168,9 +168,10 @@ program
|
|
|
168
168
|
.command("restart")
|
|
169
169
|
.description("Restart an agent (preserves agent ID)")
|
|
170
170
|
.argument("<name>", "Agent name")
|
|
171
|
-
.
|
|
171
|
+
.option("-m, --model <model>", "Model to use (default: preserve previous model)")
|
|
172
|
+
.action(async (name, options) => {
|
|
172
173
|
try {
|
|
173
|
-
await restart(name);
|
|
174
|
+
await restart(name, { model: options.model });
|
|
174
175
|
} catch (error) {
|
|
175
176
|
console.error(pc.red((error as Error).message));
|
|
176
177
|
process.exit(1);
|
package/src/cli/restart.ts
CHANGED
|
@@ -4,7 +4,11 @@ import { destroySession, getSessionName, sessionExists } from "../core/tmux.js";
|
|
|
4
4
|
import { start } from "./start.js";
|
|
5
5
|
import { stop } from "./stop.js";
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export interface RestartOptions {
|
|
8
|
+
model?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function restart(name: string, options: RestartOptions = {}): Promise<void> {
|
|
8
12
|
const config = loadConfig();
|
|
9
13
|
|
|
10
14
|
if (!config) {
|
|
@@ -34,6 +38,9 @@ export async function restart(name: string): Promise<void> {
|
|
|
34
38
|
// Find agent config to get workdir and other settings
|
|
35
39
|
const agentConfig = config.agents.find((a) => a.name === name);
|
|
36
40
|
|
|
41
|
+
// Model priority: CLI option > previous runtime model > agent config
|
|
42
|
+
const effectiveModel = options.model || agent.runtimeModel || agentConfig?.model;
|
|
43
|
+
|
|
37
44
|
console.log(pc.dim("Starting new session..."));
|
|
38
45
|
|
|
39
46
|
// Start with the same settings, agent ID will be reused from state
|
|
@@ -41,7 +48,7 @@ export async function restart(name: string): Promise<void> {
|
|
|
41
48
|
name,
|
|
42
49
|
command: agentConfig?.command,
|
|
43
50
|
workdir: agentConfig?.workdir,
|
|
44
|
-
model:
|
|
51
|
+
model: effectiveModel,
|
|
45
52
|
foreground: false,
|
|
46
53
|
});
|
|
47
54
|
|
package/src/cli/status.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { loadConfig, loadState } from "../config/loader.js";
|
|
3
|
+
import { getRunnerDisplayName } from "../core/runner.js";
|
|
3
4
|
import { getSessionName, sessionExists } from "../core/tmux.js";
|
|
4
5
|
|
|
5
6
|
interface HealthResponse {
|
|
@@ -60,7 +61,11 @@ export async function status(): Promise<void> {
|
|
|
60
61
|
console.log(` Total: ${pc.dim(String(state.agents.length))}`);
|
|
61
62
|
|
|
62
63
|
if (runningAgents.length > 0) {
|
|
63
|
-
console.log(`
|
|
64
|
+
console.log(` Active:`);
|
|
65
|
+
for (const agent of runningAgents) {
|
|
66
|
+
const modelInfo = agent.runtimeModel ? ` [${agent.runtimeModel}]` : "";
|
|
67
|
+
console.log(` - ${pc.cyan(agent.name)}${pc.dim(modelInfo)}`);
|
|
68
|
+
}
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
// Check tmux
|
package/src/cli/whoami.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { loadConfig, loadState } from "../config/loader.js";
|
|
3
|
+
import { getRunnerDisplayName, type RunnerType } from "../core/runner.js";
|
|
3
4
|
import { decodeToken, getTokenExpiry } from "../utils/jwt.js";
|
|
4
5
|
|
|
5
6
|
export async function whoami(agentName?: string): Promise<void> {
|
|
@@ -23,9 +24,18 @@ export async function whoami(agentName?: string): Promise<void> {
|
|
|
23
24
|
const expiry = getTokenExpiry(envToken);
|
|
24
25
|
const expiryStr = formatExpiry(expiry);
|
|
25
26
|
|
|
27
|
+
// Find the agent in state to get model info
|
|
28
|
+
const agentState = state.agents.find((a) => a.agentId === envAgentId);
|
|
29
|
+
|
|
26
30
|
console.log(pc.bold("Current Agent"));
|
|
27
31
|
console.log(` ID: ${pc.cyan(envAgentId)}`);
|
|
28
32
|
console.log(` Workspace: ${pc.dim(config.workspace)}`);
|
|
33
|
+
if (agentState?.runtimeModel) {
|
|
34
|
+
const runnerName = agentState.runnerType
|
|
35
|
+
? getRunnerDisplayName(agentState.runnerType)
|
|
36
|
+
: "Unknown";
|
|
37
|
+
console.log(` Model: ${pc.cyan(agentState.runtimeModel)} (${pc.dim(runnerName)})`);
|
|
38
|
+
}
|
|
29
39
|
console.log(` Token: ${expiryStr}`);
|
|
30
40
|
console.log(` Hub: ${pc.dim(config.hubUrl)}`);
|
|
31
41
|
return;
|
|
@@ -49,6 +59,10 @@ export async function whoami(agentName?: string): Promise<void> {
|
|
|
49
59
|
console.log(` ${pc.cyan(agent.name)}`);
|
|
50
60
|
console.log(` ID: ${pc.dim(agent.agentId)}`);
|
|
51
61
|
console.log(` Status: ${running ? pc.green("running") : pc.yellow("stopped")}`);
|
|
62
|
+
if (agent.runtimeModel) {
|
|
63
|
+
const runnerName = agent.runnerType ? getRunnerDisplayName(agent.runnerType) : "Unknown";
|
|
64
|
+
console.log(` Model: ${pc.dim(agent.runtimeModel)} (${runnerName})`);
|
|
65
|
+
}
|
|
52
66
|
console.log(` Token: ${expiryStr}`);
|
|
53
67
|
console.log();
|
|
54
68
|
}
|
|
@@ -71,6 +85,10 @@ export async function whoami(agentName?: string): Promise<void> {
|
|
|
71
85
|
console.log(` ID: ${pc.cyan(agent.agentId)}`);
|
|
72
86
|
console.log(` Workspace: ${pc.dim(config.workspace)}`);
|
|
73
87
|
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
|
+
}
|
|
74
92
|
console.log(` Token: ${expiryStr}`);
|
|
75
93
|
console.log(` Session: ${pc.dim(agent.tmuxSession || "none")}`);
|
|
76
94
|
console.log(` Started: ${pc.dim(agent.startedAt || "unknown")}`);
|
package/src/config/schema.ts
CHANGED
|
@@ -30,6 +30,8 @@ export const DEFAULT_CONFIG: Partial<Config> = {
|
|
|
30
30
|
export const CONFIG_PATH = `${process.env.HOME}/.agentmesh/config.json`;
|
|
31
31
|
export const STATE_PATH = `${process.env.HOME}/.agentmesh/state.json`;
|
|
32
32
|
|
|
33
|
+
export type RunnerType = "opencode" | "claude" | "custom";
|
|
34
|
+
|
|
33
35
|
export interface AgentState {
|
|
34
36
|
name: string;
|
|
35
37
|
agentId: string;
|
|
@@ -37,6 +39,10 @@ export interface AgentState {
|
|
|
37
39
|
tmuxSession: string;
|
|
38
40
|
startedAt: string;
|
|
39
41
|
token?: string;
|
|
42
|
+
/** The effective runtime model (resolved from CLI > agent > defaults) */
|
|
43
|
+
runtimeModel?: string;
|
|
44
|
+
/** The runner type (opencode, claude, custom) */
|
|
45
|
+
runnerType?: RunnerType;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export interface State {
|
package/src/core/daemon.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
addAgentToState,
|
|
3
3
|
getAgentState,
|
|
4
4
|
loadConfig,
|
|
5
|
+
loadState,
|
|
5
6
|
removeAgentFromState,
|
|
6
7
|
updateAgentInState,
|
|
7
8
|
} from "../config/loader.js";
|
|
@@ -10,6 +11,7 @@ import { loadContext, loadOrCreateContext, saveContext } from "../context/index.
|
|
|
10
11
|
import { Heartbeat } from "./heartbeat.js";
|
|
11
12
|
import { handleWebSocketEvent, injectRestoredContext, injectStartupMessage } from "./injector.js";
|
|
12
13
|
import { checkInbox, registerAgent } from "./registry.js";
|
|
14
|
+
import { buildRunnerConfig, getRunnerDisplayName, type RunnerConfig } from "./runner.js";
|
|
13
15
|
import {
|
|
14
16
|
captureSessionContext,
|
|
15
17
|
createSession,
|
|
@@ -34,6 +36,7 @@ export class AgentDaemon {
|
|
|
34
36
|
private agentName: string;
|
|
35
37
|
private config: Config;
|
|
36
38
|
private agentConfig: AgentConfig;
|
|
39
|
+
private runnerConfig: RunnerConfig;
|
|
37
40
|
private ws: AgentWebSocket | null = null;
|
|
38
41
|
private heartbeat: Heartbeat | null = null;
|
|
39
42
|
private token: string | null = null;
|
|
@@ -69,6 +72,58 @@ export class AgentDaemon {
|
|
|
69
72
|
if (options.model) agentConfig.model = options.model;
|
|
70
73
|
|
|
71
74
|
this.agentConfig = agentConfig;
|
|
75
|
+
|
|
76
|
+
// Build runner configuration with model resolution
|
|
77
|
+
this.runnerConfig = buildRunnerConfig({
|
|
78
|
+
cliModel: options.model,
|
|
79
|
+
agentModel: agentConfig.model,
|
|
80
|
+
defaultModel: config.defaults.model,
|
|
81
|
+
command: agentConfig.command,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const runnerName = getRunnerDisplayName(this.runnerConfig.type);
|
|
85
|
+
console.log(`Runner: ${runnerName}`);
|
|
86
|
+
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
|
+
}
|
|
72
127
|
}
|
|
73
128
|
|
|
74
129
|
async start(): Promise<void> {
|
|
@@ -90,6 +145,7 @@ export class AgentDaemon {
|
|
|
90
145
|
this.agentName,
|
|
91
146
|
this.agentConfig.command,
|
|
92
147
|
this.agentConfig.workdir,
|
|
148
|
+
this.runnerConfig.env, // Pass runner env vars (e.g., OPENCODE_MODEL)
|
|
93
149
|
);
|
|
94
150
|
|
|
95
151
|
if (!created) {
|
|
@@ -97,6 +153,10 @@ export class AgentDaemon {
|
|
|
97
153
|
}
|
|
98
154
|
} else {
|
|
99
155
|
console.log(`Reconnecting to existing session: ${sessionName}`);
|
|
156
|
+
// Update environment for existing session
|
|
157
|
+
if (Object.keys(this.runnerConfig.env).length > 0) {
|
|
158
|
+
updateSessionEnvironment(this.agentName, this.runnerConfig.env);
|
|
159
|
+
}
|
|
100
160
|
}
|
|
101
161
|
|
|
102
162
|
// Register with hub
|
|
@@ -132,6 +192,8 @@ export class AgentDaemon {
|
|
|
132
192
|
tmuxSession: sessionName,
|
|
133
193
|
startedAt: new Date().toISOString(),
|
|
134
194
|
token: this.token,
|
|
195
|
+
runtimeModel: this.runnerConfig.model,
|
|
196
|
+
runnerType: this.runnerConfig.type,
|
|
135
197
|
});
|
|
136
198
|
|
|
137
199
|
// Start heartbeat with auto-refresh
|