agent-relay 2.1.6 → 2.1.7
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 +9 -1
- package/dist/index.cjs +78 -13
- package/dist/src/cli/commands/doctor.d.ts.map +1 -1
- package/dist/src/cli/commands/doctor.js +32 -7
- package/dist/src/cli/commands/doctor.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +260 -27
- package/dist/src/cli/index.js.map +1 -1
- package/install.sh +545 -34
- package/package.json +18 -18
- package/packages/api-types/package.json +1 -1
- package/packages/benchmark/package.json +4 -4
- package/packages/bridge/package.json +8 -8
- package/packages/cli-tester/package.json +1 -1
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +2 -2
- package/packages/daemon/dist/server.d.ts.map +1 -1
- package/packages/daemon/dist/server.js +45 -4
- package/packages/daemon/dist/server.js.map +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/daemon/src/server.ts +42 -4
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/dist/bin.js +34 -3
- package/packages/mcp/dist/bin.js.map +1 -1
- package/packages/mcp/dist/install.d.ts.map +1 -1
- package/packages/mcp/dist/install.js +7 -8
- package/packages/mcp/dist/install.js.map +1 -1
- package/packages/mcp/package.json +4 -4
- package/packages/mcp/src/bin.ts +39 -3
- package/packages/mcp/src/install.ts +7 -8
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/package.json +3 -3
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/dist/sqlite-adapter.d.ts +1 -0
- package/packages/storage/dist/sqlite-adapter.d.ts.map +1 -1
- package/packages/storage/dist/sqlite-adapter.js +31 -3
- package/packages/storage/dist/sqlite-adapter.js.map +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/storage/src/sqlite-adapter.ts +34 -6
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +3 -3
- package/packages/wrapper/dist/relay-pty-orchestrator.d.ts.map +1 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.js +12 -0
- package/packages/wrapper/dist/relay-pty-orchestrator.js.map +1 -1
- package/packages/wrapper/package.json +6 -6
- package/packages/wrapper/src/relay-pty-orchestrator.ts +12 -0
package/dist/src/cli/index.js
CHANGED
|
@@ -27,29 +27,79 @@ import fs from 'node:fs';
|
|
|
27
27
|
import path from 'node:path';
|
|
28
28
|
import readline from 'node:readline';
|
|
29
29
|
import { promisify } from 'node:util';
|
|
30
|
-
import { exec, spawn as spawnProcess } from 'node:child_process';
|
|
30
|
+
import { exec, execSync, spawn as spawnProcess } from 'node:child_process';
|
|
31
31
|
import { fileURLToPath } from 'node:url';
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Find the dashboard binary if installed as standalone.
|
|
34
|
+
* Checks PATH and common installation locations.
|
|
35
|
+
*/
|
|
36
|
+
function findDashboardBinary() {
|
|
37
|
+
const binaryName = 'relay-dashboard-server';
|
|
38
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
39
|
+
// Common locations to check
|
|
40
|
+
const searchPaths = [
|
|
41
|
+
// In PATH (using which/where)
|
|
42
|
+
binaryName,
|
|
43
|
+
// Common installation directories
|
|
44
|
+
path.join(homeDir, '.local', 'bin', binaryName),
|
|
45
|
+
path.join(homeDir, '.agent-relay', 'bin', binaryName),
|
|
46
|
+
'/usr/local/bin/' + binaryName,
|
|
47
|
+
];
|
|
48
|
+
for (const searchPath of searchPaths) {
|
|
49
|
+
try {
|
|
50
|
+
// For absolute paths, check if file exists and is executable
|
|
51
|
+
if (path.isAbsolute(searchPath)) {
|
|
52
|
+
if (fs.existsSync(searchPath)) {
|
|
53
|
+
fs.accessSync(searchPath, fs.constants.X_OK);
|
|
54
|
+
return searchPath;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// For relative paths (just binary name), check if it's in PATH
|
|
59
|
+
const result = execSync(`which ${searchPath} 2>/dev/null || where ${searchPath} 2>nul`, { encoding: 'utf8' }).trim();
|
|
60
|
+
if (result) {
|
|
61
|
+
return result.split('\n')[0]; // Take first result
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Continue to next path
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Start dashboard (prefers standalone binary, falls back to npx).
|
|
34
73
|
* Returns the spawned child process, port, and a promise that resolves when ready.
|
|
35
74
|
*/
|
|
36
75
|
function startDashboardViaNpx(options) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'@agent-relay/dashboard-server',
|
|
76
|
+
const dashboardBinary = findDashboardBinary();
|
|
77
|
+
let dashboardProcess;
|
|
78
|
+
const args = [
|
|
41
79
|
'--integrated',
|
|
42
80
|
'--port', String(options.port),
|
|
43
81
|
'--data-dir', options.dataDir,
|
|
44
82
|
'--team-dir', options.teamDir,
|
|
45
83
|
'--project-root', options.projectRoot,
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
84
|
+
];
|
|
85
|
+
if (dashboardBinary) {
|
|
86
|
+
console.log(`Starting dashboard using binary: ${dashboardBinary}`);
|
|
87
|
+
dashboardProcess = spawnProcess(dashboardBinary, args, {
|
|
88
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
89
|
+
env: { ...process.env },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.log('Starting dashboard via npx (this may take a moment on first run)...');
|
|
94
|
+
dashboardProcess = spawnProcess('npx', [
|
|
95
|
+
'--yes',
|
|
96
|
+
'@agent-relay/dashboard-server',
|
|
97
|
+
...args,
|
|
98
|
+
], {
|
|
99
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
100
|
+
env: { ...process.env },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
53
103
|
// Promise that resolves when dashboard is ready (or after timeout)
|
|
54
104
|
let resolveReady;
|
|
55
105
|
const ready = new Promise((resolve) => {
|
|
@@ -148,9 +198,20 @@ function findPackageJson(startDir) {
|
|
|
148
198
|
}
|
|
149
199
|
throw new Error('Could not find package.json');
|
|
150
200
|
}
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
const
|
|
201
|
+
// Get version: prefer build-time injected version (for standalone binaries), fall back to package.json
|
|
202
|
+
const VERSION = (() => {
|
|
203
|
+
const envVersion = process.env.AGENT_RELAY_VERSION;
|
|
204
|
+
if (envVersion)
|
|
205
|
+
return envVersion;
|
|
206
|
+
try {
|
|
207
|
+
const packageJsonPath = findPackageJson(__dirname);
|
|
208
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
209
|
+
return packageJson.version;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return 'unknown';
|
|
213
|
+
}
|
|
214
|
+
})();
|
|
154
215
|
const execAsync = promisify(exec);
|
|
155
216
|
// Check for updates in background (non-blocking)
|
|
156
217
|
// Only show notification for interactive commands, not when wrapping agents or running update
|
|
@@ -691,21 +752,193 @@ program
|
|
|
691
752
|
program
|
|
692
753
|
.command('down')
|
|
693
754
|
.description('Stop daemon')
|
|
694
|
-
.
|
|
755
|
+
.option('--force', 'Force cleanup even if process is stuck')
|
|
756
|
+
.option('--all', 'Kill all agent-relay processes system-wide')
|
|
757
|
+
.option('--timeout <ms>', 'Timeout waiting for graceful shutdown', '5000')
|
|
758
|
+
.action(async (options) => {
|
|
695
759
|
const paths = getProjectPaths();
|
|
696
760
|
const pidPath = pidFilePathForSocket(paths.socketPath);
|
|
761
|
+
const timeout = parseInt(options.timeout ?? '5000', 10);
|
|
762
|
+
// Helper to check if process is running
|
|
763
|
+
const isProcessRunning = (pid) => {
|
|
764
|
+
try {
|
|
765
|
+
process.kill(pid, 0); // Signal 0 just checks if process exists
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
catch {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
// Helper to wait for process to die
|
|
773
|
+
const waitForProcessExit = async (pid, timeoutMs) => {
|
|
774
|
+
const start = Date.now();
|
|
775
|
+
while (Date.now() - start < timeoutMs) {
|
|
776
|
+
if (!isProcessRunning(pid)) {
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
};
|
|
783
|
+
// Helper to clean up stale files
|
|
784
|
+
const cleanupStaleFiles = () => {
|
|
785
|
+
// Clean up socket file
|
|
786
|
+
if (fs.existsSync(paths.socketPath)) {
|
|
787
|
+
try {
|
|
788
|
+
fs.unlinkSync(paths.socketPath);
|
|
789
|
+
console.log('Cleaned up stale socket');
|
|
790
|
+
}
|
|
791
|
+
catch { /* ignore */ }
|
|
792
|
+
}
|
|
793
|
+
// Clean up pid file
|
|
794
|
+
if (fs.existsSync(pidPath)) {
|
|
795
|
+
try {
|
|
796
|
+
fs.unlinkSync(pidPath);
|
|
797
|
+
console.log('Cleaned up stale pid file');
|
|
798
|
+
}
|
|
799
|
+
catch { /* ignore */ }
|
|
800
|
+
}
|
|
801
|
+
// Clean up runtime.json
|
|
802
|
+
const runtimePath = path.join(paths.dataDir, 'runtime.json');
|
|
803
|
+
if (fs.existsSync(runtimePath)) {
|
|
804
|
+
try {
|
|
805
|
+
fs.unlinkSync(runtimePath);
|
|
806
|
+
console.log('Cleaned up runtime config');
|
|
807
|
+
}
|
|
808
|
+
catch { /* ignore */ }
|
|
809
|
+
}
|
|
810
|
+
// Clean up stale mcp-identity-* files (but not mcp-identity)
|
|
811
|
+
try {
|
|
812
|
+
const files = fs.readdirSync(paths.dataDir);
|
|
813
|
+
for (const file of files) {
|
|
814
|
+
if (file.startsWith('mcp-identity-')) {
|
|
815
|
+
const identityPath = path.join(paths.dataDir, file);
|
|
816
|
+
// Check if the PID in the filename is still running
|
|
817
|
+
const match = file.match(/mcp-identity-(\d+)/);
|
|
818
|
+
if (match) {
|
|
819
|
+
const identityPid = parseInt(match[1], 10);
|
|
820
|
+
if (!isProcessRunning(identityPid)) {
|
|
821
|
+
fs.unlinkSync(identityPath);
|
|
822
|
+
console.log(`Cleaned up stale identity file: ${file}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
catch { /* ignore errors reading directory */ }
|
|
829
|
+
};
|
|
830
|
+
// Handle --all flag: kill all agent-relay processes system-wide
|
|
831
|
+
if (options.all) {
|
|
832
|
+
console.log('Stopping all agent-relay processes...');
|
|
833
|
+
const { execSync } = await import('node:child_process');
|
|
834
|
+
try {
|
|
835
|
+
// Find all agent-relay processes
|
|
836
|
+
const psOutput = execSync('ps aux', { encoding: 'utf-8' });
|
|
837
|
+
const lines = psOutput.split('\n');
|
|
838
|
+
const agentRelayProcesses = [];
|
|
839
|
+
for (const line of lines) {
|
|
840
|
+
// Match agent-relay daemon processes (not MCP servers or other commands)
|
|
841
|
+
// Look for "agent-relay up" or "agent-relay.*up" patterns
|
|
842
|
+
if ((line.includes('agent-relay') && line.includes(' up')) &&
|
|
843
|
+
!line.includes('grep') &&
|
|
844
|
+
!line.includes('agent-relay-mcp')) {
|
|
845
|
+
const parts = line.trim().split(/\s+/);
|
|
846
|
+
// ps aux format: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
|
847
|
+
const pid = parseInt(parts[1], 10);
|
|
848
|
+
if (!isNaN(pid) && pid !== process.pid && pid > 0) {
|
|
849
|
+
agentRelayProcesses.push({ pid, cmd: parts.slice(10).join(' ') });
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (agentRelayProcesses.length === 0) {
|
|
854
|
+
console.log('No agent-relay processes found');
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
console.log(`Found ${agentRelayProcesses.length} agent-relay process(es)`);
|
|
858
|
+
for (const proc of agentRelayProcesses) {
|
|
859
|
+
try {
|
|
860
|
+
console.log(` Stopping PID ${proc.pid}...`);
|
|
861
|
+
process.kill(proc.pid, 'SIGTERM');
|
|
862
|
+
}
|
|
863
|
+
catch (err) {
|
|
864
|
+
if (err.code !== 'ESRCH') {
|
|
865
|
+
console.log(` Warning: ${err.message}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
// Wait a bit for graceful shutdown
|
|
870
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
871
|
+
// Force kill any remaining
|
|
872
|
+
if (options.force) {
|
|
873
|
+
for (const proc of agentRelayProcesses) {
|
|
874
|
+
if (isProcessRunning(proc.pid)) {
|
|
875
|
+
try {
|
|
876
|
+
console.log(` Force killing PID ${proc.pid}...`);
|
|
877
|
+
process.kill(proc.pid, 'SIGKILL');
|
|
878
|
+
}
|
|
879
|
+
catch { /* ignore */ }
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
console.log('Done');
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
catch (err) {
|
|
887
|
+
console.error(`Error finding processes: ${err.message}`);
|
|
888
|
+
}
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
697
891
|
if (!fs.existsSync(pidPath)) {
|
|
698
|
-
|
|
892
|
+
// No pid file, but might have stale files
|
|
893
|
+
if (options.force) {
|
|
894
|
+
cleanupStaleFiles();
|
|
895
|
+
console.log('Cleaned up (was not running)');
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
console.log('Not running');
|
|
899
|
+
}
|
|
699
900
|
return;
|
|
700
901
|
}
|
|
701
902
|
const pid = Number(fs.readFileSync(pidPath, 'utf-8').trim());
|
|
903
|
+
// Check if process is actually running
|
|
904
|
+
if (!isProcessRunning(pid)) {
|
|
905
|
+
cleanupStaleFiles();
|
|
906
|
+
console.log('Cleaned up stale state (process was not running)');
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
// Try graceful shutdown
|
|
702
910
|
try {
|
|
911
|
+
console.log(`Stopping daemon (pid: ${pid})...`);
|
|
703
912
|
process.kill(pid, 'SIGTERM');
|
|
913
|
+
// Wait for graceful shutdown
|
|
914
|
+
const exited = await waitForProcessExit(pid, timeout);
|
|
915
|
+
if (!exited) {
|
|
916
|
+
if (options.force) {
|
|
917
|
+
console.log('Graceful shutdown timed out, forcing...');
|
|
918
|
+
try {
|
|
919
|
+
process.kill(pid, 'SIGKILL');
|
|
920
|
+
await waitForProcessExit(pid, 2000);
|
|
921
|
+
}
|
|
922
|
+
catch { /* ignore */ }
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
console.log(`Graceful shutdown timed out after ${timeout}ms. Use --force to kill.`);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// Clean up any remaining stale files
|
|
930
|
+
cleanupStaleFiles();
|
|
704
931
|
console.log('Stopped');
|
|
705
932
|
}
|
|
706
|
-
catch {
|
|
707
|
-
|
|
708
|
-
|
|
933
|
+
catch (err) {
|
|
934
|
+
if (err.code === 'ESRCH') {
|
|
935
|
+
// Process doesn't exist
|
|
936
|
+
cleanupStaleFiles();
|
|
937
|
+
console.log('Cleaned up stale state');
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
console.error(`Error stopping daemon: ${err.message}`);
|
|
941
|
+
}
|
|
709
942
|
}
|
|
710
943
|
});
|
|
711
944
|
// System prompt for Dashboard agent - plain text to avoid shell escaping issues
|
|
@@ -1879,13 +2112,13 @@ program
|
|
|
1879
2112
|
};
|
|
1880
2113
|
// Try daemon socket first (preferred path)
|
|
1881
2114
|
try {
|
|
1882
|
-
const
|
|
2115
|
+
const _paths = getProjectPaths();
|
|
1883
2116
|
// TODO: Re-enable daemon-based spawning when client.spawn() is implemented
|
|
1884
2117
|
// See: docs/SDK-MIGRATION-PLAN.md for planned implementation
|
|
1885
2118
|
// For now, fall through to HTTP API
|
|
1886
2119
|
throw new Error('Daemon-based spawn not yet implemented');
|
|
1887
2120
|
}
|
|
1888
|
-
catch
|
|
2121
|
+
catch {
|
|
1889
2122
|
// Fall through to HTTP API
|
|
1890
2123
|
// console.log('Daemon not available, trying HTTP API...');
|
|
1891
2124
|
}
|
|
@@ -1933,9 +2166,9 @@ program
|
|
|
1933
2166
|
const port = options.port || DEFAULT_DASHBOARD_PORT;
|
|
1934
2167
|
// Try daemon socket first (preferred path)
|
|
1935
2168
|
try {
|
|
1936
|
-
const
|
|
1937
|
-
const
|
|
1938
|
-
socketPath:
|
|
2169
|
+
const _paths = getProjectPaths();
|
|
2170
|
+
const _client = new RelayClient({
|
|
2171
|
+
socketPath: _paths.socketPath,
|
|
1939
2172
|
agentName: '__cli_releaser__',
|
|
1940
2173
|
quiet: true,
|
|
1941
2174
|
reconnect: false,
|
|
@@ -1948,7 +2181,7 @@ program
|
|
|
1948
2181
|
// For now, fall through to HTTP API
|
|
1949
2182
|
throw new Error('Daemon-based release not yet implemented');
|
|
1950
2183
|
}
|
|
1951
|
-
catch
|
|
2184
|
+
catch {
|
|
1952
2185
|
// Fall through to HTTP API
|
|
1953
2186
|
// console.log('Daemon not available, trying HTTP API...');
|
|
1954
2187
|
}
|