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
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"package.json"
],
"dependencies": {
"@agent-relay/cloud": "^6.0.17",
"@agent-relay/cloud": "^8.7.0",
"@agentworkforce/deploy": "workspace:*",
"@agentworkforce/persona-kit": "workspace:*",
"@agentworkforce/runtime": "workspace:*",
Expand Down
180 changes: 112 additions & 68 deletions packages/cli/src/deploy-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,33 +54,51 @@ function trapExit(throwOnExit = true): ExitTrap {
return trap;
}

test('runLogin uses cloud SDK auth, picks a workspace, and writes the active pointer (no token mint)', async () => {
test('runLogin uses cloud SDK auth, picks a workspace, and pins the canonical relay workspace key', async () => {
const calls: string[] = [];
const writes: unknown[] = [];
const pinned: unknown[] = [];
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO(),
ensureAuthenticated: async (apiUrl: string) => {
ensureCloudSession: async (options?: { apiUrl?: string }) => {
const apiUrl = options?.apiUrl ?? 'https://cloud.example.test';
calls.push(`ensure:${apiUrl}`);
return {
apiUrl,
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
auth: {
apiUrl,
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
},
client: { fetch: async () => new Response(null, { status: 500 }) }
};
},
createCloudApiClient() {
return {
async fetch(pathname: string) {
calls.push(`fetch:${pathname}`);
if (pathname === '/api/v1/workspaces/ws-1/resolve') {
return new Response(JSON.stringify({
key: 'rk_live_acme',
workspaceId: 'rw_1234abcd',
relaycastWorkspaceId: 'rw_1234abcd',
relayfileWorkspaceId: 'rf_acme',
relayauthWorkspaceId: 'ra_acme',
slug: 'acme',
name: 'Acme'
}), {
status: 200,
headers: { 'content-type': 'application/json' }
});
}
return new Response(JSON.stringify({ workspaces: [{ id: 'ws-1', slug: 'acme' }] }), {
status: 200,
headers: { 'content-type': 'application/json' }
});
}
};
},
writeActiveWorkspace: async (pointer: unknown) => {
writes.push(pointer);
setWorkspaceKey: (name: string, key: string) => {
pinned.push({ name, key });
}
});
const trap = trapExit(false);
Expand All @@ -89,46 +107,55 @@ test('runLogin uses cloud SDK auth, picks a workspace, and writes the active poi
assert.deepEqual(trap.exits, [0]);
assert.deepEqual(calls, [
'ensure:https://cloud.example.test',
'fetch:/api/v1/workspaces'
'fetch:/api/v1/workspaces',
'fetch:/api/v1/workspaces/ws-1/resolve'
]);
assert.deepEqual(writes, [{
workspace: 'acme',
workspaceSlug: 'acme',
workspaceId: 'ws-1',
cloudUrl: 'https://cloud.example.test'
}]);
assert.match(trap.stdout, /logged in: acme/);
assert.deepEqual(pinned, [{ name: 'Acme', key: 'rk_live_acme' }]);
assert.match(trap.stdout, /logged in: Acme/);
} finally {
trap.restore();
restoreDeps();
}
});

test('runLogin with --workspace skips the workspaces list, skips token mint, writes active pointer', async () => {
test('runLogin with --workspace skips the workspaces list and pins the resolved relay workspace key', async () => {
const calls: string[] = [];
const writes: unknown[] = [];
const pinned: unknown[] = [];
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO(),
ensureAuthenticated: async (apiUrl: string) => {
ensureCloudSession: async (options?: { apiUrl?: string }) => {
const apiUrl = options?.apiUrl ?? 'https://cloud.example.test';
calls.push(`ensure:${apiUrl}`);
return {
apiUrl,
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
auth: {
apiUrl,
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
},
client: { fetch: async () => new Response(null, { status: 500 }) }
};
},
createCloudApiClient() {
calls.push('createCloudApiClient');
return {
async fetch(pathname: string) {
calls.push(`fetch:${pathname}`);
return new Response('should not be called', { status: 500 });
return new Response(JSON.stringify({
key: 'rk_live_direct',
workspaceId: 'rw_5678abcd',
relaycastWorkspaceId: 'rw_5678abcd',
relayfileWorkspaceId: 'rf_direct',
relayauthWorkspaceId: 'ra_direct'
}), {
status: 200,
headers: { 'content-type': 'application/json' }
});
}
};
},
writeActiveWorkspace: async (pointer: unknown) => {
writes.push(pointer);
setWorkspaceKey: (name: string, key: string) => {
pinned.push({ name, key });
}
});
const trap = trapExit(false);
Expand All @@ -140,14 +167,14 @@ test('runLogin with --workspace skips the workspaces list, skips token mint, wri
'50587328-441d-4acb-b8f3-dbe1b3c5de99'
]);
assert.deepEqual(trap.exits, [0]);
assert.ok(
!calls.some((c) => c === 'createCloudApiClient' || c.startsWith('fetch:')),
`expected workspace-list to be skipped, got calls: ${JSON.stringify(calls)}`
);
assert.deepEqual(calls, ['ensure:https://cloud.example.test']);
assert.deepEqual(writes, [{
workspace: '50587328-441d-4acb-b8f3-dbe1b3c5de99',
cloudUrl: 'https://cloud.example.test'
assert.deepEqual(calls, [
'ensure:https://cloud.example.test',
'createCloudApiClient',
'fetch:/api/v1/workspaces/50587328-441d-4acb-b8f3-dbe1b3c5de99/resolve'
]);
assert.deepEqual(pinned, [{
name: '50587328-441d-4acb-b8f3-dbe1b3c5de99',
key: 'rk_live_direct'
}]);
assert.match(trap.stdout, /logged in: 50587328-441d-4acb-b8f3-dbe1b3c5de99/);
} finally {
Expand All @@ -159,11 +186,14 @@ test('runLogin with --workspace skips the workspaces list, skips token mint, wri
test('runLogin without --workspace surfaces a --workspace hint when the workspaces list returns 403', async () => {
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO(),
ensureAuthenticated: async (apiUrl: string) => ({
apiUrl,
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
ensureCloudSession: async (options?: { apiUrl?: string }) => ({
auth: {
apiUrl: options?.apiUrl ?? 'https://cloud.example.test',
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
},
client: { fetch: async () => new Response(null, { status: 500 }) }
}),
createCloudApiClient() {
return {
Expand All @@ -175,8 +205,8 @@ test('runLogin without --workspace surfaces a --workspace hint when the workspac
}
};
},
writeActiveWorkspace: async () => {
throw new Error('writeActiveWorkspace should not be called when listing fails');
setWorkspaceKey: () => {
throw new Error('setWorkspaceKey should not be called when listing fails');
}
});
const trap = trapExit(false);
Expand All @@ -194,11 +224,14 @@ test('runLogin without --workspace surfaces a --workspace hint when the workspac
test('runLogin without --workspace surfaces a no-workspaces message when the list comes back empty', async () => {
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO(),
ensureAuthenticated: async (apiUrl: string) => ({
apiUrl,
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
ensureCloudSession: async (options?: { apiUrl?: string }) => ({
auth: {
apiUrl: options?.apiUrl ?? 'https://cloud.example.test',
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
},
client: { fetch: async () => new Response(null, { status: 500 }) }
}),
createCloudApiClient() {
return {
Expand All @@ -210,8 +243,8 @@ test('runLogin without --workspace surfaces a no-workspaces message when the lis
}
};
},
writeActiveWorkspace: async () => {
throw new Error('writeActiveWorkspace should not be called when no workspaces');
setWorkspaceKey: () => {
throw new Error('setWorkspaceKey should not be called when no workspaces');
}
});
const trap = trapExit(false);
Expand All @@ -226,7 +259,7 @@ test('runLogin without --workspace surfaces a no-workspaces message when the lis
}
});

test('runLogout preserves shared cloud auth and clears the active pointer + legacy keychain token by default', async () => {
test('runLogout preserves shared cloud auth and clears compatibility workspace state by default', async () => {
const calls: string[] = [];
const restoreDeps = configureDeployCommandForTest({
clearStoredAuth: async () => {
Expand Down Expand Up @@ -347,40 +380,51 @@ test('parseDeployArgs: malformed --input exits with clean error', () => {
}
});

test('runLogin canonicalizes origin.agentrelay.cloud apiUrl before persisting active.json', async () => {
// ensureAuthenticated occasionally returns auth.apiUrl pointing at the
// SST origin-bypass hostname. If we persist that, every subsequent API
// call 401s because session cookies don't cross subdomains. The CLI
// must canonicalize before writing.
const writes: Array<{ cloudUrl?: string }> = [];
test('runLogin canonicalizes origin.agentrelay.cloud apiUrl before resolving the workspace', async () => {
const calls: string[] = [];
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO(),
ensureAuthenticated: async () => ({
apiUrl: 'https://origin.agentrelay.cloud',
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
ensureCloudSession: async () => ({
auth: {
apiUrl: 'https://origin.agentrelay.cloud',
accessToken: 'access',
refreshToken: 'refresh',
accessTokenExpiresAt: '2999-01-01T00:00:00.000Z'
},
client: { fetch: async () => new Response(null, { status: 500 }) }
}),
createCloudApiClient() {
createCloudApiClient(_auth, apiUrl) {
calls.push(`client:${apiUrl}`);
return {
async fetch(_pathname: string) {
async fetch(pathname: string) {
calls.push(`fetch:${pathname}`);
if (pathname === '/api/v1/workspaces/ws-1/resolve') {
return new Response(JSON.stringify({
key: 'rk_live_acme',
workspaceId: 'rw_1234abcd',
relaycastWorkspaceId: 'rw_1234abcd',
relayfileWorkspaceId: 'rf_acme',
relayauthWorkspaceId: 'ra_acme'
}), {
status: 200,
headers: { 'content-type': 'application/json' }
});
}
return new Response(JSON.stringify({ workspaces: [{ id: 'ws-1', slug: 'acme' }] }), {
status: 200,
headers: { 'content-type': 'application/json' }
});
}
};
},
writeActiveWorkspace: async (pointer: { cloudUrl?: string }) => {
writes.push(pointer);
}
setWorkspaceKey: () => {}
});
const trap = trapExit(false);
try {
await runLogin(['--cloud-url', 'https://agentrelay.com/cloud']);
assert.deepEqual(trap.exits, [0]);
assert.equal(writes.length, 1);
assert.equal(writes[0].cloudUrl, 'https://agentrelay.com/cloud');
assert.ok(calls.every((call) => !call.includes('origin.agentrelay.cloud')), calls.join('\n'));
assert.ok(calls.includes('client:https://agentrelay.com/cloud'));
} finally {
trap.restore();
restoreDeps();
Expand Down
Loading
Loading