Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions packages/cli/src/list-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,12 @@ export async function resolveDeploymentRequestContext(opts: {
workspace?: string;
cloudUrl?: string;
noPrompt?: boolean;
}): Promise<{ cloudUrl: string; workspace: string; token: string }> {
}): Promise<{
cloudUrl: string;
workspace: string;
token: string;
authSource?: 'env' | 'cloud-session';
}> {
const io = createTerminalIO();
const cloudUrl = resolveCloudUrl({
...(opts.cloudUrl ? { flag: opts.cloudUrl } : {})
Expand All @@ -360,7 +365,19 @@ export async function resolveDeploymentRequestContext(opts: {
if (!workspace) {
throw new Error('workspace is required: pass --workspace, set WORKFORCE_WORKSPACE_ID, or run `agentworkforce login`');
}
return { cloudUrl, workspace, token: auth.token };
const authSource = readAuthSource(auth);
return {
cloudUrl,
workspace,
token: auth.token,
...(authSource ? { authSource } : {})
};
}

function readAuthSource(value: unknown): 'env' | 'cloud-session' | undefined {
if (!value || typeof value !== 'object') return undefined;
const authSource = (value as { authSource?: unknown }).authSource;
return authSource === 'env' || authSource === 'cloud-session' ? authSource : undefined;
}
Comment on lines +368 to 381

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since auth is returned by resolveWorkspaceToken, which is typed to return WorkspaceAuthToken & { workspace?: string }, the authSource property is already strongly typed as 'env' | 'cloud-session' | undefined. We can directly access auth.authSource without needing the helper function readAuthSource and its runtime type checks. This simplifies the code and improves maintainability.

  return {
    cloudUrl,
    workspace,
    token: auth.token,
    ...(auth.authSource ? { authSource: auth.authSource } : {})
  };
}


export async function fetchDeployments(args: {
Expand Down
23 changes: 23 additions & 0 deletions packages/cli/src/trigger-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import {
buildTriggerUrl,
formatTriggerAuthHint,
formatTriggerResult,
parseTriggerArgs,
parseTriggerResponse
Expand Down Expand Up @@ -82,6 +83,28 @@ test('formatTriggerResult prints a concise human summary', () => {
);
});

test('formatTriggerAuthHint identifies env-token 403s', () => {
assert.match(
formatTriggerAuthHint({ authSource: 'env', workspace: 'rw_123' }),
/WORKFORCE_WORKSPACE_TOKEN.*rw_123/
);
assert.match(
formatTriggerAuthHint({ authSource: 'env', workspace: 'rw_123' }),
/unset WORKFORCE_WORKSPACE_TOKEN/
);
});

test('formatTriggerAuthHint identifies cloud-session 403s', () => {
assert.match(
formatTriggerAuthHint({ authSource: 'cloud-session', workspace: 'rw_123' }),
/Agent Relay cloud session.*rw_123/
);
assert.match(
formatTriggerAuthHint({ authSource: 'cloud-session', workspace: 'rw_123' }),
/agentworkforce login/
);
});

test('buildTriggerUrl preserves cloud base paths', () => {
assert.equal(
buildTriggerUrl({
Expand Down
25 changes: 24 additions & 1 deletion packages/cli/src/trigger-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,35 @@ export async function triggerDeployment(opts: TriggerOptions): Promise<TriggerRe
if (!res.ok) {
const body = await res.text().catch(() => '');
const hint = formatHttpErrorBody(body, { url: url.toString() });
throw new Error(`manual trigger failed: ${res.status}${hint ? ` ${hint}` : ''}`);
const authHint = res.status === 403 ? ` ${formatTriggerAuthHint(ctx)}` : '';
throw new Error(`manual trigger failed: ${res.status}${hint ? ` ${hint}` : ''}${authHint}`);
}

return parseTriggerResponse(await res.json(), opts.selector);
}

export function formatTriggerAuthHint(ctx: {
authSource?: 'env' | 'cloud-session';
workspace: string;
}): string {
if (ctx.authSource === 'env') {
return (
`Auth source: WORKFORCE_WORKSPACE_TOKEN for workspace ${ctx.workspace}. ` +
'If this token is stale or lacks trigger scope, unset WORKFORCE_WORKSPACE_TOKEN and WORKFORCE_WORKSPACE_ID, then run `agentworkforce login`.'
);
}
if (ctx.authSource === 'cloud-session') {
return (
`Auth source: Agent Relay cloud session for workspace ${ctx.workspace}. ` +
'Run `agentworkforce login` to refresh the session, or confirm this account has workspace access.'
);
}
return (
`Auth source: unknown for workspace ${ctx.workspace}. ` +
'Run `agentworkforce login`, or set WORKFORCE_WORKSPACE_ID and WORKFORCE_WORKSPACE_TOKEN explicitly.'
);
}

export function formatTriggerResult(result: TriggerResponse): string {
return (
`triggered: ${result.agentId}\n` +
Expand Down
4 changes: 2 additions & 2 deletions packages/deploy/src/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ test('resolveWorkspaceToken preserves complete WORKFORCE env credentials for CI'
io: createBufferedIO(),
noPrompt: true
}),
{ token: 'ci-token', workspace: 'rw_1234abcd' }
{ token: 'ci-token', workspace: 'rw_1234abcd', authSource: 'env' }
);
});
});
Expand All @@ -115,7 +115,7 @@ test('resolveWorkspaceToken lets --workspace pair with WORKFORCE_WORKSPACE_TOKEN
io: createBufferedIO(),
noPrompt: true
}),
{ token: 'ci-token', workspace: 'rw_5678abcd' }
{ token: 'ci-token', workspace: 'rw_5678abcd', authSource: 'env' }
);
});
});
Expand Down
7 changes: 5 additions & 2 deletions packages/deploy/src/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface WorkspaceAuthToken {
workspace?: string;
relayfileWorkspaceId?: string;
workspaceDescriptor?: ActiveWorkspaceDescriptor;
authSource?: 'env' | 'cloud-session';
}

export interface StoredWorkspaceLogin {
Expand Down Expand Up @@ -123,7 +124,8 @@ export async function resolveWorkspaceToken(args: {
if (envToken && (requestedWorkspace || envWorkspace)) {
return {
token: envToken,
workspace: requestedWorkspace || envWorkspace
workspace: requestedWorkspace || envWorkspace,
authSource: "env"
};
}

Expand All @@ -142,7 +144,8 @@ export async function resolveWorkspaceToken(args: {
token: session.auth.accessToken,
workspace: descriptor.relaycastWorkspaceId,
relayfileWorkspaceId: descriptor.relayfileWorkspaceId,
workspaceDescriptor: descriptor
workspaceDescriptor: descriptor,
authSource: "cloud-session"
};
}

Expand Down
Loading