agent-relay 2.0.11 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/bin/relay-pty-darwin-arm64 +0 -0
  2. package/bin/relay-pty-darwin-x64 +0 -0
  3. package/bin/relay-pty-linux-x64 +0 -0
  4. package/dist/dashboard/out/404.html +1 -1
  5. package/dist/dashboard/out/_next/static/chunks/320-23e5ffe6aa7eb934.js +1 -0
  6. package/{packages/dashboard/ui-dist/_next/static/css/99c2552394077586.css → dist/dashboard/out/_next/static/css/605dd4e30c91986f.css} +1 -1
  7. package/dist/dashboard/out/app/onboarding.html +1 -1
  8. package/dist/dashboard/out/app/onboarding.txt +1 -1
  9. package/dist/dashboard/out/app.html +1 -1
  10. package/dist/dashboard/out/app.txt +2 -2
  11. package/dist/dashboard/out/cloud/link.html +1 -1
  12. package/dist/dashboard/out/cloud/link.txt +1 -1
  13. package/dist/dashboard/out/connect-repos.html +1 -1
  14. package/dist/dashboard/out/connect-repos.txt +1 -1
  15. package/dist/dashboard/out/history.html +1 -1
  16. package/dist/dashboard/out/history.txt +1 -1
  17. package/dist/dashboard/out/index.html +1 -1
  18. package/dist/dashboard/out/index.txt +2 -2
  19. package/dist/dashboard/out/login.html +2 -2
  20. package/dist/dashboard/out/login.txt +1 -1
  21. package/dist/dashboard/out/metrics.html +1 -1
  22. package/dist/dashboard/out/metrics.txt +1 -1
  23. package/dist/dashboard/out/pricing.html +2 -2
  24. package/dist/dashboard/out/pricing.txt +1 -1
  25. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  26. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  27. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  28. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  29. package/dist/dashboard/out/providers/setup/cursor.html +1 -1
  30. package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
  31. package/dist/dashboard/out/providers.html +1 -1
  32. package/dist/dashboard/out/providers.txt +1 -1
  33. package/dist/dashboard/out/signup.html +2 -2
  34. package/dist/dashboard/out/signup.txt +1 -1
  35. package/package.json +15 -15
  36. package/packages/api-types/package.json +1 -1
  37. package/packages/bridge/dist/spawner.js +3 -70
  38. package/packages/bridge/package.json +7 -7
  39. package/packages/cloud/dist/api/cli-pty-runner.js +4 -25
  40. package/packages/cloud/dist/api/provider-env.d.ts +12 -0
  41. package/packages/cloud/dist/api/provider-env.js +66 -0
  42. package/packages/cloud/dist/api/providers.js +12 -2
  43. package/packages/cloud/package.json +6 -6
  44. package/packages/config/package.json +2 -2
  45. package/packages/continuity/package.json +1 -1
  46. package/packages/daemon/dist/cli-auth.js +4 -25
  47. package/packages/daemon/package.json +12 -12
  48. package/packages/dashboard/dist/server.js +32 -0
  49. package/packages/dashboard/package.json +12 -12
  50. package/packages/dashboard/ui/react-components/settings/WorkspaceSettingsPanel.tsx +44 -3
  51. package/packages/dashboard/ui-dist/404.html +1 -1
  52. package/packages/dashboard/ui-dist/_next/static/chunks/320-23e5ffe6aa7eb934.js +1 -0
  53. package/{dist/dashboard/out/_next/static/css/99c2552394077586.css → packages/dashboard/ui-dist/_next/static/css/605dd4e30c91986f.css} +1 -1
  54. package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
  55. package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
  56. package/packages/dashboard/ui-dist/app.html +1 -1
  57. package/packages/dashboard/ui-dist/app.txt +2 -2
  58. package/packages/dashboard/ui-dist/cloud/link.html +1 -1
  59. package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
  60. package/packages/dashboard/ui-dist/connect-repos.html +1 -1
  61. package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
  62. package/packages/dashboard/ui-dist/history.html +1 -1
  63. package/packages/dashboard/ui-dist/history.txt +1 -1
  64. package/packages/dashboard/ui-dist/index.html +1 -1
  65. package/packages/dashboard/ui-dist/index.txt +2 -2
  66. package/packages/dashboard/ui-dist/login.html +2 -2
  67. package/packages/dashboard/ui-dist/login.txt +1 -1
  68. package/packages/dashboard/ui-dist/metrics.html +1 -1
  69. package/packages/dashboard/ui-dist/metrics.txt +1 -1
  70. package/packages/dashboard/ui-dist/pricing.html +2 -2
  71. package/packages/dashboard/ui-dist/pricing.txt +1 -1
  72. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
  73. package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
  74. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
  75. package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
  76. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
  77. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
  78. package/packages/dashboard/ui-dist/providers.html +1 -1
  79. package/packages/dashboard/ui-dist/providers.txt +1 -1
  80. package/packages/dashboard/ui-dist/signup.html +2 -2
  81. package/packages/dashboard/ui-dist/signup.txt +1 -1
  82. package/packages/dashboard-server/package.json +12 -12
  83. package/packages/hooks/package.json +4 -4
  84. package/packages/mcp/package.json +2 -2
  85. package/packages/memory/package.json +2 -2
  86. package/packages/policy/package.json +2 -2
  87. package/packages/protocol/package.json +1 -1
  88. package/packages/resiliency/package.json +1 -1
  89. package/packages/sdk/package.json +2 -2
  90. package/packages/spawner/package.json +1 -1
  91. package/packages/state/package.json +1 -1
  92. package/packages/storage/package.json +2 -2
  93. package/packages/telemetry/package.json +1 -1
  94. package/packages/trajectory/package.json +2 -2
  95. package/packages/user-directory/dist/user-directory.d.ts +17 -0
  96. package/packages/user-directory/dist/user-directory.js +73 -0
  97. package/packages/user-directory/package.json +2 -2
  98. package/packages/utils/dist/index.d.ts +1 -0
  99. package/packages/utils/dist/index.js +1 -0
  100. package/packages/utils/dist/relay-pty-path.d.ts +44 -0
  101. package/packages/utils/dist/relay-pty-path.js +127 -0
  102. package/packages/utils/package.json +6 -1
  103. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +1 -0
  104. package/packages/wrapper/dist/relay-pty-orchestrator.js +4 -25
  105. package/packages/wrapper/package.json +6 -6
  106. package/dist/dashboard/out/_next/static/chunks/320-22ebe7be58cf982a.js +0 -1
  107. package/packages/dashboard/ui-dist/_next/static/chunks/320-22ebe7be58cf982a.js +0 -1
  108. /package/dist/dashboard/out/_next/static/{N2nFek-KCN321ZAghmjge → h1U3qU5XIfQSy46M_SDsz}/_buildManifest.js +0 -0
  109. /package/dist/dashboard/out/_next/static/{N2nFek-KCN321ZAghmjge → h1U3qU5XIfQSy46M_SDsz}/_ssgManifest.js +0 -0
  110. /package/packages/dashboard/ui-dist/_next/static/{59SiFCGkIPHj5xnvI-Hkn → 4WryIM4xHT22BbJ46YITr}/_buildManifest.js +0 -0
  111. /package/packages/dashboard/ui-dist/_next/static/{59SiFCGkIPHj5xnvI-Hkn → 4WryIM4xHT22BbJ46YITr}/_ssgManifest.js +0 -0
  112. /package/packages/dashboard/ui-dist/_next/static/{N2nFek-KCN321ZAghmjge → dS0EgrS-iG-_pkUVhBypz}/_buildManifest.js +0 -0
  113. /package/packages/dashboard/ui-dist/_next/static/{N2nFek-KCN321ZAghmjge → dS0EgrS-iG-_pkUVhBypz}/_ssgManifest.js +0 -0
  114. /package/packages/dashboard/ui-dist/_next/static/{eWYFV8hKU_42BNS8Dj84s → h1U3qU5XIfQSy46M_SDsz}/_buildManifest.js +0 -0
  115. /package/packages/dashboard/ui-dist/_next/static/{eWYFV8hKU_42BNS8Dj84s → h1U3qU5XIfQSy46M_SDsz}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Real-time agent-to-agent communication system",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -107,20 +107,20 @@
107
107
  },
108
108
  "homepage": "https://github.com/AgentWorkforce/relay#readme",
109
109
  "dependencies": {
110
- "@agent-relay/bridge": "2.0.11",
111
- "@agent-relay/config": "2.0.11",
112
- "@agent-relay/continuity": "2.0.11",
113
- "@agent-relay/daemon": "2.0.11",
114
- "@agent-relay/hooks": "2.0.11",
115
- "@agent-relay/protocol": "2.0.11",
116
- "@agent-relay/resiliency": "2.0.11",
117
- "@agent-relay/sdk": "2.0.11",
118
- "@agent-relay/storage": "2.0.11",
119
- "@agent-relay/telemetry": "2.0.11",
120
- "@agent-relay/trajectory": "2.0.11",
121
- "@agent-relay/user-directory": "2.0.11",
122
- "@agent-relay/utils": "2.0.11",
123
- "@agent-relay/wrapper": "2.0.11",
110
+ "@agent-relay/bridge": "2.0.12",
111
+ "@agent-relay/config": "2.0.12",
112
+ "@agent-relay/continuity": "2.0.12",
113
+ "@agent-relay/daemon": "2.0.12",
114
+ "@agent-relay/hooks": "2.0.12",
115
+ "@agent-relay/protocol": "2.0.12",
116
+ "@agent-relay/resiliency": "2.0.12",
117
+ "@agent-relay/sdk": "2.0.12",
118
+ "@agent-relay/storage": "2.0.12",
119
+ "@agent-relay/telemetry": "2.0.12",
120
+ "@agent-relay/trajectory": "2.0.12",
121
+ "@agent-relay/user-directory": "2.0.12",
122
+ "@agent-relay/utils": "2.0.12",
123
+ "@agent-relay/wrapper": "2.0.12",
124
124
  "@nangohq/node": "^0.69.20",
125
125
  "@types/jsonwebtoken": "^9.0.10",
126
126
  "agent-trajectories": "^0.2.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/api-types",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Shared API types and Zod schemas for Agent Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,6 +13,7 @@ import { resolveCommand } from '@agent-relay/utils/command-resolver';
13
13
  import { createTraceableError } from '@agent-relay/utils/error-tracking';
14
14
  import { createLogger } from '@agent-relay/utils/logger';
15
15
  import { mapModelToCli } from '@agent-relay/utils/model-mapping';
16
+ import { findRelayPtyBinary as findRelayPtyBinaryUtil } from '@agent-relay/utils/relay-pty-path';
16
17
  import { RelayPtyOrchestrator } from '@agent-relay/wrapper';
17
18
  import { selectShadowCli } from './shadow-cli.js';
18
19
  // Get the directory where this module is located (for binary path resolution)
@@ -182,78 +183,10 @@ function getRelayInstructions(agentName, options = {}) {
182
183
  /**
183
184
  * Check if the relay-pty binary is available.
184
185
  * Returns the path to the binary if found, null otherwise.
185
- *
186
- * Search order:
187
- * 1. bin/relay-pty in package root (installed by postinstall)
188
- * 2. relay-pty/target/release/relay-pty (local Rust build)
189
- * 3. /usr/local/bin/relay-pty (global install)
190
- * 4. In node_modules when installed as dependency
191
- * 5. Global npm installs (nvm) - both scoped and root packages
186
+ * Uses shared utility from @agent-relay/utils.
192
187
  */
193
188
  function findRelayPtyBinary() {
194
- // Determine the agent-relay package root
195
- // This code runs from either:
196
- // - packages/bridge/dist/ (development/workspace)
197
- // - node_modules/@agent-relay/bridge/dist/ (npm install)
198
- //
199
- // We need to find the agent-relay package root where bin/relay-pty lives
200
- let packageRoot;
201
- // Check if we're inside node_modules/@agent-relay/bridge/
202
- if (__dirname.includes('node_modules/@agent-relay/bridge')) {
203
- // Go from node_modules/@agent-relay/bridge/dist/ to agent-relay/
204
- // dist/ -> bridge/ -> @agent-relay/ -> node_modules/ -> agent-relay/
205
- packageRoot = path.join(__dirname, '..', '..', '..', '..');
206
- }
207
- else if (__dirname.includes('node_modules/agent-relay')) {
208
- // Direct dependency: node_modules/agent-relay/packages/bridge/dist/
209
- // dist/ -> bridge/ -> packages/ -> agent-relay/
210
- packageRoot = path.join(__dirname, '..', '..', '..');
211
- }
212
- else {
213
- // Development: packages/bridge/dist/ -> packages/ -> project root
214
- packageRoot = path.join(__dirname, '..', '..', '..');
215
- }
216
- // Find the node_modules root for global installs
217
- // When running from node_modules/@agent-relay/dashboard/node_modules/@agent-relay/bridge/dist/
218
- // we need to look for agent-relay at node_modules/agent-relay
219
- let nodeModulesRoot = null;
220
- const nodeModulesMatch = __dirname.match(/^(.+?\/node_modules)\/@agent-relay\//);
221
- if (nodeModulesMatch) {
222
- nodeModulesRoot = nodeModulesMatch[1];
223
- }
224
- const candidates = [
225
- // Primary: installed by postinstall from platform-specific binary
226
- path.join(packageRoot, 'bin', 'relay-pty'),
227
- // Development: local Rust build
228
- path.join(packageRoot, 'relay-pty', 'target', 'release', 'relay-pty'),
229
- path.join(packageRoot, 'relay-pty', 'target', 'debug', 'relay-pty'),
230
- // Local build in cwd (for development)
231
- path.join(process.cwd(), 'relay-pty', 'target', 'release', 'relay-pty'),
232
- // Installed globally
233
- '/usr/local/bin/relay-pty',
234
- // In node_modules (when installed as local dependency)
235
- path.join(process.cwd(), 'node_modules', 'agent-relay', 'bin', 'relay-pty'),
236
- // Global npm install (nvm) - root package
237
- path.join(process.env.HOME || '', '.nvm', 'versions', 'node', process.version, 'lib', 'node_modules', 'agent-relay', 'bin', 'relay-pty'),
238
- ];
239
- // Add candidate for root agent-relay package when running from scoped @agent-relay/* packages
240
- if (nodeModulesRoot) {
241
- candidates.push(path.join(nodeModulesRoot, 'agent-relay', 'bin', 'relay-pty'));
242
- }
243
- // Try common global npm paths
244
- if (process.env.HOME) {
245
- // Homebrew npm (macOS)
246
- candidates.push(path.join('/usr/local/lib/node_modules', 'agent-relay', 'bin', 'relay-pty'));
247
- candidates.push(path.join('/opt/homebrew/lib/node_modules', 'agent-relay', 'bin', 'relay-pty'));
248
- // pnpm global
249
- candidates.push(path.join(process.env.HOME, '.local', 'share', 'pnpm', 'global', 'node_modules', 'agent-relay', 'bin', 'relay-pty'));
250
- }
251
- for (const candidate of candidates) {
252
- if (fs.existsSync(candidate)) {
253
- return candidate;
254
- }
255
- }
256
- return null;
189
+ return findRelayPtyBinaryUtil(__dirname);
257
190
  }
258
191
  /** Cached result of relay-pty binary check */
259
192
  let relayPtyBinaryPath;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/bridge",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Multi-project bridge client utilities for Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,12 +22,12 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@agent-relay/protocol": "2.0.11",
26
- "@agent-relay/config": "2.0.11",
27
- "@agent-relay/utils": "2.0.11",
28
- "@agent-relay/policy": "2.0.11",
29
- "@agent-relay/user-directory": "2.0.11",
30
- "@agent-relay/wrapper": "2.0.11"
25
+ "@agent-relay/protocol": "2.0.12",
26
+ "@agent-relay/config": "2.0.12",
27
+ "@agent-relay/utils": "2.0.12",
28
+ "@agent-relay/policy": "2.0.12",
29
+ "@agent-relay/user-directory": "2.0.12",
30
+ "@agent-relay/wrapper": "2.0.12"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.19.3",
@@ -7,8 +7,7 @@
7
7
  * Uses the relay-pty Rust binary for PTY emulation.
8
8
  */
9
9
  import { spawn } from 'node:child_process';
10
- import { existsSync } from 'node:fs';
11
- import { join, dirname } from 'node:path';
10
+ import { dirname } from 'node:path';
12
11
  import { fileURLToPath } from 'node:url';
13
12
  import { randomUUID } from 'node:crypto';
14
13
  // Get the directory where this module is located
@@ -16,6 +15,7 @@ const __filename = fileURLToPath(import.meta.url);
16
15
  const __dirname = dirname(__filename);
17
16
  // Import shared config and utilities
18
17
  import { CLI_AUTH_CONFIG, stripAnsiCodes, matchesSuccessPattern, findMatchingPrompt, validateProviderConfig, validateAllProviderConfigs as validateAllConfigs, getSupportedProviders, } from '@agent-relay/config/cli-auth-config';
18
+ import { findRelayPtyBinary as findRelayPtyBinaryUtil } from '@agent-relay/utils/relay-pty-path';
19
19
  // Re-export everything from shared config for backward compatibility
20
20
  export { CLI_AUTH_CONFIG, stripAnsiCodes, matchesSuccessPattern, findMatchingPrompt, validateProviderConfig, getSupportedProviders, };
21
21
  // Wrapper that throws instead of returning array (backward compatible)
@@ -27,31 +27,10 @@ export function validateAllProviderConfigs() {
27
27
  }
28
28
  /**
29
29
  * Find the relay-pty binary path.
30
- * Returns null if not found.
30
+ * Uses shared utility from @agent-relay/utils.
31
31
  */
32
32
  function findRelayPtyBinary() {
33
- // Get the project root (five levels up from packages/cloud/dist/api/)
34
- // packages/cloud/dist/api/ -> packages/cloud/dist -> packages/cloud -> packages -> project root
35
- const projectRoot = join(__dirname, '..', '..', '..', '..', '..');
36
- const candidates = [
37
- // Primary: installed by postinstall from platform-specific binary
38
- join(projectRoot, 'bin', 'relay-pty'),
39
- // Development: local Rust build
40
- join(projectRoot, 'relay-pty', 'target', 'release', 'relay-pty'),
41
- join(projectRoot, 'relay-pty', 'target', 'debug', 'relay-pty'),
42
- // Local build in cwd (for development)
43
- join(process.cwd(), 'relay-pty', 'target', 'release', 'relay-pty'),
44
- // Installed globally
45
- '/usr/local/bin/relay-pty',
46
- // In node_modules (when installed as dependency)
47
- join(process.cwd(), 'node_modules', 'agent-relay', 'bin', 'relay-pty'),
48
- ];
49
- for (const candidate of candidates) {
50
- if (existsSync(candidate)) {
51
- return candidate;
52
- }
53
- }
54
- return null;
33
+ return findRelayPtyBinaryUtil(__dirname);
55
34
  }
56
35
  /**
57
36
  * Run CLI auth flow via PTY using relay-pty binary
@@ -11,4 +11,16 @@ export declare function setProviderApiKeyEnv(userId: string, provider: string, a
11
11
  updated: number;
12
12
  skipped: number;
13
13
  }>;
14
+ /**
15
+ * Clear provider credentials from workspace(s).
16
+ * Deletes credential files and unsets environment variables.
17
+ *
18
+ * @param userId - User ID
19
+ * @param provider - Provider name (e.g., 'google', 'anthropic', 'codex')
20
+ * @param workspaceId - Workspace to clear credentials from
21
+ */
22
+ export declare function clearProviderCredentials(userId: string, provider: string, workspaceId: string): Promise<{
23
+ cleared: boolean;
24
+ error?: string;
25
+ }>;
14
26
  //# sourceMappingURL=provider-env.d.ts.map
@@ -4,6 +4,18 @@ const PROVIDER_ENV_VARS = {
4
4
  google: 'GEMINI_API_KEY',
5
5
  gemini: 'GEMINI_API_KEY',
6
6
  };
7
+ /**
8
+ * All providers that may have credential files on the workspace.
9
+ * This includes CLI-based providers that store auth locally.
10
+ */
11
+ const ALL_CREDENTIAL_PROVIDERS = [
12
+ 'anthropic', 'claude',
13
+ 'codex', 'openai',
14
+ 'google', 'gemini',
15
+ 'opencode',
16
+ 'droid', 'factory',
17
+ 'cursor',
18
+ ];
7
19
  /**
8
20
  * Providers that need credential files written to the workspace filesystem.
9
21
  * These providers have CLIs that read from files rather than just env vars.
@@ -72,4 +84,58 @@ export async function setProviderApiKeyEnv(userId, provider, apiKey, workspaceId
72
84
  const updated = results.filter((result) => result === 'updated').length;
73
85
  return { updated, skipped: results.length - updated };
74
86
  }
87
+ /**
88
+ * Clear provider credentials from workspace(s).
89
+ * Deletes credential files and unsets environment variables.
90
+ *
91
+ * @param userId - User ID
92
+ * @param provider - Provider name (e.g., 'google', 'anthropic', 'codex')
93
+ * @param workspaceId - Workspace to clear credentials from
94
+ */
95
+ export async function clearProviderCredentials(userId, provider, workspaceId) {
96
+ const envVarName = PROVIDER_ENV_VARS[provider];
97
+ const needsCredentialFileClear = ALL_CREDENTIAL_PROVIDERS.includes(provider);
98
+ // Get the workspace
99
+ const workspace = await db.workspaces.findById(workspaceId);
100
+ if (!workspace) {
101
+ return { cleared: false, error: 'Workspace not found' };
102
+ }
103
+ if (!workspace.publicUrl) {
104
+ // Workspace not running, credentials will be gone when it restarts anyway
105
+ return { cleared: true };
106
+ }
107
+ // Clear environment variable if applicable
108
+ if (envVarName && workspace.computeId) {
109
+ const provisioner = getProvisioner();
110
+ try {
111
+ // Set to empty string to clear
112
+ await provisioner.setWorkspaceEnvVars(workspace, { [envVarName]: '' });
113
+ }
114
+ catch (err) {
115
+ console.warn(`[provider-env] Failed to clear env var ${envVarName} on workspace ${workspace.id}:`, err);
116
+ }
117
+ }
118
+ // Delete credential files from workspace
119
+ if (needsCredentialFileClear) {
120
+ try {
121
+ const response = await fetch(`${workspace.publicUrl}/api/credentials/apikey`, {
122
+ method: 'DELETE',
123
+ headers: { 'Content-Type': 'application/json' },
124
+ body: JSON.stringify({ userId, provider }),
125
+ });
126
+ if (!response.ok) {
127
+ const data = await response.json().catch(() => ({}));
128
+ console.warn(`[provider-env] Failed to delete credential files for ${provider} on workspace ${workspace.id}: ${response.status}`, data);
129
+ return { cleared: false, error: 'Failed to delete credential files on workspace' };
130
+ }
131
+ const data = await response.json();
132
+ console.log(`[provider-env] Deleted ${provider} credentials for user ${userId} on workspace ${workspace.id}:`, data.deletedPaths);
133
+ }
134
+ catch (err) {
135
+ console.warn(`[provider-env] Error deleting credential files for ${provider} on workspace ${workspace.id}:`, err);
136
+ return { cleared: false, error: 'Error connecting to workspace' };
137
+ }
138
+ }
139
+ return { cleared: true };
140
+ }
75
141
  //# sourceMappingURL=provider-env.js.map
@@ -9,7 +9,7 @@ import { createClient } from 'redis';
9
9
  import { requireAuth } from './auth.js';
10
10
  import { getConfig } from '../config.js';
11
11
  import { db } from '../db/index.js';
12
- import { setProviderApiKeyEnv } from './provider-env.js';
12
+ import { setProviderApiKeyEnv, clearProviderCredentials } from './provider-env.js';
13
13
  export const providersRouter = Router();
14
14
  // All routes require authentication
15
15
  providersRouter.use(requireAuth);
@@ -402,6 +402,7 @@ providersRouter.get('/:provider/status/:flowId', (req, res) => {
402
402
  /**
403
403
  * DELETE /api/providers/:provider
404
404
  * Disconnect a provider from a specific workspace
405
+ * Also clears credential files from the workspace filesystem
405
406
  *
406
407
  * Query: ?workspaceId=xxx
407
408
  */
@@ -420,12 +421,21 @@ providersRouter.delete('/:provider', async (req, res) => {
420
421
  }
421
422
  const workspaceId = workspaceIdParam;
422
423
  const provider = providerParam;
424
+ // Sanitize for logging (prevent log injection)
425
+ const safeProvider = String(provider).replace(/[\r\n]/g, '').substring(0, 20);
423
426
  try {
427
+ // Delete from database
424
428
  await db.credentials.deleteForWorkspace(userId, workspaceId, provider);
429
+ // Clear credentials from workspace filesystem
430
+ const clearResult = await clearProviderCredentials(userId, provider, workspaceId);
431
+ if (!clearResult.cleared) {
432
+ console.warn('[providers] Failed to clear workspace credentials', { provider: safeProvider, error: clearResult.error });
433
+ // Don't fail the request, just warn - database entry was already deleted
434
+ }
425
435
  res.json({ success: true });
426
436
  }
427
437
  catch (error) {
428
- console.error(`Error disconnecting ${provider}:`, error);
438
+ console.error('[providers] Error disconnecting', { provider: safeProvider, error: error instanceof Error ? error.message : String(error) });
429
439
  res.status(500).json({ error: 'Failed to disconnect provider' });
430
440
  }
431
441
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/cloud",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Cloud API server and services for Agent Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,11 +38,11 @@
38
38
  "test:watch": "vitest"
39
39
  },
40
40
  "dependencies": {
41
- "@agent-relay/wrapper": "2.0.11",
42
- "@agent-relay/config": "2.0.11",
43
- "@agent-relay/resiliency": "2.0.11",
44
- "@agent-relay/storage": "2.0.11",
45
- "@agent-relay/protocol": "2.0.11"
41
+ "@agent-relay/wrapper": "2.0.12",
42
+ "@agent-relay/config": "2.0.12",
43
+ "@agent-relay/resiliency": "2.0.12",
44
+ "@agent-relay/storage": "2.0.12",
45
+ "@agent-relay/protocol": "2.0.12"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.19.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/config",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Shared configuration schemas and loaders for Agent Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -83,7 +83,7 @@
83
83
  "test:watch": "vitest"
84
84
  },
85
85
  "dependencies": {
86
- "@agent-relay/protocol": "2.0.11",
86
+ "@agent-relay/protocol": "2.0.12",
87
87
  "zod": "^3.23.8",
88
88
  "zod-to-json-schema": "^3.23.1"
89
89
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/continuity",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Session continuity manager for Relay (ledgers, handoffs, resume)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,15 +8,15 @@
8
8
  * version compatibility by avoiding native module compilation.
9
9
  */
10
10
  import { spawn } from 'node:child_process';
11
- import { existsSync } from 'node:fs';
12
11
  import * as fs from 'fs/promises';
13
12
  import * as os from 'os';
14
- import { join, dirname } from 'node:path';
13
+ import { dirname } from 'node:path';
15
14
  import { fileURLToPath } from 'node:url';
16
15
  import * as crypto from 'crypto';
17
16
  import { createLogger } from '@agent-relay/resiliency';
18
17
  import { CLI_AUTH_CONFIG, stripAnsiCodes, matchesSuccessPattern, findMatchingPrompt, findMatchingError, getSupportedProviders, } from '@agent-relay/config/cli-auth-config';
19
18
  import { getUserDirectoryService } from '@agent-relay/user-directory';
19
+ import { findRelayPtyBinary as findRelayPtyBinaryUtil } from '@agent-relay/utils/relay-pty-path';
20
20
  // Get the directory where this module is located
21
21
  const __filename = fileURLToPath(import.meta.url);
22
22
  const __dirname = dirname(__filename);
@@ -25,31 +25,10 @@ const logger = createLogger('cli-auth');
25
25
  export { CLI_AUTH_CONFIG, getSupportedProviders };
26
26
  /**
27
27
  * Find the relay-pty binary path.
28
- * Returns null if not found.
28
+ * Uses shared utility from @agent-relay/utils.
29
29
  */
30
30
  function findRelayPtyBinary() {
31
- // Get the project root (four levels up from packages/daemon/dist/)
32
- // packages/daemon/dist/ -> packages/daemon -> packages -> project root
33
- const projectRoot = join(__dirname, '..', '..', '..', '..');
34
- const candidates = [
35
- // Primary: installed by postinstall from platform-specific binary
36
- join(projectRoot, 'bin', 'relay-pty'),
37
- // Development: local Rust build
38
- join(projectRoot, 'relay-pty', 'target', 'release', 'relay-pty'),
39
- join(projectRoot, 'relay-pty', 'target', 'debug', 'relay-pty'),
40
- // Local build in cwd (for development)
41
- join(process.cwd(), 'relay-pty', 'target', 'release', 'relay-pty'),
42
- // Installed globally
43
- '/usr/local/bin/relay-pty',
44
- // In node_modules (when installed as dependency)
45
- join(process.cwd(), 'node_modules', 'agent-relay', 'bin', 'relay-pty'),
46
- ];
47
- for (const candidate of candidates) {
48
- if (existsSync(candidate)) {
49
- return candidate;
50
- }
51
- }
52
- return null;
31
+ return findRelayPtyBinaryUtil(__dirname);
53
32
  }
54
33
  // Active sessions
55
34
  const sessions = new Map();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/daemon",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Relay daemon server - agent coordination and message routing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,17 +22,17 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@agent-relay/protocol": "2.0.11",
26
- "@agent-relay/config": "2.0.11",
27
- "@agent-relay/storage": "2.0.11",
28
- "@agent-relay/bridge": "2.0.11",
29
- "@agent-relay/utils": "2.0.11",
30
- "@agent-relay/policy": "2.0.11",
31
- "@agent-relay/memory": "2.0.11",
32
- "@agent-relay/resiliency": "2.0.11",
33
- "@agent-relay/user-directory": "2.0.11",
34
- "@agent-relay/wrapper": "2.0.11",
35
- "@agent-relay/telemetry": "2.0.11",
25
+ "@agent-relay/protocol": "2.0.12",
26
+ "@agent-relay/config": "2.0.12",
27
+ "@agent-relay/storage": "2.0.12",
28
+ "@agent-relay/bridge": "2.0.12",
29
+ "@agent-relay/utils": "2.0.12",
30
+ "@agent-relay/policy": "2.0.12",
31
+ "@agent-relay/memory": "2.0.12",
32
+ "@agent-relay/resiliency": "2.0.12",
33
+ "@agent-relay/user-directory": "2.0.12",
34
+ "@agent-relay/wrapper": "2.0.12",
35
+ "@agent-relay/telemetry": "2.0.12",
36
36
  "ws": "^8.18.3",
37
37
  "better-sqlite3": "^12.6.2",
38
38
  "pg": "^8.16.3",
@@ -3422,6 +3422,38 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
3422
3422
  res.status(500).json({ error: 'Failed to write credential file' });
3423
3423
  }
3424
3424
  });
3425
+ /**
3426
+ * DELETE /api/credentials/apikey - Delete API key credential from user's home directory
3427
+ * Used by cloud API to clear provider credentials when disconnecting
3428
+ */
3429
+ app.delete('/api/credentials/apikey', express.json(), async (req, res) => {
3430
+ const { userId, provider } = req.body;
3431
+ if (!userId || typeof userId !== 'string') {
3432
+ return res.status(400).json({ error: 'userId is required' });
3433
+ }
3434
+ if (!provider || typeof provider !== 'string') {
3435
+ return res.status(400).json({ error: 'provider is required' });
3436
+ }
3437
+ // Sanitize for logging (prevent log injection)
3438
+ const safeProvider = String(provider).replace(/[\r\n]/g, '').substring(0, 20);
3439
+ const safeUserId = String(userId).replace(/[\r\n]/g, '').substring(0, 50);
3440
+ try {
3441
+ // Dynamically import to avoid loading user-directory in all cases
3442
+ const { getUserDirectoryService } = await import('@agent-relay/user-directory');
3443
+ const userDirService = getUserDirectoryService();
3444
+ const deletedPaths = userDirService.deleteProviderCredentials(userId, provider);
3445
+ console.log('[credentials] Deleted credentials', { provider: safeProvider, userId: safeUserId, count: deletedPaths.length });
3446
+ res.json({
3447
+ success: true,
3448
+ message: 'credentials deleted',
3449
+ deletedPaths,
3450
+ });
3451
+ }
3452
+ catch (err) {
3453
+ console.error('[credentials] Failed to delete credentials', { provider: safeProvider, userId: safeUserId, error: err instanceof Error ? err.message : String(err) });
3454
+ res.status(500).json({ error: 'Failed to delete credential files' });
3455
+ }
3456
+ });
3425
3457
  // ===== Metrics API =====
3426
3458
  /**
3427
3459
  * GET /api/metrics - JSON format metrics for dashboard
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/dashboard",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "Web dashboard for Agent Relay - optional package for visual agent coordination",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,17 +25,17 @@
25
25
  "test:watch": "vitest"
26
26
  },
27
27
  "dependencies": {
28
- "@agent-relay/protocol": "2.0.11",
29
- "@agent-relay/config": "2.0.11",
30
- "@agent-relay/storage": "2.0.11",
31
- "@agent-relay/bridge": "2.0.11",
32
- "@agent-relay/utils": "2.0.11",
33
- "@agent-relay/resiliency": "2.0.11",
34
- "@agent-relay/trajectory": "2.0.11",
35
- "@agent-relay/cloud": "2.0.11",
36
- "@agent-relay/daemon": "2.0.11",
37
- "@agent-relay/user-directory": "2.0.11",
38
- "@agent-relay/wrapper": "2.0.11",
28
+ "@agent-relay/protocol": "2.0.12",
29
+ "@agent-relay/config": "2.0.12",
30
+ "@agent-relay/storage": "2.0.12",
31
+ "@agent-relay/bridge": "2.0.12",
32
+ "@agent-relay/utils": "2.0.12",
33
+ "@agent-relay/resiliency": "2.0.12",
34
+ "@agent-relay/trajectory": "2.0.12",
35
+ "@agent-relay/cloud": "2.0.12",
36
+ "@agent-relay/daemon": "2.0.12",
37
+ "@agent-relay/user-directory": "2.0.12",
38
+ "@agent-relay/wrapper": "2.0.12",
39
39
  "express": "^5.2.1",
40
40
  "ws": "^8.18.3"
41
41
  },
@@ -158,6 +158,9 @@ export function WorkspaceSettingsPanel({
158
158
  google: true, // Default to terminal for Gemini - allows choosing OAuth or API key
159
159
  });
160
160
 
161
+ // Provider disconnection state
162
+ const [disconnectingProvider, setDisconnectingProvider] = useState<string | null>(null);
163
+
161
164
  // Repo sync state
162
165
  const [syncingRepoId, setSyncingRepoId] = useState<string | null>(null);
163
166
 
@@ -231,6 +234,34 @@ export function WorkspaceSettingsPanel({
231
234
  // ProviderAuthFlow will handle the rest when it mounts
232
235
  };
233
236
 
237
+ // Disconnect a provider
238
+ const handleDisconnectProvider = useCallback(async (provider: AIProvider) => {
239
+ const confirmed = window.confirm(
240
+ `Are you sure you want to disconnect ${provider.displayName}? This will remove the authentication and delete credential files from the workspace.`
241
+ );
242
+ if (!confirmed) return;
243
+
244
+ setDisconnectingProvider(provider.id);
245
+ setProviderError(null);
246
+
247
+ try {
248
+ const result = await cloudApi.disconnectProvider(provider.id, workspaceId);
249
+ if (result.success) {
250
+ setProviderStatus(prev => {
251
+ const updated = { ...prev };
252
+ delete updated[provider.id];
253
+ return updated;
254
+ });
255
+ } else {
256
+ setProviderError(result.error);
257
+ }
258
+ } catch (err) {
259
+ setProviderError(err instanceof Error ? err.message : 'Failed to disconnect provider');
260
+ } finally {
261
+ setDisconnectingProvider(null);
262
+ }
263
+ }, [workspaceId]);
264
+
234
265
  const submitApiKey = async (provider: AIProvider) => {
235
266
  if (!apiKeyInput.trim()) {
236
267
  setProviderError('Please enter an API key');
@@ -605,9 +636,19 @@ export function WorkspaceSettingsPanel({
605
636
  </div>
606
637
 
607
638
  {providerStatus[provider.id] ? (
608
- <div className="flex items-center gap-2 px-4 py-2 bg-success/15 rounded-full border border-success/30">
609
- <div className="w-2 h-2 rounded-full bg-success animate-pulse" />
610
- <span className="text-sm font-medium text-success">Connected</span>
639
+ <div className="flex items-center gap-3">
640
+ <div className="flex items-center gap-2 px-4 py-2 bg-success/15 rounded-full border border-success/30">
641
+ <div className="w-2 h-2 rounded-full bg-success animate-pulse" />
642
+ <span className="text-sm font-medium text-success">Connected</span>
643
+ </div>
644
+ <button
645
+ onClick={() => handleDisconnectProvider(provider)}
646
+ disabled={disconnectingProvider === provider.id}
647
+ className="px-3 py-2 text-xs font-medium text-error/80 hover:text-error hover:bg-error/10 rounded-lg border border-transparent hover:border-error/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
648
+ title={`Disconnect ${provider.displayName}`}
649
+ >
650
+ {disconnectingProvider === provider.id ? 'Disconnecting...' : 'Disconnect'}
651
+ </button>
611
652
  </div>
612
653
  ) : null}
613
654
  </div>