Skip to content

Commit 0d0fed8

Browse files
committed
address feedback
1 parent b7faa86 commit 0d0fed8

9 files changed

Lines changed: 32 additions & 19 deletions

File tree

packages/dashboard-server/src/lib/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,26 @@
33
*/
44

55
import fs from 'fs';
6+
import os from 'node:os';
67
import path from 'path';
78
import type { Response } from 'express';
89
import { loadTeamsConfig } from '@agent-relay/config';
910
import type { DashboardChannel } from './types.js';
1011

12+
/**
13+
* Returns the current OS username, falling back to `fallback` if os.userInfo()
14+
* throws (e.g. in containers where the UID has no /etc/passwd entry).
15+
*/
16+
export function safeUsername(fallback = 'Dashboard'): string {
17+
try {
18+
const name = os.userInfo().username;
19+
if (name) return name;
20+
} catch {
21+
// no-op — fall through to fallback
22+
}
23+
return fallback;
24+
}
25+
1126
export const PHANTOM_OFFLINE_MAX_AGE_MS = 5 * 60 * 1000;
1227
export const SPAWNED_CACHE_TTL_MS = 3000;
1328
export const STANDALONE_WS_POLL_MS = 3000;

packages/dashboard-server/src/proxy-server-relay-config-refresh.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('Dashboard Server relay-config refresh', () => {
2424
});
2525

2626
it('reuses the refreshed in-memory token for later relay-config reads without any file persistence', async () => {
27-
const expectedProjectIdentity = path.basename(path.resolve(dataDir, '..'));
27+
const expectedProjectIdentity = os.userInfo().username;
2828
const getDashboardAgentToken = vi.fn()
2929
.mockResolvedValueOnce({
3030
token: 'agt_old',

packages/dashboard-server/src/proxy-server.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { createServer as createHttpServer, type Server } from 'http';
1212
import { WebSocketServer } from 'ws';
1313
import fs from 'fs';
1414
import path from 'path';
15-
import os from 'node:os';
1615
import { fileURLToPath } from 'url';
1716
import { registerMockRoutes } from './mocks/routes.js';
1817
import {
@@ -42,6 +41,7 @@ import {
4241
sendHtmlFileOrFallback,
4342
getBindHost,
4443
mapChannelForDashboard,
44+
safeUsername,
4545
} from './lib/utils.js';
4646
import {
4747
filterPhantomAgents,
@@ -210,11 +210,11 @@ export function createServer(options: DashboardServerOptions = {}): DashboardSer
210210
if (!inMemoryRelayApiKey) return null;
211211

212212
const baseUrl = process.env.RELAYCAST_API_URL || 'https://api.relaycast.dev';
213-
const projectIdentity = os.userInfo().username;
213+
const projectIdentity = safeUsername(path.basename(path.resolve(dataDir, '..')));
214214
return applyCachedAgentIdentity({
215215
apiKey: inMemoryRelayApiKey,
216216
baseUrl,
217-
projectIdentity: projectIdentity,
217+
projectIdentity,
218218
});
219219
};
220220

@@ -321,8 +321,7 @@ export function createServer(options: DashboardServerOptions = {}): DashboardSer
321321
}
322322

323323
const projectIdentity = config?.agentName?.trim()
324-
|| os.userInfo().username
325-
|| DASHBOARD_DISPLAY_NAME;
324+
|| safeUsername(DASHBOARD_DISPLAY_NAME);
326325
const senderInput = params.from?.trim() ?? '';
327326
const senderName = mode === 'proxy'
328327
? resolveIdentity(senderInput || projectIdentity, {

packages/dashboard-server/src/relaycast-provider.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ describe('relaycast-provider loadRelaycastConfig', () => {
226226

227227
expect(loaded).toMatchObject({
228228
apiKey: 'rk_test',
229-
projectIdentity: path.basename(path.resolve(dataDir, '..')),
229+
projectIdentity: os.userInfo().username,
230230
});
231231
expect(loaded?.agentName).toBeUndefined();
232232
expect(loaded?.agentToken).toBeUndefined();

packages/dashboard-server/src/relaycast-provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
*/
66

77
import path from 'path';
8-
import os from 'node:os';
98
import { RelayCast } from '@relaycast/sdk';
109
import { extractMessageId } from './lib/message-id.js';
10+
import { safeUsername } from './lib/utils.js';
1111
import type {
1212
AgentStatus,
1313
CreateChannelInput,
@@ -87,7 +87,7 @@ export function loadRelaycastConfig(dataDir: string): RelaycastConfig | null {
8787
const baseUrl = process.env.RELAYCAST_API_URL || DEFAULT_RELAYCAST_BASE_URL;
8888
const envApiKey = process.env.RELAY_API_KEY?.trim();
8989
if (envApiKey) {
90-
const projectIdentity = os.userInfo().username;
90+
const projectIdentity = safeUsername(path.basename(path.resolve(dataDir, '..')));
9191
return { apiKey: envApiKey, baseUrl, projectIdentity };
9292
}
9393

packages/dashboard-server/src/routes/channels.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* Channel route handlers: list, create, join, leave, messages, archive, etc.
33
*/
44

5-
import path from 'path';
65
import type { Express, Request, Response } from 'express';
76
import {
87
fetchChannelMembers,
@@ -20,10 +19,11 @@ import {
2019
normalizeChannelTarget,
2120
normalizeChannelName,
2221
parseInviteMembers,
22+
safeUsername,
2323
} from '../lib/utils.js';
2424

2525
export function registerChannelRoutes(app: Express, ctx: RouteContext): void {
26-
const projectName = path.basename(path.resolve(ctx.dataDir, '..')) || 'Dashboard';
26+
const projectName = safeUsername();
2727
app.get('/api/channels', async (_req: Request, res: Response) => {
2828
try {
2929
const channels = await ctx.getRelaycastChannels();

packages/dashboard-server/src/routes/health.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
* Health and keep-alive route handlers.
33
*/
44

5-
import path from 'node:path';
65
import type { Express, Request, Response } from 'express';
76
import type { RouteContext } from '../lib/types.js';
8-
import { countOnlineAgents } from '../lib/utils.js';
7+
import { countOnlineAgents, safeUsername } from '../lib/utils.js';
98
import { mockAgents } from '../mocks/fixtures.js';
109

1110
export function registerHealthRoutes(app: Express, ctx: RouteContext): void {
12-
const projectName = path.basename(path.resolve(ctx.dataDir, '..'));
11+
const projectName = safeUsername();
1312

1413
app.get('/health', (_req: Request, res: Response) => {
1514
res.json({

packages/dashboard-server/src/routes/relay-config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import crypto from 'crypto';
2-
import path from 'path';
1+
import crypto from 'node:crypto';
32
import type { Express, Request, Response } from 'express';
43
import type { RouteContext } from '../lib/types.js';
54
import { getDashboardAgentToken, getWriterClient } from '../relaycast-provider-helpers.js';
5+
import { safeUsername } from '../lib/utils.js';
66

77
export function registerRelayConfigRoutes(app: Express, ctx: RouteContext): void {
88
// Allow the workflow runner (or any local caller) to push a Relaycast API key
@@ -90,7 +90,7 @@ export function registerRelayConfigRoutes(app: Express, ctx: RouteContext): void
9090
// token was cached earlier in this process. The new token remains cached
9191
// in memory so future requests reuse it without any file persistence.
9292
let agentToken = forceRefresh ? undefined : config.agentToken;
93-
let agentName = config.agentName ?? path.basename(path.resolve(ctx.dataDir, '..'));
93+
let agentName = config.agentName ?? safeUsername();
9494

9595
if (!agentToken) {
9696
try {

packages/dashboard-server/src/routes/thread-replies.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
* Thread reply routes for proxy/standalone modes.
33
*/
44

5-
import path from 'path';
65
import type { Express, Request, Response } from 'express';
76
import { fetchAllMessages } from '../relaycast-provider.js';
87
import type { Message, RelaycastConfig } from '../relaycast-provider-types.js';
98
import { resolveIdentity } from '../lib/identity.js';
109
import type { RouteContext } from '../lib/types.js';
10+
import { safeUsername } from '../lib/utils.js';
1111

1212
function parseBeforeCursor(raw: unknown): number | undefined {
1313
if (typeof raw !== 'string' || raw.trim() === '') {
@@ -68,7 +68,7 @@ function resolveSenderName(
6868
}
6969

7070
export function registerThreadReplyRoutes(app: Express, ctx: RouteContext): void {
71-
const projectName = path.basename(path.resolve(ctx.dataDir, '..')) || 'Dashboard';
71+
const projectName = safeUsername();
7272

7373
app.get('/api/messages/:id/replies', async (req: Request, res: Response) => {
7474
const idParam = req.params.id;

0 commit comments

Comments
 (0)